From a55d75d0046e1c8c29d5f225850e291d5b9a1d7d Mon Sep 17 00:00:00 2001 From: Andreas Hindborg Date: Wed, 24 Sep 2025 14:39:24 +0200 Subject: [PATCH 0001/3902] rust: sync: add `SetOnce` Introduce the `SetOnce` type, a container that can only be written once. The container uses an internal atomic to synchronize writes to the internal value. Reviewed-by: Alice Ryhl Reviewed-by: Benno Lossin Signed-off-by: Andreas Hindborg --- rust/kernel/sync.rs | 2 + rust/kernel/sync/set_once.rs | 125 +++++++++++++++++++++++++++++++++++ 2 files changed, 127 insertions(+) create mode 100644 rust/kernel/sync/set_once.rs diff --git a/rust/kernel/sync.rs b/rust/kernel/sync.rs index cf5b638a097d99..3f957ad3a9f0af 100644 --- a/rust/kernel/sync.rs +++ b/rust/kernel/sync.rs @@ -20,6 +20,7 @@ mod locked_by; pub mod poll; pub mod rcu; mod refcount; +mod set_once; pub use arc::{Arc, ArcBorrow, UniqueArc}; pub use completion::Completion; @@ -29,6 +30,7 @@ pub use lock::mutex::{new_mutex, Mutex, MutexGuard}; pub use lock::spinlock::{new_spinlock, SpinLock, SpinLockGuard}; pub use locked_by::LockedBy; pub use refcount::Refcount; +pub use set_once::SetOnce; /// Represents a lockdep class. It's a wrapper around C's `lock_class_key`. #[repr(transparent)] diff --git a/rust/kernel/sync/set_once.rs b/rust/kernel/sync/set_once.rs new file mode 100644 index 00000000000000..bdba601807d8b3 --- /dev/null +++ b/rust/kernel/sync/set_once.rs @@ -0,0 +1,125 @@ +// SPDX-License-Identifier: GPL-2.0 + +//! A container that can be initialized at most once. + +use super::atomic::{ + ordering::{Acquire, Relaxed, Release}, + Atomic, +}; +use core::{cell::UnsafeCell, mem::MaybeUninit}; + +/// A container that can be populated at most once. Thread safe. +/// +/// Once the a [`SetOnce`] is populated, it remains populated by the same object for the +/// lifetime `Self`. +/// +/// # Invariants +/// +/// - `init` may only increase in value. +/// - `init` may only assume values in the range `0..=2`. +/// - `init == 0` if and only if `value` is uninitialized. +/// - `init == 1` if and only if there is exactly one thread with exclusive +/// access to `self.value`. +/// - `init == 2` if and only if `value` is initialized and valid for shared +/// access. +/// +/// # Example +/// +/// ``` +/// # use kernel::sync::SetOnce; +/// let value = SetOnce::new(); +/// assert_eq!(None, value.as_ref()); +/// +/// let status = value.populate(42u8); +/// assert_eq!(true, status); +/// assert_eq!(Some(&42u8), value.as_ref()); +/// assert_eq!(Some(42u8), value.copy()); +/// +/// let status = value.populate(101u8); +/// assert_eq!(false, status); +/// assert_eq!(Some(&42u8), value.as_ref()); +/// assert_eq!(Some(42u8), value.copy()); +/// ``` +pub struct SetOnce { + init: Atomic, + value: UnsafeCell>, +} + +impl Default for SetOnce { + fn default() -> Self { + Self::new() + } +} + +impl SetOnce { + /// Create a new [`SetOnce`]. + /// + /// The returned instance will be empty. + pub const fn new() -> Self { + // INVARIANT: The container is empty and we initialize `init` to `0`. + Self { + value: UnsafeCell::new(MaybeUninit::uninit()), + init: Atomic::new(0), + } + } + + /// Get a reference to the contained object. + /// + /// Returns [`None`] if this [`SetOnce`] is empty. + pub fn as_ref(&self) -> Option<&T> { + if self.init.load(Acquire) == 2 { + // SAFETY: By the type invariants of `Self`, `self.init == 2` means that `self.value` + // is initialized and valid for shared access. + Some(unsafe { &*self.value.get().cast() }) + } else { + None + } + } + + /// Populate the [`SetOnce`]. + /// + /// Returns `true` if the [`SetOnce`] was successfully populated. + pub fn populate(&self, value: T) -> bool { + // INVARIANT: If the swap succeeds: + // - We increase `init`. + // - We write the valid value `1` to `init`. + // - Only one thread can succeed in this write, so we have exclusive access after the + // write. + if let Ok(0) = self.init.cmpxchg(0, 1, Relaxed) { + // SAFETY: By the type invariants of `Self`, the fact that we succeeded in writing `1` + // to `self.init` means we obtained exclusive access to `self.value`. + unsafe { core::ptr::write(self.value.get().cast(), value) }; + // INVARIANT: + // - We increase `init`. + // - We write the valid value `2` to `init`. + // - We release our exclusive access to `self.value` and it is now valid for shared + // access. + self.init.store(2, Release); + true + } else { + false + } + } + + /// Get a copy of the contained object. + /// + /// Returns [`None`] if the [`SetOnce`] is empty. + pub fn copy(&self) -> Option + where + T: Copy, + { + self.as_ref().copied() + } +} + +impl Drop for SetOnce { + fn drop(&mut self) { + if *self.init.get_mut() == 2 { + let value = self.value.get_mut(); + // SAFETY: By the type invariants of `Self`, `self.init == 2` means that `self.value` + // contains a valid value. We have exclusive access, as we hold a `mut` reference to + // `self`. + unsafe { value.assume_init_drop() }; + } + } +} From 906abfaf0c1d50c7ebed3516962ba2d7065a13d6 Mon Sep 17 00:00:00 2001 From: Andreas Hindborg Date: Wed, 24 Sep 2025 14:39:25 +0200 Subject: [PATCH 0002/3902] rust: str: add radix prefixed integer parsing functions Add the trait `ParseInt` for parsing string representations of integers where the string representations are optionally prefixed by a radix specifier. Implement the trait for the primitive integer types. Suggested-by: Benno Lossin Tested-by: Daniel Gomez Reviewed-by: Greg Kroah-Hartman Reviewed-by: Benno Lossin Signed-off-by: Andreas Hindborg --- rust/kernel/str.rs | 2 + rust/kernel/str/parse_int.rs | 148 +++++++++++++++++++++++++++++++++++ 2 files changed, 150 insertions(+) create mode 100644 rust/kernel/str/parse_int.rs diff --git a/rust/kernel/str.rs b/rust/kernel/str.rs index 5c74e5f776014a..a1a3581eb54656 100644 --- a/rust/kernel/str.rs +++ b/rust/kernel/str.rs @@ -13,6 +13,8 @@ use core::{ ops::{self, Deref, DerefMut, Index}, }; +pub mod parse_int; + /// Byte string without UTF-8 validity guarantee. #[repr(transparent)] pub struct BStr([u8]); diff --git a/rust/kernel/str/parse_int.rs b/rust/kernel/str/parse_int.rs new file mode 100644 index 00000000000000..48eb4c202984c3 --- /dev/null +++ b/rust/kernel/str/parse_int.rs @@ -0,0 +1,148 @@ +// SPDX-License-Identifier: GPL-2.0 + +//! Integer parsing functions. +//! +//! Integer parsing functions for parsing signed and unsigned integers +//! potentially prefixed with `0x`, `0o`, or `0b`. + +use crate::prelude::*; +use crate::str::BStr; +use core::ops::Deref; + +// Make `FromStrRadix` a public type with a private name. This seals +// `ParseInt`, that is, prevents downstream users from implementing the +// trait. +mod private { + use crate::prelude::*; + use crate::str::BStr; + + /// Trait that allows parsing a [`&BStr`] to an integer with a radix. + pub trait FromStrRadix: Sized { + /// Parse `src` to [`Self`] using radix `radix`. + fn from_str_radix(src: &BStr, radix: u32) -> Result; + + /// Tries to convert `value` into [`Self`] and negates the resulting value. + fn from_u64_negated(value: u64) -> Result; + } +} + +/// Extract the radix from an integer literal optionally prefixed with +/// one of `0x`, `0X`, `0o`, `0O`, `0b`, `0B`, `0`. +fn strip_radix(src: &BStr) -> (u32, &BStr) { + match src.deref() { + [b'0', b'x' | b'X', rest @ ..] => (16, rest.as_ref()), + [b'0', b'o' | b'O', rest @ ..] => (8, rest.as_ref()), + [b'0', b'b' | b'B', rest @ ..] => (2, rest.as_ref()), + // NOTE: We are including the leading zero to be able to parse + // literal `0` here. If we removed it as a radix prefix, we would + // not be able to parse `0`. + [b'0', ..] => (8, src), + _ => (10, src), + } +} + +/// Trait for parsing string representations of integers. +/// +/// Strings beginning with `0x`, `0o`, or `0b` are parsed as hex, octal, or +/// binary respectively. Strings beginning with `0` otherwise are parsed as +/// octal. Anything else is parsed as decimal. A leading `+` or `-` is also +/// permitted. Any string parsed by [`kstrtol()`] or [`kstrtoul()`] will be +/// successfully parsed. +/// +/// [`kstrtol()`]: https://docs.kernel.org/core-api/kernel-api.html#c.kstrtol +/// [`kstrtoul()`]: https://docs.kernel.org/core-api/kernel-api.html#c.kstrtoul +/// +/// # Examples +/// +/// ``` +/// # use kernel::str::parse_int::ParseInt; +/// # use kernel::b_str; +/// +/// assert_eq!(Ok(0u8), u8::from_str(b_str!("0"))); +/// +/// assert_eq!(Ok(0xa2u8), u8::from_str(b_str!("0xa2"))); +/// assert_eq!(Ok(-0xa2i32), i32::from_str(b_str!("-0xa2"))); +/// +/// assert_eq!(Ok(-0o57i8), i8::from_str(b_str!("-0o57"))); +/// assert_eq!(Ok(0o57i8), i8::from_str(b_str!("057"))); +/// +/// assert_eq!(Ok(0b1001i16), i16::from_str(b_str!("0b1001"))); +/// assert_eq!(Ok(-0b1001i16), i16::from_str(b_str!("-0b1001"))); +/// +/// assert_eq!(Ok(127i8), i8::from_str(b_str!("127"))); +/// assert!(i8::from_str(b_str!("128")).is_err()); +/// assert_eq!(Ok(-128i8), i8::from_str(b_str!("-128"))); +/// assert!(i8::from_str(b_str!("-129")).is_err()); +/// assert_eq!(Ok(255u8), u8::from_str(b_str!("255"))); +/// assert!(u8::from_str(b_str!("256")).is_err()); +/// ``` +pub trait ParseInt: private::FromStrRadix + TryFrom { + /// Parse a string according to the description in [`Self`]. + fn from_str(src: &BStr) -> Result { + match src.deref() { + [b'-', rest @ ..] => { + let (radix, digits) = strip_radix(rest.as_ref()); + // 2's complement values range from -2^(b-1) to 2^(b-1)-1. + // So if we want to parse negative numbers as positive and + // later multiply by -1, we have to parse into a larger + // integer. We choose `u64` as sufficiently large. + // + // NOTE: 128 bit integers are not available on all + // platforms, hence the choice of 64 bits. + let val = + u64::from_str_radix(core::str::from_utf8(digits).map_err(|_| EINVAL)?, radix) + .map_err(|_| EINVAL)?; + Self::from_u64_negated(val) + } + _ => { + let (radix, digits) = strip_radix(src); + Self::from_str_radix(digits, radix).map_err(|_| EINVAL) + } + } + } +} + +macro_rules! impl_parse_int { + ($($ty:ty),*) => { + $( + impl private::FromStrRadix for $ty { + fn from_str_radix(src: &BStr, radix: u32) -> Result { + <$ty>::from_str_radix(core::str::from_utf8(src).map_err(|_| EINVAL)?, radix) + .map_err(|_| EINVAL) + } + + fn from_u64_negated(value: u64) -> Result { + const ABS_MIN: u64 = { + #[allow(unused_comparisons)] + if <$ty>::MIN < 0 { + 1u64 << (<$ty>::BITS - 1) + } else { + 0 + } + }; + + if value > ABS_MIN { + return Err(EINVAL); + } + + if value == ABS_MIN { + return Ok(<$ty>::MIN); + } + + // SAFETY: The above checks guarantee that `value` fits into `Self`: + // - if `Self` is unsigned, then `ABS_MIN == 0` and thus we have returned above + // (either `EINVAL` or `MIN`). + // - if `Self` is signed, then we have that `0 <= value < ABS_MIN`. And since + // `ABS_MIN - 1` fits into `Self` by construction, `value` also does. + let value: Self = unsafe { value.try_into().unwrap_unchecked() }; + + Ok((!value).wrapping_add(1)) + } + } + + impl ParseInt for $ty {} + )* + }; +} + +impl_parse_int![i8, u8, i16, u16, i32, u32, i64, u64, isize, usize]; From d6b267405ba22822fd3a46f7a600bc97945dc138 Mon Sep 17 00:00:00 2001 From: Andreas Hindborg Date: Wed, 24 Sep 2025 14:39:26 +0200 Subject: [PATCH 0003/3902] rust: introduce module_param module Add types and traits for interfacing the C moduleparam API. Reviewed-by: Benno Lossin Signed-off-by: Andreas Hindborg --- rust/kernel/lib.rs | 1 + rust/kernel/module_param.rs | 181 ++++++++++++++++++++++++++++++++++++ 2 files changed, 182 insertions(+) create mode 100644 rust/kernel/module_param.rs diff --git a/rust/kernel/lib.rs b/rust/kernel/lib.rs index 3dd7bebe78882a..e81bbae91c8399 100644 --- a/rust/kernel/lib.rs +++ b/rust/kernel/lib.rs @@ -107,6 +107,7 @@ pub mod list; pub mod maple_tree; pub mod miscdevice; pub mod mm; +pub mod module_param; #[cfg(CONFIG_NET)] pub mod net; pub mod of; diff --git a/rust/kernel/module_param.rs b/rust/kernel/module_param.rs new file mode 100644 index 00000000000000..e7d5c930a467d2 --- /dev/null +++ b/rust/kernel/module_param.rs @@ -0,0 +1,181 @@ +// SPDX-License-Identifier: GPL-2.0 + +//! Support for module parameters. +//! +//! C header: [`include/linux/moduleparam.h`](srctree/include/linux/moduleparam.h) + +use crate::prelude::*; +use crate::str::BStr; +use bindings; +use kernel::sync::SetOnce; + +/// Newtype to make `bindings::kernel_param` [`Sync`]. +#[repr(transparent)] +#[doc(hidden)] +pub struct KernelParam(bindings::kernel_param); + +impl KernelParam { + #[doc(hidden)] + pub const fn new(val: bindings::kernel_param) -> Self { + Self(val) + } +} + +// SAFETY: C kernel handles serializing access to this type. We never access it +// from Rust module. +unsafe impl Sync for KernelParam {} + +/// Types that can be used for module parameters. +// NOTE: This trait is `Copy` because drop could produce unsoundness during teardown. +pub trait ModuleParam: Sized + Copy { + /// Parse a parameter argument into the parameter value. + fn try_from_param_arg(arg: &BStr) -> Result; +} + +/// Set the module parameter from a string. +/// +/// Used to set the parameter value at kernel initialization, when loading +/// the module or when set through `sysfs`. +/// +/// See `struct kernel_param_ops.set`. +/// +/// # Safety +/// +/// - If `val` is non-null then it must point to a valid null-terminated string that must be valid +/// for reads for the duration of the call. +/// - `param` must be a pointer to a `bindings::kernel_param` initialized by the rust module macro. +/// The pointee must be valid for reads for the duration of the call. +/// +/// # Note +/// +/// - The safety requirements are satisfied by C API contract when this function is invoked by the +/// module subsystem C code. +/// - Currently, we only support read-only parameters that are not readable from `sysfs`. Thus, this +/// function is only called at kernel initialization time, or at module load time, and we have +/// exclusive access to the parameter for the duration of the function. +/// +/// [`module!`]: macros::module +unsafe extern "C" fn set_param(val: *const c_char, param: *const bindings::kernel_param) -> c_int +where + T: ModuleParam, +{ + // NOTE: If we start supporting arguments without values, val _is_ allowed + // to be null here. + if val.is_null() { + // TODO: Use pr_warn_once available. + crate::pr_warn!("Null pointer passed to `module_param::set_param`"); + return EINVAL.to_errno(); + } + + // SAFETY: By function safety requirement, val is non-null, null-terminated + // and valid for reads for the duration of this function. + let arg = unsafe { CStr::from_char_ptr(val) }; + + crate::error::from_result(|| { + let new_value = T::try_from_param_arg(arg)?; + + // SAFETY: By function safety requirements, this access is safe. + let container = unsafe { &*((*param).__bindgen_anon_1.arg.cast::>()) }; + + container + .populate(new_value) + .then_some(0) + .ok_or(kernel::error::code::EEXIST) + }) +} + +macro_rules! impl_int_module_param { + ($ty:ident) => { + impl ModuleParam for $ty { + fn try_from_param_arg(arg: &BStr) -> Result { + <$ty as crate::str::parse_int::ParseInt>::from_str(arg) + } + } + }; +} + +impl_int_module_param!(i8); +impl_int_module_param!(u8); +impl_int_module_param!(i16); +impl_int_module_param!(u16); +impl_int_module_param!(i32); +impl_int_module_param!(u32); +impl_int_module_param!(i64); +impl_int_module_param!(u64); +impl_int_module_param!(isize); +impl_int_module_param!(usize); + +/// A wrapper for kernel parameters. +/// +/// This type is instantiated by the [`module!`] macro when module parameters are +/// defined. You should never need to instantiate this type directly. +/// +/// Note: This type is `pub` because it is used by module crates to access +/// parameter values. +pub struct ModuleParamAccess { + value: SetOnce, + default: T, +} + +// SAFETY: We only create shared references to the contents of this container, +// so if `T` is `Sync`, so is `ModuleParamAccess`. +unsafe impl Sync for ModuleParamAccess {} + +impl ModuleParamAccess { + #[doc(hidden)] + pub const fn new(default: T) -> Self { + Self { + value: SetOnce::new(), + default, + } + } + + /// Get a shared reference to the parameter value. + // Note: When sysfs access to parameters are enabled, we have to pass in a + // held lock guard here. + pub fn value(&self) -> &T { + self.value.as_ref().unwrap_or(&self.default) + } + + /// Get a mutable pointer to `self`. + /// + /// NOTE: In most cases it is not safe deref the returned pointer. + pub const fn as_void_ptr(&self) -> *mut c_void { + core::ptr::from_ref(self).cast_mut().cast() + } +} + +#[doc(hidden)] +/// Generate a static [`kernel_param_ops`](srctree/include/linux/moduleparam.h) struct. +/// +/// # Examples +/// +/// ```ignore +/// make_param_ops!( +/// /// Documentation for new param ops. +/// PARAM_OPS_MYTYPE, // Name for the static. +/// MyType // A type which implements [`ModuleParam`]. +/// ); +/// ``` +macro_rules! make_param_ops { + ($ops:ident, $ty:ty) => { + #[doc(hidden)] + pub static $ops: $crate::bindings::kernel_param_ops = $crate::bindings::kernel_param_ops { + flags: 0, + set: Some(set_param::<$ty>), + get: None, + free: None, + }; + }; +} + +make_param_ops!(PARAM_OPS_I8, i8); +make_param_ops!(PARAM_OPS_U8, u8); +make_param_ops!(PARAM_OPS_I16, i16); +make_param_ops!(PARAM_OPS_U16, u16); +make_param_ops!(PARAM_OPS_I32, i32); +make_param_ops!(PARAM_OPS_U32, u32); +make_param_ops!(PARAM_OPS_I64, i64); +make_param_ops!(PARAM_OPS_U64, u64); +make_param_ops!(PARAM_OPS_ISIZE, isize); +make_param_ops!(PARAM_OPS_USIZE, usize); From 515d52ea76bc77ac14c3ca49f9c5622c1a3ff450 Mon Sep 17 00:00:00 2001 From: Andreas Hindborg Date: Wed, 24 Sep 2025 14:39:27 +0200 Subject: [PATCH 0004/3902] rust: module: use a reference in macros::module::module When we add parameter support to the module macro, we want to be able to pass a reference to `ModuleInfo` to a helper function. That is not possible when we move out of the local `modinfo`. So change the function to access the local via reference rather than value. Reviewed-by: Benno Lossin Signed-off-by: Andreas Hindborg --- rust/macros/module.rs | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/rust/macros/module.rs b/rust/macros/module.rs index 5ee54a00c0b656..cbf3ac0a8f7ba0 100644 --- a/rust/macros/module.rs +++ b/rust/macros/module.rs @@ -176,23 +176,23 @@ pub(crate) fn module(ts: TokenStream) -> TokenStream { // Rust does not allow hyphens in identifiers, use underscore instead. let ident = info.name.replace('-', "_"); let mut modinfo = ModInfoBuilder::new(ident.as_ref()); - if let Some(authors) = info.authors { + if let Some(authors) = &info.authors { for author in authors { - modinfo.emit("author", &author); + modinfo.emit("author", author); } } - if let Some(description) = info.description { - modinfo.emit("description", &description); + if let Some(description) = &info.description { + modinfo.emit("description", description); } modinfo.emit("license", &info.license); - if let Some(aliases) = info.alias { + if let Some(aliases) = &info.alias { for alias in aliases { - modinfo.emit("alias", &alias); + modinfo.emit("alias", alias); } } - if let Some(firmware) = info.firmware { + if let Some(firmware) = &info.firmware { for fw in firmware { - modinfo.emit("firmware", &fw); + modinfo.emit("firmware", fw); } } From b01f3dffaab0430bf200b53f84010b1ab869f503 Mon Sep 17 00:00:00 2001 From: Andreas Hindborg Date: Wed, 24 Sep 2025 14:39:28 +0200 Subject: [PATCH 0005/3902] rust: module: update the module macro with module parameter support Allow module parameters to be declared in the rust `module!` macro. Reviewed-by: Benno Lossin Signed-off-by: Andreas Hindborg --- rust/macros/helpers.rs | 25 ++++++ rust/macros/lib.rs | 31 +++++++ rust/macros/module.rs | 178 ++++++++++++++++++++++++++++++++++++++--- 3 files changed, 224 insertions(+), 10 deletions(-) diff --git a/rust/macros/helpers.rs b/rust/macros/helpers.rs index e2602be402c105..365d7eb499c085 100644 --- a/rust/macros/helpers.rs +++ b/rust/macros/helpers.rs @@ -10,6 +10,17 @@ pub(crate) fn try_ident(it: &mut token_stream::IntoIter) -> Option { } } +pub(crate) fn try_sign(it: &mut token_stream::IntoIter) -> Option { + let peek = it.clone().next(); + match peek { + Some(TokenTree::Punct(punct)) if punct.as_char() == '-' => { + let _ = it.next(); + Some(punct.as_char()) + } + _ => None, + } +} + pub(crate) fn try_literal(it: &mut token_stream::IntoIter) -> Option { if let Some(TokenTree::Literal(literal)) = it.next() { Some(literal.to_string()) @@ -103,3 +114,17 @@ pub(crate) fn file() -> String { proc_macro::Span::call_site().file() } } + +/// Parse a token stream of the form `expected_name: "value",` and return the +/// string in the position of "value". +/// +/// # Panics +/// +/// - On parse error. +pub(crate) fn expect_string_field(it: &mut token_stream::IntoIter, expected_name: &str) -> String { + assert_eq!(expect_ident(it), expected_name); + assert_eq!(expect_punct(it), ':'); + let string = expect_string(it); + assert_eq!(expect_punct(it), ','); + string +} diff --git a/rust/macros/lib.rs b/rust/macros/lib.rs index fa847cf3a9b5f4..2fb520dc930af4 100644 --- a/rust/macros/lib.rs +++ b/rust/macros/lib.rs @@ -28,6 +28,30 @@ use proc_macro::TokenStream; /// The `type` argument should be a type which implements the [`Module`] /// trait. Also accepts various forms of kernel metadata. /// +/// The `params` field describe module parameters. Each entry has the form +/// +/// ```ignore +/// parameter_name: type { +/// default: default_value, +/// description: "Description", +/// } +/// ``` +/// +/// `type` may be one of +/// +/// - [`i8`] +/// - [`u8`] +/// - [`i8`] +/// - [`u8`] +/// - [`i16`] +/// - [`u16`] +/// - [`i32`] +/// - [`u32`] +/// - [`i64`] +/// - [`u64`] +/// - [`isize`] +/// - [`usize`] +/// /// C header: [`include/linux/moduleparam.h`](srctree/include/linux/moduleparam.h) /// /// [`Module`]: ../kernel/trait.Module.html @@ -44,6 +68,12 @@ use proc_macro::TokenStream; /// description: "My very own kernel module!", /// license: "GPL", /// alias: ["alternate_module_name"], +/// params: { +/// my_parameter: i64 { +/// default: 1, +/// description: "This parameter has a default of 1", +/// }, +/// }, /// } /// /// struct MyModule(i32); @@ -52,6 +82,7 @@ use proc_macro::TokenStream; /// fn init(_module: &'static ThisModule) -> Result { /// let foo: i32 = 42; /// pr_info!("I contain: {}\n", foo); +/// pr_info!("i32 param is: {}\n", module_parameters::my_parameter.read()); /// Ok(Self(foo)) /// } /// } diff --git a/rust/macros/module.rs b/rust/macros/module.rs index cbf3ac0a8f7ba0..d62e9c1e2a8982 100644 --- a/rust/macros/module.rs +++ b/rust/macros/module.rs @@ -26,6 +26,7 @@ struct ModInfoBuilder<'a> { module: &'a str, counter: usize, buffer: String, + param_buffer: String, } impl<'a> ModInfoBuilder<'a> { @@ -34,10 +35,11 @@ impl<'a> ModInfoBuilder<'a> { module, counter: 0, buffer: String::new(), + param_buffer: String::new(), } } - fn emit_base(&mut self, field: &str, content: &str, builtin: bool) { + fn emit_base(&mut self, field: &str, content: &str, builtin: bool, param: bool) { let string = if builtin { // Built-in modules prefix their modinfo strings by `module.`. format!( @@ -51,8 +53,14 @@ impl<'a> ModInfoBuilder<'a> { format!("{field}={content}\0") }; + let buffer = if param { + &mut self.param_buffer + } else { + &mut self.buffer + }; + write!( - &mut self.buffer, + buffer, " {cfg} #[doc(hidden)] @@ -75,20 +83,119 @@ impl<'a> ModInfoBuilder<'a> { self.counter += 1; } - fn emit_only_builtin(&mut self, field: &str, content: &str) { - self.emit_base(field, content, true) + fn emit_only_builtin(&mut self, field: &str, content: &str, param: bool) { + self.emit_base(field, content, true, param) } - fn emit_only_loadable(&mut self, field: &str, content: &str) { - self.emit_base(field, content, false) + fn emit_only_loadable(&mut self, field: &str, content: &str, param: bool) { + self.emit_base(field, content, false, param) } fn emit(&mut self, field: &str, content: &str) { - self.emit_only_builtin(field, content); - self.emit_only_loadable(field, content); + self.emit_internal(field, content, false); + } + + fn emit_internal(&mut self, field: &str, content: &str, param: bool) { + self.emit_only_builtin(field, content, param); + self.emit_only_loadable(field, content, param); + } + + fn emit_param(&mut self, field: &str, param: &str, content: &str) { + let content = format!("{param}:{content}", param = param, content = content); + self.emit_internal(field, &content, true); + } + + fn emit_params(&mut self, info: &ModuleInfo) { + let Some(params) = &info.params else { + return; + }; + + for param in params { + let ops = param_ops_path(¶m.ptype); + + // Note: The spelling of these fields is dictated by the user space + // tool `modinfo`. + self.emit_param("parmtype", ¶m.name, ¶m.ptype); + self.emit_param("parm", ¶m.name, ¶m.description); + + write!( + self.param_buffer, + " + pub(crate) static {param_name}: + ::kernel::module_param::ModuleParamAccess<{param_type}> = + ::kernel::module_param::ModuleParamAccess::new({param_default}); + + const _: () = {{ + #[link_section = \"__param\"] + #[used] + static __{module_name}_{param_name}_struct: + ::kernel::module_param::KernelParam = + ::kernel::module_param::KernelParam::new( + ::kernel::bindings::kernel_param {{ + name: if ::core::cfg!(MODULE) {{ + ::kernel::c_str!(\"{param_name}\").as_bytes_with_nul() + }} else {{ + ::kernel::c_str!(\"{module_name}.{param_name}\") + .as_bytes_with_nul() + }}.as_ptr(), + // SAFETY: `__this_module` is constructed by the kernel at load + // time and will not be freed until the module is unloaded. + #[cfg(MODULE)] + mod_: unsafe {{ + core::ptr::from_ref(&::kernel::bindings::__this_module) + .cast_mut() + }}, + #[cfg(not(MODULE))] + mod_: ::core::ptr::null_mut(), + ops: core::ptr::from_ref(&{ops}), + perm: 0, // Will not appear in sysfs + level: -1, + flags: 0, + __bindgen_anon_1: ::kernel::bindings::kernel_param__bindgen_ty_1 {{ + arg: {param_name}.as_void_ptr() + }}, + }} + ); + }}; + ", + module_name = info.name, + param_type = param.ptype, + param_default = param.default, + param_name = param.name, + ops = ops, + ) + .unwrap(); + } + } +} + +fn param_ops_path(param_type: &str) -> &'static str { + match param_type { + "i8" => "::kernel::module_param::PARAM_OPS_I8", + "u8" => "::kernel::module_param::PARAM_OPS_U8", + "i16" => "::kernel::module_param::PARAM_OPS_I16", + "u16" => "::kernel::module_param::PARAM_OPS_U16", + "i32" => "::kernel::module_param::PARAM_OPS_I32", + "u32" => "::kernel::module_param::PARAM_OPS_U32", + "i64" => "::kernel::module_param::PARAM_OPS_I64", + "u64" => "::kernel::module_param::PARAM_OPS_U64", + "isize" => "::kernel::module_param::PARAM_OPS_ISIZE", + "usize" => "::kernel::module_param::PARAM_OPS_USIZE", + t => panic!("Unsupported parameter type {}", t), } } +fn expect_param_default(param_it: &mut token_stream::IntoIter) -> String { + assert_eq!(expect_ident(param_it), "default"); + assert_eq!(expect_punct(param_it), ':'); + let sign = try_sign(param_it); + let default = try_literal(param_it).expect("Expected default param value"); + assert_eq!(expect_punct(param_it), ','); + let mut value = sign.map(String::from).unwrap_or_default(); + value.push_str(&default); + value +} + #[derive(Debug, Default)] struct ModuleInfo { type_: String, @@ -98,6 +205,50 @@ struct ModuleInfo { description: Option, alias: Option>, firmware: Option>, + params: Option>, +} + +#[derive(Debug)] +struct Parameter { + name: String, + ptype: String, + default: String, + description: String, +} + +fn expect_params(it: &mut token_stream::IntoIter) -> Vec { + let params = expect_group(it); + assert_eq!(params.delimiter(), Delimiter::Brace); + let mut it = params.stream().into_iter(); + let mut parsed = Vec::new(); + + loop { + let param_name = match it.next() { + Some(TokenTree::Ident(ident)) => ident.to_string(), + Some(_) => panic!("Expected Ident or end"), + None => break, + }; + + assert_eq!(expect_punct(&mut it), ':'); + let param_type = expect_ident(&mut it); + let group = expect_group(&mut it); + assert_eq!(group.delimiter(), Delimiter::Brace); + assert_eq!(expect_punct(&mut it), ','); + + let mut param_it = group.stream().into_iter(); + let param_default = expect_param_default(&mut param_it); + let param_description = expect_string_field(&mut param_it, "description"); + expect_end(&mut param_it); + + parsed.push(Parameter { + name: param_name, + ptype: param_type, + default: param_default, + description: param_description, + }) + } + + parsed } impl ModuleInfo { @@ -112,6 +263,7 @@ impl ModuleInfo { "license", "alias", "firmware", + "params", ]; const REQUIRED_KEYS: &[&str] = &["type", "name", "license"]; let mut seen_keys = Vec::new(); @@ -137,6 +289,7 @@ impl ModuleInfo { "license" => info.license = expect_string_ascii(it), "alias" => info.alias = Some(expect_string_array(it)), "firmware" => info.firmware = Some(expect_string_array(it)), + "params" => info.params = Some(expect_params(it)), _ => panic!("Unknown key \"{key}\". Valid keys are: {EXPECTED_KEYS:?}."), } @@ -199,7 +352,9 @@ pub(crate) fn module(ts: TokenStream) -> TokenStream { // Built-in modules also export the `file` modinfo string. let file = std::env::var("RUST_MODFILE").expect("Unable to fetch RUST_MODFILE environmental variable"); - modinfo.emit_only_builtin("file", &file); + modinfo.emit_only_builtin("file", &file, false); + + modinfo.emit_params(&info); format!( " @@ -363,15 +518,18 @@ pub(crate) fn module(ts: TokenStream) -> TokenStream { __MOD.assume_init_drop(); }} }} - {modinfo} }} }} + mod module_parameters {{ + {params} + }} ", type_ = info.type_, name = info.name, ident = ident, modinfo = modinfo.buffer, + params = modinfo.param_buffer, initcall_section = ".initcall6.init" ) .parse() From fcfa0799e8ed250bba07dc4381aa8f507f17bcb2 Mon Sep 17 00:00:00 2001 From: Andreas Hindborg Date: Wed, 24 Sep 2025 14:39:29 +0200 Subject: [PATCH 0006/3902] rust: samples: add a module parameter to the rust_minimal sample Showcase the rust module parameter support by adding a module parameter to the `rust_minimal` sample. Reviewed-by: Benno Lossin Signed-off-by: Andreas Hindborg --- samples/rust/rust_minimal.rs | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/samples/rust/rust_minimal.rs b/samples/rust/rust_minimal.rs index 1fc7a1be6b6d75..8eb9583571d726 100644 --- a/samples/rust/rust_minimal.rs +++ b/samples/rust/rust_minimal.rs @@ -10,6 +10,12 @@ module! { authors: ["Rust for Linux Contributors"], description: "Rust minimal sample", license: "GPL", + params: { + test_parameter: i64 { + default: 1, + description: "This parameter has a default of 1", + }, + }, } struct RustMinimal { @@ -20,6 +26,10 @@ impl kernel::Module for RustMinimal { fn init(_module: &'static ThisModule) -> Result { pr_info!("Rust minimal sample (init)\n"); pr_info!("Am I built-in? {}\n", !cfg!(MODULE)); + pr_info!( + "test_parameter: {}\n", + *module_parameters::test_parameter.value() + ); let mut numbers = KVec::new(); numbers.push(72, GFP_KERNEL)?; From 3b1e595ffb606864f15205664aa3cbc143098278 Mon Sep 17 00:00:00 2001 From: Andreas Hindborg Date: Wed, 24 Sep 2025 14:39:30 +0200 Subject: [PATCH 0007/3902] modules: add rust modules files to MAINTAINERS The module subsystem people agreed to maintain rust support for modules [1]. Thus, add entries for relevant files to modules entry in MAINTAINERS. Link: https://lore.kernel.org/all/0d9e596a-5316-4e00-862b-fd77552ae4b5@suse.com/ [1] Acked-by: Daniel Gomez Signed-off-by: Andreas Hindborg --- MAINTAINERS | 2 ++ 1 file changed, 2 insertions(+) diff --git a/MAINTAINERS b/MAINTAINERS index e8f06145fb54c9..cd37418e4b92ca 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -17420,6 +17420,8 @@ F: include/linux/module*.h F: kernel/module/ F: lib/test_kmod.c F: lib/tests/module/ +F: rust/kernel/module_param.rs +F: rust/macros/module.rs F: scripts/module* F: tools/testing/selftests/kmod/ F: tools/testing/selftests/module/ From c5ac51f71d8ec3ce9d5b88a274adb19543e8b1bf Mon Sep 17 00:00:00 2001 From: Asahi Lina Date: Mon, 3 Feb 2025 06:11:57 +0900 Subject: [PATCH 0008/3902] rust: io: mem: Add Mem abstraction Signed-off-by: Janne Grunau --- rust/kernel/io/mem.rs | 103 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 103 insertions(+) diff --git a/rust/kernel/io/mem.rs b/rust/kernel/io/mem.rs index 6f99510bfc3a63..6b0daff2561087 100644 --- a/rust/kernel/io/mem.rs +++ b/rust/kernel/io/mem.rs @@ -3,6 +3,7 @@ //! Generic memory-mapped IO. use core::ops::Deref; +use core::ptr::NonNull; use crate::c_str; use crate::device::Bound; @@ -14,6 +15,7 @@ use crate::io::resource::Resource; use crate::io::Io; use crate::io::IoRaw; use crate::prelude::*; +use crate::types::declare_flags_type; /// An IO request for a specific device and resource. pub struct IoRequest<'a> { @@ -277,3 +279,104 @@ impl Deref for IoMem { unsafe { Io::from_raw(&self.io) } } } + +declare_flags_type! { + /// Flags to be used when remapping memory. + /// + /// They can be combined with the operators `|`, `&`, and `!`. + pub struct MemFlags(crate::ffi::c_ulong) = 0; +} + +impl MemFlags { + /// Matches the default mapping for System RAM on the architecture. + /// + /// This is usually a read-allocate write-back cache. Moreover, if this flag is specified and + /// the requested remap region is RAM, memremap() will bypass establishing a new mapping and + /// instead return a pointer into the direct map. + pub const WB: MemFlags = MemFlags(bindings::MEMREMAP_WB as _); + + /// Establish a mapping whereby writes either bypass the cache or are written through to memory + /// and never exist in a cache-dirty state with respect to program visibility. + /// + /// Attempts to map System RAM with this mapping type will fail. + pub const WT: MemFlags = MemFlags(bindings::MEMREMAP_WT as _); + /// Establish a writecombine mapping, whereby writes may be coalesced together (e.g. in the + /// CPU's write buffers), but is otherwise uncached. + /// + /// Attempts to map System RAM with this mapping type will fail. + pub const WC: MemFlags = MemFlags(bindings::MEMREMAP_WC as _); + + // Note: Skipping MEMREMAP_ENC/DEC since they are under-documented and have zero + // users outside of arch/x86. +} + +/// Represents a non-MMIO memory block. This is like [`IoMem`], but for cases where it is known +/// that the resource being mapped does not have I/O side effects. +// Invariants: +// `ptr` is a non-null and valid address of at least `usize` bytes and returned by a `memremap` +// call. +// ``` +pub struct Mem { + ptr: NonNull, + size: usize, +} + +impl Mem { + /// Tries to create a new instance of a memory block from a Resource. + /// + /// The resource described by `res` is mapped into the CPU's address space so that it can be + /// accessed directly. It is also consumed by this function so that it can't be mapped again + /// to a different address. + /// + /// If multiple caching flags are specified, the different mapping types will be attempted in + /// the order [`MemFlags::WB`], [`MemFlags::WT`], [`MemFlags::WC`]. + /// + /// # Flags + /// + /// * [`MemFlags::WB`]: Matches the default mapping for System RAM on the architecture. + /// This is usually a read-allocate write-back cache. Moreover, if this flag is specified and + /// the requested remap region is RAM, memremap() will bypass establishing a new mapping and + /// instead return a pointer into the direct map. + /// + /// * [`MemFlags::WT`]: Establish a mapping whereby writes either bypass the cache or are written + /// through to memory and never exist in a cache-dirty state with respect to program visibility. + /// Attempts to map System RAM with this mapping type will fail. + /// * [`MemFlags::WC`]: Establish a writecombine mapping, whereby writes may be coalesced together + /// (e.g. in the CPU's write buffers), but is otherwise uncached. Attempts to map System RAM with + /// this mapping type will fail. + /// + /// # Safety + /// + /// Callers must ensure that either (a) the resulting interface cannot be used to initiate DMA + /// operations, or (b) that DMA operations initiated via the returned interface use DMA handles + /// allocated through the `dma` module. + pub unsafe fn try_new(res: Resource, flags: MemFlags) -> Result { + let size: usize = res.size().try_into()?; + + let addr = unsafe { bindings::memremap(res.start(), size, flags.as_raw()) }; + let ptr = NonNull::new(addr).ok_or(ENOMEM)?; + // INVARIANT: `ptr` is non-null and was returned by `memremap`, so it is valid. + Ok(Self { ptr, size }) + } + + /// Returns the base address of the memory mapping as a raw pointer. + /// + /// It is up to the caller to use this pointer safely, depending on the requirements of the + /// hardware backing this memory block. + pub fn ptr(&self) -> *mut u8 { + self.ptr.cast().as_ptr() + } + + /// Returns the size of this mapped memory block. + pub fn size(&self) -> usize { + self.size + } +} + +impl Drop for Mem { + fn drop(&mut self) { + // SAFETY: By the type invariant, `self.ptr` is a value returned by a previous successful + // call to `memremap`. + unsafe { bindings::memunmap(self.ptr.as_ptr()) }; + } +} From 39c1ed48e657d02c4376e7a002b81745186ccd57 Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Sat, 21 Jun 2025 14:19:53 +0200 Subject: [PATCH 0009/3902] rust-for-linux: partially import "[PATCH v2 00/13] `Zeroable` improvements & bindings integration" import patch 7 and 8 of https://lore.kernel.org/rust-for-linux/20250523145125.523275-1-lossin@kernel.org/ Signed-off-by: Janne Grunau From d8b4bae5f84d8cae48f31e10862be42324fdf340 Mon Sep 17 00:00:00 2001 From: Asahi Lina Date: Wed, 8 May 2024 14:17:34 +0900 Subject: [PATCH 0010/3902] rust: init: Add default() utility function Initializer for types with Default::default() implementations in init context. This, by nature, only works for types which are not pinned. Signed-off-by: Asahi Lina --- rust/pin-init/src/lib.rs | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/rust/pin-init/src/lib.rs b/rust/pin-init/src/lib.rs index dd553212836e0d..a424e15788b601 100644 --- a/rust/pin-init/src/lib.rs +++ b/rust/pin-init/src/lib.rs @@ -1487,6 +1487,21 @@ pub unsafe trait PinnedDrop: __internal::HasPinData { fn drop(self: Pin<&mut Self>, only_call_from_drop: __internal::OnlyCallFromDrop); } +/// Create a new default T. +/// +/// The returned initializer will use Default::default to initialize the `slot`. +#[inline] +pub fn default() -> impl Init { + // SAFETY: Because `T: Default`, T cannot require pinning and + // we can just move the data into the slot. + unsafe { + init_from_closure(|slot: *mut T| { + *slot = Default::default(); + Ok(()) + }) + } +} + /// Marker trait for types that can be initialized by writing just zeroes. /// /// # Safety From 87ddaec30ac660617ff458601ecb2bbc9e9e7327 Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Fri, 21 Feb 2025 21:53:28 +0100 Subject: [PATCH 0011/3902] rust: error: Add ENODATA from uapi/asm-generic/errno.h Signed-off-by: Janne Grunau --- rust/kernel/error.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/rust/kernel/error.rs b/rust/kernel/error.rs index 1c0e0e241daa91..be7ca7b4ecc9ba 100644 --- a/rust/kernel/error.rs +++ b/rust/kernel/error.rs @@ -66,6 +66,7 @@ pub mod code { declare_err!(EPIPE, "Broken pipe."); declare_err!(EDOM, "Math argument out of domain of func."); declare_err!(ERANGE, "Math result not representable."); + declare_err!(ENODATA, "No data available."); declare_err!(EOVERFLOW, "Value too large for defined data type."); declare_err!(ETIMEDOUT, "Connection timed out."); declare_err!(ERESTARTSYS, "Restart the system call."); From 9d6efca1f1e65fe4eef06de9a839be8d20fde69b Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Fri, 21 Feb 2025 21:54:58 +0100 Subject: [PATCH 0012/3902] rust: error: Add ECANCELED from uapi/asm-generic/errno.h Signed-off-by: Janne Grunau --- rust/kernel/error.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/rust/kernel/error.rs b/rust/kernel/error.rs index be7ca7b4ecc9ba..a688e303f24b7e 100644 --- a/rust/kernel/error.rs +++ b/rust/kernel/error.rs @@ -69,6 +69,7 @@ pub mod code { declare_err!(ENODATA, "No data available."); declare_err!(EOVERFLOW, "Value too large for defined data type."); declare_err!(ETIMEDOUT, "Connection timed out."); + declare_err!(ECANCELED, "Operation Canceled."); declare_err!(ERESTARTSYS, "Restart the system call."); declare_err!(ERESTARTNOINTR, "System call was interrupted by a signal and will be restarted."); declare_err!(ERESTARTNOHAND, "Restart if no handler."); From e56b21bc5b29d9f3861a2ad449a86232eee9611a Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Sat, 1 Mar 2025 14:24:18 +0100 Subject: [PATCH 0013/3902] rust: error: Add ENOSYS from uapi/asm-generic/errno.h Signed-off-by: Janne Grunau --- rust/kernel/error.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/rust/kernel/error.rs b/rust/kernel/error.rs index a688e303f24b7e..ee3b58268b3d51 100644 --- a/rust/kernel/error.rs +++ b/rust/kernel/error.rs @@ -66,6 +66,7 @@ pub mod code { declare_err!(EPIPE, "Broken pipe."); declare_err!(EDOM, "Math argument out of domain of func."); declare_err!(ERANGE, "Math result not representable."); + declare_err!(ENOSYS, "Invalid system call number."); declare_err!(ENODATA, "No data available."); declare_err!(EOVERFLOW, "Value too large for defined data type."); declare_err!(ETIMEDOUT, "Connection timed out."); From cf57857aead87a1c07317fa8c07c76cbd9e242eb Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Sun, 22 Jun 2025 11:52:29 +0200 Subject: [PATCH 0014/3902] rust: device: Add support for locking the device Signed-off-by: Janne Grunau --- rust/helpers/device.c | 10 ++++++++++ rust/kernel/device.rs | 31 ++++++++++++++++++++++++++++++- 2 files changed, 40 insertions(+), 1 deletion(-) diff --git a/rust/helpers/device.c b/rust/helpers/device.c index 9a4316bafedfbc..41c3f4df8909e8 100644 --- a/rust/helpers/device.c +++ b/rust/helpers/device.c @@ -25,3 +25,13 @@ void rust_helper_dev_set_drvdata(struct device *dev, void *data) { dev_set_drvdata(dev, data); } + +void rust_helper_device_lock(struct device *dev) +{ + device_lock(dev); +} + +void rust_helper_device_unlock(struct device *dev) +{ + device_unlock(dev); +} diff --git a/rust/kernel/device.rs b/rust/kernel/device.rs index a849b7dde2fd91..0941b441dc9d99 100644 --- a/rust/kernel/device.rs +++ b/rust/kernel/device.rs @@ -7,7 +7,7 @@ use crate::{ bindings, fmt, sync::aref::ARef, - types::{ForeignOwnable, Opaque}, + types::{ForeignOwnable, NotThreadSafe, Opaque}, }; use core::{marker::PhantomData, ptr}; @@ -399,6 +399,35 @@ impl Device { // defined as a `#[repr(transparent)]` wrapper around `fwnode_handle`. Some(unsafe { &*fwnode_handle.cast() }) } + + /// Locks the [`Device`] for exclusive access. + pub fn lock(&self) -> Guard<'_, Ctx> { + // SAFETY: `self` is always valid by the type invariant. + unsafe { bindings::device_lock(self.as_raw()) }; + + Guard { + dev: self, + _not_send: NotThreadSafe, + } + } +} + +/// A lock guard. +/// +/// The lock is unlocked when the guard goes out of scope. +#[must_use = "the lock unlocks immediately when the guard is unused"] +pub struct Guard<'a, Ctx: DeviceContext = Normal> { + dev: &'a Device, + _not_send: NotThreadSafe, +} + +impl Drop for Guard<'_, Ctx> { + fn drop(&mut self) { + // SAFETY: + // - `self.xa.xa` is always valid by the type invariant. + // - The caller holds the lock, so it is safe to unlock it. + unsafe { bindings::device_unlock(self.dev.as_raw()) }; + } } // SAFETY: `Device` is a transparent wrapper of a type that doesn't depend on `Device`'s generic From b6fde4e79ee86b318c9ae13ceb7aa1c510be542e Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Sun, 22 Jun 2025 16:36:12 +0200 Subject: [PATCH 0015/3902] rust: device: Allow access to bound device TODO: ensure this can't be called with devices with Core/Bound context as the those will deadlock. Maybe use trylock? Signed-off-by: Janne Grunau --- rust/kernel/device.rs | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/rust/kernel/device.rs b/rust/kernel/device.rs index 0941b441dc9d99..6d2d5e222f02bb 100644 --- a/rust/kernel/device.rs +++ b/rust/kernel/device.rs @@ -410,6 +410,29 @@ impl Device { _not_send: NotThreadSafe, } } + + /// ensure Device is bound + pub fn is_bound(&self) -> Option> { + let guard = self.lock(); + if !unsafe { bindings::device_is_bound(self.as_raw()) } { + return None; + } + Some(guard) + } + + /// excute closure while the device is bound + pub fn while_bound_with(&self, f: F) -> Result + where + F: FnOnce(&Device) -> Result, + { + let _guard = self.lock(); + if unsafe { !bindings::device_is_bound(self.as_raw()) } { + return Err(ENODEV); + } + let ptr: *const Self = self; + let ptr = ptr.cast::>(); + f(unsafe { &*ptr }) + } } /// A lock guard. From 0860d5427a0de231c7150b5c9fd2514f8023df80 Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Mon, 7 Jul 2025 20:24:00 +0200 Subject: [PATCH 0016/3902] rust: kernel: platform: Add ::while_bound_with() Currently unused and unsafe (do not use while the device is already locked). Executes a closure while the devices is guaranteed to be bound. Signed-off-by: Janne Grunau --- rust/kernel/platform.rs | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/rust/kernel/platform.rs b/rust/kernel/platform.rs index 7205fe3416d36c..b197884ed3df93 100644 --- a/rust/kernel/platform.rs +++ b/rust/kernel/platform.rs @@ -265,6 +265,20 @@ impl Device { // returned by `platform_get_resource`. Some(unsafe { Resource::from_raw(resource) }) } + + /// excute closure while the device is bound + pub fn while_bound_with(&self, f: F) -> Result + where + F: FnOnce(&Device) -> Result, + { + let _guard = self.as_ref().lock(); + if unsafe { !bindings::device_is_bound(self.as_ref().as_raw()) } { + return Err(ENODEV); + } + let ptr: *const Self = self; + let ptr = ptr.cast::>(); + f(unsafe { &*ptr }) + } } impl Device { From 77560a746c01e885b6f96e03aaece27f3cca2b3e Mon Sep 17 00:00:00 2001 From: Asahi Lina Date: Wed, 8 May 2024 17:46:16 +0900 Subject: [PATCH 0017/3902] rust: allocator: Disable clippy::undocumented_unsafe_blocks lint The missing SAFETY comments should be fixed later... Signed-off-by: Asahi Lina --- rust/kernel/alloc/allocator.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/rust/kernel/alloc/allocator.rs b/rust/kernel/alloc/allocator.rs index 63bfb91b36712a..1a53412c458dd3 100644 --- a/rust/kernel/alloc/allocator.rs +++ b/rust/kernel/alloc/allocator.rs @@ -1,4 +1,6 @@ // SPDX-License-Identifier: GPL-2.0 +// FIXME +#![allow(clippy::undocumented_unsafe_blocks)] //! Allocator support. //! From cd35f21f19c26eb2588fe25b00b080797b836ff5 Mon Sep 17 00:00:00 2001 From: Sasha Finkelstein Date: Sat, 9 Nov 2024 18:35:46 +0100 Subject: [PATCH 0018/3902] rust: alloc: kbox: Add AsRef implementation to Box Signed-off-by: Sasha Finkelstein --- rust/kernel/alloc/kbox.rs | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/rust/kernel/alloc/kbox.rs b/rust/kernel/alloc/kbox.rs index 622b3529edfcbc..2684598cde4cbe 100644 --- a/rust/kernel/alloc/kbox.rs +++ b/rust/kernel/alloc/kbox.rs @@ -682,6 +682,16 @@ where } } +impl AsRef for Box +where + T: ?Sized, + A: Allocator, +{ + fn as_ref(&self) -> &T { + self + } +} + /// # Examples /// /// ``` From 67fb8b4a4ff8e995bbb5d5b134621eeece26d243 Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Sun, 10 Nov 2024 14:14:51 +0100 Subject: [PATCH 0019/3902] rust: alloc: vec: Add TryFrom trait Signed-off-by: Janne Grunau --- rust/kernel/alloc/kvec.rs | 48 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 48 insertions(+) diff --git a/rust/kernel/alloc/kvec.rs b/rust/kernel/alloc/kvec.rs index ac8d6f763ae81d..451627c9295042 100644 --- a/rust/kernel/alloc/kvec.rs +++ b/rust/kernel/alloc/kvec.rs @@ -1399,3 +1399,51 @@ mod tests { } } } + +// #[stable(feature = "array_try_from_vec", since = "1.48.0")] +impl TryFrom> for [T; N] { + type Error = Vec; + + /// Gets the entire contents of the `Vec` as an array, + /// if its size exactly matches that of the requested array. + /// + /// # Examples + /// + /// ``` + /// assert_eq!(vec![1, 2, 3].try_into(), Ok([1, 2, 3])); + /// assert_eq!(>::new().try_into(), Ok([])); + /// ``` + /// + /// If the length doesn't match, the input comes back in `Err`: + /// ``` + /// let r: Result<[i32; 4], _> = (0..10).collect::>().try_into(); + /// assert_eq!(r, Err(vec![0, 1, 2, 3, 4, 5, 6, 7, 8, 9])); + /// ``` + /// + /// If you're fine with just getting a prefix of the `Vec`, + /// you can call [`.truncate(N)`](Vec::truncate) first. + /// ``` + /// let mut v = String::from("hello world").into_bytes(); + /// v.sort(); + /// v.truncate(2); + /// let [a, b]: [_; 2] = v.try_into().unwrap(); + /// assert_eq!(a, b' '); + /// assert_eq!(b, b'd'); + /// ``` + fn try_from(mut vec: Vec) -> Result<[T; N], Vec> { + if vec.len() != N { + return Err(vec); + } + + // SAFETY: `.set_len(0)` is always sound. + unsafe { vec.dec_len(vec.len()) }; + + // SAFETY: A `Vec`'s pointer is always aligned properly, and + // the alignment the array needs is the same as the items. + // We checked earlier that we have sufficient items. + // The items will not double-drop as the `set_len` + // tells the `Vec` not to also drop them. + let array = unsafe { ptr::read(vec.as_ptr() as *const [T; N]) }; + Ok(array) + } +} From c7a6b95e6cad1ede4e809e76d33005156e19b008 Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Sat, 21 Jun 2025 21:02:51 +0200 Subject: [PATCH 0020/3902] rust: alloc: vec: Add dropped `set_len()` for ::drain() To keep in sync with Rust's std::Vec::drain() implementation keep set_len() around. Signed-off-by: Janne Grunau --- rust/kernel/alloc/kvec.rs | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/rust/kernel/alloc/kvec.rs b/rust/kernel/alloc/kvec.rs index 451627c9295042..51e0d47688a37e 100644 --- a/rust/kernel/alloc/kvec.rs +++ b/rust/kernel/alloc/kvec.rs @@ -192,6 +192,19 @@ where self.len } + /// Forcefully sets `self.len` to `new_len`. + /// + /// # Safety + /// + /// - `new_len` must be less than or equal to [`Self::capacity`]. + /// - If `new_len` is greater than `self.len`, all elements within the interval + /// [`self.len`,`new_len`) must be initialized. + #[inline] + pub unsafe fn set_len(&mut self, new_len: usize) { + debug_assert!(new_len <= self.capacity()); + self.len = new_len; + } + /// Increments `self.len` by `additional`. /// /// # Safety From 5cbbf39fe381ab39f7ec5597a3584ac131cbda8d Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Sun, 10 Nov 2024 14:17:29 +0100 Subject: [PATCH 0021/3902] rust: alloc: vec: Import .drain() / Drain from rust library Contains the implementation from https://github.com/rust-lang/rust/blob/1.82.0/library/alloc/src/vec/mod.rs and the Drain struct from https://github.com/rust-lang/rust/blob/1.82.0/library/alloc/src/vec/drain.rs modified for the Kernel. Signed-off-by: Janne Grunau --- rust/kernel/alloc/kvec.rs | 53 ++++++++++ rust/kernel/alloc/kvec/drain.rs | 181 ++++++++++++++++++++++++++++++++ rust/kernel/lib.rs | 15 +++ scripts/Makefile.build | 3 + 4 files changed, 252 insertions(+) create mode 100644 rust/kernel/alloc/kvec/drain.rs diff --git a/rust/kernel/alloc/kvec.rs b/rust/kernel/alloc/kvec.rs index 51e0d47688a37e..3802874ce153fb 100644 --- a/rust/kernel/alloc/kvec.rs +++ b/rust/kernel/alloc/kvec.rs @@ -19,12 +19,15 @@ use core::{ ops::DerefMut, ops::Index, ops::IndexMut, + ops::{Range, RangeBounds}, ptr, ptr::NonNull, slice, slice::SliceIndex, }; +mod drain; +use self::drain::Drain; mod errors; pub use self::errors::{InsertError, PushError, RemoveError}; @@ -746,6 +749,56 @@ where } self.truncate(num_kept); } + + /// Removes the specified range from the vector in bulk, returning all + /// removed elements as an iterator. If the iterator is dropped before + /// being fully consumed, it drops the remaining removed elements. + /// + /// The returned iterator keeps a mutable borrow on the vector to optimize + /// its implementation. + /// + /// # Panics + /// + /// Panics if the starting point is greater than the end point or if + /// the end point is greater than the length of the vector. + /// + /// # Leaking + /// + /// If the returned iterator goes out of scope without being dropped (due to + /// [`mem::forget`], for example), the vector may have lost and leaked + /// elements arbitrarily, including elements outside the range. + /// + /// # Examples + /// + /// ``` + /// let mut v = vec![1, 2, 3]; + /// let u: Vec<_> = v.drain(1..).collect(); + /// assert_eq!(v, &[1]); + /// assert_eq!(u, &[2, 3]); + /// + /// // A full range clears the vector, like `clear()` does + /// v.drain(..); + /// assert_eq!(v, &[]); + /// ``` + pub fn drain(&mut self, range: R) -> Drain<'_, T, A> + where + R: RangeBounds, + { + let len = self.len(); + let Range { start, end } = slice::range(range, ..len); + + unsafe { + // set self.vec length's to start, to be safe in case Drain is leaked + self.set_len(start); + let range_slice = slice::from_raw_parts(self.as_ptr().add(start), end - start); + Drain { + tail_start: end, + tail_len: len - end, + iter: range_slice.iter(), + vec: NonNull::from(self), + } + } + } } impl Vec { diff --git a/rust/kernel/alloc/kvec/drain.rs b/rust/kernel/alloc/kvec/drain.rs new file mode 100644 index 00000000000000..035878fd112843 --- /dev/null +++ b/rust/kernel/alloc/kvec/drain.rs @@ -0,0 +1,181 @@ +//! Rust standard library vendored code. +//! +//! The contents of this file come from the Rust standard library, hosted in +//! the repository, licensed under +//! "Apache-2.0 OR MIT" and adapted for kernel use. For copyright details, +//! see . +#![allow(clippy::undocumented_unsafe_blocks)] + +use core::fmt; +use core::iter::FusedIterator; +use core::mem::{self, SizedTypeProperties}; +use core::ptr::{self, NonNull}; +use core::slice::{self}; + +use super::{Allocator, Vec}; + +/// A draining iterator for `Vec`. +/// +/// This `struct` is created by [`Vec::drain`]. +/// See its documentation for more. +/// +/// # Example +/// +/// ``` +/// let mut v = vec![0, 1, 2]; +/// let iter: std::vec::Drain<'_, _> = v.drain(..); +/// ``` +// #[stable(feature = "drain", since = "1.6.0")] +pub struct Drain< + 'a, + T, + A: Allocator, + // #[unstable(feature = "allocator_api", issue = "32838")] A: Allocator + 'a = Global, +> { + /// Index of tail to preserve + pub(super) tail_start: usize, + /// Length of tail + pub(super) tail_len: usize, + /// Current remaining range to remove + pub(super) iter: slice::Iter<'a, T>, + pub(super) vec: NonNull>, +} + +// #[stable(feature = "collection_debug", since = "1.17.0")] +impl fmt::Debug for Drain<'_, T, A> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_tuple("Drain").field(&self.iter.as_slice()).finish() + } +} + +impl<'a, T, A: Allocator> Drain<'a, T, A> { + /// Returns the remaining items of this iterator as a slice. + /// + /// # Examples + /// + /// ``` + /// let mut vec = vec!['a', 'b', 'c']; + /// let mut drain = vec.drain(..); + /// assert_eq!(drain.as_slice(), &['a', 'b', 'c']); + /// let _ = drain.next().unwrap(); + /// assert_eq!(drain.as_slice(), &['b', 'c']); + /// ``` + #[must_use] + // #[stable(feature = "vec_drain_as_slice", since = "1.46.0")] + pub fn as_slice(&self) -> &[T] { + self.iter.as_slice() + } +} + +// #[stable(feature = "vec_drain_as_slice", since = "1.46.0")] +impl<'a, T, A: Allocator> AsRef<[T]> for Drain<'a, T, A> { + fn as_ref(&self) -> &[T] { + self.as_slice() + } +} + +// #[stable(feature = "drain", since = "1.6.0")] +unsafe impl Sync for Drain<'_, T, A> {} +// #[stable(feature = "drain", since = "1.6.0")] +unsafe impl Send for Drain<'_, T, A> {} + +// #[stable(feature = "drain", since = "1.6.0")] +impl Iterator for Drain<'_, T, A> { + type Item = T; + + #[inline] + fn next(&mut self) -> Option { + self.iter + .next() + .map(|elt| unsafe { ptr::read(elt as *const _) }) + } + + fn size_hint(&self) -> (usize, Option) { + self.iter.size_hint() + } +} + +// #[stable(feature = "drain", since = "1.6.0")] +impl DoubleEndedIterator for Drain<'_, T, A> { + #[inline] + fn next_back(&mut self) -> Option { + self.iter + .next_back() + .map(|elt| unsafe { ptr::read(elt as *const _) }) + } +} + +// #[stable(feature = "drain", since = "1.6.0")] +impl Drop for Drain<'_, T, A> { + fn drop(&mut self) { + /// Moves back the un-`Drain`ed elements to restore the original `Vec`. + struct DropGuard<'r, 'a, T, A: Allocator>(&'r mut Drain<'a, T, A>); + + impl<'r, 'a, T, A: Allocator> Drop for DropGuard<'r, 'a, T, A> { + fn drop(&mut self) { + if self.0.tail_len > 0 { + unsafe { + let source_vec = self.0.vec.as_mut(); + // memmove back untouched tail, update to new length + let start = source_vec.len(); + let tail = self.0.tail_start; + if tail != start { + let src = source_vec.as_ptr().add(tail); + let dst = source_vec.as_mut_ptr().add(start); + ptr::copy(src, dst, self.0.tail_len); + } + source_vec.set_len(start + self.0.tail_len); + } + } + } + } + + let iter = mem::take(&mut self.iter); + let drop_len = iter.len(); + + let mut vec = self.vec; + + if T::IS_ZST { + // ZSTs have no identity, so we don't need to move them around, we only need to drop the correct amount. + // this can be achieved by manipulating the Vec length instead of moving values out from `iter`. + unsafe { + let vec = vec.as_mut(); + let old_len = vec.len(); + vec.set_len(old_len + drop_len + self.tail_len); + vec.truncate(old_len + self.tail_len); + } + + return; + } + + // ensure elements are moved back into their appropriate places, even when drop_in_place panics + let _guard = DropGuard(self); + + if drop_len == 0 { + return; + } + + // as_slice() must only be called when iter.len() is > 0 because + // it also gets touched by vec::Splice which may turn it into a dangling pointer + // which would make it and the vec pointer point to different allocations which would + // lead to invalid pointer arithmetic below. + let drop_ptr = iter.as_slice().as_ptr(); + + unsafe { + // drop_ptr comes from a slice::Iter which only gives us a &[T] but for drop_in_place + // a pointer with mutable provenance is necessary. Therefore we must reconstruct + // it from the original vec but also avoid creating a &mut to the front since that could + // invalidate raw pointers to it which some unsafe code might rely on. + let vec_ptr = vec.as_mut().as_mut_ptr(); + #[cfg(not(version("1.87")))] + let drop_offset = drop_ptr.sub_ptr(vec_ptr); + #[cfg(version("1.87"))] + let drop_offset = drop_ptr.offset_from_unsigned(vec_ptr); + let to_drop = ptr::slice_from_raw_parts_mut(vec_ptr.add(drop_offset), drop_len); + ptr::drop_in_place(to_drop); + } + } +} + +// #[stable(feature = "fused", since = "1.26.0")] +impl FusedIterator for Drain<'_, T, A> {} diff --git a/rust/kernel/lib.rs b/rust/kernel/lib.rs index e81bbae91c8399..0f3ff1039c0f9e 100644 --- a/rust/kernel/lib.rs +++ b/rust/kernel/lib.rs @@ -16,6 +16,21 @@ // Please see https://github.com/Rust-for-Linux/linux/issues/2 for details on // the unstable features in use. // +// ============ start asahi downstream features =========== +#![feature(associated_type_defaults)] +// +#![feature(cfg_version)] +// +// Stable since Rust 1.87.0. +#![feature(ptr_sub_ptr)] +// +#![feature(sized_type_properties)] +// +#![feature(slice_range)] +// +#![cfg_attr(CONFIG_RUSTC_HAS_COERCE_POINTEE, feature(pin_coerce_unsized_trait))] +// ============ end asahi dowanstream features ============ +// // Stable since Rust 1.79.0. #![feature(generic_nonzero)] #![feature(inline_const)] diff --git a/scripts/Makefile.build b/scripts/Makefile.build index 52c08c4eb0b9af..372e6bbf784a63 100644 --- a/scripts/Makefile.build +++ b/scripts/Makefile.build @@ -319,6 +319,9 @@ $(obj)/%.lst: $(obj)/%.c FORCE # the unstable features in use. rust_allowed_features := asm_const,asm_goto,arbitrary_self_types,lint_reasons,offset_of_nested,raw_ref_op,used_with_arg +# additional rust features used by the downstream asahi kernel +rust_allowed_features := $(rust_allowed_features),ptr_sub_ptr + # `--out-dir` is required to avoid temporaries being created by `rustc` in the # current working directory, which may be not accessible in the out-of-tree # modules case. From 6fc9a778dcc8fd3fc00dd665bd1f1e7b3fcbdbcc Mon Sep 17 00:00:00 2001 From: Asahi Lina Date: Mon, 3 Feb 2025 05:28:26 +0900 Subject: [PATCH 0022/3902] rust: types: Add declare_flags_type() Add a helper macro that can be used to declare bitfield style types. Signed-off-by: Asahi Lina --- rust/kernel/types.rs | 83 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 83 insertions(+) diff --git a/rust/kernel/types.rs b/rust/kernel/types.rs index dc0a02f5c3cfc5..8be6daa291a7a8 100644 --- a/rust/kernel/types.rs +++ b/rust/kernel/types.rs @@ -443,3 +443,86 @@ pub type NotThreadSafe = PhantomData<*mut ()>; /// [`NotThreadSafe`]: type@NotThreadSafe #[allow(non_upper_case_globals)] pub const NotThreadSafe: NotThreadSafe = PhantomData; + +/// Helper macro to declare a bitfield style type. The type will automatically +/// gain boolean operator implementations, as well as the `as_raw()` and `contains()` +/// methods, Debug, Copy, Clone, and PartialEq implementations. +/// +/// Optionally, a default value can be specified with `= value` syntax, which +/// will add a Default trait implementation. +/// +/// # Examples +/// +/// ``` +/// declare_flags_type! { +/// /// Flags to be used for foo. +/// pub struct FooFlags(u32); +/// } +/// +/// declare_flags_type! { +/// /// Flags to be used for bar. +/// pub struct BarFlags(u32) = 0; +/// } +/// ``` +macro_rules! declare_flags_type ( + ( + $(#[$outer:meta])* + $v:vis struct $t:ident ( $base:ty ); + $($rest:tt)* + ) => { + $(#[$outer])* + #[derive(Debug, Clone, Copy, PartialEq)] + $v struct $t($base); + + impl $t { + /// Get the raw representation of this flag. + pub(crate) fn as_raw(self) -> $base { + self.0 + } + + /// Check whether `flags` is contained in `self`. + pub fn contains(self, flags: Self) -> bool { + (self & flags) == flags + } + } + + impl core::ops::BitOr for $t { + type Output = Self; + fn bitor(self, rhs: Self) -> Self::Output { + Self(self.0 | rhs.0) + } + } + + impl core::ops::BitAnd for $t { + type Output = Self; + fn bitand(self, rhs: Self) -> Self::Output { + Self(self.0 & rhs.0) + } + } + + impl core::ops::Not for $t { + type Output = Self; + fn not(self) -> Self::Output { + Self(!self.0) + } + } + }; + ( + $(#[$outer:meta])* + $v:vis struct $t:ident ( $base:ty ) = $default:expr; + $($rest:tt)* + ) => { + declare_flags_type! { + $(#[$outer])* + $v struct $t ($base); + $($rest)* + } + impl Default for $t { + fn default() -> Self { + Self($default) + } + } + }; +); + +pub(crate) use declare_flags_type; From 018041c7dabf989724b63b8b6970c97c17957db7 Mon Sep 17 00:00:00 2001 From: Asahi Lina Date: Mon, 3 Feb 2025 05:32:07 +0900 Subject: [PATCH 0023/3902] rust: alloc: Flags: Switch to declare_flags_type!() macro. Signed-off-by: Asahi Lina --- rust/kernel/alloc.rs | 48 +++++++++----------------------------------- 1 file changed, 9 insertions(+), 39 deletions(-) diff --git a/rust/kernel/alloc.rs b/rust/kernel/alloc.rs index e38720349dcf7e..719927971f5796 100644 --- a/rust/kernel/alloc.rs +++ b/rust/kernel/alloc.rs @@ -18,6 +18,8 @@ pub use self::kvec::KVec; pub use self::kvec::VVec; pub use self::kvec::Vec; +use crate::types::declare_flags_type; + /// Indicates an allocation error. #[derive(Copy, Clone, PartialEq, Eq, Debug)] pub struct AllocError; @@ -25,45 +27,13 @@ pub struct AllocError; use crate::error::{code::EINVAL, Result}; use core::{alloc::Layout, ptr::NonNull}; -/// Flags to be used when allocating memory. -/// -/// They can be combined with the operators `|`, `&`, and `!`. -/// -/// Values can be used from the [`flags`] module. -#[derive(Clone, Copy, PartialEq)] -pub struct Flags(u32); - -impl Flags { - /// Get the raw representation of this flag. - pub(crate) fn as_raw(self) -> u32 { - self.0 - } - - /// Check whether `flags` is contained in `self`. - pub fn contains(self, flags: Flags) -> bool { - (self & flags) == flags - } -} - -impl core::ops::BitOr for Flags { - type Output = Self; - fn bitor(self, rhs: Self) -> Self::Output { - Self(self.0 | rhs.0) - } -} - -impl core::ops::BitAnd for Flags { - type Output = Self; - fn bitand(self, rhs: Self) -> Self::Output { - Self(self.0 & rhs.0) - } -} - -impl core::ops::Not for Flags { - type Output = Self; - fn not(self) -> Self::Output { - Self(!self.0) - } +declare_flags_type! { + /// Flags to be used when allocating memory. + /// + /// They can be combined with the operators `|`, `&`, and `!`. + /// + /// Values can be used from the [`flags`] module. + pub struct Flags(u32); } /// Allocation flags. From 95f2ff10e68db755d5693d70e6e701e7fe3c9cc5 Mon Sep 17 00:00:00 2001 From: Asahi Lina Date: Fri, 28 Apr 2023 20:12:35 +0900 Subject: [PATCH 0024/3902] rust: kernel: lock: Add Lock::pin_init() This allows initializing a lock using pin_init!(), instead of requiring the inner data to be passed through the stack. Signed-off-by: Asahi Lina --- rust/kernel/sync/lock.rs | 27 +++++++++++++++++++++++++++ rust/kernel/sync/lock/mutex.rs | 13 +++++++++++++ 2 files changed, 40 insertions(+) diff --git a/rust/kernel/sync/lock.rs b/rust/kernel/sync/lock.rs index 27202beef90c88..7bedcd31a28a2b 100644 --- a/rust/kernel/sync/lock.rs +++ b/rust/kernel/sync/lock.rs @@ -8,6 +8,7 @@ use super::LockClassKey; use crate::{ str::CStr, + try_pin_init, types::{NotThreadSafe, Opaque, ScopeGuard}, }; use core::{cell::UnsafeCell, marker::PhantomPinned, pin::Pin}; @@ -115,6 +116,7 @@ pub struct Lock { _pin: PhantomPinned, /// The data protected by the lock. + #[pin] pub(crate) data: UnsafeCell, } @@ -138,6 +140,31 @@ impl Lock { }), }) } + + /// Constructs a new lock initialiser taking an initialiser. + pub fn pin_init( + t: impl PinInit, + name: &'static CStr, + key: &'static LockClassKey, + ) -> impl PinInit + where + E: core::convert::From, + { + try_pin_init!(Self { + // SAFETY: We are just forwarding the initialization across a + // cast away from UnsafeCell, so the pin_init_from_closure and + // __pinned_init() requirements are in sync. + data <- unsafe { pin_init::pin_init_from_closure(move |slot: *mut UnsafeCell| { + t.__pinned_init(slot as *mut T) + })}, + _pin: PhantomPinned, + // SAFETY: `slot` is valid while the closure is called and both `name` and `key` have + // static lifetimes so they live indefinitely. + state <- Opaque::ffi_init(|slot| unsafe { + B::init(slot, name.as_char_ptr(), key.as_ptr()) + }), + }? E) + } } impl Lock<(), B> { diff --git a/rust/kernel/sync/lock/mutex.rs b/rust/kernel/sync/lock/mutex.rs index 581cee7ab842ad..45a1c4c6483e90 100644 --- a/rust/kernel/sync/lock/mutex.rs +++ b/rust/kernel/sync/lock/mutex.rs @@ -17,6 +17,19 @@ macro_rules! new_mutex { } pub use new_mutex; +/// Creates a [`Mutex`] initialiser with the given name and a newly-created lock class, +/// given an initialiser for the inner type. +/// +/// It uses the name if one is given, otherwise it generates one based on the file name and line +/// number. +#[macro_export] +macro_rules! new_mutex_pinned { + ($inner:expr $(, $name:literal)? $(,)?) => { + $crate::sync::Mutex::pin_init( + $inner, $crate::optional_name!($($name)?), $crate::static_lock_class!()) + }; +} + /// A mutual exclusion primitive. /// /// Exposes the kernel's [`struct mutex`]. When multiple threads attempt to lock the same mutex, From b94b6d0643f87b9b66ac6df0aed8639933210657 Mon Sep 17 00:00:00 2001 From: Asahi Lina Date: Fri, 19 May 2023 16:22:09 +0900 Subject: [PATCH 0025/3902] rust: pin-init: Support type paths in pin_init!() and friends This makes directly initializing structures with a type name that isn't in the current scope work properly. Signed-off-by: Asahi Lina Signed-off-by: Janne Grunau --- rust/pin-init/src/lib.rs | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/rust/pin-init/src/lib.rs b/rust/pin-init/src/lib.rs index a424e15788b601..adb31130e98184 100644 --- a/rust/pin-init/src/lib.rs +++ b/rust/pin-init/src/lib.rs @@ -780,10 +780,10 @@ macro_rules! stack_try_pin_init { // module `macros` inside of `macros.rs`. #[macro_export] macro_rules! pin_init { - ($(&$this:ident in)? $t:ident $(::<$($generics:ty),* $(,)?>)? { + ($(&$this:ident in)? $t:ident $(::$p:ident)* $(::<$($generics:ty),* $(,)?>)? { $($fields:tt)* }) => { - $crate::try_pin_init!($(&$this in)? $t $(::<$($generics),*>)? { + $crate::try_pin_init!($(&$this in)? $t $(::$p)* $(::<$($generics),*>)? { $($fields)* }? ::core::convert::Infallible) }; @@ -831,12 +831,12 @@ macro_rules! pin_init { // module `macros` inside of `macros.rs`. #[macro_export] macro_rules! try_pin_init { - ($(&$this:ident in)? $t:ident $(::<$($generics:ty),* $(,)?>)? { + ($(&$this:ident in)? $t:ident $(::$p:ident)* $(::<$($generics:ty),* $(,)?>)? { $($fields:tt)* }? $err:ty) => { $crate::__init_internal!( @this($($this)?), - @typ($t $(::<$($generics),*>)? ), + @typ($t $(::$p)* $(::<$($generics),*>)? ), @fields($($fields)*), @error($err), @data(PinData, use_data), @@ -887,10 +887,10 @@ macro_rules! try_pin_init { // module `macros` inside of `macros.rs`. #[macro_export] macro_rules! init { - ($(&$this:ident in)? $t:ident $(::<$($generics:ty),* $(,)?>)? { + ($(&$this:ident in)? $t:ident $(::$p:ident)* $(::<$($generics:ty),* $(,)?>)? { $($fields:tt)* }) => { - $crate::try_init!($(&$this in)? $t $(::<$($generics),*>)? { + $crate::try_init!($(&$this in)? $t $(::$p)* $(::<$($generics),*>)? { $($fields)* }? ::core::convert::Infallible) } @@ -936,12 +936,12 @@ macro_rules! init { // module `macros` inside of `macros.rs`. #[macro_export] macro_rules! try_init { - ($(&$this:ident in)? $t:ident $(::<$($generics:ty),* $(,)?>)? { + ($(&$this:ident in)? $t:ident $(::$p:ident)* $(::<$($generics:ty),* $(,)?>)? { $($fields:tt)* }? $err:ty) => { $crate::__init_internal!( @this($($this)?), - @typ($t $(::<$($generics),*>)?), + @typ($t $(::$p)* $(::<$($generics),*>)?), @fields($($fields)*), @error($err), @data(InitData, /*no use_data*/), From 020e8f2d2306689080d1c12805a007fd13b89326 Mon Sep 17 00:00:00 2001 From: Asahi Lina Date: Fri, 19 May 2023 16:22:09 +0900 Subject: [PATCH 0026/3902] rust: kernel: init: Support type paths in try_init!() and try_pin_init!() This makes directly initializing structures with a type name that isn't in the current scope work properly. Signed-off-by: Asahi Lina Signed-off-by: Janne Grunau --- rust/kernel/init.rs | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/rust/kernel/init.rs b/rust/kernel/init.rs index 4949047af8d7fd..e34958ea9922a7 100644 --- a/rust/kernel/init.rs +++ b/rust/kernel/init.rs @@ -220,17 +220,17 @@ pub trait InPlaceInit: Sized { /// [`Error`]: crate::error::Error #[macro_export] macro_rules! try_init { - ($(&$this:ident in)? $t:ident $(::<$($generics:ty),* $(,)?>)? { + ($(&$this:ident in)? $t:ident $(::$p:ident)* $(::<$($generics:ty),* $(,)?>)? { $($fields:tt)* }) => { - ::pin_init::try_init!($(&$this in)? $t $(::<$($generics),*>)? { + ::pin_init::try_init!($(&$this in)? $t $(::$p)* $(::<$($generics),*>)? { $($fields)* }? $crate::error::Error) }; - ($(&$this:ident in)? $t:ident $(::<$($generics:ty),* $(,)?>)? { + ($(&$this:ident in)? $t:ident $(::$p:ident)* $(::<$($generics:ty),* $(,)?>)? { $($fields:tt)* }? $err:ty) => { - ::pin_init::try_init!($(&$this in)? $t $(::<$($generics),*>)? { + ::pin_init::try_init!($(&$this in)? $t $(::$p)* $(::<$($generics),*>)? { $($fields)* }? $err) }; @@ -280,17 +280,17 @@ macro_rules! try_init { /// [`Error`]: crate::error::Error #[macro_export] macro_rules! try_pin_init { - ($(&$this:ident in)? $t:ident $(::<$($generics:ty),* $(,)?>)? { + ($(&$this:ident in)? $t:ident $(::$p:ident)* $(::<$($generics:ty),* $(,)?>)? { $($fields:tt)* }) => { - ::pin_init::try_pin_init!($(&$this in)? $t $(::<$($generics),*>)? { + ::pin_init::try_pin_init!($(&$this in)? $t $(::$p)* $(::<$($generics),*>)? { $($fields)* }? $crate::error::Error) }; - ($(&$this:ident in)? $t:ident $(::<$($generics:ty),* $(,)?>)? { + ($(&$this:ident in)? $t:ident $(::$p:ident)* $(::<$($generics:ty),* $(,)?>)? { $($fields:tt)* }? $err:ty) => { - ::pin_init::try_pin_init!($(&$this in)? $t $(::<$($generics),*>)? { + ::pin_init::try_pin_init!($(&$this in)? $t $(::$p)* $(::<$($generics),*>)? { $($fields)* }? $err) }; From 2e4b5d2b4c782884eec989d69ce05553945b2189 Mon Sep 17 00:00:00 2001 From: Tamir Duberstein Date: Tue, 1 Jul 2025 12:27:17 -0400 Subject: [PATCH 0027/3902] rust: xarray: use the prelude Using the prelude is customary in the kernel crate. Signed-off-by: Tamir Duberstein --- rust/kernel/xarray.rs | 35 ++++++++++++++++++++--------------- 1 file changed, 20 insertions(+), 15 deletions(-) diff --git a/rust/kernel/xarray.rs b/rust/kernel/xarray.rs index a49d6db2884588..0eb42f9ba4db5b 100644 --- a/rust/kernel/xarray.rs +++ b/rust/kernel/xarray.rs @@ -5,17 +5,15 @@ //! C header: [`include/linux/xarray.h`](srctree/include/linux/xarray.h) use crate::{ - alloc, bindings, build_assert, - error::{Error, Result}, - ffi::c_void, + alloc, + prelude::*, types::{ForeignOwnable, NotThreadSafe, Opaque}, }; -use core::{iter, marker::PhantomData, pin::Pin, ptr::NonNull}; -use pin_init::{pin_data, pin_init, pinned_drop, PinInit}; +use core::{iter, marker::PhantomData, mem, ptr::NonNull}; /// An array which efficiently maps sparse integer indices to owned objects. /// -/// This is similar to a [`crate::alloc::kvec::Vec>`], but more efficient when there are +/// This is similar to a [`Vec>`], but more efficient when there are /// holes in the index space, and can be efficiently grown. /// /// # Invariants @@ -105,16 +103,23 @@ impl XArray { fn iter(&self) -> impl Iterator> + '_ { let mut index = 0; - // SAFETY: `self.xa` is always valid by the type invariant. - iter::once(unsafe { - bindings::xa_find(self.xa.get(), &mut index, usize::MAX, bindings::XA_PRESENT) - }) - .chain(iter::from_fn(move || { + core::iter::Iterator::chain( // SAFETY: `self.xa` is always valid by the type invariant. - Some(unsafe { - bindings::xa_find_after(self.xa.get(), &mut index, usize::MAX, bindings::XA_PRESENT) - }) - })) + iter::once(unsafe { + bindings::xa_find(self.xa.get(), &mut index, usize::MAX, bindings::XA_PRESENT) + }), + iter::from_fn(move || { + // SAFETY: `self.xa` is always valid by the type invariant. + Some(unsafe { + bindings::xa_find_after( + self.xa.get(), + &mut index, + usize::MAX, + bindings::XA_PRESENT, + ) + }) + }), + ) .map_while(|ptr| NonNull::new(ptr.cast())) } From 7937dd856df38976b27b2d0bafd6c1464326f3e2 Mon Sep 17 00:00:00 2001 From: Tamir Duberstein Date: Tue, 1 Jul 2025 12:27:18 -0400 Subject: [PATCH 0028/3902] rust: xarray: implement Default for AllocKind Most users are likely to want 0-indexed arrays. Clean up the documentation test accordingly. Signed-off-by: Tamir Duberstein --- rust/kernel/xarray.rs | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/rust/kernel/xarray.rs b/rust/kernel/xarray.rs index 0eb42f9ba4db5b..a1bed7f198fbcd 100644 --- a/rust/kernel/xarray.rs +++ b/rust/kernel/xarray.rs @@ -24,10 +24,11 @@ use core::{iter, marker::PhantomData, mem, ptr::NonNull}; /// # Examples /// /// ```rust -/// use kernel::alloc::KBox; -/// use kernel::xarray::{AllocKind, XArray}; +/// # use kernel::alloc::KBox; +/// # use kernel::xarray::XArray; +/// # use pin_init::stack_pin_init; /// -/// let xa = KBox::pin_init(XArray::new(AllocKind::Alloc1), GFP_KERNEL)?; +/// stack_pin_init!(let xa = XArray::new(Default::default())); /// /// let dead = KBox::new(0xdead, GFP_KERNEL)?; /// let beef = KBox::new(0xbeef, GFP_KERNEL)?; @@ -75,8 +76,10 @@ impl PinnedDrop for XArray { } /// Flags passed to [`XArray::new`] to configure the array's allocation tracking behavior. +#[derive(Default)] pub enum AllocKind { /// Consider the first element to be at index 0. + #[default] Alloc, /// Consider the first element to be at index 1. Alloc1, From b1ee949919c3d84448e16c99ebc48b2dd30a5ee9 Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Sun, 31 Aug 2025 21:00:49 +0200 Subject: [PATCH 0029/3902] nvmem: core: Fix OOB read for bit offsets of more than one byte When the bit offset is BITS_PER_BYTE or larger the read postion is advanced by `bytes_offset`. This is not taken into account in the per-byte read loop which still reads `cell->bytes` resuling in an out of bounds read of `bytes_offset` bytes. The information read OOB does not leak directly as the erroneously read bits are cleared. Detected by KASAN while looking for a use-after-free in simplefb.c. Fixes: 7a06ef7510779 ("nvmem: core: fix bit offsets of more than one byte") Signed-off-by: Janne Grunau --- drivers/nvmem/core.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/drivers/nvmem/core.c b/drivers/nvmem/core.c index 387c88c5525954..19be16943ee66e 100644 --- a/drivers/nvmem/core.c +++ b/drivers/nvmem/core.c @@ -1618,12 +1618,14 @@ static void nvmem_shift_read_buffer_in_place(struct nvmem_cell_entry *cell, void *p = *b++ >> bit_offset; /* setup rest of the bytes if any */ - for (i = 1; i < cell->bytes; i++) { + for (i = 1; i < (cell->bytes - bytes_offset); i++) { /* Get bits from next byte and shift them towards msb */ *p++ |= *b << (BITS_PER_BYTE - bit_offset); *p = *b++ >> bit_offset; } + /* point to end of the buffer unused bits will be cleared */ + p = buf + cell->bytes - 1; } else if (p != b) { memmove(p, b, cell->bytes - bytes_offset); p += cell->bytes - 1; From 336ac589cbfdd988832a244f285b3d66b842ec67 Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Thu, 25 Sep 2025 22:18:25 +0200 Subject: [PATCH 0030/3902] mfd: macsmc: Initialize mutex Struct apple_smc's mutex was not initialized before use. Surprisingly this only resulted in occasional NULL pointer dereferences in apple_smc_read() calls from the probe() functions of sub devices. Fixes: e038d985c9823 ("mfd: Add Apple Silicon System Management Controller") Signed-off-by: Janne Grunau --- drivers/mfd/macsmc.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/mfd/macsmc.c b/drivers/mfd/macsmc.c index e6cdae221f1d4e..3228e79c86eb56 100644 --- a/drivers/mfd/macsmc.c +++ b/drivers/mfd/macsmc.c @@ -413,6 +413,7 @@ static int apple_smc_probe(struct platform_device *pdev) if (!smc) return -ENOMEM; + mutex_init(&smc->mutex); smc->dev = &pdev->dev; smc->sram_base = devm_platform_get_and_ioremap_resource(pdev, 1, &smc->sram); if (IS_ERR(smc->sram_base)) From a3a6f3fdd22376320f508178fd8b89facc257576 Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Sun, 12 Oct 2025 14:45:11 +0200 Subject: [PATCH 0031/3902] bus: simple-pm-bus: Add "apple,*-pmgr" compatibles These devices are since commit 26769582bf35 ("mfd: syscon: Remove the platform driver support") without driver. There was not device specific code in the syscon driver so its removal did not cause any functional regressions. All control is done in child devices using syscon regmap. These devices use "simple-mfd" as fourth compatible. simple-pm-bus claims devices only based on the first compatible string so add all primary SoC specific apple,pmgr comaptibles. Cc: stable@vger.kernel.org Fixes: 26769582bf35 ("mfd: syscon: Remove the platform driver support") Signed-off-by: Janne Grunau --- drivers/bus/simple-pm-bus.c | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/drivers/bus/simple-pm-bus.c b/drivers/bus/simple-pm-bus.c index d8e029e7e53f72..5cae5581d4297c 100644 --- a/drivers/bus/simple-pm-bus.c +++ b/drivers/bus/simple-pm-bus.c @@ -142,6 +142,15 @@ static const struct of_device_id simple_pm_bus_of_match[] = { { .compatible = "simple-mfd", .data = ONLY_BUS }, { .compatible = "isa", .data = ONLY_BUS }, { .compatible = "arm,amba-bus", .data = ONLY_BUS }, + { .compatible = "apple,s5l8960x-pmgr", .data = ONLY_BUS }, + { .compatible = "apple,t7000-pmgr", .data = ONLY_BUS }, + { .compatible = "apple,s8000-pmgr", .data = ONLY_BUS }, + { .compatible = "apple,t8010-pmgr", .data = ONLY_BUS }, + { .compatible = "apple,t8015-pmgr", .data = ONLY_BUS }, + { .compatible = "apple,t8103-pmgr", .data = ONLY_BUS }, + { .compatible = "apple,t8112-pmgr", .data = ONLY_BUS }, + { .compatible = "apple,t6000-pmgr", .data = ONLY_BUS }, + { .compatible = "apple,t6020-pmgr", .data = ONLY_BUS }, { /* sentinel */ } }; MODULE_DEVICE_TABLE(of, simple_pm_bus_of_match); From 33649f322f32525e56e5fe1b0632289861616f4f Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Sun, 2 Nov 2025 13:17:35 +0100 Subject: [PATCH 0032/3902] media: videobuf2: Set vma_flags in vb2_dma_sg_mmap vb2_dma_contig sets VMA flags VM_DONTEXPAND and VM_DONTDUMP and I do not see a reason why vb2_dma_sg should behave differently. This avoids hitting `WARN_ON(!(vma->vm_flags & VM_DONTEXPAND));` in drm_gem_mmap_obj() during mmap() of an imported dma-buf from the out of tree Apple ISP camera capture driver which uses vb2_dma_sg_memops. gst-launch-1.0 v4l2src ! gtk4paintablesink [ 38.201528] ------------[ cut here ]------------ [ 38.202135] WARNING: CPU: 7 PID: 2362 at drivers/gpu/drm/drm_gem.c:1144 drm_gem_mmap_obj+0x1f8/0x210 [ 38.203278] Modules linked in: rfcomm snd_seq_dummy snd_hrtimer snd_seq snd_seq_device uinput nf_conntrack_netbios_ns nf_conntrack_broadcast nft_fib_inet nft_fib_ipv4 nft_fib_ipv6 nft_fib nft_reject_inet nf_reject_ipv6 nft_reject nft_ct nft_chain_nat nf_nat nf_conntrack nf_defrag_ipv6 nf_defrag_ipv4 nf_tables qrtr bnep nls_ascii i2c_dev loop fuse dm_multipath nfnetlink brcmfmac_wcc hid_magicmouse hci_bcm4377 brcmfmac brcmutil bluetooth ecdh_generic cfg80211 ecc btrfs xor xor_neon rfkill hid_apple raid6_pq joydev aop_als apple_nvmem_spmi industrialio snd_soc_aop apple_z2 snd_soc_cs42l84 tps6598x snd_soc_tas2764 macsmc_reboot spi_nor macsmc_hwmon rtc_macsmc gpio_macsmc macsmc_power regmap_spmi macsmc_input dockchannel_hid panel_summit appledrm nvme_apple dwc3 snd_soc_macaudio drm_client_lib nvme_core phy_apple_atc hwmon apple_sart apple_dockchannel macsmc apple_rtkit_helper spmi_apple_controller aop apple_wdt mfd_core nvmem_apple_efuses pinctrl_apple_gpio apple_isp apple_dcp videobuf2_dma_sg mux_core spi_apple [ 38.203300] videobuf2_memops i2c_pasemi_platform snd_soc_apple_mca videobuf2_v4l2 videodev clk_apple_nco videobuf2_common snd_pcm_dmaengine adpdrm asahi apple_admac adpdrm_mipi drm_dma_helper pwm_apple i2c_pasemi_core drm_display_helper mc cec apple_dart ofpart apple_soc_cpufreq leds_pwm phram [ 38.217677] CPU: 7 UID: 1000 PID: 2362 Comm: gst-launch-1.0 Tainted: G W 6.17.6+ #asahi-dev PREEMPT(full) [ 38.219040] Tainted: [W]=WARN [ 38.219398] Hardware name: Apple MacBook Pro (13-inch, M2, 2022) (DT) [ 38.220213] pstate: 21400005 (nzCv daif +PAN -UAO -TCO +DIT -SSBS BTYPE=--) [ 38.221088] pc : drm_gem_mmap_obj+0x1f8/0x210 [ 38.221643] lr : drm_gem_mmap_obj+0x78/0x210 [ 38.222178] sp : ffffc0008dc678e0 [ 38.222579] x29: ffffc0008dc678e0 x28: 0000000000042a97 x27: ffff8000b701b480 [ 38.223465] x26: 00000000000000fb x25: ffffc0008dc67d20 x24: ffffc0008dc67968 [ 38.224402] x23: ffff8000e3ca5600 x22: ffff8000265b7800 x21: ffff80003000c0c0 [ 38.225279] x20: 0000000000000000 x19: ffff8000b68c5200 x18: ffffc0008dc67968 [ 38.226151] x17: 0000000000000000 x16: 0000000000000000 x15: ffffc000810a30a8 [ 38.227042] x14: 00007fff637effff x13: 00005555de91ffff x12: 00007fff63293fff [ 38.227942] x11: 0000000000000000 x10: ffff8000184ecf08 x9 : ffffc0007a1900c8 [ 38.228824] x8 : ffffc0008dc67968 x7 : 0000000000000012 x6 : ffffc0015cf1c000 [ 38.229703] x5 : ffffc0008dc676a0 x4 : ffffc00081a27dc0 x3 : 0000000000000038 [ 38.230607] x2 : 0000000000000003 x1 : 0000000000000003 x0 : 00000000100000fb [ 38.231488] Call trace: [ 38.231806] drm_gem_mmap_obj+0x1f8/0x210 (P) [ 38.232342] drm_gem_mmap+0x140/0x260 [ 38.232813] __mmap_region+0x488/0x9a0 [ 38.233277] mmap_region+0xd0/0x148 [ 38.233703] do_mmap+0x350/0x5c0 [ 38.234148] vm_mmap_pgoff+0x14c/0x200 [ 38.234612] ksys_mmap_pgoff+0x150/0x208 [ 38.235107] __arm64_sys_mmap+0x34/0x50 [ 38.235611] invoke_syscall+0x50/0x120 [ 38.236075] el0_svc_common.constprop.0+0x48/0xf0 [ 38.236680] do_el0_svc+0x24/0x38 [ 38.237113] el0_svc+0x38/0x168 [ 38.237507] el0t_64_sync_handler+0xa0/0xe8 [ 38.238034] el0t_64_sync+0x198/0x1a0 [ 38.238491] ---[ end trace 0000000000000000 ]--- There were discussions in [1] at the end of 2023 that mmap() on imported dma-bufs should not be supported but as of v6.17 drm_gem_shmem_mmap() in drm_gem_shmem_helper.c still supports it. This might affect all gpu or accel drivers using drm_gem_shmem_mmap() or the wrapper drm_gem_shmem_object_mmap(). 1: https://lore.kernel.org/dri-devel/bc7f7844-0aa3-4802-b203-69d58e8be2fa@linux.intel.com/ Cc: stable@vger.kernel.org Fixes: 5ba3f757f059 ("[media] v4l: videobuf2: add DMA scatter/gather allocator") Signed-off-by: Janne Grunau --- drivers/media/common/videobuf2/videobuf2-dma-sg.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/media/common/videobuf2/videobuf2-dma-sg.c b/drivers/media/common/videobuf2/videobuf2-dma-sg.c index b3bf2173c14e1b..7c30731cb9a57b 100644 --- a/drivers/media/common/videobuf2/videobuf2-dma-sg.c +++ b/drivers/media/common/videobuf2/videobuf2-dma-sg.c @@ -345,6 +345,7 @@ static int vb2_dma_sg_mmap(void *buf_priv, struct vm_area_struct *vma) return err; } + vm_flags_set(vma, VM_DONTEXPAND | VM_DONTDUMP); /* * Use common vm_area operations to track buffer refcount. */ From 64079115aa0b8d411a5fcaf58cf52755dbab7119 Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Mon, 2 Jan 2023 19:20:55 +0100 Subject: [PATCH 0033/3902] soc: apple: rtkit: Add devm_apple_rtkit_free() To be used to free a RTKit interface while the associated device remains alive. Probably useless since it's unknown how or if RTKit based co-processors can be restarted. Signed-off-by: Janne Grunau --- drivers/soc/apple/rtkit.c | 6 ++++++ include/linux/soc/apple/rtkit.h | 7 +++++++ 2 files changed, 13 insertions(+) diff --git a/drivers/soc/apple/rtkit.c b/drivers/soc/apple/rtkit.c index b8d4da147d23f7..3a50d7a44595b1 100644 --- a/drivers/soc/apple/rtkit.c +++ b/drivers/soc/apple/rtkit.c @@ -958,6 +958,12 @@ struct apple_rtkit *devm_apple_rtkit_init(struct device *dev, void *cookie, } EXPORT_SYMBOL_GPL(devm_apple_rtkit_init); +void devm_apple_rtkit_free(struct device *dev, struct apple_rtkit *rtk) +{ + devm_release_action(dev, apple_rtkit_free_wrapper, rtk); +} +EXPORT_SYMBOL_GPL(devm_apple_rtkit_free); + MODULE_LICENSE("Dual MIT/GPL"); MODULE_AUTHOR("Sven Peter "); MODULE_DESCRIPTION("Apple RTKit driver"); diff --git a/include/linux/soc/apple/rtkit.h b/include/linux/soc/apple/rtkit.h index 736f530180179b..f43d08035a3439 100644 --- a/include/linux/soc/apple/rtkit.h +++ b/include/linux/soc/apple/rtkit.h @@ -78,6 +78,13 @@ struct apple_rtkit; struct apple_rtkit *devm_apple_rtkit_init(struct device *dev, void *cookie, const char *mbox_name, int mbox_idx, const struct apple_rtkit_ops *ops); +/* + * Frees internal RTKit state allocated by devm_apple_rtkit_init(). + * + * @dev: Pointer to the device node this coprocessor is assocated with + * @rtk: Internal RTKit state initialized by devm_apple_rtkit_init() + */ +void devm_apple_rtkit_free(struct device *dev, struct apple_rtkit *rtk); /* * Non-devm version of devm_apple_rtkit_init. Must be freed with From 77a101fce986142b58d0145317e3cb163c925a45 Mon Sep 17 00:00:00 2001 From: Hector Martin Date: Tue, 18 Apr 2023 04:19:44 +0900 Subject: [PATCH 0034/3902] soc: apple: Add driver for Apple PMGR misc controls Apple SoCs have PMGR blocks that control a bunch of power-related features. Besides the existing device power state controls (which are very uniform and handled by apple-pmgr-pwrstate), we also need to manage more random registers such as SoC-wide fabric and memory controller power states, which have a different interface. Add a driver for these kitchen sink controls. Right now it implements fabric and memory controller power state switching on system standby/s2idle, which saves about 1W of power or so on t60xx platforms. Signed-off-by: Hector Martin --- drivers/soc/apple/Kconfig | 8 ++ drivers/soc/apple/Makefile | 2 + drivers/soc/apple/apple-pmgr-misc.c | 158 ++++++++++++++++++++++++++++ 3 files changed, 168 insertions(+) create mode 100644 drivers/soc/apple/apple-pmgr-misc.c diff --git a/drivers/soc/apple/Kconfig b/drivers/soc/apple/Kconfig index ad67368892311b..2ea7a05cfb99f1 100644 --- a/drivers/soc/apple/Kconfig +++ b/drivers/soc/apple/Kconfig @@ -16,6 +16,14 @@ config APPLE_MAILBOX Say Y here if you have an Apple SoC. +config APPLE_PMGR_MISC + bool "Apple SoC PMGR miscellaneous support" + depends on PM + help + The PMGR block in Apple SoCs provides high-level power state + controls for SoC devices. This driver manages miscellaneous + power controls. + config APPLE_RTKIT tristate "Apple RTKit co-processor IPC protocol" depends on APPLE_MAILBOX diff --git a/drivers/soc/apple/Makefile b/drivers/soc/apple/Makefile index 4d9ab8f3037b71..40311a2ddaf2bf 100644 --- a/drivers/soc/apple/Makefile +++ b/drivers/soc/apple/Makefile @@ -3,6 +3,8 @@ obj-$(CONFIG_APPLE_MAILBOX) += apple-mailbox.o apple-mailbox-y = mailbox.o +obj-$(CONFIG_APPLE_PMGR_MISC) += apple-pmgr-misc.o + obj-$(CONFIG_APPLE_RTKIT) += apple-rtkit.o apple-rtkit-y = rtkit.o rtkit-crashlog.o diff --git a/drivers/soc/apple/apple-pmgr-misc.c b/drivers/soc/apple/apple-pmgr-misc.c new file mode 100644 index 00000000000000..e768f34aacc586 --- /dev/null +++ b/drivers/soc/apple/apple-pmgr-misc.c @@ -0,0 +1,158 @@ +// SPDX-License-Identifier: GPL-2.0-only OR MIT +/* + * Apple SoC PMGR device power state driver + * + * Copyright The Asahi Linux Contributors + */ + +#include +#include +#include +#include +#include +#include +#include + +#define APPLE_CLKGEN_PSTATE 0 +#define APPLE_CLKGEN_PSTATE_DESIRED GENMASK(3, 0) + +#define SYS_DEV_PSTATE_SUSPEND 1 + +enum sys_device { + DEV_FABRIC, + DEV_DCS, + DEV_MAX, +}; + +struct apple_pmgr_sys_device { + void __iomem *base; + u32 active_state; + u32 suspend_state; +}; + +struct apple_pmgr_misc { + struct device *dev; + struct apple_pmgr_sys_device devices[DEV_MAX]; +}; + +static void apple_pmgr_sys_dev_set_pstate(struct apple_pmgr_misc *misc, + enum sys_device dev, bool active) +{ + u32 pstate; + u32 val; + + if (!misc->devices[dev].base) + return; + + if (active) + pstate = misc->devices[dev].active_state; + else + pstate = misc->devices[dev].suspend_state; + + printk("set %d ps to pstate %d\n", dev, pstate); + + val = readl_relaxed(misc->devices[dev].base + APPLE_CLKGEN_PSTATE); + val &= ~APPLE_CLKGEN_PSTATE_DESIRED; + val |= FIELD_PREP(APPLE_CLKGEN_PSTATE_DESIRED, pstate); + writel_relaxed(val, misc->devices[dev].base); +} + +static int __maybe_unused apple_pmgr_misc_suspend_noirq(struct device *dev) +{ + struct apple_pmgr_misc *misc = dev_get_drvdata(dev); + int i; + + for (i = 0; i < DEV_MAX; i++) + apple_pmgr_sys_dev_set_pstate(misc, i, false); + + return 0; +} + +static int __maybe_unused apple_pmgr_misc_resume_noirq(struct device *dev) +{ + struct apple_pmgr_misc *misc = dev_get_drvdata(dev); + int i; + + for (i = 0; i < DEV_MAX; i++) + apple_pmgr_sys_dev_set_pstate(misc, i, true); + + return 0; +} + +static bool apple_pmgr_init_device(struct apple_pmgr_misc *misc, + enum sys_device dev, const char *device_name) +{ + void __iomem *base; + char name[32]; + u32 val; + + snprintf(name, sizeof(name), "%s-ps", device_name); + + base = devm_platform_ioremap_resource_byname( + to_platform_device(misc->dev), name); + if (!base) + return false; + + val = readl_relaxed(base + APPLE_CLKGEN_PSTATE); + + misc->devices[dev].base = base; + misc->devices[dev].active_state = + FIELD_GET(APPLE_CLKGEN_PSTATE_DESIRED, val); + misc->devices[dev].suspend_state = SYS_DEV_PSTATE_SUSPEND; + + snprintf(name, sizeof(name), "apple,%s-min-ps", device_name); + of_property_read_u32(misc->dev->of_node, name, + &misc->devices[dev].suspend_state); + + return true; +} + +static int apple_pmgr_misc_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct apple_pmgr_misc *misc; + int ret = -ENODEV; + + misc = devm_kzalloc(dev, sizeof(*misc), GFP_KERNEL); + if (!misc) + return -ENOMEM; + + misc->dev = dev; + + if (apple_pmgr_init_device(misc, DEV_FABRIC, "fabric")) + ret = 0; + + if (apple_pmgr_init_device(misc, DEV_DCS, "dcs")) + ret = 0; + + platform_set_drvdata(pdev, misc); + + return ret; +} + +static const struct of_device_id apple_pmgr_misc_of_match[] = { + { .compatible = "apple,t6000-pmgr-misc" }, + {} +}; + +MODULE_DEVICE_TABLE(of, apple_pmgr_misc_of_match); + +static const struct dev_pm_ops apple_pmgr_misc_pm_ops = { + SET_NOIRQ_SYSTEM_SLEEP_PM_OPS(apple_pmgr_misc_suspend_noirq, + apple_pmgr_misc_resume_noirq) +}; + +static struct platform_driver apple_pmgr_misc_driver = { + .probe = apple_pmgr_misc_probe, + .driver = { + .name = "apple-pmgr-misc", + .of_match_table = apple_pmgr_misc_of_match, + .pm = pm_ptr(&apple_pmgr_misc_pm_ops), + }, +}; + +MODULE_AUTHOR("Hector Martin "); +MODULE_DESCRIPTION("PMGR misc driver for Apple SoCs"); +MODULE_LICENSE("GPL v2"); + +module_platform_driver(apple_pmgr_misc_driver); From 6ca292ee0029ce01f6af67787e79578f83ca683b Mon Sep 17 00:00:00 2001 From: Asahi Lina Date: Fri, 29 Sep 2023 22:11:47 +0900 Subject: [PATCH 0035/3902] dt-bindings: power: apple,pmgr-pwrstate: Add force-{disable,reset} These flags are used for some ISP power domains, that apparently require more aggressive behavior on power down. Signed-off-by: Asahi Lina --- .../bindings/power/apple,pmgr-pwrstate.yaml | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/Documentation/devicetree/bindings/power/apple,pmgr-pwrstate.yaml b/Documentation/devicetree/bindings/power/apple,pmgr-pwrstate.yaml index caf15188099921..909fe8a386925f 100644 --- a/Documentation/devicetree/bindings/power/apple,pmgr-pwrstate.yaml +++ b/Documentation/devicetree/bindings/power/apple,pmgr-pwrstate.yaml @@ -80,6 +80,18 @@ properties: minimum: 0 maximum: 15 + apple,force-disable: + description: + Forces this device to be disabled (bus access blocked) when the power + domain is powered down. + type: boolean + + apple,force-reset: + description: + Forces a reset/error recovery of the power control logic when the power + domain is powered down. + type: boolean + required: - compatible - reg From b8cb082088ac9cf2d4a4d9f47931b9d11863d7fb Mon Sep 17 00:00:00 2001 From: Asahi Lina Date: Fri, 29 Sep 2023 22:07:30 +0900 Subject: [PATCH 0036/3902] soc: apple: pmgr: Add force-disable/force-reset It seems some ISP power states should have their force disable device access flag set when powered down (which may avoid this problem, but we're still figuring that out), and on some bit 12 is also explicitly set before shutdown. Add two properties to handle this case. Signed-off-by: Asahi Lina --- drivers/pmdomain/apple/pmgr-pwrstate.c | 43 ++++++++++++++++++++++---- 1 file changed, 37 insertions(+), 6 deletions(-) diff --git a/drivers/pmdomain/apple/pmgr-pwrstate.c b/drivers/pmdomain/apple/pmgr-pwrstate.c index 82c33cf727a825..c2e37eabb89f4c 100644 --- a/drivers/pmdomain/apple/pmgr-pwrstate.c +++ b/drivers/pmdomain/apple/pmgr-pwrstate.c @@ -21,7 +21,8 @@ #define APPLE_PMGR_AUTO_ENABLE BIT(28) #define APPLE_PMGR_PS_AUTO GENMASK(27, 24) #define APPLE_PMGR_PS_MIN GENMASK(19, 16) -#define APPLE_PMGR_PARENT_OFF BIT(11) +#define APPLE_PMGR_PS_RESET BIT(12) +#define APPLE_PMGR_BUSY BIT(11) #define APPLE_PMGR_DEV_DISABLE BIT(10) #define APPLE_PMGR_WAS_CLKGATED BIT(9) #define APPLE_PMGR_WAS_PWRGATED BIT(8) @@ -44,6 +45,8 @@ struct apple_pmgr_ps { struct regmap *regmap; u32 offset; u32 min_state; + bool force_disable; + bool force_reset; }; #define genpd_to_apple_pmgr_ps(_genpd) container_of(_genpd, struct apple_pmgr_ps, genpd) @@ -53,7 +56,7 @@ static int apple_pmgr_ps_set(struct generic_pm_domain *genpd, u32 pstate, bool a { int ret; struct apple_pmgr_ps *ps = genpd_to_apple_pmgr_ps(genpd); - u32 reg; + u32 reg, cur; ret = regmap_read(ps->regmap, ps->offset, ®); if (ret < 0) @@ -64,7 +67,29 @@ static int apple_pmgr_ps_set(struct generic_pm_domain *genpd, u32 pstate, bool a dev_err(ps->dev, "PS %s: powering off with RESET active\n", genpd->name); - reg &= ~(APPLE_PMGR_AUTO_ENABLE | APPLE_PMGR_FLAGS | APPLE_PMGR_PS_TARGET); + if (pstate != APPLE_PMGR_PS_ACTIVE && (ps->force_disable || ps->force_reset)) { + u32 reg_pre = reg & ~(APPLE_PMGR_AUTO_ENABLE | APPLE_PMGR_FLAGS); + + if (ps->force_disable) + reg_pre |= APPLE_PMGR_DEV_DISABLE; + if (ps->force_reset) + reg_pre |= APPLE_PMGR_PS_RESET; + + regmap_write(ps->regmap, ps->offset, reg_pre); + + ret = regmap_read_poll_timeout_atomic( + ps->regmap, ps->offset, cur, + (cur & (APPLE_PMGR_DEV_DISABLE | APPLE_PMGR_PS_RESET)) == + (reg_pre & (APPLE_PMGR_DEV_DISABLE | APPLE_PMGR_PS_RESET)), 1, + APPLE_PMGR_PS_SET_TIMEOUT); + + if (ret < 0) + dev_err(ps->dev, "PS %s: Failed to set reset/disable bits (now: 0x%x)\n", + genpd->name, reg); + } + + reg &= ~(APPLE_PMGR_DEV_DISABLE | APPLE_PMGR_PS_RESET | + APPLE_PMGR_AUTO_ENABLE | APPLE_PMGR_FLAGS | APPLE_PMGR_PS_TARGET); reg |= FIELD_PREP(APPLE_PMGR_PS_TARGET, pstate); dev_dbg(ps->dev, "PS %s: pwrstate = 0x%x: 0x%x\n", genpd->name, pstate, reg); @@ -72,16 +97,16 @@ static int apple_pmgr_ps_set(struct generic_pm_domain *genpd, u32 pstate, bool a regmap_write(ps->regmap, ps->offset, reg); ret = regmap_read_poll_timeout_atomic( - ps->regmap, ps->offset, reg, - (FIELD_GET(APPLE_PMGR_PS_ACTUAL, reg) == pstate), 1, + ps->regmap, ps->offset, cur, + FIELD_GET(APPLE_PMGR_PS_ACTUAL, cur) == pstate, 1, APPLE_PMGR_PS_SET_TIMEOUT); + if (ret < 0) dev_err(ps->dev, "PS %s: Failed to reach power state 0x%x (now: 0x%x)\n", genpd->name, pstate, reg); if (auto_enable) { /* Not all devices implement this; this is a no-op where not implemented. */ - reg &= ~APPLE_PMGR_FLAGS; reg |= APPLE_PMGR_AUTO_ENABLE; regmap_write(ps->regmap, ps->offset, reg); } @@ -244,6 +269,12 @@ static int apple_pmgr_ps_probe(struct platform_device *pdev) } } + if (of_property_read_bool(node, "apple,force-disable")) + ps->force_disable = true; + + if (of_property_read_bool(node, "apple,force-reset")) + ps->force_reset = true; + /* Turn on auto-PM if the domain is already on */ if (active) regmap_update_bits(regmap, ps->offset, APPLE_PMGR_FLAGS | APPLE_PMGR_AUTO_ENABLE, From 3d0455519be3f30cf6163881081263b95ace99be Mon Sep 17 00:00:00 2001 From: Hector Martin Date: Thu, 12 Oct 2023 23:18:14 +0900 Subject: [PATCH 0037/3902] soc: apple: pmgr: Add externally-clocked property MCA power states require an external clock to be provided. If they are powered on while this clock is not active, the power state will only go into the "clock gated" state. This is effectively working as intended, so add a property that instructs the pwrstate driver to consider the PS to be successfully powered on when it reaches the clock gated state. Signed-off-by: Hector Martin --- drivers/pmdomain/apple/pmgr-pwrstate.c | 35 ++++++++++++++++++-------- 1 file changed, 25 insertions(+), 10 deletions(-) diff --git a/drivers/pmdomain/apple/pmgr-pwrstate.c b/drivers/pmdomain/apple/pmgr-pwrstate.c index c2e37eabb89f4c..52bf2bf92f5b49 100644 --- a/drivers/pmdomain/apple/pmgr-pwrstate.c +++ b/drivers/pmdomain/apple/pmgr-pwrstate.c @@ -47,6 +47,7 @@ struct apple_pmgr_ps { u32 min_state; bool force_disable; bool force_reset; + bool externally_clocked; }; #define genpd_to_apple_pmgr_ps(_genpd) container_of(_genpd, struct apple_pmgr_ps, genpd) @@ -96,10 +97,21 @@ static int apple_pmgr_ps_set(struct generic_pm_domain *genpd, u32 pstate, bool a regmap_write(ps->regmap, ps->offset, reg); - ret = regmap_read_poll_timeout_atomic( - ps->regmap, ps->offset, cur, - FIELD_GET(APPLE_PMGR_PS_ACTUAL, cur) == pstate, 1, - APPLE_PMGR_PS_SET_TIMEOUT); + if (ps->externally_clocked && pstate == APPLE_PMGR_PS_ACTIVE) { + /* + * If this clock domain requires an external clock, then + * consider the "clock gated" state to be good enough. + */ + ret = regmap_read_poll_timeout_atomic( + ps->regmap, ps->offset, cur, + FIELD_GET(APPLE_PMGR_PS_ACTUAL, cur) >= APPLE_PMGR_PS_CLKGATE, 1, + APPLE_PMGR_PS_SET_TIMEOUT); + } else { + ret = regmap_read_poll_timeout_atomic( + ps->regmap, ps->offset, cur, + FIELD_GET(APPLE_PMGR_PS_ACTUAL, cur) == pstate, 1, + APPLE_PMGR_PS_SET_TIMEOUT); + } if (ret < 0) dev_err(ps->dev, "PS %s: Failed to reach power state 0x%x (now: 0x%x)\n", @@ -259,6 +271,15 @@ static int apple_pmgr_ps_probe(struct platform_device *pdev) regmap_update_bits(regmap, ps->offset, APPLE_PMGR_FLAGS | APPLE_PMGR_PS_MIN, FIELD_PREP(APPLE_PMGR_PS_MIN, ps->min_state)); + if (of_property_read_bool(node, "apple,force-disable")) + ps->force_disable = true; + + if (of_property_read_bool(node, "apple,force-reset")) + ps->force_reset = true; + + if (of_property_read_bool(node, "apple,externally-clocked")) + ps->externally_clocked = true; + active = apple_pmgr_ps_is_active(ps); if (of_property_read_bool(node, "apple,always-on")) { ps->genpd.flags |= GENPD_FLAG_ALWAYS_ON; @@ -269,12 +290,6 @@ static int apple_pmgr_ps_probe(struct platform_device *pdev) } } - if (of_property_read_bool(node, "apple,force-disable")) - ps->force_disable = true; - - if (of_property_read_bool(node, "apple,force-reset")) - ps->force_reset = true; - /* Turn on auto-PM if the domain is already on */ if (active) regmap_update_bits(regmap, ps->offset, APPLE_PMGR_FLAGS | APPLE_PMGR_AUTO_ENABLE, From 4c135ef5c4c15479593b86a6da1b7abf87ffa6e7 Mon Sep 17 00:00:00 2001 From: Hector Martin Date: Mon, 17 Apr 2023 20:41:13 +0900 Subject: [PATCH 0038/3902] cpuidle: apple: Add Apple SoC cpuidle driver May the PSCI conversation happen some day. Until it does, this will make the user experience a lot less painful in downstream kernels. Signed-off-by: Hector Martin --- drivers/cpuidle/Kconfig.arm | 8 ++ drivers/cpuidle/Makefile | 1 + drivers/cpuidle/cpuidle-apple.c | 157 ++++++++++++++++++++++++++++++++ 3 files changed, 166 insertions(+) create mode 100644 drivers/cpuidle/cpuidle-apple.c diff --git a/drivers/cpuidle/Kconfig.arm b/drivers/cpuidle/Kconfig.arm index a1ee475d180dac..c6870f08457632 100644 --- a/drivers/cpuidle/Kconfig.arm +++ b/drivers/cpuidle/Kconfig.arm @@ -130,3 +130,11 @@ config ARM_QCOM_SPM_CPUIDLE The Subsystem Power Manager (SPM) controls low power modes for the CPU and L2 cores. It interface with various system drivers to put the cores in low power modes. + +config ARM_APPLE_CPUIDLE + bool "Apple SoC CPU idle driver" + depends on ARM64 + default ARCH_APPLE + select CPU_IDLE_MULTIPLE_DRIVERS + help + Select this to enable cpuidle on Apple SoCs. diff --git a/drivers/cpuidle/Makefile b/drivers/cpuidle/Makefile index 1de9e92c5b0fc9..f9e7a71d52c13f 100644 --- a/drivers/cpuidle/Makefile +++ b/drivers/cpuidle/Makefile @@ -29,6 +29,7 @@ obj-$(CONFIG_ARM_PSCI_CPUIDLE) += cpuidle-psci.o obj-$(CONFIG_ARM_PSCI_CPUIDLE_DOMAIN) += cpuidle-psci-domain.o obj-$(CONFIG_ARM_TEGRA_CPUIDLE) += cpuidle-tegra.o obj-$(CONFIG_ARM_QCOM_SPM_CPUIDLE) += cpuidle-qcom-spm.o +obj-$(CONFIG_ARM_APPLE_CPUIDLE) += cpuidle-apple.o ############################################################################### # MIPS drivers diff --git a/drivers/cpuidle/cpuidle-apple.c b/drivers/cpuidle/cpuidle-apple.c new file mode 100644 index 00000000000000..1dfb10cdb5e4d6 --- /dev/null +++ b/drivers/cpuidle/cpuidle-apple.c @@ -0,0 +1,157 @@ +// SPDX-License-Identifier: GPL-2.0-only OR MIT +/* + * Copyright The Asahi Linux Contributors + * + * CPU idle support for Apple SoCs + */ + +#include +#include +#include +#include +#include +#include + +enum idle_state { + STATE_WFI, + STATE_PWRDOWN, + STATE_COUNT +}; + +asm( + ".pushsection .cpuidle.text, \"ax\"\n" + ".type apple_cpu_deep_wfi, @function\n" + "apple_cpu_deep_wfi:\n" + "str x30, [sp, #-16]!\n" + "stp x28, x29, [sp, #-16]!\n" + "stp x26, x27, [sp, #-16]!\n" + "stp x24, x25, [sp, #-16]!\n" + "stp x22, x23, [sp, #-16]!\n" + "stp x20, x21, [sp, #-16]!\n" + "stp x18, x19, [sp, #-16]!\n" + + "mrs x0, s3_5_c15_c5_0\n" + "orr x0, x0, #(3L << 24)\n" + "msr s3_5_c15_c5_0, x0\n" + + "1:\n" + "dsb sy\n" + "wfi\n" + + "mrs x0, ISR_EL1\n" + "cbz x0, 1b\n" + + "mrs x0, s3_5_c15_c5_0\n" + "bic x0, x0, #(1L << 24)\n" + "msr s3_5_c15_c5_0, x0\n" + + "ldp x18, x19, [sp], #16\n" + "ldp x20, x21, [sp], #16\n" + "ldp x22, x23, [sp], #16\n" + "ldp x24, x25, [sp], #16\n" + "ldp x26, x27, [sp], #16\n" + "ldp x28, x29, [sp], #16\n" + "ldr x30, [sp], #16\n" + + "ret\n" + ".popsection\n" +); + +void apple_cpu_deep_wfi(void); + +static __cpuidle int apple_enter_wfi(struct cpuidle_device *dev, struct cpuidle_driver *drv, int index) +{ + cpu_do_idle(); + return index; +} + +static __cpuidle int apple_enter_idle(struct cpuidle_device *dev, struct cpuidle_driver *drv, int index) +{ + /* + * Deep WFI will clobber FP state, among other things. + * The CPU PM notifier will take care of saving that and anything else + * that needs to be notified of the CPU powering down. + */ + if (cpu_pm_enter()) + return -1; + + ct_cpuidle_enter(); + + switch(index) { + case STATE_PWRDOWN: + apple_cpu_deep_wfi(); + break; + default: + WARN_ON(1); + break; + } + + ct_cpuidle_exit(); + + cpu_pm_exit(); + + return index; +} + +static struct cpuidle_driver apple_idle_driver = { + .name = "apple_idle", + .owner = THIS_MODULE, + .states = { + [STATE_WFI] = { + .enter = apple_enter_wfi, + .enter_s2idle = apple_enter_wfi, + .exit_latency = 1, + .target_residency = 1, + .power_usage = UINT_MAX, + .name = "WFI", + .desc = "CPU clock-gated", + .flags = 0, + }, + [STATE_PWRDOWN] = { + .enter = apple_enter_idle, + .enter_s2idle = apple_enter_idle, + .exit_latency = 10, + .target_residency = 10000, + .power_usage = 0, + .name = "CPU PD", + .desc = "CPU/cluster powered down", + .flags = CPUIDLE_FLAG_RCU_IDLE, + }, + }, + .safe_state_index = STATE_WFI, + .state_count = STATE_COUNT, +}; + +static int apple_cpuidle_probe(struct platform_device *pdev) +{ + return cpuidle_register(&apple_idle_driver, NULL); +} + +static struct platform_driver apple_cpuidle_driver = { + .driver = { + .name = "cpuidle-apple", + }, + .probe = apple_cpuidle_probe, +}; + +static int __init apple_cpuidle_init(void) +{ + struct platform_device *pdev; + int ret; + + ret = platform_driver_register(&apple_cpuidle_driver); + if (ret) + return ret; + + if (!of_machine_is_compatible("apple,arm-platform")) + return 0; + + pdev = platform_device_register_simple("cpuidle-apple", -1, NULL, 0); + if (IS_ERR(pdev)) { + platform_driver_unregister(&apple_cpuidle_driver); + return PTR_ERR(pdev); + } + + return 0; +} +device_initcall(apple_cpuidle_init); From a8b65183b4b4630fd9a9c885a89fedcccfd881a2 Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Sat, 31 Aug 2024 12:19:03 +0200 Subject: [PATCH 0039/3902] cpuidle: apple: Do not load on unsupported Apple platforms Pre-t8103 SoCs do not retain state during deep WFI. Reject to load on such platforms. Suggested-by: Nick Chan Signed-off-by: Janne Grunau --- drivers/cpuidle/cpuidle-apple.c | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/drivers/cpuidle/cpuidle-apple.c b/drivers/cpuidle/cpuidle-apple.c index 1dfb10cdb5e4d6..27b9144b979d3a 100644 --- a/drivers/cpuidle/cpuidle-apple.c +++ b/drivers/cpuidle/cpuidle-apple.c @@ -6,12 +6,15 @@ */ #include +#include #include #include #include #include #include +#define DEEP_WFI_STATE_RETENTION BIT(2) // retains base CPU registers in deep WFI + enum idle_state { STATE_WFI, STATE_PWRDOWN, @@ -146,6 +149,11 @@ static int __init apple_cpuidle_init(void) if (!of_machine_is_compatible("apple,arm-platform")) return 0; + if (!FIELD_GET(DEEP_WFI_STATE_RETENTION, read_sysreg(aidr_el1))) { + pr_info("cpuidle-apple: CPU does not retain state in deep WFI\n"); + return 0; + } + pdev = platform_device_register_simple("cpuidle-apple", -1, NULL, 0); if (IS_ERR(pdev)) { platform_driver_unregister(&apple_cpuidle_driver); From 0f88fc8b3266c2413cf029e0ef94c2f1a381a772 Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Thu, 13 Feb 2025 22:44:11 +0100 Subject: [PATCH 0040/3902] soc: apple: rtkit: Use scope-based cleanup in apple_rtkit_crashlog_rx() Use scope-based cleanup for the crashlog buffer to simplify the function and avoid problems like the one fixed in commit 1fb9f14458c0 ("soc: apple: rtkit: Fix use-after-free in apple_rtkit_crashlog_rx()"). Signed-off-by: Janne Grunau --- drivers/soc/apple/rtkit.c | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/drivers/soc/apple/rtkit.c b/drivers/soc/apple/rtkit.c index 3a50d7a44595b1..1cc7c76a5adead 100644 --- a/drivers/soc/apple/rtkit.c +++ b/drivers/soc/apple/rtkit.c @@ -361,7 +361,7 @@ static void apple_rtkit_memcpy(struct apple_rtkit *rtk, void *dst, static void apple_rtkit_crashlog_rx(struct apple_rtkit *rtk, u64 msg) { u8 type = FIELD_GET(APPLE_RTKIT_SYSLOG_TYPE, msg); - u8 *bfr; + u8 *bfr __free(kfree) = NULL; if (type != APPLE_RTKIT_CRASHLOG_CRASH) { dev_warn(rtk->dev, "RTKit: Unknown crashlog message: %llx\n", @@ -395,8 +395,6 @@ static void apple_rtkit_crashlog_rx(struct apple_rtkit *rtk, u64 msg) rtk->crashed = true; if (rtk->ops->crashed) rtk->ops->crashed(rtk->cookie, bfr, rtk->crashlog_buffer.size); - - kfree(bfr); } static void apple_rtkit_ioreport_rx(struct apple_rtkit *rtk, u64 msg) From 3cdb6e245cb5a4b8bfeb7df1e99482aa90375b06 Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Thu, 13 Feb 2025 22:46:51 +0100 Subject: [PATCH 0041/3902] soc: apple: rtkit: Pass 0 as size for a NULL crashlog buffer The crashlog size is not useful for the crashed() callback callee if the passed buffer is NULL. To reduce the risk of NULL pointer derefences in callees use size 0 in this case. Signed-off-by: Janne Grunau --- drivers/soc/apple/rtkit.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/soc/apple/rtkit.c b/drivers/soc/apple/rtkit.c index 1cc7c76a5adead..f39d230e9360ed 100644 --- a/drivers/soc/apple/rtkit.c +++ b/drivers/soc/apple/rtkit.c @@ -394,7 +394,7 @@ static void apple_rtkit_crashlog_rx(struct apple_rtkit *rtk, u64 msg) rtk->crashed = true; if (rtk->ops->crashed) - rtk->ops->crashed(rtk->cookie, bfr, rtk->crashlog_buffer.size); + rtk->ops->crashed(rtk->cookie, bfr, bfr ? rtk->crashlog_buffer.size : 0); } static void apple_rtkit_ioreport_rx(struct apple_rtkit *rtk, u64 msg) From 45c85eeea6b879db982a9798a08279ac320d8c13 Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Sat, 2 Nov 2024 22:44:51 +0100 Subject: [PATCH 0042/3902] irqchip/apple-aic: Add support for AICv3 Introduce support for the new AICv3 hardware block in t8122 and t603x SoCs. For AICv3 Apple started provide offsets as device tree properties suggesting those are not constant anymore like the event register offset in AICv2. Instead of adding reg entries we provide these as devicetree properties as well. Signed-off-by: Janne Grunau --- drivers/irqchip/irq-apple-aic.c | 39 +++++++++++++++++++++++++++++---- 1 file changed, 35 insertions(+), 4 deletions(-) diff --git a/drivers/irqchip/irq-apple-aic.c b/drivers/irqchip/irq-apple-aic.c index 032d66dceb8ec4..cb7850b2dbb8eb 100644 --- a/drivers/irqchip/irq-apple-aic.c +++ b/drivers/irqchip/irq-apple-aic.c @@ -54,6 +54,7 @@ #include #include #include +#include #include #include #include @@ -251,6 +252,9 @@ struct aic_info { u32 mask_set; u32 mask_clr; + u32 cap0_off; + u32 maxnumirq_off; + u32 die_stride; /* Features */ @@ -288,6 +292,14 @@ static const struct aic_info aic2_info __initconst = { .version = 2, .irq_cfg = AIC2_IRQ_CFG, + .cap0_off = AIC2_INFO1, + .maxnumirq_off = AIC2_INFO3, + + .fast_ipi = true, +}; + +static const struct aic_info aic3_info __initconst = { + .version = 3, .fast_ipi = true, .local_fast_ipi = true, @@ -310,6 +322,10 @@ static const struct of_device_id aic_info_match[] = { .compatible = "apple,aic2", .data = &aic2_info, }, + { + .compatible = "apple,aic3", + .data = &aic3_info, + }, {} }; @@ -624,7 +640,7 @@ static int aic_irq_domain_map(struct irq_domain *id, unsigned int irq, u32 type = FIELD_GET(AIC_EVENT_TYPE, hw); struct irq_chip *chip = &aic_chip; - if (ic->info.version == 2) + if (ic->info.version == 2 || ic->info.version == 3) chip = &aic2_chip; if (type == AIC_EVENT_TYPE_IRQ) { @@ -931,6 +947,7 @@ static void build_fiq_affinity(struct aic_irq_chip *ic, struct device_node *aff) static int __init aic_of_ic_init(struct device_node *node, struct device_node *parent) { + int ret; int i, die; u32 off, start_off; void __iomem *regs; @@ -974,11 +991,24 @@ static int __init aic_of_ic_init(struct device_node *node, struct device_node *p break; } + case 3: + /* read offsets from device tree for aic version 3 */ + /* extint-baseaddress? */ + ret = of_property_read_u32(node, "config-offset", &irqc->info.irq_cfg); + if (ret < 0) + return ret; + ret = of_property_read_u32(node, "cap0-offset", &irqc->info.cap0_off); + if (ret < 0) + return ret; + ret = of_property_read_u32(node, "maxnumirq-offset", &irqc->info.maxnumirq_off); + if (ret < 0) + return ret; + fallthrough; case 2: { u32 info1, info3; - info1 = aic_ic_read(irqc, AIC2_INFO1); - info3 = aic_ic_read(irqc, AIC2_INFO3); + info1 = aic_ic_read(irqc, irqc->info.cap0_off); + info3 = aic_ic_read(irqc, irqc->info.maxnumirq_off); irqc->nr_irq = FIELD_GET(AIC2_INFO1_NR_IRQ, info1); irqc->max_irq = FIELD_GET(AIC2_INFO3_MAX_IRQ, info3); @@ -1048,7 +1078,7 @@ static int __init aic_of_ic_init(struct device_node *node, struct device_node *p off += irqc->info.die_stride; } - if (irqc->info.version == 2) { + if (irqc->info.version == 2 || irqc->info.version == 3) { u32 config = aic_ic_read(irqc, AIC2_CONFIG); config |= AIC2_CONFIG_ENABLE; @@ -1099,3 +1129,4 @@ static int __init aic_of_ic_init(struct device_node *node, struct device_node *p IRQCHIP_DECLARE(apple_aic, "apple,aic", aic_of_ic_init); IRQCHIP_DECLARE(apple_aic2, "apple,aic2", aic_of_ic_init); +IRQCHIP_DECLARE(apple_aic3, "apple,aic3", aic_of_ic_init); From 976123a90eaa32bf2c409086f0f18f45572c0207 Mon Sep 17 00:00:00 2001 From: Asahi Lina Date: Fri, 29 Sep 2023 19:46:53 +0900 Subject: [PATCH 0043/3902] iommu: apple-dart: Power on device when handling IRQs It's possible for an IRQ to fire and the device to be RPM suspended before we can handle it, which then causes device register accesses to fail in the IRQ handler. Since RPM is IRQ-safe for this device, just make sure we power on the DART in the IRQ handler too. Signed-off-by: Asahi Lina --- drivers/iommu/apple-dart.c | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/drivers/iommu/apple-dart.c b/drivers/iommu/apple-dart.c index 95a4e62b8f63c3..61e7e75f270cf7 100644 --- a/drivers/iommu/apple-dart.c +++ b/drivers/iommu/apple-dart.c @@ -1097,6 +1097,17 @@ static irqreturn_t apple_dart_t8110_irq(int irq, void *dev) return IRQ_HANDLED; } +static irqreturn_t apple_dart_irq(int irq, void *dev) +{ + irqreturn_t ret; + struct apple_dart *dart = dev; + + WARN_ON(pm_runtime_get_sync(dart->dev) < 0); + ret = dart->hw->irq_handler(irq, dev); + pm_runtime_put(dart->dev); + return ret; +} + static int apple_dart_probe(struct platform_device *pdev) { int ret; @@ -1169,7 +1180,7 @@ static int apple_dart_probe(struct platform_device *pdev) if (ret) goto err_clk_disable; - ret = request_irq(dart->irq, dart->hw->irq_handler, IRQF_SHARED, + ret = request_irq(dart->irq, apple_dart_irq, IRQF_SHARED, "apple-dart fault handler", dart); if (ret) goto err_clk_disable; From efeea5c1eec7016fe3b0e0c598f53305fc60f5e5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20Povi=C5=A1er?= Date: Fri, 28 Apr 2023 19:10:56 +0200 Subject: [PATCH 0044/3902] iommu: apple-dart: Link to consumers with blanket RPM_ACTIVE MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Without the RPM_ACTIVE flag, runtime PM core only seems to consider the link insofar as it prevents the DART from suspending in case of consumers *considered active by runtime PM*. Other devices, like those on which runtime PM has yet to be enabled, or which lack any runtime PM support, are not considered in preventing the DART from suspending. DART going through suspend/resume cycle with active consumers can break the consumers' operation by the DART being reset in its resume path, among other things. Add RPM_ACTIVE flag to the link to have the consumer in the link prevent the DART from being suspended, unless the consumer itself is runtime PM suspended. This supersedes an earlier PCIe-only workaround. (TODO: Does this mean devices without bound drivers will keep their DARTs up indefinitely? This depends on the timing of the iommu probe_device/release_device calls. Investigate.) Signed-off-by: Martin Povišer --- drivers/iommu/apple-dart.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/drivers/iommu/apple-dart.c b/drivers/iommu/apple-dart.c index 61e7e75f270cf7..2c4aaa7a247a34 100644 --- a/drivers/iommu/apple-dart.c +++ b/drivers/iommu/apple-dart.c @@ -747,9 +747,9 @@ static struct iommu_device *apple_dart_probe_device(struct device *dev) return ERR_PTR(-ENODEV); for_each_stream_map(i, cfg, stream_map) - device_link_add( - dev, stream_map->dart->dev, - DL_FLAG_PM_RUNTIME | DL_FLAG_AUTOREMOVE_SUPPLIER); + device_link_add(dev, stream_map->dart->dev, + DL_FLAG_PM_RUNTIME | DL_FLAG_AUTOREMOVE_SUPPLIER | + DL_FLAG_RPM_ACTIVE); return &cfg->stream_maps[0].dart->iommu; } From 177bffd9d208ecd5dd96493eaf6057216cad051c Mon Sep 17 00:00:00 2001 From: Hector Martin Date: Mon, 12 Dec 2022 23:53:23 +0900 Subject: [PATCH 0045/3902] iommu: apple-dart: Enable runtime PM Signed-off-by: Hector Martin --- drivers/iommu/apple-dart.c | 43 +++++++++++++++++++++++++++++++++----- 1 file changed, 38 insertions(+), 5 deletions(-) diff --git a/drivers/iommu/apple-dart.c b/drivers/iommu/apple-dart.c index 2c4aaa7a247a34..fd9d69444420d2 100644 --- a/drivers/iommu/apple-dart.c +++ b/drivers/iommu/apple-dart.c @@ -28,6 +28,7 @@ #include #include #include +#include #include #include #include @@ -506,7 +507,9 @@ static void apple_dart_domain_flush_tlb(struct apple_dart_domain *domain) for (j = 0; j < BITS_TO_LONGS(stream_map.dart->num_streams); j++) stream_map.sidmap[j] = atomic_long_read(&domain_stream_map->sidmap[j]); + WARN_ON(pm_runtime_get_sync(stream_map.dart->dev) < 0); stream_map.dart->hw->invalidate_tlb(&stream_map); + pm_runtime_put(stream_map.dart->dev); } } @@ -679,17 +682,24 @@ static int apple_dart_attach_dev_paging(struct iommu_domain *domain, struct apple_dart_master_cfg *cfg = dev_iommu_priv_get(dev); struct apple_dart_domain *dart_domain = to_dart_domain(domain); + for_each_stream_map(i, cfg, stream_map) + WARN_ON(pm_runtime_get_sync(stream_map->dart->dev) < 0); + ret = apple_dart_finalize_domain(dart_domain, cfg); if (ret) - return ret; + goto err; ret = apple_dart_domain_add_streams(dart_domain, cfg); if (ret) - return ret; + goto err; for_each_stream_map(i, cfg, stream_map) apple_dart_setup_translation(dart_domain, stream_map); - return 0; + +err: + for_each_stream_map(i, cfg, stream_map) + pm_runtime_put(stream_map->dart->dev); + return ret; } static int apple_dart_attach_dev_identity(struct iommu_domain *domain, @@ -702,8 +712,14 @@ static int apple_dart_attach_dev_identity(struct iommu_domain *domain, if (!cfg->supports_bypass) return -EINVAL; + for_each_stream_map(i, cfg, stream_map) + WARN_ON(pm_runtime_get_sync(stream_map->dart->dev) < 0); + for_each_stream_map(i, cfg, stream_map) apple_dart_hw_enable_bypass(stream_map); + + for_each_stream_map(i, cfg, stream_map) + pm_runtime_put(stream_map->dart->dev); return 0; } @@ -723,8 +739,14 @@ static int apple_dart_attach_dev_blocked(struct iommu_domain *domain, struct apple_dart_stream_map *stream_map; int i; + for_each_stream_map(i, cfg, stream_map) + WARN_ON(pm_runtime_get_sync(stream_map->dart->dev) < 0); + for_each_stream_map(i, cfg, stream_map) apple_dart_hw_disable_dma(stream_map); + + for_each_stream_map(i, cfg, stream_map) + pm_runtime_put(stream_map->dart->dev); return 0; } @@ -1146,6 +1168,14 @@ static int apple_dart_probe(struct platform_device *pdev) if (ret) return ret; + pm_runtime_get_noresume(dev); + pm_runtime_set_active(dev); + pm_runtime_irq_safe(dev); + + ret = devm_pm_runtime_enable(dev); + if (ret) + goto err_clk_disable; + dart_params[0] = readl(dart->regs + DART_PARAMS1); dart_params[1] = readl(dart->regs + DART_PARAMS2); dart->pgsize = 1 << FIELD_GET(DART_PARAMS1_PAGE_SHIFT, dart_params[0]); @@ -1196,6 +1226,8 @@ static int apple_dart_probe(struct platform_device *pdev) if (ret) goto err_sysfs_remove; + pm_runtime_put(dev); + dev_info( &pdev->dev, "DART [pagesize %x, %d streams, bypass support: %d, bypass forced: %d, AS %d -> %d] initialized\n", @@ -1208,6 +1240,7 @@ static int apple_dart_probe(struct platform_device *pdev) err_free_irq: free_irq(dart->irq, dart); err_clk_disable: + pm_runtime_put(dev); clk_bulk_disable_unprepare(dart->num_clks, dart->clks); return ret; @@ -1367,7 +1400,7 @@ static __maybe_unused int apple_dart_resume(struct device *dev) return 0; } -static DEFINE_SIMPLE_DEV_PM_OPS(apple_dart_pm_ops, apple_dart_suspend, apple_dart_resume); +static DEFINE_RUNTIME_DEV_PM_OPS(apple_dart_pm_ops, apple_dart_suspend, apple_dart_resume, NULL); static const struct of_device_id apple_dart_of_match[] = { { .compatible = "apple,t8103-dart", .data = &apple_dart_hw_t8103 }, @@ -1383,7 +1416,7 @@ static struct platform_driver apple_dart_driver = { .name = "apple-dart", .of_match_table = apple_dart_of_match, .suppress_bind_attrs = true, - .pm = pm_sleep_ptr(&apple_dart_pm_ops), + .pm = pm_ptr(&apple_dart_pm_ops), }, .probe = apple_dart_probe, .remove = apple_dart_remove, From 28e2d646873eb9bc3a3be6a3fce1edef51cd5e22 Mon Sep 17 00:00:00 2001 From: Hector Martin Date: Tue, 11 Apr 2023 01:32:06 +0900 Subject: [PATCH 0046/3902] iommu: apple-dart: Support specifying the DMA aperture in the DT Apple DARTs are often connected directly to devices that expect only a portion of their address space to be used for DMA (for example, because other ranges are mapped directly to something else). Add an apple,dma-range property to allow specifying this range. This range *can* be outside of the DART's IAS. In that case, it is assumed that the hardware truncates addresses and the page tables will only map the lower bits of the address. However, the specified range cannot straddle an IAS boundary (you cannot cover more than IAS worth of address space nor wrap). This corresponds to the vm-base and vm-size properties on the Apple device tree side of things. Signed-off-by: Hector Martin --- drivers/iommu/apple-dart.c | 51 ++++++++++++++++++++++++++++++++------ 1 file changed, 43 insertions(+), 8 deletions(-) diff --git a/drivers/iommu/apple-dart.c b/drivers/iommu/apple-dart.c index fd9d69444420d2..6dba6e58373011 100644 --- a/drivers/iommu/apple-dart.c +++ b/drivers/iommu/apple-dart.c @@ -21,6 +21,7 @@ #include #include #include +#include #include #include #include @@ -224,6 +225,9 @@ struct apple_dart { u32 supports_bypass : 1; u32 four_level : 1; + dma_addr_t dma_min; + dma_addr_t dma_max; + struct iommu_group *sid2group[DART_MAX_STREAMS]; struct iommu_device iommu; @@ -268,6 +272,7 @@ struct apple_dart_domain { struct io_pgtable_ops *pgtbl_ops; bool finalized; + u64 mask; struct mutex init_lock; struct apple_dart_atomic_stream_map stream_maps[MAX_DARTS_PER_DEVICE]; @@ -540,7 +545,7 @@ static phys_addr_t apple_dart_iova_to_phys(struct iommu_domain *domain, if (!ops) return 0; - return ops->iova_to_phys(ops, iova); + return ops->iova_to_phys(ops, iova & dart_domain->mask); } static int apple_dart_map_pages(struct iommu_domain *domain, unsigned long iova, @@ -554,8 +559,8 @@ static int apple_dart_map_pages(struct iommu_domain *domain, unsigned long iova, if (!ops) return -ENODEV; - return ops->map_pages(ops, iova, paddr, pgsize, pgcount, prot, gfp, - mapped); + return ops->map_pages(ops, iova & dart_domain->mask, paddr, pgsize, + pgcount, prot, gfp, mapped); } static size_t apple_dart_unmap_pages(struct iommu_domain *domain, @@ -566,7 +571,8 @@ static size_t apple_dart_unmap_pages(struct iommu_domain *domain, struct apple_dart_domain *dart_domain = to_dart_domain(domain); struct io_pgtable_ops *ops = dart_domain->pgtbl_ops; - return ops->unmap_pages(ops, iova, pgsize, pgcount, gather); + return ops->unmap_pages(ops, iova & dart_domain->mask, pgsize, pgcount, + gather); } static void @@ -593,6 +599,8 @@ static int apple_dart_finalize_domain(struct apple_dart_domain *dart_domain, { struct apple_dart *dart = cfg->stream_maps[0].dart; struct io_pgtable_cfg pgtbl_cfg; + dma_addr_t dma_max = dart->dma_max; + u32 ias = min_t(u32, dart->ias, fls64(dma_max)); int ret = 0; int i, j; @@ -613,7 +621,7 @@ static int apple_dart_finalize_domain(struct apple_dart_domain *dart_domain, pgtbl_cfg = (struct io_pgtable_cfg){ .pgsize_bitmap = dart->pgsize, - .ias = dart->ias, + .ias = ias, .oas = dart->oas, .coherent_walk = 1, .iommu_dev = dart->dev, @@ -626,10 +634,16 @@ static int apple_dart_finalize_domain(struct apple_dart_domain *dart_domain, goto done; } + if (pgtbl_cfg.pgsize_bitmap == SZ_4K) + dart_domain->mask = DMA_BIT_MASK(min_t(u32, dart->ias, 32)); + else if (pgtbl_cfg.apple_dart_cfg.n_levels == 3) + dart_domain->mask = DMA_BIT_MASK(min_t(u32, dart->ias, 36)); + else if (pgtbl_cfg.apple_dart_cfg.n_levels == 4) + dart_domain->mask = DMA_BIT_MASK(min_t(u32, dart->ias, 47)); + dart_domain->domain.pgsize_bitmap = pgtbl_cfg.pgsize_bitmap; - dart_domain->domain.geometry.aperture_start = 0; - dart_domain->domain.geometry.aperture_end = - (dma_addr_t)DMA_BIT_MASK(pgtbl_cfg.ias); + dart_domain->domain.geometry.aperture_start = dart->dma_min; + dart_domain->domain.geometry.aperture_end = dma_max; dart_domain->domain.geometry.force_aperture = true; dart_domain->finalized = true; @@ -1137,6 +1151,7 @@ static int apple_dart_probe(struct platform_device *pdev) struct resource *res; struct apple_dart *dart; struct device *dev = &pdev->dev; + u64 dma_range[2]; dart = devm_kzalloc(dev, sizeof(*dart), GFP_KERNEL); if (!dart) @@ -1199,6 +1214,26 @@ static int apple_dart_probe(struct platform_device *pdev) break; } + dart->dma_min = 0; + dart->dma_max = DMA_BIT_MASK(dart->ias); + + ret = of_property_read_u64_array(dev->of_node, "apple,dma-range", dma_range, 2); + if (ret == -EINVAL) { + ret = 0; + } else if (ret) { + goto err_clk_disable; + } else { + dart->dma_min = dma_range[0]; + dart->dma_max = dma_range[0] + dma_range[1] - 1; + if ((dart->dma_min ^ dart->dma_max) & ~DMA_BIT_MASK(dart->ias)) { + dev_err(&pdev->dev, "Invalid DMA range for ias=%d\n", + dart->ias); + goto err_clk_disable; + } + dev_info(&pdev->dev, "Limiting DMA range to %pad..%pad\n", + &dart->dma_min, &dart->dma_max); + } + if (dart->num_streams > DART_MAX_STREAMS) { dev_err(&pdev->dev, "Too many streams (%d > %d)\n", dart->num_streams, DART_MAX_STREAMS); From 20cb1db90f62fc59491c2886eb858097bfc545b4 Mon Sep 17 00:00:00 2001 From: Hector Martin Date: Thu, 23 Nov 2023 18:08:50 +0900 Subject: [PATCH 0047/3902] iommu: apple-dart: Check for fwspec in the device probe path We need to check for a fwspec in the probe path, to ensure that the driver does not probe as a bus iommu driver. This, along with related fixes to the IOMMU core code, fixes races and issues when multiple IOMMUs assigned to the same device probe at different times. Suggested-by: Jason Gunthorpe Signed-off-by: Hector Martin iommu: apple-dart: --- drivers/iommu/apple-dart.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/iommu/apple-dart.c b/drivers/iommu/apple-dart.c index 6dba6e58373011..0e8a58f813b2ba 100644 --- a/drivers/iommu/apple-dart.c +++ b/drivers/iommu/apple-dart.c @@ -779,7 +779,7 @@ static struct iommu_device *apple_dart_probe_device(struct device *dev) struct apple_dart_stream_map *stream_map; int i; - if (!cfg) + if (!dev_iommu_fwspec_get(dev) || !cfg) return ERR_PTR(-ENODEV); for_each_stream_map(i, cfg, stream_map) From d4203d8b078cfc00055f3ff288fc3383716ff32e Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Sun, 24 Mar 2024 18:06:46 +0100 Subject: [PATCH 0048/3902] iommu/of: Free fwspec on probe deferrel For devices with multiple iommus of_iommu_configure_device() potentially inits the fwspec for one of the iommus but another iommu device might have not yet been probe resulting in -EPROBE_DEFER. Clear the fwspec in such cases to ensure the next of_iommu_configure() call retries to configure all iommus. Signed-off-by: Janne Grunau --- drivers/iommu/of_iommu.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/drivers/iommu/of_iommu.c b/drivers/iommu/of_iommu.c index 6b989a62def20e..1ccd33b9f351bf 100644 --- a/drivers/iommu/of_iommu.c +++ b/drivers/iommu/of_iommu.c @@ -147,6 +147,8 @@ int of_iommu_configure(struct device *dev, struct device_node *master_np, of_pci_check_device_ats(dev, master_np); } else { err = of_iommu_configure_device(master_np, dev, id); + if (err == -EPROBE_DEFER) + iommu_fwspec_free(dev); } if (err && dev_iommu_present) From 5c0d0c04ca08bd3332791c6c6035bc205a74dfa2 Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Sat, 22 Oct 2022 12:00:21 +0200 Subject: [PATCH 0049/3902] iommu: Add IOMMU_RESV_TRANSLATED for non 1:1 mapped reserved regions The display controller in Apple silicon SoCs uses bootloader mappings which require IOMMU translation. Signed-off-by: Janne Grunau --- drivers/iommu/iommu.c | 24 ++++++++++++++++++++---- include/linux/iommu.h | 10 ++++++++++ 2 files changed, 30 insertions(+), 4 deletions(-) diff --git a/drivers/iommu/iommu.c b/drivers/iommu/iommu.c index 59244c744eabd2..0e09bf2e31c9e9 100644 --- a/drivers/iommu/iommu.c +++ b/drivers/iommu/iommu.c @@ -90,6 +90,7 @@ static const char * const iommu_group_resv_type_string[] = { [IOMMU_RESV_RESERVED] = "reserved", [IOMMU_RESV_MSI] = "msi", [IOMMU_RESV_SW_MSI] = "msi", + [IOMMU_RESV_TRANSLATED] = "translated", }; #define IOMMU_CMD_LINE_DMA_API BIT(0) @@ -2841,10 +2842,11 @@ void iommu_put_resv_regions(struct device *dev, struct list_head *list) } EXPORT_SYMBOL(iommu_put_resv_regions); -struct iommu_resv_region *iommu_alloc_resv_region(phys_addr_t start, - size_t length, int prot, - enum iommu_resv_type type, - gfp_t gfp) +struct iommu_resv_region *iommu_alloc_resv_region_tr(phys_addr_t start, + dma_addr_t dva_start, + size_t length, int prot, + enum iommu_resv_type type, + gfp_t gfp) { struct iommu_resv_region *region; @@ -2854,11 +2856,25 @@ struct iommu_resv_region *iommu_alloc_resv_region(phys_addr_t start, INIT_LIST_HEAD(®ion->list); region->start = start; + if (type == IOMMU_RESV_TRANSLATED) + region->dva = dva_start; region->length = length; region->prot = prot; region->type = type; return region; } +EXPORT_SYMBOL_GPL(iommu_alloc_resv_region_tr); + +struct iommu_resv_region *iommu_alloc_resv_region(phys_addr_t start, + size_t length, int prot, + enum iommu_resv_type type, + gfp_t gfp) +{ + if (type == IOMMU_RESV_TRANSLATED) + return NULL; + + return iommu_alloc_resv_region_tr(start, 0, length, prot, type, gfp); +} EXPORT_SYMBOL_GPL(iommu_alloc_resv_region); void iommu_set_default_passthrough(bool cmd_line) diff --git a/include/linux/iommu.h b/include/linux/iommu.h index c30d12e16473df..779cddf6ebcd65 100644 --- a/include/linux/iommu.h +++ b/include/linux/iommu.h @@ -289,12 +289,18 @@ enum iommu_resv_type { IOMMU_RESV_MSI, /* Software-managed MSI translation window */ IOMMU_RESV_SW_MSI, + /* + * Memory regions which must be mapped with the specified mapping + * at all times. + */ + IOMMU_RESV_TRANSLATED, }; /** * struct iommu_resv_region - descriptor for a reserved memory region * @list: Linked list pointers * @start: System physical start address of the region + * @start: Device virtual start address of the region for IOMMU_RESV_TRANSLATED * @length: Length of the region in bytes * @prot: IOMMU Protection flags (READ/WRITE/...) * @type: Type of the reserved region @@ -303,6 +309,7 @@ enum iommu_resv_type { struct iommu_resv_region { struct list_head list; phys_addr_t start; + dma_addr_t dva; size_t length; int prot; enum iommu_resv_type type; @@ -936,6 +943,9 @@ extern bool iommu_default_passthrough(void); extern struct iommu_resv_region * iommu_alloc_resv_region(phys_addr_t start, size_t length, int prot, enum iommu_resv_type type, gfp_t gfp); +extern struct iommu_resv_region * +iommu_alloc_resv_region_tr(phys_addr_t start, dma_addr_t dva_start, size_t length, + int prot, enum iommu_resv_type type, gfp_t gfp); extern int iommu_get_group_resv_regions(struct iommu_group *group, struct list_head *head); From 7b7b294d757d96651e8da4613f6983d6a3d9223a Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Sat, 22 Oct 2022 12:24:54 +0200 Subject: [PATCH 0050/3902] iommu: Parse translated reserved regions These regions are setup by the boot loader and require an iommu to translate arbitray physical to device VA mappings. Signed-off-by: Janne Grunau --- drivers/iommu/dma-iommu.c | 9 +++++++-- drivers/iommu/of_iommu.c | 11 +++++++---- 2 files changed, 14 insertions(+), 6 deletions(-) diff --git a/drivers/iommu/dma-iommu.c b/drivers/iommu/dma-iommu.c index f1fb27681b0bdc..f0cc2c3d9c5d29 100644 --- a/drivers/iommu/dma-iommu.c +++ b/drivers/iommu/dma-iommu.c @@ -572,8 +572,13 @@ static int iova_reserve_iommu_regions(struct device *dev, if (region->type == IOMMU_RESV_SW_MSI) continue; - lo = iova_pfn(iovad, region->start); - hi = iova_pfn(iovad, region->start + region->length - 1); + if (region->type == IOMMU_RESV_TRANSLATED) { + lo = iova_pfn(iovad, region->dva); + hi = iova_pfn(iovad, region->dva + region->length - 1); + } else { + lo = iova_pfn(iovad, region->start); + hi = iova_pfn(iovad, region->start + region->length - 1); + } reserve_iova(iovad, lo, hi); if (region->type == IOMMU_RESV_MSI) diff --git a/drivers/iommu/of_iommu.c b/drivers/iommu/of_iommu.c index 1ccd33b9f351bf..69377addd6cebb 100644 --- a/drivers/iommu/of_iommu.c +++ b/drivers/iommu/of_iommu.c @@ -189,9 +189,7 @@ iommu_resv_region_get_type(struct device *dev, if (start == phys->start && end == phys->end) return IOMMU_RESV_DIRECT; - dev_warn(dev, "treating non-direct mapping [%pr] -> [%pap-%pap] as reservation\n", phys, - &start, &end); - return IOMMU_RESV_RESERVED; + return IOMMU_RESV_TRANSLATED; } /** @@ -262,8 +260,13 @@ void of_iommu_get_resv_regions(struct device *dev, struct list_head *list) } type = iommu_resv_region_get_type(dev, &phys, iova, length); - region = iommu_alloc_resv_region(iova, length, prot, type, + if (type == IOMMU_RESV_TRANSLATED) + region = iommu_alloc_resv_region_tr(phys.start, iova, length, prot, type, + GFP_KERNEL); + else + region = iommu_alloc_resv_region(iova, length, prot, type, GFP_KERNEL); + if (region) list_add_tail(®ion->list, list); } From 53def5f1282fdd75caa74f6e34d63887bf5de4b4 Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Tue, 1 Apr 2025 20:31:56 +0200 Subject: [PATCH 0051/3902] iommu: Rename iommu_create_device_direct_mappings() It will be used to create firmware mappings which require a paging domain and mappings installed at specific IOVA. Signed-off-by: Janne Grunau --- drivers/iommu/iommu.c | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/drivers/iommu/iommu.c b/drivers/iommu/iommu.c index 0e09bf2e31c9e9..31d1be3138a4f6 100644 --- a/drivers/iommu/iommu.c +++ b/drivers/iommu/iommu.c @@ -133,8 +133,8 @@ static void __iommu_group_set_domain_nofail(struct iommu_group *group, static int iommu_setup_default_domain(struct iommu_group *group, int target_type); -static int iommu_create_device_direct_mappings(struct iommu_domain *domain, - struct device *dev); +static int iommu_create_device_fw_mappings(struct iommu_domain *domain, + struct device *dev); static ssize_t iommu_group_store_type(struct iommu_group *group, const char *buf, size_t count); static struct group_device *iommu_group_alloc_device(struct iommu_group *group, @@ -627,7 +627,7 @@ static int __iommu_probe_device(struct device *dev, struct list_head *group_list list_add_tail(&gdev->list, &group->devices); WARN_ON(group->default_domain && !group->domain); if (group->default_domain) - iommu_create_device_direct_mappings(group->default_domain, dev); + iommu_create_device_fw_mappings(group->default_domain, dev); if (group->domain) { ret = __iommu_device_set_domain(group, dev, group->domain, 0); if (ret) @@ -1155,8 +1155,8 @@ int iommu_group_set_name(struct iommu_group *group, const char *name) } EXPORT_SYMBOL_GPL(iommu_group_set_name); -static int iommu_create_device_direct_mappings(struct iommu_domain *domain, - struct device *dev) +static int iommu_create_device_fw_mappings(struct iommu_domain *domain, + struct device *dev) { struct iommu_resv_region *entry; struct list_head mappings; @@ -2999,7 +2999,7 @@ static int iommu_setup_default_domain(struct iommu_group *group, struct iommu_domain *old_dom = group->default_domain; struct group_device *gdev; struct iommu_domain *dom; - bool direct_failed; + bool fw_failed; int req_type; int ret; @@ -3029,10 +3029,10 @@ static int iommu_setup_default_domain(struct iommu_group *group, * mapped before their device is attached, in order to guarantee * continuity with any FW activity */ - direct_failed = false; + fw_failed = false; for_each_group_device(group, gdev) { - if (iommu_create_device_direct_mappings(dom, gdev->dev)) { - direct_failed = true; + if (iommu_create_device_fw_mappings(dom, gdev->dev)) { + fw_failed = true; dev_warn_once( gdev->dev->iommu->iommu_dev->dev, "IOMMU driver was not able to establish FW requested direct mapping."); @@ -3064,9 +3064,9 @@ static int iommu_setup_default_domain(struct iommu_group *group, * trying again after attaching. If this happens it means the device * will not continuously have the IOMMU_RESV_DIRECT map. */ - if (direct_failed) { + if (fw_failed) { for_each_group_device(group, gdev) { - ret = iommu_create_device_direct_mappings(dom, gdev->dev); + ret = iommu_create_device_fw_mappings(dom, gdev->dev); if (ret) goto err_restore_domain; } From 59b0fbc2032c7605ea74134d2dae96cf1f2069e9 Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Tue, 1 Apr 2025 20:20:45 +0200 Subject: [PATCH 0052/3902] iommu: Handle translated device firmware mappings Signed-off-by: Janne Grunau --- drivers/iommu/iommu.c | 31 ++++++++++++++++++++++++++----- include/linux/iommu.h | 2 ++ 2 files changed, 28 insertions(+), 5 deletions(-) diff --git a/drivers/iommu/iommu.c b/drivers/iommu/iommu.c index 31d1be3138a4f6..06e5e7d6d53897 100644 --- a/drivers/iommu/iommu.c +++ b/drivers/iommu/iommu.c @@ -1173,27 +1173,35 @@ static int iommu_create_device_fw_mappings(struct iommu_domain *domain, /* We need to consider overlapping regions for different devices */ list_for_each_entry(entry, &mappings, list) { - dma_addr_t start, end, addr; + dma_addr_t start, end, addr, iova; size_t map_size = 0; if (entry->type == IOMMU_RESV_DIRECT) dev->iommu->require_direct = 1; + if (entry->type == IOMMU_RESV_TRANSLATED) + dev->iommu->require_translated = 1; if ((entry->type != IOMMU_RESV_DIRECT && - entry->type != IOMMU_RESV_DIRECT_RELAXABLE) || + entry->type != IOMMU_RESV_DIRECT_RELAXABLE && + entry->type != IOMMU_RESV_TRANSLATED) || !iommu_is_dma_domain(domain)) continue; start = ALIGN(entry->start, pg_size); end = ALIGN(entry->start + entry->length, pg_size); - for (addr = start; addr <= end; addr += pg_size) { + if (entry->type == IOMMU_RESV_TRANSLATED) + iova = ALIGN(entry->dva, pg_size); + else + iova = start; + + for (addr = start; addr <= end; addr += pg_size, iova += pg_size) { phys_addr_t phys_addr; if (addr == end) goto map_end; - phys_addr = iommu_iova_to_phys(domain, addr); + phys_addr = iommu_iova_to_phys(domain, iova); if (!phys_addr) { map_size += pg_size; continue; @@ -1201,7 +1209,7 @@ static int iommu_create_device_fw_mappings(struct iommu_domain *domain, map_end: if (map_size) { - ret = iommu_map(domain, addr - map_size, + ret = iommu_map(domain, iova - map_size, addr - map_size, map_size, entry->prot, GFP_KERNEL); if (ret) @@ -2303,6 +2311,19 @@ static int __iommu_device_set_domain(struct iommu_group *group, "Firmware has requested this device have a 1:1 IOMMU mapping, rejecting configuring the device without a 1:1 mapping. Contact your platform vendor.\n"); return -EINVAL; } + /* + * If the device requires IOMMU_RESV_TRANSLATED then we cannot allow + * the identy or blocking domain to be attached as it does not contain + * the required translated mapping. + */ + if (dev->iommu->require_translated && + (new_domain->type == IOMMU_DOMAIN_IDENTITY || + new_domain->type == IOMMU_DOMAIN_BLOCKED || + new_domain == group->blocking_domain)) { + dev_warn(dev, + "Firmware has requested this device have a translated IOMMU mapping, rejecting configuring the device without a translated mapping. Contact your platform vendor.\n"); + return -EINVAL; + } if (dev->iommu->attach_deferred) { if (new_domain == group->default_domain) diff --git a/include/linux/iommu.h b/include/linux/iommu.h index 779cddf6ebcd65..882573e7a84c31 100644 --- a/include/linux/iommu.h +++ b/include/linux/iommu.h @@ -844,6 +844,7 @@ struct iommu_fault_param { * @pci_32bit_workaround: Limit DMA allocations to 32-bit IOVAs * @require_direct: device requires IOMMU_RESV_DIRECT regions * @shadow_on_flush: IOTLB flushes are used to sync shadow tables + * @require_translated: device requires IOMMU_RESV_TRANSLATED regions * * TODO: migrate other per device data pointers under iommu_dev_data, e.g. * struct iommu_group *iommu_group; @@ -859,6 +860,7 @@ struct dev_iommu { u32 pci_32bit_workaround:1; u32 require_direct:1; u32 shadow_on_flush:1; + u32 require_translated:1; }; int iommu_device_register(struct iommu_device *iommu, From f96d12feffa3605ade1880476d3389019f65e4e9 Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Sun, 16 Mar 2025 22:53:21 +0100 Subject: [PATCH 0053/3902] iommu/dart: Use separate iommu_ops for DARTs w/o bypass These DARTs do not support identity mappings so use a struct iommu_ops without default identity domain. Since commit 3bc0102835f6 ("iommu: apple-dart: Allow mismatched bypass support") groups with mismatched bypass support are supported so the check for bypass support in apple_dart_attach_dev_identity() has to stay. Signed-off-by: Janne Grunau --- drivers/iommu/apple-dart.c | 53 +++++++++++++++++++++++++------------- 1 file changed, 35 insertions(+), 18 deletions(-) diff --git a/drivers/iommu/apple-dart.c b/drivers/iommu/apple-dart.c index 0e8a58f813b2ba..b04d23ae7e89b6 100644 --- a/drivers/iommu/apple-dart.c +++ b/drivers/iommu/apple-dart.c @@ -1006,6 +1006,11 @@ static int apple_dart_def_domain_type(struct device *dev) return 0; } +static int apple_dart_def_domain_type_dma(struct device *dev) +{ + return IOMMU_DOMAIN_DMA; +} + #ifndef CONFIG_PCIE_APPLE_MSI_DOORBELL_ADDR /* Keep things compiling when CONFIG_PCI_APPLE isn't selected */ #define CONFIG_PCIE_APPLE_MSI_DOORBELL_ADDR 0 @@ -1031,27 +1036,36 @@ static void apple_dart_get_resv_regions(struct device *dev, iommu_dma_get_resv_regions(dev, head); } +#define APPLE_DART_IOMMU_COMMON_OPS() \ + .domain_alloc_paging = apple_dart_domain_alloc_paging, \ + .probe_device = apple_dart_probe_device, \ + .release_device = apple_dart_release_device, \ + .device_group = apple_dart_device_group, \ + .of_xlate = apple_dart_of_xlate, \ + .get_resv_regions = apple_dart_get_resv_regions, \ + .owner = THIS_MODULE, \ + .default_domain_ops = &(const struct iommu_domain_ops) { \ + .attach_dev = apple_dart_attach_dev_paging, \ + .map_pages = apple_dart_map_pages, \ + .unmap_pages = apple_dart_unmap_pages, \ + .flush_iotlb_all = apple_dart_flush_iotlb_all, \ + .iotlb_sync = apple_dart_iotlb_sync, \ + .iotlb_sync_map = apple_dart_iotlb_sync_map, \ + .iova_to_phys = apple_dart_iova_to_phys, \ + .free = apple_dart_domain_free, \ + } + static const struct iommu_ops apple_dart_iommu_ops = { .identity_domain = &apple_dart_identity_domain, .blocked_domain = &apple_dart_blocked_domain, - .domain_alloc_paging = apple_dart_domain_alloc_paging, - .probe_device = apple_dart_probe_device, - .release_device = apple_dart_release_device, - .device_group = apple_dart_device_group, - .of_xlate = apple_dart_of_xlate, .def_domain_type = apple_dart_def_domain_type, - .get_resv_regions = apple_dart_get_resv_regions, - .owner = THIS_MODULE, - .default_domain_ops = &(const struct iommu_domain_ops) { - .attach_dev = apple_dart_attach_dev_paging, - .map_pages = apple_dart_map_pages, - .unmap_pages = apple_dart_unmap_pages, - .flush_iotlb_all = apple_dart_flush_iotlb_all, - .iotlb_sync = apple_dart_iotlb_sync, - .iotlb_sync_map = apple_dart_iotlb_sync_map, - .iova_to_phys = apple_dart_iova_to_phys, - .free = apple_dart_domain_free, - } + APPLE_DART_IOMMU_COMMON_OPS() +}; + +static const struct iommu_ops apple_dart_iommu_no_bypass_ops = { + .blocked_domain = &apple_dart_blocked_domain, + .def_domain_type = apple_dart_def_domain_type_dma, + APPLE_DART_IOMMU_COMMON_OPS() }; static irqreturn_t apple_dart_t8020_irq(int irq, void *dev) @@ -1257,7 +1271,10 @@ static int apple_dart_probe(struct platform_device *pdev) if (ret) goto err_free_irq; - ret = iommu_device_register(&dart->iommu, &apple_dart_iommu_ops, dev); + if (!dart->supports_bypass) + ret = iommu_device_register(&dart->iommu, &apple_dart_iommu_no_bypass_ops, dev); + else + ret = iommu_device_register(&dart->iommu, &apple_dart_iommu_ops, dev); if (ret) goto err_sysfs_remove; From 1bd410d07a2f4c8fbefa723aa2bbe327edb74ad6 Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Sun, 30 Mar 2025 23:05:27 +0200 Subject: [PATCH 0054/3902] iommu/dart: Use virtual memory ttbr entries in apple_dart_cfg Locked DARTs can not modify ttbr entries. To ensure atomic updates of PTEs in the L1 table the DART driver will copy entries to the preallocated L1 table. This requires access to io-pgtable-dart's tables. For all other DARTs this moves virt_to_phys() calls into the DART driver. Signed-off-by: Janne Grunau --- drivers/iommu/apple-dart.c | 7 ++++--- drivers/iommu/io-pgtable-dart.c | 2 +- include/linux/io-pgtable.h | 2 +- 3 files changed, 6 insertions(+), 5 deletions(-) diff --git a/drivers/iommu/apple-dart.c b/drivers/iommu/apple-dart.c index b04d23ae7e89b6..e3eccabb43bab7 100644 --- a/drivers/iommu/apple-dart.c +++ b/drivers/iommu/apple-dart.c @@ -583,9 +583,10 @@ apple_dart_setup_translation(struct apple_dart_domain *domain, struct io_pgtable_cfg *pgtbl_cfg = &io_pgtable_ops_to_pgtable(domain->pgtbl_ops)->cfg; - for (i = 0; i < pgtbl_cfg->apple_dart_cfg.n_ttbrs; ++i) - apple_dart_hw_set_ttbr(stream_map, i, - pgtbl_cfg->apple_dart_cfg.ttbr[i]); + for (i = 0; i < pgtbl_cfg->apple_dart_cfg.n_ttbrs; ++i) { + u64 ttbr = virt_to_phys(pgtbl_cfg->apple_dart_cfg.ttbr[i]); + apple_dart_hw_set_ttbr(stream_map, i, ttbr); + } for (; i < stream_map->dart->hw->ttbr_count; ++i) apple_dart_hw_clear_ttbr(stream_map, i); diff --git a/drivers/iommu/io-pgtable-dart.c b/drivers/iommu/io-pgtable-dart.c index 54d287cc0dd1b8..2334a2ea04bb3a 100644 --- a/drivers/iommu/io-pgtable-dart.c +++ b/drivers/iommu/io-pgtable-dart.c @@ -435,7 +435,7 @@ apple_dart_alloc_pgtable(struct io_pgtable_cfg *cfg, void *cookie) iommu_alloc_pages_sz(GFP_KERNEL, DART_GRANULE(data)); if (!data->pgd[i]) goto out_free_data; - cfg->apple_dart_cfg.ttbr[i] = virt_to_phys(data->pgd[i]); + cfg->apple_dart_cfg.ttbr[i] = data->pgd[i]; } return &data->iop; diff --git a/include/linux/io-pgtable.h b/include/linux/io-pgtable.h index 8a823c6f2b4a88..9800b3c8a597ae 100644 --- a/include/linux/io-pgtable.h +++ b/include/linux/io-pgtable.h @@ -178,7 +178,7 @@ struct io_pgtable_cfg { } arm_mali_lpae_cfg; struct { - u64 ttbr[4]; + void *ttbr[4]; u32 n_ttbrs; u32 n_levels; } apple_dart_cfg; From 0e776de66ff9aa7222ada537c60d9dc85eabd109 Mon Sep 17 00:00:00 2001 From: Alyssa Rosenzweig Date: Mon, 10 Feb 2025 14:39:53 -0500 Subject: [PATCH 0055/3902] iommu/dart: Track if the DART is locked Some DARTs are locked at boot-time. That means they are already configured and we cannot change their configuration, which requires special handling. Locked DARTs are identified in the configuration register. Check this bit when probing and save the result so we can handle accordingly. Signed-off-by: Alyssa Rosenzweig Signed-off-by: Janne Grunau --- drivers/iommu/apple-dart.c | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/drivers/iommu/apple-dart.c b/drivers/iommu/apple-dart.c index e3eccabb43bab7..6ed50d369c47eb 100644 --- a/drivers/iommu/apple-dart.c +++ b/drivers/iommu/apple-dart.c @@ -203,6 +203,7 @@ struct apple_dart_hw { * @lock: lock for hardware operations involving this dart * @pgsize: pagesize supported by this DART * @supports_bypass: indicates if this DART supports bypass mode + * @locked: indicates if this DART is locked * @sid2group: maps stream ids to iommu_groups * @iommu: iommu core device */ @@ -224,6 +225,7 @@ struct apple_dart { u32 num_streams; u32 supports_bypass : 1; u32 four_level : 1; + u32 locked : 1; dma_addr_t dma_min; dma_addr_t dma_max; @@ -858,6 +860,8 @@ static int apple_dart_of_xlate(struct device *dev, return -EINVAL; if (cfg_dart->ias != dart->ias) return -EINVAL; + if (cfg_dart->locked != dart->locked) + return -EINVAL; } cfg->supports_bypass &= dart->supports_bypass; @@ -1159,6 +1163,11 @@ static irqreturn_t apple_dart_irq(int irq, void *dev) return ret; } +static bool apple_dart_is_locked(struct apple_dart *dart) +{ + return !!(readl(dart->regs + dart->hw->lock) & dart->hw->lock_bit); +} + static int apple_dart_probe(struct platform_device *pdev) { int ret; @@ -1256,6 +1265,7 @@ static int apple_dart_probe(struct platform_device *pdev) goto err_clk_disable; } + dart->locked = apple_dart_is_locked(dart); ret = apple_dart_hw_reset(dart); if (ret) goto err_clk_disable; @@ -1283,9 +1293,9 @@ static int apple_dart_probe(struct platform_device *pdev) dev_info( &pdev->dev, - "DART [pagesize %x, %d streams, bypass support: %d, bypass forced: %d, AS %d -> %d] initialized\n", + "DART [pagesize %x, %d streams, bypass support: %d, bypass forced: %d, locked: %d, AS %d -> %d] initialized\n", dart->pgsize, dart->num_streams, dart->supports_bypass, - dart->pgsize > PAGE_SIZE, dart->ias, dart->oas); + dart->pgsize > PAGE_SIZE, dart->locked, dart->ias, dart->oas); return 0; err_sysfs_remove: From 633e1be2c76eb2d8bcf53dc4df143db5dc58319b Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Sun, 30 Mar 2025 23:53:20 +0200 Subject: [PATCH 0056/3902] iommu/dart: Add iommu_ops for locked DARTs A locked DART has partially read-only MMIO registers. Most importantly the TTBR registers are read-only. Apple's bootloader sets the DART up for its intended use before locking it. The single used streams has a L1 translation table allocated in carved out memory and its TTBRs point to this table. In addition translation and bypass can not be disabled or enabled so a locked DART must not offer default identity or blocked domains. The only observed locked DART is for the display coprocessor. It requires careful handling as translation errors result in unrecoverable crashes of the display coprocessor. Signed-off-by: Janne Grunau --- drivers/iommu/apple-dart.c | 164 ++++++++++++++++++++++++++++++++++++- 1 file changed, 161 insertions(+), 3 deletions(-) diff --git a/drivers/iommu/apple-dart.c b/drivers/iommu/apple-dart.c index 6ed50d369c47eb..b6fef32ecf0491 100644 --- a/drivers/iommu/apple-dart.c +++ b/drivers/iommu/apple-dart.c @@ -235,6 +235,8 @@ struct apple_dart { u32 save_tcr[DART_MAX_STREAMS]; u32 save_ttbr[DART_MAX_STREAMS][DART_MAX_TTBR]; + + u64 *locked_ttbr[DART_MAX_STREAMS][DART_MAX_TTBR]; }; /* @@ -386,6 +388,82 @@ apple_dart_hw_clear_all_ttbrs(struct apple_dart_stream_map *stream_map) apple_dart_hw_clear_ttbr(stream_map, i); } +static int +apple_dart_hw_map_locked_ttbr(struct apple_dart_stream_map *stream_map, u8 idx) +{ + struct apple_dart *dart = stream_map->dart; + int sid; + + for_each_set_bit(sid, stream_map->sidmap, dart->num_streams) { + u32 ttbr; + phys_addr_t phys; + u64 *l1_tbl; + + ttbr = readl(dart->regs + DART_TTBR(dart, sid, idx)); + + if (!(ttbr & dart->hw->ttbr_valid)) { + dev_err(dart->dev, "Invalid ttbr[%u] for locked dart\n", + idx); + return -EIO; + } + + ttbr &= ~dart->hw->ttbr_valid; + + if (dart->hw->ttbr_addr_field_shift) + ttbr >>= dart->hw->ttbr_addr_field_shift; + phys = ((phys_addr_t) ttbr) << dart->hw->ttbr_shift; + + l1_tbl = devm_memremap(dart->dev, phys, dart->pgsize, + MEMREMAP_WB); + if (!l1_tbl) + return -ENOMEM; + + dart->locked_ttbr[sid][idx] = l1_tbl; + } + + return 0; +} + +static int +apple_dart_hw_unmap_locked_ttbr(struct apple_dart_stream_map *stream_map, + u8 idx) +{ + struct apple_dart *dart = stream_map->dart; + int sid; + + for_each_set_bit(sid, stream_map->sidmap, dart->num_streams) { + /* TODO: locked L1 table might need to be restored to boot state */ + if (dart->locked_ttbr[sid][idx]) { + memset(dart->locked_ttbr[sid][idx], 0, dart->pgsize); + devm_memunmap(dart->dev, dart->locked_ttbr[sid][idx]); + } + dart->locked_ttbr[sid][idx] = NULL; + } + + return 0; +} + +static int +apple_dart_hw_sync_locked(struct io_pgtable_cfg *cfg, + struct apple_dart_stream_map *stream_map) +{ + struct apple_dart *dart = stream_map->dart; + int sid; + + for_each_set_bit(sid, stream_map->sidmap, dart->num_streams) { + for (int idx = 0; idx < dart->hw->ttbr_count; idx++) { + u64 *ttbrep = dart->locked_ttbr[sid][idx]; + u64 *ptep = cfg->apple_dart_cfg.ttbr[idx]; + if (!ttbrep || !ptep) + continue; + for (int entry = 0; entry < dart->pgsize / sizeof(*ptep); entry++) + ttbrep[entry] = ptep[entry]; + } + } + + return 0; +} + static int apple_dart_t8020_hw_stream_command(struct apple_dart_stream_map *stream_map, u32 command) @@ -507,6 +585,8 @@ static void apple_dart_domain_flush_tlb(struct apple_dart_domain *domain) int i, j; struct apple_dart_atomic_stream_map *domain_stream_map; struct apple_dart_stream_map stream_map; + struct io_pgtable_cfg *pgtbl_cfg = + &io_pgtable_ops_to_pgtable(domain->pgtbl_ops)->cfg; for_each_stream_map(i, domain, domain_stream_map) { stream_map.dart = domain_stream_map->dart; @@ -515,6 +595,10 @@ static void apple_dart_domain_flush_tlb(struct apple_dart_domain *domain) stream_map.sidmap[j] = atomic_long_read(&domain_stream_map->sidmap[j]); WARN_ON(pm_runtime_get_sync(stream_map.dart->dev) < 0); + + if (stream_map.dart->locked) + apple_dart_hw_sync_locked(pgtbl_cfg, &stream_map); + stream_map.dart->hw->invalidate_tlb(&stream_map); pm_runtime_put(stream_map.dart->dev); } @@ -597,6 +681,24 @@ apple_dart_setup_translation(struct apple_dart_domain *domain, stream_map->dart->hw->invalidate_tlb(stream_map); } +static void +apple_dart_setup_translation_locked(struct apple_dart_domain *domain, + struct apple_dart_stream_map *stream_map) +{ + int i; + struct io_pgtable_cfg *pgtbl_cfg = + &io_pgtable_ops_to_pgtable(domain->pgtbl_ops)->cfg; + + /* Locked DARTs are set up by the bootloader. */ + for (i = 0; i < pgtbl_cfg->apple_dart_cfg.n_ttbrs; ++i) + apple_dart_hw_map_locked_ttbr(stream_map, i); + for (; i < stream_map->dart->hw->ttbr_count; ++i) + apple_dart_hw_unmap_locked_ttbr(stream_map, i); + + apple_dart_hw_sync_locked(pgtbl_cfg, stream_map); + stream_map->dart->hw->invalidate_tlb(stream_map); +} + static int apple_dart_finalize_domain(struct apple_dart_domain *dart_domain, struct apple_dart_master_cfg *cfg) { @@ -630,6 +732,42 @@ static int apple_dart_finalize_domain(struct apple_dart_domain *dart_domain, .iommu_dev = dart->dev, }; + if (dart->locked) { + unsigned long *sidmap; + int sid; + u32 ttbr; + + /* Locked DARTs can only have a single stream bound */ + sidmap = cfg->stream_maps[0].sidmap; + sid = find_first_bit(sidmap, dart->num_streams); + + WARN_ON((sid < 0) || bitmap_weight(sidmap, dart->num_streams) > 1); + ttbr = readl(dart->regs + DART_TTBR(dart, sid, 0)); + + WARN_ON(!(ttbr & dart->hw->ttbr_valid)); + + /* If the DART is locked, we need to keep the translation level count. */ + if (dart->hw->tcr_4level && dart->ias > 36) { + if (readl(dart->regs + DART_TCR(dart, sid)) & dart->hw->tcr_4level) { + if (ias < 37) { + dev_info(dart->dev, "Expanded to ias=37 due to lock\n"); + pgtbl_cfg.ias = 37; + } + } else if (ias > 36) { + dev_info(dart->dev, "Limited to ias=36 due to lock\n"); + pgtbl_cfg.ias = 36; + if (dart->dma_min == 0 && dma_max == DMA_BIT_MASK(dart->ias)) { + dma_max = DMA_BIT_MASK(pgtbl_cfg.ias); + } else if ((dart->dma_min ^ dma_max) & ~DMA_BIT_MASK(36)) { + dev_err(dart->dev, + "Invalid DMA range for locked 3-level PT\n"); + ret = -ENOMEM; + goto done; + } + } + } + } + dart_domain->pgtbl_ops = alloc_io_pgtable_ops(dart->hw->fmt, &pgtbl_cfg, &dart_domain->domain); if (!dart_domain->pgtbl_ops) { @@ -710,8 +848,13 @@ static int apple_dart_attach_dev_paging(struct iommu_domain *domain, if (ret) goto err; - for_each_stream_map(i, cfg, stream_map) - apple_dart_setup_translation(dart_domain, stream_map); + for_each_stream_map(i, cfg, stream_map) { + if (!stream_map->dart->locked) + apple_dart_setup_translation(dart_domain, stream_map); + else + apple_dart_setup_translation_locked(dart_domain, + stream_map); + } err: for_each_stream_map(i, cfg, stream_map) @@ -795,8 +938,16 @@ static struct iommu_device *apple_dart_probe_device(struct device *dev) static void apple_dart_release_device(struct device *dev) { + int i, j; + struct apple_dart_stream_map *stream_map; struct apple_dart_master_cfg *cfg = dev_iommu_priv_get(dev); + for_each_stream_map(j, cfg, stream_map) { + if (stream_map->dart->locked) + for (i = 0; i < stream_map->dart->hw->ttbr_count; ++i) + apple_dart_hw_unmap_locked_ttbr(stream_map, i); + } + kfree(cfg); } @@ -1073,6 +1224,11 @@ static const struct iommu_ops apple_dart_iommu_no_bypass_ops = { APPLE_DART_IOMMU_COMMON_OPS() }; +static const struct iommu_ops apple_dart_iommu_locked_ops = { + .def_domain_type = apple_dart_def_domain_type_dma, + APPLE_DART_IOMMU_COMMON_OPS() +}; + static irqreturn_t apple_dart_t8020_irq(int irq, void *dev) { struct apple_dart *dart = dev; @@ -1282,7 +1438,9 @@ static int apple_dart_probe(struct platform_device *pdev) if (ret) goto err_free_irq; - if (!dart->supports_bypass) + if (dart->locked) + ret = iommu_device_register(&dart->iommu, &apple_dart_iommu_locked_ops, dev); + else if (!dart->supports_bypass) ret = iommu_device_register(&dart->iommu, &apple_dart_iommu_no_bypass_ops, dev); else ret = iommu_device_register(&dart->iommu, &apple_dart_iommu_ops, dev); From 0c8941b343d8e7c3570562e56830fdea02795fd0 Mon Sep 17 00:00:00 2001 From: Alyssa Rosenzweig Date: Mon, 10 Feb 2025 14:39:54 -0500 Subject: [PATCH 0057/3902] iommu/dart: Support locked DARTs Locked DARTs cannot be reconfigured, therefore the reset/restore procedure can't work and should not be needed. Skip it and allowing locked DARTs to probe. Co-developed-by: Hector Martin Signed-off-by: Hector Martin Signed-off-by: Alyssa Rosenzweig Signed-off-by: Janne Grunau --- drivers/iommu/apple-dart.c | 28 ++++++++++++++++------------ 1 file changed, 16 insertions(+), 12 deletions(-) diff --git a/drivers/iommu/apple-dart.c b/drivers/iommu/apple-dart.c index b6fef32ecf0491..33e0589b728f8d 100644 --- a/drivers/iommu/apple-dart.c +++ b/drivers/iommu/apple-dart.c @@ -550,17 +550,9 @@ apple_dart_t8110_hw_invalidate_tlb(struct apple_dart_stream_map *stream_map) static int apple_dart_hw_reset(struct apple_dart *dart) { - u32 config; struct apple_dart_stream_map stream_map; int i; - config = readl(dart->regs + dart->hw->lock); - if (config & dart->hw->lock_bit) { - dev_err(dart->dev, "DART is locked down until reboot: %08x\n", - config); - return -EINVAL; - } - stream_map.dart = dart; bitmap_zero(stream_map.sidmap, DART_MAX_STREAMS); bitmap_set(stream_map.sidmap, 0, dart->num_streams); @@ -1422,9 +1414,11 @@ static int apple_dart_probe(struct platform_device *pdev) } dart->locked = apple_dart_is_locked(dart); - ret = apple_dart_hw_reset(dart); - if (ret) - goto err_clk_disable; + if (!dart->locked) { + ret = apple_dart_hw_reset(dart); + if (ret) + goto err_clk_disable; + } ret = request_irq(dart->irq, apple_dart_irq, IRQF_SHARED, "apple-dart fault handler", dart); @@ -1471,7 +1465,9 @@ static void apple_dart_remove(struct platform_device *pdev) { struct apple_dart *dart = platform_get_drvdata(pdev); - apple_dart_hw_reset(dart); + if (!dart->locked) + apple_dart_hw_reset(dart); + free_irq(dart->irq, dart); iommu_device_unregister(&dart->iommu); @@ -1589,6 +1585,10 @@ static __maybe_unused int apple_dart_suspend(struct device *dev) struct apple_dart *dart = dev_get_drvdata(dev); unsigned int sid, idx; + /* Locked DARTs can't be restored so skip saving their registers/. */ + if (dart->locked) + return 0; + for (sid = 0; sid < dart->num_streams; sid++) { dart->save_tcr[sid] = readl(dart->regs + DART_TCR(dart, sid)); for (idx = 0; idx < dart->hw->ttbr_count; idx++) @@ -1605,6 +1605,10 @@ static __maybe_unused int apple_dart_resume(struct device *dev) unsigned int sid, idx; int ret; + /* Locked DARTs can't be restored, and they should not need it */ + if (dart->locked) + return 0; + ret = apple_dart_hw_reset(dart); if (ret) { dev_err(dev, "Failed to reset DART on resume\n"); From 81de521252acd64efca275ec5e27fd01463c0734 Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Wed, 9 Apr 2025 11:20:28 +0200 Subject: [PATCH 0058/3902] fixup! iommu/dart: Track if the DART is locked --- drivers/iommu/apple-dart.c | 2 -- 1 file changed, 2 deletions(-) diff --git a/drivers/iommu/apple-dart.c b/drivers/iommu/apple-dart.c index 33e0589b728f8d..5ee1286841e42e 100644 --- a/drivers/iommu/apple-dart.c +++ b/drivers/iommu/apple-dart.c @@ -1003,8 +1003,6 @@ static int apple_dart_of_xlate(struct device *dev, return -EINVAL; if (cfg_dart->ias != dart->ias) return -EINVAL; - if (cfg_dart->locked != dart->locked) - return -EINVAL; } cfg->supports_bypass &= dart->supports_bypass; From 1dc9606bbb18b0f2d30d07e1ddc25a479eff3a5c Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Wed, 9 Apr 2025 18:28:19 +0200 Subject: [PATCH 0059/3902] fixup! iommu/dart: Support locked DARTs --- drivers/iommu/apple-dart.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/iommu/apple-dart.c b/drivers/iommu/apple-dart.c index 5ee1286841e42e..bf1e0ee2bd4705 100644 --- a/drivers/iommu/apple-dart.c +++ b/drivers/iommu/apple-dart.c @@ -1583,7 +1583,7 @@ static __maybe_unused int apple_dart_suspend(struct device *dev) struct apple_dart *dart = dev_get_drvdata(dev); unsigned int sid, idx; - /* Locked DARTs can't be restored so skip saving their registers/. */ + /* Locked DARTs can't be restored so skip saving their registers. */ if (dart->locked) return 0; From 5c33afc04bf0f89c2a2abc00f70fd65b0cb007d3 Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Wed, 9 Apr 2025 18:08:08 +0200 Subject: [PATCH 0060/3902] iommu: apple-dart: Support combinations of locked and unlocked DARTs This is required for the display sub-system. m1n1 locks the DART of the boot framebuffer to minimize the blackout for the transition from boot framebuffer to the full display driver. The display blacks out when the bootloader setup mapping of the framebuffer vanishes during dart_reset(). Under certain circumstances this results in an unrecoverable crash of display coprocessor. Signed-off-by: Janne Grunau --- drivers/iommu/apple-dart.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/drivers/iommu/apple-dart.c b/drivers/iommu/apple-dart.c index bf1e0ee2bd4705..61b8d905a3b32e 100644 --- a/drivers/iommu/apple-dart.c +++ b/drivers/iommu/apple-dart.c @@ -294,6 +294,7 @@ struct apple_dart_domain { struct apple_dart_master_cfg { /* Intersection of DART capabilitles */ u32 supports_bypass : 1; + u32 locked : 1; struct apple_dart_stream_map stream_maps[MAX_DARTS_PER_DEVICE]; }; @@ -994,6 +995,8 @@ static int apple_dart_of_xlate(struct device *dev, return -ENOMEM; /* Will be ANDed with DART capabilities */ cfg->supports_bypass = true; + /* Will be ORed with DART capabilities*/ + cfg->locked = false; } dev_iommu_priv_set(dev, cfg); @@ -1006,6 +1009,7 @@ static int apple_dart_of_xlate(struct device *dev, } cfg->supports_bypass &= dart->supports_bypass; + cfg->locked |= dart->locked; for (i = 0; i < MAX_DARTS_PER_DEVICE; ++i) { if (cfg->stream_maps[i].dart == dart) { From ef50e9a98dc7740fcf00805eff5cff03e756e399 Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Wed, 9 Apr 2025 18:20:02 +0200 Subject: [PATCH 0061/3902] iommu: apple-dart: Disallow identity domains for locked DARTs The register controlling bypass support is read-only for locked DARTs. In addition trnaslation can not be disabled so blocking domain has to be implemented with an empty translation table. Signed-off-by: Janne Grunau --- drivers/iommu/apple-dart.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/drivers/iommu/apple-dart.c b/drivers/iommu/apple-dart.c index 61b8d905a3b32e..1f176d2496e612 100644 --- a/drivers/iommu/apple-dart.c +++ b/drivers/iommu/apple-dart.c @@ -865,6 +865,9 @@ static int apple_dart_attach_dev_identity(struct iommu_domain *domain, if (!cfg->supports_bypass) return -EINVAL; + if (cfg->locked) + return -EINVAL; + for_each_stream_map(i, cfg, stream_map) WARN_ON(pm_runtime_get_sync(stream_map->dart->dev) < 0); @@ -892,6 +895,9 @@ static int apple_dart_attach_dev_blocked(struct iommu_domain *domain, struct apple_dart_stream_map *stream_map; int i; + if (cfg->locked) + return -EINVAL; + for_each_stream_map(i, cfg, stream_map) WARN_ON(pm_runtime_get_sync(stream_map->dart->dev) < 0); From f2ccffe6ae6f3f2f6cd0cbef053d005f0154f119 Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Wed, 9 Apr 2025 18:24:13 +0200 Subject: [PATCH 0062/3902] iommu: apple-dart: Revert separate iommu_ops for locked/bypass DARTs Since combination of DARTs with diverging locked and supports_bypass state have to be supported those DARTs have to share the same iommu_ops pointer (see iommu_fwspec_init()). Signed-off-by: Janne Grunau --- drivers/iommu/apple-dart.c | 62 ++++++++++++-------------------------- 1 file changed, 20 insertions(+), 42 deletions(-) diff --git a/drivers/iommu/apple-dart.c b/drivers/iommu/apple-dart.c index 1f176d2496e612..2f87da14e03a3d 100644 --- a/drivers/iommu/apple-dart.c +++ b/drivers/iommu/apple-dart.c @@ -1158,15 +1158,12 @@ static int apple_dart_def_domain_type(struct device *dev) return IOMMU_DOMAIN_IDENTITY; if (!cfg->supports_bypass) return IOMMU_DOMAIN_DMA; + if (cfg->locked) + return IOMMU_DOMAIN_DMA; return 0; } -static int apple_dart_def_domain_type_dma(struct device *dev) -{ - return IOMMU_DOMAIN_DMA; -} - #ifndef CONFIG_PCIE_APPLE_MSI_DOORBELL_ADDR /* Keep things compiling when CONFIG_PCI_APPLE isn't selected */ #define CONFIG_PCIE_APPLE_MSI_DOORBELL_ADDR 0 @@ -1192,41 +1189,27 @@ static void apple_dart_get_resv_regions(struct device *dev, iommu_dma_get_resv_regions(dev, head); } -#define APPLE_DART_IOMMU_COMMON_OPS() \ - .domain_alloc_paging = apple_dart_domain_alloc_paging, \ - .probe_device = apple_dart_probe_device, \ - .release_device = apple_dart_release_device, \ - .device_group = apple_dart_device_group, \ - .of_xlate = apple_dart_of_xlate, \ - .get_resv_regions = apple_dart_get_resv_regions, \ - .owner = THIS_MODULE, \ - .default_domain_ops = &(const struct iommu_domain_ops) { \ - .attach_dev = apple_dart_attach_dev_paging, \ - .map_pages = apple_dart_map_pages, \ - .unmap_pages = apple_dart_unmap_pages, \ - .flush_iotlb_all = apple_dart_flush_iotlb_all, \ - .iotlb_sync = apple_dart_iotlb_sync, \ - .iotlb_sync_map = apple_dart_iotlb_sync_map, \ - .iova_to_phys = apple_dart_iova_to_phys, \ - .free = apple_dart_domain_free, \ - } - static const struct iommu_ops apple_dart_iommu_ops = { .identity_domain = &apple_dart_identity_domain, .blocked_domain = &apple_dart_blocked_domain, .def_domain_type = apple_dart_def_domain_type, - APPLE_DART_IOMMU_COMMON_OPS() -}; - -static const struct iommu_ops apple_dart_iommu_no_bypass_ops = { - .blocked_domain = &apple_dart_blocked_domain, - .def_domain_type = apple_dart_def_domain_type_dma, - APPLE_DART_IOMMU_COMMON_OPS() -}; - -static const struct iommu_ops apple_dart_iommu_locked_ops = { - .def_domain_type = apple_dart_def_domain_type_dma, - APPLE_DART_IOMMU_COMMON_OPS() + .domain_alloc_paging = apple_dart_domain_alloc_paging, + .probe_device = apple_dart_probe_device, + .release_device = apple_dart_release_device, + .device_group = apple_dart_device_group, + .of_xlate = apple_dart_of_xlate, + .get_resv_regions = apple_dart_get_resv_regions, + .owner = THIS_MODULE, + .default_domain_ops = &(const struct iommu_domain_ops) { + .attach_dev = apple_dart_attach_dev_paging, + .map_pages = apple_dart_map_pages, + .unmap_pages = apple_dart_unmap_pages, + .flush_iotlb_all = apple_dart_flush_iotlb_all, + .iotlb_sync = apple_dart_iotlb_sync, + .iotlb_sync_map = apple_dart_iotlb_sync_map, + .iova_to_phys = apple_dart_iova_to_phys, + .free = apple_dart_domain_free, + } }; static irqreturn_t apple_dart_t8020_irq(int irq, void *dev) @@ -1440,12 +1423,7 @@ static int apple_dart_probe(struct platform_device *pdev) if (ret) goto err_free_irq; - if (dart->locked) - ret = iommu_device_register(&dart->iommu, &apple_dart_iommu_locked_ops, dev); - else if (!dart->supports_bypass) - ret = iommu_device_register(&dart->iommu, &apple_dart_iommu_no_bypass_ops, dev); - else - ret = iommu_device_register(&dart->iommu, &apple_dart_iommu_ops, dev); + ret = iommu_device_register(&dart->iommu, &apple_dart_iommu_ops, dev); if (ret) goto err_sysfs_remove; From bc154865e0698af11ad00fc63655506754cc38aa Mon Sep 17 00:00:00 2001 From: Hector Martin Date: Mon, 20 Sep 2021 02:23:11 +0900 Subject: [PATCH 0063/3902] tty: serial: samsung_tty: Support runtime PM This allows idle UART devices to be suspended using the standard runtime-PM framework. The logic is modeled after stm32-usart. Signed-off-by: Hector Martin --- drivers/tty/serial/samsung_tty.c | 92 ++++++++++++++++++++------------ 1 file changed, 58 insertions(+), 34 deletions(-) diff --git a/drivers/tty/serial/samsung_tty.c b/drivers/tty/serial/samsung_tty.c index 2fb58c626daf35..beabd0d62908ce 100644 --- a/drivers/tty/serial/samsung_tty.c +++ b/drivers/tty/serial/samsung_tty.c @@ -34,6 +34,7 @@ #include #include #include +#include #include #include #include @@ -1298,30 +1299,49 @@ static int apple_s5l_serial_startup(struct uart_port *port) return ret; } +static int __maybe_unused s3c24xx_serial_runtime_suspend(struct device *dev) +{ + struct uart_port *port = dev_get_drvdata(dev); + struct s3c24xx_uart_port *ourport = to_ourport(port); + int timeout = 10000; + + while (--timeout && !s3c24xx_serial_txempty_nofifo(port)) + udelay(100); + + if (!IS_ERR(ourport->baudclk)) + clk_disable_unprepare(ourport->baudclk); + + clk_disable_unprepare(ourport->clk); + return 0; +}; + +static int __maybe_unused s3c24xx_serial_runtime_resume(struct device *dev) +{ + struct uart_port *port = dev_get_drvdata(dev); + struct s3c24xx_uart_port *ourport = to_ourport(port); + + clk_prepare_enable(ourport->clk); + + if (!IS_ERR(ourport->baudclk)) + clk_prepare_enable(ourport->baudclk); + return 0; +}; + static void s3c24xx_serial_pm(struct uart_port *port, unsigned int level, unsigned int old) { struct s3c24xx_uart_port *ourport = to_ourport(port); - int timeout = 10000; ourport->pm_level = level; switch (level) { - case 3: - while (--timeout && !s3c24xx_serial_txempty_nofifo(port)) - udelay(100); - - if (!IS_ERR(ourport->baudclk)) - clk_disable_unprepare(ourport->baudclk); - - clk_disable_unprepare(ourport->clk); + case UART_PM_STATE_OFF: + pm_runtime_mark_last_busy(port->dev); + pm_runtime_put_sync(port->dev); break; - case 0: - clk_prepare_enable(ourport->clk); - - if (!IS_ERR(ourport->baudclk)) - clk_prepare_enable(ourport->baudclk); + case UART_PM_STATE_ON: + pm_runtime_get_sync(port->dev); break; default: dev_err(port->dev, "s3c24xx_serial: unknown pm %d\n", level); @@ -2044,18 +2064,15 @@ static int s3c24xx_serial_probe(struct platform_device *pdev) } } + pm_runtime_get_noresume(&pdev->dev); + pm_runtime_set_active(&pdev->dev); + pm_runtime_enable(&pdev->dev); + dev_dbg(&pdev->dev, "%s: adding port\n", __func__); uart_add_one_port(&s3c24xx_uart_drv, &ourport->port); platform_set_drvdata(pdev, &ourport->port); - /* - * Deactivate the clock enabled in s3c24xx_serial_init_port here, - * so that a potential re-enablement through the pm-callback overlaps - * and keeps the clock enabled in this case. - */ - clk_disable_unprepare(ourport->clk); - if (!IS_ERR(ourport->baudclk)) - clk_disable_unprepare(ourport->baudclk); + pm_runtime_put_sync(&pdev->dev); probe_index++; @@ -2065,16 +2082,27 @@ static int s3c24xx_serial_probe(struct platform_device *pdev) static void s3c24xx_serial_remove(struct platform_device *dev) { struct uart_port *port = s3c24xx_dev_to_port(&dev->dev); + struct s3c24xx_uart_port *ourport = to_ourport(port); - if (port) + if (port) { + pm_runtime_get_sync(&dev->dev); uart_remove_one_port(&s3c24xx_uart_drv, port); + clk_disable_unprepare(ourport->clk); + if (!IS_ERR(ourport->baudclk)) + clk_disable_unprepare(ourport->baudclk); + + pm_runtime_disable(&dev->dev); + pm_runtime_set_suspended(&dev->dev); + pm_runtime_put_noidle(&dev->dev); + } + uart_unregister_driver(&s3c24xx_uart_drv); } /* UART power management code */ -#ifdef CONFIG_PM_SLEEP -static int s3c24xx_serial_suspend(struct device *dev) + +static int __maybe_unused s3c24xx_serial_suspend(struct device *dev) { struct uart_port *port = s3c24xx_dev_to_port(dev); @@ -2084,7 +2112,7 @@ static int s3c24xx_serial_suspend(struct device *dev) return 0; } -static int s3c24xx_serial_resume(struct device *dev) +static int __maybe_unused s3c24xx_serial_resume(struct device *dev) { struct uart_port *port = s3c24xx_dev_to_port(dev); struct s3c24xx_uart_port *ourport = to_ourport(port); @@ -2104,7 +2132,7 @@ static int s3c24xx_serial_resume(struct device *dev) return 0; } -static int s3c24xx_serial_resume_noirq(struct device *dev) +static int __maybe_unused s3c24xx_serial_resume_noirq(struct device *dev) { struct uart_port *port = s3c24xx_dev_to_port(dev); struct s3c24xx_uart_port *ourport = to_ourport(port); @@ -2178,13 +2206,9 @@ static int s3c24xx_serial_resume_noirq(struct device *dev) static const struct dev_pm_ops s3c24xx_serial_pm_ops = { SET_SYSTEM_SLEEP_PM_OPS(s3c24xx_serial_suspend, s3c24xx_serial_resume) SET_NOIRQ_SYSTEM_SLEEP_PM_OPS(NULL, s3c24xx_serial_resume_noirq) + SET_RUNTIME_PM_OPS(s3c24xx_serial_runtime_suspend, + s3c24xx_serial_runtime_resume, NULL) }; -#define SERIAL_SAMSUNG_PM_OPS (&s3c24xx_serial_pm_ops) - -#else /* !CONFIG_PM_SLEEP */ - -#define SERIAL_SAMSUNG_PM_OPS NULL -#endif /* CONFIG_PM_SLEEP */ /* Console code */ @@ -2672,7 +2696,7 @@ static struct platform_driver samsung_serial_driver = { .id_table = s3c24xx_serial_driver_ids, .driver = { .name = "samsung-uart", - .pm = SERIAL_SAMSUNG_PM_OPS, + .pm = &s3c24xx_serial_pm_ops, .of_match_table = of_match_ptr(s3c24xx_uart_dt_match), }, }; From 83287bcbb2cd6df03f746e11a24d49e65e0dd79b Mon Sep 17 00:00:00 2001 From: Hector Martin Date: Thu, 5 May 2022 01:40:31 +0900 Subject: [PATCH 0064/3902] mmc: sdhci-pci: Support external CD GPIO on all OF systems Allow OF systems to specify an external CD GPIO on all devices, even if they have an internal CD feature. Signed-off-by: Hector Martin --- drivers/mmc/host/sdhci-pci-core.c | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/drivers/mmc/host/sdhci-pci-core.c b/drivers/mmc/host/sdhci-pci-core.c index 47a0a738862b58..f17b85915c7ffb 100644 --- a/drivers/mmc/host/sdhci-pci-core.c +++ b/drivers/mmc/host/sdhci-pci-core.c @@ -27,6 +27,7 @@ #include #include #include +#include #include #include @@ -2221,6 +2222,15 @@ static struct sdhci_pci_slot *sdhci_pci_probe_slot( dev_warn(&pdev->dev, "failed to setup card detect gpio\n"); slot->cd_idx = -1; } + } else if (is_of_node(pdev->dev.fwnode)) { + /* Allow all OF systems to use a CD GPIO if provided */ + + ret = mmc_gpiod_request_cd(host->mmc, "cd", 0, + slot->cd_override_level, 0); + if (ret == -EPROBE_DEFER) + goto remove; + else if (ret == 0) + slot->cd_idx = 0; } if (chip->fixes && chip->fixes->add_host) From 5ab332aae207c25826f083ea132ce7975319c654 Mon Sep 17 00:00:00 2001 From: Hector Martin Date: Thu, 5 May 2022 02:27:35 +0900 Subject: [PATCH 0065/3902] mmc: sdhci-pci: Support setting CD debounce delay Some systems (e.g. 2021 MacBook Pro 14/16") have noncompliant connectors where CD activates before the card is fully inserted. We need debounce delay support on these to avoid detection failures when the card isn't inserted very quickly. Set the default to 200ms for all systems instead of 0. This is the default on non-PCI platforms, and will probably help other systems too. The naughty MacBooks will need closer to 750ms in the device tree to be reliable... Signed-off-by: Hector Martin --- drivers/mmc/host/sdhci-pci-core.c | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/drivers/mmc/host/sdhci-pci-core.c b/drivers/mmc/host/sdhci-pci-core.c index f17b85915c7ffb..93122133202367 100644 --- a/drivers/mmc/host/sdhci-pci-core.c +++ b/drivers/mmc/host/sdhci-pci-core.c @@ -2130,6 +2130,7 @@ static struct sdhci_pci_slot *sdhci_pci_probe_slot( struct sdhci_host *host; int ret, bar = first_bar + slotno; size_t priv_size = chip->fixes ? chip->fixes->priv_size : 0; + u32 cd_debounce_delay_ms; if (!(pci_resource_flags(pdev, bar) & IORESOURCE_MEM)) { dev_err(&pdev->dev, "BAR %d is not iomem. Aborting.\n", bar); @@ -2196,6 +2197,10 @@ static struct sdhci_pci_slot *sdhci_pci_probe_slot( if (host->mmc->caps & MMC_CAP_CD_WAKE) device_init_wakeup(&pdev->dev, true); + if (device_property_read_u32(&pdev->dev, "cd-debounce-delay-ms", + &cd_debounce_delay_ms)) + cd_debounce_delay_ms = 200; + if (slot->cd_idx >= 0) { struct gpiod_lookup_table *cd_gpio_lookup_table; @@ -2214,7 +2219,7 @@ static struct sdhci_pci_slot *sdhci_pci_probe_slot( ret = mmc_gpiod_request_cd(host->mmc, NULL, slot->cd_idx, slot->cd_override_level, - 0); + cd_debounce_delay_ms * 1000); if (ret == -EPROBE_DEFER) goto remove; @@ -2226,7 +2231,8 @@ static struct sdhci_pci_slot *sdhci_pci_probe_slot( /* Allow all OF systems to use a CD GPIO if provided */ ret = mmc_gpiod_request_cd(host->mmc, "cd", 0, - slot->cd_override_level, 0); + slot->cd_override_level, + cd_debounce_delay_ms * 1000); if (ret == -EPROBE_DEFER) goto remove; else if (ret == 0) From 53f87119dfa19c3f31dcc67f09ad48708e7a4c74 Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Fri, 9 Sep 2022 19:52:19 +0200 Subject: [PATCH 0066/3902] PCI: apple: Add depends on PAGE_SIZE_16KB The iommu on Apple's M1 and M2 supports only a page size of 16kB and is mandatory for PCIe devices. The PCI controller itself is not affeccted by the CPU page size the page size mismatch devices are renderer useless due to non-working DMA. While the the iommu prints a warning in this scenario it seems a common and hard to debug problem. Signed-off-by: Janne Grunau --- drivers/pci/controller/Kconfig | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/pci/controller/Kconfig b/drivers/pci/controller/Kconfig index 41748d083b933d..5caf76c0bf57f4 100644 --- a/drivers/pci/controller/Kconfig +++ b/drivers/pci/controller/Kconfig @@ -45,6 +45,7 @@ config PCIE_APPLE depends on ARCH_APPLE || COMPILE_TEST depends on OF depends on PCI_MSI + depends on PAGE_SIZE_16KB || COMPILE_TEST select PCI_HOST_COMMON select IRQ_MSI_LIB help From 235a099aae5320bb7666cf91f2626acfcbfc7c95 Mon Sep 17 00:00:00 2001 From: Hector Martin Date: Tue, 25 Oct 2022 01:12:17 +0900 Subject: [PATCH 0067/3902] firmware_loader: Add /lib/firmware/vendor path Signed-off-by: Hector Martin --- drivers/base/firmware_loader/main.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/drivers/base/firmware_loader/main.c b/drivers/base/firmware_loader/main.c index 6942c62fa59d12..070149fb409239 100644 --- a/drivers/base/firmware_loader/main.c +++ b/drivers/base/firmware_loader/main.c @@ -471,6 +471,8 @@ static int fw_decompress_xz(struct device *dev, struct fw_priv *fw_priv, static char fw_path_para[256]; static const char * const fw_path[] = { fw_path_para, + "/lib/firmware/vendor/" UTS_RELEASE, + "/lib/firmware/vendor", "/lib/firmware/updates/" UTS_RELEASE, "/lib/firmware/updates", "/lib/firmware/" UTS_RELEASE, From 92c376a53446d8f485b54fe88ab28ed6a67ca8d0 Mon Sep 17 00:00:00 2001 From: Hector Martin Date: Mon, 24 Apr 2023 23:05:40 +0900 Subject: [PATCH 0068/3902] driver core: fw_devlink: Add fw_devlink_count_absent_consumers() Some platforms have power domains that are active on boot and must remain powered up until all of their consumers probe. The genpd core needs a way to count how many consumers haven't probed yet to avoid powering off such domains. Add a fw_devlink_count_absent_consumers() function, which returns the total count of consumer devices which either have not been created at all yet (only fwlinks exist) or have been created but have no driver bound and fully probed yet. Signed-off-by: Hector Martin --- drivers/base/core.c | 26 ++++++++++++++++++++++++++ include/linux/fwnode.h | 1 + 2 files changed, 27 insertions(+) diff --git a/drivers/base/core.c b/drivers/base/core.c index f69dc9c8595455..44a2d9bbbe28ef 100644 --- a/drivers/base/core.c +++ b/drivers/base/core.c @@ -2328,6 +2328,32 @@ static void fw_devlink_link_device(struct device *dev) __fw_devlink_link_to_suppliers(dev, fwnode); } +/** + * fw_devlink_count_absent_consumers - Return how many consumers have + * either not been created yet, or do not yet have a driver attached. + * @fwnode: fwnode of the supplier + */ +int fw_devlink_count_absent_consumers(struct fwnode_handle *fwnode) +{ + struct fwnode_link *link, *tmp; + struct device_link *dlink, *dtmp; + struct device *sup_dev = get_dev_from_fwnode(fwnode); + int count = 0; + + list_for_each_entry_safe(link, tmp, &fwnode->consumers, s_hook) + count++; + + if (!sup_dev) + return count; + + list_for_each_entry_safe(dlink, dtmp, &sup_dev->links.consumers, s_node) + if (dlink->consumer->links.status != DL_DEV_DRIVER_BOUND) + count++; + + return count; +} +EXPORT_SYMBOL_GPL(fw_devlink_count_absent_consumers); + /* Device links support end. */ static struct kobject *dev_kobj; diff --git a/include/linux/fwnode.h b/include/linux/fwnode.h index 097be89487bf5c..53e46648131d91 100644 --- a/include/linux/fwnode.h +++ b/include/linux/fwnode.h @@ -229,5 +229,6 @@ int fwnode_link_add(struct fwnode_handle *con, struct fwnode_handle *sup, void fwnode_links_purge(struct fwnode_handle *fwnode); void fw_devlink_purge_absent_suppliers(struct fwnode_handle *fwnode); bool fw_devlink_is_strict(void); +int fw_devlink_count_absent_consumers(struct fwnode_handle *fwnode); #endif From a645cebbc442a6b32b609fdb4e4664cddbd7519a Mon Sep 17 00:00:00 2001 From: Hector Martin Date: Mon, 24 Apr 2023 23:08:22 +0900 Subject: [PATCH 0069/3902] PM: domains: Add a flag to defer power-off until all consumers probe In some cases, power domains are active on boot and must remain turned on until all their dependent drivers probe. Examples are: - Boot-time framebuffers - Devices that run coprocessors which are handed off already running - Parent power domains with children that are also on at boot The genpd core currently powers off the genpd as soon as a single consumer device probes and goes into runtime suspend or when general probing is complete, whichever comes first. That breaks any devices which haven't probed yet. To fix this, add a GENPD_FLAG_DEFER_OFF which requests that the genpd core refuse to power down a domain if there are any consumer devices that either haven't probed yet, or whose device nodes do not exist yet (but fwlinks do). Genpd providers can set this if they expect to be critical for devices (e.g. if they are powered on at boot). It is possible for a device to be runtime suspended from its probe callback. If this is the last device to probe, this is allowable. To account for this, check whether the device whose callbacks are being invoked in the probing state, and in that case, allow 1 instead of 0 pending devices. Signed-off-by: Hector Martin --- drivers/pmdomain/core.c | 58 +++++++++++++++++++++++++++++++++------ include/linux/pm_domain.h | 8 ++++++ 2 files changed, 58 insertions(+), 8 deletions(-) diff --git a/drivers/pmdomain/core.c b/drivers/pmdomain/core.c index 61c2277c9ce39f..c45006e7a880a4 100644 --- a/drivers/pmdomain/core.c +++ b/drivers/pmdomain/core.c @@ -7,6 +7,7 @@ #define pr_fmt(fmt) "PM: " fmt #include +#include #include #include #include @@ -188,6 +189,7 @@ static const struct genpd_lock_ops genpd_raw_spin_ops = { #define genpd_is_dev_name_fw(genpd) (genpd->flags & GENPD_FLAG_DEV_NAME_FW) #define genpd_is_no_sync_state(genpd) (genpd->flags & GENPD_FLAG_NO_SYNC_STATE) #define genpd_is_no_stay_on(genpd) (genpd->flags & GENPD_FLAG_NO_STAY_ON) +#define genpd_is_defer_off(genpd) (genpd->flags & GENPD_FLAG_DEFER_OFF) static inline bool irq_safe_dev_in_sleep_domain(struct device *dev, const struct generic_pm_domain *genpd) @@ -941,6 +943,27 @@ static void genpd_queue_power_off_work(struct generic_pm_domain *genpd) queue_work(pm_wq, &genpd->power_off_work); } +/** + * genpd_must_defer - Check whether the genpd cannot be safely powered off. + * @genpd: PM domain about to be powered down. + * @one_dev_probing: True if we are being called from RPM callbacks on a device that + * is probing, to allow poweroff if that device is the sole remaining consumer probing. + * + * Returns true if the @genpd has the GENPD_FLAG_DEFER_OFF flag and there + * are any consumer devices which either do not exist yet (only represented + * by fwlinks) or whose drivers have not probed yet. + */ +static bool genpd_must_defer(struct generic_pm_domain *genpd, bool one_dev_probing) +{ + if (genpd_is_defer_off(genpd) && genpd->has_provider) { + int absent = fw_devlink_count_absent_consumers(genpd->provider); + + if (absent > (one_dev_probing ? 1 : 0)) + return true; + } + return false; +} + /** * genpd_power_off - Remove power from a given PM domain. * @genpd: PM domain to power down. @@ -954,7 +977,7 @@ static void genpd_queue_power_off_work(struct generic_pm_domain *genpd) * have been powered down, remove power from @genpd. */ static void genpd_power_off(struct generic_pm_domain *genpd, bool one_dev_on, - unsigned int depth) + bool one_dev_probing, unsigned int depth) { struct pm_domain_data *pdd; struct gpd_link *link; @@ -1002,6 +1025,14 @@ static void genpd_power_off(struct generic_pm_domain *genpd, bool one_dev_on, if (not_suspended > 1 || (not_suspended == 1 && !one_dev_on)) return; + /* + * Do not allow PM domain to be powered off if it is marked + * as GENPD_FLAG_DEFER_OFF and there are consumer devices + * which have not probed yet. + */ + if (genpd_must_defer(genpd, one_dev_probing)) + return; + if (genpd->gov && genpd->gov->power_down_ok) { if (!genpd->gov->power_down_ok(&genpd->domain)) return; @@ -1027,7 +1058,7 @@ static void genpd_power_off(struct generic_pm_domain *genpd, bool one_dev_on, list_for_each_entry(link, &genpd->child_links, child_node) { genpd_sd_counter_dec(link->parent); genpd_lock_nested(link->parent, depth + 1); - genpd_power_off(link->parent, false, depth + 1); + genpd_power_off(link->parent, false, false, depth + 1); genpd_unlock(link->parent); } } @@ -1086,7 +1117,7 @@ static int genpd_power_on(struct generic_pm_domain *genpd, unsigned int depth) child_node) { genpd_sd_counter_dec(link->parent); genpd_lock_nested(link->parent, depth + 1); - genpd_power_off(link->parent, false, depth + 1); + genpd_power_off(link->parent, false, false, depth + 1); genpd_unlock(link->parent); } @@ -1153,7 +1184,7 @@ static void genpd_power_off_work_fn(struct work_struct *work) genpd = container_of(work, struct generic_pm_domain, power_off_work); genpd_lock(genpd); - genpd_power_off(genpd, false, 0); + genpd_power_off(genpd, false, false, 0); genpd_unlock(genpd); } @@ -1218,6 +1249,7 @@ static int genpd_runtime_suspend(struct device *dev) struct generic_pm_domain_data *gpd_data = dev_gpd_data(dev); struct gpd_timing_data *td = gpd_data->td; bool runtime_pm = pm_runtime_enabled(dev); + bool probing = dev->links.status != DL_DEV_DRIVER_BOUND; ktime_t time_start = 0; s64 elapsed_ns; int ret; @@ -1272,7 +1304,7 @@ static int genpd_runtime_suspend(struct device *dev) return 0; genpd_lock(genpd); - genpd_power_off(genpd, true, 0); + genpd_power_off(genpd, true, probing, 0); gpd_data->rpm_pstate = genpd_drop_performance_state(dev); genpd_unlock(genpd); @@ -1293,6 +1325,7 @@ static int genpd_runtime_resume(struct device *dev) struct generic_pm_domain_data *gpd_data = dev_gpd_data(dev); struct gpd_timing_data *td = gpd_data->td; bool timed = td && pm_runtime_enabled(dev); + bool probing = dev->links.status != DL_DEV_DRIVER_BOUND; ktime_t time_start = 0; s64 elapsed_ns; int ret; @@ -1350,7 +1383,7 @@ static int genpd_runtime_resume(struct device *dev) err_poweroff: if (!pm_runtime_is_irq_safe(dev) || genpd_is_irq_safe(genpd)) { genpd_lock(genpd); - genpd_power_off(genpd, true, 0); + genpd_power_off(genpd, true, probing, 0); gpd_data->rpm_pstate = genpd_drop_performance_state(dev); genpd_unlock(genpd); } @@ -1418,6 +1451,9 @@ static void genpd_sync_power_off(struct generic_pm_domain *genpd, bool use_lock, || atomic_read(&genpd->sd_count) > 0) return; + if (genpd_must_defer(genpd, false)) + return; + /* Check that the children are in their deepest (powered-off) state. */ list_for_each_entry(link, &genpd->parent_links, parent_node) { struct generic_pm_domain *child = link->child; @@ -2437,6 +2473,12 @@ int pm_genpd_init(struct generic_pm_domain *genpd, return -EINVAL; } + /* Deferred-off power domains should be powered on at initialization. */ + if (genpd_is_defer_off(genpd) && !genpd_status_on(genpd)) { + pr_warn("deferred-off PM domain %s is not on at init\n", genpd->name); + genpd->flags &= ~GENPD_FLAG_DEFER_OFF; + } + /* Multiple states but no governor doesn't make sense. */ if (!gov && genpd->state_count > 1) pr_warn("%s: no governor for states\n", genpd->name); @@ -3503,7 +3545,7 @@ void of_genpd_sync_state(struct device_node *np) if (genpd->provider == of_fwnode_handle(np)) { genpd_lock(genpd); genpd->stay_on = false; - genpd_power_off(genpd, false, 0); + genpd_power_off(genpd, false, false, 0); genpd_unlock(genpd); } } @@ -3531,7 +3573,7 @@ static void genpd_provider_sync_state(struct device *dev) case GENPD_SYNC_STATE_SIMPLE: genpd_lock(genpd); genpd->stay_on = false; - genpd_power_off(genpd, false, 0); + genpd_power_off(genpd, false, false, 0); genpd_unlock(genpd); break; diff --git a/include/linux/pm_domain.h b/include/linux/pm_domain.h index f67a2cb7d78148..dca6784d0547bc 100644 --- a/include/linux/pm_domain.h +++ b/include/linux/pm_domain.h @@ -121,6 +121,13 @@ struct dev_pm_domain_list { * powered-off until the ->sync_state() callback is * invoked. This flag informs genpd to allow a * power-off without waiting for ->sync_state(). + * GENPD_FLAG_DEFER_OFF: Defer powerdown if there are any consumer + * device fwlinks indicating that some consumer + * devices have not yet probed. This is useful + * for power domains which are active at boot and + * must not be shut down until all consumers + * complete their probe sequence. + */ #define GENPD_FLAG_PM_CLK (1U << 0) #define GENPD_FLAG_IRQ_SAFE (1U << 1) @@ -133,6 +140,7 @@ struct dev_pm_domain_list { #define GENPD_FLAG_DEV_NAME_FW (1U << 8) #define GENPD_FLAG_NO_SYNC_STATE (1U << 9) #define GENPD_FLAG_NO_STAY_ON (1U << 10) +#define GENPD_FLAG_DEFER_OFF (1U << 11) enum gpd_status { GENPD_STATE_ON = 0, /* PM domain is on */ From c622881bdb5404eabf77486f2ff94d099faca660 Mon Sep 17 00:00:00 2001 From: Hector Martin Date: Mon, 24 Apr 2023 23:13:14 +0900 Subject: [PATCH 0070/3902] soc: apple: apple-pmgr-pwrstate: Mark on-at-boot PDs as DEFER_OFF We consider any domains that are found to be powered on at boot as potentially critical for probing consumer devices. This prevents badness like the boot-time display controller being powered down as soon as its IOMMU probes. Fixes a pile of PD probe order dependencies and races that have required ALWAYS_ON workaround hacks until now, including: - ANS2 (NVMe) breaking if left on at handoff. - DISP0/DCP (boot display) completely breaking. - PM domains failing to probe when their parent was inadvertently shut down before the child probed. - PCIe losing state/fuse info/etc when it powers down before the driver is ready. - Touch Bar (DFR) display controller losing bootloader-configured state before its driver can probe and save it. The downside is that any spuriously on domains will remain on if their drivers are missing. We consider missing drivers that never get loaded a downstream bug. For older kernels running on newer DTs with extra devices, this shouldn't cause any major problems other than perhaps slightly increased power consumption (and we can always fix it in the bootloader by powering down those PDs if they don't need to be left on, since the bootloader is updated together with the DTs). Signed-off-by: Hector Martin --- drivers/pmdomain/apple/pmgr-pwrstate.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/drivers/pmdomain/apple/pmgr-pwrstate.c b/drivers/pmdomain/apple/pmgr-pwrstate.c index 82c33cf727a825..18c135057b15c4 100644 --- a/drivers/pmdomain/apple/pmgr-pwrstate.c +++ b/drivers/pmdomain/apple/pmgr-pwrstate.c @@ -242,6 +242,8 @@ static int apple_pmgr_ps_probe(struct platform_device *pdev) /* Turn it on so pm_genpd_init does not fail */ active = apple_pmgr_ps_power_on(&ps->genpd) == 0; } + } else if (active) { + ps->genpd.flags |= GENPD_FLAG_DEFER_OFF; } /* Turn on auto-PM if the domain is already on */ From 202dde8580e83b6cbcef72fbf215e985e9becbc6 Mon Sep 17 00:00:00 2001 From: Hector Martin Date: Tue, 25 Apr 2023 01:46:23 +0900 Subject: [PATCH 0071/3902] tty: serial: samsung_tty: Mark as wakeup_path on no_console_suspend Devices not in the wakeup path always have their power domains shut down on suspend, which breaks no_console_suspend. Use the wakeup path feature to stop this from happening. This is somewhat an abuse of the concept as named, but the end result is exactly what we desire. Signed-off-by: Hector Martin --- drivers/tty/serial/samsung_tty.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/drivers/tty/serial/samsung_tty.c b/drivers/tty/serial/samsung_tty.c index beabd0d62908ce..563688c507515d 100644 --- a/drivers/tty/serial/samsung_tty.c +++ b/drivers/tty/serial/samsung_tty.c @@ -2106,6 +2106,9 @@ static int __maybe_unused s3c24xx_serial_suspend(struct device *dev) { struct uart_port *port = s3c24xx_dev_to_port(dev); + if (!console_suspend_enabled && uart_console(port)) + device_set_wakeup_path(dev); + if (port) uart_suspend_port(&s3c24xx_uart_drv, port); From f5ca3aa346dab636f0e4dbec159f79875f08f82e Mon Sep 17 00:00:00 2001 From: Hector Martin Date: Tue, 25 Apr 2023 01:40:11 +0900 Subject: [PATCH 0072/3902] soc: apple: apple-pmgr-pwrstate: Mark on-at-boot PDs as wakeup The genpd core does not have a generic mechanism for skipping genpd shutdown on system sleep, but it does have the wakeup path mechanism that is essentially the same thing. Mark all PDs that are on at boot as potentially wakeup-relevant, which means they can *optionally* stay on. Drivers have to opt into this with device_set_wakeup_path() to actually force them to remain on. Signed-off-by: Hector Martin --- drivers/pmdomain/apple/pmgr-pwrstate.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/pmdomain/apple/pmgr-pwrstate.c b/drivers/pmdomain/apple/pmgr-pwrstate.c index 18c135057b15c4..ce53cf1c970da0 100644 --- a/drivers/pmdomain/apple/pmgr-pwrstate.c +++ b/drivers/pmdomain/apple/pmgr-pwrstate.c @@ -243,7 +243,7 @@ static int apple_pmgr_ps_probe(struct platform_device *pdev) active = apple_pmgr_ps_power_on(&ps->genpd) == 0; } } else if (active) { - ps->genpd.flags |= GENPD_FLAG_DEFER_OFF; + ps->genpd.flags |= GENPD_FLAG_DEFER_OFF | GENPD_FLAG_ACTIVE_WAKEUP; } /* Turn on auto-PM if the domain is already on */ From b7b59ed7e654b5f1933b055273d25c40ee9de2c9 Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Sun, 15 Oct 2023 17:41:32 +0200 Subject: [PATCH 0073/3902] drm/simpledrm: Set DMA and coherency mask Simpledrm is "DMA" access is not limited. All CPU addressible memory can be used via direct DMA mappings. Fixes following warning on Apple silicon systems. Physical memory on those systems starts at (1 << 35) or (1 << 40) so 32-bit direct DMA mappings are not possible. ------------[ cut here ]------------ simple-framebuffer 9e5064000.framebuffer: swiotlb addr 0x00000009de654000+16384 overflow (mask ffffffff, bus limit 0). WARNING: CPU: 3 PID: 961 at kernel/dma/swiotlb.c:928 swiotlb_map+0x1f4/0x2a0 Modules linked in: ... CPU: 3 PID: 961 Comm: kwin_wayland Not tainted 6.5.0-asahi+ #1 Hardware name: Apple Mac mini (M2, 2023) (DT) ... Call trace: swiotlb_map+0x1f4/0x2a0 dma_direct_map_sg+0x8c/0x2a8 dma_map_sgtable+0x5c/0xd0 drm_gem_map_dma_buf+0x64/0xb8 dma_buf_map_attachment+0xac/0x158 dma_buf_map_attachment_unlocked+0x48/0x80 drm_gem_prime_import_dev+0xa0/0x1a0 drm_gem_prime_fd_to_handle+0xc8/0x218 drm_prime_fd_to_handle_ioctl+0x34/0x50 drm_ioctl_kernel+0xe4/0x160 drm_ioctl+0x23c/0x3e0 ... ---[ end trace 0000000000000000 ]--- Avoids using swiotbl bounce buffers on other platforms when the mapped memory is above 4GB. Fixes: 11e8f5fd223b ("drm: Add simpledrm driver") Signed-off-by: Janne Grunau --- drivers/gpu/drm/sysfb/simpledrm.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/drivers/gpu/drm/sysfb/simpledrm.c b/drivers/gpu/drm/sysfb/simpledrm.c index 0358164a623c9e..d98cffd9b341d2 100644 --- a/drivers/gpu/drm/sysfb/simpledrm.c +++ b/drivers/gpu/drm/sysfb/simpledrm.c @@ -839,6 +839,12 @@ static int simpledrm_probe(struct platform_device *pdev) struct drm_device *dev; int ret; + ret = dma_set_mask_and_coherent(&pdev->dev, DMA_BIT_MASK(64)); + if (ret) + ret = dma_set_mask_and_coherent(&pdev->dev, DMA_BIT_MASK(32)); + if (ret) + return dev_err_probe(&pdev->dev, ret, "Failed to set dma mask\n"); + sdev = simpledrm_device_create(&simpledrm_driver, pdev); if (IS_ERR(sdev)) return PTR_ERR(sdev); From 110b790985085090111690e0964e9c7d7df9ef33 Mon Sep 17 00:00:00 2001 From: Asahi Lina Date: Sun, 26 May 2024 17:30:04 +0900 Subject: [PATCH 0074/3902] arm64: Increase kernel stack size to 32K To work around stack overflow with the drm/asahi driver plus zram swap-out, TBD if we can refactor things enough to bring it under 16K again... Signed-off-by: Asahi Lina --- arch/arm64/include/asm/memory.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/arch/arm64/include/asm/memory.h b/arch/arm64/include/asm/memory.h index f1505c4acb389a..41b97936f3025f 100644 --- a/arch/arm64/include/asm/memory.h +++ b/arch/arm64/include/asm/memory.h @@ -112,7 +112,7 @@ #define DIRECT_MAP_PHYSMEM_END __pa(PAGE_END - 1) -#define MIN_THREAD_SHIFT (14 + KASAN_THREAD_SHIFT) +#define MIN_THREAD_SHIFT (15 + KASAN_THREAD_SHIFT) /* * VMAP'd stacks are allocated at page granularity, so we must ensure that such From 51f4ce8736c94fe1d20c713c2d39e2d808044d8e Mon Sep 17 00:00:00 2001 From: Asahi Lina Date: Wed, 17 Jul 2024 18:23:44 +0900 Subject: [PATCH 0075/3902] Increase MAX_LOCKDEP_CHAIN_HLOCKS Got a warning somewhere in the USB subsystem while unplugging a device... --- kernel/locking/lockdep_internals.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/kernel/locking/lockdep_internals.h b/kernel/locking/lockdep_internals.h index 0e5e6ffe91a3fe..91a802fea0aa0f 100644 --- a/kernel/locking/lockdep_internals.h +++ b/kernel/locking/lockdep_internals.h @@ -121,7 +121,7 @@ enum { #define MAX_LOCKDEP_CHAINS (1UL << MAX_LOCKDEP_CHAINS_BITS) -#define AVG_LOCKDEP_CHAIN_DEPTH 5 +#define AVG_LOCKDEP_CHAIN_DEPTH 10 #define MAX_LOCKDEP_CHAIN_HLOCKS (MAX_LOCKDEP_CHAINS * AVG_LOCKDEP_CHAIN_DEPTH) extern struct lock_chain lock_chains[]; From 9d2ade3cb180171e22af85cb6469f00fe8fc660d Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Sat, 1 Mar 2025 15:15:47 +0100 Subject: [PATCH 0076/3902] mmc: core: Skip SD UHS-II enumeration on missing UHS2 cap Fixes: 79daeb241db79 ("mmc: core: Prepare to support SD UHS-II cards") Signed-off-by: Janne Grunau --- drivers/mmc/core/core.c | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/drivers/mmc/core/core.c b/drivers/mmc/core/core.c index 860378bea557b3..d42c548c073fcc 100644 --- a/drivers/mmc/core/core.c +++ b/drivers/mmc/core/core.c @@ -2300,10 +2300,11 @@ void mmc_rescan(struct work_struct *work) * while initializing the legacy SD interface. Therefore, let's start * with UHS-II for now. */ - if (!mmc_attach_sd_uhs2(host)) { - mmc_release_host(host); - goto out; - } + if (host->caps2 & MMC_CAP2_SD_UHS2) + if (!mmc_attach_sd_uhs2(host)) { + mmc_release_host(host); + goto out; + } for (i = 0; i < ARRAY_SIZE(freqs); i++) { unsigned int freq = freqs[i]; From e1a9af44381d5758a7dadd8c4bad2d7001e96ff9 Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Sat, 1 Mar 2025 15:11:54 +0100 Subject: [PATCH 0077/3902] mmc: sdhci-uhs2: Add quirk for devices with broken UHS-2 The GL9755 in Apple silicon Macboo Pros and Mac Studios has non-functioning UHS-2 under linux. Signed-off-by: Janne Grunau --- drivers/mmc/host/sdhci-uhs2.c | 3 ++- drivers/mmc/host/sdhci.h | 2 ++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/drivers/mmc/host/sdhci-uhs2.c b/drivers/mmc/host/sdhci-uhs2.c index c459a08d01da52..c7c75d21973234 100644 --- a/drivers/mmc/host/sdhci-uhs2.c +++ b/drivers/mmc/host/sdhci-uhs2.c @@ -1163,7 +1163,8 @@ static void __sdhci_uhs2_add_host_v4(struct sdhci_host *host, u32 caps1) mmc = host->mmc; /* Support UHS2 */ - if (caps1 & SDHCI_SUPPORT_UHS2) + if ((caps1 & SDHCI_SUPPORT_UHS2) && + !(host->quirks2 & SDHCI_QUIRK2_BROKEN_UHS2)) mmc->caps2 |= MMC_CAP2_SD_UHS2; max_current_caps2 = sdhci_readl(host, SDHCI_MAX_CURRENT_1); diff --git a/drivers/mmc/host/sdhci.h b/drivers/mmc/host/sdhci.h index b6a571d866fa53..d4d520a017b177 100644 --- a/drivers/mmc/host/sdhci.h +++ b/drivers/mmc/host/sdhci.h @@ -537,6 +537,8 @@ struct sdhci_host { /* Issue CMD and DATA reset together */ #define SDHCI_QUIRK2_ISSUE_CMD_DAT_RESET_TOGETHER (1<<19) +#define SDHCI_QUIRK2_BROKEN_UHS2 (1<<27) + int irq; /* Device IRQ */ void __iomem *ioaddr; /* Mapped address */ phys_addr_t mapbase; /* physical address base */ From d935da988762c4c545bf5bb504ffc931b69bc7e1 Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Sat, 1 Mar 2025 15:09:14 +0100 Subject: [PATCH 0078/3902] mmc: pci: gl9755: Quirk UHS-2 for Apple GL9755 To avoid dumped register on probe/card insertion. Signed-off-by: Janne Grunau --- drivers/mmc/host/sdhci-pci-gli.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/drivers/mmc/host/sdhci-pci-gli.c b/drivers/mmc/host/sdhci-pci-gli.c index b0f91cc9e40e43..740ec79bf0cdb1 100644 --- a/drivers/mmc/host/sdhci-pci-gli.c +++ b/drivers/mmc/host/sdhci-pci-gli.c @@ -2037,7 +2037,8 @@ static const struct sdhci_ops sdhci_gl9755_ops = { const struct sdhci_pci_fixes sdhci_gl9755 = { .quirks = SDHCI_QUIRK_NO_ENDATTR_IN_NOPDESC, - .quirks2 = SDHCI_QUIRK2_BROKEN_DDR50, + // disable non-working UHS-II mode on apple silicon devices + .quirks2 = SDHCI_QUIRK2_BROKEN_DDR50 | SDHCI_QUIRK2_BROKEN_UHS2, .probe_slot = gli_probe_slot_gl9755, .add_host = sdhci_pci_uhs2_add_host, .remove_host = sdhci_pci_uhs2_remove_host, From 862c2fa7208b22f884141c643a8f3c3ff89ba2df Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Sat, 4 Oct 2025 15:30:19 +0200 Subject: [PATCH 0079/3902] arm64: configs: Add asahi.config fragment This can be used to ensure all drivers for Apple silicon hardware are enabled. For a defconfig build it can simply be appended: ``` make defconfig asahi ``` For other build configs (a modified defconfig or distro config) it can be merged via a kernel script: ``` KCONFIG_CONFIG=.config ./scripts/kconfig/merge_config.sh -m .config arch/arm64/configs/asahi.config ``` Signed-off-by: Janne Grunau --- arch/arm64/configs/asahi.config | 71 +++++++++++++++++++++++++++++++++ 1 file changed, 71 insertions(+) create mode 100644 arch/arm64/configs/asahi.config diff --git a/arch/arm64/configs/asahi.config b/arch/arm64/configs/asahi.config new file mode 100644 index 00000000000000..f47b8e76200541 --- /dev/null +++ b/arch/arm64/configs/asahi.config @@ -0,0 +1,71 @@ +CONFIG_RUST=y +CONFIG_ARM64_ACTLR_STATE=y +CONFIG_ARCH_APPLE=y +# CONFIG_ARM64_4K_PAGES is not set +CONFIG_ARM64_16K_PAGES=y +# CONFIG_ARM64_64K_PAGES is not set +CONFIG_ARM64_MEMORY_MODEL_CONTROL=y +CONFIG_ARM_APPLE_CPUIDLE=y +CONFIG_ARM_APPLE_SOC_CPUFREQ=m +CONFIG_BT_HCIBCM4377=m +CONFIG_PCIE_APPLE=m +CONFIG_NVME_APPLE=m +CONFIG_BRCMFMAC=m +CONFIG_BRCMFMAC_PCIE=y +CONFIG_TOUCHSCREEN_APPLE_Z2=m +CONFIG_INPUT_MACSMC_INPUT=m +CONFIG_I2C_APPLE=m +CONFIG_SPI_APPLE=m +CONFIG_SPMI_APPLE=m +CONFIG_PINCTRL_APPLE_GPIO=m +CONFIG_GPIO_MACSMC=m +CONFIG_POWER_RESET_MACSMC=m +CONFIG_CHARGER_MACSMC=m +CONFIG_SENSORS_MACSMC_HWMON=m +CONFIG_APPLE_WATCHDOG=m +CONFIG_VIDEO_APPLE_ISP=m +CONFIG_DRM_ASAHI=m +CONFIG_DRM_ADP=m +CONFIG_DRM_APPLE=m +CONFIG_DRM_APPLE_AUDIO=y +CONFIG_SND_SOC_APPLE_AOP_AUDIO=m +CONFIG_SND_SOC_APPLE_MCA=m +CONFIG_SND_SOC_APPLE_MACAUDIO=m +CONFIG_SND_SOC_CS42L83=m +CONFIG_SND_SOC_CS42L84=m +CONFIG_SND_SOC_TAS2764=m +CONFIG_SND_SOC_TAS2770=m +CONFIG_HID_APPLE=m +CONFIG_HID_MAGICMOUSE=m +CONFIG_SERIAL_SAMSUNG=y +CONFIG_SERIAL_SAMSUNG_CONSOLE=y +CONFIG_HID_DOCKCHANNEL=m +CONFIG_SPI_HID_APPLE_OF=m +CONFIG_SPI_HID_APPLE_CORE=m +CONFIG_USB_XHCI_PCI_ASMEDIA=y +CONFIG_RTC_DRV_MACSMC=m +CONFIG_APPLE_ADMAC=m +CONFIG_APPLE_SIO=m +CONFIG_MFD_MACSMC=m +CONFIG_COMMON_CLK_APPLE_NCO=m +CONFIG_APPLE_DART=m +CONFIG_APPLE_DOCKCHANNEL=m +CONFIG_APPLE_MAILBOX=y +CONFIG_APPLE_PMGR_MISC=y +CONFIG_APPLE_RTKIT=y +CONFIG_APPLE_RTKIT_HELPER=m +CONFIG_APPLE_SART=m +CONFIG_RUST_APPLE_RTKIT=y +CONFIG_APPLE_AOP=m +CONFIG_APPLE_SEP=m +CONFIG_APPLE_PMGR_PWRSTATE=y +CONFIG_IIO_AOP_SENSOR_LAS=m +CONFIG_IIO_AOP_SENSOR_ALS=m +CONFIG_PWM_APPLE=m +CONFIG_APPLE_AIC=y +CONFIG_PHY_APPLE_ATC=m +CONFIG_PHY_APPLE_DPTX=m +CONFIG_APPLE_M1_CPU_PMU=y +CONFIG_NVMEM_APPLE_EFUSES=m +CONFIG_NVMEM_APPLE_SPMI=m +CONFIG_MUX_APPLE_DPXBAR=m From 9e3639bb87d2d4c1cd2e109edbff8cc7b091628c Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Wed, 5 Nov 2025 22:36:56 +0100 Subject: [PATCH 0080/3902] fixup! arm64: configs: Add asahi.config fragment Signed-off-by: Janne Grunau --- arch/arm64/configs/asahi.config | 1 + 1 file changed, 1 insertion(+) diff --git a/arch/arm64/configs/asahi.config b/arch/arm64/configs/asahi.config index f47b8e76200541..6c287b81785c55 100644 --- a/arch/arm64/configs/asahi.config +++ b/arch/arm64/configs/asahi.config @@ -24,6 +24,7 @@ CONFIG_CHARGER_MACSMC=m CONFIG_SENSORS_MACSMC_HWMON=m CONFIG_APPLE_WATCHDOG=m CONFIG_VIDEO_APPLE_ISP=m +CONFIG_DRM=y CONFIG_DRM_ASAHI=m CONFIG_DRM_ADP=m CONFIG_DRM_APPLE=m From 09075fd17a4ffc0cb421b3d229781fe7c98b1219 Mon Sep 17 00:00:00 2001 From: Jens Axboe Date: Wed, 16 Feb 2022 12:17:58 -0700 Subject: [PATCH 0081/3902] apple-nvme: defer cache flushes by a specified amount Cache flushes on the M1 nvme are really slow, taking 17-18 msec to complete. This can slow down workloads considerably, pure random writes end up being bound by the flush latency and hence run at 55-60 IOPS. Add a deferred flush work around to provide better performance, at a minimal risk. By default, flushes are delayed at most 1 second, but this is configurable. With this work-around, a pure random write workload runs at ~12K IOPS rather than 56 IOPS. Signed-off-by: Jens Axboe --- drivers/nvme/host/apple.c | 68 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 68 insertions(+) diff --git a/drivers/nvme/host/apple.c b/drivers/nvme/host/apple.c index f35d3f71d14f32..fce230f9b2d5da 100644 --- a/drivers/nvme/host/apple.c +++ b/drivers/nvme/host/apple.c @@ -203,8 +203,20 @@ struct apple_nvme { int irq; spinlock_t lock; + + /* + * Delayed cache flush handling state + */ + struct nvme_ns *flush_ns; + unsigned long flush_interval; + unsigned long last_flush; + struct delayed_work flush_dwork; }; +unsigned int flush_interval = 1000; +module_param(flush_interval, uint, 0644); +MODULE_PARM_DESC(flush_interval, "Grace period in msecs between flushes"); + static_assert(sizeof(struct nvme_command) == 64); static_assert(sizeof(struct apple_nvmmu_tcb) == 128); @@ -762,6 +774,26 @@ static int apple_nvme_remove_sq(struct apple_nvme *anv) return nvme_submit_sync_cmd(anv->ctrl.admin_q, &c, NULL, 0); } +static bool apple_nvme_delayed_flush(struct apple_nvme *anv, struct nvme_ns *ns, + struct request *req) +{ + if (!anv->flush_interval || req_op(req) != REQ_OP_FLUSH) + return false; + if (delayed_work_pending(&anv->flush_dwork)) + return true; + if (time_before(jiffies, anv->last_flush + anv->flush_interval)) { + kblockd_mod_delayed_work_on(WORK_CPU_UNBOUND, &anv->flush_dwork, + anv->flush_interval); + if (WARN_ON_ONCE(anv->flush_ns && anv->flush_ns != ns)) + goto out; + anv->flush_ns = ns; + return true; + } +out: + anv->last_flush = jiffies; + return false; +} + static blk_status_t apple_nvme_queue_rq(struct blk_mq_hw_ctx *hctx, const struct blk_mq_queue_data *bd) { @@ -798,6 +830,11 @@ static blk_status_t apple_nvme_queue_rq(struct blk_mq_hw_ctx *hctx, nvme_start_request(req); + if (apple_nvme_delayed_flush(anv, ns, req)) { + blk_mq_complete_request(req); + return BLK_STS_OK; + } + if (anv->hw->has_lsq_nvmmu) apple_nvme_submit_cmd_t8103(q, cmnd); else @@ -1452,6 +1489,28 @@ static void devm_apple_nvme_mempool_destroy(void *data) mempool_destroy(data); } +static void apple_nvme_flush_work(struct work_struct *work) +{ + struct nvme_command c = { }; + struct apple_nvme *anv; + struct nvme_ns *ns; + int err; + + anv = container_of(work, struct apple_nvme, flush_dwork.work); + ns = anv->flush_ns; + if (WARN_ON_ONCE(!ns)) + return; + + c.common.opcode = nvme_cmd_flush; + c.common.nsid = cpu_to_le32(anv->flush_ns->head->ns_id); + err = nvme_submit_sync_cmd(ns->queue, &c, NULL, 0); + if (err) { + dev_err(anv->dev, "Deferred flush failed: %d\n", err); + } else { + anv->last_flush = jiffies; + } +} + static struct apple_nvme *apple_nvme_alloc(struct platform_device *pdev) { struct device *dev = &pdev->dev; @@ -1620,6 +1679,14 @@ static int apple_nvme_probe(struct platform_device *pdev) goto out_uninit_ctrl; } + if (flush_interval) { + anv->flush_interval = msecs_to_jiffies(flush_interval); + anv->flush_ns = NULL; + anv->last_flush = jiffies - anv->flush_interval; + } + + INIT_DELAYED_WORK(&anv->flush_dwork, apple_nvme_flush_work); + nvme_reset_ctrl(&anv->ctrl); async_schedule(apple_nvme_async_probe, anv); @@ -1657,6 +1724,7 @@ static void apple_nvme_shutdown(struct platform_device *pdev) { struct apple_nvme *anv = platform_get_drvdata(pdev); + flush_delayed_work(&anv->flush_dwork); apple_nvme_disable(anv, true); if (apple_rtkit_is_running(anv->rtk)) { apple_rtkit_shutdown(anv->rtk); From 72a9b48414de80f68094715b02e4494788b10c2c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20Povi=C5=A1er?= Date: Fri, 19 Aug 2022 18:38:04 +0200 Subject: [PATCH 0082/3902] ASoC: ops: Move guts out of snd_soc_limit_volume MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit In advance of other changes, move the modification of the control itself into function of its own. Signed-off-by: Martin Povišer --- sound/soc/soc-ops.c | 28 +++++++++++++++------------- 1 file changed, 15 insertions(+), 13 deletions(-) diff --git a/sound/soc/soc-ops.c b/sound/soc/soc-ops.c index d2b6fb8e0b6c69..95546f2850abeb 100644 --- a/sound/soc/soc-ops.c +++ b/sound/soc/soc-ops.c @@ -428,6 +428,16 @@ static int snd_soc_clip_to_platform_max(struct snd_kcontrol *kctl) return ret; } +static int soc_limit_volume(struct snd_kcontrol *kctl, int max) +{ + struct soc_mixer_control *mc = (struct soc_mixer_control *)kctl->private_value; + + if (max <= 0 || max > mc->max - mc->min) + return -EINVAL; + mc->platform_max = max; + return snd_soc_clip_to_platform_max(kctl); +} + /** * snd_soc_limit_volume - Set new limit to an existing volume control. * @@ -440,24 +450,16 @@ static int snd_soc_clip_to_platform_max(struct snd_kcontrol *kctl) int snd_soc_limit_volume(struct snd_soc_card *card, const char *name, int max) { struct snd_kcontrol *kctl; - int ret = -EINVAL; - /* Sanity check for name and max */ - if (unlikely(!name || max <= 0)) + /* Sanity check for name */ + if (unlikely(!name)) return -EINVAL; kctl = snd_soc_card_get_kcontrol(card, name); - if (kctl) { - struct soc_mixer_control *mc = - (struct soc_mixer_control *)kctl->private_value; - - if (max <= mc->max - mc->min) { - mc->platform_max = max; - ret = snd_soc_clip_to_platform_max(kctl); - } - } + if (!kctl) + return -EINVAL; - return ret; + return soc_limit_volume(kctl, max); } EXPORT_SYMBOL_GPL(snd_soc_limit_volume); From aae16c0bb3dbf28ab890dd7741159ad07a2bc2a5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20Povi=C5=A1er?= Date: Fri, 19 Aug 2022 19:15:54 +0200 Subject: [PATCH 0083/3902] ASoC: ops: Accept patterns in snd_soc_limit_volume MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit In snd_soc_limit_volume, instead of looking up a single control by name, also understand wildcard-starting patterns like '* Amp Gain Volume' to touch many controls at one. Signed-off-by: Martin Povišer --- sound/soc/soc-ops.c | 51 +++++++++++++++++++++++++++++++++++++++------ 1 file changed, 45 insertions(+), 6 deletions(-) diff --git a/sound/soc/soc-ops.c b/sound/soc/soc-ops.c index 95546f2850abeb..2ec69acd31fc17 100644 --- a/sound/soc/soc-ops.c +++ b/sound/soc/soc-ops.c @@ -397,6 +397,29 @@ int snd_soc_put_volsw_sx(struct snd_kcontrol *kcontrol, } EXPORT_SYMBOL_GPL(snd_soc_put_volsw_sx); +static bool soc_control_matches(struct snd_kcontrol *kctl, + const char *pattern) +{ + const char *name = kctl->id.name; + + if (pattern[0] == '*') { + int namelen; + int patternlen; + + pattern++; + if (pattern[0] == ' ') + pattern++; + + namelen = strlen(name); + patternlen = strlen(pattern); + + if (namelen > patternlen) + name += namelen - patternlen; + } + + return !strcmp(name, pattern); +} + static int snd_soc_clip_to_platform_max(struct snd_kcontrol *kctl) { struct soc_mixer_control *mc = (struct soc_mixer_control *)kctl->private_value; @@ -439,27 +462,43 @@ static int soc_limit_volume(struct snd_kcontrol *kctl, int max) } /** - * snd_soc_limit_volume - Set new limit to an existing volume control. + * snd_soc_limit_volume - Set new limit to existing volume controls * * @card: where to look for the control - * @name: Name of the control + * @name: name pattern * @max: new maximum limit + * + * Finds controls matching the given name (which can be either a name + * verbatim, or a pattern starting with the wildcard '*') and sets + * a platform volume limit on them. * - * Return 0 for success, else error. + * Return number of matching controls on success, else error. At least + * one control needs to match the pattern. */ int snd_soc_limit_volume(struct snd_soc_card *card, const char *name, int max) { struct snd_kcontrol *kctl; + int hits = 0; + int ret; /* Sanity check for name */ if (unlikely(!name)) return -EINVAL; - kctl = snd_soc_card_get_kcontrol(card, name); - if (!kctl) + list_for_each_entry(kctl, &card->snd_card->controls, list) { + if (!soc_control_matches(kctl, name)) + continue; + + ret = soc_limit_volume(kctl, max); + if (ret < 0) + return ret; + hits++; + } + + if (!hits) return -EINVAL; - return soc_limit_volume(kctl, max); + return hits; } EXPORT_SYMBOL_GPL(snd_soc_limit_volume); From 16c2fc127a1c2d1d2ca95e4d665d2ba8daaa2f8a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20Povi=C5=A1er?= Date: Fri, 19 Aug 2022 19:24:35 +0200 Subject: [PATCH 0084/3902] ASoC: ops: Introduce 'snd_soc_deactivate_kctl' MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The new function can be used to deactivate controls -- either a single one or in bulk by pattern. It is something a machine driver may call in fixup_controls. Signed-off-by: Martin Povišer --- include/sound/soc.h | 2 ++ sound/soc/soc-ops.c | 38 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 40 insertions(+) diff --git a/include/sound/soc.h b/include/sound/soc.h index ddc508ff7b9be1..773193ea1a9c2e 100644 --- a/include/sound/soc.h +++ b/include/sound/soc.h @@ -563,6 +563,8 @@ int snd_soc_put_volsw_sx(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol); int snd_soc_limit_volume(struct snd_soc_card *card, const char *name, int max); +int snd_soc_deactivate_kctl(struct snd_soc_card *card, + const char *name, int active); int snd_soc_bytes_info(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo); int snd_soc_bytes_get(struct snd_kcontrol *kcontrol, diff --git a/sound/soc/soc-ops.c b/sound/soc/soc-ops.c index 2ec69acd31fc17..9e963295777fe9 100644 --- a/sound/soc/soc-ops.c +++ b/sound/soc/soc-ops.c @@ -502,6 +502,44 @@ int snd_soc_limit_volume(struct snd_soc_card *card, const char *name, int max) } EXPORT_SYMBOL_GPL(snd_soc_limit_volume); +/** + * snd_soc_deactivate_kctl - Activate/deactive controls matching a pattern + * + * @card: where to look for the controls + * @name: name pattern + * @active: non-zero to activate, zero to deactivate + * + * Return number of matching controls on success, else error. + * No controls need to match. + */ +int snd_soc_deactivate_kctl(struct snd_soc_card *card, + const char *name, int active) +{ + struct snd_kcontrol *kctl; + int hits = 0; + int ret; + + /* Sanity check for name */ + if (unlikely(!name)) + return -EINVAL; + + list_for_each_entry(kctl, &card->snd_card->controls, list) { + if (!soc_control_matches(kctl, name)) + continue; + + ret = snd_ctl_activate_id(card->snd_card, &kctl->id, active); + if (ret < 0) + return ret; + hits++; + } + + if (!hits) + return -EINVAL; + + return hits; +} +EXPORT_SYMBOL_GPL(snd_soc_deactivate_kctl); + int snd_soc_bytes_info(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo) { From 9521282aa030d8bc89c4ac794bf55802a636ba00 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20Povi=C5=A1er?= Date: Fri, 19 Aug 2022 19:25:36 +0200 Subject: [PATCH 0085/3902] ASoC: ops: Introduce 'soc_set_enum_kctl' MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The new function is to be used to set enumerated controls to desired values -- either a single control or many controls in bulk by pattern. It is something a machine driver may call in fixup_controls. Signed-off-by: Martin Povišer --- include/sound/soc.h | 2 ++ sound/soc/soc-ops.c | 70 +++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 72 insertions(+) diff --git a/include/sound/soc.h b/include/sound/soc.h index 773193ea1a9c2e..c2cc491931a8c0 100644 --- a/include/sound/soc.h +++ b/include/sound/soc.h @@ -565,6 +565,8 @@ int snd_soc_limit_volume(struct snd_soc_card *card, const char *name, int max); int snd_soc_deactivate_kctl(struct snd_soc_card *card, const char *name, int active); +int snd_soc_set_enum_kctl(struct snd_soc_card *card, + const char *name, const char *strval); int snd_soc_bytes_info(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo); int snd_soc_bytes_get(struct snd_kcontrol *kcontrol, diff --git a/sound/soc/soc-ops.c b/sound/soc/soc-ops.c index 9e963295777fe9..5b344dcc89476d 100644 --- a/sound/soc/soc-ops.c +++ b/sound/soc/soc-ops.c @@ -540,6 +540,76 @@ int snd_soc_deactivate_kctl(struct snd_soc_card *card, } EXPORT_SYMBOL_GPL(snd_soc_deactivate_kctl); +static int soc_set_enum_kctl(struct snd_kcontrol *kctl, const char *strval) +{ + struct snd_ctl_elem_value value; + struct snd_ctl_elem_info info; + int sel, i, ret; + + ret = kctl->info(kctl, &info); + if (ret < 0) + return ret; + + if (info.type != SNDRV_CTL_ELEM_TYPE_ENUMERATED) + return -EINVAL; + + for (sel = 0; sel < info.value.enumerated.items; sel++) { + info.value.enumerated.item = sel; + ret = kctl->info(kctl, &info); + if (ret < 0) + return ret; + + if (!strcmp(strval, info.value.enumerated.name)) + break; + } + + if (sel == info.value.enumerated.items) + return -EINVAL; + + for (i = 0; i < info.count; i++) + value.value.enumerated.item[i] = sel; + + return kctl->put(kctl, &value); +} + +/** + * snd_soc_set_enum_kctl - Set enumerated controls matching a pattern + * + * @card: where to look for the controls + * @name: name pattern + * @value: string value to set the controls to + * + * Return number of matching and set controls on success, else error. + * No controls need to match. + */ +int snd_soc_set_enum_kctl(struct snd_soc_card *card, + const char *name, const char *value) +{ + struct snd_kcontrol *kctl; + int hits = 0; + int ret; + + /* Sanity check for name */ + if (unlikely(!name)) + return -EINVAL; + + list_for_each_entry(kctl, &card->snd_card->controls, list) { + if (!soc_control_matches(kctl, name)) + continue; + + ret = soc_set_enum_kctl(kctl, value); + if (ret < 0) + return ret; + hits++; + } + + if (!hits) + return -EINVAL; + + return hits; +} +EXPORT_SYMBOL_GPL(snd_soc_set_enum_kctl); + int snd_soc_bytes_info(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo) { From 2f9b0f3e3cc1357ffe6e9cc9f417fa35ca7156ab Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20Povi=C5=A1er?= Date: Fri, 19 Aug 2022 21:09:35 +0200 Subject: [PATCH 0086/3902] ASoC: card: Let 'fixup_controls' return errors MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Let the 'fixup_controls' card method return error values which will roll back the half-done binding of the card. Signed-off-by: Martin Povišer --- include/sound/soc-card.h | 2 +- include/sound/soc.h | 2 +- sound/soc/mediatek/mt8188/mt8188-mt6359.c | 4 +++- sound/soc/soc-card.c | 12 +++++++++--- sound/soc/soc-core.c | 5 ++++- 5 files changed, 18 insertions(+), 7 deletions(-) diff --git a/include/sound/soc-card.h b/include/sound/soc-card.h index ecc02e955279fd..ef46cac97d9968 100644 --- a/include/sound/soc-card.h +++ b/include/sound/soc-card.h @@ -44,7 +44,7 @@ int snd_soc_card_resume_post(struct snd_soc_card *card); int snd_soc_card_probe(struct snd_soc_card *card); int snd_soc_card_late_probe(struct snd_soc_card *card); -void snd_soc_card_fixup_controls(struct snd_soc_card *card); +int snd_soc_card_fixup_controls(struct snd_soc_card *card); int snd_soc_card_remove(struct snd_soc_card *card); int snd_soc_card_set_bias_level(struct snd_soc_card *card, diff --git a/include/sound/soc.h b/include/sound/soc.h index c2cc491931a8c0..1d59c8dc18761c 100644 --- a/include/sound/soc.h +++ b/include/sound/soc.h @@ -993,7 +993,7 @@ struct snd_soc_card { int (*probe)(struct snd_soc_card *card); int (*late_probe)(struct snd_soc_card *card); - void (*fixup_controls)(struct snd_soc_card *card); + int (*fixup_controls)(struct snd_soc_card *card); int (*remove)(struct snd_soc_card *card); /* the pre and post PM functions are used to do any PM work before and diff --git a/sound/soc/mediatek/mt8188/mt8188-mt6359.c b/sound/soc/mediatek/mt8188/mt8188-mt6359.c index c6e7461e8f764c..7f4b9eabf41129 100644 --- a/sound/soc/mediatek/mt8188/mt8188-mt6359.c +++ b/sound/soc/mediatek/mt8188/mt8188-mt6359.c @@ -1277,7 +1277,7 @@ static struct snd_soc_dai_link mt8188_mt6359_dai_links[] = { }, }; -static void mt8188_fixup_controls(struct snd_soc_card *card) +static int mt8188_fixup_controls(struct snd_soc_card *card) { struct mtk_soc_card_data *soc_card_data = snd_soc_card_get_drvdata(card); struct mtk_platform_card_data *card_data = soc_card_data->card_data; @@ -1299,6 +1299,8 @@ static void mt8188_fixup_controls(struct snd_soc_card *card) else dev_warn(card->dev, "Cannot find ctl : Headphone Switch\n"); } + + return 0; } static struct snd_soc_card mt8188_mt6359_soc_card = { diff --git a/sound/soc/soc-card.c b/sound/soc/soc-card.c index 235427d6906173..bc02c7b864e295 100644 --- a/sound/soc/soc-card.c +++ b/sound/soc/soc-card.c @@ -184,10 +184,16 @@ int snd_soc_card_late_probe(struct snd_soc_card *card) return 0; } -void snd_soc_card_fixup_controls(struct snd_soc_card *card) +int snd_soc_card_fixup_controls(struct snd_soc_card *card) { - if (card->fixup_controls) - card->fixup_controls(card); + if (card->fixup_controls) { + int ret = card->fixup_controls(card); + + if (ret < 0) + return soc_card_ret(card, ret); + } + + return 0; } int snd_soc_card_remove(struct snd_soc_card *card) diff --git a/sound/soc/soc-core.c b/sound/soc/soc-core.c index 9dd84d73046be7..3f6db1f1a50820 100644 --- a/sound/soc/soc-core.c +++ b/sound/soc/soc-core.c @@ -2284,7 +2284,10 @@ static int snd_soc_bind_card(struct snd_soc_card *card) goto probe_end; snd_soc_dapm_new_widgets(card); - snd_soc_card_fixup_controls(card); + + ret = snd_soc_card_fixup_controls(card); + if (ret < 0) + goto probe_end; ret = snd_card_register(card->snd_card); if (ret < 0) { From 94b0fb1992279363d62cdc2ceb66524219e13c7a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20Povi=C5=A1er?= Date: Thu, 31 Mar 2022 01:16:48 +0200 Subject: [PATCH 0087/3902] dt-bindings: sound: Add Apple Macs sound peripherals MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add binding for Apple Silicon Macs' machine-level integration of sound peripherals. Signed-off-by: Martin Povišer --- .../bindings/sound/apple,macaudio.yaml | 162 ++++++++++++++++++ 1 file changed, 162 insertions(+) create mode 100644 Documentation/devicetree/bindings/sound/apple,macaudio.yaml diff --git a/Documentation/devicetree/bindings/sound/apple,macaudio.yaml b/Documentation/devicetree/bindings/sound/apple,macaudio.yaml new file mode 100644 index 00000000000000..8fe22dec3015d6 --- /dev/null +++ b/Documentation/devicetree/bindings/sound/apple,macaudio.yaml @@ -0,0 +1,162 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/sound/apple,macaudio.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Apple Silicon Macs integrated sound peripherals + +description: + This binding represents the overall machine-level integration of sound + peripherals on 'Apple Silicon' machines by Apple. + +maintainers: + - Martin Povišer + +properties: + compatible: + items: + - enum: + - apple,j274-macaudio + - apple,j293-macaudio + - apple,j314-macaudio + - const: apple,macaudio + + "#address-cells": + const: 1 + + "#size-cells": + const: 0 + + model: + description: + Model name for presentation to users + $ref: /schemas/types.yaml#/definitions/string + +patternProperties: + "^dai-link(@[0-9a-f]+)?$": + description: | + Node for each sound peripheral such as the speaker array, headphones jack, + or microphone. + type: object + + additionalProperties: false + + properties: + reg: + maxItems: 1 + + link-name: + description: | + Name for the peripheral, expecting 'Speaker' or 'Speakers' if this is + the speaker array. + $ref: /schemas/types.yaml#/definitions/string + + cpu: + type: object + + properties: + sound-dai: + description: | + DAI list with CPU-side I2S ports involved in this peripheral. + minItems: 1 + maxItems: 2 + + required: + - sound-dai + + codec: + type: object + + properties: + sound-dai: + minItems: 1 + maxItems: 8 + description: | + DAI list with the CODEC-side DAIs connected to the above CPU-side + DAIs and involved in this sound peripheral. + + The list is in left/right order if applicable. If there are more + than one CPU-side DAIs (there can be two), the CODECs must be + listed first those connected to the first CPU, then those + connected to the second. + + In addition, on some machines with many speaker codecs, the CODECs + are listed in this fixed order: + + J293: Left Front, Left Rear, Right Front, Right Rear + J314: Left Woofer 1, Left Tweeter, Left Woofer 2, + Right Woofer 1, Right Tweeter, Right Woofer 2 + + required: + - sound-dai + + required: + - reg + - cpu + - codec + +required: + - compatible + - model + +additionalProperties: false + +examples: + - | + mca: mca@9b600000 { + compatible = "apple,t6000-mca", "apple,mca"; + reg = <0x9b600000 0x10000>, + <0x9b500000 0x20000>; + + clocks = <&nco 0>, <&nco 1>, <&nco 2>, <&nco 3>; + power-domains = <&ps_audio_p>, <&ps_mca0>, <&ps_mca1>, + <&ps_mca2>, <&ps_mca3>; + dmas = <&admac 0>, <&admac 1>, <&admac 2>, <&admac 3>, + <&admac 4>, <&admac 5>, <&admac 6>, <&admac 7>, + <&admac 8>, <&admac 9>, <&admac 10>, <&admac 11>, + <&admac 12>, <&admac 13>, <&admac 14>, <&admac 15>; + dma-names = "tx0a", "rx0a", "tx0b", "rx0b", + "tx1a", "rx1a", "tx1b", "rx1b", + "tx2a", "rx2a", "tx2b", "rx2b", + "tx3a", "rx3a", "tx3b", "rx3b"; + + #sound-dai-cells = <1>; + }; + + sound { + compatible = "apple,j314-macaudio", "apple,macaudio"; + model = "MacBook Pro J314 integrated audio"; + + #address-cells = <1>; + #size-cells = <0>; + + dai-link@0 { + reg = <0>; + link-name = "Speakers"; + + cpu { + sound-dai = <&mca 0>, <&mca 1>; + }; + codec { + sound-dai = <&speaker_left_woof1>, + <&speaker_left_tweet>, + <&speaker_left_woof2>, + <&speaker_right_woof1>, + <&speaker_right_tweet>, + <&speaker_right_woof2>; + }; + }; + + dai-link@1 { + reg = <1>; + link-name = "Headphones Jack"; + + cpu { + sound-dai = <&mca 2>; + }; + codec { + sound-dai = <&jack_codec>; + }; + }; + }; From b9ef0d97ecb5eb72fc87d340bd4d8918f1ae4fcf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20Povi=C5=A1er?= Date: Sat, 19 Feb 2022 09:49:56 +0100 Subject: [PATCH 0088/3902] ASoC: apple: Add macaudio machine driver MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Martin Povišer --- sound/soc/apple/Kconfig | 16 + sound/soc/apple/Makefile | 4 + sound/soc/apple/macaudio.c | 923 +++++++++++++++++++++++++++++++++++++ 3 files changed, 943 insertions(+) create mode 100644 sound/soc/apple/macaudio.c diff --git a/sound/soc/apple/Kconfig b/sound/soc/apple/Kconfig index d8dc2f1ccc83e0..9e8232f8156050 100644 --- a/sound/soc/apple/Kconfig +++ b/sound/soc/apple/Kconfig @@ -8,4 +8,20 @@ config SND_SOC_APPLE_MCA This option enables an ASoC platform driver for MCA peripherals found on Apple Silicon SoCs. +config SND_SOC_APPLE_MACAUDIO + tristate "Sound support for Apple Silicon Macs" + depends on ARCH_APPLE || COMPILE_TEST + select SND_SOC_APPLE_MCA + select SND_SIMPLE_CARD_UTILS + select APPLE_ADMAC if DMADEVICES + select COMMON_CLK_APPLE_NCO + select SND_SOC_TAS2764 if I2C + select SND_SOC_TAS2770 if I2C + select SND_SOC_CS42L83 if I2C + select SND_SOC_CS42L84 if I2C + help + This option enables an ASoC machine-level driver for Apple Silicon Macs + and it also enables the required SoC and codec drivers for overall + sound support on these machines. + endmenu diff --git a/sound/soc/apple/Makefile b/sound/soc/apple/Makefile index 1eb8fbef60c617..c78178f365ea65 100644 --- a/sound/soc/apple/Makefile +++ b/sound/soc/apple/Makefile @@ -1,3 +1,7 @@ snd-soc-apple-mca-y := mca.o obj-$(CONFIG_SND_SOC_APPLE_MCA) += snd-soc-apple-mca.o + +snd-soc-macaudio-objs := macaudio.o + +obj-$(CONFIG_SND_SOC_APPLE_MACAUDIO) += snd-soc-macaudio.o diff --git a/sound/soc/apple/macaudio.c b/sound/soc/apple/macaudio.c new file mode 100644 index 00000000000000..1e6007bd5336bf --- /dev/null +++ b/sound/soc/apple/macaudio.c @@ -0,0 +1,923 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * ASoC machine driver for Apple Silicon Macs + * + * Copyright (C) The Asahi Linux Contributors + * + * Based on sound/soc/qcom/{sc7180.c|common.c} + * Copyright (c) 2018, Linaro Limited. + * Copyright (c) 2020, The Linux Foundation. All rights reserved. + * + * + * The platform driver has independent frontend and backend DAIs with the + * option of routing backends to any of the frontends. The platform + * driver configures the routing based on DPCM couplings in ASoC runtime + * structures, which in turn are determined from DAPM paths by ASoC. But the + * platform driver doesn't supply relevant DAPM paths and leaves that up for + * the machine driver to fill in. The filled-in virtual topology can be + * anything as long as any backend isn't connected to more than one frontend + * at any given time. (The limitation is due to the unsupported case of + * reparenting of live BEs.) + */ + +#define DEBUG + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define DRIVER_NAME "snd-soc-macaudio" + +/* + * CPU side is bit and frame clock provider + * I2S has both clocks inverted + */ +#define MACAUDIO_DAI_FMT (SND_SOC_DAIFMT_I2S | \ + SND_SOC_DAIFMT_CBC_CFC | \ + SND_SOC_DAIFMT_GATED | \ + SND_SOC_DAIFMT_IB_IF) +#define MACAUDIO_JACK_MASK (SND_JACK_HEADSET | SND_JACK_HEADPHONE) +#define MACAUDIO_SLOTWIDTH 32 + +struct macaudio_snd_data { + struct snd_soc_card card; + struct snd_soc_jack jack; + int jack_plugin_state; + + bool has_speakers; + + struct macaudio_link_props { + /* frontend props */ + unsigned int bclk_ratio; + + /* backend props */ + bool is_speakers; + bool is_headphones; + unsigned int tdm_mask; + } *link_props; + + unsigned int speaker_nchans_array[2]; + struct snd_pcm_hw_constraint_list speaker_nchans_list; +}; + +static bool void_warranty; +module_param(void_warranty, bool, 0644); +MODULE_PARM_DESC(void_warranty, "Do not bail if safety is not assured"); + +SND_SOC_DAILINK_DEFS(primary, + DAILINK_COMP_ARRAY(COMP_CPU("mca-pcm-0")), // CPU + DAILINK_COMP_ARRAY(COMP_DUMMY()), // CODEC + DAILINK_COMP_ARRAY(COMP_EMPTY())); // platform (filled at runtime) + +SND_SOC_DAILINK_DEFS(secondary, + DAILINK_COMP_ARRAY(COMP_CPU("mca-pcm-1")), // CPU + DAILINK_COMP_ARRAY(COMP_DUMMY()), // CODEC + DAILINK_COMP_ARRAY(COMP_EMPTY())); + +static struct snd_soc_dai_link macaudio_fe_links[] = { + { + .name = "Primary", + .stream_name = "Primary", + .dynamic = 1, + .dpcm_playback = 1, + .dpcm_capture = 1, + .dpcm_merged_rate = 1, + .dpcm_merged_chan = 1, + .dpcm_merged_format = 1, + .dai_fmt = MACAUDIO_DAI_FMT, + SND_SOC_DAILINK_REG(primary), + }, + { + .name = "Secondary", + .stream_name = "Secondary", + .dynamic = 1, + .dpcm_playback = 1, + .dpcm_merged_rate = 1, + .dpcm_merged_chan = 1, + .dpcm_merged_format = 1, + .dai_fmt = MACAUDIO_DAI_FMT, + SND_SOC_DAILINK_REG(secondary), + }, +}; + +static struct macaudio_link_props macaudio_fe_link_props[] = { + { + /* + * Primary FE + * + * The bclk ratio at 64 for the primary frontend is important + * to ensure that the headphones codec's idea of left and right + * in a stereo stream over I2S fits in nicely with everyone else's. + * (This is until the headphones codec's driver supports + * set_tdm_slot.) + * + * The low bclk ratio precludes transmitting more than two + * channels over I2S, but that's okay since there is the secondary + * FE for speaker arrays anyway. + */ + .bclk_ratio = 64, + }, + { + /* + * Secondary FE + * + * Here we want frames plenty long to be able to drive all + * those fancy speaker arrays. + */ + .bclk_ratio = 256, + } +}; + +static int macaudio_copy_link(struct device *dev, struct snd_soc_dai_link *target, + struct snd_soc_dai_link *source) +{ + memcpy(target, source, sizeof(struct snd_soc_dai_link)); + + target->cpus = devm_kmemdup(dev, target->cpus, + sizeof(*target->cpus) * target->num_cpus, + GFP_KERNEL); + target->codecs = devm_kmemdup(dev, target->codecs, + sizeof(*target->codecs) * target->num_codecs, + GFP_KERNEL); + target->platforms = devm_kmemdup(dev, target->platforms, + sizeof(*target->platforms) * target->num_platforms, + GFP_KERNEL); + + if (!target->cpus || !target->codecs || !target->platforms) + return -ENOMEM; + + return 0; +} + +static int macaudio_parse_of_component(struct device_node *node, int index, + struct snd_soc_dai_link_component *comp) +{ + struct of_phandle_args args; + int ret; + + ret = of_parse_phandle_with_args(node, "sound-dai", "#sound-dai-cells", + index, &args); + if (ret) + return ret; + comp->of_node = args.np; + return snd_soc_get_dai_name(&args, &comp->dai_name); +} + +/* + * Parse one DPCM backend from the devicetree. This means taking one + * of the CPU DAIs and combining it with one or more CODEC DAIs. + */ +static int macaudio_parse_of_be_dai_link(struct macaudio_snd_data *ma, + struct snd_soc_dai_link *link, + int be_index, int ncodecs_per_be, + struct device_node *cpu, + struct device_node *codec) +{ + struct snd_soc_dai_link_component *comp; + struct device *dev = ma->card.dev; + int codec_base = be_index * ncodecs_per_be; + int ret, i; + + link->no_pcm = 1; + link->dpcm_playback = 1; + link->dpcm_capture = 1; + + link->dai_fmt = MACAUDIO_DAI_FMT; + + link->num_codecs = ncodecs_per_be; + link->codecs = devm_kcalloc(dev, ncodecs_per_be, + sizeof(*comp), GFP_KERNEL); + link->num_cpus = 1; + link->cpus = devm_kzalloc(dev, sizeof(*comp), GFP_KERNEL); + + if (!link->codecs || !link->cpus) + return -ENOMEM; + + link->num_platforms = 0; + + for_each_link_codecs(link, i, comp) { + ret = macaudio_parse_of_component(codec, codec_base + i, comp); + if (ret) + return ret; + } + + ret = macaudio_parse_of_component(cpu, be_index, link->cpus); + if (ret) + return ret; + + link->name = link->cpus[0].dai_name; + + return 0; +} + +static int macaudio_parse_of(struct macaudio_snd_data *ma) +{ + struct device_node *codec = NULL; + struct device_node *cpu = NULL; + struct device_node *np = NULL; + struct device_node *platform = NULL; + struct snd_soc_dai_link *link = NULL; + struct snd_soc_card *card = &ma->card; + struct device *dev = card->dev; + struct macaudio_link_props *link_props; + int ret, num_links, i; + + ret = snd_soc_of_parse_card_name(card, "model"); + if (ret) { + dev_err(dev, "Error parsing card name: %d\n", ret); + return ret; + } + + /* Populate links, start with the fixed number of FE links */ + num_links = ARRAY_SIZE(macaudio_fe_links); + + /* Now add together the (dynamic) number of BE links */ + for_each_available_child_of_node(dev->of_node, np) { + int num_cpus; + + cpu = of_get_child_by_name(np, "cpu"); + if (!cpu) { + dev_err(dev, "missing CPU DAI node at %pOF\n", np); + ret = -EINVAL; + goto err_free; + } + + num_cpus = of_count_phandle_with_args(cpu, "sound-dai", + "#sound-dai-cells"); + + if (num_cpus <= 0) { + dev_err(card->dev, "missing sound-dai property at %pOF\n", cpu); + ret = -EINVAL; + goto err_free; + } + of_node_put(cpu); + cpu = NULL; + + /* Each CPU specified counts as one BE link */ + num_links += num_cpus; + } + + /* Allocate the DAI link array */ + card->dai_link = devm_kcalloc(dev, num_links, sizeof(*link), GFP_KERNEL); + ma->link_props = devm_kcalloc(dev, num_links, sizeof(*ma->link_props), GFP_KERNEL); + if (!card->dai_link || !ma->link_props) + return -ENOMEM; + + card->num_links = num_links; + link = card->dai_link; + link_props = ma->link_props; + + for (i = 0; i < ARRAY_SIZE(macaudio_fe_links); i++) { + ret = macaudio_copy_link(dev, link, &macaudio_fe_links[i]); + if (ret) + goto err_free; + + memcpy(link_props, &macaudio_fe_link_props[i], sizeof(struct macaudio_link_props)); + link++; link_props++; + } + + for (i = 0; i < num_links; i++) + card->dai_link[i].id = i; + + /* Fill in the BEs */ + for_each_available_child_of_node(dev->of_node, np) { + const char *link_name; + bool speakers; + int be_index, num_codecs, num_bes, ncodecs_per_cpu, nchannels; + unsigned int left_mask, right_mask; + + ret = of_property_read_string(np, "link-name", &link_name); + if (ret) { + dev_err(card->dev, "missing link name\n"); + goto err_free; + } + + speakers = !strcmp(link_name, "Speaker") + || !strcmp(link_name, "Speakers"); + if (speakers) + ma->has_speakers = 1; + + cpu = of_get_child_by_name(np, "cpu"); + codec = of_get_child_by_name(np, "codec"); + + if (!codec || !cpu) { + dev_err(dev, "missing DAI specifications for '%s'\n", link_name); + ret = -EINVAL; + goto err_free; + } + + num_bes = of_count_phandle_with_args(cpu, "sound-dai", + "#sound-dai-cells"); + if (num_bes <= 0) { + dev_err(card->dev, "missing sound-dai property at %pOF\n", cpu); + ret = -EINVAL; + goto err_free; + } + + num_codecs = of_count_phandle_with_args(codec, "sound-dai", + "#sound-dai-cells"); + if (num_codecs <= 0) { + dev_err(card->dev, "missing sound-dai property at %pOF\n", codec); + ret = -EINVAL; + goto err_free; + } + + if (num_codecs % num_bes != 0) { + dev_err(card->dev, "bad combination of CODEC (%d) and CPU (%d) number at %pOF\n", + num_codecs, num_bes, np); + ret = -EINVAL; + goto err_free; + } + + /* + * Now parse the cpu/codec lists into a number of DPCM backend links. + * In each link there will be one DAI from the cpu list paired with + * an evenly distributed number of DAIs from the codec list. (As is + * the binding semantics.) + */ + ncodecs_per_cpu = num_codecs / num_bes; + nchannels = num_codecs * (speakers ? 1 : 2); + + /* + * If there is a single speaker, assign two channels to it, because + * it can do downmix. + */ + if (nchannels < 2) + nchannels = 2; + + left_mask = 0; + for (i = 0; i < nchannels; i += 2) + left_mask = left_mask << 2 | 1; + right_mask = left_mask << 1; + + for (be_index = 0; be_index < num_bes; be_index++) { + ret = macaudio_parse_of_be_dai_link(ma, link, be_index, + ncodecs_per_cpu, cpu, codec); + if (ret) + goto err_free; + + link_props->is_speakers = speakers; + link_props->is_headphones = !speakers; + + if (num_bes == 2) + /* This sound peripheral is split between left and right BE */ + link_props->tdm_mask = be_index ? right_mask : left_mask; + else + /* One BE covers all of the peripheral */ + link_props->tdm_mask = left_mask | right_mask; + + /* Steal platform OF reference for use in FE links later */ + platform = link->cpus->of_node; + + link++; link_props++; + } + + of_node_put(codec); + of_node_put(cpu); + cpu = codec = NULL; + } + + for (i = 0; i < ARRAY_SIZE(macaudio_fe_links); i++) + card->dai_link[i].platforms->of_node = platform; + + return 0; + +err_free: + of_node_put(codec); + of_node_put(cpu); + of_node_put(np); + + if (!card->dai_link) + return ret; + + for (i = 0; i < num_links; i++) { + /* + * TODO: If we don't go through this path are the references + * freed inside ASoC? + */ + snd_soc_of_put_dai_link_codecs(&card->dai_link[i]); + snd_soc_of_put_dai_link_cpus(&card->dai_link[i]); + } + + return ret; +} + +static int macaudio_get_runtime_bclk_ratio(struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream); + struct macaudio_snd_data *ma = snd_soc_card_get_drvdata(rtd->card); + struct snd_soc_dpcm *dpcm; + + /* + * If this is a FE, look it up in link_props directly. + * If this is a BE, look it up in the respective FE. + */ + if (!rtd->dai_link->no_pcm) + return ma->link_props[rtd->dai_link->id].bclk_ratio; + + for_each_dpcm_fe(rtd, substream->stream, dpcm) { + int fe_id = dpcm->fe->dai_link->id; + + return ma->link_props[fe_id].bclk_ratio; + } + + return 0; +} + +static int macaudio_dpcm_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream); + struct snd_soc_dai *cpu_dai = snd_soc_rtd_to_cpu(rtd, 0); + int bclk_ratio = macaudio_get_runtime_bclk_ratio(substream); + int i; + + if (bclk_ratio) { + struct snd_soc_dai *dai; + int mclk = params_rate(params) * bclk_ratio; + + for_each_rtd_codec_dais(rtd, i, dai) { + snd_soc_dai_set_sysclk(dai, 0, mclk, SND_SOC_CLOCK_IN); + snd_soc_dai_set_bclk_ratio(dai, bclk_ratio); + } + + snd_soc_dai_set_sysclk(cpu_dai, 0, mclk, SND_SOC_CLOCK_OUT); + snd_soc_dai_set_bclk_ratio(cpu_dai, bclk_ratio); + } + + return 0; +} + +static void macaudio_dpcm_shutdown(struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream); + struct snd_soc_dai *cpu_dai = snd_soc_rtd_to_cpu(rtd, 0); + struct snd_soc_dai *dai; + int bclk_ratio = macaudio_get_runtime_bclk_ratio(substream); + int i; + + if (bclk_ratio) { + for_each_rtd_codec_dais(rtd, i, dai) + snd_soc_dai_set_sysclk(dai, 0, 0, SND_SOC_CLOCK_IN); + + snd_soc_dai_set_sysclk(cpu_dai, 0, 0, SND_SOC_CLOCK_OUT); + } +} + +static const struct snd_soc_ops macaudio_fe_ops = { + .shutdown = macaudio_dpcm_shutdown, + .hw_params = macaudio_dpcm_hw_params, +}; + +static const struct snd_soc_ops macaudio_be_ops = { + .shutdown = macaudio_dpcm_shutdown, + .hw_params = macaudio_dpcm_hw_params, +}; + +static int macaudio_be_assign_tdm(struct snd_soc_pcm_runtime *rtd) +{ + struct snd_soc_card *card = rtd->card; + struct macaudio_snd_data *ma = snd_soc_card_get_drvdata(card); + struct macaudio_link_props *props = &ma->link_props[rtd->dai_link->id]; + struct snd_soc_dai *dai; + unsigned int mask; + int nslots, ret, i; + + if (!props->tdm_mask) + return 0; + + mask = props->tdm_mask; + nslots = __fls(mask) + 1; + + if (rtd->dai_link->num_codecs == 1) { + ret = snd_soc_dai_set_tdm_slot(snd_soc_rtd_to_codec(rtd, 0), mask, + 0, nslots, MACAUDIO_SLOTWIDTH); + + /* + * Headphones get a pass on -ENOTSUPP (see the comment + * around bclk_ratio value for primary FE). + */ + if (ret == -ENOTSUPP && props->is_headphones) + return 0; + + return ret; + } + + for_each_rtd_codec_dais(rtd, i, dai) { + int slot = __ffs(mask); + + mask &= ~(1 << slot); + ret = snd_soc_dai_set_tdm_slot(dai, 1 << slot, 0, nslots, + MACAUDIO_SLOTWIDTH); + if (ret) + return ret; + } + + return 0; +} + +static int macaudio_be_init(struct snd_soc_pcm_runtime *rtd) +{ + struct snd_soc_card *card = rtd->card; + struct macaudio_snd_data *ma = snd_soc_card_get_drvdata(card); + struct macaudio_link_props *props = &ma->link_props[rtd->dai_link->id]; + struct snd_soc_dai *dai; + int i, ret; + + ret = macaudio_be_assign_tdm(rtd); + if (ret < 0) + return ret; + + if (props->is_headphones) { + for_each_rtd_codec_dais(rtd, i, dai) + snd_soc_component_set_jack(dai->component, &ma->jack, NULL); + } + + return 0; +} + +static void macaudio_be_exit(struct snd_soc_pcm_runtime *rtd) +{ + struct snd_soc_card *card = rtd->card; + struct macaudio_snd_data *ma = snd_soc_card_get_drvdata(card); + struct macaudio_link_props *props = &ma->link_props[rtd->dai_link->id]; + struct snd_soc_dai *dai; + int i; + + if (props->is_headphones) { + for_each_rtd_codec_dais(rtd, i, dai) + snd_soc_component_set_jack(dai->component, NULL, NULL); + } +} + +static int macaudio_fe_init(struct snd_soc_pcm_runtime *rtd) +{ + struct snd_soc_card *card = rtd->card; + struct macaudio_snd_data *ma = snd_soc_card_get_drvdata(card); + struct macaudio_link_props *props = &ma->link_props[rtd->dai_link->id]; + int nslots = props->bclk_ratio / MACAUDIO_SLOTWIDTH; + + return snd_soc_dai_set_tdm_slot(snd_soc_rtd_to_cpu(rtd, 0), (1 << nslots) - 1, + (1 << nslots) - 1, nslots, MACAUDIO_SLOTWIDTH); +} + +static struct snd_soc_jack_pin macaudio_jack_pins[] = { + { + .pin = "Headphone", + .mask = SND_JACK_HEADPHONE, + }, + { + .pin = "Headset Mic", + .mask = SND_JACK_MICROPHONE, + }, + { + .pin = "Speaker", + .mask = SND_JACK_HEADPHONE, + .invert = 1, + }, +}; + +static int macaudio_probe(struct snd_soc_card *card) +{ + struct macaudio_snd_data *ma = snd_soc_card_get_drvdata(card); + int ret; + + dev_dbg(card->dev, "%s!\n", __func__); + + ret = snd_soc_card_jack_new_pins(card, "Headphone Jack", + SND_JACK_HEADSET | SND_JACK_HEADPHONE, + &ma->jack, macaudio_jack_pins, + ARRAY_SIZE(macaudio_jack_pins)); + if (ret < 0) { + dev_err(card->dev, "jack creation failed: %d\n", ret); + return ret; + } + + return ret; +} + +static int macaudio_add_backend_dai_route(struct snd_soc_card *card, struct snd_soc_dai *dai, + bool is_speakers) +{ + struct snd_soc_dapm_route routes[2]; + struct snd_soc_dapm_route *r; + int nroutes = 0; + int ret; + + memset(routes, 0, sizeof(routes)); + + dev_dbg(card->dev, "adding routes for '%s'\n", dai->name); + + r = &routes[nroutes++]; + if (is_speakers) + r->source = "Speaker Playback"; + else + r->source = "Headphone Playback"; + r->sink = dai->stream[SNDRV_PCM_STREAM_PLAYBACK].widget->name; + + /* If headphone jack, add capture path */ + if (!is_speakers) { + r = &routes[nroutes++]; + r->source = dai->stream[SNDRV_PCM_STREAM_CAPTURE].widget->name; + r->sink = "Headphone Capture"; + } + + ret = snd_soc_dapm_add_routes(&card->dapm, routes, nroutes); + if (ret) + dev_err(card->dev, "failed adding dynamic DAPM routes for %s\n", + dai->name); + return ret; +} + +static int macaudio_add_pin_routes(struct snd_soc_card *card, struct snd_soc_component *component, + bool is_speakers) +{ + struct snd_soc_dapm_route routes[1]; + struct snd_soc_dapm_route *r; + int nroutes = 0; + char buf[32]; + int ret; + + memset(routes, 0, sizeof(routes)); + + /* Connect the far ends of CODECs to pins */ + if (is_speakers) { + r = &routes[nroutes++]; + r->source = "OUT"; + if (component->name_prefix) { + snprintf(buf, sizeof(buf) - 1, "%s OUT", component->name_prefix); + r->source = buf; + } + r->sink = "Speaker Pin Demux"; + } else { + r = &routes[nroutes++]; + r->source = "Jack HP"; + r->sink = "Headphone"; + } + + + ret = snd_soc_dapm_add_routes(&card->dapm, routes, nroutes); + if (ret) + dev_err(card->dev, "failed adding dynamic DAPM routes for %s\n", + component->name); + return ret; +} + +static int macaudio_late_probe(struct snd_soc_card *card) +{ + struct macaudio_snd_data *ma = snd_soc_card_get_drvdata(card); + struct snd_soc_pcm_runtime *rtd; + struct snd_soc_dai *dai; + int ret, i; + + /* Add the dynamic DAPM routes */ + for_each_card_rtds(card, rtd) { + struct macaudio_link_props *props = &ma->link_props[rtd->dai_link->id]; + + if (!rtd->dai_link->no_pcm) + continue; + + for_each_rtd_cpu_dais(rtd, i, dai) { + ret = macaudio_add_backend_dai_route(card, dai, props->is_speakers); + + if (ret) + return ret; + } + + for_each_rtd_codec_dais(rtd, i, dai) { + ret = macaudio_add_pin_routes(card, dai->component, + props->is_speakers); + + if (ret) + return ret; + } + } + + return 0; +} + +#define CHECK(call, pattern, value) \ + { \ + int ret = call(card, pattern, value); \ + if (ret < 1 && !void_warranty) { \ + dev_err(card->dev, "%s on '%s': %d\n", #call, pattern, ret); \ + return ret; \ + } \ + dev_dbg(card->dev, "%s on '%s': %d hits\n", #call, pattern, ret); \ + } + + +static int macaudio_j274_fixup_controls(struct snd_soc_card *card) +{ + struct macaudio_snd_data *ma = snd_soc_card_get_drvdata(card); + + if (ma->has_speakers) { + CHECK(snd_soc_limit_volume, "* Amp Gain Volume", 14); // 20 set by macOS, this is 3 dB below + } + + return 0; +} + +static int macaudio_j314_fixup_controls(struct snd_soc_card *card) +{ + struct macaudio_snd_data *ma = snd_soc_card_get_drvdata(card); + + if (ma->has_speakers) { + CHECK(snd_soc_set_enum_kctl, "* ASI1 Sel", "Left"); + CHECK(snd_soc_deactivate_kctl, "* ASI1 Sel", 0); + CHECK(snd_soc_limit_volume, "* Amp Gain Volume", 9); // 15 set by macOS, this is 3 dB below + CHECK(snd_soc_set_enum_kctl, "* Tweeter HPF Corner Freq", "800 Hz"); + CHECK(snd_soc_deactivate_kctl, "* Tweeter HPF Corner Freq", 0); + + /* + * The speaker amps suffer from spurious overcurrent + * events on their unmute, so enable autoretry. + */ + CHECK(snd_soc_set_enum_kctl, "* OCE Handling", "Retry"); + CHECK(snd_soc_deactivate_kctl, "* OCE Handling", 0); + + /* + * Since we don't set the right slots yet to avoid + * driver conflict on the I2S bus sending ISENSE/VSENSE + * samples from the codecs back to us, disable the + * controls. + */ + CHECK(snd_soc_deactivate_kctl, "* VSENSE Switch", 0); + CHECK(snd_soc_deactivate_kctl, "* ISENSE Switch", 0); + } + + return 0; +} + +static int macaudio_fallback_fixup_controls(struct snd_soc_card *card) +{ + struct macaudio_snd_data *ma = snd_soc_card_get_drvdata(card); + + if (ma->has_speakers && !void_warranty) { + dev_err(card->dev, "driver can't assure safety on this model, refusing probe\n"); + return -EINVAL; + } + + return 0; +} + +#undef CHECK + +static const char * const macaudio_spk_mux_texts[] = { + "Primary", + "Secondary" +}; + +SOC_ENUM_SINGLE_VIRT_DECL(macaudio_spk_mux_enum, macaudio_spk_mux_texts); + +static const struct snd_kcontrol_new macaudio_spk_mux = + SOC_DAPM_ENUM("Speaker Playback Mux", macaudio_spk_mux_enum); + +static const char * const macaudio_hp_mux_texts[] = { + "Primary", + "Secondary" +}; + +SOC_ENUM_SINGLE_VIRT_DECL(macaudio_hp_mux_enum, macaudio_hp_mux_texts); + +static const struct snd_kcontrol_new macaudio_hp_mux = + SOC_DAPM_ENUM("Headphones Playback Mux", macaudio_hp_mux_enum); + +static const char *macaudio_spk_demux_texts[] = { + "Inverse Jack", "Static", +}; + +static SOC_ENUM_SINGLE_DECL(macaudio_spk_demux_enum, + SND_SOC_NOPM, 0, macaudio_spk_demux_texts); + +static const struct snd_kcontrol_new macaudio_spk_demux = + SOC_DAPM_ENUM("Speaker Pin Demux", macaudio_spk_demux_enum); + +static const struct snd_soc_dapm_widget macaudio_snd_widgets[] = { + SND_SOC_DAPM_SPK("Speaker", NULL), + SND_SOC_DAPM_SPK("Speaker (Static)", NULL), + SND_SOC_DAPM_HP("Headphone", NULL), + SND_SOC_DAPM_MIC("Headset Mic", NULL), + + SND_SOC_DAPM_MUX("Speaker Playback Mux", SND_SOC_NOPM, 0, 0, &macaudio_spk_mux), + SND_SOC_DAPM_MUX("Headphone Playback Mux", SND_SOC_NOPM, 0, 0, &macaudio_hp_mux), + SND_SOC_DAPM_DEMUX("Speaker Pin Demux", SND_SOC_NOPM, 0, 0, &macaudio_spk_demux), + + SND_SOC_DAPM_AIF_OUT("Speaker Playback", NULL, 0, SND_SOC_NOPM, 0, 0), + SND_SOC_DAPM_AIF_OUT("Headphone Playback", NULL, 0, SND_SOC_NOPM, 0, 0), + + SND_SOC_DAPM_AIF_IN("Headphone Capture", NULL, 0, SND_SOC_NOPM, 0, 0), +}; + +static const struct snd_kcontrol_new macaudio_controls[] = { + SOC_DAPM_PIN_SWITCH("Speaker"), + SOC_DAPM_PIN_SWITCH("Speaker (Static)"), + SOC_DAPM_PIN_SWITCH("Headphone"), + SOC_DAPM_PIN_SWITCH("Headset Mic"), +}; + +static const struct snd_soc_dapm_route macaudio_dapm_routes[] = { + /* Playback paths */ + { "Speaker Playback Mux", "Primary", "PCM0 TX" }, + { "Speaker Playback Mux", "Secondary", "PCM1 TX" }, + { "Speaker Playback", NULL, "Speaker Playback Mux"}, + + { "Headphone Playback Mux", "Primary", "PCM0 TX" }, + { "Headphone Playback Mux", "Secondary", "PCM1 TX" }, + { "Headphone Playback", NULL, "Headphone Playback Mux"}, + /* + * Additional paths (to specific I2S ports) are added dynamically. + */ + + { "Speaker", "Inverse Jack", "Speaker Pin Demux" }, + { "Speaker (Static)", "Static", "Speaker Pin Demux" }, + + /* Capture paths */ + { "PCM0 RX", NULL, "Headphone Capture" }, +}; + +static const struct of_device_id macaudio_snd_device_id[] = { + { .compatible = "apple,j274-macaudio", .data = macaudio_j274_fixup_controls }, + { .compatible = "apple,j314-macaudio", .data = macaudio_j314_fixup_controls }, + { .compatible = "apple,macaudio"}, + { } +}; +MODULE_DEVICE_TABLE(of, macaudio_snd_device_id); + +static int macaudio_snd_platform_probe(struct platform_device *pdev) +{ + struct snd_soc_card *card; + struct macaudio_snd_data *data; + struct device *dev = &pdev->dev; + struct snd_soc_dai_link *link; + const struct of_device_id *of_id; + int ret; + int i; + + of_id = of_match_device(macaudio_snd_device_id, dev); + if (!of_id) + return -EINVAL; + + data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL); + if (!data) + return -ENOMEM; + card = &data->card; + snd_soc_card_set_drvdata(card, data); + + card->owner = THIS_MODULE; + card->driver_name = DRIVER_NAME; + card->dev = dev; + card->dapm_widgets = macaudio_snd_widgets; + card->num_dapm_widgets = ARRAY_SIZE(macaudio_snd_widgets); + card->dapm_routes = macaudio_dapm_routes; + card->num_dapm_routes = ARRAY_SIZE(macaudio_dapm_routes); + card->controls = macaudio_controls; + card->num_controls = ARRAY_SIZE(macaudio_controls); + card->probe = macaudio_probe; + card->late_probe = macaudio_late_probe; + card->component_chaining = true; + card->fully_routed = true; + + if (of_id->data) + card->fixup_controls = of_id->data; + else + card->fixup_controls = macaudio_fallback_fixup_controls; + + ret = macaudio_parse_of(data); + if (ret) + return dev_err_probe(&pdev->dev, ret, "failed OF parsing\n"); + + for_each_card_prelinks(card, i, link) { + if (link->no_pcm) { + link->ops = &macaudio_be_ops; + link->init = macaudio_be_init; + link->exit = macaudio_be_exit; + } else { + link->ops = &macaudio_fe_ops; + link->init = macaudio_fe_init; + } + } + + return devm_snd_soc_register_card(dev, card); +} + +static struct platform_driver macaudio_snd_driver = { + .probe = macaudio_snd_platform_probe, + .driver = { + .name = DRIVER_NAME, + .of_match_table = macaudio_snd_device_id, + .pm = &snd_soc_pm_ops, + }, +}; +module_platform_driver(macaudio_snd_driver); + +MODULE_AUTHOR("Martin Povišer "); +MODULE_DESCRIPTION("Apple Silicon Macs machine-level sound driver"); +MODULE_LICENSE("GPL"); From 0b2c67f5948009ccf834f363a3c45ab3b19632ba Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20Povi=C5=A1er?= Date: Wed, 3 Aug 2022 17:25:43 +0200 Subject: [PATCH 0089/3902] ASoC: cs42l42: Fix typo MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Martin Povišer --- sound/soc/codecs/cs42l42.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sound/soc/codecs/cs42l42.c b/sound/soc/codecs/cs42l42.c index 78bb093fa0cc0d..10cb1f0b83e46b 100644 --- a/sound/soc/codecs/cs42l42.c +++ b/sound/soc/codecs/cs42l42.c @@ -1676,7 +1676,7 @@ irqreturn_t cs42l42_irq_thread(int irq, void *data) return IRQ_NONE; } - /* Read sticky registers to clear interurpt */ + /* Read sticky registers to clear interrupt */ for (i = 0; i < ARRAY_SIZE(stickies); i++) { regmap_read(cs42l42->regmap, irq_params_table[i].status_addr, &(stickies[i])); From 9af44dbc1365d19ceeef690e90c0545cdc03fb91 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20Povi=C5=A1er?= Date: Tue, 6 Sep 2022 14:51:29 +0200 Subject: [PATCH 0090/3902] ASoC: cs42l42: Do not advertise sample bit symmetry MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Martin Povišer --- sound/soc/codecs/cs42l42.c | 1 - 1 file changed, 1 deletion(-) diff --git a/sound/soc/codecs/cs42l42.c b/sound/soc/codecs/cs42l42.c index 10cb1f0b83e46b..7251aecb128b68 100644 --- a/sound/soc/codecs/cs42l42.c +++ b/sound/soc/codecs/cs42l42.c @@ -1148,7 +1148,6 @@ struct snd_soc_dai_driver cs42l42_dai = { .formats = CS42L42_FORMATS, }, .symmetric_rate = 1, - .symmetric_sample_bits = 1, .ops = &cs42l42_ops, }; EXPORT_SYMBOL_NS_GPL(cs42l42_dai, "SND_SOC_CS42L42_CORE"); From 74536a33a4ee6ab0780bbf534e5f7325e40cb9c2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20Povi=C5=A1er?= Date: Sun, 21 Aug 2022 02:40:29 +0200 Subject: [PATCH 0091/3902] ASoC: macaudio: Fix headset routes MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Martin Povišer --- sound/soc/apple/macaudio.c | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/sound/soc/apple/macaudio.c b/sound/soc/apple/macaudio.c index 1e6007bd5336bf..d150676cacd0a1 100644 --- a/sound/soc/apple/macaudio.c +++ b/sound/soc/apple/macaudio.c @@ -626,7 +626,7 @@ static int macaudio_add_backend_dai_route(struct snd_soc_card *card, struct snd_ if (!is_speakers) { r = &routes[nroutes++]; r->source = dai->stream[SNDRV_PCM_STREAM_CAPTURE].widget->name; - r->sink = "Headphone Capture"; + r->sink = "Headset Capture"; } ret = snd_soc_dapm_add_routes(&card->dapm, routes, nroutes); @@ -639,7 +639,7 @@ static int macaudio_add_backend_dai_route(struct snd_soc_card *card, struct snd_ static int macaudio_add_pin_routes(struct snd_soc_card *card, struct snd_soc_component *component, bool is_speakers) { - struct snd_soc_dapm_route routes[1]; + struct snd_soc_dapm_route routes[2]; struct snd_soc_dapm_route *r; int nroutes = 0; char buf[32]; @@ -660,9 +660,11 @@ static int macaudio_add_pin_routes(struct snd_soc_card *card, struct snd_soc_com r = &routes[nroutes++]; r->source = "Jack HP"; r->sink = "Headphone"; + r = &routes[nroutes++]; + r->source = "Headset Mic"; + r->sink = "Jack HS"; } - ret = snd_soc_dapm_add_routes(&card->dapm, routes, nroutes); if (ret) dev_err(card->dev, "failed adding dynamic DAPM routes for %s\n", @@ -813,7 +815,7 @@ static const struct snd_soc_dapm_widget macaudio_snd_widgets[] = { SND_SOC_DAPM_AIF_OUT("Speaker Playback", NULL, 0, SND_SOC_NOPM, 0, 0), SND_SOC_DAPM_AIF_OUT("Headphone Playback", NULL, 0, SND_SOC_NOPM, 0, 0), - SND_SOC_DAPM_AIF_IN("Headphone Capture", NULL, 0, SND_SOC_NOPM, 0, 0), + SND_SOC_DAPM_AIF_IN("Headset Capture", NULL, 0, SND_SOC_NOPM, 0, 0), }; static const struct snd_kcontrol_new macaudio_controls[] = { @@ -840,7 +842,7 @@ static const struct snd_soc_dapm_route macaudio_dapm_routes[] = { { "Speaker (Static)", "Static", "Speaker Pin Demux" }, /* Capture paths */ - { "PCM0 RX", NULL, "Headphone Capture" }, + { "PCM0 RX", NULL, "Headset Capture" }, }; static const struct of_device_id macaudio_snd_device_id[] = { From ccd60d16b46110957731047f0891e997cbed5ecb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20Povi=C5=A1er?= Date: Sun, 21 Aug 2022 02:40:54 +0200 Subject: [PATCH 0092/3902] ASoC: dapm: Export new 'graph.dot' file in debugfs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Martin Povišer --- sound/soc/soc-dapm.c | 139 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 139 insertions(+) diff --git a/sound/soc/soc-dapm.c b/sound/soc/soc-dapm.c index 51fb09d56c5a17..a93071cce13fcd 100644 --- a/sound/soc/soc-dapm.c +++ b/sound/soc/soc-dapm.c @@ -2512,6 +2512,141 @@ static const struct file_operations dapm_bias_fops = { .llseek = default_llseek, }; +static ssize_t dapm_graph_read_file(struct file *file, char __user *user_buf, + size_t count, loff_t *ppos) +{ + struct snd_soc_card *card = file->private_data; + struct snd_soc_dapm_context *dapm; + struct snd_soc_dapm_path *p; + struct snd_soc_dapm_widget *w; + struct snd_soc_pcm_runtime *rtd; + struct snd_soc_dapm_widget *wdone[16]; + struct snd_soc_dai *dai; + int i, num_wdone = 0, cluster = 0; + char *buf; + ssize_t bufsize; + ssize_t ret = 0; + + bufsize = 1024 * card->num_dapm_widgets; + buf = kmalloc(bufsize, GFP_KERNEL); + if (!buf) + return -ENOMEM; + + mutex_lock(&card->dapm_mutex); + +#define bufprintf(...) \ + ret += scnprintf(buf + ret, bufsize - ret, __VA_ARGS__) + + bufprintf("digraph dapm {\n"); + + /* + * Print the user-visible devices of the card. + */ + bufprintf("subgraph cluster_%d {\n", cluster++); + bufprintf("label=\"Devices\";style=filled;fillcolor=gray;\n"); + for_each_card_rtds(card, rtd) { + if (rtd->dai_link->no_pcm) + continue; + + bufprintf("w%pK [label=\"%d: %s\"];\n", rtd, + rtd->pcm->device, rtd->dai_link->name); + } + bufprintf("};\n"); + + /* + * Print the playback/capture widgets of DAIs just next to + * the user-visible devices. Keep the list of already printed + * widgets in 'wdone', so they will be skipped later. + */ + for_each_card_rtds(card, rtd) { + for_each_rtd_cpu_dais(rtd, i, dai) { + if (dai->stream[SNDRV_PCM_STREAM_PLAYBACK].widget) { + w = dai->stream[SNDRV_PCM_STREAM_PLAYBACK].widget; + bufprintf("w%pK [label=\"%s\"];\n", w, w->name); + if (!rtd->dai_link->no_pcm) + bufprintf("w%pK -> w%pK;\n", rtd, w); + if (num_wdone < ARRAY_SIZE(wdone)) { + wdone[num_wdone] = w; + num_wdone++; + } + } + + if (dai->stream[SNDRV_PCM_STREAM_CAPTURE].widget) { + w = dai->stream[SNDRV_PCM_STREAM_CAPTURE].widget; + bufprintf("w%pK [label=\"%s\"];\n", w, w->name); + if (!rtd->dai_link->no_pcm) + bufprintf("w%pK -> w%pK;\n", w, rtd); + if (num_wdone < ARRAY_SIZE(wdone)) { + wdone[num_wdone] = w; + num_wdone++; + } + } + } + } + + for_each_card_dapms(card, dapm) { + const char *prefix = soc_dapm_prefix(dapm); + + if (dapm != &card->dapm) { + bufprintf("subgraph cluster_%d {\n", cluster++); + if (prefix) + bufprintf("label=\"%s\";\n", prefix); + else if (dapm->component) + bufprintf("label=\"%s\";\n", + dapm->component->name); + } + + for_each_card_widgets(dapm->card, w) { + const char *name = w->name; + bool skip = false; + + if (w->dapm != dapm) + continue; + + if (list_empty(&w->edges[0]) && list_empty(&w->edges[1])) + continue; + + for (i = 0; i < num_wdone; i++) + if (wdone[i] == w) + skip = true; + if (skip) + continue; + + if (prefix && strlen(name) > strlen(prefix) + 1) + name += strlen(prefix) + 1; + + bufprintf("w%pK [label=\"%s\"];\n", w, name); + } + + if (dapm != &card->dapm) + bufprintf("}\n"); + } + + list_for_each_entry(p, &card->paths, list) { + if (p->name) + bufprintf("w%pK -> w%pK [label=\"%s\"];\n", + p->source, p->sink, p->name); + else + bufprintf("w%pK -> w%pK;\n", p->source, p->sink); + } + + bufprintf("}\n"); +#undef bufprintf + + mutex_unlock(&card->dapm_mutex); + + ret = simple_read_from_buffer(user_buf, count, ppos, buf, ret); + + kfree(buf); + return ret; +} + +static const struct file_operations dapm_graph_fops = { + .open = simple_open, + .read = dapm_graph_read_file, + .llseek = default_llseek, +}; + void snd_soc_dapm_debugfs_init(struct snd_soc_dapm_context *dapm, struct dentry *parent) { @@ -2522,6 +2657,10 @@ void snd_soc_dapm_debugfs_init(struct snd_soc_dapm_context *dapm, debugfs_create_file("bias_level", 0444, dapm->debugfs_dapm, dapm, &dapm_bias_fops); + + if (dapm == &dapm->card->dapm) + debugfs_create_file("graph.dot", 0444, dapm->debugfs_dapm, + dapm->card, &dapm_graph_fops); } static void dapm_debugfs_add_widget(struct snd_soc_dapm_widget *w) From ae5fa5654780420409f28b667ff631903f8d1ac8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20Povi=C5=A1er?= Date: Tue, 23 Aug 2022 11:36:24 +0200 Subject: [PATCH 0093/3902] ASoC: macaudio: Add j375 fixup_controls MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Martin Povišer --- sound/soc/apple/macaudio.c | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/sound/soc/apple/macaudio.c b/sound/soc/apple/macaudio.c index d150676cacd0a1..82808a3fb6df84 100644 --- a/sound/soc/apple/macaudio.c +++ b/sound/soc/apple/macaudio.c @@ -758,6 +758,17 @@ static int macaudio_j314_fixup_controls(struct snd_soc_card *card) return 0; } +static int macaudio_j375_fixup_controls(struct snd_soc_card *card) +{ + struct macaudio_snd_data *ma = snd_soc_card_get_drvdata(card); + + if (ma->has_speakers) { + CHECK(snd_soc_limit_volume, "* Amp Gain Volume", 14); // 20 set by macOS, this is 3 dB below + } + + return 0; +} + static int macaudio_fallback_fixup_controls(struct snd_soc_card *card) { struct macaudio_snd_data *ma = snd_soc_card_get_drvdata(card); @@ -848,6 +859,7 @@ static const struct snd_soc_dapm_route macaudio_dapm_routes[] = { static const struct of_device_id macaudio_snd_device_id[] = { { .compatible = "apple,j274-macaudio", .data = macaudio_j274_fixup_controls }, { .compatible = "apple,j314-macaudio", .data = macaudio_j314_fixup_controls }, + { .compatible = "apple,j375-macaudio", .data = macaudio_j375_fixup_controls }, { .compatible = "apple,macaudio"}, { } }; From b026e693e3fb9ec35e620caaced413e71f693b0f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20Povi=C5=A1er?= Date: Tue, 30 Aug 2022 10:20:09 +0200 Subject: [PATCH 0094/3902] ASoC: macaudio: Add j493 fixup_controls MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Martin Povišer --- sound/soc/apple/macaudio.c | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/sound/soc/apple/macaudio.c b/sound/soc/apple/macaudio.c index 82808a3fb6df84..543dc0c1134816 100644 --- a/sound/soc/apple/macaudio.c +++ b/sound/soc/apple/macaudio.c @@ -769,6 +769,17 @@ static int macaudio_j375_fixup_controls(struct snd_soc_card *card) return 0; } +static int macaudio_j493_fixup_controls(struct snd_soc_card *card) +{ + struct macaudio_snd_data *ma = snd_soc_card_get_drvdata(card); + + if (ma->has_speakers) { + CHECK(snd_soc_limit_volume, "* Amp Gain Volume", 9); // 15 set by macOS, this is 3 dB below + } + + return 0; +} + static int macaudio_fallback_fixup_controls(struct snd_soc_card *card) { struct macaudio_snd_data *ma = snd_soc_card_get_drvdata(card); @@ -860,6 +871,7 @@ static const struct of_device_id macaudio_snd_device_id[] = { { .compatible = "apple,j274-macaudio", .data = macaudio_j274_fixup_controls }, { .compatible = "apple,j314-macaudio", .data = macaudio_j314_fixup_controls }, { .compatible = "apple,j375-macaudio", .data = macaudio_j375_fixup_controls }, + { .compatible = "apple,j493-macaudio", .data = macaudio_j493_fixup_controls }, { .compatible = "apple,macaudio"}, { } }; From e3d3cf8e935b1f4cbb69536e037cc59b4e7a8cc6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20Povi=C5=A1er?= Date: Sun, 4 Sep 2022 10:29:34 +0200 Subject: [PATCH 0095/3902] ASoC: macaudio: Rename ALSA driver to simple 'macaudio' MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Martin Povišer --- sound/soc/apple/macaudio.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sound/soc/apple/macaudio.c b/sound/soc/apple/macaudio.c index 543dc0c1134816..f5f43002c4a2b8 100644 --- a/sound/soc/apple/macaudio.c +++ b/sound/soc/apple/macaudio.c @@ -898,7 +898,7 @@ static int macaudio_snd_platform_probe(struct platform_device *pdev) snd_soc_card_set_drvdata(card, data); card->owner = THIS_MODULE; - card->driver_name = DRIVER_NAME; + card->driver_name = "macaudio"; card->dev = dev; card->dapm_widgets = macaudio_snd_widgets; card->num_dapm_widgets = ARRAY_SIZE(macaudio_snd_widgets); From 8b7779ca7ddceae19da1d75165e806ec8dfeab9b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20Povi=C5=A1er?= Date: Fri, 2 Sep 2022 19:40:16 +0200 Subject: [PATCH 0096/3902] ASoC: macaudio: Drop the 'inverse jack' speaker stuff MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Martin Povišer --- sound/soc/apple/macaudio.c | 22 +--------------------- 1 file changed, 1 insertion(+), 21 deletions(-) diff --git a/sound/soc/apple/macaudio.c b/sound/soc/apple/macaudio.c index f5f43002c4a2b8..fdc7293f376599 100644 --- a/sound/soc/apple/macaudio.c +++ b/sound/soc/apple/macaudio.c @@ -577,11 +577,6 @@ static struct snd_soc_jack_pin macaudio_jack_pins[] = { .pin = "Headset Mic", .mask = SND_JACK_MICROPHONE, }, - { - .pin = "Speaker", - .mask = SND_JACK_HEADPHONE, - .invert = 1, - }, }; static int macaudio_probe(struct snd_soc_card *card) @@ -655,7 +650,7 @@ static int macaudio_add_pin_routes(struct snd_soc_card *card, struct snd_soc_com snprintf(buf, sizeof(buf) - 1, "%s OUT", component->name_prefix); r->source = buf; } - r->sink = "Speaker Pin Demux"; + r->sink = "Speaker"; } else { r = &routes[nroutes++]; r->source = "Jack HP"; @@ -814,16 +809,6 @@ SOC_ENUM_SINGLE_VIRT_DECL(macaudio_hp_mux_enum, macaudio_hp_mux_texts); static const struct snd_kcontrol_new macaudio_hp_mux = SOC_DAPM_ENUM("Headphones Playback Mux", macaudio_hp_mux_enum); -static const char *macaudio_spk_demux_texts[] = { - "Inverse Jack", "Static", -}; - -static SOC_ENUM_SINGLE_DECL(macaudio_spk_demux_enum, - SND_SOC_NOPM, 0, macaudio_spk_demux_texts); - -static const struct snd_kcontrol_new macaudio_spk_demux = - SOC_DAPM_ENUM("Speaker Pin Demux", macaudio_spk_demux_enum); - static const struct snd_soc_dapm_widget macaudio_snd_widgets[] = { SND_SOC_DAPM_SPK("Speaker", NULL), SND_SOC_DAPM_SPK("Speaker (Static)", NULL), @@ -832,7 +817,6 @@ static const struct snd_soc_dapm_widget macaudio_snd_widgets[] = { SND_SOC_DAPM_MUX("Speaker Playback Mux", SND_SOC_NOPM, 0, 0, &macaudio_spk_mux), SND_SOC_DAPM_MUX("Headphone Playback Mux", SND_SOC_NOPM, 0, 0, &macaudio_hp_mux), - SND_SOC_DAPM_DEMUX("Speaker Pin Demux", SND_SOC_NOPM, 0, 0, &macaudio_spk_demux), SND_SOC_DAPM_AIF_OUT("Speaker Playback", NULL, 0, SND_SOC_NOPM, 0, 0), SND_SOC_DAPM_AIF_OUT("Headphone Playback", NULL, 0, SND_SOC_NOPM, 0, 0), @@ -842,7 +826,6 @@ static const struct snd_soc_dapm_widget macaudio_snd_widgets[] = { static const struct snd_kcontrol_new macaudio_controls[] = { SOC_DAPM_PIN_SWITCH("Speaker"), - SOC_DAPM_PIN_SWITCH("Speaker (Static)"), SOC_DAPM_PIN_SWITCH("Headphone"), SOC_DAPM_PIN_SWITCH("Headset Mic"), }; @@ -860,9 +843,6 @@ static const struct snd_soc_dapm_route macaudio_dapm_routes[] = { * Additional paths (to specific I2S ports) are added dynamically. */ - { "Speaker", "Inverse Jack", "Speaker Pin Demux" }, - { "Speaker (Static)", "Static", "Speaker Pin Demux" }, - /* Capture paths */ { "PCM0 RX", NULL, "Headset Capture" }, }; From 39016220f314b32abfb5c8620f8458848c633702 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20Povi=C5=A1er?= Date: Tue, 6 Sep 2022 15:16:44 +0200 Subject: [PATCH 0097/3902] ASoC: macaudio: s/Freq/Frequency/ in TAS2764 control MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Martin Povišer --- sound/soc/apple/macaudio.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/sound/soc/apple/macaudio.c b/sound/soc/apple/macaudio.c index fdc7293f376599..09310014d5e636 100644 --- a/sound/soc/apple/macaudio.c +++ b/sound/soc/apple/macaudio.c @@ -730,8 +730,8 @@ static int macaudio_j314_fixup_controls(struct snd_soc_card *card) CHECK(snd_soc_set_enum_kctl, "* ASI1 Sel", "Left"); CHECK(snd_soc_deactivate_kctl, "* ASI1 Sel", 0); CHECK(snd_soc_limit_volume, "* Amp Gain Volume", 9); // 15 set by macOS, this is 3 dB below - CHECK(snd_soc_set_enum_kctl, "* Tweeter HPF Corner Freq", "800 Hz"); - CHECK(snd_soc_deactivate_kctl, "* Tweeter HPF Corner Freq", 0); + CHECK(snd_soc_set_enum_kctl, "* Tweeter HPF Corner Frequency", "800 Hz"); + CHECK(snd_soc_deactivate_kctl, "* Tweeter HPF Corner Frequency", 0); /* * The speaker amps suffer from spurious overcurrent From d1b91423dfd5c1357788f04cb4dfe8fa99a01418 Mon Sep 17 00:00:00 2001 From: Hector Martin Date: Tue, 13 Sep 2022 19:56:12 +0900 Subject: [PATCH 0098/3902] ASoC: macaudio: s/void_warranty/please_blow_up_my_speakers/ We have no idea whether any of this voids warranties, but what it does do is blow up your speakers, so let's be explicit about what users are signing up for. Signed-off-by: Hector Martin --- sound/soc/apple/macaudio.c | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/sound/soc/apple/macaudio.c b/sound/soc/apple/macaudio.c index 09310014d5e636..4806854ee0656f 100644 --- a/sound/soc/apple/macaudio.c +++ b/sound/soc/apple/macaudio.c @@ -67,9 +67,9 @@ struct macaudio_snd_data { struct snd_pcm_hw_constraint_list speaker_nchans_list; }; -static bool void_warranty; -module_param(void_warranty, bool, 0644); -MODULE_PARM_DESC(void_warranty, "Do not bail if safety is not assured"); +static bool please_blow_up_my_speakers; +module_param(please_blow_up_my_speakers, bool, 0644); +MODULE_PARM_DESC(please_blow_up_my_speakers, "Allow unsafe or untested operating configurations"); SND_SOC_DAILINK_DEFS(primary, DAILINK_COMP_ARRAY(COMP_CPU("mca-pcm-0")), // CPU @@ -703,7 +703,7 @@ static int macaudio_late_probe(struct snd_soc_card *card) #define CHECK(call, pattern, value) \ { \ int ret = call(card, pattern, value); \ - if (ret < 1 && !void_warranty) { \ + if (ret < 1 && !please_blow_up_my_speakers) { \ dev_err(card->dev, "%s on '%s': %d\n", #call, pattern, ret); \ return ret; \ } \ @@ -779,7 +779,7 @@ static int macaudio_fallback_fixup_controls(struct snd_soc_card *card) { struct macaudio_snd_data *ma = snd_soc_card_get_drvdata(card); - if (ma->has_speakers && !void_warranty) { + if (ma->has_speakers && !please_blow_up_my_speakers) { dev_err(card->dev, "driver can't assure safety on this model, refusing probe\n"); return -EINVAL; } From d5d665af72c544f8e09eaabd39f36ba2977c1443 Mon Sep 17 00:00:00 2001 From: Hector Martin Date: Tue, 13 Sep 2022 19:56:47 +0900 Subject: [PATCH 0099/3902] ASoC: macaudio: Gate off experimental platforms We know at least some machines can have their speakers blown, even with these limits, so let's play it safe for now and require that users both enable stuff in the DT *and* pass this flag. Signed-off-by: Hector Martin --- sound/soc/apple/macaudio.c | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/sound/soc/apple/macaudio.c b/sound/soc/apple/macaudio.c index 4806854ee0656f..2d4b21b95309e6 100644 --- a/sound/soc/apple/macaudio.c +++ b/sound/soc/apple/macaudio.c @@ -727,6 +727,11 @@ static int macaudio_j314_fixup_controls(struct snd_soc_card *card) struct macaudio_snd_data *ma = snd_soc_card_get_drvdata(card); if (ma->has_speakers) { + if (!please_blow_up_my_speakers) { + dev_err(card->dev, "driver can't assure safety on this model, refusing probe\n"); + return -EINVAL; + } + CHECK(snd_soc_set_enum_kctl, "* ASI1 Sel", "Left"); CHECK(snd_soc_deactivate_kctl, "* ASI1 Sel", 0); CHECK(snd_soc_limit_volume, "* Amp Gain Volume", 9); // 15 set by macOS, this is 3 dB below @@ -758,6 +763,11 @@ static int macaudio_j375_fixup_controls(struct snd_soc_card *card) struct macaudio_snd_data *ma = snd_soc_card_get_drvdata(card); if (ma->has_speakers) { + if (!please_blow_up_my_speakers) { + dev_err(card->dev, "driver can't assure safety on this model, refusing probe\n"); + return -EINVAL; + } + CHECK(snd_soc_limit_volume, "* Amp Gain Volume", 14); // 20 set by macOS, this is 3 dB below } @@ -769,6 +779,11 @@ static int macaudio_j493_fixup_controls(struct snd_soc_card *card) struct macaudio_snd_data *ma = snd_soc_card_get_drvdata(card); if (ma->has_speakers) { + if (!please_blow_up_my_speakers) { + dev_err(card->dev, "driver can't assure safety on this model, refusing probe\n"); + return -EINVAL; + } + CHECK(snd_soc_limit_volume, "* Amp Gain Volume", 9); // 15 set by macOS, this is 3 dB below } From 8002ace3d200ada7e8302ae87018d10120f6054b Mon Sep 17 00:00:00 2001 From: Hector Martin Date: Tue, 13 Sep 2022 19:58:17 +0900 Subject: [PATCH 0100/3902] ASoC: macaudio: Alias f413 fixups to j314 This works as far as following the same intent as j314, but we *know* these limits are not sufficient, so this one really needs the module parameter gate. Signed-off-by: Hector Martin --- sound/soc/apple/macaudio.c | 1 + 1 file changed, 1 insertion(+) diff --git a/sound/soc/apple/macaudio.c b/sound/soc/apple/macaudio.c index 2d4b21b95309e6..0dd1253ac4f68b 100644 --- a/sound/soc/apple/macaudio.c +++ b/sound/soc/apple/macaudio.c @@ -866,6 +866,7 @@ static const struct of_device_id macaudio_snd_device_id[] = { { .compatible = "apple,j274-macaudio", .data = macaudio_j274_fixup_controls }, { .compatible = "apple,j314-macaudio", .data = macaudio_j314_fixup_controls }, { .compatible = "apple,j375-macaudio", .data = macaudio_j375_fixup_controls }, + { .compatible = "apple,j413-macaudio", .data = macaudio_j314_fixup_controls }, { .compatible = "apple,j493-macaudio", .data = macaudio_j493_fixup_controls }, { .compatible = "apple,macaudio"}, { } From afebc1c9a3a1e241f0da7d9d846c5defe8c16868 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20Povi=C5=A1er?= Date: Mon, 17 Oct 2022 12:16:20 +0200 Subject: [PATCH 0101/3902] ASoC: macaudio: Improve message on opening of unrouted PCM devices MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Martin Povišer --- sound/soc/apple/macaudio.c | 25 ++++++++++++++++++++++++- 1 file changed, 24 insertions(+), 1 deletion(-) diff --git a/sound/soc/apple/macaudio.c b/sound/soc/apple/macaudio.c index 0dd1253ac4f68b..3ccfacefbf7e85 100644 --- a/sound/soc/apple/macaudio.c +++ b/sound/soc/apple/macaudio.c @@ -455,6 +455,29 @@ static int macaudio_dpcm_hw_params(struct snd_pcm_substream *substream, return 0; } +static int macaudio_fe_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream); + struct snd_soc_pcm_runtime *be; + struct snd_soc_dpcm *dpcm; + + be = NULL; + for_each_dpcm_be(rtd, substream->stream, dpcm) { + be = dpcm->be; + break; + } + + if (!be) { + dev_err(rtd->dev, "opening PCM device '%s' with no audio route configured (bad settings applied to the sound card)\n", + rtd->dai_link->name); + return -EINVAL; + } + + return macaudio_dpcm_hw_params(substream, params); +} + + static void macaudio_dpcm_shutdown(struct snd_pcm_substream *substream) { struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream); @@ -473,7 +496,7 @@ static void macaudio_dpcm_shutdown(struct snd_pcm_substream *substream) static const struct snd_soc_ops macaudio_fe_ops = { .shutdown = macaudio_dpcm_shutdown, - .hw_params = macaudio_dpcm_hw_params, + .hw_params = macaudio_fe_hw_params, }; static const struct snd_soc_ops macaudio_be_ops = { From a1baf133d1c113a6de21b5327ffd20503a52164d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20Povi=C5=A1er?= Date: Thu, 27 Oct 2022 11:09:19 +0200 Subject: [PATCH 0102/3902] ASoC: macaudio: Add initial j313 fixup_controls MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Martin Povišer --- sound/soc/apple/macaudio.c | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/sound/soc/apple/macaudio.c b/sound/soc/apple/macaudio.c index 3ccfacefbf7e85..a3da4ec0dae6a0 100644 --- a/sound/soc/apple/macaudio.c +++ b/sound/soc/apple/macaudio.c @@ -745,6 +745,36 @@ static int macaudio_j274_fixup_controls(struct snd_soc_card *card) return 0; } +static int macaudio_j313_fixup_controls(struct snd_soc_card *card) { + struct macaudio_snd_data *ma = snd_soc_card_get_drvdata(card); + + if (ma->has_speakers) { + if (!please_blow_up_my_speakers) { + dev_err(card->dev, "driver can't assure safety on this model, refusing probe\n"); + return -EINVAL; + } + + CHECK(snd_soc_set_enum_kctl, "* ASI1 Sel", "Left"); + CHECK(snd_soc_deactivate_kctl, "* ASI1 Sel", 0); + + /* !!! This is copied from j274, not obtained by looking at + * what macOS sets. + */ + CHECK(snd_soc_limit_volume, "* Amp Gain Volume", 14); + + /* + * Since we don't set the right slots yet to avoid + * driver conflict on the I2S bus sending ISENSE/VSENSE + * samples from the codecs back to us, disable the + * controls. + */ + CHECK(snd_soc_deactivate_kctl, "* VSENSE Switch", 0); + CHECK(snd_soc_deactivate_kctl, "* ISENSE Switch", 0); + } + + return 0; +} + static int macaudio_j314_fixup_controls(struct snd_soc_card *card) { struct macaudio_snd_data *ma = snd_soc_card_get_drvdata(card); @@ -887,6 +917,7 @@ static const struct snd_soc_dapm_route macaudio_dapm_routes[] = { static const struct of_device_id macaudio_snd_device_id[] = { { .compatible = "apple,j274-macaudio", .data = macaudio_j274_fixup_controls }, + { .compatible = "apple,j313-macaudio", .data = macaudio_j313_fixup_controls }, { .compatible = "apple,j314-macaudio", .data = macaudio_j314_fixup_controls }, { .compatible = "apple,j375-macaudio", .data = macaudio_j375_fixup_controls }, { .compatible = "apple,j413-macaudio", .data = macaudio_j314_fixup_controls }, From 5032aab54fcd28aad5470b0eabc2e52cdf031a31 Mon Sep 17 00:00:00 2001 From: James Calligeros Date: Mon, 24 Oct 2022 21:17:31 +1000 Subject: [PATCH 0103/3902] ASoC: macaudio: constrain frontend channel counts In order to support the wide range of audio arrangements possible on this platform in a generic way, it is necessary for the frontend PCMs to be populated with enough TDM slots to cover all intended use cases. Userspace therefore attempts to open "phantom" channels when a frontend has more channels than its associated backend, which results in garbled audio samples and dropped frames. We must therefore dynamically constrain the frontends when they are started to ensure that userspace can never open more channels than are present on the hardware being represented by the frontend in question. Signed-off-by: James Calligeros --- sound/soc/apple/macaudio.c | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/sound/soc/apple/macaudio.c b/sound/soc/apple/macaudio.c index a3da4ec0dae6a0..e24006ca79c8fb 100644 --- a/sound/soc/apple/macaudio.c +++ b/sound/soc/apple/macaudio.c @@ -52,6 +52,7 @@ struct macaudio_snd_data { int jack_plugin_state; bool has_speakers; + unsigned int max_channels; struct macaudio_link_props { /* frontend props */ @@ -345,6 +346,10 @@ static int macaudio_parse_of(struct macaudio_snd_data *ma) ncodecs_per_cpu = num_codecs / num_bes; nchannels = num_codecs * (speakers ? 1 : 2); + /* Save the max number of channels on the platform */ + if (nchannels > ma->max_channels) + ma->max_channels = nchannels; + /* * If there is a single speaker, assign two channels to it, because * it can do downmix. @@ -455,6 +460,25 @@ static int macaudio_dpcm_hw_params(struct snd_pcm_substream *substream, return 0; } +static int macaudio_fe_startup(struct snd_pcm_substream *substream) +{ + + struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream); + struct macaudio_snd_data *ma = snd_soc_card_get_drvdata(rtd->card); + int ret; + + /* The FEs must never have more channels than the hardware */ + ret = snd_pcm_hw_constraint_minmax(substream->runtime, + SNDRV_PCM_HW_PARAM_CHANNELS, 0, ma->max_channels); + + if (ret < 0) { + dev_err(rtd->dev, "Failed to constrain FE %d! %d", rtd->dai_link->id, ret); + return ret; + } + + return 0; +} + static int macaudio_fe_hw_params(struct snd_pcm_substream *substream, struct snd_pcm_hw_params *params) { @@ -495,6 +519,7 @@ static void macaudio_dpcm_shutdown(struct snd_pcm_substream *substream) } static const struct snd_soc_ops macaudio_fe_ops = { + .startup = macaudio_fe_startup, .shutdown = macaudio_dpcm_shutdown, .hw_params = macaudio_fe_hw_params, }; From 71b36eb3d5b9d47775bb7454b3a0dd22bd7cdaa5 Mon Sep 17 00:00:00 2001 From: Hector Martin Date: Sun, 16 Apr 2023 19:27:40 +0900 Subject: [PATCH 0104/3902] ASoC: cs42l42: Set a faster digital ramp-up rate With the default ramp-up rate, there is a noticeable fade-in when streams start. This can be undesirable with aggressive muting for power saving, since the beginning of the stream is lost. Lower the digital output ramp-up time from 8 samples per period to 2 samples per period. This still leaves some fade-in to avoid pops, but it is a lot less noticeable and no longer feels like the stream is fading in. Signed-off-by: Hector Martin --- include/sound/cs42l42.h | 4 ++++ sound/soc/codecs/cs42l42.c | 10 ++++++++++ 2 files changed, 14 insertions(+) diff --git a/include/sound/cs42l42.h b/include/sound/cs42l42.h index 1bd8eee54f6665..b3657965d49109 100644 --- a/include/sound/cs42l42.h +++ b/include/sound/cs42l42.h @@ -62,6 +62,10 @@ #define CS42L42_INTERNAL_FS_MASK (1 << CS42L42_INTERNAL_FS_SHIFT) #define CS42L42_SFTRAMP_RATE (CS42L42_PAGE_10 + 0x0A) +#define CS42L42_SFTRAMP_ASR_RATE_MASK GENMASK(7, 4) +#define CS42L42_SFTRAMP_ASR_RATE_SHIFT 4 +#define CS42L42_SFTRAMP_DSR_RATE_MASK GENMASK(3, 0) +#define CS42L42_SFTRAMP_DSR_RATE_SHIFT 0 #define CS42L42_SLOW_START_ENABLE (CS42L42_PAGE_10 + 0x0B) #define CS42L42_SLOW_START_EN_MASK GENMASK(6, 4) #define CS42L42_SLOW_START_EN_SHIFT 4 diff --git a/sound/soc/codecs/cs42l42.c b/sound/soc/codecs/cs42l42.c index 7251aecb128b68..8502a1fbdc52d2 100644 --- a/sound/soc/codecs/cs42l42.c +++ b/sound/soc/codecs/cs42l42.c @@ -2418,6 +2418,16 @@ int cs42l42_init(struct cs42l42_private *cs42l42) (1 << CS42L42_ADC_PDN_SHIFT) | (0 << CS42L42_PDN_ALL_SHIFT)); + /* + * Configure a faster digital ramp time, to avoid an audible + * fade-in when streams start. + */ + regmap_update_bits(cs42l42->regmap, CS42L42_SFTRAMP_RATE, + CS42L42_SFTRAMP_ASR_RATE_MASK | + CS42L42_SFTRAMP_DSR_RATE_MASK, + (10 << CS42L42_SFTRAMP_ASR_RATE_SHIFT) | + (1 << CS42L42_SFTRAMP_DSR_RATE_SHIFT)); + ret = cs42l42_handle_device_data(cs42l42->dev, cs42l42); if (ret != 0) goto err_shutdown; From 84ec4febba77a05228d88ecf3e196de0fda07d87 Mon Sep 17 00:00:00 2001 From: Hector Martin Date: Sun, 16 Apr 2023 18:53:40 +0900 Subject: [PATCH 0105/3902] ASoC: apple: mca: Move clock shutdown to be shutdown Codecs are set to mute after hw_free, so yanking the clock out from under them in hw_free leads to breakage. Move the clock shutdown to the shutdown op, which is late enough. Signed-off-by: Hector Martin --- sound/soc/apple/mca.c | 48 ++++++++++++++++++------------------------- 1 file changed, 20 insertions(+), 28 deletions(-) diff --git a/sound/soc/apple/mca.c b/sound/soc/apple/mca.c index c4dcb2b545912c..202cd6dd6365a5 100644 --- a/sound/soc/apple/mca.c +++ b/sound/soc/apple/mca.c @@ -355,33 +355,6 @@ static int mca_be_prepare(struct snd_pcm_substream *substream, return 0; } -static int mca_be_hw_free(struct snd_pcm_substream *substream, - struct snd_soc_dai *dai) -{ - struct mca_cluster *cl = mca_dai_to_cluster(dai); - struct mca_data *mca = cl->host; - struct mca_cluster *fe_cl; - - if (cl->port_driver < 0) - return -EINVAL; - - /* - * We are operating on a foreign cluster here, but since we - * belong to the same PCM, accesses should have been - * synchronized at ASoC level. - */ - fe_cl = &mca->clusters[cl->port_driver]; - if (!mca_fe_clocks_in_use(fe_cl)) - return 0; /* Nothing to do */ - - cl->clocks_in_use[substream->stream] = false; - - if (!mca_fe_clocks_in_use(fe_cl)) - mca_fe_disable_clocks(fe_cl); - - return 0; -} - static unsigned int mca_crop_mask(unsigned int mask, int nchans) { while (hweight32(mask) > nchans) @@ -779,6 +752,26 @@ static void mca_be_shutdown(struct snd_pcm_substream *substream, struct mca_cluster *cl = mca_dai_to_cluster(dai); struct mca_data *mca = cl->host; + if (cl->clocks_in_use[substream->stream] && + !WARN_ON(cl->port_driver < 0)) { + struct mca_cluster *fe_cl = &mca->clusters[cl->port_driver]; + + /* + * Typically the CODECs we are paired with will require clocks + * to be present at time of mute with the 'mute_stream' op. + * We need to disable the clocks here at the earliest (hw_free + * would be too early). + * + * We are operating on a foreign cluster here, but since we + * belong to the same PCM, accesses should have been + * synchronized at ASoC level. + */ + cl->clocks_in_use[substream->stream] = false; + + if (!mca_fe_clocks_in_use(fe_cl)) + mca_fe_disable_clocks(fe_cl); + } + cl->port_started[substream->stream] = false; if (!mca_be_started(cl)) { @@ -796,7 +789,6 @@ static void mca_be_shutdown(struct snd_pcm_substream *substream, static const struct snd_soc_dai_ops mca_be_ops = { .prepare = mca_be_prepare, - .hw_free = mca_be_hw_free, .startup = mca_be_startup, .shutdown = mca_be_shutdown, }; From 349da3586e397ec58263ba886cfeeed9a21241c7 Mon Sep 17 00:00:00 2001 From: James Calligeros Date: Sun, 3 Sep 2023 17:09:59 +1000 Subject: [PATCH 0106/3902] ASoC: macaudio: alias j415 kcontrols to j314 Signed-off-by: James Calligeros --- sound/soc/apple/macaudio.c | 1 + 1 file changed, 1 insertion(+) diff --git a/sound/soc/apple/macaudio.c b/sound/soc/apple/macaudio.c index e24006ca79c8fb..b5543f9caf44c1 100644 --- a/sound/soc/apple/macaudio.c +++ b/sound/soc/apple/macaudio.c @@ -946,6 +946,7 @@ static const struct of_device_id macaudio_snd_device_id[] = { { .compatible = "apple,j314-macaudio", .data = macaudio_j314_fixup_controls }, { .compatible = "apple,j375-macaudio", .data = macaudio_j375_fixup_controls }, { .compatible = "apple,j413-macaudio", .data = macaudio_j314_fixup_controls }, + { .compatible = "apple,j415-macaudio", .data = macaudio_j314_fixup_controls }, { .compatible = "apple,j493-macaudio", .data = macaudio_j493_fixup_controls }, { .compatible = "apple,macaudio"}, { } From d043e1d52e2db5126c38c91e4022a0a58e649987 Mon Sep 17 00:00:00 2001 From: Hector Martin Date: Mon, 9 Oct 2023 20:45:36 +0900 Subject: [PATCH 0107/3902] ALSA: control: Add kcontrol callbacks for lock/unlock This allows drivers to implement policy around locking/unlocking controls, such as enforcing that a group of controls may only be locked by the same process/file, and taking actions when the controls lock/unlock (such as granting special access on lock and resetting values on unlock). This is, in particular, useful to implement volume safety controls, such that only a particular process (that locks controls and completes a handshake) may increase volumes above a given safe limit. It also allows the volume to be automatically lowered if that process dies (which will trigger an implicit unlock). Signed-off-by: Hector Martin --- include/sound/control.h | 7 +++++++ sound/core/control.c | 16 ++++++++++++++-- 2 files changed, 21 insertions(+), 2 deletions(-) diff --git a/include/sound/control.h b/include/sound/control.h index e07f6b960641ff..9be6546bf787de 100644 --- a/include/sound/control.h +++ b/include/sound/control.h @@ -14,9 +14,12 @@ #define snd_kcontrol_chip(kcontrol) ((kcontrol)->private_data) struct snd_kcontrol; +struct snd_ctl_file; typedef int (snd_kcontrol_info_t) (struct snd_kcontrol * kcontrol, struct snd_ctl_elem_info * uinfo); typedef int (snd_kcontrol_get_t) (struct snd_kcontrol * kcontrol, struct snd_ctl_elem_value * ucontrol); typedef int (snd_kcontrol_put_t) (struct snd_kcontrol * kcontrol, struct snd_ctl_elem_value * ucontrol); +typedef int (snd_kcontrol_lock_t) (struct snd_kcontrol * kcontrol, struct snd_ctl_file *owner); +typedef void (snd_kcontrol_unlock_t) (struct snd_kcontrol * kcontrol); typedef int (snd_kcontrol_tlv_rw_t)(struct snd_kcontrol *kcontrol, int op_flag, /* SNDRV_CTL_TLV_OP_XXX */ unsigned int size, @@ -55,6 +58,8 @@ struct snd_kcontrol_new { snd_kcontrol_info_t *info; snd_kcontrol_get_t *get; snd_kcontrol_put_t *put; + snd_kcontrol_lock_t *lock; + snd_kcontrol_unlock_t *unlock; union { snd_kcontrol_tlv_rw_t *c; const unsigned int *p; @@ -74,6 +79,8 @@ struct snd_kcontrol { snd_kcontrol_info_t *info; snd_kcontrol_get_t *get; snd_kcontrol_put_t *put; + snd_kcontrol_lock_t *lock; + snd_kcontrol_unlock_t *unlock; union { snd_kcontrol_tlv_rw_t *c; const unsigned int *p; diff --git a/sound/core/control.c b/sound/core/control.c index 9c3fd5113a6173..6ade3bfa11d891 100644 --- a/sound/core/control.c +++ b/sound/core/control.c @@ -123,10 +123,12 @@ static int snd_ctl_release(struct inode *inode, struct file *file) scoped_guard(rwsem_write, &card->controls_rwsem) { list_for_each_entry(control, &card->controls, list) for (idx = 0; idx < control->count; idx++) - if (control->vd[idx].owner == ctl) + if (control->vd[idx].owner == ctl) { control->vd[idx].owner = NULL; + if (control->unlock) + control->unlock(control); + } } - snd_fasync_free(ctl->fasync); snd_ctl_empty_read_queue(ctl); put_pid(ctl->pid); @@ -303,6 +305,8 @@ struct snd_kcontrol *snd_ctl_new1(const struct snd_kcontrol_new *ncontrol, kctl->info = ncontrol->info; kctl->get = ncontrol->get; kctl->put = ncontrol->put; + kctl->lock = ncontrol->lock; + kctl->unlock = ncontrol->unlock; kctl->tlv.p = ncontrol->tlv.p; kctl->private_value = ncontrol->private_value; @@ -1359,6 +1363,12 @@ static int snd_ctl_elem_lock(struct snd_ctl_file *file, vd = &kctl->vd[snd_ctl_get_ioff(kctl, &id)]; if (vd->owner) return -EBUSY; + + if (kctl->lock) { + int err = kctl->lock(kctl, file); + if (err < 0) + return err; + } vd->owner = file; return 0; } @@ -1383,6 +1393,8 @@ static int snd_ctl_elem_unlock(struct snd_ctl_file *file, if (vd->owner != file) return -EPERM; vd->owner = NULL; + if (kctl->unlock) + kctl->unlock(kctl); return 0; } From fa81d25a7c78ee2d19969fbd8a0d4498f9cdfc54 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20Povi=C5=A1er?= Date: Thu, 19 Jan 2023 07:45:47 +0100 Subject: [PATCH 0108/3902] ASoC: macaudio: Condition selecting NCO driver on COMMON_CLK MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Only select the NCO driver's symbol if COMMON_CLK is selected, otherwise we risk misconfiguration. Signed-off-by: Martin Povišer --- sound/soc/apple/Kconfig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sound/soc/apple/Kconfig b/sound/soc/apple/Kconfig index 9e8232f8156050..5bcfb5f025010d 100644 --- a/sound/soc/apple/Kconfig +++ b/sound/soc/apple/Kconfig @@ -14,7 +14,7 @@ config SND_SOC_APPLE_MACAUDIO select SND_SOC_APPLE_MCA select SND_SIMPLE_CARD_UTILS select APPLE_ADMAC if DMADEVICES - select COMMON_CLK_APPLE_NCO + select COMMON_CLK_APPLE_NCO if COMMON_CLK select SND_SOC_TAS2764 if I2C select SND_SOC_TAS2770 if I2C select SND_SOC_CS42L83 if I2C From e0466e6e04f15e8aed969216041bfa7b3da5d9b5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20Povi=C5=A1er?= Date: Fri, 20 Jan 2023 20:59:52 +0100 Subject: [PATCH 0109/3902] ASoC: macaudio: Tune DT parsing error messages MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Martin Povišer --- sound/soc/apple/macaudio.c | 48 ++++++++++++++++++++++++-------------- 1 file changed, 31 insertions(+), 17 deletions(-) diff --git a/sound/soc/apple/macaudio.c b/sound/soc/apple/macaudio.c index b5543f9caf44c1..4da23929440427 100644 --- a/sound/soc/apple/macaudio.c +++ b/sound/soc/apple/macaudio.c @@ -206,12 +206,14 @@ static int macaudio_parse_of_be_dai_link(struct macaudio_snd_data *ma, for_each_link_codecs(link, i, comp) { ret = macaudio_parse_of_component(codec, codec_base + i, comp); if (ret) - return ret; + return dev_err_probe(ma->card.dev, ret, "parsing CODEC DAI of link '%s' at %pOF\n", + link->name, codec); } ret = macaudio_parse_of_component(cpu, be_index, link->cpus); if (ret) - return ret; + return dev_err_probe(ma->card.dev, ret, "parsing CPU DAI of link '%s' at %pOF\n", + link->name, codec); link->name = link->cpus[0].dai_name; @@ -232,7 +234,7 @@ static int macaudio_parse_of(struct macaudio_snd_data *ma) ret = snd_soc_of_parse_card_name(card, "model"); if (ret) { - dev_err(dev, "Error parsing card name: %d\n", ret); + dev_err_probe(dev, ret, "parsing card name\n"); return ret; } @@ -245,8 +247,8 @@ static int macaudio_parse_of(struct macaudio_snd_data *ma) cpu = of_get_child_by_name(np, "cpu"); if (!cpu) { - dev_err(dev, "missing CPU DAI node at %pOF\n", np); - ret = -EINVAL; + ret = dev_err_probe(dev, -EINVAL, + "missing CPU DAI node at %pOF\n", np); goto err_free; } @@ -254,8 +256,8 @@ static int macaudio_parse_of(struct macaudio_snd_data *ma) "#sound-dai-cells"); if (num_cpus <= 0) { - dev_err(card->dev, "missing sound-dai property at %pOF\n", cpu); - ret = -EINVAL; + ret = dev_err_probe(card->dev, -EINVAL, + "missing sound-dai property at %pOF\n", cpu); goto err_free; } of_node_put(cpu); @@ -296,10 +298,12 @@ static int macaudio_parse_of(struct macaudio_snd_data *ma) ret = of_property_read_string(np, "link-name", &link_name); if (ret) { - dev_err(card->dev, "missing link name\n"); + dev_err_probe(card->dev, ret, "missing link name\n"); goto err_free; } + dev_dbg(ma->card.dev, "parsing link '%s'\n", link_name); + speakers = !strcmp(link_name, "Speaker") || !strcmp(link_name, "Speakers"); if (speakers) @@ -309,31 +313,34 @@ static int macaudio_parse_of(struct macaudio_snd_data *ma) codec = of_get_child_by_name(np, "codec"); if (!codec || !cpu) { - dev_err(dev, "missing DAI specifications for '%s'\n", link_name); - ret = -EINVAL; + ret = dev_err_probe(dev, -EINVAL, + "missing DAI specifications for '%s'\n", link_name); goto err_free; } num_bes = of_count_phandle_with_args(cpu, "sound-dai", "#sound-dai-cells"); if (num_bes <= 0) { - dev_err(card->dev, "missing sound-dai property at %pOF\n", cpu); - ret = -EINVAL; + ret = dev_err_probe(card->dev, -EINVAL, + "missing sound-dai property at %pOF\n", cpu); goto err_free; } num_codecs = of_count_phandle_with_args(codec, "sound-dai", "#sound-dai-cells"); if (num_codecs <= 0) { - dev_err(card->dev, "missing sound-dai property at %pOF\n", codec); - ret = -EINVAL; + ret = dev_err_probe(card->dev, -EINVAL, + "missing sound-dai property at %pOF\n", codec); goto err_free; } + dev_dbg(ma->card.dev, "link '%s': %d CPUs %d CODECs\n", + link_name, num_bes, num_codecs); + if (num_codecs % num_bes != 0) { - dev_err(card->dev, "bad combination of CODEC (%d) and CPU (%d) number at %pOF\n", + ret = dev_err_probe(card->dev, -EINVAL, + "bad combination of CODEC (%d) and CPU (%d) number at %pOF\n", num_codecs, num_bes, np); - ret = -EINVAL; goto err_free; } @@ -363,6 +370,13 @@ static int macaudio_parse_of(struct macaudio_snd_data *ma) right_mask = left_mask << 1; for (be_index = 0; be_index < num_bes; be_index++) { + /* + * Set initial link name to be overwritten by a BE-specific + * name later so that we can use at least use the provisional + * name in error messages. + */ + link->name = link_name; + ret = macaudio_parse_of_be_dai_link(ma, link, be_index, ncodecs_per_cpu, cpu, codec); if (ret) @@ -994,7 +1008,7 @@ static int macaudio_snd_platform_probe(struct platform_device *pdev) ret = macaudio_parse_of(data); if (ret) - return dev_err_probe(&pdev->dev, ret, "failed OF parsing\n"); + return ret; for_each_card_prelinks(card, i, link) { if (link->no_pcm) { From 9906f57e83d411d6864e2c2a702c4f463ce3cc81 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20Povi=C5=A1er?= Date: Fri, 16 Sep 2022 13:43:25 +0200 Subject: [PATCH 0110/3902] ASoC: apple: mca: Separate data & clock port setup MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Up until now FEs were always the clock providers -- feeding the clocks on any ports (BEs) they are attached to. This will soon change and FEs will be allowed to be clock consumers. Once that happens, the routing of clocks and data will to some degree decouple. In advance of the change, make preparations: * Narrow down semantics of what was formerly the 'port_driver' field to refer to clocks only. * On 'startup' of BEs, separate the clock and data aspects of the port setup. Signed-off-by: Martin Povišer --- sound/soc/apple/mca.c | 67 ++++++++++++++++++++++++++----------------- 1 file changed, 40 insertions(+), 27 deletions(-) diff --git a/sound/soc/apple/mca.c b/sound/soc/apple/mca.c index 202cd6dd6365a5..c2386d8fa8a726 100644 --- a/sound/soc/apple/mca.c +++ b/sound/soc/apple/mca.c @@ -133,8 +133,8 @@ struct mca_cluster { struct clk *clk_parent; struct dma_chan *dma_chans[SNDRV_PCM_STREAM_LAST + 1]; - bool port_started[SNDRV_PCM_STREAM_LAST + 1]; - int port_driver; /* The cluster driving this cluster's port */ + bool port_clk_started[SNDRV_PCM_STREAM_LAST + 1]; + int port_clk_driver; /* The cluster driving this cluster's port */ bool clocks_in_use[SNDRV_PCM_STREAM_LAST + 1]; struct device_link *pd_link; @@ -157,7 +157,7 @@ struct mca_data { struct reset_control *rstc; struct device_link *pd_link; - /* Mutex for accessing port_driver of foreign clusters */ + /* Mutex for accessing port_clk_driver of foreign clusters */ struct mutex port_mutex; int nclusters; @@ -311,7 +311,7 @@ static bool mca_fe_clocks_in_use(struct mca_cluster *cl) for (i = 0; i < mca->nclusters; i++) { be_cl = &mca->clusters[i]; - if (be_cl->port_driver != cl->no) + if (be_cl->port_clk_driver != cl->no) continue; for_each_pcm_streams(stream) { @@ -333,10 +333,10 @@ static int mca_be_prepare(struct snd_pcm_substream *substream, struct mca_cluster *fe_cl; int ret; - if (cl->port_driver < 0) + if (cl->port_clk_driver < 0) return -EINVAL; - fe_cl = &mca->clusters[cl->port_driver]; + fe_cl = &mca->clusters[cl->port_clk_driver]; /* * Typically the CODECs we are paired with will require clocks @@ -683,12 +683,15 @@ static const struct snd_soc_dai_ops mca_fe_ops = { .trigger = mca_fe_trigger, }; -static bool mca_be_started(struct mca_cluster *cl) +/* + * Is there a FE attached which will be feeding this port's clocks? + */ +static bool mca_be_clk_started(struct mca_cluster *cl) { int stream; for_each_pcm_streams(stream) - if (cl->port_started[stream]) + if (cl->port_clk_started[stream]) return true; return false; } @@ -719,29 +722,35 @@ static int mca_be_startup(struct snd_pcm_substream *substream, fe_cl = mca_dai_to_cluster(snd_soc_rtd_to_cpu(fe, 0)); - if (mca_be_started(cl)) { + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + writel_relaxed(PORT_DATA_SEL_TXA(fe_cl->no), + cl->base + REG_PORT_DATA_SEL); + mca_modify(cl, REG_PORT_ENABLES, PORT_ENABLES_TX_DATA, + PORT_ENABLES_TX_DATA); + } + + if (mca_be_clk_started(cl)) { /* * Port is already started in the other direction. * Make sure there isn't a conflict with another cluster - * driving the port. + * driving the port clocks. */ - if (cl->port_driver != fe_cl->no) + if (cl->port_clk_driver != fe_cl->no) return -EINVAL; - cl->port_started[substream->stream] = true; + cl->port_clk_started[substream->stream] = true; return 0; } - writel_relaxed(PORT_ENABLES_CLOCKS | PORT_ENABLES_TX_DATA, - cl->base + REG_PORT_ENABLES); writel_relaxed(FIELD_PREP(PORT_CLOCK_SEL, fe_cl->no + 1), cl->base + REG_PORT_CLOCK_SEL); - writel_relaxed(PORT_DATA_SEL_TXA(fe_cl->no), - cl->base + REG_PORT_DATA_SEL); + mca_modify(cl, REG_PORT_ENABLES, PORT_ENABLES_CLOCKS, + PORT_ENABLES_CLOCKS); + mutex_lock(&mca->port_mutex); - cl->port_driver = fe_cl->no; + cl->port_clk_driver = fe_cl->no; mutex_unlock(&mca->port_mutex); - cl->port_started[substream->stream] = true; + cl->port_clk_started[substream->stream] = true; return 0; } @@ -753,8 +762,8 @@ static void mca_be_shutdown(struct snd_pcm_substream *substream, struct mca_data *mca = cl->host; if (cl->clocks_in_use[substream->stream] && - !WARN_ON(cl->port_driver < 0)) { - struct mca_cluster *fe_cl = &mca->clusters[cl->port_driver]; + !WARN_ON(cl->port_clk_driver < 0)) { + struct mca_cluster *fe_cl = &mca->clusters[cl->port_clk_driver]; /* * Typically the CODECs we are paired with will require clocks @@ -772,17 +781,21 @@ static void mca_be_shutdown(struct snd_pcm_substream *substream, mca_fe_disable_clocks(fe_cl); } - cl->port_started[substream->stream] = false; + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + mca_modify(cl, REG_PORT_ENABLES, PORT_ENABLES_TX_DATA, 0); + writel_relaxed(0, cl->base + REG_PORT_DATA_SEL); + } - if (!mca_be_started(cl)) { + cl->port_clk_started[substream->stream] = false; + if (!mca_be_clk_started(cl)) { /* * Were we the last direction to shutdown? - * Turn off the lights. + * Turn off the lights (clocks). */ - writel_relaxed(0, cl->base + REG_PORT_ENABLES); - writel_relaxed(0, cl->base + REG_PORT_DATA_SEL); + mca_modify(cl, REG_PORT_ENABLES, PORT_ENABLES_CLOCKS, 0); + writel_relaxed(0, cl->base + REG_PORT_CLOCK_SEL); mutex_lock(&mca->port_mutex); - cl->port_driver = -1; + cl->port_clk_driver = -1; mutex_unlock(&mca->port_mutex); } } @@ -1088,7 +1101,7 @@ static int apple_mca_probe(struct platform_device *pdev) cl->host = mca; cl->no = i; cl->base = base + CLUSTER_STRIDE * i; - cl->port_driver = -1; + cl->port_clk_driver = -1; cl->clk_parent = of_clk_get(pdev->dev.of_node, i); if (IS_ERR(cl->clk_parent)) { dev_err(&pdev->dev, "unable to obtain clock %d: %ld\n", From ecc747803c036b174ed6da87f714d7a7bad176ea Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20Povi=C5=A1er?= Date: Fri, 16 Sep 2022 14:18:16 +0200 Subject: [PATCH 0111/3902] ASoC: apple: mca: Factor out mca_be_get_fe MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This is a function that we also want to use from within mca_be_shutdown, so factor it out. Signed-off-by: Martin Povišer --- sound/soc/apple/mca.c | 31 ++++++++++++++++++------------- 1 file changed, 18 insertions(+), 13 deletions(-) diff --git a/sound/soc/apple/mca.c b/sound/soc/apple/mca.c index c2386d8fa8a726..d555c34578e19f 100644 --- a/sound/soc/apple/mca.c +++ b/sound/soc/apple/mca.c @@ -696,30 +696,35 @@ static bool mca_be_clk_started(struct mca_cluster *cl) return false; } -static int mca_be_startup(struct snd_pcm_substream *substream, - struct snd_soc_dai *dai) +static struct snd_soc_pcm_runtime *mca_be_get_fe(struct snd_soc_pcm_runtime *be, + int stream) { - struct snd_soc_pcm_runtime *be = snd_soc_substream_to_rtd(substream); - struct snd_soc_pcm_runtime *fe; - struct mca_cluster *cl = mca_dai_to_cluster(dai); - struct mca_cluster *fe_cl; - struct mca_data *mca = cl->host; + struct snd_soc_pcm_runtime *fe = NULL; struct snd_soc_dpcm *dpcm; - fe = NULL; - - for_each_dpcm_fe(be, substream->stream, dpcm) { + for_each_dpcm_fe(be, stream, dpcm) { if (fe && dpcm->fe != fe) { - dev_err(mca->dev, "many FE per one BE unsupported\n"); - return -EINVAL; + dev_err(be->dev, "many FE per one BE unsupported\n"); + return NULL; } fe = dpcm->fe; } + return fe; +} + +static int mca_be_startup(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct snd_soc_pcm_runtime *be = snd_soc_substream_to_rtd(substream); + struct snd_soc_pcm_runtime *fe = mca_be_get_fe(be, substream->stream); + struct mca_cluster *cl = mca_dai_to_cluster(dai); + struct mca_cluster *fe_cl; + struct mca_data *mca = cl->host; + if (!fe) return -EINVAL; - fe_cl = mca_dai_to_cluster(snd_soc_rtd_to_cpu(fe, 0)); if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { From 2ff1981c2708b02c9f824f9e7288e74d04d00630 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20Povi=C5=A1er?= Date: Fri, 16 Sep 2022 14:25:04 +0200 Subject: [PATCH 0112/3902] ASoC: apple: mca: Support FEs being clock consumers MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Support FEs being I2S clock consumers. This does not mean we support accepting clocks from outside the SoC (although it paves the way for that support in the future), but it means multiple FEs can attach to one BE, one being clock producer and the rest clock consumers. This is useful for grabbing I/V sense data on some machines, since in such a scenario the format of the sense data on the I2S bus differs from that of the audio data (the two formats differing in slot width). With two FEs attached to the bus, we can split the responsibilities and command different slot widths to the two. Signed-off-by: Martin Povišer --- sound/soc/apple/mca.c | 109 ++++++++++++++++++++++++++++++++++-------- 1 file changed, 88 insertions(+), 21 deletions(-) diff --git a/sound/soc/apple/mca.c b/sound/soc/apple/mca.c index d555c34578e19f..407b6d49b58327 100644 --- a/sound/soc/apple/mca.c +++ b/sound/soc/apple/mca.c @@ -133,6 +133,8 @@ struct mca_cluster { struct clk *clk_parent; struct dma_chan *dma_chans[SNDRV_PCM_STREAM_LAST + 1]; + bool clk_provider; + bool port_clk_started[SNDRV_PCM_STREAM_LAST + 1]; int port_clk_driver; /* The cluster driving this cluster's port */ @@ -256,11 +258,32 @@ static int mca_fe_trigger(struct snd_pcm_substream *substream, int cmd, return 0; } +static int mca_fe_get_port(struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *fe = snd_soc_substream_to_rtd(substream); + struct snd_soc_pcm_runtime *be; + struct snd_soc_dpcm *dpcm; + + be = NULL; + for_each_dpcm_be(fe, substream->stream, dpcm) { + be = dpcm->be; + break; + } + + if (!be) + return -EINVAL; + + return mca_dai_to_cluster(snd_soc_rtd_to_cpu(be, 0))->no; +} + static int mca_fe_enable_clocks(struct mca_cluster *cl) { struct mca_data *mca = cl->host; int ret; + if (!cl->clk_provider) + return -EINVAL; + ret = clk_prepare_enable(cl->clk_parent); if (ret) { dev_err(mca->dev, @@ -334,7 +357,7 @@ static int mca_be_prepare(struct snd_pcm_substream *substream, int ret; if (cl->port_clk_driver < 0) - return -EINVAL; + return 0; fe_cl = &mca->clusters[cl->port_clk_driver]; @@ -355,6 +378,44 @@ static int mca_be_prepare(struct snd_pcm_substream *substream, return 0; } +static int mca_fe_prepare(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct mca_cluster *cl = mca_dai_to_cluster(dai); + struct mca_data *mca = cl->host; + + if (cl->clk_provider) + return 0; + + if (!mca_fe_clocks_in_use(cl)) { + int port = mca_fe_get_port(substream); + writel_relaxed(port + 6 + 1, + cl->base + REG_SYNCGEN_MCLK_SEL); + mca_modify(cl, REG_SYNCGEN_STATUS, SYNCGEN_STATUS_EN, + SYNCGEN_STATUS_EN); + } + cl->clocks_in_use[substream->stream] = true; + + return 0; +} + +static int mca_fe_hw_free(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct mca_cluster *cl = mca_dai_to_cluster(dai); + + if (cl->clk_provider) + return 0; + + cl->clocks_in_use[substream->stream] = false; + if (mca_fe_clocks_in_use(cl)) + return 0; + + mca_modify(cl, REG_SYNCGEN_STATUS, SYNCGEN_STATUS_EN, 0); + + return 0; +} + static unsigned int mca_crop_mask(unsigned int mask, int nchans) { while (hweight32(mask) > nchans) @@ -480,9 +541,18 @@ static int mca_fe_set_fmt(struct snd_soc_dai *dai, unsigned int fmt) u32 serdes_conf = 0; u32 bitstart; - if ((fmt & SND_SOC_DAIFMT_CLOCK_PROVIDER_MASK) != - SND_SOC_DAIFMT_BP_FP) + switch (fmt & SND_SOC_DAIFMT_CLOCK_PROVIDER_MASK) { + case SND_SOC_DAIFMT_BP_FP: + cl->clk_provider = true; + break; + + case SND_SOC_DAIFMT_BC_FC: + cl->clk_provider = false; + break; + + default: goto err; + } switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { case SND_SOC_DAIFMT_I2S: @@ -539,24 +609,6 @@ static int mca_set_bclk_ratio(struct snd_soc_dai *dai, unsigned int ratio) return 0; } -static int mca_fe_get_port(struct snd_pcm_substream *substream) -{ - struct snd_soc_pcm_runtime *fe = snd_soc_substream_to_rtd(substream); - struct snd_soc_pcm_runtime *be; - struct snd_soc_dpcm *dpcm; - - be = NULL; - for_each_dpcm_be(fe, substream->stream, dpcm) { - be = dpcm->be; - break; - } - - if (!be) - return -EINVAL; - - return mca_dai_to_cluster(snd_soc_rtd_to_cpu(be, 0))->no; -} - static int mca_fe_hw_params(struct snd_pcm_substream *substream, struct snd_pcm_hw_params *params, struct snd_soc_dai *dai) @@ -681,6 +733,8 @@ static const struct snd_soc_dai_ops mca_fe_ops = { .set_tdm_slot = mca_fe_set_tdm_slot, .hw_params = mca_fe_hw_params, .trigger = mca_fe_trigger, + .prepare = mca_fe_prepare, + .hw_free = mca_fe_hw_free, }; /* @@ -734,6 +788,9 @@ static int mca_be_startup(struct snd_pcm_substream *substream, PORT_ENABLES_TX_DATA); } + if (!fe_cl->clk_provider) + return 0; + if (mca_be_clk_started(cl)) { /* * Port is already started in the other direction. @@ -763,7 +820,10 @@ static int mca_be_startup(struct snd_pcm_substream *substream, static void mca_be_shutdown(struct snd_pcm_substream *substream, struct snd_soc_dai *dai) { + struct snd_soc_pcm_runtime *be = snd_soc_substream_to_rtd(substream); + struct snd_soc_pcm_runtime *fe = mca_be_get_fe(be, substream->stream); struct mca_cluster *cl = mca_dai_to_cluster(dai); + struct mca_cluster *fe_cl; struct mca_data *mca = cl->host; if (cl->clocks_in_use[substream->stream] && @@ -786,11 +846,18 @@ static void mca_be_shutdown(struct snd_pcm_substream *substream, mca_fe_disable_clocks(fe_cl); } + if (!fe) + return; + fe_cl = mca_dai_to_cluster(snd_soc_rtd_to_cpu(fe, 0)); + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { mca_modify(cl, REG_PORT_ENABLES, PORT_ENABLES_TX_DATA, 0); writel_relaxed(0, cl->base + REG_PORT_DATA_SEL); } + if (!fe_cl->clk_provider) + return; + cl->port_clk_started[substream->stream] = false; if (!mca_be_clk_started(cl)) { /* From 629bc0ad5786a848685aaa3ac1ac80f53266cd3e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20Povi=C5=A1er?= Date: Wed, 14 Dec 2022 13:07:14 +0100 Subject: [PATCH 0113/3902] ASoC: apple: mca: Fix SYNCGEN enable on FE clock consumers MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Martin Povišer --- sound/soc/apple/mca.c | 23 +++++++++++++++++++---- 1 file changed, 19 insertions(+), 4 deletions(-) diff --git a/sound/soc/apple/mca.c b/sound/soc/apple/mca.c index 407b6d49b58327..3206e07a7f016d 100644 --- a/sound/soc/apple/mca.c +++ b/sound/soc/apple/mca.c @@ -141,6 +141,9 @@ struct mca_cluster { bool clocks_in_use[SNDRV_PCM_STREAM_LAST + 1]; struct device_link *pd_link; + /* In case of clock consumer FE */ + int syncgen_in_use; + unsigned int bclk_ratio; /* Masks etc. picked up via the set_tdm_slot method */ @@ -387,14 +390,24 @@ static int mca_fe_prepare(struct snd_pcm_substream *substream, if (cl->clk_provider) return 0; - if (!mca_fe_clocks_in_use(cl)) { + if (!cl->syncgen_in_use) { int port = mca_fe_get_port(substream); + + cl->pd_link = device_link_add(mca->dev, cl->pd_dev, + DL_FLAG_STATELESS | DL_FLAG_PM_RUNTIME | + DL_FLAG_RPM_ACTIVE); + if (!cl->pd_link) { + dev_err(mca->dev, + "cluster %d: unable to prop-up power domain\n", cl->no); + return -EINVAL; + } + writel_relaxed(port + 6 + 1, cl->base + REG_SYNCGEN_MCLK_SEL); mca_modify(cl, REG_SYNCGEN_STATUS, SYNCGEN_STATUS_EN, SYNCGEN_STATUS_EN); } - cl->clocks_in_use[substream->stream] = true; + cl->syncgen_in_use |= 1 << substream->stream; return 0; } @@ -407,11 +420,13 @@ static int mca_fe_hw_free(struct snd_pcm_substream *substream, if (cl->clk_provider) return 0; - cl->clocks_in_use[substream->stream] = false; - if (mca_fe_clocks_in_use(cl)) + cl->syncgen_in_use &= ~(1 << substream->stream); + if (cl->syncgen_in_use) return 0; mca_modify(cl, REG_SYNCGEN_STATUS, SYNCGEN_STATUS_EN, 0); + if (cl->pd_link) + device_link_del(cl->pd_link); return 0; } From 17335923bd3332729f3b082f14e88b8875d91185 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20Povi=C5=A1er?= Date: Wed, 14 Dec 2022 13:06:26 +0100 Subject: [PATCH 0114/3902] ASoC: macaudio: Start speaker sense capture support MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Martin Povišer --- sound/soc/apple/macaudio.c | 34 ++++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/sound/soc/apple/macaudio.c b/sound/soc/apple/macaudio.c index 4da23929440427..e5fdde796fe47c 100644 --- a/sound/soc/apple/macaudio.c +++ b/sound/soc/apple/macaudio.c @@ -57,6 +57,7 @@ struct macaudio_snd_data { struct macaudio_link_props { /* frontend props */ unsigned int bclk_ratio; + bool is_sense; /* backend props */ bool is_speakers; @@ -82,6 +83,11 @@ SND_SOC_DAILINK_DEFS(secondary, DAILINK_COMP_ARRAY(COMP_DUMMY()), // CODEC DAILINK_COMP_ARRAY(COMP_EMPTY())); +SND_SOC_DAILINK_DEFS(sense, + DAILINK_COMP_ARRAY(COMP_CPU("mca-pcm-2")), // CPU + DAILINK_COMP_ARRAY(COMP_DUMMY()), // CODEC + DAILINK_COMP_ARRAY(COMP_EMPTY())); + static struct snd_soc_dai_link macaudio_fe_links[] = { { .name = "Primary", @@ -106,6 +112,17 @@ static struct snd_soc_dai_link macaudio_fe_links[] = { .dai_fmt = MACAUDIO_DAI_FMT, SND_SOC_DAILINK_REG(secondary), }, + { + .name = "Speaker Sense", + .stream_name = "Speaker Sense", + .dynamic = 1, + .dpcm_capture = 1, + .dai_fmt = (SND_SOC_DAIFMT_I2S | \ + SND_SOC_DAIFMT_CBP_CFP | \ + SND_SOC_DAIFMT_GATED | \ + SND_SOC_DAIFMT_IB_IF), + SND_SOC_DAILINK_REG(sense), + }, }; static struct macaudio_link_props macaudio_fe_link_props[] = { @@ -133,6 +150,9 @@ static struct macaudio_link_props macaudio_fe_link_props[] = { * those fancy speaker arrays. */ .bclk_ratio = 256, + }, + { + .is_sense = 1, } }; @@ -626,6 +646,9 @@ static int macaudio_fe_init(struct snd_soc_pcm_runtime *rtd) struct macaudio_link_props *props = &ma->link_props[rtd->dai_link->id]; int nslots = props->bclk_ratio / MACAUDIO_SLOTWIDTH; + if (props->is_sense) + return snd_soc_dai_set_tdm_slot(snd_soc_rtd_to_cpu(rtd, 0), 0, 0xffff, 16, 16); + return snd_soc_dai_set_tdm_slot(snd_soc_rtd_to_cpu(rtd, 0), (1 << nslots) - 1, (1 << nslots) - 1, nslots, MACAUDIO_SLOTWIDTH); } @@ -686,6 +709,13 @@ static int macaudio_add_backend_dai_route(struct snd_soc_card *card, struct snd_ r->sink = "Headset Capture"; } + /* If speakers, add sense capture path */ + if (is_speakers) { + r = &routes[nroutes++]; + r->source = dai->stream[SNDRV_PCM_STREAM_CAPTURE].widget->name; + r->sink = "Speaker Sense Capture"; + } + ret = snd_soc_dapm_add_routes(&card->dapm, routes, nroutes); if (ret) dev_err(card->dev, "failed adding dynamic DAPM routes for %s\n", @@ -929,6 +959,7 @@ static const struct snd_soc_dapm_widget macaudio_snd_widgets[] = { SND_SOC_DAPM_AIF_OUT("Headphone Playback", NULL, 0, SND_SOC_NOPM, 0, 0), SND_SOC_DAPM_AIF_IN("Headset Capture", NULL, 0, SND_SOC_NOPM, 0, 0), + SND_SOC_DAPM_AIF_IN("Speaker Sense Capture", NULL, 0, SND_SOC_NOPM, 0, 0), }; static const struct snd_kcontrol_new macaudio_controls[] = { @@ -952,6 +983,9 @@ static const struct snd_soc_dapm_route macaudio_dapm_routes[] = { /* Capture paths */ { "PCM0 RX", NULL, "Headset Capture" }, + + /* Sense paths */ + { "PCM2 RX", NULL, "Speaker Sense Capture" }, }; static const struct of_device_id macaudio_snd_device_id[] = { From 222b1f0670fcf53277408cf495ae1f0d9ff0aa48 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20Povi=C5=A1er?= Date: Wed, 14 Dec 2022 13:07:51 +0100 Subject: [PATCH 0115/3902] ASoC: macaudio: Tweak "no audio route" message MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Martin Povišer --- sound/soc/apple/macaudio.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sound/soc/apple/macaudio.c b/sound/soc/apple/macaudio.c index e5fdde796fe47c..de4a195f95385b 100644 --- a/sound/soc/apple/macaudio.c +++ b/sound/soc/apple/macaudio.c @@ -527,7 +527,7 @@ static int macaudio_fe_hw_params(struct snd_pcm_substream *substream, } if (!be) { - dev_err(rtd->dev, "opening PCM device '%s' with no audio route configured (bad settings applied to the sound card)\n", + dev_err(rtd->dev, "opening PCM device '%s' with no audio route configured by the user\n", rtd->dai_link->name); return -EINVAL; } From 8dcfdf5d4a627e966a38b53a6ddc6249a924911c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20Povi=C5=A1er?= Date: Thu, 19 Jan 2023 07:43:56 +0100 Subject: [PATCH 0116/3902] ASoC: macaudio: Do not constrain sense PCM MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Martin Povišer --- sound/soc/apple/macaudio.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/sound/soc/apple/macaudio.c b/sound/soc/apple/macaudio.c index de4a195f95385b..cd84efb96edae6 100644 --- a/sound/soc/apple/macaudio.c +++ b/sound/soc/apple/macaudio.c @@ -499,8 +499,12 @@ static int macaudio_fe_startup(struct snd_pcm_substream *substream) struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream); struct macaudio_snd_data *ma = snd_soc_card_get_drvdata(rtd->card); + struct macaudio_link_props *props = &ma->link_props[rtd->dai_link->id]; int ret; + if (props->is_sense) + return 0; + /* The FEs must never have more channels than the hardware */ ret = snd_pcm_hw_constraint_minmax(substream->runtime, SNDRV_PCM_HW_PARAM_CHANNELS, 0, ma->max_channels); From bff8e646971bc6ca201918c65dfc5dbe3b18050c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20Povi=C5=A1er?= Date: Fri, 20 Jan 2023 12:31:53 +0100 Subject: [PATCH 0117/3902] NOT UPSTREAMABLE: ASoC: tas2764: Redo I/V sense logic MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Only set up I/V sense transmission in case the slots are described in devicetree, never use defaults. * Move the enablement of I/V sense transmission away from hw_params up into component probe, do not condition it on the measurements itself being enabled. * Move the slot configuration from set_tdm_slot into component probe, so it's not separate from other configuration. Since this makes I/V sense unavailable in some configurations where it formerly was, and it also changes behavior depending on the pairing with a machine-level driver (depending on set_tdm_slot calls), it's probably not upstreamable as is. Signed-off-by: Martin Povišer --- sound/soc/codecs/tas2764.c | 61 ++++++++++++++------------------------ 1 file changed, 23 insertions(+), 38 deletions(-) diff --git a/sound/soc/codecs/tas2764.c b/sound/soc/codecs/tas2764.c index 36e25e48b35463..04ff5cc03020e3 100644 --- a/sound/soc/codecs/tas2764.c +++ b/sound/soc/codecs/tas2764.c @@ -261,7 +261,6 @@ static int tas2764_mute(struct snd_soc_dai *dai, int mute, int direction) static int tas2764_set_bitwidth(struct tas2764_priv *tas2764, int bitwidth) { struct snd_soc_component *component = tas2764->component; - int sense_en; int val; int ret; @@ -296,28 +295,6 @@ static int tas2764_set_bitwidth(struct tas2764_priv *tas2764, int bitwidth) if (val < 0) return val; - if (val & (1 << TAS2764_VSENSE_POWER_EN)) - sense_en = 0; - else - sense_en = TAS2764_TDM_CFG5_VSNS_ENABLE; - - ret = snd_soc_component_update_bits(tas2764->component, TAS2764_TDM_CFG5, - TAS2764_TDM_CFG5_VSNS_ENABLE, - sense_en); - if (ret < 0) - return ret; - - if (val & (1 << TAS2764_ISENSE_POWER_EN)) - sense_en = 0; - else - sense_en = TAS2764_TDM_CFG6_ISNS_ENABLE; - - ret = snd_soc_component_update_bits(tas2764->component, TAS2764_TDM_CFG6, - TAS2764_TDM_CFG6_ISNS_ENABLE, - sense_en); - if (ret < 0) - return ret; - return 0; } @@ -447,7 +424,6 @@ static int tas2764_set_dai_tdm_slot(struct snd_soc_dai *dai, int slots, int slot_width) { struct snd_soc_component *component = dai->component; - struct tas2764_priv *tas2764 = snd_soc_component_get_drvdata(component); int left_slot, right_slot; int slots_cfg; int slot_size; @@ -494,15 +470,26 @@ static int tas2764_set_dai_tdm_slot(struct snd_soc_dai *dai, if (ret < 0) return ret; - ret = snd_soc_component_update_bits(component, TAS2764_TDM_CFG5, + return 0; +} + +static int tas2764_set_ivsense_transmit(struct tas2764_priv *tas2764, int i_slot, int v_slot) +{ + int ret; + + ret = snd_soc_component_update_bits(tas2764->component, TAS2764_TDM_CFG5, + TAS2764_TDM_CFG5_VSNS_ENABLE | TAS2764_TDM_CFG5_50_MASK, - tas2764->v_sense_slot); + TAS2764_TDM_CFG5_VSNS_ENABLE | + v_slot); if (ret < 0) return ret; - ret = snd_soc_component_update_bits(component, TAS2764_TDM_CFG6, + ret = snd_soc_component_update_bits(tas2764->component, TAS2764_TDM_CFG6, + TAS2764_TDM_CFG6_ISNS_ENABLE | TAS2764_TDM_CFG6_50_MASK, - tas2764->i_sense_slot); + TAS2764_TDM_CFG6_ISNS_ENABLE | + i_slot); if (ret < 0) return ret; @@ -695,15 +682,13 @@ static int tas2764_codec_probe(struct snd_soc_component *component) dev_warn(tas2764->dev, "failed to request IRQ: %d\n", ret); } - ret = snd_soc_component_update_bits(tas2764->component, TAS2764_TDM_CFG5, - TAS2764_TDM_CFG5_VSNS_ENABLE, 0); - if (ret < 0) - return ret; + if (tas2764->i_sense_slot != -1 && tas2764->v_sense_slot != -1) { + ret = tas2764_set_ivsense_transmit(tas2764, tas2764->i_sense_slot, + tas2764->v_sense_slot); - ret = snd_soc_component_update_bits(tas2764->component, TAS2764_TDM_CFG6, - TAS2764_TDM_CFG6_ISNS_ENABLE, 0); - if (ret < 0) - return ret; + if (ret < 0) + return ret; + } switch (tas2764->devid) { case DEVID_SN012776: @@ -856,12 +841,12 @@ static int tas2764_parse_dt(struct device *dev, struct tas2764_priv *tas2764) ret = fwnode_property_read_u32(dev->fwnode, "ti,imon-slot-no", &tas2764->i_sense_slot); if (ret) - tas2764->i_sense_slot = 0; + tas2764->i_sense_slot = -1; ret = fwnode_property_read_u32(dev->fwnode, "ti,vmon-slot-no", &tas2764->v_sense_slot); if (ret) - tas2764->v_sense_slot = 2; + tas2764->v_sense_slot = -1; return 0; } From f658445d2bacaa85ce2c7357673528679be5f59e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20Povi=C5=A1er?= Date: Mon, 23 Jan 2023 10:47:01 +0100 Subject: [PATCH 0118/3902] ASoC: macaudio: Tune constraining of FEs, add BCLK MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Martin Povišer --- sound/soc/apple/macaudio.c | 25 +++++++++++++++++++------ 1 file changed, 19 insertions(+), 6 deletions(-) diff --git a/sound/soc/apple/macaudio.c b/sound/soc/apple/macaudio.c index cd84efb96edae6..69032a6ee13ee6 100644 --- a/sound/soc/apple/macaudio.c +++ b/sound/soc/apple/macaudio.c @@ -45,6 +45,15 @@ SND_SOC_DAIFMT_IB_IF) #define MACAUDIO_JACK_MASK (SND_JACK_HEADSET | SND_JACK_HEADPHONE) #define MACAUDIO_SLOTWIDTH 32 +/* + * Maximum BCLK frequency + * + * Codec maximums: + * CS42L42 26.0 MHz + * TAS2770 27.1 MHz + * TAS2764 24.576 MHz + */ +#define MACAUDIO_MAX_BCLK_FREQ 24576000 struct macaudio_snd_data { struct snd_soc_card card; @@ -500,19 +509,23 @@ static int macaudio_fe_startup(struct snd_pcm_substream *substream) struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream); struct macaudio_snd_data *ma = snd_soc_card_get_drvdata(rtd->card); struct macaudio_link_props *props = &ma->link_props[rtd->dai_link->id]; - int ret; + int max_rate, ret; if (props->is_sense) return 0; - /* The FEs must never have more channels than the hardware */ ret = snd_pcm_hw_constraint_minmax(substream->runtime, - SNDRV_PCM_HW_PARAM_CHANNELS, 0, ma->max_channels); + SNDRV_PCM_HW_PARAM_CHANNELS, + 0, ma->max_channels); + if (ret < 0) + return ret; - if (ret < 0) { - dev_err(rtd->dev, "Failed to constrain FE %d! %d", rtd->dai_link->id, ret); + max_rate = MACAUDIO_MAX_BCLK_FREQ / props->bclk_ratio; + ret = snd_pcm_hw_constraint_minmax(substream->runtime, + SNDRV_PCM_HW_PARAM_RATE, + 0, max_rate); + if (ret < 0) return ret; - } return 0; } From 7625beae42cc5b35aeaac22e0371c70f5ec65d11 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20Povi=C5=A1er?= Date: Tue, 24 Jan 2023 15:14:53 +0100 Subject: [PATCH 0119/3902] ASoC: apple: mca: Support capture on multiples BEs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When multiple BEs are linked to a FE, the former behavior was to source the data line from the DIN pin of the first BE only. Change this to ORing the DIN inputs of all linked BEs. As long as the unused slots on each BE's line are zeroed out and the slots on the BEs don't overlap, this will work out well. Signed-off-by: Martin Povišer --- sound/soc/apple/mca.c | 30 +++++++++++++----------------- 1 file changed, 13 insertions(+), 17 deletions(-) diff --git a/sound/soc/apple/mca.c b/sound/soc/apple/mca.c index 3206e07a7f016d..8c1a676a80ba5e 100644 --- a/sound/soc/apple/mca.c +++ b/sound/soc/apple/mca.c @@ -261,22 +261,18 @@ static int mca_fe_trigger(struct snd_pcm_substream *substream, int cmd, return 0; } -static int mca_fe_get_port(struct snd_pcm_substream *substream) +static int mca_fe_get_portmask(struct snd_pcm_substream *substream) { struct snd_soc_pcm_runtime *fe = snd_soc_substream_to_rtd(substream); - struct snd_soc_pcm_runtime *be; struct snd_soc_dpcm *dpcm; + int mask = 0; - be = NULL; for_each_dpcm_be(fe, substream->stream, dpcm) { - be = dpcm->be; - break; + int no = mca_dai_to_cluster(snd_soc_rtd_to_cpu(dpcm->be, 0))->no; + mask |= 1 << no; } - if (!be) - return -EINVAL; - - return mca_dai_to_cluster(snd_soc_rtd_to_cpu(be, 0))->no; + return mask; } static int mca_fe_enable_clocks(struct mca_cluster *cl) @@ -391,7 +387,7 @@ static int mca_fe_prepare(struct snd_pcm_substream *substream, return 0; if (!cl->syncgen_in_use) { - int port = mca_fe_get_port(substream); + int port = ffs(mca_fe_get_portmask(substream)) - 1; cl->pd_link = device_link_add(mca->dev, cl->pd_dev, DL_FLAG_STATELESS | DL_FLAG_PM_RUNTIME | @@ -441,7 +437,7 @@ static unsigned int mca_crop_mask(unsigned int mask, int nchans) static int mca_configure_serdes(struct mca_cluster *cl, int serdes_unit, unsigned int mask, int slots, int nchans, - int slot_width, bool is_tx, int port) + int slot_width, bool is_tx, int portmask) { __iomem void *serdes_base = cl->base + serdes_unit; u32 serdes_conf, serdes_conf_mask; @@ -500,7 +496,7 @@ static int mca_configure_serdes(struct mca_cluster *cl, int serdes_unit, serdes_base + REG_RX_SERDES_SLOTMASK); writel_relaxed(~((u32)mca_crop_mask(mask, nchans)), serdes_base + REG_RX_SERDES_SLOTMASK + 0x4); - writel_relaxed(1 << port, + writel_relaxed(portmask, serdes_base + REG_RX_SERDES_PORT); } @@ -637,7 +633,7 @@ static int mca_fe_hw_params(struct snd_pcm_substream *substream, unsigned long bclk_ratio; unsigned int tdm_slots, tdm_slot_width, tdm_mask; u32 regval, pad; - int ret, port, nchans_ceiled; + int ret, portmask, nchans_ceiled; if (!cl->tdm_slot_width) { /* @@ -686,13 +682,13 @@ static int mca_fe_hw_params(struct snd_pcm_substream *substream, tdm_mask = (1 << tdm_slots) - 1; } - port = mca_fe_get_port(substream); - if (port < 0) - return port; + portmask = mca_fe_get_portmask(substream); + if (!portmask) + return -EINVAL; ret = mca_configure_serdes(cl, is_tx ? CLUSTER_TX_OFF : CLUSTER_RX_OFF, tdm_mask, tdm_slots, params_channels(params), - tdm_slot_width, is_tx, port); + tdm_slot_width, is_tx, portmask); if (ret) return ret; From 04ac1eebc2cbc8932f01b11dd610efb0d0a9b72c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20Povi=C5=A1er?= Date: Tue, 24 Jan 2023 15:22:40 +0100 Subject: [PATCH 0120/3902] ASoC: tas2764: Configure zeroing of SDOUT slots MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The codec has an option to zero out certain TDM slots on its SDOUT output according to a preconfigured mask (otherwise the output is, for the duration of unused slots, in a Hi-Z state). Configure this feature based on a mask read from the devicetree. Signed-off-by: Martin Povišer --- sound/soc/codecs/tas2764.c | 23 +++++++++++++++++++++++ sound/soc/codecs/tas2764.h | 11 +++++++++++ 2 files changed, 34 insertions(+) diff --git a/sound/soc/codecs/tas2764.c b/sound/soc/codecs/tas2764.c index 04ff5cc03020e3..5d3849119e7433 100644 --- a/sound/soc/codecs/tas2764.c +++ b/sound/soc/codecs/tas2764.c @@ -41,6 +41,7 @@ struct tas2764_priv { int v_sense_slot; int i_sense_slot; + u32 sdout_zero_mask; bool dac_powered; bool unmuted; @@ -692,6 +693,23 @@ static int tas2764_codec_probe(struct snd_soc_component *component) switch (tas2764->devid) { case DEVID_SN012776: + if (tas2764->sdout_zero_mask) { + for (i = 0; i < 4; i++) { + ret = snd_soc_component_write(component, TAS2764_SDOUT_HIZ_1 + i, + (tas2764->sdout_zero_mask >> (i * 8)) & 0xff); + + if (ret < 0) + return ret; + } + + ret = snd_soc_component_update_bits(component, TAS2764_SDOUT_HIZ_9, + TAS2764_SDOUT_HIZ_9_FORCE_0_EN, + TAS2764_SDOUT_HIZ_9_FORCE_0_EN); + + if (ret < 0) + return ret; + } + ret = snd_soc_component_update_bits(component, TAS2764_PWR_CTRL, TAS2764_PWR_CTRL_BOP_SRC, TAS2764_PWR_CTRL_BOP_SRC); @@ -848,6 +866,11 @@ static int tas2764_parse_dt(struct device *dev, struct tas2764_priv *tas2764) if (ret) tas2764->v_sense_slot = -1; + ret = fwnode_property_read_u32(dev->fwnode, "ti,sdout-force-zero-mask", + &tas2764->sdout_zero_mask); + if (ret) + tas2764->sdout_zero_mask = 0; + return 0; } diff --git a/sound/soc/codecs/tas2764.h b/sound/soc/codecs/tas2764.h index 538290ed3d92ac..4a419c11d4b08e 100644 --- a/sound/soc/codecs/tas2764.h +++ b/sound/soc/codecs/tas2764.h @@ -126,4 +126,15 @@ #define TAS2764_BOP_CFG0 TAS2764_REG(0X0, 0x1d) +#define TAS2764_SDOUT_HIZ_1 TAS2764_REG(0x1, 0x3d) +#define TAS2764_SDOUT_HIZ_2 TAS2764_REG(0x1, 0x3e) +#define TAS2764_SDOUT_HIZ_3 TAS2764_REG(0x1, 0x3f) +#define TAS2764_SDOUT_HIZ_4 TAS2764_REG(0x1, 0x40) +#define TAS2764_SDOUT_HIZ_5 TAS2764_REG(0x1, 0x41) +#define TAS2764_SDOUT_HIZ_6 TAS2764_REG(0x1, 0x42) +#define TAS2764_SDOUT_HIZ_7 TAS2764_REG(0x1, 0x43) +#define TAS2764_SDOUT_HIZ_8 TAS2764_REG(0x1, 0x44) +#define TAS2764_SDOUT_HIZ_9 TAS2764_REG(0x1, 0x45) +#define TAS2764_SDOUT_HIZ_9_FORCE_0_EN BIT(7) + #endif /* __TAS2764__ */ From 1bbc78364e0665c98ec73fc0e25da073d431598a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20Povi=C5=A1er?= Date: Wed, 25 Jan 2023 11:14:05 +0100 Subject: [PATCH 0121/3902] ASoC: tas2764: Crop SDOUT zero-out mask based on BCLK ratio MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Martin Povišer --- sound/soc/codecs/tas2764.c | 39 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 39 insertions(+) diff --git a/sound/soc/codecs/tas2764.c b/sound/soc/codecs/tas2764.c index 5d3849119e7433..2492e6e0447192 100644 --- a/sound/soc/codecs/tas2764.c +++ b/sound/soc/codecs/tas2764.c @@ -351,6 +351,44 @@ static int tas2764_hw_params(struct snd_pcm_substream *substream, return tas2764_set_samplerate(tas2764, params_rate(params)); } +static int tas2764_write_sdout_zero_mask(struct tas2764_priv *tas2764, int bclk_ratio) +{ + struct snd_soc_component *component = tas2764->component; + int nsense_slots = bclk_ratio / 8; + u32 cropped_mask; + int i, ret; + + if (!tas2764->sdout_zero_mask) + return 0; + + cropped_mask = tas2764->sdout_zero_mask & GENMASK(nsense_slots - 1, 0); + + for (i = 0; i < 4; i++) { + ret = snd_soc_component_write(component, TAS2764_SDOUT_HIZ_1 + i, + (cropped_mask >> (i * 8)) & 0xff); + + if (ret < 0) + return ret; + } + + ret = snd_soc_component_update_bits(component, TAS2764_SDOUT_HIZ_9, + TAS2764_SDOUT_HIZ_9_FORCE_0_EN, + TAS2764_SDOUT_HIZ_9_FORCE_0_EN); + + if (ret < 0) + return ret; + + return 0; +} + +static int tas2764_set_bclk_ratio(struct snd_soc_dai *dai, unsigned int ratio) +{ + struct snd_soc_component *component = dai->component; + struct tas2764_priv *tas2764 = snd_soc_component_get_drvdata(component); + + return tas2764_write_sdout_zero_mask(tas2764, ratio); +} + static int tas2764_set_fmt(struct snd_soc_dai *dai, unsigned int fmt) { struct snd_soc_component *component = dai->component; @@ -500,6 +538,7 @@ static int tas2764_set_ivsense_transmit(struct tas2764_priv *tas2764, int i_slot static const struct snd_soc_dai_ops tas2764_dai_ops = { .mute_stream = tas2764_mute, .hw_params = tas2764_hw_params, + .set_bclk_ratio = tas2764_set_bclk_ratio, .set_fmt = tas2764_set_fmt, .set_tdm_slot = tas2764_set_dai_tdm_slot, .no_capture_mute = 1, From e99482f5f617fb583d8f1175452dbbe12a6e0b8c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20Povi=C5=A1er?= Date: Wed, 25 Jan 2023 13:41:42 +0100 Subject: [PATCH 0122/3902] ASoC: macaudio: Remove stale 'speaker_nchans' fields MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Martin Povišer --- sound/soc/apple/macaudio.c | 3 --- 1 file changed, 3 deletions(-) diff --git a/sound/soc/apple/macaudio.c b/sound/soc/apple/macaudio.c index 69032a6ee13ee6..3a9e97fd3330dd 100644 --- a/sound/soc/apple/macaudio.c +++ b/sound/soc/apple/macaudio.c @@ -73,9 +73,6 @@ struct macaudio_snd_data { bool is_headphones; unsigned int tdm_mask; } *link_props; - - unsigned int speaker_nchans_array[2]; - struct snd_pcm_hw_constraint_list speaker_nchans_list; }; static bool please_blow_up_my_speakers; From c2d744300f6afe9b964c6f969d26c387841cffae Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20Povi=C5=A1er?= Date: Wed, 25 Jan 2023 16:16:13 +0100 Subject: [PATCH 0123/3902] ASoC: macaudio: Add 'Speakers Up Indicator' control MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This control is there for userspace convenience, so that daemons watching I/V sense data know when to open the sense PCM. If they open the PCM without playback in progress, there will be no clocks on the bus and the sense capture PCM will be stuck. Signed-off-by: Martin Povišer --- sound/soc/apple/macaudio.c | 69 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 69 insertions(+) diff --git a/sound/soc/apple/macaudio.c b/sound/soc/apple/macaudio.c index 3a9e97fd3330dd..a0939fa2504393 100644 --- a/sound/soc/apple/macaudio.c +++ b/sound/soc/apple/macaudio.c @@ -73,6 +73,9 @@ struct macaudio_snd_data { bool is_headphones; unsigned int tdm_mask; } *link_props; + + bool speakers_streaming; + struct snd_kcontrol *speakers_streaming_kctl; }; static bool please_blow_up_my_speakers; @@ -566,6 +569,36 @@ static void macaudio_dpcm_shutdown(struct snd_pcm_substream *substream) } } +static int macaudio_be_prepare(struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream); + struct macaudio_snd_data *ma = snd_soc_card_get_drvdata(rtd->card); + struct macaudio_link_props *props = &ma->link_props[rtd->dai_link->id]; + + if (props->is_speakers) { + ma->speakers_streaming = true; + snd_ctl_notify(ma->card.snd_card, SNDRV_CTL_EVENT_MASK_VALUE, + &ma->speakers_streaming_kctl->id); + } + + return 0; +} + +static int macaudio_be_hw_free(struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream); + struct macaudio_snd_data *ma = snd_soc_card_get_drvdata(rtd->card); + struct macaudio_link_props *props = &ma->link_props[rtd->dai_link->id]; + + if (props->is_speakers) { + ma->speakers_streaming = false; + snd_ctl_notify(ma->card.snd_card, SNDRV_CTL_EVENT_MASK_VALUE, + &ma->speakers_streaming_kctl->id); + } + + return 0; +} + static const struct snd_soc_ops macaudio_fe_ops = { .startup = macaudio_fe_startup, .shutdown = macaudio_dpcm_shutdown, @@ -573,6 +606,8 @@ static const struct snd_soc_ops macaudio_fe_ops = { }; static const struct snd_soc_ops macaudio_be_ops = { + .prepare = macaudio_be_prepare, + .hw_free = macaudio_be_hw_free, .shutdown = macaudio_dpcm_shutdown, .hw_params = macaudio_dpcm_hw_params, }; @@ -803,6 +838,8 @@ static int macaudio_late_probe(struct snd_soc_card *card) } } + ma->speakers_streaming_kctl = snd_soc_card_get_kcontrol(card, "Speakers Up Indicator"); + return 0; } @@ -976,10 +1013,42 @@ static const struct snd_soc_dapm_widget macaudio_snd_widgets[] = { SND_SOC_DAPM_AIF_IN("Speaker Sense Capture", NULL, 0, SND_SOC_NOPM, 0, 0), }; +static int macaudio_sss_info(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo) +{ + uinfo->type = SNDRV_CTL_ELEM_TYPE_BOOLEAN; + uinfo->count = 1; + uinfo->value.integer.min = 0; + uinfo->value.integer.max = 1; + + return 0; +} + +static int macaudio_sss_get(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *uvalue) +{ + struct snd_soc_card *card = snd_kcontrol_chip(kcontrol); + struct macaudio_snd_data *ma = snd_soc_card_get_drvdata(card); + + /* + * TODO: Check if any locking is in order here. I would + * assume there is some ALSA-level lock, but DAPM implementations + * of kcontrol ops do explicit locking, so look into it. + */ + uvalue->value.integer.value[0] = ma->speakers_streaming; + + return 0; +} + static const struct snd_kcontrol_new macaudio_controls[] = { SOC_DAPM_PIN_SWITCH("Speaker"), SOC_DAPM_PIN_SWITCH("Headphone"), SOC_DAPM_PIN_SWITCH("Headset Mic"), + { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .access = SNDRV_CTL_ELEM_ACCESS_READ | + SNDRV_CTL_ELEM_ACCESS_VOLATILE, + .name = "Speakers Up Indicator", + .info = macaudio_sss_info, .get = macaudio_sss_get, + }, }; static const struct snd_soc_dapm_route macaudio_dapm_routes[] = { From bdec724a545f3e9520fc95017011b599a60f521e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20Povi=C5=A1er?= Date: Sun, 5 Feb 2023 22:53:20 +0100 Subject: [PATCH 0124/3902] ASoC: macaudio: Do not disable ISENSE/VSENSE switches on j314 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Martin Povišer --- sound/soc/apple/macaudio.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/sound/soc/apple/macaudio.c b/sound/soc/apple/macaudio.c index a0939fa2504393..fd39d039f414af 100644 --- a/sound/soc/apple/macaudio.c +++ b/sound/soc/apple/macaudio.c @@ -924,8 +924,10 @@ static int macaudio_j314_fixup_controls(struct snd_soc_card *card) * samples from the codecs back to us, disable the * controls. */ +#if 0 CHECK(snd_soc_deactivate_kctl, "* VSENSE Switch", 0); CHECK(snd_soc_deactivate_kctl, "* ISENSE Switch", 0); +#endif } return 0; From 1fb40a87cbd19b1b118594227daae8dbc31cff94 Mon Sep 17 00:00:00 2001 From: Hector Martin Date: Tue, 9 May 2023 19:04:18 +0900 Subject: [PATCH 0125/3902] ASoC: macaudio: Fix PD link double-frees? Signed-off-by: Hector Martin --- sound/soc/apple/mca.c | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/sound/soc/apple/mca.c b/sound/soc/apple/mca.c index 8c1a676a80ba5e..7c2ca8c5bd9a1f 100644 --- a/sound/soc/apple/mca.c +++ b/sound/soc/apple/mca.c @@ -296,6 +296,7 @@ static int mca_fe_enable_clocks(struct mca_cluster *cl) * the power state driver would error out on seeing the device * as clock-gated. */ + WARN_ON(cl->pd_link); cl->pd_link = device_link_add(mca->dev, cl->pd_dev, DL_FLAG_STATELESS | DL_FLAG_PM_RUNTIME | DL_FLAG_RPM_ACTIVE); @@ -319,7 +320,11 @@ static void mca_fe_disable_clocks(struct mca_cluster *cl) mca_modify(cl, REG_SYNCGEN_STATUS, SYNCGEN_STATUS_EN, 0); mca_modify(cl, REG_STATUS, STATUS_MCLK_EN, 0); - device_link_del(cl->pd_link); + if (cl->pd_link) { + device_link_del(cl->pd_link); + cl->pd_link = NULL; + } + clk_disable_unprepare(cl->clk_parent); } @@ -389,6 +394,7 @@ static int mca_fe_prepare(struct snd_pcm_substream *substream, if (!cl->syncgen_in_use) { int port = ffs(mca_fe_get_portmask(substream)) - 1; + WARN_ON(cl->pd_link); cl->pd_link = device_link_add(mca->dev, cl->pd_dev, DL_FLAG_STATELESS | DL_FLAG_PM_RUNTIME | DL_FLAG_RPM_ACTIVE); @@ -421,8 +427,10 @@ static int mca_fe_hw_free(struct snd_pcm_substream *substream, return 0; mca_modify(cl, REG_SYNCGEN_STATUS, SYNCGEN_STATUS_EN, 0); - if (cl->pd_link) + if (cl->pd_link) { device_link_del(cl->pd_link); + cl->pd_link = NULL; + } return 0; } @@ -1108,8 +1116,10 @@ static void apple_mca_release(struct mca_data *mca) dev_pm_domain_detach(cl->pd_dev, true); } - if (mca->pd_link) + if (mca->pd_link) { device_link_del(mca->pd_link); + mca->pd_link = NULL; + } if (!IS_ERR_OR_NULL(mca->pd_dev)) dev_pm_domain_detach(mca->pd_dev, true); From 62149bf8ce7034a0eaf73ccaaf651eb7620e2c24 Mon Sep 17 00:00:00 2001 From: Hector Martin Date: Tue, 9 May 2023 19:05:29 +0900 Subject: [PATCH 0126/3902] ASoC: macaudio: Sense improvements - Export speakers sample rate via mixer control - Sense device open does not force the sample rate - No more timeouts on the sense device Signed-off-by: Hector Martin --- sound/soc/apple/macaudio.c | 83 +++++++++++++++++++++++++------------- 1 file changed, 56 insertions(+), 27 deletions(-) diff --git a/sound/soc/apple/macaudio.c b/sound/soc/apple/macaudio.c index fd39d039f414af..452cb87461569b 100644 --- a/sound/soc/apple/macaudio.c +++ b/sound/soc/apple/macaudio.c @@ -74,8 +74,8 @@ struct macaudio_snd_data { unsigned int tdm_mask; } *link_props; - bool speakers_streaming; - struct snd_kcontrol *speakers_streaming_kctl; + int speaker_sample_rate; + struct snd_kcontrol *speaker_sample_rate_kctl; }; static bool please_blow_up_my_speakers; @@ -483,10 +483,37 @@ static int macaudio_dpcm_hw_params(struct snd_pcm_substream *substream, struct snd_pcm_hw_params *params) { struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream); + struct macaudio_snd_data *ma = snd_soc_card_get_drvdata(rtd->card); + struct macaudio_link_props *props = &ma->link_props[rtd->dai_link->id]; struct snd_soc_dai *cpu_dai = snd_soc_rtd_to_cpu(rtd, 0); + struct snd_interval *rate = hw_param_interval(params, + SNDRV_PCM_HW_PARAM_RATE); int bclk_ratio = macaudio_get_runtime_bclk_ratio(substream); int i; + if (props->is_sense) { + rate->min = rate->max = cpu_dai->symmetric_rate; + return 0; + } + + /* Speakers BE */ + if (props->is_speakers) { + if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) { + /* Sense PCM: keep the existing BE rate (0 if not already running) */ + rate->min = rate->max = cpu_dai->symmetric_rate; + + return 0; + } else { + /* + * Set the sense PCM rate control to inform userspace of the + * new sample rate. + */ + ma->speaker_sample_rate = params_rate(params); + snd_ctl_notify(ma->card.snd_card, SNDRV_CTL_EVENT_MASK_VALUE, + &ma->speaker_sample_rate_kctl->id); + } + } + if (bclk_ratio) { struct snd_soc_dai *dai; int mclk = params_rate(params) * bclk_ratio; @@ -511,8 +538,14 @@ static int macaudio_fe_startup(struct snd_pcm_substream *substream) struct macaudio_link_props *props = &ma->link_props[rtd->dai_link->id]; int max_rate, ret; - if (props->is_sense) + if (props->is_sense) { + /* + * Sense stream will not return data while playback is inactive, + * so do not time out. + */ + substream->wait_time = MAX_SCHEDULE_TIMEOUT; return 0; + } ret = snd_pcm_hw_constraint_minmax(substream->runtime, SNDRV_PCM_HW_PARAM_CHANNELS, @@ -569,31 +602,28 @@ static void macaudio_dpcm_shutdown(struct snd_pcm_substream *substream) } } -static int macaudio_be_prepare(struct snd_pcm_substream *substream) -{ - struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream); - struct macaudio_snd_data *ma = snd_soc_card_get_drvdata(rtd->card); - struct macaudio_link_props *props = &ma->link_props[rtd->dai_link->id]; - - if (props->is_speakers) { - ma->speakers_streaming = true; - snd_ctl_notify(ma->card.snd_card, SNDRV_CTL_EVENT_MASK_VALUE, - &ma->speakers_streaming_kctl->id); - } - - return 0; -} - static int macaudio_be_hw_free(struct snd_pcm_substream *substream) { struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream); struct macaudio_snd_data *ma = snd_soc_card_get_drvdata(rtd->card); struct macaudio_link_props *props = &ma->link_props[rtd->dai_link->id]; + struct snd_soc_dai *dai; + int i; - if (props->is_speakers) { - ma->speakers_streaming = false; + if (props->is_speakers && substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + /* + * Clear the DAI rates, so the next open can change the sample rate. + * This won't happen automatically if the sense PCM is open. + */ + for_each_rtd_dais(rtd, i, dai) { + dai->symmetric_rate = 0; + } + + /* Notify userspace that the speakers are closed */ + ma->speaker_sample_rate = 0; snd_ctl_notify(ma->card.snd_card, SNDRV_CTL_EVENT_MASK_VALUE, - &ma->speakers_streaming_kctl->id); + &ma->speaker_sample_rate_kctl->id); + } return 0; @@ -606,7 +636,6 @@ static const struct snd_soc_ops macaudio_fe_ops = { }; static const struct snd_soc_ops macaudio_be_ops = { - .prepare = macaudio_be_prepare, .hw_free = macaudio_be_hw_free, .shutdown = macaudio_dpcm_shutdown, .hw_params = macaudio_dpcm_hw_params, @@ -838,7 +867,7 @@ static int macaudio_late_probe(struct snd_soc_card *card) } } - ma->speakers_streaming_kctl = snd_soc_card_get_kcontrol(card, "Speakers Up Indicator"); + ma->speaker_sample_rate_kctl = snd_soc_card_get_kcontrol(card, "Speaker Sample Rate"); return 0; } @@ -1017,10 +1046,10 @@ static const struct snd_soc_dapm_widget macaudio_snd_widgets[] = { static int macaudio_sss_info(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo) { - uinfo->type = SNDRV_CTL_ELEM_TYPE_BOOLEAN; + uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; uinfo->count = 1; uinfo->value.integer.min = 0; - uinfo->value.integer.max = 1; + uinfo->value.integer.max = 192000; return 0; } @@ -1035,7 +1064,7 @@ static int macaudio_sss_get(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_v * assume there is some ALSA-level lock, but DAPM implementations * of kcontrol ops do explicit locking, so look into it. */ - uvalue->value.integer.value[0] = ma->speakers_streaming; + uvalue->value.integer.value[0] = ma->speaker_sample_rate; return 0; } @@ -1048,7 +1077,7 @@ static const struct snd_kcontrol_new macaudio_controls[] = { .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .access = SNDRV_CTL_ELEM_ACCESS_READ | SNDRV_CTL_ELEM_ACCESS_VOLATILE, - .name = "Speakers Up Indicator", + .name = "Speaker Sample Rate", .info = macaudio_sss_info, .get = macaudio_sss_get, }, }; From 44168f9909f3baa11efb8ecc9a86d0aaf6ecb660 Mon Sep 17 00:00:00 2001 From: Hector Martin Date: Mon, 9 Oct 2023 22:31:23 +0900 Subject: [PATCH 0127/3902] ASoC: ops: Export snd_soc_control_matches() This helper is useful for drivers that want to do their own control lookups and matching as part of more complex logic than the existing operations. Signed-off-by: Hector Martin --- include/sound/soc.h | 2 ++ sound/soc/soc-ops.c | 9 +++++---- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/include/sound/soc.h b/include/sound/soc.h index 1d59c8dc18761c..d4573212f37230 100644 --- a/include/sound/soc.h +++ b/include/sound/soc.h @@ -561,6 +561,8 @@ int snd_soc_get_volsw_sx(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol); int snd_soc_put_volsw_sx(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol); +bool snd_soc_control_matches(struct snd_kcontrol *kcontrol, + const char *pattern); int snd_soc_limit_volume(struct snd_soc_card *card, const char *name, int max); int snd_soc_deactivate_kctl(struct snd_soc_card *card, diff --git a/sound/soc/soc-ops.c b/sound/soc/soc-ops.c index 5b344dcc89476d..21651a572af8bb 100644 --- a/sound/soc/soc-ops.c +++ b/sound/soc/soc-ops.c @@ -397,7 +397,7 @@ int snd_soc_put_volsw_sx(struct snd_kcontrol *kcontrol, } EXPORT_SYMBOL_GPL(snd_soc_put_volsw_sx); -static bool soc_control_matches(struct snd_kcontrol *kctl, +bool snd_soc_control_matches(struct snd_kcontrol *kctl, const char *pattern) { const char *name = kctl->id.name; @@ -419,6 +419,7 @@ static bool soc_control_matches(struct snd_kcontrol *kctl, return !strcmp(name, pattern); } +EXPORT_SYMBOL_GPL(snd_soc_control_matches); static int snd_soc_clip_to_platform_max(struct snd_kcontrol *kctl) { @@ -486,7 +487,7 @@ int snd_soc_limit_volume(struct snd_soc_card *card, const char *name, int max) return -EINVAL; list_for_each_entry(kctl, &card->snd_card->controls, list) { - if (!soc_control_matches(kctl, name)) + if (!snd_soc_control_matches(kctl, name)) continue; ret = soc_limit_volume(kctl, max); @@ -524,7 +525,7 @@ int snd_soc_deactivate_kctl(struct snd_soc_card *card, return -EINVAL; list_for_each_entry(kctl, &card->snd_card->controls, list) { - if (!soc_control_matches(kctl, name)) + if (!snd_soc_control_matches(kctl, name)) continue; ret = snd_ctl_activate_id(card->snd_card, &kctl->id, active); @@ -594,7 +595,7 @@ int snd_soc_set_enum_kctl(struct snd_soc_card *card, return -EINVAL; list_for_each_entry(kctl, &card->snd_card->controls, list) { - if (!soc_control_matches(kctl, name)) + if (!snd_soc_control_matches(kctl, name)) continue; ret = soc_set_enum_kctl(kctl, value); From 96f9f02430870a5d9ddb257d5120be72dd212785 Mon Sep 17 00:00:00 2001 From: Hector Martin Date: Mon, 9 Oct 2023 23:36:27 +0900 Subject: [PATCH 0128/3902] macaudio: speaker volume safety interlocks Signed-off-by: Hector Martin --- sound/soc/apple/macaudio.c | 389 ++++++++++++++++++++++++++++++++++++- 1 file changed, 379 insertions(+), 10 deletions(-) diff --git a/sound/soc/apple/macaudio.c b/sound/soc/apple/macaudio.c index 452cb87461569b..0c2f919bc39d6f 100644 --- a/sound/soc/apple/macaudio.c +++ b/sound/soc/apple/macaudio.c @@ -55,11 +55,29 @@ */ #define MACAUDIO_MAX_BCLK_FREQ 24576000 +#define SPEAKER_MAGIC_VALUE (s32)0xdec1be15 +/* milliseconds */ +#define SPEAKER_LOCK_TIMEOUT 250 + +#define MAX_LIMITS 6 + +struct macaudio_limit_cfg { + const char *match; + int max_limited; + int max_unlimited; +}; + +struct macaudio_platform_cfg { + struct macaudio_limit_cfg limits[MAX_LIMITS]; + int (*fixup)(struct snd_soc_card *card); +}; + struct macaudio_snd_data { struct snd_soc_card card; struct snd_soc_jack jack; int jack_plugin_state; + const struct macaudio_platform_cfg *cfg; bool has_speakers; unsigned int max_channels; @@ -76,6 +94,18 @@ struct macaudio_snd_data { int speaker_sample_rate; struct snd_kcontrol *speaker_sample_rate_kctl; + + bool speaker_volume_unlocked; + bool speaker_volume_was_locked; + struct snd_kcontrol *speaker_lock_kctl; + struct snd_ctl_file *speaker_lock_owner; + u64 bes_active; + bool speaker_lock_timeout_enabled; + ktime_t speaker_lock_timeout; + ktime_t speaker_lock_remain; + struct delayed_work lock_timeout_work; + struct work_struct lock_update_work; + }; static bool please_blow_up_my_speakers; @@ -165,6 +195,159 @@ static struct macaudio_link_props macaudio_fe_link_props[] = { } }; +static void macaudio_vlimit_unlock(struct macaudio_snd_data *ma, bool unlock) +{ + int i, ret, max; + + for (i = 0; i < ARRAY_SIZE(ma->cfg->limits); i++) { + const struct macaudio_limit_cfg *limit = &ma->cfg->limits[i]; + + if (!limit->match) + break; + + if (unlock) + max = limit->max_unlimited; + else + max = limit->max_limited; + + ret = snd_soc_limit_volume(&ma->card, limit->match, max); + if (ret < 0) + dev_err(ma->card.dev, "Failed to %slock volume %s: %d\n", + unlock ? "un" : "", limit->match, ret); + } +} + +static void macaudio_vlimit_update(struct macaudio_snd_data *ma) +{ + int i; + bool unlock = true; + struct snd_kcontrol *kctl; + const char *reason; + + /* Do nothing if there are no limits configured */ + if (!ma->cfg->limits[0].match) + return; + + /* Check that someone is holding the main lock */ + if (!ma->speaker_lock_owner) { + reason = "Main control not locked"; + unlock = false; + } + + /* Check that the control has been pinged within the timeout */ + if (ma->speaker_lock_remain <= 0) { + reason = "Lock timeout"; + unlock = false; + } + + /* Check that *every* limited control is locked by the same owner */ + list_for_each_entry(kctl, &ma->card.snd_card->controls, list) { + bool is_limit = false; + + for (i = 0; i < ARRAY_SIZE(ma->cfg->limits); i++) { + const struct macaudio_limit_cfg *limit = &ma->cfg->limits[i]; + if (!limit->match) + break; + + is_limit = snd_soc_control_matches(kctl, limit->match); + if (is_limit) + break; + } + + if (!is_limit) + continue; + + for (i = 0; i < kctl->count; i++) { + if (kctl->vd[i].owner != ma->speaker_lock_owner) { + reason = "Not all child controls locked by the same process"; + unlock = false; + } + } + } + + + if (unlock != ma->speaker_volume_unlocked) { + if (unlock) { + dev_info(ma->card.dev, "Speaker volumes unlocked\n"); + } else { + dev_info(ma->card.dev, "Speaker volumes locked: %s\n", reason); + ma->speaker_volume_was_locked = true; + } + + macaudio_vlimit_unlock(ma, unlock); + ma->speaker_volume_unlocked = unlock; + } +} + +static void macaudio_vlimit_enable_timeout(struct macaudio_snd_data *ma) +{ + if (ma->speaker_lock_timeout_enabled) + return; + + down_write(&ma->card.snd_card->controls_rwsem); + + if (ma->speaker_lock_remain > 0) { + ma->speaker_lock_timeout = ktime_add(ktime_get(), ma->speaker_lock_remain); + schedule_delayed_work(&ma->lock_timeout_work, usecs_to_jiffies(ktime_to_us(ma->speaker_lock_remain))); + dev_dbg(ma->card.dev, "Enabling volume limit timeout: %ld us left\n", + (long)ktime_to_us(ma->speaker_lock_remain)); + } + + macaudio_vlimit_update(ma); + + up_write(&ma->card.snd_card->controls_rwsem); + ma->speaker_lock_timeout_enabled = true; +} + +static void macaudio_vlimit_disable_timeout(struct macaudio_snd_data *ma) +{ + ktime_t now = ktime_get(); + + if (!ma->speaker_lock_timeout_enabled) + return; + + down_write(&ma->card.snd_card->controls_rwsem); + + cancel_delayed_work(&ma->lock_timeout_work); + + if (ktime_after(now, ma->speaker_lock_timeout)) + ma->speaker_lock_remain = 0; + else if (ma->speaker_lock_remain > 0) + ma->speaker_lock_remain = ktime_sub(ma->speaker_lock_timeout, now); + + dev_dbg(ma->card.dev, "Disabling volume limit timeout: %ld us left\n", + (long)ktime_to_us(ma->speaker_lock_remain)); + + macaudio_vlimit_update(ma); + + up_write(&ma->card.snd_card->controls_rwsem); + ma->speaker_lock_timeout_enabled = false; +} + +static void macaudio_vlimit_timeout_work(struct work_struct *wrk) +{ + struct macaudio_snd_data *ma = container_of(to_delayed_work(wrk), + struct macaudio_snd_data, lock_timeout_work); + + down_write(&ma->card.snd_card->controls_rwsem); + + ma->speaker_lock_remain = 0; + macaudio_vlimit_update(ma); + + up_write(&ma->card.snd_card->controls_rwsem); +} + +static void macaudio_vlimit_update_work(struct work_struct *wrk) +{ + struct macaudio_snd_data *ma = container_of(wrk, + struct macaudio_snd_data, lock_update_work); + + if (ma->bes_active) + macaudio_vlimit_enable_timeout(ma); + else + macaudio_vlimit_disable_timeout(ma); +} + static int macaudio_copy_link(struct device *dev, struct snd_soc_dai_link *target, struct snd_soc_dai_link *source) { @@ -623,7 +806,34 @@ static int macaudio_be_hw_free(struct snd_pcm_substream *substream) ma->speaker_sample_rate = 0; snd_ctl_notify(ma->card.snd_card, SNDRV_CTL_EVENT_MASK_VALUE, &ma->speaker_sample_rate_kctl->id); + } + + return 0; +} + +static int macaudio_be_trigger(struct snd_pcm_substream *substream, int cmd) +{ + struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream); + struct macaudio_snd_data *ma = snd_soc_card_get_drvdata(rtd->card); + struct macaudio_link_props *props = &ma->link_props[rtd->dai_link->id]; + + if (props->is_speakers && substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + case SNDRV_PCM_TRIGGER_RESUME: + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + ma->bes_active |= BIT(rtd->dai_link->id); + break; + case SNDRV_PCM_TRIGGER_SUSPEND: + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + case SNDRV_PCM_TRIGGER_STOP: + ma->bes_active &= ~BIT(rtd->dai_link->id); + break; + default: + return -EINVAL; + } + schedule_work(&ma->lock_update_work); } return 0; @@ -639,6 +849,7 @@ static const struct snd_soc_ops macaudio_be_ops = { .hw_free = macaudio_be_hw_free, .shutdown = macaudio_dpcm_shutdown, .hw_params = macaudio_dpcm_hw_params, + .trigger = macaudio_be_trigger, }; static int macaudio_be_assign_tdm(struct snd_soc_pcm_runtime *rtd) @@ -868,10 +1079,14 @@ static int macaudio_late_probe(struct snd_soc_card *card) } ma->speaker_sample_rate_kctl = snd_soc_card_get_kcontrol(card, "Speaker Sample Rate"); + ma->speaker_lock_kctl = snd_soc_card_get_kcontrol(card, "Speaker Volume Unlock"); return 0; } +#define TAS2764_0DB 201 +#define TAS2764_DB_REDUCTION(x) (TAS2764_0DB - 2 * (x)) + #define CHECK(call, pattern, value) \ { \ int ret = call(card, pattern, value); \ @@ -882,7 +1097,6 @@ static int macaudio_late_probe(struct snd_soc_card *card) dev_dbg(card->dev, "%s on '%s': %d hits\n", #call, pattern, ret); \ } - static int macaudio_j274_fixup_controls(struct snd_soc_card *card) { struct macaudio_snd_data *ma = snd_soc_card_get_drvdata(card); @@ -894,6 +1108,10 @@ static int macaudio_j274_fixup_controls(struct snd_soc_card *card) return 0; } +struct macaudio_platform_cfg macaudio_j274_cfg = { + .fixup = macaudio_j274_fixup_controls, +}; + static int macaudio_j313_fixup_controls(struct snd_soc_card *card) { struct macaudio_snd_data *ma = snd_soc_card_get_drvdata(card); @@ -919,11 +1137,17 @@ static int macaudio_j313_fixup_controls(struct snd_soc_card *card) { */ CHECK(snd_soc_deactivate_kctl, "* VSENSE Switch", 0); CHECK(snd_soc_deactivate_kctl, "* ISENSE Switch", 0); + + macaudio_vlimit_update(ma); } return 0; } +struct macaudio_platform_cfg macaudio_j313_cfg = { + .fixup = macaudio_j313_fixup_controls, +}; + static int macaudio_j314_fixup_controls(struct snd_soc_card *card) { struct macaudio_snd_data *ma = snd_soc_card_get_drvdata(card); @@ -957,11 +1181,41 @@ static int macaudio_j314_fixup_controls(struct snd_soc_card *card) CHECK(snd_soc_deactivate_kctl, "* VSENSE Switch", 0); CHECK(snd_soc_deactivate_kctl, "* ISENSE Switch", 0); #endif + + macaudio_vlimit_update(ma); } return 0; } + +struct macaudio_platform_cfg macaudio_j314_cfg = { + .fixup = macaudio_j314_fixup_controls, + .limits = { + {.match = "* Tweeter Speaker Volume", TAS2764_DB_REDUCTION(20), TAS2764_0DB}, + {.match = "* Woofer Speaker Volume", TAS2764_DB_REDUCTION(20), TAS2764_0DB}, + } +}; + +struct macaudio_platform_cfg macaudio_j413_cfg = { + .fixup = macaudio_j314_fixup_controls, + .limits = { + /* Min gain: -17.47 dB */ + {.match = "* Tweeter Speaker Volume", TAS2764_DB_REDUCTION(20), TAS2764_0DB}, + /* Min gain: -10.63 dB */ + {.match = "* Woofer Speaker Volume", TAS2764_DB_REDUCTION(14), TAS2764_0DB}, + } +}; + +struct macaudio_platform_cfg macaudio_j415_cfg = { + .fixup = macaudio_j314_fixup_controls, + .limits = { + {.match = "* Tweeter Speaker Volume", TAS2764_DB_REDUCTION(20), TAS2764_0DB}, + {.match = "* Woofer 1 Speaker Volume", TAS2764_DB_REDUCTION(20), TAS2764_0DB}, + {.match = "* Woofer 2 Speaker Volume", TAS2764_DB_REDUCTION(20), TAS2764_0DB}, + } +}; + static int macaudio_j375_fixup_controls(struct snd_soc_card *card) { struct macaudio_snd_data *ma = snd_soc_card_get_drvdata(card); @@ -973,11 +1227,17 @@ static int macaudio_j375_fixup_controls(struct snd_soc_card *card) } CHECK(snd_soc_limit_volume, "* Amp Gain Volume", 14); // 20 set by macOS, this is 3 dB below + + macaudio_vlimit_update(ma); } return 0; } +struct macaudio_platform_cfg macaudio_j375_cfg = { + .fixup = macaudio_j375_fixup_controls, +}; + static int macaudio_j493_fixup_controls(struct snd_soc_card *card) { struct macaudio_snd_data *ma = snd_soc_card_get_drvdata(card); @@ -989,11 +1249,17 @@ static int macaudio_j493_fixup_controls(struct snd_soc_card *card) } CHECK(snd_soc_limit_volume, "* Amp Gain Volume", 9); // 15 set by macOS, this is 3 dB below + + macaudio_vlimit_update(ma); } return 0; } +struct macaudio_platform_cfg macaudio_j493_cfg = { + .fixup = macaudio_j493_fixup_controls +}; + static int macaudio_fallback_fixup_controls(struct snd_soc_card *card) { struct macaudio_snd_data *ma = snd_soc_card_get_drvdata(card); @@ -1006,6 +1272,10 @@ static int macaudio_fallback_fixup_controls(struct snd_soc_card *card) return 0; } +struct macaudio_platform_cfg macaudio_fallback_cfg = { + .fixup = macaudio_fallback_fixup_controls +}; + #undef CHECK static const char * const macaudio_spk_mux_texts[] = { @@ -1069,10 +1339,91 @@ static int macaudio_sss_get(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_v return 0; } +static int macaudio_slk_info(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo) +{ + uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; + uinfo->count = 1; + uinfo->value.integer.min = INT_MIN; + uinfo->value.integer.max = INT_MAX; + + return 0; +} + +static int macaudio_slk_put(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *uvalue) +{ + struct snd_soc_card *card = snd_kcontrol_chip(kcontrol); + struct macaudio_snd_data *ma = snd_soc_card_get_drvdata(card); + + if (!ma->speaker_lock_owner) + return -EPERM; + + if (uvalue->value.integer.value[0] != SPEAKER_MAGIC_VALUE) + return -EINVAL; + + /* Serves as a notification that the lock was lost at some point */ + if (ma->speaker_volume_was_locked) { + ma->speaker_volume_was_locked = false; + return -ETIMEDOUT; + } + + cancel_delayed_work(&ma->lock_timeout_work); + + ma->speaker_lock_remain = ms_to_ktime(SPEAKER_LOCK_TIMEOUT); + ma->speaker_lock_timeout = ktime_add(ktime_get(), ma->speaker_lock_remain); + macaudio_vlimit_update(ma); + + if (ma->speaker_lock_timeout_enabled) { + dev_dbg(ma->card.dev, "Volume limit timeout ping: %ld us left\n", + (long)ktime_to_us(ma->speaker_lock_remain)); + schedule_delayed_work(&ma->lock_timeout_work, usecs_to_jiffies(ktime_to_us(ma->speaker_lock_remain))); + } + + return 0; +} + +int macaudio_slk_lock(struct snd_kcontrol *kcontrol, struct snd_ctl_file *owner) +{ + struct snd_soc_card *card = snd_kcontrol_chip(kcontrol); + struct macaudio_snd_data *ma = snd_soc_card_get_drvdata(card); + + ma->speaker_lock_owner = owner; + macaudio_vlimit_update(ma); + + /* + * Reset the unintended lock flag when the control is first locked. + * At this point the state is locked and cannot be unlocked until + * userspace writes to this control, so this cannot spuriously become + * true again until that point. + */ + ma->speaker_volume_was_locked = false; + + return 0; +} + +static void macaudio_slk_unlock(struct snd_kcontrol *kcontrol) +{ + struct snd_soc_card *card = snd_kcontrol_chip(kcontrol); + struct macaudio_snd_data *ma = snd_soc_card_get_drvdata(card); + + ma->speaker_lock_owner = NULL; + ma->speaker_lock_timeout = 0; + macaudio_vlimit_update(ma); +} + +/* Speaker limit controls go last */ +#define MACAUDIO_NUM_SPEAKER_LIMIT_CONTROLS 2 + static const struct snd_kcontrol_new macaudio_controls[] = { SOC_DAPM_PIN_SWITCH("Speaker"), SOC_DAPM_PIN_SWITCH("Headphone"), SOC_DAPM_PIN_SWITCH("Headset Mic"), + { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .access = SNDRV_CTL_ELEM_ACCESS_WRITE, + .name = "Speaker Volume Unlock", + .info = macaudio_slk_info, .put = macaudio_slk_put, + .lock = macaudio_slk_lock, .unlock = macaudio_slk_unlock, + }, { .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .access = SNDRV_CTL_ELEM_ACCESS_READ | @@ -1103,13 +1454,13 @@ static const struct snd_soc_dapm_route macaudio_dapm_routes[] = { }; static const struct of_device_id macaudio_snd_device_id[] = { - { .compatible = "apple,j274-macaudio", .data = macaudio_j274_fixup_controls }, - { .compatible = "apple,j313-macaudio", .data = macaudio_j313_fixup_controls }, - { .compatible = "apple,j314-macaudio", .data = macaudio_j314_fixup_controls }, - { .compatible = "apple,j375-macaudio", .data = macaudio_j375_fixup_controls }, - { .compatible = "apple,j413-macaudio", .data = macaudio_j314_fixup_controls }, - { .compatible = "apple,j415-macaudio", .data = macaudio_j314_fixup_controls }, - { .compatible = "apple,j493-macaudio", .data = macaudio_j493_fixup_controls }, + { .compatible = "apple,j274-macaudio", .data = &macaudio_j274_cfg }, + { .compatible = "apple,j313-macaudio", .data = &macaudio_j313_cfg }, + { .compatible = "apple,j314-macaudio", .data = &macaudio_j314_cfg }, + { .compatible = "apple,j375-macaudio", .data = &macaudio_j375_cfg }, + { .compatible = "apple,j413-macaudio", .data = &macaudio_j413_cfg }, + { .compatible = "apple,j415-macaudio", .data = &macaudio_j415_cfg }, + { .compatible = "apple,j493-macaudio", .data = &macaudio_j493_cfg }, { .compatible = "apple,macaudio"}, { } }; @@ -1134,6 +1485,7 @@ static int macaudio_snd_platform_probe(struct platform_device *pdev) return -ENOMEM; card = &data->card; snd_soc_card_set_drvdata(card, data); + dev_set_drvdata(&pdev->dev, data); card->owner = THIS_MODULE; card->driver_name = "macaudio"; @@ -1150,9 +1502,15 @@ static int macaudio_snd_platform_probe(struct platform_device *pdev) card->fully_routed = true; if (of_id->data) - card->fixup_controls = of_id->data; + data->cfg = of_id->data; else - card->fixup_controls = macaudio_fallback_fixup_controls; + data->cfg = &macaudio_fallback_cfg; + + /* Remove speaker safety controls if we have no declared limits */ + if (!data->cfg->limits[0].match) + card->num_controls -= MACAUDIO_NUM_SPEAKER_LIMIT_CONTROLS; + + card->fixup_controls = data->cfg->fixup; ret = macaudio_parse_of(data); if (ret) @@ -1169,11 +1527,22 @@ static int macaudio_snd_platform_probe(struct platform_device *pdev) } } + INIT_WORK(&data->lock_update_work, macaudio_vlimit_update_work); + INIT_DELAYED_WORK(&data->lock_timeout_work, macaudio_vlimit_timeout_work); + return devm_snd_soc_register_card(dev, card); } +static void macaudio_snd_platform_remove(struct platform_device *pdev) +{ + struct macaudio_snd_data *ma = dev_get_drvdata(&pdev->dev); + + cancel_delayed_work_sync(&ma->lock_timeout_work); +} + static struct platform_driver macaudio_snd_driver = { .probe = macaudio_snd_platform_probe, + .remove = macaudio_snd_platform_remove, .driver = { .name = DRIVER_NAME, .of_match_table = macaudio_snd_device_id, From 629da7bd448064a80b6a642aecf32a274b515382 Mon Sep 17 00:00:00 2001 From: Hector Martin Date: Thu, 12 Oct 2023 22:53:57 +0900 Subject: [PATCH 0129/3902] alsa: pcm: Remove the qos request only if active Fixes warning: [ 8.502802] ------------[ cut here ]------------ [ 8.503445] cpu_latency_qos_remove_request called for unknown object [ 8.504269] WARNING: CPU: 5 PID: 2790 at kernel/power/qos.c:322 cpu_latency_qos_remove_request+0x48/0x98 [ 8.505499] CPU: 5 PID: 2790 Comm: wireplumber Tainted: G W 6.5.0-asahi-00708-gb9b88240f7ae #2291 [ 8.506777] Hardware name: Apple MacBook Air (13-inch, M2, 2022) (DT) [ 8.519099] Call trace: [ 8.519402] cpu_latency_qos_remove_request+0x48/0x98 [ 8.520027] snd_pcm_ioctl+0x86c/0x182c [ 8.520519] __arm64_sys_ioctl+0xf8/0xbd0 [ 8.521020] invoke_syscall.constprop.0+0x78/0xc8 [ 8.521604] do_el0_svc+0x58/0x154 [ 8.522026] el0_svc+0x34/0xe4 [ 8.522409] el0t_64_sync_handler+0x120/0x12c [ 8.522951] el0t_64_sync+0x190/0x194 [ 8.523408] ---[ end trace 0000000000000000 ]--- Signed-off-by: Hector Martin --- sound/core/pcm_native.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/sound/core/pcm_native.c b/sound/core/pcm_native.c index 68bee40c9adafd..fc4ec6c6356f87 100644 --- a/sound/core/pcm_native.c +++ b/sound/core/pcm_native.c @@ -939,8 +939,9 @@ static int snd_pcm_hw_free(struct snd_pcm_substream *substream) goto unlock; result = do_hw_free(substream); snd_pcm_set_state(substream, SNDRV_PCM_STATE_OPEN); - cpu_latency_qos_remove_request(&substream->latency_pm_qos_req); - unlock: + if (cpu_latency_qos_request_active(&substream->latency_pm_qos_req)) + cpu_latency_qos_remove_request(&substream->latency_pm_qos_req); +unlock: snd_pcm_buffer_access_unlock(runtime); return result; } From 59c5d99dd0d8e11c39c94d347ef78793fcd47eb2 Mon Sep 17 00:00:00 2001 From: Hector Martin Date: Thu, 12 Oct 2023 23:01:12 +0900 Subject: [PATCH 0130/3902] macaudio: Add a getter for the interlock alsamixer/etc really don't like write-only controls... Signed-off-by: Hector Martin --- sound/soc/apple/macaudio.c | 21 ++++++++++++++++++--- 1 file changed, 18 insertions(+), 3 deletions(-) diff --git a/sound/soc/apple/macaudio.c b/sound/soc/apple/macaudio.c index 0c2f919bc39d6f..f3fd3d017b87c9 100644 --- a/sound/soc/apple/macaudio.c +++ b/sound/soc/apple/macaudio.c @@ -276,6 +276,8 @@ static void macaudio_vlimit_update(struct macaudio_snd_data *ma) macaudio_vlimit_unlock(ma, unlock); ma->speaker_volume_unlocked = unlock; + snd_ctl_notify(ma->card.snd_card, SNDRV_CTL_EVENT_MASK_VALUE, + &ma->speaker_lock_kctl->id); } } @@ -1381,7 +1383,17 @@ static int macaudio_slk_put(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_v return 0; } -int macaudio_slk_lock(struct snd_kcontrol *kcontrol, struct snd_ctl_file *owner) +static int macaudio_slk_get(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *uvalue) +{ + struct snd_soc_card *card = snd_kcontrol_chip(kcontrol); + struct macaudio_snd_data *ma = snd_soc_card_get_drvdata(card); + + uvalue->value.integer.value[0] = ma->speaker_volume_unlocked ? 1 : 0; + + return 0; +} + +static int macaudio_slk_lock(struct snd_kcontrol *kcontrol, struct snd_ctl_file *owner) { struct snd_soc_card *card = snd_kcontrol_chip(kcontrol); struct macaudio_snd_data *ma = snd_soc_card_get_drvdata(card); @@ -1419,9 +1431,12 @@ static const struct snd_kcontrol_new macaudio_controls[] = { SOC_DAPM_PIN_SWITCH("Headset Mic"), { .iface = SNDRV_CTL_ELEM_IFACE_MIXER, - .access = SNDRV_CTL_ELEM_ACCESS_WRITE, + .access = SNDRV_CTL_ELEM_ACCESS_READ | + SNDRV_CTL_ELEM_ACCESS_WRITE | + SNDRV_CTL_ELEM_ACCESS_VOLATILE, .name = "Speaker Volume Unlock", - .info = macaudio_slk_info, .put = macaudio_slk_put, + .info = macaudio_slk_info, + .put = macaudio_slk_put, .get = macaudio_slk_get, .lock = macaudio_slk_lock, .unlock = macaudio_slk_unlock, }, { From 07fa5db0ac7412d377774e974c0d666d3f75a991 Mon Sep 17 00:00:00 2001 From: Hector Martin Date: Fri, 13 Oct 2023 01:12:55 +0900 Subject: [PATCH 0131/3902] ASoC: apple: mca: Do not mark clocks in use for non-providers On the speakers PCM, this sequence: 1. Open playback 2. Open sense 3. Close playback 4. Close sense would result in the sense FE being marked as clocks in use at (2), since there is a clock provider (playback FE). Then at (4) this would WARN since there is no driver any more when closing the in use clocks. If (1) and (2) are reversed this does not happen, since the sense PCM is not marked as using the clocks when there is no provider yet. So, check explicitly whether the substream FE is a clock provider in be_prepare, and skip everything if it isn't. Signed-off-by: Hector Martin --- sound/soc/apple/mca.c | 67 ++++++++++++++++++++++++------------------- 1 file changed, 37 insertions(+), 30 deletions(-) diff --git a/sound/soc/apple/mca.c b/sound/soc/apple/mca.c index 7c2ca8c5bd9a1f..5763c6e6869243 100644 --- a/sound/soc/apple/mca.c +++ b/sound/soc/apple/mca.c @@ -352,36 +352,6 @@ static bool mca_fe_clocks_in_use(struct mca_cluster *cl) return false; } -static int mca_be_prepare(struct snd_pcm_substream *substream, - struct snd_soc_dai *dai) -{ - struct mca_cluster *cl = mca_dai_to_cluster(dai); - struct mca_data *mca = cl->host; - struct mca_cluster *fe_cl; - int ret; - - if (cl->port_clk_driver < 0) - return 0; - - fe_cl = &mca->clusters[cl->port_clk_driver]; - - /* - * Typically the CODECs we are paired with will require clocks - * to be present at time of unmute with the 'mute_stream' op - * or at time of DAPM widget power-up. We need to enable clocks - * here at the latest (frontend prepare would be too late). - */ - if (!mca_fe_clocks_in_use(fe_cl)) { - ret = mca_fe_enable_clocks(fe_cl); - if (ret < 0) - return ret; - } - - cl->clocks_in_use[substream->stream] = true; - - return 0; -} - static int mca_fe_prepare(struct snd_pcm_substream *substream, struct snd_soc_dai *dai) { @@ -787,6 +757,43 @@ static struct snd_soc_pcm_runtime *mca_be_get_fe(struct snd_soc_pcm_runtime *be, return fe; } +static int mca_be_prepare(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct snd_soc_pcm_runtime *be = snd_soc_substream_to_rtd(substream); + struct snd_soc_pcm_runtime *fe = mca_be_get_fe(be, substream->stream); + struct mca_cluster *cl = mca_dai_to_cluster(dai); + struct mca_data *mca = cl->host; + struct mca_cluster *fe_cl, *fe_clk_cl; + int ret; + + fe_cl = mca_dai_to_cluster(snd_soc_rtd_to_cpu(fe, 0)); + + if (!fe_cl->clk_provider) + return 0; + + if (cl->port_clk_driver < 0) + return 0; + + fe_clk_cl = &mca->clusters[cl->port_clk_driver]; + + /* + * Typically the CODECs we are paired with will require clocks + * to be present at time of unmute with the 'mute_stream' op + * or at time of DAPM widget power-up. We need to enable clocks + * here at the latest (frontend prepare would be too late). + */ + if (!mca_fe_clocks_in_use(fe_clk_cl)) { + ret = mca_fe_enable_clocks(fe_clk_cl); + if (ret < 0) + return ret; + } + + cl->clocks_in_use[substream->stream] = true; + + return 0; +} + static int mca_be_startup(struct snd_pcm_substream *substream, struct snd_soc_dai *dai) { From 9fe9a85fed1ca3f10ee1b2bd7b98e8930baa0ec4 Mon Sep 17 00:00:00 2001 From: Hector Martin Date: Fri, 13 Oct 2023 02:31:55 +0900 Subject: [PATCH 0132/3902] macaudio: Allow DT enabled speakers and gate them off in the driver For machines where we do not consider things safe yet, require the commandline argument. Without it, speakers are simply disabled, we don't refuse probe entirely. Signed-off-by: Hector Martin --- sound/soc/apple/macaudio.c | 37 +++++++++++++++---------------------- 1 file changed, 15 insertions(+), 22 deletions(-) diff --git a/sound/soc/apple/macaudio.c b/sound/soc/apple/macaudio.c index f3fd3d017b87c9..cea7368a54433a 100644 --- a/sound/soc/apple/macaudio.c +++ b/sound/soc/apple/macaudio.c @@ -70,6 +70,7 @@ struct macaudio_limit_cfg { struct macaudio_platform_cfg { struct macaudio_limit_cfg limits[MAX_LIMITS]; int (*fixup)(struct snd_soc_card *card); + bool enable_speakers; }; struct macaudio_snd_data { @@ -487,7 +488,6 @@ static int macaudio_parse_of(struct macaudio_snd_data *ma) if (!card->dai_link || !ma->link_props) return -ENOMEM; - card->num_links = num_links; link = card->dai_link; link_props = ma->link_props; @@ -503,6 +503,9 @@ static int macaudio_parse_of(struct macaudio_snd_data *ma) for (i = 0; i < num_links; i++) card->dai_link[i].id = i; + /* We might disable the speakers, so count again */ + num_links = ARRAY_SIZE(macaudio_fe_links); + /* Fill in the BEs */ for_each_available_child_of_node(dev->of_node, np) { const char *link_name; @@ -520,8 +523,13 @@ static int macaudio_parse_of(struct macaudio_snd_data *ma) speakers = !strcmp(link_name, "Speaker") || !strcmp(link_name, "Speakers"); - if (speakers) + if (speakers) { + if (!ma->cfg->enable_speakers && !please_blow_up_my_speakers) { + dev_err(card->dev, "driver can't assure safety on this model, disabling speakers\n"); + continue; + } ma->has_speakers = 1; + } cpu = of_get_child_by_name(np, "cpu"); codec = of_get_child_by_name(np, "codec"); @@ -615,11 +623,15 @@ static int macaudio_parse_of(struct macaudio_snd_data *ma) of_node_put(codec); of_node_put(cpu); cpu = codec = NULL; + + num_links += num_bes; } for (i = 0; i < ARRAY_SIZE(macaudio_fe_links); i++) card->dai_link[i].platforms->of_node = platform; + card->num_links = num_links; + return 0; err_free: @@ -1112,17 +1124,13 @@ static int macaudio_j274_fixup_controls(struct snd_soc_card *card) struct macaudio_platform_cfg macaudio_j274_cfg = { .fixup = macaudio_j274_fixup_controls, + .enable_speakers = true, }; static int macaudio_j313_fixup_controls(struct snd_soc_card *card) { struct macaudio_snd_data *ma = snd_soc_card_get_drvdata(card); if (ma->has_speakers) { - if (!please_blow_up_my_speakers) { - dev_err(card->dev, "driver can't assure safety on this model, refusing probe\n"); - return -EINVAL; - } - CHECK(snd_soc_set_enum_kctl, "* ASI1 Sel", "Left"); CHECK(snd_soc_deactivate_kctl, "* ASI1 Sel", 0); @@ -1155,11 +1163,6 @@ static int macaudio_j314_fixup_controls(struct snd_soc_card *card) struct macaudio_snd_data *ma = snd_soc_card_get_drvdata(card); if (ma->has_speakers) { - if (!please_blow_up_my_speakers) { - dev_err(card->dev, "driver can't assure safety on this model, refusing probe\n"); - return -EINVAL; - } - CHECK(snd_soc_set_enum_kctl, "* ASI1 Sel", "Left"); CHECK(snd_soc_deactivate_kctl, "* ASI1 Sel", 0); CHECK(snd_soc_limit_volume, "* Amp Gain Volume", 9); // 15 set by macOS, this is 3 dB below @@ -1223,11 +1226,6 @@ static int macaudio_j375_fixup_controls(struct snd_soc_card *card) struct macaudio_snd_data *ma = snd_soc_card_get_drvdata(card); if (ma->has_speakers) { - if (!please_blow_up_my_speakers) { - dev_err(card->dev, "driver can't assure safety on this model, refusing probe\n"); - return -EINVAL; - } - CHECK(snd_soc_limit_volume, "* Amp Gain Volume", 14); // 20 set by macOS, this is 3 dB below macaudio_vlimit_update(ma); @@ -1245,11 +1243,6 @@ static int macaudio_j493_fixup_controls(struct snd_soc_card *card) struct macaudio_snd_data *ma = snd_soc_card_get_drvdata(card); if (ma->has_speakers) { - if (!please_blow_up_my_speakers) { - dev_err(card->dev, "driver can't assure safety on this model, refusing probe\n"); - return -EINVAL; - } - CHECK(snd_soc_limit_volume, "* Amp Gain Volume", 9); // 15 set by macOS, this is 3 dB below macaudio_vlimit_update(ma); From be7f32bc3567d1a2901d3b7fe2564f3827cb29e9 Mon Sep 17 00:00:00 2001 From: Hector Martin Date: Fri, 13 Oct 2023 02:43:12 +0900 Subject: [PATCH 0133/3902] macaudio: Enable VSENSE switches Signed-off-by: Hector Martin --- sound/soc/apple/macaudio.c | 20 -------------------- 1 file changed, 20 deletions(-) diff --git a/sound/soc/apple/macaudio.c b/sound/soc/apple/macaudio.c index cea7368a54433a..77f71c7478370d 100644 --- a/sound/soc/apple/macaudio.c +++ b/sound/soc/apple/macaudio.c @@ -1139,15 +1139,6 @@ static int macaudio_j313_fixup_controls(struct snd_soc_card *card) { */ CHECK(snd_soc_limit_volume, "* Amp Gain Volume", 14); - /* - * Since we don't set the right slots yet to avoid - * driver conflict on the I2S bus sending ISENSE/VSENSE - * samples from the codecs back to us, disable the - * controls. - */ - CHECK(snd_soc_deactivate_kctl, "* VSENSE Switch", 0); - CHECK(snd_soc_deactivate_kctl, "* ISENSE Switch", 0); - macaudio_vlimit_update(ma); } @@ -1176,17 +1167,6 @@ static int macaudio_j314_fixup_controls(struct snd_soc_card *card) CHECK(snd_soc_set_enum_kctl, "* OCE Handling", "Retry"); CHECK(snd_soc_deactivate_kctl, "* OCE Handling", 0); - /* - * Since we don't set the right slots yet to avoid - * driver conflict on the I2S bus sending ISENSE/VSENSE - * samples from the codecs back to us, disable the - * controls. - */ -#if 0 - CHECK(snd_soc_deactivate_kctl, "* VSENSE Switch", 0); - CHECK(snd_soc_deactivate_kctl, "* ISENSE Switch", 0); -#endif - macaudio_vlimit_update(ma); } From 8b532538226f9da803737538c0f5d26689ada17b Mon Sep 17 00:00:00 2001 From: Hector Martin Date: Fri, 13 Oct 2023 06:22:24 +0900 Subject: [PATCH 0134/3902] macaudio: Initialize speaker lock properly Signed-off-by: Hector Martin --- sound/soc/apple/macaudio.c | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/sound/soc/apple/macaudio.c b/sound/soc/apple/macaudio.c index 77f71c7478370d..130f0228d9f614 100644 --- a/sound/soc/apple/macaudio.c +++ b/sound/soc/apple/macaudio.c @@ -1095,6 +1095,8 @@ static int macaudio_late_probe(struct snd_soc_card *card) ma->speaker_sample_rate_kctl = snd_soc_card_get_kcontrol(card, "Speaker Sample Rate"); ma->speaker_lock_kctl = snd_soc_card_get_kcontrol(card, "Speaker Volume Unlock"); + macaudio_vlimit_unlock(ma, false); + return 0; } @@ -1138,8 +1140,6 @@ static int macaudio_j313_fixup_controls(struct snd_soc_card *card) { * what macOS sets. */ CHECK(snd_soc_limit_volume, "* Amp Gain Volume", 14); - - macaudio_vlimit_update(ma); } return 0; @@ -1166,8 +1166,6 @@ static int macaudio_j314_fixup_controls(struct snd_soc_card *card) */ CHECK(snd_soc_set_enum_kctl, "* OCE Handling", "Retry"); CHECK(snd_soc_deactivate_kctl, "* OCE Handling", 0); - - macaudio_vlimit_update(ma); } return 0; @@ -1207,8 +1205,6 @@ static int macaudio_j375_fixup_controls(struct snd_soc_card *card) if (ma->has_speakers) { CHECK(snd_soc_limit_volume, "* Amp Gain Volume", 14); // 20 set by macOS, this is 3 dB below - - macaudio_vlimit_update(ma); } return 0; @@ -1224,8 +1220,6 @@ static int macaudio_j493_fixup_controls(struct snd_soc_card *card) if (ma->has_speakers) { CHECK(snd_soc_limit_volume, "* Amp Gain Volume", 9); // 15 set by macOS, this is 3 dB below - - macaudio_vlimit_update(ma); } return 0; From 35a357b3255ae225f02ba3f17055a768a007b710 Mon Sep 17 00:00:00 2001 From: Hector Martin Date: Fri, 13 Oct 2023 16:57:46 +0900 Subject: [PATCH 0135/3902] macaudio: Use the same volume limit for all amps These are unintentionally aliased. Pending a solution for this, let's just use the same limit for now. Signed-off-by: Hector Martin --- sound/soc/apple/macaudio.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/sound/soc/apple/macaudio.c b/sound/soc/apple/macaudio.c index 130f0228d9f614..4789e180d8f353 100644 --- a/sound/soc/apple/macaudio.c +++ b/sound/soc/apple/macaudio.c @@ -1186,7 +1186,8 @@ struct macaudio_platform_cfg macaudio_j413_cfg = { /* Min gain: -17.47 dB */ {.match = "* Tweeter Speaker Volume", TAS2764_DB_REDUCTION(20), TAS2764_0DB}, /* Min gain: -10.63 dB */ - {.match = "* Woofer Speaker Volume", TAS2764_DB_REDUCTION(14), TAS2764_0DB}, + /* FIXME: These structures are aliased so we can't set different max volumes */ + {.match = "* Woofer Speaker Volume", TAS2764_DB_REDUCTION(20), TAS2764_0DB}, } }; From d0159719e3bfab052a3147419a40855589b2748f Mon Sep 17 00:00:00 2001 From: Hector Martin Date: Fri, 13 Oct 2023 23:39:15 +0900 Subject: [PATCH 0136/3902] macaudio: Disable debug Signed-off-by: Hector Martin --- sound/soc/apple/macaudio.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sound/soc/apple/macaudio.c b/sound/soc/apple/macaudio.c index 4789e180d8f353..fce6a77f0e264c 100644 --- a/sound/soc/apple/macaudio.c +++ b/sound/soc/apple/macaudio.c @@ -20,7 +20,7 @@ * reparenting of live BEs.) */ -#define DEBUG +/* #define DEBUG */ #include #include From d4b119b3e6556d071130723291cee53aea51bd33 Mon Sep 17 00:00:00 2001 From: Hector Martin Date: Sat, 21 Oct 2023 22:16:32 +0900 Subject: [PATCH 0137/3902] ASoC: tas2764: Add SDZ regulator Multiple amps can be connected to the same SDZ GPIO. Using raw GPIOs for this breaks, as there is no concept of refcounting/sharing. In order to model these platforms, introduce support for an SDZ "regulator". This allows us to represent the SDZ GPIO as a simple regulator-fixed, and then the regulator core takes care of refcounting so that all codecs are only powered down once all the driver instances are in the suspend state. Signed-off-by: Hector Martin --- sound/soc/codecs/tas2764.c | 44 ++++++++++++++++++++++++++++++++------ 1 file changed, 37 insertions(+), 7 deletions(-) diff --git a/sound/soc/codecs/tas2764.c b/sound/soc/codecs/tas2764.c index 2492e6e0447192..a458458128aa89 100644 --- a/sound/soc/codecs/tas2764.c +++ b/sound/soc/codecs/tas2764.c @@ -34,6 +34,7 @@ struct tas2764_priv { struct snd_soc_component *component; struct gpio_desc *reset_gpio; struct gpio_desc *sdz_gpio; + struct regulator *sdz_reg; struct regmap *regmap; struct device *dev; int irq; @@ -153,6 +154,8 @@ static int tas2764_codec_suspend(struct snd_soc_component *component) if (tas2764->sdz_gpio) gpiod_set_value_cansleep(tas2764->sdz_gpio, 0); + regulator_disable(tas2764->sdz_reg); + regcache_cache_only(tas2764->regmap, true); regcache_mark_dirty(tas2764->regmap); @@ -166,19 +169,26 @@ static int tas2764_codec_resume(struct snd_soc_component *component) struct tas2764_priv *tas2764 = snd_soc_component_get_drvdata(component); int ret; + ret = regulator_enable(tas2764->sdz_reg); + + if (ret) { + dev_err(tas2764->dev, "Failed to enable regulator\n"); + return ret; + } + if (tas2764->sdz_gpio) { gpiod_set_value_cansleep(tas2764->sdz_gpio, 1); - usleep_range(1000, 2000); } - ret = tas2764_update_pwr_ctrl(tas2764); + usleep_range(1000, 2000); + + regcache_cache_only(tas2764->regmap, false); + ret = regcache_sync(tas2764->regmap); if (ret < 0) return ret; - regcache_cache_only(tas2764->regmap, false); - - return regcache_sync(tas2764->regmap); + return tas2764_update_pwr_ctrl(tas2764); } #else #define tas2764_codec_suspend NULL @@ -211,7 +221,7 @@ static const struct snd_soc_dapm_widget tas2764_dapm_widgets[] = { SND_SOC_DAPM_DAC("DAC", NULL, SND_SOC_NOPM, 0, 0), SND_SOC_DAPM_OUTPUT("OUT"), SND_SOC_DAPM_SIGGEN("VMON"), - SND_SOC_DAPM_SIGGEN("IMON") + SND_SOC_DAPM_SIGGEN("IMON"), }; static const struct snd_soc_dapm_route tas2764_audio_map[] = { @@ -686,11 +696,18 @@ static int tas2764_codec_probe(struct snd_soc_component *component) tas2764->component = component; + ret = regulator_enable(tas2764->sdz_reg); + if (ret != 0) { + dev_err(tas2764->dev, "Failed to enable regulator: %d\n", ret); + return ret; + } + if (tas2764->sdz_gpio) { gpiod_set_value_cansleep(tas2764->sdz_gpio, 1); - usleep_range(1000, 2000); } + usleep_range(1000, 2000); + tas2764_reset(tas2764); regmap_reinit_cache(tas2764->regmap, &tas2764_i2c_regmap); @@ -778,6 +795,13 @@ static int tas2764_codec_probe(struct snd_soc_component *component) return 0; } +static void tas2764_codec_remove(struct snd_soc_component *component) +{ + struct tas2764_priv *tas2764 = snd_soc_component_get_drvdata(component); + + regulator_disable(tas2764->sdz_reg); +} + static DECLARE_TLV_DB_SCALE(tas2764_digital_tlv, 1100, 50, 0); static DECLARE_TLV_DB_SCALE(tas2764_playback_volume, -10050, 50, 1); @@ -809,6 +833,7 @@ static const struct snd_kcontrol_new tas2764_snd_controls[] = { static const struct snd_soc_component_driver soc_component_driver_tas2764 = { .probe = tas2764_codec_probe, + .remove = tas2764_codec_remove, .suspend = tas2764_codec_suspend, .resume = tas2764_codec_resume, .controls = tas2764_snd_controls, @@ -878,6 +903,11 @@ static int tas2764_parse_dt(struct device *dev, struct tas2764_priv *tas2764) { int ret = 0; + tas2764->sdz_reg = devm_regulator_get(dev, "SDZ"); + if (IS_ERR(tas2764->sdz_reg)) + return dev_err_probe(dev, PTR_ERR(tas2764->sdz_reg), + "Failed to get SDZ supply\n"); + tas2764->reset_gpio = devm_gpiod_get_optional(tas2764->dev, "reset", GPIOD_OUT_HIGH); if (IS_ERR(tas2764->reset_gpio)) { From e64bb3e71e78c41e166123e6f862e4c8cf1e833a Mon Sep 17 00:00:00 2001 From: Hector Martin Date: Sat, 21 Oct 2023 22:38:36 +0900 Subject: [PATCH 0138/3902] macaudio: Use an explicit mutex for the speaker volume lock Otherwise we can end up recursively locking the controls lock in the start/stop path, since it can be called from a control change. Signed-off-by: Hector Martin --- sound/soc/apple/macaudio.c | 38 ++++++++++++++++++++++++++++---------- 1 file changed, 28 insertions(+), 10 deletions(-) diff --git a/sound/soc/apple/macaudio.c b/sound/soc/apple/macaudio.c index fce6a77f0e264c..74aa31b438ffed 100644 --- a/sound/soc/apple/macaudio.c +++ b/sound/soc/apple/macaudio.c @@ -96,6 +96,7 @@ struct macaudio_snd_data { int speaker_sample_rate; struct snd_kcontrol *speaker_sample_rate_kctl; + struct mutex volume_lock_mutex; bool speaker_volume_unlocked; bool speaker_volume_was_locked; struct snd_kcontrol *speaker_lock_kctl; @@ -284,10 +285,12 @@ static void macaudio_vlimit_update(struct macaudio_snd_data *ma) static void macaudio_vlimit_enable_timeout(struct macaudio_snd_data *ma) { - if (ma->speaker_lock_timeout_enabled) - return; + mutex_lock(&ma->volume_lock_mutex); - down_write(&ma->card.snd_card->controls_rwsem); + if (ma->speaker_lock_timeout_enabled) { + mutex_unlock(&ma->volume_lock_mutex); + return; + } if (ma->speaker_lock_remain > 0) { ma->speaker_lock_timeout = ktime_add(ktime_get(), ma->speaker_lock_remain); @@ -298,18 +301,22 @@ static void macaudio_vlimit_enable_timeout(struct macaudio_snd_data *ma) macaudio_vlimit_update(ma); - up_write(&ma->card.snd_card->controls_rwsem); ma->speaker_lock_timeout_enabled = true; + mutex_unlock(&ma->volume_lock_mutex); } static void macaudio_vlimit_disable_timeout(struct macaudio_snd_data *ma) { - ktime_t now = ktime_get(); + ktime_t now; + + mutex_lock(&ma->volume_lock_mutex); - if (!ma->speaker_lock_timeout_enabled) + if (!ma->speaker_lock_timeout_enabled) { + mutex_unlock(&ma->volume_lock_mutex); return; + } - down_write(&ma->card.snd_card->controls_rwsem); + now = ktime_get(); cancel_delayed_work(&ma->lock_timeout_work); @@ -323,8 +330,9 @@ static void macaudio_vlimit_disable_timeout(struct macaudio_snd_data *ma) macaudio_vlimit_update(ma); - up_write(&ma->card.snd_card->controls_rwsem); ma->speaker_lock_timeout_enabled = false; + + mutex_unlock(&ma->volume_lock_mutex); } static void macaudio_vlimit_timeout_work(struct work_struct *wrk) @@ -332,12 +340,12 @@ static void macaudio_vlimit_timeout_work(struct work_struct *wrk) struct macaudio_snd_data *ma = container_of(to_delayed_work(wrk), struct macaudio_snd_data, lock_timeout_work); - down_write(&ma->card.snd_card->controls_rwsem); + mutex_lock(&ma->volume_lock_mutex); ma->speaker_lock_remain = 0; macaudio_vlimit_update(ma); - up_write(&ma->card.snd_card->controls_rwsem); + mutex_unlock(&ma->volume_lock_mutex); } static void macaudio_vlimit_update_work(struct work_struct *wrk) @@ -1095,7 +1103,9 @@ static int macaudio_late_probe(struct snd_soc_card *card) ma->speaker_sample_rate_kctl = snd_soc_card_get_kcontrol(card, "Speaker Sample Rate"); ma->speaker_lock_kctl = snd_soc_card_get_kcontrol(card, "Speaker Volume Unlock"); + mutex_lock(&ma->volume_lock_mutex); macaudio_vlimit_unlock(ma, false); + mutex_unlock(&ma->volume_lock_mutex); return 0; } @@ -1336,6 +1346,8 @@ static int macaudio_slk_put(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_v return -ETIMEDOUT; } + mutex_lock(&ma->volume_lock_mutex); + cancel_delayed_work(&ma->lock_timeout_work); ma->speaker_lock_remain = ms_to_ktime(SPEAKER_LOCK_TIMEOUT); @@ -1348,6 +1360,8 @@ static int macaudio_slk_put(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_v schedule_delayed_work(&ma->lock_timeout_work, usecs_to_jiffies(ktime_to_us(ma->speaker_lock_remain))); } + mutex_unlock(&ma->volume_lock_mutex); + return 0; } @@ -1366,6 +1380,7 @@ static int macaudio_slk_lock(struct snd_kcontrol *kcontrol, struct snd_ctl_file struct snd_soc_card *card = snd_kcontrol_chip(kcontrol); struct macaudio_snd_data *ma = snd_soc_card_get_drvdata(card); + mutex_lock(&ma->volume_lock_mutex); ma->speaker_lock_owner = owner; macaudio_vlimit_update(ma); @@ -1377,6 +1392,8 @@ static int macaudio_slk_lock(struct snd_kcontrol *kcontrol, struct snd_ctl_file */ ma->speaker_volume_was_locked = false; + mutex_unlock(&ma->volume_lock_mutex); + return 0; } @@ -1469,6 +1486,7 @@ static int macaudio_snd_platform_probe(struct platform_device *pdev) card = &data->card; snd_soc_card_set_drvdata(card, data); dev_set_drvdata(&pdev->dev, data); + mutex_init(&data->volume_lock_mutex); card->owner = THIS_MODULE; card->driver_name = "macaudio"; From a1d425b95261e275b535965dbb82015ed5e4488d Mon Sep 17 00:00:00 2001 From: Hector Martin Date: Sun, 22 Oct 2023 07:07:40 +0900 Subject: [PATCH 0139/3902] ASoC: apple: mca: Increase reset timeout Saw this fail once, let's be safer. Signed-off-by: Hector Martin --- sound/soc/apple/mca.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/sound/soc/apple/mca.c b/sound/soc/apple/mca.c index 5763c6e6869243..8b853079c74aae 100644 --- a/sound/soc/apple/mca.c +++ b/sound/soc/apple/mca.c @@ -216,9 +216,9 @@ static void mca_fe_early_trigger(struct snd_pcm_substream *substream, int cmd, SERDES_STATUS_RST); /* * Experiments suggest that it takes at most ~1 us - * for the bit to clear, so wait 2 us for good measure. + * for the bit to clear, so wait 5 us for good measure. */ - udelay(2); + udelay(5); WARN_ON(readl_relaxed(cl->base + serdes_unit + REG_SERDES_STATUS) & SERDES_STATUS_RST); mca_modify(cl, serdes_conf, SERDES_CONF_SYNC_SEL, From e6b303cc0e95bf1e77ba4b35178193ff0fc2b61b Mon Sep 17 00:00:00 2001 From: Hector Martin Date: Sun, 22 Oct 2023 08:24:10 +0900 Subject: [PATCH 0140/3902] ALSA: dmaengine: Always terminate DMA when a PCM is closed When a PCM is suspended, we pause the DMA. If the PCM is then closed while in this state, it does not receive the STOP trigger (as it is not running). In this case, we fail to properly terminate the DMA, calling dmaengine_synchronize() nonetheless, which is undefined behavior. Make sure we always call dmaengine_terminate_async() on PCM close, regardless of whether it has been called previously or not in the trigger callbacks. Signed-off-by: Hector Martin --- sound/core/pcm_dmaengine.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/sound/core/pcm_dmaengine.c b/sound/core/pcm_dmaengine.c index f0c17503df425d..931f31bb47c2d7 100644 --- a/sound/core/pcm_dmaengine.c +++ b/sound/core/pcm_dmaengine.c @@ -355,6 +355,11 @@ static void __snd_dmaengine_pcm_close(struct snd_pcm_substream *substream, if (status == DMA_PAUSED) dmaengine_terminate_async(prtd->dma_chan); + /* + * The PCM might have been closed while suspended, which would + * skip the STOP trigger. Make sure we terminate. + */ + dmaengine_terminate_async(prtd->dma_chan); dmaengine_synchronize(prtd->dma_chan); if (release_channel) dma_release_channel(prtd->dma_chan); From e78fb9701b523ea697b72b7b3bb4c93362e51116 Mon Sep 17 00:00:00 2001 From: Hector Martin Date: Sat, 28 Oct 2023 22:10:32 +0900 Subject: [PATCH 0141/3902] macaudio: Rework platform config & add all remaining platforms Instead of open-coding a fixup function for each platform, let's make it declarative. This is a lot less error-prone. Signed-off-by: Hector Martin --- sound/soc/apple/macaudio.c | 418 ++++++++++++++++++++++--------------- 1 file changed, 250 insertions(+), 168 deletions(-) diff --git a/sound/soc/apple/macaudio.c b/sound/soc/apple/macaudio.c index 74aa31b438ffed..479ee791d3c499 100644 --- a/sound/soc/apple/macaudio.c +++ b/sound/soc/apple/macaudio.c @@ -59,20 +59,45 @@ /* milliseconds */ #define SPEAKER_LOCK_TIMEOUT 250 -#define MAX_LIMITS 6 +enum macaudio_amp_type { + AMP_NONE, + AMP_TAS5770, + AMP_SN012776, + AMP_SSM3515, +}; -struct macaudio_limit_cfg { - const char *match; - int max_limited; - int max_unlimited; +enum macaudio_spkr_config { + SPKR_NONE, /* No speakers */ + SPKR_1W, /* 1 woofer / ch */ + SPKR_2W, /* 2 woofers / ch */ + SPKR_1W1T, /* 1 woofer + 1 tweeter / ch */ + SPKR_2W1T, /* 2 woofers + 1 tweeter / ch */ }; struct macaudio_platform_cfg { - struct macaudio_limit_cfg limits[MAX_LIMITS]; - int (*fixup)(struct snd_soc_card *card); bool enable_speakers; + enum macaudio_amp_type amp; + enum macaudio_spkr_config speakers; + bool stereo; + int amp_gain; + int safe_vol; +}; + +static const char *volume_control_names[] = { + [AMP_TAS5770] = "* Speaker Playback Volume", + [AMP_SN012776] = "* Speaker Volume", + [AMP_SSM3515] = "* DAC Playback Volume", }; +#define SN012776_0DB 201 +#define SN012776_DB(x) (SN012776_0DB + 2 * (x)) +/* Same as SN012776 */ +#define TAS5770_0DB SN012776_0DB +#define TAS5770_DB(x) SN012776_DB(x) + +#define SSM3515_0DB (255 - 64) /* +24dB max, steps of 3/8 dB */ +#define SSM3515_DB(x) (SSM3515_0DB + (8 * (x) / 3)) + struct macaudio_snd_data { struct snd_soc_card card; struct snd_soc_jack jack; @@ -80,6 +105,7 @@ struct macaudio_snd_data { const struct macaudio_platform_cfg *cfg; bool has_speakers; + bool has_safety; unsigned int max_channels; struct macaudio_link_props { @@ -199,24 +225,42 @@ static struct macaudio_link_props macaudio_fe_link_props[] = { static void macaudio_vlimit_unlock(struct macaudio_snd_data *ma, bool unlock) { - int i, ret, max; + int ret, max; + const char *name = volume_control_names[ma->cfg->amp]; - for (i = 0; i < ARRAY_SIZE(ma->cfg->limits); i++) { - const struct macaudio_limit_cfg *limit = &ma->cfg->limits[i]; - - if (!limit->match) - break; + if (!name) { + WARN_ON_ONCE(1); + return; + } + switch (ma->cfg->amp) { + case AMP_NONE: + WARN_ON_ONCE(1); + return; + case AMP_TAS5770: if (unlock) - max = limit->max_unlimited; + max = TAS5770_0DB; else - max = limit->max_limited; - - ret = snd_soc_limit_volume(&ma->card, limit->match, max); - if (ret < 0) - dev_err(ma->card.dev, "Failed to %slock volume %s: %d\n", - unlock ? "un" : "", limit->match, ret); + max = 1; //TAS5770_DB(ma->cfg->safe_vol); + break; + case AMP_SN012776: + if (unlock) + max = SN012776_0DB; + else + max = 1; //SN012776_DB(ma->cfg->safe_vol); + break; + case AMP_SSM3515: + if (unlock) + max = SSM3515_0DB; + else + max = SSM3515_DB(ma->cfg->safe_vol); + break; } + + ret = snd_soc_limit_volume(&ma->card, name, max); + if (ret < 0) + dev_err(ma->card.dev, "Failed to %slock volume %s: %d\n", + unlock ? "un" : "", name, ret); } static void macaudio_vlimit_update(struct macaudio_snd_data *ma) @@ -226,8 +270,8 @@ static void macaudio_vlimit_update(struct macaudio_snd_data *ma) struct snd_kcontrol *kctl; const char *reason; - /* Do nothing if there are no limits configured */ - if (!ma->cfg->limits[0].match) + /* Do nothing if there is no safety configured */ + if (!ma->has_safety) return; /* Check that someone is holding the main lock */ @@ -244,19 +288,7 @@ static void macaudio_vlimit_update(struct macaudio_snd_data *ma) /* Check that *every* limited control is locked by the same owner */ list_for_each_entry(kctl, &ma->card.snd_card->controls, list) { - bool is_limit = false; - - for (i = 0; i < ARRAY_SIZE(ma->cfg->limits); i++) { - const struct macaudio_limit_cfg *limit = &ma->cfg->limits[i]; - if (!limit->match) - break; - - is_limit = snd_soc_control_matches(kctl, limit->match); - if (is_limit) - break; - } - - if (!is_limit) + if(!snd_soc_control_matches(kctl, volume_control_names[ma->cfg->amp])) continue; for (i = 0; i < kctl->count; i++) { @@ -1100,19 +1132,21 @@ static int macaudio_late_probe(struct snd_soc_card *card) } } - ma->speaker_sample_rate_kctl = snd_soc_card_get_kcontrol(card, "Speaker Sample Rate"); - ma->speaker_lock_kctl = snd_soc_card_get_kcontrol(card, "Speaker Volume Unlock"); + if (ma->has_speakers) + ma->speaker_sample_rate_kctl = snd_soc_card_get_kcontrol(card, + "Speaker Sample Rate"); + if (ma->has_safety) { + ma->speaker_lock_kctl = snd_soc_card_get_kcontrol(card, + "Speaker Volume Unlock"); - mutex_lock(&ma->volume_lock_mutex); - macaudio_vlimit_unlock(ma, false); - mutex_unlock(&ma->volume_lock_mutex); + mutex_lock(&ma->volume_lock_mutex); + macaudio_vlimit_unlock(ma, false); + mutex_unlock(&ma->volume_lock_mutex); + } return 0; } -#define TAS2764_0DB 201 -#define TAS2764_DB_REDUCTION(x) (TAS2764_0DB - 2 * (x)) - #define CHECK(call, pattern, value) \ { \ int ret = call(card, pattern, value); \ @@ -1123,141 +1157,90 @@ static int macaudio_late_probe(struct snd_soc_card *card) dev_dbg(card->dev, "%s on '%s': %d hits\n", #call, pattern, ret); \ } -static int macaudio_j274_fixup_controls(struct snd_soc_card *card) -{ - struct macaudio_snd_data *ma = snd_soc_card_get_drvdata(card); - - if (ma->has_speakers) { - CHECK(snd_soc_limit_volume, "* Amp Gain Volume", 14); // 20 set by macOS, this is 3 dB below - } - - return 0; -} - -struct macaudio_platform_cfg macaudio_j274_cfg = { - .fixup = macaudio_j274_fixup_controls, - .enable_speakers = true, -}; - -static int macaudio_j313_fixup_controls(struct snd_soc_card *card) { - struct macaudio_snd_data *ma = snd_soc_card_get_drvdata(card); - - if (ma->has_speakers) { - CHECK(snd_soc_set_enum_kctl, "* ASI1 Sel", "Left"); - CHECK(snd_soc_deactivate_kctl, "* ASI1 Sel", 0); - - /* !!! This is copied from j274, not obtained by looking at - * what macOS sets. - */ - CHECK(snd_soc_limit_volume, "* Amp Gain Volume", 14); +#define CHECK_CONCAT(call, suffix, value) \ + { \ + snprintf(buf, sizeof(buf), "%s%s", prefix, suffix); \ + CHECK(call, buf, value); \ } - return 0; -} - -struct macaudio_platform_cfg macaudio_j313_cfg = { - .fixup = macaudio_j313_fixup_controls, -}; - -static int macaudio_j314_fixup_controls(struct snd_soc_card *card) +static int macaudio_set_speaker(struct snd_soc_card *card, const char *prefix, bool tweeter) { struct macaudio_snd_data *ma = snd_soc_card_get_drvdata(card); + char buf[256]; - if (ma->has_speakers) { - CHECK(snd_soc_set_enum_kctl, "* ASI1 Sel", "Left"); - CHECK(snd_soc_deactivate_kctl, "* ASI1 Sel", 0); - CHECK(snd_soc_limit_volume, "* Amp Gain Volume", 9); // 15 set by macOS, this is 3 dB below - CHECK(snd_soc_set_enum_kctl, "* Tweeter HPF Corner Frequency", "800 Hz"); - CHECK(snd_soc_deactivate_kctl, "* Tweeter HPF Corner Frequency", 0); - - /* - * The speaker amps suffer from spurious overcurrent - * events on their unmute, so enable autoretry. - */ - CHECK(snd_soc_set_enum_kctl, "* OCE Handling", "Retry"); - CHECK(snd_soc_deactivate_kctl, "* OCE Handling", 0); - } + if (!ma->has_speakers) + return 0; - return 0; -} + switch (ma->cfg->amp) { + case AMP_TAS5770: + if (ma->cfg->stereo) { + CHECK_CONCAT(snd_soc_set_enum_kctl, "ASI1 Sel", "Left"); + CHECK_CONCAT(snd_soc_deactivate_kctl, "ASI1 Sel", 0); + } + CHECK_CONCAT(snd_soc_limit_volume, "Amp Gain Volume", ma->cfg->amp_gain); + break; + case AMP_SN012776: + if (ma->cfg->stereo) { + CHECK_CONCAT(snd_soc_set_enum_kctl, "ASI1 Sel", "Left"); + CHECK_CONCAT(snd_soc_deactivate_kctl, "ASI1 Sel", 0); + } -struct macaudio_platform_cfg macaudio_j314_cfg = { - .fixup = macaudio_j314_fixup_controls, - .limits = { - {.match = "* Tweeter Speaker Volume", TAS2764_DB_REDUCTION(20), TAS2764_0DB}, - {.match = "* Woofer Speaker Volume", TAS2764_DB_REDUCTION(20), TAS2764_0DB}, - } -}; + CHECK_CONCAT(snd_soc_limit_volume, "Amp Gain Volume", ma->cfg->amp_gain); + CHECK_CONCAT(snd_soc_set_enum_kctl, "HPF Corner Frequency", + tweeter ? "800 Hz" : "2 Hz"); -struct macaudio_platform_cfg macaudio_j413_cfg = { - .fixup = macaudio_j314_fixup_controls, - .limits = { - /* Min gain: -17.47 dB */ - {.match = "* Tweeter Speaker Volume", TAS2764_DB_REDUCTION(20), TAS2764_0DB}, - /* Min gain: -10.63 dB */ - /* FIXME: These structures are aliased so we can't set different max volumes */ - {.match = "* Woofer Speaker Volume", TAS2764_DB_REDUCTION(20), TAS2764_0DB}, - } -}; + if (!please_blow_up_my_speakers) + CHECK_CONCAT(snd_soc_deactivate_kctl, "HPF Corner Frequency", 0); -struct macaudio_platform_cfg macaudio_j415_cfg = { - .fixup = macaudio_j314_fixup_controls, - .limits = { - {.match = "* Tweeter Speaker Volume", TAS2764_DB_REDUCTION(20), TAS2764_0DB}, - {.match = "* Woofer 1 Speaker Volume", TAS2764_DB_REDUCTION(20), TAS2764_0DB}, - {.match = "* Woofer 2 Speaker Volume", TAS2764_DB_REDUCTION(20), TAS2764_0DB}, - } -}; + CHECK_CONCAT(snd_soc_set_enum_kctl, "OCE Handling", "Retry"); + CHECK_CONCAT(snd_soc_deactivate_kctl, "OCE Handling", 0); + break; + case AMP_SSM3515: + /* TODO: check */ + CHECK_CONCAT(snd_soc_set_enum_kctl, "DAC Analog Gain Select", "8.4 V Span"); -static int macaudio_j375_fixup_controls(struct snd_soc_card *card) -{ - struct macaudio_snd_data *ma = snd_soc_card_get_drvdata(card); + if (!please_blow_up_my_speakers) + CHECK_CONCAT(snd_soc_deactivate_kctl, "DAC Analog Gain Select", 0); - if (ma->has_speakers) { - CHECK(snd_soc_limit_volume, "* Amp Gain Volume", 14); // 20 set by macOS, this is 3 dB below + /* TODO: HPF, needs new call to set */ + break; + default: + return -EINVAL; } return 0; } -struct macaudio_platform_cfg macaudio_j375_cfg = { - .fixup = macaudio_j375_fixup_controls, -}; - -static int macaudio_j493_fixup_controls(struct snd_soc_card *card) +static int macaudio_fixup_controls(struct snd_soc_card *card) { struct macaudio_snd_data *ma = snd_soc_card_get_drvdata(card); - if (ma->has_speakers) { - CHECK(snd_soc_limit_volume, "* Amp Gain Volume", 9); // 15 set by macOS, this is 3 dB below - } - - return 0; -} - -struct macaudio_platform_cfg macaudio_j493_cfg = { - .fixup = macaudio_j493_fixup_controls -}; - -static int macaudio_fallback_fixup_controls(struct snd_soc_card *card) -{ - struct macaudio_snd_data *ma = snd_soc_card_get_drvdata(card); + if (!ma->has_speakers) + return 0; - if (ma->has_speakers && !please_blow_up_my_speakers) { - dev_err(card->dev, "driver can't assure safety on this model, refusing probe\n"); - return -EINVAL; + switch(ma->cfg->speakers) { + case SPKR_NONE: + WARN_ON(!please_blow_up_my_speakers); + return please_blow_up_my_speakers ? 0 : -EINVAL; + case SPKR_1W: + case SPKR_2W: + CHECK(macaudio_set_speaker, "* ", false); + break; + case SPKR_1W1T: + CHECK(macaudio_set_speaker, "* Tweeter ", true); + CHECK(macaudio_set_speaker, "* Woofer ", false); + break; + case SPKR_2W1T: + CHECK(macaudio_set_speaker, "* Tweeter ", true); + CHECK(macaudio_set_speaker, "* Woofer 1 ", false); + CHECK(macaudio_set_speaker, "* Woofer 2 ", false); + break; } return 0; } -struct macaudio_platform_cfg macaudio_fallback_cfg = { - .fixup = macaudio_fallback_fixup_controls -}; - -#undef CHECK - static const char * const macaudio_spk_mux_texts[] = { "Primary", "Secondary" @@ -1407,8 +1390,17 @@ static void macaudio_slk_unlock(struct snd_kcontrol *kcontrol) macaudio_vlimit_update(ma); } -/* Speaker limit controls go last */ -#define MACAUDIO_NUM_SPEAKER_LIMIT_CONTROLS 2 +/* + * Speaker limit controls go last. We only drop the unlock control, + * leaving sample rate, since that can be useful for safety + * bring-up before the kernel-side caps are ready. + */ +#define MACAUDIO_NUM_SPEAKER_LIMIT_CONTROLS 1 +/* + * If there are no speakers configured at all, we can drop both + * controls. + */ +#define MACAUDIO_NUM_SPEAKER_CONTROLS 2 static const struct snd_kcontrol_new macaudio_controls[] = { SOC_DAPM_PIN_SWITCH("Speaker"), @@ -1417,19 +1409,19 @@ static const struct snd_kcontrol_new macaudio_controls[] = { { .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .access = SNDRV_CTL_ELEM_ACCESS_READ | - SNDRV_CTL_ELEM_ACCESS_WRITE | SNDRV_CTL_ELEM_ACCESS_VOLATILE, - .name = "Speaker Volume Unlock", - .info = macaudio_slk_info, - .put = macaudio_slk_put, .get = macaudio_slk_get, - .lock = macaudio_slk_lock, .unlock = macaudio_slk_unlock, + .name = "Speaker Sample Rate", + .info = macaudio_sss_info, .get = macaudio_sss_get, }, { .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .access = SNDRV_CTL_ELEM_ACCESS_READ | + SNDRV_CTL_ELEM_ACCESS_WRITE | SNDRV_CTL_ELEM_ACCESS_VOLATILE, - .name = "Speaker Sample Rate", - .info = macaudio_sss_info, .get = macaudio_sss_get, + .name = "Speaker Volume Unlock", + .info = macaudio_slk_info, + .put = macaudio_slk_put, .get = macaudio_slk_get, + .lock = macaudio_slk_lock, .unlock = macaudio_slk_unlock, }, }; @@ -1453,14 +1445,100 @@ static const struct snd_soc_dapm_route macaudio_dapm_routes[] = { { "PCM2 RX", NULL, "Speaker Sense Capture" }, }; +/* enable amp speakers stereo gain safe_vol */ +struct macaudio_platform_cfg macaudio_j180_cfg = { + false, AMP_SN012776, SPKR_1W1T, false, 4, -20, +}; +struct macaudio_platform_cfg macaudio_j274_cfg = { + true, AMP_TAS5770, SPKR_1W, false, 14, 0, /* TODO: safety */ +}; + +struct macaudio_platform_cfg macaudio_j293_cfg = { + false, AMP_TAS5770, SPKR_2W, true, 9, -20, /* TODO: check */ +}; + +struct macaudio_platform_cfg macaudio_j313_cfg = { + false, AMP_TAS5770, SPKR_1W, true, 4, -20, /* TODO: check */ +}; + +struct macaudio_platform_cfg macaudio_j314_j316_cfg = { + false, AMP_SN012776, SPKR_2W1T, true, 9, -20, +}; + +struct macaudio_platform_cfg macaudio_j37x_j47x_cfg = { + false, AMP_SN012776, SPKR_1W, false, 14, -20, +}; + +struct macaudio_platform_cfg macaudio_j413_cfg = { + false, AMP_SN012776, SPKR_1W1T, true, 9, -20, +}; + +struct macaudio_platform_cfg macaudio_j415_cfg = { + false, AMP_SN012776, SPKR_2W1T, true, 9, -20, +}; + +struct macaudio_platform_cfg macaudio_j45x_cfg = { + false, AMP_SSM3515, SPKR_1W1T, true, 9, -20, /* TODO: gain?? */ +}; + +struct macaudio_platform_cfg macaudio_j493_cfg = { + false, AMP_SN012776, SPKR_2W, true, 9, -20, +}; + +struct macaudio_platform_cfg macaudio_fallback_cfg = { + false, AMP_NONE, SPKR_NONE, false, 0, 0, +}; + +/* + * DT compatible/ID table rules: + * + * 1. Machines with **identical** speaker configurations (amps, models, chassis) + * are allowed to declare compatibility with the first model (chronologically), + * and are not enumerated in this array. + * + * 2. Machines with identical amps and speakers (=identical speaker protection + * rules) but a different chassis must use different compatibles, but may share + * the private data structure here. They are explicitly enumerated. + * + * 3. Machines with different amps or speaker layouts must use separate + * data structures. + * + * 4. Machines with identical speaker layouts and amps (but possibly different + * speaker models/chassis) may share the data structure, since only userspace + * cares about that (assuming our general -20dB safe level standard holds). + */ static const struct of_device_id macaudio_snd_device_id[] = { + /* Model ID Amp Gain Speakers */ + /* j180 AID19 sn012776 10 1× 1W+1T */ + { .compatible = "apple,j180-macaudio", .data = &macaudio_j180_cfg }, + /* j274 AID6 tas5770 20 1× 1W */ { .compatible = "apple,j274-macaudio", .data = &macaudio_j274_cfg }, + /* j293 AID3 tas5770 15 2× 2W */ + { .compatible = "apple,j293-macaudio", .data = &macaudio_j293_cfg }, + /* j313 AID4 tas5770 10 2× 1W */ { .compatible = "apple,j313-macaudio", .data = &macaudio_j313_cfg }, - { .compatible = "apple,j314-macaudio", .data = &macaudio_j314_cfg }, - { .compatible = "apple,j375-macaudio", .data = &macaudio_j375_cfg }, + /* j314 AID8 sn012776 15 2× 2W+1T */ + { .compatible = "apple,j314-macaudio", .data = &macaudio_j314_j316_cfg }, + /* j316 AID9 sn012776 15 2× 2W+1T */ + { .compatible = "apple,j316-macaudio", .data = &macaudio_j314_j316_cfg }, + /* j375 AID10 sn012776 15 1× 1W */ + { .compatible = "apple,j375-macaudio", .data = &macaudio_j37x_j47x_cfg }, + /* j413 AID13 sn012776 15 2× 1W+1T */ { .compatible = "apple,j413-macaudio", .data = &macaudio_j413_cfg }, + /* j414 AID14 sn012776 15 2× 2W+1T Compat: apple,j314-macaudio */ + /* j415 AID27 sn012776 15 2× 2W+1T */ { .compatible = "apple,j415-macaudio", .data = &macaudio_j415_cfg }, + /* j416 AID15 sn012776 15 2× 2W+1T Compat: apple,j316-macaudio */ + /* j456 AID5 ssm3515 15 2× 1W+1T */ + { .compatible = "apple,j456-macaudio", .data = &macaudio_j45x_cfg }, + /* j457 AID7 ssm3515 15 2× 1W+1T Compat: apple,j456-macaudio */ + /* j473 AID12 sn012776 20 1× 1W */ + { .compatible = "apple,j473-macaudio", .data = &macaudio_j37x_j47x_cfg }, + /* j474 AID26 sn012776 20 1× 1W Compat: apple,j473-macaudio */ + /* j475 AID25 sn012776 20 1× 1W Compat: apple,j375-macaudio */ + /* j493 AID18 sn012776 15 2× 2W */ { .compatible = "apple,j493-macaudio", .data = &macaudio_j493_cfg }, + /* Fallback, jack only */ { .compatible = "apple,macaudio"}, { } }; @@ -1507,16 +1585,20 @@ static int macaudio_snd_platform_probe(struct platform_device *pdev) else data->cfg = &macaudio_fallback_cfg; - /* Remove speaker safety controls if we have no declared limits */ - if (!data->cfg->limits[0].match) - card->num_controls -= MACAUDIO_NUM_SPEAKER_LIMIT_CONTROLS; - - card->fixup_controls = data->cfg->fixup; + card->fixup_controls = macaudio_fixup_controls; ret = macaudio_parse_of(data); if (ret) return ret; + /* Remove useless controls */ + if (!data->has_speakers) /* No speakers, remove both */ + card->num_controls -= MACAUDIO_NUM_SPEAKER_CONTROLS; + else if (!data->cfg->safe_vol) /* No safety, remove unlock */ + card->num_controls -= MACAUDIO_NUM_SPEAKER_LIMIT_CONTROLS; + else /* Speakers with safety, mark us as such */ + data->has_safety = true; + for_each_card_prelinks(card, i, link) { if (link->no_pcm) { link->ops = &macaudio_be_ops; From 40fb0b2c0a3112e164a780c25df860291e3d598b Mon Sep 17 00:00:00 2001 From: Hector Martin Date: Sun, 29 Oct 2023 17:31:48 +0900 Subject: [PATCH 0142/3902] ASoC: tas2770: Add SDZ regulator Multiple amps can be connected to the same SDZ GPIO. Using raw GPIOs for this breaks, as there is no concept of refcounting/sharing. In order to model these platforms, introduce support for an SDZ "regulator". This allows us to represent the SDZ GPIO as a simple regulator-fixed, and then the regulator core takes care of refcounting so that all codecs are only powered down once all the driver instances are in the suspend state. This also reworks the sleep/resume logic to copy what tas2764 does, which makes more sense. Signed-off-by: Hector Martin --- sound/soc/codecs/tas2770.c | 72 ++++++++++++++++++++++++++------------ sound/soc/codecs/tas2770.h | 1 + 2 files changed, 50 insertions(+), 23 deletions(-) diff --git a/sound/soc/codecs/tas2770.c b/sound/soc/codecs/tas2770.c index 6f878b01716f72..f596e4a738f58f 100644 --- a/sound/soc/codecs/tas2770.c +++ b/sound/soc/codecs/tas2770.c @@ -71,23 +71,21 @@ static int tas2770_codec_suspend(struct snd_soc_component *component) struct tas2770_priv *tas2770 = snd_soc_component_get_drvdata(component); int ret = 0; - regcache_cache_only(tas2770->regmap, true); - regcache_mark_dirty(tas2770->regmap); + ret = snd_soc_component_update_bits(component, TAS2770_PWR_CTRL, + TAS2770_PWR_CTRL_MASK, + TAS2770_PWR_CTRL_SHUTDOWN); + if (ret < 0) + return ret; - if (tas2770->sdz_gpio) { + if (tas2770->sdz_gpio) gpiod_set_value_cansleep(tas2770->sdz_gpio, 0); - } else { - ret = snd_soc_component_update_bits(component, TAS2770_PWR_CTRL, - TAS2770_PWR_CTRL_MASK, - TAS2770_PWR_CTRL_SHUTDOWN); - if (ret < 0) { - regcache_cache_only(tas2770->regmap, false); - regcache_sync(tas2770->regmap); - return ret; - } - ret = 0; - } + regulator_disable(tas2770->sdz_reg); + + regcache_cache_only(tas2770->regmap, true); + regcache_mark_dirty(tas2770->regmap); + + usleep_range(6000, 7000); return ret; } @@ -97,18 +95,26 @@ static int tas2770_codec_resume(struct snd_soc_component *component) struct tas2770_priv *tas2770 = snd_soc_component_get_drvdata(component); int ret; - if (tas2770->sdz_gpio) { - gpiod_set_value_cansleep(tas2770->sdz_gpio, 1); - usleep_range(1000, 2000); - } else { - ret = tas2770_update_pwr_ctrl(tas2770); - if (ret < 0) - return ret; + ret = regulator_enable(tas2770->sdz_reg); + + if (ret) { + dev_err(tas2770->dev, "Failed to enable regulator\n"); + return ret; } + if (tas2770->sdz_gpio) + gpiod_set_value_cansleep(tas2770->sdz_gpio, 1); + + + usleep_range(1000, 2000); + regcache_cache_only(tas2770->regmap, false); - return regcache_sync(tas2770->regmap); + ret = regcache_sync(tas2770->regmap); + if (ret < 0) + return ret; + + return tas2770_update_pwr_ctrl(tas2770); } #else #define tas2770_codec_suspend NULL @@ -623,11 +629,18 @@ static int tas2770_codec_probe(struct snd_soc_component *component) tas2770->component = component; + ret = regulator_enable(tas2770->sdz_reg); + if (ret != 0) { + dev_err(tas2770->dev, "Failed to enable regulator: %d\n", ret); + return ret; + } + if (tas2770->sdz_gpio) { gpiod_set_value_cansleep(tas2770->sdz_gpio, 1); - usleep_range(1000, 2000); } + usleep_range(1000, 2000); + tas2770_reset(tas2770); regmap_reinit_cache(tas2770->regmap, &tas2770_i2c_regmap); @@ -649,6 +662,13 @@ static int tas2770_codec_probe(struct snd_soc_component *component) return 0; } +static void tas2770_codec_remove(struct snd_soc_component *component) +{ + struct tas2770_priv *tas2770 = snd_soc_component_get_drvdata(component); + + regulator_disable(tas2770->sdz_reg); +} + static DECLARE_TLV_DB_SCALE(tas2770_digital_tlv, 1100, 50, 0); static DECLARE_TLV_DB_SCALE(tas2770_playback_volume, -10050, 50, 0); @@ -661,6 +681,7 @@ static const struct snd_kcontrol_new tas2770_snd_controls[] = { static const struct snd_soc_component_driver soc_component_driver_tas2770 = { .probe = tas2770_codec_probe, + .remove = tas2770_codec_remove, .suspend = tas2770_codec_suspend, .resume = tas2770_codec_resume, .controls = tas2770_snd_controls, @@ -790,6 +811,11 @@ static int tas2770_parse_dt(struct device *dev, struct tas2770_priv *tas2770) if (rc) tas2770->pdm_slot = -1; + tas2770->sdz_reg = devm_regulator_get(dev, "SDZ"); + if (IS_ERR(tas2770->sdz_reg)) + return dev_err_probe(dev, PTR_ERR(tas2770->sdz_reg), + "Failed to get SDZ supply\n"); + tas2770->sdz_gpio = devm_gpiod_get_optional(dev, "shutdown", GPIOD_OUT_HIGH); if (IS_ERR(tas2770->sdz_gpio)) { if (PTR_ERR(tas2770->sdz_gpio) == -EPROBE_DEFER) diff --git a/sound/soc/codecs/tas2770.h b/sound/soc/codecs/tas2770.h index 3fd2e7003c50b6..4b38bc88ff5669 100644 --- a/sound/soc/codecs/tas2770.h +++ b/sound/soc/codecs/tas2770.h @@ -139,6 +139,7 @@ struct tas2770_priv { struct snd_soc_component *component; struct gpio_desc *reset_gpio; struct gpio_desc *sdz_gpio; + struct regulator *sdz_reg; struct regmap *regmap; struct device *dev; int v_sense_slot; From dd593b042ecc605dd4f259e549cd85384d41fd4f Mon Sep 17 00:00:00 2001 From: Hector Martin Date: Sun, 29 Oct 2023 22:00:01 +0900 Subject: [PATCH 0143/3902] ASoC: tas2770: Add zero-fill and pull-down controls Expose the bits that control the behavior of the SDOUT pin when not actively transmitting slot data. Zero-fill is useful when there is a single amp on the SDOUT bus (e.g. Apple machines with mono speakers or a single stereo pair, where L/R are on separate buses). Pull-down is useful, though not perfect, when multiple amps share a bus. It typically takes around 2 bits for the line to transition from high to low after going Hi-Z, with the pull-down. Signed-off-by: Hector Martin --- sound/soc/codecs/tas2770.c | 18 +++++++++++++++++- sound/soc/codecs/tas2770.h | 13 +++++++++++++ 2 files changed, 30 insertions(+), 1 deletion(-) diff --git a/sound/soc/codecs/tas2770.c b/sound/soc/codecs/tas2770.c index f596e4a738f58f..e72027cf340bfe 100644 --- a/sound/soc/codecs/tas2770.c +++ b/sound/soc/codecs/tas2770.c @@ -654,11 +654,24 @@ static int tas2770_codec_probe(struct snd_soc_component *component) if (tas2770->pdm_slot != -1) { ret = tas2770_set_pdm_transmit(tas2770, tas2770->pdm_slot); - if (ret < 0) return ret; } + ret = snd_soc_component_update_bits(component, TAS2770_TDM_CFG_REG4, + TAS2770_TDM_CFG_REG4_TX_FILL, + tas2770->sdout_zfill ? 0 : + TAS2770_TDM_CFG_REG4_TX_FILL); + if (ret < 0) + return ret; + + ret = snd_soc_component_update_bits(component, TAS2770_DIN_PD, + TAS2770_DIN_PD_SDOUT, + tas2770->sdout_pd ? + TAS2770_DIN_PD_SDOUT : 0); + if (ret < 0) + return ret; + return 0; } @@ -811,6 +824,9 @@ static int tas2770_parse_dt(struct device *dev, struct tas2770_priv *tas2770) if (rc) tas2770->pdm_slot = -1; + tas2770->sdout_pd = fwnode_property_read_bool(dev->fwnode, "ti,sdout-pull-down"); + tas2770->sdout_zfill = fwnode_property_read_bool(dev->fwnode, "ti,sdout-zero-fill"); + tas2770->sdz_reg = devm_regulator_get(dev, "SDZ"); if (IS_ERR(tas2770->sdz_reg)) return dev_err_probe(dev, PTR_ERR(tas2770->sdz_reg), diff --git a/sound/soc/codecs/tas2770.h b/sound/soc/codecs/tas2770.h index 4b38bc88ff5669..b309d19c58e1da 100644 --- a/sound/soc/codecs/tas2770.h +++ b/sound/soc/codecs/tas2770.h @@ -67,6 +67,14 @@ #define TAS2770_TDM_CFG_REG3_RXS_SHIFT 0x4 #define TAS2770_TDM_CFG_REG3_30_MASK GENMASK(3, 0) #define TAS2770_TDM_CFG_REG3_30_SHIFT 0 + /* TDM Configuration Reg4 */ +#define TAS2770_TDM_CFG_REG4 TAS2770_REG(0X0, 0x0E) +#define TAS2770_TDM_CFG_REG4_TX_LSB_CFG BIT(7) +#define TAS2770_TDM_CFG_REG4_TX_KEEPER_CFG BIT(6) +#define TAS2770_TDM_CFG_REG4_TX_KEEPER BIT(5) +#define TAS2770_TDM_CFG_REG4_TX_FILL BIT(4) +#define TAS2770_TDM_CFG_REG4_TX_OFFSET_MASK GENMASK(3, 1) +#define TAS2770_TDM_CFG_REG4_TX_EDGE_FALLING BIT(0) /* TDM Configuration Reg5 */ #define TAS2770_TDM_CFG_REG5 TAS2770_REG(0X0, 0x0F) #define TAS2770_TDM_CFG_REG5_VSNS_MASK BIT(6) @@ -115,6 +123,9 @@ #define TAS2770_TEMP_LSB TAS2770_REG(0X0, 0x2A) /* Interrupt Configuration */ #define TAS2770_INT_CFG TAS2770_REG(0X0, 0x30) + /* Data In Pull-Down */ +#define TAS2770_DIN_PD TAS2770_REG(0X0, 0x31) +#define TAS2770_DIN_PD_SDOUT BIT(7) /* Misc IRQ */ #define TAS2770_MISC_IRQ TAS2770_REG(0X0, 0x32) /* Clock Configuration */ @@ -145,6 +156,8 @@ struct tas2770_priv { int v_sense_slot; int i_sense_slot; int pdm_slot; + bool sdout_pd; + bool sdout_zfill; bool dac_powered; bool unmuted; }; From 55b5dcd5ae5c784fe5eb10134dbad8d522070349 Mon Sep 17 00:00:00 2001 From: Hector Martin Date: Mon, 30 Oct 2023 00:26:59 +0900 Subject: [PATCH 0144/3902] macaudio: Remove -3dB safety pad from j313 This one already uses a gain lower than the others. It doesn't look like full scale no-DSP output with typical music is particularly dangerous here, and we probably want the headroom for DSP, so let's not do it for this one. Signed-off-by: Hector Martin --- sound/soc/apple/macaudio.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sound/soc/apple/macaudio.c b/sound/soc/apple/macaudio.c index 479ee791d3c499..a1d10482edc92f 100644 --- a/sound/soc/apple/macaudio.c +++ b/sound/soc/apple/macaudio.c @@ -1458,7 +1458,7 @@ struct macaudio_platform_cfg macaudio_j293_cfg = { }; struct macaudio_platform_cfg macaudio_j313_cfg = { - false, AMP_TAS5770, SPKR_1W, true, 4, -20, /* TODO: check */ + false, AMP_TAS5770, SPKR_1W, true, 10, -20, }; struct macaudio_platform_cfg macaudio_j314_j316_cfg = { From c4698f456c09550a24ce78932531468281bcf163 Mon Sep 17 00:00:00 2001 From: Hector Martin Date: Fri, 3 Nov 2023 21:10:11 +0900 Subject: [PATCH 0145/3902] macaudio: Skip speaker sense PCM if no sense or no speakers This PCM triggers speakersafetyd, so hide it if it can't work. Signed-off-by: Hector Martin --- sound/soc/apple/macaudio.c | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/sound/soc/apple/macaudio.c b/sound/soc/apple/macaudio.c index a1d10482edc92f..820c8ea7e0ed35 100644 --- a/sound/soc/apple/macaudio.c +++ b/sound/soc/apple/macaudio.c @@ -105,6 +105,7 @@ struct macaudio_snd_data { const struct macaudio_platform_cfg *cfg; bool has_speakers; + bool has_sense; bool has_safety; unsigned int max_channels; @@ -569,6 +570,8 @@ static int macaudio_parse_of(struct macaudio_snd_data *ma) continue; } ma->has_speakers = 1; + if (ma->cfg->amp != AMP_SSM3515 && ma->cfg->safe_vol != 0) + ma->has_sense = 1; } cpu = of_get_child_by_name(np, "cpu"); @@ -670,6 +673,18 @@ static int macaudio_parse_of(struct macaudio_snd_data *ma) for (i = 0; i < ARRAY_SIZE(macaudio_fe_links); i++) card->dai_link[i].platforms->of_node = platform; + /* Skip the speaker sense PCM link if this amp has no sense (or no speakers) */ + if (!ma->has_sense) { + for (i = 0; i < ARRAY_SIZE(macaudio_fe_links); i++) { + if (ma->link_props[i].is_sense) { + memmove(&card->dai_link[i], &card->dai_link[i + 1], + (num_links - i - 1) * sizeof (struct snd_soc_dai_link)); + num_links--; + break; + } + } + } + card->num_links = num_links; return 0; From 815a3f3048911bbc68c2687cf58ef4e0daeee682 Mon Sep 17 00:00:00 2001 From: Hector Martin Date: Tue, 7 Nov 2023 21:16:57 +0900 Subject: [PATCH 0146/3902] macaudio: Officially enable j313 speakers Still hard gated on speakersafetyd for now. Signed-off-by: Hector Martin --- sound/soc/apple/macaudio.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sound/soc/apple/macaudio.c b/sound/soc/apple/macaudio.c index 820c8ea7e0ed35..89a056febe665c 100644 --- a/sound/soc/apple/macaudio.c +++ b/sound/soc/apple/macaudio.c @@ -1473,7 +1473,7 @@ struct macaudio_platform_cfg macaudio_j293_cfg = { }; struct macaudio_platform_cfg macaudio_j313_cfg = { - false, AMP_TAS5770, SPKR_1W, true, 10, -20, + true, AMP_TAS5770, SPKR_1W, true, 10, -20, }; struct macaudio_platform_cfg macaudio_j314_j316_cfg = { From 7b558deb6645ec6b0327fd90be70f8397f342ab2 Mon Sep 17 00:00:00 2001 From: Hector Martin Date: Mon, 4 Dec 2023 01:21:33 +0900 Subject: [PATCH 0147/3902] macaudio: Set the card name explicitly This might fix a udev race, and also makes it possible to switch to a more descriptive "AppleJxxx" name (but before that we need to update userspace to avoid breaking users). Signed-off-by: Hector Martin --- sound/soc/apple/macaudio.c | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/sound/soc/apple/macaudio.c b/sound/soc/apple/macaudio.c index 89a056febe665c..7017c23447782d 100644 --- a/sound/soc/apple/macaudio.c +++ b/sound/soc/apple/macaudio.c @@ -1230,6 +1230,14 @@ static int macaudio_set_speaker(struct snd_soc_card *card, const char *prefix, b static int macaudio_fixup_controls(struct snd_soc_card *card) { struct macaudio_snd_data *ma = snd_soc_card_get_drvdata(card); + const char *p; + + /* Set the card ID early to avoid races with udev */ + p = strrchr(card->name, ' '); + if (p) { + snprintf(card->snd_card->id, sizeof(card->snd_card->id), + "%s", p + 1); + } if (!ma->has_speakers) return 0; From b28f941b55d1097350d924371787c02adfdb5ee4 Mon Sep 17 00:00:00 2001 From: Hector Martin Date: Tue, 5 Dec 2023 12:36:52 +0900 Subject: [PATCH 0148/3902] macaudio: Change device ID form Jxxx to AppleJxxx Signed-off-by: Hector Martin --- sound/soc/apple/macaudio.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sound/soc/apple/macaudio.c b/sound/soc/apple/macaudio.c index 7017c23447782d..4935d86c6cfd3d 100644 --- a/sound/soc/apple/macaudio.c +++ b/sound/soc/apple/macaudio.c @@ -1236,7 +1236,7 @@ static int macaudio_fixup_controls(struct snd_soc_card *card) p = strrchr(card->name, ' '); if (p) { snprintf(card->snd_card->id, sizeof(card->snd_card->id), - "%s", p + 1); + "Apple%s", p + 1); } if (!ma->has_speakers) From 68cc93b4859c5300bc40b5499a0fc862cf589e75 Mon Sep 17 00:00:00 2001 From: Hector Martin Date: Mon, 11 Dec 2023 22:34:15 +0900 Subject: [PATCH 0149/3902] macaudio: Turn please_blow_up_my_speakers into an int 1 enables new models, 2 further removes safeties. Mostly so that people who set it to 1 for early access and forget don't get stuck without safety nets. Signed-off-by: Hector Martin --- sound/soc/apple/macaudio.c | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/sound/soc/apple/macaudio.c b/sound/soc/apple/macaudio.c index 4935d86c6cfd3d..95d078b60731f0 100644 --- a/sound/soc/apple/macaudio.c +++ b/sound/soc/apple/macaudio.c @@ -137,8 +137,8 @@ struct macaudio_snd_data { }; -static bool please_blow_up_my_speakers; -module_param(please_blow_up_my_speakers, bool, 0644); +static int please_blow_up_my_speakers; +module_param(please_blow_up_my_speakers, int, 0644); MODULE_PARM_DESC(please_blow_up_my_speakers, "Allow unsafe or untested operating configurations"); SND_SOC_DAILINK_DEFS(primary, @@ -1165,7 +1165,7 @@ static int macaudio_late_probe(struct snd_soc_card *card) #define CHECK(call, pattern, value) \ { \ int ret = call(card, pattern, value); \ - if (ret < 1 && !please_blow_up_my_speakers) { \ + if (ret < 1 && (please_blow_up_my_speakers < 2)) { \ dev_err(card->dev, "%s on '%s': %d\n", #call, pattern, ret); \ return ret; \ } \ @@ -1205,7 +1205,7 @@ static int macaudio_set_speaker(struct snd_soc_card *card, const char *prefix, b CHECK_CONCAT(snd_soc_set_enum_kctl, "HPF Corner Frequency", tweeter ? "800 Hz" : "2 Hz"); - if (!please_blow_up_my_speakers) + if (please_blow_up_my_speakers < 2) CHECK_CONCAT(snd_soc_deactivate_kctl, "HPF Corner Frequency", 0); CHECK_CONCAT(snd_soc_set_enum_kctl, "OCE Handling", "Retry"); @@ -1215,7 +1215,7 @@ static int macaudio_set_speaker(struct snd_soc_card *card, const char *prefix, b /* TODO: check */ CHECK_CONCAT(snd_soc_set_enum_kctl, "DAC Analog Gain Select", "8.4 V Span"); - if (!please_blow_up_my_speakers) + if (please_blow_up_my_speakers < 2) CHECK_CONCAT(snd_soc_deactivate_kctl, "DAC Analog Gain Select", 0); /* TODO: HPF, needs new call to set */ @@ -1244,8 +1244,8 @@ static int macaudio_fixup_controls(struct snd_soc_card *card) switch(ma->cfg->speakers) { case SPKR_NONE: - WARN_ON(!please_blow_up_my_speakers); - return please_blow_up_my_speakers ? 0 : -EINVAL; + WARN_ON(please_blow_up_my_speakers < 2); + return please_blow_up_my_speakers >= 2 ? 0 : -EINVAL; case SPKR_1W: case SPKR_2W: CHECK(macaudio_set_speaker, "* ", false); From 99f07db1e799e8054e1a98ffee52d10717ba58ce Mon Sep 17 00:00:00 2001 From: Hector Martin Date: Mon, 11 Dec 2023 22:34:56 +0900 Subject: [PATCH 0150/3902] macaudio: Sync all gains with macOS We want the extra headroom, and speakersafetyd seems to be reliable. 3dB lower gain isn't going to buy us much safety at this point. Signed-off-by: Hector Martin --- sound/soc/apple/macaudio.c | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/sound/soc/apple/macaudio.c b/sound/soc/apple/macaudio.c index 95d078b60731f0..18eb6a430951df 100644 --- a/sound/soc/apple/macaudio.c +++ b/sound/soc/apple/macaudio.c @@ -1470,14 +1470,14 @@ static const struct snd_soc_dapm_route macaudio_dapm_routes[] = { /* enable amp speakers stereo gain safe_vol */ struct macaudio_platform_cfg macaudio_j180_cfg = { - false, AMP_SN012776, SPKR_1W1T, false, 4, -20, + false, AMP_SN012776, SPKR_1W1T, false, 10, -20, }; struct macaudio_platform_cfg macaudio_j274_cfg = { - true, AMP_TAS5770, SPKR_1W, false, 14, 0, /* TODO: safety */ + true, AMP_TAS5770, SPKR_1W, false, 20, -20, }; struct macaudio_platform_cfg macaudio_j293_cfg = { - false, AMP_TAS5770, SPKR_2W, true, 9, -20, /* TODO: check */ + false, AMP_TAS5770, SPKR_2W, true, 15, -20, }; struct macaudio_platform_cfg macaudio_j313_cfg = { @@ -1485,19 +1485,19 @@ struct macaudio_platform_cfg macaudio_j313_cfg = { }; struct macaudio_platform_cfg macaudio_j314_j316_cfg = { - false, AMP_SN012776, SPKR_2W1T, true, 9, -20, + false, AMP_SN012776, SPKR_2W1T, true, 15, -20, }; struct macaudio_platform_cfg macaudio_j37x_j47x_cfg = { - false, AMP_SN012776, SPKR_1W, false, 14, -20, + false, AMP_SN012776, SPKR_1W, false, 20, -20, }; struct macaudio_platform_cfg macaudio_j413_cfg = { - false, AMP_SN012776, SPKR_1W1T, true, 9, -20, + false, AMP_SN012776, SPKR_1W1T, true, 15, -20, }; struct macaudio_platform_cfg macaudio_j415_cfg = { - false, AMP_SN012776, SPKR_2W1T, true, 9, -20, + false, AMP_SN012776, SPKR_2W1T, true, 15, -20, }; struct macaudio_platform_cfg macaudio_j45x_cfg = { @@ -1505,7 +1505,7 @@ struct macaudio_platform_cfg macaudio_j45x_cfg = { }; struct macaudio_platform_cfg macaudio_j493_cfg = { - false, AMP_SN012776, SPKR_2W, true, 9, -20, + false, AMP_SN012776, SPKR_2W, true, 15, -20, }; struct macaudio_platform_cfg macaudio_fallback_cfg = { From 8fc465014e03eb82f2ecbe66d7a837c755fb91ff Mon Sep 17 00:00:00 2001 From: Hector Martin Date: Tue, 12 Dec 2023 19:57:23 +0900 Subject: [PATCH 0151/3902] macaudio: Fix CHECK return condition checking Signed-off-by: Hector Martin --- sound/soc/apple/macaudio.c | 35 ++++++++++++++++++++--------------- 1 file changed, 20 insertions(+), 15 deletions(-) diff --git a/sound/soc/apple/macaudio.c b/sound/soc/apple/macaudio.c index 18eb6a430951df..73a51e4a5fe554 100644 --- a/sound/soc/apple/macaudio.c +++ b/sound/soc/apple/macaudio.c @@ -1162,20 +1162,25 @@ static int macaudio_late_probe(struct snd_soc_card *card) return 0; } -#define CHECK(call, pattern, value) \ - { \ - int ret = call(card, pattern, value); \ - if (ret < 1 && (please_blow_up_my_speakers < 2)) { \ - dev_err(card->dev, "%s on '%s': %d\n", #call, pattern, ret); \ - return ret; \ - } \ - dev_dbg(card->dev, "%s on '%s': %d hits\n", #call, pattern, ret); \ +#define CHECK(call, pattern, value, min) \ + { \ + int ret = call(card, pattern, value); \ + int err = (ret >= 0 && ret < min) ? -ERANGE : ret; \ + if (err < 0) { \ + dev_err(card->dev, "%s on '%s': %d\n", #call, pattern, \ + ret); \ + if (please_blow_up_my_speakers < 2) \ + return err; \ + } else { \ + dev_dbg(card->dev, "%s on '%s': %d hits\n", #call, \ + pattern, ret); \ + } \ } #define CHECK_CONCAT(call, suffix, value) \ { \ snprintf(buf, sizeof(buf), "%s%s", prefix, suffix); \ - CHECK(call, buf, value); \ + CHECK(call, buf, value, 1); \ } static int macaudio_set_speaker(struct snd_soc_card *card, const char *prefix, bool tweeter) @@ -1248,16 +1253,16 @@ static int macaudio_fixup_controls(struct snd_soc_card *card) return please_blow_up_my_speakers >= 2 ? 0 : -EINVAL; case SPKR_1W: case SPKR_2W: - CHECK(macaudio_set_speaker, "* ", false); + CHECK(macaudio_set_speaker, "* ", false, 0); break; case SPKR_1W1T: - CHECK(macaudio_set_speaker, "* Tweeter ", true); - CHECK(macaudio_set_speaker, "* Woofer ", false); + CHECK(macaudio_set_speaker, "* Tweeter ", true, 0); + CHECK(macaudio_set_speaker, "* Woofer ", false, 0); break; case SPKR_2W1T: - CHECK(macaudio_set_speaker, "* Tweeter ", true); - CHECK(macaudio_set_speaker, "* Woofer 1 ", false); - CHECK(macaudio_set_speaker, "* Woofer 2 ", false); + CHECK(macaudio_set_speaker, "* Tweeter ", true, 0); + CHECK(macaudio_set_speaker, "* Woofer 1 ", false, 0); + CHECK(macaudio_set_speaker, "* Woofer 2 ", false, 0); break; } From d93bb624c6f89a727be865a8b5a079611e09e170 Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Tue, 12 Dec 2023 23:26:16 +0100 Subject: [PATCH 0152/3902] macaudio: Avoid matches against cs42l84's constrols On systems with cs42l84 headset codec "* " can't be used as control name pattern since it would match "Jack HPF Corner Frequency". Its control is not an enum and thus will always return -EINVAL. Signed-off-by: Janne Grunau --- sound/soc/apple/macaudio.c | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/sound/soc/apple/macaudio.c b/sound/soc/apple/macaudio.c index 73a51e4a5fe554..04217af964be24 100644 --- a/sound/soc/apple/macaudio.c +++ b/sound/soc/apple/macaudio.c @@ -1247,13 +1247,25 @@ static int macaudio_fixup_controls(struct snd_soc_card *card) if (!ma->has_speakers) return 0; + /* + * This needs some care to avoid matches against cs42l84's + * "Jack HPF Corner Frequency". + */ switch(ma->cfg->speakers) { case SPKR_NONE: WARN_ON(please_blow_up_my_speakers < 2); return please_blow_up_my_speakers >= 2 ? 0 : -EINVAL; case SPKR_1W: + /* only 1W stereo system (J313) is uses cs42l83 */ + if (ma->cfg->stereo) { + CHECK(macaudio_set_speaker, "* ", false, 0); + } else { + CHECK(macaudio_set_speaker, "", false, 0); + } + break; case SPKR_2W: - CHECK(macaudio_set_speaker, "* ", false, 0); + CHECK(macaudio_set_speaker, "* Front ", false, 0); + CHECK(macaudio_set_speaker, "* Rear ", false, 0); break; case SPKR_1W1T: CHECK(macaudio_set_speaker, "* Tweeter ", true, 0); From b554ae8f9eb8bcd50a81eb09bb57d24da5f921a5 Mon Sep 17 00:00:00 2001 From: Hector Martin Date: Thu, 14 Dec 2023 21:21:12 +0900 Subject: [PATCH 0153/3902] ASoC: apple: mca: Add delay after configuring clock Right after the early FE setup, ADMAC gets told to start the DMA. This can end up in a weird "slip" state with the channels transposed. Waiting a bit fixes this; presumably this allows the clock to stabilize. Signed-off-by: Hector Martin --- sound/soc/apple/mca.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/sound/soc/apple/mca.c b/sound/soc/apple/mca.c index 8b853079c74aae..ee4c18ae1b7f4f 100644 --- a/sound/soc/apple/mca.c +++ b/sound/soc/apple/mca.c @@ -225,6 +225,12 @@ static void mca_fe_early_trigger(struct snd_pcm_substream *substream, int cmd, FIELD_PREP(SERDES_CONF_SYNC_SEL, 0)); mca_modify(cl, serdes_conf, SERDES_CONF_SYNC_SEL, FIELD_PREP(SERDES_CONF_SYNC_SEL, cl->no + 1)); + /* + * ADMAC gets started right after this. This delay seems + * to be needed for that to be reliable, e.g. ensure the + * clock is stable? + */ + udelay(10); break; default: break; From fef2765953cff71df9ef64cd21d4aec2c34f3bde Mon Sep 17 00:00:00 2001 From: Hector Martin Date: Fri, 15 Dec 2023 20:27:42 +0900 Subject: [PATCH 0154/3902] macaudio: Disable j313 and j274 We are going to enable these out of band. If you are a distro packager: ** WARNING: ** ** YOU ABSOLUTELY NEED THIS PATCH IN YOUR LSP-PLUGINS PACKAGE ** https://github.com/lsp-plugins/lsp-dsp-lib/pull/20 Do NOT enable speakers without that patch, on any model. It can/will result in nasty noise that could damage them. Signed-off-by: Hector Martin --- sound/soc/apple/macaudio.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/sound/soc/apple/macaudio.c b/sound/soc/apple/macaudio.c index 04217af964be24..55dcb737fa8c96 100644 --- a/sound/soc/apple/macaudio.c +++ b/sound/soc/apple/macaudio.c @@ -1490,7 +1490,7 @@ struct macaudio_platform_cfg macaudio_j180_cfg = { false, AMP_SN012776, SPKR_1W1T, false, 10, -20, }; struct macaudio_platform_cfg macaudio_j274_cfg = { - true, AMP_TAS5770, SPKR_1W, false, 20, -20, + false, AMP_TAS5770, SPKR_1W, false, 20, -20, }; struct macaudio_platform_cfg macaudio_j293_cfg = { @@ -1498,7 +1498,7 @@ struct macaudio_platform_cfg macaudio_j293_cfg = { }; struct macaudio_platform_cfg macaudio_j313_cfg = { - true, AMP_TAS5770, SPKR_1W, true, 10, -20, + false, AMP_TAS5770, SPKR_1W, true, 10, -20, }; struct macaudio_platform_cfg macaudio_j314_j316_cfg = { From d9ce6a3813815e2371de2f5bed614bdf51860f3a Mon Sep 17 00:00:00 2001 From: Hector Martin Date: Sun, 17 Dec 2023 14:35:33 +0900 Subject: [PATCH 0155/3902] ASoC: apple: mca: Add more delay after configuring clock Sigh... hope this works. Signed-off-by: Hector Martin --- sound/soc/apple/mca.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sound/soc/apple/mca.c b/sound/soc/apple/mca.c index ee4c18ae1b7f4f..bfb8c58942e716 100644 --- a/sound/soc/apple/mca.c +++ b/sound/soc/apple/mca.c @@ -230,7 +230,7 @@ static void mca_fe_early_trigger(struct snd_pcm_substream *substream, int cmd, * to be needed for that to be reliable, e.g. ensure the * clock is stable? */ - udelay(10); + udelay(100); break; default: break; From 1fffdf5d39d555e7ef4ecb4a64c528040c3a8e63 Mon Sep 17 00:00:00 2001 From: Hector Martin Date: Tue, 19 Dec 2023 18:21:53 +0900 Subject: [PATCH 0156/3902] ASoC: apple: mca: More delay MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ¯\_(ツ)_/¯ Signed-off-by: Hector Martin --- sound/soc/apple/mca.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sound/soc/apple/mca.c b/sound/soc/apple/mca.c index bfb8c58942e716..01dacd10bd39ce 100644 --- a/sound/soc/apple/mca.c +++ b/sound/soc/apple/mca.c @@ -218,7 +218,7 @@ static void mca_fe_early_trigger(struct snd_pcm_substream *substream, int cmd, * Experiments suggest that it takes at most ~1 us * for the bit to clear, so wait 5 us for good measure. */ - udelay(5); + udelay(50); WARN_ON(readl_relaxed(cl->base + serdes_unit + REG_SERDES_STATUS) & SERDES_STATUS_RST); mca_modify(cl, serdes_conf, SERDES_CONF_SYNC_SEL, From ac9b05c17cf0126a3d09a172ec19da1e37f735aa Mon Sep 17 00:00:00 2001 From: Sasha Finkelstein Date: Sun, 17 Dec 2023 16:16:03 +0100 Subject: [PATCH 0157/3902] macaudio: Fix missing kconfig requirement Signed-off-by: Sasha Finkelstein --- sound/soc/apple/Kconfig | 1 + 1 file changed, 1 insertion(+) diff --git a/sound/soc/apple/Kconfig b/sound/soc/apple/Kconfig index 5bcfb5f025010d..d112aef692b961 100644 --- a/sound/soc/apple/Kconfig +++ b/sound/soc/apple/Kconfig @@ -19,6 +19,7 @@ config SND_SOC_APPLE_MACAUDIO select SND_SOC_TAS2770 if I2C select SND_SOC_CS42L83 if I2C select SND_SOC_CS42L84 if I2C + select REGULATOR_FIXED_VOLTAGE if REGULATOR help This option enables an ASoC machine-level driver for Apple Silicon Macs and it also enables the required SoC and codec drivers for overall From e5e3448ab10007f2ed789cd3d19aadd9f2c173cf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20Povi=C5=A1er?= Date: Fri, 17 Feb 2023 17:02:10 +0100 Subject: [PATCH 0158/3902] ALSA: Support nonatomic dmaengine PCMs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit *** possible v6.11 conflict: _snd_dmaengine_pcm_close Signed-off-by: Martin Povišer --- sound/core/pcm_dmaengine.c | 29 ++++++++++++++++++++++++++++- 1 file changed, 28 insertions(+), 1 deletion(-) diff --git a/sound/core/pcm_dmaengine.c b/sound/core/pcm_dmaengine.c index 931f31bb47c2d7..805191876fc923 100644 --- a/sound/core/pcm_dmaengine.c +++ b/sound/core/pcm_dmaengine.c @@ -22,6 +22,8 @@ struct dmaengine_pcm_runtime_data { struct dma_chan *dma_chan; dma_cookie_t cookie; + struct work_struct complete_wq; /* for nonatomic PCM */ + struct snd_pcm_substream *substream; unsigned int pos; }; @@ -147,6 +149,21 @@ static void dmaengine_pcm_dma_complete(void *arg) snd_pcm_period_elapsed(substream); } +static void dmaengine_pcm_dma_complete_nonatomic(struct work_struct *wq) +{ + struct dmaengine_pcm_runtime_data *prtd = \ + container_of(wq, struct dmaengine_pcm_runtime_data, complete_wq); + struct snd_pcm_substream *substream = prtd->substream; + dmaengine_pcm_dma_complete(substream); +} + +static void dmaengine_pcm_dma_complete_nonatomic_callback(void *arg) +{ + struct snd_pcm_substream *substream = arg; + struct dmaengine_pcm_runtime_data *prtd = substream_to_prtd(substream); + schedule_work(&prtd->complete_wq); +} + static int dmaengine_pcm_prepare_and_submit(struct snd_pcm_substream *substream) { struct dmaengine_pcm_runtime_data *prtd = substream_to_prtd(substream); @@ -169,7 +186,11 @@ static int dmaengine_pcm_prepare_and_submit(struct snd_pcm_substream *substream) if (!desc) return -ENOMEM; - desc->callback = dmaengine_pcm_dma_complete; + if (substream->pcm->nonatomic) + desc->callback = dmaengine_pcm_dma_complete_nonatomic_callback; + else + desc->callback = dmaengine_pcm_dma_complete; + desc->callback_param = substream; prtd->cookie = dmaengine_submit(desc); @@ -322,6 +343,10 @@ int snd_dmaengine_pcm_open(struct snd_pcm_substream *substream, if (!prtd) return -ENOMEM; + if (substream->pcm->nonatomic) + INIT_WORK(&prtd->complete_wq, dmaengine_pcm_dma_complete_nonatomic); + + prtd->substream = substream; prtd->dma_chan = chan; substream->runtime->private_data = prtd; @@ -361,6 +386,8 @@ static void __snd_dmaengine_pcm_close(struct snd_pcm_substream *substream, */ dmaengine_terminate_async(prtd->dma_chan); dmaengine_synchronize(prtd->dma_chan); + if (substream->pcm->nonatomic) + flush_work(&prtd->complete_wq); if (release_channel) dma_release_channel(prtd->dma_chan); kfree(prtd); From 277f972c287845a1fb4b27b8d856f0094e0100ed Mon Sep 17 00:00:00 2001 From: Hector Martin Date: Fri, 15 Dec 2023 20:38:32 +0900 Subject: [PATCH 0159/3902] READ COMMIT MESSAGE! macaudio: Enable first round of models Enables j313, j293, j493, j314, j414, j274, j375, j473, j474, j475 *** WARNING FOR DISTRO PACKAGERS WANTING TO APPLY THIS: *** *** YOU ABSOLUTELY NEED THIS PATCH IN YOUR LSP-PLUGINS PACKAGE *** https://github.com/lsp-plugins/lsp-dsp-lib/pull/20 Do NOT enable speakers without that patch, on any model. It can/will result in nasty noise that could damage them. Signed-off-by: Hector Martin --- sound/soc/apple/macaudio.c | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/sound/soc/apple/macaudio.c b/sound/soc/apple/macaudio.c index 55dcb737fa8c96..853f13b8db9ffc 100644 --- a/sound/soc/apple/macaudio.c +++ b/sound/soc/apple/macaudio.c @@ -1490,23 +1490,27 @@ struct macaudio_platform_cfg macaudio_j180_cfg = { false, AMP_SN012776, SPKR_1W1T, false, 10, -20, }; struct macaudio_platform_cfg macaudio_j274_cfg = { - false, AMP_TAS5770, SPKR_1W, false, 20, -20, + true, AMP_TAS5770, SPKR_1W, false, 20, -20, }; struct macaudio_platform_cfg macaudio_j293_cfg = { - false, AMP_TAS5770, SPKR_2W, true, 15, -20, + true, AMP_TAS5770, SPKR_2W, true, 15, -20, }; struct macaudio_platform_cfg macaudio_j313_cfg = { - false, AMP_TAS5770, SPKR_1W, true, 10, -20, + true, AMP_TAS5770, SPKR_1W, true, 10, -20, }; -struct macaudio_platform_cfg macaudio_j314_j316_cfg = { +struct macaudio_platform_cfg macaudio_j314_cfg = { + true, AMP_SN012776, SPKR_2W1T, true, 15, -20, +}; + +struct macaudio_platform_cfg macaudio_j316_cfg = { false, AMP_SN012776, SPKR_2W1T, true, 15, -20, }; struct macaudio_platform_cfg macaudio_j37x_j47x_cfg = { - false, AMP_SN012776, SPKR_1W, false, 20, -20, + true, AMP_SN012776, SPKR_1W, false, 20, -20, }; struct macaudio_platform_cfg macaudio_j413_cfg = { @@ -1522,7 +1526,7 @@ struct macaudio_platform_cfg macaudio_j45x_cfg = { }; struct macaudio_platform_cfg macaudio_j493_cfg = { - false, AMP_SN012776, SPKR_2W, true, 15, -20, + true, AMP_SN012776, SPKR_2W, true, 15, -20, }; struct macaudio_platform_cfg macaudio_fallback_cfg = { @@ -1558,9 +1562,9 @@ static const struct of_device_id macaudio_snd_device_id[] = { /* j313 AID4 tas5770 10 2× 1W */ { .compatible = "apple,j313-macaudio", .data = &macaudio_j313_cfg }, /* j314 AID8 sn012776 15 2× 2W+1T */ - { .compatible = "apple,j314-macaudio", .data = &macaudio_j314_j316_cfg }, + { .compatible = "apple,j314-macaudio", .data = &macaudio_j314_cfg }, /* j316 AID9 sn012776 15 2× 2W+1T */ - { .compatible = "apple,j316-macaudio", .data = &macaudio_j314_j316_cfg }, + { .compatible = "apple,j316-macaudio", .data = &macaudio_j316_cfg }, /* j375 AID10 sn012776 15 1× 1W */ { .compatible = "apple,j375-macaudio", .data = &macaudio_j37x_j47x_cfg }, /* j413 AID13 sn012776 15 2× 1W+1T */ From d1d53ef8ad1acc64318a1602899180258d37d05d Mon Sep 17 00:00:00 2001 From: Hector Martin Date: Fri, 15 Dec 2023 20:40:53 +0900 Subject: [PATCH 0160/3902] READ COMMIT MESSAGE! macaudio: Enable second round of models Enables j316, j413, j415, j416 *** WARNING FOR DISTRO PACKAGERS WANTING TO APPLY THIS: *** *** YOU ABSOLUTELY NEED THIS PATCH IN YOUR LSP-PLUGINS PACKAGE *** https://github.com/lsp-plugins/lsp-dsp-lib/pull/20 Do NOT enable speakers without that patch, on any model. It can/will result in nasty noise that could damage them. Signed-off-by: Hector Martin --- sound/soc/apple/macaudio.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/sound/soc/apple/macaudio.c b/sound/soc/apple/macaudio.c index 853f13b8db9ffc..808c76bc8d886a 100644 --- a/sound/soc/apple/macaudio.c +++ b/sound/soc/apple/macaudio.c @@ -1506,7 +1506,7 @@ struct macaudio_platform_cfg macaudio_j314_cfg = { }; struct macaudio_platform_cfg macaudio_j316_cfg = { - false, AMP_SN012776, SPKR_2W1T, true, 15, -20, + true, AMP_SN012776, SPKR_2W1T, true, 15, -20, }; struct macaudio_platform_cfg macaudio_j37x_j47x_cfg = { @@ -1514,11 +1514,11 @@ struct macaudio_platform_cfg macaudio_j37x_j47x_cfg = { }; struct macaudio_platform_cfg macaudio_j413_cfg = { - false, AMP_SN012776, SPKR_1W1T, true, 15, -20, + true, AMP_SN012776, SPKR_1W1T, true, 15, -20, }; struct macaudio_platform_cfg macaudio_j415_cfg = { - false, AMP_SN012776, SPKR_2W1T, true, 15, -20, + true, AMP_SN012776, SPKR_2W1T, true, 15, -20, }; struct macaudio_platform_cfg macaudio_j45x_cfg = { From 5da0590a49f562ab20e09dbc91801a6b82a7357c Mon Sep 17 00:00:00 2001 From: Sasha Finkelstein Date: Sat, 9 Nov 2024 17:50:03 +0100 Subject: [PATCH 0161/3902] soc: apple: rtkit: Add apple_rtkit_has_endpoint() To be used by RTKit consumers to check if an endpoint is present and should be enabled. Signed-off-by: Sasha Finkelstein --- drivers/soc/apple/rtkit.c | 6 ++++++ include/linux/soc/apple/rtkit.h | 8 ++++++++ 2 files changed, 14 insertions(+) diff --git a/drivers/soc/apple/rtkit.c b/drivers/soc/apple/rtkit.c index b8d4da147d23f7..88ddf3c36dc059 100644 --- a/drivers/soc/apple/rtkit.c +++ b/drivers/soc/apple/rtkit.c @@ -639,6 +639,12 @@ int apple_rtkit_poll(struct apple_rtkit *rtk) } EXPORT_SYMBOL_GPL(apple_rtkit_poll); +bool apple_rtkit_has_endpoint(struct apple_rtkit *rtk, u8 ep) +{ + return test_bit(ep, rtk->endpoints); +} +EXPORT_SYMBOL_GPL(apple_rtkit_has_endpoint); + int apple_rtkit_start_ep(struct apple_rtkit *rtk, u8 endpoint) { u64 msg; diff --git a/include/linux/soc/apple/rtkit.h b/include/linux/soc/apple/rtkit.h index 736f530180179b..9f3d0985150326 100644 --- a/include/linux/soc/apple/rtkit.h +++ b/include/linux/soc/apple/rtkit.h @@ -172,4 +172,12 @@ int apple_rtkit_send_message(struct apple_rtkit *rtk, u8 ep, u64 message, */ int apple_rtkit_poll(struct apple_rtkit *rtk); +/* + * Checks if an endpoint with a given index exists + * + * @rtk: RTKit reference + * @ep: endpoint to check for + */ +bool apple_rtkit_has_endpoint(struct apple_rtkit *rtk, u8 ep); + #endif /* _LINUX_APPLE_RTKIT_H_ */ From 3c01d052720411e89df35988ef1e95f632f7f864 Mon Sep 17 00:00:00 2001 From: Sasha Finkelstein Date: Sat, 9 Nov 2024 17:55:47 +0100 Subject: [PATCH 0162/3902] soc: apple: rtkit: Add tracekit endpoint. This system endpoint is advertised by AOP and also needs to be turned on for it to function. Signed-off-by: Sasha Finkelstein --- drivers/soc/apple/rtkit.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/drivers/soc/apple/rtkit.c b/drivers/soc/apple/rtkit.c index 88ddf3c36dc059..acf9eefd8b9a5f 100644 --- a/drivers/soc/apple/rtkit.c +++ b/drivers/soc/apple/rtkit.c @@ -22,6 +22,7 @@ enum { APPLE_RTKIT_EP_DEBUG = 3, APPLE_RTKIT_EP_IOREPORT = 4, APPLE_RTKIT_EP_OSLOG = 8, + APPLE_RTKIT_EP_TRACEKIT = 0xa, }; #define APPLE_RTKIT_MGMT_TYPE GENMASK_ULL(59, 52) @@ -191,6 +192,7 @@ static void apple_rtkit_management_rx_epmap(struct apple_rtkit *rtk, u64 msg) case APPLE_RTKIT_EP_DEBUG: case APPLE_RTKIT_EP_IOREPORT: case APPLE_RTKIT_EP_OSLOG: + case APPLE_RTKIT_EP_TRACEKIT: dev_dbg(rtk->dev, "RTKit: Starting system endpoint 0x%02x\n", ep); apple_rtkit_start_ep(rtk, ep); From f90b0ca988b7a988c1ff6cb5feb03ad39529c6ef Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Mon, 2 Dec 2024 08:46:06 +0100 Subject: [PATCH 0163/3902] fixup! ASoC: apple: Add macaudio machine driver Fixes following warnings after commit fd69dfe6789f4 ("ASoC: soc-pcm: Indicate warning if dpcm_playback/capture were used for availability limition"). | snd-soc-macaudio sound: both playback/capture are available, but not using playback_only flag (Secondary) | snd-soc-macaudio sound: dpcm_playback/capture are no longer needed, please use playback/capture_only instead | snd-soc-macaudio sound: both playback/capture are available, but not using capture_only flag (Speaker Sense) | snd-soc-macaudio sound: dpcm_playback/capture are no longer needed, please use playback/capture_only instead Fixes: fd69dfe6789f4 ("ASoC: soc-pcm: Indicate warning if dpcm_playback/capture were used for availability limition") Signed-off-by: Janne Grunau --- sound/soc/apple/macaudio.c | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/sound/soc/apple/macaudio.c b/sound/soc/apple/macaudio.c index 808c76bc8d886a..31f6ec45f80979 100644 --- a/sound/soc/apple/macaudio.c +++ b/sound/soc/apple/macaudio.c @@ -161,8 +161,6 @@ static struct snd_soc_dai_link macaudio_fe_links[] = { .name = "Primary", .stream_name = "Primary", .dynamic = 1, - .dpcm_playback = 1, - .dpcm_capture = 1, .dpcm_merged_rate = 1, .dpcm_merged_chan = 1, .dpcm_merged_format = 1, @@ -173,18 +171,18 @@ static struct snd_soc_dai_link macaudio_fe_links[] = { .name = "Secondary", .stream_name = "Secondary", .dynamic = 1, - .dpcm_playback = 1, .dpcm_merged_rate = 1, .dpcm_merged_chan = 1, .dpcm_merged_format = 1, .dai_fmt = MACAUDIO_DAI_FMT, + .playback_only = 1, SND_SOC_DAILINK_REG(secondary), }, { .name = "Speaker Sense", .stream_name = "Speaker Sense", + .capture_only = 1, .dynamic = 1, - .dpcm_capture = 1, .dai_fmt = (SND_SOC_DAIFMT_I2S | \ SND_SOC_DAIFMT_CBP_CFP | \ SND_SOC_DAIFMT_GATED | \ @@ -443,8 +441,6 @@ static int macaudio_parse_of_be_dai_link(struct macaudio_snd_data *ma, int ret, i; link->no_pcm = 1; - link->dpcm_playback = 1; - link->dpcm_capture = 1; link->dai_fmt = MACAUDIO_DAI_FMT; From 16c8fe78bb6eca8367b29c584ca0aae2fb66dd30 Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Sun, 12 Dec 2021 20:40:04 +0100 Subject: [PATCH 0164/3902] HID: add device IDs for Apple SPI HID devices Apple Silicon based laptop use SPI as transport for HID. Add support for SPI-based HID devices and and Apple keyboard and trackpad devices. Intel based laptops using the keyboard input driver applespi use the same HID over SPI protocol and can be supported later. This requires SPI keyboard/mouse HID types since Apple's intenal keyboards/trackpads use the same product id. Signed-off-by: Janne Grunau --- drivers/hid/hid-core.c | 3 +++ drivers/hid/hid-ids.h | 5 +++++ include/linux/hid.h | 6 +++++- 3 files changed, 13 insertions(+), 1 deletion(-) diff --git a/drivers/hid/hid-core.c b/drivers/hid/hid-core.c index a5b3a8ca2fcbc8..2f58d744ca3a02 100644 --- a/drivers/hid/hid-core.c +++ b/drivers/hid/hid-core.c @@ -2319,6 +2319,9 @@ int hid_connect(struct hid_device *hdev, unsigned int connect_mask) case BUS_SDW: bus = "SOUNDWIRE"; break; + case BUS_SPI: + bus = "SPI"; + break; case BUS_VIRTUAL: bus = "VIRTUAL"; break; diff --git a/drivers/hid/hid-ids.h b/drivers/hid/hid-ids.h index c4589075a5ed68..69ede73bf13219 100644 --- a/drivers/hid/hid-ids.h +++ b/drivers/hid/hid-ids.h @@ -93,6 +93,7 @@ #define USB_VENDOR_ID_APPLE 0x05ac #define BT_VENDOR_ID_APPLE 0x004c +#define SPI_VENDOR_ID_APPLE 0x05ac #define USB_DEVICE_ID_APPLE_MIGHTYMOUSE 0x0304 #define USB_DEVICE_ID_APPLE_MAGICMOUSE 0x030d #define USB_DEVICE_ID_APPLE_MAGICMOUSE2 0x0269 @@ -197,6 +198,10 @@ #define USB_DEVICE_ID_APPLE_IRCONTROL5 0x8243 #define USB_DEVICE_ID_APPLE_TOUCHBAR_BACKLIGHT 0x8102 #define USB_DEVICE_ID_APPLE_TOUCHBAR_DISPLAY 0x8302 +#define SPI_DEVICE_ID_APPLE_MACBOOK_AIR_2020 0x0281 +#define SPI_DEVICE_ID_APPLE_MACBOOK_PRO13_2020 0x0341 +#define SPI_DEVICE_ID_APPLE_MACBOOK_PRO14_2021 0x0342 +#define SPI_DEVICE_ID_APPLE_MACBOOK_PRO16_2021 0x0343 #define USB_VENDOR_ID_ASETEK 0x2433 #define USB_DEVICE_ID_ASETEK_INVICTA 0xf300 diff --git a/include/linux/hid.h b/include/linux/hid.h index a4ddb94e3ee563..8c6d3124c20c9c 100644 --- a/include/linux/hid.h +++ b/include/linux/hid.h @@ -625,7 +625,9 @@ struct hid_input { enum hid_type { HID_TYPE_OTHER = 0, HID_TYPE_USBMOUSE, - HID_TYPE_USBNONE + HID_TYPE_USBNONE, + HID_TYPE_SPI_KEYBOARD, + HID_TYPE_SPI_MOUSE, }; enum hid_battery_status { @@ -786,6 +788,8 @@ struct hid_descriptor { .bus = BUS_BLUETOOTH, .vendor = (ven), .product = (prod) #define HID_I2C_DEVICE(ven, prod) \ .bus = BUS_I2C, .vendor = (ven), .product = (prod) +#define HID_SPI_DEVICE(ven, prod) \ + .bus = BUS_SPI, .vendor = (ven), .product = (prod) #define HID_REPORT_ID(rep) \ .report_type = (rep) From 48563905c9b294bc62b96d8acc75685e1c7f1cfb Mon Sep 17 00:00:00 2001 From: Hector Martin Date: Fri, 8 Jul 2022 00:29:43 +0900 Subject: [PATCH 0165/3902] HID: add HOST vendor/device IDs for Apple MTP devices Apple M2* chips have an embedded MTP processor that handles all HID functions, and does not go over a traditional bus like SPI. The devices still have real IDs, so add them here. Signed-off-by: Hector Martin --- drivers/hid/hid-ids.h | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/drivers/hid/hid-ids.h b/drivers/hid/hid-ids.h index 69ede73bf13219..63aba2dbb3b664 100644 --- a/drivers/hid/hid-ids.h +++ b/drivers/hid/hid-ids.h @@ -94,6 +94,7 @@ #define USB_VENDOR_ID_APPLE 0x05ac #define BT_VENDOR_ID_APPLE 0x004c #define SPI_VENDOR_ID_APPLE 0x05ac +#define HOST_VENDOR_ID_APPLE 0x05ac #define USB_DEVICE_ID_APPLE_MIGHTYMOUSE 0x0304 #define USB_DEVICE_ID_APPLE_MAGICMOUSE 0x030d #define USB_DEVICE_ID_APPLE_MAGICMOUSE2 0x0269 @@ -202,6 +203,10 @@ #define SPI_DEVICE_ID_APPLE_MACBOOK_PRO13_2020 0x0341 #define SPI_DEVICE_ID_APPLE_MACBOOK_PRO14_2021 0x0342 #define SPI_DEVICE_ID_APPLE_MACBOOK_PRO16_2021 0x0343 +#define HOST_DEVICE_ID_APPLE_MACBOOK_AIR13_2022 0x0351 +#define HOST_DEVICE_ID_APPLE_MACBOOK_PRO14_2023 0x0352 +#define HOST_DEVICE_ID_APPLE_MACBOOK_PRO16_2023 0x0353 +#define HOST_DEVICE_ID_APPLE_MACBOOK_PRO13_2022 0x0354 #define USB_VENDOR_ID_ASETEK 0x2433 #define USB_DEVICE_ID_ASETEK_INVICTA 0xf300 From 489aff3c0fce5274009bc08314ecc2d2b9c5156a Mon Sep 17 00:00:00 2001 From: Hector Martin Date: Fri, 8 Jul 2022 02:06:15 +0900 Subject: [PATCH 0166/3902] HID: core: Handle HOST bus type when announcing devices Signed-off-by: Hector Martin --- drivers/hid/hid-core.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/drivers/hid/hid-core.c b/drivers/hid/hid-core.c index 2f58d744ca3a02..4a01cef476547d 100644 --- a/drivers/hid/hid-core.c +++ b/drivers/hid/hid-core.c @@ -2322,6 +2322,9 @@ int hid_connect(struct hid_device *hdev, unsigned int connect_mask) case BUS_SPI: bus = "SPI"; break; + case BUS_HOST: + bus = "HOST"; + break; case BUS_VIRTUAL: bus = "VIRTUAL"; break; From 6aa9e8008d790285f86dbbeac4523bb507e57df5 Mon Sep 17 00:00:00 2001 From: Hector Martin Date: Mon, 10 Apr 2023 22:44:44 +0900 Subject: [PATCH 0167/3902] HID: Bump maximum report size to 16384 This maximum is arbitrary. Recent Apple devices have some vendor-defined reports with 16384 here which fail to parse without this, so let's bump it to that. This value is used as follows: report->size += parser->global.report_size * parser->global.report_count; [...] /* Total size check: Allow for possible report index byte */ if (report->size > (max_buffer_size - 1) << 3) { hid_err(parser->device, "report is too long\n"); return -1; } All of these fields are unsigned integers, and report_count is bounded by HID_MAX_USAGES (12288). Therefore, as long as the respective maximums do not overflow an unsigned integer (let's say a signed integer just in case), we're safe. This holds for 16384. Signed-off-by: Hector Martin --- drivers/hid/hid-core.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/drivers/hid/hid-core.c b/drivers/hid/hid-core.c index 4a01cef476547d..3b1b999e7e5150 100644 --- a/drivers/hid/hid-core.c +++ b/drivers/hid/hid-core.c @@ -468,7 +468,10 @@ static int hid_parser_global(struct hid_parser *parser, struct hid_item *item) case HID_GLOBAL_ITEM_TAG_REPORT_SIZE: parser->global.report_size = item_udata(item); - if (parser->global.report_size > 256) { + /* Arbitrary maximum. Some Apple devices have 16384 here. + * This * HID_MAX_USAGES must fit in a signed integer. + */ + if (parser->global.report_size > 16384) { hid_err(parser->device, "invalid report_size %d\n", parser->global.report_size); return -1; From e30bfdb1ea0ed6d0407943649768033a07c36a67 Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Thu, 16 Dec 2021 21:15:31 +0100 Subject: [PATCH 0168/3902] HID: apple: Bind Apple silicon SPI devices Apple MacBook keyboards started using HID over SPI in 2015. With the addition of the SPI HID transport they can be supported by this driver. Support all product ids over with the Apple SPI vendor id for now. The Macbook Pro (M1, 13-inch, 2020) uses the same function key mapping as other Macbook Pros with touchbar and dedicated ESC key. Apple silicon Macbooks use the same function key mapping as the 2021 and later Magic Keyboards. Signed-off-by: Janne Grunau --- drivers/hid/hid-apple.c | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/drivers/hid/hid-apple.c b/drivers/hid/hid-apple.c index 57da4f86a9fa7f..a3f3dd36b3b2bb 100644 --- a/drivers/hid/hid-apple.c +++ b/drivers/hid/hid-apple.c @@ -473,6 +473,18 @@ static int hidinput_apple_event(struct hid_device *hid, struct input_dev *input, asc->fn_on = !!value; if (real_fnmode) { + switch (hid->bus) { + case BUS_SPI: + switch (hid->product) { + case SPI_DEVICE_ID_APPLE_MACBOOK_PRO13_2020: + table = macbookpro_dedicated_esc_fn_keys; + break; + default: + table = magic_keyboard_2021_and_2024_fn_keys; + break; + } + break; + default: switch (hid->product) { case USB_DEVICE_ID_APPLE_ALU_WIRELESS_ANSI: case USB_DEVICE_ID_APPLE_ALU_WIRELESS_ISO: @@ -521,6 +533,7 @@ static int hidinput_apple_event(struct hid_device *hid, struct input_dev *input, else table = apple_fn_keys; } + } trans = apple_find_translation(table, code); @@ -938,6 +951,10 @@ static int apple_probe(struct hid_device *hdev, struct apple_sc *asc; int ret; + if (id->bus == BUS_SPI && id->vendor == SPI_VENDOR_ID_APPLE && + hdev->type != HID_TYPE_SPI_KEYBOARD) + return -ENODEV; + asc = devm_kzalloc(&hdev->dev, sizeof(*asc), GFP_KERNEL); if (asc == NULL) { hid_err(hdev, "can't alloc apple descriptor\n"); @@ -1192,6 +1209,8 @@ static const struct hid_device_id apple_devices[] = { .driver_data = APPLE_NUMLOCK_EMULATION | APPLE_HAS_FN }, { HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_GEYSER1_TP_ONLY), .driver_data = APPLE_NUMLOCK_EMULATION | APPLE_HAS_FN }, + { HID_SPI_DEVICE(SPI_VENDOR_ID_APPLE, HID_ANY_ID), + .driver_data = APPLE_HAS_FN | APPLE_ISO_TILDE_QUIRK }, { HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_MAGIC_KEYBOARD_2021), .driver_data = APPLE_HAS_FN | APPLE_ISO_TILDE_QUIRK | APPLE_RDESC_BATTERY }, { HID_BLUETOOTH_DEVICE(BT_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_MAGIC_KEYBOARD_2021), From c0cd2d1ce7cb02566b73808c7a6172dbe2e37d72 Mon Sep 17 00:00:00 2001 From: Hector Martin Date: Fri, 8 Jul 2022 02:12:24 +0900 Subject: [PATCH 0169/3902] HID: apple: Bind to HOST devices for MTP We use BUS_HOST for MTP HID subdevices Signed-off-by: Hector Martin --- drivers/hid/hid-apple.c | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/drivers/hid/hid-apple.c b/drivers/hid/hid-apple.c index a3f3dd36b3b2bb..8895014fe8a495 100644 --- a/drivers/hid/hid-apple.c +++ b/drivers/hid/hid-apple.c @@ -474,9 +474,11 @@ static int hidinput_apple_event(struct hid_device *hid, struct input_dev *input, if (real_fnmode) { switch (hid->bus) { + case BUS_HOST: case BUS_SPI: switch (hid->product) { - case SPI_DEVICE_ID_APPLE_MACBOOK_PRO13_2020: + case SPI_DEVICE_ID_APPLE_MACBOOK_PRO13_2020: + case HOST_DEVICE_ID_APPLE_MACBOOK_PRO13_2022: table = macbookpro_dedicated_esc_fn_keys; break; default: @@ -951,7 +953,7 @@ static int apple_probe(struct hid_device *hdev, struct apple_sc *asc; int ret; - if (id->bus == BUS_SPI && id->vendor == SPI_VENDOR_ID_APPLE && + if ((id->bus == BUS_SPI || id->bus == BUS_HOST) && id->vendor == SPI_VENDOR_ID_APPLE && hdev->type != HID_TYPE_SPI_KEYBOARD) return -ENODEV; @@ -1223,6 +1225,8 @@ static const struct hid_device_id apple_devices[] = { .driver_data = APPLE_HAS_FN | APPLE_ISO_TILDE_QUIRK | APPLE_RDESC_BATTERY }, { HID_BLUETOOTH_DEVICE(BT_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_MAGIC_KEYBOARD_NUMPAD_2021), .driver_data = APPLE_HAS_FN | APPLE_ISO_TILDE_QUIRK }, + { HID_DEVICE(BUS_HOST, HID_GROUP_ANY, HOST_VENDOR_ID_APPLE, HID_ANY_ID), + .driver_data = APPLE_HAS_FN | APPLE_ISO_TILDE_QUIRK }, { HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_MAGIC_KEYBOARD_2024), .driver_data = APPLE_HAS_FN | APPLE_ISO_TILDE_QUIRK | APPLE_RDESC_BATTERY }, { HID_BLUETOOTH_DEVICE(BT_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_MAGIC_KEYBOARD_2024), From 892727dc6ef23d4ffd61d99af5ff14b54a674571 Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Mon, 7 Apr 2025 20:04:23 +0200 Subject: [PATCH 0170/3902] DO NOT MERGE: HID: apple: Add fnmode which ignores function keys This mode is added to ease adding new xkeyboard configs for Apple silicon Macbook keyboards. The existing ones have strange quirks [1] and as the keyboard sends a key code for the 'fn' there is desire to use it as additional modifier [2]. [1]: https://pagure.io/fedora-asahi/remix-bugs/issue/17 [2]: https://asahilinux.org/docs/project/help-wanted/ (Keyboard layout cleanup) Signed-off-by: Janne Grunau --- drivers/hid/hid-apple.c | 35 +++++++++++++++++++++++++++++++---- 1 file changed, 31 insertions(+), 4 deletions(-) diff --git a/drivers/hid/hid-apple.c b/drivers/hid/hid-apple.c index 8895014fe8a495..6d80a7e3cf185c 100644 --- a/drivers/hid/hid-apple.c +++ b/drivers/hid/hid-apple.c @@ -54,10 +54,16 @@ #define APPLE_MAGIC_REPORT_ID_POWER 3 #define APPLE_MAGIC_REPORT_ID_BRIGHTNESS 1 +// DO NOT UPSTREAM: +// temporary Fn key mode until xkeyboard-config has keyboard layouts with media +// key mappings. At that point auto mode can drop function key mappings and this +// mode can be dropped. +#define FKEYS_IGNORE 5 + static unsigned int fnmode = 3; module_param(fnmode, uint, 0644); MODULE_PARM_DESC(fnmode, "Mode of fn key on Apple keyboards (0 = disabled, " - "1 = fkeyslast, 2 = fkeysfirst, [3] = auto, 4 = fkeysdisabled)"); + "1 = fkeyslast, 2 = fkeysfirst, [3] = auto, 4 = fkeysdisabled, 5 = fkeysignore))"); static int iso_layout = -1; module_param(iso_layout, int, 0644); @@ -277,6 +283,16 @@ static const struct apple_key_translation apple_fn_keys[] = { { } }; +static const struct apple_key_translation apple_fn_keys_minimal[] = { + { KEY_BACKSPACE, KEY_DELETE }, + { KEY_ENTER, KEY_INSERT }, + { KEY_UP, KEY_PAGEUP }, + { KEY_DOWN, KEY_PAGEDOWN }, + { KEY_LEFT, KEY_HOME }, + { KEY_RIGHT, KEY_END }, + { } +}; + static const struct apple_key_translation powerbook_fn_keys[] = { { KEY_BACKSPACE, KEY_DELETE }, { KEY_F1, KEY_BRIGHTNESSDOWN, APPLE_FLAG_FKEY }, @@ -433,6 +449,8 @@ static int hidinput_apple_event(struct hid_device *hid, struct input_dev *input, real_fnmode = 2; else real_fnmode = 1; + } else if (fnmode == FKEYS_IGNORE) { + real_fnmode = 2; } else { real_fnmode = fnmode; } @@ -482,7 +500,10 @@ static int hidinput_apple_event(struct hid_device *hid, struct input_dev *input, table = macbookpro_dedicated_esc_fn_keys; break; default: - table = magic_keyboard_2021_and_2024_fn_keys; + if (fnmode == FKEYS_IGNORE) + table = apple_fn_keys_minimal; + else + table = magic_keyboard_2021_and_2024_fn_keys; break; } break; @@ -719,6 +740,7 @@ static void apple_setup_input(struct input_dev *input) /* Enable all needed keys */ apple_setup_key_translation(input, apple_fn_keys); + apple_setup_key_translation(input, apple_fn_keys_minimal); apple_setup_key_translation(input, powerbook_fn_keys); apple_setup_key_translation(input, powerbook_numlock_keys); apple_setup_key_translation(input, apple_iso_keyboard); @@ -957,6 +979,11 @@ static int apple_probe(struct hid_device *hdev, hdev->type != HID_TYPE_SPI_KEYBOARD) return -ENODEV; + // key remapping will happen in xkeyboard-config so ignore + // APPLE_ISO_TILDE_QUIRK + if ((id->bus == BUS_SPI || id->bus == BUS_HOST) && fnmode == FKEYS_IGNORE) + quirks &= ~APPLE_ISO_TILDE_QUIRK; + asc = devm_kzalloc(&hdev->dev, sizeof(*asc), GFP_KERNEL); if (asc == NULL) { hid_err(hdev, "can't alloc apple descriptor\n"); @@ -1212,7 +1239,7 @@ static const struct hid_device_id apple_devices[] = { { HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_GEYSER1_TP_ONLY), .driver_data = APPLE_NUMLOCK_EMULATION | APPLE_HAS_FN }, { HID_SPI_DEVICE(SPI_VENDOR_ID_APPLE, HID_ANY_ID), - .driver_data = APPLE_HAS_FN | APPLE_ISO_TILDE_QUIRK }, + .driver_data = APPLE_HAS_FN | APPLE_ISO_TILDE_QUIRK }, // TODO: remove APPLE_ISO_TILDE_QUIRK { HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_MAGIC_KEYBOARD_2021), .driver_data = APPLE_HAS_FN | APPLE_ISO_TILDE_QUIRK | APPLE_RDESC_BATTERY }, { HID_BLUETOOTH_DEVICE(BT_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_MAGIC_KEYBOARD_2021), @@ -1226,7 +1253,7 @@ static const struct hid_device_id apple_devices[] = { { HID_BLUETOOTH_DEVICE(BT_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_MAGIC_KEYBOARD_NUMPAD_2021), .driver_data = APPLE_HAS_FN | APPLE_ISO_TILDE_QUIRK }, { HID_DEVICE(BUS_HOST, HID_GROUP_ANY, HOST_VENDOR_ID_APPLE, HID_ANY_ID), - .driver_data = APPLE_HAS_FN | APPLE_ISO_TILDE_QUIRK }, + .driver_data = APPLE_HAS_FN | APPLE_ISO_TILDE_QUIRK }, // TODO: remove APPLE_ISO_TILDE_QUIRK { HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_MAGIC_KEYBOARD_2024), .driver_data = APPLE_HAS_FN | APPLE_ISO_TILDE_QUIRK | APPLE_RDESC_BATTERY }, { HID_BLUETOOTH_DEVICE(BT_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_MAGIC_KEYBOARD_2024), From 9ecde8bf0702d67ffac7f1c2a6715c7330eef848 Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Thu, 16 Dec 2021 00:10:51 +0100 Subject: [PATCH 0171/3902] HID: magicmouse: use a define of the max number of touch contacts Signed-off-by: Janne Grunau --- drivers/hid/hid-magicmouse.c | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/drivers/hid/hid-magicmouse.c b/drivers/hid/hid-magicmouse.c index 7d4a25c6de0eb7..82868769f3cf01 100644 --- a/drivers/hid/hid-magicmouse.c +++ b/drivers/hid/hid-magicmouse.c @@ -62,6 +62,8 @@ MODULE_PARM_DESC(report_undeciphered, "Report undeciphered multi-touch state fie #define DOUBLE_REPORT_ID 0xf7 #define USB_BATTERY_TIMEOUT_SEC 60 +#define MAX_CONTACTS 16 + /* These definitions are not precise, but they're close enough. (Bits * 0x03 seem to indicate the aspect ratio of the touch, bits 0x70 seem * to be some kind of bit mask -- 0x20 may be a near-field reading, @@ -143,8 +145,8 @@ struct magicmouse_sc { u8 size; bool scroll_x_active; bool scroll_y_active; - } touches[16]; - int tracking_ids[16]; + } touches[MAX_CONTACTS]; + int tracking_ids[MAX_CONTACTS]; struct hid_device *hdev; struct delayed_work work; @@ -615,7 +617,7 @@ static int magicmouse_setup_input(struct input_dev *input, struct hid_device *hd __set_bit(EV_ABS, input->evbit); - error = input_mt_init_slots(input, 16, mt_flags); + error = input_mt_init_slots(input, MAX_CONTACTS, mt_flags); if (error) return error; input_set_abs_params(input, ABS_MT_TOUCH_MAJOR, 0, 255 << 2, From 5db7b47f31e6c92e8567f43728dd91aea4f0d2f2 Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Thu, 16 Dec 2021 00:12:35 +0100 Subject: [PATCH 0172/3902] HID: magicmouse: use struct input_mt_pos for X/Y Signed-off-by: Janne Grunau --- drivers/hid/hid-magicmouse.c | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/drivers/hid/hid-magicmouse.c b/drivers/hid/hid-magicmouse.c index 82868769f3cf01..37de8aa9338f72 100644 --- a/drivers/hid/hid-magicmouse.c +++ b/drivers/hid/hid-magicmouse.c @@ -121,6 +121,7 @@ MODULE_PARM_DESC(report_undeciphered, "Report undeciphered multi-touch state fie * @ntouches: Number of touches in most recent touch report. * @scroll_accel: Number of consecutive scroll motions. * @scroll_jiffies: Time of last scroll motion. + * @pos: multi touch position data of the last report. * @touches: Most recent data for a touch, indexed by tracking ID. * @tracking_ids: Mapping of current touch input data to @touches. * @hdev: Pointer to the underlying HID device. @@ -135,9 +136,8 @@ struct magicmouse_sc { int scroll_accel; unsigned long scroll_jiffies; + struct input_mt_pos pos[MAX_CONTACTS]; struct { - short x; - short y; short scroll_x; short scroll_y; short scroll_x_hr; @@ -194,7 +194,7 @@ static void magicmouse_emit_buttons(struct magicmouse_sc *msc, int state) } else if (last_state != 0) { state = last_state; } else if ((id = magicmouse_firm_touch(msc)) >= 0) { - int x = msc->touches[id].x; + int x = msc->pos[id].x; if (x < middle_button_start) state = 1; else if (x > middle_button_stop) @@ -258,8 +258,8 @@ static void magicmouse_emit_touch(struct magicmouse_sc *msc, int raw_id, u8 *tda /* Store tracking ID and other fields. */ msc->tracking_ids[raw_id] = id; - msc->touches[id].x = x; - msc->touches[id].y = y; + msc->pos[id].x = x; + msc->pos[id].y = y; msc->touches[id].size = size; /* If requested, emulate a scroll wheel by detecting small From 786295d69cf79940dc3ae76a279b9db959f1c782 Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Thu, 16 Dec 2021 00:15:30 +0100 Subject: [PATCH 0173/3902] HID: magicmouse: use ops function pointers for input functionality Will be used for supporting MacBook trackpads connected via SPI. Signed-off-by: Janne Grunau --- drivers/hid/hid-magicmouse.c | 32 +++++++++++++++++++++++++++++++- 1 file changed, 31 insertions(+), 1 deletion(-) diff --git a/drivers/hid/hid-magicmouse.c b/drivers/hid/hid-magicmouse.c index 37de8aa9338f72..d235fcf8fe6c68 100644 --- a/drivers/hid/hid-magicmouse.c +++ b/drivers/hid/hid-magicmouse.c @@ -114,6 +114,13 @@ MODULE_PARM_DESC(report_undeciphered, "Report undeciphered multi-touch state fie #define TRACKPAD2_RES_Y \ ((TRACKPAD2_MAX_Y - TRACKPAD2_MIN_Y) / (TRACKPAD2_DIMENSION_Y / 100)) + +struct magicmouse_input_ops { + int (*raw_event)(struct hid_device *hdev, + struct hid_report *report, u8 *data, int size); + int (*setup_input)(struct input_dev *input, struct hid_device *hdev); +}; + /** * struct magicmouse_sc - Tracks Magic Mouse-specific data. * @input: Input device through which we report events. @@ -127,6 +134,7 @@ MODULE_PARM_DESC(report_undeciphered, "Report undeciphered multi-touch state fie * @hdev: Pointer to the underlying HID device. * @work: Workqueue to handle initialization retry for quirky devices. * @battery_timer: Timer for obtaining battery level information. + * @input_ops: Input ops based on device type. */ struct magicmouse_sc { struct input_dev *input; @@ -151,6 +159,7 @@ struct magicmouse_sc { struct hid_device *hdev; struct delayed_work work; struct timer_list battery_timer; + struct magicmouse_input_ops input_ops; }; static int magicmouse_firm_touch(struct magicmouse_sc *msc) @@ -389,6 +398,14 @@ static int magicmouse_raw_event(struct hid_device *hdev, struct hid_report *report, u8 *data, int size) { struct magicmouse_sc *msc = hid_get_drvdata(hdev); + + return msc->input_ops.raw_event(hdev, report, data, size); +} + +static int magicmouse_raw_event_usb(struct hid_device *hdev, + struct hid_report *report, u8 *data, int size) +{ + struct magicmouse_sc *msc = hid_get_drvdata(hdev); struct input_dev *input = msc->input; int x = 0, y = 0, ii, clicks = 0, npoints; @@ -538,7 +555,17 @@ static int magicmouse_event(struct hid_device *hdev, struct hid_field *field, return 0; } -static int magicmouse_setup_input(struct input_dev *input, struct hid_device *hdev) + +static int magicmouse_setup_input(struct input_dev *input, + struct hid_device *hdev) +{ + struct magicmouse_sc *msc = hid_get_drvdata(hdev); + + return msc->input_ops.setup_input(input, hdev); +} + +static int magicmouse_setup_input_usb(struct input_dev *input, + struct hid_device *hdev) { int error; int mt_flags = 0; @@ -860,6 +887,9 @@ static int magicmouse_probe(struct hid_device *hdev, return -ENOMEM; } + msc->input_ops.raw_event = magicmouse_raw_event_usb; + msc->input_ops.setup_input = magicmouse_setup_input_usb; + msc->scroll_accel = SCROLL_ACCEL_DEFAULT; msc->hdev = hdev; INIT_DEFERRABLE_WORK(&msc->work, magicmouse_enable_mt_work); From b4222f3a5c7ea6299b50b3fd1d57cd71438ea305 Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Thu, 16 Dec 2021 01:17:48 +0100 Subject: [PATCH 0174/3902] HID: magicmouse: add support for Macbook trackpads The trackpads in Macbooks beginning in 2015 are HID devices connected over SPI. On Intel Macbooks they are currently supported by applespi.c. This chang adds support for the trackpads on Apple Silicon Macbooks starting in late 2020. They use a new HID over SPI transport driver. The touch report format differs from USB/BT Magic Trackpads. It is the same format as the type 4 format supported by bcm5974.c. Signed-off-by: Janne Grunau --- drivers/hid/Kconfig | 3 +- drivers/hid/hid-magicmouse.c | 273 ++++++++++++++++++++++++++++++++++- 2 files changed, 267 insertions(+), 9 deletions(-) diff --git a/drivers/hid/Kconfig b/drivers/hid/Kconfig index 04420a713be085..8316b5e385c9fb 100644 --- a/drivers/hid/Kconfig +++ b/drivers/hid/Kconfig @@ -730,7 +730,8 @@ config HID_MAGICMOUSE Support for the Apple Magic Mouse/Trackpad multi-touch. Say Y here if you want support for the multi-touch features of the - Apple Wireless "Magic" Mouse and the Apple Wireless "Magic" Trackpad. + Apple Wireless "Magic" Mouse, the Apple Wireless "Magic" Trackpad and + force touch Trackpads in Macbooks starting from 2015. config HID_MALTRON tristate "Maltron L90 keyboard" diff --git a/drivers/hid/hid-magicmouse.c b/drivers/hid/hid-magicmouse.c index d235fcf8fe6c68..f00b508c7a54dc 100644 --- a/drivers/hid/hid-magicmouse.c +++ b/drivers/hid/hid-magicmouse.c @@ -60,6 +60,7 @@ MODULE_PARM_DESC(report_undeciphered, "Report undeciphered multi-touch state fie #define MOUSE_REPORT_ID 0x29 #define MOUSE2_REPORT_ID 0x12 #define DOUBLE_REPORT_ID 0xf7 +#define SPI_REPORT_ID 0x02 #define USB_BATTERY_TIMEOUT_SEC 60 #define MAX_CONTACTS 16 @@ -114,6 +115,18 @@ MODULE_PARM_DESC(report_undeciphered, "Report undeciphered multi-touch state fie #define TRACKPAD2_RES_Y \ ((TRACKPAD2_MAX_Y - TRACKPAD2_MIN_Y) / (TRACKPAD2_DIMENSION_Y / 100)) +#define J314_TP_DIMENSION_X (float)13000 +#define J314_TP_MIN_X -5900 +#define J314_TP_MAX_X 6500 +#define J314_TP_RES_X \ + ((J314_TP_MAX_X - J314_TP_MIN_X) / (J314_TP_DIMENSION_X / 100)) +#define J314_TP_DIMENSION_Y (float)8100 +#define J314_TP_MIN_Y -200 +#define J314_TP_MAX_Y 7400 +#define J314_TP_RES_Y \ + ((J314_TP_MAX_Y - J314_TP_MIN_Y) / (J314_TP_DIMENSION_Y / 100)) + +#define J314_TP_MAX_FINGER_ORIENTATION 16384 struct magicmouse_input_ops { int (*raw_event)(struct hid_device *hdev, @@ -537,6 +550,154 @@ static int magicmouse_raw_event_usb(struct hid_device *hdev, return 1; } +/** + * struct tp_finger - single trackpad finger structure, le16-aligned + * + * @unknown1: unknown + * @unknown2: unknown + * @abs_x: absolute x coordinate + * @abs_y: absolute y coordinate + * @rel_x: relative x coordinate + * @rel_y: relative y coordinate + * @tool_major: tool area, major axis + * @tool_minor: tool area, minor axis + * @orientation: 16384 when point, else 15 bit angle + * @touch_major: touch area, major axis + * @touch_minor: touch area, minor axis + * @unused: zeros + * @pressure: pressure on forcetouch touchpad + * @multi: one finger: varies, more fingers: constant + */ +struct tp_finger { + __le16 unknown1; + __le16 unknown2; + __le16 abs_x; + __le16 abs_y; + __le16 rel_x; + __le16 rel_y; + __le16 tool_major; + __le16 tool_minor; + __le16 orientation; + __le16 touch_major; + __le16 touch_minor; + __le16 unused[2]; + __le16 pressure; + __le16 multi; +} __attribute__((packed, aligned(2))); + +/** + * struct trackpad report + * + * @report_id: reportid + * @buttons: HID Usage Buttons 3 1-bit reports + * @num_fingers: the number of fingers being reported in @fingers + * @clicked: same as @buttons + */ +struct tp_header { + // HID mouse report + u8 report_id; + u8 buttons; + u8 rel_x; + u8 rel_y; + u8 padding[4]; + // HID vendor part, up to 1751 bytes + u8 unknown[22]; + u8 num_fingers; + u8 clicked; + u8 unknown3[14]; +}; + +static inline int le16_to_int(__le16 x) +{ + return (signed short)le16_to_cpu(x); +} + +static void report_finger_data(struct input_dev *input, int slot, + const struct input_mt_pos *pos, + const struct tp_finger *f) +{ + input_mt_slot(input, slot); + input_mt_report_slot_state(input, MT_TOOL_FINGER, true); + + input_report_abs(input, ABS_MT_TOUCH_MAJOR, + le16_to_int(f->touch_major) << 1); + input_report_abs(input, ABS_MT_TOUCH_MINOR, + le16_to_int(f->touch_minor) << 1); + input_report_abs(input, ABS_MT_WIDTH_MAJOR, + le16_to_int(f->tool_major) << 1); + input_report_abs(input, ABS_MT_WIDTH_MINOR, + le16_to_int(f->tool_minor) << 1); + input_report_abs(input, ABS_MT_ORIENTATION, + J314_TP_MAX_FINGER_ORIENTATION - le16_to_int(f->orientation)); + input_report_abs(input, ABS_MT_PRESSURE, le16_to_int(f->pressure)); + input_report_abs(input, ABS_MT_POSITION_X, pos->x); + input_report_abs(input, ABS_MT_POSITION_Y, pos->y); +} + +static int magicmouse_raw_event_spi(struct hid_device *hdev, + struct hid_report *report, u8 *data, int size) +{ + struct magicmouse_sc *msc = hid_get_drvdata(hdev); + struct input_dev *input = msc->input; + struct tp_header *tp_hdr; + struct tp_finger *f; + int i, n; + u32 npoints; + const size_t hdr_sz = sizeof(struct tp_header); + const size_t touch_sz = sizeof(struct tp_finger); + u8 map_contacs[MAX_CONTACTS]; + + // hid_warn(hdev, "%s\n", __func__); + // print_hex_dump_debug("appleft ev: ", DUMP_PREFIX_OFFSET, 16, 1, data, + // size, false); + + if (data[0] != SPI_REPORT_ID) + return 0; + + /* Expect 46 bytes of prefix, and N * 30 bytes of touch data. */ + if (size < hdr_sz || ((size - hdr_sz) % touch_sz) != 0) + return 0; + + tp_hdr = (struct tp_header *)data; + + npoints = (size - hdr_sz) / touch_sz; + if (npoints < tp_hdr->num_fingers || npoints > MAX_CONTACTS) { + hid_warn(hdev, + "unexpected number of touches (%u) for " + "report\n", + npoints); + return 0; + } + + n = 0; + for (i = 0; i < tp_hdr->num_fingers; i++) { + f = (struct tp_finger *)(data + hdr_sz + i * touch_sz); + if (le16_to_int(f->touch_major) == 0) + continue; + + hid_dbg(hdev, "ev x:%04x y:%04x\n", le16_to_int(f->abs_x), + le16_to_int(f->abs_y)); + msc->pos[n].x = le16_to_int(f->abs_x); + msc->pos[n].y = -le16_to_int(f->abs_y); + map_contacs[n] = i; + n++; + } + + input_mt_assign_slots(input, msc->tracking_ids, msc->pos, n, 0); + + for (i = 0; i < n; i++) { + int idx = map_contacs[i]; + f = (struct tp_finger *)(data + hdr_sz + idx * touch_sz); + report_finger_data(input, msc->tracking_ids[i], &msc->pos[i], f); + } + + input_mt_sync_frame(input); + input_report_key(input, BTN_MOUSE, data[1] & 1); + + input_sync(input); + return 1; +} + static int magicmouse_event(struct hid_device *hdev, struct hid_field *field, struct hid_usage *usage, __s32 value) { @@ -727,6 +888,79 @@ static int magicmouse_setup_input_usb(struct input_dev *input, return 0; } +static int magicmouse_setup_input_spi(struct input_dev *input, + struct hid_device *hdev) +{ + int error; + int mt_flags = 0; + + __set_bit(INPUT_PROP_BUTTONPAD, input->propbit); + __clear_bit(BTN_0, input->keybit); + __clear_bit(BTN_RIGHT, input->keybit); + __clear_bit(BTN_MIDDLE, input->keybit); + __clear_bit(EV_REL, input->evbit); + __clear_bit(REL_X, input->relbit); + __clear_bit(REL_Y, input->relbit); + + mt_flags = INPUT_MT_POINTER | INPUT_MT_DROP_UNUSED | INPUT_MT_TRACK; + + /* finger touch area */ + input_set_abs_params(input, ABS_MT_TOUCH_MAJOR, 0, 5000, 0, 0); + input_set_abs_params(input, ABS_MT_TOUCH_MINOR, 0, 5000, 0, 0); + + /* finger approach area */ + input_set_abs_params(input, ABS_MT_WIDTH_MAJOR, 0, 5000, 0, 0); + input_set_abs_params(input, ABS_MT_WIDTH_MINOR, 0, 5000, 0, 0); + + /* Note: Touch Y position from the device is inverted relative + * to how pointer motion is reported (and relative to how USB + * HID recommends the coordinates work). This driver keeps + * the origin at the same position, and just uses the additive + * inverse of the reported Y. + */ + + input_set_abs_params(input, ABS_MT_PRESSURE, 0, 6000, 0, 0); + + /* + * This makes libinput recognize this as a PressurePad and + * stop trying to use pressure for touch size. Pressure unit + * seems to be ~grams on these touchpads. + */ + input_abs_set_res(input, ABS_MT_PRESSURE, 1); + + /* finger orientation */ + input_set_abs_params(input, ABS_MT_ORIENTATION, -J314_TP_MAX_FINGER_ORIENTATION, + J314_TP_MAX_FINGER_ORIENTATION, 0, 0); + + /* finger position */ + input_set_abs_params(input, ABS_MT_POSITION_X, J314_TP_MIN_X, J314_TP_MAX_X, + 0, 0); + /* Y axis is inverted */ + input_set_abs_params(input, ABS_MT_POSITION_Y, -J314_TP_MAX_Y, -J314_TP_MIN_Y, + 0, 0); + + /* X/Y resolution */ + input_abs_set_res(input, ABS_MT_POSITION_X, J314_TP_RES_X); + input_abs_set_res(input, ABS_MT_POSITION_Y, J314_TP_RES_Y); + + input_set_events_per_packet(input, 60); + + /* touchpad button */ + input_set_capability(input, EV_KEY, BTN_MOUSE); + + /* + * hid-input may mark device as using autorepeat, but the trackpad does + * not actually want it. + */ + __clear_bit(EV_REP, input->evbit); + + error = input_mt_init_slots(input, MAX_CONTACTS, mt_flags); + if (error) + return error; + + return 0; +} + static int magicmouse_input_mapping(struct hid_device *hdev, struct hid_input *hi, struct hid_field *field, struct hid_usage *usage, unsigned long **bit, int *max) @@ -777,6 +1011,10 @@ static int magicmouse_enable_multitouch(struct hid_device *hdev) int feature_size; switch (hdev->product) { + case SPI_DEVICE_ID_APPLE_MACBOOK_AIR_2020: + case SPI_DEVICE_ID_APPLE_MACBOOK_PRO13_2020: + case SPI_DEVICE_ID_APPLE_MACBOOK_PRO14_2021: + case SPI_DEVICE_ID_APPLE_MACBOOK_PRO16_2021: case USB_DEVICE_ID_APPLE_MAGICTRACKPAD2: case USB_DEVICE_ID_APPLE_MAGICTRACKPAD2_USBC: switch (hdev->vendor) { @@ -784,7 +1022,7 @@ static int magicmouse_enable_multitouch(struct hid_device *hdev) feature_size = sizeof(feature_mt_trackpad2_bt); feature = feature_mt_trackpad2_bt; break; - default: /* USB_VENDOR_ID_APPLE */ + default: /* USB_VENDOR_ID_APPLE || SPI_VENDOR_ID_APPLE */ feature_size = sizeof(feature_mt_trackpad2_usb); feature = feature_mt_trackpad2_usb; } @@ -881,14 +1119,25 @@ static int magicmouse_probe(struct hid_device *hdev, struct hid_report *report; int ret; + if (id->bus == BUS_SPI && id->vendor == SPI_VENDOR_ID_APPLE && + hdev->type != HID_TYPE_SPI_MOUSE) + return -ENODEV; + msc = devm_kzalloc(&hdev->dev, sizeof(*msc), GFP_KERNEL); if (msc == NULL) { hid_err(hdev, "can't alloc magicmouse descriptor\n"); return -ENOMEM; } - msc->input_ops.raw_event = magicmouse_raw_event_usb; - msc->input_ops.setup_input = magicmouse_setup_input_usb; + // internal trackpad use a data format use input ops to avoid + // conflicts with the report ID. + if (id->vendor == SPI_VENDOR_ID_APPLE) { + msc->input_ops.raw_event = magicmouse_raw_event_spi; + msc->input_ops.setup_input = magicmouse_setup_input_spi; + } else { + msc->input_ops.raw_event = magicmouse_raw_event_usb; + msc->input_ops.setup_input = magicmouse_setup_input_usb; + } msc->scroll_accel = SCROLL_ACCEL_DEFAULT; msc->hdev = hdev; @@ -948,11 +1197,17 @@ static int magicmouse_probe(struct hid_device *hdev, TRACKPAD2_USB_REPORT_ID, 0); } break; - default: /* USB_DEVICE_ID_APPLE_MAGICTRACKPAD */ - report = hid_register_report(hdev, HID_INPUT_REPORT, - TRACKPAD_REPORT_ID, 0); - report = hid_register_report(hdev, HID_INPUT_REPORT, - DOUBLE_REPORT_ID, 0); + default: + switch (id->bus) { + case BUS_SPI: + report = hid_register_report(hdev, HID_INPUT_REPORT, SPI_REPORT_ID, 0); + break; + default: /* USB_DEVICE_ID_APPLE_MAGICTRACKPAD */ + report = hid_register_report(hdev, HID_INPUT_REPORT, + TRACKPAD_REPORT_ID, 0); + report = hid_register_report(hdev, HID_INPUT_REPORT, + DOUBLE_REPORT_ID, 0); + } } if (!report) { @@ -1055,6 +1310,8 @@ static const struct hid_device_id magic_mice[] = { USB_DEVICE_ID_APPLE_MAGICTRACKPAD2_USBC), .driver_data = 0 }, { HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_MAGICTRACKPAD2_USBC), .driver_data = 0 }, + { HID_SPI_DEVICE(SPI_VENDOR_ID_APPLE, HID_ANY_ID), + .driver_data = 0 }, { } }; MODULE_DEVICE_TABLE(hid, magic_mice); From 6379250a78f7e17cd63add0c5639d6f97eb2772b Mon Sep 17 00:00:00 2001 From: Hector Martin Date: Fri, 8 Jul 2022 02:12:57 +0900 Subject: [PATCH 0175/3902] HID: magicmouse: Add MTP multi-touch device support Apple M2 devices expose the multi-touch device over the HID over DockChannel transport, which we represent as the HOST bus type. The report format is the same, except the legacy mouse header is gone and there is no enable request needed. Signed-off-by: Hector Martin --- drivers/hid/hid-magicmouse.c | 63 +++++++++++++++++++++++++++--------- 1 file changed, 47 insertions(+), 16 deletions(-) diff --git a/drivers/hid/hid-magicmouse.c b/drivers/hid/hid-magicmouse.c index f00b508c7a54dc..2e415f4f90f74f 100644 --- a/drivers/hid/hid-magicmouse.c +++ b/drivers/hid/hid-magicmouse.c @@ -61,6 +61,7 @@ MODULE_PARM_DESC(report_undeciphered, "Report undeciphered multi-touch state fie #define MOUSE2_REPORT_ID 0x12 #define DOUBLE_REPORT_ID 0xf7 #define SPI_REPORT_ID 0x02 +#define MTP_REPORT_ID 0x75 #define USB_BATTERY_TIMEOUT_SEC 60 #define MAX_CONTACTS 16 @@ -586,25 +587,32 @@ struct tp_finger { } __attribute__((packed, aligned(2))); /** - * struct trackpad report + * vendor trackpad report * - * @report_id: reportid - * @buttons: HID Usage Buttons 3 1-bit reports * @num_fingers: the number of fingers being reported in @fingers - * @clicked: same as @buttons + * @buttons: same as HID buttons */ struct tp_header { + // HID vendor part, up to 1751 bytes + u8 unknown[22]; + u8 num_fingers; + u8 buttons; + u8 unknown3[14]; +}; + +/** + * standard HID mouse report + * + * @report_id: reportid + * @buttons: HID Usage Buttons 3 1-bit reports + */ +struct tp_mouse_report { // HID mouse report u8 report_id; u8 buttons; u8 rel_x; u8 rel_y; u8 padding[4]; - // HID vendor part, up to 1751 bytes - u8 unknown[22]; - u8 num_fingers; - u8 clicked; - u8 unknown3[14]; }; static inline int le16_to_int(__le16 x) @@ -634,7 +642,7 @@ static void report_finger_data(struct input_dev *input, int slot, input_report_abs(input, ABS_MT_POSITION_Y, pos->y); } -static int magicmouse_raw_event_spi(struct hid_device *hdev, +static int magicmouse_raw_event_mtp(struct hid_device *hdev, struct hid_report *report, u8 *data, int size) { struct magicmouse_sc *msc = hid_get_drvdata(hdev); @@ -651,9 +659,6 @@ static int magicmouse_raw_event_spi(struct hid_device *hdev, // print_hex_dump_debug("appleft ev: ", DUMP_PREFIX_OFFSET, 16, 1, data, // size, false); - if (data[0] != SPI_REPORT_ID) - return 0; - /* Expect 46 bytes of prefix, and N * 30 bytes of touch data. */ if (size < hdr_sz || ((size - hdr_sz) % touch_sz) != 0) return 0; @@ -692,12 +697,26 @@ static int magicmouse_raw_event_spi(struct hid_device *hdev, } input_mt_sync_frame(input); - input_report_key(input, BTN_MOUSE, data[1] & 1); + input_report_key(input, BTN_MOUSE, tp_hdr->buttons & 1); input_sync(input); return 1; } +static int magicmouse_raw_event_spi(struct hid_device *hdev, + struct hid_report *report, u8 *data, int size) +{ + const size_t hdr_sz = sizeof(struct tp_mouse_report); + + if (size < hdr_sz) + return 0; + + if (data[0] != SPI_REPORT_ID) + return 0; + + return magicmouse_raw_event_mtp(hdev, report, data + hdr_sz, size - hdr_sz); +} + static int magicmouse_event(struct hid_device *hdev, struct hid_field *field, struct hid_usage *usage, __s32 value) { @@ -1119,7 +1138,7 @@ static int magicmouse_probe(struct hid_device *hdev, struct hid_report *report; int ret; - if (id->bus == BUS_SPI && id->vendor == SPI_VENDOR_ID_APPLE && + if ((id->bus == BUS_SPI || id->bus == BUS_HOST) && id->vendor == SPI_VENDOR_ID_APPLE && hdev->type != HID_TYPE_SPI_MOUSE) return -ENODEV; @@ -1131,7 +1150,10 @@ static int magicmouse_probe(struct hid_device *hdev, // internal trackpad use a data format use input ops to avoid // conflicts with the report ID. - if (id->vendor == SPI_VENDOR_ID_APPLE) { + if (id->bus == BUS_HOST) { + msc->input_ops.raw_event = magicmouse_raw_event_mtp; + msc->input_ops.setup_input = magicmouse_setup_input_spi; + } else if (id->bus == BUS_SPI) { msc->input_ops.raw_event = magicmouse_raw_event_spi; msc->input_ops.setup_input = magicmouse_setup_input_spi; } else { @@ -1199,6 +1221,9 @@ static int magicmouse_probe(struct hid_device *hdev, break; default: switch (id->bus) { + case BUS_HOST: + report = hid_register_report(hdev, HID_INPUT_REPORT, MTP_REPORT_ID, 0); + break; case BUS_SPI: report = hid_register_report(hdev, HID_INPUT_REPORT, SPI_REPORT_ID, 0); break; @@ -1217,6 +1242,10 @@ static int magicmouse_probe(struct hid_device *hdev, } report->size = 6; + /* MTP devices do not need the MT enable, this is handled by the MTP driver */ + if (id->bus == BUS_HOST) + return 0; + /* * Some devices repond with 'invalid report id' when feature * report switching it into multitouch mode is sent to it. @@ -1312,6 +1341,8 @@ static const struct hid_device_id magic_mice[] = { USB_DEVICE_ID_APPLE_MAGICTRACKPAD2_USBC), .driver_data = 0 }, { HID_SPI_DEVICE(SPI_VENDOR_ID_APPLE, HID_ANY_ID), .driver_data = 0 }, + { HID_DEVICE(BUS_HOST, HID_GROUP_ANY, HOST_VENDOR_ID_APPLE, + HID_ANY_ID), .driver_data = 0 }, { } }; MODULE_DEVICE_TABLE(hid, magic_mice); From e8b4ee320a62c98a827eb61ac5f017e7af68cc46 Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Sun, 11 Dec 2022 22:56:16 +0100 Subject: [PATCH 0176/3902] HID: magicmouse: Add .reset_resume for SPI trackpads The trackpad has to request multi touch reports during resume. Signed-off-by: Janne Grunau --- drivers/hid/hid-magicmouse.c | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/drivers/hid/hid-magicmouse.c b/drivers/hid/hid-magicmouse.c index 2e415f4f90f74f..3d464eb30758d9 100644 --- a/drivers/hid/hid-magicmouse.c +++ b/drivers/hid/hid-magicmouse.c @@ -1347,6 +1347,16 @@ static const struct hid_device_id magic_mice[] = { }; MODULE_DEVICE_TABLE(hid, magic_mice); +#ifdef CONFIG_PM +static int magicmouse_reset_resume(struct hid_device *hdev) +{ + if (hdev->bus == BUS_SPI) + return magicmouse_enable_multitouch(hdev); + + return 0; +} +#endif + static struct hid_driver magicmouse_driver = { .name = "magicmouse", .id_table = magic_mice, @@ -1357,6 +1367,10 @@ static struct hid_driver magicmouse_driver = { .event = magicmouse_event, .input_mapping = magicmouse_input_mapping, .input_configured = magicmouse_input_configured, +#ifdef CONFIG_PM + .reset_resume = magicmouse_reset_resume, +#endif + }; module_hid_driver(magicmouse_driver); From 261daa24d532696acfa683b1d9044a83b0d74239 Mon Sep 17 00:00:00 2001 From: Hector Martin Date: Sun, 30 Apr 2023 23:48:45 +0900 Subject: [PATCH 0177/3902] HID: magicmouse: Handle touch controller resets on SPI devices On at least some SPI devices (e.g. recent Apple Silicon machines), the Broadcom touch controller is prone to crashing. When this happens, the STM eventually notices and resets it. It then notifies the driver via HID report 0x60, and the driver needs to re-enable MT mode to make things work again. This poses an additional issue: the hidinput core will close the low-level transport while the device is closed, which can cause us to miss a reset notification. To fix this, override the input open/close callbacks and send the MT enable every time the HID device is opened, instead of only once on probe. This should increase general robustness, even if the reset mechanism doesn't work for some reason, so it's worth doing it for USB devices too. MTP devices are exempt since they do not require the MT enable at all. Signed-off-by: Hector Martin --- drivers/hid/hid-magicmouse.c | 108 ++++++++++++++++++++++++++++------- 1 file changed, 87 insertions(+), 21 deletions(-) diff --git a/drivers/hid/hid-magicmouse.c b/drivers/hid/hid-magicmouse.c index 3d464eb30758d9..dc0ea9b1e257bc 100644 --- a/drivers/hid/hid-magicmouse.c +++ b/drivers/hid/hid-magicmouse.c @@ -61,6 +61,7 @@ MODULE_PARM_DESC(report_undeciphered, "Report undeciphered multi-touch state fie #define MOUSE2_REPORT_ID 0x12 #define DOUBLE_REPORT_ID 0xf7 #define SPI_REPORT_ID 0x02 +#define SPI_RESET_REPORT_ID 0x60 #define MTP_REPORT_ID 0x75 #define USB_BATTERY_TIMEOUT_SEC 60 @@ -176,6 +177,50 @@ struct magicmouse_sc { struct magicmouse_input_ops input_ops; }; +static int magicmouse_enable_multitouch(struct hid_device *hdev); + +static int magicmouse_open(struct input_dev *dev) +{ + struct hid_device *hdev = input_get_drvdata(dev); + struct magicmouse_sc *msc = hid_get_drvdata(hdev); + int ret; + + ret = hid_hw_open(hdev); + if (ret) + return ret; + + /* + * Some devices repond with 'invalid report id' when feature + * report switching it into multitouch mode is sent to it. + * + * This results in -EIO from the _raw low-level transport callback, + * but there seems to be no other way of switching the mode. + * Thus the super-ugly hacky success check below. + */ + ret = magicmouse_enable_multitouch(hdev); + if (ret != -EIO && ret < 0) { + hid_err(hdev, "unable to request touch data (%d)\n", ret); + return ret; + } + if (ret == -EIO && (hdev->product == USB_DEVICE_ID_APPLE_MAGICMOUSE2 || + hdev->product == USB_DEVICE_ID_APPLE_MAGICMOUSE2_USBC)) { + schedule_delayed_work(&msc->work, msecs_to_jiffies(500)); + } + + /* + * MT enable is usually not required after the first time, so don't + * consider it fatal. + */ + return 0; +} + +static void magicmouse_close(struct input_dev *dev) +{ + struct hid_device *hdev = input_get_drvdata(dev); + + hid_hw_close(hdev); +} + static int magicmouse_firm_touch(struct magicmouse_sc *msc) { int touch = -1; @@ -706,12 +751,19 @@ static int magicmouse_raw_event_mtp(struct hid_device *hdev, static int magicmouse_raw_event_spi(struct hid_device *hdev, struct hid_report *report, u8 *data, int size) { + struct magicmouse_sc *msc = hid_get_drvdata(hdev); const size_t hdr_sz = sizeof(struct tp_mouse_report); - if (size < hdr_sz) + if (!size) return 0; - if (data[0] != SPI_REPORT_ID) + if (data[0] == SPI_RESET_REPORT_ID) { + hid_info(hdev, "Touch controller was reset, re-enabling touch mode\n"); + schedule_delayed_work(&msc->work, msecs_to_jiffies(10)); + return 1; + } + + if (data[0] != SPI_REPORT_ID || size < hdr_sz) return 0; return magicmouse_raw_event_mtp(hdev, report, data + hdr_sz, size - hdr_sz); @@ -904,10 +956,17 @@ static int magicmouse_setup_input_usb(struct input_dev *input, */ __clear_bit(EV_REP, input->evbit); + /* + * This isn't strictly speaking needed for USB, but enabling MT on + * device open is probably more robust than only doing it once on probe + * even if USB devices are not known to suffer from the SPI reset issue. + */ + input->open = magicmouse_open; + input->close = magicmouse_close; return 0; } -static int magicmouse_setup_input_spi(struct input_dev *input, +static int magicmouse_setup_input_mtp(struct input_dev *input, struct hid_device *hdev) { int error; @@ -980,6 +1039,25 @@ static int magicmouse_setup_input_spi(struct input_dev *input, return 0; } +static int magicmouse_setup_input_spi(struct input_dev *input, + struct hid_device *hdev) +{ + int ret = magicmouse_setup_input_mtp(input, hdev); + if (ret) + return ret; + + /* + * Override the default input->open function to send the MT + * enable every time the device is opened. This ensures it works + * even if we missed a reset event due to the device being closed. + * input->close is overridden for symmetry. + */ + input->open = magicmouse_open; + input->close = magicmouse_close; + + return 0; +} + static int magicmouse_input_mapping(struct hid_device *hdev, struct hid_input *hi, struct hid_field *field, struct hid_usage *usage, unsigned long **bit, int *max) @@ -1041,7 +1119,7 @@ static int magicmouse_enable_multitouch(struct hid_device *hdev) feature_size = sizeof(feature_mt_trackpad2_bt); feature = feature_mt_trackpad2_bt; break; - default: /* USB_VENDOR_ID_APPLE || SPI_VENDOR_ID_APPLE */ + default: /* USB_VENDOR_ID_APPLE || SPI_VENDOR_ID_APPLE */ feature_size = sizeof(feature_mt_trackpad2_usb); feature = feature_mt_trackpad2_usb; } @@ -1152,7 +1230,7 @@ static int magicmouse_probe(struct hid_device *hdev, // conflicts with the report ID. if (id->bus == BUS_HOST) { msc->input_ops.raw_event = magicmouse_raw_event_mtp; - msc->input_ops.setup_input = magicmouse_setup_input_spi; + msc->input_ops.setup_input = magicmouse_setup_input_mtp; } else if (id->bus == BUS_SPI) { msc->input_ops.raw_event = magicmouse_raw_event_spi; msc->input_ops.setup_input = magicmouse_setup_input_spi; @@ -1246,22 +1324,10 @@ static int magicmouse_probe(struct hid_device *hdev, if (id->bus == BUS_HOST) return 0; - /* - * Some devices repond with 'invalid report id' when feature - * report switching it into multitouch mode is sent to it. - * - * This results in -EIO from the _raw low-level transport callback, - * but there seems to be no other way of switching the mode. - * Thus the super-ugly hacky success check below. - */ - ret = magicmouse_enable_multitouch(hdev); - if (ret != -EIO && ret < 0) { - hid_err(hdev, "unable to request touch data (%d)\n", ret); - goto err_stop_hw; - } - if (ret == -EIO && (id->product == USB_DEVICE_ID_APPLE_MAGICMOUSE2 || - id->product == USB_DEVICE_ID_APPLE_MAGICMOUSE2_USBC)) { - schedule_delayed_work(&msc->work, msecs_to_jiffies(500)); + /* SPI devices need to watch for reset events to re-send the MT enable */ + if (id->bus == BUS_SPI) { + report = hid_register_report(hdev, HID_INPUT_REPORT, SPI_RESET_REPORT_ID, 0); + report->size = 2; } return 0; From 3b2fcb0c2395286bbdc9c9f75b9f31019999bfc2 Mon Sep 17 00:00:00 2001 From: Hector Martin Date: Sun, 3 Dec 2023 21:08:17 +0900 Subject: [PATCH 0178/3902] HID: magicmouse: Query device dimensions via HID report For SPI/MTP trackpads, query the dimensions via HID report instead of hardcoding values. TODO: Does this work for the USB/BT devices? Maybe we can get rid of the hardcoded sizes everywhere? Signed-off-by: Hector Martin --- drivers/hid/hid-magicmouse.c | 104 +++++++++++++++++++++++++++-------- 1 file changed, 80 insertions(+), 24 deletions(-) diff --git a/drivers/hid/hid-magicmouse.c b/drivers/hid/hid-magicmouse.c index dc0ea9b1e257bc..ae6f48071e1f32 100644 --- a/drivers/hid/hid-magicmouse.c +++ b/drivers/hid/hid-magicmouse.c @@ -63,6 +63,7 @@ MODULE_PARM_DESC(report_undeciphered, "Report undeciphered multi-touch state fie #define SPI_REPORT_ID 0x02 #define SPI_RESET_REPORT_ID 0x60 #define MTP_REPORT_ID 0x75 +#define SENSOR_DIMENSIONS_REPORT_ID 0xd9 #define USB_BATTERY_TIMEOUT_SEC 60 #define MAX_CONTACTS 16 @@ -117,6 +118,7 @@ MODULE_PARM_DESC(report_undeciphered, "Report undeciphered multi-touch state fie #define TRACKPAD2_RES_Y \ ((TRACKPAD2_MAX_Y - TRACKPAD2_MIN_Y) / (TRACKPAD2_DIMENSION_Y / 100)) +/* These are fallback values, since the real values will be queried from the device. */ #define J314_TP_DIMENSION_X (float)13000 #define J314_TP_MIN_X -5900 #define J314_TP_MAX_X 6500 @@ -140,6 +142,7 @@ struct magicmouse_input_ops { * struct magicmouse_sc - Tracks Magic Mouse-specific data. * @input: Input device through which we report events. * @quirks: Currently unused. + * @query_dimensions: Whether to query and update dimensions on first open * @ntouches: Number of touches in most recent touch report. * @scroll_accel: Number of consecutive scroll motions. * @scroll_jiffies: Time of last scroll motion. @@ -154,6 +157,7 @@ struct magicmouse_input_ops { struct magicmouse_sc { struct input_dev *input; unsigned long quirks; + bool query_dimensions; int ntouches; int scroll_accel; @@ -179,6 +183,11 @@ struct magicmouse_sc { static int magicmouse_enable_multitouch(struct hid_device *hdev); +static inline int le16_to_int(__le16 x) +{ + return (signed short)le16_to_cpu(x); +} + static int magicmouse_open(struct input_dev *dev) { struct hid_device *hdev = input_get_drvdata(dev); @@ -196,21 +205,69 @@ static int magicmouse_open(struct input_dev *dev) * This results in -EIO from the _raw low-level transport callback, * but there seems to be no other way of switching the mode. * Thus the super-ugly hacky success check below. + * + * MTP devices do not need this. */ - ret = magicmouse_enable_multitouch(hdev); - if (ret != -EIO && ret < 0) { - hid_err(hdev, "unable to request touch data (%d)\n", ret); - return ret; - } - if (ret == -EIO && (hdev->product == USB_DEVICE_ID_APPLE_MAGICMOUSE2 || - hdev->product == USB_DEVICE_ID_APPLE_MAGICMOUSE2_USBC)) { - schedule_delayed_work(&msc->work, msecs_to_jiffies(500)); + if (hdev->bus != BUS_HOST) { + ret = magicmouse_enable_multitouch(hdev); + if (ret != -EIO && ret < 0) { + hid_err(hdev, "unable to request touch data (%d)\n", ret); + return ret; + } + if (ret == -EIO && (hdev->product == USB_DEVICE_ID_APPLE_MAGICMOUSE2 || + hdev->product == USB_DEVICE_ID_APPLE_MAGICMOUSE2_USBC)) { + schedule_delayed_work(&msc->work, msecs_to_jiffies(500)); + } } /* - * MT enable is usually not required after the first time, so don't - * consider it fatal. + * For Apple Silicon trackpads, we want to query the dimensions on + * device open. This is because doing so requires the firmware, but + * we don't want to force a firmware load until the device is opened + * for the first time. So do that here and update the input properties + * just in time before userspace queries them. */ + if (msc->query_dimensions) { + struct input_dev *input = msc->input; + u8 buf[32]; + struct { + __le32 width; + __le32 height; + __le16 min_x; + __le16 min_y; + __le16 max_x; + __le16 max_y; + } dim; + uint32_t x_span, y_span; + + ret = hid_hw_raw_request(hdev, SENSOR_DIMENSIONS_REPORT_ID, buf, sizeof(buf), HID_FEATURE_REPORT, HID_REQ_GET_REPORT); + if (ret < (int)(1 + sizeof(dim))) { + hid_err(hdev, "unable to request dimensions (%d)\n", ret); + return ret; + } + + memcpy(&dim, buf + 1, sizeof(dim)); + + /* finger position */ + input_set_abs_params(input, ABS_MT_POSITION_X, + le16_to_int(dim.min_x), le16_to_int(dim.max_x), 0, 0); + /* Y axis is inverted */ + input_set_abs_params(input, ABS_MT_POSITION_Y, + -le16_to_int(dim.max_y), -le16_to_int(dim.min_y), 0, 0); + x_span = le16_to_int(dim.max_x) - le16_to_int(dim.min_x); + y_span = le16_to_int(dim.max_y) - le16_to_int(dim.min_y); + + /* X/Y resolution */ + input_abs_set_res(input, ABS_MT_POSITION_X, 100 * x_span / le32_to_cpu(dim.width) ); + input_abs_set_res(input, ABS_MT_POSITION_Y, 100 * y_span / le32_to_cpu(dim.height) ); + + /* copy info, as input_mt_init_slots() does */ + dev->absinfo[ABS_X] = dev->absinfo[ABS_MT_POSITION_X]; + dev->absinfo[ABS_Y] = dev->absinfo[ABS_MT_POSITION_Y]; + + msc->query_dimensions = false; + } + return 0; } @@ -660,11 +717,6 @@ struct tp_mouse_report { u8 padding[4]; }; -static inline int le16_to_int(__le16 x) -{ - return (signed short)le16_to_cpu(x); -} - static void report_finger_data(struct input_dev *input, int slot, const struct input_mt_pos *pos, const struct tp_finger *f) @@ -971,6 +1023,7 @@ static int magicmouse_setup_input_mtp(struct input_dev *input, { int error; int mt_flags = 0; + struct magicmouse_sc *msc = hid_get_drvdata(hdev); __set_bit(INPUT_PROP_BUTTONPAD, input->propbit); __clear_bit(BTN_0, input->keybit); @@ -1036,6 +1089,18 @@ static int magicmouse_setup_input_mtp(struct input_dev *input, if (error) return error; + /* + * Override the default input->open function to send the MT + * enable every time the device is opened. This ensures it works + * even if we missed a reset event due to the device being closed. + * input->close is overridden for symmetry. + * + * This also takes care of the dimensions query. + */ + input->open = magicmouse_open; + input->close = magicmouse_close; + msc->query_dimensions = true; + return 0; } @@ -1046,15 +1111,6 @@ static int magicmouse_setup_input_spi(struct input_dev *input, if (ret) return ret; - /* - * Override the default input->open function to send the MT - * enable every time the device is opened. This ensures it works - * even if we missed a reset event due to the device being closed. - * input->close is overridden for symmetry. - */ - input->open = magicmouse_open; - input->close = magicmouse_close; - return 0; } From 0839f2c1f9bd15bcfc02938695b145cf5fba6d0e Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Fri, 10 Dec 2021 19:38:43 +0100 Subject: [PATCH 0179/3902] WIP: HID: transport: spi: add Apple SPI transport Keyboard and trackpad of Apple Sillicon SoCs (M1, M1 Pro/Max) laptops are are HID devices connected via SPI. This is the same protocol as implemented by applespi.c. It was not noticed that protocol is a transport for HID. Adding support for ACPI based Intel MacBooks will be done in a separate commit. How HID is mapped in this protocol is not yet fully understood. Microsoft has a specification for HID over SPI [1] incompatible with the transport protocol used by Apple. [1] https://docs.microsoft.com/en-us/windows-hardware/drivers/hid/hid-over-spi Contains "HID: transport: spi: apple: Increase receive buffer size" The SPI receive buffer is passed directly to hid_input_report() if it contains a complete report. It is then passed to hid_report_raw_event() which computes the expected report size and memsets the "missing trailing data up to HID_MAX_BUFFER_SIZE (16K) or hid_ll_driver.max_buffer_size (if set) to zero. Co-developed-by: Hector Martin Signed-off-by: Hector Martin Signed-off-by: Janne Grunau --- drivers/hid/Kconfig | 2 + drivers/hid/Makefile | 2 + drivers/hid/spi-hid/Kconfig | 24 + drivers/hid/spi-hid/Makefile | 10 + drivers/hid/spi-hid/spi-hid-apple-core.c | 1194 ++++++++++++++++++++++ drivers/hid/spi-hid/spi-hid-apple-of.c | 153 +++ drivers/hid/spi-hid/spi-hid-apple.h | 35 + 7 files changed, 1420 insertions(+) create mode 100644 drivers/hid/spi-hid/Kconfig create mode 100644 drivers/hid/spi-hid/Makefile create mode 100644 drivers/hid/spi-hid/spi-hid-apple-core.c create mode 100644 drivers/hid/spi-hid/spi-hid-apple-of.c create mode 100644 drivers/hid/spi-hid/spi-hid-apple.h diff --git a/drivers/hid/Kconfig b/drivers/hid/Kconfig index 8316b5e385c9fb..5a9b8f192dcd64 100644 --- a/drivers/hid/Kconfig +++ b/drivers/hid/Kconfig @@ -1446,4 +1446,6 @@ endif # HID source "drivers/hid/usbhid/Kconfig" +source "drivers/hid/spi-hid/Kconfig" + endif # HID_SUPPORT diff --git a/drivers/hid/Makefile b/drivers/hid/Makefile index 361a7daedeb854..e626eee18b11f5 100644 --- a/drivers/hid/Makefile +++ b/drivers/hid/Makefile @@ -173,6 +173,8 @@ obj-$(CONFIG_INTEL_ISH_HID) += intel-ish-hid/ obj-$(CONFIG_AMD_SFH_HID) += amd-sfh-hid/ +obj-$(CONFIG_SPI_HID_APPLE_CORE) += spi-hid/ + obj-$(CONFIG_SURFACE_HID_CORE) += surface-hid/ obj-$(CONFIG_INTEL_THC_HID) += intel-thc-hid/ diff --git a/drivers/hid/spi-hid/Kconfig b/drivers/hid/spi-hid/Kconfig new file mode 100644 index 00000000000000..59076c6ebeed9b --- /dev/null +++ b/drivers/hid/spi-hid/Kconfig @@ -0,0 +1,24 @@ +# SPDX-License-Identifier: GPL-2.0-only +menu "SPI HID support" + depends on SPI + +config SPI_HID_APPLE_OF + tristate "HID over SPI transport layer for Apple Silicon SoCs" + depends on INPUT && OF + select SPI_HID_APPLE_CORE + help + Say Y here if you use Apple Silicon based laptop. The keyboard and + touchpad are HID based devices connected via SPI. + + If unsure, say N. + + This support is also available as a module. If so, the module + will be called spi-hid-apple-of. It will also build/depend on the + module spi-hid-apple. + +endmenu + +config SPI_HID_APPLE_CORE + tristate + select HID + select CRC16 diff --git a/drivers/hid/spi-hid/Makefile b/drivers/hid/spi-hid/Makefile new file mode 100644 index 00000000000000..f276ee12cb94fc --- /dev/null +++ b/drivers/hid/spi-hid/Makefile @@ -0,0 +1,10 @@ +# SPDX-License-Identifier: GPL-2.0-only +# +# Makefile for SPI HID tarnsport drivers +# + +obj-$(CONFIG_SPI_HID_APPLE_CORE) += spi-hid-apple.o + +spi-hid-apple-objs = spi-hid-apple-core.o + +obj-$(CONFIG_SPI_HID_APPLE_OF) += spi-hid-apple-of.o diff --git a/drivers/hid/spi-hid/spi-hid-apple-core.c b/drivers/hid/spi-hid/spi-hid-apple-core.c new file mode 100644 index 00000000000000..2ed909895391c8 --- /dev/null +++ b/drivers/hid/spi-hid/spi-hid-apple-core.c @@ -0,0 +1,1194 @@ +/* + * SPDX-License-Identifier: GPL-2.0 + * + * Apple SPI HID transport driver + * + * Copyright (C) The Asahi Linux Contributors + * + * Based on: drivers/input/applespi.c + * + * MacBook (Pro) SPI keyboard and touchpad driver + * + * Copyright (c) 2015-2018 Federico Lorenzi + * Copyright (c) 2017-2018 Ronald Tschalär + * + */ + +//#define DEBUG 2 + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "spi-hid-apple.h" + +#define SPIHID_DEF_WAIT msecs_to_jiffies(1000) + +#define SPIHID_MAX_INPUT_REPORT_SIZE 0x800 + +/* support only keyboard, trackpad and management dev for now */ +#define SPIHID_MAX_DEVICES 3 + +#define SPIHID_DEVICE_ID_MNGT 0x0 +#define SPIHID_DEVICE_ID_KBD 0x1 +#define SPIHID_DEVICE_ID_TP 0x2 +#define SPIHID_DEVICE_ID_INFO 0xd0 + +#define SPIHID_READ_PACKET 0x20 +#define SPIHID_WRITE_PACKET 0x40 + +#define SPIHID_DESC_MAX 512 + +#define SPIHID_SET_LEDS 0x0151 /* caps lock */ + +#define SPI_RW_CHG_DELAY_US 200 /* 'Inter Stage Us'? */ + +static const u8 spi_hid_apple_booted[4] = { 0xa0, 0x80, 0x00, 0x00 }; +static const u8 spi_hid_apple_status_ok[4] = { 0xac, 0x27, 0x68, 0xd5 }; + +struct spihid_interface { + struct hid_device *hid; + u8 *hid_desc; + u32 hid_desc_len; + u32 id; + unsigned country; + u32 max_control_report_len; + u32 max_input_report_len; + u32 max_output_report_len; + u8 name[32]; + u8 reply_buf[SPIHID_DESC_MAX]; + u32 reply_len; + bool ready; +}; + +struct spihid_input_report { + u8 *buf; + u32 length; + u32 offset; + u8 device; + u8 flags; +}; + +struct spihid_apple { + struct spi_device *spidev; + + struct spihid_apple_ops *ops; + + struct spihid_interface mngt; + struct spihid_interface kbd; + struct spihid_interface tp; + + wait_queue_head_t wait; + struct mutex tx_lock; //< protects against concurrent SPI writes + + struct spi_message rx_msg; + struct spi_message tx_msg; + struct spi_transfer rx_transfer; + struct spi_transfer tx_transfer; + struct spi_transfer status_transfer; + + u8 *rx_buf; + u8 *tx_buf; + u8 *status_buf; + + u8 vendor[32]; + u8 product[64]; + u8 serial[32]; + + u32 num_devices; + + u32 vendor_id; + u32 product_id; + u32 version_number; + + u8 msg_id; + + /* fragmented HID report */ + struct spihid_input_report report; + + /* state tracking flags */ + bool status_booted; + +#ifdef IRQ_WAKE_SUPPORT + bool irq_wake_enabled; +#endif +}; + +/** + * struct spihid_msg_hdr - common header of protocol messages. + * + * Each message begins with fixed header, followed by a message-type specific + * payload, and ends with a 16-bit crc. Because of the varying lengths of the + * payload, the crc is defined at the end of each payload struct, rather than + * in this struct. + * + * @unknown0: request type? output, input (0x10), feature, protocol + * @unknown1: maybe report id? + * @unknown2: mostly zero, in info request maybe device num + * @id: incremented on each message, rolls over after 255; there is a + * separate counter for each message type. + * @rsplen: response length (the exact nature of this field is quite + * speculative). On a request/write this is often the same as + * @length, though in some cases it has been seen to be much larger + * (e.g. 0x400); on a response/read this the same as on the + * request; for reads that are not responses it is 0. + * @length: length of the remainder of the data in the whole message + * structure (after re-assembly in case of being split over + * multiple spi-packets), minus the trailing crc. The total size + * of a message is therefore @length + 10. + */ + +struct spihid_msg_hdr { + u8 unknown0; + u8 unknown1; + u8 unknown2; + u8 id; + __le16 rsplen; + __le16 length; +}; + +/** + * struct spihid_transfer_packet - a complete spi packet; always 256 bytes. This carries + * the (parts of the) message in the data. But note that this does not + * necessarily contain a complete message, as in some cases (e.g. many + * fingers pressed) the message is split over multiple packets (see the + * @offset, @remain, and @length fields). In general the data parts in + * spihid_transfer_packet's are concatenated until @remaining is 0, and the + * result is an message. + * + * @flags: 0x40 = write (to device), 0x20 = read (from device); note that + * the response to a write still has 0x40. + * @device: 1 = keyboard, 2 = touchpad + * @offset: specifies the offset of this packet's data in the complete + * message; i.e. > 0 indicates this is a continuation packet (in + * the second packet for a message split over multiple packets + * this would then be the same as the @length in the first packet) + * @remain: number of message bytes remaining in subsequents packets (in + * the first packet of a message split over two packets this would + * then be the same as the @length in the second packet) + * @length: length of the valid data in the @data in this packet + * @data: all or part of a message + * @crc16: crc over this whole structure minus this @crc16 field. This + * covers just this packet, even on multi-packet messages (in + * contrast to the crc in the message). + */ +struct spihid_transfer_packet { + u8 flags; + u8 device; + __le16 offset; + __le16 remain; + __le16 length; + u8 data[246]; + __le16 crc16; +}; + +/* + * how HID is mapped onto the protocol is not fully clear. This are the known + * reports/request: + * + * pkt.flags pkt.dev? msg.u0 msg.u1 msg.u2 + * info 0x40 0xd0 0x20 0x01 0xd0 + * + * info mngt: 0x40 0xd0 0x20 0x10 0x00 + * info kbd: 0x40 0xd0 0x20 0x10 0x01 + * info tp: 0x40 0xd0 0x20 0x10 0x02 + * + * desc kbd: 0x40 0xd0 0x20 0x10 0x01 + * desc trackpad: 0x40 0xd0 0x20 0x10 0x02 + * + * mt mode: 0x40 0x02 0x52 0x02 0x00 set protocol? + * capslock led 0x40 0x01 0x51 0x01 0x00 output report + * + * report kbd: 0x20 0x01 0x10 0x01 0x00 input report + * report tp: 0x20 0x02 0x10 0x02 0x00 input report + * + */ + + +static int spihid_apple_request(struct spihid_apple *spihid, u8 target, u8 unk0, + u8 unk1, u8 unk2, u16 resp_len, u8 *buf, + size_t len) +{ + struct spihid_transfer_packet *pkt; + struct spihid_msg_hdr *hdr; + u16 crc; + int err; + + /* know reports are small enoug to fit in a single packet */ + if (len > sizeof(pkt->data) - sizeof(*hdr) - sizeof(__le16)) + return -EINVAL; + + err = mutex_lock_interruptible(&spihid->tx_lock); + if (err < 0) + return err; + + pkt = (struct spihid_transfer_packet *)spihid->tx_buf; + + memset(pkt, 0, sizeof(*pkt)); + pkt->flags = SPIHID_WRITE_PACKET; + pkt->device = target; + pkt->length = cpu_to_le16(sizeof(*hdr) + len + sizeof(__le16)); + + hdr = (struct spihid_msg_hdr *)&pkt->data[0]; + hdr->unknown0 = unk0; + hdr->unknown1 = unk1; + hdr->unknown2 = unk2; + hdr->id = spihid->msg_id++; + hdr->rsplen = cpu_to_le16(resp_len); + hdr->length = cpu_to_le16(len); + + if (len) + memcpy(pkt->data + sizeof(*hdr), buf, len); + crc = crc16(0, &pkt->data[0], sizeof(*hdr) + len); + put_unaligned_le16(crc, pkt->data + sizeof(*hdr) + len); + + pkt->crc16 = cpu_to_le16(crc16(0, spihid->tx_buf, + offsetof(struct spihid_transfer_packet, crc16))); + + memset(spihid->status_buf, 0, sizeof(spi_hid_apple_status_ok)); + + err = spi_sync(spihid->spidev, &spihid->tx_msg); + + if (memcmp(spihid->status_buf, spi_hid_apple_status_ok, + sizeof(spi_hid_apple_status_ok))) { + u8 *b = spihid->status_buf; + dev_warn_ratelimited(&spihid->spidev->dev, "status message " + "mismatch: %02x %02x %02x %02x\n", + b[0], b[1], b[2], b[3]); + } + mutex_unlock(&spihid->tx_lock); + if (err < 0) + return err; + + return (int)len; +} + +static struct spihid_apple *spihid_get_data(struct spihid_interface *idev) +{ + switch (idev->id) { + case SPIHID_DEVICE_ID_KBD: + return container_of(idev, struct spihid_apple, kbd); + case SPIHID_DEVICE_ID_TP: + return container_of(idev, struct spihid_apple, tp); + default: + return NULL; + } +} + +static int apple_ll_start(struct hid_device *hdev) +{ + /* no-op SPI transport is already setup */ + return 0; +}; + +static void apple_ll_stop(struct hid_device *hdev) +{ + /* no-op, devices will be desstroyed on driver destruction */ +} + +static int apple_ll_open(struct hid_device *hdev) +{ + struct spihid_apple *spihid; + struct spihid_interface *idev = hdev->driver_data; + + if (idev->hid_desc_len == 0) { + spihid = spihid_get_data(idev); + dev_warn(&spihid->spidev->dev, + "HID descriptor missing for dev %u", idev->id); + } else + idev->ready = true; + + return 0; +} + +static void apple_ll_close(struct hid_device *hdev) +{ + struct spihid_interface *idev = hdev->driver_data; + idev->ready = false; +} + +static int apple_ll_parse(struct hid_device *hdev) +{ + struct spihid_interface *idev = hdev->driver_data; + + return hid_parse_report(hdev, idev->hid_desc, idev->hid_desc_len); +} + +static int apple_ll_raw_request(struct hid_device *hdev, + unsigned char reportnum, __u8 *buf, size_t len, + unsigned char rtype, int reqtype) +{ + struct spihid_interface *idev = hdev->driver_data; + struct spihid_apple *spihid = spihid_get_data(idev); + int ret; + + dev_dbg(&spihid->spidev->dev, + "apple_ll_raw_request: device:%u reportnum:%hhu rtype:%hhu", + idev->id, reportnum, rtype); + + switch (reqtype) { + case HID_REQ_GET_REPORT: + if (rtype != HID_FEATURE_REPORT) + return -EINVAL; + + idev->reply_len = 0; + ret = spihid_apple_request(spihid, idev->id, 0x32, reportnum, 0x00, len, NULL, 0); + if (ret < 0) + return ret; + + ret = wait_event_interruptible_timeout(spihid->wait, idev->reply_len, + SPIHID_DEF_WAIT); + if (ret == 0) + ret = -ETIMEDOUT; + if (ret < 0) { + dev_err(&spihid->spidev->dev, "waiting for get report failed: %d", ret); + return ret; + } + memcpy(buf, idev->reply_buf, max_t(size_t, len, idev->reply_len)); + return idev->reply_len; + + case HID_REQ_SET_REPORT: + if (buf[0] != reportnum) + return -EINVAL; + if (reportnum != idev->id) { + dev_warn(&spihid->spidev->dev, + "device:%u reportnum:" + "%hhu mismatch", + idev->id, reportnum); + return -EINVAL; + } + return spihid_apple_request(spihid, idev->id, 0x52, reportnum, 0x00, 2, buf, len); + default: + return -EIO; + } +} + +static int apple_ll_output_report(struct hid_device *hdev, __u8 *buf, + size_t len) +{ + struct spihid_interface *idev = hdev->driver_data; + struct spihid_apple *spihid = spihid_get_data(idev); + if (!spihid) + return -1; + + dev_dbg(&spihid->spidev->dev, + "apple_ll_output_report: device:%u len:%zu:", + idev->id, len); + // second idev->id should maybe be buf[0]? + return spihid_apple_request(spihid, idev->id, 0x51, idev->id, 0x00, 0, buf, len); +} + +static struct hid_ll_driver apple_hid_ll = { + .start = &apple_ll_start, + .stop = &apple_ll_stop, + .open = &apple_ll_open, + .close = &apple_ll_close, + .parse = &apple_ll_parse, + .raw_request = &apple_ll_raw_request, + .output_report = &apple_ll_output_report, + .max_buffer_size = SPIHID_MAX_INPUT_REPORT_SIZE, +}; + +static struct spihid_interface *spihid_get_iface(struct spihid_apple *spihid, + u32 iface) +{ + switch (iface) { + case SPIHID_DEVICE_ID_MNGT: + return &spihid->mngt; + case SPIHID_DEVICE_ID_KBD: + return &spihid->kbd; + case SPIHID_DEVICE_ID_TP: + return &spihid->tp; + default: + return NULL; + } +} + +static int spihid_verify_msg(struct spihid_apple *spihid, u8 *buf, size_t len) +{ + u16 msg_crc, crc; + struct device *dev = &spihid->spidev->dev; + + crc = crc16(0, buf, len - sizeof(__le16)); + msg_crc = get_unaligned_le16(buf + len - sizeof(__le16)); + if (crc != msg_crc) { + dev_warn_ratelimited(dev, "Read message crc mismatch\n"); + return 0; + } + return 1; +} + +static bool spihid_status_report(struct spihid_apple *spihid, u8 *pl, + size_t len) +{ + struct device *dev = &spihid->spidev->dev; + dev_dbg(dev, "%s: len: %zu", __func__, len); + if (len == 5 && pl[0] == 0xe0) + return true; + + return false; +} + +static bool spihid_process_input_report(struct spihid_apple *spihid, u32 device, + struct spihid_msg_hdr *hdr, u8 *payload, + size_t len) +{ + //dev_dbg(&spihid>spidev->dev, "input report: req:%hx iface:%u ", hdr->unknown0, device); + if (hdr->unknown0 != 0x10) + return false; + + /* HID device as well but Vendor usage only, handle it internally for now */ + if (device == 0) { + if (hdr->unknown1 == 0xe0) { + return spihid_status_report(spihid, payload, len); + } + } else if (device < SPIHID_MAX_DEVICES) { + struct spihid_interface *iface = + spihid_get_iface(spihid, device); + if (iface && iface->hid && iface->ready) { + hid_input_report(iface->hid, HID_INPUT_REPORT, payload, + len, 1); + return true; + } + } else + dev_dbg(&spihid->spidev->dev, + "unexpected iface:%u for input report", device); + + return false; +} + +struct spihid_device_info { + __le16 u0[2]; + __le16 num_devices; + __le16 vendor_id; + __le16 product_id; + __le16 version_number; + __le16 vendor_str[2]; //< offset and string length + __le16 product_str[2]; //< offset and string length + __le16 serial_str[2]; //< offset and string length +}; + +static bool spihid_process_device_info(struct spihid_apple *spihid, u32 iface, + u8 *payload, size_t len) +{ + struct device *dev = &spihid->spidev->dev; + + if (iface != SPIHID_DEVICE_ID_INFO) + return false; + + if (spihid->vendor_id == 0 && + len >= sizeof(struct spihid_device_info)) { + struct spihid_device_info *info = + (struct spihid_device_info *)payload; + u16 voff, vlen, poff, plen, soff, slen; + u32 num_devices; + + num_devices = __le16_to_cpu(info->num_devices); + + if (num_devices < SPIHID_MAX_DEVICES) { + dev_err(dev, + "Device info reports %u devices, expecting at least 3", + num_devices); + return false; + } + spihid->num_devices = num_devices; + + if (spihid->num_devices > SPIHID_MAX_DEVICES) { + dev_info( + dev, + "limiting the number of devices to mngt, kbd and mouse"); + spihid->num_devices = SPIHID_MAX_DEVICES; + } + + spihid->vendor_id = __le16_to_cpu(info->vendor_id); + spihid->product_id = __le16_to_cpu(info->product_id); + spihid->version_number = __le16_to_cpu(info->version_number); + + voff = __le16_to_cpu(info->vendor_str[0]); + vlen = __le16_to_cpu(info->vendor_str[1]); + + if (voff < len && vlen <= len - voff && + vlen < sizeof(spihid->vendor)) { + memcpy(spihid->vendor, payload + voff, vlen); + spihid->vendor[vlen] = '\0'; + } + + poff = __le16_to_cpu(info->product_str[0]); + plen = __le16_to_cpu(info->product_str[1]); + + if (poff < len && plen <= len - poff && + plen < sizeof(spihid->product)) { + memcpy(spihid->product, payload + poff, plen); + spihid->product[plen] = '\0'; + } + + soff = __le16_to_cpu(info->serial_str[0]); + slen = __le16_to_cpu(info->serial_str[1]); + + if (soff < len && slen <= len - soff && + slen < sizeof(spihid->serial)) { + memcpy(spihid->vendor, payload + soff, slen); + spihid->serial[slen] = '\0'; + } + + wake_up_interruptible(&spihid->wait); + } + return true; +} + +struct spihid_iface_info { + u8 u_0; + u8 interface_num; + u8 u_2; + u8 u_3; + u8 u_4; + u8 country_code; + __le16 max_input_report_len; + __le16 max_output_report_len; + __le16 max_control_report_len; + __le16 name_offset; + __le16 name_length; +}; + +static bool spihid_process_iface_info(struct spihid_apple *spihid, u32 num, + u8 *payload, size_t len) +{ + struct spihid_iface_info *info; + struct spihid_interface *iface = spihid_get_iface(spihid, num); + u32 name_off, name_len; + + if (!iface) + return false; + + if (!iface->max_input_report_len) { + if (len < sizeof(*info)) + return false; + + info = (struct spihid_iface_info *)payload; + + iface->max_input_report_len = + le16_to_cpu(info->max_input_report_len); + iface->max_output_report_len = + le16_to_cpu(info->max_output_report_len); + iface->max_control_report_len = + le16_to_cpu(info->max_control_report_len); + iface->country = info->country_code; + + name_off = le16_to_cpu(info->name_offset); + name_len = le16_to_cpu(info->name_length); + + if (name_off < len && name_len <= len - name_off && + name_len < sizeof(iface->name)) { + memcpy(iface->name, payload + name_off, name_len); + iface->name[name_len] = '\0'; + } + + dev_dbg(&spihid->spidev->dev, "Info for %s, country code: 0x%x", + iface->name, iface->country); + + wake_up_interruptible(&spihid->wait); + } + + return true; +} + +static int spihid_register_hid_device(struct spihid_apple *spihid, + struct spihid_interface *idev, u8 device); + +static bool spihid_process_iface_hid_report_desc(struct spihid_apple *spihid, + u32 num, u8 *payload, + size_t len) +{ + struct spihid_interface *iface = spihid_get_iface(spihid, num); + + if (!iface) + return false; + + if (iface->hid_desc_len == 0) { + if (len > SPIHID_DESC_MAX) + return false; + memcpy(iface->hid_desc, payload, len); + iface->hid_desc_len = len; + + /* do not register the mngt iface as HID device */ + if (num > 0) + spihid_register_hid_device(spihid, iface, num); + + wake_up_interruptible(&spihid->wait); + } + return true; +} + +static bool spihid_process_iface_get_report(struct spihid_apple *spihid, + u32 device, u8 report, + u8 *payload, size_t len) +{ + struct spihid_interface *iface = spihid_get_iface(spihid, device); + + if (!iface) + return false; + + if (len > sizeof(iface->reply_buf) || len < 1) + return false; + + memcpy(iface->reply_buf, payload, len); + iface->reply_len = len; + + wake_up_interruptible(&spihid->wait); + + return true; +} + +static bool spihid_process_response(struct spihid_apple *spihid, u32 device, + struct spihid_msg_hdr *hdr, u8 *payload, + size_t len) +{ + if (hdr->unknown0 == 0x20) { + switch (hdr->unknown1) { + case 0x01: + return spihid_process_device_info(spihid, hdr->unknown2, + payload, len); + case 0x02: + return spihid_process_iface_info(spihid, hdr->unknown2, + payload, len); + case 0x10: + return spihid_process_iface_hid_report_desc( + spihid, hdr->unknown2, payload, len); + default: + break; + } + } + + if (hdr->unknown0 == 0x32) { + return spihid_process_iface_get_report(spihid, device, hdr->unknown1, payload, len); + } + + return false; +} + +static void spihid_process_message(struct spihid_apple *spihid, u8 *data, + size_t length, u8 device, u8 flags) +{ + struct device *dev = &spihid->spidev->dev; + struct spihid_msg_hdr *hdr; + bool handled = false; + size_t payload_len; + u8 *payload; + + if (!spihid_verify_msg(spihid, data, length)) + return; + + hdr = (struct spihid_msg_hdr *)data; + payload_len = le16_to_cpu(hdr->length); + + if (payload_len == 0 || + (payload_len + sizeof(struct spihid_msg_hdr) + 2) > length) + return; + + payload = data + sizeof(struct spihid_msg_hdr); + + switch (flags) { + case SPIHID_READ_PACKET: + handled = spihid_process_input_report(spihid, device, hdr, + payload, payload_len); + break; + case SPIHID_WRITE_PACKET: + handled = spihid_process_response(spihid, device, hdr, payload, + payload_len); + break; + default: + break; + } + +#if defined(DEBUG) && DEBUG > 1 + { + dev_dbg(dev, + "R msg: req:%02hhx rep:%02hhx dev:%02hhx id:%hu len:%hu\n", + hdr->unknown0, hdr->unknown1, hdr->unknown2, hdr->id, + hdr->length); + print_hex_dump_debug("spihid msg: ", DUMP_PREFIX_OFFSET, 16, 1, + payload, le16_to_cpu(hdr->length), true); + } +#else + if (!handled) { + dev_dbg(dev, + "R unhandled msg: req:%02hhx rep:%02hhx dev:%02hhx id:%hu len:%hu\n", + hdr->unknown0, hdr->unknown1, hdr->unknown2, hdr->id, + hdr->length); + print_hex_dump_debug("spihid msg: ", DUMP_PREFIX_OFFSET, 16, 1, + payload, le16_to_cpu(hdr->length), true); + } +#endif +} + +static void spihid_assemble_message(struct spihid_apple *spihid, + struct spihid_transfer_packet *pkt) +{ + size_t length, offset, remain; + struct device *dev = &spihid->spidev->dev; + struct spihid_input_report *rep = &spihid->report; + + length = le16_to_cpu(pkt->length); + remain = le16_to_cpu(pkt->remain); + offset = le16_to_cpu(pkt->offset); + + if (offset + length + remain > U16_MAX) { + return; + } + + if (pkt->device != rep->device || pkt->flags != rep->flags || + offset != rep->offset) { + rep->device = 0; + rep->flags = 0; + rep->offset = 0; + rep->length = 0; + } + + if (offset == 0) { + if (rep->offset != 0) { + dev_warn(dev, "incomplete report off:%u len:%u", + rep->offset, rep->length); + } + memcpy(rep->buf, pkt->data, length); + rep->offset = length; + rep->length = length + remain; + rep->device = pkt->device; + rep->flags = pkt->flags; + } else if (offset == rep->offset) { + if (offset + length + remain != rep->length) { + dev_warn(dev, "incomplete report off:%u len:%u", + rep->offset, rep->length); + return; + } + memcpy(rep->buf + offset, pkt->data, length); + rep->offset += length; + + if (rep->offset == rep->length) { + spihid_process_message(spihid, rep->buf, rep->length, + rep->device, rep->flags); + rep->device = 0; + rep->flags = 0; + rep->offset = 0; + rep->length = 0; + } + } +} + +static void spihid_process_read(struct spihid_apple *spihid) +{ + u16 crc; + size_t length; + struct device *dev = &spihid->spidev->dev; + struct spihid_transfer_packet *pkt; + + pkt = (struct spihid_transfer_packet *)spihid->rx_buf; + + /* check transfer packet crc */ + crc = crc16(0, spihid->rx_buf, + offsetof(struct spihid_transfer_packet, crc16)); + if (crc != le16_to_cpu(pkt->crc16)) { + dev_warn_ratelimited(dev, "Read package crc mismatch\n"); + return; + } + + length = le16_to_cpu(pkt->length); + + if (length < sizeof(struct spihid_msg_hdr) + 2) { + if (length == sizeof(spi_hid_apple_booted) && + !memcmp(pkt->data, spi_hid_apple_booted, length)) { + if (!spihid->status_booted) { + spihid->status_booted = true; + wake_up_interruptible(&spihid->wait); + } + } else { + dev_info(dev, "R short packet: len:%zu\n", length); + print_hex_dump(KERN_INFO, "spihid pkt:", + DUMP_PREFIX_OFFSET, 16, 1, pkt->data, + length, false); + } + return; + } + +#if defined(DEBUG) && DEBUG > 1 + dev_dbg(dev, + "R pkt: flags:%02hhx dev:%02hhx off:%hu remain:%hu, len:%zu\n", + pkt->flags, pkt->device, pkt->offset, pkt->remain, length); +#if defined(DEBUG) && DEBUG > 2 + print_hex_dump_debug("spihid pkt: ", DUMP_PREFIX_OFFSET, 16, 1, + spihid->rx_buf, + sizeof(struct spihid_transfer_packet), true); +#endif +#endif + + if (length > sizeof(pkt->data)) { + dev_warn_ratelimited(dev, "Invalid pkt len:%zu", length); + return; + } + + /* short message */ + if (pkt->offset == 0 && pkt->remain == 0) { + spihid_process_message(spihid, pkt->data, length, pkt->device, + pkt->flags); + } else { + spihid_assemble_message(spihid, pkt); + } +} + +static void spihid_read_packet_sync(struct spihid_apple *spihid) +{ + int err; + + err = spi_sync(spihid->spidev, &spihid->rx_msg); + if (!err) { + spihid_process_read(spihid); + } else { + dev_warn(&spihid->spidev->dev, "RX failed: %d\n", err); + } +} + +irqreturn_t spihid_apple_core_irq(int irq, void *data) +{ + struct spi_device *spi = data; + struct spihid_apple *spihid = spi_get_drvdata(spi); + + spihid_read_packet_sync(spihid); + + return IRQ_HANDLED; +} +EXPORT_SYMBOL_GPL(spihid_apple_core_irq); + +static void spihid_apple_setup_spi_msgs(struct spihid_apple *spihid) +{ + memset(&spihid->rx_transfer, 0, sizeof(spihid->rx_transfer)); + + spihid->rx_transfer.rx_buf = spihid->rx_buf; + spihid->rx_transfer.len = sizeof(struct spihid_transfer_packet); + + spi_message_init(&spihid->rx_msg); + spi_message_add_tail(&spihid->rx_transfer, &spihid->rx_msg); + + memset(&spihid->tx_transfer, 0, sizeof(spihid->rx_transfer)); + memset(&spihid->status_transfer, 0, sizeof(spihid->status_transfer)); + + spihid->tx_transfer.tx_buf = spihid->tx_buf; + spihid->tx_transfer.len = sizeof(struct spihid_transfer_packet); + spihid->tx_transfer.delay.unit = SPI_DELAY_UNIT_USECS; + spihid->tx_transfer.delay.value = SPI_RW_CHG_DELAY_US; + + spihid->status_transfer.rx_buf = spihid->status_buf; + spihid->status_transfer.len = sizeof(spi_hid_apple_status_ok); + + spi_message_init(&spihid->tx_msg); + spi_message_add_tail(&spihid->tx_transfer, &spihid->tx_msg); + spi_message_add_tail(&spihid->status_transfer, &spihid->tx_msg); +} + +static int spihid_apple_setup_spi(struct spihid_apple *spihid) +{ + spihid_apple_setup_spi_msgs(spihid); + + return spihid->ops->power_on(spihid->ops); +} + +static int spihid_register_hid_device(struct spihid_apple *spihid, + struct spihid_interface *iface, u8 device) +{ + int ret; + char *suffix; + struct hid_device *hid; + + iface->id = device; + + hid = hid_allocate_device(); + if (IS_ERR(hid)) + return PTR_ERR(hid); + + /* + * Use 'Apple SPI Keyboard' and 'Apple SPI Trackpad' as input device + * names. The device names need to be distinct since at least Kwin uses + * the tripple Vendor ID, Product ID, Name to identify devices. + */ + snprintf(hid->name, sizeof(hid->name), "Apple SPI %s", iface->name); + // strip ' / Boot' suffix from the name + suffix = strstr(hid->name, " / Boot"); + if (suffix) + suffix[0] = '\0'; + snprintf(hid->phys, sizeof(hid->phys), "%s (%hhx)", + dev_name(&spihid->spidev->dev), device); + strscpy(hid->uniq, spihid->serial, sizeof(hid->uniq)); + + hid->ll_driver = &apple_hid_ll; + hid->bus = BUS_SPI; + hid->vendor = spihid->vendor_id; + hid->product = spihid->product_id; + hid->version = spihid->version_number; + + if (device == SPIHID_DEVICE_ID_KBD) + hid->type = HID_TYPE_SPI_KEYBOARD; + else if (device == SPIHID_DEVICE_ID_TP) + hid->type = HID_TYPE_SPI_MOUSE; + + hid->country = iface->country; + hid->dev.parent = &spihid->spidev->dev; + hid->driver_data = iface; + + ret = hid_add_device(hid); + if (ret < 0) { + hid_destroy_device(hid); + dev_warn(&spihid->spidev->dev, + "Failed to register hid device %hhu", device); + return ret; + } + + iface->hid = hid; + + return 0; +} + +static void spihid_destroy_hid_device(struct spihid_interface *iface) +{ + if (iface->hid) { + hid_destroy_device(iface->hid); + iface->hid = NULL; + } + iface->ready = false; +} + +int spihid_apple_core_probe(struct spi_device *spi, struct spihid_apple_ops *ops) +{ + struct device *dev = &spi->dev; + struct spihid_apple *spihid; + int err, i; + + if (!ops || !ops->power_on || !ops->power_off || !ops->enable_irq || !ops->disable_irq) + return -EINVAL; + + spihid = devm_kzalloc(dev, sizeof(*spihid), GFP_KERNEL); + if (!spihid) + return -ENOMEM; + + spihid->ops = ops; + spihid->spidev = spi; + + // init spi + spi_set_drvdata(spi, spihid); + + /* + * allocate SPI buffers + * Overallocate the receice buffer since it passed directly into + * hid_input_report / hid_report_raw_event. The later expects the buffer + * to be HID_MAX_BUFFER_SIZE (16k) or hid_ll_driver.max_buffer_size if + * set. + */ + spihid->rx_buf = devm_kmalloc( + &spi->dev, SPIHID_MAX_INPUT_REPORT_SIZE, GFP_KERNEL); + spihid->tx_buf = devm_kmalloc( + &spi->dev, sizeof(struct spihid_transfer_packet), GFP_KERNEL); + spihid->status_buf = devm_kmalloc( + &spi->dev, sizeof(spi_hid_apple_status_ok), GFP_KERNEL); + + if (!spihid->rx_buf || !spihid->tx_buf || !spihid->status_buf) + return -ENOMEM; + + spihid->report.buf = + devm_kmalloc(dev, SPIHID_MAX_INPUT_REPORT_SIZE, GFP_KERNEL); + + spihid->kbd.hid_desc = devm_kmalloc(dev, SPIHID_DESC_MAX, GFP_KERNEL); + spihid->tp.hid_desc = devm_kmalloc(dev, SPIHID_DESC_MAX, GFP_KERNEL); + + if (!spihid->report.buf || !spihid->kbd.hid_desc || + !spihid->tp.hid_desc) + return -ENOMEM; + + init_waitqueue_head(&spihid->wait); + + mutex_init(&spihid->tx_lock); + + /* Init spi transfer buffers and power device on */ + err = spihid_apple_setup_spi(spihid); + if (err < 0) + goto error; + + /* enable HID irq */ + spihid->ops->enable_irq(spihid->ops); + + // wait for boot message + err = wait_event_interruptible_timeout(spihid->wait, + spihid->status_booted, + msecs_to_jiffies(1000)); + if (err == 0) + err = -ENODEV; + if (err < 0) { + dev_err(dev, "waiting for device boot failed: %d", err); + goto error; + } + + /* request device information */ + dev_dbg(dev, "request device info"); + spihid_apple_request(spihid, 0xd0, 0x20, 0x01, 0xd0, 0, NULL, 0); + err = wait_event_interruptible_timeout(spihid->wait, spihid->vendor_id, + SPIHID_DEF_WAIT); + if (err == 0) + err = -ENODEV; + if (err < 0) { + dev_err(dev, "waiting for device info failed: %d", err); + goto error; + } + + /* request interface information */ + for (i = 0; i < spihid->num_devices; i++) { + struct spihid_interface *iface = spihid_get_iface(spihid, i); + if (!iface) + continue; + dev_dbg(dev, "request interface info 0x%02x", i); + spihid_apple_request(spihid, 0xd0, 0x20, 0x02, i, + SPIHID_DESC_MAX, NULL, 0); + err = wait_event_interruptible_timeout( + spihid->wait, iface->max_input_report_len, + SPIHID_DEF_WAIT); + } + + /* request HID report descriptors */ + for (i = 1; i < spihid->num_devices; i++) { + struct spihid_interface *iface = spihid_get_iface(spihid, i); + if (!iface) + continue; + dev_dbg(dev, "request hid report desc 0x%02x", i); + spihid_apple_request(spihid, 0xd0, 0x20, 0x10, i, + SPIHID_DESC_MAX, NULL, 0); + wait_event_interruptible_timeout( + spihid->wait, iface->hid_desc_len, SPIHID_DEF_WAIT); + } + + return 0; +error: + return err; +} +EXPORT_SYMBOL_GPL(spihid_apple_core_probe); + +void spihid_apple_core_remove(struct spi_device *spi) +{ + struct spihid_apple *spihid = spi_get_drvdata(spi); + + /* destroy input devices */ + + spihid_destroy_hid_device(&spihid->tp); + spihid_destroy_hid_device(&spihid->kbd); + + /* disable irq */ + spihid->ops->disable_irq(spihid->ops); + + /* power SPI device down */ + spihid->ops->power_off(spihid->ops); +} +EXPORT_SYMBOL_GPL(spihid_apple_core_remove); + +void spihid_apple_core_shutdown(struct spi_device *spi) +{ + struct spihid_apple *spihid = spi_get_drvdata(spi); + + /* disable irq */ + spihid->ops->disable_irq(spihid->ops); + + /* power SPI device down */ + spihid->ops->power_off(spihid->ops); +} +EXPORT_SYMBOL_GPL(spihid_apple_core_shutdown); + +#ifdef CONFIG_PM_SLEEP +static int spihid_apple_core_suspend(struct device *dev) +{ + int ret; +#ifdef IRQ_WAKE_SUPPORT + int wake_status; +#endif + struct spihid_apple *spihid = spi_get_drvdata(to_spi_device(dev)); + + if (spihid->tp.hid) { + ret = hid_driver_suspend(spihid->tp.hid, PMSG_SUSPEND); + if (ret < 0) + return ret; + } + + if (spihid->kbd.hid) { + ret = hid_driver_suspend(spihid->kbd.hid, PMSG_SUSPEND); + if (ret < 0) { + if (spihid->tp.hid) + hid_driver_resume(spihid->tp.hid); + return ret; + } + } + + /* Save some power */ + spihid->ops->disable_irq(spihid->ops); + +#ifdef IRQ_WAKE_SUPPORT + if (device_may_wakeup(dev)) { + wake_status = spihid->ops->enable_irq_wake(spihid->ops); + if (!wake_status) + spihid->irq_wake_enabled = true; + else + dev_warn(dev, "Failed to enable irq wake: %d\n", + wake_status); + } else { + spihid->ops->power_off(spihid->ops); + } +#else + spihid->ops->power_off(spihid->ops); +#endif + + return 0; +} + +static int spihid_apple_core_resume(struct device *dev) +{ + int ret_tp = 0, ret_kbd = 0; + struct spihid_apple *spihid = spi_get_drvdata(to_spi_device(dev)); +#ifdef IRQ_WAKE_SUPPORT + int wake_status; + + if (!device_may_wakeup(dev)) { + spihid->ops->power_on(spihid->ops); + } else if (spihid->irq_wake_enabled) { + wake_status = spihid->ops->disable_irq_wake(spihid->ops); + if (!wake_status) + spihid->irq_wake_enabled = false; + else + dev_warn(dev, "Failed to disable irq wake: %d\n", + wake_status); + } +#endif + + spihid->ops->enable_irq(spihid->ops); + spihid->ops->power_on(spihid->ops); + + if (spihid->tp.hid) + ret_tp = hid_driver_reset_resume(spihid->tp.hid); + if (spihid->kbd.hid) + ret_kbd = hid_driver_reset_resume(spihid->kbd.hid); + + if (ret_tp < 0) + return ret_tp; + + return ret_kbd; +} +#endif + +const struct dev_pm_ops spihid_apple_core_pm = { + SET_SYSTEM_SLEEP_PM_OPS(spihid_apple_core_suspend, + spihid_apple_core_resume) +}; +EXPORT_SYMBOL_GPL(spihid_apple_core_pm); + +MODULE_DESCRIPTION("Apple SPI HID transport driver"); +MODULE_AUTHOR("Janne Grunau "); +MODULE_LICENSE("GPL"); diff --git a/drivers/hid/spi-hid/spi-hid-apple-of.c b/drivers/hid/spi-hid/spi-hid-apple-of.c new file mode 100644 index 00000000000000..b631212b836d30 --- /dev/null +++ b/drivers/hid/spi-hid/spi-hid-apple-of.c @@ -0,0 +1,153 @@ +/* + * SPDX-License-Identifier: GPL-2.0 + * + * Apple SPI HID transport driver - Open Firmware + * + * Copyright (C) The Asahi Linux Contributors + */ + +#include +#include +#include +#include + +#include "spi-hid-apple.h" + + +struct spihid_apple_of { + struct spihid_apple_ops ops; + + struct gpio_desc *enable_gpio; + int irq; +}; + +static int spihid_apple_of_power_on(struct spihid_apple_ops *ops) +{ + struct spihid_apple_of *sh_of = container_of(ops, struct spihid_apple_of, ops); + + /* reset the controller on boot */ + gpiod_direction_output(sh_of->enable_gpio, 1); + msleep(5); + gpiod_direction_output(sh_of->enable_gpio, 0); + msleep(5); + /* turn SPI device on */ + gpiod_direction_output(sh_of->enable_gpio, 1); + msleep(50); + + return 0; +} + +static int spihid_apple_of_power_off(struct spihid_apple_ops *ops) +{ + struct spihid_apple_of *sh_of = container_of(ops, struct spihid_apple_of, ops); + + /* turn SPI device off */ + gpiod_direction_output(sh_of->enable_gpio, 0); + + return 0; +} + +static int spihid_apple_of_enable_irq(struct spihid_apple_ops *ops) +{ + struct spihid_apple_of *sh_of = container_of(ops, struct spihid_apple_of, ops); + + enable_irq(sh_of->irq); + + return 0; +} + +static int spihid_apple_of_disable_irq(struct spihid_apple_ops *ops) +{ + struct spihid_apple_of *sh_of = container_of(ops, struct spihid_apple_of, ops); + + disable_irq(sh_of->irq); + + return 0; +} + +static int spihid_apple_of_enable_irq_wake(struct spihid_apple_ops *ops) +{ + struct spihid_apple_of *sh_of = container_of(ops, struct spihid_apple_of, ops); + + return enable_irq_wake(sh_of->irq); +} + +static int spihid_apple_of_disable_irq_wake(struct spihid_apple_ops *ops) +{ + struct spihid_apple_of *sh_of = container_of(ops, struct spihid_apple_of, ops); + + return disable_irq_wake(sh_of->irq); +} + +static int spihid_apple_of_probe(struct spi_device *spi) +{ + struct device *dev = &spi->dev; + struct spihid_apple_of *spihid_of; + int err; + + spihid_of = devm_kzalloc(dev, sizeof(*spihid_of), GFP_KERNEL); + if (!spihid_of) + return -ENOMEM; + + spihid_of->ops.power_on = spihid_apple_of_power_on; + spihid_of->ops.power_off = spihid_apple_of_power_off; + spihid_of->ops.enable_irq = spihid_apple_of_enable_irq; + spihid_of->ops.disable_irq = spihid_apple_of_disable_irq; + spihid_of->ops.enable_irq_wake = spihid_apple_of_enable_irq_wake; + spihid_of->ops.disable_irq_wake = spihid_apple_of_disable_irq_wake; + + spihid_of->enable_gpio = devm_gpiod_get_index(dev, "spien", 0, 0); + if (IS_ERR(spihid_of->enable_gpio)) { + err = PTR_ERR(spihid_of->enable_gpio); + dev_err(dev, "failed to get 'spien' gpio pin: %d", err); + return err; + } + + spihid_of->irq = of_irq_get(dev->of_node, 0); + if (spihid_of->irq < 0) { + err = spihid_of->irq; + dev_err(dev, "failed to get 'extended-irq': %d", err); + return err; + } + err = devm_request_threaded_irq(dev, spihid_of->irq, NULL, + spihid_apple_core_irq, IRQF_ONESHOT | IRQF_NO_AUTOEN, + "spi-hid-apple-irq", spi); + if (err < 0) { + dev_err(dev, "failed to request extended-irq %d: %d", + spihid_of->irq, err); + return err; + } + + return spihid_apple_core_probe(spi, &spihid_of->ops); +} + +static const struct of_device_id spihid_apple_of_match[] = { + { .compatible = "apple,spi-hid-transport" }, + {}, +}; +MODULE_DEVICE_TABLE(of, spihid_apple_of_match); + +static struct spi_device_id spihid_apple_of_id[] = { + { "spi-hid-transport", 0 }, + {} +}; +MODULE_DEVICE_TABLE(spi, spihid_apple_of_id); + +static struct spi_driver spihid_apple_of_driver = { + .driver = { + .name = "spi-hid-apple-of", + .pm = &spihid_apple_core_pm, + .of_match_table = of_match_ptr(spihid_apple_of_match), + }, + + .id_table = spihid_apple_of_id, + .probe = spihid_apple_of_probe, + .remove = spihid_apple_core_remove, + .shutdown = spihid_apple_core_shutdown, +}; + +module_spi_driver(spihid_apple_of_driver); + +MODULE_DESCRIPTION("Apple SPI HID transport driver for OpenFirmware systems"); +MODULE_AUTHOR("Janne Grunau "); +MODULE_LICENSE("GPL"); diff --git a/drivers/hid/spi-hid/spi-hid-apple.h b/drivers/hid/spi-hid/spi-hid-apple.h new file mode 100644 index 00000000000000..9abecd1ba78028 --- /dev/null +++ b/drivers/hid/spi-hid/spi-hid-apple.h @@ -0,0 +1,35 @@ +/* SPDX-License-Identifier: GPL-2.0-only OR MIT */ + +#ifndef SPI_HID_APPLE_H +#define SPI_HID_APPLE_H + +#include +#include + +/** + * struct spihid_apple_ops - Ops to control the device from the core driver. + * + * @power_on: reset and power the device on. + * @power_off: power the device off. + * @enable_irq: enable irq or ACPI gpe. + * @disable_irq: disable irq or ACPI gpe. + */ + +struct spihid_apple_ops { + int (*power_on)(struct spihid_apple_ops *ops); + int (*power_off)(struct spihid_apple_ops *ops); + int (*enable_irq)(struct spihid_apple_ops *ops); + int (*disable_irq)(struct spihid_apple_ops *ops); + int (*enable_irq_wake)(struct spihid_apple_ops *ops); + int (*disable_irq_wake)(struct spihid_apple_ops *ops); +}; + +irqreturn_t spihid_apple_core_irq(int irq, void *data); + +int spihid_apple_core_probe(struct spi_device *spi, struct spihid_apple_ops *ops); +void spihid_apple_core_remove(struct spi_device *spi); +void spihid_apple_core_shutdown(struct spi_device *spi); + +extern const struct dev_pm_ops spihid_apple_core_pm; + +#endif /* SPI_HID_APPLE_H */ From c76440711dfdd8ff32f1ee5b7357f738b86fadd2 Mon Sep 17 00:00:00 2001 From: Hector Martin Date: Fri, 8 Jul 2022 02:09:24 +0900 Subject: [PATCH 0180/3902] soc: apple: Add DockChannel driver DockChannel is a simple FIFO interface used to communicate between SoC blocks. Add a driver that represents the shared interrupt controller for the DockChannel block, and then exposes probe and data transfer functions that child device drivers can use to instantiate individual FIFOs. Signed-off-by: Hector Martin --- drivers/soc/apple/Kconfig | 9 + drivers/soc/apple/Makefile | 3 + drivers/soc/apple/dockchannel.c | 406 ++++++++++++++++++++++++++ include/linux/soc/apple/dockchannel.h | 26 ++ 4 files changed, 444 insertions(+) create mode 100644 drivers/soc/apple/dockchannel.c create mode 100644 include/linux/soc/apple/dockchannel.h diff --git a/drivers/soc/apple/Kconfig b/drivers/soc/apple/Kconfig index ad67368892311b..a63be2293bdc84 100644 --- a/drivers/soc/apple/Kconfig +++ b/drivers/soc/apple/Kconfig @@ -4,6 +4,15 @@ if ARCH_APPLE || COMPILE_TEST menu "Apple SoC drivers" +config APPLE_DOCKCHANNEL + tristate "Apple DockChannel FIFO" + depends on ARCH_APPLE || COMPILE_TEST + help + DockChannel is a simple FIFO used on Apple SoCs for debug and inter-processor + communications. + + Say 'y' here if you have an Apple SoC. + config APPLE_MAILBOX tristate "Apple SoC mailboxes" depends on PM diff --git a/drivers/soc/apple/Makefile b/drivers/soc/apple/Makefile index 4d9ab8f3037b71..0b6a9f92bbbbf8 100644 --- a/drivers/soc/apple/Makefile +++ b/drivers/soc/apple/Makefile @@ -1,5 +1,8 @@ # SPDX-License-Identifier: GPL-2.0-only +obj-$(CONFIG_APPLE_DOCKCHANNEL) += apple-dockchannel.o +apple-dockchannel-y = dockchannel.o + obj-$(CONFIG_APPLE_MAILBOX) += apple-mailbox.o apple-mailbox-y = mailbox.o diff --git a/drivers/soc/apple/dockchannel.c b/drivers/soc/apple/dockchannel.c new file mode 100644 index 00000000000000..3a0d7964007c95 --- /dev/null +++ b/drivers/soc/apple/dockchannel.c @@ -0,0 +1,406 @@ +// SPDX-License-Identifier: GPL-2.0-only OR MIT +/* + * Apple DockChannel FIFO driver + * Copyright The Asahi Linux Contributors + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define DOCKCHANNEL_MAX_IRQ 32 + +#define DOCKCHANNEL_TX_TIMEOUT_MS 1000 +#define DOCKCHANNEL_RX_TIMEOUT_MS 1000 + +#define IRQ_MASK 0x0 +#define IRQ_FLAG 0x4 + +#define IRQ_TX BIT(0) +#define IRQ_RX BIT(1) + +#define CONFIG_TX_THRESH 0x0 +#define CONFIG_RX_THRESH 0x4 + +#define DATA_TX8 0x4 +#define DATA_TX16 0x8 +#define DATA_TX24 0xc +#define DATA_TX32 0x10 +#define DATA_TX_FREE 0x14 +#define DATA_RX8 0x1c +#define DATA_RX16 0x20 +#define DATA_RX24 0x24 +#define DATA_RX32 0x28 +#define DATA_RX_COUNT 0x2c + +struct dockchannel { + struct device *dev; + int tx_irq; + int rx_irq; + + void __iomem *config_base; + void __iomem *data_base; + + u32 fifo_size; + bool awaiting; + struct completion tx_comp; + struct completion rx_comp; + + void *cookie; + void (*data_available)(void *cookie, size_t avail); +}; + +struct dockchannel_common { + struct device *dev; + struct irq_domain *domain; + int irq; + + void __iomem *irq_base; +}; + +/* Dockchannel FIFO functions */ + +static irqreturn_t dockchannel_tx_irq(int irq, void *data) +{ + struct dockchannel *dockchannel = data; + + disable_irq_nosync(irq); + complete(&dockchannel->tx_comp); + + return IRQ_HANDLED; +} + +static irqreturn_t dockchannel_rx_irq(int irq, void *data) +{ + struct dockchannel *dockchannel = data; + + disable_irq_nosync(irq); + + if (dockchannel->awaiting) { + return IRQ_WAKE_THREAD; + } else { + complete(&dockchannel->rx_comp); + return IRQ_HANDLED; + } +} + +static irqreturn_t dockchannel_rx_irq_thread(int irq, void *data) +{ + struct dockchannel *dockchannel = data; + size_t avail = readl_relaxed(dockchannel->data_base + DATA_RX_COUNT); + + dockchannel->awaiting = false; + dockchannel->data_available(dockchannel->cookie, avail); + + return IRQ_HANDLED; +} + +int dockchannel_send(struct dockchannel *dockchannel, const void *buf, size_t count) +{ + size_t left = count; + const u8 *p = buf; + + while (left > 0) { + size_t avail = readl_relaxed(dockchannel->data_base + DATA_TX_FREE); + size_t block = min(left, avail); + + if (avail == 0) { + size_t threshold = min((size_t)(dockchannel->fifo_size / 2), left); + + writel_relaxed(threshold, dockchannel->config_base + CONFIG_TX_THRESH); + reinit_completion(&dockchannel->tx_comp); + enable_irq(dockchannel->tx_irq); + + if (!wait_for_completion_timeout(&dockchannel->tx_comp, + msecs_to_jiffies(DOCKCHANNEL_TX_TIMEOUT_MS))) { + disable_irq(dockchannel->tx_irq); + return -ETIMEDOUT; + } + + continue; + } + + while (block >= 4) { + writel_relaxed(get_unaligned_le32(p), dockchannel->data_base + DATA_TX32); + p += 4; + left -= 4; + block -= 4; + } + while (block > 0) { + writeb_relaxed(*p++, dockchannel->data_base + DATA_TX8); + left--; + block--; + } + } + + return count; +} +EXPORT_SYMBOL(dockchannel_send); + +int dockchannel_recv(struct dockchannel *dockchannel, void *buf, size_t count) +{ + size_t left = count; + u8 *p = buf; + + while (left > 0) { + size_t avail = readl_relaxed(dockchannel->data_base + DATA_RX_COUNT); + size_t block = min(left, avail); + + if (avail == 0) { + size_t threshold = min((size_t)(dockchannel->fifo_size / 2), left); + + writel_relaxed(threshold, dockchannel->config_base + CONFIG_RX_THRESH); + reinit_completion(&dockchannel->rx_comp); + enable_irq(dockchannel->rx_irq); + + if (!wait_for_completion_timeout(&dockchannel->rx_comp, + msecs_to_jiffies(DOCKCHANNEL_RX_TIMEOUT_MS))) { + disable_irq(dockchannel->rx_irq); + return -ETIMEDOUT; + } + + continue; + } + + while (block >= 4) { + put_unaligned_le32(readl_relaxed(dockchannel->data_base + DATA_RX32), p); + p += 4; + left -= 4; + block -= 4; + } + while (block > 0) { + *p++ = readl_relaxed(dockchannel->data_base + DATA_RX8) >> 8; + left--; + block--; + } + } + + return count; +} +EXPORT_SYMBOL(dockchannel_recv); + +int dockchannel_await(struct dockchannel *dockchannel, + void (*callback)(void *cookie, size_t avail), + void *cookie, size_t count) +{ + size_t threshold = min((size_t)dockchannel->fifo_size, count); + + if (!count) { + dockchannel->awaiting = false; + disable_irq(dockchannel->rx_irq); + return 0; + } + + dockchannel->data_available = callback; + dockchannel->cookie = cookie; + dockchannel->awaiting = true; + writel_relaxed(threshold, dockchannel->config_base + CONFIG_RX_THRESH); + enable_irq(dockchannel->rx_irq); + + return threshold; +} +EXPORT_SYMBOL(dockchannel_await); + +struct dockchannel *dockchannel_init(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct dockchannel *dockchannel; + int ret; + + dockchannel = devm_kzalloc(dev, sizeof(*dockchannel), GFP_KERNEL); + if (!dockchannel) + return ERR_PTR(-ENOMEM); + + dockchannel->dev = dev; + dockchannel->config_base = devm_platform_ioremap_resource_byname(pdev, "config"); + if (IS_ERR(dockchannel->config_base)) + return (__force void *)dockchannel->config_base; + + dockchannel->data_base = devm_platform_ioremap_resource_byname(pdev, "data"); + if (IS_ERR(dockchannel->data_base)) + return (__force void *)dockchannel->data_base; + + ret = of_property_read_u32(dev->of_node, "apple,fifo-size", &dockchannel->fifo_size); + if (ret) + return ERR_PTR(dev_err_probe(dev, ret, "Missing apple,fifo-size property")); + + init_completion(&dockchannel->tx_comp); + init_completion(&dockchannel->rx_comp); + + dockchannel->tx_irq = platform_get_irq_byname(pdev, "tx"); + if (dockchannel->tx_irq <= 0) { + return ERR_PTR(dev_err_probe(dev, dockchannel->tx_irq, + "Failed to get TX IRQ")); + } + + dockchannel->rx_irq = platform_get_irq_byname(pdev, "rx"); + if (dockchannel->rx_irq <= 0) { + return ERR_PTR(dev_err_probe(dev, dockchannel->rx_irq, + "Failed to get RX IRQ")); + } + + ret = devm_request_irq(dev, dockchannel->tx_irq, dockchannel_tx_irq, IRQF_NO_AUTOEN, + "apple-dockchannel-tx", dockchannel); + if (ret) + return ERR_PTR(dev_err_probe(dev, ret, "Failed to request TX IRQ")); + + ret = devm_request_threaded_irq(dev, dockchannel->rx_irq, dockchannel_rx_irq, + dockchannel_rx_irq_thread, IRQF_NO_AUTOEN, + "apple-dockchannel-rx", dockchannel); + if (ret) + return ERR_PTR(dev_err_probe(dev, ret, "Failed to request RX IRQ")); + + return dockchannel; +} +EXPORT_SYMBOL(dockchannel_init); + + +/* Dockchannel IRQchip */ + +static void dockchannel_irq(struct irq_desc *desc) +{ + unsigned int irq = irq_desc_get_irq(desc); + struct irq_chip *chip = irq_desc_get_chip(desc); + struct dockchannel_common *dcc = irq_get_handler_data(irq); + unsigned long flags = readl_relaxed(dcc->irq_base + IRQ_FLAG); + int bit; + + chained_irq_enter(chip, desc); + + for_each_set_bit(bit, &flags, DOCKCHANNEL_MAX_IRQ) + generic_handle_domain_irq(dcc->domain, bit); + + chained_irq_exit(chip, desc); +} + +static void dockchannel_irq_ack(struct irq_data *data) +{ + struct dockchannel_common *dcc = irq_data_get_irq_chip_data(data); + unsigned int hwirq = data->hwirq; + + writel_relaxed(BIT(hwirq), dcc->irq_base + IRQ_FLAG); +} + +static void dockchannel_irq_mask(struct irq_data *data) +{ + struct dockchannel_common *dcc = irq_data_get_irq_chip_data(data); + unsigned int hwirq = data->hwirq; + u32 val = readl_relaxed(dcc->irq_base + IRQ_MASK); + + writel_relaxed(val & ~BIT(hwirq), dcc->irq_base + IRQ_MASK); +} + +static void dockchannel_irq_unmask(struct irq_data *data) +{ + struct dockchannel_common *dcc = irq_data_get_irq_chip_data(data); + unsigned int hwirq = data->hwirq; + u32 val = readl_relaxed(dcc->irq_base + IRQ_MASK); + + writel_relaxed(val | BIT(hwirq), dcc->irq_base + IRQ_MASK); +} + +static const struct irq_chip dockchannel_irqchip = { + .name = "dockchannel-irqc", + .irq_ack = dockchannel_irq_ack, + .irq_mask = dockchannel_irq_mask, + .irq_unmask = dockchannel_irq_unmask, +}; + +static int dockchannel_irq_domain_map(struct irq_domain *d, unsigned int virq, + irq_hw_number_t hw) +{ + irq_set_chip_data(virq, d->host_data); + irq_set_chip_and_handler(virq, &dockchannel_irqchip, handle_level_irq); + + return 0; +} + +static const struct irq_domain_ops dockchannel_irq_domain_ops = { + .xlate = irq_domain_xlate_twocell, + .map = dockchannel_irq_domain_map, +}; + +static int dockchannel_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct dockchannel_common *dcc; + struct device_node *child; + + dcc = devm_kzalloc(dev, sizeof(*dcc), GFP_KERNEL); + if (!dcc) + return -ENOMEM; + + dcc->dev = dev; + platform_set_drvdata(pdev, dcc); + + dcc->irq_base = devm_platform_ioremap_resource_byname(pdev, "irq"); + if (IS_ERR(dcc->irq_base)) + return PTR_ERR(dcc->irq_base); + + writel_relaxed(0, dcc->irq_base + IRQ_MASK); + writel_relaxed(~0, dcc->irq_base + IRQ_FLAG); + + dcc->domain = irq_domain_add_linear(dev->of_node, DOCKCHANNEL_MAX_IRQ, + &dockchannel_irq_domain_ops, dcc); + if (!dcc->domain) + return -ENOMEM; + + dcc->irq = platform_get_irq(pdev, 0); + if (dcc->irq <= 0) + return dev_err_probe(dev, dcc->irq, "Failed to get IRQ"); + + irq_set_handler_data(dcc->irq, dcc); + irq_set_chained_handler(dcc->irq, dockchannel_irq); + + for_each_child_of_node(dev->of_node, child) + of_platform_device_create(child, NULL, dev); + + return 0; +} + +static void dockchannel_remove(struct platform_device *pdev) +{ + struct dockchannel_common *dcc = platform_get_drvdata(pdev); + int hwirq; + + device_for_each_child(&pdev->dev, NULL, of_platform_device_destroy); + + irq_set_chained_handler_and_data(dcc->irq, NULL, NULL); + + for (hwirq = 0; hwirq < DOCKCHANNEL_MAX_IRQ; hwirq++) + irq_dispose_mapping(irq_find_mapping(dcc->domain, hwirq)); + + irq_domain_remove(dcc->domain); + + writel_relaxed(0, dcc->irq_base + IRQ_MASK); + writel_relaxed(~0, dcc->irq_base + IRQ_FLAG); +} + +static const struct of_device_id dockchannel_of_match[] = { + { .compatible = "apple,dockchannel" }, + {}, +}; +MODULE_DEVICE_TABLE(of, dockchannel_of_match); + +static struct platform_driver dockchannel_driver = { + .driver = { + .name = "dockchannel", + .of_match_table = dockchannel_of_match, + }, + .probe = dockchannel_probe, + .remove = dockchannel_remove, +}; +module_platform_driver(dockchannel_driver); + +MODULE_AUTHOR("Hector Martin "); +MODULE_LICENSE("Dual MIT/GPL"); +MODULE_DESCRIPTION("Apple DockChannel driver"); diff --git a/include/linux/soc/apple/dockchannel.h b/include/linux/soc/apple/dockchannel.h new file mode 100644 index 00000000000000..0b7093935ddf47 --- /dev/null +++ b/include/linux/soc/apple/dockchannel.h @@ -0,0 +1,26 @@ +/* SPDX-License-Identifier: GPL-2.0-only OR MIT */ +/* + * Apple Dockchannel devices + * Copyright (C) The Asahi Linux Contributors + */ +#ifndef _LINUX_APPLE_DOCKCHANNEL_H_ +#define _LINUX_APPLE_DOCKCHANNEL_H_ + +#include +#include +#include + +#if IS_ENABLED(CONFIG_APPLE_DOCKCHANNEL) + +struct dockchannel; + +struct dockchannel *dockchannel_init(struct platform_device *pdev); + +int dockchannel_send(struct dockchannel *dockchannel, const void *buf, size_t count); +int dockchannel_recv(struct dockchannel *dockchannel, void *buf, size_t count); +int dockchannel_await(struct dockchannel *dockchannel, + void (*callback)(void *cookie, size_t avail), + void *cookie, size_t count); + +#endif +#endif From 4cd171516f87ef265a1c930fb9630c96d74d3832 Mon Sep 17 00:00:00 2001 From: Hector Martin Date: Fri, 8 Jul 2022 02:11:21 +0900 Subject: [PATCH 0181/3902] HID: Add Apple DockChannel HID transport driver Apple M2 devices have an MTP coprocessor embedded in the SoC that handles HID for the integrated touchpad/keyboard, and communicates over the DockChannel interface. This driver implements this new interface. Signed-off-by: Hector Martin --- drivers/hid/Kconfig | 2 + drivers/hid/Makefile | 4 + drivers/hid/dockchannel-hid/Kconfig | 13 + drivers/hid/dockchannel-hid/Makefile | 6 + drivers/hid/dockchannel-hid/dockchannel-hid.c | 1213 +++++++++++++++++ 5 files changed, 1238 insertions(+) create mode 100644 drivers/hid/dockchannel-hid/Kconfig create mode 100644 drivers/hid/dockchannel-hid/Makefile create mode 100644 drivers/hid/dockchannel-hid/dockchannel-hid.c diff --git a/drivers/hid/Kconfig b/drivers/hid/Kconfig index 5a9b8f192dcd64..988e06c80fb28d 100644 --- a/drivers/hid/Kconfig +++ b/drivers/hid/Kconfig @@ -1448,4 +1448,6 @@ source "drivers/hid/usbhid/Kconfig" source "drivers/hid/spi-hid/Kconfig" +source "drivers/hid/dockchannel-hid/Kconfig" + endif # HID_SUPPORT diff --git a/drivers/hid/Makefile b/drivers/hid/Makefile index e626eee18b11f5..2ed4942821539f 100644 --- a/drivers/hid/Makefile +++ b/drivers/hid/Makefile @@ -173,8 +173,12 @@ obj-$(CONFIG_INTEL_ISH_HID) += intel-ish-hid/ obj-$(CONFIG_AMD_SFH_HID) += amd-sfh-hid/ +obj-$(CONFIG_HID_DOCKCHANNEL) += dockchannel-hid/ + obj-$(CONFIG_SPI_HID_APPLE_CORE) += spi-hid/ +obj-$(CONFIG_HID_DOCKCHANNEL) += dockchannel-hid/ + obj-$(CONFIG_SURFACE_HID_CORE) += surface-hid/ obj-$(CONFIG_INTEL_THC_HID) += intel-thc-hid/ diff --git a/drivers/hid/dockchannel-hid/Kconfig b/drivers/hid/dockchannel-hid/Kconfig new file mode 100644 index 00000000000000..254961ad15e19c --- /dev/null +++ b/drivers/hid/dockchannel-hid/Kconfig @@ -0,0 +1,13 @@ +# SPDX-License-Identifier: GPL-2.0-only OR MIT +menu "DockChannel HID support" + depends on APPLE_DOCKCHANNEL + +config HID_DOCKCHANNEL + tristate "HID over DockChannel transport layer for Apple Silicon SoCs" + depends on APPLE_DOCKCHANNEL && INPUT && OF && HID + help + Say Y here if you use an M2 or later Apple Silicon based laptop. + The keyboard and touchpad are HID based devices connected via the + proprietary DockChannel interface. + +endmenu diff --git a/drivers/hid/dockchannel-hid/Makefile b/drivers/hid/dockchannel-hid/Makefile new file mode 100644 index 00000000000000..7dba766b047fcc --- /dev/null +++ b/drivers/hid/dockchannel-hid/Makefile @@ -0,0 +1,6 @@ +# SPDX-License-Identifier: GPL-2.0-only OR MIT +# +# Makefile for DockChannel HID transport drivers +# + +obj-$(CONFIG_HID_DOCKCHANNEL) += dockchannel-hid.o diff --git a/drivers/hid/dockchannel-hid/dockchannel-hid.c b/drivers/hid/dockchannel-hid/dockchannel-hid.c new file mode 100644 index 00000000000000..a712a724ded30b --- /dev/null +++ b/drivers/hid/dockchannel-hid/dockchannel-hid.c @@ -0,0 +1,1213 @@ +/* + * SPDX-License-Identifier: GPL-2.0 OR MIT + * + * Apple DockChannel HID transport driver + * + * Copyright The Asahi Linux Contributors + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "../hid-ids.h" + +#define COMMAND_TIMEOUT_MS 1000 +#define START_TIMEOUT_MS 2000 + +#define MAX_INTERFACES 16 + +/* Data + checksum */ +#define MAX_PKT_SIZE (0xffff + 4) + +#define DCHID_CHANNEL_CMD 0x11 +#define DCHID_CHANNEL_REPORT 0x12 + +struct dchid_hdr { + u8 hdr_len; + u8 channel; + u16 length; + u8 seq; + u8 iface; + u16 pad; +} __packed; + +#define IFACE_COMM 0 + +#define FLAGS_GROUP GENMASK(7, 6) +#define FLAGS_REQ GENMASK(5, 0) + +#define REQ_SET_REPORT 0 +#define REQ_GET_REPORT 1 + +struct dchid_subhdr { + u8 flags; + u8 unk; + u16 length; + u32 retcode; +} __packed; + +#define EVENT_GPIO_CMD 0xa0 +#define EVENT_INIT 0xf0 +#define EVENT_READY 0xf1 + +struct dchid_init_hdr { + u8 type; + u8 unk1; + u8 unk2; + u8 iface; + char name[16]; + u8 more_packets; + u8 unkpad; +} __packed; + +#define INIT_HID_DESCRIPTOR 0 +#define INIT_GPIO_REQUEST 1 +#define INIT_TERMINATOR 2 +#define INIT_PRODUCT_NAME 7 + +#define CMD_RESET_INTERFACE 0x40 +#define CMD_SEND_FIRMWARE 0x95 +#define CMD_ENABLE_INTERFACE 0xb4 +#define CMD_ACK_GPIO_CMD 0xa1 + +struct dchid_init_block_hdr { + u16 type; + u16 length; +} __packed; + +#define MAX_GPIO_NAME 32 + +struct dchid_gpio_request { + u16 unk; + u16 id; + char name[MAX_GPIO_NAME]; +} __packed; + +struct dchid_gpio_cmd { + u8 type; + u8 iface; + u8 gpio; + u8 unk; + u8 cmd; +} __packed; + +struct dchid_gpio_ack { + u8 type; + u32 retcode; + u8 cmd[]; +} __packed; + +#define STM_REPORT_ID 0x10 +#define STM_REPORT_SERIAL 0x11 +#define STM_REPORT_KEYBTYPE 0x14 + +struct dchid_stm_id { + u8 unk; + u16 vendor_id; + u16 product_id; + u16 version_number; + u8 unk2; + u8 unk3; + u8 keyboard_type; + u8 serial_length; + /* Serial follows, but we grab it with a different report. */ +} __packed; + +#define FW_MAGIC 0x46444948 +#define FW_VER 1 + +struct fw_header { + u32 magic; + u32 version; + u32 hdr_length; + u32 data_length; + u32 iface_offset; +} __packed; + +struct dchid_work { + struct work_struct work; + struct dchid_iface *iface; + + struct dchid_hdr hdr; + u8 data[]; +}; + +struct dchid_iface { + struct dockchannel_hid *dchid; + struct hid_device *hid; + struct workqueue_struct *wq; + + bool creating; + struct work_struct create_work; + + int index; + const char *name; + const struct device_node *of_node; + + uint8_t tx_seq; + bool deferred; + bool starting; + bool open; + struct completion ready; + + void *hid_desc; + size_t hid_desc_len; + + struct gpio_desc *gpio; + char gpio_name[MAX_GPIO_NAME]; + int gpio_id; + + struct mutex out_mutex; + u32 out_flags; + int out_report; + u32 retcode; + void *resp_buf; + size_t resp_size; + struct completion out_complete; + + u32 keyboard_layout_id; +}; + +struct dockchannel_hid { + struct device *dev; + struct dockchannel *dc; + struct device_link *helper_link; + + bool id_ready; + struct dchid_stm_id device_id; + char serial[64]; + + struct dchid_iface *comm; + struct dchid_iface *ifaces[MAX_INTERFACES]; + + u8 pkt_buf[MAX_PKT_SIZE]; + + /* Workqueue to asynchronously create HID devices */ + struct workqueue_struct *new_iface_wq; +}; + +static ssize_t apple_layout_id_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct hid_device *hdev = to_hid_device(dev); + struct dchid_iface *iface = hdev->driver_data; + + return scnprintf(buf, PAGE_SIZE, "%d\n", iface->keyboard_layout_id); +} + +static DEVICE_ATTR_RO(apple_layout_id); + +static struct dchid_iface * +dchid_get_interface(struct dockchannel_hid *dchid, int index, const char *name) +{ + struct dchid_iface *iface; + + if (index >= MAX_INTERFACES) { + dev_err(dchid->dev, "Interface index %d out of range\n", index); + return NULL; + } + + if (dchid->ifaces[index]) + return dchid->ifaces[index]; + + iface = devm_kzalloc(dchid->dev, sizeof(struct dchid_iface), GFP_KERNEL); + if (!iface) + return NULL; + + iface->index = index; + iface->name = devm_kstrdup(dchid->dev, name, GFP_KERNEL); + iface->dchid = dchid; + iface->out_report= -1; + init_completion(&iface->out_complete); + init_completion(&iface->ready); + mutex_init(&iface->out_mutex); + iface->wq = alloc_ordered_workqueue("dchid-%s", WQ_MEM_RECLAIM, iface->name); + if (!iface->wq) + return NULL; + + /* Comm is not a HID subdevice */ + if (!strcmp(name, "comm")) { + dchid->ifaces[index] = iface; + return iface; + } + + iface->of_node = of_get_child_by_name(dchid->dev->of_node, name); + if (!iface->of_node) { + dev_warn(dchid->dev, "No OF node for subdevice %s, ignoring.", name); + return NULL; + } + + dchid->ifaces[index] = iface; + return iface; +} + +static u32 dchid_checksum(void *p, size_t length) +{ + u32 sum = 0; + + while (length >= 4) { + sum += get_unaligned_le32(p); + p += 4; + length -= 4; + } + + WARN_ON_ONCE(length); + return sum; +} + +static int dchid_send(struct dchid_iface *iface, u32 flags, void *msg, size_t size) +{ + u32 checksum = 0xffffffff; + size_t wsize = round_down(size, 4); + size_t tsize = size - wsize; + int ret; + struct { + struct dchid_hdr hdr; + struct dchid_subhdr sub; + } __packed h; + + memset(&h, 0, sizeof(h)); + h.hdr.hdr_len = sizeof(h.hdr); + h.hdr.channel = DCHID_CHANNEL_CMD; + h.hdr.length = round_up(size, 4) + sizeof(h.sub); + h.hdr.seq = iface->tx_seq; + h.hdr.iface = iface->index; + h.sub.flags = flags; + h.sub.length = size; + + ret = dockchannel_send(iface->dchid->dc, &h, sizeof(h)); + if (ret < 0) + return ret; + checksum -= dchid_checksum(&h, sizeof(h)); + + ret = dockchannel_send(iface->dchid->dc, msg, wsize); + if (ret < 0) + return ret; + checksum -= dchid_checksum(msg, wsize); + + if (tsize) { + u8 tail[4] = {0, 0, 0, 0}; + + memcpy(tail, msg + wsize, tsize); + ret = dockchannel_send(iface->dchid->dc, tail, sizeof(tail)); + if (ret < 0) + return ret; + checksum -= dchid_checksum(tail, sizeof(tail)); + } + + ret = dockchannel_send(iface->dchid->dc, &checksum, sizeof(checksum)); + if (ret < 0) + return ret; + + return 0; +} + +static int dchid_cmd(struct dchid_iface *iface, u32 type, u32 req, + void *data, size_t size, void *resp_buf, size_t resp_size) +{ + int ret; + int report_id = *(u8*)data; + + mutex_lock(&iface->out_mutex); + + WARN_ON(iface->out_report != -1); + iface->out_report = report_id; + iface->out_flags = FIELD_PREP(FLAGS_GROUP, type) | FIELD_PREP(FLAGS_REQ, req); + iface->resp_buf = resp_buf; + iface->resp_size = resp_size; + reinit_completion(&iface->out_complete); + + ret = dchid_send(iface, iface->out_flags, data, size); + if (ret < 0) + goto done; + + if (!wait_for_completion_timeout(&iface->out_complete, msecs_to_jiffies(COMMAND_TIMEOUT_MS))) { + dev_err(iface->dchid->dev, "output report 0x%x to iface %d (%s) timed out\n", + report_id, iface->index, iface->name); + ret = -ETIMEDOUT; + goto done; + } + + ret = iface->resp_size; + if (iface->retcode) { + dev_err(iface->dchid->dev, + "output report 0x%x to iface %d (%s) failed with err 0x%x\n", + report_id, iface->index, iface->name, iface->retcode); + ret = -EIO; + } + +done: + iface->tx_seq++; + iface->out_report = -1; + iface->out_flags = 0; + iface->resp_buf = NULL; + iface->resp_size = 0; + mutex_unlock(&iface->out_mutex); + return ret; +} + +static int dchid_comm_cmd(struct dockchannel_hid *dchid, void *cmd, size_t size) +{ + return dchid_cmd(dchid->comm, HID_FEATURE_REPORT, REQ_SET_REPORT, cmd, size, NULL, 0); +} + +static int dchid_enable_interface(struct dchid_iface *iface) +{ + u8 msg[] = { CMD_ENABLE_INTERFACE, iface->index }; + + return dchid_comm_cmd(iface->dchid, msg, sizeof(msg)); +} + +static int dchid_reset_interface(struct dchid_iface *iface, int state) +{ + u8 msg[] = { CMD_RESET_INTERFACE, 1, iface->index, state }; + + return dchid_comm_cmd(iface->dchid, msg, sizeof(msg)); +} + +static int dchid_send_firmware(struct dchid_iface *iface, void *firmware, size_t size) +{ + struct { + u8 cmd; + u8 unk1; + u8 unk2; + u8 iface; + u64 addr; + u32 size; + } __packed msg = { + .cmd = CMD_SEND_FIRMWARE, + .unk1 = 2, + .unk2 = 0, + .iface = iface->index, + .size = size, + }; + dma_addr_t addr; + void *buf = dmam_alloc_coherent(iface->dchid->dev, size, &addr, GFP_KERNEL); + + if (IS_ERR_OR_NULL(buf)) + return buf ? PTR_ERR(buf) : -ENOMEM; + + msg.addr = addr; + memcpy(buf, firmware, size); + wmb(); + + return dchid_comm_cmd(iface->dchid, &msg, sizeof(msg)); +} + +static int dchid_get_firmware(struct dchid_iface *iface, void **firmware, size_t *size) +{ + int ret; + const char *fw_name; + const struct firmware *fw; + struct fw_header *hdr; + u8 *fw_data; + + ret = of_property_read_string(iface->of_node, "firmware-name", &fw_name); + if (ret) { + /* Firmware is only for some devices */ + *firmware = NULL; + *size = 0; + return 0; + } + + ret = request_firmware(&fw, fw_name, iface->dchid->dev); + if (ret) + return ret; + + hdr = (struct fw_header *)fw->data; + + if (hdr->magic != FW_MAGIC || hdr->version != FW_VER || + hdr->hdr_length < sizeof(*hdr) || hdr->hdr_length > fw->size || + (hdr->hdr_length + (size_t)hdr->data_length) > fw->size || + hdr->iface_offset >= hdr->data_length) { + dev_warn(iface->dchid->dev, "%s: invalid firmware header\n", + fw_name); + ret = -EINVAL; + goto done; + } + + fw_data = devm_kmemdup(iface->dchid->dev, fw->data + hdr->hdr_length, + hdr->data_length, GFP_KERNEL); + if (!fw_data) { + ret = -ENOMEM; + goto done; + } + + if (hdr->iface_offset) + fw_data[hdr->iface_offset] = iface->index; + + *firmware = fw_data; + *size = hdr->data_length; + +done: + release_firmware(fw); + return ret; +} + +static int dchid_request_gpio(struct dchid_iface *iface) +{ + char prop_name[MAX_GPIO_NAME + 16]; + + if (iface->gpio) + return 0; + + dev_info(iface->dchid->dev, "Requesting GPIO %s#%d: %s\n", + iface->name, iface->gpio_id, iface->gpio_name); + + snprintf(prop_name, sizeof(prop_name), "apple,%s", iface->gpio_name); + + iface->gpio = devm_gpiod_get_index(iface->dchid->dev, prop_name, 0, GPIOD_OUT_LOW); + + if (IS_ERR_OR_NULL(iface->gpio)) { + dev_err(iface->dchid->dev, "Failed to request GPIO %s-gpios\n", prop_name); + iface->gpio = NULL; + return -1; + } + + return 0; +} + +static int dchid_start_interface(struct dchid_iface *iface) +{ + void *fw; + size_t size; + int ret; + + if (iface->starting) { + dev_warn(iface->dchid->dev, "Interface %s is already starting", iface->name); + return -EINPROGRESS; + } + + dev_info(iface->dchid->dev, "Starting interface %s\n", iface->name); + + iface->starting = true; + + /* Look to see if we need firmware */ + ret = dchid_get_firmware(iface, &fw, &size); + if (ret < 0) + goto err; + + /* If we need a GPIO, make sure we have it. */ + if (iface->gpio_id) { + ret = dchid_request_gpio(iface); + if (ret < 0) + goto err; + } + + /* Only multi-touch has firmware */ + if (fw && size) { + + /* Send firmware to the device */ + dev_info(iface->dchid->dev, "Sending firmware for %s\n", iface->name); + ret = dchid_send_firmware(iface, fw, size); + if (ret < 0) { + dev_err(iface->dchid->dev, "Failed to send %s firmwareS", iface->name); + goto err; + } + + /* After loading firmware, multi-touch needs a reset */ + dev_info(iface->dchid->dev, "Resetting %s\n", iface->name); + dchid_reset_interface(iface, 0); + dchid_reset_interface(iface, 2); + } + + return 0; + +err: + iface->starting = false; + return ret; +} + +static int dchid_start(struct hid_device *hdev) +{ + struct dchid_iface *iface = hdev->driver_data; + + if (iface->keyboard_layout_id) { + int ret = device_create_file(&hdev->dev, &dev_attr_apple_layout_id); + if (ret) { + dev_warn(iface->dchid->dev, "Failed to create apple_layout_id: %d", ret); + iface->keyboard_layout_id = 0; + } + } + + return 0; +}; + +static void dchid_stop(struct hid_device *hdev) +{ + struct dchid_iface *iface = hdev->driver_data; + + if (iface->keyboard_layout_id) + device_remove_file(&hdev->dev, &dev_attr_apple_layout_id); +} + +static int dchid_open(struct hid_device *hdev) +{ + struct dchid_iface *iface = hdev->driver_data; + int ret; + + if (!completion_done(&iface->ready)) { + ret = dchid_start_interface(iface); + if (ret < 0) + return ret; + + if (!wait_for_completion_timeout(&iface->ready, msecs_to_jiffies(START_TIMEOUT_MS))) { + dev_err(iface->dchid->dev, "iface %s start timed out\n", iface->name); + return -ETIMEDOUT; + } + } + + iface->open = true; + return 0; +} + +static void dchid_close(struct hid_device *hdev) +{ + struct dchid_iface *iface = hdev->driver_data; + + iface->open = false; +} + +static int dchid_parse(struct hid_device *hdev) +{ + struct dchid_iface *iface = hdev->driver_data; + + return hid_parse_report(hdev, iface->hid_desc, iface->hid_desc_len); +} + +/* Note: buf excludes report number! For ease of fetching strings/etc. */ +static int dchid_get_report_cmd(struct dchid_iface *iface, u8 reportnum, void *buf, size_t len) +{ + int ret = dchid_cmd(iface, HID_FEATURE_REPORT, REQ_GET_REPORT, &reportnum, 1, buf, len); + + return ret <= 0 ? ret : ret - 1; +} + +/* Note: buf includes report number! */ +static int dchid_set_report(struct dchid_iface *iface, void *buf, size_t len) +{ + return dchid_cmd(iface, HID_OUTPUT_REPORT, REQ_SET_REPORT, buf, len, NULL, 0); +} + +static int dchid_raw_request(struct hid_device *hdev, + unsigned char reportnum, __u8 *buf, size_t len, + unsigned char rtype, int reqtype) +{ + struct dchid_iface *iface = hdev->driver_data; + + switch (reqtype) { + case HID_REQ_GET_REPORT: + buf[0] = reportnum; + return dchid_cmd(iface, rtype, REQ_GET_REPORT, &reportnum, 1, buf + 1, len - 1); + case HID_REQ_SET_REPORT: + return dchid_set_report(iface, buf, len); + default: + return -EIO; + } + + return 0; +} + +static struct hid_ll_driver dchid_ll = { + .start = &dchid_start, + .stop = &dchid_stop, + .open = &dchid_open, + .close = &dchid_close, + .parse = &dchid_parse, + .raw_request = &dchid_raw_request, +}; + +static void dchid_create_interface_work(struct work_struct *ws) +{ + struct dchid_iface *iface = container_of(ws, struct dchid_iface, create_work); + struct dockchannel_hid *dchid = iface->dchid; + struct hid_device *hid; + int ret; + + if (iface->hid) { + dev_warn(dchid->dev, "Interface %s already created!\n", + iface->name); + return; + } + + dev_info(dchid->dev, "New interface %s\n", iface->name); + + /* Start the interface. This is not the entire init process, as firmware is loaded later on device open. */ + ret = dchid_enable_interface(iface); + if (ret < 0) { + dev_warn(dchid->dev, "Failed to enable %s: %d\n", iface->name, ret); + return; + } + + iface->deferred = false; + + hid = hid_allocate_device(); + if (IS_ERR(hid)) + return; + + snprintf(hid->name, sizeof(hid->name), "Apple MTP %s", iface->name); + snprintf(hid->phys, sizeof(hid->phys), "%s.%d (%s)", + dev_name(dchid->dev), iface->index, iface->name); + strscpy(hid->uniq, dchid->serial, sizeof(hid->uniq)); + + hid->ll_driver = &dchid_ll; + hid->bus = BUS_HOST; + hid->vendor = dchid->device_id.vendor_id; + hid->product = dchid->device_id.product_id; + hid->version = dchid->device_id.version_number; + hid->type = HID_TYPE_OTHER; + if (!strcmp(iface->name, "multi-touch")) { + hid->type = HID_TYPE_SPI_MOUSE; + } else if (!strcmp(iface->name, "keyboard")) { + u32 country_code = 0; + + hid->type = HID_TYPE_SPI_KEYBOARD; + + /* + * We have to get the country code from the device tree, since the + * device provides no reliable way to get this info. + */ + if (!of_property_read_u32(iface->of_node, "hid-country-code", &country_code)) + hid->country = country_code; + + of_property_read_u32(iface->of_node, "apple,keyboard-layout-id", + &iface->keyboard_layout_id); + } + + hid->dev.parent = iface->dchid->dev; + hid->driver_data = iface; + + iface->hid = hid; + + ret = hid_add_device(hid); + if (ret < 0) { + iface->hid = NULL; + hid_destroy_device(hid); + dev_warn(iface->dchid->dev, "Failed to register hid device %s", iface->name); + } +} + +static int dchid_create_interface(struct dchid_iface *iface) +{ + if (iface->creating) + return -EBUSY; + + iface->creating = true; + INIT_WORK(&iface->create_work, dchid_create_interface_work); + return queue_work(iface->dchid->new_iface_wq, &iface->create_work); +} + +static void dchid_handle_descriptor(struct dchid_iface *iface, void *hid_desc, size_t desc_len) +{ + if (iface->hid) { + dev_warn(iface->dchid->dev, "Tried to initialize already started interface %s!\n", + iface->name); + return; + } + + iface->hid_desc = devm_kmemdup(iface->dchid->dev, hid_desc, desc_len, GFP_KERNEL); + if (!iface->hid_desc) + return; + + iface->hid_desc_len = desc_len; +} + +static void dchid_handle_ready(struct dockchannel_hid *dchid, void *data, size_t length) +{ + struct dchid_iface *iface; + u8 *pkt = data; + u8 index; + int i, ret; + + if (length < 2) { + dev_err(dchid->dev, "Bad length for ready message: %zu\n", length); + return; + } + + index = pkt[1]; + + if (index >= MAX_INTERFACES) { + dev_err(dchid->dev, "Got ready notification for bad iface %d\n", index); + return; + } + + iface = dchid->ifaces[index]; + if (!iface) { + dev_err(dchid->dev, "Got ready notification for unknown iface %d\n", index); + return; + } + + dev_info(dchid->dev, "Interface %s is now ready\n", iface->name); + complete_all(&iface->ready); + + /* When STM is ready, grab global device info */ + if (!strcmp(iface->name, "stm")) { + ret = dchid_get_report_cmd(iface, STM_REPORT_ID, &dchid->device_id, + sizeof(dchid->device_id)); + if (ret < sizeof(dchid->device_id)) { + dev_warn(iface->dchid->dev, "Failed to get device ID from STM!\n"); + /* Fake it and keep going. Things might still work... */ + memset(&dchid->device_id, 0, sizeof(dchid->device_id)); + dchid->device_id.vendor_id = HOST_VENDOR_ID_APPLE; + } + ret = dchid_get_report_cmd(iface, STM_REPORT_SERIAL, dchid->serial, + sizeof(dchid->serial) - 1); + if (ret < 0) { + dev_warn(iface->dchid->dev, "Failed to get serial from STM!\n"); + dchid->serial[0] = 0; + } + + dchid->id_ready = true; + for (i = 0; i < MAX_INTERFACES; i++) { + if (!dchid->ifaces[i] || !dchid->ifaces[i]->deferred) + continue; + dchid_create_interface(dchid->ifaces[i]); + } + } +} + +static void dchid_handle_init(struct dockchannel_hid *dchid, void *data, size_t length) +{ + struct dchid_init_hdr *hdr = data; + struct dchid_iface *iface; + struct dchid_init_block_hdr *blk; + + if (length < sizeof(*hdr)) + return; + + iface = dchid_get_interface(dchid, hdr->iface, hdr->name); + if (!iface) + return; + + data += sizeof(*hdr); + length -= sizeof(*hdr); + + while (length >= sizeof(*blk)) { + blk = data; + data += sizeof(*blk); + length -= sizeof(*blk); + + if (blk->length > length) + break; + + switch (blk->type) { + case INIT_HID_DESCRIPTOR: + dchid_handle_descriptor(iface, data, blk->length); + break; + + case INIT_GPIO_REQUEST: { + struct dchid_gpio_request *req = data; + + if (sizeof(*req) > length) + break; + + if (iface->gpio_id) { + dev_err(dchid->dev, + "Cannot request more than one GPIO per interface!\n"); + break; + } + + strscpy(iface->gpio_name, req->name, MAX_GPIO_NAME); + iface->gpio_id = req->id; + break; + } + + case INIT_TERMINATOR: + break; + + case INIT_PRODUCT_NAME: { + char *product = data; + + if (product[blk->length - 1] != 0) { + dev_warn(dchid->dev, "Unterminated product name for %s\n", + iface->name); + } else { + dev_info(dchid->dev, "Product name for %s: %s\n", + iface->name, product); + } + break; + } + + default: + dev_warn(dchid->dev, "Unknown init packet %d for %s\n", + blk->type, iface->name); + break; + } + + data += blk->length; + length -= blk->length; + + if (blk->type == INIT_TERMINATOR) + break; + } + + if (hdr->more_packets) + return; + + /* We need to enable STM first, since it'll give us the device IDs */ + if (iface->dchid->id_ready || !strcmp(iface->name, "stm")) { + dchid_create_interface(iface); + } else { + iface->deferred = true; + } +} + +static void dchid_handle_gpio(struct dockchannel_hid *dchid, void *data, size_t length) +{ + struct dchid_gpio_cmd *cmd = data; + struct dchid_iface *iface; + u32 retcode = 0xe000f00d; /* Give it a random Apple-style error code */ + struct dchid_gpio_ack *ack; + + if (length < sizeof(*cmd)) + return; + + if (cmd->iface >= MAX_INTERFACES || !(iface = dchid->ifaces[cmd->iface])) { + dev_err(dchid->dev, "Got GPIO command for bad inteface %d\n", cmd->iface); + goto err; + } + + if (dchid_request_gpio(iface) < 0) + goto err; + + if (!iface->gpio || cmd->gpio != iface->gpio_id) { + dev_err(dchid->dev, "Got GPIO command for bad GPIO %s#%d\n", + iface->name, cmd->gpio); + goto err; + } + + dev_info(dchid->dev, "GPIO command: %s#%d: %d\n", iface->name, cmd->gpio, cmd->cmd); + + switch (cmd->cmd) { + case 3: + /* Pulse. */ + gpiod_set_value_cansleep(iface->gpio, 1); + msleep(10); /* Random guess... */ + gpiod_set_value_cansleep(iface->gpio, 0); + retcode = 0; + break; + default: + dev_err(dchid->dev, "Unknown GPIO command %d\n", cmd->cmd ); + break; + } + +err: + /* Ack it */ + ack = kzalloc(sizeof(*ack) + length, GFP_KERNEL); + if (!ack) + return; + + ack->type = CMD_ACK_GPIO_CMD; + ack->retcode = retcode; + memcpy(ack->cmd, data, length); + + if (dchid_comm_cmd(dchid, ack, sizeof(*ack) + length) < 0) + dev_err(dchid->dev, "Failed to ACK GPIO command\n"); + + kfree(ack); +} + +static void dchid_handle_event(struct dockchannel_hid *dchid, void *data, size_t length) +{ + u8 *p = data; + switch (*p) { + case EVENT_INIT: + dchid_handle_init(dchid, data, length); + break; + case EVENT_READY: + dchid_handle_ready(dchid, data, length); + break; + case EVENT_GPIO_CMD: + dchid_handle_gpio(dchid, data, length); + break; + } +} + +static void dchid_handle_report(struct dchid_iface *iface, void *data, size_t length) +{ + struct dockchannel_hid *dchid = iface->dchid; + + if (!iface->hid) { + dev_warn(dchid->dev, "Report received but %s is not initialized!\n", iface->name); + return; + } + + if (!iface->open) + return; + + hid_input_report(iface->hid, HID_INPUT_REPORT, data, length, 1); +} + +static void dchid_packet_work(struct work_struct *ws) +{ + struct dchid_work *work = container_of(ws, struct dchid_work, work); + struct dchid_subhdr *shdr = (void *)work->data; + struct dockchannel_hid *dchid = work->iface->dchid; + int type = FIELD_GET(FLAGS_GROUP, shdr->flags); + u8 *payload = work->data + sizeof(*shdr); + + if (shdr->length + sizeof(*shdr) > work->hdr.length) { + dev_err(dchid->dev, "Bad sub header length (%d > %zu)\n", + shdr->length, work->hdr.length - sizeof(*shdr)); + return; + } + + switch (type) { + case HID_INPUT_REPORT: + if (work->hdr.iface == IFACE_COMM) + dchid_handle_event(dchid, payload, shdr->length); + else + dchid_handle_report(work->iface, payload, shdr->length); + break; + default: + dev_err(dchid->dev, "Received unknown packet type %d\n", type); + break; + } + + kfree(work); +} + +static void dchid_handle_ack(struct dchid_iface *iface, struct dchid_hdr *hdr, void *data) +{ + struct dchid_subhdr *shdr = (void *)data; + u8 *payload = data + sizeof(*shdr); + + if (shdr->length + sizeof(*shdr) > hdr->length) { + dev_err(iface->dchid->dev, "Bad sub header length (%d > %ld)\n", + shdr->length, hdr->length - sizeof(*shdr)); + return; + } + if (shdr->flags != iface->out_flags) { + dev_err(iface->dchid->dev, + "Received unexpected flags 0x%x on ACK channel (expFected 0x%x)\n", + shdr->flags, iface->out_flags); + return; + } + + if (shdr->length < 1) { + dev_err(iface->dchid->dev, "Received length 0 output report ack\n"); + return; + } + if (iface->tx_seq != hdr->seq) { + dev_err(iface->dchid->dev, "Received ACK with bad seq (expected %d, got %d)\n", + iface->tx_seq, hdr->seq); + return; + } + if (iface->out_report != payload[0]) { + dev_err(iface->dchid->dev, "Received ACK with bad report (expected %d, got %d\n", + iface->out_report, payload[0]); + return; + } + + if (iface->resp_buf && iface->resp_size) + memcpy(iface->resp_buf, payload + 1, min((size_t)shdr->length - 1, iface->resp_size)); + + iface->resp_size = shdr->length; + iface->out_report = -1; + iface->retcode = shdr->retcode; + complete(&iface->out_complete); +} + +static void dchid_handle_packet(void *cookie, size_t avail) +{ + struct dockchannel_hid *dchid = cookie; + struct dchid_hdr hdr; + struct dchid_work *work; + struct dchid_iface *iface; + u32 checksum; + + if (dockchannel_recv(dchid->dc, &hdr, sizeof(hdr)) != sizeof(hdr)) { + dev_err(dchid->dev, "Read failed (header)\n"); + return; + } + + if (hdr.hdr_len != sizeof(hdr)) { + dev_err(dchid->dev, "Bad header length %d\n", hdr.hdr_len); + goto done; + } + + if (dockchannel_recv(dchid->dc, dchid->pkt_buf, hdr.length + 4) != (hdr.length + 4)) { + dev_err(dchid->dev, "Read failed (body)\n"); + goto done; + } + + checksum = dchid_checksum(&hdr, sizeof(hdr)); + checksum += dchid_checksum(dchid->pkt_buf, hdr.length + 4); + + if (checksum != 0xffffffff) { + dev_err(dchid->dev, "Checksum mismatch (iface %d): 0x%08x != 0xffffffff\n", + hdr.iface, checksum); + goto done; + } + + + if (hdr.iface >= MAX_INTERFACES) { + dev_err(dchid->dev, "Bad iface %d\n", hdr.iface); + } + + iface = dchid->ifaces[hdr.iface]; + + if (!iface) { + dev_err(dchid->dev, "Received packet for uninitialized iface %d\n", hdr.iface); + goto done; + } + + switch (hdr.channel) { + case DCHID_CHANNEL_CMD: + dchid_handle_ack(iface, &hdr, dchid->pkt_buf); + goto done; + case DCHID_CHANNEL_REPORT: + break; + default: + dev_warn(dchid->dev, "Unknown channel 0x%x, treating as report...\n", + hdr.channel); + break; + } + + work = kzalloc(sizeof(*work) + hdr.length, GFP_KERNEL); + if (!work) + return; + + work->hdr = hdr; + work->iface = iface; + memcpy(work->data, dchid->pkt_buf, hdr.length); + INIT_WORK(&work->work, dchid_packet_work); + + queue_work(iface->wq, &work->work); + +done: + dockchannel_await(dchid->dc, dchid_handle_packet, dchid, sizeof(struct dchid_hdr)); +} + +static int dockchannel_hid_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct dockchannel_hid *dchid; + struct device_node *child, *helper; + struct platform_device *helper_pdev; + struct property *prop; + int ret; + + ret = dma_set_mask_and_coherent(&pdev->dev, DMA_BIT_MASK(64)); + if (ret) + return ret; + + dchid = devm_kzalloc(dev, sizeof(*dchid), GFP_KERNEL); + if (!dchid) { + return -ENOMEM; + } + + dchid->dev = dev; + + /* + * First make sure all the GPIOs are available, in cased we need to defer. + * This is necessary because MTP will request them by name later, and by then + * it's too late to defer the probe. + */ + + for_each_child_of_node(dev->of_node, child) { + for_each_property_of_node(child, prop) { + size_t len = strlen(prop->name); + struct gpio_desc *gpio; + + if (len < 12 || strncmp("apple,", prop->name, 6) || + strcmp("-gpios", prop->name + len - 6)) + continue; + + gpio = fwnode_gpiod_get_index(&child->fwnode, prop->name, 0, GPIOD_ASIS, + prop->name); + if (IS_ERR_OR_NULL(gpio)) { + if (PTR_ERR(gpio) == -EPROBE_DEFER) { + of_node_put(child); + return -EPROBE_DEFER; + } + } else { + gpiod_put(gpio); + } + } + } + + /* + * Make sure we also have the MTP coprocessor available, and + * defer probe if the helper hasn't probed yet. + */ + helper = of_parse_phandle(dev->of_node, "apple,helper-cpu", 0); + if (!helper) { + dev_err(dev, "Missing apple,helper-cpu property"); + return -EINVAL; + } + + helper_pdev = of_find_device_by_node(helper); + of_node_put(helper); + if (!helper_pdev) { + dev_err(dev, "Failed to find helper device"); + return -EINVAL; + } + + dchid->helper_link = device_link_add(dev, &helper_pdev->dev, + DL_FLAG_AUTOREMOVE_CONSUMER); + put_device(&helper_pdev->dev); + if (!dchid->helper_link) { + dev_err(dev, "Failed to link to helper device"); + return -EINVAL; + } + + if (dchid->helper_link->supplier->links.status != DL_DEV_DRIVER_BOUND) + return -EPROBE_DEFER; + + /* Now it is safe to begin initializing */ + dchid->dc = dockchannel_init(pdev); + if (IS_ERR_OR_NULL(dchid->dc)) { + return PTR_ERR(dchid->dc); + } + dchid->new_iface_wq = alloc_workqueue("dchid-new", WQ_MEM_RECLAIM, 0); + if (!dchid->new_iface_wq) + return -ENOMEM; + + dchid->comm = dchid_get_interface(dchid, IFACE_COMM, "comm"); + if (!dchid->comm) { + dev_err(dchid->dev, "Failed to initialize comm interface"); + return -EIO; + } + + dev_info(dchid->dev, "Initialized, awaiting packets\n"); + dockchannel_await(dchid->dc, dchid_handle_packet, dchid, sizeof(struct dchid_hdr)); + + return 0; +} + +static void dockchannel_hid_remove(struct platform_device *pdev) +{ + BUG_ON(1); +} + +static const struct of_device_id dockchannel_hid_of_match[] = { + { .compatible = "apple,dockchannel-hid" }, + {}, +}; +MODULE_DEVICE_TABLE(of, dockchannel_hid_of_match); +MODULE_FIRMWARE("apple/tpmtfw-*.bin"); + +static struct platform_driver dockchannel_hid_driver = { + .driver = { + .name = "dockchannel-hid", + .of_match_table = dockchannel_hid_of_match, + }, + .probe = dockchannel_hid_probe, + .remove = dockchannel_hid_remove, +}; +module_platform_driver(dockchannel_hid_driver); + +MODULE_DESCRIPTION("Apple DockChannel HID transport driver"); +MODULE_AUTHOR("Hector Martin "); +MODULE_LICENSE("Dual MIT/GPL"); From 7e72c13de9773e2936b5089d407fa605e5770ba5 Mon Sep 17 00:00:00 2001 From: Hector Martin Date: Sun, 3 Jul 2022 23:33:37 +0900 Subject: [PATCH 0182/3902] soc: apple: Add RTKit helper driver This driver can be used for coprocessors that do some background task or communicate out-of-band, and do not do any mailbox I/O beyond the standard RTKit initialization. Signed-off-by: Hector Martin --- drivers/soc/apple/Kconfig | 13 +++ drivers/soc/apple/Makefile | 3 + drivers/soc/apple/rtkit-helper.c | 151 +++++++++++++++++++++++++++++++ 3 files changed, 167 insertions(+) create mode 100644 drivers/soc/apple/rtkit-helper.c diff --git a/drivers/soc/apple/Kconfig b/drivers/soc/apple/Kconfig index a63be2293bdc84..d19b03403e502e 100644 --- a/drivers/soc/apple/Kconfig +++ b/drivers/soc/apple/Kconfig @@ -37,6 +37,19 @@ config APPLE_RTKIT Say 'y' here if you have an Apple SoC. +config APPLE_RTKIT_HELPER + tristate "Apple Generic RTKit helper co-processor" + depends on APPLE_RTKIT + depends on ARCH_APPLE || COMPILE_TEST + help + Apple SoCs such as the M1 come with various co-processors running + their proprietary RTKit operating system. This option enables support + for a generic co-processor that does not implement any additional + in-band communications. It can be used for testing purposes, or for + coprocessors such as MTP that communicate over a different interface. + + Say 'y' here if you have an Apple SoC. + config APPLE_SART tristate "Apple SART DMA address filter" depends on ARCH_APPLE || COMPILE_TEST diff --git a/drivers/soc/apple/Makefile b/drivers/soc/apple/Makefile index 0b6a9f92bbbbf8..5e526a9edcf2b7 100644 --- a/drivers/soc/apple/Makefile +++ b/drivers/soc/apple/Makefile @@ -9,5 +9,8 @@ apple-mailbox-y = mailbox.o obj-$(CONFIG_APPLE_RTKIT) += apple-rtkit.o apple-rtkit-y = rtkit.o rtkit-crashlog.o +obj-$(CONFIG_APPLE_RTKIT_HELPER) += apple-rtkit-helper.o +apple-rtkit-helper-y = rtkit-helper.o + obj-$(CONFIG_APPLE_SART) += apple-sart.o apple-sart-y = sart.o diff --git a/drivers/soc/apple/rtkit-helper.c b/drivers/soc/apple/rtkit-helper.c new file mode 100644 index 00000000000000..080d083ed9bd2f --- /dev/null +++ b/drivers/soc/apple/rtkit-helper.c @@ -0,0 +1,151 @@ +// SPDX-License-Identifier: GPL-2.0-only OR MIT +/* + * Apple Generic RTKit helper coprocessor + * Copyright The Asahi Linux Contributors + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#define APPLE_ASC_CPU_CONTROL 0x44 +#define APPLE_ASC_CPU_CONTROL_RUN BIT(4) + +struct apple_rtkit_helper { + struct device *dev; + struct apple_rtkit *rtk; + + void __iomem *asc_base; + + struct resource *sram; + void __iomem *sram_base; +}; + +static int apple_rtkit_helper_shmem_setup(void *cookie, struct apple_rtkit_shmem *bfr) +{ + struct apple_rtkit_helper *helper = cookie; + struct resource res = { + .start = bfr->iova, + .end = bfr->iova + bfr->size - 1, + .name = "rtkit_map", + }; + + if (!bfr->iova) { + bfr->buffer = dma_alloc_coherent(helper->dev, bfr->size, + &bfr->iova, GFP_KERNEL); + if (!bfr->buffer) + return -ENOMEM; + return 0; + } + + if (!helper->sram) { + dev_err(helper->dev, + "RTKit buffer request with no SRAM region: %pR", &res); + return -EFAULT; + } + + res.flags = helper->sram->flags; + + if (res.end < res.start || !resource_contains(helper->sram, &res)) { + dev_err(helper->dev, + "RTKit buffer request outside SRAM region: %pR", &res); + return -EFAULT; + } + + bfr->iomem = helper->sram_base + (res.start - helper->sram->start); + bfr->is_mapped = true; + + return 0; +} + +static void apple_rtkit_helper_shmem_destroy(void *cookie, struct apple_rtkit_shmem *bfr) +{ + // no-op +} + +static const struct apple_rtkit_ops apple_rtkit_helper_ops = { + .shmem_setup = apple_rtkit_helper_shmem_setup, + .shmem_destroy = apple_rtkit_helper_shmem_destroy, +}; + +static int apple_rtkit_helper_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct apple_rtkit_helper *helper; + int ret; + + /* 44 bits for addresses in standard RTKit requests */ + ret = dma_set_mask_and_coherent(&pdev->dev, DMA_BIT_MASK(44)); + if (ret) + return ret; + + helper = devm_kzalloc(dev, sizeof(*helper), GFP_KERNEL); + if (!helper) + return -ENOMEM; + + helper->dev = dev; + platform_set_drvdata(pdev, helper); + + helper->asc_base = devm_platform_ioremap_resource_byname(pdev, "asc"); + if (IS_ERR(helper->asc_base)) + return PTR_ERR(helper->asc_base); + + helper->sram = platform_get_resource_byname(pdev, IORESOURCE_MEM, "sram"); + if (helper->sram) { + helper->sram_base = devm_ioremap_resource(dev, helper->sram); + if (IS_ERR(helper->sram_base)) + return dev_err_probe(dev, PTR_ERR(helper->sram_base), + "Failed to map SRAM region"); + } + + helper->rtk = + devm_apple_rtkit_init(dev, helper, NULL, 0, &apple_rtkit_helper_ops); + if (IS_ERR(helper->rtk)) + return dev_err_probe(dev, PTR_ERR(helper->rtk), + "Failed to intialize RTKit"); + + writel_relaxed(APPLE_ASC_CPU_CONTROL_RUN, + helper->asc_base + APPLE_ASC_CPU_CONTROL); + + /* Works for both wake and boot */ + ret = apple_rtkit_wake(helper->rtk); + if (ret != 0) + return dev_err_probe(dev, ret, "Failed to wake up coprocessor"); + + return 0; +} + +static void apple_rtkit_helper_remove(struct platform_device *pdev) +{ + struct apple_rtkit_helper *helper = platform_get_drvdata(pdev); + + if (apple_rtkit_is_running(helper->rtk)) + apple_rtkit_quiesce(helper->rtk); + + writel_relaxed(0, helper->asc_base + APPLE_ASC_CPU_CONTROL); +} + +static const struct of_device_id apple_rtkit_helper_of_match[] = { + { .compatible = "apple,rtk-helper-asc4" }, + {}, +}; +MODULE_DEVICE_TABLE(of, apple_rtkit_helper_of_match); + +static struct platform_driver apple_rtkit_helper_driver = { + .driver = { + .name = "rtkit-helper", + .of_match_table = apple_rtkit_helper_of_match, + }, + .probe = apple_rtkit_helper_probe, + .remove = apple_rtkit_helper_remove, +}; +module_platform_driver(apple_rtkit_helper_driver); + +MODULE_AUTHOR("Hector Martin "); +MODULE_LICENSE("Dual MIT/GPL"); +MODULE_DESCRIPTION("Apple RTKit helper driver"); From e39123c16e51c38ae3fc0eef4f08e41305d8d950 Mon Sep 17 00:00:00 2001 From: Sven Peter Date: Tue, 7 Oct 2025 21:16:42 +1000 Subject: [PATCH 0183/3902] dt-bindings: rtc: Add Apple SMC RTC Apple Silicon Macs (M1, etc.) have an RTC that is part of the PMU IC, but most of the PMU functionality is abstracted out by the SMC. An additional RTC offset stored inside NVMEM is required to compute the current date/time. Reviewed-by: Mark Kettenis Reviewed-by: Neal Gompa Reviewed-by: Rob Herring (Arm) Signed-off-by: Sven Peter Signed-off-by: James Calligeros Reviewd-by: Mark Kettenis --- .../devicetree/bindings/mfd/apple,smc.yaml | 9 +++++ .../bindings/rtc/apple,smc-rtc.yaml | 35 +++++++++++++++++++ MAINTAINERS | 1 + 3 files changed, 45 insertions(+) create mode 100644 Documentation/devicetree/bindings/rtc/apple,smc-rtc.yaml diff --git a/Documentation/devicetree/bindings/mfd/apple,smc.yaml b/Documentation/devicetree/bindings/mfd/apple,smc.yaml index 5429538f7e2e91..0410e712c900a7 100644 --- a/Documentation/devicetree/bindings/mfd/apple,smc.yaml +++ b/Documentation/devicetree/bindings/mfd/apple,smc.yaml @@ -46,6 +46,9 @@ properties: reboot: $ref: /schemas/power/reset/apple,smc-reboot.yaml + rtc: + $ref: /schemas/rtc/apple,smc-rtc.yaml + additionalProperties: false required: @@ -80,5 +83,11 @@ examples: nvmem-cell-names = "shutdown_flag", "boot_stage", "boot_error_count", "panic_count"; }; + + rtc { + compatible = "apple,smc-rtc"; + nvmem-cells = <&rtc_offset>; + nvmem-cell-names = "rtc_offset"; + }; }; }; diff --git a/Documentation/devicetree/bindings/rtc/apple,smc-rtc.yaml b/Documentation/devicetree/bindings/rtc/apple,smc-rtc.yaml new file mode 100644 index 00000000000000..607b610665a28b --- /dev/null +++ b/Documentation/devicetree/bindings/rtc/apple,smc-rtc.yaml @@ -0,0 +1,35 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/rtc/apple,smc-rtc.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Apple SMC RTC + +description: + Apple Silicon Macs (M1, etc.) have an RTC that is part of the PMU IC, + but most of the PMU functionality is abstracted out by the SMC. + An additional RTC offset stored inside NVMEM is required to compute + the current date/time. + +maintainers: + - Sven Peter + +properties: + compatible: + const: apple,smc-rtc + + nvmem-cells: + items: + - description: 48bit RTC offset, specified in 32768 (2^15) Hz clock ticks + + nvmem-cell-names: + items: + - const: rtc_offset + +required: + - compatible + - nvmem-cells + - nvmem-cell-names + +additionalProperties: false diff --git a/MAINTAINERS b/MAINTAINERS index e8f06145fb54c9..a666cd834a8186 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -2440,6 +2440,7 @@ F: Documentation/devicetree/bindings/pinctrl/apple,pinctrl.yaml F: Documentation/devicetree/bindings/power/apple* F: Documentation/devicetree/bindings/power/reset/apple,smc-reboot.yaml F: Documentation/devicetree/bindings/pwm/apple,s5l-fpwm.yaml +F: Documentation/devicetree/bindings/rtc/apple,smc-rtc.yaml F: Documentation/devicetree/bindings/spi/apple,spi.yaml F: Documentation/devicetree/bindings/spmi/apple,spmi.yaml F: Documentation/devicetree/bindings/watchdog/apple,wdt.yaml From 3bb6371ed78b64d9b08c6f628254ea64cdde7039 Mon Sep 17 00:00:00 2001 From: James Calligeros Date: Tue, 7 Oct 2025 21:16:43 +1000 Subject: [PATCH 0184/3902] dt-bindings: hwmon: Add Apple System Management Controller hwmon schema Apple Silicon devices integrate a vast array of sensors, monitoring current, power, temperature, and voltage across almost every part of the system. The sensors themselves are all connected to the System Management Controller (SMC). The SMC firmware exposes the data reported by these sensors via its standard FourCC-based key-value API. The SMC is also responsible for monitoring and controlling any fans connected to the system, exposing them in the same way. For reasons known only to Apple, each device exposes its sensors with an almost totally unique set of keys. This is true even for devices which share an SoC. An M1 Mac mini, for example, will report its core temperatures on different keys to an M1 MacBook Pro. Worse still, the SMC does not provide a way to enumerate the available keys at runtime, nor do the keys follow any sort of reasonable or consistent naming rules that could be used to deduce their purpose. We must therefore know which keys are present on any given device, and which function they serve, ahead of time. Add a schema so that we can describe the available sensors for a given Apple Silicon device in the Devicetree. Reviewed-by: Neal Gompa Signed-off-by: James Calligeros Reviewed-by: Rob Herring (Arm) --- .../bindings/hwmon/apple,smc-hwmon.yaml | 86 +++++++++++++++++++ .../devicetree/bindings/mfd/apple,smc.yaml | 36 ++++++++ MAINTAINERS | 1 + 3 files changed, 123 insertions(+) create mode 100644 Documentation/devicetree/bindings/hwmon/apple,smc-hwmon.yaml diff --git a/Documentation/devicetree/bindings/hwmon/apple,smc-hwmon.yaml b/Documentation/devicetree/bindings/hwmon/apple,smc-hwmon.yaml new file mode 100644 index 00000000000000..2eec317bc4b3e6 --- /dev/null +++ b/Documentation/devicetree/bindings/hwmon/apple,smc-hwmon.yaml @@ -0,0 +1,86 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/hwmon/apple,smc-hwmon.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Apple SMC Hardware Monitoring + +description: + Apple's System Management Controller (SMC) exposes a vast array of + hardware monitoring sensors, including temperature probes, current and + voltage sense, power meters, and fan speeds. It also provides endpoints + to manually control the speed of each fan individually. Each Apple + Silicon device exposes a different set of endpoints via SMC keys. This + is true even when two machines share an SoC. The CPU core temperature + sensor keys on an M1 Mac mini are different to those on an M1 MacBook + Pro, for example. + +maintainers: + - James Calligeros + +$defs: + sensor: + type: object + + properties: + apple,key-id: + $ref: /schemas/types.yaml#/definitions/string + pattern: "^[A-Za-z0-9]{4}$" + description: The SMC FourCC key of the desired sensor. + Must match the node's suffix. + + label: + description: Human-readable name for the sensor + + required: + - apple,key-id + +properties: + compatible: + const: apple,smc-hwmon + +patternProperties: + "^current-[A-Za-z0-9]{4}$": + $ref: "#/$defs/sensor" + unevaluatedProperties: false + + "^fan-[A-Za-z0-9]{4}$": + $ref: "#/$defs/sensor" + unevaluatedProperties: false + + properties: + apple,fan-minimum: + $ref: /schemas/types.yaml#/definitions/string + pattern: "^[A-Za-z0-9]{4}$" + description: SMC key containing the fan's minimum speed + + apple,fan-maximum: + $ref: /schemas/types.yaml#/definitions/string + pattern: "^[A-Za-z0-9]{4}$" + description: SMC key containing the fan's maximum speed + + apple,fan-target: + $ref: /schemas/types.yaml#/definitions/string + pattern: "^[A-Za-z0-9]{4}$" + description: Writeable endpoint for setting desired fan speed + + apple,fan-mode: + $ref: /schemas/types.yaml#/definitions/string + pattern: "^[A-Za-z0-9]{4}$" + description: Writeable key to enable/disable manual fan control + + + "^power-[A-Za-z0-9]{4}$": + $ref: "#/$defs/sensor" + unevaluatedProperties: false + + "^temperature-[A-Za-z0-9]{4}$": + $ref: "#/$defs/sensor" + unevaluatedProperties: false + + "^voltage-[A-Za-z0-9]{4}$": + $ref: "#/$defs/sensor" + unevaluatedProperties: false + +additionalProperties: false diff --git a/Documentation/devicetree/bindings/mfd/apple,smc.yaml b/Documentation/devicetree/bindings/mfd/apple,smc.yaml index 0410e712c900a7..34ce048619f5f7 100644 --- a/Documentation/devicetree/bindings/mfd/apple,smc.yaml +++ b/Documentation/devicetree/bindings/mfd/apple,smc.yaml @@ -49,6 +49,9 @@ properties: rtc: $ref: /schemas/rtc/apple,smc-rtc.yaml + hwmon: + $ref: /schemas/hwmon/apple,smc-hwmon.yaml + additionalProperties: false required: @@ -89,5 +92,38 @@ examples: nvmem-cells = <&rtc_offset>; nvmem-cell-names = "rtc_offset"; }; + + hwmon { + compatible = "apple,smc-hwmon"; + + current-ID0R { + apple,key-id = "ID0R"; + label = "AC Input Current"; + }; + + fan-F0Ac { + apple,key-id = "F0Ac"; + apple,fan-minimum = "F0Mn"; + apple,fan-maximum = "F0Mx"; + apple,fan-target = "F0Tg"; + apple,fan-mode = "F0Md"; + label = "Fan 1"; + }; + + power-PSTR { + apple,key-id = "PSTR"; + label = "Total System Power"; + }; + + temperature-TW0P { + apple,key-id = "TW0P"; + label = "WiFi/BT Module Temperature"; + }; + + voltage-VD0R { + apple,key-id = "VD0R"; + label = "AC Input Voltage"; + }; + }; }; }; diff --git a/MAINTAINERS b/MAINTAINERS index a666cd834a8186..cd296c1f2731c7 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -2423,6 +2423,7 @@ F: Documentation/devicetree/bindings/cpufreq/apple,cluster-cpufreq.yaml F: Documentation/devicetree/bindings/dma/apple,admac.yaml F: Documentation/devicetree/bindings/gpio/apple,smc-gpio.yaml F: Documentation/devicetree/bindings/gpu/apple,agx.yaml +F: Documentation/devicetree/bindings/hwmon/apple,smc-hwmon.yaml F: Documentation/devicetree/bindings/i2c/apple,i2c.yaml F: Documentation/devicetree/bindings/input/touchscreen/apple,z2-multitouch.yaml F: Documentation/devicetree/bindings/interrupt-controller/apple,* From 69ce1636a5260ba9c0b7eab0d62933afeda2cd50 Mon Sep 17 00:00:00 2001 From: Hector Martin Date: Tue, 7 Oct 2025 21:16:44 +1000 Subject: [PATCH 0185/3902] rtc: Add new rtc-macsmc driver for Apple Silicon Macs Apple Silicon Macs (M1, etc.) have an RTC that is part of the PMU IC, but most of the PMU functionality is abstracted out by the SMC. On T600x machines, the RTC counter must be accessed via the SMC to get full functionality, and it seems likely that future machines will move towards making SMC handle all RTC functionality. The SMC RTC counter access is implemented on all current machines as of the time of this writing, on firmware 12.x. However, the RTC offset (needed to set the time) is still only accessible via direct PMU access. To handle this, we expose the RTC offset as an NVMEM cell from the SPMI PMU device node, and this driver consumes that cell and uses it to compute/set the current time. Reviewed-by: Neal Gompa Signed-off-by: Hector Martin Signed-off-by: Sven Peter Signed-off-by: James Calligeros --- MAINTAINERS | 1 + drivers/rtc/Kconfig | 11 +++ drivers/rtc/Makefile | 1 + drivers/rtc/rtc-macsmc.c | 141 +++++++++++++++++++++++++++++++++++++++ 4 files changed, 154 insertions(+) create mode 100644 drivers/rtc/rtc-macsmc.c diff --git a/MAINTAINERS b/MAINTAINERS index cd296c1f2731c7..ab6a6df1a31e27 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -2465,6 +2465,7 @@ F: drivers/nvmem/apple-spmi-nvmem.c F: drivers/pinctrl/pinctrl-apple-gpio.c F: drivers/power/reset/macsmc-reboot.c F: drivers/pwm/pwm-apple.c +F: drivers/rtc/rtc-macsmc.c F: drivers/soc/apple/* F: drivers/spi/spi-apple.c F: drivers/spmi/spmi-apple-controller.c diff --git a/drivers/rtc/Kconfig b/drivers/rtc/Kconfig index 2933c41c77c88e..cf3fa1475ffd05 100644 --- a/drivers/rtc/Kconfig +++ b/drivers/rtc/Kconfig @@ -2074,6 +2074,17 @@ config RTC_DRV_WILCO_EC This can also be built as a module. If so, the module will be named "rtc_wilco_ec". +config RTC_DRV_MACSMC + tristate "Apple Mac System Management Controller RTC" + depends on MFD_MACSMC + help + If you say yes here you get support for RTC functions + inside Apple SPMI PMUs accessed through the SoC's + System Management Controller + + To compile this driver as a module, choose M here: the + module will be called rtc-macsmc. + config RTC_DRV_MSC313 tristate "MStar MSC313 RTC" depends on ARCH_MSTARV7 || COMPILE_TEST diff --git a/drivers/rtc/Makefile b/drivers/rtc/Makefile index 8221bda6e6dcab..0485b09caceba4 100644 --- a/drivers/rtc/Makefile +++ b/drivers/rtc/Makefile @@ -93,6 +93,7 @@ obj-$(CONFIG_RTC_DRV_M48T35) += rtc-m48t35.o obj-$(CONFIG_RTC_DRV_M48T59) += rtc-m48t59.o obj-$(CONFIG_RTC_DRV_M48T86) += rtc-m48t86.o obj-$(CONFIG_RTC_DRV_MA35D1) += rtc-ma35d1.o +obj-$(CONFIG_RTC_DRV_MACSMC) += rtc-macsmc.o obj-$(CONFIG_RTC_DRV_MAX31335) += rtc-max31335.o obj-$(CONFIG_RTC_DRV_MAX6900) += rtc-max6900.o obj-$(CONFIG_RTC_DRV_MAX6902) += rtc-max6902.o diff --git a/drivers/rtc/rtc-macsmc.c b/drivers/rtc/rtc-macsmc.c new file mode 100644 index 00000000000000..05e360277f630f --- /dev/null +++ b/drivers/rtc/rtc-macsmc.c @@ -0,0 +1,141 @@ +// SPDX-License-Identifier: GPL-2.0-only OR MIT +/* + * Apple SMC RTC driver + * Copyright The Asahi Linux Contributors + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* 48-bit RTC */ +#define RTC_BYTES 6 +#define RTC_BITS (8 * RTC_BYTES) + +/* 32768 Hz clock */ +#define RTC_SEC_SHIFT 15 + +struct macsmc_rtc { + struct device *dev; + struct apple_smc *smc; + struct rtc_device *rtc_dev; + struct nvmem_cell *rtc_offset; +}; + +static int macsmc_rtc_get_time(struct device *dev, struct rtc_time *tm) +{ + struct macsmc_rtc *rtc = dev_get_drvdata(dev); + u64 ctr = 0, off = 0; + time64_t now; + void *p_off; + size_t len; + int ret; + + ret = apple_smc_read(rtc->smc, SMC_KEY(CLKM), &ctr, RTC_BYTES); + if (ret < 0) + return ret; + if (ret != RTC_BYTES) + return -EIO; + + p_off = nvmem_cell_read(rtc->rtc_offset, &len); + if (IS_ERR(p_off)) + return PTR_ERR(p_off); + if (len < RTC_BYTES) { + kfree(p_off); + return -EIO; + } + + memcpy(&off, p_off, RTC_BYTES); + kfree(p_off); + + /* Sign extend from 48 to 64 bits, then arithmetic shift right 15 bits to get seconds */ + now = sign_extend64(ctr + off, RTC_BITS - 1) >> RTC_SEC_SHIFT; + rtc_time64_to_tm(now, tm); + + return ret; +} + +static int macsmc_rtc_set_time(struct device *dev, struct rtc_time *tm) +{ + struct macsmc_rtc *rtc = dev_get_drvdata(dev); + u64 ctr = 0, off = 0; + int ret; + + ret = apple_smc_read(rtc->smc, SMC_KEY(CLKM), &ctr, RTC_BYTES); + if (ret < 0) + return ret; + if (ret != RTC_BYTES) + return -EIO; + + /* This sets the offset such that the set second begins now */ + off = (rtc_tm_to_time64(tm) << RTC_SEC_SHIFT) - ctr; + return nvmem_cell_write(rtc->rtc_offset, &off, RTC_BYTES); +} + +static const struct rtc_class_ops macsmc_rtc_ops = { + .read_time = macsmc_rtc_get_time, + .set_time = macsmc_rtc_set_time, +}; + +static int macsmc_rtc_probe(struct platform_device *pdev) +{ + struct apple_smc *smc = dev_get_drvdata(pdev->dev.parent); + struct macsmc_rtc *rtc; + + /* + * MFD will probe this device even without a node in the device tree, + * thus bail out early if the SMC on the current machines does not + * support RTC and has no node in the device tree. + */ + if (!pdev->dev.of_node) + return -ENODEV; + + rtc = devm_kzalloc(&pdev->dev, sizeof(*rtc), GFP_KERNEL); + if (!rtc) + return -ENOMEM; + + rtc->dev = &pdev->dev; + rtc->smc = smc; + + rtc->rtc_offset = devm_nvmem_cell_get(&pdev->dev, "rtc_offset"); + if (IS_ERR(rtc->rtc_offset)) + return dev_err_probe(&pdev->dev, PTR_ERR(rtc->rtc_offset), + "Failed to get rtc_offset NVMEM cell\n"); + + rtc->rtc_dev = devm_rtc_allocate_device(&pdev->dev); + if (IS_ERR(rtc->rtc_dev)) + return PTR_ERR(rtc->rtc_dev); + + rtc->rtc_dev->ops = &macsmc_rtc_ops; + rtc->rtc_dev->range_min = S64_MIN >> (RTC_SEC_SHIFT + (64 - RTC_BITS)); + rtc->rtc_dev->range_max = S64_MAX >> (RTC_SEC_SHIFT + (64 - RTC_BITS)); + + platform_set_drvdata(pdev, rtc); + + return devm_rtc_register_device(rtc->rtc_dev); +} + +static const struct of_device_id macsmc_rtc_of_table[] = { + { .compatible = "apple,smc-rtc", }, + {} +}; +MODULE_DEVICE_TABLE(of, macsmc_rtc_of_table); + +static struct platform_driver macsmc_rtc_driver = { + .driver = { + .name = "macsmc-rtc", + .of_match_table = macsmc_rtc_of_table, + }, + .probe = macsmc_rtc_probe, +}; +module_platform_driver(macsmc_rtc_driver); + +MODULE_LICENSE("Dual MIT/GPL"); +MODULE_DESCRIPTION("Apple SMC RTC driver"); +MODULE_AUTHOR("Hector Martin "); From 0b6ffa4de08efcf04a332331de138e877bf7d70a Mon Sep 17 00:00:00 2001 From: James Calligeros Date: Tue, 7 Oct 2025 21:16:45 +1000 Subject: [PATCH 0186/3902] mfd: macsmc: Wire up Apple SMC RTC subdevice Add the new SMC RTC function to the mfd device Reviewed-by: Neal Gompa Signed-off-by: James Calligeros --- drivers/mfd/macsmc.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/mfd/macsmc.c b/drivers/mfd/macsmc.c index e6cdae221f1d4e..500395bb48daa7 100644 --- a/drivers/mfd/macsmc.c +++ b/drivers/mfd/macsmc.c @@ -47,6 +47,7 @@ static const struct mfd_cell apple_smc_devs[] = { MFD_CELL_OF("macsmc-gpio", NULL, NULL, 0, 0, "apple,smc-gpio"), MFD_CELL_OF("macsmc-reboot", NULL, NULL, 0, 0, "apple,smc-reboot"), + MFD_CELL_OF("macsmc-rtc", NULL, NULL, 0, 0, "apple,smc-rtc"), }; static int apple_smc_cmd_locked(struct apple_smc *smc, u64 cmd, u64 arg, From 705516f983958d2909036da6389b1c4a8c10c386 Mon Sep 17 00:00:00 2001 From: James Calligeros Date: Tue, 7 Oct 2025 21:16:46 +1000 Subject: [PATCH 0187/3902] mfd: macsmc: add new __SMC_KEY macro When using the _SMC_KEY macro in switch/case statements, GCC 15.2.1 errors out with 'case label does not reduce to an integer constant'. Introduce a new __SMC_KEY macro that can be used instead. Signed-off-by: James Calligeros --- include/linux/mfd/macsmc.h | 1 + 1 file changed, 1 insertion(+) diff --git a/include/linux/mfd/macsmc.h b/include/linux/mfd/macsmc.h index 6b13f01a85924f..f6f80c33b5cf79 100644 --- a/include/linux/mfd/macsmc.h +++ b/include/linux/mfd/macsmc.h @@ -41,6 +41,7 @@ typedef u32 smc_key; */ #define SMC_KEY(s) (smc_key)(_SMC_KEY(#s)) #define _SMC_KEY(s) (((s)[0] << 24) | ((s)[1] << 16) | ((s)[2] << 8) | (s)[3]) +#define __SMC_KEY(a, b, c, d) (((u32)(a) << 24) | ((u32)(b) << 16) | ((u32)(c) << 8) | ((u32)(d))) #define APPLE_SMC_READABLE BIT(7) #define APPLE_SMC_WRITABLE BIT(6) From d33e6a701bd26fadd63f260c9b1dbdee36ce8e2b Mon Sep 17 00:00:00 2001 From: James Calligeros Date: Tue, 7 Oct 2025 21:16:47 +1000 Subject: [PATCH 0188/3902] hwmon: Add Apple Silicon SMC hwmon driver The System Management Controller on Apple Silicon devices is responsible for integrating and exposing the data reported by the vast array of hardware monitoring sensors present on these devices. It is also responsible for fan control, and allows users to manually set fan speeds if they so desire. Add a hwmon driver to expose current, power, temperature, and voltage monitoring sensors, as well as fan speed monitoring and control via the SMC on Apple Silicon devices. The SMC firmware has no consistency between devices, even when they share an SoC. The FourCC keys used to access sensors are almost random. An M1 Mac mini will have different FourCCs for its CPU core temperature sensors to an M1 MacBook Pro, for example. For this reason, the valid sensors for a given device are specified in a child of the SMC Devicetree node. The driver uses this information to determine which sensors to make available at runtime. Reviewed-by: Neal Gompa Co-developed-by: Janne Grunau Signed-off-by: James Calligeros Acked-by: Guenter Roeck Signed-off-by: Janne Grunau --- Documentation/hwmon/macsmc-hwmon.rst | 71 +++ MAINTAINERS | 2 + drivers/hwmon/Kconfig | 12 + drivers/hwmon/Makefile | 1 + drivers/hwmon/macsmc-hwmon.c | 850 +++++++++++++++++++++++++++ 5 files changed, 936 insertions(+) create mode 100644 Documentation/hwmon/macsmc-hwmon.rst create mode 100644 drivers/hwmon/macsmc-hwmon.c diff --git a/Documentation/hwmon/macsmc-hwmon.rst b/Documentation/hwmon/macsmc-hwmon.rst new file mode 100644 index 00000000000000..6903f76df62bf2 --- /dev/null +++ b/Documentation/hwmon/macsmc-hwmon.rst @@ -0,0 +1,71 @@ +.. SPDX-License-Identifier: GPL-2.0-only + +Kernel driver macsmc-hwmon +========================== + +Supported hardware + + * Apple Silicon Macs (M1 and up) + +Author: James Calligeros + +Description +----------- + +macsmc-hwmon exposes the Apple System Management controller's +temperature, voltage, current and power sensors, as well as +fan speed and control capabilities, via hwmon. + +Because each Apple Silicon Mac exposes a different set of sensors +(e.g. the MacBooks expose battery telemetry that is not present on +the desktop Macs), sensors present on any given machine are described +via Devicetree. The driver picks these up and registers them with +hwmon when probed. + +Manual fan speed is supported via the fan_control module parameter. This +is disabled by default and marked as unsafe, as it cannot be proven that +the system will fail safe if overheating due to manual fan control being +used. + +sysfs interface +--------------- + +currX_input + Ammeter value + +currX_label + Ammeter label + +fanX_input + Current fan speed + +fanX_label + Fan label + +fanX_min + Minimum possible fan speed + +fanX_max + Maximum possible fan speed + +fanX_target + Current fan setpoint + +inX_input + Voltmeter value + +inX_label + Voltmeter label + +powerX_input + Power meter value + +powerX_label + Power meter label + +tempX_input + Temperature sensor value + +tempX_label + Temperature sensor label + diff --git a/MAINTAINERS b/MAINTAINERS index ab6a6df1a31e27..3e42c9a572c23f 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -2445,12 +2445,14 @@ F: Documentation/devicetree/bindings/rtc/apple,smc-rtc.yaml F: Documentation/devicetree/bindings/spi/apple,spi.yaml F: Documentation/devicetree/bindings/spmi/apple,spmi.yaml F: Documentation/devicetree/bindings/watchdog/apple,wdt.yaml +F: Documentation/hwmon/macsmc-hwmon.rst F: arch/arm64/boot/dts/apple/ F: drivers/bluetooth/hci_bcm4377.c F: drivers/clk/clk-apple-nco.c F: drivers/cpufreq/apple-soc-cpufreq.c F: drivers/dma/apple-admac.c F: drivers/gpio/gpio-macsmc.c +F: drivers/hwmon/macsmc-hwmon.c F: drivers/pmdomain/apple/ F: drivers/i2c/busses/i2c-pasemi-core.c F: drivers/i2c/busses/i2c-pasemi-platform.c diff --git a/drivers/hwmon/Kconfig b/drivers/hwmon/Kconfig index 2760feb9f83b5d..d6e9e39d276280 100644 --- a/drivers/hwmon/Kconfig +++ b/drivers/hwmon/Kconfig @@ -1174,6 +1174,18 @@ config SENSORS_LTQ_CPUTEMP If you say yes here you get support for the temperature sensor inside your CPU. +config SENSORS_MACSMC_HWMON + tristate "Apple SMC (Apple Silicon)" + depends on MFD_MACSMC && OF + help + This driver enables hwmon support for current, power, temperature, + and voltage sensors, as well as fan speed reporting and control + on Apple Silicon devices. Say Y here if you have an Apple Silicon + device. + + This driver can also be built as a module. If so, the module will + be called macsmc-hwmon. + config SENSORS_MAX1111 tristate "Maxim MAX1111 Serial 8-bit ADC chip and compatibles" depends on SPI_MASTER diff --git a/drivers/hwmon/Makefile b/drivers/hwmon/Makefile index 73b2abdcc6dd9c..f9c049ce912469 100644 --- a/drivers/hwmon/Makefile +++ b/drivers/hwmon/Makefile @@ -148,6 +148,7 @@ obj-$(CONFIG_SENSORS_LTC4260) += ltc4260.o obj-$(CONFIG_SENSORS_LTC4261) += ltc4261.o obj-$(CONFIG_SENSORS_LTC4282) += ltc4282.o obj-$(CONFIG_SENSORS_LTQ_CPUTEMP) += ltq-cputemp.o +obj-$(CONFIG_SENSORS_MACSMC_HWMON) += macsmc-hwmon.o obj-$(CONFIG_SENSORS_MAX1111) += max1111.o obj-$(CONFIG_SENSORS_MAX127) += max127.o obj-$(CONFIG_SENSORS_MAX16065) += max16065.o diff --git a/drivers/hwmon/macsmc-hwmon.c b/drivers/hwmon/macsmc-hwmon.c new file mode 100644 index 00000000000000..342fe3a5ff6245 --- /dev/null +++ b/drivers/hwmon/macsmc-hwmon.c @@ -0,0 +1,850 @@ +// SPDX-License-Identifier: GPL-2.0-only OR MIT +/* + * Apple SMC hwmon driver for Apple Silicon platforms + * + * The System Management Controller on Apple Silicon devices is responsible for + * measuring data from sensors across the SoC and machine. These include power, + * temperature, voltage and current sensors. Some "sensors" actually expose + * derived values. An example of this is the key PHPC, which is an estimate + * of the heat energy being dissipated by the SoC. + * + * While each SoC only has one SMC variant, each platform exposes a different + * set of sensors. For example, M1 MacBooks expose battery telemetry sensors + * which are not present on the M1 Mac mini. For this reason, the available + * sensors for a given platform are described in the device tree in a child + * node of the SMC device. We must walk this list of available sensors and + * populate the required hwmon data structures at runtime. + * + * Originally based on a concept by Jean-Francois Bortolotti + * + * Copyright The Asahi Linux Contributors + */ + +#include +#include +#include +#include +#include + +#define MAX_LABEL_LENGTH 32 + +/* Temperature, voltage, current, power, fan(s) */ +#define NUM_SENSOR_TYPES 5 + +#define FLT_EXP_BIAS 127 +#define FLT_EXP_MASK GENMASK(30, 23) +#define FLT_MANT_BIAS 23 +#define FLT_MANT_MASK GENMASK(22, 0) +#define FLT_SIGN_MASK BIT(31) + +static bool fan_control; +module_param_unsafe(fan_control, bool, 0644); +MODULE_PARM_DESC(fan_control, + "Override the SMC to set your own fan speeds on supported machines"); + +struct macsmc_hwmon_sensor { + struct apple_smc_key_info info; + smc_key macsmc_key; + char label[MAX_LABEL_LENGTH]; + u32 attrs; +}; + +struct macsmc_hwmon_fan { + struct macsmc_hwmon_sensor now; + struct macsmc_hwmon_sensor min; + struct macsmc_hwmon_sensor max; + struct macsmc_hwmon_sensor set; + struct macsmc_hwmon_sensor mode; + char label[MAX_LABEL_LENGTH]; + u32 attrs; + bool manual; +}; + +struct macsmc_hwmon_sensors { + struct hwmon_channel_info channel_info; + struct macsmc_hwmon_sensor *sensors; + u32 count; +}; + +struct macsmc_hwmon_fans { + struct hwmon_channel_info channel_info; + struct macsmc_hwmon_fan *fans; + u32 count; +}; + +struct macsmc_hwmon { + struct device *dev; + struct apple_smc *smc; + struct device *hwmon_dev; + struct hwmon_chip_info chip_info; + /* Chip + sensor types + NULL */ + const struct hwmon_channel_info *channel_infos[1 + NUM_SENSOR_TYPES + 1]; + struct macsmc_hwmon_sensors temp; + struct macsmc_hwmon_sensors volt; + struct macsmc_hwmon_sensors curr; + struct macsmc_hwmon_sensors power; + struct macsmc_hwmon_fans fan; +}; + +static int macsmc_hwmon_read_label(struct device *dev, + enum hwmon_sensor_types type, u32 attr, + int channel, const char **str) +{ + struct macsmc_hwmon *hwmon = dev_get_drvdata(dev); + + switch (type) { + case hwmon_temp: + *str = hwmon->temp.sensors[channel].label; + break; + case hwmon_in: + *str = hwmon->volt.sensors[channel].label; + break; + case hwmon_curr: + *str = hwmon->curr.sensors[channel].label; + break; + case hwmon_power: + *str = hwmon->power.sensors[channel].label; + break; + case hwmon_fan: + *str = hwmon->fan.fans[channel].label; + break; + default: + return -EOPNOTSUPP; + } + + return 0; +} + +/* + * A number of sensors report data in a 48.16 fixed-point decimal format that is + * not used by any other function of the SMC. + */ +static int macsmc_hwmon_read_ioft_scaled(struct apple_smc *smc, smc_key key, + u64 *p, int scale) +{ + u64 val; + int ret; + + ret = apple_smc_read_u64(smc, key, &val); + if (ret < 0) + return ret; + + *p = mult_frac(val, scale, 65536); + + return 0; +} + +/* + * Many sensors report their data as IEEE-754 floats. No other SMC function uses + * them. + */ +static int macsmc_hwmon_read_f32_scaled(struct apple_smc *smc, smc_key key, + int *p, int scale) +{ + u32 fval; + u64 val; + int ret, exp; + + ret = apple_smc_read_u32(smc, key, &fval); + if (ret < 0) + return ret; + + val = ((u64)((fval & FLT_MANT_MASK) | BIT(23))); + exp = ((fval >> 23) & 0xff) - FLT_EXP_BIAS - FLT_MANT_BIAS; + + /* We never have negatively scaled SMC floats */ + val *= scale; + + if (exp > 63) + val = U64_MAX; + else if (exp < -63) + val = 0; + else if (exp < 0) + val >>= -exp; + else if (exp != 0 && (val & ~((1UL << (64 - exp)) - 1))) /* overflow */ + val = U64_MAX; + else + val <<= exp; + + if (fval & FLT_SIGN_MASK) { + if (val > (-(s64)INT_MIN)) + *p = INT_MIN; + else + *p = -val; + } else { + if (val > INT_MAX) + *p = INT_MAX; + else + *p = val; + } + + return 0; +} + +/* + * The SMC has keys of multiple types, denoted by a FourCC of the same format + * as the key ID. We don't know what data type a key encodes until we poke at it. + */ +static int macsmc_hwmon_read_key(struct apple_smc *smc, + struct macsmc_hwmon_sensor *sensor, int scale, + long *val) +{ + int ret; + + switch (sensor->info.type_code) { + /* 32-bit IEEE 754 float */ + case __SMC_KEY('f', 'l', 't', ' '): { + u32 flt_ = 0; + + ret = macsmc_hwmon_read_f32_scaled(smc, sensor->macsmc_key, + &flt_, scale); + if (ret) + return ret; + + *val = flt_; + break; + } + /* 48.16 fixed point decimal */ + case __SMC_KEY('i', 'o', 'f', 't'): { + u64 ioft = 0; + + ret = macsmc_hwmon_read_ioft_scaled(smc, sensor->macsmc_key, + &ioft, scale); + if (ret) + return ret; + + *val = (long)ioft; + break; + } + default: + return -EOPNOTSUPP; + } + + return 0; +} + +static int macsmc_hwmon_write_f32(struct apple_smc *smc, smc_key key, int value) +{ + u64 val; + u32 fval = 0; + int exp = 0, neg; + + val = abs(value); + neg = val != value; + + if (val) { + int msb = __fls(val) - exp; + + if (msb > 23) { + val >>= msb - FLT_MANT_BIAS; + exp -= msb - FLT_MANT_BIAS; + } else if (msb < 23) { + val <<= FLT_MANT_BIAS - msb; + exp += msb; + } + + fval = FIELD_PREP(FLT_SIGN_MASK, neg) | + FIELD_PREP(FLT_EXP_MASK, exp + FLT_EXP_BIAS) | + FIELD_PREP(FLT_MANT_MASK, val); + } + + return apple_smc_write_u32(smc, key, fval); +} + +static int macsmc_hwmon_write_key(struct apple_smc *smc, + struct macsmc_hwmon_sensor *sensor, long val) +{ + switch (sensor->info.type_code) { + /* 32-bit IEEE 754 float */ + case __SMC_KEY('f', 'l', 't', ' '): + return macsmc_hwmon_write_f32(smc, sensor->macsmc_key, val); + /* unsigned 8-bit integer */ + case __SMC_KEY('u', 'i', '8', ' '): + return apple_smc_write_u8(smc, sensor->macsmc_key, val); + default: + return -EOPNOTSUPP; + } +} + +static int macsmc_hwmon_read_fan(struct macsmc_hwmon *hwmon, u32 attr, int chan, + long *val) +{ + switch (attr) { + case hwmon_fan_input: + return macsmc_hwmon_read_key(hwmon->smc, + &hwmon->fan.fans[chan].now, 1, val); + case hwmon_fan_min: + return macsmc_hwmon_read_key(hwmon->smc, + &hwmon->fan.fans[chan].min, 1, val); + case hwmon_fan_max: + return macsmc_hwmon_read_key(hwmon->smc, + &hwmon->fan.fans[chan].max, 1, val); + case hwmon_fan_target: + return macsmc_hwmon_read_key(hwmon->smc, + &hwmon->fan.fans[chan].set, 1, val); + default: + return -EOPNOTSUPP; + } +} + +static int macsmc_hwmon_write_fan(struct device *dev, u32 attr, int channel, + long val) +{ + struct macsmc_hwmon *hwmon = dev_get_drvdata(dev); + long min, max; + int ret; + + if (!fan_control || hwmon->fan.fans[channel].mode.macsmc_key == 0) + return -EOPNOTSUPP; + + /* + * The SMC does no sanity checks on requested fan speeds, so we need to. + */ + ret = macsmc_hwmon_read_key(hwmon->smc, &hwmon->fan.fans[channel].min, + 1, &min); + if (ret) + return ret; + + ret = macsmc_hwmon_read_key(hwmon->smc, &hwmon->fan.fans[channel].max, + 1, &max); + if (ret) + return ret; + + if (val >= min && val <= max) { + if (!hwmon->fan.fans[channel].manual) { + /* Write 1 to mode key for manual control */ + ret = macsmc_hwmon_write_key(hwmon->smc, + &hwmon->fan.fans[channel].mode, 1); + if (ret < 0) + return ret; + + hwmon->fan.fans[channel].manual = true; + } + return macsmc_hwmon_write_key(hwmon->smc, + &hwmon->fan.fans[channel].set, val); + } else if (!val) { + if (hwmon->fan.fans[channel].manual) { + ret = macsmc_hwmon_write_key(hwmon->smc, + &hwmon->fan.fans[channel].mode, 0); + if (ret < 0) + return ret; + + hwmon->fan.fans[channel].manual = false; + } + } else { + return -EINVAL; + } + + return 0; +} + +static int macsmc_hwmon_read(struct device *dev, enum hwmon_sensor_types type, + u32 attr, int channel, long *val) +{ + struct macsmc_hwmon *hwmon = dev_get_drvdata(dev); + int ret = 0; + + switch (type) { + case hwmon_temp: + ret = macsmc_hwmon_read_key(hwmon->smc, + &hwmon->temp.sensors[channel], 1000, val); + break; + case hwmon_in: + ret = macsmc_hwmon_read_key(hwmon->smc, + &hwmon->volt.sensors[channel], 1000, val); + break; + case hwmon_curr: + ret = macsmc_hwmon_read_key(hwmon->smc, + &hwmon->curr.sensors[channel], 1000, val); + break; + case hwmon_power: + /* SMC returns power in Watts with acceptable precision to scale to uW */ + ret = macsmc_hwmon_read_key(hwmon->smc, + &hwmon->power.sensors[channel], + 1000000, val); + break; + case hwmon_fan: + ret = macsmc_hwmon_read_fan(hwmon, attr, channel, val); + break; + default: + return -EOPNOTSUPP; + } + + return ret; +} + +static int macsmc_hwmon_write(struct device *dev, enum hwmon_sensor_types type, + u32 attr, int channel, long val) +{ + switch (type) { + case hwmon_fan: + return macsmc_hwmon_write_fan(dev, attr, channel, val); + default: + return -EOPNOTSUPP; + } +} + +static umode_t macsmc_hwmon_fan_is_visible(const struct macsmc_hwmon_fan *fan, + u32 attr) +{ + if (fan->attrs & BIT(attr)) { + if (attr == hwmon_fan_target && fan_control && fan->mode.macsmc_key) + return 0644; + + return 0444; + } + + return 0; +} + +static umode_t macsmc_hwmon_is_visible(const void *data, + enum hwmon_sensor_types type, u32 attr, + int channel) +{ + const struct macsmc_hwmon *hwmon = data; + struct macsmc_hwmon_sensor *sensor; + + switch (type) { + case hwmon_in: + sensor = &hwmon->volt.sensors[channel]; + break; + case hwmon_curr: + sensor = &hwmon->curr.sensors[channel]; + break; + case hwmon_power: + sensor = &hwmon->power.sensors[channel]; + break; + case hwmon_temp: + sensor = &hwmon->temp.sensors[channel]; + break; + case hwmon_fan: + return macsmc_hwmon_fan_is_visible(&hwmon->fan.fans[channel], attr); + default: + return 0; + } + + /* Sensors only register ro attributes */ + if (sensor->attrs & BIT(attr)) + return 0444; + + return 0; +} + +static const struct hwmon_ops macsmc_hwmon_ops = { + .is_visible = macsmc_hwmon_is_visible, + .read = macsmc_hwmon_read, + .read_string = macsmc_hwmon_read_label, + .write = macsmc_hwmon_write, +}; + +/* + * Get the key metadata, including key data type, from the SMC. + */ +static int macsmc_hwmon_parse_key(struct device *dev, struct apple_smc *smc, + struct macsmc_hwmon_sensor *sensor, + const char *key) +{ + int ret; + + ret = apple_smc_get_key_info(smc, _SMC_KEY(key), &sensor->info); + if (ret) { + dev_dbg(dev, "Failed to retrieve key info for %s\n", key); + return ret; + } + + sensor->macsmc_key = _SMC_KEY(key); + + return 0; +} + +/* + * A sensor is a single key-value pair as made available by the SMC. + * The devicetree gives us the SMC key ID and a friendly name where the + * purpose of the sensor is known. + */ +static int macsmc_hwmon_create_sensor(struct device *dev, struct apple_smc *smc, + struct device_node *sensor_node, + struct macsmc_hwmon_sensor *sensor) +{ + const char *key, *label; + int ret; + + ret = of_property_read_string(sensor_node, "apple,key-id", &key); + if (ret) { + dev_dbg(dev, "Could not find apple,key-id in sensor node\n"); + return ret; + } + + ret = macsmc_hwmon_parse_key(dev, smc, sensor, key); + if (ret) + return ret; + + ret = of_property_read_string(sensor_node, "label", &label); + if (ret) + dev_dbg(dev, "No label found for sensor %s\n", key); + else + strscpy_pad(sensor->label, label, sizeof(sensor->label)); + + return 0; +} + +/* + * Fan data is exposed by the SMC as multiple sensors. + * + * The devicetree schema reuses apple,key-id for the actual fan speed sensor. + * Min, max and target keys do not need labels, so we can reuse label + * for naming the entire fan. + */ +static int macsmc_hwmon_create_fan(struct device *dev, struct apple_smc *smc, + struct device_node *fan_node, + struct macsmc_hwmon_fan *fan) +{ + const char *label, *now, *min, *max, *set, *mode; + int ret; + + ret = of_property_read_string(fan_node, "apple,key-id", &now); + if (ret) { + dev_err(dev, "apple,key-id not found in fan node!\n"); + return ret; + } + + ret = macsmc_hwmon_parse_key(dev, smc, &fan->now, now); + if (ret) + return ret; + + fan->attrs = HWMON_F_INPUT; + + ret = of_property_read_string(fan_node, "label", &label); + if (ret) { + dev_dbg(dev, "No label found for fan %s\n", now); + } else { + strscpy_pad(fan->label, label, sizeof(fan->label)); + fan->attrs |= HWMON_F_LABEL; + } + + /* The following keys are not required to simply monitor fan speed */ + if (!of_property_read_string(fan_node, "apple,fan-minimum", &min)) { + ret = macsmc_hwmon_parse_key(dev, smc, &fan->min, min); + if (ret) + return ret; + + fan->attrs |= HWMON_F_MIN; + } + + if (!of_property_read_string(fan_node, "apple,fan-maximum", &max)) { + ret = macsmc_hwmon_parse_key(dev, smc, &fan->max, max); + if (ret) + return ret; + + fan->attrs |= HWMON_F_MAX; + } + + if (!of_property_read_string(fan_node, "apple,fan-target", &set)) { + ret = macsmc_hwmon_parse_key(dev, smc, &fan->set, set); + if (ret) + return ret; + + fan->attrs |= HWMON_F_TARGET; + } + + if (!of_property_read_string(fan_node, "apple,fan-mode", &mode)) { + ret = macsmc_hwmon_parse_key(dev, smc, &fan->mode, mode); + if (ret) + return ret; + } + + /* Initialise fan control mode to automatic */ + fan->manual = false; + + return 0; +} + +static int macsmc_hwmon_populate_sensors(struct macsmc_hwmon *hwmon, + struct device_node *hwmon_node) +{ + struct device_node *key_node __maybe_unused; + struct macsmc_hwmon_sensor *sensor; + u32 n_current = 0, n_fan = 0, n_power = 0, n_temperature = 0, n_voltage = 0; + + for_each_child_of_node_with_prefix(hwmon_node, key_node, "current-") { + n_current++; + } + + if (n_current) { + hwmon->curr.sensors = devm_kcalloc(hwmon->dev, n_current, + sizeof(struct macsmc_hwmon_sensor), GFP_KERNEL); + if (!hwmon->curr.sensors) + return -ENOMEM; + + for_each_child_of_node_with_prefix(hwmon_node, key_node, "current-") { + sensor = &hwmon->curr.sensors[hwmon->curr.count]; + if (!macsmc_hwmon_create_sensor(hwmon->dev, hwmon->smc, key_node, sensor)) { + sensor->attrs = HWMON_C_INPUT; + + if (*sensor->label) + sensor->attrs |= HWMON_C_LABEL; + + hwmon->curr.count++; + } + } + } + + for_each_child_of_node_with_prefix(hwmon_node, key_node, "fan-") { + n_fan++; + } + + if (n_fan) { + hwmon->fan.fans = devm_kcalloc(hwmon->dev, n_fan, + sizeof(struct macsmc_hwmon_fan), GFP_KERNEL); + if (!hwmon->fan.fans) + return -ENOMEM; + + for_each_child_of_node_with_prefix(hwmon_node, key_node, "fan-") { + if (!macsmc_hwmon_create_fan(hwmon->dev, hwmon->smc, key_node, + &hwmon->fan.fans[hwmon->fan.count])) + hwmon->fan.count++; + } + } + + for_each_child_of_node_with_prefix(hwmon_node, key_node, "power-") { + n_power++; + } + + if (n_power) { + hwmon->power.sensors = devm_kcalloc(hwmon->dev, n_power, + sizeof(struct macsmc_hwmon_sensor), GFP_KERNEL); + if (!hwmon->power.sensors) + return -ENOMEM; + + for_each_child_of_node_with_prefix(hwmon_node, key_node, "power-") { + sensor = &hwmon->power.sensors[hwmon->power.count]; + if (!macsmc_hwmon_create_sensor(hwmon->dev, hwmon->smc, key_node, sensor)) { + sensor->attrs = HWMON_P_INPUT; + + if (*sensor->label) + sensor->attrs |= HWMON_P_LABEL; + + hwmon->power.count++; + } + } + } + + for_each_child_of_node_with_prefix(hwmon_node, key_node, "temperature-") { + n_temperature++; + } + + if (n_temperature) { + hwmon->temp.sensors = devm_kcalloc(hwmon->dev, n_temperature, + sizeof(struct macsmc_hwmon_sensor), GFP_KERNEL); + if (!hwmon->temp.sensors) + return -ENOMEM; + + for_each_child_of_node_with_prefix(hwmon_node, key_node, "temperature-") { + sensor = &hwmon->temp.sensors[hwmon->temp.count]; + if (!macsmc_hwmon_create_sensor(hwmon->dev, hwmon->smc, key_node, sensor)) { + sensor->attrs = HWMON_T_INPUT; + + if (*sensor->label) + sensor->attrs |= HWMON_T_LABEL; + + hwmon->temp.count++; + } + } + } + + for_each_child_of_node_with_prefix(hwmon_node, key_node, "voltage-") { + n_voltage++; + } + + if (n_voltage) { + hwmon->volt.sensors = devm_kcalloc(hwmon->dev, n_voltage, + sizeof(struct macsmc_hwmon_sensor), GFP_KERNEL); + if (!hwmon->volt.sensors) + return -ENOMEM; + + for_each_child_of_node_with_prefix(hwmon_node, key_node, "volt-") { + sensor = &hwmon->temp.sensors[hwmon->temp.count]; + if (!macsmc_hwmon_create_sensor(hwmon->dev, hwmon->smc, key_node, sensor)) { + sensor->attrs = HWMON_I_INPUT; + + if (*sensor->label) + sensor->attrs |= HWMON_I_LABEL; + + hwmon->volt.count++; + } + } + } + + return 0; +} + +/* Create NULL-terminated config arrays */ +static void macsmc_hwmon_populate_configs(u32 *configs, const struct macsmc_hwmon_sensors *sensors) +{ + int idx; + + for (idx = 0; idx < sensors->count; idx++) + configs[idx] = sensors->sensors[idx].attrs; +} + +static void macsmc_hwmon_populate_fan_configs(u32 *configs, const struct macsmc_hwmon_fans *fans) +{ + int idx; + + for (idx = 0; idx < fans->count; idx++) + configs[idx] = fans->fans[idx].attrs; +} + +static const struct hwmon_channel_info *const macsmc_chip_channel_info = + HWMON_CHANNEL_INFO(chip, HWMON_C_REGISTER_TZ); + +static int macsmc_hwmon_create_infos(struct macsmc_hwmon *hwmon) +{ + struct hwmon_channel_info *channel_info; + int i = 0; + + /* chip */ + hwmon->channel_infos[i++] = macsmc_chip_channel_info; + + if (hwmon->curr.count) { + channel_info = &hwmon->curr.channel_info; + channel_info->type = hwmon_curr; + channel_info->config = devm_kcalloc(hwmon->dev, hwmon->curr.count + 1, + sizeof(u32), GFP_KERNEL); + if (!channel_info->config) + return -ENOMEM; + + macsmc_hwmon_populate_configs((u32 *)channel_info->config, &hwmon->curr); + hwmon->channel_infos[i++] = channel_info; + } + + if (hwmon->fan.count) { + channel_info = &hwmon->fan.channel_info; + channel_info->type = hwmon_fan; + channel_info->config = devm_kcalloc(hwmon->dev, hwmon->fan.count + 1, + sizeof(u32), GFP_KERNEL); + if (!channel_info->config) + return -ENOMEM; + + macsmc_hwmon_populate_fan_configs((u32 *)channel_info->config, &hwmon->fan); + hwmon->channel_infos[i++] = channel_info; + } + + if (hwmon->power.count) { + channel_info = &hwmon->power.channel_info; + channel_info->type = hwmon_power; + channel_info->config = devm_kcalloc(hwmon->dev, hwmon->power.count + 1, + sizeof(u32), GFP_KERNEL); + if (!channel_info->config) + return -ENOMEM; + + macsmc_hwmon_populate_configs((u32 *)channel_info->config, &hwmon->power); + hwmon->channel_infos[i++] = channel_info; + } + + if (hwmon->temp.count) { + channel_info = &hwmon->temp.channel_info; + channel_info->type = hwmon_temp; + channel_info->config = devm_kcalloc(hwmon->dev, hwmon->temp.count + 1, + sizeof(u32), GFP_KERNEL); + if (!channel_info->config) + return -ENOMEM; + + macsmc_hwmon_populate_configs((u32 *)channel_info->config, &hwmon->temp); + hwmon->channel_infos[i++] = channel_info; + } + + if (hwmon->volt.count) { + channel_info = &hwmon->volt.channel_info; + channel_info->type = hwmon_in; + channel_info->config = devm_kcalloc(hwmon->dev, hwmon->volt.count + 1, + sizeof(u32), GFP_KERNEL); + if (!channel_info->config) + return -ENOMEM; + + macsmc_hwmon_populate_configs((u32 *)channel_info->config, &hwmon->volt); + hwmon->channel_infos[i++] = channel_info; + } + + return 0; +} + +static int macsmc_hwmon_probe(struct platform_device *pdev) +{ + struct apple_smc *smc = dev_get_drvdata(pdev->dev.parent); + struct macsmc_hwmon *hwmon; + int ret; + + /* + * The MFD driver will try to probe us unconditionally. Some devices + * with the SMC do not have hwmon capabilities. Only probe if we have + * a hwmon node. + */ + if (!pdev->dev.of_node) + return -ENODEV; + + hwmon = devm_kzalloc(&pdev->dev, sizeof(*hwmon), + GFP_KERNEL); + if (!hwmon) + return -ENOMEM; + + hwmon->dev = &pdev->dev; + hwmon->smc = smc; + + ret = macsmc_hwmon_populate_sensors(hwmon, hwmon->dev->of_node); + if (ret) { + dev_err(hwmon->dev, "Could not parse sensors\n"); + return ret; + } + + if (!hwmon->curr.count && !hwmon->fan.count && + !hwmon->power.count && !hwmon->temp.count && + !hwmon->volt.count) { + dev_err(hwmon->dev, + "No valid sensors found of any supported type\n"); + return -ENODEV; + } + + ret = macsmc_hwmon_create_infos(hwmon); + if (ret) + return ret; + + hwmon->chip_info.ops = &macsmc_hwmon_ops; + hwmon->chip_info.info = + (const struct hwmon_channel_info *const *)&hwmon->channel_infos; + + hwmon->hwmon_dev = devm_hwmon_device_register_with_info(&pdev->dev, + "macsmc_hwmon", hwmon, + &hwmon->chip_info, NULL); + if (IS_ERR(hwmon->hwmon_dev)) + return dev_err_probe(hwmon->dev, PTR_ERR(hwmon->hwmon_dev), + "Probing SMC hwmon device failed\n"); + + dev_info(hwmon->dev, "Registered SMC hwmon device. Sensors:"); + dev_info(hwmon->dev, + "Current: %d, Fans: %d, Power: %d, Temperature: %d, Voltage: %d", + hwmon->curr.count, hwmon->fan.count, + hwmon->power.count, hwmon->temp.count, + hwmon->volt.count); + + return 0; +} + +static const struct of_device_id macsmc_hwmon_of_table[] = { + { .compatible = "apple,smc-hwmon" }, + {} +}; +MODULE_DEVICE_TABLE(of, macsmc_hwmon_of_table); + +static struct platform_driver macsmc_hwmon_driver = { + .probe = macsmc_hwmon_probe, + .driver = { + .name = "macsmc-hwmon", + .of_match_table = macsmc_hwmon_of_table, + }, +}; +module_platform_driver(macsmc_hwmon_driver); + +MODULE_DESCRIPTION("Apple Silicon SMC hwmon driver"); +MODULE_AUTHOR("James Calligeros "); +MODULE_LICENSE("Dual MIT/GPL"); From 2533d8023e26dd0f2fa3f6bcf6bafcb7fead5fbf Mon Sep 17 00:00:00 2001 From: James Calligeros Date: Tue, 7 Oct 2025 21:16:48 +1000 Subject: [PATCH 0189/3902] mfd: macsmc: Wire up Apple SMC hwmon subdevice Add the SMC hwmon functionality to the mfd device Reviewed-by: Neal Gompa Signed-off-by: James Calligeros --- drivers/mfd/macsmc.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/mfd/macsmc.c b/drivers/mfd/macsmc.c index 500395bb48daa7..51dd667d3b5fe0 100644 --- a/drivers/mfd/macsmc.c +++ b/drivers/mfd/macsmc.c @@ -46,6 +46,7 @@ static const struct mfd_cell apple_smc_devs[] = { MFD_CELL_OF("macsmc-gpio", NULL, NULL, 0, 0, "apple,smc-gpio"), + MFD_CELL_OF("macsmc-hwmon", NULL, NULL, 0, 0, "apple,smc-hwmon"), MFD_CELL_OF("macsmc-reboot", NULL, NULL, 0, 0, "apple,smc-reboot"), MFD_CELL_OF("macsmc-rtc", NULL, NULL, 0, 0, "apple,smc-rtc"), }; From cf58d239bb987b8da358cb8d5b628a12c3f7431e Mon Sep 17 00:00:00 2001 From: Hector Martin Date: Tue, 7 Oct 2025 21:16:49 +1000 Subject: [PATCH 0190/3902] input: macsmc-input: New driver to handle the Apple Mac SMC buttons/lid This driver implements power button and lid switch support for Apple Mac devices using SMC controllers driven by the macsmc driver. In addition to basic input support, this also responds to the final shutdown warning (when the power button is held down long enough) by doing an emergency kernel poweroff. This allows the NVMe controller to be cleanly shut down, which prevents data loss for in-cache data. Reviewed-by: Neal Gompa Signed-off-by: Hector Martin Co-developed-by: Sven Peter Signed-off-by: Sven Peter Signed-off-by: James Calligeros --- MAINTAINERS | 1 + drivers/input/misc/Kconfig | 11 ++ drivers/input/misc/Makefile | 1 + drivers/input/misc/macsmc-input.c | 208 ++++++++++++++++++++++++++++++ 4 files changed, 221 insertions(+) create mode 100644 drivers/input/misc/macsmc-input.c diff --git a/MAINTAINERS b/MAINTAINERS index 3e42c9a572c23f..c382111261dbf9 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -2456,6 +2456,7 @@ F: drivers/hwmon/macsmc-hwmon.c F: drivers/pmdomain/apple/ F: drivers/i2c/busses/i2c-pasemi-core.c F: drivers/i2c/busses/i2c-pasemi-platform.c +F: drivers/input/misc/macsmc-input.c F: drivers/input/touchscreen/apple_z2.c F: drivers/iommu/apple-dart.c F: drivers/iommu/io-pgtable-dart.c diff --git a/drivers/input/misc/Kconfig b/drivers/input/misc/Kconfig index cc2558630797e6..aa3d86afbad077 100644 --- a/drivers/input/misc/Kconfig +++ b/drivers/input/misc/Kconfig @@ -992,4 +992,15 @@ config INPUT_STPMIC1_ONKEY To compile this driver as a module, choose M here: the module will be called stpmic1_onkey. +config INPUT_MACSMC_INPUT + tristate "Apple Mac SMC lid/buttons" + depends on MFD_MACSMC + help + Say Y here if you want to use the input events delivered via the + SMC controller on Apple Mac machines using the macsmc driver. + This includes lid open/close and the power button. + + To compile this driver as a module, choose M here: the + module will be called macsmc-input. + endif diff --git a/drivers/input/misc/Makefile b/drivers/input/misc/Makefile index f5ebfa9d998314..9667a53372ce26 100644 --- a/drivers/input/misc/Makefile +++ b/drivers/input/misc/Makefile @@ -52,6 +52,7 @@ obj-$(CONFIG_INPUT_IQS7222) += iqs7222.o obj-$(CONFIG_INPUT_KEYSPAN_REMOTE) += keyspan_remote.o obj-$(CONFIG_INPUT_KXTJ9) += kxtj9.o obj-$(CONFIG_INPUT_M68K_BEEP) += m68kspkr.o +obj-$(CONFIG_INPUT_MACSMC_INPUT) += macsmc-input.o obj-$(CONFIG_INPUT_MAX7360_ROTARY) += max7360-rotary.o obj-$(CONFIG_INPUT_MAX77650_ONKEY) += max77650-onkey.o obj-$(CONFIG_INPUT_MAX77693_HAPTIC) += max77693-haptic.o diff --git a/drivers/input/misc/macsmc-input.c b/drivers/input/misc/macsmc-input.c new file mode 100644 index 00000000000000..ebbc7dfc31f53d --- /dev/null +++ b/drivers/input/misc/macsmc-input.c @@ -0,0 +1,208 @@ +// SPDX-License-Identifier: GPL-2.0-only OR MIT +/* + * Apple SMC input event driver + * Copyright The Asahi Linux Contributors + * + * This driver exposes HID events from the SMC as an input device. + * This includes the lid open/close and power button notifications. + */ + +#include +#include +#include +#include +#include +#include + +/** + * struct macsmc_input + * @dev: Underlying struct device for the input sub-device + * @smc: Pointer to apple_smc struct of the mfd parent + * @input: Allocated input_dev; devres managed + * @nb: Notifier block used for incoming events from SMC (e.g. button pressed down) + * @wakeup_mode: Set to true when system is suspended and power button events should wake it + */ +struct macsmc_input { + struct device *dev; + struct apple_smc *smc; + struct input_dev *input; + struct notifier_block nb; + bool wakeup_mode; +}; + +#define SMC_EV_BTN 0x7201 +#define SMC_EV_LID 0x7203 + +#define BTN_POWER 0x01 /* power button on e.g. Mac Mini chasis pressed */ +#define BTN_TOUCHID 0x06 /* combined TouchID / power button on MacBooks pressed */ +#define BTN_POWER_HELD_SHORT 0xfe /* power button briefly held down */ +#define BTN_POWER_HELD_LONG 0x00 /* power button held down; sent just before forced poweroff */ + +static void macsmc_input_event_button(struct macsmc_input *smcin, unsigned long event) +{ + u8 button = (event >> 8) & 0xff; + u8 state = !!(event & 0xff); + + switch (button) { + case BTN_POWER: + case BTN_TOUCHID: + if (smcin->wakeup_mode) { + if (state) + pm_wakeup_event(smcin->dev, 0); + } else { + input_report_key(smcin->input, KEY_POWER, state); + input_sync(smcin->input); + } + break; + case BTN_POWER_HELD_SHORT: /* power button held down; ignore */ + break; + case BTN_POWER_HELD_LONG: + /* + * If we get here the power button has been held down for a while and + * we have about 4 seconds before forced power-off is triggered by SMC. + * Try to do an emergency shutdown to make sure the NVMe cache is + * flushed. macOS actually does this by panicing (!)... + */ + if (state) { + dev_crit(smcin->dev, "Triggering forced shutdown!\n"); + if (kernel_can_power_off()) + kernel_power_off(); + else /* Missing macsmc-reboot driver? */ + kernel_restart("SMC power button triggered restart"); + } + break; + default: + dev_warn(smcin->dev, "Unknown SMC button event: %04lx\n", event & 0xffff); + } +} + +static void macsmc_input_event_lid(struct macsmc_input *smcin, unsigned long event) +{ + u8 lid_state = !!((event >> 8) & 0xff); + + if (smcin->wakeup_mode && !lid_state) + pm_wakeup_event(smcin->dev, 0); + + input_report_switch(smcin->input, SW_LID, lid_state); + input_sync(smcin->input); +} + +static int macsmc_input_event(struct notifier_block *nb, unsigned long event, void *data) +{ + struct macsmc_input *smcin = container_of(nb, struct macsmc_input, nb); + u16 type = event >> 16; + + switch (type) { + case SMC_EV_BTN: + macsmc_input_event_button(smcin, event); + return NOTIFY_OK; + case SMC_EV_LID: + macsmc_input_event_lid(smcin, event); + return NOTIFY_OK; + default: + /* SMC event meant for another driver */ + return NOTIFY_DONE; + } +} + +static int macsmc_input_probe(struct platform_device *pdev) +{ + struct apple_smc *smc = dev_get_drvdata(pdev->dev.parent); + struct macsmc_input *smcin; + bool have_lid, have_power; + int error; + + /* Bail early if this SMC neither supports power button nor lid events */ + have_lid = apple_smc_key_exists(smc, SMC_KEY(MSLD)); + have_power = apple_smc_key_exists(smc, SMC_KEY(bHLD)); + if (!have_lid && !have_power) + return -ENODEV; + + smcin = devm_kzalloc(&pdev->dev, sizeof(*smcin), GFP_KERNEL); + if (!smcin) + return -ENOMEM; + + smcin->dev = &pdev->dev; + smcin->smc = smc; + platform_set_drvdata(pdev, smcin); + + smcin->input = devm_input_allocate_device(&pdev->dev); + if (!smcin->input) + return -ENOMEM; + + smcin->input->phys = "macsmc-input (0)"; + smcin->input->name = "Apple SMC power/lid events"; + + if (have_lid) + input_set_capability(smcin->input, EV_SW, SW_LID); + if (have_power) + input_set_capability(smcin->input, EV_KEY, KEY_POWER); + + if (have_lid) { + u8 val; + + error = apple_smc_read_u8(smc, SMC_KEY(MSLD), &val); + if (error < 0) + dev_warn(&pdev->dev, "Failed to read initial lid state\n"); + else + input_report_switch(smcin->input, SW_LID, val); + } + + if (have_power) { + u32 val; + + error = apple_smc_read_u32(smc, SMC_KEY(bHLD), &val); + if (error < 0) + dev_warn(&pdev->dev, "Failed to read initial power button state\n"); + else + input_report_key(smcin->input, KEY_POWER, val & 1); + } + + error = input_register_device(smcin->input); + if (error) { + dev_err(&pdev->dev, "Failed to register input device: %d\n", error); + return error; + } + + input_sync(smcin->input); + + smcin->nb.notifier_call = macsmc_input_event; + blocking_notifier_chain_register(&smc->event_handlers, &smcin->nb); + + device_init_wakeup(&pdev->dev, 1); + + return 0; +} + +static int macsmc_input_pm_prepare(struct device *dev) +{ + struct macsmc_input *smcin = dev_get_drvdata(dev); + + smcin->wakeup_mode = true; + return 0; +} + +static void macsmc_input_pm_complete(struct device *dev) +{ + struct macsmc_input *smcin = dev_get_drvdata(dev); + + smcin->wakeup_mode = false; +} + +static const struct dev_pm_ops macsmc_input_pm_ops = { + .prepare = macsmc_input_pm_prepare, + .complete = macsmc_input_pm_complete, +}; + +static struct platform_driver macsmc_input_driver = { + .driver = { + .name = "macsmc-input", + .pm = &macsmc_input_pm_ops, + }, + .probe = macsmc_input_probe, +}; +module_platform_driver(macsmc_input_driver); + +MODULE_AUTHOR("Hector Martin "); +MODULE_LICENSE("Dual MIT/GPL"); +MODULE_DESCRIPTION("Apple SMC input driver"); From ab34012b5bd69504a87ca897ea68a81d9677d4a4 Mon Sep 17 00:00:00 2001 From: James Calligeros Date: Tue, 7 Oct 2025 21:16:50 +1000 Subject: [PATCH 0191/3902] mfd: macsmc: Wire up Apple SMC input subdevice Add the new SMC input function to the mfd device Reviewed-by: Neal Gompa Signed-off-by: James Calligeros --- drivers/mfd/macsmc.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/mfd/macsmc.c b/drivers/mfd/macsmc.c index 51dd667d3b5fe0..3b69eb6d032a3b 100644 --- a/drivers/mfd/macsmc.c +++ b/drivers/mfd/macsmc.c @@ -45,6 +45,7 @@ #define SMC_TIMEOUT_MS 500 static const struct mfd_cell apple_smc_devs[] = { + MFD_CELL_NAME("macsmc-input"), MFD_CELL_OF("macsmc-gpio", NULL, NULL, 0, 0, "apple,smc-gpio"), MFD_CELL_OF("macsmc-hwmon", NULL, NULL, 0, 0, "apple,smc-hwmon"), MFD_CELL_OF("macsmc-reboot", NULL, NULL, 0, 0, "apple,smc-reboot"), From 71f9f6475cde8d2ee522579988583e4ae88d990c Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Thu, 16 Oct 2025 21:58:41 +0200 Subject: [PATCH 0192/3902] fixup! input: macsmc-input: New driver to handle the Apple Mac SMC buttons/lid Signed-off-by: Janne Grunau --- drivers/input/misc/macsmc-input.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/input/misc/macsmc-input.c b/drivers/input/misc/macsmc-input.c index ebbc7dfc31f53d..2c05b2e882c53c 100644 --- a/drivers/input/misc/macsmc-input.c +++ b/drivers/input/misc/macsmc-input.c @@ -206,3 +206,4 @@ module_platform_driver(macsmc_input_driver); MODULE_AUTHOR("Hector Martin "); MODULE_LICENSE("Dual MIT/GPL"); MODULE_DESCRIPTION("Apple SMC input driver"); +MODULE_ALIAS("platform:macsmc-input"); From dda2dd28095c16bc66a3cca2989995fc199b0eab Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Fri, 26 Sep 2025 09:37:43 +0200 Subject: [PATCH 0193/3902] power: reset: macsmc-reboot: Prevent probing without of_node MFD will probe sub devices declared with MFD_CELL_OF() even without match on the device tree compatible. macsmc-reboot depends on nvmem provided via device tree. Fail probe() with -ENODEV if this information is missing. Signed-off-by: Janne Grunau --- drivers/power/reset/macsmc-reboot.c | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/drivers/power/reset/macsmc-reboot.c b/drivers/power/reset/macsmc-reboot.c index e9702acdd366b0..94fcbf12fe3b93 100644 --- a/drivers/power/reset/macsmc-reboot.c +++ b/drivers/power/reset/macsmc-reboot.c @@ -205,6 +205,14 @@ static int macsmc_reboot_probe(struct platform_device *pdev) struct macsmc_reboot *reboot; int ret, i; + /* + * MFD will probe this device even without a node in the device tree, + * thus bail out early if the SMC on the current machines does not + * support reboot and has no node in the device tree. + */ + if (!pdev->dev.of_node) + return -ENODEV; + reboot = devm_kzalloc(&pdev->dev, sizeof(*reboot), GFP_KERNEL); if (!reboot) return -ENOMEM; From 4b0354ce67efab9b79935bd345d2da5dadcb1373 Mon Sep 17 00:00:00 2001 From: Hector Martin Date: Tue, 8 Feb 2022 02:30:16 +0900 Subject: [PATCH 0194/3902] power: supply: macsmc_power: Driver for Apple SMC power/battery stats MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This driver implements support for battery stats on top of the macsmc framework, to support Apple M1 Mac machines. power: supply: macsmc_power: Add cycle count and health props power: supply: macsmc_power: Add present prop power: supply: macsmc_power: Add more props, rework others power: supply: macsmc_power: Use BUIC instead of BRSC for charge power: supply: macsmc_power: Turn off OBC flags if macOS left them on power: supply: macsmc_power: Add AC power supply power: supply: macsmc_power: Add critical level shutdown & misc events power: supply: macsmc_power: Add CHWA charge thresholds This is a hardcoded charge threshold feature present in firmware 13.0 or newer. Userspace settings are rounded to one of the two possible behaviors. power: supply: macsmc_power: Report available charge_behaviours The generic handling if charge_behaviours in the power_supply core requires power_supply_desc.charge_behaviours to be set. power: supply: macsmc_power: Add more properties Report more voltages from the battery, and also fudge energy numbers from charge numbers. This way userspace doesn't try to convert on its own (and gets it very wrong). power: supply: macsmc_power: Add CHLS charge thresholds Since macOS Sequoia firmware, CHLS replaced CHWA and now allows an arbitrary end charge threshold to be configured. Prefer CHWA over CHLS since the SMC firmware from iBoot-10151.1.1 (macOS 14.0) is not compatible with our CHGLS usage. It was working with the SMC firmware from iBoot-10151.121.1 (macOS 14.5). power: supply: macsmc_power: Remove CSIL Gone in Sequoia firmware. power: supply: macsmc_power: Report not charging for CHLS thresholds If a CHLS charge threshold is configured and the current SoC is above the start threshold report a busy BMS as not charging. power: supply: macsmc_power: Report only supported properties The SMC firmware in macOS 15.4 dropped "AC-i" and "AC-n" (and all keys with lower case last letter) without obvious replacement. Stop reporting VOLTAGE_NOW / INPUT_CURRENT_LIMIT if "AC-n" is not present. Signed-off-by: Thomas Weißschuh Co-developed-by: Thomas Weißschuh Signed-off-by: Janne Grunau Co-developed-by: Janne Grunau Co-authored-by: Joey Gouly Signed-off-by: Hector Martin --- drivers/mfd/macsmc.c | 1 + drivers/power/supply/Kconfig | 7 + drivers/power/supply/Makefile | 1 + drivers/power/supply/macsmc-power.c | 810 ++++++++++++++++++++++++++++ 4 files changed, 819 insertions(+) create mode 100644 drivers/power/supply/macsmc-power.c diff --git a/drivers/mfd/macsmc.c b/drivers/mfd/macsmc.c index 3b69eb6d032a3b..96006145afda88 100644 --- a/drivers/mfd/macsmc.c +++ b/drivers/mfd/macsmc.c @@ -46,6 +46,7 @@ static const struct mfd_cell apple_smc_devs[] = { MFD_CELL_NAME("macsmc-input"), + MFD_CELL_NAME("macsmc-power"), MFD_CELL_OF("macsmc-gpio", NULL, NULL, 0, 0, "apple,smc-gpio"), MFD_CELL_OF("macsmc-hwmon", NULL, NULL, 0, 0, "apple,smc-hwmon"), MFD_CELL_OF("macsmc-reboot", NULL, NULL, 0, 0, "apple,smc-reboot"), diff --git a/drivers/power/supply/Kconfig b/drivers/power/supply/Kconfig index dca4be23ee7095..aa2a1119cb6b7e 100644 --- a/drivers/power/supply/Kconfig +++ b/drivers/power/supply/Kconfig @@ -1097,4 +1097,11 @@ config FUEL_GAUGE_MM8013 the state of charge, temperature, cycle count, actual and design capacity, etc. +config CHARGER_MACSMC + tristate "Apple SMC Charger / Battery support" + depends on MFD_MACSMC + help + Say Y here to enable support for the charger and battery controls on + Apple SMC controllers, as used on Apple Silicon Macs. + endif # POWER_SUPPLY diff --git a/drivers/power/supply/Makefile b/drivers/power/supply/Makefile index 99a820d38197cd..9a9b3bb54e4f0b 100644 --- a/drivers/power/supply/Makefile +++ b/drivers/power/supply/Makefile @@ -79,6 +79,7 @@ obj-$(CONFIG_CHARGER_GPIO) += gpio-charger.o obj-$(CONFIG_CHARGER_MANAGER) += charger-manager.o obj-$(CONFIG_CHARGER_LT3651) += lt3651-charger.o obj-$(CONFIG_CHARGER_LTC4162L) += ltc4162-l-charger.o +obj-$(CONFIG_CHARGER_MACSMC) += macsmc-power.o obj-$(CONFIG_CHARGER_MAX14577) += max14577_charger.o obj-$(CONFIG_CHARGER_DETECTOR_MAX14656) += max14656_charger_detector.o obj-$(CONFIG_CHARGER_MAX77650) += max77650-charger.o diff --git a/drivers/power/supply/macsmc-power.c b/drivers/power/supply/macsmc-power.c new file mode 100644 index 00000000000000..575230d57d6dc6 --- /dev/null +++ b/drivers/power/supply/macsmc-power.c @@ -0,0 +1,810 @@ +// SPDX-License-Identifier: GPL-2.0-only OR MIT +/* + * Apple SMC Power/Battery Management + * Copyright The Asahi Linux Contributors + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define MAX_STRING_LENGTH 256 + +/* + * This number is not reported anywhere by SMC, but seems to be a good + * conversion factor for charge to energy across machines. We need this + * to convert in the driver, since if we don't userspace will try to do + * the conversion with a randomly guessed voltage and get it wrong. + * + * Ideally there would be a power supply prop to inform userspace of this + * number, but there isn't, only min/max. + */ +#define MACSMC_NOMINAL_CELL_VOLTAGE_MV 3800 + +struct macsmc_power { + struct device *dev; + struct apple_smc *smc; + struct power_supply_desc ac_desc; + struct power_supply_desc batt_desc; + + struct power_supply *batt; + char model_name[MAX_STRING_LENGTH]; + char serial_number[MAX_STRING_LENGTH]; + char mfg_date[MAX_STRING_LENGTH]; + bool has_chwa; + bool has_chls; + u8 num_cells; + int nominal_voltage_mv; + + struct power_supply *ac; + + struct notifier_block nb; + + struct work_struct critical_work; + bool shutdown_started; +}; + +#define CHNC_BATTERY_FULL BIT(0) +#define CHNC_NO_CHARGER BIT(7) +#define CHNC_NOCHG_CH0C BIT(14) +#define CHNC_NOCHG_CH0B_CH0K BIT(15) +#define CHNC_BATTERY_FULL_2 BIT(18) +#define CHNC_BMS_BUSY BIT(23) +#define CHNC_CHLS_LIMIT BIT(24) +#define CHNC_NOAC_CH0J BIT(53) +#define CHNC_NOAC_CH0I BIT(54) + +#define CH0R_LOWER_FLAGS GENMASK(15, 0) +#define CH0R_NOAC_CH0I BIT(0) +#define CH0R_NOAC_DISCONNECTED BIT(4) +#define CH0R_NOAC_CH0J BIT(5) +#define CH0R_BMS_BUSY BIT(8) +#define CH0R_NOAC_CH0K BIT(9) +#define CH0R_NOAC_CHWA BIT(11) + +#define CH0X_CH0C BIT(0) +#define CH0X_CH0B BIT(1) + +#define ACSt_CAN_BOOT_AP BIT(2) +#define ACSt_CAN_BOOT_IBOOT BIT(1) + +#define CHWA_CHLS_FIXED_START_OFFSET 5 +#define CHLS_MIN_END_THRESHOLD 10 +#define CHLS_FORCE_DISCHARGE 0x100 +#define CHWA_FIXED_END_THRESHOLD 80 +#define CHWA_PROP_WRITE_THRESHOLD 95 + +static int macsmc_battery_get_status(struct macsmc_power *power) +{ + u64 nocharge_flags; + u32 nopower_flags; + u16 ac_current; + int charge_limit = 0; + bool limited = false; + bool flag; + int ret; + + /* + * Note: there are fallbacks in case some of these SMC keys disappear in the future + * or are not present on some machines. We treat the absence of the CHCE/CHCC/BSFC/CHSC + * flags as an error, since they are quite fundamental and simple booleans. + */ + + /* + * If power input is inhibited, we are definitely discharging. + * However, if the only reason is the BMS is doing a balancing cycle, + * go ahead and ignore that one to avoid spooking users. + */ + ret = apple_smc_read_u32(power->smc, SMC_KEY(CH0R), &nopower_flags); + if (!ret && (nopower_flags & CH0R_LOWER_FLAGS & ~CH0R_BMS_BUSY)) + return POWER_SUPPLY_STATUS_DISCHARGING; + + /* If no charger is present, we are definitely discharging. */ + ret = apple_smc_read_flag(power->smc, SMC_KEY(CHCE), &flag); + if (ret < 0) + return ret; + if (!flag) + return POWER_SUPPLY_STATUS_DISCHARGING; + + /* If AC is not charge capable, we are definitely discharging. */ + ret = apple_smc_read_flag(power->smc, SMC_KEY(CHCC), &flag); + if (ret < 0) + return ret; + if (!flag) + return POWER_SUPPLY_STATUS_DISCHARGING; + + /* + * If the AC input current limit is tiny or 0, we are discharging no matter + * how much the BMS believes it can charge. + */ + ret = apple_smc_read_u16(power->smc, SMC_KEY(AC-i), &ac_current); + if (!ret && ac_current < 100) + return POWER_SUPPLY_STATUS_DISCHARGING; + + /* If the battery is full, report it as such. */ + ret = apple_smc_read_flag(power->smc, SMC_KEY(BSFC), &flag); + if (ret < 0) + return ret; + if (flag) + return POWER_SUPPLY_STATUS_FULL; + + /* + * If we have charge limits supported and enabled and the SoC is above + * the start threshold, that means we are not charging for that reason + * (if not charging). + */ + if (power->has_chls) { + u16 vu16; + ret = apple_smc_read_u16(power->smc, SMC_KEY(CHLS), &vu16); + if (ret == sizeof(vu16) && (vu16 & 0xff) >= CHLS_MIN_END_THRESHOLD) + charge_limit = (vu16 & 0xff) - CHWA_CHLS_FIXED_START_OFFSET; + } else if (power->has_chwa) { + ret = apple_smc_read_flag(power->smc, SMC_KEY(CHWA), &flag); + if (ret == 0 && flag) + charge_limit = CHWA_FIXED_END_THRESHOLD - CHWA_CHLS_FIXED_START_OFFSET; + } + + if (charge_limit > 0) { + u8 buic = 0; + if (apple_smc_read_u8(power->smc, SMC_KEY(BUIC), &buic) >= 0 && + buic >= charge_limit) + limited = true; + } + + /* If there are reasons we aren't charging... */ + ret = apple_smc_read_u64(power->smc, SMC_KEY(CHNC), &nocharge_flags); + if (!ret) { + /* Perhaps the battery is full after all */ + if (nocharge_flags & CHNC_BATTERY_FULL) + return POWER_SUPPLY_STATUS_FULL; + /* + * Or maybe the BMS is just busy doing something, if so call it charging anyway. + * But CHWA limits show up as this, so exclude those. + */ + else if (nocharge_flags == CHNC_BMS_BUSY && !limited) + return POWER_SUPPLY_STATUS_CHARGING; + /* If we have other reasons we aren't charging, say we aren't */ + else if (nocharge_flags) + return POWER_SUPPLY_STATUS_NOT_CHARGING; + /* Else we're either charging or about to charge */ + else + return POWER_SUPPLY_STATUS_CHARGING; + } + + /* As a fallback, use the system charging flag. */ + ret = apple_smc_read_flag(power->smc, SMC_KEY(CHSC), &flag); + if (ret < 0) + return ret; + if (!flag) + return POWER_SUPPLY_STATUS_NOT_CHARGING; + else + return POWER_SUPPLY_STATUS_CHARGING; +} + +static int macsmc_battery_get_charge_behaviour(struct macsmc_power *power) +{ + int ret; + u8 val; + + /* CH0I returns a bitmask like the low byte of CH0R */ + ret = apple_smc_read_u8(power->smc, SMC_KEY(CH0I), &val); + if (ret) + return ret; + if (val & CH0R_NOAC_CH0I) + return POWER_SUPPLY_CHARGE_BEHAVIOUR_FORCE_DISCHARGE; + + /* CH0C returns a bitmask containing CH0B/CH0C flags */ + ret = apple_smc_read_u8(power->smc, SMC_KEY(CH0C), &val); + if (ret) + return ret; + if (val & CH0X_CH0C) + return POWER_SUPPLY_CHARGE_BEHAVIOUR_INHIBIT_CHARGE; + else + return POWER_SUPPLY_CHARGE_BEHAVIOUR_AUTO; +} + +static int macsmc_battery_set_charge_behaviour(struct macsmc_power *power, int val) +{ + u8 ch0i, ch0c; + int ret; + + /* + * CH0I/CH0C are "hard" controls that will allow the battery to run down to 0. + * CH0K/CH0B are "soft" controls that are reset to 0 when SOC drops below 50%; + * we don't expose these yet. + */ + + switch (val) { + case POWER_SUPPLY_CHARGE_BEHAVIOUR_AUTO: + ch0i = ch0c = 0; + break; + case POWER_SUPPLY_CHARGE_BEHAVIOUR_INHIBIT_CHARGE: + ch0i = 0; + ch0c = 1; + break; + case POWER_SUPPLY_CHARGE_BEHAVIOUR_FORCE_DISCHARGE: + ch0i = 1; + ch0c = 0; + break; + default: + return -EINVAL; + } + ret = apple_smc_write_u8(power->smc, SMC_KEY(CH0I), ch0i); + if (ret) + return ret; + return apple_smc_write_u8(power->smc, SMC_KEY(CH0C), ch0c); +} + +static int macsmc_battery_get_date(const char *s, int *out) +{ + if (!isdigit(s[0]) || !isdigit(s[1])) + return -ENOTSUPP; + + *out = (s[0] - '0') * 10 + s[1] - '0'; + return 0; +} + +static int macsmc_battery_get_capacity_level(struct macsmc_power *power) +{ + bool flag; + u32 val; + int ret; + + /* Check for emergency shutdown condition */ + if (apple_smc_read_u32(power->smc, SMC_KEY(BCF0), &val) >= 0 && val) + return POWER_SUPPLY_CAPACITY_LEVEL_CRITICAL; + + /* Check AC status for whether we could boot in this state */ + if (apple_smc_read_u32(power->smc, SMC_KEY(ACSt), &val) >= 0) { + if (!(val & ACSt_CAN_BOOT_IBOOT)) + return POWER_SUPPLY_CAPACITY_LEVEL_CRITICAL; + + if (!(val & ACSt_CAN_BOOT_AP)) + return POWER_SUPPLY_CAPACITY_LEVEL_LOW; + } + + /* Check battery full flag */ + ret = apple_smc_read_flag(power->smc, SMC_KEY(BSFC), &flag); + if (ret < 0) + return POWER_SUPPLY_CAPACITY_LEVEL_UNKNOWN; + if (flag) + return POWER_SUPPLY_CAPACITY_LEVEL_FULL; + else + return POWER_SUPPLY_CAPACITY_LEVEL_NORMAL; +} + +static int macsmc_battery_get_property(struct power_supply *psy, + enum power_supply_property psp, + union power_supply_propval *val) +{ + struct macsmc_power *power = power_supply_get_drvdata(psy); + int ret = 0; + u8 vu8; + u16 vu16; + s16 vs16; + s32 vs32; + s64 vs64; + bool flag; + + switch (psp) { + case POWER_SUPPLY_PROP_STATUS: + val->intval = macsmc_battery_get_status(power); + ret = val->intval < 0 ? val->intval : 0; + break; + case POWER_SUPPLY_PROP_PRESENT: + val->intval = 1; + break; + case POWER_SUPPLY_PROP_CHARGE_BEHAVIOUR: + val->intval = macsmc_battery_get_charge_behaviour(power); + ret = val->intval < 0 ? val->intval : 0; + break; + case POWER_SUPPLY_PROP_TIME_TO_EMPTY_NOW: + ret = apple_smc_read_u16(power->smc, SMC_KEY(B0TE), &vu16); + val->intval = vu16 == 0xffff ? 0 : vu16 * 60; + break; + case POWER_SUPPLY_PROP_TIME_TO_FULL_NOW: + ret = apple_smc_read_u16(power->smc, SMC_KEY(B0TF), &vu16); + val->intval = vu16 == 0xffff ? 0 : vu16 * 60; + break; + case POWER_SUPPLY_PROP_CAPACITY: + ret = apple_smc_read_u8(power->smc, SMC_KEY(BUIC), &vu8); + val->intval = vu8; + break; + case POWER_SUPPLY_PROP_CAPACITY_LEVEL: + val->intval = macsmc_battery_get_capacity_level(power); + ret = val->intval < 0 ? val->intval : 0; + break; + case POWER_SUPPLY_PROP_VOLTAGE_NOW: + ret = apple_smc_read_u16(power->smc, SMC_KEY(B0AV), &vu16); + val->intval = vu16 * 1000; + break; + case POWER_SUPPLY_PROP_CURRENT_NOW: + ret = apple_smc_read_s16(power->smc, SMC_KEY(B0AC), &vs16); + val->intval = vs16 * 1000; + break; + case POWER_SUPPLY_PROP_POWER_NOW: + ret = apple_smc_read_s32(power->smc, SMC_KEY(B0AP), &vs32); + val->intval = vs32 * 1000; + break; + case POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN: + ret = apple_smc_read_u16(power->smc, SMC_KEY(BITV), &vu16); + val->intval = vu16 * 1000; + break; + case POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN: + /* + * Battery cell max voltage? BVV* seem to return per-cell voltages, + * BVV[NOP] are probably the max voltages for the 3 cells but we don't + * know what will happen if they ever change the number of cells. + * So go with BVVN and multiply by the cell count (BNCB). + * BVVL seems to be the per-cell limit adjusted dynamically. + * Guess: BVVL = Limit, BVVN = Nominal, and the other cells got filled + * in around nearby letters? + */ + ret = apple_smc_read_u16(power->smc, SMC_KEY(BVVN), &vu16); + val->intval = vu16 * 1000 * power->num_cells; + break; + case POWER_SUPPLY_PROP_VOLTAGE_MIN: + /* Lifetime min */ + ret = apple_smc_read_s16(power->smc, SMC_KEY(BLPM), &vs16); + val->intval = vs16 * 1000; + break; + case POWER_SUPPLY_PROP_VOLTAGE_MAX: + /* Lifetime max */ + ret = apple_smc_read_s16(power->smc, SMC_KEY(BLPX), &vs16); + val->intval = vs16 * 1000; + break; + case POWER_SUPPLY_PROP_CHARGE_TERM_CURRENT: + ret = apple_smc_read_u16(power->smc, SMC_KEY(B0RC), &vu16); + val->intval = vu16 * 1000; + break; + case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT_MAX: + ret = apple_smc_read_u16(power->smc, SMC_KEY(B0RI), &vu16); + val->intval = vu16 * 1000; + break; + case POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE: + ret = apple_smc_read_u16(power->smc, SMC_KEY(B0RV), &vu16); + val->intval = vu16 * 1000; + break; + case POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN: + ret = apple_smc_read_u16(power->smc, SMC_KEY(B0DC), &vu16); + val->intval = vu16 * 1000; + break; + case POWER_SUPPLY_PROP_CHARGE_FULL: + ret = apple_smc_read_u16(power->smc, SMC_KEY(B0FC), &vu16); + val->intval = vu16 * 1000; + break; + case POWER_SUPPLY_PROP_CHARGE_NOW: + ret = apple_smc_read_u16(power->smc, SMC_KEY(B0RM), &vu16); + val->intval = swab16(vu16) * 1000; + break; + case POWER_SUPPLY_PROP_ENERGY_FULL_DESIGN: + ret = apple_smc_read_u16(power->smc, SMC_KEY(B0DC), &vu16); + val->intval = vu16 * power->nominal_voltage_mv; + break; + case POWER_SUPPLY_PROP_ENERGY_FULL: + ret = apple_smc_read_u16(power->smc, SMC_KEY(B0FC), &vu16); + val->intval = vu16 * power->nominal_voltage_mv; + break; + case POWER_SUPPLY_PROP_ENERGY_NOW: + ret = apple_smc_read_u16(power->smc, SMC_KEY(B0RM), &vu16); + val->intval = swab16(vu16) * power->nominal_voltage_mv; + break; + case POWER_SUPPLY_PROP_TEMP: + ret = apple_smc_read_u16(power->smc, SMC_KEY(B0AT), &vu16); + val->intval = vu16 - 2732; + break; + case POWER_SUPPLY_PROP_CHARGE_COUNTER: + ret = apple_smc_read_s64(power->smc, SMC_KEY(BAAC), &vs64); + val->intval = vs64; + break; + case POWER_SUPPLY_PROP_CYCLE_COUNT: + ret = apple_smc_read_u16(power->smc, SMC_KEY(B0CT), &vu16); + val->intval = vu16; + break; + case POWER_SUPPLY_PROP_SCOPE: + val->intval = POWER_SUPPLY_SCOPE_SYSTEM; + break; + case POWER_SUPPLY_PROP_HEALTH: + flag = false; + ret = apple_smc_read_flag(power->smc, SMC_KEY(BBAD), &flag); + val->intval = flag ? POWER_SUPPLY_HEALTH_DEAD : POWER_SUPPLY_HEALTH_GOOD; + break; + case POWER_SUPPLY_PROP_MODEL_NAME: + val->strval = power->model_name; + break; + case POWER_SUPPLY_PROP_SERIAL_NUMBER: + val->strval = power->serial_number; + break; + case POWER_SUPPLY_PROP_MANUFACTURE_YEAR: + ret = macsmc_battery_get_date(&power->mfg_date[0], &val->intval); + val->intval += 2000 - 8; /* -8 is a fixup for a firmware bug... */ + break; + case POWER_SUPPLY_PROP_MANUFACTURE_MONTH: + ret = macsmc_battery_get_date(&power->mfg_date[2], &val->intval); + break; + case POWER_SUPPLY_PROP_MANUFACTURE_DAY: + ret = macsmc_battery_get_date(&power->mfg_date[4], &val->intval); + break; + case POWER_SUPPLY_PROP_CHARGE_CONTROL_START_THRESHOLD: + case POWER_SUPPLY_PROP_CHARGE_CONTROL_END_THRESHOLD: + if (power->has_chls) { + ret = apple_smc_read_u16(power->smc, SMC_KEY(CHLS), &vu16); + val->intval = vu16 & 0xff; + if (val->intval < CHLS_MIN_END_THRESHOLD || val->intval >= 100) + val->intval = 100; + } + else if (power->has_chwa) { + flag = false; + ret = apple_smc_read_flag(power->smc, SMC_KEY(CHWA), &flag); + val->intval = flag ? CHWA_FIXED_END_THRESHOLD : 100; + } else { + return -EINVAL; + } + if (psp == POWER_SUPPLY_PROP_CHARGE_CONTROL_START_THRESHOLD && + ret >= 0 && val->intval < 100 && val->intval >= CHLS_MIN_END_THRESHOLD) + val->intval -= CHWA_CHLS_FIXED_START_OFFSET; + break; + default: + return -EINVAL; + } + + return ret; +} + +static int macsmc_battery_set_property(struct power_supply *psy, + enum power_supply_property psp, + const union power_supply_propval *val) +{ + struct macsmc_power *power = power_supply_get_drvdata(psy); + + switch (psp) { + case POWER_SUPPLY_PROP_CHARGE_BEHAVIOUR: + return macsmc_battery_set_charge_behaviour(power, val->intval); + case POWER_SUPPLY_PROP_CHARGE_CONTROL_START_THRESHOLD: + /* + * Ignore, we allow writes so userspace isn't confused but this is + * not configurable independently, it always is end - 5 or 100 depending + * on the end_threshold setting. + */ + return 0; + case POWER_SUPPLY_PROP_CHARGE_CONTROL_END_THRESHOLD: + if (power->has_chls) { + u16 kval = 0; + /* TODO: Make CHLS_FORCE_DISCHARGE configurable */ + if (val->intval < CHLS_MIN_END_THRESHOLD) + kval = CHLS_FORCE_DISCHARGE | CHLS_MIN_END_THRESHOLD; + else if (val->intval < 100) + kval = CHLS_FORCE_DISCHARGE | (val->intval & 0xff); + return apple_smc_write_u16(power->smc, SMC_KEY(CHLS), kval); + } else if (power->has_chwa) { + return apple_smc_write_flag(power->smc, SMC_KEY(CHWA), + val->intval <= CHWA_PROP_WRITE_THRESHOLD); + } else { + return -EINVAL; + } + default: + return -EINVAL; + } +} + +static int macsmc_battery_property_is_writeable(struct power_supply *psy, + enum power_supply_property psp) +{ + struct macsmc_power *power = power_supply_get_drvdata(psy); + + switch (psp) { + case POWER_SUPPLY_PROP_CHARGE_BEHAVIOUR: + return true; + case POWER_SUPPLY_PROP_CHARGE_CONTROL_START_THRESHOLD: + case POWER_SUPPLY_PROP_CHARGE_CONTROL_END_THRESHOLD: + return power->has_chwa || power->has_chls; + default: + return false; + } +} + +static const enum power_supply_property macsmc_battery_props[] = { + POWER_SUPPLY_PROP_STATUS, + POWER_SUPPLY_PROP_PRESENT, + POWER_SUPPLY_PROP_CHARGE_BEHAVIOUR, + POWER_SUPPLY_PROP_TIME_TO_EMPTY_NOW, + POWER_SUPPLY_PROP_TIME_TO_FULL_NOW, + POWER_SUPPLY_PROP_CAPACITY, + POWER_SUPPLY_PROP_CAPACITY_LEVEL, + POWER_SUPPLY_PROP_VOLTAGE_NOW, + POWER_SUPPLY_PROP_CURRENT_NOW, + POWER_SUPPLY_PROP_POWER_NOW, + POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN, + POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN, + POWER_SUPPLY_PROP_VOLTAGE_MIN, + POWER_SUPPLY_PROP_VOLTAGE_MAX, + POWER_SUPPLY_PROP_CHARGE_TERM_CURRENT, + POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT_MAX, + POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE, + POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN, + POWER_SUPPLY_PROP_CHARGE_FULL, + POWER_SUPPLY_PROP_CHARGE_NOW, + POWER_SUPPLY_PROP_ENERGY_FULL_DESIGN, + POWER_SUPPLY_PROP_ENERGY_FULL, + POWER_SUPPLY_PROP_ENERGY_NOW, + POWER_SUPPLY_PROP_TEMP, + POWER_SUPPLY_PROP_CHARGE_COUNTER, + POWER_SUPPLY_PROP_CYCLE_COUNT, + POWER_SUPPLY_PROP_SCOPE, + POWER_SUPPLY_PROP_HEALTH, + POWER_SUPPLY_PROP_MODEL_NAME, + POWER_SUPPLY_PROP_SERIAL_NUMBER, + POWER_SUPPLY_PROP_MANUFACTURE_YEAR, + POWER_SUPPLY_PROP_MANUFACTURE_MONTH, + POWER_SUPPLY_PROP_MANUFACTURE_DAY, + POWER_SUPPLY_PROP_CHARGE_CONTROL_START_THRESHOLD, + POWER_SUPPLY_PROP_CHARGE_CONTROL_END_THRESHOLD +}; + +static const struct power_supply_desc macsmc_battery_desc = { + .name = "macsmc-battery", + .type = POWER_SUPPLY_TYPE_BATTERY, + .get_property = macsmc_battery_get_property, + .set_property = macsmc_battery_set_property, + .property_is_writeable = macsmc_battery_property_is_writeable, + .properties = macsmc_battery_props, + .num_properties = ARRAY_SIZE(macsmc_battery_props), + .charge_behaviours = BIT(POWER_SUPPLY_CHARGE_BEHAVIOUR_AUTO) + | BIT(POWER_SUPPLY_CHARGE_BEHAVIOUR_FORCE_DISCHARGE) + | BIT(POWER_SUPPLY_CHARGE_BEHAVIOUR_INHIBIT_CHARGE), +}; + +static int macsmc_ac_get_property(struct power_supply *psy, + enum power_supply_property psp, + union power_supply_propval *val) +{ + struct macsmc_power *power = power_supply_get_drvdata(psy); + int ret = 0; + u16 vu16; + u32 vu32; + + switch (psp) { + case POWER_SUPPLY_PROP_ONLINE: + ret = apple_smc_read_u32(power->smc, SMC_KEY(CHIS), &vu32); + val->intval = !!vu32; + break; + case POWER_SUPPLY_PROP_VOLTAGE_NOW: + ret = apple_smc_read_u16(power->smc, SMC_KEY(AC-n), &vu16); + val->intval = vu16 * 1000; + break; + case POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT: + ret = apple_smc_read_u16(power->smc, SMC_KEY(AC-i), &vu16); + val->intval = vu16 * 1000; + break; + case POWER_SUPPLY_PROP_INPUT_POWER_LIMIT: + ret = apple_smc_read_u32(power->smc, SMC_KEY(ACPW), &vu32); + val->intval = vu32 * 1000; + break; + default: + return -EINVAL; + } + + return ret; +} + +static enum power_supply_property macsmc_ac_props[] = { + POWER_SUPPLY_PROP_ONLINE, + POWER_SUPPLY_PROP_INPUT_POWER_LIMIT, + POWER_SUPPLY_PROP_VOLTAGE_NOW, + POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT, +}; + +static const struct power_supply_desc macsmc_ac_desc = { + .name = "macsmc-ac", + .type = POWER_SUPPLY_TYPE_MAINS, + .get_property = macsmc_ac_get_property, + .properties = macsmc_ac_props, + .num_properties = ARRAY_SIZE(macsmc_ac_props), +}; + +static void macsmc_power_critical_work(struct work_struct *wrk) +{ + struct macsmc_power *power = container_of(wrk, struct macsmc_power, critical_work); + int ret; + u32 bcf0; + u16 bitv, b0av; + + /* + * Check if the battery voltage is below the design voltage. If it is, + * we have a few seconds until the machine dies. Explicitly shut down, + * which at least gets the NVMe controller to flush its cache. + */ + if (apple_smc_read_u16(power->smc, SMC_KEY(BITV), &bitv) >= 0 && + apple_smc_read_u16(power->smc, SMC_KEY(B0AV), &b0av) >= 0 && + b0av < bitv) { + dev_crit(power->dev, "Emergency notification: Battery is critical\n"); + if (kernel_can_power_off()) + kernel_power_off(); + else /* Missing macsmc-reboot driver? In this state, this will not boot anyway. */ + kernel_restart("Battery is critical"); + } + + /* This spams once per second, so make sure we only trigger shutdown once. */ + if (power->shutdown_started) + return; + + /* Check for battery empty condition */ + ret = apple_smc_read_u32(power->smc, SMC_KEY(BCF0), &bcf0); + if (ret < 0) { + dev_err(power->dev, + "Emergency notification: Failed to read battery status\n"); + } else if (bcf0 == 0) { + dev_warn(power->dev, "Emergency notification: Battery status is OK?\n"); + return; + } else { + dev_warn(power->dev, "Emergency notification: Battery is empty\n"); + } + + power->shutdown_started = true; + + /* + * Attempt to trigger an orderly shutdown. At this point, we should have a few + * minutes of reserve capacity left, enough to do a clean shutdown. + */ + dev_warn(power->dev, "Shutting down in 10 seconds\n"); + ssleep(10); + + /* + * Don't force it; if this stalls or fails, the last-resort check above will + * trigger a hard shutdown when shutdown is truly imminent. + */ + orderly_poweroff(false); +} + +static int macsmc_power_event(struct notifier_block *nb, unsigned long event, void *data) +{ + struct macsmc_power *power = container_of(nb, struct macsmc_power, nb); + + if ((event & 0xffffff00) == 0x71010100) { + bool charging = (event & 0xff) != 0; + + dev_info(power->dev, "Charging: %d\n", charging); + power_supply_changed(power->batt); + power_supply_changed(power->ac); + + return NOTIFY_OK; + } else if (event == 0x71020000) { + schedule_work(&power->critical_work); + + return NOTIFY_OK; + } else if ((event & 0xffff0000) == 0x71060000) { + u8 changed_port = event >> 8; + u8 cur_port; + + /* Port charging state change? */ + if (apple_smc_read_u8(power->smc, SMC_KEY(AC-W), &cur_port) >= 0) { + dev_info(power->dev, "Port %d state change (charge port: %d)\n", + changed_port + 1, cur_port); + } + + power_supply_changed(power->batt); + power_supply_changed(power->ac); + + return NOTIFY_OK; + } else if ((event & 0xff000000) == 0x71000000) { + dev_info(power->dev, "Unknown charger event 0x%lx\n", event); + + return NOTIFY_OK; + } else if ((event & 0xffff0000) == 0x72010000) { + return NOTIFY_OK; + } + + return NOTIFY_DONE; +} + +static int macsmc_power_probe(struct platform_device *pdev) +{ + struct apple_smc *smc = dev_get_drvdata(pdev->dev.parent); + struct power_supply_config psy_cfg = {}; + struct macsmc_power *power; + bool flag; + u32 val; + u16 vu16; + int ret; + + power = devm_kzalloc(&pdev->dev, sizeof(*power), GFP_KERNEL); + if (!power) + return -ENOMEM; + + power->dev = &pdev->dev; + power->smc = smc; + power->ac_desc = macsmc_ac_desc; + power->batt_desc = macsmc_battery_desc; + dev_set_drvdata(&pdev->dev, power); + + /* Ignore devices without a charger/battery */ + if (macsmc_battery_get_status(power) <= POWER_SUPPLY_STATUS_UNKNOWN) + return -ENODEV; + + /* Fetch string properties */ + apple_smc_read(smc, SMC_KEY(BMDN), power->model_name, sizeof(power->model_name) - 1); + apple_smc_read(smc, SMC_KEY(BMSN), power->serial_number, sizeof(power->serial_number) - 1); + apple_smc_read(smc, SMC_KEY(BMDT), power->mfg_date, sizeof(power->mfg_date) - 1); + + /* Turn off the "optimized battery charging" flags, in case macOS left them on */ + apple_smc_write_u8(power->smc, SMC_KEY(CH0K), 0); + apple_smc_write_u8(power->smc, SMC_KEY(CH0B), 0); + + /* + * Prefer CHWA as the SMC firmware from iBoot-10151.1.1 is not compatible with + * this CHLS usage. + */ + if (apple_smc_read_flag(power->smc, SMC_KEY(CHWA), &flag) == 0) { + power->has_chwa = true; + } else if (apple_smc_read_u16(power->smc, SMC_KEY(CHLS), &vu16) >= 0) { + power->has_chls = true; + } else { + /* Remove the last 2 properties that control the charge threshold */ + power->batt_desc.num_properties -= 2; + } + + apple_smc_read_u8(power->smc, SMC_KEY(BNCB), &power->num_cells); + power->nominal_voltage_mv = MACSMC_NOMINAL_CELL_VOLTAGE_MV * power->num_cells; + + /* Doing one read of this flag enables critical shutdown notifications */ + apple_smc_read_u32(power->smc, SMC_KEY(BCF0), &val); + + psy_cfg.drv_data = power; + power->batt = devm_power_supply_register(&pdev->dev, &power->batt_desc, &psy_cfg); + if (IS_ERR(power->batt)) { + dev_err(&pdev->dev, "Failed to register battery\n"); + ret = PTR_ERR(power->batt); + return ret; + } + + /* SMC firmware in macOS 15.4 dropped "AC-i" and "AC-n" (and all keys + * with lower case last letter) without obvious replacement. */ + if (apple_smc_read_u16(power->smc, SMC_KEY(AC-n), &vu16) < 0) + power->ac_desc.num_properties -= 2; + + power->ac = devm_power_supply_register(&pdev->dev, &power->ac_desc, &psy_cfg); + if (IS_ERR(power->ac)) { + dev_err(&pdev->dev, "Failed to register AC adapter\n"); + ret = PTR_ERR(power->ac); + return ret; + } + + power->nb.notifier_call = macsmc_power_event; + blocking_notifier_chain_register(&smc->event_handlers, &power->nb); + + INIT_WORK(&power->critical_work, macsmc_power_critical_work); + + return 0; +} + +static void macsmc_power_remove(struct platform_device *pdev) +{ + struct macsmc_power *power = dev_get_drvdata(&pdev->dev); + + cancel_work(&power->critical_work); + + blocking_notifier_chain_unregister(&power->smc->event_handlers, &power->nb); +} + +static struct platform_driver macsmc_power_driver = { + .driver = { + .name = "macsmc-power", + .owner = THIS_MODULE, + }, + .probe = macsmc_power_probe, + .remove = macsmc_power_remove, +}; +module_platform_driver(macsmc_power_driver); + +MODULE_LICENSE("Dual MIT/GPL"); +MODULE_DESCRIPTION("Apple SMC battery and power management driver"); +MODULE_AUTHOR("Hector Martin "); +MODULE_ALIAS("platform:macsmc-power"); From 7bce9de8f4422bd9661bb717fb04bd7d865064a7 Mon Sep 17 00:00:00 2001 From: Hector Martin Date: Mon, 12 Dec 2022 23:36:17 +0900 Subject: [PATCH 0195/3902] power: supply: macsmc_power: Add a debug mode to print power usage power: supply: macsmc_power: Log power data on button presses This helps catch s2idle power stats, since we get early data when the system resumes due to a power button press. Signed-off-by: Hector Martin --- drivers/power/supply/macsmc-power.c | 136 ++++++++++++++++++++++++++++ 1 file changed, 136 insertions(+) diff --git a/drivers/power/supply/macsmc-power.c b/drivers/power/supply/macsmc-power.c index 575230d57d6dc6..0948ede776cf70 100644 --- a/drivers/power/supply/macsmc-power.c +++ b/drivers/power/supply/macsmc-power.c @@ -50,8 +50,25 @@ struct macsmc_power { struct work_struct critical_work; bool shutdown_started; + + struct delayed_work dbg_log_work; +}; + +static int macsmc_log_power_set(const char *val, const struct kernel_param *kp); + +static const struct kernel_param_ops macsmc_log_power_ops = { + .set = macsmc_log_power_set, + .get = param_get_bool, }; +static bool log_power = false; +module_param_cb(log_power, &macsmc_log_power_ops, &log_power, 0644); +MODULE_PARM_DESC(log_power, "Periodically log power consumption for debugging"); + +#define POWER_LOG_INTERVAL (HZ) + +static struct macsmc_power *g_power; + #define CHNC_BATTERY_FULL BIT(0) #define CHNC_NO_CHARGER BIT(7) #define CHNC_NOCHG_CH0C BIT(14) @@ -82,6 +99,88 @@ struct macsmc_power { #define CHWA_FIXED_END_THRESHOLD 80 #define CHWA_PROP_WRITE_THRESHOLD 95 +#define FLT_EXP_BIAS 127 +#define FLT_EXP_MASK GENMASK(30, 23) +#define FLT_MANT_BIAS 23 +#define FLT_MANT_MASK GENMASK(22, 0) +#define FLT_SIGN_MASK BIT(31) +/* + * Many sensors report their data as IEEE-754 floats. No other SMC function uses + * them. + */ +static int apple_smc_read_f32_scaled(struct apple_smc *smc, smc_key key, + int *p, int scale) +{ + u32 fval; + u64 val; + int ret, exp; + + BUILD_BUG_ON(scale <= 0); + + ret = apple_smc_read_u32(smc, key, &fval); + if (ret < 0) + return ret; + + val = ((u64)((fval & FLT_MANT_MASK) | BIT(23))); + exp = ((fval >> 23) & 0xff) - FLT_EXP_BIAS - FLT_MANT_BIAS; + val *= scale; + + if (exp > 63) + val = U64_MAX; + else if (exp < -63) + val = 0; + else if (exp < 0) + val >>= -exp; + else if (exp != 0 && (val & ~((1UL << (64 - exp)) - 1))) /* overflow */ + val = U64_MAX; + else + val <<= exp; + + if (fval & FLT_SIGN_MASK) { + if (val > (-(s64)INT_MIN)) + *p = INT_MIN; + else + *p = -val; + } else { + if (val > INT_MAX) + *p = INT_MAX; + else + *p = val; + } + + return 0; +} + +static void macsmc_do_dbg(struct macsmc_power *power) +{ + int p_in = 0, p_sys = 0, p_3v8 = 0, p_mpmu = 0, p_spmu = 0, p_clvr = 0, p_cpu = 0; + s32 p_bat = 0; + s16 t_full = 0, t_empty = 0; + u8 charge = 0; + + apple_smc_read_f32_scaled(power->smc, SMC_KEY(PDTR), &p_in, 1000); + apple_smc_read_f32_scaled(power->smc, SMC_KEY(PSTR), &p_sys, 1000); + apple_smc_read_f32_scaled(power->smc, SMC_KEY(PMVR), &p_3v8, 1000); + apple_smc_read_f32_scaled(power->smc, SMC_KEY(PHPC), &p_cpu, 1000); + apple_smc_read_f32_scaled(power->smc, SMC_KEY(PSVR), &p_clvr, 1000); + apple_smc_read_f32_scaled(power->smc, SMC_KEY(PPMC), &p_mpmu, 1000); + apple_smc_read_f32_scaled(power->smc, SMC_KEY(PPSC), &p_spmu, 1000); + apple_smc_read_s32(power->smc, SMC_KEY(B0AP), &p_bat); + apple_smc_read_s16(power->smc, SMC_KEY(B0TE), &t_empty); + apple_smc_read_s16(power->smc, SMC_KEY(B0TF), &t_full); + apple_smc_read_u8(power->smc, SMC_KEY(BUIC), &charge); + +#define FD3(x) ((x) / 1000), abs((x) % 1000) + dev_info(power->dev, + "In %2d.%03dW Sys %2d.%03dW 3V8 %2d.%03dW MPMU %2d.%03dW SPMU %2d.%03dW " + "CLVR %2d.%03dW CPU %2d.%03dW Batt %2d.%03dW %d%% T%s %dm\n", + FD3(p_in), FD3(p_sys), FD3(p_3v8), FD3(p_mpmu), FD3(p_spmu), FD3(p_clvr), + FD3(p_cpu), FD3(p_bat), charge, + t_full >= 0 ? "full" : "empty", + t_full >= 0 ? t_full : t_empty); +#undef FD3 +} + static int macsmc_battery_get_status(struct macsmc_power *power) { u64 nocharge_flags; @@ -610,6 +709,30 @@ static const struct power_supply_desc macsmc_ac_desc = { .num_properties = ARRAY_SIZE(macsmc_ac_props), }; +static int macsmc_log_power_set(const char *val, const struct kernel_param *kp) +{ + int ret = param_set_bool(val, kp); + + if (ret < 0) + return ret; + + if (log_power && g_power) + schedule_delayed_work(&g_power->dbg_log_work, 0); + + return 0; +} + +static void macsmc_dbg_work(struct work_struct *wrk) +{ + struct macsmc_power *power = container_of(to_delayed_work(wrk), + struct macsmc_power, dbg_log_work); + + macsmc_do_dbg(power); + + if (log_power) + schedule_delayed_work(&power->dbg_log_work, POWER_LOG_INTERVAL); +} + static void macsmc_power_critical_work(struct work_struct *wrk) { struct macsmc_power *power = container_of(wrk, struct macsmc_power, critical_work); @@ -699,6 +822,10 @@ static int macsmc_power_event(struct notifier_block *nb, unsigned long event, vo return NOTIFY_OK; } else if ((event & 0xffff0000) == 0x72010000) { + /* Button event handled by macsmc-hid, but let's do a debug print */ + if (log_power) + macsmc_do_dbg(power); + return NOTIFY_OK; } @@ -781,6 +908,12 @@ static int macsmc_power_probe(struct platform_device *pdev) blocking_notifier_chain_register(&smc->event_handlers, &power->nb); INIT_WORK(&power->critical_work, macsmc_power_critical_work); + INIT_DELAYED_WORK(&power->dbg_log_work, macsmc_dbg_work); + + g_power = power; + + if (log_power) + schedule_delayed_work(&power->dbg_log_work, 0); return 0; } @@ -790,6 +923,9 @@ static void macsmc_power_remove(struct platform_device *pdev) struct macsmc_power *power = dev_get_drvdata(&pdev->dev); cancel_work(&power->critical_work); + cancel_delayed_work(&power->dbg_log_work); + + g_power = NULL; blocking_notifier_chain_unregister(&power->smc->event_handlers, &power->nb); } From 212aef7bdb5aaa7ac42fd612e3facb869f4dbdcd Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Thu, 16 Oct 2025 22:44:12 +0200 Subject: [PATCH 0196/3902] fixup! hwmon: Add Apple Silicon SMC hwmon driver Signed-off-by: Janne Grunau --- drivers/hwmon/macsmc-hwmon.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/hwmon/macsmc-hwmon.c b/drivers/hwmon/macsmc-hwmon.c index 342fe3a5ff6245..d0de488393f332 100644 --- a/drivers/hwmon/macsmc-hwmon.c +++ b/drivers/hwmon/macsmc-hwmon.c @@ -20,6 +20,7 @@ * Copyright The Asahi Linux Contributors */ +#include #include #include #include From 03a790d25559781359f1c9696ccb2b8a9e19cbb8 Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Tue, 21 Oct 2025 22:37:15 +0200 Subject: [PATCH 0197/3902] input: macsmc-input: Fix wakeup from s2idle Hard wakeup events are required to wake from s2idle. The comment in [1] to always send wakeup events is correct though. To combine both requirements use pm_wakeup_dev_event() and evaluate the previous conditions for calling pm_wakeup_hard_event() as hard parameters. The remark about always reporting KEY_POWER is only partially correct though. (Some) User space handles that indeed correctly but a system offering a agetty login prompt shuts down immediately after waking from s2idle. 1: https://lore.kernel.org/all/qffp7kadq3xojla5k6f5pr37irgytqfsqvabr6ydvulxnkcgnn@bv5mrraxrhhe/ Signed-off-by: Janne Grunau --- drivers/input/misc/macsmc-input.c | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/drivers/input/misc/macsmc-input.c b/drivers/input/misc/macsmc-input.c index 2c05b2e882c53c..1a583d85566130 100644 --- a/drivers/input/misc/macsmc-input.c +++ b/drivers/input/misc/macsmc-input.c @@ -46,13 +46,16 @@ static void macsmc_input_event_button(struct macsmc_input *smcin, unsigned long switch (button) { case BTN_POWER: case BTN_TOUCHID: - if (smcin->wakeup_mode) { - if (state) - pm_wakeup_event(smcin->dev, 0); - } else { - input_report_key(smcin->input, KEY_POWER, state); - input_sync(smcin->input); - } + pm_wakeup_dev_event(smcin->dev, 0, (smcin->wakeup_mode && state)); + /* + * Suppress KEY_POWER reports when suspended to avoid powering down + * immediately after waking from s2idle. + * */ + if (smcin->wakeup_mode) + return; + + input_report_key(smcin->input, KEY_POWER, state); + input_sync(smcin->input); break; case BTN_POWER_HELD_SHORT: /* power button held down; ignore */ break; @@ -80,9 +83,7 @@ static void macsmc_input_event_lid(struct macsmc_input *smcin, unsigned long eve { u8 lid_state = !!((event >> 8) & 0xff); - if (smcin->wakeup_mode && !lid_state) - pm_wakeup_event(smcin->dev, 0); - + pm_wakeup_dev_event(smcin->dev, 0, (smcin->wakeup_mode && !lid_state)); input_report_switch(smcin->input, SW_LID, lid_state); input_sync(smcin->input); } From fe39760be35ca7bbb9018ad840d97f6a2dd4179d Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Tue, 21 Oct 2025 22:53:52 +0200 Subject: [PATCH 0198/3902] input: macsmc-input: Prefer `true` as boolean literal Signed-off-by: Janne Grunau --- drivers/input/misc/macsmc-input.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/input/misc/macsmc-input.c b/drivers/input/misc/macsmc-input.c index 1a583d85566130..2cead3b7f45fed 100644 --- a/drivers/input/misc/macsmc-input.c +++ b/drivers/input/misc/macsmc-input.c @@ -170,7 +170,7 @@ static int macsmc_input_probe(struct platform_device *pdev) smcin->nb.notifier_call = macsmc_input_event; blocking_notifier_chain_register(&smc->event_handlers, &smcin->nb); - device_init_wakeup(&pdev->dev, 1); + device_init_wakeup(&pdev->dev, true); return 0; } From b3708c25cbe56c8008b67f5952347c1e5b4f1d13 Mon Sep 17 00:00:00 2001 From: Hector Martin Date: Tue, 18 Oct 2022 21:59:24 +0900 Subject: [PATCH 0199/3902] wifi: brcmfmac: Add missing shared area defines to pcie.c There are many newer flags and extended shared area fields used by newer firmwares that are not yet defined. Add them for future usage. Signed-off-by: Hector Martin --- .../broadcom/brcm80211/brcmfmac/pcie.c | 58 +++++++++++++++++++ 1 file changed, 58 insertions(+) diff --git a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/pcie.c b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/pcie.c index 6327f4eca50078..44f722ca69ee63 100644 --- a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/pcie.c +++ b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/pcie.c @@ -219,11 +219,64 @@ static const struct brcmf_firmware_mapping brcmf_pcie_fwnames[] = { #define BRCMF_PCIE_SHARED_VERSION_MASK 0x00FF #define BRCMF_PCIE_SHARED_DMA_INDEX 0x10000 #define BRCMF_PCIE_SHARED_DMA_2B_IDX 0x100000 +#define BRCMF_PCIE_SHARED_USE_MAILBOX 0x2000000 +#define BRCMF_PCIE_SHARED_TIMESTAMP_DB0 0x8000000 #define BRCMF_PCIE_SHARED_HOSTRDY_DB1 0x10000000 +#define BRCMF_PCIE_SHARED_NO_OOB_DW 0x20000000 +#define BRCMF_PCIE_SHARED_INBAND_DS 0x40000000 +#define BRCMF_PCIE_SHARED_DAR 0x80000000 + +#define BRCMF_PCIE_SHARED2_EXTENDED_TRAP_DATA 0x1 +#define BRCMF_PCIE_SHARED2_TXSTATUS_METADATA 0x2 +#define BRCMF_PCIE_SHARED2_BT_LOGGING 0x4 +#define BRCMF_PCIE_SHARED2_SNAPSHOT_UPLOAD 0x8 +#define BRCMF_PCIE_SHARED2_SUBMIT_COUNT_WAR 0x10 +#define BRCMF_PCIE_SHARED2_FAST_DELETE_RING 0x20 +#define BRCMF_PCIE_SHARED2_EVTBUF_MAX_MASK 0xC0 +#define BRCMF_PCIE_SHARED2_PKT_TX_STATUS 0x100 +#define BRCMF_PCIE_SHARED2_FW_SMALL_MEMDUMP 0x200 +#define BRCMF_PCIE_SHARED2_FW_HC_ON_TRAP 0x400 +#define BRCMF_PCIE_SHARED2_HSCB 0x800 +#define BRCMF_PCIE_SHARED2_EDL_RING 0x1000 +#define BRCMF_PCIE_SHARED2_DEBUG_BUF_DEST 0x2000 +#define BRCMF_PCIE_SHARED2_PCIE_ENUM_RESET_FLR 0x4000 +#define BRCMF_PCIE_SHARED2_PKT_TIMESTAMP 0x8000 +#define BRCMF_PCIE_SHARED2_HP2P 0x10000 +#define BRCMF_PCIE_SHARED2_HWA 0x20000 +#define BRCMF_PCIE_SHARED2_TRAP_ON_HOST_DB7 0x40000 +#define BRCMF_PCIE_SHARED2_DURATION_SCALE 0x100000 +#define BRCMF_PCIE_SHARED2_D2H_D11_TX_STATUS 0x40000000 +#define BRCMF_PCIE_SHARED2_H2D_D11_TX_STATUS 0x80000000 #define BRCMF_PCIE_FLAGS_HTOD_SPLIT 0x4000 #define BRCMF_PCIE_FLAGS_DTOH_SPLIT 0x8000 +#define BRCMF_HOSTCAP_PCIEAPI_VERSION_MASK 0x000000FF +#define BRCMF_HOSTCAP_H2D_VALID_PHASE 0x00000100 +#define BRCMF_HOSTCAP_H2D_ENABLE_TRAP_ON_BADPHASE 0x00000200 +#define BRCMF_HOSTCAP_H2D_ENABLE_HOSTRDY 0x400 +#define BRCMF_HOSTCAP_DB0_TIMESTAMP 0x800 +#define BRCMF_HOSTCAP_DS_NO_OOB_DW 0x1000 +#define BRCMF_HOSTCAP_DS_INBAND_DW 0x2000 +#define BRCMF_HOSTCAP_H2D_IDMA 0x4000 +#define BRCMF_HOSTCAP_H2D_IFRM 0x8000 +#define BRCMF_HOSTCAP_H2D_DAR 0x10000 +#define BRCMF_HOSTCAP_EXTENDED_TRAP_DATA 0x20000 +#define BRCMF_HOSTCAP_TXSTATUS_METADATA 0x40000 +#define BRCMF_HOSTCAP_BT_LOGGING 0x80000 +#define BRCMF_HOSTCAP_SNAPSHOT_UPLOAD 0x100000 +#define BRCMF_HOSTCAP_FAST_DELETE_RING 0x200000 +#define BRCMF_HOSTCAP_PKT_TXSTATUS 0x400000 +#define BRCMF_HOSTCAP_UR_FW_NO_TRAP 0x800000 +#define BRCMF_HOSTCAP_HSCB 0x2000000 +#define BRCMF_HOSTCAP_EXT_TRAP_DBGBUF 0x4000000 +#define BRCMF_HOSTCAP_EDL_RING 0x10000000 +#define BRCMF_HOSTCAP_PKT_TIMESTAMP 0x20000000 +#define BRCMF_HOSTCAP_PKT_HP2P 0x40000000 +#define BRCMF_HOSTCAP_HWA 0x80000000 +#define BRCMF_HOSTCAP2_DURATION_SCALE_MASK 0x3F + +#define BRCMF_SHARED_FLAGS_OFFSET 0 #define BRCMF_SHARED_MAX_RXBUFPOST_OFFSET 34 #define BRCMF_SHARED_RING_BASE_OFFSET 52 #define BRCMF_SHARED_RX_DATAOFFSET_OFFSET 36 @@ -235,6 +288,11 @@ static const struct brcmf_firmware_mapping brcmf_pcie_fwnames[] = { #define BRCMF_SHARED_DMA_SCRATCH_ADDR_OFFSET 56 #define BRCMF_SHARED_DMA_RINGUPD_LEN_OFFSET 64 #define BRCMF_SHARED_DMA_RINGUPD_ADDR_OFFSET 68 +#define BRCMF_SHARED_FLAGS2_OFFSET 80 +#define BRCMF_SHARED_HOST_CAP_OFFSET 84 +#define BRCMF_SHARED_FLAGS3_OFFSET 108 +#define BRCMF_SHARED_HOST_CAP2_OFFSET 112 +#define BRCMF_SHARED_HOST_CAP3_OFFSET 116 #define BRCMF_RING_H2D_RING_COUNT_OFFSET 0 #define BRCMF_RING_D2H_RING_COUNT_OFFSET 1 From cbff002b2dfe0d039c4891d52465cd26567fa9e9 Mon Sep 17 00:00:00 2001 From: Hector Martin Date: Tue, 18 Oct 2022 22:02:10 +0900 Subject: [PATCH 0200/3902] wifi: brcmfmac: Handle PCIe MSI properly On newer firmwares under at least certain conditions, MSI mode does not leave interrupt flags set (they are cleared by the firmware). Handle this by always checking for ring data when we get an MSI, regardless of whether any IRQ flags were set. Signed-off-by: Hector Martin --- .../broadcom/brcm80211/brcmfmac/pcie.c | 21 +++++++++++++------ 1 file changed, 15 insertions(+), 6 deletions(-) diff --git a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/pcie.c b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/pcie.c index 44f722ca69ee63..8c13a38d4ab21c 100644 --- a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/pcie.c +++ b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/pcie.c @@ -405,6 +405,7 @@ struct brcmf_pciedev_info { wait_queue_head_t mbdata_resp_wait; bool mbdata_completed; bool irq_allocated; + bool have_msi; bool wowl_enabled; u8 dma_idx_sz; void *idxbuf; @@ -992,6 +993,11 @@ static irqreturn_t brcmf_pcie_quick_check_isr(int irq, void *arg) brcmf_dbg(PCIE, "Enter\n"); return IRQ_WAKE_THREAD; } + + /* mailboxint is cleared by the firmware in MSI mode */ + if (devinfo->have_msi) + return IRQ_WAKE_THREAD; + return IRQ_NONE; } @@ -1009,12 +1015,12 @@ static irqreturn_t brcmf_pcie_isr_thread(int irq, void *arg) status); if (status & devinfo->reginfo->int_fn0) brcmf_pcie_handle_mb_data(devinfo); - if (status & devinfo->reginfo->int_d2h_db) { - if (devinfo->state == BRCMFMAC_PCIE_STATE_UP) - brcmf_proto_msgbuf_rx_trigger( - &devinfo->pdev->dev); - } } + if (devinfo->have_msi || status & devinfo->reginfo->int_d2h_db) { + if (devinfo->state == BRCMFMAC_PCIE_STATE_UP) + brcmf_proto_msgbuf_rx_trigger(&devinfo->pdev->dev); + } + brcmf_pcie_bus_console_read(devinfo, false); if (devinfo->state == BRCMFMAC_PCIE_STATE_UP) brcmf_pcie_intr_enable(devinfo); @@ -1032,7 +1038,10 @@ static int brcmf_pcie_request_irq(struct brcmf_pciedev_info *devinfo) brcmf_dbg(PCIE, "Enter\n"); - pci_enable_msi(pdev); + devinfo->have_msi = pci_enable_msi(pdev) >= 0; + if (devinfo->have_msi) + brcmf_dbg(PCIE, "MSI enabled\n"); + if (request_threaded_irq(pdev->irq, brcmf_pcie_quick_check_isr, brcmf_pcie_isr_thread, IRQF_SHARED, "brcmf_pcie_intr", devinfo)) { From 110da1c8cf7ac2682d5c27c79604c2cafb333579 Mon Sep 17 00:00:00 2001 From: Hector Martin Date: Tue, 18 Oct 2022 22:06:40 +0900 Subject: [PATCH 0201/3902] wifi: brcmfmac: Fix logic for deciding which doorbell registers to use While the other >PCIe r64 registers (which are apparently called DAR registers) are always used on newer revisions, which doorbell registers should be used depends only on flags set by firmware. Take them out of the reginfo struct and check the flag to decide instead. Signed-off-by: Hector Martin --- .../broadcom/brcm80211/brcmfmac/pcie.c | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/pcie.c b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/pcie.c index 8c13a38d4ab21c..65a02023bf83ce 100644 --- a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/pcie.c +++ b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/pcie.c @@ -492,8 +492,6 @@ struct brcmf_pcie_reginfo { u32 intmask; u32 mailboxint; u32 mailboxmask; - u32 h2d_mailbox_0; - u32 h2d_mailbox_1; u32 int_d2h_db; u32 int_fn0; }; @@ -502,8 +500,6 @@ static const struct brcmf_pcie_reginfo brcmf_reginfo_default = { .intmask = BRCMF_PCIE_PCIE2REG_INTMASK, .mailboxint = BRCMF_PCIE_PCIE2REG_MAILBOXINT, .mailboxmask = BRCMF_PCIE_PCIE2REG_MAILBOXMASK, - .h2d_mailbox_0 = BRCMF_PCIE_PCIE2REG_H2D_MAILBOX_0, - .h2d_mailbox_1 = BRCMF_PCIE_PCIE2REG_H2D_MAILBOX_1, .int_d2h_db = BRCMF_PCIE_MB_INT_D2H_DB, .int_fn0 = BRCMF_PCIE_MB_INT_FN0, }; @@ -512,8 +508,6 @@ static const struct brcmf_pcie_reginfo brcmf_reginfo_64 = { .intmask = BRCMF_PCIE_64_PCIE2REG_INTMASK, .mailboxint = BRCMF_PCIE_64_PCIE2REG_MAILBOXINT, .mailboxmask = BRCMF_PCIE_64_PCIE2REG_MAILBOXMASK, - .h2d_mailbox_0 = BRCMF_PCIE_64_PCIE2REG_H2D_MAILBOX_0, - .h2d_mailbox_1 = BRCMF_PCIE_64_PCIE2REG_H2D_MAILBOX_1, .int_d2h_db = BRCMF_PCIE_64_MB_INT_D2H_DB, .int_fn0 = 0, }; @@ -979,9 +973,12 @@ static void brcmf_pcie_intr_enable(struct brcmf_pciedev_info *devinfo) static void brcmf_pcie_hostready(struct brcmf_pciedev_info *devinfo) { - if (devinfo->shared.flags & BRCMF_PCIE_SHARED_HOSTRDY_DB1) - brcmf_pcie_write_reg32(devinfo, - devinfo->reginfo->h2d_mailbox_1, 1); + if (devinfo->shared.flags & BRCMF_PCIE_SHARED_HOSTRDY_DB1) { + if (devinfo->shared.flags & BRCMF_PCIE_SHARED_DAR) + brcmf_pcie_write_reg32(devinfo, BRCMF_PCIE_64_PCIE2REG_H2D_MAILBOX_1, 1); + else + brcmf_pcie_write_reg32(devinfo, BRCMF_PCIE_PCIE2REG_H2D_MAILBOX_1, 1); + } } static irqreturn_t brcmf_pcie_quick_check_isr(int irq, void *arg) @@ -1130,7 +1127,10 @@ static int brcmf_pcie_ring_mb_ring_bell(void *ctx) brcmf_dbg(PCIE, "RING !\n"); /* Any arbitrary value will do, lets use 1 */ - brcmf_pcie_write_reg32(devinfo, devinfo->reginfo->h2d_mailbox_0, 1); + if (devinfo->shared.flags & BRCMF_PCIE_SHARED_DAR) + brcmf_pcie_write_reg32(devinfo, BRCMF_PCIE_64_PCIE2REG_H2D_MAILBOX_0, 1); + else + brcmf_pcie_write_reg32(devinfo, BRCMF_PCIE_PCIE2REG_H2D_MAILBOX_0, 1); return 0; } From 21c917678a05645470286d2052ce0714ed482fcd Mon Sep 17 00:00:00 2001 From: Hector Martin Date: Tue, 18 Oct 2022 22:10:08 +0900 Subject: [PATCH 0202/3902] wifi: brcmfmac: Support v6+ flags and set host_cap properly Interface versions 6 and above support having the host tell the dongle about what it supports via a host_cap field (it seems that if it is set to zero, some kind of unknown defaults are used). Explicitly support and set this. This also disables OOB deep sleep support; it doesn't look like deep sleep is properly supported yet at all (it needs more logic than merely acking requests, which is all pcie.c does right now). Signed-off-by: Hector Martin --- .../broadcom/brcm80211/brcmfmac/pcie.c | 35 ++++++++++++++++++- 1 file changed, 34 insertions(+), 1 deletion(-) diff --git a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/pcie.c b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/pcie.c index 65a02023bf83ce..a3b877f3d6eb43 100644 --- a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/pcie.c +++ b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/pcie.c @@ -357,6 +357,8 @@ struct brcmf_pcie_console { struct brcmf_pcie_shared_info { u32 tcm_base_address; u32 flags; + u32 flags2; + u32 flags3; struct brcmf_pcie_ringbuf *commonrings[BRCMF_NROF_COMMON_MSGRINGS]; struct brcmf_pcie_ringbuf *flowrings; u16 max_rxbufpost; @@ -1687,12 +1689,16 @@ brcmf_pcie_init_share_ram_info(struct brcmf_pciedev_info *devinfo, { struct brcmf_bus *bus = dev_get_drvdata(&devinfo->pdev->dev); struct brcmf_pcie_shared_info *shared; + u32 host_cap; + u32 host_cap2; u32 addr; shared = &devinfo->shared; shared->tcm_base_address = sharedram_addr; - shared->flags = brcmf_pcie_read_tcm32(devinfo, sharedram_addr); + shared->flags = brcmf_pcie_read_tcm32(devinfo, sharedram_addr + + BRCMF_SHARED_FLAGS_OFFSET); + shared->version = (u8)(shared->flags & BRCMF_PCIE_SHARED_VERSION_MASK); brcmf_dbg(PCIE, "PCIe protocol version %d\n", shared->version); if ((shared->version > BRCMF_PCIE_MAX_SHARED_VERSION) || @@ -1733,6 +1739,33 @@ brcmf_pcie_init_share_ram_info(struct brcmf_pciedev_info *devinfo, brcmf_pcie_bus_console_init(devinfo); brcmf_pcie_bus_console_read(devinfo, false); + /* Features added in revision 6 follow */ + if (shared->version < 6) + return 0; + + shared->flags2 = brcmf_pcie_read_tcm32(devinfo, sharedram_addr + + BRCMF_SHARED_FLAGS2_OFFSET); + shared->flags3 = brcmf_pcie_read_tcm32(devinfo, sharedram_addr + + BRCMF_SHARED_FLAGS3_OFFSET); + + /* Update host support flags */ + host_cap = shared->version; + host_cap2 = 0; + + if (shared->flags & BRCMF_PCIE_SHARED_HOSTRDY_DB1) + host_cap |= BRCMF_HOSTCAP_H2D_ENABLE_HOSTRDY; + + if (shared->flags & BRCMF_PCIE_SHARED_DAR) + host_cap |= BRCMF_HOSTCAP_H2D_DAR; + + /* Disable DS: this is not currently properly supported */ + host_cap |= BRCMF_HOSTCAP_DS_NO_OOB_DW; + + brcmf_pcie_write_tcm32(devinfo, sharedram_addr + + BRCMF_SHARED_HOST_CAP_OFFSET, host_cap); + brcmf_pcie_write_tcm32(devinfo, sharedram_addr + + BRCMF_SHARED_HOST_CAP2_OFFSET, host_cap2); + return 0; } From 68696efb6e0988551ced97f42c9841dec3bc31a8 Mon Sep 17 00:00:00 2001 From: Hector Martin Date: Tue, 18 Oct 2022 21:55:52 +0900 Subject: [PATCH 0203/3902] wifi: brcmfmac: Add newer msgbuf packet types up to 0x2e There are many newer msgbuf packet types that are not yet listed in the defines in msgbuf.c. Add them for future use. Signed-off-by: Hector Martin --- .../broadcom/brcm80211/brcmfmac/msgbuf.c | 26 +++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/msgbuf.c b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/msgbuf.c index 45fbcbdc7d9e4b..4405451b0c59a4 100644 --- a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/msgbuf.c +++ b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/msgbuf.c @@ -47,6 +47,32 @@ #define MSGBUF_TYPE_RX_CMPLT 0x12 #define MSGBUF_TYPE_LPBK_DMAXFER 0x13 #define MSGBUF_TYPE_LPBK_DMAXFER_CMPLT 0x14 +#define MSGBUF_TYPE_FLOW_RING_RESUME 0x15 +#define MSGBUF_TYPE_FLOW_RING_RESUME_CMPLT 0x16 +#define MSGBUF_TYPE_FLOW_RING_SUSPEND 0x17 +#define MSGBUF_TYPE_FLOW_RING_SUSPEND_CMPLT 0x18 +#define MSGBUF_TYPE_INFO_BUF_POST 0x19 +#define MSGBUF_TYPE_INFO_BUF_CMPLT 0x1A +#define MSGBUF_TYPE_H2D_RING_CREATE 0x1B +#define MSGBUF_TYPE_D2H_RING_CREATE 0x1C +#define MSGBUF_TYPE_H2D_RING_CREATE_CMPLT 0x1D +#define MSGBUF_TYPE_D2H_RING_CREATE_CMPLT 0x1E +#define MSGBUF_TYPE_H2D_RING_CONFIG 0x1F +#define MSGBUF_TYPE_D2H_RING_CONFIG 0x20 +#define MSGBUF_TYPE_H2D_RING_CONFIG_CMPLT 0x21 +#define MSGBUF_TYPE_D2H_RING_CONFIG_CMPLT 0x22 +#define MSGBUF_TYPE_H2D_MAILBOX_DATA 0x23 +#define MSGBUF_TYPE_D2H_MAILBOX_DATA 0x24 +#define MSGBUF_TYPE_TIMSTAMP_BUFPOST 0x25 +#define MSGBUF_TYPE_HOSTTIMSTAMP 0x26 +#define MSGBUF_TYPE_HOSTTIMSTAMP_CMPLT 0x27 +#define MSGBUF_TYPE_FIRMWARE_TIMESTAMP 0x28 +#define MSGBUF_TYPE_SNAPSHOT_UPLOAD 0x29 +#define MSGBUF_TYPE_SNAPSHOT_CMPLT 0x2A +#define MSGBUF_TYPE_H2D_RING_DELETE 0x2B +#define MSGBUF_TYPE_D2H_RING_DELETE 0x2C +#define MSGBUF_TYPE_H2D_RING_DELETE_CMPLT 0x2D +#define MSGBUF_TYPE_D2H_RING_DELETE_CMPLT 0x2E #define NR_TX_PKTIDS 2048 #define NR_RX_PKTIDS 1024 From 6aa1e10644ff4adcff936e8f2dda4d89a071b7ff Mon Sep 17 00:00:00 2001 From: Hector Martin Date: Tue, 18 Oct 2022 21:56:53 +0900 Subject: [PATCH 0204/3902] wifi: brcmfmac: Add a new bus op for D2H mailbox message handling Newer firmware versions use the common ring for sending mailbox messages between the dongle and host, instead of the hardware mailboxes. This needs the protocol driver to call back into the bus driver, so add a callback for this to bus.h. Signed-off-by: Hector Martin --- drivers/net/wireless/broadcom/brcm80211/brcmfmac/bus.h | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/bus.h b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/bus.h index fe31051a9e11b1..5efd7f6d757a4c 100644 --- a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/bus.h +++ b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/bus.h @@ -107,6 +107,7 @@ struct brcmf_bus_ops { void (*debugfs_create)(struct device *dev); int (*reset)(struct device *dev); void (*remove)(struct device *dev); + void (*d2h_mb_rx)(struct device *dev, u32 data); }; @@ -286,6 +287,15 @@ static inline void brcmf_bus_remove(struct brcmf_bus *bus) bus->ops->remove(bus->dev); } +static inline +void brcmf_bus_d2h_mb_rx(struct brcmf_bus *bus, u32 data) +{ + if (!bus->ops->d2h_mb_rx) + return; + + return bus->ops->d2h_mb_rx(bus->dev, data); +} + /* * interface functions from common layer */ From ffbe832e5d28e3afd11866bccaa740d2f1f21554 Mon Sep 17 00:00:00 2001 From: Hector Martin Date: Tue, 18 Oct 2022 21:58:21 +0900 Subject: [PATCH 0205/3902] wifi: brcmfmac: Implement the H2D/D2H mailbox data commonring messages Newer firmware versions use these to exchange mailbox data, instead of the hardware mailbox registers. Add handling for them to msgbuf.c. Signed-off-by: Hector Martin --- .../broadcom/brcm80211/brcmfmac/msgbuf.c | 59 +++++++++++++++++++ .../broadcom/brcm80211/brcmfmac/msgbuf.h | 1 + 2 files changed, 60 insertions(+) diff --git a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/msgbuf.c b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/msgbuf.c index 4405451b0c59a4..93206850373300 100644 --- a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/msgbuf.c +++ b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/msgbuf.c @@ -244,6 +244,19 @@ struct msgbuf_flowring_flush_resp { __le32 rsvd0[3]; }; +struct msgbuf_h2d_mailbox_data { + struct msgbuf_common_hdr msg; + __le32 data; + __le32 rsvd0[7]; +}; + +struct msgbuf_d2h_mailbox_data { + struct msgbuf_common_hdr msg; + struct msgbuf_completion_hdr compl_hdr; + __le32 data; + __le32 rsvd0[2]; +}; + struct brcmf_msgbuf_work_item { struct list_head queue; u32 flowid; @@ -1311,6 +1324,16 @@ brcmf_msgbuf_process_flow_ring_delete_response(struct brcmf_msgbuf *msgbuf, } +static void brcmf_msgbuf_process_d2h_mailbox_data(struct brcmf_msgbuf *msgbuf, + void *buf) +{ + struct msgbuf_d2h_mailbox_data *d2h_mb_data = buf; + struct brcmf_pub *drvr = msgbuf->drvr; + + brcmf_bus_d2h_mb_rx(drvr->bus_if, le32_to_cpu(d2h_mb_data->data)); +} + + static void brcmf_msgbuf_process_msgtype(struct brcmf_msgbuf *msgbuf, void *buf) { struct brcmf_pub *drvr = msgbuf->drvr; @@ -1353,6 +1376,10 @@ static void brcmf_msgbuf_process_msgtype(struct brcmf_msgbuf *msgbuf, void *buf) brcmf_dbg(MSGBUF, "MSGBUF_TYPE_RX_CMPLT\n"); brcmf_msgbuf_process_rx_complete(msgbuf, buf); break; + case MSGBUF_TYPE_D2H_MAILBOX_DATA: + brcmf_dbg(MSGBUF, "MSGBUF_TYPE_D2H_MAILBOX_DATA\n"); + brcmf_msgbuf_process_d2h_mailbox_data(msgbuf, buf); + break; default: bphy_err(drvr, "Unsupported msgtype %d\n", msg->msgtype); break; @@ -1491,6 +1518,38 @@ void brcmf_msgbuf_delete_flowring(struct brcmf_pub *drvr, u16 flowid) } } + +int brcmf_msgbuf_h2d_mb_write(struct brcmf_pub *drvr, u32 data) +{ + struct brcmf_msgbuf *msgbuf = (struct brcmf_msgbuf *)drvr->proto->pd; + struct brcmf_commonring *commonring; + struct msgbuf_h2d_mailbox_data *request; + void *ret_ptr; + int err; + + commonring = msgbuf->commonrings[BRCMF_H2D_MSGRING_CONTROL_SUBMIT]; + brcmf_commonring_lock(commonring); + ret_ptr = brcmf_commonring_reserve_for_write(commonring); + if (!ret_ptr) { + bphy_err(drvr, "Failed to reserve space in commonring\n"); + brcmf_commonring_unlock(commonring); + return -ENOMEM; + } + + request = (struct msgbuf_h2d_mailbox_data *)ret_ptr; + request->msg.msgtype = MSGBUF_TYPE_H2D_MAILBOX_DATA; + request->msg.ifidx = -1; + request->msg.flags = 0; + request->msg.request_id = 0; + request->data = data; + + err = brcmf_commonring_write_complete(commonring); + brcmf_commonring_unlock(commonring); + + return err; +} + + #ifdef DEBUG static int brcmf_msgbuf_stats_read(struct seq_file *seq, void *data) { diff --git a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/msgbuf.h b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/msgbuf.h index 6a849f4a94dd7f..89b6b7f9ddb748 100644 --- a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/msgbuf.h +++ b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/msgbuf.h @@ -32,6 +32,7 @@ int brcmf_proto_msgbuf_rx_trigger(struct device *dev); void brcmf_msgbuf_delete_flowring(struct brcmf_pub *drvr, u16 flowid); int brcmf_proto_msgbuf_attach(struct brcmf_pub *drvr); void brcmf_proto_msgbuf_detach(struct brcmf_pub *drvr); +int brcmf_msgbuf_h2d_mb_write(struct brcmf_pub *drvr, u32 data); #else static inline int brcmf_proto_msgbuf_attach(struct brcmf_pub *drvr) { From 65944c6e47133e481467a5febd37efc8ac8afc0e Mon Sep 17 00:00:00 2001 From: Hector Martin Date: Tue, 18 Oct 2022 22:12:15 +0900 Subject: [PATCH 0206/3902] wifi: brcmfmac: Support exchanging power mailbox messages via commonring Newer firmwares have switched from using the hardware mailbox to commonring messages for power mailbox data. Implement this, which makes D3 work on WiFi chipsets in Apple devices. This is only enabled on v6 or newer, iff BRCMF_PCIE_SHARED_USE_MAILBOX is not set in the flags. Signed-off-by: Hector Martin --- .../broadcom/brcm80211/brcmfmac/pcie.c | 75 ++++++++++++++----- 1 file changed, 55 insertions(+), 20 deletions(-) diff --git a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/pcie.c b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/pcie.c index a3b877f3d6eb43..6101d4cc204be0 100644 --- a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/pcie.c +++ b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/pcie.c @@ -375,6 +375,7 @@ struct brcmf_pcie_shared_info { void *ringupd; dma_addr_t ringupd_dmahandle; u8 version; + bool mb_via_ctl; }; #define BRCMF_OTP_MAX_PARAM_LEN 16 @@ -824,6 +825,19 @@ brcmf_pcie_send_mb_data(struct brcmf_pciedev_info *devinfo, u32 htod_mb_data) u32 i; shared = &devinfo->shared; + + if (shared->mb_via_ctl) { + struct pci_dev *pdev = devinfo->pdev; + struct brcmf_bus *bus = dev_get_drvdata(&pdev->dev); + int ret; + + ret = brcmf_msgbuf_h2d_mb_write(bus->drvr, htod_mb_data); + if (ret < 0) + brcmf_err(bus, "Failed to send H2D mailbox data (%d)\n", + ret); + return ret; + } + addr = shared->htod_mb_data_addr; cur_htod_mb_data = brcmf_pcie_read_tcm32(devinfo, addr); @@ -851,8 +865,29 @@ brcmf_pcie_send_mb_data(struct brcmf_pciedev_info *devinfo, u32 htod_mb_data) return 0; } +static void brcmf_pcie_handle_mb_data(struct brcmf_pciedev_info *devinfo, u32 data) +{ + brcmf_dbg(PCIE, "D2H_MB_DATA: 0x%04x\n", data); + if (data & BRCMF_D2H_DEV_DS_ENTER_REQ) { + brcmf_dbg(PCIE, "D2H_MB_DATA: DEEP SLEEP REQ\n"); + brcmf_pcie_send_mb_data(devinfo, BRCMF_H2D_HOST_DS_ACK); + brcmf_dbg(PCIE, "D2H_MB_DATA: sent DEEP SLEEP ACK\n"); + } + if (data & BRCMF_D2H_DEV_DS_EXIT_NOTE) + brcmf_dbg(PCIE, "D2H_MB_DATA: DEEP SLEEP EXIT\n"); + if (data & BRCMF_D2H_DEV_D3_ACK) { + brcmf_dbg(PCIE, "D2H_MB_DATA: D3 ACK\n"); + devinfo->mbdata_completed = true; + wake_up(&devinfo->mbdata_resp_wait); + } + if (data & BRCMF_D2H_DEV_FWHALT) { + brcmf_dbg(PCIE, "D2H_MB_DATA: FW HALT\n"); + brcmf_fw_crashed(&devinfo->pdev->dev); + } +} + -static void brcmf_pcie_handle_mb_data(struct brcmf_pciedev_info *devinfo) +static void brcmf_pcie_poll_mb_data(struct brcmf_pciedev_info *devinfo) { struct brcmf_pcie_shared_info *shared; u32 addr; @@ -867,23 +902,16 @@ static void brcmf_pcie_handle_mb_data(struct brcmf_pciedev_info *devinfo) brcmf_pcie_write_tcm32(devinfo, addr, 0); - brcmf_dbg(PCIE, "D2H_MB_DATA: 0x%04x\n", dtoh_mb_data); - if (dtoh_mb_data & BRCMF_D2H_DEV_DS_ENTER_REQ) { - brcmf_dbg(PCIE, "D2H_MB_DATA: DEEP SLEEP REQ\n"); - brcmf_pcie_send_mb_data(devinfo, BRCMF_H2D_HOST_DS_ACK); - brcmf_dbg(PCIE, "D2H_MB_DATA: sent DEEP SLEEP ACK\n"); - } - if (dtoh_mb_data & BRCMF_D2H_DEV_DS_EXIT_NOTE) - brcmf_dbg(PCIE, "D2H_MB_DATA: DEEP SLEEP EXIT\n"); - if (dtoh_mb_data & BRCMF_D2H_DEV_D3_ACK) { - brcmf_dbg(PCIE, "D2H_MB_DATA: D3 ACK\n"); - devinfo->mbdata_completed = true; - wake_up(&devinfo->mbdata_resp_wait); - } - if (dtoh_mb_data & BRCMF_D2H_DEV_FWHALT) { - brcmf_dbg(PCIE, "D2H_MB_DATA: FW HALT\n"); - brcmf_fw_crashed(&devinfo->pdev->dev); - } + brcmf_pcie_handle_mb_data(devinfo, dtoh_mb_data); +} + + +static void brcmf_pcie_d2h_mb_rx(struct device *dev, u32 data) +{ + struct brcmf_bus *bus_if = dev_get_drvdata(dev); + struct brcmf_pciedev *buspub = bus_if->bus_priv.pcie; + + brcmf_pcie_handle_mb_data(buspub->devinfo, data); } @@ -1013,7 +1041,7 @@ static irqreturn_t brcmf_pcie_isr_thread(int irq, void *arg) brcmf_pcie_write_reg32(devinfo, devinfo->reginfo->mailboxint, status); if (status & devinfo->reginfo->int_fn0) - brcmf_pcie_handle_mb_data(devinfo); + brcmf_pcie_poll_mb_data(devinfo); } if (devinfo->have_msi || status & devinfo->reginfo->int_d2h_db) { if (devinfo->state == BRCMFMAC_PCIE_STATE_UP) @@ -1658,6 +1686,7 @@ static const struct brcmf_bus_ops brcmf_pcie_bus_ops = { .get_blob = brcmf_pcie_get_blob, .reset = brcmf_pcie_reset, .debugfs_create = brcmf_pcie_debugfs_create, + .d2h_mb_rx = brcmf_pcie_d2h_mb_rx, }; @@ -1748,6 +1777,10 @@ brcmf_pcie_init_share_ram_info(struct brcmf_pciedev_info *devinfo, shared->flags3 = brcmf_pcie_read_tcm32(devinfo, sharedram_addr + BRCMF_SHARED_FLAGS3_OFFSET); + /* Check which mailbox mechanism to use */ + if (!(shared->flags & BRCMF_PCIE_SHARED_USE_MAILBOX)) + shared->mb_via_ctl = true; + /* Update host support flags */ host_cap = shared->version; host_cap2 = 0; @@ -2770,10 +2803,11 @@ static int brcmf_pcie_pm_leave_D3(struct device *dev) /* Check if device is still up and running, if so we are ready */ if (brcmf_pcie_read_reg32(devinfo, devinfo->reginfo->intmask) != 0) { brcmf_dbg(PCIE, "Try to wakeup device....\n"); + /* Set the device up, so we can write the MB data message in ring mode */ + devinfo->state = BRCMFMAC_PCIE_STATE_UP; if (brcmf_pcie_send_mb_data(devinfo, BRCMF_H2D_HOST_D0_INFORM)) goto cleanup; brcmf_dbg(PCIE, "Hot resume, continue....\n"); - devinfo->state = BRCMFMAC_PCIE_STATE_UP; brcmf_pcie_select_core(devinfo, BCMA_CORE_PCIE2); brcmf_bus_change_state(bus, BRCMF_BUS_UP); brcmf_pcie_intr_enable(devinfo); @@ -2783,6 +2817,7 @@ static int brcmf_pcie_pm_leave_D3(struct device *dev) } cleanup: + devinfo->state = BRCMFMAC_PCIE_STATE_DOWN; brcmf_chip_detach(devinfo->ci); devinfo->ci = NULL; pdev = devinfo->pdev; From eb173299b92b63eaa7c2879e64f82ae69aaf1370 Mon Sep 17 00:00:00 2001 From: Hector Martin Date: Sat, 25 Mar 2023 15:23:04 +0900 Subject: [PATCH 0207/3902] wifi: brcmfmac: Shut up p2p unknown frame error People keep complaining about this and think their wifi is broken for some reason... Signed-off-by: Hector Martin --- drivers/net/wireless/broadcom/brcm80211/brcmfmac/p2p.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/p2p.c b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/p2p.c index e1752a513c733d..06d2933162b26f 100644 --- a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/p2p.c +++ b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/p2p.c @@ -1784,8 +1784,8 @@ bool brcmf_p2p_send_action_frame(struct brcmf_if *ifp, /* do not configure anything. it will be */ /* sent with a default configuration */ } else { - bphy_err(drvr, "Unknown Frame: category 0x%x, action 0x%x\n", - category, action); + bphy_info_once(drvr, "Unknown Frame: category 0x%x, action 0x%x\n", + category, action); return false; } From a48f05b10beae40f194de5d996e0cb62fd5cde5b Mon Sep 17 00:00:00 2001 From: Hector Martin Date: Tue, 6 Jun 2023 15:53:23 +0900 Subject: [PATCH 0208/3902] wifi: brcmfmac: Do not service msgbuf IRQs until ready in MSI mode This is the counterpart to b50255c83b. In MSI mode we can still get MSIs even with IRQs disabled, so add an explicit gate for it. Signed-off-by: Hector Martin --- drivers/net/wireless/broadcom/brcm80211/brcmfmac/pcie.c | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/pcie.c b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/pcie.c index 6101d4cc204be0..917d8082fbbdce 100644 --- a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/pcie.c +++ b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/pcie.c @@ -408,6 +408,7 @@ struct brcmf_pciedev_info { wait_queue_head_t mbdata_resp_wait; bool mbdata_completed; bool irq_allocated; + bool irq_ready; bool have_msi; bool wowl_enabled; u8 dma_idx_sz; @@ -991,6 +992,8 @@ static void brcmf_pcie_bus_console_read(struct brcmf_pciedev_info *devinfo, static void brcmf_pcie_intr_disable(struct brcmf_pciedev_info *devinfo) { brcmf_pcie_write_reg32(devinfo, devinfo->reginfo->mailboxmask, 0); + + devinfo->irq_ready = false; } @@ -999,6 +1002,8 @@ static void brcmf_pcie_intr_enable(struct brcmf_pciedev_info *devinfo) brcmf_pcie_write_reg32(devinfo, devinfo->reginfo->mailboxmask, devinfo->reginfo->int_d2h_db | devinfo->reginfo->int_fn0); + + devinfo->irq_ready = true; } static void brcmf_pcie_hostready(struct brcmf_pciedev_info *devinfo) @@ -1044,7 +1049,7 @@ static irqreturn_t brcmf_pcie_isr_thread(int irq, void *arg) brcmf_pcie_poll_mb_data(devinfo); } if (devinfo->have_msi || status & devinfo->reginfo->int_d2h_db) { - if (devinfo->state == BRCMFMAC_PCIE_STATE_UP) + if (devinfo->state == BRCMFMAC_PCIE_STATE_UP && devinfo->irq_ready) brcmf_proto_msgbuf_rx_trigger(&devinfo->pdev->dev); } From 78500691da09ff98b581775d8a6be42592d959e5 Mon Sep 17 00:00:00 2001 From: Hector Martin Date: Mon, 2 Oct 2023 22:55:08 +0900 Subject: [PATCH 0209/3902] wifi: brcmfmac: Add support for SYSMEM corerev >= 12 & fix < 12 SYSMEM corerev 12+ uses different coreinfo masks for the ROM/RAM sizes. The masks for cores <12 also look like they were wrong all along, since the register layout is not the same as for SOCRAM (even though it was sharing the defines). Plus we need to skip the ROM banks, which we weren't doing. So it looks like this was always wrong for SYSMEM chips. Fix it and add support for the new revisions. Signed-off-by: Hector Martin --- .../broadcom/brcm80211/brcmfmac/chip.c | 20 +++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/chip.c b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/chip.c index 4239f2b21e5423..d89da1513b9110 100644 --- a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/chip.c +++ b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/chip.c @@ -162,6 +162,15 @@ struct sbconfig { #define SRCI_SRBSZ_SHIFT 0 #define SR_BSZ_BASE 14 +#define SYSMEM_SRCI_ROMNB_MASK 0x3e0 +#define SYSMEM_SRCI_ROMNB_SHIFT 5 +#define SYSMEM_SRCI_SRNB_MASK 0x1f +#define SYSMEM_SRCI_SRNB_SHIFT 0 +#define SYSMEM_SRCI_NEW_ROMNB_MASK 0xff000000 +#define SYSMEM_SRCI_NEW_ROMNB_SHIFT 24 +#define SYSMEM_SRCI_NEW_SRNB_MASK 0xff0000 +#define SYSMEM_SRCI_NEW_SRNB_SHIFT 16 + struct sbsocramregs { u32 coreinfo; u32 bwalloc; @@ -659,6 +668,7 @@ static u32 brcmf_chip_sysmem_ramsize(struct brcmf_core_priv *sysmem) u32 memsize = 0; u32 coreinfo; u32 idx; + u32 nrb; u32 nb; u32 banksize; @@ -666,10 +676,16 @@ static u32 brcmf_chip_sysmem_ramsize(struct brcmf_core_priv *sysmem) brcmf_chip_resetcore(&sysmem->pub, 0, 0, 0); coreinfo = brcmf_chip_core_read32(sysmem, SYSMEMREGOFFS(coreinfo)); - nb = (coreinfo & SRCI_SRNB_MASK) >> SRCI_SRNB_SHIFT; + if (sysmem->pub.rev >= 12) { + nrb = (coreinfo & SYSMEM_SRCI_NEW_ROMNB_MASK) >> SYSMEM_SRCI_NEW_ROMNB_SHIFT; + nb = (coreinfo & SYSMEM_SRCI_NEW_SRNB_MASK) >> SYSMEM_SRCI_NEW_SRNB_SHIFT; + } else { + nrb = (coreinfo & SYSMEM_SRCI_ROMNB_MASK) >> SYSMEM_SRCI_ROMNB_SHIFT; + nb = (coreinfo & SYSMEM_SRCI_SRNB_MASK) >> SYSMEM_SRCI_SRNB_SHIFT; + } for (idx = 0; idx < nb; idx++) { - brcmf_chip_socram_banksize(sysmem, idx, &banksize); + brcmf_chip_socram_banksize(sysmem, idx + nrb, &banksize); memsize += banksize; } From c629a7dbea8e6d853847ecdeae8e927d64b65a06 Mon Sep 17 00:00:00 2001 From: Hector Martin Date: Tue, 3 Oct 2023 17:28:02 +0900 Subject: [PATCH 0210/3902] wifi: brcmfmac: Add support for firmware signatures Beginning with BCM4388, Apple machines are using firmware signing. This requires a new firmware blob (as the signature is provided out-of-band) as well as an extension of the existing random seed upload mechanism to populate the data structures required for signature verification by the bootloader. To implement this, refactor the existing random seed code to be more generic, and use it to implement the signature upload. Drive-by changes: Remove two unused members of brcmf_pciedev_info (which are confusing as they are never initialized), and also zero out the unused portion of TCM to make TCM dumps less noisy. With this, the TCM contents are 1:1 identical to what the macOS driver ends up doing, except for the NVRAM which has the injected macaddr property at the end instead of at the start. Signed-off-by: Hector Martin --- .../broadcom/brcm80211/brcmfmac/pcie.c | 199 +++++++++++++++--- 1 file changed, 169 insertions(+), 30 deletions(-) diff --git a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/pcie.c b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/pcie.c index 917d8082fbbdce..3349b284739a6e 100644 --- a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/pcie.c +++ b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/pcie.c @@ -392,6 +392,7 @@ struct brcmf_pciedev_info { bool in_irq; struct pci_dev *pdev; char fw_name[BRCMF_FW_NAME_LEN]; + char sig_name[BRCMF_FW_NAME_LEN]; char nvram_name[BRCMF_FW_NAME_LEN]; char clm_name[BRCMF_FW_NAME_LEN]; char txcap_name[BRCMF_FW_NAME_LEN]; @@ -400,8 +401,7 @@ struct brcmf_pciedev_info { const struct brcmf_pcie_reginfo *reginfo; void __iomem *regs; void __iomem *tcm; - u32 ram_base; - u32 ram_size; + u32 fw_size; struct brcmf_chip *ci; u32 coreid; struct brcmf_pcie_shared_info shared; @@ -1807,26 +1807,164 @@ brcmf_pcie_init_share_ram_info(struct brcmf_pciedev_info *devinfo, return 0; } -struct brcmf_random_seed_footer { +struct brcmf_rtlv_footer { __le32 length; __le32 magic; }; +struct brcmf_fw_memmap { + u32 pad1[8]; + u32 vstatus_start; + u32 vstatus_end; + u32 fw_start; + u32 fw_end; + u32 sig_start; + u32 sig_end; + u32 heap_start; + u32 heap_end; + u32 pad2[6]; +}; + + +#define BRCMF_BL_HEAP_START_GAP 0x1000 +#define BRCMF_BL_HEAP_SIZE 0x10000 #define BRCMF_RANDOM_SEED_MAGIC 0xfeedc0de #define BRCMF_RANDOM_SEED_LENGTH 0x100 +#define BRCMF_SIG_MAGIC 0xfeedfe51 +#define BRCMF_VSTATUS_MAGIC 0xfeedfe54 +#define BRCMF_VSTATUS_SIZE 0x28 +#define BRCMF_MEMMAP_MAGIC 0xfeedfe53 +#define BRCMF_END_MAGIC 0xfeed0e2d + +static int brcmf_alloc_rtlv(struct brcmf_pciedev_info *devinfo, u32 *address, u32 type, size_t length) +{ + struct brcmf_bus *bus = dev_get_drvdata(&devinfo->pdev->dev); + u32 boundary = devinfo->ci->rambase + devinfo->fw_size + + BRCMF_BL_HEAP_START_GAP + BRCMF_BL_HEAP_SIZE; + u32 start_addr; + struct brcmf_rtlv_footer footer = { + .magic = type, + }; + + length = ALIGN(length, 4); + start_addr = *address - length - sizeof(struct brcmf_rtlv_footer); + + if (length > 0xffff || start_addr > *address || start_addr < boundary) { + brcmf_err(bus, "failed to allocate 0x%zx bytes for rTLV type 0x%x\n", + length, type); + return -ENOMEM; + } + + /* Random seed does not use the length check code */ + if (type == BRCMF_RANDOM_SEED_MAGIC) + footer.length = length; + else + footer.length = length | ((length ^ 0xffff) << 16); + + memcpy_toio(devinfo->tcm + *address - sizeof(struct brcmf_rtlv_footer), + &footer, sizeof(struct brcmf_rtlv_footer)); + + *address = start_addr; + + return 0; +} -static noinline_for_stack void -brcmf_pcie_provide_random_bytes(struct brcmf_pciedev_info *devinfo, u32 address) +static noinline_for_stack int +brcmf_pcie_add_random_seed(struct brcmf_pciedev_info *devinfo, u32 *address) { + int err; u8 randbuf[BRCMF_RANDOM_SEED_LENGTH]; + err = brcmf_alloc_rtlv(devinfo, address, + BRCMF_RANDOM_SEED_MAGIC, BRCMF_RANDOM_SEED_LENGTH); + if (err) + return err; + + /* Some Apple chips/firmwares expect a buffer of random + * data to be present before NVRAM + */ + brcmf_dbg(PCIE, "Download random seed\n"); + get_random_bytes(randbuf, BRCMF_RANDOM_SEED_LENGTH); memcpy_toio(devinfo->tcm + address, randbuf, BRCMF_RANDOM_SEED_LENGTH); + + return 0; +} + +static int brcmf_pcie_add_signature(struct brcmf_pciedev_info *devinfo, + u32 *address, const struct firmware *fwsig) +{ + int err; + struct brcmf_fw_memmap memmap; + + brcmf_dbg(PCIE, "Download firmware signature\n"); + + memset(&memmap, 0, sizeof(memmap)); + + memmap.sig_end = *address; + err = brcmf_alloc_rtlv(devinfo, address, BRCMF_SIG_MAGIC, fwsig->size); + if (err) + return err; + memmap.sig_start = *address; + + memmap.vstatus_end = *address; + err = brcmf_alloc_rtlv(devinfo, address, BRCMF_VSTATUS_MAGIC, BRCMF_VSTATUS_SIZE); + if (err) + return err; + memmap.vstatus_start = *address; + + err = brcmf_alloc_rtlv(devinfo, address, BRCMF_MEMMAP_MAGIC, sizeof(memmap)); + if (err) + return err; + + memmap.fw_start = devinfo->ci->rambase; + memmap.fw_end = memmap.fw_start + devinfo->fw_size; + memmap.heap_start = memmap.fw_end + BRCMF_BL_HEAP_START_GAP; + memmap.heap_end = memmap.heap_start + BRCMF_BL_HEAP_SIZE; + + if (memmap.heap_end > *address) + return -ENOMEM; + + memcpy_toio(devinfo->tcm + memmap.sig_start, fwsig->data, fwsig->size); + memset_io(devinfo->tcm + memmap.vstatus_start, 0, BRCMF_VSTATUS_SIZE); + memcpy_toio(devinfo->tcm + *address, &memmap, sizeof(memmap)); + + err = brcmf_alloc_rtlv(devinfo, address, BRCMF_END_MAGIC, 0); + if (err) + return err; + + return 0; +} + +static int brcmf_pcie_populate_footers(struct brcmf_pciedev_info *devinfo, + u32 *address, const struct firmware *fwsig) +{ + int err; + + /* We only do this for Apple firmwares. If any other + * production firmwares are found to need this, the condition + * needs to be adjusted. + */ + if (!devinfo->fwseed) + return 0; + + err = brcmf_pcie_add_random_seed(devinfo, address); + if (err) + return err; + + if (fwsig) { + err = brcmf_pcie_add_signature(devinfo, address, fwsig); + if (err) + return err; + } + + return 0; } static int brcmf_pcie_download_fw_nvram(struct brcmf_pciedev_info *devinfo, - const struct firmware *fw, void *nvram, - u32 nvram_len) + const struct firmware *fw, + const struct firmware *fwsig, + void *nvram, u32 nvram_len) { struct brcmf_bus *bus = dev_get_drvdata(&devinfo->pdev->dev); u32 sharedram_addr; @@ -1846,6 +1984,7 @@ static int brcmf_pcie_download_fw_nvram(struct brcmf_pciedev_info *devinfo, (void *)fw->data, fw->size); resetintr = get_unaligned_le32(fw->data); + devinfo->fw_size = fw->size; release_firmware(fw); /* reset last 4 bytes of RAM address. to be used for shared @@ -1853,37 +1992,31 @@ static int brcmf_pcie_download_fw_nvram(struct brcmf_pciedev_info *devinfo, */ brcmf_pcie_write_ram32(devinfo, devinfo->ci->ramsize - 4, 0); + address = devinfo->ci->rambase + devinfo->ci->ramsize; + if (nvram) { brcmf_dbg(PCIE, "Download NVRAM %s\n", devinfo->nvram_name); - address = devinfo->ci->rambase + devinfo->ci->ramsize - - nvram_len; + address -= nvram_len; memcpy_toio(devinfo->tcm + address, nvram, nvram_len); brcmf_fw_nvram_free(nvram); - if (devinfo->fwseed) { - size_t rand_len = BRCMF_RANDOM_SEED_LENGTH; - struct brcmf_random_seed_footer footer = { - .length = cpu_to_le32(rand_len), - .magic = cpu_to_le32(BRCMF_RANDOM_SEED_MAGIC), - }; - - /* Some chips/firmwares expect a buffer of random - * data to be present before NVRAM - */ - brcmf_dbg(PCIE, "Download random seed\n"); - - address -= sizeof(footer); - memcpy_toio(devinfo->tcm + address, &footer, - sizeof(footer)); - - address -= rand_len; - brcmf_pcie_provide_random_bytes(devinfo, address); - } + err = brcmf_pcie_populate_footers(devinfo, &address, fwsig); + if (err) + brcmf_err(bus, "failed to populate firmware footers err=%d\n", err); } else { brcmf_dbg(PCIE, "No matching NVRAM file found %s\n", devinfo->nvram_name); } + release_firmware(fwsig); + + /* Clear free TCM. This isn't really necessary, but it + * makes debugging memory dumps a lot easier since we + * don't get a bunch of junk filling up the free space. + */ + memset_io(devinfo->tcm + devinfo->ci->rambase + devinfo->fw_size, + 0, address - devinfo->fw_size - devinfo->ci->rambase); + sharedram_addr_written = brcmf_pcie_read_ram32(devinfo, devinfo->ci->ramsize - 4); @@ -2269,11 +2402,12 @@ static int brcmf_pcie_read_otp(struct brcmf_pciedev_info *devinfo) #define BRCMF_PCIE_FW_NVRAM 1 #define BRCMF_PCIE_FW_CLM 2 #define BRCMF_PCIE_FW_TXCAP 3 +#define BRCMF_PCIE_FW_SIG 4 static void brcmf_pcie_setup(struct device *dev, int ret, struct brcmf_fw_request *fwreq) { - const struct firmware *fw; + const struct firmware *fw, *fwsig; void *nvram; struct brcmf_bus *bus; struct brcmf_pciedev *pcie_bus_dev; @@ -2292,6 +2426,7 @@ static void brcmf_pcie_setup(struct device *dev, int ret, brcmf_pcie_attach(devinfo); fw = fwreq->items[BRCMF_PCIE_FW_CODE].binary; + fwsig = fwreq->items[BRCMF_PCIE_FW_SIG].binary; nvram = fwreq->items[BRCMF_PCIE_FW_NVRAM].nv_data.data; nvram_len = fwreq->items[BRCMF_PCIE_FW_NVRAM].nv_data.len; devinfo->clm_fw = fwreq->items[BRCMF_PCIE_FW_CLM].binary; @@ -2302,6 +2437,7 @@ static void brcmf_pcie_setup(struct device *dev, int ret, if (ret) { brcmf_err(bus, "Failed to get RAM info\n"); release_firmware(fw); + release_firmware(fwsig); brcmf_fw_nvram_free(nvram); goto fail; } @@ -2313,7 +2449,7 @@ static void brcmf_pcie_setup(struct device *dev, int ret, */ brcmf_pcie_adjust_ramsize(devinfo, (u8 *)fw->data, fw->size); - ret = brcmf_pcie_download_fw_nvram(devinfo, fw, nvram, nvram_len); + ret = brcmf_pcie_download_fw_nvram(devinfo, fw, fwsig, nvram, nvram_len); if (ret) goto fail; @@ -2378,6 +2514,7 @@ brcmf_pcie_prepare_fw_request(struct brcmf_pciedev_info *devinfo) { ".txt", devinfo->nvram_name }, { ".clm_blob", devinfo->clm_name }, { ".txcap_blob", devinfo->txcap_name }, + { ".sig", devinfo->sig_name }, }; fwreq = brcmf_fw_alloc_request(devinfo->ci->chip, devinfo->ci->chiprev, @@ -2388,6 +2525,8 @@ brcmf_pcie_prepare_fw_request(struct brcmf_pciedev_info *devinfo) return NULL; fwreq->items[BRCMF_PCIE_FW_CODE].type = BRCMF_FW_TYPE_BINARY; + fwreq->items[BRCMF_PCIE_FW_SIG].type = BRCMF_FW_TYPE_BINARY; + fwreq->items[BRCMF_PCIE_FW_SIG].flags = BRCMF_FW_REQF_OPTIONAL; fwreq->items[BRCMF_PCIE_FW_NVRAM].type = BRCMF_FW_TYPE_NVRAM; fwreq->items[BRCMF_PCIE_FW_NVRAM].flags = BRCMF_FW_REQF_OPTIONAL; fwreq->items[BRCMF_PCIE_FW_CLM].type = BRCMF_FW_TYPE_BINARY; From 14824f74f1b6f1fc84731c2715508728f259fb5a Mon Sep 17 00:00:00 2001 From: Hector Martin Date: Tue, 3 Oct 2023 18:33:36 +0900 Subject: [PATCH 0211/3902] wifi: brcmfmac: msgbuf: Increase RX ring sizes to 2048 New chips, bigger rings again. BCM4388 Apple firmware posts more than 1024 RX buffers, so we need to bump this up again. This also requires increasing the number of RX PKTIDs. Signed-off-by: Hector Martin --- drivers/net/wireless/broadcom/brcm80211/brcmfmac/msgbuf.c | 2 +- drivers/net/wireless/broadcom/brcm80211/brcmfmac/msgbuf.h | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/msgbuf.c b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/msgbuf.c index 93206850373300..0e41d618486d39 100644 --- a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/msgbuf.c +++ b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/msgbuf.c @@ -75,7 +75,7 @@ #define MSGBUF_TYPE_D2H_RING_DELETE_CMPLT 0x2E #define NR_TX_PKTIDS 2048 -#define NR_RX_PKTIDS 1024 +#define NR_RX_PKTIDS 2048 #define BRCMF_IOCTL_REQ_PKTID 0xFFFE diff --git a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/msgbuf.h b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/msgbuf.h index 89b6b7f9ddb748..0ed48cf13d93cf 100644 --- a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/msgbuf.h +++ b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/msgbuf.h @@ -8,10 +8,10 @@ #ifdef CONFIG_BRCMFMAC_PROTO_MSGBUF #define BRCMF_H2D_MSGRING_CONTROL_SUBMIT_MAX_ITEM 64 -#define BRCMF_H2D_MSGRING_RXPOST_SUBMIT_MAX_ITEM 1024 +#define BRCMF_H2D_MSGRING_RXPOST_SUBMIT_MAX_ITEM 2048 #define BRCMF_D2H_MSGRING_CONTROL_COMPLETE_MAX_ITEM 64 #define BRCMF_D2H_MSGRING_TX_COMPLETE_MAX_ITEM 1024 -#define BRCMF_D2H_MSGRING_RX_COMPLETE_MAX_ITEM 1024 +#define BRCMF_D2H_MSGRING_RX_COMPLETE_MAX_ITEM 2048 #define BRCMF_H2D_TXFLOWRING_MAX_ITEM 512 #define BRCMF_H2D_MSGRING_CONTROL_SUBMIT_ITEMSIZE 40 From 9e167f4c599b350d47c1e267f85022c0478bf1d8 Mon Sep 17 00:00:00 2001 From: Hector Martin Date: Wed, 4 Oct 2023 04:55:02 +0900 Subject: [PATCH 0212/3902] wifi: brcmfmac: Increase bandlist size BCM4388 supports more bands, so make space for them. Signed-off-by: Hector Martin --- drivers/net/wireless/broadcom/brcm80211/brcmfmac/cfg80211.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/cfg80211.c b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/cfg80211.c index bb96b87b2a6e56..2740f1944d82fe 100644 --- a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/cfg80211.c +++ b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/cfg80211.c @@ -7640,7 +7640,7 @@ static int brcmf_setup_wiphy(struct wiphy *wiphy, struct brcmf_if *ifp) struct ieee80211_supported_band *band; u16 max_interfaces = 0; bool gscan; - __le32 bandlist[3]; + __le32 bandlist[16]; u32 n_bands; int err, i; From 7115e1f595673e07666ed27c99dd059759a3cd64 Mon Sep 17 00:00:00 2001 From: Hector Martin Date: Wed, 4 Oct 2023 04:57:34 +0900 Subject: [PATCH 0213/3902] wifi: brcmfmac: chip: ca7: Only disable D11 cores; handle an arbitrary number This is the ca7 version of 3c7c07ca7ab1 ("wifi: brcmfmac: chip: Only disable D11 cores; handle an arbitrary number"). Instead of the hack in resetcore to handle multiple 80211 cores, let's just iterate in set_passive. Signed-off-by: Hector Martin --- .../broadcom/brcm80211/brcmfmac/chip.c | 46 +++---------------- 1 file changed, 6 insertions(+), 40 deletions(-) diff --git a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/chip.c b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/chip.c index d89da1513b9110..56290fe71cec8b 100644 --- a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/chip.c +++ b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/chip.c @@ -445,25 +445,11 @@ static void brcmf_chip_ai_resetcore(struct brcmf_core_priv *core, u32 prereset, { struct brcmf_chip_priv *ci; int count; - struct brcmf_core *d11core2 = NULL; - struct brcmf_core_priv *d11priv2 = NULL; ci = core->chip; - /* special handle two D11 cores reset */ - if (core->pub.id == BCMA_CORE_80211) { - d11core2 = brcmf_chip_get_d11core(&ci->pub, 1); - if (d11core2) { - brcmf_dbg(INFO, "found two d11 cores, reset both\n"); - d11priv2 = container_of(d11core2, - struct brcmf_core_priv, pub); - } - } - /* must disable first to work for arbitrary current core state */ brcmf_chip_ai_coredisable(core, prereset, reset); - if (d11priv2) - brcmf_chip_ai_coredisable(d11priv2, prereset, reset); count = 0; while (ci->ops->read32(ci->ctx, core->wrapbase + BCMA_RESET_CTL) & @@ -475,30 +461,9 @@ static void brcmf_chip_ai_resetcore(struct brcmf_core_priv *core, u32 prereset, usleep_range(40, 60); } - if (d11priv2) { - count = 0; - while (ci->ops->read32(ci->ctx, - d11priv2->wrapbase + BCMA_RESET_CTL) & - BCMA_RESET_CTL_RESET) { - ci->ops->write32(ci->ctx, - d11priv2->wrapbase + BCMA_RESET_CTL, - 0); - count++; - if (count > 50) - break; - usleep_range(40, 60); - } - } - ci->ops->write32(ci->ctx, core->wrapbase + BCMA_IOCTL, postreset | BCMA_IOCTL_CLK); ci->ops->read32(ci->ctx, core->wrapbase + BCMA_IOCTL); - - if (d11priv2) { - ci->ops->write32(ci->ctx, d11priv2->wrapbase + BCMA_IOCTL, - postreset | BCMA_IOCTL_CLK); - ci->ops->read32(ci->ctx, d11priv2->wrapbase + BCMA_IOCTL); - } } char *brcmf_chip_name(u32 id, u32 rev, char *buf, uint len) @@ -1354,14 +1319,15 @@ static inline void brcmf_chip_ca7_set_passive(struct brcmf_chip_priv *chip) { struct brcmf_core *core; + int i; brcmf_chip_disable_arm(chip, BCMA_CORE_ARM_CA7); - core = brcmf_chip_get_core(&chip->pub, BCMA_CORE_80211); - brcmf_chip_resetcore(core, D11_BCMA_IOCTL_PHYRESET | - D11_BCMA_IOCTL_PHYCLOCKEN, - D11_BCMA_IOCTL_PHYCLOCKEN, - D11_BCMA_IOCTL_PHYCLOCKEN); + /* Disable the cores only and let the firmware enable them. */ + for (i = 0; (core = brcmf_chip_get_d11core(&chip->pub, i)); i++) + brcmf_chip_coredisable(core, D11_BCMA_IOCTL_PHYRESET | + D11_BCMA_IOCTL_PHYCLOCKEN, + D11_BCMA_IOCTL_PHYCLOCKEN); } static bool brcmf_chip_ca7_set_active(struct brcmf_chip_priv *chip, u32 rstvec) From cb4e06937dd2a26a15a15f04a06da8815c6bce5c Mon Sep 17 00:00:00 2001 From: Hector Martin Date: Wed, 4 Oct 2023 05:00:34 +0900 Subject: [PATCH 0214/3902] wifi: brcmfmac: Handle watchdog properly in newer cores On newer cores, we need to explicitly set the subsystems to reset via the watchdog. Logic adapted from bcmdhd. Signed-off-by: Hector Martin --- .../broadcom/brcm80211/brcmfmac/pcie.c | 26 +++++++++++++++++-- .../broadcom/brcm80211/include/chipcommon.h | 8 ++++++ 2 files changed, 32 insertions(+), 2 deletions(-) diff --git a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/pcie.c b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/pcie.c index 3349b284739a6e..3169216d48a838 100644 --- a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/pcie.c +++ b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/pcie.c @@ -743,8 +743,30 @@ static void brcmf_pcie_reset_device(struct brcmf_pciedev_info *devinfo) /* Watchdog reset */ brcmf_pcie_select_core(devinfo, BCMA_CORE_CHIPCOMMON); - WRITECC32(devinfo, watchdog, 4); - msleep(100); + core = brcmf_chip_get_chipcommon(devinfo->ci); + + if (core->rev >= 65) { + u32 mask = CC_WD_SSRESET_PCIE_F0_EN; + + core = brcmf_chip_get_core(devinfo->ci, BCMA_CORE_PCIE2); + if (core->rev < 66) + mask |= CC_WD_SSRESET_PCIE_ALL_FN_EN; + + val = READCC32(devinfo, watchdog); + val &= ~CC_WD_ENABLE_MASK; + val |= mask; + WRITECC32(devinfo, watchdog, val); + val &= ~CC_WD_COUNTER_MASK; + val |= 4; + WRITECC32(devinfo, watchdog, val); + msleep(10); + val = READCC32(devinfo, intstatus); + val |= mask; + WRITECC32(devinfo, intstatus, val); + } else { + WRITECC32(devinfo, watchdog, 4); + msleep(100); + } /* Restore ASPM */ brcmf_pcie_select_core(devinfo, BCMA_CORE_PCIE2); diff --git a/drivers/net/wireless/broadcom/brcm80211/include/chipcommon.h b/drivers/net/wireless/broadcom/brcm80211/include/chipcommon.h index 0340bba968688f..5c3b8fb41194ae 100644 --- a/drivers/net/wireless/broadcom/brcm80211/include/chipcommon.h +++ b/drivers/net/wireless/broadcom/brcm80211/include/chipcommon.h @@ -302,6 +302,14 @@ struct chipcregs { #define PMU_RCTL_LOGIC_DISABLE_MASK (1 << 27) +/* watchdog */ +#define CC_WD_SSRESET_PCIE_F0_EN 0x10000000 +#define CC_WD_SSRESET_PCIE_F1_EN 0x20000000 +#define CC_WD_SSRESET_PCIE_F2_EN 0x40000000 +#define CC_WD_SSRESET_PCIE_ALL_FN_EN 0x80000000 +#define CC_WD_COUNTER_MASK 0x0fffffff +#define CC_WD_ENABLE_MASK 0xf0000000 + /* * Maximum delay for the PMU state transition in us. * This is an upper bound intended for spinwaits etc. From 44177ac19c92ece03b08c4762163b90308ca1b6b Mon Sep 17 00:00:00 2001 From: Hector Martin Date: Wed, 4 Oct 2023 05:03:38 +0900 Subject: [PATCH 0215/3902] wifi: brcmfmac: pcie: Access pcie core registers via dedicated window Currently the pcie code multiplexes all register accesses through a single window. This isn't very efficient, and it creates race conditions when we access registers from multiple paths (e.g. in the interrupt handler). Since the chip has a dedicated window for the PCIe core registers, we can use that instead, avoid all the gratuitous window switching, and fix the IRQ race issues. Signed-off-by: Hector Martin --- .../broadcom/brcm80211/brcmfmac/pcie.c | 53 ++++++++++++------- 1 file changed, 33 insertions(+), 20 deletions(-) diff --git a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/pcie.c b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/pcie.c index 3169216d48a838..63e27d7342b135 100644 --- a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/pcie.c +++ b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/pcie.c @@ -550,6 +550,19 @@ brcmf_pcie_write_reg32(struct brcmf_pciedev_info *devinfo, u32 reg_offset, iowrite32(value, address); } +static u32 +brcmf_pcie_read_pcie32(struct brcmf_pciedev_info *devinfo, u32 reg_offset) +{ + return brcmf_pcie_read_reg32(devinfo, 0x2000 + reg_offset); +} + + +static void +brcmf_pcie_write_pcie32(struct brcmf_pciedev_info *devinfo, u32 reg_offset, + u32 value) +{ + brcmf_pcie_write_reg32(devinfo, 0x2000 + reg_offset, value); +} static u8 brcmf_pcie_read_tcm8(struct brcmf_pciedev_info *devinfo, u32 mem_offset) @@ -776,14 +789,14 @@ static void brcmf_pcie_reset_device(struct brcmf_pciedev_info *devinfo) core = brcmf_chip_get_core(devinfo->ci, BCMA_CORE_PCIE2); if (core->rev <= 13) { for (i = 0; i < ARRAY_SIZE(cfg_offset); i++) { - brcmf_pcie_write_reg32(devinfo, + brcmf_pcie_write_pcie32(devinfo, BRCMF_PCIE_PCIE2REG_CONFIGADDR, cfg_offset[i]); - val = brcmf_pcie_read_reg32(devinfo, + val = brcmf_pcie_read_pcie32(devinfo, BRCMF_PCIE_PCIE2REG_CONFIGDATA); brcmf_dbg(PCIE, "config offset 0x%04x, value 0x%04x\n", cfg_offset[i], val); - brcmf_pcie_write_reg32(devinfo, + brcmf_pcie_write_pcie32(devinfo, BRCMF_PCIE_PCIE2REG_CONFIGDATA, val); } @@ -797,9 +810,9 @@ static void brcmf_pcie_attach(struct brcmf_pciedev_info *devinfo) /* BAR1 window may not be sized properly */ brcmf_pcie_select_core(devinfo, BCMA_CORE_PCIE2); - brcmf_pcie_write_reg32(devinfo, BRCMF_PCIE_PCIE2REG_CONFIGADDR, 0x4e0); - config = brcmf_pcie_read_reg32(devinfo, BRCMF_PCIE_PCIE2REG_CONFIGDATA); - brcmf_pcie_write_reg32(devinfo, BRCMF_PCIE_PCIE2REG_CONFIGDATA, config); + brcmf_pcie_write_pcie32(devinfo, BRCMF_PCIE_PCIE2REG_CONFIGADDR, 0x4e0); + config = brcmf_pcie_read_pcie32(devinfo, BRCMF_PCIE_PCIE2REG_CONFIGDATA); + brcmf_pcie_write_pcie32(devinfo, BRCMF_PCIE_PCIE2REG_CONFIGDATA, config); device_wakeup_enable(&devinfo->pdev->dev); } @@ -1013,7 +1026,7 @@ static void brcmf_pcie_bus_console_read(struct brcmf_pciedev_info *devinfo, static void brcmf_pcie_intr_disable(struct brcmf_pciedev_info *devinfo) { - brcmf_pcie_write_reg32(devinfo, devinfo->reginfo->mailboxmask, 0); + brcmf_pcie_write_pcie32(devinfo, devinfo->reginfo->mailboxmask, 0); devinfo->irq_ready = false; } @@ -1021,7 +1034,7 @@ static void brcmf_pcie_intr_disable(struct brcmf_pciedev_info *devinfo) static void brcmf_pcie_intr_enable(struct brcmf_pciedev_info *devinfo) { - brcmf_pcie_write_reg32(devinfo, devinfo->reginfo->mailboxmask, + brcmf_pcie_write_pcie32(devinfo, devinfo->reginfo->mailboxmask, devinfo->reginfo->int_d2h_db | devinfo->reginfo->int_fn0); @@ -1032,9 +1045,9 @@ static void brcmf_pcie_hostready(struct brcmf_pciedev_info *devinfo) { if (devinfo->shared.flags & BRCMF_PCIE_SHARED_HOSTRDY_DB1) { if (devinfo->shared.flags & BRCMF_PCIE_SHARED_DAR) - brcmf_pcie_write_reg32(devinfo, BRCMF_PCIE_64_PCIE2REG_H2D_MAILBOX_1, 1); + brcmf_pcie_write_pcie32(devinfo, BRCMF_PCIE_64_PCIE2REG_H2D_MAILBOX_1, 1); else - brcmf_pcie_write_reg32(devinfo, BRCMF_PCIE_PCIE2REG_H2D_MAILBOX_1, 1); + brcmf_pcie_write_pcie32(devinfo, BRCMF_PCIE_PCIE2REG_H2D_MAILBOX_1, 1); } } @@ -1042,7 +1055,7 @@ static irqreturn_t brcmf_pcie_quick_check_isr(int irq, void *arg) { struct brcmf_pciedev_info *devinfo = (struct brcmf_pciedev_info *)arg; - if (brcmf_pcie_read_reg32(devinfo, devinfo->reginfo->mailboxint)) { + if (brcmf_pcie_read_pcie32(devinfo, devinfo->reginfo->mailboxint)) { brcmf_pcie_intr_disable(devinfo); brcmf_dbg(PCIE, "Enter\n"); return IRQ_WAKE_THREAD; @@ -1062,10 +1075,10 @@ static irqreturn_t brcmf_pcie_isr_thread(int irq, void *arg) u32 status; devinfo->in_irq = true; - status = brcmf_pcie_read_reg32(devinfo, devinfo->reginfo->mailboxint); + status = brcmf_pcie_read_pcie32(devinfo, devinfo->reginfo->mailboxint); brcmf_dbg(PCIE, "Enter %x\n", status); if (status) { - brcmf_pcie_write_reg32(devinfo, devinfo->reginfo->mailboxint, + brcmf_pcie_write_pcie32(devinfo, devinfo->reginfo->mailboxint, status); if (status & devinfo->reginfo->int_fn0) brcmf_pcie_poll_mb_data(devinfo); @@ -1131,8 +1144,8 @@ static void brcmf_pcie_release_irq(struct brcmf_pciedev_info *devinfo) if (devinfo->in_irq) brcmf_err(bus, "Still in IRQ (processing) !!!\n"); - status = brcmf_pcie_read_reg32(devinfo, devinfo->reginfo->mailboxint); - brcmf_pcie_write_reg32(devinfo, devinfo->reginfo->mailboxint, status); + status = brcmf_pcie_read_pcie32(devinfo, devinfo->reginfo->mailboxint); + brcmf_pcie_write_pcie32(devinfo, devinfo->reginfo->mailboxint, status); devinfo->irq_allocated = false; } @@ -1185,9 +1198,9 @@ static int brcmf_pcie_ring_mb_ring_bell(void *ctx) brcmf_dbg(PCIE, "RING !\n"); /* Any arbitrary value will do, lets use 1 */ if (devinfo->shared.flags & BRCMF_PCIE_SHARED_DAR) - brcmf_pcie_write_reg32(devinfo, BRCMF_PCIE_64_PCIE2REG_H2D_MAILBOX_0, 1); + brcmf_pcie_write_pcie32(devinfo, BRCMF_PCIE_64_PCIE2REG_H2D_MAILBOX_0, 1); else - brcmf_pcie_write_reg32(devinfo, BRCMF_PCIE_PCIE2REG_H2D_MAILBOX_0, 1); + brcmf_pcie_write_pcie32(devinfo, BRCMF_PCIE_PCIE2REG_H2D_MAILBOX_0, 1); return 0; } @@ -2182,9 +2195,9 @@ static int brcmf_pcie_buscore_reset(void *ctx, struct brcmf_chip *chip) else reg = BRCMF_PCIE_PCIE2REG_MAILBOXINT; - val = brcmf_pcie_read_reg32(devinfo, reg); + val = brcmf_pcie_read_pcie32(devinfo, reg); if (val != 0xffffffff) - brcmf_pcie_write_reg32(devinfo, reg, val); + brcmf_pcie_write_pcie32(devinfo, reg, val); return 0; } @@ -2967,7 +2980,7 @@ static int brcmf_pcie_pm_leave_D3(struct device *dev) brcmf_dbg(PCIE, "Enter, dev=%p, bus=%p\n", dev, bus); /* Check if device is still up and running, if so we are ready */ - if (brcmf_pcie_read_reg32(devinfo, devinfo->reginfo->intmask) != 0) { + if (brcmf_pcie_read_pcie32(devinfo, devinfo->reginfo->intmask) != 0) { brcmf_dbg(PCIE, "Try to wakeup device....\n"); /* Set the device up, so we can write the MB data message in ring mode */ devinfo->state = BRCMFMAC_PCIE_STATE_UP; From f521901759da61846f91ef30ad7231144850d01e Mon Sep 17 00:00:00 2001 From: Hector Martin Date: Wed, 4 Oct 2023 05:08:25 +0900 Subject: [PATCH 0216/3902] wifi: brcmfmac: pcie: Initialize IRQs before firmware boot Newer firmwares notify the host of boot completion via an MSI, so let's make sure that is initialized before booting the firmware. Signed-off-by: Hector Martin --- .../net/wireless/broadcom/brcm80211/brcmfmac/pcie.c | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/pcie.c b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/pcie.c index 63e27d7342b135..8152ebcff80a43 100644 --- a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/pcie.c +++ b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/pcie.c @@ -2484,6 +2484,14 @@ static void brcmf_pcie_setup(struct device *dev, int ret, */ brcmf_pcie_adjust_ramsize(devinfo, (u8 *)fw->data, fw->size); + /* Newer firmwares will signal firmware boot via MSI, so make sure we + * initialize that upfront. + */ + brcmf_pcie_select_core(devinfo, BCMA_CORE_PCIE2); + ret = brcmf_pcie_request_irq(devinfo); + if (ret) + goto fail; + ret = brcmf_pcie_download_fw_nvram(devinfo, fw, fwsig, nvram, nvram_len); if (ret) goto fail; @@ -2499,9 +2507,6 @@ static void brcmf_pcie_setup(struct device *dev, int ret, goto fail; brcmf_pcie_select_core(devinfo, BCMA_CORE_PCIE2); - ret = brcmf_pcie_request_irq(devinfo); - if (ret) - goto fail; /* hook the commonrings in the bus structure. */ for (i = 0; i < BRCMF_NROF_COMMON_MSGRINGS; i++) From 90f2d39466b385378ab5a4b59913dc6d4b6d78e5 Mon Sep 17 00:00:00 2001 From: Hector Martin Date: Wed, 4 Oct 2023 05:21:22 +0900 Subject: [PATCH 0217/3902] wifi: brcmfmac: Do not set reset vector when signatures are in use With secure boot, the vector is not accessible and trying to write it triggers PCIe errors. Skip it in that case. Signed-off-by: Hector Martin --- drivers/net/wireless/broadcom/brcm80211/brcmfmac/pcie.c | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/pcie.c b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/pcie.c index 8152ebcff80a43..9e3f1676abbfc2 100644 --- a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/pcie.c +++ b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/pcie.c @@ -402,6 +402,7 @@ struct brcmf_pciedev_info { void __iomem *regs; void __iomem *tcm; u32 fw_size; + bool skip_reset_vector; struct brcmf_chip *ci; u32 coreid; struct brcmf_pcie_shared_info shared; @@ -1968,6 +1969,8 @@ static int brcmf_pcie_add_signature(struct brcmf_pciedev_info *devinfo, if (err) return err; + devinfo->skip_reset_vector = true; + return 0; } @@ -2208,7 +2211,8 @@ static void brcmf_pcie_buscore_activate(void *ctx, struct brcmf_chip *chip, { struct brcmf_pciedev_info *devinfo = (struct brcmf_pciedev_info *)ctx; - brcmf_pcie_write_tcm32(devinfo, 0, rstvec); + if (!devinfo->skip_reset_vector) + brcmf_pcie_write_tcm32(devinfo, 0, rstvec); } From ac7043daf93d72de3cc4f70878b3c643de37f743 Mon Sep 17 00:00:00 2001 From: Hector Martin Date: Wed, 4 Oct 2023 05:27:54 +0900 Subject: [PATCH 0218/3902] wifi: brcmfmac: Mask all IRQs before starting firmware Make sure the firmware can't get any early notifications by masking all IRQs explicitly before loading the firmware. Signed-off-by: Hector Martin --- .../wireless/broadcom/brcm80211/brcmfmac/pcie.c | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/pcie.c b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/pcie.c index 9e3f1676abbfc2..637f39ad813049 100644 --- a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/pcie.c +++ b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/pcie.c @@ -338,6 +338,7 @@ static const struct brcmf_firmware_mapping brcmf_pcie_fwnames[] = { #define BRCMF_PCIE_CFGREG_PML1_SUB_CTRL1 0x248 #define BRCMF_PCIE_CFGREG_REG_BAR2_CONFIG 0x4E0 #define BRCMF_PCIE_CFGREG_REG_BAR3_CONFIG 0x4F4 +#define BRCMF_PCIE_CFGREG_TLCNTRL_5 0x814 #define BRCMF_PCIE_LINK_STATUS_CTRL_ASPM_ENAB 3 /* Magic number at a magic location to find RAM size */ @@ -832,6 +833,21 @@ static int brcmf_pcie_enter_download_state(struct brcmf_pciedev_info *devinfo) brcmf_pcie_write_reg32(devinfo, BRCMF_PCIE_ARMCR4REG_BANKPDA, 0); } + + /* Ensure all IRQs are masked so the firmware doesn't get + * a hostready notification too early. + */ + + brcmf_pcie_write_pcie32(devinfo, devinfo->reginfo->mailboxmask, 0); + brcmf_pcie_write_pcie32(devinfo, devinfo->reginfo->mailboxint, + 0xffffffff); + + pci_write_config_dword(devinfo->pdev, BRCMF_PCIE_REG_INTMASK, 0); + + brcmf_pcie_write_pcie32(devinfo, BRCMF_PCIE_PCIE2REG_CONFIGADDR, + BRCMF_PCIE_CFGREG_TLCNTRL_5); + brcmf_pcie_write_pcie32(devinfo, BRCMF_PCIE_PCIE2REG_CONFIGDATA, + 0xffffffff); return 0; } From 13ab24e5a43c02ef447887071d37b1c0f8887b5b Mon Sep 17 00:00:00 2001 From: Hector Martin Date: Wed, 4 Oct 2023 05:50:00 +0900 Subject: [PATCH 0219/3902] wifi: brcmfmac: Add support for SCAN_V3 This is essentially identical to SCAN_V2 with an extra field where we had a padding byte, so don't bother duplicating the entire structure. Just add the field and the logic to set the version properly. Signed-off-by: Hector Martin --- .../broadcom/brcm80211/brcmfmac/cfg80211.c | 20 +++++++++++++------ .../broadcom/brcm80211/brcmfmac/feature.c | 16 ++++++++++++++- .../broadcom/brcm80211/brcmfmac/feature.h | 1 + .../broadcom/brcm80211/brcmfmac/fwil_types.h | 15 +++++++++++++- 4 files changed, 44 insertions(+), 8 deletions(-) diff --git a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/cfg80211.c b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/cfg80211.c index 2740f1944d82fe..9709eeb03039dc 100644 --- a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/cfg80211.c +++ b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/cfg80211.c @@ -1093,6 +1093,7 @@ static void brcmf_scan_params_v2_to_v1(struct brcmf_scan_params_v2_le *params_v2 } static void brcmf_escan_prep(struct brcmf_cfg80211_info *cfg, + struct brcmf_if *ifp, struct brcmf_scan_params_v2_le *params_le, struct cfg80211_scan_request *request) { @@ -1109,8 +1110,13 @@ static void brcmf_escan_prep(struct brcmf_cfg80211_info *cfg, length = BRCMF_SCAN_PARAMS_V2_FIXED_SIZE; - params_le->version = cpu_to_le16(BRCMF_SCAN_PARAMS_VERSION_V2); + if (brcmf_feat_is_enabled(ifp, BRCMF_FEAT_SCAN_V3)) + params_le->version = cpu_to_le16(BRCMF_SCAN_PARAMS_VERSION_V3); + else + params_le->version = cpu_to_le16(BRCMF_SCAN_PARAMS_VERSION_V2); + params_le->bss_type = DOT11_BSSTYPE_ANY; + params_le->ssid_type = 0; params_le->scan_type = cpu_to_le32(BRCMF_SCANTYPE_ACTIVE); params_le->channel_num = 0; params_le->nprobes = cpu_to_le32(-1); @@ -1204,7 +1210,7 @@ s32 brcmf_notify_escan_complete(struct brcmf_cfg80211_info *cfg, /* Do a scan abort to stop the driver's scan engine */ brcmf_dbg(SCAN, "ABORT scan in firmware\n"); - brcmf_escan_prep(cfg, ¶ms_v2_le, NULL); + brcmf_escan_prep(cfg, ifp, ¶ms_v2_le, NULL); /* E-Scan (or anyother type) can be aborted by SCAN */ if (brcmf_feat_is_enabled(ifp, BRCMF_FEAT_SCAN_V2)) { @@ -1464,11 +1470,13 @@ brcmf_run_escan(struct brcmf_cfg80211_info *cfg, struct brcmf_if *ifp, goto exit; } BUG_ON(params_size + sizeof("escan") >= BRCMF_DCMD_MEDLEN); - brcmf_escan_prep(cfg, ¶ms->params_v2_le, request); + brcmf_escan_prep(cfg, ifp, ¶ms->params_v2_le, request); - params->version = cpu_to_le32(BRCMF_ESCAN_REQ_VERSION_V2); - - if (!brcmf_feat_is_enabled(ifp, BRCMF_FEAT_SCAN_V2)) { + if (brcmf_feat_is_enabled(ifp, BRCMF_FEAT_SCAN_V3)) { + params->version = cpu_to_le32(BRCMF_ESCAN_REQ_VERSION_V3); + } else if (brcmf_feat_is_enabled(ifp, BRCMF_FEAT_SCAN_V2)) { + params->version = cpu_to_le32(BRCMF_ESCAN_REQ_VERSION_V2); + } else { struct brcmf_escan_params_le *params_v1; params_size -= BRCMF_SCAN_PARAMS_V2_FIXED_SIZE; diff --git a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/feature.c b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/feature.c index 488364ef8ff2a1..a5661af031d234 100644 --- a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/feature.c +++ b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/feature.c @@ -289,6 +289,7 @@ static int brcmf_feat_fwcap_debugfs_read(struct seq_file *seq, void *data) void brcmf_feat_attach(struct brcmf_pub *drvr) { struct brcmf_if *ifp = brcmf_get_ifp(drvr, 0); + struct brcmf_wl_scan_version_le scan_ver; struct brcmf_pno_macaddr_le pfn_mac; struct brcmf_gscan_config gscan_cfg; u32 wowl_cap; @@ -339,7 +340,20 @@ void brcmf_feat_attach(struct brcmf_pub *drvr) ifp->drvr->feat_flags |= BIT(BRCMF_FEAT_SCAN_RANDOM_MAC); brcmf_feat_iovar_int_get(ifp, BRCMF_FEAT_FWSUP, "sup_wpa"); - brcmf_feat_iovar_int_get(ifp, BRCMF_FEAT_SCAN_V2, "scan_ver"); + + err = brcmf_fil_iovar_data_get(ifp, "scan_ver", &scan_ver, sizeof(scan_ver)); + if (!err) { + int ver = le16_to_cpu(scan_ver.scan_ver_major); + + if (ver == 2) { + ifp->drvr->feat_flags |= BIT(BRCMF_FEAT_SCAN_V2); + } else if (ver == 3) { + /* We consider SCAN_V3 a subtype of SCAN_V2 since the + * structure is essentially the same. + */ + ifp->drvr->feat_flags |= BIT(BRCMF_FEAT_SCAN_V2) | BIT(BRCMF_FEAT_SCAN_V3); + } + } brcmf_feat_wlc_version_overrides(drvr); brcmf_feat_firmware_overrides(drvr); diff --git a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/feature.h b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/feature.h index 31f8695ca41765..99f6c3d983a398 100644 --- a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/feature.h +++ b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/feature.h @@ -57,6 +57,7 @@ BRCMF_FEAT_DEF(FWAUTH) \ BRCMF_FEAT_DEF(DUMP_OBSS) \ BRCMF_FEAT_DEF(SCAN_V2) \ + BRCMF_FEAT_DEF(SCAN_V3) \ BRCMF_FEAT_DEF(PMKID_V2) \ BRCMF_FEAT_DEF(PMKID_V3) \ BRCMF_FEAT_DEF(SAE_EXT) diff --git a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/fwil_types.h b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/fwil_types.h index e74a23e11830c1..7ff6cf948e624d 100644 --- a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/fwil_types.h +++ b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/fwil_types.h @@ -52,6 +52,7 @@ /* version of brcmf_scan_params structure */ #define BRCMF_SCAN_PARAMS_VERSION_V2 2 +#define BRCMF_SCAN_PARAMS_VERSION_V3 3 /* masks for channel and ssid count */ #define BRCMF_SCAN_PARAMS_COUNT_MASK 0x0000ffff @@ -72,6 +73,7 @@ #define DOT11_BSSTYPE_ANY 2 #define BRCMF_ESCAN_REQ_VERSION 1 #define BRCMF_ESCAN_REQ_VERSION_V2 2 +#define BRCMF_ESCAN_REQ_VERSION_V3 3 #define BRCMF_MAXRATES_IN_SET 16 /* max # of rates in rateset */ @@ -414,7 +416,7 @@ struct brcmf_scan_params_v2_le { s8 bss_type; /* default: any, * DOT11_BSSTYPE_ANY/INFRASTRUCTURE/INDEPENDENT */ - u8 pad; + u8 ssid_type; /* v3 only */ __le32 scan_type; /* flags, 0 use default */ __le32 nprobes; /* -1 use default, number of probes per channel */ __le32 active_time; /* -1 use default, dwell time per channel for @@ -833,6 +835,17 @@ struct brcmf_wlc_version_le { __le16 wlc_ver_minor; }; +/** + * struct brcmf_wl_scan_version_le - scan interface version + */ +struct brcmf_wl_scan_version_le { + __le16 version; + __le16 length; + __le16 scan_ver_major; +}; + +#define BRCMF_WL_SCAN_VERSION_VERSION 1 + /** * struct brcmf_assoclist_le - request assoc list. * From 7afd758b6830bd86ed013701ec355b08672b3e5c Mon Sep 17 00:00:00 2001 From: Hector Martin Date: Wed, 4 Oct 2023 23:54:19 +0900 Subject: [PATCH 0220/3902] wifi: brcmfmac: Implement event_msgs_ext This extended command supports bit set/clear operations, but we just use it like the old full mask set command. Signed-off-by: Hector Martin --- .../broadcom/brcm80211/brcmfmac/common.c | 23 +--- .../broadcom/brcm80211/brcmfmac/core.c | 5 + .../broadcom/brcm80211/brcmfmac/feature.c | 1 + .../broadcom/brcm80211/brcmfmac/feature.h | 3 +- .../broadcom/brcm80211/brcmfmac/fweh.c | 102 +++++++++++++++--- .../broadcom/brcm80211/brcmfmac/fweh.h | 1 + .../broadcom/brcm80211/brcmfmac/fwil_types.h | 27 +++++ 7 files changed, 127 insertions(+), 35 deletions(-) diff --git a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/common.c b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/common.c index 688f16c513192d..b80e0b4ad422ab 100644 --- a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/common.c +++ b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/common.c @@ -13,6 +13,7 @@ #include "core.h" #include "bus.h" #include "debug.h" +#include "fweh.h" #include "fwil.h" #include "fwil_types.h" #include "tracepoint.h" @@ -266,7 +267,6 @@ static int brcmf_c_process_cal_blob(struct brcmf_if *ifp) int brcmf_c_preinit_dcmds(struct brcmf_if *ifp) { struct brcmf_pub *drvr = ifp->drvr; - struct brcmf_fweh_info *fweh = drvr->fweh; u8 buf[BRCMF_DCMD_SMLEN]; struct brcmf_bus *bus; struct brcmf_rev_info_le revinfo; @@ -412,27 +412,6 @@ int brcmf_c_preinit_dcmds(struct brcmf_if *ifp) brcmf_c_set_joinpref_default(ifp); - /* Setup event_msgs, enable E_IF */ - err = brcmf_fil_iovar_data_get(ifp, "event_msgs", fweh->event_mask, - fweh->event_mask_len); - if (err) { - bphy_err(drvr, "Get event_msgs error (%d)\n", err); - goto done; - } - /* - * BRCMF_E_IF can safely be used to set the appropriate bit - * in the event_mask as the firmware event code is guaranteed - * to match the value of BRCMF_E_IF because it is old cruft - * that all vendors have. - */ - setbit(fweh->event_mask, BRCMF_E_IF); - err = brcmf_fil_iovar_data_set(ifp, "event_msgs", fweh->event_mask, - fweh->event_mask_len); - if (err) { - bphy_err(drvr, "Set event_msgs error (%d)\n", err); - goto done; - } - /* Setup default scan channel time */ err = brcmf_fil_cmd_int_set(ifp, BRCMF_C_SET_SCAN_CHANNEL_TIME, BRCMF_DEFAULT_SCAN_CHANNEL_TIME); diff --git a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/core.c b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/core.c index 862a0336a0b591..260daa64c1cd58 100644 --- a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/core.c +++ b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/core.c @@ -1229,6 +1229,11 @@ static int brcmf_bus_started(struct brcmf_pub *drvr, struct cfg80211_ops *ops) brcmf_feat_attach(drvr); + /* Setup event_msgs, enable E_IF */ + ret = brcmf_fweh_init_events(ifp); + if (ret) + goto fail; + ret = brcmf_proto_init_done(drvr); if (ret < 0) goto fail; diff --git a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/feature.c b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/feature.c index a5661af031d234..5dadc704985b3c 100644 --- a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/feature.c +++ b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/feature.c @@ -332,6 +332,7 @@ void brcmf_feat_attach(struct brcmf_pub *drvr) brcmf_feat_iovar_int_get(ifp, BRCMF_FEAT_TDLS, "tdls_enable"); brcmf_feat_iovar_int_get(ifp, BRCMF_FEAT_MFP, "mfp"); brcmf_feat_iovar_int_get(ifp, BRCMF_FEAT_DUMP_OBSS, "dump_obss"); + brcmf_feat_iovar_int_get(ifp, BRCMF_FEAT_EVENT_MSGS_EXT, "event_msgs_ext"); pfn_mac.version = BRCMF_PFN_MACADDR_CFG_VER; err = brcmf_fil_iovar_data_get(ifp, "pfn_macaddr", &pfn_mac, diff --git a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/feature.h b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/feature.h index 99f6c3d983a398..a275b7f9811576 100644 --- a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/feature.h +++ b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/feature.h @@ -60,7 +60,8 @@ BRCMF_FEAT_DEF(SCAN_V3) \ BRCMF_FEAT_DEF(PMKID_V2) \ BRCMF_FEAT_DEF(PMKID_V3) \ - BRCMF_FEAT_DEF(SAE_EXT) + BRCMF_FEAT_DEF(SAE_EXT) \ + BRCMF_FEAT_DEF(EVENT_MSGS_EXT) \ /* * Quirks: diff --git a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/fweh.c b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/fweh.c index c2d98ee6652f3a..09819d233af97e 100644 --- a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/fweh.c +++ b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/fweh.c @@ -11,8 +11,10 @@ #include "core.h" #include "debug.h" #include "tracepoint.h" +#include "feature.h" #include "fweh.h" #include "fwil.h" +#include "fwil_types.h" #include "proto.h" #include "bus.h" #include "fwvid.h" @@ -425,6 +427,67 @@ void brcmf_fweh_unregister(struct brcmf_pub *drvr, drvr->fweh->evt_handler[evt_handler_idx] = NULL; } +/** + * brcmf_fweh_init_events() - initialize event handling. + * + * @ifp: primary interface object. + */ +int brcmf_fweh_init_events(struct brcmf_if *ifp) +{ + struct brcmf_pub *drvr = ifp->drvr; + struct brcmf_eventmsgs_ext_le *eventmsgs; + size_t size = sizeof(*eventmsgs) + drvr->fweh->event_mask_len; + int err; + + eventmsgs = kzalloc(size, GFP_KERNEL); + if(!eventmsgs) + return -ENOMEM; + + eventmsgs->version = EVENTMSGS_VER; + eventmsgs->command = EVENTMSGS_NONE; + eventmsgs->len = drvr->fweh->event_mask_len; + eventmsgs->maxgetsize = drvr->fweh->event_mask_len; + + if (brcmf_feat_is_enabled(ifp, BRCMF_FEAT_EVENT_MSGS_EXT)) + err = brcmf_fil_iovar_data_get(ifp, "event_msgs_ext", + eventmsgs, size); + else + err = brcmf_fil_iovar_data_get(ifp, "event_msgs", + drvr->fweh->event_mask, + drvr->fweh->event_mask_len); + + if (err) { + bphy_err(drvr, "Get event_msgs error (%d)\n", err); + kfree(eventmsgs); + return err; + } + + brcmf_dbg(EVENT, "Event mask len: driver=%d fw=%d\n", + drvr->fweh->event_mask_len, eventmsgs->len); + + /* want to handle IF event as well */ + brcmf_dbg(EVENT, "enable event IF\n"); + setbit(eventmsgs->mask, BRCMF_E_IF); + + eventmsgs->version = EVENTMSGS_VER; + eventmsgs->command = EVENTMSGS_SET_MASK; + eventmsgs->len = drvr->fweh->event_mask_len; + + if (brcmf_feat_is_enabled(ifp, BRCMF_FEAT_EVENT_MSGS_EXT)) + err = brcmf_fil_iovar_data_set(ifp, "event_msgs_ext", + eventmsgs, size); + else + err = brcmf_fil_iovar_data_set(ifp, "event_msgs", + drvr->fweh->event_mask, + drvr->fweh->event_mask_len); + + if (err) + bphy_err(drvr, "Set event_msgs error (%d)\n", err); + + kfree(eventmsgs); + return err; +} + /** * brcmf_fweh_activate_events() - enables firmware events registered. * @@ -432,32 +495,47 @@ void brcmf_fweh_unregister(struct brcmf_pub *drvr, */ int brcmf_fweh_activate_events(struct brcmf_if *ifp) { - struct brcmf_fweh_info *fweh = ifp->drvr->fweh; - enum brcmf_fweh_event_code code; + struct brcmf_pub *drvr = ifp->drvr; + struct brcmf_eventmsgs_ext_le *eventmsgs; + size_t size = sizeof(*eventmsgs) + drvr->fweh->event_mask_len; int i, err; - memset(fweh->event_mask, 0, fweh->event_mask_len); - for (i = 0; i < fweh->num_event_codes; i++) { - if (fweh->evt_handler[i]) { - brcmf_fweh_map_fwevt_code(fweh, i, &code); + eventmsgs = kzalloc(size, GFP_KERNEL); + if(!eventmsgs) + return -ENOMEM; + + for (i = 0; i < drvr->fweh->num_event_codes; i++) { + if (drvr->fweh->evt_handler[i]) { brcmf_dbg(EVENT, "enable event %s\n", - brcmf_fweh_event_name(code)); - setbit(fweh->event_mask, i); + brcmf_fweh_event_name(i)); + setbit(eventmsgs->mask, i); } } /* want to handle IF event as well */ brcmf_dbg(EVENT, "enable event IF\n"); - setbit(fweh->event_mask, BRCMF_E_IF); + setbit(eventmsgs->mask, BRCMF_E_IF); + + eventmsgs->version = EVENTMSGS_VER; + eventmsgs->command = EVENTMSGS_SET_MASK; + eventmsgs->len = drvr->fweh->event_mask_len; /* allow per-vendor method to activate firmware events */ if (!brcmf_fwvid_activate_events(ifp)) return 0; - err = brcmf_fil_iovar_data_set(ifp, "event_msgs", fweh->event_mask, - fweh->event_mask_len); + if (brcmf_feat_is_enabled(ifp, BRCMF_FEAT_EVENT_MSGS_EXT)) + err = brcmf_fil_iovar_data_set(ifp, "event_msgs_ext", + eventmsgs, size); + else + err = brcmf_fil_iovar_data_set(ifp, "event_msgs", + drvr->fweh->event_mask, + drvr->fweh->event_mask_len); + if (err) - bphy_err(fweh->drvr, "Set event_msgs error (%d)\n", err); + bphy_err(drvr, "Set event_msgs error (%d)\n", err); + + kfree(eventmsgs); return err; } diff --git a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/fweh.h b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/fweh.h index e327dd58d29c95..53c4b58e6323cc 100644 --- a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/fweh.h +++ b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/fweh.h @@ -356,6 +356,7 @@ int brcmf_fweh_register(struct brcmf_pub *drvr, enum brcmf_fweh_event_code code, void *data)); void brcmf_fweh_unregister(struct brcmf_pub *drvr, enum brcmf_fweh_event_code code); +int brcmf_fweh_init_events(struct brcmf_if *ifp); int brcmf_fweh_activate_events(struct brcmf_if *ifp); void brcmf_fweh_process_event(struct brcmf_pub *drvr, struct brcmf_event *event_packet, diff --git a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/fwil_types.h b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/fwil_types.h index 7ff6cf948e624d..74f4c7a72596ec 100644 --- a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/fwil_types.h +++ b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/fwil_types.h @@ -1249,4 +1249,31 @@ struct brcmf_mkeep_alive_pkt_le { u8 data[]; } __packed; +enum event_msgs_ext_command { + EVENTMSGS_NONE = 0, + EVENTMSGS_SET_BIT = 1, + EVENTMSGS_RESET_BIT = 2, + EVENTMSGS_SET_MASK = 3 +}; + +#define EVENTMSGS_VER 1 + +/** + * struct brcmf_eventmsgs_ext_le - new event message mask commands + * + * @version: EVENTMSGS_VER + * @command: one of enum event_msgs_ext_command + * @len: for set, the mask size from the application to the firmware. + * for get, the actual firmware mask size. + * @maxgetsize: for get, the max size that the application can read from + * the firmware. + */ +struct brcmf_eventmsgs_ext_le { + u8 version; + u8 command; + u8 len; + u8 maxgetsize; + u8 mask[]; +}; + #endif /* FWIL_TYPES_H_ */ From 53ad1139291a69dd7af88d26374bde36f961a048 Mon Sep 17 00:00:00 2001 From: Hector Martin Date: Thu, 5 Oct 2023 00:34:04 +0900 Subject: [PATCH 0221/3902] wifi: brcmfmac: Support bss_info up to v112 The structures are compatible and just add fields, so we can just treat it as always v112. If we start using new fields, that will have to be gated on the version. Signed-off-by: Hector Martin --- .../broadcom/brcm80211/brcmfmac/cfg80211.c | 5 ++- .../broadcom/brcm80211/brcmfmac/fwil_types.h | 37 +++++++++++++++++-- 2 files changed, 36 insertions(+), 6 deletions(-) diff --git a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/cfg80211.c b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/cfg80211.c index 9709eeb03039dc..6290ec350c7e50 100644 --- a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/cfg80211.c +++ b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/cfg80211.c @@ -3432,8 +3432,9 @@ static s32 brcmf_inform_bss(struct brcmf_cfg80211_info *cfg) bss_list = (struct brcmf_scan_results *)cfg->escan_info.escan_buf; if (bss_list->count != 0 && - bss_list->version != BRCMF_BSS_INFO_VERSION) { - bphy_err(drvr, "Version %d != WL_BSS_INFO_VERSION\n", + (bss_list->version < BRCMF_BSS_INFO_MIN_VERSION || + bss_list->version > BRCMF_BSS_INFO_MAX_VERSION)) { + bphy_err(drvr, "BSS info version %d unsupported\n", bss_list->version); return -EOPNOTSUPP; } diff --git a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/fwil_types.h b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/fwil_types.h index 74f4c7a72596ec..cd7057e6b13adb 100644 --- a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/fwil_types.h +++ b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/fwil_types.h @@ -18,7 +18,8 @@ #define BRCMF_ARP_OL_HOST_AUTO_REPLY 0x00000004 #define BRCMF_ARP_OL_PEER_AUTO_REPLY 0x00000008 -#define BRCMF_BSS_INFO_VERSION 109 /* curr ver of brcmf_bss_info_le struct */ +#define BRCMF_BSS_INFO_MIN_VERSION 109 /* min ver of brcmf_bss_info_le struct */ +#define BRCMF_BSS_INFO_MAX_VERSION 112 /* max ver of brcmf_bss_info_le struct */ #define BRCMF_BSS_RSSI_ON_CHANNEL 0x0004 #define BRCMF_STA_BRCM 0x00000001 /* Running a Broadcom driver */ @@ -323,28 +324,56 @@ struct brcmf_bss_info_le { __le16 capability; /* Capability information */ u8 SSID_len; u8 SSID[32]; + u8 bcnflags; /* additional flags w.r.t. beacon */ struct { __le32 count; /* # rates in this set */ u8 rates[16]; /* rates in 500kbps units w/hi bit set if basic */ } rateset; /* supported rates */ __le16 chanspec; /* chanspec for bss */ __le16 atim_window; /* units are Kusec */ - u8 dtim_period; /* DTIM period */ + u8 dtim_period; /* DTIM period */ + u8 accessnet; /* from beacon interwork IE (if bcnflags) */ __le16 RSSI; /* receive signal strength (in dBm) */ s8 phy_noise; /* noise (in dBm) */ u8 n_cap; /* BSS is 802.11N Capable */ + u8 he_cap; /* BSS is he capable */ + u8 load; /* BSS Load from QBSS load IE if available */ /* 802.11N BSS Capabilities (based on HT_CAP_*): */ __le32 nbss_cap; u8 ctl_ch; /* 802.11N BSS control channel number */ - __le32 reserved32[1]; /* Reserved for expansion of BSS properties */ + u8 reserved1[3]; /* Reserved for expansion of BSS properties */ + __le16 vht_rxmcsmap; /* VHT rx mcs map (802.11ac IE, VHT_CAP_MCS_MAP_*) */ + __le16 vht_txmcsmap; /* VHT tx mcs map (802.11ac IE, VHT_CAP_MCS_MAP_*) */ u8 flags; /* flags */ - u8 reserved[3]; /* Reserved for expansion of BSS properties */ + u8 vht_cap; /* BSS is vht capable */ + u8 reserved2[2]; /* Reserved for expansion of BSS properties */ u8 basic_mcs[BRCMF_MCSSET_LEN]; /* 802.11N BSS required MCS set */ __le16 ie_offset; /* offset at which IEs start, from beginning */ + u8 reserved3[2]; /* Reserved for expansion of BSS properties */ __le32 ie_length; /* byte length of Information Elements */ __le16 SNR; /* average SNR of during frame reception */ + __le16 vht_mcsmap; /**< STA's Associated vhtmcsmap */ + __le16 vht_mcsmap_prop; /**< STA's Associated prop vhtmcsmap */ + __le16 vht_txmcsmap_prop; /**< prop VHT tx mcs prop */ + __le32 he_mcsmap; /**< STA's Associated hemcsmap */ + __le32 he_rxmcsmap; /**< HE rx mcs map (802.11ax IE, HE_CAP_MCS_MAP_*) */ + __le32 he_txmcsmap; /**< HE tx mcs map (802.11ax IE, HE_CAP_MCS_MAP_*) */ + __le32 timestamp[2]; /* Beacon Timestamp for FAKEAP req */ + /* V112 fields follow */ + u8 eht_cap; /* BSS is EHT capable */ + u8 reserved4[3]; /* Reserved for expansion of BSS properties */ + /* by the spec. it is maximum 16 streams hence all mcs code for all nss may not fit + * in a 32 bit mcs nss map but since this field only reflects the common mcs nss map + * between that of the peer and our device so it's probably ok to make it 32 bit and + * allow only a limited number of nss e.g. upto 8 of them in the map given the fact + * that our device probably won't exceed 4 streams anyway... + */ + __le32 eht_mcsmap; /* STA's associated EHT mcs code map */ + /* FIXME: change the following mcs code map to uint32 if all mcs+nss can fit in */ + u8 eht_rxmcsmap[6]; /* EHT rx mcs code map */ + u8 eht_txmcsmap[6]; /* EHT tx mcs code map */ /* Add new fields here */ /* variable length Information Elements */ }; From 5e43d246c07e078f7e392584bf641dfe39d787c6 Mon Sep 17 00:00:00 2001 From: Hector Martin Date: Thu, 5 Oct 2023 00:34:44 +0900 Subject: [PATCH 0222/3902] wifi: brcmfmac: Extend brcmf_wsec_pmk_le New firmware wants extra fields, hopefully old firmware ignores them. Signed-off-by: Hector Martin --- .../net/wireless/broadcom/brcm80211/brcmfmac/fwil_types.h | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/fwil_types.h b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/fwil_types.h index cd7057e6b13adb..a4ec3808a5c84c 100644 --- a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/fwil_types.h +++ b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/fwil_types.h @@ -67,7 +67,7 @@ #define BRCMF_WSEC_MAX_PSK_LEN 32 #define BRCMF_WSEC_PASSPHRASE BIT(0) -#define BRCMF_WSEC_MAX_SAE_PASSWORD_LEN 128 +#define BRCMF_WSEC_MAX_SAE_PASSWORD_LEN 256 /* primary (ie tx) key */ #define BRCMF_PRIMARY_KEY (1 << 1) @@ -611,11 +611,15 @@ struct brcmf_wsec_key_le { * @key_len: number of octets in key material. * @flags: key handling qualifiers. * @key: PMK key material. + * @opt_len: optional field length + * @opt_tlvs: optional fields in TLV format */ struct brcmf_wsec_pmk_le { __le16 key_len; __le16 flags; u8 key[BRCMF_WSEC_MAX_SAE_PASSWORD_LEN]; + __le16 opt_len; + u8 opt_tlvs[]; }; /** From 8213257aac9744aaeff5fefcf56c1dbc7fe842ba Mon Sep 17 00:00:00 2001 From: Hector Martin Date: Mon, 2 Oct 2023 22:26:16 +0900 Subject: [PATCH 0223/3902] wifi: brcmfmac: Add BCM4388 support Signed-off-by: Hector Martin --- .../net/wireless/broadcom/brcm80211/brcmfmac/chip.c | 1 + .../net/wireless/broadcom/brcm80211/brcmfmac/pcie.c | 10 ++++++++++ .../wireless/broadcom/brcm80211/include/brcm_hw_ids.h | 2 ++ 3 files changed, 13 insertions(+) diff --git a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/chip.c b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/chip.c index 56290fe71cec8b..75b6bfa28b6c29 100644 --- a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/chip.c +++ b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/chip.c @@ -712,6 +712,7 @@ static u32 brcmf_chip_tcm_rambase(struct brcmf_chip_priv *ci) case BRCM_CC_4366_CHIP_ID: case BRCM_CC_43664_CHIP_ID: case BRCM_CC_43666_CHIP_ID: + case BRCM_CC_4388_CHIP_ID: return 0x200000; case BRCM_CC_4355_CHIP_ID: case BRCM_CC_4359_CHIP_ID: diff --git a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/pcie.c b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/pcie.c index 637f39ad813049..5ad17aa12ce3dd 100644 --- a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/pcie.c +++ b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/pcie.c @@ -71,6 +71,8 @@ BRCMF_FW_CLM_DEF(4377B3, "brcmfmac4377b3-pcie"); BRCMF_FW_CLM_DEF(4378B1, "brcmfmac4378b1-pcie"); BRCMF_FW_CLM_DEF(4378B3, "brcmfmac4378b3-pcie"); BRCMF_FW_CLM_DEF(4387C2, "brcmfmac4387c2-pcie"); +BRCMF_FW_CLM_DEF(4388B0, "brcmfmac4388b0-pcie"); +BRCMF_FW_CLM_DEF(4388C0, "brcmfmac4388c0-pcie"); BRCMF_FW_CLM_DEF(54591, "brcmfmac54591-pcie"); /* firmware config files */ @@ -112,6 +114,8 @@ static const struct brcmf_firmware_mapping brcmf_pcie_fwnames[] = { BRCMF_FW_ENTRY(BRCM_CC_4378_CHIP_ID, 0x0000000F, 4378B1), /* revision ID 3 */ BRCMF_FW_ENTRY(BRCM_CC_4378_CHIP_ID, 0xFFFFFFE0, 4378B3), /* revision ID 5 */ BRCMF_FW_ENTRY(BRCM_CC_4387_CHIP_ID, 0xFFFFFFFF, 4387C2), /* revision ID 7 */ + BRCMF_FW_ENTRY(BRCM_CC_4388_CHIP_ID, 0x0000000F, 4388B0), + BRCMF_FW_ENTRY(BRCM_CC_4388_CHIP_ID, 0xFFFFFFF0, 4388C0), /* revision ID 4 */ }; #define BRCMF_PCIE_FW_UP_TIMEOUT 5000 /* msec */ @@ -2399,6 +2403,11 @@ static int brcmf_pcie_read_otp(struct brcmf_pciedev_info *devinfo) base = 0x113c; words = 0x170; break; + case BRCM_CC_4388_CHIP_ID: + coreid = BCMA_CORE_GCI; + base = 0x115c; + words = 0x150; + break; default: /* OTP not supported on this chip */ return 0; @@ -3089,6 +3098,7 @@ static const struct pci_device_id brcmf_pcie_devid_table[] = { BRCMF_PCIE_DEVICE(BRCM_PCIE_4377_DEVICE_ID, WCC_SEED), BRCMF_PCIE_DEVICE(BRCM_PCIE_4378_DEVICE_ID, WCC_SEED), BRCMF_PCIE_DEVICE(BRCM_PCIE_4387_DEVICE_ID, WCC_SEED), + BRCMF_PCIE_DEVICE(BRCM_PCIE_4388_DEVICE_ID, WCC_SEED), BRCMF_PCIE_DEVICE(BRCM_PCIE_43752_DEVICE_ID, WCC_SEED), BRCMF_PCIE_DEVICE(CY_PCIE_54591_DEVICE_ID, CYW), { /* end: all zeroes */ } diff --git a/drivers/net/wireless/broadcom/brcm80211/include/brcm_hw_ids.h b/drivers/net/wireless/broadcom/brcm80211/include/brcm_hw_ids.h index df3b67ba4db290..f749337a06942e 100644 --- a/drivers/net/wireless/broadcom/brcm80211/include/brcm_hw_ids.h +++ b/drivers/net/wireless/broadcom/brcm80211/include/brcm_hw_ids.h @@ -57,6 +57,7 @@ #define BRCM_CC_4377_CHIP_ID 0x4377 #define BRCM_CC_4378_CHIP_ID 0x4378 #define BRCM_CC_4387_CHIP_ID 0x4387 +#define BRCM_CC_4388_CHIP_ID 0x4388 #define CY_CC_4373_CHIP_ID 0x4373 #define CY_CC_43012_CHIP_ID 43012 #define CY_CC_43439_CHIP_ID 43439 @@ -99,6 +100,7 @@ #define BRCM_PCIE_4377_DEVICE_ID 0x4488 #define BRCM_PCIE_4378_DEVICE_ID 0x4425 #define BRCM_PCIE_4387_DEVICE_ID 0x4433 +#define BRCM_PCIE_4388_DEVICE_ID 0x4434 #define CY_PCIE_54591_DEVICE_ID 0x4417 /* brcmsmac IDs */ From c6ee6cafd2d6b344d8eeb6cb3cdc4a734f62368b Mon Sep 17 00:00:00 2001 From: Patrick Blass Date: Sun, 3 Sep 2023 15:34:06 +0200 Subject: [PATCH 0224/3902] brcmfmac: Fix AP mode Fix access point mode by bringing firmware into appropriate state before setting up the device. Signed-off-by: Patrick Blass --- .../broadcom/brcm80211/brcmfmac/cfg80211.c | 19 +++++++++++++++++++ .../broadcom/brcm80211/include/brcmu_wifi.h | 2 ++ 2 files changed, 21 insertions(+) diff --git a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/cfg80211.c b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/cfg80211.c index 6290ec350c7e50..008e76c167de8d 100644 --- a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/cfg80211.c +++ b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/cfg80211.c @@ -5158,6 +5158,25 @@ brcmf_cfg80211_start_ap(struct wiphy *wiphy, struct net_device *ndev, settings->inactivity_timeout); dev_role = ifp->vif->wdev.iftype; mbss = ifp->vif->mbss; + /* Bring firmware into correct state for AP mode*/ + if (dev_role == NL80211_IFTYPE_AP) { + brcmf_dbg(TRACE, "set AP mode\n"); + err = brcmf_fil_cmd_int_set(ifp, BRCMF_C_SET_AP, 1); + if (err < 0) { + bphy_err(drvr, "setting AP mode failed %d\n", + err); + goto exit; + } + + bss_enable.bsscfgidx = cpu_to_le32(ifp->bsscfgidx); + bss_enable.enable = cpu_to_le32(WLC_AP_IOV_OP_MANUAL_AP_BSSCFG_CREATE); + err = brcmf_fil_iovar_data_set(ifp, "bss", &bss_enable, + sizeof(bss_enable)); + if (err < 0) { + bphy_err(drvr, "AP role set error, %d\n", err); + goto exit; + } + } /* store current 11d setting */ if (brcmf_fil_cmd_int_get(ifp, BRCMF_C_GET_REGULATORY, diff --git a/drivers/net/wireless/broadcom/brcm80211/include/brcmu_wifi.h b/drivers/net/wireless/broadcom/brcm80211/include/brcmu_wifi.h index 7552bdb91991ce..889dc7343899cf 100644 --- a/drivers/net/wireless/broadcom/brcm80211/include/brcmu_wifi.h +++ b/drivers/net/wireless/broadcom/brcm80211/include/brcmu_wifi.h @@ -94,6 +94,8 @@ #define WLC_BAND_2G 2 /* 2.4 Ghz */ #define WLC_BAND_ALL 3 /* all bands */ +#define WLC_AP_IOV_OP_MANUAL_AP_BSSCFG_CREATE 2 + #define CHSPEC_CHANNEL(chspec) ((u8)((chspec) & WL_CHANSPEC_CHAN_MASK)) #define CHSPEC_BAND(chspec) ((chspec) & WL_CHANSPEC_BAND_MASK) From 06592d122f61417c97dc3aac2869245028523204 Mon Sep 17 00:00:00 2001 From: Daniel Berlin Date: Tue, 24 Oct 2023 09:49:40 -0400 Subject: [PATCH 0225/3902] [brcmfmac] Finish firmware mem map, fix heap start calculation bug. This patch fixes the firmware memory map structure to be complete. Along the way, we fix a failure to align the heap memory start address, which causes failures with the newest apple wifi firmware. With this patch, we can load the latest (sonoma 14.0 as of right now) apple wifi firmware. Signed-off-by: Daniel Berlin --- .../broadcom/brcm80211/brcmfmac/pcie.c | 83 ++++++++++++------- 1 file changed, 53 insertions(+), 30 deletions(-) diff --git a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/pcie.c b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/pcie.c index 5ad17aa12ce3dd..48de33ca6da2b3 100644 --- a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/pcie.c +++ b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/pcie.c @@ -1868,35 +1868,58 @@ struct brcmf_rtlv_footer { __le32 magic; }; -struct brcmf_fw_memmap { - u32 pad1[8]; - u32 vstatus_start; - u32 vstatus_end; - u32 fw_start; - u32 fw_end; - u32 sig_start; - u32 sig_end; - u32 heap_start; - u32 heap_end; - u32 pad2[6]; +/** struct brcmf_fw_memmap_region - start/end of memory regions for chip + */ +struct brcmf_fw_memmap_region { + u32 start; + u32 end; }; +/** struct brcmf_fw_memmap + * + * @reset_vec - Reset vector - read only + * @int_vec - copied from ram, jumps here on success + * @rom - bootloader at rom start + * @mmap - struct/memory map written by host + * @vstatus - verification status + * @fw - firmware + * @sig - firwmare signature + * @heap - region for heap allocations + * @stack - region for stack allocations + * @prng - PRNG data, may be 0 length + * @nvram - NVRAM data + */ +struct brcmf_fw_memmap { + struct brcmf_fw_memmap_region reset_vec; + struct brcmf_fw_memmap_region int_vec; + struct brcmf_fw_memmap_region rom; + struct brcmf_fw_memmap_region mmap; + struct brcmf_fw_memmap_region vstatus; + struct brcmf_fw_memmap_region fw; + struct brcmf_fw_memmap_region sig; + struct brcmf_fw_memmap_region heap; + struct brcmf_fw_memmap_region stack; + struct brcmf_fw_memmap_region prng; + struct brcmf_fw_memmap_region nvram; +}; #define BRCMF_BL_HEAP_START_GAP 0x1000 #define BRCMF_BL_HEAP_SIZE 0x10000 #define BRCMF_RANDOM_SEED_MAGIC 0xfeedc0de #define BRCMF_RANDOM_SEED_LENGTH 0x100 -#define BRCMF_SIG_MAGIC 0xfeedfe51 +#define BRCMF_FW_SIG_MAGIC 0xfeedfe51 +#define BRCMF_NVRAM_SIG_MAGIC 0xfeedfe52 +#define BRCMF_MEMMAP_MAGIC 0xfeedfe53 #define BRCMF_VSTATUS_MAGIC 0xfeedfe54 #define BRCMF_VSTATUS_SIZE 0x28 -#define BRCMF_MEMMAP_MAGIC 0xfeedfe53 #define BRCMF_END_MAGIC 0xfeed0e2d -static int brcmf_alloc_rtlv(struct brcmf_pciedev_info *devinfo, u32 *address, u32 type, size_t length) +static int brcmf_alloc_rtlv(struct brcmf_pciedev_info *devinfo, u32 *address, u32 type, u32 length) { struct brcmf_bus *bus = dev_get_drvdata(&devinfo->pdev->dev); - u32 boundary = devinfo->ci->rambase + devinfo->fw_size + - BRCMF_BL_HEAP_START_GAP + BRCMF_BL_HEAP_SIZE; + u32 fw_top = devinfo->ci->rambase + devinfo->fw_size; + u32 ram_start = ALIGN(fw_top + BRCMF_BL_HEAP_START_GAP, 4); + u32 ram_end = ram_start + BRCMF_BL_HEAP_SIZE; u32 start_addr; struct brcmf_rtlv_footer footer = { .magic = type, @@ -1905,8 +1928,8 @@ static int brcmf_alloc_rtlv(struct brcmf_pciedev_info *devinfo, u32 *address, u3 length = ALIGN(length, 4); start_addr = *address - length - sizeof(struct brcmf_rtlv_footer); - if (length > 0xffff || start_addr > *address || start_addr < boundary) { - brcmf_err(bus, "failed to allocate 0x%zx bytes for rTLV type 0x%x\n", + if (length > 0xffff || start_addr > *address || start_addr < ram_end) { + brcmf_err(bus, "failed to allocate 0x%x bytes for rTLV type 0x%x\n", length, type); return -ENOMEM; } @@ -1957,32 +1980,32 @@ static int brcmf_pcie_add_signature(struct brcmf_pciedev_info *devinfo, memset(&memmap, 0, sizeof(memmap)); - memmap.sig_end = *address; - err = brcmf_alloc_rtlv(devinfo, address, BRCMF_SIG_MAGIC, fwsig->size); + memmap.sig.end = *address; + err = brcmf_alloc_rtlv(devinfo, address, BRCMF_FW_SIG_MAGIC, fwsig->size); if (err) return err; - memmap.sig_start = *address; + memmap.sig.start = *address; - memmap.vstatus_end = *address; + memmap.vstatus.end = *address; err = brcmf_alloc_rtlv(devinfo, address, BRCMF_VSTATUS_MAGIC, BRCMF_VSTATUS_SIZE); if (err) return err; - memmap.vstatus_start = *address; + memmap.vstatus.start = *address; err = brcmf_alloc_rtlv(devinfo, address, BRCMF_MEMMAP_MAGIC, sizeof(memmap)); if (err) return err; - memmap.fw_start = devinfo->ci->rambase; - memmap.fw_end = memmap.fw_start + devinfo->fw_size; - memmap.heap_start = memmap.fw_end + BRCMF_BL_HEAP_START_GAP; - memmap.heap_end = memmap.heap_start + BRCMF_BL_HEAP_SIZE; + memmap.fw.start = devinfo->ci->rambase; + memmap.fw.end = memmap.fw.start + devinfo->fw_size; + memmap.heap.start = ALIGN(memmap.fw.end + BRCMF_BL_HEAP_START_GAP, 4); + memmap.heap.end = memmap.heap.start + BRCMF_BL_HEAP_SIZE; - if (memmap.heap_end > *address) + if (memmap.heap.end > *address) return -ENOMEM; - memcpy_toio(devinfo->tcm + memmap.sig_start, fwsig->data, fwsig->size); - memset_io(devinfo->tcm + memmap.vstatus_start, 0, BRCMF_VSTATUS_SIZE); + memcpy_toio(devinfo->tcm + memmap.sig.start, fwsig->data, fwsig->size); + memset_io(devinfo->tcm + memmap.vstatus.start, 0, BRCMF_VSTATUS_SIZE); memcpy_toio(devinfo->tcm + *address, &memmap, sizeof(memmap)); err = brcmf_alloc_rtlv(devinfo, address, BRCMF_END_MAGIC, 0); From 10e5619a62fe654d8bf1f7f328f62cdd9a600466 Mon Sep 17 00:00:00 2001 From: Daniel Berlin Date: Sat, 14 Oct 2023 11:38:13 -0400 Subject: [PATCH 0226/3902] [brcmfmac] Add support for encoding/decoding 6g chanspecs This patch adds support for 6G chanspecs, as part of adding 6G and 802.11ax support. Signed-off-by: Daniel Berlin --- .../broadcom/brcm80211/brcmfmac/cfg80211.c | 1 - .../broadcom/brcm80211/brcmutil/d11.c | 46 +++++++++++++++---- .../broadcom/brcm80211/include/brcmu_d11.h | 46 +++++++++++++------ .../broadcom/brcm80211/include/brcmu_wifi.h | 27 ++++++++--- 4 files changed, 89 insertions(+), 31 deletions(-) diff --git a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/cfg80211.c b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/cfg80211.c index 008e76c167de8d..b7b058220ae9c9 100644 --- a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/cfg80211.c +++ b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/cfg80211.c @@ -7055,7 +7055,6 @@ static int brcmf_construct_chaninfo(struct brcmf_cfg80211_info *cfg, if (band) for (i = 0; i < band->n_channels; i++) band->channels[i].flags = IEEE80211_CHAN_DISABLED; - total = le32_to_cpu(list->count); if (total > BRCMF_MAX_CHANSPEC_LIST) { bphy_err(drvr, "Invalid count of channel Spec. (%u)\n", diff --git a/drivers/net/wireless/broadcom/brcm80211/brcmutil/d11.c b/drivers/net/wireless/broadcom/brcm80211/brcmutil/d11.c index 1e2b1e487eb76e..faf7eeeeb2d57e 100644 --- a/drivers/net/wireless/broadcom/brcm80211/brcmutil/d11.c +++ b/drivers/net/wireless/broadcom/brcm80211/brcmutil/d11.c @@ -87,10 +87,20 @@ static void brcmu_d11ac_encchspec(struct brcmu_chan *ch) 0, d11ac_bw(ch->bw)); ch->chspec &= ~BRCMU_CHSPEC_D11AC_BND_MASK; - if (ch->chnum <= CH_MAX_2G_CHANNEL) - ch->chspec |= BRCMU_CHSPEC_D11AC_BND_2G; - else + switch (ch->band) { + case BRCMU_CHAN_BAND_6G: + ch->chspec |= BRCMU_CHSPEC_D11AC_BND_6G; + break; + case BRCMU_CHAN_BAND_5G: ch->chspec |= BRCMU_CHSPEC_D11AC_BND_5G; + break; + case BRCMU_CHAN_BAND_2G: + ch->chspec |= BRCMU_CHSPEC_D11AC_BND_2G; + break; + default: + WARN_ONCE(1, "Invalid band 0x%04x\n", ch->band); + break; + } } static void brcmu_d11n_decchspec(struct brcmu_chan *ch) @@ -117,7 +127,9 @@ static void brcmu_d11n_decchspec(struct brcmu_chan *ch) } break; default: - WARN_ONCE(1, "Invalid chanspec 0x%04x\n", ch->chspec); + WARN_ONCE(1, + "Invalid chanspec - unknown 11n bandwidth 0x%04x\n", + ch->chspec); break; } @@ -129,7 +141,8 @@ static void brcmu_d11n_decchspec(struct brcmu_chan *ch) ch->band = BRCMU_CHAN_BAND_2G; break; default: - WARN_ONCE(1, "Invalid chanspec 0x%04x\n", ch->chspec); + WARN_ONCE(1, "Invalid chanspec - unknown 11n band 0x%04x\n", + ch->chspec); break; } } @@ -156,7 +169,9 @@ static void brcmu_d11ac_decchspec(struct brcmu_chan *ch) ch->sb = BRCMU_CHAN_SB_U; ch->control_ch_num += CH_10MHZ_APART; } else { - WARN_ONCE(1, "Invalid chanspec 0x%04x\n", ch->chspec); + WARN_ONCE(1, + "Invalid chanspec - unknown 11ac channel distance 0x%04x\n", + ch->chspec); } break; case BRCMU_CHSPEC_D11AC_BW_80: @@ -177,7 +192,9 @@ static void brcmu_d11ac_decchspec(struct brcmu_chan *ch) ch->control_ch_num += CH_30MHZ_APART; break; default: - WARN_ONCE(1, "Invalid chanspec 0x%04x\n", ch->chspec); + WARN_ONCE(1, + "Invalid chanspec - unknown 11ac channel distance 0x%04x\n", + ch->chspec); break; } break; @@ -211,17 +228,24 @@ static void brcmu_d11ac_decchspec(struct brcmu_chan *ch) ch->control_ch_num += CH_70MHZ_APART; break; default: - WARN_ONCE(1, "Invalid chanspec 0x%04x\n", ch->chspec); + WARN_ONCE(1, + "Invalid chanspec - unknown 11ac channel distance 0x%04x\n", + ch->chspec); break; } break; case BRCMU_CHSPEC_D11AC_BW_8080: default: - WARN_ONCE(1, "Invalid chanspec 0x%04x\n", ch->chspec); + WARN_ONCE(1, + "Invalid chanspec - unknown 11ac channel bandwidth 0x%04x\n", + ch->chspec); break; } switch (ch->chspec & BRCMU_CHSPEC_D11AC_BND_MASK) { + case BRCMU_CHSPEC_D11AC_BND_6G: + ch->band = BRCMU_CHAN_BAND_6G; + break; case BRCMU_CHSPEC_D11AC_BND_5G: ch->band = BRCMU_CHAN_BAND_5G; break; @@ -229,7 +253,9 @@ static void brcmu_d11ac_decchspec(struct brcmu_chan *ch) ch->band = BRCMU_CHAN_BAND_2G; break; default: - WARN_ONCE(1, "Invalid chanspec 0x%04x\n", ch->chspec); + WARN_ONCE(1, + "Invalid chanspec - unknown 11ac channel band 0x%04x\n", + ch->chspec); break; } } diff --git a/drivers/net/wireless/broadcom/brcm80211/include/brcmu_d11.h b/drivers/net/wireless/broadcom/brcm80211/include/brcmu_d11.h index f6344023855c36..bb48b744206223 100644 --- a/drivers/net/wireless/broadcom/brcm80211/include/brcmu_d11.h +++ b/drivers/net/wireless/broadcom/brcm80211/include/brcmu_d11.h @@ -69,24 +69,44 @@ #define BRCMU_CHSPEC_D11AC_SB_UU BRCMU_CHSPEC_D11AC_SB_LUU #define BRCMU_CHSPEC_D11AC_SB_L BRCMU_CHSPEC_D11AC_SB_LLL #define BRCMU_CHSPEC_D11AC_SB_U BRCMU_CHSPEC_D11AC_SB_LLU +/* channel sideband indication for frequency >= 240MHz */ +#define BRCMU_CHSPEC_D11AC_320_SB_MASK 0x0780 +#define BRCMU_CHSPEC_D11AC_320_SB_SHIFT 7 +#define BRCMU_CHSPEC_D11AC_SB_LLLL 0x0000 +#define BRCMU_CHSPEC_D11AC_SB_LLLU 0x0080 +#define BRCMU_CHSPEC_D11AC_SB_LLUL 0x0100 +#define BRCMU_CHSPEC_D11AC_SB_LLUU 0x0180 +#define BRCMU_CHSPEC_D11AC_SB_LULL 0x0200 +#define BRCMU_CHSPEC_D11AC_SB_LULU 0x0280 +#define BRCMU_CHSPEC_D11AC_SB_LUUL 0x0300 +#define BRCMU_CHSPEC_D11AC_SB_LUUU 0x0380 +#define BRCMU_CHSPEC_D11AC_SB_ULLL 0x0400 +#define BRCMU_CHSPEC_D11AC_SB_ULLU 0x0480 +#define BRCMU_CHSPEC_D11AC_SB_ULUL 0x0500 +#define BRCMU_CHSPEC_D11AC_SB_ULUU 0x0580 +#define BRCMU_CHSPEC_D11AC_SB_UULL 0x0600 +#define BRCMU_CHSPEC_D11AC_SB_UULU 0x0680 +#define BRCMU_CHSPEC_D11AC_SB_UUUL 0x0700 +#define BRCMU_CHSPEC_D11AC_SB_UUUU 0x0780 #define BRCMU_CHSPEC_D11AC_BW_MASK 0x3800 #define BRCMU_CHSPEC_D11AC_BW_SHIFT 11 -#define BRCMU_CHSPEC_D11AC_BW_5 0x0000 -#define BRCMU_CHSPEC_D11AC_BW_10 0x0800 -#define BRCMU_CHSPEC_D11AC_BW_20 0x1000 -#define BRCMU_CHSPEC_D11AC_BW_40 0x1800 -#define BRCMU_CHSPEC_D11AC_BW_80 0x2000 -#define BRCMU_CHSPEC_D11AC_BW_160 0x2800 -#define BRCMU_CHSPEC_D11AC_BW_8080 0x3000 -#define BRCMU_CHSPEC_D11AC_BND_MASK 0xc000 -#define BRCMU_CHSPEC_D11AC_BND_SHIFT 14 -#define BRCMU_CHSPEC_D11AC_BND_2G 0x0000 -#define BRCMU_CHSPEC_D11AC_BND_3G 0x4000 -#define BRCMU_CHSPEC_D11AC_BND_4G 0x8000 -#define BRCMU_CHSPEC_D11AC_BND_5G 0xc000 +#define BRCMU_CHSPEC_D11AC_BW_10 0x0800 +#define BRCMU_CHSPEC_D11AC_BW_20 0x1000 +#define BRCMU_CHSPEC_D11AC_BW_40 0x1800 +#define BRCMU_CHSPEC_D11AC_BW_80 0x2000 +#define BRCMU_CHSPEC_D11AC_BW_160 0x2800 +#define BRCMU_CHSPEC_D11AC_BW_320 0x0000 +#define BRCMU_CHSPEC_D11AC_BW_8080 0x3000 +#define BRCMU_CHSPEC_D11AC_BND_MASK 0xc000 +#define BRCMU_CHSPEC_D11AC_BND_SHIFT 14 +#define BRCMU_CHSPEC_D11AC_BND_2G 0x0000 +#define BRCMU_CHSPEC_D11AC_BND_4G 0x8000 +#define BRCMU_CHSPEC_D11AC_BND_5G 0xc000 +#define BRCMU_CHSPEC_D11AC_BND_6G 0x4000 #define BRCMU_CHAN_BAND_2G 0 #define BRCMU_CHAN_BAND_5G 1 +#define BRCMU_CHAN_BAND_6G 2 enum brcmu_chan_bw { BRCMU_CHAN_BW_20, diff --git a/drivers/net/wireless/broadcom/brcm80211/include/brcmu_wifi.h b/drivers/net/wireless/broadcom/brcm80211/include/brcmu_wifi.h index 889dc7343899cf..e054b84443563e 100644 --- a/drivers/net/wireless/broadcom/brcm80211/include/brcmu_wifi.h +++ b/drivers/net/wireless/broadcom/brcm80211/include/brcmu_wifi.h @@ -31,6 +31,7 @@ /* bandstate array indices */ #define BAND_2G_INDEX 0 /* wlc->bandstate[x] index */ #define BAND_5G_INDEX 1 /* wlc->bandstate[x] index */ +#define BAND_6G_INDEX 2 /* wlc->bandstate[x] index */ /* * max # supported channels. The max channel no is 216, this is that + 1 @@ -48,17 +49,22 @@ #define WL_CHANSPEC_CTL_SB_UPPER 0x0200 #define WL_CHANSPEC_CTL_SB_NONE 0x0300 -#define WL_CHANSPEC_BW_MASK 0x0C00 -#define WL_CHANSPEC_BW_SHIFT 10 +#define WL_CHANSPEC_BW_MASK 0x3800 +#define WL_CHANSPEC_BW_SHIFT 11 #define WL_CHANSPEC_BW_10 0x0400 #define WL_CHANSPEC_BW_20 0x0800 #define WL_CHANSPEC_BW_40 0x0C00 #define WL_CHANSPEC_BW_80 0x2000 - -#define WL_CHANSPEC_BAND_MASK 0xf000 -#define WL_CHANSPEC_BAND_SHIFT 12 -#define WL_CHANSPEC_BAND_5G 0x1000 -#define WL_CHANSPEC_BAND_2G 0x2000 +#define WL_CHANSPEC_BW_160 0x2800 +#define WL_CHANSPEC_BW_8080 0x3000 +#define WL_CHANSPEC_BW_320 0x0000 + +#define WL_CHANSPEC_BAND_MASK 0xc000 +#define WL_CHANSPEC_BAND_SHIFT 14 +#define WL_CHANSPEC_BAND_2G 0x0000 +#define WL_CHANSPEC_BAND_4G 0x8000 +#define WL_CHANSPEC_BAND_5G 0xc000 +#define WL_CHANSPEC_BAND_6G 0x4000 #define INVCHANSPEC 255 #define WL_CHAN_VALID_HW (1 << 0) /* valid with current HW */ @@ -93,6 +99,7 @@ #define WLC_BAND_5G 1 /* 5 Ghz */ #define WLC_BAND_2G 2 /* 2.4 Ghz */ #define WLC_BAND_ALL 3 /* all bands */ +#define WLC_BAND_6G 4 /* 6 Ghz */ #define WLC_AP_IOV_OP_MANUAL_AP_BSSCFG_CREATE 2 @@ -114,6 +121,12 @@ #define CHSPEC_IS80(chspec) \ (((chspec) & WL_CHANSPEC_BW_MASK) == WL_CHANSPEC_BW_80) +#define CHSPEC_IS160(chspec) \ + (((chspec) & WL_CHANSPEC_BW_MASK) == WL_CHANSPEC_BW_160) + +#define CHSPEC_IS6G(chspec) \ + (((chspec) & WL_CHANSPEC_BAND_MASK) == WL_CHANSPEC_BAND_6G) + #define CHSPEC_IS5G(chspec) \ (((chspec) & WL_CHANSPEC_BAND_MASK) == WL_CHANSPEC_BAND_5G) From 42d98d9149a526e3dd6f02e54ae54627051c6d43 Mon Sep 17 00:00:00 2001 From: Daniel Berlin Date: Mon, 9 Oct 2023 14:04:16 -0400 Subject: [PATCH 0227/3902] [brcmfmac] Dynamically configure VHT settings to match firmware 1. Correct VHT MCS settings to support as many tx/rx streams as chip does. 2. Correct VHT capabilities to support what all chips do. 3. Correct max AMPDU capabilities for VHT. 4. Support LDPC and STBC in VHT where available. Signed-off-by: Daniel Berlin --- .../broadcom/brcm80211/brcmfmac/cfg80211.c | 50 +++++++++++++++---- 1 file changed, 41 insertions(+), 9 deletions(-) diff --git a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/cfg80211.c b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/cfg80211.c index b7b058220ae9c9..0300d4765b3d6a 100644 --- a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/cfg80211.c +++ b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/cfg80211.c @@ -7293,20 +7293,22 @@ static void brcmf_update_ht_cap(struct ieee80211_supported_band *band, band->ht_cap.mcs.tx_params = IEEE80211_HT_MCS_TX_DEFINED; } -static __le16 brcmf_get_mcs_map(u32 nchain, enum ieee80211_vht_mcs_support supp) +static __le16 brcmf_get_mcs_map(u32 nstreams, + enum ieee80211_vht_mcs_support supp) { u16 mcs_map; int i; - for (i = 0, mcs_map = 0xFFFF; i < nchain; i++) + for (i = 0, mcs_map = 0xFFFF; i < nstreams; i++) mcs_map = (mcs_map << 2) | supp; return cpu_to_le16(mcs_map); } static void brcmf_update_vht_cap(struct ieee80211_supported_band *band, - u32 bw_cap[2], u32 nchain, u32 txstreams, - u32 txbf_bfe_cap, u32 txbf_bfr_cap) + u32 bw_cap[2], u32 txstreams, u32 rxstreams, + u32 txbf_bfe_cap, u32 txbf_bfr_cap, + u32 ldpc_cap, u32 stbc_rx, u32 stbc_tx) { __le16 mcs_map; @@ -7315,6 +7317,21 @@ static void brcmf_update_vht_cap(struct ieee80211_supported_band *band, return; band->vht_cap.vht_supported = true; + band->vht_cap.vht_mcs.tx_highest = cpu_to_le16(433 * txstreams); + band->vht_cap.vht_mcs.rx_highest = cpu_to_le16(433 * rxstreams); + + band->vht_cap.cap |= IEEE80211_VHT_CAP_RX_ANTENNA_PATTERN | + IEEE80211_VHT_CAP_TX_ANTENNA_PATTERN; + + if (ldpc_cap) + band->vht_cap.cap |= IEEE80211_VHT_CAP_RXLDPC; + if (stbc_tx) + band->vht_cap.cap |= IEEE80211_VHT_CAP_TXSTBC; + + if (stbc_rx) + band->vht_cap.cap |= + (stbc_rx << IEEE80211_VHT_CAP_RXSTBC_SHIFT); + /* 80MHz is mandatory */ band->vht_cap.cap |= IEEE80211_VHT_CAP_SHORT_GI_80; if (bw_cap[band->band] & WLC_BW_160MHZ_BIT) { @@ -7322,8 +7339,10 @@ static void brcmf_update_vht_cap(struct ieee80211_supported_band *band, band->vht_cap.cap |= IEEE80211_VHT_CAP_SHORT_GI_160; } /* all support 256-QAM */ - mcs_map = brcmf_get_mcs_map(nchain, IEEE80211_VHT_MCS_SUPPORT_0_9); + mcs_map = brcmf_get_mcs_map(rxstreams, IEEE80211_VHT_MCS_SUPPORT_0_9); band->vht_cap.vht_mcs.rx_mcs_map = mcs_map; + mcs_map = brcmf_get_mcs_map(txstreams, IEEE80211_VHT_MCS_SUPPORT_0_9); + band->vht_cap.vht_mcs.tx_mcs_map = mcs_map; /* Beamforming support information */ @@ -7339,11 +7358,15 @@ static void brcmf_update_vht_cap(struct ieee80211_supported_band *band, if ((txbf_bfe_cap || txbf_bfr_cap) && (txstreams > 1)) { band->vht_cap.cap |= (2 << IEEE80211_VHT_CAP_BEAMFORMEE_STS_SHIFT); - band->vht_cap.cap |= ((txstreams - 1) << - IEEE80211_VHT_CAP_SOUNDING_DIMENSIONS_SHIFT); + band->vht_cap.cap |= + ((txstreams - 1) + << IEEE80211_VHT_CAP_SOUNDING_DIMENSIONS_SHIFT); band->vht_cap.cap |= IEEE80211_VHT_CAP_VHT_LINK_ADAPTATION_VHT_MRQ_MFB; } + /* AMPDU length limit, support max 1MB (2 ^ (13 + 7)) */ + band->vht_cap.cap |= + (7 << IEEE80211_VHT_CAP_MAX_A_MPDU_LENGTH_EXPONENT_SHIFT); } static int brcmf_setup_wiphybands(struct brcmf_cfg80211_info *cfg) @@ -7360,10 +7383,17 @@ static int brcmf_setup_wiphybands(struct brcmf_cfg80211_info *cfg) s32 i; struct ieee80211_supported_band *band; u32 txstreams = 0; + u32 rxstreams = 0; u32 txbf_bfe_cap = 0; u32 txbf_bfr_cap = 0; + u32 ldpc_cap = 0; + u32 stbc_rx = 0; + u32 stbc_tx = 0; (void)brcmf_fil_iovar_int_get(ifp, "vhtmode", &vhtmode); + (void)brcmf_fil_iovar_int_get(ifp, "ldpc_cap", &ldpc_cap); + (void)brcmf_fil_iovar_int_get(ifp, "stbc_rx", &stbc_rx); + (void)brcmf_fil_iovar_int_get(ifp, "stbc_tx", &stbc_tx); err = brcmf_fil_iovar_int_get(ifp, "nmode", &nmode); if (err) { bphy_err(drvr, "nmode error (%d)\n", err); @@ -7396,6 +7426,7 @@ static int brcmf_setup_wiphybands(struct brcmf_cfg80211_info *cfg) } if (vhtmode) { + (void)brcmf_fil_iovar_int_get(ifp, "rxstreams", &rxstreams); (void)brcmf_fil_iovar_int_get(ifp, "txstreams", &txstreams); (void)brcmf_fil_iovar_int_get(ifp, "txbf_bfe_cap", &txbf_bfe_cap); @@ -7411,8 +7442,9 @@ static int brcmf_setup_wiphybands(struct brcmf_cfg80211_info *cfg) if (nmode) brcmf_update_ht_cap(band, bw_cap, nchain); if (vhtmode) - brcmf_update_vht_cap(band, bw_cap, nchain, txstreams, - txbf_bfe_cap, txbf_bfr_cap); + brcmf_update_vht_cap(band, bw_cap, txstreams, rxstreams, + txbf_bfe_cap, txbf_bfr_cap, + ldpc_cap, stbc_rx, stbc_tx); } return 0; From 6c582b307a31b026ce4492dad349cbb12e04032f Mon Sep 17 00:00:00 2001 From: Daniel Berlin Date: Mon, 9 Oct 2023 19:19:45 -0400 Subject: [PATCH 0228/3902] [brcmfmac] Compute number of available antennas and set it in wiphy structure. Signed-off-by: Daniel Berlin --- .../broadcom/brcm80211/brcmfmac/cfg80211.c | 35 +++++++++++++++---- 1 file changed, 28 insertions(+), 7 deletions(-) diff --git a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/cfg80211.c b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/cfg80211.c index 0300d4765b3d6a..d6263bd0cd0a3f 100644 --- a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/cfg80211.c +++ b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/cfg80211.c @@ -7278,7 +7278,7 @@ static void brcmf_get_bwcap(struct brcmf_if *ifp, u32 bw_cap[]) } static void brcmf_update_ht_cap(struct ieee80211_supported_band *band, - u32 bw_cap[2], u32 nchain) + u32 bw_cap[2], u32 nrxchain) { band->ht_cap.ht_supported = true; if (bw_cap[band->band] & WLC_BW_40MHZ_BIT) { @@ -7289,7 +7289,7 @@ static void brcmf_update_ht_cap(struct ieee80211_supported_band *band, band->ht_cap.cap |= IEEE80211_HT_CAP_DSSSCCK40; band->ht_cap.ampdu_factor = IEEE80211_HT_MAX_AMPDU_64K; band->ht_cap.ampdu_density = IEEE80211_HT_MPDU_DENSITY_16; - memset(band->ht_cap.mcs.rx_mask, 0xff, nchain); + memset(band->ht_cap.mcs.rx_mask, 0xff, nrxchain); band->ht_cap.mcs.tx_params = IEEE80211_HT_MCS_TX_DEFINED; } @@ -7378,7 +7378,9 @@ static int brcmf_setup_wiphybands(struct brcmf_cfg80211_info *cfg) u32 vhtmode = 0; u32 bw_cap[2] = { WLC_BW_20MHZ_BIT, WLC_BW_20MHZ_BIT }; u32 rxchain; - u32 nchain; + u32 txchain; + u32 nrxchain; + u32 ntxchain; int err; s32 i; struct ieee80211_supported_band *band; @@ -7412,12 +7414,31 @@ static int brcmf_setup_wiphybands(struct brcmf_cfg80211_info *cfg) else bphy_err(drvr, "rxchain error (%d)\n", err); - nchain = 1; + nrxchain = 1; + rxchain = 1; } else { - for (nchain = 0; rxchain; nchain++) + for (nrxchain = 0; rxchain; nrxchain++) rxchain = rxchain & (rxchain - 1); } - brcmf_dbg(INFO, "nchain=%d\n", nchain); + brcmf_dbg(INFO, "nrxchain=%d\n", nrxchain); + err = brcmf_fil_iovar_int_get(ifp, "txchain", &txchain); + if (err) { + /* rxchain unsupported by firmware of older chips */ + if (err == -EBADE) + bphy_info_once(drvr, "rxchain unsupported\n"); + else + bphy_err(drvr, "rxchain error (%d)\n", err); + + ntxchain = 1; + txchain = 1; + } else { + for (ntxchain = 0; txchain; ntxchain++) + txchain = txchain & (txchain - 1); + } + brcmf_dbg(INFO, "ntxchain=%d\n", ntxchain); + + wiphy->available_antennas_rx = nrxchain; + wiphy->available_antennas_tx = ntxchain; err = brcmf_construct_chaninfo(cfg, bw_cap); if (err) { @@ -7440,7 +7461,7 @@ static int brcmf_setup_wiphybands(struct brcmf_cfg80211_info *cfg) continue; if (nmode) - brcmf_update_ht_cap(band, bw_cap, nchain); + brcmf_update_ht_cap(band, bw_cap, nrxchain); if (vhtmode) brcmf_update_vht_cap(band, bw_cap, txstreams, rxstreams, txbf_bfe_cap, txbf_bfr_cap, From 184acf412abae35427b80d3bc74f6259d8cf8f97 Mon Sep 17 00:00:00 2001 From: Daniel Berlin Date: Tue, 10 Oct 2023 09:42:36 -0400 Subject: [PATCH 0229/3902] [brcmfmac] Support GCMP cipher suite, used by WPA3. This patch adds support for using GCMP/etc during offload where supported by the firmware. Signed-off-by: Daniel Berlin --- .../broadcom/brcm80211/brcmfmac/cfg80211.c | 132 +++++++++++++++++- .../broadcom/brcm80211/brcmfmac/feature.c | 1 + .../broadcom/brcm80211/brcmfmac/feature.h | 6 + .../broadcom/brcm80211/brcmfmac/fwil_types.h | 18 +++ .../broadcom/brcm80211/include/brcmu_wifi.h | 7 + 5 files changed, 160 insertions(+), 4 deletions(-) diff --git a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/cfg80211.c b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/cfg80211.c index d6263bd0cd0a3f..424404b229267a 100644 --- a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/cfg80211.c +++ b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/cfg80211.c @@ -32,7 +32,9 @@ #include "vendor.h" #include "bus.h" #include "common.h" +#include "feature.h" #include "fwvid.h" +#include "xtlv.h" #define BRCMF_SCAN_IE_LEN_MAX 2048 @@ -124,6 +126,13 @@ struct cca_msrmnt_query { u32 time_req; }; +/* algo bit vector */ +#define KEY_ALGO_MASK(_algo) (1 << (_algo)) + +/* start enum value for BSS properties */ +#define WL_WSEC_INFO_BSS_BASE 0x0100 +#define WL_WSEC_INFO_BSS_ALGOS (WL_WSEC_INFO_BSS_BASE + 6) + static bool check_vif_up(struct brcmf_cfg80211_vif *vif) { if (!test_bit(BRCMF_VIF_STATUS_READY, &vif->sme_state)) { @@ -236,16 +245,22 @@ static const struct ieee80211_regdomain brcmf_regdom = { /* Note: brcmf_cipher_suites is an array of int defining which cipher suites * are supported. A pointer to this array and the number of entries is passed * on to upper layers. AES_CMAC defines whether or not the driver supports MFP. - * So the cipher suite AES_CMAC has to be the last one in the array, and when - * device does not support MFP then the number of suites will be decreased by 1 + * MFP support includes a few other suites, so if MFP is not supported, + * then the number of suites will be decreased by 4 */ static const u32 brcmf_cipher_suites[] = { WLAN_CIPHER_SUITE_WEP40, WLAN_CIPHER_SUITE_WEP104, WLAN_CIPHER_SUITE_TKIP, WLAN_CIPHER_SUITE_CCMP, - /* Keep as last entry: */ - WLAN_CIPHER_SUITE_AES_CMAC + WLAN_CIPHER_SUITE_CCMP_256, + WLAN_CIPHER_SUITE_GCMP, + WLAN_CIPHER_SUITE_GCMP_256, + /* Keep as last 4 entries: */ + WLAN_CIPHER_SUITE_AES_CMAC, + WLAN_CIPHER_SUITE_BIP_CMAC_256, + WLAN_CIPHER_SUITE_BIP_GMAC_128, + WLAN_CIPHER_SUITE_BIP_GMAC_256 }; /* Vendor specific ie. id = 221, oui and type defines exact ie */ @@ -2034,6 +2049,48 @@ static s32 brcmf_set_auth_type(struct net_device *ndev, return err; } +static s32 brcmf_set_wsec_info_algos(struct brcmf_if *ifp, u32 algos, u32 mask) +{ + struct brcmf_pub *drvr = ifp->drvr; + s32 err = 0; + struct brcmf_wsec_info *wsec_info; + struct brcmf_xtlv *wsec_info_tlv; + u16 tlv_data_len; + u8 tlv_data[8]; + u32 param_len; + u8 *buf; + + brcmf_dbg(TRACE, "Enter\n"); + + buf = kzalloc(sizeof(struct brcmf_wsec_info) + sizeof(tlv_data), + GFP_KERNEL); + if (!buf) { + bphy_err(drvr, "unable to allocate.\n"); + return -ENOMEM; + } + wsec_info = (struct brcmf_wsec_info *)buf; + wsec_info->version = BRCMF_WSEC_INFO_VER; + wsec_info_tlv = + (struct brcmf_xtlv *)(buf + + offsetof(struct brcmf_wsec_info, tlvs)); + wsec_info->num_tlvs++; + tlv_data_len = sizeof(tlv_data); + memcpy(tlv_data, &algos, sizeof(algos)); + memcpy(tlv_data + sizeof(algos), &mask, sizeof(mask)); + brcmf_xtlv_pack_header(wsec_info_tlv, WL_WSEC_INFO_BSS_ALGOS, + tlv_data_len, tlv_data, 0); + + param_len = offsetof(struct brcmf_wsec_info, tlvs) + + offsetof(struct brcmf_wsec_info_tlv, data) + tlv_data_len; + + err = brcmf_fil_bsscfg_data_set(ifp, "wsec_info", buf, param_len); + if (err) + brcmf_err("set wsec_info_error:%d\n", err); + + kfree(buf); + return err; +} + static s32 brcmf_set_wsec_mode(struct net_device *ndev, struct cfg80211_connect_params *sme) @@ -2046,6 +2103,8 @@ brcmf_set_wsec_mode(struct net_device *ndev, s32 gval = 0; s32 wsec; s32 err = 0; + u32 algos = 0; + u32 mask = 0; if (sme->crypto.n_ciphers_pairwise) { switch (sme->crypto.ciphers_pairwise[0]) { @@ -2062,6 +2121,15 @@ brcmf_set_wsec_mode(struct net_device *ndev, case WLAN_CIPHER_SUITE_AES_CMAC: pval = AES_ENABLED; break; + case WLAN_CIPHER_SUITE_GCMP_256: + if (!brcmf_feat_is_enabled(ifp, BRCMF_FEAT_GCMP)) { + brcmf_err("This chip does not support GCMP\n"); + return -EOPNOTSUPP; + } + pval = AES_ENABLED; + algos = KEY_ALGO_MASK(CRYPTO_ALGO_AES_GCM256); + mask = algos | KEY_ALGO_MASK(CRYPTO_ALGO_AES_CCM); + break; default: bphy_err(drvr, "invalid cipher pairwise (%d)\n", sme->crypto.ciphers_pairwise[0]); @@ -2083,6 +2151,15 @@ brcmf_set_wsec_mode(struct net_device *ndev, case WLAN_CIPHER_SUITE_AES_CMAC: gval = AES_ENABLED; break; + case WLAN_CIPHER_SUITE_GCMP_256: + if (!brcmf_feat_is_enabled(ifp, BRCMF_FEAT_GCMP)) { + brcmf_err("This chip does not support GCMP\n"); + return -EOPNOTSUPP; + } + gval = AES_ENABLED; + algos = KEY_ALGO_MASK(CRYPTO_ALGO_AES_GCM256); + mask = algos | KEY_ALGO_MASK(CRYPTO_ALGO_AES_CCM); + break; default: bphy_err(drvr, "invalid cipher group (%d)\n", sme->crypto.cipher_group); @@ -2091,6 +2168,7 @@ brcmf_set_wsec_mode(struct net_device *ndev, } brcmf_dbg(CONN, "pval (%d) gval (%d)\n", pval, gval); + brcmf_dbg(CONN, "algos (0x%x) mask (0x%x)\n", algos, mask); /* In case of privacy, but no security and WPS then simulate */ /* setting AES. WPS-2.0 allows no security */ if (brcmf_find_wpsie(sme->ie, sme->ie_len) && !pval && !gval && @@ -2103,6 +2181,15 @@ brcmf_set_wsec_mode(struct net_device *ndev, bphy_err(drvr, "error (%d)\n", err); return err; } + if (brcmf_feat_is_enabled(ifp, BRCMF_FEAT_GCMP)) { + brcmf_dbg(CONN, "set_wsec_info algos (0x%x) mask (0x%x)\n", + algos, mask); + err = brcmf_set_wsec_info_algos(ifp, algos, mask); + if (err) { + brcmf_err("set wsec_info error (%d)\n", err); + return err; + } + } sec = &profile->sec; sec->cipher_pairwise = sme->crypto.ciphers_pairwise[0]; @@ -2815,6 +2902,8 @@ brcmf_cfg80211_add_key(struct wiphy *wiphy, struct net_device *ndev, s32 val; s32 wsec; s32 err; + u32 algos = 0; + u32 mask = 0; u8 keybuf[8]; bool ext_key; @@ -2898,6 +2987,30 @@ brcmf_cfg80211_add_key(struct wiphy *wiphy, struct net_device *ndev, val = AES_ENABLED; brcmf_dbg(CONN, "WLAN_CIPHER_SUITE_CCMP\n"); break; + case WLAN_CIPHER_SUITE_GCMP_256: + if (!brcmf_feat_is_enabled(ifp, BRCMF_FEAT_GCMP)) { + brcmf_err("the low layer not support GCMP\n"); + err = -EOPNOTSUPP; + goto done; + } + key->algo = CRYPTO_ALGO_AES_GCM256; + val = AES_ENABLED; + brcmf_dbg(CONN, "WLAN_CIPHER_SUITE_GCMP_256\n"); + algos = KEY_ALGO_MASK(CRYPTO_ALGO_AES_GCM256); + mask = algos | KEY_ALGO_MASK(CRYPTO_ALGO_AES_CCM); + break; + case WLAN_CIPHER_SUITE_BIP_GMAC_256: + if (!brcmf_feat_is_enabled(ifp, BRCMF_FEAT_GCMP)) { + brcmf_err("the low layer not support GCMP\n"); + err = -EOPNOTSUPP; + goto done; + } + key->algo = CRYPTO_ALGO_BIP_GMAC256; + val = AES_ENABLED; + algos = KEY_ALGO_MASK(CRYPTO_ALGO_BIP_GMAC256); + mask = algos | KEY_ALGO_MASK(CRYPTO_ALGO_AES_CCM); + brcmf_dbg(CONN, "WLAN_CIPHER_SUITE_BIP_GMAC_256\n"); + break; default: bphy_err(drvr, "Invalid cipher (0x%x)\n", params->cipher); err = -EINVAL; @@ -2919,6 +3032,17 @@ brcmf_cfg80211_add_key(struct wiphy *wiphy, struct net_device *ndev, bphy_err(drvr, "set wsec error (%d)\n", err); goto done; } + if (brcmf_feat_is_enabled(ifp, BRCMF_FEAT_GCMP)) { + brcmf_dbg(CONN, + "set_wsdec_info algos (0x%x) mask (0x%x)\n", + algos, mask); + err = brcmf_set_wsec_info_algos(ifp, algos, mask); + if (err) { + brcmf_err("set wsec_info error (%d)\n", err); + return err; + } + } + done: brcmf_dbg(TRACE, "Exit\n"); diff --git a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/feature.c b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/feature.c index 5dadc704985b3c..b3bae1b2f79048 100644 --- a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/feature.c +++ b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/feature.c @@ -45,6 +45,7 @@ static const struct brcmf_feat_fwcap brcmf_fwcap_map[] = { { BRCMF_FEAT_SAE, "sae " }, { BRCMF_FEAT_FWAUTH, "idauth" }, { BRCMF_FEAT_SAE_EXT, "sae_ext" }, + { BRCMF_FEAT_GCMP, "gcmp"} }; #ifdef DEBUG diff --git a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/feature.h b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/feature.h index a275b7f9811576..1c967e54c0c78b 100644 --- a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/feature.h +++ b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/feature.h @@ -32,6 +32,11 @@ * DUMP_OBSS: Firmware has capable to dump obss info to support ACS * SCAN_V2: Version 2 scan params * SAE_EXT: SAE authentication handled by user-space supplicant + * SCAN_v3: Version 3 scan params + * PMKID_V2: Version 2 PMKID + * PMKID_V3: Version 3 PMKID + * JOIN_V1: Version 1 join struct + * GCMP: GCMP Cipher suite support */ #define BRCMF_FEAT_LIST \ BRCMF_FEAT_DEF(MBSS) \ @@ -62,6 +67,7 @@ BRCMF_FEAT_DEF(PMKID_V3) \ BRCMF_FEAT_DEF(SAE_EXT) \ BRCMF_FEAT_DEF(EVENT_MSGS_EXT) \ + BRCMF_FEAT_DEF(GCMP) /* * Quirks: diff --git a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/fwil_types.h b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/fwil_types.h index a4ec3808a5c84c..27ec9a41433896 100644 --- a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/fwil_types.h +++ b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/fwil_types.h @@ -1309,4 +1309,22 @@ struct brcmf_eventmsgs_ext_le { u8 mask[]; }; +/* version of the brcmf_wl_wsec_info structure */ +#define BRCMF_WSEC_INFO_VER 1 + +/* tlv used to return wl_wsec_info properties */ +struct brcmf_wsec_info_tlv { + u16 type; + u16 len; /* data length */ + u8 data[1]; /* data follows */ +}; + +/* input/output data type for wsec_info iovar */ +struct brcmf_wsec_info { + u8 version; /* structure version */ + u8 pad[2]; + u8 num_tlvs; + struct brcmf_wsec_info_tlv tlvs[1]; /* tlv data follows */ +}; + #endif /* FWIL_TYPES_H_ */ diff --git a/drivers/net/wireless/broadcom/brcm80211/include/brcmu_wifi.h b/drivers/net/wireless/broadcom/brcm80211/include/brcmu_wifi.h index e054b84443563e..0ab1b95318e581 100644 --- a/drivers/net/wireless/broadcom/brcm80211/include/brcmu_wifi.h +++ b/drivers/net/wireless/broadcom/brcm80211/include/brcmu_wifi.h @@ -215,6 +215,13 @@ static inline bool ac_bitmap_tst(u8 bitmap, int prec) #define CRYPTO_ALGO_AES_RESERVED1 5 #define CRYPTO_ALGO_AES_RESERVED2 6 #define CRYPTO_ALGO_NALG 7 +#define CRYPTO_ALGO_AES_GCM 14 /* 128 bit GCM */ +#define CRYPTO_ALGO_AES_CCM256 15 /* 256 bit CCM */ +#define CRYPTO_ALGO_AES_GCM256 16 /* 256 bit GCM */ +#define CRYPTO_ALGO_BIP_CMAC256 17 /* 256 bit BIP CMAC */ +#define CRYPTO_ALGO_BIP_GMAC 18 /* 128 bit BIP GMAC */ +#define CRYPTO_ALGO_BIP_GMAC256 19 /* 256 bit BIP GMAC */ + /* wireless security bitvec */ From 589d7055abdb2b72afc24a4e7175929f8938130f Mon Sep 17 00:00:00 2001 From: Daniel Berlin Date: Wed, 18 Oct 2023 19:03:58 -0400 Subject: [PATCH 0230/3902] [brcmfmac] Support high power/low power/etc scan flags This patch adds support for handling the scan flags that come from the 802.11 stack. This enables the stack to control whether we are doing high/low power scans, as well as other options. Signed-off-by: Daniel Berlin --- .../broadcom/brcm80211/brcmfmac/cfg80211.c | 41 ++++++++++++++++++- .../broadcom/brcm80211/brcmfmac/fwil_types.h | 9 ++++ 2 files changed, 48 insertions(+), 2 deletions(-) diff --git a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/cfg80211.c b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/cfg80211.c index 424404b229267a..2ca94708e26cd3 100644 --- a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/cfg80211.c +++ b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/cfg80211.c @@ -1107,6 +1107,28 @@ static void brcmf_scan_params_v2_to_v1(struct brcmf_scan_params_v2_le *params_v2 ¶ms_v2_le->channel_list[0], params_size); } +static u32 brcmf_nl80211_scan_flags_to_scan_flags(u32 nl80211_flags) +{ + u32 scan_flags = 0; + if (nl80211_flags & NL80211_SCAN_FLAG_LOW_SPAN) { + scan_flags |= BRCMF_SCANFLAGS_LOW_SPAN; + brcmf_dbg(SCAN, "requested low span scan\n"); + } + if (nl80211_flags & NL80211_SCAN_FLAG_HIGH_ACCURACY) { + scan_flags |= BRCMF_SCANFLAGS_HIGH_ACCURACY; + brcmf_dbg(SCAN, "requested high accuracy scan\n"); + } + if (nl80211_flags & NL80211_SCAN_FLAG_LOW_POWER) { + scan_flags |= BRCMF_SCANFLAGS_LOW_POWER; + brcmf_dbg(SCAN, "requested low power scan\n"); + } + if (nl80211_flags & NL80211_SCAN_FLAG_LOW_PRIORITY) { + scan_flags |= BRCMF_SCANFLAGS_LOW_PRIO; + brcmf_dbg(SCAN, "requested low priority scan\n"); + } + return scan_flags; +} + static void brcmf_escan_prep(struct brcmf_cfg80211_info *cfg, struct brcmf_if *ifp, struct brcmf_scan_params_v2_le *params_le, @@ -1120,6 +1142,7 @@ static void brcmf_escan_prep(struct brcmf_cfg80211_info *cfg, char *ptr; int length; struct brcmf_ssid_le ssid_le; + u32 scan_type = BRCMF_SCANTYPE_ACTIVE; eth_broadcast_addr(params_le->bssid); @@ -1132,7 +1155,6 @@ static void brcmf_escan_prep(struct brcmf_cfg80211_info *cfg, params_le->bss_type = DOT11_BSSTYPE_ANY; params_le->ssid_type = 0; - params_le->scan_type = cpu_to_le32(BRCMF_SCANTYPE_ACTIVE); params_le->channel_num = 0; params_le->nprobes = cpu_to_le32(-1); params_le->active_time = cpu_to_le32(-1); @@ -1192,9 +1214,17 @@ static void brcmf_escan_prep(struct brcmf_cfg80211_info *cfg, } } else { brcmf_dbg(SCAN, "Performing passive scan\n"); - params_le->scan_type = cpu_to_le32(BRCMF_SCANTYPE_PASSIVE); + scan_type = BRCMF_SCANTYPE_PASSIVE; } + scan_type |= brcmf_nl80211_scan_flags_to_scan_flags(request->flags); + params_le->scan_type = cpu_to_le32(scan_type); params_le->length = cpu_to_le16(length); + + /* Include RNR results if requested */ + if (request->flags & NL80211_SCAN_FLAG_COLOCATED_6GHZ) { + params_le->ssid_type |= BRCMF_SCANSSID_INC_RNR; + } + /* Adding mask to channel numbers */ params_le->channel_num = cpu_to_le32((n_ssids << BRCMF_SCAN_PARAMS_NSSID_SHIFT) | @@ -7912,6 +7942,13 @@ static int brcmf_setup_wiphy(struct wiphy *wiphy, struct brcmf_if *ifp) } if (brcmf_feat_is_enabled(ifp, BRCMF_FEAT_SAE_EXT)) wiphy->features |= NL80211_FEATURE_SAE; + + /* High accuracy and low power scans are always supported. */ + wiphy_ext_feature_set(wiphy, NL80211_EXT_FEATURE_HIGH_ACCURACY_SCAN); + wiphy_ext_feature_set(wiphy, NL80211_EXT_FEATURE_LOW_POWER_SCAN); + wiphy_ext_feature_set(wiphy, NL80211_EXT_FEATURE_LOW_SPAN_SCAN); + wiphy->features |= NL80211_FEATURE_LOW_PRIORITY_SCAN; + wiphy->mgmt_stypes = brcmf_txrx_stypes; wiphy->max_remain_on_channel_duration = 5000; if (brcmf_feat_is_enabled(ifp, BRCMF_FEAT_PNO)) { diff --git a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/fwil_types.h b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/fwil_types.h index 27ec9a41433896..70deae79286083 100644 --- a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/fwil_types.h +++ b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/fwil_types.h @@ -64,6 +64,15 @@ #define BRCMF_SCANTYPE_ACTIVE 0 #define BRCMF_SCANTYPE_PASSIVE 1 +/* Additional scanning flags */ +#define BRCMF_SCANFLAGS_LOW_PRIO 0x2 +#define BRCMF_SCANFLAGS_LOW_POWER 0x1000 +#define BRCMF_SCANFLAGS_HIGH_ACCURACY 0x2000 +#define BRCMF_SCANFLAGS_LOW_SPAN 0x4000 + +/* scan ssid_type flags */ +#define BRCMF_SCANSSID_INC_RNR 0x02 /* Include RNR channels*/ + #define BRCMF_WSEC_MAX_PSK_LEN 32 #define BRCMF_WSEC_PASSPHRASE BIT(0) From bfa504fa5a7424c4e2454943543fbdb60a9265d5 Mon Sep 17 00:00:00 2001 From: Daniel Berlin Date: Sun, 15 Oct 2023 08:59:44 -0400 Subject: [PATCH 0231/3902] [brcmfmac] Add support for 6G bands and HE This patch adds support for 6G bands, along with HE capabilities, as they are required to register 6G bands with wiphy. This in turn, enables 802.11ax support for the other bands. Scanning is not updated in this patch, so the bands are unused except to be able to process what the firmware tells us. Existing code is updated to handle all the bands rather than just 2g and 5g channels. Signed-off-by: Daniel Berlin --- .../broadcom/brcm80211/brcmfmac/cfg80211.c | 373 +++++++++++++++--- .../broadcom/brcm80211/brcmfmac/debug.h | 2 + .../broadcom/brcm80211/brcmfmac/fwil_types.h | 92 +++++ 3 files changed, 414 insertions(+), 53 deletions(-) diff --git a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/cfg80211.c b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/cfg80211.c index 2ca94708e26cd3..0c9188c9f671f6 100644 --- a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/cfg80211.c +++ b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/cfg80211.c @@ -187,6 +187,15 @@ static struct ieee80211_rate __wl_rates[] = { .max_power = 30, \ } +#define CHAN6G(_channel) { \ + .band = NL80211_BAND_6GHZ, \ + .center_freq = ((_channel == 2) ? 5935 : 5950 + (5 * (_channel))), \ + .hw_value = (_channel), \ + .max_antenna_gain = 0, \ + .max_power = 30, \ +} + + static struct ieee80211_channel __wl_2ghz_channels[] = { CHAN2G(1, 2412), CHAN2G(2, 2417), CHAN2G(3, 2422), CHAN2G(4, 2427), CHAN2G(5, 2432), CHAN2G(6, 2437), CHAN2G(7, 2442), CHAN2G(8, 2447), @@ -203,6 +212,23 @@ static struct ieee80211_channel __wl_5ghz_channels[] = { CHAN5G(153), CHAN5G(157), CHAN5G(161), CHAN5G(165) }; +static struct ieee80211_channel __wl_6ghz_channels[] = { + CHAN6G(1), CHAN6G(2), CHAN6G(5), CHAN6G(9), CHAN6G(13), + CHAN6G(17), CHAN6G(21), CHAN6G(25), CHAN6G(29), CHAN6G(33), + CHAN6G(37), CHAN6G(41), CHAN6G(45), CHAN6G(49), CHAN6G(53), + CHAN6G(57), CHAN6G(61), CHAN6G(65), CHAN6G(69), CHAN6G(73), + CHAN6G(77), CHAN6G(81), CHAN6G(85), CHAN6G(89), CHAN6G(93), + CHAN6G(97), CHAN6G(101), CHAN6G(105), CHAN6G(109), CHAN6G(113), + CHAN6G(117), CHAN6G(121), CHAN6G(125), CHAN6G(129), CHAN6G(133), + CHAN6G(137), CHAN6G(141), CHAN6G(145), CHAN6G(149), CHAN6G(153), + CHAN6G(157), CHAN6G(161), CHAN6G(165), CHAN6G(169), CHAN6G(173), + CHAN6G(177), CHAN6G(181), CHAN6G(185), CHAN6G(189), CHAN6G(193), + CHAN6G(197), CHAN6G(201), CHAN6G(205), CHAN6G(209), CHAN6G(213), + CHAN6G(217), CHAN6G(221), CHAN6G(225), CHAN6G(229), CHAN6G(233), +}; + +struct ieee80211_sband_iftype_data sdata[NUM_NL80211_BANDS]; + /* Band templates duplicated per wiphy. The channel info * above is added to the band during setup. */ @@ -218,6 +244,12 @@ static const struct ieee80211_supported_band __wl_band_5ghz = { .n_bitrates = wl_a_rates_size, }; +static const struct ieee80211_supported_band __wl_band_6ghz = { + .band = NL80211_BAND_6GHZ, + .bitrates = wl_a_rates, + .n_bitrates = wl_a_rates_size, +}; + /* This is to override regulatory domains defined in cfg80211 module (reg.c) * By default world regulatory domain defined in reg.c puts the flags * NL80211_RRF_NO_IR for 5GHz channels (for * 36..48 and 149..165). @@ -226,20 +258,22 @@ static const struct ieee80211_supported_band __wl_band_5ghz = { * domain are to be done here. */ static const struct ieee80211_regdomain brcmf_regdom = { - .n_reg_rules = 4, + .n_reg_rules = 5, .alpha2 = "99", .reg_rules = { /* IEEE 802.11b/g, channels 1..11 */ - REG_RULE(2412-10, 2472+10, 40, 6, 20, 0), + REG_RULE(2412 - 10, 2472 + 10, 40, 6, 20, 0), /* If any */ /* IEEE 802.11 channel 14 - Only JP enables * this and for 802.11b only */ - REG_RULE(2484-10, 2484+10, 20, 6, 20, 0), + REG_RULE(2484 - 10, 2484 + 10, 20, 6, 20, 0), /* IEEE 802.11a, channel 36..64 */ - REG_RULE(5150-10, 5350+10, 160, 6, 20, 0), + REG_RULE(5150 - 10, 5350 + 10, 160, 6, 20, 0), /* IEEE 802.11a, channel 100..165 */ - REG_RULE(5470-10, 5850+10, 160, 6, 20, 0), } + REG_RULE(5470 - 10, 5850 + 10, 160, 6, 20, 0), + /* IEEE 802.11ax, 6E */ + REG_RULE(5935 - 10, 7115 + 10, 160, 6, 20, 0), } }; /* Note: brcmf_cipher_suites is an array of int defining which cipher suites @@ -331,6 +365,8 @@ static u8 nl80211_band_to_fwil(enum nl80211_band band) return WLC_BAND_2G; case NL80211_BAND_5GHZ: return WLC_BAND_5G; + case NL80211_BAND_6GHZ: + return WLC_BAND_6G; default: WARN_ON(1); break; @@ -338,6 +374,23 @@ static u8 nl80211_band_to_fwil(enum nl80211_band band) return 0; } +static int nl80211_band_to_chanspec_band(enum nl80211_band band) +{ + switch (band) { + case NL80211_BAND_2GHZ: + return BRCMU_CHAN_BAND_2G; + case NL80211_BAND_5GHZ: + return BRCMU_CHAN_BAND_5G; + case NL80211_BAND_6GHZ: + return BRCMU_CHAN_BAND_6G; + case NL80211_BAND_60GHZ: + default: + WARN_ON_ONCE(1); + // Choose a safe default + return BRCMU_CHAN_BAND_2G; + } +} + static u16 chandef_to_chanspec(struct brcmu_d11inf *d11inf, struct cfg80211_chan_def *ch) { @@ -397,17 +450,7 @@ static u16 chandef_to_chanspec(struct brcmu_d11inf *d11inf, default: WARN_ON_ONCE(1); } - switch (ch->chan->band) { - case NL80211_BAND_2GHZ: - ch_inf.band = BRCMU_CHAN_BAND_2G; - break; - case NL80211_BAND_5GHZ: - ch_inf.band = BRCMU_CHAN_BAND_5G; - break; - case NL80211_BAND_60GHZ: - default: - WARN_ON_ONCE(1); - } + ch_inf.band = nl80211_band_to_chanspec_band(ch->chan->band); d11inf->encchspec(&ch_inf); brcmf_dbg(TRACE, "chanspec: 0x%x\n", ch_inf.chspec); @@ -419,6 +462,7 @@ u16 channel_to_chanspec(struct brcmu_d11inf *d11inf, { struct brcmu_chan ch_inf; + ch_inf.band = nl80211_band_to_chanspec_band(ch->band); ch_inf.chnum = ieee80211_frequency_to_channel(ch->center_freq); ch_inf.bw = BRCMU_CHAN_BW_20; d11inf->encchspec(&ch_inf); @@ -3511,6 +3555,7 @@ static s32 brcmf_inform_single_bss(struct brcmf_cfg80211_info *cfg, struct cfg80211_bss *bss; enum nl80211_band band; struct brcmu_chan ch; + u16 chanspec; u16 channel; u32 freq; u16 notify_capability; @@ -3524,20 +3569,41 @@ static s32 brcmf_inform_single_bss(struct brcmf_cfg80211_info *cfg, return -EINVAL; } + chanspec = le16_to_cpu(bi->chanspec); if (!bi->ctl_ch) { - ch.chspec = le16_to_cpu(bi->chanspec); + ch.chspec = chanspec; cfg->d11inf.decchspec(&ch); bi->ctl_ch = ch.control_ch_num; } channel = bi->ctl_ch; - if (channel <= CH_MAX_2G_CHANNEL) - band = NL80211_BAND_2GHZ; - else + if (CHSPEC_IS6G(chanspec)) + band = NL80211_BAND_6GHZ; + else if (CHSPEC_IS5G(chanspec)) band = NL80211_BAND_5GHZ; + else + band = NL80211_BAND_2GHZ; freq = ieee80211_channel_to_frequency(channel, band); + if (!freq) { + brcmf_err("Invalid frequency %d returned for channel %d, band %d. chanspec was %04x\n", + freq, channel, band, bi->chanspec); + + /* We ignore this BSS ID rather than try to continue on. + * Otherwise we will cause an OOPs because our frequency is 0. + * The main case this occurs is some new frequency band + * we have not seen before, and if we return an error, + * we will cause the scan to fail. It seems better to + * report the error, skip this BSS, and move on. + */ + return 0; + } bss_data.chan = ieee80211_get_channel(wiphy, freq); + if (!bss_data.chan) { + brcmf_err("Could not convert frequency into channel for channel %d, band %d, chanspec was %04x\n", + channel, band, bi->chanspec); + return 0; + } bss_data.boottime_ns = ktime_to_ns(ktime_get_boottime()); notify_capability = le16_to_cpu(bi->capability); @@ -3626,7 +3692,7 @@ static s32 brcmf_inform_ibss(struct brcmf_cfg80211_info *cfg, buf = kzalloc(WL_BSS_INFO_MAX, GFP_KERNEL); if (buf == NULL) { err = -ENOMEM; - goto CleanUp; + goto cleanup; } *(__le32 *)buf = cpu_to_le32(WL_BSS_INFO_MAX); @@ -3635,7 +3701,7 @@ static s32 brcmf_inform_ibss(struct brcmf_cfg80211_info *cfg, buf, WL_BSS_INFO_MAX); if (err) { bphy_err(drvr, "WLC_GET_BSS_INFO failed: %d\n", err); - goto CleanUp; + goto cleanup; } bi = (struct brcmf_bss_info_le *)(buf + 4); @@ -3645,10 +3711,18 @@ static s32 brcmf_inform_ibss(struct brcmf_cfg80211_info *cfg, if (ch.band == BRCMU_CHAN_BAND_2G) band = wiphy->bands[NL80211_BAND_2GHZ]; - else + else if (ch.band == BRCMU_CHAN_BAND_5G) band = wiphy->bands[NL80211_BAND_5GHZ]; + else + band = wiphy->bands[NL80211_BAND_6GHZ]; freq = ieee80211_channel_to_frequency(ch.control_ch_num, band->band); + if (freq == 0) { + brcmf_err("Invalid frequency %d returned for channel %d, band %d. chanspec was %04x\n", + freq, ch.control_ch_num, ch.band, bi->chanspec); + goto cleanup; + } + cfg->channel = freq; notify_channel = ieee80211_get_channel(wiphy, freq); @@ -3671,12 +3745,12 @@ static s32 brcmf_inform_ibss(struct brcmf_cfg80211_info *cfg, if (!bss) { err = -ENOMEM; - goto CleanUp; + goto cleanup; } cfg80211_put_bss(wiphy, bss); -CleanUp: +cleanup: kfree(buf); @@ -5924,6 +5998,9 @@ static int brcmf_cfg80211_get_channel(struct wiphy *wiphy, case BRCMU_CHAN_BAND_5G: band = NL80211_BAND_5GHZ; break; + case BRCMU_CHAN_BAND_6G: + band = NL80211_BAND_6GHZ; + break; } switch (ch.bw) { @@ -5945,9 +6022,19 @@ static int brcmf_cfg80211_get_channel(struct wiphy *wiphy, } freq = ieee80211_channel_to_frequency(ch.control_ch_num, band); + if (freq == 0) { + brcmf_err("Invalid frequency %d returned for channel %d, band %d. chanspec was %04x\n", + freq, ch.control_ch_num, ch.band, chanspec); + return -EINVAL; + } chandef->chan = ieee80211_get_channel(wiphy, freq); chandef->width = width; chandef->center_freq1 = ieee80211_channel_to_frequency(ch.chnum, band); + if (chandef->center_freq1 == 0) { + brcmf_err("Invalid frequency %d returned for channel %d, band %d. chanspec was %04x\n", + freq, ch.chnum, ch.band, chanspec); + return -EINVAL; + } chandef->center_freq2 = 0; return 0; @@ -6625,10 +6712,17 @@ brcmf_bss_roaming_done(struct brcmf_cfg80211_info *cfg, if (ch.band == BRCMU_CHAN_BAND_2G) band = wiphy->bands[NL80211_BAND_2GHZ]; - else + else if (ch.band == BRCMU_CHAN_BAND_5G) band = wiphy->bands[NL80211_BAND_5GHZ]; + else + band = wiphy->bands[NL80211_BAND_6GHZ]; freq = ieee80211_channel_to_frequency(ch.control_ch_num, band->band); + if (freq == 0) { + brcmf_err("Invalid frequency %d returned for channel %d, band %d. chanspec was %04x\n", + freq, ch.control_ch_num, ch.band, bi->chanspec); + goto done; + } notify_channel = ieee80211_get_channel(wiphy, freq); done: @@ -7206,6 +7300,10 @@ static int brcmf_construct_chaninfo(struct brcmf_cfg80211_info *cfg, for (i = 0; i < band->n_channels; i++) band->channels[i].flags = IEEE80211_CHAN_DISABLED; band = wiphy->bands[NL80211_BAND_5GHZ]; + if (band) + for (i = 0; i < band->n_channels; i++) + band->channels[i].flags = IEEE80211_CHAN_DISABLED; + band = wiphy->bands[NL80211_BAND_6GHZ]; if (band) for (i = 0; i < band->n_channels; i++) band->channels[i].flags = IEEE80211_CHAN_DISABLED; @@ -7225,6 +7323,8 @@ static int brcmf_construct_chaninfo(struct brcmf_cfg80211_info *cfg, band = wiphy->bands[NL80211_BAND_2GHZ]; } else if (ch.band == BRCMU_CHAN_BAND_5G) { band = wiphy->bands[NL80211_BAND_5GHZ]; + } else if (ch.band == BRCMU_CHAN_BAND_6G) { + band = wiphy->bands[NL80211_BAND_6GHZ]; } else { bphy_err(drvr, "Invalid channel Spec. 0x%x.\n", ch.chspec); @@ -7390,7 +7490,7 @@ static int brcmf_enable_bw40_2g(struct brcmf_cfg80211_info *cfg) return err; } -static void brcmf_get_bwcap(struct brcmf_if *ifp, u32 bw_cap[]) +static void brcmf_get_bwcap(struct brcmf_if *ifp, u32 bw_cap[], bool has_6g) { struct brcmf_pub *drvr = ifp->drvr; u32 band, mimo_bwcap; @@ -7398,17 +7498,29 @@ static void brcmf_get_bwcap(struct brcmf_if *ifp, u32 bw_cap[]) band = WLC_BAND_2G; err = brcmf_fil_iovar_int_query(ifp, "bw_cap", &band); - if (!err) { - bw_cap[NL80211_BAND_2GHZ] = band; - band = WLC_BAND_5G; - err = brcmf_fil_iovar_int_query(ifp, "bw_cap", &band); - if (!err) { - bw_cap[NL80211_BAND_5GHZ] = band; - return; - } - WARN_ON(1); + if (err) + goto fallback; + bw_cap[NL80211_BAND_2GHZ] = band; + band = WLC_BAND_5G; + err |= brcmf_fil_iovar_int_query(ifp, "bw_cap", &band); + if (err) + goto fallback; + bw_cap[NL80211_BAND_5GHZ] = band; + if (!has_6g) return; - } + band = WLC_BAND_6G; + err |= brcmf_fil_iovar_int_query(ifp, "bw_cap", &band); + /* Prior to the introduction of 6g, this function only + * did fallback in the case of 2g and 5g -failing. + * As mimo_bwcap does not have 6g bwcap info anyway, + * we keep that behavior. + */ + if (err) + return; + bw_cap[NL80211_BAND_6GHZ] = band; + return; +fallback: + brcmf_dbg(INFO, "fallback to mimo_bw_cap info\n"); err = brcmf_fil_iovar_int_get(ifp, "mimo_bw_cap", &mimo_bwcap); if (err) @@ -7434,6 +7546,9 @@ static void brcmf_get_bwcap(struct brcmf_if *ifp, u32 bw_cap[]) static void brcmf_update_ht_cap(struct ieee80211_supported_band *band, u32 bw_cap[2], u32 nrxchain) { + /* Not supported in 6G band */ + if (band->band == NL80211_BAND_6GHZ) + return; band->ht_cap.ht_supported = true; if (bw_cap[band->band] & WLC_BW_40MHZ_BIT) { band->ht_cap.cap |= IEEE80211_HT_CAP_SGI_40; @@ -7466,8 +7581,8 @@ static void brcmf_update_vht_cap(struct ieee80211_supported_band *band, { __le16 mcs_map; - /* not allowed in 2.4G band */ - if (band->band == NL80211_BAND_2GHZ) + /* not allowed in 2.4G or 6G band */ + if (band->band == NL80211_BAND_2GHZ || band->band == NL80211_BAND_6GHZ) return; band->vht_cap.vht_supported = true; @@ -7523,6 +7638,120 @@ static void brcmf_update_vht_cap(struct ieee80211_supported_band *band, (7 << IEEE80211_VHT_CAP_MAX_A_MPDU_LENGTH_EXPONENT_SHIFT); } +static void brcmf_update_he_cap(struct ieee80211_supported_band *band, + struct ieee80211_sband_iftype_data *data) +{ + int idx = 1; + struct ieee80211_sta_he_cap *he_cap = &data->he_cap; + struct ieee80211_he_cap_elem *he_cap_elem = &he_cap->he_cap_elem; + struct ieee80211_he_mcs_nss_supp *he_mcs = &he_cap->he_mcs_nss_supp; + struct ieee80211_he_6ghz_capa *he_6ghz_capa = &data->he_6ghz_capa; + + if (!data) { + brcmf_err("failed to allocate sdata\n"); + return; + } + + data->types_mask = BIT(NL80211_IFTYPE_STATION); + he_cap->has_he = true; + + /* HE MAC Capabilities Information */ + he_cap_elem->mac_cap_info[0] = IEEE80211_HE_MAC_CAP0_HTC_HE | + IEEE80211_HE_MAC_CAP0_TWT_REQ | + IEEE80211_HE_MAC_CAP0_TWT_RES; + + he_cap_elem->mac_cap_info[1] = + IEEE80211_HE_MAC_CAP1_TF_MAC_PAD_DUR_8US | + IEEE80211_HE_MAC_CAP1_TF_MAC_PAD_DUR_16US; + + he_cap_elem->mac_cap_info[2] = IEEE80211_HE_MAC_CAP2_BSR | + IEEE80211_HE_MAC_CAP2_BCAST_TWT; + + he_cap_elem->mac_cap_info[3] = + IEEE80211_HE_MAC_CAP3_OMI_CONTROL | + IEEE80211_HE_MAC_CAP3_MAX_AMPDU_LEN_EXP_EXT_1 | + IEEE80211_HE_MAC_CAP3_FLEX_TWT_SCHED; + + he_cap_elem->mac_cap_info[4] = IEEE80211_HE_MAC_CAP4_AMSDU_IN_AMPDU; + + /* HE PHY Capabilities Information */ + he_cap_elem->phy_cap_info[0] = + IEEE80211_HE_PHY_CAP0_CHANNEL_WIDTH_SET_40MHZ_IN_2G | + IEEE80211_HE_PHY_CAP0_CHANNEL_WIDTH_SET_40MHZ_80MHZ_IN_5G | + IEEE80211_HE_PHY_CAP0_CHANNEL_WIDTH_SET_160MHZ_IN_5G; + ; + + he_cap_elem->phy_cap_info[1] = + IEEE80211_HE_PHY_CAP1_LDPC_CODING_IN_PAYLOAD; + + he_cap_elem->phy_cap_info[2] = + IEEE80211_HE_PHY_CAP2_NDP_4x_LTF_AND_3_2US | + IEEE80211_HE_PHY_CAP2_UL_MU_FULL_MU_MIMO | + IEEE80211_HE_PHY_CAP2_UL_MU_PARTIAL_MU_MIMO; + + he_cap_elem->phy_cap_info[3] = + IEEE80211_HE_PHY_CAP3_DCM_MAX_CONST_TX_QPSK | + IEEE80211_HE_PHY_CAP3_DCM_MAX_TX_NSS_2 | + IEEE80211_HE_PHY_CAP3_DCM_MAX_CONST_RX_16_QAM | + IEEE80211_HE_PHY_CAP3_SU_BEAMFORMER; + + he_cap_elem->phy_cap_info[4] = + IEEE80211_HE_PHY_CAP4_SU_BEAMFORMEE | + IEEE80211_HE_PHY_CAP4_BEAMFORMEE_MAX_STS_UNDER_80MHZ_MASK | + IEEE80211_HE_PHY_CAP4_BEAMFORMEE_MAX_STS_ABOVE_80MHZ_4 | + IEEE80211_HE_PHY_CAP4_BEAMFORMEE_MAX_STS_UNDER_80MHZ_8; + + he_cap_elem->phy_cap_info[5] = + IEEE80211_HE_PHY_CAP5_NG16_SU_FEEDBACK | + IEEE80211_HE_PHY_CAP5_NG16_MU_FEEDBACK | + IEEE80211_HE_PHY_CAP5_BEAMFORMEE_NUM_SND_DIM_UNDER_80MHZ_2; + + he_cap_elem->phy_cap_info[6] = + IEEE80211_HE_PHY_CAP6_CODEBOOK_SIZE_42_SU | + IEEE80211_HE_PHY_CAP6_CODEBOOK_SIZE_75_MU | + IEEE80211_HE_PHY_CAP6_TRIG_SU_BEAMFORMING_FB | + IEEE80211_HE_PHY_CAP6_TRIG_MU_BEAMFORMING_PARTIAL_BW_FB | + IEEE80211_HE_PHY_CAP6_TRIG_CQI_FB | + IEEE80211_HE_PHY_CAP6_PARTIAL_BW_EXT_RANGE | + IEEE80211_HE_PHY_CAP6_PPE_THRESHOLD_PRESENT; + + he_cap_elem->phy_cap_info[7] = + IEEE80211_HE_PHY_CAP7_HE_SU_MU_PPDU_4XLTF_AND_08_US_GI | + IEEE80211_HE_PHY_CAP7_MAX_NC_1; + + he_cap_elem->phy_cap_info[8] = + IEEE80211_HE_PHY_CAP8_HE_ER_SU_PPDU_4XLTF_AND_08_US_GI | + IEEE80211_HE_PHY_CAP8_20MHZ_IN_40MHZ_HE_PPDU_IN_2G | + IEEE80211_HE_PHY_CAP8_20MHZ_IN_160MHZ_HE_PPDU | + IEEE80211_HE_PHY_CAP8_80MHZ_IN_160MHZ_HE_PPDU; + + he_cap_elem->phy_cap_info[9] = + IEEE80211_HE_PHY_CAP9_TX_1024_QAM_LESS_THAN_242_TONE_RU | + IEEE80211_HE_PHY_CAP9_RX_1024_QAM_LESS_THAN_242_TONE_RU | + IEEE80211_HE_PHY_CAP9_RX_FULL_BW_SU_USING_MU_WITH_COMP_SIGB | + IEEE80211_HE_PHY_CAP9_RX_FULL_BW_SU_USING_MU_WITH_NON_COMP_SIGB; + + /* HE Supported MCS and NSS Set */ + he_mcs->rx_mcs_80 = cpu_to_le16(0xfffa); + he_mcs->tx_mcs_80 = cpu_to_le16(0xfffa); + he_mcs->rx_mcs_160 = cpu_to_le16(0xfffa); + he_mcs->tx_mcs_160 = cpu_to_le16(0xfffa); + /* HE 6 GHz band capabilities */ + if (band->band == NL80211_BAND_6GHZ) { + u16 capa = 0; + + capa = FIELD_PREP(IEEE80211_HE_6GHZ_CAP_MIN_MPDU_START, + IEEE80211_HT_MPDU_DENSITY_8) | + FIELD_PREP(IEEE80211_HE_6GHZ_CAP_MAX_AMPDU_LEN_EXP, + IEEE80211_VHT_MAX_AMPDU_1024K) | + FIELD_PREP(IEEE80211_HE_6GHZ_CAP_MAX_MPDU_LEN, + IEEE80211_VHT_CAP_MAX_MPDU_LENGTH_11454); + he_6ghz_capa->capa = cpu_to_le16(capa); + } + band->n_iftype_data = idx; + band->iftype_data = data; +} + static int brcmf_setup_wiphybands(struct brcmf_cfg80211_info *cfg) { struct brcmf_pub *drvr = cfg->pub; @@ -7530,7 +7759,8 @@ static int brcmf_setup_wiphybands(struct brcmf_cfg80211_info *cfg) struct wiphy *wiphy = cfg_to_wiphy(cfg); u32 nmode; u32 vhtmode = 0; - u32 bw_cap[2] = { WLC_BW_20MHZ_BIT, WLC_BW_20MHZ_BIT }; + /* 2GHZ, 5GHZ, 60GHZ, 6GHZ */ + u32 bw_cap[4] = { WLC_BW_20MHZ_BIT, WLC_BW_20MHZ_BIT, 0, 0 }; u32 rxchain; u32 txchain; u32 nrxchain; @@ -7542,6 +7772,8 @@ static int brcmf_setup_wiphybands(struct brcmf_cfg80211_info *cfg) u32 rxstreams = 0; u32 txbf_bfe_cap = 0; u32 txbf_bfr_cap = 0; + u8 he_enable; + struct brcmf_he_defcap he_cap; u32 ldpc_cap = 0; u32 stbc_rx = 0; u32 stbc_tx = 0; @@ -7550,15 +7782,26 @@ static int brcmf_setup_wiphybands(struct brcmf_cfg80211_info *cfg) (void)brcmf_fil_iovar_int_get(ifp, "ldpc_cap", &ldpc_cap); (void)brcmf_fil_iovar_int_get(ifp, "stbc_rx", &stbc_rx); (void)brcmf_fil_iovar_int_get(ifp, "stbc_tx", &stbc_tx); + err = brcmf_fil_xtlv_int8_get(ifp, "he", BRCMF_HE_CMD_ENABLE, + &he_enable); + if (!err && he_enable) { + brcmf_fil_xtlv_data_get(ifp, "he", BRCMF_HE_CMD_DEFCAP, &he_cap, + sizeof(he_cap)); + brcmf_dbg_hex_dump(BRCMF_INFO_ON(), he_cap.mac_cap, 6, + "default HE mac cap\n"); + brcmf_dbg_hex_dump(BRCMF_INFO_ON(), he_cap.phy_cap, 11, + "default HE phy cap\n"); + } err = brcmf_fil_iovar_int_get(ifp, "nmode", &nmode); if (err) { bphy_err(drvr, "nmode error (%d)\n", err); - } else { - brcmf_get_bwcap(ifp, bw_cap); } - brcmf_dbg(INFO, "nmode=%d, vhtmode=%d, bw_cap=(%d, %d)\n", + brcmf_get_bwcap(ifp, bw_cap, he_enable != 0); + brcmf_dbg(INFO, + "nmode=%d, vhtmode=%d, bw_cap=(%d, %d, %d), he_enable=%d\n", nmode, vhtmode, bw_cap[NL80211_BAND_2GHZ], - bw_cap[NL80211_BAND_5GHZ]); + bw_cap[NL80211_BAND_5GHZ], bw_cap[NL80211_BAND_6GHZ], + he_enable); err = brcmf_fil_iovar_int_get(ifp, "rxchain", &rxchain); if (err) { @@ -7620,6 +7863,8 @@ static int brcmf_setup_wiphybands(struct brcmf_cfg80211_info *cfg) brcmf_update_vht_cap(band, bw_cap, txstreams, rxstreams, txbf_bfe_cap, txbf_bfr_cap, ldpc_cap, stbc_rx, stbc_tx); + if (he_enable) + brcmf_update_he_cap(band, &sdata[band->band]); } return 0; @@ -8004,12 +8249,27 @@ static int brcmf_setup_wiphy(struct wiphy *wiphy, struct brcmf_if *ifp) band->n_channels = ARRAY_SIZE(__wl_5ghz_channels); wiphy->bands[NL80211_BAND_5GHZ] = band; } - } + if (bandlist[i] == cpu_to_le32(WLC_BAND_6G)) { + band = kmemdup(&__wl_band_6ghz, sizeof(__wl_band_6ghz), + GFP_KERNEL); + if (!band) + return -ENOMEM; + band->channels = kmemdup(&__wl_6ghz_channels, + sizeof(__wl_6ghz_channels), + GFP_KERNEL); + if (!band->channels) { + kfree(band); + return -ENOMEM; + } + + band->n_channels = ARRAY_SIZE(__wl_6ghz_channels); + wiphy->bands[NL80211_BAND_6GHZ] = band; + } + } if (wiphy->bands[NL80211_BAND_5GHZ] && brcmf_feat_is_enabled(ifp, BRCMF_FEAT_DOT11H)) - wiphy_ext_feature_set(wiphy, - NL80211_EXT_FEATURE_DFS_OFFLOAD); + wiphy_ext_feature_set(wiphy, NL80211_EXT_FEATURE_DFS_OFFLOAD); wiphy_ext_feature_set(wiphy, NL80211_EXT_FEATURE_CQM_RSSI_LIST); @@ -8539,6 +8799,10 @@ static void brcmf_free_wiphy(struct wiphy *wiphy) kfree(wiphy->bands[NL80211_BAND_5GHZ]->channels); kfree(wiphy->bands[NL80211_BAND_5GHZ]); } + if (wiphy->bands[NL80211_BAND_6GHZ]) { + kfree(wiphy->bands[NL80211_BAND_6GHZ]->channels); + kfree(wiphy->bands[NL80211_BAND_6GHZ]); + } #if IS_ENABLED(CONFIG_PM) if (wiphy->wowlan != &brcmf_wowlan_support) kfree(wiphy->wowlan); @@ -8630,18 +8894,21 @@ struct brcmf_cfg80211_info *brcmf_cfg80211_attach(struct brcmf_pub *drvr, if (brcmf_feat_is_enabled(ifp, BRCMF_FEAT_DUMP_OBSS)) ops->dump_survey = brcmf_cfg80211_dump_survey; - err = wiphy_register(wiphy); - if (err < 0) { - bphy_err(drvr, "Could not register wiphy device (%d)\n", err); - goto priv_out; - } - + /* We have to configure the bands before we register the wiphy device + * because it requires that band capabilities be correct. + */ err = brcmf_setup_wiphybands(cfg); if (err) { bphy_err(drvr, "Setting wiphy bands failed (%d)\n", err); goto wiphy_unreg_out; } + err = wiphy_register(wiphy); + if (err < 0) { + bphy_err(drvr, "Could not register wiphy device (%d)\n", err); + goto priv_out; + } + /* If cfg80211 didn't disable 40MHz HT CAP in wiphy_register(), * setup 40MHz in 2GHz band and enable OBSS scanning. */ diff --git a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/debug.h b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/debug.h index 9bb5f709d41a27..432d93ae8fb854 100644 --- a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/debug.h +++ b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/debug.h @@ -85,6 +85,7 @@ do { \ #define BRCMF_FIL_ON() (brcmf_msg_level & BRCMF_FIL_VAL) #define BRCMF_FWCON_ON() (brcmf_msg_level & BRCMF_FWCON_VAL) #define BRCMF_SCAN_ON() (brcmf_msg_level & BRCMF_SCAN_VAL) +#define BRCMF_INFO_ON() (brcmf_msg_level & BRCMF_INFO_VAL) #else /* defined(DEBUG) || defined(CONFIG_BRCM_TRACING) */ @@ -104,6 +105,7 @@ do { \ #define BRCMF_FIL_ON() 0 #define BRCMF_FWCON_ON() 0 #define BRCMF_SCAN_ON() 0 +#define BRCMF_INFO_ON() 0 #endif /* defined(DEBUG) || defined(CONFIG_BRCM_TRACING) */ diff --git a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/fwil_types.h b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/fwil_types.h index 70deae79286083..d8f8101c625258 100644 --- a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/fwil_types.h +++ b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/fwil_types.h @@ -1336,4 +1336,96 @@ struct brcmf_wsec_info { struct brcmf_wsec_info_tlv tlvs[1]; /* tlv data follows */ }; +/* HE top level command IDs */ +enum { + BRCMF_HE_CMD_ENABLE = 0, + BRCMF_HE_CMD_FEATURES = 1, + BRCMF_HE_CMD_SR = 2, + BRCMF_HE_CMD_TESTBED = 3, + BRCMF_HE_CMD_BSR_SUPPORT = 4, + BRCMF_HE_CMD_BSSCOLOR = 5, + BRCMF_HE_CMD_PARTIAL_BSSCOLOR = 6, + BRCMF_HE_CMD_CAP = 7, + BRCMF_HE_CMD_OMI = 8, + BRCMF_HE_CMD_RANGE_EXT = 9, + BRCMF_HE_CMD_RTSDURTHRESH = 10, + BRCMF_HE_CMD_PEDURATION = 11, + BRCMF_HE_CMD_MUEDCA = 12, + BRCMF_HE_CMD_DYNFRAG = 13, + BRCMF_HE_CMD_PPET = 14, + BRCMF_HE_CMD_HTC = 15, + BRCMF_HE_CMD_AXMODE = 16, + BRCMF_HE_CMD_FRAGTX = 17, + BRCMF_HE_CMD_DEFCAP = 18, +}; + +#define BRCMF_HE_VER_1 1 + +struct brcmf_he_bsscolor { + u8 color; /* 1..63, on get returns currently in use color */ + u8 disabled; /* 0/1, 0 means disabled is false, so coloring is enabled */ + u8 switch_count; /* 0, immediate programming, 1 .. 255 beacon count down */ + u8 PAD; +}; + +struct brcmf_he_omi { + u8 peer[ETH_ALEN]; /* leave it all 0s' for non-AP */ + u8 rx_nss; /* 0..7 */ + u8 channel_width; /* 0:20, 1:40, 2:80, 3:160 */ + u8 ul_mu_disable; /* 0|1 */ + u8 tx_nsts; /* 0..7 */ + u8 er_su_disable; /* 0|1 */ + u8 dl_mumimo_resound; /* 0|1 */ + u8 ul_mu_data_disable; /* 0|1 */ + u8 tx_override; /* 0, only used for testbed AP */ + u8 PAD[2]; +}; + +struct brcmf_he_edca_v1 { + u8 aci_aifsn; + u8 ecw_min_max; + u8 muedca_timer; + u8 PAD; +}; + +#define BRCMF_AC_COUNT 4 +struct brcmf_he_muedca_v1 { + /* structure control */ + __le16 version; /* structure version */ + __le16 length; /* data length (starting after this field) */ + struct brcmf_he_edca_v1 ac_param_ap[BRCMF_AC_COUNT]; + struct brcmf_he_edca_v1 ac_param_sta[BRCMF_AC_COUNT]; +}; + +#define BRCMF_HE_SR_VER_1 1 + +#define SRC_PSR_DIS 0x01 +#define SRC_NON_SRG_OBSS_PD_SR_DIS 0x02 +#define SRC_NON_SRG_OFFSET_PRESENT 0x04 +#define SRC_SRG_INFORMATION_PRESENT 0x08 +#define SRC_HESIGA_SPATIAL_REUSE_VALUE15_ALLOWED 0x10 + +#define HE_SR_SRG_INFO_LEN 18 + +struct brcmf_he_sr_v1 { + /* structure control */ + __le16 version; /* structure version */ + __le16 length; /* data length (starting after this field) */ + u8 enabled; + u8 src; /* SR control, see above defines. */ + u8 non_srg_offset; /* Non-SRG Offset */ + u8 srg[HE_SR_SRG_INFO_LEN]; /* SRG Information */ +}; + +#define BRCMF_HE_DEFCAP_VER_1 1 + +struct brcmf_he_defcap { + __le16 version; /* structure version */ + __le16 length; /* data length (starting after this field) */ + u8 bsscfg_type; + u8 bsscfg_subtype; + u8 mac_cap[6]; + u8 phy_cap[11]; +}; + #endif /* FWIL_TYPES_H_ */ From c02147b7613b8dcce4529b24c17c0fec1db1b616 Mon Sep 17 00:00:00 2001 From: Daniel Berlin Date: Wed, 18 Oct 2023 23:30:49 -0400 Subject: [PATCH 0232/3902] [brcmfmac] Fix regulatory domain handling to reset bands properly Currently, we ignore the default country in the reg notifier. We also register a custom regulatory domain, which is set as the default. As a result, the chip is likely to be set to the correct country, but the regulatory domain will not match it. When the regulatory notifier is then called, we see the countries are the same and do not change anything, even though the domain is wrong. This patch forces us to reset the bands on the first country change even if the chip is already set to that country. We also restore the original band info before reconstructing channel info, as the new regdom power limits may be higher than what is currently set. Signed-off-by: Daniel Berlin --- .../broadcom/brcm80211/brcmfmac/cfg80211.c | 37 ++++++++++++++++--- .../broadcom/brcm80211/brcmfmac/cfg80211.h | 2 + 2 files changed, 33 insertions(+), 6 deletions(-) diff --git a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/cfg80211.c b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/cfg80211.c index 0c9188c9f671f6..0dd6e9a911b2b6 100644 --- a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/cfg80211.c +++ b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/cfg80211.c @@ -7295,18 +7295,34 @@ static int brcmf_construct_chaninfo(struct brcmf_cfg80211_info *cfg, goto fail_pbuf; } + /* Changing regulatory domain may change power limits upwards. + * To ensure that we correctly set the new band info, copy the original + * info first. + */ band = wiphy->bands[NL80211_BAND_2GHZ]; - if (band) + if (band) { + memcpy(band->channels, &__wl_2ghz_channels, + sizeof(__wl_2ghz_channels)); + band->n_channels = ARRAY_SIZE(__wl_2ghz_channels); for (i = 0; i < band->n_channels; i++) band->channels[i].flags = IEEE80211_CHAN_DISABLED; + } band = wiphy->bands[NL80211_BAND_5GHZ]; - if (band) + if (band) { + memcpy(band->channels, &__wl_5ghz_channels, + sizeof(__wl_5ghz_channels)); + band->n_channels = ARRAY_SIZE(__wl_5ghz_channels); for (i = 0; i < band->n_channels; i++) band->channels[i].flags = IEEE80211_CHAN_DISABLED; + } band = wiphy->bands[NL80211_BAND_6GHZ]; - if (band) + if (band) { + memcpy(band->channels, &__wl_6ghz_channels, + sizeof(__wl_6ghz_channels)); + band->n_channels = ARRAY_SIZE(__wl_6ghz_channels); for (i = 0; i < band->n_channels; i++) band->channels[i].flags = IEEE80211_CHAN_DISABLED; + } total = le32_to_cpu(list->count); if (total > BRCMF_MAX_CHANSPEC_LIST) { bphy_err(drvr, "Invalid count of channel Spec. (%u)\n", @@ -8768,9 +8784,17 @@ static void brcmf_cfg80211_reg_notifier(struct wiphy *wiphy, } err = brcmf_translate_country_code(ifp->drvr, req->alpha2, &ccreq); - if (err) - return; - + if (err) { + /* Because we ignore the default country code above, + * we will start out in our custom reg domain, but the chip + * may already be set to the right country. + * As such, we force the bands to be re-set the first + * time we try to set a country for real. + */ + if (err != -EAGAIN || !cfg->force_band_setup) + return; + } + cfg->force_band_setup = false; err = brcmf_fil_iovar_data_set(ifp, "country", &ccreq, sizeof(ccreq)); if (err) { bphy_err(drvr, "Firmware rejected country setting\n"); @@ -8837,6 +8861,7 @@ struct brcmf_cfg80211_info *brcmf_cfg80211_attach(struct brcmf_pub *drvr, cfg->pub = drvr; init_vif_event(&cfg->vif_event); INIT_LIST_HEAD(&cfg->vif_list); + cfg->force_band_setup = true; vif = brcmf_alloc_vif(cfg, NL80211_IFTYPE_STATION); if (IS_ERR(vif)) diff --git a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/cfg80211.h b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/cfg80211.h index 273c80f2d483a3..2ecdef71724aeb 100644 --- a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/cfg80211.h +++ b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/cfg80211.h @@ -350,6 +350,7 @@ struct brcmf_cfg80211_wowl { * @dongle_up: indicate whether dongle up or not. * @roam_on: on/off switch for dongle self-roaming. * @scan_tried: indicates if first scan attempted. + * @force_band_setup: indicates if we should force band setup * @dcmd_buf: dcmd buffer. * @extra_buf: mainly to grab assoc information. * @debugfsdir: debugfs folder for this device. @@ -380,6 +381,7 @@ struct brcmf_cfg80211_info { bool pwr_save; bool dongle_up; bool scan_tried; + bool force_band_setup; u8 *dcmd_buf; u8 *extra_buf; struct dentry *debugfsdir; From 77f589e0777698197f24e03baa24704f4dec2441 Mon Sep 17 00:00:00 2001 From: Daniel Berlin Date: Sun, 12 Nov 2023 15:49:54 -0500 Subject: [PATCH 0233/3902] fixup! fix FWIL definition to use SSID length constant Signed-off-by: Daniel Berlin --- drivers/net/wireless/broadcom/brcm80211/brcmfmac/fwil_types.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/fwil_types.h b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/fwil_types.h index d8f8101c625258..b8376ec39e4340 100644 --- a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/fwil_types.h +++ b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/fwil_types.h @@ -332,7 +332,7 @@ struct brcmf_bss_info_le { __le16 beacon_period; /* units are Kusec */ __le16 capability; /* Capability information */ u8 SSID_len; - u8 SSID[32]; + u8 SSID[IEEE80211_MAX_SSID_LEN]; u8 bcnflags; /* additional flags w.r.t. beacon */ struct { __le32 count; /* # rates in this set */ From 519f9ec132e352419822b216fa331a8e5b8a2299 Mon Sep 17 00:00:00 2001 From: Daniel Berlin Date: Sun, 12 Nov 2023 15:50:57 -0500 Subject: [PATCH 0234/3902] fixup! define missing event message extension Signed-off-by: Daniel Berlin --- drivers/net/wireless/broadcom/brcm80211/brcmfmac/feature.h | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/feature.h b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/feature.h index 1c967e54c0c78b..bf33ea606c0c7e 100644 --- a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/feature.h +++ b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/feature.h @@ -35,6 +35,7 @@ * SCAN_v3: Version 3 scan params * PMKID_V2: Version 2 PMKID * PMKID_V3: Version 3 PMKID + * EVENT_MSGS_EXT: Event messages extension * JOIN_V1: Version 1 join struct * GCMP: GCMP Cipher suite support */ From f3fd250bd8feed254d9da9e06ed97256a8c0fc9c Mon Sep 17 00:00:00 2001 From: Daniel Berlin Date: Sun, 12 Nov 2023 15:46:08 -0500 Subject: [PATCH 0235/3902] [brcmfmac] Structurize PNF scan and add support for latest version This patch structurizes PNF scan handling, adding support for netinfo v3 and PNO v3 structures. This in turn, enables the chip to tell us about 6G scan results, as the results contain chanspecs and not just channels. Signed-off-by: Daniel Berlin --- .../broadcom/brcm80211/brcmfmac/cfg80211.c | 123 +++----- .../broadcom/brcm80211/brcmfmac/cfg80211.h | 17 + .../broadcom/brcm80211/brcmfmac/core.h | 20 ++ .../broadcom/brcm80211/brcmfmac/feature.c | 12 + .../broadcom/brcm80211/brcmfmac/fwil_types.h | 70 +++++ .../broadcom/brcm80211/brcmfmac/pno.c | 294 +++++++++++++++++- .../broadcom/brcm80211/brcmfmac/pno.h | 10 +- 7 files changed, 456 insertions(+), 90 deletions(-) diff --git a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/cfg80211.c b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/cfg80211.c index 0dd6e9a911b2b6..382f4cc0d54263 100644 --- a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/cfg80211.c +++ b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/cfg80211.c @@ -4002,17 +4002,11 @@ brcmf_alloc_internal_escan_request(struct wiphy *wiphy, u32 n_netinfo) { } static int brcmf_internal_escan_add_info(struct cfg80211_scan_request *req, - u8 *ssid, u8 ssid_len, u8 channel) + u8 *ssid, u8 ssid_len, u8 channel, enum nl80211_band band) { struct ieee80211_channel *chan; - enum nl80211_band band; int freq, i; - if (channel <= CH_MAX_2G_CHANNEL) - band = NL80211_BAND_2GHZ; - else - band = NL80211_BAND_5GHZ; - freq = ieee80211_channel_to_frequency(channel, band); if (!freq) return -EINVAL; @@ -4068,53 +4062,30 @@ static int brcmf_start_internal_escan(struct brcmf_if *ifp, u32 fwmap, return 0; } -static struct brcmf_pno_net_info_le * -brcmf_get_netinfo_array(struct brcmf_pno_scanresults_le *pfn_v1) -{ - struct brcmf_pno_scanresults_v2_le *pfn_v2; - struct brcmf_pno_net_info_le *netinfo; - - switch (pfn_v1->version) { - default: - WARN_ON(1); - fallthrough; - case cpu_to_le32(1): - netinfo = (struct brcmf_pno_net_info_le *)(pfn_v1 + 1); - break; - case cpu_to_le32(2): - pfn_v2 = (struct brcmf_pno_scanresults_v2_le *)pfn_v1; - netinfo = (struct brcmf_pno_net_info_le *)(pfn_v2 + 1); - break; - } - - return netinfo; -} - /* PFN result doesn't have all the info which are required by the supplicant * (For e.g IEs) Do a target Escan so that sched scan results are reported * via wl_inform_single_bss in the required format. Escan does require the * scan request in the form of cfg80211_scan_request. For timebeing, create * cfg80211_scan_request one out of the received PNO event. */ -static s32 -brcmf_notify_sched_scan_results(struct brcmf_if *ifp, - const struct brcmf_event_msg *e, void *data) +static s32 brcmf_notify_sched_scan_results(struct brcmf_if *ifp, + const struct brcmf_event_msg *e, + void *data) { struct brcmf_pub *drvr = ifp->drvr; struct brcmf_cfg80211_info *cfg = drvr->config; - struct brcmf_pno_net_info_le *netinfo, *netinfo_start; struct cfg80211_scan_request *request = NULL; struct wiphy *wiphy = cfg_to_wiphy(cfg); int i, err = 0; - struct brcmf_pno_scanresults_le *pfn_result; u32 bucket_map; u32 result_count; u32 status; - u32 datalen; + u32 min_data_len; brcmf_dbg(SCAN, "Enter\n"); + min_data_len = drvr->pno_handler.get_min_data_len(); - if (e->datalen < (sizeof(*pfn_result) + sizeof(*netinfo))) { + if (e->datalen < min_data_len) { brcmf_dbg(SCAN, "Event data too small. Ignore\n"); return 0; } @@ -4124,9 +4095,8 @@ brcmf_notify_sched_scan_results(struct brcmf_if *ifp, return 0; } - pfn_result = (struct brcmf_pno_scanresults_le *)data; - result_count = le32_to_cpu(pfn_result->count); - status = le32_to_cpu(pfn_result->status); + result_count = drvr->pno_handler.get_result_count(data); + status = drvr->pno_handler.get_result_status(data); /* PFN event is limited to fit 512 bytes so we may get * multiple NET_FOUND events. For now place a warning here. @@ -4137,38 +4107,33 @@ brcmf_notify_sched_scan_results(struct brcmf_if *ifp, bphy_err(drvr, "FALSE PNO Event. (pfn_count == 0)\n"); goto out_err; } - - netinfo_start = brcmf_get_netinfo_array(pfn_result); - datalen = e->datalen - ((void *)netinfo_start - (void *)pfn_result); - if (datalen < result_count * sizeof(*netinfo)) { - bphy_err(drvr, "insufficient event data\n"); + err = drvr->pno_handler.validate_pfn_results(data, e->datalen); + if (err) { + bphy_err(drvr, "Invalid escan results (%d)", err); goto out_err; } - - request = brcmf_alloc_internal_escan_request(wiphy, - result_count); + request = brcmf_alloc_internal_escan_request(wiphy, result_count); if (!request) { err = -ENOMEM; goto out_err; } - bucket_map = 0; for (i = 0; i < result_count; i++) { - netinfo = &netinfo_start[i]; - - if (netinfo->SSID_len > IEEE80211_MAX_SSID_LEN) - netinfo->SSID_len = IEEE80211_MAX_SSID_LEN; - brcmf_dbg(SCAN, "SSID:%.32s Channel:%d\n", - netinfo->SSID, netinfo->channel); - bucket_map |= brcmf_pno_get_bucket_map(cfg->pno, netinfo); - err = brcmf_internal_escan_add_info(request, - netinfo->SSID, - netinfo->SSID_len, - netinfo->channel); + u8 channel; + enum nl80211_band band; + u8 ssid[IEEE80211_MAX_SSID_LEN]; + u8 ssid_len; + + drvr->pno_handler.get_result_info(data, i, &ssid, &ssid_len, + &channel, &band); + brcmf_dbg(SCAN, "SSID:%.32s Channel:%d Band:%d\n", ssid, + channel, band); + bucket_map |= drvr->pno_handler.get_bucket_map(data, i, cfg->pno); + err = brcmf_internal_escan_add_info(request, ssid, ssid_len, + channel, band); if (err) goto out_err; } - if (!bucket_map) goto free_req; @@ -4271,48 +4236,50 @@ static s32 brcmf_config_wowl_pattern(struct brcmf_if *ifp, u8 cmd[4], return ret; } -static s32 -brcmf_wowl_nd_results(struct brcmf_if *ifp, const struct brcmf_event_msg *e, - void *data) +static s32 brcmf_wowl_nd_results(struct brcmf_if *ifp, + const struct brcmf_event_msg *e, void *data) { struct brcmf_pub *drvr = ifp->drvr; struct brcmf_cfg80211_info *cfg = drvr->config; - struct brcmf_pno_scanresults_le *pfn_result; - struct brcmf_pno_net_info_le *netinfo; + u32 min_data_len; + u8 channel; + enum nl80211_band band; + u8 ssid[IEEE80211_MAX_SSID_LEN]; + u8 ssid_len; + u32 result_count; brcmf_dbg(SCAN, "Enter\n"); - if (e->datalen < (sizeof(*pfn_result) + sizeof(*netinfo))) { + min_data_len = drvr->pno_handler.get_min_data_len(); + + if (e->datalen < min_data_len) { brcmf_dbg(SCAN, "Event data too small. Ignore\n"); return 0; } - pfn_result = (struct brcmf_pno_scanresults_le *)data; if (e->event_code == BRCMF_E_PFN_NET_LOST) { brcmf_dbg(SCAN, "PFN NET LOST event. Ignore\n"); return 0; } - if (le32_to_cpu(pfn_result->count) < 1) { + result_count = drvr->pno_handler.get_result_count(data); + if (result_count < 1) { bphy_err(drvr, "Invalid result count, expected 1 (%d)\n", - le32_to_cpu(pfn_result->count)); + result_count); return -EINVAL; } - netinfo = brcmf_get_netinfo_array(pfn_result); - if (netinfo->SSID_len > IEEE80211_MAX_SSID_LEN) - netinfo->SSID_len = IEEE80211_MAX_SSID_LEN; - memcpy(cfg->wowl.nd->ssid.ssid, netinfo->SSID, netinfo->SSID_len); - cfg->wowl.nd->ssid.ssid_len = netinfo->SSID_len; + drvr->pno_handler.get_result_info(data, 0, &ssid, &ssid_len, &channel, + &band); + memcpy(cfg->wowl.nd->ssid.ssid, ssid, ssid_len); + cfg->wowl.nd->ssid.ssid_len = ssid_len; cfg->wowl.nd->n_channels = 1; cfg->wowl.nd->channels[0] = - ieee80211_channel_to_frequency(netinfo->channel, - netinfo->channel <= CH_MAX_2G_CHANNEL ? - NL80211_BAND_2GHZ : NL80211_BAND_5GHZ); + ieee80211_channel_to_frequency(channel, band); + cfg->wowl.nd_info->n_matches = 1; cfg->wowl.nd_info->matches[0] = cfg->wowl.nd; - /* Inform (the resume task) that the net detect information was recvd */ cfg->wowl.nd_data_completed = true; wake_up(&cfg->wowl.nd_data_wait); diff --git a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/cfg80211.h b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/cfg80211.h index 2ecdef71724aeb..9fef47e60e0868 100644 --- a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/cfg80211.h +++ b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/cfg80211.h @@ -8,6 +8,7 @@ /* for brcmu_d11inf */ #include +#include #include "core.h" #include "fwil_types.h" @@ -411,6 +412,22 @@ struct brcmf_tlv { u8 data[]; }; +static inline enum nl80211_band fwil_band_to_nl80211(u16 band) +{ + switch (band) { + case WLC_BAND_2G: + return NL80211_BAND_2GHZ; + case WLC_BAND_5G: + return NL80211_BAND_5GHZ; + case WLC_BAND_6G: + return NL80211_BAND_6GHZ; + default: + WARN_ON(1); + break; + } + return 0; +} + static inline struct wiphy *cfg_to_wiphy(struct brcmf_cfg80211_info *cfg) { return cfg->wiphy; diff --git a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/core.h b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/core.h index 399b6810e394de..a75ce5e9297eb5 100644 --- a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/core.h +++ b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/core.h @@ -97,6 +97,24 @@ struct brcmf_rev_info { u32 nvramrev; }; +struct brcmf_pno_info; +/** + * struct pno_struct_handler + */ +struct pno_struct_handler { + u8 version; + int (*pno_config)(struct brcmf_if *ifp, u32 scan_freq, u32 mscan, + u32 bestn); + u32 (*get_min_data_len)(void); + u32 (*get_result_count)(void *data); + u32 (*get_result_status)(void *data); + int (*validate_pfn_results)(void *data, u32 event_datalen); + u32 (*get_bucket_map)(void *data, int idx, struct brcmf_pno_info *pi); + int (*get_result_info)(void *data, int result_idx, + u8 (*ssid)[IEEE80211_MAX_SSID_LEN], u8 *ssid_len, + u8 *channel, enum nl80211_band *band); +}; + /* Common structure for module and instance linkage */ struct brcmf_pub { /* Linkage ponters */ @@ -145,6 +163,8 @@ struct brcmf_pub { u8 sta_mac_idx; const struct brcmf_fwvid_ops *vops; void *vdata; + u16 cnt_ver; + struct pno_struct_handler pno_handler; }; /* forward declarations */ diff --git a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/feature.c b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/feature.c index b3bae1b2f79048..341f988afca30d 100644 --- a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/feature.c +++ b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/feature.c @@ -16,6 +16,7 @@ #include "fwvid.h" #include "feature.h" #include "common.h" +#include "pno.h" #define BRCMF_FW_UNSUPPORTED 23 @@ -291,6 +292,7 @@ void brcmf_feat_attach(struct brcmf_pub *drvr) { struct brcmf_if *ifp = brcmf_get_ifp(drvr, 0); struct brcmf_wl_scan_version_le scan_ver; + struct brcmf_pno_param_v3_le pno_params; struct brcmf_pno_macaddr_le pfn_mac; struct brcmf_gscan_config gscan_cfg; u32 wowl_cap; @@ -357,6 +359,16 @@ void brcmf_feat_attach(struct brcmf_pub *drvr) } } + /* See what version of PFN scan is supported*/ + err = brcmf_fil_iovar_data_get(ifp, "pno_set", &pno_params, + sizeof(pno_params)); + if (!err) { + brcmf_pno_setup_for_version(drvr, le16_to_cpu(pno_params.version)); + } else { + /* Default to version 2, supported by all chips we support. */ + brcmf_pno_setup_for_version(drvr, 2); + } + brcmf_feat_wlc_version_overrides(drvr); brcmf_feat_firmware_overrides(drvr); diff --git a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/fwil_types.h b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/fwil_types.h index b8376ec39e4340..151cef2c2e3196 100644 --- a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/fwil_types.h +++ b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/fwil_types.h @@ -1064,6 +1064,46 @@ struct brcmf_pno_param_le { __le32 slow_freq; }; +/** + * struct brcmf_pno_param_le - PNO scan configuration parameters + * + * @version: PNO parameters version. + * @length: Length of PNO structure + * @scan_freq: scan frequency. + * @lost_network_timeout: #sec. to declare discovered network as lost. + * @flags: Bit field to control features of PFN such as sort criteria auto + * enable switch and background scan. + * @rssi_margin: Margin to avoid jitter for choosing a PFN based on RSSI sort + * criteria. + * @bestn: number of best networks in each scan. + * @mscan: number of scans recorded. + * @repeat: minimum number of scan intervals before scan frequency changes + * in adaptive scan. + * @exp: exponent of 2 for maximum scan interval. + * @slow_freq: slow scan period. + * @min_bound: min bound for scan time randomization + * @max_bound: max bound for scan time randomization + * @pfn_lp_scan_disable: unused + * @pfn_lp_scan_cnt: allow interleaving lp scan with hp scan + */ +struct brcmf_pno_param_v3_le { + __le16 version; + __le16 length; + __le32 scan_freq; + __le32 lost_network_timeout; + __le16 flags; + __le16 rssi_margin; + u8 bestn; + u8 mscan; + u8 repeat; + u8 exp; + __le32 slow_freq; + u8 min_bound; + u8 max_bound; + u8 pfn_lp_scan_disable; + u8 pfn_lp_scan_cnt; +}; + /** * struct brcmf_pno_config_le - PNO channel configuration. * @@ -1117,6 +1157,28 @@ struct brcmf_pno_net_info_le { __le16 timestamp; }; +/** + * struct brcmf_pno_net_info_v3_le - information per found network. + * + * @bssid: BSS network identifier. + * @chanspec: channel spec. + * @SSID_len: length of ssid. + * @SSID: ssid characters. + * @flags: flags + * @RSSI: receive signal strength (in dBm). + * @timestamp: age in seconds. + */ +struct brcmf_pno_net_info_v3_le { + u8 bssid[6]; + u16 chanspec; + u8 SSID_len; + u8 padding; + u16 flags; + u8 SSID[32]; + __le16 RSSI; + __le16 timestamp; +}; + /** * struct brcmf_pno_scanresults_le - result returned in PNO NET FOUND event. * @@ -1137,6 +1199,14 @@ struct brcmf_pno_scanresults_v2_le { __le32 scan_ch_bucket; }; +/* V2 and V3 structs are the same */ +struct brcmf_pno_scanresults_v3_le { + __le32 version; + __le32 status; + __le32 count; + __le32 scan_ch_bucket; +}; + /** * struct brcmf_pno_macaddr_le - to configure PNO macaddr randomization. * diff --git a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/pno.c b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/pno.c index 05f66ab13bed6d..dbeeaef75b165a 100644 --- a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/pno.c +++ b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/pno.c @@ -12,8 +12,10 @@ #include "fwil_types.h" #include "cfg80211.h" #include "pno.h" +#include "feature.h" -#define BRCMF_PNO_VERSION 2 +#define BRCMF_PNO_VERSION_2 2 +#define BRCMF_PNO_VERSION_3 3 #define BRCMF_PNO_REPEAT 4 #define BRCMF_PNO_FREQ_EXPO_MAX 3 #define BRCMF_PNO_IMMEDIATE_SCAN_BIT 3 @@ -99,8 +101,62 @@ static int brcmf_pno_channel_config(struct brcmf_if *ifp, return brcmf_fil_iovar_data_set(ifp, "pfn_cfg", cfg, sizeof(*cfg)); } -static int brcmf_pno_config(struct brcmf_if *ifp, u32 scan_freq, - u32 mscan, u32 bestn) +static int brcmf_pno_config_v3(struct brcmf_if *ifp, u32 scan_freq, u32 mscan, + u32 bestn) +{ + struct brcmf_pub *drvr = ifp->drvr; + struct brcmf_pno_param_v3_le pfn_param; + u16 flags; + u32 pfnmem; + s32 err; + + memset(&pfn_param, 0, sizeof(pfn_param)); + pfn_param.version = cpu_to_le16(BRCMF_PNO_VERSION_3); + pfn_param.length = cpu_to_le16(sizeof(struct brcmf_pno_param_v3_le)); + + /* set extra pno params */ + flags = BIT(BRCMF_PNO_IMMEDIATE_SCAN_BIT) | + BIT(BRCMF_PNO_ENABLE_ADAPTSCAN_BIT); + pfn_param.repeat = BRCMF_PNO_REPEAT; + pfn_param.exp = BRCMF_PNO_FREQ_EXPO_MAX; + + /* set up pno scan fr */ + pfn_param.scan_freq = cpu_to_le32(scan_freq); + + if (mscan) { + pfnmem = bestn; + + /* set bestn in firmware */ + err = brcmf_fil_iovar_int_set(ifp, "pfnmem", pfnmem); + if (err < 0) { + bphy_err(drvr, "failed to set pfnmem\n"); + goto exit; + } + /* get max mscan which the firmware supports */ + err = brcmf_fil_iovar_int_get(ifp, "pfnmem", &pfnmem); + if (err < 0) { + bphy_err(drvr, "failed to get pfnmem\n"); + goto exit; + } + mscan = min_t(u32, mscan, pfnmem); + pfn_param.mscan = mscan; + pfn_param.bestn = bestn; + flags |= BIT(BRCMF_PNO_ENABLE_BD_SCAN_BIT); + brcmf_dbg(INFO, "mscan=%d, bestn=%d\n", mscan, bestn); + } + + pfn_param.flags = cpu_to_le16(flags); + err = brcmf_fil_iovar_data_set(ifp, "pfn_set", &pfn_param, + sizeof(pfn_param)); + if (err) + bphy_err(drvr, "pfn_set failed, err=%d\n", err); + +exit: + return err; +} + +static int brcmf_pno_config_v2(struct brcmf_if *ifp, u32 scan_freq, u32 mscan, + u32 bestn) { struct brcmf_pub *drvr = ifp->drvr; struct brcmf_pno_param_le pfn_param; @@ -109,7 +165,7 @@ static int brcmf_pno_config(struct brcmf_if *ifp, u32 scan_freq, s32 err; memset(&pfn_param, 0, sizeof(pfn_param)); - pfn_param.version = cpu_to_le32(BRCMF_PNO_VERSION); + pfn_param.version = cpu_to_le32(BRCMF_PNO_VERSION_2); /* set extra pno params */ flags = BIT(BRCMF_PNO_IMMEDIATE_SCAN_BIT) | @@ -152,6 +208,12 @@ static int brcmf_pno_config(struct brcmf_if *ifp, u32 scan_freq, return err; } +static int brcmf_pno_config(struct brcmf_if *ifp, u32 scan_freq, u32 mscan, + u32 bestn) +{ + return ifp->drvr->pno_handler.pno_config(ifp, scan_freq, mscan, bestn); +} + static int brcmf_pno_set_random(struct brcmf_if *ifp, struct brcmf_pno_info *pi) { struct brcmf_pub *drvr = ifp->drvr; @@ -275,7 +337,7 @@ static int brcmf_pno_get_bucket_channels(struct cfg80211_sched_scan_request *r, { u32 n_chan = le32_to_cpu(pno_cfg->channel_num); u16 chan; - int i, err = 0; + int i, err; for (i = 0; i < r->n_channels; i++) { if (n_chan >= BRCMF_NUMCHANNELS) { @@ -562,9 +624,82 @@ u64 brcmf_pno_find_reqid_by_bucket(struct brcmf_pno_info *pi, u32 bucket) return reqid; } -u32 brcmf_pno_get_bucket_map(struct brcmf_pno_info *pi, - struct brcmf_pno_net_info_le *ni) + +static struct brcmf_pno_net_info_le * +brcmf_get_netinfo_array(void *pfn_v1_data) +{ + struct brcmf_pno_scanresults_le *pfn_v1 = + (struct brcmf_pno_scanresults_le *)pfn_v1_data; + struct brcmf_pno_scanresults_v2_le *pfn_v2; + struct brcmf_pno_net_info_le *netinfo = NULL; + + switch (pfn_v1->version) { + default: + WARN_ON(1); + fallthrough; + case cpu_to_le32(1): + netinfo = (struct brcmf_pno_net_info_le *)(pfn_v1 + 1); + break; + case cpu_to_le32(2): + pfn_v2 = (struct brcmf_pno_scanresults_v2_le *)pfn_v1; + netinfo = (struct brcmf_pno_net_info_le *)(pfn_v2 + 1); + break; + case cpu_to_le32(3): + brcmf_err("Need to use brcmf_get_netinfo_v3_array\n"); + break; + } + + return netinfo; +} + +static struct brcmf_pno_net_info_v3_le * +brcmf_get_netinfo_v3_array(void*pfn_v3_data) +{ + struct brcmf_pno_scanresults_v3_le *pfn_v3 = + (struct brcmf_pno_scanresults_v3_le *)pfn_v3_data; + return (struct brcmf_pno_net_info_v3_le *) (pfn_v3 + 1); +} + +static u32 brcmf_pno_get_bucket_map(void *data, int idx, struct brcmf_pno_info *pi) +{ + + struct brcmf_pno_net_info_le *netinfo_start = + brcmf_get_netinfo_array(data); + struct brcmf_pno_net_info_le *ni = &netinfo_start[idx]; + struct cfg80211_sched_scan_request *req; + struct cfg80211_match_set *ms; + u32 bucket_map = 0; + int i, j; + + mutex_lock(&pi->req_lock); + for (i = 0; i < pi->n_reqs; i++) { + req = pi->reqs[i]; + + if (!req->n_match_sets) + continue; + for (j = 0; j < req->n_match_sets; j++) { + ms = &req->match_sets[j]; + if (ms->ssid.ssid_len == ni->SSID_len && + !memcmp(ms->ssid.ssid, ni->SSID, ni->SSID_len)) { + bucket_map |= BIT(i); + break; + } + if (is_valid_ether_addr(ms->bssid) && + !memcmp(ms->bssid, ni->bssid, ETH_ALEN)) { + bucket_map |= BIT(i); + break; + } + } + } + mutex_unlock(&pi->req_lock); + return bucket_map; +} + +static u32 brcmf_pno_get_bucket_map_v3(void *data, int idx, struct brcmf_pno_info *pi) { + struct brcmf_pno_net_info_v3_le *netinfo_v3_start = + brcmf_get_netinfo_v3_array(data); + struct brcmf_pno_net_info_v3_le *ni = &netinfo_v3_start[idx]; struct cfg80211_sched_scan_request *req; struct cfg80211_match_set *ms; u32 bucket_map = 0; @@ -593,3 +728,148 @@ u32 brcmf_pno_get_bucket_map(struct brcmf_pno_info *pi, mutex_unlock(&pi->req_lock); return bucket_map; } + +static u32 brcmf_pno_min_data_len(void) +{ + return sizeof(struct brcmf_pno_scanresults_le) + + sizeof(struct brcmf_pno_net_info_le); +} +static u32 brcmf_pno_min_data_len_v3(void) +{ + return sizeof(struct brcmf_pno_scanresults_v3_le) + + sizeof(struct brcmf_pno_net_info_v3_le); +} + +static int brcmf_pno_validate_pfn_results_v3(void *data, u32 eventlen) +{ + struct brcmf_pno_scanresults_v3_le *scanresult = + (struct brcmf_pno_scanresults_v3_le *)data; + struct brcmf_pno_net_info_v3_le *netinfo_v3_start = + brcmf_get_netinfo_v3_array(scanresult); + u32 datalen; + + if (!netinfo_v3_start) { + brcmf_err("did not get netinfo_v3 data\n"); + return -EINVAL; + } + datalen = eventlen - ((void *)netinfo_v3_start - (void *)data); + if (datalen < le32_to_cpu(scanresult->count) * sizeof(struct brcmf_pno_net_info_v3_le)) { + brcmf_err("insufficient event data\n"); + return -EINVAL; + } + return 0; +} + +static int brcmf_pno_validate_pfn_results(void *data, u32 eventlen) +{ + struct brcmf_pno_scanresults_le *scanresult = + (struct brcmf_pno_scanresults_le *)data; + struct brcmf_pno_net_info_le *netinfo_start = + brcmf_get_netinfo_array(scanresult); + u32 datalen; + + if (!netinfo_start) { + brcmf_err("did not get netinfo data\n"); + return -EINVAL; + } + datalen = eventlen - ((void *)netinfo_start - (void *)data); + if (datalen < le32_to_cpu(scanresult->count) * sizeof(struct brcmf_pno_net_info_le)) { + brcmf_err("insufficient event data\n"); + return -EINVAL; + } + return 0; +} + +static int brcmf_pno_get_result_info(void *data, int result_idx, + u8 (*ssid)[IEEE80211_MAX_SSID_LEN], + u8 *ssid_len, u8 *channel, + enum nl80211_band *band) +{ + struct brcmf_pno_scanresults_le *scanresult = + (struct brcmf_pno_scanresults_le *)data; + struct brcmf_pno_net_info_le *netinfo_start = + brcmf_get_netinfo_array(scanresult); + struct brcmf_pno_net_info_le *netinfo = &netinfo_start[result_idx]; + + *channel = netinfo->channel; + *band = netinfo->channel <= CH_MAX_2G_CHANNEL ? NL80211_BAND_2GHZ : + NL80211_BAND_5GHZ; + *ssid_len = netinfo->SSID_len; + if (netinfo->SSID_len > IEEE80211_MAX_SSID_LEN) + *ssid_len = IEEE80211_MAX_SSID_LEN; + memcpy(ssid, netinfo->SSID, *ssid_len); + + return 0; +} + +static int brcmf_pno_get_result_info_v3(void *data, int result_idx, + u8 (*ssid)[IEEE80211_MAX_SSID_LEN], + u8 *ssid_len, u8 *channel, + enum nl80211_band *band) +{ + struct brcmf_pno_scanresults_v3_le *scanresult = + (struct brcmf_pno_scanresults_v3_le *)data; + struct brcmf_pno_net_info_v3_le *netinfo_v3_start = + brcmf_get_netinfo_v3_array(scanresult); + struct brcmf_pno_net_info_v3_le *netinfo_v3 = + &netinfo_v3_start[result_idx]; + + *channel = CHSPEC_CHANNEL(netinfo_v3->chanspec); + *band = fwil_band_to_nl80211(CHSPEC_BAND(netinfo_v3->chanspec)); + *ssid_len = netinfo_v3->SSID_len; + if (netinfo_v3->SSID_len > IEEE80211_MAX_SSID_LEN) + *ssid_len = IEEE80211_MAX_SSID_LEN; + memcpy(ssid, netinfo_v3->SSID, *ssid_len); + + return 0; +} + +/* The count and status fields are in the same place for v1/2/3 */ +static u32 brcmf_pno_get_result_count_v123(void *data) +{ + struct brcmf_pno_scanresults_le *results = + (struct brcmf_pno_scanresults_le *)data; + return le32_to_cpu(results->count); +} +static u32 brcmf_pno_get_result_status_v123(void *data) +{ + struct brcmf_pno_scanresults_le *results = + (struct brcmf_pno_scanresults_le *)data; + return le32_to_cpu(results->status); +} + +int brcmf_pno_setup_for_version(struct brcmf_pub *drvr, u8 vers) +{ + /* The first supported version by this driver was version 2. + * The v2 functions handle version one structures if handed to them, + * but the config was always set to interface version 2. */ + switch (vers) { + case BRCMF_PNO_VERSION_2: { + drvr->pno_handler.version = BRCMF_PNO_VERSION_2; + drvr->pno_handler.pno_config = brcmf_pno_config_v2; + drvr->pno_handler.get_result_count = brcmf_pno_get_result_count_v123; + drvr->pno_handler.get_result_status = brcmf_pno_get_result_status_v123; + drvr->pno_handler.get_bucket_map = brcmf_pno_get_bucket_map; + drvr->pno_handler.get_min_data_len = brcmf_pno_min_data_len; + drvr->pno_handler.get_result_info = brcmf_pno_get_result_info; + drvr->pno_handler.validate_pfn_results = + brcmf_pno_validate_pfn_results; + break; + } + case BRCMF_PNO_VERSION_3: { + drvr->pno_handler.version = BRCMF_PNO_VERSION_3; + drvr->pno_handler.pno_config = brcmf_pno_config_v3; + drvr->pno_handler.get_result_count = brcmf_pno_get_result_count_v123; + drvr->pno_handler.get_result_status = brcmf_pno_get_result_status_v123; + drvr->pno_handler.get_bucket_map = brcmf_pno_get_bucket_map_v3; + drvr->pno_handler.get_min_data_len = brcmf_pno_min_data_len_v3; + drvr->pno_handler.get_result_info = brcmf_pno_get_result_info_v3; + drvr->pno_handler.validate_pfn_results = + brcmf_pno_validate_pfn_results_v3; + break; + } + default: + return -EINVAL; + } + return 0; +} diff --git a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/pno.h b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/pno.h index 25d406019ac340..0163c762f5385a 100644 --- a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/pno.h +++ b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/pno.h @@ -61,12 +61,12 @@ void brcmf_pno_detach(struct brcmf_cfg80211_info *cfg); u64 brcmf_pno_find_reqid_by_bucket(struct brcmf_pno_info *pi, u32 bucket); /** - * brcmf_pno_get_bucket_map - determine bucket map for given netinfo. + * brcmf_pno_setup_for_version - setup our PNO handler for whatever version structures + * are supported by the chip * - * @pi: pno instance used. - * @netinfo: netinfo to compare with bucket configuration. + * @cfg: CFG to fill in. + * @vers: Version to use */ -u32 brcmf_pno_get_bucket_map(struct brcmf_pno_info *pi, - struct brcmf_pno_net_info_le *netinfo); +int brcmf_pno_setup_for_version(struct brcmf_pub *drvr, u8 vers); #endif /* _BRCMF_PNO_H */ From 1d84867c1739ac4affa819a519b2c92719323651 Mon Sep 17 00:00:00 2001 From: Daniel Berlin Date: Sun, 22 Oct 2023 12:40:57 -0400 Subject: [PATCH 0236/3902] [brcmfmac] Structurize scan parameter handling Signed-off-by: Daniel Berlin --- .../broadcom/brcm80211/brcmfmac/Makefile | 2 + .../broadcom/brcm80211/brcmfmac/cfg80211.c | 235 ++------- .../broadcom/brcm80211/brcmfmac/core.h | 9 + .../broadcom/brcm80211/brcmfmac/feature.c | 18 +- .../broadcom/brcm80211/brcmfmac/feature.h | 4 - .../broadcom/brcm80211/brcmfmac/fwil_types.h | 190 +++++--- .../broadcom/brcm80211/brcmfmac/scan_param.c | 446 ++++++++++++++++++ .../broadcom/brcm80211/brcmfmac/scan_param.h | 22 + 8 files changed, 643 insertions(+), 283 deletions(-) create mode 100644 drivers/net/wireless/broadcom/brcm80211/brcmfmac/scan_param.c create mode 100644 drivers/net/wireless/broadcom/brcm80211/brcmfmac/scan_param.h diff --git a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/Makefile b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/Makefile index e5ca0f51182271..f3f72f9524578c 100644 --- a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/Makefile +++ b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/Makefile @@ -25,7 +25,9 @@ brcmfmac-objs += \ btcoex.o \ vendor.o \ pno.o \ + scan_param.o \ xtlv.o + brcmfmac-$(CONFIG_BRCMFMAC_PROTO_BCDC) += \ bcdc.o \ fwsignal.o diff --git a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/cfg80211.c b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/cfg80211.c index 382f4cc0d54263..eb901ec7bbe116 100644 --- a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/cfg80211.c +++ b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/cfg80211.c @@ -1117,170 +1117,11 @@ bool brcmf_is_apmode_operating(struct wiphy *wiphy) return ret; } -static void brcmf_scan_params_v2_to_v1(struct brcmf_scan_params_v2_le *params_v2_le, - struct brcmf_scan_params_le *params_le) -{ - size_t params_size; - u32 ch; - int n_channels, n_ssids; - - memcpy(¶ms_le->ssid_le, ¶ms_v2_le->ssid_le, - sizeof(params_le->ssid_le)); - memcpy(¶ms_le->bssid, ¶ms_v2_le->bssid, - sizeof(params_le->bssid)); - - params_le->bss_type = params_v2_le->bss_type; - params_le->scan_type = le32_to_cpu(params_v2_le->scan_type); - params_le->nprobes = params_v2_le->nprobes; - params_le->active_time = params_v2_le->active_time; - params_le->passive_time = params_v2_le->passive_time; - params_le->home_time = params_v2_le->home_time; - params_le->channel_num = params_v2_le->channel_num; - - ch = le32_to_cpu(params_v2_le->channel_num); - n_channels = ch & BRCMF_SCAN_PARAMS_COUNT_MASK; - n_ssids = ch >> BRCMF_SCAN_PARAMS_NSSID_SHIFT; - - params_size = sizeof(u16) * n_channels; - if (n_ssids > 0) { - params_size = roundup(params_size, sizeof(u32)); - params_size += sizeof(struct brcmf_ssid_le) * n_ssids; - } - - memcpy(¶ms_le->channel_list[0], - ¶ms_v2_le->channel_list[0], params_size); -} - -static u32 brcmf_nl80211_scan_flags_to_scan_flags(u32 nl80211_flags) -{ - u32 scan_flags = 0; - if (nl80211_flags & NL80211_SCAN_FLAG_LOW_SPAN) { - scan_flags |= BRCMF_SCANFLAGS_LOW_SPAN; - brcmf_dbg(SCAN, "requested low span scan\n"); - } - if (nl80211_flags & NL80211_SCAN_FLAG_HIGH_ACCURACY) { - scan_flags |= BRCMF_SCANFLAGS_HIGH_ACCURACY; - brcmf_dbg(SCAN, "requested high accuracy scan\n"); - } - if (nl80211_flags & NL80211_SCAN_FLAG_LOW_POWER) { - scan_flags |= BRCMF_SCANFLAGS_LOW_POWER; - brcmf_dbg(SCAN, "requested low power scan\n"); - } - if (nl80211_flags & NL80211_SCAN_FLAG_LOW_PRIORITY) { - scan_flags |= BRCMF_SCANFLAGS_LOW_PRIO; - brcmf_dbg(SCAN, "requested low priority scan\n"); - } - return scan_flags; -} - -static void brcmf_escan_prep(struct brcmf_cfg80211_info *cfg, - struct brcmf_if *ifp, - struct brcmf_scan_params_v2_le *params_le, - struct cfg80211_scan_request *request) -{ - u32 n_ssids; - u32 n_channels; - s32 i; - s32 offset; - u16 chanspec; - char *ptr; - int length; - struct brcmf_ssid_le ssid_le; - u32 scan_type = BRCMF_SCANTYPE_ACTIVE; - - eth_broadcast_addr(params_le->bssid); - - length = BRCMF_SCAN_PARAMS_V2_FIXED_SIZE; - - if (brcmf_feat_is_enabled(ifp, BRCMF_FEAT_SCAN_V3)) - params_le->version = cpu_to_le16(BRCMF_SCAN_PARAMS_VERSION_V3); - else - params_le->version = cpu_to_le16(BRCMF_SCAN_PARAMS_VERSION_V2); - - params_le->bss_type = DOT11_BSSTYPE_ANY; - params_le->ssid_type = 0; - params_le->channel_num = 0; - params_le->nprobes = cpu_to_le32(-1); - params_le->active_time = cpu_to_le32(-1); - params_le->passive_time = cpu_to_le32(-1); - params_le->home_time = cpu_to_le32(-1); - memset(¶ms_le->ssid_le, 0, sizeof(params_le->ssid_le)); - - /* Scan abort */ - if (!request) { - length += sizeof(u16); - params_le->channel_num = cpu_to_le32(1); - params_le->channel_list[0] = cpu_to_le16(-1); - params_le->length = cpu_to_le16(length); - return; - } - - n_ssids = request->n_ssids; - n_channels = request->n_channels; - - /* Copy channel array if applicable */ - brcmf_dbg(SCAN, "### List of channelspecs to scan ### %d\n", - n_channels); - if (n_channels > 0) { - length += roundup(sizeof(u16) * n_channels, sizeof(u32)); - for (i = 0; i < n_channels; i++) { - chanspec = channel_to_chanspec(&cfg->d11inf, - request->channels[i]); - brcmf_dbg(SCAN, "Chan : %d, Channel spec: %x\n", - request->channels[i]->hw_value, chanspec); - params_le->channel_list[i] = cpu_to_le16(chanspec); - } - } else { - brcmf_dbg(SCAN, "Scanning all channels\n"); - } - - /* Copy ssid array if applicable */ - brcmf_dbg(SCAN, "### List of SSIDs to scan ### %d\n", n_ssids); - if (n_ssids > 0) { - offset = offsetof(struct brcmf_scan_params_v2_le, channel_list) + - n_channels * sizeof(u16); - offset = roundup(offset, sizeof(u32)); - length += sizeof(ssid_le) * n_ssids; - ptr = (char *)params_le + offset; - for (i = 0; i < n_ssids; i++) { - memset(&ssid_le, 0, sizeof(ssid_le)); - ssid_le.SSID_len = - cpu_to_le32(request->ssids[i].ssid_len); - memcpy(ssid_le.SSID, request->ssids[i].ssid, - request->ssids[i].ssid_len); - if (!ssid_le.SSID_len) - brcmf_dbg(SCAN, "%d: Broadcast scan\n", i); - else - brcmf_dbg(SCAN, "%d: scan for %.32s size=%d\n", - i, ssid_le.SSID, ssid_le.SSID_len); - memcpy(ptr, &ssid_le, sizeof(ssid_le)); - ptr += sizeof(ssid_le); - } - } else { - brcmf_dbg(SCAN, "Performing passive scan\n"); - scan_type = BRCMF_SCANTYPE_PASSIVE; - } - scan_type |= brcmf_nl80211_scan_flags_to_scan_flags(request->flags); - params_le->scan_type = cpu_to_le32(scan_type); - params_le->length = cpu_to_le16(length); - - /* Include RNR results if requested */ - if (request->flags & NL80211_SCAN_FLAG_COLOCATED_6GHZ) { - params_le->ssid_type |= BRCMF_SCANSSID_INC_RNR; - } - - /* Adding mask to channel numbers */ - params_le->channel_num = - cpu_to_le32((n_ssids << BRCMF_SCAN_PARAMS_NSSID_SHIFT) | - (n_channels & BRCMF_SCAN_PARAMS_COUNT_MASK)); -} - s32 brcmf_notify_escan_complete(struct brcmf_cfg80211_info *cfg, struct brcmf_if *ifp, bool aborted, bool fw_abort) { struct brcmf_pub *drvr = cfg->pub; - struct brcmf_scan_params_v2_le params_v2_le; struct cfg80211_scan_request *scan_request; u64 reqid; u32 bucket; @@ -1296,25 +1137,16 @@ s32 brcmf_notify_escan_complete(struct brcmf_cfg80211_info *cfg, timer_delete_sync(&cfg->escan_timeout); if (fw_abort) { + u32 len; + void *data = drvr->scan_param_handler.get_prepped_struct(cfg, &len, NULL); + if (!data){ + bphy_err(drvr, "Scan abort failed to prepare abort struct\n"); + return 0; + } /* Do a scan abort to stop the driver's scan engine */ brcmf_dbg(SCAN, "ABORT scan in firmware\n"); - - brcmf_escan_prep(cfg, ifp, ¶ms_v2_le, NULL); - - /* E-Scan (or anyother type) can be aborted by SCAN */ - if (brcmf_feat_is_enabled(ifp, BRCMF_FEAT_SCAN_V2)) { - err = brcmf_fil_cmd_data_set(ifp, BRCMF_C_SCAN, - ¶ms_v2_le, - sizeof(params_v2_le)); - } else { - struct brcmf_scan_params_le params_le; - - brcmf_scan_params_v2_to_v1(¶ms_v2_le, ¶ms_le); - err = brcmf_fil_cmd_data_set(ifp, BRCMF_C_SCAN, - ¶ms_le, - sizeof(params_le)); - } - + err = brcmf_fil_cmd_data_set(ifp, BRCMF_C_SCAN, data, len); + kfree(data); if (err) bphy_err(drvr, "Scan abort failed\n"); } @@ -1538,19 +1370,24 @@ brcmf_run_escan(struct brcmf_cfg80211_info *cfg, struct brcmf_if *ifp, struct cfg80211_scan_request *request) { struct brcmf_pub *drvr = cfg->pub; - s32 params_size = BRCMF_SCAN_PARAMS_V2_FIXED_SIZE + - offsetof(struct brcmf_escan_params_le, params_v2_le); + u32 struct_size = 0; + void *prepped_params = NULL; + u32 params_size = 0; struct brcmf_escan_params_le *params; s32 err = 0; brcmf_dbg(SCAN, "E-SCAN START\n"); - if (request != NULL) { - /* Allocate space for populating ssids in struct */ - params_size += sizeof(u32) * ((request->n_channels + 1) / 2); - - /* Allocate space for populating ssids in struct */ - params_size += sizeof(struct brcmf_ssid_le) * request->n_ssids; + prepped_params = drvr->scan_param_handler.get_prepped_struct(cfg, &struct_size, request); + if (!prepped_params) { + err = -EINVAL; + goto exit; + } + params_size = struct_size + + offsetof(struct brcmf_escan_params_le, params_v4_le); + if (!params_size) { + err = -EINVAL; + goto exit; } params = kzalloc(params_size, GFP_KERNEL); @@ -1558,29 +1395,14 @@ brcmf_run_escan(struct brcmf_cfg80211_info *cfg, struct brcmf_if *ifp, err = -ENOMEM; goto exit; } - BUG_ON(params_size + sizeof("escan") >= BRCMF_DCMD_MEDLEN); - brcmf_escan_prep(cfg, ifp, ¶ms->params_v2_le, request); - - if (brcmf_feat_is_enabled(ifp, BRCMF_FEAT_SCAN_V3)) { - params->version = cpu_to_le32(BRCMF_ESCAN_REQ_VERSION_V3); - } else if (brcmf_feat_is_enabled(ifp, BRCMF_FEAT_SCAN_V2)) { - params->version = cpu_to_le32(BRCMF_ESCAN_REQ_VERSION_V2); - } else { - struct brcmf_escan_params_le *params_v1; - - params_size -= BRCMF_SCAN_PARAMS_V2_FIXED_SIZE; - params_size += BRCMF_SCAN_PARAMS_FIXED_SIZE; - params_v1 = kzalloc(params_size, GFP_KERNEL); - if (!params_v1) { - err = -ENOMEM; - goto exit_params; - } - params_v1->version = cpu_to_le32(BRCMF_ESCAN_REQ_VERSION); - brcmf_scan_params_v2_to_v1(¶ms->params_v2_le, ¶ms_v1->params_le); - kfree(params); - params = params_v1; - } + /* Copy into the largest part */ + unsafe_memcpy( + ¶ms->params_v4_le, prepped_params, struct_size, + /* A composite flex-array that is at least as large as the memcpy due to the allocation above */); + /* We can now free the original prepped parameters */ + kfree(prepped_params); + params->version = cpu_to_le32(drvr->scan_param_handler.version); params->action = cpu_to_le16(WL_ESCAN_ACTION_START); params->sync_id = cpu_to_le16(0x1234); @@ -1592,7 +1414,6 @@ brcmf_run_escan(struct brcmf_cfg80211_info *cfg, struct brcmf_if *ifp, bphy_err(drvr, "error (%d)\n", err); } -exit_params: kfree(params); exit: return err; diff --git a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/core.h b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/core.h index a75ce5e9297eb5..c7562bdb61e86c 100644 --- a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/core.h +++ b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/core.h @@ -98,6 +98,7 @@ struct brcmf_rev_info { }; struct brcmf_pno_info; +enum nl80211_band; /** * struct pno_struct_handler */ @@ -114,6 +115,13 @@ struct pno_struct_handler { u8 (*ssid)[IEEE80211_MAX_SSID_LEN], u8 *ssid_len, u8 *channel, enum nl80211_band *band); }; +struct cfg80211_scan_request; +struct scan_param_struct_handler { + u8 version; + void *(*get_prepped_struct)(struct brcmf_cfg80211_info *cfg, + u32 *struct_size, + struct cfg80211_scan_request *request); +}; /* Common structure for module and instance linkage */ struct brcmf_pub { @@ -165,6 +173,7 @@ struct brcmf_pub { void *vdata; u16 cnt_ver; struct pno_struct_handler pno_handler; + struct scan_param_struct_handler scan_param_handler; }; /* forward declarations */ diff --git a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/feature.c b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/feature.c index 341f988afca30d..a6725b66ebf07a 100644 --- a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/feature.c +++ b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/feature.c @@ -17,6 +17,7 @@ #include "feature.h" #include "common.h" #include "pno.h" +#include "scan_param.h" #define BRCMF_FW_UNSUPPORTED 23 @@ -291,7 +292,7 @@ static int brcmf_feat_fwcap_debugfs_read(struct seq_file *seq, void *data) void brcmf_feat_attach(struct brcmf_pub *drvr) { struct brcmf_if *ifp = brcmf_get_ifp(drvr, 0); - struct brcmf_wl_scan_version_le scan_ver; + struct brcmf_scan_version_le scan_ver; struct brcmf_pno_param_v3_le pno_params; struct brcmf_pno_macaddr_le pfn_mac; struct brcmf_gscan_config gscan_cfg; @@ -347,16 +348,11 @@ void brcmf_feat_attach(struct brcmf_pub *drvr) err = brcmf_fil_iovar_data_get(ifp, "scan_ver", &scan_ver, sizeof(scan_ver)); if (!err) { - int ver = le16_to_cpu(scan_ver.scan_ver_major); - - if (ver == 2) { - ifp->drvr->feat_flags |= BIT(BRCMF_FEAT_SCAN_V2); - } else if (ver == 3) { - /* We consider SCAN_V3 a subtype of SCAN_V2 since the - * structure is essentially the same. - */ - ifp->drvr->feat_flags |= BIT(BRCMF_FEAT_SCAN_V2) | BIT(BRCMF_FEAT_SCAN_V3); - } + u16 ver = le16_to_cpu(scan_ver.scan_ver_major); + brcmf_scan_param_setup_for_version(drvr, ver); + } else { + /* Default tp version 1. */ + brcmf_scan_param_setup_for_version(drvr, 1); } /* See what version of PFN scan is supported*/ diff --git a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/feature.h b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/feature.h index bf33ea606c0c7e..4088141508a035 100644 --- a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/feature.h +++ b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/feature.h @@ -30,9 +30,7 @@ * SAE: simultaneous authentication of equals * FWAUTH: Firmware authenticator * DUMP_OBSS: Firmware has capable to dump obss info to support ACS - * SCAN_V2: Version 2 scan params * SAE_EXT: SAE authentication handled by user-space supplicant - * SCAN_v3: Version 3 scan params * PMKID_V2: Version 2 PMKID * PMKID_V3: Version 3 PMKID * EVENT_MSGS_EXT: Event messages extension @@ -62,8 +60,6 @@ BRCMF_FEAT_DEF(SAE) \ BRCMF_FEAT_DEF(FWAUTH) \ BRCMF_FEAT_DEF(DUMP_OBSS) \ - BRCMF_FEAT_DEF(SCAN_V2) \ - BRCMF_FEAT_DEF(SCAN_V3) \ BRCMF_FEAT_DEF(PMKID_V2) \ BRCMF_FEAT_DEF(PMKID_V3) \ BRCMF_FEAT_DEF(SAE_EXT) \ diff --git a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/fwil_types.h b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/fwil_types.h index 151cef2c2e3196..e4b3b13a8ff92c 100644 --- a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/fwil_types.h +++ b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/fwil_types.h @@ -47,13 +47,10 @@ #define BRCMF_STA_DWDS_CAP 0x01000000 /* DWDS CAP */ #define BRCMF_STA_DWDS 0x02000000 /* DWDS active */ -/* size of brcmf_scan_params not including variable length array */ -#define BRCMF_SCAN_PARAMS_FIXED_SIZE 64 -#define BRCMF_SCAN_PARAMS_V2_FIXED_SIZE 72 - /* version of brcmf_scan_params structure */ #define BRCMF_SCAN_PARAMS_VERSION_V2 2 #define BRCMF_SCAN_PARAMS_VERSION_V3 3 +#define BRCMF_SCAN_PARAMS_VERSION_V4 4 /* masks for channel and ssid count */ #define BRCMF_SCAN_PARAMS_COUNT_MASK 0x0000ffff @@ -406,23 +403,23 @@ struct brcmf_ssid8_le { }; struct brcmf_scan_params_le { - struct brcmf_ssid_le ssid_le; /* default: {0, ""} */ - u8 bssid[ETH_ALEN]; /* default: bcast */ - s8 bss_type; /* default: any, + struct brcmf_ssid_le ssid_le; /* default: {0, ""} */ + u8 bssid[ETH_ALEN]; /* default: bcast */ + s8 bss_type; /* default: any, * DOT11_BSSTYPE_ANY/INFRASTRUCTURE/INDEPENDENT */ - u8 scan_type; /* flags, 0 use default */ - __le32 nprobes; /* -1 use default, number of probes per channel */ - __le32 active_time; /* -1 use default, dwell time per channel for + u8 scan_type; /* flags, 0 use default */ + __le32 nprobes; /* -1 use default, number of probes per channel */ + __le32 active_time; /* -1 use default, dwell time per channel for * active scanning */ - __le32 passive_time; /* -1 use default, dwell time per channel + __le32 passive_time; /* -1 use default, dwell time per channel * for passive scanning */ __le32 home_time; /* -1 use default, dwell time for the * home channel between channel scans */ - __le32 channel_num; /* count of channels and ssids that follow + __le32 channel_num; /* count of channels and ssids that follow * * low half is count of channels in * channel_list, 0 means default (use all @@ -438,56 +435,125 @@ struct brcmf_scan_params_le { * fixed parameter portion is assumed, otherwise * ssid in the fixed portion is ignored */ - union { - __le16 padding; /* Reserve space for at least 1 entry for abort - * which uses an on stack brcmf_scan_params_le - */ - DECLARE_FLEX_ARRAY(__le16, channel_list); /* chanspecs */ - }; + __le16 channel_list[]; /* chanspecs */ }; struct brcmf_scan_params_v2_le { - __le16 version; /* structure version */ - __le16 length; /* structure length */ - struct brcmf_ssid_le ssid_le; /* default: {0, ""} */ - u8 bssid[ETH_ALEN]; /* default: bcast */ - s8 bss_type; /* default: any, - * DOT11_BSSTYPE_ANY/INFRASTRUCTURE/INDEPENDENT - */ - u8 ssid_type; /* v3 only */ - __le32 scan_type; /* flags, 0 use default */ - __le32 nprobes; /* -1 use default, number of probes per channel */ - __le32 active_time; /* -1 use default, dwell time per channel for - * active scanning - */ - __le32 passive_time; /* -1 use default, dwell time per channel - * for passive scanning - */ - __le32 home_time; /* -1 use default, dwell time for the - * home channel between channel scans - */ - __le32 channel_num; /* count of channels and ssids that follow - * - * low half is count of channels in - * channel_list, 0 means default (use all - * available channels) - * - * high half is entries in struct brcmf_ssid - * array that follows channel_list, aligned for - * s32 (4 bytes) meaning an odd channel count - * implies a 2-byte pad between end of - * channel_list and first ssid - * - * if ssid count is zero, single ssid in the - * fixed parameter portion is assumed, otherwise - * ssid in the fixed portion is ignored - */ - union { - __le16 padding; /* Reserve space for at least 1 entry for abort - * which uses an on stack brcmf_scan_params_v2_le - */ - DECLARE_FLEX_ARRAY(__le16, channel_list); /* chanspecs */ - }; + __le16 version; /* structure version */ + __le16 length; /* structure length */ + struct brcmf_ssid_le ssid_le; /* default: {0, ""} */ + u8 bssid[ETH_ALEN]; /* default: bcast */ + s8 bss_type; /* default: any, + * DOT11_BSSTYPE_ANY/INFRASTRUCTURE/INDEPENDENT + */ + u8 PAD; + __le32 scan_type; /* flags, 0 use default */ + __le32 nprobes; /* -1 use default, number of probes per channel */ + __le32 active_time; /* -1 use default, dwell time per channel for + * active scanning + */ + __le32 passive_time; /* -1 use default, dwell time per channel + * for passive scanning + */ + __le32 home_time; /* -1 use default, dwell time for the + * home channel between channel scans + */ + __le32 channel_num; /* count of channels and ssids that follow + * + * low half is count of channels in + * channel_list, 0 means default (use all + * available channels) + * + * high half is entries in struct brcmf_ssid + * array that follows channel_list, aligned for + * s32 (4 bytes) meaning an odd channel count + * implies a 2-byte pad between end of + * channel_list and first ssid + * + * if ssid count is zero, single ssid in the + * fixed parameter portion is assumed, otherwise + * ssid in the fixed portion is ignored + */ + __le16 channel_list[]; /* chanspecs */ +}; + +struct brcmf_scan_params_v3_le { + __le16 version; /* structure version */ + __le16 length; /* structure length */ + struct brcmf_ssid_le ssid_le; /* default: {0, ""} */ + u8 bssid[ETH_ALEN]; /* default: bcast */ + s8 bss_type; /* default: any, + * DOT11_BSSTYPE_ANY/INFRASTRUCTURE/INDEPENDENT + */ + u8 ssid_type; /* short vs regular SSID */ + __le32 scan_type; /* flags, 0 use default */ + __le32 nprobes; /* -1 use default, number of probes per channel */ + __le32 active_time; /* -1 use default, dwell time per channel for + * active scanning + */ + __le32 passive_time; /* -1 use default, dwell time per channel + * for passive scanning + */ + __le32 home_time; /* -1 use default, dwell time for the + * home channel between channel scans + */ + __le32 channel_num; /* count of channels and ssids that follow + * + * low half is count of channels in + * channel_list, 0 means default (use all + * available channels) + * + * high half is entries in struct brcmf_ssid + * array that follows channel_list, aligned for + * s32 (4 bytes) meaning an odd channel count + * implies a 2-byte pad between end of + * channel_list and first ssid + * + * if ssid count is zero, single ssid in the + * fixed parameter portion is assumed, otherwise + * ssid in the fixed portion is ignored + */ + __le16 channel_list[]; /* chanspecs */ +}; + +struct brcmf_scan_params_v4_le { + __le16 version; /* structure version */ + __le16 length; /* structure length */ + struct brcmf_ssid_le ssid_le; /* default: {0, ""} */ + u8 bssid[ETH_ALEN]; /* default: bcast */ + s8 bss_type; /* default: any, + * DOT11_BSSTYPE_ANY/INFRASTRUCTURE/INDEPENDENT + */ + u8 ssid_type; /* short vs regular SSID */ + __le32 scan_type; /* flags, 0 use default */ + __le32 scan_type_ext; /* ext flags, 0 use default */ + __le32 nprobes; /* -1 use default, number of probes per channel */ + __le32 active_time; /* -1 use default, dwell time per channel for + * active scanning + */ + __le32 passive_time; /* -1 use default, dwell time per channel + * for passive scanning + */ + __le32 home_time; /* -1 use default, dwell time for the + * home channel between channel scans + */ + __le32 channel_num; /* count of channels and ssids that follow + * + * low half is count of channels in + * channel_list, 0 means default (use all + * available channels) + * + * high half is entries in struct brcmf_ssid + * array that follows channel_list, aligned for + * s32 (4 bytes) meaning an odd channel count + * implies a 2-byte pad between end of + * channel_list and first ssid + * + * if ssid count is zero, single ssid in the + * fixed parameter portion is assumed, otherwise + * ssid in the fixed portion is ignored + */ + __le16 channel_list[]; /* chanspecs */ }; struct brcmf_scan_results { @@ -504,6 +570,8 @@ struct brcmf_escan_params_le { union { struct brcmf_scan_params_le params_le; struct brcmf_scan_params_v2_le params_v2_le; + struct brcmf_scan_params_v3_le params_v3_le; + struct brcmf_scan_params_v4_le params_v4_le; }; }; @@ -880,13 +948,13 @@ struct brcmf_wlc_version_le { /** * struct brcmf_wl_scan_version_le - scan interface version */ -struct brcmf_wl_scan_version_le { +struct brcmf_scan_version_le { __le16 version; __le16 length; __le16 scan_ver_major; }; -#define BRCMF_WL_SCAN_VERSION_VERSION 1 +#define BRCMF_SCAN_VERSION_VERSION 1 /** * struct brcmf_assoclist_le - request assoc list. diff --git a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/scan_param.c b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/scan_param.c new file mode 100644 index 00000000000000..6bd5f6d1616c04 --- /dev/null +++ b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/scan_param.c @@ -0,0 +1,446 @@ +// SPDX-License-Identifier: ISC +/* + * Copyright (c) 2023 Daniel Berlin + */ +#include +#include + +#include "core.h" +#include "debug.h" +#include "fwil_types.h" +#include "cfg80211.h" +#include "scan_param.h" + +static void brcmf_scan_param_set_defaults(u8 (*bssid)[ETH_ALEN], s8 *bss_type, __le32 *channel_num, + __le32 *nprobes, __le32 *active_time, + __le32 *passive_time, + __le32 *home_time) +{ + eth_broadcast_addr(*bssid); + *bss_type = DOT11_BSSTYPE_ANY; + *channel_num = 0; + *nprobes = cpu_to_le32(-1); + *active_time = cpu_to_le32(-1); + *passive_time = cpu_to_le32(-1); + *home_time = cpu_to_le32(-1); +} + +static void brcmf_scan_param_copy_chanspecs( + struct brcmf_cfg80211_info *cfg, __le16 (*dest_channels)[], + struct ieee80211_channel **in_channels, u32 n_channels) +{ + int i; + for (i = 0; i < n_channels; i++) { + u32 chanspec = + channel_to_chanspec(&cfg->d11inf, in_channels[i]); + brcmf_dbg(SCAN, "Chan : %d, Channel spec: %x\n", + in_channels[i]->hw_value, chanspec); + (*dest_channels)[i] = cpu_to_le16(chanspec); + } +} + +static void brcmf_scan_param_copy_ssids(char *dest_ssids, + struct cfg80211_ssid *in_ssids, + u32 n_ssids) +{ + int i; + for (i = 0; i < n_ssids; i++) { + struct brcmf_ssid_le ssid_le; + memset(&ssid_le, 0, sizeof(ssid_le)); + ssid_le.SSID_len = cpu_to_le32(in_ssids[i].ssid_len); + memcpy(ssid_le.SSID, in_ssids[i].ssid, in_ssids[i].ssid_len); + if (!ssid_le.SSID_len) + brcmf_dbg(SCAN, "%d: Broadcast scan\n", i); + else + brcmf_dbg(SCAN, "%d: scan for %.32s size=%d\n", i, + ssid_le.SSID, ssid_le.SSID_len); + memcpy(dest_ssids, &ssid_le, sizeof(ssid_le)); + dest_ssids += sizeof(ssid_le); + } +} + +/* The scan parameter structures have an array of SSID's that appears at the end in some cases. + * In these cases, the chan list is really the lower half of a pair, the upper half is a ssid number, + * and then after all of that there is an array of SSIDs */ +static u32 +brcmf_scan_param_tail_size(const struct cfg80211_scan_request *request, + u32 params_size) +{ + if (request != NULL) { + /* Allocate space for populating ssid upper half in struct */ + params_size += sizeof(u32) * ((request->n_channels + 1) / 2); + /* Allocate space for populating ssids in struct */ + params_size += sizeof(struct brcmf_ssid_le) * request->n_ssids; + } else { + params_size += sizeof(u16); + } + return params_size; +} + +static u32 brcmf_nl80211_scan_flags_to_scan_flags(u32 nl80211_flags) +{ + u32 scan_flags = 0; + if (nl80211_flags & NL80211_SCAN_FLAG_LOW_SPAN) { + scan_flags |= BRCMF_SCANFLAGS_LOW_SPAN; + brcmf_dbg(SCAN, "requested low span scan\n"); + } + if (nl80211_flags & NL80211_SCAN_FLAG_HIGH_ACCURACY) { + scan_flags |= BRCMF_SCANFLAGS_HIGH_ACCURACY; + brcmf_dbg(SCAN, "requested high accuracy scan\n"); + } + if (nl80211_flags & NL80211_SCAN_FLAG_LOW_POWER) { + scan_flags |= BRCMF_SCANFLAGS_LOW_POWER; + brcmf_dbg(SCAN, "requested low power scan\n"); + } + if (nl80211_flags & NL80211_SCAN_FLAG_LOW_PRIORITY) { + scan_flags |= BRCMF_SCANFLAGS_LOW_PRIO; + brcmf_dbg(SCAN, "requested low priority scan\n"); + } + return scan_flags; +} + +static void * +brcmf_scan_param_get_prepped_struct_v1(struct brcmf_cfg80211_info *cfg, + u32 *struct_size, + struct cfg80211_scan_request *request) +{ + u32 n_ssids; + u32 n_channels; + u32 params_size = sizeof(struct brcmf_scan_params_le); + u32 length; + struct brcmf_scan_params_le *params_le = NULL; + u8 scan_type = BRCMF_SCANTYPE_ACTIVE; + + length = offsetof(struct brcmf_scan_params_le, channel_list); + params_size = brcmf_scan_param_tail_size(request, params_size); + params_le = kzalloc(params_size, GFP_KERNEL); + if (!params_le) { + bphy_err(cfg, "Could not allocate scan params\n"); + return NULL; + } + brcmf_scan_param_set_defaults(¶ms_le->bssid, + ¶ms_le->bss_type, ¶ms_le->channel_num, + ¶ms_le->nprobes, ¶ms_le->active_time, + ¶ms_le->passive_time, ¶ms_le->home_time); + + /* Scan abort */ + if (!request) { + params_le->channel_num = cpu_to_le32(1); + params_le->channel_list[0] = cpu_to_le16(-1); + goto done; + } + + n_ssids = request->n_ssids; + n_channels = request->n_channels; + + /* Copy channel array if applicable */ + brcmf_dbg(SCAN, "### List of channelspecs to scan ### %d\n", + n_channels); + if (n_channels > 0) { + length += roundup(sizeof(u16) * n_channels, sizeof(u32)); + brcmf_scan_param_copy_chanspecs(cfg, ¶ms_le->channel_list, + request->channels, n_channels); + } else { + brcmf_dbg(SCAN, "Scanning all channels\n"); + } + + /* Copy ssid array if applicable */ + brcmf_dbg(SCAN, "### List of SSIDs to scan ### %d\n", n_ssids); + if (n_ssids > 0) { + s32 offset; + char *ptr; + + offset = + offsetof(struct brcmf_scan_params_le, channel_list) + + n_channels * sizeof(u16); + offset = roundup(offset, sizeof(u32)); + length += sizeof(struct brcmf_ssid_le) * n_ssids; + ptr = (char *)params_le + offset; + brcmf_scan_param_copy_ssids(ptr, request->ssids, n_ssids); + } else { + brcmf_dbg(SCAN, "Performing passive scan\n"); + scan_type = BRCMF_SCANTYPE_PASSIVE; + } + scan_type |= brcmf_nl80211_scan_flags_to_scan_flags(request->flags); + params_le->scan_type =scan_type; + /* Adding mask to channel numbers */ + params_le->channel_num = + cpu_to_le32((n_ssids << BRCMF_SCAN_PARAMS_NSSID_SHIFT) | + (n_channels & BRCMF_SCAN_PARAMS_COUNT_MASK)); +done: + *struct_size = length; + return params_le; +} + +static void * +brcmf_scan_param_get_prepped_struct_v2(struct brcmf_cfg80211_info *cfg, + u32 *struct_size, + struct cfg80211_scan_request *request) +{ + u32 n_ssids; + u32 n_channels; + u32 params_size = sizeof(struct brcmf_scan_params_v2_le); + u32 length; + struct brcmf_scan_params_v2_le *params_le = NULL; + u32 scan_type = BRCMF_SCANTYPE_ACTIVE; + + length = offsetof(struct brcmf_scan_params_v2_le, channel_list); + params_size = brcmf_scan_param_tail_size(request, params_size); + params_le = kzalloc(params_size, GFP_KERNEL); + if (!params_le) { + bphy_err(cfg, "Could not allocate scan params\n"); + return NULL; + } + params_le->version = cpu_to_le16(BRCMF_SCAN_PARAMS_VERSION_V2); + brcmf_scan_param_set_defaults(¶ms_le->bssid, + ¶ms_le->bss_type, ¶ms_le->channel_num, + ¶ms_le->nprobes, ¶ms_le->active_time, + ¶ms_le->passive_time, ¶ms_le->home_time); + + /* Scan abort */ + if (!request) { + length += sizeof(u16); + params_le->channel_num = cpu_to_le32(1); + params_le->channel_list[0] = cpu_to_le16(-1); + params_le->length = cpu_to_le16(length); + goto done; + } + + n_ssids = request->n_ssids; + n_channels = request->n_channels; + + /* Copy channel array if applicable */ + brcmf_dbg(SCAN, "### List of channelspecs to scan ### %d\n", + n_channels); + if (n_channels > 0) { + length += roundup(sizeof(u16) * n_channels, sizeof(u32)); + brcmf_scan_param_copy_chanspecs(cfg, ¶ms_le->channel_list, + request->channels, n_channels); + } else { + brcmf_dbg(SCAN, "Scanning all channels\n"); + } + + /* Copy ssid array if applicable */ + brcmf_dbg(SCAN, "### List of SSIDs to scan ### %d\n", n_ssids); + if (n_ssids > 0) { + s32 offset; + char *ptr; + + offset = + offsetof(struct brcmf_scan_params_v2_le, channel_list) + + n_channels * sizeof(u16); + offset = roundup(offset, sizeof(u32)); + length += sizeof(struct brcmf_ssid_le) * n_ssids; + ptr = (char *)params_le + offset; + brcmf_scan_param_copy_ssids(ptr, request->ssids, n_ssids); + + } else { + brcmf_dbg(SCAN, "Performing passive scan\n"); + scan_type = BRCMF_SCANTYPE_PASSIVE; + } + scan_type |= brcmf_nl80211_scan_flags_to_scan_flags(request->flags); + params_le->scan_type = cpu_to_le32(scan_type); + params_le->length = cpu_to_le16(length); + /* Adding mask to channel numbers */ + params_le->channel_num = + cpu_to_le32((n_ssids << BRCMF_SCAN_PARAMS_NSSID_SHIFT) | + (n_channels & BRCMF_SCAN_PARAMS_COUNT_MASK)); +done: + *struct_size = length; + return params_le; +} + +static void * +brcmf_scan_param_get_prepped_struct_v3(struct brcmf_cfg80211_info *cfg, + u32 *struct_size, + struct cfg80211_scan_request *request) +{ + u32 n_ssids; + u32 n_channels; + u32 params_size = sizeof(struct brcmf_scan_params_v3_le); + u32 length; + struct brcmf_scan_params_v3_le *params_le = NULL; + u32 scan_type = BRCMF_SCANTYPE_ACTIVE; + + length = offsetof(struct brcmf_scan_params_v3_le, channel_list); + params_size = brcmf_scan_param_tail_size(request, params_size); + params_le = kzalloc(params_size, GFP_KERNEL); + if (!params_le) { + bphy_err(cfg, "Could not allocate scan params\n"); + return NULL; + } + + params_le->version = cpu_to_le16(BRCMF_SCAN_PARAMS_VERSION_V3); + params_le->ssid_type = 0; + brcmf_scan_param_set_defaults(¶ms_le->bssid, + ¶ms_le->bss_type, ¶ms_le->channel_num, + ¶ms_le->nprobes, ¶ms_le->active_time, + ¶ms_le->passive_time, ¶ms_le->home_time); + + /* Scan abort */ + if (!request) { + length += sizeof(u16); + params_le->channel_num = cpu_to_le32(1); + params_le->channel_list[0] = cpu_to_le16(-1); + params_le->length = cpu_to_le16(length); + goto done; + } + + n_ssids = request->n_ssids; + n_channels = request->n_channels; + + /* Copy channel array if applicable */ + brcmf_dbg(SCAN, "### List of channelspecs to scan ### %d\n", + n_channels); + if (n_channels > 0) { + length += roundup(sizeof(u16) * n_channels, sizeof(u32)); + brcmf_scan_param_copy_chanspecs(cfg, ¶ms_le->channel_list, + request->channels, n_channels); + + } else { + brcmf_dbg(SCAN, "Scanning all channels\n"); + } + + /* Copy ssid array if applicable */ + brcmf_dbg(SCAN, "### List of SSIDs to scan ### %d\n", n_ssids); + if (n_ssids > 0) { + s32 offset; + char *ptr; + + offset = + offsetof(struct brcmf_scan_params_v3_le, channel_list) + + n_channels * sizeof(u16); + offset = roundup(offset, sizeof(u32)); + length += sizeof(struct brcmf_ssid_le) * n_ssids; + ptr = (char *)params_le + offset; + brcmf_scan_param_copy_ssids(ptr, request->ssids, n_ssids); + + } else { + brcmf_dbg(SCAN, "Performing passive scan\n"); + scan_type = BRCMF_SCANTYPE_PASSIVE; + } + scan_type |= brcmf_nl80211_scan_flags_to_scan_flags(request->flags); + params_le->scan_type = cpu_to_le32(scan_type); + params_le->length = cpu_to_le16(length); + params_le->channel_num = + cpu_to_le32((n_ssids << BRCMF_SCAN_PARAMS_NSSID_SHIFT) | + (n_channels & BRCMF_SCAN_PARAMS_COUNT_MASK)); + + /* Include RNR results if requested */ + if (request->flags & NL80211_SCAN_FLAG_COLOCATED_6GHZ) { + params_le->ssid_type |= BRCMF_SCANSSID_INC_RNR; + } + /* Adding mask to channel numbers */ +done: + *struct_size = length; + return params_le; +} + +static void * +brcmf_scan_param_get_prepped_struct_v4(struct brcmf_cfg80211_info *cfg, + u32 *struct_size, + struct cfg80211_scan_request *request) +{ + u32 n_ssids; + u32 n_channels; + u32 params_size = sizeof(struct brcmf_scan_params_v4_le); + u32 length; + struct brcmf_scan_params_v4_le *params_le = NULL; + u32 scan_type = BRCMF_SCANTYPE_ACTIVE; + + length = offsetof(struct brcmf_scan_params_v4_le, channel_list); + params_size = brcmf_scan_param_tail_size(request, params_size); + params_le = kzalloc(params_size, GFP_KERNEL); + if (!params_le) { + bphy_err(cfg, "Could not allocate scan params\n"); + return NULL; + } + params_le->version = cpu_to_le16(BRCMF_SCAN_PARAMS_VERSION_V4); + params_le->ssid_type = 0; + brcmf_scan_param_set_defaults(¶ms_le->bssid, + ¶ms_le->bss_type, ¶ms_le->channel_num, + ¶ms_le->nprobes, ¶ms_le->active_time, + ¶ms_le->passive_time, ¶ms_le->home_time); + + /* Scan abort */ + if (!request) { + length += sizeof(u16); + params_le->channel_num = cpu_to_le32(1); + params_le->channel_list[0] = cpu_to_le16(-1); + params_le->length = cpu_to_le16(length); + goto done; + } + + n_ssids = request->n_ssids; + n_channels = request->n_channels; + + /* Copy channel array if applicable */ + brcmf_dbg(SCAN, "### List of channelspecs to scan ### %d\n", + n_channels); + if (n_channels > 0) { + length += roundup(sizeof(u16) * n_channels, sizeof(u32)); + brcmf_scan_param_copy_chanspecs(cfg, ¶ms_le->channel_list, + request->channels, n_channels); + } else { + brcmf_dbg(SCAN, "Scanning all channels\n"); + } + + /* Copy ssid array if applicable */ + brcmf_dbg(SCAN, "### List of SSIDs to scan ### %d\n", n_ssids); + if (n_ssids > 0) { + s32 offset; + char *ptr; + + offset = + offsetof(struct brcmf_scan_params_v4_le, channel_list) + + n_channels * sizeof(u16); + offset = roundup(offset, sizeof(u32)); + length += sizeof(struct brcmf_ssid_le) * n_ssids; + ptr = (char *)params_le + offset; + brcmf_scan_param_copy_ssids(ptr, request->ssids, n_ssids); + } else { + brcmf_dbg(SCAN, "Performing passive scan\n"); + scan_type = BRCMF_SCANTYPE_PASSIVE; + } + scan_type |= brcmf_nl80211_scan_flags_to_scan_flags(request->flags); + params_le->scan_type = cpu_to_le32(scan_type); + params_le->length = cpu_to_le16(length); + /* Adding mask to channel numbers */ + params_le->channel_num = + cpu_to_le32((n_ssids << BRCMF_SCAN_PARAMS_NSSID_SHIFT) | + (n_channels & BRCMF_SCAN_PARAMS_COUNT_MASK)); + /* Include RNR results if requested */ + if (request->flags & NL80211_SCAN_FLAG_COLOCATED_6GHZ) { + params_le->ssid_type |= BRCMF_SCANSSID_INC_RNR; + } +done: + *struct_size = length; + return params_le; +} + +int brcmf_scan_param_setup_for_version(struct brcmf_pub *drvr, u8 version) +{ + drvr->scan_param_handler.version = version; + switch (version) { + case 1: { + drvr->scan_param_handler.get_prepped_struct = + brcmf_scan_param_get_prepped_struct_v1; + } break; + case 2: { + drvr->scan_param_handler.get_prepped_struct = + brcmf_scan_param_get_prepped_struct_v2; + } break; + case 3: { + drvr->scan_param_handler.get_prepped_struct = + brcmf_scan_param_get_prepped_struct_v3; + } break; + case 4: { + drvr->scan_param_handler.get_prepped_struct = + brcmf_scan_param_get_prepped_struct_v4; + + } break; + default: + return -EINVAL; + } + return 0; +} diff --git a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/scan_param.h b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/scan_param.h new file mode 100644 index 00000000000000..577de083c6e3cd --- /dev/null +++ b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/scan_param.h @@ -0,0 +1,22 @@ +// SPDX-License-Identifier: ISC +/* + * Copyright (c) 2023 Daniel Berlin + */ + +#ifndef _BRCMF_SCAN_PARAM_H +#define _BRCMF_SCAN_PARAM_H + +struct brcmf_pub; + +/** + * brcmf_scan_param_setup_for_version() - Setup the driver to handle join structures + * + * There are a number of different structures and interface versions for scanning info + * This sets up the driver to handle a particular interface version. + * + * @drvr Driver structure to setup + * @ver Interface version + * Return: %0 if okay, error code otherwise + */ +int brcmf_scan_param_setup_for_version(struct brcmf_pub *, u8 ver); +#endif /* _BRCMF_SCAN_PARAM_H */ From d65c704092de7aba3b5e821521e575a254f4cbf3 Mon Sep 17 00:00:00 2001 From: Hector Martin Date: Thu, 5 Oct 2023 01:23:32 +0900 Subject: [PATCH 0237/3902] [brcmfmac] Support new join parameter structure versions To support new join parameter versions, we move to using a function pointer structure that knows how to deal with the different versions of structures Drive-by fix: Always count the assoc_params length even if no bssid is provided. It doesn't make sense to truncate it off, since we need to set the bssid to the broadcast addr anyway in that case. Signed-off-by: Hector Martin Signed-off-by: Daniel Berlin --- .../broadcom/brcm80211/brcmfmac/Makefile | 1 + .../broadcom/brcm80211/brcmfmac/cfg80211.c | 309 +++++++----------- .../broadcom/brcm80211/brcmfmac/cfg80211.h | 2 + .../broadcom/brcm80211/brcmfmac/core.h | 43 ++- .../broadcom/brcm80211/brcmfmac/feature.c | 42 ++- .../broadcom/brcm80211/brcmfmac/fwil_types.h | 118 ++++++- .../broadcom/brcm80211/brcmfmac/join_param.c | 288 ++++++++++++++++ .../broadcom/brcm80211/brcmfmac/join_param.h | 22 ++ .../broadcom/brcm80211/brcmfmac/scan_param.c | 8 +- 9 files changed, 629 insertions(+), 204 deletions(-) create mode 100644 drivers/net/wireless/broadcom/brcm80211/brcmfmac/join_param.c create mode 100644 drivers/net/wireless/broadcom/brcm80211/brcmfmac/join_param.h diff --git a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/Makefile b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/Makefile index f3f72f9524578c..694b50a0664f24 100644 --- a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/Makefile +++ b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/Makefile @@ -25,6 +25,7 @@ brcmfmac-objs += \ btcoex.o \ vendor.o \ pno.o \ + join_param.o \ scan_param.o \ xtlv.o diff --git a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/cfg80211.c b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/cfg80211.c index eb901ec7bbe116..da882306cf9ca6 100644 --- a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/cfg80211.c +++ b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/cfg80211.c @@ -78,10 +78,6 @@ #define DOT11_BCN_PRB_FIXED_LEN 12 /* beacon/probe fixed length */ -#define BRCMF_SCAN_JOIN_ACTIVE_DWELL_TIME_MS 320 -#define BRCMF_SCAN_JOIN_PASSIVE_DWELL_TIME_MS 400 -#define BRCMF_SCAN_JOIN_PROBE_INTERVAL_MS 20 - #define BRCMF_SCAN_CHANNEL_TIME 40 #define BRCMF_SCAN_UNASSOC_TIME 40 #define BRCMF_SCAN_PASSIVE_TIME 120 @@ -100,9 +96,6 @@ #define PKT_TOKEN_IDX 15 #define IDLE_TOKEN_IDX 12 -#define BRCMF_ASSOC_PARAMS_FIXED_SIZE \ - (sizeof(struct brcmf_assoc_params_le) - sizeof(u16)) - #define BRCMF_MAX_CHANSPEC_LIST \ (BRCMF_DCMD_MEDLEN / sizeof(__le32) - 1) @@ -391,8 +384,8 @@ static int nl80211_band_to_chanspec_band(enum nl80211_band band) } } -static u16 chandef_to_chanspec(struct brcmu_d11inf *d11inf, - struct cfg80211_chan_def *ch) +u16 chandef_to_chanspec(struct brcmu_d11inf *d11inf, + struct cfg80211_chan_def *ch) { struct brcmu_chan ch_inf; s32 primary_offset; @@ -1138,7 +1131,7 @@ s32 brcmf_notify_escan_complete(struct brcmf_cfg80211_info *cfg, if (fw_abort) { u32 len; - void *data = drvr->scan_param_handler.get_prepped_struct(cfg, &len, NULL); + void *data = drvr->scan_param_handler.get_struct_for_request(cfg, &len, NULL); if (!data){ bphy_err(drvr, "Scan abort failed to prepare abort struct\n"); return 0; @@ -1378,7 +1371,7 @@ brcmf_run_escan(struct brcmf_cfg80211_info *cfg, struct brcmf_if *ifp, brcmf_dbg(SCAN, "E-SCAN START\n"); - prepped_params = drvr->scan_param_handler.get_prepped_struct(cfg, &struct_size, request); + prepped_params = drvr->scan_param_handler.get_struct_for_request(cfg, &struct_size, request); if (!prepped_params) { err = -EINVAL; goto exit; @@ -1697,21 +1690,19 @@ static void brcmf_link_down(struct brcmf_cfg80211_vif *vif, u16 reason, brcmf_dbg(TRACE, "Exit\n"); } -static s32 -brcmf_cfg80211_join_ibss(struct wiphy *wiphy, struct net_device *ndev, - struct cfg80211_ibss_params *params) +static s32 brcmf_cfg80211_join_ibss(struct wiphy *wiphy, + struct net_device *ndev, + struct cfg80211_ibss_params *params) { struct brcmf_cfg80211_info *cfg = wiphy_to_cfg(wiphy); struct brcmf_if *ifp = netdev_priv(ndev); struct brcmf_cfg80211_profile *profile = &ifp->vif->profile; struct brcmf_pub *drvr = cfg->pub; - struct brcmf_join_params join_params; - size_t join_params_size = 0; - s32 err = 0; + void *join_params; + u32 join_params_size = 0; s32 wsec = 0; s32 bcnprd; - u16 chanspec; - u32 ssid_len; + s32 err = 0; brcmf_dbg(TRACE, "Enter\n"); if (!check_vif_up(ifp->vif)) @@ -1785,58 +1776,40 @@ brcmf_cfg80211_join_ibss(struct wiphy *wiphy, struct net_device *ndev, goto done; } - /* Configure required join parameter */ - memset(&join_params, 0, sizeof(struct brcmf_join_params)); - - /* SSID */ - ssid_len = min_t(u32, params->ssid_len, IEEE80211_MAX_SSID_LEN); - memcpy(join_params.ssid_le.SSID, params->ssid, ssid_len); - join_params.ssid_le.SSID_len = cpu_to_le32(ssid_len); - join_params_size = sizeof(join_params.ssid_le); - - /* BSSID */ if (params->bssid) { - memcpy(join_params.params_le.bssid, params->bssid, ETH_ALEN); - join_params_size += BRCMF_ASSOC_PARAMS_FIXED_SIZE; memcpy(profile->bssid, params->bssid, ETH_ALEN); } else { - eth_broadcast_addr(join_params.params_le.bssid); eth_zero_addr(profile->bssid); } - /* Channel */ + cfg->ibss_starter = false; + cfg->channel = 0; if (params->chandef.chan) { - u32 target_channel; + u16 chanspec; + cfg->channel = ieee80211_frequency_to_channel( + params->chandef.chan->center_freq); + /* adding chanspec */ + chanspec = chandef_to_chanspec(&cfg->d11inf, ¶ms->chandef); + + /* set chanspec */ + err = brcmf_fil_iovar_int_set(ifp, "chanspec", chanspec); - cfg->channel = - ieee80211_frequency_to_channel( - params->chandef.chan->center_freq); - if (params->channel_fixed) { - /* adding chanspec */ - chanspec = chandef_to_chanspec(&cfg->d11inf, - ¶ms->chandef); - join_params.params_le.chanspec_list[0] = - cpu_to_le16(chanspec); - join_params.params_le.chanspec_num = cpu_to_le32(1); - join_params_size += sizeof(join_params.params_le); - } - - /* set channel for starter */ - target_channel = cfg->channel; - err = brcmf_fil_cmd_int_set(ifp, BRCMF_C_SET_CHANNEL, - target_channel); if (err) { bphy_err(drvr, "WLC_SET_CHANNEL failed (%d)\n", err); goto done; } - } else - cfg->channel = 0; - - cfg->ibss_starter = false; - + } - err = brcmf_fil_cmd_data_set(ifp, BRCMF_C_SET_SSID, - &join_params, join_params_size); + join_params = drvr->join_param_handler.get_struct_for_ibss( + cfg, &join_params_size, params); + if (!join_params) { + bphy_err(drvr, "Converting join params failed\n"); + goto done; + } + err = brcmf_fil_cmd_data_set(ifp, BRCMF_C_SET_SSID, join_params, + join_params_size); + /* Free params no matter what */ + kfree(join_params); if (err) { bphy_err(drvr, "WLC_SET_SSID failed (%d)\n", err); goto done; @@ -2365,52 +2338,51 @@ static void brcmf_set_join_pref(struct brcmf_if *ifp, static s32 brcmf_cfg80211_connect(struct wiphy *wiphy, struct net_device *ndev, - struct cfg80211_connect_params *sme) + struct cfg80211_connect_params *params) { struct brcmf_cfg80211_info *cfg = wiphy_to_cfg(wiphy); struct brcmf_if *ifp = netdev_priv(ndev); struct brcmf_cfg80211_profile *profile = &ifp->vif->profile; - struct ieee80211_channel *chan = sme->channel; + struct ieee80211_channel *chan = params->channel; struct brcmf_pub *drvr = ifp->drvr; - struct brcmf_join_params join_params; - size_t join_params_size; + void *join_params; + u32 join_params_size; + void *fallback_join_params; + u32 fallback_join_params_size; const struct brcmf_tlv *rsn_ie; const struct brcmf_vs_tlv *wpa_ie; const void *ie; u32 ie_len; - struct brcmf_ext_join_params_le *ext_join_params; - u16 chanspec; s32 err = 0; - u32 ssid_len; brcmf_dbg(TRACE, "Enter\n"); if (!check_vif_up(ifp->vif)) return -EIO; - if (!sme->ssid) { + if (!params->ssid) { bphy_err(drvr, "Invalid ssid\n"); return -EOPNOTSUPP; } - if (sme->channel_hint) - chan = sme->channel_hint; + if (params->channel_hint) + chan = params->channel_hint; - if (sme->bssid_hint) - sme->bssid = sme->bssid_hint; + if (params->bssid_hint) + params->bssid = params->bssid_hint; if (ifp->vif == cfg->p2p.bss_idx[P2PAPI_BSSCFG_PRIMARY].vif) { /* A normal (non P2P) connection request setup. */ ie = NULL; ie_len = 0; /* find the WPA_IE */ - wpa_ie = brcmf_find_wpaie((u8 *)sme->ie, sme->ie_len); + wpa_ie = brcmf_find_wpaie((u8 *)params->ie, params->ie_len); if (wpa_ie) { ie = wpa_ie; ie_len = wpa_ie->len + TLV_HDR_LEN; } else { /* find the RSN_IE */ - rsn_ie = brcmf_parse_tlvs((const u8 *)sme->ie, - sme->ie_len, + rsn_ie = brcmf_parse_tlvs((const u8 *)params->ie, + params->ie_len, WLAN_EID_RSN); if (rsn_ie) { ie = rsn_ie; @@ -2421,7 +2393,7 @@ brcmf_cfg80211_connect(struct wiphy *wiphy, struct net_device *ndev, } err = brcmf_vif_set_mgmt_ie(ifp->vif, BRCMF_VNDR_IE_ASSOCREQ_FLAG, - sme->ie, sme->ie_len); + params->ie, params->ie_len); if (err) bphy_err(drvr, "Set Assoc REQ IE Failed\n"); else @@ -2432,166 +2404,117 @@ brcmf_cfg80211_connect(struct wiphy *wiphy, struct net_device *ndev, if (chan) { cfg->channel = ieee80211_frequency_to_channel(chan->center_freq); - chanspec = channel_to_chanspec(&cfg->d11inf, chan); - brcmf_dbg(CONN, "channel=%d, center_req=%d, chanspec=0x%04x\n", - cfg->channel, chan->center_freq, chanspec); + brcmf_dbg(CONN, "channel=%d, center_req=%d\n", + cfg->channel, chan->center_freq); } else { cfg->channel = 0; - chanspec = 0; } - brcmf_dbg(INFO, "ie (%p), ie_len (%zd)\n", sme->ie, sme->ie_len); + brcmf_dbg(INFO, "ie (%p), ie_len (%zd)\n", params->ie, params->ie_len); - err = brcmf_set_wpa_version(ndev, sme); + err = brcmf_set_wpa_version(ndev, params); if (err) { bphy_err(drvr, "wl_set_wpa_version failed (%d)\n", err); goto done; } - sme->auth_type = brcmf_war_auth_type(ifp, sme->auth_type); - err = brcmf_set_auth_type(ndev, sme); + params->auth_type = brcmf_war_auth_type(ifp, params->auth_type); + err = brcmf_set_auth_type(ndev, params); if (err) { bphy_err(drvr, "wl_set_auth_type failed (%d)\n", err); goto done; } - err = brcmf_set_wsec_mode(ndev, sme); + err = brcmf_set_wsec_mode(ndev, params); if (err) { bphy_err(drvr, "wl_set_set_cipher failed (%d)\n", err); goto done; } - err = brcmf_set_key_mgmt(ndev, sme); + err = brcmf_set_key_mgmt(ndev, params); if (err) { bphy_err(drvr, "wl_set_key_mgmt failed (%d)\n", err); goto done; } - err = brcmf_set_sharedkey(ndev, sme); + err = brcmf_set_sharedkey(ndev, params); if (err) { bphy_err(drvr, "brcmf_set_sharedkey failed (%d)\n", err); goto done; } - - if (sme->crypto.psk && - profile->use_fwsup != BRCMF_PROFILE_FWSUP_SAE) { - if (WARN_ON(profile->use_fwsup != BRCMF_PROFILE_FWSUP_NONE)) { - err = -EINVAL; - goto done; + if (brcmf_feat_is_enabled(ifp, BRCMF_FEAT_FWSUP)) { + if (params->crypto.psk) { + if ((profile->use_fwsup != BRCMF_PROFILE_FWSUP_SAE) && + (profile->use_fwsup != BRCMF_PROFILE_FWSUP_PSK)) { + if (WARN_ON(profile->use_fwsup != + BRCMF_PROFILE_FWSUP_NONE)) { + err = -EINVAL; + goto done; + } + brcmf_dbg(INFO, "using PSK offload\n"); + profile->use_fwsup = BRCMF_PROFILE_FWSUP_PSK; + } } - brcmf_dbg(INFO, "using PSK offload\n"); - profile->use_fwsup = BRCMF_PROFILE_FWSUP_PSK; - } - if (profile->use_fwsup != BRCMF_PROFILE_FWSUP_NONE) { - /* enable firmware supplicant for this interface */ - err = brcmf_fil_iovar_int_set(ifp, "sup_wpa", 1); - if (err < 0) { - bphy_err(drvr, "failed to enable fw supplicant\n"); - goto done; + if ((profile->use_fwsup == BRCMF_PROFILE_FWSUP_PSK) && + params->crypto.psk) + err = brcmf_set_pmk(ifp, params->crypto.psk, + BRCMF_WSEC_MAX_PSK_LEN); + else if (profile->use_fwsup == BRCMF_PROFILE_FWSUP_SAE) { + /* clean up user-space RSNE */ + if (brcmf_fil_iovar_data_set(ifp, "wpaie", NULL, 0)) { + bphy_err( + drvr, + "failed to clean up user-space RSNE\n"); + goto done; + } + err = brcmf_fwvid_set_sae_password(ifp, ¶ms->crypto); + if (!err && params->crypto.psk) + err = brcmf_set_pmk(ifp, params->crypto.psk, + BRCMF_WSEC_MAX_PSK_LEN); } - } - - if (profile->use_fwsup == BRCMF_PROFILE_FWSUP_PSK) - err = brcmf_set_pmk(ifp, sme->crypto.psk, - BRCMF_WSEC_MAX_PSK_LEN); - else if (profile->use_fwsup == BRCMF_PROFILE_FWSUP_SAE) { - /* clean up user-space RSNE */ - err = brcmf_fil_iovar_data_set(ifp, "wpaie", NULL, 0); - if (err) { - bphy_err(drvr, "failed to clean up user-space RSNE\n"); + if (err) goto done; - } - err = brcmf_fwvid_set_sae_password(ifp, &sme->crypto); - if (!err && sme->crypto.psk) - err = brcmf_set_pmk(ifp, sme->crypto.psk, - BRCMF_WSEC_MAX_PSK_LEN); } - if (err) - goto done; - - /* Join with specific BSSID and cached SSID - * If SSID is zero join based on BSSID only - */ - join_params_size = offsetof(struct brcmf_ext_join_params_le, assoc_le) + - offsetof(struct brcmf_assoc_params_le, chanspec_list); - if (cfg->channel) - join_params_size += sizeof(u16); - ext_join_params = kzalloc(sizeof(*ext_join_params), GFP_KERNEL); - if (ext_join_params == NULL) { - err = -ENOMEM; - goto done; - } - ssid_len = min_t(u32, sme->ssid_len, IEEE80211_MAX_SSID_LEN); - ext_join_params->ssid_le.SSID_len = cpu_to_le32(ssid_len); - memcpy(&ext_join_params->ssid_le.SSID, sme->ssid, ssid_len); - if (ssid_len < IEEE80211_MAX_SSID_LEN) - brcmf_dbg(CONN, "SSID \"%s\", len (%d)\n", - ext_join_params->ssid_le.SSID, ssid_len); - - /* Set up join scan parameters */ - ext_join_params->scan_le.scan_type = -1; - ext_join_params->scan_le.home_time = cpu_to_le32(-1); - - if (sme->bssid) - memcpy(&ext_join_params->assoc_le.bssid, sme->bssid, ETH_ALEN); - else - eth_broadcast_addr(ext_join_params->assoc_le.bssid); + brcmf_set_join_pref(ifp, ¶ms->bss_select); + if (params->ssid_len < IEEE80211_MAX_SSID_LEN) + brcmf_dbg(CONN, "SSID \"%s\", len (%zu)\n", params->ssid, + params->ssid_len); + join_params = drvr->join_param_handler.get_struct_for_connect( + cfg, &join_params_size, params); - if (cfg->channel) { - ext_join_params->assoc_le.chanspec_num = cpu_to_le32(1); + if (join_params) { + err = brcmf_fil_bsscfg_data_set(ifp, "join", join_params, + join_params_size); - ext_join_params->assoc_le.chanspec_list[0] = - cpu_to_le16(chanspec); - /* Increase dwell time to receive probe response or detect - * beacon from target AP at a noisy air only during connect - * command. - */ - ext_join_params->scan_le.active_time = - cpu_to_le32(BRCMF_SCAN_JOIN_ACTIVE_DWELL_TIME_MS); - ext_join_params->scan_le.passive_time = - cpu_to_le32(BRCMF_SCAN_JOIN_PASSIVE_DWELL_TIME_MS); - /* To sync with presence period of VSDB GO send probe request - * more frequently. Probe request will be stopped when it gets - * probe response from target AP/GO. - */ - ext_join_params->scan_le.nprobes = - cpu_to_le32(BRCMF_SCAN_JOIN_ACTIVE_DWELL_TIME_MS / - BRCMF_SCAN_JOIN_PROBE_INTERVAL_MS); - } else { - ext_join_params->scan_le.active_time = cpu_to_le32(-1); - ext_join_params->scan_le.passive_time = cpu_to_le32(-1); - ext_join_params->scan_le.nprobes = cpu_to_le32(-1); + /* We only free the join parameters if we were successful. + * Otherwise they are used to extract the fallback, below */ + if (!err) { + kfree(join_params); + /* This is it. join command worked, we are done */ + goto done; + } + /* For versions >= 1, this should have worked, so report the error */ + if (drvr->join_param_handler.version >= 1) { + bphy_err(drvr, "Failed to use join iovar to join: %d\n", + err); + } } - brcmf_set_join_pref(ifp, &sme->bss_select); - - err = brcmf_fil_bsscfg_data_set(ifp, "join", ext_join_params, - join_params_size); - kfree(ext_join_params); - if (!err) - /* This is it. join command worked, we are done */ + /* Fallback to using WLC_SET_SSID approach, which just uses join_params parts of the structure */ + fallback_join_params = drvr->join_param_handler.get_join_from_ext_join( + join_params, &fallback_join_params_size); + if (!fallback_join_params) { + bphy_err(drvr, "Unable to generate fallback join params\n"); + kfree(join_params); goto done; - - /* join command failed, fallback to set ssid */ - memset(&join_params, 0, sizeof(join_params)); - join_params_size = sizeof(join_params.ssid_le); - - memcpy(&join_params.ssid_le.SSID, sme->ssid, ssid_len); - join_params.ssid_le.SSID_len = cpu_to_le32(ssid_len); - - if (sme->bssid) - memcpy(join_params.params_le.bssid, sme->bssid, ETH_ALEN); - else - eth_broadcast_addr(join_params.params_le.bssid); - - if (cfg->channel) { - join_params.params_le.chanspec_list[0] = cpu_to_le16(chanspec); - join_params.params_le.chanspec_num = cpu_to_le32(1); - join_params_size += sizeof(join_params.params_le); } err = brcmf_fil_cmd_data_set(ifp, BRCMF_C_SET_SSID, - &join_params, join_params_size); + fallback_join_params, + fallback_join_params_size); + + kfree(join_params); + kfree(fallback_join_params); if (err) bphy_err(drvr, "BRCMF_C_SET_SSID failed (%d)\n", err); diff --git a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/cfg80211.h b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/cfg80211.h index 9fef47e60e0868..732a4a87988035 100644 --- a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/cfg80211.h +++ b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/cfg80211.h @@ -495,6 +495,8 @@ s32 brcmf_vif_set_mgmt_ie(struct brcmf_cfg80211_vif *vif, s32 pktflag, s32 brcmf_vif_clear_mgmt_ies(struct brcmf_cfg80211_vif *vif); u16 channel_to_chanspec(struct brcmu_d11inf *d11inf, struct ieee80211_channel *ch); +u16 chandef_to_chanspec(struct brcmu_d11inf *d11inf, + struct cfg80211_chan_def *ch); bool brcmf_get_vif_state_any(struct brcmf_cfg80211_info *cfg, unsigned long state); void brcmf_cfg80211_arm_vif_event(struct brcmf_cfg80211_info *cfg, diff --git a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/core.h b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/core.h index c7562bdb61e86c..4b52a3aa855de8 100644 --- a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/core.h +++ b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/core.h @@ -115,12 +115,48 @@ struct pno_struct_handler { u8 (*ssid)[IEEE80211_MAX_SSID_LEN], u8 *ssid_len, u8 *channel, enum nl80211_band *band); }; + struct cfg80211_scan_request; struct scan_param_struct_handler { u8 version; - void *(*get_prepped_struct)(struct brcmf_cfg80211_info *cfg, - u32 *struct_size, - struct cfg80211_scan_request *request); + void *(*get_struct_for_request)(struct brcmf_cfg80211_info *cfg, + u32 *struct_size, + struct cfg80211_scan_request *request); +}; + +struct cfg80211_ibss_params; +struct cfg80211_connect_params; + +/** + * struct join_param_struct_handler - Handler for different join parameter versions + * + * There are a number of different, incompatible structures and interface versions for join/extended join parameters + * We abstract away the actual structures used, so that code does not have to worry about filling in structs properly. + * + * This interface deliberately takes and returns opaque structures. + * + * @version - Interface version the firmware supports/uses + * @get_struct_for_ibss - Return a join parameter structure for a set of IBSS parameters. + * This structure can be used to join the passed BSS. + * @get_struct_for_connect - Return an extended join parameter structure for a set of connect + * parameters. This structure can be used to join the SSID specified in the parameters. + * @get_join_from_ext_join - When an extended join does not work, we fall back to a regular join. + * This function produces a join parameter struture from an extended join one. + */ +struct join_param_struct_handler { + u8 version; + /* This returns a join_param type struct */ + void *(*get_struct_for_ibss)(struct brcmf_cfg80211_info *cfg, + u32 *struct_size, + struct cfg80211_ibss_params *params); + /* This returns an ext_join_param type struct */ + void *(*get_struct_for_connect)(struct brcmf_cfg80211_info *cfg, + u32 *struct_size, + struct cfg80211_connect_params *params); + /* This returns the join param portion of an ext_join_param type struct. + * The memory returned is separately allocated from the passed-in struct. + */ + void *(*get_join_from_ext_join)(void *ext_join_param, u32 *struct_size); }; /* Common structure for module and instance linkage */ @@ -174,6 +210,7 @@ struct brcmf_pub { u16 cnt_ver; struct pno_struct_handler pno_handler; struct scan_param_struct_handler scan_param_handler; + struct join_param_struct_handler join_param_handler; }; /* forward declarations */ diff --git a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/feature.c b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/feature.c index a6725b66ebf07a..4b438758d03d83 100644 --- a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/feature.c +++ b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/feature.c @@ -18,9 +18,22 @@ #include "common.h" #include "pno.h" #include "scan_param.h" +#include "join_param.h" #define BRCMF_FW_UNSUPPORTED 23 +/* MIN branch version supporting join iovar versioning */ +#define MIN_JOINEXT_V1_FW_MAJOR 17u +/* Branch/es supporting join iovar versioning prior to + * MIN_JOINEXT_V1_FW_MAJOR + */ +#define MIN_JOINEXT_V1_BR2_FW_MAJOR 16 +#define MIN_JOINEXT_V1_BR2_FW_MINOR 1 + +#define MIN_JOINEXT_V1_BR1_FW_MAJOR 14 +#define MIN_JOINEXT_V1_BR1_FW_MINOR_2 2 +#define MIN_JOINEXT_V1_BR1_FW_MINOR_4 4 + /* * expand feature list to array of feature strings. */ @@ -139,7 +152,7 @@ struct brcmf_feat_wlcfeat { static const struct brcmf_feat_wlcfeat brcmf_feat_wlcfeat_map[] = { { 12, 0, BIT(BRCMF_FEAT_PMKID_V2) }, - { 13, 0, BIT(BRCMF_FEAT_PMKID_V3) }, + { 13, 0, BIT(BRCMF_FEAT_PMKID_V3) } }; static void brcmf_feat_wlc_version_overrides(struct brcmf_pub *drv) @@ -292,6 +305,7 @@ static int brcmf_feat_fwcap_debugfs_read(struct seq_file *seq, void *data) void brcmf_feat_attach(struct brcmf_pub *drvr) { struct brcmf_if *ifp = brcmf_get_ifp(drvr, 0); + struct brcmf_join_version_le join_ver; struct brcmf_scan_version_le scan_ver; struct brcmf_pno_param_v3_le pno_params; struct brcmf_pno_macaddr_le pfn_mac; @@ -346,12 +360,36 @@ void brcmf_feat_attach(struct brcmf_pub *drvr) brcmf_feat_iovar_int_get(ifp, BRCMF_FEAT_FWSUP, "sup_wpa"); + err = brcmf_fil_iovar_data_get(ifp, "join_ver", &join_ver, sizeof(join_ver)); + if (!err) { + u16 ver = le16_to_cpu(join_ver.join_ver_major); + brcmf_join_param_setup_for_version(drvr, ver); + } else { + /* Default to version 0, unless it is one of the firmware branches + * that doesn't have a join_ver iovar but are still version 1 */ + u8 version = 0; + struct brcmf_wlc_version_le ver; + err = brcmf_fil_iovar_data_get(ifp, "wlc_ver", &ver, sizeof(ver)); + if (!err) { + u16 major = le16_to_cpu(ver.wlc_ver_major); + u16 minor = le16_to_cpu(ver.wlc_ver_minor); + if (((major == MIN_JOINEXT_V1_BR1_FW_MAJOR) && + ((minor == MIN_JOINEXT_V1_BR1_FW_MINOR_2) || + (minor == MIN_JOINEXT_V1_BR1_FW_MINOR_4))) || + ((major == MIN_JOINEXT_V1_BR2_FW_MAJOR) && + (minor >= MIN_JOINEXT_V1_BR2_FW_MINOR)) || + (major >= MIN_JOINEXT_V1_FW_MAJOR)) { + version = 1; + } + } + brcmf_join_param_setup_for_version(drvr, version); + } err = brcmf_fil_iovar_data_get(ifp, "scan_ver", &scan_ver, sizeof(scan_ver)); if (!err) { u16 ver = le16_to_cpu(scan_ver.scan_ver_major); brcmf_scan_param_setup_for_version(drvr, ver); } else { - /* Default tp version 1. */ + /* Default to version 1. */ brcmf_scan_param_setup_for_version(drvr, 1); } diff --git a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/fwil_types.h b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/fwil_types.h index e4b3b13a8ff92c..14d91e7749e82d 100644 --- a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/fwil_types.h +++ b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/fwil_types.h @@ -590,11 +590,67 @@ struct brcmf_escan_result_le { struct brcmf_assoc_params_le { /* 00:00:00:00:00:00: broadcast scan */ u8 bssid[ETH_ALEN]; + /* 0: use chanspec_num, and the single bssid, + * otherwise count of chanspecs in chanspec_list + * AND paired bssids following chanspec_list + * also, chanspec_num has to be set to zero + * for bssid list to be used + */ + __le16 bssid_cnt; + /* 0: all available channels, otherwise count of chanspecs in + * chanspec_list */ + __le32 chanspec_num; + /* list of chanspecs */ + __le16 chanspec_list[]; +}; + +struct brcmf_assoc_params_v1_le { + __le16 version; + __le16 flags; + /* 00:00:00:00:00:00: broadcast scan */ + u8 bssid[ETH_ALEN]; + /* 0: use chanspec_num, and the single bssid, + * otherwise count of chanspecs in chanspec_list + * AND paired bssids following chanspec_list + * also, chanspec_num has to be set to zero + * for bssid list to be used + */ + __le16 bssid_cnt; + /* 0: all available channels, otherwise count of chanspecs in + * chanspec_list */ + __le32 chanspec_num; + /* list of chanspecs */ + __le16 chanspec_list[]; +}; + +/* ML assoc and scan params */ +struct brcmf_ml_assoc_scan_params_v1_le { + /* whether to follow strictly ordered assoc ? */ + u8 ml_assoc_mode; + /* to identify whether ml scan needs to be triggered */ + u8 ml_scan_mode; + u8 pad[2]; +}; + +struct brcmf_assoc_params_v2_le { + __le16 version; + __le16 flags; + /* 00:00:00:00:00:00: broadcast scan */ + u8 bssid[ETH_ALEN]; + /* 0: use chanspec_num, and the single bssid, + * otherwise count of chanspecs in chanspec_list + * AND paired bssids following chanspec_list + * also, chanspec_num has to be set to zero + * for bssid list to be used + */ + __le16 bssid_cnt; + /* Multilink association and scan params */ + struct brcmf_ml_assoc_scan_params_v1_le ml_assoc_scan_params; /* 0: all available channels, otherwise count of chanspecs in * chanspec_list */ __le32 chanspec_num; /* list of chanspecs */ - __le16 chanspec_list[1]; + __le16 chanspec_list[]; }; /** @@ -619,9 +675,19 @@ struct brcmf_join_params { struct brcmf_assoc_params_le params_le; }; +struct brcmf_join_params_v1 { + struct brcmf_ssid_le ssid_le; + struct brcmf_assoc_params_v1_le params_le; +}; +struct brcmf_join_params_v2 { + struct brcmf_ssid_le ssid_le; + struct brcmf_assoc_params_v2_le params_le; +}; + /* scan params for extended join */ struct brcmf_join_scan_params_le { u8 scan_type; /* 0 use default, active or passive scan */ + u8 PAD[3]; __le32 nprobes; /* -1 use default, nr of probes per channel */ __le32 active_time; /* -1 use default, dwell time per channel for * active scanning @@ -634,6 +700,23 @@ struct brcmf_join_scan_params_le { */ }; +/* scan params for extended join */ +struct brcmf_join_scan_params_v1_le { + u8 scan_type; /* 0 use default, active or passive scan */ + u8 ml_scan_mode; /* 0 scan ML channels in RNR, 1 scan only provided channels */ + u8 PAD[2]; + __le32 nprobes; /* -1 use default, nr of probes per channel */ + __le32 active_time; /* -1 use default, dwell time per channel for + * active scanning + */ + __le32 passive_time; /* -1 use default, dwell time per channel + * for passive scanning + */ + __le32 home_time; /* -1 use default, dwell time for the home + * channel between channel scans + */ +}; + /* extended join params */ struct brcmf_ext_join_params_le { struct brcmf_ssid_le ssid_le; /* {0, ""}: wildcard scan */ @@ -641,6 +724,24 @@ struct brcmf_ext_join_params_le { struct brcmf_assoc_params_le assoc_le; }; +/* extended join params */ +struct brcmf_ext_join_params_v1_le { + __le16 version; + u16 pad; + struct brcmf_ssid_le ssid_le; /* {0, ""}: wildcard scan */ + struct brcmf_join_scan_params_le scan_le; + struct brcmf_assoc_params_v1_le assoc_le; +}; + +/* extended join params v2 */ +struct brcmf_ext_join_params_v2_le { + __le16 version; + u16 pad; + struct brcmf_ssid_le ssid_le; /* {0, ""}: wildcard scan */ + struct brcmf_join_scan_params_v1_le scan_le; + struct brcmf_assoc_params_v2_le assoc_le; +}; + struct brcmf_wsec_key { u32 index; /* key index */ u32 len; /* key length */ @@ -946,7 +1047,20 @@ struct brcmf_wlc_version_le { }; /** - * struct brcmf_wl_scan_version_le - scan interface version + * struct brcmf_join_version_le - join interface version + */ +struct brcmf_join_version_le { + __le16 version; /**< version of the structure */ + __le16 length; /**< length of the entire structure */ + + /* join interface version numbers */ + __le16 join_ver_major; /**< join interface major version number */ + u8 pad[2]; +}; +#define BRCMF_JOIN_VERSION_VERSION 1 + +/** + * struct brcmf_scan_version_le - scan interface version */ struct brcmf_scan_version_le { __le16 version; diff --git a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/join_param.c b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/join_param.c new file mode 100644 index 00000000000000..4f026571c7e7eb --- /dev/null +++ b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/join_param.c @@ -0,0 +1,288 @@ +// SPDX-License-Identifier: ISC +/* + * Copyright (c) 2023 Daniel Berlin + */ +#include +#include + +#include "core.h" +#include "debug.h" +#include "fwil_types.h" +#include "cfg80211.h" +#include "join_param.h" + +/* These defaults are the same as found in the DHD drivers, and represent + * reasonable defaults for various scan dwell and probe times. */ +#define BRCMF_SCAN_JOIN_ACTIVE_DWELL_TIME_MS 320 +#define BRCMF_SCAN_JOIN_PASSIVE_DWELL_TIME_MS 400 +#define BRCMF_SCAN_JOIN_PROBE_INTERVAL_MS 20 + +/* Most of the actual structure fields we fill in are the same for various versions + * However, due to various incompatible changes and variants, the fields are not always + * in the same place. + * This makes for code duplication, so we try to commonize setting fields where it makes sense. + */ + +static void brcmf_joinscan_set_ssid(struct brcmf_ssid_le *ssid_le, + const u8 *ssid, u32 ssid_len) +{ + ssid_len = min_t(u32, ssid_len, IEEE80211_MAX_SSID_LEN); + ssid_le->SSID_len = cpu_to_le32(ssid_len); + memcpy(ssid_le->SSID, ssid, ssid_len); +} + +static void brcmf_joinscan_set_bssid(u8 out_bssid[6], const u8 *in_bssid) +{ + if (in_bssid) { + memcpy(out_bssid, in_bssid, ETH_ALEN); + } else { + eth_broadcast_addr(out_bssid); + } +} + +/* Create a single channel chanspec list from a wireless stack channel */ +static void brcmf_joinscan_set_single_chanspec_from_channel( + struct brcmf_cfg80211_info *cfg, struct ieee80211_channel *chan, + __le32 *chanspec_count, __le16 (*chanspec_list)[]) +{ + u16 chanspec = channel_to_chanspec(&cfg->d11inf, chan); + *chanspec_count = cpu_to_le32(1); + (*chanspec_list)[0] = cpu_to_le16(chanspec); +} + +/* Create a single channel chanspec list from a wireless stack chandef */ +static void brcmf_joinscan_set_single_chanspec_from_chandef( + struct brcmf_cfg80211_info *cfg, struct cfg80211_chan_def *chandef, + __le32 *chanspec_count, __le16 (*chanspec_list)[]) +{ + u16 chanspec = chandef_to_chanspec(&cfg->d11inf, chandef); + *chanspec_count = cpu_to_le32(1); + (*chanspec_list)[0] = cpu_to_le16(chanspec); +} + +static void *brcmf_get_struct_for_ibss_v0(struct brcmf_cfg80211_info *cfg, + u32 *struct_size, + struct cfg80211_ibss_params *params) +{ + struct brcmf_join_params *join_params; + + u32 join_params_size = struct_size(join_params, params_le.chanspec_list, + params->chandef.chan != NULL); + + *struct_size = join_params_size; + join_params = kzalloc(join_params_size, GFP_KERNEL); + if (!join_params) { + bphy_err(cfg, "Unable to allocate memory for join params\n"); + return NULL; + } + brcmf_joinscan_set_ssid(&join_params->ssid_le, params->ssid, + params->ssid_len); + brcmf_joinscan_set_bssid(join_params->params_le.bssid, params->bssid); + /* Channel */ + if (cfg->channel) { + brcmf_joinscan_set_single_chanspec_from_chandef( + cfg, ¶ms->chandef, + &join_params->params_le.chanspec_num, + &join_params->params_le.chanspec_list); + } + return join_params; +} + +static void * +brcmf_get_prepped_struct_for_ibss_v1(struct brcmf_cfg80211_info *cfg, + u32 *struct_size, + struct cfg80211_ibss_params *params) +{ + struct brcmf_join_params_v1 *join_params; + u32 join_params_size = struct_size(join_params, params_le.chanspec_list, + params->chandef.chan != NULL); + + *struct_size = join_params_size; + join_params = kzalloc(join_params_size, GFP_KERNEL); + if (!join_params) { + bphy_err(cfg, "Unable to allocate memory for join params\n"); + return NULL; + } + join_params->params_le.version = cpu_to_le16(1); + brcmf_joinscan_set_ssid(&join_params->ssid_le, params->ssid, + params->ssid_len); + brcmf_joinscan_set_bssid(join_params->params_le.bssid, params->bssid); + /* Channel */ + if (cfg->channel) { + brcmf_joinscan_set_single_chanspec_from_chandef( + cfg, ¶ms->chandef, + &join_params->params_le.chanspec_num, + &join_params->params_le.chanspec_list); + } + return join_params; +} + +static void +brcmf_joinscan_set_common_v0v1_params(struct brcmf_join_scan_params_le *scan_le, + bool have_channel) +{ + /* Set up join scan parameters */ + scan_le->scan_type = 0; + scan_le->home_time = cpu_to_le32(-1); + + if (have_channel) { + /* Increase dwell time to receive probe response or detect + * beacon from target AP at a noisy air only during connect + * command. + */ + scan_le->active_time = + cpu_to_le32(BRCMF_SCAN_JOIN_ACTIVE_DWELL_TIME_MS); + scan_le->passive_time = + cpu_to_le32(BRCMF_SCAN_JOIN_PASSIVE_DWELL_TIME_MS); + /* To sync with presence period of VSDB GO send probe request + * more frequently. Probe request will be stopped when it gets + * probe response from target AP/GO. + */ + scan_le->nprobes = + cpu_to_le32(BRCMF_SCAN_JOIN_ACTIVE_DWELL_TIME_MS / + BRCMF_SCAN_JOIN_PROBE_INTERVAL_MS); + } else { + scan_le->active_time = cpu_to_le32(-1); + scan_le->passive_time = cpu_to_le32(-1); + scan_le->nprobes = cpu_to_le32(-1); + } +} +static void * +brcmf_get_struct_for_connect_v0(struct brcmf_cfg80211_info *cfg, + u32 *struct_size, + struct cfg80211_connect_params *params) +{ + struct brcmf_ext_join_params_le *ext_v0; + u32 join_params_size = + struct_size(ext_v0, assoc_le.chanspec_list, cfg->channel != 0); + + *struct_size = join_params_size; + ext_v0 = kzalloc(join_params_size, GFP_KERNEL); + if (!ext_v0) { + bphy_err( + cfg, + "Could not allocate memory for extended join parameters\n"); + return NULL; + } + brcmf_joinscan_set_ssid(&ext_v0->ssid_le, params->ssid, + params->ssid_len); + brcmf_joinscan_set_common_v0v1_params(&ext_v0->scan_le, + cfg->channel != 0); + brcmf_joinscan_set_bssid(ext_v0->assoc_le.bssid, params->bssid); + if (cfg->channel) { + struct ieee80211_channel *chan = params->channel_hint ? + params->channel_hint : + params->channel; + brcmf_joinscan_set_single_chanspec_from_channel( + cfg, chan, &ext_v0->assoc_le.chanspec_num, + &ext_v0->assoc_le.chanspec_list); + } + return ext_v0; +} + +static void * +brcmf_get_struct_for_connect_v1(struct brcmf_cfg80211_info *cfg, + u32 *struct_size, + struct cfg80211_connect_params *params) +{ + struct brcmf_ext_join_params_v1_le *ext_v1; + u32 join_params_size = + struct_size(ext_v1, assoc_le.chanspec_list, cfg->channel != 0); + + *struct_size = join_params_size; + ext_v1 = kzalloc(join_params_size, GFP_KERNEL); + if (!ext_v1) { + bphy_err( + cfg, + "Could not allocate memory for extended join parameters\n"); + return NULL; + } + ext_v1->version = cpu_to_le16(1); + ext_v1->assoc_le.version = cpu_to_le16(1); + brcmf_joinscan_set_ssid(&ext_v1->ssid_le, params->ssid, + params->ssid_len); + brcmf_joinscan_set_common_v0v1_params(&ext_v1->scan_le, + cfg->channel != 0); + brcmf_joinscan_set_bssid(ext_v1->assoc_le.bssid, params->bssid); + if (cfg->channel) { + struct ieee80211_channel *chan = params->channel_hint ? + params->channel_hint : + params->channel; + brcmf_joinscan_set_single_chanspec_from_channel( + cfg, chan, &ext_v1->assoc_le.chanspec_num, + &ext_v1->assoc_le.chanspec_list); + } + return ext_v1; +} + +static void *brcmf_get_join_from_ext_join_v0(void *ext_join, u32 *struct_size) +{ + struct brcmf_ext_join_params_le *ext_join_v0 = + (struct brcmf_ext_join_params_le *)ext_join; + u32 chanspec_num = le32_to_cpu(ext_join_v0->assoc_le.chanspec_num); + struct brcmf_join_params *join_params; + u32 join_params_size = + struct_size(join_params, params_le.chanspec_list, chanspec_num); + u32 assoc_size = struct_size_t(struct brcmf_assoc_params_le, + chanspec_list, chanspec_num); + + *struct_size = join_params_size; + join_params = kzalloc(join_params_size, GFP_KERNEL); + if (!join_params) { + return NULL; + } + memcpy(&join_params->ssid_le, &ext_join_v0->ssid_le, + sizeof(ext_join_v0->ssid_le)); + memcpy(&join_params->params_le, &ext_join_v0->assoc_le, assoc_size); + + return join_params; +} + +static void *brcmf_get_join_from_ext_join_v1(void *ext_join, u32 *struct_size) +{ + struct brcmf_ext_join_params_v1_le *ext_join_v1 = + (struct brcmf_ext_join_params_v1_le *)ext_join; + u32 chanspec_num = le32_to_cpu(ext_join_v1->assoc_le.chanspec_num); + struct brcmf_join_params_v1 *join_params; + u32 join_params_size = + struct_size(join_params, params_le.chanspec_list, chanspec_num); + u32 assoc_size = struct_size_t(struct brcmf_assoc_params_le, + chanspec_list, chanspec_num); + + *struct_size = join_params_size; + join_params = kzalloc(join_params_size, GFP_KERNEL); + if (!join_params) { + return NULL; + } + memcpy(&join_params->ssid_le, &ext_join_v1->ssid_le, + sizeof(ext_join_v1->ssid_le)); + memcpy(&join_params->params_le, &ext_join_v1->assoc_le, assoc_size); + + return join_params; +} + +int brcmf_join_param_setup_for_version(struct brcmf_pub *drvr, u8 version) +{ + drvr->join_param_handler.version = version; + switch (version) { + case 0: + drvr->join_param_handler.get_struct_for_ibss = + brcmf_get_struct_for_ibss_v0; + drvr->join_param_handler.get_struct_for_connect = + brcmf_get_struct_for_connect_v0; + drvr->join_param_handler.get_join_from_ext_join = + brcmf_get_join_from_ext_join_v0; + break; + case 1: + drvr->join_param_handler.get_struct_for_ibss = + brcmf_get_prepped_struct_for_ibss_v1; + drvr->join_param_handler.get_struct_for_connect = + brcmf_get_struct_for_connect_v1; + drvr->join_param_handler.get_join_from_ext_join = + brcmf_get_join_from_ext_join_v1; + break; + default: + return -EINVAL; + } + return 0; +} diff --git a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/join_param.h b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/join_param.h new file mode 100644 index 00000000000000..f549fe2a740823 --- /dev/null +++ b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/join_param.h @@ -0,0 +1,22 @@ +// SPDX-License-Identifier: ISC +/* + * Copyright (c) 2023 Daniel Berlin + */ + +#ifndef _BRCMF_JOIN_PARAM_H +#define _BRCMF_JOIN_PARAM_H + +struct brcmf_pub; + +/** + * brcmf_join_param_setup_for_version() - Setup the driver to handle join structures + * + * There are a number of different structures and interface versions for join/extended join parameters + * This sets up the driver to handle a particular interface version. + * + * @drvr Driver structure to setup + * @ver Interface version + * Return: %0 if okay, error code otherwise + */ +int brcmf_join_param_setup_for_version(struct brcmf_pub *drvr, u8 ver); +#endif /* _BRCMF_JOIN_PARAM_H */ diff --git a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/scan_param.c b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/scan_param.c index 6bd5f6d1616c04..4f634509d25256 100644 --- a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/scan_param.c +++ b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/scan_param.c @@ -423,19 +423,19 @@ int brcmf_scan_param_setup_for_version(struct brcmf_pub *drvr, u8 version) drvr->scan_param_handler.version = version; switch (version) { case 1: { - drvr->scan_param_handler.get_prepped_struct = + drvr->scan_param_handler.get_struct_for_request = brcmf_scan_param_get_prepped_struct_v1; } break; case 2: { - drvr->scan_param_handler.get_prepped_struct = + drvr->scan_param_handler.get_struct_for_request = brcmf_scan_param_get_prepped_struct_v2; } break; case 3: { - drvr->scan_param_handler.get_prepped_struct = + drvr->scan_param_handler.get_struct_for_request = brcmf_scan_param_get_prepped_struct_v3; } break; case 4: { - drvr->scan_param_handler.get_prepped_struct = + drvr->scan_param_handler.get_struct_for_request = brcmf_scan_param_get_prepped_struct_v4; } break; From fd3c4486e1fb61ad821a0f7e217f83883504114c Mon Sep 17 00:00:00 2001 From: Daniel Berlin Date: Mon, 30 Oct 2023 21:17:22 -0400 Subject: [PATCH 0238/3902] [brcmfmac] Let feature attachment fail, and fail if we can't handle the interface versions we find. Signed-off-by: Daniel Berlin --- .../broadcom/brcm80211/brcmfmac/core.c | 4 +- .../broadcom/brcm80211/brcmfmac/feature.c | 39 +++++++++++++------ .../broadcom/brcm80211/brcmfmac/feature.h | 4 +- 3 files changed, 34 insertions(+), 13 deletions(-) diff --git a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/core.c b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/core.c index 260daa64c1cd58..3ef91663863da8 100644 --- a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/core.c +++ b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/core.c @@ -1227,7 +1227,9 @@ static int brcmf_bus_started(struct brcmf_pub *drvr, struct cfg80211_ops *ops) if (ret < 0) goto fail; - brcmf_feat_attach(drvr); + ret = brcmf_feat_attach(drvr); + if (ret) + goto fail; /* Setup event_msgs, enable E_IF */ ret = brcmf_fweh_init_events(ifp); diff --git a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/feature.c b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/feature.c index 4b438758d03d83..d823ced048454a 100644 --- a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/feature.c +++ b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/feature.c @@ -302,7 +302,7 @@ static int brcmf_feat_fwcap_debugfs_read(struct seq_file *seq, void *data) return 0; } -void brcmf_feat_attach(struct brcmf_pub *drvr) +int brcmf_feat_attach(struct brcmf_pub *drvr) { struct brcmf_if *ifp = brcmf_get_ifp(drvr, 0); struct brcmf_join_version_le join_ver; @@ -363,13 +363,14 @@ void brcmf_feat_attach(struct brcmf_pub *drvr) err = brcmf_fil_iovar_data_get(ifp, "join_ver", &join_ver, sizeof(join_ver)); if (!err) { u16 ver = le16_to_cpu(join_ver.join_ver_major); - brcmf_join_param_setup_for_version(drvr, ver); + err = brcmf_join_param_setup_for_version(drvr, ver); } else { /* Default to version 0, unless it is one of the firmware branches * that doesn't have a join_ver iovar but are still version 1 */ u8 version = 0; struct brcmf_wlc_version_le ver; - err = brcmf_fil_iovar_data_get(ifp, "wlc_ver", &ver, sizeof(ver)); + err = brcmf_fil_iovar_data_get(ifp, "wlc_ver", &ver, + sizeof(ver)); if (!err) { u16 major = le16_to_cpu(ver.wlc_ver_major); u16 minor = le16_to_cpu(ver.wlc_ver_minor); @@ -382,32 +383,47 @@ void brcmf_feat_attach(struct brcmf_pub *drvr) version = 1; } } - brcmf_join_param_setup_for_version(drvr, version); + err = brcmf_join_param_setup_for_version(drvr, version); } - err = brcmf_fil_iovar_data_get(ifp, "scan_ver", &scan_ver, sizeof(scan_ver)); + if (err) { + bphy_err(drvr, "Error setting up join structure handler: %d\n", + err); + return err; + } + err = brcmf_fil_iovar_data_get(ifp, "scan_ver", &scan_ver, + sizeof(scan_ver)); if (!err) { u16 ver = le16_to_cpu(scan_ver.scan_ver_major); - brcmf_scan_param_setup_for_version(drvr, ver); + err = brcmf_scan_param_setup_for_version(drvr, ver); } else { /* Default to version 1. */ - brcmf_scan_param_setup_for_version(drvr, 1); + err = brcmf_scan_param_setup_for_version(drvr, 1); + } + if (err) { + bphy_err(drvr, "Error setting up scan structure handler: %d\n", + err); + return err; } - /* See what version of PFN scan is supported*/ err = brcmf_fil_iovar_data_get(ifp, "pno_set", &pno_params, sizeof(pno_params)); if (!err) { - brcmf_pno_setup_for_version(drvr, le16_to_cpu(pno_params.version)); + err = brcmf_pno_setup_for_version( + drvr, le16_to_cpu(pno_params.version)); } else { /* Default to version 2, supported by all chips we support. */ - brcmf_pno_setup_for_version(drvr, 2); + err = brcmf_pno_setup_for_version(drvr, 2); + } + if (err) { + bphy_err(drvr, "Error setting up escan structure handler: %d\n", + err); + return err; } brcmf_feat_wlc_version_overrides(drvr); brcmf_feat_firmware_overrides(drvr); brcmf_fwvid_feat_attach(ifp); - if (drvr->settings->feature_disable) { brcmf_dbg(INFO, "Features: 0x%02x, disable: 0x%02x\n", ifp->drvr->feat_flags, @@ -427,6 +443,7 @@ void brcmf_feat_attach(struct brcmf_pub *drvr) /* no quirks */ break; } + return 0; } void brcmf_feat_debugfs_create(struct brcmf_pub *drvr) diff --git a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/feature.h b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/feature.h index 4088141508a035..be271ca0fca588 100644 --- a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/feature.h +++ b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/feature.h @@ -102,8 +102,10 @@ enum brcmf_feat_quirk { * brcmf_feat_attach() - determine features and quirks. * * @drvr: driver instance. + * + * Return: 0 in case of success, error code otherwise. */ -void brcmf_feat_attach(struct brcmf_pub *drvr); +int brcmf_feat_attach(struct brcmf_pub *drvr); /** * brcmf_feat_debugfs_create() - create debugfs entries. From 778e56231fbf818d3e65e6cb6251cc9a994cf0df Mon Sep 17 00:00:00 2001 From: Daniel Berlin Date: Tue, 17 Oct 2023 20:36:07 -0400 Subject: [PATCH 0239/3902] [brcmfmac] Add support for more auth suites in roaming offload This adds support for more authentication types during roaming offload, enabling the firmware to handle roaming for ~all authentication types. Signed-off-by: Daniel Berlin --- .../broadcom/brcm80211/brcmfmac/cfg80211.c | 194 ++++++++++++++++-- .../broadcom/brcm80211/brcmfmac/cfg80211.h | 4 +- .../broadcom/brcm80211/include/brcmu_wifi.h | 7 + 3 files changed, 187 insertions(+), 18 deletions(-) diff --git a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/cfg80211.c b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/cfg80211.c index da882306cf9ca6..f9c1a74ac44703 100644 --- a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/cfg80211.c +++ b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/cfg80211.c @@ -66,6 +66,8 @@ #define RSN_CAP_MFPR_MASK BIT(6) #define RSN_CAP_MFPC_MASK BIT(7) #define RSN_PMKID_COUNT_LEN 2 +#define DPP_AKM_SUITE_TYPE 2 +#define WLAN_AKM_SUITE_DPP SUITE(WLAN_OUI_WFA, DPP_AKM_SUITE_TYPE) #define VNDR_IE_CMD_LEN 4 /* length of the set command * string :"add", "del" (+ NUL) @@ -1860,6 +1862,10 @@ static s32 brcmf_set_wpa_version(struct net_device *ndev, if (drvr->bus_if->fwvid == BRCMF_FWVENDOR_CYW && sme->crypto.akm_suites[0] == WLAN_AKM_SUITE_SAE) val = WPA3_AUTH_SAE_PSK; + else if (sme->crypto.akm_suites[0] == WLAN_AKM_SUITE_SAE) + val = WPA3_AUTH_SAE_PSK; + else if (sme->crypto.akm_suites[0] == WLAN_AKM_SUITE_OWE) + val = WPA3_AUTH_OWE; else val = WPA2_AUTH_PSK | WPA2_AUTH_UNSPECIFIED; } else if (sme->crypto.wpa_versions & NL80211_WPA_VERSION_3) { @@ -2081,9 +2087,13 @@ brcmf_set_key_mgmt(struct net_device *ndev, struct cfg80211_connect_params *sme) u16 rsn_cap; u32 mfp; u16 count; + s32 okc_enable; + u16 pmkid_count; + const u8 *group_mgmt_cs = NULL; profile->use_fwsup = BRCMF_PROFILE_FWSUP_NONE; profile->is_ft = false; + profile->is_okc = false; if (!sme->crypto.n_akm_suites) return 0; @@ -2100,13 +2110,15 @@ brcmf_set_key_mgmt(struct net_device *ndev, struct cfg80211_connect_params *sme) val = WPA_AUTH_UNSPECIFIED; if (sme->want_1x) profile->use_fwsup = BRCMF_PROFILE_FWSUP_1X; + else + profile->use_fwsup = BRCMF_PROFILE_FWSUP_ROAM; break; case WLAN_AKM_SUITE_PSK: val = WPA_AUTH_PSK; break; default: - bphy_err(drvr, "invalid akm suite (%d)\n", - sme->crypto.akm_suites[0]); + bphy_err(drvr, "invalid cipher group (%d)\n", + sme->crypto.cipher_group); return -EINVAL; } } else if (val & (WPA2_AUTH_PSK | WPA2_AUTH_UNSPECIFIED)) { @@ -2115,11 +2127,15 @@ brcmf_set_key_mgmt(struct net_device *ndev, struct cfg80211_connect_params *sme) val = WPA2_AUTH_UNSPECIFIED; if (sme->want_1x) profile->use_fwsup = BRCMF_PROFILE_FWSUP_1X; + else + profile->use_fwsup = BRCMF_PROFILE_FWSUP_ROAM; break; case WLAN_AKM_SUITE_8021X_SHA256: val = WPA2_AUTH_1X_SHA256; if (sme->want_1x) profile->use_fwsup = BRCMF_PROFILE_FWSUP_1X; + else + profile->use_fwsup = BRCMF_PROFILE_FWSUP_ROAM; break; case WLAN_AKM_SUITE_PSK_SHA256: val = WPA2_AUTH_PSK_SHA256; @@ -2132,14 +2148,35 @@ brcmf_set_key_mgmt(struct net_device *ndev, struct cfg80211_connect_params *sme) profile->is_ft = true; if (sme->want_1x) profile->use_fwsup = BRCMF_PROFILE_FWSUP_1X; + else + profile->use_fwsup = BRCMF_PROFILE_FWSUP_ROAM; break; case WLAN_AKM_SUITE_FT_PSK: val = WPA2_AUTH_PSK | WPA2_AUTH_FT; profile->is_ft = true; + if (brcmf_feat_is_enabled(ifp, BRCMF_FEAT_FWSUP)) + profile->use_fwsup = BRCMF_PROFILE_FWSUP_PSK; + else + profile->use_fwsup = BRCMF_PROFILE_FWSUP_ROAM; + break; + case WLAN_AKM_SUITE_DPP: + val = WFA_AUTH_DPP; + profile->use_fwsup = BRCMF_PROFILE_FWSUP_NONE; + break; + case WLAN_AKM_SUITE_OWE: + val = WPA3_AUTH_OWE; + profile->use_fwsup = BRCMF_PROFILE_FWSUP_ROAM; + break; + case WLAN_AKM_SUITE_8021X_SUITE_B_192: + val = WPA3_AUTH_1X_SUITE_B_SHA384; + if (sme->want_1x) + profile->use_fwsup = BRCMF_PROFILE_FWSUP_1X; + else + profile->use_fwsup = BRCMF_PROFILE_FWSUP_ROAM; break; default: - bphy_err(drvr, "invalid akm suite (%d)\n", - sme->crypto.akm_suites[0]); + bphy_err(drvr, "invalid cipher group (%d)\n", + sme->crypto.cipher_group); return -EINVAL; } } else if (val & WPA3_AUTH_SAE_PSK) { @@ -2152,17 +2189,38 @@ brcmf_set_key_mgmt(struct net_device *ndev, struct cfg80211_connect_params *sme) profile->is_ft = true; break; default: - bphy_err(drvr, "invalid akm suite (%d)\n", - sme->crypto.akm_suites[0]); + bphy_err(drvr, "invalid cipher group (%d)\n", + sme->crypto.cipher_group); return -EINVAL; } if (sme->crypto.sae_pwd) { profile->use_fwsup = BRCMF_PROFILE_FWSUP_SAE; } } - - if (profile->use_fwsup == BRCMF_PROFILE_FWSUP_1X) + if ((profile->use_fwsup == BRCMF_PROFILE_FWSUP_1X) || + (profile->use_fwsup == BRCMF_PROFILE_FWSUP_ROAM)) { brcmf_dbg(INFO, "using 1X offload\n"); + err = brcmf_fil_bsscfg_int_get(netdev_priv(ndev), "okc_enable", + &okc_enable); + if (err) { + bphy_err(drvr, "get okc_enable failed (%d)\n", err); + } else { + brcmf_dbg(INFO, "get okc_enable (%d)\n", okc_enable); + profile->is_okc = okc_enable; + } + } else if (profile->use_fwsup != BRCMF_PROFILE_FWSUP_SAE && + (val == WPA3_AUTH_SAE_PSK)) { + brcmf_dbg(INFO, "not using SAE offload\n"); + err = brcmf_fil_bsscfg_int_get(netdev_priv(ndev), "okc_enable", + &okc_enable); + if (err) { + bphy_err(drvr, "get okc_enable failed (%d)\n", err); + } else { + brcmf_dbg(INFO, "get okc_enable (%d)\n", okc_enable); + profile->is_okc = okc_enable; + } + } + if (profile->use_fwsup == BRCMF_PROFILE_FWSUP_SAE) brcmf_dbg(INFO, "using SAE offload\n"); @@ -2198,14 +2256,47 @@ brcmf_set_key_mgmt(struct net_device *ndev, struct cfg80211_connect_params *sme) mfp = BRCMF_MFP_REQUIRED; else if (rsn_cap & RSN_CAP_MFPC_MASK) mfp = BRCMF_MFP_CAPABLE; + /* In case of dpp, very low tput is observed if MFPC is set in + * firmmare. Firmware needs to ensure that MFPC is not set when + * MFPR was requested from fmac. However since this change being + * specific to DPP, fmac needs to set wpa_auth prior to mfp, so + * that firmware can use this info to prevent MFPC being set in + * case of dpp. + */ + if (val == WFA_AUTH_DPP) { + brcmf_dbg(CONN, "setting wpa_auth to 0x%0x\n", val); + err = brcmf_fil_bsscfg_int_set(netdev_priv(ndev), "wpa_auth", + val); + if (err) { + bphy_err(drvr, "could not set wpa_auth (%d)\n", err); + return err; + } + } + brcmf_fil_bsscfg_int_set(netdev_priv(ndev), "mfp", mfp); + offset += RSN_CAP_LEN; + if (mfp && (ie_len - offset >= RSN_PMKID_COUNT_LEN)) { + pmkid_count = ie[offset] + (ie[offset + 1] << 8); + offset += RSN_PMKID_COUNT_LEN + (pmkid_count * WLAN_PMKID_LEN); + if (ie_len - offset >= WPA_IE_MIN_OUI_LEN) { + group_mgmt_cs = &ie[offset]; + if (memcmp(group_mgmt_cs, RSN_OUI, TLV_OUI_LEN) == 0) { + brcmf_fil_bsscfg_data_set(ifp, "bip", + (void *)group_mgmt_cs, + WPA_IE_MIN_OUI_LEN); + } + } + } skip_mfp_config: - brcmf_dbg(CONN, "setting wpa_auth to 0x%0x\n", val); - err = brcmf_fil_bsscfg_int_set(netdev_priv(ndev), "wpa_auth", val); - if (err) { - bphy_err(drvr, "could not set wpa_auth (%d)\n", err); - return err; + if (val != WFA_AUTH_DPP) { + brcmf_dbg(CONN, "setting wpa_auth to 0x%0x\n", val); + err = brcmf_fil_bsscfg_int_set(netdev_priv(ndev), "wpa_auth", + val); + if (err) { + bphy_err(drvr, "could not set wpa_auth (%d)\n", err); + return err; + } } return err; @@ -2456,6 +2547,18 @@ brcmf_cfg80211_connect(struct wiphy *wiphy, struct net_device *ndev, } } + if (profile->use_fwsup != BRCMF_PROFILE_FWSUP_NONE) { + /* enable firmware supplicant for this interface */ + err = brcmf_fil_iovar_int_set(ifp, "sup_wpa", 1); + if (err < 0) { + bphy_err(drvr, + "failed to enable fw supplicant\n"); + goto done; + } + } else { + err = brcmf_fil_iovar_int_set(ifp, "sup_wpa", 0); + } + if ((profile->use_fwsup == BRCMF_PROFILE_FWSUP_PSK) && params->crypto.psk) err = brcmf_set_pmk(ifp, params->crypto.psk, @@ -5910,17 +6013,29 @@ static int brcmf_cfg80211_set_pmk(struct wiphy *wiphy, struct net_device *dev, const struct cfg80211_pmk_conf *conf) { struct brcmf_if *ifp; - + struct brcmf_pub *drvr; + int ret; brcmf_dbg(TRACE, "enter\n"); /* expect using firmware supplicant for 1X */ ifp = netdev_priv(dev); - if (WARN_ON(ifp->vif->profile.use_fwsup != BRCMF_PROFILE_FWSUP_1X)) + drvr = ifp->drvr; + if (WARN_ON((ifp->vif->profile.use_fwsup != BRCMF_PROFILE_FWSUP_1X) && + (ifp->vif->profile.use_fwsup != BRCMF_PROFILE_FWSUP_ROAM) && + (ifp->vif->profile.is_ft != true) && + (ifp->vif->profile.is_okc != true))) return -EINVAL; if (conf->pmk_len > BRCMF_WSEC_MAX_PSK_LEN) return -ERANGE; + if (ifp->vif->profile.is_okc) { + ret = brcmf_fil_iovar_data_set(ifp, "okc_info_pmk", conf->pmk, + conf->pmk_len); + if (ret < 0) + bphy_err(drvr, "okc_info_pmk iovar failed: ret=%d\n", + ret); + } return brcmf_set_pmk(ifp, conf->pmk, conf->pmk_len); } @@ -6379,6 +6494,46 @@ static s32 brcmf_get_assoc_ies(struct brcmf_cfg80211_info *cfg, return err; } +static bool brcmf_has_pmkid(const u8 *parse, u32 len) +{ + const struct brcmf_tlv *rsn_ie; + const u8 *ie; + u32 ie_len; + u32 offset; + u16 count; + + rsn_ie = brcmf_parse_tlvs(parse, len, WLAN_EID_RSN); + if (!rsn_ie) + goto done; + ie = (const u8 *)rsn_ie; + ie_len = rsn_ie->len + TLV_HDR_LEN; + /* Skip group data cipher suite */ + offset = TLV_HDR_LEN + WPA_IE_VERSION_LEN + WPA_IE_MIN_OUI_LEN; + if (offset + WPA_IE_SUITE_COUNT_LEN >= ie_len) + goto done; + /* Skip pairwise cipher suite(s) */ + count = ie[offset] + (ie[offset + 1] << 8); + offset += WPA_IE_SUITE_COUNT_LEN + (count * WPA_IE_MIN_OUI_LEN); + if (offset + WPA_IE_SUITE_COUNT_LEN >= ie_len) + goto done; + /* Skip auth key management suite(s) */ + count = ie[offset] + (ie[offset + 1] << 8); + offset += WPA_IE_SUITE_COUNT_LEN + (count * WPA_IE_MIN_OUI_LEN); + if (offset + RSN_CAP_LEN >= ie_len) + goto done; + /* Skip rsn capabilities */ + offset += RSN_CAP_LEN; + if (offset + RSN_PMKID_COUNT_LEN > ie_len) + goto done; + /* Extract PMKID count */ + count = ie[offset] + (ie[offset + 1] << 8); + if (count) + return true; + +done: + return false; +} + static s32 brcmf_bss_roaming_done(struct brcmf_cfg80211_info *cfg, struct net_device *ndev, @@ -6449,11 +6604,16 @@ brcmf_bss_roaming_done(struct brcmf_cfg80211_info *cfg, cfg80211_roamed(ndev, &roam_info, GFP_KERNEL); brcmf_dbg(CONN, "Report roaming result\n"); - if (profile->use_fwsup == BRCMF_PROFILE_FWSUP_1X && profile->is_ft) { - cfg80211_port_authorized(ndev, profile->bssid, NULL, 0, GFP_KERNEL); + if (((profile->use_fwsup == BRCMF_PROFILE_FWSUP_1X || + profile->use_fwsup == BRCMF_PROFILE_FWSUP_ROAM) && + (brcmf_has_pmkid(roam_info.req_ie, roam_info.req_ie_len) || + profile->is_ft || profile->is_okc))) { + cfg80211_port_authorized(ndev, profile->bssid, NULL, 0, + GFP_KERNEL); brcmf_dbg(CONN, "Report port authorized\n"); } + clear_bit(BRCMF_VIF_STATUS_CONNECTING, &ifp->vif->sme_state); set_bit(BRCMF_VIF_STATUS_CONNECTED, &ifp->vif->sme_state); brcmf_dbg(TRACE, "Exit\n"); return err; diff --git a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/cfg80211.h b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/cfg80211.h index 732a4a87988035..11f651ab9f4228 100644 --- a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/cfg80211.h +++ b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/cfg80211.h @@ -128,7 +128,8 @@ enum brcmf_profile_fwsup { BRCMF_PROFILE_FWSUP_NONE, BRCMF_PROFILE_FWSUP_PSK, BRCMF_PROFILE_FWSUP_1X, - BRCMF_PROFILE_FWSUP_SAE + BRCMF_PROFILE_FWSUP_SAE, + BRCMF_PROFILE_FWSUP_ROAM }; /** @@ -173,6 +174,7 @@ struct brcmf_cfg80211_profile { enum brcmf_profile_fwsup use_fwsup; u16 use_fwauth; bool is_ft; + bool is_okc; }; /** diff --git a/drivers/net/wireless/broadcom/brcm80211/include/brcmu_wifi.h b/drivers/net/wireless/broadcom/brcm80211/include/brcmu_wifi.h index 0ab1b95318e581..ef042beeb586f9 100644 --- a/drivers/net/wireless/broadcom/brcm80211/include/brcmu_wifi.h +++ b/drivers/net/wireless/broadcom/brcm80211/include/brcmu_wifi.h @@ -254,6 +254,13 @@ static inline bool ac_bitmap_tst(u8 bitmap, int prec) #define WPA2_AUTH_PSK_SHA256 0x8000 /* PSK with SHA256 key derivation */ #define WPA3_AUTH_SAE_PSK 0x40000 /* SAE with 4-way handshake */ +#define WPA3_AUTH_OWE 0x100000 /* OWE */ +#define WFA_AUTH_DPP 0x200000 /* WFA DPP AUTH */ +#define WPA3_AUTH_1X_SUITE_B_SHA384 0x400000 /* Suite B-192 SHA384 */ + + +#define WFA_OUI "\x50\x6F\x9A" /* WFA OUI */ +#define DPP_VER 0x1A /* WFA DPP v1.0 */ #define DOT11_DEFAULT_RTS_LEN 2347 #define DOT11_DEFAULT_FRAG_LEN 2346 From 289c740b427decaacffec5d189562fb4a27ba577 Mon Sep 17 00:00:00 2001 From: Daniel Berlin Date: Sun, 29 Oct 2023 16:22:24 -0400 Subject: [PATCH 0240/3902] [brcmfmac] Set chanspec during join. Signed-off-by: Daniel Berlin --- drivers/net/wireless/broadcom/brcm80211/brcmfmac/cfg80211.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/cfg80211.c b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/cfg80211.c index f9c1a74ac44703..9ab9c70c2d5233 100644 --- a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/cfg80211.c +++ b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/cfg80211.c @@ -1795,9 +1795,8 @@ static s32 brcmf_cfg80211_join_ibss(struct wiphy *wiphy, /* set chanspec */ err = brcmf_fil_iovar_int_set(ifp, "chanspec", chanspec); - if (err) { - bphy_err(drvr, "WLC_SET_CHANNEL failed (%d)\n", err); + bphy_err(drvr, "Setting chanspec failed (%d)\n", err); goto done; } } From dc1782086efc8aef94e742782bfe21895c7a1098 Mon Sep 17 00:00:00 2001 From: Daniel Berlin Date: Tue, 31 Oct 2023 00:06:22 -0400 Subject: [PATCH 0241/3902] [brcmfmac] Add support for more rate info in station dumps We try to retrieve a newer sta_info structure that has both rx and tx ratespecs, but if we don't get the structure we are expecting we fall back to tx rate info only. Signed-off-by: Daniel Berlin --- .../broadcom/brcm80211/brcmfmac/cfg80211.c | 93 ++++++- .../broadcom/brcm80211/brcmfmac/fwil_types.h | 12 + .../broadcom/brcm80211/brcmfmac/ratespec.h | 252 ++++++++++++++++++ 3 files changed, 355 insertions(+), 2 deletions(-) create mode 100644 drivers/net/wireless/broadcom/brcm80211/brcmfmac/ratespec.h diff --git a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/cfg80211.c b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/cfg80211.c index 9ab9c70c2d5233..afb8aff348c6e2 100644 --- a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/cfg80211.c +++ b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/cfg80211.c @@ -35,6 +35,7 @@ #include "feature.h" #include "fwvid.h" #include "xtlv.h" +#include "ratespec.h" #define BRCMF_SCAN_IE_LEN_MAX 2048 @@ -3183,6 +3184,70 @@ brcmf_cfg80211_get_station_ibss(struct brcmf_if *ifp, return 0; } +static void brcmf_convert_ratespec_to_rateinfo(u32 ratespec, + struct rate_info *rateinfo) +{ + /* First extract the bandwidth info */ + switch (ratespec & BRCMF_RSPEC_BW_MASK) { + case BRCMF_RSPEC_BW_20MHZ: + rateinfo->bw = RATE_INFO_BW_20; + break; + case BRCMF_RSPEC_BW_40MHZ: + rateinfo->bw = RATE_INFO_BW_40; + break; + case BRCMF_RSPEC_BW_80MHZ: + rateinfo->bw = RATE_INFO_BW_80; + break; + case BRCMF_RSPEC_BW_160MHZ: + rateinfo->bw = RATE_INFO_BW_160; + break; + case BRCMF_RSPEC_BW_320MHZ: + rateinfo->bw = RATE_INFO_BW_320; + break; + default: + /* Fill in nothing */ + break; + } + if (BRCMF_RSPEC_ISHT(ratespec)) { + rateinfo->flags |= RATE_INFO_FLAGS_MCS; + rateinfo->mcs = ratespec & BRCMF_RSPEC_HT_MCS_MASK; + } else if (BRCMF_RSPEC_ISVHT(ratespec)) { + rateinfo->flags |= RATE_INFO_FLAGS_VHT_MCS; + rateinfo->mcs = ratespec & BRCMF_RSPEC_VHT_MCS_MASK; + rateinfo->nss = (ratespec & BRCMF_RSPEC_VHT_NSS_MASK) >> + BRCMF_RSPEC_VHT_NSS_SHIFT; + } else if (BRCMF_RSPEC_ISHE(ratespec)) { + u32 ltf_gi = BRCMF_RSPEC_HE_LTF_GI(ratespec); + + rateinfo->flags |= RATE_INFO_FLAGS_HE_MCS; + rateinfo->mcs = ratespec & BRCMF_RSPEC_HE_MCS_MASK; + rateinfo->nss = (ratespec & BRCMF_RSPEC_HE_NSS_MASK) >> + BRCMF_RSPEC_HE_NSS_SHIFT; + rateinfo->he_dcm = BRCMF_RSPEC_HE_DCM(ratespec); + if (HE_IS_GI_0_8us(ltf_gi)) { + rateinfo->he_gi = NL80211_RATE_INFO_HE_GI_0_8; + } else if (HE_IS_GI_1_6us(ltf_gi)) { + rateinfo->he_gi = NL80211_RATE_INFO_HE_GI_1_6; + } else if (HE_IS_GI_3_2us(ltf_gi)) { + rateinfo->he_gi = NL80211_RATE_INFO_HE_GI_3_2; + } + } else if (BRCMF_RSPEC_ISEHT(ratespec)) { + u32 ltf_gi = BRCMF_RSPEC_EHT_LTF_GI(ratespec); + + rateinfo->flags |= RATE_INFO_FLAGS_EHT_MCS; + rateinfo->mcs = ratespec & BRCMF_RSPEC_EHT_MCS_MASK; + rateinfo->nss = (ratespec & BRCMF_RSPEC_EHT_NSS_MASK) >> + BRCMF_RSPEC_EHT_NSS_SHIFT; + if (EHT_IS_GI_0_8us(ltf_gi)) { + rateinfo->eht_gi = NL80211_RATE_INFO_EHT_GI_0_8; + } else if (EHT_IS_GI_1_6us(ltf_gi)) { + rateinfo->eht_gi = NL80211_RATE_INFO_EHT_GI_1_6; + } else if (EHT_IS_GI_3_2us(ltf_gi)) { + rateinfo->eht_gi = NL80211_RATE_INFO_EHT_GI_3_2; + } + } +} + static s32 brcmf_cfg80211_get_station(struct wiphy *wiphy, struct net_device *ndev, const u8 *mac, struct station_info *sinfo) @@ -3200,6 +3265,8 @@ brcmf_cfg80211_get_station(struct wiphy *wiphy, struct net_device *ndev, s32 count_rssi = 0; int rssi; u32 i; + u16 struct_ver; + u16 info_len; brcmf_dbg(TRACE, "Enter, MAC %pM\n", mac); if (!check_vif_up(ifp->vif)) @@ -3223,7 +3290,9 @@ brcmf_cfg80211_get_station(struct wiphy *wiphy, struct net_device *ndev, goto done; } } - brcmf_dbg(TRACE, "version %d\n", le16_to_cpu(sta_info_le.ver)); + info_len = le16_to_cpu(sta_info_le.len); + struct_ver = le16_to_cpu(sta_info_le.ver); + brcmf_dbg(TRACE, "version %d\n", struct_ver); sinfo->filled = BIT_ULL(NL80211_STA_INFO_INACTIVE_TIME); sinfo->inactive_time = le32_to_cpu(sta_info_le.idle) * 1000; sta_flags = le32_to_cpu(sta_info_le.flags); @@ -3257,12 +3326,13 @@ brcmf_cfg80211_get_station(struct wiphy *wiphy, struct net_device *ndev, sinfo->rxrate.legacy = le32_to_cpu(sta_info_le.rx_rate) / 100; } - if (le16_to_cpu(sta_info_le.ver) >= 4) { + if (struct_ver >= 4) { sinfo->filled |= BIT_ULL(NL80211_STA_INFO_TX_BYTES); sinfo->tx_bytes = le64_to_cpu(sta_info_le.tx_tot_bytes); sinfo->filled |= BIT_ULL(NL80211_STA_INFO_RX_BYTES); sinfo->rx_bytes = le64_to_cpu(sta_info_le.rx_tot_bytes); } + for (i = 0; i < BRCMF_ANT_MAX; i++) { if (sta_info_le.rssi[i] == 0 || sta_info_le.rx_lastpkt_rssi[i] == 0) @@ -3301,6 +3371,25 @@ brcmf_cfg80211_get_station(struct wiphy *wiphy, struct net_device *ndev, } } } + /* Some version 7 structs have ratespecs from the last packet. */ + if (struct_ver >= 7) { + if (info_len >= sizeof(sta_info_le)) { + brcmf_convert_ratespec_to_rateinfo( + le32_to_cpu(sta_info_le.v7.tx_rspec), + &sinfo->txrate); + brcmf_convert_ratespec_to_rateinfo( + le32_to_cpu(sta_info_le.v7.rx_rspec), + &sinfo->rxrate); + } else { + /* We didn't get the fields we were expecting, fallback to nrate */ + u32 nrate = 0; + err = brcmf_fil_iovar_int_get(ifp, "nrate", &nrate); + if (!err) { + brcmf_convert_ratespec_to_rateinfo( + nrate, &sinfo->txrate); + } + } + } done: brcmf_dbg(TRACE, "Exit\n"); return err; diff --git a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/fwil_types.h b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/fwil_types.h index 14d91e7749e82d..7b8f809cdc412d 100644 --- a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/fwil_types.h +++ b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/fwil_types.h @@ -824,13 +824,17 @@ struct brcmf_channel_info_le { __le32 scan_channel; }; +#define BRCMF_MAX_ASSOC_OUI_NUM 6 +#define BRCMF_ASSOC_OUI_LEN 3 struct brcmf_sta_info_le { __le16 ver; /* version of this struct */ __le16 len; /* length in bytes of this structure */ __le16 cap; /* sta's advertised capabilities */ + u16 PAD; __le32 flags; /* flags defined below */ __le32 idle; /* time since data pkt rx'd from sta */ u8 ea[ETH_ALEN]; /* Station address */ + u16 PAD2; __le32 count; /* # rates in this set */ u8 rates[BRCMF_MAXRATES_IN_SET]; /* rates in 500kbps units */ /* w/hi bit set if basic */ @@ -862,6 +866,7 @@ struct brcmf_sta_info_le { __le16 aid; /* association ID */ __le16 ht_capabilities; /* advertised ht caps */ __le16 vht_flags; /* converted vht flags */ + u16 PAD3; __le32 tx_pkts_retry_cnt; /* # of frames where a retry was * exhausted. */ @@ -914,6 +919,13 @@ struct brcmf_sta_info_le { __le32 tx_rspec; /* Rate of last successful tx frame */ __le32 rx_rspec; /* Rate of last successful rx frame */ __le32 wnm_cap; /* wnm capabilities */ + __le16 he_flags; /* converted he flags */ + u16 PAD; + struct { + u8 count; + u8 oui[BRCMF_MAX_ASSOC_OUI_NUM][BRCMF_ASSOC_OUI_LEN]; + } vendor_oui; + u8 link_bw; } v7; }; }; diff --git a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/ratespec.h b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/ratespec.h new file mode 100644 index 00000000000000..37e722daab14d4 --- /dev/null +++ b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/ratespec.h @@ -0,0 +1,252 @@ +// SPDX-License-Identifier: ISC +/* + * Copyright (c) 2023 Daniel Berlin + */ + +#ifndef BRCMFMAC_RATESPEC_H +#define BRCMFMAC_RATESPEC_H +/* Rate spec. definitions */ +/* for BRCMF_RSPEC_ENCODING field >= BRCMF_RSPEC_ENCODING_HE, backward compatible */ + +/**< Legacy rate or MCS or MCS + NSS */ +#define BRCMF_RSPEC_RATE_MASK 0x000000FFu +/**< Tx chain expansion beyond Nsts */ +#define BRCMF_RSPEC_TXEXP_MASK 0x00000300u +#define BRCMF_RSPEC_TXEXP_SHIFT 8u +/* EHT GI indices */ +#define BRCMF_RSPEC_EHT_GI_MASK 0x00000C00u +#define BRCMF_RSPEC_EHT_GI_SHIFT 10u +/* HE GI indices */ +#define BRCMF_RSPEC_HE_GI_MASK 0x00000C00u +#define BRCMF_RSPEC_HE_GI_SHIFT 10u +/**< Range extension mask */ +#define BRCMF_RSPEC_ER_MASK 0x0000C000u +#define BRCMF_RSPEC_ER_SHIFT 14u +/**< Range extension tone config */ +#define BRCMF_RSPEC_ER_TONE_MASK 0x00004000u +#define BRCMF_RSPEC_ER_TONE_SHIFT 14u +/**< Range extension enable */ +#define BRCMF_RSPEC_ER_ENAB_MASK 0x00008000u +#define BRCMF_RSPEC_ER_ENAB_SHIFT 15u +/**< Bandwidth */ +#define BRCMF_RSPEC_BW_MASK 0x00070000u +#define BRCMF_RSPEC_BW_SHIFT 16u +/**< Dual Carrier Modulation */ +#define BRCMF_RSPEC_DCM 0x00080000u +#define BRCMF_RSPEC_DCM_SHIFT 19u +/**< STBC expansion, Nsts = 2 * Nss */ +#define BRCMF_RSPEC_STBC 0x00100000u +#define BRCMF_RSPEC_TXBF 0x00200000u +#define BRCMF_RSPEC_LDPC 0x00400000u +/* HT/VHT SGI indication */ +#define BRCMF_RSPEC_SGI 0x00800000u +/**< DSSS short preable - Encoding 0 */ +#define BRCMF_RSPEC_SHORT_PREAMBLE 0x00800000u +/**< Encoding of RSPEC_RATE field */ +#define BRCMF_RSPEC_ENCODING_MASK 0x07000000u +#define BRCMF_RSPEC_ENCODING_SHIFT 24u +#define BRCMF_RSPEC_OVERRIDE_RATE 0x40000000u /**< override rate only */ +#define BRCMF_RSPEC_OVERRIDE_MODE 0x80000000u /**< override both rate & mode */ + +/* ======== RSPEC_EHT_GI|RSPEC_SGI fields for EHT ======== */ +/* 11be Draft 0.4 Table 36-35:Common field for non-OFDMA transmission. + * Table 36-32 Common field for OFDMA transmission + */ +#define BRCMF_RSPEC_EHT_LTF_GI(rspec) \ + (((rspec) & BRCMF_RSPEC_EHT_GI_MASK) >> BRCMF_RSPEC_EHT_GI_SHIFT) +#define BRCMF_RSPEC_EHT_2x_LTF_GI_0_8us (0x0u) +#define BRCMF_RSPEC_EHT_2x_LTF_GI_1_6us (0x1u) +#define BRCMF_RSPEC_EHT_4x_LTF_GI_0_8us (0x2u) +#define BRCMF_RSPEC_EHT_4x_LTF_GI_3_2us (0x3u) +#define WL_EHT_GI_TO_RSPEC(gi) \ + ((u32)(((gi) << BRCMF_RSPEC_EHT_GI_SHIFT) & \ + BRCMF_RSPEC_EHT_GI_MASK)) +#define WL_EHT_GI_TO_RSPEC_SET(rspec, gi) \ + ((rspec & (~BRCMF_RSPEC_EHT_GI_MASK)) | WL_EHT_GI_TO_RSPEC(gi)) + +/* Macros for EHT LTF and GI */ +#define EHT_IS_2X_LTF(gi) \ + (((gi) == BRCMF_RSPEC_EHT_2x_LTF_GI_0_8us) || \ + ((gi) == BRCMF_RSPEC_EHT_2x_LTF_GI_1_6us)) +#define EHT_IS_4X_LTF(gi) \ + (((gi) == BRCMF_RSPEC_EHT_4x_LTF_GI_0_8us) || \ + ((gi) == BRCMF_RSPEC_EHT_4x_LTF_GI_3_2us)) + +#define EHT_IS_GI_0_8us(gi) \ + (((gi) == BRCMF_RSPEC_EHT_2x_LTF_GI_0_8us) || \ + ((gi) == BRCMF_RSPEC_EHT_4x_LTF_GI_0_8us)) +#define EHT_IS_GI_1_6us(gi) ((gi) == BRCMF_RSPEC_EHT_2x_LTF_GI_1_6us) +#define EHT_IS_GI_3_2us(gi) ((gi) == BRCMF_RSPEC_EHT_4x_LTF_GI_3_2us) + +/* ======== RSPEC_HE_GI|RSPEC_SGI fields for HE ======== */ + +/* GI for HE */ +#define BRCMF_RSPEC_HE_LTF_GI(rspec) \ + (((rspec) & BRCMF_RSPEC_HE_GI_MASK) >> BRCMF_RSPEC_HE_GI_SHIFT) +#define BRCMF_RSPEC_HE_1x_LTF_GI_0_8us (0x0u) +#define BRCMF_RSPEC_HE_2x_LTF_GI_0_8us (0x1u) +#define BRCMF_RSPEC_HE_2x_LTF_GI_1_6us (0x2u) +#define BRCMF_RSPEC_HE_4x_LTF_GI_3_2us (0x3u) +#define BRCMF_RSPEC_ISHEGI(rspec) \ + (RSPEC_HE_LTF_GI(rspec) > BRCMF_RSPEC_HE_1x_LTF_GI_0_8us) +#define HE_GI_TO_RSPEC(gi) \ + (((u32)(gi) << BRCMF_RSPEC_HE_GI_SHIFT) & BRCMF_RSPEC_HE_GI_MASK) +#define HE_GI_TO_RSPEC_SET(rspec, gi) \ + ((rspec & (~BRCMF_RSPEC_HE_GI_MASK)) | HE_GI_TO_RSPEC(gi)) + +/* Macros for HE LTF and GI */ +#define HE_IS_1X_LTF(gi) ((gi) == BRCMF_RSPEC_HE_1x_LTF_GI_0_8us) +#define HE_IS_2X_LTF(gi) \ + (((gi) == BRCMF_RSPEC_HE_2x_LTF_GI_0_8us) || \ + ((gi) == BRCMF_RSPEC_HE_2x_LTF_GI_1_6us)) +#define HE_IS_4X_LTF(gi) ((gi) == BRCMF_RSPEC_HE_4x_LTF_GI_3_2us) + +#define HE_IS_GI_0_8us(gi) \ + (((gi) == BRCMF_RSPEC_HE_1x_LTF_GI_0_8us) || \ + ((gi) == BRCMF_RSPEC_HE_2x_LTF_GI_0_8us)) +#define HE_IS_GI_1_6us(gi) ((gi) == BRCMF_RSPEC_HE_2x_LTF_GI_1_6us) +#define HE_IS_GI_3_2us(gi) ((gi) == BRCMF_RSPEC_HE_4x_LTF_GI_3_2us) + +/* RSPEC Macros for extracting and using HE-ER and DCM */ +#define BRCMF_RSPEC_HE_DCM(rspec) \ + (((rspec) & BRCMF_RSPEC_DCM) >> BRCMF_RSPEC_DCM_SHIFT) +#define BRCMF_RSPEC_HE_ER(rspec) \ + (((rspec) & BRCMF_RSPEC_ER_MASK) >> BRCMF_RSPEC_ER_SHIFT) +#define BRCMF_RSPEC_HE_ER_ENAB(rspec) \ + (((rspec) & BRCMF_RSPEC_ER_ENAB_MASK) >> BRCMF_RSPEC_ER_ENAB_SHIFT) +#define BRCMF_RSPEC_HE_ER_TONE(rspec) \ + (((rspec) & BRCMF_RSPEC_ER_TONE_MASK) >> BRCMF_RSPEC_ER_TONE_SHIFT) +/* ======== RSPEC_RATE field ======== */ + +/* Encoding 0 - legacy rate */ +/* DSSS, CCK, and OFDM rates in [500kbps] units */ +#define BRCMF_RSPEC_LEGACY_RATE_MASK 0x0000007F +#define WLC_RATE_1M 2 +#define WLC_RATE_2M 4 +#define WLC_RATE_5M5 11 +#define WLC_RATE_11M 22 +#define WLC_RATE_6M 12 +#define WLC_RATE_9M 18 +#define WLC_RATE_12M 24 +#define WLC_RATE_18M 36 +#define WLC_RATE_24M 48 +#define WLC_RATE_36M 72 +#define WLC_RATE_48M 96 +#define WLC_RATE_54M 108 + +/* Encoding 1 - HT MCS */ +/**< HT MCS value mask in rspec */ +#define BRCMF_RSPEC_HT_MCS_MASK 0x0000007F + +/* Encoding >= 2 */ +/* NSS & MCS values mask in rspec */ +#define BRCMF_RSPEC_NSS_MCS_MASK 0x000000FF +/* mimo MCS value mask in rspec */ +#define BRCMF_RSPEC_MCS_MASK 0x0000000F +/* mimo NSS value mask in rspec */ +#define BRCMF_RSPEC_NSS_MASK 0x000000F0 +/* mimo NSS value shift in rspec */ +#define BRCMF_RSPEC_NSS_SHIFT 4 + +/* Encoding 2 - VHT MCS + NSS */ +/**< VHT MCS value mask in rspec */ +#define BRCMF_RSPEC_VHT_MCS_MASK BRCMF_RSPEC_MCS_MASK +/**< VHT Nss value mask in rspec */ +#define BRCMF_RSPEC_VHT_NSS_MASK BRCMF_RSPEC_NSS_MASK +/**< VHT Nss value shift in rspec */ +#define BRCMF_RSPEC_VHT_NSS_SHIFT BRCMF_RSPEC_NSS_SHIFT + +/* Encoding 3 - HE MCS + NSS */ +/**< HE MCS value mask in rspec */ +#define BRCMF_RSPEC_HE_MCS_MASK BRCMF_RSPEC_MCS_MASK +/**< HE Nss value mask in rspec */ +#define BRCMF_RSPEC_HE_NSS_MASK BRCMF_RSPEC_NSS_MASK +/**< HE Nss value shift in rpsec */ +#define BRCMF_RSPEC_HE_NSS_SHIFT BRCMF_RSPEC_NSS_SHIFT + +#define BRCMF_RSPEC_HE_NSS_UNSPECIFIED 0xf + +/* Encoding 4 - EHT MCS + NSS */ +/**< EHT MCS value mask in rspec */ +#define BRCMF_RSPEC_EHT_MCS_MASK BRCMF_RSPEC_MCS_MASK +/**< EHT Nss value mask in rspec */ +#define BRCMF_RSPEC_EHT_NSS_MASK BRCMF_RSPEC_NSS_MASK +/**< EHT Nss value shift in rpsec */ +#define BRCMF_RSPEC_EHT_NSS_SHIFT BRCMF_RSPEC_NSS_SHIFT + +/* ======== RSPEC_BW field ======== */ + +#define BRCMF_RSPEC_BW_UNSPECIFIED 0u +#define BRCMF_RSPEC_BW_20MHZ 0x00010000u +#define BRCMF_RSPEC_BW_40MHZ 0x00020000u +#define BRCMF_RSPEC_BW_80MHZ 0x00030000u +#define BRCMF_RSPEC_BW_160MHZ 0x00040000u +#define BRCMF_RSPEC_BW_320MHZ 0x00060000u + +/* ======== RSPEC_ENCODING field ======== */ + +/* NOTE: Assuming the rate field is always NSS+MCS starting from VHT encoding! + * Modify/fix RSPEC_ISNSSMCS() macro if above condition changes any time. + */ +/**< Legacy rate is stored in RSPEC_RATE */ +#define BRCMF_RSPEC_ENCODE_RATE 0x00000000u +/**< HT MCS is stored in RSPEC_RATE */ +#define BRCMF_RSPEC_ENCODE_HT 0x01000000u +/**< VHT MCS and NSS are stored in RSPEC_RATE */ +#define BRCMF_RSPEC_ENCODE_VHT 0x02000000u +/**< HE MCS and NSS are stored in RSPEC_RATE */ +#define BRCMF_RSPEC_ENCODE_HE 0x03000000u +/**< EHT MCS and NSS are stored in RSPEC_RATE */ +#define BRCMF_RSPEC_ENCODE_EHT 0x04000000u + +/** + * =============================== + * Handy macros to parse rate spec + * =============================== + */ +#define BRCMF_RSPEC_BW(rspec) ((rspec) & BRCMF_RSPEC_BW_MASK) +#define BRCMF_RSPEC_IS20MHZ(rspec) (RSPEC_BW(rspec) == BRCMF_RSPEC_BW_20MHZ) +#define BRCMF_RSPEC_IS40MHZ(rspec) (RSPEC_BW(rspec) == BRCMF_RSPEC_BW_40MHZ) +#define BRCMF_RSPEC_IS80MHZ(rspec) (RSPEC_BW(rspec) == BRCMF_RSPEC_BW_80MHZ) +#define BRCMF_RSPEC_IS160MHZ(rspec) (RSPEC_BW(rspec) == BRCMF_RSPEC_BW_160MHZ) +#if defined(WL_BW320MHZ) +#define BRCMF_RSPEC_IS320MHZ(rspec) (RSPEC_BW(rspec) == BRCMF_RSPEC_BW_320MHZ) +#else +#define BRCMF_RSPEC_IS320MHZ(rspec) (FALSE) +#endif /* WL_BW320MHZ */ + +#define BRCMF_RSPEC_BW_GE(rspec, rspec_bw) (RSPEC_BW(rspec) >= rspec_bw) +#define BRCMF_RSPEC_BW_LE(rspec, rspec_bw) (RSPEC_BW(rspec) <= rspec_bw) +#define BRCMF_RSPEC_BW_GT(rspec, rspec_bw) (!RSPEC_BW_LE(rspec, rspec_bw)) +#define BRCMF_RSPEC_BW_LT(rspec, rspec_bw) (!RSPEC_BW_GE(rspec, rspec_bw)) + +#define BRCMF_RSPEC_ISSGI(rspec) (((rspec) & BRCMF_RSPEC_SGI) != 0) +#define BRCMF_RSPEC_ISLDPC(rspec) (((rspec) & BRCMF_RSPEC_LDPC) != 0) +#define BRCMF_RSPEC_ISSTBC(rspec) (((rspec) & BRCMF_RSPEC_STBC) != 0) +#define BRCMF_RSPEC_ISTXBF(rspec) (((rspec) & BRCMF_RSPEC_TXBF) != 0) + +#define BRCMF_RSPEC_TXEXP(rspec) \ + (((rspec) & BRCMF_RSPEC_TXEXP_MASK) >> BRCMF_RSPEC_TXEXP_SHIFT) + +#define BRCMF_RSPEC_ENCODE(rspec) \ + (((rspec) & BRCMF_RSPEC_ENCODING_MASK) >> BRCMF_RSPEC_ENCODING_SHIFT) +#define BRCMF_RSPEC_ISLEGACY(rspec) \ + (((rspec) & BRCMF_RSPEC_ENCODING_MASK) == BRCMF_RSPEC_ENCODE_RATE) + +#define BRCMF_RSPEC_ISHT(rspec) \ + (((rspec) & BRCMF_RSPEC_ENCODING_MASK) == BRCMF_RSPEC_ENCODE_HT) +#define BRCMF_RSPEC_ISVHT(rspec) \ + (((rspec) & BRCMF_RSPEC_ENCODING_MASK) == BRCMF_RSPEC_ENCODE_VHT) +#define BRCMF_RSPEC_ISHE(rspec) \ + (((rspec) & BRCMF_RSPEC_ENCODING_MASK) == BRCMF_RSPEC_ENCODE_HE) +#define BRCMF_RSPEC_ISEHT(rspec) \ + (((rspec) & BRCMF_RSPEC_ENCODING_MASK) == BRCMF_RSPEC_ENCODE_EHT) + +/* fast check if rate field is NSS+MCS format (starting from VHT ratespec) */ +#define BRCMF_RSPEC_ISVHTEXT(rspec) \ + (((rspec) & BRCMF_RSPEC_ENCODING_MASK) >= BRCMF_RSPEC_ENCODE_VHT) +/* fast check if rate field is NSS+MCS format (starting from HE ratespec) */ +#define BRCMF_RSPEC_ISHEEXT(rspec) \ + (((rspec) & BRCMF_RSPEC_ENCODING_MASK) >= BRCMF_RSPEC_ENCODE_HE) + +#endif /* BRCMFMAC_RATESPEC_H */ From ab3253bd468327f8117f0c67c16e911de32e87a6 Mon Sep 17 00:00:00 2001 From: Daniel Berlin Date: Thu, 19 Oct 2023 23:55:07 -0400 Subject: [PATCH 0242/3902] [brcmfmac] Support bandwidth caps for all bands Signed-off-by: Daniel Berlin --- .../broadcom/brcm80211/brcmfmac/cfg80211.c | 21 +++++++++++++------ 1 file changed, 15 insertions(+), 6 deletions(-) diff --git a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/cfg80211.c b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/cfg80211.c index afb8aff348c6e2..bbd6955aedeadc 100644 --- a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/cfg80211.c +++ b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/cfg80211.c @@ -7321,6 +7321,7 @@ static int brcmf_construct_chaninfo(struct brcmf_cfg80211_info *cfg, break; } } + if (!channel) { /* It seems firmware supports some channel we never * considered. Something new in IEEE standard? @@ -7393,17 +7394,25 @@ static int brcmf_enable_bw40_2g(struct brcmf_cfg80211_info *cfg) struct brcmu_chan ch; u32 num_chan; int i, j; + s32 updown; /* verify support for bw_cap command */ - val = WLC_BAND_5G; + val = WLC_BAND_2G; err = brcmf_fil_iovar_int_query(ifp, "bw_cap", &val); - + brcmf_dbg(INFO, "Check bw_cap support:%d\n", err); if (!err) { + /* Setting the bw_cap is DOWN restricted. */ + updown = 0; + brcmf_fil_cmd_data_set(ifp, BRCMF_C_DOWN, &updown, sizeof(s32)); /* only set 2G bandwidth using bw_cap command */ band_bwcap.band = cpu_to_le32(WLC_BAND_2G); band_bwcap.bw_cap = cpu_to_le32(WLC_BW_CAP_40MHZ); err = brcmf_fil_iovar_data_set(ifp, "bw_cap", &band_bwcap, sizeof(band_bwcap)); + brcmf_dbg(INFO, "set bw_cap support:%d\n", err); + brcmf_c_set_joinpref_default(ifp); + updown = 1; + brcmf_fil_cmd_data_set(ifp, BRCMF_C_UP, &updown, sizeof(s32)); } else { brcmf_dbg(INFO, "fallback to mimo_bw_cap\n"); val = WLC_N_BW_40ALL; @@ -7465,7 +7474,7 @@ static int brcmf_enable_bw40_2g(struct brcmf_cfg80211_info *cfg) return err; } -static void brcmf_get_bwcap(struct brcmf_if *ifp, u32 bw_cap[], bool has_6g) +static void brcmf_get_bwcap(struct brcmf_if *ifp, u32 bw_cap[4], bool has_6g) { struct brcmf_pub *drvr = ifp->drvr; u32 band, mimo_bwcap; @@ -7519,7 +7528,7 @@ static void brcmf_get_bwcap(struct brcmf_if *ifp, u32 bw_cap[], bool has_6g) } static void brcmf_update_ht_cap(struct ieee80211_supported_band *band, - u32 bw_cap[2], u32 nrxchain) + u32 bw_cap[4], u32 nrxchain) { /* Not supported in 6G band */ if (band->band == NL80211_BAND_6GHZ) @@ -7550,7 +7559,7 @@ static __le16 brcmf_get_mcs_map(u32 nstreams, } static void brcmf_update_vht_cap(struct ieee80211_supported_band *band, - u32 bw_cap[2], u32 txstreams, u32 rxstreams, + u32 bw_cap[4], u32 txstreams, u32 rxstreams, u32 txbf_bfe_cap, u32 txbf_bfr_cap, u32 ldpc_cap, u32 stbc_rx, u32 stbc_tx) { @@ -7735,7 +7744,7 @@ static int brcmf_setup_wiphybands(struct brcmf_cfg80211_info *cfg) u32 nmode; u32 vhtmode = 0; /* 2GHZ, 5GHZ, 60GHZ, 6GHZ */ - u32 bw_cap[4] = { WLC_BW_20MHZ_BIT, WLC_BW_20MHZ_BIT, 0, 0 }; + u32 bw_cap[4] = { 0, 0, 0, 0 }; u32 rxchain; u32 txchain; u32 nrxchain; From 30d7ce3bd353220a0837cdfeebc671ac8f4021ef Mon Sep 17 00:00:00 2001 From: Daniel Berlin Date: Sat, 11 Nov 2023 21:28:39 -0500 Subject: [PATCH 0243/3902] [brcmfmac] Clean up and common interface creation handling This makes firmware-side interface creation structures private to interface creation, and commons out how creation is handled Signed-off-by: Daniel Berlin --- .../broadcom/brcm80211/brcmfmac/Makefile | 3 +- .../broadcom/brcm80211/brcmfmac/cfg80211.c | 270 +----------------- .../brcm80211/brcmfmac/interface_create.c | 270 ++++++++++++++++++ .../brcm80211/brcmfmac/interface_create.h | 13 + 4 files changed, 286 insertions(+), 270 deletions(-) create mode 100644 drivers/net/wireless/broadcom/brcm80211/brcmfmac/interface_create.c create mode 100644 drivers/net/wireless/broadcom/brcm80211/brcmfmac/interface_create.h diff --git a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/Makefile b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/Makefile index 694b50a0664f24..6fd805023500be 100644 --- a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/Makefile +++ b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/Makefile @@ -27,7 +27,8 @@ brcmfmac-objs += \ pno.o \ join_param.o \ scan_param.o \ - xtlv.o + xtlv.o \ + interface_create.o brcmfmac-$(CONFIG_BRCMFMAC_PROTO_BCDC) += \ bcdc.o \ diff --git a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/cfg80211.c b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/cfg80211.c index bbd6955aedeadc..a87965c5d1226a 100644 --- a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/cfg80211.c +++ b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/cfg80211.c @@ -36,6 +36,7 @@ #include "fwvid.h" #include "xtlv.h" #include "ratespec.h" +#include "interface_create.h" #define BRCMF_SCAN_IE_LEN_MAX 2048 @@ -312,48 +313,6 @@ struct parsed_vndr_ies { struct parsed_vndr_ie_info ie_info[VNDR_IE_PARSE_LIMIT]; }; -#define WL_INTERFACE_CREATE_VER_1 1 -#define WL_INTERFACE_CREATE_VER_2 2 -#define WL_INTERFACE_CREATE_VER_3 3 -#define WL_INTERFACE_CREATE_VER_MAX WL_INTERFACE_CREATE_VER_3 - -#define WL_INTERFACE_MAC_DONT_USE 0x0 -#define WL_INTERFACE_MAC_USE 0x2 - -#define WL_INTERFACE_CREATE_STA 0x0 -#define WL_INTERFACE_CREATE_AP 0x1 - -struct wl_interface_create_v1 { - u16 ver; /* structure version */ - u32 flags; /* flags for operation */ - u8 mac_addr[ETH_ALEN]; /* MAC address */ - u32 wlc_index; /* optional for wlc index */ -}; - -struct wl_interface_create_v2 { - u16 ver; /* structure version */ - u8 pad1[2]; - u32 flags; /* flags for operation */ - u8 mac_addr[ETH_ALEN]; /* MAC address */ - u8 iftype; /* type of interface created */ - u8 pad2; - u32 wlc_index; /* optional for wlc index */ -}; - -struct wl_interface_create_v3 { - u16 ver; /* structure version */ - u16 len; /* length of structure + data */ - u16 fixed_len; /* length of structure */ - u8 iftype; /* type of interface created */ - u8 wlc_index; /* optional for wlc index */ - u32 flags; /* flags for operation */ - u8 mac_addr[ETH_ALEN]; /* MAC address */ - u8 bssid[ETH_ALEN]; /* optional for BSSID */ - u8 if_index; /* interface index request */ - u8 pad[3]; - u8 data[]; /* Optional for specific data */ -}; - static u8 nl80211_band_to_fwil(enum nl80211_band band) { switch (band) { @@ -636,231 +595,6 @@ brcmf_cfg80211_update_proto_addr_mode(struct wireless_dev *wdev) ADDR_INDIRECT); } -static int brcmf_get_first_free_bsscfgidx(struct brcmf_pub *drvr) -{ - int bsscfgidx; - - for (bsscfgidx = 0; bsscfgidx < BRCMF_MAX_IFS; bsscfgidx++) { - /* bsscfgidx 1 is reserved for legacy P2P */ - if (bsscfgidx == 1) - continue; - if (!drvr->iflist[bsscfgidx]) - return bsscfgidx; - } - - return -ENOMEM; -} - -static void brcmf_set_vif_sta_macaddr(struct brcmf_if *ifp, u8 *mac_addr) -{ - u8 mac_idx = ifp->drvr->sta_mac_idx; - - /* set difference MAC address with locally administered bit */ - memcpy(mac_addr, ifp->mac_addr, ETH_ALEN); - mac_addr[0] |= 0x02; - mac_addr[3] ^= mac_idx ? 0xC0 : 0xA0; - mac_idx++; - mac_idx = mac_idx % 2; - ifp->drvr->sta_mac_idx = mac_idx; -} - -static int brcmf_cfg80211_request_sta_if(struct brcmf_if *ifp, u8 *macaddr) -{ - struct wl_interface_create_v1 iface_v1; - struct wl_interface_create_v2 iface_v2; - struct wl_interface_create_v3 iface_v3; - u32 iface_create_ver; - int err; - - /* interface_create version 1 */ - memset(&iface_v1, 0, sizeof(iface_v1)); - iface_v1.ver = WL_INTERFACE_CREATE_VER_1; - iface_v1.flags = WL_INTERFACE_CREATE_STA | - WL_INTERFACE_MAC_USE; - if (!is_zero_ether_addr(macaddr)) - memcpy(iface_v1.mac_addr, macaddr, ETH_ALEN); - else - brcmf_set_vif_sta_macaddr(ifp, iface_v1.mac_addr); - - err = brcmf_fil_iovar_data_get(ifp, "interface_create", - &iface_v1, - sizeof(iface_v1)); - if (err) { - brcmf_info("failed to create interface(v1), err=%d\n", - err); - } else { - brcmf_dbg(INFO, "interface created(v1)\n"); - return 0; - } - - /* interface_create version 2 */ - memset(&iface_v2, 0, sizeof(iface_v2)); - iface_v2.ver = WL_INTERFACE_CREATE_VER_2; - iface_v2.flags = WL_INTERFACE_MAC_USE; - iface_v2.iftype = WL_INTERFACE_CREATE_STA; - if (!is_zero_ether_addr(macaddr)) - memcpy(iface_v2.mac_addr, macaddr, ETH_ALEN); - else - brcmf_set_vif_sta_macaddr(ifp, iface_v2.mac_addr); - - err = brcmf_fil_iovar_data_get(ifp, "interface_create", - &iface_v2, - sizeof(iface_v2)); - if (err) { - brcmf_info("failed to create interface(v2), err=%d\n", - err); - } else { - brcmf_dbg(INFO, "interface created(v2)\n"); - return 0; - } - - /* interface_create version 3+ */ - /* get supported version from firmware side */ - iface_create_ver = 0; - err = brcmf_fil_bsscfg_int_query(ifp, "interface_create", - &iface_create_ver); - if (err) { - brcmf_err("fail to get supported version, err=%d\n", err); - return -EOPNOTSUPP; - } - - switch (iface_create_ver) { - case WL_INTERFACE_CREATE_VER_3: - memset(&iface_v3, 0, sizeof(iface_v3)); - iface_v3.ver = WL_INTERFACE_CREATE_VER_3; - iface_v3.flags = WL_INTERFACE_MAC_USE; - iface_v3.iftype = WL_INTERFACE_CREATE_STA; - if (!is_zero_ether_addr(macaddr)) - memcpy(iface_v3.mac_addr, macaddr, ETH_ALEN); - else - brcmf_set_vif_sta_macaddr(ifp, iface_v3.mac_addr); - - err = brcmf_fil_iovar_data_get(ifp, "interface_create", - &iface_v3, - sizeof(iface_v3)); - - if (!err) - brcmf_dbg(INFO, "interface created(v3)\n"); - break; - default: - brcmf_err("not support interface create(v%d)\n", - iface_create_ver); - err = -EOPNOTSUPP; - break; - } - - if (err) { - brcmf_info("station interface creation failed (%d)\n", - err); - return -EIO; - } - - return 0; -} - -static int brcmf_cfg80211_request_ap_if(struct brcmf_if *ifp) -{ - struct wl_interface_create_v1 iface_v1; - struct wl_interface_create_v2 iface_v2; - struct wl_interface_create_v3 iface_v3; - u32 iface_create_ver; - struct brcmf_pub *drvr = ifp->drvr; - struct brcmf_mbss_ssid_le mbss_ssid_le; - int bsscfgidx; - int err; - - /* interface_create version 1 */ - memset(&iface_v1, 0, sizeof(iface_v1)); - iface_v1.ver = WL_INTERFACE_CREATE_VER_1; - iface_v1.flags = WL_INTERFACE_CREATE_AP | - WL_INTERFACE_MAC_USE; - - brcmf_set_vif_sta_macaddr(ifp, iface_v1.mac_addr); - - err = brcmf_fil_iovar_data_get(ifp, "interface_create", - &iface_v1, - sizeof(iface_v1)); - if (err) { - brcmf_info("failed to create interface(v1), err=%d\n", - err); - } else { - brcmf_dbg(INFO, "interface created(v1)\n"); - return 0; - } - - /* interface_create version 2 */ - memset(&iface_v2, 0, sizeof(iface_v2)); - iface_v2.ver = WL_INTERFACE_CREATE_VER_2; - iface_v2.flags = WL_INTERFACE_MAC_USE; - iface_v2.iftype = WL_INTERFACE_CREATE_AP; - - brcmf_set_vif_sta_macaddr(ifp, iface_v2.mac_addr); - - err = brcmf_fil_iovar_data_get(ifp, "interface_create", - &iface_v2, - sizeof(iface_v2)); - if (err) { - brcmf_info("failed to create interface(v2), err=%d\n", - err); - } else { - brcmf_dbg(INFO, "interface created(v2)\n"); - return 0; - } - - /* interface_create version 3+ */ - /* get supported version from firmware side */ - iface_create_ver = 0; - err = brcmf_fil_bsscfg_int_query(ifp, "interface_create", - &iface_create_ver); - if (err) { - brcmf_err("fail to get supported version, err=%d\n", err); - return -EOPNOTSUPP; - } - - switch (iface_create_ver) { - case WL_INTERFACE_CREATE_VER_3: - memset(&iface_v3, 0, sizeof(iface_v3)); - iface_v3.ver = WL_INTERFACE_CREATE_VER_3; - iface_v3.flags = WL_INTERFACE_MAC_USE; - iface_v3.iftype = WL_INTERFACE_CREATE_AP; - brcmf_set_vif_sta_macaddr(ifp, iface_v3.mac_addr); - - err = brcmf_fil_iovar_data_get(ifp, "interface_create", - &iface_v3, - sizeof(iface_v3)); - - if (!err) - brcmf_dbg(INFO, "interface created(v3)\n"); - break; - default: - brcmf_err("not support interface create(v%d)\n", - iface_create_ver); - err = -EOPNOTSUPP; - break; - } - - if (err) { - brcmf_info("Does not support interface_create (%d)\n", - err); - memset(&mbss_ssid_le, 0, sizeof(mbss_ssid_le)); - bsscfgidx = brcmf_get_first_free_bsscfgidx(ifp->drvr); - if (bsscfgidx < 0) - return bsscfgidx; - - mbss_ssid_le.bsscfgidx = cpu_to_le32(bsscfgidx); - mbss_ssid_le.SSID_len = cpu_to_le32(5); - sprintf(mbss_ssid_le.SSID, "ssid%d", bsscfgidx); - - err = brcmf_fil_bsscfg_data_set(ifp, "bsscfg:ssid", &mbss_ssid_le, - sizeof(mbss_ssid_le)); - - if (err < 0) - bphy_err(drvr, "setting ssid failed %d\n", err); - } - - return err; -} - /** * brcmf_apsta_add_vif() - create a new AP or STA virtual interface * @@ -7160,8 +6894,6 @@ static s32 brcmf_dongle_roam(struct brcmf_if *ifp) if (err) bphy_err(drvr, "WLC_SET_ROAM_TRIGGER error (%d)\n", err); - roam_delta[0] = cpu_to_le32(WL_ROAM_DELTA); - roam_delta[1] = cpu_to_le32(BRCM_BAND_ALL); err = brcmf_fil_cmd_data_set(ifp, BRCMF_C_SET_ROAM_DELTA, (void *)roam_delta, sizeof(roam_delta)); if (err) diff --git a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/interface_create.c b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/interface_create.c new file mode 100644 index 00000000000000..1f40ff8d632c25 --- /dev/null +++ b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/interface_create.c @@ -0,0 +1,270 @@ +// SPDX-License-Identifier: ISC +/* + * Copyright (c) 2023 Daniel Berlin + */ + +/* This file handles firmware-side interface creation */ + +#include +#include +#include +#include +#include +#include + +#include +#include +#include "cfg80211.h" +#include "debug.h" +#include "fwil.h" +#include "proto.h" +#include "bus.h" +#include "common.h" +#include "interface_create.h" + +#define BRCMF_INTERFACE_CREATE_VER_1 1 +#define BRCMF_INTERFACE_CREATE_VER_2 2 +#define BRCMF_INTERFACE_CREATE_VER_3 3 +#define BRCMF_INTERFACE_CREATE_VER_MAX BRCMF_INTERFACE_CREATE_VER_3 + +/* These sets of flags specify whether to use various fields in the interface create structures */ + +/* This is only used with version 0 or 1 */ +#define BRCMF_INTERFACE_CREATE_STA (0 << 0) +#define BRCMF_INTERFACE_CREATE_AP (1 << 0) + +#define BRCMF_INTERFACE_MAC_DONT_USE (0 << 1) +#define BRCMF_INTERFACE_MAC_USE (1 << 1) + +#define BRCMF_INTERFACE_WLC_INDEX_DONT_USE (0 << 2) +#define BRCMF_INTERFACE_WLC_INDEX_USE (1 << 2) + +#define BRCMF_INTERFACE_IF_INDEX_DONT_USE (0 << 3) +#define BRCMF_INTERFACE_IF_INDEX_USE (1 << 3) + +#define BRCMF_INTERFACE_BSSID_DONT_USE (0 << 4) +#define BRCMF_INTERFACE_BSSID_USE (1 << 4) + +/* + * From revision >= 2 Bit 0 of flags field will not be used for STA or AP interface creation. + * "iftype" field shall be used for identifying the interface type. + */ +enum brcmf_interface_type { + BRCMF_INTERFACE_TYPE_STA = 0, + BRCMF_INTERFACE_TYPE_AP = 1, + /* The missing number here is deliberate */ + BRCMF_INTERFACE_TYPE_NAN = 3, + BRCMF_INTERFACE_TYPE_P2P_GO = 4, + BRCMF_INTERFACE_TYPE_P2P_GC = 5, + BRCMF_INTERFACE_TYPE_P2P_DISC = 6, + BRCMF_INTERFACE_TYPE_IBSS = 7, + BRCMF_INTERFACE_TYPE_MESH = 8 +}; + + +/* All sources treat these structures as being host endian. + * However, firmware treats it as little endian, so we do as well */ + +struct brcmf_interface_create_v1 { + __le16 ver; /* structure version */ + u8 pad1[2]; + __le32 flags; /* flags for operation */ + u8 mac_addr[ETH_ALEN]; /* MAC address */ + u8 pad2[2]; + __le32 wlc_index; /* optional for wlc index */ +}; + +struct brcmf_interface_create_v2 { + __le16 ver; /* structure version */ + u8 pad1[2]; + __le32 flags; /* flags for operation */ + u8 mac_addr[ETH_ALEN]; /* MAC address */ + u8 iftype; /* type of interface created */ + u8 pad2; + u32 wlc_index; /* optional for wlc index */ +}; + +struct brcmf_interface_create_v3 { + __le16 ver; /* structure version */ + __le16 len; /* length of structure + data */ + __le16 fixed_len; /* length of structure */ + u8 iftype; /* type of interface created */ + u8 wlc_index; /* optional for wlc index */ + __le32 flags; /* flags for operation */ + u8 mac_addr[ETH_ALEN]; /* MAC address */ + u8 bssid[ETH_ALEN]; /* optional for BSSID */ + u8 if_index; /* interface index request */ + u8 pad[3]; + u8 data[]; /* Optional for specific data */ +}; + +static int brcmf_get_first_free_bsscfgidx(struct brcmf_pub *drvr) +{ + int bsscfgidx; + + for (bsscfgidx = 0; bsscfgidx < BRCMF_MAX_IFS; bsscfgidx++) { + /* bsscfgidx 1 is reserved for legacy P2P */ + if (bsscfgidx == 1) + continue; + if (!drvr->iflist[bsscfgidx]) + return bsscfgidx; + } + + return -ENOMEM; +} + +static void brcmf_set_vif_sta_macaddr(struct brcmf_if *ifp, u8 *mac_addr) +{ + u8 mac_idx = ifp->drvr->sta_mac_idx; + + /* set difference MAC address with locally administered bit */ + memcpy(mac_addr, ifp->mac_addr, ETH_ALEN); + mac_addr[0] |= 0x02; + mac_addr[3] ^= mac_idx ? 0xC0 : 0xA0; + mac_idx++; + mac_idx = mac_idx % 2; + ifp->drvr->sta_mac_idx = mac_idx; +} + +static int brcmf_cfg80211_request_if_internal(struct brcmf_if *ifp, u32 version, + enum brcmf_interface_type if_type, + u8 *macaddr) +{ + switch (version) { + case BRCMF_INTERFACE_CREATE_VER_1: { + struct brcmf_interface_create_v1 iface_v1 = {}; + u32 flags = if_type; + + iface_v1.ver = cpu_to_le16(BRCMF_INTERFACE_CREATE_VER_1); + if (macaddr) { + flags |= BRCMF_INTERFACE_MAC_USE; + if (!is_zero_ether_addr(macaddr)) + memcpy(iface_v1.mac_addr, macaddr, ETH_ALEN); + else + brcmf_set_vif_sta_macaddr(ifp, + iface_v1.mac_addr); + } + iface_v1.flags = cpu_to_le32(flags); + return brcmf_fil_iovar_data_get(ifp, "interface_create", + &iface_v1, sizeof(iface_v1)); + } + case BRCMF_INTERFACE_CREATE_VER_2: { + struct brcmf_interface_create_v2 iface_v2 = {}; + u32 flags = 0; + + iface_v2.ver = cpu_to_le16(BRCMF_INTERFACE_CREATE_VER_2); + iface_v2.iftype = if_type; + if (macaddr) { + flags = BRCMF_INTERFACE_MAC_USE; + if (!is_zero_ether_addr(macaddr)) + memcpy(iface_v2.mac_addr, macaddr, ETH_ALEN); + else + brcmf_set_vif_sta_macaddr(ifp, + iface_v2.mac_addr); + } + iface_v2.flags = cpu_to_le32(flags); + return brcmf_fil_iovar_data_get(ifp, "interface_create", + &iface_v2, sizeof(iface_v2)); + } + case BRCMF_INTERFACE_CREATE_VER_3: { + struct brcmf_interface_create_v3 iface_v3 = {}; + u32 flags = 0; + + iface_v3.ver = cpu_to_le16(BRCMF_INTERFACE_CREATE_VER_3); + iface_v3.iftype = if_type; + iface_v3.len = cpu_to_le16(sizeof(iface_v3)); + iface_v3.fixed_len = cpu_to_le16(sizeof(iface_v3)); + if (macaddr) { + flags = BRCMF_INTERFACE_MAC_USE; + if (!is_zero_ether_addr(macaddr)) + memcpy(iface_v3.mac_addr, macaddr, ETH_ALEN); + else + brcmf_set_vif_sta_macaddr(ifp, + iface_v3.mac_addr); + } + iface_v3.flags = cpu_to_le32(flags); + return brcmf_fil_iovar_data_get(ifp, "interface_create", + &iface_v3, sizeof(iface_v3)); + } + default: + bphy_err(ifp->drvr, "Unknown interface create version:%d\n", + version); + return -EINVAL; + } +} +static int brcmf_cfg80211_request_if(struct brcmf_if *ifp, + enum brcmf_interface_type if_type, + u8 *macaddr) +{ + s32 err; + u32 iface_create_ver; + + /* Query the creation version, see if the firmware knows */ + iface_create_ver = 0; + err = brcmf_fil_bsscfg_int_query(ifp, "interface_create", + &iface_create_ver); + if (!err) { + err = brcmf_cfg80211_request_if_internal(ifp, iface_create_ver, + if_type, macaddr); + if (!err) { + brcmf_info("interface created (version %d)\n", + iface_create_ver); + } else { + bphy_err(ifp->drvr, + "failed to create interface (version %d):%d\n", + iface_create_ver, err); + } + return err; + } + /* Either version one or version two */ + err = brcmf_cfg80211_request_if_internal( + ifp, if_type, BRCMF_INTERFACE_CREATE_VER_2, macaddr); + if (!err) { + brcmf_info("interface created (version 2)\n"); + return 0; + } + err = brcmf_cfg80211_request_if_internal( + ifp, if_type, BRCMF_INTERFACE_CREATE_VER_1, macaddr); + if (!err) { + brcmf_info("interface created (version 1)\n"); + return 0; + } + bphy_err(ifp->drvr, + "interface creation failed, tried query, v2, v1: %d\n", err); + return -EINVAL; +} + +int brcmf_cfg80211_request_sta_if(struct brcmf_if *ifp, u8 *macaddr) +{ + return brcmf_cfg80211_request_if(ifp, BRCMF_INTERFACE_TYPE_STA, + macaddr); +} + +int brcmf_cfg80211_request_ap_if(struct brcmf_if *ifp) +{ + int err; + + err = brcmf_cfg80211_request_if(ifp, BRCMF_INTERFACE_TYPE_AP, NULL); + if (err) { + struct brcmf_mbss_ssid_le mbss_ssid_le; + int bsscfgidx; + + brcmf_info("Does not support interface_create (%d)\n", err); + memset(&mbss_ssid_le, 0, sizeof(mbss_ssid_le)); + bsscfgidx = brcmf_get_first_free_bsscfgidx(ifp->drvr); + if (bsscfgidx < 0) + return bsscfgidx; + + mbss_ssid_le.bsscfgidx = cpu_to_le32(bsscfgidx); + mbss_ssid_le.SSID_len = cpu_to_le32(5); + sprintf(mbss_ssid_le.SSID, "ssid%d", bsscfgidx); + + err = brcmf_fil_bsscfg_data_set(ifp, "bsscfg:ssid", + &mbss_ssid_le, + sizeof(mbss_ssid_le)); + + if (err < 0) + bphy_err(ifp->drvr, "setting ssid failed %d\n", err); + } + return err; +} diff --git a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/interface_create.h b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/interface_create.h new file mode 100644 index 00000000000000..669fa1508b67f6 --- /dev/null +++ b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/interface_create.h @@ -0,0 +1,13 @@ +// SPDX-License-Identifier: ISC +/* + * Copyright (c) 2023 Daniel Berlin + */ + +#ifndef _BRCMF_INTERFACE_CREATE_H_ +#define _BRCMF_INTERFACE_CREATE_H_ +#include "core.h" + +int brcmf_cfg80211_request_sta_if(struct brcmf_if *ifp, u8 *macaddr); +int brcmf_cfg80211_request_ap_if(struct brcmf_if *ifp); + +#endif /* _BRCMF_INTERFACE_CREATE_H_ */ From 8671d1bcedd59e17b0824f1d2c848e50007eac4e Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Sun, 2 Jun 2024 19:04:35 +0200 Subject: [PATCH 0244/3902] fixup! wifi: brcmfmac: Add support for firmware signatures Signed-off-by: Janne Grunau --- drivers/net/wireless/broadcom/brcm80211/brcmfmac/pcie.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/pcie.c b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/pcie.c index 48de33ca6da2b3..9f11d555ade453 100644 --- a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/pcie.c +++ b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/pcie.c @@ -1965,7 +1965,7 @@ brcmf_pcie_add_random_seed(struct brcmf_pciedev_info *devinfo, u32 *address) brcmf_dbg(PCIE, "Download random seed\n"); get_random_bytes(randbuf, BRCMF_RANDOM_SEED_LENGTH); - memcpy_toio(devinfo->tcm + address, randbuf, BRCMF_RANDOM_SEED_LENGTH); + memcpy_toio(devinfo->tcm + *address, randbuf, BRCMF_RANDOM_SEED_LENGTH); return 0; } From 4635e2ac9c365c526d03a35a284ec9171f225cfa Mon Sep 17 00:00:00 2001 From: Hector Martin Date: Mon, 2 May 2022 21:17:41 +0900 Subject: [PATCH 0245/3902] dt-bindings: pci: apple,pcie: Add subnode binding, pwren-gpios property We weren't properly validating root port subnodes, so let's do that. Then, also add the new `pwren-gpios` property there to handle device power-up. Signed-off-by: Hector Martin --- .../devicetree/bindings/pci/apple,pcie.yaml | 51 +++++++++++++++++-- 1 file changed, 48 insertions(+), 3 deletions(-) diff --git a/Documentation/devicetree/bindings/pci/apple,pcie.yaml b/Documentation/devicetree/bindings/pci/apple,pcie.yaml index c0852be04f6ded..c0eb0ec87f946e 100644 --- a/Documentation/devicetree/bindings/pci/apple,pcie.yaml +++ b/Documentation/devicetree/bindings/pci/apple,pcie.yaml @@ -82,6 +82,27 @@ properties: power-domains: maxItems: 1 +patternProperties: + "^pci@": + $ref: /schemas/pci/pci-bus.yaml# + type: object + description: A single PCI root port + + properties: + reg: + maxItems: 1 + + pwren-gpios: + description: Optional GPIO to power on the device + maxItems: 1 + + required: + - reset-gpios + - interrupt-controller + - "#interrupt-cells" + - interrupt-map-mask + - interrupt-map + required: - compatible - reg @@ -161,7 +182,7 @@ examples: pinctrl-0 = <&pcie_pins>; pinctrl-names = "default"; - pci@0,0 { + port00: pci@0,0 { device_type = "pci"; reg = <0x0 0x0 0x0 0x0 0x0>; reset-gpios = <&pinctrl_ap 152 0>; @@ -169,9 +190,17 @@ examples: #address-cells = <3>; #size-cells = <2>; ranges; + + interrupt-controller; + #interrupt-cells = <1>; + interrupt-map-mask = <0 0 0 7>; + interrupt-map = <0 0 0 1 &port00 0 0 0 0>, + <0 0 0 2 &port00 0 0 0 1>, + <0 0 0 3 &port00 0 0 0 2>, + <0 0 0 4 &port00 0 0 0 3>; }; - pci@1,0 { + port01: pci@1,0 { device_type = "pci"; reg = <0x800 0x0 0x0 0x0 0x0>; reset-gpios = <&pinctrl_ap 153 0>; @@ -179,9 +208,17 @@ examples: #address-cells = <3>; #size-cells = <2>; ranges; + + interrupt-controller; + #interrupt-cells = <1>; + interrupt-map-mask = <0 0 0 7>; + interrupt-map = <0 0 0 1 &port01 0 0 0 0>, + <0 0 0 2 &port01 0 0 0 1>, + <0 0 0 3 &port01 0 0 0 2>, + <0 0 0 4 &port01 0 0 0 3>; }; - pci@2,0 { + port02: pci@2,0 { device_type = "pci"; reg = <0x1000 0x0 0x0 0x0 0x0>; reset-gpios = <&pinctrl_ap 33 0>; @@ -189,6 +226,14 @@ examples: #address-cells = <3>; #size-cells = <2>; ranges; + + interrupt-controller; + #interrupt-cells = <1>; + interrupt-map-mask = <0 0 0 7>; + interrupt-map = <0 0 0 1 &port02 0 0 0 0>, + <0 0 0 2 &port02 0 0 0 1>, + <0 0 0 3 &port02 0 0 0 2>, + <0 0 0 4 &port02 0 0 0 3>; }; }; }; From 62193aa3e3012cea48fd70a44736e6ac01d04c64 Mon Sep 17 00:00:00 2001 From: Hector Martin Date: Sun, 6 Feb 2022 21:15:39 +0900 Subject: [PATCH 0246/3902] PCI: apple: Probe all GPIOs for availability first If we're probing the PCI controller and some GPIOs are not available and cause a probe defer, we can end up leaving some ports initialized and not others and making a mess. Check for PERST# GPIOs for all ports first, and just return -EPROBE_DEFER if any are not ready yet, without bringing anything up. Fixes: 1e33888fbe44 ("PCI: apple: Add initial hardware bring-up") Cc: stable@vger.kernel.org Acked-by: Marc Zyngier Signed-off-by: Hector Martin Signed-off-by: Janne Grunau --- drivers/pci/controller/pcie-apple.c | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/drivers/pci/controller/pcie-apple.c b/drivers/pci/controller/pcie-apple.c index 0380d300adca6f..2edeec4fe98288 100644 --- a/drivers/pci/controller/pcie-apple.c +++ b/drivers/pci/controller/pcie-apple.c @@ -872,12 +872,36 @@ static const struct pci_ecam_ops apple_pcie_cfg_ecam_ops = { } }; +static int apple_pcie_probe_port(struct device_node *np) +{ + struct gpio_desc *gd; + + /* check whether the GPPIO pin exists but leave it as is */ + gd = fwnode_gpiod_get_index(of_fwnode_handle(np), "reset", 0, + GPIOD_ASIS, "PERST#"); + if (IS_ERR(gd)) + return PTR_ERR(gd); + + gpiod_put(gd); + return 0; +} + static int apple_pcie_probe(struct platform_device *pdev) { struct device *dev = &pdev->dev; + struct device_node *of_port; struct apple_pcie *pcie; int ret; + /* Check for probe dependencies for all ports first */ + for_each_available_child_of_node(dev->of_node, of_port) { + ret = apple_pcie_probe_port(of_port); + if (ret) { + of_node_put(of_port); + return dev_err_probe(dev, ret, "Port %pOF probe fail\n", of_port); + } + } + pcie = devm_kzalloc(dev, sizeof(*pcie), GFP_KERNEL); if (!pcie) return -ENOMEM; From 6172c2bb9f84957ba7229b10463becc7f0036938 Mon Sep 17 00:00:00 2001 From: Hector Martin Date: Sun, 6 Feb 2022 21:18:18 +0900 Subject: [PATCH 0247/3902] PCI: apple: Add support for optional PWREN GPIO WiFi and SD card devices on M1 Macs have a separate power enable GPIO. Add support for this to the PCIe controller. This is modeled after how pcie-fu740 does it. Acked-by: Marc Zyngier Signed-off-by: Hector Martin --- drivers/pci/controller/pcie-apple.c | 34 ++++++++++++++++++++++++++--- 1 file changed, 31 insertions(+), 3 deletions(-) diff --git a/drivers/pci/controller/pcie-apple.c b/drivers/pci/controller/pcie-apple.c index 2edeec4fe98288..55e7aa95345c57 100644 --- a/drivers/pci/controller/pcie-apple.c +++ b/drivers/pci/controller/pcie-apple.c @@ -559,7 +559,7 @@ static int apple_pcie_setup_port(struct apple_pcie *pcie, { struct platform_device *platform = to_platform_device(pcie->dev); struct apple_pcie_port *port; - struct gpio_desc *reset; + struct gpio_desc *reset, *pwren = NULL; struct resource *res; char name[16]; u32 stat, idx; @@ -570,6 +570,15 @@ static int apple_pcie_setup_port(struct apple_pcie *pcie, if (IS_ERR(reset)) return PTR_ERR(reset); + pwren = devm_fwnode_gpiod_get(pcie->dev, of_fwnode_handle(np), "pwren", + GPIOD_ASIS, "PWREN"); + if (IS_ERR(pwren)) { + if (PTR_ERR(pwren) == -ENOENT) + pwren = NULL; + else + return PTR_ERR(pwren); + } + port = devm_kzalloc(pcie->dev, sizeof(*port), GFP_KERNEL); if (!port) return -ENOMEM; @@ -610,12 +619,21 @@ static int apple_pcie_setup_port(struct apple_pcie *pcie, /* Assert PERST# before setting up the clock */ gpiod_set_value_cansleep(reset, 1); + /* Power on the device if required */ + gpiod_set_value_cansleep(pwren, 1); + ret = apple_pcie_setup_refclk(pcie, port); if (ret < 0) return ret; - /* The minimal Tperst-clk value is 100us (PCIe CEM r5.0, 2.9.2) */ - usleep_range(100, 200); + /* + * The minimal Tperst-clk value is 100us (PCIe CEM r5.0, 2.9.2) + * If powering up, the minimal Tpvperl is 100ms + */ + if (pwren) + msleep(100); + else + usleep_range(100, 200); /* Deassert PERST# */ rmw_set(PORT_PERST_OFF, port->base + pcie->hw->port_perst); @@ -883,6 +901,16 @@ static int apple_pcie_probe_port(struct device_node *np) return PTR_ERR(gd); gpiod_put(gd); + + gd = fwnode_gpiod_get_index(of_fwnode_handle(np), "pwren", 0, + GPIOD_ASIS, "PWREN"); + if (IS_ERR(gd)) { + if (PTR_ERR(gd) != -ENOENT) + return PTR_ERR(gd); + } else { + gpiod_put(gd); + } + return 0; } From 1a6b96f247757b8426fe2357ea2a6bd1a639da2c Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Thu, 20 Apr 2023 23:24:59 +0200 Subject: [PATCH 0248/3902] PCI: apple: Skip controller port setup for online links U-boot gained recently support for PCIe controller on Apple silicon devices. It is currently unkown how to reset / retrain already brought up ports. Redoing the controller level setup breaks the links. Check the link status before performing controller level port/link setup. Link: https://lore.kernel.org/u-boot/20230121192800.82428-1-kettenis@openbsd.org/ Signed-off-by: Janne Grunau --- drivers/pci/controller/pcie-apple.c | 102 +++++++++++++++++----------- 1 file changed, 62 insertions(+), 40 deletions(-) diff --git a/drivers/pci/controller/pcie-apple.c b/drivers/pci/controller/pcie-apple.c index 55e7aa95345c57..3e2409f1025898 100644 --- a/drivers/pci/controller/pcie-apple.c +++ b/drivers/pci/controller/pcie-apple.c @@ -554,16 +554,13 @@ static u32 apple_pcie_rid2sid_write(struct apple_pcie_port *port, return readl_relaxed(port_rid2sid_addr(port, idx)); } -static int apple_pcie_setup_port(struct apple_pcie *pcie, +static int apple_pcie_setup_link(struct apple_pcie *pcie, + struct apple_pcie_port *port, struct device_node *np) { - struct platform_device *platform = to_platform_device(pcie->dev); - struct apple_pcie_port *port; struct gpio_desc *reset, *pwren = NULL; - struct resource *res; - char name[16]; - u32 stat, idx; - int ret, i; + u32 stat; + int ret; reset = devm_fwnode_gpiod_get(pcie->dev, of_fwnode_handle(np), "reset", GPIOD_OUT_LOW, "PERST#"); @@ -579,6 +576,54 @@ static int apple_pcie_setup_port(struct apple_pcie *pcie, return PTR_ERR(pwren); } + rmw_set(PORT_APPCLK_EN, port->base + PORT_APPCLK); + + /* Assert PERST# before setting up the clock */ + gpiod_set_value_cansleep(reset, 1); + + /* Power on the device if required */ + gpiod_set_value_cansleep(pwren, 1); + + ret = apple_pcie_setup_refclk(pcie, port); + if (ret < 0) + return ret; + + /* + * The minimal Tperst-clk value is 100us (PCIe CEM r5.0, 2.9.2) + * If powering up, the minimal Tpvperl is 100ms + */ + if (pwren) + msleep(100); + else + usleep_range(100, 200); + + /* Deassert PERST# */ + rmw_set(PORT_PERST_OFF, port->base + pcie->hw->port_perst); + gpiod_set_value_cansleep(reset, 0); + + /* Wait for 100ms after PERST# deassertion (PCIe r5.0, 6.6.1) */ + msleep(100); + + ret = readl_relaxed_poll_timeout(port->base + PORT_STATUS, stat, + stat & PORT_STATUS_READY, 100, 250000); + if (ret < 0) { + dev_err(pcie->dev, "port %pOF ready wait timeout\n", np); + return ret; + } + + return 0; +} + +static int apple_pcie_setup_port(struct apple_pcie *pcie, + struct device_node *np) +{ + struct platform_device *platform = to_platform_device(pcie->dev); + struct apple_pcie_port *port; + struct resource *res; + char name[16]; + u32 link_stat, idx; + int ret, i; + port = devm_kzalloc(pcie->dev, sizeof(*port), GFP_KERNEL); if (!port) return -ENOMEM; @@ -614,39 +659,12 @@ static int apple_pcie_setup_port(struct apple_pcie *pcie, else port->phy = pcie->base + CORE_PHY_DEFAULT_BASE(port->idx); - rmw_set(PORT_APPCLK_EN, port->base + PORT_APPCLK); - - /* Assert PERST# before setting up the clock */ - gpiod_set_value_cansleep(reset, 1); - - /* Power on the device if required */ - gpiod_set_value_cansleep(pwren, 1); - - ret = apple_pcie_setup_refclk(pcie, port); - if (ret < 0) - return ret; - - /* - * The minimal Tperst-clk value is 100us (PCIe CEM r5.0, 2.9.2) - * If powering up, the minimal Tpvperl is 100ms - */ - if (pwren) - msleep(100); - else - usleep_range(100, 200); - - /* Deassert PERST# */ - rmw_set(PORT_PERST_OFF, port->base + pcie->hw->port_perst); - gpiod_set_value_cansleep(reset, 0); - - /* Wait for 100ms after PERST# deassertion (PCIe r5.0, 6.6.1) */ - msleep(100); - - ret = readl_relaxed_poll_timeout(port->base + PORT_STATUS, stat, - stat & PORT_STATUS_READY, 100, 250000); - if (ret < 0) { - dev_err(pcie->dev, "port %pOF ready wait timeout\n", np); - return ret; + /* link might be already brought up by u-boot, skip setup then */ + link_stat = readl_relaxed(port->base + PORT_LINKSTS); + if (!(link_stat & PORT_LINKSTS_UP)) { + ret = apple_pcie_setup_link(pcie, port, np); + if (ret) + return ret; } if (pcie->hw->port_refclk) @@ -680,6 +698,10 @@ static int apple_pcie_setup_port(struct apple_pcie *pcie, ret = apple_pcie_port_register_irqs(port); WARN_ON(ret); + if (link_stat & PORT_LINKSTS_UP) + return 0; + + /* start link training */ writel_relaxed(PORT_LTSSMCTL_START, port->base + PORT_LTSSMCTL); if (!wait_for_completion_timeout(&pcie->event, HZ / 10)) From 035d7afb15032510e3108f525abc33e6d4194010 Mon Sep 17 00:00:00 2001 From: Hector Martin Date: Thu, 18 May 2023 16:12:39 +0900 Subject: [PATCH 0249/3902] PCI: apple: Make link up timeout configurable, default to 500ms We're seeing link up timeouts and it looks like devices are just too slow. Let's just increase this. Signed-off-by: Hector Martin --- drivers/pci/controller/pcie-apple.c | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/drivers/pci/controller/pcie-apple.c b/drivers/pci/controller/pcie-apple.c index 3e2409f1025898..e2c3eea5c035bd 100644 --- a/drivers/pci/controller/pcie-apple.c +++ b/drivers/pci/controller/pcie-apple.c @@ -33,6 +33,10 @@ #include "pci-host-common.h" +static int link_up_timeout = 500; +module_param(link_up_timeout, int, 0644); +MODULE_PARM_DESC(link_up_timeout, "PCIe link training timeout in milliseconds"); + /* T8103 (original M1) and related SoCs */ #define CORE_RC_PHYIF_CTL 0x00024 #define CORE_RC_PHYIF_CTL_RUN BIT(0) @@ -704,7 +708,7 @@ static int apple_pcie_setup_port(struct apple_pcie *pcie, /* start link training */ writel_relaxed(PORT_LTSSMCTL_START, port->base + PORT_LTSSMCTL); - if (!wait_for_completion_timeout(&pcie->event, HZ / 10)) + if (!wait_for_completion_timeout(&pcie->event, link_up_timeout * HZ / 1000)) dev_warn(pcie->dev, "%pOF link didn't come up\n", np); return 0; From 975d8123bd39e17f9836d6e4d3f9c7b4b5bcd3dc Mon Sep 17 00:00:00 2001 From: Hector Martin Date: Thu, 18 May 2023 16:18:29 +0900 Subject: [PATCH 0250/3902] PCI: apple: Reorder & improve link-up logic Always re-check LINKSTS right before deciding whether to start the link training and wait for it, just in case the link happened to come up while we were setting up IRQs. Also, always do the clock-gate disable even if the link is already up. Signed-off-by: Hector Martin --- drivers/pci/controller/pcie-apple.c | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/drivers/pci/controller/pcie-apple.c b/drivers/pci/controller/pcie-apple.c index e2c3eea5c035bd..9c0984dc2e40a1 100644 --- a/drivers/pci/controller/pcie-apple.c +++ b/drivers/pci/controller/pcie-apple.c @@ -702,14 +702,14 @@ static int apple_pcie_setup_port(struct apple_pcie *pcie, ret = apple_pcie_port_register_irqs(port); WARN_ON(ret); - if (link_stat & PORT_LINKSTS_UP) - return 0; - - /* start link training */ - writel_relaxed(PORT_LTSSMCTL_START, port->base + PORT_LTSSMCTL); + link_stat = readl_relaxed(port->base + PORT_LINKSTS); + if (!(link_stat & PORT_LINKSTS_UP)) { + /* start link training */ + writel_relaxed(PORT_LTSSMCTL_START, port->base + PORT_LTSSMCTL); - if (!wait_for_completion_timeout(&pcie->event, link_up_timeout * HZ / 1000)) - dev_warn(pcie->dev, "%pOF link didn't come up\n", np); + if (!wait_for_completion_timeout(&pcie->event, link_up_timeout * HZ / 1000)) + dev_warn(pcie->dev, "%pOF link didn't come up\n", np); + } return 0; } From ed572d2dc315b71769fd78e18fbbe115040de600 Mon Sep 17 00:00:00 2001 From: Hector Martin Date: Thu, 18 May 2023 17:03:32 +0900 Subject: [PATCH 0251/3902] PCI: apple: Log the time it takes for links to come up Signed-off-by: Hector Martin --- drivers/pci/controller/pcie-apple.c | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/drivers/pci/controller/pcie-apple.c b/drivers/pci/controller/pcie-apple.c index 9c0984dc2e40a1..df775f3b5e8aae 100644 --- a/drivers/pci/controller/pcie-apple.c +++ b/drivers/pci/controller/pcie-apple.c @@ -704,11 +704,18 @@ static int apple_pcie_setup_port(struct apple_pcie *pcie, link_stat = readl_relaxed(port->base + PORT_LINKSTS); if (!(link_stat & PORT_LINKSTS_UP)) { + unsigned long timeout, left; /* start link training */ writel_relaxed(PORT_LTSSMCTL_START, port->base + PORT_LTSSMCTL); - if (!wait_for_completion_timeout(&pcie->event, link_up_timeout * HZ / 1000)) + timeout = link_up_timeout * HZ / 1000; + left = wait_for_completion_timeout(&pcie->event, timeout); + if (!left) dev_warn(pcie->dev, "%pOF link didn't come up\n", np); + else + dev_info(pcie->dev, "%pOF link up after %ldms\n", np, + (timeout - left) * 1000 / HZ); + } return 0; From 406c5b1c8fc2f71014983e25cf4fb64adddf6b7b Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Mon, 9 Sep 2024 18:23:04 +0200 Subject: [PATCH 0252/3902] PCI: apple: Avoid PERST# deassertion through gpiod initialization The Aquantia AQC113 10GB ethernet device used in Apple silicon Mac Studio, Mac Pro and as option in Mac mini is sensitive to PERST# deassertion before clock setup. The perst pins are defined as GPIO_ACTIVE_LOW in the device tree. GPIOD_OUT_LOW will deassert the PERST# pin. This breaks the link setup reliably under m1n1's hypervisor on a M1 Ultra Mac Studio. There might have been reports of unavailable 10GB NICs before u-boot took over the PCIe link setup. Signed-off-by: Janne Grunau Fixes: a6b9ede1f3df ("PCI: apple: Do not leak reset GPIO on unbind/unload/error") Fixes: 1e33888fbe44 ("PCI: apple: Add initial hardware bring-up") --- drivers/pci/controller/pcie-apple.c | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/drivers/pci/controller/pcie-apple.c b/drivers/pci/controller/pcie-apple.c index df775f3b5e8aae..3e47f4ba872230 100644 --- a/drivers/pci/controller/pcie-apple.c +++ b/drivers/pci/controller/pcie-apple.c @@ -566,8 +566,14 @@ static int apple_pcie_setup_link(struct apple_pcie *pcie, u32 stat; int ret; + /* + * Assert PERST# and configure the pin as output. + * The Aquantia AQC113 10GB nic used desktop macs is sensitive to + * deasserting it without prior clock setup. + * Observed on M1 Max/Ultra Mac Studios under m1n1's hypervisor. + */ reset = devm_fwnode_gpiod_get(pcie->dev, of_fwnode_handle(np), "reset", - GPIOD_OUT_LOW, "PERST#"); + GPIOD_OUT_HIGH, "PERST#"); if (IS_ERR(reset)) return PTR_ERR(reset); From d425de580fa8109d671d6ed5ccd8961a73fe08f5 Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Mon, 28 Jul 2025 23:05:05 +0200 Subject: [PATCH 0253/3902] NOT-FOR-UPSTREAM: PCI: apple: Use up to 4 "reset-gpios" This brings both ASM3142 PCIe xHCI and the Wlan/BT controller in the Mac Pro (M2 Ultra, 2023) online. Handle the device reset-gpios as auxiliary ones until this can be replaced once "PCI/pwrctrl: Allow pwrctrl framework to control PERST# GPIO if available" [1] is upstream. 1: https://lore.kernel.org/linux-pci/20250707-pci-pwrctrl-perst-v1-0-c3c7e513e312@kernel.org/ Signed-off-by: Janne Grunau --- drivers/pci/controller/pcie-apple.c | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/drivers/pci/controller/pcie-apple.c b/drivers/pci/controller/pcie-apple.c index 3e47f4ba872230..05c5dd47f32e72 100644 --- a/drivers/pci/controller/pcie-apple.c +++ b/drivers/pci/controller/pcie-apple.c @@ -562,6 +562,9 @@ static int apple_pcie_setup_link(struct apple_pcie *pcie, struct apple_pcie_port *port, struct device_node *np) { +#define MAX_AUX_PERST 3 + struct gpio_desc *aux_reset[MAX_AUX_PERST] = { NULL }; + u32 num_aux_resets = 0; struct gpio_desc *reset, *pwren = NULL; u32 stat; int ret; @@ -576,6 +579,22 @@ static int apple_pcie_setup_link(struct apple_pcie *pcie, GPIOD_OUT_HIGH, "PERST#"); if (IS_ERR(reset)) return PTR_ERR(reset); + // HACK: use additional "reset-gpios" until pci-pwrctrl gains PERST# support. + for (u32 idx = 0; idx < MAX_AUX_PERST; idx++) { + aux_reset[idx] = devm_fwnode_gpiod_get_index(pcie->dev, + of_fwnode_handle(np), + "reset", idx + 1, + GPIOD_OUT_HIGH, + "PERST#"); + if (IS_ERR(aux_reset[idx])) { + if (PTR_ERR(aux_reset[idx]) == -ENOENT) + break; + else + return PTR_ERR(aux_reset[idx]); + } + num_aux_resets++; + } + dev_info(pcie->dev, "Using %u auxiliary PERST#\n", num_aux_resets); pwren = devm_fwnode_gpiod_get(pcie->dev, of_fwnode_handle(np), "pwren", GPIOD_ASIS, "PWREN"); @@ -590,6 +609,8 @@ static int apple_pcie_setup_link(struct apple_pcie *pcie, /* Assert PERST# before setting up the clock */ gpiod_set_value_cansleep(reset, 1); + for (u32 idx = 0; idx < num_aux_resets; idx++) + gpiod_set_value_cansleep(aux_reset[idx], 1); /* Power on the device if required */ gpiod_set_value_cansleep(pwren, 1); @@ -610,6 +631,8 @@ static int apple_pcie_setup_link(struct apple_pcie *pcie, /* Deassert PERST# */ rmw_set(PORT_PERST_OFF, port->base + pcie->hw->port_perst); gpiod_set_value_cansleep(reset, 0); + for (u32 idx = 0; idx < num_aux_resets; idx++) + gpiod_set_value_cansleep(aux_reset[idx], 0); /* Wait for 100ms after PERST# deassertion (PCIe r5.0, 6.6.1) */ msleep(100); From 073e770352c1959c7268ed625331a74dc5b7e5dc Mon Sep 17 00:00:00 2001 From: Hector Martin Date: Tue, 30 Aug 2022 02:11:48 +0900 Subject: [PATCH 0254/3902] xhci-pci: asmedia: Add a firmware loader for ASM2214a chips Apple ships ASM2214a ICs in some Apple Silicon hardware (notably, the 2021 iMac and the 2022 Mac Studio) without a flash ROM, and relies on the OS to load firmware on startup. Add support for this to the generic xhci-pci driver. The loader code first checks the firmware version, and only attempts to load firmware if the version isn't the known ROM version. Since this arrangement only exists on Apple machines so far, and Apple are the only source of the (non-redistributable) firmware intended for use on these machines, the firmware is named asmedia/asm2214a-apple.bin. If this style of firmware loading ever becomes necessary on non-Apple machines, we should add a generic firmware name at the time (if it can be part of linux-firmware) or another vendor-specific firmware name. Signed-off-by: Hector Martin --- drivers/usb/host/Kconfig | 9 + drivers/usb/host/Makefile | 2 + drivers/usb/host/xhci-pci-asmedia.c | 392 ++++++++++++++++++ .../usb/host/{xhci-pci.c => xhci-pci-core.c} | 25 ++ drivers/usb/host/xhci-pci.h | 18 + drivers/usb/host/xhci.h | 1 + 6 files changed, 447 insertions(+) create mode 100644 drivers/usb/host/xhci-pci-asmedia.c rename drivers/usb/host/{xhci-pci.c => xhci-pci-core.c} (97%) diff --git a/drivers/usb/host/Kconfig b/drivers/usb/host/Kconfig index c4f17ce5c77b15..5f3ecce8398b39 100644 --- a/drivers/usb/host/Kconfig +++ b/drivers/usb/host/Kconfig @@ -51,6 +51,15 @@ config USB_XHCI_PCI_RENESAS installed on your system for this device to work. If unsure, say 'N'. +config USB_XHCI_PCI_ASMEDIA + bool "Support firmware loading for ASMedia xHCI controllers" + default USB_XHCI_PCI if ARCH_APPLE + depends on USB_XHCI_PCI + help + Say 'Y' to enable support for ASMedia xHCI controllers with + host-supplied firmware. These are usually present on Apple devices. + If unsure, say 'N'. + config USB_XHCI_PLATFORM tristate "Generic xHCI driver for a platform device" help diff --git a/drivers/usb/host/Makefile b/drivers/usb/host/Makefile index 4df946c05ba0ee..a197e0979f89bd 100644 --- a/drivers/usb/host/Makefile +++ b/drivers/usb/host/Makefile @@ -72,6 +72,8 @@ obj-$(CONFIG_USB_UHCI_HCD) += uhci-hcd.o obj-$(CONFIG_USB_FHCI_HCD) += fhci.o obj-$(CONFIG_USB_XHCI_HCD) += xhci-hcd.o obj-$(CONFIG_USB_XHCI_PCI) += xhci-pci.o +xhci-pci-y += xhci-pci-core.o +xhci-pci-$(CONFIG_USB_XHCI_PCI_ASMEDIA) += xhci-pci-asmedia.o obj-$(CONFIG_USB_XHCI_PCI_RENESAS) += xhci-pci-renesas.o obj-$(CONFIG_USB_XHCI_PLATFORM) += xhci-plat-hcd.o obj-$(CONFIG_USB_XHCI_HISTB) += xhci-histb.o diff --git a/drivers/usb/host/xhci-pci-asmedia.c b/drivers/usb/host/xhci-pci-asmedia.c new file mode 100644 index 00000000000000..36cdd962fea06b --- /dev/null +++ b/drivers/usb/host/xhci-pci-asmedia.c @@ -0,0 +1,392 @@ +// SPDX-License-Identifier: GPL-2.0 OR MIT +/* + * ASMedia xHCI firmware loader + * Copyright (C) The Asahi Linux Contributors + */ + +#include +#include +#include +#include +#include +#include + +#include "xhci.h" +#include "xhci-trace.h" +#include "xhci-pci.h" + +/* Configuration space registers */ +#define ASMT_CFG_CONTROL 0xe0 +#define ASMT_CFG_CONTROL_WRITE BIT(1) +#define ASMT_CFG_CONTROL_READ BIT(0) + +#define ASMT_CFG_SRAM_ADDR 0xe2 + +#define ASMT_CFG_SRAM_ACCESS 0xef +#define ASMT_CFG_SRAM_ACCESS_READ BIT(6) +#define ASMT_CFG_SRAM_ACCESS_ENABLE BIT(7) + +#define ASMT_CFG_DATA_READ0 0xf0 +#define ASMT_CFG_DATA_READ1 0xf4 + +#define ASMT_CFG_DATA_WRITE0 0xf8 +#define ASMT_CFG_DATA_WRITE1 0xfc + +#define ASMT_CMD_GET_FWVER 0x8000060840 +#define ASMT_FWVER_ROM 0x010250090816 + +/* BAR0 registers */ +#define ASMT_REG_ADDR 0x3000 + +#define ASMT_REG_WDATA 0x3004 +#define ASMT_REG_RDATA 0x3008 + +#define ASMT_REG_STATUS 0x3009 +#define ASMT_REG_STATUS_BUSY BIT(7) + +#define ASMT_REG_CODE_WDATA 0x3010 +#define ASMT_REG_CODE_RDATA 0x3018 + +#define ASMT_MMIO_CPU_MISC 0x500e +#define ASMT_MMIO_CPU_MISC_CODE_RAM_WR BIT(0) + +#define ASMT_MMIO_CPU_MODE_NEXT 0x5040 +#define ASMT_MMIO_CPU_MODE_CUR 0x5041 + +#define ASMT_MMIO_CPU_MODE_RAM BIT(0) +#define ASMT_MMIO_CPU_MODE_HALFSPEED BIT(1) + +#define ASMT_MMIO_CPU_EXEC_CTRL 0x5042 +#define ASMT_MMIO_CPU_EXEC_CTRL_RESET BIT(0) +#define ASMT_MMIO_CPU_EXEC_CTRL_HALT BIT(1) + +#define TIMEOUT_USEC 10000 +#define RESET_TIMEOUT_USEC 500000 + +static int asmedia_mbox_tx(struct pci_dev *pdev, u64 data) +{ + u8 op; + int i; + + for (i = 0; i < TIMEOUT_USEC; i++) { + pci_read_config_byte(pdev, ASMT_CFG_CONTROL, &op); + if (!(op & ASMT_CFG_CONTROL_WRITE)) + break; + udelay(1); + } + + if (op & ASMT_CFG_CONTROL_WRITE) { + dev_err(&pdev->dev, + "Timed out on mailbox tx: 0x%llx\n", + data); + return -ETIMEDOUT; + } + + pci_write_config_dword(pdev, ASMT_CFG_DATA_WRITE0, data); + pci_write_config_dword(pdev, ASMT_CFG_DATA_WRITE1, data >> 32); + pci_write_config_byte(pdev, ASMT_CFG_CONTROL, ASMT_CFG_CONTROL_WRITE); + + return 0; +} + +static int asmedia_mbox_rx(struct pci_dev *pdev, u64 *data) +{ + u8 op; + u32 low, high; + int i; + + for (i = 0; i < TIMEOUT_USEC; i++) { + pci_read_config_byte(pdev, ASMT_CFG_CONTROL, &op); + if (op & ASMT_CFG_CONTROL_READ) + break; + udelay(1); + } + + if (!(op & ASMT_CFG_CONTROL_READ)) { + dev_err(&pdev->dev, "Timed out on mailbox rx\n"); + return -ETIMEDOUT; + } + + pci_read_config_dword(pdev, ASMT_CFG_DATA_READ0, &low); + pci_read_config_dword(pdev, ASMT_CFG_DATA_READ1, &high); + pci_write_config_byte(pdev, ASMT_CFG_CONTROL, ASMT_CFG_CONTROL_READ); + + *data = ((u64)high << 32) | low; + return 0; +} + +static int asmedia_get_fw_version(struct pci_dev *pdev, u64 *version) +{ + int err = 0; + u64 cmd; + + err = asmedia_mbox_tx(pdev, ASMT_CMD_GET_FWVER); + if (err) + return err; + err = asmedia_mbox_tx(pdev, 0); + if (err) + return err; + + err = asmedia_mbox_rx(pdev, &cmd); + if (err) + return err; + err = asmedia_mbox_rx(pdev, version); + if (err) + return err; + + if (cmd != ASMT_CMD_GET_FWVER) { + dev_err(&pdev->dev, "Unexpected reply command 0x%llx\n", cmd); + return -EIO; + } + + return 0; +} + +static bool asmedia_check_firmware(struct pci_dev *pdev) +{ + u64 fwver; + int ret; + + ret = asmedia_get_fw_version(pdev, &fwver); + if (ret) + return ret; + + dev_info(&pdev->dev, "Firmware version: 0x%llx\n", fwver); + + return fwver != ASMT_FWVER_ROM; +} + +static int asmedia_wait_reset(struct pci_dev *pdev) +{ + struct usb_hcd *hcd = dev_get_drvdata(&pdev->dev); + struct xhci_cap_regs __iomem *cap = hcd->regs; + struct xhci_op_regs __iomem *op; + u32 val; + int ret; + + op = hcd->regs + HC_LENGTH(readl(&cap->hc_capbase)); + + ret = readl_poll_timeout(&op->command, + val, !(val & CMD_RESET), + 1000, RESET_TIMEOUT_USEC); + + if (!ret) + return 0; + + dev_err(hcd->self.controller, "Reset timed out, trying to kick it\n"); + + pci_write_config_byte(pdev, ASMT_CFG_SRAM_ACCESS, + ASMT_CFG_SRAM_ACCESS_ENABLE); + + pci_write_config_byte(pdev, ASMT_CFG_SRAM_ACCESS, 0); + + ret = readl_poll_timeout(&op->command, + val, !(val & CMD_RESET), + 1000, RESET_TIMEOUT_USEC); + + if (ret) + dev_err(hcd->self.controller, "Reset timed out, giving up\n"); + + return ret; +} + +static u8 asmedia_read_reg(struct usb_hcd *hcd, u16 addr) { + void __iomem *regs = hcd->regs; + u8 status; + int ret; + + ret = readb_poll_timeout(regs + ASMT_REG_STATUS, status, + !(status & ASMT_REG_STATUS_BUSY), + 1000, TIMEOUT_USEC); + + if (ret) { + dev_err(hcd->self.controller, + "Read reg wait timed out ([%04x])\n", addr); + return ~0; + } + + writew_relaxed(addr, regs + ASMT_REG_ADDR); + + ret = readb_poll_timeout(regs + ASMT_REG_STATUS, status, + !(status & ASMT_REG_STATUS_BUSY), + 1000, TIMEOUT_USEC); + + if (ret) { + dev_err(hcd->self.controller, + "Read reg addr timed out ([%04x])\n", addr); + return ~0; + } + + return readb_relaxed(regs + ASMT_REG_RDATA); +} + +static void asmedia_write_reg(struct usb_hcd *hcd, u16 addr, u8 data, bool wait) { + void __iomem *regs = hcd->regs; + u8 status; + int ret, i; + + writew_relaxed(addr, regs + ASMT_REG_ADDR); + + ret = readb_poll_timeout(regs + ASMT_REG_STATUS, status, + !(status & ASMT_REG_STATUS_BUSY), + 1000, TIMEOUT_USEC); + + if (ret) + dev_err(hcd->self.controller, + "Write reg addr timed out ([%04x] = %02x)\n", + addr, data); + + writeb_relaxed(data, regs + ASMT_REG_WDATA); + + ret = readb_poll_timeout(regs + ASMT_REG_STATUS, status, + !(status & ASMT_REG_STATUS_BUSY), + 1000, TIMEOUT_USEC); + + if (ret) + dev_err(hcd->self.controller, + "Write reg data timed out ([%04x] = %02x)\n", + addr, data); + + if (!wait) + return; + + for (i = 0; i < TIMEOUT_USEC; i++) { + if (asmedia_read_reg(hcd, addr) == data) + break; + } + + if (i >= TIMEOUT_USEC) { + dev_err(hcd->self.controller, + "Verify register timed out ([%04x] = %02x)\n", + addr, data); + } +} + +static int asmedia_load_fw(struct pci_dev *pdev, const struct firmware *fw) +{ + struct usb_hcd *hcd; + void __iomem *regs; + const u16 *fw_data = (const u16 *)fw->data; + u16 raddr; + u32 data; + size_t index = 0, addr = 0; + size_t words = fw->size >> 1; + int ret, i; + + hcd = dev_get_drvdata(&pdev->dev); + regs = hcd->regs; + + asmedia_write_reg(hcd, ASMT_MMIO_CPU_MODE_NEXT, + ASMT_MMIO_CPU_MODE_HALFSPEED, false); + + asmedia_write_reg(hcd, ASMT_MMIO_CPU_EXEC_CTRL, + ASMT_MMIO_CPU_EXEC_CTRL_RESET, false); + + ret = asmedia_wait_reset(pdev); + if (ret) { + dev_err(hcd->self.controller, "Failed pre-upload reset\n"); + return ret; + } + + asmedia_write_reg(hcd, ASMT_MMIO_CPU_EXEC_CTRL, + ASMT_MMIO_CPU_EXEC_CTRL_HALT, false); + + asmedia_write_reg(hcd, ASMT_MMIO_CPU_MISC, + ASMT_MMIO_CPU_MISC_CODE_RAM_WR, true); + + pci_write_config_byte(pdev, ASMT_CFG_SRAM_ACCESS, + ASMT_CFG_SRAM_ACCESS_ENABLE); + + /* The firmware upload is interleaved in 0x4000 word blocks */ + addr = index = 0; + while (index < words) { + data = fw_data[index]; + if ((index | 0x4000) < words) + data |= fw_data[index | 0x4000] << 16; + + pci_write_config_word(pdev, ASMT_CFG_SRAM_ADDR, + addr); + + writel_relaxed(data, regs + ASMT_REG_CODE_WDATA); + + for (i = 0; i < TIMEOUT_USEC; i++) { + pci_read_config_word(pdev, ASMT_CFG_SRAM_ADDR, &raddr); + if (raddr != addr) + break; + udelay(1); + } + + if (raddr == addr) { + dev_err(hcd->self.controller, "Word write timed out\n"); + return -ETIMEDOUT; + } + + if (++index & 0x4000) + index += 0x4000; + addr += 2; + } + + pci_write_config_byte(pdev, ASMT_CFG_SRAM_ACCESS, 0); + + asmedia_write_reg(hcd, ASMT_MMIO_CPU_MISC, 0, true); + + asmedia_write_reg(hcd, ASMT_MMIO_CPU_MODE_NEXT, + ASMT_MMIO_CPU_MODE_RAM | + ASMT_MMIO_CPU_MODE_HALFSPEED, false); + + asmedia_write_reg(hcd, ASMT_MMIO_CPU_EXEC_CTRL, 0, false); + + ret = asmedia_wait_reset(pdev); + if (ret) { + dev_err(hcd->self.controller, "Failed post-upload reset\n"); + return ret; + } + + return 0; +} + +int asmedia_xhci_check_request_fw(struct pci_dev *pdev, + const struct pci_device_id *id) +{ + struct xhci_driver_data *driver_data = + (struct xhci_driver_data *)id->driver_data; + const char *fw_name = driver_data->firmware; + const struct firmware *fw; + int ret; + + /* Check if device has firmware, if so skip everything */ + ret = asmedia_check_firmware(pdev); + if (ret < 0) + return ret; + else if (ret == 1) + return 0; + + pci_dev_get(pdev); + ret = request_firmware(&fw, fw_name, &pdev->dev); + pci_dev_put(pdev); + if (ret) { + dev_err(&pdev->dev, "Could not load firmware %s: %d\n", + fw_name, ret); + return ret; + } + + ret = asmedia_load_fw(pdev, fw); + if (ret) { + dev_err(&pdev->dev, "Firmware upload failed: %d\n", ret); + goto err; + } + + ret = asmedia_check_firmware(pdev); + if (ret < 0) { + goto err; + } else if (ret != 1) { + dev_err(&pdev->dev, "Firmware version is too old after upload\n"); + ret = -EIO; + } else { + ret = 0; + } + +err: + release_firmware(fw); + return ret; +} diff --git a/drivers/usb/host/xhci-pci.c b/drivers/usb/host/xhci-pci-core.c similarity index 97% rename from drivers/usb/host/xhci-pci.c rename to drivers/usb/host/xhci-pci-core.c index f67a4d9562046f..89bc33aa4ce991 100644 --- a/drivers/usb/host/xhci-pci.c +++ b/drivers/usb/host/xhci-pci-core.c @@ -569,6 +569,18 @@ static int xhci_pci_setup(struct usb_hcd *hcd) struct pci_dev *pdev = to_pci_dev(hcd->self.controller); int retval; u8 sbrn; + struct xhci_driver_data *driver_data; + const struct pci_device_id *id; + + id = pci_match_id(to_pci_driver(pdev->dev.driver)->id_table, pdev); + if (id && id->driver_data && usb_hcd_is_primary_hcd(hcd)) { + driver_data = (struct xhci_driver_data *)id->driver_data; + if (driver_data->quirks & XHCI_ASMEDIA_FW_QUIRK) { + retval = asmedia_xhci_check_request_fw(pdev, id); + if (retval < 0) + return retval; + } + } xhci = hcd_to_xhci(hcd); @@ -938,10 +950,19 @@ static void xhci_pci_shutdown(struct usb_hcd *hcd) pci_set_power_state(pdev, PCI_D3hot); } +#define ASMEDIA_APPLE_FW_NAME "asmedia/asm2214a-apple.bin" + /*-------------------------------------------------------------------------*/ +static const struct xhci_driver_data asmedia_data = { + .quirks = XHCI_ASMEDIA_FW_QUIRK, + .firmware = ASMEDIA_APPLE_FW_NAME, +}; /* PCI driver selection metadata; PCI hotplugging uses this */ static const struct pci_device_id pci_ids[] = { + { PCI_DEVICE(PCI_VENDOR_ID_ASMEDIA, 0x2142), + .driver_data = (unsigned long)&asmedia_data, + }, /* handle any USB 3.0 xHCI controller */ { PCI_DEVICE_CLASS(PCI_CLASS_SERIAL_USB_XHCI, ~0), }, @@ -949,6 +970,10 @@ static const struct pci_device_id pci_ids[] = { }; MODULE_DEVICE_TABLE(pci, pci_ids); +#if IS_ENABLED(CONFIG_USB_XHCI_PCI_ASMEDIA) +MODULE_FIRMWARE(ASMEDIA_APPLE_FW_NAME); +#endif + /* pci driver glue; this is a "new style" PCI driver module */ static struct pci_driver xhci_pci_driver = { .name = hcd_name, diff --git a/drivers/usb/host/xhci-pci.h b/drivers/usb/host/xhci-pci.h index e87c7d9d76b8e2..452908d1c069ba 100644 --- a/drivers/usb/host/xhci-pci.h +++ b/drivers/usb/host/xhci-pci.h @@ -7,4 +7,22 @@ int xhci_pci_common_probe(struct pci_dev *dev, const struct pci_device_id *id); void xhci_pci_remove(struct pci_dev *dev); +struct xhci_driver_data { + u64 quirks; + const char *firmware; +}; + +#if IS_ENABLED(CONFIG_USB_XHCI_PCI_ASMEDIA) +int asmedia_xhci_check_request_fw(struct pci_dev *dev, + const struct pci_device_id *id); + +#else +static inline int asmedia_xhci_check_request_fw(struct pci_dev *dev, + const struct pci_device_id *id) +{ + return 0; +} + +#endif + #endif diff --git a/drivers/usb/host/xhci.h b/drivers/usb/host/xhci.h index 58a51f09cceb8f..78c71d99d104c4 100644 --- a/drivers/usb/host/xhci.h +++ b/drivers/usb/host/xhci.h @@ -1645,6 +1645,7 @@ struct xhci_hcd { #define XHCI_CDNS_SCTX_QUIRK BIT_ULL(48) #define XHCI_ETRON_HOST BIT_ULL(49) #define XHCI_LIMIT_ENDPOINT_INTERVAL_9 BIT_ULL(50) +#define XHCI_ASMEDIA_FW_QUIRK BIT_ULL(51) unsigned int num_active_eps; unsigned int limit_active_eps; From 0ec70c5aa0c9103953747b679c5afa21576fbc7a Mon Sep 17 00:00:00 2001 From: Mark Kettenis Date: Fri, 19 Sep 2025 22:00:08 +0200 Subject: [PATCH 0255/3902] xhci-pci: asmedia: {read,write}_reg changes from u-boot review Change asmedia_read_reg and asmedia_write_reg to return error codes. The read value for asmedia_read_reg is passed via pointer. Signed-off-by: Mark Kettenis Signed-off-by: Janne Grunau --- drivers/usb/host/xhci-pci-asmedia.c | 69 ++++++++++++++++++++--------- 1 file changed, 47 insertions(+), 22 deletions(-) diff --git a/drivers/usb/host/xhci-pci-asmedia.c b/drivers/usb/host/xhci-pci-asmedia.c index 36cdd962fea06b..732fd3b446a8bc 100644 --- a/drivers/usb/host/xhci-pci-asmedia.c +++ b/drivers/usb/host/xhci-pci-asmedia.c @@ -190,7 +190,7 @@ static int asmedia_wait_reset(struct pci_dev *pdev) return ret; } -static u8 asmedia_read_reg(struct usb_hcd *hcd, u16 addr) { +static int asmedia_read_reg(struct usb_hcd *hcd, u16 addr, u8 *val) { void __iomem *regs = hcd->regs; u8 status; int ret; @@ -202,7 +202,7 @@ static u8 asmedia_read_reg(struct usb_hcd *hcd, u16 addr) { if (ret) { dev_err(hcd->self.controller, "Read reg wait timed out ([%04x])\n", addr); - return ~0; + return ret; } writew_relaxed(addr, regs + ASMT_REG_ADDR); @@ -214,13 +214,14 @@ static u8 asmedia_read_reg(struct usb_hcd *hcd, u16 addr) { if (ret) { dev_err(hcd->self.controller, "Read reg addr timed out ([%04x])\n", addr); - return ~0; + return ret; } - return readb_relaxed(regs + ASMT_REG_RDATA); + *val = readb_relaxed(regs + ASMT_REG_RDATA); + return 0; } -static void asmedia_write_reg(struct usb_hcd *hcd, u16 addr, u8 data, bool wait) { +static int asmedia_write_reg(struct usb_hcd *hcd, u16 addr, u8 data, bool wait) { void __iomem *regs = hcd->regs; u8 status; int ret, i; @@ -231,10 +232,12 @@ static void asmedia_write_reg(struct usb_hcd *hcd, u16 addr, u8 data, bool wait) !(status & ASMT_REG_STATUS_BUSY), 1000, TIMEOUT_USEC); - if (ret) + if (ret) { dev_err(hcd->self.controller, "Write reg addr timed out ([%04x] = %02x)\n", addr, data); + return ret; + } writeb_relaxed(data, regs + ASMT_REG_WDATA); @@ -242,16 +245,21 @@ static void asmedia_write_reg(struct usb_hcd *hcd, u16 addr, u8 data, bool wait) !(status & ASMT_REG_STATUS_BUSY), 1000, TIMEOUT_USEC); - if (ret) + if (ret) { dev_err(hcd->self.controller, "Write reg data timed out ([%04x] = %02x)\n", addr, data); + return ret; + } if (!wait) - return; + return 0; for (i = 0; i < TIMEOUT_USEC; i++) { - if (asmedia_read_reg(hcd, addr) == data) + ret = asmedia_read_reg(hcd, addr, &status); + if (ret) + return ret; + if (status == data) break; } @@ -259,7 +267,10 @@ static void asmedia_write_reg(struct usb_hcd *hcd, u16 addr, u8 data, bool wait) dev_err(hcd->self.controller, "Verify register timed out ([%04x] = %02x)\n", addr, data); + return -ETIMEDOUT; } + + return 0; } static int asmedia_load_fw(struct pci_dev *pdev, const struct firmware *fw) @@ -276,11 +287,15 @@ static int asmedia_load_fw(struct pci_dev *pdev, const struct firmware *fw) hcd = dev_get_drvdata(&pdev->dev); regs = hcd->regs; - asmedia_write_reg(hcd, ASMT_MMIO_CPU_MODE_NEXT, - ASMT_MMIO_CPU_MODE_HALFSPEED, false); + ret = asmedia_write_reg(hcd, ASMT_MMIO_CPU_MODE_NEXT, + ASMT_MMIO_CPU_MODE_HALFSPEED, false); + if (ret) + return ret; - asmedia_write_reg(hcd, ASMT_MMIO_CPU_EXEC_CTRL, - ASMT_MMIO_CPU_EXEC_CTRL_RESET, false); + ret = asmedia_write_reg(hcd, ASMT_MMIO_CPU_EXEC_CTRL, + ASMT_MMIO_CPU_EXEC_CTRL_RESET, false); + if (ret) + return ret; ret = asmedia_wait_reset(pdev); if (ret) { @@ -288,11 +303,15 @@ static int asmedia_load_fw(struct pci_dev *pdev, const struct firmware *fw) return ret; } - asmedia_write_reg(hcd, ASMT_MMIO_CPU_EXEC_CTRL, - ASMT_MMIO_CPU_EXEC_CTRL_HALT, false); + ret = asmedia_write_reg(hcd, ASMT_MMIO_CPU_EXEC_CTRL, + ASMT_MMIO_CPU_EXEC_CTRL_HALT, false); + if (ret) + return ret; - asmedia_write_reg(hcd, ASMT_MMIO_CPU_MISC, - ASMT_MMIO_CPU_MISC_CODE_RAM_WR, true); + ret = asmedia_write_reg(hcd, ASMT_MMIO_CPU_MISC, + ASMT_MMIO_CPU_MISC_CODE_RAM_WR, true); + if (ret) + return ret; pci_write_config_byte(pdev, ASMT_CFG_SRAM_ACCESS, ASMT_CFG_SRAM_ACCESS_ENABLE); @@ -328,13 +347,19 @@ static int asmedia_load_fw(struct pci_dev *pdev, const struct firmware *fw) pci_write_config_byte(pdev, ASMT_CFG_SRAM_ACCESS, 0); - asmedia_write_reg(hcd, ASMT_MMIO_CPU_MISC, 0, true); + ret = asmedia_write_reg(hcd, ASMT_MMIO_CPU_MISC, 0, true); + if (ret) + return ret; - asmedia_write_reg(hcd, ASMT_MMIO_CPU_MODE_NEXT, - ASMT_MMIO_CPU_MODE_RAM | - ASMT_MMIO_CPU_MODE_HALFSPEED, false); + ret = asmedia_write_reg(hcd, ASMT_MMIO_CPU_MODE_NEXT, + ASMT_MMIO_CPU_MODE_RAM | + ASMT_MMIO_CPU_MODE_HALFSPEED, false); + if (ret) + return ret; - asmedia_write_reg(hcd, ASMT_MMIO_CPU_EXEC_CTRL, 0, false); + ret = asmedia_write_reg(hcd, ASMT_MMIO_CPU_EXEC_CTRL, 0, false); + if (ret) + return ret; ret = asmedia_wait_reset(pdev); if (ret) { From 21e6945c3c383f7a0f9aca7a3d6f5dbeb9203b8c Mon Sep 17 00:00:00 2001 From: Mark Kettenis Date: Fri, 19 Sep 2025 22:00:08 +0200 Subject: [PATCH 0256/3902] xhci-pci: asmedia: Use read_poll_timeout() Use read_poll_timeout() instead of open coding timeout loops. Suggested in the upstream submission of the same change to u-boot. Signed-off-by: Mark Kettenis Signed-off-by: Janne Grunau --- drivers/usb/host/xhci-pci-asmedia.c | 81 ++++++++++++++--------------- 1 file changed, 39 insertions(+), 42 deletions(-) diff --git a/drivers/usb/host/xhci-pci-asmedia.c b/drivers/usb/host/xhci-pci-asmedia.c index 732fd3b446a8bc..d6b12f5c540296 100644 --- a/drivers/usb/host/xhci-pci-asmedia.c +++ b/drivers/usb/host/xhci-pci-asmedia.c @@ -66,21 +66,20 @@ static int asmedia_mbox_tx(struct pci_dev *pdev, u64 data) { u8 op; - int i; + int ret, err; - for (i = 0; i < TIMEOUT_USEC; i++) { - pci_read_config_byte(pdev, ASMT_CFG_CONTROL, &op); - if (!(op & ASMT_CFG_CONTROL_WRITE)) - break; - udelay(1); - } - - if (op & ASMT_CFG_CONTROL_WRITE) { + ret = read_poll_timeout(pci_read_config_byte, err, + err || !(op & ASMT_CFG_CONTROL_WRITE), + 1, TIMEOUT_USEC, false, pdev, ASMT_CFG_CONTROL, + &op); + if (ret) { dev_err(&pdev->dev, "Timed out on mailbox tx: 0x%llx\n", data); - return -ETIMEDOUT; + return ret; } + if (err) + return err; pci_write_config_dword(pdev, ASMT_CFG_DATA_WRITE0, data); pci_write_config_dword(pdev, ASMT_CFG_DATA_WRITE1, data >> 32); @@ -93,19 +92,18 @@ static int asmedia_mbox_rx(struct pci_dev *pdev, u64 *data) { u8 op; u32 low, high; - int i; + int ret, err; - for (i = 0; i < TIMEOUT_USEC; i++) { - pci_read_config_byte(pdev, ASMT_CFG_CONTROL, &op); - if (op & ASMT_CFG_CONTROL_READ) - break; - udelay(1); - } - - if (!(op & ASMT_CFG_CONTROL_READ)) { + ret = read_poll_timeout(pci_read_config_byte, err, + err || (op & ASMT_CFG_CONTROL_READ), + 1, TIMEOUT_USEC, false, pdev, ASMT_CFG_CONTROL, + &op); + if (ret) { dev_err(&pdev->dev, "Timed out on mailbox rx\n"); - return -ETIMEDOUT; + return ret; } + if (err) + return err; pci_read_config_dword(pdev, ASMT_CFG_DATA_READ0, &low); pci_read_config_dword(pdev, ASMT_CFG_DATA_READ1, &high); @@ -223,8 +221,8 @@ static int asmedia_read_reg(struct usb_hcd *hcd, u16 addr, u8 *val) { static int asmedia_write_reg(struct usb_hcd *hcd, u16 addr, u8 data, bool wait) { void __iomem *regs = hcd->regs; - u8 status; - int ret, i; + u8 status, val; + int ret, err; writew_relaxed(addr, regs + ASMT_REG_ADDR); @@ -255,19 +253,19 @@ static int asmedia_write_reg(struct usb_hcd *hcd, u16 addr, u8 data, bool wait) if (!wait) return 0; - for (i = 0; i < TIMEOUT_USEC; i++) { - ret = asmedia_read_reg(hcd, addr, &status); - if (ret) - return ret; - if (status == data) - break; - } - - if (i >= TIMEOUT_USEC) { + ret = read_poll_timeout(asmedia_read_reg, err, err || val == data, + 0, TIMEOUT_USEC, false, hcd, addr, &val); + if (ret) { dev_err(hcd->self.controller, "Verify register timed out ([%04x] = %02x)\n", addr, data); - return -ETIMEDOUT; + return ret; + } + if (err) { + dev_err(hcd->self.controller, + "Verify register read error ([%04x] = %02x)\n", + addr, data); + return err; } return 0; @@ -282,7 +280,7 @@ static int asmedia_load_fw(struct pci_dev *pdev, const struct firmware *fw) u32 data; size_t index = 0, addr = 0; size_t words = fw->size >> 1; - int ret, i; + int ret, err; hcd = dev_get_drvdata(&pdev->dev); regs = hcd->regs; @@ -328,17 +326,16 @@ static int asmedia_load_fw(struct pci_dev *pdev, const struct firmware *fw) writel_relaxed(data, regs + ASMT_REG_CODE_WDATA); - for (i = 0; i < TIMEOUT_USEC; i++) { - pci_read_config_word(pdev, ASMT_CFG_SRAM_ADDR, &raddr); - if (raddr != addr) - break; - udelay(1); - } - - if (raddr == addr) { + ret = read_poll_timeout(pci_read_config_word, err, + err || (raddr != addr), + 1, TIMEOUT_USEC, false, pdev, + ASMT_CFG_SRAM_ADDR, &raddr); + if (ret) { dev_err(hcd->self.controller, "Word write timed out\n"); - return -ETIMEDOUT; + return ret; } + if (err) + return err; if (++index & 0x4000) index += 0x4000; From 959d890d3a2653628b02771534dec24f72129a23 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20Povi=C5=A1er?= Date: Fri, 13 Oct 2023 18:49:35 +0200 Subject: [PATCH 0257/3902] dt-bindings: dma: apple,sio: Add schema MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Describe the SIO coprocessor which serves as pretend DMA controller on recent Apple platforms. Reviewed-by: Rob Herring Signed-off-by: Martin Povišer --- .../devicetree/bindings/dma/apple,sio.yaml | 111 ++++++++++++++++++ 1 file changed, 111 insertions(+) create mode 100644 Documentation/devicetree/bindings/dma/apple,sio.yaml diff --git a/Documentation/devicetree/bindings/dma/apple,sio.yaml b/Documentation/devicetree/bindings/dma/apple,sio.yaml new file mode 100644 index 00000000000000..0e3780ad9dd79a --- /dev/null +++ b/Documentation/devicetree/bindings/dma/apple,sio.yaml @@ -0,0 +1,111 @@ +# SPDX-License-Identifier: GPL-2.0-only OR BSD-2-Clause +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/dma/apple,sio.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Apple SIO Coprocessor + +description: + SIO is a coprocessor on Apple M1 and later chips (and maybe also on earlier + chips). Its role is to offload SPI, UART and DisplayPort audio transfers, + being a pretend DMA controller. + +maintainers: + - Martin Povišer + +allOf: + - $ref: dma-controller.yaml# + +properties: + compatible: + items: + - enum: + - apple,t6000-sio + - apple,t8103-sio + - const: apple,sio + + reg: + maxItems: 1 + + '#dma-cells': + const: 1 + description: + DMA clients specify a single cell that corresponds to the RTKit endpoint + number used for arranging the transfers in question + + dma-channels: + maximum: 128 + + mboxes: + maxItems: 1 + + iommus: + maxItems: 1 + + power-domains: + maxItems: 1 + + memory-region: + minItems: 2 + maxItems: 8 + description: + A number of references to reserved memory regions among which are the DATA/TEXT + sections of coprocessor executable firmware and also auxiliary firmware data + describing the available DMA-enabled peripherals + + apple,sio-firmware-params: + $ref: /schemas/types.yaml#/definitions/uint32-array + description: | + Parameters in the form of opaque key/value pairs that are to be sent to the SIO + coprocesssor once it boots. These parameters can point into the reserved memory + regions (in device address space). + + Note that unlike Apple's firmware, we treat the parameters, and the data they + refer to, as opaque. Apple embed short data blobs into their SIO devicetree node + that describe the DMA-enabled peripherals (presumably with defined semantics). + Their driver processes those blobs and sets up data structure in mapped device + memory, then references this memory in the parameters sent to the SIO. At the + level of description we are opting for in this binding, we assume the job of + constructing those data structures has been done in advance, leaving behind an + opaque list of key/value parameter pairs to be sent by a prospective driver. + + This approach is chosen for two reasons: + + - It means we don't need to try to understand the semantics of Apple's blobs + as long as we know the transformation we need to do from Apple's devicetree + data to SIO data (which can be shoved away into a loader). It also means the + semantics of Apple's blobs (or of something to replace them) need not be part + of the binding and be kept up with Apple's firmware changes in the future. + + - It leaves less work for the driver attaching on this binding. Instead the work + is done upfront in the loader which can be better suited for keeping up with + Apple's firmware changes. + +required: + - compatible + - reg + - '#dma-cells' + - dma-channels + - mboxes + - iommus + - power-domains + +additionalProperties: false + +examples: + - | + sio: dma-controller@36400000 { + compatible = "apple,t8103-sio", "apple,sio"; + reg = <0x36400000 0x8000>; + dma-channels = <128>; + #dma-cells = <1>; + mboxes = <&sio_mbox>; + iommus = <&sio_dart 0>; + power-domains = <&ps_sio_cpu>; + memory-region = <&sio_text>, <&sio_data>, + <&sio_auxdata1>, <&sio_auxdata2>; /* Filled by loader */ + apple,sio-firmware-params = <0xb 0x10>, <0xc 0x1b80>, <0xf 0x14>, + <0x10 0x1e000>, <0x30d 0x34>, <0x30e 0x4000>, + <0x1a 0x38>, <0x1b 0x50>; /* Filled by loader */ + }; From 7976bafcd464e505d535a31757e061c8360e3aec Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20Povi=C5=A1er?= Date: Mon, 28 Nov 2022 09:55:07 +0100 Subject: [PATCH 0258/3902] dmaengine: apple-sio: Add Apple SIO driver MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add a dmaengine driver for the Apple SIO coprocessor found on Apple SoCs where it provides DMA services. Have the driver support cyclic transactions so that ALSA drivers can rely on it in audio output to HDMI and DisplayPort. Signed-off-by: Martin Povišer --- MAINTAINERS | 2 + drivers/dma/Kconfig | 10 + drivers/dma/Makefile | 1 + drivers/dma/apple-sio.c | 912 ++++++++++++++++++++++++++++++++++++++++ 4 files changed, 925 insertions(+) create mode 100644 drivers/dma/apple-sio.c diff --git a/MAINTAINERS b/MAINTAINERS index e8f06145fb54c9..5d3be6e7c13e34 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -2397,9 +2397,11 @@ M: Martin Povišer L: asahi@lists.linux.dev L: linux-sound@vger.kernel.org S: Maintained +F: Documentation/devicetree/bindings/dma/apple,sio.yaml F: Documentation/devicetree/bindings/sound/adi,ssm3515.yaml F: Documentation/devicetree/bindings/sound/cirrus,cs42l84.yaml F: Documentation/devicetree/bindings/sound/apple,* +F: drivers/dma/apple-sio.c F: sound/soc/apple/* F: sound/soc/codecs/cs42l83-i2c.c F: sound/soc/codecs/cs42l84.* diff --git a/drivers/dma/Kconfig b/drivers/dma/Kconfig index b8a74b1798ba1d..19ad5e1df76824 100644 --- a/drivers/dma/Kconfig +++ b/drivers/dma/Kconfig @@ -92,6 +92,16 @@ config APPLE_ADMAC help Enable support for Audio DMA Controller found on Apple Silicon SoCs. +config APPLE_SIO + tristate "Apple SIO support" + depends on ARCH_APPLE || COMPILE_TEST + depends on APPLE_RTKIT + depends on OF_ADDRESS + select DMA_ENGINE + help + Enable support for the SIO coprocessor found on Apple Silicon SoCs + where it provides DMA services. + config ARM_DMA350 tristate "Arm DMA-350 support" depends on ARM || ARM64 || COMPILE_TEST diff --git a/drivers/dma/Makefile b/drivers/dma/Makefile index a54d7688392b1a..1c11fdc02692cc 100644 --- a/drivers/dma/Makefile +++ b/drivers/dma/Makefile @@ -17,6 +17,7 @@ obj-$(CONFIG_ALTERA_MSGDMA) += altera-msgdma.o obj-$(CONFIG_AMBA_PL08X) += amba-pl08x.o obj-$(CONFIG_AMCC_PPC440SPE_ADMA) += ppc4xx/ obj-$(CONFIG_APPLE_ADMAC) += apple-admac.o +obj-$(CONFIG_APPLE_SIO) += apple-sio.o obj-$(CONFIG_ARM_DMA350) += arm-dma350.o obj-$(CONFIG_AT_HDMAC) += at_hdmac.o obj-$(CONFIG_AT_XDMAC) += at_xdmac.o diff --git a/drivers/dma/apple-sio.c b/drivers/dma/apple-sio.c new file mode 100644 index 00000000000000..d0a940499d734d --- /dev/null +++ b/drivers/dma/apple-sio.c @@ -0,0 +1,912 @@ +// SPDX-License-Identifier: GPL-2.0-only OR MIT +/* + * Driver for SIO coprocessor on t8103 (M1) and other Apple SoCs + * + * Copyright (C) The Asahi Linux Contributors + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "dmaengine.h" +#include "virt-dma.h" + +#define NCHANNELS_MAX 0x80 + +#define REG_CPU_CONTROL 0x44 +#define CPU_CONTROL_RUN BIT(4) + +#define SIOMSG_DATA GENMASK(63, 32) +#define SIOMSG_TYPE GENMASK(23, 16) +#define SIOMSG_PARAM GENMASK(31, 24) +#define SIOMSG_TAG GENMASK(13, 8) +#define SIOMSG_EP GENMASK(7, 0) + +#define EP_SIO 0x20 + +#define MSG_START 0x2 +#define MSG_SETUP 0x3 +#define MSG_CONFIGURE 0x5 +#define MSG_ISSUE 0x6 +#define MSG_TERMINATE 0x8 +#define MSG_ACK 0x65 +#define MSG_NACK 0x66 +#define MSG_STARTED 0x67 +#define MSG_REPORT 0x68 + +#define SIO_CALL_TIMEOUT_MS 100 +#define SIO_SHMEM_SIZE 0x1000 +#define SIO_NO_DESC_SLOTS 64 + +/* + * There are two kinds of 'transaction descriptors' in play here. + * + * There's the struct sio_tx, and the struct dma_async_tx_descriptor embedded + * inside, which jointly represent a transaction to the dmaengine subsystem. + * At this time we only support those transactions to be cyclic. + * + * Then there are the coprocessor descriptors, which is what the coprocessor + * knows and understands. These don't seem to have a cyclic regime, so we can't + * map the dmaengine transaction on an exact coprocessor counterpart. Instead + * we continually queue up many coprocessor descriptors to implement a cyclic + * transaction. + * + * The number below is the maximum of how far ahead (how many) coprocessor + * descriptors we should be queuing up, per channel, for a cyclic transaction. + * Basically it's a made-up number. + */ +#define SIO_MAX_NINFLIGHT 4 + +struct sio_coproc_desc { + u32 pad1; + u32 flag; + u64 unk; + u64 iova; + u64 size; + u64 pad2; + u64 pad3; +} __packed; +static_assert(sizeof(struct sio_coproc_desc) == 48); + +struct sio_shmem_chan_config { + u32 datashape; + u32 timeout; + u32 fifo; + u32 threshold; + u32 limit; +} __packed; + +struct sio_data; +struct sio_tx; + +struct sio_chan { + unsigned int no; + struct sio_data *host; + struct virt_dma_chan vc; + struct work_struct terminate_wq; + + bool configured; + struct sio_shmem_chan_config cfg; + + struct sio_tx *current_tx; +}; + +#define SIO_NTAGS 16 + +typedef void (*sio_ack_callback)(struct sio_chan *, void *, bool); + +struct sio_data { + void __iomem *base; + struct dma_device dma; + struct device *dev; + struct apple_rtkit *rtk; + void *shmem; + struct sio_coproc_desc *shmem_desc_base; + unsigned long *desc_allocated; + + struct sio_tagdata { + DECLARE_BITMAP(allocated, SIO_NTAGS); + int last_tag; + + struct completion completions[SIO_NTAGS]; + bool atomic[SIO_NTAGS]; + bool acked[SIO_NTAGS]; + + sio_ack_callback ack_callback[SIO_NTAGS]; + void *cookie[SIO_NTAGS]; + } tags; + + int nchannels; + struct sio_chan channels[]; +}; + +struct sio_tx { + struct virt_dma_desc vd; + struct completion done; + + bool terminated; + size_t period_len; + int nperiods; + int ninflight; + int next; + + struct sio_coproc_desc *siodesc[]; +}; + +static int sio_send_siomsg(struct sio_data *sio, u64 msg); +static int sio_send_siomsg_atomic(struct sio_data *sio, u64 msg, + sio_ack_callback ack_callback, + void *cookie); +static int sio_call(struct sio_data *sio, u64 msg); + +static struct sio_chan *to_sio_chan(struct dma_chan *chan) +{ + return container_of(chan, struct sio_chan, vc.chan); +} + +static struct sio_tx *to_sio_tx(struct dma_async_tx_descriptor *tx) +{ + return container_of(tx, struct sio_tx, vd.tx); +} + +static int sio_alloc_tag(struct sio_data *sio) +{ + struct sio_tagdata *tags = &sio->tags; + int tag, i; + + /* + * Because tag number 0 is special, the usable tag range + * is 1...(SIO_NTAGS - 1). So, to pick the next usable tag, + * we do modulo (SIO_NTAGS - 1) *then* plus one. + */ + +#define SIO_USABLE_TAGS (SIO_NTAGS - 1) + tag = (READ_ONCE(tags->last_tag) % SIO_USABLE_TAGS) + 1; + + for (i = 0; i < SIO_USABLE_TAGS; i++) { + if (!test_and_set_bit(tag, tags->allocated)) + break; + + tag = (tag % SIO_USABLE_TAGS) + 1; + } + + WRITE_ONCE(tags->last_tag, tag); + + if (i < SIO_USABLE_TAGS) + return tag; + else + return -EBUSY; +#undef SIO_USABLE_TAGS +} + +static void sio_free_tag(struct sio_data *sio, int tag) +{ + struct sio_tagdata *tags = &sio->tags; + + if (WARN_ON(tag >= SIO_NTAGS)) + return; + + tags->atomic[tag] = false; + tags->ack_callback[tag] = NULL; + + WARN_ON(!test_and_clear_bit(tag, tags->allocated)); +} + +static void sio_set_tag_atomic(struct sio_data *sio, int tag, + sio_ack_callback ack_callback, + void *cookie) +{ + struct sio_tagdata *tags = &sio->tags; + + tags->atomic[tag] = true; + tags->ack_callback[tag] = ack_callback; + tags->cookie[tag] = cookie; +} + +static struct sio_coproc_desc *sio_alloc_desc(struct sio_data *sio) +{ + int i; + + for (i = 0; i < SIO_NO_DESC_SLOTS; i++) + if (!test_and_set_bit(i, sio->desc_allocated)) + return sio->shmem_desc_base + i; + + return NULL; +} + +static void sio_free_desc(struct sio_data *sio, struct sio_coproc_desc *desc) +{ + clear_bit(desc - sio->shmem_desc_base, sio->desc_allocated); +} + +static int sio_coproc_desc_slot(struct sio_data *sio, struct sio_coproc_desc *desc) +{ + return (desc - sio->shmem_desc_base) * 4; +} + +static enum dma_transfer_direction sio_chan_direction(int channo) +{ + /* Channel directions are fixed based on channel number */ + return (channo & 1) ? DMA_DEV_TO_MEM : DMA_MEM_TO_DEV; +} + +static void sio_tx_free(struct virt_dma_desc *vd) +{ + struct sio_data *sio = to_sio_chan(vd->tx.chan)->host; + struct sio_tx *siotx = to_sio_tx(&vd->tx); + int i; + + for (i = 0; i < siotx->nperiods; i++) + if (siotx->siodesc[i]) + sio_free_desc(sio, siotx->siodesc[i]); + kfree(siotx); +} + +static struct dma_async_tx_descriptor *sio_prep_dma_cyclic( + struct dma_chan *chan, dma_addr_t buf_addr, size_t buf_len, + size_t period_len, enum dma_transfer_direction direction, + unsigned long flags) +{ + struct sio_chan *siochan = to_sio_chan(chan); + struct sio_tx *siotx = NULL; + int i, nperiods = buf_len / period_len; + + if (direction != sio_chan_direction(siochan->no)) + return NULL; + + siotx = kzalloc(struct_size(siotx, siodesc, nperiods), GFP_NOWAIT); + if (!siotx) + return NULL; + + init_completion(&siotx->done); + siotx->period_len = period_len; + siotx->nperiods = nperiods; + + for (i = 0; i < nperiods; i++) { + struct sio_coproc_desc *d; + + siotx->siodesc[i] = d = sio_alloc_desc(siochan->host); + if (!d) { + sio_tx_free(&siotx->vd); + return NULL; + } + + d->flag = 1; /* not sure what's up with this */ + d->iova = buf_addr + period_len * i; + d->size = period_len; + } + dma_wmb(); + + return vchan_tx_prep(&siochan->vc, &siotx->vd, flags); +} + +static enum dma_status sio_tx_status(struct dma_chan *chan, dma_cookie_t cookie, + struct dma_tx_state *txstate) +{ + struct sio_chan *siochan = to_sio_chan(chan); + struct virt_dma_desc *vd; + struct sio_tx *siotx; + enum dma_status ret; + unsigned long flags; + int periods_residue; + size_t residue; + + ret = dma_cookie_status(chan, cookie, txstate); + if (ret == DMA_COMPLETE || !txstate) + return ret; + + spin_lock_irqsave(&siochan->vc.lock, flags); + siotx = siochan->current_tx; + + if (siotx && siotx->vd.tx.cookie == cookie) { + ret = DMA_IN_PROGRESS; + periods_residue = siotx->next - siotx->ninflight; + while (periods_residue < 0) + periods_residue += siotx->nperiods; + residue = (siotx->nperiods - periods_residue) * siotx->period_len; + } else { + ret = DMA_IN_PROGRESS; + residue = 0; + vd = vchan_find_desc(&siochan->vc, cookie); + if (vd) { + siotx = to_sio_tx(&vd->tx); + residue = siotx->period_len * siotx->nperiods; + } + } + spin_unlock_irqrestore(&siochan->vc.lock, flags); + dma_set_residue(txstate, residue); + + return ret; +} + +static bool sio_fill_in_locked(struct sio_chan *siochan); + +static void sio_handle_issue_ack(struct sio_chan *siochan, void *cookie, bool ok) +{ + dma_cookie_t tx_cookie = (unsigned long) cookie; + unsigned long flags; + struct sio_tx *tx; + + if (!ok) { + dev_err(siochan->host->dev, "nacked issue on chan %d\n", siochan->no); + return; + } + + spin_lock_irqsave(&siochan->vc.lock, flags); + if (!siochan->current_tx || tx_cookie != siochan->current_tx->vd.tx.cookie || + siochan->current_tx->terminated) + goto out; + + tx = siochan->current_tx; + tx->next = (tx->next + 1) % tx->nperiods; + tx->ninflight++; + sio_fill_in_locked(siochan); + +out: + spin_unlock_irqrestore(&siochan->vc.lock, flags); +} + +static bool sio_fill_in_locked(struct sio_chan *siochan) +{ + struct sio_data *sio = siochan->host; + struct sio_tx *tx = siochan->current_tx; + struct sio_coproc_desc *d = tx->siodesc[tx->next]; + int ret; + + if (tx->ninflight >= SIO_MAX_NINFLIGHT || tx->terminated) + return false; + + static_assert(sizeof(dma_cookie_t) <= sizeof(void *)); + ret = sio_send_siomsg_atomic(sio, FIELD_PREP(SIOMSG_EP, siochan->no) | + FIELD_PREP(SIOMSG_TYPE, MSG_ISSUE) | + FIELD_PREP(SIOMSG_DATA, sio_coproc_desc_slot(sio, d)), + sio_handle_issue_ack, (void *) (uintptr_t) tx->vd.tx.cookie); + if (ret < 0) + dev_err_ratelimited(sio->dev, "can't issue on chan %d ninflight %d: %d\n", + siochan->no, tx->ninflight, ret); + return true; +} + +static void sio_update_current_tx_locked(struct sio_chan *siochan) +{ + struct virt_dma_desc *vd = vchan_next_desc(&siochan->vc); + + if (vd && !siochan->current_tx) { + list_del(&vd->node); + siochan->current_tx = to_sio_tx(&vd->tx); + sio_fill_in_locked(siochan); + } +} + +static void sio_issue_pending(struct dma_chan *chan) +{ + struct sio_chan *siochan = to_sio_chan(chan); + unsigned long flags; + + spin_lock_irqsave(&siochan->vc.lock, flags); + vchan_issue_pending(&siochan->vc); + sio_update_current_tx_locked(siochan); + spin_unlock_irqrestore(&siochan->vc.lock, flags); +} + +static int sio_terminate_all(struct dma_chan *chan) +{ + struct sio_chan *siochan = to_sio_chan(chan); + unsigned long flags; + LIST_HEAD(to_free); + + spin_lock_irqsave(&siochan->vc.lock, flags); + if (siochan->current_tx && !siochan->current_tx->terminated) { + dma_cookie_complete(&siochan->current_tx->vd.tx); + siochan->current_tx->terminated = true; + schedule_work(&siochan->terminate_wq); + } + vchan_get_all_descriptors(&siochan->vc, &to_free); + spin_unlock_irqrestore(&siochan->vc.lock, flags); + + vchan_dma_desc_free_list(&siochan->vc, &to_free); + + return 0; +} + +static void sio_terminate_work(struct work_struct *wq) +{ + struct sio_chan *siochan = container_of(wq, struct sio_chan, terminate_wq); + struct sio_tx *tx; + unsigned long flags; + int ret; + + spin_lock_irqsave(&siochan->vc.lock, flags); + tx = siochan->current_tx; + spin_unlock_irqrestore(&siochan->vc.lock, flags); + + if (WARN_ON(!tx)) + return; + + ret = sio_call(siochan->host, FIELD_PREP(SIOMSG_EP, siochan->no) | + FIELD_PREP(SIOMSG_TYPE, MSG_TERMINATE)); + if (ret < 0) + dev_err(siochan->host->dev, "terminate call on chan %d failed: %d\n", + siochan->no, ret); + + ret = wait_for_completion_timeout(&tx->done, msecs_to_jiffies(500)); + if (!ret) + dev_err(siochan->host->dev, "terminate descriptor wait timed out\n"); + + tasklet_kill(&siochan->vc.task); + + spin_lock_irqsave(&siochan->vc.lock, flags); + WARN_ON(siochan->current_tx != tx); + siochan->current_tx = NULL; + sio_update_current_tx_locked(siochan); + spin_unlock_irqrestore(&siochan->vc.lock, flags); + + sio_tx_free(&tx->vd); +} + +static void sio_synchronize(struct dma_chan *chan) +{ + struct sio_chan *siochan = to_sio_chan(chan); + + flush_work(&siochan->terminate_wq); +} + +static void sio_free_chan_resources(struct dma_chan *chan) +{ + sio_terminate_all(chan); + sio_synchronize(chan); + vchan_free_chan_resources(&to_sio_chan(chan)->vc); +} + +static struct dma_chan *sio_dma_of_xlate(struct of_phandle_args *dma_spec, + struct of_dma *ofdma) +{ + struct sio_data *sio = (struct sio_data *) ofdma->of_dma_data; + unsigned int index = dma_spec->args[0]; + + if (dma_spec->args_count != 1 || index >= sio->nchannels) + return ERR_PTR(-EINVAL); + + return dma_get_slave_channel(&sio->channels[index].vc.chan); +} + +static void sio_rtk_crashed(void *cookie, const void *crashlog, size_t crashlog_size) +{ + struct sio_data *sio = cookie; + + dev_err(sio->dev, "SIO down (crashed)"); +} + +static void sio_process_report(struct sio_chan *siochan) +{ + unsigned long flags; + + spin_lock_irqsave(&siochan->vc.lock, flags); + if (siochan->current_tx) { + struct sio_tx *tx = siochan->current_tx; + + if (tx->ninflight) + tx->ninflight--; + vchan_cyclic_callback(&tx->vd); + if (!sio_fill_in_locked(siochan) && !tx->ninflight) + complete(&tx->done); + } + spin_unlock_irqrestore(&siochan->vc.lock, flags); +} + +static void sio_recv_msg(void *cookie, u8 ep, u64 msg) +{ + struct sio_data *sio = cookie; + struct sio_tagdata *tags = &sio->tags; + u32 data; + u8 type, tag, sioep; + + if (ep != EP_SIO) + goto unknown; + + data = FIELD_GET(SIOMSG_DATA, msg); + // param = FIELD_GET(SIOMSG_PARAM, msg); + type = FIELD_GET(SIOMSG_TYPE, msg); + tag = FIELD_GET(SIOMSG_TAG, msg); + sioep = FIELD_GET(SIOMSG_EP, msg); + + switch (type) { + case MSG_STARTED: + dev_info(sio->dev, "SIO protocol v%u\n", data); + type = MSG_ACK; /* Pretend this is an ACK */ + fallthrough; + case MSG_ACK: + case MSG_NACK: + if (WARN_ON(tag >= SIO_NTAGS)) + break; + + if (tags->atomic[tag]) { + sio_ack_callback callback = tags->ack_callback[tag]; + + if (callback && !WARN_ON(sioep >= sio->nchannels)) + callback(&sio->channels[sioep], + tags->cookie[tag], type == MSG_ACK); + if (type == MSG_NACK) + dev_err(sio->dev, "got a NACK on channel %d\n", sioep); + sio_free_tag(sio, tag); + } else { + tags->acked[tag] = (type == MSG_ACK); + complete(&tags->completions[tag]); + } + break; + + case MSG_REPORT: + if (WARN_ON(sioep >= sio->nchannels)) + break; + + sio_process_report(&sio->channels[sioep]); + break; + + default: + goto unknown; + } + return; + +unknown: + dev_warn(sio->dev, "received unknown message: ep %x data %016llx\n", + ep, msg); +} + +static int _sio_send_siomsg(struct sio_data *sio, u64 msg, bool atomic, + sio_ack_callback ack_callback, void *cookie) +{ + int tag, ret; + + tag = sio_alloc_tag(sio); + if (tag < 0) + return tag; + + if (atomic) + sio_set_tag_atomic(sio, tag, ack_callback, cookie); + else + reinit_completion(&sio->tags.completions[tag]); + + msg &= ~SIOMSG_TAG; + msg |= FIELD_PREP(SIOMSG_TAG, tag); + ret = apple_rtkit_send_message(sio->rtk, EP_SIO, msg, NULL, + atomic); + if (ret < 0) { + sio_free_tag(sio, tag); + return ret; + } + + return tag; +} + +static int sio_send_siomsg(struct sio_data *sio, u64 msg) +{ + return _sio_send_siomsg(sio, msg, false, NULL, NULL); +} + +static int sio_send_siomsg_atomic(struct sio_data *sio, u64 msg, + sio_ack_callback ack_callback, + void *cookie) +{ + return _sio_send_siomsg(sio, msg, true, ack_callback, cookie); +} + +static int sio_call(struct sio_data *sio, u64 msg) +{ + int tag, ret; + + tag = sio_send_siomsg(sio, msg); + if (tag < 0) + return tag; + + ret = wait_for_completion_timeout(&sio->tags.completions[tag], + msecs_to_jiffies(SIO_CALL_TIMEOUT_MS)); + if (!ret) { + dev_warn(sio->dev, "call %8llx timed out\n", msg); + sio_free_tag(sio, tag); + return -ETIME; + } + + ret = sio->tags.acked[tag]; + sio_free_tag(sio, tag); + + return ret; +} + +static const struct apple_rtkit_ops sio_rtkit_ops = { + .crashed = sio_rtk_crashed, + .recv_message = sio_recv_msg, +}; + +static int sio_device_config(struct dma_chan *chan, + struct dma_slave_config *config) +{ + struct sio_chan *siochan = to_sio_chan(chan); + struct sio_data *sio = siochan->host; + bool is_tx = sio_chan_direction(siochan->no) == DMA_MEM_TO_DEV; + struct sio_shmem_chan_config *cfg_shmem = sio->shmem; + struct sio_shmem_chan_config cfg; + int ret; + + switch (is_tx ? config->dst_addr_width : config->src_addr_width) { + case DMA_SLAVE_BUSWIDTH_1_BYTE: + cfg.datashape = 0; + break; + case DMA_SLAVE_BUSWIDTH_2_BYTES: + cfg.datashape = 1; + break; + case DMA_SLAVE_BUSWIDTH_4_BYTES: + cfg.datashape = 2; + break; + default: + return -EINVAL; + } + + cfg.timeout = 0; + cfg.fifo = 0x800; + cfg.limit = 0x800; + cfg.threshold = 0x800; + + /* + * Dmaengine prescribes we ought to apply the new configuration only + * to newly-queued descriptors. + * + * To comply with dmaengine's interface we take the lazy path here: + * we apply the configuration right away, we only allow the channel + * to be configured once, which means subsequent calls to `device_config` + * either return -EBUSY if the configuration differs, or they are + * a no-op if the configuration is the same as the starting one. + * + * This is the reasonable thing to do given that these sio channels + * are tied to fixed peripherals, and what's more given that the + * only planned consumer of this dmaengine driver in the kernel is + * diplayport audio support, where the DMA configuration is fixed, + * and no more than a single descriptor (a cyclic one) gets ever issued + * at the same time. + * + * The code complexity cost of tracking to which descriptor + * the configuration relates would be significant here, especially + * since we need to do a non-atomic operation to apply it (a call to + * the coprocessor) and dmaengine has its bunch of atomicity + * restrictions. And this complexity would be for naught since it + * doesn't even get exercised by the only planned consumer. + */ + if (siochan->configured && memcmp(&siochan->cfg, &cfg, sizeof(cfg))) + return -EBUSY; + + *cfg_shmem = cfg; + dma_wmb(); + + ret = sio_call(sio, FIELD_PREP(SIOMSG_TYPE, MSG_CONFIGURE) | + FIELD_PREP(SIOMSG_EP, siochan->no)); + + if (ret == 1) + ret = 0; + else if (ret == 0) + ret = -EINVAL; + + if (ret == 0) { + siochan->configured = true; + siochan->cfg = cfg; + } + + return ret; +} + +static int sio_alloc_shmem(struct sio_data *sio) +{ + dma_addr_t iova; + int err; + + sio->shmem = dma_alloc_coherent(sio->dev, SIO_SHMEM_SIZE, + &iova, GFP_KERNEL | __GFP_ZERO); + if (!sio->shmem) + return -ENOMEM; + + sio->shmem_desc_base = (struct sio_coproc_desc *) (sio->shmem + 56); + sio->desc_allocated = devm_kzalloc(sio->dev, SIO_NO_DESC_SLOTS / 32, + GFP_KERNEL); + if (!sio->desc_allocated) + return -ENOMEM; + + err = sio_call(sio, FIELD_PREP(SIOMSG_TYPE, MSG_SETUP) | + FIELD_PREP(SIOMSG_PARAM, 1) | + FIELD_PREP(SIOMSG_DATA, iova >> 12)); + if (err != 1) { + if (err == 0) + err = -EINVAL; + return err; + } + + err = sio_call(sio, FIELD_PREP(SIOMSG_TYPE, MSG_SETUP) | + FIELD_PREP(SIOMSG_PARAM, 2) | + FIELD_PREP(SIOMSG_DATA, SIO_SHMEM_SIZE)); + if (err != 1) { + if (err == 0) + err = -EINVAL; + return err; + } + + return 0; +} + +static int sio_send_dt_params(struct sio_data *sio) +{ + struct device_node *np = sio->dev->of_node; + const char *propname = "apple,sio-firmware-params"; + int nparams, err, i; + + nparams = of_property_count_u32_elems(np, propname); + if (nparams < 0) { + err = nparams; + goto badprop; + } + + for (i = 0; i < nparams / 2; i++) { + u32 key, val; + + err = of_property_read_u32_index(np, propname, 2 * i, &key); + if (err) + goto badprop; + err = of_property_read_u32_index(np, propname, 2 * i + 1, &val); + if (err) + goto badprop; + + err = sio_call(sio, FIELD_PREP(SIOMSG_TYPE, MSG_SETUP) | + FIELD_PREP(SIOMSG_PARAM, key & 0xff) | + FIELD_PREP(SIOMSG_EP, key >> 8) | + FIELD_PREP(SIOMSG_DATA, val)); + if (err < 1) { + if (err == 0) + err = -ENXIO; + return dev_err_probe(sio->dev, err, "sending SIO parameter %#x value %#x\n", + key, val); + } + } + + return 0; + +badprop: + return dev_err_probe(sio->dev, err, "failed to read '%s'\n", propname); +} + +static int sio_probe(struct platform_device *pdev) +{ + struct device_node *np = pdev->dev.of_node; + struct sio_data *sio; + struct dma_device *dma; + int nchannels; + int err, i; + + err = dma_set_mask_and_coherent(&pdev->dev, DMA_BIT_MASK(42)); + if (err) + return dev_err_probe(&pdev->dev, err, "Failed to set DMA mask\n"); + + err = of_property_read_u32(np, "dma-channels", &nchannels); + if (err || nchannels > NCHANNELS_MAX) + return dev_err_probe(&pdev->dev, -EINVAL, + "missing or invalid dma-channels property\n"); + + sio = devm_kzalloc(&pdev->dev, struct_size(sio, channels, nchannels), GFP_KERNEL); + if (!sio) + return -ENOMEM; + + platform_set_drvdata(pdev, sio); + sio->dev = &pdev->dev; + sio->nchannels = nchannels; + + sio->base = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(sio->base)) + return PTR_ERR(sio->base); + + sio->rtk = devm_apple_rtkit_init(&pdev->dev, sio, NULL, 0, &sio_rtkit_ops); + if (IS_ERR(sio->rtk)) + return dev_err_probe(&pdev->dev, PTR_ERR(sio->rtk), + "couldn't initialize rtkit\n"); + for (i = 1; i < SIO_NTAGS; i++) + init_completion(&sio->tags.completions[i]); + + dma = &sio->dma; + dma_cap_set(DMA_PRIVATE, dma->cap_mask); + dma_cap_set(DMA_CYCLIC, dma->cap_mask); + + dma->dev = &pdev->dev; + dma->device_free_chan_resources = sio_free_chan_resources; + dma->device_tx_status = sio_tx_status; + dma->device_issue_pending = sio_issue_pending; + dma->device_terminate_all = sio_terminate_all; + dma->device_synchronize = sio_synchronize; + dma->device_prep_dma_cyclic = sio_prep_dma_cyclic; + dma->device_config = sio_device_config; + + dma->directions = BIT(DMA_MEM_TO_DEV); + dma->residue_granularity = DMA_RESIDUE_GRANULARITY_SEGMENT; + dma->dst_addr_widths = BIT(DMA_SLAVE_BUSWIDTH_1_BYTE) | + BIT(DMA_SLAVE_BUSWIDTH_2_BYTES) | + BIT(DMA_SLAVE_BUSWIDTH_4_BYTES); + + INIT_LIST_HEAD(&dma->channels); + for (i = 0; i < nchannels; i++) { + struct sio_chan *siochan = &sio->channels[i]; + + siochan->host = sio; + siochan->no = i; + siochan->vc.desc_free = sio_tx_free; + INIT_WORK(&siochan->terminate_wq, sio_terminate_work); + vchan_init(&siochan->vc, dma); + } + + writel(CPU_CONTROL_RUN, sio->base + REG_CPU_CONTROL); + + err = apple_rtkit_boot(sio->rtk); + if (err) + return dev_err_probe(&pdev->dev, err, "SIO did not boot\n"); + + err = apple_rtkit_start_ep(sio->rtk, EP_SIO); + if (err) + return dev_err_probe(&pdev->dev, err, "starting SIO endpoint\n"); + + err = sio_call(sio, FIELD_PREP(SIOMSG_TYPE, MSG_START)); + if (err < 1) { + if (err == 0) + err = -ENXIO; + return dev_err_probe(&pdev->dev, err, "starting SIO service\n"); + } + + err = sio_send_dt_params(sio); + if (err < 0) + return dev_err_probe(&pdev->dev, err, "failed to send boot-up parameters\n"); + + err = sio_alloc_shmem(sio); + if (err < 0) + return err; + + err = dma_async_device_register(&sio->dma); + if (err) + return dev_err_probe(&pdev->dev, err, "failed to register DMA device\n"); + + err = of_dma_controller_register(pdev->dev.of_node, sio_dma_of_xlate, sio); + if (err) { + dma_async_device_unregister(&sio->dma); + return dev_err_probe(&pdev->dev, err, "failed to register with OF\n"); + } + + return 0; +} + +static void sio_remove(struct platform_device *pdev) +{ + struct sio_data *sio = platform_get_drvdata(pdev); + + of_dma_controller_free(pdev->dev.of_node); + dma_async_device_unregister(&sio->dma); +} + +static const struct of_device_id sio_of_match[] = { + { .compatible = "apple,sio", }, + { } +}; +MODULE_DEVICE_TABLE(of, sio_of_match); + +static struct platform_driver apple_sio_driver = { + .driver = { + .name = "apple-sio", + .of_match_table = sio_of_match, + }, + .probe = sio_probe, + .remove = sio_remove, +}; +module_platform_driver(apple_sio_driver); + +MODULE_AUTHOR("Martin Povišer "); +MODULE_DESCRIPTION("Driver for SIO coprocessor on Apple SoCs"); +MODULE_LICENSE("Dual MIT/GPL"); From 8e91765bfea403839ffbbf7fe622fb5243f90f7a Mon Sep 17 00:00:00 2001 From: Asahi Lina Date: Sat, 15 Jun 2024 22:10:41 +0900 Subject: [PATCH 0259/3902] dmaengine: apple-sio: Fix chan freeing in error path Signed-off-by: Asahi Lina --- drivers/dma/apple-sio.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/dma/apple-sio.c b/drivers/dma/apple-sio.c index d0a940499d734d..4354dc50111975 100644 --- a/drivers/dma/apple-sio.c +++ b/drivers/dma/apple-sio.c @@ -277,6 +277,7 @@ static struct dma_async_tx_descriptor *sio_prep_dma_cyclic( siotx->siodesc[i] = d = sio_alloc_desc(siochan->host); if (!d) { + siotx->vd.tx.chan = &siochan->vc.chan; sio_tx_free(&siotx->vd); return NULL; } From 81b1e75f015553bd8f2726dc5943fcf04cc26f54 Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Sat, 9 Nov 2024 22:14:57 +0100 Subject: [PATCH 0260/3902] dmaengine: apple-sio: Implement runtime PM Signed-off-by: Janne Grunau --- drivers/dma/apple-sio.c | 37 +++++++++++++++++++++++++++++++++---- 1 file changed, 33 insertions(+), 4 deletions(-) diff --git a/drivers/dma/apple-sio.c b/drivers/dma/apple-sio.c index 4354dc50111975..511f91999ed3de 100644 --- a/drivers/dma/apple-sio.c +++ b/drivers/dma/apple-sio.c @@ -16,6 +16,7 @@ #include #include #include +#include #include #include "dmaengine.h" @@ -809,10 +810,19 @@ static int sio_probe(struct platform_device *pdev) if (IS_ERR(sio->base)) return PTR_ERR(sio->base); + pm_runtime_get_noresume(&pdev->dev); + pm_runtime_set_active(&pdev->dev); + err = devm_pm_runtime_enable(&pdev->dev); + if (err < 0) + return dev_err_probe(&pdev->dev, err, + "pm_runtime_enable failed: %d\n", err); + sio->rtk = devm_apple_rtkit_init(&pdev->dev, sio, NULL, 0, &sio_rtkit_ops); - if (IS_ERR(sio->rtk)) - return dev_err_probe(&pdev->dev, PTR_ERR(sio->rtk), - "couldn't initialize rtkit\n"); + if (IS_ERR(sio->rtk)) { + err = PTR_ERR(sio->rtk); + dev_err(&pdev->dev, "couldn't initialize rtkit\n"); + goto rpm_put; + } for (i = 1; i < SIO_NTAGS; i++) init_completion(&sio->tags.completions[i]); @@ -881,7 +891,10 @@ static int sio_probe(struct platform_device *pdev) return dev_err_probe(&pdev->dev, err, "failed to register with OF\n"); } - return 0; +rpm_put: + pm_runtime_put(&pdev->dev); + + return err; } static void sio_remove(struct platform_device *pdev) @@ -898,10 +911,26 @@ static const struct of_device_id sio_of_match[] = { }; MODULE_DEVICE_TABLE(of, sio_of_match); +static __maybe_unused int sio_suspend(struct device *dev) +{ + /* + * TODO: SIO coproc sleep state + */ + return 0; +} + +static __maybe_unused int sio_resume(struct device *dev) +{ + return 0; +} + +static DEFINE_RUNTIME_DEV_PM_OPS(sio_pm_ops, sio_suspend, sio_resume, NULL); + static struct platform_driver apple_sio_driver = { .driver = { .name = "apple-sio", .of_match_table = sio_of_match, + .pm = pm_ptr(&sio_pm_ops), }, .probe = sio_probe, .remove = sio_remove, From f4061e19619300078162c617c6e3caffd86418fd Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Wed, 24 Sep 2025 12:22:02 +0200 Subject: [PATCH 0261/3902] dmaengine: apple-admac: Select DMA_VIRTUAL_CHANNELS Investigate. Was previously part of commit "dmaengine: apple-sio: Add Apple SIO driver". Signed-off-by: Janne Grunau --- drivers/dma/Kconfig | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/dma/Kconfig b/drivers/dma/Kconfig index 19ad5e1df76824..3c96c57d02634a 100644 --- a/drivers/dma/Kconfig +++ b/drivers/dma/Kconfig @@ -89,6 +89,7 @@ config APPLE_ADMAC tristate "Apple ADMAC support" depends on ARCH_APPLE || COMPILE_TEST select DMA_ENGINE + select DMA_VIRTUAL_CHANNELS help Enable support for Audio DMA Controller found on Apple Silicon SoCs. From 244735d3758bee8edea8db90a6487bb5a87307c6 Mon Sep 17 00:00:00 2001 From: Hector Martin Date: Thu, 11 Apr 2024 09:51:20 +0900 Subject: [PATCH 0262/3902] prctl: Introduce PR_{SET,GET}_MEM_MODEL On some architectures, it is possible to query and/or change the CPU memory model. This allows userspace to switch to a stricter memory model for performance reasons, such as when emulating code for another architecture where that model is the default. Introduce two prctls to allow userspace to query and set the memory model for a thread. Two models are initially defined: - PR_SET_MEM_MODEL_DEFAULT requests the default memory model for the architecture. - PR_SET_MEM_MODEL_TSO requests the x86 TSO memory model. PR_SET_MEM_MODEL is allowed to set a stricter memory model than requested if available, in which case it will return successfully. If the requested memory model cannot be fulfilled, it will return an error. The memory model that was actually set can be queried by a subsequent call to PR_GET_MEM_MODEL. Examples: - On a CPU with not support for a memory model at least as strong as TSO, PR_SET_MEM_MODEL(PR_SET_MEM_MODEL_TSO) fails. - On a CPU with runtime-configurable TSO support, PR_SET_MEM_MODEL can toggle the memory model between DEFAULT and TSO at will. - On a CPU where the only memory model is at least as strict as TSO, PR_GET_MEM_MODEL will return PR_SET_MEM_MODEL_DEFAULT, and PR_SET_MEM_MODEL(PR_SET_MEM_MODEL_TSO) will return success but leave the memory model at PR_SET_MEM_MODEL_DEFAULT. This implies that the default is in fact at least as strict as TSO. Signed-off-by: Hector Martin Reviewed-by: Neal Gompa --- include/linux/memory_ordering_model.h | 11 +++++++++++ include/uapi/linux/prctl.h | 5 +++++ kernel/sys.c | 21 +++++++++++++++++++++ 3 files changed, 37 insertions(+) create mode 100644 include/linux/memory_ordering_model.h diff --git a/include/linux/memory_ordering_model.h b/include/linux/memory_ordering_model.h new file mode 100644 index 00000000000000..267a12ca66307e --- /dev/null +++ b/include/linux/memory_ordering_model.h @@ -0,0 +1,11 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +#ifndef __ASM_MEMORY_ORDERING_MODEL_H +#define __ASM_MEMORY_ORDERING_MODEL_H + +/* Arch hooks to implement the PR_{GET_SET}_MEM_MODEL prctls */ + +struct task_struct; +int arch_prctl_mem_model_get(struct task_struct *t); +int arch_prctl_mem_model_set(struct task_struct *t, unsigned long val); + +#endif diff --git a/include/uapi/linux/prctl.h b/include/uapi/linux/prctl.h index 51c4e8c82b1e98..d58fce1f4ffafb 100644 --- a/include/uapi/linux/prctl.h +++ b/include/uapi/linux/prctl.h @@ -6,6 +6,11 @@ /* Values to pass as first argument to prctl() */ +#define PR_GET_MEM_MODEL 0x6d4d444c +#define PR_SET_MEM_MODEL 0x4d4d444c +# define PR_SET_MEM_MODEL_DEFAULT 0 +# define PR_SET_MEM_MODEL_TSO 1 + #define PR_SET_PDEATHSIG 1 /* Second arg is a signal */ #define PR_GET_PDEATHSIG 2 /* Second arg is a ptr to return the signal */ diff --git a/kernel/sys.c b/kernel/sys.c index 8b58eece4e580b..93e4d60b6fead8 100644 --- a/kernel/sys.c +++ b/kernel/sys.c @@ -45,6 +45,7 @@ #include #include #include +#include #include #include @@ -2515,6 +2516,16 @@ static int prctl_set_thp_disable(bool thp_disable, unsigned long flags, return 0; } +int __weak arch_prctl_mem_model_get(struct task_struct *t) +{ + return -EINVAL; +} + +int __weak arch_prctl_mem_model_set(struct task_struct *t, unsigned long val) +{ + return -EINVAL; +} + SYSCALL_DEFINE5(prctl, int, option, unsigned long, arg2, unsigned long, arg3, unsigned long, arg4, unsigned long, arg5) { @@ -2528,6 +2539,16 @@ SYSCALL_DEFINE5(prctl, int, option, unsigned long, arg2, unsigned long, arg3, error = 0; switch (option) { + case PR_GET_MEM_MODEL: + if (arg2 || arg3 || arg4 || arg5) + return -EINVAL; + error = arch_prctl_mem_model_get(me); + break; + case PR_SET_MEM_MODEL: + if (arg3 || arg4 || arg5) + return -EINVAL; + error = arch_prctl_mem_model_set(me, arg2); + break; case PR_SET_PDEATHSIG: if (!valid_signal(arg2)) { error = -EINVAL; From 6ace16e4dec02b2c1cd825e8494db1223a09e45d Mon Sep 17 00:00:00 2001 From: Hector Martin Date: Thu, 11 Apr 2024 09:51:21 +0900 Subject: [PATCH 0263/3902] arm64: Implement PR_{GET,SET}_MEM_MODEL for always-TSO CPUs Some ARM64 implementations are known to always use the TSO memory model. Add trivial support for the PR_{GET,SET}_MEM_MODEL prctl, which allows userspace to learn this fact. Known TSO implementations: - Nvidia Denver - Nvidia Carmel - Fujitsu A64FX Signed-off-by: Hector Martin Reviewed-by: Neal Gompa --- arch/arm64/Kconfig | 9 +++++++ arch/arm64/include/asm/cpufeature.h | 4 +++ arch/arm64/kernel/Makefile | 1 + arch/arm64/kernel/cpufeature.c | 11 ++++---- arch/arm64/kernel/cpufeature_impdef.c | 38 +++++++++++++++++++++++++++ arch/arm64/kernel/process.c | 24 +++++++++++++++++ arch/arm64/tools/cpucaps | 1 + 7 files changed, 83 insertions(+), 5 deletions(-) create mode 100644 arch/arm64/kernel/cpufeature_impdef.c diff --git a/arch/arm64/Kconfig b/arch/arm64/Kconfig index 6663ffd23f252e..1ec446616a9407 100644 --- a/arch/arm64/Kconfig +++ b/arch/arm64/Kconfig @@ -2268,6 +2268,15 @@ config ARM64_DEBUG_PRIORITY_MASKING If unsure, say N endif # ARM64_PSEUDO_NMI +config ARM64_MEMORY_MODEL_CONTROL + bool "Runtime memory model control" + help + Some ARM64 CPUs support runtime switching of the CPU memory + model, which can be useful to emulate other CPU architectures + which have different memory models. Say Y to enable support + for the PR_SET_MEM_MODEL/PR_GET_MEM_MODEL prctl() calls on + CPUs with this feature. + config RELOCATABLE bool "Build a relocatable kernel image" if EXPERT select ARCH_HAS_RELR diff --git a/arch/arm64/include/asm/cpufeature.h b/arch/arm64/include/asm/cpufeature.h index e223cbf350e49c..629d38196090a9 100644 --- a/arch/arm64/include/asm/cpufeature.h +++ b/arch/arm64/include/asm/cpufeature.h @@ -1078,6 +1078,10 @@ static inline bool cpu_has_lpa2(void) #endif } +void __init init_cpucap_indirect_list_impdef(void); +void __init init_cpucap_indirect_list_from_array(const struct arm64_cpu_capabilities *caps); +bool cpufeature_matches(u64 reg, const struct arm64_cpu_capabilities *entry); + #endif /* __ASSEMBLY__ */ #endif diff --git a/arch/arm64/kernel/Makefile b/arch/arm64/kernel/Makefile index 76f32e424065e5..70bf2f40a0e4dd 100644 --- a/arch/arm64/kernel/Makefile +++ b/arch/arm64/kernel/Makefile @@ -34,6 +34,7 @@ obj-y := debug-monitors.o entry.o irq.o fpsimd.o \ cpufeature.o alternative.o cacheinfo.o \ smp.o smp_spin_table.o topology.o smccc-call.o \ syscall.o proton-pack.o idle.o patching.o pi/ \ + cpufeature_impdef.o \ rsi.o jump_label.o obj-$(CONFIG_COMPAT) += sys32.o signal32.o \ diff --git a/arch/arm64/kernel/cpufeature.c b/arch/arm64/kernel/cpufeature.c index e25b0f84a22daf..881ff8c901498a 100644 --- a/arch/arm64/kernel/cpufeature.c +++ b/arch/arm64/kernel/cpufeature.c @@ -1080,7 +1080,7 @@ static void init_cpu_ftr_reg(u32 sys_reg, u64 new) extern const struct arm64_cpu_capabilities arm64_errata[]; static const struct arm64_cpu_capabilities arm64_features[]; -static void __init +void __init init_cpucap_indirect_list_from_array(const struct arm64_cpu_capabilities *caps) { for (; caps->matches; caps++) { @@ -1592,8 +1592,8 @@ has_always(const struct arm64_cpu_capabilities *entry, int scope) return true; } -static bool -feature_matches(u64 reg, const struct arm64_cpu_capabilities *entry) +bool +cpufeature_matches(u64 reg, const struct arm64_cpu_capabilities *entry) { int val, min, max; u64 tmp; @@ -1646,14 +1646,14 @@ has_user_cpuid_feature(const struct arm64_cpu_capabilities *entry, int scope) if (!mask) return false; - return feature_matches(val, entry); + return cpufeature_matches(val, entry); } static bool has_cpuid_feature(const struct arm64_cpu_capabilities *entry, int scope) { u64 val = read_scoped_sysreg(entry, scope); - return feature_matches(val, entry); + return cpufeature_matches(val, entry); } const struct cpumask *system_32bit_el0_cpumask(void) @@ -3829,6 +3829,7 @@ void __init setup_boot_cpu_features(void) * handle the boot CPU. */ init_cpucap_indirect_list(); + init_cpucap_indirect_list_impdef(); /* * Detect broken pseudo-NMI. Must be called _before_ the call to diff --git a/arch/arm64/kernel/cpufeature_impdef.c b/arch/arm64/kernel/cpufeature_impdef.c new file mode 100644 index 00000000000000..82224d613db266 --- /dev/null +++ b/arch/arm64/kernel/cpufeature_impdef.c @@ -0,0 +1,38 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Contains implementation-defined CPU feature definitions. + */ + +#include + +#ifdef CONFIG_ARM64_MEMORY_MODEL_CONTROL +static bool has_tso_fixed(const struct arm64_cpu_capabilities *entry, int scope) +{ + /* List of CPUs that always use the TSO memory model */ + static const struct midr_range fixed_tso_list[] = { + MIDR_ALL_VERSIONS(MIDR_NVIDIA_DENVER), + MIDR_ALL_VERSIONS(MIDR_NVIDIA_CARMEL), + MIDR_ALL_VERSIONS(MIDR_FUJITSU_A64FX), + { /* sentinel */ } + }; + + return is_midr_in_range_list(fixed_tso_list); +} +#endif + +static const struct arm64_cpu_capabilities arm64_impdef_features[] = { +#ifdef CONFIG_ARM64_MEMORY_MODEL_CONTROL + { + .desc = "TSO memory model (Fixed)", + .capability = ARM64_HAS_TSO_FIXED, + .type = ARM64_CPUCAP_EARLY_LOCAL_CPU_FEATURE, + .matches = has_tso_fixed, + }, +#endif + {}, +}; + +void __init init_cpucap_indirect_list_impdef(void) +{ + init_cpucap_indirect_list_from_array(arm64_impdef_features); +} diff --git a/arch/arm64/kernel/process.c b/arch/arm64/kernel/process.c index fba7ca102a8c42..b8dc8385cf6357 100644 --- a/arch/arm64/kernel/process.c +++ b/arch/arm64/kernel/process.c @@ -41,6 +41,7 @@ #include #include #include +#include #include #include @@ -698,6 +699,25 @@ void update_sctlr_el1(u64 sctlr) isb(); } +#ifdef CONFIG_ARM64_MEMORY_MODEL_CONTROL +int arch_prctl_mem_model_get(struct task_struct *t) +{ + return PR_SET_MEM_MODEL_DEFAULT; +} + +int arch_prctl_mem_model_set(struct task_struct *t, unsigned long val) +{ + if (alternative_has_cap_unlikely(ARM64_HAS_TSO_FIXED) && + val == PR_SET_MEM_MODEL_TSO) + return 0; + + if (val == PR_SET_MEM_MODEL_DEFAULT) + return 0; + + return -EINVAL; +} +#endif + /* * Thread switching. */ @@ -839,6 +859,10 @@ void arch_setup_new_exec(void) arch_prctl_spec_ctrl_set(current, PR_SPEC_STORE_BYPASS, PR_SPEC_ENABLE); } + +#ifdef CONFIG_ARM64_MEMORY_MODEL_CONTROL + arch_prctl_mem_model_set(current, PR_SET_MEM_MODEL_DEFAULT); +#endif } #ifdef CONFIG_ARM64_TAGGED_ADDR_ABI diff --git a/arch/arm64/tools/cpucaps b/arch/arm64/tools/cpucaps index 1b32c1232d28d8..e536879118b943 100644 --- a/arch/arm64/tools/cpucaps +++ b/arch/arm64/tools/cpucaps @@ -61,6 +61,7 @@ HAS_STAGE2_FWB HAS_TCR2 HAS_TIDCP1 HAS_TLB_RANGE +HAS_TSO_FIXED HAS_VA52 HAS_VIRT_HOST_EXTN HAS_WFXT From e302466b02e7d5139a4657212ded2192cd069886 Mon Sep 17 00:00:00 2001 From: Hector Martin Date: Thu, 11 Apr 2024 09:51:22 +0900 Subject: [PATCH 0264/3902] arm64: Introduce scaffolding to add ACTLR_EL1 to thread state Some CPUs expose IMPDEF features in ACTLR_EL1 that can be meaningfully controlled per-thread (like TSO control on Apple cores). Add the basic scaffolding to save/restore this register as part of context switching. This mechanism is disabled by default both by config symbol and via a runtime check, which ensures it is never triggered unless the system is known to need it for some feature (which also implies that the layout of ACTLR_EL1 is uniform between all CPU core types). Signed-off-by: Hector Martin Reviewed-by: Neal Gompa --- arch/arm64/Kconfig | 3 +++ arch/arm64/include/asm/cpufeature.h | 5 +++++ arch/arm64/include/asm/processor.h | 3 +++ arch/arm64/kernel/process.c | 25 +++++++++++++++++++++++++ arch/arm64/kernel/setup.c | 8 ++++++++ 5 files changed, 44 insertions(+) diff --git a/arch/arm64/Kconfig b/arch/arm64/Kconfig index 1ec446616a9407..c6bae18bd587ef 100644 --- a/arch/arm64/Kconfig +++ b/arch/arm64/Kconfig @@ -443,6 +443,9 @@ config KASAN_SHADOW_OFFSET config UNWIND_TABLES bool +config ARM64_ACTLR_STATE + bool + source "arch/arm64/Kconfig.platforms" menu "Kernel Features" diff --git a/arch/arm64/include/asm/cpufeature.h b/arch/arm64/include/asm/cpufeature.h index 629d38196090a9..b03f32668c6f82 100644 --- a/arch/arm64/include/asm/cpufeature.h +++ b/arch/arm64/include/asm/cpufeature.h @@ -955,6 +955,11 @@ static inline unsigned int get_vmid_bits(u64 mmfr1) return 8; } +static __always_inline bool system_has_actlr_state(void) +{ + return false; +} + s64 arm64_ftr_safe_value(const struct arm64_ftr_bits *ftrp, s64 new, s64 cur); struct arm64_ftr_reg *get_arm64_ftr_reg(u32 sys_id); diff --git a/arch/arm64/include/asm/processor.h b/arch/arm64/include/asm/processor.h index 61d62bfd5a7bfc..a8dad2c6788c3e 100644 --- a/arch/arm64/include/asm/processor.h +++ b/arch/arm64/include/asm/processor.h @@ -194,6 +194,9 @@ struct thread_struct { u64 gcs_base; u64 gcs_size; #endif +#ifdef CONFIG_ARM64_ACTLR_STATE + u64 actlr; +#endif }; static inline unsigned int thread_get_vl(struct thread_struct *thread, diff --git a/arch/arm64/kernel/process.c b/arch/arm64/kernel/process.c index b8dc8385cf6357..eeda5c7fa47382 100644 --- a/arch/arm64/kernel/process.c +++ b/arch/arm64/kernel/process.c @@ -442,6 +442,11 @@ int copy_thread(struct task_struct *p, const struct kernel_clone_args *args) if (system_supports_poe()) p->thread.por_el0 = read_sysreg_s(SYS_POR_EL0); +#ifdef CONFIG_ARM64_ACTLR_STATE + if (system_has_actlr_state()) + p->thread.actlr = read_sysreg(actlr_el1); +#endif + if (stack_start) { if (is_compat_thread(task_thread_info(p))) childregs->compat_sp = stack_start; @@ -718,6 +723,25 @@ int arch_prctl_mem_model_set(struct task_struct *t, unsigned long val) } #endif +#ifdef CONFIG_ARM64_ACTLR_STATE +/* + * IMPDEF control register ACTLR_EL1 handling. Some CPUs use this to + * expose features that can be controlled by userspace. + */ +static void actlr_thread_switch(struct task_struct *next) +{ + if (!system_has_actlr_state()) + return; + + current->thread.actlr = read_sysreg(actlr_el1); + write_sysreg(next->thread.actlr, actlr_el1); +} +#else +static inline void actlr_thread_switch(struct task_struct *next) +{ +} +#endif + /* * Thread switching. */ @@ -737,6 +761,7 @@ struct task_struct *__switch_to(struct task_struct *prev, ptrauth_thread_switch_user(next); permission_overlay_switch(next); gcs_thread_switch(next); + actlr_thread_switch(next); /* * Complete any pending TLB or cache maintenance on this CPU in case the diff --git a/arch/arm64/kernel/setup.c b/arch/arm64/kernel/setup.c index 23c05dc7a8f2ac..0fa2403c6fc0e6 100644 --- a/arch/arm64/kernel/setup.c +++ b/arch/arm64/kernel/setup.c @@ -368,6 +368,14 @@ void __init __no_sanitize_address setup_arch(char **cmdline_p) */ init_task.thread_info.ttbr0 = phys_to_ttbr(__pa_symbol(reserved_pg_dir)); #endif +#ifdef CONFIG_ARM64_ACTLR_STATE + /* Store the boot CPU ACTLR_EL1 value as the default. This will only + * be actually restored during context switching iff the platform is + * known to use ACTLR_EL1 for exposable features and its layout is + * known to be the same on all CPUs. + */ + init_task.thread.actlr = read_sysreg(actlr_el1); +#endif if (boot_args[1] || boot_args[2] || boot_args[3]) { pr_err("WARNING: x1-x3 nonzero in violation of boot protocol:\n" From e36204155a24b8b5d92506add02f8107aad66133 Mon Sep 17 00:00:00 2001 From: Hector Martin Date: Thu, 11 Apr 2024 09:51:23 +0900 Subject: [PATCH 0265/3902] arm64: Implement Apple IMPDEF TSO memory model control Apple CPUs may implement the TSO memory model as an optional configurable mode. This allows x86 emulators to simplify their load/store handling, greatly increasing performance. Expose this via the prctl PR_SET_MEM_MODEL_TSO mechanism. We use the Apple IMPDEF AIDR_EL1 register to check for the availability of TSO mode, and enable this codepath on all CPUs with an Apple implementer. This relies on the ACTLR_EL1 thread state scaffolding introduced earlier. Signed-off-by: Hector Martin Reviewed-by: Neal Gompa --- arch/arm64/Kconfig | 1 + arch/arm64/include/asm/apple_cpufeature.h | 15 +++++++ arch/arm64/include/asm/cpufeature.h | 3 +- arch/arm64/kernel/cpufeature_impdef.c | 53 +++++++++++++++++++++++ arch/arm64/kernel/process.c | 22 ++++++++++ arch/arm64/tools/cpucaps | 1 + 6 files changed, 94 insertions(+), 1 deletion(-) create mode 100644 arch/arm64/include/asm/apple_cpufeature.h diff --git a/arch/arm64/Kconfig b/arch/arm64/Kconfig index c6bae18bd587ef..5a97bc145a980a 100644 --- a/arch/arm64/Kconfig +++ b/arch/arm64/Kconfig @@ -2273,6 +2273,7 @@ endif # ARM64_PSEUDO_NMI config ARM64_MEMORY_MODEL_CONTROL bool "Runtime memory model control" + select ARM64_ACTLR_STATE help Some ARM64 CPUs support runtime switching of the CPU memory model, which can be useful to emulate other CPU architectures diff --git a/arch/arm64/include/asm/apple_cpufeature.h b/arch/arm64/include/asm/apple_cpufeature.h new file mode 100644 index 00000000000000..4370d91ffa3ec9 --- /dev/null +++ b/arch/arm64/include/asm/apple_cpufeature.h @@ -0,0 +1,15 @@ +// SPDX-License-Identifier: GPL-2.0 + +#ifndef __ASM_APPLE_CPUFEATURES_H +#define __ASM_APPLE_CPUFEATURES_H + +#include +#include + +#define AIDR_APPLE_TSO_SHIFT 9 +#define AIDR_APPLE_TSO BIT(9) + +#define ACTLR_APPLE_TSO_SHIFT 1 +#define ACTLR_APPLE_TSO BIT(1) + +#endif diff --git a/arch/arm64/include/asm/cpufeature.h b/arch/arm64/include/asm/cpufeature.h index b03f32668c6f82..ccea6e4b696186 100644 --- a/arch/arm64/include/asm/cpufeature.h +++ b/arch/arm64/include/asm/cpufeature.h @@ -957,7 +957,8 @@ static inline unsigned int get_vmid_bits(u64 mmfr1) static __always_inline bool system_has_actlr_state(void) { - return false; + return IS_ENABLED(CONFIG_ARM64_ACTLR_STATE) && + alternative_has_cap_unlikely(ARM64_HAS_TSO_APPLE); } s64 arm64_ftr_safe_value(const struct arm64_ftr_bits *ftrp, s64 new, s64 cur); diff --git a/arch/arm64/kernel/cpufeature_impdef.c b/arch/arm64/kernel/cpufeature_impdef.c index 82224d613db266..29bde12180eabc 100644 --- a/arch/arm64/kernel/cpufeature_impdef.c +++ b/arch/arm64/kernel/cpufeature_impdef.c @@ -3,9 +3,51 @@ * Contains implementation-defined CPU feature definitions. */ +#define pr_fmt(fmt) "CPU features: " fmt + #include +#include +#include +#include +#include #ifdef CONFIG_ARM64_MEMORY_MODEL_CONTROL +static bool has_apple_feature(const struct arm64_cpu_capabilities *entry, int scope) +{ + u64 val; + WARN_ON(scope == SCOPE_LOCAL_CPU && preemptible()); + + if (read_cpuid_implementor() != ARM_CPU_IMP_APPLE) + return false; + + val = read_sysreg(aidr_el1); + return cpufeature_matches(val, entry); +} + +static bool has_apple_tso(const struct arm64_cpu_capabilities *entry, int scope) +{ + u64 val; + + if (!has_apple_feature(entry, scope)) + return false; + + /* + * KVM and old versions of the macOS hypervisor will advertise TSO in + * AIDR_EL1, but then ignore writes to ACTLR_EL1. Test that the bit is + * actually writable before enabling TSO. + */ + + val = read_sysreg(actlr_el1); + write_sysreg(val ^ ACTLR_APPLE_TSO, actlr_el1); + if (!((val ^ read_sysreg(actlr_el1)) & ACTLR_APPLE_TSO)) { + pr_info_once("CPU advertises Apple TSO but it is broken, ignoring\n"); + return false; + } + + write_sysreg(val, actlr_el1); + return true; +} + static bool has_tso_fixed(const struct arm64_cpu_capabilities *entry, int scope) { /* List of CPUs that always use the TSO memory model */ @@ -22,6 +64,17 @@ static bool has_tso_fixed(const struct arm64_cpu_capabilities *entry, int scope) static const struct arm64_cpu_capabilities arm64_impdef_features[] = { #ifdef CONFIG_ARM64_MEMORY_MODEL_CONTROL + { + .desc = "TSO memory model (Apple)", + .capability = ARM64_HAS_TSO_APPLE, + .type = ARM64_CPUCAP_EARLY_LOCAL_CPU_FEATURE, + .matches = has_apple_tso, + .field_pos = AIDR_APPLE_TSO_SHIFT, + .field_width = 1, + .sign = FTR_UNSIGNED, + .min_field_value = 1, + .max_field_value = 1, + }, { .desc = "TSO memory model (Fixed)", .capability = ARM64_HAS_TSO_FIXED, diff --git a/arch/arm64/kernel/process.c b/arch/arm64/kernel/process.c index eeda5c7fa47382..b7e0ad1dd58ffd 100644 --- a/arch/arm64/kernel/process.c +++ b/arch/arm64/kernel/process.c @@ -44,6 +44,7 @@ #include #include +#include #include #include #include @@ -707,6 +708,10 @@ void update_sctlr_el1(u64 sctlr) #ifdef CONFIG_ARM64_MEMORY_MODEL_CONTROL int arch_prctl_mem_model_get(struct task_struct *t) { + if (alternative_has_cap_unlikely(ARM64_HAS_TSO_APPLE) && + t->thread.actlr & ACTLR_APPLE_TSO) + return PR_SET_MEM_MODEL_TSO; + return PR_SET_MEM_MODEL_DEFAULT; } @@ -716,6 +721,23 @@ int arch_prctl_mem_model_set(struct task_struct *t, unsigned long val) val == PR_SET_MEM_MODEL_TSO) return 0; + if (alternative_has_cap_unlikely(ARM64_HAS_TSO_APPLE)) { + WARN_ON(!system_has_actlr_state()); + + switch (val) { + case PR_SET_MEM_MODEL_TSO: + t->thread.actlr |= ACTLR_APPLE_TSO; + break; + case PR_SET_MEM_MODEL_DEFAULT: + t->thread.actlr &= ~ACTLR_APPLE_TSO; + break; + default: + return -EINVAL; + } + write_sysreg(t->thread.actlr, actlr_el1); + return 0; + } + if (val == PR_SET_MEM_MODEL_DEFAULT) return 0; diff --git a/arch/arm64/tools/cpucaps b/arch/arm64/tools/cpucaps index e536879118b943..a830c4eb4e8e51 100644 --- a/arch/arm64/tools/cpucaps +++ b/arch/arm64/tools/cpucaps @@ -61,6 +61,7 @@ HAS_STAGE2_FWB HAS_TCR2 HAS_TIDCP1 HAS_TLB_RANGE +HAS_TSO_APPLE HAS_TSO_FIXED HAS_VA52 HAS_VIRT_HOST_EXTN From 9c5b7b59b1d7579eb9c43eb2cf58678374b28769 Mon Sep 17 00:00:00 2001 From: Asahi Lina Date: Sat, 25 May 2024 20:22:29 +0900 Subject: [PATCH 0266/3902] KVM: arm64: Expose TSO capability to guests and context switch Signed-off-by: Asahi Lina --- arch/arm64/include/asm/kvm_emulate.h | 5 +++++ arch/arm64/kernel/cpufeature_impdef.c | 26 ++++++++++++++++++++++ arch/arm64/kvm/hyp/include/hyp/sysreg-sr.h | 17 ++++++++++++++ arch/arm64/tools/cpucaps | 2 ++ 4 files changed, 50 insertions(+) diff --git a/arch/arm64/include/asm/kvm_emulate.h b/arch/arm64/include/asm/kvm_emulate.h index c9eab316398e2e..32088a71dc10bf 100644 --- a/arch/arm64/include/asm/kvm_emulate.h +++ b/arch/arm64/include/asm/kvm_emulate.h @@ -103,6 +103,11 @@ static inline void vcpu_reset_hcr(struct kvm_vcpu *vcpu) { if (!vcpu_has_run_once(vcpu)) vcpu->arch.hcr_el2 = HCR_GUEST_FLAGS; + if (IS_ENABLED(CONFIG_ARM64_ACTLR_STATE) && ( + alternative_has_cap_unlikely(ARM64_HAS_ACTLR_VIRT) || + alternative_has_cap_unlikely(ARM64_HAS_ACTLR_VIRT_APPLE) + )) + vcpu->arch.hcr_el2 &= ~HCR_TACR; /* * For non-FWB CPUs, we trap VM ops (HCR_EL2.TVM) until M+C diff --git a/arch/arm64/kernel/cpufeature_impdef.c b/arch/arm64/kernel/cpufeature_impdef.c index 29bde12180eabc..aee7571fbadb84 100644 --- a/arch/arm64/kernel/cpufeature_impdef.c +++ b/arch/arm64/kernel/cpufeature_impdef.c @@ -62,6 +62,20 @@ static bool has_tso_fixed(const struct arm64_cpu_capabilities *entry, int scope) } #endif +static bool has_apple_actlr_virt_impdef(const struct arm64_cpu_capabilities *entry, int scope) +{ + u64 midr = read_cpuid_id() & MIDR_CPU_MODEL_MASK; + + return midr >= MIDR_APPLE_M1_ICESTORM && midr <= MIDR_APPLE_M1_FIRESTORM_MAX; +} + +static bool has_apple_actlr_virt(const struct arm64_cpu_capabilities *entry, int scope) +{ + u64 midr = read_cpuid_id() & MIDR_CPU_MODEL_MASK; + + return midr >= MIDR_APPLE_M2_BLIZZARD && midr <= MIDR_CPU_MODEL(ARM_CPU_IMP_APPLE, 0xfff); +} + static const struct arm64_cpu_capabilities arm64_impdef_features[] = { #ifdef CONFIG_ARM64_MEMORY_MODEL_CONTROL { @@ -82,6 +96,18 @@ static const struct arm64_cpu_capabilities arm64_impdef_features[] = { .matches = has_tso_fixed, }, #endif + { + .desc = "ACTLR virtualization (IMPDEF, Apple)", + .capability = ARM64_HAS_ACTLR_VIRT_APPLE, + .type = ARM64_CPUCAP_EARLY_LOCAL_CPU_FEATURE, + .matches = has_apple_actlr_virt_impdef, + }, + { + .desc = "ACTLR virtualization (architectural?)", + .capability = ARM64_HAS_ACTLR_VIRT, + .type = ARM64_CPUCAP_EARLY_LOCAL_CPU_FEATURE, + .matches = has_apple_actlr_virt, + }, {}, }; diff --git a/arch/arm64/kvm/hyp/include/hyp/sysreg-sr.h b/arch/arm64/kvm/hyp/include/hyp/sysreg-sr.h index a17cbe7582de90..7c8383c809ea36 100644 --- a/arch/arm64/kvm/hyp/include/hyp/sysreg-sr.h +++ b/arch/arm64/kvm/hyp/include/hyp/sysreg-sr.h @@ -16,6 +16,9 @@ #include #include +#define SYS_IMP_APL_ACTLR_EL12 sys_reg(3, 6, 15, 14, 6) +#define SYS_ACTLR_EL12 sys_reg(3, 5, 1, 0, 1) + static inline bool ctxt_has_s1poe(struct kvm_cpu_context *ctxt); static inline struct kvm_vcpu *ctxt_to_vcpu(struct kvm_cpu_context *ctxt) @@ -172,6 +175,13 @@ static inline void __sysreg_save_el1_state(struct kvm_cpu_context *ctxt) if (ctxt_has_sctlr2(ctxt)) ctxt_sys_reg(ctxt, SCTLR2_EL1) = read_sysreg_el1(SYS_SCTLR2); + + if (IS_ENABLED(CONFIG_ARM64_ACTLR_STATE)) { + if (alternative_has_cap_unlikely(ARM64_HAS_ACTLR_VIRT)) + ctxt_sys_reg(ctxt, ACTLR_EL1) = read_sysreg_s(SYS_ACTLR_EL12); + else if (alternative_has_cap_unlikely(ARM64_HAS_ACTLR_VIRT_APPLE)) + ctxt_sys_reg(ctxt, ACTLR_EL1) = read_sysreg_s(SYS_IMP_APL_ACTLR_EL12); + } } static inline void __sysreg_save_el2_return_state(struct kvm_cpu_context *ctxt) @@ -256,6 +266,13 @@ static inline void __sysreg_restore_el1_state(struct kvm_cpu_context *ctxt, write_sysreg(ctxt_sys_reg(ctxt, PAR_EL1), par_el1); write_sysreg(ctxt_sys_reg(ctxt, TPIDR_EL1), tpidr_el1); + if (IS_ENABLED(CONFIG_ARM64_ACTLR_STATE)) { + if (alternative_has_cap_unlikely(ARM64_HAS_ACTLR_VIRT)) + write_sysreg_s(ctxt_sys_reg(ctxt, ACTLR_EL1), SYS_ACTLR_EL12); + else if (alternative_has_cap_unlikely(ARM64_HAS_ACTLR_VIRT_APPLE)) + write_sysreg_s(ctxt_sys_reg(ctxt, ACTLR_EL1), SYS_IMP_APL_ACTLR_EL12); + } + if (ctxt_has_mte(ctxt)) { write_sysreg_el1(ctxt_sys_reg(ctxt, TFSR_EL1), SYS_TFSR); write_sysreg_s(ctxt_sys_reg(ctxt, TFSRE0_EL1), SYS_TFSRE0_EL1); diff --git a/arch/arm64/tools/cpucaps b/arch/arm64/tools/cpucaps index a830c4eb4e8e51..7398b50bb090a4 100644 --- a/arch/arm64/tools/cpucaps +++ b/arch/arm64/tools/cpucaps @@ -8,6 +8,8 @@ BTI # Unreliable: use system_supports_32bit_el0() instead. HAS_32BIT_EL0_DO_NOT_USE HAS_32BIT_EL1 +HAS_ACTLR_VIRT +HAS_ACTLR_VIRT_APPLE HAS_ADDRESS_AUTH HAS_ADDRESS_AUTH_ARCH_QARMA3 HAS_ADDRESS_AUTH_ARCH_QARMA5 From ba31db044e9af93e19d2e86899c3f6bca173569e Mon Sep 17 00:00:00 2001 From: Eileen Yoon Date: Thu, 31 Aug 2023 19:08:46 +0900 Subject: [PATCH 0267/3902] media: apple: Add Apple ISP driver Signed-off-by: Eileen Yoon --- drivers/media/platform/Kconfig | 1 + drivers/media/platform/Makefile | 1 + drivers/media/platform/apple/Kconfig | 5 + drivers/media/platform/apple/Makefile | 3 + drivers/media/platform/apple/isp/.gitignore | 1 + drivers/media/platform/apple/isp/Kconfig | 10 + drivers/media/platform/apple/isp/Makefile | 3 + drivers/media/platform/apple/isp/isp-cam.c | 540 +++++++++++++++++ drivers/media/platform/apple/isp/isp-cam.h | 20 + drivers/media/platform/apple/isp/isp-cmd.c | 544 +++++++++++++++++ drivers/media/platform/apple/isp/isp-cmd.h | 532 ++++++++++++++++ drivers/media/platform/apple/isp/isp-drv.c | 333 ++++++++++ drivers/media/platform/apple/isp/isp-drv.h | 258 ++++++++ drivers/media/platform/apple/isp/isp-fw.c | 606 +++++++++++++++++++ drivers/media/platform/apple/isp/isp-fw.h | 12 + drivers/media/platform/apple/isp/isp-iommu.c | 275 +++++++++ drivers/media/platform/apple/isp/isp-iommu.h | 38 ++ drivers/media/platform/apple/isp/isp-ipc.c | 329 ++++++++++ drivers/media/platform/apple/isp/isp-ipc.h | 26 + drivers/media/platform/apple/isp/isp-regs.h | 62 ++ drivers/media/platform/apple/isp/isp-v4l2.c | 602 ++++++++++++++++++ drivers/media/platform/apple/isp/isp-v4l2.h | 12 + 22 files changed, 4213 insertions(+) create mode 100644 drivers/media/platform/apple/Kconfig create mode 100644 drivers/media/platform/apple/Makefile create mode 100644 drivers/media/platform/apple/isp/.gitignore create mode 100644 drivers/media/platform/apple/isp/Kconfig create mode 100644 drivers/media/platform/apple/isp/Makefile create mode 100644 drivers/media/platform/apple/isp/isp-cam.c create mode 100644 drivers/media/platform/apple/isp/isp-cam.h create mode 100644 drivers/media/platform/apple/isp/isp-cmd.c create mode 100644 drivers/media/platform/apple/isp/isp-cmd.h create mode 100644 drivers/media/platform/apple/isp/isp-drv.c create mode 100644 drivers/media/platform/apple/isp/isp-drv.h create mode 100644 drivers/media/platform/apple/isp/isp-fw.c create mode 100644 drivers/media/platform/apple/isp/isp-fw.h create mode 100644 drivers/media/platform/apple/isp/isp-iommu.c create mode 100644 drivers/media/platform/apple/isp/isp-iommu.h create mode 100644 drivers/media/platform/apple/isp/isp-ipc.c create mode 100644 drivers/media/platform/apple/isp/isp-ipc.h create mode 100644 drivers/media/platform/apple/isp/isp-regs.h create mode 100644 drivers/media/platform/apple/isp/isp-v4l2.c create mode 100644 drivers/media/platform/apple/isp/isp-v4l2.h diff --git a/drivers/media/platform/Kconfig b/drivers/media/platform/Kconfig index 9287faafdce587..26c8cff57c38ab 100644 --- a/drivers/media/platform/Kconfig +++ b/drivers/media/platform/Kconfig @@ -65,6 +65,7 @@ config VIDEO_MUX source "drivers/media/platform/allegro-dvt/Kconfig" source "drivers/media/platform/amlogic/Kconfig" source "drivers/media/platform/amphion/Kconfig" +source "drivers/media/platform/apple/Kconfig" source "drivers/media/platform/aspeed/Kconfig" source "drivers/media/platform/atmel/Kconfig" source "drivers/media/platform/broadcom/Kconfig" diff --git a/drivers/media/platform/Makefile b/drivers/media/platform/Makefile index 6fd7db0541c7ec..e8d019518cde7f 100644 --- a/drivers/media/platform/Makefile +++ b/drivers/media/platform/Makefile @@ -8,6 +8,7 @@ obj-y += allegro-dvt/ obj-y += amlogic/ obj-y += amphion/ +obj-y += apple/ obj-y += aspeed/ obj-y += atmel/ obj-y += broadcom/ diff --git a/drivers/media/platform/apple/Kconfig b/drivers/media/platform/apple/Kconfig new file mode 100644 index 00000000000000..f16508bff5242a --- /dev/null +++ b/drivers/media/platform/apple/Kconfig @@ -0,0 +1,5 @@ +# SPDX-License-Identifier: GPL-2.0-only + +comment "Apple media platform drivers" + +source "drivers/media/platform/apple/isp/Kconfig" diff --git a/drivers/media/platform/apple/Makefile b/drivers/media/platform/apple/Makefile new file mode 100644 index 00000000000000..d8fe985b0e6c37 --- /dev/null +++ b/drivers/media/platform/apple/Makefile @@ -0,0 +1,3 @@ +# SPDX-License-Identifier: GPL-2.0-only + +obj-y += isp/ diff --git a/drivers/media/platform/apple/isp/.gitignore b/drivers/media/platform/apple/isp/.gitignore new file mode 100644 index 00000000000000..bd7fab40e0d98a --- /dev/null +++ b/drivers/media/platform/apple/isp/.gitignore @@ -0,0 +1 @@ +.clang-format diff --git a/drivers/media/platform/apple/isp/Kconfig b/drivers/media/platform/apple/isp/Kconfig new file mode 100644 index 00000000000000..f0e2173640ab73 --- /dev/null +++ b/drivers/media/platform/apple/isp/Kconfig @@ -0,0 +1,10 @@ +# SPDX-License-Identifier: GPL-2.0-only + +config VIDEO_APPLE_ISP + tristate "Apple Silicon Image Signal Processor driver" + select VIDEOBUF2_CORE + select VIDEOBUF2_V4L2 + select VIDEOBUF2_DMA_SG + depends on ARCH_APPLE || COMPILE_TEST + depends on V4L_PLATFORM_DRIVERS + depends on VIDEO_DEV diff --git a/drivers/media/platform/apple/isp/Makefile b/drivers/media/platform/apple/isp/Makefile new file mode 100644 index 00000000000000..4649f32987f025 --- /dev/null +++ b/drivers/media/platform/apple/isp/Makefile @@ -0,0 +1,3 @@ +# SPDX-License-Identifier: GPL-2.0-only +apple-isp-y := isp-cam.o isp-cmd.o isp-drv.o isp-fw.o isp-iommu.o isp-ipc.o isp-v4l2.o +obj-$(CONFIG_VIDEO_APPLE_ISP) += apple-isp.o diff --git a/drivers/media/platform/apple/isp/isp-cam.c b/drivers/media/platform/apple/isp/isp-cam.c new file mode 100644 index 00000000000000..6d08248ef44776 --- /dev/null +++ b/drivers/media/platform/apple/isp/isp-cam.c @@ -0,0 +1,540 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* Copyright 2023 Eileen Yoon */ + +#include + +#include "isp-cam.h" +#include "isp-cmd.h" +#include "isp-fw.h" +#include "isp-iommu.h" + +struct isp_setfile { + u32 version; + u32 magic; + const char *path; + size_t size; +}; + +struct isp_preset { + u32 index; + u32 width; + u32 height; + u32 x1; + u32 y1; + u32 x2; + u32 y2; + u32 orig_width; + u32 orig_height; +}; + +// clang-format off +static const struct isp_setfile isp_setfiles[] = { + [ISP_IMX248_1820_01] = {0x248, 0x18200103, "isp/1820_01XX.dat", 0x442c}, + [ISP_IMX248_1822_02] = {0x248, 0x18220201, "isp/1822_02XX.dat", 0x442c}, + [ISP_IMX343_5221_02] = {0x343, 0x52210211, "isp/5221_02XX.dat", 0x4870}, + [ISP_IMX354_9251_02] = {0x354, 0x92510208, "isp/9251_02XX.dat", 0xa5ec}, + [ISP_IMX356_4820_01] = {0x356, 0x48200107, "isp/4820_01XX.dat", 0x9324}, + [ISP_IMX356_4820_02] = {0x356, 0x48200206, "isp/4820_02XX.dat", 0x9324}, + [ISP_IMX364_8720_01] = {0x364, 0x87200103, "isp/8720_01XX.dat", 0x36ac}, + [ISP_IMX364_8723_01] = {0x364, 0x87230101, "isp/8723_01XX.dat", 0x361c}, + [ISP_IMX372_3820_01] = {0x372, 0x38200108, "isp/3820_01XX.dat", 0xfdb0}, + [ISP_IMX372_3820_02] = {0x372, 0x38200205, "isp/3820_02XX.dat", 0xfdb0}, + [ISP_IMX372_3820_11] = {0x372, 0x38201104, "isp/3820_11XX.dat", 0xfdb0}, + [ISP_IMX372_3820_12] = {0x372, 0x38201204, "isp/3820_12XX.dat", 0xfdb0}, + [ISP_IMX405_9720_01] = {0x405, 0x97200102, "isp/9720_01XX.dat", 0x92c8}, + [ISP_IMX405_9721_01] = {0x405, 0x97210102, "isp/9721_01XX.dat", 0x9818}, + [ISP_IMX405_9723_01] = {0x405, 0x97230101, "isp/9723_01XX.dat", 0x92c8}, + [ISP_IMX414_2520_01] = {0x414, 0x25200102, "isp/2520_01XX.dat", 0xa444}, + [ISP_IMX503_7820_01] = {0x503, 0x78200109, "isp/7820_01XX.dat", 0xb268}, + [ISP_IMX503_7820_02] = {0x503, 0x78200206, "isp/7820_02XX.dat", 0xb268}, + [ISP_IMX505_3921_01] = {0x505, 0x39210102, "isp/3921_01XX.dat", 0x89b0}, + [ISP_IMX514_2820_01] = {0x514, 0x28200108, "isp/2820_01XX.dat", 0xa198}, + [ISP_IMX514_2820_02] = {0x514, 0x28200205, "isp/2820_02XX.dat", 0xa198}, + [ISP_IMX514_2820_03] = {0x514, 0x28200305, "isp/2820_03XX.dat", 0xa198}, + [ISP_IMX514_2820_04] = {0x514, 0x28200405, "isp/2820_04XX.dat", 0xa198}, + [ISP_IMX558_1921_01] = {0x558, 0x19210106, "isp/1921_01XX.dat", 0xad40}, + [ISP_IMX558_1922_02] = {0x558, 0x19220201, "isp/1922_02XX.dat", 0xad40}, + [ISP_IMX603_7920_01] = {0x603, 0x79200109, "isp/7920_01XX.dat", 0xad2c}, + [ISP_IMX603_7920_02] = {0x603, 0x79200205, "isp/7920_02XX.dat", 0xad2c}, + [ISP_IMX603_7921_01] = {0x603, 0x79210104, "isp/7921_01XX.dat", 0xad90}, + [ISP_IMX613_4920_01] = {0x613, 0x49200108, "isp/4920_01XX.dat", 0x9324}, + [ISP_IMX613_4920_02] = {0x613, 0x49200204, "isp/4920_02XX.dat", 0x9324}, + [ISP_IMX614_2921_01] = {0x614, 0x29210107, "isp/2921_01XX.dat", 0xed6c}, + [ISP_IMX614_2921_02] = {0x614, 0x29210202, "isp/2921_02XX.dat", 0xed6c}, + [ISP_IMX614_2922_02] = {0x614, 0x29220201, "isp/2922_02XX.dat", 0xed6c}, + [ISP_IMX633_3622_01] = {0x633, 0x36220111, "isp/3622_01XX.dat", 0x100d4}, + [ISP_IMX703_7721_01] = {0x703, 0x77210106, "isp/7721_01XX.dat", 0x936c}, + [ISP_IMX703_7722_01] = {0x703, 0x77220106, "isp/7722_01XX.dat", 0xac20}, + [ISP_IMX713_4721_01] = {0x713, 0x47210107, "isp/4721_01XX.dat", 0x936c}, + [ISP_IMX713_4722_01] = {0x713, 0x47220109, "isp/4722_01XX.dat", 0x9218}, + [ISP_IMX714_2022_01] = {0x714, 0x20220107, "isp/2022_01XX.dat", 0xa198}, + [ISP_IMX772_3721_01] = {0x772, 0x37210106, "isp/3721_01XX.dat", 0xfdf8}, + [ISP_IMX772_3721_11] = {0x772, 0x37211106, "isp/3721_11XX.dat", 0xfe14}, + [ISP_IMX772_3722_01] = {0x772, 0x37220104, "isp/3722_01XX.dat", 0xfca4}, + [ISP_IMX772_3723_01] = {0x772, 0x37230106, "isp/3723_01XX.dat", 0xfca4}, + [ISP_IMX814_2123_01] = {0x814, 0x21230101, "isp/2123_01XX.dat", 0xed54}, + [ISP_IMX853_7622_01] = {0x853, 0x76220112, "isp/7622_01XX.dat", 0x247f8}, + [ISP_IMX913_7523_01] = {0x913, 0x75230107, "isp/7523_01XX.dat", 0x247f8}, + [ISP_VD56G0_6221_01] = {0xd56, 0x62210102, "isp/6221_01XX.dat", 0x1b80}, + [ISP_VD56G0_6222_01] = {0xd56, 0x62220102, "isp/6222_01XX.dat", 0x1b80}, +}; +// clang-format on + +// one day we will do this intelligently +static const struct isp_preset isp_presets[] = { + [ISP_IMX248_1820_01] = { 0, 1280, 720, 8, 8, 1280, 720, 1296, 736 }, +}; + +static int isp_ch_get_sensor_id(struct apple_isp *isp, u32 ch) +{ + struct isp_format *fmt = isp_get_format(isp, ch); + enum isp_sensor_id id; + int err = 0; + + /* TODO need more datapoints to figure out the sub-versions + * Defaulting to 1st release for now, the calib files aren't too different. + */ + switch (fmt->version) { + case 0x248: + id = ISP_IMX248_1820_01; + break; + case 0x343: + id = ISP_IMX343_5221_02; + break; + case 0x354: + id = ISP_IMX354_9251_02; + break; + case 0x356: + id = ISP_IMX356_4820_01; + break; + case 0x364: + id = ISP_IMX364_8720_01; + break; + case 0x372: + id = ISP_IMX372_3820_01; + break; + case 0x405: + id = ISP_IMX405_9720_01; + break; + case 0x414: + id = ISP_IMX414_2520_01; + break; + case 0x503: + id = ISP_IMX503_7820_01; + break; + case 0x505: + id = ISP_IMX505_3921_01; + break; + case 0x514: + id = ISP_IMX514_2820_01; + break; + case 0x558: + id = ISP_IMX558_1921_01; + break; + case 0x603: + id = ISP_IMX603_7920_01; + break; + case 0x613: + id = ISP_IMX613_4920_01; + break; + case 0x614: + id = ISP_IMX614_2921_01; + break; + case 0x633: + id = ISP_IMX633_3622_01; + break; + case 0x703: + id = ISP_IMX703_7721_01; + break; + case 0x713: + id = ISP_IMX713_4721_01; + break; + case 0x714: + id = ISP_IMX714_2022_01; + break; + case 0x772: + id = ISP_IMX772_3721_01; + break; + case 0x814: + id = ISP_IMX814_2123_01; + break; + case 0x853: + id = ISP_IMX853_7622_01; + break; + case 0x913: + id = ISP_IMX913_7523_01; + break; + case 0xd56: + id = ISP_VD56G0_6221_01; + break; + default: + err = -EINVAL; + break; + } + + if (err) + dev_err(isp->dev, "invalid sensor version: 0x%x\n", + fmt->version); + else + fmt->id = id; + + return err; +} + +static int isp_ch_cache_sensor_info(struct apple_isp *isp, u32 ch) +{ + struct isp_format *fmt = isp_get_format(isp, ch); + int err = 0; + + struct cmd_ch_info *args; /* Too big to allocate on stack */ + args = kzalloc(sizeof(*args), GFP_KERNEL); + if (!args) + return -ENOMEM; + + err = isp_cmd_ch_info_get(isp, ch, args); + if (err) + goto exit; + + dev_info(isp->dev, "found sensor %x %s on ch %d\n", args->version, + args->module_sn, ch); + + fmt->version = args->version; + fmt->num_presets = args->num_presets; + + pr_info("apple-isp: ch: CISP_CMD_CH_INFO_GET: %d\n", ch); + print_hex_dump(KERN_INFO, "apple-isp: ch: ", DUMP_PREFIX_NONE, 32, 4, + args, sizeof(*args), false); + + err = isp_ch_get_sensor_id(isp, ch); + if (err || (fmt->id != ISP_IMX248_1820_01)) { + dev_err(isp->dev, + "ch %d: unsupported sensor. Please file a bug report with hardware info & dmesg trace.\n", + ch); + return -ENODEV; + } + +exit: + kfree(args); + + return err; +} + +static int isp_ch_get_camera_preset(struct apple_isp *isp, u32 ch, u32 ps) +{ + int err = 0; + + struct cmd_ch_camera_config *args; /* Too big to allocate on stack */ + args = kzalloc(sizeof(*args), GFP_KERNEL); + if (!args) + return -ENOMEM; + + err = isp_cmd_ch_camera_config_get(isp, ch, ps, args); + if (err) + goto exit; + + pr_info("apple-isp: ps: CISP_CMD_CH_CAMERA_CONFIG_GET: %d\n", ps); + print_hex_dump(KERN_INFO, "apple-isp: ps: ", DUMP_PREFIX_NONE, 32, 4, + args, sizeof(*args), false); + +exit: + kfree(args); + + return err; +} + +static void isp_ch_dump_camera_presets(struct apple_isp *isp, u32 ch) +{ + struct isp_format *fmt = isp_get_format(isp, ch); + for (u32 ps = 0; ps < fmt->num_presets; ps++) { + isp_ch_get_camera_preset(isp, ch, ps); + } +} + +static int isp_ch_cache_camera_preset(struct apple_isp *isp, u32 ch) +{ + struct isp_format *fmt = isp_get_format(isp, ch); + const struct isp_preset *preset = &isp_presets[fmt->id]; + size_t total_size; + + isp_ch_dump_camera_presets(isp, ch); + + fmt->preset = preset->index; + + fmt->width = preset->width; + fmt->height = preset->height; + + fmt->x1 = preset->x1; + fmt->y1 = preset->y1; + fmt->x2 = preset->x2; + fmt->y2 = preset->y2; + + /* I really fucking hope they all use NV12. */ + fmt->num_planes = 2; + fmt->plane_size[0] = fmt->width * fmt->height; + fmt->plane_size[1] = fmt->plane_size[0] / 2; + + total_size = 0; + for (int i = 0; i < fmt->num_planes; i++) + total_size += fmt->plane_size[i]; + fmt->total_size = total_size; + + return 0; +} + +static int isp_ch_cache_camera_info(struct apple_isp *isp, u32 ch) +{ + int err; + + err = isp_ch_cache_sensor_info(isp, ch); + if (err) { + dev_err(isp->dev, "ch %d: failed to cache sensor info: %d\n", + ch, err); + return err; + } + + err = isp_ch_cache_camera_preset(isp, ch); + if (err) { + dev_err(isp->dev, "ch %d: failed to cache camera preset: %d\n", + ch, err); + return err; + } + + return 0; +} + +static int isp_detect_camera(struct apple_isp *isp) +{ + int err; + + struct cmd_config_get args; + memset(&args, 0, sizeof(args)); + + err = isp_cmd_config_get(isp, &args); + if (err) + return err; + + pr_info("apple-isp: CISP_CMD_CONFIG_GET: \n"); + print_hex_dump(KERN_INFO, "apple-isp: ", DUMP_PREFIX_NONE, 32, 4, &args, + sizeof(args), false); + + if (!args.num_channels) { + dev_err(isp->dev, "did not detect any channels\n"); + return -ENODEV; + } + + if (args.num_channels > ISP_MAX_CHANNELS) { + dev_warn(isp->dev, "found %d channels when maximum is %d\n", + args.num_channels, ISP_MAX_CHANNELS); + args.num_channels = ISP_MAX_CHANNELS; + } + + if (args.num_channels > 1) { + dev_warn( + isp->dev, + "warning: driver doesn't support multiple channels. Please file a bug report with hardware info & dmesg trace.\n"); + } + + isp->num_channels = args.num_channels; + isp->current_ch = 0; + + return isp_ch_cache_camera_info(isp, isp->current_ch); /* I told you */ +} + +int apple_isp_detect_camera(struct apple_isp *isp) +{ + int err; + + /* RPM must be enabled prior to calling this */ + err = apple_isp_firmware_boot(isp); + if (err) { + dev_err(isp->dev, + "failed to boot firmware for initial sensor detection: %d\n", + err); + return -EPROBE_DEFER; + } + + err = isp_detect_camera(isp); + apple_isp_firmware_shutdown(isp); + + return err; +} + +static int isp_ch_load_setfile(struct apple_isp *isp, u32 ch) +{ + struct isp_format *fmt = isp_get_format(isp, ch); + const struct isp_setfile *setfile = &isp_setfiles[fmt->id]; + const struct firmware *fw; + u32 magic; + int err; + + err = request_firmware(&fw, setfile->path, isp->dev); + if (err) { + dev_err(isp->dev, "failed to request setfile '%s': %d\n", + setfile->path, err); + return err; + } + + if (fw->size < setfile->size) { + dev_err(isp->dev, "setfile too small (0x%lx/0x%zx)\n", fw->size, + setfile->size); + release_firmware(fw); + return -EINVAL; + } + + magic = be32_to_cpup((__be32 *)fw->data); + if (magic != setfile->magic) { + dev_err(isp->dev, "setfile '%s' corrupted?\n", setfile->path); + release_firmware(fw); + return -EINVAL; + } + + isp_iowrite(isp, isp->data_surf->iova, (void *)fw->data, setfile->size); + release_firmware(fw); + + return isp_cmd_ch_set_file_load(isp, ch, isp->data_surf->iova, + setfile->size); +} + +static int isp_ch_configure_capture(struct apple_isp *isp, u32 ch) +{ + struct isp_format *fmt = isp_get_format(isp, ch); + int err; + + /* The setfile isn't requisite but then we don't get calibration */ + err = isp_ch_load_setfile(isp, ch); + if (err) { + dev_err(isp->dev, "warning: calibration data not loaded: %d\n", + err); + } + + err = isp_cmd_ch_sbs_enable(isp, ch, 1); + if (err) + return err; + + err = isp_cmd_ch_buffer_recycle_mode_set( + isp, ch, CISP_BUFFER_RECYCLE_MODE_EMPTY_ONLY); + if (err) + return err; + + err = isp_cmd_ch_buffer_recycle_start(isp, ch); + if (err) + return err; + + err = isp_cmd_ch_camera_config_select(isp, ch, fmt->preset); + if (err) + return err; + + err = isp_cmd_ch_crop_set(isp, ch, fmt->x1, fmt->y1, fmt->x2, fmt->y2); + if (err) + return err; + + err = isp_cmd_ch_output_config_set(isp, ch, fmt->width, fmt->height, + CISP_COLORSPACE_REC709, + CISP_OUTPUT_FORMAT_NV12); + if (err) + return err; + + err = isp_cmd_ch_preview_stream_set(isp, ch, 1); + if (err) + return err; + + err = isp_cmd_ch_cnr_start(isp, ch); + if (err) + return err; + + err = isp_cmd_ch_mbnr_enable(isp, ch, 0, 1, 1); + if (err) + return err; + + err = isp_cmd_apple_ch_temporal_filter_start(isp, ch); + if (err) + return err; + + err = isp_cmd_apple_ch_motion_history_start(isp, ch); + if (err) + return err; + + err = isp_cmd_apple_ch_temporal_filter_enable(isp, ch); + if (err) + return err; + + err = isp_cmd_apple_ch_ae_fd_scene_metering_config_set(isp, ch); + if (err) + return err; + + err = isp_cmd_apple_ch_ae_metering_mode_set(isp, ch, 3); + if (err) + return err; + + err = isp_cmd_ch_ae_stability_set(isp, ch, 32); + if (err) + return err; + + err = isp_cmd_ch_ae_stability_to_stable_set(isp, ch, 20); + if (err) + return err; + + err = isp_cmd_ch_sif_pixel_format_set(isp, ch); + if (err) + return err; + + err = isp_cmd_ch_ae_frame_rate_max_set(isp, ch, ISP_FRAME_RATE_DEN); + if (err) + return err; + + err = isp_cmd_ch_ae_frame_rate_min_set(isp, ch, ISP_FRAME_RATE_DEN); + if (err) + return err; + + err = isp_cmd_ch_buffer_pool_config_set(isp, ch, CISP_POOL_TYPE_META); + if (err) + return err; + + err = isp_cmd_ch_buffer_pool_config_set(isp, ch, + CISP_POOL_TYPE_META_CAPTURE); + if (err) + return err; + + return 0; +} + +static int isp_configure_capture(struct apple_isp *isp) +{ + return isp_ch_configure_capture(isp, isp->current_ch); +} + +int apple_isp_start_camera(struct apple_isp *isp) +{ + int err; + + err = apple_isp_firmware_boot(isp); + if (err < 0) { + dev_err(isp->dev, "failed to boot firmware: %d\n", err); + return err; + } + + err = isp_configure_capture(isp); + if (err) { + dev_err(isp->dev, "failed to configure capture: %d\n", err); + apple_isp_firmware_shutdown(isp); + return err; + } + + return 0; +} + +void apple_isp_stop_camera(struct apple_isp *isp) +{ + apple_isp_firmware_shutdown(isp); +} + +int apple_isp_start_capture(struct apple_isp *isp) +{ + return isp_cmd_ch_start(isp, 0); // TODO channel mask +} + +void apple_isp_stop_capture(struct apple_isp *isp) +{ + isp_cmd_ch_stop(isp, 0); // TODO channel mask + isp_cmd_ch_buffer_return(isp, isp->current_ch); +} diff --git a/drivers/media/platform/apple/isp/isp-cam.h b/drivers/media/platform/apple/isp/isp-cam.h new file mode 100644 index 00000000000000..126e5806c8c416 --- /dev/null +++ b/drivers/media/platform/apple/isp/isp-cam.h @@ -0,0 +1,20 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* Copyright 2023 Eileen Yoon */ + +#ifndef __ISP_CAM_H__ +#define __ISP_CAM_H__ + +#include "isp-drv.h" + +#define ISP_FRAME_RATE_NUM 256 +#define ISP_FRAME_RATE_DEN 7680 + +int apple_isp_detect_camera(struct apple_isp *isp); + +int apple_isp_start_camera(struct apple_isp *isp); +void apple_isp_stop_camera(struct apple_isp *isp); + +int apple_isp_start_capture(struct apple_isp *isp); +void apple_isp_stop_capture(struct apple_isp *isp); + +#endif /* __ISP_CAM_H__ */ diff --git a/drivers/media/platform/apple/isp/isp-cmd.c b/drivers/media/platform/apple/isp/isp-cmd.c new file mode 100644 index 00000000000000..79ffb2b1c33881 --- /dev/null +++ b/drivers/media/platform/apple/isp/isp-cmd.c @@ -0,0 +1,544 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* Copyright 2023 Eileen Yoon */ + +#include "isp-cmd.h" +#include "isp-iommu.h" +#include "isp-ipc.h" + +#define CISP_OPCODE_SHIFT 32UL +#define CISP_OPCODE(x) (((u64)(x)) << CISP_OPCODE_SHIFT) +#define CISP_OPCODE_GET(x) (((u64)(x)) >> CISP_OPCODE_SHIFT) + +#define CISP_TIMEOUT msecs_to_jiffies(3000) +#define CISP_SEND_IN(x, a) (cisp_send((x), &(a), sizeof(a), 0)) +#define CISP_SEND_INOUT(x, a) (cisp_send((x), &(a), sizeof(a), sizeof(a))) +#define CISP_SEND_OUT(x, a) (cisp_send_read((x), (a), sizeof(*a), sizeof(*a))) + +static int cisp_send(struct apple_isp *isp, void *args, u32 insize, u32 outsize) +{ + struct isp_channel *chan = isp->chan_io; + struct isp_message *req = &chan->req; + int err; + + req->arg0 = isp->cmd_iova; + req->arg1 = insize; + req->arg2 = outsize; + + isp_iowrite(isp, isp->cmd_iova, args, insize); + err = ipc_chan_send(isp, chan, CISP_TIMEOUT); + if (err) { + u64 opcode; + memcpy(&opcode, args, sizeof(opcode)); + dev_err(isp->dev, + "%s: failed to send OPCODE 0x%04llx: [0x%llx, 0x%llx, 0x%llx]\n", + chan->name, CISP_OPCODE_GET(opcode), req->arg0, + req->arg1, req->arg2); + } + + return err; +} + +static int cisp_send_read(struct apple_isp *isp, void *args, u32 insize, + u32 outsize) +{ + /* TODO do I need to lock the iova space? */ + int err = cisp_send(isp, args, insize, outsize); + if (err) + return err; + isp_ioread(isp, isp->cmd_iova, args, outsize); + return 0; +} + +int isp_cmd_start(struct apple_isp *isp, u32 mode) +{ + struct cmd_start args = { + .opcode = CISP_OPCODE(CISP_CMD_START), + .mode = mode, + }; + return CISP_SEND_IN(isp, args); +} + +int isp_cmd_suspend(struct apple_isp *isp) +{ + struct cmd_suspend args = { + .opcode = CISP_OPCODE(CISP_CMD_SUSPEND), + }; + return CISP_SEND_IN(isp, args); +} + +int isp_cmd_print_enable(struct apple_isp *isp, u32 enable) +{ + struct cmd_print_enable args = { + .opcode = CISP_OPCODE(CISP_CMD_PRINT_ENABLE), + .enable = enable, + }; + return CISP_SEND_INOUT(isp, args); +} + +int isp_cmd_trace_enable(struct apple_isp *isp, u32 enable) +{ + struct cmd_trace_enable args = { + .opcode = CISP_OPCODE(CISP_CMD_TRACE_ENABLE), + .enable = enable, + }; + return CISP_SEND_INOUT(isp, args); +} + +int isp_cmd_config_get(struct apple_isp *isp, struct cmd_config_get *args) +{ + args->opcode = CISP_OPCODE(CISP_CMD_CONFIG_GET); + return CISP_SEND_OUT(isp, args); +} + +int isp_cmd_set_isp_pmu_base(struct apple_isp *isp, u64 pmu_base) +{ + struct cmd_set_isp_pmu_base args = { + .opcode = CISP_OPCODE(CISP_CMD_SET_ISP_PMU_BASE), + .pmu_base = pmu_base, + }; + return CISP_SEND_IN(isp, args); +} + +int isp_cmd_set_dsid_clr_req_base2(struct apple_isp *isp, u64 dsid_clr_base0, + u64 dsid_clr_base1, u64 dsid_clr_base2, + u64 dsid_clr_base3, u32 dsid_clr_range0, + u32 dsid_clr_range1, u32 dsid_clr_range2, + u32 dsid_clr_range3) +{ + struct cmd_set_dsid_clr_req_base2 args = { + .opcode = CISP_OPCODE(CISP_CMD_SET_DSID_CLR_REG_BASE2), + .dsid_clr_base0 = dsid_clr_base0, + .dsid_clr_base1 = dsid_clr_base1, + .dsid_clr_base2 = dsid_clr_base2, + .dsid_clr_base3 = dsid_clr_base3, + .dsid_clr_range0 = dsid_clr_range0, + .dsid_clr_range1 = dsid_clr_range1, + .dsid_clr_range2 = dsid_clr_range2, + .dsid_clr_range3 = dsid_clr_range3, + }; + return CISP_SEND_IN(isp, args); +} + +int isp_cmd_pmp_ctrl_set(struct apple_isp *isp, u64 clock_scratch, + u64 clock_base, u8 clock_bit, u8 clock_size, + u64 bandwidth_scratch, u64 bandwidth_base, + u8 bandwidth_bit, u8 bandwidth_size) +{ + struct cmd_pmp_ctrl_set args = { + .opcode = CISP_OPCODE(CISP_CMD_PMP_CTRL_SET), + .clock_scratch = clock_scratch, + .clock_base = clock_base, + .clock_bit = clock_bit, + .clock_size = clock_size, + .clock_pad = 0, + .bandwidth_scratch = bandwidth_scratch, + .bandwidth_base = bandwidth_base, + .bandwidth_bit = bandwidth_bit, + .bandwidth_size = bandwidth_size, + .bandwidth_pad = 0, + }; + return CISP_SEND_IN(isp, args); +} + +int isp_cmd_fid_enter(struct apple_isp *isp) +{ + struct cmd_fid_enter args = { + .opcode = CISP_OPCODE(CISP_CMD_FID_ENTER), + }; + return CISP_SEND_IN(isp, args); +} + +int isp_cmd_fid_exit(struct apple_isp *isp) +{ + struct cmd_fid_exit args = { + .opcode = CISP_OPCODE(CISP_CMD_FID_EXIT), + }; + return CISP_SEND_IN(isp, args); +} + +int isp_cmd_ch_start(struct apple_isp *isp, u32 chan) +{ + struct cmd_ch_start args = { + .opcode = CISP_OPCODE(CISP_CMD_CH_START), + .chan = chan, + }; + return CISP_SEND_IN(isp, args); +} + +int isp_cmd_ch_stop(struct apple_isp *isp, u32 chan) +{ + struct cmd_ch_stop args = { + .opcode = CISP_OPCODE(CISP_CMD_CH_STOP), + .chan = chan, + }; + return CISP_SEND_IN(isp, args); +} + +int isp_cmd_ch_info_get(struct apple_isp *isp, u32 chan, + struct cmd_ch_info *args) +{ + args->opcode = CISP_OPCODE(CISP_CMD_CH_INFO_GET); + args->chan = chan; + return CISP_SEND_OUT(isp, args); +} + +int isp_cmd_ch_camera_config_get(struct apple_isp *isp, u32 chan, u32 preset, + struct cmd_ch_camera_config *args) +{ + args->opcode = CISP_OPCODE(CISP_CMD_CH_CAMERA_CONFIG_GET); + args->preset = preset; + args->chan = chan; + return CISP_SEND_OUT(isp, args); +} + +int isp_cmd_ch_camera_config_current_get(struct apple_isp *isp, u32 chan, + struct cmd_ch_camera_config *args) +{ + args->opcode = CISP_OPCODE(CISP_CMD_CH_CAMERA_CONFIG_CURRENT_GET); + args->chan = chan; + return CISP_SEND_OUT(isp, args); +} + +int isp_cmd_ch_camera_config_select(struct apple_isp *isp, u32 chan, u32 preset) +{ + struct cmd_ch_camera_config_select args = { + .opcode = CISP_OPCODE(CISP_CMD_CH_CAMERA_CONFIG_SELECT), + .chan = chan, + .preset = preset, + }; + return CISP_SEND_IN(isp, args); +} + +int isp_cmd_ch_buffer_return(struct apple_isp *isp, u32 chan) +{ + struct cmd_ch_buffer_return args = { + .opcode = CISP_OPCODE(CISP_CMD_CH_BUFFER_RETURN), + .chan = chan, + }; + return CISP_SEND_IN(isp, args); +} + +int isp_cmd_ch_set_file_load(struct apple_isp *isp, u32 chan, u32 addr, + u32 size) +{ + struct cmd_ch_set_file_load args = { + .opcode = CISP_OPCODE(CISP_CMD_CH_SET_FILE_LOAD), + .chan = chan, + .addr = addr, + .size = size, + }; + return CISP_SEND_IN(isp, args); +} + +int isp_cmd_ch_sbs_enable(struct apple_isp *isp, u32 chan, u32 enable) +{ + struct cmd_ch_sbs_enable args = { + .opcode = CISP_OPCODE(CISP_CMD_CH_SBS_ENABLE), + .chan = chan, + .enable = enable, + }; + return CISP_SEND_IN(isp, args); +} + +int isp_cmd_ch_crop_set(struct apple_isp *isp, u32 chan, u32 x1, u32 y1, u32 x2, + u32 y2) +{ + struct cmd_ch_crop_set args = { + .opcode = CISP_OPCODE(CISP_CMD_CH_CROP_SET), + .chan = chan, + .x1 = x1, + .y1 = y1, + .x2 = x2, + .y2 = y2, + }; + return CISP_SEND_IN(isp, args); +} + +int isp_cmd_ch_output_config_set(struct apple_isp *isp, u32 chan, u32 width, + u32 height, u32 colorspace, u32 format) +{ + struct cmd_ch_output_config_set args = { + .opcode = CISP_OPCODE(CISP_CMD_CH_OUTPUT_CONFIG_SET), + .chan = chan, + .width = width, + .height = height, + .colorspace = colorspace, + .format = format, + .unk_w0 = width, + .unk_w1 = width, + .unk_24 = 0, + .padding_rows = 0, + .unk_h0 = height, + .compress = 0, + .unk_w2 = width, + }; + return CISP_SEND_IN(isp, args); +} + +int isp_cmd_ch_preview_stream_set(struct apple_isp *isp, u32 chan, u32 stream) +{ + struct cmd_ch_preview_stream_set args = { + .opcode = CISP_OPCODE(CISP_CMD_CH_PREVIEW_STREAM_SET), + .chan = chan, + .stream = stream, + }; + return CISP_SEND_IN(isp, args); +} + +int isp_cmd_ch_als_disable(struct apple_isp *isp, u32 chan) +{ + struct cmd_ch_als_disable args = { + .opcode = CISP_OPCODE(CISP_CMD_CH_ALS_DISABLE), + .chan = chan, + }; + return CISP_SEND_IN(isp, args); +} + +int isp_cmd_ch_cnr_start(struct apple_isp *isp, u32 chan) +{ + struct cmd_ch_cnr_start args = { + .opcode = CISP_OPCODE(CISP_CMD_CH_CNR_START), + .chan = chan, + }; + return CISP_SEND_IN(isp, args); +} + +int isp_cmd_ch_mbnr_enable(struct apple_isp *isp, u32 chan, u32 use_case, + u32 mode, u32 enable_chroma) +{ + struct cmd_ch_mbnr_enable args = { + .opcode = CISP_OPCODE(CISP_CMD_CH_MBNR_ENABLE), + .chan = chan, + .use_case = use_case, + .mode = mode, + .enable_chroma = enable_chroma, + }; + return CISP_SEND_IN(isp, args); +} + +int isp_cmd_ch_sif_pixel_format_set(struct apple_isp *isp, u32 chan) +{ + struct cmd_ch_sif_pixel_format_set args = { + .opcode = CISP_OPCODE(CISP_CMD_CH_SIF_PIXEL_FORMAT_SET), + .chan = chan, + .format = 3, + .type = 1, + .compress = 0, + .unk_10 = 0, + }; + return CISP_SEND_IN(isp, args); +} + +int isp_cmd_ch_buffer_recycle_mode_set(struct apple_isp *isp, u32 chan, + u32 mode) +{ + struct cmd_ch_buffer_recycle_mode_set args = { + .opcode = CISP_OPCODE(CISP_CMD_CH_BUFFER_RECYCLE_MODE_SET), + .chan = chan, + .mode = mode, + }; + return CISP_SEND_IN(isp, args); +} + +int isp_cmd_ch_buffer_recycle_start(struct apple_isp *isp, u32 chan) +{ + struct cmd_ch_buffer_recycle_start args = { + .opcode = CISP_OPCODE(CISP_CMD_CH_BUFFER_RECYCLE_START), + .chan = chan, + }; + return CISP_SEND_IN(isp, args); +} + +int isp_cmd_ch_buffer_pool_config_set(struct apple_isp *isp, u32 chan, u16 type) +{ + struct cmd_ch_buffer_pool_config_set args = { + .opcode = CISP_OPCODE(CISP_CMD_CH_BUFFER_POOL_CONFIG_SET), + .chan = chan, + .type = type, + .count = 16, + .meta_size0 = ISP_META_SIZE, + .meta_size1 = ISP_META_SIZE, + .data_blocks = 1, + .compress = 0, + }; + memset(args.zero, 0, sizeof(u32) * 0x1f); + return CISP_SEND_INOUT(isp, args); +} + +int isp_cmd_ch_buffer_pool_return(struct apple_isp *isp, u32 chan) +{ + struct cmd_ch_buffer_pool_return args = { + .opcode = CISP_OPCODE(CISP_CMD_CH_BUFFER_POOL_RETURN), + .chan = chan, + }; + return CISP_SEND_IN(isp, args); +} + +int isp_cmd_apple_ch_temporal_filter_start(struct apple_isp *isp, u32 chan) +{ + struct cmd_apple_ch_temporal_filter_start args = { + .opcode = CISP_OPCODE(CISP_CMD_APPLE_CH_TEMPORAL_FILTER_START), + .chan = chan, + .unk_c = 1, + .unk_10 = 0, + }; + return CISP_SEND_IN(isp, args); +} + +int isp_cmd_apple_ch_temporal_filter_stop(struct apple_isp *isp, u32 chan) +{ + struct cmd_apple_ch_temporal_filter_stop args = { + .opcode = CISP_OPCODE(CISP_CMD_APPLE_CH_TEMPORAL_FILTER_STOP), + .chan = chan, + }; + return CISP_SEND_IN(isp, args); +} + +int isp_cmd_apple_ch_motion_history_start(struct apple_isp *isp, u32 chan) +{ + struct cmd_apple_ch_motion_history_start args = { + .opcode = CISP_OPCODE(CISP_CMD_APPLE_CH_MOTION_HISTORY_START), + .chan = chan, + }; + return CISP_SEND_IN(isp, args); +} + +int isp_cmd_apple_ch_motion_history_stop(struct apple_isp *isp, u32 chan) +{ + struct cmd_apple_ch_motion_history_stop args = { + .opcode = CISP_OPCODE(CISP_CMD_APPLE_CH_MOTION_HISTORY_STOP), + .chan = chan, + }; + return CISP_SEND_IN(isp, args); +} + +int isp_cmd_apple_ch_temporal_filter_enable(struct apple_isp *isp, u32 chan) +{ + struct cmd_apple_ch_temporal_filter_enable args = { + .opcode = CISP_OPCODE(CISP_CMD_APPLE_CH_TEMPORAL_FILTER_ENABLE), + .chan = chan, + }; + return CISP_SEND_IN(isp, args); +} + +int isp_cmd_apple_ch_temporal_filter_disable(struct apple_isp *isp, u32 chan) +{ + struct cmd_apple_ch_temporal_filter_disable args = { + .opcode = + CISP_OPCODE(CISP_CMD_APPLE_CH_TEMPORAL_FILTER_DISABLE), + .chan = chan, + }; + return CISP_SEND_IN(isp, args); +} + +int isp_cmd_ch_ae_stability_set(struct apple_isp *isp, u32 chan, u32 stability) +{ + struct cmd_ch_ae_stability_set args = { + .opcode = CISP_OPCODE(CISP_CMD_CH_AE_STABILITY_SET), + .chan = chan, + .stability = stability, + }; + return CISP_SEND_IN(isp, args); +} + +int isp_cmd_ch_ae_stability_to_stable_set(struct apple_isp *isp, u32 chan, + u32 stability) +{ + struct cmd_ch_ae_stability_to_stable_set args = { + .opcode = CISP_OPCODE(CISP_CMD_CH_AE_STABILITY_TO_STABLE_SET), + .chan = chan, + .stability = stability, + }; + return CISP_SEND_IN(isp, args); +} + +int isp_cmd_ch_ae_frame_rate_max_get(struct apple_isp *isp, u32 chan, + struct cmd_ch_ae_frame_rate_max_get *args) +{ + args->opcode = CISP_OPCODE(CISP_CMD_CH_AE_FRAME_RATE_MAX_GET); + args->chan = chan; + return CISP_SEND_OUT(isp, args); +} + +int isp_cmd_ch_ae_frame_rate_max_set(struct apple_isp *isp, u32 chan, + u32 framerate) +{ + struct cmd_ch_ae_frame_rate_max_set args = { + .opcode = CISP_OPCODE(CISP_CMD_CH_AE_FRAME_RATE_MAX_SET), + .chan = chan, + .framerate = framerate, + }; + return CISP_SEND_IN(isp, args); +} + +int isp_cmd_ch_ae_frame_rate_min_set(struct apple_isp *isp, u32 chan, + u32 framerate) +{ + struct cmd_ch_ae_frame_rate_min_set args = { + .opcode = CISP_OPCODE(CISP_CMD_CH_AE_FRAME_RATE_MIN_SET), + .chan = chan, + .framerate = framerate, + }; + return CISP_SEND_IN(isp, args); +} + +int isp_cmd_apple_ch_ae_fd_scene_metering_config_set(struct apple_isp *isp, + u32 chan) +{ + struct cmd_apple_ch_ae_fd_scene_metering_config_set args = { + .opcode = CISP_OPCODE( + CISP_CMD_APPLE_CH_AE_FD_SCENE_METERING_CONFIG_SET), + .chan = chan, + .unk_c = 0xb8, + .unk_10 = 0x2000200, + .unk_14 = 0x280800, + .unk_18 = 0xe10028, + .unk_1c = 0xa0399, + .unk_20 = 0x3cc02cc, + }; + return CISP_SEND_INOUT(isp, args); +} + +int isp_cmd_apple_ch_ae_metering_mode_set(struct apple_isp *isp, u32 chan, + u32 mode) +{ + struct cmd_apple_ch_ae_metering_mode_set args = { + .opcode = CISP_OPCODE(CISP_CMD_APPLE_CH_AE_METERING_MODE_SET), + .chan = chan, + .mode = mode, + }; + return CISP_SEND_IN(isp, args); +} + +int isp_cmd_apple_ch_ae_flicker_freq_update_current_set(struct apple_isp *isp, + u32 chan, u32 freq) +{ + struct cmd_apple_ch_ae_flicker_freq_update_current_set args = { + .opcode = CISP_OPCODE( + CISP_CMD_APPLE_CH_AE_FLICKER_FREQ_UPDATE_CURRENT_SET), + .chan = chan, + .freq = freq, + }; + return CISP_SEND_IN(isp, args); +} + +int isp_cmd_ch_semantic_video_enable(struct apple_isp *isp, u32 chan, + u32 enable) +{ + struct cmd_ch_semantic_video_enable args = { + .opcode = CISP_OPCODE(CISP_CMD_CH_SEMANTIC_VIDEO_ENABLE), + .chan = chan, + .enable = enable, + }; + return CISP_SEND_IN(isp, args); +} + +int isp_cmd_ch_semantic_awb_enable(struct apple_isp *isp, u32 chan, u32 enable) +{ + struct cmd_ch_semantic_awb_enable args = { + .opcode = CISP_OPCODE(CISP_CMD_CH_SEMANTIC_AWB_ENABLE), + .chan = chan, + .enable = enable, + }; + return CISP_SEND_IN(isp, args); +} diff --git a/drivers/media/platform/apple/isp/isp-cmd.h b/drivers/media/platform/apple/isp/isp-cmd.h new file mode 100644 index 00000000000000..dde6aad506c23e --- /dev/null +++ b/drivers/media/platform/apple/isp/isp-cmd.h @@ -0,0 +1,532 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* Copyright 2023 Eileen Yoon */ + +#ifndef __ISP_CMD_H__ +#define __ISP_CMD_H__ + +#include "isp-drv.h" + +#define CISP_CMD_START 0x0000 +#define CISP_CMD_STOP 0x0001 +#define CISP_CMD_CONFIG_GET 0x0003 +#define CISP_CMD_PRINT_ENABLE 0x0004 +#define CISP_CMD_BUILDINFO 0x0006 +#define CISP_CMD_GET_BES_PARAM 0x000f +#define CISP_CMD_SET_ISP_PMU_BASE 0x0011 +#define CISP_CMD_PMP_CTRL_SET 0x001c +#define CISP_CMD_TRACE_ENABLE 0x001d +#define CISP_CMD_SUSPEND 0x0021 +#define CISP_CMD_FID_ENTER 0x0022 +#define CISP_CMD_FID_EXIT 0x0023 +#define CISP_CMD_FLICKER_SENSOR_SET 0x0024 +#define CISP_CMD_CH_START 0x0100 +#define CISP_CMD_CH_STOP 0x0101 +#define CISP_CMD_CH_BUFFER_RETURN 0x0104 +#define CISP_CMD_CH_CAMERA_CONFIG_CURRENT_GET 0x0105 +#define CISP_CMD_CH_CAMERA_CONFIG_GET 0x0106 +#define CISP_CMD_CH_CAMERA_CONFIG_SELECT 0x0107 +#define CISP_CMD_CH_INFO_GET 0x010d +#define CISP_CMD_CH_BUFFER_RECYCLE_MODE_SET 0x010e +#define CISP_CMD_CH_BUFFER_RECYCLE_START 0x010f +#define CISP_CMD_CH_BUFFER_RECYCLE_STOP 0x0110 +#define CISP_CMD_CH_SET_FILE_LOAD 0x0111 +#define CISP_CMD_CH_SIF_PIXEL_FORMAT_SET 0x0115 +#define CISP_CMD_CH_BUFFER_POOL_CONFIG_GET 0x0116 +#define CISP_CMD_CH_BUFFER_POOL_CONFIG_SET 0x0117 +#define CISP_CMD_CH_CAMERA_MIPI_FREQUENCY_GET 0x011a +#define CISP_CMD_CH_CAMERA_PIX_FREQUENCY_GET 0x011f +#define CISP_CMD_CH_LOCAL_RAW_BUFFER_ENABLE 0x0125 +#define CISP_CMD_CH_CAMERA_MIPI_FREQUENCY_TOTAL_GET 0x0133 +#define CISP_CMD_CH_SBS_ENABLE 0x013b +#define CISP_CMD_CH_LSC_POLYNOMIAL_COEFF_GET 0x0142 +#define CISP_CMD_CH_BUFFER_POOL_RETURN 0x015b +#define CISP_CMD_CH_CAMERA_AGILE_FREQ_ARRAY_CURRENT_GET 0x015e +#define CISP_CMD_CH_AE_START 0x0200 +#define CISP_CMD_CH_AE_STOP 0x0201 +#define CISP_CMD_CH_AE_FRAME_RATE_MAX_GET 0x0207 +#define CISP_CMD_CH_AE_FRAME_RATE_MAX_SET 0x0208 +#define CISP_CMD_CH_AE_FRAME_RATE_MIN_GET 0x0209 +#define CISP_CMD_CH_AE_FRAME_RATE_MIN_SET 0x020a +#define CISP_CMD_CH_AE_STABILITY_SET 0x021a +#define CISP_CMD_CH_AE_STABILITY_TO_STABLE_SET 0x0229 +#define CISP_CMD_CH_SENSOR_NVM_GET 0x0501 +#define CISP_CMD_CH_SENSOR_PERMODULE_LSC_INFO_GET 0x0507 +#define CISP_CMD_CH_SENSOR_PERMODULE_LSC_GRID_GET 0x0511 +#define CISP_CMD_CH_FOCUS_LIMITS_GET 0x0701 +#define CISP_CMD_CH_CROP_SET 0x0801 +#define CISP_CMD_CH_ALS_ENABLE 0x0a1c +#define CISP_CMD_CH_ALS_DISABLE 0x0a1d +#define CISP_CMD_CH_CNR_START 0x0a2f +#define CISP_CMD_CH_MBNR_ENABLE 0x0a3a +#define CISP_CMD_CH_OUTPUT_CONFIG_SET 0x0b01 +#define CISP_CMD_CH_PREVIEW_STREAM_SET 0x0b0d +#define CISP_CMD_CH_SEMANTIC_VIDEO_ENABLE 0x0b17 +#define CISP_CMD_CH_SEMANTIC_AWB_ENABLE 0x0b18 +#define CISP_CMD_CH_FACE_DETECTION_START 0x0d00 +#define CISP_CMD_CH_FACE_DETECTION_CONFIG_GET 0x0d02 +#define CISP_CMD_CH_FACE_DETECTION_CONFIG_SET 0x0d03 +#define CISP_CMD_CH_FACE_DETECTION_ENABLE 0x0d05 +#define CISP_CMD_CH_FID_START 0x3000 +#define CISP_CMD_CH_FID_STOP 0x3001 +#define CISP_CMD_IPC_ENDPOINT_SET2 0x300c +#define CISP_CMD_IPC_ENDPOINT_UNSET2 0x300d +#define CISP_CMD_SET_DSID_CLR_REG_BASE2 0x3204 +#define CISP_CMD_APPLE_CH_AE_METERING_MODE_SET 0x8206 +#define CISP_CMD_APPLE_CH_AE_FD_SCENE_METERING_CONFIG_SET 0x820e +#define CISP_CMD_APPLE_CH_AE_FLICKER_FREQ_UPDATE_CURRENT_SET 0x8212 +#define CISP_CMD_APPLE_CH_TEMPORAL_FILTER_START 0xc100 +#define CISP_CMD_APPLE_CH_TEMPORAL_FILTER_STOP 0xc101 +#define CISP_CMD_APPLE_CH_MOTION_HISTORY_START 0xc102 +#define CISP_CMD_APPLE_CH_MOTION_HISTORY_STOP 0xc103 +#define CISP_CMD_APPLE_CH_TEMPORAL_FILTER_ENABLE 0xc113 +#define CISP_CMD_APPLE_CH_TEMPORAL_FILTER_DISABLE 0xc114 + +#define CISP_POOL_TYPE_META 0x0 +#define CISP_POOL_TYPE_RENDERED 0x1 +#define CISP_POOL_TYPE_FD 0x2 +#define CISP_POOL_TYPE_RAW 0x3 +#define CISP_POOL_TYPE_STAT 0x4 +#define CISP_POOL_TYPE_META_CAPTURE 0x8 + +#define CISP_COLORSPACE_REC709 0x1 +#define CISP_OUTPUT_FORMAT_NV12 0x0 +#define CISP_BUFFER_RECYCLE_MODE_EMPTY_ONLY 0x1 + +struct cmd_start { + u64 opcode; + u32 mode; +} __packed; +static_assert(sizeof(struct cmd_start) == 0xc); + +struct cmd_suspend { + u64 opcode; +} __packed; +static_assert(sizeof(struct cmd_suspend) == 0x8); + +struct cmd_print_enable { + u64 opcode; + u32 enable; +} __packed; +static_assert(sizeof(struct cmd_print_enable) == 0xc); + +struct cmd_trace_enable { + u64 opcode; + u32 enable; +} __packed; +static_assert(sizeof(struct cmd_trace_enable) == 0xc); + +struct cmd_config_get { + u64 opcode; + u32 timestamp_freq; + u32 num_channels; + u32 unk_10; + u32 unk_14; + u32 unk_18; +} __packed; +static_assert(sizeof(struct cmd_config_get) == 0x1c); + +struct cmd_set_isp_pmu_base { + u64 opcode; + u64 pmu_base; +} __packed; +static_assert(sizeof(struct cmd_set_isp_pmu_base) == 0x10); + +struct cmd_set_dsid_clr_req_base2 { + u64 opcode; + u64 dsid_clr_base0; + u64 dsid_clr_base1; + u64 dsid_clr_base2; + u64 dsid_clr_base3; + u32 dsid_clr_range0; + u32 dsid_clr_range1; + u32 dsid_clr_range2; + u32 dsid_clr_range3; +} __packed; +static_assert(sizeof(struct cmd_set_dsid_clr_req_base2) == 0x38); + +struct cmd_pmp_ctrl_set { + u64 opcode; + u64 clock_scratch; + u64 clock_base; + u8 clock_bit; + u8 clock_size; + u16 clock_pad; + u64 bandwidth_scratch; + u64 bandwidth_base; + u8 bandwidth_bit; + u8 bandwidth_size; + u16 bandwidth_pad; +} __packed; +static_assert(sizeof(struct cmd_pmp_ctrl_set) == 0x30); + +struct cmd_fid_enter { + u64 opcode; +} __packed; +static_assert(sizeof(struct cmd_fid_enter) == 0x8); + +struct cmd_fid_exit { + u64 opcode; +} __packed; +static_assert(sizeof(struct cmd_fid_exit) == 0x8); + +int isp_cmd_start(struct apple_isp *isp, u32 mode); +int isp_cmd_suspend(struct apple_isp *isp); +int isp_cmd_print_enable(struct apple_isp *isp, u32 enable); +int isp_cmd_trace_enable(struct apple_isp *isp, u32 enable); +int isp_cmd_config_get(struct apple_isp *isp, struct cmd_config_get *args); +int isp_cmd_set_isp_pmu_base(struct apple_isp *isp, u64 pmu_base); +int isp_cmd_set_dsid_clr_req_base2(struct apple_isp *isp, u64 dsid_clr_base0, + u64 dsid_clr_base1, u64 dsid_clr_base2, + u64 dsid_clr_base3, u32 dsid_clr_range0, + u32 dsid_clr_range1, u32 dsid_clr_range2, + u32 dsid_clr_range3); +int isp_cmd_pmp_ctrl_set(struct apple_isp *isp, u64 clock_scratch, + u64 clock_base, u8 clock_bit, u8 clock_size, + u64 bandwidth_scratch, u64 bandwidth_base, + u8 bandwidth_bit, u8 bandwidth_size); +int isp_cmd_fid_enter(struct apple_isp *isp); +int isp_cmd_fid_exit(struct apple_isp *isp); + +struct cmd_ch_start { + u64 opcode; + u32 chan; +} __packed; +static_assert(sizeof(struct cmd_ch_start) == 0xc); + +struct cmd_ch_stop { + u64 opcode; + u32 chan; +} __packed; +static_assert(sizeof(struct cmd_ch_stop) == 0xc); + +struct cmd_ch_info { + u64 opcode; + u32 chan; + u32 unk_c; + u32 unk_10[4]; + u32 version; + u32 unk_24[3]; + u32 unk_30[12]; + u32 num_presets; + u32 unk_64[7]; + u32 unk_80[6]; + u32 unk_98_freq; + u16 pad_9c; + char module_sn[20]; + u16 pad_b0; + u32 unk_b4[25]; +} __packed; +static_assert(sizeof(struct cmd_ch_info) == 0x118); + +struct cmd_ch_camera_config { + u64 opcode; + u32 chan; + u32 preset; + u16 in_width; + u16 in_height; + u16 out_width; + u16 out_height; + u32 unk[49]; +} __packed; +static_assert(sizeof(struct cmd_ch_camera_config) == 0xdc); + +struct cmd_ch_camera_config_select { + u64 opcode; + u32 chan; + u32 preset; +} __packed; +static_assert(sizeof(struct cmd_ch_camera_config_select) == 0x10); + +struct cmd_ch_set_file_load { + u64 opcode; + u32 chan; + u32 addr; + u32 size; +} __packed; +static_assert(sizeof(struct cmd_ch_set_file_load) == 0x14); + +struct cmd_ch_buffer_return { + u64 opcode; + u32 chan; +} __packed; +static_assert(sizeof(struct cmd_ch_buffer_return) == 0xc); + +struct cmd_ch_sbs_enable { + u64 opcode; + u32 chan; + u32 enable; +} __packed; +static_assert(sizeof(struct cmd_ch_sbs_enable) == 0x10); + +struct cmd_ch_crop_set { + u64 opcode; + u32 chan; + u32 x1; + u32 y1; + u32 x2; + u32 y2; +} __packed; +static_assert(sizeof(struct cmd_ch_crop_set) == 0x1c); + +struct cmd_ch_output_config_set { + u64 opcode; + u32 chan; + u32 width; + u32 height; + u32 colorspace; + u32 format; + u32 unk_w0; + u32 unk_w1; + u32 unk_24; + u32 padding_rows; + u32 unk_h0; + u32 compress; + u32 unk_w2; +} __packed; +static_assert(sizeof(struct cmd_ch_output_config_set) == 0x38); + +struct cmd_ch_preview_stream_set { + u64 opcode; + u32 chan; + u32 stream; +} __packed; +static_assert(sizeof(struct cmd_ch_preview_stream_set) == 0x10); + +struct cmd_ch_als_disable { + u64 opcode; + u32 chan; +} __packed; +static_assert(sizeof(struct cmd_ch_als_disable) == 0xc); + +struct cmd_ch_cnr_start { + u64 opcode; + u32 chan; +} __packed; +static_assert(sizeof(struct cmd_ch_cnr_start) == 0xc); + +struct cmd_ch_mbnr_enable { + u64 opcode; + u32 chan; + u32 use_case; + u32 mode; + u32 enable_chroma; +} __packed; +static_assert(sizeof(struct cmd_ch_mbnr_enable) == 0x18); + +struct cmd_ch_sif_pixel_format_set { + u64 opcode; + u32 chan; + u8 format; + u8 type; + u16 compress; + u32 unk_10; +} __packed; +static_assert(sizeof(struct cmd_ch_sif_pixel_format_set) == 0x14); + +int isp_cmd_ch_start(struct apple_isp *isp, u32 chan); +int isp_cmd_ch_stop(struct apple_isp *isp, u32 chan); +int isp_cmd_ch_info_get(struct apple_isp *isp, u32 chan, + struct cmd_ch_info *args); +int isp_cmd_ch_camera_config_get(struct apple_isp *isp, u32 chan, u32 preset, + struct cmd_ch_camera_config *args); +int isp_cmd_ch_camera_config_current_get(struct apple_isp *isp, u32 chan, + struct cmd_ch_camera_config *args); +int isp_cmd_ch_camera_config_select(struct apple_isp *isp, u32 chan, + u32 preset); +int isp_cmd_ch_set_file_load(struct apple_isp *isp, u32 chan, u32 addr, + u32 size); +int isp_cmd_ch_buffer_return(struct apple_isp *isp, u32 chan); +int isp_cmd_ch_sbs_enable(struct apple_isp *isp, u32 chan, u32 enable); +int isp_cmd_ch_crop_set(struct apple_isp *isp, u32 chan, u32 x1, u32 y1, u32 x2, + u32 y2); +int isp_cmd_ch_output_config_set(struct apple_isp *isp, u32 chan, u32 width, + u32 height, u32 colorspace, u32 format); +int isp_cmd_ch_preview_stream_set(struct apple_isp *isp, u32 chan, u32 stream); +int isp_cmd_ch_als_disable(struct apple_isp *isp, u32 chan); +int isp_cmd_ch_cnr_start(struct apple_isp *isp, u32 chan); +int isp_cmd_ch_mbnr_enable(struct apple_isp *isp, u32 chan, u32 use_case, + u32 mode, u32 enable_chroma); +int isp_cmd_ch_sif_pixel_format_set(struct apple_isp *isp, u32 chan); + +struct cmd_ch_buffer_recycle_mode_set { + u64 opcode; + u32 chan; + u32 mode; +} __packed; +static_assert(sizeof(struct cmd_ch_buffer_recycle_mode_set) == 0x10); + +struct cmd_ch_buffer_recycle_start { + u64 opcode; + u32 chan; +} __packed; +static_assert(sizeof(struct cmd_ch_buffer_recycle_start) == 0xc); + +struct cmd_ch_buffer_pool_config_set { + u64 opcode; + u32 chan; + u16 type; + u16 count; + u32 meta_size0; + u32 meta_size1; + u32 zero[0x1f]; + u32 data_blocks; + u32 compress; +} __packed; +static_assert(sizeof(struct cmd_ch_buffer_pool_config_set) == 0x9c); + +struct cmd_ch_buffer_pool_return { + u64 opcode; + u32 chan; +} __packed; +static_assert(sizeof(struct cmd_ch_buffer_pool_return) == 0xc); + +int isp_cmd_ch_buffer_recycle_mode_set(struct apple_isp *isp, u32 chan, + u32 mode); +int isp_cmd_ch_buffer_recycle_start(struct apple_isp *isp, u32 chan); +int isp_cmd_ch_buffer_pool_config_set(struct apple_isp *isp, u32 chan, + u16 type); +int isp_cmd_ch_buffer_pool_return(struct apple_isp *isp, u32 chan); + +struct cmd_apple_ch_temporal_filter_start { + u64 opcode; + u32 chan; + u32 unk_c; + u32 unk_10; +} __packed; +static_assert(sizeof(struct cmd_apple_ch_temporal_filter_start) == 0x14); + +struct cmd_apple_ch_temporal_filter_stop { + u64 opcode; + u32 chan; +} __packed; +static_assert(sizeof(struct cmd_apple_ch_temporal_filter_stop) == 0xc); + +struct cmd_apple_ch_motion_history_start { + u64 opcode; + u32 chan; +} __packed; +static_assert(sizeof(struct cmd_apple_ch_motion_history_start) == 0xc); + +struct cmd_apple_ch_motion_history_stop { + u64 opcode; + u32 chan; +} __packed; +static_assert(sizeof(struct cmd_apple_ch_motion_history_stop) == 0xc); + +struct cmd_apple_ch_temporal_filter_enable { + u64 opcode; + u32 chan; +} __packed; +static_assert(sizeof(struct cmd_apple_ch_temporal_filter_enable) == 0xc); + +struct cmd_apple_ch_temporal_filter_disable { + u64 opcode; + u32 chan; +} __packed; +static_assert(sizeof(struct cmd_apple_ch_temporal_filter_disable) == 0xc); + +int isp_cmd_apple_ch_temporal_filter_start(struct apple_isp *isp, u32 chan); +int isp_cmd_apple_ch_temporal_filter_stop(struct apple_isp *isp, u32 chan); +int isp_cmd_apple_ch_motion_history_start(struct apple_isp *isp, u32 chan); +int isp_cmd_apple_ch_motion_history_stop(struct apple_isp *isp, u32 chan); +int isp_cmd_apple_ch_temporal_filter_enable(struct apple_isp *isp, u32 chan); +int isp_cmd_apple_ch_temporal_filter_disable(struct apple_isp *isp, u32 chan); + +struct cmd_ch_ae_stability_set { + u64 opcode; + u32 chan; + u32 stability; +} __packed; +static_assert(sizeof(struct cmd_ch_ae_stability_set) == 0x10); + +struct cmd_ch_ae_stability_to_stable_set { + u64 opcode; + u32 chan; + u32 stability; +} __packed; +static_assert(sizeof(struct cmd_ch_ae_stability_to_stable_set) == 0x10); + +struct cmd_ch_ae_frame_rate_max_get { + u64 opcode; + u32 chan; + u32 framerate; +} __packed; +static_assert(sizeof(struct cmd_ch_ae_frame_rate_max_get) == 0x10); + +struct cmd_ch_ae_frame_rate_max_set { + u64 opcode; + u32 chan; + u32 framerate; +} __packed; +static_assert(sizeof(struct cmd_ch_ae_frame_rate_max_set) == 0x10); + +struct cmd_ch_ae_frame_rate_min_set { + u64 opcode; + u32 chan; + u32 framerate; +} __packed; +static_assert(sizeof(struct cmd_ch_ae_frame_rate_min_set) == 0x10); + +struct cmd_apple_ch_ae_fd_scene_metering_config_set { + u64 opcode; + u32 chan; + u32 unk_c; + u32 unk_10; + u32 unk_14; + u32 unk_18; + u32 unk_1c; + u32 unk_20; +} __packed; +static_assert(sizeof(struct cmd_apple_ch_ae_fd_scene_metering_config_set) == + 0x24); + +struct cmd_apple_ch_ae_metering_mode_set { + u64 opcode; + u32 chan; + u32 mode; +} __packed; +static_assert(sizeof(struct cmd_apple_ch_ae_metering_mode_set) == 0x10); + +struct cmd_apple_ch_ae_flicker_freq_update_current_set { + u64 opcode; + u32 chan; + u32 freq; +} __packed; +static_assert(sizeof(struct cmd_apple_ch_ae_flicker_freq_update_current_set) == + 0x10); + +int isp_cmd_ch_ae_stability_set(struct apple_isp *isp, u32 chan, u32 stability); +int isp_cmd_ch_ae_stability_to_stable_set(struct apple_isp *isp, u32 chan, + u32 stability); +int isp_cmd_ch_ae_frame_rate_max_get(struct apple_isp *isp, u32 chan, + struct cmd_ch_ae_frame_rate_max_get *args); +int isp_cmd_ch_ae_frame_rate_max_set(struct apple_isp *isp, u32 chan, + u32 framerate); +int isp_cmd_ch_ae_frame_rate_min_set(struct apple_isp *isp, u32 chan, + u32 framerate); +int isp_cmd_apple_ch_ae_fd_scene_metering_config_set(struct apple_isp *isp, + u32 chan); +int isp_cmd_apple_ch_ae_metering_mode_set(struct apple_isp *isp, u32 chan, + u32 mode); +int isp_cmd_apple_ch_ae_flicker_freq_update_current_set(struct apple_isp *isp, + u32 chan, u32 freq); + +struct cmd_ch_semantic_video_enable { + u64 opcode; + u32 chan; + u32 enable; +} __packed; +static_assert(sizeof(struct cmd_ch_semantic_video_enable) == 0x10); + +struct cmd_ch_semantic_awb_enable { + u64 opcode; + u32 chan; + u32 enable; +} __packed; +static_assert(sizeof(struct cmd_ch_semantic_awb_enable) == 0x10); + +int isp_cmd_ch_semantic_video_enable(struct apple_isp *isp, u32 chan, + u32 enable); +int isp_cmd_ch_semantic_awb_enable(struct apple_isp *isp, u32 chan, u32 enable); + +#endif /* __ISP_CMD_H__ */ diff --git a/drivers/media/platform/apple/isp/isp-drv.c b/drivers/media/platform/apple/isp/isp-drv.c new file mode 100644 index 00000000000000..e8e32ba73ad962 --- /dev/null +++ b/drivers/media/platform/apple/isp/isp-drv.c @@ -0,0 +1,333 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Apple Image Signal Processor driver + * + * Copyright (C) 2023 The Asahi Linux Contributors + * + * Based on aspeed/aspeed-video.c + * Copyright 2020 IBM Corp. + * Copyright (c) 2019-2020 Intel Corporation + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "isp-cam.h" +#include "isp-iommu.h" +#include "isp-v4l2.h" + +static void apple_isp_detach_genpd(struct apple_isp *isp) +{ + if (isp->pd_count <= 1) + return; + + for (int i = isp->pd_count - 1; i >= 0; i--) { + if (isp->pd_link[i]) + device_link_del(isp->pd_link[i]); + if (!IS_ERR_OR_NULL(isp->pd_dev[i])) + dev_pm_domain_detach(isp->pd_dev[i], true); + } + + return; +} + +static int apple_isp_attach_genpd(struct apple_isp *isp) +{ + struct device *dev = isp->dev; + + isp->pd_count = of_count_phandle_with_args( + dev->of_node, "power-domains", "#power-domain-cells"); + if (isp->pd_count <= 1) + return 0; + + isp->pd_dev = devm_kcalloc(dev, isp->pd_count, sizeof(*isp->pd_dev), + GFP_KERNEL); + if (!isp->pd_dev) + return -ENOMEM; + + isp->pd_link = devm_kcalloc(dev, isp->pd_count, sizeof(*isp->pd_link), + GFP_KERNEL); + if (!isp->pd_link) + return -ENOMEM; + + for (int i = 0; i < isp->pd_count; i++) { + isp->pd_dev[i] = dev_pm_domain_attach_by_id(dev, i); + if (IS_ERR(isp->pd_dev[i])) { + apple_isp_detach_genpd(isp); + return PTR_ERR(isp->pd_dev[i]); + } + + isp->pd_link[i] = + device_link_add(dev, isp->pd_dev[i], + DL_FLAG_STATELESS | DL_FLAG_PM_RUNTIME | + DL_FLAG_RPM_ACTIVE); + if (!isp->pd_link[i]) { + apple_isp_detach_genpd(isp); + return -EINVAL; + } + } + + return 0; +} + +static int apple_isp_init_iommu(struct apple_isp *isp) +{ + struct device *dev = isp->dev; + struct isp_firmware *fw = &isp->fw; + u64 heap_base, heap_size, vm_size; + int err; + int i = 0; + + isp->domain = iommu_get_domain_for_dev(isp->dev); + if (!isp->domain) + return -EPROBE_DEFER; + isp->shift = __ffs(isp->domain->pgsize_bitmap); + + err = of_property_read_u64(dev->of_node, "apple,isp-heap-base", + &heap_base); + if (err) { + dev_err(dev, "failed to read 'apple,isp-heap-base': %d\n", err); + return err; + } + + err = of_property_read_u64(dev->of_node, "apple,isp-heap-size", + &heap_size); + if (err) { + dev_err(dev, "failed to read 'apple,isp-heap-size': %d\n", err); + return err; + } + + err = of_property_read_u64(dev->of_node, "apple,dart-vm-size", + &vm_size); + if (err) { + dev_err(dev, "failed to read 'apple,dart-vm-size': %d\n", err); + return err; + } + + drm_mm_init(&isp->iovad, heap_base, vm_size - heap_base); + + /* Allocate read-only coprocessor private heap */ + fw->heap = isp_alloc_surface(isp, heap_size); + if (!fw->heap) { + drm_mm_takedown(&isp->iovad); + err = -ENOMEM; + return err; + } + + apple_isp_iommu_sync_ttbr(isp); + + return 0; +} + +static void apple_isp_free_iommu(struct apple_isp *isp) +{ + isp_free_surface(isp, isp->fw.heap); + drm_mm_takedown(&isp->iovad); +} + +static int apple_isp_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct apple_isp *isp; + struct resource *res; + int err; + + isp = devm_kzalloc(dev, sizeof(*isp), GFP_KERNEL); + if (!isp) + return -ENOMEM; + + isp->dev = dev; + isp->hw = of_device_get_match_data(dev); + platform_set_drvdata(pdev, isp); + dev_set_drvdata(dev, isp); + + err = apple_isp_attach_genpd(isp); + if (err) { + dev_err(dev, "failed to attatch power domains\n"); + return err; + } + + isp->asc = devm_platform_ioremap_resource_byname(pdev, "asc"); + if (IS_ERR(isp->asc)) { + err = PTR_ERR(isp->asc); + goto detach_genpd; + } + + isp->core = devm_platform_ioremap_resource_byname(pdev, "core"); + if (IS_ERR(isp->core)) { + err = PTR_ERR(isp->core); + goto detach_genpd; + } + + res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "dart0"); + if (!res) { + err = -ENODEV; + goto detach_genpd; + } + + /* Simply ioremap since it's a shared register zone */ + isp->dart0 = devm_ioremap(dev, res->start, resource_size(res)); + if (IS_ERR(isp->dart0)) { + err = PTR_ERR(isp->dart0); + goto detach_genpd; + } + + isp->dart1 = devm_platform_ioremap_resource_byname(pdev, "dart1"); + if (IS_ERR(isp->dart1)) { + err = PTR_ERR(isp->dart1); + goto detach_genpd; + } + + isp->dart2 = devm_platform_ioremap_resource_byname(pdev, "dart2"); + if (IS_ERR(isp->dart2)) { + err = PTR_ERR(isp->dart2); + goto detach_genpd; + } + + isp->irq = platform_get_irq(pdev, 0); + if (isp->irq < 0) { + err = isp->irq; + goto detach_genpd; + } + if (!isp->irq) { + err = -ENODEV; + goto detach_genpd; + } + + mutex_init(&isp->iovad_lock); + mutex_init(&isp->video_lock); + spin_lock_init(&isp->buf_lock); + init_waitqueue_head(&isp->wait); + INIT_LIST_HEAD(&isp->gc); + INIT_LIST_HEAD(&isp->buffers); + isp->wq = alloc_workqueue("apple-isp-wq", WQ_UNBOUND, 0); + if (!isp->wq) { + dev_err(dev, "failed to create workqueue\n"); + err = -ENOMEM; + goto detach_genpd; + } + + err = apple_isp_init_iommu(isp); + if (err) { + dev_err(dev, "failed to init iommu: %d\n", err); + goto destroy_wq; + } + + pm_runtime_enable(dev); + + err = apple_isp_detect_camera(isp); + if (err) { + dev_err(dev, "failed to detect camera: %d\n", err); + goto free_iommu; + } + + err = apple_isp_setup_video(isp); + if (err) { + dev_err(dev, "failed to register video device: %d\n", err); + goto free_iommu; + } + + dev_info(dev, "apple-isp probe!\n"); + + return 0; + +free_iommu: + pm_runtime_disable(dev); + apple_isp_free_iommu(isp); +destroy_wq: + destroy_workqueue(isp->wq); +detach_genpd: + apple_isp_detach_genpd(isp); + return err; +} + +static void apple_isp_remove(struct platform_device *pdev) +{ + struct apple_isp *isp = platform_get_drvdata(pdev); + + apple_isp_remove_video(isp); + pm_runtime_disable(isp->dev); + apple_isp_free_iommu(isp); + destroy_workqueue(isp->wq); + apple_isp_detach_genpd(isp); + return 0; +} + +/* T8020/T6000 registers */ +#define DART_T8020_STREAM_COMMAND 0x20 +#define DART_T8020_STREAM_SELECT 0x34 +#define DART_T8020_TTBR 0x200 +#define DART_T8020_STREAM_COMMAND_INVALIDATE BIT(20) + +static const struct apple_isp_hw apple_isp_hw_t8103 = { + .pmu_base = 0x23b704000, + + .dsid_clr_base0 = 0x200014000, + .dsid_clr_base1 = 0x200054000, + .dsid_clr_base2 = 0x200094000, + .dsid_clr_base3 = 0x2000d4000, + .dsid_clr_range0 = 0x1000, + .dsid_clr_range1 = 0x1000, + .dsid_clr_range2 = 0x1000, + .dsid_clr_range3 = 0x1000, + + .clock_scratch = 0x23b738010, + .clock_base = 0x23bc3c000, + .clock_bit = 0x1, + .clock_size = 0x4, + .bandwidth_scratch = 0x23b73800c, + .bandwidth_base = 0x23bc3c000, + .bandwidth_bit = 0x0, + .bandwidth_size = 0x4, + + .stream_command = DART_T8020_STREAM_COMMAND, + .stream_select = DART_T8020_STREAM_SELECT, + .ttbr = DART_T8020_TTBR, + .stream_command_invalidate = DART_T8020_STREAM_COMMAND_INVALIDATE, +}; + +static const struct of_device_id apple_isp_of_match[] = { + { .compatible = "apple,t8103-isp", .data = &apple_isp_hw_t8103 }, + {}, +}; +MODULE_DEVICE_TABLE(of, apple_isp_of_match); + +static __maybe_unused int apple_isp_suspend(struct device *dev) +{ + struct apple_isp *isp = dev_get_drvdata(dev); + + apple_isp_iommu_invalidate_tlb(isp); + + return 0; +} + +static __maybe_unused int apple_isp_resume(struct device *dev) +{ + struct apple_isp *isp = dev_get_drvdata(dev); + + apple_isp_iommu_sync_ttbr(isp); + + return 0; +} +DEFINE_RUNTIME_DEV_PM_OPS(apple_isp_pm_ops, apple_isp_suspend, apple_isp_resume, NULL); + +static struct platform_driver apple_isp_driver = { + .driver = { + .name = "apple-isp", + .of_match_table = apple_isp_of_match, + .pm = pm_ptr(&apple_isp_pm_ops), + }, + .probe = apple_isp_probe, + .remove = apple_isp_remove, +}; +module_platform_driver(apple_isp_driver); + +MODULE_AUTHOR("Eileen Yoon "); +MODULE_DESCRIPTION("Apple ISP driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/media/platform/apple/isp/isp-drv.h b/drivers/media/platform/apple/isp/isp-drv.h new file mode 100644 index 00000000000000..5db64dcc844863 --- /dev/null +++ b/drivers/media/platform/apple/isp/isp-drv.h @@ -0,0 +1,258 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* Copyright 2023 Eileen Yoon */ + +#ifndef __ISP_DRV_H__ +#define __ISP_DRV_H__ + +#include +#include +#include +#include + +#include +#include +#include +#include + +/* #define APPLE_ISP_DEBUG */ +#define APPLE_ISP_DEVICE_NAME "apple-isp" + +#define ISP_MAX_CHANNELS 6 +#define ISP_IPC_MESSAGE_SIZE 64 +#define ISP_IPC_FLAG_ACK 0x1 +#define ISP_META_SIZE 0x4640 + +struct isp_surf { + struct drm_mm_node *mm; + struct list_head head; + u64 size; + u32 num_pages; + struct page **pages; + struct sg_table sgt; + dma_addr_t iova; + void *virt; + refcount_t refcount; + bool gc; +}; + +struct isp_message { + u64 arg0; + u64 arg1; + u64 arg2; + u64 arg3; + u64 arg4; + u64 arg5; + u64 arg6; + u64 arg7; +} __packed; +static_assert(sizeof(struct isp_message) == ISP_IPC_MESSAGE_SIZE); + +struct isp_channel { + char *name; + u32 type; + u32 src; + u32 num; + u64 size; + dma_addr_t iova; + u32 doorbell; + u32 cursor; + spinlock_t lock; + struct isp_message req; + struct isp_message rsp; + const struct isp_chan_ops *ops; +}; + +struct apple_isp_hw { + u64 pmu_base; + + u64 dsid_clr_base0; + u64 dsid_clr_base1; + u64 dsid_clr_base2; + u64 dsid_clr_base3; + u32 dsid_clr_range0; + u32 dsid_clr_range1; + u32 dsid_clr_range2; + u32 dsid_clr_range3; + + u64 clock_scratch; + u64 clock_base; + u8 clock_bit; + u8 clock_size; + u64 bandwidth_scratch; + u64 bandwidth_base; + u8 bandwidth_bit; + u8 bandwidth_size; + + u32 stream_command; + u32 stream_select; + u32 ttbr; + u32 stream_command_invalidate; +}; + +struct isp_resv { + phys_addr_t phys; + dma_addr_t iova; + u64 size; +}; + +enum isp_sensor_id { + ISP_IMX248_1820_01, + ISP_IMX248_1822_02, + ISP_IMX343_5221_02, + ISP_IMX354_9251_02, + ISP_IMX356_4820_01, + ISP_IMX356_4820_02, + ISP_IMX364_8720_01, + ISP_IMX364_8723_01, + ISP_IMX372_3820_01, + ISP_IMX372_3820_02, + ISP_IMX372_3820_11, + ISP_IMX372_3820_12, + ISP_IMX405_9720_01, + ISP_IMX405_9721_01, + ISP_IMX405_9723_01, + ISP_IMX414_2520_01, + ISP_IMX503_7820_01, + ISP_IMX503_7820_02, + ISP_IMX505_3921_01, + ISP_IMX514_2820_01, + ISP_IMX514_2820_02, + ISP_IMX514_2820_03, + ISP_IMX514_2820_04, + ISP_IMX558_1921_01, + ISP_IMX558_1922_02, + ISP_IMX603_7920_01, + ISP_IMX603_7920_02, + ISP_IMX603_7921_01, + ISP_IMX613_4920_01, + ISP_IMX613_4920_02, + ISP_IMX614_2921_01, + ISP_IMX614_2921_02, + ISP_IMX614_2922_02, + ISP_IMX633_3622_01, + ISP_IMX703_7721_01, + ISP_IMX703_7722_01, + ISP_IMX713_4721_01, + ISP_IMX713_4722_01, + ISP_IMX714_2022_01, + ISP_IMX772_3721_01, + ISP_IMX772_3721_11, + ISP_IMX772_3722_01, + ISP_IMX772_3723_01, + ISP_IMX814_2123_01, + ISP_IMX853_7622_01, + ISP_IMX913_7523_01, + ISP_VD56G0_6221_01, + ISP_VD56G0_6222_01, +}; + +struct isp_format { + enum isp_sensor_id id; + u32 version; + u32 num_presets; + u32 preset; + u32 width; + u32 height; + u32 x1; + u32 y1; + u32 x2; + u32 y2; + unsigned int num_planes; + size_t plane_size[VB2_MAX_PLANES]; + size_t total_size; +}; + +struct apple_isp { + struct device *dev; + const struct apple_isp_hw *hw; + + int num_channels; + struct isp_format fmts[ISP_MAX_CHANNELS]; + unsigned int current_ch; + + struct video_device vdev; + struct media_device mdev; + struct v4l2_device v4l2_dev; + struct vb2_queue vbq; + struct mutex video_lock; + unsigned int sequence; + bool multiplanar; + + int pd_count; + struct device **pd_dev; + struct device_link **pd_link; + + int irq; + + void __iomem *asc; + void __iomem *core; + void __iomem *dart0; + void __iomem *dart1; + void __iomem *dart2; + + struct iommu_domain *domain; + unsigned long shift; + struct drm_mm iovad; /* TODO iova.c can't allocate bottom-up */ + struct mutex iovad_lock; + + struct isp_firmware { + struct isp_surf *heap; + } fw; + + struct isp_surf *ipc_surf; + struct isp_surf *extra_surf; + struct isp_surf *data_surf; + struct list_head gc; + struct workqueue_struct *wq; + + int num_ipc_chans; + struct isp_channel **ipc_chans; + struct isp_channel *chan_tm; /* TERMINAL */ + struct isp_channel *chan_io; /* IO */ + struct isp_channel *chan_dg; /* DEBUG */ + struct isp_channel *chan_bh; /* BUF_H2T */ + struct isp_channel *chan_bt; /* BUF_T2H */ + struct isp_channel *chan_sm; /* SHAREDMALLOC */ + struct isp_channel *chan_it; /* IO_T2H */ + + wait_queue_head_t wait; + dma_addr_t cmd_iova; + + unsigned long state; + spinlock_t buf_lock; + struct list_head buffers; +}; + +struct isp_chan_ops { + int (*handle)(struct apple_isp *isp, struct isp_channel *chan); +}; + +struct isp_buffer { + struct vb2_v4l2_buffer vb; + struct list_head link; + struct isp_surf surfs[VB2_MAX_PLANES]; + struct isp_surf *meta; +}; + +#define to_isp_buffer(x) container_of((x), struct isp_buffer, vb) + +enum { + ISP_STATE_STREAMING, + ISP_STATE_LOGGING, +}; + +#ifdef APPLE_ISP_DEBUG +#define isp_dbg(isp, fmt, ...) \ + dev_info((isp)->dev, "[%s] " fmt, __func__, ##__VA_ARGS__) +#else +#define isp_dbg(isp, fmt, ...) \ + dev_dbg((isp)->dev, "[%s] " fmt, __func__, ##__VA_ARGS__) +#endif + +#define isp_err(isp, fmt, ...) \ + dev_err((isp)->dev, "[%s] " fmt, __func__, ##__VA_ARGS__) + +#define isp_get_format(isp, ch) (&(isp)->fmts[(ch)]) +#define isp_get_current_format(isp) (isp_get_format(isp, isp->current_ch)) + +#endif /* __ISP_DRV_H__ */ diff --git a/drivers/media/platform/apple/isp/isp-fw.c b/drivers/media/platform/apple/isp/isp-fw.c new file mode 100644 index 00000000000000..12b9c0694d68e8 --- /dev/null +++ b/drivers/media/platform/apple/isp/isp-fw.c @@ -0,0 +1,606 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* Copyright 2023 Eileen Yoon */ + +#include +#include +#include + +#include "isp-cmd.h" +#include "isp-iommu.h" +#include "isp-ipc.h" +#include "isp-regs.h" + +#define ISP_FIRMWARE_MDELAY 1 +#define ISP_FIRMWARE_MAX_TRIES 1000 + +#define ISP_FIRMWARE_BOOTARGS_SIZE 0x180 +#define ISP_FIRMWARE_IPC_SIZE 0x1c000 +#define ISP_FIRMWARE_DATA_SIZE 0x28000 + +static inline u32 isp_asc_read32(struct apple_isp *isp, u32 reg) +{ + return readl(isp->asc + reg); +} + +static inline void isp_asc_write32(struct apple_isp *isp, u32 reg, u32 val) +{ + writel(val, isp->asc + reg); +} + +struct isp_firmware_bootargs { + u32 pad_0[2]; + u64 ipc_iova; + u64 unk_size; + u64 unk_inv; + u64 extra_iova; + u64 extra_size; + u32 unk4; + u32 pad_40[7]; + u32 ipc_size; + u32 pad_60[5]; + u32 unk5; + u32 pad_7c[13]; + u32 pad_b0; + u32 unk7; + u32 pad_b8[5]; + u32 unk_iova1; + u32 pad_c0[47]; + u32 unk9; +} __packed; +static_assert(sizeof(struct isp_firmware_bootargs) == + ISP_FIRMWARE_BOOTARGS_SIZE); + +struct isp_chan_desc { + char name[64]; + u32 type; + u32 src; + u32 num; + u32 pad; + u64 iova; + u32 padding[0x2a]; +} __packed; +static_assert(sizeof(struct isp_chan_desc) == 0x100); + +static const struct isp_chan_ops tm_ops = { + .handle = ipc_tm_handle, +}; + +static const struct isp_chan_ops sm_ops = { + .handle = ipc_sm_handle, +}; + +static const struct isp_chan_ops bt_ops = { + .handle = ipc_bt_handle, +}; + +static irqreturn_t apple_isp_isr(int irq, void *dev) +{ + struct apple_isp *isp = dev; + + isp_core_write32(isp, ISP_CORE_IRQ_ACK, + isp_core_read32(isp, ISP_CORE_IRQ_INTERRUPT)); + + wake_up_interruptible_all(&isp->wait); + + ipc_chan_handle(isp, isp->chan_sm); + wake_up_interruptible_all(&isp->wait); /* Some commands depend on sm */ + + ipc_chan_handle(isp, isp->chan_tm); + + ipc_chan_handle(isp, isp->chan_bt); + wake_up_interruptible_all(&isp->wait); + + return IRQ_HANDLED; +} + +static void isp_disable_irq(struct apple_isp *isp) +{ + isp_core_write32(isp, ISP_CORE_IRQ_ENABLE, 0x0); + free_irq(isp->irq, isp); + isp_core_write32(isp, ISP_CORE_GPIO_1, 0xfeedbabe); /* real funny */ +} + +static int isp_enable_irq(struct apple_isp *isp) +{ + int err; + + err = request_irq(isp->irq, apple_isp_isr, 0, "apple-isp", isp); + if (err < 0) { + isp_err(isp, "failed to request IRQ#%u (%d)\n", isp->irq, err); + return err; + } + + isp_dbg(isp, "about to enable interrupts...\n"); + + isp_core_write32(isp, ISP_CORE_IRQ_ENABLE, 0xf); + + return 0; +} + +static int isp_coproc_ready(struct apple_isp *isp) +{ + int retries; + u32 status; + + isp_asc_write32(isp, ISP_ASC_EDPRCR, 0x2); + + isp_asc_write32(isp, ISP_ASC_PMGR_0, 0xff00ff); + isp_asc_write32(isp, ISP_ASC_PMGR_1, 0xff00ff); + isp_asc_write32(isp, ISP_ASC_PMGR_2, 0xff00ff); + isp_asc_write32(isp, ISP_ASC_PMGR_3, 0xff00ff); + + isp_asc_write32(isp, ISP_ASC_IRQ_MASK_0, 0xffffffff); + isp_asc_write32(isp, ISP_ASC_IRQ_MASK_1, 0xffffffff); + isp_asc_write32(isp, ISP_ASC_IRQ_MASK_2, 0xffffffff); + isp_asc_write32(isp, ISP_ASC_IRQ_MASK_3, 0xffffffff); + isp_asc_write32(isp, ISP_ASC_IRQ_MASK_4, 0xffffffff); + isp_asc_write32(isp, ISP_ASC_IRQ_MASK_5, 0xffffffff); + + for (retries = 0; retries < ISP_FIRMWARE_MAX_TRIES; retries++) { + status = isp_asc_read32(isp, ISP_ASC_STATUS); + if (!((status & 0x3) == 0)) { + isp_dbg(isp, "%d: coproc in WFI (status: 0x%x)\n", + retries, status); + break; + } + mdelay(ISP_FIRMWARE_MDELAY); + } + if (retries >= ISP_FIRMWARE_MAX_TRIES) { + isp_err(isp, "coproc NOT in WFI (status: 0x%x)\n", status); + return -ENODEV; + } + + return 0; +} + +static void isp_firmware_shutdown_stage1(struct apple_isp *isp) +{ + isp_asc_write32(isp, ISP_ASC_CONTROL, 0x0); +} + +static int isp_firmware_boot_stage1(struct apple_isp *isp) +{ + int err, retries; + + err = isp_coproc_ready(isp); + if (err < 0) + return err; + + isp_core_write32(isp, ISP_CORE_CLOCK_EN, 0x1); + + isp_core_write32(isp, ISP_CORE_GPIO_0, 0x0); + isp_core_write32(isp, ISP_CORE_GPIO_1, 0x0); + isp_core_write32(isp, ISP_CORE_GPIO_2, 0x0); + isp_core_write32(isp, ISP_CORE_GPIO_3, 0x0); + isp_core_write32(isp, ISP_CORE_GPIO_4, 0x0); + isp_core_write32(isp, ISP_CORE_GPIO_5, 0x0); + isp_core_write32(isp, ISP_CORE_GPIO_6, 0x0); + isp_core_write32(isp, ISP_CORE_GPIO_7, 0x0); + + isp_core_write32(isp, ISP_CORE_IRQ_ENABLE, 0x0); + + isp_asc_write32(isp, ISP_ASC_CONTROL, 0x0); + isp_asc_write32(isp, ISP_ASC_CONTROL, 0x10); + + /* Wait for ISP_CORE_GPIO_7 to 0x0 -> 0x8042006 */ + isp_core_write32(isp, ISP_CORE_GPIO_7, 0x0); + for (retries = 0; retries < ISP_FIRMWARE_MAX_TRIES; retries++) { + u32 val = isp_core_read32(isp, ISP_CORE_GPIO_7); + if (val == 0x8042006) { + isp_dbg(isp, + "got first magic number (0x%x) from firmware\n", + val); + break; + } + mdelay(ISP_FIRMWARE_MDELAY); + } + if (retries >= ISP_FIRMWARE_MAX_TRIES) { + isp_err(isp, + "never received first magic number from firmware\n"); + return -ENODEV; + } + + return 0; +} + +static void isp_firmware_shutdown_stage2(struct apple_isp *isp) +{ + isp_free_surface(isp, isp->data_surf); + isp_free_surface(isp, isp->extra_surf); + isp_free_surface(isp, isp->ipc_surf); +} + +static int isp_firmware_boot_stage2(struct apple_isp *isp) +{ + struct isp_firmware_bootargs args; + dma_addr_t args_iova; + int err, retries; + + u32 num_ipc_chans = isp_core_read32(isp, ISP_CORE_GPIO_0); + u32 args_offset = isp_core_read32(isp, ISP_CORE_GPIO_1); + u32 extra_size = isp_core_read32(isp, ISP_CORE_GPIO_3); + isp->num_ipc_chans = num_ipc_chans; + + if (!isp->num_ipc_chans) { + dev_err(isp->dev, "No IPC channels found\n"); + return -ENODEV; + } + + if (isp->num_ipc_chans != 7) + dev_warn(isp->dev, "unexpected channel count (%d)\n", + num_ipc_chans); + + isp->ipc_surf = isp_alloc_surface_vmap(isp, ISP_FIRMWARE_IPC_SIZE); + if (!isp->ipc_surf) { + isp_err(isp, "failed to alloc surface for ipc\n"); + return -ENOMEM; + } + + isp->extra_surf = isp_alloc_surface_vmap(isp, extra_size); + if (!isp->extra_surf) { + isp_err(isp, "failed to alloc surface for extra heap\n"); + goto free_ipc; + } + + isp->data_surf = isp_alloc_surface_vmap(isp, ISP_FIRMWARE_DATA_SIZE); + if (!isp->data_surf) { + isp_err(isp, "failed to alloc surface for data files\n"); + goto free_extra; + } + + args_iova = isp->ipc_surf->iova + args_offset + 0x40; + isp->cmd_iova = args_iova + sizeof(args) + 0x40; + + memset(&args, 0, sizeof(args)); + args.ipc_iova = isp->ipc_surf->iova; + args.ipc_size = isp->ipc_surf->size; + args.unk_size = 0x1800000; + args.unk_inv = 0x10000000 - args.unk_size; + args.extra_iova = isp->extra_surf->iova; + args.extra_size = isp->extra_surf->size; + args.unk4 = 0x1; + args.unk5 = 0x40; + args.unk7 = 0x1; + args.unk_iova1 = args_iova + ISP_FIRMWARE_BOOTARGS_SIZE - 0xc; + args.unk9 = 0x3; + isp_iowrite(isp, args_iova, &args, sizeof(args)); + + isp_core_write32(isp, ISP_CORE_GPIO_0, args_iova); + isp_core_write32(isp, ISP_CORE_GPIO_1, 0x0); + + /* Wait for ISP_CORE_GPIO_7 to 0xf7fbdff9 -> 0x8042006 */ + isp_core_write32(isp, ISP_CORE_GPIO_7, 0xf7fbdff9); + + for (retries = 0; retries < ISP_FIRMWARE_MAX_TRIES; retries++) { + u32 val = isp_core_read32(isp, ISP_CORE_GPIO_7); + if (val == 0x8042006) { + isp_dbg(isp, + "got second magic number (0x%x) from firmware\n", + val); + break; + } + mdelay(ISP_FIRMWARE_MDELAY); + } + if (retries >= ISP_FIRMWARE_MAX_TRIES) { + isp_err(isp, + "never received second magic number from firmware\n"); + err = -ENODEV; + goto free_file; + } + + return 0; + +free_file: + isp_free_surface(isp, isp->data_surf); +free_extra: + isp_free_surface(isp, isp->extra_surf); +free_ipc: + isp_free_surface(isp, isp->ipc_surf); + return err; +} + +static inline struct isp_channel *isp_get_chan_index(struct apple_isp *isp, + const char *name) +{ + for (int i = 0; i < isp->num_ipc_chans; i++) { + if (!strcasecmp(isp->ipc_chans[i]->name, name)) + return isp->ipc_chans[i]; + } + return NULL; +} + +static void isp_free_channel_info(struct apple_isp *isp) +{ + for (int i = 0; i < isp->num_ipc_chans; i++) { + struct isp_channel *chan = isp->ipc_chans[i]; + if (!chan) + continue; + kfree(chan->name); + kfree(chan); + isp->ipc_chans[i] = NULL; + } + kfree(isp->ipc_chans); + isp->ipc_chans = NULL; +} + +static int isp_fill_channel_info(struct apple_isp *isp) +{ + u32 table_iova = isp_core_read32(isp, ISP_CORE_GPIO_0); + + isp->ipc_chans = kcalloc(isp->num_ipc_chans, + sizeof(struct isp_channel *), GFP_KERNEL); + if (!isp->ipc_chans) + goto out; + + for (int i = 0; i < isp->num_ipc_chans; i++) { + struct isp_chan_desc desc; + dma_addr_t desc_iova = table_iova + (i * sizeof(desc)); + struct isp_channel *chan = + kzalloc(sizeof(struct isp_channel), GFP_KERNEL); + if (!chan) + goto out; + isp->ipc_chans[i] = chan; + + isp_ioread(isp, desc_iova, &desc, sizeof(desc)); + chan->name = kstrdup(desc.name, GFP_KERNEL); + chan->type = desc.type; + chan->src = desc.src; + chan->doorbell = 1 << chan->src; + chan->num = desc.num; + chan->size = desc.num * ISP_IPC_MESSAGE_SIZE; + chan->iova = desc.iova; + chan->cursor = 0; + spin_lock_init(&chan->lock); + + if ((chan->type != ISP_IPC_CHAN_TYPE_COMMAND) && + (chan->type != ISP_IPC_CHAN_TYPE_REPLY) && + (chan->type != ISP_IPC_CHAN_TYPE_REPORT)) { + isp_err(isp, "invalid ipc chan type (%d)\n", + chan->type); + goto out; + } + + isp_dbg(isp, "chan: %s type: %d src: %d num: %d iova: 0x%llx\n", + chan->name, chan->type, chan->src, chan->num, + chan->iova); + } + + isp->chan_tm = isp_get_chan_index(isp, "TERMINAL"); + isp->chan_io = isp_get_chan_index(isp, "IO"); + isp->chan_dg = isp_get_chan_index(isp, "DEBUG"); + isp->chan_bh = isp_get_chan_index(isp, "BUF_H2T"); + isp->chan_bt = isp_get_chan_index(isp, "BUF_T2H"); + isp->chan_sm = isp_get_chan_index(isp, "SHAREDMALLOC"); + isp->chan_it = isp_get_chan_index(isp, "IO_T2H"); + + if (!isp->chan_tm || !isp->chan_io || !isp->chan_dg || !isp->chan_bh || + !isp->chan_bt || !isp->chan_sm || !isp->chan_it) { + isp_err(isp, "did not find all of the required ipc chans\n"); + goto out; + } + + isp->chan_tm->ops = &tm_ops; + isp->chan_sm->ops = &sm_ops; + isp->chan_bt->ops = &bt_ops; + + return 0; +out: + isp_free_channel_info(isp); + return -ENOMEM; +} + +static void isp_firmware_shutdown_stage3(struct apple_isp *isp) +{ + isp_free_channel_info(isp); +} + +static int isp_firmware_boot_stage3(struct apple_isp *isp) +{ + int err, retries; + + err = isp_fill_channel_info(isp); + if (err < 0) + return err; + + /* Mask the command channels to prepare for submission */ + for (int i = 0; i < isp->num_ipc_chans; i++) { + struct isp_channel *chan = isp->ipc_chans[i]; + if (chan->type != ISP_IPC_CHAN_TYPE_COMMAND) + continue; + for (int j = 0; j < chan->num; j++) { + struct isp_message msg; + dma_addr_t msg_iova = chan->iova + (j * sizeof(msg)); + + memset(&msg, 0, sizeof(msg)); + msg.arg0 = ISP_IPC_FLAG_ACK; + isp_iowrite(isp, msg_iova, &msg, sizeof(msg)); + } + } + + /* Wait for ISP_CORE_GPIO_3 to 0x8042006 -> 0x0 */ + isp_core_write32(isp, ISP_CORE_GPIO_3, 0x8042006); + + for (retries = 0; retries < ISP_FIRMWARE_MAX_TRIES; retries++) { + u32 val = isp_core_read32(isp, ISP_CORE_GPIO_3); + if (val == 0x0) { + isp_dbg(isp, + "got third magic number (0x%x) from firmware\n", + val); + break; + } + mdelay(ISP_FIRMWARE_MDELAY); + } + if (retries >= ISP_FIRMWARE_MAX_TRIES) { + isp_err(isp, + "never received third magic number from firmware\n"); + isp_free_channel_info(isp); + return -ENODEV; + } + + isp_dbg(isp, "firmware booted!\n"); + + return 0; +} + +static int isp_stop_command_processor(struct apple_isp *isp) +{ + int retries; + + /* Wait for ISP_CORE_GPIO_0 to 0xf7fbdff9 -> 0x8042006 */ + isp_core_write32(isp, ISP_CORE_GPIO_0, 0xf7fbdff9); + + /* Their CISP_CMD_STOP implementation is buggy */ + isp_cmd_suspend(isp); + + for (retries = 0; retries < ISP_FIRMWARE_MAX_TRIES; retries++) { + u32 val = isp_core_read32(isp, ISP_CORE_GPIO_0); + if (val == 0x8042006) { + isp_dbg(isp, "got magic number (0x%x) from firmware\n", + val); + break; + } + mdelay(ISP_FIRMWARE_MDELAY); + } + if (retries >= ISP_FIRMWARE_MAX_TRIES) { + isp_err(isp, "never received magic number from firmware\n"); + return -ENODEV; + } + + return 0; +} + +static int isp_start_command_processor(struct apple_isp *isp) +{ + int err; + + err = isp_cmd_print_enable(isp, 1); + if (err) + return err; + + err = isp_cmd_set_isp_pmu_base(isp, isp->hw->pmu_base); + if (err) + return err; + + err = isp_cmd_set_dsid_clr_req_base2( + isp, isp->hw->dsid_clr_base0, isp->hw->dsid_clr_base1, + isp->hw->dsid_clr_base2, isp->hw->dsid_clr_base3, + isp->hw->dsid_clr_range0, isp->hw->dsid_clr_range1, + isp->hw->dsid_clr_range2, isp->hw->dsid_clr_range3); + if (err) + return err; + + err = isp_cmd_pmp_ctrl_set( + isp, isp->hw->clock_scratch, isp->hw->clock_base, + isp->hw->clock_bit, isp->hw->clock_size, + isp->hw->bandwidth_scratch, isp->hw->bandwidth_base, + isp->hw->bandwidth_bit, isp->hw->bandwidth_size); + if (err) + return err; + + err = isp_cmd_start(isp, 0); + if (err) + return err; + + /* Now we can access CISP_CMD_CH_* commands */ + + return 0; +} + +static void isp_collect_gc_surface(struct apple_isp *isp) +{ + struct isp_surf *tmp, *surf; + list_for_each_entry_safe_reverse(surf, tmp, &isp->gc, head) { + isp_dbg(isp, "freeing iova: 0x%llx size: 0x%llx virt: %pS\n", + surf->iova, surf->size, (void *)surf->virt); + isp_free_surface(isp, surf); + } +} + +static int isp_firmware_boot(struct apple_isp *isp) +{ + int err; + + err = isp_firmware_boot_stage1(isp); + if (err < 0) { + isp_err(isp, "failed firmware boot stage 1: %d\n", err); + goto garbage_collect; + } + + err = isp_firmware_boot_stage2(isp); + if (err < 0) { + isp_err(isp, "failed firmware boot stage 2: %d\n", err); + goto shutdown_stage1; + } + + err = isp_firmware_boot_stage3(isp); + if (err < 0) { + isp_err(isp, "failed firmware boot stage 3: %d\n", err); + goto shutdown_stage2; + } + + err = isp_enable_irq(isp); + if (err < 0) { + isp_err(isp, "failed to enable interrupts: %d\n", err); + goto shutdown_stage3; + } + + err = isp_start_command_processor(isp); + if (err < 0) { + isp_err(isp, "failed to start command processor: %d\n", err); + goto disable_irqs; + } + + flush_workqueue(isp->wq); + + return 0; + +disable_irqs: + isp_disable_irq(isp); +shutdown_stage3: + isp_firmware_shutdown_stage3(isp); +shutdown_stage2: + isp_firmware_shutdown_stage2(isp); +shutdown_stage1: + isp_firmware_shutdown_stage1(isp); +garbage_collect: + isp_collect_gc_surface(isp); + return err; +} + +static void isp_firmware_shutdown(struct apple_isp *isp) +{ + flush_workqueue(isp->wq); + isp_stop_command_processor(isp); + isp_disable_irq(isp); + isp_firmware_shutdown_stage3(isp); + isp_firmware_shutdown_stage2(isp); + isp_firmware_shutdown_stage1(isp); + isp_collect_gc_surface(isp); +} + +int apple_isp_firmware_boot(struct apple_isp *isp) +{ + int err; + + /* Needs to be power cycled for IOMMU to behave correctly */ + err = pm_runtime_resume_and_get(isp->dev); + if (err < 0) { + dev_err(isp->dev, "failed to enable power: %d\n", err); + return err; + } + + err = isp_firmware_boot(isp); + if (err) { + dev_err(isp->dev, "failed to boot firmware: %d\n", err); + pm_runtime_put_sync(isp->dev); + return err; + } + + return 0; +} + +void apple_isp_firmware_shutdown(struct apple_isp *isp) +{ + isp_firmware_shutdown(isp); + pm_runtime_put_sync(isp->dev); +} diff --git a/drivers/media/platform/apple/isp/isp-fw.h b/drivers/media/platform/apple/isp/isp-fw.h new file mode 100644 index 00000000000000..ad9f4fdf641aaa --- /dev/null +++ b/drivers/media/platform/apple/isp/isp-fw.h @@ -0,0 +1,12 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* Copyright 2023 Eileen Yoon */ + +#ifndef __ISP_FW_H__ +#define __ISP_FW_H__ + +#include "isp-drv.h" + +int apple_isp_firmware_boot(struct apple_isp *isp); +void apple_isp_firmware_shutdown(struct apple_isp *isp); + +#endif /* __ISP_FW_H__ */ diff --git a/drivers/media/platform/apple/isp/isp-iommu.c b/drivers/media/platform/apple/isp/isp-iommu.c new file mode 100644 index 00000000000000..28935d37205024 --- /dev/null +++ b/drivers/media/platform/apple/isp/isp-iommu.c @@ -0,0 +1,275 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* Copyright 2023 Eileen Yoon */ + +#include +#include + +#include "isp-iommu.h" + +void apple_isp_iommu_sync_ttbr(struct apple_isp *isp) +{ + writel(readl(isp->dart0 + isp->hw->ttbr), isp->dart1 + isp->hw->ttbr); + writel(readl(isp->dart0 + isp->hw->ttbr), isp->dart2 + isp->hw->ttbr); +} + +void apple_isp_iommu_invalidate_tlb(struct apple_isp *isp) +{ + iommu_flush_iotlb_all(isp->domain); + writel(0x1, isp->dart1 + isp->hw->stream_select); + writel(isp->hw->stream_command_invalidate, + isp->dart1 + isp->hw->stream_command); + writel(0x1, isp->dart2 + isp->hw->stream_select); + writel(isp->hw->stream_command_invalidate, + isp->dart2 + isp->hw->stream_command); +} + +static void isp_surf_free_pages(struct isp_surf *surf) +{ + for (u32 i = 0; i < surf->num_pages && surf->pages[i] != NULL; i++) { + __free_page(surf->pages[i]); + } + kvfree(surf->pages); +} + +static int isp_surf_alloc_pages(struct isp_surf *surf) +{ + surf->pages = kvmalloc_array(surf->num_pages, sizeof(*surf->pages), + GFP_KERNEL); + if (!surf->pages) + return -ENOMEM; + + for (u32 i = 0; i < surf->num_pages; i++) { + surf->pages[i] = alloc_page(GFP_KERNEL); + if (surf->pages[i] == NULL) + goto free_pages; + } + + return 0; + +free_pages: + isp_surf_free_pages(surf); + return -ENOMEM; +} + +int isp_surf_vmap(struct apple_isp *isp, struct isp_surf *surf) +{ + surf->virt = vmap(surf->pages, surf->num_pages, VM_MAP, + pgprot_writecombine(PAGE_KERNEL)); + if (surf->virt == NULL) { + dev_err(isp->dev, "failed to vmap size 0x%llx\n", surf->size); + return -EINVAL; + } + + return 0; +} + +static void isp_surf_vunmap(struct apple_isp *isp, struct isp_surf *surf) +{ + if (surf->virt) + vunmap(surf->virt); + surf->virt = NULL; +} + +static void isp_surf_unreserve_iova(struct apple_isp *isp, + struct isp_surf *surf) +{ + if (surf->mm) { + mutex_lock(&isp->iovad_lock); + drm_mm_remove_node(surf->mm); + mutex_unlock(&isp->iovad_lock); + kfree(surf->mm); + } + surf->mm = NULL; +} + +static int isp_surf_reserve_iova(struct apple_isp *isp, struct isp_surf *surf) +{ + int err; + + surf->mm = kzalloc(sizeof(*surf->mm), GFP_KERNEL); + if (!surf->mm) + return -ENOMEM; + + mutex_lock(&isp->iovad_lock); + err = drm_mm_insert_node_generic(&isp->iovad, surf->mm, + ALIGN(surf->size, 1UL << isp->shift), + 1UL << isp->shift, 0, 0); + mutex_unlock(&isp->iovad_lock); + if (err < 0) { + dev_err(isp->dev, "failed to reserve 0x%llx of iova space\n", + surf->size); + goto mm_free; + } + + surf->iova = surf->mm->start; + + return 0; +mm_free: + kfree(surf->mm); + surf->mm = NULL; + return err; +} + +static void isp_surf_iommu_unmap(struct apple_isp *isp, struct isp_surf *surf) +{ + iommu_unmap(isp->domain, surf->iova, surf->size); + apple_isp_iommu_invalidate_tlb(isp); + sg_free_table(&surf->sgt); +} + +static int isp_surf_iommu_map(struct apple_isp *isp, struct isp_surf *surf) +{ + unsigned long size; + int err; + + err = sg_alloc_table_from_pages(&surf->sgt, surf->pages, + surf->num_pages, 0, surf->size, + GFP_KERNEL); + if (err < 0) { + dev_err(isp->dev, "failed to alloc sgt from pages\n"); + return err; + } + + size = iommu_map_sgtable(isp->domain, surf->iova, &surf->sgt, + IOMMU_READ | IOMMU_WRITE); + if (size < surf->size) { + dev_err(isp->dev, "failed to iommu_map sgt to iova 0x%llx\n", + surf->iova); + sg_free_table(&surf->sgt); + return -ENXIO; + } + + return 0; +} + +static void __isp_surf_init(struct apple_isp *isp, struct isp_surf *surf, + u64 size, bool gc) +{ + surf->mm = NULL; + surf->virt = NULL; + surf->size = ALIGN(size, 1UL << isp->shift); + surf->num_pages = surf->size >> isp->shift; + surf->gc = gc; +} + +struct isp_surf *__isp_alloc_surface(struct apple_isp *isp, u64 size, bool gc) +{ + int err; + + struct isp_surf *surf = kzalloc(sizeof(struct isp_surf), GFP_KERNEL); + if (!surf) + return NULL; + + __isp_surf_init(isp, surf, size, gc); + + err = isp_surf_alloc_pages(surf); + if (err < 0) { + dev_err(isp->dev, "failed to allocate %d pages\n", + surf->num_pages); + goto free_surf; + } + + err = isp_surf_reserve_iova(isp, surf); + if (err < 0) { + dev_err(isp->dev, "failed to reserve 0x%llx of iova space\n", + surf->size); + goto free_pages; + } + + err = isp_surf_iommu_map(isp, surf); + if (err < 0) { + dev_err(isp->dev, + "failed to iommu_map size 0x%llx to iova 0x%llx\n", + surf->size, surf->iova); + goto unreserve_iova; + } + + refcount_set(&surf->refcount, 1); + if (surf->gc) + list_add_tail(&surf->head, &isp->gc); + + return surf; + +unreserve_iova: + isp_surf_unreserve_iova(isp, surf); +free_pages: + isp_surf_free_pages(surf); +free_surf: + kfree(surf); + return NULL; +} + +struct isp_surf *isp_alloc_surface_vmap(struct apple_isp *isp, u64 size) +{ + int err; + + struct isp_surf *surf = __isp_alloc_surface(isp, size, false); + if (!surf) + return NULL; + + err = isp_surf_vmap(isp, surf); + if (err < 0) { + dev_err(isp->dev, "failed to vmap iova 0x%llx - 0x%llx\n", + surf->iova, surf->iova + surf->size); + isp_free_surface(isp, surf); + return NULL; + } + + return surf; +} + +void isp_free_surface(struct apple_isp *isp, struct isp_surf *surf) +{ + if (refcount_dec_and_test(&surf->refcount)) { + isp_surf_vunmap(isp, surf); + isp_surf_iommu_unmap(isp, surf); + isp_surf_unreserve_iova(isp, surf); + isp_surf_free_pages(surf); + if (surf->gc) + list_del(&surf->head); + kfree(surf); + } +} + +void *isp_iotranslate(struct apple_isp *isp, dma_addr_t iova) +{ + phys_addr_t phys = iommu_iova_to_phys(isp->domain, iova); + return phys_to_virt(phys); +} + +int apple_isp_iommu_map_sgt(struct apple_isp *isp, struct isp_surf *surf, + struct sg_table *sgt, u64 size) +{ + int err; + ssize_t mapped; + + // TODO userptr sends unaligned sizes + surf->mm = NULL; + surf->size = size; + + err = isp_surf_reserve_iova(isp, surf); + if (err < 0) { + dev_err(isp->dev, "failed to reserve 0x%llx of iova space\n", + surf->size); + return err; + } + + mapped = iommu_map_sgtable(isp->domain, surf->iova, sgt, + IOMMU_READ | IOMMU_WRITE); + if (mapped < surf->size) { + dev_err(isp->dev, "failed to iommu_map sgt to iova 0x%llx\n", + surf->iova); + isp_surf_unreserve_iova(isp, surf); + return -ENXIO; + } + surf->size = mapped; + + return 0; +} + +void apple_isp_iommu_unmap_sgt(struct apple_isp *isp, struct isp_surf *surf) +{ + iommu_unmap(isp->domain, surf->iova, surf->size); + apple_isp_iommu_invalidate_tlb(isp); + isp_surf_unreserve_iova(isp, surf); +} diff --git a/drivers/media/platform/apple/isp/isp-iommu.h b/drivers/media/platform/apple/isp/isp-iommu.h new file mode 100644 index 00000000000000..f9972bd9ff93e7 --- /dev/null +++ b/drivers/media/platform/apple/isp/isp-iommu.h @@ -0,0 +1,38 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* Copyright 2023 Eileen Yoon */ + +#ifndef __ISP_IOMMU_H__ +#define __ISP_IOMMU_H__ + +#include "isp-drv.h" + +void apple_isp_iommu_sync_ttbr(struct apple_isp *isp); +void apple_isp_iommu_invalidate_tlb(struct apple_isp *isp); + +struct isp_surf *__isp_alloc_surface(struct apple_isp *isp, u64 size, bool gc); +#define isp_alloc_surface(isp, size) (__isp_alloc_surface(isp, size, false)) +#define isp_alloc_surface_gc(isp, size) (__isp_alloc_surface(isp, size, true)) +struct isp_surf *isp_alloc_surface_vmap(struct apple_isp *isp, u64 size); +int isp_surf_vmap(struct apple_isp *isp, struct isp_surf *surf); +void isp_free_surface(struct apple_isp *isp, struct isp_surf *surf); +void *isp_iotranslate(struct apple_isp *isp, dma_addr_t iova); + +static inline void isp_ioread(struct apple_isp *isp, dma_addr_t iova, + void *data, u64 size) +{ + void *virt = isp_iotranslate(isp, iova); + memcpy(data, virt, size); +} + +static inline void isp_iowrite(struct apple_isp *isp, dma_addr_t iova, + void *data, u64 size) +{ + void *virt = isp_iotranslate(isp, iova); + memcpy(virt, data, size); +} + +int apple_isp_iommu_map_sgt(struct apple_isp *isp, struct isp_surf *surf, + struct sg_table *sgt, u64 size); +void apple_isp_iommu_unmap_sgt(struct apple_isp *isp, struct isp_surf *surf); + +#endif /* __ISP_IOMMU_H__ */ diff --git a/drivers/media/platform/apple/isp/isp-ipc.c b/drivers/media/platform/apple/isp/isp-ipc.c new file mode 100644 index 00000000000000..ef3498c4fcd191 --- /dev/null +++ b/drivers/media/platform/apple/isp/isp-ipc.c @@ -0,0 +1,329 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* Copyright 2023 Eileen Yoon */ + +#include "isp-iommu.h" +#include "isp-ipc.h" +#include "isp-regs.h" + +#define ISP_IPC_FLAG_TERMINAL_ACK 0x3 +#define ISP_IPC_BUFEXC_STAT_META_OFFSET 0x10 + +struct isp_sm_deferred_work { + struct work_struct work; + struct apple_isp *isp; + struct isp_surf *surf; +}; + +struct isp_bufexc_stat { + u64 unk_0; // 2 + u64 unk_8; // 2 + + u64 meta_iova; + u64 pad_20[3]; + u64 meta_size; // 0x4640 + u64 unk_38; + + u32 unk_40; // 1 + u32 unk_44; + u64 unk_48; + + u64 iova0; + u64 iova1; + u64 iova2; + u64 iova3; + u32 pad_70[4]; + + u32 unk_80; // 2 + u32 unk_84; // 1 + u32 unk_88; // 0x10 || 0x13 + u32 unk_8c; + u32 pad_90[96]; + + u32 unk_210; // 0x28 + u32 unk_214; + u32 index; + u16 bes_width; // 1296, 0x510 + u16 bes_height; // 736, 0x2e0 + + u32 unk_220; // 0x0 || 0x1 + u32 pad_224[3]; + u32 unk_230; // 0xf7ed38 + u32 unk_234; // 3 + u32 pad_238[2]; + u32 pad_240[16]; +} __packed; +static_assert(sizeof(struct isp_bufexc_stat) == ISP_IPC_BUFEXC_STAT_SIZE); + +static inline dma_addr_t chan_msg_iova(struct isp_channel *chan, u32 index) +{ + return chan->iova + (index * ISP_IPC_MESSAGE_SIZE); +} + +static inline void chan_read_msg_index(struct apple_isp *isp, + struct isp_channel *chan, + struct isp_message *msg, u32 index) +{ + isp_ioread(isp, chan_msg_iova(chan, index), msg, sizeof(*msg)); +} + +static inline void chan_read_msg(struct apple_isp *isp, + struct isp_channel *chan, + struct isp_message *msg) +{ + chan_read_msg_index(isp, chan, msg, chan->cursor); +} + +static inline void chan_write_msg_index(struct apple_isp *isp, + struct isp_channel *chan, + struct isp_message *msg, u32 index) +{ + isp_iowrite(isp, chan_msg_iova(chan, index), msg, sizeof(*msg)); +} + +static inline void chan_write_msg(struct apple_isp *isp, + struct isp_channel *chan, + struct isp_message *msg) +{ + chan_write_msg_index(isp, chan, msg, chan->cursor); +} + +static inline void chan_update_cursor(struct isp_channel *chan) +{ + if (chan->cursor >= (chan->num - 1)) { + chan->cursor = 0; + } else { + chan->cursor += 1; + } +} + +static int chan_handle_once(struct apple_isp *isp, struct isp_channel *chan) +{ + int err; + + lockdep_assert_held(&chan->lock); + + err = chan->ops->handle(isp, chan); + if (err < 0) { + dev_err(isp->dev, "%s: handler failed: %d)\n", chan->name, err); + return err; + } + + chan_write_msg(isp, chan, &chan->rsp); + + isp_core_write32(isp, ISP_CORE_IRQ_DOORBELL, chan->doorbell); + + chan_update_cursor(chan); + + return 0; +} + +static inline bool chan_rx_done(struct apple_isp *isp, struct isp_channel *chan) +{ + if (((chan->req.arg0 & 0xf) == ISP_IPC_FLAG_ACK) || + ((chan->req.arg0 & 0xf) == ISP_IPC_FLAG_TERMINAL_ACK)) { + return true; + } + return false; +} + +int ipc_chan_handle(struct apple_isp *isp, struct isp_channel *chan) +{ + int err = 0; + + spin_lock(&chan->lock); + while (1) { + chan_read_msg(isp, chan, &chan->req); + if (chan_rx_done(isp, chan)) { + err = 0; + break; + } + err = chan_handle_once(isp, chan); + if (err < 0) { + break; + } + } + spin_unlock(&chan->lock); + + return err; +} + +static inline bool chan_tx_done(struct apple_isp *isp, struct isp_channel *chan) +{ + chan_read_msg(isp, chan, &chan->rsp); + if ((chan->rsp.arg0) == (chan->req.arg0 | ISP_IPC_FLAG_ACK)) { + chan_update_cursor(chan); + return true; + } + return false; +} + +int ipc_chan_send(struct apple_isp *isp, struct isp_channel *chan, + unsigned long timeout) +{ + long t; + + chan_write_msg(isp, chan, &chan->req); + wmb(); + + isp_core_write32(isp, ISP_CORE_IRQ_DOORBELL, chan->doorbell); + + t = wait_event_interruptible_timeout(isp->wait, chan_tx_done(isp, chan), + timeout); + if (t == 0) { + dev_err(isp->dev, + "%s: timed out on request [0x%llx, 0x%llx, 0x%llx]\n", + chan->name, chan->req.arg0, chan->req.arg1, + chan->req.arg2); + return -ETIME; + } + + isp_dbg(isp, "%s: request success (%ld)\n", chan->name, t); + + return 0; +} + +int ipc_tm_handle(struct apple_isp *isp, struct isp_channel *chan) +{ + struct isp_message *rsp = &chan->rsp; + +#ifdef APPLE_ISP_DEBUG + struct isp_message *req = &chan->req; + char buf[512]; + dma_addr_t iova = req->arg0 & ~ISP_IPC_FLAG_TERMINAL_ACK; + u32 size = req->arg1; + if (iova && size && test_bit(ISP_STATE_LOGGING, &isp->state)) { + size = min_t(u32, size, 512); + isp_ioread(isp, iova, buf, size); + isp_dbg(isp, "ISPASC: %.*s", size, buf); + } +#endif + + rsp->arg0 = ISP_IPC_FLAG_ACK; + rsp->arg1 = 0x0; + rsp->arg2 = 0x0; + + return 0; +} + +/* The kernel accesses exactly two dynamically allocated shared surfaces: + * 1) LOG: Surface for terminal logs. Optional, only enabled in debug builds. + * 2) STAT: Surface for BUFT2H rendered frame stat buffer. We isp_ioread() in + * the BUFT2H ISR below. Since the BUFT2H IRQ is triggered by the BUF_H2T + * doorbell, the STAT vmap must complete before the first buffer submission + * under VIDIOC_STREAMON(). The CISP_CMD_PRINT_ENABLE completion depends on the + * STAT buffer SHAREDMALLOC ISR, which is part of the firmware initialization + * sequence. We also call flush_workqueue(), so a fault should not occur. + */ +static void sm_malloc_deferred_worker(struct work_struct *work) +{ + struct isp_sm_deferred_work *dwork = + container_of(work, struct isp_sm_deferred_work, work); + struct apple_isp *isp = dwork->isp; + struct isp_surf *surf = dwork->surf; + int err; + + err = isp_surf_vmap(isp, surf); /* Can't vmap in interrupt ctx */ + if (err < 0) { + isp_err(isp, "failed to vmap iova=0x%llx size=0x%llx\n", + surf->iova, surf->size); + goto out; + } + +#ifdef APPLE_ISP_DEBUG + /* Only enabled in debug builds so it shouldn't matter, but + * the LOG surface is always the first surface requested. + */ + if (!test_bit(ISP_STATE_LOGGING, &isp->state)) + set_bit(ISP_STATE_LOGGING, &isp->state); +#endif + +out: + kfree(dwork); +} + +int ipc_sm_handle(struct apple_isp *isp, struct isp_channel *chan) +{ + struct isp_message *req = &chan->req, *rsp = &chan->rsp; + + if (req->arg0 == 0x0) { + struct isp_sm_deferred_work *dwork; + struct isp_surf *surf; + + dwork = kzalloc(sizeof(*dwork), GFP_KERNEL); + if (!dwork) + return -ENOMEM; + dwork->isp = isp; + + surf = isp_alloc_surface_gc(isp, req->arg1); + if (!surf) { + isp_err(isp, "failed to alloc requested size 0x%llx\n", + req->arg1); + kfree(dwork); + return -ENOMEM; + } + dwork->surf = surf; + + rsp->arg0 = surf->iova | ISP_IPC_FLAG_ACK; + rsp->arg1 = 0x0; + rsp->arg2 = 0x0; /* macOS uses this to index surfaces */ + + INIT_WORK(&dwork->work, sm_malloc_deferred_worker); + if (!queue_work(isp->wq, &dwork->work)) { + isp_err(isp, "failed to queue deferred work\n"); + isp_free_surface(isp, surf); + kfree(dwork); + return -ENOMEM; + } + /* To the gc it goes... */ + + } else { + /* This should be the shared surface free request, but + * 1) The fw doesn't request to free all of what it requested + * 2) The fw continues to access the surface after + * So we link it to the gc, which runs after fw shutdown + */ +#ifdef APPLE_ISP_DEBUG + if (test_bit(ISP_STATE_LOGGING, &isp->state)) + clear_bit(ISP_STATE_LOGGING, &isp->state); +#endif + rsp->arg0 = req->arg0 | ISP_IPC_FLAG_ACK; + rsp->arg1 = 0x0; + rsp->arg2 = 0x0; + } + + return 0; +} + +int ipc_bt_handle(struct apple_isp *isp, struct isp_channel *chan) +{ + struct isp_message *req = &chan->req, *rsp = &chan->rsp; + struct isp_buffer *tmp, *buf; + int err = 0; + + /* No need to read the whole struct */ + u64 meta_iova; + isp_ioread(isp, req->arg0 + ISP_IPC_BUFEXC_STAT_META_OFFSET, &meta_iova, + sizeof(meta_iova)); + + spin_lock(&isp->buf_lock); + list_for_each_entry_safe_reverse(buf, tmp, &isp->buffers, link) { + if (buf->meta->iova == meta_iova) { + enum vb2_buffer_state state = VB2_BUF_STATE_ERROR; + buf->vb.vb2_buf.timestamp = ktime_get_ns(); + buf->vb.sequence = isp->sequence++; + buf->vb.field = V4L2_FIELD_NONE; + if (req->arg2 == ISP_IPC_BUFEXC_FLAG_RENDER) + state = VB2_BUF_STATE_DONE; + vb2_buffer_done(&buf->vb.vb2_buf, state); + list_del(&buf->link); + break; + } + } + spin_unlock(&isp->buf_lock); + + rsp->arg0 = req->arg0 | ISP_IPC_FLAG_ACK; + rsp->arg1 = 0x0; + rsp->arg2 = ISP_IPC_BUFEXC_FLAG_ACK; + + return err; +} diff --git a/drivers/media/platform/apple/isp/isp-ipc.h b/drivers/media/platform/apple/isp/isp-ipc.h new file mode 100644 index 00000000000000..32d1e1bf321006 --- /dev/null +++ b/drivers/media/platform/apple/isp/isp-ipc.h @@ -0,0 +1,26 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* Copyright 2023 Eileen Yoon */ + +#ifndef __ISP_IPC_H__ +#define __ISP_IPC_H__ + +#include "isp-drv.h" + +#define ISP_IPC_CHAN_TYPE_COMMAND 0 +#define ISP_IPC_CHAN_TYPE_REPLY 1 +#define ISP_IPC_CHAN_TYPE_REPORT 2 + +#define ISP_IPC_BUFEXC_STAT_SIZE 0x280 +#define ISP_IPC_BUFEXC_FLAG_RENDER 0x10000000 +#define ISP_IPC_BUFEXC_FLAG_COMMAND 0x30000000 +#define ISP_IPC_BUFEXC_FLAG_ACK 0x80000000 + +int ipc_chan_handle(struct apple_isp *isp, struct isp_channel *chan); +int ipc_chan_send(struct apple_isp *isp, struct isp_channel *chan, + unsigned long timeout); + +int ipc_tm_handle(struct apple_isp *isp, struct isp_channel *chan); +int ipc_sm_handle(struct apple_isp *isp, struct isp_channel *chan); +int ipc_bt_handle(struct apple_isp *isp, struct isp_channel *chan); + +#endif /* __ISP_IPC_H__ */ diff --git a/drivers/media/platform/apple/isp/isp-regs.h b/drivers/media/platform/apple/isp/isp-regs.h new file mode 100644 index 00000000000000..b9bd505844d9de --- /dev/null +++ b/drivers/media/platform/apple/isp/isp-regs.h @@ -0,0 +1,62 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* Copyright 2023 Eileen Yoon */ + +#ifndef __ISP_REGS_H__ +#define __ISP_REGS_H__ + +#include "isp-drv.h" + +#define ISP_ASC_PMGR_0 0x738 +#define ISP_ASC_PMGR_1 0x798 +#define ISP_ASC_PMGR_2 0x7f8 +#define ISP_ASC_PMGR_3 0x858 + +#define ISP_ASC_RVBAR 0x1050000 +#define ISP_ASC_EDPRCR 0x1010310 +#define ISP_ASC_CONTROL 0x1400044 +#define ISP_ASC_STATUS 0x1400048 + +#define ISP_ASC_IRQ_MASK_0 0x1400a00 +#define ISP_ASC_IRQ_MASK_1 0x1400a04 +#define ISP_ASC_IRQ_MASK_2 0x1400a08 +#define ISP_ASC_IRQ_MASK_3 0x1400a0c +#define ISP_ASC_IRQ_MASK_4 0x1400a10 +#define ISP_ASC_IRQ_MASK_5 0x1400a14 + +#define ISP_CORE_IRQ_INTERRUPT 0x2104000 +#define ISP_CORE_IRQ_ENABLE 0x2104004 +#define ISP_CORE_IRQ_DOORBELL 0x21043f0 +#define ISP_CORE_IRQ_ACK 0x21043fc + +#define ISP_CORE_GPIO_0 0x2104170 +#define ISP_CORE_GPIO_1 0x2104174 +#define ISP_CORE_GPIO_2 0x2104178 +#define ISP_CORE_GPIO_3 0x210417c +#define ISP_CORE_GPIO_4 0x2104180 +#define ISP_CORE_GPIO_5 0x2104184 +#define ISP_CORE_GPIO_6 0x2104188 +#define ISP_CORE_GPIO_7 0x210418c + +#define ISP_CORE_CLOCK_EN 0x2104190 + +#define ISP_CORE_DPE_CTRL_0 0x2504000 +#define ISP_CORE_DPE_CTRL_1 0x2508000 + +static inline u32 isp_core_read32(struct apple_isp *isp, u32 reg) +{ + return readl(isp->core + reg - 0x2104000); // TODO this sucks +} + +static inline void isp_core_write32(struct apple_isp *isp, u32 reg, u32 val) +{ + writel(val, isp->core + reg - 0x2104000); +} + +static inline void isp_core_mask32(struct apple_isp *isp, u32 reg, u32 clear, + u32 set) +{ + isp_core_write32(isp, reg, isp_core_read32(isp, reg) & ~clear); + isp_core_write32(isp, reg, isp_core_read32(isp, reg) | set); +} + +#endif /* __ISP_REGS_H__ */ diff --git a/drivers/media/platform/apple/isp/isp-v4l2.c b/drivers/media/platform/apple/isp/isp-v4l2.c new file mode 100644 index 00000000000000..9de6549ec9bee7 --- /dev/null +++ b/drivers/media/platform/apple/isp/isp-v4l2.c @@ -0,0 +1,602 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* Copyright 2023 Eileen Yoon */ + +#include +#include +#include +#include +#include + +#include "isp-cam.h" +#include "isp-cmd.h" +#include "isp-iommu.h" +#include "isp-ipc.h" +#include "isp-v4l2.h" + +#define ISP_MIN_FRAMES 2 +#define ISP_MAX_PLANES 4 +#define ISP_MAX_PIX_FORMATS 2 +#define ISP_BUFFER_TIMEOUT msecs_to_jiffies(1500) + +struct isp_h2t_buffer { + u64 iovas[ISP_MAX_PLANES]; + u32 flags[ISP_MAX_PLANES]; + u32 num_planes; + u32 pool_type; + u32 tag; + u32 pad; +} __packed; +static_assert(sizeof(struct isp_h2t_buffer) == 0x40); + +struct isp_h2t_args { + u64 enable; + u64 num_buffers; + struct isp_h2t_buffer meta; + struct isp_h2t_buffer render; +} __packed; + +static int isp_submit_buffers(struct apple_isp *isp) +{ + struct isp_format *fmt = isp_get_current_format(isp); + struct isp_channel *chan = isp->chan_bh; + struct isp_message *req = &chan->req; + struct isp_buffer *buf; + unsigned long flags; + size_t offset; + int err; + + struct isp_h2t_args *args = + kzalloc(sizeof(struct isp_h2t_args), GFP_KERNEL); + if (!args) + return -ENOMEM; + + spin_lock_irqsave(&isp->buf_lock, flags); + buf = list_first_entry_or_null(&isp->buffers, struct isp_buffer, link); + if (!buf) { + spin_unlock_irqrestore(&isp->buf_lock, flags); + kfree(args); + return -EPROTO; + } + + args->meta.num_planes = 1; + args->meta.pool_type = CISP_POOL_TYPE_META; + args->meta.iovas[0] = buf->meta->iova; + args->meta.flags[0] = 0x40000000; + + args->render.num_planes = fmt->num_planes; + args->render.pool_type = CISP_POOL_TYPE_RENDERED; + offset = 0; + for (int j = 0; j < fmt->num_planes; j++) { + args->render.iovas[j] = buf->surfs[0].iova + offset; + args->render.flags[j] = 0x40000000; + offset += fmt->plane_size[j]; + } + spin_unlock_irqrestore(&isp->buf_lock, flags); + + args->enable = 0x1; + args->num_buffers = 2; + + req->arg0 = isp->cmd_iova; + req->arg1 = ISP_IPC_BUFEXC_STAT_SIZE; + req->arg2 = ISP_IPC_BUFEXC_FLAG_COMMAND; + + isp_iowrite(isp, req->arg0, args, sizeof(*args)); + err = ipc_chan_send(isp, chan, ISP_BUFFER_TIMEOUT); + if (err) { + dev_err(isp->dev, + "%s: failed to send bufs: [0x%llx, 0x%llx, 0x%llx]\n", + chan->name, req->arg0, req->arg1, req->arg2); + } + + kfree(args); + + return err; +} + +/* + * Videobuf2 section + */ +static int isp_vb2_queue_setup(struct vb2_queue *vq, unsigned int *nbuffers, + unsigned int *num_planes, unsigned int sizes[], + struct device *alloc_devs[]) +{ + struct apple_isp *isp = vb2_get_drv_priv(vq); + struct isp_format *fmt = isp_get_current_format(isp); + + if (*num_planes) { + if (sizes[0] < fmt->total_size) + return -EINVAL; + + return 0; + } + + *num_planes = 1; + sizes[0] = fmt->total_size; + + return 0; +} + +static void __isp_vb2_buf_cleanup(struct vb2_buffer *vb, unsigned int i) +{ + struct apple_isp *isp = vb2_get_drv_priv(vb->vb2_queue); + struct isp_buffer *buf = + container_of(vb, struct isp_buffer, vb.vb2_buf); + + while (i--) + apple_isp_iommu_unmap_sgt(isp, &buf->surfs[i]); + isp_free_surface(isp, buf->meta); +} + +static void isp_vb2_buf_cleanup(struct vb2_buffer *vb) +{ + __isp_vb2_buf_cleanup(vb, vb->num_planes); +} + +static int isp_vb2_buf_init(struct vb2_buffer *vb) +{ + struct apple_isp *isp = vb2_get_drv_priv(vb->vb2_queue); + struct isp_buffer *buf = + container_of(vb, struct isp_buffer, vb.vb2_buf); + unsigned int i; + int err; + + buf->meta = isp_alloc_surface(isp, ISP_META_SIZE); + if (!buf->meta) + return -ENOMEM; + + for (i = 0; i < vb->num_planes; i++) { + struct sg_table *sgt = vb2_dma_sg_plane_desc(vb, i); + err = apple_isp_iommu_map_sgt(isp, &buf->surfs[i], sgt, + vb2_plane_size(vb, i)); + if (err) + goto cleanup; + } + + return 0; + +cleanup: + __isp_vb2_buf_cleanup(vb, i); + return err; +} + +static int isp_vb2_buf_prepare(struct vb2_buffer *vb) +{ + struct apple_isp *isp = vb2_get_drv_priv(vb->vb2_queue); + struct isp_format *fmt = isp_get_current_format(isp); + + if (vb2_plane_size(vb, 0) < fmt->total_size) + return -EINVAL; + + vb2_set_plane_payload(vb, 0, fmt->total_size); + + return 0; +} + +static void isp_vb2_release_buffers(struct apple_isp *isp, + enum vb2_buffer_state state) +{ + struct isp_buffer *buf; + unsigned long flags; + + spin_lock_irqsave(&isp->buf_lock, flags); + list_for_each_entry(buf, &isp->buffers, link) + vb2_buffer_done(&buf->vb.vb2_buf, state); + INIT_LIST_HEAD(&isp->buffers); + spin_unlock_irqrestore(&isp->buf_lock, flags); +} + +static void isp_vb2_buf_queue(struct vb2_buffer *vb) +{ + struct apple_isp *isp = vb2_get_drv_priv(vb->vb2_queue); + struct isp_buffer *buf = + container_of(vb, struct isp_buffer, vb.vb2_buf); + unsigned long flags; + bool empty; + + spin_lock_irqsave(&isp->buf_lock, flags); + empty = list_empty(&isp->buffers); + list_add_tail(&buf->link, &isp->buffers); + spin_unlock_irqrestore(&isp->buf_lock, flags); + + if (test_bit(ISP_STATE_STREAMING, &isp->state) && !empty) + isp_submit_buffers(isp); +} + +static int isp_vb2_start_streaming(struct vb2_queue *q, unsigned int count) +{ + struct apple_isp *isp = vb2_get_drv_priv(q); + int err; + + isp->sequence = 0; + + err = apple_isp_start_camera(isp); + if (err) { + dev_err(isp->dev, "failed to start camera: %d\n", err); + goto release_buffers; + } + + err = isp_submit_buffers(isp); + if (err) { + dev_err(isp->dev, "failed to send initial batch: %d\n", err); + goto stop_camera; + } + + err = apple_isp_start_capture(isp); + if (err) { + dev_err(isp->dev, "failed to start capture: %d\n", err); + goto stop_camera; + } + + set_bit(ISP_STATE_STREAMING, &isp->state); + + return 0; + +stop_camera: + apple_isp_stop_camera(isp); +release_buffers: + isp_vb2_release_buffers(isp, VB2_BUF_STATE_QUEUED); + return err; +} + +static void isp_vb2_stop_streaming(struct vb2_queue *q) +{ + struct apple_isp *isp = vb2_get_drv_priv(q); + + clear_bit(ISP_STATE_STREAMING, &isp->state); + apple_isp_stop_capture(isp); + apple_isp_stop_camera(isp); + isp_vb2_release_buffers(isp, VB2_BUF_STATE_ERROR); +} + +static const struct vb2_ops isp_vb2_ops = { + .queue_setup = isp_vb2_queue_setup, + .buf_init = isp_vb2_buf_init, + .buf_cleanup = isp_vb2_buf_cleanup, + .buf_prepare = isp_vb2_buf_prepare, + .buf_queue = isp_vb2_buf_queue, + .start_streaming = isp_vb2_start_streaming, + .stop_streaming = isp_vb2_stop_streaming, + .wait_prepare = vb2_ops_wait_prepare, + .wait_finish = vb2_ops_wait_finish, +}; + +/* + * V4L2 ioctl section + */ +static int isp_vidioc_querycap(struct file *file, void *priv, + struct v4l2_capability *cap) +{ + strscpy(cap->card, APPLE_ISP_DEVICE_NAME, sizeof(cap->card)); + strscpy(cap->driver, APPLE_ISP_DEVICE_NAME, sizeof(cap->driver)); + + return 0; +} + +static int isp_vidioc_enum_format(struct file *file, void *fh, + struct v4l2_fmtdesc *f) +{ + if (f->index >= ISP_MAX_PIX_FORMATS) + return -EINVAL; + + if (!f->index) + f->pixelformat = V4L2_PIX_FMT_NV12; + else + f->pixelformat = V4L2_PIX_FMT_NV12M; + + return 0; +} + +static int isp_vidioc_enum_framesizes(struct file *file, void *fh, + struct v4l2_frmsizeenum *f) +{ + struct apple_isp *isp = video_drvdata(file); + struct isp_format *fmt = isp_get_current_format(isp); + + if (f->index >= ISP_MAX_PIX_FORMATS) + return -EINVAL; + + if ((!f->index && f->pixel_format != V4L2_PIX_FMT_NV12) || + (f->index && f->pixel_format != V4L2_PIX_FMT_NV12M)) + return -EINVAL; + + f->discrete.width = fmt->width; + f->discrete.height = fmt->height; + f->type = V4L2_FRMSIZE_TYPE_DISCRETE; + + return 0; +} + +static inline void isp_set_sp_pix_format(struct apple_isp *isp, + struct v4l2_format *f) +{ + struct isp_format *fmt = isp_get_current_format(isp); + + f->fmt.pix.width = fmt->width; + f->fmt.pix.height = fmt->height; + f->fmt.pix.sizeimage = fmt->total_size; + + f->fmt.pix.field = V4L2_FIELD_NONE; + f->fmt.pix.pixelformat = V4L2_PIX_FMT_NV12; + f->fmt.pix.colorspace = V4L2_COLORSPACE_REC709; + f->fmt.pix.ycbcr_enc = V4L2_YCBCR_ENC_709; + f->fmt.pix.xfer_func = V4L2_XFER_FUNC_709; +} + +static inline void isp_set_mp_pix_format(struct apple_isp *isp, + struct v4l2_format *f) +{ + struct isp_format *fmt = isp_get_current_format(isp); + + f->fmt.pix_mp.width = fmt->width; + f->fmt.pix_mp.height = fmt->height; + f->fmt.pix_mp.num_planes = fmt->num_planes; + for (int i = 0; i < fmt->num_planes; i++) + f->fmt.pix_mp.plane_fmt[i].sizeimage = fmt->plane_size[i]; + + f->fmt.pix_mp.field = V4L2_FIELD_NONE; + f->fmt.pix_mp.pixelformat = V4L2_PIX_FMT_NV12M; + f->fmt.pix_mp.colorspace = V4L2_COLORSPACE_REC709; + f->fmt.pix_mp.ycbcr_enc = V4L2_YCBCR_ENC_709; + f->fmt.pix_mp.xfer_func = V4L2_XFER_FUNC_709; +} + +static int isp_vidioc_get_format(struct file *file, void *fh, + struct v4l2_format *f) +{ + struct apple_isp *isp = video_drvdata(file); + + if (isp->multiplanar) + return -ENOTTY; + + isp_set_sp_pix_format(isp, f); + + return 0; +} + +static int isp_vidioc_set_format(struct file *file, void *fh, + struct v4l2_format *f) +{ + struct apple_isp *isp = video_drvdata(file); + + if (isp->multiplanar) + return -ENOTTY; + + isp_set_sp_pix_format(isp, f); // no + + return 0; +} + +static int isp_vidioc_try_format(struct file *file, void *fh, + struct v4l2_format *f) +{ + struct apple_isp *isp = video_drvdata(file); + + if (isp->multiplanar) + return -ENOTTY; + + isp_set_sp_pix_format(isp, f); // still no + + return 0; +} + +static int isp_vidioc_get_format_mplane(struct file *file, void *fh, + struct v4l2_format *f) +{ + struct apple_isp *isp = video_drvdata(file); + + if (!isp->multiplanar) + return -ENOTTY; + + isp_set_mp_pix_format(isp, f); + + return 0; +} + +static int isp_vidioc_set_format_mplane(struct file *file, void *fh, + struct v4l2_format *f) +{ + struct apple_isp *isp = video_drvdata(file); + + if (!isp->multiplanar) + return -ENOTTY; + + isp_set_mp_pix_format(isp, f); // no + + return 0; +} + +static int isp_vidioc_try_format_mplane(struct file *file, void *fh, + struct v4l2_format *f) +{ + struct apple_isp *isp = video_drvdata(file); + + if (!isp->multiplanar) + return -ENOTTY; + + isp_set_mp_pix_format(isp, f); // still no + + return 0; +} + +static int isp_vidioc_enum_input(struct file *file, void *fh, + struct v4l2_input *inp) +{ + if (inp->index) + return -EINVAL; + + strscpy(inp->name, APPLE_ISP_DEVICE_NAME, sizeof(inp->name)); + inp->type = V4L2_INPUT_TYPE_CAMERA; + + return 0; +} + +static int isp_vidioc_get_input(struct file *file, void *fh, unsigned int *i) +{ + *i = 0; + + return 0; +} + +static int isp_vidioc_set_input(struct file *file, void *fh, unsigned int i) +{ + if (i) + return -EINVAL; + + return 0; +} + +static int isp_vidioc_get_param(struct file *file, void *fh, + struct v4l2_streamparm *a) +{ + struct apple_isp *isp = video_drvdata(file); + + if (a->type != (isp->multiplanar ? V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE : + V4L2_BUF_TYPE_VIDEO_CAPTURE)) + return -EINVAL; + + a->parm.capture.capability = V4L2_CAP_TIMEPERFRAME; + a->parm.capture.readbuffers = ISP_MIN_FRAMES; + a->parm.capture.timeperframe.numerator = ISP_FRAME_RATE_NUM; + a->parm.capture.timeperframe.denominator = ISP_FRAME_RATE_DEN; + + return 0; +} + +static int isp_vidioc_set_param(struct file *file, void *fh, + struct v4l2_streamparm *a) +{ + struct apple_isp *isp = video_drvdata(file); + + if (a->type != (isp->multiplanar ? V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE : + V4L2_BUF_TYPE_VIDEO_CAPTURE)) + return -EINVAL; + + /* Not supporting frame rate sets. No use. Plus floats. */ + a->parm.capture.timeperframe.numerator = ISP_FRAME_RATE_NUM; + a->parm.capture.timeperframe.denominator = ISP_FRAME_RATE_DEN; + + return 0; +} + +static const struct v4l2_ioctl_ops isp_v4l2_ioctl_ops = { + .vidioc_querycap = isp_vidioc_querycap, + + .vidioc_enum_fmt_vid_cap = isp_vidioc_enum_format, + .vidioc_g_fmt_vid_cap = isp_vidioc_get_format, + .vidioc_s_fmt_vid_cap = isp_vidioc_set_format, + .vidioc_try_fmt_vid_cap = isp_vidioc_try_format, + .vidioc_g_fmt_vid_cap_mplane = isp_vidioc_get_format_mplane, + .vidioc_s_fmt_vid_cap_mplane = isp_vidioc_set_format_mplane, + .vidioc_try_fmt_vid_cap_mplane = isp_vidioc_try_format_mplane, + + .vidioc_enum_framesizes = isp_vidioc_enum_framesizes, + .vidioc_enum_input = isp_vidioc_enum_input, + .vidioc_g_input = isp_vidioc_get_input, + .vidioc_s_input = isp_vidioc_set_input, + .vidioc_g_parm = isp_vidioc_get_param, + .vidioc_s_parm = isp_vidioc_set_param, + + .vidioc_reqbufs = vb2_ioctl_reqbufs, + .vidioc_querybuf = vb2_ioctl_querybuf, + .vidioc_create_bufs = vb2_ioctl_create_bufs, + .vidioc_qbuf = vb2_ioctl_qbuf, + .vidioc_expbuf = vb2_ioctl_expbuf, + .vidioc_dqbuf = vb2_ioctl_dqbuf, + .vidioc_prepare_buf = vb2_ioctl_prepare_buf, + .vidioc_streamon = vb2_ioctl_streamon, + .vidioc_streamoff = vb2_ioctl_streamoff, +}; + +static const struct v4l2_file_operations isp_v4l2_fops = { + .owner = THIS_MODULE, + .open = v4l2_fh_open, + .release = vb2_fop_release, + .read = vb2_fop_read, + .poll = vb2_fop_poll, + .mmap = vb2_fop_mmap, + .unlocked_ioctl = video_ioctl2, +}; + +static const struct media_device_ops isp_media_device_ops = { + .link_notify = v4l2_pipeline_link_notify, +}; + +int apple_isp_setup_video(struct apple_isp *isp) +{ + struct video_device *vdev = &isp->vdev; + struct vb2_queue *vbq = &isp->vbq; + int err; + + media_device_init(&isp->mdev); + isp->v4l2_dev.mdev = &isp->mdev; + isp->mdev.ops = &isp_media_device_ops; + isp->mdev.dev = isp->dev; + strscpy(isp->mdev.model, APPLE_ISP_DEVICE_NAME, sizeof(isp->mdev.model)); + + err = media_device_register(&isp->mdev); + if (err) { + dev_err(isp->dev, "failed to register media device: %d\n", err); + goto media_cleanup; + } + + isp->multiplanar = 0; + + err = v4l2_device_register(isp->dev, &isp->v4l2_dev); + if (err) { + dev_err(isp->dev, "failed to register v4l2 device: %d\n", err); + goto media_unregister; + } + + vbq->drv_priv = isp; + vbq->type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + vbq->io_modes = VB2_MMAP; + vbq->dev = isp->dev; + vbq->ops = &isp_vb2_ops; + vbq->mem_ops = &vb2_dma_sg_memops; + vbq->buf_struct_size = sizeof(struct isp_buffer); + vbq->timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_MONOTONIC; + vbq->min_queued_buffers = ISP_MIN_FRAMES; + vbq->lock = &isp->video_lock; + + err = vb2_queue_init(vbq); + if (err) { + dev_err(isp->dev, "failed to init vb2 queue: %d\n", err); + goto v4l2_unregister; + } + + vdev->queue = vbq; + vdev->fops = &isp_v4l2_fops; + vdev->ioctl_ops = &isp_v4l2_ioctl_ops; + vdev->device_caps = V4L2_BUF_TYPE_VIDEO_CAPTURE | V4L2_CAP_STREAMING; + vdev->v4l2_dev = &isp->v4l2_dev; + vdev->vfl_type = VFL_TYPE_VIDEO; + vdev->vfl_dir = VFL_DIR_RX; + vdev->release = video_device_release_empty; + vdev->lock = &isp->video_lock; + strscpy(vdev->name, APPLE_ISP_DEVICE_NAME, sizeof(vdev->name)); + video_set_drvdata(vdev, isp); + + err = video_register_device(vdev, VFL_TYPE_VIDEO, 0); + if (err) { + dev_err(isp->dev, "failed to register video device: %d\n", err); + goto v4l2_unregister; + } + + return 0; + +v4l2_unregister: + v4l2_device_unregister(&isp->v4l2_dev); +media_unregister: + media_device_unregister(&isp->mdev); +media_cleanup: + media_device_cleanup(&isp->mdev); + return err; +} + +void apple_isp_remove_video(struct apple_isp *isp) +{ + vb2_video_unregister_device(&isp->vdev); + v4l2_device_unregister(&isp->v4l2_dev); + media_device_unregister(&isp->mdev); + media_device_cleanup(&isp->mdev); +} diff --git a/drivers/media/platform/apple/isp/isp-v4l2.h b/drivers/media/platform/apple/isp/isp-v4l2.h new file mode 100644 index 00000000000000..df9b961d77bc17 --- /dev/null +++ b/drivers/media/platform/apple/isp/isp-v4l2.h @@ -0,0 +1,12 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* Copyright 2023 Eileen Yoon */ + +#ifndef __ISP_V4L2_H__ +#define __ISP_V4L2_H__ + +#include "isp-drv.h" + +int apple_isp_setup_video(struct apple_isp *isp); +void apple_isp_remove_video(struct apple_isp *isp); + +#endif /* __ISP_V4L2_H__ */ From 74a320a299d55ed2eedcdbff962a00e1a644d6ff Mon Sep 17 00:00:00 2001 From: Eileen Yoon Date: Sat, 2 Sep 2023 00:47:39 +0900 Subject: [PATCH 0268/3902] media: apple: isp: IMX558 initial support Signed-off-by: Eileen Yoon --- drivers/media/platform/apple/isp/isp-cam.c | 5 +- drivers/media/platform/apple/isp/isp-drv.c | 54 ++++++++++++++++++++++ 2 files changed, 57 insertions(+), 2 deletions(-) diff --git a/drivers/media/platform/apple/isp/isp-cam.c b/drivers/media/platform/apple/isp/isp-cam.c index 6d08248ef44776..bb90337cb7c19f 100644 --- a/drivers/media/platform/apple/isp/isp-cam.c +++ b/drivers/media/platform/apple/isp/isp-cam.c @@ -78,12 +78,13 @@ static const struct isp_setfile isp_setfiles[] = { [ISP_VD56G0_6221_01] = {0xd56, 0x62210102, "isp/6221_01XX.dat", 0x1b80}, [ISP_VD56G0_6222_01] = {0xd56, 0x62220102, "isp/6222_01XX.dat", 0x1b80}, }; -// clang-format on // one day we will do this intelligently static const struct isp_preset isp_presets[] = { - [ISP_IMX248_1820_01] = { 0, 1280, 720, 8, 8, 1280, 720, 1296, 736 }, + [ISP_IMX248_1820_01] = {0, 1280, 720, 8, 8, 1280, 720, 1296, 736}, // J293AP + [ISP_IMX558_1921_01] = {1, 1920, 1080, 0, 0, 1920, 1080, 1920, 1080}, // J316sAP, J415AP }; +// clang-format on static int isp_ch_get_sensor_id(struct apple_isp *isp, u32 ch) { diff --git a/drivers/media/platform/apple/isp/isp-drv.c b/drivers/media/platform/apple/isp/isp-drv.c index e8e32ba73ad962..31aaf1e78b9e98 100644 --- a/drivers/media/platform/apple/isp/isp-drv.c +++ b/drivers/media/platform/apple/isp/isp-drv.c @@ -292,6 +292,60 @@ static const struct apple_isp_hw apple_isp_hw_t8103 = { .stream_command_invalidate = DART_T8020_STREAM_COMMAND_INVALIDATE, }; +static const struct apple_isp_hw apple_isp_hw_t6000 = { + .pmu_base = 0x28e584000, + + .dsid_clr_base0 = 0x200014000, + .dsid_clr_base1 = 0x200054000, + .dsid_clr_base2 = 0x200094000, + .dsid_clr_base3 = 0x2000d4000, + .dsid_clr_range0 = 0x1000, + .dsid_clr_range1 = 0x1000, + .dsid_clr_range2 = 0x1000, + .dsid_clr_range3 = 0x1000, + + .clock_scratch = 0x28e3d0868, + .clock_base = 0x0, + .clock_bit = 0x0, + .clock_size = 0x8, + .bandwidth_scratch = 0x28e3d0980, + .bandwidth_base = 0x0, + .bandwidth_bit = 0x0, + .bandwidth_size = 0x8, + + .stream_command = DART_T8020_STREAM_COMMAND, + .stream_select = DART_T8020_STREAM_SELECT, + .ttbr = DART_T8020_TTBR, + .stream_command_invalidate = DART_T8020_STREAM_COMMAND_INVALIDATE, +}; + +static const struct apple_isp_hw apple_isp_hw_t8110 = { + .pmu_base = 0x23b704000, + + .dsid_clr_base0 = 0x200014000, // TODO + .dsid_clr_base1 = 0x200054000, + .dsid_clr_base2 = 0x200094000, + .dsid_clr_base3 = 0x2000d4000, + .dsid_clr_range0 = 0x1000, + .dsid_clr_range1 = 0x1000, + .dsid_clr_range2 = 0x1000, + .dsid_clr_range3 = 0x1000, + + .clock_scratch = 0x23b3d0560, + .clock_base = 0x0, + .clock_bit = 0x0, + .clock_size = 0x8, + .bandwidth_scratch = 0x23b3d05d0, + .bandwidth_base = 0x0, + .bandwidth_bit = 0x0, + .bandwidth_size = 0x8, + + .stream_command = DART_T8020_STREAM_COMMAND, // TODO + .stream_select = DART_T8020_STREAM_SELECT, + .ttbr = DART_T8020_TTBR, + .stream_command_invalidate = DART_T8020_STREAM_COMMAND_INVALIDATE, +}; + static const struct of_device_id apple_isp_of_match[] = { { .compatible = "apple,t8103-isp", .data = &apple_isp_hw_t8103 }, {}, From 1df23fa38bf20033bfdea5e5068f326b0bddae08 Mon Sep 17 00:00:00 2001 From: Hector Martin Date: Fri, 8 Sep 2023 00:45:36 +0900 Subject: [PATCH 0269/3902] media: apple: isp: Use preallocated heap Signed-off-by: Hector Martin --- drivers/media/platform/apple/isp/isp-drv.c | 51 ++++++++++++---------- drivers/media/platform/apple/isp/isp-drv.h | 2 +- 2 files changed, 29 insertions(+), 24 deletions(-) diff --git a/drivers/media/platform/apple/isp/isp-drv.c b/drivers/media/platform/apple/isp/isp-drv.c index 31aaf1e78b9e98..d02a60bb34b10e 100644 --- a/drivers/media/platform/apple/isp/isp-drv.c +++ b/drivers/media/platform/apple/isp/isp-drv.c @@ -79,30 +79,44 @@ static int apple_isp_attach_genpd(struct apple_isp *isp) static int apple_isp_init_iommu(struct apple_isp *isp) { struct device *dev = isp->dev; - struct isp_firmware *fw = &isp->fw; - u64 heap_base, heap_size, vm_size; + phys_addr_t heap_base; + size_t heap_size; + u64 vm_size; int err; - int i = 0; + int idx; + int size; + struct device_node *mem_node; + const __be32 *maps, *end; isp->domain = iommu_get_domain_for_dev(isp->dev); if (!isp->domain) return -EPROBE_DEFER; isp->shift = __ffs(isp->domain->pgsize_bitmap); - err = of_property_read_u64(dev->of_node, "apple,isp-heap-base", - &heap_base); - if (err) { - dev_err(dev, "failed to read 'apple,isp-heap-base': %d\n", err); - return err; + idx = of_property_match_string(dev->of_node, "memory-region-names", "heap"); + mem_node = of_parse_phandle(dev->of_node, "memory-region", idx); + if (!mem_node) { + dev_err(dev, "No memory-region found for heap\n"); + return -ENODEV; } - err = of_property_read_u64(dev->of_node, "apple,isp-heap-size", - &heap_size); - if (err) { - dev_err(dev, "failed to read 'apple,isp-heap-size': %d\n", err); - return err; + maps = of_get_property(mem_node, "iommu-addresses", &size); + if (!maps || !size) { + dev_err(dev, "No valid iommu-addresses found for heap\n"); + return -ENODEV; + } + + end = maps + size / sizeof(__be32); + + while (maps < end) { + maps++; + maps = of_translate_dma_region(dev->of_node, maps, &heap_base, &heap_size); } + printk("heap: 0x%llx 0x%lx\n", heap_base, heap_size); + + isp->fw.heap_top = heap_base + heap_size; + err = of_property_read_u64(dev->of_node, "apple,dart-vm-size", &vm_size); if (err) { @@ -110,15 +124,7 @@ static int apple_isp_init_iommu(struct apple_isp *isp) return err; } - drm_mm_init(&isp->iovad, heap_base, vm_size - heap_base); - - /* Allocate read-only coprocessor private heap */ - fw->heap = isp_alloc_surface(isp, heap_size); - if (!fw->heap) { - drm_mm_takedown(&isp->iovad); - err = -ENOMEM; - return err; - } + drm_mm_init(&isp->iovad, isp->fw.heap_top, vm_size - heap_base); apple_isp_iommu_sync_ttbr(isp); @@ -127,7 +133,6 @@ static int apple_isp_init_iommu(struct apple_isp *isp) static void apple_isp_free_iommu(struct apple_isp *isp) { - isp_free_surface(isp, isp->fw.heap); drm_mm_takedown(&isp->iovad); } diff --git a/drivers/media/platform/apple/isp/isp-drv.h b/drivers/media/platform/apple/isp/isp-drv.h index 5db64dcc844863..7b463eaef1c9ce 100644 --- a/drivers/media/platform/apple/isp/isp-drv.h +++ b/drivers/media/platform/apple/isp/isp-drv.h @@ -196,7 +196,7 @@ struct apple_isp { struct mutex iovad_lock; struct isp_firmware { - struct isp_surf *heap; + u64 heap_top; } fw; struct isp_surf *ipc_surf; From 0df4131452d22cfd11189ec1d52b7bf23772825f Mon Sep 17 00:00:00 2001 From: Hector Martin Date: Fri, 8 Sep 2023 00:45:49 +0900 Subject: [PATCH 0270/3902] media: apple: isp: Fixup shared region arg Signed-off-by: Hector Martin --- drivers/media/platform/apple/isp/isp-fw.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/drivers/media/platform/apple/isp/isp-fw.c b/drivers/media/platform/apple/isp/isp-fw.c index 12b9c0694d68e8..4315653a0510a0 100644 --- a/drivers/media/platform/apple/isp/isp-fw.c +++ b/drivers/media/platform/apple/isp/isp-fw.c @@ -30,8 +30,8 @@ static inline void isp_asc_write32(struct apple_isp *isp, u32 reg, u32 val) struct isp_firmware_bootargs { u32 pad_0[2]; u64 ipc_iova; - u64 unk_size; - u64 unk_inv; + u64 shared_base; + u64 shared_size; u64 extra_iova; u64 extra_size; u32 unk4; @@ -254,8 +254,8 @@ static int isp_firmware_boot_stage2(struct apple_isp *isp) memset(&args, 0, sizeof(args)); args.ipc_iova = isp->ipc_surf->iova; args.ipc_size = isp->ipc_surf->size; - args.unk_size = 0x1800000; - args.unk_inv = 0x10000000 - args.unk_size; + args.shared_base = isp->fw.heap_top; + args.shared_size = 0x10000000 - isp->fw.heap_top; args.extra_iova = isp->extra_surf->iova; args.extra_size = isp->extra_surf->size; args.unk4 = 0x1; From cd22f6d43e9a967b6b052341fd25e403c20bfc8c Mon Sep 17 00:00:00 2001 From: Hector Martin Date: Sun, 10 Sep 2023 22:57:06 +0900 Subject: [PATCH 0271/3902] media: apple: isp: Enable t6000 Signed-off-by: Hector Martin --- drivers/media/platform/apple/isp/isp-drv.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/media/platform/apple/isp/isp-drv.c b/drivers/media/platform/apple/isp/isp-drv.c index d02a60bb34b10e..094af7f7c33523 100644 --- a/drivers/media/platform/apple/isp/isp-drv.c +++ b/drivers/media/platform/apple/isp/isp-drv.c @@ -353,6 +353,7 @@ static const struct apple_isp_hw apple_isp_hw_t8110 = { static const struct of_device_id apple_isp_of_match[] = { { .compatible = "apple,t8103-isp", .data = &apple_isp_hw_t8103 }, + { .compatible = "apple,t6000-isp", .data = &apple_isp_hw_t6000 }, {}, }; MODULE_DEVICE_TABLE(of, apple_isp_of_match); From a68b3cd0a479f184cf4a3ab7ed92e9bd19ddb4bd Mon Sep 17 00:00:00 2001 From: Eileen Yoon Date: Sat, 2 Sep 2023 01:07:08 +0900 Subject: [PATCH 0272/3902] media: apple: isp: Split gpio/mbox MMIO range Offsets differ across socs. Makes more sense than "core" too. Signed-off-by: Eileen Yoon --- drivers/media/platform/apple/isp/isp-drv.c | 12 +++- drivers/media/platform/apple/isp/isp-drv.h | 3 +- drivers/media/platform/apple/isp/isp-fw.c | 76 ++++++++++++--------- drivers/media/platform/apple/isp/isp-ipc.c | 4 +- drivers/media/platform/apple/isp/isp-regs.h | 49 ++++++------- 5 files changed, 75 insertions(+), 69 deletions(-) diff --git a/drivers/media/platform/apple/isp/isp-drv.c b/drivers/media/platform/apple/isp/isp-drv.c index 094af7f7c33523..eb585d37d3239f 100644 --- a/drivers/media/platform/apple/isp/isp-drv.c +++ b/drivers/media/platform/apple/isp/isp-drv.c @@ -164,9 +164,15 @@ static int apple_isp_probe(struct platform_device *pdev) goto detach_genpd; } - isp->core = devm_platform_ioremap_resource_byname(pdev, "core"); - if (IS_ERR(isp->core)) { - err = PTR_ERR(isp->core); + isp->mbox = devm_platform_ioremap_resource_byname(pdev, "mbox"); + if (IS_ERR(isp->mbox)) { + err = PTR_ERR(isp->mbox); + goto detach_genpd; + } + + isp->gpio = devm_platform_ioremap_resource_byname(pdev, "gpio"); + if (IS_ERR(isp->gpio)) { + err = PTR_ERR(isp->gpio); goto detach_genpd; } diff --git a/drivers/media/platform/apple/isp/isp-drv.h b/drivers/media/platform/apple/isp/isp-drv.h index 7b463eaef1c9ce..de9b3fd2def5ee 100644 --- a/drivers/media/platform/apple/isp/isp-drv.h +++ b/drivers/media/platform/apple/isp/isp-drv.h @@ -185,7 +185,8 @@ struct apple_isp { int irq; void __iomem *asc; - void __iomem *core; + void __iomem *mbox; + void __iomem *gpio; void __iomem *dart0; void __iomem *dart1; void __iomem *dart2; diff --git a/drivers/media/platform/apple/isp/isp-fw.c b/drivers/media/platform/apple/isp/isp-fw.c index 4315653a0510a0..1f01d175416174 100644 --- a/drivers/media/platform/apple/isp/isp-fw.c +++ b/drivers/media/platform/apple/isp/isp-fw.c @@ -27,6 +27,16 @@ static inline void isp_asc_write32(struct apple_isp *isp, u32 reg, u32 val) writel(val, isp->asc + reg); } +static inline u32 isp_gpio_read32(struct apple_isp *isp, u32 reg) +{ + return readl(isp->gpio + reg); +} + +static inline void isp_gpio_write32(struct apple_isp *isp, u32 reg, u32 val) +{ + writel(val, isp->gpio + reg); +} + struct isp_firmware_bootargs { u32 pad_0[2]; u64 ipc_iova; @@ -77,8 +87,8 @@ static irqreturn_t apple_isp_isr(int irq, void *dev) { struct apple_isp *isp = dev; - isp_core_write32(isp, ISP_CORE_IRQ_ACK, - isp_core_read32(isp, ISP_CORE_IRQ_INTERRUPT)); + isp_mbox_write32(isp, ISP_MBOX_IRQ_ACK, + isp_mbox_read32(isp, ISP_MBOX_IRQ_INTERRUPT)); wake_up_interruptible_all(&isp->wait); @@ -95,9 +105,9 @@ static irqreturn_t apple_isp_isr(int irq, void *dev) static void isp_disable_irq(struct apple_isp *isp) { - isp_core_write32(isp, ISP_CORE_IRQ_ENABLE, 0x0); + isp_mbox_write32(isp, ISP_MBOX_IRQ_ENABLE, 0x0); free_irq(isp->irq, isp); - isp_core_write32(isp, ISP_CORE_GPIO_1, 0xfeedbabe); /* real funny */ + isp_gpio_write32(isp, ISP_GPIO_1, 0xfeedbabe); /* real funny */ } static int isp_enable_irq(struct apple_isp *isp) @@ -112,7 +122,7 @@ static int isp_enable_irq(struct apple_isp *isp) isp_dbg(isp, "about to enable interrupts...\n"); - isp_core_write32(isp, ISP_CORE_IRQ_ENABLE, 0xf); + isp_mbox_write32(isp, ISP_MBOX_IRQ_ENABLE, 0xf); return 0; } @@ -166,26 +176,26 @@ static int isp_firmware_boot_stage1(struct apple_isp *isp) if (err < 0) return err; - isp_core_write32(isp, ISP_CORE_CLOCK_EN, 0x1); + isp_gpio_write32(isp, ISP_GPIO_CLOCK_EN, 0x1); - isp_core_write32(isp, ISP_CORE_GPIO_0, 0x0); - isp_core_write32(isp, ISP_CORE_GPIO_1, 0x0); - isp_core_write32(isp, ISP_CORE_GPIO_2, 0x0); - isp_core_write32(isp, ISP_CORE_GPIO_3, 0x0); - isp_core_write32(isp, ISP_CORE_GPIO_4, 0x0); - isp_core_write32(isp, ISP_CORE_GPIO_5, 0x0); - isp_core_write32(isp, ISP_CORE_GPIO_6, 0x0); - isp_core_write32(isp, ISP_CORE_GPIO_7, 0x0); + isp_gpio_write32(isp, ISP_GPIO_0, 0x0); + isp_gpio_write32(isp, ISP_GPIO_1, 0x0); + isp_gpio_write32(isp, ISP_GPIO_2, 0x0); + isp_gpio_write32(isp, ISP_GPIO_3, 0x0); + isp_gpio_write32(isp, ISP_GPIO_4, 0x0); + isp_gpio_write32(isp, ISP_GPIO_5, 0x0); + isp_gpio_write32(isp, ISP_GPIO_6, 0x0); + isp_gpio_write32(isp, ISP_GPIO_7, 0x0); - isp_core_write32(isp, ISP_CORE_IRQ_ENABLE, 0x0); + isp_mbox_write32(isp, ISP_MBOX_IRQ_ENABLE, 0x0); isp_asc_write32(isp, ISP_ASC_CONTROL, 0x0); isp_asc_write32(isp, ISP_ASC_CONTROL, 0x10); - /* Wait for ISP_CORE_GPIO_7 to 0x0 -> 0x8042006 */ - isp_core_write32(isp, ISP_CORE_GPIO_7, 0x0); + /* Wait for ISP_GPIO_7 to 0x0 -> 0x8042006 */ + isp_gpio_write32(isp, ISP_GPIO_7, 0x0); for (retries = 0; retries < ISP_FIRMWARE_MAX_TRIES; retries++) { - u32 val = isp_core_read32(isp, ISP_CORE_GPIO_7); + u32 val = isp_gpio_read32(isp, ISP_GPIO_7); if (val == 0x8042006) { isp_dbg(isp, "got first magic number (0x%x) from firmware\n", @@ -216,9 +226,9 @@ static int isp_firmware_boot_stage2(struct apple_isp *isp) dma_addr_t args_iova; int err, retries; - u32 num_ipc_chans = isp_core_read32(isp, ISP_CORE_GPIO_0); - u32 args_offset = isp_core_read32(isp, ISP_CORE_GPIO_1); - u32 extra_size = isp_core_read32(isp, ISP_CORE_GPIO_3); + u32 num_ipc_chans = isp_gpio_read32(isp, ISP_GPIO_0); + u32 args_offset = isp_gpio_read32(isp, ISP_GPIO_1); + u32 extra_size = isp_gpio_read32(isp, ISP_GPIO_3); isp->num_ipc_chans = num_ipc_chans; if (!isp->num_ipc_chans) { @@ -265,14 +275,14 @@ static int isp_firmware_boot_stage2(struct apple_isp *isp) args.unk9 = 0x3; isp_iowrite(isp, args_iova, &args, sizeof(args)); - isp_core_write32(isp, ISP_CORE_GPIO_0, args_iova); - isp_core_write32(isp, ISP_CORE_GPIO_1, 0x0); + isp_gpio_write32(isp, ISP_GPIO_0, args_iova); + isp_gpio_write32(isp, ISP_GPIO_1, 0x0); - /* Wait for ISP_CORE_GPIO_7 to 0xf7fbdff9 -> 0x8042006 */ - isp_core_write32(isp, ISP_CORE_GPIO_7, 0xf7fbdff9); + /* Wait for ISP_GPIO_7 to 0xf7fbdff9 -> 0x8042006 */ + isp_gpio_write32(isp, ISP_GPIO_7, 0xf7fbdff9); for (retries = 0; retries < ISP_FIRMWARE_MAX_TRIES; retries++) { - u32 val = isp_core_read32(isp, ISP_CORE_GPIO_7); + u32 val = isp_gpio_read32(isp, ISP_GPIO_7); if (val == 0x8042006) { isp_dbg(isp, "got second magic number (0x%x) from firmware\n", @@ -325,7 +335,7 @@ static void isp_free_channel_info(struct apple_isp *isp) static int isp_fill_channel_info(struct apple_isp *isp) { - u32 table_iova = isp_core_read32(isp, ISP_CORE_GPIO_0); + u32 table_iova = isp_gpio_read32(isp, ISP_GPIO_0); isp->ipc_chans = kcalloc(isp->num_ipc_chans, sizeof(struct isp_channel *), GFP_KERNEL); @@ -417,11 +427,11 @@ static int isp_firmware_boot_stage3(struct apple_isp *isp) } } - /* Wait for ISP_CORE_GPIO_3 to 0x8042006 -> 0x0 */ - isp_core_write32(isp, ISP_CORE_GPIO_3, 0x8042006); + /* Wait for ISP_GPIO_3 to 0x8042006 -> 0x0 */ + isp_gpio_write32(isp, ISP_GPIO_3, 0x8042006); for (retries = 0; retries < ISP_FIRMWARE_MAX_TRIES; retries++) { - u32 val = isp_core_read32(isp, ISP_CORE_GPIO_3); + u32 val = isp_gpio_read32(isp, ISP_GPIO_3); if (val == 0x0) { isp_dbg(isp, "got third magic number (0x%x) from firmware\n", @@ -446,14 +456,14 @@ static int isp_stop_command_processor(struct apple_isp *isp) { int retries; - /* Wait for ISP_CORE_GPIO_0 to 0xf7fbdff9 -> 0x8042006 */ - isp_core_write32(isp, ISP_CORE_GPIO_0, 0xf7fbdff9); + /* Wait for ISP_GPIO_0 to 0xf7fbdff9 -> 0x8042006 */ + isp_gpio_write32(isp, ISP_GPIO_0, 0xf7fbdff9); /* Their CISP_CMD_STOP implementation is buggy */ isp_cmd_suspend(isp); for (retries = 0; retries < ISP_FIRMWARE_MAX_TRIES; retries++) { - u32 val = isp_core_read32(isp, ISP_CORE_GPIO_0); + u32 val = isp_gpio_read32(isp, ISP_GPIO_0); if (val == 0x8042006) { isp_dbg(isp, "got magic number (0x%x) from firmware\n", val); diff --git a/drivers/media/platform/apple/isp/isp-ipc.c b/drivers/media/platform/apple/isp/isp-ipc.c index ef3498c4fcd191..a9a0fdb73a4d9f 100644 --- a/drivers/media/platform/apple/isp/isp-ipc.c +++ b/drivers/media/platform/apple/isp/isp-ipc.c @@ -110,7 +110,7 @@ static int chan_handle_once(struct apple_isp *isp, struct isp_channel *chan) chan_write_msg(isp, chan, &chan->rsp); - isp_core_write32(isp, ISP_CORE_IRQ_DOORBELL, chan->doorbell); + isp_mbox_write32(isp, ISP_MBOX_IRQ_DOORBELL, chan->doorbell); chan_update_cursor(chan); @@ -165,7 +165,7 @@ int ipc_chan_send(struct apple_isp *isp, struct isp_channel *chan, chan_write_msg(isp, chan, &chan->req); wmb(); - isp_core_write32(isp, ISP_CORE_IRQ_DOORBELL, chan->doorbell); + isp_mbox_write32(isp, ISP_MBOX_IRQ_DOORBELL, chan->doorbell); t = wait_event_interruptible_timeout(isp->wait, chan_tx_done(isp, chan), timeout); diff --git a/drivers/media/platform/apple/isp/isp-regs.h b/drivers/media/platform/apple/isp/isp-regs.h index b9bd505844d9de..e21485ec4ce823 100644 --- a/drivers/media/platform/apple/isp/isp-regs.h +++ b/drivers/media/platform/apple/isp/isp-regs.h @@ -23,40 +23,29 @@ #define ISP_ASC_IRQ_MASK_4 0x1400a10 #define ISP_ASC_IRQ_MASK_5 0x1400a14 -#define ISP_CORE_IRQ_INTERRUPT 0x2104000 -#define ISP_CORE_IRQ_ENABLE 0x2104004 -#define ISP_CORE_IRQ_DOORBELL 0x21043f0 -#define ISP_CORE_IRQ_ACK 0x21043fc - -#define ISP_CORE_GPIO_0 0x2104170 -#define ISP_CORE_GPIO_1 0x2104174 -#define ISP_CORE_GPIO_2 0x2104178 -#define ISP_CORE_GPIO_3 0x210417c -#define ISP_CORE_GPIO_4 0x2104180 -#define ISP_CORE_GPIO_5 0x2104184 -#define ISP_CORE_GPIO_6 0x2104188 -#define ISP_CORE_GPIO_7 0x210418c - -#define ISP_CORE_CLOCK_EN 0x2104190 - -#define ISP_CORE_DPE_CTRL_0 0x2504000 -#define ISP_CORE_DPE_CTRL_1 0x2508000 - -static inline u32 isp_core_read32(struct apple_isp *isp, u32 reg) +#define ISP_MBOX_IRQ_INTERRUPT 0x000 +#define ISP_MBOX_IRQ_ENABLE 0x004 +#define ISP_MBOX_IRQ_DOORBELL 0x3f0 +#define ISP_MBOX_IRQ_ACK 0x3fc + +#define ISP_GPIO_0 0x00 +#define ISP_GPIO_1 0x04 +#define ISP_GPIO_2 0x08 +#define ISP_GPIO_3 0x0c +#define ISP_GPIO_4 0x10 +#define ISP_GPIO_5 0x14 +#define ISP_GPIO_6 0x18 +#define ISP_GPIO_7 0x1c +#define ISP_GPIO_CLOCK_EN 0x20 + +static inline u32 isp_mbox_read32(struct apple_isp *isp, u32 reg) { - return readl(isp->core + reg - 0x2104000); // TODO this sucks + return readl(isp->mbox + reg); } -static inline void isp_core_write32(struct apple_isp *isp, u32 reg, u32 val) +static inline void isp_mbox_write32(struct apple_isp *isp, u32 reg, u32 val) { - writel(val, isp->core + reg - 0x2104000); -} - -static inline void isp_core_mask32(struct apple_isp *isp, u32 reg, u32 clear, - u32 set) -{ - isp_core_write32(isp, reg, isp_core_read32(isp, reg) & ~clear); - isp_core_write32(isp, reg, isp_core_read32(isp, reg) | set); + writel(val, isp->mbox + reg); } #endif /* __ISP_REGS_H__ */ From d22a9191ac9b7a4eb9fe46142605de00d74479d8 Mon Sep 17 00:00:00 2001 From: Hector Martin Date: Sun, 10 Sep 2023 23:36:12 +0900 Subject: [PATCH 0273/3902] media: apple: isp: Drop the DART mirroring stuff Signed-off-by: Hector Martin --- drivers/media/platform/apple/isp/isp-drv.c | 57 -------------------- drivers/media/platform/apple/isp/isp-drv.h | 8 --- drivers/media/platform/apple/isp/isp-iommu.c | 19 ------- drivers/media/platform/apple/isp/isp-iommu.h | 3 -- 4 files changed, 87 deletions(-) diff --git a/drivers/media/platform/apple/isp/isp-drv.c b/drivers/media/platform/apple/isp/isp-drv.c index eb585d37d3239f..1829f36acdd5b8 100644 --- a/drivers/media/platform/apple/isp/isp-drv.c +++ b/drivers/media/platform/apple/isp/isp-drv.c @@ -126,8 +126,6 @@ static int apple_isp_init_iommu(struct apple_isp *isp) drm_mm_init(&isp->iovad, isp->fw.heap_top, vm_size - heap_base); - apple_isp_iommu_sync_ttbr(isp); - return 0; } @@ -140,7 +138,6 @@ static int apple_isp_probe(struct platform_device *pdev) { struct device *dev = &pdev->dev; struct apple_isp *isp; - struct resource *res; int err; isp = devm_kzalloc(dev, sizeof(*isp), GFP_KERNEL); @@ -176,31 +173,6 @@ static int apple_isp_probe(struct platform_device *pdev) goto detach_genpd; } - res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "dart0"); - if (!res) { - err = -ENODEV; - goto detach_genpd; - } - - /* Simply ioremap since it's a shared register zone */ - isp->dart0 = devm_ioremap(dev, res->start, resource_size(res)); - if (IS_ERR(isp->dart0)) { - err = PTR_ERR(isp->dart0); - goto detach_genpd; - } - - isp->dart1 = devm_platform_ioremap_resource_byname(pdev, "dart1"); - if (IS_ERR(isp->dart1)) { - err = PTR_ERR(isp->dart1); - goto detach_genpd; - } - - isp->dart2 = devm_platform_ioremap_resource_byname(pdev, "dart2"); - if (IS_ERR(isp->dart2)) { - err = PTR_ERR(isp->dart2); - goto detach_genpd; - } - isp->irq = platform_get_irq(pdev, 0); if (isp->irq < 0) { err = isp->irq; @@ -270,12 +242,6 @@ static void apple_isp_remove(struct platform_device *pdev) return 0; } -/* T8020/T6000 registers */ -#define DART_T8020_STREAM_COMMAND 0x20 -#define DART_T8020_STREAM_SELECT 0x34 -#define DART_T8020_TTBR 0x200 -#define DART_T8020_STREAM_COMMAND_INVALIDATE BIT(20) - static const struct apple_isp_hw apple_isp_hw_t8103 = { .pmu_base = 0x23b704000, @@ -296,11 +262,6 @@ static const struct apple_isp_hw apple_isp_hw_t8103 = { .bandwidth_base = 0x23bc3c000, .bandwidth_bit = 0x0, .bandwidth_size = 0x4, - - .stream_command = DART_T8020_STREAM_COMMAND, - .stream_select = DART_T8020_STREAM_SELECT, - .ttbr = DART_T8020_TTBR, - .stream_command_invalidate = DART_T8020_STREAM_COMMAND_INVALIDATE, }; static const struct apple_isp_hw apple_isp_hw_t6000 = { @@ -323,11 +284,6 @@ static const struct apple_isp_hw apple_isp_hw_t6000 = { .bandwidth_base = 0x0, .bandwidth_bit = 0x0, .bandwidth_size = 0x8, - - .stream_command = DART_T8020_STREAM_COMMAND, - .stream_select = DART_T8020_STREAM_SELECT, - .ttbr = DART_T8020_TTBR, - .stream_command_invalidate = DART_T8020_STREAM_COMMAND_INVALIDATE, }; static const struct apple_isp_hw apple_isp_hw_t8110 = { @@ -350,11 +306,6 @@ static const struct apple_isp_hw apple_isp_hw_t8110 = { .bandwidth_base = 0x0, .bandwidth_bit = 0x0, .bandwidth_size = 0x8, - - .stream_command = DART_T8020_STREAM_COMMAND, // TODO - .stream_select = DART_T8020_STREAM_SELECT, - .ttbr = DART_T8020_TTBR, - .stream_command_invalidate = DART_T8020_STREAM_COMMAND_INVALIDATE, }; static const struct of_device_id apple_isp_of_match[] = { @@ -366,19 +317,11 @@ MODULE_DEVICE_TABLE(of, apple_isp_of_match); static __maybe_unused int apple_isp_suspend(struct device *dev) { - struct apple_isp *isp = dev_get_drvdata(dev); - - apple_isp_iommu_invalidate_tlb(isp); - return 0; } static __maybe_unused int apple_isp_resume(struct device *dev) { - struct apple_isp *isp = dev_get_drvdata(dev); - - apple_isp_iommu_sync_ttbr(isp); - return 0; } DEFINE_RUNTIME_DEV_PM_OPS(apple_isp_pm_ops, apple_isp_suspend, apple_isp_resume, NULL); diff --git a/drivers/media/platform/apple/isp/isp-drv.h b/drivers/media/platform/apple/isp/isp-drv.h index de9b3fd2def5ee..bf3824cc0636b9 100644 --- a/drivers/media/platform/apple/isp/isp-drv.h +++ b/drivers/media/platform/apple/isp/isp-drv.h @@ -82,11 +82,6 @@ struct apple_isp_hw { u64 bandwidth_base; u8 bandwidth_bit; u8 bandwidth_size; - - u32 stream_command; - u32 stream_select; - u32 ttbr; - u32 stream_command_invalidate; }; struct isp_resv { @@ -187,9 +182,6 @@ struct apple_isp { void __iomem *asc; void __iomem *mbox; void __iomem *gpio; - void __iomem *dart0; - void __iomem *dart1; - void __iomem *dart2; struct iommu_domain *domain; unsigned long shift; diff --git a/drivers/media/platform/apple/isp/isp-iommu.c b/drivers/media/platform/apple/isp/isp-iommu.c index 28935d37205024..0a9d0d6a350c9a 100644 --- a/drivers/media/platform/apple/isp/isp-iommu.c +++ b/drivers/media/platform/apple/isp/isp-iommu.c @@ -6,23 +6,6 @@ #include "isp-iommu.h" -void apple_isp_iommu_sync_ttbr(struct apple_isp *isp) -{ - writel(readl(isp->dart0 + isp->hw->ttbr), isp->dart1 + isp->hw->ttbr); - writel(readl(isp->dart0 + isp->hw->ttbr), isp->dart2 + isp->hw->ttbr); -} - -void apple_isp_iommu_invalidate_tlb(struct apple_isp *isp) -{ - iommu_flush_iotlb_all(isp->domain); - writel(0x1, isp->dart1 + isp->hw->stream_select); - writel(isp->hw->stream_command_invalidate, - isp->dart1 + isp->hw->stream_command); - writel(0x1, isp->dart2 + isp->hw->stream_select); - writel(isp->hw->stream_command_invalidate, - isp->dart2 + isp->hw->stream_command); -} - static void isp_surf_free_pages(struct isp_surf *surf) { for (u32 i = 0; i < surf->num_pages && surf->pages[i] != NULL; i++) { @@ -113,7 +96,6 @@ static int isp_surf_reserve_iova(struct apple_isp *isp, struct isp_surf *surf) static void isp_surf_iommu_unmap(struct apple_isp *isp, struct isp_surf *surf) { iommu_unmap(isp->domain, surf->iova, surf->size); - apple_isp_iommu_invalidate_tlb(isp); sg_free_table(&surf->sgt); } @@ -270,6 +252,5 @@ int apple_isp_iommu_map_sgt(struct apple_isp *isp, struct isp_surf *surf, void apple_isp_iommu_unmap_sgt(struct apple_isp *isp, struct isp_surf *surf) { iommu_unmap(isp->domain, surf->iova, surf->size); - apple_isp_iommu_invalidate_tlb(isp); isp_surf_unreserve_iova(isp, surf); } diff --git a/drivers/media/platform/apple/isp/isp-iommu.h b/drivers/media/platform/apple/isp/isp-iommu.h index f9972bd9ff93e7..326cf7c12aa745 100644 --- a/drivers/media/platform/apple/isp/isp-iommu.h +++ b/drivers/media/platform/apple/isp/isp-iommu.h @@ -6,9 +6,6 @@ #include "isp-drv.h" -void apple_isp_iommu_sync_ttbr(struct apple_isp *isp); -void apple_isp_iommu_invalidate_tlb(struct apple_isp *isp); - struct isp_surf *__isp_alloc_surface(struct apple_isp *isp, u64 size, bool gc); #define isp_alloc_surface(isp, size) (__isp_alloc_surface(isp, size, false)) #define isp_alloc_surface_gc(isp, size) (__isp_alloc_surface(isp, size, true)) From 57783fbeff42085af6568c128491b0b22b25fe55 Mon Sep 17 00:00:00 2001 From: Hector Martin Date: Mon, 11 Sep 2023 00:12:11 +0900 Subject: [PATCH 0274/3902] media: apple: isp: Do not defer on failure to initialize DART This can fail for non-DEFER reasons. If this can happen due to probe defers, we need to figure out some way to signal that specifically... Signed-off-by: Hector Martin --- drivers/media/platform/apple/isp/isp-drv.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/media/platform/apple/isp/isp-drv.c b/drivers/media/platform/apple/isp/isp-drv.c index 1829f36acdd5b8..00299fd89e6038 100644 --- a/drivers/media/platform/apple/isp/isp-drv.c +++ b/drivers/media/platform/apple/isp/isp-drv.c @@ -90,7 +90,7 @@ static int apple_isp_init_iommu(struct apple_isp *isp) isp->domain = iommu_get_domain_for_dev(isp->dev); if (!isp->domain) - return -EPROBE_DEFER; + return -ENODEV; isp->shift = __ffs(isp->domain->pgsize_bitmap); idx = of_property_match_string(dev->of_node, "memory-region-names", "heap"); From 8e16e278004523e517d30e63fbd790622df0e964 Mon Sep 17 00:00:00 2001 From: Hector Martin Date: Mon, 11 Sep 2023 02:06:05 +0900 Subject: [PATCH 0275/3902] media: apple: WIP: t6000 hax --- drivers/media/platform/apple/isp/isp-cam.c | 2 +- drivers/media/platform/apple/isp/isp-fw.c | 6 +++++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/drivers/media/platform/apple/isp/isp-cam.c b/drivers/media/platform/apple/isp/isp-cam.c index bb90337cb7c19f..74125b3c652433 100644 --- a/drivers/media/platform/apple/isp/isp-cam.c +++ b/drivers/media/platform/apple/isp/isp-cam.c @@ -207,7 +207,7 @@ static int isp_ch_cache_sensor_info(struct apple_isp *isp, u32 ch) args, sizeof(*args), false); err = isp_ch_get_sensor_id(isp, ch); - if (err || (fmt->id != ISP_IMX248_1820_01)) { + if (err || (fmt->id != ISP_IMX248_1820_01 && fmt->id != ISP_IMX558_1921_01)) { dev_err(isp->dev, "ch %d: unsupported sensor. Please file a bug report with hardware info & dmesg trace.\n", ch); diff --git a/drivers/media/platform/apple/isp/isp-fw.c b/drivers/media/platform/apple/isp/isp-fw.c index 1f01d175416174..2fc91a9c434e0e 100644 --- a/drivers/media/platform/apple/isp/isp-fw.c +++ b/drivers/media/platform/apple/isp/isp-fw.c @@ -268,8 +268,12 @@ static int isp_firmware_boot_stage2(struct apple_isp *isp) args.shared_size = 0x10000000 - isp->fw.heap_top; args.extra_iova = isp->extra_surf->iova; args.extra_size = isp->extra_surf->size; - args.unk4 = 0x1; + args.unk4 = 0x3; + //args.pad_40[1] = 0x3128000; + //args.pad_40[3] = 0x48000; + args.pad_40[5] = 0x90; args.unk5 = 0x40; + //args.pad_7c[3] = 0x3b54000; args.unk7 = 0x1; args.unk_iova1 = args_iova + ISP_FIRMWARE_BOOTARGS_SIZE - 0xc; args.unk9 = 0x3; From 3a902bb22a2819d5b83c6b892c0b067fe3371989 Mon Sep 17 00:00:00 2001 From: Eileen Yoon Date: Tue, 12 Sep 2023 17:58:26 +0900 Subject: [PATCH 0276/3902] media: apple: isp: Set platform_id in bootargs Signed-off-by: Eileen Yoon --- drivers/media/platform/apple/isp/isp-drv.c | 3 +++ drivers/media/platform/apple/isp/isp-drv.h | 1 + drivers/media/platform/apple/isp/isp-fw.c | 5 ++--- 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/drivers/media/platform/apple/isp/isp-drv.c b/drivers/media/platform/apple/isp/isp-drv.c index 00299fd89e6038..8e6a846a867d00 100644 --- a/drivers/media/platform/apple/isp/isp-drv.c +++ b/drivers/media/platform/apple/isp/isp-drv.c @@ -243,6 +243,7 @@ static void apple_isp_remove(struct platform_device *pdev) } static const struct apple_isp_hw apple_isp_hw_t8103 = { + .platform_id = 0x1, .pmu_base = 0x23b704000, .dsid_clr_base0 = 0x200014000, @@ -265,6 +266,7 @@ static const struct apple_isp_hw apple_isp_hw_t8103 = { }; static const struct apple_isp_hw apple_isp_hw_t6000 = { + .platform_id = 0x3, .pmu_base = 0x28e584000, .dsid_clr_base0 = 0x200014000, @@ -287,6 +289,7 @@ static const struct apple_isp_hw apple_isp_hw_t6000 = { }; static const struct apple_isp_hw apple_isp_hw_t8110 = { + .platform_id = 0xe, // J413AP .pmu_base = 0x23b704000, .dsid_clr_base0 = 0x200014000, // TODO diff --git a/drivers/media/platform/apple/isp/isp-drv.h b/drivers/media/platform/apple/isp/isp-drv.h index bf3824cc0636b9..fb7a785b87c1c5 100644 --- a/drivers/media/platform/apple/isp/isp-drv.h +++ b/drivers/media/platform/apple/isp/isp-drv.h @@ -63,6 +63,7 @@ struct isp_channel { }; struct apple_isp_hw { + u32 platform_id; u64 pmu_base; u64 dsid_clr_base0; diff --git a/drivers/media/platform/apple/isp/isp-fw.c b/drivers/media/platform/apple/isp/isp-fw.c index 2fc91a9c434e0e..06e4d64cf05e73 100644 --- a/drivers/media/platform/apple/isp/isp-fw.c +++ b/drivers/media/platform/apple/isp/isp-fw.c @@ -44,7 +44,7 @@ struct isp_firmware_bootargs { u64 shared_size; u64 extra_iova; u64 extra_size; - u32 unk4; + u32 platform_id; u32 pad_40[7]; u32 ipc_size; u32 pad_60[5]; @@ -268,10 +268,9 @@ static int isp_firmware_boot_stage2(struct apple_isp *isp) args.shared_size = 0x10000000 - isp->fw.heap_top; args.extra_iova = isp->extra_surf->iova; args.extra_size = isp->extra_surf->size; - args.unk4 = 0x3; + args.platform_id = isp->hw->platform_id; //args.pad_40[1] = 0x3128000; //args.pad_40[3] = 0x48000; - args.pad_40[5] = 0x90; args.unk5 = 0x40; //args.pad_7c[3] = 0x3b54000; args.unk7 = 0x1; From 0e8913b9352a6bbad208bfc84895c1b82abc9d6c Mon Sep 17 00:00:00 2001 From: Eileen Yoon Date: Tue, 12 Sep 2023 18:49:25 +0900 Subject: [PATCH 0277/3902] media: apple: isp: Better document info struct fields "Document". I also counted wrong multiple times. Signed-off-by: Eileen Yoon --- drivers/media/platform/apple/isp/isp-cmd.h | 64 +++++++++++++++++++--- 1 file changed, 55 insertions(+), 9 deletions(-) diff --git a/drivers/media/platform/apple/isp/isp-cmd.h b/drivers/media/platform/apple/isp/isp-cmd.h index dde6aad506c23e..1fc484fa687853 100644 --- a/drivers/media/platform/apple/isp/isp-cmd.h +++ b/drivers/media/platform/apple/isp/isp-cmd.h @@ -202,19 +202,53 @@ static_assert(sizeof(struct cmd_ch_stop) == 0xc); struct cmd_ch_info { u64 opcode; u32 chan; - u32 unk_c; - u32 unk_10[4]; + u32 unk_c; // 0x7da0001, 0x7db0001 + u32 unk_10; // 0x300ac, 0x5006d + u32 unk_14; // 0x40007, 0x10007 + u32 unk_18; // 0x5, 0x2 + u32 unk_1c; // 0x1, 0x1 u32 version; - u32 unk_24[3]; - u32 unk_30[12]; + u32 unk_24; // 0x7, 0x9 + u32 unk_28; // 0x1, 0x1410 + u32 unk_2c; // 0x7, 0x2 + u32 pad_30[7]; + u32 unk_4c; // 0x10000, 0x50000 + u32 unk_50; // 0x1, 0x1 + u32 unk_54; // 0x0, 0x0 + u32 unk_58; // 0x4, 0x4 + u32 unk_5c; // 0x10, 0x20 u32 num_presets; - u32 unk_64[7]; - u32 unk_80[6]; - u32 unk_98_freq; + u32 unk_64; // 0x0, 0x0 + u32 unk_68; // 0x44c0, 0x4680 + u32 unk_6c; // 0x40, 0x40 + u32 unk_70; // 0x1, 0x1 + u32 unk_74; // 0x2, 0x2 + u32 unk_78; // 0x4000, 0x4000 + u32 unk_7c; // 0x40, 0x40 + u32 unk_80; // 0x1, 0x1 + u32 pad_84[2]; + u32 unk_8c; // 0x36, 0x36 + u32 pad_90[2]; + u32 timestamp_freq; u16 pad_9c; char module_sn[20]; u16 pad_b0; - u32 unk_b4[25]; + u32 unk_b4; // 0x8, 0x8 + u32 pad_b8[2]; + u32 unk_c0; // 0x4, 0x1 + u32 unk_c4; // 0x0, 0x0 + u32 unk_c8; // 0x0, 0x100 + u32 pad_cc[4]; + u32 unk_dc; // 0xff0000, 0xff0000 + u32 unk_e0; // 0xc00, 0xc00 + u32 unk_e4; // 0x0, 0x0 + u32 unk_e8; // 0x1c, 0x1c + u32 unk_ec; // 0x640, 0x680 + u32 unk_f0; // 0x4, 0x4 + u32 unk_f4; // 0x4, 0x4 + u32 pad_f8[6]; + u32 unk_110; // 0x0, 0x7800000 + u32 unk_114; // 0x0, 0x780 } __packed; static_assert(sizeof(struct cmd_ch_info) == 0x118); @@ -226,7 +260,19 @@ struct cmd_ch_camera_config { u16 in_height; u16 out_width; u16 out_height; - u32 unk[49]; + u32 unk_28; + u32 unk_2c; + u32 unk_30[16]; + u32 sensor_clk; + u32 unk_64[4]; + u32 timestamp_freq; + u32 unk_78[2]; + u32 unk_80[16]; + u32 in_width2; // repeated in u32?? + u32 in_height2; + u32 unk_c8[3]; + u32 out_width2; + u32 out_height2; } __packed; static_assert(sizeof(struct cmd_ch_camera_config) == 0xdc); From c623f05338da7e41e1d1b9ec7657e62799e311e0 Mon Sep 17 00:00:00 2001 From: Eileen Yoon Date: Tue, 12 Sep 2023 19:44:52 +0900 Subject: [PATCH 0278/3902] media: apple: isp: Don't use define for bootargs size Signed-off-by: Eileen Yoon --- drivers/media/platform/apple/isp/isp-fw.c | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/drivers/media/platform/apple/isp/isp-fw.c b/drivers/media/platform/apple/isp/isp-fw.c index 06e4d64cf05e73..1d1bbc119cd700 100644 --- a/drivers/media/platform/apple/isp/isp-fw.c +++ b/drivers/media/platform/apple/isp/isp-fw.c @@ -13,7 +13,6 @@ #define ISP_FIRMWARE_MDELAY 1 #define ISP_FIRMWARE_MAX_TRIES 1000 -#define ISP_FIRMWARE_BOOTARGS_SIZE 0x180 #define ISP_FIRMWARE_IPC_SIZE 0x1c000 #define ISP_FIRMWARE_DATA_SIZE 0x28000 @@ -57,8 +56,7 @@ struct isp_firmware_bootargs { u32 pad_c0[47]; u32 unk9; } __packed; -static_assert(sizeof(struct isp_firmware_bootargs) == - ISP_FIRMWARE_BOOTARGS_SIZE); +static_assert(sizeof(struct isp_firmware_bootargs) == 0x180); struct isp_chan_desc { char name[64]; @@ -274,7 +272,7 @@ static int isp_firmware_boot_stage2(struct apple_isp *isp) args.unk5 = 0x40; //args.pad_7c[3] = 0x3b54000; args.unk7 = 0x1; - args.unk_iova1 = args_iova + ISP_FIRMWARE_BOOTARGS_SIZE - 0xc; + args.unk_iova1 = args_iova + sizeof(args) - 0xc; args.unk9 = 0x3; isp_iowrite(isp, args_iova, &args, sizeof(args)); From 362d6438a36737dddc006f47ea65a88ef7a9cb17 Mon Sep 17 00:00:00 2001 From: Eileen Yoon Date: Tue, 12 Sep 2023 19:53:11 +0900 Subject: [PATCH 0279/3902] media: apple: isp: wmb() before GPIO write Signed-off-by: Eileen Yoon --- drivers/media/platform/apple/isp/isp-fw.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/media/platform/apple/isp/isp-fw.c b/drivers/media/platform/apple/isp/isp-fw.c index 1d1bbc119cd700..9cbeb74cf96601 100644 --- a/drivers/media/platform/apple/isp/isp-fw.c +++ b/drivers/media/platform/apple/isp/isp-fw.c @@ -278,6 +278,7 @@ static int isp_firmware_boot_stage2(struct apple_isp *isp) isp_gpio_write32(isp, ISP_GPIO_0, args_iova); isp_gpio_write32(isp, ISP_GPIO_1, 0x0); + wmb(); /* Wait for ISP_GPIO_7 to 0xf7fbdff9 -> 0x8042006 */ isp_gpio_write32(isp, ISP_GPIO_7, 0xf7fbdff9); From 17fd0e207f515c5e9d184f6c3e442f48ea712d56 Mon Sep 17 00:00:00 2001 From: Eileen Yoon Date: Tue, 12 Sep 2023 20:05:34 +0900 Subject: [PATCH 0280/3902] media: apple: isp: s/asc/coproc/ Signed-off-by: Eileen Yoon --- drivers/media/platform/apple/isp/isp-drv.c | 6 +-- drivers/media/platform/apple/isp/isp-drv.h | 2 +- drivers/media/platform/apple/isp/isp-fw.c | 46 ++++++++++----------- drivers/media/platform/apple/isp/isp-regs.h | 32 +++++++------- 4 files changed, 43 insertions(+), 43 deletions(-) diff --git a/drivers/media/platform/apple/isp/isp-drv.c b/drivers/media/platform/apple/isp/isp-drv.c index 8e6a846a867d00..7ade4b6f330371 100644 --- a/drivers/media/platform/apple/isp/isp-drv.c +++ b/drivers/media/platform/apple/isp/isp-drv.c @@ -155,9 +155,9 @@ static int apple_isp_probe(struct platform_device *pdev) return err; } - isp->asc = devm_platform_ioremap_resource_byname(pdev, "asc"); - if (IS_ERR(isp->asc)) { - err = PTR_ERR(isp->asc); + isp->coproc = devm_platform_ioremap_resource_byname(pdev, "coproc"); + if (IS_ERR(isp->coproc)) { + err = PTR_ERR(isp->coproc); goto detach_genpd; } diff --git a/drivers/media/platform/apple/isp/isp-drv.h b/drivers/media/platform/apple/isp/isp-drv.h index fb7a785b87c1c5..ed567c06d8dccf 100644 --- a/drivers/media/platform/apple/isp/isp-drv.h +++ b/drivers/media/platform/apple/isp/isp-drv.h @@ -180,7 +180,7 @@ struct apple_isp { int irq; - void __iomem *asc; + void __iomem *coproc; void __iomem *mbox; void __iomem *gpio; diff --git a/drivers/media/platform/apple/isp/isp-fw.c b/drivers/media/platform/apple/isp/isp-fw.c index 9cbeb74cf96601..064626c8ed8dec 100644 --- a/drivers/media/platform/apple/isp/isp-fw.c +++ b/drivers/media/platform/apple/isp/isp-fw.c @@ -10,20 +10,20 @@ #include "isp-ipc.h" #include "isp-regs.h" -#define ISP_FIRMWARE_MDELAY 1 -#define ISP_FIRMWARE_MAX_TRIES 1000 +#define ISP_FIRMWARE_MDELAY 1 +#define ISP_FIRMWARE_MAX_TRIES 1000 -#define ISP_FIRMWARE_IPC_SIZE 0x1c000 -#define ISP_FIRMWARE_DATA_SIZE 0x28000 +#define ISP_FIRMWARE_IPC_SIZE 0x1c000 +#define ISP_FIRMWARE_DATA_SIZE 0x28000 -static inline u32 isp_asc_read32(struct apple_isp *isp, u32 reg) +static inline u32 isp_coproc_read32(struct apple_isp *isp, u32 reg) { - return readl(isp->asc + reg); + return readl(isp->coproc + reg); } -static inline void isp_asc_write32(struct apple_isp *isp, u32 reg, u32 val) +static inline void isp_coproc_write32(struct apple_isp *isp, u32 reg, u32 val) { - writel(val, isp->asc + reg); + writel(val, isp->coproc + reg); } static inline u32 isp_gpio_read32(struct apple_isp *isp, u32 reg) @@ -130,22 +130,22 @@ static int isp_coproc_ready(struct apple_isp *isp) int retries; u32 status; - isp_asc_write32(isp, ISP_ASC_EDPRCR, 0x2); + isp_coproc_write32(isp, ISP_COPROC_EDPRCR, 0x2); - isp_asc_write32(isp, ISP_ASC_PMGR_0, 0xff00ff); - isp_asc_write32(isp, ISP_ASC_PMGR_1, 0xff00ff); - isp_asc_write32(isp, ISP_ASC_PMGR_2, 0xff00ff); - isp_asc_write32(isp, ISP_ASC_PMGR_3, 0xff00ff); + isp_coproc_write32(isp, ISP_COPROC_PMGR_0, 0xff00ff); + isp_coproc_write32(isp, ISP_COPROC_PMGR_1, 0xff00ff); + isp_coproc_write32(isp, ISP_COPROC_PMGR_2, 0xff00ff); + isp_coproc_write32(isp, ISP_COPROC_PMGR_3, 0xff00ff); - isp_asc_write32(isp, ISP_ASC_IRQ_MASK_0, 0xffffffff); - isp_asc_write32(isp, ISP_ASC_IRQ_MASK_1, 0xffffffff); - isp_asc_write32(isp, ISP_ASC_IRQ_MASK_2, 0xffffffff); - isp_asc_write32(isp, ISP_ASC_IRQ_MASK_3, 0xffffffff); - isp_asc_write32(isp, ISP_ASC_IRQ_MASK_4, 0xffffffff); - isp_asc_write32(isp, ISP_ASC_IRQ_MASK_5, 0xffffffff); + isp_coproc_write32(isp, ISP_COPROC_IRQ_MASK_0, 0xffffffff); + isp_coproc_write32(isp, ISP_COPROC_IRQ_MASK_1, 0xffffffff); + isp_coproc_write32(isp, ISP_COPROC_IRQ_MASK_2, 0xffffffff); + isp_coproc_write32(isp, ISP_COPROC_IRQ_MASK_3, 0xffffffff); + isp_coproc_write32(isp, ISP_COPROC_IRQ_MASK_4, 0xffffffff); + isp_coproc_write32(isp, ISP_COPROC_IRQ_MASK_5, 0xffffffff); for (retries = 0; retries < ISP_FIRMWARE_MAX_TRIES; retries++) { - status = isp_asc_read32(isp, ISP_ASC_STATUS); + status = isp_coproc_read32(isp, ISP_COPROC_STATUS); if (!((status & 0x3) == 0)) { isp_dbg(isp, "%d: coproc in WFI (status: 0x%x)\n", retries, status); @@ -163,7 +163,7 @@ static int isp_coproc_ready(struct apple_isp *isp) static void isp_firmware_shutdown_stage1(struct apple_isp *isp) { - isp_asc_write32(isp, ISP_ASC_CONTROL, 0x0); + isp_coproc_write32(isp, ISP_COPROC_CONTROL, 0x0); } static int isp_firmware_boot_stage1(struct apple_isp *isp) @@ -187,8 +187,8 @@ static int isp_firmware_boot_stage1(struct apple_isp *isp) isp_mbox_write32(isp, ISP_MBOX_IRQ_ENABLE, 0x0); - isp_asc_write32(isp, ISP_ASC_CONTROL, 0x0); - isp_asc_write32(isp, ISP_ASC_CONTROL, 0x10); + isp_coproc_write32(isp, ISP_COPROC_CONTROL, 0x0); + isp_coproc_write32(isp, ISP_COPROC_CONTROL, 0x10); /* Wait for ISP_GPIO_7 to 0x0 -> 0x8042006 */ isp_gpio_write32(isp, ISP_GPIO_7, 0x0); diff --git a/drivers/media/platform/apple/isp/isp-regs.h b/drivers/media/platform/apple/isp/isp-regs.h index e21485ec4ce823..b3032e9112c012 100644 --- a/drivers/media/platform/apple/isp/isp-regs.h +++ b/drivers/media/platform/apple/isp/isp-regs.h @@ -6,22 +6,22 @@ #include "isp-drv.h" -#define ISP_ASC_PMGR_0 0x738 -#define ISP_ASC_PMGR_1 0x798 -#define ISP_ASC_PMGR_2 0x7f8 -#define ISP_ASC_PMGR_3 0x858 - -#define ISP_ASC_RVBAR 0x1050000 -#define ISP_ASC_EDPRCR 0x1010310 -#define ISP_ASC_CONTROL 0x1400044 -#define ISP_ASC_STATUS 0x1400048 - -#define ISP_ASC_IRQ_MASK_0 0x1400a00 -#define ISP_ASC_IRQ_MASK_1 0x1400a04 -#define ISP_ASC_IRQ_MASK_2 0x1400a08 -#define ISP_ASC_IRQ_MASK_3 0x1400a0c -#define ISP_ASC_IRQ_MASK_4 0x1400a10 -#define ISP_ASC_IRQ_MASK_5 0x1400a14 +#define ISP_COPROC_PMGR_0 0x738 +#define ISP_COPROC_PMGR_1 0x798 +#define ISP_COPROC_PMGR_2 0x7f8 +#define ISP_COPROC_PMGR_3 0x858 + +#define ISP_COPROC_RVBAR 0x1050000 +#define ISP_COPROC_EDPRCR 0x1010310 +#define ISP_COPROC_CONTROL 0x1400044 +#define ISP_COPROC_STATUS 0x1400048 + +#define ISP_COPROC_IRQ_MASK_0 0x1400a00 +#define ISP_COPROC_IRQ_MASK_1 0x1400a04 +#define ISP_COPROC_IRQ_MASK_2 0x1400a08 +#define ISP_COPROC_IRQ_MASK_3 0x1400a0c +#define ISP_COPROC_IRQ_MASK_4 0x1400a10 +#define ISP_COPROC_IRQ_MASK_5 0x1400a14 #define ISP_MBOX_IRQ_INTERRUPT 0x000 #define ISP_MBOX_IRQ_ENABLE 0x004 From 673be3700dfff15bee997f5395864d78199673da Mon Sep 17 00:00:00 2001 From: Eileen Yoon Date: Thu, 14 Sep 2023 18:26:09 +0900 Subject: [PATCH 0281/3902] media: apple: isp: rm unused bootargs members Signed-off-by: Eileen Yoon --- drivers/media/platform/apple/isp/isp-fw.c | 3 --- 1 file changed, 3 deletions(-) diff --git a/drivers/media/platform/apple/isp/isp-fw.c b/drivers/media/platform/apple/isp/isp-fw.c index 064626c8ed8dec..3d0d550ff52183 100644 --- a/drivers/media/platform/apple/isp/isp-fw.c +++ b/drivers/media/platform/apple/isp/isp-fw.c @@ -267,10 +267,7 @@ static int isp_firmware_boot_stage2(struct apple_isp *isp) args.extra_iova = isp->extra_surf->iova; args.extra_size = isp->extra_surf->size; args.platform_id = isp->hw->platform_id; - //args.pad_40[1] = 0x3128000; - //args.pad_40[3] = 0x48000; args.unk5 = 0x40; - //args.pad_7c[3] = 0x3b54000; args.unk7 = 0x1; args.unk_iova1 = args_iova + sizeof(args) - 0xc; args.unk9 = 0x3; From 648c3eb268c3a5ba04bdd28205efe6fad38788e0 Mon Sep 17 00:00:00 2001 From: Eileen Yoon Date: Thu, 14 Sep 2023 19:17:46 +0900 Subject: [PATCH 0282/3902] media: apple: isp: rm old isp_resv struct Signed-off-by: Eileen Yoon --- drivers/media/platform/apple/isp/isp-drv.h | 6 ------ 1 file changed, 6 deletions(-) diff --git a/drivers/media/platform/apple/isp/isp-drv.h b/drivers/media/platform/apple/isp/isp-drv.h index ed567c06d8dccf..e672c62c0ec41c 100644 --- a/drivers/media/platform/apple/isp/isp-drv.h +++ b/drivers/media/platform/apple/isp/isp-drv.h @@ -85,12 +85,6 @@ struct apple_isp_hw { u8 bandwidth_size; }; -struct isp_resv { - phys_addr_t phys; - dma_addr_t iova; - u64 size; -}; - enum isp_sensor_id { ISP_IMX248_1820_01, ISP_IMX248_1822_02, From 2e7126598fc838eb685751dbf335f2c64047b191 Mon Sep 17 00:00:00 2001 From: Eileen Yoon Date: Thu, 14 Sep 2023 19:32:24 +0900 Subject: [PATCH 0283/3902] media: apple: isp: misc isp-fw.c improvements Signed-off-by: Eileen Yoon --- drivers/media/platform/apple/isp/isp-fw.c | 19 +++++++++++-------- drivers/media/platform/apple/isp/isp-regs.h | 8 ++++---- 2 files changed, 15 insertions(+), 12 deletions(-) diff --git a/drivers/media/platform/apple/isp/isp-fw.c b/drivers/media/platform/apple/isp/isp-fw.c index 3d0d550ff52183..57c1db6aee3dbc 100644 --- a/drivers/media/platform/apple/isp/isp-fw.c +++ b/drivers/media/platform/apple/isp/isp-fw.c @@ -16,6 +16,8 @@ #define ISP_FIRMWARE_IPC_SIZE 0x1c000 #define ISP_FIRMWARE_DATA_SIZE 0x28000 +#define ISP_COPROC_IN_WFI 0x3 + static inline u32 isp_coproc_read32(struct apple_isp *isp, u32 reg) { return readl(isp->coproc + reg); @@ -125,17 +127,17 @@ static int isp_enable_irq(struct apple_isp *isp) return 0; } -static int isp_coproc_ready(struct apple_isp *isp) +static int isp_reset_coproc(struct apple_isp *isp) { int retries; u32 status; isp_coproc_write32(isp, ISP_COPROC_EDPRCR, 0x2); - isp_coproc_write32(isp, ISP_COPROC_PMGR_0, 0xff00ff); - isp_coproc_write32(isp, ISP_COPROC_PMGR_1, 0xff00ff); - isp_coproc_write32(isp, ISP_COPROC_PMGR_2, 0xff00ff); - isp_coproc_write32(isp, ISP_COPROC_PMGR_3, 0xff00ff); + isp_coproc_write32(isp, ISP_COPROC_FABRIC_0, 0xff00ff); + isp_coproc_write32(isp, ISP_COPROC_FABRIC_1, 0xff00ff); + isp_coproc_write32(isp, ISP_COPROC_FABRIC_2, 0xff00ff); + isp_coproc_write32(isp, ISP_COPROC_FABRIC_3, 0xff00ff); isp_coproc_write32(isp, ISP_COPROC_IRQ_MASK_0, 0xffffffff); isp_coproc_write32(isp, ISP_COPROC_IRQ_MASK_1, 0xffffffff); @@ -146,7 +148,7 @@ static int isp_coproc_ready(struct apple_isp *isp) for (retries = 0; retries < ISP_FIRMWARE_MAX_TRIES; retries++) { status = isp_coproc_read32(isp, ISP_COPROC_STATUS); - if (!((status & 0x3) == 0)) { + if (status & ISP_COPROC_IN_WFI) { isp_dbg(isp, "%d: coproc in WFI (status: 0x%x)\n", retries, status); break; @@ -170,7 +172,7 @@ static int isp_firmware_boot_stage1(struct apple_isp *isp) { int err, retries; - err = isp_coproc_ready(isp); + err = isp_reset_coproc(isp); if (err < 0) return err; @@ -263,7 +265,7 @@ static int isp_firmware_boot_stage2(struct apple_isp *isp) args.ipc_iova = isp->ipc_surf->iova; args.ipc_size = isp->ipc_surf->size; args.shared_base = isp->fw.heap_top; - args.shared_size = 0x10000000 - isp->fw.heap_top; + args.shared_size = 0x10000000UL - isp->fw.heap_top; args.extra_iova = isp->extra_surf->iova; args.extra_size = isp->extra_surf->size; args.platform_id = isp->hw->platform_id; @@ -425,6 +427,7 @@ static int isp_firmware_boot_stage3(struct apple_isp *isp) isp_iowrite(isp, msg_iova, &msg, sizeof(msg)); } } + wmb(); /* Wait for ISP_GPIO_3 to 0x8042006 -> 0x0 */ isp_gpio_write32(isp, ISP_GPIO_3, 0x8042006); diff --git a/drivers/media/platform/apple/isp/isp-regs.h b/drivers/media/platform/apple/isp/isp-regs.h index b3032e9112c012..3a99229f6d4c8f 100644 --- a/drivers/media/platform/apple/isp/isp-regs.h +++ b/drivers/media/platform/apple/isp/isp-regs.h @@ -6,10 +6,10 @@ #include "isp-drv.h" -#define ISP_COPROC_PMGR_0 0x738 -#define ISP_COPROC_PMGR_1 0x798 -#define ISP_COPROC_PMGR_2 0x7f8 -#define ISP_COPROC_PMGR_3 0x858 +#define ISP_COPROC_FABRIC_0 0x738 +#define ISP_COPROC_FABRIC_1 0x798 +#define ISP_COPROC_FABRIC_2 0x7f8 +#define ISP_COPROC_FABRIC_3 0x858 #define ISP_COPROC_RVBAR 0x1050000 #define ISP_COPROC_EDPRCR 0x1010310 From 51e20a37593e6296fd93e83405bd9d3352c9093b Mon Sep 17 00:00:00 2001 From: Eileen Yoon Date: Thu, 14 Sep 2023 20:06:55 +0900 Subject: [PATCH 0284/3902] media: apple: isp: alloc static surfaces only once Signed-off-by: Eileen Yoon --- drivers/media/platform/apple/isp/isp-drv.c | 16 ++++++-- drivers/media/platform/apple/isp/isp-fw.c | 47 +++++++++++++--------- drivers/media/platform/apple/isp/isp-fw.h | 3 ++ 3 files changed, 43 insertions(+), 23 deletions(-) diff --git a/drivers/media/platform/apple/isp/isp-drv.c b/drivers/media/platform/apple/isp/isp-drv.c index 7ade4b6f330371..c188724b4d773b 100644 --- a/drivers/media/platform/apple/isp/isp-drv.c +++ b/drivers/media/platform/apple/isp/isp-drv.c @@ -19,6 +19,7 @@ #include #include "isp-cam.h" +#include "isp-fw.h" #include "isp-iommu.h" #include "isp-v4l2.h" @@ -202,26 +203,34 @@ static int apple_isp_probe(struct platform_device *pdev) goto destroy_wq; } + err = apple_isp_alloc_firmware_surface(isp); + if (err) { + dev_err(dev, "failed to alloc firmware surface: %d\n", err); + goto free_iommu; + } + pm_runtime_enable(dev); err = apple_isp_detect_camera(isp); if (err) { dev_err(dev, "failed to detect camera: %d\n", err); - goto free_iommu; + goto free_surface; } err = apple_isp_setup_video(isp); if (err) { dev_err(dev, "failed to register video device: %d\n", err); - goto free_iommu; + goto free_surface; } dev_info(dev, "apple-isp probe!\n"); return 0; -free_iommu: +free_surface: pm_runtime_disable(dev); + apple_isp_free_firmware_surface(isp); +free_iommu: apple_isp_free_iommu(isp); destroy_wq: destroy_workqueue(isp->wq); @@ -236,6 +245,7 @@ static void apple_isp_remove(struct platform_device *pdev) apple_isp_remove_video(isp); pm_runtime_disable(isp->dev); + apple_isp_free_firmware_surface(isp); apple_isp_free_iommu(isp); destroy_workqueue(isp->wq); apple_isp_detach_genpd(isp); diff --git a/drivers/media/platform/apple/isp/isp-fw.c b/drivers/media/platform/apple/isp/isp-fw.c index 57c1db6aee3dbc..93e18df1cf41c1 100644 --- a/drivers/media/platform/apple/isp/isp-fw.c +++ b/drivers/media/platform/apple/isp/isp-fw.c @@ -213,13 +213,36 @@ static int isp_firmware_boot_stage1(struct apple_isp *isp) return 0; } -static void isp_firmware_shutdown_stage2(struct apple_isp *isp) +int apple_isp_alloc_firmware_surface(struct apple_isp *isp) +{ + /* These are static, so let's do it once and for all */ + isp->ipc_surf = isp_alloc_surface_vmap(isp, ISP_FIRMWARE_IPC_SIZE); + if (!isp->ipc_surf) { + isp_err(isp, "failed to alloc shared surface for ipc\n"); + return -ENOMEM; + } + + isp->data_surf = isp_alloc_surface_vmap(isp, ISP_FIRMWARE_DATA_SIZE); + if (!isp->data_surf) { + isp_err(isp, "failed to alloc shared surface for data files\n"); + isp_free_surface(isp, isp->ipc_surf); + return -ENOMEM; + } + + return 0; +} + +void apple_isp_free_firmware_surface(struct apple_isp *isp) { isp_free_surface(isp, isp->data_surf); - isp_free_surface(isp, isp->extra_surf); isp_free_surface(isp, isp->ipc_surf); } +static void isp_firmware_shutdown_stage2(struct apple_isp *isp) +{ + isp_free_surface(isp, isp->extra_surf); +} + static int isp_firmware_boot_stage2(struct apple_isp *isp) { struct isp_firmware_bootargs args; @@ -240,22 +263,10 @@ static int isp_firmware_boot_stage2(struct apple_isp *isp) dev_warn(isp->dev, "unexpected channel count (%d)\n", num_ipc_chans); - isp->ipc_surf = isp_alloc_surface_vmap(isp, ISP_FIRMWARE_IPC_SIZE); - if (!isp->ipc_surf) { - isp_err(isp, "failed to alloc surface for ipc\n"); - return -ENOMEM; - } - isp->extra_surf = isp_alloc_surface_vmap(isp, extra_size); if (!isp->extra_surf) { isp_err(isp, "failed to alloc surface for extra heap\n"); - goto free_ipc; - } - - isp->data_surf = isp_alloc_surface_vmap(isp, ISP_FIRMWARE_DATA_SIZE); - if (!isp->data_surf) { - isp_err(isp, "failed to alloc surface for data files\n"); - goto free_extra; + return -ENOMEM; } args_iova = isp->ipc_surf->iova + args_offset + 0x40; @@ -296,17 +307,13 @@ static int isp_firmware_boot_stage2(struct apple_isp *isp) isp_err(isp, "never received second magic number from firmware\n"); err = -ENODEV; - goto free_file; + goto free_extra; } return 0; -free_file: - isp_free_surface(isp, isp->data_surf); free_extra: isp_free_surface(isp, isp->extra_surf); -free_ipc: - isp_free_surface(isp, isp->ipc_surf); return err; } diff --git a/drivers/media/platform/apple/isp/isp-fw.h b/drivers/media/platform/apple/isp/isp-fw.h index ad9f4fdf641aaa..264717793cea02 100644 --- a/drivers/media/platform/apple/isp/isp-fw.h +++ b/drivers/media/platform/apple/isp/isp-fw.h @@ -6,6 +6,9 @@ #include "isp-drv.h" +int apple_isp_alloc_firmware_surface(struct apple_isp *isp); +void apple_isp_free_firmware_surface(struct apple_isp *isp); + int apple_isp_firmware_boot(struct apple_isp *isp); void apple_isp_firmware_shutdown(struct apple_isp *isp); From c76e35cade93e2c4c17a7aed8c0b90db8284f0e3 Mon Sep 17 00:00:00 2001 From: Eileen Yoon Date: Thu, 14 Sep 2023 20:32:02 +0900 Subject: [PATCH 0285/3902] media: apple: isp: fix copyright Not really anymore. Signed-off-by: Eileen Yoon --- drivers/media/platform/apple/isp/isp-drv.c | 4 ---- 1 file changed, 4 deletions(-) diff --git a/drivers/media/platform/apple/isp/isp-drv.c b/drivers/media/platform/apple/isp/isp-drv.c index c188724b4d773b..936543681cc588 100644 --- a/drivers/media/platform/apple/isp/isp-drv.c +++ b/drivers/media/platform/apple/isp/isp-drv.c @@ -3,10 +3,6 @@ * Apple Image Signal Processor driver * * Copyright (C) 2023 The Asahi Linux Contributors - * - * Based on aspeed/aspeed-video.c - * Copyright 2020 IBM Corp. - * Copyright (c) 2019-2020 Intel Corporation */ #include From 0902d7590bf1d655d9b740acad002e30614ae023 Mon Sep 17 00:00:00 2001 From: Asahi Lina Date: Sun, 24 Sep 2023 01:01:59 +0900 Subject: [PATCH 0286/3902] media: apple: isp: Support >32bit VAs for t602x Signed-off-by: Asahi Lina --- drivers/media/platform/apple/isp/isp-drv.c | 7 ++++++- drivers/media/platform/apple/isp/isp-fw.c | 9 +++++---- 2 files changed, 11 insertions(+), 5 deletions(-) diff --git a/drivers/media/platform/apple/isp/isp-drv.c b/drivers/media/platform/apple/isp/isp-drv.c index 936543681cc588..109a40a18219bd 100644 --- a/drivers/media/platform/apple/isp/isp-drv.c +++ b/drivers/media/platform/apple/isp/isp-drv.c @@ -121,7 +121,8 @@ static int apple_isp_init_iommu(struct apple_isp *isp) return err; } - drm_mm_init(&isp->iovad, isp->fw.heap_top, vm_size - heap_base); + // FIXME: refactor this, maybe use regular iova stuff? + drm_mm_init(&isp->iovad, isp->fw.heap_top, vm_size - (heap_base & 0xffffffff)); return 0; } @@ -137,6 +138,10 @@ static int apple_isp_probe(struct platform_device *pdev) struct apple_isp *isp; int err; + err = dma_set_mask_and_coherent(dev, DMA_BIT_MASK(42)); + if (err) + return err; + isp = devm_kzalloc(dev, sizeof(*isp), GFP_KERNEL); if (!isp) return -ENOMEM; diff --git a/drivers/media/platform/apple/isp/isp-fw.c b/drivers/media/platform/apple/isp/isp-fw.c index 93e18df1cf41c1..70ffaa97cd260a 100644 --- a/drivers/media/platform/apple/isp/isp-fw.c +++ b/drivers/media/platform/apple/isp/isp-fw.c @@ -275,8 +275,8 @@ static int isp_firmware_boot_stage2(struct apple_isp *isp) memset(&args, 0, sizeof(args)); args.ipc_iova = isp->ipc_surf->iova; args.ipc_size = isp->ipc_surf->size; - args.shared_base = isp->fw.heap_top; - args.shared_size = 0x10000000UL - isp->fw.heap_top; + args.shared_base = isp->fw.heap_top & 0xffffffff; + args.shared_size = 0x10000000UL - args.shared_base; args.extra_iova = isp->extra_surf->iova; args.extra_size = isp->extra_surf->size; args.platform_id = isp->hw->platform_id; @@ -287,7 +287,7 @@ static int isp_firmware_boot_stage2(struct apple_isp *isp) isp_iowrite(isp, args_iova, &args, sizeof(args)); isp_gpio_write32(isp, ISP_GPIO_0, args_iova); - isp_gpio_write32(isp, ISP_GPIO_1, 0x0); + isp_gpio_write32(isp, ISP_GPIO_1, args_iova >> 32); wmb(); /* Wait for ISP_GPIO_7 to 0xf7fbdff9 -> 0x8042006 */ @@ -343,7 +343,8 @@ static void isp_free_channel_info(struct apple_isp *isp) static int isp_fill_channel_info(struct apple_isp *isp) { - u32 table_iova = isp_gpio_read32(isp, ISP_GPIO_0); + u64 table_iova = isp_gpio_read32(isp, ISP_GPIO_0) | + ((u64)isp_gpio_read32(isp, ISP_GPIO_1)) << 32; isp->ipc_chans = kcalloc(isp->num_ipc_chans, sizeof(struct isp_channel *), GFP_KERNEL); From 460fef3d0f06c320658a3216b1647894ba096d54 Mon Sep 17 00:00:00 2001 From: Asahi Lina Date: Sun, 24 Sep 2023 01:02:41 +0900 Subject: [PATCH 0287/3902] media: apple: isp: t602x hw config Signed-off-by: Asahi Lina --- drivers/media/platform/apple/isp/isp-drv.c | 24 ++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/drivers/media/platform/apple/isp/isp-drv.c b/drivers/media/platform/apple/isp/isp-drv.c index 109a40a18219bd..91dd1cb607076b 100644 --- a/drivers/media/platform/apple/isp/isp-drv.c +++ b/drivers/media/platform/apple/isp/isp-drv.c @@ -322,9 +322,33 @@ static const struct apple_isp_hw apple_isp_hw_t8110 = { .bandwidth_size = 0x8, }; +static const struct apple_isp_hw apple_isp_hw_t6020 = { + .platform_id = 0x7, // J416cAP + .pmu_base = 0x290284000, + + .dsid_clr_base0 = 0x200014000, // TODO + .dsid_clr_base1 = 0x200054000, + .dsid_clr_base2 = 0x200094000, + .dsid_clr_base3 = 0x2000d4000, + .dsid_clr_range0 = 0x1000, + .dsid_clr_range1 = 0x1000, + .dsid_clr_range2 = 0x1000, + .dsid_clr_range3 = 0x1000, + + .clock_scratch = 0x28e3d0868, // CHECK + .clock_base = 0x0, + .clock_bit = 0x0, + .clock_size = 0x8, + .bandwidth_scratch = 0x28e3d0980, // CHECK + .bandwidth_base = 0x0, + .bandwidth_bit = 0x0, + .bandwidth_size = 0x8, +}; + static const struct of_device_id apple_isp_of_match[] = { { .compatible = "apple,t8103-isp", .data = &apple_isp_hw_t8103 }, { .compatible = "apple,t6000-isp", .data = &apple_isp_hw_t6000 }, + { .compatible = "apple,t6020-isp", .data = &apple_isp_hw_t6020 }, {}, }; MODULE_DEVICE_TABLE(of, apple_isp_of_match); From 863f80196b600a3af2939a9940f4ccdd543b182d Mon Sep 17 00:00:00 2001 From: Asahi Lina Date: Sun, 24 Sep 2023 01:03:11 +0900 Subject: [PATCH 0288/3902] media: apple: isp: Working t602x and multiple formats and more fixes Sorry for the horrible big commit... Signed-off-by: Asahi Lina --- drivers/media/platform/apple/isp/isp-cam.c | 256 ++++++-------- drivers/media/platform/apple/isp/isp-cmd.c | 94 +++++- drivers/media/platform/apple/isp/isp-cmd.h | 106 +++++- drivers/media/platform/apple/isp/isp-drv.c | 145 ++++++-- drivers/media/platform/apple/isp/isp-drv.h | 43 ++- drivers/media/platform/apple/isp/isp-fw.c | 30 +- drivers/media/platform/apple/isp/isp-ipc.c | 9 +- drivers/media/platform/apple/isp/isp-v4l2.c | 349 ++++++++++++++------ 8 files changed, 697 insertions(+), 335 deletions(-) diff --git a/drivers/media/platform/apple/isp/isp-cam.c b/drivers/media/platform/apple/isp/isp-cam.c index 74125b3c652433..593b780ab73b15 100644 --- a/drivers/media/platform/apple/isp/isp-cam.c +++ b/drivers/media/platform/apple/isp/isp-cam.c @@ -8,6 +8,8 @@ #include "isp-fw.h" #include "isp-iommu.h" +#define ISP_MAX_PRESETS 32 + struct isp_setfile { u32 version; u32 magic; @@ -15,74 +17,56 @@ struct isp_setfile { size_t size; }; -struct isp_preset { - u32 index; - u32 width; - u32 height; - u32 x1; - u32 y1; - u32 x2; - u32 y2; - u32 orig_width; - u32 orig_height; -}; - // clang-format off static const struct isp_setfile isp_setfiles[] = { - [ISP_IMX248_1820_01] = {0x248, 0x18200103, "isp/1820_01XX.dat", 0x442c}, - [ISP_IMX248_1822_02] = {0x248, 0x18220201, "isp/1822_02XX.dat", 0x442c}, - [ISP_IMX343_5221_02] = {0x343, 0x52210211, "isp/5221_02XX.dat", 0x4870}, - [ISP_IMX354_9251_02] = {0x354, 0x92510208, "isp/9251_02XX.dat", 0xa5ec}, - [ISP_IMX356_4820_01] = {0x356, 0x48200107, "isp/4820_01XX.dat", 0x9324}, - [ISP_IMX356_4820_02] = {0x356, 0x48200206, "isp/4820_02XX.dat", 0x9324}, - [ISP_IMX364_8720_01] = {0x364, 0x87200103, "isp/8720_01XX.dat", 0x36ac}, - [ISP_IMX364_8723_01] = {0x364, 0x87230101, "isp/8723_01XX.dat", 0x361c}, - [ISP_IMX372_3820_01] = {0x372, 0x38200108, "isp/3820_01XX.dat", 0xfdb0}, - [ISP_IMX372_3820_02] = {0x372, 0x38200205, "isp/3820_02XX.dat", 0xfdb0}, - [ISP_IMX372_3820_11] = {0x372, 0x38201104, "isp/3820_11XX.dat", 0xfdb0}, - [ISP_IMX372_3820_12] = {0x372, 0x38201204, "isp/3820_12XX.dat", 0xfdb0}, - [ISP_IMX405_9720_01] = {0x405, 0x97200102, "isp/9720_01XX.dat", 0x92c8}, - [ISP_IMX405_9721_01] = {0x405, 0x97210102, "isp/9721_01XX.dat", 0x9818}, - [ISP_IMX405_9723_01] = {0x405, 0x97230101, "isp/9723_01XX.dat", 0x92c8}, - [ISP_IMX414_2520_01] = {0x414, 0x25200102, "isp/2520_01XX.dat", 0xa444}, - [ISP_IMX503_7820_01] = {0x503, 0x78200109, "isp/7820_01XX.dat", 0xb268}, - [ISP_IMX503_7820_02] = {0x503, 0x78200206, "isp/7820_02XX.dat", 0xb268}, - [ISP_IMX505_3921_01] = {0x505, 0x39210102, "isp/3921_01XX.dat", 0x89b0}, - [ISP_IMX514_2820_01] = {0x514, 0x28200108, "isp/2820_01XX.dat", 0xa198}, - [ISP_IMX514_2820_02] = {0x514, 0x28200205, "isp/2820_02XX.dat", 0xa198}, - [ISP_IMX514_2820_03] = {0x514, 0x28200305, "isp/2820_03XX.dat", 0xa198}, - [ISP_IMX514_2820_04] = {0x514, 0x28200405, "isp/2820_04XX.dat", 0xa198}, - [ISP_IMX558_1921_01] = {0x558, 0x19210106, "isp/1921_01XX.dat", 0xad40}, - [ISP_IMX558_1922_02] = {0x558, 0x19220201, "isp/1922_02XX.dat", 0xad40}, - [ISP_IMX603_7920_01] = {0x603, 0x79200109, "isp/7920_01XX.dat", 0xad2c}, - [ISP_IMX603_7920_02] = {0x603, 0x79200205, "isp/7920_02XX.dat", 0xad2c}, - [ISP_IMX603_7921_01] = {0x603, 0x79210104, "isp/7921_01XX.dat", 0xad90}, - [ISP_IMX613_4920_01] = {0x613, 0x49200108, "isp/4920_01XX.dat", 0x9324}, - [ISP_IMX613_4920_02] = {0x613, 0x49200204, "isp/4920_02XX.dat", 0x9324}, - [ISP_IMX614_2921_01] = {0x614, 0x29210107, "isp/2921_01XX.dat", 0xed6c}, - [ISP_IMX614_2921_02] = {0x614, 0x29210202, "isp/2921_02XX.dat", 0xed6c}, - [ISP_IMX614_2922_02] = {0x614, 0x29220201, "isp/2922_02XX.dat", 0xed6c}, - [ISP_IMX633_3622_01] = {0x633, 0x36220111, "isp/3622_01XX.dat", 0x100d4}, - [ISP_IMX703_7721_01] = {0x703, 0x77210106, "isp/7721_01XX.dat", 0x936c}, - [ISP_IMX703_7722_01] = {0x703, 0x77220106, "isp/7722_01XX.dat", 0xac20}, - [ISP_IMX713_4721_01] = {0x713, 0x47210107, "isp/4721_01XX.dat", 0x936c}, - [ISP_IMX713_4722_01] = {0x713, 0x47220109, "isp/4722_01XX.dat", 0x9218}, - [ISP_IMX714_2022_01] = {0x714, 0x20220107, "isp/2022_01XX.dat", 0xa198}, - [ISP_IMX772_3721_01] = {0x772, 0x37210106, "isp/3721_01XX.dat", 0xfdf8}, - [ISP_IMX772_3721_11] = {0x772, 0x37211106, "isp/3721_11XX.dat", 0xfe14}, - [ISP_IMX772_3722_01] = {0x772, 0x37220104, "isp/3722_01XX.dat", 0xfca4}, - [ISP_IMX772_3723_01] = {0x772, 0x37230106, "isp/3723_01XX.dat", 0xfca4}, - [ISP_IMX814_2123_01] = {0x814, 0x21230101, "isp/2123_01XX.dat", 0xed54}, - [ISP_IMX853_7622_01] = {0x853, 0x76220112, "isp/7622_01XX.dat", 0x247f8}, - [ISP_IMX913_7523_01] = {0x913, 0x75230107, "isp/7523_01XX.dat", 0x247f8}, - [ISP_VD56G0_6221_01] = {0xd56, 0x62210102, "isp/6221_01XX.dat", 0x1b80}, - [ISP_VD56G0_6222_01] = {0xd56, 0x62220102, "isp/6222_01XX.dat", 0x1b80}, -}; - -// one day we will do this intelligently -static const struct isp_preset isp_presets[] = { - [ISP_IMX248_1820_01] = {0, 1280, 720, 8, 8, 1280, 720, 1296, 736}, // J293AP - [ISP_IMX558_1921_01] = {1, 1920, 1080, 0, 0, 1920, 1080, 1920, 1080}, // J316sAP, J415AP + [ISP_IMX248_1820_01] = {0x248, 0x18200103, "apple/isp_1820_01XX.dat", 0x442c}, + [ISP_IMX248_1822_02] = {0x248, 0x18220201, "apple/isp_1822_02XX.dat", 0x442c}, + [ISP_IMX343_5221_02] = {0x343, 0x52210211, "apple/isp_5221_02XX.dat", 0x4870}, + [ISP_IMX354_9251_02] = {0x354, 0x92510208, "apple/isp_9251_02XX.dat", 0xa5ec}, + [ISP_IMX356_4820_01] = {0x356, 0x48200107, "apple/isp_4820_01XX.dat", 0x9324}, + [ISP_IMX356_4820_02] = {0x356, 0x48200206, "apple/isp_4820_02XX.dat", 0x9324}, + [ISP_IMX364_8720_01] = {0x364, 0x87200103, "apple/isp_8720_01XX.dat", 0x36ac}, + [ISP_IMX364_8723_01] = {0x364, 0x87230101, "apple/isp_8723_01XX.dat", 0x361c}, + [ISP_IMX372_3820_01] = {0x372, 0x38200108, "apple/isp_3820_01XX.dat", 0xfdb0}, + [ISP_IMX372_3820_02] = {0x372, 0x38200205, "apple/isp_3820_02XX.dat", 0xfdb0}, + [ISP_IMX372_3820_11] = {0x372, 0x38201104, "apple/isp_3820_11XX.dat", 0xfdb0}, + [ISP_IMX372_3820_12] = {0x372, 0x38201204, "apple/isp_3820_12XX.dat", 0xfdb0}, + [ISP_IMX405_9720_01] = {0x405, 0x97200102, "apple/isp_9720_01XX.dat", 0x92c8}, + [ISP_IMX405_9721_01] = {0x405, 0x97210102, "apple/isp_9721_01XX.dat", 0x9818}, + [ISP_IMX405_9723_01] = {0x405, 0x97230101, "apple/isp_9723_01XX.dat", 0x92c8}, + [ISP_IMX414_2520_01] = {0x414, 0x25200102, "apple/isp_2520_01XX.dat", 0xa444}, + [ISP_IMX503_7820_01] = {0x503, 0x78200109, "apple/isp_7820_01XX.dat", 0xb268}, + [ISP_IMX503_7820_02] = {0x503, 0x78200206, "apple/isp_7820_02XX.dat", 0xb268}, + [ISP_IMX505_3921_01] = {0x505, 0x39210102, "apple/isp_3921_01XX.dat", 0x89b0}, + [ISP_IMX514_2820_01] = {0x514, 0x28200108, "apple/isp_2820_01XX.dat", 0xa198}, + [ISP_IMX514_2820_02] = {0x514, 0x28200205, "apple/isp_2820_02XX.dat", 0xa198}, + [ISP_IMX514_2820_03] = {0x514, 0x28200305, "apple/isp_2820_03XX.dat", 0xa198}, + [ISP_IMX514_2820_04] = {0x514, 0x28200405, "apple/isp_2820_04XX.dat", 0xa198}, + [ISP_IMX558_1921_01] = {0x558, 0x19210106, "apple/isp_1921_01XX.dat", 0xad40}, + [ISP_IMX558_1922_02] = {0x558, 0x19220201, "apple/isp_1922_02XX.dat", 0xad40}, + [ISP_IMX603_7920_01] = {0x603, 0x79200109, "apple/isp_7920_01XX.dat", 0xad2c}, + [ISP_IMX603_7920_02] = {0x603, 0x79200205, "apple/isp_7920_02XX.dat", 0xad2c}, + [ISP_IMX603_7921_01] = {0x603, 0x79210104, "apple/isp_7921_01XX.dat", 0xad90}, + [ISP_IMX613_4920_01] = {0x613, 0x49200108, "apple/isp_4920_01XX.dat", 0x9324}, + [ISP_IMX613_4920_02] = {0x613, 0x49200204, "apple/isp_4920_02XX.dat", 0x9324}, + [ISP_IMX614_2921_01] = {0x614, 0x29210107, "apple/isp_2921_01XX.dat", 0xed6c}, + [ISP_IMX614_2921_02] = {0x614, 0x29210202, "apple/isp_2921_02XX.dat", 0xed6c}, + [ISP_IMX614_2922_02] = {0x614, 0x29220201, "apple/isp_2922_02XX.dat", 0xed6c}, + [ISP_IMX633_3622_01] = {0x633, 0x36220111, "apple/isp_3622_01XX.dat", 0x100d4}, + [ISP_IMX703_7721_01] = {0x703, 0x77210106, "apple/isp_7721_01XX.dat", 0x936c}, + [ISP_IMX703_7722_01] = {0x703, 0x77220106, "apple/isp_7722_01XX.dat", 0xac20}, + [ISP_IMX713_4721_01] = {0x713, 0x47210107, "apple/isp_4721_01XX.dat", 0x936c}, + [ISP_IMX713_4722_01] = {0x713, 0x47220109, "apple/isp_4722_01XX.dat", 0x9218}, + [ISP_IMX714_2022_01] = {0x714, 0x20220107, "apple/isp_2022_01XX.dat", 0xa198}, + [ISP_IMX772_3721_01] = {0x772, 0x37210106, "apple/isp_3721_01XX.dat", 0xfdf8}, + [ISP_IMX772_3721_11] = {0x772, 0x37211106, "apple/isp_3721_11XX.dat", 0xfe14}, + [ISP_IMX772_3722_01] = {0x772, 0x37220104, "apple/isp_3722_01XX.dat", 0xfca4}, + [ISP_IMX772_3723_01] = {0x772, 0x37230106, "apple/isp_3723_01XX.dat", 0xfca4}, + [ISP_IMX814_2123_01] = {0x814, 0x21230101, "apple/isp_2123_01XX.dat", 0xed54}, + [ISP_IMX853_7622_01] = {0x853, 0x76220112, "apple/isp_7622_01XX.dat", 0x247f8}, + [ISP_IMX913_7523_01] = {0x913, 0x75230107, "apple/isp_7523_01XX.dat", 0x247f8}, + [ISP_VD56G0_6221_01] = {0xd56, 0x62210102, "apple/isp_6221_01XX.dat", 0x1b80}, + [ISP_VD56G0_6222_01] = {0xd56, 0x62220102, "apple/isp_6222_01XX.dat", 0x1b80}, }; // clang-format on @@ -182,125 +166,69 @@ static int isp_ch_get_sensor_id(struct apple_isp *isp, u32 ch) return err; } -static int isp_ch_cache_sensor_info(struct apple_isp *isp, u32 ch) +static int isp_ch_get_camera_preset(struct apple_isp *isp, u32 ch, u32 ps) { - struct isp_format *fmt = isp_get_format(isp, ch); int err = 0; - struct cmd_ch_info *args; /* Too big to allocate on stack */ + struct cmd_ch_camera_config *args; /* Too big to allocate on stack */ args = kzalloc(sizeof(*args), GFP_KERNEL); if (!args) return -ENOMEM; - err = isp_cmd_ch_info_get(isp, ch, args); + err = isp_cmd_ch_camera_config_get(isp, ch, ps, args); if (err) goto exit; - dev_info(isp->dev, "found sensor %x %s on ch %d\n", args->version, - args->module_sn, ch); - - fmt->version = args->version; - fmt->num_presets = args->num_presets; - - pr_info("apple-isp: ch: CISP_CMD_CH_INFO_GET: %d\n", ch); - print_hex_dump(KERN_INFO, "apple-isp: ch: ", DUMP_PREFIX_NONE, 32, 4, + pr_info("apple-isp: ps: CISP_CMD_CH_CAMERA_CONFIG_GET: %d\n", ps); + print_hex_dump(KERN_INFO, "apple-isp: ps: ", DUMP_PREFIX_NONE, 32, 4, args, sizeof(*args), false); - err = isp_ch_get_sensor_id(isp, ch); - if (err || (fmt->id != ISP_IMX248_1820_01 && fmt->id != ISP_IMX558_1921_01)) { - dev_err(isp->dev, - "ch %d: unsupported sensor. Please file a bug report with hardware info & dmesg trace.\n", - ch); - return -ENODEV; - } - exit: kfree(args); return err; } -static int isp_ch_get_camera_preset(struct apple_isp *isp, u32 ch, u32 ps) +static int isp_ch_cache_sensor_info(struct apple_isp *isp, u32 ch) { + struct isp_format *fmt = isp_get_format(isp, ch); int err = 0; - struct cmd_ch_camera_config *args; /* Too big to allocate on stack */ + struct cmd_ch_info *args; /* Too big to allocate on stack */ args = kzalloc(sizeof(*args), GFP_KERNEL); if (!args) return -ENOMEM; - err = isp_cmd_ch_camera_config_get(isp, ch, ps, args); + err = isp_cmd_ch_info_get(isp, ch, args); if (err) goto exit; - pr_info("apple-isp: ps: CISP_CMD_CH_CAMERA_CONFIG_GET: %d\n", ps); - print_hex_dump(KERN_INFO, "apple-isp: ps: ", DUMP_PREFIX_NONE, 32, 4, - args, sizeof(*args), false); + dev_info(isp->dev, "found sensor %x %s on ch %d\n", args->version, + args->module_sn, ch); -exit: - kfree(args); + fmt->version = args->version; - return err; -} + pr_info("apple-isp: ch: CISP_CMD_CH_INFO_GET: %d\n", ch); + print_hex_dump(KERN_INFO, "apple-isp: ch: ", DUMP_PREFIX_NONE, 32, 4, + args, sizeof(*args), false); -static void isp_ch_dump_camera_presets(struct apple_isp *isp, u32 ch) -{ - struct isp_format *fmt = isp_get_format(isp, ch); - for (u32 ps = 0; ps < fmt->num_presets; ps++) { - isp_ch_get_camera_preset(isp, ch, ps); + err = isp_ch_get_sensor_id(isp, ch); + if (err || + (fmt->id != ISP_IMX248_1820_01 && fmt->id != ISP_IMX558_1921_01)) { + dev_err(isp->dev, + "ch %d: unsupported sensor. Please file a bug report with hardware info & dmesg trace.\n", + ch); + return -ENODEV; } -} - -static int isp_ch_cache_camera_preset(struct apple_isp *isp, u32 ch) -{ - struct isp_format *fmt = isp_get_format(isp, ch); - const struct isp_preset *preset = &isp_presets[fmt->id]; - size_t total_size; - - isp_ch_dump_camera_presets(isp, ch); - - fmt->preset = preset->index; - - fmt->width = preset->width; - fmt->height = preset->height; - - fmt->x1 = preset->x1; - fmt->y1 = preset->y1; - fmt->x2 = preset->x2; - fmt->y2 = preset->y2; - - /* I really fucking hope they all use NV12. */ - fmt->num_planes = 2; - fmt->plane_size[0] = fmt->width * fmt->height; - fmt->plane_size[1] = fmt->plane_size[0] / 2; - - total_size = 0; - for (int i = 0; i < fmt->num_planes; i++) - total_size += fmt->plane_size[i]; - fmt->total_size = total_size; - - return 0; -} - -static int isp_ch_cache_camera_info(struct apple_isp *isp, u32 ch) -{ - int err; - err = isp_ch_cache_sensor_info(isp, ch); - if (err) { - dev_err(isp->dev, "ch %d: failed to cache sensor info: %d\n", - ch, err); - return err; + for (u32 ps = 0; ps < args->num_presets; ps++) { + isp_ch_get_camera_preset(isp, ch, ps); } - err = isp_ch_cache_camera_preset(isp, ch); - if (err) { - dev_err(isp->dev, "ch %d: failed to cache camera preset: %d\n", - ch, err); - return err; - } +exit: + kfree(args); - return 0; + return err; } static int isp_detect_camera(struct apple_isp *isp) @@ -338,7 +266,13 @@ static int isp_detect_camera(struct apple_isp *isp) isp->num_channels = args.num_channels; isp->current_ch = 0; - return isp_ch_cache_camera_info(isp, isp->current_ch); /* I told you */ + err = isp_ch_cache_sensor_info(isp, isp->current_ch); + if (err) { + dev_err(isp->dev, "failed to cache sensor info\n"); + return err; + } + + return 0; } int apple_isp_detect_camera(struct apple_isp *isp) @@ -408,6 +342,12 @@ static int isp_ch_configure_capture(struct apple_isp *isp, u32 ch) err); } + if (isp->hw->gen >= ISP_GEN_T8112) { + err = isp_cmd_ch_lpdp_hs_receiver_tuning_set(isp, ch, 1, 15); + if (err) + return err; + } + err = isp_cmd_ch_sbs_enable(isp, ch, 1); if (err) return err; @@ -421,17 +361,21 @@ static int isp_ch_configure_capture(struct apple_isp *isp, u32 ch) if (err) return err; - err = isp_cmd_ch_camera_config_select(isp, ch, fmt->preset); + err = isp_cmd_ch_camera_config_select(isp, ch, fmt->preset->index); if (err) return err; - err = isp_cmd_ch_crop_set(isp, ch, fmt->x1, fmt->y1, fmt->x2, fmt->y2); + err = isp_cmd_ch_crop_set(isp, ch, fmt->preset->crop_offset.x, + fmt->preset->crop_offset.y, + fmt->preset->crop_size.x, + fmt->preset->crop_size.y); if (err) return err; - err = isp_cmd_ch_output_config_set(isp, ch, fmt->width, fmt->height, - CISP_COLORSPACE_REC709, - CISP_OUTPUT_FORMAT_NV12); + err = isp_cmd_ch_output_config_set(isp, ch, fmt->preset->output_dim.x, + fmt->preset->output_dim.y, + fmt->strides, CISP_COLORSPACE_REC709, + CISP_OUTPUT_FORMAT_YUV_2PLANE); if (err) return err; @@ -443,7 +387,7 @@ static int isp_ch_configure_capture(struct apple_isp *isp, u32 ch) if (err) return err; - err = isp_cmd_ch_mbnr_enable(isp, ch, 0, 1, 1); + err = isp_cmd_ch_mbnr_enable(isp, ch, 0, ISP_MBNR_MODE_ENABLE, 1); if (err) return err; diff --git a/drivers/media/platform/apple/isp/isp-cmd.c b/drivers/media/platform/apple/isp/isp-cmd.c index 79ffb2b1c33881..1e812400e52f7d 100644 --- a/drivers/media/platform/apple/isp/isp-cmd.c +++ b/drivers/media/platform/apple/isp/isp-cmd.c @@ -119,6 +119,17 @@ int isp_cmd_set_dsid_clr_req_base2(struct apple_isp *isp, u64 dsid_clr_base0, return CISP_SEND_IN(isp, args); } +int isp_cmd_set_dsid_clr_req_base(struct apple_isp *isp, u64 dsid_clr_base, + u32 dsid_clr_range) +{ + struct cmd_set_dsid_clr_req_base args = { + .opcode = CISP_OPCODE(CISP_CMD_SET_DSID_CLR_REG_BASE), + .dsid_clr_base = dsid_clr_base, + .dsid_clr_range = dsid_clr_range, + }; + return CISP_SEND_IN(isp, args); +} + int isp_cmd_pmp_ctrl_set(struct apple_isp *isp, u64 clock_scratch, u64 clock_base, u8 clock_bit, u8 clock_size, u64 bandwidth_scratch, u64 bandwidth_base, @@ -218,16 +229,26 @@ int isp_cmd_ch_buffer_return(struct apple_isp *isp, u32 chan) return CISP_SEND_IN(isp, args); } -int isp_cmd_ch_set_file_load(struct apple_isp *isp, u32 chan, u32 addr, +int isp_cmd_ch_set_file_load(struct apple_isp *isp, u32 chan, u64 addr, u32 size) { - struct cmd_ch_set_file_load args = { - .opcode = CISP_OPCODE(CISP_CMD_CH_SET_FILE_LOAD), - .chan = chan, - .addr = addr, - .size = size, - }; - return CISP_SEND_IN(isp, args); + if (isp->hw->gen >= ISP_GEN_T8112) { + struct cmd_ch_set_file_load64 args = { + .opcode = CISP_OPCODE(CISP_CMD_CH_SET_FILE_LOAD), + .chan = chan, + .addr = addr, + .size = size, + }; + return CISP_SEND_IN(isp, args); + } else { + struct cmd_ch_set_file_load args = { + .opcode = CISP_OPCODE(CISP_CMD_CH_SET_FILE_LOAD), + .chan = chan, + .addr = addr, + .size = size, + }; + return CISP_SEND_IN(isp, args); + } } int isp_cmd_ch_sbs_enable(struct apple_isp *isp, u32 chan, u32 enable) @@ -244,7 +265,8 @@ int isp_cmd_ch_crop_set(struct apple_isp *isp, u32 chan, u32 x1, u32 y1, u32 x2, u32 y2) { struct cmd_ch_crop_set args = { - .opcode = CISP_OPCODE(CISP_CMD_CH_CROP_SET), + .opcode = CISP_OPCODE(isp->hw->scl1 ? CISP_CMD_CH_CROP_SCL1_SET + : CISP_CMD_CH_CROP_SET), .chan = chan, .x1 = x1, .y1 = y1, @@ -255,23 +277,22 @@ int isp_cmd_ch_crop_set(struct apple_isp *isp, u32 chan, u32 x1, u32 y1, u32 x2, } int isp_cmd_ch_output_config_set(struct apple_isp *isp, u32 chan, u32 width, - u32 height, u32 colorspace, u32 format) + u32 height, u32 strides[3], u32 colorspace, u32 format) { struct cmd_ch_output_config_set args = { - .opcode = CISP_OPCODE(CISP_CMD_CH_OUTPUT_CONFIG_SET), + .opcode = CISP_OPCODE(isp->hw->scl1 ? CISP_CMD_CH_OUTPUT_CONFIG_SCL1_SET + : CISP_CMD_CH_OUTPUT_CONFIG_SET), .chan = chan, .width = width, .height = height, .colorspace = colorspace, .format = format, - .unk_w0 = width, - .unk_w1 = width, - .unk_24 = 0, .padding_rows = 0, .unk_h0 = height, .compress = 0, .unk_w2 = width, }; + memcpy(args.strides, strides, sizeof(args.strides)); return CISP_SEND_IN(isp, args); } @@ -356,12 +377,14 @@ int isp_cmd_ch_buffer_pool_config_set(struct apple_isp *isp, u32 chan, u16 type) .chan = chan, .type = type, .count = 16, - .meta_size0 = ISP_META_SIZE, - .meta_size1 = ISP_META_SIZE, + .meta_size0 = isp->hw->meta_size, + .meta_size1 = isp->hw->meta_size, + .unk0 = 0, + .unk1 = 0, + .unk2 = 0, .data_blocks = 1, .compress = 0, }; - memset(args.zero, 0, sizeof(u32) * 0x1f); return CISP_SEND_INOUT(isp, args); } @@ -542,3 +565,40 @@ int isp_cmd_ch_semantic_awb_enable(struct apple_isp *isp, u32 chan, u32 enable) }; return CISP_SEND_IN(isp, args); } + +int isp_cmd_ch_lpdp_hs_receiver_tuning_set(struct apple_isp *isp, u32 chan, u32 unk1, u32 unk2) +{ + struct cmd_ch_lpdp_hs_receiver_tuning_set args = { + .opcode = CISP_OPCODE(CISP_CMD_CH_LPDP_HS_RECEIVER_TUNING_SET), + .chan = chan, + .unk1 = unk1, + .unk2 = unk2, + }; + return CISP_SEND_IN(isp, args); +} + +int isp_cmd_ch_property_write(struct apple_isp *isp, u32 chan, u32 prop, u32 val) +{ + struct cmd_ch_property_write args = { + .opcode = CISP_OPCODE(CISP_CMD_CH_PROPERTY_WRITE), + .chan = chan, + .prop = prop, + .val = val, + }; + return CISP_SEND_IN(isp, args); +} + +int isp_cmd_ch_property_read(struct apple_isp *isp, u32 chan, u32 prop, u32 *val) +{ + struct cmd_ch_property_write args = { + .opcode = CISP_OPCODE(CISP_CMD_CH_PROPERTY_READ), + .chan = chan, + .prop = prop, + .val = 0xdeadbeef, + }; + int ret = CISP_SEND_OUT(isp, &args); + + *val = args.val; + + return ret; +} diff --git a/drivers/media/platform/apple/isp/isp-cmd.h b/drivers/media/platform/apple/isp/isp-cmd.h index 1fc484fa687853..1586df89f1cdab 100644 --- a/drivers/media/platform/apple/isp/isp-cmd.h +++ b/drivers/media/platform/apple/isp/isp-cmd.h @@ -35,10 +35,14 @@ #define CISP_CMD_CH_BUFFER_POOL_CONFIG_SET 0x0117 #define CISP_CMD_CH_CAMERA_MIPI_FREQUENCY_GET 0x011a #define CISP_CMD_CH_CAMERA_PIX_FREQUENCY_GET 0x011f +#define CISP_CMD_CH_PROPERTY_WRITE 0x0122 +#define CISP_CMD_CH_PROPERTY_READ 0x0123 #define CISP_CMD_CH_LOCAL_RAW_BUFFER_ENABLE 0x0125 +#define CISP_CMD_CH_META_DATA_ENABLE 0x0126 #define CISP_CMD_CH_CAMERA_MIPI_FREQUENCY_TOTAL_GET 0x0133 #define CISP_CMD_CH_SBS_ENABLE 0x013b #define CISP_CMD_CH_LSC_POLYNOMIAL_COEFF_GET 0x0142 +#define CISP_CMD_CH_SET_META_DATA_REQUIRED 0x014f #define CISP_CMD_CH_BUFFER_POOL_RETURN 0x015b #define CISP_CMD_CH_CAMERA_AGILE_FREQ_ARRAY_CURRENT_GET 0x015e #define CISP_CMD_CH_AE_START 0x0200 @@ -52,25 +56,35 @@ #define CISP_CMD_CH_SENSOR_NVM_GET 0x0501 #define CISP_CMD_CH_SENSOR_PERMODULE_LSC_INFO_GET 0x0507 #define CISP_CMD_CH_SENSOR_PERMODULE_LSC_GRID_GET 0x0511 +#define CISP_CMD_CH_LPDP_HS_RECEIVER_TUNING_SET 0x051b #define CISP_CMD_CH_FOCUS_LIMITS_GET 0x0701 +#define CISP_CMD_CH_CROP_GET 0x0800 #define CISP_CMD_CH_CROP_SET 0x0801 +#define CISP_CMD_CH_SCALER_CROP_SET 0x080a +#define CISP_CMD_CH_CROP_SCL1_GET 0x080b +#define CISP_CMD_CH_CROP_SCL1_SET 0x080c +#define CISP_CMD_CH_SCALER_CROP_SCL1_SET 0x080d #define CISP_CMD_CH_ALS_ENABLE 0x0a1c #define CISP_CMD_CH_ALS_DISABLE 0x0a1d #define CISP_CMD_CH_CNR_START 0x0a2f #define CISP_CMD_CH_MBNR_ENABLE 0x0a3a #define CISP_CMD_CH_OUTPUT_CONFIG_SET 0x0b01 +#define CISP_CMD_CH_OUTPUT_CONFIG_SCL1_SET 0x0b09 #define CISP_CMD_CH_PREVIEW_STREAM_SET 0x0b0d #define CISP_CMD_CH_SEMANTIC_VIDEO_ENABLE 0x0b17 #define CISP_CMD_CH_SEMANTIC_AWB_ENABLE 0x0b18 #define CISP_CMD_CH_FACE_DETECTION_START 0x0d00 +#define CISP_CMD_CH_FACE_DETECTION_STOP 0x0d01 #define CISP_CMD_CH_FACE_DETECTION_CONFIG_GET 0x0d02 #define CISP_CMD_CH_FACE_DETECTION_CONFIG_SET 0x0d03 +#define CISP_CMD_CH_FACE_DETECTION_DISABLE 0x0d04 #define CISP_CMD_CH_FACE_DETECTION_ENABLE 0x0d05 #define CISP_CMD_CH_FID_START 0x3000 #define CISP_CMD_CH_FID_STOP 0x3001 #define CISP_CMD_IPC_ENDPOINT_SET2 0x300c #define CISP_CMD_IPC_ENDPOINT_UNSET2 0x300d #define CISP_CMD_SET_DSID_CLR_REG_BASE2 0x3204 +#define CISP_CMD_SET_DSID_CLR_REG_BASE 0x3205 #define CISP_CMD_APPLE_CH_AE_METERING_MODE_SET 0x8206 #define CISP_CMD_APPLE_CH_AE_FD_SCENE_METERING_CONFIG_SET 0x820e #define CISP_CMD_APPLE_CH_AE_FLICKER_FREQ_UPDATE_CURRENT_SET 0x8212 @@ -86,10 +100,28 @@ #define CISP_POOL_TYPE_FD 0x2 #define CISP_POOL_TYPE_RAW 0x3 #define CISP_POOL_TYPE_STAT 0x4 +#define CISP_POOL_TYPE_RAW_AUX 0x5 +#define CISP_POOL_TYPE_YCC 0x6 +#define CISP_POOL_TYPE_CAPTURE_FULL_RES 0x7 #define CISP_POOL_TYPE_META_CAPTURE 0x8 +#define CISP_POOL_TYPE_RENDERED_SCL1 0x9 +#define CISP_POOL_TYPE_STAT_PIXELOUTPUT 0x11 +#define CISP_POOL_TYPE_FSCL 0x12 +#define CISP_POOL_TYPE_CAPTURE_FULL_RES_YCC 0x13 +#define CISP_POOL_TYPE_RENDERED_RAW 0x14 +#define CISP_POOL_TYPE_CAPTURE_PDC_RAW 0x16 +#define CISP_POOL_TYPE_FPC_DATA 0x17 +#define CISP_POOL_TYPE_AICAM_SEG 0x19 +#define CISP_POOL_TYPE_SPD 0x1a +#define CISP_POOL_TYPE_META_DEPTH 0x1c +#define CISP_POOL_TYPE_JASPER_DEPTH 0x1d +#define CISP_POOL_TYPE_RAW_SIFR 0x1f +#define CISP_POOL_TYPE_FEP_THUMBNAIL_DYNAMIC_POOL_RAW 0x21 #define CISP_COLORSPACE_REC709 0x1 -#define CISP_OUTPUT_FORMAT_NV12 0x0 +#define CISP_OUTPUT_FORMAT_YUV_2PLANE 0x0 +#define CISP_OUTPUT_FORMAT_YUV_1PLANE 0x1 +#define CISP_OUTPUT_FORMAT_RGB 0x2 #define CISP_BUFFER_RECYCLE_MODE_EMPTY_ONLY 0x1 struct cmd_start { @@ -144,6 +176,13 @@ struct cmd_set_dsid_clr_req_base2 { } __packed; static_assert(sizeof(struct cmd_set_dsid_clr_req_base2) == 0x38); +struct cmd_set_dsid_clr_req_base { + u64 opcode; + u64 dsid_clr_base; + u32 dsid_clr_range; +} __packed; +static_assert(sizeof(struct cmd_set_dsid_clr_req_base) == 0x14); + struct cmd_pmp_ctrl_set { u64 opcode; u64 clock_scratch; @@ -169,12 +208,26 @@ struct cmd_fid_exit { } __packed; static_assert(sizeof(struct cmd_fid_exit) == 0x8); +struct cmd_ipc_endpoint_set2 { + u64 opcode; + u32 unk; + u64 addr1; + u32 size1; + u64 addr2; + u32 size2; + u64 regs; + u32 unk2; +} __packed; +static_assert(sizeof(struct cmd_ipc_endpoint_set2) == 0x30); + int isp_cmd_start(struct apple_isp *isp, u32 mode); int isp_cmd_suspend(struct apple_isp *isp); int isp_cmd_print_enable(struct apple_isp *isp, u32 enable); int isp_cmd_trace_enable(struct apple_isp *isp, u32 enable); int isp_cmd_config_get(struct apple_isp *isp, struct cmd_config_get *args); int isp_cmd_set_isp_pmu_base(struct apple_isp *isp, u64 pmu_base); +int isp_cmd_set_dsid_clr_req_base(struct apple_isp *isp, u64 dsid_clr_base, + u32 dsid_clr_range); int isp_cmd_set_dsid_clr_req_base2(struct apple_isp *isp, u64 dsid_clr_base0, u64 dsid_clr_base1, u64 dsid_clr_base2, u64 dsid_clr_base3, u32 dsid_clr_range0, @@ -291,6 +344,14 @@ struct cmd_ch_set_file_load { } __packed; static_assert(sizeof(struct cmd_ch_set_file_load) == 0x14); +struct cmd_ch_set_file_load64 { + u64 opcode; + u32 chan; + u64 addr; + u32 size; +} __packed; +static_assert(sizeof(struct cmd_ch_set_file_load64) == 0x18); + struct cmd_ch_buffer_return { u64 opcode; u32 chan; @@ -321,9 +382,7 @@ struct cmd_ch_output_config_set { u32 height; u32 colorspace; u32 format; - u32 unk_w0; - u32 unk_w1; - u32 unk_24; + u32 strides[3]; u32 padding_rows; u32 unk_h0; u32 compress; @@ -369,6 +428,24 @@ struct cmd_ch_sif_pixel_format_set { } __packed; static_assert(sizeof(struct cmd_ch_sif_pixel_format_set) == 0x14); +struct cmd_ch_lpdp_hs_receiver_tuning_set { + u64 opcode; + u32 chan; + u32 unk1; + u32 unk2; +} __packed; +static_assert(sizeof(struct cmd_ch_lpdp_hs_receiver_tuning_set) == 0x14); + +struct cmd_ch_property_write { + u64 opcode; + u32 chan; + u32 prop; + u32 val; + u32 unk1; + u32 unk2; +} __packed; +static_assert(sizeof(struct cmd_ch_property_write) == 0x1c); + int isp_cmd_ch_start(struct apple_isp *isp, u32 chan); int isp_cmd_ch_stop(struct apple_isp *isp, u32 chan); int isp_cmd_ch_info_get(struct apple_isp *isp, u32 chan, @@ -379,20 +456,30 @@ int isp_cmd_ch_camera_config_current_get(struct apple_isp *isp, u32 chan, struct cmd_ch_camera_config *args); int isp_cmd_ch_camera_config_select(struct apple_isp *isp, u32 chan, u32 preset); -int isp_cmd_ch_set_file_load(struct apple_isp *isp, u32 chan, u32 addr, +int isp_cmd_ch_set_file_load(struct apple_isp *isp, u32 chan, u64 addr, u32 size); int isp_cmd_ch_buffer_return(struct apple_isp *isp, u32 chan); int isp_cmd_ch_sbs_enable(struct apple_isp *isp, u32 chan, u32 enable); int isp_cmd_ch_crop_set(struct apple_isp *isp, u32 chan, u32 x1, u32 y1, u32 x2, u32 y2); int isp_cmd_ch_output_config_set(struct apple_isp *isp, u32 chan, u32 width, - u32 height, u32 colorspace, u32 format); + u32 height, u32 strides[3], u32 colorspace, u32 format); int isp_cmd_ch_preview_stream_set(struct apple_isp *isp, u32 chan, u32 stream); int isp_cmd_ch_als_disable(struct apple_isp *isp, u32 chan); int isp_cmd_ch_cnr_start(struct apple_isp *isp, u32 chan); int isp_cmd_ch_mbnr_enable(struct apple_isp *isp, u32 chan, u32 use_case, u32 mode, u32 enable_chroma); int isp_cmd_ch_sif_pixel_format_set(struct apple_isp *isp, u32 chan); +int isp_cmd_ch_lpdp_hs_receiver_tuning_set(struct apple_isp *isp, u32 chan, u32 unk1, u32 unk2); + +int isp_cmd_ch_property_read(struct apple_isp *isp, u32 chan, u32 prop, u32 *val); +int isp_cmd_ch_property_write(struct apple_isp *isp, u32 chan, u32 prop, u32 val); + +enum isp_mbnr_mode { + ISP_MBNR_MODE_DISABLE = 0, + ISP_MBNR_MODE_ENABLE = 1, + ISP_MBNR_MODE_BYPASS = 2, +}; struct cmd_ch_buffer_recycle_mode_set { u64 opcode; @@ -414,7 +501,10 @@ struct cmd_ch_buffer_pool_config_set { u16 count; u32 meta_size0; u32 meta_size1; - u32 zero[0x1f]; + u64 unk0; + u64 unk1; + u64 unk2; + u32 zero[0x19]; u32 data_blocks; u32 compress; } __packed; @@ -431,6 +521,8 @@ int isp_cmd_ch_buffer_recycle_mode_set(struct apple_isp *isp, u32 chan, int isp_cmd_ch_buffer_recycle_start(struct apple_isp *isp, u32 chan); int isp_cmd_ch_buffer_pool_config_set(struct apple_isp *isp, u32 chan, u16 type); +int isp_cmd_ch_buffer_pool_config_get(struct apple_isp *isp, u32 chan, + u16 type); int isp_cmd_ch_buffer_pool_return(struct apple_isp *isp, u32 chan); struct cmd_apple_ch_temporal_filter_start { diff --git a/drivers/media/platform/apple/isp/isp-drv.c b/drivers/media/platform/apple/isp/isp-drv.c index 91dd1cb607076b..5a15b812c3dcfa 100644 --- a/drivers/media/platform/apple/isp/isp-drv.c +++ b/drivers/media/platform/apple/isp/isp-drv.c @@ -90,7 +90,8 @@ static int apple_isp_init_iommu(struct apple_isp *isp) return -ENODEV; isp->shift = __ffs(isp->domain->pgsize_bitmap); - idx = of_property_match_string(dev->of_node, "memory-region-names", "heap"); + idx = of_property_match_string(dev->of_node, "memory-region-names", + "heap"); mem_node = of_parse_phandle(dev->of_node, "memory-region", idx); if (!mem_node) { dev_err(dev, "No memory-region found for heap\n"); @@ -107,11 +108,10 @@ static int apple_isp_init_iommu(struct apple_isp *isp) while (maps < end) { maps++; - maps = of_translate_dma_region(dev->of_node, maps, &heap_base, &heap_size); + maps = of_translate_dma_region(dev->of_node, maps, &heap_base, + &heap_size); } - printk("heap: 0x%llx 0x%lx\n", heap_base, heap_size); - isp->fw.heap_top = heap_base + heap_size; err = of_property_read_u64(dev->of_node, "apple,dart-vm-size", @@ -122,7 +122,8 @@ static int apple_isp_init_iommu(struct apple_isp *isp) } // FIXME: refactor this, maybe use regular iova stuff? - drm_mm_init(&isp->iovad, isp->fw.heap_top, vm_size - (heap_base & 0xffffffff)); + drm_mm_init(&isp->iovad, isp->fw.heap_top, + vm_size - (heap_base & 0xffffffff)); return 0; } @@ -132,6 +133,83 @@ static void apple_isp_free_iommu(struct apple_isp *isp) drm_mm_takedown(&isp->iovad); } +static int isp_of_read_coord(struct device *dev, struct device_node *np, + const char *prop, struct coord *val) +{ + u32 xy[2]; + int ret; + + ret = of_property_read_u32_array(np, prop, xy, 2); + if (ret) { + dev_err(dev, "failed to read '%s' property\n", prop); + return ret; + } + + val->x = xy[0]; + val->y = xy[1]; + return 0; +} + +static int apple_isp_init_presets(struct apple_isp *isp) +{ + struct device *dev = isp->dev; + struct isp_preset *preset; + int err = 0; + + struct device_node *np __free(device_node) = + of_get_child_by_name(dev->of_node, "sensor-presets"); + if (!np) { + dev_err(dev, "failed to get DT node 'presets'\n"); + return -EINVAL; + } + + isp->num_presets = of_get_child_count(np); + if (!isp->num_presets) { + dev_err(dev, "no sensor presets found\n"); + return -EINVAL; + } + + isp->presets = devm_kzalloc( + dev, sizeof(*isp->presets) * isp->num_presets, GFP_KERNEL); + if (!isp->presets) + return -ENOMEM; + + preset = isp->presets; + for_each_child_of_node_scoped(np, child) { + u32 xywh[4]; + + err = of_property_read_u32(child, "apple,config-index", + &preset->index); + if (err) { + dev_err(dev, "no apple,config-index property\n"); + return err; + } + + err = isp_of_read_coord(dev, child, "apple,input-size", + &preset->input_dim); + if (err) + return err; + err = isp_of_read_coord(dev, child, "apple,output-size", + &preset->output_dim); + if (err) + return err; + + err = of_property_read_u32_array(child, "apple,crop", xywh, 4); + if (err) { + dev_err(dev, "failed to read 'apple,crop' property\n"); + return err; + } + preset->crop_offset.x = xywh[0]; + preset->crop_offset.y = xywh[1]; + preset->crop_size.x = xywh[2]; + preset->crop_size.y = xywh[3]; + + preset++; + } + + return 0; +} + static int apple_isp_probe(struct platform_device *pdev) { struct device *dev = &pdev->dev; @@ -151,6 +229,20 @@ static int apple_isp_probe(struct platform_device *pdev) platform_set_drvdata(pdev, isp); dev_set_drvdata(dev, isp); + err = of_property_read_u32(dev->of_node, "apple,platform-id", + &isp->platform_id); + if (err) { + dev_err(dev, "failed to get 'apple,platform-id' property: %d\n", + err); + return err; + } + + err = apple_isp_init_presets(isp); + if (err) { + dev_err(dev, "failed to initialize presets\n"); + return err; + } + err = apple_isp_attach_genpd(isp); if (err) { dev_err(dev, "failed to attatch power domains\n"); @@ -190,7 +282,8 @@ static int apple_isp_probe(struct platform_device *pdev) spin_lock_init(&isp->buf_lock); init_waitqueue_head(&isp->wait); INIT_LIST_HEAD(&isp->gc); - INIT_LIST_HEAD(&isp->buffers); + INIT_LIST_HEAD(&isp->bufs_pending); + INIT_LIST_HEAD(&isp->bufs_submitted); isp->wq = alloc_workqueue("apple-isp-wq", WQ_UNBOUND, 0); if (!isp->wq) { dev_err(dev, "failed to create workqueue\n"); @@ -250,13 +343,13 @@ static void apple_isp_remove(struct platform_device *pdev) apple_isp_free_iommu(isp); destroy_workqueue(isp->wq); apple_isp_detach_genpd(isp); - return 0; } static const struct apple_isp_hw apple_isp_hw_t8103 = { - .platform_id = 0x1, + .gen = ISP_GEN_T8103, .pmu_base = 0x23b704000, + .dsid_count = 4, .dsid_clr_base0 = 0x200014000, .dsid_clr_base1 = 0x200054000, .dsid_clr_base2 = 0x200094000, @@ -274,12 +367,16 @@ static const struct apple_isp_hw apple_isp_hw_t8103 = { .bandwidth_base = 0x23bc3c000, .bandwidth_bit = 0x0, .bandwidth_size = 0x4, + + .scl1 = false, + .meta_size = ISP_META_SIZE_T8103, }; static const struct apple_isp_hw apple_isp_hw_t6000 = { - .platform_id = 0x3, + .gen = ISP_GEN_T8103, .pmu_base = 0x28e584000, + .dsid_count = 1, .dsid_clr_base0 = 0x200014000, .dsid_clr_base1 = 0x200054000, .dsid_clr_base2 = 0x200094000, @@ -297,12 +394,16 @@ static const struct apple_isp_hw apple_isp_hw_t6000 = { .bandwidth_base = 0x0, .bandwidth_bit = 0x0, .bandwidth_size = 0x8, + + .scl1 = false, + .meta_size = ISP_META_SIZE_T8103, }; static const struct apple_isp_hw apple_isp_hw_t8110 = { - .platform_id = 0xe, // J413AP + .gen = ISP_GEN_T8112, .pmu_base = 0x23b704000, + .dsid_count = 4, .dsid_clr_base0 = 0x200014000, // TODO .dsid_clr_base1 = 0x200054000, .dsid_clr_base2 = 0x200094000, @@ -320,29 +421,30 @@ static const struct apple_isp_hw apple_isp_hw_t8110 = { .bandwidth_base = 0x0, .bandwidth_bit = 0x0, .bandwidth_size = 0x8, + + .scl1 = true, + .meta_size = ISP_META_SIZE_T8112, }; static const struct apple_isp_hw apple_isp_hw_t6020 = { - .platform_id = 0x7, // J416cAP + .gen = ISP_GEN_T8112, .pmu_base = 0x290284000, - .dsid_clr_base0 = 0x200014000, // TODO - .dsid_clr_base1 = 0x200054000, - .dsid_clr_base2 = 0x200094000, - .dsid_clr_base3 = 0x2000d4000, + .dsid_count = 1, + .dsid_clr_base0 = 0x200f14000, .dsid_clr_range0 = 0x1000, - .dsid_clr_range1 = 0x1000, - .dsid_clr_range2 = 0x1000, - .dsid_clr_range3 = 0x1000, - .clock_scratch = 0x28e3d0868, // CHECK + .clock_scratch = 0x28e3d10a8, .clock_base = 0x0, .clock_bit = 0x0, .clock_size = 0x8, - .bandwidth_scratch = 0x28e3d0980, // CHECK + .bandwidth_scratch = 0x28e3d1200, .bandwidth_base = 0x0, .bandwidth_bit = 0x0, .bandwidth_size = 0x8, + + .scl1 = true, + .meta_size = ISP_META_SIZE_T8112, }; static const struct of_device_id apple_isp_of_match[] = { @@ -362,7 +464,8 @@ static __maybe_unused int apple_isp_resume(struct device *dev) { return 0; } -DEFINE_RUNTIME_DEV_PM_OPS(apple_isp_pm_ops, apple_isp_suspend, apple_isp_resume, NULL); +DEFINE_RUNTIME_DEV_PM_OPS(apple_isp_pm_ops, apple_isp_suspend, apple_isp_resume, + NULL); static struct platform_driver apple_isp_driver = { .driver = { diff --git a/drivers/media/platform/apple/isp/isp-drv.h b/drivers/media/platform/apple/isp/isp-drv.h index e672c62c0ec41c..926c921849544a 100644 --- a/drivers/media/platform/apple/isp/isp-drv.h +++ b/drivers/media/platform/apple/isp/isp-drv.h @@ -20,7 +20,13 @@ #define ISP_MAX_CHANNELS 6 #define ISP_IPC_MESSAGE_SIZE 64 #define ISP_IPC_FLAG_ACK 0x1 -#define ISP_META_SIZE 0x4640 +#define ISP_META_SIZE_T8103 0x4640 +#define ISP_META_SIZE_T8112 0x4840 + +enum isp_generation { + ISP_GEN_T8103, + ISP_GEN_T8112, +}; struct isp_surf { struct drm_mm_node *mm; @@ -62,10 +68,24 @@ struct isp_channel { const struct isp_chan_ops *ops; }; +struct coord { + u32 x; + u32 y; +}; + +struct isp_preset { + u32 index; + struct coord input_dim; + struct coord output_dim; + struct coord crop_offset; + struct coord crop_size; +}; + struct apple_isp_hw { - u32 platform_id; + enum isp_generation gen; u64 pmu_base; + int dsid_count; u64 dsid_clr_base0; u64 dsid_clr_base1; u64 dsid_clr_base2; @@ -83,6 +103,9 @@ struct apple_isp_hw { u64 bandwidth_base; u8 bandwidth_bit; u8 bandwidth_size; + + u32 meta_size; + bool scl1; }; enum isp_sensor_id { @@ -139,15 +162,9 @@ enum isp_sensor_id { struct isp_format { enum isp_sensor_id id; u32 version; - u32 num_presets; - u32 preset; - u32 width; - u32 height; - u32 x1; - u32 y1; - u32 x2; - u32 y2; + struct isp_preset *preset; unsigned int num_planes; + u32 strides[VB2_MAX_PLANES]; size_t plane_size[VB2_MAX_PLANES]; size_t total_size; }; @@ -155,6 +172,9 @@ struct isp_format { struct apple_isp { struct device *dev; const struct apple_isp_hw *hw; + u32 platform_id; + struct isp_preset *presets; + int num_presets; int num_channels; struct isp_format fmts[ISP_MAX_CHANNELS]; @@ -208,7 +228,8 @@ struct apple_isp { unsigned long state; spinlock_t buf_lock; - struct list_head buffers; + struct list_head bufs_pending; + struct list_head bufs_submitted; }; struct isp_chan_ops { diff --git a/drivers/media/platform/apple/isp/isp-fw.c b/drivers/media/platform/apple/isp/isp-fw.c index 70ffaa97cd260a..972867a93a0193 100644 --- a/drivers/media/platform/apple/isp/isp-fw.c +++ b/drivers/media/platform/apple/isp/isp-fw.c @@ -46,7 +46,10 @@ struct isp_firmware_bootargs { u64 extra_iova; u64 extra_size; u32 platform_id; - u32 pad_40[7]; + u32 pad_40; + u64 logbuf_addr; + u64 logbuf_size; + u64 logbuf_entsize; u32 ipc_size; u32 pad_60[5]; u32 unk5; @@ -279,9 +282,9 @@ static int isp_firmware_boot_stage2(struct apple_isp *isp) args.shared_size = 0x10000000UL - args.shared_base; args.extra_iova = isp->extra_surf->iova; args.extra_size = isp->extra_surf->size; - args.platform_id = isp->hw->platform_id; + args.platform_id = isp->platform_id; args.unk5 = 0x40; - args.unk7 = 0x1; + args.unk7 = 0x1; // 0? args.unk_iova1 = args_iova + sizeof(args) - 0xc; args.unk9 = 0x3; isp_iowrite(isp, args_iova, &args, sizeof(args)); @@ -501,13 +504,20 @@ static int isp_start_command_processor(struct apple_isp *isp) if (err) return err; - err = isp_cmd_set_dsid_clr_req_base2( - isp, isp->hw->dsid_clr_base0, isp->hw->dsid_clr_base1, - isp->hw->dsid_clr_base2, isp->hw->dsid_clr_base3, - isp->hw->dsid_clr_range0, isp->hw->dsid_clr_range1, - isp->hw->dsid_clr_range2, isp->hw->dsid_clr_range3); - if (err) - return err; + if (isp->hw->dsid_count == 1) { + err = isp_cmd_set_dsid_clr_req_base( + isp, isp->hw->dsid_clr_base0, isp->hw->dsid_clr_range0); + if (err) + return err; + } else { + err = isp_cmd_set_dsid_clr_req_base2( + isp, isp->hw->dsid_clr_base0, isp->hw->dsid_clr_base1, + isp->hw->dsid_clr_base2, isp->hw->dsid_clr_base3, + isp->hw->dsid_clr_range0, isp->hw->dsid_clr_range1, + isp->hw->dsid_clr_range2, isp->hw->dsid_clr_range3); + if (err) + return err; + } err = isp_cmd_pmp_ctrl_set( isp, isp->hw->clock_scratch, isp->hw->clock_base, diff --git a/drivers/media/platform/apple/isp/isp-ipc.c b/drivers/media/platform/apple/isp/isp-ipc.c index a9a0fdb73a4d9f..14249a44798ba5 100644 --- a/drivers/media/platform/apple/isp/isp-ipc.c +++ b/drivers/media/platform/apple/isp/isp-ipc.c @@ -230,8 +230,8 @@ static void sm_malloc_deferred_worker(struct work_struct *work) } #ifdef APPLE_ISP_DEBUG - /* Only enabled in debug builds so it shouldn't matter, but - * the LOG surface is always the first surface requested. + /* Only enabled in debug builds so it shouldn't matter, but + * the LOG surface is always the first surface requested. */ if (!test_bit(ISP_STATE_LOGGING, &isp->state)) set_bit(ISP_STATE_LOGGING, &isp->state); @@ -306,9 +306,10 @@ int ipc_bt_handle(struct apple_isp *isp, struct isp_channel *chan) sizeof(meta_iova)); spin_lock(&isp->buf_lock); - list_for_each_entry_safe_reverse(buf, tmp, &isp->buffers, link) { - if (buf->meta->iova == meta_iova) { + list_for_each_entry_safe_reverse(buf, tmp, &isp->bufs_submitted, link) { + if ((u32)buf->meta->iova == (u32)meta_iova) { enum vb2_buffer_state state = VB2_BUF_STATE_ERROR; + buf->vb.vb2_buf.timestamp = ktime_get_ns(); buf->vb.sequence = isp->sequence++; buf->vb.field = V4L2_FIELD_NONE; diff --git a/drivers/media/platform/apple/isp/isp-v4l2.c b/drivers/media/platform/apple/isp/isp-v4l2.c index 9de6549ec9bee7..a35b9cbf20fef9 100644 --- a/drivers/media/platform/apple/isp/isp-v4l2.c +++ b/drivers/media/platform/apple/isp/isp-v4l2.c @@ -13,10 +13,11 @@ #include "isp-ipc.h" #include "isp-v4l2.h" -#define ISP_MIN_FRAMES 2 -#define ISP_MAX_PLANES 4 -#define ISP_MAX_PIX_FORMATS 2 -#define ISP_BUFFER_TIMEOUT msecs_to_jiffies(1500) +#define ISP_MIN_FRAMES 2 +#define ISP_MAX_PLANES 4 +#define ISP_MAX_PIX_FORMATS 2 +#define ISP_BUFFER_TIMEOUT msecs_to_jiffies(1500) +#define ISP_STRIDE_ALIGNMENT 64 struct isp_h2t_buffer { u64 iovas[ISP_MAX_PLANES]; @@ -40,7 +41,7 @@ static int isp_submit_buffers(struct apple_isp *isp) struct isp_format *fmt = isp_get_current_format(isp); struct isp_channel *chan = isp->chan_bh; struct isp_message *req = &chan->req; - struct isp_buffer *buf; + struct isp_buffer *buf, *buf2, *tmp; unsigned long flags; size_t offset; int err; @@ -51,43 +52,76 @@ static int isp_submit_buffers(struct apple_isp *isp) return -ENOMEM; spin_lock_irqsave(&isp->buf_lock, flags); - buf = list_first_entry_or_null(&isp->buffers, struct isp_buffer, link); - if (!buf) { + while ((buf = list_first_entry_or_null(&isp->bufs_pending, + struct isp_buffer, link))) { + args->meta.num_planes = 1; + args->meta.pool_type = 0; + args->meta.iovas[0] = buf->meta->iova; + args->meta.flags[0] = 0x40000000; + + args->render.num_planes = fmt->num_planes; + args->render.pool_type = isp->hw->scl1 ? + CISP_POOL_TYPE_RENDERED_SCL1 : + CISP_POOL_TYPE_RENDERED; + offset = 0; + for (int j = 0; j < fmt->num_planes; j++) { + args->render.iovas[j] = buf->surfs[0].iova + offset; + args->render.flags[j] = 0x40000000; + offset += fmt->plane_size[j]; + } + + /* + * Queue the buffer as submitted and release the lock for now. + * We need to do this before actually submitting to avoid a + * race with the buffer return codepath. + */ + list_move_tail(&buf->link, &isp->bufs_submitted); spin_unlock_irqrestore(&isp->buf_lock, flags); - kfree(args); - return -EPROTO; - } - args->meta.num_planes = 1; - args->meta.pool_type = CISP_POOL_TYPE_META; - args->meta.iovas[0] = buf->meta->iova; - args->meta.flags[0] = 0x40000000; - - args->render.num_planes = fmt->num_planes; - args->render.pool_type = CISP_POOL_TYPE_RENDERED; - offset = 0; - for (int j = 0; j < fmt->num_planes; j++) { - args->render.iovas[j] = buf->surfs[0].iova + offset; - args->render.flags[j] = 0x40000000; - offset += fmt->plane_size[j]; + args->enable = 0x1; + args->num_buffers = 2; + + req->arg0 = isp->cmd_iova; + req->arg1 = ISP_IPC_BUFEXC_STAT_SIZE; + req->arg2 = ISP_IPC_BUFEXC_FLAG_COMMAND; + + isp_iowrite(isp, req->arg0, args, sizeof(*args)); + err = ipc_chan_send(isp, chan, ISP_BUFFER_TIMEOUT); + if (err) { + /* If we fail, consider the buffer not submitted. */ + dev_err(isp->dev, + "%s: failed to send bufs: [0x%llx, 0x%llx, 0x%llx]\n", + chan->name, req->arg0, req->arg1, req->arg2); + + /* + * Try to find the buffer in the list, and if it's + * still there, move it back to the pending list. + */ + spin_lock_irqsave(&isp->buf_lock, flags); + list_for_each_entry_safe_reverse( + buf2, tmp, &isp->bufs_submitted, link) { + if (buf2 == buf) { + list_move_tail(&buf->link, + &isp->bufs_pending); + spin_unlock_irqrestore(&isp->buf_lock, + flags); + return err; + } + } + /* + * We didn't find the buffer, which means it somehow was returned + * by the firmware even though submission failed? + */ + dev_err(isp->dev, + "buffer submission failed but buffer was returned?\n"); + spin_unlock_irqrestore(&isp->buf_lock, flags); + return err; + } + + spin_lock_irqsave(&isp->buf_lock, flags); } spin_unlock_irqrestore(&isp->buf_lock, flags); - args->enable = 0x1; - args->num_buffers = 2; - - req->arg0 = isp->cmd_iova; - req->arg1 = ISP_IPC_BUFEXC_STAT_SIZE; - req->arg2 = ISP_IPC_BUFEXC_FLAG_COMMAND; - - isp_iowrite(isp, req->arg0, args, sizeof(*args)); - err = ipc_chan_send(isp, chan, ISP_BUFFER_TIMEOUT); - if (err) { - dev_err(isp->dev, - "%s: failed to send bufs: [0x%llx, 0x%llx, 0x%llx]\n", - chan->name, req->arg0, req->arg1, req->arg2); - } - kfree(args); return err; @@ -140,7 +174,7 @@ static int isp_vb2_buf_init(struct vb2_buffer *vb) unsigned int i; int err; - buf->meta = isp_alloc_surface(isp, ISP_META_SIZE); + buf->meta = isp_alloc_surface(isp, isp->hw->meta_size); if (!buf->meta) return -ENOMEM; @@ -179,9 +213,12 @@ static void isp_vb2_release_buffers(struct apple_isp *isp, unsigned long flags; spin_lock_irqsave(&isp->buf_lock, flags); - list_for_each_entry(buf, &isp->buffers, link) + list_for_each_entry(buf, &isp->bufs_submitted, link) + vb2_buffer_done(&buf->vb.vb2_buf, state); + INIT_LIST_HEAD(&isp->bufs_submitted); + list_for_each_entry(buf, &isp->bufs_pending, link) vb2_buffer_done(&buf->vb.vb2_buf, state); - INIT_LIST_HEAD(&isp->buffers); + INIT_LIST_HEAD(&isp->bufs_pending); spin_unlock_irqrestore(&isp->buf_lock, flags); } @@ -194,8 +231,9 @@ static void isp_vb2_buf_queue(struct vb2_buffer *vb) bool empty; spin_lock_irqsave(&isp->buf_lock, flags); - empty = list_empty(&isp->buffers); - list_add_tail(&buf->link, &isp->buffers); + empty = list_empty(&isp->bufs_pending) && + list_empty(&isp->bufs_submitted); + list_add_tail(&buf->link, &isp->bufs_pending); spin_unlock_irqrestore(&isp->buf_lock, flags); if (test_bit(ISP_STATE_STREAMING, &isp->state) && !empty) @@ -249,17 +287,64 @@ static void isp_vb2_stop_streaming(struct vb2_queue *q) } static const struct vb2_ops isp_vb2_ops = { - .queue_setup = isp_vb2_queue_setup, - .buf_init = isp_vb2_buf_init, - .buf_cleanup = isp_vb2_buf_cleanup, - .buf_prepare = isp_vb2_buf_prepare, - .buf_queue = isp_vb2_buf_queue, + .queue_setup = isp_vb2_queue_setup, + .buf_init = isp_vb2_buf_init, + .buf_cleanup = isp_vb2_buf_cleanup, + .buf_prepare = isp_vb2_buf_prepare, + .buf_queue = isp_vb2_buf_queue, .start_streaming = isp_vb2_start_streaming, - .stop_streaming = isp_vb2_stop_streaming, - .wait_prepare = vb2_ops_wait_prepare, - .wait_finish = vb2_ops_wait_finish, + .stop_streaming = isp_vb2_stop_streaming, + .wait_prepare = vb2_ops_wait_prepare, + .wait_finish = vb2_ops_wait_finish, }; +static int isp_set_preset(struct apple_isp *isp, struct isp_format *fmt, + struct isp_preset *preset) +{ + int i; + size_t total_size; + + fmt->preset = preset; + + /* I really fucking hope they all use NV12. */ + fmt->num_planes = 2; + fmt->strides[0] = ALIGN(preset->output_dim.x, ISP_STRIDE_ALIGNMENT); + /* UV subsampled interleaved */ + fmt->strides[1] = ALIGN(preset->output_dim.x, ISP_STRIDE_ALIGNMENT); + fmt->plane_size[0] = fmt->strides[0] * preset->output_dim.y; + fmt->plane_size[1] = fmt->strides[1] * preset->output_dim.y / 2; + + total_size = 0; + for (i = 0; i < fmt->num_planes; i++) + total_size += fmt->plane_size[i]; + fmt->total_size = total_size; + + return 0; +} + +static struct isp_preset *isp_select_preset(struct apple_isp *isp, u32 width, + u32 height) +{ + struct isp_preset *preset, *best = &isp->presets[0]; + int i, score, best_score = INT_MAX; + + /* Default if no dimensions */ + if (width == 0 || height == 0) + return &isp->presets[0]; + + for (i = 0; i < isp->num_presets; i++) { + preset = &isp->presets[i]; + score = abs((int)preset->output_dim.x - (int)width) + + abs((int)preset->output_dim.y - (int)height); + if (score < best_score) { + best = preset; + best_score = score; + } + } + + return best; +} + /* * V4L2 ioctl section */ @@ -290,29 +375,28 @@ static int isp_vidioc_enum_framesizes(struct file *file, void *fh, struct v4l2_frmsizeenum *f) { struct apple_isp *isp = video_drvdata(file); - struct isp_format *fmt = isp_get_current_format(isp); - if (f->index >= ISP_MAX_PIX_FORMATS) + if (f->index >= isp->num_presets) return -EINVAL; - if ((!f->index && f->pixel_format != V4L2_PIX_FMT_NV12) || - (f->index && f->pixel_format != V4L2_PIX_FMT_NV12M)) + if ((f->pixel_format != V4L2_PIX_FMT_NV12) || + (f->pixel_format != V4L2_PIX_FMT_NV12M)) return -EINVAL; - f->discrete.width = fmt->width; - f->discrete.height = fmt->height; + f->discrete.width = isp->presets[f->index].output_dim.x; + f->discrete.height = isp->presets[f->index].output_dim.y; f->type = V4L2_FRMSIZE_TYPE_DISCRETE; return 0; } -static inline void isp_set_sp_pix_format(struct apple_isp *isp, - struct v4l2_format *f) +static inline void isp_get_sp_pix_format(struct apple_isp *isp, + struct v4l2_format *f, + struct isp_format *fmt) { - struct isp_format *fmt = isp_get_current_format(isp); - - f->fmt.pix.width = fmt->width; - f->fmt.pix.height = fmt->height; + f->fmt.pix.width = fmt->preset->output_dim.x; + f->fmt.pix.height = fmt->preset->output_dim.y; + f->fmt.pix.bytesperline = fmt->strides[0]; f->fmt.pix.sizeimage = fmt->total_size; f->fmt.pix.field = V4L2_FIELD_NONE; @@ -322,16 +406,17 @@ static inline void isp_set_sp_pix_format(struct apple_isp *isp, f->fmt.pix.xfer_func = V4L2_XFER_FUNC_709; } -static inline void isp_set_mp_pix_format(struct apple_isp *isp, - struct v4l2_format *f) +static inline void isp_get_mp_pix_format(struct apple_isp *isp, + struct v4l2_format *f, + struct isp_format *fmt) { - struct isp_format *fmt = isp_get_current_format(isp); - - f->fmt.pix_mp.width = fmt->width; - f->fmt.pix_mp.height = fmt->height; + f->fmt.pix_mp.width = fmt->preset->output_dim.x; + f->fmt.pix_mp.height = fmt->preset->output_dim.y; f->fmt.pix_mp.num_planes = fmt->num_planes; - for (int i = 0; i < fmt->num_planes; i++) + for (int i = 0; i < fmt->num_planes; i++) { f->fmt.pix_mp.plane_fmt[i].sizeimage = fmt->plane_size[i]; + f->fmt.pix_mp.plane_fmt[i].bytesperline = fmt->strides[i]; + } f->fmt.pix_mp.field = V4L2_FIELD_NONE; f->fmt.pix_mp.pixelformat = V4L2_PIX_FMT_NV12M; @@ -344,11 +429,12 @@ static int isp_vidioc_get_format(struct file *file, void *fh, struct v4l2_format *f) { struct apple_isp *isp = video_drvdata(file); + struct isp_format *fmt = isp_get_current_format(isp); if (isp->multiplanar) return -ENOTTY; - isp_set_sp_pix_format(isp, f); + isp_get_sp_pix_format(isp, f, fmt); return 0; } @@ -357,11 +443,19 @@ static int isp_vidioc_set_format(struct file *file, void *fh, struct v4l2_format *f) { struct apple_isp *isp = video_drvdata(file); + struct isp_format *fmt = isp_get_current_format(isp); + struct isp_preset *preset; + int err; if (isp->multiplanar) return -ENOTTY; - isp_set_sp_pix_format(isp, f); // no + preset = isp_select_preset(isp, f->fmt.pix.width, f->fmt.pix.height); + err = isp_set_preset(isp, fmt, preset); + if (err) + return err; + + isp_get_sp_pix_format(isp, f, fmt); return 0; } @@ -370,11 +464,19 @@ static int isp_vidioc_try_format(struct file *file, void *fh, struct v4l2_format *f) { struct apple_isp *isp = video_drvdata(file); + struct isp_format fmt = *isp_get_current_format(isp); + struct isp_preset *preset; + int err; if (isp->multiplanar) return -ENOTTY; - isp_set_sp_pix_format(isp, f); // still no + preset = isp_select_preset(isp, f->fmt.pix.width, f->fmt.pix.height); + err = isp_set_preset(isp, &fmt, preset); + if (err) + return err; + + isp_get_sp_pix_format(isp, f, &fmt); return 0; } @@ -383,11 +485,12 @@ static int isp_vidioc_get_format_mplane(struct file *file, void *fh, struct v4l2_format *f) { struct apple_isp *isp = video_drvdata(file); + struct isp_format *fmt = isp_get_current_format(isp); if (!isp->multiplanar) return -ENOTTY; - isp_set_mp_pix_format(isp, f); + isp_get_mp_pix_format(isp, f, fmt); return 0; } @@ -396,11 +499,20 @@ static int isp_vidioc_set_format_mplane(struct file *file, void *fh, struct v4l2_format *f) { struct apple_isp *isp = video_drvdata(file); + struct isp_format *fmt = isp_get_current_format(isp); + struct isp_preset *preset; + int err; if (!isp->multiplanar) return -ENOTTY; - isp_set_mp_pix_format(isp, f); // no + preset = isp_select_preset(isp, f->fmt.pix_mp.width, + f->fmt.pix_mp.height); + err = isp_set_preset(isp, fmt, preset); + if (err) + return err; + + isp_get_mp_pix_format(isp, f, fmt); return 0; } @@ -409,11 +521,20 @@ static int isp_vidioc_try_format_mplane(struct file *file, void *fh, struct v4l2_format *f) { struct apple_isp *isp = video_drvdata(file); + struct isp_format fmt = *isp_get_current_format(isp); + struct isp_preset *preset; + int err; if (!isp->multiplanar) return -ENOTTY; - isp_set_mp_pix_format(isp, f); // still no + preset = isp_select_preset(isp, f->fmt.pix_mp.width, + f->fmt.pix_mp.height); + err = isp_set_preset(isp, &fmt, preset); + if (err) + return err; + + isp_get_mp_pix_format(isp, f, &fmt); return 0; } @@ -472,6 +593,8 @@ static int isp_vidioc_set_param(struct file *file, void *fh, return -EINVAL; /* Not supporting frame rate sets. No use. Plus floats. */ + a->parm.capture.capability = V4L2_CAP_TIMEPERFRAME; + a->parm.capture.readbuffers = ISP_MIN_FRAMES; a->parm.capture.timeperframe.numerator = ISP_FRAME_RATE_NUM; a->parm.capture.timeperframe.denominator = ISP_FRAME_RATE_DEN; @@ -479,59 +602,67 @@ static int isp_vidioc_set_param(struct file *file, void *fh, } static const struct v4l2_ioctl_ops isp_v4l2_ioctl_ops = { - .vidioc_querycap = isp_vidioc_querycap, - - .vidioc_enum_fmt_vid_cap = isp_vidioc_enum_format, - .vidioc_g_fmt_vid_cap = isp_vidioc_get_format, - .vidioc_s_fmt_vid_cap = isp_vidioc_set_format, - .vidioc_try_fmt_vid_cap = isp_vidioc_try_format, - .vidioc_g_fmt_vid_cap_mplane = isp_vidioc_get_format_mplane, - .vidioc_s_fmt_vid_cap_mplane = isp_vidioc_set_format_mplane, - .vidioc_try_fmt_vid_cap_mplane = isp_vidioc_try_format_mplane, - - .vidioc_enum_framesizes = isp_vidioc_enum_framesizes, - .vidioc_enum_input = isp_vidioc_enum_input, - .vidioc_g_input = isp_vidioc_get_input, - .vidioc_s_input = isp_vidioc_set_input, - .vidioc_g_parm = isp_vidioc_get_param, - .vidioc_s_parm = isp_vidioc_set_param, - - .vidioc_reqbufs = vb2_ioctl_reqbufs, - .vidioc_querybuf = vb2_ioctl_querybuf, - .vidioc_create_bufs = vb2_ioctl_create_bufs, - .vidioc_qbuf = vb2_ioctl_qbuf, - .vidioc_expbuf = vb2_ioctl_expbuf, - .vidioc_dqbuf = vb2_ioctl_dqbuf, - .vidioc_prepare_buf = vb2_ioctl_prepare_buf, - .vidioc_streamon = vb2_ioctl_streamon, - .vidioc_streamoff = vb2_ioctl_streamoff, + .vidioc_querycap = isp_vidioc_querycap, + + .vidioc_enum_fmt_vid_cap = isp_vidioc_enum_format, + .vidioc_g_fmt_vid_cap = isp_vidioc_get_format, + .vidioc_s_fmt_vid_cap = isp_vidioc_set_format, + .vidioc_try_fmt_vid_cap = isp_vidioc_try_format, + .vidioc_g_fmt_vid_cap_mplane = isp_vidioc_get_format_mplane, + .vidioc_s_fmt_vid_cap_mplane = isp_vidioc_set_format_mplane, + .vidioc_try_fmt_vid_cap_mplane = isp_vidioc_try_format_mplane, + + .vidioc_enum_framesizes = isp_vidioc_enum_framesizes, + .vidioc_enum_input = isp_vidioc_enum_input, + .vidioc_g_input = isp_vidioc_get_input, + .vidioc_s_input = isp_vidioc_set_input, + .vidioc_g_parm = isp_vidioc_get_param, + .vidioc_s_parm = isp_vidioc_set_param, + + .vidioc_reqbufs = vb2_ioctl_reqbufs, + .vidioc_querybuf = vb2_ioctl_querybuf, + .vidioc_create_bufs = vb2_ioctl_create_bufs, + .vidioc_qbuf = vb2_ioctl_qbuf, + .vidioc_expbuf = vb2_ioctl_expbuf, + .vidioc_dqbuf = vb2_ioctl_dqbuf, + .vidioc_prepare_buf = vb2_ioctl_prepare_buf, + .vidioc_streamon = vb2_ioctl_streamon, + .vidioc_streamoff = vb2_ioctl_streamoff, }; static const struct v4l2_file_operations isp_v4l2_fops = { - .owner = THIS_MODULE, - .open = v4l2_fh_open, - .release = vb2_fop_release, - .read = vb2_fop_read, - .poll = vb2_fop_poll, - .mmap = vb2_fop_mmap, + .owner = THIS_MODULE, + .open = v4l2_fh_open, + .release = vb2_fop_release, + .read = vb2_fop_read, + .poll = vb2_fop_poll, + .mmap = vb2_fop_mmap, .unlocked_ioctl = video_ioctl2, }; static const struct media_device_ops isp_media_device_ops = { - .link_notify = v4l2_pipeline_link_notify, + .link_notify = v4l2_pipeline_link_notify, }; int apple_isp_setup_video(struct apple_isp *isp) { struct video_device *vdev = &isp->vdev; struct vb2_queue *vbq = &isp->vbq; + struct isp_format *fmt = isp_get_current_format(isp); int err; + err = isp_set_preset(isp, fmt, &isp->presets[0]); + if (err) { + dev_err(isp->dev, "failed to set default preset: %d\n", err); + return err; + } + media_device_init(&isp->mdev); isp->v4l2_dev.mdev = &isp->mdev; isp->mdev.ops = &isp_media_device_ops; isp->mdev.dev = isp->dev; - strscpy(isp->mdev.model, APPLE_ISP_DEVICE_NAME, sizeof(isp->mdev.model)); + strscpy(isp->mdev.model, APPLE_ISP_DEVICE_NAME, + sizeof(isp->mdev.model)); err = media_device_register(&isp->mdev); if (err) { From f8b5ae1ca914b21a36d698f6e0fe9ebebfd85d8e Mon Sep 17 00:00:00 2001 From: Asahi Lina Date: Fri, 29 Sep 2023 16:06:31 +0900 Subject: [PATCH 0289/3902] media: apple: isp: Always enable singleplane API, make multiple a module param This requires modifying the vbq type when set_format is called, depending on the style... this is ugly, but it should work? Multiplane is still quite broken, but this enables testing it with gstreamer. Still lots of things to fix to make this actually work. Signed-off-by: Asahi Lina --- drivers/media/platform/apple/isp/isp-v4l2.c | 49 ++++++++++++++------- 1 file changed, 32 insertions(+), 17 deletions(-) diff --git a/drivers/media/platform/apple/isp/isp-v4l2.c b/drivers/media/platform/apple/isp/isp-v4l2.c index a35b9cbf20fef9..1d1e8a8bd6c81e 100644 --- a/drivers/media/platform/apple/isp/isp-v4l2.c +++ b/drivers/media/platform/apple/isp/isp-v4l2.c @@ -1,6 +1,8 @@ // SPDX-License-Identifier: GPL-2.0-only /* Copyright 2023 Eileen Yoon */ +#include + #include #include #include @@ -19,6 +21,10 @@ #define ISP_BUFFER_TIMEOUT msecs_to_jiffies(1500) #define ISP_STRIDE_ALIGNMENT 64 +static bool multiplanar = false; +module_param(multiplanar, bool, 0644); +MODULE_PARM_DESC(multiplanar, "Enable multiplanar API"); + struct isp_h2t_buffer { u64 iovas[ISP_MAX_PLANES]; u32 flags[ISP_MAX_PLANES]; @@ -360,13 +366,23 @@ static int isp_vidioc_querycap(struct file *file, void *priv, static int isp_vidioc_enum_format(struct file *file, void *fh, struct v4l2_fmtdesc *f) { + struct apple_isp *isp = video_drvdata(file); + if (f->index >= ISP_MAX_PIX_FORMATS) return -EINVAL; - if (!f->index) + switch (f->index) { + case 0: f->pixelformat = V4L2_PIX_FMT_NV12; - else + break; + case 1: + if (!isp->multiplanar) + return -EINVAL; f->pixelformat = V4L2_PIX_FMT_NV12M; + break; + default: + return -EINVAL; + } return 0; } @@ -379,7 +395,7 @@ static int isp_vidioc_enum_framesizes(struct file *file, void *fh, if (f->index >= isp->num_presets) return -EINVAL; - if ((f->pixel_format != V4L2_PIX_FMT_NV12) || + if ((f->pixel_format != V4L2_PIX_FMT_NV12) && (f->pixel_format != V4L2_PIX_FMT_NV12M)) return -EINVAL; @@ -431,9 +447,6 @@ static int isp_vidioc_get_format(struct file *file, void *fh, struct apple_isp *isp = video_drvdata(file); struct isp_format *fmt = isp_get_current_format(isp); - if (isp->multiplanar) - return -ENOTTY; - isp_get_sp_pix_format(isp, f, fmt); return 0; @@ -447,9 +460,6 @@ static int isp_vidioc_set_format(struct file *file, void *fh, struct isp_preset *preset; int err; - if (isp->multiplanar) - return -ENOTTY; - preset = isp_select_preset(isp, f->fmt.pix.width, f->fmt.pix.height); err = isp_set_preset(isp, fmt, preset); if (err) @@ -457,6 +467,8 @@ static int isp_vidioc_set_format(struct file *file, void *fh, isp_get_sp_pix_format(isp, f, fmt); + isp->vbq.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + return 0; } @@ -468,9 +480,6 @@ static int isp_vidioc_try_format(struct file *file, void *fh, struct isp_preset *preset; int err; - if (isp->multiplanar) - return -ENOTTY; - preset = isp_select_preset(isp, f->fmt.pix.width, f->fmt.pix.height); err = isp_set_preset(isp, &fmt, preset); if (err) @@ -514,6 +523,8 @@ static int isp_vidioc_set_format_mplane(struct file *file, void *fh, isp_get_mp_pix_format(isp, f, fmt); + isp->vbq.type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE; + return 0; } @@ -571,8 +582,9 @@ static int isp_vidioc_get_param(struct file *file, void *fh, { struct apple_isp *isp = video_drvdata(file); - if (a->type != (isp->multiplanar ? V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE : - V4L2_BUF_TYPE_VIDEO_CAPTURE)) + if (a->type != V4L2_BUF_TYPE_VIDEO_CAPTURE && + (!isp->multiplanar || + a->type != V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE)) return -EINVAL; a->parm.capture.capability = V4L2_CAP_TIMEPERFRAME; @@ -588,8 +600,9 @@ static int isp_vidioc_set_param(struct file *file, void *fh, { struct apple_isp *isp = video_drvdata(file); - if (a->type != (isp->multiplanar ? V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE : - V4L2_BUF_TYPE_VIDEO_CAPTURE)) + if (a->type != V4L2_BUF_TYPE_VIDEO_CAPTURE && + (!isp->multiplanar || + a->type != V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE)) return -EINVAL; /* Not supporting frame rate sets. No use. Plus floats. */ @@ -670,7 +683,7 @@ int apple_isp_setup_video(struct apple_isp *isp) goto media_cleanup; } - isp->multiplanar = 0; + isp->multiplanar = multiplanar; err = v4l2_device_register(isp->dev, &isp->v4l2_dev); if (err) { @@ -699,6 +712,8 @@ int apple_isp_setup_video(struct apple_isp *isp) vdev->fops = &isp_v4l2_fops; vdev->ioctl_ops = &isp_v4l2_ioctl_ops; vdev->device_caps = V4L2_BUF_TYPE_VIDEO_CAPTURE | V4L2_CAP_STREAMING; + if (isp->multiplanar) + vdev->device_caps |= V4L2_CAP_VIDEO_CAPTURE_MPLANE; vdev->v4l2_dev = &isp->v4l2_dev; vdev->vfl_type = VFL_TYPE_VIDEO; vdev->vfl_dir = VFL_DIR_RX; From bfce960130f9704bc07a840bbbed7bb7110deb3f Mon Sep 17 00:00:00 2001 From: Asahi Lina Date: Fri, 29 Sep 2023 19:10:58 +0900 Subject: [PATCH 0290/3902] media: apple: isp: Switch to threaded IRQs There's no reason to run all the command handling in hard IRQ context. Let's switch to threaded IRQs, which should simplify some things. Signed-off-by: Asahi Lina --- drivers/media/platform/apple/isp/isp-fw.c | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/drivers/media/platform/apple/isp/isp-fw.c b/drivers/media/platform/apple/isp/isp-fw.c index 972867a93a0193..01b81714547206 100644 --- a/drivers/media/platform/apple/isp/isp-fw.c +++ b/drivers/media/platform/apple/isp/isp-fw.c @@ -93,6 +93,13 @@ static irqreturn_t apple_isp_isr(int irq, void *dev) isp_mbox_write32(isp, ISP_MBOX_IRQ_ACK, isp_mbox_read32(isp, ISP_MBOX_IRQ_INTERRUPT)); + return IRQ_WAKE_THREAD; +} + +static irqreturn_t apple_isp_isr_thread(int irq, void *dev) +{ + struct apple_isp *isp = dev; + wake_up_interruptible_all(&isp->wait); ipc_chan_handle(isp, isp->chan_sm); @@ -117,7 +124,8 @@ static int isp_enable_irq(struct apple_isp *isp) { int err; - err = request_irq(isp->irq, apple_isp_isr, 0, "apple-isp", isp); + err = request_threaded_irq(isp->irq, apple_isp_isr, + apple_isp_isr_thread, 0, "apple-isp", isp); if (err < 0) { isp_err(isp, "failed to request IRQ#%u (%d)\n", isp->irq, err); return err; From e87dadec3790e31957adc944cd9dab2488898255 Mon Sep 17 00:00:00 2001 From: Asahi Lina Date: Fri, 29 Sep 2023 18:38:22 +0900 Subject: [PATCH 0291/3902] media: apple: isp: Remove ioread/iowrite and stop doing raw address translation Translating IOVAs via the DART and then trying to access physical memory directly is slow and error-prone. We know what surfaces IOVAs are supposed to be part of, so we can use the surface vmap to access the contents. Where we get an IOVA from the firmware, assert that it is within the expected range before accessing it. Since we're using threaded IRQs now, this also lets us get rid of the deferred vmap. Signed-off-by: Asahi Lina --- drivers/media/platform/apple/isp/isp-cam.c | 2 +- drivers/media/platform/apple/isp/isp-cmd.c | 5 +- drivers/media/platform/apple/isp/isp-drv.h | 5 + drivers/media/platform/apple/isp/isp-fw.c | 70 +++++++++++-- drivers/media/platform/apple/isp/isp-fw.h | 9 ++ drivers/media/platform/apple/isp/isp-iommu.c | 6 -- drivers/media/platform/apple/isp/isp-iommu.h | 15 --- drivers/media/platform/apple/isp/isp-ipc.c | 105 +++++++++---------- drivers/media/platform/apple/isp/isp-v4l2.c | 2 +- 9 files changed, 130 insertions(+), 89 deletions(-) diff --git a/drivers/media/platform/apple/isp/isp-cam.c b/drivers/media/platform/apple/isp/isp-cam.c index 593b780ab73b15..abdc9e345933d8 100644 --- a/drivers/media/platform/apple/isp/isp-cam.c +++ b/drivers/media/platform/apple/isp/isp-cam.c @@ -323,7 +323,7 @@ static int isp_ch_load_setfile(struct apple_isp *isp, u32 ch) return -EINVAL; } - isp_iowrite(isp, isp->data_surf->iova, (void *)fw->data, setfile->size); + memcpy(isp->data_surf->virt, (void *)fw->data, setfile->size); release_firmware(fw); return isp_cmd_ch_set_file_load(isp, ch, isp->data_surf->iova, diff --git a/drivers/media/platform/apple/isp/isp-cmd.c b/drivers/media/platform/apple/isp/isp-cmd.c index 1e812400e52f7d..1166f0990830ed 100644 --- a/drivers/media/platform/apple/isp/isp-cmd.c +++ b/drivers/media/platform/apple/isp/isp-cmd.c @@ -24,7 +24,7 @@ static int cisp_send(struct apple_isp *isp, void *args, u32 insize, u32 outsize) req->arg1 = insize; req->arg2 = outsize; - isp_iowrite(isp, isp->cmd_iova, args, insize); + memcpy(isp->cmd_virt, args, insize); err = ipc_chan_send(isp, chan, CISP_TIMEOUT); if (err) { u64 opcode; @@ -45,7 +45,8 @@ static int cisp_send_read(struct apple_isp *isp, void *args, u32 insize, int err = cisp_send(isp, args, insize, outsize); if (err) return err; - isp_ioread(isp, isp->cmd_iova, args, outsize); + + memcpy(args, isp->cmd_virt, outsize); return 0; } diff --git a/drivers/media/platform/apple/isp/isp-drv.h b/drivers/media/platform/apple/isp/isp-drv.h index 926c921849544a..26b9ee0e4d709f 100644 --- a/drivers/media/platform/apple/isp/isp-drv.h +++ b/drivers/media/platform/apple/isp/isp-drv.h @@ -32,6 +32,7 @@ struct isp_surf { struct drm_mm_node *mm; struct list_head head; u64 size; + u64 type; u32 num_pages; struct page **pages; struct sg_table sgt; @@ -60,6 +61,7 @@ struct isp_channel { u32 num; u64 size; dma_addr_t iova; + void *virt; u32 doorbell; u32 cursor; spinlock_t lock; @@ -210,6 +212,8 @@ struct apple_isp { struct isp_surf *ipc_surf; struct isp_surf *extra_surf; struct isp_surf *data_surf; + struct isp_surf *log_surf; + struct isp_surf *bt_surf; struct list_head gc; struct workqueue_struct *wq; @@ -225,6 +229,7 @@ struct apple_isp { wait_queue_head_t wait; dma_addr_t cmd_iova; + void *cmd_virt; unsigned long state; spinlock_t buf_lock; diff --git a/drivers/media/platform/apple/isp/isp-fw.c b/drivers/media/platform/apple/isp/isp-fw.c index 01b81714547206..70e201ea1ebd6f 100644 --- a/drivers/media/platform/apple/isp/isp-fw.c +++ b/drivers/media/platform/apple/isp/isp-fw.c @@ -1,6 +1,9 @@ // SPDX-License-Identifier: GPL-2.0-only /* Copyright 2023 Eileen Yoon */ +#include "isp-fw.h" + +#include #include #include #include @@ -38,6 +41,35 @@ static inline void isp_gpio_write32(struct apple_isp *isp, u32 reg, u32 val) writel(val, isp->gpio + reg); } +void *apple_isp_translate(struct apple_isp *isp, struct isp_surf *surf, + dma_addr_t iova, size_t size) +{ + dma_addr_t end = iova + size; + if (!surf) { + dev_err(isp->dev, + "Failed to translate IPC iova 0x%llx (0x%zx): No surface\n", + (long long)iova, size); + return NULL; + } + + if (end < iova || iova < surf->iova || + end > (surf->iova + surf->size)) { + dev_err(isp->dev, + "Failed to translate IPC iova 0x%llx (0x%zx): Out of bounds\n", + (long long)iova, size); + return NULL; + } + + if (!surf->virt) { + dev_err(isp->dev, + "Failed to translate IPC iova 0x%llx (0x%zx): No VMap\n", + (long long)iova, size); + return NULL; + } + + return surf->virt + (iova - surf->iova); +} + struct isp_firmware_bootargs { u32 pad_0[2]; u64 ipc_iova; @@ -232,6 +264,8 @@ int apple_isp_alloc_firmware_surface(struct apple_isp *isp) isp_err(isp, "failed to alloc shared surface for ipc\n"); return -ENOMEM; } + dev_info(isp->dev, "IPC surface iova: 0x%llx\n", + (long long)isp->ipc_surf->iova); isp->data_surf = isp_alloc_surface_vmap(isp, ISP_FIRMWARE_DATA_SIZE); if (!isp->data_surf) { @@ -239,6 +273,8 @@ int apple_isp_alloc_firmware_surface(struct apple_isp *isp) isp_free_surface(isp, isp->ipc_surf); return -ENOMEM; } + dev_info(isp->dev, "Data surface iova: 0x%llx\n", + (long long)isp->data_surf->iova); return 0; } @@ -258,6 +294,7 @@ static int isp_firmware_boot_stage2(struct apple_isp *isp) { struct isp_firmware_bootargs args; dma_addr_t args_iova; + void *args_virt; int err, retries; u32 num_ipc_chans = isp_gpio_read32(isp, ISP_GPIO_0); @@ -281,7 +318,9 @@ static int isp_firmware_boot_stage2(struct apple_isp *isp) } args_iova = isp->ipc_surf->iova + args_offset + 0x40; + args_virt = isp->ipc_surf->virt + args_offset + 0x40; isp->cmd_iova = args_iova + sizeof(args) + 0x40; + isp->cmd_virt = args_virt + sizeof(args) + 0x40; memset(&args, 0, sizeof(args)); args.ipc_iova = isp->ipc_surf->iova; @@ -295,7 +334,7 @@ static int isp_firmware_boot_stage2(struct apple_isp *isp) args.unk7 = 0x1; // 0? args.unk_iova1 = args_iova + sizeof(args) - 0xc; args.unk9 = 0x3; - isp_iowrite(isp, args_iova, &args, sizeof(args)); + memcpy(args_virt, &args, sizeof(args)); isp_gpio_write32(isp, ISP_GPIO_0, args_iova); isp_gpio_write32(isp, ISP_GPIO_1, args_iova >> 32); @@ -355,7 +394,15 @@ static void isp_free_channel_info(struct apple_isp *isp) static int isp_fill_channel_info(struct apple_isp *isp) { u64 table_iova = isp_gpio_read32(isp, ISP_GPIO_0) | - ((u64)isp_gpio_read32(isp, ISP_GPIO_1)) << 32; + ((u64)isp_gpio_read32(isp, ISP_GPIO_1)) << 32; + void *table_virt = apple_isp_ipc_translate( + isp, table_iova, + sizeof(struct isp_chan_desc) * isp->num_ipc_chans); + + if (!table_virt) { + dev_err(isp->dev, "Failed to find channel table\n"); + return -EIO; + } isp->ipc_chans = kcalloc(isp->num_ipc_chans, sizeof(struct isp_channel *), GFP_KERNEL); @@ -364,14 +411,14 @@ static int isp_fill_channel_info(struct apple_isp *isp) for (int i = 0; i < isp->num_ipc_chans; i++) { struct isp_chan_desc desc; - dma_addr_t desc_iova = table_iova + (i * sizeof(desc)); + void *desc_virt = table_virt + (i * sizeof(desc)); struct isp_channel *chan = kzalloc(sizeof(struct isp_channel), GFP_KERNEL); if (!chan) goto out; isp->ipc_chans[i] = chan; - isp_ioread(isp, desc_iova, &desc, sizeof(desc)); + memcpy(&desc, desc_virt, sizeof(desc)); chan->name = kstrdup(desc.name, GFP_KERNEL); chan->type = desc.type; chan->src = desc.src; @@ -379,9 +426,16 @@ static int isp_fill_channel_info(struct apple_isp *isp) chan->num = desc.num; chan->size = desc.num * ISP_IPC_MESSAGE_SIZE; chan->iova = desc.iova; + chan->virt = + apple_isp_ipc_translate(isp, desc.iova, chan->size); chan->cursor = 0; spin_lock_init(&chan->lock); + if (!chan->virt) { + dev_err(isp->dev, "Failed to find channel buffer\n"); + goto out; + } + if ((chan->type != ISP_IPC_CHAN_TYPE_COMMAND) && (chan->type != ISP_IPC_CHAN_TYPE_REPLY) && (chan->type != ISP_IPC_CHAN_TYPE_REPORT)) { @@ -439,11 +493,11 @@ static int isp_firmware_boot_stage3(struct apple_isp *isp) continue; for (int j = 0; j < chan->num; j++) { struct isp_message msg; - dma_addr_t msg_iova = chan->iova + (j * sizeof(msg)); + void *msg_virt = chan->virt + (j * sizeof(msg)); memset(&msg, 0, sizeof(msg)); msg.arg0 = ISP_IPC_FLAG_ACK; - isp_iowrite(isp, msg_iova, &msg, sizeof(msg)); + memcpy(msg_virt, &msg, sizeof(msg)); } } wmb(); @@ -547,6 +601,10 @@ static int isp_start_command_processor(struct apple_isp *isp) static void isp_collect_gc_surface(struct apple_isp *isp) { struct isp_surf *tmp, *surf; + + isp->log_surf = NULL; + isp->bt_surf = NULL; + list_for_each_entry_safe_reverse(surf, tmp, &isp->gc, head) { isp_dbg(isp, "freeing iova: 0x%llx size: 0x%llx virt: %pS\n", surf->iova, surf->size, (void *)surf->virt); diff --git a/drivers/media/platform/apple/isp/isp-fw.h b/drivers/media/platform/apple/isp/isp-fw.h index 264717793cea02..974216f0989f91 100644 --- a/drivers/media/platform/apple/isp/isp-fw.h +++ b/drivers/media/platform/apple/isp/isp-fw.h @@ -12,4 +12,13 @@ void apple_isp_free_firmware_surface(struct apple_isp *isp); int apple_isp_firmware_boot(struct apple_isp *isp); void apple_isp_firmware_shutdown(struct apple_isp *isp); +void *apple_isp_translate(struct apple_isp *isp, struct isp_surf *surf, + dma_addr_t iova, size_t size); + +static inline void *apple_isp_ipc_translate(struct apple_isp *isp, + dma_addr_t iova, size_t size) +{ + return apple_isp_translate(isp, isp->ipc_surf, iova, size); +} + #endif /* __ISP_FW_H__ */ diff --git a/drivers/media/platform/apple/isp/isp-iommu.c b/drivers/media/platform/apple/isp/isp-iommu.c index 0a9d0d6a350c9a..845c35da0253ae 100644 --- a/drivers/media/platform/apple/isp/isp-iommu.c +++ b/drivers/media/platform/apple/isp/isp-iommu.c @@ -213,12 +213,6 @@ void isp_free_surface(struct apple_isp *isp, struct isp_surf *surf) } } -void *isp_iotranslate(struct apple_isp *isp, dma_addr_t iova) -{ - phys_addr_t phys = iommu_iova_to_phys(isp->domain, iova); - return phys_to_virt(phys); -} - int apple_isp_iommu_map_sgt(struct apple_isp *isp, struct isp_surf *surf, struct sg_table *sgt, u64 size) { diff --git a/drivers/media/platform/apple/isp/isp-iommu.h b/drivers/media/platform/apple/isp/isp-iommu.h index 326cf7c12aa745..b99a182e284b72 100644 --- a/drivers/media/platform/apple/isp/isp-iommu.h +++ b/drivers/media/platform/apple/isp/isp-iommu.h @@ -12,21 +12,6 @@ struct isp_surf *__isp_alloc_surface(struct apple_isp *isp, u64 size, bool gc); struct isp_surf *isp_alloc_surface_vmap(struct apple_isp *isp, u64 size); int isp_surf_vmap(struct apple_isp *isp, struct isp_surf *surf); void isp_free_surface(struct apple_isp *isp, struct isp_surf *surf); -void *isp_iotranslate(struct apple_isp *isp, dma_addr_t iova); - -static inline void isp_ioread(struct apple_isp *isp, dma_addr_t iova, - void *data, u64 size) -{ - void *virt = isp_iotranslate(isp, iova); - memcpy(data, virt, size); -} - -static inline void isp_iowrite(struct apple_isp *isp, dma_addr_t iova, - void *data, u64 size) -{ - void *virt = isp_iotranslate(isp, iova); - memcpy(virt, data, size); -} int apple_isp_iommu_map_sgt(struct apple_isp *isp, struct isp_surf *surf, struct sg_table *sgt, u64 size); diff --git a/drivers/media/platform/apple/isp/isp-ipc.c b/drivers/media/platform/apple/isp/isp-ipc.c index 14249a44798ba5..00bd7642177a59 100644 --- a/drivers/media/platform/apple/isp/isp-ipc.c +++ b/drivers/media/platform/apple/isp/isp-ipc.c @@ -4,6 +4,7 @@ #include "isp-iommu.h" #include "isp-ipc.h" #include "isp-regs.h" +#include "isp-fw.h" #define ISP_IPC_FLAG_TERMINAL_ACK 0x3 #define ISP_IPC_BUFEXC_STAT_META_OFFSET 0x10 @@ -54,16 +55,16 @@ struct isp_bufexc_stat { } __packed; static_assert(sizeof(struct isp_bufexc_stat) == ISP_IPC_BUFEXC_STAT_SIZE); -static inline dma_addr_t chan_msg_iova(struct isp_channel *chan, u32 index) +static inline void *chan_msg_virt(struct isp_channel *chan, u32 index) { - return chan->iova + (index * ISP_IPC_MESSAGE_SIZE); + return chan->virt + (index * ISP_IPC_MESSAGE_SIZE); } static inline void chan_read_msg_index(struct apple_isp *isp, struct isp_channel *chan, struct isp_message *msg, u32 index) { - isp_ioread(isp, chan_msg_iova(chan, index), msg, sizeof(*msg)); + memcpy(msg, chan_msg_virt(chan, index), sizeof(*msg)); } static inline void chan_read_msg(struct apple_isp *isp, @@ -77,7 +78,7 @@ static inline void chan_write_msg_index(struct apple_isp *isp, struct isp_channel *chan, struct isp_message *msg, u32 index) { - isp_iowrite(isp, chan_msg_iova(chan, index), msg, sizeof(*msg)); + memcpy(chan_msg_virt(chan, index), msg, sizeof(*msg)); } static inline void chan_write_msg(struct apple_isp *isp, @@ -191,10 +192,14 @@ int ipc_tm_handle(struct apple_isp *isp, struct isp_channel *chan) char buf[512]; dma_addr_t iova = req->arg0 & ~ISP_IPC_FLAG_TERMINAL_ACK; u32 size = req->arg1; - if (iova && size && test_bit(ISP_STATE_LOGGING, &isp->state)) { - size = min_t(u32, size, 512); - isp_ioread(isp, iova, buf, size); - isp_dbg(isp, "ISPASC: %.*s", size, buf); + if (iova && size && size < sizeof(buf) && + test_bit(ISP_STATE_LOGGING, &isp->state)) { + void *p = apple_isp_translate(isp, isp->log_surf, iova, size); + if (p) { + size = min_t(u32, size, 512); + memcpy(buf, p, size); + isp_dbg(isp, "ISPASC: %.*s", size, buf); + } } #endif @@ -205,55 +210,15 @@ int ipc_tm_handle(struct apple_isp *isp, struct isp_channel *chan) return 0; } -/* The kernel accesses exactly two dynamically allocated shared surfaces: - * 1) LOG: Surface for terminal logs. Optional, only enabled in debug builds. - * 2) STAT: Surface for BUFT2H rendered frame stat buffer. We isp_ioread() in - * the BUFT2H ISR below. Since the BUFT2H IRQ is triggered by the BUF_H2T - * doorbell, the STAT vmap must complete before the first buffer submission - * under VIDIOC_STREAMON(). The CISP_CMD_PRINT_ENABLE completion depends on the - * STAT buffer SHAREDMALLOC ISR, which is part of the firmware initialization - * sequence. We also call flush_workqueue(), so a fault should not occur. - */ -static void sm_malloc_deferred_worker(struct work_struct *work) -{ - struct isp_sm_deferred_work *dwork = - container_of(work, struct isp_sm_deferred_work, work); - struct apple_isp *isp = dwork->isp; - struct isp_surf *surf = dwork->surf; - int err; - - err = isp_surf_vmap(isp, surf); /* Can't vmap in interrupt ctx */ - if (err < 0) { - isp_err(isp, "failed to vmap iova=0x%llx size=0x%llx\n", - surf->iova, surf->size); - goto out; - } - -#ifdef APPLE_ISP_DEBUG - /* Only enabled in debug builds so it shouldn't matter, but - * the LOG surface is always the first surface requested. - */ - if (!test_bit(ISP_STATE_LOGGING, &isp->state)) - set_bit(ISP_STATE_LOGGING, &isp->state); -#endif - -out: - kfree(dwork); -} - int ipc_sm_handle(struct apple_isp *isp, struct isp_channel *chan) { struct isp_message *req = &chan->req, *rsp = &chan->rsp; + int err; if (req->arg0 == 0x0) { struct isp_sm_deferred_work *dwork; struct isp_surf *surf; - dwork = kzalloc(sizeof(*dwork), GFP_KERNEL); - if (!dwork) - return -ENOMEM; - dwork->isp = isp; - surf = isp_alloc_surface_gc(isp, req->arg1); if (!surf) { isp_err(isp, "failed to alloc requested size 0x%llx\n", @@ -261,19 +226,36 @@ int ipc_sm_handle(struct apple_isp *isp, struct isp_channel *chan) kfree(dwork); return -ENOMEM; } - dwork->surf = surf; + surf->type = req->arg2; rsp->arg0 = surf->iova | ISP_IPC_FLAG_ACK; rsp->arg1 = 0x0; rsp->arg2 = 0x0; /* macOS uses this to index surfaces */ - INIT_WORK(&dwork->work, sm_malloc_deferred_worker); - if (!queue_work(isp->wq, &dwork->work)) { - isp_err(isp, "failed to queue deferred work\n"); - isp_free_surface(isp, surf); - kfree(dwork); - return -ENOMEM; + err = isp_surf_vmap(isp, surf); + if (err < 0) { + isp_err(isp, "failed to vmap iova=0x%llx size=0x%llx\n", + surf->iova, surf->size); + } else { + switch (surf->type) { + case 0x4c4f47: /* "LOG" */ + isp->log_surf = surf; + break; + case 0x4d495343: /* "MISC" */ + /* Hacky... maybe there's a better way to identify this surface? */ + if (surf->size == 0xc000) + isp->bt_surf = surf; + break; + } } + +#ifdef APPLE_ISP_DEBUG + /* Only enabled in debug builds so it shouldn't matter, but + * the LOG surface is always the first surface requested. + */ + if (!test_bit(ISP_STATE_LOGGING, &isp->state)) + set_bit(ISP_STATE_LOGGING, &isp->state); +#endif /* To the gc it goes... */ } else { @@ -302,8 +284,15 @@ int ipc_bt_handle(struct apple_isp *isp, struct isp_channel *chan) /* No need to read the whole struct */ u64 meta_iova; - isp_ioread(isp, req->arg0 + ISP_IPC_BUFEXC_STAT_META_OFFSET, &meta_iova, - sizeof(meta_iova)); + u64 *p_meta_iova = apple_isp_translate( + isp, isp->bt_surf, req->arg0 + ISP_IPC_BUFEXC_STAT_META_OFFSET, + sizeof(u64)); + + if (!p_meta_iova) { + dev_err(isp->dev, "Failed to find bufexc stat meta\n"); + return -EIO; + } + meta_iova = *p_meta_iova; spin_lock(&isp->buf_lock); list_for_each_entry_safe_reverse(buf, tmp, &isp->bufs_submitted, link) { diff --git a/drivers/media/platform/apple/isp/isp-v4l2.c b/drivers/media/platform/apple/isp/isp-v4l2.c index 1d1e8a8bd6c81e..daa49f8e3214dc 100644 --- a/drivers/media/platform/apple/isp/isp-v4l2.c +++ b/drivers/media/platform/apple/isp/isp-v4l2.c @@ -91,7 +91,7 @@ static int isp_submit_buffers(struct apple_isp *isp) req->arg1 = ISP_IPC_BUFEXC_STAT_SIZE; req->arg2 = ISP_IPC_BUFEXC_FLAG_COMMAND; - isp_iowrite(isp, req->arg0, args, sizeof(*args)); + memcpy(isp->cmd_virt, args, sizeof(*args)); err = ipc_chan_send(isp, chan, ISP_BUFFER_TIMEOUT); if (err) { /* If we fail, consider the buffer not submitted. */ From 2570d4bff2c7d4cad1f32f2b4faf3fe92f576513 Mon Sep 17 00:00:00 2001 From: Asahi Lina Date: Fri, 29 Sep 2023 19:18:40 +0900 Subject: [PATCH 0292/3902] media: apple: isp: Propagate EINTR from firmware loads Signed-off-by: Asahi Lina --- drivers/media/platform/apple/isp/isp-cam.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/drivers/media/platform/apple/isp/isp-cam.c b/drivers/media/platform/apple/isp/isp-cam.c index abdc9e345933d8..9ccdc2a1304bed 100644 --- a/drivers/media/platform/apple/isp/isp-cam.c +++ b/drivers/media/platform/apple/isp/isp-cam.c @@ -340,6 +340,10 @@ static int isp_ch_configure_capture(struct apple_isp *isp, u32 ch) if (err) { dev_err(isp->dev, "warning: calibration data not loaded: %d\n", err); + + /* If this failed due to a signal, propagate */ + if (err == -EINTR) + return err; } if (isp->hw->gen >= ISP_GEN_T8112) { From 8fffaddf2356514f946386a456020a718dfdd985 Mon Sep 17 00:00:00 2001 From: Asahi Lina Date: Fri, 29 Sep 2023 19:19:12 +0900 Subject: [PATCH 0293/3902] media: apple: isp: Implement posted commands Useful for shutdown type commands which may not be acked... Signed-off-by: Asahi Lina --- drivers/media/platform/apple/isp/isp-cmd.c | 11 ++++++----- drivers/media/platform/apple/isp/isp-ipc.c | 3 +++ 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/drivers/media/platform/apple/isp/isp-cmd.c b/drivers/media/platform/apple/isp/isp-cmd.c index 1166f0990830ed..26ae639b3a63d9 100644 --- a/drivers/media/platform/apple/isp/isp-cmd.c +++ b/drivers/media/platform/apple/isp/isp-cmd.c @@ -10,11 +10,12 @@ #define CISP_OPCODE_GET(x) (((u64)(x)) >> CISP_OPCODE_SHIFT) #define CISP_TIMEOUT msecs_to_jiffies(3000) -#define CISP_SEND_IN(x, a) (cisp_send((x), &(a), sizeof(a), 0)) -#define CISP_SEND_INOUT(x, a) (cisp_send((x), &(a), sizeof(a), sizeof(a))) +#define CISP_SEND_IN(x, a) (cisp_send((x), &(a), sizeof(a), 0, CISP_TIMEOUT)) +#define CISP_SEND_INOUT(x, a) (cisp_send((x), &(a), sizeof(a), sizeof(a), CISP_TIMEOUT)) #define CISP_SEND_OUT(x, a) (cisp_send_read((x), (a), sizeof(*a), sizeof(*a))) +#define CISP_POST_IN(x, a) (cisp_send((x), &(a), sizeof(a), 0, 0)) -static int cisp_send(struct apple_isp *isp, void *args, u32 insize, u32 outsize) +static int cisp_send(struct apple_isp *isp, void *args, u32 insize, u32 outsize, int timeout) { struct isp_channel *chan = isp->chan_io; struct isp_message *req = &chan->req; @@ -25,7 +26,7 @@ static int cisp_send(struct apple_isp *isp, void *args, u32 insize, u32 outsize) req->arg2 = outsize; memcpy(isp->cmd_virt, args, insize); - err = ipc_chan_send(isp, chan, CISP_TIMEOUT); + err = ipc_chan_send(isp, chan, timeout); if (err) { u64 opcode; memcpy(&opcode, args, sizeof(opcode)); @@ -42,7 +43,7 @@ static int cisp_send_read(struct apple_isp *isp, void *args, u32 insize, u32 outsize) { /* TODO do I need to lock the iova space? */ - int err = cisp_send(isp, args, insize, outsize); + int err = cisp_send(isp, args, insize, outsize, CISP_TIMEOUT); if (err) return err; diff --git a/drivers/media/platform/apple/isp/isp-ipc.c b/drivers/media/platform/apple/isp/isp-ipc.c index 00bd7642177a59..56a482c17424cb 100644 --- a/drivers/media/platform/apple/isp/isp-ipc.c +++ b/drivers/media/platform/apple/isp/isp-ipc.c @@ -168,6 +168,9 @@ int ipc_chan_send(struct apple_isp *isp, struct isp_channel *chan, isp_mbox_write32(isp, ISP_MBOX_IRQ_DOORBELL, chan->doorbell); + if (!timeout) + return 0; + t = wait_event_interruptible_timeout(isp->wait, chan_tx_done(isp, chan), timeout); if (t == 0) { From a52418fcde8206e5ef821cf7222e01ec79003705 Mon Sep 17 00:00:00 2001 From: Asahi Lina Date: Fri, 29 Sep 2023 19:21:38 +0900 Subject: [PATCH 0294/3902] media: apple: isp: Add STOP and POWER_DOWN commands Not sure if these work properly yet, but worth having them to experiment. Signed-off-by: Asahi Lina --- drivers/media/platform/apple/isp/isp-cmd.c | 17 +++++++++++++++++ drivers/media/platform/apple/isp/isp-cmd.h | 14 ++++++++++++++ 2 files changed, 31 insertions(+) diff --git a/drivers/media/platform/apple/isp/isp-cmd.c b/drivers/media/platform/apple/isp/isp-cmd.c index 26ae639b3a63d9..bd82d266522dc0 100644 --- a/drivers/media/platform/apple/isp/isp-cmd.c +++ b/drivers/media/platform/apple/isp/isp-cmd.c @@ -60,6 +60,23 @@ int isp_cmd_start(struct apple_isp *isp, u32 mode) return CISP_SEND_IN(isp, args); } +int isp_cmd_stop(struct apple_isp *isp, u32 mode) +{ + struct cmd_stop args = { + .opcode = CISP_OPCODE(CISP_CMD_STOP), + .mode = mode, + }; + return CISP_SEND_IN(isp, args); +} + +int isp_cmd_power_down(struct apple_isp *isp) +{ + struct cmd_power_down args = { + .opcode = CISP_OPCODE(CISP_CMD_POWER_DOWN), + }; + return CISP_POST_INOUT(isp, args); +} + int isp_cmd_suspend(struct apple_isp *isp) { struct cmd_suspend args = { diff --git a/drivers/media/platform/apple/isp/isp-cmd.h b/drivers/media/platform/apple/isp/isp-cmd.h index 1586df89f1cdab..2de2a49f2cd398 100644 --- a/drivers/media/platform/apple/isp/isp-cmd.h +++ b/drivers/media/platform/apple/isp/isp-cmd.h @@ -12,6 +12,7 @@ #define CISP_CMD_PRINT_ENABLE 0x0004 #define CISP_CMD_BUILDINFO 0x0006 #define CISP_CMD_GET_BES_PARAM 0x000f +#define CISP_CMD_POWER_DOWN 0x0010 #define CISP_CMD_SET_ISP_PMU_BASE 0x0011 #define CISP_CMD_PMP_CTRL_SET 0x001c #define CISP_CMD_TRACE_ENABLE 0x001d @@ -130,6 +131,17 @@ struct cmd_start { } __packed; static_assert(sizeof(struct cmd_start) == 0xc); +struct cmd_stop { + u64 opcode; + u32 mode; +} __packed; +static_assert(sizeof(struct cmd_stop) == 0xc); + +struct cmd_power_down { + u64 opcode; +} __packed; +static_assert(sizeof(struct cmd_power_down) == 0x8); + struct cmd_suspend { u64 opcode; } __packed; @@ -221,6 +233,8 @@ struct cmd_ipc_endpoint_set2 { static_assert(sizeof(struct cmd_ipc_endpoint_set2) == 0x30); int isp_cmd_start(struct apple_isp *isp, u32 mode); +int isp_cmd_stop(struct apple_isp *isp, u32 mode); +int isp_cmd_power_down(struct apple_isp *isp); int isp_cmd_suspend(struct apple_isp *isp); int isp_cmd_print_enable(struct apple_isp *isp, u32 enable); int isp_cmd_trace_enable(struct apple_isp *isp, u32 enable); From ada4f35d8cc74b757a23b06508e98a3d497f50a6 Mon Sep 17 00:00:00 2001 From: Asahi Lina Date: Sat, 30 Sep 2023 00:15:27 +0900 Subject: [PATCH 0295/3902] media: apple: isp: Maybe fix some DMA ordering issues Maybe. Signed-off-by: Asahi Lina --- drivers/media/platform/apple/isp/isp-fw.c | 4 ++-- drivers/media/platform/apple/isp/isp-ipc.c | 11 +++++++++-- 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/drivers/media/platform/apple/isp/isp-fw.c b/drivers/media/platform/apple/isp/isp-fw.c index 70e201ea1ebd6f..2ee815fcc0c72c 100644 --- a/drivers/media/platform/apple/isp/isp-fw.c +++ b/drivers/media/platform/apple/isp/isp-fw.c @@ -338,7 +338,7 @@ static int isp_firmware_boot_stage2(struct apple_isp *isp) isp_gpio_write32(isp, ISP_GPIO_0, args_iova); isp_gpio_write32(isp, ISP_GPIO_1, args_iova >> 32); - wmb(); + dma_wmb(); /* Wait for ISP_GPIO_7 to 0xf7fbdff9 -> 0x8042006 */ isp_gpio_write32(isp, ISP_GPIO_7, 0xf7fbdff9); @@ -500,7 +500,7 @@ static int isp_firmware_boot_stage3(struct apple_isp *isp) memcpy(msg_virt, &msg, sizeof(msg)); } } - wmb(); + dma_wmb(); /* Wait for ISP_GPIO_3 to 0x8042006 -> 0x0 */ isp_gpio_write32(isp, ISP_GPIO_3, 0x8042006); diff --git a/drivers/media/platform/apple/isp/isp-ipc.c b/drivers/media/platform/apple/isp/isp-ipc.c index 56a482c17424cb..21c494f49ebc5f 100644 --- a/drivers/media/platform/apple/isp/isp-ipc.c +++ b/drivers/media/platform/apple/isp/isp-ipc.c @@ -78,7 +78,14 @@ static inline void chan_write_msg_index(struct apple_isp *isp, struct isp_channel *chan, struct isp_message *msg, u32 index) { - memcpy(chan_msg_virt(chan, index), msg, sizeof(*msg)); + u64 *p0 = chan_msg_virt(chan, index); + memcpy(p0 + 1, &msg->arg1, sizeof(*msg) - 8); + + /* Make sure we write arg0 last, since that indicates message validity. */ + + dma_wmb(); + *p0 = msg->arg0; + dma_wmb(); } static inline void chan_write_msg(struct apple_isp *isp, @@ -164,7 +171,7 @@ int ipc_chan_send(struct apple_isp *isp, struct isp_channel *chan, long t; chan_write_msg(isp, chan, &chan->req); - wmb(); + dma_wmb(); isp_mbox_write32(isp, ISP_MBOX_IRQ_DOORBELL, chan->doorbell); From 33dee72de39338cd2521394ffb9ca750f7b33dba Mon Sep 17 00:00:00 2001 From: Asahi Lina Date: Sat, 30 Sep 2023 00:15:41 +0900 Subject: [PATCH 0296/3902] media: apple: isp: Make channel sends not interruptible Otherwise processes receiving a signal will break our command flows. Signed-off-by: Asahi Lina --- drivers/media/platform/apple/isp/isp-fw.c | 6 +++--- drivers/media/platform/apple/isp/isp-ipc.c | 3 +-- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/drivers/media/platform/apple/isp/isp-fw.c b/drivers/media/platform/apple/isp/isp-fw.c index 2ee815fcc0c72c..dd88ddf8a2a8c6 100644 --- a/drivers/media/platform/apple/isp/isp-fw.c +++ b/drivers/media/platform/apple/isp/isp-fw.c @@ -132,15 +132,15 @@ static irqreturn_t apple_isp_isr_thread(int irq, void *dev) { struct apple_isp *isp = dev; - wake_up_interruptible_all(&isp->wait); + wake_up_all(&isp->wait); ipc_chan_handle(isp, isp->chan_sm); - wake_up_interruptible_all(&isp->wait); /* Some commands depend on sm */ + wake_up_all(&isp->wait); /* Some commands depend on sm */ ipc_chan_handle(isp, isp->chan_tm); ipc_chan_handle(isp, isp->chan_bt); - wake_up_interruptible_all(&isp->wait); + wake_up_all(&isp->wait); return IRQ_HANDLED; } diff --git a/drivers/media/platform/apple/isp/isp-ipc.c b/drivers/media/platform/apple/isp/isp-ipc.c index 21c494f49ebc5f..c63babfb9951b6 100644 --- a/drivers/media/platform/apple/isp/isp-ipc.c +++ b/drivers/media/platform/apple/isp/isp-ipc.c @@ -178,8 +178,7 @@ int ipc_chan_send(struct apple_isp *isp, struct isp_channel *chan, if (!timeout) return 0; - t = wait_event_interruptible_timeout(isp->wait, chan_tx_done(isp, chan), - timeout); + t = wait_event_timeout(isp->wait, chan_tx_done(isp, chan), timeout); if (t == 0) { dev_err(isp->dev, "%s: timed out on request [0x%llx, 0x%llx, 0x%llx]\n", From 6254ae56e08a5eaea67896bae683d0eb0631c00e Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Thu, 28 Sep 2023 08:11:20 +0200 Subject: [PATCH 0297/3902] media: apple: isp: Use a second region for MBOX_IRQ_{DOORBELL,ACK} t8112 uses a different register layout. Signed-off-by: Janne Grunau --- drivers/media/platform/apple/isp/isp-drv.c | 6 ++++++ drivers/media/platform/apple/isp/isp-drv.h | 1 + drivers/media/platform/apple/isp/isp-fw.c | 2 +- drivers/media/platform/apple/isp/isp-ipc.c | 4 ++-- drivers/media/platform/apple/isp/isp-regs.h | 13 +++++++++---- 5 files changed, 19 insertions(+), 7 deletions(-) diff --git a/drivers/media/platform/apple/isp/isp-drv.c b/drivers/media/platform/apple/isp/isp-drv.c index 5a15b812c3dcfa..0070cda4e516da 100644 --- a/drivers/media/platform/apple/isp/isp-drv.c +++ b/drivers/media/platform/apple/isp/isp-drv.c @@ -267,6 +267,12 @@ static int apple_isp_probe(struct platform_device *pdev) goto detach_genpd; } + isp->mbox2 = devm_platform_ioremap_resource_byname(pdev, "mbox2"); + if (IS_ERR(isp->mbox2)) { + err = PTR_ERR(isp->mbox2); + goto detach_genpd; + } + isp->irq = platform_get_irq(pdev, 0); if (isp->irq < 0) { err = isp->irq; diff --git a/drivers/media/platform/apple/isp/isp-drv.h b/drivers/media/platform/apple/isp/isp-drv.h index 26b9ee0e4d709f..4d3b1bd7603aea 100644 --- a/drivers/media/platform/apple/isp/isp-drv.h +++ b/drivers/media/platform/apple/isp/isp-drv.h @@ -199,6 +199,7 @@ struct apple_isp { void __iomem *coproc; void __iomem *mbox; void __iomem *gpio; + void __iomem *mbox2; struct iommu_domain *domain; unsigned long shift; diff --git a/drivers/media/platform/apple/isp/isp-fw.c b/drivers/media/platform/apple/isp/isp-fw.c index dd88ddf8a2a8c6..5c1739e58ab001 100644 --- a/drivers/media/platform/apple/isp/isp-fw.c +++ b/drivers/media/platform/apple/isp/isp-fw.c @@ -122,7 +122,7 @@ static irqreturn_t apple_isp_isr(int irq, void *dev) { struct apple_isp *isp = dev; - isp_mbox_write32(isp, ISP_MBOX_IRQ_ACK, + isp_mbox2_write32(isp, ISP_MBOX2_IRQ_ACK, isp_mbox_read32(isp, ISP_MBOX_IRQ_INTERRUPT)); return IRQ_WAKE_THREAD; diff --git a/drivers/media/platform/apple/isp/isp-ipc.c b/drivers/media/platform/apple/isp/isp-ipc.c index c63babfb9951b6..0475b3cf2699ee 100644 --- a/drivers/media/platform/apple/isp/isp-ipc.c +++ b/drivers/media/platform/apple/isp/isp-ipc.c @@ -118,7 +118,7 @@ static int chan_handle_once(struct apple_isp *isp, struct isp_channel *chan) chan_write_msg(isp, chan, &chan->rsp); - isp_mbox_write32(isp, ISP_MBOX_IRQ_DOORBELL, chan->doorbell); + isp_mbox2_write32(isp, ISP_MBOX2_IRQ_DOORBELL, chan->doorbell); chan_update_cursor(chan); @@ -173,7 +173,7 @@ int ipc_chan_send(struct apple_isp *isp, struct isp_channel *chan, chan_write_msg(isp, chan, &chan->req); dma_wmb(); - isp_mbox_write32(isp, ISP_MBOX_IRQ_DOORBELL, chan->doorbell); + isp_mbox2_write32(isp, ISP_MBOX2_IRQ_DOORBELL, chan->doorbell); if (!timeout) return 0; diff --git a/drivers/media/platform/apple/isp/isp-regs.h b/drivers/media/platform/apple/isp/isp-regs.h index 3a99229f6d4c8f..7357fa10fa5483 100644 --- a/drivers/media/platform/apple/isp/isp-regs.h +++ b/drivers/media/platform/apple/isp/isp-regs.h @@ -23,10 +23,10 @@ #define ISP_COPROC_IRQ_MASK_4 0x1400a10 #define ISP_COPROC_IRQ_MASK_5 0x1400a14 -#define ISP_MBOX_IRQ_INTERRUPT 0x000 -#define ISP_MBOX_IRQ_ENABLE 0x004 -#define ISP_MBOX_IRQ_DOORBELL 0x3f0 -#define ISP_MBOX_IRQ_ACK 0x3fc +#define ISP_MBOX_IRQ_INTERRUPT 0x00 +#define ISP_MBOX_IRQ_ENABLE 0x04 +#define ISP_MBOX2_IRQ_DOORBELL 0x00 +#define ISP_MBOX2_IRQ_ACK 0x0c #define ISP_GPIO_0 0x00 #define ISP_GPIO_1 0x04 @@ -48,4 +48,9 @@ static inline void isp_mbox_write32(struct apple_isp *isp, u32 reg, u32 val) writel(val, isp->mbox + reg); } +static inline void isp_mbox2_write32(struct apple_isp *isp, u32 reg, u32 val) +{ + writel(val, isp->mbox2 + reg); +} + #endif /* __ISP_REGS_H__ */ From 11af6d744c1693aa025f3a72d761427fc1c1598e Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Thu, 28 Sep 2023 08:27:10 +0200 Subject: [PATCH 0298/3902] media: apple: isp: t8112 HW config Not yet working. Signed-off-by: Janne Grunau --- drivers/media/platform/apple/isp/isp-drv.c | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) diff --git a/drivers/media/platform/apple/isp/isp-drv.c b/drivers/media/platform/apple/isp/isp-drv.c index 0070cda4e516da..195e916021d4c6 100644 --- a/drivers/media/platform/apple/isp/isp-drv.c +++ b/drivers/media/platform/apple/isp/isp-drv.c @@ -405,19 +405,14 @@ static const struct apple_isp_hw apple_isp_hw_t6000 = { .meta_size = ISP_META_SIZE_T8103, }; -static const struct apple_isp_hw apple_isp_hw_t8110 = { +static const struct apple_isp_hw apple_isp_hw_t8112 = { .gen = ISP_GEN_T8112, .pmu_base = 0x23b704000, - .dsid_count = 4, - .dsid_clr_base0 = 0x200014000, // TODO - .dsid_clr_base1 = 0x200054000, - .dsid_clr_base2 = 0x200094000, - .dsid_clr_base3 = 0x2000d4000, + // TODO: verify + .dsid_count = 1, + .dsid_clr_base0 = 0x200f14000, .dsid_clr_range0 = 0x1000, - .dsid_clr_range1 = 0x1000, - .dsid_clr_range2 = 0x1000, - .dsid_clr_range3 = 0x1000, .clock_scratch = 0x23b3d0560, .clock_base = 0x0, @@ -455,6 +450,7 @@ static const struct apple_isp_hw apple_isp_hw_t6020 = { static const struct of_device_id apple_isp_of_match[] = { { .compatible = "apple,t8103-isp", .data = &apple_isp_hw_t8103 }, + { .compatible = "apple,t8112-isp", .data = &apple_isp_hw_t8112 }, { .compatible = "apple,t6000-isp", .data = &apple_isp_hw_t6000 }, { .compatible = "apple,t6020-isp", .data = &apple_isp_hw_t6020 }, {}, From e4701c001d9710ee820039e62bd65ca7d808b5f0 Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Thu, 28 Sep 2023 20:45:18 +0200 Subject: [PATCH 0299/3902] media: apple: isp: Limit maximal number of buffers ISP (FW 12.3) on t6001 times out if more buffers than count in the buffer pool config are submitted before streaming is started. To avoid keeping track of the number of submitted buffers limit the number. 16 buffers / frames should be more than enough. Signed-off-by: Janne Grunau --- drivers/media/platform/apple/isp/isp-cmd.c | 2 +- drivers/media/platform/apple/isp/isp-drv.h | 3 +++ drivers/media/platform/apple/isp/isp-v4l2.c | 8 ++++++++ 3 files changed, 12 insertions(+), 1 deletion(-) diff --git a/drivers/media/platform/apple/isp/isp-cmd.c b/drivers/media/platform/apple/isp/isp-cmd.c index bd82d266522dc0..cbd9348f592dc2 100644 --- a/drivers/media/platform/apple/isp/isp-cmd.c +++ b/drivers/media/platform/apple/isp/isp-cmd.c @@ -395,7 +395,7 @@ int isp_cmd_ch_buffer_pool_config_set(struct apple_isp *isp, u32 chan, u16 type) .opcode = CISP_OPCODE(CISP_CMD_CH_BUFFER_POOL_CONFIG_SET), .chan = chan, .type = type, - .count = 16, + .count = ISP_MAX_BUFFERS, .meta_size0 = isp->hw->meta_size, .meta_size1 = isp->hw->meta_size, .unk0 = 0, diff --git a/drivers/media/platform/apple/isp/isp-drv.h b/drivers/media/platform/apple/isp/isp-drv.h index 4d3b1bd7603aea..8269b772bbd1bd 100644 --- a/drivers/media/platform/apple/isp/isp-drv.h +++ b/drivers/media/platform/apple/isp/isp-drv.h @@ -23,6 +23,9 @@ #define ISP_META_SIZE_T8103 0x4640 #define ISP_META_SIZE_T8112 0x4840 +/* used to limit the user space buffers to the buffer_pool_config */ +#define ISP_MAX_BUFFERS 16 + enum isp_generation { ISP_GEN_T8103, ISP_GEN_T8112, diff --git a/drivers/media/platform/apple/isp/isp-v4l2.c b/drivers/media/platform/apple/isp/isp-v4l2.c index daa49f8e3214dc..ae15aee4513f00 100644 --- a/drivers/media/platform/apple/isp/isp-v4l2.c +++ b/drivers/media/platform/apple/isp/isp-v4l2.c @@ -11,6 +11,7 @@ #include "isp-cam.h" #include "isp-cmd.h" +#include "isp-drv.h" #include "isp-iommu.h" #include "isp-ipc.h" #include "isp-v4l2.h" @@ -143,6 +144,13 @@ static int isp_vb2_queue_setup(struct vb2_queue *vq, unsigned int *nbuffers, struct apple_isp *isp = vb2_get_drv_priv(vq); struct isp_format *fmt = isp_get_current_format(isp); + /* This is not strictly neccessary but makes it easy to enforce that + * at most 16 buffers are submitted at once. ISP on t6001 (FW 12.3) + * times out if more buffers are submitted than set in the buffer pool + * config before streaming is started. + */ + *nbuffers = min_t(unsigned int, *nbuffers, ISP_MAX_BUFFERS); + if (*num_planes) { if (sizes[0] < fmt->total_size) return -EINVAL; From d21076a957c8f092f466d6d0df5b26358787ceec Mon Sep 17 00:00:00 2001 From: Asahi Lina Date: Sat, 30 Sep 2023 18:53:26 +0900 Subject: [PATCH 0300/3902] media: apple: isp: t8112 fixes... Signed-off-by: Asahi Lina --- drivers/media/platform/apple/isp/isp-cam.c | 4 ++-- drivers/media/platform/apple/isp/isp-cmd.c | 4 ++-- drivers/media/platform/apple/isp/isp-cmd.h | 2 +- drivers/media/platform/apple/isp/isp-drv.c | 12 ++++++++++-- drivers/media/platform/apple/isp/isp-drv.h | 2 ++ 5 files changed, 17 insertions(+), 7 deletions(-) diff --git a/drivers/media/platform/apple/isp/isp-cam.c b/drivers/media/platform/apple/isp/isp-cam.c index 9ccdc2a1304bed..4966fe64aac299 100644 --- a/drivers/media/platform/apple/isp/isp-cam.c +++ b/drivers/media/platform/apple/isp/isp-cam.c @@ -346,7 +346,7 @@ static int isp_ch_configure_capture(struct apple_isp *isp, u32 ch) return err; } - if (isp->hw->gen >= ISP_GEN_T8112) { + if (isp->hw->lpdp) { err = isp_cmd_ch_lpdp_hs_receiver_tuning_set(isp, ch, 1, 15); if (err) return err; @@ -395,7 +395,7 @@ static int isp_ch_configure_capture(struct apple_isp *isp, u32 ch) if (err) return err; - err = isp_cmd_apple_ch_temporal_filter_start(isp, ch); + err = isp_cmd_apple_ch_temporal_filter_start(isp, ch, isp->temporal_filter); if (err) return err; diff --git a/drivers/media/platform/apple/isp/isp-cmd.c b/drivers/media/platform/apple/isp/isp-cmd.c index cbd9348f592dc2..15a5ec22778ced 100644 --- a/drivers/media/platform/apple/isp/isp-cmd.c +++ b/drivers/media/platform/apple/isp/isp-cmd.c @@ -416,13 +416,13 @@ int isp_cmd_ch_buffer_pool_return(struct apple_isp *isp, u32 chan) return CISP_SEND_IN(isp, args); } -int isp_cmd_apple_ch_temporal_filter_start(struct apple_isp *isp, u32 chan) +int isp_cmd_apple_ch_temporal_filter_start(struct apple_isp *isp, u32 chan, u32 arg) { struct cmd_apple_ch_temporal_filter_start args = { .opcode = CISP_OPCODE(CISP_CMD_APPLE_CH_TEMPORAL_FILTER_START), .chan = chan, .unk_c = 1, - .unk_10 = 0, + .unk_10 = arg, }; return CISP_SEND_IN(isp, args); } diff --git a/drivers/media/platform/apple/isp/isp-cmd.h b/drivers/media/platform/apple/isp/isp-cmd.h index 2de2a49f2cd398..718ae88045ac25 100644 --- a/drivers/media/platform/apple/isp/isp-cmd.h +++ b/drivers/media/platform/apple/isp/isp-cmd.h @@ -577,7 +577,7 @@ struct cmd_apple_ch_temporal_filter_disable { } __packed; static_assert(sizeof(struct cmd_apple_ch_temporal_filter_disable) == 0xc); -int isp_cmd_apple_ch_temporal_filter_start(struct apple_isp *isp, u32 chan); +int isp_cmd_apple_ch_temporal_filter_start(struct apple_isp *isp, u32 chan, u32 arg); int isp_cmd_apple_ch_temporal_filter_stop(struct apple_isp *isp, u32 chan); int isp_cmd_apple_ch_motion_history_start(struct apple_isp *isp, u32 chan); int isp_cmd_apple_ch_motion_history_stop(struct apple_isp *isp, u32 chan); diff --git a/drivers/media/platform/apple/isp/isp-drv.c b/drivers/media/platform/apple/isp/isp-drv.c index 195e916021d4c6..0c0f9d6110f230 100644 --- a/drivers/media/platform/apple/isp/isp-drv.c +++ b/drivers/media/platform/apple/isp/isp-drv.c @@ -237,6 +237,11 @@ static int apple_isp_probe(struct platform_device *pdev) return err; } + err = of_property_read_u32(dev->of_node, "apple,temporal-filter", + &isp->temporal_filter); + if (err) + isp->temporal_filter = 0; + err = apple_isp_init_presets(isp); if (err) { dev_err(dev, "failed to initialize presets\n"); @@ -375,6 +380,7 @@ static const struct apple_isp_hw apple_isp_hw_t8103 = { .bandwidth_size = 0x4, .scl1 = false, + .lpdp = false, .meta_size = ISP_META_SIZE_T8103, }; @@ -402,6 +408,7 @@ static const struct apple_isp_hw apple_isp_hw_t6000 = { .bandwidth_size = 0x8, .scl1 = false, + .lpdp = false, .meta_size = ISP_META_SIZE_T8103, }; @@ -409,7 +416,6 @@ static const struct apple_isp_hw apple_isp_hw_t8112 = { .gen = ISP_GEN_T8112, .pmu_base = 0x23b704000, - // TODO: verify .dsid_count = 1, .dsid_clr_base0 = 0x200f14000, .dsid_clr_range0 = 0x1000, @@ -423,7 +429,8 @@ static const struct apple_isp_hw apple_isp_hw_t8112 = { .bandwidth_bit = 0x0, .bandwidth_size = 0x8, - .scl1 = true, + .scl1 = false, + .lpdp = false, .meta_size = ISP_META_SIZE_T8112, }; @@ -445,6 +452,7 @@ static const struct apple_isp_hw apple_isp_hw_t6020 = { .bandwidth_size = 0x8, .scl1 = true, + .lpdp = true, .meta_size = ISP_META_SIZE_T8112, }; diff --git a/drivers/media/platform/apple/isp/isp-drv.h b/drivers/media/platform/apple/isp/isp-drv.h index 8269b772bbd1bd..b62d389442e810 100644 --- a/drivers/media/platform/apple/isp/isp-drv.h +++ b/drivers/media/platform/apple/isp/isp-drv.h @@ -111,6 +111,7 @@ struct apple_isp_hw { u32 meta_size; bool scl1; + bool lpdp; }; enum isp_sensor_id { @@ -178,6 +179,7 @@ struct apple_isp { struct device *dev; const struct apple_isp_hw *hw; u32 platform_id; + u32 temporal_filter; struct isp_preset *presets; int num_presets; From a25ec23f24c6694e884f6cd6e0ddb151cadf0cfb Mon Sep 17 00:00:00 2001 From: Asahi Lina Date: Wed, 4 Oct 2023 22:18:25 +0900 Subject: [PATCH 0301/3902] media: apple: isp: Add flicker_sensor_set cmd Signed-off-by: Asahi Lina --- drivers/media/platform/apple/isp/isp-cmd.c | 10 ++++++++++ drivers/media/platform/apple/isp/isp-cmd.h | 7 +++++++ 2 files changed, 17 insertions(+) diff --git a/drivers/media/platform/apple/isp/isp-cmd.c b/drivers/media/platform/apple/isp/isp-cmd.c index 15a5ec22778ced..9c5808b4e831be 100644 --- a/drivers/media/platform/apple/isp/isp-cmd.c +++ b/drivers/media/platform/apple/isp/isp-cmd.c @@ -14,6 +14,7 @@ #define CISP_SEND_INOUT(x, a) (cisp_send((x), &(a), sizeof(a), sizeof(a), CISP_TIMEOUT)) #define CISP_SEND_OUT(x, a) (cisp_send_read((x), (a), sizeof(*a), sizeof(*a))) #define CISP_POST_IN(x, a) (cisp_send((x), &(a), sizeof(a), 0, 0)) +#define CISP_POST_INOUT(x, a) (cisp_send((x), &(a), sizeof(a), sizeof(a), 0)) static int cisp_send(struct apple_isp *isp, void *args, u32 insize, u32 outsize, int timeout) { @@ -204,6 +205,15 @@ int isp_cmd_ch_stop(struct apple_isp *isp, u32 chan) return CISP_SEND_IN(isp, args); } +int isp_cmd_flicker_sensor_set(struct apple_isp *isp, u32 mode) +{ + struct cmd_flicker_sensor_set args = { + .opcode = CISP_OPCODE(CISP_CMD_FLICKER_SENSOR_SET), + .mode = mode, + }; + return CISP_SEND_INOUT(isp, args); +} + int isp_cmd_ch_info_get(struct apple_isp *isp, u32 chan, struct cmd_ch_info *args) { diff --git a/drivers/media/platform/apple/isp/isp-cmd.h b/drivers/media/platform/apple/isp/isp-cmd.h index 718ae88045ac25..5a3c8cd9177e48 100644 --- a/drivers/media/platform/apple/isp/isp-cmd.h +++ b/drivers/media/platform/apple/isp/isp-cmd.h @@ -232,6 +232,12 @@ struct cmd_ipc_endpoint_set2 { } __packed; static_assert(sizeof(struct cmd_ipc_endpoint_set2) == 0x30); +struct cmd_flicker_sensor_set { + u64 opcode; + u32 mode; +} __packed; +static_assert(sizeof(struct cmd_flicker_sensor_set) == 0xc); + int isp_cmd_start(struct apple_isp *isp, u32 mode); int isp_cmd_stop(struct apple_isp *isp, u32 mode); int isp_cmd_power_down(struct apple_isp *isp); @@ -253,6 +259,7 @@ int isp_cmd_pmp_ctrl_set(struct apple_isp *isp, u64 clock_scratch, u8 bandwidth_bit, u8 bandwidth_size); int isp_cmd_fid_enter(struct apple_isp *isp); int isp_cmd_fid_exit(struct apple_isp *isp); +int isp_cmd_flicker_sensor_set(struct apple_isp *isp, u32 mode); struct cmd_ch_start { u64 opcode; From 1441d0b1db03115ecbe9b390137ac97b03141dd5 Mon Sep 17 00:00:00 2001 From: Asahi Lina Date: Wed, 4 Oct 2023 22:18:46 +0900 Subject: [PATCH 0302/3902] media: apple: isp: Minor changes to cam flow Signed-off-by: Asahi Lina --- drivers/media/platform/apple/isp/isp-cam.c | 36 +++++++++++++--------- drivers/media/platform/apple/isp/isp-cam.h | 1 + 2 files changed, 23 insertions(+), 14 deletions(-) diff --git a/drivers/media/platform/apple/isp/isp-cam.c b/drivers/media/platform/apple/isp/isp-cam.c index 4966fe64aac299..cc0c24c3cfb715 100644 --- a/drivers/media/platform/apple/isp/isp-cam.c +++ b/drivers/media/platform/apple/isp/isp-cam.c @@ -289,6 +289,12 @@ int apple_isp_detect_camera(struct apple_isp *isp) } err = isp_detect_camera(isp); + + isp_cmd_flicker_sensor_set(isp, 0); + + isp_cmd_ch_stop(isp, 0); + isp_cmd_ch_buffer_return(isp, isp->current_ch); + apple_isp_firmware_shutdown(isp); return err; @@ -335,6 +341,8 @@ static int isp_ch_configure_capture(struct apple_isp *isp, u32 ch) struct isp_format *fmt = isp_get_format(isp, ch); int err; + isp_cmd_flicker_sensor_set(isp, 0); + /* The setfile isn't requisite but then we don't get calibration */ err = isp_ch_load_setfile(isp, ch); if (err) { @@ -356,16 +364,16 @@ static int isp_ch_configure_capture(struct apple_isp *isp, u32 ch) if (err) return err; - err = isp_cmd_ch_buffer_recycle_mode_set( - isp, ch, CISP_BUFFER_RECYCLE_MODE_EMPTY_ONLY); + err = isp_cmd_ch_camera_config_select(isp, ch, fmt->preset->index); if (err) return err; - err = isp_cmd_ch_buffer_recycle_start(isp, ch); + err = isp_cmd_ch_buffer_recycle_mode_set( + isp, ch, CISP_BUFFER_RECYCLE_MODE_EMPTY_ONLY); if (err) return err; - err = isp_cmd_ch_camera_config_select(isp, ch, fmt->preset->index); + err = isp_cmd_ch_buffer_recycle_start(isp, ch); if (err) return err; @@ -395,43 +403,43 @@ static int isp_ch_configure_capture(struct apple_isp *isp, u32 ch) if (err) return err; - err = isp_cmd_apple_ch_temporal_filter_start(isp, ch, isp->temporal_filter); + err = isp_cmd_apple_ch_ae_fd_scene_metering_config_set(isp, ch); if (err) return err; - err = isp_cmd_apple_ch_motion_history_start(isp, ch); + err = isp_cmd_apple_ch_ae_metering_mode_set(isp, ch, 3); if (err) return err; - err = isp_cmd_apple_ch_temporal_filter_enable(isp, ch); + err = isp_cmd_ch_ae_stability_set(isp, ch, 32); if (err) return err; - err = isp_cmd_apple_ch_ae_fd_scene_metering_config_set(isp, ch); + err = isp_cmd_ch_ae_stability_to_stable_set(isp, ch, 20); if (err) return err; - err = isp_cmd_apple_ch_ae_metering_mode_set(isp, ch, 3); + err = isp_cmd_ch_sif_pixel_format_set(isp, ch); if (err) return err; - err = isp_cmd_ch_ae_stability_set(isp, ch, 32); + err = isp_cmd_ch_ae_frame_rate_max_set(isp, ch, ISP_FRAME_RATE_DEN); if (err) return err; - err = isp_cmd_ch_ae_stability_to_stable_set(isp, ch, 20); + err = isp_cmd_ch_ae_frame_rate_min_set(isp, ch, ISP_FRAME_RATE_DEN2); if (err) return err; - err = isp_cmd_ch_sif_pixel_format_set(isp, ch); + err = isp_cmd_apple_ch_temporal_filter_start(isp, ch, isp->temporal_filter); if (err) return err; - err = isp_cmd_ch_ae_frame_rate_max_set(isp, ch, ISP_FRAME_RATE_DEN); + err = isp_cmd_apple_ch_motion_history_start(isp, ch); if (err) return err; - err = isp_cmd_ch_ae_frame_rate_min_set(isp, ch, ISP_FRAME_RATE_DEN); + err = isp_cmd_apple_ch_temporal_filter_enable(isp, ch); if (err) return err; diff --git a/drivers/media/platform/apple/isp/isp-cam.h b/drivers/media/platform/apple/isp/isp-cam.h index 126e5806c8c416..f4fa4224c7a934 100644 --- a/drivers/media/platform/apple/isp/isp-cam.h +++ b/drivers/media/platform/apple/isp/isp-cam.h @@ -8,6 +8,7 @@ #define ISP_FRAME_RATE_NUM 256 #define ISP_FRAME_RATE_DEN 7680 +#define ISP_FRAME_RATE_DEN2 3840 int apple_isp_detect_camera(struct apple_isp *isp); From 5da734dd2b33ebef080537ca64e9b485f72de98d Mon Sep 17 00:00:00 2001 From: Asahi Lina Date: Wed, 4 Oct 2023 22:21:54 +0900 Subject: [PATCH 0303/3902] media: apple: isp: Make sub-pmdomain handling explicit Signed-off-by: Asahi Lina --- drivers/media/platform/apple/isp/isp-drv.c | 11 ++++-- drivers/media/platform/apple/isp/isp-drv.h | 1 + drivers/media/platform/apple/isp/isp-fw.c | 45 ++++++++++++++++++++++ 3 files changed, 54 insertions(+), 3 deletions(-) diff --git a/drivers/media/platform/apple/isp/isp-drv.c b/drivers/media/platform/apple/isp/isp-drv.c index 0c0f9d6110f230..2ea4ecad36c75e 100644 --- a/drivers/media/platform/apple/isp/isp-drv.c +++ b/drivers/media/platform/apple/isp/isp-drv.c @@ -54,6 +54,12 @@ static int apple_isp_attach_genpd(struct apple_isp *isp) return -ENOMEM; for (int i = 0; i < isp->pd_count; i++) { + int flags = DL_FLAG_STATELESS; + + /* Primary power domain uses RPM integration */ + if (i == 0) + flags |= DL_FLAG_PM_RUNTIME | DL_FLAG_RPM_ACTIVE; + isp->pd_dev[i] = dev_pm_domain_attach_by_id(dev, i); if (IS_ERR(isp->pd_dev[i])) { apple_isp_detach_genpd(isp); @@ -61,9 +67,8 @@ static int apple_isp_attach_genpd(struct apple_isp *isp) } isp->pd_link[i] = - device_link_add(dev, isp->pd_dev[i], - DL_FLAG_STATELESS | DL_FLAG_PM_RUNTIME | - DL_FLAG_RPM_ACTIVE); + device_link_add(dev, isp->pd_dev[i], flags); + if (!isp->pd_link[i]) { apple_isp_detach_genpd(isp); return -EINVAL; diff --git a/drivers/media/platform/apple/isp/isp-drv.h b/drivers/media/platform/apple/isp/isp-drv.h index b62d389442e810..775a435c4a06ad 100644 --- a/drivers/media/platform/apple/isp/isp-drv.h +++ b/drivers/media/platform/apple/isp/isp-drv.h @@ -198,6 +198,7 @@ struct apple_isp { int pd_count; struct device **pd_dev; struct device_link **pd_link; + bool pds_active; int irq; diff --git a/drivers/media/platform/apple/isp/isp-fw.c b/drivers/media/platform/apple/isp/isp-fw.c index 5c1739e58ab001..75405c2258239e 100644 --- a/drivers/media/platform/apple/isp/isp-fw.c +++ b/drivers/media/platform/apple/isp/isp-fw.c @@ -41,6 +41,46 @@ static inline void isp_gpio_write32(struct apple_isp *isp, u32 reg, u32 val) writel(val, isp->gpio + reg); } +int apple_isp_power_up_domains(struct apple_isp *isp) +static int apple_isp_power_up_domains(struct apple_isp *isp) + int ret; + + if (isp->pds_active) + return 0; + + for (int i = 1; i < isp->pd_count; i++) { + ret = pm_runtime_get_sync(isp->pd_dev[i]); + if (ret < 0) { + dev_err(isp->dev, + "Failed to power up power domain %d: %d\n", i, ret); + while (--i != 1) + pm_runtime_put_sync(isp->pd_dev[i]); + return ret; + } + } + + isp->pds_active = true; + + return 0; +} + +void apple_isp_power_down_domains(struct apple_isp *isp) +static void apple_isp_power_down_domains(struct apple_isp *isp) + int ret; + + if (!isp->pds_active) + return; + + for (int i = isp->pd_count - 1; i >= 1; i--) { + ret = pm_runtime_put_sync(isp->pd_dev[i]); + if (ret < 0) + dev_err(isp->dev, + "Failed to power up power domain %d: %d\n", i, ret); + } + + isp->pds_active = false; +} + void *apple_isp_translate(struct apple_isp *isp, struct isp_surf *surf, dma_addr_t iova, size_t size) { @@ -209,11 +249,16 @@ static int isp_reset_coproc(struct apple_isp *isp) static void isp_firmware_shutdown_stage1(struct apple_isp *isp) { isp_coproc_write32(isp, ISP_COPROC_CONTROL, 0x0); + + apple_isp_power_down_domains(isp); } static int isp_firmware_boot_stage1(struct apple_isp *isp) { int err, retries; + err = apple_isp_power_up_domains(isp); + if (err < 0) + return err; err = isp_reset_coproc(isp); if (err < 0) From b5eec78af2f367a04d3a3f56146d4370e14a73d1 Mon Sep 17 00:00:00 2001 From: Asahi Lina Date: Wed, 4 Oct 2023 22:22:49 +0900 Subject: [PATCH 0304/3902] media: apple: isp: Zero out pages allocated to ISP Signed-off-by: Asahi Lina --- drivers/media/platform/apple/isp/isp-iommu.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/media/platform/apple/isp/isp-iommu.c b/drivers/media/platform/apple/isp/isp-iommu.c index 845c35da0253ae..19d3c3bfa62ee9 100644 --- a/drivers/media/platform/apple/isp/isp-iommu.c +++ b/drivers/media/platform/apple/isp/isp-iommu.c @@ -22,7 +22,7 @@ static int isp_surf_alloc_pages(struct isp_surf *surf) return -ENOMEM; for (u32 i = 0; i < surf->num_pages; i++) { - surf->pages[i] = alloc_page(GFP_KERNEL); + surf->pages[i] = alloc_page(GFP_KERNEL | __GFP_ZERO); if (surf->pages[i] == NULL) goto free_pages; } From 6e7cb2b5f827c603990fc7a739cfc9605859a72c Mon Sep 17 00:00:00 2001 From: Asahi Lina Date: Wed, 4 Oct 2023 22:22:58 +0900 Subject: [PATCH 0305/3902] media: apple: isp: Use cached IOMMU mappings Signed-off-by: Asahi Lina --- drivers/media/platform/apple/isp/isp-iommu.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/media/platform/apple/isp/isp-iommu.c b/drivers/media/platform/apple/isp/isp-iommu.c index 19d3c3bfa62ee9..1ddd089d77355a 100644 --- a/drivers/media/platform/apple/isp/isp-iommu.c +++ b/drivers/media/platform/apple/isp/isp-iommu.c @@ -113,7 +113,7 @@ static int isp_surf_iommu_map(struct apple_isp *isp, struct isp_surf *surf) } size = iommu_map_sgtable(isp->domain, surf->iova, &surf->sgt, - IOMMU_READ | IOMMU_WRITE); + IOMMU_READ | IOMMU_WRITE | IOMMU_CACHE); if (size < surf->size) { dev_err(isp->dev, "failed to iommu_map sgt to iova 0x%llx\n", surf->iova); @@ -231,7 +231,7 @@ int apple_isp_iommu_map_sgt(struct apple_isp *isp, struct isp_surf *surf, } mapped = iommu_map_sgtable(isp->domain, surf->iova, sgt, - IOMMU_READ | IOMMU_WRITE); + IOMMU_READ | IOMMU_WRITE | IOMMU_CACHE); if (mapped < surf->size) { dev_err(isp->dev, "failed to iommu_map sgt to iova 0x%llx\n", surf->iova); From 2f86076adcfc1133b6d4156cbad34d2ce2e3a3d5 Mon Sep 17 00:00:00 2001 From: Asahi Lina Date: Wed, 4 Oct 2023 22:23:42 +0900 Subject: [PATCH 0306/3902] media: apple: isp: Rework meta surface handling & buffer return Now we keep track of meta surfaces independently, and always allocate 16 of them, plus handle buffer return messages more correctly. Fixes t8112 asserts (for some reason). Signed-off-by: Asahi Lina --- drivers/media/platform/apple/isp/isp-drv.h | 3 +- drivers/media/platform/apple/isp/isp-fw.c | 1 + drivers/media/platform/apple/isp/isp-ipc.c | 42 ---- drivers/media/platform/apple/isp/isp-ipc.h | 1 - drivers/media/platform/apple/isp/isp-v4l2.c | 239 ++++++++++++++------ drivers/media/platform/apple/isp/isp-v4l2.h | 1 + 6 files changed, 176 insertions(+), 111 deletions(-) diff --git a/drivers/media/platform/apple/isp/isp-drv.h b/drivers/media/platform/apple/isp/isp-drv.h index 775a435c4a06ad..31c527532aebac 100644 --- a/drivers/media/platform/apple/isp/isp-drv.h +++ b/drivers/media/platform/apple/isp/isp-drv.h @@ -43,6 +43,7 @@ struct isp_surf { void *virt; refcount_t refcount; bool gc; + bool submitted; }; struct isp_message { @@ -221,6 +222,7 @@ struct apple_isp { struct isp_surf *data_surf; struct isp_surf *log_surf; struct isp_surf *bt_surf; + struct isp_surf *meta_surfs[ISP_MAX_BUFFERS]; struct list_head gc; struct workqueue_struct *wq; @@ -252,7 +254,6 @@ struct isp_buffer { struct vb2_v4l2_buffer vb; struct list_head link; struct isp_surf surfs[VB2_MAX_PLANES]; - struct isp_surf *meta; }; #define to_isp_buffer(x) container_of((x), struct isp_buffer, vb) diff --git a/drivers/media/platform/apple/isp/isp-fw.c b/drivers/media/platform/apple/isp/isp-fw.c index 75405c2258239e..1db1294f843a7a 100644 --- a/drivers/media/platform/apple/isp/isp-fw.c +++ b/drivers/media/platform/apple/isp/isp-fw.c @@ -12,6 +12,7 @@ #include "isp-iommu.h" #include "isp-ipc.h" #include "isp-regs.h" +#include "isp-v4l2.h" #define ISP_FIRMWARE_MDELAY 1 #define ISP_FIRMWARE_MAX_TRIES 1000 diff --git a/drivers/media/platform/apple/isp/isp-ipc.c b/drivers/media/platform/apple/isp/isp-ipc.c index 0475b3cf2699ee..7df434513b6c64 100644 --- a/drivers/media/platform/apple/isp/isp-ipc.c +++ b/drivers/media/platform/apple/isp/isp-ipc.c @@ -284,45 +284,3 @@ int ipc_sm_handle(struct apple_isp *isp, struct isp_channel *chan) return 0; } - -int ipc_bt_handle(struct apple_isp *isp, struct isp_channel *chan) -{ - struct isp_message *req = &chan->req, *rsp = &chan->rsp; - struct isp_buffer *tmp, *buf; - int err = 0; - - /* No need to read the whole struct */ - u64 meta_iova; - u64 *p_meta_iova = apple_isp_translate( - isp, isp->bt_surf, req->arg0 + ISP_IPC_BUFEXC_STAT_META_OFFSET, - sizeof(u64)); - - if (!p_meta_iova) { - dev_err(isp->dev, "Failed to find bufexc stat meta\n"); - return -EIO; - } - meta_iova = *p_meta_iova; - - spin_lock(&isp->buf_lock); - list_for_each_entry_safe_reverse(buf, tmp, &isp->bufs_submitted, link) { - if ((u32)buf->meta->iova == (u32)meta_iova) { - enum vb2_buffer_state state = VB2_BUF_STATE_ERROR; - - buf->vb.vb2_buf.timestamp = ktime_get_ns(); - buf->vb.sequence = isp->sequence++; - buf->vb.field = V4L2_FIELD_NONE; - if (req->arg2 == ISP_IPC_BUFEXC_FLAG_RENDER) - state = VB2_BUF_STATE_DONE; - vb2_buffer_done(&buf->vb.vb2_buf, state); - list_del(&buf->link); - break; - } - } - spin_unlock(&isp->buf_lock); - - rsp->arg0 = req->arg0 | ISP_IPC_FLAG_ACK; - rsp->arg1 = 0x0; - rsp->arg2 = ISP_IPC_BUFEXC_FLAG_ACK; - - return err; -} diff --git a/drivers/media/platform/apple/isp/isp-ipc.h b/drivers/media/platform/apple/isp/isp-ipc.h index 32d1e1bf321006..0c1d681835c72f 100644 --- a/drivers/media/platform/apple/isp/isp-ipc.h +++ b/drivers/media/platform/apple/isp/isp-ipc.h @@ -21,6 +21,5 @@ int ipc_chan_send(struct apple_isp *isp, struct isp_channel *chan, int ipc_tm_handle(struct apple_isp *isp, struct isp_channel *chan); int ipc_sm_handle(struct apple_isp *isp, struct isp_channel *chan); -int ipc_bt_handle(struct apple_isp *isp, struct isp_channel *chan); #endif /* __ISP_IPC_H__ */ diff --git a/drivers/media/platform/apple/isp/isp-v4l2.c b/drivers/media/platform/apple/isp/isp-v4l2.c index ae15aee4513f00..ff2ea72f57ee9d 100644 --- a/drivers/media/platform/apple/isp/isp-v4l2.c +++ b/drivers/media/platform/apple/isp/isp-v4l2.c @@ -11,9 +11,9 @@ #include "isp-cam.h" #include "isp-cmd.h" -#include "isp-drv.h" #include "isp-iommu.h" #include "isp-ipc.h" +#include "isp-fw.h" #include "isp-v4l2.h" #define ISP_MIN_FRAMES 2 @@ -26,7 +26,7 @@ static bool multiplanar = false; module_param(multiplanar, bool, 0644); MODULE_PARM_DESC(multiplanar, "Enable multiplanar API"); -struct isp_h2t_buffer { +struct isp_buflist_buffer { u64 iovas[ISP_MAX_PLANES]; u32 flags[ISP_MAX_PLANES]; u32 num_planes; @@ -34,102 +34,190 @@ struct isp_h2t_buffer { u32 tag; u32 pad; } __packed; -static_assert(sizeof(struct isp_h2t_buffer) == 0x40); +static_assert(sizeof(struct isp_buflist_buffer) == 0x40); -struct isp_h2t_args { - u64 enable; +struct isp_buflist { + u64 type; u64 num_buffers; - struct isp_h2t_buffer meta; - struct isp_h2t_buffer render; -} __packed; + struct isp_buflist_buffer buffers[]; +}; + +int ipc_bt_handle(struct apple_isp *isp, struct isp_channel *chan) +{ + struct isp_message *req = &chan->req, *rsp = &chan->rsp; + struct isp_buffer *tmp, *buf; + struct isp_buflist *bl; + u32 count; + int err = 0; + + /* printk("H2T: 0x%llx 0x%llx 0x%llx\n", (long long)req->arg0, + (long long)req->arg1, (long long)req->arg2); */ + + if (req->arg1 < sizeof(struct isp_buflist)) { + dev_err(isp->dev, "%s: Bad length 0x%llx\n", chan->name, + req->arg1); + return -EIO; + } + + bl = apple_isp_translate(isp, isp->bt_surf, req->arg0, req->arg1); + + count = bl->num_buffers; + if (count > (req->arg1 - sizeof(struct isp_buffer)) / + sizeof(struct isp_buflist_buffer)) { + dev_err(isp->dev, "%s: Bad length 0x%llx\n", chan->name, + req->arg1); + return -EIO; + } + + spin_lock(&isp->buf_lock); + for (int i = 0; i < count; i++) { + struct isp_buflist_buffer *bufd = &bl->buffers[i]; + + /* printk("Return: 0x%llx (%d)\n", bufd->iovas[0], + bufd->pool_type); */ + + if (bufd->pool_type == 0) { + for (int j = 0; j < ARRAY_SIZE(isp->meta_surfs); j++) { + struct isp_surf *meta = isp->meta_surfs[j]; + if ((u32)bufd->iovas[0] == (u32)meta->iova) { + WARN_ON(!meta->submitted); + meta->submitted = false; + } + } + } else { + list_for_each_entry_safe_reverse( + buf, tmp, &isp->bufs_submitted, link) { + if ((u32)buf->surfs[0].iova == + (u32)bufd->iovas[0]) { + enum vb2_buffer_state state = + VB2_BUF_STATE_ERROR; + + buf->vb.vb2_buf.timestamp = + ktime_get_ns(); + buf->vb.sequence = isp->sequence++; + buf->vb.field = V4L2_FIELD_NONE; + if (req->arg2 == + ISP_IPC_BUFEXC_FLAG_RENDER) + state = VB2_BUF_STATE_DONE; + vb2_buffer_done(&buf->vb.vb2_buf, + state); + list_del(&buf->link); + } + } + } + } + spin_unlock(&isp->buf_lock); + + rsp->arg0 = req->arg0 | ISP_IPC_FLAG_ACK; + rsp->arg1 = 0x0; + rsp->arg2 = ISP_IPC_BUFEXC_FLAG_ACK; + + return err; +} static int isp_submit_buffers(struct apple_isp *isp) { struct isp_format *fmt = isp_get_current_format(isp); struct isp_channel *chan = isp->chan_bh; struct isp_message *req = &chan->req; - struct isp_buffer *buf, *buf2, *tmp; + struct isp_buffer *buf, *tmp; unsigned long flags; size_t offset; int err; - struct isp_h2t_args *args = - kzalloc(sizeof(struct isp_h2t_args), GFP_KERNEL); - if (!args) - return -ENOMEM; + struct isp_buflist *bl = isp->cmd_virt; + struct isp_buflist_buffer *bufd = &bl->buffers[0]; + + bl->type = 1; + bl->num_buffers = 0; spin_lock_irqsave(&isp->buf_lock, flags); + for (int i = 0; i < ARRAY_SIZE(isp->meta_surfs); i++) { + struct isp_surf *meta = isp->meta_surfs[i]; + + if (meta->submitted) + continue; + + /* printk("Submit: 0x%llx .. 0x%llx (meta)\n", meta->iova, + meta->iova + meta->size); */ + + bufd->num_planes = 1; + bufd->pool_type = 0; + bufd->iovas[0] = meta->iova; + bufd->flags[0] = 0x40000000; + bufd++; + bl->num_buffers++; + + meta->submitted = true; + } + while ((buf = list_first_entry_or_null(&isp->bufs_pending, struct isp_buffer, link))) { - args->meta.num_planes = 1; - args->meta.pool_type = 0; - args->meta.iovas[0] = buf->meta->iova; - args->meta.flags[0] = 0x40000000; - - args->render.num_planes = fmt->num_planes; - args->render.pool_type = isp->hw->scl1 ? - CISP_POOL_TYPE_RENDERED_SCL1 : - CISP_POOL_TYPE_RENDERED; + memset(bufd, 0, sizeof(*bufd)); + + bufd->num_planes = fmt->num_planes; + bufd->pool_type = isp->hw->scl1 ? CISP_POOL_TYPE_RENDERED_SCL1 : + CISP_POOL_TYPE_RENDERED; offset = 0; for (int j = 0; j < fmt->num_planes; j++) { - args->render.iovas[j] = buf->surfs[0].iova + offset; - args->render.flags[j] = 0x40000000; + bufd->iovas[j] = buf->surfs[0].iova + offset; + bufd->flags[j] = 0x40000000; offset += fmt->plane_size[j]; } + /* printk("Submit: 0x%llx .. 0x%llx (render)\n", + buf->surfs[0].iova, + buf->surfs[0].iova + buf->surfs[0].size); */ + bufd++; + bl->num_buffers++; + /* * Queue the buffer as submitted and release the lock for now. * We need to do this before actually submitting to avoid a * race with the buffer return codepath. */ list_move_tail(&buf->link, &isp->bufs_submitted); - spin_unlock_irqrestore(&isp->buf_lock, flags); + } + + spin_unlock_irqrestore(&isp->buf_lock, flags); + + req->arg0 = isp->cmd_iova; + req->arg1 = max_t(u64, ISP_IPC_BUFEXC_STAT_SIZE, + ((uintptr_t)bufd - (uintptr_t)bl)); + req->arg2 = ISP_IPC_BUFEXC_FLAG_COMMAND; + + err = ipc_chan_send(isp, chan, ISP_BUFFER_TIMEOUT); + if (err) { + /* If we fail, consider the buffer not submitted. */ + dev_err(isp->dev, + "%s: failed to send bufs: [0x%llx, 0x%llx, 0x%llx]\n", + chan->name, req->arg0, req->arg1, req->arg2); + + /* + * Try to find the buffer in the list, and if it's + * still there, move it back to the pending list. + */ + spin_lock_irqsave(&isp->buf_lock, flags); - args->enable = 0x1; - args->num_buffers = 2; - - req->arg0 = isp->cmd_iova; - req->arg1 = ISP_IPC_BUFEXC_STAT_SIZE; - req->arg2 = ISP_IPC_BUFEXC_FLAG_COMMAND; - - memcpy(isp->cmd_virt, args, sizeof(*args)); - err = ipc_chan_send(isp, chan, ISP_BUFFER_TIMEOUT); - if (err) { - /* If we fail, consider the buffer not submitted. */ - dev_err(isp->dev, - "%s: failed to send bufs: [0x%llx, 0x%llx, 0x%llx]\n", - chan->name, req->arg0, req->arg1, req->arg2); - - /* - * Try to find the buffer in the list, and if it's - * still there, move it back to the pending list. - */ - spin_lock_irqsave(&isp->buf_lock, flags); + bufd = &bl->buffers[0]; + for (int i = 0; i < bl->num_buffers; i++, bufd++) { list_for_each_entry_safe_reverse( - buf2, tmp, &isp->bufs_submitted, link) { - if (buf2 == buf) { + buf, tmp, &isp->bufs_submitted, link) { + if (bufd->iovas[0] == buf->surfs[0].iova) { list_move_tail(&buf->link, &isp->bufs_pending); - spin_unlock_irqrestore(&isp->buf_lock, - flags); - return err; } } - /* - * We didn't find the buffer, which means it somehow was returned - * by the firmware even though submission failed? - */ - dev_err(isp->dev, - "buffer submission failed but buffer was returned?\n"); - spin_unlock_irqrestore(&isp->buf_lock, flags); - return err; + for (int j = 0; j < ARRAY_SIZE(isp->meta_surfs); j++) { + struct isp_surf *meta = isp->meta_surfs[j]; + if (bufd->iovas[0] == meta->iova) { + meta->submitted = false; + } + } } - spin_lock_irqsave(&isp->buf_lock, flags); + spin_unlock_irqrestore(&isp->buf_lock, flags); } - spin_unlock_irqrestore(&isp->buf_lock, flags); - - kfree(args); return err; } @@ -172,7 +260,6 @@ static void __isp_vb2_buf_cleanup(struct vb2_buffer *vb, unsigned int i) while (i--) apple_isp_iommu_unmap_sgt(isp, &buf->surfs[i]); - isp_free_surface(isp, buf->meta); } static void isp_vb2_buf_cleanup(struct vb2_buffer *vb) @@ -188,10 +275,6 @@ static int isp_vb2_buf_init(struct vb2_buffer *vb) unsigned int i; int err; - buf->meta = isp_alloc_surface(isp, isp->hw->meta_size); - if (!buf->meta) - return -ENOMEM; - for (i = 0; i < vb->num_planes; i++) { struct sg_table *sgt = vb2_dma_sg_plane_desc(vb, i); err = apple_isp_iommu_map_sgt(isp, &buf->surfs[i], sgt, @@ -678,6 +761,16 @@ int apple_isp_setup_video(struct apple_isp *isp) return err; } + for (int i = 0; i < ARRAY_SIZE(isp->meta_surfs); i++) { + isp->meta_surfs[i] = + isp_alloc_surface_vmap(isp, isp->hw->meta_size); + if (!isp->meta_surfs[i]) { + isp_err(isp, "failed to alloc meta surface\n"); + err = -ENOMEM; + goto surf_cleanup; + } + } + media_device_init(&isp->mdev); isp->v4l2_dev.mdev = &isp->mdev; isp->mdev.ops = &isp_media_device_ops; @@ -744,6 +837,13 @@ int apple_isp_setup_video(struct apple_isp *isp) media_device_unregister(&isp->mdev); media_cleanup: media_device_cleanup(&isp->mdev); +surf_cleanup: + for (int i = 0; i < ARRAY_SIZE(isp->meta_surfs); i++) { + if (isp->meta_surfs[i]) + isp_free_surface(isp, isp->meta_surfs[i]); + isp->meta_surfs[i] = NULL; + } + return err; } @@ -753,4 +853,9 @@ void apple_isp_remove_video(struct apple_isp *isp) v4l2_device_unregister(&isp->v4l2_dev); media_device_unregister(&isp->mdev); media_device_cleanup(&isp->mdev); + for (int i = 0; i < ARRAY_SIZE(isp->meta_surfs); i++) { + if (isp->meta_surfs[i]) + isp_free_surface(isp, isp->meta_surfs[i]); + isp->meta_surfs[i] = NULL; + } } diff --git a/drivers/media/platform/apple/isp/isp-v4l2.h b/drivers/media/platform/apple/isp/isp-v4l2.h index df9b961d77bc17..e81e4de6ca641f 100644 --- a/drivers/media/platform/apple/isp/isp-v4l2.h +++ b/drivers/media/platform/apple/isp/isp-v4l2.h @@ -8,5 +8,6 @@ int apple_isp_setup_video(struct apple_isp *isp); void apple_isp_remove_video(struct apple_isp *isp); +int ipc_bt_handle(struct apple_isp *isp, struct isp_channel *chan); #endif /* __ISP_V4L2_H__ */ From 3aa59f171ea89e4606f022c5f407f77fb76b29f9 Mon Sep 17 00:00:00 2001 From: Asahi Lina Date: Wed, 4 Oct 2023 22:25:07 +0900 Subject: [PATCH 0307/3902] media: apple: isp: Clear IRQs when resetting coproc XXX this might be wrong on some chips? Signed-off-by: Asahi Lina --- drivers/media/platform/apple/isp/isp-fw.c | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/drivers/media/platform/apple/isp/isp-fw.c b/drivers/media/platform/apple/isp/isp-fw.c index 1db1294f843a7a..addf6ba6b37525 100644 --- a/drivers/media/platform/apple/isp/isp-fw.c +++ b/drivers/media/platform/apple/isp/isp-fw.c @@ -215,6 +215,7 @@ static int isp_reset_coproc(struct apple_isp *isp) { int retries; u32 status; + u32 val; isp_coproc_write32(isp, ISP_COPROC_EDPRCR, 0x2); @@ -230,6 +231,18 @@ static int isp_reset_coproc(struct apple_isp *isp) isp_coproc_write32(isp, ISP_COPROC_IRQ_MASK_4, 0xffffffff); isp_coproc_write32(isp, ISP_COPROC_IRQ_MASK_5, 0xffffffff); + for (retries = 0; retries < 128; retries++) { + val = isp_coproc_read32(isp, 0x818); + if (val == 0) + break; + } + + for (retries = 0; retries < 128; retries++) { + val = isp_coproc_read32(isp, 0x81c); + if (val == 0) + break; + } + for (retries = 0; retries < ISP_FIRMWARE_MAX_TRIES; retries++) { status = isp_coproc_read32(isp, ISP_COPROC_STATUS); if (status & ISP_COPROC_IN_WFI) { From a23a0cf567a0964d03e5b9a3681a890a939d5ca5 Mon Sep 17 00:00:00 2001 From: Asahi Lina Date: Wed, 4 Oct 2023 22:25:50 +0900 Subject: [PATCH 0308/3902] media: apple: isp: Add a missing read barrier (possibly?) Signed-off-by: Asahi Lina --- drivers/media/platform/apple/isp/isp-ipc.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/drivers/media/platform/apple/isp/isp-ipc.c b/drivers/media/platform/apple/isp/isp-ipc.c index 7df434513b6c64..9d965ef7756b9b 100644 --- a/drivers/media/platform/apple/isp/isp-ipc.c +++ b/drivers/media/platform/apple/isp/isp-ipc.c @@ -157,6 +157,8 @@ int ipc_chan_handle(struct apple_isp *isp, struct isp_channel *chan) static inline bool chan_tx_done(struct apple_isp *isp, struct isp_channel *chan) { + dma_rmb(); + chan_read_msg(isp, chan, &chan->rsp); if ((chan->rsp.arg0) == (chan->req.arg0 | ISP_IPC_FLAG_ACK)) { chan_update_cursor(chan); From 8a20d764d63ac07745c57763a7a1fde2d1d98199 Mon Sep 17 00:00:00 2001 From: Asahi Lina Date: Wed, 4 Oct 2023 22:26:47 +0900 Subject: [PATCH 0309/3902] media: apple: isp: VMap only what is necessary, remove redundant logging state bit Signed-off-by: Asahi Lina --- drivers/media/platform/apple/isp/isp-ipc.c | 41 ++++++++-------------- 1 file changed, 15 insertions(+), 26 deletions(-) diff --git a/drivers/media/platform/apple/isp/isp-ipc.c b/drivers/media/platform/apple/isp/isp-ipc.c index 9d965ef7756b9b..54a88ed876c2b7 100644 --- a/drivers/media/platform/apple/isp/isp-ipc.c +++ b/drivers/media/platform/apple/isp/isp-ipc.c @@ -204,7 +204,7 @@ int ipc_tm_handle(struct apple_isp *isp, struct isp_channel *chan) dma_addr_t iova = req->arg0 & ~ISP_IPC_FLAG_TERMINAL_ACK; u32 size = req->arg1; if (iova && size && size < sizeof(buf) && - test_bit(ISP_STATE_LOGGING, &isp->state)) { + isp->log_surf) { void *p = apple_isp_translate(isp, isp->log_surf, iova, size); if (p) { size = min_t(u32, size, 512); @@ -243,42 +243,31 @@ int ipc_sm_handle(struct apple_isp *isp, struct isp_channel *chan) rsp->arg1 = 0x0; rsp->arg2 = 0x0; /* macOS uses this to index surfaces */ + switch (surf->type) { + case 0x4c4f47: /* "LOG" */ + isp->log_surf = surf; + break; + case 0x4d495343: /* "MISC" */ + /* Hacky... maybe there's a better way to identify this surface? */ + if (surf->size == 0xc000) + isp->bt_surf = surf; + break; + default: + // skip vmap + return 0; + } + err = isp_surf_vmap(isp, surf); if (err < 0) { isp_err(isp, "failed to vmap iova=0x%llx size=0x%llx\n", surf->iova, surf->size); - } else { - switch (surf->type) { - case 0x4c4f47: /* "LOG" */ - isp->log_surf = surf; - break; - case 0x4d495343: /* "MISC" */ - /* Hacky... maybe there's a better way to identify this surface? */ - if (surf->size == 0xc000) - isp->bt_surf = surf; - break; - } } - -#ifdef APPLE_ISP_DEBUG - /* Only enabled in debug builds so it shouldn't matter, but - * the LOG surface is always the first surface requested. - */ - if (!test_bit(ISP_STATE_LOGGING, &isp->state)) - set_bit(ISP_STATE_LOGGING, &isp->state); -#endif - /* To the gc it goes... */ - } else { /* This should be the shared surface free request, but * 1) The fw doesn't request to free all of what it requested * 2) The fw continues to access the surface after * So we link it to the gc, which runs after fw shutdown */ -#ifdef APPLE_ISP_DEBUG - if (test_bit(ISP_STATE_LOGGING, &isp->state)) - clear_bit(ISP_STATE_LOGGING, &isp->state); -#endif rsp->arg0 = req->arg0 | ISP_IPC_FLAG_ACK; rsp->arg1 = 0x0; rsp->arg2 = 0x0; From 491917ff48a63d82ffe5d46f8b032c25f86ccfca Mon Sep 17 00:00:00 2001 From: Asahi Lina Date: Wed, 4 Oct 2023 22:28:03 +0900 Subject: [PATCH 0310/3902] media: apple: isp: Only reset coproc when necessary, fix minor race Signed-off-by: Asahi Lina --- drivers/media/platform/apple/isp/isp-fw.c | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/drivers/media/platform/apple/isp/isp-fw.c b/drivers/media/platform/apple/isp/isp-fw.c index addf6ba6b37525..a61c14453479d9 100644 --- a/drivers/media/platform/apple/isp/isp-fw.c +++ b/drivers/media/platform/apple/isp/isp-fw.c @@ -270,16 +270,22 @@ static void isp_firmware_shutdown_stage1(struct apple_isp *isp) static int isp_firmware_boot_stage1(struct apple_isp *isp) { int err, retries; + u32 val; + err = apple_isp_power_up_domains(isp); if (err < 0) return err; - err = isp_reset_coproc(isp); - if (err < 0) - return err; isp_gpio_write32(isp, ISP_GPIO_CLOCK_EN, 0x1); + val = isp_gpio_read32(isp, ISP_GPIO_1); + if (val == 0xfeedbabe) { + err = isp_reset_coproc(isp); + if (err < 0) + return err; + } + isp_gpio_write32(isp, ISP_GPIO_0, 0x0); isp_gpio_write32(isp, ISP_GPIO_1, 0x0); isp_gpio_write32(isp, ISP_GPIO_2, 0x0); @@ -295,7 +301,6 @@ static int isp_firmware_boot_stage1(struct apple_isp *isp) isp_coproc_write32(isp, ISP_COPROC_CONTROL, 0x10); /* Wait for ISP_GPIO_7 to 0x0 -> 0x8042006 */ - isp_gpio_write32(isp, ISP_GPIO_7, 0x0); for (retries = 0; retries < ISP_FIRMWARE_MAX_TRIES; retries++) { u32 val = isp_gpio_read32(isp, ISP_GPIO_7); if (val == 0x8042006) { From 48b7dc75f8929e0d949a9c448c71d107bd40e3b5 Mon Sep 17 00:00:00 2001 From: Asahi Lina Date: Wed, 4 Oct 2023 22:28:55 +0900 Subject: [PATCH 0311/3902] media: apple: isp: Option to use CMD_STOP (ifdeffed out) Signed-off-by: Asahi Lina --- drivers/media/platform/apple/isp/isp-fw.c | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/drivers/media/platform/apple/isp/isp-fw.c b/drivers/media/platform/apple/isp/isp-fw.c index a61c14453479d9..90895616c7c5ad 100644 --- a/drivers/media/platform/apple/isp/isp-fw.c +++ b/drivers/media/platform/apple/isp/isp-fw.c @@ -595,11 +595,26 @@ static int isp_stop_command_processor(struct apple_isp *isp) { int retries; +#if 0 + int res = isp_cmd_stop(isp, 0); + if (res) { + isp_err(isp, "isp_cmd_stop() failed\n"); + return res; + } + /* Wait for ISP_GPIO_0 to 0xf7fbdff9 -> 0x8042006 */ isp_gpio_write32(isp, ISP_GPIO_0, 0xf7fbdff9); - /* Their CISP_CMD_STOP implementation is buggy */ - isp_cmd_suspend(isp); + isp_cmd_power_down(isp); +#else + isp_gpio_write32(isp, ISP_GPIO_0, 0xf7fbdff9); + + int res = isp_cmd_suspend(isp); + if (res) { + isp_err(isp, "isp_cmd_suspend() failed\n"); + return res; + } +#endif for (retries = 0; retries < ISP_FIRMWARE_MAX_TRIES; retries++) { u32 val = isp_gpio_read32(isp, ISP_GPIO_0); From 008af3935e04dbf378218c2b416ea3411bef9272 Mon Sep 17 00:00:00 2001 From: Hector Martin Date: Thu, 5 Oct 2023 23:24:32 +0900 Subject: [PATCH 0312/3902] media: apple: isp: Use a more user-friendly device name Signed-off-by: Hector Martin --- drivers/media/platform/apple/isp/isp-drv.h | 1 + drivers/media/platform/apple/isp/isp-v4l2.c | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/drivers/media/platform/apple/isp/isp-drv.h b/drivers/media/platform/apple/isp/isp-drv.h index 31c527532aebac..847e0a90975fb5 100644 --- a/drivers/media/platform/apple/isp/isp-drv.h +++ b/drivers/media/platform/apple/isp/isp-drv.h @@ -16,6 +16,7 @@ /* #define APPLE_ISP_DEBUG */ #define APPLE_ISP_DEVICE_NAME "apple-isp" +#define APPLE_ISP_CARD_NAME "FaceTime HD Camera" #define ISP_MAX_CHANNELS 6 #define ISP_IPC_MESSAGE_SIZE 64 diff --git a/drivers/media/platform/apple/isp/isp-v4l2.c b/drivers/media/platform/apple/isp/isp-v4l2.c index ff2ea72f57ee9d..44d79cc8e0f444 100644 --- a/drivers/media/platform/apple/isp/isp-v4l2.c +++ b/drivers/media/platform/apple/isp/isp-v4l2.c @@ -448,7 +448,7 @@ static struct isp_preset *isp_select_preset(struct apple_isp *isp, u32 width, static int isp_vidioc_querycap(struct file *file, void *priv, struct v4l2_capability *cap) { - strscpy(cap->card, APPLE_ISP_DEVICE_NAME, sizeof(cap->card)); + strscpy(cap->card, APPLE_ISP_CARD_NAME, sizeof(cap->card)); strscpy(cap->driver, APPLE_ISP_DEVICE_NAME, sizeof(cap->driver)); return 0; From 4405b31bbb8ab2f10d77c9828490f597512da1e2 Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Fri, 6 Oct 2023 21:34:11 +0200 Subject: [PATCH 0313/3902] media: apple: isp: Parse firmware version from device tree Required since t8112-isp uses a 32-bit address in the CISP_CMD_CH_SET_FILE_LOAD command with the macOS 12.4 firmware. Signed-off-by: Janne Grunau --- drivers/media/platform/apple/isp/isp-cmd.c | 3 +- drivers/media/platform/apple/isp/isp-drv.c | 71 ++++++++++++++++++++++ drivers/media/platform/apple/isp/isp-drv.h | 8 +++ 3 files changed, 81 insertions(+), 1 deletion(-) diff --git a/drivers/media/platform/apple/isp/isp-cmd.c b/drivers/media/platform/apple/isp/isp-cmd.c index 9c5808b4e831be..ee491d2cb42c5b 100644 --- a/drivers/media/platform/apple/isp/isp-cmd.c +++ b/drivers/media/platform/apple/isp/isp-cmd.c @@ -2,6 +2,7 @@ /* Copyright 2023 Eileen Yoon */ #include "isp-cmd.h" +#include "isp-drv.h" #include "isp-iommu.h" #include "isp-ipc.h" @@ -261,7 +262,7 @@ int isp_cmd_ch_buffer_return(struct apple_isp *isp, u32 chan) int isp_cmd_ch_set_file_load(struct apple_isp *isp, u32 chan, u64 addr, u32 size) { - if (isp->hw->gen >= ISP_GEN_T8112) { + if (isp->fw_compat >= ISP_FIRMWARE_V_13_5) { struct cmd_ch_set_file_load64 args = { .opcode = CISP_OPCODE(CISP_CMD_CH_SET_FILE_LOAD), .chan = chan, diff --git a/drivers/media/platform/apple/isp/isp-drv.c b/drivers/media/platform/apple/isp/isp-drv.c index 2ea4ecad36c75e..09bc0af68aab74 100644 --- a/drivers/media/platform/apple/isp/isp-drv.c +++ b/drivers/media/platform/apple/isp/isp-drv.c @@ -215,6 +215,72 @@ static int apple_isp_init_presets(struct apple_isp *isp) return 0; } +static const char * isp_fw2str(enum isp_firmware_version version) +{ + switch (version) { + case ISP_FIRMWARE_V_12_3: + return "12.3"; + case ISP_FIRMWARE_V_12_4: + return "12.4"; + case ISP_FIRMWARE_V_13_5: + return "13.5"; + default: + return "unknown"; + } +} + +#define ISP_FW_VERSION_MIN_LEN 3 +#define ISP_FW_VERSION_MAX_LEN 5 + +static enum isp_firmware_version isp_read_fw_version(struct device *dev, + const char *name) +{ + u32 ver[ISP_FW_VERSION_MAX_LEN]; + int len = of_property_read_variable_u32_array(dev->of_node, name, ver, + ISP_FW_VERSION_MIN_LEN, + ISP_FW_VERSION_MAX_LEN); + + switch (len) { + case 3: + if (ver[0] == 12 && ver[1] == 3 && ver[2] <= 1) + return ISP_FIRMWARE_V_12_3; + else if (ver[0] == 12 && ver[1] == 4 && ver[2] == 0) + return ISP_FIRMWARE_V_12_4; + else if (ver[0] == 13 && ver[1] == 5 && ver[2] == 0) + return ISP_FIRMWARE_V_13_5; + + dev_warn(dev, "unknown %s: %d.%d.%d\n", name, ver[0], ver[1], ver[2]); + break; + case 4: + dev_warn(dev, "unknown %s: %d.%d.%d.%d\n", name, ver[0], ver[1], + ver[2], ver[3]); + break; + case 5: + dev_warn(dev, "unknown %s: %d.%d.%d.%d.%d\n", name, ver[0], + ver[1], ver[2], ver[3], ver[4]); + break; + default: + dev_warn(dev, "could not parse %s: %d\n", name, len); + break; + } + + return ISP_FIRMWARE_V_UNKNOWN; +} + +static enum isp_firmware_version isp_check_firmware_version(struct device *dev) +{ + enum isp_firmware_version version, compat; + + /* firmware version is just informative */ + version = isp_read_fw_version(dev, "apple,firmware-version"); + compat = isp_read_fw_version(dev, "apple,firmware-compat"); + + dev_info(dev, "ISP firmware-compat: %s (FW: %s)\n", isp_fw2str(compat), + isp_fw2str(version)); + + return compat; +} + static int apple_isp_probe(struct platform_device *pdev) { struct device *dev = &pdev->dev; @@ -234,6 +300,11 @@ static int apple_isp_probe(struct platform_device *pdev) platform_set_drvdata(pdev, isp); dev_set_drvdata(dev, isp); + /* Differences between firmware versions are rather minor so try to work + * with unknown firmware. + */ + isp->fw_compat = isp_check_firmware_version(dev); + err = of_property_read_u32(dev->of_node, "apple,platform-id", &isp->platform_id); if (err) { diff --git a/drivers/media/platform/apple/isp/isp-drv.h b/drivers/media/platform/apple/isp/isp-drv.h index 847e0a90975fb5..2ccd3524be65b8 100644 --- a/drivers/media/platform/apple/isp/isp-drv.h +++ b/drivers/media/platform/apple/isp/isp-drv.h @@ -32,6 +32,13 @@ enum isp_generation { ISP_GEN_T8112, }; +enum isp_firmware_version { + ISP_FIRMWARE_V_UNKNOWN, + ISP_FIRMWARE_V_12_3, + ISP_FIRMWARE_V_12_4, + ISP_FIRMWARE_V_13_5, +}; + struct isp_surf { struct drm_mm_node *mm; struct list_head head; @@ -180,6 +187,7 @@ struct isp_format { struct apple_isp { struct device *dev; const struct apple_isp_hw *hw; + enum isp_firmware_version fw_compat; u32 platform_id; u32 temporal_filter; struct isp_preset *presets; From a4aa177753b926ca7c66b40dc8256375b09c472b Mon Sep 17 00:00:00 2001 From: Hector Martin Date: Sun, 8 Oct 2023 18:02:12 +0900 Subject: [PATCH 0314/3902] media: apple: isp: Show camera presets even for unsupported sensors This makes adding support easier. Signed-off-by: Hector Martin --- drivers/media/platform/apple/isp/isp-cam.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/drivers/media/platform/apple/isp/isp-cam.c b/drivers/media/platform/apple/isp/isp-cam.c index cc0c24c3cfb715..fac81fef11bc7e 100644 --- a/drivers/media/platform/apple/isp/isp-cam.c +++ b/drivers/media/platform/apple/isp/isp-cam.c @@ -212,6 +212,10 @@ static int isp_ch_cache_sensor_info(struct apple_isp *isp, u32 ch) print_hex_dump(KERN_INFO, "apple-isp: ch: ", DUMP_PREFIX_NONE, 32, 4, args, sizeof(*args), false); + for (u32 ps = 0; ps < args->num_presets; ps++) { + isp_ch_get_camera_preset(isp, ch, ps); + } + err = isp_ch_get_sensor_id(isp, ch); if (err || (fmt->id != ISP_IMX248_1820_01 && fmt->id != ISP_IMX558_1921_01)) { @@ -221,10 +225,6 @@ static int isp_ch_cache_sensor_info(struct apple_isp *isp, u32 ch) return -ENODEV; } - for (u32 ps = 0; ps < args->num_presets; ps++) { - isp_ch_get_camera_preset(isp, ch, ps); - } - exit: kfree(args); From 8f66db3379a9336d0e3e5ea87150f654894d94fc Mon Sep 17 00:00:00 2001 From: Hector Martin Date: Sun, 8 Oct 2023 18:03:20 +0900 Subject: [PATCH 0315/3902] media: apple: isp: Enable IMX364 sensor This is used on j45[67]. Signed-off-by: Hector Martin --- drivers/media/platform/apple/isp/isp-cam.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/drivers/media/platform/apple/isp/isp-cam.c b/drivers/media/platform/apple/isp/isp-cam.c index fac81fef11bc7e..c889173bd348f3 100644 --- a/drivers/media/platform/apple/isp/isp-cam.c +++ b/drivers/media/platform/apple/isp/isp-cam.c @@ -218,7 +218,8 @@ static int isp_ch_cache_sensor_info(struct apple_isp *isp, u32 ch) err = isp_ch_get_sensor_id(isp, ch); if (err || - (fmt->id != ISP_IMX248_1820_01 && fmt->id != ISP_IMX558_1921_01)) { + (fmt->id != ISP_IMX248_1820_01 && fmt->id != ISP_IMX558_1921_01 && + fmt->id != ISP_IMX364_8720_01)) { dev_err(isp->dev, "ch %d: unsupported sensor. Please file a bug report with hardware info & dmesg trace.\n", ch); From f829ef38668c6d53b7e15c48926a96391277d969 Mon Sep 17 00:00:00 2001 From: Hector Martin Date: Thu, 12 Oct 2023 02:41:08 +0900 Subject: [PATCH 0316/3902] media: apple: isp: implement ENUM_FRAMEINTERVALS trivially Signed-off-by: Hector Martin --- drivers/media/platform/apple/isp/isp-v4l2.c | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/drivers/media/platform/apple/isp/isp-v4l2.c b/drivers/media/platform/apple/isp/isp-v4l2.c index 44d79cc8e0f444..34a0d6d91c1274 100644 --- a/drivers/media/platform/apple/isp/isp-v4l2.c +++ b/drivers/media/platform/apple/isp/isp-v4l2.c @@ -497,6 +497,18 @@ static int isp_vidioc_enum_framesizes(struct file *file, void *fh, return 0; } +static int isp_vidioc_enum_frameintervals(struct file *filp, void *priv, + struct v4l2_frmivalenum *interval) +{ + if (interval->index != 0) + return -EINVAL; + + interval->type = V4L2_FRMIVAL_TYPE_DISCRETE; + interval->discrete.numerator = 1; + interval->discrete.denominator = 30; + return 0; +} + static inline void isp_get_sp_pix_format(struct apple_isp *isp, struct v4l2_format *f, struct isp_format *fmt) @@ -717,6 +729,7 @@ static const struct v4l2_ioctl_ops isp_v4l2_ioctl_ops = { .vidioc_try_fmt_vid_cap_mplane = isp_vidioc_try_format_mplane, .vidioc_enum_framesizes = isp_vidioc_enum_framesizes, + .vidioc_enum_frameintervals = isp_vidioc_enum_frameintervals, .vidioc_enum_input = isp_vidioc_enum_input, .vidioc_g_input = isp_vidioc_get_input, .vidioc_s_input = isp_vidioc_set_input, From 81b0f8d27cab8053ea1dfdb2a9742f4502d38cac Mon Sep 17 00:00:00 2001 From: Hector Martin Date: Fri, 3 Nov 2023 20:49:38 +0900 Subject: [PATCH 0317/3902] media: apple: isp: Use a mutex instead of a spinlock for channels Fixes lockdep splats because we do surface stuff with this held, which takes a mutex. Signed-off-by: Hector Martin --- drivers/media/platform/apple/isp/isp-drv.h | 2 +- drivers/media/platform/apple/isp/isp-fw.c | 2 +- drivers/media/platform/apple/isp/isp-ipc.c | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/drivers/media/platform/apple/isp/isp-drv.h b/drivers/media/platform/apple/isp/isp-drv.h index 2ccd3524be65b8..4bdf7616e0efe4 100644 --- a/drivers/media/platform/apple/isp/isp-drv.h +++ b/drivers/media/platform/apple/isp/isp-drv.h @@ -76,7 +76,7 @@ struct isp_channel { void *virt; u32 doorbell; u32 cursor; - spinlock_t lock; + struct mutex lock; struct isp_message req; struct isp_message rsp; const struct isp_chan_ops *ops; diff --git a/drivers/media/platform/apple/isp/isp-fw.c b/drivers/media/platform/apple/isp/isp-fw.c index 90895616c7c5ad..3e322d40fb881f 100644 --- a/drivers/media/platform/apple/isp/isp-fw.c +++ b/drivers/media/platform/apple/isp/isp-fw.c @@ -493,7 +493,7 @@ static int isp_fill_channel_info(struct apple_isp *isp) chan->virt = apple_isp_ipc_translate(isp, desc.iova, chan->size); chan->cursor = 0; - spin_lock_init(&chan->lock); + mutex_init(&chan->lock); if (!chan->virt) { dev_err(isp->dev, "Failed to find channel buffer\n"); diff --git a/drivers/media/platform/apple/isp/isp-ipc.c b/drivers/media/platform/apple/isp/isp-ipc.c index 54a88ed876c2b7..7300eb60892116 100644 --- a/drivers/media/platform/apple/isp/isp-ipc.c +++ b/drivers/media/platform/apple/isp/isp-ipc.c @@ -138,7 +138,7 @@ int ipc_chan_handle(struct apple_isp *isp, struct isp_channel *chan) { int err = 0; - spin_lock(&chan->lock); + mutex_lock(&chan->lock); while (1) { chan_read_msg(isp, chan, &chan->req); if (chan_rx_done(isp, chan)) { @@ -150,7 +150,7 @@ int ipc_chan_handle(struct apple_isp *isp, struct isp_channel *chan) break; } } - spin_unlock(&chan->lock); + mutex_unlock(&chan->lock); return err; } From bdd67b26ac25ba6b798febc15c822401a5f8459d Mon Sep 17 00:00:00 2001 From: Eileen Yoon Date: Fri, 13 Oct 2023 21:09:43 +0900 Subject: [PATCH 0318/3902] media: apple: isp: Support system sleep Signed-off-by: Eileen Yoon --- drivers/media/platform/apple/isp/isp-drv.c | 29 +++++++++++- drivers/media/platform/apple/isp/isp-drv.h | 1 + drivers/media/platform/apple/isp/isp-fw.c | 13 ++++-- drivers/media/platform/apple/isp/isp-v4l2.c | 50 ++++++++++++++++++--- drivers/media/platform/apple/isp/isp-v4l2.h | 3 ++ 5 files changed, 84 insertions(+), 12 deletions(-) diff --git a/drivers/media/platform/apple/isp/isp-drv.c b/drivers/media/platform/apple/isp/isp-drv.c index 09bc0af68aab74..848f7abd535a7f 100644 --- a/drivers/media/platform/apple/isp/isp-drv.c +++ b/drivers/media/platform/apple/isp/isp-drv.c @@ -541,17 +541,42 @@ static const struct of_device_id apple_isp_of_match[] = { }; MODULE_DEVICE_TABLE(of, apple_isp_of_match); +static __maybe_unused int apple_isp_runtime_suspend(struct device *dev) +{ + /* RPM sleep is called when the V4L2 file handle is closed */ + return 0; +} + +static __maybe_unused int apple_isp_runtime_resume(struct device *dev) +{ + return 0; +} + static __maybe_unused int apple_isp_suspend(struct device *dev) { + struct apple_isp *isp = dev_get_drvdata(dev); + + /* We must restore V4L2 context on system resume. If we were streaming + * before, we (essentially) stop streaming and start streaming again. + */ + apple_isp_video_suspend(isp); + return 0; } static __maybe_unused int apple_isp_resume(struct device *dev) { + struct apple_isp *isp = dev_get_drvdata(dev); + + apple_isp_video_resume(isp); + return 0; } -DEFINE_RUNTIME_DEV_PM_OPS(apple_isp_pm_ops, apple_isp_suspend, apple_isp_resume, - NULL); + +static const struct dev_pm_ops apple_isp_pm_ops = { + SYSTEM_SLEEP_PM_OPS(apple_isp_suspend, apple_isp_resume) + RUNTIME_PM_OPS(apple_isp_runtime_suspend, apple_isp_runtime_resume, NULL) +}; static struct platform_driver apple_isp_driver = { .driver = { diff --git a/drivers/media/platform/apple/isp/isp-drv.h b/drivers/media/platform/apple/isp/isp-drv.h index 4bdf7616e0efe4..96a1d0b39f860d 100644 --- a/drivers/media/platform/apple/isp/isp-drv.h +++ b/drivers/media/platform/apple/isp/isp-drv.h @@ -270,6 +270,7 @@ struct isp_buffer { enum { ISP_STATE_STREAMING, ISP_STATE_LOGGING, + ISP_STATE_SLEEPING, }; #ifdef APPLE_ISP_DEBUG diff --git a/drivers/media/platform/apple/isp/isp-fw.c b/drivers/media/platform/apple/isp/isp-fw.c index 3e322d40fb881f..a39f5fb4445fa7 100644 --- a/drivers/media/platform/apple/isp/isp-fw.c +++ b/drivers/media/platform/apple/isp/isp-fw.c @@ -42,8 +42,8 @@ static inline void isp_gpio_write32(struct apple_isp *isp, u32 reg, u32 val) writel(val, isp->gpio + reg); } -int apple_isp_power_up_domains(struct apple_isp *isp) static int apple_isp_power_up_domains(struct apple_isp *isp) +{ int ret; if (isp->pds_active) @@ -65,8 +65,8 @@ static int apple_isp_power_up_domains(struct apple_isp *isp) return 0; } -void apple_isp_power_down_domains(struct apple_isp *isp) static void apple_isp_power_down_domains(struct apple_isp *isp) +{ int ret; if (!isp->pds_active) @@ -270,7 +270,7 @@ static void isp_firmware_shutdown_stage1(struct apple_isp *isp) static int isp_firmware_boot_stage1(struct apple_isp *isp) { int err, retries; - u32 val; + // u32 val; err = apple_isp_power_up_domains(isp); if (err < 0) @@ -279,12 +279,19 @@ static int isp_firmware_boot_stage1(struct apple_isp *isp) isp_gpio_write32(isp, ISP_GPIO_CLOCK_EN, 0x1); +#if 0 + /* This doesn't work well with system sleep */ val = isp_gpio_read32(isp, ISP_GPIO_1); if (val == 0xfeedbabe) { err = isp_reset_coproc(isp); if (err < 0) return err; } +#endif + + err = isp_reset_coproc(isp); + if (err < 0) + return err; isp_gpio_write32(isp, ISP_GPIO_0, 0x0); isp_gpio_write32(isp, ISP_GPIO_1, 0x0); diff --git a/drivers/media/platform/apple/isp/isp-v4l2.c b/drivers/media/platform/apple/isp/isp-v4l2.c index 34a0d6d91c1274..0561653ea7becd 100644 --- a/drivers/media/platform/apple/isp/isp-v4l2.c +++ b/drivers/media/platform/apple/isp/isp-v4l2.c @@ -337,13 +337,10 @@ static void isp_vb2_buf_queue(struct vb2_buffer *vb) isp_submit_buffers(isp); } -static int isp_vb2_start_streaming(struct vb2_queue *q, unsigned int count) +static int apple_isp_start_streaming(struct apple_isp *isp) { - struct apple_isp *isp = vb2_get_drv_priv(q); int err; - isp->sequence = 0; - err = apple_isp_start_camera(isp); if (err) { dev_err(isp->dev, "failed to start camera: %d\n", err); @@ -373,16 +370,55 @@ static int isp_vb2_start_streaming(struct vb2_queue *q, unsigned int count) return err; } -static void isp_vb2_stop_streaming(struct vb2_queue *q) +static void apple_isp_stop_streaming(struct apple_isp *isp) { - struct apple_isp *isp = vb2_get_drv_priv(q); - clear_bit(ISP_STATE_STREAMING, &isp->state); apple_isp_stop_capture(isp); apple_isp_stop_camera(isp); +} + +static int isp_vb2_start_streaming(struct vb2_queue *q, unsigned int count) +{ + struct apple_isp *isp = vb2_get_drv_priv(q); + + isp->sequence = 0; + + return apple_isp_start_streaming(isp); +} + +static void isp_vb2_stop_streaming(struct vb2_queue *q) +{ + struct apple_isp *isp = vb2_get_drv_priv(q); + + apple_isp_stop_streaming(isp); isp_vb2_release_buffers(isp, VB2_BUF_STATE_ERROR); } +int apple_isp_video_suspend(struct apple_isp *isp) +{ + /* Swap into STATE_SLEEPING as isp_vb2_buf_queue() submits on + * STATE_STREAMING. + */ + if (test_bit(ISP_STATE_STREAMING, &isp->state)) { + /* Signal buffers to be recycled for clean shutdown */ + isp_vb2_release_buffers(isp, VB2_BUF_STATE_QUEUED); + apple_isp_stop_streaming(isp); + set_bit(ISP_STATE_SLEEPING, &isp->state); + } + + return 0; +} + +int apple_isp_video_resume(struct apple_isp *isp) +{ + if (test_bit(ISP_STATE_SLEEPING, &isp->state)) { + clear_bit(ISP_STATE_SLEEPING, &isp->state); + apple_isp_start_streaming(isp); + } + + return 0; +} + static const struct vb2_ops isp_vb2_ops = { .queue_setup = isp_vb2_queue_setup, .buf_init = isp_vb2_buf_init, diff --git a/drivers/media/platform/apple/isp/isp-v4l2.h b/drivers/media/platform/apple/isp/isp-v4l2.h index e81e4de6ca641f..4d47deeb83b055 100644 --- a/drivers/media/platform/apple/isp/isp-v4l2.h +++ b/drivers/media/platform/apple/isp/isp-v4l2.h @@ -10,4 +10,7 @@ int apple_isp_setup_video(struct apple_isp *isp); void apple_isp_remove_video(struct apple_isp *isp); int ipc_bt_handle(struct apple_isp *isp, struct isp_channel *chan); +int apple_isp_video_suspend(struct apple_isp *isp); +int apple_isp_video_resume(struct apple_isp *isp); + #endif /* __ISP_V4L2_H__ */ From 8e192eb56fd4f922cb5896b66b0349adfcedff2d Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Wed, 9 Apr 2025 11:08:43 +0200 Subject: [PATCH 0319/3902] fixup! media: apple: Add Apple ISP driver --- drivers/media/platform/apple/isp/Kconfig | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/media/platform/apple/isp/Kconfig b/drivers/media/platform/apple/isp/Kconfig index f0e2173640ab73..5695bef44adf5b 100644 --- a/drivers/media/platform/apple/isp/Kconfig +++ b/drivers/media/platform/apple/isp/Kconfig @@ -6,5 +6,6 @@ config VIDEO_APPLE_ISP select VIDEOBUF2_V4L2 select VIDEOBUF2_DMA_SG depends on ARCH_APPLE || COMPILE_TEST + depends on OF_ADDRESS depends on V4L_PLATFORM_DRIVERS depends on VIDEO_DEV From 0996191f4b2f6c21525e045ef308cd9e13bb44f7 Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Tue, 14 Oct 2025 22:27:34 +0200 Subject: [PATCH 0320/3902] fixup! media: apple: isp: Remove ioread/iowrite and stop doing raw address translation Signed-off-by: Janne Grunau --- drivers/media/platform/apple/isp/isp-ipc.c | 2 -- 1 file changed, 2 deletions(-) diff --git a/drivers/media/platform/apple/isp/isp-ipc.c b/drivers/media/platform/apple/isp/isp-ipc.c index 7300eb60892116..a1948717a31968 100644 --- a/drivers/media/platform/apple/isp/isp-ipc.c +++ b/drivers/media/platform/apple/isp/isp-ipc.c @@ -227,14 +227,12 @@ int ipc_sm_handle(struct apple_isp *isp, struct isp_channel *chan) int err; if (req->arg0 == 0x0) { - struct isp_sm_deferred_work *dwork; struct isp_surf *surf; surf = isp_alloc_surface_gc(isp, req->arg1); if (!surf) { isp_err(isp, "failed to alloc requested size 0x%llx\n", req->arg1); - kfree(dwork); return -ENOMEM; } surf->type = req->arg2; From 1482cddf358581746929d8bd18e36b5ac1f22344 Mon Sep 17 00:00:00 2001 From: Tamir Duberstein Date: Tue, 1 Jul 2025 12:27:19 -0400 Subject: [PATCH 0321/3902] rust: xarray: add `insert` and `reserve` Add `Guard::{insert,reserve}` and `Guard::{insert,reserve}_limit`, which are akin to `__xa_{alloc,insert}` in C. Note that unlike `xa_reserve` which only ensures that memory is allocated, the semantics of `Reservation` are stricter and require precise management of the reservation. Indices which have been reserved can still be overwritten with `Guard::store`, which allows for C-like semantics if desired. `__xa_cmpxchg_raw` is exported to facilitate the semantics described above. Signed-off-by: Tamir Duberstein --- include/linux/xarray.h | 2 + lib/xarray.c | 28 ++- rust/helpers/xarray.c | 5 + rust/kernel/xarray.rs | 419 ++++++++++++++++++++++++++++++++++++++++- 4 files changed, 447 insertions(+), 7 deletions(-) diff --git a/include/linux/xarray.h b/include/linux/xarray.h index be850174e802e6..64f2a5e06cebcc 100644 --- a/include/linux/xarray.h +++ b/include/linux/xarray.h @@ -563,6 +563,8 @@ void *__xa_erase(struct xarray *, unsigned long index); void *__xa_store(struct xarray *, unsigned long index, void *entry, gfp_t); void *__xa_cmpxchg(struct xarray *, unsigned long index, void *old, void *entry, gfp_t); +void *__xa_cmpxchg_raw(struct xarray *, unsigned long index, void *old, + void *entry, gfp_t); int __must_check __xa_insert(struct xarray *, unsigned long index, void *entry, gfp_t); int __must_check __xa_alloc(struct xarray *, u32 *id, void *entry, diff --git a/lib/xarray.c b/lib/xarray.c index 9a8b4916540cf1..fe7f18d7194187 100644 --- a/lib/xarray.c +++ b/lib/xarray.c @@ -1738,9 +1738,6 @@ void *xa_store(struct xarray *xa, unsigned long index, void *entry, gfp_t gfp) } EXPORT_SYMBOL(xa_store); -static inline void *__xa_cmpxchg_raw(struct xarray *xa, unsigned long index, - void *old, void *entry, gfp_t gfp); - /** * __xa_cmpxchg() - Conditionally replace an entry in the XArray. * @xa: XArray. @@ -1767,7 +1764,29 @@ void *__xa_cmpxchg(struct xarray *xa, unsigned long index, } EXPORT_SYMBOL(__xa_cmpxchg); -static inline void *__xa_cmpxchg_raw(struct xarray *xa, unsigned long index, +/** + * __xa_cmpxchg_raw() - Conditionally replace an entry in the XArray. + * @xa: XArray. + * @index: Index into array. + * @old: Old value to test against. + * @entry: New value to place in array. + * @gfp: Memory allocation flags. + * + * You must already be holding the xa_lock when calling this function. + * It will drop the lock if needed to allocate memory, and then reacquire + * it afterwards. + * + * If the entry at @index is the same as @old, replace it with @entry. + * If the return value is equal to @old, then the exchange was successful. + * + * This function is the same as __xa_cmpxchg() except that it does not coerce + * XA_ZERO_ENTRY to NULL on egress. + * + * Context: Any context. Expects xa_lock to be held on entry. May + * release and reacquire xa_lock if @gfp flags permit. + * Return: The old value at this index or xa_err() if an error happened. + */ +void *__xa_cmpxchg_raw(struct xarray *xa, unsigned long index, void *old, void *entry, gfp_t gfp) { XA_STATE(xas, xa, index); @@ -1787,6 +1806,7 @@ static inline void *__xa_cmpxchg_raw(struct xarray *xa, unsigned long index, return xas_result(&xas, curr); } +EXPORT_SYMBOL(__xa_cmpxchg_raw); /** * __xa_insert() - Store this entry in the XArray if no entry is present. diff --git a/rust/helpers/xarray.c b/rust/helpers/xarray.c index 60b299f11451d2..b6c078e6a343c2 100644 --- a/rust/helpers/xarray.c +++ b/rust/helpers/xarray.c @@ -2,6 +2,11 @@ #include +void *rust_helper_xa_zero_entry(void) +{ + return XA_ZERO_ENTRY; +} + int rust_helper_xa_err(void *entry) { return xa_err(entry); diff --git a/rust/kernel/xarray.rs b/rust/kernel/xarray.rs index a1bed7f198fbcd..83182e09086ac8 100644 --- a/rust/kernel/xarray.rs +++ b/rust/kernel/xarray.rs @@ -9,7 +9,12 @@ use crate::{ prelude::*, types::{ForeignOwnable, NotThreadSafe, Opaque}, }; -use core::{iter, marker::PhantomData, mem, ptr::NonNull}; +use core::{ + fmt, iter, + marker::PhantomData, + mem, ops, + ptr::{null_mut, NonNull}, +}; /// An array which efficiently maps sparse integer indices to owned objects. /// @@ -126,6 +131,19 @@ impl XArray { .map_while(|ptr| NonNull::new(ptr.cast())) } + fn with_guard(&self, guard: Option<&mut Guard<'_, T>>, f: F) -> U + where + F: FnOnce(&mut Guard<'_, T>) -> U, + { + match guard { + None => f(&mut self.lock()), + Some(guard) => { + assert_eq!(guard.xa.xa.get(), self.xa.get()); + f(guard) + } + } + } + /// Attempts to lock the [`XArray`] for exclusive access. pub fn try_lock(&self) -> Option> { // SAFETY: `self.xa` is always valid by the type invariant. @@ -172,6 +190,7 @@ impl Drop for Guard<'_, T> { /// The error returned by [`store`](Guard::store). /// /// Contains the underlying error and the value that was not stored. +#[derive(Debug)] pub struct StoreError { /// The error that occurred. pub error: Error, @@ -185,6 +204,11 @@ impl From> for Error { } } +fn to_usize(i: u32) -> usize { + i.try_into() + .unwrap_or_else(|_| build_error!("cannot convert u32 to usize")) +} + impl<'a, T: ForeignOwnable> Guard<'a, T> { fn load(&self, index: usize, f: F) -> Option where @@ -219,7 +243,7 @@ impl<'a, T: ForeignOwnable> Guard<'a, T> { // - The caller holds the lock. let ptr = unsafe { bindings::__xa_erase(self.xa.xa.get(), index) }.cast(); // SAFETY: - // - `ptr` is either NULL or came from `T::into_foreign`. + // - `ptr` is either `NULL` or came from `T::into_foreign`. // - `&mut self` guarantees that the lifetimes of [`T::Borrowed`] and [`T::BorrowedMut`] // borrowed from `self` have ended. unsafe { T::try_from_foreign(ptr) } @@ -267,13 +291,272 @@ impl<'a, T: ForeignOwnable> Guard<'a, T> { }) } else { let old = old.cast(); - // SAFETY: `ptr` is either NULL or came from `T::into_foreign`. + // SAFETY: `ptr` is either `NULL` or came from `T::into_foreign`. // // NB: `XA_ZERO_ENTRY` is never returned by functions belonging to the Normal XArray // API; such entries present as `NULL`. Ok(unsafe { T::try_from_foreign(old) }) } } + + /// Stores an element at the given index if no entry is present. + /// + /// May drop the lock if needed to allocate memory, and then reacquire it afterwards. + /// + /// On failure, returns the element which was attempted to be stored. + pub fn insert( + &mut self, + index: usize, + value: T, + gfp: alloc::Flags, + ) -> Result<(), StoreError> { + build_assert!( + T::FOREIGN_ALIGN >= 4, + "pointers stored in XArray must be 4-byte aligned" + ); + let ptr = value.into_foreign(); + // SAFETY: `self.xa` is always valid by the type invariant. + // + // INVARIANT: `ptr` came from `T::into_foreign`. + match unsafe { bindings::__xa_insert(self.xa.xa.get(), index, ptr.cast(), gfp.as_raw()) } { + 0 => Ok(()), + errno => { + // SAFETY: `ptr` came from `T::into_foreign` and `__xa_insert` does not take + // ownership of the value on error. + let value = unsafe { T::from_foreign(ptr) }; + Err(StoreError { + value, + error: Error::from_errno(errno), + }) + } + } + } + + /// Wrapper around `__xa_alloc`. + /// + /// On success, takes ownership of pointers passed in `op`. + /// + /// On failure, ownership returns to the caller. + /// + /// # Safety + /// + /// `ptr` must be `NULL` or have come from a previous call to `T::into_foreign`. + unsafe fn alloc( + &mut self, + limit: impl ops::RangeBounds, + ptr: *mut c_void, + gfp: alloc::Flags, + ) -> Result { + // NB: `xa_limit::{max,min}` are inclusive. + let limit = bindings::xa_limit { + max: match limit.end_bound() { + ops::Bound::Included(&end) => end, + ops::Bound::Excluded(&end) => end - 1, + ops::Bound::Unbounded => u32::MAX, + }, + min: match limit.start_bound() { + ops::Bound::Included(&start) => start, + ops::Bound::Excluded(&start) => start + 1, + ops::Bound::Unbounded => 0, + }, + }; + + let mut index = u32::MAX; + + // SAFETY: + // - `self.xa` is always valid by the type invariant. + // - `self.xa` was initialized with `XA_FLAGS_ALLOC` or `XA_FLAGS_ALLOC1`. + // + // INVARIANT: `ptr` is either `NULL` or came from `T::into_foreign`. + match unsafe { + bindings::__xa_alloc( + self.xa.xa.get(), + &mut index, + ptr.cast(), + limit, + gfp.as_raw(), + ) + } { + 0 => Ok(to_usize(index)), + errno => Err(Error::from_errno(errno)), + } + } + + /// Allocates an entry somewhere in the array. + /// + /// On success, returns the index at which the entry was stored. + /// + /// On failure, returns the entry which was attempted to be stored. + pub fn insert_limit( + &mut self, + limit: impl ops::RangeBounds, + value: T, + gfp: alloc::Flags, + ) -> Result> { + build_assert!( + T::FOREIGN_ALIGN >= 4, + "pointers stored in XArray must be 4-byte aligned" + ); + let ptr = value.into_foreign(); + // SAFETY: `ptr` came from `T::into_foreign`. + unsafe { self.alloc(limit, ptr, gfp) }.map_err(|error| { + // SAFETY: `ptr` came from `T::into_foreign` and `self.alloc` does not take ownership of + // the value on error. + let value = unsafe { T::from_foreign(ptr) }; + StoreError { value, error } + }) + } + + /// Reserves an entry in the array. + pub fn reserve(&mut self, index: usize, gfp: alloc::Flags) -> Result> { + // NB: `__xa_insert` internally coerces `NULL` to `XA_ZERO_ENTRY` on ingress. + let ptr = null_mut(); + // SAFETY: `self.xa` is always valid by the type invariant. + // + // INVARIANT: `ptr` is `NULL`. + match unsafe { bindings::__xa_insert(self.xa.xa.get(), index, ptr, gfp.as_raw()) } { + 0 => Ok(Reservation { xa: self.xa, index }), + errno => Err(Error::from_errno(errno)), + } + } + + /// Reserves an entry somewhere in the array. + pub fn reserve_limit( + &mut self, + limit: impl ops::RangeBounds, + gfp: alloc::Flags, + ) -> Result> { + // NB: `__xa_alloc` internally coerces `NULL` to `XA_ZERO_ENTRY` on ingress. + let ptr = null_mut(); + // SAFETY: `ptr` is `NULL`. + unsafe { self.alloc(limit, ptr, gfp) }.map(|index| Reservation { xa: self.xa, index }) + } +} + +/// A reserved slot in an array. +/// +/// The slot is released when the reservation goes out of scope. +/// +/// Note that the array lock *must not* be held when the reservation is filled or dropped as this +/// will lead to deadlock. [`Reservation::fill_locked`] and [`Reservation::release_locked`] can be +/// used in context where the array lock is held. +#[must_use = "the reservation is released immediately when the reservation is unused"] +pub struct Reservation<'a, T: ForeignOwnable> { + xa: &'a XArray, + index: usize, +} + +impl fmt::Debug for Reservation<'_, T> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("Reservation") + .field("index", &self.index()) + .finish() + } +} + +impl Reservation<'_, T> { + /// Returns the index of the reservation. + pub fn index(&self) -> usize { + self.index + } + + /// Replaces the reserved entry with the given entry. + /// + /// # Safety + /// + /// `ptr` must be `NULL` or have come from a previous call to `T::into_foreign`. + unsafe fn replace(guard: &mut Guard<'_, T>, index: usize, ptr: *mut c_void) -> Result { + // SAFETY: `xa_zero_entry` wraps `XA_ZERO_ENTRY` which is always safe to use. + let old = unsafe { bindings::xa_zero_entry() }; + + // NB: `__xa_cmpxchg_raw` is used over `__xa_cmpxchg` because the latter coerces + // `XA_ZERO_ENTRY` to `NULL` on egress, which would prevent us from determining whether a + // replacement was made. + // + // SAFETY: `self.xa` is always valid by the type invariant. + // + // INVARIANT: `ptr` is either `NULL` or came from `T::into_foreign` and `old` is + // `XA_ZERO_ENTRY`. + let ret = + unsafe { bindings::__xa_cmpxchg_raw(guard.xa.xa.get(), index, old, ptr.cast(), 0) }; + + // SAFETY: `__xa_cmpxchg_raw` returns the old entry at this index on success or `xa_err` if + // an error happened. + match unsafe { bindings::xa_err(ret) } { + 0 => { + if ret == old { + Ok(()) + } else { + Err(EBUSY) + } + } + errno => Err(Error::from_errno(errno)), + } + } + + fn fill_inner(&self, guard: Option<&mut Guard<'_, T>>, value: T) -> Result<(), StoreError> { + let Self { xa, index } = self; + let index = *index; + + let ptr = value.into_foreign(); + xa.with_guard(guard, |guard| { + // SAFETY: `ptr` came from `T::into_foreign`. + unsafe { Self::replace(guard, index, ptr) } + }) + .map_err(|error| { + // SAFETY: `ptr` came from `T::into_foreign` and `Self::replace` does not take ownership + // of the value on error. + let value = unsafe { T::from_foreign(ptr) }; + StoreError { value, error } + }) + } + + /// Fills the reservation. + pub fn fill(self, value: T) -> Result<(), StoreError> { + let result = self.fill_inner(None, value); + mem::forget(self); + result + } + + /// Fills the reservation without acquiring the array lock. + /// + /// # Panics + /// + /// Panics if the passed guard locks a different array. + pub fn fill_locked(self, guard: &mut Guard<'_, T>, value: T) -> Result<(), StoreError> { + let result = self.fill_inner(Some(guard), value); + mem::forget(self); + result + } + + fn release_inner(&self, guard: Option<&mut Guard<'_, T>>) -> Result { + let Self { xa, index } = self; + let index = *index; + + xa.with_guard(guard, |guard| { + let ptr = null_mut(); + // SAFETY: `ptr` is `NULL`. + unsafe { Self::replace(guard, index, ptr) } + }) + } + + /// Releases the reservation without acquiring the array lock. + /// + /// # Panics + /// + /// Panics if the passed guard locks a different array. + pub fn release_locked(self, guard: &mut Guard<'_, T>) -> Result { + let result = self.release_inner(Some(guard)); + mem::forget(self); + result + } +} + +impl Drop for Reservation<'_, T> { + fn drop(&mut self) { + // NB: Errors here are possible since `Guard::store` does not honor reservations. + let _: Result = self.release_inner(None); + } } // SAFETY: `XArray` has no shared mutable state so it is `Send` iff `T` is `Send`. @@ -282,3 +565,133 @@ unsafe impl Send for XArray {} // SAFETY: `XArray` serialises the interior mutability it provides so it is `Sync` iff `T` is // `Send`. unsafe impl Sync for XArray {} + +#[macros::kunit_tests(rust_xarray_kunit)] +mod tests { + use super::*; + use pin_init::stack_pin_init; + + fn new_kbox(value: T) -> Result> { + KBox::new(value, GFP_KERNEL).map_err(Into::into) + } + + #[test] + fn test_alloc_kind_alloc() -> Result { + test_alloc_kind(AllocKind::Alloc, 0) + } + + #[test] + fn test_alloc_kind_alloc1() -> Result { + test_alloc_kind(AllocKind::Alloc1, 1) + } + + fn test_alloc_kind(kind: AllocKind, expected_index: usize) -> Result { + stack_pin_init!(let xa = XArray::new(kind)); + let mut guard = xa.lock(); + + let reservation = guard.reserve_limit(.., GFP_KERNEL)?; + assert_eq!(reservation.index(), expected_index); + reservation.release_locked(&mut guard)?; + + let insertion = guard.insert_limit(.., new_kbox(0x1337)?, GFP_KERNEL); + assert!(insertion.is_ok()); + let insertion_index = insertion.unwrap(); + assert_eq!(insertion_index, expected_index); + + Ok(()) + } + + const IDX: usize = 0x1337; + + fn insert(guard: &mut Guard<'_, T>, value: T) -> Result<(), StoreError> { + guard.insert(IDX, value, GFP_KERNEL) + } + + fn reserve<'a, T: ForeignOwnable>(guard: &mut Guard<'a, T>) -> Result> { + guard.reserve(IDX, GFP_KERNEL) + } + + #[track_caller] + fn check_not_vacant<'a>(guard: &mut Guard<'a, KBox>) -> Result { + // Insertion fails. + { + let beef = new_kbox(0xbeef)?; + let ret = insert(guard, beef); + assert!(ret.is_err()); + let StoreError { error, value } = ret.unwrap_err(); + assert_eq!(error, EBUSY); + assert_eq!(*value, 0xbeef); + } + + // Reservation fails. + { + let ret = reserve(guard); + assert!(ret.is_err()); + assert_eq!(ret.unwrap_err(), EBUSY); + } + + Ok(()) + } + + #[test] + fn test_insert_and_reserve_interaction() -> Result { + stack_pin_init!(let xa = XArray::new(Default::default())); + let mut guard = xa.lock(); + + // Vacant. + assert_eq!(guard.get(IDX), None); + + // Reservation succeeds. + let reservation = { + let ret = reserve(&mut guard); + assert!(ret.is_ok()); + ret.unwrap() + }; + + // Reserved presents as vacant. + assert_eq!(guard.get(IDX), None); + + check_not_vacant(&mut guard)?; + + // Release reservation. + { + let ret = reservation.release_locked(&mut guard); + assert!(ret.is_ok()); + let () = ret.unwrap(); + } + + // Vacant again. + assert_eq!(guard.get(IDX), None); + + // Insert succeeds. + { + let dead = new_kbox(0xdead)?; + let ret = insert(&mut guard, dead); + assert!(ret.is_ok()); + let () = ret.unwrap(); + } + + check_not_vacant(&mut guard)?; + + // Remove. + assert_eq!(guard.remove(IDX).as_deref(), Some(&0xdead)); + + // Reserve and fill. + { + let beef = new_kbox(0xbeef)?; + let ret = reserve(&mut guard); + assert!(ret.is_ok()); + let reservation = ret.unwrap(); + let ret = reservation.fill_locked(&mut guard, beef); + assert!(ret.is_ok()); + let () = ret.unwrap(); + }; + + check_not_vacant(&mut guard)?; + + // Remove. + assert_eq!(guard.remove(IDX).as_deref(), Some(&0xbeef)); + + Ok(()) + } +} From 202d9313d1e0b74a3cbee33225f5074bae95ed63 Mon Sep 17 00:00:00 2001 From: Asahi Lina Date: Mon, 7 Jul 2025 20:20:54 +0200 Subject: [PATCH 0322/3902] rust: kernel: xarray: Implement XArray::find() Signed-off-by: Asahi Lina Co-developed-by: Janne Grunau Signed-off-by: Janne Grunau --- rust/kernel/xarray.rs | 63 ++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 62 insertions(+), 1 deletion(-) diff --git a/rust/kernel/xarray.rs b/rust/kernel/xarray.rs index 83182e09086ac8..4a4bce9d9de956 100644 --- a/rust/kernel/xarray.rs +++ b/rust/kernel/xarray.rs @@ -7,7 +7,7 @@ use crate::{ alloc, prelude::*, - types::{ForeignOwnable, NotThreadSafe, Opaque}, + types::{ForeignOwnable, NotThreadSafe, Opaque, ScopeGuard}, }; use core::{ fmt, iter, @@ -131,6 +131,36 @@ impl XArray { .map_while(|ptr| NonNull::new(ptr.cast())) } + /// Looks up and returns a reference to the lowest entry in the array between index and max, + /// returning a tuple of its index and a `Guard` if one exists. + /// + /// This guard blocks all other actions on the `XArray`. Callers are expected to drop the + /// `Guard` eagerly to avoid blocking other users, such as by taking a clone of the value. + pub fn find(&self, index: usize, max: usize) -> Option<(usize, ValueGuard<'_, T>)> { + let mut index: usize = index; + + // SAFETY: `self.xa` is always valid by the type invariant. + unsafe { bindings::xa_lock(self.xa.get()) }; + + // SAFETY: `self.xa` is always valid by the type invariant. + let guard = ScopeGuard::new(|| unsafe { bindings::xa_unlock(self.xa.get()) }); + + // SAFETY: `self.xa` is always valid by the type invariant. + let p = unsafe { bindings::xa_find(self.xa.get(), &mut index, max, bindings::XA_PRESENT) }; + + NonNull::new(p as *mut T).map(|ptr| { + guard.dismiss(); + ( + index, + ValueGuard { + xa: self, + ptr, + _not_send: NotThreadSafe, + }, + ) + }) + } + fn with_guard(&self, guard: Option<&mut Guard<'_, T>>, f: F) -> U where F: FnOnce(&mut Guard<'_, T>) -> U, @@ -187,6 +217,37 @@ impl Drop for Guard<'_, T> { } } +/// A lock guard. +/// +/// The lock is unlocked when the guard goes out of scope. +#[must_use = "the lock unlocks immediately when the guard is unused"] +pub struct ValueGuard<'a, T: ForeignOwnable> { + xa: &'a XArray, + ptr: NonNull, + _not_send: NotThreadSafe, +} + +impl<'a, T: ForeignOwnable> ValueGuard<'a, T> { + /// Borrow the underlying value wrapped by the `Guard`. + /// + /// Returns a `T::Borrowed` type for the owned `ForeignOwnable` type. + pub fn borrow(&self) -> T::Borrowed<'_> { + // SAFETY: The value is owned by the `XArray`, the lifetime it is borrowed for must not + // outlive the `XArray` itself, nor the Guard that holds the lock ensuring the value + // remains in the `XArray`. + unsafe { T::borrow(self.ptr.as_ptr() as _) } + } +} + +impl Drop for ValueGuard<'_, T> { + fn drop(&mut self) { + // SAFETY: + // - `self.xa.xa` is always valid by the type invariant. + // - The caller holds the lock, so it is safe to unlock it. + unsafe { bindings::xa_unlock(self.xa.xa.get()) }; + } +} + /// The error returned by [`store`](Guard::store). /// /// Contains the underlying error and the value that was not stored. From fb9d19cdb1c434036485de3f1d3aff9efacb0c45 Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Thu, 10 Jul 2025 17:29:40 +0200 Subject: [PATCH 0323/3902] rust: xarray: Add xarray::remove() convenience function Ensures the xarray is unlocked before the removed element is dropped. --- rust/kernel/xarray.rs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/rust/kernel/xarray.rs b/rust/kernel/xarray.rs index 4a4bce9d9de956..e1f7e2d9b629f0 100644 --- a/rust/kernel/xarray.rs +++ b/rust/kernel/xarray.rs @@ -197,6 +197,12 @@ impl XArray { _not_send: NotThreadSafe, } } + + /// Removes and returns the element at the given index. + pub fn remove(&self, index: usize) -> Option { + let mut guard = self.lock(); + guard.remove(index) + } } /// A lock guard. From a68b06bdc17205d79196044192e436c55b239513 Mon Sep 17 00:00:00 2001 From: Bagas Sanjaya Date: Wed, 22 Oct 2025 10:43:35 +0700 Subject: [PATCH 0324/3902] Documentation: process: Also mention Sasha Levin as stable tree maintainer commit ba2457109d5b47a90fe565b39524f7225fc23e60 upstream. Sasha has also maintaining stable branch in conjunction with Greg since cb5d21946d2a2f ("MAINTAINERS: Add Sasha as a stable branch maintainer"). Mention him in 2.Process.rst. Cc: stable@vger.kernel.org Signed-off-by: Bagas Sanjaya Reviewed-by: Randy Dunlap Acked-by: Greg Kroah-Hartman Signed-off-by: Jonathan Corbet Message-ID: <20251022034336.22839-1-bagasdotme@gmail.com> Signed-off-by: Greg Kroah-Hartman --- Documentation/process/2.Process.rst | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/Documentation/process/2.Process.rst b/Documentation/process/2.Process.rst index ef3b116492df08..f4fc0da8999d90 100644 --- a/Documentation/process/2.Process.rst +++ b/Documentation/process/2.Process.rst @@ -104,8 +104,10 @@ kernels go out with a handful of known regressions though, hopefully, none of them are serious. Once a stable release is made, its ongoing maintenance is passed off to the -"stable team," currently Greg Kroah-Hartman. The stable team will release -occasional updates to the stable release using the 5.x.y numbering scheme. +"stable team," currently consists of Greg Kroah-Hartman and Sasha Levin. The +stable team will release occasional updates to the stable release using the +5.x.y numbering scheme. + To be considered for an update release, a patch must (1) fix a significant bug, and (2) already be merged into the mainline for the next development kernel. Kernels will typically receive stable updates for a little more From aa1703f3f706ea0867fb1991dcac709c9ec94cfb Mon Sep 17 00:00:00 2001 From: Ye Bin Date: Sat, 25 Oct 2025 15:26:57 +0800 Subject: [PATCH 0325/3902] jbd2: avoid bug_on in jbd2_journal_get_create_access() when file system corrupted commit 986835bf4d11032bba4ab8414d18fce038c61bb4 upstream. There's issue when file system corrupted: ------------[ cut here ]------------ kernel BUG at fs/jbd2/transaction.c:1289! Oops: invalid opcode: 0000 [#1] SMP KASAN PTI CPU: 5 UID: 0 PID: 2031 Comm: mkdir Not tainted 6.18.0-rc1-next RIP: 0010:jbd2_journal_get_create_access+0x3b6/0x4d0 RSP: 0018:ffff888117aafa30 EFLAGS: 00010202 RAX: 0000000000000000 RBX: ffff88811a86b000 RCX: ffffffff89a63534 RDX: 1ffff110200ec602 RSI: 0000000000000004 RDI: ffff888100763010 RBP: ffff888100763000 R08: 0000000000000001 R09: ffff888100763028 R10: 0000000000000003 R11: 0000000000000000 R12: 0000000000000000 R13: ffff88812c432000 R14: ffff88812c608000 R15: ffff888120bfc000 CS: 0010 DS: 0000 ES: 0000 CR0: 0000000080050033 CR2: 00007f91d6970c99 CR3: 00000001159c4000 CR4: 00000000000006f0 Call Trace: __ext4_journal_get_create_access+0x42/0x170 ext4_getblk+0x319/0x6f0 ext4_bread+0x11/0x100 ext4_append+0x1e6/0x4a0 ext4_init_new_dir+0x145/0x1d0 ext4_mkdir+0x326/0x920 vfs_mkdir+0x45c/0x740 do_mkdirat+0x234/0x2f0 __x64_sys_mkdir+0xd6/0x120 do_syscall_64+0x5f/0xfa0 entry_SYSCALL_64_after_hwframe+0x76/0x7e The above issue occurs with us in errors=continue mode when accompanied by storage failures. There have been many inconsistencies in the file system data. In the case of file system data inconsistency, for example, if the block bitmap of a referenced block is not set, it can lead to the situation where a block being committed is allocated and used again. As a result, the following condition will not be satisfied then trigger BUG_ON. Of course, it is entirely possible to construct a problematic image that can trigger this BUG_ON through specific operations. In fact, I have constructed such an image and easily reproduced this issue. Therefore, J_ASSERT() holds true only under ideal conditions, but it may not necessarily be satisfied in exceptional scenarios. Using J_ASSERT() directly in abnormal situations would cause the system to crash, which is clearly not what we want. So here we directly trigger a JBD abort instead of immediately invoking BUG_ON. Fixes: 470decc613ab ("[PATCH] jbd2: initial copy of files from jbd") Signed-off-by: Ye Bin Reviewed-by: Jan Kara Message-ID: <20251025072657.307851-1-yebin@huaweicloud.com> Signed-off-by: Theodore Ts'o Cc: stable@kernel.org Signed-off-by: Greg Kroah-Hartman --- fs/jbd2/transaction.c | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/fs/jbd2/transaction.c b/fs/jbd2/transaction.c index 3e510564de6ee8..9ce626ac947e30 100644 --- a/fs/jbd2/transaction.c +++ b/fs/jbd2/transaction.c @@ -1284,14 +1284,23 @@ int jbd2_journal_get_create_access(handle_t *handle, struct buffer_head *bh) * committing transaction's lists, but it HAS to be in Forget state in * that case: the transaction must have deleted the buffer for it to be * reused here. + * In the case of file system data inconsistency, for example, if the + * block bitmap of a referenced block is not set, it can lead to the + * situation where a block being committed is allocated and used again. + * As a result, the following condition will not be satisfied, so here + * we directly trigger a JBD abort instead of immediately invoking + * bugon. */ spin_lock(&jh->b_state_lock); - J_ASSERT_JH(jh, (jh->b_transaction == transaction || - jh->b_transaction == NULL || - (jh->b_transaction == journal->j_committing_transaction && - jh->b_jlist == BJ_Forget))); + if (!(jh->b_transaction == transaction || jh->b_transaction == NULL || + (jh->b_transaction == journal->j_committing_transaction && + jh->b_jlist == BJ_Forget)) || jh->b_next_transaction != NULL) { + err = -EROFS; + spin_unlock(&jh->b_state_lock); + jbd2_journal_abort(journal, err); + goto out; + } - J_ASSERT_JH(jh, jh->b_next_transaction == NULL); J_ASSERT_JH(jh, buffer_locked(jh2bh(jh))); if (jh->b_transaction == NULL) { From 58df743faf21ceb1880f930aa5dd428e2a5e415d Mon Sep 17 00:00:00 2001 From: Deepanshu Kartikey Date: Mon, 20 Oct 2025 11:39:36 +0530 Subject: [PATCH 0326/3902] ext4: refresh inline data size before write operations commit 892e1cf17555735e9d021ab036c36bc7b58b0e3b upstream. The cached ei->i_inline_size can become stale between the initial size check and when ext4_update_inline_data()/ext4_create_inline_data() use it. Although ext4_get_max_inline_size() reads the correct value at the time of the check, concurrent xattr operations can modify i_inline_size before ext4_write_lock_xattr() is acquired. This causes ext4_update_inline_data() and ext4_create_inline_data() to work with stale capacity values, leading to a BUG_ON() crash in ext4_write_inline_data(): kernel BUG at fs/ext4/inline.c:1331! BUG_ON(pos + len > EXT4_I(inode)->i_inline_size); The race window: 1. ext4_get_max_inline_size() reads i_inline_size = 60 (correct) 2. Size check passes for 50-byte write 3. [Another thread adds xattr, i_inline_size changes to 40] 4. ext4_write_lock_xattr() acquires lock 5. ext4_update_inline_data() uses stale i_inline_size = 60 6. Attempts to write 50 bytes but only 40 bytes actually available 7. BUG_ON() triggers Fix this by recalculating i_inline_size via ext4_find_inline_data_nolock() immediately after acquiring xattr_sem. This ensures ext4_update_inline_data() and ext4_create_inline_data() work with current values that are protected from concurrent modifications. This is similar to commit a54c4613dac1 ("ext4: fix race writing to an inline_data file while its xattrs are changing") which fixed i_inline_off staleness. This patch addresses the related i_inline_size staleness issue. Reported-by: syzbot+f3185be57d7e8dda32b8@syzkaller.appspotmail.com Link: https://syzkaller.appspot.com/bug?extid=f3185be57d7e8dda32b8 Cc: stable@kernel.org Signed-off-by: Deepanshu Kartikey Message-ID: <20251020060936.474314-1-kartikey406@gmail.com> Signed-off-by: Theodore Ts'o Signed-off-by: Greg Kroah-Hartman --- fs/ext4/inline.c | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/fs/ext4/inline.c b/fs/ext4/inline.c index 1b094a4f386632..b48c7dbe76a2b1 100644 --- a/fs/ext4/inline.c +++ b/fs/ext4/inline.c @@ -418,7 +418,12 @@ static int ext4_prepare_inline_data(handle_t *handle, struct inode *inode, return -ENOSPC; ext4_write_lock_xattr(inode, &no_expand); - + /* + * ei->i_inline_size may have changed since the initial check + * if other xattrs were added. Recalculate to ensure + * ext4_update_inline_data() validates against current capacity. + */ + (void) ext4_find_inline_data_nolock(inode); if (ei->i_inline_off) ret = ext4_update_inline_data(handle, inode, len); else From 8229c6ca50cea701e25a7ee25f48441b582ec5fa Mon Sep 17 00:00:00 2001 From: Qianchang Zhao Date: Wed, 26 Nov 2025 12:24:18 +0900 Subject: [PATCH 0327/3902] ksmbd: ipc: fix use-after-free in ipc_msg_send_request commit 1fab1fa091f5aa97265648b53ea031deedd26235 upstream. ipc_msg_send_request() waits for a generic netlink reply using an ipc_msg_table_entry on the stack. The generic netlink handler (handle_generic_event()/handle_response()) fills entry->response under ipc_msg_table_lock, but ipc_msg_send_request() used to validate and free entry->response without holding the same lock. Under high concurrency this allows a race where handle_response() is copying data into entry->response while ipc_msg_send_request() has just freed it, leading to a slab-use-after-free reported by KASAN in handle_generic_event(): BUG: KASAN: slab-use-after-free in handle_generic_event+0x3c4/0x5f0 [ksmbd] Write of size 12 at addr ffff888198ee6e20 by task pool/109349 ... Freed by task: kvfree ipc_msg_send_request [ksmbd] ksmbd_rpc_open -> ksmbd_session_rpc_open [ksmbd] Fix by: - Taking ipc_msg_table_lock in ipc_msg_send_request() while validating entry->response, freeing it when invalid, and removing the entry from ipc_msg_table. - Returning the final entry->response pointer to the caller only after the hash entry is removed under the lock. - Returning NULL in the error path, preserving the original API semantics. This makes all accesses to entry->response consistent with handle_response(), which already updates and fills the response buffer under ipc_msg_table_lock, and closes the race that allowed the UAF. Cc: stable@vger.kernel.org Reported-by: Qianchang Zhao Reported-by: Zhitong Liu Signed-off-by: Qianchang Zhao Acked-by: Namjae Jeon Signed-off-by: Steve French Signed-off-by: Greg Kroah-Hartman --- fs/smb/server/transport_ipc.c | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/fs/smb/server/transport_ipc.c b/fs/smb/server/transport_ipc.c index 2c08cccfa6809f..2dbabe2d800554 100644 --- a/fs/smb/server/transport_ipc.c +++ b/fs/smb/server/transport_ipc.c @@ -553,12 +553,16 @@ static void *ipc_msg_send_request(struct ksmbd_ipc_msg *msg, unsigned int handle up_write(&ipc_msg_table_lock); ret = ipc_msg_send(msg); - if (ret) + if (ret) { + down_write(&ipc_msg_table_lock); goto out; + } ret = wait_event_interruptible_timeout(entry.wait, entry.response != NULL, IPC_WAIT_TIMEOUT); + + down_write(&ipc_msg_table_lock); if (entry.response) { ret = ipc_validate_msg(&entry); if (ret) { @@ -567,7 +571,6 @@ static void *ipc_msg_send_request(struct ksmbd_ipc_msg *msg, unsigned int handle } } out: - down_write(&ipc_msg_table_lock); hash_del(&entry.ipc_table_hlist); up_write(&ipc_msg_table_lock); return entry.response; From 396a9270a7b90886be501611b13aa636f2e8c703 Mon Sep 17 00:00:00 2001 From: Alexander Sverdlin Date: Fri, 19 Sep 2025 11:12:38 +0200 Subject: [PATCH 0328/3902] locking/spinlock/debug: Fix data-race in do_raw_write_lock commit c14ecb555c3ee80eeb030a4e46d00e679537f03a upstream. KCSAN reports: BUG: KCSAN: data-race in do_raw_write_lock / do_raw_write_lock write (marked) to 0xffff800009cf504c of 4 bytes by task 1102 on cpu 1: do_raw_write_lock+0x120/0x204 _raw_write_lock_irq do_exit call_usermodehelper_exec_async ret_from_fork read to 0xffff800009cf504c of 4 bytes by task 1103 on cpu 0: do_raw_write_lock+0x88/0x204 _raw_write_lock_irq do_exit call_usermodehelper_exec_async ret_from_fork value changed: 0xffffffff -> 0x00000001 Reported by Kernel Concurrency Sanitizer on: CPU: 0 PID: 1103 Comm: kworker/u4:1 6.1.111 Commit 1a365e822372 ("locking/spinlock/debug: Fix various data races") has adressed most of these races, but seems to be not consistent/not complete. >From do_raw_write_lock() only debug_write_lock_after() part has been converted to WRITE_ONCE(), but not debug_write_lock_before() part. Do it now. Fixes: 1a365e822372 ("locking/spinlock/debug: Fix various data races") Reported-by: Adrian Freihofer Signed-off-by: Alexander Sverdlin Signed-off-by: Boqun Feng Signed-off-by: Peter Zijlstra (Intel) Reviewed-by: Paul E. McKenney Acked-by: Waiman Long Cc: stable@vger.kernel.org Signed-off-by: Greg Kroah-Hartman --- kernel/locking/spinlock_debug.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/kernel/locking/spinlock_debug.c b/kernel/locking/spinlock_debug.c index 87b03d2e41dbbe..2338b3adfb55ff 100644 --- a/kernel/locking/spinlock_debug.c +++ b/kernel/locking/spinlock_debug.c @@ -184,8 +184,8 @@ void do_raw_read_unlock(rwlock_t *lock) static inline void debug_write_lock_before(rwlock_t *lock) { RWLOCK_BUG_ON(lock->magic != RWLOCK_MAGIC, lock, "bad magic"); - RWLOCK_BUG_ON(lock->owner == current, lock, "recursion"); - RWLOCK_BUG_ON(lock->owner_cpu == raw_smp_processor_id(), + RWLOCK_BUG_ON(READ_ONCE(lock->owner) == current, lock, "recursion"); + RWLOCK_BUG_ON(READ_ONCE(lock->owner_cpu) == raw_smp_processor_id(), lock, "cpu recursion"); } From e983feaa79de1e46c9087fb9f02fedb0e5397ce6 Mon Sep 17 00:00:00 2001 From: Giovanni Cabiddu Date: Thu, 20 Nov 2025 16:26:09 +0000 Subject: [PATCH 0329/3902] crypto: zstd - fix double-free in per-CPU stream cleanup commit 48bc9da3c97c15f1ea24934bcb3b736acd30163d upstream. The crypto/zstd module has a double-free bug that occurs when multiple tfms are allocated and freed. The issue happens because zstd_streams (per-CPU contexts) are freed in zstd_exit() during every tfm destruction, rather than being managed at the module level. When multiple tfms exist, each tfm exit attempts to free the same shared per-CPU streams, resulting in a double-free. This leads to a stack trace similar to: BUG: Bad page state in process kworker/u16:1 pfn:106fd93 page: refcount:0 mapcount:0 mapping:0000000000000000 index:0x0 pfn:0x106fd93 flags: 0x17ffffc0000000(node=0|zone=2|lastcpupid=0x1fffff) page_type: 0xffffffff() raw: 0017ffffc0000000 dead000000000100 dead000000000122 0000000000000000 raw: 0000000000000000 0000000000000000 00000000ffffffff 0000000000000000 page dumped because: nonzero entire_mapcount Modules linked in: ... CPU: 3 UID: 0 PID: 2506 Comm: kworker/u16:1 Kdump: loaded Tainted: G B Hardware name: ... Workqueue: btrfs-delalloc btrfs_work_helper Call Trace: dump_stack_lvl+0x5d/0x80 bad_page+0x71/0xd0 free_unref_page_prepare+0x24e/0x490 free_unref_page+0x60/0x170 crypto_acomp_free_streams+0x5d/0xc0 crypto_acomp_exit_tfm+0x23/0x50 crypto_destroy_tfm+0x60/0xc0 ... Change the lifecycle management of zstd_streams to free the streams only once during module cleanup. Fixes: f5ad93ffb541 ("crypto: zstd - convert to acomp") Cc: stable@vger.kernel.org Signed-off-by: Giovanni Cabiddu Reviewed-by: Suman Kumar Chakraborty Signed-off-by: Herbert Xu Signed-off-by: Greg Kroah-Hartman --- crypto/zstd.c | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/crypto/zstd.c b/crypto/zstd.c index ac318d333b6847..32a339b74f34cf 100644 --- a/crypto/zstd.c +++ b/crypto/zstd.c @@ -75,11 +75,6 @@ static int zstd_init(struct crypto_acomp *acomp_tfm) return ret; } -static void zstd_exit(struct crypto_acomp *acomp_tfm) -{ - crypto_acomp_free_streams(&zstd_streams); -} - static int zstd_compress_one(struct acomp_req *req, struct zstd_ctx *ctx, const void *src, void *dst, unsigned int *dlen) { @@ -297,7 +292,6 @@ static struct acomp_alg zstd_acomp = { .cra_module = THIS_MODULE, }, .init = zstd_init, - .exit = zstd_exit, .compress = zstd_compress, .decompress = zstd_decompress, }; @@ -310,6 +304,7 @@ static int __init zstd_mod_init(void) static void __exit zstd_mod_fini(void) { crypto_unregister_acomp(&zstd_acomp); + crypto_acomp_free_streams(&zstd_streams); } module_init(zstd_mod_init); From 5cad18e527ba8a9ca5463cc170073eeb5a4826f4 Mon Sep 17 00:00:00 2001 From: Alexey Nepomnyashih Date: Tue, 4 Nov 2025 09:33:25 +0000 Subject: [PATCH 0330/3902] ext4: add i_data_sem protection in ext4_destroy_inline_data_nolock() commit 0cd8feea8777f8d9b9a862b89c688b049a5c8475 upstream. Fix a race between inline data destruction and block mapping. The function ext4_destroy_inline_data_nolock() changes the inode data layout by clearing EXT4_INODE_INLINE_DATA and setting EXT4_INODE_EXTENTS. At the same time, another thread may execute ext4_map_blocks(), which tests EXT4_INODE_EXTENTS to decide whether to call ext4_ext_map_blocks() or ext4_ind_map_blocks(). Without i_data_sem protection, ext4_ind_map_blocks() may receive inode with EXT4_INODE_EXTENTS flag and triggering assert. kernel BUG at fs/ext4/indirect.c:546! EXT4-fs (loop2): unmounting filesystem. invalid opcode: 0000 [#1] PREEMPT SMP KASAN NOPTI Hardware name: QEMU Standard PC (i440FX + PIIX, 1996), BIOS 1.12.0-1 04/01/2014 RIP: 0010:ext4_ind_map_blocks.cold+0x2b/0x5a fs/ext4/indirect.c:546 Call Trace: ext4_map_blocks+0xb9b/0x16f0 fs/ext4/inode.c:681 _ext4_get_block+0x242/0x590 fs/ext4/inode.c:822 ext4_block_write_begin+0x48b/0x12c0 fs/ext4/inode.c:1124 ext4_write_begin+0x598/0xef0 fs/ext4/inode.c:1255 ext4_da_write_begin+0x21e/0x9c0 fs/ext4/inode.c:3000 generic_perform_write+0x259/0x5d0 mm/filemap.c:3846 ext4_buffered_write_iter+0x15b/0x470 fs/ext4/file.c:285 ext4_file_write_iter+0x8e0/0x17f0 fs/ext4/file.c:679 call_write_iter include/linux/fs.h:2271 [inline] do_iter_readv_writev+0x212/0x3c0 fs/read_write.c:735 do_iter_write+0x186/0x710 fs/read_write.c:861 vfs_iter_write+0x70/0xa0 fs/read_write.c:902 iter_file_splice_write+0x73b/0xc90 fs/splice.c:685 do_splice_from fs/splice.c:763 [inline] direct_splice_actor+0x10f/0x170 fs/splice.c:950 splice_direct_to_actor+0x33a/0xa10 fs/splice.c:896 do_splice_direct+0x1a9/0x280 fs/splice.c:1002 do_sendfile+0xb13/0x12c0 fs/read_write.c:1255 __do_sys_sendfile64 fs/read_write.c:1323 [inline] __se_sys_sendfile64 fs/read_write.c:1309 [inline] __x64_sys_sendfile64+0x1cf/0x210 fs/read_write.c:1309 do_syscall_x64 arch/x86/entry/common.c:51 [inline] do_syscall_64+0x35/0x80 arch/x86/entry/common.c:81 entry_SYSCALL_64_after_hwframe+0x6e/0xd8 Fixes: c755e251357a ("ext4: fix deadlock between inline_data and ext4_expand_extra_isize_ea()") Cc: stable@vger.kernel.org # v4.11+ Signed-off-by: Alexey Nepomnyashih Message-ID: <20251104093326.697381-1-sdl@nppct.ru> Signed-off-by: Theodore Ts'o Signed-off-by: Greg Kroah-Hartman --- fs/ext4/inline.c | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/fs/ext4/inline.c b/fs/ext4/inline.c index b48c7dbe76a2b1..1f6bc05593df16 100644 --- a/fs/ext4/inline.c +++ b/fs/ext4/inline.c @@ -451,9 +451,13 @@ static int ext4_destroy_inline_data_nolock(handle_t *handle, if (!ei->i_inline_off) return 0; + down_write(&ei->i_data_sem); + error = ext4_get_inode_loc(inode, &is.iloc); - if (error) + if (error) { + up_write(&ei->i_data_sem); return error; + } error = ext4_xattr_ibody_find(inode, &i, &is); if (error) @@ -492,6 +496,7 @@ static int ext4_destroy_inline_data_nolock(handle_t *handle, brelse(is.iloc.bh); if (error == -ENODATA) error = 0; + up_write(&ei->i_data_sem); return error; } From 3428831264096d32f830a7fcfc7885dd263e511a Mon Sep 17 00:00:00 2001 From: Alice Ryhl Date: Tue, 11 Nov 2025 14:23:32 +0000 Subject: [PATCH 0331/3902] rust_binder: fix race condition on death_list commit 3e0ae02ba831da2b707905f4e602e43f8507b8cc upstream. Rust Binder contains the following unsafe operation: // SAFETY: A `NodeDeath` is never inserted into the death list // of any node other than its owner, so it is either in this // death list or in no death list. unsafe { node_inner.death_list.remove(self) }; This operation is unsafe because when touching the prev/next pointers of a list element, we have to ensure that no other thread is also touching them in parallel. If the node is present in the list that `remove` is called on, then that is fine because we have exclusive access to that list. If the node is not in any list, then it's also ok. But if it's present in a different list that may be accessed in parallel, then that may be a data race on the prev/next pointers. And unfortunately that is exactly what is happening here. In Node::release, we: 1. Take the lock. 2. Move all items to a local list on the stack. 3. Drop the lock. 4. Iterate the local list on the stack. Combined with threads using the unsafe remove method on the original list, this leads to memory corruption of the prev/next pointers. This leads to crashes like this one: Unable to handle kernel paging request at virtual address 000bb9841bcac70e Mem abort info: ESR = 0x0000000096000044 EC = 0x25: DABT (current EL), IL = 32 bits SET = 0, FnV = 0 EA = 0, S1PTW = 0 FSC = 0x04: level 0 translation fault Data abort info: ISV = 0, ISS = 0x00000044, ISS2 = 0x00000000 CM = 0, WnR = 1, TnD = 0, TagAccess = 0 GCS = 0, Overlay = 0, DirtyBit = 0, Xs = 0 [000bb9841bcac70e] address between user and kernel address ranges Internal error: Oops: 0000000096000044 [#1] PREEMPT SMP google-cdd 538c004.gcdd: context saved(CPU:1) item - log_kevents is disabled Modules linked in: ... rust_binder CPU: 1 UID: 0 PID: 2092 Comm: kworker/1:178 Tainted: G S W OE 6.12.52-android16-5-g98debd5df505-4k #1 f94a6367396c5488d635708e43ee0c888d230b0b Tainted: [S]=CPU_OUT_OF_SPEC, [W]=WARN, [O]=OOT_MODULE, [E]=UNSIGNED_MODULE Hardware name: MUSTANG PVT 1.0 based on LGA (DT) Workqueue: events _RNvXs6_NtCsdfZWD8DztAw_6kernel9workqueueINtNtNtB7_4sync3arc3ArcNtNtCs8QPsHWIn21X_16rust_binder_main7process7ProcessEINtB5_15WorkItemPointerKy0_E3runB13_ [rust_binder] pstate: 23400005 (nzCv daif +PAN -UAO +TCO +DIT -SSBS BTYPE=--) pc : _RNvXs3_NtCs8QPsHWIn21X_16rust_binder_main7processNtB5_7ProcessNtNtCsdfZWD8DztAw_6kernel9workqueue8WorkItem3run+0x450/0x11f8 [rust_binder] lr : _RNvXs3_NtCs8QPsHWIn21X_16rust_binder_main7processNtB5_7ProcessNtNtCsdfZWD8DztAw_6kernel9workqueue8WorkItem3run+0x464/0x11f8 [rust_binder] sp : ffffffc09b433ac0 x29: ffffffc09b433d30 x28: ffffff8821690000 x27: ffffffd40cbaa448 x26: ffffff8821690000 x25: 00000000ffffffff x24: ffffff88d0376578 x23: 0000000000000001 x22: ffffffc09b433c78 x21: ffffff88e8f9bf40 x20: ffffff88e8f9bf40 x19: ffffff882692b000 x18: ffffffd40f10bf00 x17: 00000000c006287d x16: 00000000c006287d x15: 00000000000003b0 x14: 0000000000000100 x13: 000000201cb79ae0 x12: fffffffffffffff0 x11: 0000000000000000 x10: 0000000000000001 x9 : 0000000000000000 x8 : b80bb9841bcac706 x7 : 0000000000000001 x6 : fffffffebee63f30 x5 : 0000000000000000 x4 : 0000000000000001 x3 : 0000000000000000 x2 : 0000000000004c31 x1 : ffffff88216900c0 x0 : ffffff88e8f9bf00 Call trace: _RNvXs3_NtCs8QPsHWIn21X_16rust_binder_main7processNtB5_7ProcessNtNtCsdfZWD8DztAw_6kernel9workqueue8WorkItem3run+0x450/0x11f8 [rust_binder bbc172b53665bbc815363b22e97e3f7e3fe971fc] process_scheduled_works+0x1c4/0x45c worker_thread+0x32c/0x3e8 kthread+0x11c/0x1c8 ret_from_fork+0x10/0x20 Code: 94218d85 b4000155 a94026a8 d10102a0 (f9000509) ---[ end trace 0000000000000000 ]--- Thus, modify Node::release to pop items directly off the original list. Cc: stable@vger.kernel.org Fixes: eafedbc7c050 ("rust_binder: add Rust Binder driver") Signed-off-by: Alice Ryhl Acked-by: Miguel Ojeda Link: https://patch.msgid.link/20251111-binder-fix-list-remove-v1-1-8ed14a0da63d@google.com Signed-off-by: Greg Kroah-Hartman Signed-off-by: Greg Kroah-Hartman --- drivers/android/binder/node.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/drivers/android/binder/node.rs b/drivers/android/binder/node.rs index 08d362deaf61e5..c26d113ede9668 100644 --- a/drivers/android/binder/node.rs +++ b/drivers/android/binder/node.rs @@ -541,10 +541,10 @@ impl Node { guard = self.owner.inner.lock(); } - let death_list = core::mem::take(&mut self.inner.access_mut(&mut guard).death_list); - drop(guard); - for death in death_list { + while let Some(death) = self.inner.access_mut(&mut guard).death_list.pop_front() { + drop(guard); death.into_arc().set_dead(); + guard = self.owner.inner.lock(); } } From 877adccfacb32687b90714a27cfb09f444fdfa16 Mon Sep 17 00:00:00 2001 From: Nikita Zhandarovich Date: Thu, 23 Oct 2025 17:14:56 +0300 Subject: [PATCH 0332/3902] comedi: pcl818: fix null-ptr-deref in pcl818_ai_cancel() commit a51f025b5038abd3d22eed2ede4cd46793d89565 upstream. Syzbot identified an issue [1] in pcl818_ai_cancel(), which stems from the fact that in case of early device detach via pcl818_detach(), subdevice dev->read_subdev may not have initialized its pointer to &struct comedi_async as intended. Thus, any such dereferencing of &s->async->cmd will lead to general protection fault and kernel crash. Mitigate this problem by removing a call to pcl818_ai_cancel() from pcl818_detach() altogether. This way, if the subdevice setups its support for async commands, everything async-related will be handled via subdevice's own ->cancel() function in comedi_device_detach_locked() even before pcl818_detach(). If no support for asynchronous commands is provided, there is no need to cancel anything either. [1] Syzbot crash: Oops: general protection fault, probably for non-canonical address 0xdffffc0000000005: 0000 [#1] SMP KASAN PTI KASAN: null-ptr-deref in range [0x0000000000000028-0x000000000000002f] CPU: 1 UID: 0 PID: 6050 Comm: syz.0.18 Not tainted syzkaller #0 PREEMPT(full) Hardware name: Google Google Compute Engine/Google Compute Engine, BIOS Google 08/18/2025 RIP: 0010:pcl818_ai_cancel+0x69/0x3f0 drivers/comedi/drivers/pcl818.c:762 ... Call Trace: pcl818_detach+0x66/0xd0 drivers/comedi/drivers/pcl818.c:1115 comedi_device_detach_locked+0x178/0x750 drivers/comedi/drivers.c:207 do_devconfig_ioctl drivers/comedi/comedi_fops.c:848 [inline] comedi_unlocked_ioctl+0xcde/0x1020 drivers/comedi/comedi_fops.c:2178 vfs_ioctl fs/ioctl.c:51 [inline] __do_sys_ioctl fs/ioctl.c:597 [inline] ... Reported-by: syzbot+fce5d9d5bd067d6fbe9b@syzkaller.appspotmail.com Closes: https://syzkaller.appspot.com/bug?extid=fce5d9d5bd067d6fbe9b Fixes: 00aba6e7b565 ("staging: comedi: pcl818: remove 'neverending_ai' from private data") Cc: stable Signed-off-by: Nikita Zhandarovich Reviewed-by: Ian Abbott Link: https://patch.msgid.link/20251023141457.398685-1-n.zhandarovich@fintech.ru Signed-off-by: Greg Kroah-Hartman Signed-off-by: Greg Kroah-Hartman --- drivers/comedi/drivers/pcl818.c | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/drivers/comedi/drivers/pcl818.c b/drivers/comedi/drivers/pcl818.c index 4127adcfb22955..06fe06396f23ab 100644 --- a/drivers/comedi/drivers/pcl818.c +++ b/drivers/comedi/drivers/pcl818.c @@ -1111,10 +1111,9 @@ static void pcl818_detach(struct comedi_device *dev) { struct pcl818_private *devpriv = dev->private; - if (devpriv) { - pcl818_ai_cancel(dev, dev->read_subdev); + if (devpriv) pcl818_reset(dev); - } + pcl818_free_dma(dev); comedi_legacy_detach(dev); } From 53903ac9ca1abffa27327e85075ec496fa55ccf3 Mon Sep 17 00:00:00 2001 From: Omar Sandoval Date: Tue, 4 Nov 2025 09:55:26 -0800 Subject: [PATCH 0333/3902] KVM: SVM: Don't skip unrelated instruction if INT3/INTO is replaced commit 4da3768e1820cf15cced390242d8789aed34f54d upstream. When re-injecting a soft interrupt from an INT3, INT0, or (select) INTn instruction, discard the exception and retry the instruction if the code stream is changed (e.g. by a different vCPU) between when the CPU executes the instruction and when KVM decodes the instruction to get the next RIP. As effectively predicted by commit 6ef88d6e36c2 ("KVM: SVM: Re-inject INT3/INTO instead of retrying the instruction"), failure to verify that the correct INTn instruction was decoded can effectively clobber guest state due to decoding the wrong instruction and thus specifying the wrong next RIP. The bug most often manifests as "Oops: int3" panics on static branch checks in Linux guests. Enabling or disabling a static branch in Linux uses the kernel's "text poke" code patching mechanism. To modify code while other CPUs may be executing that code, Linux (temporarily) replaces the first byte of the original instruction with an int3 (opcode 0xcc), then patches in the new code stream except for the first byte, and finally replaces the int3 with the first byte of the new code stream. If a CPU hits the int3, i.e. executes the code while it's being modified, then the guest kernel must look up the RIP to determine how to handle the #BP, e.g. by emulating the new instruction. If the RIP is incorrect, then this lookup fails and the guest kernel panics. The bug reproduces almost instantly by hacking the guest kernel to repeatedly check a static branch[1] while running a drgn script[2] on the host to constantly swap out the memory containing the guest's TSS. [1]: https://gist.github.com/osandov/44d17c51c28c0ac998ea0334edf90b5a [2]: https://gist.github.com/osandov/10e45e45afa29b11e0c7209247afc00b Fixes: 6ef88d6e36c2 ("KVM: SVM: Re-inject INT3/INTO instead of retrying the instruction") Cc: stable@vger.kernel.org Co-developed-by: Sean Christopherson Signed-off-by: Omar Sandoval Link: https://patch.msgid.link/1cc6dcdf36e3add7ee7c8d90ad58414eeb6c3d34.1762278762.git.osandov@fb.com Signed-off-by: Sean Christopherson Signed-off-by: Greg Kroah-Hartman --- arch/x86/include/asm/kvm_host.h | 9 +++++++++ arch/x86/kvm/svm/svm.c | 24 +++++++++++++----------- arch/x86/kvm/x86.c | 21 +++++++++++++++++++++ 3 files changed, 43 insertions(+), 11 deletions(-) diff --git a/arch/x86/include/asm/kvm_host.h b/arch/x86/include/asm/kvm_host.h index 48598d017d6f3f..974d64bf0a4d2c 100644 --- a/arch/x86/include/asm/kvm_host.h +++ b/arch/x86/include/asm/kvm_host.h @@ -2143,6 +2143,11 @@ u64 vcpu_tsc_khz(struct kvm_vcpu *vcpu); * the gfn, i.e. retrying the instruction will hit a * !PRESENT fault, which results in a new shadow page * and sends KVM back to square one. + * + * EMULTYPE_SKIP_SOFT_INT - Set in combination with EMULTYPE_SKIP to only skip + * an instruction if it could generate a given software + * interrupt, which must be encoded via + * EMULTYPE_SET_SOFT_INT_VECTOR(). */ #define EMULTYPE_NO_DECODE (1 << 0) #define EMULTYPE_TRAP_UD (1 << 1) @@ -2153,6 +2158,10 @@ u64 vcpu_tsc_khz(struct kvm_vcpu *vcpu); #define EMULTYPE_PF (1 << 6) #define EMULTYPE_COMPLETE_USER_EXIT (1 << 7) #define EMULTYPE_WRITE_PF_TO_SP (1 << 8) +#define EMULTYPE_SKIP_SOFT_INT (1 << 9) + +#define EMULTYPE_SET_SOFT_INT_VECTOR(v) ((u32)((v) & 0xff) << 16) +#define EMULTYPE_GET_SOFT_INT_VECTOR(e) (((e) >> 16) & 0xff) static inline bool kvm_can_emulate_event_vectoring(int emul_type) { diff --git a/arch/x86/kvm/svm/svm.c b/arch/x86/kvm/svm/svm.c index 9d29b2e7e855d6..f2fa69dd5cc72c 100644 --- a/arch/x86/kvm/svm/svm.c +++ b/arch/x86/kvm/svm/svm.c @@ -272,6 +272,7 @@ static void svm_set_interrupt_shadow(struct kvm_vcpu *vcpu, int mask) } static int __svm_skip_emulated_instruction(struct kvm_vcpu *vcpu, + int emul_type, bool commit_side_effects) { struct vcpu_svm *svm = to_svm(vcpu); @@ -293,7 +294,7 @@ static int __svm_skip_emulated_instruction(struct kvm_vcpu *vcpu, if (unlikely(!commit_side_effects)) old_rflags = svm->vmcb->save.rflags; - if (!kvm_emulate_instruction(vcpu, EMULTYPE_SKIP)) + if (!kvm_emulate_instruction(vcpu, emul_type)) return 0; if (unlikely(!commit_side_effects)) @@ -311,11 +312,13 @@ static int __svm_skip_emulated_instruction(struct kvm_vcpu *vcpu, static int svm_skip_emulated_instruction(struct kvm_vcpu *vcpu) { - return __svm_skip_emulated_instruction(vcpu, true); + return __svm_skip_emulated_instruction(vcpu, EMULTYPE_SKIP, true); } -static int svm_update_soft_interrupt_rip(struct kvm_vcpu *vcpu) +static int svm_update_soft_interrupt_rip(struct kvm_vcpu *vcpu, u8 vector) { + const int emul_type = EMULTYPE_SKIP | EMULTYPE_SKIP_SOFT_INT | + EMULTYPE_SET_SOFT_INT_VECTOR(vector); unsigned long rip, old_rip = kvm_rip_read(vcpu); struct vcpu_svm *svm = to_svm(vcpu); @@ -331,7 +334,7 @@ static int svm_update_soft_interrupt_rip(struct kvm_vcpu *vcpu) * in use, the skip must not commit any side effects such as clearing * the interrupt shadow or RFLAGS.RF. */ - if (!__svm_skip_emulated_instruction(vcpu, !nrips)) + if (!__svm_skip_emulated_instruction(vcpu, emul_type, !nrips)) return -EIO; rip = kvm_rip_read(vcpu); @@ -367,7 +370,7 @@ static void svm_inject_exception(struct kvm_vcpu *vcpu) kvm_deliver_exception_payload(vcpu, ex); if (kvm_exception_is_soft(ex->vector) && - svm_update_soft_interrupt_rip(vcpu)) + svm_update_soft_interrupt_rip(vcpu, ex->vector)) return; svm->vmcb->control.event_inj = ex->vector @@ -3633,11 +3636,12 @@ static bool svm_set_vnmi_pending(struct kvm_vcpu *vcpu) static void svm_inject_irq(struct kvm_vcpu *vcpu, bool reinjected) { + struct kvm_queued_interrupt *intr = &vcpu->arch.interrupt; struct vcpu_svm *svm = to_svm(vcpu); u32 type; - if (vcpu->arch.interrupt.soft) { - if (svm_update_soft_interrupt_rip(vcpu)) + if (intr->soft) { + if (svm_update_soft_interrupt_rip(vcpu, intr->nr)) return; type = SVM_EVTINJ_TYPE_SOFT; @@ -3645,12 +3649,10 @@ static void svm_inject_irq(struct kvm_vcpu *vcpu, bool reinjected) type = SVM_EVTINJ_TYPE_INTR; } - trace_kvm_inj_virq(vcpu->arch.interrupt.nr, - vcpu->arch.interrupt.soft, reinjected); + trace_kvm_inj_virq(intr->nr, intr->soft, reinjected); ++vcpu->stat.irq_injections; - svm->vmcb->control.event_inj = vcpu->arch.interrupt.nr | - SVM_EVTINJ_VALID | type; + svm->vmcb->control.event_inj = intr->nr | SVM_EVTINJ_VALID | type; } void svm_complete_interrupt_delivery(struct kvm_vcpu *vcpu, int delivery_mode, diff --git a/arch/x86/kvm/x86.c b/arch/x86/kvm/x86.c index c9c2aa6f4705e1..e6f2e34ec97d10 100644 --- a/arch/x86/kvm/x86.c +++ b/arch/x86/kvm/x86.c @@ -9337,6 +9337,23 @@ static bool is_vmware_backdoor_opcode(struct x86_emulate_ctxt *ctxt) return false; } +static bool is_soft_int_instruction(struct x86_emulate_ctxt *ctxt, + int emulation_type) +{ + u8 vector = EMULTYPE_GET_SOFT_INT_VECTOR(emulation_type); + + switch (ctxt->b) { + case 0xcc: + return vector == BP_VECTOR; + case 0xcd: + return vector == ctxt->src.val; + case 0xce: + return vector == OF_VECTOR; + default: + return false; + } +} + /* * Decode an instruction for emulation. The caller is responsible for handling * code breakpoints. Note, manually detecting code breakpoints is unnecessary @@ -9447,6 +9464,10 @@ int x86_emulate_instruction(struct kvm_vcpu *vcpu, gpa_t cr2_or_gpa, * injecting single-step #DBs. */ if (emulation_type & EMULTYPE_SKIP) { + if (emulation_type & EMULTYPE_SKIP_SOFT_INT && + !is_soft_int_instruction(ctxt, emulation_type)) + return 0; + if (ctxt->mode != X86EMUL_MODE_PROT64) ctxt->eip = (u32)ctxt->_eip; else From 99115ab00c1fcded6cae2403cea72ee09734d9c6 Mon Sep 17 00:00:00 2001 From: Slark Xiao Date: Tue, 18 Nov 2025 14:45:28 +0800 Subject: [PATCH 0334/3902] USB: serial: option: add Foxconn T99W760 commit 7970b4969c4c99bcdaf105f9f39c6d2021f6d244 upstream. T99W760 is designed based on Qualcomm SDX35 (5G redcap) chip. There are three serial ports to be enumerated: Modem, NMEA and Diag. test evidence as below: T: Bus=03 Lev=01 Prnt=01 Port=03 Cnt=01 Dev#= 4 Spd=5000 MxCh= 0 D: Ver= 3.20 Cls=ef(misc ) Sub=02 Prot=01 MxPS=64 #Cfgs= 1 P: Vendor=0489 ProdID=e123 Rev=05.15 S: Manufacturer=QCOM S: Product=SDXBAAGHA-IDP _SN:39A8D3E4 S: SerialNumber=39a8d3e4 C: #Ifs= 6 Cfg#= 1 Atr=a0 MxPwr=896mA I: If#= 0 Alt= 0 #EPs= 1 Cls=02(commc) Sub=0e Prot=00 Driver=cdc_mbim E: Ad=82(I) Atr=03(Int.) MxPS= 64 Ivl=32ms I: If#= 1 Alt= 1 #EPs= 2 Cls=0a(data ) Sub=00 Prot=02 Driver=cdc_mbim E: Ad=01(O) Atr=02(Bulk) MxPS=1024 Ivl=0ms E: Ad=81(I) Atr=02(Bulk) MxPS=1024 Ivl=0ms I: If#= 2 Alt= 0 #EPs= 3 Cls=ff(vend.) Sub=ff Prot=40 Driver=option E: Ad=02(O) Atr=02(Bulk) MxPS=1024 Ivl=0ms E: Ad=83(I) Atr=02(Bulk) MxPS=1024 Ivl=0ms E: Ad=84(I) Atr=03(Int.) MxPS= 10 Ivl=32ms I: If#= 3 Alt= 0 #EPs= 1 Cls=ff(vend.) Sub=ff Prot=ff Driver=(none) E: Ad=85(I) Atr=03(Int.) MxPS= 64 Ivl=32ms I: If#= 4 Alt= 0 #EPs= 3 Cls=ff(vend.) Sub=ff Prot=40 Driver=option E: Ad=03(O) Atr=02(Bulk) MxPS=1024 Ivl=0ms E: Ad=86(I) Atr=02(Bulk) MxPS=1024 Ivl=0ms E: Ad=87(I) Atr=03(Int.) MxPS= 10 Ivl=32ms I: If#= 5 Alt= 0 #EPs= 2 Cls=ff(vend.) Sub=ff Prot=30 Driver=option E: Ad=04(O) Atr=02(Bulk) MxPS=1024 Ivl=0ms E: Ad=88(I) Atr=02(Bulk) MxPS=1024 Ivl=0ms 0&1: MBIM, 2:Modem, 3:GNSS(non-serial port), 4: NMEA, 5:Diag Signed-off-by: Slark Xiao Cc: stable@vger.kernel.org Signed-off-by: Johan Hovold Signed-off-by: Greg Kroah-Hartman --- drivers/usb/serial/option.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/drivers/usb/serial/option.c b/drivers/usb/serial/option.c index e9400727ad36eb..d13226f9b811fc 100644 --- a/drivers/usb/serial/option.c +++ b/drivers/usb/serial/option.c @@ -2376,6 +2376,8 @@ static const struct usb_device_id option_ids[] = { .driver_info = RSVD(3) }, { USB_DEVICE_INTERFACE_CLASS(0x0489, 0xe0f0, 0xff), /* Foxconn T99W373 MBIM */ .driver_info = RSVD(3) }, + { USB_DEVICE_INTERFACE_CLASS(0x0489, 0xe123, 0xff), /* Foxconn T99W760 MBIM */ + .driver_info = RSVD(3) }, { USB_DEVICE_INTERFACE_CLASS(0x0489, 0xe145, 0xff), /* Foxconn T99W651 RNDIS */ .driver_info = RSVD(5) | RSVD(6) }, { USB_DEVICE_INTERFACE_CLASS(0x0489, 0xe15f, 0xff), /* Foxconn T99W709 */ From 1425cc83a839a4c2f25c3272efa56461ff563065 Mon Sep 17 00:00:00 2001 From: Fabio Porcedda Date: Wed, 26 Nov 2025 15:26:39 +0100 Subject: [PATCH 0335/3902] USB: serial: option: add Telit Cinterion FE910C04 new compositions commit c908039a29aa70870871f4848125b3d743f929bf upstream. Add the following Telit Cinterion new compositions: 0x10c1: RNDIS + tty (AT/NMEA) + tty (AT) + tty (diag) T: Bus=01 Lev=01 Prnt=01 Port=00 Cnt=01 Dev#= 2 Spd=480 MxCh= 0 D: Ver= 2.00 Cls=00(>ifc ) Sub=00 Prot=00 MxPS=64 #Cfgs= 1 P: Vendor=1bc7 ProdID=10c1 Rev=05.15 S: Manufacturer=Telit Cinterion S: Product=FE910 S: SerialNumber=f71b8b32 C: #Ifs= 5 Cfg#= 1 Atr=e0 MxPwr=500mA I: If#= 0 Alt= 0 #EPs= 1 Cls=ef(misc ) Sub=04 Prot=01 Driver=rndis_host E: Ad=82(I) Atr=03(Int.) MxPS= 8 Ivl=32ms I: If#= 1 Alt= 0 #EPs= 2 Cls=0a(data ) Sub=00 Prot=00 Driver=rndis_host E: Ad=01(O) Atr=02(Bulk) MxPS= 512 Ivl=0ms E: Ad=81(I) Atr=02(Bulk) MxPS= 512 Ivl=0ms I: If#= 2 Alt= 0 #EPs= 3 Cls=ff(vend.) Sub=ff Prot=60 Driver=option E: Ad=02(O) Atr=02(Bulk) MxPS= 512 Ivl=0ms E: Ad=83(I) Atr=02(Bulk) MxPS= 512 Ivl=0ms E: Ad=84(I) Atr=03(Int.) MxPS= 10 Ivl=32ms I: If#= 3 Alt= 0 #EPs= 3 Cls=ff(vend.) Sub=ff Prot=40 Driver=option E: Ad=03(O) Atr=02(Bulk) MxPS= 512 Ivl=0ms E: Ad=85(I) Atr=02(Bulk) MxPS= 512 Ivl=0ms E: Ad=86(I) Atr=03(Int.) MxPS= 10 Ivl=32ms I: If#= 4 Alt= 0 #EPs= 2 Cls=ff(vend.) Sub=ff Prot=30 Driver=option E: Ad=04(O) Atr=02(Bulk) MxPS= 512 Ivl=0ms E: Ad=87(I) Atr=02(Bulk) MxPS= 512 Ivl=0ms 0x10c2: MBIM + tty (AT/NMEA) + tty (AT) + tty (diag) T: Bus=01 Lev=01 Prnt=01 Port=00 Cnt=01 Dev#= 8 Spd=480 MxCh= 0 D: Ver= 2.00 Cls=ef(misc ) Sub=02 Prot=01 MxPS=64 #Cfgs= 1 P: Vendor=1bc7 ProdID=10c2 Rev=05.15 S: Manufacturer=Telit Cinterion S: Product=FE910 S: SerialNumber=f71b8b32 C: #Ifs= 5 Cfg#= 1 Atr=e0 MxPwr=500mA I: If#= 0 Alt= 0 #EPs= 1 Cls=02(commc) Sub=0e Prot=00 Driver=cdc_mbim E: Ad=82(I) Atr=03(Int.) MxPS= 64 Ivl=32ms I: If#= 1 Alt= 1 #EPs= 2 Cls=0a(data ) Sub=00 Prot=02 Driver=cdc_mbim E: Ad=01(O) Atr=02(Bulk) MxPS= 512 Ivl=0ms E: Ad=81(I) Atr=02(Bulk) MxPS= 512 Ivl=0ms I: If#= 2 Alt= 0 #EPs= 3 Cls=ff(vend.) Sub=ff Prot=60 Driver=option E: Ad=02(O) Atr=02(Bulk) MxPS= 512 Ivl=0ms E: Ad=83(I) Atr=02(Bulk) MxPS= 512 Ivl=0ms E: Ad=84(I) Atr=03(Int.) MxPS= 10 Ivl=32ms I: If#= 3 Alt= 0 #EPs= 3 Cls=ff(vend.) Sub=ff Prot=40 Driver=option E: Ad=03(O) Atr=02(Bulk) MxPS= 512 Ivl=0ms E: Ad=85(I) Atr=02(Bulk) MxPS= 512 Ivl=0ms E: Ad=86(I) Atr=03(Int.) MxPS= 10 Ivl=32ms I: If#= 4 Alt= 0 #EPs= 2 Cls=ff(vend.) Sub=ff Prot=30 Driver=option E: Ad=04(O) Atr=02(Bulk) MxPS= 512 Ivl=0ms E: Ad=87(I) Atr=02(Bulk) MxPS= 512 Ivl=0ms 0x10c3: ECM + tty (AT/NMEA) + tty (AT) + tty (diag) T: Bus=01 Lev=01 Prnt=01 Port=00 Cnt=01 Dev#= 9 Spd=480 MxCh= 0 D: Ver= 2.00 Cls=00(>ifc ) Sub=00 Prot=00 MxPS=64 #Cfgs= 1 P: Vendor=1bc7 ProdID=10c3 Rev=05.15 S: Manufacturer=Telit Cinterion S: Product=FE910 S: SerialNumber=f71b8b32 C: #Ifs= 5 Cfg#= 1 Atr=e0 MxPwr=500mA I: If#= 0 Alt= 0 #EPs= 1 Cls=02(commc) Sub=06 Prot=00 Driver=cdc_ether E: Ad=82(I) Atr=03(Int.) MxPS= 16 Ivl=32ms I: If#= 1 Alt= 1 #EPs= 2 Cls=0a(data ) Sub=00 Prot=00 Driver=cdc_ether E: Ad=01(O) Atr=02(Bulk) MxPS= 512 Ivl=0ms E: Ad=81(I) Atr=02(Bulk) MxPS= 512 Ivl=0ms I: If#= 2 Alt= 0 #EPs= 3 Cls=ff(vend.) Sub=ff Prot=60 Driver=option E: Ad=02(O) Atr=02(Bulk) MxPS= 512 Ivl=0ms E: Ad=83(I) Atr=02(Bulk) MxPS= 512 Ivl=0ms E: Ad=84(I) Atr=03(Int.) MxPS= 10 Ivl=32ms I: If#= 3 Alt= 0 #EPs= 3 Cls=ff(vend.) Sub=ff Prot=40 Driver=option E: Ad=03(O) Atr=02(Bulk) MxPS= 512 Ivl=0ms E: Ad=85(I) Atr=02(Bulk) MxPS= 512 Ivl=0ms E: Ad=86(I) Atr=03(Int.) MxPS= 10 Ivl=32ms I: If#= 4 Alt= 0 #EPs= 2 Cls=ff(vend.) Sub=ff Prot=30 Driver=option E: Ad=04(O) Atr=02(Bulk) MxPS= 512 Ivl=0ms E: Ad=87(I) Atr=02(Bulk) MxPS= 512 Ivl=0ms 0x10c5: RNDIS + tty (AT) + tty (AT) + tty (diag) T: Bus=01 Lev=01 Prnt=01 Port=00 Cnt=01 Dev#= 10 Spd=480 MxCh= 0 D: Ver= 2.00 Cls=00(>ifc ) Sub=00 Prot=00 MxPS=64 #Cfgs= 1 P: Vendor=1bc7 ProdID=10c5 Rev=05.15 S: Manufacturer=Telit Cinterion S: Product=FE910 S: SerialNumber=f71b8b32 C: #Ifs= 5 Cfg#= 1 Atr=e0 MxPwr=500mA I: If#= 0 Alt= 0 #EPs= 1 Cls=ef(misc ) Sub=04 Prot=01 Driver=rndis_host E: Ad=82(I) Atr=03(Int.) MxPS= 8 Ivl=32ms I: If#= 1 Alt= 0 #EPs= 2 Cls=0a(data ) Sub=00 Prot=00 Driver=rndis_host E: Ad=01(O) Atr=02(Bulk) MxPS= 512 Ivl=0ms E: Ad=81(I) Atr=02(Bulk) MxPS= 512 Ivl=0ms I: If#= 2 Alt= 0 #EPs= 3 Cls=ff(vend.) Sub=ff Prot=40 Driver=option E: Ad=02(O) Atr=02(Bulk) MxPS= 512 Ivl=0ms E: Ad=83(I) Atr=02(Bulk) MxPS= 512 Ivl=0ms E: Ad=84(I) Atr=03(Int.) MxPS= 10 Ivl=32ms I: If#= 3 Alt= 0 #EPs= 3 Cls=ff(vend.) Sub=ff Prot=40 Driver=option E: Ad=03(O) Atr=02(Bulk) MxPS= 512 Ivl=0ms E: Ad=85(I) Atr=02(Bulk) MxPS= 512 Ivl=0ms E: Ad=86(I) Atr=03(Int.) MxPS= 10 Ivl=32ms I: If#= 4 Alt= 0 #EPs= 2 Cls=ff(vend.) Sub=ff Prot=30 Driver=option E: Ad=04(O) Atr=02(Bulk) MxPS= 512 Ivl=0ms E: Ad=87(I) Atr=02(Bulk) MxPS= 512 Ivl=0ms 0x10c6: MBIM + tty (AT) + tty (AT) + tty (diag) T: Bus=01 Lev=01 Prnt=01 Port=00 Cnt=01 Dev#= 11 Spd=480 MxCh= 0 D: Ver= 2.00 Cls=ef(misc ) Sub=02 Prot=01 MxPS=64 #Cfgs= 1 P: Vendor=1bc7 ProdID=10c6 Rev=05.15 S: Manufacturer=Telit Cinterion S: Product=FE910 S: SerialNumber=f71b8b32 C: #Ifs= 5 Cfg#= 1 Atr=e0 MxPwr=500mA I: If#= 0 Alt= 0 #EPs= 1 Cls=02(commc) Sub=0e Prot=00 Driver=cdc_mbim E: Ad=82(I) Atr=03(Int.) MxPS= 64 Ivl=32ms I: If#= 1 Alt= 1 #EPs= 2 Cls=0a(data ) Sub=00 Prot=02 Driver=cdc_mbim E: Ad=01(O) Atr=02(Bulk) MxPS= 512 Ivl=0ms E: Ad=81(I) Atr=02(Bulk) MxPS= 512 Ivl=0ms I: If#= 2 Alt= 0 #EPs= 3 Cls=ff(vend.) Sub=ff Prot=40 Driver=option E: Ad=02(O) Atr=02(Bulk) MxPS= 512 Ivl=0ms E: Ad=83(I) Atr=02(Bulk) MxPS= 512 Ivl=0ms E: Ad=84(I) Atr=03(Int.) MxPS= 10 Ivl=32ms I: If#= 3 Alt= 0 #EPs= 3 Cls=ff(vend.) Sub=ff Prot=40 Driver=option E: Ad=03(O) Atr=02(Bulk) MxPS= 512 Ivl=0ms E: Ad=85(I) Atr=02(Bulk) MxPS= 512 Ivl=0ms E: Ad=86(I) Atr=03(Int.) MxPS= 10 Ivl=32ms I: If#= 4 Alt= 0 #EPs= 2 Cls=ff(vend.) Sub=ff Prot=30 Driver=option E: Ad=04(O) Atr=02(Bulk) MxPS= 512 Ivl=0ms E: Ad=87(I) Atr=02(Bulk) MxPS= 512 Ivl=0ms 0x10c9: MBIM + tty (AT) + tty (diag) + DPL (Data Packet Logging) + adb T: Bus=01 Lev=01 Prnt=01 Port=00 Cnt=01 Dev#= 13 Spd=480 MxCh= 0 D: Ver= 2.00 Cls=ef(misc ) Sub=02 Prot=01 MxPS=64 #Cfgs= 1 P: Vendor=1bc7 ProdID=10c9 Rev=05.15 S: Manufacturer=Telit Cinterion S: Product=FE910 S: SerialNumber=f71b8b32 C: #Ifs= 6 Cfg#= 1 Atr=e0 MxPwr=500mA I: If#= 0 Alt= 0 #EPs= 1 Cls=02(commc) Sub=0e Prot=00 Driver=cdc_mbim E: Ad=82(I) Atr=03(Int.) MxPS= 64 Ivl=32ms I: If#= 1 Alt= 1 #EPs= 2 Cls=0a(data ) Sub=00 Prot=02 Driver=cdc_mbim E: Ad=01(O) Atr=02(Bulk) MxPS= 512 Ivl=0ms E: Ad=81(I) Atr=02(Bulk) MxPS= 512 Ivl=0ms I: If#= 2 Alt= 0 #EPs= 3 Cls=ff(vend.) Sub=ff Prot=40 Driver=option E: Ad=02(O) Atr=02(Bulk) MxPS= 512 Ivl=0ms E: Ad=83(I) Atr=02(Bulk) MxPS= 512 Ivl=0ms E: Ad=84(I) Atr=03(Int.) MxPS= 10 Ivl=32ms I: If#= 3 Alt= 0 #EPs= 2 Cls=ff(vend.) Sub=ff Prot=30 Driver=option E: Ad=03(O) Atr=02(Bulk) MxPS= 512 Ivl=0ms E: Ad=85(I) Atr=02(Bulk) MxPS= 512 Ivl=0ms I: If#= 4 Alt= 0 #EPs= 1 Cls=ff(vend.) Sub=ff Prot=80 Driver=(none) E: Ad=86(I) Atr=02(Bulk) MxPS= 512 Ivl=0ms I: If#= 5 Alt= 0 #EPs= 2 Cls=ff(vend.) Sub=42 Prot=01 Driver=usbfs E: Ad=04(O) Atr=02(Bulk) MxPS= 512 Ivl=0ms E: Ad=87(I) Atr=02(Bulk) MxPS= 512 Ivl=0ms 0x10cb: RNDIS + tty (AT) + tty (diag) + DPL (Data Packet Logging) + adb T: Bus=01 Lev=01 Prnt=01 Port=09 Cnt=01 Dev#= 9 Spd=480 MxCh= 0 D: Ver= 2.00 Cls=00(>ifc ) Sub=00 Prot=00 MxPS=64 #Cfgs= 1 P: Vendor=1bc7 ProdID=10cb Rev=05.15 S: Manufacturer=Telit Cinterion S: Product=FE910 S: SerialNumber=f71b8b32 C: #Ifs= 6 Cfg#= 1 Atr=e0 MxPwr=500mA I: If#= 0 Alt= 0 #EPs= 1 Cls=ef(misc ) Sub=04 Prot=01 Driver=rndis_host E: Ad=82(I) Atr=03(Int.) MxPS= 8 Ivl=32ms I: If#= 1 Alt= 0 #EPs= 2 Cls=0a(data ) Sub=00 Prot=00 Driver=rndis_host E: Ad=01(O) Atr=02(Bulk) MxPS= 512 Ivl=0ms E: Ad=81(I) Atr=02(Bulk) MxPS= 512 Ivl=0ms I: If#= 2 Alt= 0 #EPs= 3 Cls=ff(vend.) Sub=ff Prot=40 Driver=option E: Ad=02(O) Atr=02(Bulk) MxPS= 512 Ivl=0ms E: Ad=83(I) Atr=02(Bulk) MxPS= 512 Ivl=0ms E: Ad=84(I) Atr=03(Int.) MxPS= 10 Ivl=32ms I: If#= 3 Alt= 0 #EPs= 2 Cls=ff(vend.) Sub=ff Prot=30 Driver=option E: Ad=03(O) Atr=02(Bulk) MxPS= 512 Ivl=0ms E: Ad=85(I) Atr=02(Bulk) MxPS= 512 Ivl=0ms I: If#= 4 Alt= 0 #EPs= 1 Cls=ff(vend.) Sub=ff Prot=80 Driver=(none) E: Ad=86(I) Atr=02(Bulk) MxPS= 512 Ivl=0ms I: If#= 5 Alt= 0 #EPs= 2 Cls=ff(vend.) Sub=42 Prot=01 Driver=(none) E: Ad=04(O) Atr=02(Bulk) MxPS= 512 Ivl=0ms E: Ad=87(I) Atr=02(Bulk) MxPS= 512 Ivl=0ms Cc: stable@vger.kernel.org Signed-off-by: Fabio Porcedda Signed-off-by: Johan Hovold Signed-off-by: Greg Kroah-Hartman --- drivers/usb/serial/option.c | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/drivers/usb/serial/option.c b/drivers/usb/serial/option.c index d13226f9b811fc..09f1876811b0d3 100644 --- a/drivers/usb/serial/option.c +++ b/drivers/usb/serial/option.c @@ -1433,10 +1433,24 @@ static const struct usb_device_id option_ids[] = { { USB_DEVICE_AND_INTERFACE_INFO(TELIT_VENDOR_ID, 0x10b3, 0xff, 0xff, 0x60) }, { USB_DEVICE_INTERFACE_CLASS(TELIT_VENDOR_ID, 0x10c0, 0xff), /* Telit FE910C04 (rmnet) */ .driver_info = RSVD(0) | NCTRL(3) }, + { USB_DEVICE_INTERFACE_CLASS(TELIT_VENDOR_ID, 0x10c1, 0xff), /* Telit FE910C04 (RNDIS) */ + .driver_info = NCTRL(4) }, + { USB_DEVICE_INTERFACE_CLASS(TELIT_VENDOR_ID, 0x10c2, 0xff), /* Telit FE910C04 (MBIM) */ + .driver_info = NCTRL(4) }, + { USB_DEVICE_INTERFACE_CLASS(TELIT_VENDOR_ID, 0x10c3, 0xff), /* Telit FE910C04 (ECM) */ + .driver_info = NCTRL(4) }, { USB_DEVICE_INTERFACE_CLASS(TELIT_VENDOR_ID, 0x10c4, 0xff), /* Telit FE910C04 (rmnet) */ .driver_info = RSVD(0) | NCTRL(3) }, + { USB_DEVICE_INTERFACE_CLASS(TELIT_VENDOR_ID, 0x10c5, 0xff), /* Telit FE910C04 (RNDIS) */ + .driver_info = NCTRL(4) }, + { USB_DEVICE_INTERFACE_CLASS(TELIT_VENDOR_ID, 0x10c6, 0xff), /* Telit FE910C04 (MBIM) */ + .driver_info = NCTRL(4) }, { USB_DEVICE_INTERFACE_CLASS(TELIT_VENDOR_ID, 0x10c8, 0xff), /* Telit FE910C04 (rmnet) */ .driver_info = RSVD(0) | NCTRL(2) | RSVD(3) | RSVD(4) }, + { USB_DEVICE_INTERFACE_CLASS(TELIT_VENDOR_ID, 0x10c9, 0xff), /* Telit FE910C04 (MBIM) */ + .driver_info = NCTRL(3) | RSVD(4) | RSVD(5) }, + { USB_DEVICE_INTERFACE_CLASS(TELIT_VENDOR_ID, 0x10cb, 0xff), /* Telit FE910C04 (RNDIS) */ + .driver_info = NCTRL(3) | RSVD(4) | RSVD(5) }, { USB_DEVICE_AND_INTERFACE_INFO(TELIT_VENDOR_ID, 0x10d0, 0xff, 0xff, 0x30), /* Telit FN990B (rmnet) */ .driver_info = NCTRL(5) }, { USB_DEVICE_AND_INTERFACE_INFO(TELIT_VENDOR_ID, 0x10d0, 0xff, 0xff, 0x40) }, From a0538d99153f3f688649d09b4d637902846b585e Mon Sep 17 00:00:00 2001 From: Fabio Porcedda Date: Wed, 26 Nov 2025 15:26:40 +0100 Subject: [PATCH 0336/3902] USB: serial: option: move Telit 0x10c7 composition in the right place commit 072f2c49572547f4b0776fe2da6b8f61e4b34699 upstream. Move Telit 0x10c7 composition right after 0x10c6 composition and before 0x10c8 composition. Signed-off-by: Fabio Porcedda Cc: stable@vger.kernel.org Signed-off-by: Johan Hovold Signed-off-by: Greg Kroah-Hartman --- drivers/usb/serial/option.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/drivers/usb/serial/option.c b/drivers/usb/serial/option.c index 09f1876811b0d3..4c0e5a3ab557b2 100644 --- a/drivers/usb/serial/option.c +++ b/drivers/usb/serial/option.c @@ -1445,6 +1445,9 @@ static const struct usb_device_id option_ids[] = { .driver_info = NCTRL(4) }, { USB_DEVICE_INTERFACE_CLASS(TELIT_VENDOR_ID, 0x10c6, 0xff), /* Telit FE910C04 (MBIM) */ .driver_info = NCTRL(4) }, + { USB_DEVICE_AND_INTERFACE_INFO(TELIT_VENDOR_ID, 0x10c7, 0xff, 0xff, 0x30), /* Telit FE910C04 (ECM) */ + .driver_info = NCTRL(4) }, + { USB_DEVICE_AND_INTERFACE_INFO(TELIT_VENDOR_ID, 0x10c7, 0xff, 0xff, 0x40) }, { USB_DEVICE_INTERFACE_CLASS(TELIT_VENDOR_ID, 0x10c8, 0xff), /* Telit FE910C04 (rmnet) */ .driver_info = RSVD(0) | NCTRL(2) | RSVD(3) | RSVD(4) }, { USB_DEVICE_INTERFACE_CLASS(TELIT_VENDOR_ID, 0x10c9, 0xff), /* Telit FE910C04 (MBIM) */ @@ -1455,9 +1458,6 @@ static const struct usb_device_id option_ids[] = { .driver_info = NCTRL(5) }, { USB_DEVICE_AND_INTERFACE_INFO(TELIT_VENDOR_ID, 0x10d0, 0xff, 0xff, 0x40) }, { USB_DEVICE_AND_INTERFACE_INFO(TELIT_VENDOR_ID, 0x10d0, 0xff, 0xff, 0x60) }, - { USB_DEVICE_AND_INTERFACE_INFO(TELIT_VENDOR_ID, 0x10c7, 0xff, 0xff, 0x30), /* Telit FE910C04 (ECM) */ - .driver_info = NCTRL(4) }, - { USB_DEVICE_AND_INTERFACE_INFO(TELIT_VENDOR_ID, 0x10c7, 0xff, 0xff, 0x40) }, { USB_DEVICE_AND_INTERFACE_INFO(TELIT_VENDOR_ID, 0x10d1, 0xff, 0xff, 0x30), /* Telit FN990B (MBIM) */ .driver_info = NCTRL(6) }, { USB_DEVICE_AND_INTERFACE_INFO(TELIT_VENDOR_ID, 0x10d1, 0xff, 0xff, 0x40) }, From 64934857c8847ade8d1e061faf1128a929d1ab17 Mon Sep 17 00:00:00 2001 From: Johan Hovold Date: Mon, 10 Nov 2025 12:12:05 +0100 Subject: [PATCH 0337/3902] USB: serial: ftdi_sio: match on interface number for jtag commit 4e31a5d0a9ee672f708fc993c1d5520643f769fd upstream. Some FTDI devices have the first port reserved for JTAG and have been using a dedicated quirk to prevent binding to it. As can be inferred directly or indirectly from the commit messages, almost all of these devices are dual port devices which means that the more recently added macro for matching on interface number can be used instead (and some such devices do so already). This avoids probing interfaces that will never be bound and cleans up the match table somewhat. Note that the JTAG quirk is kept for quad port devices, which would otherwise require three match entries. Cc: stable@vger.kernel.org Signed-off-by: Johan Hovold Signed-off-by: Greg Kroah-Hartman --- drivers/usb/serial/ftdi_sio.c | 72 ++++++++++++----------------------- 1 file changed, 24 insertions(+), 48 deletions(-) diff --git a/drivers/usb/serial/ftdi_sio.c b/drivers/usb/serial/ftdi_sio.c index b37fa31f56943d..9993a5123344e3 100644 --- a/drivers/usb/serial/ftdi_sio.c +++ b/drivers/usb/serial/ftdi_sio.c @@ -628,10 +628,8 @@ static const struct usb_device_id id_table_combined[] = { { USB_DEVICE(FTDI_VID, FTDI_IBS_PEDO_PID) }, { USB_DEVICE(FTDI_VID, FTDI_IBS_PROD_PID) }, { USB_DEVICE(FTDI_VID, FTDI_TAVIR_STK500_PID) }, - { USB_DEVICE(FTDI_VID, FTDI_TIAO_UMPA_PID), - .driver_info = (kernel_ulong_t)&ftdi_jtag_quirk }, - { USB_DEVICE(FTDI_VID, FTDI_NT_ORIONLXM_PID), - .driver_info = (kernel_ulong_t)&ftdi_jtag_quirk }, + { USB_DEVICE_INTERFACE_NUMBER(FTDI_VID, FTDI_TIAO_UMPA_PID, 1) }, + { USB_DEVICE_INTERFACE_NUMBER(FTDI_VID, FTDI_NT_ORIONLXM_PID, 1) }, { USB_DEVICE(FTDI_VID, FTDI_NT_ORIONLX_PLUS_PID) }, { USB_DEVICE(FTDI_VID, FTDI_NT_ORION_IO_PID) }, { USB_DEVICE(FTDI_VID, FTDI_NT_ORIONMX_PID) }, @@ -842,24 +840,17 @@ static const struct usb_device_id id_table_combined[] = { { USB_DEVICE(FTDI_VID, FTDI_ELSTER_UNICOM_PID) }, { USB_DEVICE(FTDI_VID, FTDI_PROPOX_JTAGCABLEII_PID) }, { USB_DEVICE(FTDI_VID, FTDI_PROPOX_ISPCABLEIII_PID) }, - { USB_DEVICE(FTDI_VID, CYBER_CORTEX_AV_PID), - .driver_info = (kernel_ulong_t)&ftdi_jtag_quirk }, + { USB_DEVICE_INTERFACE_NUMBER(FTDI_VID, CYBER_CORTEX_AV_PID, 1) }, { USB_DEVICE_INTERFACE_NUMBER(OLIMEX_VID, OLIMEX_ARM_USB_OCD_PID, 1) }, { USB_DEVICE_INTERFACE_NUMBER(OLIMEX_VID, OLIMEX_ARM_USB_OCD_H_PID, 1) }, { USB_DEVICE_INTERFACE_NUMBER(OLIMEX_VID, OLIMEX_ARM_USB_TINY_PID, 1) }, { USB_DEVICE_INTERFACE_NUMBER(OLIMEX_VID, OLIMEX_ARM_USB_TINY_H_PID, 1) }, - { USB_DEVICE(FIC_VID, FIC_NEO1973_DEBUG_PID), - .driver_info = (kernel_ulong_t)&ftdi_jtag_quirk }, - { USB_DEVICE(FTDI_VID, FTDI_OOCDLINK_PID), - .driver_info = (kernel_ulong_t)&ftdi_jtag_quirk }, - { USB_DEVICE(FTDI_VID, LMI_LM3S_DEVEL_BOARD_PID), - .driver_info = (kernel_ulong_t)&ftdi_jtag_quirk }, - { USB_DEVICE(FTDI_VID, LMI_LM3S_EVAL_BOARD_PID), - .driver_info = (kernel_ulong_t)&ftdi_jtag_quirk }, - { USB_DEVICE(FTDI_VID, LMI_LM3S_ICDI_BOARD_PID), - .driver_info = (kernel_ulong_t)&ftdi_jtag_quirk }, - { USB_DEVICE(FTDI_VID, FTDI_TURTELIZER_PID), - .driver_info = (kernel_ulong_t)&ftdi_jtag_quirk }, + { USB_DEVICE_INTERFACE_NUMBER(FIC_VID, FIC_NEO1973_DEBUG_PID, 1) }, + { USB_DEVICE_INTERFACE_NUMBER(FTDI_VID, FTDI_OOCDLINK_PID, 1) }, + { USB_DEVICE_INTERFACE_NUMBER(FTDI_VID, LMI_LM3S_DEVEL_BOARD_PID, 1) }, + { USB_DEVICE_INTERFACE_NUMBER(FTDI_VID, LMI_LM3S_EVAL_BOARD_PID, 1) }, + { USB_DEVICE_INTERFACE_NUMBER(FTDI_VID, LMI_LM3S_ICDI_BOARD_PID, 1) }, + { USB_DEVICE_INTERFACE_NUMBER(FTDI_VID, FTDI_TURTELIZER_PID, 1) }, { USB_DEVICE(RATOC_VENDOR_ID, RATOC_PRODUCT_ID_USB60F) }, { USB_DEVICE(RATOC_VENDOR_ID, RATOC_PRODUCT_ID_SCU18) }, { USB_DEVICE(FTDI_VID, FTDI_REU_TINY_PID) }, @@ -901,17 +892,14 @@ static const struct usb_device_id id_table_combined[] = { { USB_DEVICE(ATMEL_VID, STK541_PID) }, { USB_DEVICE(DE_VID, STB_PID) }, { USB_DEVICE(DE_VID, WHT_PID) }, - { USB_DEVICE(ADI_VID, ADI_GNICE_PID), - .driver_info = (kernel_ulong_t)&ftdi_jtag_quirk }, - { USB_DEVICE(ADI_VID, ADI_GNICEPLUS_PID), - .driver_info = (kernel_ulong_t)&ftdi_jtag_quirk }, + { USB_DEVICE_INTERFACE_NUMBER(ADI_VID, ADI_GNICE_PID, 1) }, + { USB_DEVICE_INTERFACE_NUMBER(ADI_VID, ADI_GNICEPLUS_PID, 1) }, { USB_DEVICE_AND_INTERFACE_INFO(MICROCHIP_VID, MICROCHIP_USB_BOARD_PID, USB_CLASS_VENDOR_SPEC, USB_SUBCLASS_VENDOR_SPEC, 0x00) }, { USB_DEVICE_INTERFACE_NUMBER(ACTEL_VID, MICROSEMI_ARROW_SF2PLUS_BOARD_PID, 2) }, { USB_DEVICE(JETI_VID, JETI_SPC1201_PID) }, - { USB_DEVICE(MARVELL_VID, MARVELL_SHEEVAPLUG_PID), - .driver_info = (kernel_ulong_t)&ftdi_jtag_quirk }, + { USB_DEVICE_INTERFACE_NUMBER(MARVELL_VID, MARVELL_SHEEVAPLUG_PID, 1) }, { USB_DEVICE(LARSENBRUSGAARD_VID, LB_ALTITRACK_PID) }, { USB_DEVICE(GN_OTOMETRICS_VID, AURICAL_USB_PID) }, { USB_DEVICE(FTDI_VID, PI_C865_PID) }, @@ -934,10 +922,8 @@ static const struct usb_device_id id_table_combined[] = { { USB_DEVICE(PI_VID, PI_1016_PID) }, { USB_DEVICE(KONDO_VID, KONDO_USB_SERIAL_PID) }, { USB_DEVICE(BAYER_VID, BAYER_CONTOUR_CABLE_PID) }, - { USB_DEVICE(FTDI_VID, MARVELL_OPENRD_PID), - .driver_info = (kernel_ulong_t)&ftdi_jtag_quirk }, - { USB_DEVICE(FTDI_VID, TI_XDS100V2_PID), - .driver_info = (kernel_ulong_t)&ftdi_jtag_quirk }, + { USB_DEVICE_INTERFACE_NUMBER(FTDI_VID, MARVELL_OPENRD_PID, 1) }, + { USB_DEVICE_INTERFACE_NUMBER(FTDI_VID, TI_XDS100V2_PID, 1) }, { USB_DEVICE(FTDI_VID, HAMEG_HO820_PID) }, { USB_DEVICE(FTDI_VID, HAMEG_HO720_PID) }, { USB_DEVICE(FTDI_VID, HAMEG_HO730_PID) }, @@ -946,18 +932,14 @@ static const struct usb_device_id id_table_combined[] = { { USB_DEVICE(FTDI_VID, MJSG_SR_RADIO_PID) }, { USB_DEVICE(FTDI_VID, MJSG_HD_RADIO_PID) }, { USB_DEVICE(FTDI_VID, MJSG_XM_RADIO_PID) }, - { USB_DEVICE(FTDI_VID, XVERVE_SIGNALYZER_ST_PID), - .driver_info = (kernel_ulong_t)&ftdi_jtag_quirk }, - { USB_DEVICE(FTDI_VID, XVERVE_SIGNALYZER_SLITE_PID), - .driver_info = (kernel_ulong_t)&ftdi_jtag_quirk }, - { USB_DEVICE(FTDI_VID, XVERVE_SIGNALYZER_SH2_PID), - .driver_info = (kernel_ulong_t)&ftdi_jtag_quirk }, + { USB_DEVICE_INTERFACE_NUMBER(FTDI_VID, XVERVE_SIGNALYZER_ST_PID, 1) }, + { USB_DEVICE_INTERFACE_NUMBER(FTDI_VID, XVERVE_SIGNALYZER_SLITE_PID, 1) }, + { USB_DEVICE_INTERFACE_NUMBER(FTDI_VID, XVERVE_SIGNALYZER_SH2_PID, 1) }, { USB_DEVICE(FTDI_VID, XVERVE_SIGNALYZER_SH4_PID), .driver_info = (kernel_ulong_t)&ftdi_jtag_quirk }, { USB_DEVICE(FTDI_VID, SEGWAY_RMP200_PID) }, { USB_DEVICE(FTDI_VID, ACCESIO_COM4SM_PID) }, - { USB_DEVICE(IONICS_VID, IONICS_PLUGCOMPUTER_PID), - .driver_info = (kernel_ulong_t)&ftdi_jtag_quirk }, + { USB_DEVICE_INTERFACE_NUMBER(IONICS_VID, IONICS_PLUGCOMPUTER_PID, 1) }, { USB_DEVICE(FTDI_VID, FTDI_CHAMSYS_24_MASTER_WING_PID) }, { USB_DEVICE(FTDI_VID, FTDI_CHAMSYS_PC_WING_PID) }, { USB_DEVICE(FTDI_VID, FTDI_CHAMSYS_USB_DMX_PID) }, @@ -972,15 +954,12 @@ static const struct usb_device_id id_table_combined[] = { { USB_DEVICE(FTDI_VID, FTDI_CINTERION_MC55I_PID) }, { USB_DEVICE(FTDI_VID, FTDI_FHE_PID) }, { USB_DEVICE(FTDI_VID, FTDI_DOTEC_PID) }, - { USB_DEVICE(QIHARDWARE_VID, MILKYMISTONE_JTAGSERIAL_PID), - .driver_info = (kernel_ulong_t)&ftdi_jtag_quirk }, - { USB_DEVICE(ST_VID, ST_STMCLT_2232_PID), - .driver_info = (kernel_ulong_t)&ftdi_jtag_quirk }, + { USB_DEVICE_INTERFACE_NUMBER(QIHARDWARE_VID, MILKYMISTONE_JTAGSERIAL_PID, 1) }, + { USB_DEVICE_INTERFACE_NUMBER(ST_VID, ST_STMCLT_2232_PID, 1) }, { USB_DEVICE(ST_VID, ST_STMCLT_4232_PID), .driver_info = (kernel_ulong_t)&ftdi_stmclite_quirk }, { USB_DEVICE(FTDI_VID, FTDI_RF_R106) }, - { USB_DEVICE(FTDI_VID, FTDI_DISTORTEC_JTAG_LOCK_PICK_PID), - .driver_info = (kernel_ulong_t)&ftdi_jtag_quirk }, + { USB_DEVICE_INTERFACE_NUMBER(FTDI_VID, FTDI_DISTORTEC_JTAG_LOCK_PICK_PID, 1) }, { USB_DEVICE(FTDI_VID, FTDI_LUMEL_PD12_PID) }, /* Crucible Devices */ { USB_DEVICE(FTDI_VID, FTDI_CT_COMET_PID) }, @@ -1055,8 +1034,7 @@ static const struct usb_device_id id_table_combined[] = { { USB_DEVICE(ICPDAS_VID, ICPDAS_I7561U_PID) }, { USB_DEVICE(ICPDAS_VID, ICPDAS_I7563U_PID) }, { USB_DEVICE(WICED_VID, WICED_USB20706V2_PID) }, - { USB_DEVICE(TI_VID, TI_CC3200_LAUNCHPAD_PID), - .driver_info = (kernel_ulong_t)&ftdi_jtag_quirk }, + { USB_DEVICE_INTERFACE_NUMBER(TI_VID, TI_CC3200_LAUNCHPAD_PID, 1) }, { USB_DEVICE(CYPRESS_VID, CYPRESS_WICED_BT_USB_PID) }, { USB_DEVICE(CYPRESS_VID, CYPRESS_WICED_WL_USB_PID) }, { USB_DEVICE(AIRBUS_DS_VID, AIRBUS_DS_P8GR) }, @@ -1076,10 +1054,8 @@ static const struct usb_device_id id_table_combined[] = { { USB_DEVICE(UBLOX_VID, UBLOX_C099F9P_ODIN_PID) }, { USB_DEVICE_INTERFACE_NUMBER(UBLOX_VID, UBLOX_EVK_M101_PID, 2) }, /* FreeCalypso USB adapters */ - { USB_DEVICE(FTDI_VID, FTDI_FALCONIA_JTAG_BUF_PID), - .driver_info = (kernel_ulong_t)&ftdi_jtag_quirk }, - { USB_DEVICE(FTDI_VID, FTDI_FALCONIA_JTAG_UNBUF_PID), - .driver_info = (kernel_ulong_t)&ftdi_jtag_quirk }, + { USB_DEVICE_INTERFACE_NUMBER(FTDI_VID, FTDI_FALCONIA_JTAG_BUF_PID, 1) }, + { USB_DEVICE_INTERFACE_NUMBER(FTDI_VID, FTDI_FALCONIA_JTAG_UNBUF_PID, 1) }, /* GMC devices */ { USB_DEVICE(GMC_VID, GMC_Z216C_PID) }, /* Altera USB Blaster 3 */ From 48e5794baf58c5ad789fd3301991eaa60417a6f0 Mon Sep 17 00:00:00 2001 From: Magne Bruno Date: Mon, 10 Nov 2025 17:24:56 +0100 Subject: [PATCH 0338/3902] serial: add support of CPCI cards commit 0e5a99e0e5f50353b86939ff6e424800d769c818 upstream. Addi-Data GmbH is manufacturing multi-serial ports cards supporting CompactPCI (known as CPCI). Those cards are identified with different DeviceIds. Those cards integrating standard UARTs work the same way as PCI/PCIe models already supported in the serial driver. Signed-off-by: Magne Bruno Link: https://patch.msgid.link/20251110162456.341029-1-magne.bruno@addi-data.com Cc: stable Signed-off-by: Greg Kroah-Hartman --- drivers/tty/serial/8250/8250_pci.c | 37 ++++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/drivers/tty/serial/8250/8250_pci.c b/drivers/tty/serial/8250/8250_pci.c index 152f914c599dc5..12e8ceffab65f2 100644 --- a/drivers/tty/serial/8250/8250_pci.c +++ b/drivers/tty/serial/8250/8250_pci.c @@ -95,6 +95,11 @@ #define PCI_DEVICE_ID_MOXA_CP138E_A 0x1381 #define PCI_DEVICE_ID_MOXA_CP168EL_A 0x1683 +#define PCI_DEVICE_ID_ADDIDATA_CPCI7500 0x7003 +#define PCI_DEVICE_ID_ADDIDATA_CPCI7500_NG 0x7024 +#define PCI_DEVICE_ID_ADDIDATA_CPCI7420_NG 0x7025 +#define PCI_DEVICE_ID_ADDIDATA_CPCI7300_NG 0x7026 + /* Unknown vendors/cards - this should not be in linux/pci_ids.h */ #define PCI_SUBDEVICE_ID_UNKNOWN_0x1584 0x1584 #define PCI_SUBDEVICE_ID_UNKNOWN_0x1588 0x1588 @@ -5996,6 +6001,38 @@ static const struct pci_device_id serial_pci_tbl[] = { 0, pbn_ADDIDATA_PCIe_8_3906250 }, + { PCI_VENDOR_ID_ADDIDATA, + PCI_DEVICE_ID_ADDIDATA_CPCI7500, + PCI_ANY_ID, + PCI_ANY_ID, + 0, + 0, + pbn_b0_4_115200 }, + + { PCI_VENDOR_ID_ADDIDATA, + PCI_DEVICE_ID_ADDIDATA_CPCI7500_NG, + PCI_ANY_ID, + PCI_ANY_ID, + 0, + 0, + pbn_b0_4_115200 }, + + { PCI_VENDOR_ID_ADDIDATA, + PCI_DEVICE_ID_ADDIDATA_CPCI7420_NG, + PCI_ANY_ID, + PCI_ANY_ID, + 0, + 0, + pbn_b0_2_115200 }, + + { PCI_VENDOR_ID_ADDIDATA, + PCI_DEVICE_ID_ADDIDATA_CPCI7300_NG, + PCI_ANY_ID, + PCI_ANY_ID, + 0, + 0, + pbn_b0_1_115200 }, + { PCI_VENDOR_ID_NETMOS, PCI_DEVICE_ID_NETMOS_9835, PCI_VENDOR_ID_IBM, 0x0299, 0, 0, pbn_b0_bt_2_115200 }, From a136ffb888bbb4b2a1e0f284dbc40d5cc399eb17 Mon Sep 17 00:00:00 2001 From: Biju Das Date: Fri, 14 Nov 2025 10:13:46 +0000 Subject: [PATCH 0339/3902] dt-bindings: serial: rsci: Drop "uart-has-rtscts: false" commit a6cdfd69ad38997108b862f9aafc547891506701 upstream. Drop "uart-has-rtscts: false" from binding as the IP supports hardware flow control on all SoCs. Cc: stable@kernel.org Fixes: 25422e8f46c1 ("dt-bindings: serial: Add compatible for Renesas RZ/T2H SoC in sci") Acked-by: Conor Dooley Reviewed-by: Geert Uytterhoeven Signed-off-by: Biju Das Link: https://patch.msgid.link/20251114101350.106699-2-biju.das.jz@bp.renesas.com Signed-off-by: Greg Kroah-Hartman --- Documentation/devicetree/bindings/serial/renesas,rsci.yaml | 2 -- 1 file changed, 2 deletions(-) diff --git a/Documentation/devicetree/bindings/serial/renesas,rsci.yaml b/Documentation/devicetree/bindings/serial/renesas,rsci.yaml index f50d8e02f47643..6b1f827a335b37 100644 --- a/Documentation/devicetree/bindings/serial/renesas,rsci.yaml +++ b/Documentation/devicetree/bindings/serial/renesas,rsci.yaml @@ -54,8 +54,6 @@ properties: power-domains: maxItems: 1 - uart-has-rtscts: false - required: - compatible - reg From a1210a4023aaef57cbaf66066c78b3eefbc68731 Mon Sep 17 00:00:00 2001 From: Biju Das Date: Fri, 14 Nov 2025 10:13:47 +0000 Subject: [PATCH 0340/3902] serial: sh-sci: Fix deadlock during RSCI FIFO overrun error commit 75a9f4c54770f062f4b3813a83667452b326dda3 upstream. On RSCI IP, a deadlock occurs during a FIFO overrun error, as it uses a different register to clear the FIFO overrun error status. Cc: stable@kernel.org Fixes: 0666e3fe95ab ("serial: sh-sci: Add support for RZ/T2H SCI") Signed-off-by: Biju Das Reviewed-by: Geert Uytterhoeven Link: https://patch.msgid.link/20251114101350.106699-3-biju.das.jz@bp.renesas.com Signed-off-by: Greg Kroah-Hartman --- drivers/tty/serial/sh-sci.c | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/drivers/tty/serial/sh-sci.c b/drivers/tty/serial/sh-sci.c index 62bb62b82cbe58..1db4af6fe59096 100644 --- a/drivers/tty/serial/sh-sci.c +++ b/drivers/tty/serial/sh-sci.c @@ -1024,8 +1024,16 @@ static int sci_handle_fifo_overrun(struct uart_port *port) status = s->ops->read_reg(port, s->params->overrun_reg); if (status & s->params->overrun_mask) { - status &= ~s->params->overrun_mask; - s->ops->write_reg(port, s->params->overrun_reg, status); + if (s->type == SCI_PORT_RSCI) { + /* + * All of the CFCLR_*C clearing bits match the corresponding + * CSR_*status bits. So, reuse the overrun mask for clearing. + */ + s->ops->clear_SCxSR(port, s->params->overrun_mask); + } else { + status &= ~s->params->overrun_mask; + s->ops->write_reg(port, s->params->overrun_reg, status); + } port->icount.overrun++; From 1888f286b7afeeb7a66eb30f46c9053ac8c0b25d Mon Sep 17 00:00:00 2001 From: Johan Hovold Date: Wed, 22 Oct 2025 17:26:33 +0200 Subject: [PATCH 0341/3902] USB: serial: belkin_sa: fix TIOCMBIS and TIOCMBIC commit b6e0b3016187446ddef9edac03cd9d544ac63f11 upstream. Asserting or deasserting a modem control line using TIOCMBIS or TIOCMBIC should not deassert any lines that are not in the mask. Fix this long-standing regression dating back to 2003 when the tiocmset() callback was introduced. Fixes: 1da177e4c3f4 ("Linux-2.6.12-rc2") Cc: stable@vger.kernel.org Reviewed-by: Greg Kroah-Hartman Signed-off-by: Johan Hovold Signed-off-by: Greg Kroah-Hartman --- drivers/usb/serial/belkin_sa.c | 28 +++++++++++++++++----------- 1 file changed, 17 insertions(+), 11 deletions(-) diff --git a/drivers/usb/serial/belkin_sa.c b/drivers/usb/serial/belkin_sa.c index 44f5b58beec922..aa6b4c4ad5ecbe 100644 --- a/drivers/usb/serial/belkin_sa.c +++ b/drivers/usb/serial/belkin_sa.c @@ -435,7 +435,7 @@ static int belkin_sa_tiocmset(struct tty_struct *tty, struct belkin_sa_private *priv = usb_get_serial_port_data(port); unsigned long control_state; unsigned long flags; - int retval; + int retval = 0; int rts = 0; int dtr = 0; @@ -452,26 +452,32 @@ static int belkin_sa_tiocmset(struct tty_struct *tty, } if (clear & TIOCM_RTS) { control_state &= ~TIOCM_RTS; - rts = 0; + rts = 1; } if (clear & TIOCM_DTR) { control_state &= ~TIOCM_DTR; - dtr = 0; + dtr = 1; } priv->control_state = control_state; spin_unlock_irqrestore(&priv->lock, flags); - retval = BSA_USB_CMD(BELKIN_SA_SET_RTS_REQUEST, rts); - if (retval < 0) { - dev_err(&port->dev, "Set RTS error %d\n", retval); - goto exit; + if (rts) { + retval = BSA_USB_CMD(BELKIN_SA_SET_RTS_REQUEST, + !!(control_state & TIOCM_RTS)); + if (retval < 0) { + dev_err(&port->dev, "Set RTS error %d\n", retval); + goto exit; + } } - retval = BSA_USB_CMD(BELKIN_SA_SET_DTR_REQUEST, dtr); - if (retval < 0) { - dev_err(&port->dev, "Set DTR error %d\n", retval); - goto exit; + if (dtr) { + retval = BSA_USB_CMD(BELKIN_SA_SET_DTR_REQUEST, + !!(control_state & TIOCM_DTR)); + if (retval < 0) { + dev_err(&port->dev, "Set DTR error %d\n", retval); + goto exit; + } } exit: return retval; From e7edecc410886b75e9dd2b29391e1be4a6423f0a Mon Sep 17 00:00:00 2001 From: Johan Hovold Date: Wed, 22 Oct 2025 17:26:34 +0200 Subject: [PATCH 0342/3902] USB: serial: kobil_sct: fix TIOCMBIS and TIOCMBIC commit d432df758f92c4c28aac409bc807fd1716167577 upstream. Asserting or deasserting a modem control line using TIOCMBIS or TIOCMBIC should not deassert any lines that are not in the mask. Fix this long-standing issue dating back to 2003 when the support for these ioctls was added with the introduction of the tiocmset() callback. Fixes: 1da177e4c3f4 ("Linux-2.6.12-rc2") Cc: stable@vger.kernel.org Reviewed-by: Greg Kroah-Hartman Signed-off-by: Johan Hovold Signed-off-by: Greg Kroah-Hartman --- drivers/usb/serial/kobil_sct.c | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/drivers/usb/serial/kobil_sct.c b/drivers/usb/serial/kobil_sct.c index 464433be203443..96ea571c436a71 100644 --- a/drivers/usb/serial/kobil_sct.c +++ b/drivers/usb/serial/kobil_sct.c @@ -418,7 +418,7 @@ static int kobil_tiocmset(struct tty_struct *tty, struct usb_serial_port *port = tty->driver_data; struct device *dev = &port->dev; struct kobil_private *priv; - int result; + int result = 0; int dtr = 0; int rts = 0; @@ -435,12 +435,12 @@ static int kobil_tiocmset(struct tty_struct *tty, if (set & TIOCM_DTR) dtr = 1; if (clear & TIOCM_RTS) - rts = 0; + rts = 1; if (clear & TIOCM_DTR) - dtr = 0; + dtr = 1; - if (priv->device_type == KOBIL_ADAPTER_B_PRODUCT_ID) { - if (dtr != 0) + if (dtr && priv->device_type == KOBIL_ADAPTER_B_PRODUCT_ID) { + if (set & TIOCM_DTR) dev_dbg(dev, "%s - Setting DTR\n", __func__); else dev_dbg(dev, "%s - Clearing DTR\n", __func__); @@ -448,13 +448,13 @@ static int kobil_tiocmset(struct tty_struct *tty, usb_sndctrlpipe(port->serial->dev, 0), SUSBCRequest_SetStatusLinesOrQueues, USB_TYPE_VENDOR | USB_RECIP_ENDPOINT | USB_DIR_OUT, - ((dtr != 0) ? SUSBCR_SSL_SETDTR : SUSBCR_SSL_CLRDTR), + ((set & TIOCM_DTR) ? SUSBCR_SSL_SETDTR : SUSBCR_SSL_CLRDTR), 0, NULL, 0, KOBIL_TIMEOUT); - } else { - if (rts != 0) + } else if (rts) { + if (set & TIOCM_RTS) dev_dbg(dev, "%s - Setting RTS\n", __func__); else dev_dbg(dev, "%s - Clearing RTS\n", __func__); @@ -462,7 +462,7 @@ static int kobil_tiocmset(struct tty_struct *tty, usb_sndctrlpipe(port->serial->dev, 0), SUSBCRequest_SetStatusLinesOrQueues, USB_TYPE_VENDOR | USB_RECIP_ENDPOINT | USB_DIR_OUT, - ((rts != 0) ? SUSBCR_SSL_SETRTS : SUSBCR_SSL_CLRRTS), + ((set & TIOCM_RTS) ? SUSBCR_SSL_SETRTS : SUSBCR_SSL_CLRRTS), 0, NULL, 0, From ce1d0f5c7b428007973c3d7f97329a5349d9fe33 Mon Sep 17 00:00:00 2001 From: Gopi Krishna Menon Date: Mon, 13 Oct 2025 16:27:20 +0700 Subject: [PATCH 0343/3902] Documentation/rtla: rename common_xxx.rst files to common_xxx.txt commit 96b546c241b11a97ba1247580208c554458e7866 upstream. Sphinx reports htmldocs errors: Documentation/tools/rtla/common_options.rst:58: ERROR: Undefined substitution referenced: "threshold". Documentation/tools/rtla/common_options.rst:88: ERROR: Undefined substitution referenced: "tool". Documentation/tools/rtla/common_options.rst:88: ERROR: Undefined substitution referenced: "thresharg". Documentation/tools/rtla/common_options.rst:88: ERROR: Undefined substitution referenced: "tracer". Documentation/tools/rtla/common_options.rst:92: ERROR: Undefined substitution referenced: "tracer". Documentation/tools/rtla/common_options.rst:98: ERROR: Undefined substitution referenced: "actionsperf". Documentation/tools/rtla/common_options.rst:113: ERROR: Undefined substitution referenced: "tool". common_*.rst files are snippets that are intended to be included by rtla docs (rtla*.rst). common_options.rst in particular contains substitutions which depend on other common_* includes, so building it independently as reST source results in above errors. Rename all common_*.rst files to common_*.txt to prevent Sphinx from building these snippets as standalone reST source and update all include references accordingly. Link: https://www.sphinx-doc.org/en/master/usage/restructuredtext/basics.html#substitutions Suggested-by: Tomas Glozar Suggested-by: Bagas Sanjaya Signed-off-by: Gopi Krishna Menon Reviewed-by: Tomas Glozar Fixes: 05b7e10687c6 ("tools/rtla: Add remaining support for osnoise actions") Reviewed-by: Bagas Sanjaya Link: https://lore.kernel.org/r/20251008184522.13201-1-krishnagopi487@gmail.com [Bagas: massage commit message and apply trailers] Signed-off-by: Bagas Sanjaya Reviewed-by: Randy Dunlap Tested-by: Randy Dunlap Signed-off-by: Jonathan Corbet Message-ID: <20251013092719.30780-2-bagasdotme@gmail.com> Signed-off-by: Bagas Sanjaya Signed-off-by: Greg Kroah-Hartman --- .../{common_appendix.rst => common_appendix.txt} | 0 ...mmon_hist_options.rst => common_hist_options.txt} | 0 .../rtla/{common_options.rst => common_options.txt} | 0 ...escription.rst => common_osnoise_description.txt} | 0 ...snoise_options.rst => common_osnoise_options.txt} | 0 ...common_timerlat_aa.rst => common_timerlat_aa.txt} | 0 ...scription.rst => common_timerlat_description.txt} | 0 ...erlat_options.rst => common_timerlat_options.txt} | 0 ...common_top_options.rst => common_top_options.txt} | 0 Documentation/tools/rtla/rtla-hwnoise.rst | 8 ++++---- Documentation/tools/rtla/rtla-osnoise-hist.rst | 10 +++++----- Documentation/tools/rtla/rtla-osnoise-top.rst | 10 +++++----- Documentation/tools/rtla/rtla-osnoise.rst | 4 ++-- Documentation/tools/rtla/rtla-timerlat-hist.rst | 12 ++++++------ Documentation/tools/rtla/rtla-timerlat-top.rst | 12 ++++++------ Documentation/tools/rtla/rtla-timerlat.rst | 4 ++-- Documentation/tools/rtla/rtla.rst | 2 +- 17 files changed, 31 insertions(+), 31 deletions(-) rename Documentation/tools/rtla/{common_appendix.rst => common_appendix.txt} (100%) rename Documentation/tools/rtla/{common_hist_options.rst => common_hist_options.txt} (100%) rename Documentation/tools/rtla/{common_options.rst => common_options.txt} (100%) rename Documentation/tools/rtla/{common_osnoise_description.rst => common_osnoise_description.txt} (100%) rename Documentation/tools/rtla/{common_osnoise_options.rst => common_osnoise_options.txt} (100%) rename Documentation/tools/rtla/{common_timerlat_aa.rst => common_timerlat_aa.txt} (100%) rename Documentation/tools/rtla/{common_timerlat_description.rst => common_timerlat_description.txt} (100%) rename Documentation/tools/rtla/{common_timerlat_options.rst => common_timerlat_options.txt} (100%) rename Documentation/tools/rtla/{common_top_options.rst => common_top_options.txt} (100%) diff --git a/Documentation/tools/rtla/common_appendix.rst b/Documentation/tools/rtla/common_appendix.txt similarity index 100% rename from Documentation/tools/rtla/common_appendix.rst rename to Documentation/tools/rtla/common_appendix.txt diff --git a/Documentation/tools/rtla/common_hist_options.rst b/Documentation/tools/rtla/common_hist_options.txt similarity index 100% rename from Documentation/tools/rtla/common_hist_options.rst rename to Documentation/tools/rtla/common_hist_options.txt diff --git a/Documentation/tools/rtla/common_options.rst b/Documentation/tools/rtla/common_options.txt similarity index 100% rename from Documentation/tools/rtla/common_options.rst rename to Documentation/tools/rtla/common_options.txt diff --git a/Documentation/tools/rtla/common_osnoise_description.rst b/Documentation/tools/rtla/common_osnoise_description.txt similarity index 100% rename from Documentation/tools/rtla/common_osnoise_description.rst rename to Documentation/tools/rtla/common_osnoise_description.txt diff --git a/Documentation/tools/rtla/common_osnoise_options.rst b/Documentation/tools/rtla/common_osnoise_options.txt similarity index 100% rename from Documentation/tools/rtla/common_osnoise_options.rst rename to Documentation/tools/rtla/common_osnoise_options.txt diff --git a/Documentation/tools/rtla/common_timerlat_aa.rst b/Documentation/tools/rtla/common_timerlat_aa.txt similarity index 100% rename from Documentation/tools/rtla/common_timerlat_aa.rst rename to Documentation/tools/rtla/common_timerlat_aa.txt diff --git a/Documentation/tools/rtla/common_timerlat_description.rst b/Documentation/tools/rtla/common_timerlat_description.txt similarity index 100% rename from Documentation/tools/rtla/common_timerlat_description.rst rename to Documentation/tools/rtla/common_timerlat_description.txt diff --git a/Documentation/tools/rtla/common_timerlat_options.rst b/Documentation/tools/rtla/common_timerlat_options.txt similarity index 100% rename from Documentation/tools/rtla/common_timerlat_options.rst rename to Documentation/tools/rtla/common_timerlat_options.txt diff --git a/Documentation/tools/rtla/common_top_options.rst b/Documentation/tools/rtla/common_top_options.txt similarity index 100% rename from Documentation/tools/rtla/common_top_options.rst rename to Documentation/tools/rtla/common_top_options.txt diff --git a/Documentation/tools/rtla/rtla-hwnoise.rst b/Documentation/tools/rtla/rtla-hwnoise.rst index 3a7163c02ac8e8..26512b15fe7ba5 100644 --- a/Documentation/tools/rtla/rtla-hwnoise.rst +++ b/Documentation/tools/rtla/rtla-hwnoise.rst @@ -29,11 +29,11 @@ collection of the tracer output. OPTIONS ======= -.. include:: common_osnoise_options.rst +.. include:: common_osnoise_options.txt -.. include:: common_top_options.rst +.. include:: common_top_options.txt -.. include:: common_options.rst +.. include:: common_options.txt EXAMPLE ======= @@ -106,4 +106,4 @@ AUTHOR ====== Written by Daniel Bristot de Oliveira -.. include:: common_appendix.rst +.. include:: common_appendix.txt diff --git a/Documentation/tools/rtla/rtla-osnoise-hist.rst b/Documentation/tools/rtla/rtla-osnoise-hist.rst index 1fc60ef2610677..007521c865d97e 100644 --- a/Documentation/tools/rtla/rtla-osnoise-hist.rst +++ b/Documentation/tools/rtla/rtla-osnoise-hist.rst @@ -15,7 +15,7 @@ SYNOPSIS DESCRIPTION =========== -.. include:: common_osnoise_description.rst +.. include:: common_osnoise_description.txt The **rtla osnoise hist** tool collects all **osnoise:sample_threshold** occurrence in a histogram, displaying the results in a user-friendly way. @@ -24,11 +24,11 @@ collection of the tracer output. OPTIONS ======= -.. include:: common_osnoise_options.rst +.. include:: common_osnoise_options.txt -.. include:: common_hist_options.rst +.. include:: common_hist_options.txt -.. include:: common_options.rst +.. include:: common_options.txt EXAMPLE ======= @@ -65,4 +65,4 @@ AUTHOR ====== Written by Daniel Bristot de Oliveira -.. include:: common_appendix.rst +.. include:: common_appendix.txt diff --git a/Documentation/tools/rtla/rtla-osnoise-top.rst b/Documentation/tools/rtla/rtla-osnoise-top.rst index b1cbd7bcd4aed2..6ccadae3894570 100644 --- a/Documentation/tools/rtla/rtla-osnoise-top.rst +++ b/Documentation/tools/rtla/rtla-osnoise-top.rst @@ -15,7 +15,7 @@ SYNOPSIS DESCRIPTION =========== -.. include:: common_osnoise_description.rst +.. include:: common_osnoise_description.txt **rtla osnoise top** collects the periodic summary from the *osnoise* tracer, including the counters of the occurrence of the interference source, @@ -26,11 +26,11 @@ collection of the tracer output. OPTIONS ======= -.. include:: common_osnoise_options.rst +.. include:: common_osnoise_options.txt -.. include:: common_top_options.rst +.. include:: common_top_options.txt -.. include:: common_options.rst +.. include:: common_options.txt EXAMPLE ======= @@ -60,4 +60,4 @@ AUTHOR ====== Written by Daniel Bristot de Oliveira -.. include:: common_appendix.rst +.. include:: common_appendix.txt diff --git a/Documentation/tools/rtla/rtla-osnoise.rst b/Documentation/tools/rtla/rtla-osnoise.rst index c129b206ce3484..540d2bf6c15247 100644 --- a/Documentation/tools/rtla/rtla-osnoise.rst +++ b/Documentation/tools/rtla/rtla-osnoise.rst @@ -14,7 +14,7 @@ SYNOPSIS DESCRIPTION =========== -.. include:: common_osnoise_description.rst +.. include:: common_osnoise_description.txt The *osnoise* tracer outputs information in two ways. It periodically prints a summary of the noise of the operating system, including the counters of @@ -56,4 +56,4 @@ AUTHOR ====== Written by Daniel Bristot de Oliveira -.. include:: common_appendix.rst +.. include:: common_appendix.txt diff --git a/Documentation/tools/rtla/rtla-timerlat-hist.rst b/Documentation/tools/rtla/rtla-timerlat-hist.rst index 4923a362129bbd..f56fe546411bd4 100644 --- a/Documentation/tools/rtla/rtla-timerlat-hist.rst +++ b/Documentation/tools/rtla/rtla-timerlat-hist.rst @@ -16,7 +16,7 @@ SYNOPSIS DESCRIPTION =========== -.. include:: common_timerlat_description.rst +.. include:: common_timerlat_description.txt The **rtla timerlat hist** displays a histogram of each tracer event occurrence. This tool uses the periodic information, and the @@ -25,13 +25,13 @@ occurrence. This tool uses the periodic information, and the OPTIONS ======= -.. include:: common_timerlat_options.rst +.. include:: common_timerlat_options.txt -.. include:: common_hist_options.rst +.. include:: common_hist_options.txt -.. include:: common_options.rst +.. include:: common_options.txt -.. include:: common_timerlat_aa.rst +.. include:: common_timerlat_aa.txt EXAMPLE ======= @@ -110,4 +110,4 @@ AUTHOR ====== Written by Daniel Bristot de Oliveira -.. include:: common_appendix.rst +.. include:: common_appendix.txt diff --git a/Documentation/tools/rtla/rtla-timerlat-top.rst b/Documentation/tools/rtla/rtla-timerlat-top.rst index 50968cdd2095a1..7dbe625d0c4243 100644 --- a/Documentation/tools/rtla/rtla-timerlat-top.rst +++ b/Documentation/tools/rtla/rtla-timerlat-top.rst @@ -16,7 +16,7 @@ SYNOPSIS DESCRIPTION =========== -.. include:: common_timerlat_description.rst +.. include:: common_timerlat_description.txt The **rtla timerlat top** displays a summary of the periodic output from the *timerlat* tracer. It also provides information for each @@ -26,13 +26,13 @@ seem with the option **-T**. OPTIONS ======= -.. include:: common_timerlat_options.rst +.. include:: common_timerlat_options.txt -.. include:: common_top_options.rst +.. include:: common_top_options.txt -.. include:: common_options.rst +.. include:: common_options.txt -.. include:: common_timerlat_aa.rst +.. include:: common_timerlat_aa.txt **--aa-only** *us* @@ -133,4 +133,4 @@ AUTHOR ------ Written by Daniel Bristot de Oliveira -.. include:: common_appendix.rst +.. include:: common_appendix.txt diff --git a/Documentation/tools/rtla/rtla-timerlat.rst b/Documentation/tools/rtla/rtla-timerlat.rst index 20e2d259467fd0..ce9f57e038c37f 100644 --- a/Documentation/tools/rtla/rtla-timerlat.rst +++ b/Documentation/tools/rtla/rtla-timerlat.rst @@ -14,7 +14,7 @@ SYNOPSIS DESCRIPTION =========== -.. include:: common_timerlat_description.rst +.. include:: common_timerlat_description.txt The **rtla timerlat top** mode displays a summary of the periodic output from the *timerlat* tracer. The **rtla timerlat hist** mode displays @@ -51,4 +51,4 @@ AUTHOR ====== Written by Daniel Bristot de Oliveira -.. include:: common_appendix.rst +.. include:: common_appendix.txt diff --git a/Documentation/tools/rtla/rtla.rst b/Documentation/tools/rtla/rtla.rst index fc0d233efcd5df..2a5fb7004ad448 100644 --- a/Documentation/tools/rtla/rtla.rst +++ b/Documentation/tools/rtla/rtla.rst @@ -45,4 +45,4 @@ AUTHOR ====== Daniel Bristot de Oliveira -.. include:: common_appendix.rst +.. include:: common_appendix.txt From f75b9d2c9f5b53cc3acacef62326417cd97e5e39 Mon Sep 17 00:00:00 2001 From: Zenm Chen Date: Mon, 29 Sep 2025 11:57:18 +0800 Subject: [PATCH 0344/3902] wifi: rtl8xxxu: Add USB ID 2001:3328 for D-Link AN3U rev. A1 commit 3f9553f65d0b77b724565bbe42c4daa3fab57d5c upstream. Add USB ID 2001:3328 for D-Link AN3U rev. A1 which is a RTL8192FU-based Wi-Fi adapter. Compile tested only. Cc: stable@vger.kernel.org # 6.6.x Signed-off-by: Zenm Chen Reviewed-by: Ping-Ke Shih Signed-off-by: Ping-Ke Shih Link: https://patch.msgid.link/20250929035719.6172-1-zenmchen@gmail.com Signed-off-by: Greg Kroah-Hartman --- drivers/net/wireless/realtek/rtl8xxxu/core.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/drivers/net/wireless/realtek/rtl8xxxu/core.c b/drivers/net/wireless/realtek/rtl8xxxu/core.c index 3ded5952729fc6..be39463bd6c464 100644 --- a/drivers/net/wireless/realtek/rtl8xxxu/core.c +++ b/drivers/net/wireless/realtek/rtl8xxxu/core.c @@ -8136,6 +8136,9 @@ static const struct usb_device_id dev_table[] = { /* TP-Link TL-WN823N V2 */ {USB_DEVICE_AND_INTERFACE_INFO(0x2357, 0x0135, 0xff, 0xff, 0xff), .driver_info = (unsigned long)&rtl8192fu_fops}, +/* D-Link AN3U rev. A1 */ +{USB_DEVICE_AND_INTERFACE_INFO(0x2001, 0x3328, 0xff, 0xff, 0xff), + .driver_info = (unsigned long)&rtl8192fu_fops}, #ifdef CONFIG_RTL8XXXU_UNTESTED /* Still supported by rtlwifi */ {USB_DEVICE_AND_INTERFACE_INFO(USB_VENDOR_ID_REALTEK, 0x8176, 0xff, 0xff, 0xff), From 401b10a72f5a69ecdb5f57d554ab9e04a19268f1 Mon Sep 17 00:00:00 2001 From: Zenm Chen Date: Mon, 29 Sep 2025 11:57:19 +0800 Subject: [PATCH 0345/3902] wifi: rtw88: Add USB ID 2001:3329 for D-Link AC13U rev. A1 commit b377dcd9a286a6f81922ae442cd1c743bc4a2b35 upstream. Add USB ID 2001:3329 for D-Link AC13U rev. A1 which is a RTL8812CU-based Wi-Fi adapter. Compile tested only. Cc: stable@vger.kernel.org # 6.6.x Signed-off-by: Zenm Chen Acked-by: Ping-Ke Shih Signed-off-by: Ping-Ke Shih Link: https://patch.msgid.link/20250929035719.6172-2-zenmchen@gmail.com Signed-off-by: Greg Kroah-Hartman --- drivers/net/wireless/realtek/rtw88/rtw8822cu.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/drivers/net/wireless/realtek/rtw88/rtw8822cu.c b/drivers/net/wireless/realtek/rtw88/rtw8822cu.c index 324fd5c8bfd440..755f76840b1210 100644 --- a/drivers/net/wireless/realtek/rtw88/rtw8822cu.c +++ b/drivers/net/wireless/realtek/rtw88/rtw8822cu.c @@ -21,6 +21,8 @@ static const struct usb_device_id rtw_8822cu_id_table[] = { .driver_info = (kernel_ulong_t)&(rtw8822c_hw_spec) }, { USB_DEVICE_AND_INTERFACE_INFO(0x13b1, 0x0043, 0xff, 0xff, 0xff), .driver_info = (kernel_ulong_t)&(rtw8822c_hw_spec) }, /* Alpha - Alpha */ + { USB_DEVICE_AND_INTERFACE_INFO(0x2001, 0x3329, 0xff, 0xff, 0xff), + .driver_info = (kernel_ulong_t)&(rtw8822c_hw_spec) }, /* D-Link AC13U rev. A1 */ {}, }; MODULE_DEVICE_TABLE(usb, rtw_8822cu_id_table); From 4e6a40977495118373ce0e5d6b3ba4fe7d16ba17 Mon Sep 17 00:00:00 2001 From: Antoniu Miclaus Date: Tue, 7 Oct 2025 11:15:20 +0000 Subject: [PATCH 0346/3902] iio: adc: ad4080: fix chip identification MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit commit b66cddc8be7278fd14650ff9182f3794397f8b31 upstream. Fix AD4080 chip identification by using the correct 16-bit product ID (0x0050) instead of GENMASK(2, 0). Update the chip reading logic to use regmap_bulk_read to read both PRODUCT_ID_L and PRODUCT_ID_H registers and combine them into a 16-bit value. The original implementation was incorrectly reading only 3 bits, which would not correctly identify the AD4080 chip. Fixes: 6b31ba1811b6 ("iio: adc: ad4080: add driver support") Signed-off-by: Antoniu Miclaus Reviewed-by: Nuno Sá Cc: Signed-off-by: Jonathan Cameron Signed-off-by: Greg Kroah-Hartman --- drivers/iio/adc/ad4080.c | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/drivers/iio/adc/ad4080.c b/drivers/iio/adc/ad4080.c index 6e61787ed3213f..e15310fcd21a36 100644 --- a/drivers/iio/adc/ad4080.c +++ b/drivers/iio/adc/ad4080.c @@ -125,7 +125,7 @@ /* Miscellaneous Definitions */ #define AD4080_SPI_READ BIT(7) -#define AD4080_CHIP_ID GENMASK(2, 0) +#define AD4080_CHIP_ID 0x0050 #define AD4080_LVDS_CNV_CLK_CNT_MAX 7 @@ -445,7 +445,8 @@ static int ad4080_setup(struct iio_dev *indio_dev) { struct ad4080_state *st = iio_priv(indio_dev); struct device *dev = regmap_get_device(st->regmap); - unsigned int id; + __le16 id_le; + u16 id; int ret; ret = regmap_write(st->regmap, AD4080_REG_INTERFACE_CONFIG_A, @@ -458,10 +459,12 @@ static int ad4080_setup(struct iio_dev *indio_dev) if (ret) return ret; - ret = regmap_read(st->regmap, AD4080_REG_CHIP_TYPE, &id); + ret = regmap_bulk_read(st->regmap, AD4080_REG_PRODUCT_ID_L, &id_le, + sizeof(id_le)); if (ret) return ret; + id = le16_to_cpu(id_le); if (id != AD4080_CHIP_ID) dev_info(dev, "Unrecognized CHIP_ID 0x%X\n", id); From 888f7e2847bcb9df8257e656e1e837828942c53b Mon Sep 17 00:00:00 2001 From: Ian Abbott Date: Thu, 23 Oct 2025 13:31:41 +0100 Subject: [PATCH 0347/3902] comedi: c6xdigio: Fix invalid PNP driver unregistration commit 72262330f7b3ad2130e800cecf02adcce3c32c77 upstream. The Comedi low-level driver "c6xdigio" seems to be for a parallel port connected device. When the Comedi core calls the driver's Comedi "attach" handler `c6xdigio_attach()` to configure a Comedi to use this driver, it tries to enable the parallel port PNP resources by registering a PNP driver with `pnp_register_driver()`, but ignores the return value. (The `struct pnp_driver` it uses has only the `name` and `id_table` members filled in.) The driver's Comedi "detach" handler `c6xdigio_detach()` unconditionally unregisters the PNP driver with `pnp_unregister_driver()`. It is possible for `c6xdigio_attach()` to return an error before it calls `pnp_register_driver()` and it is possible for the call to `pnp_register_driver()` to return an error (that is ignored). In both cases, the driver should not be calling `pnp_unregister_driver()` as it does in `c6xdigio_detach()`. (Note that `c6xdigio_detach()` will be called by the Comedi core if `c6xdigio_attach()` returns an error, or if the Comedi core decides to detach the Comedi device from the driver for some other reason.) The unconditional call to `pnp_unregister_driver()` without a previous successful call to `pnp_register_driver()` will cause `driver_unregister()` to issue a warning "Unexpected driver unregister!". This was detected by Syzbot [1]. Also, the PNP driver registration and unregistration should be done at module init and exit time, respectively, not when attaching or detaching Comedi devices to the driver. (There might be more than one Comedi device being attached to the driver, although that is unlikely.) Change the driver to do the PNP driver registration at module init time, and the unregistration at module exit time. Since `c6xdigio_detach()` now only calls `comedi_legacy_detach()`, remove the function and change the Comedi driver "detach" handler to `comedi_legacy_detach`. ------------------------------------------- [1] Syzbot sample crash report: Unexpected driver unregister! WARNING: CPU: 0 PID: 5970 at drivers/base/driver.c:273 driver_unregister drivers/base/driver.c:273 [inline] WARNING: CPU: 0 PID: 5970 at drivers/base/driver.c:273 driver_unregister+0x90/0xb0 drivers/base/driver.c:270 Modules linked in: CPU: 0 UID: 0 PID: 5970 Comm: syz.0.17 Not tainted syzkaller #0 PREEMPT(full) Hardware name: Google Google Compute Engine/Google Compute Engine, BIOS Google 10/02/2025 RIP: 0010:driver_unregister drivers/base/driver.c:273 [inline] RIP: 0010:driver_unregister+0x90/0xb0 drivers/base/driver.c:270 Code: 48 89 ef e8 c2 e6 82 fc 48 89 df e8 3a 93 ff ff 5b 5d e9 c3 6d d9 fb e8 be 6d d9 fb 90 48 c7 c7 e0 f8 1f 8c e8 51 a2 97 fb 90 <0f> 0b 90 90 5b 5d e9 a5 6d d9 fb e8 e0 f4 41 fc eb 94 e8 d9 f4 41 RSP: 0018:ffffc9000373f9a0 EFLAGS: 00010282 RAX: 0000000000000000 RBX: ffffffff8ff24720 RCX: ffffffff817b6ee8 RDX: ffff88807c932480 RSI: ffffffff817b6ef5 RDI: 0000000000000001 RBP: 0000000000000000 R08: 0000000000000001 R09: 0000000000000000 R10: 0000000000000001 R11: 0000000000000001 R12: ffffffff8ff24660 R13: dffffc0000000000 R14: 0000000000000000 R15: ffff88814cca0000 FS: 000055556dab1500(0000) GS:ffff8881249d9000(0000) knlGS:0000000000000000 CS: 0010 DS: 0000 ES: 0000 CR0: 0000000080050033 CR2: 000055f77f285cd0 CR3: 000000007d871000 CR4: 00000000003526f0 Call Trace: comedi_device_detach_locked+0x12f/0xa50 drivers/comedi/drivers.c:207 comedi_device_detach+0x67/0xb0 drivers/comedi/drivers.c:215 comedi_device_attach+0x43d/0x900 drivers/comedi/drivers.c:1011 do_devconfig_ioctl+0x1b1/0x710 drivers/comedi/comedi_fops.c:872 comedi_unlocked_ioctl+0x165d/0x2f00 drivers/comedi/comedi_fops.c:2178 vfs_ioctl fs/ioctl.c:51 [inline] __do_sys_ioctl fs/ioctl.c:597 [inline] __se_sys_ioctl fs/ioctl.c:583 [inline] __x64_sys_ioctl+0x18e/0x210 fs/ioctl.c:583 do_syscall_x64 arch/x86/entry/syscall_64.c:63 [inline] do_syscall_64+0xcd/0xfa0 arch/x86/entry/syscall_64.c:94 entry_SYSCALL_64_after_hwframe+0x77/0x7f RIP: 0033:0x7fc05798eec9 Code: ff ff c3 66 2e 0f 1f 84 00 00 00 00 00 0f 1f 40 00 48 89 f8 48 89 f7 48 89 d6 48 89 ca 4d 89 c2 4d 89 c8 4c 8b 4c 24 08 0f 05 <48> 3d 01 f0 ff ff 73 01 c3 48 c7 c1 a8 ff ff ff f7 d8 64 89 01 48 RSP: 002b:00007ffcf8184238 EFLAGS: 00000246 ORIG_RAX: 0000000000000010 RAX: ffffffffffffffda RBX: 00007fc057be5fa0 RCX: 00007fc05798eec9 RDX: 0000200000000080 RSI: 0000000040946400 RDI: 0000000000000003 RBP: 00007fc057a11f91 R08: 0000000000000000 R09: 0000000000000000 R10: 0000000000000000 R11: 0000000000000246 R12: 0000000000000000 R13: 00007fc057be5fa0 R14: 00007fc057be5fa0 R15: 0000000000000003 ------------------------------------------- Reported-by: syzbot+6616bba359cec7a1def1@syzkaller.appspotmail.com Closes: https://syzkaller.appspot.com/bug?extid=6616bba359cec7a1def1 Fixes: 2c89e159cd2f ("Staging: comedi: add c6xdigio driver") Cc: stable Signed-off-by: Ian Abbott Link: https://patch.msgid.link/20251023123141.6537-1-abbotti@mev.co.uk Signed-off-by: Greg Kroah-Hartman --- drivers/comedi/drivers/c6xdigio.c | 46 +++++++++++++++++++++++-------- 1 file changed, 35 insertions(+), 11 deletions(-) diff --git a/drivers/comedi/drivers/c6xdigio.c b/drivers/comedi/drivers/c6xdigio.c index 14b90d1c64dc1c..8a38d97d463b2e 100644 --- a/drivers/comedi/drivers/c6xdigio.c +++ b/drivers/comedi/drivers/c6xdigio.c @@ -249,9 +249,6 @@ static int c6xdigio_attach(struct comedi_device *dev, if (ret) return ret; - /* Make sure that PnP ports get activated */ - pnp_register_driver(&c6xdigio_pnp_driver); - s = &dev->subdevices[0]; /* pwm output subdevice */ s->type = COMEDI_SUBD_PWM; @@ -278,19 +275,46 @@ static int c6xdigio_attach(struct comedi_device *dev, return 0; } -static void c6xdigio_detach(struct comedi_device *dev) -{ - comedi_legacy_detach(dev); - pnp_unregister_driver(&c6xdigio_pnp_driver); -} - static struct comedi_driver c6xdigio_driver = { .driver_name = "c6xdigio", .module = THIS_MODULE, .attach = c6xdigio_attach, - .detach = c6xdigio_detach, + .detach = comedi_legacy_detach, }; -module_comedi_driver(c6xdigio_driver); + +static bool c6xdigio_pnp_registered = false; + +static int __init c6xdigio_module_init(void) +{ + int ret; + + ret = comedi_driver_register(&c6xdigio_driver); + if (ret) + return ret; + + if (IS_ENABLED(CONFIG_PNP)) { + /* Try to activate the PnP ports */ + ret = pnp_register_driver(&c6xdigio_pnp_driver); + if (ret) { + pr_warn("failed to register pnp driver - err %d\n", + ret); + ret = 0; /* ignore the error. */ + } else { + c6xdigio_pnp_registered = true; + } + } + + return 0; +} +module_init(c6xdigio_module_init); + +static void __exit c6xdigio_module_exit(void) +{ + if (c6xdigio_pnp_registered) + pnp_unregister_driver(&c6xdigio_pnp_driver); + comedi_driver_unregister(&c6xdigio_driver); +} +module_exit(c6xdigio_module_exit); MODULE_AUTHOR("Comedi https://www.comedi.org"); MODULE_DESCRIPTION("Comedi driver for the C6x_DIGIO DSP daughter card"); From 543f4c380c2e1f35e60528df7cb54705cda7fee3 Mon Sep 17 00:00:00 2001 From: Nikita Zhandarovich Date: Thu, 23 Oct 2025 16:22:04 +0300 Subject: [PATCH 0348/3902] comedi: multiq3: sanitize config options in multiq3_attach() commit f24c6e3a39fa355dabfb684c9ca82db579534e72 upstream. Syzbot identified an issue [1] in multiq3_attach() that induces a task timeout due to open() or COMEDI_DEVCONFIG ioctl operations, specifically, in the case of multiq3 driver. This problem arose when syzkaller managed to craft weird configuration options used to specify the number of channels in encoder subdevice. If a particularly great number is passed to s->n_chan in multiq3_attach() via it->options[2], then multiple calls to multiq3_encoder_reset() at the end of driver-specific attach() method will be running for minutes, thus blocking tasks and affected devices as well. While this issue is most likely not too dangerous for real-life devices, it still makes sense to sanitize configuration inputs. Enable a sensible limit on the number of encoder chips (4 chips max, each with 2 channels) to stop this behaviour from manifesting. [1] Syzbot crash: INFO: task syz.2.19:6067 blocked for more than 143 seconds. ... Call Trace: context_switch kernel/sched/core.c:5254 [inline] __schedule+0x17c4/0x4d60 kernel/sched/core.c:6862 __schedule_loop kernel/sched/core.c:6944 [inline] schedule+0x165/0x360 kernel/sched/core.c:6959 schedule_preempt_disabled+0x13/0x30 kernel/sched/core.c:7016 __mutex_lock_common kernel/locking/mutex.c:676 [inline] __mutex_lock+0x7e6/0x1350 kernel/locking/mutex.c:760 comedi_open+0xc0/0x590 drivers/comedi/comedi_fops.c:2868 chrdev_open+0x4cc/0x5e0 fs/char_dev.c:414 do_dentry_open+0x953/0x13f0 fs/open.c:965 vfs_open+0x3b/0x340 fs/open.c:1097 ... Reported-by: syzbot+7811bb68a317954a0347@syzkaller.appspotmail.com Closes: https://syzkaller.appspot.com/bug?extid=7811bb68a317954a0347 Fixes: 77e01cdbad51 ("Staging: comedi: add multiq3 driver") Cc: stable Signed-off-by: Nikita Zhandarovich Reviewed-by: Ian Abbott Link: https://patch.msgid.link/20251023132205.395753-1-n.zhandarovich@fintech.ru Signed-off-by: Greg Kroah-Hartman --- drivers/comedi/drivers/multiq3.c | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/drivers/comedi/drivers/multiq3.c b/drivers/comedi/drivers/multiq3.c index 07ff5383da9976..ac369e9a262d72 100644 --- a/drivers/comedi/drivers/multiq3.c +++ b/drivers/comedi/drivers/multiq3.c @@ -67,6 +67,11 @@ #define MULTIQ3_TRSFRCNTR_OL 0x10 /* xfer CNTR to OL (x and y) */ #define MULTIQ3_EFLAG_RESET 0x06 /* reset E bit of flag reg */ +/* + * Limit on the number of optional encoder channels + */ +#define MULTIQ3_MAX_ENC_CHANS 8 + static void multiq3_set_ctrl(struct comedi_device *dev, unsigned int bits) { /* @@ -312,6 +317,10 @@ static int multiq3_attach(struct comedi_device *dev, s->insn_read = multiq3_encoder_insn_read; s->insn_config = multiq3_encoder_insn_config; + /* sanity check for number of encoder channels */ + if (s->n_chan > MULTIQ3_MAX_ENC_CHANS) + s->n_chan = MULTIQ3_MAX_ENC_CHANS; + for (i = 0; i < s->n_chan; i++) multiq3_encoder_reset(dev, i); From aac80e912de306815297a3b74f0426873ffa7dc3 Mon Sep 17 00:00:00 2001 From: Nikita Zhandarovich Date: Thu, 23 Oct 2025 16:22:32 +0300 Subject: [PATCH 0349/3902] comedi: check device's attached status in compat ioctls commit 0de7d9cd07a2671fa6089173bccc0b2afe6b93ee upstream. Syzbot identified an issue [1] that crashes kernel, seemingly due to unexistent callback dev->get_valid_routes(). By all means, this should not occur as said callback must always be set to get_zero_valid_routes() in __comedi_device_postconfig(). As the crash seems to appear exclusively in i386 kernels, at least, judging from [1] reports, the blame lies with compat versions of standard IOCTL handlers. Several of them are modified and do not use comedi_unlocked_ioctl(). While functionality of these ioctls essentially copy their original versions, they do not have required sanity check for device's attached status. This, in turn, leads to a possibility of calling select IOCTLs on a device that has not been properly setup, even via COMEDI_DEVCONFIG. Doing so on unconfigured devices means that several crucial steps are missed, for instance, specifying dev->get_valid_routes() callback. Fix this somewhat crudely by ensuring device's attached status before performing any ioctls, improving logic consistency between modern and compat functions. [1] Syzbot report: BUG: kernel NULL pointer dereference, address: 0000000000000000 ... CR2: ffffffffffffffd6 CR3: 000000006c717000 CR4: 0000000000352ef0 Call Trace: get_valid_routes drivers/comedi/comedi_fops.c:1322 [inline] parse_insn+0x78c/0x1970 drivers/comedi/comedi_fops.c:1401 do_insnlist_ioctl+0x272/0x700 drivers/comedi/comedi_fops.c:1594 compat_insnlist drivers/comedi/comedi_fops.c:3208 [inline] comedi_compat_ioctl+0x810/0x990 drivers/comedi/comedi_fops.c:3273 __do_compat_sys_ioctl fs/ioctl.c:695 [inline] __se_compat_sys_ioctl fs/ioctl.c:638 [inline] __ia32_compat_sys_ioctl+0x242/0x370 fs/ioctl.c:638 do_syscall_32_irqs_on arch/x86/entry/syscall_32.c:83 [inline] ... Reported-by: syzbot+ab8008c24e84adee93ff@syzkaller.appspotmail.com Closes: https://syzkaller.appspot.com/bug?extid=ab8008c24e84adee93ff Fixes: 3fbfd2223a27 ("comedi: get rid of compat_alloc_user_space() mess in COMEDI_CHANINFO compat") Cc: stable Reviewed-by: Ian Abbott Signed-off-by: Nikita Zhandarovich Link: https://patch.msgid.link/20251023132234.395794-1-n.zhandarovich@fintech.ru Signed-off-by: Greg Kroah-Hartman --- drivers/comedi/comedi_fops.c | 42 ++++++++++++++++++++++++++++++------ 1 file changed, 36 insertions(+), 6 deletions(-) diff --git a/drivers/comedi/comedi_fops.c b/drivers/comedi/comedi_fops.c index 7e2f2b1a1c362e..b2e62e04afd994 100644 --- a/drivers/comedi/comedi_fops.c +++ b/drivers/comedi/comedi_fops.c @@ -3023,7 +3023,12 @@ static int compat_chaninfo(struct file *file, unsigned long arg) chaninfo.rangelist = compat_ptr(chaninfo32.rangelist); mutex_lock(&dev->mutex); - err = do_chaninfo_ioctl(dev, &chaninfo); + if (!dev->attached) { + dev_dbg(dev->class_dev, "no driver attached\n"); + err = -ENODEV; + } else { + err = do_chaninfo_ioctl(dev, &chaninfo); + } mutex_unlock(&dev->mutex); return err; } @@ -3044,7 +3049,12 @@ static int compat_rangeinfo(struct file *file, unsigned long arg) rangeinfo.range_ptr = compat_ptr(rangeinfo32.range_ptr); mutex_lock(&dev->mutex); - err = do_rangeinfo_ioctl(dev, &rangeinfo); + if (!dev->attached) { + dev_dbg(dev->class_dev, "no driver attached\n"); + err = -ENODEV; + } else { + err = do_rangeinfo_ioctl(dev, &rangeinfo); + } mutex_unlock(&dev->mutex); return err; } @@ -3120,7 +3130,12 @@ static int compat_cmd(struct file *file, unsigned long arg) return rc; mutex_lock(&dev->mutex); - rc = do_cmd_ioctl(dev, &cmd, ©, file); + if (!dev->attached) { + dev_dbg(dev->class_dev, "no driver attached\n"); + rc = -ENODEV; + } else { + rc = do_cmd_ioctl(dev, &cmd, ©, file); + } mutex_unlock(&dev->mutex); if (copy) { /* Special case: copy cmd back to user. */ @@ -3145,7 +3160,12 @@ static int compat_cmdtest(struct file *file, unsigned long arg) return rc; mutex_lock(&dev->mutex); - rc = do_cmdtest_ioctl(dev, &cmd, ©, file); + if (!dev->attached) { + dev_dbg(dev->class_dev, "no driver attached\n"); + rc = -ENODEV; + } else { + rc = do_cmdtest_ioctl(dev, &cmd, ©, file); + } mutex_unlock(&dev->mutex); if (copy) { err = put_compat_cmd(compat_ptr(arg), &cmd); @@ -3205,7 +3225,12 @@ static int compat_insnlist(struct file *file, unsigned long arg) } mutex_lock(&dev->mutex); - rc = do_insnlist_ioctl(dev, insns, insnlist32.n_insns, file); + if (!dev->attached) { + dev_dbg(dev->class_dev, "no driver attached\n"); + rc = -ENODEV; + } else { + rc = do_insnlist_ioctl(dev, insns, insnlist32.n_insns, file); + } mutex_unlock(&dev->mutex); kfree(insns); return rc; @@ -3224,7 +3249,12 @@ static int compat_insn(struct file *file, unsigned long arg) return rc; mutex_lock(&dev->mutex); - rc = do_insn_ioctl(dev, &insn, file); + if (!dev->attached) { + dev_dbg(dev->class_dev, "no driver attached\n"); + rc = -ENODEV; + } else { + rc = do_insn_ioctl(dev, &insn, file); + } mutex_unlock(&dev->mutex); return rc; } From c0d93d69e1472ba75b78898979b90a98ba2a2501 Mon Sep 17 00:00:00 2001 From: Navaneeth K Date: Thu, 20 Nov 2025 16:23:52 +0000 Subject: [PATCH 0350/3902] staging: rtl8723bs: fix out-of-bounds read in rtw_get_ie() parser commit 154828bf9559b9c8421fc2f0d7f7f76b3683aaed upstream. The Information Element (IE) parser rtw_get_ie() trusted the length byte of each IE without validating that the IE body (len bytes after the 2-byte header) fits inside the remaining frame buffer. A malformed frame can advertise an IE length larger than the available data, causing the parser to increment its pointer beyond the buffer end. This results in out-of-bounds reads or, depending on the pattern, an infinite loop. Fix by validating that (offset + 2 + len) does not exceed the limit before accepting the IE or advancing to the next element. This prevents OOB reads and ensures the parser terminates safely on malformed frames. Signed-off-by: Navaneeth K Cc: stable Signed-off-by: Greg Kroah-Hartman --- drivers/staging/rtl8723bs/core/rtw_ieee80211.c | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/drivers/staging/rtl8723bs/core/rtw_ieee80211.c b/drivers/staging/rtl8723bs/core/rtw_ieee80211.c index 53d4c113b19c8c..df35c616e71ff0 100644 --- a/drivers/staging/rtl8723bs/core/rtw_ieee80211.c +++ b/drivers/staging/rtl8723bs/core/rtw_ieee80211.c @@ -140,22 +140,24 @@ u8 *rtw_get_ie(u8 *pbuf, signed int index, signed int *len, signed int limit) signed int tmp, i; u8 *p; - if (limit < 1) + if (limit < 2) return NULL; p = pbuf; i = 0; *len = 0; - while (1) { + while (i + 2 <= limit) { + tmp = *(p + 1); + if (i + 2 + tmp > limit) + break; + if (*p == index) { - *len = *(p + 1); + *len = tmp; return p; } - tmp = *(p + 1); + p += (tmp + 2); i += (tmp + 2); - if (i >= limit) - break; } return NULL; } From e841d8ea722315b781c4fc5bf4f7670fbca88875 Mon Sep 17 00:00:00 2001 From: Navaneeth K Date: Thu, 20 Nov 2025 16:33:08 +0000 Subject: [PATCH 0351/3902] staging: rtl8723bs: fix stack buffer overflow in OnAssocReq IE parsing commit 6ef0e1c10455927867cac8f0ed6b49f328f8cf95 upstream. The Supported Rates IE length from an incoming Association Request frame was used directly as the memcpy() length when copying into a fixed-size 16-byte stack buffer (supportRate). A malicious station can advertise an IE length larger than 16 bytes, causing a stack buffer overflow. Clamp ie_len to the buffer size before copying the Supported Rates IE, and correct the bounds check when merging Extended Supported Rates to prevent a second potential overflow. This prevents kernel stack corruption triggered by malformed association requests. Signed-off-by: Navaneeth K Cc: stable Signed-off-by: Greg Kroah-Hartman --- drivers/staging/rtl8723bs/core/rtw_mlme_ext.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/drivers/staging/rtl8723bs/core/rtw_mlme_ext.c b/drivers/staging/rtl8723bs/core/rtw_mlme_ext.c index a897c433d2b061..fd7fa9278a7801 100644 --- a/drivers/staging/rtl8723bs/core/rtw_mlme_ext.c +++ b/drivers/staging/rtl8723bs/core/rtw_mlme_ext.c @@ -1042,6 +1042,9 @@ unsigned int OnAssocReq(struct adapter *padapter, union recv_frame *precv_frame) status = WLAN_STATUS_CHALLENGE_FAIL; goto OnAssocReqFail; } else { + if (ie_len > sizeof(supportRate)) + ie_len = sizeof(supportRate); + memcpy(supportRate, p+2, ie_len); supportRateNum = ie_len; @@ -1049,7 +1052,7 @@ unsigned int OnAssocReq(struct adapter *padapter, union recv_frame *precv_frame) pkt_len - WLAN_HDR_A3_LEN - ie_offset); if (p) { - if (supportRateNum <= sizeof(supportRate)) { + if (supportRateNum + ie_len <= sizeof(supportRate)) { memcpy(supportRate+supportRateNum, p+2, ie_len); supportRateNum += ie_len; } From bf323db1d883c209880bd92f3b12503e3531c3fc Mon Sep 17 00:00:00 2001 From: Navaneeth K Date: Thu, 20 Nov 2025 16:35:20 +0000 Subject: [PATCH 0352/3902] staging: rtl8723bs: fix out-of-bounds read in OnBeacon ESR IE parsing commit 502ddcc405b69fa92e0add6c1714d654504f6fd7 upstream. The Extended Supported Rates (ESR) IE handling in OnBeacon accessed *(p + 1 + ielen) and *(p + 2 + ielen) without verifying that these offsets lie within the received frame buffer. A malformed beacon with an ESR IE positioned at the end of the buffer could cause an out-of-bounds read, potentially triggering a kernel panic. Add a boundary check to ensure that the ESR IE body and the subsequent bytes are within the limits of the frame before attempting to access them. This prevents OOB reads caused by malformed beacon frames. Signed-off-by: Navaneeth K Cc: stable Signed-off-by: Greg Kroah-Hartman --- drivers/staging/rtl8723bs/core/rtw_mlme_ext.c | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/drivers/staging/rtl8723bs/core/rtw_mlme_ext.c b/drivers/staging/rtl8723bs/core/rtw_mlme_ext.c index fd7fa9278a7801..72eb48d554a35b 100644 --- a/drivers/staging/rtl8723bs/core/rtw_mlme_ext.c +++ b/drivers/staging/rtl8723bs/core/rtw_mlme_ext.c @@ -588,9 +588,11 @@ unsigned int OnBeacon(struct adapter *padapter, union recv_frame *precv_frame) p = rtw_get_ie(pframe + sizeof(struct ieee80211_hdr_3addr) + _BEACON_IE_OFFSET_, WLAN_EID_EXT_SUPP_RATES, &ielen, precv_frame->u.hdr.len - sizeof(struct ieee80211_hdr_3addr) - _BEACON_IE_OFFSET_); if (p && ielen > 0) { - if ((*(p + 1 + ielen) == 0x2D) && (*(p + 2 + ielen) != 0x2D)) - /* Invalid value 0x2D is detected in Extended Supported Rates (ESR) IE. Try to fix the IE length to avoid failed Beacon parsing. */ - *(p + 1) = ielen - 1; + if (p + 2 + ielen < pframe + len) { + if ((*(p + 1 + ielen) == 0x2D) && (*(p + 2 + ielen) != 0x2D)) + /* Invalid value 0x2D is detected in Extended Supported Rates (ESR) IE. Try to fix the IE length to avoid failed Beacon parsing. */ + *(p + 1) = ielen - 1; + } } if (pmlmeext->sitesurvey_res.state == SCAN_PROCESS) { From 25442251cbda7590d87d8203a8dc1ddf2c93de61 Mon Sep 17 00:00:00 2001 From: Greg Kroah-Hartman Date: Fri, 12 Dec 2025 18:42:47 +0100 Subject: [PATCH 0353/3902] Linux 6.18.1 Link: https://lore.kernel.org/r/20251210072944.363788552@linuxfoundation.org Tested-by: Brett A C Sheffield Tested-by: Takeshi Ogasawara Tested-by: Jeffrin Jose T Tested-by: Salvatore Bonaccorso Tested-By: Achill Gilgenast = Tested-by: Peter Schneider Tested-by: Florian Fainelli Tested-by: Hardik Garg Tested-by: Dileep Malepu Tested-by: Ronald Warsow Tested-by: Ron Economos Tested-by: Linux Kernel Functional Testing Tested-by: Mark Brown Tested-by: Jon Hunter Signed-off-by: Greg Kroah-Hartman --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index a082a1d7c7d9b4..23cc6c39819b11 100644 --- a/Makefile +++ b/Makefile @@ -1,7 +1,7 @@ # SPDX-License-Identifier: GPL-2.0 VERSION = 6 PATCHLEVEL = 18 -SUBLEVEL = 0 +SUBLEVEL = 1 EXTRAVERSION = NAME = Baby Opossum Posse From 7d25645732a66760f2b096cbcddce7520c038e08 Mon Sep 17 00:00:00 2001 From: Sven Peter Date: Wed, 15 Oct 2025 15:40:41 +0000 Subject: [PATCH 0354/3902] dt-bindings: usb: Add Apple dwc3 Apple Silicon uses Synopsys DesignWare dwc3 based USB controllers for their Type-C ports. Reviewed-by: Krzysztof Kozlowski Reviewed-by: Neal Gompa Signed-off-by: Sven Peter Link: https://patch.msgid.link/20251015-b4-aplpe-dwc3-v2-1-cbd65a2d511a@kernel.org Signed-off-by: Greg Kroah-Hartman --- .../devicetree/bindings/usb/apple,dwc3.yaml | 80 +++++++++++++++++++ MAINTAINERS | 1 + 2 files changed, 81 insertions(+) create mode 100644 Documentation/devicetree/bindings/usb/apple,dwc3.yaml diff --git a/Documentation/devicetree/bindings/usb/apple,dwc3.yaml b/Documentation/devicetree/bindings/usb/apple,dwc3.yaml new file mode 100644 index 00000000000000..f70c33f32c5d61 --- /dev/null +++ b/Documentation/devicetree/bindings/usb/apple,dwc3.yaml @@ -0,0 +1,80 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/usb/apple,dwc3.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Apple Silicon DWC3 USB controller + +maintainers: + - Sven Peter + +description: + Apple Silicon SoCs use a Synopsys DesignWare DWC3 based controller for each of + their Type-C ports. + +allOf: + - $ref: snps,dwc3-common.yaml# + +properties: + compatible: + oneOf: + - items: + - enum: + - apple,t6000-dwc3 + - apple,t6020-dwc3 + - apple,t8112-dwc3 + - const: apple,t8103-dwc3 + - const: apple,t8103-dwc3 + + reg: + items: + - description: Core DWC3 region + - description: Apple-specific DWC3 region + + reg-names: + items: + - const: dwc3-core + - const: dwc3-apple + + interrupts: + maxItems: 1 + + iommus: + maxItems: 2 + + resets: + maxItems: 1 + + power-domains: + maxItems: 1 + +required: + - compatible + - reg + - reg-names + - interrupts + - iommus + - resets + - power-domains + - usb-role-switch + +unevaluatedProperties: false + +examples: + - | + #include + #include + + usb@82280000 { + compatible = "apple,t8103-dwc3"; + reg = <0x82280000 0xcd00>, <0x8228cd00 0x3200>; + reg-names = "dwc3-core", "dwc3-apple"; + interrupts = ; + iommus = <&dwc3_0_dart_0 0>, <&dwc3_0_dart_1 1>; + + power-domains = <&ps_atc0_usb>; + resets = <&atcphy0>; + + usb-role-switch; + }; diff --git a/MAINTAINERS b/MAINTAINERS index e8f06145fb54c9..0cd6faab0824c9 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -2442,6 +2442,7 @@ F: Documentation/devicetree/bindings/power/reset/apple,smc-reboot.yaml F: Documentation/devicetree/bindings/pwm/apple,s5l-fpwm.yaml F: Documentation/devicetree/bindings/spi/apple,spi.yaml F: Documentation/devicetree/bindings/spmi/apple,spmi.yaml +F: Documentation/devicetree/bindings/usb/apple,dwc3.yaml F: Documentation/devicetree/bindings/watchdog/apple,wdt.yaml F: arch/arm64/boot/dts/apple/ F: drivers/bluetooth/hci_bcm4377.c From 8a985bc3fea679c2ae07b4065fbd96c8e191eaaf Mon Sep 17 00:00:00 2001 From: Frank Li Date: Mon, 29 Sep 2025 10:24:15 -0400 Subject: [PATCH 0355/3902] usb: dwc3: Add software-managed properties for flattened model Add software-managed properties for the flattened model, which does not need to use device tree properties to pass down information to the common DWC3 core. Add 'properties' in dwc3_probe_data and set default values for existing users (dwc3-qcom, dwc3-generic-plat). No functional changes. Acked-by: Thinh Nguyen Signed-off-by: Frank Li Link: https://lore.kernel.org/r/20250929-ls_dma_coherence-v5-2-2ebee578eb7e@nxp.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/dwc3/core.c | 12 ++++++++++-- drivers/usb/dwc3/dwc3-generic-plat.c | 1 + drivers/usb/dwc3/dwc3-qcom.c | 1 + drivers/usb/dwc3/glue.h | 14 ++++++++++++++ 4 files changed, 26 insertions(+), 2 deletions(-) diff --git a/drivers/usb/dwc3/core.c b/drivers/usb/dwc3/core.c index c2ce2f5e60a19c..f744114d8b0b83 100644 --- a/drivers/usb/dwc3/core.c +++ b/drivers/usb/dwc3/core.c @@ -1667,7 +1667,8 @@ static void dwc3_core_exit_mode(struct dwc3 *dwc) dwc3_set_prtcap(dwc, DWC3_GCTL_PRTCAP_DEVICE, true); } -static void dwc3_get_software_properties(struct dwc3 *dwc) +static void dwc3_get_software_properties(struct dwc3 *dwc, + const struct dwc3_properties *properties) { struct device *tmpdev; u16 gsbuscfg0_reqinfo; @@ -1675,6 +1676,12 @@ static void dwc3_get_software_properties(struct dwc3 *dwc) dwc->gsbuscfg0_reqinfo = DWC3_GSBUSCFG0_REQINFO_UNSPECIFIED; + if (properties->gsbuscfg0_reqinfo != + DWC3_GSBUSCFG0_REQINFO_UNSPECIFIED) { + dwc->gsbuscfg0_reqinfo = properties->gsbuscfg0_reqinfo; + return; + } + /* * Iterate over all parent nodes for finding swnode properties * and non-DT (non-ABI) properties. @@ -2207,7 +2214,7 @@ int dwc3_core_probe(const struct dwc3_probe_data *data) dwc3_get_properties(dwc); - dwc3_get_software_properties(dwc); + dwc3_get_software_properties(dwc, &data->properties); dwc->usb_psy = dwc3_get_usb_power_supply(dwc); if (IS_ERR(dwc->usb_psy)) @@ -2357,6 +2364,7 @@ static int dwc3_probe(struct platform_device *pdev) probe_data.dwc = dwc; probe_data.res = res; + probe_data.properties = DWC3_DEFAULT_PROPERTIES; return dwc3_core_probe(&probe_data); } diff --git a/drivers/usb/dwc3/dwc3-generic-plat.c b/drivers/usb/dwc3/dwc3-generic-plat.c index f8ad79c08c4e5c..ba3aec4cb9631c 100644 --- a/drivers/usb/dwc3/dwc3-generic-plat.c +++ b/drivers/usb/dwc3/dwc3-generic-plat.c @@ -75,6 +75,7 @@ static int dwc3_generic_probe(struct platform_device *pdev) probe_data.dwc = &dwc3g->dwc; probe_data.res = res; probe_data.ignore_clocks_and_resets = true; + probe_data.properties = DWC3_DEFAULT_PROPERTIES; ret = dwc3_core_probe(&probe_data); if (ret) return dev_err_probe(dev, ret, "failed to register DWC3 Core\n"); diff --git a/drivers/usb/dwc3/dwc3-qcom.c b/drivers/usb/dwc3/dwc3-qcom.c index ded2ca86670c0b..9ac75547820d97 100644 --- a/drivers/usb/dwc3/dwc3-qcom.c +++ b/drivers/usb/dwc3/dwc3-qcom.c @@ -704,6 +704,7 @@ static int dwc3_qcom_probe(struct platform_device *pdev) probe_data.dwc = &qcom->dwc; probe_data.res = &res; probe_data.ignore_clocks_and_resets = true; + probe_data.properties = DWC3_DEFAULT_PROPERTIES; ret = dwc3_core_probe(&probe_data); if (ret) { ret = dev_err_probe(dev, ret, "failed to register DWC3 Core\n"); diff --git a/drivers/usb/dwc3/glue.h b/drivers/usb/dwc3/glue.h index 2efd00e763be4f..cc6e138bd9ef25 100644 --- a/drivers/usb/dwc3/glue.h +++ b/drivers/usb/dwc3/glue.h @@ -9,17 +9,31 @@ #include #include "core.h" +/** + * dwc3_properties: DWC3 core properties + * @gsbuscfg0_reqinfo: Value to be programmed in the GSBUSCFG0.REQINFO field + */ +struct dwc3_properties { + u32 gsbuscfg0_reqinfo; +}; + +#define DWC3_DEFAULT_PROPERTIES ((struct dwc3_properties){ \ + .gsbuscfg0_reqinfo = DWC3_GSBUSCFG0_REQINFO_UNSPECIFIED, \ + }) + /** * dwc3_probe_data: Initialization parameters passed to dwc3_core_probe() * @dwc: Reference to dwc3 context structure * @res: resource for the DWC3 core mmio region * @ignore_clocks_and_resets: clocks and resets defined for the device should * be ignored by the DWC3 core, as they are managed by the glue + * @properties: dwc3 software manage properties */ struct dwc3_probe_data { struct dwc3 *dwc; struct resource *res; bool ignore_clocks_and_resets; + struct dwc3_properties properties; }; int dwc3_core_probe(const struct dwc3_probe_data *data); From e4d5f1f4809cd505f4aa0af179671baa0187c0b4 Mon Sep 17 00:00:00 2001 From: Frank Li Date: Mon, 29 Sep 2025 10:24:16 -0400 Subject: [PATCH 0356/3902] usb: dwc3: dwc3-generic-plat: Add layerscape dwc3 support Add layerscape dwc3 support by using flatten dwc3 core library. Layerscape dwc3 need set gsbuscfg0-reqinfo as 0x2222 when dma-coherence set. Signed-off-by: Frank Li Acked-by: Thinh Nguyen Link: https://lore.kernel.org/r/20250929-ls_dma_coherence-v5-3-2ebee578eb7e@nxp.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/dwc3/dwc3-generic-plat.c | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/drivers/usb/dwc3/dwc3-generic-plat.c b/drivers/usb/dwc3/dwc3-generic-plat.c index ba3aec4cb9631c..e869c7de7bc848 100644 --- a/drivers/usb/dwc3/dwc3-generic-plat.c +++ b/drivers/usb/dwc3/dwc3-generic-plat.c @@ -29,6 +29,7 @@ static void dwc3_generic_reset_control_assert(void *data) static int dwc3_generic_probe(struct platform_device *pdev) { + const struct dwc3_properties *properties; struct dwc3_probe_data probe_data = {}; struct device *dev = &pdev->dev; struct dwc3_generic *dwc3g; @@ -75,7 +76,13 @@ static int dwc3_generic_probe(struct platform_device *pdev) probe_data.dwc = &dwc3g->dwc; probe_data.res = res; probe_data.ignore_clocks_and_resets = true; - probe_data.properties = DWC3_DEFAULT_PROPERTIES; + + properties = of_device_get_match_data(dev); + if (properties) + probe_data.properties = *properties; + else + probe_data.properties = DWC3_DEFAULT_PROPERTIES; + ret = dwc3_core_probe(&probe_data); if (ret) return dev_err_probe(dev, ret, "failed to register DWC3 Core\n"); @@ -143,8 +150,13 @@ static const struct dev_pm_ops dwc3_generic_dev_pm_ops = { dwc3_generic_runtime_idle) }; +static const struct dwc3_properties fsl_ls1028_dwc3 = { + .gsbuscfg0_reqinfo = 0x2222, +}; + static const struct of_device_id dwc3_generic_of_match[] = { { .compatible = "spacemit,k1-dwc3", }, + { .compatible = "fsl,ls1028a-dwc3", &fsl_ls1028_dwc3}, { /* sentinel */ } }; MODULE_DEVICE_TABLE(of, dwc3_generic_of_match); From bcb84b475c8644cc10a3a85fce9edb4531ac8e1e Mon Sep 17 00:00:00 2001 From: Sven Peter Date: Sat, 23 Aug 2025 11:50:18 +0000 Subject: [PATCH 0357/3902] usb: dwc3: Use ioremap_np when required in dwc3_power_off_all_roothub_ports() On Apple Silicon machines we can't use ioremap() / Device-nGnRE to map most regions but must use ioremap_np() / Device-nGnRnE whenever IORESOURCE_MEM_NONPOSTED is set. Make sure this is also done inside dwc3_power_off_all_roothub_ports to prevent SErrors. Fixes: 2d2a3349521d ("usb: dwc3: Add workaround for host mode VBUS glitch when boot") Signed-off-by: Sven Peter --- drivers/usb/dwc3/host.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/drivers/usb/dwc3/host.c b/drivers/usb/dwc3/host.c index 1c513bf8002ec9..e77fd86d09cf0a 100644 --- a/drivers/usb/dwc3/host.c +++ b/drivers/usb/dwc3/host.c @@ -37,7 +37,10 @@ static void dwc3_power_off_all_roothub_ports(struct dwc3 *dwc) /* xhci regs are not mapped yet, do it temporarily here */ if (dwc->xhci_resources[0].start) { - xhci_regs = ioremap(dwc->xhci_resources[0].start, DWC3_XHCI_REGS_END); + if (dwc->xhci_resources[0].flags & IORESOURCE_MEM_NONPOSTED) + xhci_regs = ioremap_np(dwc->xhci_resources[0].start, DWC3_XHCI_REGS_END); + else + xhci_regs = ioremap(dwc->xhci_resources[0].start, DWC3_XHCI_REGS_END); if (!xhci_regs) { dev_err(dwc->dev, "Failed to ioremap xhci_regs\n"); return; From 895933b83304121c0583f9c1e5bf19b9829f4fb6 Mon Sep 17 00:00:00 2001 From: Sven Peter Date: Wed, 15 Oct 2025 15:40:43 +0000 Subject: [PATCH 0358/3902] usb: dwc3: glue: Add documentation We're about to add more exported functions to be used inside glue driver which will need more detailed documentation explaining how they must be used. Let's also add documentation for the functions already available. Acked-by: Thinh Nguyen Signed-off-by: Sven Peter Link: https://patch.msgid.link/20251015-b4-aplpe-dwc3-v2-3-cbd65a2d511a@kernel.org Signed-off-by: Greg Kroah-Hartman --- drivers/usb/dwc3/glue.h | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/drivers/usb/dwc3/glue.h b/drivers/usb/dwc3/glue.h index cc6e138bd9ef25..bef4d9d68558f1 100644 --- a/drivers/usb/dwc3/glue.h +++ b/drivers/usb/dwc3/glue.h @@ -36,9 +36,36 @@ struct dwc3_probe_data { struct dwc3_properties properties; }; +/** + * dwc3_core_probe - Initialize the core dwc3 driver + * @data: Initialization and configuration parameters for the controller + * + * Initializes the DesignWare USB3 core driver by setting up resources, + * registering interrupts, performing hardware setup, and preparing + * the controller for operation in the appropriate mode (host, gadget, + * or OTG). This is the main initialization function called by glue + * layer drivers to set up the core controller. + * + * Return: 0 on success, negative error code on failure + */ int dwc3_core_probe(const struct dwc3_probe_data *data); + +/** + * dwc3_core_remove - Deinitialize and remove the core dwc3 driver + * @dwc: Pointer to DWC3 controller context + * + * Cleans up resources and disables the dwc3 core driver. This should be called + * during driver removal or when the glue layer needs to shut down the + * controller completely. + */ void dwc3_core_remove(struct dwc3 *dwc); +/* + * The following callbacks are provided for glue drivers to call from their + * own pm callbacks provided in struct dev_pm_ops. Glue drivers can perform + * platform-specific work before or after calling these functions and delegate + * the core suspend/resume operations to the core driver. + */ int dwc3_runtime_suspend(struct dwc3 *dwc); int dwc3_runtime_resume(struct dwc3 *dwc); int dwc3_runtime_idle(struct dwc3 *dwc); From 9b2b156215850d94d6d20bd4ff072d598f077947 Mon Sep 17 00:00:00 2001 From: Sven Peter Date: Wed, 15 Oct 2025 15:40:44 +0000 Subject: [PATCH 0359/3902] usb: dwc3: glue: Allow more fine grained control over mode switches We need fine grained control over mode switched on the DWC3 controller present on Apple Silicon. Export core, host and gadget init and exit, ptrcap and susphy control functions. Also introduce an additional parameter to probe_data that allows to skip the final initialization step that would bring up host or gadget mode. Acked-by: Thinh Nguyen Signed-off-by: Sven Peter Link: https://patch.msgid.link/20251015-b4-aplpe-dwc3-v2-4-cbd65a2d511a@kernel.org Signed-off-by: Greg Kroah-Hartman --- drivers/usb/dwc3/core.c | 16 ++++-- drivers/usb/dwc3/gadget.c | 2 + drivers/usb/dwc3/glue.h | 116 ++++++++++++++++++++++++++++++++++++++ drivers/usb/dwc3/host.c | 2 + 4 files changed, 131 insertions(+), 5 deletions(-) diff --git a/drivers/usb/dwc3/core.c b/drivers/usb/dwc3/core.c index f744114d8b0b83..7b84512b13a285 100644 --- a/drivers/usb/dwc3/core.c +++ b/drivers/usb/dwc3/core.c @@ -133,6 +133,7 @@ void dwc3_enable_susphy(struct dwc3 *dwc, bool enable) dwc3_writel(dwc->regs, DWC3_GUSB2PHYCFG(i), reg); } } +EXPORT_SYMBOL_GPL(dwc3_enable_susphy); void dwc3_set_prtcap(struct dwc3 *dwc, u32 mode, bool ignore_susphy) { @@ -159,6 +160,7 @@ void dwc3_set_prtcap(struct dwc3 *dwc, u32 mode, bool ignore_susphy) dwc->current_dr_role = mode; trace_dwc3_set_prtcap(mode); } +EXPORT_SYMBOL_GPL(dwc3_set_prtcap); static void __dwc3_set_mode(struct work_struct *work) { @@ -976,7 +978,7 @@ static void dwc3_clk_disable(struct dwc3 *dwc) clk_disable_unprepare(dwc->bus_clk); } -static void dwc3_core_exit(struct dwc3 *dwc) +void dwc3_core_exit(struct dwc3 *dwc) { dwc3_event_buffers_cleanup(dwc); dwc3_phy_power_off(dwc); @@ -984,6 +986,7 @@ static void dwc3_core_exit(struct dwc3 *dwc) dwc3_clk_disable(dwc); reset_control_assert(dwc->reset); } +EXPORT_SYMBOL_GPL(dwc3_core_exit); static bool dwc3_core_is_valid(struct dwc3 *dwc) { @@ -1329,7 +1332,7 @@ static void dwc3_config_threshold(struct dwc3 *dwc) * * Returns 0 on success otherwise negative errno. */ -static int dwc3_core_init(struct dwc3 *dwc) +int dwc3_core_init(struct dwc3 *dwc) { unsigned int hw_mode; u32 reg; @@ -1529,6 +1532,7 @@ static int dwc3_core_init(struct dwc3 *dwc) return ret; } +EXPORT_SYMBOL_GPL(dwc3_core_init); static int dwc3_core_get_phy(struct dwc3 *dwc) { @@ -2307,9 +2311,11 @@ int dwc3_core_probe(const struct dwc3_probe_data *data) dwc3_check_params(dwc); dwc3_debugfs_init(dwc); - ret = dwc3_core_init_mode(dwc); - if (ret) - goto err_exit_debugfs; + if (!data->skip_core_init_mode) { + ret = dwc3_core_init_mode(dwc); + if (ret) + goto err_exit_debugfs; + } pm_runtime_put(dev); diff --git a/drivers/usb/dwc3/gadget.c b/drivers/usb/dwc3/gadget.c index 5e4997f974ddde..a1d2ff9254b44c 100644 --- a/drivers/usb/dwc3/gadget.c +++ b/drivers/usb/dwc3/gadget.c @@ -4817,6 +4817,7 @@ int dwc3_gadget_init(struct dwc3 *dwc) err0: return ret; } +EXPORT_SYMBOL_GPL(dwc3_gadget_init); /* -------------------------------------------------------------------------- */ @@ -4835,6 +4836,7 @@ void dwc3_gadget_exit(struct dwc3 *dwc) dma_free_coherent(dwc->sysdev, sizeof(*dwc->ep0_trb) * 2, dwc->ep0_trb, dwc->ep0_trb_addr); } +EXPORT_SYMBOL_GPL(dwc3_gadget_exit); int dwc3_gadget_suspend(struct dwc3 *dwc) { diff --git a/drivers/usb/dwc3/glue.h b/drivers/usb/dwc3/glue.h index bef4d9d68558f1..df86e14cb706ca 100644 --- a/drivers/usb/dwc3/glue.h +++ b/drivers/usb/dwc3/glue.h @@ -27,12 +27,15 @@ struct dwc3_properties { * @res: resource for the DWC3 core mmio region * @ignore_clocks_and_resets: clocks and resets defined for the device should * be ignored by the DWC3 core, as they are managed by the glue + * @skip_core_init_mode: Skip the finial initialization of the target mode, as + * it must be managed by the glue * @properties: dwc3 software manage properties */ struct dwc3_probe_data { struct dwc3 *dwc; struct resource *res; bool ignore_clocks_and_resets; + bool skip_core_init_mode; struct dwc3_properties properties; }; @@ -74,4 +77,117 @@ int dwc3_pm_resume(struct dwc3 *dwc); void dwc3_pm_complete(struct dwc3 *dwc); int dwc3_pm_prepare(struct dwc3 *dwc); + +/* All of the following functions must only be used with skip_core_init_mode */ + +/** + * dwc3_core_init - Initialize DWC3 core hardware + * @dwc: Pointer to DWC3 controller context + * + * Configures and initializes the core hardware, usually done by dwc3_core_probe. + * This function is provided for platforms that use skip_core_init_mode and need + * to finalize the core initialization after some platform-specific setup. + * It must only be called when using skip_core_init_mode and before + * dwc3_host_init or dwc3_gadget_init. + * + * Return: 0 on success, negative error code on failure + */ +int dwc3_core_init(struct dwc3 *dwc); + +/** + * dwc3_core_exit - Shut down DWC3 core hardware + * @dwc: Pointer to DWC3 controller context + * + * Disables and cleans up the core hardware state. This is usually handled + * internally by dwc3 and must only be called when using skip_core_init_mode + * and only after dwc3_core_init. Afterwards, dwc3_core_init may be called + * again. + */ +void dwc3_core_exit(struct dwc3 *dwc); + +/** + * dwc3_host_init - Initialize host mode operation + * @dwc: Pointer to DWC3 controller context + * + * Initializes the controller for USB host mode operation, usually done by + * dwc3_core_probe or from within the dwc3 USB role switch callback. + * This function is provided for platforms that use skip_core_init_mode and need + * to finalize the host initialization after some platform-specific setup. + * It must not be called before dwc3_core_init or when skip_core_init_mode is + * not used. It must also not be called when gadget or host mode has already + * been initialized. + * + * Return: 0 on success, negative error code on failure + */ +int dwc3_host_init(struct dwc3 *dwc); + +/** + * dwc3_host_exit - Shut down host mode operation + * @dwc: Pointer to DWC3 controller context + * + * Disables and cleans up host mode resources, usually done by + * the dwc3 USB role switch callback before switching controller mode. + * It must only be called when skip_core_init_mode is used and only after + * dwc3_host_init. + */ +void dwc3_host_exit(struct dwc3 *dwc); + +/** + * dwc3_gadget_init - Initialize gadget mode operation + * @dwc: Pointer to DWC3 controller context + * + * Initializes the controller for USB gadget mode operation, usually done by + * dwc3_core_probe or from within the dwc3 USB role switch callback. This + * function is provided for platforms that use skip_core_init_mode and need to + * finalize the gadget initialization after some platform-specific setup. + * It must not be called before dwc3_core_init or when skip_core_init_mode is + * not used. It must also not be called when gadget or host mode has already + * been initialized. + * + * Return: 0 on success, negative error code on failure + */ +int dwc3_gadget_init(struct dwc3 *dwc); + +/** + * dwc3_gadget_exit - Shut down gadget mode operation + * @dwc: Pointer to DWC3 controller context + * + * Disables and cleans up gadget mode resources, usually done by + * the dwc3 USB role switch callback before switching controller mode. + * It must only be called when skip_core_init_mode is used and only after + * dwc3_gadget_init. + */ +void dwc3_gadget_exit(struct dwc3 *dwc); + +/** + * dwc3_enable_susphy - Control SUSPHY status for all USB ports + * @dwc: Pointer to DWC3 controller context + * @enable: True to enable SUSPHY, false to disable + * + * Enables or disables the USB3 PHY SUSPEND and USB2 PHY SUSPHY feature for + * all available ports. + * This is usually handled by the dwc3 core code and should only be used + * when skip_core_init_mode is used and the glue layer needs to manage SUSPHY + * settings itself, e.g., due to platform-specific requirements during mode + * switches. + */ +void dwc3_enable_susphy(struct dwc3 *dwc, bool enable); + +/** + * dwc3_set_prtcap - Set the USB controller PRTCAP mode + * @dwc: Pointer to DWC3 controller context + * @mode: Target mode, must be one of DWC3_GCTL_PRTCAP_{HOST,DEVICE,OTG} + * @ignore_susphy: If true, skip disabling the SUSPHY and keep the current state + * + * Updates PRTCAP of the controller and current_dr_role inside the dwc3 + * structure. For DRD controllers, this also disables SUSPHY unless explicitly + * told to skip via the ignore_susphy parameter. + * + * This is usually handled by the dwc3 core code and should only be used + * when skip_core_init_mode is used and the glue layer needs to manage mode + * transitions itself due to platform-specific requirements. It must be called + * with the correct mode before calling dwc3_host_init or dwc3_gadget_init. + */ +void dwc3_set_prtcap(struct dwc3 *dwc, u32 mode, bool ignore_susphy); + #endif diff --git a/drivers/usb/dwc3/host.c b/drivers/usb/dwc3/host.c index e77fd86d09cf0a..cf6512ed17a691 100644 --- a/drivers/usb/dwc3/host.c +++ b/drivers/usb/dwc3/host.c @@ -220,6 +220,7 @@ int dwc3_host_init(struct dwc3 *dwc) platform_device_put(xhci); return ret; } +EXPORT_SYMBOL_GPL(dwc3_host_init); void dwc3_host_exit(struct dwc3 *dwc) { @@ -230,3 +231,4 @@ void dwc3_host_exit(struct dwc3 *dwc) platform_device_unregister(dwc->xhci); dwc->xhci = NULL; } +EXPORT_SYMBOL_GPL(dwc3_host_exit); From 82150bb7bfacced79bb9ec5901a63f2a244a8bcf Mon Sep 17 00:00:00 2001 From: Sven Peter Date: Wed, 15 Oct 2025 15:40:45 +0000 Subject: [PATCH 0360/3902] usb: dwc3: Add Apple Silicon DWC3 glue layer driver The dwc3 controller present on Apple Silicon SoCs like the M1 requires a specific order of operations synchronized between its PHY and its Type-C controller. Specifically, the PHY first has to go through initial bringup (which requires knowledge of the lane mode and orientation) before dwc3 itself can be brought up and can then finalize the PHY configuration. Additionally, dwc3 has to be teared down and re-initialized whenever the cable is changed due to hardware quirks that prevent a new device from being recognized and due to the PHY being unable to switch lane mode or orientation while dwc3 is up and running. These controllers also have a Apple-specific MMIO region after the common dwc3 region where some controls have to be updated. PHY bringup and shutdown also requires SUSPHY to be enabled for the ports to work correctly. In the future, this driver will also gain support for USB3-via-USB4 tunneling which will require additional tweaks. Add a glue driver that takes of all of these constraints. Reviewed-by: Neal Gompa Acked-by: Thinh Nguyen Signed-off-by: Sven Peter Link: https://patch.msgid.link/20251015-b4-aplpe-dwc3-v2-5-cbd65a2d511a@kernel.org Signed-off-by: Greg Kroah-Hartman --- MAINTAINERS | 1 + drivers/usb/dwc3/Kconfig | 11 + drivers/usb/dwc3/Makefile | 1 + drivers/usb/dwc3/dwc3-apple.c | 489 ++++++++++++++++++++++++++++++++++ 4 files changed, 502 insertions(+) create mode 100644 drivers/usb/dwc3/dwc3-apple.c diff --git a/MAINTAINERS b/MAINTAINERS index 0cd6faab0824c9..1ff913e8b98e48 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -2467,6 +2467,7 @@ F: drivers/pwm/pwm-apple.c F: drivers/soc/apple/* F: drivers/spi/spi-apple.c F: drivers/spmi/spmi-apple-controller.c +F: drivers/usb/dwc3/dwc3-apple.c F: drivers/video/backlight/apple_dwi_bl.c F: drivers/watchdog/apple_wdt.c F: include/dt-bindings/interrupt-controller/apple-aic.h diff --git a/drivers/usb/dwc3/Kconfig b/drivers/usb/dwc3/Kconfig index 4925d15084f816..bf3e0463513100 100644 --- a/drivers/usb/dwc3/Kconfig +++ b/drivers/usb/dwc3/Kconfig @@ -200,4 +200,15 @@ config USB_DWC3_GENERIC_PLAT the dwc3 child node in the device tree. Say 'Y' or 'M' here if your platform integrates DWC3 in a similar way. +config USB_DWC3_APPLE + tristate "Apple Silicon DWC3 Platform Driver" + depends on OF && ARCH_APPLE + default USB_DWC3 + select USB_ROLE_SWITCH + help + Support Apple Silicon SoCs with DesignWare Core USB3 IP. + The DesignWare Core USB3 IP has to be used in dual-role + mode on these machines. + Say 'Y' or 'M' if you have such device. + endif diff --git a/drivers/usb/dwc3/Makefile b/drivers/usb/dwc3/Makefile index 96469e48ff9d18..89d46ab5006856 100644 --- a/drivers/usb/dwc3/Makefile +++ b/drivers/usb/dwc3/Makefile @@ -43,6 +43,7 @@ endif ## obj-$(CONFIG_USB_DWC3_AM62) += dwc3-am62.o +obj-$(CONFIG_USB_DWC3_APPLE) += dwc3-apple.o obj-$(CONFIG_USB_DWC3_OMAP) += dwc3-omap.o obj-$(CONFIG_USB_DWC3_EXYNOS) += dwc3-exynos.o obj-$(CONFIG_USB_DWC3_PCI) += dwc3-pci.o diff --git a/drivers/usb/dwc3/dwc3-apple.c b/drivers/usb/dwc3/dwc3-apple.c new file mode 100644 index 00000000000000..6e41bd0e34f461 --- /dev/null +++ b/drivers/usb/dwc3/dwc3-apple.c @@ -0,0 +1,489 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Apple Silicon DWC3 Glue driver + * Copyright (C) The Asahi Linux Contributors + * + * Based on: + * - dwc3-qcom.c Copyright (c) 2018, The Linux Foundation. All rights reserved. + * - dwc3-of-simple.c Copyright (c) 2015 Texas Instruments Incorporated - https://www.ti.com + */ + +#include +#include +#include +#include +#include + +#include "glue.h" + +/* + * This platform requires a very specific sequence of operations to bring up dwc3 and its USB3 PHY: + * + * 1) The PHY itself has to be brought up; for this we need to know the mode (USB3, + * USB3+DisplayPort, USB4, etc) and the lane orientation. This happens through typec_mux_set. + * 2) DWC3 has to be brought up but we must not touch the gadget area or start xhci yet. + * 3) The PHY bring-up has to be finalized and dwc3's PIPE interface has to be switched to the + * USB3 PHY, this is done inside phy_set_mode. + * 4) We can now initialize xhci or gadget mode. + * + * We can switch 1 and 2 but 3 has to happen after (1 and 2) and 4 has to happen after 3. + * + * And then to bring this all down again: + * + * 1) DWC3 has to exit host or gadget mode and must no longer touch those registers + * 2) The PHY has to switch dwc3's PIPE interface back to the dummy backend + * 3) The PHY itself can be shut down, this happens from typec_mux_set + * + * We also can't transition the PHY from one mode to another while dwc3 is up and running (this is + * slightly wrong, some transitions are possible, others aren't but because we have no documentation + * for this I'd rather play it safe). + * + * After both the PHY and dwc3 are initialized we will only ever see a single "new device connected" + * event. If we just keep them running only the first device plugged in will ever work. XHCI's port + * status register actually does show the correct state but no interrupt ever comes in. In gadget + * mode we don't even get a USBDisconnected event and everything looks like there's still something + * connected on the other end. + * This can be partially explained because the USB2 D+/D- lines are connected through a stateful + * eUSB2 repeater which in turn is controlled by a variant of the TI TPS6598x USB PD chip which + * resets the repeater out-of-band everytime the CC lines are (dis)connected. This then requires a + * PHY reset to make sure the PHY and the eUSB2 repeater state are synchronized again. + * + * And to make this all extra fun: If we get the order of some of this wrong either the port is just + * broken until a phy+dwc3 reset, or it's broken until a full SoC reset (likely because we can't + * reset some parts of the PHY), or some watchdog kicks in after a few seconds and forces a full SoC + * reset (mostly seen this with USB4/Thunderbolt but there's clearly some watchdog that hates + * invalid states). + * + * Hence there's really no good way to keep dwc3 fully up and running after we disconnect a cable + * because then we can't shut down the PHY anymore. And if we kept the PHY running in whatever mode + * it was until the next cable is connected we'd need to tear it all down and bring it back up again + * anyway to detect and use the next device. + * + * Instead, we just shut down everything when a cable is disconnected and transition to + * DWC3_APPLE_NO_CABLE. + * During initial probe we don't have any information about the connected cable and can't bring up + * the PHY properly and thus also can't fully bring up dwc3. Instead, we just keep everything off + * and defer the first dwc3 probe until we get the first cable connected event. Until then we stay + * in DWC3_APPLE_PROBE_PENDING. + * Once a cable is connected we then keep track of the controller mode here by transitioning to + * DWC3_APPLE_HOST or DWC3_APPLE_DEVICE. + */ +enum dwc3_apple_state { + DWC3_APPLE_PROBE_PENDING, /* Before first cable connection, dwc3_core_probe not called */ + DWC3_APPLE_NO_CABLE, /* No cable connected, dwc3 suspended after dwc3_core_exit */ + DWC3_APPLE_HOST, /* Cable connected, dwc3 in host mode */ + DWC3_APPLE_DEVICE, /* Cable connected, dwc3 in device mode */ +}; + +/** + * struct dwc3_apple - Apple-specific DWC3 USB controller + * @dwc: Core DWC3 structure + * @dev: Pointer to the device structure + * @mmio_resource: Resource to be passed to dwc3_core_probe + * @apple_regs: Apple-specific DWC3 registers + * @resets: Reset control + * @role_sw: USB role switch + * @lock: Mutex for synchronizing access + * @state: Current state of the controller, see documentation for the enum for details + */ +struct dwc3_apple { + struct dwc3 dwc; + + struct device *dev; + struct resource *mmio_resource; + void __iomem *apple_regs; + + struct reset_control *resets; + struct usb_role_switch *role_sw; + + struct mutex lock; + + enum dwc3_apple_state state; +}; + +#define to_dwc3_apple(d) container_of((d), struct dwc3_apple, dwc) + +/* + * Apple Silicon dwc3 vendor-specific registers + * + * These registers were identified by tracing XNU's memory access patterns and correlating them with + * debug output over serial to determine their names. We don't exactly know what these do but + * without these USB3 devices sometimes don't work. + */ +#define APPLE_DWC3_REGS_START 0xcd00 +#define APPLE_DWC3_REGS_END 0xcdff + +#define APPLE_DWC3_CIO_LFPS_OFFSET 0xcd38 +#define APPLE_DWC3_CIO_LFPS_OFFSET_VALUE 0xf800f80 + +#define APPLE_DWC3_CIO_BW_NGT_OFFSET 0xcd3c +#define APPLE_DWC3_CIO_BW_NGT_OFFSET_VALUE 0xfc00fc0 + +#define APPLE_DWC3_CIO_LINK_TIMER 0xcd40 +#define APPLE_DWC3_CIO_PENDING_HP_TIMER GENMASK(23, 16) +#define APPLE_DWC3_CIO_PENDING_HP_TIMER_VALUE 0x14 +#define APPLE_DWC3_CIO_PM_LC_TIMER GENMASK(15, 8) +#define APPLE_DWC3_CIO_PM_LC_TIMER_VALUE 0xa +#define APPLE_DWC3_CIO_PM_ENTRY_TIMER GENMASK(7, 0) +#define APPLE_DWC3_CIO_PM_ENTRY_TIMER_VALUE 0x10 + +static inline void dwc3_apple_writel(struct dwc3_apple *appledwc, u32 offset, u32 value) +{ + writel(value, appledwc->apple_regs + offset - APPLE_DWC3_REGS_START); +} + +static inline u32 dwc3_apple_readl(struct dwc3_apple *appledwc, u32 offset) +{ + return readl(appledwc->apple_regs + offset - APPLE_DWC3_REGS_START); +} + +static inline void dwc3_apple_mask(struct dwc3_apple *appledwc, u32 offset, u32 mask, u32 value) +{ + u32 reg; + + reg = dwc3_apple_readl(appledwc, offset); + reg &= ~mask; + reg |= value; + dwc3_apple_writel(appledwc, offset, reg); +} + +static void dwc3_apple_setup_cio(struct dwc3_apple *appledwc) +{ + dwc3_apple_writel(appledwc, APPLE_DWC3_CIO_LFPS_OFFSET, APPLE_DWC3_CIO_LFPS_OFFSET_VALUE); + dwc3_apple_writel(appledwc, APPLE_DWC3_CIO_BW_NGT_OFFSET, + APPLE_DWC3_CIO_BW_NGT_OFFSET_VALUE); + dwc3_apple_mask(appledwc, APPLE_DWC3_CIO_LINK_TIMER, APPLE_DWC3_CIO_PENDING_HP_TIMER, + FIELD_PREP(APPLE_DWC3_CIO_PENDING_HP_TIMER, + APPLE_DWC3_CIO_PENDING_HP_TIMER_VALUE)); + dwc3_apple_mask(appledwc, APPLE_DWC3_CIO_LINK_TIMER, APPLE_DWC3_CIO_PM_LC_TIMER, + FIELD_PREP(APPLE_DWC3_CIO_PM_LC_TIMER, APPLE_DWC3_CIO_PM_LC_TIMER_VALUE)); + dwc3_apple_mask(appledwc, APPLE_DWC3_CIO_LINK_TIMER, APPLE_DWC3_CIO_PM_ENTRY_TIMER, + FIELD_PREP(APPLE_DWC3_CIO_PM_ENTRY_TIMER, + APPLE_DWC3_CIO_PM_ENTRY_TIMER_VALUE)); +} + +static void dwc3_apple_set_ptrcap(struct dwc3_apple *appledwc, u32 mode) +{ + guard(spinlock_irqsave)(&appledwc->dwc.lock); + dwc3_set_prtcap(&appledwc->dwc, mode, false); +} + +static int dwc3_apple_core_probe(struct dwc3_apple *appledwc) +{ + struct dwc3_probe_data probe_data = {}; + int ret; + + lockdep_assert_held(&appledwc->lock); + WARN_ON_ONCE(appledwc->state != DWC3_APPLE_PROBE_PENDING); + + appledwc->dwc.dev = appledwc->dev; + probe_data.dwc = &appledwc->dwc; + probe_data.res = appledwc->mmio_resource; + probe_data.ignore_clocks_and_resets = true; + probe_data.skip_core_init_mode = true; + probe_data.properties = DWC3_DEFAULT_PROPERTIES; + + ret = dwc3_core_probe(&probe_data); + if (ret) + return ret; + + appledwc->state = DWC3_APPLE_NO_CABLE; + return 0; +} + +static int dwc3_apple_core_init(struct dwc3_apple *appledwc) +{ + int ret; + + lockdep_assert_held(&appledwc->lock); + + switch (appledwc->state) { + case DWC3_APPLE_PROBE_PENDING: + ret = dwc3_apple_core_probe(appledwc); + if (ret) + dev_err(appledwc->dev, "Failed to probe DWC3 Core, err=%d\n", ret); + break; + case DWC3_APPLE_NO_CABLE: + ret = dwc3_core_init(&appledwc->dwc); + if (ret) + dev_err(appledwc->dev, "Failed to initialize DWC3 Core, err=%d\n", ret); + break; + default: + /* Unreachable unless there's a bug in this driver */ + WARN_ON_ONCE(1); + ret = -EINVAL; + break; + } + + return ret; +} + +static void dwc3_apple_phy_set_mode(struct dwc3_apple *appledwc, enum phy_mode mode) +{ + lockdep_assert_held(&appledwc->lock); + + /* + * This platform requires SUSPHY to be enabled here already in order to properly configure + * the PHY and switch dwc3's PIPE interface to USB3 PHY. + */ + dwc3_enable_susphy(&appledwc->dwc, true); + phy_set_mode(appledwc->dwc.usb2_generic_phy[0], mode); + phy_set_mode(appledwc->dwc.usb3_generic_phy[0], mode); +} + +static int dwc3_apple_init(struct dwc3_apple *appledwc, enum dwc3_apple_state state) +{ + int ret, ret_reset; + + lockdep_assert_held(&appledwc->lock); + + ret = reset_control_deassert(appledwc->resets); + if (ret) { + dev_err(appledwc->dev, "Failed to deassert resets, err=%d\n", ret); + return ret; + } + + ret = dwc3_apple_core_init(appledwc); + if (ret) + goto reset_assert; + + /* + * Now that the core is initialized and already went through dwc3_core_soft_reset we can + * configure some unknown Apple-specific settings and then bring up xhci or gadget mode. + */ + dwc3_apple_setup_cio(appledwc); + + switch (state) { + case DWC3_APPLE_HOST: + appledwc->dwc.dr_mode = USB_DR_MODE_HOST; + dwc3_apple_set_ptrcap(appledwc, DWC3_GCTL_PRTCAP_HOST); + dwc3_apple_phy_set_mode(appledwc, PHY_MODE_USB_HOST); + ret = dwc3_host_init(&appledwc->dwc); + if (ret) { + dev_err(appledwc->dev, "Failed to initialize host, ret=%d\n", ret); + goto core_exit; + } + + break; + case DWC3_APPLE_DEVICE: + appledwc->dwc.dr_mode = USB_DR_MODE_PERIPHERAL; + dwc3_apple_set_ptrcap(appledwc, DWC3_GCTL_PRTCAP_DEVICE); + dwc3_apple_phy_set_mode(appledwc, PHY_MODE_USB_DEVICE); + ret = dwc3_gadget_init(&appledwc->dwc); + if (ret) { + dev_err(appledwc->dev, "Failed to initialize gadget, ret=%d\n", ret); + goto core_exit; + } + break; + default: + /* Unreachable unless there's a bug in this driver */ + WARN_ON_ONCE(1); + ret = -EINVAL; + goto core_exit; + } + + appledwc->state = state; + return 0; + +core_exit: + dwc3_core_exit(&appledwc->dwc); +reset_assert: + ret_reset = reset_control_assert(appledwc->resets); + if (ret_reset) + dev_warn(appledwc->dev, "Failed to assert resets, err=%d\n", ret_reset); + + return ret; +} + +static int dwc3_apple_exit(struct dwc3_apple *appledwc) +{ + int ret = 0; + + lockdep_assert_held(&appledwc->lock); + + switch (appledwc->state) { + case DWC3_APPLE_PROBE_PENDING: + case DWC3_APPLE_NO_CABLE: + /* Nothing to do if we're already off */ + return 0; + case DWC3_APPLE_DEVICE: + dwc3_gadget_exit(&appledwc->dwc); + break; + case DWC3_APPLE_HOST: + dwc3_host_exit(&appledwc->dwc); + break; + } + + /* + * This platform requires SUSPHY to be enabled in order to properly power down the PHY + * and switch dwc3's PIPE interface back to a dummy PHY (i.e. no USB3 support and USB2 via + * a different PHY connected through ULPI). + */ + dwc3_enable_susphy(&appledwc->dwc, true); + dwc3_core_exit(&appledwc->dwc); + appledwc->state = DWC3_APPLE_NO_CABLE; + + ret = reset_control_assert(appledwc->resets); + if (ret) { + dev_err(appledwc->dev, "Failed to assert resets, err=%d\n", ret); + return ret; + } + + return 0; +} + +static int dwc3_usb_role_switch_set(struct usb_role_switch *sw, enum usb_role role) +{ + struct dwc3_apple *appledwc = usb_role_switch_get_drvdata(sw); + int ret; + + guard(mutex)(&appledwc->lock); + + /* + * We need to tear all of dwc3 down and re-initialize it every time a cable is + * connected or disconnected or when the mode changes. See the documentation for enum + * dwc3_apple_state for details. + */ + ret = dwc3_apple_exit(appledwc); + if (ret) + return ret; + + switch (role) { + case USB_ROLE_NONE: + /* Nothing to do if no cable is connected */ + return 0; + case USB_ROLE_HOST: + return dwc3_apple_init(appledwc, DWC3_APPLE_HOST); + case USB_ROLE_DEVICE: + return dwc3_apple_init(appledwc, DWC3_APPLE_DEVICE); + default: + dev_err(appledwc->dev, "Invalid target role: %d\n", role); + return -EINVAL; + } +} + +static enum usb_role dwc3_usb_role_switch_get(struct usb_role_switch *sw) +{ + struct dwc3_apple *appledwc = usb_role_switch_get_drvdata(sw); + + guard(mutex)(&appledwc->lock); + + switch (appledwc->state) { + case DWC3_APPLE_HOST: + return USB_ROLE_HOST; + case DWC3_APPLE_DEVICE: + return USB_ROLE_DEVICE; + case DWC3_APPLE_NO_CABLE: + case DWC3_APPLE_PROBE_PENDING: + return USB_ROLE_NONE; + default: + /* Unreachable unless there's a bug in this driver */ + dev_err(appledwc->dev, "Invalid internal state: %d\n", appledwc->state); + return USB_ROLE_NONE; + } +} + +static int dwc3_apple_setup_role_switch(struct dwc3_apple *appledwc) +{ + struct usb_role_switch_desc dwc3_role_switch = { NULL }; + + dwc3_role_switch.fwnode = dev_fwnode(appledwc->dev); + dwc3_role_switch.set = dwc3_usb_role_switch_set; + dwc3_role_switch.get = dwc3_usb_role_switch_get; + dwc3_role_switch.driver_data = appledwc; + appledwc->role_sw = usb_role_switch_register(appledwc->dev, &dwc3_role_switch); + if (IS_ERR(appledwc->role_sw)) + return PTR_ERR(appledwc->role_sw); + + return 0; +} + +static int dwc3_apple_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct dwc3_apple *appledwc; + int ret; + + appledwc = devm_kzalloc(&pdev->dev, sizeof(*appledwc), GFP_KERNEL); + if (!appledwc) + return -ENOMEM; + + appledwc->dev = &pdev->dev; + mutex_init(&appledwc->lock); + + appledwc->resets = devm_reset_control_array_get_exclusive(dev); + if (IS_ERR(appledwc->resets)) + return dev_err_probe(&pdev->dev, PTR_ERR(appledwc->resets), + "Failed to get resets\n"); + + ret = reset_control_assert(appledwc->resets); + if (ret) { + dev_err(&pdev->dev, "Failed to assert resets, err=%d\n", ret); + return ret; + } + + appledwc->mmio_resource = platform_get_resource_byname(pdev, IORESOURCE_MEM, "dwc3-core"); + if (!appledwc->mmio_resource) { + dev_err(dev, "Failed to get DWC3 MMIO\n"); + return -EINVAL; + } + + appledwc->apple_regs = devm_platform_ioremap_resource_byname(pdev, "dwc3-apple"); + if (IS_ERR(appledwc->apple_regs)) + return dev_err_probe(dev, PTR_ERR(appledwc->apple_regs), + "Failed to map Apple-specific MMIO\n"); + + /* + * On this platform, DWC3 can only be brought up after parts of the PHY have been + * initialized with knowledge of the target mode and cable orientation from typec_set_mux. + * Since this has not happened here we cannot setup DWC3 yet and instead defer this until + * the first cable is connected. See the documentation for enum dwc3_apple_state for + * details. + */ + appledwc->state = DWC3_APPLE_PROBE_PENDING; + ret = dwc3_apple_setup_role_switch(appledwc); + if (ret) + return dev_err_probe(&pdev->dev, ret, "Failed to setup role switch\n"); + + return 0; +} + +static void dwc3_apple_remove(struct platform_device *pdev) +{ + struct dwc3 *dwc = platform_get_drvdata(pdev); + struct dwc3_apple *appledwc = to_dwc3_apple(dwc); + + guard(mutex)(&appledwc->lock); + + usb_role_switch_unregister(appledwc->role_sw); + + /* + * If we're still in DWC3_APPLE_PROBE_PENDING we never got any cable connected event and + * dwc3_core_probe was never called and there's hence no need to call dwc3_core_remove. + * dwc3_apple_exit can be called unconditionally because it checks the state itself. + */ + dwc3_apple_exit(appledwc); + if (appledwc->state != DWC3_APPLE_PROBE_PENDING) + dwc3_core_remove(&appledwc->dwc); +} + +static const struct of_device_id dwc3_apple_of_match[] = { + { .compatible = "apple,t8103-dwc3" }, + {} +}; +MODULE_DEVICE_TABLE(of, dwc3_apple_of_match); + +static struct platform_driver dwc3_apple_driver = { + .probe = dwc3_apple_probe, + .remove = dwc3_apple_remove, + .driver = { + .name = "dwc3-apple", + .of_match_table = dwc3_apple_of_match, + }, +}; + +module_platform_driver(dwc3_apple_driver); + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Sven Peter "); +MODULE_DESCRIPTION("DesignWare DWC3 Apple Silicon Glue Driver"); From a02b5ed2fb8a751baefc585f1d8c4b65388bd170 Mon Sep 17 00:00:00 2001 From: Pritam Manohar Sutar Date: Fri, 24 Oct 2025 14:24:55 +0530 Subject: [PATCH 0361/3902] usb: dwc3: Allow usb role swich control from userspace There is a possibility of user needs for USB mode switching on boards that lack external hardware support for dynamic host/device role detection. This is particularly relevant in automotive applications where userspace applications need to switch USB roles (host to device) at runtime for CarPlay/Android Auto integration. Add an `allow_userspace_control` flag to handle such cases. When enabled, it exposes a sysfs attribute that allows userspace to switch the USB role manually between host and device. This provides flexibility for platforms that cannot rely on hardware-based mode detection. The role switch can be done as below echo host > /sys/class/usb_role/.usb-role-switch/role echo device > /sys/class/usb_role/.usb-role-switch/role Acked-by: Thinh Nguyen Signed-off-by: Pritam Manohar Sutar Link: https://patch.msgid.link/20251024085455.789555-1-pritam.sutar@samsung.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/dwc3/drd.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/usb/dwc3/drd.c b/drivers/usb/dwc3/drd.c index 4c91240eb4299a..589bbeb2745419 100644 --- a/drivers/usb/dwc3/drd.c +++ b/drivers/usb/dwc3/drd.c @@ -515,6 +515,7 @@ static int dwc3_setup_role_switch(struct dwc3 *dwc) dwc3_role_switch.set = dwc3_usb_role_switch_set; dwc3_role_switch.get = dwc3_usb_role_switch_get; dwc3_role_switch.driver_data = dwc; + dwc3_role_switch.allow_userspace_control = true; dwc->role_sw = usb_role_switch_register(dwc->dev, &dwc3_role_switch); if (IS_ERR(dwc->role_sw)) return PTR_ERR(dwc->role_sw); From 04a093d3f0e5bd69b1e9051ee0e21ab474f0991f Mon Sep 17 00:00:00 2001 From: Sven Peter Date: Sun, 26 Oct 2025 12:21:36 +0000 Subject: [PATCH 0362/3902] usb: dwc3: apple: Only support a single reset controller As pointed out by Philipp, Apple's dwc3 controller only uses a single reset line and there's thus no need to use reset controller array functions. The only functional change here is replacing devm_reset_control_array_get_exclusive with devm_reset_control_get_exclusive. The rest are only cosmetic changes to replace "resets" with "reset". Reported-by: Philipp Zabel Closes: https://lore.kernel.org/asahi/47112ace39ea096242e68659d67a401e931abf3a.camel@pengutronix.de/ Fixes: 0ec946d32ef7 ("usb: dwc3: Add Apple Silicon DWC3 glue layer driver") Signed-off-by: Sven Peter Link: https://patch.msgid.link/20251026-b4-dwc3-apple-reset-array-fix-v1-1-ccdbacd63f78@kernel.org Signed-off-by: Greg Kroah-Hartman --- drivers/usb/dwc3/dwc3-apple.c | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/drivers/usb/dwc3/dwc3-apple.c b/drivers/usb/dwc3/dwc3-apple.c index 6e41bd0e34f461..cc47cad232e397 100644 --- a/drivers/usb/dwc3/dwc3-apple.c +++ b/drivers/usb/dwc3/dwc3-apple.c @@ -81,7 +81,7 @@ enum dwc3_apple_state { * @dev: Pointer to the device structure * @mmio_resource: Resource to be passed to dwc3_core_probe * @apple_regs: Apple-specific DWC3 registers - * @resets: Reset control + * @reset: Reset control * @role_sw: USB role switch * @lock: Mutex for synchronizing access * @state: Current state of the controller, see documentation for the enum for details @@ -93,7 +93,7 @@ struct dwc3_apple { struct resource *mmio_resource; void __iomem *apple_regs; - struct reset_control *resets; + struct reset_control *reset; struct usb_role_switch *role_sw; struct mutex lock; @@ -237,9 +237,9 @@ static int dwc3_apple_init(struct dwc3_apple *appledwc, enum dwc3_apple_state st lockdep_assert_held(&appledwc->lock); - ret = reset_control_deassert(appledwc->resets); + ret = reset_control_deassert(appledwc->reset); if (ret) { - dev_err(appledwc->dev, "Failed to deassert resets, err=%d\n", ret); + dev_err(appledwc->dev, "Failed to deassert reset, err=%d\n", ret); return ret; } @@ -288,9 +288,9 @@ static int dwc3_apple_init(struct dwc3_apple *appledwc, enum dwc3_apple_state st core_exit: dwc3_core_exit(&appledwc->dwc); reset_assert: - ret_reset = reset_control_assert(appledwc->resets); + ret_reset = reset_control_assert(appledwc->reset); if (ret_reset) - dev_warn(appledwc->dev, "Failed to assert resets, err=%d\n", ret_reset); + dev_warn(appledwc->dev, "Failed to assert reset, err=%d\n", ret_reset); return ret; } @@ -323,9 +323,9 @@ static int dwc3_apple_exit(struct dwc3_apple *appledwc) dwc3_core_exit(&appledwc->dwc); appledwc->state = DWC3_APPLE_NO_CABLE; - ret = reset_control_assert(appledwc->resets); + ret = reset_control_assert(appledwc->reset); if (ret) { - dev_err(appledwc->dev, "Failed to assert resets, err=%d\n", ret); + dev_err(appledwc->dev, "Failed to assert reset, err=%d\n", ret); return ret; } @@ -411,14 +411,14 @@ static int dwc3_apple_probe(struct platform_device *pdev) appledwc->dev = &pdev->dev; mutex_init(&appledwc->lock); - appledwc->resets = devm_reset_control_array_get_exclusive(dev); - if (IS_ERR(appledwc->resets)) - return dev_err_probe(&pdev->dev, PTR_ERR(appledwc->resets), - "Failed to get resets\n"); + appledwc->reset = devm_reset_control_get_exclusive(dev, NULL); + if (IS_ERR(appledwc->reset)) + return dev_err_probe(&pdev->dev, PTR_ERR(appledwc->reset), + "Failed to get reset control\n"); - ret = reset_control_assert(appledwc->resets); + ret = reset_control_assert(appledwc->reset); if (ret) { - dev_err(&pdev->dev, "Failed to assert resets, err=%d\n", ret); + dev_err(&pdev->dev, "Failed to assert reset, err=%d\n", ret); return ret; } From e66d189d807fceb5ae8d5a5b9566800505d1c32f Mon Sep 17 00:00:00 2001 From: Sven Peter Date: Sat, 20 Sep 2025 12:28:03 +0000 Subject: [PATCH 0363/3902] usb: typec: tipd: Fix error handling in cd321x_read_data_status Right now cd321x_read_data_status always returns true even if it encounters any errors: tps6598x_read_data_status returns a boolean but we treated it as an errno and then we have a bunch of dev_errs in case tps6598x_block_read fails but just continue along and return true. Fix that to correctly report errors to the callee. Reported-by: Dan Carpenter Closes: https://lore.kernel.org/linux-usb/aMvWJo3IkClmFoAA@stanley.mountain/ Signed-off-by: Sven Peter Reviewed-by: Heikki Krogerus Link: https://lore.kernel.org/r/20250920-tipd-fix-v1-1-49886d4f081d@kernel.org Signed-off-by: Greg Kroah-Hartman --- drivers/usb/typec/tipd/core.c | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/drivers/usb/typec/tipd/core.c b/drivers/usb/typec/tipd/core.c index 2b1049c9a6f3c4..d0c86347251c5c 100644 --- a/drivers/usb/typec/tipd/core.c +++ b/drivers/usb/typec/tipd/core.c @@ -577,30 +577,36 @@ static bool cd321x_read_data_status(struct tps6598x *tps) int ret; ret = tps6598x_read_data_status(tps); - if (ret < 0) + if (!ret) return false; if (tps->data_status & TPS_DATA_STATUS_DP_CONNECTION) { ret = tps6598x_block_read(tps, TPS_REG_DP_SID_STATUS, &cd321x->dp_sid_status, sizeof(cd321x->dp_sid_status)); - if (ret) + if (ret) { dev_err(tps->dev, "Failed to read DP SID Status: %d\n", ret); + return false; + } } if (tps->data_status & TPS_DATA_STATUS_TBT_CONNECTION) { ret = tps6598x_block_read(tps, TPS_REG_INTEL_VID_STATUS, &cd321x->intel_vid_status, sizeof(cd321x->intel_vid_status)); - if (ret) + if (ret) { dev_err(tps->dev, "Failed to read Intel VID Status: %d\n", ret); + return false; + } } if (tps->data_status & CD321X_DATA_STATUS_USB4_CONNECTION) { ret = tps6598x_block_read(tps, TPS_REG_USB4_STATUS, &cd321x->usb4_status, sizeof(cd321x->usb4_status)); - if (ret) + if (ret) { dev_err(tps->dev, "Failed to read USB4 Status: %d\n", ret); + return false; + } } return true; From 934763c2069d2f8e769fde1dfdf4760ade1d43dd Mon Sep 17 00:00:00 2001 From: Peter Korsgaard Date: Fri, 7 Nov 2025 16:13:10 +0100 Subject: [PATCH 0364/3902] usb: typec: tipd: mark as orientation aware The driver contains orientation detection logic and correctly calls typec_set_orientation(), but forgets to set the orientation_aware capability, so the orientation value is not visible in sysfs - Fix that. Signed-off-by: Peter Korsgaard Reviewed-by: Heikki Krogerus Link: https://patch.msgid.link/20251107151311.2089806-1-peter@korsgaard.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/typec/tipd/core.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/usb/typec/tipd/core.c b/drivers/usb/typec/tipd/core.c index d0c86347251c5c..e2b26af2b84a8d 100644 --- a/drivers/usb/typec/tipd/core.c +++ b/drivers/usb/typec/tipd/core.c @@ -1701,6 +1701,7 @@ tps25750_register_port(struct tps6598x *tps, struct fwnode_handle *fwnode) typec_cap.data = ret; typec_cap.revision = USB_TYPEC_REV_1_3; typec_cap.pd_revision = 0x300; + typec_cap.orientation_aware = true; typec_cap.driver_data = tps; typec_cap.ops = &tps6598x_ops; typec_cap.fwnode = fwnode; From 99231e1be1da937572551616184465ca0bb2cb04 Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Mon, 15 Sep 2025 17:40:22 +0200 Subject: [PATCH 0365/3902] usb: typec: tipd: Add cd321x specific control commands CD321x support USB-PD commands to control connected devices which use CD321x as well. The most useful commands can reboot the connected device and route a debug UART over SBU pins. The linked tuxvdmtool exists as simple tool to trigger these actions. Link: https://asahilinux.org/docs/hw/soc/usb-pd/ Link: https://github.com/AsahiLinux/tuxvdmtool Co-developed-by: Martin R Signed-off-by: Martin R Signed-off-by: Janne Grunau --- drivers/usb/typec/tipd/core.c | 190 ++++++++++++++++++++++++++++++++++ 1 file changed, 190 insertions(+) diff --git a/drivers/usb/typec/tipd/core.c b/drivers/usb/typec/tipd/core.c index e2b26af2b84a8d..923477105483b1 100644 --- a/drivers/usb/typec/tipd/core.c +++ b/drivers/usb/typec/tipd/core.c @@ -22,6 +22,8 @@ #include #include #include +#include +#include #include "tps6598x.h" #include "trace.h" @@ -47,6 +49,7 @@ #define TPS_REG_POWER_STATUS 0x3f #define TPS_REG_PD_STATUS 0x40 #define TPS_REG_RX_IDENTITY_SOP 0x48 +#define TPS_REG_TX_VDM 0x4d #define TPS_REG_CF_VID_STATUS 0x5e #define TPS_REG_DP_SID_STATUS 0x58 #define TPS_REG_INTEL_VID_STATUS 0x59 @@ -130,6 +133,7 @@ enum { TPS_MODE_BIST, TPS_MODE_DISC, TPS_MODE_PTCH, + CD_MODE_DBMA, }; static const char *const modes[] = { @@ -138,11 +142,14 @@ static const char *const modes[] = { [TPS_MODE_BIST] = "BIST", [TPS_MODE_DISC] = "DISC", [TPS_MODE_PTCH] = "PTCH", + [CD_MODE_DBMA] = "DBMa", }; /* Unrecognized commands will be replaced with "!CMD" */ #define INVALID_CMD(_cmd_) (_cmd_ == 0x444d4321) +#define TPS_VDMS_MAX_LEN (7 * 4 + 1) + struct tps6598x; struct tipd_data { @@ -168,6 +175,7 @@ struct tps6598x { struct regmap *regmap; struct mutex lock; /* device lock */ u8 i2c_protocol:1; + u8 cd321x_unlocked:1; struct gpio_desc *reset; struct typec_port *port; @@ -1076,6 +1084,8 @@ static int tps6598x_check_mode(struct tps6598x *tps) return ret; case TPS_MODE_BIST: case TPS_MODE_DISC: + case CD_MODE_DBMA: + return ret; default: dev_err(tps->dev, "controller in unsupported mode \"%s\"\n", mode); @@ -1631,6 +1641,175 @@ static int tps6598x_apply_patch(struct tps6598x *tps) return ret; } + +static ssize_t lock_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct i2c_client *client = to_i2c_client(dev); + struct tps6598x *tps = i2c_get_clientdata(client); + + if (tps->cd321x_unlocked) + return sysfs_emit(buf, "unlocked\n"); + else + return sysfs_emit(buf, "locked\n"); +} +static DEVICE_ATTR_RO(lock); + +static ssize_t mode_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct i2c_client *client = to_i2c_client(dev); + struct tps6598x *tps = i2c_get_clientdata(client); + + int mode = tps6598x_check_mode(tps); + switch (mode) { + case TPS_MODE_APP ... CD_MODE_DBMA: + return sysfs_emit(buf, "%s\n", modes[mode]); + default: + return sysfs_emit(buf, "unkown\n"); + } +} +static DEVICE_ATTR_RO(mode); + +static ssize_t power_status_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct i2c_client *client = to_i2c_client(dev); + struct tps6598x *tps = i2c_get_clientdata(client); + + return sysfs_emit(buf, "0x%04hx\n", tps->pwr_status); +} +static DEVICE_ATTR_RO(power_status); + +/* this assumes cd321x has a customized UnlockCode */ +static ssize_t commad_lock(struct tps6598x *tps, const char *buf, size_t count) +{ + const char default_code[4] = { 0, 0, 0, 0 }; + int ret; + + if (count < 4) + return -EINVAL; + + ret = tps6598x_exec_cmd(tps, "LOCK", 4, buf, 0, NULL); + if (ret) + dev_err(tps->dev, "Unlock command failed: %d\n", ret); + else + /* Key 0 locks cd321x when UnlockCode is customized */ + tps->cd321x_unlocked = !!memcmp(buf, default_code, 4); + + return count; +} + +static ssize_t commad_dbma(struct tps6598x *tps, const char *buf, size_t count) +{ + int ret, mode; + bool enable; + + if (count < 1) + return -EINVAL; + + enable = buf[0] != 0; + + if (enable && !tps->cd321x_unlocked) + return -EINVAL; + + ret = tps6598x_exec_cmd(tps, "DBMa", 1, buf, 0, NULL); + if (ret) { + dev_err(tps->dev, "Failed to exec 'DBMa' command: %d\n", ret); + return ret; + } + + mode = tps6598x_check_mode(tps); + if (enable && mode != CD_MODE_DBMA) { + dev_err(tps->dev, "CD321x failed to enter \"DBMa\" mode\n"); + return -EIO; + } else if (!enable && mode != TPS_MODE_APP) { + dev_err(tps->dev, "CD321x failed to exit \"DBMa\" mode\n"); + return -EIO; + } + + return count; +} + +static ssize_t commad_vdms(struct tps6598x *tps, const char *buf, size_t count) +{ + int ret; + + if (count < 5 || ((count -1) % 4) != 0 || count > TPS_VDMS_MAX_LEN) + return -EINVAL; + + if (tps6598x_check_mode(tps) != CD_MODE_DBMA) + return -EIO; + + ret = tps6598x_exec_cmd_tmo(tps, "VDMs", count, buf, 0, NULL, 200, 200); + if (ret) { + dev_err(tps->dev, "Sending VDM failed: %d\n", ret); + return ret; + } + + return count; +} + +static ssize_t commad_devn(struct tps6598x *tps, const char *buf, size_t count) +{ + int ret; + + if (count < 4) + return -EINVAL; + + if (tps6598x_check_mode(tps) != CD_MODE_DBMA) + return -EIO; + + ret = tps6598x_exec_cmd(tps, "DVEn", 4, buf, 0, NULL); + if (ret) + dev_err(tps->dev, "Could not enter local serial mode: %d\n", + ret); + + return count; +} + +#define CMD_LEN 4 +static ssize_t command_store(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct i2c_client *client = to_i2c_client(dev); + struct tps6598x *tps = i2c_get_clientdata(client); + int ret; + + if (count < CMD_LEN) + return -EINVAL; + + if (memcmp(buf, "LOCK", CMD_LEN) == 0) + ret = commad_lock(tps, buf + 4, count - 4); + else if (memcmp(buf, "DBMa", CMD_LEN) == 0) + ret = commad_dbma(tps, buf + 4, count - 4); + else if (memcmp(buf, "VDMs", CMD_LEN) == 0) + ret = commad_vdms(tps, buf + 4, count - 4); + else if (memcmp(buf, "DEVn", CMD_LEN) == 0) + ret = commad_devn(tps, buf + 4, count - 4); + else + return -EINVAL; + + if (ret < 0) + return ret; + + return ret + CMD_LEN; +} +static DEVICE_ATTR_WO(command); + +static struct attribute *vdm_attrs[] = { + &dev_attr_lock.attr, + &dev_attr_mode.attr, + &dev_attr_power_status.attr, + &dev_attr_command.attr, + NULL, +}; + +static const struct attribute_group vdm_group = { + .name = "cd321x_vdm", + .attrs = vdm_attrs, +}; + static int cd321x_init(struct tps6598x *tps) { return 0; @@ -1866,6 +2045,14 @@ static int tps6598x_probe(struct i2c_client *client) enable_irq_wake(client->irq); } + if (device_is_compatible(tps->dev, "apple,cd321x")) { + int err; + err = sysfs_create_group(&client->dev.kobj, &vdm_group); + if (err < 0) + dev_err(tps->dev, "Couldn't register sysfs group for " + "CD321x VDMs\n"); + } + return 0; err_disconnect: @@ -1889,6 +2076,9 @@ static void tps6598x_remove(struct i2c_client *client) { struct tps6598x *tps = i2c_get_clientdata(client); + if (device_is_compatible(tps->dev, "apple,cd321x")) + sysfs_remove_group(&client->dev.kobj, &vdm_group); + if (!client->irq) cancel_delayed_work_sync(&tps->wq_poll); else From f69f6df37ac66acfa81efe19b58ab54c1ddfaf13 Mon Sep 17 00:00:00 2001 From: Sven Peter Date: Sun, 14 Dec 2025 11:51:35 +0000 Subject: [PATCH 0366/3902] dt-bindings: phy: Add Apple Type-C PHY Apple's Type-C PHY (ATCPHY) is a PHY for USB 2.0, USB 3.x, USB4/Thunderbolt, and DisplayPort connectivity found in Apple Silicon SoCs. The PHY handles muxing between these different protocols and also provides the reset controller for the attached dwc3 USB controller. Reviewed-by: Neal Gompa Reviewed-by: Rob Herring (Arm) Signed-off-by: Sven Peter --- .../devicetree/bindings/phy/apple,atcphy.yaml | 222 ++++++++++++++++++ MAINTAINERS | 1 + 2 files changed, 223 insertions(+) create mode 100644 Documentation/devicetree/bindings/phy/apple,atcphy.yaml diff --git a/Documentation/devicetree/bindings/phy/apple,atcphy.yaml b/Documentation/devicetree/bindings/phy/apple,atcphy.yaml new file mode 100644 index 00000000000000..0acac7e3ee67ef --- /dev/null +++ b/Documentation/devicetree/bindings/phy/apple,atcphy.yaml @@ -0,0 +1,222 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/phy/apple,atcphy.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Apple Type-C PHY (ATCPHY) + +maintainers: + - Sven Peter + +description: > + The Apple Type-C PHY (ATCPHY) is a combined PHY for USB 2.0, USB 3.x, + USB4/Thunderbolt, and DisplayPort connectivity via Type-C ports found in + Apple Silicon SoCs. + + The PHY handles muxing between these different protocols and also provides the + reset controller for the attached DWC3 USB controller. + + It is designed for USB4 operation and does not handle individual differential + pairs as distinct DisplayPort lanes. Any reference to lane in this binding + hence refers to two differential pairs (RX and TX) as used in USB terminology. + + In order to correctly setup these lanes for the various modes calibration + values copied from Apple's firmware and converted to the format described + below by our bootloader m1n1 are required. Without these only USB2 operation + is possible. + +allOf: + - $ref: /schemas/usb/usb-switch.yaml# + +$defs: + apple,tunable: + $ref: /schemas/types.yaml#/definitions/uint32-matrix + items: + items: + - description: Register offset + - description: Mask to be applied to the register value + - description: Bits to be set after applying the mask + description: > + List of (register offset, mask, value) tuples copied from Apple's Device + Tree by our bootloader m1n1 and used to configure the PHY. These values + even vary for a single product/device and likely contain calibration + values determined by Apple at manufacturing time. + Unless otherwise noted these tunables are always applied to the core + register region. + +properties: + compatible: + oneOf: + - items: + - enum: + - apple,t6000-atcphy + - apple,t6020-atcphy + - apple,t8112-atcphy + - const: apple,t8103-atcphy + - const: apple,t8103-atcphy + + reg: + items: + - description: Common controls for all PHYs (USB2/3/4, DisplayPort, TBT) + - description: DisplayPort Alternate Mode PHY specific controls + - description: Type-C PHY AXI to Apple Fabric interconnect controls + - description: USB2 PHY specific controls + - description: USB3 PIPE interface controls + + reg-names: + items: + - const: core + - const: lpdptx + - const: axi2af + - const: usb2phy + - const: pipehandler + + "#phy-cells": + const: 1 + + "#reset-cells": + const: 0 + + mode-switch: true + orientation-switch: true + + power-domains: + maxItems: 1 + + ports: + $ref: /schemas/graph.yaml#/properties/ports + properties: + port@0: + $ref: /schemas/graph.yaml#/properties/port + description: Outgoing connection to the SS port of the Type-C connector. + + port@1: + $ref: /schemas/graph.yaml#/properties/port + description: Incoming endpoint from the USB3 controller. + + port@2: + $ref: /schemas/graph.yaml#/properties/port + description: Incoming endpoint from the DisplayPort controller. + + port@3: + $ref: /schemas/graph.yaml#/properties/port + description: Incoming endpoint from the USB4/Thunderbolt controller. + + apple,tunable-common-a: + $ref: "#/$defs/apple,tunable" + description: > + Common tunables required for all modes, applied before tunable-axi2af. + + apple,tunable-axi2af: + $ref: "#/$defs/apple,tunable" + description: > + AXI to Apple Fabric tunables, required for all modes. Unlike all other + tunables these are applied to the axi2af region. + + apple,tunable-common-b: + $ref: "#/$defs/apple,tunable" + description: > + Common tunables required for all modes, applied after tunable-axi2af. + + apple,tunable-lane0-usb: + $ref: "#/$defs/apple,tunable" + description: USB3 tunables for lane 0. + + apple,tunable-lane1-usb: + $ref: "#/$defs/apple,tunable" + description: USB3 tunables for lane 1. + + apple,tunable-lane0-cio: + $ref: "#/$defs/apple,tunable" + description: USB4/Thunderbolt ("Converged IO") tunables for lane 0. + + apple,tunable-lane1-cio: + $ref: "#/$defs/apple,tunable" + description: USB4/Thunderbolt ("Converged IO") tunables for lane 1. + + apple,tunable-lane0-dp: + $ref: "#/$defs/apple,tunable" + description: > + DisplayPort tunables for lane 0. + + Note that lane here refers to a USB RX and TX pair re-used for DisplayPort + and not to an individual DisplayPort differential lane. + + apple,tunable-lane1-dp: + $ref: "#/$defs/apple,tunable" + description: > + DisplayPort tunables for lane 1. + + Note that lane here refers to a USB RX and TX pair re-used for DisplayPort + and not to an individual DisplayPort differential lane. + +required: + - compatible + - reg + - reg-names + - "#phy-cells" + - "#reset-cells" + - orientation-switch + - mode-switch + - power-domains + - ports + +additionalProperties: false + +examples: + - | + phy@83000000 { + compatible = "apple,t8103-atcphy"; + reg = <0x83000000 0x4c000>, + <0x83050000 0x8000>, + <0x80000000 0x4000>, + <0x82a90000 0x4000>, + <0x82a84000 0x4000>; + reg-names = "core", "lpdptx", "axi2af", "usb2phy", + "pipehandler"; + + #phy-cells = <1>; + #reset-cells = <0>; + + orientation-switch; + mode-switch; + power-domains = <&ps_atc0_usb>; + + ports { + #address-cells = <1>; + #size-cells = <0>; + + port@0 { + reg = <0>; + + endpoint { + remote-endpoint = <&typec_connector_ss>; + }; + }; + + port@1 { + reg = <1>; + + endpoint { + remote-endpoint = <&dwc3_ss_out>; + }; + }; + + port@2 { + reg = <2>; + + endpoint { + remote-endpoint = <&dcp_dp_out>; + }; + }; + + port@3 { + reg = <3>; + + endpoint { + remote-endpoint = <&acio_tbt_out>; + }; + }; + }; + }; diff --git a/MAINTAINERS b/MAINTAINERS index e8f06145fb54c9..d48c789907348e 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -2436,6 +2436,7 @@ F: Documentation/devicetree/bindings/nvme/apple,nvme-ans.yaml F: Documentation/devicetree/bindings/nvmem/apple,efuses.yaml F: Documentation/devicetree/bindings/nvmem/apple,spmi-nvmem.yaml F: Documentation/devicetree/bindings/pci/apple,pcie.yaml +F: Documentation/devicetree/bindings/phy/apple,atcphy.yaml F: Documentation/devicetree/bindings/pinctrl/apple,pinctrl.yaml F: Documentation/devicetree/bindings/power/apple* F: Documentation/devicetree/bindings/power/reset/apple,smc-reboot.yaml From 62a71b08352ac6c94019b9a0fc1009a1f2feb411 Mon Sep 17 00:00:00 2001 From: Sven Peter Date: Sun, 14 Dec 2025 11:51:36 +0000 Subject: [PATCH 0367/3902] phy: apple: Add Apple Type-C PHY The Apple Type-C PHY (ATCPHY) is a PHY for USB 2.0, USB 3.x, USB4/Thunderbolt, and DisplayPort connectivity found in Apple Silicon SoCs. The PHY handles muxing between these different protocols and also provides the reset controller for the attached dwc3 USB controller. There is no documentation available for this PHY and the entire sequence of MMIO pokes has been figured out by tracing all MMIO access of Apple's driver under a thin hypervisor and correlating the register reads/writes to their kernel's debug output to find their names. Deviations from this sequence generally results in the port not working or, especially when the mode is switched to USB4 or Thunderbolt, to some watchdog resetting the entire SoC. This initial commit already introduces support for Display Port and USB4/Thunderbolt but the drivers for these are not ready. We cannot control the alternate mode negotiation and are stuck with whatever Apple's firmware decides such that any DisplayPort or USB4/Thunderbolt device will result in a correctly setup PHY but not be usable until the other drivers are upstreamed as well. Co-developed-by: Janne Grunau Co-developed-by: Hector Martin Signed-off-by: Hector Martin Reviewed-by: Philipp Zabel # for reset controller Reviewed-by: Neal Gompa Signed-off-by: Sven Peter Signed-off-by: Janne Grunau --- MAINTAINERS | 1 + drivers/phy/Kconfig | 1 + drivers/phy/Makefile | 1 + drivers/phy/apple/Kconfig | 14 + drivers/phy/apple/Makefile | 4 + drivers/phy/apple/atc.c | 2294 ++++++++++++++++++++++++++++++++++++ 6 files changed, 2315 insertions(+) create mode 100644 drivers/phy/apple/Kconfig create mode 100644 drivers/phy/apple/Makefile create mode 100644 drivers/phy/apple/atc.c diff --git a/MAINTAINERS b/MAINTAINERS index d48c789907348e..b1ad1c5b9cb746 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -2461,6 +2461,7 @@ F: drivers/mfd/macsmc.c F: drivers/nvme/host/apple.c F: drivers/nvmem/apple-efuses.c F: drivers/nvmem/apple-spmi-nvmem.c +F: drivers/phy/apple/ F: drivers/pinctrl/pinctrl-apple-gpio.c F: drivers/power/reset/macsmc-reboot.c F: drivers/pwm/pwm-apple.c diff --git a/drivers/phy/Kconfig b/drivers/phy/Kconfig index 678dd0452f0aa0..2fb5e3b313effc 100644 --- a/drivers/phy/Kconfig +++ b/drivers/phy/Kconfig @@ -103,6 +103,7 @@ config PHY_NXP_PTN3222 source "drivers/phy/allwinner/Kconfig" source "drivers/phy/amlogic/Kconfig" +source "drivers/phy/apple/Kconfig" source "drivers/phy/broadcom/Kconfig" source "drivers/phy/cadence/Kconfig" source "drivers/phy/freescale/Kconfig" diff --git a/drivers/phy/Makefile b/drivers/phy/Makefile index bfb27fb5a49428..eb84c3328d1062 100644 --- a/drivers/phy/Makefile +++ b/drivers/phy/Makefile @@ -15,6 +15,7 @@ obj-$(CONFIG_PHY_AIROHA_PCIE) += phy-airoha-pcie.o obj-$(CONFIG_PHY_NXP_PTN3222) += phy-nxp-ptn3222.o obj-y += allwinner/ \ amlogic/ \ + apple/ \ broadcom/ \ cadence/ \ freescale/ \ diff --git a/drivers/phy/apple/Kconfig b/drivers/phy/apple/Kconfig new file mode 100644 index 00000000000000..67f46051259260 --- /dev/null +++ b/drivers/phy/apple/Kconfig @@ -0,0 +1,14 @@ +# SPDX-License-Identifier: GPL-2.0 OR BSD-2-Clause +config PHY_APPLE_ATC + tristate "Apple Type-C PHY" + depends on (ARM64 && ARCH_APPLE) || (COMPILE_TEST && !GENERIC_ATOMIC64) + depends on TYPEC + select GENERIC_PHY + select APPLE_TUNABLE + help + Enable this to add support for the Apple Type-C PHY found in + Apple Silicon M-series SoCs. This PHY supports USB2, + USB3, USB4, Thunderbolt, and DisplayPort. + + If M is selected the module will be called 'phy-apple-atc'. + diff --git a/drivers/phy/apple/Makefile b/drivers/phy/apple/Makefile new file mode 100644 index 00000000000000..e02836a63df3b5 --- /dev/null +++ b/drivers/phy/apple/Makefile @@ -0,0 +1,4 @@ +# SPDX-License-Identifier: GPL-2.0 OR BSD-2-Clause + +obj-$(CONFIG_PHY_APPLE_ATC) += phy-apple-atc.o +phy-apple-atc-y := atc.o diff --git a/drivers/phy/apple/atc.c b/drivers/phy/apple/atc.c new file mode 100644 index 00000000000000..c8a58ee64b7aad --- /dev/null +++ b/drivers/phy/apple/atc.c @@ -0,0 +1,2294 @@ +// SPDX-License-Identifier: GPL-2.0 OR BSD-2-Clause +/* + * Apple Type-C PHY driver + * + * The Apple Type-C PHY (ATCPHY) is a combined PHY for USB 2.0, USB 3.x, + * USB4/Thunderbolt, and DisplayPort connectivity via Type-C ports found in + * Apple Silicon SoCs. + * + * The PHY handles muxing between these different protocols and also provides the + * reset controller for the attached DWC3 USB controller. + * + * No documentation for this PHY is available and its operation has been + * reverse engineered by observing the XNU's MMIO access using a thin hypervisor + * and correlating register access to XNU's very verbose debug output. Most + * register names comes from this debug output as well. + * + * In order to correctly setup the high speed lanes for the various modes + * calibration values copied from Apple's firmware by our bootloader m1n1 are + * required. Without these only USB2 operation is possible. + * + * Copyright (C) The Asahi Linux Contributors + * Author: Sven Peter + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define AUSPLL_FSM_CTRL 0x1014 + +#define AUSPLL_APB_CMD_OVERRIDE 0x2000 +#define AUSPLL_APB_CMD_OVERRIDE_REQ BIT(0) +#define AUSPLL_APB_CMD_OVERRIDE_ACK BIT(1) +#define AUSPLL_APB_CMD_OVERRIDE_UNK28 BIT(28) +#define AUSPLL_APB_CMD_OVERRIDE_CMD GENMASK(27, 3) + +#define AUSPLL_FREQ_DESC_A 0x2080 +#define AUSPLL_FD_FREQ_COUNT_TARGET GENMASK(9, 0) +#define AUSPLL_FD_FBDIVN_HALF BIT(10) +#define AUSPLL_FD_REV_DIVN GENMASK(13, 11) +#define AUSPLL_FD_KI_MAN GENMASK(17, 14) +#define AUSPLL_FD_KI_EXP GENMASK(21, 18) +#define AUSPLL_FD_KP_MAN GENMASK(25, 22) +#define AUSPLL_FD_KP_EXP GENMASK(29, 26) +#define AUSPLL_FD_KPKI_SCALE_HBW GENMASK(31, 30) + +#define AUSPLL_FREQ_DESC_B 0x2084 +#define AUSPLL_FD_FBDIVN_FRAC_DEN GENMASK(13, 0) +#define AUSPLL_FD_FBDIVN_FRAC_NUM GENMASK(27, 14) + +#define AUSPLL_FREQ_DESC_C 0x2088 +#define AUSPLL_FD_SDM_SSC_STEP GENMASK(7, 0) +#define AUSPLL_FD_SDM_SSC_EN BIT(8) +#define AUSPLL_FD_PCLK_DIV_SEL GENMASK(13, 9) +#define AUSPLL_FD_LFSDM_DIV GENMASK(15, 14) +#define AUSPLL_FD_LFCLK_CTRL GENMASK(19, 16) +#define AUSPLL_FD_VCLK_OP_DIVN GENMASK(21, 20) +#define AUSPLL_FD_VCLK_PRE_DIVN BIT(22) + +#define AUSPLL_DCO_EFUSE_SPARE 0x222c +#define AUSPLL_RODCO_ENCAP_EFUSE GENMASK(10, 9) +#define AUSPLL_RODCO_BIAS_ADJUST_EFUSE GENMASK(14, 12) + +#define AUSPLL_FRACN_CAN 0x22a4 +#define AUSPLL_DLL_START_CAPCODE GENMASK(18, 17) + +#define AUSPLL_CLKOUT_MASTER 0x2200 +#define AUSPLL_CLKOUT_MASTER_PCLK_DRVR_EN BIT(2) +#define AUSPLL_CLKOUT_MASTER_PCLK2_DRVR_EN BIT(4) +#define AUSPLL_CLKOUT_MASTER_REFBUFCLK_DRVR_EN BIT(6) + +#define AUSPLL_CLKOUT_DIV 0x2208 +#define AUSPLL_CLKOUT_PLLA_REFBUFCLK_DI GENMASK(20, 16) + +#define AUSPLL_BGR 0x2214 +#define AUSPLL_BGR_CTRL_AVAIL BIT(0) + +#define AUSPLL_CLKOUT_DTC_VREG 0x2220 +#define AUSPLL_DTC_VREG_ADJUST GENMASK(16, 14) +#define AUSPLL_DTC_VREG_BYPASS BIT(7) + +#define AUSPLL_FREQ_CFG 0x2224 +#define AUSPLL_FREQ_REFCLK GENMASK(1, 0) + +#define AUS_COMMON_SHIM_BLK_VREG 0x0a04 +#define AUS_VREG_TRIM GENMASK(6, 2) + +#define AUS_UNK_A20 0x0a20 +#define AUS_UNK_A20_TX_CAL_CODE GENMASK(23, 20) + +#define ACIOPHY_CMN_SHM_STS_REG0 0x0a74 +#define ACIOPHY_CMN_SHM_STS_REG0_CMD_READY BIT(0) + +#define CIO3PLL_CLK_CTRL 0x2a00 +#define CIO3PLL_CLK_PCLK_EN BIT(1) +#define CIO3PLL_CLK_REFCLK_EN BIT(5) + +#define CIO3PLL_DCO_NCTRL 0x2a38 +#define CIO3PLL_DCO_COARSEBIN_EFUSE0 GENMASK(6, 0) +#define CIO3PLL_DCO_COARSEBIN_EFUSE1 GENMASK(23, 17) + +#define CIO3PLL_FRACN_CAN 0x2aa4 +#define CIO3PLL_DLL_CAL_START_CAPCODE GENMASK(18, 17) + +#define CIO3PLL_DTC_VREG 0x2a20 +#define CIO3PLL_DTC_VREG_ADJUST GENMASK(16, 14) + +#define ACIOPHY_CFG0 0x08 +#define ACIOPHY_CFG0_COMMON_BIG_OV BIT(1) +#define ACIOPHY_CFG0_COMMON_SMALL_OV BIT(3) +#define ACIOPHY_CFG0_COMMON_CLAMP_OV BIT(5) +#define ACIOPHY_CFG0_RX_SMALL_OV GENMASK(9, 8) +#define ACIOPHY_CFG0_RX_BIG_OV GENMASK(13, 12) +#define ACIOPHY_CFG0_RX_CLAMP_OV GENMASK(17, 16) + +#define ACIOPHY_CROSSBAR 0x4c +#define ACIOPHY_CROSSBAR_PROTOCOL GENMASK(4, 0) +#define ACIOPHY_CROSSBAR_PROTOCOL_USB4 0x0 +#define ACIOPHY_CROSSBAR_PROTOCOL_USB4_SWAPPED 0x1 +#define ACIOPHY_CROSSBAR_PROTOCOL_USB3 0xa +#define ACIOPHY_CROSSBAR_PROTOCOL_USB3_SWAPPED 0xb +#define ACIOPHY_CROSSBAR_PROTOCOL_USB3_DP 0x10 +#define ACIOPHY_CROSSBAR_PROTOCOL_USB3_DP_SWAPPED 0x11 +#define ACIOPHY_CROSSBAR_PROTOCOL_DP 0x14 +#define ACIOPHY_CROSSBAR_DP_SINGLE_PMA GENMASK(16, 5) +#define ACIOPHY_CROSSBAR_DP_SINGLE_PMA_NONE 0x0000 +#define ACIOPHY_CROSSBAR_DP_SINGLE_PMA_UNK100 0x100 +#define ACIOPHY_CROSSBAR_DP_SINGLE_PMA_UNK008 0x008 +#define ACIOPHY_CROSSBAR_DP_BOTH_PMA BIT(17) + +#define ACIOPHY_LANE_MODE 0x48 +#define ACIOPHY_LANE_MODE_RX0 GENMASK(2, 0) +#define ACIOPHY_LANE_MODE_TX0 GENMASK(5, 3) +#define ACIOPHY_LANE_MODE_RX1 GENMASK(8, 6) +#define ACIOPHY_LANE_MODE_TX1 GENMASK(11, 9) + +enum atcphy_lane_mode { + ACIOPHY_LANE_MODE_USB4 = 0, + ACIOPHY_LANE_MODE_USB3 = 1, + ACIOPHY_LANE_MODE_DP = 2, + ACIOPHY_LANE_MODE_OFF = 3, +}; + +#define ACIOPHY_TOP_BIST_CIOPHY_CFG1 0x84 +#define ACIOPHY_TOP_BIST_CIOPHY_CFG1_CLK_EN BIT(27) +#define ACIOPHY_TOP_BIST_CIOPHY_CFG1_BIST_EN BIT(28) + +#define ACIOPHY_TOP_BIST_OV_CFG 0x8c +#define ACIOPHY_TOP_BIST_OV_CFG_LN0_RESET_N_OV BIT(13) +#define ACIOPHY_TOP_BIST_OV_CFG_LN0_PWR_DOWN_OV BIT(25) + +#define ACIOPHY_TOP_BIST_READ_CTRL 0x90 +#define ACIOPHY_TOP_BIST_READ_CTRL_LN0_PHY_STATUS_RE BIT(2) + +#define ACIOPHY_TOP_PHY_STAT 0x9c +#define ACIOPHY_TOP_PHY_STAT_LN0_UNK0 BIT(0) +#define ACIOPHY_TOP_PHY_STAT_LN0_UNK23 BIT(23) + +#define ACIOPHY_TOP_BIST_PHY_CFG0 0xa8 +#define ACIOPHY_TOP_BIST_PHY_CFG0_LN0_RESET_N BIT(0) + +#define ACIOPHY_TOP_BIST_PHY_CFG1 0xac +#define ACIOPHY_TOP_BIST_PHY_CFG1_LN0_PWR_DOWN GENMASK(13, 10) + +#define ACIOPHY_SLEEP_CTRL 0x1b0 +#define ACIOPHY_SLEEP_CTRL_TX_BIG_OV GENMASK(3, 2) +#define ACIOPHY_SLEEP_CTRL_TX_SMALL_OV GENMASK(7, 6) +#define ACIOPHY_SLEEP_CTRL_TX_CLAMP_OV GENMASK(11, 10) + +#define ACIOPHY_PLL_PCTL_FSM_CTRL1 0x1014 +#define ACIOPHY_PLL_APB_REQ_OV_SEL GENMASK(21, 13) +#define ACIOPHY_PLL_COMMON_CTRL 0x1028 +#define ACIOPHY_PLL_WAIT_FOR_CMN_READY_BEFORE_RESET_EXIT BIT(24) + +#define ATCPHY_POWER_CTRL 0x20000 +#define ATCPHY_POWER_STAT 0x20004 +#define ATCPHY_POWER_SLEEP_SMALL BIT(0) +#define ATCPHY_POWER_SLEEP_BIG BIT(1) +#define ATCPHY_POWER_CLAMP_EN BIT(2) +#define ATCPHY_POWER_APB_RESET_N BIT(3) +#define ATCPHY_POWER_PHY_RESET_N BIT(4) + +#define ATCPHY_MISC 0x20008 +#define ATCPHY_MISC_RESET_N BIT(0) +#define ATCPHY_MISC_LANE_SWAP BIT(2) + +#define ACIOPHY_LANE_DP_CFG_BLK_TX_DP_CTRL0 0x7000 +#define DP_PMA_BYTECLK_RESET BIT(0) +#define DP_MAC_DIV20_CLK_SEL BIT(1) +#define DPTXPHY_PMA_LANE_RESET_N BIT(2) +#define DPTXPHY_PMA_LANE_RESET_N_OV BIT(3) +#define DPTX_PCLK1_SELECT GENMASK(6, 4) +#define DPTX_PCLK2_SELECT GENMASK(9, 7) +#define DPRX_PCLK_SELECT GENMASK(12, 10) +#define DPTX_PCLK1_ENABLE BIT(13) +#define DPTX_PCLK2_ENABLE BIT(14) +#define DPRX_PCLK_ENABLE BIT(15) + +#define ACIOPHY_DP_PCLK_STAT 0x7044 +#define ACIOPHY_AUSPLL_LOCK BIT(3) + +#define LN0_AUSPMA_RX_TOP 0x9000 +#define LN0_AUSPMA_RX_EQ 0xA000 +#define LN0_AUSPMA_RX_SHM 0xB000 +#define LN0_AUSPMA_TX_TOP 0xC000 +#define LN0_AUSPMA_TX_SHM 0xD000 + +#define LN1_AUSPMA_RX_TOP 0x10000 +#define LN1_AUSPMA_RX_EQ 0x11000 +#define LN1_AUSPMA_RX_SHM 0x12000 +#define LN1_AUSPMA_TX_TOP 0x13000 +#define LN1_AUSPMA_TX_SHM 0x14000 + +#define LN_AUSPMA_RX_TOP_PMAFSM 0x0010 +#define LN_AUSPMA_RX_TOP_PMAFSM_PCS_OV BIT(0) +#define LN_AUSPMA_RX_TOP_PMAFSM_PCS_REQ BIT(9) + +#define LN_AUSPMA_RX_TOP_TJ_CFG_RX_TXMODE 0x00F0 +#define LN_RX_TXMODE BIT(0) + +#define LN_AUSPMA_RX_SHM_TJ_RXA_CTLE_CTRL0 0x00 +#define LN_TX_CLK_EN BIT(20) +#define LN_TX_CLK_EN_OV BIT(21) + +#define LN_AUSPMA_RX_SHM_TJ_RXA_AFE_CTRL1 0x04 +#define LN_RX_DIV20_RESET_N_OV BIT(29) +#define LN_RX_DIV20_RESET_N BIT(30) + +#define LN_AUSPMA_RX_SHM_TJ_RXA_UNK_CTRL2 0x08 +#define LN_AUSPMA_RX_SHM_TJ_RXA_UNK_CTRL3 0x0C +#define LN_AUSPMA_RX_SHM_TJ_RXA_UNK_CTRL4 0x10 +#define LN_AUSPMA_RX_SHM_TJ_RXA_UNK_CTRL5 0x14 +#define LN_AUSPMA_RX_SHM_TJ_RXA_UNK_CTRL6 0x18 +#define LN_AUSPMA_RX_SHM_TJ_RXA_UNK_CTRL7 0x1C +#define LN_AUSPMA_RX_SHM_TJ_RXA_UNK_CTRL8 0x20 +#define LN_AUSPMA_RX_SHM_TJ_RXA_UNK_CTRL9 0x24 +#define LN_AUSPMA_RX_SHM_TJ_RXA_DFE_CTRL10 0x28 +#define LN_DTVREG_ADJUST GENMASK(31, 27) + +#define LN_AUSPMA_RX_SHM_TJ_RXA_DFE_CTRL11 0x2C +#define LN_DTVREG_BIG_EN BIT(23) +#define LN_DTVREG_BIG_EN_OV BIT(24) +#define LN_DTVREG_SML_EN BIT(25) +#define LN_DTVREG_SML_EN_OV BIT(26) + +#define LN_AUSPMA_RX_SHM_TJ_RXA_DFE_CTRL12 0x30 +#define LN_TX_BYTECLK_RESET_SYNC_CLR BIT(22) +#define LN_TX_BYTECLK_RESET_SYNC_CLR_OV BIT(23) +#define LN_TX_BYTECLK_RESET_SYNC_EN BIT(24) +#define LN_TX_BYTECLK_RESET_SYNC_EN_OV BIT(25) +#define LN_TX_HRCLK_SEL BIT(28) +#define LN_TX_HRCLK_SEL_OV BIT(29) +#define LN_TX_PBIAS_EN BIT(30) +#define LN_TX_PBIAS_EN_OV BIT(31) + +#define LN_AUSPMA_RX_SHM_TJ_RXA_DFE_CTRL13 0x34 +#define LN_TX_PRE_EN BIT(0) +#define LN_TX_PRE_EN_OV BIT(1) +#define LN_TX_PST1_EN BIT(2) +#define LN_TX_PST1_EN_OV BIT(3) +#define LN_DTVREG_ADJUST_OV BIT(15) + +#define LN_AUSPMA_RX_SHM_TJ_UNK_CTRL14A 0x38 +#define LN_AUSPMA_RX_SHM_TJ_UNK_CTRL14B 0x3C +#define LN_AUSPMA_RX_SHM_TJ_UNK_CTRL15A 0x40 +#define LN_AUSPMA_RX_SHM_TJ_UNK_CTRL15B 0x44 +#define LN_AUSPMA_RX_SHM_TJ_RXA_SAVOS_CTRL16 0x48 +#define LN_RXTERM_EN BIT(21) +#define LN_RXTERM_EN_OV BIT(22) +#define LN_RXTERM_PULLUP_LEAK_EN BIT(23) +#define LN_RXTERM_PULLUP_LEAK_EN_OV BIT(24) +#define LN_TX_CAL_CODE GENMASK(29, 25) +#define LN_TX_CAL_CODE_OV BIT(30) + +#define LN_AUSPMA_RX_SHM_TJ_RXA_TX_CTRL17 0x4C +#define LN_TX_MARGIN GENMASK(19, 15) +#define LN_TX_MARGIN_OV BIT(20) +#define LN_TX_MARGIN_LSB BIT(21) +#define LN_TX_MARGIN_LSB_OV BIT(22) +#define LN_TX_MARGIN_P1 GENMASK(26, 23) +#define LN_TX_MARGIN_P1_OV BIT(27) +#define LN_TX_MARGIN_P1_LSB GENMASK(29, 28) +#define LN_TX_MARGIN_P1_LSB_OV BIT(30) + +#define LN_AUSPMA_RX_SHM_TJ_RXA_TX_CTRL18 0x50 +#define LN_TX_P1_CODE GENMASK(3, 0) +#define LN_TX_P1_CODE_OV BIT(4) +#define LN_TX_P1_LSB_CODE GENMASK(6, 5) +#define LN_TX_P1_LSB_CODE_OV BIT(7) +#define LN_TX_MARGIN_PRE GENMASK(10, 8) +#define LN_TX_MARGIN_PRE_OV BIT(11) +#define LN_TX_MARGIN_PRE_LSB GENMASK(13, 12) +#define LN_TX_MARGIN_PRE_LSB_OV BIT(14) +#define LN_TX_PRE_LSB_CODE GENMASK(16, 15) +#define LN_TX_PRE_LSB_CODE_OV BIT(17) +#define LN_TX_PRE_CODE GENMASK(21, 18) +#define LN_TX_PRE_CODE_OV BIT(22) + +#define LN_AUSPMA_RX_SHM_TJ_RXA_TERM_CTRL19 0x54 +#define LN_TX_TEST_EN BIT(21) +#define LN_TX_TEST_EN_OV BIT(22) +#define LN_TX_EN BIT(23) +#define LN_TX_EN_OV BIT(24) +#define LN_TX_CLK_DLY_CTRL_TAPGEN GENMASK(27, 25) +#define LN_TX_CLK_DIV2_EN BIT(28) +#define LN_TX_CLK_DIV2_EN_OV BIT(29) +#define LN_TX_CLK_DIV2_RST BIT(30) +#define LN_TX_CLK_DIV2_RST_OV BIT(31) + +#define LN_AUSPMA_RX_SHM_TJ_RXA_UNK_CTRL20 0x58 +#define LN_AUSPMA_RX_SHM_TJ_RXA_UNK_CTRL21 0x5C +#define LN_AUSPMA_RX_SHM_TJ_RXA_VREF_CTRL22 0x60 +#define LN_VREF_ADJUST_GRAY GENMASK(11, 7) +#define LN_VREF_ADJUST_GRAY_OV BIT(12) +#define LN_VREF_BIAS_SEL GENMASK(14, 13) +#define LN_VREF_BIAS_SEL_OV BIT(15) +#define LN_VREF_BOOST_EN BIT(16) +#define LN_VREF_BOOST_EN_OV BIT(17) +#define LN_VREF_EN BIT(18) +#define LN_VREF_EN_OV BIT(19) +#define LN_VREF_LPBKIN_DATA GENMASK(29, 28) +#define LN_VREF_TEST_RXLPBKDT_EN BIT(30) +#define LN_VREF_TEST_RXLPBKDT_EN_OV BIT(31) + +#define LN_AUSPMA_TX_SHM_TXA_CFG_MAIN_REG0 0x00 +#define LN_BYTECLK_RESET_SYNC_EN_OV BIT(2) +#define LN_BYTECLK_RESET_SYNC_EN BIT(3) +#define LN_BYTECLK_RESET_SYNC_CLR_OV BIT(4) +#define LN_BYTECLK_RESET_SYNC_CLR BIT(5) +#define LN_BYTECLK_RESET_SYNC_SEL_OV BIT(6) + +#define LN_AUSPMA_TX_SHM_TXA_CFG_MAIN_REG1 0x04 +#define LN_TXA_DIV2_EN_OV BIT(8) +#define LN_TXA_DIV2_EN BIT(9) +#define LN_TXA_DIV2_RESET_OV BIT(10) +#define LN_TXA_DIV2_RESET BIT(11) +#define LN_TXA_CLK_EN_OV BIT(22) +#define LN_TXA_CLK_EN BIT(23) + +#define LN_AUSPMA_TX_SHM_TXA_IMP_REG0 0x08 +#define LN_TXA_CAL_CTRL_OV BIT(0) +#define LN_TXA_CAL_CTRL GENMASK(18, 1) +#define LN_TXA_CAL_CTRL_BASE_OV BIT(19) +#define LN_TXA_CAL_CTRL_BASE GENMASK(23, 20) +#define LN_TXA_HIZ_OV BIT(29) +#define LN_TXA_HIZ BIT(30) + +#define LN_AUSPMA_TX_SHM_TXA_IMP_REG1 0x0C +#define LN_AUSPMA_TX_SHM_TXA_IMP_REG2 0x10 +#define LN_TXA_MARGIN_OV BIT(0) +#define LN_TXA_MARGIN GENMASK(18, 1) +#define LN_TXA_MARGIN_2R_OV BIT(19) +#define LN_TXA_MARGIN_2R BIT(20) + +#define LN_AUSPMA_TX_SHM_TXA_IMP_REG3 0x14 +#define LN_TXA_MARGIN_POST_OV BIT(0) +#define LN_TXA_MARGIN_POST GENMASK(10, 1) +#define LN_TXA_MARGIN_POST_2R_OV BIT(11) +#define LN_TXA_MARGIN_POST_2R BIT(12) +#define LN_TXA_MARGIN_POST_4R_OV BIT(13) +#define LN_TXA_MARGIN_POST_4R BIT(14) +#define LN_TXA_MARGIN_PRE_OV BIT(15) +#define LN_TXA_MARGIN_PRE GENMASK(21, 16) +#define LN_TXA_MARGIN_PRE_2R_OV BIT(22) +#define LN_TXA_MARGIN_PRE_2R BIT(23) +#define LN_TXA_MARGIN_PRE_4R_OV BIT(24) +#define LN_TXA_MARGIN_PRE_4R BIT(25) + +#define LN_AUSPMA_TX_SHM_TXA_UNK_REG0 0x18 +#define LN_AUSPMA_TX_SHM_TXA_UNK_REG1 0x1C +#define LN_AUSPMA_TX_SHM_TXA_UNK_REG2 0x20 + +#define LN_AUSPMA_TX_SHM_TXA_LDOCLK 0x24 +#define LN_LDOCLK_BYPASS_SML_OV BIT(8) +#define LN_LDOCLK_BYPASS_SML BIT(9) +#define LN_LDOCLK_BYPASS_BIG_OV BIT(10) +#define LN_LDOCLK_BYPASS_BIG BIT(11) +#define LN_LDOCLK_EN_SML_OV BIT(12) +#define LN_LDOCLK_EN_SML BIT(13) +#define LN_LDOCLK_EN_BIG_OV BIT(14) +#define LN_LDOCLK_EN_BIG BIT(15) + +/* LPDPTX registers */ +#define LPDPTX_AUX_CFG_BLK_AUX_CTRL 0x0000 +#define LPDPTX_BLK_AUX_CTRL_PWRDN BIT(4) +#define LPDPTX_BLK_AUX_RXOFFSET GENMASK(25, 22) + +#define LPDPTX_AUX_CFG_BLK_AUX_LDO_CTRL 0x0008 + +#define LPDPTX_AUX_CFG_BLK_AUX_MARGIN 0x000c +#define LPDPTX_MARGIN_RCAL_RXOFFSET_EN BIT(5) +#define LPDPTX_AUX_MARGIN_RCAL_TXSWING GENMASK(10, 6) + +#define LPDPTX_AUX_SHM_CFG_BLK_AUX_CTRL_REG0 0x0204 +#define LPDPTX_CFG_PMA_AUX_SEL_LF_DATA BIT(15) + +#define LPDPTX_AUX_SHM_CFG_BLK_AUX_CTRL_REG1 0x0208 +#define LPDPTX_CFG_PMA_PHYS_ADJ GENMASK(22, 20) +#define LPDPTX_CFG_PMA_PHYS_ADJ_OV BIT(19) + +#define LPDPTX_AUX_CONTROL 0x4000 +#define LPDPTX_AUX_PWN_DOWN 0x10 +#define LPDPTX_AUX_CLAMP_EN 0x04 +#define LPDPTX_SLEEP_B_BIG_IN 0x02 +#define LPDPTX_SLEEP_B_SML_IN 0x01 +#define LPDPTX_TXTERM_CODEMSB 0x400 +#define LPDPTX_TXTERM_CODE GENMASK(9, 5) + +/* pipehandler registers */ +#define PIPEHANDLER_OVERRIDE 0x00 +#define PIPEHANDLER_OVERRIDE_RXVALID BIT(0) +#define PIPEHANDLER_OVERRIDE_RXDETECT BIT(2) + +#define PIPEHANDLER_OVERRIDE_VALUES 0x04 +#define PIPEHANDLER_OVERRIDE_VAL_RXDETECT0 BIT(1) +#define PIPEHANDLER_OVERRIDE_VAL_RXDETECT1 BIT(2) +#define PIPEHANDLER_OVERRIDE_VAL_PHY_STATUS BIT(4) + +#define PIPEHANDLER_MUX_CTRL 0x0c +#define PIPEHANDLER_MUX_CTRL_CLK GENMASK(5, 3) +#define PIPEHANDLER_MUX_CTRL_DATA GENMASK(2, 0) +#define PIPEHANDLER_MUX_CTRL_CLK_OFF 0 +#define PIPEHANDLER_MUX_CTRL_CLK_USB3 1 +#define PIPEHANDLER_MUX_CTRL_CLK_USB4 2 +#define PIPEHANDLER_MUX_CTRL_CLK_DUMMY 4 + +#define PIPEHANDLER_MUX_CTRL_DATA_USB3 0 +#define PIPEHANDLER_MUX_CTRL_DATA_USB4 1 +#define PIPEHANDLER_MUX_CTRL_DATA_DUMMY 2 + +#define PIPEHANDLER_LOCK_REQ 0x10 +#define PIPEHANDLER_LOCK_ACK 0x14 +#define PIPEHANDLER_LOCK_EN BIT(0) + +#define PIPEHANDLER_AON_GEN 0x1C +#define PIPEHANDLER_AON_GEN_DWC3_FORCE_CLAMP_EN BIT(4) +#define PIPEHANDLER_AON_GEN_DWC3_RESET_N BIT(0) + +#define PIPEHANDLER_NONSELECTED_OVERRIDE 0x20 +#define PIPEHANDLER_NATIVE_RESET BIT(12) +#define PIPEHANDLER_DUMMY_PHY_EN BIT(15) +#define PIPEHANDLER_NATIVE_POWER_DOWN GENMASK(3, 0) + +#define PIPEHANDLER_LOCK_ACK_TIMEOUT_US 1000 + +/* USB2 PHY regs */ +#define USB2PHY_USBCTL 0x00 +#define USB2PHY_USBCTL_RUN 2 +#define USB2PHY_USBCTL_ISOLATION 4 + +#define USB2PHY_CTL 0x04 +#define USB2PHY_CTL_RESET BIT(0) +#define USB2PHY_CTL_PORT_RESET BIT(1) +#define USB2PHY_CTL_APB_RESET_N BIT(2) +#define USB2PHY_CTL_SIDDQ BIT(3) + +#define USB2PHY_SIG 0x08 +#define USB2PHY_SIG_VBUSDET_FORCE_VAL BIT(0) +#define USB2PHY_SIG_VBUSDET_FORCE_EN BIT(1) +#define USB2PHY_SIG_VBUSVLDEXT_FORCE_VAL BIT(2) +#define USB2PHY_SIG_VBUSVLDEXT_FORCE_EN BIT(3) +#define USB2PHY_SIG_HOST (7 << 12) + +#define USB2PHY_MISCTUNE 0x1c +#define USB2PHY_MISCTUNE_APBCLK_GATE_OFF BIT(29) +#define USB2PHY_MISCTUNE_REFCLK_GATE_OFF BIT(30) + +enum atcphy_dp_link_rate { + ATCPHY_DP_LINK_RATE_RBR, + ATCPHY_DP_LINK_RATE_HBR, + ATCPHY_DP_LINK_RATE_HBR2, + ATCPHY_DP_LINK_RATE_HBR3, +}; + +/** + * enum atcphy_pipehandler_state - States of the PIPE mux interface ("pipehandler") + * @ATCPHY_PIPEHANDLER_STATE_DUMMY: "Dummy PHY" (disables USB3, USB2 only) + * @ATCPHY_PIPEHANDLER_STATE_USB3: USB3 directly connected to the Type-C port + * @ATCPHY_PIPEHANDLER_STATE_USB4: USB3 tunneled via USB4/Thunderbolt + * + * DWC3's USB3 PIPE interface is connected to a multiplexer inside this PHY + * which can switch between a dummy state (which effectively disables any USB3 + * support and falls back to USB2 only operation via the separate ULPI interface), + * a USB3 state (for regular USB3 or USB3+DisplayPort operation) and a USB4 state + * (for USB3 tunneled via USB4/Thunderbolt). + */ +enum atcphy_pipehandler_state { + ATCPHY_PIPEHANDLER_STATE_DUMMY, + ATCPHY_PIPEHANDLER_STATE_USB3, + ATCPHY_PIPEHANDLER_STATE_USB4, +}; + +/** + * enum atcphy_mode - Operating modes of the PHY + * @APPLE_ATCPHY_MODE_OFF: all PHYs powered off + * @APPLE_ATCPHY_MODE_USB2: Nothing on the four SS lanes (i.e. USB2 only on D-/+) + * @APPLE_ATCPHY_MODE_USB3: USB3 on two lanes, nothing on the other two + * @APPLE_ATCPHY_MODE_USB3_DP: USB3 on two lanes and DisplayPort on the other two + * @APPLE_ATCPHY_MODE_TBT: Thunderbolt on all lanes + * @APPLE_ATCPHY_MODE_USB4: USB4 on all lanes + * @APPLE_ATCPHY_MODE_DP: DisplayPort on all lanes + */ +enum atcphy_mode { + APPLE_ATCPHY_MODE_OFF, + APPLE_ATCPHY_MODE_USB2, + APPLE_ATCPHY_MODE_USB3, + APPLE_ATCPHY_MODE_USB3_DP, + APPLE_ATCPHY_MODE_TBT, + APPLE_ATCPHY_MODE_USB4, + APPLE_ATCPHY_MODE_DP, +}; + +enum atcphy_lane { + APPLE_ATCPHY_LANE_0, + APPLE_ATCPHY_LANE_1, +}; + +/* Link rate configuration, field names are taken from XNU debug output or register names */ +struct atcphy_dp_link_rate_configuration { + u16 freqinit_count_target; + u16 fbdivn_frac_den; + u16 fbdivn_frac_num; + u16 pclk_div_sel; + u8 lfclk_ctrl; + u8 vclk_op_divn; + bool plla_clkout_vreg_bypass; + bool txa_ldoclk_bypass; + bool txa_div2_en; +}; + +/* Crossbar and lane configuration */ +struct atcphy_mode_configuration { + u32 crossbar; + u32 crossbar_dp_single_pma; + bool crossbar_dp_both_pma; + enum atcphy_lane_mode lane_mode[2]; + bool dp_lane[2]; + bool set_swap; +}; + +/** + * struct apple_atcphy - Apple Type-C PHY device struct + * @np: Device node pointer + * @dev: Device pointer + * @tunables: Firmware-provided tunable parameters + * @tunables.axi2af: AXI to AF interface tunables + * @tunables.common: Common tunables for all lanes + * @tunables.lane_usb3: USB3 lane-specific tunables + * @tunables.lane_dp: DisplayPort lane-specific tunables + * @tunables.lane_usb4: USB4 lane-specific tunables + * @mode: Current PHY operating mode + * @swap_lanes: True if lanes must be swapped due to cable orientation + * @dp_link_rate: DisplayPort link rate + * @pipehandler_up: True if the PIPE mux ("pipehandler") is set to USB3 or USB4 mode + * @regs: Memory-mapped registers + * @regs.core: Core registers + * @regs.axi2af: AXI to Apple Fabric interface registers + * @regs.usb2phy: USB2 PHY registers + * @regs.pipehandler: USB3 PIPE interface ("pipehandler") registers + * @regs.lpdptx: DisplayPort registers + * @res: Resources for memory-mapped registers, used to verify that tunables aren't out of bounds + * @res.core: Core register resource + * @res.axi2af: AXI to Apple Fabric interface resource + * @phys: PHY instances + * @phys.usb2: USB2 PHY instance + * @phys.usb3: USB3 PHY instance + * @phys.dp: DisplayPort PHY instance + * @phy_provider: PHY provider instance + * @rcdev: Reset controller device + * @sw: Type-C switch instance + * @mux: Type-C mux instance + * @lock: Mutex for synchronizing register access across PHY, Type-C switch/mux and reset controller + */ +struct apple_atcphy { + struct device_node *np; + struct device *dev; + + struct { + struct apple_tunable *axi2af; + struct apple_tunable *common[2]; + struct apple_tunable *lane_usb3[2]; + struct apple_tunable *lane_dp[2]; + struct apple_tunable *lane_usb4[2]; + } tunables; + + enum atcphy_mode mode; + bool swap_lanes; + int dp_link_rate; + bool pipehandler_up; + + struct { + void __iomem *core; + void __iomem *axi2af; + void __iomem *usb2phy; + void __iomem *pipehandler; + void __iomem *lpdptx; + } regs; + + struct { + struct resource *core; + struct resource *axi2af; + } res; + + struct { + struct phy *usb2; + struct phy *usb3; + struct phy *dp; + } phys; + struct phy_provider *phy_provider; + + struct reset_controller_dev rcdev; + + struct typec_switch *sw; + struct typec_mux *mux; + + struct mutex lock; +}; + +static const struct { + const struct atcphy_mode_configuration normal; + const struct atcphy_mode_configuration swapped; + bool enable_dp_aux; + enum atcphy_pipehandler_state pipehandler_state; +} atcphy_modes[] = { + [APPLE_ATCPHY_MODE_OFF] = { + .normal = { + .crossbar = ACIOPHY_CROSSBAR_PROTOCOL_USB3, + .crossbar_dp_single_pma = ACIOPHY_CROSSBAR_DP_SINGLE_PMA_NONE, + .crossbar_dp_both_pma = false, + .lane_mode = {ACIOPHY_LANE_MODE_OFF, ACIOPHY_LANE_MODE_OFF}, + .dp_lane = {false, false}, + .set_swap = false, + }, + .swapped = { + .crossbar = ACIOPHY_CROSSBAR_PROTOCOL_USB3_SWAPPED, + .crossbar_dp_single_pma = ACIOPHY_CROSSBAR_DP_SINGLE_PMA_NONE, + .crossbar_dp_both_pma = false, + .lane_mode = {ACIOPHY_LANE_MODE_OFF, ACIOPHY_LANE_MODE_OFF}, + .dp_lane = {false, false}, + .set_swap = false, /* doesn't matter since the SS lanes are off */ + }, + .enable_dp_aux = false, + .pipehandler_state = ATCPHY_PIPEHANDLER_STATE_DUMMY, + }, + [APPLE_ATCPHY_MODE_USB2] = { + .normal = { + .crossbar = ACIOPHY_CROSSBAR_PROTOCOL_USB3, + .crossbar_dp_single_pma = ACIOPHY_CROSSBAR_DP_SINGLE_PMA_NONE, + .crossbar_dp_both_pma = false, + .lane_mode = {ACIOPHY_LANE_MODE_OFF, ACIOPHY_LANE_MODE_OFF}, + .dp_lane = {false, false}, + .set_swap = false, + }, + .swapped = { + .crossbar = ACIOPHY_CROSSBAR_PROTOCOL_USB3_SWAPPED, + .crossbar_dp_single_pma = ACIOPHY_CROSSBAR_DP_SINGLE_PMA_NONE, + .crossbar_dp_both_pma = false, + .lane_mode = {ACIOPHY_LANE_MODE_OFF, ACIOPHY_LANE_MODE_OFF}, + .dp_lane = {false, false}, + .set_swap = false, /* doesn't matter since the SS lanes are off */ + }, + .enable_dp_aux = false, + .pipehandler_state = ATCPHY_PIPEHANDLER_STATE_DUMMY, + }, + [APPLE_ATCPHY_MODE_USB3] = { + /* + * Setting up the lanes as DP/USB3 is intentional here, USB3/USB3 does not work + * and isn't required since this PHY does not support 20GBps mode anyway. + * The only difference to APPLE_ATCPHY_MODE_USB3_DP is that DP Aux is not enabled. + */ + .normal = { + .crossbar = ACIOPHY_CROSSBAR_PROTOCOL_USB3_DP, + .crossbar_dp_single_pma = ACIOPHY_CROSSBAR_DP_SINGLE_PMA_UNK008, + .crossbar_dp_both_pma = false, + .lane_mode = {ACIOPHY_LANE_MODE_USB3, ACIOPHY_LANE_MODE_DP}, + .dp_lane = {false, true}, + .set_swap = false, + }, + .swapped = { + .crossbar = ACIOPHY_CROSSBAR_PROTOCOL_USB3_DP_SWAPPED, + .crossbar_dp_single_pma = ACIOPHY_CROSSBAR_DP_SINGLE_PMA_UNK008, + .crossbar_dp_both_pma = false, + .lane_mode = {ACIOPHY_LANE_MODE_DP, ACIOPHY_LANE_MODE_USB3}, + .dp_lane = {true, false}, + .set_swap = true, + }, + .enable_dp_aux = false, + .pipehandler_state = ATCPHY_PIPEHANDLER_STATE_USB3, + }, + [APPLE_ATCPHY_MODE_USB3_DP] = { + .normal = { + .crossbar = ACIOPHY_CROSSBAR_PROTOCOL_USB3_DP, + .crossbar_dp_single_pma = ACIOPHY_CROSSBAR_DP_SINGLE_PMA_UNK008, + .crossbar_dp_both_pma = false, + .lane_mode = {ACIOPHY_LANE_MODE_USB3, ACIOPHY_LANE_MODE_DP}, + .dp_lane = {false, true}, + .set_swap = false, + }, + .swapped = { + .crossbar = ACIOPHY_CROSSBAR_PROTOCOL_USB3_DP_SWAPPED, + .crossbar_dp_single_pma = ACIOPHY_CROSSBAR_DP_SINGLE_PMA_UNK008, + .crossbar_dp_both_pma = false, + .lane_mode = {ACIOPHY_LANE_MODE_DP, ACIOPHY_LANE_MODE_USB3}, + .dp_lane = {true, false}, + .set_swap = true, + }, + .enable_dp_aux = true, + .pipehandler_state = ATCPHY_PIPEHANDLER_STATE_USB3, + }, + [APPLE_ATCPHY_MODE_TBT] = { + .normal = { + .crossbar = ACIOPHY_CROSSBAR_PROTOCOL_USB4, + .crossbar_dp_single_pma = ACIOPHY_CROSSBAR_DP_SINGLE_PMA_NONE, + .crossbar_dp_both_pma = false, + .lane_mode = {ACIOPHY_LANE_MODE_USB4, ACIOPHY_LANE_MODE_USB4}, + .dp_lane = {false, false}, + .set_swap = false, + }, + .swapped = { + .crossbar = ACIOPHY_CROSSBAR_PROTOCOL_USB4_SWAPPED, + .crossbar_dp_single_pma = ACIOPHY_CROSSBAR_DP_SINGLE_PMA_NONE, + .crossbar_dp_both_pma = false, + .lane_mode = {ACIOPHY_LANE_MODE_USB4, ACIOPHY_LANE_MODE_USB4}, + .dp_lane = {false, false}, + .set_swap = false, /* intentionally false */ + }, + .enable_dp_aux = false, + .pipehandler_state = ATCPHY_PIPEHANDLER_STATE_DUMMY, + }, + [APPLE_ATCPHY_MODE_USB4] = { + .normal = { + .crossbar = ACIOPHY_CROSSBAR_PROTOCOL_USB4, + .crossbar_dp_single_pma = ACIOPHY_CROSSBAR_DP_SINGLE_PMA_NONE, + .crossbar_dp_both_pma = false, + .lane_mode = {ACIOPHY_LANE_MODE_USB4, ACIOPHY_LANE_MODE_USB4}, + .dp_lane = {false, false}, + .set_swap = false, + }, + .swapped = { + .crossbar = ACIOPHY_CROSSBAR_PROTOCOL_USB4_SWAPPED, + .crossbar_dp_single_pma = ACIOPHY_CROSSBAR_DP_SINGLE_PMA_NONE, + .crossbar_dp_both_pma = false, + .lane_mode = {ACIOPHY_LANE_MODE_USB4, ACIOPHY_LANE_MODE_USB4}, + .dp_lane = {false, false}, + .set_swap = false, /* intentionally false */ + }, + .enable_dp_aux = false, + .pipehandler_state = ATCPHY_PIPEHANDLER_STATE_USB4, + }, + [APPLE_ATCPHY_MODE_DP] = { + .normal = { + .crossbar = ACIOPHY_CROSSBAR_PROTOCOL_DP, + .crossbar_dp_single_pma = ACIOPHY_CROSSBAR_DP_SINGLE_PMA_UNK100, + .crossbar_dp_both_pma = true, + .lane_mode = {ACIOPHY_LANE_MODE_DP, ACIOPHY_LANE_MODE_DP}, + .dp_lane = {true, true}, + .set_swap = false, + }, + .swapped = { + .crossbar = ACIOPHY_CROSSBAR_PROTOCOL_DP, + .crossbar_dp_single_pma = ACIOPHY_CROSSBAR_DP_SINGLE_PMA_UNK008, + .crossbar_dp_both_pma = false, /* intentionally false */ + .lane_mode = {ACIOPHY_LANE_MODE_DP, ACIOPHY_LANE_MODE_DP}, + .dp_lane = {true, true}, + .set_swap = false, /* intentionally false */ + }, + .enable_dp_aux = true, + .pipehandler_state = ATCPHY_PIPEHANDLER_STATE_DUMMY, + }, +}; + +static const struct atcphy_dp_link_rate_configuration dp_lr_config[] = { + [ATCPHY_DP_LINK_RATE_RBR] = { + .freqinit_count_target = 0x21c, + .fbdivn_frac_den = 0x0, + .fbdivn_frac_num = 0x0, + .pclk_div_sel = 0x13, + .lfclk_ctrl = 0x5, + .vclk_op_divn = 0x2, + .plla_clkout_vreg_bypass = true, + .txa_ldoclk_bypass = true, + .txa_div2_en = true, + }, + [ATCPHY_DP_LINK_RATE_HBR] = { + .freqinit_count_target = 0x1c2, + .fbdivn_frac_den = 0x3ffe, + .fbdivn_frac_num = 0x1fff, + .pclk_div_sel = 0x9, + .lfclk_ctrl = 0x5, + .vclk_op_divn = 0x2, + .plla_clkout_vreg_bypass = true, + .txa_ldoclk_bypass = true, + .txa_div2_en = false, + }, + [ATCPHY_DP_LINK_RATE_HBR2] = { + .freqinit_count_target = 0x1c2, + .fbdivn_frac_den = 0x3ffe, + .fbdivn_frac_num = 0x1fff, + .pclk_div_sel = 0x4, + .lfclk_ctrl = 0x5, + .vclk_op_divn = 0x0, + .plla_clkout_vreg_bypass = true, + .txa_ldoclk_bypass = true, + .txa_div2_en = false, + }, + [ATCPHY_DP_LINK_RATE_HBR3] = { + .freqinit_count_target = 0x2a3, + .fbdivn_frac_den = 0x3ffc, + .fbdivn_frac_num = 0x2ffd, + .pclk_div_sel = 0x4, + .lfclk_ctrl = 0x6, + .vclk_op_divn = 0x0, + .plla_clkout_vreg_bypass = false, + .txa_ldoclk_bypass = false, + .txa_div2_en = false, + }, +}; + +static inline void mask32(void __iomem *reg, u32 mask, u32 set) +{ + u32 value = readl(reg); + + value &= ~mask; + value |= set; + writel(value, reg); +} + +static inline void core_mask32(struct apple_atcphy *atcphy, u32 reg, u32 mask, u32 set) +{ + mask32(atcphy->regs.core + reg, mask, set); +} + +static inline void set32(void __iomem *reg, u32 set) +{ + mask32(reg, 0, set); +} + +static inline void core_set32(struct apple_atcphy *atcphy, u32 reg, u32 set) +{ + core_mask32(atcphy, reg, 0, set); +} + +static inline void clear32(void __iomem *reg, u32 clear) +{ + mask32(reg, clear, 0); +} + +static inline void core_clear32(struct apple_atcphy *atcphy, u32 reg, u32 clear) +{ + core_mask32(atcphy, reg, clear, 0); +} + +static const struct atcphy_mode_configuration *atcphy_get_mode_config(struct apple_atcphy *atcphy, + enum atcphy_mode mode) +{ + if (atcphy->swap_lanes) + return &atcphy_modes[mode].swapped; + else + return &atcphy_modes[mode].normal; +} + +static void atcphy_apply_tunables(struct apple_atcphy *atcphy, enum atcphy_mode mode) +{ + const int lane0 = atcphy->swap_lanes ? 1 : 0; + const int lane1 = atcphy->swap_lanes ? 0 : 1; + + apple_tunable_apply(atcphy->regs.core, atcphy->tunables.common[0]); + apple_tunable_apply(atcphy->regs.axi2af, atcphy->tunables.axi2af); + apple_tunable_apply(atcphy->regs.core, atcphy->tunables.common[1]); + + switch (mode) { + /* + * USB 3.2 Gen 2x2 / SuperSpeed 20Gbps is not supported by this hardware and applying USB3 + * tunables to both lanes does not result in a working PHY configuration. Thus, both + * USB3-only and USB3/DP get the same tunable setup here. + */ + case APPLE_ATCPHY_MODE_USB3: + case APPLE_ATCPHY_MODE_USB3_DP: + apple_tunable_apply(atcphy->regs.core, atcphy->tunables.lane_usb3[lane0]); + apple_tunable_apply(atcphy->regs.core, atcphy->tunables.lane_dp[lane1]); + break; + + case APPLE_ATCPHY_MODE_DP: + apple_tunable_apply(atcphy->regs.core, atcphy->tunables.lane_dp[lane0]); + apple_tunable_apply(atcphy->regs.core, atcphy->tunables.lane_dp[lane1]); + break; + + /* + * Even though the various Thunderbolt versions and USB4 are different protocols they need + * the same tunables. The actual protocol-specific setup happens inside the Thunderbolt/USB4 + * native host interface. + */ + case APPLE_ATCPHY_MODE_TBT: + case APPLE_ATCPHY_MODE_USB4: + apple_tunable_apply(atcphy->regs.core, atcphy->tunables.lane_usb4[lane0]); + apple_tunable_apply(atcphy->regs.core, atcphy->tunables.lane_usb4[lane1]); + break; + + case APPLE_ATCPHY_MODE_OFF: + case APPLE_ATCPHY_MODE_USB2: + break; + } +} + +static int atcphy_pipehandler_lock(struct apple_atcphy *atcphy) +{ + int ret; + u32 reg; + + if (readl(atcphy->regs.pipehandler + PIPEHANDLER_LOCK_REQ) & PIPEHANDLER_LOCK_EN) { + dev_warn(atcphy->dev, "Pipehandler already locked\n"); + return 0; + } + + set32(atcphy->regs.pipehandler + PIPEHANDLER_LOCK_REQ, PIPEHANDLER_LOCK_EN); + + ret = readl_poll_timeout(atcphy->regs.pipehandler + PIPEHANDLER_LOCK_ACK, reg, + reg & PIPEHANDLER_LOCK_EN, 10, PIPEHANDLER_LOCK_ACK_TIMEOUT_US); + if (ret) { + clear32(atcphy->regs.pipehandler + PIPEHANDLER_LOCK_REQ, 1); + dev_warn(atcphy->dev, "Pipehandler lock not acked.\n"); + } + + return ret; +} + +static int atcphy_pipehandler_unlock(struct apple_atcphy *atcphy) +{ + int ret; + u32 reg; + + clear32(atcphy->regs.pipehandler + PIPEHANDLER_LOCK_REQ, PIPEHANDLER_LOCK_EN); + ret = readl_poll_timeout(atcphy->regs.pipehandler + PIPEHANDLER_LOCK_ACK, reg, + !(reg & PIPEHANDLER_LOCK_EN), 10, PIPEHANDLER_LOCK_ACK_TIMEOUT_US); + if (ret) + dev_warn(atcphy->dev, "Pipehandler lock release not acked.\n"); + + return ret; +} + +static int atcphy_pipehandler_check(struct apple_atcphy *atcphy) +{ + int ret; + + lockdep_assert_held(&atcphy->lock); + + if (readl(atcphy->regs.pipehandler + PIPEHANDLER_LOCK_ACK) & PIPEHANDLER_LOCK_EN) { + dev_warn(atcphy->dev, "Pipehandler already locked\n"); + + ret = atcphy_pipehandler_unlock(atcphy); + if (ret) { + dev_err(atcphy->dev, "Failed to unlock pipehandler\n"); + return ret; + } + } + + return 0; +} + +static int atcphy_configure_pipehandler_usb3(struct apple_atcphy *atcphy, bool host) +{ + int ret; + u32 reg; + + ret = atcphy_pipehandler_check(atcphy); + if (ret) + return ret; + + /* + * Only host mode requires this unknown BIST sequence to work correctly, possibly due to + * some hardware quirk. Guest mode breaks if we try to apply this sequence. + */ + if (host) { + /* Force disable link detection */ + clear32(atcphy->regs.pipehandler + PIPEHANDLER_OVERRIDE_VALUES, + PIPEHANDLER_OVERRIDE_VAL_RXDETECT0 | PIPEHANDLER_OVERRIDE_VAL_RXDETECT1); + set32(atcphy->regs.pipehandler + PIPEHANDLER_OVERRIDE, + PIPEHANDLER_OVERRIDE_RXVALID); + set32(atcphy->regs.pipehandler + PIPEHANDLER_OVERRIDE, + PIPEHANDLER_OVERRIDE_RXDETECT); + + ret = atcphy_pipehandler_lock(atcphy); + if (ret) { + dev_err(atcphy->dev, "Failed to lock pipehandler"); + return ret; + } + + /* BIST dance */ + core_set32(atcphy, ACIOPHY_TOP_BIST_PHY_CFG0, + ACIOPHY_TOP_BIST_PHY_CFG0_LN0_RESET_N); + core_set32(atcphy, ACIOPHY_TOP_BIST_OV_CFG, ACIOPHY_TOP_BIST_OV_CFG_LN0_RESET_N_OV); + ret = readl_poll_timeout(atcphy->regs.core + ACIOPHY_TOP_PHY_STAT, reg, + !(reg & ACIOPHY_TOP_PHY_STAT_LN0_UNK23), 10, 10000); + if (ret) + dev_warn(atcphy->dev, + "Timed out waiting for ACIOPHY_TOP_PHY_STAT_LN0_UNK23\n"); + + core_set32(atcphy, ACIOPHY_TOP_BIST_READ_CTRL, + ACIOPHY_TOP_BIST_READ_CTRL_LN0_PHY_STATUS_RE); + core_clear32(atcphy, ACIOPHY_TOP_BIST_READ_CTRL, + ACIOPHY_TOP_BIST_READ_CTRL_LN0_PHY_STATUS_RE); + + core_mask32(atcphy, ACIOPHY_TOP_BIST_PHY_CFG1, + ACIOPHY_TOP_BIST_PHY_CFG1_LN0_PWR_DOWN, + FIELD_PREP(ACIOPHY_TOP_BIST_PHY_CFG1_LN0_PWR_DOWN, 3)); + + core_set32(atcphy, ACIOPHY_TOP_BIST_OV_CFG, + ACIOPHY_TOP_BIST_OV_CFG_LN0_PWR_DOWN_OV); + core_set32(atcphy, ACIOPHY_TOP_BIST_CIOPHY_CFG1, + ACIOPHY_TOP_BIST_CIOPHY_CFG1_CLK_EN); + core_set32(atcphy, ACIOPHY_TOP_BIST_CIOPHY_CFG1, + ACIOPHY_TOP_BIST_CIOPHY_CFG1_BIST_EN); + writel(0, atcphy->regs.core + ACIOPHY_TOP_BIST_CIOPHY_CFG1); + + ret = readl_poll_timeout(atcphy->regs.core + ACIOPHY_TOP_PHY_STAT, reg, + (reg & ACIOPHY_TOP_PHY_STAT_LN0_UNK0), 10, 10000); + if (ret) + dev_warn(atcphy->dev, + "timed out waiting for ACIOPHY_TOP_PHY_STAT_LN0_UNK0\n"); + + ret = readl_poll_timeout(atcphy->regs.core + ACIOPHY_TOP_PHY_STAT, reg, + !(reg & ACIOPHY_TOP_PHY_STAT_LN0_UNK23), 10, 10000); + if (ret) + dev_warn(atcphy->dev, + "timed out waiting for ACIOPHY_TOP_PHY_STAT_LN0_UNK23\n"); + + /* Clear reset for non-selected USB3 PHY (?) */ + mask32(atcphy->regs.pipehandler + PIPEHANDLER_NONSELECTED_OVERRIDE, + PIPEHANDLER_NATIVE_POWER_DOWN, FIELD_PREP(PIPEHANDLER_NATIVE_POWER_DOWN, 3)); + clear32(atcphy->regs.pipehandler + PIPEHANDLER_NONSELECTED_OVERRIDE, + PIPEHANDLER_NATIVE_RESET); + + /* More BIST stuff (?) */ + writel(0, atcphy->regs.core + ACIOPHY_TOP_BIST_OV_CFG); + core_set32(atcphy, ACIOPHY_TOP_BIST_CIOPHY_CFG1, + ACIOPHY_TOP_BIST_CIOPHY_CFG1_CLK_EN); + core_set32(atcphy, ACIOPHY_TOP_BIST_CIOPHY_CFG1, + ACIOPHY_TOP_BIST_CIOPHY_CFG1_BIST_EN); + } + + /* Configure PIPE mux to USB3 PHY */ + mask32(atcphy->regs.pipehandler + PIPEHANDLER_MUX_CTRL, PIPEHANDLER_MUX_CTRL_CLK, + FIELD_PREP(PIPEHANDLER_MUX_CTRL_CLK, PIPEHANDLER_MUX_CTRL_CLK_OFF)); + udelay(10); + mask32(atcphy->regs.pipehandler + PIPEHANDLER_MUX_CTRL, PIPEHANDLER_MUX_CTRL_DATA, + FIELD_PREP(PIPEHANDLER_MUX_CTRL_DATA, PIPEHANDLER_MUX_CTRL_DATA_USB3)); + udelay(10); + mask32(atcphy->regs.pipehandler + PIPEHANDLER_MUX_CTRL, PIPEHANDLER_MUX_CTRL_CLK, + FIELD_PREP(PIPEHANDLER_MUX_CTRL_CLK, PIPEHANDLER_MUX_CTRL_CLK_USB3)); + udelay(10); + + /* Remove link detection override */ + clear32(atcphy->regs.pipehandler + PIPEHANDLER_OVERRIDE, PIPEHANDLER_OVERRIDE_RXVALID); + clear32(atcphy->regs.pipehandler + PIPEHANDLER_OVERRIDE, PIPEHANDLER_OVERRIDE_RXDETECT); + + /* Pipehandler was only locked when the BIST sequence was applied for host mode */ + if (host) { + ret = atcphy_pipehandler_unlock(atcphy); + if (ret) + dev_warn(atcphy->dev, "Failed to unlock pipehandler"); + } + + return 0; +} + +static int atcphy_configure_pipehandler_dummy(struct apple_atcphy *atcphy) +{ + int ret; + + ret = atcphy_pipehandler_check(atcphy); + if (ret) + return ret; + + /* Force disable link detection */ + clear32(atcphy->regs.pipehandler + PIPEHANDLER_OVERRIDE_VALUES, + PIPEHANDLER_OVERRIDE_VAL_RXDETECT0 | PIPEHANDLER_OVERRIDE_VAL_RXDETECT1); + set32(atcphy->regs.pipehandler + PIPEHANDLER_OVERRIDE, PIPEHANDLER_OVERRIDE_RXVALID); + set32(atcphy->regs.pipehandler + PIPEHANDLER_OVERRIDE, PIPEHANDLER_OVERRIDE_RXDETECT); + + ret = atcphy_pipehandler_lock(atcphy); + if (ret) + dev_warn(atcphy->dev, "Failed to lock pipehandler"); + + /* Switch to dummy PHY */ + mask32(atcphy->regs.pipehandler + PIPEHANDLER_MUX_CTRL, PIPEHANDLER_MUX_CTRL_CLK, + FIELD_PREP(PIPEHANDLER_MUX_CTRL_CLK, PIPEHANDLER_MUX_CTRL_CLK_OFF)); + udelay(10); + mask32(atcphy->regs.pipehandler + PIPEHANDLER_MUX_CTRL, PIPEHANDLER_MUX_CTRL_DATA, + FIELD_PREP(PIPEHANDLER_MUX_CTRL_DATA, PIPEHANDLER_MUX_CTRL_DATA_DUMMY)); + udelay(10); + mask32(atcphy->regs.pipehandler + PIPEHANDLER_MUX_CTRL, PIPEHANDLER_MUX_CTRL_CLK, + FIELD_PREP(PIPEHANDLER_MUX_CTRL_CLK, PIPEHANDLER_MUX_CTRL_CLK_DUMMY)); + udelay(10); + + ret = atcphy_pipehandler_unlock(atcphy); + if (ret) + dev_warn(atcphy->dev, "Failed to unlock pipehandler"); + + mask32(atcphy->regs.pipehandler + PIPEHANDLER_NONSELECTED_OVERRIDE, + PIPEHANDLER_NATIVE_POWER_DOWN, FIELD_PREP(PIPEHANDLER_NATIVE_POWER_DOWN, 2)); + set32(atcphy->regs.pipehandler + PIPEHANDLER_NONSELECTED_OVERRIDE, + PIPEHANDLER_NATIVE_RESET); + + return 0; +} + +static int atcphy_configure_pipehandler(struct apple_atcphy *atcphy, bool host) +{ + int ret; + + lockdep_assert_held(&atcphy->lock); + + switch (atcphy_modes[atcphy->mode].pipehandler_state) { + case ATCPHY_PIPEHANDLER_STATE_USB3: + ret = atcphy_configure_pipehandler_usb3(atcphy, host); + atcphy->pipehandler_up = true; + break; + case ATCPHY_PIPEHANDLER_STATE_USB4: + dev_warn(atcphy->dev, + "ATCPHY_PIPEHANDLER_STATE_USB4 not implemented; falling back to USB2\n"); + ret = atcphy_configure_pipehandler_dummy(atcphy); + atcphy->pipehandler_up = false; + break; + default: + ret = -EINVAL; + } + + return ret; +} + +static void atcphy_setup_pipehandler(struct apple_atcphy *atcphy) +{ + lockdep_assert_held(&atcphy->lock); + + mask32(atcphy->regs.pipehandler + PIPEHANDLER_MUX_CTRL, PIPEHANDLER_MUX_CTRL_CLK, + FIELD_PREP(PIPEHANDLER_MUX_CTRL_CLK, PIPEHANDLER_MUX_CTRL_CLK_OFF)); + udelay(10); + mask32(atcphy->regs.pipehandler + PIPEHANDLER_MUX_CTRL, PIPEHANDLER_MUX_CTRL_DATA, + FIELD_PREP(PIPEHANDLER_MUX_CTRL_DATA, PIPEHANDLER_MUX_CTRL_DATA_DUMMY)); + udelay(10); + mask32(atcphy->regs.pipehandler + PIPEHANDLER_MUX_CTRL, PIPEHANDLER_MUX_CTRL_CLK, + FIELD_PREP(PIPEHANDLER_MUX_CTRL_CLK, PIPEHANDLER_MUX_CTRL_CLK_DUMMY)); + udelay(10); +} + +static void atcphy_configure_lanes(struct apple_atcphy *atcphy, enum atcphy_mode mode) +{ + const struct atcphy_mode_configuration *mode_cfg = atcphy_get_mode_config(atcphy, mode); + + core_mask32(atcphy, ACIOPHY_LANE_MODE, ACIOPHY_LANE_MODE_RX0, + FIELD_PREP(ACIOPHY_LANE_MODE_RX0, mode_cfg->lane_mode[0])); + core_mask32(atcphy, ACIOPHY_LANE_MODE, ACIOPHY_LANE_MODE_TX0, + FIELD_PREP(ACIOPHY_LANE_MODE_TX0, mode_cfg->lane_mode[0])); + core_mask32(atcphy, ACIOPHY_LANE_MODE, ACIOPHY_LANE_MODE_RX1, + FIELD_PREP(ACIOPHY_LANE_MODE_RX1, mode_cfg->lane_mode[1])); + core_mask32(atcphy, ACIOPHY_LANE_MODE, ACIOPHY_LANE_MODE_TX1, + FIELD_PREP(ACIOPHY_LANE_MODE_TX1, mode_cfg->lane_mode[1])); + core_mask32(atcphy, ACIOPHY_CROSSBAR, ACIOPHY_CROSSBAR_PROTOCOL, + FIELD_PREP(ACIOPHY_CROSSBAR_PROTOCOL, mode_cfg->crossbar)); + + if (mode_cfg->set_swap) + core_set32(atcphy, ATCPHY_MISC, ATCPHY_MISC_LANE_SWAP); + else + core_clear32(atcphy, ATCPHY_MISC, ATCPHY_MISC_LANE_SWAP); + + core_mask32(atcphy, ACIOPHY_CROSSBAR, ACIOPHY_CROSSBAR_DP_SINGLE_PMA, + FIELD_PREP(ACIOPHY_CROSSBAR_DP_SINGLE_PMA, mode_cfg->crossbar_dp_single_pma)); + if (mode_cfg->crossbar_dp_both_pma) + core_set32(atcphy, ACIOPHY_CROSSBAR, ACIOPHY_CROSSBAR_DP_BOTH_PMA); + else + core_clear32(atcphy, ACIOPHY_CROSSBAR, ACIOPHY_CROSSBAR_DP_BOTH_PMA); + + if (mode_cfg->dp_lane[0]) { + core_set32(atcphy, LN0_AUSPMA_RX_TOP + LN_AUSPMA_RX_TOP_PMAFSM, + LN_AUSPMA_RX_TOP_PMAFSM_PCS_OV); + udelay(10); + core_clear32(atcphy, LN0_AUSPMA_RX_TOP + LN_AUSPMA_RX_TOP_PMAFSM, + LN_AUSPMA_RX_TOP_PMAFSM_PCS_REQ); + } else { + core_clear32(atcphy, LN0_AUSPMA_RX_TOP + LN_AUSPMA_RX_TOP_PMAFSM, + LN_AUSPMA_RX_TOP_PMAFSM_PCS_OV); + udelay(10); + } + + if (mode_cfg->dp_lane[1]) { + core_set32(atcphy, LN1_AUSPMA_RX_TOP + LN_AUSPMA_RX_TOP_PMAFSM, + LN_AUSPMA_RX_TOP_PMAFSM_PCS_OV); + udelay(10); + core_clear32(atcphy, LN1_AUSPMA_RX_TOP + LN_AUSPMA_RX_TOP_PMAFSM, + LN_AUSPMA_RX_TOP_PMAFSM_PCS_REQ); + } else { + core_clear32(atcphy, LN1_AUSPMA_RX_TOP + LN_AUSPMA_RX_TOP_PMAFSM, + LN_AUSPMA_RX_TOP_PMAFSM_PCS_OV); + udelay(10); + } +} + +static void atcphy_enable_dp_aux(struct apple_atcphy *atcphy) +{ + core_set32(atcphy, ACIOPHY_LANE_DP_CFG_BLK_TX_DP_CTRL0, DPTXPHY_PMA_LANE_RESET_N); + core_set32(atcphy, ACIOPHY_LANE_DP_CFG_BLK_TX_DP_CTRL0, DPTXPHY_PMA_LANE_RESET_N_OV); + + core_mask32(atcphy, ACIOPHY_LANE_DP_CFG_BLK_TX_DP_CTRL0, DPRX_PCLK_SELECT, + FIELD_PREP(DPRX_PCLK_SELECT, 1)); + core_set32(atcphy, ACIOPHY_LANE_DP_CFG_BLK_TX_DP_CTRL0, DPRX_PCLK_ENABLE); + + core_mask32(atcphy, ACIOPHY_LANE_DP_CFG_BLK_TX_DP_CTRL0, DPTX_PCLK1_SELECT, + FIELD_PREP(DPTX_PCLK1_SELECT, 1)); + core_set32(atcphy, ACIOPHY_LANE_DP_CFG_BLK_TX_DP_CTRL0, DPTX_PCLK1_ENABLE); + + core_mask32(atcphy, ACIOPHY_LANE_DP_CFG_BLK_TX_DP_CTRL0, DPTX_PCLK2_SELECT, + FIELD_PREP(DPTX_PCLK2_SELECT, 1)); + core_set32(atcphy, ACIOPHY_LANE_DP_CFG_BLK_TX_DP_CTRL0, DPTX_PCLK2_ENABLE); + + core_set32(atcphy, ACIOPHY_PLL_COMMON_CTRL, + ACIOPHY_PLL_WAIT_FOR_CMN_READY_BEFORE_RESET_EXIT); + + set32(atcphy->regs.lpdptx + LPDPTX_AUX_CONTROL, LPDPTX_AUX_CLAMP_EN); + set32(atcphy->regs.lpdptx + LPDPTX_AUX_CONTROL, LPDPTX_SLEEP_B_SML_IN); + udelay(10); + set32(atcphy->regs.lpdptx + LPDPTX_AUX_CONTROL, LPDPTX_SLEEP_B_BIG_IN); + udelay(10); + clear32(atcphy->regs.lpdptx + LPDPTX_AUX_CONTROL, LPDPTX_AUX_CLAMP_EN); + clear32(atcphy->regs.lpdptx + LPDPTX_AUX_CONTROL, LPDPTX_AUX_PWN_DOWN); + clear32(atcphy->regs.lpdptx + LPDPTX_AUX_CONTROL, LPDPTX_TXTERM_CODEMSB); + mask32(atcphy->regs.lpdptx + LPDPTX_AUX_CONTROL, LPDPTX_TXTERM_CODE, + FIELD_PREP(LPDPTX_TXTERM_CODE, 0x16)); + + set32(atcphy->regs.lpdptx + LPDPTX_AUX_CFG_BLK_AUX_LDO_CTRL, 0x1c00); + mask32(atcphy->regs.lpdptx + LPDPTX_AUX_SHM_CFG_BLK_AUX_CTRL_REG1, LPDPTX_CFG_PMA_PHYS_ADJ, + FIELD_PREP(LPDPTX_CFG_PMA_PHYS_ADJ, 5)); + set32(atcphy->regs.lpdptx + LPDPTX_AUX_SHM_CFG_BLK_AUX_CTRL_REG1, + LPDPTX_CFG_PMA_PHYS_ADJ_OV); + + clear32(atcphy->regs.lpdptx + LPDPTX_AUX_CFG_BLK_AUX_MARGIN, + LPDPTX_MARGIN_RCAL_RXOFFSET_EN); + + clear32(atcphy->regs.lpdptx + LPDPTX_AUX_CFG_BLK_AUX_CTRL, LPDPTX_BLK_AUX_CTRL_PWRDN); + set32(atcphy->regs.lpdptx + LPDPTX_AUX_SHM_CFG_BLK_AUX_CTRL_REG0, + LPDPTX_CFG_PMA_AUX_SEL_LF_DATA); + mask32(atcphy->regs.lpdptx + LPDPTX_AUX_CFG_BLK_AUX_CTRL, LPDPTX_BLK_AUX_RXOFFSET, + FIELD_PREP(LPDPTX_BLK_AUX_RXOFFSET, 3)); + + mask32(atcphy->regs.lpdptx + LPDPTX_AUX_CFG_BLK_AUX_MARGIN, LPDPTX_AUX_MARGIN_RCAL_TXSWING, + FIELD_PREP(LPDPTX_AUX_MARGIN_RCAL_TXSWING, 12)); + + atcphy->dp_link_rate = -1; +} + +static void atcphy_disable_dp_aux(struct apple_atcphy *atcphy) +{ + set32(atcphy->regs.lpdptx + LPDPTX_AUX_CONTROL, LPDPTX_AUX_PWN_DOWN); + set32(atcphy->regs.lpdptx + LPDPTX_AUX_CFG_BLK_AUX_CTRL, LPDPTX_BLK_AUX_CTRL_PWRDN); + set32(atcphy->regs.lpdptx + LPDPTX_AUX_CONTROL, LPDPTX_AUX_CLAMP_EN); + clear32(atcphy->regs.lpdptx + LPDPTX_AUX_CONTROL, LPDPTX_SLEEP_B_SML_IN); + udelay(10); + clear32(atcphy->regs.lpdptx + LPDPTX_AUX_CONTROL, LPDPTX_SLEEP_B_BIG_IN); + udelay(10); + + core_clear32(atcphy, ACIOPHY_LANE_DP_CFG_BLK_TX_DP_CTRL0, DPTXPHY_PMA_LANE_RESET_N); + core_clear32(atcphy, ACIOPHY_LANE_DP_CFG_BLK_TX_DP_CTRL0, DPRX_PCLK_ENABLE); + core_clear32(atcphy, ACIOPHY_LANE_DP_CFG_BLK_TX_DP_CTRL0, DPTX_PCLK1_ENABLE); + core_clear32(atcphy, ACIOPHY_LANE_DP_CFG_BLK_TX_DP_CTRL0, DPTX_PCLK2_ENABLE); +} + +static int atcphy_dp_configure_lane(struct apple_atcphy *atcphy, enum atcphy_lane lane, + const struct atcphy_dp_link_rate_configuration *cfg) +{ + void __iomem *tx_shm, *rx_shm, *rx_top; + unsigned int tx_cal_code; + + lockdep_assert_held(&atcphy->lock); + + switch (lane) { + case APPLE_ATCPHY_LANE_0: + tx_shm = atcphy->regs.core + LN0_AUSPMA_TX_SHM; + rx_shm = atcphy->regs.core + LN0_AUSPMA_RX_SHM; + rx_top = atcphy->regs.core + LN0_AUSPMA_RX_TOP; + break; + case APPLE_ATCPHY_LANE_1: + tx_shm = atcphy->regs.core + LN1_AUSPMA_TX_SHM; + rx_shm = atcphy->regs.core + LN1_AUSPMA_RX_SHM; + rx_top = atcphy->regs.core + LN1_AUSPMA_RX_TOP; + break; + default: + return -EINVAL; + } + + set32(tx_shm + LN_AUSPMA_TX_SHM_TXA_LDOCLK, LN_LDOCLK_EN_SML); + set32(tx_shm + LN_AUSPMA_TX_SHM_TXA_LDOCLK, LN_LDOCLK_EN_SML_OV); + udelay(10); + + set32(tx_shm + LN_AUSPMA_TX_SHM_TXA_LDOCLK, LN_LDOCLK_EN_BIG); + set32(tx_shm + LN_AUSPMA_TX_SHM_TXA_LDOCLK, LN_LDOCLK_EN_BIG_OV); + udelay(10); + + if (cfg->txa_ldoclk_bypass) { + set32(tx_shm + LN_AUSPMA_TX_SHM_TXA_LDOCLK, LN_LDOCLK_BYPASS_SML); + set32(tx_shm + LN_AUSPMA_TX_SHM_TXA_LDOCLK, LN_LDOCLK_BYPASS_SML_OV); + udelay(10); + + set32(tx_shm + LN_AUSPMA_TX_SHM_TXA_LDOCLK, LN_LDOCLK_BYPASS_BIG); + set32(tx_shm + LN_AUSPMA_TX_SHM_TXA_LDOCLK, LN_LDOCLK_BYPASS_BIG_OV); + udelay(10); + } else { + clear32(tx_shm + LN_AUSPMA_TX_SHM_TXA_LDOCLK, LN_LDOCLK_BYPASS_SML); + clear32(tx_shm + LN_AUSPMA_TX_SHM_TXA_LDOCLK, LN_LDOCLK_BYPASS_SML_OV); + udelay(10); + + clear32(tx_shm + LN_AUSPMA_TX_SHM_TXA_LDOCLK, LN_LDOCLK_BYPASS_BIG); + clear32(tx_shm + LN_AUSPMA_TX_SHM_TXA_LDOCLK, LN_LDOCLK_BYPASS_BIG_OV); + udelay(10); + } + + set32(tx_shm + LN_AUSPMA_TX_SHM_TXA_CFG_MAIN_REG0, LN_BYTECLK_RESET_SYNC_SEL_OV); + set32(tx_shm + LN_AUSPMA_TX_SHM_TXA_CFG_MAIN_REG0, LN_BYTECLK_RESET_SYNC_EN); + set32(tx_shm + LN_AUSPMA_TX_SHM_TXA_CFG_MAIN_REG0, LN_BYTECLK_RESET_SYNC_EN_OV); + clear32(tx_shm + LN_AUSPMA_TX_SHM_TXA_CFG_MAIN_REG0, LN_BYTECLK_RESET_SYNC_CLR); + set32(tx_shm + LN_AUSPMA_TX_SHM_TXA_CFG_MAIN_REG0, LN_BYTECLK_RESET_SYNC_CLR_OV); + + if (cfg->txa_div2_en) + set32(tx_shm + LN_AUSPMA_TX_SHM_TXA_CFG_MAIN_REG1, LN_TXA_DIV2_EN); + else + clear32(tx_shm + LN_AUSPMA_TX_SHM_TXA_CFG_MAIN_REG1, LN_TXA_DIV2_EN); + set32(tx_shm + LN_AUSPMA_TX_SHM_TXA_CFG_MAIN_REG1, LN_TXA_DIV2_EN_OV); + set32(tx_shm + LN_AUSPMA_TX_SHM_TXA_CFG_MAIN_REG1, LN_TXA_CLK_EN); + set32(tx_shm + LN_AUSPMA_TX_SHM_TXA_CFG_MAIN_REG1, LN_TXA_CLK_EN_OV); + clear32(tx_shm + LN_AUSPMA_TX_SHM_TXA_CFG_MAIN_REG1, LN_TXA_DIV2_RESET); + set32(tx_shm + LN_AUSPMA_TX_SHM_TXA_CFG_MAIN_REG1, LN_TXA_DIV2_RESET_OV); + + mask32(tx_shm + LN_AUSPMA_TX_SHM_TXA_IMP_REG0, LN_TXA_CAL_CTRL_BASE, + FIELD_PREP(LN_TXA_CAL_CTRL_BASE, 0xf)); + set32(tx_shm + LN_AUSPMA_TX_SHM_TXA_IMP_REG0, LN_TXA_CAL_CTRL_BASE_OV); + + tx_cal_code = FIELD_GET(AUS_UNK_A20_TX_CAL_CODE, readl(atcphy->regs.core + AUS_UNK_A20)); + mask32(tx_shm + LN_AUSPMA_TX_SHM_TXA_IMP_REG0, LN_TXA_CAL_CTRL, + FIELD_PREP(LN_TXA_CAL_CTRL, (1 << tx_cal_code) - 1)); + set32(tx_shm + LN_AUSPMA_TX_SHM_TXA_IMP_REG0, LN_TXA_CAL_CTRL_OV); + + clear32(tx_shm + LN_AUSPMA_TX_SHM_TXA_IMP_REG2, LN_TXA_MARGIN); + set32(tx_shm + LN_AUSPMA_TX_SHM_TXA_IMP_REG2, LN_TXA_MARGIN_OV); + clear32(tx_shm + LN_AUSPMA_TX_SHM_TXA_IMP_REG2, LN_TXA_MARGIN_2R); + set32(tx_shm + LN_AUSPMA_TX_SHM_TXA_IMP_REG2, LN_TXA_MARGIN_2R_OV); + + clear32(tx_shm + LN_AUSPMA_TX_SHM_TXA_IMP_REG3, LN_TXA_MARGIN_POST); + set32(tx_shm + LN_AUSPMA_TX_SHM_TXA_IMP_REG3, LN_TXA_MARGIN_POST_OV); + clear32(tx_shm + LN_AUSPMA_TX_SHM_TXA_IMP_REG3, LN_TXA_MARGIN_POST_2R); + set32(tx_shm + LN_AUSPMA_TX_SHM_TXA_IMP_REG3, LN_TXA_MARGIN_POST_2R_OV); + clear32(tx_shm + LN_AUSPMA_TX_SHM_TXA_IMP_REG3, LN_TXA_MARGIN_POST_4R); + set32(tx_shm + LN_AUSPMA_TX_SHM_TXA_IMP_REG3, LN_TXA_MARGIN_POST_4R_OV); + clear32(tx_shm + LN_AUSPMA_TX_SHM_TXA_IMP_REG3, LN_TXA_MARGIN_PRE); + set32(tx_shm + LN_AUSPMA_TX_SHM_TXA_IMP_REG3, LN_TXA_MARGIN_PRE_OV); + clear32(tx_shm + LN_AUSPMA_TX_SHM_TXA_IMP_REG3, LN_TXA_MARGIN_PRE_2R); + set32(tx_shm + LN_AUSPMA_TX_SHM_TXA_IMP_REG3, LN_TXA_MARGIN_PRE_2R_OV); + clear32(tx_shm + LN_AUSPMA_TX_SHM_TXA_IMP_REG3, LN_TXA_MARGIN_PRE_4R); + set32(tx_shm + LN_AUSPMA_TX_SHM_TXA_IMP_REG3, LN_TXA_MARGIN_PRE_4R_OV); + + clear32(tx_shm + LN_AUSPMA_TX_SHM_TXA_IMP_REG0, LN_TXA_HIZ); + set32(tx_shm + LN_AUSPMA_TX_SHM_TXA_IMP_REG0, LN_TXA_HIZ_OV); + + clear32(rx_shm + LN_AUSPMA_RX_SHM_TJ_RXA_AFE_CTRL1, LN_RX_DIV20_RESET_N); + set32(rx_shm + LN_AUSPMA_RX_SHM_TJ_RXA_AFE_CTRL1, LN_RX_DIV20_RESET_N_OV); + udelay(10); + + set32(rx_shm + LN_AUSPMA_RX_SHM_TJ_RXA_AFE_CTRL1, LN_RX_DIV20_RESET_N); + + set32(rx_shm + LN_AUSPMA_RX_SHM_TJ_RXA_DFE_CTRL12, LN_TX_BYTECLK_RESET_SYNC_EN); + set32(rx_shm + LN_AUSPMA_RX_SHM_TJ_RXA_DFE_CTRL12, LN_TX_BYTECLK_RESET_SYNC_EN_OV); + + mask32(rx_shm + LN_AUSPMA_RX_SHM_TJ_RXA_SAVOS_CTRL16, LN_TX_CAL_CODE, + FIELD_PREP(LN_TX_CAL_CODE, tx_cal_code)); + set32(rx_shm + LN_AUSPMA_RX_SHM_TJ_RXA_SAVOS_CTRL16, LN_TX_CAL_CODE_OV); + + mask32(rx_shm + LN_AUSPMA_RX_SHM_TJ_RXA_TERM_CTRL19, LN_TX_CLK_DLY_CTRL_TAPGEN, + FIELD_PREP(LN_TX_CLK_DLY_CTRL_TAPGEN, 3)); + + clear32(rx_shm + LN_AUSPMA_RX_SHM_TJ_RXA_DFE_CTRL10, LN_DTVREG_ADJUST); + set32(rx_shm + LN_AUSPMA_RX_SHM_TJ_RXA_DFE_CTRL13, LN_DTVREG_ADJUST_OV); + + clear32(rx_shm + LN_AUSPMA_RX_SHM_TJ_RXA_SAVOS_CTRL16, LN_RXTERM_EN); + set32(rx_shm + LN_AUSPMA_RX_SHM_TJ_RXA_SAVOS_CTRL16, LN_RXTERM_EN_OV); + + clear32(rx_shm + LN_AUSPMA_RX_SHM_TJ_RXA_TERM_CTRL19, LN_TX_TEST_EN); + set32(rx_shm + LN_AUSPMA_RX_SHM_TJ_RXA_TERM_CTRL19, LN_TX_TEST_EN_OV); + + set32(rx_shm + LN_AUSPMA_RX_SHM_TJ_RXA_VREF_CTRL22, LN_VREF_TEST_RXLPBKDT_EN); + set32(rx_shm + LN_AUSPMA_RX_SHM_TJ_RXA_VREF_CTRL22, LN_VREF_TEST_RXLPBKDT_EN_OV); + mask32(rx_shm + LN_AUSPMA_RX_SHM_TJ_RXA_VREF_CTRL22, LN_VREF_LPBKIN_DATA, + FIELD_PREP(LN_VREF_LPBKIN_DATA, 3)); + mask32(rx_shm + LN_AUSPMA_RX_SHM_TJ_RXA_VREF_CTRL22, LN_VREF_BIAS_SEL, + FIELD_PREP(LN_VREF_BIAS_SEL, 2)); + set32(rx_shm + LN_AUSPMA_RX_SHM_TJ_RXA_VREF_CTRL22, LN_VREF_BIAS_SEL_OV); + mask32(rx_shm + LN_AUSPMA_RX_SHM_TJ_RXA_VREF_CTRL22, LN_VREF_ADJUST_GRAY, + FIELD_PREP(LN_VREF_ADJUST_GRAY, 0x18)); + set32(rx_shm + LN_AUSPMA_RX_SHM_TJ_RXA_VREF_CTRL22, LN_VREF_ADJUST_GRAY_OV); + set32(rx_shm + LN_AUSPMA_RX_SHM_TJ_RXA_VREF_CTRL22, LN_VREF_EN); + set32(rx_shm + LN_AUSPMA_RX_SHM_TJ_RXA_VREF_CTRL22, LN_VREF_EN_OV); + set32(rx_shm + LN_AUSPMA_RX_SHM_TJ_RXA_VREF_CTRL22, LN_VREF_BOOST_EN); + set32(rx_shm + LN_AUSPMA_RX_SHM_TJ_RXA_VREF_CTRL22, LN_VREF_BOOST_EN_OV); + udelay(10); + + clear32(rx_shm + LN_AUSPMA_RX_SHM_TJ_RXA_VREF_CTRL22, LN_VREF_BOOST_EN); + set32(rx_shm + LN_AUSPMA_RX_SHM_TJ_RXA_VREF_CTRL22, LN_VREF_BOOST_EN_OV); + udelay(10); + + clear32(rx_shm + LN_AUSPMA_RX_SHM_TJ_RXA_DFE_CTRL13, LN_TX_PRE_EN); + set32(rx_shm + LN_AUSPMA_RX_SHM_TJ_RXA_DFE_CTRL13, LN_TX_PRE_EN_OV); + clear32(rx_shm + LN_AUSPMA_RX_SHM_TJ_RXA_DFE_CTRL13, LN_TX_PST1_EN); + set32(rx_shm + LN_AUSPMA_RX_SHM_TJ_RXA_DFE_CTRL13, LN_TX_PST1_EN_OV); + + clear32(rx_shm + LN_AUSPMA_RX_SHM_TJ_RXA_DFE_CTRL12, LN_TX_PBIAS_EN); + set32(rx_shm + LN_AUSPMA_RX_SHM_TJ_RXA_DFE_CTRL12, LN_TX_PBIAS_EN_OV); + + clear32(rx_shm + LN_AUSPMA_RX_SHM_TJ_RXA_SAVOS_CTRL16, LN_RXTERM_PULLUP_LEAK_EN); + set32(rx_shm + LN_AUSPMA_RX_SHM_TJ_RXA_SAVOS_CTRL16, LN_RXTERM_PULLUP_LEAK_EN_OV); + + set32(rx_top + LN_AUSPMA_RX_TOP_TJ_CFG_RX_TXMODE, LN_RX_TXMODE); + + if (cfg->txa_div2_en) + set32(rx_shm + LN_AUSPMA_RX_SHM_TJ_RXA_TERM_CTRL19, LN_TX_CLK_DIV2_EN); + else + clear32(rx_shm + LN_AUSPMA_RX_SHM_TJ_RXA_TERM_CTRL19, LN_TX_CLK_DIV2_EN); + set32(rx_shm + LN_AUSPMA_RX_SHM_TJ_RXA_TERM_CTRL19, LN_TX_CLK_DIV2_EN_OV); + + clear32(rx_shm + LN_AUSPMA_RX_SHM_TJ_RXA_TERM_CTRL19, LN_TX_CLK_DIV2_RST); + set32(rx_shm + LN_AUSPMA_RX_SHM_TJ_RXA_TERM_CTRL19, LN_TX_CLK_DIV2_RST_OV); + + clear32(rx_shm + LN_AUSPMA_RX_SHM_TJ_RXA_DFE_CTRL12, LN_TX_HRCLK_SEL); + set32(rx_shm + LN_AUSPMA_RX_SHM_TJ_RXA_DFE_CTRL12, LN_TX_HRCLK_SEL_OV); + + clear32(rx_shm + LN_AUSPMA_RX_SHM_TJ_RXA_TX_CTRL17, LN_TX_MARGIN); + set32(rx_shm + LN_AUSPMA_RX_SHM_TJ_RXA_TX_CTRL17, LN_TX_MARGIN_OV); + clear32(rx_shm + LN_AUSPMA_RX_SHM_TJ_RXA_TX_CTRL17, LN_TX_MARGIN_LSB); + set32(rx_shm + LN_AUSPMA_RX_SHM_TJ_RXA_TX_CTRL17, LN_TX_MARGIN_LSB_OV); + clear32(rx_shm + LN_AUSPMA_RX_SHM_TJ_RXA_TX_CTRL17, LN_TX_MARGIN_P1); + set32(rx_shm + LN_AUSPMA_RX_SHM_TJ_RXA_TX_CTRL17, LN_TX_MARGIN_P1_OV); + clear32(rx_shm + LN_AUSPMA_RX_SHM_TJ_RXA_TX_CTRL17, LN_TX_MARGIN_P1_LSB); + set32(rx_shm + LN_AUSPMA_RX_SHM_TJ_RXA_TX_CTRL17, LN_TX_MARGIN_P1_LSB_OV); + + clear32(rx_shm + LN_AUSPMA_RX_SHM_TJ_RXA_TX_CTRL18, LN_TX_P1_CODE); + set32(rx_shm + LN_AUSPMA_RX_SHM_TJ_RXA_TX_CTRL18, LN_TX_P1_CODE_OV); + clear32(rx_shm + LN_AUSPMA_RX_SHM_TJ_RXA_TX_CTRL18, LN_TX_P1_LSB_CODE); + set32(rx_shm + LN_AUSPMA_RX_SHM_TJ_RXA_TX_CTRL18, LN_TX_P1_LSB_CODE_OV); + clear32(rx_shm + LN_AUSPMA_RX_SHM_TJ_RXA_TX_CTRL18, LN_TX_MARGIN_PRE); + set32(rx_shm + LN_AUSPMA_RX_SHM_TJ_RXA_TX_CTRL18, LN_TX_MARGIN_PRE_OV); + clear32(rx_shm + LN_AUSPMA_RX_SHM_TJ_RXA_TX_CTRL18, LN_TX_MARGIN_PRE_LSB); + set32(rx_shm + LN_AUSPMA_RX_SHM_TJ_RXA_TX_CTRL18, LN_TX_MARGIN_PRE_LSB_OV); + clear32(rx_shm + LN_AUSPMA_RX_SHM_TJ_RXA_TX_CTRL18, LN_TX_PRE_LSB_CODE); + set32(rx_shm + LN_AUSPMA_RX_SHM_TJ_RXA_TX_CTRL18, LN_TX_PRE_LSB_CODE_OV); + clear32(rx_shm + LN_AUSPMA_RX_SHM_TJ_RXA_TX_CTRL18, LN_TX_PRE_CODE); + set32(rx_shm + LN_AUSPMA_RX_SHM_TJ_RXA_TX_CTRL18, LN_TX_PRE_CODE_OV); + + set32(rx_shm + LN_AUSPMA_RX_SHM_TJ_RXA_DFE_CTRL11, LN_DTVREG_SML_EN); + set32(rx_shm + LN_AUSPMA_RX_SHM_TJ_RXA_DFE_CTRL11, LN_DTVREG_SML_EN_OV); + udelay(10); + + set32(rx_shm + LN_AUSPMA_RX_SHM_TJ_RXA_DFE_CTRL11, LN_DTVREG_BIG_EN); + set32(rx_shm + LN_AUSPMA_RX_SHM_TJ_RXA_DFE_CTRL11, LN_DTVREG_BIG_EN_OV); + udelay(10); + + mask32(rx_shm + LN_AUSPMA_RX_SHM_TJ_RXA_DFE_CTRL10, LN_DTVREG_ADJUST, + FIELD_PREP(LN_DTVREG_ADJUST, 0xa)); + set32(rx_shm + LN_AUSPMA_RX_SHM_TJ_RXA_DFE_CTRL13, LN_DTVREG_ADJUST_OV); + udelay(10); + + set32(rx_shm + LN_AUSPMA_RX_SHM_TJ_RXA_TERM_CTRL19, LN_TX_EN); + set32(rx_shm + LN_AUSPMA_RX_SHM_TJ_RXA_TERM_CTRL19, LN_TX_EN_OV); + udelay(10); + + set32(rx_shm + LN_AUSPMA_RX_SHM_TJ_RXA_CTLE_CTRL0, LN_TX_CLK_EN); + set32(rx_shm + LN_AUSPMA_RX_SHM_TJ_RXA_CTLE_CTRL0, LN_TX_CLK_EN_OV); + + clear32(rx_shm + LN_AUSPMA_RX_SHM_TJ_RXA_DFE_CTRL12, LN_TX_BYTECLK_RESET_SYNC_CLR); + set32(rx_shm + LN_AUSPMA_RX_SHM_TJ_RXA_DFE_CTRL12, LN_TX_BYTECLK_RESET_SYNC_CLR_OV); + + return 0; +} + +static int atcphy_auspll_apb_command(struct apple_atcphy *atcphy, u32 command) +{ + int ret; + u32 reg; + + reg = readl(atcphy->regs.core + AUSPLL_APB_CMD_OVERRIDE); + reg &= ~AUSPLL_APB_CMD_OVERRIDE_CMD; + reg |= FIELD_PREP(AUSPLL_APB_CMD_OVERRIDE_CMD, command); + reg |= AUSPLL_APB_CMD_OVERRIDE_REQ; + reg |= AUSPLL_APB_CMD_OVERRIDE_UNK28; + writel(reg, atcphy->regs.core + AUSPLL_APB_CMD_OVERRIDE); + + ret = readl_poll_timeout(atcphy->regs.core + AUSPLL_APB_CMD_OVERRIDE, reg, + (reg & AUSPLL_APB_CMD_OVERRIDE_ACK), 10, 10000); + if (ret) + dev_warn(atcphy->dev, "AUSPLL APB command was not acked\n"); + + core_clear32(atcphy, AUSPLL_APB_CMD_OVERRIDE, AUSPLL_APB_CMD_OVERRIDE_REQ); + + return 0; +} + +static int atcphy_dp_configure(struct apple_atcphy *atcphy, enum atcphy_dp_link_rate lr) +{ + const struct atcphy_dp_link_rate_configuration *cfg; + const struct atcphy_mode_configuration *mode_cfg; + int ret; + u32 reg; + + guard(mutex)(&atcphy->lock); + mode_cfg = atcphy_get_mode_config(atcphy, atcphy->mode); + cfg = &dp_lr_config[lr]; + + if (atcphy->dp_link_rate == lr) + return 0; + + ret = readl_poll_timeout(atcphy->regs.core + ACIOPHY_CMN_SHM_STS_REG0, reg, + (reg & ACIOPHY_CMN_SHM_STS_REG0_CMD_READY), 10, 10000); + if (ret) { + dev_err(atcphy->dev, "ACIOPHY_CMN_SHM_STS_REG0_CMD_READY not set.\n"); + return ret; + } + + core_clear32(atcphy, AUSPLL_FREQ_CFG, AUSPLL_FREQ_REFCLK); + + core_mask32(atcphy, AUSPLL_FREQ_DESC_A, AUSPLL_FD_FREQ_COUNT_TARGET, + FIELD_PREP(AUSPLL_FD_FREQ_COUNT_TARGET, cfg->freqinit_count_target)); + core_clear32(atcphy, AUSPLL_FREQ_DESC_A, AUSPLL_FD_FBDIVN_HALF); + core_clear32(atcphy, AUSPLL_FREQ_DESC_A, AUSPLL_FD_REV_DIVN); + core_mask32(atcphy, AUSPLL_FREQ_DESC_A, AUSPLL_FD_KI_MAN, FIELD_PREP(AUSPLL_FD_KI_MAN, 8)); + core_mask32(atcphy, AUSPLL_FREQ_DESC_A, AUSPLL_FD_KI_EXP, FIELD_PREP(AUSPLL_FD_KI_EXP, 3)); + core_mask32(atcphy, AUSPLL_FREQ_DESC_A, AUSPLL_FD_KP_MAN, FIELD_PREP(AUSPLL_FD_KP_MAN, 8)); + core_mask32(atcphy, AUSPLL_FREQ_DESC_A, AUSPLL_FD_KP_EXP, FIELD_PREP(AUSPLL_FD_KP_EXP, 7)); + core_clear32(atcphy, AUSPLL_FREQ_DESC_A, AUSPLL_FD_KPKI_SCALE_HBW); + + core_mask32(atcphy, AUSPLL_FREQ_DESC_B, AUSPLL_FD_FBDIVN_FRAC_DEN, + FIELD_PREP(AUSPLL_FD_FBDIVN_FRAC_DEN, cfg->fbdivn_frac_den)); + core_mask32(atcphy, AUSPLL_FREQ_DESC_B, AUSPLL_FD_FBDIVN_FRAC_NUM, + FIELD_PREP(AUSPLL_FD_FBDIVN_FRAC_NUM, cfg->fbdivn_frac_num)); + + core_clear32(atcphy, AUSPLL_FREQ_DESC_C, AUSPLL_FD_SDM_SSC_STEP); + core_clear32(atcphy, AUSPLL_FREQ_DESC_C, AUSPLL_FD_SDM_SSC_EN); + core_mask32(atcphy, AUSPLL_FREQ_DESC_C, AUSPLL_FD_PCLK_DIV_SEL, + FIELD_PREP(AUSPLL_FD_PCLK_DIV_SEL, cfg->pclk_div_sel)); + core_mask32(atcphy, AUSPLL_FREQ_DESC_C, AUSPLL_FD_LFSDM_DIV, + FIELD_PREP(AUSPLL_FD_LFSDM_DIV, 1)); + core_mask32(atcphy, AUSPLL_FREQ_DESC_C, AUSPLL_FD_LFCLK_CTRL, + FIELD_PREP(AUSPLL_FD_LFCLK_CTRL, cfg->lfclk_ctrl)); + core_mask32(atcphy, AUSPLL_FREQ_DESC_C, AUSPLL_FD_VCLK_OP_DIVN, + FIELD_PREP(AUSPLL_FD_VCLK_OP_DIVN, cfg->vclk_op_divn)); + core_set32(atcphy, AUSPLL_FREQ_DESC_C, AUSPLL_FD_VCLK_PRE_DIVN); + + core_mask32(atcphy, AUSPLL_CLKOUT_DIV, AUSPLL_CLKOUT_PLLA_REFBUFCLK_DI, + FIELD_PREP(AUSPLL_CLKOUT_PLLA_REFBUFCLK_DI, 7)); + + if (cfg->plla_clkout_vreg_bypass) + core_set32(atcphy, AUSPLL_CLKOUT_DTC_VREG, AUSPLL_DTC_VREG_BYPASS); + else + core_clear32(atcphy, AUSPLL_CLKOUT_DTC_VREG, AUSPLL_DTC_VREG_BYPASS); + + core_set32(atcphy, AUSPLL_BGR, AUSPLL_BGR_CTRL_AVAIL); + + core_set32(atcphy, AUSPLL_CLKOUT_MASTER, AUSPLL_CLKOUT_MASTER_PCLK_DRVR_EN); + core_set32(atcphy, AUSPLL_CLKOUT_MASTER, AUSPLL_CLKOUT_MASTER_PCLK2_DRVR_EN); + core_set32(atcphy, AUSPLL_CLKOUT_MASTER, AUSPLL_CLKOUT_MASTER_REFBUFCLK_DRVR_EN); + + ret = atcphy_auspll_apb_command(atcphy, 0); + if (ret) + return ret; + + ret = readl_poll_timeout(atcphy->regs.core + ACIOPHY_DP_PCLK_STAT, reg, + (reg & ACIOPHY_AUSPLL_LOCK), 10, 10000); + if (ret) { + dev_err(atcphy->dev, "ACIOPHY_DP_PCLK did not lock.\n"); + return ret; + } + + ret = atcphy_auspll_apb_command(atcphy, 0x2800); + if (ret) + return ret; + + if (mode_cfg->dp_lane[0]) { + ret = atcphy_dp_configure_lane(atcphy, APPLE_ATCPHY_LANE_0, cfg); + if (ret) + return ret; + } + + if (mode_cfg->dp_lane[1]) { + ret = atcphy_dp_configure_lane(atcphy, APPLE_ATCPHY_LANE_1, cfg); + if (ret) + return ret; + } + + core_clear32(atcphy, ACIOPHY_LANE_DP_CFG_BLK_TX_DP_CTRL0, DP_PMA_BYTECLK_RESET); + core_clear32(atcphy, ACIOPHY_LANE_DP_CFG_BLK_TX_DP_CTRL0, DP_MAC_DIV20_CLK_SEL); + + atcphy->dp_link_rate = lr; + return 0; +} + +static void atcphy_usb2_power_off(struct apple_atcphy *atcphy) +{ + /* Disable the PHY, this clears USB2PHY_USBCTL_RUN */ + writel(USB2PHY_USBCTL_ISOLATION, atcphy->regs.usb2phy + USB2PHY_USBCTL); + udelay(10); + + /* Switch the PHY to low power mode */ + set32(atcphy->regs.usb2phy + USB2PHY_CTL, USB2PHY_CTL_SIDDQ); + udelay(10); + + /* Enable all resets */ + set32(atcphy->regs.usb2phy + USB2PHY_CTL, USB2PHY_CTL_PORT_RESET); + udelay(10); + set32(atcphy->regs.usb2phy + USB2PHY_CTL, USB2PHY_CTL_RESET); + udelay(10); + clear32(atcphy->regs.usb2phy + USB2PHY_CTL, USB2PHY_CTL_APB_RESET_N); + udelay(10); + set32(atcphy->regs.usb2phy + USB2PHY_MISCTUNE, USB2PHY_MISCTUNE_APBCLK_GATE_OFF); + set32(atcphy->regs.usb2phy + USB2PHY_MISCTUNE, USB2PHY_MISCTUNE_REFCLK_GATE_OFF); +} + +static int atcphy_power_off(struct apple_atcphy *atcphy) +{ + u32 reg; + int ret; + + atcphy_disable_dp_aux(atcphy); + + /* Enable all reset lines */ + core_clear32(atcphy, ATCPHY_POWER_CTRL, ATCPHY_POWER_PHY_RESET_N); + core_set32(atcphy, ATCPHY_POWER_CTRL, ATCPHY_POWER_CLAMP_EN); + core_clear32(atcphy, ATCPHY_MISC, ATCPHY_MISC_RESET_N | ATCPHY_MISC_LANE_SWAP); + core_clear32(atcphy, ATCPHY_POWER_CTRL, ATCPHY_POWER_APB_RESET_N); + + core_clear32(atcphy, ATCPHY_POWER_CTRL, ATCPHY_POWER_SLEEP_BIG); + ret = readl_poll_timeout(atcphy->regs.core + ATCPHY_POWER_STAT, reg, + !(reg & ATCPHY_POWER_SLEEP_BIG), 10, 1000); + if (ret) { + dev_err(atcphy->dev, "Failed to sleep atcphy \"big\"\n"); + return ret; + } + + core_clear32(atcphy, ATCPHY_POWER_CTRL, ATCPHY_POWER_SLEEP_SMALL); + ret = readl_poll_timeout(atcphy->regs.core + ATCPHY_POWER_STAT, reg, + !(reg & ATCPHY_POWER_SLEEP_SMALL), 10, 1000); + if (ret) { + dev_err(atcphy->dev, "Failed to sleep atcphy \"small\"\n"); + return ret; + } + + return 0; +} + +static void atcphy_usb2_power_on(struct apple_atcphy *atcphy) +{ + set32(atcphy->regs.usb2phy + USB2PHY_SIG, + USB2PHY_SIG_VBUSDET_FORCE_VAL | USB2PHY_SIG_VBUSDET_FORCE_EN | + USB2PHY_SIG_VBUSVLDEXT_FORCE_VAL | USB2PHY_SIG_VBUSVLDEXT_FORCE_EN); + udelay(10); + + /* Take the PHY out of its low power state */ + clear32(atcphy->regs.usb2phy + USB2PHY_CTL, USB2PHY_CTL_SIDDQ); + udelay(10); + + /* Release reset */ + clear32(atcphy->regs.usb2phy + USB2PHY_CTL, USB2PHY_CTL_RESET); + udelay(10); + clear32(atcphy->regs.usb2phy + USB2PHY_CTL, USB2PHY_CTL_PORT_RESET); + udelay(10); + set32(atcphy->regs.usb2phy + USB2PHY_CTL, USB2PHY_CTL_APB_RESET_N); + udelay(10); + clear32(atcphy->regs.usb2phy + USB2PHY_MISCTUNE, USB2PHY_MISCTUNE_APBCLK_GATE_OFF); + clear32(atcphy->regs.usb2phy + USB2PHY_MISCTUNE, USB2PHY_MISCTUNE_REFCLK_GATE_OFF); + + /* Enable the PHY */ + writel(USB2PHY_USBCTL_RUN, atcphy->regs.usb2phy + USB2PHY_USBCTL); +} + +static int atcphy_power_on(struct apple_atcphy *atcphy) +{ + u32 reg; + int ret; + + atcphy_usb2_power_on(atcphy); + + core_set32(atcphy, ATCPHY_MISC, ATCPHY_MISC_RESET_N); + + core_set32(atcphy, ATCPHY_POWER_CTRL, ATCPHY_POWER_SLEEP_SMALL); + ret = readl_poll_timeout(atcphy->regs.core + ATCPHY_POWER_STAT, reg, + reg & ATCPHY_POWER_SLEEP_SMALL, 100, 100000); + if (ret) { + dev_err(atcphy->dev, "failed to wakeup atcphy \"small\"\n"); + return ret; + } + + core_set32(atcphy, ATCPHY_POWER_CTRL, ATCPHY_POWER_SLEEP_BIG); + ret = readl_poll_timeout(atcphy->regs.core + ATCPHY_POWER_STAT, reg, + reg & ATCPHY_POWER_SLEEP_BIG, 100, 100000); + if (ret) { + dev_err(atcphy->dev, "failed to wakeup atcphy \"big\"\n"); + return ret; + } + + core_clear32(atcphy, ATCPHY_POWER_CTRL, ATCPHY_POWER_CLAMP_EN); + core_set32(atcphy, ATCPHY_POWER_CTRL, ATCPHY_POWER_APB_RESET_N); + + return 0; +} + +static int atcphy_configure(struct apple_atcphy *atcphy, enum atcphy_mode mode) +{ + int ret = 0; + + lockdep_assert_held(&atcphy->lock); + + if (mode == APPLE_ATCPHY_MODE_OFF) { + ret = atcphy_power_off(atcphy); + atcphy->mode = mode; + return ret; + } + + ret = atcphy_power_on(atcphy); + if (ret) + return ret; + + atcphy_apply_tunables(atcphy, mode); + + core_set32(atcphy, AUSPLL_FSM_CTRL, 0x1fe000); + core_set32(atcphy, AUSPLL_APB_CMD_OVERRIDE, AUSPLL_APB_CMD_OVERRIDE_UNK28); + + set32(atcphy->regs.core + ACIOPHY_CFG0, ACIOPHY_CFG0_COMMON_SMALL_OV); + udelay(10); + set32(atcphy->regs.core + ACIOPHY_CFG0, ACIOPHY_CFG0_COMMON_BIG_OV); + udelay(10); + set32(atcphy->regs.core + ACIOPHY_CFG0, ACIOPHY_CFG0_COMMON_CLAMP_OV); + udelay(10); + + mask32(atcphy->regs.core + ACIOPHY_SLEEP_CTRL, ACIOPHY_SLEEP_CTRL_TX_SMALL_OV, + FIELD_PREP(ACIOPHY_SLEEP_CTRL_TX_SMALL_OV, 3)); + udelay(10); + mask32(atcphy->regs.core + ACIOPHY_SLEEP_CTRL, ACIOPHY_SLEEP_CTRL_TX_BIG_OV, + FIELD_PREP(ACIOPHY_SLEEP_CTRL_TX_BIG_OV, 3)); + udelay(10); + mask32(atcphy->regs.core + ACIOPHY_SLEEP_CTRL, ACIOPHY_SLEEP_CTRL_TX_CLAMP_OV, + FIELD_PREP(ACIOPHY_SLEEP_CTRL_TX_CLAMP_OV, 3)); + udelay(10); + + mask32(atcphy->regs.core + ACIOPHY_CFG0, ACIOPHY_CFG0_RX_BIG_OV, + FIELD_PREP(ACIOPHY_CFG0_RX_BIG_OV, 3)); + udelay(10); + mask32(atcphy->regs.core + ACIOPHY_CFG0, ACIOPHY_CFG0_RX_SMALL_OV, + FIELD_PREP(ACIOPHY_CFG0_RX_SMALL_OV, 3)); + udelay(10); + mask32(atcphy->regs.core + ACIOPHY_CFG0, ACIOPHY_CFG0_RX_CLAMP_OV, + FIELD_PREP(ACIOPHY_CFG0_RX_CLAMP_OV, 3)); + udelay(10); + + /* Setup AUX channel if DP altmode is requested */ + if (atcphy_modes[mode].enable_dp_aux) + atcphy_enable_dp_aux(atcphy); + + /* Enable clocks and configure lanes */ + core_set32(atcphy, CIO3PLL_CLK_CTRL, CIO3PLL_CLK_PCLK_EN); + core_set32(atcphy, CIO3PLL_CLK_CTRL, CIO3PLL_CLK_REFCLK_EN); + atcphy_configure_lanes(atcphy, mode); + + /* Take the USB3 PHY out of reset */ + core_set32(atcphy, ATCPHY_POWER_CTRL, ATCPHY_POWER_PHY_RESET_N); + + atcphy->mode = mode; + + return 0; +} + +static int atcphy_usb2_set_mode(struct phy *phy, enum phy_mode mode, int submode) +{ + struct apple_atcphy *atcphy = phy_get_drvdata(phy); + + guard(mutex)(&atcphy->lock); + + switch (mode) { + case PHY_MODE_USB_HOST: + set32(atcphy->regs.usb2phy + USB2PHY_SIG, USB2PHY_SIG_HOST); + break; + case PHY_MODE_USB_DEVICE: + clear32(atcphy->regs.usb2phy + USB2PHY_SIG, USB2PHY_SIG_HOST); + break; + default: + return -EINVAL; + } + + return 0; +} + +static const struct phy_ops apple_atc_usb2_phy_ops = { + .owner = THIS_MODULE, + .set_mode = atcphy_usb2_set_mode, +}; + +static int atcphy_usb3_power_off(struct phy *phy) +{ + struct apple_atcphy *atcphy = phy_get_drvdata(phy); + int ret; + + guard(mutex)(&atcphy->lock); + + ret = atcphy_configure_pipehandler_dummy(atcphy); + if (ret) + dev_warn(atcphy->dev, "Failed to switch pipe to dummy: %d", ret); + + atcphy->pipehandler_up = false; + + if (atcphy->mode != APPLE_ATCPHY_MODE_OFF) + atcphy_configure(atcphy, APPLE_ATCPHY_MODE_OFF); + + return 0; +} + +static int atcphy_usb3_set_mode(struct phy *phy, enum phy_mode mode, int submode) +{ + struct apple_atcphy *atcphy = phy_get_drvdata(phy); + + guard(mutex)(&atcphy->lock); + + /* + * We may get multiple calls to set_mode (for host mode e.g. at least one from the dwc3 glue + * driver and then another one from the generic xhci code) but must only configure the + * PIPE handler once. + */ + if (atcphy->pipehandler_up) + return 0; + + switch (mode) { + case PHY_MODE_USB_HOST: + return atcphy_configure_pipehandler(atcphy, true); + case PHY_MODE_USB_DEVICE: + return atcphy_configure_pipehandler(atcphy, false); + default: + return -EINVAL; + } +} + +static const struct phy_ops apple_atc_usb3_phy_ops = { + .owner = THIS_MODULE, + .power_off = atcphy_usb3_power_off, + .set_mode = atcphy_usb3_set_mode, +}; + +static int atcphy_dpphy_set_mode(struct phy *phy, enum phy_mode mode, int submode) +{ + /* Nothing to do here since the setup already happened in mux_set */ + if (mode == PHY_MODE_DP && submode == 0) + return 0; + return -EINVAL; +} + +static int atcphy_dpphy_validate(struct phy *phy, enum phy_mode mode, int submode, + union phy_configure_opts *opts_) +{ + struct phy_configure_opts_dp *opts = &opts_->dp; + struct apple_atcphy *atcphy = phy_get_drvdata(phy); + + if (mode != PHY_MODE_DP) + return -EINVAL; + if (submode != 0) + return -EINVAL; + + switch (atcphy->mode) { + case APPLE_ATCPHY_MODE_USB3_DP: + opts->lanes = 2; + break; + case APPLE_ATCPHY_MODE_DP: + opts->lanes = 4; + break; + default: + opts->lanes = 0; + } + + return 0; +} + +static int atcphy_dpphy_configure(struct phy *phy, union phy_configure_opts *opts_) +{ + struct phy_configure_opts_dp *opts = &opts_->dp; + struct apple_atcphy *atcphy = phy_get_drvdata(phy); + enum atcphy_dp_link_rate link_rate; + + if (opts->set_voltages) + return -EINVAL; + if (opts->set_lanes) + return -EINVAL; + + if (opts->set_rate) { + switch (opts->link_rate) { + case 1620: + link_rate = ATCPHY_DP_LINK_RATE_RBR; + break; + case 2700: + link_rate = ATCPHY_DP_LINK_RATE_HBR; + break; + case 5400: + link_rate = ATCPHY_DP_LINK_RATE_HBR2; + break; + case 8100: + link_rate = ATCPHY_DP_LINK_RATE_HBR3; + break; + case 0: + return 0; + default: + dev_err(atcphy->dev, "Unsupported link rate: %d\n", opts->link_rate); + return -EINVAL; + } + + return atcphy_dp_configure(atcphy, link_rate); + } + + return 0; +} + +static const struct phy_ops apple_atc_dp_phy_ops = { + .owner = THIS_MODULE, + .configure = atcphy_dpphy_configure, + .validate = atcphy_dpphy_validate, + .set_mode = atcphy_dpphy_set_mode, +}; + +static struct phy *atcphy_xlate(struct device *dev, const struct of_phandle_args *args) +{ + struct apple_atcphy *atcphy = dev_get_drvdata(dev); + + switch (args->args[0]) { + case PHY_TYPE_USB2: + return atcphy->phys.usb2; + case PHY_TYPE_USB3: + return atcphy->phys.usb3; + case PHY_TYPE_DP: + return atcphy->phys.dp; + } + return ERR_PTR(-ENODEV); +} + +static int atcphy_probe_phy(struct apple_atcphy *atcphy) +{ + struct { + struct phy **phy; + const struct phy_ops *ops; + } phys[] = { + { &atcphy->phys.usb2, &apple_atc_usb2_phy_ops }, + { &atcphy->phys.usb3, &apple_atc_usb3_phy_ops }, + { &atcphy->phys.dp, &apple_atc_dp_phy_ops }, + }; + + for (int i = 0; i < ARRAY_SIZE(phys); i++) { + *phys[i].phy = devm_phy_create(atcphy->dev, NULL, phys[i].ops); + if (IS_ERR(*phys[i].phy)) + return PTR_ERR(*phys[i].phy); + phy_set_drvdata(*phys[i].phy, atcphy); + } + + atcphy->phy_provider = devm_of_phy_provider_register(atcphy->dev, atcphy_xlate); + if (IS_ERR(atcphy->phy_provider)) + return PTR_ERR(atcphy->phy_provider); + return 0; +} + +static void _atcphy_dwc3_reset_assert(struct apple_atcphy *atcphy) +{ + lockdep_assert_held(&atcphy->lock); + + clear32(atcphy->regs.pipehandler + PIPEHANDLER_AON_GEN, PIPEHANDLER_AON_GEN_DWC3_RESET_N); + set32(atcphy->regs.pipehandler + PIPEHANDLER_AON_GEN, + PIPEHANDLER_AON_GEN_DWC3_FORCE_CLAMP_EN); +} + +static int atcphy_dwc3_reset_assert(struct reset_controller_dev *rcdev, unsigned long id) +{ + struct apple_atcphy *atcphy = container_of(rcdev, struct apple_atcphy, rcdev); + int ret; + + guard(mutex)(&atcphy->lock); + + _atcphy_dwc3_reset_assert(atcphy); + + if (atcphy->pipehandler_up) { + ret = atcphy_configure_pipehandler_dummy(atcphy); + if (ret) + dev_warn(atcphy->dev, "Failed to switch PIPE to dummy: %d\n", ret); + else + atcphy->pipehandler_up = false; + } + + atcphy_usb2_power_off(atcphy); + + return 0; +} + +static int atcphy_dwc3_reset_deassert(struct reset_controller_dev *rcdev, unsigned long id) +{ + struct apple_atcphy *atcphy = container_of(rcdev, struct apple_atcphy, rcdev); + + guard(mutex)(&atcphy->lock); + + clear32(atcphy->regs.pipehandler + PIPEHANDLER_AON_GEN, + PIPEHANDLER_AON_GEN_DWC3_FORCE_CLAMP_EN); + set32(atcphy->regs.pipehandler + PIPEHANDLER_AON_GEN, PIPEHANDLER_AON_GEN_DWC3_RESET_N); + + return 0; +} + +const struct reset_control_ops atcphy_dwc3_reset_ops = { + .assert = atcphy_dwc3_reset_assert, + .deassert = atcphy_dwc3_reset_deassert, +}; + +static int atcphy_reset_xlate(struct reset_controller_dev *rcdev, + const struct of_phandle_args *reset_spec) +{ + return 0; +} + +static int atcphy_probe_rcdev(struct apple_atcphy *atcphy) +{ + atcphy->rcdev.owner = THIS_MODULE; + atcphy->rcdev.nr_resets = 1; + atcphy->rcdev.ops = &atcphy_dwc3_reset_ops; + atcphy->rcdev.of_node = atcphy->dev->of_node; + atcphy->rcdev.of_reset_n_cells = 0; + atcphy->rcdev.of_xlate = atcphy_reset_xlate; + + return devm_reset_controller_register(atcphy->dev, &atcphy->rcdev); +} + +static int atcphy_sw_set(struct typec_switch_dev *sw, enum typec_orientation orientation) +{ + struct apple_atcphy *atcphy = typec_switch_get_drvdata(sw); + + guard(mutex)(&atcphy->lock); + + switch (orientation) { + case TYPEC_ORIENTATION_NONE: + break; + case TYPEC_ORIENTATION_NORMAL: + atcphy->swap_lanes = false; + break; + case TYPEC_ORIENTATION_REVERSE: + atcphy->swap_lanes = true; + break; + } + + return 0; +} + +static int atcphy_probe_switch(struct apple_atcphy *atcphy) +{ + struct typec_switch_desc sw_desc = { + .drvdata = atcphy, + .fwnode = atcphy->dev->fwnode, + .set = atcphy_sw_set, + }; + + return PTR_ERR_OR_ZERO(typec_switch_register(atcphy->dev, &sw_desc)); +} + +static int atcphy_mux_set(struct typec_mux_dev *mux, struct typec_mux_state *state) +{ + struct apple_atcphy *atcphy = typec_mux_get_drvdata(mux); + enum atcphy_mode target_mode; + + guard(mutex)(&atcphy->lock); + + if (state->mode == TYPEC_STATE_SAFE) { + target_mode = APPLE_ATCPHY_MODE_OFF; + } else if (state->mode == TYPEC_STATE_USB) { + target_mode = APPLE_ATCPHY_MODE_USB3; + } else if (!state->alt && state->mode == TYPEC_MODE_USB4) { + struct enter_usb_data *data = state->data; + u32 eudo_usb_mode = FIELD_GET(EUDO_USB_MODE_MASK, data->eudo); + + switch (eudo_usb_mode) { + case EUDO_USB_MODE_USB2: + target_mode = APPLE_ATCPHY_MODE_USB2; + break; + case EUDO_USB_MODE_USB3: + target_mode = APPLE_ATCPHY_MODE_USB3; + break; + case EUDO_USB_MODE_USB4: + target_mode = APPLE_ATCPHY_MODE_USB4; + break; + default: + dev_warn(atcphy->dev, "Unsupported EUDO USB mode: 0x%x.\n", eudo_usb_mode); + target_mode = APPLE_ATCPHY_MODE_OFF; + } + } else if (state->alt && state->alt->svid == USB_TYPEC_TBT_SID) { + target_mode = APPLE_ATCPHY_MODE_TBT; + } else if (state->alt && state->alt->svid == USB_TYPEC_DP_SID) { + switch (state->mode) { + case TYPEC_DP_STATE_C: + case TYPEC_DP_STATE_E: + target_mode = APPLE_ATCPHY_MODE_DP; + break; + case TYPEC_DP_STATE_D: + target_mode = APPLE_ATCPHY_MODE_USB3_DP; + break; + default: + dev_err(atcphy->dev, + "Unsupported DP pin assignment: 0x%lx, your connected device will not work.\n", + state->mode); + target_mode = APPLE_ATCPHY_MODE_OFF; + } + } else if (state->alt) { + dev_err(atcphy->dev, + "Unknown alternate mode SVID: 0x%x, your connected device will not work.\n", + state->alt->svid); + target_mode = APPLE_ATCPHY_MODE_OFF; + } else { + dev_err(atcphy->dev, "Unknown mode: 0x%lx, your connected device will not work.\n", + state->mode); + target_mode = APPLE_ATCPHY_MODE_OFF; + } + + if (atcphy->mode == target_mode) + return 0; + + /* + * If the pipehandler is still/already up here there's a bug somewhere so make sure to + * complain loudly. We can still try to switch modes and hope for the best though, + * in the worst case the hardware will fall back to USB2-only. + */ + WARN_ON_ONCE(atcphy->pipehandler_up); + return atcphy_configure(atcphy, target_mode); +} + +static int atcphy_probe_mux(struct apple_atcphy *atcphy) +{ + struct typec_mux_desc mux_desc = { + .drvdata = atcphy, + .fwnode = atcphy->dev->fwnode, + .set = atcphy_mux_set, + }; + + return PTR_ERR_OR_ZERO(typec_mux_register(atcphy->dev, &mux_desc)); +} + +static int atcphy_load_tunables(struct apple_atcphy *atcphy) +{ + struct { + const char *dt_name; + struct apple_tunable **tunable; + struct resource *res; + } tunables[] = { + { "apple,tunable-axi2af", &atcphy->tunables.axi2af, atcphy->res.axi2af }, + { "apple,tunable-common-a", &atcphy->tunables.common[0], atcphy->res.core }, + { "apple,tunable-common-b", &atcphy->tunables.common[1], atcphy->res.core }, + { "apple,tunable-lane0-usb", &atcphy->tunables.lane_usb3[0], atcphy->res.core }, + { "apple,tunable-lane1-usb", &atcphy->tunables.lane_usb3[1], atcphy->res.core }, + { "apple,tunable-lane0-cio", &atcphy->tunables.lane_usb4[0], atcphy->res.core }, + { "apple,tunable-lane1-cio", &atcphy->tunables.lane_usb4[1], atcphy->res.core }, + { "apple,tunable-lane0-dp", &atcphy->tunables.lane_dp[0], atcphy->res.core }, + { "apple,tunable-lane1-dp", &atcphy->tunables.lane_dp[1], atcphy->res.core }, + }; + + for (int i = 0; i < ARRAY_SIZE(tunables); i++) { + *tunables[i].tunable = devm_apple_tunable_parse( + atcphy->dev, atcphy->np, tunables[i].dt_name, tunables[i].res); + if (IS_ERR(tunables[i].tunable)) { + dev_err(atcphy->dev, "Failed to read tunable %s: %ld\n", + tunables[i].dt_name, PTR_ERR(tunables[i].tunable)); + return PTR_ERR(tunables[i].tunable); + } + } + + return 0; +} + +static int atcphy_map_resources(struct platform_device *pdev, struct apple_atcphy *atcphy) +{ + struct { + const char *name; + void __iomem **addr; + struct resource **res; + } resources[] = { + { "core", &atcphy->regs.core, &atcphy->res.core }, + { "lpdptx", &atcphy->regs.lpdptx, NULL }, + { "axi2af", &atcphy->regs.axi2af, &atcphy->res.axi2af }, + { "usb2phy", &atcphy->regs.usb2phy, NULL }, + { "pipehandler", &atcphy->regs.pipehandler, NULL }, + }; + struct resource *res; + + for (int i = 0; i < ARRAY_SIZE(resources); i++) { + res = platform_get_resource_byname(pdev, IORESOURCE_MEM, resources[i].name); + *resources[i].addr = devm_ioremap_resource(&pdev->dev, res); + if (IS_ERR(resources[i].addr)) + return dev_err_probe(atcphy->dev, PTR_ERR(resources[i].addr), + "Unable to map %s regs", resources[i].name); + + if (resources[i].res) + *resources[i].res = res; + } + + return 0; +} + +static int atcphy_probe_finalize(struct apple_atcphy *atcphy) +{ + int ret; + + guard(mutex)(&atcphy->lock); + + /* Reset dwc3 on probe, let dwc3 (consumer) deassert it */ + _atcphy_dwc3_reset_assert(atcphy); + + /* Reset atcphy to clear any state potentially left by the bootloader */ + atcphy_power_off(atcphy); + atcphy_setup_pipehandler(atcphy); + + ret = atcphy_probe_rcdev(atcphy); + if (ret) + return dev_err_probe(atcphy->dev, ret, "Probing rcdev failed"); + ret = atcphy_probe_mux(atcphy); + if (ret) + return dev_err_probe(atcphy->dev, ret, "Probing mux failed"); + ret = atcphy_probe_switch(atcphy); + if (ret) + return dev_err_probe(atcphy->dev, ret, "Probing switch failed"); + ret = atcphy_probe_phy(atcphy); + if (ret) + return dev_err_probe(atcphy->dev, ret, "Probing phy failed"); + + return 0; +} + +static int atcphy_probe(struct platform_device *pdev) +{ + struct apple_atcphy *atcphy; + struct device *dev = &pdev->dev; + int ret; + + atcphy = devm_kzalloc(&pdev->dev, sizeof(*atcphy), GFP_KERNEL); + if (!atcphy) + return -ENOMEM; + + atcphy->dev = dev; + atcphy->np = dev->of_node; + mutex_init(&atcphy->lock); + platform_set_drvdata(pdev, atcphy); + + ret = atcphy_map_resources(pdev, atcphy); + if (ret) + return ret; + ret = atcphy_load_tunables(atcphy); + if (ret) + return ret; + + atcphy->mode = APPLE_ATCPHY_MODE_OFF; + atcphy->pipehandler_up = false; + + return atcphy_probe_finalize(atcphy); +} + +static const struct of_device_id atcphy_match[] = { + { .compatible = "apple,t8103-atcphy" }, + {}, +}; +MODULE_DEVICE_TABLE(of, atcphy_match); + +static struct platform_driver atcphy_driver = { + .driver = { + .name = "phy-apple-atc", + .of_match_table = atcphy_match, + }, + .probe = atcphy_probe, +}; +module_platform_driver(atcphy_driver); + +MODULE_AUTHOR("Sven Peter "); +MODULE_DESCRIPTION("Apple Type-C PHY driver"); +MODULE_LICENSE("GPL"); From a598ea1f438c58d2b5592bd001f05c7f3a61e56c Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Fri, 5 May 2023 17:40:26 +0200 Subject: [PATCH 0368/3902] phy: apple: Add DP TX phy driver This driver is found on Apple's Mac mini (M2, 2023) and controls one output of the main display controller. It is connected to a MCDP 29XX (public known part is MCDP 2900) DP 1.4 to HDMI 2.0a protocol converter. Signed-off-by: Janne Grunau --- drivers/phy/apple/Kconfig | 9 + drivers/phy/apple/Makefile | 3 + drivers/phy/apple/dptx.c | 690 +++++++++++++++++++++++++++++++++++++ drivers/phy/apple/dptx.h | 18 + 4 files changed, 720 insertions(+) create mode 100644 drivers/phy/apple/dptx.c create mode 100644 drivers/phy/apple/dptx.h diff --git a/drivers/phy/apple/Kconfig b/drivers/phy/apple/Kconfig index 67f46051259260..8409b67f6d1ecb 100644 --- a/drivers/phy/apple/Kconfig +++ b/drivers/phy/apple/Kconfig @@ -12,3 +12,12 @@ config PHY_APPLE_ATC If M is selected the module will be called 'phy-apple-atc'. +config PHY_APPLE_DPTX + tristate "Apple DPTX PHY" + depends on ARCH_APPLE || COMPILE_TEST + select GENERIC_PHY + help + Enable this to add support for the Apple DPTX PHY found on Apple SoCs + such as the M2. + This driver provides support for DisplayPort and is used on the + Mac mini (M2 and M2 Pro, 2023). diff --git a/drivers/phy/apple/Makefile b/drivers/phy/apple/Makefile index e02836a63df3b5..b9e7bf3e4ac170 100644 --- a/drivers/phy/apple/Makefile +++ b/drivers/phy/apple/Makefile @@ -2,3 +2,6 @@ obj-$(CONFIG_PHY_APPLE_ATC) += phy-apple-atc.o phy-apple-atc-y := atc.o + +obj-$(CONFIG_PHY_APPLE_DPTX) += phy-apple-dptx.o +phy-apple-dptx-y += dptx.o diff --git a/drivers/phy/apple/dptx.c b/drivers/phy/apple/dptx.c new file mode 100644 index 00000000000000..f0df2d40a18023 --- /dev/null +++ b/drivers/phy/apple/dptx.c @@ -0,0 +1,690 @@ +// SPDX-License-Identifier: GPL-2.0 OR BSD-2-Clause +/* + * Apple dptx PHY driver + * + * Copyright (C) The Asahi Linux Contributors + * Author: Janne Grunau + * + * based on drivers/phy/apple/atc.c + * + * Copyright (C) The Asahi Linux Contributors + * Author: Sven Peter + */ + +#include "dptx.h" + +#include +#include "linux/of.h" +#include +#include +#include +#include +#include +#include +#include + +#define DPTX_MAX_LANES 4 +#define DPTX_LANE0_OFFSET 0x5000 +#define DPTX_LANE_STRIDE 0x1000 +#define DPTX_LANE_END (DPTX_LANE0_OFFSET + DPTX_MAX_LANES * DPTX_LANE_STRIDE) + +enum apple_dptx_type { + DPTX_PHY_T8112, + DPTX_PHY_T6020, +}; + +struct apple_dptx_phy_hw { + enum apple_dptx_type type; +}; + +struct apple_dptx_phy { + struct device *dev; + + struct apple_dptx_phy_hw hw; + + int dp_link_rate; + + struct { + void __iomem *core; + void __iomem *dptx; + } regs; + + struct phy *phy_dp; + struct phy_provider *phy_provider; + + struct mutex lock; + + // TODO: m1n1 port things to clean up + u32 active_lanes; +}; + + +static inline void mask32(void __iomem *reg, u32 mask, u32 set) +{ + u32 value = readl(reg); + value &= ~mask; + value |= set; + writel(value, reg); +} + +static inline void set32(void __iomem *reg, u32 set) +{ + mask32(reg, 0, set); +} + +static inline void clear32(void __iomem *reg, u32 clear) +{ + mask32(reg, clear, 0); +} + + +static int dptx_phy_set_active_lane_count(struct apple_dptx_phy *phy, u32 num_lanes) +{ + u32 l, ctrl; + + dev_dbg(phy->dev, "set_active_lane_count(%u)\n", num_lanes); + + if (num_lanes == 3 || num_lanes > DPTX_MAX_LANES) + return -1; + + ctrl = readl(phy->regs.dptx + 0x4000); + writel(ctrl, phy->regs.dptx + 0x4000); + + for (l = 0; l < num_lanes; l++) { + u64 offset = 0x5000 + 0x1000 * l; + readl(phy->regs.dptx + offset); + writel(0x100, phy->regs.dptx + offset); + } + for (; l < DPTX_MAX_LANES; l++) { + u64 offset = 0x5000 + 0x1000 * l; + readl(phy->regs.dptx + offset); + writel(0x300, phy->regs.dptx + offset); + } + for (l = 0; l < num_lanes; l++) { + u64 offset = 0x5000 + 0x1000 * l; + readl(phy->regs.dptx + offset); + writel(0x0, phy->regs.dptx + offset); + } + for (; l < DPTX_MAX_LANES; l++) { + u64 offset = 0x5000 + 0x1000 * l; + readl(phy->regs.dptx + offset); + writel(0x300, phy->regs.dptx + offset); + } + + if (num_lanes > 0) { + // clear32(phy->regs.dptx + 0x4000, 0x4000000); + ctrl = readl(phy->regs.dptx + 0x4000); + ctrl &= ~0x4000000; + writel(ctrl, phy->regs.dptx + 0x4000); + } + phy->active_lanes = num_lanes; + + return 0; +} + +static int dptx_phy_activate(struct apple_dptx_phy *phy, u32 dcp_index) +{ + u32 val_2014; + u32 val_4008; + u32 val_4408; + + dev_dbg(phy->dev, "activate(dcp:%u)\n", dcp_index); + + // MMIO: R.4 0x23c500010 (dptx-phy[1], offset 0x10) = 0x0 + // MMIO: W.4 0x23c500010 (dptx-phy[1], offset 0x10) = 0x0 + readl(phy->regs.core + 0x10); + writel(dcp_index, phy->regs.core + 0x10); + + // MMIO: R.4 0x23c500048 (dptx-phy[1], offset 0x48) = 0x444 + // MMIO: W.4 0x23c500048 (dptx-phy[1], offset 0x48) = 0x454 + set32(phy->regs.core + 0x48, 0x010); + // MMIO: R.4 0x23c500048 (dptx-phy[1], offset 0x48) = 0x454 + // MMIO: W.4 0x23c500048 (dptx-phy[1], offset 0x48) = 0x474 + set32(phy->regs.core + 0x48, 0x020); + // MMIO: R.4 0x23c500048 (dptx-phy[1], offset 0x48) = 0x474 + // MMIO: W.4 0x23c500048 (dptx-phy[1], offset 0x48) = 0x434 + clear32(phy->regs.core + 0x48, 0x040); + // MMIO: R.4 0x23c500048 (dptx-phy[1], offset 0x48) = 0x434 + // MMIO: W.4 0x23c500048 (dptx-phy[1], offset 0x48) = 0x534 + set32(phy->regs.core + 0x48, 0x100); + // MMIO: R.4 0x23c500048 (dptx-phy[1], offset 0x48) = 0x534 + // MMIO: W.4 0x23c500048 (dptx-phy[1], offset 0x48) = 0x734 + set32(phy->regs.core + 0x48, 0x200); + // MMIO: R.4 0x23c500048 (dptx-phy[1], offset 0x48) = 0x734 + // MMIO: W.4 0x23c500048 (dptx-phy[1], offset 0x48) = 0x334 + clear32(phy->regs.core + 0x48, 0x400); + // MMIO: R.4 0x23c500048 (dptx-phy[1], offset 0x48) = 0x334 + // MMIO: W.4 0x23c500048 (dptx-phy[1], offset 0x48) = 0x335 + set32(phy->regs.core + 0x48, 0x001); + // MMIO: R.4 0x23c500048 (dptx-phy[1], offset 0x48) = 0x335 + // MMIO: W.4 0x23c500048 (dptx-phy[1], offset 0x48) = 0x337 + set32(phy->regs.core + 0x48, 0x002); + // MMIO: R.4 0x23c500048 (dptx-phy[1], offset 0x48) = 0x337 + // MMIO: W.4 0x23c500048 (dptx-phy[1], offset 0x48) = 0x333 + clear32(phy->regs.core + 0x48, 0x004); + + // MMIO: R.4 0x23c542014 (dptx-phy[0], offset 0x2014) = 0x80a0c + val_2014 = readl(phy->regs.dptx + 0x2014); + // MMIO: W.4 0x23c542014 (dptx-phy[0], offset 0x2014) = 0x300a0c + writel((0x30 << 16) | (val_2014 & 0xffff), phy->regs.dptx + 0x2014); + + // MMIO: R.4 0x23c5420b8 (dptx-phy[0], offset 0x20b8) = 0x644800 + // MMIO: W.4 0x23c5420b8 (dptx-phy[0], offset 0x20b8) = 0x654800 + set32(phy->regs.dptx + 0x20b8, 0x010000); + + // MMIO: R.4 0x23c542220 (dptx-phy[0], offset 0x2220) = 0x11090a2 + // MMIO: W.4 0x23c542220 (dptx-phy[0], offset 0x2220) = 0x11090a0 + clear32(phy->regs.dptx + 0x2220, 0x0000002); + + // MMIO: R.4 0x23c54222c (dptx-phy[0], offset 0x222c) = 0x103003 + // MMIO: W.4 0x23c54222c (dptx-phy[0], offset 0x222c) = 0x103803 + set32(phy->regs.dptx + 0x222c, 0x000800); + // MMIO: R.4 0x23c54222c (dptx-phy[0], offset 0x222c) = 0x103803 + // MMIO: W.4 0x23c54222c (dptx-phy[0], offset 0x222c) = 0x103903 + set32(phy->regs.dptx + 0x222c, 0x000100); + + // MMIO: R.4 0x23c542230 (dptx-phy[0], offset 0x2230) = 0x2308804 + // MMIO: W.4 0x23c542230 (dptx-phy[0], offset 0x2230) = 0x2208804 + clear32(phy->regs.dptx + 0x2230, 0x0100000); + + // MMIO: R.4 0x23c542278 (dptx-phy[0], offset 0x2278) = 0x18300811 + // MMIO: W.4 0x23c542278 (dptx-phy[0], offset 0x2278) = 0x10300811 + clear32(phy->regs.dptx + 0x2278, 0x08000000); + + // MMIO: R.4 0x23c5422a4 (dptx-phy[0], offset 0x22a4) = 0x1044200 + // MMIO: W.4 0x23c5422a4 (dptx-phy[0], offset 0x22a4) = 0x1044201 + set32(phy->regs.dptx + 0x22a4, 0x0000001); + + // MMIO: R.4 0x23c544008 (dptx-phy[0], offset 0x4008) = 0x18030 + val_4008 = readl(phy->regs.dptx + 0x4008); + // MMIO: W.4 0x23c544008 (dptx-phy[0], offset 0x4008) = 0x30030 + writel((0x6 << 15) | (val_4008 & 0x7fff), phy->regs.dptx + 0x4008); + // MMIO: R.4 0x23c544008 (dptx-phy[0], offset 0x4008) = 0x30030 + // MMIO: W.4 0x23c544008 (dptx-phy[0], offset 0x4008) = 0x30010 + clear32(phy->regs.dptx + 0x4008, 0x00020); + + // MMIO: R.4 0x23c54420c (dptx-phy[0], offset 0x420c) = 0x88e3 + // MMIO: W.4 0x23c54420c (dptx-phy[0], offset 0x420c) = 0x88c3 + clear32(phy->regs.dptx + 0x420c, 0x0020); + + // MMIO: R.4 0x23c544600 (dptx-phy[0], offset 0x4600) = 0x0 + // MMIO: W.4 0x23c544600 (dptx-phy[0], offset 0x4600) = 0x8000000 + set32(phy->regs.dptx + 0x4600, 0x8000000); + + // MMIO: R.4 0x23c545040 (dptx-phy[0], offset 0x5040) = 0x21780 + // MMIO: W.4 0x23c545040 (dptx-phy[0], offset 0x5040) = 0x221780 + // MMIO: R.4 0x23c546040 (dptx-phy[0], offset 0x6040) = 0x21780 + // MMIO: W.4 0x23c546040 (dptx-phy[0], offset 0x6040) = 0x221780 + // MMIO: R.4 0x23c547040 (dptx-phy[0], offset 0x7040) = 0x21780 + // MMIO: W.4 0x23c547040 (dptx-phy[0], offset 0x7040) = 0x221780 + // MMIO: R.4 0x23c548040 (dptx-phy[0], offset 0x8040) = 0x21780 + // MMIO: W.4 0x23c548040 (dptx-phy[0], offset 0x8040) = 0x221780 + for (u32 loff = DPTX_LANE0_OFFSET; loff < DPTX_LANE_END; + loff += DPTX_LANE_STRIDE) + set32(phy->regs.dptx + loff + 0x40, 0x200000); + + // MMIO: R.4 0x23c545040 (dptx-phy[0], offset 0x5040) = 0x221780 + // MMIO: W.4 0x23c545040 (dptx-phy[0], offset 0x5040) = 0x2a1780 + // MMIO: R.4 0x23c546040 (dptx-phy[0], offset 0x6040) = 0x221780 + // MMIO: W.4 0x23c546040 (dptx-phy[0], offset 0x6040) = 0x2a1780 + // MMIO: R.4 0x23c547040 (dptx-phy[0], offset 0x7040) = 0x221780 + // MMIO: W.4 0x23c547040 (dptx-phy[0], offset 0x7040) = 0x2a1780 + // MMIO: R.4 0x23c548040 (dptx-phy[0], offset 0x8040) = 0x221780 + // MMIO: W.4 0x23c548040 (dptx-phy[0], offset 0x8040) = 0x2a1780 + for (u32 loff = DPTX_LANE0_OFFSET; loff < DPTX_LANE_END; + loff += DPTX_LANE_STRIDE) + set32(phy->regs.dptx + loff + 0x40, 0x080000); + + // MMIO: R.4 0x23c545244 (dptx-phy[0], offset 0x5244) = 0x18 + // MMIO: W.4 0x23c545244 (dptx-phy[0], offset 0x5244) = 0x8 + // MMIO: R.4 0x23c546244 (dptx-phy[0], offset 0x6244) = 0x18 + // MMIO: W.4 0x23c546244 (dptx-phy[0], offset 0x6244) = 0x8 + // MMIO: R.4 0x23c547244 (dptx-phy[0], offset 0x7244) = 0x18 + // MMIO: W.4 0x23c547244 (dptx-phy[0], offset 0x7244) = 0x8 + // MMIO: R.4 0x23c548244 (dptx-phy[0], offset 0x8244) = 0x18 + // MMIO: W.4 0x23c548244 (dptx-phy[0], offset 0x8244) = 0x8 + for (u32 loff = DPTX_LANE0_OFFSET; loff < DPTX_LANE_END; + loff += DPTX_LANE_STRIDE) + clear32(phy->regs.dptx + loff + 0x244, 0x10); + + // MMIO: R.4 0x23c542214 (dptx-phy[0], offset 0x2214) = 0x1e0 + // MMIO: W.4 0x23c542214 (dptx-phy[0], offset 0x2214) = 0x1e1 + set32(phy->regs.dptx + 0x2214, 0x001); + + // MMIO: R.4 0x23c542224 (dptx-phy[0], offset 0x2224) = 0x20086001 + // MMIO: W.4 0x23c542224 (dptx-phy[0], offset 0x2224) = 0x20086000 + clear32(phy->regs.dptx + 0x2224, 0x00000001); + + // MMIO: R.4 0x23c542200 (dptx-phy[0], offset 0x2200) = 0x2000 + // MMIO: W.4 0x23c542200 (dptx-phy[0], offset 0x2200) = 0x2002 + set32(phy->regs.dptx + 0x2200, 0x0002); + + // MMIO: R.4 0x23c541000 (dptx-phy[0], offset 0x1000) = 0xe0000003 + // MMIO: W.4 0x23c541000 (dptx-phy[0], offset 0x1000) = 0xe0000001 + clear32(phy->regs.dptx + 0x1000, 0x00000002); + + // MMIO: R.4 0x23c544004 (dptx-phy[0], offset 0x4004) = 0x41 + // MMIO: W.4 0x23c544004 (dptx-phy[0], offset 0x4004) = 0x49 + set32(phy->regs.dptx + 0x4004, 0x08); + + /* TODO: no idea what happens here, supposedly setting/clearing some bits */ + // MMIO: R.4 0x23c544404 (dptx-phy[0], offset 0x4404) = 0x555d444 + readl(phy->regs.dptx + 0x4404); + // MMIO: W.4 0x23c544404 (dptx-phy[0], offset 0x4404) = 0x555d444 + writel(0x555d444, phy->regs.dptx + 0x4404); + // MMIO: R.4 0x23c544404 (dptx-phy[0], offset 0x4404) = 0x555d444 + readl(phy->regs.dptx + 0x4404); + // MMIO: W.4 0x23c544404 (dptx-phy[0], offset 0x4404) = 0x555d444 + writel(0x555d444, phy->regs.dptx + 0x4404); + + dptx_phy_set_active_lane_count(phy, 0); + + // MMIO: R.4 0x23c544200 (dptx-phy[0], offset 0x4200) = 0x4002430 + // MMIO: W.4 0x23c544200 (dptx-phy[0], offset 0x4200) = 0x4002420 + clear32(phy->regs.dptx + 0x4200, 0x0000010); + + // MMIO: R.4 0x23c544600 (dptx-phy[0], offset 0x4600) = 0x8000000 + // MMIO: W.4 0x23c544600 (dptx-phy[0], offset 0x4600) = 0x8000000 + clear32(phy->regs.dptx + 0x4600, 0x0000001); + // MMIO: R.4 0x23c544600 (dptx-phy[0], offset 0x4600) = 0x8000000 + // MMIO: W.4 0x23c544600 (dptx-phy[0], offset 0x4600) = 0x8000001 + set32(phy->regs.dptx + 0x4600, 0x0000001); + // MMIO: R.4 0x23c544600 (dptx-phy[0], offset 0x4600) = 0x8000001 + // MMIO: W.4 0x23c544600 (dptx-phy[0], offset 0x4600) = 0x8000003 + set32(phy->regs.dptx + 0x4600, 0x0000002); + // MMIO: R.4 0x23c544600 (dptx-phy[0], offset 0x4600) = 0x8000043 + // MMIO: R.4 0x23c544600 (dptx-phy[0], offset 0x4600) = 0x8000043 + // MMIO: W.4 0x23c544600 (dptx-phy[0], offset 0x4600) = 0x8000041 + /* TODO: read first to check if the previous set(...,0x2) sticked? */ + readl(phy->regs.dptx + 0x4600); + clear32(phy->regs.dptx + 0x4600, 0x0000001); + + // MMIO: R.4 0x23c544408 (dptx-phy[0], offset 0x4408) = 0x482 + // MMIO: W.4 0x23c544408 (dptx-phy[0], offset 0x4408) = 0x482 + /* TODO: probably a set32 of an already set bit */ + val_4408 = readl(phy->regs.dptx + 0x4408); + if (val_4408 != 0x482 && val_4408 != 0x483) + dev_warn( + phy->dev, + "unexpected initial value at regs.dptx offset 0x4408: 0x%03x\n", + val_4408); + writel(val_4408, phy->regs.dptx + 0x4408); + // MMIO: R.4 0x23c544408 (dptx-phy[0], offset 0x4408) = 0x482 + // MMIO: W.4 0x23c544408 (dptx-phy[0], offset 0x4408) = 0x483 + set32(phy->regs.dptx + 0x4408, 0x001); + + return 0; +} + +static int dptx_phy_deactivate(struct apple_dptx_phy *phy) +{ + return 0; +} + +static int dptx_phy_set_link_rate(struct apple_dptx_phy *phy, u32 link_rate) +{ + u32 sts_1008, sts_1014, val_100c, val_20b0, val_20b4; + + dev_dbg(phy->dev, "set_link_rate(%u)\n", link_rate); + + // MMIO: R.4 0x23c544004 (dptx-phy[0], offset 0x4004) = 0x49 + // MMIO: W.4 0x23c544004 (dptx-phy[0], offset 0x4004) = 0x49 + set32(phy->regs.dptx + 0x4004, 0x08); + + // MMIO: R.4 0x23c544000 (dptx-phy[0], offset 0x4000) = 0x41021ac + // MMIO: W.4 0x23c544000 (dptx-phy[0], offset 0x4000) = 0x41021ac + clear32(phy->regs.dptx + 0x4000, 0x0000040); + + // MMIO: R.4 0x23c544004 (dptx-phy[0], offset 0x4004) = 0x49 + // MMIO: W.4 0x23c544004 (dptx-phy[0], offset 0x4004) = 0x41 + clear32(phy->regs.dptx + 0x4004, 0x08); + + // MMIO: R.4 0x23c544000 (dptx-phy[0], offset 0x4000) = 0x41021ac + // MMIO: W.4 0x23c544000 (dptx-phy[0], offset 0x4000) = 0x41021ac + clear32(phy->regs.dptx + 0x4000, 0x2000000); + // MMIO: R.4 0x23c544000 (dptx-phy[0], offset 0x4000) = 0x41021ac + // MMIO: W.4 0x23c544000 (dptx-phy[0], offset 0x4000) = 0x41021ac + set32(phy->regs.dptx + 0x4000, 0x1000000); + + // MMIO: R.4 0x23c542200 (dptx-phy[0], offset 0x2200) = 0x2002 + // MMIO: R.4 0x23c542200 (dptx-phy[0], offset 0x2200) = 0x2002 + // MMIO: W.4 0x23c542200 (dptx-phy[0], offset 0x2200) = 0x2000 + /* TODO: what is this read checking for? */ + readl(phy->regs.dptx + 0x2200); + clear32(phy->regs.dptx + 0x2200, 0x0002); + + // MMIO: R.4 0x23c54100c (dptx-phy[0], offset 0x100c) = 0xf000 + // MMIO: W.4 0x23c54100c (dptx-phy[0], offset 0x100c) = 0xf000 + // MMIO: R.4 0x23c54100c (dptx-phy[0], offset 0x100c) = 0xf000 + // MMIO: W.4 0x23c54100c (dptx-phy[0], offset 0x100c) = 0xf008 + /* TODO: what is the setting/clearing? */ + val_100c = readl(phy->regs.dptx + 0x100c); + writel(val_100c, phy->regs.dptx + 0x100c); + set32(phy->regs.dptx + 0x100c, 0x0008); + + // MMIO: R.4 0x23c541014 (dptx-phy[0], offset 0x1014) = 0x1 + sts_1014 = readl(phy->regs.dptx + 0x1014); + if (sts_1014 != 0x1) + dev_dbg(phy->dev, "unexpected?: dptx[0x1014]: %02x\n", sts_1014); + + // MMIO: R.4 0x23c54100c (dptx-phy[0], offset 0x100c) = 0xf008 + // MMIO: W.4 0x23c54100c (dptx-phy[0], offset 0x100c) = 0xf000 + clear32(phy->regs.dptx + 0x100c, 0x0008); + + // MMIO: R.4 0x23c541008 (dptx-phy[0], offset 0x1008) = 0x1 + sts_1008 = readl(phy->regs.dptx + 0x1008); + if (sts_1008 != 0x1) + dev_dbg(phy->dev, "unexpected?: dptx[0x1008]: %02x\n", sts_1008); + + // MMIO: R.4 0x23c542220 (dptx-phy[0], offset 0x2220) = 0x11090a0 + // MMIO: W.4 0x23c542220 (dptx-phy[0], offset 0x2220) = 0x1109020 + clear32(phy->regs.dptx + 0x2220, 0x0000080); + + // MMIO: R.4 0x23c5420b0 (dptx-phy[0], offset 0x20b0) = 0x1e0e01c2 + // MMIO: W.4 0x23c5420b0 (dptx-phy[0], offset 0x20b0) = 0x1e0e01c2 + val_20b0 = readl(phy->regs.dptx + 0x20b0); + /* TODO: what happens on dptx-phy */ + if (phy->hw.type == DPTX_PHY_T6020) + val_20b0 = (val_20b0 & ~0x3ff) | 0x2a3; + writel(val_20b0, phy->regs.dptx + 0x20b0); + + // MMIO: R.4 0x23c5420b4 (dptx-phy[0], offset 0x20b4) = 0x7fffffe + // MMIO: W.4 0x23c5420b4 (dptx-phy[0], offset 0x20b4) = 0x7fffffe + val_20b4 = readl(phy->regs.dptx + 0x20b4); + /* TODO: what happens on dptx-phy */ + if (phy->hw.type == DPTX_PHY_T6020) + val_20b4 = (val_20b4 | 0x4000000) & ~0x0008000; + writel(val_20b4, phy->regs.dptx + 0x20b4); + + // MMIO: R.4 0x23c5420b4 (dptx-phy[0], offset 0x20b4) = 0x7fffffe + // MMIO: W.4 0x23c5420b4 (dptx-phy[0], offset 0x20b4) = 0x7fffffe + val_20b4 = readl(phy->regs.dptx + 0x20b4); + /* TODO: what happens on dptx-phy */ + if (phy->hw.type == DPTX_PHY_T6020) + val_20b4 = (val_20b4 | 0x0000001) & ~0x0000004; + writel(val_20b4, phy->regs.dptx + 0x20b4); + + // MMIO: R.4 0x23c5420b8 (dptx-phy[0], offset 0x20b8) = 0x654800 + // MMIO: W.4 0x23c5420b8 (dptx-phy[0], offset 0x20b8) = 0x654800 + /* TODO: unclear */ + set32(phy->regs.dptx + 0x20b8, 0); + // MMIO: R.4 0x23c5420b8 (dptx-phy[0], offset 0x20b8) = 0x654800 + // MMIO: W.4 0x23c5420b8 (dptx-phy[0], offset 0x20b8) = 0x654800 + /* TODO: unclear */ + set32(phy->regs.dptx + 0x20b8, 0); + // MMIO: R.4 0x23c5420b8 (dptx-phy[0], offset 0x20b8) = 0x654800 + // MMIO: W.4 0x23c5420b8 (dptx-phy[0], offset 0x20b8) = 0x654800 + /* TODO: unclear */ + if (phy->hw.type == DPTX_PHY_T6020) + set32(phy->regs.dptx + 0x20b8, 0x010000); + else + set32(phy->regs.dptx + 0x20b8, 0); + // MMIO: R.4 0x23c5420b8 (dptx-phy[0], offset 0x20b8) = 0x654800 + // MMIO: W.4 0x23c5420b8 (dptx-phy[0], offset 0x20b8) = 0x454800 + clear32(phy->regs.dptx + 0x20b8, 0x200000); + + // MMIO: R.4 0x23c5420b8 (dptx-phy[0], offset 0x20b8) = 0x454800 + // MMIO: W.4 0x23c5420b8 (dptx-phy[0], offset 0x20b8) = 0x454800 + /* TODO: unclear */ + set32(phy->regs.dptx + 0x20b8, 0); + + // MMIO: R.4 0x23c5000a0 (dptx-phy[1], offset 0xa0) = 0x0 + // MMIO: W.4 0x23c5000a0 (dptx-phy[1], offset 0xa0) = 0x8 + // MMIO: R.4 0x23c5000a0 (dptx-phy[1], offset 0xa0) = 0x8 + // MMIO: W.4 0x23c5000a0 (dptx-phy[1], offset 0xa0) = 0xc + // MMIO: R.4 0x23c5000a0 (dptx-phy[1], offset 0xa0) = 0xc + // MMIO: W.4 0x23c5000a0 (dptx-phy[1], offset 0xa0) = 0x4000c + // MMIO: R.4 0x23c5000a0 (dptx-phy[1], offset 0xa0) = 0x4000c + // MMIO: W.4 0x23c5000a0 (dptx-phy[1], offset 0xa0) = 0xc + // MMIO: R.4 0x23c5000a0 (dptx-phy[1], offset 0xa0) = 0xc + // MMIO: W.4 0x23c5000a0 (dptx-phy[1], offset 0xa0) = 0x8000c + // MMIO: R.4 0x23c5000a0 (dptx-phy[1], offset 0xa0) = 0x8000c + // MMIO: W.4 0x23c5000a0 (dptx-phy[1], offset 0xa0) = 0xc + // MMIO: R.4 0x23c5000a0 (dptx-phy[1], offset 0xa0) = 0xc + // MMIO: W.4 0x23c5000a0 (dptx-phy[1], offset 0xa0) = 0x8 + // MMIO: R.4 0x23c5000a0 (dptx-phy[1], offset 0xa0) = 0x8 + // MMIO: W.4 0x23c5000a0 (dptx-phy[1], offset 0xa0) = 0x0 + set32(phy->regs.core + 0xa0, 0x8); + set32(phy->regs.core + 0xa0, 0x4); + set32(phy->regs.core + 0xa0, 0x40000); + clear32(phy->regs.core + 0xa0, 0x40000); + set32(phy->regs.core + 0xa0, 0x80000); + clear32(phy->regs.core + 0xa0, 0x80000); + clear32(phy->regs.core + 0xa0, 0x4); + clear32(phy->regs.core + 0xa0, 0x8); + + // MMIO: R.4 0x23c542000 (dptx-phy[0], offset 0x2000) = 0x2 + // MMIO: W.4 0x23c542000 (dptx-phy[0], offset 0x2000) = 0x2 + /* TODO: unclear */ + set32(phy->regs.dptx + 0x2000, 0x0); + + // MMIO: R.4 0x23c542018 (dptx-phy[0], offset 0x2018) = 0x0 + // MMIO: W.4 0x23c542018 (dptx-phy[0], offset 0x2018) = 0x0 + clear32(phy->regs.dptx + 0x2018, 0x0); + + // MMIO: R.4 0x23c54100c (dptx-phy[0], offset 0x100c) = 0xf000 + // MMIO: W.4 0x23c54100c (dptx-phy[0], offset 0x100c) = 0xf007 + set32(phy->regs.dptx + 0x100c, 0x0007); + // MMIO: R.4 0x23c54100c (dptx-phy[0], offset 0x100c) = 0xf007 + // MMIO: W.4 0x23c54100c (dptx-phy[0], offset 0x100c) = 0xf00f + set32(phy->regs.dptx + 0x100c, 0x0008); + + // MMIO: R.4 0x23c541014 (dptx-phy[0], offset 0x1014) = 0x38f + sts_1014 = readl(phy->regs.dptx + 0x1014); + if (sts_1014 != 0x38f) + dev_dbg(phy->dev, "unexpected?: dptx[0x1014]: %02x\n", sts_1014); + + // MMIO: R.4 0x23c54100c (dptx-phy[0], offset 0x100c) = 0xf00f + // MMIO: W.4 0x23c54100c (dptx-phy[0], offset 0x100c) = 0xf007 + clear32(phy->regs.dptx + 0x100c, 0x0008); + + // MMIO: R.4 0x23c541008 (dptx-phy[0], offset 0x1008) = 0x9 + sts_1008 = readl(phy->regs.dptx + 0x1008); + if (sts_1008 != 0x9) + dev_dbg(phy->dev, "unexpected?: dptx[0x1008]: %02x\n", sts_1008); + + // MMIO: R.4 0x23c542200 (dptx-phy[0], offset 0x2200) = 0x2000 + // MMIO: W.4 0x23c542200 (dptx-phy[0], offset 0x2200) = 0x2002 + set32(phy->regs.dptx + 0x2200, 0x0002); + + // MMIO: R.4 0x23c545010 (dptx-phy[0], offset 0x5010) = 0x18003000 + // MMIO: W.4 0x23c545010 (dptx-phy[0], offset 0x5010) = 0x18003000 + // MMIO: R.4 0x23c546010 (dptx-phy[0], offset 0x6010) = 0x18003000 + // MMIO: W.4 0x23c546010 (dptx-phy[0], offset 0x6010) = 0x18003000 + // MMIO: R.4 0x23c547010 (dptx-phy[0], offset 0x7010) = 0x18003000 + // MMIO: W.4 0x23c547010 (dptx-phy[0], offset 0x7010) = 0x18003000 + // MMIO: R.4 0x23c548010 (dptx-phy[0], offset 0x8010) = 0x18003000 + // MMIO: W.4 0x23c548010 (dptx-phy[0], offset 0x8010) = 0x18003000 + writel(0x18003000, phy->regs.dptx + 0x8010); + for (u32 loff = DPTX_LANE0_OFFSET; loff < DPTX_LANE_END; loff += DPTX_LANE_STRIDE) { + u32 val_l010 = readl(phy->regs.dptx + loff + 0x10); + writel(val_l010, phy->regs.dptx + loff + 0x10); + } + + // MMIO: R.4 0x23c544000 (dptx-phy[0], offset 0x4000) = 0x41021ac + // MMIO: W.4 0x23c544000 (dptx-phy[0], offset 0x4000) = 0x51021ac + set32(phy->regs.dptx + 0x4000, 0x1000000); + // MMIO: R.4 0x23c544000 (dptx-phy[0], offset 0x4000) = 0x51021ac + // MMIO: W.4 0x23c544000 (dptx-phy[0], offset 0x4000) = 0x71021ac + set32(phy->regs.dptx + 0x4000, 0x2000000); + + // MMIO: R.4 0x23c544004 (dptx-phy[0], offset 0x4004) = 0x41 + // MMIO: W.4 0x23c544004 (dptx-phy[0], offset 0x4004) = 0x49 + set32(phy->regs.dptx + 0x4004, 0x08); + + // MMIO: R.4 0x23c544000 (dptx-phy[0], offset 0x4000) = 0x71021ac + // MMIO: W.4 0x23c544000 (dptx-phy[0], offset 0x4000) = 0x71021ec + set32(phy->regs.dptx + 0x4000, 0x0000040); + + // MMIO: R.4 0x23c544004 (dptx-phy[0], offset 0x4004) = 0x49 + // MMIO: W.4 0x23c544004 (dptx-phy[0], offset 0x4004) = 0x48 + clear32(phy->regs.dptx + 0x4004, 0x01); + + return 0; +} + +static int dptx_phy_set_mode(struct phy *phy, enum phy_mode mode, int submode) +{ + struct apple_dptx_phy *dptx_phy = phy_get_drvdata(phy); + + switch (mode) { + case PHY_MODE_INVALID: + return dptx_phy_deactivate(dptx_phy); + case PHY_MODE_DP: + if (submode < 0 || submode > 5) + return -EINVAL; + return dptx_phy_activate(dptx_phy, submode); + default: + break; + } + + return -EINVAL; +} + +static int dptx_phy_validate(struct phy *phy, enum phy_mode mode, int submode, + union phy_configure_opts *opts_) +{ + struct phy_configure_opts_dp *opts = &opts_->dp; + + if (mode == PHY_MODE_INVALID) { + memset(opts, 0, sizeof(*opts)); + return 0; + } + + if (mode != PHY_MODE_DP) + return -EINVAL; + if (submode < 0 || submode > 5) + return -EINVAL; + + opts->lanes = 4; + opts->link_rate = 8100; + + for (int i = 0; i < 4; ++i) { + opts->voltage[i] = 3; + opts->pre[i] = 3; + } + + return 0; +} + +static int dptx_phy_configure(struct phy *phy, union phy_configure_opts *opts_) +{ + struct phy_configure_opts_dp *opts = &opts_->dp; + struct apple_dptx_phy *dptx_phy = phy_get_drvdata(phy); + enum dptx_phy_link_rate link_rate; + int ret = 0; + + if (opts->set_lanes) { + mutex_lock(&dptx_phy->lock); + ret = dptx_phy_set_active_lane_count(dptx_phy, opts->lanes); + mutex_unlock(&dptx_phy->lock); + } + + if (opts->set_rate) { + switch (opts->link_rate) { + case 1620: + link_rate = DPTX_PHY_LINK_RATE_RBR; + break; + case 2700: + link_rate = DPTX_PHY_LINK_RATE_HBR; + break; + case 5400: + link_rate = DPTX_PHY_LINK_RATE_HBR2; + break; + case 8100: + link_rate = DPTX_PHY_LINK_RATE_HBR3; + break; + case 0: + // TODO: disable! + return 0; + break; + default: + dev_err(dptx_phy->dev, "Unsupported link rate: %d\n", + opts->link_rate); + return -EINVAL; + } + + mutex_lock(&dptx_phy->lock); + ret = dptx_phy_set_link_rate(dptx_phy, link_rate); + mutex_unlock(&dptx_phy->lock); + } + + return ret; +} + +static const struct phy_ops apple_atc_dp_phy_ops = { + .owner = THIS_MODULE, + .configure = dptx_phy_configure, + .validate = dptx_phy_validate, + .set_mode = dptx_phy_set_mode, +}; + +static int dptx_phy_probe(struct platform_device *pdev) +{ + struct apple_dptx_phy *dptx_phy; + struct device *dev = &pdev->dev; + + dptx_phy = devm_kzalloc(dev, sizeof(*dptx_phy), GFP_KERNEL); + if (!dptx_phy) + return -ENOMEM; + + dptx_phy->dev = dev; + dptx_phy->hw = + *(struct apple_dptx_phy_hw *)of_device_get_match_data(dev); + platform_set_drvdata(pdev, dptx_phy); + + mutex_init(&dptx_phy->lock); + + dptx_phy->regs.core = + devm_platform_ioremap_resource_byname(pdev, "core"); + if (IS_ERR(dptx_phy->regs.core)) + return PTR_ERR(dptx_phy->regs.core); + dptx_phy->regs.dptx = + devm_platform_ioremap_resource_byname(pdev, "dptx"); + if (IS_ERR(dptx_phy->regs.dptx)) + return PTR_ERR(dptx_phy->regs.dptx); + + /* create phy */ + dptx_phy->phy_dp = + devm_phy_create(dptx_phy->dev, NULL, &apple_atc_dp_phy_ops); + if (IS_ERR(dptx_phy->phy_dp)) + return PTR_ERR(dptx_phy->phy_dp); + phy_set_drvdata(dptx_phy->phy_dp, dptx_phy); + + dptx_phy->phy_provider = + devm_of_phy_provider_register(dev, of_phy_simple_xlate); + if (IS_ERR(dptx_phy->phy_provider)) + return PTR_ERR(dptx_phy->phy_provider); + + return 0; +} + +static const struct apple_dptx_phy_hw apple_dptx_hw_t6020 = { + .type = DPTX_PHY_T6020, +}; + +static const struct apple_dptx_phy_hw apple_dptx_hw_t8112 = { + .type = DPTX_PHY_T8112, +}; + +static const struct of_device_id dptx_phy_match[] = { + { .compatible = "apple,t6020-dptx-phy", .data = &apple_dptx_hw_t6020 }, + { .compatible = "apple,t8112-dptx-phy", .data = &apple_dptx_hw_t8112 }, + {}, +}; +MODULE_DEVICE_TABLE(of, dptx_phy_match); + +static struct platform_driver dptx_phy_driver = { + .driver = { + .name = "phy-apple-dptx", + .of_match_table = dptx_phy_match, + }, + .probe = dptx_phy_probe, +}; + +module_platform_driver(dptx_phy_driver); + +MODULE_AUTHOR("Janne Grunau "); +MODULE_DESCRIPTION("Apple DP TX PHY driver"); + +MODULE_LICENSE("GPL"); diff --git a/drivers/phy/apple/dptx.h b/drivers/phy/apple/dptx.h new file mode 100644 index 00000000000000..2dd36d753eb357 --- /dev/null +++ b/drivers/phy/apple/dptx.h @@ -0,0 +1,18 @@ +// SPDX-License-Identifier: GPL-2.0 OR BSD-2-Clause +/* + * Apple DP TX PHY driver + * + * Copyright (C) The Asahi Linux Contributors + * Author: Janne Grunau + */ + +#ifndef PHY_APPLE_DPTX_H +#define PHY_APPLE_DPTX_H + +enum dptx_phy_link_rate { + DPTX_PHY_LINK_RATE_RBR, + DPTX_PHY_LINK_RATE_HBR, + DPTX_PHY_LINK_RATE_HBR2, + DPTX_PHY_LINK_RATE_HBR3, +}; +#endif /* PHY_APPLE_DPTX_H */ From ae59b602c2cc0caac2808fea7937dfb2f581020f Mon Sep 17 00:00:00 2001 From: Sven Peter Date: Sun, 14 Dec 2025 11:51:34 +0000 Subject: [PATCH 0369/3902] soc: apple: Add hardware tunable support Various hardware, like the Type-C PHY or the Thunderbolt/USB4 NHI, present on Apple SoCs need machine-specific tunables passed from our bootloader m1n1 to the device tree. Add generic helpers so that we don't have to duplicate this across multiple drivers. Reviewed-by: Alyssa Rosenzweig Reviewed-by: Neal Gompa Reviewed-by: Janne Grunau Signed-off-by: Sven Peter Bogus ordering in Kconfig/Makefile to avoid merge conflicts with RUST_APPLE_RTKIT and AOP/SEP from bits/190-rust and bits/250-aop. This is the only workable solution which avoids conflicts with bits/090-spi-hid as well. Signed-off-by: Janne Grunau --- drivers/soc/apple/Kconfig | 4 ++ drivers/soc/apple/Makefile | 3 ++ drivers/soc/apple/tunable.c | 80 +++++++++++++++++++++++++++++++ include/linux/soc/apple/tunable.h | 62 ++++++++++++++++++++++++ 4 files changed, 149 insertions(+) create mode 100644 drivers/soc/apple/tunable.c create mode 100644 include/linux/soc/apple/tunable.h diff --git a/drivers/soc/apple/Kconfig b/drivers/soc/apple/Kconfig index 2ea7a05cfb99f1..4d52ba8b6faaa4 100644 --- a/drivers/soc/apple/Kconfig +++ b/drivers/soc/apple/Kconfig @@ -24,6 +24,10 @@ config APPLE_PMGR_MISC controls for SoC devices. This driver manages miscellaneous power controls. +config APPLE_TUNABLE + tristate + depends on ARCH_APPLE || COMPILE_TEST + config APPLE_RTKIT tristate "Apple RTKit co-processor IPC protocol" depends on APPLE_MAILBOX diff --git a/drivers/soc/apple/Makefile b/drivers/soc/apple/Makefile index 40311a2ddaf2bf..5d3d5f1ab8a3a8 100644 --- a/drivers/soc/apple/Makefile +++ b/drivers/soc/apple/Makefile @@ -5,6 +5,9 @@ apple-mailbox-y = mailbox.o obj-$(CONFIG_APPLE_PMGR_MISC) += apple-pmgr-misc.o +obj-$(CONFIG_APPLE_TUNABLE) += apple-tunable.o +apple-tunable-y = tunable.o + obj-$(CONFIG_APPLE_RTKIT) += apple-rtkit.o apple-rtkit-y = rtkit.o rtkit-crashlog.o diff --git a/drivers/soc/apple/tunable.c b/drivers/soc/apple/tunable.c new file mode 100644 index 00000000000000..6593238391715b --- /dev/null +++ b/drivers/soc/apple/tunable.c @@ -0,0 +1,80 @@ +// SPDX-License-Identifier: GPL-2.0-only OR MIT +/* + * Apple Silicon hardware tunable support + * + * Each tunable is a list with each entry containing a offset into the MMIO + * region, a mask of bits to be cleared and a set of bits to be set. These + * tunables are passed along by the previous boot stages and vary from device + * to device such that they cannot be hardcoded in the individual drivers. + * + * Copyright (C) The Asahi Linux Contributors + */ + +#include +#include +#include +#include +#include + +struct apple_tunable *devm_apple_tunable_parse(struct device *dev, + struct device_node *np, + const char *name, + struct resource *res) +{ + struct apple_tunable *tunable; + struct property *prop; + const __be32 *p; + size_t sz; + int i; + + if (resource_size(res) < 4) + return ERR_PTR(-EINVAL); + + prop = of_find_property(np, name, NULL); + if (!prop) + return ERR_PTR(-ENOENT); + + if (prop->length % (3 * sizeof(u32))) + return ERR_PTR(-EINVAL); + sz = prop->length / (3 * sizeof(u32)); + + tunable = devm_kzalloc(dev, struct_size(tunable, values, sz), GFP_KERNEL); + if (!tunable) + return ERR_PTR(-ENOMEM); + tunable->sz = sz; + + for (i = 0, p = NULL; i < tunable->sz; ++i) { + p = of_prop_next_u32(prop, p, &tunable->values[i].offset); + p = of_prop_next_u32(prop, p, &tunable->values[i].mask); + p = of_prop_next_u32(prop, p, &tunable->values[i].value); + + /* Sanity checks to catch bugs in our bootloader */ + if (tunable->values[i].offset % 4) + return ERR_PTR(-EINVAL); + if (tunable->values[i].offset > (resource_size(res) - 4)) + return ERR_PTR(-EINVAL); + } + + return tunable; +} +EXPORT_SYMBOL(devm_apple_tunable_parse); + +void apple_tunable_apply(void __iomem *regs, struct apple_tunable *tunable) +{ + size_t i; + + for (i = 0; i < tunable->sz; ++i) { + u32 val, old_val; + + old_val = readl(regs + tunable->values[i].offset); + val = old_val & ~tunable->values[i].mask; + val |= tunable->values[i].value; + if (val != old_val) + writel(val, regs + tunable->values[i].offset); + } +} +EXPORT_SYMBOL(apple_tunable_apply); + +MODULE_LICENSE("Dual MIT/GPL"); +MODULE_AUTHOR("Sven Peter "); +MODULE_DESCRIPTION("Apple Silicon hardware tunable support"); diff --git a/include/linux/soc/apple/tunable.h b/include/linux/soc/apple/tunable.h new file mode 100644 index 00000000000000..531ca814cd0235 --- /dev/null +++ b/include/linux/soc/apple/tunable.h @@ -0,0 +1,62 @@ +/* SPDX-License-Identifier: GPL-2.0-only OR MIT */ +/* + * Apple Silicon hardware tunable support + * + * Each tunable is a list with each entry containing a offset into the MMIO + * region, a mask of bits to be cleared and a set of bits to be set. These + * tunables are passed along by the previous boot stages and vary from device + * to device such that they cannot be hardcoded in the individual drivers. + * + * Copyright (C) The Asahi Linux Contributors + */ + +#ifndef _LINUX_SOC_APPLE_TUNABLE_H_ +#define _LINUX_SOC_APPLE_TUNABLE_H_ + +#include +#include + +/** + * Struct to store an Apple Silicon hardware tunable. + * + * Each tunable is a list with each entry containing a offset into the MMIO + * region, a mask of bits to be cleared and a set of bits to be set. These + * tunables are passed along by the previous boot stages and vary from device + * to device such that they cannot be hardcoded in the individual drivers. + * + * @param sz Number of [offset, mask, value] tuples stored in values. + * @param values [offset, mask, value] array. + */ +struct apple_tunable { + size_t sz; + struct { + u32 offset; + u32 mask; + u32 value; + } values[] __counted_by(sz); +}; + +/** + * Parse an array of hardware tunables from the device tree. + * + * @dev: Device node used for devm_kzalloc internally. + * @np: Device node which contains the tunable array. + * @name: Name of the device tree property which contains the tunables. + * @res: Resource to which the tunables will be applied, used for bound checking + * + * @return: devres allocated struct on success or PTR_ERR on failure. + */ +struct apple_tunable *devm_apple_tunable_parse(struct device *dev, + struct device_node *np, + const char *name, + struct resource *res); + +/** + * Apply a previously loaded hardware tunable. + * + * @param regs: MMIO to which the tunable will be applied. + * @param tunable: Pointer to the tunable. + */ +void apple_tunable_apply(void __iomem *regs, struct apple_tunable *tunable); + +#endif From cf036137162c1f227e8e488d3a60b99894853593 Mon Sep 17 00:00:00 2001 From: Konstantin Andreev Date: Mon, 16 Jun 2025 04:07:29 +0300 Subject: [PATCH 0370/3902] smack: fix bug: SMACK64TRANSMUTE set on non-directory [ Upstream commit 195da3ff244deff119c3f5244b464b2236ea1725 ] When a new file system object is created and the conditions for label transmutation are met, the SMACK64TRANSMUTE extended attribute is set on the object regardless of its type: file, pipe, socket, symlink, or directory. However, SMACK64TRANSMUTE may only be set on directories. This bug is a combined effect of the commits [1] and [2] which both transfer functionality from smack_d_instantiate() to smack_inode_init_security(), but only in part. Commit [1] set blank SMACK64TRANSMUTE on improper object types. Commit [2] set "TRUE" SMACK64TRANSMUTE on improper object types. [1] 2023-06-10, Fixes: baed456a6a2f ("smack: Set the SMACK64TRANSMUTE xattr in smack_inode_init_security()") Link: https://lore.kernel.org/linux-security-module/20230610075738.3273764-3-roberto.sassu@huaweicloud.com/ [2] 2023-11-16, Fixes: e63d86b8b764 ("smack: Initialize the in-memory inode in smack_inode_init_security()") Link: https://lore.kernel.org/linux-security-module/20231116090125.187209-5-roberto.sassu@huaweicloud.com/ Signed-off-by: Konstantin Andreev Signed-off-by: Casey Schaufler Signed-off-by: Sasha Levin --- security/smack/smack_lsm.c | 26 ++++++++++++++------------ 1 file changed, 14 insertions(+), 12 deletions(-) diff --git a/security/smack/smack_lsm.c b/security/smack/smack_lsm.c index af986587841d8c..39a4caeb6bc8ed 100644 --- a/security/smack/smack_lsm.c +++ b/security/smack/smack_lsm.c @@ -1015,18 +1015,20 @@ static int smack_inode_init_security(struct inode *inode, struct inode *dir, if (tsp->smk_task != tsp->smk_transmuted) isp = issp->smk_inode = dsp; - issp->smk_flags |= SMK_INODE_TRANSMUTE; - xattr_transmute = lsm_get_xattr_slot(xattrs, - xattr_count); - if (xattr_transmute) { - xattr_transmute->value = kmemdup(TRANS_TRUE, - TRANS_TRUE_SIZE, - GFP_NOFS); - if (!xattr_transmute->value) - return -ENOMEM; - - xattr_transmute->value_len = TRANS_TRUE_SIZE; - xattr_transmute->name = XATTR_SMACK_TRANSMUTE; + if (S_ISDIR(inode->i_mode)) { + issp->smk_flags |= SMK_INODE_TRANSMUTE; + xattr_transmute = lsm_get_xattr_slot(xattrs, + xattr_count); + if (xattr_transmute) { + xattr_transmute->value = kmemdup(TRANS_TRUE, + TRANS_TRUE_SIZE, + GFP_NOFS); + if (!xattr_transmute->value) + return -ENOMEM; + + xattr_transmute->value_len = TRANS_TRUE_SIZE; + xattr_transmute->name = XATTR_SMACK_TRANSMUTE; + } } } From 06025d5e707f760992946f17af403831d5a6347c Mon Sep 17 00:00:00 2001 From: Konstantin Andreev Date: Mon, 16 Jun 2025 04:07:28 +0300 Subject: [PATCH 0371/3902] smack: deduplicate "does access rule request transmutation" [ Upstream commit 635a01da8385fc00a144ec24684100bd1aa9db11 ] Signed-off-by: Konstantin Andreev Signed-off-by: Casey Schaufler Stable-dep-of: 78fc6a94be25 ("smack: fix bug: invalid label of unix socket file") Signed-off-by: Sasha Levin --- security/smack/smack_lsm.c | 57 +++++++++++++++++++++----------------- 1 file changed, 32 insertions(+), 25 deletions(-) diff --git a/security/smack/smack_lsm.c b/security/smack/smack_lsm.c index 39a4caeb6bc8ed..a005f5afb8e0d5 100644 --- a/security/smack/smack_lsm.c +++ b/security/smack/smack_lsm.c @@ -962,6 +962,24 @@ static int smack_inode_alloc_security(struct inode *inode) return 0; } +/** + * smk_rule_transmutes - does access rule for (subject,object) contain 't'? + * @subject: a pointer to the subject's Smack label entry + * @object: a pointer to the object's Smack label entry + */ +static bool +smk_rule_transmutes(struct smack_known *subject, + const struct smack_known *object) +{ + int may; + + rcu_read_lock(); + may = smk_access_entry(subject->smk_known, object->smk_known, + &subject->smk_rules); + rcu_read_unlock(); + return (may > 0) && (may & MAY_TRANSMUTE); +} + /** * smack_inode_init_security - copy out the smack from an inode * @inode: the newly created inode @@ -977,23 +995,19 @@ static int smack_inode_init_security(struct inode *inode, struct inode *dir, struct xattr *xattrs, int *xattr_count) { struct task_smack *tsp = smack_cred(current_cred()); - struct inode_smack *issp = smack_inode(inode); - struct smack_known *skp = smk_of_task(tsp); - struct smack_known *isp = smk_of_inode(inode); + struct inode_smack * const issp = smack_inode(inode); struct smack_known *dsp = smk_of_inode(dir); struct xattr *xattr = lsm_get_xattr_slot(xattrs, xattr_count); - int may; + bool trans_cred; + bool trans_rule; /* * If equal, transmuting already occurred in * smack_dentry_create_files_as(). No need to check again. */ - if (tsp->smk_task != tsp->smk_transmuted) { - rcu_read_lock(); - may = smk_access_entry(skp->smk_known, dsp->smk_known, - &skp->smk_rules); - rcu_read_unlock(); - } + trans_cred = (tsp->smk_task == tsp->smk_transmuted); + if (!trans_cred) + trans_rule = smk_rule_transmutes(smk_of_task(tsp), dsp); /* * In addition to having smk_task equal to smk_transmuted, @@ -1001,9 +1015,7 @@ static int smack_inode_init_security(struct inode *inode, struct inode *dir, * requests transmutation then by all means transmute. * Mark the inode as changed. */ - if ((tsp->smk_task == tsp->smk_transmuted) || - (may > 0 && ((may & MAY_TRANSMUTE) != 0) && - smk_inode_transmutable(dir))) { + if (trans_cred || (trans_rule && smk_inode_transmutable(dir))) { struct xattr *xattr_transmute; /* @@ -1012,8 +1024,8 @@ static int smack_inode_init_security(struct inode *inode, struct inode *dir, * inode label was already set correctly in * smack_inode_alloc_security(). */ - if (tsp->smk_task != tsp->smk_transmuted) - isp = issp->smk_inode = dsp; + if (!trans_cred) + issp->smk_inode = dsp; if (S_ISDIR(inode->i_mode)) { issp->smk_flags |= SMK_INODE_TRANSMUTE; @@ -1035,11 +1047,13 @@ static int smack_inode_init_security(struct inode *inode, struct inode *dir, issp->smk_flags |= SMK_INODE_INSTANT; if (xattr) { - xattr->value = kstrdup(isp->smk_known, GFP_NOFS); + const char *inode_label = issp->smk_inode->smk_known; + + xattr->value = kstrdup(inode_label, GFP_NOFS); if (!xattr->value) return -ENOMEM; - xattr->value_len = strlen(isp->smk_known); + xattr->value_len = strlen(inode_label); xattr->name = XATTR_SMACK_SUFFIX; } @@ -4917,7 +4931,6 @@ static int smack_dentry_create_files_as(struct dentry *dentry, int mode, struct task_smack *otsp = smack_cred(old); struct task_smack *ntsp = smack_cred(new); struct inode_smack *isp; - int may; /* * Use the process credential unless all of @@ -4931,18 +4944,12 @@ static int smack_dentry_create_files_as(struct dentry *dentry, int mode, isp = smack_inode(d_inode(dentry->d_parent)); if (isp->smk_flags & SMK_INODE_TRANSMUTE) { - rcu_read_lock(); - may = smk_access_entry(otsp->smk_task->smk_known, - isp->smk_inode->smk_known, - &otsp->smk_task->smk_rules); - rcu_read_unlock(); - /* * If the directory is transmuting and the rule * providing access is transmuting use the containing * directory label instead of the process label. */ - if (may > 0 && (may & MAY_TRANSMUTE)) { + if (smk_rule_transmutes(otsp->smk_task, isp->smk_inode)) { ntsp->smk_task = isp->smk_inode; ntsp->smk_transmuted = ntsp->smk_task; } From 3c4f1ca5bd912d08d33415bf537f99adeb1a9708 Mon Sep 17 00:00:00 2001 From: Konstantin Andreev Date: Mon, 16 Jun 2025 04:07:30 +0300 Subject: [PATCH 0372/3902] smack: deduplicate xattr setting in smack_inode_init_security() [ Upstream commit 8e5d9f916a9678e2dcbed2289b87efd453e4e052 ] Signed-off-by: Konstantin Andreev Signed-off-by: Casey Schaufler Stable-dep-of: 78fc6a94be25 ("smack: fix bug: invalid label of unix socket file") Signed-off-by: Sasha Levin --- security/smack/smack_lsm.c | 56 ++++++++++++++++++++------------------ 1 file changed, 29 insertions(+), 27 deletions(-) diff --git a/security/smack/smack_lsm.c b/security/smack/smack_lsm.c index a005f5afb8e0d5..9eefb5cfccba5e 100644 --- a/security/smack/smack_lsm.c +++ b/security/smack/smack_lsm.c @@ -980,6 +980,24 @@ smk_rule_transmutes(struct smack_known *subject, return (may > 0) && (may & MAY_TRANSMUTE); } +static int +xattr_dupval(struct xattr *xattrs, int *xattr_count, + const char *name, const void *value, unsigned int vallen) +{ + struct xattr * const xattr = lsm_get_xattr_slot(xattrs, xattr_count); + + if (!xattr) + return 0; + + xattr->value = kmemdup(value, vallen, GFP_NOFS); + if (!xattr->value) + return -ENOMEM; + + xattr->value_len = vallen; + xattr->name = name; + return 0; +} + /** * smack_inode_init_security - copy out the smack from an inode * @inode: the newly created inode @@ -997,7 +1015,6 @@ static int smack_inode_init_security(struct inode *inode, struct inode *dir, struct task_smack *tsp = smack_cred(current_cred()); struct inode_smack * const issp = smack_inode(inode); struct smack_known *dsp = smk_of_inode(dir); - struct xattr *xattr = lsm_get_xattr_slot(xattrs, xattr_count); bool trans_cred; bool trans_rule; @@ -1016,8 +1033,6 @@ static int smack_inode_init_security(struct inode *inode, struct inode *dir, * Mark the inode as changed. */ if (trans_cred || (trans_rule && smk_inode_transmutable(dir))) { - struct xattr *xattr_transmute; - /* * The caller of smack_dentry_create_files_as() * should have overridden the current cred, so the @@ -1029,35 +1044,22 @@ static int smack_inode_init_security(struct inode *inode, struct inode *dir, if (S_ISDIR(inode->i_mode)) { issp->smk_flags |= SMK_INODE_TRANSMUTE; - xattr_transmute = lsm_get_xattr_slot(xattrs, - xattr_count); - if (xattr_transmute) { - xattr_transmute->value = kmemdup(TRANS_TRUE, - TRANS_TRUE_SIZE, - GFP_NOFS); - if (!xattr_transmute->value) - return -ENOMEM; - - xattr_transmute->value_len = TRANS_TRUE_SIZE; - xattr_transmute->name = XATTR_SMACK_TRANSMUTE; - } + + if (xattr_dupval(xattrs, xattr_count, + XATTR_SMACK_TRANSMUTE, + TRANS_TRUE, + TRANS_TRUE_SIZE + )) + return -ENOMEM; } } issp->smk_flags |= SMK_INODE_INSTANT; - if (xattr) { - const char *inode_label = issp->smk_inode->smk_known; - - xattr->value = kstrdup(inode_label, GFP_NOFS); - if (!xattr->value) - return -ENOMEM; - - xattr->value_len = strlen(inode_label); - xattr->name = XATTR_SMACK_SUFFIX; - } - - return 0; + return xattr_dupval(xattrs, xattr_count, + XATTR_SMACK_SUFFIX, + issp->smk_inode->smk_known, + strlen(issp->smk_inode->smk_known)); } /** From f1dbb370eca563582f90744618e6eaa95335fde6 Mon Sep 17 00:00:00 2001 From: Konstantin Andreev Date: Mon, 16 Jun 2025 04:07:31 +0300 Subject: [PATCH 0373/3902] smack: always "instantiate" inode in smack_inode_init_security() [ Upstream commit 69204f6cdb90f56b7ca27966d1080841108fc5de ] If memory allocation for the SMACK64TRANSMUTE xattr value fails in smack_inode_init_security(), the SMK_INODE_INSTANT flag is not set in (struct inode_smack *issp)->smk_flags, leaving the inode as not "instantiated". It does not matter if fs frees the inode after failed smack_inode_init_security() call, but there is no guarantee for this. To be safe, mark the inode as "instantiated", even if allocation of xattr values fails. Signed-off-by: Konstantin Andreev Signed-off-by: Casey Schaufler Stable-dep-of: 78fc6a94be25 ("smack: fix bug: invalid label of unix socket file") Signed-off-by: Sasha Levin --- security/smack/smack_lsm.c | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/security/smack/smack_lsm.c b/security/smack/smack_lsm.c index 9eefb5cfccba5e..db2a9aa9f1a055 100644 --- a/security/smack/smack_lsm.c +++ b/security/smack/smack_lsm.c @@ -1015,6 +1015,8 @@ static int smack_inode_init_security(struct inode *inode, struct inode *dir, struct task_smack *tsp = smack_cred(current_cred()); struct inode_smack * const issp = smack_inode(inode); struct smack_known *dsp = smk_of_inode(dir); + int rc = 0; + int transflag = 0; bool trans_cred; bool trans_rule; @@ -1043,18 +1045,20 @@ static int smack_inode_init_security(struct inode *inode, struct inode *dir, issp->smk_inode = dsp; if (S_ISDIR(inode->i_mode)) { - issp->smk_flags |= SMK_INODE_TRANSMUTE; + transflag = SMK_INODE_TRANSMUTE; if (xattr_dupval(xattrs, xattr_count, XATTR_SMACK_TRANSMUTE, TRANS_TRUE, TRANS_TRUE_SIZE )) - return -ENOMEM; + rc = -ENOMEM; } } - issp->smk_flags |= SMK_INODE_INSTANT; + issp->smk_flags |= (SMK_INODE_INSTANT | transflag); + if (rc) + return rc; return xattr_dupval(xattrs, xattr_count, XATTR_SMACK_SUFFIX, From 70c5be42f691fb6b46084bdf42e191cab0e9dde7 Mon Sep 17 00:00:00 2001 From: Konstantin Andreev Date: Mon, 16 Jun 2025 04:07:32 +0300 Subject: [PATCH 0374/3902] smack: fix bug: invalid label of unix socket file [ Upstream commit 78fc6a94be252b27bb73e4926eed70b5e302a8e0 ] According to [1], the label of a UNIX domain socket (UDS) file (i.e., the filesystem object representing the socket) is not supposed to participate in Smack security. To achieve this, [1] labels UDS files with "*" in smack_d_instantiate(). Before [2], smack_d_instantiate() was responsible for initializing Smack security for all inodes, except ones under /proc [2] imposed the sole responsibility for initializing inode security for newly created filesystem objects on smack_inode_init_security(). However, smack_inode_init_security() lacks some logic present in smack_d_instantiate(). In particular, it does not label UDS files with "*". This patch adds the missing labeling of UDS files with "*" to smack_inode_init_security(). Labeling UDS files with "*" in smack_d_instantiate() still works for stale UDS files that already exist on disk. Stale UDS files are useless, but I keep labeling them for consistency and maybe to make easier for user to delete them. Compared to [1], this version introduces the following improvements: * UDS file label is held inside inode only and not saved to xattrs. * relabeling UDS files (setxattr, removexattr, etc.) is blocked. [1] 2010-11-24 Casey Schaufler commit b4e0d5f0791b ("Smack: UDS revision") [2] 2023-11-16 roberto.sassu Fixes: e63d86b8b764 ("smack: Initialize the in-memory inode in smack_inode_init_security()") Link: https://lore.kernel.org/linux-security-module/20231116090125.187209-5-roberto.sassu@huaweicloud.com/ Signed-off-by: Konstantin Andreev Signed-off-by: Casey Schaufler Signed-off-by: Sasha Levin --- Documentation/admin-guide/LSM/Smack.rst | 5 +++ security/smack/smack_lsm.c | 58 +++++++++++++++++++------ 2 files changed, 49 insertions(+), 14 deletions(-) diff --git a/Documentation/admin-guide/LSM/Smack.rst b/Documentation/admin-guide/LSM/Smack.rst index 6d44f4fdbf59ff..1b554b5bf98e6a 100644 --- a/Documentation/admin-guide/LSM/Smack.rst +++ b/Documentation/admin-guide/LSM/Smack.rst @@ -696,6 +696,11 @@ sockets. A privileged program may set this to match the label of another task with which it hopes to communicate. +UNIX domain socket (UDS) with a BSD address functions both as a file in a +filesystem and as a socket. As a file, it carries the SMACK64 attribute. This +attribute is not involved in Smack security enforcement and is immutably +assigned the label "*". + Smack Netlabel Exceptions ~~~~~~~~~~~~~~~~~~~~~~~~~ diff --git a/security/smack/smack_lsm.c b/security/smack/smack_lsm.c index db2a9aa9f1a055..d16453801a79e0 100644 --- a/security/smack/smack_lsm.c +++ b/security/smack/smack_lsm.c @@ -1020,6 +1020,16 @@ static int smack_inode_init_security(struct inode *inode, struct inode *dir, bool trans_cred; bool trans_rule; + /* + * UNIX domain sockets use lower level socket data. Let + * UDS inode have fixed * label to keep smack_inode_permission() calm + * when called from unix_find_bsd() + */ + if (S_ISSOCK(inode->i_mode)) { + /* forced label, no need to save to xattrs */ + issp->smk_inode = &smack_known_star; + goto instant_inode; + } /* * If equal, transmuting already occurred in * smack_dentry_create_files_as(). No need to check again. @@ -1056,14 +1066,16 @@ static int smack_inode_init_security(struct inode *inode, struct inode *dir, } } - issp->smk_flags |= (SMK_INODE_INSTANT | transflag); - if (rc) - return rc; - - return xattr_dupval(xattrs, xattr_count, + if (rc == 0) + if (xattr_dupval(xattrs, xattr_count, XATTR_SMACK_SUFFIX, issp->smk_inode->smk_known, - strlen(issp->smk_inode->smk_known)); + strlen(issp->smk_inode->smk_known) + )) + rc = -ENOMEM; +instant_inode: + issp->smk_flags |= (SMK_INODE_INSTANT | transflag); + return rc; } /** @@ -1337,13 +1349,23 @@ static int smack_inode_setxattr(struct mnt_idmap *idmap, int check_import = 0; int check_star = 0; int rc = 0; + umode_t const i_mode = d_backing_inode(dentry)->i_mode; /* * Check label validity here so import won't fail in post_setxattr */ - if (strcmp(name, XATTR_NAME_SMACK) == 0 || - strcmp(name, XATTR_NAME_SMACKIPIN) == 0 || - strcmp(name, XATTR_NAME_SMACKIPOUT) == 0) { + if (strcmp(name, XATTR_NAME_SMACK) == 0) { + /* + * UDS inode has fixed label + */ + if (S_ISSOCK(i_mode)) { + rc = -EINVAL; + } else { + check_priv = 1; + check_import = 1; + } + } else if (strcmp(name, XATTR_NAME_SMACKIPIN) == 0 || + strcmp(name, XATTR_NAME_SMACKIPOUT) == 0) { check_priv = 1; check_import = 1; } else if (strcmp(name, XATTR_NAME_SMACKEXEC) == 0 || @@ -1353,7 +1375,7 @@ static int smack_inode_setxattr(struct mnt_idmap *idmap, check_star = 1; } else if (strcmp(name, XATTR_NAME_SMACKTRANSMUTE) == 0) { check_priv = 1; - if (!S_ISDIR(d_backing_inode(dentry)->i_mode) || + if (!S_ISDIR(i_mode) || size != TRANS_TRUE_SIZE || strncmp(value, TRANS_TRUE, TRANS_TRUE_SIZE) != 0) rc = -EINVAL; @@ -1484,12 +1506,15 @@ static int smack_inode_removexattr(struct mnt_idmap *idmap, * Don't do anything special for these. * XATTR_NAME_SMACKIPIN * XATTR_NAME_SMACKIPOUT + * XATTR_NAME_SMACK if S_ISSOCK (UDS inode has fixed label) */ if (strcmp(name, XATTR_NAME_SMACK) == 0) { - struct super_block *sbp = dentry->d_sb; - struct superblock_smack *sbsp = smack_superblock(sbp); + if (!S_ISSOCK(d_backing_inode(dentry)->i_mode)) { + struct super_block *sbp = dentry->d_sb; + struct superblock_smack *sbsp = smack_superblock(sbp); - isp->smk_inode = sbsp->smk_default; + isp->smk_inode = sbsp->smk_default; + } } else if (strcmp(name, XATTR_NAME_SMACKEXEC) == 0) isp->smk_task = NULL; else if (strcmp(name, XATTR_NAME_SMACKMMAP) == 0) @@ -3607,7 +3632,7 @@ static void smack_d_instantiate(struct dentry *opt_dentry, struct inode *inode) */ /* - * UNIX domain sockets use lower level socket data. + * UDS inode has fixed label (*) */ if (S_ISSOCK(inode->i_mode)) { final = &smack_known_star; @@ -4872,6 +4897,11 @@ static int smack_secctx_to_secid(const char *secdata, u32 seclen, u32 *secid) static int smack_inode_notifysecctx(struct inode *inode, void *ctx, u32 ctxlen) { + /* + * UDS inode has fixed label. Ignore nfs label. + */ + if (S_ISSOCK(inode->i_mode)) + return 0; return smack_inode_setsecurity(inode, XATTR_SMACK_SUFFIX, ctx, ctxlen, 0); } From 60e8d49989410a7ade60f5dadfcd979c117d05c0 Mon Sep 17 00:00:00 2001 From: Konstantin Andreev Date: Tue, 17 Jun 2025 00:32:16 +0300 Subject: [PATCH 0375/3902] smack: fix bug: unprivileged task can create labels [ Upstream commit c147e13ea7fe9f118f8c9ba5e96cbd644b00d6b3 ] If an unprivileged task is allowed to relabel itself (/smack/relabel-self is not empty), it can freely create new labels by writing their names into own /proc/PID/attr/smack/current This occurs because do_setattr() imports the provided label in advance, before checking "relabel-self" list. This change ensures that the "relabel-self" list is checked before importing the label. Fixes: 38416e53936e ("Smack: limited capability for changing process label") Signed-off-by: Konstantin Andreev Signed-off-by: Casey Schaufler Signed-off-by: Sasha Levin --- security/smack/smack_lsm.c | 41 +++++++++++++++++++++++++------------- 1 file changed, 27 insertions(+), 14 deletions(-) diff --git a/security/smack/smack_lsm.c b/security/smack/smack_lsm.c index d16453801a79e0..07dbc4cd303a73 100644 --- a/security/smack/smack_lsm.c +++ b/security/smack/smack_lsm.c @@ -3778,8 +3778,8 @@ static int do_setattr(u64 attr, void *value, size_t size) struct task_smack *tsp = smack_cred(current_cred()); struct cred *new; struct smack_known *skp; - struct smack_known_list_elem *sklep; - int rc; + char *labelstr; + int rc = 0; if (!smack_privileged(CAP_MAC_ADMIN) && list_empty(&tsp->smk_relabel)) return -EPERM; @@ -3790,28 +3790,41 @@ static int do_setattr(u64 attr, void *value, size_t size) if (attr != LSM_ATTR_CURRENT) return -EOPNOTSUPP; - skp = smk_import_entry(value, size); - if (IS_ERR(skp)) - return PTR_ERR(skp); + labelstr = smk_parse_smack(value, size); + if (IS_ERR(labelstr)) + return PTR_ERR(labelstr); /* * No process is ever allowed the web ("@") label * and the star ("*") label. */ - if (skp == &smack_known_web || skp == &smack_known_star) - return -EINVAL; + if (labelstr[1] == '\0' /* '@', '*' */) { + const char c = labelstr[0]; + + if (c == *smack_known_web.smk_known || + c == *smack_known_star.smk_known) { + rc = -EPERM; + goto free_labelstr; + } + } if (!smack_privileged(CAP_MAC_ADMIN)) { - rc = -EPERM; + const struct smack_known_list_elem *sklep; list_for_each_entry(sklep, &tsp->smk_relabel, list) - if (sklep->smk_label == skp) { - rc = 0; - break; - } - if (rc) - return rc; + if (strcmp(sklep->smk_label->smk_known, labelstr) == 0) + goto free_labelstr; + rc = -EPERM; } +free_labelstr: + kfree(labelstr); + if (rc) + return -EPERM; + + skp = smk_import_entry(value, size); + if (IS_ERR(skp)) + return PTR_ERR(skp); + new = prepare_creds(); if (new == NULL) return -ENOMEM; From cf008f5bd3d5db210d1edd244e7cd1d879cb1a5e Mon Sep 17 00:00:00 2001 From: Konstantin Andreev Date: Tue, 17 Jun 2025 00:32:17 +0300 Subject: [PATCH 0376/3902] smack: fix bug: setting task label silently ignores input garbage [ Upstream commit 674e2b24791cbe8fd5dc8a0aed4cb4404fcd2028 ] This command: # echo foo/bar >/proc/$$/attr/smack/current gives the task a label 'foo' w/o indication that label does not match input. Setting the label with lsm_set_self_attr() syscall behaves identically. This occures because: 1) smk_parse_smack() is used to convert input to a label 2) smk_parse_smack() takes only that part from the beginning of the input that looks like a label. 3) `/' is prohibited in labels, so only "foo" is taken. (2) is by design, because smk_parse_smack() is used for parsing strings which are more than just a label. Silent failure is not a good thing, and there are two indicators that this was not done intentionally: (size >= SMK_LONGLABEL) ~> invalid clause at the beginning of the do_setattr() and the "Returns the length of the smack label" claim in the do_setattr() description. So I fixed this by adding one tiny check: the taken label length == input length. Since input length is now strictly controlled, I changed the two ways of setting label smack_setselfattr(): lsm_set_self_attr() syscall smack_setprocattr(): > /proc/.../current to accommodate the divergence in what they understand by "input length": smack_setselfattr counts mandatory \0 into input length, smack_setprocattr does not. smack_setprocattr allows various trailers after label Related changes: * fixed description for smk_parse_smack * allow unprivileged tasks validate label syntax. * extract smk_parse_label_len() from smk_parse_smack() so parsing may be done w/o string allocation. * extract smk_import_valid_label() from smk_import_entry() to avoid repeated parsing. * smk_parse_smack(): scan null-terminated strings for no more than SMK_LONGLABEL(256) characters * smack_setselfattr(): require struct lsm_ctx . flags == 0 to reserve them for future. Fixes: e114e473771c ("Smack: Simplified Mandatory Access Control Kernel") Signed-off-by: Konstantin Andreev Signed-off-by: Casey Schaufler Signed-off-by: Sasha Levin --- Documentation/admin-guide/LSM/Smack.rst | 11 ++- security/smack/smack.h | 3 + security/smack/smack_access.c | 93 ++++++++++++++----- security/smack/smack_lsm.c | 115 +++++++++++++++--------- 4 files changed, 156 insertions(+), 66 deletions(-) diff --git a/Documentation/admin-guide/LSM/Smack.rst b/Documentation/admin-guide/LSM/Smack.rst index 1b554b5bf98e6a..c5ed775f2d107b 100644 --- a/Documentation/admin-guide/LSM/Smack.rst +++ b/Documentation/admin-guide/LSM/Smack.rst @@ -601,10 +601,15 @@ specification. Task Attribute ~~~~~~~~~~~~~~ -The Smack label of a process can be read from /proc//attr/current. A -process can read its own Smack label from /proc/self/attr/current. A +The Smack label of a process can be read from ``/proc//attr/current``. A +process can read its own Smack label from ``/proc/self/attr/current``. A privileged process can change its own Smack label by writing to -/proc/self/attr/current but not the label of another process. +``/proc/self/attr/current`` but not the label of another process. + +Format of writing is : only the label or the label followed by one of the +3 trailers: ``\n`` (by common agreement for ``/proc/...`` interfaces), +``\0`` (because some applications incorrectly include it), +``\n\0`` (because we think some applications may incorrectly include it). File Attribute ~~~~~~~~~~~~~~ diff --git a/security/smack/smack.h b/security/smack/smack.h index bf6a6ed3946cec..759343a6bbaeb0 100644 --- a/security/smack/smack.h +++ b/security/smack/smack.h @@ -286,9 +286,12 @@ int smk_tskacc(struct task_smack *, struct smack_known *, int smk_curacc(struct smack_known *, u32, struct smk_audit_info *); int smack_str_from_perm(char *string, int access); struct smack_known *smack_from_secid(const u32); +int smk_parse_label_len(const char *string, int len); char *smk_parse_smack(const char *string, int len); int smk_netlbl_mls(int, char *, struct netlbl_lsm_secattr *, int); struct smack_known *smk_import_entry(const char *, int); +struct smack_known *smk_import_valid_label(const char *label, int label_len, + gfp_t gfp); void smk_insert_entry(struct smack_known *skp); struct smack_known *smk_find_entry(const char *); bool smack_privileged(int cap); diff --git a/security/smack/smack_access.c b/security/smack/smack_access.c index 2e4a0cb22782b4..a289cb6672bd42 100644 --- a/security/smack/smack_access.c +++ b/security/smack/smack_access.c @@ -443,19 +443,19 @@ struct smack_known *smk_find_entry(const char *string) } /** - * smk_parse_smack - parse smack label from a text string - * @string: a text string that might contain a Smack label - * @len: the maximum size, or zero if it is NULL terminated. + * smk_parse_label_len - calculate the length of the starting segment + * in the string that constitutes a valid smack label + * @string: a text string that might contain a Smack label at the beginning + * @len: the maximum size to look into, may be zero if string is null-terminated * - * Returns a pointer to the clean label or an error code. + * Returns the length of the segment (0 < L < SMK_LONGLABEL) or an error code. */ -char *smk_parse_smack(const char *string, int len) +int smk_parse_label_len(const char *string, int len) { - char *smack; int i; - if (len <= 0) - len = strlen(string) + 1; + if (len <= 0 || len > SMK_LONGLABEL) + len = SMK_LONGLABEL; /* * Reserve a leading '-' as an indicator that @@ -463,7 +463,7 @@ char *smk_parse_smack(const char *string, int len) * including /smack/cipso and /smack/cipso2 */ if (string[0] == '-') - return ERR_PTR(-EINVAL); + return -EINVAL; for (i = 0; i < len; i++) if (string[i] > '~' || string[i] <= ' ' || string[i] == '/' || @@ -471,6 +471,25 @@ char *smk_parse_smack(const char *string, int len) break; if (i == 0 || i >= SMK_LONGLABEL) + return -EINVAL; + + return i; +} + +/** + * smk_parse_smack - copy the starting segment in the string + * that constitutes a valid smack label + * @string: a text string that might contain a Smack label at the beginning + * @len: the maximum size to look into, may be zero if string is null-terminated + * + * Returns a pointer to the copy of the label or an error code. + */ +char *smk_parse_smack(const char *string, int len) +{ + char *smack; + int i = smk_parse_label_len(string, len); + + if (i < 0) return ERR_PTR(-EINVAL); smack = kstrndup(string, i, GFP_NOFS); @@ -554,31 +573,25 @@ int smack_populate_secattr(struct smack_known *skp) } /** - * smk_import_entry - import a label, return the list entry - * @string: a text string that might be a Smack label - * @len: the maximum size, or zero if it is NULL terminated. + * smk_import_valid_allocated_label - import a label, return the list entry + * @smack: a text string that is a valid Smack label and may be kfree()ed. + * It is consumed: either becomes a part of the entry or kfree'ed. * - * Returns a pointer to the entry in the label list that - * matches the passed string, adding it if necessary, - * or an error code. + * Returns: see description of smk_import_entry() */ -struct smack_known *smk_import_entry(const char *string, int len) +static struct smack_known * +smk_import_allocated_label(char *smack, gfp_t gfp) { struct smack_known *skp; - char *smack; int rc; - smack = smk_parse_smack(string, len); - if (IS_ERR(smack)) - return ERR_CAST(smack); - mutex_lock(&smack_known_lock); skp = smk_find_entry(smack); if (skp != NULL) goto freeout; - skp = kzalloc(sizeof(*skp), GFP_NOFS); + skp = kzalloc(sizeof(*skp), gfp); if (skp == NULL) { skp = ERR_PTR(-ENOMEM); goto freeout; @@ -608,6 +621,42 @@ struct smack_known *smk_import_entry(const char *string, int len) return skp; } +/** + * smk_import_entry - import a label, return the list entry + * @string: a text string that might contain a Smack label at the beginning + * @len: the maximum size to look into, may be zero if string is null-terminated + * + * Returns a pointer to the entry in the label list that + * matches the passed string, adding it if necessary, + * or an error code. + */ +struct smack_known *smk_import_entry(const char *string, int len) +{ + char *smack = smk_parse_smack(string, len); + + if (IS_ERR(smack)) + return ERR_CAST(smack); + + return smk_import_allocated_label(smack, GFP_NOFS); +} + +/** + * smk_import_valid_label - import a label, return the list entry + * @label a text string that is a valid Smack label, not null-terminated + * + * Returns: see description of smk_import_entry() + */ +struct smack_known * +smk_import_valid_label(const char *label, int label_len, gfp_t gfp) +{ + char *smack = kstrndup(label, label_len, gfp); + + if (!smack) + return ERR_PTR(-ENOMEM); + + return smk_import_allocated_label(smack, gfp); +} + /** * smack_from_secid - find the Smack label associated with a secid * @secid: an integer that might be associated with a Smack label diff --git a/security/smack/smack_lsm.c b/security/smack/smack_lsm.c index 07dbc4cd303a73..7fd8a60b8be908 100644 --- a/security/smack/smack_lsm.c +++ b/security/smack/smack_lsm.c @@ -3710,7 +3710,7 @@ static void smack_d_instantiate(struct dentry *opt_dentry, struct inode *inode) * @attr: which attribute to fetch * @ctx: buffer to receive the result * @size: available size in, actual size out - * @flags: unused + * @flags: reserved, currently zero * * Fill the passed user space @ctx with the details of the requested * attribute. @@ -3771,57 +3771,52 @@ static int smack_getprocattr(struct task_struct *p, const char *name, char **val * Sets the Smack value of the task. Only setting self * is permitted and only with privilege * - * Returns the length of the smack label or an error code + * Returns zero on success or an error code */ -static int do_setattr(u64 attr, void *value, size_t size) +static int do_setattr(unsigned int attr, void *value, size_t size) { struct task_smack *tsp = smack_cred(current_cred()); struct cred *new; struct smack_known *skp; - char *labelstr; - int rc = 0; - - if (!smack_privileged(CAP_MAC_ADMIN) && list_empty(&tsp->smk_relabel)) - return -EPERM; + int label_len; + /* + * let unprivileged user validate input, check permissions later + */ if (value == NULL || size == 0 || size >= SMK_LONGLABEL) return -EINVAL; - if (attr != LSM_ATTR_CURRENT) - return -EOPNOTSUPP; - - labelstr = smk_parse_smack(value, size); - if (IS_ERR(labelstr)) - return PTR_ERR(labelstr); + label_len = smk_parse_label_len(value, size); + if (label_len < 0 || label_len != size) + return -EINVAL; /* * No process is ever allowed the web ("@") label * and the star ("*") label. */ - if (labelstr[1] == '\0' /* '@', '*' */) { - const char c = labelstr[0]; + if (label_len == 1 /* '@', '*' */) { + const char c = *(const char *)value; if (c == *smack_known_web.smk_known || - c == *smack_known_star.smk_known) { - rc = -EPERM; - goto free_labelstr; - } + c == *smack_known_star.smk_known) + return -EPERM; } if (!smack_privileged(CAP_MAC_ADMIN)) { const struct smack_known_list_elem *sklep; - list_for_each_entry(sklep, &tsp->smk_relabel, list) - if (strcmp(sklep->smk_label->smk_known, labelstr) == 0) - goto free_labelstr; - rc = -EPERM; - } + list_for_each_entry(sklep, &tsp->smk_relabel, list) { + const char *cp = sklep->smk_label->smk_known; -free_labelstr: - kfree(labelstr); - if (rc) + if (strlen(cp) == label_len && + strncmp(cp, value, label_len) == 0) + goto in_relabel; + } return -EPERM; +in_relabel: + ; + } - skp = smk_import_entry(value, size); + skp = smk_import_valid_label(value, label_len, GFP_KERNEL); if (IS_ERR(skp)) return PTR_ERR(skp); @@ -3837,7 +3832,7 @@ static int do_setattr(u64 attr, void *value, size_t size) smk_destroy_label_list(&tsp->smk_relabel); commit_creds(new); - return size; + return 0; } /** @@ -3845,7 +3840,7 @@ static int do_setattr(u64 attr, void *value, size_t size) * @attr: which attribute to set * @ctx: buffer containing the data * @size: size of @ctx - * @flags: unused + * @flags: reserved, must be zero * * Fill the passed user space @ctx with the details of the requested * attribute. @@ -3855,12 +3850,26 @@ static int do_setattr(u64 attr, void *value, size_t size) static int smack_setselfattr(unsigned int attr, struct lsm_ctx *ctx, u32 size, u32 flags) { - int rc; + if (attr != LSM_ATTR_CURRENT) + return -EOPNOTSUPP; - rc = do_setattr(attr, ctx->ctx, ctx->ctx_len); - if (rc > 0) - return 0; - return rc; + if (ctx->flags) + return -EINVAL; + /* + * string must have \0 terminator, included in ctx->ctx + * (see description of struct lsm_ctx) + */ + if (ctx->ctx_len == 0) + return -EINVAL; + + if (ctx->ctx[ctx->ctx_len - 1] != '\0') + return -EINVAL; + /* + * other do_setattr() caller, smack_setprocattr(), + * does not count \0 into size, so + * decreasing length by 1 to accommodate the divergence. + */ + return do_setattr(attr, ctx->ctx, ctx->ctx_len - 1); } /** @@ -3872,15 +3881,39 @@ static int smack_setselfattr(unsigned int attr, struct lsm_ctx *ctx, * Sets the Smack value of the task. Only setting self * is permitted and only with privilege * - * Returns the length of the smack label or an error code + * Returns the size of the input value or an error code */ static int smack_setprocattr(const char *name, void *value, size_t size) { - int attr = lsm_name_to_attr(name); + size_t realsize = size; + unsigned int attr = lsm_name_to_attr(name); - if (attr != LSM_ATTR_UNDEF) - return do_setattr(attr, value, size); - return -EINVAL; + switch (attr) { + case LSM_ATTR_UNDEF: return -EINVAL; + default: return -EOPNOTSUPP; + case LSM_ATTR_CURRENT: + ; + } + + /* + * The value for the "current" attribute is the label + * followed by one of the 4 trailers: none, \0, \n, \n\0 + * + * I.e. following inputs are accepted as 3-characters long label "foo": + * + * "foo" (3 characters) + * "foo\0" (4 characters) + * "foo\n" (4 characters) + * "foo\n\0" (5 characters) + */ + + if (realsize && (((const char *)value)[realsize - 1] == '\0')) + --realsize; + + if (realsize && (((const char *)value)[realsize - 1] == '\n')) + --realsize; + + return do_setattr(attr, value, realsize) ? : size; } /** From 79197c6007f2afbfd7bcf5b9b80ccabf8483d774 Mon Sep 17 00:00:00 2001 From: Mainak Sen Date: Mon, 7 Jul 2025 18:17:39 +0900 Subject: [PATCH 0377/3902] gpu: host1x: Fix race in syncpt alloc/free [ Upstream commit c7d393267c497502fa737607f435f05dfe6e3d9b ] Fix race condition between host1x_syncpt_alloc() and host1x_syncpt_put() by using kref_put_mutex() instead of kref_put() + manual mutex locking. This ensures no thread can acquire the syncpt_mutex after the refcount drops to zero but before syncpt_release acquires it. This prevents races where syncpoints could be allocated while still being cleaned up from a previous release. Remove explicit mutex locking in syncpt_release as kref_put_mutex() handles this atomically. Signed-off-by: Mainak Sen Fixes: f5ba33fb9690 ("gpu: host1x: Reserve VBLANK syncpoints at initialization") Signed-off-by: Mikko Perttunen Signed-off-by: Thierry Reding Link: https://lore.kernel.org/r/20250707-host1x-syncpt-race-fix-v1-1-28b0776e70bc@nvidia.com Signed-off-by: Sasha Levin --- drivers/gpu/host1x/syncpt.c | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/drivers/gpu/host1x/syncpt.c b/drivers/gpu/host1x/syncpt.c index f63d14a57a1d95..acc7d82e0585e8 100644 --- a/drivers/gpu/host1x/syncpt.c +++ b/drivers/gpu/host1x/syncpt.c @@ -345,8 +345,6 @@ static void syncpt_release(struct kref *ref) sp->locked = false; - mutex_lock(&sp->host->syncpt_mutex); - host1x_syncpt_base_free(sp->base); kfree(sp->name); sp->base = NULL; @@ -369,7 +367,7 @@ void host1x_syncpt_put(struct host1x_syncpt *sp) if (!sp) return; - kref_put(&sp->ref, syncpt_release); + kref_put_mutex(&sp->ref, syncpt_release, &sp->host->syncpt_mutex); } EXPORT_SYMBOL(host1x_syncpt_put); From 359653edd5374fbba28f93043554dcc494aee85f Mon Sep 17 00:00:00 2001 From: Lizhi Hou Date: Tue, 9 Sep 2025 08:45:31 -0700 Subject: [PATCH 0378/3902] accel/amdxdna: Fix an integer overflow in aie2_query_ctx_status_array() [ Upstream commit 9e16c8bf9aebf629344cfd4cd5e3dc7d8c3f7d82 ] The unpublished smatch static checker reported a warning. drivers/accel/amdxdna/aie2_pci.c:904 aie2_query_ctx_status_array() warn: potential user controlled sizeof overflow 'args->num_element * args->element_size' '1-u32max(user) * 1-u32max(user)' Even this will not cause a real issue, it is better to put a reasonable limitation for element_size and num_element. Add condition to make sure the input element_size <= 4K and num_element <= 1K. Reported-by: Dan Carpenter Closes: https://lore.kernel.org/dri-devel/aL56ZCLyl3tLQM1e@stanley.mountain/ Fixes: 2f509fe6a42c ("accel/amdxdna: Add ioctl DRM_IOCTL_AMDXDNA_GET_ARRAY") Reviewed-by: Maciej Falkowski Signed-off-by: Lizhi Hou Link: https://lore.kernel.org/r/20250909154531.3469979-1-lizhi.hou@amd.com Signed-off-by: Sasha Levin --- drivers/accel/amdxdna/aie2_pci.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/drivers/accel/amdxdna/aie2_pci.c b/drivers/accel/amdxdna/aie2_pci.c index 87c425e3d2b99c..6e39c769bb6d8f 100644 --- a/drivers/accel/amdxdna/aie2_pci.c +++ b/drivers/accel/amdxdna/aie2_pci.c @@ -898,6 +898,12 @@ static int aie2_query_ctx_status_array(struct amdxdna_client *client, drm_WARN_ON(&xdna->ddev, !mutex_is_locked(&xdna->dev_lock)); + if (args->element_size > SZ_4K || args->num_element > SZ_1K) { + XDNA_DBG(xdna, "Invalid element size %d or number of element %d", + args->element_size, args->num_element); + return -EINVAL; + } + array_args.element_size = min(args->element_size, sizeof(struct amdxdna_drm_hwctx_entry)); array_args.buffer = args->buffer; From 3bb9d125972c748a07eb65bd4e9396b06fd73e2b Mon Sep 17 00:00:00 2001 From: Lizhi Hou Date: Tue, 16 Sep 2025 10:48:42 -0700 Subject: [PATCH 0379/3902] accel/amdxdna: Call dma_buf_vmap_unlocked() for imported object [ Upstream commit 457f4393d02fdb612a93912fb09cef70e6e545c9 ] In amdxdna_gem_obj_vmap(), calling dma_buf_vmap() triggers a kernel warning if LOCKDEP is enabled. So for imported object, use dma_buf_vmap_unlocked(). Then, use drm_gem_vmap() for other objects. The similar change applies to vunmap code. Fixes: bd72d4acda10 ("accel/amdxdna: Support user space allocated buffer") Reviewed-by: Maciej Falkowski Signed-off-by: Lizhi Hou Link: https://lore.kernel.org/r/20250916174842.234709-1-lizhi.hou@amd.com Signed-off-by: Sasha Levin --- drivers/accel/amdxdna/amdxdna_gem.c | 47 ++++++++++++----------------- 1 file changed, 20 insertions(+), 27 deletions(-) diff --git a/drivers/accel/amdxdna/amdxdna_gem.c b/drivers/accel/amdxdna/amdxdna_gem.c index d407a36eb41265..7f91863c3f24ce 100644 --- a/drivers/accel/amdxdna/amdxdna_gem.c +++ b/drivers/accel/amdxdna/amdxdna_gem.c @@ -392,35 +392,33 @@ static const struct dma_buf_ops amdxdna_dmabuf_ops = { .vunmap = drm_gem_dmabuf_vunmap, }; -static int amdxdna_gem_obj_vmap(struct drm_gem_object *obj, struct iosys_map *map) +static int amdxdna_gem_obj_vmap(struct amdxdna_gem_obj *abo, void **vaddr) { - struct amdxdna_gem_obj *abo = to_xdna_obj(obj); - - iosys_map_clear(map); - - dma_resv_assert_held(obj->resv); + struct iosys_map map = IOSYS_MAP_INIT_VADDR(NULL); + int ret; if (is_import_bo(abo)) - dma_buf_vmap(abo->dma_buf, map); + ret = dma_buf_vmap_unlocked(abo->dma_buf, &map); else - drm_gem_shmem_object_vmap(obj, map); - - if (!map->vaddr) - return -ENOMEM; + ret = drm_gem_vmap(to_gobj(abo), &map); - return 0; + *vaddr = map.vaddr; + return ret; } -static void amdxdna_gem_obj_vunmap(struct drm_gem_object *obj, struct iosys_map *map) +static void amdxdna_gem_obj_vunmap(struct amdxdna_gem_obj *abo) { - struct amdxdna_gem_obj *abo = to_xdna_obj(obj); + struct iosys_map map; + + if (!abo->mem.kva) + return; - dma_resv_assert_held(obj->resv); + iosys_map_set_vaddr(&map, abo->mem.kva); if (is_import_bo(abo)) - dma_buf_vunmap(abo->dma_buf, map); + dma_buf_vunmap_unlocked(abo->dma_buf, &map); else - drm_gem_shmem_object_vunmap(obj, map); + drm_gem_vunmap(to_gobj(abo), &map); } static struct dma_buf *amdxdna_gem_prime_export(struct drm_gem_object *gobj, int flags) @@ -455,7 +453,6 @@ static void amdxdna_gem_obj_free(struct drm_gem_object *gobj) { struct amdxdna_dev *xdna = to_xdna_dev(gobj->dev); struct amdxdna_gem_obj *abo = to_xdna_obj(gobj); - struct iosys_map map = IOSYS_MAP_INIT_VADDR(abo->mem.kva); XDNA_DBG(xdna, "BO type %d xdna_addr 0x%llx", abo->type, abo->mem.dev_addr); @@ -468,7 +465,7 @@ static void amdxdna_gem_obj_free(struct drm_gem_object *gobj) if (abo->type == AMDXDNA_BO_DEV_HEAP) drm_mm_takedown(&abo->mm); - drm_gem_vunmap(gobj, &map); + amdxdna_gem_obj_vunmap(abo); mutex_destroy(&abo->lock); if (is_import_bo(abo)) { @@ -489,8 +486,8 @@ static const struct drm_gem_object_funcs amdxdna_gem_shmem_funcs = { .pin = drm_gem_shmem_object_pin, .unpin = drm_gem_shmem_object_unpin, .get_sg_table = drm_gem_shmem_object_get_sg_table, - .vmap = amdxdna_gem_obj_vmap, - .vunmap = amdxdna_gem_obj_vunmap, + .vmap = drm_gem_shmem_object_vmap, + .vunmap = drm_gem_shmem_object_vunmap, .mmap = amdxdna_gem_obj_mmap, .vm_ops = &drm_gem_shmem_vm_ops, .export = amdxdna_gem_prime_export, @@ -663,7 +660,6 @@ amdxdna_drm_create_dev_heap(struct drm_device *dev, struct drm_file *filp) { struct amdxdna_client *client = filp->driver_priv; - struct iosys_map map = IOSYS_MAP_INIT_VADDR(NULL); struct amdxdna_dev *xdna = to_xdna_dev(dev); struct amdxdna_gem_obj *abo; int ret; @@ -692,12 +688,11 @@ amdxdna_drm_create_dev_heap(struct drm_device *dev, abo->mem.dev_addr = client->xdna->dev_info->dev_mem_base; drm_mm_init(&abo->mm, abo->mem.dev_addr, abo->mem.size); - ret = drm_gem_vmap(to_gobj(abo), &map); + ret = amdxdna_gem_obj_vmap(abo, &abo->mem.kva); if (ret) { XDNA_ERR(xdna, "Vmap heap bo failed, ret %d", ret); goto release_obj; } - abo->mem.kva = map.vaddr; client->dev_heap = abo; drm_gem_object_get(to_gobj(abo)); @@ -748,7 +743,6 @@ amdxdna_drm_create_cmd_bo(struct drm_device *dev, struct amdxdna_drm_create_bo *args, struct drm_file *filp) { - struct iosys_map map = IOSYS_MAP_INIT_VADDR(NULL); struct amdxdna_dev *xdna = to_xdna_dev(dev); struct amdxdna_gem_obj *abo; int ret; @@ -770,12 +764,11 @@ amdxdna_drm_create_cmd_bo(struct drm_device *dev, abo->type = AMDXDNA_BO_CMD; abo->client = filp->driver_priv; - ret = drm_gem_vmap(to_gobj(abo), &map); + ret = amdxdna_gem_obj_vmap(abo, &abo->mem.kva); if (ret) { XDNA_ERR(xdna, "Vmap cmd bo failed, ret %d", ret); goto release_obj; } - abo->mem.kva = map.vaddr; return abo; From 270f4eb14e0fb8c212ce57c11466da45e0a9c262 Mon Sep 17 00:00:00 2001 From: Karol Wachowski Date: Tue, 16 Sep 2025 10:48:09 +0200 Subject: [PATCH 0380/3902] accel/ivpu: Ensure rpm_runtime_put in case of engine reset/resume fail [ Upstream commit 9f6c63285737b141ca25a619add80a96111b8b96 ] Previously, aborting work could return early after engine reset or resume failure, skipping the necessary runtime_put cleanup leaving the device with incorrect reference count breaking runtime power management state. Replace early returns with goto statements to ensure runtime_put is always executed. Fixes: a47e36dc5d90 ("accel/ivpu: Trigger device recovery on engine reset/resume failure") Reviewed-by: Lizhi Hou Signed-off-by: Karol Wachowski Link: https://lore.kernel.org/r/20250916084809.850073-1-karol.wachowski@linux.intel.com Signed-off-by: Sasha Levin --- drivers/accel/ivpu/ivpu_job.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/accel/ivpu/ivpu_job.c b/drivers/accel/ivpu/ivpu_job.c index 060f1fc031d347..dbefa43c74e287 100644 --- a/drivers/accel/ivpu/ivpu_job.c +++ b/drivers/accel/ivpu/ivpu_job.c @@ -1012,7 +1012,7 @@ void ivpu_context_abort_work_fn(struct work_struct *work) if (vdev->fw->sched_mode == VPU_SCHEDULING_MODE_HW) if (ivpu_jsm_reset_engine(vdev, 0)) - return; + goto runtime_put; mutex_lock(&vdev->context_list_lock); xa_for_each(&vdev->context_xa, ctx_id, file_priv) { @@ -1036,7 +1036,7 @@ void ivpu_context_abort_work_fn(struct work_struct *work) goto runtime_put; if (ivpu_jsm_hws_resume_engine(vdev, 0)) - return; + goto runtime_put; /* * In hardware scheduling mode NPU already has stopped processing jobs * and won't send us any further notifications, thus we have to free job related resources From 28540b8f226b5f2fe5158c0ae2bc1e159b7965a0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Guido=20G=C3=BCnther?= Date: Wed, 10 Sep 2025 18:39:56 +0200 Subject: [PATCH 0381/3902] drm/panel: visionox-rm69299: Fix clock frequency for SHIFT6mq MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [ Upstream commit d298062312724606855294503acebc7ee55ffbca ] Make the clock frequency match what the sdm845 downstream kernel uses. Otherwise the panel stays black. Fixes: 783334f366b18 ("drm/panel: visionox-rm69299: support the variant found in the SHIFT6mq") Signed-off-by: Guido Günther Reviewed-by: Neil Armstrong Reviewed-by: Dmitry Baryshkov Signed-off-by: Neil Armstrong Link: https://lore.kernel.org/r/20250910-shift6mq-panel-v3-1-a7729911afb9@sigxcpu.org Signed-off-by: Sasha Levin --- drivers/gpu/drm/panel/panel-visionox-rm69299.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/gpu/drm/panel/panel-visionox-rm69299.c b/drivers/gpu/drm/panel/panel-visionox-rm69299.c index 909c280eab1fb4..5491d601681cff 100644 --- a/drivers/gpu/drm/panel/panel-visionox-rm69299.c +++ b/drivers/gpu/drm/panel/panel-visionox-rm69299.c @@ -247,7 +247,7 @@ static const struct drm_display_mode visionox_rm69299_1080x2248_60hz = { }; static const struct drm_display_mode visionox_rm69299_1080x2160_60hz = { - .clock = 158695, + .clock = (2160 + 8 + 4 + 4) * (1080 + 26 + 2 + 36) * 60 / 1000, .hdisplay = 1080, .hsync_start = 1080 + 26, .hsync_end = 1080 + 26 + 2, From f94792340ecc6b8762777da034e2644b469e707a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Guido=20G=C3=BCnther?= Date: Wed, 10 Sep 2025 18:39:57 +0200 Subject: [PATCH 0382/3902] drm/panel: visionox-rm69299: Don't clear all mode flags MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [ Upstream commit 39144b611e9cd4f5814f4098c891b545dd70c536 ] Don't clear all mode flags. We only want to maek sure we use HS mode during unprepare. Fixes: c7f66d32dd431 ("drm/panel: add support for rm69299 visionox panel") Reviewed-by: Neil Armstrong Signed-off-by: Guido Günther Reviewed-by: Dmitry Baryshkov Signed-off-by: Neil Armstrong Link: https://lore.kernel.org/r/20250910-shift6mq-panel-v3-2-a7729911afb9@sigxcpu.org Signed-off-by: Sasha Levin --- drivers/gpu/drm/panel/panel-visionox-rm69299.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/gpu/drm/panel/panel-visionox-rm69299.c b/drivers/gpu/drm/panel/panel-visionox-rm69299.c index 5491d601681cff..66c30db3b73a71 100644 --- a/drivers/gpu/drm/panel/panel-visionox-rm69299.c +++ b/drivers/gpu/drm/panel/panel-visionox-rm69299.c @@ -192,7 +192,7 @@ static int visionox_rm69299_unprepare(struct drm_panel *panel) struct visionox_rm69299 *ctx = panel_to_ctx(panel); struct mipi_dsi_multi_context dsi_ctx = { .dsi = ctx->dsi }; - ctx->dsi->mode_flags = 0; + ctx->dsi->mode_flags &= ~MIPI_DSI_MODE_LPM; mipi_dsi_dcs_set_display_off_multi(&dsi_ctx); From 94c56e7f63c2d952ca64a2c9a0a1ef0f8c71cf9b Mon Sep 17 00:00:00 2001 From: Jacek Lawrynowicz Date: Thu, 25 Sep 2025 16:50:59 +0200 Subject: [PATCH 0383/3902] accel/ivpu: Rework bind/unbind of imported buffers [ Upstream commit e0c0891cd63bf8338c25c423e28a5a93aed3d74c ] Ensure that imported buffers are properly mapped and unmapped in the same way as regular buffers to properly handle buffers during device's bind and unbind operations to prevent resource leaks and inconsistent buffer states. Imported buffers are now dma_mapped before submission and dma_unmapped in ivpu_bo_unbind(), guaranteeing they are unmapped when the device is unbound. Add also imported buffers to vdev->bo_list for consistent unmapping on device unbind. The bo->ctx_id is set in open() so imported buffers have a valid context ID. Debug logs have been updated to match the new code structure. The function ivpu_bo_pin() has been renamed to ivpu_bo_bind() to better reflect its purpose, and unbind tests have been refactored for improved coverage and clarity. Signed-off-by: Jacek Lawrynowicz Signed-off-by: Maciej Falkowski Reviewed-by: Karol Wachowski Signed-off-by: Karol Wachowski Link: https://lore.kernel.org/r/20250925145059.1446243-1-maciej.falkowski@linux.intel.com Stable-dep-of: 8b694b405a84 ("accel/ivpu: Fix page fault in ivpu_bo_unbind_all_bos_from_context()") Signed-off-by: Sasha Levin --- drivers/accel/ivpu/ivpu_gem.c | 90 ++++++++++++++++++++++------------- drivers/accel/ivpu/ivpu_gem.h | 2 +- drivers/accel/ivpu/ivpu_job.c | 2 +- 3 files changed, 60 insertions(+), 34 deletions(-) diff --git a/drivers/accel/ivpu/ivpu_gem.c b/drivers/accel/ivpu/ivpu_gem.c index 59cfcf3eaded96..dc6d0d15cda9cb 100644 --- a/drivers/accel/ivpu/ivpu_gem.c +++ b/drivers/accel/ivpu/ivpu_gem.c @@ -27,8 +27,8 @@ static const struct drm_gem_object_funcs ivpu_gem_funcs; static inline void ivpu_dbg_bo(struct ivpu_device *vdev, struct ivpu_bo *bo, const char *action) { ivpu_dbg(vdev, BO, - "%6s: bo %8p vpu_addr %9llx size %8zu ctx %d has_pages %d dma_mapped %d mmu_mapped %d wc %d imported %d\n", - action, bo, bo->vpu_addr, ivpu_bo_size(bo), bo->ctx_id, + "%6s: bo %8p size %9zu ctx %d vpu_addr %9llx pages %d sgt %d mmu_mapped %d wc %d imported %d\n", + action, bo, ivpu_bo_size(bo), bo->ctx_id, bo->vpu_addr, (bool)bo->base.pages, (bool)bo->base.sgt, bo->mmu_mapped, bo->base.map_wc, (bool)drm_gem_is_imported(&bo->base.base)); } @@ -43,22 +43,46 @@ static inline void ivpu_bo_unlock(struct ivpu_bo *bo) dma_resv_unlock(bo->base.base.resv); } +static struct sg_table *ivpu_bo_map_attachment(struct ivpu_device *vdev, struct ivpu_bo *bo) +{ + struct sg_table *sgt = bo->base.sgt; + + drm_WARN_ON(&vdev->drm, !bo->base.base.import_attach); + + ivpu_bo_lock(bo); + + if (!sgt) { + sgt = dma_buf_map_attachment(bo->base.base.import_attach, DMA_BIDIRECTIONAL); + if (IS_ERR(sgt)) + ivpu_err(vdev, "Failed to map BO in IOMMU: %ld\n", PTR_ERR(sgt)); + else + bo->base.sgt = sgt; + } + + ivpu_bo_unlock(bo); + + return sgt; +} + /* - * ivpu_bo_pin() - pin the backing physical pages and map them to VPU. + * ivpu_bo_bind() - pin the backing physical pages and map them to VPU. * * This function pins physical memory pages, then maps the physical pages * to IOMMU address space and finally updates the VPU MMU page tables * to allow the VPU to translate VPU address to IOMMU address. */ -int __must_check ivpu_bo_pin(struct ivpu_bo *bo) +int __must_check ivpu_bo_bind(struct ivpu_bo *bo) { struct ivpu_device *vdev = ivpu_bo_to_vdev(bo); struct sg_table *sgt; int ret = 0; - ivpu_dbg_bo(vdev, bo, "pin"); + ivpu_dbg_bo(vdev, bo, "bind"); - sgt = drm_gem_shmem_get_pages_sgt(&bo->base); + if (bo->base.base.import_attach) + sgt = ivpu_bo_map_attachment(vdev, bo); + else + sgt = drm_gem_shmem_get_pages_sgt(&bo->base); if (IS_ERR(sgt)) { ret = PTR_ERR(sgt); ivpu_err(vdev, "Failed to map BO in IOMMU: %d\n", ret); @@ -99,7 +123,9 @@ ivpu_bo_alloc_vpu_addr(struct ivpu_bo *bo, struct ivpu_mmu_context *ctx, ret = ivpu_mmu_context_insert_node(ctx, range, ivpu_bo_size(bo), &bo->mm_node); if (!ret) { bo->ctx = ctx; + bo->ctx_id = ctx->id; bo->vpu_addr = bo->mm_node.start; + ivpu_dbg_bo(vdev, bo, "vaddr"); } else { ivpu_err(vdev, "Failed to add BO to context %u: %d\n", ctx->id, ret); } @@ -115,7 +141,7 @@ static void ivpu_bo_unbind_locked(struct ivpu_bo *bo) { struct ivpu_device *vdev = ivpu_bo_to_vdev(bo); - lockdep_assert(dma_resv_held(bo->base.base.resv) || !kref_read(&bo->base.base.refcount)); + dma_resv_assert_held(bo->base.base.resv); if (bo->mmu_mapped) { drm_WARN_ON(&vdev->drm, !bo->ctx); @@ -134,9 +160,14 @@ static void ivpu_bo_unbind_locked(struct ivpu_bo *bo) return; if (bo->base.sgt) { - dma_unmap_sgtable(vdev->drm.dev, bo->base.sgt, DMA_BIDIRECTIONAL, 0); - sg_free_table(bo->base.sgt); - kfree(bo->base.sgt); + if (bo->base.base.import_attach) { + dma_buf_unmap_attachment(bo->base.base.import_attach, + bo->base.sgt, DMA_BIDIRECTIONAL); + } else { + dma_unmap_sgtable(vdev->drm.dev, bo->base.sgt, DMA_BIDIRECTIONAL, 0); + sg_free_table(bo->base.sgt); + kfree(bo->base.sgt); + } bo->base.sgt = NULL; } } @@ -162,6 +193,7 @@ void ivpu_bo_unbind_all_bos_from_context(struct ivpu_device *vdev, struct ivpu_m struct drm_gem_object *ivpu_gem_create_object(struct drm_device *dev, size_t size) { + struct ivpu_device *vdev = to_ivpu_device(dev); struct ivpu_bo *bo; if (size == 0 || !PAGE_ALIGNED(size)) @@ -176,6 +208,11 @@ struct drm_gem_object *ivpu_gem_create_object(struct drm_device *dev, size_t siz INIT_LIST_HEAD(&bo->bo_list_node); + mutex_lock(&vdev->bo_list_lock); + list_add_tail(&bo->bo_list_node, &vdev->bo_list); + mutex_unlock(&vdev->bo_list_lock); + + ivpu_dbg(vdev, BO, " alloc: bo %8p size %9zu\n", bo, size); return &bo->base.base; } @@ -184,7 +221,6 @@ struct drm_gem_object *ivpu_gem_prime_import(struct drm_device *dev, { struct device *attach_dev = dev->dev; struct dma_buf_attachment *attach; - struct sg_table *sgt; struct drm_gem_object *obj; int ret; @@ -194,16 +230,10 @@ struct drm_gem_object *ivpu_gem_prime_import(struct drm_device *dev, get_dma_buf(dma_buf); - sgt = dma_buf_map_attachment_unlocked(attach, DMA_BIDIRECTIONAL); - if (IS_ERR(sgt)) { - ret = PTR_ERR(sgt); - goto fail_detach; - } - - obj = drm_gem_shmem_prime_import_sg_table(dev, attach, sgt); + obj = drm_gem_shmem_prime_import_sg_table(dev, attach, NULL); if (IS_ERR(obj)) { ret = PTR_ERR(obj); - goto fail_unmap; + goto fail_detach; } obj->import_attach = attach; @@ -211,8 +241,6 @@ struct drm_gem_object *ivpu_gem_prime_import(struct drm_device *dev, return obj; -fail_unmap: - dma_buf_unmap_attachment_unlocked(attach, sgt, DMA_BIDIRECTIONAL); fail_detach: dma_buf_detach(dma_buf, attach); dma_buf_put(dma_buf); @@ -220,7 +248,7 @@ struct drm_gem_object *ivpu_gem_prime_import(struct drm_device *dev, return ERR_PTR(ret); } -static struct ivpu_bo *ivpu_bo_alloc(struct ivpu_device *vdev, u64 size, u32 flags, u32 ctx_id) +static struct ivpu_bo *ivpu_bo_alloc(struct ivpu_device *vdev, u64 size, u32 flags) { struct drm_gem_shmem_object *shmem; struct ivpu_bo *bo; @@ -238,16 +266,9 @@ static struct ivpu_bo *ivpu_bo_alloc(struct ivpu_device *vdev, u64 size, u32 fla return ERR_CAST(shmem); bo = to_ivpu_bo(&shmem->base); - bo->ctx_id = ctx_id; bo->base.map_wc = flags & DRM_IVPU_BO_WC; bo->flags = flags; - mutex_lock(&vdev->bo_list_lock); - list_add_tail(&bo->bo_list_node, &vdev->bo_list); - mutex_unlock(&vdev->bo_list_lock); - - ivpu_dbg_bo(vdev, bo, "alloc"); - return bo; } @@ -281,6 +302,8 @@ static void ivpu_gem_bo_free(struct drm_gem_object *obj) ivpu_dbg_bo(vdev, bo, "free"); + drm_WARN_ON(&vdev->drm, list_empty(&bo->bo_list_node)); + mutex_lock(&vdev->bo_list_lock); list_del(&bo->bo_list_node); mutex_unlock(&vdev->bo_list_lock); @@ -290,7 +313,10 @@ static void ivpu_gem_bo_free(struct drm_gem_object *obj) drm_WARN_ON(&vdev->drm, ivpu_bo_size(bo) == 0); drm_WARN_ON(&vdev->drm, bo->base.vaddr); + ivpu_bo_lock(bo); ivpu_bo_unbind_locked(bo); + ivpu_bo_unlock(bo); + drm_WARN_ON(&vdev->drm, bo->mmu_mapped); drm_WARN_ON(&vdev->drm, bo->ctx); @@ -326,7 +352,7 @@ int ivpu_bo_create_ioctl(struct drm_device *dev, void *data, struct drm_file *fi if (size == 0) return -EINVAL; - bo = ivpu_bo_alloc(vdev, size, args->flags, file_priv->ctx.id); + bo = ivpu_bo_alloc(vdev, size, args->flags); if (IS_ERR(bo)) { ivpu_err(vdev, "Failed to allocate BO: %pe (ctx %u size %llu flags 0x%x)", bo, file_priv->ctx.id, args->size, args->flags); @@ -360,7 +386,7 @@ ivpu_bo_create(struct ivpu_device *vdev, struct ivpu_mmu_context *ctx, drm_WARN_ON(&vdev->drm, !PAGE_ALIGNED(range->end)); drm_WARN_ON(&vdev->drm, !PAGE_ALIGNED(size)); - bo = ivpu_bo_alloc(vdev, size, flags, IVPU_GLOBAL_CONTEXT_MMU_SSID); + bo = ivpu_bo_alloc(vdev, size, flags); if (IS_ERR(bo)) { ivpu_err(vdev, "Failed to allocate BO: %pe (vpu_addr 0x%llx size %llu flags 0x%x)", bo, range->start, size, flags); @@ -371,7 +397,7 @@ ivpu_bo_create(struct ivpu_device *vdev, struct ivpu_mmu_context *ctx, if (ret) goto err_put; - ret = ivpu_bo_pin(bo); + ret = ivpu_bo_bind(bo); if (ret) goto err_put; diff --git a/drivers/accel/ivpu/ivpu_gem.h b/drivers/accel/ivpu/ivpu_gem.h index aa8ff14f7aae1a..ade0d127453ff3 100644 --- a/drivers/accel/ivpu/ivpu_gem.h +++ b/drivers/accel/ivpu/ivpu_gem.h @@ -24,7 +24,7 @@ struct ivpu_bo { bool mmu_mapped; }; -int ivpu_bo_pin(struct ivpu_bo *bo); +int ivpu_bo_bind(struct ivpu_bo *bo); void ivpu_bo_unbind_all_bos_from_context(struct ivpu_device *vdev, struct ivpu_mmu_context *ctx); struct drm_gem_object *ivpu_gem_create_object(struct drm_device *dev, size_t size); diff --git a/drivers/accel/ivpu/ivpu_job.c b/drivers/accel/ivpu/ivpu_job.c index dbefa43c74e287..1e4caf5726474d 100644 --- a/drivers/accel/ivpu/ivpu_job.c +++ b/drivers/accel/ivpu/ivpu_job.c @@ -732,7 +732,7 @@ ivpu_job_prepare_bos_for_submit(struct drm_file *file, struct ivpu_job *job, u32 job->bos[i] = to_ivpu_bo(obj); - ret = ivpu_bo_pin(job->bos[i]); + ret = ivpu_bo_bind(job->bos[i]); if (ret) return ret; } From c9ef5ccd8bd9bcf598b6d3f77e7eb4dde7149aec Mon Sep 17 00:00:00 2001 From: Jacek Lawrynowicz Date: Thu, 25 Sep 2025 16:51:14 +0200 Subject: [PATCH 0384/3902] accel/ivpu: Fix page fault in ivpu_bo_unbind_all_bos_from_context() [ Upstream commit 8b694b405a84696f1d964f6da7cf9721e68c4714 ] Don't add BO to the vdev->bo_list in ivpu_gem_create_object(). When failure happens inside drm_gem_shmem_create(), the BO is not fully created and ivpu_gem_bo_free() callback will not be called causing a deleted BO to be left on the list. Fixes: 8d88e4cdce4f ("accel/ivpu: Use GEM shmem helper for all buffers") Signed-off-by: Jacek Lawrynowicz Signed-off-by: Maciej Falkowski Reviewed-by: Karol Wachowski Signed-off-by: Karol Wachowski Link: https://lore.kernel.org/r/20250925145114.1446283-1-maciej.falkowski@linux.intel.com Signed-off-by: Sasha Levin --- drivers/accel/ivpu/ivpu_gem.c | 22 ++++++++++++++++------ 1 file changed, 16 insertions(+), 6 deletions(-) diff --git a/drivers/accel/ivpu/ivpu_gem.c b/drivers/accel/ivpu/ivpu_gem.c index dc6d0d15cda9cb..171e809575ad65 100644 --- a/drivers/accel/ivpu/ivpu_gem.c +++ b/drivers/accel/ivpu/ivpu_gem.c @@ -193,7 +193,6 @@ void ivpu_bo_unbind_all_bos_from_context(struct ivpu_device *vdev, struct ivpu_m struct drm_gem_object *ivpu_gem_create_object(struct drm_device *dev, size_t size) { - struct ivpu_device *vdev = to_ivpu_device(dev); struct ivpu_bo *bo; if (size == 0 || !PAGE_ALIGNED(size)) @@ -208,20 +207,17 @@ struct drm_gem_object *ivpu_gem_create_object(struct drm_device *dev, size_t siz INIT_LIST_HEAD(&bo->bo_list_node); - mutex_lock(&vdev->bo_list_lock); - list_add_tail(&bo->bo_list_node, &vdev->bo_list); - mutex_unlock(&vdev->bo_list_lock); - - ivpu_dbg(vdev, BO, " alloc: bo %8p size %9zu\n", bo, size); return &bo->base.base; } struct drm_gem_object *ivpu_gem_prime_import(struct drm_device *dev, struct dma_buf *dma_buf) { + struct ivpu_device *vdev = to_ivpu_device(dev); struct device *attach_dev = dev->dev; struct dma_buf_attachment *attach; struct drm_gem_object *obj; + struct ivpu_bo *bo; int ret; attach = dma_buf_attach(dma_buf, attach_dev); @@ -239,6 +235,14 @@ struct drm_gem_object *ivpu_gem_prime_import(struct drm_device *dev, obj->import_attach = attach; obj->resv = dma_buf->resv; + bo = to_ivpu_bo(obj); + + mutex_lock(&vdev->bo_list_lock); + list_add_tail(&bo->bo_list_node, &vdev->bo_list); + mutex_unlock(&vdev->bo_list_lock); + + ivpu_dbg(vdev, BO, "import: bo %8p size %9zu\n", bo, ivpu_bo_size(bo)); + return obj; fail_detach: @@ -269,6 +273,12 @@ static struct ivpu_bo *ivpu_bo_alloc(struct ivpu_device *vdev, u64 size, u32 fla bo->base.map_wc = flags & DRM_IVPU_BO_WC; bo->flags = flags; + mutex_lock(&vdev->bo_list_lock); + list_add_tail(&bo->bo_list_node, &vdev->bo_list); + mutex_unlock(&vdev->bo_list_lock); + + ivpu_dbg(vdev, BO, " alloc: bo %8p size %9llu\n", bo, size); + return bo; } From ec6ca941783a96c2b4217b5d511c2f675e0f8b44 Mon Sep 17 00:00:00 2001 From: Karol Wachowski Date: Wed, 1 Oct 2025 12:43:22 +0200 Subject: [PATCH 0385/3902] accel/ivpu: Fix DCT active percent format [ Upstream commit aa1c2b073ad23847dd2e7bdc7d30009f34ed7f59 ] The pcode MAILBOX STATUS register PARAM2 field expects DCT active percent in U1.7 value format. Convert percentage value to this format before writing to the register. Fixes: a19bffb10c46 ("accel/ivpu: Implement DCT handling") Reviewed-by: Lizhi Hou Signed-off-by: Karol Wachowski Link: https://lore.kernel.org/r/20251001104322.1249896-1-karol.wachowski@linux.intel.com Signed-off-by: Sasha Levin --- drivers/accel/ivpu/ivpu_hw_btrs.c | 2 +- drivers/accel/ivpu/ivpu_hw_btrs.h | 2 +- drivers/accel/ivpu/ivpu_pm.c | 9 +++++++-- 3 files changed, 9 insertions(+), 4 deletions(-) diff --git a/drivers/accel/ivpu/ivpu_hw_btrs.c b/drivers/accel/ivpu/ivpu_hw_btrs.c index afdb3b2aa72a70..aa33f562d29c12 100644 --- a/drivers/accel/ivpu/ivpu_hw_btrs.c +++ b/drivers/accel/ivpu/ivpu_hw_btrs.c @@ -752,7 +752,7 @@ int ivpu_hw_btrs_dct_get_request(struct ivpu_device *vdev, bool *enable) } } -void ivpu_hw_btrs_dct_set_status(struct ivpu_device *vdev, bool enable, u32 active_percent) +void ivpu_hw_btrs_dct_set_status(struct ivpu_device *vdev, bool enable, u8 active_percent) { u32 val = 0; u32 cmd = enable ? DCT_ENABLE : DCT_DISABLE; diff --git a/drivers/accel/ivpu/ivpu_hw_btrs.h b/drivers/accel/ivpu/ivpu_hw_btrs.h index 032c384ac3d4d5..c4c10e22f30f37 100644 --- a/drivers/accel/ivpu/ivpu_hw_btrs.h +++ b/drivers/accel/ivpu/ivpu_hw_btrs.h @@ -36,7 +36,7 @@ u32 ivpu_hw_btrs_dpu_freq_get(struct ivpu_device *vdev); bool ivpu_hw_btrs_irq_handler_mtl(struct ivpu_device *vdev, int irq); bool ivpu_hw_btrs_irq_handler_lnl(struct ivpu_device *vdev, int irq); int ivpu_hw_btrs_dct_get_request(struct ivpu_device *vdev, bool *enable); -void ivpu_hw_btrs_dct_set_status(struct ivpu_device *vdev, bool enable, u32 active_percent); +void ivpu_hw_btrs_dct_set_status(struct ivpu_device *vdev, bool enable, u8 active_percent); u32 ivpu_hw_btrs_telemetry_offset_get(struct ivpu_device *vdev); u32 ivpu_hw_btrs_telemetry_size_get(struct ivpu_device *vdev); u32 ivpu_hw_btrs_telemetry_enable_get(struct ivpu_device *vdev); diff --git a/drivers/accel/ivpu/ivpu_pm.c b/drivers/accel/ivpu/ivpu_pm.c index 475ddc94f1cfe3..457ccf09df5455 100644 --- a/drivers/accel/ivpu/ivpu_pm.c +++ b/drivers/accel/ivpu/ivpu_pm.c @@ -502,6 +502,11 @@ void ivpu_pm_irq_dct_work_fn(struct work_struct *work) else ret = ivpu_pm_dct_disable(vdev); - if (!ret) - ivpu_hw_btrs_dct_set_status(vdev, enable, vdev->pm->dct_active_percent); + if (!ret) { + /* Convert percent to U1.7 format */ + u8 val = DIV_ROUND_CLOSEST(vdev->pm->dct_active_percent * 128, 100); + + ivpu_hw_btrs_dct_set_status(vdev, enable, val); + } + } From 1f0ca9d3e7c38a39f1f12377c24decf0bba46e54 Mon Sep 17 00:00:00 2001 From: Janusz Krzysztofik Date: Fri, 26 Sep 2025 17:26:27 +0200 Subject: [PATCH 0386/3902] drm/vgem-fence: Fix potential deadlock on release MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [ Upstream commit 78b4d6463e9e69e5103f98b367f8984ad12cdc6f ] A timer that expires a vgem fence automatically in 10 seconds is now released with timer_delete_sync() from fence->ops.release() called on last dma_fence_put(). In some scenarios, it can run in IRQ context, which is not safe unless TIMER_IRQSAFE is used. One potentially risky scenario was demonstrated in Intel DRM CI trybot, BAT run on machine bat-adlp-6, while working on new IGT subtests syncobj_timeline@stress-* as user space replacements of some problematic test cases of a dma-fence-chain selftest [1]. [117.004338] ================================ [117.004340] WARNING: inconsistent lock state [117.004342] 6.17.0-rc7-CI_DRM_17270-g7644974e648c+ #1 Tainted: G S U [117.004346] -------------------------------- [117.004347] inconsistent {HARDIRQ-ON-W} -> {IN-HARDIRQ-W} usage. [117.004349] swapper/0/0 [HC1[1]:SC1[1]:HE0:SE0] takes: [117.004352] ffff888138f86aa8 ((&fence->timer)){?.-.}-{0:0}, at: __timer_delete_sync+0x4b/0x190 [117.004361] {HARDIRQ-ON-W} state was registered at: [117.004363] lock_acquire+0xc4/0x2e0 [117.004366] call_timer_fn+0x80/0x2a0 [117.004368] __run_timers+0x231/0x310 [117.004370] run_timer_softirq+0x76/0xe0 [117.004372] handle_softirqs+0xd4/0x4d0 [117.004375] __irq_exit_rcu+0x13f/0x160 [117.004377] irq_exit_rcu+0xe/0x20 [117.004379] sysvec_apic_timer_interrupt+0xa0/0xc0 [117.004382] asm_sysvec_apic_timer_interrupt+0x1b/0x20 [117.004385] cpuidle_enter_state+0x12b/0x8a0 [117.004388] cpuidle_enter+0x2e/0x50 [117.004393] call_cpuidle+0x22/0x60 [117.004395] do_idle+0x1fd/0x260 [117.004398] cpu_startup_entry+0x29/0x30 [117.004401] start_secondary+0x12d/0x160 [117.004404] common_startup_64+0x13e/0x141 [117.004407] irq event stamp: 2282669 [117.004409] hardirqs last enabled at (2282668): [] _raw_spin_unlock_irqrestore+0x51/0x80 [117.004414] hardirqs last disabled at (2282669): [] sysvec_irq_work+0x11/0xc0 [117.004419] softirqs last enabled at (2254702): [] __do_softirq+0x10/0x18 [117.004423] softirqs last disabled at (2254725): [] __irq_exit_rcu+0x13f/0x160 [117.004426] other info that might help us debug this: [117.004429] Possible unsafe locking scenario: [117.004432] CPU0 [117.004433] ---- [117.004434] lock((&fence->timer)); [117.004436] [117.004438] lock((&fence->timer)); [117.004440] *** DEADLOCK *** [117.004443] 1 lock held by swapper/0/0: [117.004445] #0: ffffc90000003d50 ((&fence->timer)){?.-.}-{0:0}, at: call_timer_fn+0x7a/0x2a0 [117.004450] stack backtrace: [117.004453] CPU: 0 UID: 0 PID: 0 Comm: swapper/0 Tainted: G S U 6.17.0-rc7-CI_DRM_17270-g7644974e648c+ #1 PREEMPT(voluntary) [117.004455] Tainted: [S]=CPU_OUT_OF_SPEC, [U]=USER [117.004455] Hardware name: Intel Corporation Alder Lake Client Platform/AlderLake-P DDR4 RVP, BIOS RPLPFWI1.R00.4035.A00.2301200723 01/20/2023 [117.004456] Call Trace: [117.004456] [117.004457] dump_stack_lvl+0x91/0xf0 [117.004460] dump_stack+0x10/0x20 [117.004461] print_usage_bug.part.0+0x260/0x360 [117.004463] mark_lock+0x76e/0x9c0 [117.004465] ? register_lock_class+0x48/0x4a0 [117.004467] __lock_acquire+0xbc3/0x2860 [117.004469] lock_acquire+0xc4/0x2e0 [117.004470] ? __timer_delete_sync+0x4b/0x190 [117.004472] ? __timer_delete_sync+0x4b/0x190 [117.004473] __timer_delete_sync+0x68/0x190 [117.004474] ? __timer_delete_sync+0x4b/0x190 [117.004475] timer_delete_sync+0x10/0x20 [117.004476] vgem_fence_release+0x19/0x30 [vgem] [117.004478] dma_fence_release+0xc1/0x3b0 [117.004480] ? dma_fence_release+0xa1/0x3b0 [117.004481] dma_fence_chain_release+0xe7/0x130 [117.004483] dma_fence_release+0xc1/0x3b0 [117.004484] ? _raw_spin_unlock_irqrestore+0x27/0x80 [117.004485] dma_fence_chain_irq_work+0x59/0x80 [117.004487] irq_work_single+0x75/0xa0 [117.004490] irq_work_run_list+0x33/0x60 [117.004491] irq_work_run+0x18/0x40 [117.004493] __sysvec_irq_work+0x35/0x170 [117.004494] sysvec_irq_work+0x47/0xc0 [117.004496] asm_sysvec_irq_work+0x1b/0x20 [117.004497] RIP: 0010:_raw_spin_unlock_irqrestore+0x57/0x80 [117.004499] Code: 00 75 1c 65 ff 0d d9 34 68 01 74 20 5b 41 5c 5d 31 c0 31 d2 31 c9 31 f6 31 ff c3 cc cc cc cc e8 7f 9d d3 fe fb 0f 1f 44 00 00 d7 0f 1f 44 00 00 5b 41 5c 5d 31 c0 31 d2 31 c9 31 f6 31 ff c3 [117.004499] RSP: 0018:ffffc90000003cf0 EFLAGS: 00000246 [117.004500] RAX: 0000000000000000 RBX: ffff888155e94c40 RCX: 0000000000000000 [117.004501] RDX: 0000000000000000 RSI: 0000000000000000 RDI: 0000000000000000 [117.004502] RBP: ffffc90000003d00 R08: 0000000000000000 R09: 0000000000000000 [117.004502] R10: 0000000000000000 R11: 0000000000000000 R12: 0000000000000246 [117.004502] R13: 0000000000000001 R14: 0000000000000246 R15: ffff888155e94c80 [117.004506] dma_fence_signal+0x49/0xb0 [117.004507] ? __pfx_vgem_fence_timeout+0x10/0x10 [vgem] [117.004508] vgem_fence_timeout+0x12/0x20 [vgem] [117.004509] call_timer_fn+0xa1/0x2a0 [117.004512] ? __pfx_vgem_fence_timeout+0x10/0x10 [vgem] [117.004513] __run_timers+0x231/0x310 [117.004514] ? tmigr_handle_remote+0x2ac/0x560 [117.004517] timer_expire_remote+0x46/0x70 [117.004518] tmigr_handle_remote+0x433/0x560 [117.004520] ? __run_timers+0x239/0x310 [117.004521] ? run_timer_softirq+0x21/0xe0 [117.004522] ? lock_release+0xce/0x2a0 [117.004524] run_timer_softirq+0xcf/0xe0 [117.004525] handle_softirqs+0xd4/0x4d0 [117.004526] __irq_exit_rcu+0x13f/0x160 [117.004527] irq_exit_rcu+0xe/0x20 [117.004528] sysvec_apic_timer_interrupt+0xa0/0xc0 [117.004529] [117.004529] [117.004529] asm_sysvec_apic_timer_interrupt+0x1b/0x20 [117.004530] RIP: 0010:cpuidle_enter_state+0x12b/0x8a0 [117.004532] Code: 48 0f a3 05 97 ce 0e 01 0f 82 2e 03 00 00 31 ff e8 8a 41 bd fe 80 7d d0 00 0f 85 11 03 00 00 e8 8b 06 d5 fe fb 0f 1f 44 00 00 <45> 85 f6 0f 88 67 02 00 00 4d 63 ee 49 83 fd 0a 0f 83 34 06 00 00 [117.004532] RSP: 0018:ffffffff83403d88 EFLAGS: 00000246 [117.004533] RAX: 0000000000000000 RBX: ffff88888f046440 RCX: 0000000000000000 [117.004533] RDX: 0000000000000000 RSI: 0000000000000000 RDI: 0000000000000000 [117.004534] RBP: ffffffff83403dd8 R08: 0000000000000000 R09: 0000000000000000 [117.004534] R10: 0000000000000000 R11: 0000000000000000 R12: ffffffff837cbe80 [117.004534] R13: 0000000000000004 R14: 0000000000000004 R15: 0000001ad1df466b [117.004537] ? cpuidle_enter_state+0x125/0x8a0 [117.004538] ? sched_clock_noinstr+0x9/0x10 [117.004540] cpuidle_enter+0x2e/0x50 [117.004542] call_cpuidle+0x22/0x60 [117.004542] do_idle+0x1fd/0x260 [117.004544] cpu_startup_entry+0x29/0x30 [117.004546] rest_init+0x104/0x200 [117.004548] start_kernel+0x93d/0xbd0 [117.004550] ? load_ucode_intel_bsp+0x2a/0x90 [117.004551] ? sme_unmap_bootdata+0x14/0x80 [117.004554] x86_64_start_reservations+0x18/0x30 [117.004555] x86_64_start_kernel+0xfd/0x150 [117.004556] ? soft_restart_cpu+0x14/0x14 [117.004558] common_startup_64+0x13e/0x141 [117.004560] [117.004565] ------------[ cut here ]------------ [117.004692] WARNING: CPU: 0 PID: 0 at kernel/time/timer.c:1610 __timer_delete_sync+0x126/0x190 [117.004697] Modules linked in: vgem snd_hda_codec_intelhdmi snd_hda_codec_hdmi i915 prime_numbers ttm drm_buddy drm_display_helper cec rc_core i2c_algo_bit hid_sensor_custom hid_sensor_hub hid_generic intel_ishtp_hid hid intel_uncore_frequency intel_uncore_frequency_common x86_pkg_temp_thermal intel_powerclamp cmdlinepart ee1004 r8153_ecm spi_nor coretemp cdc_ether mei_pxp mei_hdcp usbnet mtd intel_rapl_msr wmi_bmof kvm_intel snd_hda_intel snd_intel_dspcfg processor_thermal_device_pci kvm snd_hda_codec processor_thermal_device irqbypass processor_thermal_wt_hint polyval_clmulni platform_temperature_control snd_hda_core ghash_clmulni_intel processor_thermal_rfim spi_pxa2xx_platform snd_hwdep aesni_intel processor_thermal_rapl dw_dmac snd_pcm dw_dmac_core intel_rapl_common r8152 rapl mii intel_cstate spi_pxa2xx_core i2c_i801 processor_thermal_wt_req snd_timer i2c_mux mei_me intel_ish_ipc processor_thermal_power_floor e1000e snd i2c_smbus spi_intel_pci processor_thermal_mbox mei soundcore intel_ishtp thunderbolt idma64 [117.004733] spi_intel int340x_thermal_zone igen6_edac binfmt_misc intel_skl_int3472_tps68470 intel_pmc_core tps68470_regulator video clk_tps68470 pmt_telemetry pmt_discovery nls_iso8859_1 pmt_class intel_pmc_ssram_telemetry intel_skl_int3472_discrete int3400_thermal intel_hid intel_skl_int3472_common acpi_thermal_rel intel_vsec wmi pinctrl_tigerlake acpi_tad sparse_keymap acpi_pad dm_multipath msr nvme_fabrics fuse efi_pstore nfnetlink autofs4 [117.004782] CPU: 0 UID: 0 PID: 0 Comm: swapper/0 Tainted: G S U 6.17.0-rc7-CI_DRM_17270-g7644974e648c+ #1 PREEMPT(voluntary) [117.004787] Tainted: [S]=CPU_OUT_OF_SPEC, [U]=USER [117.004789] Hardware name: Intel Corporation Alder Lake Client Platform/AlderLake-P DDR4 RVP, BIOS RPLPFWI1.R00.4035.A00.2301200723 01/20/2023 [117.004793] RIP: 0010:__timer_delete_sync+0x126/0x190 [117.004795] Code: 31 c0 45 31 c9 c3 cc cc cc cc 48 8b 75 d0 45 84 f6 74 63 49 c7 45 18 00 00 00 00 48 89 c7 e8 51 46 39 01 f3 90 e9 66 ff ff ff <0f> 0b e9 5f ff ff ff e8 ee e4 0c 00 49 8d 5d 28 45 31 c9 31 c9 4c [117.004801] RSP: 0018:ffffc90000003a40 EFLAGS: 00010046 [117.004804] RAX: ffffffff815093fb RBX: ffff888138f86aa8 RCX: 0000000000000000 [117.004807] RDX: 0000000000000000 RSI: 0000000000000000 RDI: 0000000000000000 [117.004809] RBP: ffffc90000003a70 R08: 0000000000000000 R09: 0000000000000000 [117.004812] R10: 0000000000000000 R11: 0000000000000000 R12: ffffffff815093fb [117.004814] R13: ffff888138f86a80 R14: 0000000000000000 R15: 0000000000000000 [117.004817] FS: 0000000000000000(0000) GS:ffff88890b0f7000(0000) knlGS:0000000000000000 [117.004820] CS: 0010 DS: 0000 ES: 0000 CR0: 0000000080050033 [117.004823] CR2: 00005db8131eb7f0 CR3: 0000000003448000 CR4: 0000000000f52ef0 [117.004826] PKRU: 55555554 [117.004827] Call Trace: [117.004829] [117.004831] timer_delete_sync+0x10/0x20 [117.004833] vgem_fence_release+0x19/0x30 [vgem] [117.004836] dma_fence_release+0xc1/0x3b0 [117.004838] ? dma_fence_release+0xa1/0x3b0 [117.004841] dma_fence_chain_release+0xe7/0x130 [117.004844] dma_fence_release+0xc1/0x3b0 [117.004847] ? _raw_spin_unlock_irqrestore+0x27/0x80 [117.004850] dma_fence_chain_irq_work+0x59/0x80 [117.004853] irq_work_single+0x75/0xa0 [117.004857] irq_work_run_list+0x33/0x60 [117.004860] irq_work_run+0x18/0x40 [117.004863] __sysvec_irq_work+0x35/0x170 [117.004865] sysvec_irq_work+0x47/0xc0 [117.004868] asm_sysvec_irq_work+0x1b/0x20 [117.004871] RIP: 0010:_raw_spin_unlock_irqrestore+0x57/0x80 [117.004874] Code: 00 75 1c 65 ff 0d d9 34 68 01 74 20 5b 41 5c 5d 31 c0 31 d2 31 c9 31 f6 31 ff c3 cc cc cc cc e8 7f 9d d3 fe fb 0f 1f 44 00 00 d7 0f 1f 44 00 00 5b 41 5c 5d 31 c0 31 d2 31 c9 31 f6 31 ff c3 [117.004879] RSP: 0018:ffffc90000003cf0 EFLAGS: 00000246 [117.004882] RAX: 0000000000000000 RBX: ffff888155e94c40 RCX: 0000000000000000 [117.004884] RDX: 0000000000000000 RSI: 0000000000000000 RDI: 0000000000000000 [117.004887] RBP: ffffc90000003d00 R08: 0000000000000000 R09: 0000000000000000 [117.004890] R10: 0000000000000000 R11: 0000000000000000 R12: 0000000000000246 [117.004892] R13: 0000000000000001 R14: 0000000000000246 R15: ffff888155e94c80 [117.004897] dma_fence_signal+0x49/0xb0 [117.004899] ? __pfx_vgem_fence_timeout+0x10/0x10 [vgem] [117.004902] vgem_fence_timeout+0x12/0x20 [vgem] [117.004904] call_timer_fn+0xa1/0x2a0 [117.004908] ? __pfx_vgem_fence_timeout+0x10/0x10 [vgem] [117.004910] __run_timers+0x231/0x310 [117.004913] ? tmigr_handle_remote+0x2ac/0x560 [117.004917] timer_expire_remote+0x46/0x70 [117.004919] tmigr_handle_remote+0x433/0x560 [117.004923] ? __run_timers+0x239/0x310 [117.004925] ? run_timer_softirq+0x21/0xe0 [117.004928] ? lock_release+0xce/0x2a0 [117.004931] run_timer_softirq+0xcf/0xe0 [117.004933] handle_softirqs+0xd4/0x4d0 [117.004936] __irq_exit_rcu+0x13f/0x160 [117.004938] irq_exit_rcu+0xe/0x20 [117.004940] sysvec_apic_timer_interrupt+0xa0/0xc0 [117.004943] [117.004944] [117.004946] asm_sysvec_apic_timer_interrupt+0x1b/0x20 [117.004949] RIP: 0010:cpuidle_enter_state+0x12b/0x8a0 [117.004953] Code: 48 0f a3 05 97 ce 0e 01 0f 82 2e 03 00 00 31 ff e8 8a 41 bd fe 80 7d d0 00 0f 85 11 03 00 00 e8 8b 06 d5 fe fb 0f 1f 44 00 00 <45> 85 f6 0f 88 67 02 00 00 4d 63 ee 49 83 fd 0a 0f 83 34 06 00 00 [117.004961] RSP: 0018:ffffffff83403d88 EFLAGS: 00000246 [117.004963] RAX: 0000000000000000 RBX: ffff88888f046440 RCX: 0000000000000000 [117.004966] RDX: 0000000000000000 RSI: 0000000000000000 RDI: 0000000000000000 [117.004968] RBP: ffffffff83403dd8 R08: 0000000000000000 R09: 0000000000000000 [117.004971] R10: 0000000000000000 R11: 0000000000000000 R12: ffffffff837cbe80 [117.004974] R13: 0000000000000004 R14: 0000000000000004 R15: 0000001ad1df466b [117.004978] ? cpuidle_enter_state+0x125/0x8a0 [117.004981] ? sched_clock_noinstr+0x9/0x10 [117.004985] cpuidle_enter+0x2e/0x50 [117.004989] call_cpuidle+0x22/0x60 [117.004991] do_idle+0x1fd/0x260 [117.005001] cpu_startup_entry+0x29/0x30 [117.005004] rest_init+0x104/0x200 [117.005008] start_kernel+0x93d/0xbd0 [117.005011] ? load_ucode_intel_bsp+0x2a/0x90 [117.005014] ? sme_unmap_bootdata+0x14/0x80 [117.005017] x86_64_start_reservations+0x18/0x30 [117.005020] x86_64_start_kernel+0xfd/0x150 [117.005023] ? soft_restart_cpu+0x14/0x14 [117.005026] common_startup_64+0x13e/0x141 [117.005030] [117.005032] irq event stamp: 2282669 [117.005034] hardirqs last enabled at (2282668): [] _raw_spin_unlock_irqrestore+0x51/0x80 [117.005038] hardirqs last disabled at (2282669): [] sysvec_irq_work+0x11/0xc0 [117.005043] softirqs last enabled at (2254702): [] __do_softirq+0x10/0x18 [117.005047] softirqs last disabled at (2254725): [] __irq_exit_rcu+0x13f/0x160 [117.005051] ---[ end trace 0000000000000000 ]--- Make the timer IRQ safe. [1] https://patchwork.freedesktop.org/series/154987/#rev2 Fixes: 4077798484459 ("drm/vgem: Attach sw fences to exported vGEM dma-buf (ioctl)") Signed-off-by: Janusz Krzysztofik Reviewed-by: Christian König Link: https://lore.kernel.org/r/20250926152628.2165080-2-janusz.krzysztofik@linux.intel.com Signed-off-by: Maarten Lankhorst Signed-off-by: Sasha Levin --- drivers/gpu/drm/vgem/vgem_fence.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/gpu/drm/vgem/vgem_fence.c b/drivers/gpu/drm/vgem/vgem_fence.c index fd76730fd38c07..07db319c3d7f9c 100644 --- a/drivers/gpu/drm/vgem/vgem_fence.c +++ b/drivers/gpu/drm/vgem/vgem_fence.c @@ -79,7 +79,7 @@ static struct dma_fence *vgem_fence_create(struct vgem_file *vfile, dma_fence_init(&fence->base, &vgem_fence_ops, &fence->lock, dma_fence_context_alloc(1), 1); - timer_setup(&fence->timer, vgem_fence_timeout, 0); + timer_setup(&fence->timer, vgem_fence_timeout, TIMER_IRQSAFE); /* We force the fence to expire within 10s to prevent driver hangs */ mod_timer(&fence->timer, jiffies + VGEM_FENCE_TIMEOUT); From 20798610f0e2c91a8deccda8c3b597269af72557 Mon Sep 17 00:00:00 2001 From: Siddharth Chintamaneni Date: Wed, 1 Oct 2025 17:27:02 +0000 Subject: [PATCH 0387/3902] bpf: Cleanup unused func args in rqspinlock implementation [ Upstream commit 56b4d162392dda2365fbc1f482184a24b489d07d ] cleanup unused function args in check_deadlock* functions. Fixes: 31158ad02ddb ("rqspinlock: Add deadlock detection and recovery") Signed-off-by: Siddharth Chintamaneni Reviewed-by: Eduard Zingerman Acked-by: Kumar Kartikeya Dwivedi Link: https://lore.kernel.org/r/20251001172702.122838-1-sidchintamaneni@gmail.com Signed-off-by: Alexei Starovoitov Signed-off-by: Sasha Levin --- kernel/bpf/rqspinlock.c | 19 ++++++++----------- 1 file changed, 8 insertions(+), 11 deletions(-) diff --git a/kernel/bpf/rqspinlock.c b/kernel/bpf/rqspinlock.c index a00561b1d3e515..21be48108e9620 100644 --- a/kernel/bpf/rqspinlock.c +++ b/kernel/bpf/rqspinlock.c @@ -89,15 +89,14 @@ struct rqspinlock_timeout { DEFINE_PER_CPU_ALIGNED(struct rqspinlock_held, rqspinlock_held_locks); EXPORT_SYMBOL_GPL(rqspinlock_held_locks); -static bool is_lock_released(rqspinlock_t *lock, u32 mask, struct rqspinlock_timeout *ts) +static bool is_lock_released(rqspinlock_t *lock, u32 mask) { if (!(atomic_read_acquire(&lock->val) & (mask))) return true; return false; } -static noinline int check_deadlock_AA(rqspinlock_t *lock, u32 mask, - struct rqspinlock_timeout *ts) +static noinline int check_deadlock_AA(rqspinlock_t *lock) { struct rqspinlock_held *rqh = this_cpu_ptr(&rqspinlock_held_locks); int cnt = min(RES_NR_HELD, rqh->cnt); @@ -118,8 +117,7 @@ static noinline int check_deadlock_AA(rqspinlock_t *lock, u32 mask, * more locks, which reduce to ABBA). This is not exhaustive, and we rely on * timeouts as the final line of defense. */ -static noinline int check_deadlock_ABBA(rqspinlock_t *lock, u32 mask, - struct rqspinlock_timeout *ts) +static noinline int check_deadlock_ABBA(rqspinlock_t *lock, u32 mask) { struct rqspinlock_held *rqh = this_cpu_ptr(&rqspinlock_held_locks); int rqh_cnt = min(RES_NR_HELD, rqh->cnt); @@ -142,7 +140,7 @@ static noinline int check_deadlock_ABBA(rqspinlock_t *lock, u32 mask, * Let's ensure to break out of this loop if the lock is available for * us to potentially acquire. */ - if (is_lock_released(lock, mask, ts)) + if (is_lock_released(lock, mask)) return 0; /* @@ -198,15 +196,14 @@ static noinline int check_deadlock_ABBA(rqspinlock_t *lock, u32 mask, return 0; } -static noinline int check_deadlock(rqspinlock_t *lock, u32 mask, - struct rqspinlock_timeout *ts) +static noinline int check_deadlock(rqspinlock_t *lock, u32 mask) { int ret; - ret = check_deadlock_AA(lock, mask, ts); + ret = check_deadlock_AA(lock); if (ret) return ret; - ret = check_deadlock_ABBA(lock, mask, ts); + ret = check_deadlock_ABBA(lock, mask); if (ret) return ret; @@ -234,7 +231,7 @@ static noinline int check_timeout(rqspinlock_t *lock, u32 mask, */ if (prev + NSEC_PER_MSEC < time) { ts->cur = time; - return check_deadlock(lock, mask, ts); + return check_deadlock(lock, mask); } return 0; From d8edc163ccf59547ccdcbb09ede8f4a1afbac582 Mon Sep 17 00:00:00 2001 From: Kumar Kartikeya Dwivedi Date: Tue, 7 Oct 2025 22:03:47 +0000 Subject: [PATCH 0388/3902] bpf: Fix sleepable context for async callbacks [ Upstream commit 469d638d1520a9332cd0d034690e75e845610a51 ] Fix the BPF verifier to correctly determine the sleepable context of async callbacks based on the async primitive type rather than the arming program's context. The bug is in in_sleepable() which uses OR logic to check if the current execution context is sleepable. When a sleepable program arms a timer callback, the callback's state correctly has in_sleepable=false, but in_sleepable() would still return true due to env->prog->sleepable being true. This incorrectly allows sleepable helpers like bpf_copy_from_user() inside timer callbacks when armed from sleepable programs, even though timer callbacks always execute in non-sleepable context. Fix in_sleepable() to rely solely on env->cur_state->in_sleepable, and initialize state->in_sleepable to env->prog->sleepable in do_check_common() for the main program entry. This ensures the sleepable context is properly tracked per verification state rather than being overridden by the program's sleepability. The env->cur_state NULL check in in_sleepable() was only needed for do_misc_fixups() which runs after verification when env->cur_state is set to NULL. Update do_misc_fixups() to use env->prog->sleepable directly for the storage_get_function check, and remove the redundant NULL check from in_sleepable(). Introduce is_async_cb_sleepable() helper to explicitly determine async callback sleepability based on the primitive type: - bpf_timer callbacks are never sleepable - bpf_wq and bpf_task_work callbacks are always sleepable Add verifier_bug() check to catch unhandled async callback types, ensuring future additions cannot be silently mishandled. Move the is_task_work_add_kfunc() forward declaration to the top alongside other callback-related helpers. We update push_async_cb() to adjust to the new changes. At the same time, while simplifying in_sleepable(), we notice a problem in do_misc_fixups. Fix storage_get helpers to use GFP_ATOMIC when called from non-sleepable contexts within sleepable programs, such as bpf_timer callbacks. Currently, the check in do_misc_fixups assumes that env->prog->sleepable, previously in_sleepable(env) which only resolved to this check before last commit, holds across the program's execution, but that is not true. Instead, the func_atomic bit must be set whenever we see the function being called in an atomic context. Previously, this is being done when the helper is invoked in atomic contexts in sleepable programs, we can simply just set the value to true without doing an in_sleepable() check. We must also do a standalone in_sleepable() check to handle cases where the async callback itself is armed from a sleepable program, but is itself non-sleepable (e.g., timer callback) and invokes such a helper, thus needing the func_atomic bit to be true for the said call. Adjust do_misc_fixups() to drop any checks regarding sleepable nature of the program, and just depend on the func_atomic bit to decide which GFP flag to pass. Fixes: 81f1d7a583fa ("bpf: wq: add bpf_wq_set_callback_impl") Fixes: b00fa38a9c1c ("bpf: Enable non-atomic allocations in local storage") Acked-by: Eduard Zingerman Signed-off-by: Kumar Kartikeya Dwivedi Link: https://lore.kernel.org/r/20251007220349.3852807-2-memxor@gmail.com Signed-off-by: Alexei Starovoitov Signed-off-by: Sasha Levin --- kernel/bpf/verifier.c | 41 ++++++++++++++++++++++++++++++----------- 1 file changed, 30 insertions(+), 11 deletions(-) diff --git a/kernel/bpf/verifier.c b/kernel/bpf/verifier.c index fbe4bb91c564ae..460107b0449fec 100644 --- a/kernel/bpf/verifier.c +++ b/kernel/bpf/verifier.c @@ -515,6 +515,7 @@ static bool is_callback_calling_kfunc(u32 btf_id); static bool is_bpf_throw_kfunc(struct bpf_insn *insn); static bool is_bpf_wq_set_callback_impl_kfunc(u32 btf_id); +static bool is_task_work_add_kfunc(u32 func_id); static bool is_sync_callback_calling_function(enum bpf_func_id func_id) { @@ -547,6 +548,21 @@ static bool is_async_callback_calling_insn(struct bpf_insn *insn) (bpf_pseudo_kfunc_call(insn) && is_async_callback_calling_kfunc(insn->imm)); } +static bool is_async_cb_sleepable(struct bpf_verifier_env *env, struct bpf_insn *insn) +{ + /* bpf_timer callbacks are never sleepable. */ + if (bpf_helper_call(insn) && insn->imm == BPF_FUNC_timer_set_callback) + return false; + + /* bpf_wq and bpf_task_work callbacks are always sleepable. */ + if (bpf_pseudo_kfunc_call(insn) && insn->off == 0 && + (is_bpf_wq_set_callback_impl_kfunc(insn->imm) || is_task_work_add_kfunc(insn->imm))) + return true; + + verifier_bug(env, "unhandled async callback in is_async_cb_sleepable"); + return false; +} + static bool is_may_goto_insn(struct bpf_insn *insn) { return insn->code == (BPF_JMP | BPF_JCOND) && insn->src_reg == BPF_MAY_GOTO; @@ -5826,8 +5842,7 @@ static int map_kptr_match_type(struct bpf_verifier_env *env, static bool in_sleepable(struct bpf_verifier_env *env) { - return env->prog->sleepable || - (env->cur_state && env->cur_state->in_sleepable); + return env->cur_state->in_sleepable; } /* The non-sleepable programs and sleepable programs with explicit bpf_rcu_read_lock() @@ -10368,8 +10383,6 @@ typedef int (*set_callee_state_fn)(struct bpf_verifier_env *env, struct bpf_func_state *callee, int insn_idx); -static bool is_task_work_add_kfunc(u32 func_id); - static int set_callee_state(struct bpf_verifier_env *env, struct bpf_func_state *caller, struct bpf_func_state *callee, int insn_idx); @@ -10588,8 +10601,7 @@ static int push_callback_call(struct bpf_verifier_env *env, struct bpf_insn *ins env->subprog_info[subprog].is_async_cb = true; async_cb = push_async_cb(env, env->subprog_info[subprog].start, insn_idx, subprog, - is_bpf_wq_set_callback_impl_kfunc(insn->imm) || - is_task_work_add_kfunc(insn->imm)); + is_async_cb_sleepable(env, insn)); if (!async_cb) return -EFAULT; callee = async_cb->frame[0]; @@ -11428,7 +11440,7 @@ static int check_helper_call(struct bpf_verifier_env *env, struct bpf_insn *insn return -EINVAL; } - if (in_sleepable(env) && is_storage_get_function(func_id)) + if (is_storage_get_function(func_id)) env->insn_aux_data[insn_idx].storage_get_func_atomic = true; } @@ -11439,7 +11451,7 @@ static int check_helper_call(struct bpf_verifier_env *env, struct bpf_insn *insn return -EINVAL; } - if (in_sleepable(env) && is_storage_get_function(func_id)) + if (is_storage_get_function(func_id)) env->insn_aux_data[insn_idx].storage_get_func_atomic = true; } @@ -11450,10 +11462,17 @@ static int check_helper_call(struct bpf_verifier_env *env, struct bpf_insn *insn return -EINVAL; } - if (in_sleepable(env) && is_storage_get_function(func_id)) + if (is_storage_get_function(func_id)) env->insn_aux_data[insn_idx].storage_get_func_atomic = true; } + /* + * Non-sleepable contexts in sleepable programs (e.g., timer callbacks) + * are atomic and must use GFP_ATOMIC for storage_get helpers. + */ + if (!in_sleepable(env) && is_storage_get_function(func_id)) + env->insn_aux_data[insn_idx].storage_get_func_atomic = true; + meta.func_id = func_id; /* check args */ for (i = 0; i < MAX_BPF_FUNC_REG_ARGS; i++) { @@ -22485,8 +22504,7 @@ static int do_misc_fixups(struct bpf_verifier_env *env) } if (is_storage_get_function(insn->imm)) { - if (!in_sleepable(env) || - env->insn_aux_data[i + delta].storage_get_func_atomic) + if (env->insn_aux_data[i + delta].storage_get_func_atomic) insn_buf[0] = BPF_MOV64_IMM(BPF_REG_5, (__force __s32)GFP_ATOMIC); else insn_buf[0] = BPF_MOV64_IMM(BPF_REG_5, (__force __s32)GFP_KERNEL); @@ -23156,6 +23174,7 @@ static int do_check_common(struct bpf_verifier_env *env, int subprog) state->curframe = 0; state->speculative = false; state->branches = 1; + state->in_sleepable = env->prog->sleepable; state->frame[0] = kzalloc(sizeof(struct bpf_func_state), GFP_KERNEL_ACCOUNT); if (!state->frame[0]) { kfree(state); From 19e359b18ae346d444f882eeb30733fed8f04484 Mon Sep 17 00:00:00 2001 From: Mykyta Yatsenko Date: Fri, 10 Oct 2025 17:46:04 +0100 Subject: [PATCH 0389/3902] bpf: Fix handling maps with no BTF and non-constant offsets for the bpf_wq [ Upstream commit 5f8d41172931a92339c5cce81a3142065fa56e45 ] Fix handling maps with no BTF and non-constant offsets for the bpf_wq. This de-duplicates logic with other internal structs (task_work, timer), keeps error reporting consistent, and makes future changes to the layout handling centralized. Fixes: d940c9b94d7e ("bpf: add support for KF_ARG_PTR_TO_WORKQUEUE") Signed-off-by: Mykyta Yatsenko Acked-by: Andrii Nakryiko Acked-by: Eduard Zingerman Link: https://lore.kernel.org/r/20251010164606.147298-1-mykyta.yatsenko5@gmail.com Signed-off-by: Alexei Starovoitov Signed-off-by: Sasha Levin --- kernel/bpf/verifier.c | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/kernel/bpf/verifier.c b/kernel/bpf/verifier.c index 460107b0449fec..52c01c011c6fb7 100644 --- a/kernel/bpf/verifier.c +++ b/kernel/bpf/verifier.c @@ -8479,6 +8479,9 @@ static int check_map_field_pointer(struct bpf_verifier_env *env, u32 regno, case BPF_TASK_WORK: field_off = map->record->task_work_off; break; + case BPF_WORKQUEUE: + field_off = map->record->wq_off; + break; default: verifier_bug(env, "unsupported BTF field type: %s\n", struct_name); return -EINVAL; @@ -8520,13 +8523,17 @@ static int process_wq_func(struct bpf_verifier_env *env, int regno, { struct bpf_reg_state *regs = cur_regs(env), *reg = ®s[regno]; struct bpf_map *map = reg->map_ptr; - u64 val = reg->var_off.value; + int err; - if (map->record->wq_off != val + reg->off) { - verbose(env, "off %lld doesn't point to 'struct bpf_wq' that is at %d\n", - val + reg->off, map->record->wq_off); - return -EINVAL; + err = check_map_field_pointer(env, regno, BPF_WORKQUEUE); + if (err) + return err; + + if (meta->map.ptr) { + verifier_bug(env, "Two map pointers in a bpf_wq helper"); + return -EFAULT; } + meta->map.uid = reg->map_uid; meta->map.ptr = map; return 0; From e506f976c9143ed5920456f3847b102a581edbae Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20Wei=C3=9Fschuh?= Date: Fri, 26 Sep 2025 16:26:58 +0200 Subject: [PATCH 0390/3902] tools/nolibc: handle NULL wstatus argument to waitpid() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [ Upstream commit 812f223fe9be03dc22abb85240b6f075135d2386 ] wstatus is allowed to be NULL. Avoid a segmentation fault in this case. Fixes: 0c89abf5ab3f ("tools/nolibc: implement waitpid() in terms of waitid()") Signed-off-by: Thomas Weißschuh Acked-by: Willy Tarreau Signed-off-by: Thomas Weißschuh Signed-off-by: Sasha Levin --- tools/include/nolibc/sys/wait.h | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/tools/include/nolibc/sys/wait.h b/tools/include/nolibc/sys/wait.h index 4e66e1f7a03e45..9d9319ba92cbd3 100644 --- a/tools/include/nolibc/sys/wait.h +++ b/tools/include/nolibc/sys/wait.h @@ -65,23 +65,29 @@ pid_t waitpid(pid_t pid, int *status, int options) switch (info.si_code) { case 0: - *status = 0; + if (status) + *status = 0; break; case CLD_EXITED: - *status = (info.si_status & 0xff) << 8; + if (status) + *status = (info.si_status & 0xff) << 8; break; case CLD_KILLED: - *status = info.si_status & 0x7f; + if (status) + *status = info.si_status & 0x7f; break; case CLD_DUMPED: - *status = (info.si_status & 0x7f) | 0x80; + if (status) + *status = (info.si_status & 0x7f) | 0x80; break; case CLD_STOPPED: case CLD_TRAPPED: - *status = (info.si_status << 8) + 0x7f; + if (status) + *status = (info.si_status << 8) + 0x7f; break; case CLD_CONTINUED: - *status = 0xffff; + if (status) + *status = 0xffff; break; default: return -1; From caceff35c24ad8daad32cc96294d65a4c5bb1b8d Mon Sep 17 00:00:00 2001 From: Seungjin Bae Date: Sun, 28 Sep 2025 14:56:11 -0400 Subject: [PATCH 0391/3902] USB: Fix descriptor count when handling invalid MBIM extended descriptor [ Upstream commit 5570ad1423ee60f6e972dadb63fb2e5f90a54cbe ] In cdc_parse_cdc_header(), the check for the USB_CDC_MBIM_EXTENDED_TYPE descriptor was using 'break' upon detecting an invalid length. This was incorrect because 'break' only exits the switch statement, causing the code to fall through to cnt++, thus incorrectly incrementing the count of parsed descriptors for a descriptor that was actually invalid and being discarded. This patch changes 'break' to 'goto next_desc;' to ensure that the logic skips the counter increment and correctly proceeds to the next descriptor in the buffer. This maintains an accurate count of only the successfully parsed descriptors. Fixes: e4c6fb7794982 ("usbnet: move the CDC parser into USB core") Signed-off-by: Seungjin Bae Link: https://lore.kernel.org/r/20250928185611.764589-1-eeodqql09@gmail.com Signed-off-by: Greg Kroah-Hartman Signed-off-by: Sasha Levin --- drivers/usb/core/message.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/usb/core/message.c b/drivers/usb/core/message.c index d2b2787be4092e..6138468c67c472 100644 --- a/drivers/usb/core/message.c +++ b/drivers/usb/core/message.c @@ -2431,7 +2431,7 @@ int cdc_parse_cdc_header(struct usb_cdc_parsed_header *hdr, break; case USB_CDC_MBIM_EXTENDED_TYPE: if (elength < sizeof(struct usb_cdc_mbim_extended_desc)) - break; + goto next_desc; hdr->usb_cdc_mbim_extended_desc = (struct usb_cdc_mbim_extended_desc *)buffer; break; From b5b479a13b90a1684f54835e61617d9e1f2f312d Mon Sep 17 00:00:00 2001 From: Ian Rogers Date: Thu, 9 Oct 2025 06:29:11 -0700 Subject: [PATCH 0392/3902] perf bpf_counter: Fix opening of "any"(-1) CPU events [ Upstream commit 2a67955de13624ec17d1c2504d2c9eeb37933b77 ] The bperf BPF counter code doesn't handle "any"(-1) CPU events, always wanting to aggregate a count against a CPU, which avoids the need for atomics so let's not change that. Force evsels used for BPF counters to require a CPU when not in system-wide mode so that the "any"(-1) value isn't used during map propagation and evsel's CPU map matches that of the PMU. Fixes: b91917c0c6fa ("perf bpf_counter: Fix handling of cpumap fixing hybrid") Signed-off-by: Ian Rogers Signed-off-by: Namhyung Kim Signed-off-by: Sasha Levin --- tools/perf/builtin-stat.c | 13 +++++++++++++ tools/perf/util/bpf_counter.c | 7 ++++++- 2 files changed, 19 insertions(+), 1 deletion(-) diff --git a/tools/perf/builtin-stat.c b/tools/perf/builtin-stat.c index 7006f848f87a63..f1c9d6c94fc509 100644 --- a/tools/perf/builtin-stat.c +++ b/tools/perf/builtin-stat.c @@ -2540,6 +2540,7 @@ int cmd_stat(int argc, const char **argv) unsigned int interval, timeout; const char * const stat_subcommands[] = { "record", "report" }; char errbuf[BUFSIZ]; + struct evsel *counter; setlocale(LC_ALL, ""); @@ -2797,6 +2798,18 @@ int cmd_stat(int argc, const char **argv) evlist__warn_user_requested_cpus(evsel_list, target.cpu_list); + evlist__for_each_entry(evsel_list, counter) { + /* + * Setup BPF counters to require CPUs as any(-1) isn't + * supported. evlist__create_maps below will propagate this + * information to the evsels. Note, evsel__is_bperf isn't yet + * set up, and this change must happen early, so directly use + * the bpf_counter variable and target information. + */ + if ((counter->bpf_counter || target.use_bpf) && !target__has_cpu(&target)) + counter->core.requires_cpu = true; + } + if (evlist__create_maps(evsel_list, &target) < 0) { if (target__has_task(&target)) { pr_err("Problems finding threads of monitor\n"); diff --git a/tools/perf/util/bpf_counter.c b/tools/perf/util/bpf_counter.c index ca5d01b9017dba..a5882b5822057b 100644 --- a/tools/perf/util/bpf_counter.c +++ b/tools/perf/util/bpf_counter.c @@ -460,6 +460,7 @@ static int bperf_reload_leader_program(struct evsel *evsel, int attr_map_fd, struct bperf_leader_bpf *skel = bperf_leader_bpf__open(); int link_fd, diff_map_fd, err; struct bpf_link *link = NULL; + struct perf_thread_map *threads; if (!skel) { pr_err("Failed to open leader skeleton\n"); @@ -495,7 +496,11 @@ static int bperf_reload_leader_program(struct evsel *evsel, int attr_map_fd, * following evsel__open_per_cpu call */ evsel->leader_skel = skel; - evsel__open(evsel, evsel->core.cpus, evsel->core.threads); + assert(!perf_cpu_map__has_any_cpu_or_is_empty(evsel->core.cpus)); + /* Always open system wide. */ + threads = thread_map__new_by_tid(-1); + evsel__open(evsel, evsel->core.cpus, threads); + perf_thread_map__put(threads); out: bperf_leader_bpf__destroy(skel); From 0c1e8e2c6e0687647321bf2194c102ecead26b48 Mon Sep 17 00:00:00 2001 From: Abel Vesa Date: Fri, 19 Sep 2025 15:17:11 +0300 Subject: [PATCH 0393/3902] pinctrl: qcom: glymur: Drop unnecessary platform data from match table [ Upstream commit 37e7b536061a33c8f27030e3ffc5a717a6c319e3 ] The platform specific configuration is already passed on to the generic msm probe. So it's useless to exist in the match table next to the compatible. So drop it from match table. Fixes: 87ebcd8baebf ("pinctrl: qcom: Add glymur pinctrl driver") Signed-off-by: Abel Vesa Reviewed-by: Dmitry Baryshkov Signed-off-by: Linus Walleij Signed-off-by: Sasha Levin --- drivers/pinctrl/qcom/pinctrl-glymur.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/pinctrl/qcom/pinctrl-glymur.c b/drivers/pinctrl/qcom/pinctrl-glymur.c index 9913f98e953110..9781e7fcb3a11c 100644 --- a/drivers/pinctrl/qcom/pinctrl-glymur.c +++ b/drivers/pinctrl/qcom/pinctrl-glymur.c @@ -1743,7 +1743,7 @@ static const struct msm_pinctrl_soc_data glymur_tlmm = { }; static const struct of_device_id glymur_tlmm_of_match[] = { - { .compatible = "qcom,glymur-tlmm", .data = &glymur_tlmm }, + { .compatible = "qcom,glymur-tlmm", }, { } }; From e558173196b0e9d4fbff8832c4debad3d5bad6ad Mon Sep 17 00:00:00 2001 From: Abel Vesa Date: Fri, 19 Sep 2025 15:17:12 +0300 Subject: [PATCH 0394/3902] pinctrl: qcom: glymur: Fix the gpio and egpio pin functions [ Upstream commit e73fda2dcb0bad6650e654556c5242b773707257 ] Mark the gpio/egpio as GPIO specific pin functions, othewise the pin muxing generic framework will complain about the gpio being already requested by a different owner. Fixes: 87ebcd8baebf ("pinctrl: qcom: Add glymur pinctrl driver") Signed-off-by: Abel Vesa Reviewed-by: Bartosz Golaszewski Reviewed-by: Dmitry Baryshkov Signed-off-by: Linus Walleij Signed-off-by: Sasha Levin --- drivers/pinctrl/qcom/pinctrl-glymur.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/pinctrl/qcom/pinctrl-glymur.c b/drivers/pinctrl/qcom/pinctrl-glymur.c index 9781e7fcb3a11c..335005084b6bc8 100644 --- a/drivers/pinctrl/qcom/pinctrl-glymur.c +++ b/drivers/pinctrl/qcom/pinctrl-glymur.c @@ -1316,7 +1316,7 @@ static const char *const wcn_sw_ctrl_groups[] = { }; static const struct pinfunction glymur_functions[] = { - MSM_PIN_FUNCTION(gpio), + MSM_GPIO_PIN_FUNCTION(gpio), MSM_PIN_FUNCTION(resout_gpio_n), MSM_PIN_FUNCTION(aoss_cti), MSM_PIN_FUNCTION(asc_cci), @@ -1342,7 +1342,7 @@ static const struct pinfunction glymur_functions[] = { MSM_PIN_FUNCTION(edp0_hot), MSM_PIN_FUNCTION(edp0_lcd), MSM_PIN_FUNCTION(edp1_lcd), - MSM_PIN_FUNCTION(egpio), + MSM_GPIO_PIN_FUNCTION(egpio), MSM_PIN_FUNCTION(eusb_ac_en), MSM_PIN_FUNCTION(gcc_gp1), MSM_PIN_FUNCTION(gcc_gp2), From d07b4af12015cc136f2795bb88ef4d7b869aa9d1 Mon Sep 17 00:00:00 2001 From: Roberto Sassu Date: Wed, 8 Oct 2025 13:35:03 +0200 Subject: [PATCH 0395/3902] ima: Attach CREDS_CHECK IMA hook to bprm_creds_from_file LSM hook [ Upstream commit 8f3fc4f3f8aa6e99266c69cc78bdaa58379e65fc ] Since commit 56305aa9b6fa ("exec: Compute file based creds only once"), the credentials to be applied to the process after execution are not calculated anymore for each step of finding intermediate interpreters (including the final binary), but only after the final binary to be executed without interpreter has been found. In particular, that means that the bprm_check_security LSM hook will not see the updated cred->e[ug]id for the intermediate and for the final binary to be executed, since the function doing this task has been moved from prepare_binprm(), which calls the bprm_check_security hook, to bprm_creds_from_file(). This breaks the IMA expectation for the CREDS_CHECK hook, introduced with commit d906c10d8a31 ("IMA: Support using new creds in appraisal policy"), which expects to evaluate "the credentials that will be committed when the new process is started". This is clearly not the case for the CREDS_CHECK IMA hook, which is attached to bprm_check_security. This issue does not affect systems which load a policy with the BPRM_CHECK hook with no other criteria, as is the case with the built-in "tcb" and/or "appraise_tcb" IMA policies. The "tcb" built-in policy measures all executions regardless of the new credentials, and the "appraise_tcb" policy is written in terms of the file owner, rather than IMA hooks. However, it does affect systems without a BPRM_CHECK policy rule or with a BPRM_CHECK policy rule that does not include what CREDS_CHECK evaluates. As an extreme example, taking a standalone rule like: measure func=CREDS_CHECK euid=0 This will not measure for example sudo (because CREDS_CHECK still sees the bprm->cred->euid set to the regular user UID), but only the subsequent commands after the euid was applied to the children. Make set[ug]id programs measured/appraised again by splitting ima_bprm_check() in two separate hook implementations (CREDS_CHECK now being implemented by ima_creds_check()), and by attaching CREDS_CHECK to the bprm_creds_from_file LSM hook. The limitation of this approach is that CREDS_CHECK will not be invoked anymore for the intermediate interpreters, like it was before, but only for the final binary. This limitation can be removed only by reverting commit 56305aa9b6fa ("exec: Compute file based creds only once"). Link: https://github.com/linux-integrity/linux/issues/3 Fixes: 56305aa9b6fa ("exec: Compute file based creds only once") Cc: Serge E. Hallyn Cc: Matthew Garrett Cc: Eric W. Biederman Cc: Jann Horn Cc: Christian Brauner Cc: Kees Cook Signed-off-by: Roberto Sassu Signed-off-by: Mimi Zohar Signed-off-by: Sasha Levin --- security/integrity/ima/ima_main.c | 42 ++++++++++++++++++++++++------- 1 file changed, 33 insertions(+), 9 deletions(-) diff --git a/security/integrity/ima/ima_main.c b/security/integrity/ima/ima_main.c index cdd225f65a6295..ebaebccfbe9ab1 100644 --- a/security/integrity/ima/ima_main.c +++ b/security/integrity/ima/ima_main.c @@ -573,18 +573,41 @@ static int ima_file_mprotect(struct vm_area_struct *vma, unsigned long reqprot, */ static int ima_bprm_check(struct linux_binprm *bprm) { - int ret; struct lsm_prop prop; security_current_getlsmprop_subj(&prop); - ret = process_measurement(bprm->file, current_cred(), - &prop, NULL, 0, MAY_EXEC, BPRM_CHECK); - if (ret) - return ret; - - security_cred_getlsmprop(bprm->cred, &prop); - return process_measurement(bprm->file, bprm->cred, &prop, NULL, 0, - MAY_EXEC, CREDS_CHECK); + return process_measurement(bprm->file, current_cred(), + &prop, NULL, 0, MAY_EXEC, BPRM_CHECK); +} + +/** + * ima_creds_check - based on policy, collect/store measurement. + * @bprm: contains the linux_binprm structure + * @file: contains the file descriptor of the binary being executed + * + * The OS protects against an executable file, already open for write, + * from being executed in deny_write_access() and an executable file, + * already open for execute, from being modified in get_write_access(). + * So we can be certain that what we verify and measure here is actually + * what is being executed. + * + * The difference from ima_bprm_check() is that ima_creds_check() is invoked + * only after determining the final binary to be executed without interpreter, + * and not when searching for intermediate binaries. The reason is that since + * commit 56305aa9b6fab ("exec: Compute file based creds only once"), the + * credentials to be applied to the process are calculated only at that stage + * (bprm_creds_from_file security hook instead of bprm_check_security). + * + * On success return 0. On integrity appraisal error, assuming the file + * is in policy and IMA-appraisal is in enforcing mode, return -EACCES. + */ +static int ima_creds_check(struct linux_binprm *bprm, const struct file *file) +{ + struct lsm_prop prop; + + security_current_getlsmprop_subj(&prop); + return process_measurement((struct file *)file, bprm->cred, &prop, NULL, + 0, MAY_EXEC, CREDS_CHECK); } /** @@ -1242,6 +1265,7 @@ static int __init init_ima(void) static struct security_hook_list ima_hooks[] __ro_after_init = { LSM_HOOK_INIT(bprm_check_security, ima_bprm_check), LSM_HOOK_INIT(bprm_creds_for_exec, ima_bprm_creds_for_exec), + LSM_HOOK_INIT(bprm_creds_from_file, ima_creds_check), LSM_HOOK_INIT(file_post_open, ima_file_check), LSM_HOOK_INIT(inode_post_create_tmpfile, ima_post_create_tmpfile), LSM_HOOK_INIT(file_release, ima_file_free), From 1d49bdea86e3f84c9eef15d296ad2eec333d8295 Mon Sep 17 00:00:00 2001 From: Biju Das Date: Sun, 21 Sep 2025 12:15:52 +0100 Subject: [PATCH 0396/3902] pinctrl: renesas: rzg2l: Fix PMC restore [ Upstream commit cea950101108b7bfffe26ec4007b8e263a4b56a8 ] PMC restore needs unlocking the register using the PWPR register. Fixes: ede014cd1ea6422d ("pinctrl: renesas: rzg2l: Add function pointer for PMC register write") Signed-off-by: Biju Das Reviewed-by: Geert Uytterhoeven Link: https://patch.msgid.link/20250921111557.103069-2-biju.das.jz@bp.renesas.com Signed-off-by: Geert Uytterhoeven Signed-off-by: Sasha Levin --- drivers/pinctrl/renesas/pinctrl-rzg2l.c | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/drivers/pinctrl/renesas/pinctrl-rzg2l.c b/drivers/pinctrl/renesas/pinctrl-rzg2l.c index f524af6f586f4a..94cb77949f5953 100644 --- a/drivers/pinctrl/renesas/pinctrl-rzg2l.c +++ b/drivers/pinctrl/renesas/pinctrl-rzg2l.c @@ -2993,7 +2993,11 @@ static void rzg2l_pinctrl_pm_setup_regs(struct rzg2l_pinctrl *pctrl, bool suspen * Now cache the registers or set them in the order suggested by * HW manual (section "Operation for GPIO Function"). */ - RZG2L_PCTRL_REG_ACCESS8(suspend, pctrl->base + PMC(off), cache->pmc[port]); + if (suspend) + RZG2L_PCTRL_REG_ACCESS8(suspend, pctrl->base + PMC(off), cache->pmc[port]); + else + pctrl->data->pmc_writeb(pctrl, cache->pmc[port], PMC(off)); + if (has_iolh) { RZG2L_PCTRL_REG_ACCESS32(suspend, pctrl->base + IOLH(off), cache->iolh[0][port]); From f57b5f2ad106a9f7ab5fa4e03bb8fe978d3f31ec Mon Sep 17 00:00:00 2001 From: Marek Vasut Date: Thu, 18 Sep 2025 05:04:43 +0200 Subject: [PATCH 0397/3902] clk: renesas: cpg-mssr: Add missing 1ms delay into reset toggle callback [ Upstream commit 62abfd7bedc2b3d86d4209a4146f9d2b5ae21fab ] R-Car V4H Reference Manual R19UH0186EJ0130 Rev.1.30 Apr. 21, 2025 page 583 Figure 9.3.1(a) Software Reset flow (A) as well as flow (B) / (C) indicate after reset has been asserted by writing a matching reset bit into register SRCR, it is mandatory to wait 1ms. This 1ms delay is documented on R-Car V4H and V4M, it is currently unclear whether S4 is affected as well. This patch does apply the extra delay on R-Car S4 as well. Fix the reset driver to respect the additional delay when toggling resets. Drivers which use separate reset_control_(de)assert() must assure matching delay in their driver code. Fixes: 0ab55cf18341 ("clk: renesas: cpg-mssr: Add support for R-Car V4H") Signed-off-by: Marek Vasut Reviewed-by: Geert Uytterhoeven Link: https://patch.msgid.link/20250918030552.331389-1-marek.vasut+renesas@mailbox.org Signed-off-by: Geert Uytterhoeven Signed-off-by: Sasha Levin --- drivers/clk/renesas/renesas-cpg-mssr.c | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/drivers/clk/renesas/renesas-cpg-mssr.c b/drivers/clk/renesas/renesas-cpg-mssr.c index de1cf7ba45b78b..7063d896249eab 100644 --- a/drivers/clk/renesas/renesas-cpg-mssr.c +++ b/drivers/clk/renesas/renesas-cpg-mssr.c @@ -689,8 +689,15 @@ static int cpg_mssr_reset(struct reset_controller_dev *rcdev, /* Reset module */ writel(bitmask, priv->pub.base0 + priv->reset_regs[reg]); - /* Wait for at least one cycle of the RCLK clock (@ ca. 32 kHz) */ - udelay(35); + /* + * On R-Car Gen4, delay after SRCR has been written is 1ms. + * On older SoCs, delay after SRCR has been written is 35us + * (one cycle of the RCLK clock @ ca. 32 kHz). + */ + if (priv->reg_layout == CLK_REG_LAYOUT_RCAR_GEN4) + usleep_range(1000, 2000); + else + usleep_range(35, 1000); /* Release module from reset state */ writel(bitmask, priv->pub.base0 + priv->reset_clear_regs[reg]); From b1c7a8145137c093e67a36cca42e7e11a25e4c23 Mon Sep 17 00:00:00 2001 From: Marek Vasut Date: Mon, 22 Sep 2025 18:20:38 +0200 Subject: [PATCH 0398/3902] clk: renesas: cpg-mssr: Read back reset registers to assure values latched [ Upstream commit b91401af6c00ffab003698bfabd4c166df30748b ] On R-Car V4H, the PCIEC controller DBI read would generate an SError in case the controller reset is released by writing SRSTCLR register first, and immediately afterward reading some PCIEC controller DBI register. The issue triggers in rcar_gen4_pcie_additional_common_init() on dw_pcie_readl_dbi(dw, PCIE_PORT_LANE_SKEW), which on V4H is the first read after reset_control_deassert(dw->core_rsts[DW_PCIE_PWR_RST].rstc). The reset controller which contains the SRSTCLR register and the PCIEC controller which contains the DBI register share the same root access bus, but the bus then splits into separate segments before reaching each IP. Even if the SRSTCLR write access was posted on the bus before the DBI read access, it seems the DBI read access may reach the PCIEC controller before the SRSTCLR write completed, and trigger the SError. Mitigate the issue by adding a dummy SRSTCLR read, which assures the SRSTCLR write completes fully and is latched into the reset controller, before the PCIEC DBI read access can occur. Fixes: 0ab55cf18341 ("clk: renesas: cpg-mssr: Add support for R-Car V4H") Reviewed-by: Wolfram Sang Tested-by: Geert Uytterhoeven Signed-off-by: Marek Vasut Reviewed-by: Geert Uytterhoeven Link: https://patch.msgid.link/20250922162113.113223-1-marek.vasut+renesas@mailbox.org Signed-off-by: Geert Uytterhoeven Signed-off-by: Sasha Levin --- drivers/clk/renesas/renesas-cpg-mssr.c | 46 ++++++++++++-------------- 1 file changed, 21 insertions(+), 25 deletions(-) diff --git a/drivers/clk/renesas/renesas-cpg-mssr.c b/drivers/clk/renesas/renesas-cpg-mssr.c index 7063d896249eab..a0a68ec0490f7c 100644 --- a/drivers/clk/renesas/renesas-cpg-mssr.c +++ b/drivers/clk/renesas/renesas-cpg-mssr.c @@ -676,18 +676,32 @@ static int __init cpg_mssr_add_clk_domain(struct device *dev, #define rcdev_to_priv(x) container_of(x, struct cpg_mssr_priv, rcdev) -static int cpg_mssr_reset(struct reset_controller_dev *rcdev, - unsigned long id) +static int cpg_mssr_reset_operate(struct reset_controller_dev *rcdev, + const char *func, bool set, unsigned long id) { struct cpg_mssr_priv *priv = rcdev_to_priv(rcdev); unsigned int reg = id / 32; unsigned int bit = id % 32; + const u16 off = set ? priv->reset_regs[reg] : priv->reset_clear_regs[reg]; u32 bitmask = BIT(bit); - dev_dbg(priv->dev, "reset %u%02u\n", reg, bit); + if (func) + dev_dbg(priv->dev, "%s %u%02u\n", func, reg, bit); + + writel(bitmask, priv->pub.base0 + off); + readl(priv->pub.base0 + off); + barrier_data(priv->pub.base0 + off); + + return 0; +} + +static int cpg_mssr_reset(struct reset_controller_dev *rcdev, + unsigned long id) +{ + struct cpg_mssr_priv *priv = rcdev_to_priv(rcdev); /* Reset module */ - writel(bitmask, priv->pub.base0 + priv->reset_regs[reg]); + cpg_mssr_reset_operate(rcdev, "reset", true, id); /* * On R-Car Gen4, delay after SRCR has been written is 1ms. @@ -700,36 +714,18 @@ static int cpg_mssr_reset(struct reset_controller_dev *rcdev, usleep_range(35, 1000); /* Release module from reset state */ - writel(bitmask, priv->pub.base0 + priv->reset_clear_regs[reg]); - - return 0; + return cpg_mssr_reset_operate(rcdev, NULL, false, id); } static int cpg_mssr_assert(struct reset_controller_dev *rcdev, unsigned long id) { - struct cpg_mssr_priv *priv = rcdev_to_priv(rcdev); - unsigned int reg = id / 32; - unsigned int bit = id % 32; - u32 bitmask = BIT(bit); - - dev_dbg(priv->dev, "assert %u%02u\n", reg, bit); - - writel(bitmask, priv->pub.base0 + priv->reset_regs[reg]); - return 0; + return cpg_mssr_reset_operate(rcdev, "assert", true, id); } static int cpg_mssr_deassert(struct reset_controller_dev *rcdev, unsigned long id) { - struct cpg_mssr_priv *priv = rcdev_to_priv(rcdev); - unsigned int reg = id / 32; - unsigned int bit = id % 32; - u32 bitmask = BIT(bit); - - dev_dbg(priv->dev, "deassert %u%02u\n", reg, bit); - - writel(bitmask, priv->pub.base0 + priv->reset_clear_regs[reg]); - return 0; + return cpg_mssr_reset_operate(rcdev, "deassert", false, id); } static int cpg_mssr_status(struct reset_controller_dev *rcdev, From ac35eed93429cdfe8b5ad71d167b8dd33bdcaeee Mon Sep 17 00:00:00 2001 From: Cyrille Pitchen Date: Mon, 14 Oct 2024 15:19:42 +0530 Subject: [PATCH 0399/3902] drm: atmel-hlcdc: fix atmel_xlcdc_plane_setup_scaler() [ Upstream commit a312acdcec57b3955fbf1f3057c13a6d38e4aa2a ] On SoCs, like the SAM9X75, which embed the XLCDC ip, the registers that configure the unified scaling engine were not filled with proper values. Indeed, for YCbCr formats, the VXSCFACT bitfield of the HEOCFG25 register and the HXSCFACT bitfield of the HEOCFG27 register were incorrect. For 4:2:0 formats, both vertical and horizontal factors for chroma chanels should be divided by 2 from the factors for the luma channel. Hence: HEOCFG24.VXSYFACT = VFACTOR HEOCFG25.VSXCFACT = VFACTOR / 2 HEOCFG26.HXSYFACT = HFACTOR HEOCFG27.HXSCFACT = HFACTOR / 2 However, for 4:2:2 formats, only the horizontal factor for chroma chanels should be divided by 2 from the factor for the luma channel; the vertical factor is the same for all the luma and chroma channels. Hence: HEOCFG24.VXSYFACT = VFACTOR HEOCFG25.VXSCFACT = VFACTOR HEOCFG26.HXSYFACT = HFACTOR HEOCFG27.HXSCFACT = HFACTOR / 2 Fixes: d498771b0b83 ("drm: atmel_hlcdc: Add support for XLCDC using IP specific driver ops") Signed-off-by: Cyrille Pitchen Reviewed-by: Dmitry Baryshkov Acked-by: Nicolas Ferre Link: https://lore.kernel.org/r/20241014094942.325211-1-manikandan.m@microchip.com Signed-off-by: Manikandan Muralidharan Signed-off-by: Sasha Levin --- .../gpu/drm/atmel-hlcdc/atmel_hlcdc_plane.c | 27 ++++++++++++++++--- 1 file changed, 24 insertions(+), 3 deletions(-) diff --git a/drivers/gpu/drm/atmel-hlcdc/atmel_hlcdc_plane.c b/drivers/gpu/drm/atmel-hlcdc/atmel_hlcdc_plane.c index 4a7ba0918eca19..3787db014501ed 100644 --- a/drivers/gpu/drm/atmel-hlcdc/atmel_hlcdc_plane.c +++ b/drivers/gpu/drm/atmel-hlcdc/atmel_hlcdc_plane.c @@ -365,13 +365,34 @@ void atmel_xlcdc_plane_setup_scaler(struct atmel_hlcdc_plane *plane, xfactor); /* - * With YCbCr 4:2:2 and YCbYcr 4:2:0 window resampling, configuration - * register LCDC_HEOCFG25.VXSCFACT and LCDC_HEOCFG27.HXSCFACT is half + * With YCbCr 4:2:0 window resampling, configuration register + * LCDC_HEOCFG25.VXSCFACT and LCDC_HEOCFG27.HXSCFACT values are half * the value of yfactor and xfactor. + * + * On the other hand, with YCbCr 4:2:2 window resampling, only the + * configuration register LCDC_HEOCFG27.HXSCFACT value is half the value + * of the xfactor; the value of LCDC_HEOCFG25.VXSCFACT is yfactor (no + * division by 2). */ - if (state->base.fb->format->format == DRM_FORMAT_YUV420) { + switch (state->base.fb->format->format) { + /* YCbCr 4:2:2 */ + case DRM_FORMAT_YUYV: + case DRM_FORMAT_UYVY: + case DRM_FORMAT_YVYU: + case DRM_FORMAT_VYUY: + case DRM_FORMAT_YUV422: + case DRM_FORMAT_NV61: + xfactor /= 2; + break; + + /* YCbCr 4:2:0 */ + case DRM_FORMAT_YUV420: + case DRM_FORMAT_NV21: yfactor /= 2; xfactor /= 2; + break; + default: + break; } atmel_hlcdc_layer_write_cfg(&plane->layer, desc->layout.scaler_config + 2, From 4e02cff54ba0a45db8d1a9d472cfcb321bfb017b Mon Sep 17 00:00:00 2001 From: Mavroudis Chatzilazaridis Date: Thu, 2 Oct 2025 19:30:58 +0000 Subject: [PATCH 0400/3902] HID: logitech-hidpp: Do not assume FAP in hidpp_send_message_sync() [ Upstream commit aba7963544d47d82cdf36602a6678a093af0299d ] Currently, hidpp_send_message_sync() retries sending the message when the device returns a busy error code, specifically HIDPP20_ERROR_BUSY, which has a different meaning under RAP. This ends up being a problem because this function is used for both FAP and RAP messages. This issue is not noticeable on older receivers with unreachable devices since they return HIDPP_ERROR_RESOURCE_ERROR (0x09), which is not equal to HIDPP20_ERROR_BUSY (0x08). However, newer receivers return HIDPP_ERROR_UNKNOWN_DEVICE (0x08) which happens to equal to HIDPP20_ERROR_BUSY, causing unnecessary retries when the device is not actually busy. This is resolved by checking if the error response is FAP or RAP and picking the respective ERROR_BUSY code. Fixes: 60165ab774cb ("HID: logitech-hidpp: rework one more time the retries attempts") Signed-off-by: Mavroudis Chatzilazaridis Tested-by: Stuart Hayhurst Signed-off-by: Jiri Kosina Signed-off-by: Sasha Levin --- drivers/hid/hid-logitech-hidpp.c | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/drivers/hid/hid-logitech-hidpp.c b/drivers/hid/hid-logitech-hidpp.c index 5e763de4b94fd4..a88f2e5f791c6f 100644 --- a/drivers/hid/hid-logitech-hidpp.c +++ b/drivers/hid/hid-logitech-hidpp.c @@ -352,10 +352,15 @@ static int hidpp_send_message_sync(struct hidpp_device *hidpp, do { ret = __do_hidpp_send_message_sync(hidpp, message, response); - if (ret != HIDPP20_ERROR_BUSY) + if (response->report_id == REPORT_ID_HIDPP_SHORT && + ret != HIDPP_ERROR_BUSY) + break; + if ((response->report_id == REPORT_ID_HIDPP_LONG || + response->report_id == REPORT_ID_HIDPP_VERY_LONG) && + ret != HIDPP20_ERROR_BUSY) break; - dbg_hid("%s:got busy hidpp 2.0 error %02X, retrying\n", __func__, ret); + dbg_hid("%s:got busy hidpp error %02X, retrying\n", __func__, ret); } while (--max_retries); mutex_unlock(&hidpp->send_mutex); From 80b75beb1619a0015553057ab517a019c9721d83 Mon Sep 17 00:00:00 2001 From: Peng Fan Date: Fri, 26 Sep 2025 20:33:15 +0800 Subject: [PATCH 0401/3902] remoteproc: imx_rproc: Fix runtime PM cleanup and improve remove path [ Upstream commit 80405a34e1f8975cdb2d7d8679bca7f768861035 ] Proper cleanup should be done when rproc_add() fails by invoking both pm_runtime_disable() and pm_runtime_put_noidle() to avoid leaving the device in an inconsistent power state. Fix it by adding pm_runtime_put_noidle() and pm_runtime_disable() in the error path. Also Update the remove() callback to use pm_runtime_put_noidle() instead of pm_runtime_put(), to clearly indicate that only need to restore the usage count. Fixes: a876a3aacc43 ("remoteproc: imx_rproc: detect and attach to pre-booted remote cores") Cc: Ulf Hansson Cc: Hiago De Franco Suggested-by: Ulf Hansson Signed-off-by: Peng Fan Reviewed-by: Ulf Hansson Link: https://lore.kernel.org/r/20250926-imx_rproc_v3-v3-1-4c0ec279cc5f@nxp.com Signed-off-by: Mathieu Poirier Signed-off-by: Sasha Levin --- drivers/remoteproc/imx_rproc.c | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/drivers/remoteproc/imx_rproc.c b/drivers/remoteproc/imx_rproc.c index bb25221a4a8987..8424e6ea5569b9 100644 --- a/drivers/remoteproc/imx_rproc.c +++ b/drivers/remoteproc/imx_rproc.c @@ -1136,11 +1136,16 @@ static int imx_rproc_probe(struct platform_device *pdev) ret = rproc_add(rproc); if (ret) { dev_err(dev, "rproc_add failed\n"); - goto err_put_clk; + goto err_put_pm; } return 0; +err_put_pm: + if (dcfg->method == IMX_RPROC_SCU_API) { + pm_runtime_disable(dev); + pm_runtime_put_noidle(dev); + } err_put_clk: clk_disable_unprepare(priv->clk); err_put_scu: @@ -1160,7 +1165,7 @@ static void imx_rproc_remove(struct platform_device *pdev) if (priv->dcfg->method == IMX_RPROC_SCU_API) { pm_runtime_disable(priv->dev); - pm_runtime_put(priv->dev); + pm_runtime_put_noidle(priv->dev); } clk_disable_unprepare(priv->clk); rproc_del(rproc); From bee781b8c03ed5db1a8c0f471afa991138fe5f5c Mon Sep 17 00:00:00 2001 From: Dylan Hatch Date: Tue, 23 Sep 2025 00:49:41 +0000 Subject: [PATCH 0402/3902] objtool: Fix standalone --hacks=jump_label [ Upstream commit be8374a5ba7cbab6b97df94b4ffe0b92f5c8a6d2 ] The objtool command line 'objtool --hacks=jump_label foo.o' on its own should be expected to rewrite jump labels to NOPs. This means the add_special_section_alts() code path needs to run when only this option is provided. This is mainly relevant in certain debugging situations, but could potentially also fix kernel builds in which objtool is run with --hacks=jump_label but without --orc, --stackval, --uaccess, or --hacks=noinstr. Fixes: de6fbcedf5ab ("objtool: Read special sections with alts only when specific options are selected") Signed-off-by: Dylan Hatch Signed-off-by: Josh Poimboeuf Signed-off-by: Sasha Levin --- tools/objtool/check.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tools/objtool/check.c b/tools/objtool/check.c index 9004fbc0676939..6059a546fb759b 100644 --- a/tools/objtool/check.c +++ b/tools/objtool/check.c @@ -2564,7 +2564,8 @@ static int decode_sections(struct objtool_file *file) * Must be before add_jump_destinations(), which depends on 'func' * being set for alternatives, to enable proper sibling call detection. */ - if (opts.stackval || opts.orc || opts.uaccess || opts.noinstr) { + if (opts.stackval || opts.orc || opts.uaccess || opts.noinstr || + opts.hack_jump_label) { ret = add_special_section_alts(file); if (ret) return ret; From 157d7d16f81714eb278927e210c4c164031ffa92 Mon Sep 17 00:00:00 2001 From: Josh Poimboeuf Date: Wed, 17 Sep 2025 09:03:27 -0700 Subject: [PATCH 0403/3902] objtool: Fix weak symbol detection [ Upstream commit 72567c630d32bc31f671977f78228c80937ed80e ] find_symbol_hole_containing() fails to find a symbol hole (aka stripped weak symbol) if its section has no symbols before the hole. This breaks weak symbol detection if -ffunction-sections is enabled. Fix that by allowing the interval tree to contain section symbols, which are always at offset zero for a given section. Fixes a bunch of (-ffunction-sections) warnings like: vmlinux.o: warning: objtool: .text.__x64_sys_io_setup+0x10: unreachable instruction Fixes: 4adb23686795 ("objtool: Ignore extra-symbol code") Acked-by: Petr Mladek Tested-by: Joe Lawrence Signed-off-by: Josh Poimboeuf Signed-off-by: Sasha Levin --- tools/objtool/elf.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/tools/objtool/elf.c b/tools/objtool/elf.c index ca5d77db692a23..9cb51fcde7986e 100644 --- a/tools/objtool/elf.c +++ b/tools/objtool/elf.c @@ -108,7 +108,7 @@ struct symbol_hole { }; /* - * Find !section symbol where @offset is after it. + * Find the last symbol before @offset. */ static int symbol_hole_by_offset(const void *key, const struct rb_node *node) { @@ -119,8 +119,7 @@ static int symbol_hole_by_offset(const void *key, const struct rb_node *node) return -1; if (sh->key >= s->offset + s->len) { - if (s->type != STT_SECTION) - sh->sym = s; + sh->sym = s; return 1; } @@ -412,7 +411,8 @@ static void elf_add_symbol(struct elf *elf, struct symbol *sym) sym->len = sym->sym.st_size; __sym_for_each(iter, &sym->sec->symbol_tree, sym->offset, sym->offset) { - if (iter->offset == sym->offset && iter->type == sym->type) + if (iter->offset == sym->offset && iter->type == sym->type && + iter->len == sym->len) iter->alias = sym; } From fe14b92fb97c38ab40216165bab5fa3cd9cbd386 Mon Sep 17 00:00:00 2001 From: "Wludzik, Jozef" Date: Tue, 14 Oct 2025 09:17:25 +0200 Subject: [PATCH 0404/3902] accel/ivpu: Fix race condition when mapping dmabuf [ Upstream commit 63c7870fab67b2ab2bfe75e8b46f3c37b88c47a8 ] Fix a race that can occur when multiple jobs submit the same dmabuf. This could cause the sg_table to be mapped twice, leading to undefined behavior. Fixes: e0c0891cd63b ("accel/ivpu: Rework bind/unbind of imported buffers") Signed-off-by: Wludzik, Jozef Reviewed-by: Jeff Hugo Signed-off-by: Karol Wachowski Link: https://lore.kernel.org/r/20251014071725.3047287-1-karol.wachowski@linux.intel.com Signed-off-by: Sasha Levin --- drivers/accel/ivpu/ivpu_gem.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/drivers/accel/ivpu/ivpu_gem.c b/drivers/accel/ivpu/ivpu_gem.c index 171e809575ad65..1fca969df19dc3 100644 --- a/drivers/accel/ivpu/ivpu_gem.c +++ b/drivers/accel/ivpu/ivpu_gem.c @@ -45,12 +45,13 @@ static inline void ivpu_bo_unlock(struct ivpu_bo *bo) static struct sg_table *ivpu_bo_map_attachment(struct ivpu_device *vdev, struct ivpu_bo *bo) { - struct sg_table *sgt = bo->base.sgt; + struct sg_table *sgt; drm_WARN_ON(&vdev->drm, !bo->base.base.import_attach); ivpu_bo_lock(bo); + sgt = bo->base.sgt; if (!sgt) { sgt = dma_buf_map_attachment(bo->base.base.import_attach, DMA_BIDIRECTIONAL); if (IS_ERR(sgt)) From 27858e7883778919a79c2b5633a22e422f834ee0 Mon Sep 17 00:00:00 2001 From: Ian Rogers Date: Sun, 5 Oct 2025 11:24:04 -0700 Subject: [PATCH 0405/3902] perf parse-events: Fix legacy cache events if event is duplicated in a PMU [ Upstream commit b7b76f607a15f16031001687e733046b5f6f5d86 ] The term list when adding an event to a PMU is expected to have the event name for the alias lookup. Also, set found_supported so that -EINVAL isn't returned. Fixes: 62593394f66a ("perf parse-events: Legacy cache names on all PMUs and lower priority") Tested-by: Thomas Richter Signed-off-by: Ian Rogers Tested-by: James Clark Signed-off-by: Namhyung Kim Signed-off-by: Sasha Levin --- tools/perf/util/parse-events.c | 28 +++++++++++++++++++++++++++- tools/perf/util/parse-events.h | 3 ++- tools/perf/util/parse-events.y | 2 +- 3 files changed, 30 insertions(+), 3 deletions(-) diff --git a/tools/perf/util/parse-events.c b/tools/perf/util/parse-events.c index da73d686f6b937..90a765016f64f3 100644 --- a/tools/perf/util/parse-events.c +++ b/tools/perf/util/parse-events.c @@ -475,8 +475,10 @@ static int parse_events_add_pmu(struct parse_events_state *parse_state, int parse_events_add_cache(struct list_head *list, int *idx, const char *name, struct parse_events_state *parse_state, - struct parse_events_terms *parsed_terms) + struct parse_events_terms *parsed_terms, + void *loc_) { + YYLTYPE *loc = loc_; struct perf_pmu *pmu = NULL; bool found_supported = false; const char *config_name = get_config_name(parsed_terms); @@ -497,12 +499,36 @@ int parse_events_add_cache(struct list_head *list, int *idx, const char *name, * The PMU has the event so add as not a legacy cache * event. */ + struct parse_events_terms temp_terms; + struct parse_events_term *term; + char *config = strdup(name); + + if (!config) + goto out_err; + + parse_events_terms__init(&temp_terms); + if (!parsed_terms) + parsed_terms = &temp_terms; + + if (parse_events_term__num(&term, + PARSE_EVENTS__TERM_TYPE_USER, + config, /*num=*/1, /*novalue=*/true, + loc, /*loc_val=*/NULL) < 0) { + zfree(&config); + goto out_err; + } + list_add(&term->list, &parsed_terms->terms); + ret = parse_events_add_pmu(parse_state, list, pmu, parsed_terms, first_wildcard_match, /*alternate_hw_config=*/PERF_COUNT_HW_MAX); + list_del_init(&term->list); + parse_events_term__delete(term); + parse_events_terms__exit(&temp_terms); if (ret) goto out_err; + found_supported = true; if (first_wildcard_match == NULL) first_wildcard_match = container_of(list->prev, struct evsel, core.node); diff --git a/tools/perf/util/parse-events.h b/tools/perf/util/parse-events.h index 8f8c8e7fbcf186..8af19dec6a17da 100644 --- a/tools/perf/util/parse-events.h +++ b/tools/perf/util/parse-events.h @@ -237,7 +237,8 @@ int parse_events_add_numeric(struct parse_events_state *parse_state, bool wildcard); int parse_events_add_cache(struct list_head *list, int *idx, const char *name, struct parse_events_state *parse_state, - struct parse_events_terms *parsed_terms); + struct parse_events_terms *parsed_terms, + void *loc); int parse_events__decode_legacy_cache(const char *name, int pmu_type, __u64 *config); int parse_events_add_breakpoint(struct parse_events_state *parse_state, struct list_head *list, diff --git a/tools/perf/util/parse-events.y b/tools/perf/util/parse-events.y index a2361c0040d759..ced26c549c33ac 100644 --- a/tools/perf/util/parse-events.y +++ b/tools/perf/util/parse-events.y @@ -353,7 +353,7 @@ PE_LEGACY_CACHE opt_event_config if (!list) YYNOMEM; - err = parse_events_add_cache(list, &parse_state->idx, $1, parse_state, $2); + err = parse_events_add_cache(list, &parse_state->idx, $1, parse_state, $2, &@1); parse_events_terms__delete($2); free($1); From 5248b34df18c2d60fa9f0f5f2101ae00bb8cf0bf Mon Sep 17 00:00:00 2001 From: Danilo Krummrich Date: Fri, 26 Sep 2025 15:05:52 +0200 Subject: [PATCH 0406/3902] gpu: nova-core: gsp: remove useless conversion [ Upstream commit 87990025b87283f1b8c50d4d75379ca6d86d2211 ] Because nova-core depends on CONFIG_64BIT and a raw DmaAddress is always a u64, we can remove the now actually useless conversion. Signed-off-by: Danilo Krummrich Reviewed-by: John Hubbard [acourbot@nvidia.com: reword commit as suggested by John.] Signed-off-by: Alexandre Courbot Message-ID: <20250926130623.61316-1-dakr@kernel.org> Stable-dep-of: f7a33a67c50c ("gpu: nova-core: gsp: do not unwrap() SGEntry") Signed-off-by: Sasha Levin --- drivers/gpu/nova-core/firmware/gsp.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/drivers/gpu/nova-core/firmware/gsp.rs b/drivers/gpu/nova-core/firmware/gsp.rs index 9b70095434c61f..ca785860e1c82a 100644 --- a/drivers/gpu/nova-core/firmware/gsp.rs +++ b/drivers/gpu/nova-core/firmware/gsp.rs @@ -202,8 +202,7 @@ impl GspFirmware { let mut level0_data = kvec![0u8; GSP_PAGE_SIZE]?; // Fill level 1 page entry. - #[allow(clippy::useless_conversion)] - let level1_entry = u64::from(level1.iter().next().unwrap().dma_address()); + let level1_entry = level1.iter().next().unwrap().dma_address(); let dst = &mut level0_data[..size_of_val(&level1_entry)]; dst.copy_from_slice(&level1_entry.to_le_bytes()); From 72f8a15524f315ba5594d35511b2926a8a42fb46 Mon Sep 17 00:00:00 2001 From: Danilo Krummrich Date: Fri, 26 Sep 2025 15:05:53 +0200 Subject: [PATCH 0407/3902] gpu: nova-core: gsp: do not unwrap() SGEntry [ Upstream commit f7a33a67c50c92589b046e69b9075b7d28d31f87 ] Don't use unwrap() to extract an Option, instead handle the error condition gracefully. Fixes: a841614e607c ("gpu: nova-core: firmware: process and prepare the GSP firmware") Signed-off-by: Danilo Krummrich Reviewed-by: John Hubbard Signed-off-by: Alexandre Courbot Message-ID: <20250926130623.61316-2-dakr@kernel.org> Signed-off-by: Sasha Levin --- drivers/gpu/nova-core/firmware/gsp.rs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/drivers/gpu/nova-core/firmware/gsp.rs b/drivers/gpu/nova-core/firmware/gsp.rs index ca785860e1c82a..6b0761460a57d3 100644 --- a/drivers/gpu/nova-core/firmware/gsp.rs +++ b/drivers/gpu/nova-core/firmware/gsp.rs @@ -202,9 +202,10 @@ impl GspFirmware { let mut level0_data = kvec![0u8; GSP_PAGE_SIZE]?; // Fill level 1 page entry. - let level1_entry = level1.iter().next().unwrap().dma_address(); - let dst = &mut level0_data[..size_of_val(&level1_entry)]; - dst.copy_from_slice(&level1_entry.to_le_bytes()); + let level1_entry = level1.iter().next().ok_or(EINVAL)?; + let level1_entry_addr = level1_entry.dma_address(); + let dst = &mut level0_data[..size_of_val(&level1_entry_addr)]; + dst.copy_from_slice(&level1_entry_addr.to_le_bytes()); // Turn the level0 page table into a [`DmaObject`]. DmaObject::from_data(dev, &level0_data)? From b1497ea246396962156b63d5c568a16d6e32de0b Mon Sep 17 00:00:00 2001 From: Kang Yang Date: Tue, 14 Oct 2025 19:07:57 +0800 Subject: [PATCH 0408/3902] wifi: ath10k: move recovery check logic into a new work MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [ Upstream commit f35a07a4842a88801d9182b1a76d178bfa616978 ] Currently, ath10k has a recovery check logic. It will wait for the last recovery to finish by wait_for_completion_timeout(); But in SDIO scenarios, the recovery function may be invoked from interrupt context, where long blocking waits are undesirable and can lead to system instability. Additionally, Linux’s ordered workqueue processes one task at a time. If a previous recovery is still queued or executing, new triggers are ignored. This prevents accurate tracking of consecutive failures and delays transition to the WEDGED state. To address this, move the recovery check logic into a different workqueue. Tested-on: QCA6174 hw3.2 PCI WLAN.RM.4.4.1-00288-QCARMSWPZ-1 Tested-on: QCA6174 hw3.2 SDIO WLAN.RMH.4.4.1-00189 Fixes: c256a94d1b1b ("wifi: ath10k: shutdown driver when hardware is unreliable") Signed-off-by: Kang Yang Reviewed-by: Baochen Qiang Link: https://patch.msgid.link/20251014110757.155-1-kang.yang@oss.qualcomm.com Signed-off-by: Jeff Johnson Signed-off-by: Sasha Levin --- drivers/net/wireless/ath/ath10k/core.c | 20 +++++++++----------- drivers/net/wireless/ath/ath10k/core.h | 2 +- drivers/net/wireless/ath/ath10k/mac.c | 2 +- 3 files changed, 11 insertions(+), 13 deletions(-) diff --git a/drivers/net/wireless/ath/ath10k/core.c b/drivers/net/wireless/ath/ath10k/core.c index 6f78f1752cd6ff..9ae3595fb6986d 100644 --- a/drivers/net/wireless/ath/ath10k/core.c +++ b/drivers/net/wireless/ath/ath10k/core.c @@ -3,7 +3,6 @@ * Copyright (c) 2005-2011 Atheros Communications Inc. * Copyright (c) 2011-2017 Qualcomm Atheros, Inc. * Copyright (c) 2018-2019, The Linux Foundation. All rights reserved. - * Copyright (c) 2021-2024 Qualcomm Innovation Center, Inc. All rights reserved. * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries. */ @@ -2493,8 +2492,9 @@ static int ath10k_init_hw_params(struct ath10k *ar) return 0; } -static bool ath10k_core_needs_recovery(struct ath10k *ar) +static void ath10k_core_recovery_check_work(struct work_struct *work) { + struct ath10k *ar = container_of(work, struct ath10k, recovery_check_work); long time_left; /* Sometimes the recovery will fail and then the next all recovery fail, @@ -2504,7 +2504,7 @@ static bool ath10k_core_needs_recovery(struct ath10k *ar) ath10k_err(ar, "consecutive fail %d times, will shutdown driver!", atomic_read(&ar->fail_cont_count)); ar->state = ATH10K_STATE_WEDGED; - return false; + return; } ath10k_dbg(ar, ATH10K_DBG_BOOT, "total recovery count: %d", ++ar->recovery_count); @@ -2518,27 +2518,24 @@ static bool ath10k_core_needs_recovery(struct ath10k *ar) ATH10K_RECOVERY_TIMEOUT_HZ); if (time_left) { ath10k_warn(ar, "previous recovery succeeded, skip this!\n"); - return false; + return; } /* Record the continuous recovery fail count when recovery failed. */ atomic_inc(&ar->fail_cont_count); /* Avoid having multiple recoveries at the same time. */ - return false; + return; } atomic_inc(&ar->pending_recovery); - - return true; + queue_work(ar->workqueue, &ar->restart_work); } void ath10k_core_start_recovery(struct ath10k *ar) { - if (!ath10k_core_needs_recovery(ar)) - return; - - queue_work(ar->workqueue, &ar->restart_work); + /* Use workqueue_aux to avoid blocking recovery tracking */ + queue_work(ar->workqueue_aux, &ar->recovery_check_work); } EXPORT_SYMBOL(ath10k_core_start_recovery); @@ -3734,6 +3731,7 @@ struct ath10k *ath10k_core_create(size_t priv_size, struct device *dev, INIT_WORK(&ar->register_work, ath10k_core_register_work); INIT_WORK(&ar->restart_work, ath10k_core_restart); + INIT_WORK(&ar->recovery_check_work, ath10k_core_recovery_check_work); INIT_WORK(&ar->set_coverage_class_work, ath10k_core_set_coverage_class_work); diff --git a/drivers/net/wireless/ath/ath10k/core.h b/drivers/net/wireless/ath/ath10k/core.h index 8c72ed386edb73..859176fcb5a29d 100644 --- a/drivers/net/wireless/ath/ath10k/core.h +++ b/drivers/net/wireless/ath/ath10k/core.h @@ -3,7 +3,6 @@ * Copyright (c) 2005-2011 Atheros Communications Inc. * Copyright (c) 2011-2017 Qualcomm Atheros, Inc. * Copyright (c) 2018-2019, The Linux Foundation. All rights reserved. - * Copyright (c) 2022 Qualcomm Innovation Center, Inc. All rights reserved. * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries. */ @@ -1208,6 +1207,7 @@ struct ath10k { struct work_struct register_work; struct work_struct restart_work; + struct work_struct recovery_check_work; struct work_struct bundle_tx_work; struct work_struct tx_complete_work; diff --git a/drivers/net/wireless/ath/ath10k/mac.c b/drivers/net/wireless/ath/ath10k/mac.c index 154ac7a709824e..da6f7957a0ae7b 100644 --- a/drivers/net/wireless/ath/ath10k/mac.c +++ b/drivers/net/wireless/ath/ath10k/mac.c @@ -3,7 +3,6 @@ * Copyright (c) 2005-2011 Atheros Communications Inc. * Copyright (c) 2011-2017 Qualcomm Atheros, Inc. * Copyright (c) 2018-2019, The Linux Foundation. All rights reserved. - * Copyright (c) 2021-2024 Qualcomm Innovation Center, Inc. All rights reserved. * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries. */ @@ -5428,6 +5427,7 @@ static void ath10k_stop(struct ieee80211_hw *hw, bool suspend) cancel_work_sync(&ar->set_coverage_class_work); cancel_delayed_work_sync(&ar->scan.timeout); cancel_work_sync(&ar->restart_work); + cancel_work_sync(&ar->recovery_check_work); } static int ath10k_config_ps(struct ath10k *ar) From 99fc0689a58802498e9a1a245b43634ba20b856f Mon Sep 17 00:00:00 2001 From: Baochen Qiang Date: Tue, 14 Oct 2025 10:30:20 +0800 Subject: [PATCH 0409/3902] wifi: ath11k: restore register window after global reset [ Upstream commit 596b911644cc19ecba0dbc9c92849fb59390e29a ] Hardware target implements an address space larger than that PCI BAR can map. In order to be able to access the whole target address space, the BAR space is split into 4 segments, of which the last 3, called windows, can be dynamically mapped to the desired area. This is achieved by updating window register with appropriate window value. Currently each time when accessing a register that beyond ATH11K_PCI_WINDOW_START, host calculates the window value and caches it after window update, this way next time when accessing a register falling in the same window, host knows that the window is already good hence no additional update needed. However this mechanism breaks after global reset is triggered in ath11k_pci_soc_global_reset(), because with global reset hardware resets window register hence the window is not properly mapped any more. Current host does nothing about this, as a result a subsequent register access may not work as expected if it falls in a window same as before. Although there is no obvious issue seen now, better to fix it to avoid future problem. The fix is done by restoring the window register after global reset. Tested-on: WCN6855 hw2.0 PCI WLAN.HSP.1.1-03125-QCAHSPSWPL_V1_V2_SILICONZ_LITE-3.6510.30 Fixes: d5c65159f289 ("ath11k: driver for Qualcomm IEEE 802.11ax devices") Signed-off-by: Baochen Qiang Reviewed-by: Vasanthakumar Thiagarajan Link: https://patch.msgid.link/20251014-ath11k-reset-window-cache-v1-1-b85271b111dd@oss.qualcomm.com Signed-off-by: Jeff Johnson Signed-off-by: Sasha Levin --- drivers/net/wireless/ath/ath11k/pci.c | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/drivers/net/wireless/ath/ath11k/pci.c b/drivers/net/wireless/ath/ath11k/pci.c index d8655badd96d0f..7114eca8810dbf 100644 --- a/drivers/net/wireless/ath/ath11k/pci.c +++ b/drivers/net/wireless/ath/ath11k/pci.c @@ -1,7 +1,7 @@ // SPDX-License-Identifier: BSD-3-Clause-Clear /* * Copyright (c) 2019-2020 The Linux Foundation. All rights reserved. - * Copyright (c) 2021-2025 Qualcomm Innovation Center, Inc. All rights reserved. + * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries. */ #include @@ -177,6 +177,19 @@ static inline void ath11k_pci_select_static_window(struct ath11k_pci *ab_pci) ab_pci->ab->mem + ATH11K_PCI_WINDOW_REG_ADDRESS); } +static void ath11k_pci_restore_window(struct ath11k_base *ab) +{ + struct ath11k_pci *ab_pci = ath11k_pci_priv(ab); + + spin_lock_bh(&ab_pci->window_lock); + + iowrite32(ATH11K_PCI_WINDOW_ENABLE_BIT | ab_pci->register_window, + ab->mem + ATH11K_PCI_WINDOW_REG_ADDRESS); + ioread32(ab->mem + ATH11K_PCI_WINDOW_REG_ADDRESS); + + spin_unlock_bh(&ab_pci->window_lock); +} + static void ath11k_pci_soc_global_reset(struct ath11k_base *ab) { u32 val, delay; @@ -201,6 +214,11 @@ static void ath11k_pci_soc_global_reset(struct ath11k_base *ab) val = ath11k_pcic_read32(ab, PCIE_SOC_GLOBAL_RESET); if (val == 0xffffffff) ath11k_warn(ab, "link down error during global reset\n"); + + /* Restore window register as its content is cleared during + * hardware global reset, such that it aligns with host cache. + */ + ath11k_pci_restore_window(ab); } static void ath11k_pci_clear_dbg_registers(struct ath11k_base *ab) From ab0554f51e5f2b9506e8a09e8accd02f00056729 Mon Sep 17 00:00:00 2001 From: Sarika Sharma Date: Tue, 30 Sep 2025 14:45:50 +0530 Subject: [PATCH 0410/3902] wifi: ath12k: Fix MSDU buffer types handling in RX error path [ Upstream commit 36f9edbb9d0fc36c865c74f3c1ad8e1261ad3981 ] Currently, packets received on the REO exception ring from unassociated peers are of MSDU buffer type, while the driver expects link descriptor type packets. These packets are not parsed further due to a return check on packet type in ath12k_hal_desc_reo_parse_err(), but the associated skb is not freed. This may lead to kernel crashes and buffer leaks. Hence to fix, update the RX error handler to explicitly drop MSDU buffer type packets received on the REO exception ring. This prevents further processing of invalid packets and ensures stability in the RX error handling path. Tested-on: QCN9274 hw2.0 PCI WLAN.WBE.1.4.1-00199-QCAHKSWPL_SILICONZ-1 Fixes: d889913205cf ("wifi: ath12k: driver for Qualcomm Wi-Fi 7 devices") Signed-off-by: Sarika Sharma Reviewed-by: Baochen Qiang Reviewed-by: Vasanthakumar Thiagarajan Link: https://patch.msgid.link/20250930091551.3305312-2-sarika.sharma@oss.qualcomm.com Signed-off-by: Jeff Johnson Signed-off-by: Sasha Levin --- drivers/net/wireless/ath/ath12k/dp_rx.c | 70 ++++++++++++++++++++++-- drivers/net/wireless/ath/ath12k/hal_rx.c | 10 +--- 2 files changed, 66 insertions(+), 14 deletions(-) diff --git a/drivers/net/wireless/ath/ath12k/dp_rx.c b/drivers/net/wireless/ath/ath12k/dp_rx.c index 5e5c14a70316de..99d29eda26cf1d 100644 --- a/drivers/net/wireless/ath/ath12k/dp_rx.c +++ b/drivers/net/wireless/ath/ath12k/dp_rx.c @@ -1,7 +1,7 @@ // SPDX-License-Identifier: BSD-3-Clause-Clear /* * Copyright (c) 2018-2021 The Linux Foundation. All rights reserved. - * Copyright (c) 2021-2025 Qualcomm Innovation Center, Inc. All rights reserved. + * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries. */ #include @@ -3781,6 +3781,48 @@ ath12k_dp_process_rx_err_buf(struct ath12k *ar, struct hal_reo_dest_ring *desc, return 0; } +static int ath12k_dp_h_msdu_buffer_type(struct ath12k_base *ab, + struct list_head *list, + struct hal_reo_dest_ring *desc) +{ + struct ath12k_rx_desc_info *desc_info; + struct ath12k_skb_rxcb *rxcb; + struct sk_buff *msdu; + u64 desc_va; + + desc_va = (u64)le32_to_cpu(desc->buf_va_hi) << 32 | + le32_to_cpu(desc->buf_va_lo); + desc_info = (struct ath12k_rx_desc_info *)(uintptr_t)desc_va; + if (!desc_info) { + u32 cookie; + + cookie = le32_get_bits(desc->buf_addr_info.info1, + BUFFER_ADDR_INFO1_SW_COOKIE); + desc_info = ath12k_dp_get_rx_desc(ab, cookie); + if (!desc_info) { + ath12k_warn(ab, "Invalid cookie in manual descriptor retrieval: 0x%x\n", + cookie); + return -EINVAL; + } + } + + if (desc_info->magic != ATH12K_DP_RX_DESC_MAGIC) { + ath12k_warn(ab, "rx exception, magic check failed with value: %u\n", + desc_info->magic); + return -EINVAL; + } + + msdu = desc_info->skb; + desc_info->skb = NULL; + list_add_tail(&desc_info->list, list); + rxcb = ATH12K_SKB_RXCB(msdu); + dma_unmap_single(ab->dev, rxcb->paddr, msdu->len + skb_tailroom(msdu), + DMA_FROM_DEVICE); + dev_kfree_skb_any(msdu); + + return 0; +} + int ath12k_dp_rx_process_err(struct ath12k_base *ab, struct napi_struct *napi, int budget) { @@ -3825,6 +3867,26 @@ int ath12k_dp_rx_process_err(struct ath12k_base *ab, struct napi_struct *napi, drop = false; ab->device_stats.err_ring_pkts++; + hw_link_id = le32_get_bits(reo_desc->info0, + HAL_REO_DEST_RING_INFO0_SRC_LINK_ID); + device_id = hw_links[hw_link_id].device_id; + partner_ab = ath12k_ag_to_ab(ag, device_id); + + /* Below case is added to handle data packet from un-associated clients. + * As it is expected that AST lookup will fail for + * un-associated station's data packets. + */ + if (le32_get_bits(reo_desc->info0, HAL_REO_DEST_RING_INFO0_BUFFER_TYPE) == + HAL_REO_DEST_RING_BUFFER_TYPE_MSDU) { + if (!ath12k_dp_h_msdu_buffer_type(partner_ab, + &rx_desc_used_list[device_id], + reo_desc)) { + num_buffs_reaped[device_id]++; + tot_n_bufs_reaped++; + } + goto next_desc; + } + ret = ath12k_hal_desc_reo_parse_err(ab, reo_desc, &paddr, &desc_bank); if (ret) { @@ -3833,11 +3895,6 @@ int ath12k_dp_rx_process_err(struct ath12k_base *ab, struct napi_struct *napi, continue; } - hw_link_id = le32_get_bits(reo_desc->info0, - HAL_REO_DEST_RING_INFO0_SRC_LINK_ID); - device_id = hw_links[hw_link_id].device_id; - partner_ab = ath12k_ag_to_ab(ag, device_id); - pdev_id = ath12k_hw_mac_id_to_pdev_id(partner_ab->hw_params, hw_links[hw_link_id].pdev_idx); ar = partner_ab->pdevs[pdev_id].ar; @@ -3886,6 +3943,7 @@ int ath12k_dp_rx_process_err(struct ath12k_base *ab, struct napi_struct *napi, } } +next_desc: if (tot_n_bufs_reaped >= quota) { tot_n_bufs_reaped = quota; goto exit; diff --git a/drivers/net/wireless/ath/ath12k/hal_rx.c b/drivers/net/wireless/ath/ath12k/hal_rx.c index 669096278fdd48..c4443ca05cd653 100644 --- a/drivers/net/wireless/ath/ath12k/hal_rx.c +++ b/drivers/net/wireless/ath/ath12k/hal_rx.c @@ -1,7 +1,7 @@ // SPDX-License-Identifier: BSD-3-Clause-Clear /* * Copyright (c) 2018-2021 The Linux Foundation. All rights reserved. - * Copyright (c) 2021-2025 Qualcomm Innovation Center, Inc. All rights reserved. + * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries. */ #include "debug.h" @@ -323,7 +323,7 @@ int ath12k_hal_desc_reo_parse_err(struct ath12k_base *ab, { enum hal_reo_dest_ring_push_reason push_reason; enum hal_reo_dest_ring_error_code err_code; - u32 cookie, val; + u32 cookie; push_reason = le32_get_bits(desc->info0, HAL_REO_DEST_RING_INFO0_PUSH_REASON); @@ -338,12 +338,6 @@ int ath12k_hal_desc_reo_parse_err(struct ath12k_base *ab, return -EINVAL; } - val = le32_get_bits(desc->info0, HAL_REO_DEST_RING_INFO0_BUFFER_TYPE); - if (val != HAL_REO_DEST_RING_BUFFER_TYPE_LINK_DESC) { - ath12k_warn(ab, "expected buffer type link_desc"); - return -EINVAL; - } - ath12k_hal_rx_reo_ent_paddr_get(ab, &desc->buf_addr_info, paddr, &cookie); *desc_bank = u32_get_bits(cookie, DP_LINK_DESC_BANK_MASK); From d911fa97dab3ba026a8b96bb7f833d007b7fc4e1 Mon Sep 17 00:00:00 2001 From: Baochen Qiang Date: Thu, 9 Oct 2025 14:16:55 -0700 Subject: [PATCH 0411/3902] wifi: ath12k: fix VHT MCS assignment [ Upstream commit 8c21b32c2cc82224c7fc1a9f67318f3b1199744b ] While associating, firmware needs the peer's receive capability to calculate its own VHT transmit MCS. Currently, the host sends this information via mcs->rx_mcs_set field, but firmware actually reads it from mcs->tx_mcs_set field. This mismatch is incorrect. This issue has not caused failures so far because most peers advertise identical TX and RX capabilities. Fix this by assigning the value to tx_mcs_set as expected. Additionally, the rate control mask is intended to limit our transmit MCS, so it should also apply to the peer's receive capability. Update the logic accordingly. Tested-on: QCN9274 hw2.0 PCI WLAN.WBE.1.0.1-00029-QCAHKSWPL_SILICONZ-1 Fixes: d889913205cf ("wifi: ath12k: driver for Qualcomm Wi-Fi 7 devices") Signed-off-by: Baochen Qiang Signed-off-by: Pradeep Kumar Chitrapu Reviewed-by: Vasanthakumar Thiagarajan Reviewed-by: Baochen Qiang Link: https://patch.msgid.link/20251009211656.2386085-2-quic_pradeepc@quicinc.com Signed-off-by: Jeff Johnson Signed-off-by: Sasha Levin --- drivers/net/wireless/ath/ath12k/mac.c | 7 +++---- drivers/net/wireless/ath/ath12k/wmi.c | 13 ++++++++----- drivers/net/wireless/ath/ath12k/wmi.h | 2 ++ 3 files changed, 13 insertions(+), 9 deletions(-) diff --git a/drivers/net/wireless/ath/ath12k/mac.c b/drivers/net/wireless/ath/ath12k/mac.c index db351c92201817..4e6d136433202d 100644 --- a/drivers/net/wireless/ath/ath12k/mac.c +++ b/drivers/net/wireless/ath/ath12k/mac.c @@ -2249,7 +2249,6 @@ static void ath12k_peer_assoc_h_vht(struct ath12k *ar, struct cfg80211_chan_def def; enum nl80211_band band; u16 *vht_mcs_mask; - u16 tx_mcs_map; u8 ampdu_factor; u8 max_nss, vht_mcs; int i, vht_nss, nss_idx; @@ -2340,10 +2339,10 @@ static void ath12k_peer_assoc_h_vht(struct ath12k *ar, arg->peer_nss = min(link_sta->rx_nss, max_nss); arg->rx_max_rate = __le16_to_cpu(vht_cap->vht_mcs.rx_highest); arg->rx_mcs_set = __le16_to_cpu(vht_cap->vht_mcs.rx_mcs_map); - arg->tx_max_rate = __le16_to_cpu(vht_cap->vht_mcs.tx_highest); + arg->rx_mcs_set = ath12k_peer_assoc_h_vht_limit(arg->rx_mcs_set, vht_mcs_mask); - tx_mcs_map = __le16_to_cpu(vht_cap->vht_mcs.tx_mcs_map); - arg->tx_mcs_set = ath12k_peer_assoc_h_vht_limit(tx_mcs_map, vht_mcs_mask); + arg->tx_max_rate = __le16_to_cpu(vht_cap->vht_mcs.tx_highest); + arg->tx_mcs_set = __le16_to_cpu(vht_cap->vht_mcs.tx_mcs_map); /* In QCN9274 platform, VHT MCS rate 10 and 11 is enabled by default. * VHT MCS rate 10 and 11 is not supported in 11ac standard. diff --git a/drivers/net/wireless/ath/ath12k/wmi.c b/drivers/net/wireless/ath/ath12k/wmi.c index ff6b3d4ea82084..e76275bd6916f7 100644 --- a/drivers/net/wireless/ath/ath12k/wmi.c +++ b/drivers/net/wireless/ath/ath12k/wmi.c @@ -1,7 +1,7 @@ // SPDX-License-Identifier: BSD-3-Clause-Clear /* * Copyright (c) 2018-2021 The Linux Foundation. All rights reserved. - * Copyright (c) 2021-2025 Qualcomm Innovation Center, Inc. All rights reserved. + * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries. */ #include #include @@ -2367,10 +2367,13 @@ int ath12k_wmi_send_peer_assoc_cmd(struct ath12k *ar, cmd->peer_bw_rxnss_override |= cpu_to_le32(arg->peer_bw_rxnss_override); if (arg->vht_capable) { - mcs->rx_max_rate = cpu_to_le32(arg->rx_max_rate); - mcs->rx_mcs_set = cpu_to_le32(arg->rx_mcs_set); - mcs->tx_max_rate = cpu_to_le32(arg->tx_max_rate); - mcs->tx_mcs_set = cpu_to_le32(arg->tx_mcs_set); + /* Firmware interprets mcs->tx_mcs_set field as peer's + * RX capability + */ + mcs->rx_max_rate = cpu_to_le32(arg->tx_max_rate); + mcs->rx_mcs_set = cpu_to_le32(arg->tx_mcs_set); + mcs->tx_max_rate = cpu_to_le32(arg->rx_max_rate); + mcs->tx_mcs_set = cpu_to_le32(arg->rx_mcs_set); } /* HE Rates */ diff --git a/drivers/net/wireless/ath/ath12k/wmi.h b/drivers/net/wireless/ath/ath12k/wmi.h index a8c3190e8ad955..6d9c645e3d5d04 100644 --- a/drivers/net/wireless/ath/ath12k/wmi.h +++ b/drivers/net/wireless/ath/ath12k/wmi.h @@ -4218,8 +4218,10 @@ struct wmi_unit_test_cmd { struct ath12k_wmi_vht_rate_set_params { __le32 tlv_header; __le32 rx_max_rate; + /* MCS at which the peer can transmit */ __le32 rx_mcs_set; __le32 tx_max_rate; + /* MCS at which the peer can receive */ __le32 tx_mcs_set; __le32 tx_max_mcs_nss; } __packed; From ca2a33cee1ef8bc6fb16fbb4e065fadaddea977c Mon Sep 17 00:00:00 2001 From: Pradeep Kumar Chitrapu Date: Thu, 9 Oct 2025 14:16:56 -0700 Subject: [PATCH 0412/3902] wifi: ath12k: fix TX and RX MCS rate configurations in HE mode [ Upstream commit 9c5f229b1312a31aff762b2111f6751e4e3722fe ] Currently, the TX and RX MCS rate configurations per peer are reversed when sent to the firmware. As a result, RX MCS rates are configured for TX, and vice versa. This commit rectifies the configuration to match what the firmware expects. Tested-on: QCN9274 hw2.0 PCI WLAN.WBE.1.0.1-00029-QCAHKSWPL_SILICONZ-1 Fixes: d889913205cf ("wifi: ath12k: driver for Qualcomm Wi-Fi 7 devices") Signed-off-by: Pradeep Kumar Chitrapu Reviewed-by: Vasanthakumar Thiagarajan Reviewed-by: Baochen Qiang Link: https://patch.msgid.link/20251009211656.2386085-3-quic_pradeepc@quicinc.com Signed-off-by: Jeff Johnson Signed-off-by: Sasha Levin --- drivers/net/wireless/ath/ath12k/mac.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/drivers/net/wireless/ath/ath12k/mac.c b/drivers/net/wireless/ath/ath12k/mac.c index 4e6d136433202d..0cec6c47fca29f 100644 --- a/drivers/net/wireless/ath/ath12k/mac.c +++ b/drivers/net/wireless/ath/ath12k/mac.c @@ -2624,9 +2624,10 @@ static void ath12k_peer_assoc_h_he(struct ath12k *ar, switch (link_sta->bandwidth) { case IEEE80211_STA_RX_BW_160: v = le16_to_cpu(he_cap->he_mcs_nss_supp.rx_mcs_160); + v = ath12k_peer_assoc_h_he_limit(v, he_mcs_mask); arg->peer_he_rx_mcs_set[WMI_HECAP_TXRX_MCS_NSS_IDX_160] = v; - v = ath12k_peer_assoc_h_he_limit(v, he_mcs_mask); + v = le16_to_cpu(he_cap->he_mcs_nss_supp.tx_mcs_160); arg->peer_he_tx_mcs_set[WMI_HECAP_TXRX_MCS_NSS_IDX_160] = v; arg->peer_he_mcs_count++; @@ -2636,10 +2637,10 @@ static void ath12k_peer_assoc_h_he(struct ath12k *ar, default: v = le16_to_cpu(he_cap->he_mcs_nss_supp.rx_mcs_80); + v = ath12k_peer_assoc_h_he_limit(v, he_mcs_mask); arg->peer_he_rx_mcs_set[WMI_HECAP_TXRX_MCS_NSS_IDX_80] = v; v = le16_to_cpu(he_cap->he_mcs_nss_supp.tx_mcs_80); - v = ath12k_peer_assoc_h_he_limit(v, he_mcs_mask); arg->peer_he_tx_mcs_set[WMI_HECAP_TXRX_MCS_NSS_IDX_80] = v; arg->peer_he_mcs_count++; From 01f1097166380de2a745cb9a03d0752a341102cc Mon Sep 17 00:00:00 2001 From: Fernand Sieber Date: Thu, 18 Sep 2025 17:05:28 +0200 Subject: [PATCH 0413/3902] sched/fair: Forfeit vruntime on yield [ Upstream commit 79104becf42baeeb4a3f2b106f954b9fc7c10a3c ] If a task yields, the scheduler may decide to pick it again. The task in turn may decide to yield immediately or shortly after, leading to a tight loop of yields. If there's another runnable task as this point, the deadline will be increased by the slice at each loop. This can cause the deadline to runaway pretty quickly, and subsequent elevated run delays later on as the task doesn't get picked again. The reason the scheduler can pick the same task again and again despite its deadline increasing is because it may be the only eligible task at that point. Fix this by making the task forfeiting its remaining vruntime and pushing the deadline one slice ahead. This implements yield behavior more authentically. We limit the forfeiting to eligible tasks. This is because core scheduling prefers running ineligible tasks rather than force idling. As such, without the condition, we can end up on a yield loop which makes the vruntime increase rapidly, leading to anomalous run delays later down the line. Fixes: 147f3efaa24182 ("sched/fair: Implement an EEVDF-like scheduling policy") Signed-off-by: Fernand Sieber Signed-off-by: Peter Zijlstra (Intel) Link: https://lore.kernel.org/r/20250401123622.584018-1-sieberf@amazon.com Link: https://lore.kernel.org/r/20250911095113.203439-1-sieberf@amazon.com Link: https://lore.kernel.org/r/20250916140228.452231-1-sieberf@amazon.com Signed-off-by: Sasha Levin --- kernel/sched/fair.c | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/kernel/sched/fair.c b/kernel/sched/fair.c index 5b752324270b08..2a4a1c6e25da07 100644 --- a/kernel/sched/fair.c +++ b/kernel/sched/fair.c @@ -9014,7 +9014,19 @@ static void yield_task_fair(struct rq *rq) */ rq_clock_skip_update(rq); - se->deadline += calc_delta_fair(se->slice, se); + /* + * Forfeit the remaining vruntime, only if the entity is eligible. This + * condition is necessary because in core scheduling we prefer to run + * ineligible tasks rather than force idling. If this happens we may + * end up in a loop where the core scheduler picks the yielding task, + * which yields immediately again; without the condition the vruntime + * ends up quickly running away. + */ + if (entity_eligible(cfs_rq, se)) { + se->vruntime = se->deadline; + se->deadline += calc_delta_fair(se->slice, se); + update_min_vruntime(cfs_rq); + } } static bool yield_to_task_fair(struct rq *rq, struct task_struct *p) From 2f525dea50f2db43821616e2634d3770520841f2 Mon Sep 17 00:00:00 2001 From: Johan Hovold Date: Mon, 13 Oct 2025 11:46:01 +0200 Subject: [PATCH 0414/3902] irqchip/bcm2712-mip: Fix OF node reference imbalance [ Upstream commit 0435bcc4e5858c632c1b6d5afa637580d9779890 ] The init callback must not decrement the reference count of the provided irqchip OF node. This should not cause any trouble currently, but if the driver ever starts probe deferring it could lead to warnings about reference underflow and saturation. Fixes: 32c6c054661a ("irqchip: Add Broadcom BCM2712 MSI-X interrupt controller") Signed-off-by: Johan Hovold Signed-off-by: Thomas Gleixner Reviewed-by: Florian Fainelli Signed-off-by: Sasha Levin --- drivers/irqchip/irq-bcm2712-mip.c | 1 - 1 file changed, 1 deletion(-) diff --git a/drivers/irqchip/irq-bcm2712-mip.c b/drivers/irqchip/irq-bcm2712-mip.c index 9bd7bc0bf6d597..256c2d59f717d8 100644 --- a/drivers/irqchip/irq-bcm2712-mip.c +++ b/drivers/irqchip/irq-bcm2712-mip.c @@ -239,7 +239,6 @@ static int __init mip_of_msi_init(struct device_node *node, struct device_node * int ret; pdev = of_find_device_by_node(node); - of_node_put(node); if (!pdev) return -EPROBE_DEFER; From e74c71452437fa9d0b79710f4f2c24a118b9d4e9 Mon Sep 17 00:00:00 2001 From: Johan Hovold Date: Mon, 13 Oct 2025 11:46:02 +0200 Subject: [PATCH 0415/3902] irqchip/bcm2712-mip: Fix section mismatch [ Upstream commit a8452d1d59d46066051e676d5daa472cd08cb304 ] Platform drivers can be probed after their init sections have been discarded so the irqchip init callback must not live in init. Fixes: 32c6c054661a ("irqchip: Add Broadcom BCM2712 MSI-X interrupt controller") Signed-off-by: Johan Hovold Signed-off-by: Thomas Gleixner Reviewed-by: Florian Fainelli Signed-off-by: Sasha Levin --- drivers/irqchip/irq-bcm2712-mip.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/irqchip/irq-bcm2712-mip.c b/drivers/irqchip/irq-bcm2712-mip.c index 256c2d59f717d8..8466646e5a2da0 100644 --- a/drivers/irqchip/irq-bcm2712-mip.c +++ b/drivers/irqchip/irq-bcm2712-mip.c @@ -232,7 +232,7 @@ static int mip_parse_dt(struct mip_priv *mip, struct device_node *np) return ret; } -static int __init mip_of_msi_init(struct device_node *node, struct device_node *parent) +static int mip_of_msi_init(struct device_node *node, struct device_node *parent) { struct platform_device *pdev; struct mip_priv *mip; From 2f36222dbc040967f5493e049526dbf9c3f92194 Mon Sep 17 00:00:00 2001 From: Johan Hovold Date: Mon, 13 Oct 2025 11:46:03 +0200 Subject: [PATCH 0416/3902] irqchip/irq-bcm7038-l1: Fix section mismatch [ Upstream commit e9db5332caaf4789ae3bafe72f61ad8e6e0c2d81 ] Platform drivers can be probed after their init sections have been discarded so the irqchip init callback must not live in init. Fixes: c057c799e379 ("irqchip/irq-bcm7038-l1: Switch to IRQCHIP_PLATFORM_DRIVER") Signed-off-by: Johan Hovold Signed-off-by: Thomas Gleixner Reviewed-by: Florian Fainelli Signed-off-by: Sasha Levin --- drivers/irqchip/irq-bcm7038-l1.c | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/drivers/irqchip/irq-bcm7038-l1.c b/drivers/irqchip/irq-bcm7038-l1.c index 04fac0cc857fd0..eda33bd5d080c3 100644 --- a/drivers/irqchip/irq-bcm7038-l1.c +++ b/drivers/irqchip/irq-bcm7038-l1.c @@ -219,9 +219,8 @@ static int bcm7038_l1_set_affinity(struct irq_data *d, } #endif -static int __init bcm7038_l1_init_one(struct device_node *dn, - unsigned int idx, - struct bcm7038_l1_chip *intc) +static int bcm7038_l1_init_one(struct device_node *dn, unsigned int idx, + struct bcm7038_l1_chip *intc) { struct resource res; resource_size_t sz; @@ -395,8 +394,7 @@ static const struct irq_domain_ops bcm7038_l1_domain_ops = { .map = bcm7038_l1_map, }; -static int __init bcm7038_l1_of_init(struct device_node *dn, - struct device_node *parent) +static int bcm7038_l1_of_init(struct device_node *dn, struct device_node *parent) { struct bcm7038_l1_chip *intc; int idx, ret; From 6769909e3ed8d21811996755ad920e03c2579186 Mon Sep 17 00:00:00 2001 From: Johan Hovold Date: Mon, 13 Oct 2025 11:46:04 +0200 Subject: [PATCH 0417/3902] irqchip/irq-bcm7120-l2: Fix section mismatch [ Upstream commit bfc0c5beab1fde843677923cf008f41d583c980a ] Platform drivers can be probed after their init sections have been discarded so the irqchip init callbacks must not live in init. Fixes: 3ac268d5ed22 ("irqchip/irq-bcm7120-l2: Switch to IRQCHIP_PLATFORM_DRIVER") Signed-off-by: Johan Hovold Signed-off-by: Thomas Gleixner Reviewed-by: Florian Fainelli Signed-off-by: Sasha Levin --- drivers/irqchip/irq-bcm7120-l2.c | 17 ++++++----------- 1 file changed, 6 insertions(+), 11 deletions(-) diff --git a/drivers/irqchip/irq-bcm7120-l2.c b/drivers/irqchip/irq-bcm7120-l2.c index ff22c310440181..b6c85560c42eac 100644 --- a/drivers/irqchip/irq-bcm7120-l2.c +++ b/drivers/irqchip/irq-bcm7120-l2.c @@ -143,8 +143,7 @@ static int bcm7120_l2_intc_init_one(struct device_node *dn, return 0; } -static int __init bcm7120_l2_intc_iomap_7120(struct device_node *dn, - struct bcm7120_l2_intc_data *data) +static int bcm7120_l2_intc_iomap_7120(struct device_node *dn, struct bcm7120_l2_intc_data *data) { int ret; @@ -177,8 +176,7 @@ static int __init bcm7120_l2_intc_iomap_7120(struct device_node *dn, return 0; } -static int __init bcm7120_l2_intc_iomap_3380(struct device_node *dn, - struct bcm7120_l2_intc_data *data) +static int bcm7120_l2_intc_iomap_3380(struct device_node *dn, struct bcm7120_l2_intc_data *data) { unsigned int gc_idx; @@ -208,10 +206,9 @@ static int __init bcm7120_l2_intc_iomap_3380(struct device_node *dn, return 0; } -static int __init bcm7120_l2_intc_probe(struct device_node *dn, - struct device_node *parent, +static int bcm7120_l2_intc_probe(struct device_node *dn, struct device_node *parent, int (*iomap_regs_fn)(struct device_node *, - struct bcm7120_l2_intc_data *), + struct bcm7120_l2_intc_data *), const char *intc_name) { unsigned int clr = IRQ_NOREQUEST | IRQ_NOPROBE | IRQ_NOAUTOEN; @@ -339,15 +336,13 @@ static int __init bcm7120_l2_intc_probe(struct device_node *dn, return ret; } -static int __init bcm7120_l2_intc_probe_7120(struct device_node *dn, - struct device_node *parent) +static int bcm7120_l2_intc_probe_7120(struct device_node *dn, struct device_node *parent) { return bcm7120_l2_intc_probe(dn, parent, bcm7120_l2_intc_iomap_7120, "BCM7120 L2"); } -static int __init bcm7120_l2_intc_probe_3380(struct device_node *dn, - struct device_node *parent) +static int bcm7120_l2_intc_probe_3380(struct device_node *dn, struct device_node *parent) { return bcm7120_l2_intc_probe(dn, parent, bcm7120_l2_intc_iomap_3380, "BCM3380 L2"); From 99e8c4978410032c64fe94d8ead6bc89ee2c0c8a Mon Sep 17 00:00:00 2001 From: Johan Hovold Date: Mon, 13 Oct 2025 11:46:05 +0200 Subject: [PATCH 0418/3902] irqchip/irq-brcmstb-l2: Fix section mismatch [ Upstream commit bbe1775924478e95372c2f896064ab6446000713 ] Platform drivers can be probed after their init sections have been discarded so the irqchip init callbacks must not live in init. Fixes: 51d9db5c8fbb ("irqchip/irq-brcmstb-l2: Switch to IRQCHIP_PLATFORM_DRIVER") Signed-off-by: Johan Hovold Signed-off-by: Thomas Gleixner Reviewed-by: Florian Fainelli Signed-off-by: Sasha Levin --- drivers/irqchip/irq-brcmstb-l2.c | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/drivers/irqchip/irq-brcmstb-l2.c b/drivers/irqchip/irq-brcmstb-l2.c index 1bec5b2cd3f0ed..53e67c6c01f7a7 100644 --- a/drivers/irqchip/irq-brcmstb-l2.c +++ b/drivers/irqchip/irq-brcmstb-l2.c @@ -138,10 +138,8 @@ static void brcmstb_l2_intc_resume(struct irq_data *d) irq_reg_writel(gc, ~b->saved_mask, ct->regs.enable); } -static int __init brcmstb_l2_intc_of_init(struct device_node *np, - struct device_node *parent, - const struct brcmstb_intc_init_params - *init_params) +static int brcmstb_l2_intc_of_init(struct device_node *np, struct device_node *parent, + const struct brcmstb_intc_init_params *init_params) { unsigned int clr = IRQ_NOREQUEST | IRQ_NOPROBE | IRQ_NOAUTOEN; unsigned int set = 0; @@ -257,14 +255,12 @@ static int __init brcmstb_l2_intc_of_init(struct device_node *np, return ret; } -static int __init brcmstb_l2_edge_intc_of_init(struct device_node *np, - struct device_node *parent) +static int brcmstb_l2_edge_intc_of_init(struct device_node *np, struct device_node *parent) { return brcmstb_l2_intc_of_init(np, parent, &l2_edge_intc_init); } -static int __init brcmstb_l2_lvl_intc_of_init(struct device_node *np, - struct device_node *parent) +static int brcmstb_l2_lvl_intc_of_init(struct device_node *np, struct device_node *parent) { return brcmstb_l2_intc_of_init(np, parent, &l2_lvl_intc_init); } From 60597d44de5fe532614fbfa23dc57a0177c838ab Mon Sep 17 00:00:00 2001 From: Johan Hovold Date: Mon, 13 Oct 2025 11:46:06 +0200 Subject: [PATCH 0419/3902] irqchip/imx-mu-msi: Fix section mismatch [ Upstream commit 64acfd8e680ff8992c101fe19aadb112ce551072 ] Platform drivers can be probed after their init sections have been discarded so the irqchip init callbacks must not live in init. Fixes: 70afdab904d2 ("irqchip: Add IMX MU MSI controller driver") Signed-off-by: Johan Hovold Signed-off-by: Thomas Gleixner Signed-off-by: Sasha Levin --- drivers/irqchip/irq-imx-mu-msi.c | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) diff --git a/drivers/irqchip/irq-imx-mu-msi.c b/drivers/irqchip/irq-imx-mu-msi.c index d2a4e8a61a42b1..41df168aa7da2e 100644 --- a/drivers/irqchip/irq-imx-mu-msi.c +++ b/drivers/irqchip/irq-imx-mu-msi.c @@ -296,9 +296,8 @@ static const struct imx_mu_dcfg imx_mu_cfg_imx8ulp = { }, }; -static int __init imx_mu_of_init(struct device_node *dn, - struct device_node *parent, - const struct imx_mu_dcfg *cfg) +static int imx_mu_of_init(struct device_node *dn, struct device_node *parent, + const struct imx_mu_dcfg *cfg) { struct platform_device *pdev = of_find_device_by_node(dn); struct device_link *pd_link_a; @@ -416,20 +415,17 @@ static const struct dev_pm_ops imx_mu_pm_ops = { imx_mu_runtime_resume, NULL) }; -static int __init imx_mu_imx7ulp_of_init(struct device_node *dn, - struct device_node *parent) +static int imx_mu_imx7ulp_of_init(struct device_node *dn, struct device_node *parent) { return imx_mu_of_init(dn, parent, &imx_mu_cfg_imx7ulp); } -static int __init imx_mu_imx6sx_of_init(struct device_node *dn, - struct device_node *parent) +static int imx_mu_imx6sx_of_init(struct device_node *dn, struct device_node *parent) { return imx_mu_of_init(dn, parent, &imx_mu_cfg_imx6sx); } -static int __init imx_mu_imx8ulp_of_init(struct device_node *dn, - struct device_node *parent) +static int imx_mu_imx8ulp_of_init(struct device_node *dn, struct device_node *parent) { return imx_mu_of_init(dn, parent, &imx_mu_cfg_imx8ulp); } From 3f1ac747652fe2ce067d243f5410e21b4b425326 Mon Sep 17 00:00:00 2001 From: Johan Hovold Date: Mon, 13 Oct 2025 11:46:07 +0200 Subject: [PATCH 0420/3902] irqchip/renesas-rzg2l: Fix section mismatch [ Upstream commit 5b338fbb2b5b21d61a9eaba14dcf43108de30258 ] Platform drivers can be probed after their init sections have been discarded so the irqchip init callbacks must not live in init. Fixes: d011c022efe27579 ("irqchip/renesas-rzg2l: Add support for RZ/Five SoC") Signed-off-by: Johan Hovold Signed-off-by: Thomas Gleixner Reviewed-by: Geert Uytterhoeven Signed-off-by: Sasha Levin --- drivers/irqchip/irq-renesas-rzg2l.c | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/drivers/irqchip/irq-renesas-rzg2l.c b/drivers/irqchip/irq-renesas-rzg2l.c index 2a54adeb4cc714..12b6eb1503016e 100644 --- a/drivers/irqchip/irq-renesas-rzg2l.c +++ b/drivers/irqchip/irq-renesas-rzg2l.c @@ -597,14 +597,12 @@ static int rzg2l_irqc_common_init(struct device_node *node, struct device_node * return 0; } -static int __init rzg2l_irqc_init(struct device_node *node, - struct device_node *parent) +static int rzg2l_irqc_init(struct device_node *node, struct device_node *parent) { return rzg2l_irqc_common_init(node, parent, &rzg2l_irqc_chip); } -static int __init rzfive_irqc_init(struct device_node *node, - struct device_node *parent) +static int rzfive_irqc_init(struct device_node *node, struct device_node *parent) { return rzg2l_irqc_common_init(node, parent, &rzfive_irqc_chip); } From d4caa2df1f0812fe6c7eb8029ee3d62b7978e578 Mon Sep 17 00:00:00 2001 From: Johan Hovold Date: Mon, 13 Oct 2025 11:46:08 +0200 Subject: [PATCH 0421/3902] irqchip/starfive-jh8100: Fix section mismatch [ Upstream commit f798bdb9aa81c425184f92e3d0b44d3b53d10da7 ] Platform drivers can be probed after their init sections have been discarded so the irqchip init callback must not live in init. Fixes: e4e535036173 ("irqchip: Add StarFive external interrupt controller") Signed-off-by: Johan Hovold Signed-off-by: Thomas Gleixner Reviewed-by: Changhuang Liang Signed-off-by: Sasha Levin --- drivers/irqchip/irq-starfive-jh8100-intc.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/drivers/irqchip/irq-starfive-jh8100-intc.c b/drivers/irqchip/irq-starfive-jh8100-intc.c index 2460798ec158b6..117f2c651ebd00 100644 --- a/drivers/irqchip/irq-starfive-jh8100-intc.c +++ b/drivers/irqchip/irq-starfive-jh8100-intc.c @@ -114,8 +114,7 @@ static void starfive_intc_irq_handler(struct irq_desc *desc) chained_irq_exit(chip, desc); } -static int __init starfive_intc_init(struct device_node *intc, - struct device_node *parent) +static int starfive_intc_init(struct device_node *intc, struct device_node *parent) { struct starfive_irq_chip *irqc; struct reset_control *rst; From defdeae616070373e406fc6f69d343a0ecba15a0 Mon Sep 17 00:00:00 2001 From: Johan Hovold Date: Mon, 13 Oct 2025 11:46:09 +0200 Subject: [PATCH 0422/3902] irqchip/qcom-irq-combiner: Fix section mismatch [ Upstream commit 9b685058ca936752285c5520d351b828312ac965 ] Platform drivers can be probed after their init sections have been discarded so the probe callback must not live in init. Fixes: f20cc9b00c7b ("irqchip/qcom: Add IRQ combiner driver") Signed-off-by: Johan Hovold Signed-off-by: Thomas Gleixner Signed-off-by: Sasha Levin --- drivers/irqchip/qcom-irq-combiner.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/irqchip/qcom-irq-combiner.c b/drivers/irqchip/qcom-irq-combiner.c index 18e696dc7f4d64..9308088773be7f 100644 --- a/drivers/irqchip/qcom-irq-combiner.c +++ b/drivers/irqchip/qcom-irq-combiner.c @@ -222,7 +222,7 @@ static int get_registers(struct platform_device *pdev, struct combiner *comb) return 0; } -static int __init combiner_probe(struct platform_device *pdev) +static int combiner_probe(struct platform_device *pdev) { struct combiner *combiner; int nregs; From d2e28c3ed2e2a3d0072f9e21c27680e3a82d0d74 Mon Sep 17 00:00:00 2001 From: Johan Hovold Date: Mon, 13 Oct 2025 11:46:10 +0200 Subject: [PATCH 0423/3902] irqchip: Drop leftover brackets [ Upstream commit 3540d99c03a88d4ebf65026f1f1926d3af658fb1 ] Drop some unnecessary brackets in platform_irqchip_probe() mistakenly left by commit 9322d1915f9d ("irqchip: Plug a OF node reference leak in platform_irqchip_probe()"). Signed-off-by: Johan Hovold Signed-off-by: Thomas Gleixner Reviewed-by: Geert Uytterhoeven Stable-dep-of: 1e3e330c0707 ("irqchip: Pass platform device to platform drivers") Signed-off-by: Sasha Levin --- drivers/irqchip/irqchip.c | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/drivers/irqchip/irqchip.c b/drivers/irqchip/irqchip.c index 0ee7b6b71f5fa5..652d20d2b07f18 100644 --- a/drivers/irqchip/irqchip.c +++ b/drivers/irqchip/irqchip.c @@ -38,9 +38,8 @@ int platform_irqchip_probe(struct platform_device *pdev) struct device_node *par_np __free(device_node) = of_irq_find_parent(np); of_irq_init_cb_t irq_init_cb = of_device_get_match_data(&pdev->dev); - if (!irq_init_cb) { + if (!irq_init_cb) return -EINVAL; - } if (par_np == np) par_np = NULL; @@ -53,9 +52,8 @@ int platform_irqchip_probe(struct platform_device *pdev) * interrupt controller. The actual initialization callback of this * interrupt controller can check for specific domains as necessary. */ - if (par_np && !irq_find_matching_host(par_np, DOMAIN_BUS_ANY)) { + if (par_np && !irq_find_matching_host(par_np, DOMAIN_BUS_ANY)) return -EPROBE_DEFER; - } return irq_init_cb(np, par_np); } From 58c47ba6f329f1597f077640c84c290a2adb1119 Mon Sep 17 00:00:00 2001 From: Johan Hovold Date: Mon, 13 Oct 2025 11:46:11 +0200 Subject: [PATCH 0424/3902] irqchip: Pass platform device to platform drivers [ Upstream commit 1e3e330c07076a0582385bbea029c9cc918fa30d ] The IRQCHIP_PLATFORM_DRIVER macros can be used to convert OF irqchip drivers to platform drivers but currently reuse the OF init callback prototype that only takes OF nodes as arguments. This forces drivers to do reverse lookups of their struct devices during probe if they need them for things like dev_printk() and device managed resources. Half of the drivers doing reverse lookups also currently fail to release the additional reference taken during the lookup, while other drivers have had the reference leak plugged in various ways (e.g. using non-intuitive cleanup constructs which still confuse static checkers). Switch to using a probe callback that takes a platform device as its first argument to simplify drivers and plug the remaining (mostly benign) reference leaks. Fixes: 32c6c054661a ("irqchip: Add Broadcom BCM2712 MSI-X interrupt controller") Fixes: 70afdab904d2 ("irqchip: Add IMX MU MSI controller driver") Fixes: a6199bb514d8 ("irqchip: Add Qualcomm MPM controller driver") Signed-off-by: Johan Hovold Signed-off-by: Thomas Gleixner Reviewed-by: Florian Fainelli Reviewed-by: Changhuang Liang Signed-off-by: Sasha Levin --- drivers/irqchip/irq-bcm2712-mip.c | 10 ++----- drivers/irqchip/irq-bcm7038-l1.c | 5 ++-- drivers/irqchip/irq-bcm7120-l2.c | 20 ++++--------- drivers/irqchip/irq-brcmstb-l2.c | 21 ++++++------- drivers/irqchip/irq-imx-mu-msi.c | 24 +++++++-------- drivers/irqchip/irq-mchp-eic.c | 5 ++-- drivers/irqchip/irq-meson-gpio.c | 5 ++-- drivers/irqchip/irq-qcom-mpm.c | 6 ++-- drivers/irqchip/irq-renesas-rzg2l.c | 35 +++++++--------------- drivers/irqchip/irq-renesas-rzv2h.c | 32 ++++++-------------- drivers/irqchip/irq-starfive-jh8100-intc.c | 5 ++-- drivers/irqchip/irqchip.c | 6 ++-- drivers/irqchip/qcom-pdc.c | 5 ++-- include/linux/irqchip.h | 8 ++++- 14 files changed, 78 insertions(+), 109 deletions(-) diff --git a/drivers/irqchip/irq-bcm2712-mip.c b/drivers/irqchip/irq-bcm2712-mip.c index 8466646e5a2da0..4761974ad650a9 100644 --- a/drivers/irqchip/irq-bcm2712-mip.c +++ b/drivers/irqchip/irq-bcm2712-mip.c @@ -232,16 +232,12 @@ static int mip_parse_dt(struct mip_priv *mip, struct device_node *np) return ret; } -static int mip_of_msi_init(struct device_node *node, struct device_node *parent) +static int mip_msi_probe(struct platform_device *pdev, struct device_node *parent) { - struct platform_device *pdev; + struct device_node *node = pdev->dev.of_node; struct mip_priv *mip; int ret; - pdev = of_find_device_by_node(node); - if (!pdev) - return -EPROBE_DEFER; - mip = kzalloc(sizeof(*mip), GFP_KERNEL); if (!mip) return -ENOMEM; @@ -284,7 +280,7 @@ static int mip_of_msi_init(struct device_node *node, struct device_node *parent) } IRQCHIP_PLATFORM_DRIVER_BEGIN(mip_msi) -IRQCHIP_MATCH("brcm,bcm2712-mip", mip_of_msi_init) +IRQCHIP_MATCH("brcm,bcm2712-mip", mip_msi_probe) IRQCHIP_PLATFORM_DRIVER_END(mip_msi) MODULE_DESCRIPTION("Broadcom BCM2712 MSI-X interrupt controller"); MODULE_AUTHOR("Phil Elwell "); diff --git a/drivers/irqchip/irq-bcm7038-l1.c b/drivers/irqchip/irq-bcm7038-l1.c index eda33bd5d080c3..821b288587cab4 100644 --- a/drivers/irqchip/irq-bcm7038-l1.c +++ b/drivers/irqchip/irq-bcm7038-l1.c @@ -394,8 +394,9 @@ static const struct irq_domain_ops bcm7038_l1_domain_ops = { .map = bcm7038_l1_map, }; -static int bcm7038_l1_of_init(struct device_node *dn, struct device_node *parent) +static int bcm7038_l1_probe(struct platform_device *pdev, struct device_node *parent) { + struct device_node *dn = pdev->dev.of_node; struct bcm7038_l1_chip *intc; int idx, ret; @@ -453,7 +454,7 @@ static int bcm7038_l1_of_init(struct device_node *dn, struct device_node *parent } IRQCHIP_PLATFORM_DRIVER_BEGIN(bcm7038_l1) -IRQCHIP_MATCH("brcm,bcm7038-l1-intc", bcm7038_l1_of_init) +IRQCHIP_MATCH("brcm,bcm7038-l1-intc", bcm7038_l1_probe) IRQCHIP_PLATFORM_DRIVER_END(bcm7038_l1) MODULE_DESCRIPTION("Broadcom STB 7038-style L1/L2 interrupt controller"); MODULE_LICENSE("GPL v2"); diff --git a/drivers/irqchip/irq-bcm7120-l2.c b/drivers/irqchip/irq-bcm7120-l2.c index b6c85560c42eac..518c9d4366a55a 100644 --- a/drivers/irqchip/irq-bcm7120-l2.c +++ b/drivers/irqchip/irq-bcm7120-l2.c @@ -206,14 +206,14 @@ static int bcm7120_l2_intc_iomap_3380(struct device_node *dn, struct bcm7120_l2_ return 0; } -static int bcm7120_l2_intc_probe(struct device_node *dn, struct device_node *parent, +static int bcm7120_l2_intc_probe(struct platform_device *pdev, struct device_node *parent, int (*iomap_regs_fn)(struct device_node *, struct bcm7120_l2_intc_data *), const char *intc_name) { unsigned int clr = IRQ_NOREQUEST | IRQ_NOPROBE | IRQ_NOAUTOEN; + struct device_node *dn = pdev->dev.of_node; struct bcm7120_l2_intc_data *data; - struct platform_device *pdev; struct irq_chip_generic *gc; struct irq_chip_type *ct; int ret = 0; @@ -224,14 +224,7 @@ static int bcm7120_l2_intc_probe(struct device_node *dn, struct device_node *par if (!data) return -ENOMEM; - pdev = of_find_device_by_node(dn); - if (!pdev) { - ret = -ENODEV; - goto out_free_data; - } - data->num_parent_irqs = platform_irq_count(pdev); - put_device(&pdev->dev); if (data->num_parent_irqs <= 0) { pr_err("invalid number of parent interrupts\n"); ret = -ENOMEM; @@ -331,20 +324,19 @@ static int bcm7120_l2_intc_probe(struct device_node *dn, struct device_node *par if (data->map_base[idx]) iounmap(data->map_base[idx]); } -out_free_data: kfree(data); return ret; } -static int bcm7120_l2_intc_probe_7120(struct device_node *dn, struct device_node *parent) +static int bcm7120_l2_intc_probe_7120(struct platform_device *pdev, struct device_node *parent) { - return bcm7120_l2_intc_probe(dn, parent, bcm7120_l2_intc_iomap_7120, + return bcm7120_l2_intc_probe(pdev, parent, bcm7120_l2_intc_iomap_7120, "BCM7120 L2"); } -static int bcm7120_l2_intc_probe_3380(struct device_node *dn, struct device_node *parent) +static int bcm7120_l2_intc_probe_3380(struct platform_device *pdev, struct device_node *parent) { - return bcm7120_l2_intc_probe(dn, parent, bcm7120_l2_intc_iomap_3380, + return bcm7120_l2_intc_probe(pdev, parent, bcm7120_l2_intc_iomap_3380, "BCM3380 L2"); } diff --git a/drivers/irqchip/irq-brcmstb-l2.c b/drivers/irqchip/irq-brcmstb-l2.c index 53e67c6c01f7a7..bb7078d6524f9b 100644 --- a/drivers/irqchip/irq-brcmstb-l2.c +++ b/drivers/irqchip/irq-brcmstb-l2.c @@ -138,11 +138,12 @@ static void brcmstb_l2_intc_resume(struct irq_data *d) irq_reg_writel(gc, ~b->saved_mask, ct->regs.enable); } -static int brcmstb_l2_intc_of_init(struct device_node *np, struct device_node *parent, - const struct brcmstb_intc_init_params *init_params) +static int brcmstb_l2_intc_probe(struct platform_device *pdev, struct device_node *parent, + const struct brcmstb_intc_init_params *init_params) { unsigned int clr = IRQ_NOREQUEST | IRQ_NOPROBE | IRQ_NOAUTOEN; unsigned int set = 0; + struct device_node *np = pdev->dev.of_node; struct brcmstb_l2_intc_data *data; struct irq_chip_type *ct; int ret; @@ -255,21 +256,21 @@ static int brcmstb_l2_intc_of_init(struct device_node *np, struct device_node *p return ret; } -static int brcmstb_l2_edge_intc_of_init(struct device_node *np, struct device_node *parent) +static int brcmstb_l2_edge_intc_probe(struct platform_device *pdev, struct device_node *parent) { - return brcmstb_l2_intc_of_init(np, parent, &l2_edge_intc_init); + return brcmstb_l2_intc_probe(pdev, parent, &l2_edge_intc_init); } -static int brcmstb_l2_lvl_intc_of_init(struct device_node *np, struct device_node *parent) +static int brcmstb_l2_lvl_intc_probe(struct platform_device *pdev, struct device_node *parent) { - return brcmstb_l2_intc_of_init(np, parent, &l2_lvl_intc_init); + return brcmstb_l2_intc_probe(pdev, parent, &l2_lvl_intc_init); } IRQCHIP_PLATFORM_DRIVER_BEGIN(brcmstb_l2) -IRQCHIP_MATCH("brcm,l2-intc", brcmstb_l2_edge_intc_of_init) -IRQCHIP_MATCH("brcm,hif-spi-l2-intc", brcmstb_l2_edge_intc_of_init) -IRQCHIP_MATCH("brcm,upg-aux-aon-l2-intc", brcmstb_l2_edge_intc_of_init) -IRQCHIP_MATCH("brcm,bcm7271-l2-intc", brcmstb_l2_lvl_intc_of_init) +IRQCHIP_MATCH("brcm,l2-intc", brcmstb_l2_edge_intc_probe) +IRQCHIP_MATCH("brcm,hif-spi-l2-intc", brcmstb_l2_edge_intc_probe) +IRQCHIP_MATCH("brcm,upg-aux-aon-l2-intc", brcmstb_l2_edge_intc_probe) +IRQCHIP_MATCH("brcm,bcm7271-l2-intc", brcmstb_l2_lvl_intc_probe) IRQCHIP_PLATFORM_DRIVER_END(brcmstb_l2) MODULE_DESCRIPTION("Broadcom STB generic L2 interrupt controller"); MODULE_LICENSE("GPL v2"); diff --git a/drivers/irqchip/irq-imx-mu-msi.c b/drivers/irqchip/irq-imx-mu-msi.c index 41df168aa7da2e..c598f2f52fc6f3 100644 --- a/drivers/irqchip/irq-imx-mu-msi.c +++ b/drivers/irqchip/irq-imx-mu-msi.c @@ -296,10 +296,9 @@ static const struct imx_mu_dcfg imx_mu_cfg_imx8ulp = { }, }; -static int imx_mu_of_init(struct device_node *dn, struct device_node *parent, - const struct imx_mu_dcfg *cfg) +static int imx_mu_probe(struct platform_device *pdev, struct device_node *parent, + const struct imx_mu_dcfg *cfg) { - struct platform_device *pdev = of_find_device_by_node(dn); struct device_link *pd_link_a; struct device_link *pd_link_b; struct imx_mu_msi *msi_data; @@ -415,28 +414,27 @@ static const struct dev_pm_ops imx_mu_pm_ops = { imx_mu_runtime_resume, NULL) }; -static int imx_mu_imx7ulp_of_init(struct device_node *dn, struct device_node *parent) +static int imx_mu_imx7ulp_probe(struct platform_device *pdev, struct device_node *parent) { - return imx_mu_of_init(dn, parent, &imx_mu_cfg_imx7ulp); + return imx_mu_probe(pdev, parent, &imx_mu_cfg_imx7ulp); } -static int imx_mu_imx6sx_of_init(struct device_node *dn, struct device_node *parent) +static int imx_mu_imx6sx_probe(struct platform_device *pdev, struct device_node *parent) { - return imx_mu_of_init(dn, parent, &imx_mu_cfg_imx6sx); + return imx_mu_probe(pdev, parent, &imx_mu_cfg_imx6sx); } -static int imx_mu_imx8ulp_of_init(struct device_node *dn, struct device_node *parent) +static int imx_mu_imx8ulp_probe(struct platform_device *pdev, struct device_node *parent) { - return imx_mu_of_init(dn, parent, &imx_mu_cfg_imx8ulp); + return imx_mu_probe(pdev, parent, &imx_mu_cfg_imx8ulp); } IRQCHIP_PLATFORM_DRIVER_BEGIN(imx_mu_msi) -IRQCHIP_MATCH("fsl,imx7ulp-mu-msi", imx_mu_imx7ulp_of_init) -IRQCHIP_MATCH("fsl,imx6sx-mu-msi", imx_mu_imx6sx_of_init) -IRQCHIP_MATCH("fsl,imx8ulp-mu-msi", imx_mu_imx8ulp_of_init) +IRQCHIP_MATCH("fsl,imx7ulp-mu-msi", imx_mu_imx7ulp_probe) +IRQCHIP_MATCH("fsl,imx6sx-mu-msi", imx_mu_imx6sx_probe) +IRQCHIP_MATCH("fsl,imx8ulp-mu-msi", imx_mu_imx8ulp_probe) IRQCHIP_PLATFORM_DRIVER_END(imx_mu_msi, .pm = &imx_mu_pm_ops) - MODULE_AUTHOR("Frank Li "); MODULE_DESCRIPTION("Freescale MU MSI controller driver"); MODULE_LICENSE("GPL"); diff --git a/drivers/irqchip/irq-mchp-eic.c b/drivers/irqchip/irq-mchp-eic.c index 516a3a0e359cc3..b513a899c08537 100644 --- a/drivers/irqchip/irq-mchp-eic.c +++ b/drivers/irqchip/irq-mchp-eic.c @@ -199,8 +199,9 @@ static const struct irq_domain_ops mchp_eic_domain_ops = { .free = irq_domain_free_irqs_common, }; -static int mchp_eic_init(struct device_node *node, struct device_node *parent) +static int mchp_eic_probe(struct platform_device *pdev, struct device_node *parent) { + struct device_node *node = pdev->dev.of_node; struct irq_domain *parent_domain = NULL; int ret, i; @@ -273,7 +274,7 @@ static int mchp_eic_init(struct device_node *node, struct device_node *parent) } IRQCHIP_PLATFORM_DRIVER_BEGIN(mchp_eic) -IRQCHIP_MATCH("microchip,sama7g5-eic", mchp_eic_init) +IRQCHIP_MATCH("microchip,sama7g5-eic", mchp_eic_probe) IRQCHIP_PLATFORM_DRIVER_END(mchp_eic) MODULE_DESCRIPTION("Microchip External Interrupt Controller"); diff --git a/drivers/irqchip/irq-meson-gpio.c b/drivers/irqchip/irq-meson-gpio.c index 7d177626d64ba4..09ebf1d9c21b03 100644 --- a/drivers/irqchip/irq-meson-gpio.c +++ b/drivers/irqchip/irq-meson-gpio.c @@ -572,8 +572,9 @@ static int meson_gpio_irq_parse_dt(struct device_node *node, struct meson_gpio_i return 0; } -static int meson_gpio_irq_of_init(struct device_node *node, struct device_node *parent) +static int meson_gpio_irq_probe(struct platform_device *pdev, struct device_node *parent) { + struct device_node *node = pdev->dev.of_node; struct irq_domain *domain, *parent_domain; struct meson_gpio_irq_controller *ctl; int ret; @@ -630,7 +631,7 @@ static int meson_gpio_irq_of_init(struct device_node *node, struct device_node * } IRQCHIP_PLATFORM_DRIVER_BEGIN(meson_gpio_intc) -IRQCHIP_MATCH("amlogic,meson-gpio-intc", meson_gpio_irq_of_init) +IRQCHIP_MATCH("amlogic,meson-gpio-intc", meson_gpio_irq_probe) IRQCHIP_PLATFORM_DRIVER_END(meson_gpio_intc) MODULE_AUTHOR("Jerome Brunet "); diff --git a/drivers/irqchip/irq-qcom-mpm.c b/drivers/irqchip/irq-qcom-mpm.c index 8d569f7c5a7aaa..83f31ea657b74a 100644 --- a/drivers/irqchip/irq-qcom-mpm.c +++ b/drivers/irqchip/irq-qcom-mpm.c @@ -320,9 +320,9 @@ static bool gic_hwirq_is_mapped(struct mpm_gic_map *maps, int cnt, u32 hwirq) return false; } -static int qcom_mpm_init(struct device_node *np, struct device_node *parent) +static int qcom_mpm_probe(struct platform_device *pdev, struct device_node *parent) { - struct platform_device *pdev = of_find_device_by_node(np); + struct device_node *np = pdev->dev.of_node; struct device *dev = &pdev->dev; struct irq_domain *parent_domain; struct generic_pm_domain *genpd; @@ -478,7 +478,7 @@ static int qcom_mpm_init(struct device_node *np, struct device_node *parent) } IRQCHIP_PLATFORM_DRIVER_BEGIN(qcom_mpm) -IRQCHIP_MATCH("qcom,mpm", qcom_mpm_init) +IRQCHIP_MATCH("qcom,mpm", qcom_mpm_probe) IRQCHIP_PLATFORM_DRIVER_END(qcom_mpm) MODULE_DESCRIPTION("Qualcomm Technologies, Inc. MSM Power Manager"); MODULE_LICENSE("GPL v2"); diff --git a/drivers/irqchip/irq-renesas-rzg2l.c b/drivers/irqchip/irq-renesas-rzg2l.c index 12b6eb1503016e..1bf19deb02c4e9 100644 --- a/drivers/irqchip/irq-renesas-rzg2l.c +++ b/drivers/irqchip/irq-renesas-rzg2l.c @@ -8,7 +8,6 @@ */ #include -#include #include #include #include @@ -528,18 +527,15 @@ static int rzg2l_irqc_parse_interrupts(struct rzg2l_irqc_priv *priv, return 0; } -static int rzg2l_irqc_common_init(struct device_node *node, struct device_node *parent, - const struct irq_chip *irq_chip) +static int rzg2l_irqc_common_probe(struct platform_device *pdev, struct device_node *parent, + const struct irq_chip *irq_chip) { - struct platform_device *pdev = of_find_device_by_node(node); - struct device *dev __free(put_device) = pdev ? &pdev->dev : NULL; struct irq_domain *irq_domain, *parent_domain; + struct device_node *node = pdev->dev.of_node; + struct device *dev = &pdev->dev; struct reset_control *resetn; int ret; - if (!pdev) - return -ENODEV; - parent_domain = irq_find_host(parent); if (!parent_domain) return dev_err_probe(dev, -ENODEV, "cannot find parent domain\n"); @@ -583,33 +579,22 @@ static int rzg2l_irqc_common_init(struct device_node *node, struct device_node * register_syscore_ops(&rzg2l_irqc_syscore_ops); - /* - * Prevent the cleanup function from invoking put_device by assigning - * NULL to dev. - * - * make coccicheck will complain about missing put_device calls, but - * those are false positives, as dev will be automatically "put" via - * __free_put_device on the failing path. - * On the successful path we don't actually want to "put" dev. - */ - dev = NULL; - return 0; } -static int rzg2l_irqc_init(struct device_node *node, struct device_node *parent) +static int rzg2l_irqc_probe(struct platform_device *pdev, struct device_node *parent) { - return rzg2l_irqc_common_init(node, parent, &rzg2l_irqc_chip); + return rzg2l_irqc_common_probe(pdev, parent, &rzg2l_irqc_chip); } -static int rzfive_irqc_init(struct device_node *node, struct device_node *parent) +static int rzfive_irqc_probe(struct platform_device *pdev, struct device_node *parent) { - return rzg2l_irqc_common_init(node, parent, &rzfive_irqc_chip); + return rzg2l_irqc_common_probe(pdev, parent, &rzfive_irqc_chip); } IRQCHIP_PLATFORM_DRIVER_BEGIN(rzg2l_irqc) -IRQCHIP_MATCH("renesas,rzg2l-irqc", rzg2l_irqc_init) -IRQCHIP_MATCH("renesas,r9a07g043f-irqc", rzfive_irqc_init) +IRQCHIP_MATCH("renesas,rzg2l-irqc", rzg2l_irqc_probe) +IRQCHIP_MATCH("renesas,r9a07g043f-irqc", rzfive_irqc_probe) IRQCHIP_PLATFORM_DRIVER_END(rzg2l_irqc) MODULE_AUTHOR("Lad Prabhakar "); MODULE_DESCRIPTION("Renesas RZ/G2L IRQC Driver"); diff --git a/drivers/irqchip/irq-renesas-rzv2h.c b/drivers/irqchip/irq-renesas-rzv2h.c index 9018d9c3911e3e..899a423b5da8ff 100644 --- a/drivers/irqchip/irq-renesas-rzv2h.c +++ b/drivers/irqchip/irq-renesas-rzv2h.c @@ -490,29 +490,15 @@ static int rzv2h_icu_parse_interrupts(struct rzv2h_icu_priv *priv, struct device return 0; } -static void rzv2h_icu_put_device(void *data) -{ - put_device(data); -} - -static int rzv2h_icu_init_common(struct device_node *node, struct device_node *parent, - const struct rzv2h_hw_info *hw_info) +static int rzv2h_icu_probe_common(struct platform_device *pdev, struct device_node *parent, + const struct rzv2h_hw_info *hw_info) { struct irq_domain *irq_domain, *parent_domain; + struct device_node *node = pdev->dev.of_node; struct rzv2h_icu_priv *rzv2h_icu_data; - struct platform_device *pdev; struct reset_control *resetn; int ret; - pdev = of_find_device_by_node(node); - if (!pdev) - return -ENODEV; - - ret = devm_add_action_or_reset(&pdev->dev, rzv2h_icu_put_device, - &pdev->dev); - if (ret < 0) - return ret; - parent_domain = irq_find_host(parent); if (!parent_domain) { dev_err(&pdev->dev, "cannot find parent domain\n"); @@ -618,19 +604,19 @@ static const struct rzv2h_hw_info rzv2h_hw_params = { .field_width = 8, }; -static int rzg3e_icu_init(struct device_node *node, struct device_node *parent) +static int rzg3e_icu_probe(struct platform_device *pdev, struct device_node *parent) { - return rzv2h_icu_init_common(node, parent, &rzg3e_hw_params); + return rzv2h_icu_probe_common(pdev, parent, &rzg3e_hw_params); } -static int rzv2h_icu_init(struct device_node *node, struct device_node *parent) +static int rzv2h_icu_probe(struct platform_device *pdev, struct device_node *parent) { - return rzv2h_icu_init_common(node, parent, &rzv2h_hw_params); + return rzv2h_icu_probe_common(pdev, parent, &rzv2h_hw_params); } IRQCHIP_PLATFORM_DRIVER_BEGIN(rzv2h_icu) -IRQCHIP_MATCH("renesas,r9a09g047-icu", rzg3e_icu_init) -IRQCHIP_MATCH("renesas,r9a09g057-icu", rzv2h_icu_init) +IRQCHIP_MATCH("renesas,r9a09g047-icu", rzg3e_icu_probe) +IRQCHIP_MATCH("renesas,r9a09g057-icu", rzv2h_icu_probe) IRQCHIP_PLATFORM_DRIVER_END(rzv2h_icu) MODULE_AUTHOR("Fabrizio Castro "); MODULE_DESCRIPTION("Renesas RZ/V2H(P) ICU Driver"); diff --git a/drivers/irqchip/irq-starfive-jh8100-intc.c b/drivers/irqchip/irq-starfive-jh8100-intc.c index 117f2c651ebd00..705361b4ebe07d 100644 --- a/drivers/irqchip/irq-starfive-jh8100-intc.c +++ b/drivers/irqchip/irq-starfive-jh8100-intc.c @@ -114,8 +114,9 @@ static void starfive_intc_irq_handler(struct irq_desc *desc) chained_irq_exit(chip, desc); } -static int starfive_intc_init(struct device_node *intc, struct device_node *parent) +static int starfive_intc_probe(struct platform_device *pdev, struct device_node *parent) { + struct device_node *intc = pdev->dev.of_node; struct starfive_irq_chip *irqc; struct reset_control *rst; struct clk *clk; @@ -198,7 +199,7 @@ static int starfive_intc_init(struct device_node *intc, struct device_node *pare } IRQCHIP_PLATFORM_DRIVER_BEGIN(starfive_intc) -IRQCHIP_MATCH("starfive,jh8100-intc", starfive_intc_init) +IRQCHIP_MATCH("starfive,jh8100-intc", starfive_intc_probe) IRQCHIP_PLATFORM_DRIVER_END(starfive_intc) MODULE_DESCRIPTION("StarFive JH8100 External Interrupt Controller"); diff --git a/drivers/irqchip/irqchip.c b/drivers/irqchip/irqchip.c index 652d20d2b07f18..689c8e44890192 100644 --- a/drivers/irqchip/irqchip.c +++ b/drivers/irqchip/irqchip.c @@ -36,9 +36,9 @@ int platform_irqchip_probe(struct platform_device *pdev) { struct device_node *np = pdev->dev.of_node; struct device_node *par_np __free(device_node) = of_irq_find_parent(np); - of_irq_init_cb_t irq_init_cb = of_device_get_match_data(&pdev->dev); + platform_irq_probe_t irq_probe = of_device_get_match_data(&pdev->dev); - if (!irq_init_cb) + if (!irq_probe) return -EINVAL; if (par_np == np) @@ -55,6 +55,6 @@ int platform_irqchip_probe(struct platform_device *pdev) if (par_np && !irq_find_matching_host(par_np, DOMAIN_BUS_ANY)) return -EPROBE_DEFER; - return irq_init_cb(np, par_np); + return irq_probe(pdev, par_np); } EXPORT_SYMBOL_GPL(platform_irqchip_probe); diff --git a/drivers/irqchip/qcom-pdc.c b/drivers/irqchip/qcom-pdc.c index 52d77546aacb95..518f7f0f3dab57 100644 --- a/drivers/irqchip/qcom-pdc.c +++ b/drivers/irqchip/qcom-pdc.c @@ -350,9 +350,10 @@ static int pdc_setup_pin_mapping(struct device_node *np) #define QCOM_PDC_SIZE 0x30000 -static int qcom_pdc_init(struct device_node *node, struct device_node *parent) +static int qcom_pdc_probe(struct platform_device *pdev, struct device_node *parent) { struct irq_domain *parent_domain, *pdc_domain; + struct device_node *node = pdev->dev.of_node; resource_size_t res_size; struct resource res; int ret; @@ -428,7 +429,7 @@ static int qcom_pdc_init(struct device_node *node, struct device_node *parent) } IRQCHIP_PLATFORM_DRIVER_BEGIN(qcom_pdc) -IRQCHIP_MATCH("qcom,pdc", qcom_pdc_init) +IRQCHIP_MATCH("qcom,pdc", qcom_pdc_probe) IRQCHIP_PLATFORM_DRIVER_END(qcom_pdc) MODULE_DESCRIPTION("Qualcomm Technologies, Inc. Power Domain Controller"); MODULE_LICENSE("GPL v2"); diff --git a/include/linux/irqchip.h b/include/linux/irqchip.h index d5e6024cb2a8c1..bc4ddacd6ddc17 100644 --- a/include/linux/irqchip.h +++ b/include/linux/irqchip.h @@ -17,12 +17,18 @@ #include #include +typedef int (*platform_irq_probe_t)(struct platform_device *, struct device_node *); + /* Undefined on purpose */ extern of_irq_init_cb_t typecheck_irq_init_cb; +extern platform_irq_probe_t typecheck_irq_probe; #define typecheck_irq_init_cb(fn) \ (__typecheck(typecheck_irq_init_cb, &fn) ? fn : fn) +#define typecheck_irq_probe(fn) \ + (__typecheck(typecheck_irq_probe, &fn) ? fn : fn) + /* * This macro must be used by the different irqchip drivers to declare * the association between their DT compatible string and their @@ -42,7 +48,7 @@ extern int platform_irqchip_probe(struct platform_device *pdev); static const struct of_device_id drv_name##_irqchip_match_table[] = { #define IRQCHIP_MATCH(compat, fn) { .compatible = compat, \ - .data = typecheck_irq_init_cb(fn), }, + .data = typecheck_irq_probe(fn), }, #define IRQCHIP_PLATFORM_DRIVER_END(drv_name, ...) \ From 841b3868a64f6f3fd06bf7797ba149eb53c2ee45 Mon Sep 17 00:00:00 2001 From: Herbert Xu Date: Wed, 24 Sep 2025 18:20:17 +0800 Subject: [PATCH 0425/3902] crypto: authenc - Correctly pass EINPROGRESS back up to the caller [ Upstream commit 96feb73def02d175850daa0e7c2c90c876681b5c ] When authenc is invoked with MAY_BACKLOG, it needs to pass EINPROGRESS notifications back up to the caller when the underlying algorithm returns EBUSY synchronously. However, if the EBUSY comes from the second part of an authenc call, i.e., it is asynchronous, both the EBUSY and the subsequent EINPROGRESS notification must not be passed to the caller. Implement this by passing a mask to the function that starts the second half of authenc and using it to determine whether EBUSY and EINPROGRESS should be passed to the caller. This was a deficiency in the original implementation of authenc because it was not expected to be used with MAY_BACKLOG. Reported-by: Ingo Franzki Reported-by: Mikulas Patocka Fixes: 180ce7e81030 ("crypto: authenc - Add EINPROGRESS check") Signed-off-by: Herbert Xu Signed-off-by: Sasha Levin --- crypto/authenc.c | 75 ++++++++++++++++++++++++++++++++---------------- 1 file changed, 50 insertions(+), 25 deletions(-) diff --git a/crypto/authenc.c b/crypto/authenc.c index a723769c87779d..ac679ce2cb9533 100644 --- a/crypto/authenc.c +++ b/crypto/authenc.c @@ -37,7 +37,7 @@ struct authenc_request_ctx { static void authenc_request_complete(struct aead_request *req, int err) { - if (err != -EINPROGRESS) + if (err != -EINPROGRESS && err != -EBUSY) aead_request_complete(req, err); } @@ -107,27 +107,42 @@ static int crypto_authenc_setkey(struct crypto_aead *authenc, const u8 *key, return err; } -static void authenc_geniv_ahash_done(void *data, int err) +static void authenc_geniv_ahash_finish(struct aead_request *req) { - struct aead_request *req = data; struct crypto_aead *authenc = crypto_aead_reqtfm(req); struct aead_instance *inst = aead_alg_instance(authenc); struct authenc_instance_ctx *ictx = aead_instance_ctx(inst); struct authenc_request_ctx *areq_ctx = aead_request_ctx(req); struct ahash_request *ahreq = (void *)(areq_ctx->tail + ictx->reqoff); - if (err) - goto out; - scatterwalk_map_and_copy(ahreq->result, req->dst, req->assoclen + req->cryptlen, crypto_aead_authsize(authenc), 1); +} -out: +static void authenc_geniv_ahash_done(void *data, int err) +{ + struct aead_request *req = data; + + if (!err) + authenc_geniv_ahash_finish(req); aead_request_complete(req, err); } -static int crypto_authenc_genicv(struct aead_request *req, unsigned int flags) +/* + * Used when the ahash request was invoked in the async callback context + * of the previous skcipher request. Eat any EINPROGRESS notifications. + */ +static void authenc_geniv_ahash_done2(void *data, int err) +{ + struct aead_request *req = data; + + if (!err) + authenc_geniv_ahash_finish(req); + authenc_request_complete(req, err); +} + +static int crypto_authenc_genicv(struct aead_request *req, unsigned int mask) { struct crypto_aead *authenc = crypto_aead_reqtfm(req); struct aead_instance *inst = aead_alg_instance(authenc); @@ -136,6 +151,7 @@ static int crypto_authenc_genicv(struct aead_request *req, unsigned int flags) struct crypto_ahash *auth = ctx->auth; struct authenc_request_ctx *areq_ctx = aead_request_ctx(req); struct ahash_request *ahreq = (void *)(areq_ctx->tail + ictx->reqoff); + unsigned int flags = aead_request_flags(req) & ~mask; u8 *hash = areq_ctx->tail; int err; @@ -143,7 +159,8 @@ static int crypto_authenc_genicv(struct aead_request *req, unsigned int flags) ahash_request_set_crypt(ahreq, req->dst, hash, req->assoclen + req->cryptlen); ahash_request_set_callback(ahreq, flags, - authenc_geniv_ahash_done, req); + mask ? authenc_geniv_ahash_done2 : + authenc_geniv_ahash_done, req); err = crypto_ahash_digest(ahreq); if (err) @@ -159,12 +176,11 @@ static void crypto_authenc_encrypt_done(void *data, int err) { struct aead_request *areq = data; - if (err) - goto out; - - err = crypto_authenc_genicv(areq, 0); - -out: + if (err) { + aead_request_complete(areq, err); + return; + } + err = crypto_authenc_genicv(areq, CRYPTO_TFM_REQ_MAY_SLEEP); authenc_request_complete(areq, err); } @@ -199,11 +215,18 @@ static int crypto_authenc_encrypt(struct aead_request *req) if (err) return err; - return crypto_authenc_genicv(req, aead_request_flags(req)); + return crypto_authenc_genicv(req, 0); +} + +static void authenc_decrypt_tail_done(void *data, int err) +{ + struct aead_request *req = data; + + authenc_request_complete(req, err); } static int crypto_authenc_decrypt_tail(struct aead_request *req, - unsigned int flags) + unsigned int mask) { struct crypto_aead *authenc = crypto_aead_reqtfm(req); struct aead_instance *inst = aead_alg_instance(authenc); @@ -214,6 +237,7 @@ static int crypto_authenc_decrypt_tail(struct aead_request *req, struct skcipher_request *skreq = (void *)(areq_ctx->tail + ictx->reqoff); unsigned int authsize = crypto_aead_authsize(authenc); + unsigned int flags = aead_request_flags(req) & ~mask; u8 *ihash = ahreq->result + authsize; struct scatterlist *src, *dst; @@ -230,7 +254,9 @@ static int crypto_authenc_decrypt_tail(struct aead_request *req, skcipher_request_set_tfm(skreq, ctx->enc); skcipher_request_set_callback(skreq, flags, - req->base.complete, req->base.data); + mask ? authenc_decrypt_tail_done : + req->base.complete, + mask ? req : req->base.data); skcipher_request_set_crypt(skreq, src, dst, req->cryptlen - authsize, req->iv); @@ -241,12 +267,11 @@ static void authenc_verify_ahash_done(void *data, int err) { struct aead_request *req = data; - if (err) - goto out; - - err = crypto_authenc_decrypt_tail(req, 0); - -out: + if (err) { + aead_request_complete(req, err); + return; + } + err = crypto_authenc_decrypt_tail(req, CRYPTO_TFM_REQ_MAY_SLEEP); authenc_request_complete(req, err); } @@ -273,7 +298,7 @@ static int crypto_authenc_decrypt(struct aead_request *req) if (err) return err; - return crypto_authenc_decrypt_tail(req, aead_request_flags(req)); + return crypto_authenc_decrypt_tail(req, 0); } static int crypto_authenc_init_tfm(struct crypto_aead *tfm) From 81ffe9a265df3e41534726b852ab08792e3d374d Mon Sep 17 00:00:00 2001 From: Raphael Pinsonneault-Thibeault Date: Sun, 12 Oct 2025 16:16:34 -0400 Subject: [PATCH 0426/3902] ntfs3: fix uninit memory after failed mi_read in mi_format_new MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [ Upstream commit 73e6b9dacf72a1e7a4265eacca46f8f33e0997d6 ] Fix a KMSAN un-init bug found by syzkaller. ntfs_get_bh() expects a buffer from sb_getblk(), that buffer may not be uptodate. We do not bring the buffer uptodate before setting it as uptodate. If the buffer were to not be uptodate, it could mean adding a buffer with un-init data to the mi record. Attempting to load that record will trigger KMSAN. Avoid this by setting the buffer as uptodate, if it’s not already, by overwriting it. Reported-by: syzbot+7a2ba6b7b66340cff225@syzkaller.appspotmail.com Closes: https://syzkaller.appspot.com/bug?extid=7a2ba6b7b66340cff225 Tested-by: syzbot+7a2ba6b7b66340cff225@syzkaller.appspotmail.com Fixes: 4342306f0f0d5 ("fs/ntfs3: Add file operations and implementation") Signed-off-by: Raphael Pinsonneault-Thibeault Signed-off-by: Konstantin Komarov Signed-off-by: Sasha Levin --- fs/ntfs3/fsntfs.c | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/fs/ntfs3/fsntfs.c b/fs/ntfs3/fsntfs.c index c7a2f191254dad..5ae910e9ecbda4 100644 --- a/fs/ntfs3/fsntfs.c +++ b/fs/ntfs3/fsntfs.c @@ -1349,7 +1349,14 @@ int ntfs_get_bh(struct ntfs_sb_info *sbi, const struct runs_tree *run, u64 vbo, } if (buffer_locked(bh)) __wait_on_buffer(bh); - set_buffer_uptodate(bh); + + lock_buffer(bh); + if (!buffer_uptodate(bh)) + { + memset(bh->b_data, 0, blocksize); + set_buffer_uptodate(bh); + } + unlock_buffer(bh); } else { bh = ntfs_bread(sb, block); if (!bh) { From b40a4eb4a0543d49686a6e693745009dac3b86a9 Mon Sep 17 00:00:00 2001 From: Sidharth Seela Date: Tue, 23 Sep 2025 12:10:16 +0530 Subject: [PATCH 0427/3902] ntfs3: Fix uninit buffer allocated by __getname() [ Upstream commit 9948dcb2f7b5a1bf8e8710eafaf6016e00be3ad6 ] Fix uninit errors caused after buffer allocation given to 'de'; by initializing the buffer with zeroes. The fix was found by using KMSAN. Reported-by: syzbot+332bd4e9d148f11a87dc@syzkaller.appspotmail.com Fixes: 78ab59fee07f2 ("fs/ntfs3: Rework file operations") Signed-off-by: Sidharth Seela Signed-off-by: Konstantin Komarov Signed-off-by: Sasha Levin --- fs/ntfs3/inode.c | 1 + 1 file changed, 1 insertion(+) diff --git a/fs/ntfs3/inode.c b/fs/ntfs3/inode.c index 3959f23c487a2c..3a0676871badec 100644 --- a/fs/ntfs3/inode.c +++ b/fs/ntfs3/inode.c @@ -1722,6 +1722,7 @@ int ntfs_link_inode(struct inode *inode, struct dentry *dentry) de = __getname(); if (!de) return -ENOMEM; + memset(de, 0, PATH_MAX); /* Mark rw ntfs as dirty. It will be cleared at umount. */ ntfs_set_state(sbi, NTFS_DIRTY_DIRTY); From fdbf4423bb81168fc670c5e99f2e7523bd68b095 Mon Sep 17 00:00:00 2001 From: Kathiravan Thirumoorthy Date: Tue, 14 Oct 2025 10:25:01 +0530 Subject: [PATCH 0428/3902] arm64: dts: qcom: ipq5424: correct the TF-A reserved memory to 512K [ Upstream commit 28803705b552a0a711fa849490f14dca2bc5296e ] Correct the reserved memory size for TF-A to 512K, as it was mistakenly marked as 500K. Update the reserved memory node accordingly. Fixes: 8517204c982b ("arm64: dts: qcom: ipq5424: Add reserved memory for TF-A") Signed-off-by: Kathiravan Thirumoorthy Link: https://lore.kernel.org/r/20251014-tfa-reserved-mem-v1-1-48c82033c8a7@oss.qualcomm.com Signed-off-by: Bjorn Andersson Signed-off-by: Sasha Levin --- arch/arm64/boot/dts/qcom/ipq5424.dtsi | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/arch/arm64/boot/dts/qcom/ipq5424.dtsi b/arch/arm64/boot/dts/qcom/ipq5424.dtsi index ef2b52f3597d9b..227d5ce2975151 100644 --- a/arch/arm64/boot/dts/qcom/ipq5424.dtsi +++ b/arch/arm64/boot/dts/qcom/ipq5424.dtsi @@ -213,7 +213,7 @@ }; tfa@8a832000 { - reg = <0x0 0x8a832000 0x0 0x7d000>; + reg = <0x0 0x8a832000 0x0 0x80000>; no-map; status = "disabled"; }; From d98d38c7f8ca522684919ba049069e8052677fc3 Mon Sep 17 00:00:00 2001 From: Francesco Lavra Date: Fri, 17 Oct 2025 18:42:54 +0200 Subject: [PATCH 0429/3902] iio: imu: st_lsm6dsx: Fix measurement unit for odr struct member [ Upstream commit c6d702f2b77194b62fb2098c63bb7f2a87da142d ] The `odr` field in struct st_lsm6dsx_sensor contains a data rate value expressed in mHz, not in Hz. Fixes: f8710f0357bc3 ("iio: imu: st_lsm6dsx: express odr in mHZ") Signed-off-by: Francesco Lavra Acked-by: Lorenzo Bianconi Signed-off-by: Jonathan Cameron Signed-off-by: Sasha Levin --- drivers/iio/imu/st_lsm6dsx/st_lsm6dsx.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/iio/imu/st_lsm6dsx/st_lsm6dsx.h b/drivers/iio/imu/st_lsm6dsx/st_lsm6dsx.h index 381b016fa52434..56244d49ab2fcc 100644 --- a/drivers/iio/imu/st_lsm6dsx/st_lsm6dsx.h +++ b/drivers/iio/imu/st_lsm6dsx/st_lsm6dsx.h @@ -383,7 +383,7 @@ enum st_lsm6dsx_fifo_mode { * @id: Sensor identifier. * @hw: Pointer to instance of struct st_lsm6dsx_hw. * @gain: Configured sensor sensitivity. - * @odr: Output data rate of the sensor [Hz]. + * @odr: Output data rate of the sensor [mHz]. * @samples_to_discard: Number of samples to discard for filters settling time. * @watermark: Sensor watermark level. * @decimator: Sensor decimation factor. From b346cdd95f7ac367f66dcb7e591eed51961357e5 Mon Sep 17 00:00:00 2001 From: Randy Dunlap Date: Fri, 17 Oct 2025 12:13:23 -0700 Subject: [PATCH 0430/3902] firmware: qcom: tzmem: fix qcom_tzmem_policy kernel-doc [ Upstream commit edd548dc64a699d71ea4f537f815044e763d01e1 ] Fix kernel-doc warnings by using correct kernel-doc syntax and formatting to prevent warnings: Warning: include/linux/firmware/qcom/qcom_tzmem.h:25 Enum value 'QCOM_TZMEM_POLICY_STATIC' not described in enum 'qcom_tzmem_policy' Warning: ../include/linux/firmware/qcom/qcom_tzmem.h:25 Enum value 'QCOM_TZMEM_POLICY_MULTIPLIER' not described in enum 'qcom_tzmem_policy' Warning: ../include/linux/firmware/qcom/qcom_tzmem.h:25 Enum value 'QCOM_TZMEM_POLICY_ON_DEMAND' not described in enum 'qcom_tzmem_policy' Fixes: 84f5a7b67b61 ("firmware: qcom: add a dedicated TrustZone buffer allocator") Signed-off-by: Randy Dunlap Link: https://lore.kernel.org/r/20251017191323.1820167-1-rdunlap@infradead.org Signed-off-by: Bjorn Andersson Signed-off-by: Sasha Levin --- include/linux/firmware/qcom/qcom_tzmem.h | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/include/linux/firmware/qcom/qcom_tzmem.h b/include/linux/firmware/qcom/qcom_tzmem.h index 48ac0e5454c7fc..23173e0c3dddd1 100644 --- a/include/linux/firmware/qcom/qcom_tzmem.h +++ b/include/linux/firmware/qcom/qcom_tzmem.h @@ -17,11 +17,20 @@ struct qcom_tzmem_pool; * enum qcom_tzmem_policy - Policy for pool growth. */ enum qcom_tzmem_policy { - /**< Static pool, never grow above initial size. */ + /** + * @QCOM_TZMEM_POLICY_STATIC: Static pool, + * never grow above initial size. + */ QCOM_TZMEM_POLICY_STATIC = 1, - /**< When out of memory, add increment * current size of memory. */ + /** + * @QCOM_TZMEM_POLICY_MULTIPLIER: When out of memory, + * add increment * current size of memory. + */ QCOM_TZMEM_POLICY_MULTIPLIER, - /**< When out of memory add as much as is needed until max_size. */ + /** + * @QCOM_TZMEM_POLICY_ON_DEMAND: When out of memory + * add as much as is needed until max_size. + */ QCOM_TZMEM_POLICY_ON_DEMAND, }; From 67c42f3ec0bdff098c804b9132ad204c2804829c Mon Sep 17 00:00:00 2001 From: Ian Rogers Date: Fri, 17 Oct 2025 16:03:57 -0700 Subject: [PATCH 0431/3902] perf parse-events: Make X modifier more respectful of groups [ Upstream commit 800201997a509c298e74696da3586d82b1a2b6f4 ] Events with an X modifier were reordered within a group, for example slots was made the leader in: ``` $ perf record -e '{cpu/mem-stores/ppu,cpu/slots/uX}' -- sleep 1 ``` Fix by making `dont_regroup` evsels always use their index for sorting. Make the cur_leader, when fixing the groups, be that of `dont_regroup` evsel so that the `dont_regroup` evsel doesn't become a leader. On a tigerlake this patch corrects this and meets expectations in: ``` $ perf stat -e '{cpu/mem-stores/,cpu/slots/uX}' -a -- sleep 0.1 Performance counter stats for 'system wide': 83,458,652 cpu/mem-stores/ 2,720,854,880 cpu/slots/uX 0.103780587 seconds time elapsed $ perf stat -e 'slots,slots:X' -a -- sleep 0.1 Performance counter stats for 'system wide': 732,042,247 slots (48.96%) 643,288,155 slots:X (51.04%) 0.102731018 seconds time elapsed ``` Closes: https://lore.kernel.org/lkml/18f20d38-070c-4e17-bc90-cf7102e1e53d@linux.intel.com/ Fixes: 035c17893082 ("perf parse-events: Add 'X' modifier to exclude an event from being regrouped") Signed-off-by: Ian Rogers Signed-off-by: Namhyung Kim Signed-off-by: Sasha Levin --- tools/perf/util/parse-events.c | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/tools/perf/util/parse-events.c b/tools/perf/util/parse-events.c index 90a765016f64f3..cd9315d3ca1171 100644 --- a/tools/perf/util/parse-events.c +++ b/tools/perf/util/parse-events.c @@ -2121,14 +2121,18 @@ static int evlist__cmp(void *_fg_idx, const struct list_head *l, const struct li * event's index is used. An index may be forced for events that * must be in the same group, namely Intel topdown events. */ - if (*force_grouped_idx != -1 && arch_evsel__must_be_in_group(lhs)) { + if (lhs->dont_regroup) { + lhs_sort_idx = lhs_core->idx; + } else if (*force_grouped_idx != -1 && arch_evsel__must_be_in_group(lhs)) { lhs_sort_idx = *force_grouped_idx; } else { bool lhs_has_group = lhs_core->leader != lhs_core || lhs_core->nr_members > 1; lhs_sort_idx = lhs_has_group ? lhs_core->leader->idx : lhs_core->idx; } - if (*force_grouped_idx != -1 && arch_evsel__must_be_in_group(rhs)) { + if (rhs->dont_regroup) { + rhs_sort_idx = rhs_core->idx; + } else if (*force_grouped_idx != -1 && arch_evsel__must_be_in_group(rhs)) { rhs_sort_idx = *force_grouped_idx; } else { bool rhs_has_group = rhs_core->leader != rhs_core || rhs_core->nr_members > 1; @@ -2226,10 +2230,10 @@ static int parse_events__sort_events_and_fix_groups(struct list_head *list) */ idx = 0; list_for_each_entry(pos, list, core.node) { - const struct evsel *pos_leader = evsel__leader(pos); + struct evsel *pos_leader = evsel__leader(pos); const char *pos_pmu_name = pos->group_pmu_name; const char *cur_leader_pmu_name; - bool pos_force_grouped = force_grouped_idx != -1 && + bool pos_force_grouped = force_grouped_idx != -1 && !pos->dont_regroup && arch_evsel__must_be_in_group(pos); /* Reset index and nr_members. */ @@ -2243,8 +2247,8 @@ static int parse_events__sort_events_and_fix_groups(struct list_head *list) * groups can't span PMUs. */ if (!cur_leader || pos->dont_regroup) { - cur_leader = pos; - cur_leaders_grp = &pos->core; + cur_leader = pos->dont_regroup ? pos_leader : pos; + cur_leaders_grp = &cur_leader->core; if (pos_force_grouped) force_grouped_leader = pos; } From 12b413f5460c393d1151a37f591140693eca0f84 Mon Sep 17 00:00:00 2001 From: T Pratham Date: Wed, 8 Oct 2025 15:03:14 +0530 Subject: [PATCH 0432/3902] crypto: aead - Fix reqsize handling [ Upstream commit 9b04d8f00569573796dd05397f5779135593eb24 ] Commit afddce13ce81d ("crypto: api - Add reqsize to crypto_alg") introduced cra_reqsize field in crypto_alg struct to replace type specific reqsize fields. It looks like this was introduced specifically for ahash and acomp from the commit description as subsequent commits add necessary changes in these alg frameworks. However, this is being recommended for use in all crypto algs instead of setting reqsize using crypto_*_set_reqsize(). Using cra_reqsize in aead algorithms, hence, causes memory corruptions and crashes as the underlying functions in the algorithm framework have not been updated to set the reqsize properly from cra_reqsize. [1] Add proper set_reqsize calls in the aead init function to properly initialize reqsize for these algorithms in the framework. [1]: https://gist.github.com/Pratham-T/24247446f1faf4b7843e4014d5089f6b Fixes: afddce13ce81d ("crypto: api - Add reqsize to crypto_alg") Signed-off-by: T Pratham Signed-off-by: Herbert Xu Signed-off-by: Sasha Levin --- crypto/aead.c | 1 + 1 file changed, 1 insertion(+) diff --git a/crypto/aead.c b/crypto/aead.c index 5d14b775036eeb..51ab3af691af25 100644 --- a/crypto/aead.c +++ b/crypto/aead.c @@ -120,6 +120,7 @@ static int crypto_aead_init_tfm(struct crypto_tfm *tfm) struct aead_alg *alg = crypto_aead_alg(aead); crypto_aead_set_flags(aead, CRYPTO_TFM_NEED_KEY); + crypto_aead_set_reqsize(aead, crypto_tfm_alg_reqsize(tfm)); aead->authsize = alg->maxauthsize; From a43afb170963876684ac895c4ea1bdb2f2bbf978 Mon Sep 17 00:00:00 2001 From: Christophe JAILLET Date: Mon, 29 Sep 2025 20:13:22 +0200 Subject: [PATCH 0433/3902] PCI: sg2042: Fix a reference count issue in sg2042_pcie_remove() [ Upstream commit 932ec9dff6da40382ee63049a11a6ff047bdc259 ] devm_pm_runtime_enable() is used in the probe, so pm_runtime_disable() should not be called explicitly in the remove function. Fixes: 1c72774df028 ("PCI: sg2042: Add Sophgo SG2042 PCIe driver") Signed-off-by: Christophe JAILLET Signed-off-by: Manivannan Sadhasivam Tested-by: Chen Wang # on Pioneerbox. Acked-by: Chen Wang Link: https://patch.msgid.link/242eca0ff6601de7966a53706e9950fbcb10aac8.1759169586.git.christophe.jaillet@wanadoo.fr Signed-off-by: Sasha Levin --- drivers/pci/controller/cadence/pcie-sg2042.c | 3 --- 1 file changed, 3 deletions(-) diff --git a/drivers/pci/controller/cadence/pcie-sg2042.c b/drivers/pci/controller/cadence/pcie-sg2042.c index a077b28d489494..0c50c74d03eeb2 100644 --- a/drivers/pci/controller/cadence/pcie-sg2042.c +++ b/drivers/pci/controller/cadence/pcie-sg2042.c @@ -74,15 +74,12 @@ static int sg2042_pcie_probe(struct platform_device *pdev) static void sg2042_pcie_remove(struct platform_device *pdev) { struct cdns_pcie *pcie = platform_get_drvdata(pdev); - struct device *dev = &pdev->dev; struct cdns_pcie_rc *rc; rc = container_of(pcie, struct cdns_pcie_rc, pcie); cdns_pcie_host_disable(rc); cdns_pcie_disable_phy(pcie); - - pm_runtime_disable(dev); } static int sg2042_pcie_suspend_noirq(struct device *dev) From 95b122036d7a454f879954977ac0783f5c2dec13 Mon Sep 17 00:00:00 2001 From: Bart Van Assche Date: Mon, 13 Oct 2025 12:28:02 -0700 Subject: [PATCH 0434/3902] block/mq-deadline: Introduce dd_start_request() [ Upstream commit 93a358af59c6e8ab00b57cfdb1c437516a4948ca ] Prepare for adding a second caller of this function. No functionality has been changed. Cc: Damien Le Moal Cc: Yu Kuai Cc: chengkaitao Signed-off-by: Bart Van Assche Reviewed-by: Damien Le Moal Signed-off-by: Jens Axboe Stable-dep-of: d60055cf5270 ("block/mq-deadline: Switch back to a single dispatch list") Signed-off-by: Sasha Levin --- block/mq-deadline.c | 22 ++++++++++++++-------- 1 file changed, 14 insertions(+), 8 deletions(-) diff --git a/block/mq-deadline.c b/block/mq-deadline.c index 3e741d33142d3a..647a45f6d93527 100644 --- a/block/mq-deadline.c +++ b/block/mq-deadline.c @@ -306,6 +306,19 @@ static bool started_after(struct deadline_data *dd, struct request *rq, return time_after(start_time, latest_start); } +static struct request *dd_start_request(struct deadline_data *dd, + enum dd_data_dir data_dir, + struct request *rq) +{ + u8 ioprio_class = dd_rq_ioclass(rq); + enum dd_prio prio = ioprio_class_to_prio[ioprio_class]; + + dd->per_prio[prio].latest_pos[data_dir] = blk_rq_pos(rq); + dd->per_prio[prio].stats.dispatched++; + rq->rq_flags |= RQF_STARTED; + return rq; +} + /* * deadline_dispatch_requests selects the best request according to * read/write expire, fifo_batch, etc and with a start time <= @latest_start. @@ -316,8 +329,6 @@ static struct request *__dd_dispatch_request(struct deadline_data *dd, { struct request *rq, *next_rq; enum dd_data_dir data_dir; - enum dd_prio prio; - u8 ioprio_class; lockdep_assert_held(&dd->lock); @@ -411,12 +422,7 @@ static struct request *__dd_dispatch_request(struct deadline_data *dd, dd->batching++; deadline_move_request(dd, per_prio, rq); done: - ioprio_class = dd_rq_ioclass(rq); - prio = ioprio_class_to_prio[ioprio_class]; - dd->per_prio[prio].latest_pos[data_dir] = blk_rq_pos(rq); - dd->per_prio[prio].stats.dispatched++; - rq->rq_flags |= RQF_STARTED; - return rq; + return dd_start_request(dd, data_dir, rq); } /* From 204f13654142c91a6f01096ae7196a68eb3f32e9 Mon Sep 17 00:00:00 2001 From: Bart Van Assche Date: Mon, 13 Oct 2025 12:28:03 -0700 Subject: [PATCH 0435/3902] block/mq-deadline: Switch back to a single dispatch list [ Upstream commit d60055cf52703a705b86fb25b9b7931ec7ee399c ] Commit c807ab520fc3 ("block/mq-deadline: Add I/O priority support") modified the behavior of request flag BLK_MQ_INSERT_AT_HEAD from dispatching a request before other requests into dispatching a request before other requests with the same I/O priority. This is not correct since BLK_MQ_INSERT_AT_HEAD is used when requeuing requests and also when a flush request is inserted. Both types of requests should be dispatched as soon as possible. Hence, make the mq-deadline I/O scheduler again ignore the I/O priority for BLK_MQ_INSERT_AT_HEAD requests. Cc: Damien Le Moal Cc: Yu Kuai Reported-by: chengkaitao Closes: https://lore.kernel.org/linux-block/20251009155253.14611-1-pilgrimtao@gmail.com/ Fixes: c807ab520fc3 ("block/mq-deadline: Add I/O priority support") Signed-off-by: Bart Van Assche Reviewed-by: Damien Le Moalv Signed-off-by: Jens Axboe Signed-off-by: Sasha Levin --- block/mq-deadline.c | 107 +++++++++++++++++++------------------------- 1 file changed, 47 insertions(+), 60 deletions(-) diff --git a/block/mq-deadline.c b/block/mq-deadline.c index 647a45f6d93527..3e3719093aec72 100644 --- a/block/mq-deadline.c +++ b/block/mq-deadline.c @@ -71,7 +71,6 @@ struct io_stats_per_prio { * present on both sort_list[] and fifo_list[]. */ struct dd_per_prio { - struct list_head dispatch; struct rb_root sort_list[DD_DIR_COUNT]; struct list_head fifo_list[DD_DIR_COUNT]; /* Position of the most recently dispatched request. */ @@ -84,6 +83,7 @@ struct deadline_data { * run time data */ + struct list_head dispatch; struct dd_per_prio per_prio[DD_PRIO_COUNT]; /* Data direction of latest dispatched request. */ @@ -332,16 +332,6 @@ static struct request *__dd_dispatch_request(struct deadline_data *dd, lockdep_assert_held(&dd->lock); - if (!list_empty(&per_prio->dispatch)) { - rq = list_first_entry(&per_prio->dispatch, struct request, - queuelist); - if (started_after(dd, rq, latest_start)) - return NULL; - list_del_init(&rq->queuelist); - data_dir = rq_data_dir(rq); - goto done; - } - /* * batches are currently reads XOR writes */ @@ -421,7 +411,6 @@ static struct request *__dd_dispatch_request(struct deadline_data *dd, */ dd->batching++; deadline_move_request(dd, per_prio, rq); -done: return dd_start_request(dd, data_dir, rq); } @@ -469,6 +458,14 @@ static struct request *dd_dispatch_request(struct blk_mq_hw_ctx *hctx) enum dd_prio prio; spin_lock(&dd->lock); + + if (!list_empty(&dd->dispatch)) { + rq = list_first_entry(&dd->dispatch, struct request, queuelist); + list_del_init(&rq->queuelist); + dd_start_request(dd, rq_data_dir(rq), rq); + goto unlock; + } + rq = dd_dispatch_prio_aged_requests(dd, now); if (rq) goto unlock; @@ -557,10 +554,10 @@ static int dd_init_sched(struct request_queue *q, struct elevator_queue *eq) eq->elevator_data = dd; + INIT_LIST_HEAD(&dd->dispatch); for (prio = 0; prio <= DD_PRIO_MAX; prio++) { struct dd_per_prio *per_prio = &dd->per_prio[prio]; - INIT_LIST_HEAD(&per_prio->dispatch); INIT_LIST_HEAD(&per_prio->fifo_list[DD_READ]); INIT_LIST_HEAD(&per_prio->fifo_list[DD_WRITE]); per_prio->sort_list[DD_READ] = RB_ROOT; @@ -664,7 +661,7 @@ static void dd_insert_request(struct blk_mq_hw_ctx *hctx, struct request *rq, trace_block_rq_insert(rq); if (flags & BLK_MQ_INSERT_AT_HEAD) { - list_add(&rq->queuelist, &per_prio->dispatch); + list_add(&rq->queuelist, &dd->dispatch); rq->fifo_time = jiffies; } else { deadline_add_rq_rb(per_prio, rq); @@ -731,8 +728,7 @@ static void dd_finish_request(struct request *rq) static bool dd_has_work_for_prio(struct dd_per_prio *per_prio) { - return !list_empty_careful(&per_prio->dispatch) || - !list_empty_careful(&per_prio->fifo_list[DD_READ]) || + return !list_empty_careful(&per_prio->fifo_list[DD_READ]) || !list_empty_careful(&per_prio->fifo_list[DD_WRITE]); } @@ -741,6 +737,9 @@ static bool dd_has_work(struct blk_mq_hw_ctx *hctx) struct deadline_data *dd = hctx->queue->elevator->elevator_data; enum dd_prio prio; + if (!list_empty_careful(&dd->dispatch)) + return true; + for (prio = 0; prio <= DD_PRIO_MAX; prio++) if (dd_has_work_for_prio(&dd->per_prio[prio])) return true; @@ -949,49 +948,39 @@ static int dd_owned_by_driver_show(void *data, struct seq_file *m) return 0; } -#define DEADLINE_DISPATCH_ATTR(prio) \ -static void *deadline_dispatch##prio##_start(struct seq_file *m, \ - loff_t *pos) \ - __acquires(&dd->lock) \ -{ \ - struct request_queue *q = m->private; \ - struct deadline_data *dd = q->elevator->elevator_data; \ - struct dd_per_prio *per_prio = &dd->per_prio[prio]; \ - \ - spin_lock(&dd->lock); \ - return seq_list_start(&per_prio->dispatch, *pos); \ -} \ - \ -static void *deadline_dispatch##prio##_next(struct seq_file *m, \ - void *v, loff_t *pos) \ -{ \ - struct request_queue *q = m->private; \ - struct deadline_data *dd = q->elevator->elevator_data; \ - struct dd_per_prio *per_prio = &dd->per_prio[prio]; \ - \ - return seq_list_next(v, &per_prio->dispatch, pos); \ -} \ - \ -static void deadline_dispatch##prio##_stop(struct seq_file *m, void *v) \ - __releases(&dd->lock) \ -{ \ - struct request_queue *q = m->private; \ - struct deadline_data *dd = q->elevator->elevator_data; \ - \ - spin_unlock(&dd->lock); \ -} \ - \ -static const struct seq_operations deadline_dispatch##prio##_seq_ops = { \ - .start = deadline_dispatch##prio##_start, \ - .next = deadline_dispatch##prio##_next, \ - .stop = deadline_dispatch##prio##_stop, \ - .show = blk_mq_debugfs_rq_show, \ +static void *deadline_dispatch_start(struct seq_file *m, loff_t *pos) + __acquires(&dd->lock) +{ + struct request_queue *q = m->private; + struct deadline_data *dd = q->elevator->elevator_data; + + spin_lock(&dd->lock); + return seq_list_start(&dd->dispatch, *pos); } -DEADLINE_DISPATCH_ATTR(0); -DEADLINE_DISPATCH_ATTR(1); -DEADLINE_DISPATCH_ATTR(2); -#undef DEADLINE_DISPATCH_ATTR +static void *deadline_dispatch_next(struct seq_file *m, void *v, loff_t *pos) +{ + struct request_queue *q = m->private; + struct deadline_data *dd = q->elevator->elevator_data; + + return seq_list_next(v, &dd->dispatch, pos); +} + +static void deadline_dispatch_stop(struct seq_file *m, void *v) + __releases(&dd->lock) +{ + struct request_queue *q = m->private; + struct deadline_data *dd = q->elevator->elevator_data; + + spin_unlock(&dd->lock); +} + +static const struct seq_operations deadline_dispatch_seq_ops = { + .start = deadline_dispatch_start, + .next = deadline_dispatch_next, + .stop = deadline_dispatch_stop, + .show = blk_mq_debugfs_rq_show, +}; #define DEADLINE_QUEUE_DDIR_ATTRS(name) \ {#name "_fifo_list", 0400, \ @@ -1014,9 +1003,7 @@ static const struct blk_mq_debugfs_attr deadline_queue_debugfs_attrs[] = { {"batching", 0400, deadline_batching_show}, {"starved", 0400, deadline_starved_show}, {"async_depth", 0400, dd_async_depth_show}, - {"dispatch0", 0400, .seq_ops = &deadline_dispatch0_seq_ops}, - {"dispatch1", 0400, .seq_ops = &deadline_dispatch1_seq_ops}, - {"dispatch2", 0400, .seq_ops = &deadline_dispatch2_seq_ops}, + {"dispatch", 0400, .seq_ops = &deadline_dispatch_seq_ops}, {"owned_by_driver", 0400, dd_owned_by_driver_show}, {"queued", 0400, dd_queued_show}, {}, From fbea4c63b5385588cb44ab21f91e55e33c719a54 Mon Sep 17 00:00:00 2001 From: Daniel Borkmann Date: Mon, 20 Oct 2025 09:54:41 +0200 Subject: [PATCH 0436/3902] bpf: Do not let BPF test infra emit invalid GSO types to stack [ Upstream commit 04a899573fb87273a656f178b5f920c505f68875 ] Yinhao et al. reported that their fuzzer tool was able to trigger a skb_warn_bad_offload() from netif_skb_features() -> gso_features_check(). When a BPF program - triggered via BPF test infra - pushes the packet to the loopback device via bpf_clone_redirect() then mentioned offload warning can be seen. GSO-related features are then rightfully disabled. We get into this situation due to convert___skb_to_skb() setting gso_segs and gso_size but not gso_type. Technically, it makes sense that this warning triggers since the GSO properties are malformed due to the gso_type. Potentially, the gso_type could be marked non-trustworthy through setting it at least to SKB_GSO_DODGY without any other specific assumptions, but that also feels wrong given we should not go further into the GSO engine in the first place. The checks were added in 121d57af308d ("gso: validate gso_type in GSO handlers") because there were malicious (syzbot) senders that combine a protocol with a non-matching gso_type. If we would want to drop such packets, gso_features_check() currently only returns feature flags via netif_skb_features(), so one location for potentially dropping such skbs could be validate_xmit_unreadable_skb(), but then otoh it would be an additional check in the fast-path for a very corner case. Given bpf_clone_redirect() is the only place where BPF test infra could emit such packets, lets reject them right there. Fixes: 850a88cc4096 ("bpf: Expose __sk_buff wire_len/gso_segs to BPF_PROG_TEST_RUN") Fixes: cf62089b0edd ("bpf: Add gso_size to __sk_buff") Reported-by: Yinhao Hu Reported-by: Kaiyan Mei Reported-by: Dongliang Mu Signed-off-by: Daniel Borkmann Signed-off-by: Martin KaFai Lau Link: https://patch.msgid.link/20251020075441.127980-1-daniel@iogearbox.net Signed-off-by: Sasha Levin --- net/bpf/test_run.c | 5 +++++ net/core/filter.c | 7 +++++++ 2 files changed, 12 insertions(+) diff --git a/net/bpf/test_run.c b/net/bpf/test_run.c index 8b7d0b90fea76b..55a79337ac51fd 100644 --- a/net/bpf/test_run.c +++ b/net/bpf/test_run.c @@ -939,6 +939,11 @@ static int convert___skb_to_skb(struct sk_buff *skb, struct __sk_buff *__skb) if (__skb->gso_segs > GSO_MAX_SEGS) return -EINVAL; + + /* Currently GSO type is zero/unset. If this gets extended with + * a small list of accepted GSO types in future, the filter for + * an unset GSO type in bpf_clone_redirect() can be lifted. + */ skb_shinfo(skb)->gso_segs = __skb->gso_segs; skb_shinfo(skb)->gso_size = __skb->gso_size; skb_shinfo(skb)->hwtstamps.hwtstamp = __skb->hwtstamp; diff --git a/net/core/filter.c b/net/core/filter.c index fa06c5a08e22f1..1efec0d70d7839 100644 --- a/net/core/filter.c +++ b/net/core/filter.c @@ -2458,6 +2458,13 @@ BPF_CALL_3(bpf_clone_redirect, struct sk_buff *, skb, u32, ifindex, u64, flags) if (unlikely(flags & (~(BPF_F_INGRESS) | BPF_F_REDIRECT_INTERNAL))) return -EINVAL; + /* BPF test infra's convert___skb_to_skb() can create type-less + * GSO packets. gso_features_check() will detect this as a bad + * offload. However, lets not leak them out in the first place. + */ + if (unlikely(skb_is_gso(skb) && !skb_shinfo(skb)->gso_type)) + return -EBADMSG; + dev = dev_get_by_index_rcu(dev_net(skb->dev), ifindex); if (unlikely(!dev)) return -EINVAL; From a45df9009fa81b7881865be0c16e87d428cbe630 Mon Sep 17 00:00:00 2001 From: Tim Harvey Date: Thu, 18 Sep 2025 08:44:45 -0700 Subject: [PATCH 0437/3902] arm64: dts: freescale: imx8mp-venice-gw7905-2x: remove duplicate usdhc1 props [ Upstream commit 8b7e58ab4a02601a0e86e9f9701d4612038d8b29 ] Remove the un-intended duplicate properties from usdhc1. Fixes: 0d5b288c2110e ("arm64: dts: freescale: Add imx8mp-venice-gw7905-2x") Signed-off-by: Tim Harvey Reviewed-by: Peng Fan Signed-off-by: Shawn Guo Signed-off-by: Sasha Levin --- arch/arm64/boot/dts/freescale/imx8mp-venice-gw702x.dtsi | 3 --- 1 file changed, 3 deletions(-) diff --git a/arch/arm64/boot/dts/freescale/imx8mp-venice-gw702x.dtsi b/arch/arm64/boot/dts/freescale/imx8mp-venice-gw702x.dtsi index cbf0c9a740faa6..303995a8adce8e 100644 --- a/arch/arm64/boot/dts/freescale/imx8mp-venice-gw702x.dtsi +++ b/arch/arm64/boot/dts/freescale/imx8mp-venice-gw702x.dtsi @@ -423,9 +423,6 @@ bus-width = <4>; non-removable; status = "okay"; - bus-width = <4>; - non-removable; - status = "okay"; }; /* eMMC */ From 152044f4b08d282f41bd5a16d68644c2fded5a57 Mon Sep 17 00:00:00 2001 From: Tim Harvey Date: Thu, 18 Sep 2025 08:44:49 -0700 Subject: [PATCH 0438/3902] arm64: dts: imx8mm-venice-gw72xx: remove unused sdhc1 pinctrl [ Upstream commit d949b8d12d6e8fa119bca10d3157cd42e810f6f7 ] The SDHC1 interface is not used on the imx8mm-venice-gw72xx. Remove the unused pinctrl_usdhc1 iomux node. Fixes: 6f30b27c5ef5 ("arm64: dts: imx8mm: Add Gateworks i.MX 8M Mini Development Kits") Signed-off-by: Tim Harvey Reviewed-by: Peng Fan Signed-off-by: Shawn Guo Signed-off-by: Sasha Levin --- .../boot/dts/freescale/imx8mm-venice-gw72xx.dtsi | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/arch/arm64/boot/dts/freescale/imx8mm-venice-gw72xx.dtsi b/arch/arm64/boot/dts/freescale/imx8mm-venice-gw72xx.dtsi index 752caa38eb03bf..266038fbbef976 100644 --- a/arch/arm64/boot/dts/freescale/imx8mm-venice-gw72xx.dtsi +++ b/arch/arm64/boot/dts/freescale/imx8mm-venice-gw72xx.dtsi @@ -351,17 +351,6 @@ >; }; - pinctrl_usdhc1: usdhc1grp { - fsl,pins = < - MX8MM_IOMUXC_SD1_CLK_USDHC1_CLK 0x190 - MX8MM_IOMUXC_SD1_CMD_USDHC1_CMD 0x1d0 - MX8MM_IOMUXC_SD1_DATA0_USDHC1_DATA0 0x1d0 - MX8MM_IOMUXC_SD1_DATA1_USDHC1_DATA1 0x1d0 - MX8MM_IOMUXC_SD1_DATA2_USDHC1_DATA2 0x1d0 - MX8MM_IOMUXC_SD1_DATA3_USDHC1_DATA3 0x1d0 - >; - }; - pinctrl_usdhc2: usdhc2grp { fsl,pins = < MX8MM_IOMUXC_SD2_CLK_USDHC2_CLK 0x190 From 7e13775ed0b78f49c879fd003a51a0299ee7330e Mon Sep 17 00:00:00 2001 From: Tim Harvey Date: Thu, 18 Sep 2025 08:44:50 -0700 Subject: [PATCH 0439/3902] arm64: dts: imx8mp-venice-gw702x: remove off-board uart [ Upstream commit effe98060f70eb96e142f656e750d6af275ceac3 ] UART1 and UART3 go to a connector for use on a baseboard and as such are defined in the baseboard device-trees. Remove them from the gw702x SOM device-tree. Fixes: 0d5b288c2110 ("arm64: dts: freescale: Add imx8mp-venice-gw7905-2x") Signed-off-by: Tim Harvey Reviewed-by: Peng Fan Signed-off-by: Shawn Guo Signed-off-by: Sasha Levin --- .../dts/freescale/imx8mp-venice-gw702x.dtsi | 28 ------------------- 1 file changed, 28 deletions(-) diff --git a/arch/arm64/boot/dts/freescale/imx8mp-venice-gw702x.dtsi b/arch/arm64/boot/dts/freescale/imx8mp-venice-gw702x.dtsi index 303995a8adce8e..086ee4510cd837 100644 --- a/arch/arm64/boot/dts/freescale/imx8mp-venice-gw702x.dtsi +++ b/arch/arm64/boot/dts/freescale/imx8mp-venice-gw702x.dtsi @@ -395,13 +395,6 @@ status = "okay"; }; -/* off-board header */ -&uart1 { - pinctrl-names = "default"; - pinctrl-0 = <&pinctrl_uart1>; - status = "okay"; -}; - /* console */ &uart2 { pinctrl-names = "default"; @@ -409,13 +402,6 @@ status = "okay"; }; -/* off-board header */ -&uart3 { - pinctrl-names = "default"; - pinctrl-0 = <&pinctrl_uart3>; - status = "okay"; -}; - /* off-board */ &usdhc1 { pinctrl-names = "default"; @@ -520,13 +506,6 @@ >; }; - pinctrl_uart1: uart1grp { - fsl,pins = < - MX8MP_IOMUXC_UART1_RXD__UART1_DCE_RX 0x140 - MX8MP_IOMUXC_UART1_TXD__UART1_DCE_TX 0x140 - >; - }; - pinctrl_uart2: uart2grp { fsl,pins = < MX8MP_IOMUXC_UART2_RXD__UART2_DCE_RX 0x140 @@ -534,13 +513,6 @@ >; }; - pinctrl_uart3: uart3grp { - fsl,pins = < - MX8MP_IOMUXC_UART3_RXD__UART3_DCE_RX 0x140 - MX8MP_IOMUXC_UART3_TXD__UART3_DCE_TX 0x140 - >; - }; - pinctrl_usdhc1: usdhc1grp { fsl,pins = < MX8MP_IOMUXC_SD1_CLK__USDHC1_CLK 0x190 From a482d003391615f8c2d6a0f740086e1558c88614 Mon Sep 17 00:00:00 2001 From: Tim Harvey Date: Thu, 18 Sep 2025 08:44:51 -0700 Subject: [PATCH 0440/3902] arm64: dts: imx8mp-venice-gw702x: remove off-board sdhc1 [ Upstream commit 9db04b310ef99b546e4240c55842e81b06b78579 ] SDHC1 on the GW702x SOM routes to a connector for use on a baseboard and as such are defined in the baseboard device-trees. Remove it from the gw702x SOM device-tree. Fixes: 0d5b288c2110 ("arm64: dts: freescale: Add imx8mp-venice-gw7905-2x") Signed-off-by: Tim Harvey Reviewed-by: Peng Fan Signed-off-by: Shawn Guo Signed-off-by: Sasha Levin --- .../dts/freescale/imx8mp-venice-gw702x.dtsi | 20 ------------------- .../dts/freescale/imx8mp-venice-gw72xx.dtsi | 11 ---------- 2 files changed, 31 deletions(-) diff --git a/arch/arm64/boot/dts/freescale/imx8mp-venice-gw702x.dtsi b/arch/arm64/boot/dts/freescale/imx8mp-venice-gw702x.dtsi index 086ee4510cd837..fb159199b39de3 100644 --- a/arch/arm64/boot/dts/freescale/imx8mp-venice-gw702x.dtsi +++ b/arch/arm64/boot/dts/freescale/imx8mp-venice-gw702x.dtsi @@ -402,15 +402,6 @@ status = "okay"; }; -/* off-board */ -&usdhc1 { - pinctrl-names = "default"; - pinctrl-0 = <&pinctrl_usdhc1>; - bus-width = <4>; - non-removable; - status = "okay"; -}; - /* eMMC */ &usdhc3 { pinctrl-names = "default", "state_100mhz", "state_200mhz"; @@ -513,17 +504,6 @@ >; }; - pinctrl_usdhc1: usdhc1grp { - fsl,pins = < - MX8MP_IOMUXC_SD1_CLK__USDHC1_CLK 0x190 - MX8MP_IOMUXC_SD1_CMD__USDHC1_CMD 0x1d0 - MX8MP_IOMUXC_SD1_DATA0__USDHC1_DATA0 0x1d0 - MX8MP_IOMUXC_SD1_DATA1__USDHC1_DATA1 0x1d0 - MX8MP_IOMUXC_SD1_DATA2__USDHC1_DATA2 0x1d0 - MX8MP_IOMUXC_SD1_DATA3__USDHC1_DATA3 0x1d0 - >; - }; - pinctrl_usdhc3: usdhc3grp { fsl,pins = < MX8MP_IOMUXC_NAND_WE_B__USDHC3_CLK 0x190 diff --git a/arch/arm64/boot/dts/freescale/imx8mp-venice-gw72xx.dtsi b/arch/arm64/boot/dts/freescale/imx8mp-venice-gw72xx.dtsi index cf747ec6fa16eb..76020ef89bf3e8 100644 --- a/arch/arm64/boot/dts/freescale/imx8mp-venice-gw72xx.dtsi +++ b/arch/arm64/boot/dts/freescale/imx8mp-venice-gw72xx.dtsi @@ -365,17 +365,6 @@ >; }; - pinctrl_usdhc1: usdhc1grp { - fsl,pins = < - MX8MP_IOMUXC_SD1_CLK__USDHC1_CLK 0x190 - MX8MP_IOMUXC_SD1_CMD__USDHC1_CMD 0x1d0 - MX8MP_IOMUXC_SD1_DATA0__USDHC1_DATA0 0x1d0 - MX8MP_IOMUXC_SD1_DATA1__USDHC1_DATA1 0x1d0 - MX8MP_IOMUXC_SD1_DATA2__USDHC1_DATA2 0x1d0 - MX8MP_IOMUXC_SD1_DATA3__USDHC1_DATA3 0x1d0 - >; - }; - pinctrl_usdhc2: usdhc2grp { fsl,pins = < MX8MP_IOMUXC_SD2_CLK__USDHC2_CLK 0x190 From 7ba92dd7206b147966f2d076eff8867b8b685f0d Mon Sep 17 00:00:00 2001 From: Joy Zou Date: Fri, 19 Sep 2025 10:27:04 +0800 Subject: [PATCH 0441/3902] arm64: dts: imx95-15x15-evk: add fan-supply property for pwm-fan [ Upstream commit 93b2fac5cdaf0d501d04c9a4b0e5024632a6af7c ] Add fan-supply regulator to pwm-fan node to specify power source. Fixes: e3e8b199aff8 ("arm64: dts: imx95: Add imx95-15x15-evk support") Signed-off-by: Joy Zou Reviewed-by: Frank Li Signed-off-by: Shawn Guo Signed-off-by: Sasha Levin --- arch/arm64/boot/dts/freescale/imx95-15x15-evk.dts | 1 + 1 file changed, 1 insertion(+) diff --git a/arch/arm64/boot/dts/freescale/imx95-15x15-evk.dts b/arch/arm64/boot/dts/freescale/imx95-15x15-evk.dts index 148243470dd4ab..0953c25ef55768 100644 --- a/arch/arm64/boot/dts/freescale/imx95-15x15-evk.dts +++ b/arch/arm64/boot/dts/freescale/imx95-15x15-evk.dts @@ -61,6 +61,7 @@ fan0: pwm-fan { compatible = "pwm-fan"; + fan-supply = <®_vcc_12v>; #cooling-cells = <2>; cooling-levels = <64 128 192 255>; pwms = <&tpm6 0 4000000 PWM_POLARITY_INVERTED>; From 9d231ef85d9a9f7aa86b3f4830b274b7e2fe4283 Mon Sep 17 00:00:00 2001 From: Tianyou Li Date: Mon, 20 Oct 2025 15:30:05 +0800 Subject: [PATCH 0442/3902] perf annotate: Check return value of evsel__get_arch() properly [ Upstream commit f1204e5846d22fb2fffbd1164eeb19535f306797 ] Check the error code of evsel__get_arch() in the symbol__annotate(). Previously it checked non-zero value but after the refactoring it does only for negative values. Fixes: 0669729eb0afb0cf ("perf annotate: Factor out evsel__get_arch()") Suggested-by: James Clark Acked-by: Namhyung Kim Signed-off-by: Tianyou Li Signed-off-by: Namhyung Kim Signed-off-by: Sasha Levin --- tools/perf/util/annotate.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/perf/util/annotate.c b/tools/perf/util/annotate.c index a2e34f149a074a..1d6900033b3a0c 100644 --- a/tools/perf/util/annotate.c +++ b/tools/perf/util/annotate.c @@ -1021,7 +1021,7 @@ int symbol__annotate(struct map_symbol *ms, struct evsel *evsel, int err, nr; err = evsel__get_arch(evsel, &arch); - if (err < 0) + if (err) return err; if (parch) From 1321c33a8fc5eaa3fb6a54ad31e06614bedcc1e3 Mon Sep 17 00:00:00 2001 From: Peter Griffin Date: Mon, 13 Oct 2025 21:51:33 +0100 Subject: [PATCH 0443/3902] arm64: dts: exynos: gs101: fix clock module unit reg sizes MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [ Upstream commit ddb2a16804d005a96e8b5ffc0925e2f5bff65767 ] The memory map lists each clock module unit as having a size of 0x10000. Additionally there are some undocumented registers in this region that need to be used for automatic clock gating mode. Some of those registers also need to be saved/restored on suspend & resume. Fixes: 86124c76683e ("arm64: dts: exynos: gs101: enable cmu-hsi2 clock controller") Fixes: 4982a4a2092e ("arm64: dts: exynos: gs101: enable cmu-hsi0 clock controller") Fixes: 7d66d98b5bf3 ("arm64: dts: exynos: gs101: enable cmu-peric1 clock controller") Fixes: e62c706f3aa0 ("arm64: dts: exynos: gs101: enable cmu-peric0 clock controller") Fixes: ea89fdf24fd9 ("arm64: dts: exynos: google: Add initial Google gs101 SoC support") Signed-off-by: Peter Griffin Reviewed-by: André Draszik Link: https://patch.msgid.link/20251013-automatic-clocks-v1-4-72851ee00300@linaro.org Signed-off-by: Krzysztof Kozlowski Signed-off-by: Sasha Levin --- arch/arm64/boot/dts/exynos/google/gs101.dtsi | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/arch/arm64/boot/dts/exynos/google/gs101.dtsi b/arch/arm64/boot/dts/exynos/google/gs101.dtsi index 31c99526470d0b..6335e0a8136bee 100644 --- a/arch/arm64/boot/dts/exynos/google/gs101.dtsi +++ b/arch/arm64/boot/dts/exynos/google/gs101.dtsi @@ -288,7 +288,7 @@ cmu_misc: clock-controller@10010000 { compatible = "google,gs101-cmu-misc"; - reg = <0x10010000 0x8000>; + reg = <0x10010000 0x10000>; #clock-cells = <1>; clocks = <&cmu_top CLK_DOUT_CMU_MISC_BUS>, <&cmu_top CLK_DOUT_CMU_MISC_SSS>; @@ -365,7 +365,7 @@ cmu_peric0: clock-controller@10800000 { compatible = "google,gs101-cmu-peric0"; - reg = <0x10800000 0x4000>; + reg = <0x10800000 0x10000>; #clock-cells = <1>; clocks = <&ext_24_5m>, <&cmu_top CLK_DOUT_CMU_PERIC0_BUS>, @@ -911,7 +911,7 @@ cmu_peric1: clock-controller@10c00000 { compatible = "google,gs101-cmu-peric1"; - reg = <0x10c00000 0x4000>; + reg = <0x10c00000 0x10000>; #clock-cells = <1>; clocks = <&ext_24_5m>, <&cmu_top CLK_DOUT_CMU_PERIC1_BUS>, @@ -1265,7 +1265,7 @@ cmu_hsi0: clock-controller@11000000 { compatible = "google,gs101-cmu-hsi0"; - reg = <0x11000000 0x4000>; + reg = <0x11000000 0x10000>; #clock-cells = <1>; clocks = <&ext_24_5m>, @@ -1332,7 +1332,7 @@ cmu_hsi2: clock-controller@14400000 { compatible = "google,gs101-cmu-hsi2"; - reg = <0x14400000 0x4000>; + reg = <0x14400000 0x10000>; #clock-cells = <1>; clocks = <&ext_24_5m>, <&cmu_top CLK_DOUT_CMU_HSI2_BUS>, @@ -1395,7 +1395,7 @@ cmu_apm: clock-controller@17400000 { compatible = "google,gs101-cmu-apm"; - reg = <0x17400000 0x8000>; + reg = <0x17400000 0x10000>; #clock-cells = <1>; clocks = <&ext_24_5m>; @@ -1497,7 +1497,7 @@ cmu_top: clock-controller@1e080000 { compatible = "google,gs101-cmu-top"; - reg = <0x1e080000 0x8000>; + reg = <0x1e080000 0x10000>; #clock-cells = <1>; clocks = <&ext_24_5m>; From 4b9404cc439d41d394acedf4f6def3b786c6ef86 Mon Sep 17 00:00:00 2001 From: Peter Griffin Date: Mon, 13 Oct 2025 21:51:34 +0100 Subject: [PATCH 0444/3902] arm64: dts: exynos: gs101: fix sysreg_apm reg property MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [ Upstream commit 4348c22a4f15dbef1314f1a353d7f053b24e9ace ] Both the start address and size are incorrect for the apm_sysreg DT node. Update to match the TRM (rather than how it was defined downstream). Fixes: ea89fdf24fd9 ("arm64: dts: exynos: google: Add initial Google gs101 SoC support") Signed-off-by: Peter Griffin Reviewed-by: André Draszik Link: https://patch.msgid.link/20251013-automatic-clocks-v1-5-72851ee00300@linaro.org Signed-off-by: Krzysztof Kozlowski Signed-off-by: Sasha Levin --- arch/arm64/boot/dts/exynos/google/gs101.dtsi | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/arch/arm64/boot/dts/exynos/google/gs101.dtsi b/arch/arm64/boot/dts/exynos/google/gs101.dtsi index 6335e0a8136bee..c01ff4e1b0d5d3 100644 --- a/arch/arm64/boot/dts/exynos/google/gs101.dtsi +++ b/arch/arm64/boot/dts/exynos/google/gs101.dtsi @@ -1402,9 +1402,9 @@ clock-names = "oscclk"; }; - sysreg_apm: syscon@174204e0 { + sysreg_apm: syscon@17420000 { compatible = "google,gs101-apm-sysreg", "syscon"; - reg = <0x174204e0 0x1000>; + reg = <0x17420000 0x10000>; }; pmu_system_controller: system-controller@17460000 { From 712102126091c8aa8bc79afbadc2024e54dde67d Mon Sep 17 00:00:00 2001 From: Geert Uytterhoeven Date: Fri, 3 Oct 2025 10:35:34 +0200 Subject: [PATCH 0445/3902] PCI: rcar-gen2: Drop ARM dependency from PCI_RCAR_GEN2 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [ Upstream commit d312742f686582e6457070bcfd24bee8acfdf213 ] Since the reliance on ARM-specific struct pci_sys_data was removed, this driver can be compile-tested on other architectures. While at it, make the help text a bit more generic, as some members of the R-Car Gen2 family have a different number of internal PCI controllers. Fixes: 4a957563fe0231e0 ("PCI: rcar-gen2: Convert to use modern host bridge probe functions") Suggested-by: Ilpo Jarvinen Signed-off-by: Geert Uytterhoeven Signed-off-by: Manivannan Sadhasivam [bhelgaas: add rcar-gen2 to subject] Signed-off-by: Bjorn Helgaas Reviewed-by: Ilpo Järvinen Link: https://patch.msgid.link/00f75d6732eacce93f04ffaeedc415d2db714cd6.1759480426.git.geert+renesas@glider.be Signed-off-by: Sasha Levin --- drivers/pci/controller/Kconfig | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/drivers/pci/controller/Kconfig b/drivers/pci/controller/Kconfig index 41748d083b933d..0452151a7bccc8 100644 --- a/drivers/pci/controller/Kconfig +++ b/drivers/pci/controller/Kconfig @@ -259,12 +259,11 @@ config PCIE_RCAR_EP config PCI_RCAR_GEN2 bool "Renesas R-Car Gen2 Internal PCI controller" - depends on ARCH_RENESAS || COMPILE_TEST - depends on ARM + depends on (ARCH_RENESAS && ARM) || COMPILE_TEST help Say Y here if you want internal PCI support on R-Car Gen2 SoC. - There are 3 internal PCI controllers available with a single - built-in EHCI/OHCI host controller present on each one. + Each internal PCI controller contains a single built-in EHCI/OHCI + host controller. config PCIE_ROCKCHIP bool From 78bf5afa25d6a8838e61f99b45c150db10ff91a6 Mon Sep 17 00:00:00 2001 From: Li Qiang Date: Wed, 15 Oct 2025 14:40:20 +0800 Subject: [PATCH 0446/3902] uio: uio_fsl_elbc_gpcm:: Add null pointer check to uio_fsl_elbc_gpcm_probe [ Upstream commit d48fb15e6ad142e0577428a8c5028136e10c7b3d ] devm_kasprintf() returns a pointer to dynamically allocated memory which can be NULL upon failure. Fixes: d57801c45f53e ("uio: uio_fsl_elbc_gpcm: use device-managed allocators") Signed-off-by: Li Qiang Link: https://patch.msgid.link/20251015064020.56589-1-liqiang01@kylinos.cn Signed-off-by: Greg Kroah-Hartman Signed-off-by: Sasha Levin --- drivers/uio/uio_fsl_elbc_gpcm.c | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/drivers/uio/uio_fsl_elbc_gpcm.c b/drivers/uio/uio_fsl_elbc_gpcm.c index 81454c3e2484c8..338dd2aaabc871 100644 --- a/drivers/uio/uio_fsl_elbc_gpcm.c +++ b/drivers/uio/uio_fsl_elbc_gpcm.c @@ -384,6 +384,11 @@ static int uio_fsl_elbc_gpcm_probe(struct platform_device *pdev) /* set all UIO data */ info->mem[0].name = devm_kasprintf(&pdev->dev, GFP_KERNEL, "%pOFn", node); + if (!info->mem[0].name) { + ret = -ENODEV; + goto out_err3; + } + info->mem[0].addr = res.start; info->mem[0].size = resource_size(&res); info->mem[0].memtype = UIO_MEM_PHYS; @@ -423,6 +428,8 @@ static int uio_fsl_elbc_gpcm_probe(struct platform_device *pdev) out_err2: if (priv->shutdown) priv->shutdown(info, true); + +out_err3: iounmap(info->mem[0].internal_addr); return ret; } From 9ff72116e6c86545b60da5c1eb5344858c3daadd Mon Sep 17 00:00:00 2001 From: Sherry Sun Date: Thu, 2 Oct 2025 12:52:58 +0800 Subject: [PATCH 0447/3902] tty: serial: imx: Only configure the wake register when device is set as wakeup source [ Upstream commit d55f3d2375ceeb08330d30f1e08196993c0b6583 ] Currently, the i.MX UART driver enables wake-related registers for all UART devices by default. However, this is unnecessary for devices that are not configured as wakeup sources. To address this, add a device_may_wakeup() check before configuring the UART wake-related registers. Fixes: db1a9b55004c ("tty: serial: imx: Allow UART to be a source for wakeup") Signed-off-by: Sherry Sun Reviewed-by: Frank Li Link: https://patch.msgid.link/20251002045259.2725461-2-sherry.sun@nxp.com Signed-off-by: Greg Kroah-Hartman Signed-off-by: Sasha Levin --- drivers/tty/serial/imx.c | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/drivers/tty/serial/imx.c b/drivers/tty/serial/imx.c index 500dfc009d03e8..90e2ea1e8afe5c 100644 --- a/drivers/tty/serial/imx.c +++ b/drivers/tty/serial/imx.c @@ -2697,8 +2697,22 @@ static void imx_uart_save_context(struct imx_port *sport) /* called with irq off */ static void imx_uart_enable_wakeup(struct imx_port *sport, bool on) { + struct tty_port *port = &sport->port.state->port; + struct device *tty_dev; + bool may_wake = false; u32 ucr3; + scoped_guard(tty_port_tty, port) { + struct tty_struct *tty = scoped_tty(); + + tty_dev = tty->dev; + may_wake = tty_dev && device_may_wakeup(tty_dev); + } + + /* only configure the wake register when device set as wakeup source */ + if (!may_wake) + return; + uart_port_lock_irq(&sport->port); ucr3 = imx_uart_readl(sport, UCR3); From 69671a253ffe065eae90c637f5b8145626357b68 Mon Sep 17 00:00:00 2001 From: Vladimir Zapolskiy Date: Wed, 22 Oct 2025 02:44:45 +0300 Subject: [PATCH 0448/3902] clk: qcom: camcc-sm8550: Specify Titan GDSC power domain as a parent to other [ Upstream commit d8f1121ebf4036884fc9ab1968f606523dd1c1fe ] When a consumer turns on/off a power domain dependent on another power domain in hardware, the parent power domain shall be turned on/off by the power domain provider as well, and to get it the power domain hardware hierarchy shall be described in the CAMCC driver. Establish the power domain hierarchy with a Titan GDSC set as a parent of all other GDSC power domains provided by the SM8550 camera clock controller to enforce a correct sequence of enabling and disabling power domains by the consumers, this fixes the CAMCC as a supplier of power domains to CAMSS IP and its driver. Fixes: ccc4e6a061a2 ("clk: qcom: camcc-sm8550: Add camera clock controller driver for SM8550") Reviewed-by: Konrad Dybcio Reviewed-by: Imran Shaik Reviewed-by: Bryan O'Donoghue Signed-off-by: Vladimir Zapolskiy Link: https://lore.kernel.org/r/20251021234450.2271279-2-vladimir.zapolskiy@linaro.org Signed-off-by: Bjorn Andersson Signed-off-by: Sasha Levin --- drivers/clk/qcom/camcc-sm8550.c | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/drivers/clk/qcom/camcc-sm8550.c b/drivers/clk/qcom/camcc-sm8550.c index 63aed9e4c362d5..b8ece8a57a8a9d 100644 --- a/drivers/clk/qcom/camcc-sm8550.c +++ b/drivers/clk/qcom/camcc-sm8550.c @@ -3204,6 +3204,8 @@ static struct clk_branch cam_cc_sfe_1_fast_ahb_clk = { }, }; +static struct gdsc cam_cc_titan_top_gdsc; + static struct gdsc cam_cc_bps_gdsc = { .gdscr = 0x10004, .en_rest_wait_val = 0x2, @@ -3213,6 +3215,7 @@ static struct gdsc cam_cc_bps_gdsc = { .name = "cam_cc_bps_gdsc", }, .pwrsts = PWRSTS_OFF_ON, + .parent = &cam_cc_titan_top_gdsc.pd, .flags = POLL_CFG_GDSCR | RETAIN_FF_ENABLE, }; @@ -3225,6 +3228,7 @@ static struct gdsc cam_cc_ife_0_gdsc = { .name = "cam_cc_ife_0_gdsc", }, .pwrsts = PWRSTS_OFF_ON, + .parent = &cam_cc_titan_top_gdsc.pd, .flags = POLL_CFG_GDSCR | RETAIN_FF_ENABLE, }; @@ -3237,6 +3241,7 @@ static struct gdsc cam_cc_ife_1_gdsc = { .name = "cam_cc_ife_1_gdsc", }, .pwrsts = PWRSTS_OFF_ON, + .parent = &cam_cc_titan_top_gdsc.pd, .flags = POLL_CFG_GDSCR | RETAIN_FF_ENABLE, }; @@ -3249,6 +3254,7 @@ static struct gdsc cam_cc_ife_2_gdsc = { .name = "cam_cc_ife_2_gdsc", }, .pwrsts = PWRSTS_OFF_ON, + .parent = &cam_cc_titan_top_gdsc.pd, .flags = POLL_CFG_GDSCR | RETAIN_FF_ENABLE, }; @@ -3261,6 +3267,7 @@ static struct gdsc cam_cc_ipe_0_gdsc = { .name = "cam_cc_ipe_0_gdsc", }, .pwrsts = PWRSTS_OFF_ON, + .parent = &cam_cc_titan_top_gdsc.pd, .flags = POLL_CFG_GDSCR | RETAIN_FF_ENABLE, }; @@ -3273,6 +3280,7 @@ static struct gdsc cam_cc_sbi_gdsc = { .name = "cam_cc_sbi_gdsc", }, .pwrsts = PWRSTS_OFF_ON, + .parent = &cam_cc_titan_top_gdsc.pd, .flags = POLL_CFG_GDSCR | RETAIN_FF_ENABLE, }; @@ -3285,6 +3293,7 @@ static struct gdsc cam_cc_sfe_0_gdsc = { .name = "cam_cc_sfe_0_gdsc", }, .pwrsts = PWRSTS_OFF_ON, + .parent = &cam_cc_titan_top_gdsc.pd, .flags = POLL_CFG_GDSCR | RETAIN_FF_ENABLE, }; @@ -3297,6 +3306,7 @@ static struct gdsc cam_cc_sfe_1_gdsc = { .name = "cam_cc_sfe_1_gdsc", }, .pwrsts = PWRSTS_OFF_ON, + .parent = &cam_cc_titan_top_gdsc.pd, .flags = POLL_CFG_GDSCR | RETAIN_FF_ENABLE, }; From 27fa51ddddb363a3b87f91c5e192cd5f0550de3f Mon Sep 17 00:00:00 2001 From: Vladimir Zapolskiy Date: Wed, 22 Oct 2025 02:44:46 +0300 Subject: [PATCH 0449/3902] clk: qcom: camcc-sm6350: Specify Titan GDSC power domain as a parent to other [ Upstream commit a76ce61d7225934b0a52c8172a8cd944002a8c6f ] When a consumer turns on/off a power domain dependent on another power domain in hardware, the parent power domain shall be turned on/off by the power domain provider as well, and to get it the power domain hardware hierarchy shall be described in the CAMCC driver. Establish the power domain hierarchy with a Titan GDSC set as a parent of all other GDSC power domains provided by the SM6350 camera clock controller to enforce a correct sequence of enabling and disabling power domains by the consumers, this fixes the CAMCC as a supplier of power domains to CAMSS IP and its driver. Fixes: 80f5451d9a7c ("clk: qcom: Add camera clock controller driver for SM6350") Reviewed-by: Konrad Dybcio Reviewed-by: Imran Shaik Reviewed-by: Bryan O'Donoghue Signed-off-by: Vladimir Zapolskiy Link: https://lore.kernel.org/r/20251021234450.2271279-3-vladimir.zapolskiy@linaro.org Signed-off-by: Bjorn Andersson Signed-off-by: Sasha Levin --- drivers/clk/qcom/camcc-sm6350.c | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/drivers/clk/qcom/camcc-sm6350.c b/drivers/clk/qcom/camcc-sm6350.c index 8aac97d29ce3ff..6c272f7b07219d 100644 --- a/drivers/clk/qcom/camcc-sm6350.c +++ b/drivers/clk/qcom/camcc-sm6350.c @@ -1693,6 +1693,8 @@ static struct clk_branch camcc_sys_tmr_clk = { }, }; +static struct gdsc titan_top_gdsc; + static struct gdsc bps_gdsc = { .gdscr = 0x6004, .en_rest_wait_val = 0x2, @@ -1702,6 +1704,7 @@ static struct gdsc bps_gdsc = { .name = "bps_gdsc", }, .pwrsts = PWRSTS_OFF_ON, + .parent = &titan_top_gdsc.pd, .flags = VOTABLE, }; @@ -1714,6 +1717,7 @@ static struct gdsc ipe_0_gdsc = { .name = "ipe_0_gdsc", }, .pwrsts = PWRSTS_OFF_ON, + .parent = &titan_top_gdsc.pd, .flags = VOTABLE, }; @@ -1726,6 +1730,7 @@ static struct gdsc ife_0_gdsc = { .name = "ife_0_gdsc", }, .pwrsts = PWRSTS_OFF_ON, + .parent = &titan_top_gdsc.pd, }; static struct gdsc ife_1_gdsc = { @@ -1737,6 +1742,7 @@ static struct gdsc ife_1_gdsc = { .name = "ife_1_gdsc", }, .pwrsts = PWRSTS_OFF_ON, + .parent = &titan_top_gdsc.pd, }; static struct gdsc ife_2_gdsc = { @@ -1748,6 +1754,7 @@ static struct gdsc ife_2_gdsc = { .name = "ife_2_gdsc", }, .pwrsts = PWRSTS_OFF_ON, + .parent = &titan_top_gdsc.pd, }; static struct gdsc titan_top_gdsc = { From 6d9ed274d8da1a4532da4eb43b7340f4265ed54a Mon Sep 17 00:00:00 2001 From: Taniya Das Date: Wed, 24 Sep 2025 00:08:30 +0530 Subject: [PATCH 0450/3902] clk: qcom: gcc-sm8750: Add a new frequency for sdcc2 clock [ Upstream commit 393f7834cd2b1eaf4a9eff2701e706e73e660dd7 ] The SD card support requires a 37.5MHz clock; add it to the frequency list for the storage SW driver to be able to request for the frequency. Fixes: 3267c774f3ff ("clk: qcom: Add support for GCC on SM8750") Signed-off-by: Taniya Das Reviewed-by: Imran Shaik Link: https://lore.kernel.org/r/20250924-sm8750_gcc_sdcc2_frequency-v1-1-541fd321125f@oss.qualcomm.com Signed-off-by: Bjorn Andersson Signed-off-by: Sasha Levin --- drivers/clk/qcom/gcc-sm8750.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/clk/qcom/gcc-sm8750.c b/drivers/clk/qcom/gcc-sm8750.c index 8092dd6b37b56f..def86b71a3da53 100644 --- a/drivers/clk/qcom/gcc-sm8750.c +++ b/drivers/clk/qcom/gcc-sm8750.c @@ -1012,6 +1012,7 @@ static struct clk_rcg2 gcc_qupv3_wrap2_s7_clk_src = { static const struct freq_tbl ftbl_gcc_sdcc2_apps_clk_src[] = { F(400000, P_BI_TCXO, 12, 1, 4), F(25000000, P_GCC_GPLL0_OUT_EVEN, 12, 0, 0), + F(37500000, P_GCC_GPLL0_OUT_EVEN, 8, 0, 0), F(50000000, P_GCC_GPLL0_OUT_EVEN, 6, 0, 0), F(100000000, P_GCC_GPLL0_OUT_EVEN, 3, 0, 0), F(202000000, P_GCC_GPLL9_OUT_MAIN, 4, 0, 0), From 25dddde6564299f433d1a1acc680cb77fc452de4 Mon Sep 17 00:00:00 2001 From: Taniya Das Date: Thu, 25 Sep 2025 15:49:00 +0530 Subject: [PATCH 0451/3902] clk: qcom: gcc-glymur: Update the halt check flags for pipe clocks [ Upstream commit 18da820eb632fbd99167f3fc6650a30db714ddfd ] The pipe clocks for PCIE and USB are externally sourced and they should not be polled by the clock driver. Update the halt_check flags to 'SKIP' to disable polling for these clocks. This helps avoid the clock status stuck at 'off' warnings, which are benign, since all consumers of the PHYs must initialize a given instance before performing any operations. Fixes: efe504300a17 ("clk: qcom: gcc: Add support for Global Clock Controller") Reviewed-by: Konrad Dybcio Signed-off-by: Taniya Das Reviewed-by: Imran Shaik Reviewed-by: Dmitry Baryshkov Link: https://lore.kernel.org/r/20250925-glymur_gcc_usb_fixes-v2-1-ee4619571efe@oss.qualcomm.com Signed-off-by: Bjorn Andersson Signed-off-by: Sasha Levin --- drivers/clk/qcom/gcc-glymur.c | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/drivers/clk/qcom/gcc-glymur.c b/drivers/clk/qcom/gcc-glymur.c index 62059120f9720b..d938e7dc5b66ec 100644 --- a/drivers/clk/qcom/gcc-glymur.c +++ b/drivers/clk/qcom/gcc-glymur.c @@ -6760,7 +6760,7 @@ static struct clk_branch gcc_usb3_prim_phy_com_aux_clk = { static struct clk_branch gcc_usb3_prim_phy_pipe_clk = { .halt_reg = 0x3f088, - .halt_check = BRANCH_HALT_DELAY, + .halt_check = BRANCH_HALT_SKIP, .hwcg_reg = 0x3f088, .hwcg_bit = 1, .clkr = { @@ -6816,7 +6816,7 @@ static struct clk_branch gcc_usb3_sec_phy_com_aux_clk = { static struct clk_branch gcc_usb3_sec_phy_pipe_clk = { .halt_reg = 0xe2078, - .halt_check = BRANCH_HALT_VOTED, + .halt_check = BRANCH_HALT_SKIP, .hwcg_reg = 0xe2078, .hwcg_bit = 1, .clkr = { @@ -6872,7 +6872,7 @@ static struct clk_branch gcc_usb3_tert_phy_com_aux_clk = { static struct clk_branch gcc_usb3_tert_phy_pipe_clk = { .halt_reg = 0xe1078, - .halt_check = BRANCH_HALT_VOTED, + .halt_check = BRANCH_HALT_SKIP, .hwcg_reg = 0xe1078, .hwcg_bit = 1, .clkr = { @@ -6961,7 +6961,7 @@ static struct clk_branch gcc_usb4_0_master_clk = { static struct clk_branch gcc_usb4_0_phy_p2rr2p_pipe_clk = { .halt_reg = 0x2b0f4, - .halt_check = BRANCH_HALT, + .halt_check = BRANCH_HALT_SKIP, .clkr = { .enable_reg = 0x2b0f4, .enable_mask = BIT(0), @@ -6979,7 +6979,7 @@ static struct clk_branch gcc_usb4_0_phy_p2rr2p_pipe_clk = { static struct clk_branch gcc_usb4_0_phy_pcie_pipe_clk = { .halt_reg = 0x2b04c, - .halt_check = BRANCH_HALT_VOTED, + .halt_check = BRANCH_HALT_SKIP, .clkr = { .enable_reg = 0x62010, .enable_mask = BIT(11), @@ -7033,7 +7033,7 @@ static struct clk_branch gcc_usb4_0_phy_rx1_clk = { static struct clk_branch gcc_usb4_0_phy_usb_pipe_clk = { .halt_reg = 0x2b0bc, - .halt_check = BRANCH_HALT_VOTED, + .halt_check = BRANCH_HALT_SKIP, .hwcg_reg = 0x2b0bc, .hwcg_bit = 1, .clkr = { @@ -7196,7 +7196,7 @@ static struct clk_branch gcc_usb4_1_master_clk = { static struct clk_branch gcc_usb4_1_phy_p2rr2p_pipe_clk = { .halt_reg = 0x2d118, - .halt_check = BRANCH_HALT, + .halt_check = BRANCH_HALT_SKIP, .clkr = { .enable_reg = 0x2d118, .enable_mask = BIT(0), @@ -7214,7 +7214,7 @@ static struct clk_branch gcc_usb4_1_phy_p2rr2p_pipe_clk = { static struct clk_branch gcc_usb4_1_phy_pcie_pipe_clk = { .halt_reg = 0x2d04c, - .halt_check = BRANCH_HALT_VOTED, + .halt_check = BRANCH_HALT_SKIP, .clkr = { .enable_reg = 0x62010, .enable_mask = BIT(12), @@ -7268,7 +7268,7 @@ static struct clk_branch gcc_usb4_1_phy_rx1_clk = { static struct clk_branch gcc_usb4_1_phy_usb_pipe_clk = { .halt_reg = 0x2d0e0, - .halt_check = BRANCH_HALT_VOTED, + .halt_check = BRANCH_HALT_SKIP, .hwcg_reg = 0x2d0e0, .hwcg_bit = 1, .clkr = { @@ -7431,7 +7431,7 @@ static struct clk_branch gcc_usb4_2_master_clk = { static struct clk_branch gcc_usb4_2_phy_p2rr2p_pipe_clk = { .halt_reg = 0xe00f8, - .halt_check = BRANCH_HALT, + .halt_check = BRANCH_HALT_SKIP, .clkr = { .enable_reg = 0xe00f8, .enable_mask = BIT(0), @@ -7449,7 +7449,7 @@ static struct clk_branch gcc_usb4_2_phy_p2rr2p_pipe_clk = { static struct clk_branch gcc_usb4_2_phy_pcie_pipe_clk = { .halt_reg = 0xe004c, - .halt_check = BRANCH_HALT_VOTED, + .halt_check = BRANCH_HALT_SKIP, .clkr = { .enable_reg = 0x62010, .enable_mask = BIT(13), @@ -7503,7 +7503,7 @@ static struct clk_branch gcc_usb4_2_phy_rx1_clk = { static struct clk_branch gcc_usb4_2_phy_usb_pipe_clk = { .halt_reg = 0xe00c0, - .halt_check = BRANCH_HALT_VOTED, + .halt_check = BRANCH_HALT_SKIP, .hwcg_reg = 0xe00c0, .hwcg_bit = 1, .clkr = { From b2427d5693c37a19c80a66db0dda9cba7dcb23a4 Mon Sep 17 00:00:00 2001 From: Luo Jie Date: Tue, 14 Oct 2025 22:35:26 +0800 Subject: [PATCH 0452/3902] clk: qcom: gcc-ipq5424: Correct the icc_first_node_id [ Upstream commit 464ce94531f5a62ce29081a9d3c70eb4d525f443 ] Update to use the expected icc_first_node_id for registering the icc clocks, ensuring correct association of clocks with interconnect nodes. Fixes: 170f3d2c065e ("clk: qcom: ipq5424: Use icc-clk for enabling NoC related clocks") Reviewed-by: Konrad Dybcio Signed-off-by: Luo Jie Link: https://lore.kernel.org/r/20251014-qcom_ipq5424_nsscc-v7-1-081f4956be02@quicinc.com Signed-off-by: Bjorn Andersson Signed-off-by: Sasha Levin --- drivers/clk/qcom/gcc-ipq5424.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/drivers/clk/qcom/gcc-ipq5424.c b/drivers/clk/qcom/gcc-ipq5424.c index 3d42f3d85c7a92..71afa1b86b7233 100644 --- a/drivers/clk/qcom/gcc-ipq5424.c +++ b/drivers/clk/qcom/gcc-ipq5424.c @@ -1,7 +1,7 @@ // SPDX-License-Identifier: GPL-2.0 /* * Copyright (c) 2018,2020 The Linux Foundation. All rights reserved. - * Copyright (c) 2024 Qualcomm Innovation Center, Inc. All rights reserved. + * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries. */ #include @@ -3284,6 +3284,7 @@ static const struct qcom_cc_desc gcc_ipq5424_desc = { .num_clk_hws = ARRAY_SIZE(gcc_ipq5424_hws), .icc_hws = icc_ipq5424_hws, .num_icc_hws = ARRAY_SIZE(icc_ipq5424_hws), + .icc_first_node_id = IPQ_APPS_ID, }; static int gcc_ipq5424_probe(struct platform_device *pdev) From 680df1f60e027ca06f380b297e25666988f70ae7 Mon Sep 17 00:00:00 2001 From: Luca Weiss Date: Tue, 21 Oct 2025 20:08:54 +0200 Subject: [PATCH 0453/3902] clk: qcom: camcc-sm6350: Fix PLL config of PLL2 [ Upstream commit ab0e13141d679fdffdd3463a272c5c1b10be1794 ] The 'Agera' PLLs (with clk_agera_pll_configure) do not take some of the parameters that are provided in the vendor driver. Instead the upstream configuration should provide the final user_ctl value that is written to the USER_CTL register. Fix the config so that the PLL is configured correctly, and fixes CAMCC_MCLK* being stuck off. Fixes: 80f5451d9a7c ("clk: qcom: Add camera clock controller driver for SM6350") Suggested-by: Taniya Das Signed-off-by: Luca Weiss Reviewed-by: Abel Vesa Reviewed-by: Taniya Das Link: https://lore.kernel.org/r/20251021-agera-pll-fixups-v1-1-8c1d8aff4afc@fairphone.com Signed-off-by: Bjorn Andersson Signed-off-by: Sasha Levin --- drivers/clk/qcom/camcc-sm6350.c | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/drivers/clk/qcom/camcc-sm6350.c b/drivers/clk/qcom/camcc-sm6350.c index 6c272f7b07219d..7df12c1311c682 100644 --- a/drivers/clk/qcom/camcc-sm6350.c +++ b/drivers/clk/qcom/camcc-sm6350.c @@ -145,15 +145,11 @@ static struct clk_alpha_pll_postdiv camcc_pll1_out_even = { static const struct alpha_pll_config camcc_pll2_config = { .l = 0x64, .alpha = 0x0, - .post_div_val = 0x3 << 8, - .post_div_mask = 0x3 << 8, - .aux_output_mask = BIT(1), - .main_output_mask = BIT(0), - .early_output_mask = BIT(3), .config_ctl_val = 0x20000800, .config_ctl_hi_val = 0x400003d2, .test_ctl_val = 0x04000400, .test_ctl_hi_val = 0x00004000, + .user_ctl_val = 0x0000030b, }; static struct clk_alpha_pll camcc_pll2 = { From 37b8870d96d4daa639f19b7aba5d85e96f3ff16b Mon Sep 17 00:00:00 2001 From: Luca Weiss Date: Tue, 21 Oct 2025 20:08:55 +0200 Subject: [PATCH 0454/3902] clk: qcom: camcc-sm7150: Fix PLL config of PLL2 [ Upstream commit 415aad75c7e5cdb72e0672dc1159be1a99535ecd ] The 'Agera' PLLs (with clk_agera_pll_configure) do not take some of the parameters that are provided in the vendor driver. Instead the upstream configuration should provide the final user_ctl value that is written to the USER_CTL register. Fix the config so that the PLL is configured correctly. Fixes: 9f0532da4226 ("clk: qcom: Add Camera Clock Controller driver for SM7150") Suggested-by: Taniya Das Signed-off-by: Luca Weiss Reviewed-by: Abel Vesa Reviewed-by: Taniya Das Link: https://lore.kernel.org/r/20251021-agera-pll-fixups-v1-2-8c1d8aff4afc@fairphone.com Signed-off-by: Bjorn Andersson Signed-off-by: Sasha Levin --- drivers/clk/qcom/camcc-sm7150.c | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/drivers/clk/qcom/camcc-sm7150.c b/drivers/clk/qcom/camcc-sm7150.c index 4a3baf5d8e858c..590548cac45bf4 100644 --- a/drivers/clk/qcom/camcc-sm7150.c +++ b/drivers/clk/qcom/camcc-sm7150.c @@ -139,13 +139,9 @@ static struct clk_fixed_factor camcc_pll1_out_even = { /* 1920MHz configuration */ static const struct alpha_pll_config camcc_pll2_config = { .l = 0x64, - .post_div_val = 0x3 << 8, - .post_div_mask = 0x3 << 8, - .early_output_mask = BIT(3), - .aux_output_mask = BIT(1), - .main_output_mask = BIT(0), .config_ctl_hi_val = 0x400003d6, .config_ctl_val = 0x20000954, + .user_ctl_val = 0x0000030b, }; static struct clk_alpha_pll camcc_pll2 = { From 599b5f3f4224a53c7622e7ddf12cfaa8ee78aa35 Mon Sep 17 00:00:00 2001 From: Haotian Zhang Date: Tue, 21 Oct 2025 00:02:15 +0800 Subject: [PATCH 0455/3902] soc: qcom: gsbi: fix double disable caused by devm [ Upstream commit 2286e18e3937c69cc103308a8c1d4898d8a7b04f ] In the commit referenced by the Fixes tag, devm_clk_get_enabled() was introduced to replace devm_clk_get() and clk_prepare_enable(). While the clk_disable_unprepare() call in the error path was correctly removed, the one in the remove function was overlooked, leading to a double disable issue. Remove the redundant clk_disable_unprepare() call from gsbi_remove() to fix this issue. Since all resources are now managed by devres and will be automatically released, the remove function serves no purpose and can be deleted entirely. Fixes: 489d7a8cc286 ("soc: qcom: use devm_clk_get_enabled() in gsbi_probe()") Signed-off-by: Haotian Zhang Reviewed-by: Konrad Dybcio Link: https://lore.kernel.org/stable/20251020160215.523-1-vulab%40iscas.ac.cn Link: https://lore.kernel.org/r/20251020160215.523-1-vulab@iscas.ac.cn Signed-off-by: Bjorn Andersson Signed-off-by: Sasha Levin --- drivers/soc/qcom/qcom_gsbi.c | 8 -------- 1 file changed, 8 deletions(-) diff --git a/drivers/soc/qcom/qcom_gsbi.c b/drivers/soc/qcom/qcom_gsbi.c index 8f1158e0c6313a..a25d1de592f060 100644 --- a/drivers/soc/qcom/qcom_gsbi.c +++ b/drivers/soc/qcom/qcom_gsbi.c @@ -212,13 +212,6 @@ static int gsbi_probe(struct platform_device *pdev) return of_platform_populate(node, NULL, NULL, &pdev->dev); } -static void gsbi_remove(struct platform_device *pdev) -{ - struct gsbi_info *gsbi = platform_get_drvdata(pdev); - - clk_disable_unprepare(gsbi->hclk); -} - static const struct of_device_id gsbi_dt_match[] = { { .compatible = "qcom,gsbi-v1.0.0", }, { }, @@ -232,7 +225,6 @@ static struct platform_driver gsbi_driver = { .of_match_table = gsbi_dt_match, }, .probe = gsbi_probe, - .remove = gsbi_remove, }; module_platform_driver(gsbi_driver); From b7090a5c153105b9fd221a5a81459ee8cd5babd6 Mon Sep 17 00:00:00 2001 From: Thorsten Blum Date: Mon, 13 Oct 2025 13:40:10 +0200 Subject: [PATCH 0456/3902] crypto: asymmetric_keys - prevent overflow in asymmetric_key_generate_id [ Upstream commit df0845cf447ae1556c3440b8b155de0926cbaa56 ] Use check_add_overflow() to guard against potential integer overflows when adding the binary blob lengths and the size of an asymmetric_key_id structure and return ERR_PTR(-EOVERFLOW) accordingly. This prevents a possible buffer overflow when copying data from potentially malicious X.509 certificate fields that can be arbitrarily large, such as ASN.1 INTEGER serial numbers, issuer names, etc. Fixes: 7901c1a8effb ("KEYS: Implement binary asymmetric key ID handling") Signed-off-by: Thorsten Blum Reviewed-by: Lukas Wunner Signed-off-by: Herbert Xu Signed-off-by: Sasha Levin --- crypto/asymmetric_keys/asymmetric_type.c | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/crypto/asymmetric_keys/asymmetric_type.c b/crypto/asymmetric_keys/asymmetric_type.c index ba2d9d1ea235a7..348966ea2175c9 100644 --- a/crypto/asymmetric_keys/asymmetric_type.c +++ b/crypto/asymmetric_keys/asymmetric_type.c @@ -11,6 +11,7 @@ #include #include #include +#include #include #include #include @@ -141,12 +142,17 @@ struct asymmetric_key_id *asymmetric_key_generate_id(const void *val_1, size_t len_2) { struct asymmetric_key_id *kid; - - kid = kmalloc(sizeof(struct asymmetric_key_id) + len_1 + len_2, - GFP_KERNEL); + size_t kid_sz; + size_t len; + + if (check_add_overflow(len_1, len_2, &len)) + return ERR_PTR(-EOVERFLOW); + if (check_add_overflow(sizeof(struct asymmetric_key_id), len, &kid_sz)) + return ERR_PTR(-EOVERFLOW); + kid = kmalloc(kid_sz, GFP_KERNEL); if (!kid) return ERR_PTR(-ENOMEM); - kid->len = len_1 + len_2; + kid->len = len; memcpy(kid->data, val_1, len_1); memcpy(kid->data + len_1, val_2, len_2); return kid; From 5c75e55e43e8699ee1189ee568a5157753d097c1 Mon Sep 17 00:00:00 2001 From: nieweiqiang Date: Sat, 18 Oct 2025 19:27:39 +0800 Subject: [PATCH 0457/3902] crypto: hisilicon/qm - restore original qos values [ Upstream commit e7066160f5b4187ad9869b712fa7a35d3d5be6b9 ] When the new qos valus setting fails, restore to the original qos values. Fixes: 72b010dc33b9 ("crypto: hisilicon/qm - supports writing QoS int the host") Signed-off-by: nieweiqiang Signed-off-by: Chenghai Huang Signed-off-by: Herbert Xu Signed-off-by: Sasha Levin --- drivers/crypto/hisilicon/qm.c | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/drivers/crypto/hisilicon/qm.c b/drivers/crypto/hisilicon/qm.c index 3b391a1466353d..0968304c0cb510 100644 --- a/drivers/crypto/hisilicon/qm.c +++ b/drivers/crypto/hisilicon/qm.c @@ -3678,6 +3678,7 @@ static void qm_clear_vft_config(struct hisi_qm *qm) static int qm_func_shaper_enable(struct hisi_qm *qm, u32 fun_index, u32 qos) { struct device *dev = &qm->pdev->dev; + struct qm_shaper_factor t_factor; u32 ir = qos * QM_QOS_RATE; int ret, total_vfs, i; @@ -3685,6 +3686,7 @@ static int qm_func_shaper_enable(struct hisi_qm *qm, u32 fun_index, u32 qos) if (fun_index > total_vfs) return -EINVAL; + memcpy(&t_factor, &qm->factor[fun_index], sizeof(t_factor)); qm->factor[fun_index].func_qos = qos; ret = qm_get_shaper_para(ir, &qm->factor[fun_index]); @@ -3698,11 +3700,21 @@ static int qm_func_shaper_enable(struct hisi_qm *qm, u32 fun_index, u32 qos) ret = qm_set_vft_common(qm, SHAPER_VFT, fun_index, i, 1); if (ret) { dev_err(dev, "type: %d, failed to set shaper vft!\n", i); - return -EINVAL; + goto back_func_qos; } } return 0; + +back_func_qos: + memcpy(&qm->factor[fun_index], &t_factor, sizeof(t_factor)); + for (i--; i >= ALG_TYPE_0; i--) { + ret = qm_set_vft_common(qm, SHAPER_VFT, fun_index, i, 1); + if (ret) + dev_err(dev, "failed to restore shaper vft during rollback!\n"); + } + + return -EINVAL; } static u32 qm_get_shaper_vft_qos(struct hisi_qm *qm, u32 fun_index) From 18d4706956b73895ea75131b827a76c24775348c Mon Sep 17 00:00:00 2001 From: Baochen Qiang Date: Fri, 17 Oct 2025 09:48:59 +0800 Subject: [PATCH 0458/3902] wifi: ath11k: fix VHT MCS assignment [ Upstream commit 47d0cd6bccb4604192633cc8d29511e85d811fc0 ] While associating, firmware needs to know peer's receive capability to calculate its own VHT transmit MCS, currently host sends this information to firmware via mcs->rx_mcs_set field, this is wrong as firmware actually takes it from mcs->tx_mcs_set field. Till now there is no failure seen due to this, most likely because almost all peers are advertising the same capability for both transmit and receive. Swap the assignment to fix it. Besides, rate control mask is meant to limit our own transmit MCS, hence need to go via mcs->tx_mcs_set field. With the aforementioned swapping done, change is needed as well to apply it to the peer's receive capability rather than transmit capability. Tested-on: WCN6855 hw2.1 PCI WLAN.HSP.1.1-03125-QCAHSPSWPL_V1_V2_SILICONZ_LITE-3.6510.41 Tested-on: QCN9274 hw2.0 PCI WLAN.WBE.1.4.1-00199-QCAHKSWPL_SILICONZ-1 Fixes: d5c65159f289 ("ath11k: driver for Qualcomm IEEE 802.11ax devices") Signed-off-by: Baochen Qiang Reviewed-by: Vasanthakumar Thiagarajan Link: https://patch.msgid.link/20251017-ath11k-mcs-assignment-v1-1-da40825c1783@oss.qualcomm.com Signed-off-by: Jeff Johnson Signed-off-by: Sasha Levin --- drivers/net/wireless/ath/ath11k/mac.c | 4 ++-- drivers/net/wireless/ath/ath11k/wmi.c | 13 ++++++++----- drivers/net/wireless/ath/ath11k/wmi.h | 2 ++ 3 files changed, 12 insertions(+), 7 deletions(-) diff --git a/drivers/net/wireless/ath/ath11k/mac.c b/drivers/net/wireless/ath/ath11k/mac.c index 0e41b5a91d66da..49c639d73d58d4 100644 --- a/drivers/net/wireless/ath/ath11k/mac.c +++ b/drivers/net/wireless/ath/ath11k/mac.c @@ -2235,9 +2235,9 @@ static void ath11k_peer_assoc_h_vht(struct ath11k *ar, arg->peer_nss = min(sta->deflink.rx_nss, max_nss); arg->rx_max_rate = __le16_to_cpu(vht_cap->vht_mcs.rx_highest); arg->rx_mcs_set = __le16_to_cpu(vht_cap->vht_mcs.rx_mcs_map); + arg->rx_mcs_set = ath11k_peer_assoc_h_vht_limit(arg->rx_mcs_set, vht_mcs_mask); arg->tx_max_rate = __le16_to_cpu(vht_cap->vht_mcs.tx_highest); - arg->tx_mcs_set = ath11k_peer_assoc_h_vht_limit( - __le16_to_cpu(vht_cap->vht_mcs.tx_mcs_map), vht_mcs_mask); + arg->tx_mcs_set = __le16_to_cpu(vht_cap->vht_mcs.tx_mcs_map); /* In IPQ8074 platform, VHT mcs rate 10 and 11 is enabled by default. * VHT mcs rate 10 and 11 is not supported in 11ac standard. diff --git a/drivers/net/wireless/ath/ath11k/wmi.c b/drivers/net/wireless/ath/ath11k/wmi.c index e3b444333deed1..649839d2432931 100644 --- a/drivers/net/wireless/ath/ath11k/wmi.c +++ b/drivers/net/wireless/ath/ath11k/wmi.c @@ -1,7 +1,7 @@ // SPDX-License-Identifier: BSD-3-Clause-Clear /* * Copyright (c) 2018-2019 The Linux Foundation. All rights reserved. - * Copyright (c) 2021-2025 Qualcomm Innovation Center, Inc. All rights reserved. + * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries. */ #include #include @@ -2061,10 +2061,13 @@ int ath11k_wmi_send_peer_assoc_cmd(struct ath11k *ar, cmd->peer_bw_rxnss_override |= param->peer_bw_rxnss_override; if (param->vht_capable) { - mcs->rx_max_rate = param->rx_max_rate; - mcs->rx_mcs_set = param->rx_mcs_set; - mcs->tx_max_rate = param->tx_max_rate; - mcs->tx_mcs_set = param->tx_mcs_set; + /* firmware interprets mcs->tx_mcs_set field as peer's + * RX capability + */ + mcs->tx_max_rate = param->rx_max_rate; + mcs->tx_mcs_set = param->rx_mcs_set; + mcs->rx_max_rate = param->tx_max_rate; + mcs->rx_mcs_set = param->tx_mcs_set; } /* HE Rates */ diff --git a/drivers/net/wireless/ath/ath11k/wmi.h b/drivers/net/wireless/ath/ath11k/wmi.h index 9fcffaa2f383c9..6e9354297e71d3 100644 --- a/drivers/net/wireless/ath/ath11k/wmi.h +++ b/drivers/net/wireless/ath/ath11k/wmi.h @@ -4133,8 +4133,10 @@ struct wmi_rate_set { struct wmi_vht_rate_set { u32 tlv_header; u32 rx_max_rate; + /* MCS at which the peer can transmit */ u32 rx_mcs_set; u32 tx_max_rate; + /* MCS at which the peer can receive */ u32 tx_mcs_set; u32 tx_max_mcs_nss; } __packed; From 6b1a0da75932353f66e710976ca85a7131f647ff Mon Sep 17 00:00:00 2001 From: Baochen Qiang Date: Fri, 17 Oct 2025 09:49:00 +0800 Subject: [PATCH 0459/3902] wifi: ath11k: fix peer HE MCS assignment [ Upstream commit 4a013ca2d490c73c40588d62712ffaa432046a04 ] In ath11k_wmi_send_peer_assoc_cmd(), peer's transmit MCS is sent to firmware as receive MCS while peer's receive MCS sent as transmit MCS, which goes against firmwire's definition. While connecting to a misbehaved AP that advertises 0xffff (meaning not supported) for 160 MHz transmit MCS map, firmware crashes due to 0xffff is assigned to he_mcs->rx_mcs_set field. Ext Tag: HE Capabilities [...] Supported HE-MCS and NSS Set [...] Rx and Tx MCS Maps 160 MHz [...] Tx HE-MCS Map 160 MHz: 0xffff Swap the assignment to fix this issue. As the HE rate control mask is meant to limit our own transmit MCS, it needs to go via he_mcs->rx_mcs_set field. With the aforementioned swapping done, change is needed as well to apply it to the peer's receive MCS. Tested-on: WCN6855 hw2.1 PCI WLAN.HSP.1.1-03125-QCAHSPSWPL_V1_V2_SILICONZ_LITE-3.6510.41 Tested-on: QCN9274 hw2.0 PCI WLAN.WBE.1.4.1-00199-QCAHKSWPL_SILICONZ-1 Fixes: 61fe43e7216d ("ath11k: add support for setting fixed HE rate/gi/ltf") Signed-off-by: Baochen Qiang Reviewed-by: Vasanthakumar Thiagarajan Link: https://patch.msgid.link/20251017-ath11k-mcs-assignment-v1-2-da40825c1783@oss.qualcomm.com Signed-off-by: Jeff Johnson Signed-off-by: Sasha Levin --- drivers/net/wireless/ath/ath11k/mac.c | 4 ++-- drivers/net/wireless/ath/ath11k/wmi.c | 7 +++++-- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/drivers/net/wireless/ath/ath11k/mac.c b/drivers/net/wireless/ath/ath11k/mac.c index 49c639d73d58d4..f142c17aa9aa74 100644 --- a/drivers/net/wireless/ath/ath11k/mac.c +++ b/drivers/net/wireless/ath/ath11k/mac.c @@ -2522,10 +2522,10 @@ static void ath11k_peer_assoc_h_he(struct ath11k *ar, he_tx_mcs = v; } v = le16_to_cpu(he_cap->he_mcs_nss_supp.rx_mcs_160); + v = ath11k_peer_assoc_h_he_limit(v, he_mcs_mask); arg->peer_he_rx_mcs_set[WMI_HECAP_TXRX_MCS_NSS_IDX_160] = v; v = le16_to_cpu(he_cap->he_mcs_nss_supp.tx_mcs_160); - v = ath11k_peer_assoc_h_he_limit(v, he_mcs_mask); arg->peer_he_tx_mcs_set[WMI_HECAP_TXRX_MCS_NSS_IDX_160] = v; arg->peer_he_mcs_count++; @@ -2535,10 +2535,10 @@ static void ath11k_peer_assoc_h_he(struct ath11k *ar, default: v = le16_to_cpu(he_cap->he_mcs_nss_supp.rx_mcs_80); + v = ath11k_peer_assoc_h_he_limit(v, he_mcs_mask); arg->peer_he_rx_mcs_set[WMI_HECAP_TXRX_MCS_NSS_IDX_80] = v; v = le16_to_cpu(he_cap->he_mcs_nss_supp.tx_mcs_80); - v = ath11k_peer_assoc_h_he_limit(v, he_mcs_mask); arg->peer_he_tx_mcs_set[WMI_HECAP_TXRX_MCS_NSS_IDX_80] = v; arg->peer_he_mcs_count++; diff --git a/drivers/net/wireless/ath/ath11k/wmi.c b/drivers/net/wireless/ath/ath11k/wmi.c index 649839d2432931..110035dae8a618 100644 --- a/drivers/net/wireless/ath/ath11k/wmi.c +++ b/drivers/net/wireless/ath/ath11k/wmi.c @@ -2091,8 +2091,11 @@ int ath11k_wmi_send_peer_assoc_cmd(struct ath11k *ar, FIELD_PREP(WMI_TLV_LEN, sizeof(*he_mcs) - TLV_HDR_SIZE); - he_mcs->rx_mcs_set = param->peer_he_tx_mcs_set[i]; - he_mcs->tx_mcs_set = param->peer_he_rx_mcs_set[i]; + /* firmware interprets mcs->rx_mcs_set field as peer's + * RX capability + */ + he_mcs->rx_mcs_set = param->peer_he_rx_mcs_set[i]; + he_mcs->tx_mcs_set = param->peer_he_tx_mcs_set[i]; ptr += sizeof(*he_mcs); } From 1acc6fc225a9cf872425be5d6ffde6ea5d42bb4f Mon Sep 17 00:00:00 2001 From: Heiko Carstens Date: Mon, 20 Oct 2025 16:17:54 +0200 Subject: [PATCH 0460/3902] s390/smp: Fix fallback CPU detection [ Upstream commit 07a75d08cfa1b883a6e1256666e5f0617ee99231 ] In case SCLP CPU detection does not work a fallback mechanism using SIGP is in place. Since a cleanup this does not work correctly anymore: new CPUs are only considered if their type matches the boot CPU. Before the cleanup the information if a CPU type should be considered was also part of a structure generated by the fallback mechanism and indicated that a CPU type should not be considered when adding CPUs. Since the rework a global SCLP state is used instead. If the global SCLP state indicates that the CPU type should be considered and the fallback mechanism is used, there may be a mismatch with CPU types if CPUs are added. This can lead to a system with only a single CPU even tough there are many more CPUs. Address this by simply copying the boot cpu type into the generated data structure from the fallback mechanism. Reported-by: Alexander Egorenkov Fixes: d08d94306e90 ("s390/smp: cleanup core vs. cpu in the SCLP interface") Reviewed-by: Mete Durlu Signed-off-by: Heiko Carstens Signed-off-by: Sasha Levin --- arch/s390/kernel/smp.c | 1 + 1 file changed, 1 insertion(+) diff --git a/arch/s390/kernel/smp.c b/arch/s390/kernel/smp.c index da84c0dc6b7e0f..70df4ca5d4436c 100644 --- a/arch/s390/kernel/smp.c +++ b/arch/s390/kernel/smp.c @@ -697,6 +697,7 @@ static void __ref smp_get_core_info(struct sclp_core_info *info, int early) continue; info->core[info->configured].core_id = address >> smp_cpu_mt_shift; + info->core[info->configured].type = boot_core_type; info->configured++; } info->combined = info->configured; From 27c62aeef710d443dd24e16bb9d05013db737f37 Mon Sep 17 00:00:00 2001 From: Bart Van Assche Date: Tue, 14 Oct 2025 13:00:58 -0700 Subject: [PATCH 0461/3902] scsi: ufs: core: Move the ufshcd_enable_intr() declaration [ Upstream commit b30006b5bec1dcba207bc42e7f7cd96a568acc27 ] ufshcd_enable_intr() is not exported and hence should not be declared in include/ufs/ufshcd.h. Fixes: 253757797973 ("scsi: ufs: core: Change MCQ interrupt enable flow") Signed-off-by: Bart Van Assche Reviewed-by: Peter Wang Link: https://patch.msgid.link/20251014200118.3390839-7-bvanassche@acm.org Signed-off-by: Martin K. Petersen Signed-off-by: Sasha Levin --- drivers/ufs/core/ufshcd-priv.h | 2 ++ include/ufs/ufshcd.h | 1 - 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/drivers/ufs/core/ufshcd-priv.h b/drivers/ufs/core/ufshcd-priv.h index d0a2c963a27d37..1f0d38aa37f920 100644 --- a/drivers/ufs/core/ufshcd-priv.h +++ b/drivers/ufs/core/ufshcd-priv.h @@ -6,6 +6,8 @@ #include #include +void ufshcd_enable_intr(struct ufs_hba *hba, u32 intrs); + static inline bool ufshcd_is_user_access_allowed(struct ufs_hba *hba) { return !hba->shutting_down; diff --git a/include/ufs/ufshcd.h b/include/ufs/ufshcd.h index 0f95576bf1f6ca..d949db3a467596 100644 --- a/include/ufs/ufshcd.h +++ b/include/ufs/ufshcd.h @@ -1302,7 +1302,6 @@ static inline void ufshcd_rmwl(struct ufs_hba *hba, u32 mask, u32 val, u32 reg) void ufshcd_enable_irq(struct ufs_hba *hba); void ufshcd_disable_irq(struct ufs_hba *hba); -void ufshcd_enable_intr(struct ufs_hba *hba, u32 intrs); int ufshcd_alloc_host(struct device *, struct ufs_hba **); int ufshcd_hba_enable(struct ufs_hba *hba); int ufshcd_init(struct ufs_hba *, void __iomem *, unsigned int); From 0f984f5f8126378423c2f346a15ecf9f216c2983 Mon Sep 17 00:00:00 2001 From: Heiko Carstens Date: Fri, 24 Oct 2025 12:24:33 +0200 Subject: [PATCH 0462/3902] s390/ap: Don't leak debug feature files if AP instructions are not available [ Upstream commit 020d5dc57874e58d3ebae398f3fe258f029e3d06 ] If no AP instructions are available the AP bus module leaks registered debug feature files. Change function call order to fix this. Fixes: cccd85bfb7bf ("s390/zcrypt: Rework debug feature invocations.") Reviewed-by: Harald Freudenberger Signed-off-by: Heiko Carstens Signed-off-by: Sasha Levin --- drivers/s390/crypto/ap_bus.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/drivers/s390/crypto/ap_bus.c b/drivers/s390/crypto/ap_bus.c index 65f1a127cc3f6b..dfd5d0f61a70de 100644 --- a/drivers/s390/crypto/ap_bus.c +++ b/drivers/s390/crypto/ap_bus.c @@ -2484,15 +2484,15 @@ static int __init ap_module_init(void) { int rc; - rc = ap_debug_init(); - if (rc) - return rc; - if (!ap_instructions_available()) { pr_warn("The hardware system does not support AP instructions\n"); return -ENODEV; } + rc = ap_debug_init(); + if (rc) + return rc; + /* init ap_queue hashtable */ hash_init(ap_queues); From 04d808a6efb58f0a2fe040301d7ebe736c259d6b Mon Sep 17 00:00:00 2001 From: Len Brown Date: Mon, 20 Oct 2025 18:41:38 -0300 Subject: [PATCH 0463/3902] tools/power turbostat: Regression fix Uncore MHz printed in hex [ Upstream commit 92664f2e6ab2228a3330734fc72dabeaf8a49ee1 ] A patch to allow specifying FORMAT_AVERAGE to added counters... broke the internally added counter for Cluster Uncore MHz -- printing it in HEX. Fixes: dcd1c379b0f1 ("tools/power turbostat: add format "average" for external attributes") Reported-by: Andrej Tkalcec Signed-off-by: Len Brown Signed-off-by: Sasha Levin --- tools/power/x86/turbostat/turbostat.c | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/tools/power/x86/turbostat/turbostat.c b/tools/power/x86/turbostat/turbostat.c index f2512d78bcbd8f..1b5ca2f4e92ff2 100644 --- a/tools/power/x86/turbostat/turbostat.c +++ b/tools/power/x86/turbostat/turbostat.c @@ -3285,13 +3285,13 @@ int format_counters(PER_THREAD_PARAMS) /* Added counters */ for (i = 0, mp = sys.tp; mp; i++, mp = mp->next) { - if (mp->format == FORMAT_RAW || mp->format == FORMAT_AVERAGE) { + if (mp->format == FORMAT_RAW) { if (mp->width == 32) outp += sprintf(outp, "%s0x%08x", (printed++ ? delim : ""), (unsigned int)t->counter[i]); else outp += sprintf(outp, "%s0x%016llx", (printed++ ? delim : ""), t->counter[i]); - } else if (mp->format == FORMAT_DELTA) { + } else if (mp->format == FORMAT_DELTA || mp->format == FORMAT_AVERAGE) { if ((mp->type == COUNTER_ITEMS) && sums_need_wide_columns) outp += sprintf(outp, "%s%8lld", (printed++ ? delim : ""), t->counter[i]); else @@ -3382,13 +3382,13 @@ int format_counters(PER_THREAD_PARAMS) outp += sprintf(outp, "%s%lld", (printed++ ? delim : ""), c->core_throt_cnt); for (i = 0, mp = sys.cp; mp; i++, mp = mp->next) { - if (mp->format == FORMAT_RAW || mp->format == FORMAT_AVERAGE) { + if (mp->format == FORMAT_RAW) { if (mp->width == 32) outp += sprintf(outp, "%s0x%08x", (printed++ ? delim : ""), (unsigned int)c->counter[i]); else outp += sprintf(outp, "%s0x%016llx", (printed++ ? delim : ""), c->counter[i]); - } else if (mp->format == FORMAT_DELTA) { + } else if (mp->format == FORMAT_DELTA || mp->format == FORMAT_AVERAGE) { if ((mp->type == COUNTER_ITEMS) && sums_need_wide_columns) outp += sprintf(outp, "%s%8lld", (printed++ ? delim : ""), c->counter[i]); else @@ -3581,7 +3581,7 @@ int format_counters(PER_THREAD_PARAMS) outp += sprintf(outp, "%s%d", (printed++ ? delim : ""), p->uncore_mhz); for (i = 0, mp = sys.pp; mp; i++, mp = mp->next) { - if (mp->format == FORMAT_RAW || mp->format == FORMAT_AVERAGE) { + if (mp->format == FORMAT_RAW) { if (mp->width == 32) outp += sprintf(outp, "%s0x%08x", (printed++ ? delim : ""), (unsigned int)p->counter[i]); @@ -3758,7 +3758,7 @@ int delta_package(struct pkg_data *new, struct pkg_data *old) new->rapl_dram_perf_status.raw_value - old->rapl_dram_perf_status.raw_value; for (i = 0, mp = sys.pp; mp; i++, mp = mp->next) { - if (mp->format == FORMAT_RAW || mp->format == FORMAT_AVERAGE) + if (mp->format == FORMAT_RAW) old->counter[i] = new->counter[i]; else if (mp->format == FORMAT_AVERAGE) old->counter[i] = new->counter[i]; From d0a5b5d56438b372cf601b50087e4330165524f2 Mon Sep 17 00:00:00 2001 From: Baochen Qiang Date: Fri, 17 Oct 2025 10:36:36 +0800 Subject: [PATCH 0464/3902] wifi: ath12k: restore register window after global reset [ Upstream commit a41281f6518e485220d180a6031d302a736fc463 ] Hardware target implements an address space larger than that PCI BAR can map. In order to be able to access the whole target address space, the BAR space is split into 4 segments, of which the last 3, called windows, can be dynamically mapped to the desired area. This is achieved by updating WINDOW_REG_ADDRESS register with appropriate window value. Currently each time when accessing a register that beyond WINDOW_START, host calculates the window value and caches it after window update, this way next time when accessing a register falling in the same window, host knows that the window is already good hence no additional update needed. However this mechanism breaks after global reset is triggered in ath12k_pci_soc_global_reset(), because with global reset hardware resets WINDOW_REG_ADDRESS register hence the window is not properly mapped any more. Current host does nothing about this, as a result a subsequent register access may not work as expected if it falls in a window same as before. Although there is no obvious issue seen now, better to fix it to avoid future problem. The fix is done by restoring the window register after global reset. Tested-on: WCN7850 hw2.0 PCI WLAN.HMT.1.1.c5-00284.1-QCAHMTSWPL_V1.0_V2.0_SILICONZ-3 Fixes: d889913205cf ("wifi: ath12k: driver for Qualcomm Wi-Fi 7 devices") Signed-off-by: Baochen Qiang Reviewed-by: Vasanthakumar Thiagarajan Link: https://patch.msgid.link/20251017-ath12k-reset-window-cache-v1-1-29e0e751deed@oss.qualcomm.com Signed-off-by: Jeff Johnson Signed-off-by: Sasha Levin --- drivers/net/wireless/ath/ath12k/pci.c | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/drivers/net/wireless/ath/ath12k/pci.c b/drivers/net/wireless/ath/ath12k/pci.c index c729d5526c753d..60b8f7361b7f63 100644 --- a/drivers/net/wireless/ath/ath12k/pci.c +++ b/drivers/net/wireless/ath/ath12k/pci.c @@ -1,7 +1,7 @@ // SPDX-License-Identifier: BSD-3-Clause-Clear /* * Copyright (c) 2019-2021 The Linux Foundation. All rights reserved. - * Copyright (c) 2021-2025 Qualcomm Innovation Center, Inc. All rights reserved. + * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries. */ #include @@ -218,6 +218,19 @@ static inline bool ath12k_pci_is_offset_within_mhi_region(u32 offset) return (offset >= PCI_MHIREGLEN_REG && offset <= PCI_MHI_REGION_END); } +static void ath12k_pci_restore_window(struct ath12k_base *ab) +{ + struct ath12k_pci *ab_pci = ath12k_pci_priv(ab); + + spin_lock_bh(&ab_pci->window_lock); + + iowrite32(WINDOW_ENABLE_BIT | ab_pci->register_window, + ab->mem + WINDOW_REG_ADDRESS); + ioread32(ab->mem + WINDOW_REG_ADDRESS); + + spin_unlock_bh(&ab_pci->window_lock); +} + static void ath12k_pci_soc_global_reset(struct ath12k_base *ab) { u32 val, delay; @@ -242,6 +255,11 @@ static void ath12k_pci_soc_global_reset(struct ath12k_base *ab) val = ath12k_pci_read32(ab, PCIE_SOC_GLOBAL_RESET); if (val == 0xffffffff) ath12k_warn(ab, "link down error during global reset\n"); + + /* Restore window register as its content is cleared during + * hardware global reset, such that it aligns with host cache. + */ + ath12k_pci_restore_window(ab); } static void ath12k_pci_clear_dbg_registers(struct ath12k_base *ab) From 62e122809aed1aaeb61b67dc8d00648a4a230bdf Mon Sep 17 00:00:00 2001 From: Lizhi Hou Date: Fri, 24 Oct 2025 09:55:03 -0700 Subject: [PATCH 0465/3902] accel/amdxdna: Fix uninitialized return value [ Upstream commit 81233d5419cf20e4f5ef505882951616888a2ef9 ] In aie2_get_hwctx_status() and aie2_query_ctx_status_array(), the functions could return an uninitialized value in some cases. Update them to always return 0. The amount of valid results is indicated by the returned buffer_size, element_size, and num_element fields. Fixes: 2f509fe6a42c ("accel/amdxdna: Add ioctl DRM_IOCTL_AMDXDNA_GET_ARRAY") Reviewed-by: Mario Limonciello (AMD) Signed-off-by: Lizhi Hou Link: https://patch.msgid.link/20251024165503.1548131-1-lizhi.hou@amd.com Signed-off-by: Sasha Levin --- drivers/accel/amdxdna/aie2_pci.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/accel/amdxdna/aie2_pci.c b/drivers/accel/amdxdna/aie2_pci.c index 6e39c769bb6d8f..43f725e1a2d761 100644 --- a/drivers/accel/amdxdna/aie2_pci.c +++ b/drivers/accel/amdxdna/aie2_pci.c @@ -845,7 +845,7 @@ static int aie2_get_hwctx_status(struct amdxdna_client *client, } args->buffer_size -= (u32)(array_args.buffer - args->buffer); - return ret; + return 0; } static int aie2_get_info(struct amdxdna_client *client, struct amdxdna_drm_get_info *args) @@ -920,7 +920,7 @@ static int aie2_query_ctx_status_array(struct amdxdna_client *client, args->num_element = (u32)((array_args.buffer - args->buffer) / args->element_size); - return ret; + return 0; } static int aie2_get_array(struct amdxdna_client *client, From fa66269056680f61289f46da813e63dd439ccdb7 Mon Sep 17 00:00:00 2001 From: Przemek Kitszel Date: Fri, 12 Sep 2025 15:06:20 +0200 Subject: [PATCH 0466/3902] ice: move service task start out of ice_init_pf() [ Upstream commit 806c4f32a80627f8977fda53520afc41491d162f ] Move service task start out of ice_init_pf(). Do analogous with deinit. Service task is needed up to the very end of driver removal, later commit of the series will move it later on execution timeline. Signed-off-by: Przemek Kitszel Tested-by: Rinitha S (A Contingent worker at Intel) Signed-off-by: Tony Nguyen Stable-dep-of: 1390b8b3d2be ("ice: remove duplicate call to ice_deinit_hw() on error paths") Signed-off-by: Sasha Levin --- drivers/net/ethernet/intel/ice/ice.h | 1 + drivers/net/ethernet/intel/ice/ice_main.c | 18 +++++++++++------- 2 files changed, 12 insertions(+), 7 deletions(-) diff --git a/drivers/net/ethernet/intel/ice/ice.h b/drivers/net/ethernet/intel/ice/ice.h index 22b8323ff0d0e4..0e58a58c23eb38 100644 --- a/drivers/net/ethernet/intel/ice/ice.h +++ b/drivers/net/ethernet/intel/ice/ice.h @@ -1029,6 +1029,7 @@ int ice_open(struct net_device *netdev); int ice_open_internal(struct net_device *netdev); int ice_stop(struct net_device *netdev); void ice_service_task_schedule(struct ice_pf *pf); +void ice_start_service_task(struct ice_pf *pf); int ice_load(struct ice_pf *pf); void ice_unload(struct ice_pf *pf); void ice_adv_lnk_speed_maps_init(void); diff --git a/drivers/net/ethernet/intel/ice/ice_main.c b/drivers/net/ethernet/intel/ice/ice_main.c index 86f5859e88ef53..41c2f0d52ce0d9 100644 --- a/drivers/net/ethernet/intel/ice/ice_main.c +++ b/drivers/net/ethernet/intel/ice/ice_main.c @@ -3951,7 +3951,6 @@ u16 ice_get_avail_rxq_count(struct ice_pf *pf) */ static void ice_deinit_pf(struct ice_pf *pf) { - ice_service_task_stop(pf); mutex_destroy(&pf->lag_mutex); mutex_destroy(&pf->adev_mutex); mutex_destroy(&pf->sw_mutex); @@ -4030,6 +4029,14 @@ static void ice_set_pf_caps(struct ice_pf *pf) pf->max_pf_rxqs = func_caps->common_cap.num_rxq; } +void ice_start_service_task(struct ice_pf *pf) +{ + timer_setup(&pf->serv_tmr, ice_service_timer, 0); + pf->serv_tmr_period = HZ; + INIT_WORK(&pf->serv_task, ice_service_task); + clear_bit(ICE_SERVICE_SCHED, pf->state); +} + /** * ice_init_pf - Initialize general software structures (struct ice_pf) * @pf: board private structure to initialize @@ -4049,12 +4056,6 @@ static int ice_init_pf(struct ice_pf *pf) init_waitqueue_head(&pf->reset_wait_queue); - /* setup service timer and periodic service task */ - timer_setup(&pf->serv_tmr, ice_service_timer, 0); - pf->serv_tmr_period = HZ; - INIT_WORK(&pf->serv_task, ice_service_task); - clear_bit(ICE_SERVICE_SCHED, pf->state); - mutex_init(&pf->avail_q_mutex); pf->avail_txqs = bitmap_zalloc(pf->max_pf_txqs, GFP_KERNEL); if (!pf->avail_txqs) @@ -4745,6 +4746,7 @@ int ice_init_dev(struct ice_pf *pf) ice_set_safe_mode_caps(hw); } + ice_start_service_task(pf); err = ice_init_pf(pf); if (err) { dev_err(dev, "ice_init_pf failed: %d\n", err); @@ -4791,6 +4793,7 @@ int ice_init_dev(struct ice_pf *pf) ice_clear_interrupt_scheme(pf); unroll_pf_init: ice_deinit_pf(pf); + ice_service_task_stop(pf); return err; } @@ -4799,6 +4802,7 @@ void ice_deinit_dev(struct ice_pf *pf) ice_free_irq_msix_misc(pf); ice_deinit_pf(pf); ice_deinit_hw(&pf->hw); + ice_service_task_stop(pf); /* Service task is already stopped, so call reset directly. */ ice_reset(&pf->hw, ICE_RESET_PFR); From 0380ea173bf93f0ad33afc3f992ca4e8091cf9fd Mon Sep 17 00:00:00 2001 From: Przemek Kitszel Date: Fri, 12 Sep 2025 15:06:21 +0200 Subject: [PATCH 0467/3902] ice: move ice_init_interrupt_scheme() prior ice_init_pf() [ Upstream commit 2fe18288fce6b421f1dc585bcb9dd3afa32d3ad9 ] Move ice_init_interrupt_scheme() prior ice_init_pf(). To enable the move ice_set_pf_caps() was moved out from ice_init_pf() to the caller (ice_init_dev()), and placed prior to the irq scheme init. The move makes deinit order of ice_deinit_dev() and failure-path of ice_init_pf() match (at least in terms of not calling ice_clear_interrupt_scheme() and ice_deinit_pf() in opposite ways). The new order aligns with findings made by Jakub Buchocki in the commit 24b454bc354a ("ice: Fix ice module unload"). Signed-off-by: Przemek Kitszel Reviewed-by: Aleksandr Loktionov Tested-by: Rinitha S (A Contingent worker at Intel) Signed-off-by: Tony Nguyen Stable-dep-of: 1390b8b3d2be ("ice: remove duplicate call to ice_deinit_hw() on error paths") Signed-off-by: Sasha Levin --- drivers/net/ethernet/intel/ice/ice_main.c | 25 ++++++++++------------- 1 file changed, 11 insertions(+), 14 deletions(-) diff --git a/drivers/net/ethernet/intel/ice/ice_main.c b/drivers/net/ethernet/intel/ice/ice_main.c index 41c2f0d52ce0d9..13db83fb383e68 100644 --- a/drivers/net/ethernet/intel/ice/ice_main.c +++ b/drivers/net/ethernet/intel/ice/ice_main.c @@ -4043,8 +4043,6 @@ void ice_start_service_task(struct ice_pf *pf) */ static int ice_init_pf(struct ice_pf *pf) { - ice_set_pf_caps(pf); - mutex_init(&pf->sw_mutex); mutex_init(&pf->tc_mutex); mutex_init(&pf->adev_mutex); @@ -4746,11 +4744,18 @@ int ice_init_dev(struct ice_pf *pf) ice_set_safe_mode_caps(hw); } + ice_set_pf_caps(pf); + err = ice_init_interrupt_scheme(pf); + if (err) { + dev_err(dev, "ice_init_interrupt_scheme failed: %d\n", err); + return -EIO; + } + ice_start_service_task(pf); err = ice_init_pf(pf); if (err) { dev_err(dev, "ice_init_pf failed: %d\n", err); - return err; + goto unroll_irq_scheme_init; } pf->hw.udp_tunnel_nic.set_port = ice_udp_tunnel_set_port; @@ -4768,14 +4773,6 @@ int ice_init_dev(struct ice_pf *pf) pf->hw.udp_tunnel_nic.tables[1].tunnel_types = UDP_TUNNEL_TYPE_GENEVE; } - - err = ice_init_interrupt_scheme(pf); - if (err) { - dev_err(dev, "ice_init_interrupt_scheme failed: %d\n", err); - err = -EIO; - goto unroll_pf_init; - } - /* In case of MSIX we are going to setup the misc vector right here * to handle admin queue events etc. In case of legacy and MSI * the misc functionality and queue processing is combined in @@ -4784,16 +4781,16 @@ int ice_init_dev(struct ice_pf *pf) err = ice_req_irq_msix_misc(pf); if (err) { dev_err(dev, "setup of misc vector failed: %d\n", err); - goto unroll_irq_scheme_init; + goto unroll_pf_init; } return 0; -unroll_irq_scheme_init: - ice_clear_interrupt_scheme(pf); unroll_pf_init: ice_deinit_pf(pf); +unroll_irq_scheme_init: ice_service_task_stop(pf); + ice_clear_interrupt_scheme(pf); return err; } From 46d0b034b15d4b75c346293560468edf8a28796e Mon Sep 17 00:00:00 2001 From: Przemek Kitszel Date: Fri, 12 Sep 2025 15:06:22 +0200 Subject: [PATCH 0468/3902] ice: ice_init_pf: destroy mutexes and xarrays on memory alloc failure [ Upstream commit 71430451f81bd6550e46d89b69103a111fc42982 ] Unroll actions of ice_init_pf() when it fails. ice_deinit_pf() happens to be perfect to call here. Signed-off-by: Przemek Kitszel Reviewed-by: Aleksandr Loktionov Tested-by: Rinitha S (A Contingent worker at Intel) Signed-off-by: Tony Nguyen Stable-dep-of: 1390b8b3d2be ("ice: remove duplicate call to ice_deinit_hw() on error paths") Signed-off-by: Sasha Levin --- drivers/net/ethernet/intel/ice/ice_main.c | 31 +++++++++-------------- 1 file changed, 12 insertions(+), 19 deletions(-) diff --git a/drivers/net/ethernet/intel/ice/ice_main.c b/drivers/net/ethernet/intel/ice/ice_main.c index 13db83fb383e68..80349aed1f9d6e 100644 --- a/drivers/net/ethernet/intel/ice/ice_main.c +++ b/drivers/net/ethernet/intel/ice/ice_main.c @@ -3951,6 +3951,8 @@ u16 ice_get_avail_rxq_count(struct ice_pf *pf) */ static void ice_deinit_pf(struct ice_pf *pf) { + /* note that we unroll also on ice_init_pf() failure here */ + mutex_destroy(&pf->lag_mutex); mutex_destroy(&pf->adev_mutex); mutex_destroy(&pf->sw_mutex); @@ -4055,25 +4057,6 @@ static int ice_init_pf(struct ice_pf *pf) init_waitqueue_head(&pf->reset_wait_queue); mutex_init(&pf->avail_q_mutex); - pf->avail_txqs = bitmap_zalloc(pf->max_pf_txqs, GFP_KERNEL); - if (!pf->avail_txqs) - return -ENOMEM; - - pf->avail_rxqs = bitmap_zalloc(pf->max_pf_rxqs, GFP_KERNEL); - if (!pf->avail_rxqs) { - bitmap_free(pf->avail_txqs); - pf->avail_txqs = NULL; - return -ENOMEM; - } - - pf->txtime_txqs = bitmap_zalloc(pf->max_pf_txqs, GFP_KERNEL); - if (!pf->txtime_txqs) { - bitmap_free(pf->avail_txqs); - pf->avail_txqs = NULL; - bitmap_free(pf->avail_rxqs); - pf->avail_rxqs = NULL; - return -ENOMEM; - } mutex_init(&pf->vfs.table_lock); hash_init(pf->vfs.table); @@ -4086,7 +4069,17 @@ static int ice_init_pf(struct ice_pf *pf) xa_init(&pf->dyn_ports); xa_init(&pf->sf_nums); + pf->avail_txqs = bitmap_zalloc(pf->max_pf_txqs, GFP_KERNEL); + pf->avail_rxqs = bitmap_zalloc(pf->max_pf_rxqs, GFP_KERNEL); + pf->txtime_txqs = bitmap_zalloc(pf->max_pf_txqs, GFP_KERNEL); + if (!pf->avail_txqs || !pf->avail_rxqs || !pf->txtime_txqs) + goto undo_init; + return 0; +undo_init: + /* deinit handles half-initialized pf just fine */ + ice_deinit_pf(pf); + return -ENOMEM; } /** From 6a7d04909a959d8a3d0b54cd69cae76313d7ed1c Mon Sep 17 00:00:00 2001 From: Przemek Kitszel Date: Fri, 12 Sep 2025 15:06:23 +0200 Subject: [PATCH 0469/3902] ice: move udp_tunnel_nic and misc IRQ setup into ice_init_pf() [ Upstream commit e3bf1cdde7471bab7fc20dd1a37c2cdb82d3f76b ] Move udp_tunnel_nic setup and ice_req_irq_msix_misc() call into ice_init_pf(), remove some redundancy in the former while moving. Move ice_free_irq_msix_misc() call into ice_deinit_pf(), to mimic the above in terms of needed cleanup. Guard it via emptiness check, to keep the allowance of half-initialized pf being cleaned up. Signed-off-by: Przemek Kitszel Reviewed-by: Aleksandr Loktionov Tested-by: Rinitha S (A Contingent worker at Intel) Signed-off-by: Tony Nguyen Stable-dep-of: 1390b8b3d2be ("ice: remove duplicate call to ice_deinit_hw() on error paths") Signed-off-by: Sasha Levin --- drivers/net/ethernet/intel/ice/ice_main.c | 58 +++++++++++------------ 1 file changed, 28 insertions(+), 30 deletions(-) diff --git a/drivers/net/ethernet/intel/ice/ice_main.c b/drivers/net/ethernet/intel/ice/ice_main.c index 80349aed1f9d6e..6773b918daffc9 100644 --- a/drivers/net/ethernet/intel/ice/ice_main.c +++ b/drivers/net/ethernet/intel/ice/ice_main.c @@ -3978,6 +3978,9 @@ static void ice_deinit_pf(struct ice_pf *pf) if (pf->ptp.clock) ptp_clock_unregister(pf->ptp.clock); + if (!xa_empty(&pf->irq_tracker.entries)) + ice_free_irq_msix_misc(pf); + xa_destroy(&pf->dyn_ports); xa_destroy(&pf->sf_nums); } @@ -4045,6 +4048,11 @@ void ice_start_service_task(struct ice_pf *pf) */ static int ice_init_pf(struct ice_pf *pf) { + struct udp_tunnel_nic_info *udp_tunnel_nic = &pf->hw.udp_tunnel_nic; + struct device *dev = ice_pf_to_dev(pf); + struct ice_hw *hw = &pf->hw; + int err = -ENOMEM; + mutex_init(&pf->sw_mutex); mutex_init(&pf->tc_mutex); mutex_init(&pf->adev_mutex); @@ -4075,11 +4083,30 @@ static int ice_init_pf(struct ice_pf *pf) if (!pf->avail_txqs || !pf->avail_rxqs || !pf->txtime_txqs) goto undo_init; + udp_tunnel_nic->set_port = ice_udp_tunnel_set_port; + udp_tunnel_nic->unset_port = ice_udp_tunnel_unset_port; + udp_tunnel_nic->shared = &hw->udp_tunnel_shared; + udp_tunnel_nic->tables[0].n_entries = hw->tnl.valid_count[TNL_VXLAN]; + udp_tunnel_nic->tables[0].tunnel_types = UDP_TUNNEL_TYPE_VXLAN; + udp_tunnel_nic->tables[1].n_entries = hw->tnl.valid_count[TNL_GENEVE]; + udp_tunnel_nic->tables[1].tunnel_types = UDP_TUNNEL_TYPE_GENEVE; + + /* In case of MSIX we are going to setup the misc vector right here + * to handle admin queue events etc. In case of legacy and MSI + * the misc functionality and queue processing is combined in + * the same vector and that gets setup at open. + */ + err = ice_req_irq_msix_misc(pf); + if (err) { + dev_err(dev, "setup of misc vector failed: %d\n", err); + goto undo_init; + } + return 0; undo_init: /* deinit handles half-initialized pf just fine */ ice_deinit_pf(pf); - return -ENOMEM; + return err; } /** @@ -4751,36 +4778,8 @@ int ice_init_dev(struct ice_pf *pf) goto unroll_irq_scheme_init; } - pf->hw.udp_tunnel_nic.set_port = ice_udp_tunnel_set_port; - pf->hw.udp_tunnel_nic.unset_port = ice_udp_tunnel_unset_port; - pf->hw.udp_tunnel_nic.shared = &pf->hw.udp_tunnel_shared; - if (pf->hw.tnl.valid_count[TNL_VXLAN]) { - pf->hw.udp_tunnel_nic.tables[0].n_entries = - pf->hw.tnl.valid_count[TNL_VXLAN]; - pf->hw.udp_tunnel_nic.tables[0].tunnel_types = - UDP_TUNNEL_TYPE_VXLAN; - } - if (pf->hw.tnl.valid_count[TNL_GENEVE]) { - pf->hw.udp_tunnel_nic.tables[1].n_entries = - pf->hw.tnl.valid_count[TNL_GENEVE]; - pf->hw.udp_tunnel_nic.tables[1].tunnel_types = - UDP_TUNNEL_TYPE_GENEVE; - } - /* In case of MSIX we are going to setup the misc vector right here - * to handle admin queue events etc. In case of legacy and MSI - * the misc functionality and queue processing is combined in - * the same vector and that gets setup at open. - */ - err = ice_req_irq_msix_misc(pf); - if (err) { - dev_err(dev, "setup of misc vector failed: %d\n", err); - goto unroll_pf_init; - } - return 0; -unroll_pf_init: - ice_deinit_pf(pf); unroll_irq_scheme_init: ice_service_task_stop(pf); ice_clear_interrupt_scheme(pf); @@ -4789,7 +4788,6 @@ int ice_init_dev(struct ice_pf *pf) void ice_deinit_dev(struct ice_pf *pf) { - ice_free_irq_msix_misc(pf); ice_deinit_pf(pf); ice_deinit_hw(&pf->hw); ice_service_task_stop(pf); From 8cea2a81db58e1288d3d4347109f1b04a989a125 Mon Sep 17 00:00:00 2001 From: Przemek Kitszel Date: Fri, 12 Sep 2025 15:06:24 +0200 Subject: [PATCH 0470/3902] ice: move ice_init_pf() out of ice_init_dev() [ Upstream commit ef825bdb4605742c4efc03f67d930e80c42f33cb ] Move ice_init_pf() out of ice_init_dev(). Do the same for deinit counterpart. Signed-off-by: Przemek Kitszel Reviewed-by: Aleksandr Loktionov Tested-by: Rinitha S (A Contingent worker at Intel) Signed-off-by: Tony Nguyen Stable-dep-of: 1390b8b3d2be ("ice: remove duplicate call to ice_deinit_hw() on error paths") Signed-off-by: Sasha Levin --- .../net/ethernet/intel/ice/devlink/devlink.c | 16 ++++++++-- drivers/net/ethernet/intel/ice/ice.h | 2 ++ drivers/net/ethernet/intel/ice/ice_main.c | 32 +++++++++---------- 3 files changed, 31 insertions(+), 19 deletions(-) diff --git a/drivers/net/ethernet/intel/ice/devlink/devlink.c b/drivers/net/ethernet/intel/ice/devlink/devlink.c index fb2de521731aee..c354a03c950cd9 100644 --- a/drivers/net/ethernet/intel/ice/devlink/devlink.c +++ b/drivers/net/ethernet/intel/ice/devlink/devlink.c @@ -459,6 +459,7 @@ static void ice_devlink_reinit_down(struct ice_pf *pf) rtnl_lock(); ice_vsi_decfg(ice_get_main_vsi(pf)); rtnl_unlock(); + ice_deinit_pf(pf); ice_deinit_dev(pf); } @@ -1231,11 +1232,12 @@ static void ice_set_min_max_msix(struct ice_pf *pf) static int ice_devlink_reinit_up(struct ice_pf *pf) { struct ice_vsi *vsi = ice_get_main_vsi(pf); + struct device *dev = ice_pf_to_dev(pf); int err; err = ice_init_hw(&pf->hw); if (err) { - dev_err(ice_pf_to_dev(pf), "ice_init_hw failed: %d\n", err); + dev_err(dev, "ice_init_hw failed: %d\n", err); return err; } @@ -1246,13 +1248,19 @@ static int ice_devlink_reinit_up(struct ice_pf *pf) if (err) goto unroll_hw_init; + err = ice_init_pf(pf); + if (err) { + dev_err(dev, "ice_init_pf failed: %d\n", err); + goto unroll_dev_init; + } + vsi->flags = ICE_VSI_FLAG_INIT; rtnl_lock(); err = ice_vsi_cfg(vsi); rtnl_unlock(); if (err) - goto err_vsi_cfg; + goto unroll_pf_init; /* No need to take devl_lock, it's already taken by devlink API */ err = ice_load(pf); @@ -1265,7 +1273,9 @@ static int ice_devlink_reinit_up(struct ice_pf *pf) rtnl_lock(); ice_vsi_decfg(vsi); rtnl_unlock(); -err_vsi_cfg: +unroll_pf_init: + ice_deinit_pf(pf); +unroll_dev_init: ice_deinit_dev(pf); unroll_hw_init: ice_deinit_hw(&pf->hw); diff --git a/drivers/net/ethernet/intel/ice/ice.h b/drivers/net/ethernet/intel/ice/ice.h index 0e58a58c23eb38..9a1abd45733729 100644 --- a/drivers/net/ethernet/intel/ice/ice.h +++ b/drivers/net/ethernet/intel/ice/ice.h @@ -1035,6 +1035,8 @@ void ice_unload(struct ice_pf *pf); void ice_adv_lnk_speed_maps_init(void); int ice_init_dev(struct ice_pf *pf); void ice_deinit_dev(struct ice_pf *pf); +int ice_init_pf(struct ice_pf *pf); +void ice_deinit_pf(struct ice_pf *pf); int ice_change_mtu(struct net_device *netdev, int new_mtu); void ice_tx_timeout(struct net_device *netdev, unsigned int txqueue); int ice_xdp(struct net_device *dev, struct netdev_bpf *xdp); diff --git a/drivers/net/ethernet/intel/ice/ice_main.c b/drivers/net/ethernet/intel/ice/ice_main.c index 6773b918daffc9..6b3d941f419b33 100644 --- a/drivers/net/ethernet/intel/ice/ice_main.c +++ b/drivers/net/ethernet/intel/ice/ice_main.c @@ -3949,7 +3949,7 @@ u16 ice_get_avail_rxq_count(struct ice_pf *pf) * ice_deinit_pf - Unrolls initialziations done by ice_init_pf * @pf: board private structure to initialize */ -static void ice_deinit_pf(struct ice_pf *pf) +void ice_deinit_pf(struct ice_pf *pf) { /* note that we unroll also on ice_init_pf() failure here */ @@ -4045,8 +4045,9 @@ void ice_start_service_task(struct ice_pf *pf) /** * ice_init_pf - Initialize general software structures (struct ice_pf) * @pf: board private structure to initialize + * Return: 0 on success, negative errno otherwise. */ -static int ice_init_pf(struct ice_pf *pf) +int ice_init_pf(struct ice_pf *pf) { struct udp_tunnel_nic_info *udp_tunnel_nic = &pf->hw.udp_tunnel_nic; struct device *dev = ice_pf_to_dev(pf); @@ -4772,23 +4773,12 @@ int ice_init_dev(struct ice_pf *pf) } ice_start_service_task(pf); - err = ice_init_pf(pf); - if (err) { - dev_err(dev, "ice_init_pf failed: %d\n", err); - goto unroll_irq_scheme_init; - } return 0; - -unroll_irq_scheme_init: - ice_service_task_stop(pf); - ice_clear_interrupt_scheme(pf); - return err; } void ice_deinit_dev(struct ice_pf *pf) { - ice_deinit_pf(pf); ice_deinit_hw(&pf->hw); ice_service_task_stop(pf); @@ -5030,21 +5020,28 @@ static void ice_deinit_devlink(struct ice_pf *pf) static int ice_init(struct ice_pf *pf) { + struct device *dev = ice_pf_to_dev(pf); int err; err = ice_init_dev(pf); if (err) return err; + err = ice_init_pf(pf); + if (err) { + dev_err(dev, "ice_init_pf failed: %d\n", err); + goto unroll_dev_init; + } + if (pf->hw.mac_type == ICE_MAC_E830) { err = pci_enable_ptm(pf->pdev, NULL); if (err) - dev_dbg(ice_pf_to_dev(pf), "PCIe PTM not supported by PCIe bus/controller\n"); + dev_dbg(dev, "PCIe PTM not supported by PCIe bus/controller\n"); } err = ice_alloc_vsis(pf); if (err) - goto err_alloc_vsis; + goto unroll_pf_init; err = ice_init_pf_sw(pf); if (err) @@ -5081,7 +5078,9 @@ static int ice_init(struct ice_pf *pf) ice_deinit_pf_sw(pf); err_init_pf_sw: ice_dealloc_vsis(pf); -err_alloc_vsis: +unroll_pf_init: + ice_deinit_pf(pf); +unroll_dev_init: ice_deinit_dev(pf); return err; } @@ -5093,6 +5092,7 @@ static void ice_deinit(struct ice_pf *pf) ice_deinit_pf_sw(pf); ice_dealloc_vsis(pf); + ice_deinit_pf(pf); ice_deinit_dev(pf); } From 3cef143aba0462d284ce18b004581cd26e6067ae Mon Sep 17 00:00:00 2001 From: Przemek Kitszel Date: Fri, 12 Sep 2025 15:06:25 +0200 Subject: [PATCH 0471/3902] ice: extract ice_init_dev() from ice_init() [ Upstream commit c2fb9398f73d41cb2b5da74ff505578525ee3fd8 ] Extract ice_init_dev() from ice_init(), to allow service task and IRQ scheme teardown to be put after clearing SW constructs in the subsequent commit. Signed-off-by: Przemek Kitszel Reviewed-by: Aleksandr Loktionov Tested-by: Rinitha S (A Contingent worker at Intel) Signed-off-by: Tony Nguyen Stable-dep-of: 1390b8b3d2be ("ice: remove duplicate call to ice_deinit_hw() on error paths") Signed-off-by: Sasha Levin --- drivers/net/ethernet/intel/ice/ice_main.c | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/drivers/net/ethernet/intel/ice/ice_main.c b/drivers/net/ethernet/intel/ice/ice_main.c index 6b3d941f419b33..a12dcc733e041b 100644 --- a/drivers/net/ethernet/intel/ice/ice_main.c +++ b/drivers/net/ethernet/intel/ice/ice_main.c @@ -5023,14 +5023,10 @@ static int ice_init(struct ice_pf *pf) struct device *dev = ice_pf_to_dev(pf); int err; - err = ice_init_dev(pf); - if (err) - return err; - err = ice_init_pf(pf); if (err) { dev_err(dev, "ice_init_pf failed: %d\n", err); - goto unroll_dev_init; + return err; } if (pf->hw.mac_type == ICE_MAC_E830) { @@ -5080,8 +5076,6 @@ static int ice_init(struct ice_pf *pf) ice_dealloc_vsis(pf); unroll_pf_init: ice_deinit_pf(pf); -unroll_dev_init: - ice_deinit_dev(pf); return err; } @@ -5093,7 +5087,6 @@ static void ice_deinit(struct ice_pf *pf) ice_deinit_pf_sw(pf); ice_dealloc_vsis(pf); ice_deinit_pf(pf); - ice_deinit_dev(pf); } /** @@ -5323,10 +5316,14 @@ ice_probe(struct pci_dev *pdev, const struct pci_device_id __always_unused *ent) } pf->adapter = adapter; - err = ice_init(pf); + err = ice_init_dev(pf); if (err) goto unroll_adapter; + err = ice_init(pf); + if (err) + goto unroll_dev_init; + devl_lock(priv_to_devlink(pf)); err = ice_load(pf); if (err) @@ -5344,6 +5341,8 @@ ice_probe(struct pci_dev *pdev, const struct pci_device_id __always_unused *ent) unroll_init: devl_unlock(priv_to_devlink(pf)); ice_deinit(pf); +unroll_dev_init: + ice_deinit_dev(pf); unroll_adapter: ice_adapter_put(pdev); unroll_hw_init: @@ -5457,6 +5456,7 @@ static void ice_remove(struct pci_dev *pdev) devl_unlock(priv_to_devlink(pf)); ice_deinit(pf); + ice_deinit_dev(pf); ice_vsi_release_all(pf); ice_setup_mc_magic_wake(pf); From ac4d0d4c469c84b81068608afb6a53d61c2a12d2 Mon Sep 17 00:00:00 2001 From: Przemek Kitszel Date: Fri, 12 Sep 2025 15:06:26 +0200 Subject: [PATCH 0472/3902] ice: move ice_deinit_dev() to the end of deinit paths [ Upstream commit 8a37f9e2ff40f4a4fa8def22febefe4daf58e573 ] ice_deinit_dev() takes care of turning off adminq processing, which is much needed during driver teardown (remove, reset, error path). Move it to the very end where applicable. For example, ice_deinit_hw() called after adminq deinit slows rmmod on my two-card setup by about 60 seconds. ice_init_dev() and ice_deinit_dev() scopes were reduced by previous commits of the series, with a final touch of extracting ice_init_dev_hw() out now (there is no deinit counterpart). Note that removed ice_service_task_stop() call from ice_remove() is placed in the ice_deinit_dev() (and stopping twice makes no sense). Signed-off-by: Przemek Kitszel Reviewed-by: Aleksandr Loktionov Tested-by: Rinitha S (A Contingent worker at Intel) Signed-off-by: Tony Nguyen Stable-dep-of: 1390b8b3d2be ("ice: remove duplicate call to ice_deinit_hw() on error paths") Signed-off-by: Sasha Levin --- .../net/ethernet/intel/ice/devlink/devlink.c | 5 +++- drivers/net/ethernet/intel/ice/ice.h | 1 + drivers/net/ethernet/intel/ice/ice_common.c | 3 +++ drivers/net/ethernet/intel/ice/ice_main.c | 23 ++++++++++++------- 4 files changed, 23 insertions(+), 9 deletions(-) diff --git a/drivers/net/ethernet/intel/ice/devlink/devlink.c b/drivers/net/ethernet/intel/ice/devlink/devlink.c index c354a03c950cd9..938914abbe0667 100644 --- a/drivers/net/ethernet/intel/ice/devlink/devlink.c +++ b/drivers/net/ethernet/intel/ice/devlink/devlink.c @@ -1233,6 +1233,7 @@ static int ice_devlink_reinit_up(struct ice_pf *pf) { struct ice_vsi *vsi = ice_get_main_vsi(pf); struct device *dev = ice_pf_to_dev(pf); + bool need_dev_deinit = false; int err; err = ice_init_hw(&pf->hw); @@ -1276,9 +1277,11 @@ static int ice_devlink_reinit_up(struct ice_pf *pf) unroll_pf_init: ice_deinit_pf(pf); unroll_dev_init: - ice_deinit_dev(pf); + need_dev_deinit = true; unroll_hw_init: ice_deinit_hw(&pf->hw); + if (need_dev_deinit) + ice_deinit_dev(pf); return err; } diff --git a/drivers/net/ethernet/intel/ice/ice.h b/drivers/net/ethernet/intel/ice/ice.h index 9a1abd45733729..9ee596773f34e7 100644 --- a/drivers/net/ethernet/intel/ice/ice.h +++ b/drivers/net/ethernet/intel/ice/ice.h @@ -1033,6 +1033,7 @@ void ice_start_service_task(struct ice_pf *pf); int ice_load(struct ice_pf *pf); void ice_unload(struct ice_pf *pf); void ice_adv_lnk_speed_maps_init(void); +void ice_init_dev_hw(struct ice_pf *pf); int ice_init_dev(struct ice_pf *pf); void ice_deinit_dev(struct ice_pf *pf); int ice_init_pf(struct ice_pf *pf); diff --git a/drivers/net/ethernet/intel/ice/ice_common.c b/drivers/net/ethernet/intel/ice/ice_common.c index 2532b6f82e971e..6edeb06b4dce25 100644 --- a/drivers/net/ethernet/intel/ice/ice_common.c +++ b/drivers/net/ethernet/intel/ice/ice_common.c @@ -1161,6 +1161,9 @@ int ice_init_hw(struct ice_hw *hw) status = ice_init_hw_tbls(hw); if (status) goto err_unroll_fltr_mgmt_struct; + + ice_init_dev_hw(hw->back); + mutex_init(&hw->tnl_lock); ice_init_chk_recipe_reuse_support(hw); diff --git a/drivers/net/ethernet/intel/ice/ice_main.c b/drivers/net/ethernet/intel/ice/ice_main.c index a12dcc733e041b..f1ebdb7dbdc73a 100644 --- a/drivers/net/ethernet/intel/ice/ice_main.c +++ b/drivers/net/ethernet/intel/ice/ice_main.c @@ -4742,9 +4742,8 @@ static void ice_decfg_netdev(struct ice_vsi *vsi) vsi->netdev = NULL; } -int ice_init_dev(struct ice_pf *pf) +void ice_init_dev_hw(struct ice_pf *pf) { - struct device *dev = ice_pf_to_dev(pf); struct ice_hw *hw = &pf->hw; int err; @@ -4764,6 +4763,12 @@ int ice_init_dev(struct ice_pf *pf) */ ice_set_safe_mode_caps(hw); } +} + +int ice_init_dev(struct ice_pf *pf) +{ + struct device *dev = ice_pf_to_dev(pf); + int err; ice_set_pf_caps(pf); err = ice_init_interrupt_scheme(pf); @@ -5220,6 +5225,7 @@ static int ice_probe(struct pci_dev *pdev, const struct pci_device_id __always_unused *ent) { struct device *dev = &pdev->dev; + bool need_dev_deinit = false; struct ice_adapter *adapter; struct ice_pf *pf; struct ice_hw *hw; @@ -5342,11 +5348,13 @@ ice_probe(struct pci_dev *pdev, const struct pci_device_id __always_unused *ent) devl_unlock(priv_to_devlink(pf)); ice_deinit(pf); unroll_dev_init: - ice_deinit_dev(pf); + need_dev_deinit = true; unroll_adapter: ice_adapter_put(pdev); unroll_hw_init: ice_deinit_hw(hw); + if (need_dev_deinit) + ice_deinit_dev(pf); return err; } @@ -5441,10 +5449,6 @@ static void ice_remove(struct pci_dev *pdev) ice_hwmon_exit(pf); - ice_service_task_stop(pf); - ice_aq_cancel_waiting_tasks(pf); - set_bit(ICE_DOWN, pf->state); - if (!ice_is_safe_mode(pf)) ice_remove_arfs(pf); @@ -5456,13 +5460,16 @@ static void ice_remove(struct pci_dev *pdev) devl_unlock(priv_to_devlink(pf)); ice_deinit(pf); - ice_deinit_dev(pf); ice_vsi_release_all(pf); ice_setup_mc_magic_wake(pf); ice_set_wake(pf); ice_adapter_put(pdev); + + ice_deinit_dev(pf); + ice_aq_cancel_waiting_tasks(pf); + set_bit(ICE_DOWN, pf->state); } /** From c91eee346089e4d287d5a111719e57cac605275e Mon Sep 17 00:00:00 2001 From: Przemek Kitszel Date: Fri, 12 Sep 2025 15:06:27 +0200 Subject: [PATCH 0473/3902] ice: remove duplicate call to ice_deinit_hw() on error paths [ Upstream commit 1390b8b3d2bef9bfbb852fc735430798bfca36e7 ] Current unwinding code on error paths of ice_devlink_reinit_up() and ice_probe() have manual call to ice_deinit_hw() (which is good, as there is also manual call to ice_hw_init() there), which is then duplicated (and was prior current series) in ice_deinit_dev(). Fix the above by removing ice_deinit_hw() from ice_deinit_dev(). Add a (now missing) call in ice_remove(). Reported-by: Jacob Keller Link: https://lore.kernel.org/intel-wired-lan/20250717-jk-ddp-safe-mode-issue-v1-1-e113b2baed79@intel.com/ Fixes: 4d3f59bfa2cd ("ice: split ice_init_hw() out from ice_init_dev()") Signed-off-by: Przemek Kitszel Reviewed-by: Aleksandr Loktionov Tested-by: Rinitha S (A Contingent worker at Intel) Signed-off-by: Tony Nguyen Signed-off-by: Sasha Levin --- drivers/net/ethernet/intel/ice/ice_main.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/net/ethernet/intel/ice/ice_main.c b/drivers/net/ethernet/intel/ice/ice_main.c index f1ebdb7dbdc73a..b0f8a96c13b471 100644 --- a/drivers/net/ethernet/intel/ice/ice_main.c +++ b/drivers/net/ethernet/intel/ice/ice_main.c @@ -4784,7 +4784,6 @@ int ice_init_dev(struct ice_pf *pf) void ice_deinit_dev(struct ice_pf *pf) { - ice_deinit_hw(&pf->hw); ice_service_task_stop(pf); /* Service task is already stopped, so call reset directly. */ @@ -5466,6 +5465,7 @@ static void ice_remove(struct pci_dev *pdev) ice_set_wake(pf); ice_adapter_put(pdev); + ice_deinit_hw(&pf->hw); ice_deinit_dev(pf); ice_aq_cancel_waiting_tasks(pf); From 2f902d08e1f313190c05566f72a2baace73b501a Mon Sep 17 00:00:00 2001 From: Thomas Richard Date: Mon, 20 Oct 2025 17:36:25 +0200 Subject: [PATCH 0474/3902] leds: upboard: Fix module alias [ Upstream commit c06a017439110debd335b6864bc2d69835624235 ] The module alias does not match the cell name defined in the MFD driver, preventing automatic loading when the driver is built as a module. So fix the module alias to ensure proper module auto-loading. Fixes: 0ef2929a0181 ("leds: Add AAEON UP board LED driver") Signed-off-by: Thomas Richard Reviewed-by: Krzysztof Kozlowski Link: https://patch.msgid.link/20251020-leds-upboard-fix-module-alias-v2-1-84ac5c3a1a81@bootlin.com Signed-off-by: Lee Jones Signed-off-by: Sasha Levin --- drivers/leds/leds-upboard.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/leds/leds-upboard.c b/drivers/leds/leds-upboard.c index b350eb294280fd..12989b2f195309 100644 --- a/drivers/leds/leds-upboard.c +++ b/drivers/leds/leds-upboard.c @@ -123,4 +123,4 @@ MODULE_AUTHOR("Gary Wang "); MODULE_AUTHOR("Thomas Richard "); MODULE_DESCRIPTION("UP Board LED driver"); MODULE_LICENSE("GPL"); -MODULE_ALIAS("platform:upboard-led"); +MODULE_ALIAS("platform:upboard-leds"); From b8b3bdfb10765e71712f27a6ed093a22b252f645 Mon Sep 17 00:00:00 2001 From: Bhanu Seshu Kumar Valluri Date: Tue, 14 Oct 2025 08:11:09 +0530 Subject: [PATCH 0475/3902] PCI: endpoint: pci-epf-test: Fix sleeping function being called from atomic context [ Upstream commit 25423cda145f9ed6ee4a72d9f2603ac2a4685e74 ] When Root Complex (RC) triggers a Doorbell interrupt to Endpoint (EP), it triggers the below warning in the EP: BUG: sleeping function called from invalid context at kernel/locking/mutex.c:271 Call trace: __might_resched+0x130/0x158 __might_sleep+0x70/0x88 mutex_lock+0x2c/0x80 pci_epc_get_msi+0x78/0xd8 pci_epf_test_raise_irq.isra.0+0x74/0x138 pci_epf_test_doorbell_handler+0x34/0x50 The BUG arises because the EP's pci_epf_test_doorbell_handler() which is running in the hard IRQ context is making an indirect call to pci_epc_get_msi(), which uses mutex inside. To fix the issue, convert the hard IRQ handler to a threaded IRQ handler to allow it to call functions that can sleep during bottom half execution. Also, register the threaded IRQ handler with IRQF_ONESHOT to keep the interrupt line disabled until the threaded IRQ handler completes execution. Fixes: eff0c286aa91 ("PCI: endpoint: pci-epf-test: Add doorbell test support") Signed-off-by: Bhanu Seshu Kumar Valluri [mani: reworded description a bit] Signed-off-by: Manivannan Sadhasivam Reviewed-by: Niklas Cassel Reviewed-by: Frank Li Link: https://patch.msgid.link/20251014024109.42287-1-bhanuseshukumar@gmail.com Signed-off-by: Sasha Levin --- drivers/pci/endpoint/functions/pci-epf-test.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/drivers/pci/endpoint/functions/pci-epf-test.c b/drivers/pci/endpoint/functions/pci-epf-test.c index 31617772ad5165..b05e8db575c353 100644 --- a/drivers/pci/endpoint/functions/pci-epf-test.c +++ b/drivers/pci/endpoint/functions/pci-epf-test.c @@ -730,8 +730,9 @@ static void pci_epf_test_enable_doorbell(struct pci_epf_test *epf_test, if (bar < BAR_0) goto err_doorbell_cleanup; - ret = request_irq(epf->db_msg[0].virq, pci_epf_test_doorbell_handler, 0, - "pci-ep-test-doorbell", epf_test); + ret = request_threaded_irq(epf->db_msg[0].virq, NULL, + pci_epf_test_doorbell_handler, IRQF_ONESHOT, + "pci-ep-test-doorbell", epf_test); if (ret) { dev_err(&epf->dev, "Failed to request doorbell IRQ: %d\n", From 01caee81ca7b76aa4d813e3ba3b87d1e92c50b38 Mon Sep 17 00:00:00 2001 From: Randolph Sapp Date: Fri, 19 Sep 2025 14:33:42 -0500 Subject: [PATCH 0476/3902] arm64: dts: ti: k3-am62p: Fix memory ranges for GPU [ Upstream commit 76546090b1726118cd6fb3db7159fc2a3fdda8a0 ] Update the memory region listed in the k3-am62p.dtsi for the BXS-4-64 GPU to match the Main Memory Map described in the TRM [1]. [1] https://www.ti.com/lit/ug/spruj83b/spruj83b.pdf Fixes: 29075cc09f43 ("arm64: dts: ti: Introduce AM62P5 family of SoCs") Signed-off-by: Randolph Sapp Reviewed-by: Michael Walle Link: https://patch.msgid.link/20250919193341.707660-2-rs@ti.com Signed-off-by: Vignesh Raghavendra Signed-off-by: Sasha Levin --- arch/arm64/boot/dts/ti/k3-am62p.dtsi | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/arch/arm64/boot/dts/ti/k3-am62p.dtsi b/arch/arm64/boot/dts/ti/k3-am62p.dtsi index 75a15c368c11b0..dd24c40c7965d7 100644 --- a/arch/arm64/boot/dts/ti/k3-am62p.dtsi +++ b/arch/arm64/boot/dts/ti/k3-am62p.dtsi @@ -59,7 +59,7 @@ <0x00 0x01000000 0x00 0x01000000 0x00 0x01b28400>, /* First peripheral window */ <0x00 0x08000000 0x00 0x08000000 0x00 0x00200000>, /* Main CPSW */ <0x00 0x0e000000 0x00 0x0e000000 0x00 0x01d20000>, /* Second peripheral window */ - <0x00 0x0fd00000 0x00 0x0fd00000 0x00 0x00020000>, /* GPU */ + <0x00 0x0fd80000 0x00 0x0fd80000 0x00 0x00080000>, /* GPU */ <0x00 0x20000000 0x00 0x20000000 0x00 0x0a008000>, /* Third peripheral window */ <0x00 0x30040000 0x00 0x30040000 0x00 0x00080000>, /* PRUSS-M */ <0x00 0x30101000 0x00 0x30101000 0x00 0x00010100>, /* CSI window */ From a705920161fb36dd6b3e1a8fd0586afdb1bf2283 Mon Sep 17 00:00:00 2001 From: Peng Fan Date: Fri, 17 Oct 2025 09:56:24 +0800 Subject: [PATCH 0477/3902] firmware: imx: scu-irq: fix OF node leak in [ Upstream commit ee67247843a2b62d1473cfa4df300e69b5190ccf ] imx_scu_enable_general_irq_channel() calls of_parse_phandle_with_args(), but does not release the OF node reference. Add a of_node_put() call to release the reference. Fixes: 851826c7566e ("firmware: imx: enable imx scu general irq function") Reviewed-by: Frank Li Signed-off-by: Peng Fan Signed-off-by: Shawn Guo Signed-off-by: Sasha Levin --- drivers/firmware/imx/imx-scu-irq.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/drivers/firmware/imx/imx-scu-irq.c b/drivers/firmware/imx/imx-scu-irq.c index 6125cccc9ba79c..f2b902e95b738f 100644 --- a/drivers/firmware/imx/imx-scu-irq.c +++ b/drivers/firmware/imx/imx-scu-irq.c @@ -226,8 +226,10 @@ int imx_scu_enable_general_irq_channel(struct device *dev) INIT_WORK(&imx_sc_irq_work, imx_scu_irq_work_handler); if (!of_parse_phandle_with_args(dev->of_node, "mboxes", - "#mbox-cells", 0, &spec)) + "#mbox-cells", 0, &spec)) { i = of_alias_get_id(spec.np, "mu"); + of_node_put(spec.np); + } /* use mu1 as general mu irq channel if failed */ if (i < 0) From ca3630751d7f83f29c2a5429836b8e5bc7c1d5e3 Mon Sep 17 00:00:00 2001 From: Krishna Kurapati Date: Sun, 19 Oct 2025 17:26:30 +0530 Subject: [PATCH 0478/3902] arm64: dts: qcom: x1e80100: Fix compile warnings for USB HS controller [ Upstream commit 0dab10c38282e6ef87ef88efb99d4106cce7ed33 ] With W=1, the following error comes up: Warning (graph_child_address): /soc@0/usb@a2f8800/usb@a200000/ports: graph node has single child node 'port@0', #address-cells/#size-cells are not necessary This could be since the controller is only HS capable and only one port node is added. Fixes: 4af46b7bd66f ("arm64: dts: qcom: x1e80100: Add USB nodes") Signed-off-by: Krishna Kurapati Reviewed-by: Konrad Dybcio Reviewed-by: Dmitry Baryshkov Link: https://lore.kernel.org/r/20251019115630.2222720-1-krishna.kurapati@oss.qualcomm.com Signed-off-by: Bjorn Andersson Signed-off-by: Sasha Levin --- arch/arm64/boot/dts/qcom/x1e80100.dtsi | 11 ++--------- 1 file changed, 2 insertions(+), 9 deletions(-) diff --git a/arch/arm64/boot/dts/qcom/x1e80100.dtsi b/arch/arm64/boot/dts/qcom/x1e80100.dtsi index 51576d9c935dec..6beef835c33adf 100644 --- a/arch/arm64/boot/dts/qcom/x1e80100.dtsi +++ b/arch/arm64/boot/dts/qcom/x1e80100.dtsi @@ -4939,15 +4939,8 @@ dma-coherent; - ports { - #address-cells = <1>; - #size-cells = <0>; - - port@0 { - reg = <0>; - - usb_2_dwc3_hs: endpoint { - }; + port { + usb_2_dwc3_hs: endpoint { }; }; }; From 7ecbc8f8b2da66116976816fc8c9f43366422b29 Mon Sep 17 00:00:00 2001 From: Krishna Kurapati Date: Fri, 24 Oct 2025 16:20:18 +0530 Subject: [PATCH 0479/3902] arm64: dts: qcom: x1e80100: Add missing quirk for HS only USB controller [ Upstream commit 6b3e8a5d6c88609d9ce93789524f818cca0aa485 ] The PIPE clock is provided by the USB3 PHY, which is predictably not connected to the HS-only controller. Add "qcom,select-utmi-as-pipe-clk" quirk to HS only USB controller to disable pipe clock requirement. Fixes: 4af46b7bd66f ("arm64: dts: qcom: x1e80100: Add USB nodes") Signed-off-by: Krishna Kurapati Reviewed-by: Abel Vesa Link: https://lore.kernel.org/r/20251024105019.2220832-2-krishna.kurapati@oss.qualcomm.com Signed-off-by: Bjorn Andersson Signed-off-by: Sasha Levin --- arch/arm64/boot/dts/qcom/x1e80100.dtsi | 1 + 1 file changed, 1 insertion(+) diff --git a/arch/arm64/boot/dts/qcom/x1e80100.dtsi b/arch/arm64/boot/dts/qcom/x1e80100.dtsi index 6beef835c33adf..662ad694cd914f 100644 --- a/arch/arm64/boot/dts/qcom/x1e80100.dtsi +++ b/arch/arm64/boot/dts/qcom/x1e80100.dtsi @@ -4922,6 +4922,7 @@ interconnect-names = "usb-ddr", "apps-usb"; + qcom,select-utmi-as-pipe-clk; wakeup-source; status = "disabled"; From b5cc20b665cfe1650633e593696138e262acb0c8 Mon Sep 17 00:00:00 2001 From: Krishna Kurapati Date: Fri, 24 Oct 2025 16:20:19 +0530 Subject: [PATCH 0480/3902] arm64: dts: qcom: lemans: Add missing quirk for HS only USB controller [ Upstream commit 0903296efd0b4e17c8d556ce8c33347147301870 ] The PIPE clock is provided by the USB3 PHY, which is predictably not connected to the HS-only controller. Add "qcom,select-utmi-as-pipe-clk" quirk to HS only USB controller to disable pipe clock requirement. Fixes: de1001525c1a ("arm64: dts: qcom: sa8775p: add USB nodes") Signed-off-by: Krishna Kurapati Reviewed-by: Abel Vesa Link: https://lore.kernel.org/r/20251024105019.2220832-3-krishna.kurapati@oss.qualcomm.com Signed-off-by: Bjorn Andersson Signed-off-by: Sasha Levin --- arch/arm64/boot/dts/qcom/lemans.dtsi | 1 + 1 file changed, 1 insertion(+) diff --git a/arch/arm64/boot/dts/qcom/lemans.dtsi b/arch/arm64/boot/dts/qcom/lemans.dtsi index cf685cb186edca..c2d2200d845b38 100644 --- a/arch/arm64/boot/dts/qcom/lemans.dtsi +++ b/arch/arm64/boot/dts/qcom/lemans.dtsi @@ -4106,6 +4106,7 @@ <&gem_noc MASTER_APPSS_PROC 0 &config_noc SLAVE_USB2 0>; interconnect-names = "usb-ddr", "apps-usb"; + qcom,select-utmi-as-pipe-clk; wakeup-source; iommus = <&apps_smmu 0x020 0x0>; From 6044e4081959519faaf3b91495bee97ff2c57795 Mon Sep 17 00:00:00 2001 From: Willy Tarreau Date: Mon, 27 Oct 2025 11:24:57 +0100 Subject: [PATCH 0481/3902] tools/nolibc: x86: fix section mismatch caused by asm "mem*" functions MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [ Upstream commit 2602949b22330f1275138e2b5aea5d49126b9757 ] I recently got occasional build failures at -Os or -Oz that would always involve waitpid(), where the assembler would complain about this: init.s: Error: .size expression for waitpid.constprop.0 does not evaluate to a constant And without -fno-asynchronous-unwind-tables it could also spit such errors: init.s:836: Error: CFI instruction used without previous .cfi_startproc init.s:838: Error: .cfi_endproc without corresponding .cfi_startproc init.s: Error: open CFI at the end of file; missing .cfi_endproc directive A trimmed down reproducer is as simple as this: int main(int argc, char **argv) { int ret, status; if (argc == 0) ret = waitpid(-1, &status, 0); else ret = waitpid(-1, &status, 0); return status; } It produces the following asm code on x86_64: .text .section .text.nolibc_memmove_memcpy .weak memmove .weak memcpy memmove: memcpy: movq %rdx, %rcx (...) retq .section .text.nolibc_memset .weak memset memset: xchgl %eax, %esi movq %rdx, %rcx pushq %rdi rep stosb popq %rax retq .type waitpid.constprop.0.isra.0, @function waitpid.constprop.0.isra.0: subq $8, %rsp (...) jmp *.L5(,%rax,8) .section .rodata .align 8 .align 4 .L5: .quad .L10 (...) .quad .L4 .text .L10: (...) .cfi_def_cfa_offset 8 ret .cfi_endproc .LFE273: .size waitpid.constprop.0.isra.0, .-waitpid.constprop.0.isra.0 It's a bit dense, but here's the explanation: the compiler has emitted a ".text" statement because it knows it's working in the .text section. Then, our hand-written asm code for the mem* functions forced the section to .text.something without the compiler knowing about it, so it thinks the code is still being emitted for .text. As such, without any .section statement, the waitpid.constprop.0.isra.0 label is in fact placed in the previously created section, here .text.nolibc_memset. The waitpid() function involves a switch/case statement that can be turned to a jump table, which is what the compiler does with the .rodata section, and after that it restores .text, which is no longer the previous .text.nolibc_memset section. Then the CFI statements cross a section, so does the .size calculation, which explains the error. While a first approach consisting in placing an explicit ".text" at the end of these functions was verified to work, it's still unreliable as it depends on what the compiler remembers having emitted previously. A better approach is to replace the ".section" with ".pushsection", and place a ".popsection" at the end, so that these code blocks are agnostic to where they're placed relative to other blocks. Fixes: 553845eebd60 ("tools/nolibc: x86-64: Use `rep movsb` for `memcpy()` and `memmove()`") Fixes: 12108aa8c1a1 ("tools/nolibc: x86-64: Use `rep stosb` for `memset()`") Signed-off-by: Willy Tarreau Signed-off-by: Thomas Weißschuh Signed-off-by: Sasha Levin --- tools/include/nolibc/arch-x86.h | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/tools/include/nolibc/arch-x86.h b/tools/include/nolibc/arch-x86.h index d3efc0c3b8adcf..c8b0c3e624a518 100644 --- a/tools/include/nolibc/arch-x86.h +++ b/tools/include/nolibc/arch-x86.h @@ -351,7 +351,7 @@ void *memcpy(void *dst, const void *src, size_t len); void *memset(void *dst, int c, size_t len); __asm__ ( -".section .text.nolibc_memmove_memcpy\n" +".pushsection .text.nolibc_memmove_memcpy\n" ".weak memmove\n" ".weak memcpy\n" "memmove:\n" @@ -371,8 +371,9 @@ __asm__ ( "rep movsb\n\t" "cld\n\t" "retq\n" +".popsection\n" -".section .text.nolibc_memset\n" +".pushsection .text.nolibc_memset\n" ".weak memset\n" "memset:\n" "xchgl %eax, %esi\n\t" @@ -381,6 +382,7 @@ __asm__ ( "rep stosb\n\t" "popq %rax\n\t" "retq\n" +".popsection\n" ); #endif /* !defined(__x86_64__) */ From ff24f6314f1cfc82a9304681ba915b90ab14902e Mon Sep 17 00:00:00 2001 From: Dzmitry Sankouski Date: Fri, 26 Sep 2025 20:13:26 +0300 Subject: [PATCH 0482/3902] arm64: dts: qcom: sdm845-starqltechn: remove (address|size)-cells [ Upstream commit 4133486382364f60ea7e4f2c9070555689d9606e ] Drop the unused address/size-cells properties to silence the DT checker warning: pmic@66 (maxim,max77705): '#address-cells', '#size-cells' do not match any of the regexes: '^pinctrl-[0-9]+$' Fixes: 7a88a931d095 ("arm64: dts: qcom: sdm845-starqltechn: add max77705 PMIC") Reviewed-by: Konrad Dybcio Signed-off-by: Dzmitry Sankouski Reviewed-by: Dmitry Baryshkov Link: https://lore.kernel.org/r/20250926-starqltechn-correct_max77705_nodes-v5-1-c6ab35165534@gmail.com Signed-off-by: Bjorn Andersson Signed-off-by: Sasha Levin --- arch/arm64/boot/dts/qcom/sdm845-samsung-starqltechn.dts | 2 -- 1 file changed, 2 deletions(-) diff --git a/arch/arm64/boot/dts/qcom/sdm845-samsung-starqltechn.dts b/arch/arm64/boot/dts/qcom/sdm845-samsung-starqltechn.dts index 75a53f0bbebd07..45c7aa0f602d85 100644 --- a/arch/arm64/boot/dts/qcom/sdm845-samsung-starqltechn.dts +++ b/arch/arm64/boot/dts/qcom/sdm845-samsung-starqltechn.dts @@ -606,8 +606,6 @@ interrupts = <11 IRQ_TYPE_LEVEL_LOW>; pinctrl-0 = <&pmic_int_default>; pinctrl-names = "default"; - #address-cells = <1>; - #size-cells = <0>; leds { compatible = "maxim,max77705-rgb"; From f4e3cbc96b8632888c970478b59c8a6b6f056ff8 Mon Sep 17 00:00:00 2001 From: Dzmitry Sankouski Date: Fri, 26 Sep 2025 20:13:27 +0300 Subject: [PATCH 0483/3902] arm64: dts: qcom: sdm845-starqltechn: fix max77705 interrupts [ Upstream commit 4372b15d89e253e40816f0bde100890cddd25a81 ] Since max77705 has a register, which indicates interrupt source, it acts as an interrupt controller. Direct MAX77705's subdevices to use the IC's internal interrupt controller, instead of listening to every interrupt fired by the chip towards the host device. Fixes: 7a88a931d095 ("arm64: dts: qcom: sdm845-starqltechn: add max77705 PMIC") Reviewed-by: Dmitry Baryshkov Signed-off-by: Dzmitry Sankouski Reviewed-by: Konrad Dybcio Link: https://lore.kernel.org/r/20250926-starqltechn-correct_max77705_nodes-v5-2-c6ab35165534@gmail.com Signed-off-by: Bjorn Andersson Signed-off-by: Sasha Levin --- .../boot/dts/qcom/sdm845-samsung-starqltechn.dts | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/arch/arm64/boot/dts/qcom/sdm845-samsung-starqltechn.dts b/arch/arm64/boot/dts/qcom/sdm845-samsung-starqltechn.dts index 45c7aa0f602d85..215e1491f3e9ad 100644 --- a/arch/arm64/boot/dts/qcom/sdm845-samsung-starqltechn.dts +++ b/arch/arm64/boot/dts/qcom/sdm845-samsung-starqltechn.dts @@ -599,11 +599,13 @@ &i2c14 { status = "okay"; - pmic@66 { + max77705: pmic@66 { compatible = "maxim,max77705"; reg = <0x66>; + #interrupt-cells = <1>; interrupt-parent = <&pm8998_gpios>; interrupts = <11 IRQ_TYPE_LEVEL_LOW>; + interrupt-controller; pinctrl-0 = <&pmic_int_default>; pinctrl-names = "default"; @@ -644,8 +646,8 @@ reg = <0x69>; compatible = "maxim,max77705-charger"; monitored-battery = <&battery>; - interrupt-parent = <&pm8998_gpios>; - interrupts = <11 IRQ_TYPE_LEVEL_LOW>; + interrupt-parent = <&max77705>; + interrupts = <0>; }; fuel-gauge@36 { @@ -653,8 +655,8 @@ compatible = "maxim,max77705-battery"; power-supplies = <&max77705_charger>; maxim,rsns-microohm = <5000>; - interrupt-parent = <&pm8998_gpios>; - interrupts = <11 IRQ_TYPE_LEVEL_LOW>; + interrupt-parent = <&max77705>; + interrupts = <2>; }; }; From 63f8119b2ca14ed1e0b4bab598bef0811866f705 Mon Sep 17 00:00:00 2001 From: Gergo Koteles Date: Sat, 27 Sep 2025 13:20:28 +0200 Subject: [PATCH 0484/3902] arm64: dts: qcom: sdm845-oneplus: Correct gpio used for slider [ Upstream commit d7ec7d34237498fab7a6afed8da4b7139b0e387c ] The previous GPIO numbers were wrong. Update them to the correct ones and fix the label. Fixes: 288ef8a42612 ("arm64: dts: sdm845: add oneplus6/6t devices") Signed-off-by: Gergo Koteles Signed-off-by: David Heidelberg Reviewed-by: Konrad Dybcio Link: https://lore.kernel.org/r/20250927-slider-correct-v1-1-fb8cc7fdcedf@ixit.cz Signed-off-by: Bjorn Andersson Signed-off-by: Sasha Levin --- arch/arm64/boot/dts/qcom/sdm845-oneplus-common.dtsi | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/arch/arm64/boot/dts/qcom/sdm845-oneplus-common.dtsi b/arch/arm64/boot/dts/qcom/sdm845-oneplus-common.dtsi index dcfffb271fcf31..51a9a276399ac7 100644 --- a/arch/arm64/boot/dts/qcom/sdm845-oneplus-common.dtsi +++ b/arch/arm64/boot/dts/qcom/sdm845-oneplus-common.dtsi @@ -803,8 +803,8 @@ bias-disable; }; - tri_state_key_default: tri-state-key-default-state { - pins = "gpio40", "gpio42", "gpio26"; + alert_slider_default: alert-slider-default-state { + pins = "gpio126", "gpio52", "gpio24"; function = "gpio"; drive-strength = <2>; bias-disable; From d9360ce07ea1274ce2af59f1eca2c858809bd3d6 Mon Sep 17 00:00:00 2001 From: Luca Weiss Date: Tue, 30 Sep 2025 15:57:01 +0200 Subject: [PATCH 0485/3902] arm64: dts: qcom: qcm6490-fairphone-fp5: Add supplies to simple-fb node [ Upstream commit 3d4142cac46b4dde4e60908c509c4cf107067114 ] Add the OLED power supplies to the simple-framebuffer node, so that the regulators don't get turned off while the simple-fb is being used. Fixes: c365a026155c ("arm64: dts: qcom: qcm6490-fairphone-fp5: Enable display") Signed-off-by: Luca Weiss Reviewed-by: Dmitry Baryshkov Link: https://lore.kernel.org/r/20250930-sc7280-dts-misc-v1-1-5a45923ef705@fairphone.com Signed-off-by: Bjorn Andersson Signed-off-by: Sasha Levin --- arch/arm64/boot/dts/qcom/qcm6490-fairphone-fp5.dts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/arch/arm64/boot/dts/qcom/qcm6490-fairphone-fp5.dts b/arch/arm64/boot/dts/qcom/qcm6490-fairphone-fp5.dts index 519e458e1a8908..36d5750584831d 100644 --- a/arch/arm64/boot/dts/qcom/qcm6490-fairphone-fp5.dts +++ b/arch/arm64/boot/dts/qcom/qcm6490-fairphone-fp5.dts @@ -47,6 +47,8 @@ stride = <(1224 * 4)>; format = "a8r8g8b8"; clocks = <&gcc GCC_DISP_HF_AXI_CLK>; + vci-supply = <&vreg_oled_vci>; + dvdd-supply = <&vreg_oled_dvdd>; }; }; From 436017ec6beebc72550a621977be77f890221194 Mon Sep 17 00:00:00 2001 From: Neil Armstrong Date: Tue, 7 Oct 2025 20:53:44 +0200 Subject: [PATCH 0486/3902] arm64: dts: qcom: sm8650: set ufs as dma coherent [ Upstream commit c2703c90161b45bca5b65f362adbae02ed71fcc1 ] The UFS device is ovbiously dma coherent like the other IOMMU devices like usb, mmc, ... let's fix this by adding the flag. To be sure an extensive test has been performed to be sure it's safe, as downstream uses this flag for UFS as well. As an experiment, I checked how the dma-coherent could impact the UFS bandwidth, and it happens the max bandwidth on cached write is slighly highter (up to 10%) while using less cpu time since cache sync/flush is skipped. Fixes: 10e024671295 ("arm64: dts: qcom: sm8650: add interconnect dependent device nodes") Signed-off-by: Neil Armstrong Reviewed-by: Krzysztof Kozlowski Reviewed-by: Dmitry Baryshkov Link: https://lore.kernel.org/r/20251007-topic-sm8650-upstream-ufs-dma-coherent-v1-1-f3cfeaee04ce@linaro.org Signed-off-by: Bjorn Andersson Signed-off-by: Sasha Levin --- arch/arm64/boot/dts/qcom/sm8650.dtsi | 2 ++ 1 file changed, 2 insertions(+) diff --git a/arch/arm64/boot/dts/qcom/sm8650.dtsi b/arch/arm64/boot/dts/qcom/sm8650.dtsi index ebf1971b1bfbeb..3b03c135393863 100644 --- a/arch/arm64/boot/dts/qcom/sm8650.dtsi +++ b/arch/arm64/boot/dts/qcom/sm8650.dtsi @@ -3988,6 +3988,8 @@ iommus = <&apps_smmu 0x60 0>; + dma-coherent; + lanes-per-direction = <2>; qcom,ice = <&ice>; From d30ffee3c9f8c98dd27b80becc0cf2035c28247b Mon Sep 17 00:00:00 2001 From: Krishna Chaitanya Chundru Date: Wed, 8 Oct 2025 10:08:55 +0530 Subject: [PATCH 0487/3902] arm64: dts: qcom: sm8750-mtp: move PCIe GPIOs to pcieport0 node [ Upstream commit cc8056a16472d186140d1a66ed5648cee41f4379 ] Relocate the wake-gpios and perst-gpios properties from the pcie0 controller node to the pcieport0 node. These GPIOs are associated with the PCIe root port and should reside under the pcieport0 node. Also rename perst-gpios to reset-gpios to match the expected property name in the PCIe port node. Fixes: 141714e163bb ("arm64: dts: qcom: sm8750-mtp: Add WiFi and Bluetooth") Signed-off-by: Krishna Chaitanya Chundru Reviewed-by: Krzysztof Kozlowski Tested-by: Krzysztof Kozlowski Link: https://lore.kernel.org/r/20251008-sm8750-v1-1-daeadfcae980@oss.qualcomm.com Signed-off-by: Bjorn Andersson Signed-off-by: Sasha Levin --- arch/arm64/boot/dts/qcom/sm8750-mtp.dts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/arch/arm64/boot/dts/qcom/sm8750-mtp.dts b/arch/arm64/boot/dts/qcom/sm8750-mtp.dts index 3bbb53b7c71f3c..45b5f758156700 100644 --- a/arch/arm64/boot/dts/qcom/sm8750-mtp.dts +++ b/arch/arm64/boot/dts/qcom/sm8750-mtp.dts @@ -960,9 +960,6 @@ }; &pcie0 { - wake-gpios = <&tlmm 104 GPIO_ACTIVE_HIGH>; - perst-gpios = <&tlmm 102 GPIO_ACTIVE_LOW>; - pinctrl-0 = <&pcie0_default_state>; pinctrl-names = "default"; @@ -977,6 +974,9 @@ }; &pcieport0 { + wake-gpios = <&tlmm 104 GPIO_ACTIVE_HIGH>; + reset-gpios = <&tlmm 102 GPIO_ACTIVE_LOW>; + wifi@0 { compatible = "pci17cb,1107"; reg = <0x10000 0x0 0x0 0x0 0x0>; From f8d49ad15f580b0638fb62dac5a333e068c47eb9 Mon Sep 17 00:00:00 2001 From: Alexander Martinz Date: Thu, 9 Oct 2025 11:06:33 +0200 Subject: [PATCH 0488/3902] arm64: dts: qcom: qcm6490-shift-otter: Add missing reserved-memory [ Upstream commit f404fdcb50021fdad6bc734d69468cc777901a80 ] It seems we also need to reserve a region of 81 MiB called "removed_mem" otherwise we can easily hit memory errors with higher RAM usage. Fixes: 249666e34c24 ("arm64: dts: qcom: add QCM6490 SHIFTphone 8") Signed-off-by: Alexander Martinz Reviewed-by: Konrad Dybcio Signed-off-by: Luca Weiss Reviewed-by: Dmitry Baryshkov Link: https://lore.kernel.org/r/20251009-otter-further-bringup-v2-3-5bb2f4a02cea@fairphone.com Signed-off-by: Bjorn Andersson Signed-off-by: Sasha Levin --- arch/arm64/boot/dts/qcom/qcm6490-shift-otter.dts | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/arch/arm64/boot/dts/qcom/qcm6490-shift-otter.dts b/arch/arm64/boot/dts/qcom/qcm6490-shift-otter.dts index eb8efba1b9dda4..cc99925798873c 100644 --- a/arch/arm64/boot/dts/qcom/qcm6490-shift-otter.dts +++ b/arch/arm64/boot/dts/qcom/qcm6490-shift-otter.dts @@ -118,6 +118,11 @@ no-map; }; + removed_mem: removed@c0000000 { + reg = <0x0 0xc0000000 0x0 0x5100000>; + no-map; + }; + rmtfs_mem: rmtfs@f8500000 { compatible = "qcom,rmtfs-mem"; reg = <0x0 0xf8500000 0x0 0x600000>; From e4badd5c24f75637a05b386010262e7076433f8b Mon Sep 17 00:00:00 2001 From: Val Packett Date: Sun, 12 Oct 2025 19:40:08 -0300 Subject: [PATCH 0489/3902] arm64: dts: qcom: x1-dell-thena: Add missing pinctrl for eDP HPD [ Upstream commit 1bdfe3edd4c862f97ac65b60da1db999981fc52a ] The commit a41d23142d87 ("arm64: dts: qcom: x1e80100-dell-xps13-9345: Add missing pinctrl for eDP HPD") has applied this change to a very similar machine, so apply it here too. This allows us not to rely on the boot firmware to set up the pinctrl for the eDP HPD line of the internal display. Fixes: e7733b42111c ("arm64: dts: qcom: Add support for Dell Inspiron 7441 / Latitude 7455") Reviewed-by: Bryan O'Donoghue Signed-off-by: Val Packett Link: https://lore.kernel.org/r/20251012224706.14311-1-val@packett.cool Signed-off-by: Bjorn Andersson Signed-off-by: Sasha Levin --- arch/arm64/boot/dts/qcom/x1-dell-thena.dtsi | 3 +++ 1 file changed, 3 insertions(+) diff --git a/arch/arm64/boot/dts/qcom/x1-dell-thena.dtsi b/arch/arm64/boot/dts/qcom/x1-dell-thena.dtsi index cc64558ed5e6fa..9df66295660c36 100644 --- a/arch/arm64/boot/dts/qcom/x1-dell-thena.dtsi +++ b/arch/arm64/boot/dts/qcom/x1-dell-thena.dtsi @@ -1039,6 +1039,9 @@ &mdss_dp3 { /delete-property/ #sound-dai-cells; + pinctrl-0 = <&edp0_hpd_default>; + pinctrl-names = "default"; + status = "okay"; aux-bus { From a2d1270ee7eff05fee743b264be52c9d9dc37b08 Mon Sep 17 00:00:00 2001 From: Val Packett Date: Sun, 12 Oct 2025 19:48:07 -0300 Subject: [PATCH 0490/3902] arm64: dts: qcom: x1-dell-thena: remove dp data-lanes [ Upstream commit 147d5eefab8f0e17e9951fb5e0c4c77bada34558 ] The commit 458de584248a ("arm64: dts: qcom: x1e80100: move dp0/1/2 data-lanes to SoC dtsi") has landed before this file was added, so the data-lanes lines here remained. Remove them to enable 4-lane DP on the X1E Dell Inspiron/Latitude. Fixes: e7733b42111c ("arm64: dts: qcom: Add support for Dell Inspiron 7441 / Latitude 7455") Reviewed-by: Bryan O'Donoghue Signed-off-by: Val Packett Reviewed-by: Konrad Dybcio Link: https://lore.kernel.org/r/20251012224909.14988-1-val@packett.cool Signed-off-by: Bjorn Andersson Signed-off-by: Sasha Levin --- arch/arm64/boot/dts/qcom/x1-dell-thena.dtsi | 2 -- 1 file changed, 2 deletions(-) diff --git a/arch/arm64/boot/dts/qcom/x1-dell-thena.dtsi b/arch/arm64/boot/dts/qcom/x1-dell-thena.dtsi index 9df66295660c36..847b678f040c00 100644 --- a/arch/arm64/boot/dts/qcom/x1-dell-thena.dtsi +++ b/arch/arm64/boot/dts/qcom/x1-dell-thena.dtsi @@ -1023,7 +1023,6 @@ }; &mdss_dp0_out { - data-lanes = <0 1>; link-frequencies = /bits/ 64 <1620000000 2700000000 5400000000 8100000000>; }; @@ -1032,7 +1031,6 @@ }; &mdss_dp1_out { - data-lanes = <0 1>; link-frequencies = /bits/ 64 <1620000000 2700000000 5400000000 8100000000>; }; From 20d56cf50da21d6eda835df433ba3525c187d880 Mon Sep 17 00:00:00 2001 From: Pengyu Luo Date: Mon, 13 Oct 2025 19:55:05 +0800 Subject: [PATCH 0491/3902] arm64: dts: qcom: sc8280xp: Fix shifted GPI DMA channels [ Upstream commit fb48d3f3abba9a7bca2814fa2e9db8ac5b9e16b9 ] The GPI DMA channels in sc8280xp.dtsi are wrong. Let's fix it. Origianl patch was rebased to the linux-next and formated to a new patch again later, then it got the GPI DMA channels in the new patch shifted. Fixes: 71b12166a2be ("arm64: dts: qcom: sc8280xp: Describe GPI DMA controller nodes") Signed-off-by: Pengyu Luo Reviewed-by: Konrad Dybcio Link: https://lore.kernel.org/r/20251013115506.103649-1-mitltlatltl@gmail.com Signed-off-by: Bjorn Andersson Signed-off-by: Sasha Levin --- arch/arm64/boot/dts/qcom/sc8280xp.dtsi | 170 ++++++++++++------------- 1 file changed, 85 insertions(+), 85 deletions(-) diff --git a/arch/arm64/boot/dts/qcom/sc8280xp.dtsi b/arch/arm64/boot/dts/qcom/sc8280xp.dtsi index 279e5e6beae209..963ce2362a52eb 100644 --- a/arch/arm64/boot/dts/qcom/sc8280xp.dtsi +++ b/arch/arm64/boot/dts/qcom/sc8280xp.dtsi @@ -967,8 +967,8 @@ <&aggre1_noc MASTER_QUP_2 0 &mc_virt SLAVE_EBI1 0>; interconnect-names = "qup-core", "qup-config", "qup-memory"; - dmas = <&gpi_dma2 0 6 QCOM_GPI_SPI>, - <&gpi_dma2 1 6 QCOM_GPI_SPI>; + dmas = <&gpi_dma2 0 0 QCOM_GPI_I2C>, + <&gpi_dma2 1 0 QCOM_GPI_I2C>; dma-names = "tx", "rx"; @@ -989,8 +989,8 @@ <&aggre1_noc MASTER_QUP_2 0 &mc_virt SLAVE_EBI1 0>; interconnect-names = "qup-core", "qup-config", "qup-memory"; - dmas = <&gpi_dma2 0 0 QCOM_GPI_I2C>, - <&gpi_dma2 1 0 QCOM_GPI_I2C>; + dmas = <&gpi_dma2 0 0 QCOM_GPI_SPI>, + <&gpi_dma2 1 0 QCOM_GPI_SPI>; dma-names = "tx", "rx"; @@ -1011,8 +1011,8 @@ <&aggre1_noc MASTER_QUP_2 0 &mc_virt SLAVE_EBI1 0>; interconnect-names = "qup-core", "qup-config", "qup-memory"; - dmas = <&gpi_dma2 0 0 QCOM_GPI_SPI>, - <&gpi_dma2 1 0 QCOM_GPI_SPI>; + dmas = <&gpi_dma2 0 1 QCOM_GPI_I2C>, + <&gpi_dma2 1 1 QCOM_GPI_I2C>; dma-names = "tx", "rx"; @@ -1033,8 +1033,8 @@ <&aggre1_noc MASTER_QUP_2 0 &mc_virt SLAVE_EBI1 0>; interconnect-names = "qup-core", "qup-config", "qup-memory"; - dmas = <&gpi_dma2 0 1 QCOM_GPI_I2C>, - <&gpi_dma2 1 1 QCOM_GPI_I2C>; + dmas = <&gpi_dma2 0 1 QCOM_GPI_SPI>, + <&gpi_dma2 1 1 QCOM_GPI_SPI>; dma-names = "tx", "rx"; @@ -1069,8 +1069,8 @@ <&aggre1_noc MASTER_QUP_2 0 &mc_virt SLAVE_EBI1 0>; interconnect-names = "qup-core", "qup-config", "qup-memory"; - dmas = <&gpi_dma2 0 1 QCOM_GPI_SPI>, - <&gpi_dma2 1 1 QCOM_GPI_SPI>; + dmas = <&gpi_dma2 0 2 QCOM_GPI_I2C>, + <&gpi_dma2 1 2 QCOM_GPI_I2C>; dma-names = "tx", "rx"; @@ -1091,8 +1091,8 @@ <&aggre1_noc MASTER_QUP_2 0 &mc_virt SLAVE_EBI1 0>; interconnect-names = "qup-core", "qup-config", "qup-memory"; - dmas = <&gpi_dma2 0 2 QCOM_GPI_I2C>, - <&gpi_dma2 1 2 QCOM_GPI_I2C>; + dmas = <&gpi_dma2 0 2 QCOM_GPI_SPI>, + <&gpi_dma2 1 2 QCOM_GPI_SPI>; dma-names = "tx", "rx"; @@ -1131,8 +1131,8 @@ <&aggre1_noc MASTER_QUP_2 0 &mc_virt SLAVE_EBI1 0>; interconnect-names = "qup-core", "qup-config", "qup-memory"; - dmas = <&gpi_dma2 0 2 QCOM_GPI_SPI>, - <&gpi_dma2 1 2 QCOM_GPI_SPI>; + dmas = <&gpi_dma2 0 3 QCOM_GPI_I2C>, + <&gpi_dma2 1 3 QCOM_GPI_I2C>; dma-names = "tx", "rx"; @@ -1153,8 +1153,8 @@ <&aggre1_noc MASTER_QUP_2 0 &mc_virt SLAVE_EBI1 0>; interconnect-names = "qup-core", "qup-config", "qup-memory"; - dmas = <&gpi_dma2 0 3 QCOM_GPI_I2C>, - <&gpi_dma2 1 3 QCOM_GPI_I2C>; + dmas = <&gpi_dma2 0 3 QCOM_GPI_SPI>, + <&gpi_dma2 1 3 QCOM_GPI_SPI>; dma-names = "tx", "rx"; @@ -1175,8 +1175,8 @@ <&aggre1_noc MASTER_QUP_2 0 &mc_virt SLAVE_EBI1 0>; interconnect-names = "qup-core", "qup-config", "qup-memory"; - dmas = <&gpi_dma2 0 3 QCOM_GPI_SPI>, - <&gpi_dma2 1 3 QCOM_GPI_SPI>; + dmas = <&gpi_dma2 0 4 QCOM_GPI_I2C>, + <&gpi_dma2 1 4 QCOM_GPI_I2C>; dma-names = "tx", "rx"; @@ -1197,8 +1197,8 @@ <&aggre1_noc MASTER_QUP_2 0 &mc_virt SLAVE_EBI1 0>; interconnect-names = "qup-core", "qup-config", "qup-memory"; - dmas = <&gpi_dma2 0 4 QCOM_GPI_I2C>, - <&gpi_dma2 1 4 QCOM_GPI_I2C>; + dmas = <&gpi_dma2 0 4 QCOM_GPI_SPI>, + <&gpi_dma2 1 4 QCOM_GPI_SPI>; dma-names = "tx", "rx"; @@ -1241,8 +1241,8 @@ <&aggre1_noc MASTER_QUP_2 0 &mc_virt SLAVE_EBI1 0>; interconnect-names = "qup-core", "qup-config", "qup-memory"; - dmas = <&gpi_dma2 0 4 QCOM_GPI_SPI>, - <&gpi_dma2 1 4 QCOM_GPI_SPI>; + dmas = <&gpi_dma2 0 5 QCOM_GPI_SPI>, + <&gpi_dma2 1 5 QCOM_GPI_SPI>; dma-names = "tx", "rx"; @@ -1285,8 +1285,8 @@ <&aggre1_noc MASTER_QUP_2 0 &mc_virt SLAVE_EBI1 0>; interconnect-names = "qup-core", "qup-config", "qup-memory"; - dmas = <&gpi_dma2 0 5 QCOM_GPI_SPI>, - <&gpi_dma2 1 5 QCOM_GPI_SPI>; + dmas = <&gpi_dma2 0 6 QCOM_GPI_SPI>, + <&gpi_dma2 1 6 QCOM_GPI_SPI>; dma-names = "tx", "rx"; @@ -1338,7 +1338,7 @@ }; }; - gpi_dma0: dma-controller@900000 { + gpi_dma0: dma-controller@900000 { compatible = "qcom,sc8280xp-gpi-dma", "qcom,sm6350-gpi-dma"; reg = <0 0x00900000 0 0x60000>; @@ -1393,8 +1393,8 @@ <&aggre1_noc MASTER_QUP_0 0 &mc_virt SLAVE_EBI1 0>; interconnect-names = "qup-core", "qup-config", "qup-memory"; - dmas = <&gpi_dma0 0 7 QCOM_GPI_I2C>, - <&gpi_dma0 1 7 QCOM_GPI_I2C>; + dmas = <&gpi_dma0 0 0 QCOM_GPI_I2C>, + <&gpi_dma0 1 0 QCOM_GPI_I2C>; dma-names = "tx", "rx"; @@ -1415,8 +1415,8 @@ <&aggre1_noc MASTER_QUP_0 0 &mc_virt SLAVE_EBI1 0>; interconnect-names = "qup-core", "qup-config", "qup-memory"; - dmas = <&gpi_dma0 0 0 QCOM_GPI_I2C>, - <&gpi_dma0 1 0 QCOM_GPI_I2C>; + dmas = <&gpi_dma0 0 0 QCOM_GPI_SPI>, + <&gpi_dma0 1 0 QCOM_GPI_SPI>; dma-names = "tx", "rx"; @@ -1437,8 +1437,8 @@ <&aggre1_noc MASTER_QUP_0 0 &mc_virt SLAVE_EBI1 0>; interconnect-names = "qup-core", "qup-config", "qup-memory"; - dmas = <&gpi_dma0 0 0 QCOM_GPI_SPI>, - <&gpi_dma0 1 0 QCOM_GPI_SPI>; + dmas = <&gpi_dma0 0 1 QCOM_GPI_I2C>, + <&gpi_dma0 1 1 QCOM_GPI_I2C>; dma-names = "tx", "rx"; @@ -1459,8 +1459,8 @@ <&aggre1_noc MASTER_QUP_0 0 &mc_virt SLAVE_EBI1 0>; interconnect-names = "qup-core", "qup-config", "qup-memory"; - dmas = <&gpi_dma0 0 1 QCOM_GPI_I2C>, - <&gpi_dma0 1 1 QCOM_GPI_I2C>; + dmas = <&gpi_dma0 0 1 QCOM_GPI_SPI>, + <&gpi_dma0 1 1 QCOM_GPI_SPI>; dma-names = "tx", "rx"; @@ -1481,8 +1481,8 @@ <&aggre1_noc MASTER_QUP_0 0 &mc_virt SLAVE_EBI1 0>; interconnect-names = "qup-core", "qup-config", "qup-memory"; - dmas = <&gpi_dma0 0 1 QCOM_GPI_SPI>, - <&gpi_dma0 1 1 QCOM_GPI_SPI>; + dmas = <&gpi_dma0 0 2 QCOM_GPI_I2C>, + <&gpi_dma0 1 2 QCOM_GPI_I2C>; dma-names = "tx", "rx"; @@ -1503,8 +1503,8 @@ <&aggre1_noc MASTER_QUP_0 0 &mc_virt SLAVE_EBI1 0>; interconnect-names = "qup-core", "qup-config", "qup-memory"; - dmas = <&gpi_dma0 0 2 QCOM_GPI_I2C>, - <&gpi_dma0 1 2 QCOM_GPI_I2C>; + dmas = <&gpi_dma0 0 2 QCOM_GPI_SPI>, + <&gpi_dma0 1 2 QCOM_GPI_SPI>; dma-names = "tx", "rx"; @@ -1539,8 +1539,8 @@ <&aggre1_noc MASTER_QUP_0 0 &mc_virt SLAVE_EBI1 0>; interconnect-names = "qup-core", "qup-config", "qup-memory"; - dmas = <&gpi_dma0 0 2 QCOM_GPI_SPI>, - <&gpi_dma0 1 2 QCOM_GPI_SPI>; + dmas = <&gpi_dma0 0 3 QCOM_GPI_I2C>, + <&gpi_dma0 1 3 QCOM_GPI_I2C>; dma-names = "tx", "rx"; @@ -1561,8 +1561,8 @@ <&aggre1_noc MASTER_QUP_0 0 &mc_virt SLAVE_EBI1 0>; interconnect-names = "qup-core", "qup-config", "qup-memory"; - dmas = <&gpi_dma0 0 3 QCOM_GPI_I2C>, - <&gpi_dma0 1 3 QCOM_GPI_I2C>; + dmas = <&gpi_dma0 0 3 QCOM_GPI_SPI>, + <&gpi_dma0 1 3 QCOM_GPI_SPI>; dma-names = "tx", "rx"; @@ -1583,8 +1583,8 @@ <&aggre1_noc MASTER_QUP_0 0 &mc_virt SLAVE_EBI1 0>; interconnect-names = "qup-core", "qup-config", "qup-memory"; - dmas = <&gpi_dma0 0 3 QCOM_GPI_SPI>, - <&gpi_dma0 1 3 QCOM_GPI_SPI>; + dmas = <&gpi_dma0 0 4 QCOM_GPI_I2C>, + <&gpi_dma0 1 4 QCOM_GPI_I2C>; dma-names = "tx", "rx"; @@ -1605,8 +1605,8 @@ <&aggre1_noc MASTER_QUP_0 0 &mc_virt SLAVE_EBI1 0>; interconnect-names = "qup-core", "qup-config", "qup-memory"; - dmas = <&gpi_dma0 0 4 QCOM_GPI_I2C>, - <&gpi_dma0 1 4 QCOM_GPI_I2C>; + dmas = <&gpi_dma0 0 4 QCOM_GPI_SPI>, + <&gpi_dma0 1 4 QCOM_GPI_SPI>; dma-names = "tx", "rx"; @@ -1627,8 +1627,8 @@ <&aggre1_noc MASTER_QUP_0 0 &mc_virt SLAVE_EBI1 0>; interconnect-names = "qup-core", "qup-config", "qup-memory"; - dmas = <&gpi_dma0 0 4 QCOM_GPI_SPI>, - <&gpi_dma0 1 4 QCOM_GPI_SPI>; + dmas = <&gpi_dma0 0 5 QCOM_GPI_I2C>, + <&gpi_dma0 1 5 QCOM_GPI_I2C>; dma-names = "tx", "rx"; @@ -1649,8 +1649,8 @@ <&aggre1_noc MASTER_QUP_0 0 &mc_virt SLAVE_EBI1 0>; interconnect-names = "qup-core", "qup-config", "qup-memory"; - dmas = <&gpi_dma0 0 5 QCOM_GPI_I2C>, - <&gpi_dma0 1 5 QCOM_GPI_I2C>; + dmas = <&gpi_dma0 0 5 QCOM_GPI_SPI>, + <&gpi_dma0 1 5 QCOM_GPI_SPI>; dma-names = "tx", "rx"; @@ -1671,8 +1671,8 @@ <&aggre1_noc MASTER_QUP_0 0 &mc_virt SLAVE_EBI1 0>; interconnect-names = "qup-core", "qup-config", "qup-memory"; - dmas = <&gpi_dma0 0 5 QCOM_GPI_SPI>, - <&gpi_dma0 1 5 QCOM_GPI_SPI>; + dmas = <&gpi_dma0 0 6 QCOM_GPI_I2C>, + <&gpi_dma0 1 6 QCOM_GPI_I2C>; dma-names = "tx", "rx"; @@ -1693,8 +1693,8 @@ <&aggre1_noc MASTER_QUP_0 0 &mc_virt SLAVE_EBI1 0>; interconnect-names = "qup-core", "qup-config", "qup-memory"; - dmas = <&gpi_dma0 0 6 QCOM_GPI_I2C>, - <&gpi_dma0 1 6 QCOM_GPI_I2C>; + dmas = <&gpi_dma0 0 6 QCOM_GPI_SPI>, + <&gpi_dma0 1 6 QCOM_GPI_SPI>; dma-names = "tx", "rx"; @@ -1715,8 +1715,8 @@ <&aggre1_noc MASTER_QUP_0 0 &mc_virt SLAVE_EBI1 0>; interconnect-names = "qup-core", "qup-config", "qup-memory"; - dmas = <&gpi_dma0 0 6 QCOM_GPI_SPI>, - <&gpi_dma0 1 6 QCOM_GPI_SPI>; + dmas = <&gpi_dma0 0 7 QCOM_GPI_I2C>, + <&gpi_dma0 1 7 QCOM_GPI_I2C>; dma-names = "tx", "rx"; @@ -1800,8 +1800,8 @@ <&aggre1_noc MASTER_QUP_1 0 &mc_virt SLAVE_EBI1 0>; interconnect-names = "qup-core", "qup-config", "qup-memory"; - dmas = <&gpi_dma1 0 7 QCOM_GPI_I2C>, - <&gpi_dma1 1 7 QCOM_GPI_I2C>; + dmas = <&gpi_dma1 0 0 QCOM_GPI_I2C>, + <&gpi_dma1 1 0 QCOM_GPI_I2C>; dma-names = "tx", "rx"; @@ -1822,8 +1822,8 @@ <&aggre1_noc MASTER_QUP_1 0 &mc_virt SLAVE_EBI1 0>; interconnect-names = "qup-core", "qup-config", "qup-memory"; - dmas = <&gpi_dma1 0 0 QCOM_GPI_I2C>, - <&gpi_dma1 1 0 QCOM_GPI_I2C>; + dmas = <&gpi_dma1 0 0 QCOM_GPI_SPI>, + <&gpi_dma1 1 0 QCOM_GPI_SPI>; dma-names = "tx", "rx"; @@ -1844,8 +1844,8 @@ <&aggre1_noc MASTER_QUP_1 0 &mc_virt SLAVE_EBI1 0>; interconnect-names = "qup-core", "qup-config", "qup-memory"; - dmas = <&gpi_dma1 0 0 QCOM_GPI_SPI>, - <&gpi_dma1 1 0 QCOM_GPI_SPI>; + dmas = <&gpi_dma1 0 1 QCOM_GPI_I2C>, + <&gpi_dma1 1 1 QCOM_GPI_I2C>; dma-names = "tx", "rx"; @@ -1866,8 +1866,8 @@ <&aggre1_noc MASTER_QUP_1 0 &mc_virt SLAVE_EBI1 0>; interconnect-names = "qup-core", "qup-config", "qup-memory"; - dmas = <&gpi_dma1 0 1 QCOM_GPI_I2C>, - <&gpi_dma1 1 1 QCOM_GPI_I2C>; + dmas = <&gpi_dma1 0 1 QCOM_GPI_SPI>, + <&gpi_dma1 1 1 QCOM_GPI_SPI>; dma-names = "tx", "rx"; @@ -1888,8 +1888,8 @@ <&aggre1_noc MASTER_QUP_1 0 &mc_virt SLAVE_EBI1 0>; interconnect-names = "qup-core", "qup-config", "qup-memory"; - dmas = <&gpi_dma1 0 1 QCOM_GPI_SPI>, - <&gpi_dma1 1 1 QCOM_GPI_SPI>; + dmas = <&gpi_dma1 0 2 QCOM_GPI_I2C>, + <&gpi_dma1 1 2 QCOM_GPI_I2C>; dma-names = "tx", "rx"; @@ -1910,8 +1910,8 @@ <&aggre1_noc MASTER_QUP_1 0 &mc_virt SLAVE_EBI1 0>; interconnect-names = "qup-core", "qup-config", "qup-memory"; - dmas = <&gpi_dma1 0 2 QCOM_GPI_I2C>, - <&gpi_dma1 1 2 QCOM_GPI_I2C>; + dmas = <&gpi_dma1 0 2 QCOM_GPI_SPI>, + <&gpi_dma1 1 2 QCOM_GPI_SPI>; dma-names = "tx", "rx"; @@ -1932,8 +1932,8 @@ <&aggre1_noc MASTER_QUP_1 0 &mc_virt SLAVE_EBI1 0>; interconnect-names = "qup-core", "qup-config", "qup-memory"; - dmas = <&gpi_dma1 0 2 QCOM_GPI_SPI>, - <&gpi_dma1 1 2 QCOM_GPI_SPI>; + dmas = <&gpi_dma1 0 3 QCOM_GPI_I2C>, + <&gpi_dma1 1 3 QCOM_GPI_I2C>; dma-names = "tx", "rx"; @@ -1954,8 +1954,8 @@ <&aggre1_noc MASTER_QUP_1 0 &mc_virt SLAVE_EBI1 0>; interconnect-names = "qup-core", "qup-config", "qup-memory"; - dmas = <&gpi_dma1 0 3 QCOM_GPI_I2C>, - <&gpi_dma1 1 3 QCOM_GPI_I2C>; + dmas = <&gpi_dma1 0 3 QCOM_GPI_SPI>, + <&gpi_dma1 1 3 QCOM_GPI_SPI>; dma-names = "tx", "rx"; @@ -1976,8 +1976,8 @@ <&aggre1_noc MASTER_QUP_1 0 &mc_virt SLAVE_EBI1 0>; interconnect-names = "qup-core", "qup-config", "qup-memory"; - dmas = <&gpi_dma1 0 3 QCOM_GPI_SPI>, - <&gpi_dma1 1 3 QCOM_GPI_SPI>; + dmas = <&gpi_dma1 0 4 QCOM_GPI_I2C>, + <&gpi_dma1 1 4 QCOM_GPI_I2C>; dma-names = "tx", "rx"; @@ -1998,8 +1998,8 @@ <&aggre1_noc MASTER_QUP_1 0 &mc_virt SLAVE_EBI1 0>; interconnect-names = "qup-core", "qup-config", "qup-memory"; - dmas = <&gpi_dma1 0 4 QCOM_GPI_I2C>, - <&gpi_dma1 1 4 QCOM_GPI_I2C>; + dmas = <&gpi_dma1 0 4 QCOM_GPI_SPI>, + <&gpi_dma1 1 4 QCOM_GPI_SPI>; dma-names = "tx", "rx"; @@ -2020,8 +2020,8 @@ <&aggre1_noc MASTER_QUP_1 0 &mc_virt SLAVE_EBI1 0>; interconnect-names = "qup-core", "qup-config", "qup-memory"; - dmas = <&gpi_dma1 0 4 QCOM_GPI_SPI>, - <&gpi_dma1 1 4 QCOM_GPI_SPI>; + dmas = <&gpi_dma1 0 5 QCOM_GPI_I2C>, + <&gpi_dma1 1 5 QCOM_GPI_I2C>; dma-names = "tx", "rx"; @@ -2042,8 +2042,8 @@ <&aggre1_noc MASTER_QUP_1 0 &mc_virt SLAVE_EBI1 0>; interconnect-names = "qup-core", "qup-config", "qup-memory"; - dmas = <&gpi_dma1 0 5 QCOM_GPI_I2C>, - <&gpi_dma1 1 5 QCOM_GPI_I2C>; + dmas = <&gpi_dma1 0 5 QCOM_GPI_SPI>, + <&gpi_dma1 1 5 QCOM_GPI_SPI>; dma-names = "tx", "rx"; @@ -2064,8 +2064,8 @@ <&aggre1_noc MASTER_QUP_1 0 &mc_virt SLAVE_EBI1 0>; interconnect-names = "qup-core", "qup-config", "qup-memory"; - dmas = <&gpi_dma1 0 5 QCOM_GPI_SPI>, - <&gpi_dma1 1 5 QCOM_GPI_SPI>; + dmas = <&gpi_dma1 0 6 QCOM_GPI_I2C>, + <&gpi_dma1 1 6 QCOM_GPI_I2C>; dma-names = "tx", "rx"; @@ -2086,8 +2086,8 @@ <&aggre1_noc MASTER_QUP_1 0 &mc_virt SLAVE_EBI1 0>; interconnect-names = "qup-core", "qup-config", "qup-memory"; - dmas = <&gpi_dma1 0 6 QCOM_GPI_I2C>, - <&gpi_dma1 1 6 QCOM_GPI_I2C>; + dmas = <&gpi_dma1 0 6 QCOM_GPI_SPI>, + <&gpi_dma1 1 6 QCOM_GPI_SPI>; dma-names = "tx", "rx"; @@ -2108,8 +2108,8 @@ <&aggre1_noc MASTER_QUP_1 0 &mc_virt SLAVE_EBI1 0>; interconnect-names = "qup-core", "qup-config", "qup-memory"; - dmas = <&gpi_dma1 0 6 QCOM_GPI_SPI>, - <&gpi_dma1 1 6 QCOM_GPI_SPI>; + dmas = <&gpi_dma1 0 7 QCOM_GPI_I2C>, + <&gpi_dma1 1 7 QCOM_GPI_I2C>; dma-names = "tx", "rx"; From 21c7d04fa88dcec046ceabdbea4700d41a018f98 Mon Sep 17 00:00:00 2001 From: Konrad Dybcio Date: Wed, 15 Oct 2025 18:32:16 +0200 Subject: [PATCH 0492/3902] arm64: dts: qcom: sdm845-starqltechn: Fix i2c-gpio node name [ Upstream commit 6030fa06360b8b8d898b66ac156adaaf990b83cb ] Fix the following DT checker warning: $nodename:0: 'i2c21' does not match '^i2c(@.+|-[a-z0-9]+)?$' Fixes: 3a4600448bef ("arm64: dts: qcom: sdm845-starqltechn: add display PMIC") Signed-off-by: Konrad Dybcio Reviewed-by: Dmitry Baryshkov Link: https://lore.kernel.org/r/20251015-topic-starltechn_i2c_gpio-v1-1-6d303184ee87@oss.qualcomm.com Signed-off-by: Bjorn Andersson Signed-off-by: Sasha Levin --- arch/arm64/boot/dts/qcom/sdm845-samsung-starqltechn.dts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/arch/arm64/boot/dts/qcom/sdm845-samsung-starqltechn.dts b/arch/arm64/boot/dts/qcom/sdm845-samsung-starqltechn.dts index 215e1491f3e9ad..493c69e9917461 100644 --- a/arch/arm64/boot/dts/qcom/sdm845-samsung-starqltechn.dts +++ b/arch/arm64/boot/dts/qcom/sdm845-samsung-starqltechn.dts @@ -158,7 +158,7 @@ }; }; - i2c21 { + i2c-21 { compatible = "i2c-gpio"; sda-gpios = <&tlmm 127 (GPIO_ACTIVE_HIGH | GPIO_OPEN_DRAIN)>; scl-gpios = <&tlmm 128 (GPIO_ACTIVE_HIGH | GPIO_OPEN_DRAIN)>; From bd55d00190673cbf609528fe3d4f8dc221a5226e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Eric=20Gon=C3=A7alves?= Date: Thu, 16 Oct 2025 16:21:29 -0400 Subject: [PATCH 0493/3902] arm64: dts: qcom: sm8250-samsung-common: correct reserved pins MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [ Upstream commit 42e56b53a1919dbbd78e140a9f8223f8136ac360 ] The S20 series has additional reserved pins for the fingerprint sensor, GPIO 20-23. Correct it by adding them into gpio-reserved-ranges. Fixes: 6657fe9e9f23 ("arm64: dts: qcom: add initial support for Samsung Galaxy S20 FE") Signed-off-by: Eric Gonçalves Reviewed-by: Dmitry Baryshkov Reviewed-by: Konrad Dybcio Link: https://lore.kernel.org/r/20251016202129.226449-1-ghatto404@gmail.com Signed-off-by: Bjorn Andersson Signed-off-by: Sasha Levin --- arch/arm64/boot/dts/qcom/sm8250-samsung-common.dtsi | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/arch/arm64/boot/dts/qcom/sm8250-samsung-common.dtsi b/arch/arm64/boot/dts/qcom/sm8250-samsung-common.dtsi index cf3d917addd828..ef7ea4f72bf999 100644 --- a/arch/arm64/boot/dts/qcom/sm8250-samsung-common.dtsi +++ b/arch/arm64/boot/dts/qcom/sm8250-samsung-common.dtsi @@ -159,7 +159,8 @@ }; &tlmm { - gpio-reserved-ranges = <40 4>; /* I2C (Unused) */ + gpio-reserved-ranges = <20 4>, /* SPI (fingerprint scanner) */ + <40 4>; /* Unused */ }; &usb_1 { From cc12e90519c287c43de32dfbd6122fae5213bef7 Mon Sep 17 00:00:00 2001 From: Michal Suchanek Date: Wed, 22 Oct 2025 12:38:35 +0200 Subject: [PATCH 0494/3902] perf hwmon_pmu: Fix uninitialized variable warning MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [ Upstream commit 2fee899c068c159e486e62623afe9e2a4975bd79 ] The line_len is only set on success. Check the return value instead. util/hwmon_pmu.c: In function ‘perf_pmus__read_hwmon_pmus’: util/hwmon_pmu.c:742:20: warning: ‘line_len’ may be used uninitialized [-Wmaybe-uninitialized] 742 | if (line_len > 0 && line[line_len - 1] == '\n') | ^ util/hwmon_pmu.c:719:24: note: ‘line_len’ was declared here 719 | size_t line_len; Fixes: 53cc0b351ec9 ("perf hwmon_pmu: Add a tool PMU exposing events from hwmon in sysfs") Signed-off-by: Michal Suchanek Signed-off-by: Namhyung Kim Signed-off-by: Sasha Levin --- tools/perf/util/hwmon_pmu.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/tools/perf/util/hwmon_pmu.c b/tools/perf/util/hwmon_pmu.c index 416dfea9ffff61..5c27256a220a51 100644 --- a/tools/perf/util/hwmon_pmu.c +++ b/tools/perf/util/hwmon_pmu.c @@ -742,8 +742,7 @@ int perf_pmus__read_hwmon_pmus(struct list_head *pmus) continue; } io__init(&io, name_fd, buf2, sizeof(buf2)); - io__getline(&io, &line, &line_len); - if (line_len > 0 && line[line_len - 1] == '\n') + if (io__getline(&io, &line, &line_len) > 0 && line[line_len - 1] == '\n') line[line_len - 1] = '\0'; hwmon_pmu__new(pmus, buf, class_hwmon_ent->d_name, line); close(name_fd); From 2cf1278e942b2d202f2d4ceeab70a810ebc4da0a Mon Sep 17 00:00:00 2001 From: Horatiu Vultur Date: Thu, 23 Oct 2025 21:13:50 +0200 Subject: [PATCH 0495/3902] phy: mscc: Fix PTP for VSC8574 and VSC8572 [ Upstream commit ea5df88aeca112aac69e6c32e3dd1433a113b0c9 ] The PTP initialization is two-step. First part are the function vsc8584_ptp_probe_once() and vsc8584_ptp_probe() at probe time which initialize the locks, queues, creates the PTP device. The second part is the function vsc8584_ptp_init() at config_init() time which initialize PTP in the HW. For VSC8574 and VSC8572, the PTP initialization is incomplete. It is missing the first part but it makes the second part. Meaning that the ptp_clock_register() is never called. There is no crash without the first part when enabling PTP but this is unexpected because some PHys have PTP functionality exposed by the driver and some don't even though they share the same PTP clock PTP. Fixes: 774626fa440e ("net: phy: mscc: Add PTP support for 2 more VSC PHYs") Reviewed-by: Maxime Chevallier Signed-off-by: Horatiu Vultur Link: https://patch.msgid.link/20251023191350.190940-3-horatiu.vultur@microchip.com Signed-off-by: Jakub Kicinski Signed-off-by: Sasha Levin --- drivers/net/phy/mscc/mscc_main.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/drivers/net/phy/mscc/mscc_main.c b/drivers/net/phy/mscc/mscc_main.c index ef0ef1570d3923..48d43f60b8ff8c 100644 --- a/drivers/net/phy/mscc/mscc_main.c +++ b/drivers/net/phy/mscc/mscc_main.c @@ -2625,7 +2625,7 @@ static struct phy_driver vsc85xx_driver[] = { .suspend = &genphy_suspend, .resume = &genphy_resume, .remove = &vsc85xx_remove, - .probe = &vsc8574_probe, + .probe = &vsc8584_probe, .set_wol = &vsc85xx_wol_set, .get_wol = &vsc85xx_wol_get, .get_tunable = &vsc85xx_get_tunable, @@ -2648,12 +2648,12 @@ static struct phy_driver vsc85xx_driver[] = { .config_aneg = &vsc85xx_config_aneg, .aneg_done = &genphy_aneg_done, .read_status = &vsc85xx_read_status, - .handle_interrupt = vsc85xx_handle_interrupt, + .handle_interrupt = vsc8584_handle_interrupt, .config_intr = &vsc85xx_config_intr, .suspend = &genphy_suspend, .resume = &genphy_resume, .remove = &vsc85xx_remove, - .probe = &vsc8574_probe, + .probe = &vsc8584_probe, .set_wol = &vsc85xx_wol_set, .get_wol = &vsc85xx_wol_get, .get_tunable = &vsc85xx_get_tunable, From 7a26bcbe469c043780f1027bc10d1369ad1fc0b2 Mon Sep 17 00:00:00 2001 From: Kuniyuki Iwashima Date: Thu, 23 Oct 2025 23:16:50 +0000 Subject: [PATCH 0496/3902] sctp: Defer SCTP_DBG_OBJCNT_DEC() to sctp_destroy_sock(). [ Upstream commit 622e8838a29845316668ec2e7648428878df7f9a ] SCTP_DBG_OBJCNT_INC() is called only when sctp_init_sock() returns 0 after successfully allocating sctp_sk(sk)->ep. OTOH, SCTP_DBG_OBJCNT_DEC() is called in sctp_close(). The code seems to expect that the socket is always exposed to userspace once SCTP_DBG_OBJCNT_INC() is incremented, but there is a path where the assumption is not true. In sctp_accept(), sctp_sock_migrate() could fail after sctp_init_sock(). Then, sk_common_release() does not call inet_release() nor sctp_close(). Instead, it calls sk->sk_prot->destroy(). Let's move SCTP_DBG_OBJCNT_DEC() from sctp_close() to sctp_destroy_sock(). Fixes: 1da177e4c3f4 ("Linux-2.6.12-rc2") Signed-off-by: Kuniyuki Iwashima Acked-by: Xin Long Link: https://patch.msgid.link/20251023231751.4168390-2-kuniyu@google.com Signed-off-by: Jakub Kicinski Signed-off-by: Sasha Levin --- net/sctp/socket.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/net/sctp/socket.c b/net/sctp/socket.c index ed8293a3424029..d190e75e46454a 100644 --- a/net/sctp/socket.c +++ b/net/sctp/socket.c @@ -1553,8 +1553,6 @@ static void sctp_close(struct sock *sk, long timeout) spin_unlock_bh(&net->sctp.addr_wq_lock); sock_put(sk); - - SCTP_DBG_OBJCNT_DEC(sock); } /* Handle EPIPE error. */ @@ -5109,9 +5107,12 @@ static void sctp_destroy_sock(struct sock *sk) sp->do_auto_asconf = 0; list_del(&sp->auto_asconf_list); } + sctp_endpoint_free(sp->ep); + sk_sockets_allocated_dec(sk); sock_prot_inuse_add(sock_net(sk), sk->sk_prot, -1); + SCTP_DBG_OBJCNT_DEC(sock); } static void sctp_destruct_sock(struct sock *sk) From a10b6eb5c5e59d74360829c7a8ac249a0a7f8160 Mon Sep 17 00:00:00 2001 From: Loic Poulain Date: Thu, 18 Sep 2025 17:54:56 +0200 Subject: [PATCH 0497/3902] arm64: dts: qcom: qcm2290: Fix camss register prop ordering [ Upstream commit 67445dc8a8060309eeb7aebbc41fa0e58302fc09 ] The qcm2290 CAMSS node has been applied from the V4 series, but a later version changed the order of the register property, fix it to prevent dtb check error. Fixes: 2b3aef30dd9d ("arm64: dts: qcom: qcm2290: Add CAMSS node") Signed-off-by: Loic Poulain Link: https://lore.kernel.org/r/20250918155456.1158691-1-loic.poulain@oss.qualcomm.com Signed-off-by: Bjorn Andersson Signed-off-by: Sasha Levin --- arch/arm64/boot/dts/qcom/qcm2290.dtsi | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/arch/arm64/boot/dts/qcom/qcm2290.dtsi b/arch/arm64/boot/dts/qcom/qcm2290.dtsi index 08141b41de2462..3b0ba590ee825d 100644 --- a/arch/arm64/boot/dts/qcom/qcm2290.dtsi +++ b/arch/arm64/boot/dts/qcom/qcm2290.dtsi @@ -1685,25 +1685,25 @@ }; }; - camss: camss@5c6e000 { + camss: camss@5c11000 { compatible = "qcom,qcm2290-camss"; - reg = <0x0 0x5c6e000 0x0 0x1000>, + reg = <0x0 0x5c11000 0x0 0x1000>, + <0x0 0x5c6e000 0x0 0x1000>, <0x0 0x5c75000 0x0 0x1000>, <0x0 0x5c52000 0x0 0x1000>, <0x0 0x5c53000 0x0 0x1000>, <0x0 0x5c66000 0x0 0x400>, <0x0 0x5c68000 0x0 0x400>, - <0x0 0x5c11000 0x0 0x1000>, <0x0 0x5c6f000 0x0 0x4000>, <0x0 0x5c76000 0x0 0x4000>; - reg-names = "csid0", + reg-names = "top", + "csid0", "csid1", "csiphy0", "csiphy1", "csitpg0", "csitpg1", - "top", "vfe0", "vfe1"; From bc4c14a3863cc0e03698caec9a0cdabd779776ee Mon Sep 17 00:00:00 2001 From: Zhu Yanjun Date: Mon, 27 Oct 2025 14:52:03 -0700 Subject: [PATCH 0498/3902] RDMA/rxe: Fix null deref on srq->rq.queue after resize failure [ Upstream commit 503a5e4690ae14c18570141bc0dcf7501a8419b0 ] A NULL pointer dereference can occur in rxe_srq_chk_attr() when ibv_modify_srq() is invoked twice in succession under certain error conditions. The first call may fail in rxe_queue_resize(), which leads rxe_srq_from_attr() to set srq->rq.queue = NULL. The second call then triggers a crash (null deref) when accessing srq->rq.queue->buf->index_mask. Call Trace: rxe_modify_srq+0x170/0x480 [rdma_rxe] ? __pfx_rxe_modify_srq+0x10/0x10 [rdma_rxe] ? uverbs_try_lock_object+0x4f/0xa0 [ib_uverbs] ? rdma_lookup_get_uobject+0x1f0/0x380 [ib_uverbs] ib_uverbs_modify_srq+0x204/0x290 [ib_uverbs] ? __pfx_ib_uverbs_modify_srq+0x10/0x10 [ib_uverbs] ? tryinc_node_nr_active+0xe6/0x150 ? uverbs_fill_udata+0xed/0x4f0 [ib_uverbs] ib_uverbs_handler_UVERBS_METHOD_INVOKE_WRITE+0x2c0/0x470 [ib_uverbs] ? __pfx_ib_uverbs_handler_UVERBS_METHOD_INVOKE_WRITE+0x10/0x10 [ib_uverbs] ? uverbs_fill_udata+0xed/0x4f0 [ib_uverbs] ib_uverbs_run_method+0x55a/0x6e0 [ib_uverbs] ? __pfx_ib_uverbs_handler_UVERBS_METHOD_INVOKE_WRITE+0x10/0x10 [ib_uverbs] ib_uverbs_cmd_verbs+0x54d/0x800 [ib_uverbs] ? __pfx_ib_uverbs_cmd_verbs+0x10/0x10 [ib_uverbs] ? __pfx___raw_spin_lock_irqsave+0x10/0x10 ? __pfx_do_vfs_ioctl+0x10/0x10 ? ioctl_has_perm.constprop.0.isra.0+0x2c7/0x4c0 ? __pfx_ioctl_has_perm.constprop.0.isra.0+0x10/0x10 ib_uverbs_ioctl+0x13e/0x220 [ib_uverbs] ? __pfx_ib_uverbs_ioctl+0x10/0x10 [ib_uverbs] __x64_sys_ioctl+0x138/0x1c0 do_syscall_64+0x82/0x250 ? fdget_pos+0x58/0x4c0 ? ksys_write+0xf3/0x1c0 ? __pfx_ksys_write+0x10/0x10 ? do_syscall_64+0xc8/0x250 ? __pfx_vm_mmap_pgoff+0x10/0x10 ? fget+0x173/0x230 ? fput+0x2a/0x80 ? ksys_mmap_pgoff+0x224/0x4c0 ? do_syscall_64+0xc8/0x250 ? do_user_addr_fault+0x37b/0xfe0 ? clear_bhb_loop+0x50/0xa0 ? clear_bhb_loop+0x50/0xa0 ? clear_bhb_loop+0x50/0xa0 entry_SYSCALL_64_after_hwframe+0x76/0x7e Fixes: 8700e3e7c485 ("Soft RoCE driver") Tested-by: Liu Yi Signed-off-by: Zhu Yanjun Link: https://patch.msgid.link/20251027215203.1321-1-yanjun.zhu@linux.dev Signed-off-by: Leon Romanovsky Signed-off-by: Sasha Levin --- drivers/infiniband/sw/rxe/rxe_srq.c | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/drivers/infiniband/sw/rxe/rxe_srq.c b/drivers/infiniband/sw/rxe/rxe_srq.c index 3661cb627d28a9..2a234f26ac1044 100644 --- a/drivers/infiniband/sw/rxe/rxe_srq.c +++ b/drivers/infiniband/sw/rxe/rxe_srq.c @@ -171,7 +171,7 @@ int rxe_srq_from_attr(struct rxe_dev *rxe, struct rxe_srq *srq, udata, mi, &srq->rq.producer_lock, &srq->rq.consumer_lock); if (err) - goto err_free; + return err; srq->rq.max_wr = attr->max_wr; } @@ -180,11 +180,6 @@ int rxe_srq_from_attr(struct rxe_dev *rxe, struct rxe_srq *srq, srq->limit = attr->srq_limit; return 0; - -err_free: - rxe_queue_cleanup(q); - srq->rq.queue = NULL; - return err; } void rxe_srq_cleanup(struct rxe_pool_elem *elem) From 479a71edb7b36eeaafbe8333a901124e2fe92c27 Mon Sep 17 00:00:00 2001 From: Wolfram Sang Date: Mon, 29 Sep 2025 11:36:02 +0200 Subject: [PATCH 0499/3902] ARM: dts: renesas: gose: Remove superfluous port property [ Upstream commit 00df14f34615630f92f97c9d6790bd9d25c4242d ] 'bus-width' is defined for the corresponding vin input port already. No need to declare it in the output port again. Fixes: arch/arm/boot/dts/renesas/r8a7793-gose.dtb: composite-in@20 (adi,adv7180cp): ports:port@3:endpoint: Unevaluated properties are not allowed ('bus-width' was unexpected) from schema $id: http://devicetree.org/schemas/media/i2c/adi,adv7180.yaml# Signed-off-by: Wolfram Sang Reviewed-by: Geert Uytterhoeven Link: https://patch.msgid.link/20250929093616.17679-2-wsa+renesas@sang-engineering.com Signed-off-by: Geert Uytterhoeven Signed-off-by: Sasha Levin --- arch/arm/boot/dts/renesas/r8a7793-gose.dts | 1 - 1 file changed, 1 deletion(-) diff --git a/arch/arm/boot/dts/renesas/r8a7793-gose.dts b/arch/arm/boot/dts/renesas/r8a7793-gose.dts index 45b267ec267943..5c6928c941aca9 100644 --- a/arch/arm/boot/dts/renesas/r8a7793-gose.dts +++ b/arch/arm/boot/dts/renesas/r8a7793-gose.dts @@ -373,7 +373,6 @@ port@3 { reg = <3>; adv7180_out: endpoint { - bus-width = <8>; remote-endpoint = <&vin1ep>; }; }; From ee3ff9742a44d9ec040d7aef9ff505d76e095ffd Mon Sep 17 00:00:00 2001 From: Wolfram Sang Date: Tue, 7 Oct 2025 12:46:25 +0200 Subject: [PATCH 0500/3902] ARM: dts: renesas: r9a06g032-rzn1d400-db: Drop invalid #cells properties [ Upstream commit ca7fffb6e92a7c93604ea2bae0e1c89b20750937 ] The 'ethernet-ports' node in the SoC DTSI handles them already. Fixes: arch/arm/boot/dts/renesas/r9a06g032-rzn1d400-db.dtb: switch@44050000 (renesas,r9a06g032-a5psw): Unevaluated properties are not allowed ('#address-cells', '#size-cells' were unexpected) from schema $id: http://devicetree.org/schemas/net/dsa/renesas,rzn1-a5psw.yaml# Fixes: 5b6d7c3c5861ad4a ("ARM: dts: r9a06g032-rzn1d400-db: Add switch description") Signed-off-by: Wolfram Sang Reviewed-by: Geert Uytterhoeven Link: https://patch.msgid.link/20251007104624.19786-2-wsa+renesas@sang-engineering.com Signed-off-by: Geert Uytterhoeven Signed-off-by: Sasha Levin --- arch/arm/boot/dts/renesas/r9a06g032-rzn1d400-db.dts | 2 -- 1 file changed, 2 deletions(-) diff --git a/arch/arm/boot/dts/renesas/r9a06g032-rzn1d400-db.dts b/arch/arm/boot/dts/renesas/r9a06g032-rzn1d400-db.dts index 3258b2e274346d..4a72aa7663f252 100644 --- a/arch/arm/boot/dts/renesas/r9a06g032-rzn1d400-db.dts +++ b/arch/arm/boot/dts/renesas/r9a06g032-rzn1d400-db.dts @@ -308,8 +308,6 @@ &switch { status = "okay"; - #address-cells = <1>; - #size-cells = <0>; pinctrl-names = "default"; pinctrl-0 = <&pins_eth3>, <&pins_eth4>, <&pins_mdio1>; From 77ac34b838e2093d498e6dc9f5944038ba83c06d Mon Sep 17 00:00:00 2001 From: Prike Liang Date: Mon, 29 Sep 2025 13:52:13 +0800 Subject: [PATCH 0501/3902] drm/amdgpu: add userq object va track helpers [ Upstream commit 5cfa33fabf01f2cc0af6b1feed6e65cb81806a37 ] Add the userq object virtual address list_add() helpers for tracking the userq obj va address usage. Signed-off-by: Prike Liang Reviewed-by: Alex Deucher Signed-off-by: Alex Deucher Stable-dep-of: a0559012a18a ("drm/amdgpu/userq: fix SDMA and compute validation") Signed-off-by: Sasha Levin --- drivers/gpu/drm/amd/amdgpu/amdgpu_object.h | 1 + drivers/gpu/drm/amd/amdgpu/amdgpu_userq.c | 44 ++++++++++++++++------ drivers/gpu/drm/amd/amdgpu/amdgpu_userq.h | 12 ++++-- drivers/gpu/drm/amd/amdgpu/mes_userqueue.c | 16 ++++---- 4 files changed, 52 insertions(+), 21 deletions(-) diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_object.h b/drivers/gpu/drm/amd/amdgpu/amdgpu_object.h index 656b8a931dae85..52c2d1731aabf6 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_object.h +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_object.h @@ -96,6 +96,7 @@ struct amdgpu_bo_va { * if non-zero, cannot unmap from GPU because user queues may still access it */ unsigned int queue_refcount; + atomic_t userq_va_mapped; }; struct amdgpu_bo { diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_userq.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_userq.c index 1add21160d2187..79c7fa0a9ff7bb 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_userq.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_userq.c @@ -44,10 +44,29 @@ u32 amdgpu_userq_get_supported_ip_mask(struct amdgpu_device *adev) return userq_ip_mask; } -int amdgpu_userq_input_va_validate(struct amdgpu_vm *vm, u64 addr, - u64 expected_size) +static int amdgpu_userq_buffer_va_list_add(struct amdgpu_usermode_queue *queue, + struct amdgpu_bo_va_mapping *va_map, u64 addr) +{ + struct amdgpu_userq_va_cursor *va_cursor; + struct userq_va_list; + + va_cursor = kzalloc(sizeof(*va_cursor), GFP_KERNEL); + if (!va_cursor) + return -ENOMEM; + + INIT_LIST_HEAD(&va_cursor->list); + va_cursor->gpu_addr = addr; + atomic_set(&va_map->bo_va->userq_va_mapped, 1); + list_add(&va_cursor->list, &queue->userq_va_list); + + return 0; +} + +int amdgpu_userq_input_va_validate(struct amdgpu_usermode_queue *queue, + u64 addr, u64 expected_size) { struct amdgpu_bo_va_mapping *va_map; + struct amdgpu_vm *vm = queue->vm; u64 user_addr; u64 size; int r = 0; @@ -67,6 +86,7 @@ int amdgpu_userq_input_va_validate(struct amdgpu_vm *vm, u64 addr, /* Only validate the userq whether resident in the VM mapping range */ if (user_addr >= va_map->start && va_map->last - user_addr + 1 >= size) { + amdgpu_userq_buffer_va_list_add(queue, va_map, user_addr); amdgpu_bo_unreserve(vm->root.bo); return 0; } @@ -185,6 +205,7 @@ amdgpu_userq_cleanup(struct amdgpu_userq_mgr *uq_mgr, uq_funcs->mqd_destroy(uq_mgr, queue); amdgpu_userq_fence_driver_free(queue); idr_remove(&uq_mgr->userq_idr, queue_id); + list_del(&queue->userq_va_list); kfree(queue); } @@ -505,14 +526,7 @@ amdgpu_userq_create(struct drm_file *filp, union drm_amdgpu_userq *args) goto unlock; } - /* Validate the userq virtual address.*/ - if (amdgpu_userq_input_va_validate(&fpriv->vm, args->in.queue_va, args->in.queue_size) || - amdgpu_userq_input_va_validate(&fpriv->vm, args->in.rptr_va, AMDGPU_GPU_PAGE_SIZE) || - amdgpu_userq_input_va_validate(&fpriv->vm, args->in.wptr_va, AMDGPU_GPU_PAGE_SIZE)) { - r = -EINVAL; - kfree(queue); - goto unlock; - } + INIT_LIST_HEAD(&queue->userq_va_list); queue->doorbell_handle = args->in.doorbell_handle; queue->queue_type = args->in.ip_type; queue->vm = &fpriv->vm; @@ -523,6 +537,15 @@ amdgpu_userq_create(struct drm_file *filp, union drm_amdgpu_userq *args) db_info.db_obj = &queue->db_obj; db_info.doorbell_offset = args->in.doorbell_offset; + /* Validate the userq virtual address.*/ + if (amdgpu_userq_input_va_validate(queue, args->in.queue_va, args->in.queue_size) || + amdgpu_userq_input_va_validate(queue, args->in.rptr_va, AMDGPU_GPU_PAGE_SIZE) || + amdgpu_userq_input_va_validate(queue, args->in.wptr_va, AMDGPU_GPU_PAGE_SIZE)) { + r = -EINVAL; + kfree(queue); + goto unlock; + } + /* Convert relative doorbell offset into absolute doorbell index */ index = amdgpu_userq_get_doorbell_index(uq_mgr, &db_info, filp); if (index == (uint64_t)-EINVAL) { @@ -548,7 +571,6 @@ amdgpu_userq_create(struct drm_file *filp, union drm_amdgpu_userq *args) goto unlock; } - qid = idr_alloc(&uq_mgr->userq_idr, queue, 1, AMDGPU_MAX_USERQ_COUNT, GFP_KERNEL); if (qid < 0) { drm_file_err(uq_mgr->file, "Failed to allocate a queue id\n"); diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_userq.h b/drivers/gpu/drm/amd/amdgpu/amdgpu_userq.h index c027dd9166727f..dbc13a807ca821 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_userq.h +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_userq.h @@ -47,6 +47,11 @@ struct amdgpu_userq_obj { struct amdgpu_bo *obj; }; +struct amdgpu_userq_va_cursor { + u64 gpu_addr; + struct list_head list; +}; + struct amdgpu_usermode_queue { int queue_type; enum amdgpu_userq_state state; @@ -66,6 +71,8 @@ struct amdgpu_usermode_queue { u32 xcp_id; int priority; struct dentry *debugfs_queue; + + struct list_head userq_va_list; }; struct amdgpu_userq_funcs { @@ -136,7 +143,6 @@ int amdgpu_userq_stop_sched_for_enforce_isolation(struct amdgpu_device *adev, u32 idx); int amdgpu_userq_start_sched_for_enforce_isolation(struct amdgpu_device *adev, u32 idx); - -int amdgpu_userq_input_va_validate(struct amdgpu_vm *vm, u64 addr, - u64 expected_size); +int amdgpu_userq_input_va_validate(struct amdgpu_usermode_queue *queue, + u64 addr, u64 expected_size); #endif diff --git a/drivers/gpu/drm/amd/amdgpu/mes_userqueue.c b/drivers/gpu/drm/amd/amdgpu/mes_userqueue.c index 1cd9eaeef38f97..5c63480dda9c44 100644 --- a/drivers/gpu/drm/amd/amdgpu/mes_userqueue.c +++ b/drivers/gpu/drm/amd/amdgpu/mes_userqueue.c @@ -298,8 +298,9 @@ static int mes_userq_mqd_create(struct amdgpu_userq_mgr *uq_mgr, goto free_mqd; } - if (amdgpu_userq_input_va_validate(queue->vm, compute_mqd->eop_va, - max_t(u32, PAGE_SIZE, AMDGPU_GPU_PAGE_SIZE))) + r = amdgpu_userq_input_va_validate(queue, compute_mqd->eop_va, + max_t(u32, PAGE_SIZE, AMDGPU_GPU_PAGE_SIZE)); + if (r) goto free_mqd; userq_props->eop_gpu_addr = compute_mqd->eop_va; @@ -330,8 +331,9 @@ static int mes_userq_mqd_create(struct amdgpu_userq_mgr *uq_mgr, userq_props->tmz_queue = mqd_user->flags & AMDGPU_USERQ_CREATE_FLAGS_QUEUE_SECURE; - if (amdgpu_userq_input_va_validate(queue->vm, mqd_gfx_v11->shadow_va, - shadow_info.shadow_size)) + r = amdgpu_userq_input_va_validate(queue, mqd_gfx_v11->shadow_va, + shadow_info.shadow_size); + if (r) goto free_mqd; kfree(mqd_gfx_v11); @@ -350,9 +352,9 @@ static int mes_userq_mqd_create(struct amdgpu_userq_mgr *uq_mgr, r = -ENOMEM; goto free_mqd; } - - if (amdgpu_userq_input_va_validate(queue->vm, mqd_sdma_v11->csa_va, - shadow_info.csa_size)) + r = amdgpu_userq_input_va_validate(queue, mqd_sdma_v11->csa_va, + shadow_info.csa_size); + if (r) goto free_mqd; userq_props->csa_addr = mqd_sdma_v11->csa_va; From 3e3cb76251769af696907e83a2101676eaac0044 Mon Sep 17 00:00:00 2001 From: Alex Deucher Date: Fri, 10 Oct 2025 15:21:02 -0400 Subject: [PATCH 0502/3902] drm/amdgpu/userq: fix SDMA and compute validation [ Upstream commit a0559012a18a5a6ad87516e982892765a403b8ab ] The CSA and EOP buffers have different alignement requirements. Hardcode them for now as a bug fix. A proper query will be added in a subsequent patch. v2: verify gfx shadow helper callback (Prike) Fixes: 9e46b8bb0539 ("drm/amdgpu: validate userq buffer virtual address and size") Reviewed-by: Prike Liang Signed-off-by: Alex Deucher Signed-off-by: Sasha Levin --- drivers/gpu/drm/amd/amdgpu/mes_userqueue.c | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/drivers/gpu/drm/amd/amdgpu/mes_userqueue.c b/drivers/gpu/drm/amd/amdgpu/mes_userqueue.c index 5c63480dda9c44..f5aa83ff57f351 100644 --- a/drivers/gpu/drm/amd/amdgpu/mes_userqueue.c +++ b/drivers/gpu/drm/amd/amdgpu/mes_userqueue.c @@ -254,7 +254,6 @@ static int mes_userq_mqd_create(struct amdgpu_userq_mgr *uq_mgr, struct amdgpu_mqd *mqd_hw_default = &adev->mqds[queue->queue_type]; struct drm_amdgpu_userq_in *mqd_user = args_in; struct amdgpu_mqd_prop *userq_props; - struct amdgpu_gfx_shadow_info shadow_info; int r; /* Structure to initialize MQD for userqueue using generic MQD init function */ @@ -280,8 +279,6 @@ static int mes_userq_mqd_create(struct amdgpu_userq_mgr *uq_mgr, userq_props->doorbell_index = queue->doorbell_index; userq_props->fence_address = queue->fence_drv->gpu_addr; - if (adev->gfx.funcs->get_gfx_shadow_info) - adev->gfx.funcs->get_gfx_shadow_info(adev, &shadow_info, true); if (queue->queue_type == AMDGPU_HW_IP_COMPUTE) { struct drm_amdgpu_userq_mqd_compute_gfx11 *compute_mqd; @@ -299,7 +296,7 @@ static int mes_userq_mqd_create(struct amdgpu_userq_mgr *uq_mgr, } r = amdgpu_userq_input_va_validate(queue, compute_mqd->eop_va, - max_t(u32, PAGE_SIZE, AMDGPU_GPU_PAGE_SIZE)); + 2048); if (r) goto free_mqd; @@ -312,6 +309,14 @@ static int mes_userq_mqd_create(struct amdgpu_userq_mgr *uq_mgr, kfree(compute_mqd); } else if (queue->queue_type == AMDGPU_HW_IP_GFX) { struct drm_amdgpu_userq_mqd_gfx11 *mqd_gfx_v11; + struct amdgpu_gfx_shadow_info shadow_info; + + if (adev->gfx.funcs->get_gfx_shadow_info) { + adev->gfx.funcs->get_gfx_shadow_info(adev, &shadow_info, true); + } else { + r = -EINVAL; + goto free_mqd; + } if (mqd_user->mqd_size != sizeof(*mqd_gfx_v11) || !mqd_user->mqd) { DRM_ERROR("Invalid GFX MQD\n"); @@ -335,6 +340,10 @@ static int mes_userq_mqd_create(struct amdgpu_userq_mgr *uq_mgr, shadow_info.shadow_size); if (r) goto free_mqd; + r = amdgpu_userq_input_va_validate(queue, mqd_gfx_v11->csa_va, + shadow_info.csa_size); + if (r) + goto free_mqd; kfree(mqd_gfx_v11); } else if (queue->queue_type == AMDGPU_HW_IP_DMA) { @@ -353,7 +362,7 @@ static int mes_userq_mqd_create(struct amdgpu_userq_mgr *uq_mgr, goto free_mqd; } r = amdgpu_userq_input_va_validate(queue, mqd_sdma_v11->csa_va, - shadow_info.csa_size); + 32); if (r) goto free_mqd; From 1564c04afa92c2c7af79891d5596642384b34999 Mon Sep 17 00:00:00 2001 From: Li Qiang Date: Fri, 17 Oct 2025 12:11:28 +0800 Subject: [PATCH 0503/3902] wifi: iwlwifi: mld: add null check for kzalloc() in iwl_mld_send_proto_offload() [ Upstream commit 3df28496673bd8009f1cd3a85a63650c96e369f4 ] Add a missing NULL pointer check after kzalloc() in iwl_mld_send_proto_offload(). Without this check, a failed allocation could lead to a NULL dereference. Fixes: d1e879ec600f9 ("wifi: iwlwifi: add iwlmld sub-driver") Signed-off-by: Li Qiang Link: https://patch.msgid.link/20251017041128.1379715-1-liqiang01@kylinos.cn Signed-off-by: Miri Korenblit Signed-off-by: Sasha Levin --- drivers/net/wireless/intel/iwlwifi/mld/d3.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/drivers/net/wireless/intel/iwlwifi/mld/d3.c b/drivers/net/wireless/intel/iwlwifi/mld/d3.c index 1d4282a21f09e0..dd85be94433cc5 100644 --- a/drivers/net/wireless/intel/iwlwifi/mld/d3.c +++ b/drivers/net/wireless/intel/iwlwifi/mld/d3.c @@ -1794,6 +1794,10 @@ iwl_mld_send_proto_offload(struct iwl_mld *mld, u32 enabled = 0; cmd = kzalloc(hcmd.len[0], GFP_KERNEL); + if (!cmd) { + IWL_DEBUG_WOWLAN(mld, "Failed to allocate proto offload cmd\n"); + return -ENOMEM; + } #if IS_ENABLED(CONFIG_IPV6) struct iwl_mld_vif *mld_vif = iwl_mld_vif_from_mac80211(vif); From af76d442a543f596fd78a13f7369a20831fa1c76 Mon Sep 17 00:00:00 2001 From: Aryan Srivastava Date: Fri, 24 Oct 2025 13:19:41 +1300 Subject: [PATCH 0504/3902] Revert "mtd: rawnand: marvell: fix layouts" [ Upstream commit fbd72cb463fdea3a0c900dd5d6e813cdebc3a73c ] This reverts commit e6a30d0c48a1e8a68f1cc413bee65302ab03ddfb. This change resulted in the 8bit ECC layouts having the incorrect amount of read/write chunks, the last spare bytes chunk would always be missed. Fixes: e6a30d0c48a1 ("mtd: rawnand: marvell: fix layouts") Signed-off-by: Aryan Srivastava Signed-off-by: Miquel Raynal Signed-off-by: Sasha Levin --- drivers/mtd/nand/raw/marvell_nand.c | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/drivers/mtd/nand/raw/marvell_nand.c b/drivers/mtd/nand/raw/marvell_nand.c index 303b3016a070bd..38b7eb5b992c85 100644 --- a/drivers/mtd/nand/raw/marvell_nand.c +++ b/drivers/mtd/nand/raw/marvell_nand.c @@ -290,13 +290,16 @@ static const struct marvell_hw_ecc_layout marvell_nfc_layouts[] = { MARVELL_LAYOUT( 2048, 512, 4, 1, 1, 2048, 32, 30, 0, 0, 0), MARVELL_LAYOUT( 2048, 512, 8, 2, 1, 1024, 0, 30,1024,32, 30), MARVELL_LAYOUT( 2048, 512, 8, 2, 1, 1024, 0, 30,1024,64, 30), - MARVELL_LAYOUT( 2048, 512, 16, 4, 4, 512, 0, 30, 0, 32, 30), + MARVELL_LAYOUT( 2048, 512, 12, 3, 2, 704, 0, 30,640, 0, 30), + MARVELL_LAYOUT( 2048, 512, 16, 5, 4, 512, 0, 30, 0, 32, 30), MARVELL_LAYOUT( 4096, 512, 4, 2, 2, 2048, 32, 30, 0, 0, 0), - MARVELL_LAYOUT( 4096, 512, 8, 4, 4, 1024, 0, 30, 0, 64, 30), - MARVELL_LAYOUT( 4096, 512, 16, 8, 8, 512, 0, 30, 0, 32, 30), + MARVELL_LAYOUT( 4096, 512, 8, 5, 4, 1024, 0, 30, 0, 64, 30), + MARVELL_LAYOUT( 4096, 512, 12, 6, 5, 704, 0, 30,576, 32, 30), + MARVELL_LAYOUT( 4096, 512, 16, 9, 8, 512, 0, 30, 0, 32, 30), MARVELL_LAYOUT( 8192, 512, 4, 4, 4, 2048, 0, 30, 0, 0, 0), - MARVELL_LAYOUT( 8192, 512, 8, 8, 8, 1024, 0, 30, 0, 160, 30), - MARVELL_LAYOUT( 8192, 512, 16, 16, 16, 512, 0, 30, 0, 32, 30), + MARVELL_LAYOUT( 8192, 512, 8, 9, 8, 1024, 0, 30, 0, 160, 30), + MARVELL_LAYOUT( 8192, 512, 12, 12, 11, 704, 0, 30,448, 64, 30), + MARVELL_LAYOUT( 8192, 512, 16, 17, 16, 512, 0, 30, 0, 32, 30), }; /** From 34de3867bdfdf5bee1e9b27efa91a0aab2e5d11e Mon Sep 17 00:00:00 2001 From: Aryan Srivastava Date: Fri, 24 Oct 2025 13:19:42 +1300 Subject: [PATCH 0505/3902] mtd: nand: relax ECC parameter validation check [ Upstream commit 050553c683f21eebd7d1020df9b2ec852e2a9e4e ] Due to the custom handling and layouts of certain nand controllers this validity check will always fail for certain layouts. The check inherently depends on even chunk sizing and this is not always the case. Modify the check to only print a warning, instead of failing to init the attached NAND. This allows various 8 bit and 12 ECC strength layouts to be used. Fixes: 68c18dae6888 ("mtd: rawnand: marvell: add missing layouts") Signed-off-by: Aryan Srivastava Signed-off-by: Miquel Raynal Signed-off-by: Sasha Levin --- drivers/mtd/nand/raw/nand_base.c | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/drivers/mtd/nand/raw/nand_base.c b/drivers/mtd/nand/raw/nand_base.c index c7d9501f646b36..ad6d66309597b3 100644 --- a/drivers/mtd/nand/raw/nand_base.c +++ b/drivers/mtd/nand/raw/nand_base.c @@ -6338,11 +6338,14 @@ static int nand_scan_tail(struct nand_chip *chip) ecc->steps = mtd->writesize / ecc->size; if (!base->ecc.ctx.nsteps) base->ecc.ctx.nsteps = ecc->steps; - if (ecc->steps * ecc->size != mtd->writesize) { - WARN(1, "Invalid ECC parameters\n"); - ret = -EINVAL; - goto err_nand_manuf_cleanup; - } + + /* + * Validity check: Warn if ECC parameters are not compatible with page size. + * Due to the custom handling of ECC blocks in certain controllers the check + * may result in an expected failure. + */ + if (ecc->steps * ecc->size != mtd->writesize) + pr_warn("ECC parameters may be invalid in reference to underlying NAND chip\n"); if (!ecc->total) { ecc->total = ecc->steps * ecc->bytes; From 8f4e27e870960cdc102e3e9ad89517f3c42ab8ae Mon Sep 17 00:00:00 2001 From: Haotian Zhang Date: Tue, 28 Oct 2025 17:47:47 +0800 Subject: [PATCH 0506/3902] mtd: rawnand: lpc32xx_slc: fix GPIO descriptor leak on probe error and remove [ Upstream commit cdf44f1add4ec9ee80569d5a43e6e9bba0d74c7a ] The driver calls gpiod_get_optional() in the probe function but never calls gpiod_put() in the remove function or in the probe error path. This leads to a GPIO descriptor resource leak. The lpc32xx_mlc.c driver in the same directory handles this correctly by calling gpiod_put() on both paths. Add gpiod_put() in the remove function and in the probe error path to fix the resource leak. Fixes: 6b923db2867c ("mtd: rawnand: lpc32xx_slc: switch to using gpiod API") Signed-off-by: Haotian Zhang Signed-off-by: Miquel Raynal Signed-off-by: Sasha Levin --- drivers/mtd/nand/raw/lpc32xx_slc.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/drivers/mtd/nand/raw/lpc32xx_slc.c b/drivers/mtd/nand/raw/lpc32xx_slc.c index b54d76547ffb29..fea3705a21386c 100644 --- a/drivers/mtd/nand/raw/lpc32xx_slc.c +++ b/drivers/mtd/nand/raw/lpc32xx_slc.c @@ -937,6 +937,7 @@ static int lpc32xx_nand_probe(struct platform_device *pdev) dma_release_channel(host->dma_chan); enable_wp: lpc32xx_wp_enable(host); + gpiod_put(host->wp_gpio); return res; } @@ -962,6 +963,7 @@ static void lpc32xx_nand_remove(struct platform_device *pdev) writel(tmp, SLC_CTRL(host->io_base)); lpc32xx_wp_enable(host); + gpiod_put(host->wp_gpio); } static int lpc32xx_nand_resume(struct platform_device *pdev) From 8fc78e468f9e4dfaa278a84d0b349e2f773aa19b Mon Sep 17 00:00:00 2001 From: Arnaud Lecomte Date: Sat, 25 Oct 2025 19:28:58 +0000 Subject: [PATCH 0507/3902] bpf: Refactor stack map trace depth calculation into helper function [ Upstream commit e17d62fedd10ae56e2426858bd0757da544dbc73 ] Extract the duplicated maximum allowed depth computation for stack traces stored in BPF stacks from bpf_get_stackid() and __bpf_get_stack() into a dedicated stack_map_calculate_max_depth() helper function. This unifies the logic for: - The max depth computation - Enforcing the sysctl_perf_event_max_stack limit No functional changes for existing code paths. Signed-off-by: Arnaud Lecomte Signed-off-by: Andrii Nakryiko Acked-by: Yonghong Song Acked-by: Song Liu Link: https://lore.kernel.org/bpf/20251025192858.31424-1-contact@arnaud-lcm.com Stable-dep-of: 23f852daa4ba ("bpf: Fix stackmap overflow check in __bpf_get_stackid()") Signed-off-by: Sasha Levin --- kernel/bpf/stackmap.c | 47 +++++++++++++++++++++++++++++-------------- 1 file changed, 32 insertions(+), 15 deletions(-) diff --git a/kernel/bpf/stackmap.c b/kernel/bpf/stackmap.c index 4d53cdd1374cf7..5e9ad050333c25 100644 --- a/kernel/bpf/stackmap.c +++ b/kernel/bpf/stackmap.c @@ -42,6 +42,28 @@ static inline int stack_map_data_size(struct bpf_map *map) sizeof(struct bpf_stack_build_id) : sizeof(u64); } +/** + * stack_map_calculate_max_depth - Calculate maximum allowed stack trace depth + * @size: Size of the buffer/map value in bytes + * @elem_size: Size of each stack trace element + * @flags: BPF stack trace flags (BPF_F_USER_STACK, BPF_F_USER_BUILD_ID, ...) + * + * Return: Maximum number of stack trace entries that can be safely stored + */ +static u32 stack_map_calculate_max_depth(u32 size, u32 elem_size, u64 flags) +{ + u32 skip = flags & BPF_F_SKIP_FIELD_MASK; + u32 max_depth; + u32 curr_sysctl_max_stack = READ_ONCE(sysctl_perf_event_max_stack); + + max_depth = size / elem_size; + max_depth += skip; + if (max_depth > curr_sysctl_max_stack) + return curr_sysctl_max_stack; + + return max_depth; +} + static int prealloc_elems_and_freelist(struct bpf_stack_map *smap) { u64 elem_size = sizeof(struct stack_map_bucket) + @@ -300,20 +322,17 @@ static long __bpf_get_stackid(struct bpf_map *map, BPF_CALL_3(bpf_get_stackid, struct pt_regs *, regs, struct bpf_map *, map, u64, flags) { - u32 max_depth = map->value_size / stack_map_data_size(map); - u32 skip = flags & BPF_F_SKIP_FIELD_MASK; + u32 elem_size = stack_map_data_size(map); bool user = flags & BPF_F_USER_STACK; struct perf_callchain_entry *trace; bool kernel = !user; + u32 max_depth; if (unlikely(flags & ~(BPF_F_SKIP_FIELD_MASK | BPF_F_USER_STACK | BPF_F_FAST_STACK_CMP | BPF_F_REUSE_STACKID))) return -EINVAL; - max_depth += skip; - if (max_depth > sysctl_perf_event_max_stack) - max_depth = sysctl_perf_event_max_stack; - + max_depth = stack_map_calculate_max_depth(map->value_size, elem_size, flags); trace = get_perf_callchain(regs, kernel, user, max_depth, false, false); @@ -406,7 +425,7 @@ static long __bpf_get_stack(struct pt_regs *regs, struct task_struct *task, struct perf_callchain_entry *trace_in, void *buf, u32 size, u64 flags, bool may_fault) { - u32 trace_nr, copy_len, elem_size, num_elem, max_depth; + u32 trace_nr, copy_len, elem_size, max_depth; bool user_build_id = flags & BPF_F_USER_BUILD_ID; bool crosstask = task && task != current; u32 skip = flags & BPF_F_SKIP_FIELD_MASK; @@ -438,21 +457,20 @@ static long __bpf_get_stack(struct pt_regs *regs, struct task_struct *task, goto clear; } - num_elem = size / elem_size; - max_depth = num_elem + skip; - if (sysctl_perf_event_max_stack < max_depth) - max_depth = sysctl_perf_event_max_stack; + max_depth = stack_map_calculate_max_depth(size, elem_size, flags); if (may_fault) rcu_read_lock(); /* need RCU for perf's callchain below */ - if (trace_in) + if (trace_in) { trace = trace_in; - else if (kernel && task) + trace->nr = min_t(u32, trace->nr, max_depth); + } else if (kernel && task) { trace = get_callchain_entry_for_task(task, max_depth); - else + } else { trace = get_perf_callchain(regs, kernel, user, max_depth, crosstask, false); + } if (unlikely(!trace) || trace->nr < skip) { if (may_fault) @@ -461,7 +479,6 @@ static long __bpf_get_stack(struct pt_regs *regs, struct task_struct *task, } trace_nr = trace->nr - skip; - trace_nr = (trace_nr <= num_elem) ? trace_nr : num_elem; copy_len = trace_nr * elem_size; ips = trace->ip + skip; From 4669a8db976c8cbd5427fe9945f12c5fa5168ff3 Mon Sep 17 00:00:00 2001 From: Arnaud Lecomte Date: Sat, 25 Oct 2025 19:29:41 +0000 Subject: [PATCH 0508/3902] bpf: Fix stackmap overflow check in __bpf_get_stackid() [ Upstream commit 23f852daa4bab4d579110e034e4d513f7d490846 ] Syzkaller reported a KASAN slab-out-of-bounds write in __bpf_get_stackid() when copying stack trace data. The issue occurs when the perf trace contains more stack entries than the stack map bucket can hold, leading to an out-of-bounds write in the bucket's data array. Fixes: ee2a098851bf ("bpf: Adjust BPF stack helper functions to accommodate skip > 0") Reported-by: syzbot+c9b724fbb41cf2538b7b@syzkaller.appspotmail.com Signed-off-by: Arnaud Lecomte Signed-off-by: Andrii Nakryiko Acked-by: Yonghong Song Acked-by: Song Liu Link: https://lore.kernel.org/bpf/20251025192941.1500-1-contact@arnaud-lcm.com Closes: https://syzkaller.appspot.com/bug?extid=c9b724fbb41cf2538b7b Signed-off-by: Sasha Levin --- kernel/bpf/stackmap.c | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/kernel/bpf/stackmap.c b/kernel/bpf/stackmap.c index 5e9ad050333c25..2365541c81dd14 100644 --- a/kernel/bpf/stackmap.c +++ b/kernel/bpf/stackmap.c @@ -251,8 +251,8 @@ static long __bpf_get_stackid(struct bpf_map *map, { struct bpf_stack_map *smap = container_of(map, struct bpf_stack_map, map); struct stack_map_bucket *bucket, *new_bucket, *old_bucket; + u32 hash, id, trace_nr, trace_len, i, max_depth; u32 skip = flags & BPF_F_SKIP_FIELD_MASK; - u32 hash, id, trace_nr, trace_len, i; bool user = flags & BPF_F_USER_STACK; u64 *ips; bool hash_matches; @@ -261,7 +261,8 @@ static long __bpf_get_stackid(struct bpf_map *map, /* skipping more than usable stack trace */ return -EFAULT; - trace_nr = trace->nr - skip; + max_depth = stack_map_calculate_max_depth(map->value_size, stack_map_data_size(map), flags); + trace_nr = min_t(u32, trace->nr - skip, max_depth - skip); trace_len = trace_nr * sizeof(u64); ips = trace->ip + skip; hash = jhash2((u32 *)ips, trace_len / sizeof(u32), 0); @@ -390,15 +391,11 @@ BPF_CALL_3(bpf_get_stackid_pe, struct bpf_perf_event_data_kern *, ctx, return -EFAULT; nr_kernel = count_kernel_ip(trace); + __u64 nr = trace->nr; /* save original */ if (kernel) { - __u64 nr = trace->nr; - trace->nr = nr_kernel; ret = __bpf_get_stackid(map, trace, flags); - - /* restore nr */ - trace->nr = nr; } else { /* user */ u64 skip = flags & BPF_F_SKIP_FIELD_MASK; @@ -409,6 +406,10 @@ BPF_CALL_3(bpf_get_stackid_pe, struct bpf_perf_event_data_kern *, ctx, flags = (flags & ~BPF_F_SKIP_FIELD_MASK) | skip; ret = __bpf_get_stackid(map, trace, flags); } + + /* restore nr */ + trace->nr = nr; + return ret; } From 75446d62696c9cf486074e49844922e1de732381 Mon Sep 17 00:00:00 2001 From: Zhang Rui Date: Thu, 23 Oct 2025 15:37:52 -0700 Subject: [PATCH 0509/3902] perf/x86/intel/cstate: Remove PC3 support from LunarLake [ Upstream commit 4ba45f041abe60337fdeeb68553b9ee1217d544e ] LunarLake doesn't support Package C3. Remove the PC3 residency counter support from LunarLake. Fixes: 26579860fbd5 ("perf/x86/intel/cstate: Add Lunarlake support") Signed-off-by: Zhang Rui Signed-off-by: Peter Zijlstra (Intel) Reviewed-by: Kan Liang Reviewed-by: Dapeng Mi Link: https://patch.msgid.link/20251023223754.1743928-3-zide.chen@intel.com Signed-off-by: Sasha Levin --- arch/x86/events/intel/cstate.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/arch/x86/events/intel/cstate.c b/arch/x86/events/intel/cstate.c index ec753e39b00778..6f5286a99e0c3c 100644 --- a/arch/x86/events/intel/cstate.c +++ b/arch/x86/events/intel/cstate.c @@ -70,7 +70,7 @@ * perf code: 0x01 * Available model: NHM,WSM,SNB,IVB,HSW,BDW,SKL,KNL, * GLM,CNL,KBL,CML,ICL,TGL,TNT,RKL, - * ADL,RPL,MTL,ARL,LNL + * ADL,RPL,MTL,ARL * Scope: Package (physical package) * MSR_PKG_C6_RESIDENCY: Package C6 Residency Counter. * perf code: 0x02 @@ -522,7 +522,6 @@ static const struct cstate_model lnl_cstates __initconst = { BIT(PERF_CSTATE_CORE_C7_RES), .pkg_events = BIT(PERF_CSTATE_PKG_C2_RES) | - BIT(PERF_CSTATE_PKG_C3_RES) | BIT(PERF_CSTATE_PKG_C6_RES) | BIT(PERF_CSTATE_PKG_C10_RES), }; From 5b56a123422330e826184b5b78fb94cefc45c8a4 Mon Sep 17 00:00:00 2001 From: Peter Zijlstra Date: Mon, 22 Sep 2025 15:47:00 +0200 Subject: [PATCH 0510/3902] task_work: Fix NMI race condition [ Upstream commit ef1ea98c8fffe227e5319215d84a53fa2a4bcebc ] __schedule() // disable irqs task_work_add(current, work, TWA_NMI_CURRENT); // current = next; // enable irqs task_work_set_notify_irq() test_and_set_tsk_thread_flag(current, TIF_NOTIFY_RESUME); // wrong task! // original task skips task work on its next return to user (or exit!) Fixes: 466e4d801cd4 ("task_work: Add TWA_NMI_CURRENT as an additional notify mode.") Reported-by: Josh Poimboeuf Signed-off-by: Peter Zijlstra (Intel) Reviewed-by: Steven Rostedt (Google) Link: https://patch.msgid.link/20250924080118.425949403@infradead.org Signed-off-by: Sasha Levin --- kernel/task_work.c | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/kernel/task_work.c b/kernel/task_work.c index d1efec571a4a47..0f7519f8e7c93f 100644 --- a/kernel/task_work.c +++ b/kernel/task_work.c @@ -9,7 +9,12 @@ static struct callback_head work_exited; /* all we need is ->next == NULL */ #ifdef CONFIG_IRQ_WORK static void task_work_set_notify_irq(struct irq_work *entry) { - test_and_set_tsk_thread_flag(current, TIF_NOTIFY_RESUME); + /* + * no-op IPI + * + * TWA_NMI_CURRENT will already have set the TIF flag, all + * this interrupt does it tickle the return-to-user path. + */ } static DEFINE_PER_CPU(struct irq_work, irq_work_NMI_resume) = IRQ_WORK_INIT_HARD(task_work_set_notify_irq); @@ -86,6 +91,7 @@ int task_work_add(struct task_struct *task, struct callback_head *work, break; #ifdef CONFIG_IRQ_WORK case TWA_NMI_CURRENT: + set_tsk_thread_flag(current, TIF_NOTIFY_RESUME); irq_work_queue(this_cpu_ptr(&irq_work_NMI_resume)); break; #endif From 390a08dd68e647606afddd7187643ff8b043daf8 Mon Sep 17 00:00:00 2001 From: Marek Vasut Date: Wed, 29 Oct 2025 00:28:11 +0100 Subject: [PATCH 0511/3902] drm/rcar-du: dsi: Fix missing parameter in RXSETR_...EN macros [ Upstream commit 42bbf82b73bda18bbc3e158cb6fda040bb586feb ] The RXSETR_CRCEN(n) and RXSETR_ECCEN(n) macros both take parameter (n), add the missing macro parameter. Neither of those macros is used by the driver, so for now the bug is harmless. Fixes: 685e8dae19df ("drm/rcar-du: dsi: Implement DSI command support") Reviewed-by: Laurent Pinchart Reviewed-by: Tomi Valkeinen Signed-off-by: Marek Vasut Reported-by: Laurent Pinchart Link: https://patch.msgid.link/20251028232959.109936-2-marek.vasut+renesas@mailbox.org Signed-off-by: Tomi Valkeinen Signed-off-by: Sasha Levin --- drivers/gpu/drm/renesas/rcar-du/rcar_mipi_dsi_regs.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/gpu/drm/renesas/rcar-du/rcar_mipi_dsi_regs.h b/drivers/gpu/drm/renesas/rcar-du/rcar_mipi_dsi_regs.h index 76521276e2af8e..dd871e17dcf535 100644 --- a/drivers/gpu/drm/renesas/rcar-du/rcar_mipi_dsi_regs.h +++ b/drivers/gpu/drm/renesas/rcar-du/rcar_mipi_dsi_regs.h @@ -50,8 +50,8 @@ #define TXCMPPD3R 0x16c #define RXSETR 0x200 -#define RXSETR_CRCEN (((n) & 0xf) << 24) -#define RXSETR_ECCEN (((n) & 0xf) << 16) +#define RXSETR_CRCEN(n) (((n) & 0xf) << 24) +#define RXSETR_ECCEN(n) (((n) & 0xf) << 16) #define RXPSETR 0x210 #define RXPSETR_LPPDACC (1 << 0) #define RXPSR 0x220 From d53bfb48f6d01d8af988a01549cce52b9a5719ae Mon Sep 17 00:00:00 2001 From: Tengda Wu Date: Thu, 23 Oct 2025 09:06:32 +0000 Subject: [PATCH 0512/3902] x86/dumpstack: Prevent KASAN false positive warnings in __show_regs() [ Upstream commit ced37e9ceae50e4cb6cd058963bd315ec9afa651 ] When triggering a stack dump via sysrq (echo t > /proc/sysrq-trigger), KASAN may report false-positive out-of-bounds access: BUG: KASAN: out-of-bounds in __show_regs+0x4b/0x340 Call Trace: dump_stack_lvl print_address_description.constprop.0 print_report __show_regs show_trace_log_lvl sched_show_task show_state_filter sysrq_handle_showstate __handle_sysrq write_sysrq_trigger proc_reg_write vfs_write ksys_write do_syscall_64 entry_SYSCALL_64_after_hwframe The issue occurs as follows: Task A (walk other tasks' stacks) Task B (running) 1. echo t > /proc/sysrq-trigger show_trace_log_lvl regs = unwind_get_entry_regs() show_regs_if_on_stack(regs) 2. The stack value pointed by `regs` keeps changing, and so are the tags in its KASAN shadow region. __show_regs(regs) regs->ax, regs->bx, ... 3. hit KASAN redzones, OOB When task A walks task B's stack without suspending it, the continuous changes in task B's stack (and corresponding KASAN shadow tags) may cause task A to hit KASAN redzones when accessing obsolete values on the stack, resulting in false positive reports. Simply stopping the task before unwinding is not a viable fix, as it would alter the state intended to inspect. This is especially true for diagnosing misbehaving tasks (e.g., in a hard lockup), where stopping might fail or hide the root cause by changing the call stack. Therefore, fix this by disabling KASAN checks during asynchronous stack unwinding, which is identified when the unwinding task does not match the current task (task != current). [ bp: Align arguments on function's opening brace. ] Fixes: 3b3fa11bc700 ("x86/dumpstack: Print any pt_regs found on the stack") Signed-off-by: Tengda Wu Signed-off-by: Borislav Petkov (AMD) Reviewed-by: Andrey Ryabinin Acked-by: Josh Poimboeuf Link: https://patch.msgid.link/all/20251023090632.269121-1-wutengda@huaweicloud.com Signed-off-by: Sasha Levin --- arch/x86/kernel/dumpstack.c | 23 +++++++++++++++++++++-- 1 file changed, 21 insertions(+), 2 deletions(-) diff --git a/arch/x86/kernel/dumpstack.c b/arch/x86/kernel/dumpstack.c index 71ee20102a8afb..b10684dedc589f 100644 --- a/arch/x86/kernel/dumpstack.c +++ b/arch/x86/kernel/dumpstack.c @@ -181,8 +181,8 @@ static void show_regs_if_on_stack(struct stack_info *info, struct pt_regs *regs, * in false positive reports. Disable instrumentation to avoid those. */ __no_kmsan_checks -static void show_trace_log_lvl(struct task_struct *task, struct pt_regs *regs, - unsigned long *stack, const char *log_lvl) +static void __show_trace_log_lvl(struct task_struct *task, struct pt_regs *regs, + unsigned long *stack, const char *log_lvl) { struct unwind_state state; struct stack_info stack_info = {0}; @@ -303,6 +303,25 @@ static void show_trace_log_lvl(struct task_struct *task, struct pt_regs *regs, } } +static void show_trace_log_lvl(struct task_struct *task, struct pt_regs *regs, + unsigned long *stack, const char *log_lvl) +{ + /* + * Disable KASAN to avoid false positives during walking another + * task's stacks, as values on these stacks may change concurrently + * with task execution. + */ + bool disable_kasan = task && task != current; + + if (disable_kasan) + kasan_disable_current(); + + __show_trace_log_lvl(task, regs, stack, log_lvl); + + if (disable_kasan) + kasan_enable_current(); +} + void show_stack(struct task_struct *task, unsigned long *sp, const char *loglvl) { From 22da82674af6f6f625202b1e89a7781d82db7809 Mon Sep 17 00:00:00 2001 From: Maciej Falkowski Date: Mon, 27 Oct 2025 16:09:32 +0100 Subject: [PATCH 0513/3902] accel/ivpu: Remove skip of dma unmap for imported buffers [ Upstream commit c063c1bbee67391f12956d2ffdd5da00eb87ff79 ] Rework of imported buffers introduced in the commit e0c0891cd63b ("accel/ivpu: Rework bind/unbind of imported buffers") switched the logic of imported buffers by dma mapping/unmapping them just as the regular buffers. The commit didn't include removal of skipping dma unmap of imported buffers which results in them being mapped without unmapping. Fixes: e0c0891cd63b ("accel/ivpu: Rework bind/unbind of imported buffers") Reviewed-by: Jeff Hugo Reviewed-by: Karol Wachowski Signed-off-by: Maciej Falkowski Link: https://patch.msgid.link/20251027150933.2384538-1-maciej.falkowski@linux.intel.com Signed-off-by: Sasha Levin --- drivers/accel/ivpu/ivpu_gem.c | 3 --- 1 file changed, 3 deletions(-) diff --git a/drivers/accel/ivpu/ivpu_gem.c b/drivers/accel/ivpu/ivpu_gem.c index 1fca969df19dc3..a38e41f9c71239 100644 --- a/drivers/accel/ivpu/ivpu_gem.c +++ b/drivers/accel/ivpu/ivpu_gem.c @@ -157,9 +157,6 @@ static void ivpu_bo_unbind_locked(struct ivpu_bo *bo) bo->ctx = NULL; } - if (drm_gem_is_imported(&bo->base.base)) - return; - if (bo->base.sgt) { if (bo->base.base.import_attach) { dma_buf_unmap_attachment(bo->base.base.import_attach, From 35eb1ae22c79d24f35cbe401c59738081fce2f23 Mon Sep 17 00:00:00 2001 From: Benjamin Berg Date: Wed, 24 Sep 2025 16:20:50 +0200 Subject: [PATCH 0514/3902] tools/nolibc/stdio: let perror work when NOLIBC_IGNORE_ERRNO is set MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [ Upstream commit c485ca3aff2442adea4c08ceb5183e671ebed22a ] There is no errno variable when NOLIBC_IGNORE_ERRNO is defined. As such, simply print the message with "unknown error" rather than the integer value of errno. Fixes: acab7bcdb1bc ("tools/nolibc/stdio: add perror() to report the errno value") Signed-off-by: Benjamin Berg Signed-off-by: Thomas Weißschuh Signed-off-by: Sasha Levin --- tools/include/nolibc/stdio.h | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/tools/include/nolibc/stdio.h b/tools/include/nolibc/stdio.h index 7630234408c584..724d05ce69624f 100644 --- a/tools/include/nolibc/stdio.h +++ b/tools/include/nolibc/stdio.h @@ -600,7 +600,11 @@ int sscanf(const char *str, const char *format, ...) static __attribute__((unused)) void perror(const char *msg) { +#ifdef NOLIBC_IGNORE_ERRNO + fprintf(stderr, "%s%sunknown error\n", (msg && *msg) ? msg : "", (msg && *msg) ? ": " : ""); +#else fprintf(stderr, "%s%serrno=%d\n", (msg && *msg) ? msg : "", (msg && *msg) ? ": " : "", errno); +#endif } static __attribute__((unused)) From aa857ba3ef527cc16ac53ce9e851a55dda97a6a5 Mon Sep 17 00:00:00 2001 From: Benjamin Berg Date: Wed, 24 Sep 2025 16:20:51 +0200 Subject: [PATCH 0515/3902] tools/nolibc/dirent: avoid errno in readdir_r MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [ Upstream commit 4ada5679f18dbbe92d87c37a842c3368e6ab5e4a ] Using errno is not possible when NOLIBC_IGNORE_ERRNO is set. Use sys_lseek instead of lseek as that avoids using errno. Fixes: 665fa8dea90d ("tools/nolibc: add support for directory access") Signed-off-by: Benjamin Berg Signed-off-by: Thomas Weißschuh Signed-off-by: Sasha Levin --- tools/include/nolibc/dirent.h | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tools/include/nolibc/dirent.h b/tools/include/nolibc/dirent.h index 758b95c48e7a4c..61a122a60327d2 100644 --- a/tools/include/nolibc/dirent.h +++ b/tools/include/nolibc/dirent.h @@ -86,9 +86,9 @@ int readdir_r(DIR *dirp, struct dirent *entry, struct dirent **result) * readdir() can only return one entry at a time. * Make sure the non-returned ones are not skipped. */ - ret = lseek(fd, ldir->d_off, SEEK_SET); - if (ret == -1) - return errno; + ret = sys_lseek(fd, ldir->d_off, SEEK_SET); + if (ret < 0) + return -ret; entry->d_ino = ldir->d_ino; /* the destination should always be big enough */ From 4e45bd3f95fa703199b7b8e975c11318a4d818e5 Mon Sep 17 00:00:00 2001 From: Taniya Das Date: Wed, 29 Oct 2025 15:07:54 +0530 Subject: [PATCH 0516/3902] clk: qcom: gcc-qcs615: Update the SDCC clock to use shared_floor_ops [ Upstream commit 0820c9373369c83de5202871d02682d583a91a9c ] Fix "gcc_sdcc2_apps_clk_src: rcg didn't update its configuration" during boot. This happens due to the floor_ops tries to update the rcg configuration even if the clock is not enabled. The shared_floor_ops ensures that the RCG is safely parked and the new parent configuration is cached in the parked_cfg when the clock is off. Ensure to use the ops for the other SDCC clock instances as well. Fixes: 39d6dcf67fe9 ("clk: qcom: gcc: Add support for QCS615 GCC clocks") Reviewed-by: Abel Vesa Signed-off-by: Taniya Das Reviewed-by: Konrad Dybcio Link: https://lore.kernel.org/r/20251029-sdcc_rcg2_shared_ops-v3-1-ecf47d9601d1@oss.qualcomm.com Signed-off-by: Bjorn Andersson Signed-off-by: Sasha Levin --- drivers/clk/qcom/gcc-qcs615.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/drivers/clk/qcom/gcc-qcs615.c b/drivers/clk/qcom/gcc-qcs615.c index 9695446bc2a3c8..5b3b8dd4f114bd 100644 --- a/drivers/clk/qcom/gcc-qcs615.c +++ b/drivers/clk/qcom/gcc-qcs615.c @@ -784,7 +784,7 @@ static struct clk_rcg2 gcc_sdcc1_apps_clk_src = { .name = "gcc_sdcc1_apps_clk_src", .parent_data = gcc_parent_data_1, .num_parents = ARRAY_SIZE(gcc_parent_data_1), - .ops = &clk_rcg2_floor_ops, + .ops = &clk_rcg2_shared_floor_ops, }, }; @@ -806,7 +806,7 @@ static struct clk_rcg2 gcc_sdcc1_ice_core_clk_src = { .name = "gcc_sdcc1_ice_core_clk_src", .parent_data = gcc_parent_data_0, .num_parents = ARRAY_SIZE(gcc_parent_data_0), - .ops = &clk_rcg2_floor_ops, + .ops = &clk_rcg2_shared_floor_ops, }, }; @@ -830,7 +830,7 @@ static struct clk_rcg2 gcc_sdcc2_apps_clk_src = { .name = "gcc_sdcc2_apps_clk_src", .parent_data = gcc_parent_data_8, .num_parents = ARRAY_SIZE(gcc_parent_data_8), - .ops = &clk_rcg2_floor_ops, + .ops = &clk_rcg2_shared_floor_ops, }, }; From 921eed200c8f6675e91daa5df6a64fcf62faebc6 Mon Sep 17 00:00:00 2001 From: Haotian Zhang Date: Wed, 29 Oct 2025 10:27:33 +0800 Subject: [PATCH 0517/3902] soc: qcom: smem: fix hwspinlock resource leak in probe error paths [ Upstream commit dc5db35073a19f6d3c30bea367b551c1a784ef8f ] The hwspinlock acquired via hwspin_lock_request_specific() is not released on several error paths. This results in resource leakage when probe fails. Switch to devm_hwspin_lock_request_specific() to automatically handle cleanup on probe failure. Remove the manual hwspin_lock_free() in qcom_smem_remove() as devm handles it automatically. Fixes: 20bb6c9de1b7 ("soc: qcom: smem: map only partitions used by local HOST") Signed-off-by: Haotian Zhang Reviewed-by: Konrad Dybcio Link: https://lore.kernel.org/r/20251029022733.255-1-vulab@iscas.ac.cn Signed-off-by: Bjorn Andersson Signed-off-by: Sasha Levin --- drivers/soc/qcom/smem.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/drivers/soc/qcom/smem.c b/drivers/soc/qcom/smem.c index c4c45f15dca4fb..f1d1b5aa5e4db6 100644 --- a/drivers/soc/qcom/smem.c +++ b/drivers/soc/qcom/smem.c @@ -1190,7 +1190,7 @@ static int qcom_smem_probe(struct platform_device *pdev) return dev_err_probe(&pdev->dev, hwlock_id, "failed to retrieve hwlock\n"); - smem->hwlock = hwspin_lock_request_specific(hwlock_id); + smem->hwlock = devm_hwspin_lock_request_specific(&pdev->dev, hwlock_id); if (!smem->hwlock) return -ENXIO; @@ -1243,7 +1243,6 @@ static void qcom_smem_remove(struct platform_device *pdev) { platform_device_unregister(__smem->socinfo); - hwspin_lock_free(__smem->hwlock); __smem = NULL; } From 3dc8f5c1956bfa259fd3e378864f474bda762978 Mon Sep 17 00:00:00 2001 From: Haotian Zhang Date: Wed, 29 Oct 2025 09:42:52 +0800 Subject: [PATCH 0518/3902] pinctrl: stm32: fix hwspinlock resource leak in probe function [ Upstream commit 002679f79ed605e543fbace465557317cd307c9a ] In stm32_pctl_probe(), hwspin_lock_request_specific() is called to request a hwspinlock, but the acquired lock is not freed on multiple error paths after this call. This causes resource leakage when the function fails to initialize properly. Use devm_hwspin_lock_request_specific() instead of hwspin_lock_request_specific() to automatically manage the hwspinlock resource lifecycle. Fixes: 97cfb6cd34f2 ("pinctrl: stm32: protect configuration registers with a hwspinlock") Signed-off-by: Haotian Zhang Reviewed-by: Antonio Borneo Signed-off-by: Linus Walleij Signed-off-by: Sasha Levin --- drivers/pinctrl/stm32/pinctrl-stm32.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/pinctrl/stm32/pinctrl-stm32.c b/drivers/pinctrl/stm32/pinctrl-stm32.c index 3ebb468de830db..e73feb8ac90d16 100644 --- a/drivers/pinctrl/stm32/pinctrl-stm32.c +++ b/drivers/pinctrl/stm32/pinctrl-stm32.c @@ -1671,7 +1671,7 @@ int stm32_pctl_probe(struct platform_device *pdev) if (hwlock_id == -EPROBE_DEFER) return hwlock_id; } else { - pctl->hwlock = hwspin_lock_request_specific(hwlock_id); + pctl->hwlock = devm_hwspin_lock_request_specific(dev, hwlock_id); } spin_lock_init(&pctl->irqmux_lock); From 48efc0303ac98707e4d8d6308972d18be73f45ff Mon Sep 17 00:00:00 2001 From: Danilo Krummrich Date: Tue, 28 Oct 2025 12:00:53 +0100 Subject: [PATCH 0519/3902] drm: nova: select NOVA_CORE [ Upstream commit 97ad568cd6a58804129ba071f3258b5c4782fb0d ] The nova-drm driver does not provide any value without nova-core being selected as well, hence select NOVA_CORE. Fixes: cdeaeb9dd762 ("drm: nova-drm: add initial driver skeleton") Reviewed-by: Alexandre Courbot Reviewed-by: John Hubbard Link: https://patch.msgid.link/20251028110058.340320-2-dakr@kernel.org Signed-off-by: Danilo Krummrich Signed-off-by: Sasha Levin --- drivers/gpu/drm/nova/Kconfig | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/gpu/drm/nova/Kconfig b/drivers/gpu/drm/nova/Kconfig index cca6a3fea879b8..bd1df08791911f 100644 --- a/drivers/gpu/drm/nova/Kconfig +++ b/drivers/gpu/drm/nova/Kconfig @@ -4,6 +4,7 @@ config DRM_NOVA depends on PCI depends on RUST select AUXILIARY_BUS + select NOVA_CORE default n help Choose this if you want to build the Nova DRM driver for Nvidia From d71333ffdd3707d84cfb95acfaf8ba892adc066b Mon Sep 17 00:00:00 2001 From: Tomasz Rusinowicz Date: Wed, 29 Oct 2025 08:14:51 +0100 Subject: [PATCH 0520/3902] accel/ivpu: Fix race condition when unbinding BOs [ Upstream commit 00812636df370bedf4e44a0c81b86ea96bca8628 ] Fix 'Memory manager not clean during takedown' warning that occurs when ivpu_gem_bo_free() removes the BO from the BOs list before it gets unmapped. Then file_priv_unbind() triggers a warning in drm_mm_takedown() during context teardown. Protect the unmapping sequence with bo_list_lock to ensure the BO is always fully unmapped when removed from the list. This ensures the BO is either fully unmapped at context teardown time or present on the list and unmapped by file_priv_unbind(). Fixes: 48aea7f2a2ef ("accel/ivpu: Fix locking in ivpu_bo_remove_all_bos_from_context()") Signed-off-by: Tomasz Rusinowicz Reviewed-by: Jeff Hugo Signed-off-by: Karol Wachowski Link: https://patch.msgid.link/20251029071451.184243-1-karol.wachowski@linux.intel.com Signed-off-by: Sasha Levin --- drivers/accel/ivpu/ivpu_gem.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/drivers/accel/ivpu/ivpu_gem.c b/drivers/accel/ivpu/ivpu_gem.c index a38e41f9c71239..fda0a18e6d639b 100644 --- a/drivers/accel/ivpu/ivpu_gem.c +++ b/drivers/accel/ivpu/ivpu_gem.c @@ -314,7 +314,6 @@ static void ivpu_gem_bo_free(struct drm_gem_object *obj) mutex_lock(&vdev->bo_list_lock); list_del(&bo->bo_list_node); - mutex_unlock(&vdev->bo_list_lock); drm_WARN_ON(&vdev->drm, !drm_gem_is_imported(&bo->base.base) && !dma_resv_test_signaled(obj->resv, DMA_RESV_USAGE_READ)); @@ -325,6 +324,8 @@ static void ivpu_gem_bo_free(struct drm_gem_object *obj) ivpu_bo_unbind_locked(bo); ivpu_bo_unlock(bo); + mutex_unlock(&vdev->bo_list_lock); + drm_WARN_ON(&vdev->drm, bo->mmu_mapped); drm_WARN_ON(&vdev->drm, bo->ctx); From eab1b59664ee29852587a967517ce680b29ad501 Mon Sep 17 00:00:00 2001 From: Christian Brauner Date: Tue, 28 Oct 2025 09:45:48 +0100 Subject: [PATCH 0521/3902] pidfs: add missing PIDFD_INFO_SIZE_VER1 [ Upstream commit 4061c43a99772c66c378cfacaa71550ab3b35909 ] We grew struct pidfd_info not too long ago. Link: https://patch.msgid.link/20251028-work-coredump-signal-v1-3-ca449b7b7aa0@kernel.org Fixes: 1d8db6fd698d ("pidfs, coredump: add PIDFD_INFO_COREDUMP") Reviewed-by: Alexander Mikhalitsyn Reviewed-by: Oleg Nesterov Signed-off-by: Christian Brauner Signed-off-by: Sasha Levin --- include/uapi/linux/pidfd.h | 1 + 1 file changed, 1 insertion(+) diff --git a/include/uapi/linux/pidfd.h b/include/uapi/linux/pidfd.h index 957db425d459a1..6ccbabd9a68d81 100644 --- a/include/uapi/linux/pidfd.h +++ b/include/uapi/linux/pidfd.h @@ -28,6 +28,7 @@ #define PIDFD_INFO_COREDUMP (1UL << 4) /* Only returned if requested. */ #define PIDFD_INFO_SIZE_VER0 64 /* sizeof first published struct */ +#define PIDFD_INFO_SIZE_VER1 72 /* sizeof second published struct */ /* * Values for @coredump_mask in pidfd_info. From ae7a542dbab5bc5af5aa9789d27e15d3791e357f Mon Sep 17 00:00:00 2001 From: Christian Brauner Date: Tue, 28 Oct 2025 09:45:49 +0100 Subject: [PATCH 0522/3902] pidfs: add missing BUILD_BUG_ON() assert on struct pidfd_info [ Upstream commit d8fc51d8fa3b9894713e7eebcf574bee488fa3e1 ] Validate that the size of struct pidfd_info is correctly updated. Link: https://patch.msgid.link/20251028-work-coredump-signal-v1-4-ca449b7b7aa0@kernel.org Fixes: 1d8db6fd698d ("pidfs, coredump: add PIDFD_INFO_COREDUMP") Reviewed-by: Alexander Mikhalitsyn Reviewed-by: Oleg Nesterov Signed-off-by: Christian Brauner Signed-off-by: Sasha Levin --- fs/pidfs.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/fs/pidfs.c b/fs/pidfs.c index 0ef5b47d796a26..f4d7dac1b44949 100644 --- a/fs/pidfs.c +++ b/fs/pidfs.c @@ -306,6 +306,8 @@ static long pidfd_info(struct file *file, unsigned int cmd, unsigned long arg) const struct cred *c; __u64 mask; + BUILD_BUG_ON(sizeof(struct pidfd_info) != PIDFD_INFO_SIZE_VER1); + if (!uinfo) return -EINVAL; if (usize < PIDFD_INFO_SIZE_VER0) From 670b969647302b16fa88a179b1950714da18f2c5 Mon Sep 17 00:00:00 2001 From: Aniket Limaye Date: Wed, 22 Oct 2025 17:56:33 +0530 Subject: [PATCH 0523/3902] arm64: dts: ti: k3-j784s4: Fix I2C pinmux pull configuration [ Upstream commit 671c852fc53d1b6f5eccdb03c1889a484c9d1996 ] The I2C pins for some of the instances on J784S4/J742S2/AM69 are configured as PIN_INPUT_PULLUP while these pins are open-drain type and do not support internal pull-ups [0][1][2]. The pullup configuration bits in the corresponding padconfig registers are reserved and any writes to them have no effect and readback checks on those bits fail. Update the pinmux settings to use PIN_INPUT instead of PIN_INPUT_PULLUP to reflect the correct hardware behaviour. [0]: https://www.ti.com/lit/gpn/tda4ah-q1 (J784S4 Datasheet: Table 5-1. Pin Attributes) [1]: https://www.ti.com/lit/gpn/tda4ape-q1 (J742S2 Datasheet: Table 5-1. Pin Attributes) [2]: https://www.ti.com/lit/gpn/am69a (AM69 Datasheet: Table 5-1. Pin Attributes) Fixes: e20a06aca5c9 ("arm64: dts: ti: Add support for J784S4 EVM board") Fixes: 635fb18ba008 ("arch: arm64: dts: Add support for AM69 Starter Kit") Fixes: 0ec1a48d99dd ("arm64: dts: ti: k3-am69-sk: Add pinmux for RPi Header") Signed-off-by: Aniket Limaye Reviewed-by: Udit Kumar Link: https://patch.msgid.link/20251022122638.234367-1-a-limaye@ti.com Signed-off-by: Vignesh Raghavendra Signed-off-by: Sasha Levin --- arch/arm64/boot/dts/ti/k3-am69-sk.dts | 8 ++++---- arch/arm64/boot/dts/ti/k3-j784s4-j742s2-evm-common.dtsi | 4 ++-- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/arch/arm64/boot/dts/ti/k3-am69-sk.dts b/arch/arm64/boot/dts/ti/k3-am69-sk.dts index 5896e57b5b9ed6..0e2d12cb051da8 100644 --- a/arch/arm64/boot/dts/ti/k3-am69-sk.dts +++ b/arch/arm64/boot/dts/ti/k3-am69-sk.dts @@ -236,8 +236,8 @@ main_i2c0_pins_default: main-i2c0-default-pins { pinctrl-single,pins = < - J784S4_IOPAD(0x0e0, PIN_INPUT_PULLUP, 0) /* (AN36) I2C0_SCL */ - J784S4_IOPAD(0x0e4, PIN_INPUT_PULLUP, 0) /* (AP37) I2C0_SDA */ + J784S4_IOPAD(0x0e0, PIN_INPUT, 0) /* (AN36) I2C0_SCL */ + J784S4_IOPAD(0x0e4, PIN_INPUT, 0) /* (AP37) I2C0_SDA */ >; }; @@ -416,8 +416,8 @@ mcu_i2c0_pins_default: mcu-i2c0-default-pins { pinctrl-single,pins = < - J784S4_WKUP_IOPAD(0x0a0, PIN_INPUT_PULLUP, 0) /* (M35) MCU_I2C0_SCL */ - J784S4_WKUP_IOPAD(0x0a4, PIN_INPUT_PULLUP, 0) /* (G34) MCU_I2C0_SDA */ + J784S4_WKUP_IOPAD(0x0a0, PIN_INPUT, 0) /* (M35) MCU_I2C0_SCL */ + J784S4_WKUP_IOPAD(0x0a4, PIN_INPUT, 0) /* (G34) MCU_I2C0_SDA */ >; }; diff --git a/arch/arm64/boot/dts/ti/k3-j784s4-j742s2-evm-common.dtsi b/arch/arm64/boot/dts/ti/k3-j784s4-j742s2-evm-common.dtsi index 419c1a70e028d0..2834f0a8bbee05 100644 --- a/arch/arm64/boot/dts/ti/k3-j784s4-j742s2-evm-common.dtsi +++ b/arch/arm64/boot/dts/ti/k3-j784s4-j742s2-evm-common.dtsi @@ -270,8 +270,8 @@ main_i2c0_pins_default: main-i2c0-default-pins { pinctrl-single,pins = < - J784S4_IOPAD(0x0e0, PIN_INPUT_PULLUP, 0) /* (AN36) I2C0_SCL */ - J784S4_IOPAD(0x0e4, PIN_INPUT_PULLUP, 0) /* (AP37) I2C0_SDA */ + J784S4_IOPAD(0x0e0, PIN_INPUT, 0) /* (AN36) I2C0_SCL */ + J784S4_IOPAD(0x0e4, PIN_INPUT, 0) /* (AP37) I2C0_SDA */ >; }; From c704ccb558eef6704cccc2b4f8f22e09ae0ebe6f Mon Sep 17 00:00:00 2001 From: Frank Li Date: Thu, 16 Oct 2025 10:38:13 -0400 Subject: [PATCH 0524/3902] i3c: fix refcount inconsistency in i3c_master_register [ Upstream commit 9d4f219807d5ac11fb1d596e4ddb09336b040067 ] In `i3c_master_register`, a possible refcount inconsistency has been identified, causing possible resource leak. Function `of_node_get` increases the refcount of `parent->of_node`. If function `i3c_bus_init` fails, the function returns immediately without a corresponding decrease, resulting in an inconsistent refcounter. Move call i3c_bus_init() after device_initialize() to let callback i3c_masterdev_release() release of_node. Reported-by: Shuhao Fu Closes: https://lore.kernel.org/linux-i3c/aO2tjp_FsV_WohPG@osx.local/T/#m2c05a982beeb14e7bf039c1d8db856734bf234c7 Fixes: 3a379bbcea0a ("i3c: Add core I3C infrastructure") Signed-off-by: Frank Li Link: https://patch.msgid.link/20251016143814.2551256-1-Frank.Li@nxp.com Signed-off-by: Alexandre Belloni Signed-off-by: Sasha Levin --- drivers/i3c/master.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/drivers/i3c/master.c b/drivers/i3c/master.c index d946db75df7068..66513a27e6e776 100644 --- a/drivers/i3c/master.c +++ b/drivers/i3c/master.c @@ -2883,10 +2883,6 @@ int i3c_master_register(struct i3c_master_controller *master, INIT_LIST_HEAD(&master->boardinfo.i2c); INIT_LIST_HEAD(&master->boardinfo.i3c); - ret = i3c_bus_init(i3cbus, master->dev.of_node); - if (ret) - return ret; - device_initialize(&master->dev); dev_set_name(&master->dev, "i3c-%d", i3cbus->id); @@ -2894,6 +2890,10 @@ int i3c_master_register(struct i3c_master_controller *master, master->dev.coherent_dma_mask = parent->coherent_dma_mask; master->dev.dma_parms = parent->dma_parms; + ret = i3c_bus_init(i3cbus, master->dev.of_node); + if (ret) + goto err_put_dev; + ret = of_populate_i3c_bus(master); if (ret) goto err_put_dev; From 8ddff9989f06aa9a30fa8b76f3154149f76b42fd Mon Sep 17 00:00:00 2001 From: Stanley Chu Date: Mon, 27 Oct 2025 11:47:15 +0800 Subject: [PATCH 0525/3902] i3c: master: svc: Prevent incomplete IBI transaction [ Upstream commit 3a36273e5a07dda0ccec193800f3b78c3c0380af ] If no free IBI slot is available, svc_i3c_master_handle_ibi returns immediately. This causes the STOP condition to be missed because the EmitStop request is sent when the transfer is not complete. To resolve this, svc_i3c_master_handle_ibi must wait for the transfer to complete before returning. Fixes: dd3c52846d59 ("i3c: master: svc: Add Silvaco I3C master driver") Signed-off-by: Stanley Chu Reviewed-by: Frank Li Reviewed-by: Miquel Raynal Link: https://patch.msgid.link/20251027034715.708243-1-yschu@nuvoton.com Signed-off-by: Alexandre Belloni Signed-off-by: Sasha Levin --- drivers/i3c/master/svc-i3c-master.c | 22 ++++++++++++++-------- 1 file changed, 14 insertions(+), 8 deletions(-) diff --git a/drivers/i3c/master/svc-i3c-master.c b/drivers/i3c/master/svc-i3c-master.c index 9641e66a4e5f2d..e70a64f2a32fa5 100644 --- a/drivers/i3c/master/svc-i3c-master.c +++ b/drivers/i3c/master/svc-i3c-master.c @@ -406,21 +406,27 @@ static int svc_i3c_master_handle_ibi(struct svc_i3c_master *master, int ret, val; u8 *buf; - slot = i3c_generic_ibi_get_free_slot(data->ibi_pool); - if (!slot) - return -ENOSPC; - - slot->len = 0; - buf = slot->data; - + /* + * Wait for transfer to complete before returning. Otherwise, the EmitStop + * request might be sent when the transfer is not complete. + */ ret = readl_relaxed_poll_timeout(master->regs + SVC_I3C_MSTATUS, val, SVC_I3C_MSTATUS_COMPLETE(val), 0, 1000); if (ret) { dev_err(master->dev, "Timeout when polling for COMPLETE\n"); - i3c_generic_ibi_recycle_slot(data->ibi_pool, slot); return ret; } + slot = i3c_generic_ibi_get_free_slot(data->ibi_pool); + if (!slot) { + dev_dbg(master->dev, "No free ibi slot, drop the data\n"); + writel(SVC_I3C_MDATACTRL_FLUSHRB, master->regs + SVC_I3C_MDATACTRL); + return -ENOSPC; + } + + slot->len = 0; + buf = slot->data; + while (SVC_I3C_MSTATUS_RXPEND(readl(master->regs + SVC_I3C_MSTATUS)) && slot->len < SVC_I3C_FIFO_SIZE) { mdatactrl = readl(master->regs + SVC_I3C_MDATACTRL); From cbd5021199db356c6dc1928d48e3fb132dbbc9bb Mon Sep 17 00:00:00 2001 From: Arnd Bergmann Date: Tue, 10 Jun 2025 11:27:08 +0200 Subject: [PATCH 0526/3902] random: use offstack cpumask when necessary [ Upstream commit 5d49f1a5bd358d24e5f88b23b46da833de1dbec8 ] The entropy generation function keeps a local cpu mask on the stack, which can trigger warnings in configurations with a large number of CPUs: drivers/char/random.c:1292:20: error: stack frame size (1288) exceeds limit (1280) in 'try_to_generate_entropy' [-Werror,-Wframe-larger-than] Use the cpumask interface to dynamically allocate it in those configurations. Fixes: 1c21fe00eda7 ("random: spread out jitter callback to different CPUs") Signed-off-by: Arnd Bergmann Signed-off-by: Jason A. Donenfeld Signed-off-by: Sasha Levin --- drivers/char/random.c | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/drivers/char/random.c b/drivers/char/random.c index b8b24b6ed3fe43..4ba5f0c4c8b242 100644 --- a/drivers/char/random.c +++ b/drivers/char/random.c @@ -1296,6 +1296,7 @@ static void __cold try_to_generate_entropy(void) struct entropy_timer_state *stack = PTR_ALIGN((void *)stack_bytes, SMP_CACHE_BYTES); unsigned int i, num_different = 0; unsigned long last = random_get_entropy(); + cpumask_var_t timer_cpus; int cpu = -1; for (i = 0; i < NUM_TRIAL_SAMPLES - 1; ++i) { @@ -1310,13 +1311,15 @@ static void __cold try_to_generate_entropy(void) atomic_set(&stack->samples, 0); timer_setup_on_stack(&stack->timer, entropy_timer, 0); + if (!alloc_cpumask_var(&timer_cpus, GFP_KERNEL)) + goto out; + while (!crng_ready() && !signal_pending(current)) { /* * Check !timer_pending() and then ensure that any previous callback has finished * executing by checking timer_delete_sync_try(), before queueing the next one. */ if (!timer_pending(&stack->timer) && timer_delete_sync_try(&stack->timer) >= 0) { - struct cpumask timer_cpus; unsigned int num_cpus; /* @@ -1326,19 +1329,19 @@ static void __cold try_to_generate_entropy(void) preempt_disable(); /* Only schedule callbacks on timer CPUs that are online. */ - cpumask_and(&timer_cpus, housekeeping_cpumask(HK_TYPE_TIMER), cpu_online_mask); - num_cpus = cpumask_weight(&timer_cpus); + cpumask_and(timer_cpus, housekeeping_cpumask(HK_TYPE_TIMER), cpu_online_mask); + num_cpus = cpumask_weight(timer_cpus); /* In very bizarre case of misconfiguration, fallback to all online. */ if (unlikely(num_cpus == 0)) { - timer_cpus = *cpu_online_mask; - num_cpus = cpumask_weight(&timer_cpus); + *timer_cpus = *cpu_online_mask; + num_cpus = cpumask_weight(timer_cpus); } /* Basic CPU round-robin, which avoids the current CPU. */ do { - cpu = cpumask_next(cpu, &timer_cpus); + cpu = cpumask_next(cpu, timer_cpus); if (cpu >= nr_cpu_ids) - cpu = cpumask_first(&timer_cpus); + cpu = cpumask_first(timer_cpus); } while (cpu == smp_processor_id() && num_cpus > 1); /* Expiring the timer at `jiffies` means it's the next tick. */ @@ -1354,6 +1357,8 @@ static void __cold try_to_generate_entropy(void) } mix_pool_bytes(&stack->entropy, sizeof(stack->entropy)); + free_cpumask_var(timer_cpus); +out: timer_delete_sync(&stack->timer); timer_destroy_on_stack(&stack->timer); } From d0bb3db7b2959581270b77669faca67441a8f3af Mon Sep 17 00:00:00 2001 From: Abdun Nihaal Date: Tue, 28 Oct 2025 22:34:55 +0530 Subject: [PATCH 0527/3902] wifi: ath12k: fix potential memory leak in ath12k_wow_arp_ns_offload() [ Upstream commit be5febd51c478bc8e24ad3480435f2754a403b14 ] When the call to ath12k_wmi_arp_ns_offload() fails, the temporary memory allocation for offload is not freed before returning. Fix that by freeing offload in the error path. Fixes: 1666108c74c4 ("wifi: ath12k: support ARP and NS offload") Signed-off-by: Abdun Nihaal Reviewed-by: Baochen Qiang Link: https://patch.msgid.link/20251028170457.134608-1-nihaal@cse.iitm.ac.in Signed-off-by: Jeff Johnson Signed-off-by: Sasha Levin --- drivers/net/wireless/ath/ath12k/wow.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/net/wireless/ath/ath12k/wow.c b/drivers/net/wireless/ath/ath12k/wow.c index dce9bd0bcaefb8..e8481626f19404 100644 --- a/drivers/net/wireless/ath/ath12k/wow.c +++ b/drivers/net/wireless/ath/ath12k/wow.c @@ -758,6 +758,7 @@ static int ath12k_wow_arp_ns_offload(struct ath12k *ar, bool enable) if (ret) { ath12k_warn(ar->ab, "failed to set arp ns offload vdev %i: enable %d, ret %d\n", arvif->vdev_id, enable, ret); + kfree(offload); return ret; } } From 36faecac7a2c238d46008f2c8f088d0eba674252 Mon Sep 17 00:00:00 2001 From: Baochen Qiang Date: Wed, 29 Oct 2025 10:07:14 +0800 Subject: [PATCH 0528/3902] wifi: ath12k: fix reusing m3 memory [ Upstream commit 00575bb44b2c2aa53d0a768de2b80c9c1af0174d ] During firmware recovery or suspend/resume, m3 memory could be reused if the size of the new m3 binary is equal to or less than that of the existing memory. There will be issues for the latter case, since m3_mem->size will be updated with a smaller value and this value is eventually used in the free path, where the original total size should be used instead. To fix it, add a new member in m3_mem_region structure to track the original memory size and use it in free path. Tested-on: WCN7850 hw2.0 PCI WLAN.HMT.1.1.c5-00302-QCAHMTSWPL_V1.0_V2.0_SILICONZ-1.115823.3 Fixes: 05090ae82f44 ("wifi: ath12k: check M3 buffer size as well whey trying to reuse it") Signed-off-by: Baochen Qiang Reviewed-by: Vasanthakumar Thiagarajan Link: https://patch.msgid.link/20251029-ath12k-fix-m3-reuse-v1-1-69225bacfc5d@oss.qualcomm.com Signed-off-by: Jeff Johnson Signed-off-by: Sasha Levin --- drivers/net/wireless/ath/ath12k/qmi.c | 11 +++++++---- drivers/net/wireless/ath/ath12k/qmi.h | 5 ++++- 2 files changed, 11 insertions(+), 5 deletions(-) diff --git a/drivers/net/wireless/ath/ath12k/qmi.c b/drivers/net/wireless/ath/ath12k/qmi.c index 36325e62aa2423..8de9aee2498ec5 100644 --- a/drivers/net/wireless/ath/ath12k/qmi.c +++ b/drivers/net/wireless/ath/ath12k/qmi.c @@ -1,7 +1,7 @@ // SPDX-License-Identifier: BSD-3-Clause-Clear /* * Copyright (c) 2018-2021 The Linux Foundation. All rights reserved. - * Copyright (c) 2021-2025 Qualcomm Innovation Center, Inc. All rights reserved. + * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries. */ #include @@ -3114,9 +3114,10 @@ static void ath12k_qmi_m3_free(struct ath12k_base *ab) if (!m3_mem->vaddr) return; - dma_free_coherent(ab->dev, m3_mem->size, + dma_free_coherent(ab->dev, m3_mem->total_size, m3_mem->vaddr, m3_mem->paddr); m3_mem->vaddr = NULL; + m3_mem->total_size = 0; m3_mem->size = 0; } @@ -3152,7 +3153,7 @@ static int ath12k_qmi_m3_load(struct ath12k_base *ab) /* In recovery/resume cases, M3 buffer is not freed, try to reuse that */ if (m3_mem->vaddr) { - if (m3_mem->size >= m3_len) + if (m3_mem->total_size >= m3_len) goto skip_m3_alloc; /* Old buffer is too small, free and reallocate */ @@ -3164,11 +3165,13 @@ static int ath12k_qmi_m3_load(struct ath12k_base *ab) GFP_KERNEL); if (!m3_mem->vaddr) { ath12k_err(ab, "failed to allocate memory for M3 with size %zu\n", - fw->size); + m3_len); ret = -ENOMEM; goto out; } + m3_mem->total_size = m3_len; + skip_m3_alloc: memcpy(m3_mem->vaddr, m3_data, m3_len); m3_mem->size = m3_len; diff --git a/drivers/net/wireless/ath/ath12k/qmi.h b/drivers/net/wireless/ath/ath12k/qmi.h index 4767d9a2e309e4..7a88268aa1e9e9 100644 --- a/drivers/net/wireless/ath/ath12k/qmi.h +++ b/drivers/net/wireless/ath/ath12k/qmi.h @@ -1,7 +1,7 @@ /* SPDX-License-Identifier: BSD-3-Clause-Clear */ /* * Copyright (c) 2018-2021 The Linux Foundation. All rights reserved. - * Copyright (c) 2021-2025 Qualcomm Innovation Center, Inc. All rights reserved. + * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries. */ #ifndef ATH12K_QMI_H @@ -120,6 +120,9 @@ struct target_info { }; struct m3_mem_region { + /* total memory allocated */ + u32 total_size; + /* actual memory being used */ u32 size; dma_addr_t paddr; void *vaddr; From 4f93750fa58861abee4318115ef0023c8d3ba0c0 Mon Sep 17 00:00:00 2001 From: Baochen Qiang Date: Thu, 30 Oct 2025 10:08:43 +0800 Subject: [PATCH 0529/3902] wifi: ath12k: fix error handling in creating hardware group [ Upstream commit 088a099690e4c0d291db505013317ab5dd58b4d5 ] In ath12k_core_init() when ath12k_core_hw_group_create() fails, ath12k_core_hw_group_destroy() is called where for each device below path would get executed ath12k_core_soc_destroy() ath12k_qmi_deinit_service() qmi_handle_release() This results in kernel crash in case one of the device fails at qmi_handle_init() when creating hardware group: ath12k_pci 0000:10:00.0: failed to initialize qmi handle ath12k_pci 0000:10:00.0: failed to initialize qmi :-517 ath12k_pci 0000:10:00.0: failed to create soc core: -517 ath12k_pci 0000:10:00.0: unable to create hw group BUG: unable to handle page fault for address: ffffffffffffffb7 RIP: 0010:qmi_handle_release Call Trace: ath12k_qmi_deinit_service ath12k_core_hw_group_destroy ath12k_core_init ath12k_pci_probe The detailed reason is, when qmi_handle_init() fails for a device ab->qmi.handle is not correctly initialized. Then ath12k_core_hw_group_create() returns failure, since error handing is done for all device, eventually qmi_handle_release() is called for the issue device and finally kernel crashes due to the uninitialized ab->qmi.handle. Fix this by moving error handling to ath12k_core_hw_group_create(), this way the issue device can be skipped. Tested-on: WCN7850 hw2.0 PCI WLAN.HMT.1.1.c5-00284.1-QCAHMTSWPL_V1.0_V2.0_SILICONZ-3 Fixes: 6f245ea0ec6c ("wifi: ath12k: introduce device group abstraction") Link: https://lore.kernel.org/ath12k/fabc97122016d1a66a53ddedd965d134@posteo.net Reported-by: a-development Closes: https://bugzilla.kernel.org/show_bug.cgi?id=220518 Tested-by: a-development Signed-off-by: Baochen Qiang Reviewed-by: Vasanthakumar Thiagarajan Link: https://patch.msgid.link/20251030-fix-hw-group-create-err-handling-v1-1-0659e4d15fb9@oss.qualcomm.com Signed-off-by: Jeff Johnson Signed-off-by: Sasha Levin --- drivers/net/wireless/ath/ath12k/core.c | 22 +++++++++++++++++----- 1 file changed, 17 insertions(+), 5 deletions(-) diff --git a/drivers/net/wireless/ath/ath12k/core.c b/drivers/net/wireless/ath/ath12k/core.c index 5d494c5cdc0da3..a2137b363c2fea 100644 --- a/drivers/net/wireless/ath/ath12k/core.c +++ b/drivers/net/wireless/ath/ath12k/core.c @@ -2106,14 +2106,27 @@ static int ath12k_core_hw_group_create(struct ath12k_hw_group *ag) ret = ath12k_core_soc_create(ab); if (ret) { mutex_unlock(&ab->core_lock); - ath12k_err(ab, "failed to create soc core: %d\n", ret); - return ret; + ath12k_err(ab, "failed to create soc %d core: %d\n", i, ret); + goto destroy; } mutex_unlock(&ab->core_lock); } return 0; + +destroy: + for (i--; i >= 0; i--) { + ab = ag->ab[i]; + if (!ab) + continue; + + mutex_lock(&ab->core_lock); + ath12k_core_soc_destroy(ab); + mutex_unlock(&ab->core_lock); + } + + return ret; } void ath12k_core_hw_group_set_mlo_capable(struct ath12k_hw_group *ag) @@ -2188,7 +2201,7 @@ int ath12k_core_init(struct ath12k_base *ab) if (ret) { mutex_unlock(&ag->mutex); ath12k_warn(ab, "unable to create hw group\n"); - goto err_destroy_hw_group; + goto err_unassign_hw_group; } } @@ -2196,8 +2209,7 @@ int ath12k_core_init(struct ath12k_base *ab) return 0; -err_destroy_hw_group: - ath12k_core_hw_group_destroy(ab->ag); +err_unassign_hw_group: ath12k_core_hw_group_unassign(ab); err_unregister_notifier: ath12k_core_panic_notifier_unregister(ab); From 41b6231291750e2caa9550cf2728e04fed255bda Mon Sep 17 00:00:00 2001 From: Rameshkumar Sundaram Date: Sun, 26 Oct 2025 23:52:53 +0530 Subject: [PATCH 0530/3902] wifi: ath12k: enforce vdev limit in ath12k_mac_vdev_create() [ Upstream commit 448bf7b51426bcca54b5ac1ddd1045a36c9d1dea ] Currently, vdev limit check is performed only in ath12k_mac_assign_vif_to_vdev(). If the host has already created maximum number of vdevs for the radio (ar) and a scan request arrives for the same radio, ath12k_mac_initiate_hw_scan() attempts to create a vdev without checking the limit, causing firmware asserts. Centralize the vdev limit guard by moving the check into ath12k_mac_vdev_create() so that all callers obey the limit. While doing this, update the condition from `num_created_vdevs > (TARGET_NUM_VDEVS(ab) - 1)` to `num_created_vdevs >= TARGET_NUM_VDEVS(ab)` for clarity and to eliminate unnecessary arithmetic. Tested-on: QCN9274 hw2.0 PCI WLAN.WBE.1.4.1-00199-QCAHKSWPL_SILICONZ-1 Fixes: 0d6e6736ed9f ("wifi: ath12k: scan statemachine changes for single wiphy") Fixes: 4938ba733ee2 ("wifi: ath12k: modify remain on channel for single wiphy") Signed-off-by: Rameshkumar Sundaram Reviewed-by: Vasanthakumar Thiagarajan Link: https://patch.msgid.link/20251026182254.1399650-2-rameshkumar.sundaram@oss.qualcomm.com Signed-off-by: Jeff Johnson Signed-off-by: Sasha Levin --- drivers/net/wireless/ath/ath12k/mac.c | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/drivers/net/wireless/ath/ath12k/mac.c b/drivers/net/wireless/ath/ath12k/mac.c index 0cec6c47fca29f..8f139505019c4b 100644 --- a/drivers/net/wireless/ath/ath12k/mac.c +++ b/drivers/net/wireless/ath/ath12k/mac.c @@ -9687,6 +9687,12 @@ int ath12k_mac_vdev_create(struct ath12k *ar, struct ath12k_link_vif *arvif) if (vif->type == NL80211_IFTYPE_MONITOR && ar->monitor_vdev_created) return -EINVAL; + if (ar->num_created_vdevs >= TARGET_NUM_VDEVS(ab)) { + ath12k_warn(ab, "failed to create vdev, reached max vdev limit %d\n", + TARGET_NUM_VDEVS(ab)); + return -ENOSPC; + } + link_id = arvif->link_id; if (link_id < IEEE80211_MLD_MAX_NUM_LINKS) { @@ -10046,12 +10052,6 @@ static struct ath12k *ath12k_mac_assign_vif_to_vdev(struct ieee80211_hw *hw, if (arvif->is_created) goto flush; - if (ar->num_created_vdevs > (TARGET_NUM_VDEVS(ab) - 1)) { - ath12k_warn(ab, "failed to create vdev, reached max vdev limit %d\n", - TARGET_NUM_VDEVS(ab)); - goto unlock; - } - ret = ath12k_mac_vdev_create(ar, arvif); if (ret) { ath12k_warn(ab, "failed to create vdev %pM ret %d", vif->addr, ret); From e442e820e95389931be507c9d6c174e9ce4c6167 Mon Sep 17 00:00:00 2001 From: Rameshkumar Sundaram Date: Sun, 26 Oct 2025 23:52:54 +0530 Subject: [PATCH 0531/3902] wifi: ath12k: unassign arvif on scan vdev create failure [ Upstream commit e70515039d44be61b6a73aafb401d141b0034d12 ] During scan and remain-on-channel requests, a scan link vif (arvif) is assigned and a temporary vdev is created. If vdev creation fails, the assigned arvif is left attached until the virtual interface is removed, leaving a stale link in ahvif. Fix this by freeing the stale arvif and resetting the corresponding link in ahvif by calling ath12k_mac_unassign_link_vif() when vdev creation fails. While at it, propagate the actual error code from ath12k_mac_vdev_create() instead of returning -EINVAL in ath12k_mac_initiate_hw_scan(). Tested-on: QCN9274 hw2.0 PCI WLAN.WBE.1.4.1-00199-QCAHKSWPL_SILICONZ-1 Fixes: 477cabfdb776 ("wifi: ath12k: modify link arvif creation and removal for MLO") Signed-off-by: Rameshkumar Sundaram Reviewed-by: Vasanthakumar Thiagarajan Link: https://patch.msgid.link/20251026182254.1399650-3-rameshkumar.sundaram@oss.qualcomm.com Signed-off-by: Jeff Johnson Signed-off-by: Sasha Levin --- drivers/net/wireless/ath/ath12k/mac.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/drivers/net/wireless/ath/ath12k/mac.c b/drivers/net/wireless/ath/ath12k/mac.c index 8f139505019c4b..095b49a39683ce 100644 --- a/drivers/net/wireless/ath/ath12k/mac.c +++ b/drivers/net/wireless/ath/ath12k/mac.c @@ -5059,7 +5059,8 @@ static int ath12k_mac_initiate_hw_scan(struct ieee80211_hw *hw, ret = ath12k_mac_vdev_create(ar, arvif); if (ret) { ath12k_warn(ar->ab, "unable to create scan vdev %d\n", ret); - return -EINVAL; + ath12k_mac_unassign_link_vif(arvif); + return ret; } } @@ -12895,6 +12896,7 @@ static int ath12k_mac_op_remain_on_channel(struct ieee80211_hw *hw, if (ret) { ath12k_warn(ar->ab, "unable to create scan vdev for roc: %d\n", ret); + ath12k_mac_unassign_link_vif(arvif); return ret; } } From 3fcb47f405e9f6d15c6b1d1cb3c4358d5b34d871 Mon Sep 17 00:00:00 2001 From: Dmitry Baryshkov Date: Thu, 2 Oct 2025 11:53:00 +0300 Subject: [PATCH 0532/3902] interconnect: qcom: msm8996: add missing link to SLAVE_USB_HS [ Upstream commit 8cf9b43f6b4d90e19a9341edefdd46842d4adb55 ] >From the initial submission the interconnect driver missed the link from SNOC_PNOC to the USB 2 configuration space. Add missing link in order to let the platform configure and utilize this path. Fixes: 7add937f5222 ("interconnect: qcom: Add MSM8996 interconnect provider driver") Signed-off-by: Dmitry Baryshkov Reviewed-by: Konrad Dybcio Link: https://lore.kernel.org/r/20251002-fix-msm8996-icc-v1-1-a36a05d1f869@oss.qualcomm.com Signed-off-by: Georgi Djakov Signed-off-by: Sasha Levin --- drivers/interconnect/qcom/msm8996.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/interconnect/qcom/msm8996.c b/drivers/interconnect/qcom/msm8996.c index b73566c9b21f9d..84cfafb22aa17d 100644 --- a/drivers/interconnect/qcom/msm8996.c +++ b/drivers/interconnect/qcom/msm8996.c @@ -552,6 +552,7 @@ static struct qcom_icc_node mas_venus_vmem = { static const u16 mas_snoc_pnoc_links[] = { MSM8996_SLAVE_BLSP_1, MSM8996_SLAVE_BLSP_2, + MSM8996_SLAVE_USB_HS, MSM8996_SLAVE_SDCC_1, MSM8996_SLAVE_SDCC_2, MSM8996_SLAVE_SDCC_4, From 30f32fa4d2331896ae05ac75dfc06aaa007a35d6 Mon Sep 17 00:00:00 2001 From: Dmitry Baryshkov Date: Thu, 2 Oct 2025 11:53:01 +0300 Subject: [PATCH 0533/3902] arm64: dts: qcom: msm8996: add interconnect paths to USB2 controller [ Upstream commit 242f7558e7bf54cb63c06506f7b0630dd67d45a4 ] Add the missing interconnects to the USB2 host. The Fixes tag points to the commit which broke probing of the USB host on that platform. Fixes: 130733a10079 ("interconnect: qcom: msm8996: Promote to core_initcall") Signed-off-by: Dmitry Baryshkov Reviewed-by: Konrad Dybcio Acked-by: Bjorn Andersson Link: https://lore.kernel.org/r/20251002-fix-msm8996-icc-v1-2-a36a05d1f869@oss.qualcomm.com Signed-off-by: Georgi Djakov Signed-off-by: Sasha Levin --- arch/arm64/boot/dts/qcom/msm8996.dtsi | 3 +++ 1 file changed, 3 insertions(+) diff --git a/arch/arm64/boot/dts/qcom/msm8996.dtsi b/arch/arm64/boot/dts/qcom/msm8996.dtsi index c75b522f6eba66..33608b1d7d060b 100644 --- a/arch/arm64/boot/dts/qcom/msm8996.dtsi +++ b/arch/arm64/boot/dts/qcom/msm8996.dtsi @@ -3496,6 +3496,9 @@ <&gcc GCC_USB20_MASTER_CLK>; assigned-clock-rates = <19200000>, <60000000>; + interconnects = <&pnoc MASTER_USB_HS &bimc SLAVE_EBI_CH0>, + <&bimc MASTER_AMPSS_M0 &pnoc SLAVE_USB_HS>; + interconnect-names = "usb-ddr", "apps-usb"; power-domains = <&gcc USB30_GDSC>; qcom,select-utmi-as-pipe-clk; status = "disabled"; From 14700e6b28f3e21fe9fe2f506b6d4e45c289f37f Mon Sep 17 00:00:00 2001 From: Lizhi Hou Date: Wed, 29 Oct 2025 12:34:23 -0700 Subject: [PATCH 0534/3902] accel/amdxdna: Fix incorrect command state for timed out job [ Upstream commit 6fb7f298883246e21f60f971065adcb789ae6eba ] When a command times out, mark it as ERT_CMD_STATE_TIMEOUT. Any other commands that are canceled due to this timeout should be marked as ERT_CMD_STATE_ABORT. Fixes: aac243092b70 ("accel/amdxdna: Add command execution") Reviewed-by: Mario Limonciello (AMD) Signed-off-by: Lizhi Hou Link: https://patch.msgid.link/20251029193423.2430463-1-lizhi.hou@amd.com Signed-off-by: Sasha Levin --- drivers/accel/amdxdna/aie2_ctx.c | 15 +++++++++++++-- drivers/accel/amdxdna/amdxdna_ctx.h | 1 + 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/drivers/accel/amdxdna/aie2_ctx.c b/drivers/accel/amdxdna/aie2_ctx.c index e9f9b1fa5dc1b1..c9f712f0ec00cc 100644 --- a/drivers/accel/amdxdna/aie2_ctx.c +++ b/drivers/accel/amdxdna/aie2_ctx.c @@ -204,10 +204,13 @@ aie2_sched_resp_handler(void *handle, void __iomem *data, size_t size) cmd_abo = job->cmd_bo; - if (unlikely(!data)) + if (unlikely(job->job_timeout)) { + amdxdna_cmd_set_state(cmd_abo, ERT_CMD_STATE_TIMEOUT); + ret = -EINVAL; goto out; + } - if (unlikely(size != sizeof(u32))) { + if (unlikely(!data) || unlikely(size != sizeof(u32))) { amdxdna_cmd_set_state(cmd_abo, ERT_CMD_STATE_ABORT); ret = -EINVAL; goto out; @@ -260,6 +263,13 @@ aie2_sched_cmdlist_resp_handler(void *handle, void __iomem *data, size_t size) int ret = 0; cmd_abo = job->cmd_bo; + + if (unlikely(job->job_timeout)) { + amdxdna_cmd_set_state(cmd_abo, ERT_CMD_STATE_TIMEOUT); + ret = -EINVAL; + goto out; + } + if (unlikely(!data) || unlikely(size != sizeof(u32) * 3)) { amdxdna_cmd_set_state(cmd_abo, ERT_CMD_STATE_ABORT); ret = -EINVAL; @@ -362,6 +372,7 @@ aie2_sched_job_timedout(struct drm_sched_job *sched_job) xdna = hwctx->client->xdna; trace_xdna_job(sched_job, hwctx->name, "job timedout", job->seq); + job->job_timeout = true; mutex_lock(&xdna->dev_lock); aie2_hwctx_stop(xdna, hwctx, sched_job); diff --git a/drivers/accel/amdxdna/amdxdna_ctx.h b/drivers/accel/amdxdna/amdxdna_ctx.h index 7cd7a55936f099..8c1d181df6e794 100644 --- a/drivers/accel/amdxdna/amdxdna_ctx.h +++ b/drivers/accel/amdxdna/amdxdna_ctx.h @@ -105,6 +105,7 @@ struct amdxdna_sched_job { /* user can wait on this fence */ struct dma_fence *out_fence; bool job_done; + bool job_timeout; u64 seq; struct amdxdna_gem_obj *cmd_bo; size_t bo_cnt; From 18fe7b456f99c478a1d7cc3fd66ec2eacfeb8f66 Mon Sep 17 00:00:00 2001 From: Kuan-Wei Chiu Date: Fri, 10 Oct 2025 23:14:47 +0800 Subject: [PATCH 0535/3902] interconnect: debugfs: Fix incorrect error handling for NULL path [ Upstream commit 6bfe104fd0f94d0248af22c256ce725ee087157b ] The icc_commit_set() function, used by the debugfs interface, checks the validity of the global cur_path pointer using IS_ERR_OR_NULL(). However, in the specific case where cur_path is NULL, while IS_ERR_OR_NULL(NULL) correctly evaluates to true, the subsequent call to PTR_ERR(NULL) returns 0. This causes the function to return a success code (0) instead of an error, misleading the user into believing their bandwidth request was successfully committed when, in fact, no operation was performed. Fix this by adding an explicit check to return -EINVAL if cur_path is NULL. This prevents silent failures and ensures that an invalid operational sequence is immediately and clearly reported as an error. Fixes: 770c69f037c1 ("interconnect: Add debugfs test client") Signed-off-by: Kuan-Wei Chiu Link: https://lore.kernel.org/r/20251010151447.2289779-1-visitorckw@gmail.com Signed-off-by: Georgi Djakov Signed-off-by: Sasha Levin --- drivers/interconnect/debugfs-client.c | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/drivers/interconnect/debugfs-client.c b/drivers/interconnect/debugfs-client.c index bc3fd8a7b9eb41..778deeb4a7e8a6 100644 --- a/drivers/interconnect/debugfs-client.c +++ b/drivers/interconnect/debugfs-client.c @@ -117,7 +117,12 @@ static int icc_commit_set(void *data, u64 val) mutex_lock(&debugfs_lock); - if (IS_ERR_OR_NULL(cur_path)) { + if (!cur_path) { + ret = -EINVAL; + goto out; + } + + if (IS_ERR(cur_path)) { ret = PTR_ERR(cur_path); goto out; } From e620258d7d5ee5b9fe35e6e4932b6b73aae385cf Mon Sep 17 00:00:00 2001 From: Marek Vasut Date: Mon, 27 Oct 2025 19:45:53 +0100 Subject: [PATCH 0536/3902] arm64: dts: renesas: sparrow-hawk: Fix full-size DP connector node name and labels MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [ Upstream commit 9d22a34a016313137b9e534a918f1f9aa790aa69 ] The DisplayPort connector on Retronix R-Car V4H Sparrow Hawk board is a full-size DisplayPort connector. Fix the copy-paste error and update the DT node name and labels accordingly. No functional change. Fixes: a719915e76f2 ("arm64: dts: renesas: r8a779g3: Add Retronix R-Car V4H Sparrow Hawk board support") Signed-off-by: Marek Vasut Reviewed-by: Niklas Söderlund Reviewed-by: Geert Uytterhoeven Link: https://patch.msgid.link/20251027184604.34550-1-marek.vasut+renesas@mailbox.org Signed-off-by: Geert Uytterhoeven Signed-off-by: Sasha Levin --- arch/arm64/boot/dts/renesas/r8a779g3-sparrow-hawk.dts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/arch/arm64/boot/dts/renesas/r8a779g3-sparrow-hawk.dts b/arch/arm64/boot/dts/renesas/r8a779g3-sparrow-hawk.dts index 1da8e476b2193d..ff07d984cbf299 100644 --- a/arch/arm64/boot/dts/renesas/r8a779g3-sparrow-hawk.dts +++ b/arch/arm64/boot/dts/renesas/r8a779g3-sparrow-hawk.dts @@ -119,13 +119,13 @@ }; /* Page 27 / DSI to Display */ - mini-dp-con { + dp-con { compatible = "dp-connector"; label = "CN6"; type = "full-size"; port { - mini_dp_con_in: endpoint { + dp_con_in: endpoint { remote-endpoint = <&sn65dsi86_out>; }; }; @@ -407,7 +407,7 @@ port@1 { reg = <1>; sn65dsi86_out: endpoint { - remote-endpoint = <&mini_dp_con_in>; + remote-endpoint = <&dp_con_in>; }; }; }; From d41ba8c6f667c76fb9592748d5e0c7224183d6c1 Mon Sep 17 00:00:00 2001 From: Christian Brauner Date: Wed, 29 Oct 2025 13:20:19 +0100 Subject: [PATCH 0537/3902] cgroup: add cgroup namespace to tree after owner is set [ Upstream commit 768b1565d9d1e1eebf7567f477f7f46c05a98a4d ] Otherwise we trip VFS_WARN_ON_ONC() in __ns_tree_add_raw(). Link: https://patch.msgid.link/20251029-work-namespace-nstree-listns-v4-6-2e6f823ebdc0@kernel.org Fixes: 7c6059398533 ("cgroup: support ns lookup") Tested-by: syzbot@syzkaller.appspotmail.com Reviewed-by: Jeff Layton Signed-off-by: Christian Brauner Signed-off-by: Sasha Levin --- kernel/cgroup/namespace.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/kernel/cgroup/namespace.c b/kernel/cgroup/namespace.c index fdbe57578e6886..db9617556dd706 100644 --- a/kernel/cgroup/namespace.c +++ b/kernel/cgroup/namespace.c @@ -30,7 +30,6 @@ static struct cgroup_namespace *alloc_cgroup_ns(void) ret = ns_common_init(new_ns); if (ret) return ERR_PTR(ret); - ns_tree_add(new_ns); return no_free_ptr(new_ns); } @@ -86,6 +85,7 @@ struct cgroup_namespace *copy_cgroup_ns(u64 flags, new_ns->ucounts = ucounts; new_ns->root_cset = cset; + ns_tree_add(new_ns); return new_ns; } From 76103ac4b4b9fe3b7e85fc614dc16011ed70f734 Mon Sep 17 00:00:00 2001 From: Geert Uytterhoeven Date: Wed, 29 Oct 2025 16:00:28 +0100 Subject: [PATCH 0538/3902] drm/imagination: Fix reference to devm_platform_get_and_ioremap_resource() [ Upstream commit f1aa93005d0d6fb3293ca9c3eb08d1d1557117bf ] The call to devm_platform_ioremap_resource() was replaced by a call to devm_platform_get_and_ioremap_resource(), but the comment referring to the function's possible returned error codes was not updated. Fixes: 927f3e0253c11276 ("drm/imagination: Implement MIPS firmware processor and MMU support") Signed-off-by: Geert Uytterhoeven Reviewed-by: Matt Coster Link: https://patch.msgid.link/2266514318480d17f52c7e5e67578dae6827914e.1761745586.git.geert+renesas@glider.be Signed-off-by: Matt Coster Signed-off-by: Sasha Levin --- drivers/gpu/drm/imagination/pvr_device.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/gpu/drm/imagination/pvr_device.c b/drivers/gpu/drm/imagination/pvr_device.c index 294b6019b4155b..78d6b8a0a4506c 100644 --- a/drivers/gpu/drm/imagination/pvr_device.c +++ b/drivers/gpu/drm/imagination/pvr_device.c @@ -48,7 +48,7 @@ * * Return: * * 0 on success, or - * * Any error returned by devm_platform_ioremap_resource(). + * * Any error returned by devm_platform_get_and_ioremap_resource(). */ static int pvr_device_reg_init(struct pvr_device *pvr_dev) From 8d88ab9f20884dfc15b4af7611b7719d31d059a6 Mon Sep 17 00:00:00 2001 From: Namhyung Kim Date: Wed, 29 Oct 2025 21:01:39 -0700 Subject: [PATCH 0539/3902] perf lock contention: Load kernel map before lookup [ Upstream commit 553d18c98a896094b99a01765b9698b204183d49 ] On some machines, it caused troubles when it tried to find kernel symbols. I think it's because kernel modules and kallsyms are messed up during load and split. Basically we want to make sure the kernel map is loaded and the code has it in the lock_contention_read(). But recently we added more lookups in the lock_contention_prepare() which is called before _read(). Also the kernel map (kallsyms) may not be the first one in the group like on ARM. Let's use machine__kernel_map() rather than just loading the first map. Reviewed-by: Ian Rogers Fixes: 688d2e8de231c54e ("perf lock contention: Add -l/--lock-addr option") Signed-off-by: Namhyung Kim Signed-off-by: Sasha Levin --- tools/perf/util/bpf_lock_contention.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tools/perf/util/bpf_lock_contention.c b/tools/perf/util/bpf_lock_contention.c index 60b81d586323f3..7b5671f13c5352 100644 --- a/tools/perf/util/bpf_lock_contention.c +++ b/tools/perf/util/bpf_lock_contention.c @@ -184,6 +184,9 @@ int lock_contention_prepare(struct lock_contention *con) struct evlist *evlist = con->evlist; struct target *target = con->target; + /* make sure it loads the kernel map before lookup */ + map__load(machine__kernel_map(con->machine)); + skel = lock_contention_bpf__open(); if (!skel) { pr_err("Failed to open lock-contention BPF skeleton\n"); @@ -749,9 +752,6 @@ int lock_contention_read(struct lock_contention *con) bpf_prog_test_run_opts(prog_fd, &opts); } - /* make sure it loads the kernel map */ - maps__load_first(machine->kmaps); - prev_key = NULL; while (!bpf_map_get_next_key(fd, prev_key, &key)) { s64 ls_key; From d733e1e6ed7196086e13933ffcd2ff9bcf954169 Mon Sep 17 00:00:00 2001 From: Shuai Xue Date: Thu, 23 Oct 2025 09:50:43 +0800 Subject: [PATCH 0540/3902] perf record: skip synthesize event when open evsel failed [ Upstream commit 163e5f2b96632b7fb2eaa965562aca0dbdf9f996 ] When using perf record with the `--overwrite` option, a segmentation fault occurs if an event fails to open. For example: perf record -e cycles-ct -F 1000 -a --overwrite Error: cycles-ct:H: PMU Hardware doesn't support sampling/overflow-interrupts. Try 'perf stat' perf: Segmentation fault #0 0x6466b6 in dump_stack debug.c:366 #1 0x646729 in sighandler_dump_stack debug.c:378 #2 0x453fd1 in sigsegv_handler builtin-record.c:722 #3 0x7f8454e65090 in __restore_rt libc-2.32.so[54090] #4 0x6c5671 in __perf_event__synthesize_id_index synthetic-events.c:1862 #5 0x6c5ac0 in perf_event__synthesize_id_index synthetic-events.c:1943 #6 0x458090 in record__synthesize builtin-record.c:2075 #7 0x45a85a in __cmd_record builtin-record.c:2888 #8 0x45deb6 in cmd_record builtin-record.c:4374 #9 0x4e5e33 in run_builtin perf.c:349 #10 0x4e60bf in handle_internal_command perf.c:401 #11 0x4e6215 in run_argv perf.c:448 #12 0x4e653a in main perf.c:555 #13 0x7f8454e4fa72 in __libc_start_main libc-2.32.so[3ea72] #14 0x43a3ee in _start ??:0 The --overwrite option implies --tail-synthesize, which collects non-sample events reflecting the system status when recording finishes. However, when evsel opening fails (e.g., unsupported event 'cycles-ct'), session->evlist is not initialized and remains NULL. The code unconditionally calls record__synthesize() in the error path, which iterates through the NULL evlist pointer and causes a segfault. To fix it, move the record__synthesize() call inside the error check block, so it's only called when there was no error during recording, ensuring that evlist is properly initialized. Fixes: 4ea648aec019 ("perf record: Add --tail-synthesize option") Signed-off-by: Shuai Xue Signed-off-by: Namhyung Kim Signed-off-by: Sasha Levin --- tools/perf/builtin-record.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/perf/builtin-record.c b/tools/perf/builtin-record.c index d76f01956e33b4..b1fb87016d5aa9 100644 --- a/tools/perf/builtin-record.c +++ b/tools/perf/builtin-record.c @@ -2883,11 +2883,11 @@ static int __cmd_record(struct record *rec, int argc, const char **argv) rec->bytes_written += off_cpu_write(rec->session); record__read_lost_samples(rec); - record__synthesize(rec, true); /* this will be recalculated during process_buildids() */ rec->samples = 0; if (!err) { + record__synthesize(rec, true); if (!rec->timestamp_filename) { record__finish_output(rec); } else { From d7eaed82c21ea760acb16ded5924896b95cce767 Mon Sep 17 00:00:00 2001 From: Taniya Das Date: Fri, 31 Oct 2025 15:32:25 +0530 Subject: [PATCH 0541/3902] clk: qcom: tcsrcc-glymur: Update register offsets for clock refs [ Upstream commit a4aa1ceb89f5c0d27a55671d88699cf5eae7331b ] Update the register offsets for all the clock ref branches to match the new address mapping in the TCSR subsystem. Fixes: 2c1d6ce4f3da ("clk: qcom: Add TCSR clock driver for Glymur SoC") Signed-off-by: Taniya Das Reviewed-by: Abel Vesa Tested-by: Jagadeesh Kona Link: https://lore.kernel.org/r/20251031-tcsrcc_glymur-v1-1-0efb031f0ac5@oss.qualcomm.com Signed-off-by: Bjorn Andersson Signed-off-by: Sasha Levin --- drivers/clk/qcom/tcsrcc-glymur.c | 54 ++++++++++++++++---------------- 1 file changed, 27 insertions(+), 27 deletions(-) diff --git a/drivers/clk/qcom/tcsrcc-glymur.c b/drivers/clk/qcom/tcsrcc-glymur.c index c1f8b6d10b7fd6..215bc2ac548da8 100644 --- a/drivers/clk/qcom/tcsrcc-glymur.c +++ b/drivers/clk/qcom/tcsrcc-glymur.c @@ -28,10 +28,10 @@ enum { }; static struct clk_branch tcsr_edp_clkref_en = { - .halt_reg = 0x1c, + .halt_reg = 0x60, .halt_check = BRANCH_HALT_DELAY, .clkr = { - .enable_reg = 0x1c, + .enable_reg = 0x60, .enable_mask = BIT(0), .hw.init = &(const struct clk_init_data) { .name = "tcsr_edp_clkref_en", @@ -45,10 +45,10 @@ static struct clk_branch tcsr_edp_clkref_en = { }; static struct clk_branch tcsr_pcie_1_clkref_en = { - .halt_reg = 0x4, + .halt_reg = 0x48, .halt_check = BRANCH_HALT_DELAY, .clkr = { - .enable_reg = 0x4, + .enable_reg = 0x48, .enable_mask = BIT(0), .hw.init = &(const struct clk_init_data) { .name = "tcsr_pcie_1_clkref_en", @@ -62,10 +62,10 @@ static struct clk_branch tcsr_pcie_1_clkref_en = { }; static struct clk_branch tcsr_pcie_2_clkref_en = { - .halt_reg = 0x8, + .halt_reg = 0x4c, .halt_check = BRANCH_HALT_DELAY, .clkr = { - .enable_reg = 0x8, + .enable_reg = 0x4c, .enable_mask = BIT(0), .hw.init = &(const struct clk_init_data) { .name = "tcsr_pcie_2_clkref_en", @@ -79,10 +79,10 @@ static struct clk_branch tcsr_pcie_2_clkref_en = { }; static struct clk_branch tcsr_pcie_3_clkref_en = { - .halt_reg = 0x10, + .halt_reg = 0x54, .halt_check = BRANCH_HALT_DELAY, .clkr = { - .enable_reg = 0x10, + .enable_reg = 0x54, .enable_mask = BIT(0), .hw.init = &(const struct clk_init_data) { .name = "tcsr_pcie_3_clkref_en", @@ -96,10 +96,10 @@ static struct clk_branch tcsr_pcie_3_clkref_en = { }; static struct clk_branch tcsr_pcie_4_clkref_en = { - .halt_reg = 0x14, + .halt_reg = 0x58, .halt_check = BRANCH_HALT_DELAY, .clkr = { - .enable_reg = 0x14, + .enable_reg = 0x58, .enable_mask = BIT(0), .hw.init = &(const struct clk_init_data) { .name = "tcsr_pcie_4_clkref_en", @@ -113,10 +113,10 @@ static struct clk_branch tcsr_pcie_4_clkref_en = { }; static struct clk_branch tcsr_usb2_1_clkref_en = { - .halt_reg = 0x28, + .halt_reg = 0x6c, .halt_check = BRANCH_HALT_DELAY, .clkr = { - .enable_reg = 0x28, + .enable_reg = 0x6c, .enable_mask = BIT(0), .hw.init = &(const struct clk_init_data) { .name = "tcsr_usb2_1_clkref_en", @@ -130,10 +130,10 @@ static struct clk_branch tcsr_usb2_1_clkref_en = { }; static struct clk_branch tcsr_usb2_2_clkref_en = { - .halt_reg = 0x2c, + .halt_reg = 0x70, .halt_check = BRANCH_HALT_DELAY, .clkr = { - .enable_reg = 0x2c, + .enable_reg = 0x70, .enable_mask = BIT(0), .hw.init = &(const struct clk_init_data) { .name = "tcsr_usb2_2_clkref_en", @@ -147,10 +147,10 @@ static struct clk_branch tcsr_usb2_2_clkref_en = { }; static struct clk_branch tcsr_usb2_3_clkref_en = { - .halt_reg = 0x30, + .halt_reg = 0x74, .halt_check = BRANCH_HALT_DELAY, .clkr = { - .enable_reg = 0x30, + .enable_reg = 0x74, .enable_mask = BIT(0), .hw.init = &(const struct clk_init_data) { .name = "tcsr_usb2_3_clkref_en", @@ -164,10 +164,10 @@ static struct clk_branch tcsr_usb2_3_clkref_en = { }; static struct clk_branch tcsr_usb2_4_clkref_en = { - .halt_reg = 0x44, + .halt_reg = 0x88, .halt_check = BRANCH_HALT_DELAY, .clkr = { - .enable_reg = 0x44, + .enable_reg = 0x88, .enable_mask = BIT(0), .hw.init = &(const struct clk_init_data) { .name = "tcsr_usb2_4_clkref_en", @@ -181,10 +181,10 @@ static struct clk_branch tcsr_usb2_4_clkref_en = { }; static struct clk_branch tcsr_usb3_0_clkref_en = { - .halt_reg = 0x20, + .halt_reg = 0x64, .halt_check = BRANCH_HALT_DELAY, .clkr = { - .enable_reg = 0x20, + .enable_reg = 0x64, .enable_mask = BIT(0), .hw.init = &(const struct clk_init_data) { .name = "tcsr_usb3_0_clkref_en", @@ -198,10 +198,10 @@ static struct clk_branch tcsr_usb3_0_clkref_en = { }; static struct clk_branch tcsr_usb3_1_clkref_en = { - .halt_reg = 0x24, + .halt_reg = 0x68, .halt_check = BRANCH_HALT_DELAY, .clkr = { - .enable_reg = 0x24, + .enable_reg = 0x68, .enable_mask = BIT(0), .hw.init = &(const struct clk_init_data) { .name = "tcsr_usb3_1_clkref_en", @@ -215,10 +215,10 @@ static struct clk_branch tcsr_usb3_1_clkref_en = { }; static struct clk_branch tcsr_usb4_1_clkref_en = { - .halt_reg = 0x0, + .halt_reg = 0x44, .halt_check = BRANCH_HALT_DELAY, .clkr = { - .enable_reg = 0x0, + .enable_reg = 0x44, .enable_mask = BIT(0), .hw.init = &(const struct clk_init_data) { .name = "tcsr_usb4_1_clkref_en", @@ -232,10 +232,10 @@ static struct clk_branch tcsr_usb4_1_clkref_en = { }; static struct clk_branch tcsr_usb4_2_clkref_en = { - .halt_reg = 0x18, + .halt_reg = 0x5c, .halt_check = BRANCH_HALT_DELAY, .clkr = { - .enable_reg = 0x18, + .enable_reg = 0x5c, .enable_mask = BIT(0), .hw.init = &(const struct clk_init_data) { .name = "tcsr_usb4_2_clkref_en", @@ -268,7 +268,7 @@ static const struct regmap_config tcsr_cc_glymur_regmap_config = { .reg_bits = 32, .reg_stride = 4, .val_bits = 32, - .max_register = 0x44, + .max_register = 0x94, .fast_io = true, }; From e21665bac15c642c2cd48a283a06de8c51e689fb Mon Sep 17 00:00:00 2001 From: Frederic Weisbecker Date: Fri, 24 Oct 2025 15:25:31 +0200 Subject: [PATCH 0542/3902] timers/migration: Convert "while" loops to use "for" [ Upstream commit 6c181b5667eea3e6564d334443536a5974190e15 ] Both the "do while" and "while" loops in tmigr_setup_groups() eventually mimic the behaviour of "for" loops. Simplify accordingly. Signed-off-by: Frederic Weisbecker Signed-off-by: Thomas Gleixner Link: https://patch.msgid.link/20251024132536.39841-2-frederic@kernel.org Stable-dep-of: 5eb579dfd46b ("timers/migration: Fix imbalanced NUMA trees") Signed-off-by: Sasha Levin --- kernel/time/timer_migration.c | 23 +++++++++++++---------- 1 file changed, 13 insertions(+), 10 deletions(-) diff --git a/kernel/time/timer_migration.c b/kernel/time/timer_migration.c index c0c54dc5314c3f..1e371f1fdc86cf 100644 --- a/kernel/time/timer_migration.c +++ b/kernel/time/timer_migration.c @@ -1642,22 +1642,23 @@ static void tmigr_connect_child_parent(struct tmigr_group *child, static int tmigr_setup_groups(unsigned int cpu, unsigned int node) { struct tmigr_group *group, *child, **stack; - int top = 0, err = 0, i = 0; + int i, top = 0, err = 0; struct list_head *lvllist; stack = kcalloc(tmigr_hierarchy_levels, sizeof(*stack), GFP_KERNEL); if (!stack) return -ENOMEM; - do { + for (i = 0; i < tmigr_hierarchy_levels; i++) { group = tmigr_get_group(cpu, node, i); if (IS_ERR(group)) { err = PTR_ERR(group); + i--; break; } top = i; - stack[i++] = group; + stack[i] = group; /* * When booting only less CPUs of a system than CPUs are @@ -1667,16 +1668,18 @@ static int tmigr_setup_groups(unsigned int cpu, unsigned int node) * be different from tmigr_hierarchy_levels, contains only a * single group. */ - if (group->parent || list_is_singular(&tmigr_level_list[i - 1])) + if (group->parent || list_is_singular(&tmigr_level_list[i])) break; + } - } while (i < tmigr_hierarchy_levels); - - /* Assert single root */ - WARN_ON_ONCE(!err && !group->parent && !list_is_singular(&tmigr_level_list[top])); + /* Assert single root without parent */ + if (WARN_ON_ONCE(i >= tmigr_hierarchy_levels)) + return -EINVAL; + if (WARN_ON_ONCE(!err && !group->parent && !list_is_singular(&tmigr_level_list[top]))) + return -EINVAL; - while (i > 0) { - group = stack[--i]; + for (; i >= 0; i--) { + group = stack[i]; if (err < 0) { list_del(&group->list); From cc25b81fe0eadde8d24431f810dbb64f5be981ae Mon Sep 17 00:00:00 2001 From: Frederic Weisbecker Date: Fri, 24 Oct 2025 15:25:32 +0200 Subject: [PATCH 0543/3902] timers/migration: Remove locking on group connection [ Upstream commit fa9620355d4192200f15cb3d97c6eb9c02442249 ] Initializing the tmc's group, the group's number of children and the group's parent can all be done without locking because: 1) Reading the group's parent and its group mask is done locklessly. 2) The connections prepared for a given CPU hierarchy are visible to the target CPU once online, thanks to the CPU hotplug enforced memory ordering. 3) In case of a newly created upper level, the new root and its connections and initialization are made visible by the CPU which made the connections. When that CPUs goes idle in the future, the new link is published by tmigr_inactive_up() through the atomic RmW on ->migr_state. 4) If CPUs were still walking up the active hierarchy, they could observe the new root earlier. In this case the ordering is enforced by an early initialization of the group mask and by barriers that maintain address dependency as explained in: b729cc1ec21a ("timers/migration: Fix another race between hotplug and idle entry/exit") de3ced72a792 ("timers/migration: Enforce group initialization visibility to tree walkers") 5) Timers are propagated by a chain of group locking from the bottom to the top. And while doing so, the tree also propagates groups links and initialization. Therefore remote expiration, which also relies on group locking, will observe those links and initialization while holding the root lock before walking the tree remotely and update remote timers. This is especially important for migrators in the active hierarchy that may observe the new root early. Therefore the locking is unnecessary at initialization. If anything, it just brings confusion. Remove it. Signed-off-by: Frederic Weisbecker Signed-off-by: Thomas Gleixner Link: https://patch.msgid.link/20251024132536.39841-3-frederic@kernel.org Stable-dep-of: 5eb579dfd46b ("timers/migration: Fix imbalanced NUMA trees") Signed-off-by: Sasha Levin --- kernel/time/timer_migration.c | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/kernel/time/timer_migration.c b/kernel/time/timer_migration.c index 1e371f1fdc86cf..5f8aef94ca0f71 100644 --- a/kernel/time/timer_migration.c +++ b/kernel/time/timer_migration.c @@ -1573,9 +1573,6 @@ static void tmigr_connect_child_parent(struct tmigr_group *child, { struct tmigr_walk data; - raw_spin_lock_irq(&child->lock); - raw_spin_lock_nested(&parent->lock, SINGLE_DEPTH_NESTING); - if (activate) { /* * @child is the old top and @parent the new one. In this @@ -1596,9 +1593,6 @@ static void tmigr_connect_child_parent(struct tmigr_group *child, */ smp_store_release(&child->parent, parent); - raw_spin_unlock(&parent->lock); - raw_spin_unlock_irq(&child->lock); - trace_tmigr_connect_child_parent(child); if (!activate) @@ -1695,13 +1689,9 @@ static int tmigr_setup_groups(unsigned int cpu, unsigned int node) if (i == 0) { struct tmigr_cpu *tmc = per_cpu_ptr(&tmigr_cpu, cpu); - raw_spin_lock_irq(&group->lock); - tmc->tmgroup = group; tmc->groupmask = BIT(group->num_children++); - raw_spin_unlock_irq(&group->lock); - trace_tmigr_connect_cpu_parent(tmc); /* There are no children that need to be connected */ From d4b3a4c2aa7b042e61303f1342ba7113783dbd09 Mon Sep 17 00:00:00 2001 From: Frederic Weisbecker Date: Fri, 24 Oct 2025 15:25:33 +0200 Subject: [PATCH 0544/3902] timers/migration: Fix imbalanced NUMA trees [ Upstream commit 5eb579dfd46b4949117ecb0f1ba2f12d3dc9a6f2 ] When a CPU from a new node boots, the old root may happen to be connected to the new root even if their node mismatch, as depicted in the following scenario: 1) CPU 0 boots and creates the first group for node 0. [GRP0:0] node 0 | CPU 0 2) CPU 1 from node 1 boots and creates a new top that corresponds to node 1, but it also connects the old root from node 0 to the new root from node 1 by mistake. [GRP1:0] node 1 / \ / \ [GRP0:0] [GRP0:1] node 0 node 1 | | CPU 0 CPU 1 3) This eventually leads to an imbalanced tree where some node 0 CPUs migrate node 1 timers (and vice versa) way before reaching the crossnode groups, resulting in more frequent remote memory accesses than expected. [GRP2:0] NUMA_NO_NODE / \ [GRP1:0] [GRP1:1] node 1 node 0 / \ | / \ [...] [GRP0:0] [GRP0:1] node 0 node 1 | | CPU 0... CPU 1... A balanced tree should only contain groups having children that belong to the same node: [GRP2:0] NUMA_NO_NODE / \ [GRP1:0] [GRP1:0] node 0 node 1 / \ / \ / \ / \ [GRP0:0] [...] [...] [GRP0:1] node 0 node 1 | | CPU 0... CPU 1... In order to fix this, the hierarchy must be unfolded up to the crossnode level as soon as a node mismatch is detected. For example the stage 2 above should lead to this layout: [GRP2:0] NUMA_NO_NODE / \ [GRP1:0] [GRP1:1] node 0 node 1 / \ / \ [GRP0:0] [GRP0:1] node 0 node 1 | | CPU 0 CPU 1 This means that not only GRP1:0 must be created but also GRP1:1 and GRP2:0 in order to prepare a balanced tree for next CPUs to boot. Fixes: 7ee988770326 ("timers: Implement the hierarchical pull model") Signed-off-by: Frederic Weisbecker Signed-off-by: Thomas Gleixner Link: https://patch.msgid.link/20251024132536.39841-4-frederic@kernel.org Signed-off-by: Sasha Levin --- kernel/time/timer_migration.c | 231 +++++++++++++++++++--------------- 1 file changed, 127 insertions(+), 104 deletions(-) diff --git a/kernel/time/timer_migration.c b/kernel/time/timer_migration.c index 5f8aef94ca0f71..49635a2b7ee28d 100644 --- a/kernel/time/timer_migration.c +++ b/kernel/time/timer_migration.c @@ -420,6 +420,8 @@ static struct list_head *tmigr_level_list __read_mostly; static unsigned int tmigr_hierarchy_levels __read_mostly; static unsigned int tmigr_crossnode_level __read_mostly; +static struct tmigr_group *tmigr_root; + static DEFINE_PER_CPU(struct tmigr_cpu, tmigr_cpu); #define TMIGR_NONE 0xFF @@ -522,11 +524,9 @@ struct tmigr_walk { typedef bool (*up_f)(struct tmigr_group *, struct tmigr_group *, struct tmigr_walk *); -static void __walk_groups(up_f up, struct tmigr_walk *data, - struct tmigr_cpu *tmc) +static void __walk_groups_from(up_f up, struct tmigr_walk *data, + struct tmigr_group *child, struct tmigr_group *group) { - struct tmigr_group *child = NULL, *group = tmc->tmgroup; - do { WARN_ON_ONCE(group->level >= tmigr_hierarchy_levels); @@ -544,6 +544,12 @@ static void __walk_groups(up_f up, struct tmigr_walk *data, } while (group); } +static void __walk_groups(up_f up, struct tmigr_walk *data, + struct tmigr_cpu *tmc) +{ + __walk_groups_from(up, data, NULL, tmc->tmgroup); +} + static void walk_groups(up_f up, struct tmigr_walk *data, struct tmigr_cpu *tmc) { lockdep_assert_held(&tmc->lock); @@ -1498,21 +1504,6 @@ static void tmigr_init_group(struct tmigr_group *group, unsigned int lvl, s.seq = 0; atomic_set(&group->migr_state, s.state); - /* - * If this is a new top-level, prepare its groupmask in advance. - * This avoids accidents where yet another new top-level is - * created in the future and made visible before the current groupmask. - */ - if (list_empty(&tmigr_level_list[lvl])) { - group->groupmask = BIT(0); - /* - * The previous top level has prepared its groupmask already, - * simply account it as the first child. - */ - if (lvl > 0) - group->num_children = 1; - } - timerqueue_init_head(&group->events); timerqueue_init(&group->groupevt.nextevt); group->groupevt.nextevt.expires = KTIME_MAX; @@ -1567,22 +1558,51 @@ static struct tmigr_group *tmigr_get_group(unsigned int cpu, int node, return group; } +static bool tmigr_init_root(struct tmigr_group *group, bool activate) +{ + if (!group->parent && group != tmigr_root) { + /* + * This is the new top-level, prepare its groupmask in advance + * to avoid accidents where yet another new top-level is + * created in the future and made visible before this groupmask. + */ + group->groupmask = BIT(0); + WARN_ON_ONCE(activate); + + return true; + } + + return false; + +} + static void tmigr_connect_child_parent(struct tmigr_group *child, struct tmigr_group *parent, bool activate) { - struct tmigr_walk data; + if (tmigr_init_root(parent, activate)) { + /* + * The previous top level had prepared its groupmask already, + * simply account it in advance as the first child. If some groups + * have been created between the old and new root due to node + * mismatch, the new root's child will be intialized accordingly. + */ + parent->num_children = 1; + } - if (activate) { + /* Connecting old root to new root ? */ + if (!parent->parent && activate) { /* - * @child is the old top and @parent the new one. In this - * case groupmask is pre-initialized and @child already - * accounted, along with its new sibling corresponding to the - * CPU going up. + * @child is the old top, or in case of node mismatch, some + * intermediate group between the old top and the new one in + * @parent. In this case the @child must be pre-accounted above + * as the first child. Its new inactive sibling corresponding + * to the CPU going up has been accounted as the second child. */ - WARN_ON_ONCE(child->groupmask != BIT(0) || parent->num_children != 2); + WARN_ON_ONCE(parent->num_children != 2); + child->groupmask = BIT(0); } else { - /* Adding @child for the CPU going up to @parent. */ + /* Common case adding @child for the CPU going up to @parent. */ child->groupmask = BIT(parent->num_children++); } @@ -1594,56 +1614,28 @@ static void tmigr_connect_child_parent(struct tmigr_group *child, smp_store_release(&child->parent, parent); trace_tmigr_connect_child_parent(child); - - if (!activate) - return; - - /* - * To prevent inconsistent states, active children need to be active in - * the new parent as well. Inactive children are already marked inactive - * in the parent group: - * - * * When new groups were created by tmigr_setup_groups() starting from - * the lowest level (and not higher then one level below the current - * top level), then they are not active. They will be set active when - * the new online CPU comes active. - * - * * But if a new group above the current top level is required, it is - * mandatory to propagate the active state of the already existing - * child to the new parent. So tmigr_connect_child_parent() is - * executed with the formerly top level group (child) and the newly - * created group (parent). - * - * * It is ensured that the child is active, as this setup path is - * executed in hotplug prepare callback. This is exectued by an - * already connected and !idle CPU. Even if all other CPUs go idle, - * the CPU executing the setup will be responsible up to current top - * level group. And the next time it goes inactive, it will release - * the new childmask and parent to subsequent walkers through this - * @child. Therefore propagate active state unconditionally. - */ - data.childmask = child->groupmask; - - /* - * There is only one new level per time (which is protected by - * tmigr_mutex). When connecting the child and the parent and set the - * child active when the parent is inactive, the parent needs to be the - * uppermost level. Otherwise there went something wrong! - */ - WARN_ON(!tmigr_active_up(parent, child, &data) && parent->parent); } -static int tmigr_setup_groups(unsigned int cpu, unsigned int node) +static int tmigr_setup_groups(unsigned int cpu, unsigned int node, + struct tmigr_group *start, bool activate) { struct tmigr_group *group, *child, **stack; - int i, top = 0, err = 0; - struct list_head *lvllist; + int i, top = 0, err = 0, start_lvl = 0; + bool root_mismatch = false; stack = kcalloc(tmigr_hierarchy_levels, sizeof(*stack), GFP_KERNEL); if (!stack) return -ENOMEM; - for (i = 0; i < tmigr_hierarchy_levels; i++) { + if (start) { + stack[start->level] = start; + start_lvl = start->level + 1; + } + + if (tmigr_root) + root_mismatch = tmigr_root->numa_node != node; + + for (i = start_lvl; i < tmigr_hierarchy_levels; i++) { group = tmigr_get_group(cpu, node, i); if (IS_ERR(group)) { err = PTR_ERR(group); @@ -1656,23 +1648,25 @@ static int tmigr_setup_groups(unsigned int cpu, unsigned int node) /* * When booting only less CPUs of a system than CPUs are - * available, not all calculated hierarchy levels are required. + * available, not all calculated hierarchy levels are required, + * unless a node mismatch is detected. * * The loop is aborted as soon as the highest level, which might * be different from tmigr_hierarchy_levels, contains only a - * single group. + * single group, unless the nodes mismatch below tmigr_crossnode_level */ - if (group->parent || list_is_singular(&tmigr_level_list[i])) + if (group->parent) + break; + if ((!root_mismatch || i >= tmigr_crossnode_level) && + list_is_singular(&tmigr_level_list[i])) break; } /* Assert single root without parent */ if (WARN_ON_ONCE(i >= tmigr_hierarchy_levels)) return -EINVAL; - if (WARN_ON_ONCE(!err && !group->parent && !list_is_singular(&tmigr_level_list[top]))) - return -EINVAL; - for (; i >= 0; i--) { + for (; i >= start_lvl; i--) { group = stack[i]; if (err < 0) { @@ -1692,48 +1686,63 @@ static int tmigr_setup_groups(unsigned int cpu, unsigned int node) tmc->tmgroup = group; tmc->groupmask = BIT(group->num_children++); + tmigr_init_root(group, activate); + trace_tmigr_connect_cpu_parent(tmc); /* There are no children that need to be connected */ continue; } else { child = stack[i - 1]; - /* Will be activated at online time */ - tmigr_connect_child_parent(child, group, false); + tmigr_connect_child_parent(child, group, activate); } + } - /* check if uppermost level was newly created */ - if (top != i) - continue; - - WARN_ON_ONCE(top == 0); + if (err < 0) + goto out; - lvllist = &tmigr_level_list[top]; + if (activate) { + struct tmigr_walk data; /* - * Newly created root level should have accounted the upcoming - * CPU's child group and pre-accounted the old root. + * To prevent inconsistent states, active children need to be active in + * the new parent as well. Inactive children are already marked inactive + * in the parent group: + * + * * When new groups were created by tmigr_setup_groups() starting from + * the lowest level, then they are not active. They will be set active + * when the new online CPU comes active. + * + * * But if new groups above the current top level are required, it is + * mandatory to propagate the active state of the already existing + * child to the new parents. So tmigr_active_up() activates the + * new parents while walking up from the old root to the new. + * + * * It is ensured that @start is active, as this setup path is + * executed in hotplug prepare callback. This is executed by an + * already connected and !idle CPU. Even if all other CPUs go idle, + * the CPU executing the setup will be responsible up to current top + * level group. And the next time it goes inactive, it will release + * the new childmask and parent to subsequent walkers through this + * @child. Therefore propagate active state unconditionally. */ - if (group->num_children == 2 && list_is_singular(lvllist)) { - /* - * The target CPU must never do the prepare work, except - * on early boot when the boot CPU is the target. Otherwise - * it may spuriously activate the old top level group inside - * the new one (nevertheless whether old top level group is - * active or not) and/or release an uninitialized childmask. - */ - WARN_ON_ONCE(cpu == raw_smp_processor_id()); - - lvllist = &tmigr_level_list[top - 1]; - list_for_each_entry(child, lvllist, list) { - if (child->parent) - continue; + WARN_ON_ONCE(!start->parent); + data.childmask = start->groupmask; + __walk_groups_from(tmigr_active_up, &data, start, start->parent); + } - tmigr_connect_child_parent(child, group, true); - } + /* Root update */ + if (list_is_singular(&tmigr_level_list[top])) { + group = list_first_entry(&tmigr_level_list[top], + typeof(*group), list); + WARN_ON_ONCE(group->parent); + if (tmigr_root) { + /* Old root should be the same or below */ + WARN_ON_ONCE(tmigr_root->level > top); } + tmigr_root = group; } - +out: kfree(stack); return err; @@ -1741,12 +1750,26 @@ static int tmigr_setup_groups(unsigned int cpu, unsigned int node) static int tmigr_add_cpu(unsigned int cpu) { + struct tmigr_group *old_root = tmigr_root; int node = cpu_to_node(cpu); int ret; - mutex_lock(&tmigr_mutex); - ret = tmigr_setup_groups(cpu, node); - mutex_unlock(&tmigr_mutex); + guard(mutex)(&tmigr_mutex); + + ret = tmigr_setup_groups(cpu, node, NULL, false); + + /* Root has changed? Connect the old one to the new */ + if (ret >= 0 && old_root && old_root != tmigr_root) { + /* + * The target CPU must never do the prepare work, except + * on early boot when the boot CPU is the target. Otherwise + * it may spuriously activate the old top level group inside + * the new one (nevertheless whether old top level group is + * active or not) and/or release an uninitialized childmask. + */ + WARN_ON_ONCE(cpu == raw_smp_processor_id()); + ret = tmigr_setup_groups(-1, old_root->numa_node, old_root, true); + } return ret; } From 8c473255e5ee235e91bc5a41bbf60431a16a2db6 Mon Sep 17 00:00:00 2001 From: Haotian Zhang Date: Mon, 29 Sep 2025 19:32:34 +0800 Subject: [PATCH 0545/3902] power: supply: rt5033_charger: Fix device node reference leaks [ Upstream commit 6cdc4d488c2f3a61174bfba4e8cc4ac92c219258 ] The device node pointers `np_conn` and `np_edev`, obtained from of_parse_phandle() and of_get_parent() respectively, are not released. This results in a reference count leak. Add of_node_put() calls after the last use of these device nodes to properly release their references and fix the leaks. Fixes: 8242336dc8a8 ("power: supply: rt5033_charger: Add cable detection and USB OTG supply") Signed-off-by: Haotian Zhang Link: https://patch.msgid.link/20250929113234.1726-1-vulab@iscas.ac.cn Signed-off-by: Sebastian Reichel Signed-off-by: Sasha Levin --- drivers/power/supply/rt5033_charger.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/drivers/power/supply/rt5033_charger.c b/drivers/power/supply/rt5033_charger.c index 2fdc5843970754..de724f23e453b3 100644 --- a/drivers/power/supply/rt5033_charger.c +++ b/drivers/power/supply/rt5033_charger.c @@ -701,6 +701,8 @@ static int rt5033_charger_probe(struct platform_device *pdev) np_conn = of_parse_phandle(pdev->dev.of_node, "richtek,usb-connector", 0); np_edev = of_get_parent(np_conn); charger->edev = extcon_find_edev_by_node(np_edev); + of_node_put(np_edev); + of_node_put(np_conn); if (IS_ERR(charger->edev)) { dev_warn(charger->dev, "no extcon device found in device-tree\n"); goto out; From 97ec116a7e57439081fa7567c618b39d833a7625 Mon Sep 17 00:00:00 2001 From: Ivan Abramov Date: Wed, 8 Oct 2025 15:07:11 +0300 Subject: [PATCH 0546/3902] power: supply: cw2015: Check devm_delayed_work_autocancel() return code [ Upstream commit 92ec7e7b86ec0aff9cd7db64d9dce50a0ea7c542 ] Since devm_delayed_work_autocancel() may fail, add return code check and exit cw_bat_probe() on error. Found by Linux Verification Center (linuxtesting.org) with SVACE. Fixes: 0cb172a4918e ("power: supply: cw2015: Use device managed API to simplify the code") Signed-off-by: Ivan Abramov Link: https://patch.msgid.link/20251008120711.556021-1-i.abramov@mt-integration.ru Signed-off-by: Sebastian Reichel Signed-off-by: Sasha Levin --- drivers/power/supply/cw2015_battery.c | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/drivers/power/supply/cw2015_battery.c b/drivers/power/supply/cw2015_battery.c index 2263d5d3448fdf..0806abea2372fe 100644 --- a/drivers/power/supply/cw2015_battery.c +++ b/drivers/power/supply/cw2015_battery.c @@ -699,7 +699,13 @@ static int cw_bat_probe(struct i2c_client *client) if (!cw_bat->battery_workqueue) return -ENOMEM; - devm_delayed_work_autocancel(&client->dev, &cw_bat->battery_delay_work, cw_bat_work); + ret = devm_delayed_work_autocancel(&client->dev, &cw_bat->battery_delay_work, cw_bat_work); + if (ret) { + dev_err_probe(&client->dev, ret, + "Failed to register delayed work\n"); + return ret; + } + queue_delayed_work(cw_bat->battery_workqueue, &cw_bat->battery_delay_work, msecs_to_jiffies(10)); return 0; From 1cea1ca5e8d4a6e03eda1bb2747c850103a8cd57 Mon Sep 17 00:00:00 2001 From: Ivan Abramov Date: Wed, 8 Oct 2025 16:36:47 +0300 Subject: [PATCH 0547/3902] power: supply: max17040: Check iio_read_channel_processed() return code [ Upstream commit 2c68ac48c52ad146523f32b01d70009622bf81aa ] Since iio_read_channel_processed() may fail, return its exit code on error. Found by Linux Verification Center (linuxtesting.org) with SVACE. Fixes: 814755c48f8b ("power: max17040: get thermal data from adc if available") Signed-off-by: Ivan Abramov Link: https://patch.msgid.link/20251008133648.559286-1-i.abramov@mt-integration.ru Signed-off-by: Sebastian Reichel Signed-off-by: Sasha Levin --- drivers/power/supply/max17040_battery.c | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/drivers/power/supply/max17040_battery.c b/drivers/power/supply/max17040_battery.c index c1640bc6accd27..48453508688a49 100644 --- a/drivers/power/supply/max17040_battery.c +++ b/drivers/power/supply/max17040_battery.c @@ -388,6 +388,7 @@ static int max17040_get_property(struct power_supply *psy, union power_supply_propval *val) { struct max17040_chip *chip = power_supply_get_drvdata(psy); + int ret; switch (psp) { case POWER_SUPPLY_PROP_ONLINE: @@ -410,7 +411,10 @@ static int max17040_get_property(struct power_supply *psy, if (!chip->channel_temp) return -ENODATA; - iio_read_channel_processed(chip->channel_temp, &val->intval); + ret = iio_read_channel_processed(chip->channel_temp, &val->intval); + if (ret) + return ret; + val->intval /= 100; /* Convert from milli- to deci-degree */ break; From 52d3f2076122ad2d0ea11b32e3f45ae05df4180e Mon Sep 17 00:00:00 2001 From: Ivan Abramov Date: Thu, 9 Oct 2025 17:47:24 +0300 Subject: [PATCH 0548/3902] power: supply: rt9467: Return error on failure in rt9467_set_value_from_ranges() [ Upstream commit 8b27fe2d8d2380118c343629175385ff587e2fe4 ] The return value of rt9467_set_value_from_ranges() when setting AICL VTH is not checked, even though it may fail. Log error and return from rt9467_run_aicl() on fail. Found by Linux Verification Center (linuxtesting.org) with SVACE. Fixes: 6f7f70e3a8dd ("power: supply: rt9467: Add Richtek RT9467 charger driver") Signed-off-by: Ivan Abramov Link: https://patch.msgid.link/20251009144725.562278-1-i.abramov@mt-integration.ru Signed-off-by: Sebastian Reichel Signed-off-by: Sasha Levin --- drivers/power/supply/rt9467-charger.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/drivers/power/supply/rt9467-charger.c b/drivers/power/supply/rt9467-charger.c index fe773dd8b404f3..b4917514bd701c 100644 --- a/drivers/power/supply/rt9467-charger.c +++ b/drivers/power/supply/rt9467-charger.c @@ -588,6 +588,10 @@ static int rt9467_run_aicl(struct rt9467_chg_data *data) aicl_vth = mivr_vth + RT9467_AICLVTH_GAP_uV; ret = rt9467_set_value_from_ranges(data, F_AICL_VTH, RT9467_RANGE_AICL_VTH, aicl_vth); + if (ret) { + dev_err(data->dev, "Failed to set AICL VTH\n"); + return ret; + } /* Trigger AICL function */ ret = regmap_field_write(data->rm_field[F_AICL_MEAS], 1); From a77929363637c5625ee58d5eafa3736cb69126b5 Mon Sep 17 00:00:00 2001 From: Murad Masimov Date: Thu, 9 Oct 2025 17:53:08 +0300 Subject: [PATCH 0549/3902] power: supply: rt9467: Prevent using uninitialized local variable in rt9467_set_value_from_ranges() [ Upstream commit 15aca30cc6c69806054b896a2ccf7577239cb878 ] There is a typo in rt9467_set_value_from_ranges() that can cause leaving local variable sel with an undefined value which is then used in regmap_field_write(). Found by Linux Verification Center (linuxtesting.org) with SVACE. Fixes: 6f7f70e3a8dd ("power: supply: rt9467: Add Richtek RT9467 charger driver") Signed-off-by: Murad Masimov Link: https://patch.msgid.link/20251009145308.1830893-1-m.masimov@mt-integration.ru Signed-off-by: Sebastian Reichel Signed-off-by: Sasha Levin --- drivers/power/supply/rt9467-charger.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/power/supply/rt9467-charger.c b/drivers/power/supply/rt9467-charger.c index b4917514bd701c..44c26fb37a775f 100644 --- a/drivers/power/supply/rt9467-charger.c +++ b/drivers/power/supply/rt9467-charger.c @@ -376,7 +376,7 @@ static int rt9467_set_value_from_ranges(struct rt9467_chg_data *data, if (rsel == RT9467_RANGE_VMIVR) { ret = linear_range_get_selector_high(range, value, &sel, &found); if (ret) - value = range->max_sel; + sel = range->max_sel; } else { linear_range_get_selector_within(range, value, &sel); } From eade8ea73b91a2fc03c0dfb89ec3c829938fffce Mon Sep 17 00:00:00 2001 From: Ivan Abramov Date: Thu, 9 Oct 2025 20:05:52 +0300 Subject: [PATCH 0550/3902] power: supply: wm831x: Check wm831x_set_bits() return value [ Upstream commit ea14bae6df18942bccb467fcf5ff33ca677b8253 ] Since wm831x_set_bits() may return error, log failure and exit from wm831x_usb_limit_change() in such case. Found by Linux Verification Center (linuxtesting.org) with SVACE. Fixes: 626b6cd5f52e ("power: wm831x_power: Support USB charger current limit management") Signed-off-by: Ivan Abramov Link: https://patch.msgid.link/20251009170553.566561-1-i.abramov@mt-integration.ru Signed-off-by: Sebastian Reichel Signed-off-by: Sasha Levin --- drivers/power/supply/wm831x_power.c | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/drivers/power/supply/wm831x_power.c b/drivers/power/supply/wm831x_power.c index 6acdba7885ca57..78fa0573ef25c7 100644 --- a/drivers/power/supply/wm831x_power.c +++ b/drivers/power/supply/wm831x_power.c @@ -144,6 +144,7 @@ static int wm831x_usb_limit_change(struct notifier_block *nb, struct wm831x_power, usb_notify); unsigned int i, best; + int ret; /* Find the highest supported limit */ best = 0; @@ -156,8 +157,13 @@ static int wm831x_usb_limit_change(struct notifier_block *nb, dev_dbg(wm831x_power->wm831x->dev, "Limiting USB current to %umA", wm831x_usb_limits[best]); - wm831x_set_bits(wm831x_power->wm831x, WM831X_POWER_STATE, - WM831X_USB_ILIM_MASK, best); + ret = wm831x_set_bits(wm831x_power->wm831x, WM831X_POWER_STATE, + WM831X_USB_ILIM_MASK, best); + if (ret < 0) { + dev_err(wm831x_power->wm831x->dev, + "Failed to set USB current limit: %d\n", ret); + return ret; + } return 0; } From 2614ebcf2bca48e5f2b4ca1835870db4d1e65a0b Mon Sep 17 00:00:00 2001 From: Val Packett Date: Sun, 12 Oct 2025 20:32:18 -0300 Subject: [PATCH 0551/3902] power: supply: qcom_battmgr: clamp charge control thresholds [ Upstream commit 8809980fdc8a86070667032fa4005ee83f1c62f3 ] The sysfs API documentation says that drivers "round written values to the nearest supported value" for charge_control_end_threshold. Let's do this for both thresholds, as userspace (e.g. upower) generally does not expect these writes to fail at all. Fixes: cc3e883a0625 ("power: supply: qcom_battmgr: Add charge control support") Signed-off-by: Val Packett Link: https://patch.msgid.link/20251012233333.19144-3-val@packett.cool Signed-off-by: Sebastian Reichel Signed-off-by: Sasha Levin --- drivers/power/supply/qcom_battmgr.c | 14 ++------------ 1 file changed, 2 insertions(+), 12 deletions(-) diff --git a/drivers/power/supply/qcom_battmgr.c b/drivers/power/supply/qcom_battmgr.c index 3c2837ef346173..c8028606bba009 100644 --- a/drivers/power/supply/qcom_battmgr.c +++ b/drivers/power/supply/qcom_battmgr.c @@ -678,12 +678,7 @@ static int qcom_battmgr_set_charge_start_threshold(struct qcom_battmgr *battmgr, u32 target_soc, delta_soc; int ret; - if (start_soc < CHARGE_CTRL_START_THR_MIN || - start_soc > CHARGE_CTRL_START_THR_MAX) { - dev_err(battmgr->dev, "charge control start threshold exceed range: [%u - %u]\n", - CHARGE_CTRL_START_THR_MIN, CHARGE_CTRL_START_THR_MAX); - return -EINVAL; - } + start_soc = clamp(start_soc, CHARGE_CTRL_START_THR_MIN, CHARGE_CTRL_START_THR_MAX); /* * If the new start threshold is larger than the old end threshold, @@ -716,12 +711,7 @@ static int qcom_battmgr_set_charge_end_threshold(struct qcom_battmgr *battmgr, i u32 delta_soc = CHARGE_CTRL_DELTA_SOC; int ret; - if (end_soc < CHARGE_CTRL_END_THR_MIN || - end_soc > CHARGE_CTRL_END_THR_MAX) { - dev_err(battmgr->dev, "charge control end threshold exceed range: [%u - %u]\n", - CHARGE_CTRL_END_THR_MIN, CHARGE_CTRL_END_THR_MAX); - return -EINVAL; - } + end_soc = clamp(end_soc, CHARGE_CTRL_END_THR_MIN, CHARGE_CTRL_END_THR_MAX); if (battmgr->info.charge_ctrl_start && end_soc > battmgr->info.charge_ctrl_start) delta_soc = end_soc - battmgr->info.charge_ctrl_start; From 69675b2d60416cbcc8de89e350124d0d33f8cae7 Mon Sep 17 00:00:00 2001 From: Val Packett Date: Sun, 12 Oct 2025 20:32:19 -0300 Subject: [PATCH 0552/3902] power: supply: qcom_battmgr: support disabling charge control [ Upstream commit 446fcf494691da4e685923e5fad02b163955fc0e ] Existing userspace (in particular, upower) disables charge control by setting the start threshold to 0 and the stop threshold to 100. Handle that by actually setting the enable bit to 0 when a start threshold of 0 was requested. Fixes: cc3e883a0625 ("power: supply: qcom_battmgr: Add charge control support") Signed-off-by: Val Packett Link: https://patch.msgid.link/20251012233333.19144-4-val@packett.cool Signed-off-by: Sebastian Reichel Signed-off-by: Sasha Levin --- drivers/power/supply/qcom_battmgr.c | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/drivers/power/supply/qcom_battmgr.c b/drivers/power/supply/qcom_battmgr.c index c8028606bba009..e6f01e0122e1c1 100644 --- a/drivers/power/supply/qcom_battmgr.c +++ b/drivers/power/supply/qcom_battmgr.c @@ -257,6 +257,7 @@ struct qcom_battmgr_info { unsigned int capacity_warning; unsigned int cycle_count; unsigned int charge_count; + bool charge_ctrl_enable; unsigned int charge_ctrl_start; unsigned int charge_ctrl_end; char model_number[BATTMGR_STRING_LEN]; @@ -659,13 +660,13 @@ static int qcom_battmgr_bat_get_property(struct power_supply *psy, } static int qcom_battmgr_set_charge_control(struct qcom_battmgr *battmgr, - u32 target_soc, u32 delta_soc) + bool enable, u32 target_soc, u32 delta_soc) { struct qcom_battmgr_charge_ctrl_request request = { .hdr.owner = cpu_to_le32(PMIC_GLINK_OWNER_BATTMGR), .hdr.type = cpu_to_le32(PMIC_GLINK_REQ_RESP), .hdr.opcode = cpu_to_le32(BATTMGR_CHG_CTRL_LIMIT_EN), - .enable = cpu_to_le32(1), + .enable = cpu_to_le32(enable), .target_soc = cpu_to_le32(target_soc), .delta_soc = cpu_to_le32(delta_soc), }; @@ -677,6 +678,7 @@ static int qcom_battmgr_set_charge_start_threshold(struct qcom_battmgr *battmgr, { u32 target_soc, delta_soc; int ret; + bool enable = start_soc != 0; start_soc = clamp(start_soc, CHARGE_CTRL_START_THR_MIN, CHARGE_CTRL_START_THR_MAX); @@ -696,9 +698,10 @@ static int qcom_battmgr_set_charge_start_threshold(struct qcom_battmgr *battmgr, } mutex_lock(&battmgr->lock); - ret = qcom_battmgr_set_charge_control(battmgr, target_soc, delta_soc); + ret = qcom_battmgr_set_charge_control(battmgr, enable, target_soc, delta_soc); mutex_unlock(&battmgr->lock); if (!ret) { + battmgr->info.charge_ctrl_enable = enable; battmgr->info.charge_ctrl_start = start_soc; battmgr->info.charge_ctrl_end = target_soc; } @@ -710,6 +713,7 @@ static int qcom_battmgr_set_charge_end_threshold(struct qcom_battmgr *battmgr, i { u32 delta_soc = CHARGE_CTRL_DELTA_SOC; int ret; + bool enable = battmgr->info.charge_ctrl_enable; end_soc = clamp(end_soc, CHARGE_CTRL_END_THR_MIN, CHARGE_CTRL_END_THR_MAX); @@ -717,7 +721,7 @@ static int qcom_battmgr_set_charge_end_threshold(struct qcom_battmgr *battmgr, i delta_soc = end_soc - battmgr->info.charge_ctrl_start; mutex_lock(&battmgr->lock); - ret = qcom_battmgr_set_charge_control(battmgr, end_soc, delta_soc); + ret = qcom_battmgr_set_charge_control(battmgr, enable, end_soc, delta_soc); mutex_unlock(&battmgr->lock); if (!ret) { battmgr->info.charge_ctrl_start = end_soc - delta_soc; From fb8f2b55a101c3c71440b417e61825ffb614ba71 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ahelenia=20Ziemia=C5=84ska?= Date: Fri, 17 Oct 2025 00:05:18 +0200 Subject: [PATCH 0553/3902] power: supply: apm_power: only unset own apm_get_power_status MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [ Upstream commit bd44ea12919ac4e83c9f3997240fe58266aa8799 ] Mirroring drivers/macintosh/apm_emu.c, this means that modprobe apm_power && modprobe $anotherdriver && modprobe -r apm_power leaves $anotherdriver's apm_get_power_status instead of deleting it. Fixes: 3788ec932bfd ("[BATTERY] APM emulation driver for class batteries") Signed-off-by: Ahelenia Ziemiańska Link: https://patch.msgid.link/xczpgox57hxbunkcbdl5fxhc4gnsajsipldfidi7355afezk64@tarta.nabijaczleweli.xyz Signed-off-by: Sebastian Reichel Signed-off-by: Sasha Levin --- drivers/power/supply/apm_power.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/drivers/power/supply/apm_power.c b/drivers/power/supply/apm_power.c index 9236e007857860..9933cdc5c387a1 100644 --- a/drivers/power/supply/apm_power.c +++ b/drivers/power/supply/apm_power.c @@ -364,7 +364,8 @@ static int __init apm_battery_init(void) static void __exit apm_battery_exit(void) { - apm_get_power_status = NULL; + if (apm_get_power_status == apm_battery_apm_get_power_status) + apm_get_power_status = NULL; } module_init(apm_battery_init); From e4a462b4e9961284a4f6e1f427c70f1b3f523d75 Mon Sep 17 00:00:00 2001 From: Bart Van Assche Date: Mon, 27 Oct 2025 11:46:38 -0700 Subject: [PATCH 0554/3902] scsi: target: Do not write NUL characters into ASCII configfs output [ Upstream commit c03b55f235e283cae49c88b9602fd11096b92eba ] NUL characters are not allowed in ASCII configfs output. Hence this patch. Fixes: c66ac9db8d4a ("[SCSI] target: Add LIO target core v4.0.0-rc6") Signed-off-by: Bart Van Assche Link: https://patch.msgid.link/20251027184639.3501254-2-bvanassche@acm.org Signed-off-by: Martin K. Petersen Signed-off-by: Sasha Levin --- drivers/target/target_core_configfs.c | 1 - 1 file changed, 1 deletion(-) diff --git a/drivers/target/target_core_configfs.c b/drivers/target/target_core_configfs.c index b19acd662726d4..1bd28482e7cb3e 100644 --- a/drivers/target/target_core_configfs.c +++ b/drivers/target/target_core_configfs.c @@ -2772,7 +2772,6 @@ static ssize_t target_lu_gp_members_show(struct config_item *item, char *page) cur_len = snprintf(buf, LU_GROUP_NAME_BUF, "%s/%s\n", config_item_name(&hba->hba_group.cg_item), config_item_name(&dev->dev_group.cg_item)); - cur_len++; /* Extra byte for NULL terminator */ if ((cur_len + len) > PAGE_SIZE || cur_len > LU_GROUP_NAME_BUF) { pr_warn("Ran out of lu_gp_show_attr" From 8d11a6c811a5f58cb13af7b6417177b834df8edc Mon Sep 17 00:00:00 2001 From: Mike Christie Date: Wed, 17 Sep 2025 17:12:53 -0500 Subject: [PATCH 0555/3902] scsi: target: Fix LUN/device R/W and total command stats [ Upstream commit 95aa2041c654161d1b5c1eca5379d67d91ef1cf2 ] In commit 9cf2317b795d ("scsi: target: Move I/O path stats to per CPU") I saw we sometimes use %u and also misread the spec. As a result I thought all the stats were supposed to be 32-bit only. However, for the majority of cases we support currently, the spec specifies u64 bit stats. This patch converts the stats changed in the commit above to u64. Fixes: 9cf2317b795d ("scsi: target: Move I/O path stats to per CPU") Signed-off-by: Mike Christie Reviewed-by: Dmitry Bogdanov Link: https://patch.msgid.link/20250917221338.14813-2-michael.christie@oracle.com Signed-off-by: Martin K. Petersen Signed-off-by: Sasha Levin --- drivers/target/target_core_stat.c | 24 ++++++++++++------------ include/target/target_core_base.h | 12 ++++++------ 2 files changed, 18 insertions(+), 18 deletions(-) diff --git a/drivers/target/target_core_stat.c b/drivers/target/target_core_stat.c index 6bdf2d8bd69422..4fdc307ea38bc8 100644 --- a/drivers/target/target_core_stat.c +++ b/drivers/target/target_core_stat.c @@ -282,7 +282,7 @@ static ssize_t target_stat_lu_num_cmds_show(struct config_item *item, struct se_device *dev = to_stat_lu_dev(item); struct se_dev_io_stats *stats; unsigned int cpu; - u32 cmds = 0; + u64 cmds = 0; for_each_possible_cpu(cpu) { stats = per_cpu_ptr(dev->stats, cpu); @@ -290,7 +290,7 @@ static ssize_t target_stat_lu_num_cmds_show(struct config_item *item, } /* scsiLuNumCommands */ - return snprintf(page, PAGE_SIZE, "%u\n", cmds); + return snprintf(page, PAGE_SIZE, "%llu\n", cmds); } static ssize_t target_stat_lu_read_mbytes_show(struct config_item *item, @@ -299,7 +299,7 @@ static ssize_t target_stat_lu_read_mbytes_show(struct config_item *item, struct se_device *dev = to_stat_lu_dev(item); struct se_dev_io_stats *stats; unsigned int cpu; - u32 bytes = 0; + u64 bytes = 0; for_each_possible_cpu(cpu) { stats = per_cpu_ptr(dev->stats, cpu); @@ -307,7 +307,7 @@ static ssize_t target_stat_lu_read_mbytes_show(struct config_item *item, } /* scsiLuReadMegaBytes */ - return snprintf(page, PAGE_SIZE, "%u\n", bytes >> 20); + return snprintf(page, PAGE_SIZE, "%llu\n", bytes >> 20); } static ssize_t target_stat_lu_write_mbytes_show(struct config_item *item, @@ -316,7 +316,7 @@ static ssize_t target_stat_lu_write_mbytes_show(struct config_item *item, struct se_device *dev = to_stat_lu_dev(item); struct se_dev_io_stats *stats; unsigned int cpu; - u32 bytes = 0; + u64 bytes = 0; for_each_possible_cpu(cpu) { stats = per_cpu_ptr(dev->stats, cpu); @@ -324,7 +324,7 @@ static ssize_t target_stat_lu_write_mbytes_show(struct config_item *item, } /* scsiLuWrittenMegaBytes */ - return snprintf(page, PAGE_SIZE, "%u\n", bytes >> 20); + return snprintf(page, PAGE_SIZE, "%llu\n", bytes >> 20); } static ssize_t target_stat_lu_resets_show(struct config_item *item, char *page) @@ -1044,7 +1044,7 @@ static ssize_t target_stat_auth_num_cmds_show(struct config_item *item, struct se_dev_entry *deve; unsigned int cpu; ssize_t ret; - u32 cmds = 0; + u64 cmds = 0; rcu_read_lock(); deve = target_nacl_find_deve(nacl, lacl->mapped_lun); @@ -1059,7 +1059,7 @@ static ssize_t target_stat_auth_num_cmds_show(struct config_item *item, } /* scsiAuthIntrOutCommands */ - ret = snprintf(page, PAGE_SIZE, "%u\n", cmds); + ret = snprintf(page, PAGE_SIZE, "%llu\n", cmds); rcu_read_unlock(); return ret; } @@ -1073,7 +1073,7 @@ static ssize_t target_stat_auth_read_mbytes_show(struct config_item *item, struct se_dev_entry *deve; unsigned int cpu; ssize_t ret; - u32 bytes = 0; + u64 bytes = 0; rcu_read_lock(); deve = target_nacl_find_deve(nacl, lacl->mapped_lun); @@ -1088,7 +1088,7 @@ static ssize_t target_stat_auth_read_mbytes_show(struct config_item *item, } /* scsiAuthIntrReadMegaBytes */ - ret = snprintf(page, PAGE_SIZE, "%u\n", bytes >> 20); + ret = snprintf(page, PAGE_SIZE, "%llu\n", bytes >> 20); rcu_read_unlock(); return ret; } @@ -1102,7 +1102,7 @@ static ssize_t target_stat_auth_write_mbytes_show(struct config_item *item, struct se_dev_entry *deve; unsigned int cpu; ssize_t ret; - u32 bytes = 0; + u64 bytes = 0; rcu_read_lock(); deve = target_nacl_find_deve(nacl, lacl->mapped_lun); @@ -1117,7 +1117,7 @@ static ssize_t target_stat_auth_write_mbytes_show(struct config_item *item, } /* scsiAuthIntrWrittenMegaBytes */ - ret = snprintf(page, PAGE_SIZE, "%u\n", bytes >> 20); + ret = snprintf(page, PAGE_SIZE, "%llu\n", bytes >> 20); rcu_read_unlock(); return ret; } diff --git a/include/target/target_core_base.h b/include/target/target_core_base.h index c4d9116904aa0f..27e1f9d5f0c6cd 100644 --- a/include/target/target_core_base.h +++ b/include/target/target_core_base.h @@ -671,9 +671,9 @@ struct se_lun_acl { }; struct se_dev_entry_io_stats { - u32 total_cmds; - u32 read_bytes; - u32 write_bytes; + u64 total_cmds; + u64 read_bytes; + u64 write_bytes; }; struct se_dev_entry { @@ -806,9 +806,9 @@ struct se_device_queue { }; struct se_dev_io_stats { - u32 total_cmds; - u32 read_bytes; - u32 write_bytes; + u64 total_cmds; + u64 read_bytes; + u64 write_bytes; }; struct se_device { From 1254303ef82c84466a38a81238a81987ebfe00e5 Mon Sep 17 00:00:00 2001 From: Tingmao Wang Date: Sun, 2 Nov 2025 23:56:30 +0000 Subject: [PATCH 0556/3902] fs/9p: Don't open remote file with APPEND mode when writeback cache is used [ Upstream commit a63dd8fd137933551bfd9aeeeaa942f04c7aad65 ] When page cache is used, writebacks are done on a page granularity, and it is expected that the underlying filesystem (such as v9fs) should respect the write position. However, currently v9fs will passthrough O_APPEND to the server even on cached mode. This causes data corruption if a sync or fstat gets between two writes to the same file. This patch removes the APPEND flag from the open request we send to the server when writeback caching is involved. I believe keeping server-side APPEND is probably fine for uncached mode (even if two fds are opened, one without O_APPEND and one with it, this should still be fine since they would use separate fid for the writes). Signed-off-by: Tingmao Wang Fixes: 4eb3117888a9 ("fs/9p: Rework cache modes and add new options to Documentation") Message-ID: <20251102235631.8724-1-m@maowtm.org> Signed-off-by: Dominique Martinet Signed-off-by: Sasha Levin --- fs/9p/vfs_file.c | 11 ++++++++--- fs/9p/vfs_inode.c | 3 +-- fs/9p/vfs_inode_dotl.c | 2 +- 3 files changed, 10 insertions(+), 6 deletions(-) diff --git a/fs/9p/vfs_file.c b/fs/9p/vfs_file.c index eb0b083da269b4..d1db03093d4c3c 100644 --- a/fs/9p/vfs_file.c +++ b/fs/9p/vfs_file.c @@ -43,14 +43,18 @@ int v9fs_file_open(struct inode *inode, struct file *file) struct v9fs_session_info *v9ses; struct p9_fid *fid; int omode; + int o_append; p9_debug(P9_DEBUG_VFS, "inode: %p file: %p\n", inode, file); v9ses = v9fs_inode2v9ses(inode); - if (v9fs_proto_dotl(v9ses)) + if (v9fs_proto_dotl(v9ses)) { omode = v9fs_open_to_dotl_flags(file->f_flags); - else + o_append = P9_DOTL_APPEND; + } else { omode = v9fs_uflags2omode(file->f_flags, v9fs_proto_dotu(v9ses)); + o_append = P9_OAPPEND; + } fid = file->private_data; if (!fid) { fid = v9fs_fid_clone(file_dentry(file)); @@ -58,9 +62,10 @@ int v9fs_file_open(struct inode *inode, struct file *file) return PTR_ERR(fid); if ((v9ses->cache & CACHE_WRITEBACK) && (omode & P9_OWRITE)) { - int writeback_omode = (omode & ~P9_OWRITE) | P9_ORDWR; + int writeback_omode = (omode & ~(P9_OWRITE | o_append)) | P9_ORDWR; p9_debug(P9_DEBUG_CACHE, "write-only file with writeback enabled, try opening O_RDWR\n"); + err = p9_client_open(fid, writeback_omode); if (err < 0) { p9_debug(P9_DEBUG_CACHE, "could not open O_RDWR, disabling caches\n"); diff --git a/fs/9p/vfs_inode.c b/fs/9p/vfs_inode.c index d0c77ec31b1dd6..0f3189a0a516af 100644 --- a/fs/9p/vfs_inode.c +++ b/fs/9p/vfs_inode.c @@ -786,7 +786,7 @@ v9fs_vfs_atomic_open(struct inode *dir, struct dentry *dentry, p9_omode = v9fs_uflags2omode(flags, v9fs_proto_dotu(v9ses)); if ((v9ses->cache & CACHE_WRITEBACK) && (p9_omode & P9_OWRITE)) { - p9_omode = (p9_omode & ~P9_OWRITE) | P9_ORDWR; + p9_omode = (p9_omode & ~(P9_OWRITE | P9_OAPPEND)) | P9_ORDWR; p9_debug(P9_DEBUG_CACHE, "write-only file with writeback enabled, creating w/ O_RDWR\n"); } @@ -1393,4 +1393,3 @@ static const struct inode_operations v9fs_symlink_inode_operations = { .getattr = v9fs_vfs_getattr, .setattr = v9fs_vfs_setattr, }; - diff --git a/fs/9p/vfs_inode_dotl.c b/fs/9p/vfs_inode_dotl.c index be297e33546889..6312b3590f7438 100644 --- a/fs/9p/vfs_inode_dotl.c +++ b/fs/9p/vfs_inode_dotl.c @@ -282,7 +282,7 @@ v9fs_vfs_atomic_open_dotl(struct inode *dir, struct dentry *dentry, } if ((v9ses->cache & CACHE_WRITEBACK) && (p9_omode & P9_OWRITE)) { - p9_omode = (p9_omode & ~P9_OWRITE) | P9_ORDWR; + p9_omode = (p9_omode & ~(P9_OWRITE | P9_DOTL_APPEND)) | P9_ORDWR; p9_debug(P9_DEBUG_CACHE, "write-only file with writeback enabled, creating w/ O_RDWR\n"); } From 1292bd488a816a797c839765ce79f8cb2ef28ea0 Mon Sep 17 00:00:00 2001 From: Boris Brezillon Date: Fri, 31 Oct 2025 17:03:17 +0100 Subject: [PATCH 0557/3902] drm/panthor: Handle errors returned by drm_sched_entity_init() [ Upstream commit bb7939e332c64c4ef33974a0eae4f3841acfa8eb ] In practice it's not going to fail because we're passing the current sanity checks done by drm_sched_entity_init(), and that's the only reason it would return an error, but better safe than sorry. Fixes: de8548813824 ("drm/panthor: Add the scheduler logical block") Reviewed-by: Liviu Dudau Signed-off-by: Boris Brezillon Link: https://patch.msgid.link/20251031160318.832427-1-boris.brezillon@collabora.com Signed-off-by: Liviu Dudau Signed-off-by: Sasha Levin --- drivers/gpu/drm/panthor/panthor_sched.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/drivers/gpu/drm/panthor/panthor_sched.c b/drivers/gpu/drm/panthor/panthor_sched.c index 3d1f57e3990f4a..85ef9a7acc1477 100644 --- a/drivers/gpu/drm/panthor/panthor_sched.c +++ b/drivers/gpu/drm/panthor/panthor_sched.c @@ -3403,6 +3403,8 @@ group_create_queue(struct panthor_group *group, drm_sched = &queue->scheduler; ret = drm_sched_entity_init(&queue->entity, 0, &drm_sched, 1, NULL); + if (ret) + goto err_free_queue; return queue; From 1ec19ea882841ff9de37d94c7db908af6a3ba325 Mon Sep 17 00:00:00 2001 From: Boris Brezillon Date: Fri, 31 Oct 2025 17:03:18 +0100 Subject: [PATCH 0558/3902] drm/panthor: Fix group_free_queue() for partially initialized queues MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [ Upstream commit 94a6d20feadbbe24e8a7b1c56394789ea5358fcc ] group_free_queue() can be called on a partially initialized queue object if something fails in group_create_queue(). Make sure we don't call drm_sched_entity_destroy() on an entity that hasn't been initialized. Fixes: 7d9c3442b02a ("drm/panthor: Defer scheduler entitiy destruction to queue release") Reviewed-by: Adrián Larumbe Reviewed-by: Liviu Dudau Signed-off-by: Boris Brezillon Link: https://patch.msgid.link/20251031160318.832427-2-boris.brezillon@collabora.com Signed-off-by: Liviu Dudau Signed-off-by: Sasha Levin --- drivers/gpu/drm/panthor/panthor_sched.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/drivers/gpu/drm/panthor/panthor_sched.c b/drivers/gpu/drm/panthor/panthor_sched.c index 85ef9a7acc1477..a39f0fb370dc61 100644 --- a/drivers/gpu/drm/panthor/panthor_sched.c +++ b/drivers/gpu/drm/panthor/panthor_sched.c @@ -895,7 +895,8 @@ static void group_free_queue(struct panthor_group *group, struct panthor_queue * if (IS_ERR_OR_NULL(queue)) return; - drm_sched_entity_destroy(&queue->entity); + if (queue->entity.fence_context) + drm_sched_entity_destroy(&queue->entity); if (queue->scheduler.ops) drm_sched_fini(&queue->scheduler); From 6c1da9ae2c123a9ffda5375e64cc81f9ed3cc04a Mon Sep 17 00:00:00 2001 From: Ketil Johnsen Date: Mon, 27 Oct 2025 15:02:15 +0100 Subject: [PATCH 0559/3902] drm/panthor: Fix UAF race between device unplug and FW event processing [ Upstream commit 7051f6ba968fa69918d72cc26de4d6cf7ea05b90 ] The function panthor_fw_unplug() will free the FW memory sections. The problem is that there could still be pending FW events which are yet not handled at this point. process_fw_events_work() can in this case try to access said freed memory. Simply call disable_work_sync() to both drain and prevent future invocation of process_fw_events_work(). Signed-off-by: Ketil Johnsen Fixes: de85488138247 ("drm/panthor: Add the scheduler logical block") Reviewed-by: Liviu Dudau Link: https://patch.msgid.link/20251027140217.121274-1-ketil.johnsen@arm.com Signed-off-by: Liviu Dudau Signed-off-by: Sasha Levin --- drivers/gpu/drm/panthor/panthor_sched.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/gpu/drm/panthor/panthor_sched.c b/drivers/gpu/drm/panthor/panthor_sched.c index a39f0fb370dc61..0279e19aadae93 100644 --- a/drivers/gpu/drm/panthor/panthor_sched.c +++ b/drivers/gpu/drm/panthor/panthor_sched.c @@ -3859,6 +3859,7 @@ void panthor_sched_unplug(struct panthor_device *ptdev) struct panthor_scheduler *sched = ptdev->scheduler; cancel_delayed_work_sync(&sched->tick_work); + disable_work_sync(&sched->fw_events_work); mutex_lock(&sched->lock); if (sched->pm.has_ref) { From 05bfa79dec11c96397b9087dca4a661f48cc29e3 Mon Sep 17 00:00:00 2001 From: Ketil Johnsen Date: Wed, 22 Oct 2025 12:32:41 +0200 Subject: [PATCH 0560/3902] drm/panthor: Fix race with suspend during unplug [ Upstream commit 08be57e6e8aa20ea5a6dd2552e38ac168d6a9b11 ] There is a race between panthor_device_unplug() and panthor_device_suspend() which can lead to IRQ handlers running on a powered down GPU. This is how it can happen: - unplug routine calls drm_dev_unplug() - panthor_device_suspend() can now execute, and will skip a lot of important work because the device is currently marked as unplugged. - IRQs will remain active in this case and IRQ handlers can therefore try to access a powered down GPU. The fix is simply to take the PM ref in panthor_device_unplug() a little bit earlier, before drm_dev_unplug(). Signed-off-by: Ketil Johnsen Fixes: 5fe909cae118a ("drm/panthor: Add the device logical block") Reviewed-by: Boris Brezillon Reviewed-by: Liviu Dudau Reviewed-by: Steven Price Link: https://patch.msgid.link/20251022103242.1083311-1-ketil.johnsen@arm.com Signed-off-by: Liviu Dudau Signed-off-by: Sasha Levin --- drivers/gpu/drm/panthor/panthor_device.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/gpu/drm/panthor/panthor_device.c b/drivers/gpu/drm/panthor/panthor_device.c index 81df49880bd87c..962a10e00848ef 100644 --- a/drivers/gpu/drm/panthor/panthor_device.c +++ b/drivers/gpu/drm/panthor/panthor_device.c @@ -83,6 +83,8 @@ void panthor_device_unplug(struct panthor_device *ptdev) return; } + drm_WARN_ON(&ptdev->base, pm_runtime_get_sync(ptdev->base.dev) < 0); + /* Call drm_dev_unplug() so any access to HW blocks happening after * that point get rejected. */ @@ -93,8 +95,6 @@ void panthor_device_unplug(struct panthor_device *ptdev) */ mutex_unlock(&ptdev->unplug.lock); - drm_WARN_ON(&ptdev->base, pm_runtime_get_sync(ptdev->base.dev) < 0); - /* Now, try to cleanly shutdown the GPU before the device resources * get reclaimed. */ From 0612704b6f6ddf2ae223019c52148c5ac76cf70e Mon Sep 17 00:00:00 2001 From: Boris Brezillon Date: Fri, 31 Oct 2025 16:48:15 +0100 Subject: [PATCH 0561/3902] drm/panthor: Fix UAF on kernel BO VA nodes [ Upstream commit 98dd5143447af0ee33551776d8b2560c35d0bc4a ] If the MMU is down, panthor_vm_unmap_range() might return an error. We expect the page table to be updated still, and if the MMU is blocked, the rest of the GPU should be blocked too, so no risk of accessing physical memory returned to the system (which the current code doesn't cover for anyway). Proceed with the rest of the cleanup instead of bailing out and leaving the va_node inserted in the drm_mm, which leads to UAF when other adjacent nodes are removed from the drm_mm tree. Reported-by: Lars-Ivar Hesselberg Simonsen Closes: https://gitlab.freedesktop.org/panfrost/linux/-/issues/57 Fixes: 8a1cc07578bf ("drm/panthor: Add GEM logical block") Signed-off-by: Boris Brezillon Reviewed-by: Liviu Dudau Link: https://patch.msgid.link/20251031154818.821054-2-boris.brezillon@collabora.com Signed-off-by: Liviu Dudau Signed-off-by: Sasha Levin --- drivers/gpu/drm/panthor/panthor_gem.c | 14 +++----------- 1 file changed, 3 insertions(+), 11 deletions(-) diff --git a/drivers/gpu/drm/panthor/panthor_gem.c b/drivers/gpu/drm/panthor/panthor_gem.c index 3f43686f019588..14ed09d700f2f7 100644 --- a/drivers/gpu/drm/panthor/panthor_gem.c +++ b/drivers/gpu/drm/panthor/panthor_gem.c @@ -86,7 +86,6 @@ static void panthor_gem_free_object(struct drm_gem_object *obj) void panthor_kernel_bo_destroy(struct panthor_kernel_bo *bo) { struct panthor_vm *vm; - int ret; if (IS_ERR_OR_NULL(bo)) return; @@ -94,18 +93,11 @@ void panthor_kernel_bo_destroy(struct panthor_kernel_bo *bo) vm = bo->vm; panthor_kernel_bo_vunmap(bo); - if (drm_WARN_ON(bo->obj->dev, - to_panthor_bo(bo->obj)->exclusive_vm_root_gem != panthor_vm_root_gem(vm))) - goto out_free_bo; - - ret = panthor_vm_unmap_range(vm, bo->va_node.start, bo->va_node.size); - if (ret) - goto out_free_bo; - + drm_WARN_ON(bo->obj->dev, + to_panthor_bo(bo->obj)->exclusive_vm_root_gem != panthor_vm_root_gem(vm)); + panthor_vm_unmap_range(vm, bo->va_node.start, bo->va_node.size); panthor_vm_free_va(vm, &bo->va_node); drm_gem_object_put(bo->obj); - -out_free_bo: panthor_vm_put(vm); kfree(bo); } From 6a6f1e983edc7229499e8f7d0ba19e497d6fdadc Mon Sep 17 00:00:00 2001 From: "Thomas Richard (TI.com)" Date: Fri, 31 Oct 2025 13:44:56 +0100 Subject: [PATCH 0562/3902] firmware: ti_sci: Set IO Isolation only if the firmware is capable [ Upstream commit 999e9bc953e321651d69556fdd5dfd178f96f128 ] Prevent calling ti_sci_cmd_set_io_isolation() on firmware that does not support the IO_ISOLATION capability. Add the MSG_FLAG_CAPS_IO_ISOLATION capability flag and check it before attempting to set IO isolation during suspend/resume operations. Without this check, systems with older firmware may experience undefined behavior or errors when entering/exiting suspend states. Fixes: ec24643bdd62 ("firmware: ti_sci: Add system suspend and resume call") Signed-off-by: Thomas Richard (TI.com) Reviewed-by: Kevin Hilman Link: https://patch.msgid.link/20251031-ti-sci-io-isolation-v2-1-60d826b65949@bootlin.com Signed-off-by: Nishanth Menon Signed-off-by: Sasha Levin --- drivers/firmware/ti_sci.c | 21 +++++++++++++-------- drivers/firmware/ti_sci.h | 2 ++ 2 files changed, 15 insertions(+), 8 deletions(-) diff --git a/drivers/firmware/ti_sci.c b/drivers/firmware/ti_sci.c index 49fd2ae01055d0..8d96a3c12b36a9 100644 --- a/drivers/firmware/ti_sci.c +++ b/drivers/firmware/ti_sci.c @@ -3751,9 +3751,11 @@ static int __maybe_unused ti_sci_suspend_noirq(struct device *dev) struct ti_sci_info *info = dev_get_drvdata(dev); int ret = 0; - ret = ti_sci_cmd_set_io_isolation(&info->handle, TISCI_MSG_VALUE_IO_ENABLE); - if (ret) - return ret; + if (info->fw_caps & MSG_FLAG_CAPS_IO_ISOLATION) { + ret = ti_sci_cmd_set_io_isolation(&info->handle, TISCI_MSG_VALUE_IO_ENABLE); + if (ret) + return ret; + } return 0; } @@ -3767,9 +3769,11 @@ static int __maybe_unused ti_sci_resume_noirq(struct device *dev) u8 pin; u8 mode; - ret = ti_sci_cmd_set_io_isolation(&info->handle, TISCI_MSG_VALUE_IO_DISABLE); - if (ret) - return ret; + if (info->fw_caps & MSG_FLAG_CAPS_IO_ISOLATION) { + ret = ti_sci_cmd_set_io_isolation(&info->handle, TISCI_MSG_VALUE_IO_DISABLE); + if (ret) + return ret; + } ret = ti_sci_msg_cmd_lpm_wake_reason(&info->handle, &source, &time, &pin, &mode); /* Do not fail to resume on error as the wake reason is not critical */ @@ -3928,11 +3932,12 @@ static int ti_sci_probe(struct platform_device *pdev) } ti_sci_msg_cmd_query_fw_caps(&info->handle, &info->fw_caps); - dev_dbg(dev, "Detected firmware capabilities: %s%s%s%s\n", + dev_dbg(dev, "Detected firmware capabilities: %s%s%s%s%s\n", info->fw_caps & MSG_FLAG_CAPS_GENERIC ? "Generic" : "", info->fw_caps & MSG_FLAG_CAPS_LPM_PARTIAL_IO ? " Partial-IO" : "", info->fw_caps & MSG_FLAG_CAPS_LPM_DM_MANAGED ? " DM-Managed" : "", - info->fw_caps & MSG_FLAG_CAPS_LPM_ABORT ? " LPM-Abort" : "" + info->fw_caps & MSG_FLAG_CAPS_LPM_ABORT ? " LPM-Abort" : "", + info->fw_caps & MSG_FLAG_CAPS_IO_ISOLATION ? " IO-Isolation" : "" ); ti_sci_setup_ops(info); diff --git a/drivers/firmware/ti_sci.h b/drivers/firmware/ti_sci.h index 701c416b2e78f8..7559cde17b6ccf 100644 --- a/drivers/firmware/ti_sci.h +++ b/drivers/firmware/ti_sci.h @@ -149,6 +149,7 @@ struct ti_sci_msg_req_reboot { * MSG_FLAG_CAPS_LPM_PARTIAL_IO: Partial IO in LPM * MSG_FLAG_CAPS_LPM_DM_MANAGED: LPM can be managed by DM * MSG_FLAG_CAPS_LPM_ABORT: Abort entry to LPM + * MSG_FLAG_CAPS_IO_ISOLATION: IO Isolation support * * Response to a generic message with message type TI_SCI_MSG_QUERY_FW_CAPS * providing currently available SOC/firmware capabilities. SoC that don't @@ -160,6 +161,7 @@ struct ti_sci_msg_resp_query_fw_caps { #define MSG_FLAG_CAPS_LPM_PARTIAL_IO TI_SCI_MSG_FLAG(4) #define MSG_FLAG_CAPS_LPM_DM_MANAGED TI_SCI_MSG_FLAG(5) #define MSG_FLAG_CAPS_LPM_ABORT TI_SCI_MSG_FLAG(9) +#define MSG_FLAG_CAPS_IO_ISOLATION TI_SCI_MSG_FLAG(7) #define MSG_MASK_CAPS_LPM GENMASK_ULL(4, 1) u64 fw_caps; } __packed; From ad804f43873aa290dbe7463454359b256b414e49 Mon Sep 17 00:00:00 2001 From: Christian Brauner Date: Mon, 3 Nov 2025 16:10:10 +0100 Subject: [PATCH 0563/3902] ns: add NS_COMMON_INIT() [ Upstream commit d915fe20e5cba4bd50e41e792a32dcddc7490e25 ] Add an initializer that can be used for the ns common initialization for static namespace such as most init namespaces. Suggested-by: Thomas Gleixner Link: https://patch.msgid.link/87ecqhy2y5.ffs@tglx Signed-off-by: Christian Brauner Stable-dep-of: 3dd50c58664e ("ns: initialize ns_list_node for initial namespaces") Signed-off-by: Sasha Levin --- include/linux/ns_common.h | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/include/linux/ns_common.h b/include/linux/ns_common.h index f5b68b8abb5433..3a72c3f81eca42 100644 --- a/include/linux/ns_common.h +++ b/include/linux/ns_common.h @@ -119,6 +119,16 @@ void __ns_common_free(struct ns_common *ns); struct user_namespace *: CLONE_NEWUSER, \ struct uts_namespace *: CLONE_NEWUTS) +#define NS_COMMON_INIT(nsname, refs) \ +{ \ + .ns_type = ns_common_type(&nsname), \ + .ns_id = 0, \ + .inum = ns_init_inum(&nsname), \ + .ops = to_ns_operations(&nsname), \ + .stashed = NULL, \ + .__ns_ref = REFCOUNT_INIT(refs), \ +} + #define ns_common_init(__ns) \ __ns_common_init(to_ns_common(__ns), \ ns_common_type(__ns), \ From e31c902d785411eb4a246fba2e8a32aa59d33ce2 Mon Sep 17 00:00:00 2001 From: Christian Brauner Date: Wed, 29 Oct 2025 13:20:21 +0100 Subject: [PATCH 0564/3902] ns: initialize ns_list_node for initial namespaces [ Upstream commit 3dd50c58664e2684bd610a57bf3ab713cbb0ea91 ] Make sure that the list is always initialized for initial namespaces. Link: https://patch.msgid.link/20251029-work-namespace-nstree-listns-v4-8-2e6f823ebdc0@kernel.org Fixes: 885fc8ac0a4d ("nstree: make iterator generic") Tested-by: syzbot@syzkaller.appspotmail.com Reviewed-by: Jeff Layton Signed-off-by: Christian Brauner Signed-off-by: Sasha Levin --- include/linux/ns_common.h | 1 + 1 file changed, 1 insertion(+) diff --git a/include/linux/ns_common.h b/include/linux/ns_common.h index 3a72c3f81eca42..71a5e28344d119 100644 --- a/include/linux/ns_common.h +++ b/include/linux/ns_common.h @@ -127,6 +127,7 @@ void __ns_common_free(struct ns_common *ns); .ops = to_ns_operations(&nsname), \ .stashed = NULL, \ .__ns_ref = REFCOUNT_INIT(refs), \ + .ns_list_node = LIST_HEAD_INIT(nsname.ns.ns_list_node), \ } #define ns_common_init(__ns) \ From 0ec4aaf5f3f559716a6559f3d6d9616e9470bed6 Mon Sep 17 00:00:00 2001 From: Songtang Liu Date: Thu, 30 Oct 2025 22:55:25 -0700 Subject: [PATCH 0565/3902] iommu/amd: Fix potential out-of-bounds read in iommu_mmio_show [ Upstream commit a0c7005333f9a968abb058b1d77bbcd7fb7fd1e7 ] In iommu_mmio_write(), it validates the user-provided offset with the check: `iommu->dbg_mmio_offset > iommu->mmio_phys_end - 4`. This assumes a 4-byte access. However, the corresponding show handler, iommu_mmio_show(), uses readq() to perform an 8-byte (64-bit) read. If a user provides an offset equal to `mmio_phys_end - 4`, the check passes, and will lead to a 4-byte out-of-bounds read. Fix this by adjusting the boundary check to use sizeof(u64), which corresponds to the size of the readq() operation. Fixes: 7a4ee419e8c1 ("iommu/amd: Add debugfs support to dump IOMMU MMIO registers") Signed-off-by: Songtang Liu Reviewed-by: Dheeraj Kumar Srivastava Tested-by: Dheeraj Kumar Srivastava Signed-off-by: Joerg Roedel Signed-off-by: Sasha Levin --- drivers/iommu/amd/debugfs.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/iommu/amd/debugfs.c b/drivers/iommu/amd/debugfs.c index 10fa217a711993..20b04996441d62 100644 --- a/drivers/iommu/amd/debugfs.c +++ b/drivers/iommu/amd/debugfs.c @@ -37,7 +37,7 @@ static ssize_t iommu_mmio_write(struct file *filp, const char __user *ubuf, if (ret) return ret; - if (iommu->dbg_mmio_offset > iommu->mmio_phys_end - 4) { + if (iommu->dbg_mmio_offset > iommu->mmio_phys_end - sizeof(u64)) { iommu->dbg_mmio_offset = -1; return -EINVAL; } From 83143f9df885f793f8f20236cf569d6f1b7b8b81 Mon Sep 17 00:00:00 2001 From: Christian Brauner Date: Mon, 3 Nov 2025 00:12:40 +0100 Subject: [PATCH 0566/3902] cleanup: fix scoped_class() [ Upstream commit 4e97bae1b412cd6ed8053b3d8a242122952985cc ] This is a class, not a guard so why on earth is it checking for guard pointers or conditional lock acquisition? None of it makes any sense at all. I'm not sure what happened back then. Maybe I had a brief psychedelic period that I completely forgot about and spaced out into a zone where that initial macro implementation made any sense at all. Link: https://patch.msgid.link/20251103-work-creds-init_cred-v1-1-cb3ec8711a6a@kernel.org Fixes: 5c21c5f22d07 ("cleanup: add a scoped version of CLASS()") Reviewed-by: Jens Axboe Signed-off-by: Christian Brauner Signed-off-by: Sasha Levin --- include/linux/cleanup.h | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/include/linux/cleanup.h b/include/linux/cleanup.h index 2573585b7f068a..19c7e475d3a4d9 100644 --- a/include/linux/cleanup.h +++ b/include/linux/cleanup.h @@ -290,15 +290,16 @@ static inline class_##_name##_t class_##_name##ext##_constructor(_init_args) \ class_##_name##_t var __cleanup(class_##_name##_destructor) = \ class_##_name##_constructor -#define scoped_class(_name, var, args) \ - for (CLASS(_name, var)(args); \ - __guard_ptr(_name)(&var) || !__is_cond_ptr(_name); \ - ({ goto _label; })) \ - if (0) { \ -_label: \ - break; \ +#define __scoped_class(_name, var, _label, args...) \ + for (CLASS(_name, var)(args); ; ({ goto _label; })) \ + if (0) { \ +_label: \ + break; \ } else +#define scoped_class(_name, var, args...) \ + __scoped_class(_name, var, __UNIQUE_ID(label), args) + /* * DEFINE_GUARD(name, type, lock, unlock): * trivial wrapper around DEFINE_CLASS() above specifically From 01bbf25c767219b14c3235bfa85906b8d2cb8fbc Mon Sep 17 00:00:00 2001 From: Vishwaroop A Date: Tue, 28 Oct 2025 15:57:01 +0000 Subject: [PATCH 0567/3902] spi: tegra210-quad: Fix timeout handling [ Upstream commit b4e002d8a7cee3b1d70efad0e222567f92a73000 ] When the CPU that the QSPI interrupt handler runs on (typically CPU 0) is excessively busy, it can lead to rare cases of the IRQ thread not running before the transfer timeout is reached. While handling the timeouts, any pending transfers are cleaned up and the message that they correspond to is marked as failed, which leaves the curr_xfer field pointing at stale memory. To avoid this, clear curr_xfer to NULL upon timeout and check for this condition when the IRQ thread is finally run. While at it, also make sure to clear interrupts on failure so that new interrupts can be run. A better, more involved, fix would move the interrupt clearing into a hard IRQ handler. Ideally we would also want to signal that the IRQ thread no longer needs to be run after the timeout is hit to avoid the extra check for a valid transfer. Fixes: 921fc1838fb0 ("spi: tegra210-quad: Add support for Tegra210 QSPI controller") Signed-off-by: Thierry Reding Signed-off-by: Vishwaroop A Link: https://patch.msgid.link/20251028155703.4151791-2-va@nvidia.com Signed-off-by: Mark Brown Signed-off-by: Sasha Levin --- drivers/spi/spi-tegra210-quad.c | 22 ++++++++++++++++++++-- 1 file changed, 20 insertions(+), 2 deletions(-) diff --git a/drivers/spi/spi-tegra210-quad.c b/drivers/spi/spi-tegra210-quad.c index 3be7499db21ec4..d9ca3d7b082f29 100644 --- a/drivers/spi/spi-tegra210-quad.c +++ b/drivers/spi/spi-tegra210-quad.c @@ -1024,8 +1024,10 @@ static void tegra_qspi_handle_error(struct tegra_qspi *tqspi) dev_err(tqspi->dev, "error in transfer, fifo status 0x%08x\n", tqspi->status_reg); tegra_qspi_dump_regs(tqspi); tegra_qspi_flush_fifos(tqspi, true); - if (device_reset(tqspi->dev) < 0) + if (device_reset(tqspi->dev) < 0) { dev_warn_once(tqspi->dev, "device reset failed\n"); + tegra_qspi_mask_clear_irq(tqspi); + } } static void tegra_qspi_transfer_end(struct spi_device *spi) @@ -1176,9 +1178,11 @@ static int tegra_qspi_combined_seq_xfer(struct tegra_qspi *tqspi, } /* Reset controller if timeout happens */ - if (device_reset(tqspi->dev) < 0) + if (device_reset(tqspi->dev) < 0) { dev_warn_once(tqspi->dev, "device reset failed\n"); + tegra_qspi_mask_clear_irq(tqspi); + } ret = -EIO; goto exit; } @@ -1200,11 +1204,13 @@ static int tegra_qspi_combined_seq_xfer(struct tegra_qspi *tqspi, tegra_qspi_transfer_end(spi); spi_transfer_delay_exec(xfer); } + tqspi->curr_xfer = NULL; transfer_phase++; } ret = 0; exit: + tqspi->curr_xfer = NULL; msg->status = ret; return ret; @@ -1290,6 +1296,8 @@ static int tegra_qspi_non_combined_seq_xfer(struct tegra_qspi *tqspi, msg->actual_length += xfer->len + dummy_bytes; complete_xfer: + tqspi->curr_xfer = NULL; + if (ret < 0) { tegra_qspi_transfer_end(spi); spi_transfer_delay_exec(xfer); @@ -1395,6 +1403,7 @@ static irqreturn_t handle_cpu_based_xfer(struct tegra_qspi *tqspi) tegra_qspi_calculate_curr_xfer_param(tqspi, t); tegra_qspi_start_cpu_based_transfer(tqspi, t); exit: + tqspi->curr_xfer = NULL; spin_unlock_irqrestore(&tqspi->lock, flags); return IRQ_HANDLED; } @@ -1480,6 +1489,15 @@ static irqreturn_t tegra_qspi_isr_thread(int irq, void *context_data) { struct tegra_qspi *tqspi = context_data; + /* + * Occasionally the IRQ thread takes a long time to wake up (usually + * when the CPU that it's running on is excessively busy) and we have + * already reached the timeout before and cleaned up the timed out + * transfer. Avoid any processing in that case and bail out early. + */ + if (!tqspi->curr_xfer) + return IRQ_NONE; + tqspi->status_reg = tegra_qspi_readl(tqspi, QSPI_FIFO_STATUS); if (tqspi->cur_direction & DATA_DIR_TX) From b454e6282cbfefd2e6f592c7a73131b36cb7fb96 Mon Sep 17 00:00:00 2001 From: Alan Maguire Date: Tue, 4 Nov 2025 20:33:08 +0000 Subject: [PATCH 0568/3902] libbpf: Fix parsing of multi-split BTF [ Upstream commit 4f596acc260e691a2e348f64230392f3472feea3 ] When creating multi-split BTF we correctly set the start string offset to be the size of the base string section plus the base BTF start string offset; the latter is needed for multi-split BTF since the offset is non-zero there. Unfortunately the BTF parsing case needed that logic and it was missed. Fixes: 4e29128a9ace ("libbpf/btf: Fix string handling to support multi-split BTF") Signed-off-by: Alan Maguire Signed-off-by: Andrii Nakryiko Link: https://lore.kernel.org/bpf/20251104203309.318429-2-alan.maguire@oracle.com Signed-off-by: Sasha Levin --- tools/lib/bpf/btf.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tools/lib/bpf/btf.c b/tools/lib/bpf/btf.c index 18907f0fcf9f01..9f141395c074e3 100644 --- a/tools/lib/bpf/btf.c +++ b/tools/lib/bpf/btf.c @@ -1061,7 +1061,7 @@ static struct btf *btf_new(const void *data, __u32 size, struct btf *base_btf, b if (base_btf) { btf->base_btf = base_btf; btf->start_id = btf__type_cnt(base_btf); - btf->start_str_off = base_btf->hdr->str_len; + btf->start_str_off = base_btf->hdr->str_len + base_btf->start_str_off; } if (is_mmap) { @@ -5818,7 +5818,7 @@ void btf_set_base_btf(struct btf *btf, const struct btf *base_btf) { btf->base_btf = (struct btf *)base_btf; btf->start_id = btf__type_cnt(base_btf); - btf->start_str_off = base_btf->hdr->str_len; + btf->start_str_off = base_btf->hdr->str_len + base_btf->start_str_off; } int btf__relocate(struct btf *btf, const struct btf *base_btf) From 29e886ee371b674220a7557dd0070c73dab73c0b Mon Sep 17 00:00:00 2001 From: Geert Uytterhoeven Date: Thu, 2 Oct 2025 11:53:05 +0200 Subject: [PATCH 0569/3902] ARM: dts: am33xx: Add missing serial console speed [ Upstream commit 9c95fc710b0d05f797db9e26d56524efa74f8978 ] Without a serial console speed specified in chosen/stdout-path in the DTB, the serial console uses the default speed of the serial driver, unless explicitly overridden in a legacy console= kernel command-line parameter. After dropping "ti,omap3-uart" from the list of compatible values in DT, AM33xx serial ports can no longer be used with the legacy OMAP serial driver, but only with the OMAP-flavored 8250 serial driver (which is mutually-exclusive with the former). However, replacing CONFIG_SERIAL_OMAP=y by CONFIG_SERIAL_8250_OMAP=y (with/without enabling CONFIG_SERIAL_8250_OMAP_TTYO_FIXUP) may not be sufficient to restore serial console functionality: the legacy OMAP serial driver defaults to 115200 bps, while the 8250 serial driver defaults to 9600 bps, causing no visible output on the serial console when no appropriate console= kernel command-line parameter is specified. Fix this for all AM33xx boards by adding ":115200n8" to chosen/stdout-path. This requires replacing the "&uartN" reference by the corresponding "serialN" DT alias. Fixes: ca8be8fc2c306efb ("ARM: dts: am33xx-l4: fix UART compatible") Fixes: 077e1cde78c3f904 ("ARM: omap2plus_defconfig: Enable 8250_OMAP") Closes: https://lore.kernel.org/CAMuHMdUb7Jb2=GqK3=Rn+Gv5G9KogcQieqDvjDCkJA4zyX4VcA@mail.gmail.com Signed-off-by: Geert Uytterhoeven Reviewed-by: Matti Vaittinen Tested-by: Matti Vaittinen Reviewed-by: Bruno Thomsen Link: https://lore.kernel.org/r/63cef5c3643d359e8ec13366ca79377f12dd73b1.1759398641.git.geert+renesas@glider.be Signed-off-by: Kevin Hilman Signed-off-by: Sasha Levin --- arch/arm/boot/dts/ti/omap/am335x-bone-common.dtsi | 2 +- arch/arm/boot/dts/ti/omap/am335x-boneblue.dts | 2 +- arch/arm/boot/dts/ti/omap/am335x-chiliboard.dts | 2 +- arch/arm/boot/dts/ti/omap/am335x-evm.dts | 2 +- arch/arm/boot/dts/ti/omap/am335x-evmsk.dts | 2 +- arch/arm/boot/dts/ti/omap/am335x-guardian.dts | 2 +- arch/arm/boot/dts/ti/omap/am335x-icev2.dts | 2 +- arch/arm/boot/dts/ti/omap/am335x-myirtech-myd.dts | 2 +- arch/arm/boot/dts/ti/omap/am335x-osd3358-sm-red.dts | 2 +- arch/arm/boot/dts/ti/omap/am335x-pdu001.dts | 2 +- arch/arm/boot/dts/ti/omap/am335x-pocketbeagle.dts | 2 +- arch/arm/boot/dts/ti/omap/am335x-sl50.dts | 2 +- 12 files changed, 12 insertions(+), 12 deletions(-) diff --git a/arch/arm/boot/dts/ti/omap/am335x-bone-common.dtsi b/arch/arm/boot/dts/ti/omap/am335x-bone-common.dtsi index ad1e60a9b6fde8..b75dabfa56ae76 100644 --- a/arch/arm/boot/dts/ti/omap/am335x-bone-common.dtsi +++ b/arch/arm/boot/dts/ti/omap/am335x-bone-common.dtsi @@ -16,7 +16,7 @@ }; chosen { - stdout-path = &uart0; + stdout-path = "serial0:115200n8"; }; leds { diff --git a/arch/arm/boot/dts/ti/omap/am335x-boneblue.dts b/arch/arm/boot/dts/ti/omap/am335x-boneblue.dts index f579df4c2c540d..d430f0bef16537 100644 --- a/arch/arm/boot/dts/ti/omap/am335x-boneblue.dts +++ b/arch/arm/boot/dts/ti/omap/am335x-boneblue.dts @@ -13,7 +13,7 @@ compatible = "ti,am335x-bone-blue", "ti,am33xx"; chosen { - stdout-path = &uart0; + stdout-path = "serial0:115200n8"; }; leds { diff --git a/arch/arm/boot/dts/ti/omap/am335x-chiliboard.dts b/arch/arm/boot/dts/ti/omap/am335x-chiliboard.dts index 648e97fe1dfd5e..ae5bc589849722 100644 --- a/arch/arm/boot/dts/ti/omap/am335x-chiliboard.dts +++ b/arch/arm/boot/dts/ti/omap/am335x-chiliboard.dts @@ -12,7 +12,7 @@ "ti,am33xx"; chosen { - stdout-path = &uart0; + stdout-path = "serial0:115200n8"; }; leds { diff --git a/arch/arm/boot/dts/ti/omap/am335x-evm.dts b/arch/arm/boot/dts/ti/omap/am335x-evm.dts index 20222f82f21bfd..856fa1191ed24e 100644 --- a/arch/arm/boot/dts/ti/omap/am335x-evm.dts +++ b/arch/arm/boot/dts/ti/omap/am335x-evm.dts @@ -23,7 +23,7 @@ }; chosen { - stdout-path = &uart0; + stdout-path = "serial0:115200n8"; }; vbat: fixedregulator0 { diff --git a/arch/arm/boot/dts/ti/omap/am335x-evmsk.dts b/arch/arm/boot/dts/ti/omap/am335x-evmsk.dts index eba888dcd60e7f..d8baccdf8bc463 100644 --- a/arch/arm/boot/dts/ti/omap/am335x-evmsk.dts +++ b/arch/arm/boot/dts/ti/omap/am335x-evmsk.dts @@ -30,7 +30,7 @@ }; chosen { - stdout-path = &uart0; + stdout-path = "serial0:115200n8"; }; vbat: fixedregulator0 { diff --git a/arch/arm/boot/dts/ti/omap/am335x-guardian.dts b/arch/arm/boot/dts/ti/omap/am335x-guardian.dts index 4b070e634b2810..6ce3a2d029eedc 100644 --- a/arch/arm/boot/dts/ti/omap/am335x-guardian.dts +++ b/arch/arm/boot/dts/ti/omap/am335x-guardian.dts @@ -14,7 +14,7 @@ compatible = "bosch,am335x-guardian", "ti,am33xx"; chosen { - stdout-path = &uart0; + stdout-path = "serial0:115200n8"; tick-timer = &timer2; }; diff --git a/arch/arm/boot/dts/ti/omap/am335x-icev2.dts b/arch/arm/boot/dts/ti/omap/am335x-icev2.dts index 6f0f4fba043b96..ba488bba6925de 100644 --- a/arch/arm/boot/dts/ti/omap/am335x-icev2.dts +++ b/arch/arm/boot/dts/ti/omap/am335x-icev2.dts @@ -22,7 +22,7 @@ }; chosen { - stdout-path = &uart3; + stdout-path = "serial3:115200n8"; }; vbat: fixedregulator0 { diff --git a/arch/arm/boot/dts/ti/omap/am335x-myirtech-myd.dts b/arch/arm/boot/dts/ti/omap/am335x-myirtech-myd.dts index 06a352f98b220b..476a6bdaf43f38 100644 --- a/arch/arm/boot/dts/ti/omap/am335x-myirtech-myd.dts +++ b/arch/arm/boot/dts/ti/omap/am335x-myirtech-myd.dts @@ -15,7 +15,7 @@ compatible = "myir,myd-am335x", "myir,myc-am335x", "ti,am33xx"; chosen { - stdout-path = &uart0; + stdout-path = "serial0:115200n8"; }; clk12m: clk12m { diff --git a/arch/arm/boot/dts/ti/omap/am335x-osd3358-sm-red.dts b/arch/arm/boot/dts/ti/omap/am335x-osd3358-sm-red.dts index d28d3972884765..23caaaabf35134 100644 --- a/arch/arm/boot/dts/ti/omap/am335x-osd3358-sm-red.dts +++ b/arch/arm/boot/dts/ti/omap/am335x-osd3358-sm-red.dts @@ -147,7 +147,7 @@ }; chosen { - stdout-path = &uart0; + stdout-path = "serial0:115200n8"; }; leds { diff --git a/arch/arm/boot/dts/ti/omap/am335x-pdu001.dts b/arch/arm/boot/dts/ti/omap/am335x-pdu001.dts index c9ccb9de21ad7b..9f611debc20907 100644 --- a/arch/arm/boot/dts/ti/omap/am335x-pdu001.dts +++ b/arch/arm/boot/dts/ti/omap/am335x-pdu001.dts @@ -21,7 +21,7 @@ compatible = "ti,am33xx"; chosen { - stdout-path = &uart3; + stdout-path = "serial3:115200n8"; }; cpus { diff --git a/arch/arm/boot/dts/ti/omap/am335x-pocketbeagle.dts b/arch/arm/boot/dts/ti/omap/am335x-pocketbeagle.dts index 78ce860e59b3de..24d9f90fad01f7 100644 --- a/arch/arm/boot/dts/ti/omap/am335x-pocketbeagle.dts +++ b/arch/arm/boot/dts/ti/omap/am335x-pocketbeagle.dts @@ -15,7 +15,7 @@ compatible = "ti,am335x-pocketbeagle", "ti,am335x-bone", "ti,am33xx"; chosen { - stdout-path = &uart0; + stdout-path = "serial0:115200n8"; }; leds { diff --git a/arch/arm/boot/dts/ti/omap/am335x-sl50.dts b/arch/arm/boot/dts/ti/omap/am335x-sl50.dts index f3524e5ee43e27..1dc4e344efd63e 100644 --- a/arch/arm/boot/dts/ti/omap/am335x-sl50.dts +++ b/arch/arm/boot/dts/ti/omap/am335x-sl50.dts @@ -25,7 +25,7 @@ }; chosen { - stdout-path = &uart0; + stdout-path = "serial0:115200n8"; }; leds { From 97f035128363a8240d229f767914ee7200f3452d Mon Sep 17 00:00:00 2001 From: Yegor Yefremov Date: Tue, 7 Oct 2025 12:38:51 +0200 Subject: [PATCH 0570/3902] ARM: dts: am335x-netcom-plus-2xx: add missing GPIO labels [ Upstream commit d0c4b1723c419a18cb434903c7754954ecb51d35 ] Fixes: 8e9d75fd2ec2 ("ARM: dts: am335x-netcom: add GPIO names for NetCom Plus 2-port devices") Signed-off-by: Yegor Yefremov Link: https://lore.kernel.org/r/20251007103851.3765678-1-yegorslists@googlemail.com Signed-off-by: Kevin Hilman Signed-off-by: Sasha Levin --- arch/arm/boot/dts/ti/omap/am335x-netcom-plus-2xx.dts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/arch/arm/boot/dts/ti/omap/am335x-netcom-plus-2xx.dts b/arch/arm/boot/dts/ti/omap/am335x-netcom-plus-2xx.dts index f66d57bb685ee1..f0519ab3014160 100644 --- a/arch/arm/boot/dts/ti/omap/am335x-netcom-plus-2xx.dts +++ b/arch/arm/boot/dts/ti/omap/am335x-netcom-plus-2xx.dts @@ -222,10 +222,10 @@ "ModeA1", "ModeA2", "ModeA3", - "NC", - "NC", - "NC", - "NC", + "ModeB0", + "ModeB1", + "ModeB2", + "ModeB3", "NC", "NC", "NC", From ecd2c6686f49371ac3398e57743bec1aa2b6130b Mon Sep 17 00:00:00 2001 From: Jihed Chaibi Date: Sun, 14 Sep 2025 21:25:15 +0200 Subject: [PATCH 0571/3902] ARM: dts: omap3: beagle-xm: Correct obsolete TWL4030 power compatible [ Upstream commit f7f3bc18300a230e0f1bfb17fc8889435c1e47f5 ] The "ti,twl4030-power-beagleboard-xm" compatible string is obsolete and is not supported by any in-kernel driver. Currently, the kernel falls back to the second entry, "ti,twl4030-power-idle-osc-off", to bind a driver to this node. Make this fallback explicit by removing the obsolete board-specific compatible. This preserves the existing functionality while making the DTS compliant with the new, stricter 'ti,twl.yaml' binding. Fixes: 9188883fd66e9 ("ARM: dts: Enable twl4030 off-idle configuration for selected omaps") Signed-off-by: Jihed Chaibi Link: https://lore.kernel.org/r/20250914192516.164629-3-jihed.chaibi.dev@gmail.com Signed-off-by: Kevin Hilman Signed-off-by: Sasha Levin --- arch/arm/boot/dts/ti/omap/omap3-beagle-xm.dts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/arch/arm/boot/dts/ti/omap/omap3-beagle-xm.dts b/arch/arm/boot/dts/ti/omap/omap3-beagle-xm.dts index 08ee0f8ea68fda..71b39a923d37c6 100644 --- a/arch/arm/boot/dts/ti/omap/omap3-beagle-xm.dts +++ b/arch/arm/boot/dts/ti/omap/omap3-beagle-xm.dts @@ -291,7 +291,7 @@ }; twl_power: power { - compatible = "ti,twl4030-power-beagleboard-xm", "ti,twl4030-power-idle-osc-off"; + compatible = "ti,twl4030-power-idle-osc-off"; ti,use_poweroff; }; }; From a1b7235ba4388eacf90ec6cbc1c7697bf77bf44a Mon Sep 17 00:00:00 2001 From: Jihed Chaibi Date: Sun, 14 Sep 2025 21:25:16 +0200 Subject: [PATCH 0572/3902] ARM: dts: omap3: n900: Correct obsolete TWL4030 power compatible [ Upstream commit 3862123e9b56663c7a3e4a308e6e65bffe44f646 ] The "ti,twl4030-power-n900" compatible string is obsolete and is not supported by any in-kernel driver. Currently, the kernel falls back to the second entry, "ti,twl4030-power-idle-osc-off", to bind a driver to this node. Make this fallback explicit by removing the obsolete board-specific compatible. This preserves the existing functionality while making the DTS compliant with the new, stricter 'ti,twl.yaml' binding. Fixes: daebabd578647 ("mfd: twl4030-power: Fix PM idle pin configuration to not conflict with regulators") Signed-off-by: Jihed Chaibi Link: https://lore.kernel.org/r/20250914192516.164629-4-jihed.chaibi.dev@gmail.com Signed-off-by: Kevin Hilman Signed-off-by: Sasha Levin --- arch/arm/boot/dts/ti/omap/omap3-n900.dts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/arch/arm/boot/dts/ti/omap/omap3-n900.dts b/arch/arm/boot/dts/ti/omap/omap3-n900.dts index c50ca572d1b9b5..7db73d9bed9e4b 100644 --- a/arch/arm/boot/dts/ti/omap/omap3-n900.dts +++ b/arch/arm/boot/dts/ti/omap/omap3-n900.dts @@ -508,7 +508,7 @@ }; twl_power: power { - compatible = "ti,twl4030-power-n900", "ti,twl4030-power-idle-osc-off"; + compatible = "ti,twl4030-power-idle-osc-off"; ti,use_poweroff; }; }; From 040ce00735c17b4c30c18c8f36805ba952a447e2 Mon Sep 17 00:00:00 2001 From: Peter Zijlstra Date: Wed, 5 Nov 2025 11:00:14 +0100 Subject: [PATCH 0573/3902] entry,unwind/deferred: Fix unwind_reset_info() placement [ Upstream commit cf76553aaa363620f58a6b6409bf544f4bcfa8de ] Stephen reported that on KASAN builds he's seeing: vmlinux.o: warning: objtool: user_exc_vmm_communication+0x15a: call to __kasan_check_read() leaves .noinstr.text section vmlinux.o: warning: objtool: exc_debug_user+0x182: call to __kasan_check_read() leaves .noinstr.text section vmlinux.o: warning: objtool: exc_int3+0x123: call to __kasan_check_read() leaves .noinstr.text section vmlinux.o: warning: objtool: noist_exc_machine_check+0x17a: call to __kasan_check_read() leaves .noinstr.text section vmlinux.o: warning: objtool: fred_exc_machine_check+0x17e: call to __kasan_check_read() leaves .noinstr.text section This turns out to be atomic ops from unwind_reset_info() that have explicit instrumentation. Place unwind_reset_info() in the preceding instrumentation_begin() section. Fixes: c6439bfaabf2 ("Merge tag 'trace-deferred-unwind-v6.17' of git://git.kernel.org/pub/scm/linux/kernel/git/trace/linux-trace") Reported-by: Stephen Rothwell Reported-by: Ingo Molnar Signed-off-by: Peter Zijlstra (Intel) Link: https://patch.msgid.link/20251105100014.GY4068168@noisy.programming.kicks-ass.net Signed-off-by: Sasha Levin --- include/linux/irq-entry-common.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/linux/irq-entry-common.h b/include/linux/irq-entry-common.h index d643c7c87822e1..ba1ed42f8a1c29 100644 --- a/include/linux/irq-entry-common.h +++ b/include/linux/irq-entry-common.h @@ -253,11 +253,11 @@ static __always_inline void exit_to_user_mode_prepare(struct pt_regs *regs) static __always_inline void exit_to_user_mode(void) { instrumentation_begin(); + unwind_reset_info(); trace_hardirqs_on_prepare(); lockdep_hardirqs_on_prepare(); instrumentation_end(); - unwind_reset_info(); user_enter_irqoff(); arch_exit_to_user_mode(); lockdep_hardirqs_on(CALLER_ADDR0); From 414c982faffa39f234af1ec889785d4c5733d615 Mon Sep 17 00:00:00 2001 From: Niklas Cassel Date: Thu, 9 Oct 2025 16:22:54 +0200 Subject: [PATCH 0574/3902] arm64: tegra: Add pinctrl definitions for pcie-ep nodes [ Upstream commit 21ef26d0e71f053e809926d45b86b0afbc3686bb ] When the PCIe controller is running in endpoint mode, the controller initialization is triggered by a PERST# (PCIe reset) GPIO deassertion. The driver has configured an IRQ to trigger when the PERST# GPIO changes state. Without the pinctrl definition, we do not get an IRQ when PERST# is deasserted, so the PCIe controller never gets initialized. Add the missing definitions, so that the controller actually gets initialized. Fixes: ec142c44b026 ("arm64: tegra: Add P2U and PCIe controller nodes to Tegra234 DT") Fixes: 0580286d0d22 ("arm64: tegra: Add Tegra234 PCIe C4 EP definition") Signed-off-by: Niklas Cassel Reviewed-by: Manikanta Maddireddy [treding@nvidia.com: add blank lines to separate blocks] Signed-off-by: Thierry Reding Signed-off-by: Sasha Levin --- arch/arm64/boot/dts/nvidia/tegra234.dtsi | 61 ++++++++++++++++++++++++ 1 file changed, 61 insertions(+) diff --git a/arch/arm64/boot/dts/nvidia/tegra234.dtsi b/arch/arm64/boot/dts/nvidia/tegra234.dtsi index df034dbb82853e..5657045c53d90d 100644 --- a/arch/arm64/boot/dts/nvidia/tegra234.dtsi +++ b/arch/arm64/boot/dts/nvidia/tegra234.dtsi @@ -9,6 +9,7 @@ #include #include #include +#include / { compatible = "nvidia,tegra234"; @@ -127,6 +128,56 @@ pinmux: pinmux@2430000 { compatible = "nvidia,tegra234-pinmux"; reg = <0x0 0x2430000 0x0 0x19100>; + + pex_rst_c4_in_state: pinmux-pex-rst-c4-in { + pex_rst { + nvidia,pins = "pex_l4_rst_n_pl1"; + nvidia,function = "rsvd1"; + nvidia,pull = ; + nvidia,tristate = ; + nvidia,enable-input = ; + }; + }; + + pex_rst_c5_in_state: pinmux-pex-rst-c5-in { + pex_rst { + nvidia,pins = "pex_l5_rst_n_paf1"; + nvidia,function = "rsvd1"; + nvidia,pull = ; + nvidia,tristate = ; + nvidia,enable-input = ; + }; + }; + + pex_rst_c6_in_state: pinmux-pex-rst-c6-in { + pex_rst { + nvidia,pins = "pex_l6_rst_n_paf3"; + nvidia,function = "rsvd1"; + nvidia,pull = ; + nvidia,tristate = ; + nvidia,enable-input = ; + }; + }; + + pex_rst_c7_in_state: pinmux-pex-rst-c7-in { + pex_rst { + nvidia,pins = "pex_l7_rst_n_pag1"; + nvidia,function = "rsvd1"; + nvidia,pull = ; + nvidia,tristate = ; + nvidia,enable-input = ; + }; + }; + + pex_rst_c10_in_state: pinmux-pex-rst-c10-in { + pex_rst { + nvidia,pins = "pex_l10_rst_n_pag7"; + nvidia,function = "rsvd1"; + nvidia,pull = ; + nvidia,tristate = ; + nvidia,enable-input = ; + }; + }; }; gpcdma: dma-controller@2600000 { @@ -4630,6 +4681,8 @@ <&bpmp TEGRA234_RESET_PEX2_CORE_10>; reset-names = "apb", "core"; + pinctrl-names = "default"; + pinctrl-0 = <&pex_rst_c10_in_state>; interrupts = ; /* controller interrupt */ interrupt-names = "intr"; @@ -4881,6 +4934,8 @@ <&bpmp TEGRA234_RESET_PEX0_CORE_4>; reset-names = "apb", "core"; + pinctrl-names = "default"; + pinctrl-0 = <&pex_rst_c4_in_state>; interrupts = ; /* controller interrupt */ interrupt-names = "intr"; nvidia,bpmp = <&bpmp 4>; @@ -5023,6 +5078,8 @@ <&bpmp TEGRA234_RESET_PEX1_CORE_5>; reset-names = "apb", "core"; + pinctrl-names = "default"; + pinctrl-0 = <&pex_rst_c5_in_state>; interrupts = ; /* controller interrupt */ interrupt-names = "intr"; @@ -5115,6 +5172,8 @@ <&bpmp TEGRA234_RESET_PEX1_CORE_6>; reset-names = "apb", "core"; + pinctrl-names = "default"; + pinctrl-0 = <&pex_rst_c6_in_state>; interrupts = ; /* controller interrupt */ interrupt-names = "intr"; @@ -5207,6 +5266,8 @@ <&bpmp TEGRA234_RESET_PEX2_CORE_7>; reset-names = "apb", "core"; + pinctrl-names = "default"; + pinctrl-0 = <&pex_rst_c7_in_state>; interrupts = ; /* controller interrupt */ interrupt-names = "intr"; From cda077a19f5c8d6ec61e5b97deca203d95e3a422 Mon Sep 17 00:00:00 2001 From: Xiaoqi Zhuang Date: Tue, 21 Oct 2025 16:45:25 +0800 Subject: [PATCH 0575/3902] coresight: ETR: Fix ETR buffer use-after-free issue [ Upstream commit 35501ac3c7d40a7bb9568c2f89d6b56beaf9bed3 ] When ETR is enabled as CS_MODE_SYSFS, if the buffer size is changed and enabled again, currently sysfs_buf will point to the newly allocated memory(buf_new) and free the old memory(buf_old). But the etr_buf that is being used by the ETR remains pointed to buf_old, not updated to buf_new. In this case, it will result in a memory use-after-free issue. Fix this by checking ETR's mode before updating and releasing buf_old, if the mode is CS_MODE_SYSFS, then skip updating and releasing it. Fixes: bd2767ec3df2 ("coresight: Fix run time warnings while reusing ETR buffer") Signed-off-by: Xiaoqi Zhuang Signed-off-by: Suzuki K Poulose Tested-by: Leo Yan Link: https://lore.kernel.org/r/20251021-fix_etr_issue-v3-1-99a2d066fee2@oss.qualcomm.com Signed-off-by: Sasha Levin --- drivers/hwtracing/coresight/coresight-tmc-etr.c | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/drivers/hwtracing/coresight/coresight-tmc-etr.c b/drivers/hwtracing/coresight/coresight-tmc-etr.c index b07fcdb3fe1a8c..800be06598c1b7 100644 --- a/drivers/hwtracing/coresight/coresight-tmc-etr.c +++ b/drivers/hwtracing/coresight/coresight-tmc-etr.c @@ -1250,6 +1250,13 @@ static struct etr_buf *tmc_etr_get_sysfs_buffer(struct coresight_device *csdev) * with the lock released. */ raw_spin_lock_irqsave(&drvdata->spinlock, flags); + + /* + * If the ETR is already enabled, continue with the existing buffer. + */ + if (coresight_get_mode(csdev) == CS_MODE_SYSFS) + goto out; + sysfs_buf = READ_ONCE(drvdata->sysfs_buf); if (!sysfs_buf || (sysfs_buf->size != drvdata->size)) { raw_spin_unlock_irqrestore(&drvdata->spinlock, flags); From ac3cd83b02c75289a855eb37b79d30430f562e5f Mon Sep 17 00:00:00 2001 From: Usama Arif Date: Mon, 3 Nov 2025 14:09:22 +0000 Subject: [PATCH 0576/3902] x86/boot: Fix page table access in 5-level to 4-level paging transition [ Upstream commit eb2266312507d7b757859e2227aa5c4ba6280ebe ] When transitioning from 5-level to 4-level paging, the existing code incorrectly accesses page table entries by directly dereferencing CR3 and applying PAGE_MASK. This approach has several issues: - __native_read_cr3() returns the raw CR3 register value, which on x86_64 includes not just the physical address but also flags. Bits above the physical address width of the system i.e. above __PHYSICAL_MASK_SHIFT) are also not masked. - The PGD entry is masked by PAGE_SIZE which doesn't take into account the higher bits such as _PAGE_BIT_NOPTISHADOW. Replace this with proper accessor functions: - native_read_cr3_pa(): Uses CR3_ADDR_MASK to additionally mask metadata out of CR3 (like SME or LAM bits). All remaining bits are real address bits or reserved and must be 0. - mask pgd value with PTE_PFN_MASK instead of PAGE_MASK, accounting for flags above bit 51 (_PAGE_BIT_NOPTISHADOW in particular). Bits below 51, but above the max physical address are reserved and must be 0. Fixes: e9d0e6330eb8 ("x86/boot/compressed/64: Prepare new top-level page table for trampoline") Reported-by: Michael van der Westhuizen Reported-by: Tobias Fleig Co-developed-by: Kiryl Shutsemau Signed-off-by: Kiryl Shutsemau Signed-off-by: Usama Arif Signed-off-by: Borislav Petkov (AMD) Reviewed-by: Ard Biesheuvel Acked-by: Dave Hansen Link: https://lore.kernel.org/r/a482fd68-ce54-472d-8df1-33d6ac9f6bb5@intel.com Signed-off-by: Sasha Levin --- arch/x86/boot/compressed/pgtable_64.c | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/arch/x86/boot/compressed/pgtable_64.c b/arch/x86/boot/compressed/pgtable_64.c index bdd26050dff773..0e89e197e11264 100644 --- a/arch/x86/boot/compressed/pgtable_64.c +++ b/arch/x86/boot/compressed/pgtable_64.c @@ -3,6 +3,7 @@ #include #include #include +#include #include #include "../string.h" #include "efi.h" @@ -168,9 +169,10 @@ asmlinkage void configure_5level_paging(struct boot_params *bp, void *pgtable) * For 4- to 5-level paging transition, set up current CR3 as * the first and the only entry in a new top-level page table. */ - *trampoline_32bit = __native_read_cr3() | _PAGE_TABLE_NOENC; + *trampoline_32bit = native_read_cr3_pa() | _PAGE_TABLE_NOENC; } else { - unsigned long src; + u64 *new_cr3; + pgd_t *pgdp; /* * For 5- to 4-level paging transition, copy page table pointed @@ -180,8 +182,9 @@ asmlinkage void configure_5level_paging(struct boot_params *bp, void *pgtable) * We cannot just point to the page table from trampoline as it * may be above 4G. */ - src = *(unsigned long *)__native_read_cr3() & PAGE_MASK; - memcpy(trampoline_32bit, (void *)src, PAGE_SIZE); + pgdp = (pgd_t *)native_read_cr3_pa(); + new_cr3 = (u64 *)(native_pgd_val(pgdp[0]) & PTE_PFN_MASK); + memcpy(trampoline_32bit, new_cr3, PAGE_SIZE); } toggle_la57(trampoline_32bit); From a89d6e83f0e814bda39d073dc361457dd51ae3c3 Mon Sep 17 00:00:00 2001 From: Usama Arif Date: Mon, 3 Nov 2025 14:09:23 +0000 Subject: [PATCH 0577/3902] efi/libstub: Fix page table access in 5-level to 4-level paging transition [ Upstream commit 84361123413efc84b06f3441c6c827b95d902732 ] When transitioning from 5-level to 4-level paging, the existing code incorrectly accesses page table entries by directly dereferencing CR3 and applying PAGE_MASK. This approach has several issues: - __native_read_cr3() returns the raw CR3 register value, which on x86_64 includes not just the physical address but also flags Bits above the physical address width of the system (i.e. above __PHYSICAL_MASK_SHIFT) are also not masked. - The pgd value is masked by PAGE_SIZE which doesn't take into account the higher bits such as _PAGE_BIT_NOPTISHADOW. Replace this with proper accessor functions: - native_read_cr3_pa(): Uses CR3_ADDR_MASK to additionally mask metadata out of CR3 (like SME or LAM bits). All remaining bits are real address bits or reserved and must be 0. - mask pgd value with PTE_PFN_MASK instead of PAGE_MASK, accounting for flags above bit 51 (_PAGE_BIT_NOPTISHADOW in particular). Bits below 51, but above the max physical address are reserved and must be 0. Fixes: cb1c9e02b0c1 ("x86/efistub: Perform 4/5 level paging switch from the stub") Reported-by: Michael van der Westhuizen Reported-by: Tobias Fleig Co-developed-by: Kiryl Shutsemau Signed-off-by: Kiryl Shutsemau Signed-off-by: Usama Arif Signed-off-by: Borislav Petkov (AMD) Reviewed-by: Ard Biesheuvel Link: https://patch.msgid.link/20251103141002.2280812-3-usamaarif642@gmail.com Signed-off-by: Sasha Levin --- drivers/firmware/efi/libstub/x86-5lvl.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/firmware/efi/libstub/x86-5lvl.c b/drivers/firmware/efi/libstub/x86-5lvl.c index f1c5fb45d5f7cf..c00d0ae7ed5d5a 100644 --- a/drivers/firmware/efi/libstub/x86-5lvl.c +++ b/drivers/firmware/efi/libstub/x86-5lvl.c @@ -66,7 +66,7 @@ void efi_5level_switch(void) bool have_la57 = native_read_cr4() & X86_CR4_LA57; bool need_toggle = want_la57 ^ have_la57; u64 *pgt = (void *)la57_toggle + PAGE_SIZE; - u64 *cr3 = (u64 *)__native_read_cr3(); + pgd_t *cr3 = (pgd_t *)native_read_cr3_pa(); u64 *new_cr3; if (!la57_toggle || !need_toggle) @@ -82,7 +82,7 @@ void efi_5level_switch(void) new_cr3[0] = (u64)cr3 | _PAGE_TABLE_NOENC; } else { /* take the new root table pointer from the current entry #0 */ - new_cr3 = (u64 *)(cr3[0] & PAGE_MASK); + new_cr3 = (u64 *)(native_pgd_val(cr3[0]) & PTE_PFN_MASK); /* copy the new root table if it is not 32-bit addressable */ if ((u64)new_cr3 > U32_MAX) From 9253f39bcf9af402a16ef6edd8bd502513bbef14 Mon Sep 17 00:00:00 2001 From: Wang Liang Date: Wed, 5 Nov 2025 12:19:56 -0800 Subject: [PATCH 0578/3902] locktorture: Fix memory leak in param_set_cpumask() [ Upstream commit e52b43883d084a9af263c573f2a1bd1ca5088389 ] With CONFIG_CPUMASK_OFFSTACK=y, the 'bind_writers' buffer is allocated via alloc_cpumask_var() in param_set_cpumask(). But it is not freed, when setting the module parameter multiple times by sysfs interface or removing module. Below kmemleak trace is seen for this issue: unreferenced object 0xffff888100aabff8 (size 8): comm "bash", pid 323, jiffies 4295059233 hex dump (first 8 bytes): 07 00 00 00 00 00 00 00 ........ backtrace (crc ac50919): __kmalloc_node_noprof+0x2e5/0x420 alloc_cpumask_var_node+0x1f/0x30 param_set_cpumask+0x26/0xb0 [locktorture] param_attr_store+0x93/0x100 module_attr_store+0x1b/0x30 kernfs_fop_write_iter+0x114/0x1b0 vfs_write+0x300/0x410 ksys_write+0x60/0xd0 do_syscall_64+0xa4/0x260 entry_SYSCALL_64_after_hwframe+0x77/0x7f This issue can be reproduced by: insmod locktorture.ko bind_writers=1 rmmod locktorture or: insmod locktorture.ko bind_writers=1 echo 2 > /sys/module/locktorture/parameters/bind_writers Considering that setting the module parameter 'bind_writers' or 'bind_readers' by sysfs interface has no real effect, set the parameter permissions to 0444. To fix the memory leak when removing module, free 'bind_writers' and 'bind_readers' memory in lock_torture_cleanup(). Fixes: 73e341242483 ("locktorture: Add readers_bind and writers_bind module parameters") Suggested-by: Zhang Changzhong Signed-off-by: Wang Liang Signed-off-by: Paul E. McKenney Signed-off-by: Frederic Weisbecker Signed-off-by: Sasha Levin --- kernel/locking/locktorture.c | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/kernel/locking/locktorture.c b/kernel/locking/locktorture.c index ce0362f0a8719a..6567e5eeacc0e1 100644 --- a/kernel/locking/locktorture.c +++ b/kernel/locking/locktorture.c @@ -103,8 +103,8 @@ static const struct kernel_param_ops lt_bind_ops = { .get = param_get_cpumask, }; -module_param_cb(bind_readers, <_bind_ops, &bind_readers, 0644); -module_param_cb(bind_writers, <_bind_ops, &bind_writers, 0644); +module_param_cb(bind_readers, <_bind_ops, &bind_readers, 0444); +module_param_cb(bind_writers, <_bind_ops, &bind_writers, 0444); long torture_sched_setaffinity(pid_t pid, const struct cpumask *in_mask, bool dowarn); @@ -1211,6 +1211,10 @@ static void lock_torture_cleanup(void) cxt.cur_ops->exit(); cxt.init_called = false; } + + free_cpumask_var(bind_readers); + free_cpumask_var(bind_writers); + torture_cleanup_end(); } From bcf44fe565bf1bf098fecd338a625db3180ec531 Mon Sep 17 00:00:00 2001 From: Fedor Pchelkin Date: Tue, 4 Nov 2025 16:57:08 +0300 Subject: [PATCH 0579/3902] wifi: rtw89: usb: use common error path for skbs in rtw89_usb_rx_handler() [ Upstream commit 28a45575289f3292aa9cb7bacae18ba3ee7a6adf ] Allow adding rx_skb to rx_free_queue for later reuse on the common error handling path, otherwise free it. Found by Linux Verification Center (linuxtesting.org). Fixes: 2135c28be6a8 ("wifi: rtw89: Add usb.{c,h}") Acked-by: Ping-Ke Shih Signed-off-by: Fedor Pchelkin Signed-off-by: Ping-Ke Shih Link: https://patch.msgid.link/20251104135720.321110-2-pchelkin@ispras.ru Signed-off-by: Sasha Levin --- drivers/net/wireless/realtek/rtw89/usb.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/drivers/net/wireless/realtek/rtw89/usb.c b/drivers/net/wireless/realtek/rtw89/usb.c index 6cf89aee252eda..e8e064cf7e0ad9 100644 --- a/drivers/net/wireless/realtek/rtw89/usb.c +++ b/drivers/net/wireless/realtek/rtw89/usb.c @@ -410,8 +410,7 @@ static void rtw89_usb_rx_handler(struct work_struct *work) if (skb_queue_len(&rtwusb->rx_queue) >= RTW89_USB_MAX_RXQ_LEN) { rtw89_warn(rtwdev, "rx_queue overflow\n"); - dev_kfree_skb_any(rx_skb); - continue; + goto free_or_reuse; } memset(&desc_info, 0, sizeof(desc_info)); @@ -422,7 +421,7 @@ static void rtw89_usb_rx_handler(struct work_struct *work) rtw89_debug(rtwdev, RTW89_DBG_HCI, "failed to allocate RX skb of size %u\n", desc_info.pkt_size); - continue; + goto free_or_reuse; } pkt_offset = desc_info.offset + desc_info.rxd_len; @@ -432,6 +431,7 @@ static void rtw89_usb_rx_handler(struct work_struct *work) rtw89_core_rx(rtwdev, &desc_info, skb); +free_or_reuse: if (skb_queue_len(&rtwusb->rx_free_queue) >= RTW89_USB_RX_SKB_NUM) dev_kfree_skb_any(rx_skb); else From 87cc6fe79c3e5a4760c396fb715dc8b137ffe149 Mon Sep 17 00:00:00 2001 From: Fedor Pchelkin Date: Tue, 4 Nov 2025 16:57:09 +0300 Subject: [PATCH 0580/3902] wifi: rtw89: usb: fix leak in rtw89_usb_write_port() [ Upstream commit 7543818e97d5d54b3b2f75f1c4dedee298d7d914 ] When there is an attempt to write data and RTW89_FLAG_UNPLUGGED is set, this means device is disconnected and no urb is submitted. Return appropriate error code to the caller to properly free the allocated resources. Found by Linux Verification Center (linuxtesting.org). Fixes: 2135c28be6a8 ("wifi: rtw89: Add usb.{c,h}") Acked-by: Ping-Ke Shih Signed-off-by: Fedor Pchelkin Signed-off-by: Ping-Ke Shih Link: https://patch.msgid.link/20251104135720.321110-3-pchelkin@ispras.ru Signed-off-by: Sasha Levin --- drivers/net/wireless/realtek/rtw89/usb.c | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/drivers/net/wireless/realtek/rtw89/usb.c b/drivers/net/wireless/realtek/rtw89/usb.c index e8e064cf7e0ad9..512a46dd9d06a3 100644 --- a/drivers/net/wireless/realtek/rtw89/usb.c +++ b/drivers/net/wireless/realtek/rtw89/usb.c @@ -256,7 +256,7 @@ static int rtw89_usb_write_port(struct rtw89_dev *rtwdev, u8 ch_dma, int ret; if (test_bit(RTW89_FLAG_UNPLUGGED, rtwdev->flags)) - return 0; + return -ENODEV; urb = usb_alloc_urb(0, GFP_ATOMIC); if (!urb) @@ -305,8 +305,9 @@ static void rtw89_usb_ops_tx_kick_off(struct rtw89_dev *rtwdev, u8 txch) ret = rtw89_usb_write_port(rtwdev, txch, skb->data, skb->len, txcb); if (ret) { - rtw89_err(rtwdev, "write port txch %d failed: %d\n", - txch, ret); + if (ret != -ENODEV) + rtw89_err(rtwdev, "write port txch %d failed: %d\n", + txch, ret); skb_dequeue(&txcb->tx_ack_queue); kfree(txcb); From 9922a95abfc1b13d49cd07343fa5268cabee3bf1 Mon Sep 17 00:00:00 2001 From: Haotian Zhang Date: Fri, 10 Oct 2025 09:17:36 +0800 Subject: [PATCH 0581/3902] mfd: da9055: Fix missing regmap_del_irq_chip() in error path [ Upstream commit 1b58acfd067ca16116b9234cd6b2d30cc8ab7502 ] When da9055_device_init() fails after regmap_add_irq_chip() succeeds but mfd_add_devices() fails, the error handling path only calls mfd_remove_devices() but forgets to call regmap_del_irq_chip(). This results in a resource leak. Fix this by adding regmap_del_irq_chip() to the error path so that resources are released properly. Fixes: 2896434cf272 ("mfd: DA9055 core driver") Signed-off-by: Haotian Zhang Link: https://patch.msgid.link/20251010011737.1078-1-vulab@iscas.ac.cn Signed-off-by: Lee Jones Signed-off-by: Sasha Levin --- drivers/mfd/da9055-core.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/mfd/da9055-core.c b/drivers/mfd/da9055-core.c index 1f727ef60d6387..8c989b74f924e3 100644 --- a/drivers/mfd/da9055-core.c +++ b/drivers/mfd/da9055-core.c @@ -388,6 +388,7 @@ int da9055_device_init(struct da9055 *da9055) err: mfd_remove_devices(da9055->dev); + regmap_del_irq_chip(da9055->chip_irq, da9055->irq_data); return ret; } From c7ca777c58795d0940891ae3577f629c85311d61 Mon Sep 17 00:00:00 2001 From: Manish Dharanenthiran Date: Fri, 31 Oct 2025 08:37:46 +0530 Subject: [PATCH 0582/3902] wifi: ath12k: Fix timeout error during beacon stats retrieval [ Upstream commit 2977567b244f056d86658160659f06cd6c78ba3d ] Currently, for beacon_stats, ath12k_mac_get_fw_stats() is called for each started BSS on the specified hardware. ath12k_mac_get_fw_stats() will wait for the fw_stats_done completion after fetching the requested data from firmware. For the beacon_stats, fw_stats_done completion will be set only when stats are received for all BSSes. However, for other stats like vdev_stats or pdev_stats, there is one request to the firmware for all enabled BSSes. Since beacon_stats is fetched individually for all BSSes enabled in that pdev, waiting for the completion event results in a timeout error when multiple BSSes are enabled. Avoid this by completing the fw_stats_done immediately after updating the requested BSS's beacon stats in the list. Subsequently, this list will be used to display the beacon stats for all enabled BSSes in the requested pdev. Additionally, remove 'num_bcn_recvd' from the ath12k_fw_stats struct as it is no longer needed. Tested-on: QCN9274 hw2.0 PCI WLAN.WBE.1.4.1-00199-QCAHKSWPL_SILICONZ-1 Fixes: 9fe4669ae919 ("wifi: ath12k: Request beacon stats from firmware") Signed-off-by: Manish Dharanenthiran Reviewed-by: Vasanthakumar Thiagarajan Reviewed-by: Baochen Qiang Link: https://patch.msgid.link/20251031-beacon_stats-v1-2-f52fce7b03ac@qti.qualcomm.com Signed-off-by: Jeff Johnson Signed-off-by: Sasha Levin --- drivers/net/wireless/ath/ath12k/core.c | 2 -- drivers/net/wireless/ath/ath12k/core.h | 1 - drivers/net/wireless/ath/ath12k/wmi.c | 10 +--------- 3 files changed, 1 insertion(+), 12 deletions(-) diff --git a/drivers/net/wireless/ath/ath12k/core.c b/drivers/net/wireless/ath/ath12k/core.c index a2137b363c2fea..cc352eef193993 100644 --- a/drivers/net/wireless/ath/ath12k/core.c +++ b/drivers/net/wireless/ath/ath12k/core.c @@ -1,7 +1,6 @@ // SPDX-License-Identifier: BSD-3-Clause-Clear /* * Copyright (c) 2018-2021 The Linux Foundation. All rights reserved. - * Copyright (c) 2021-2025 Qualcomm Innovation Center, Inc. All rights reserved. * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries. */ @@ -1250,7 +1249,6 @@ void ath12k_fw_stats_reset(struct ath12k *ar) spin_lock_bh(&ar->data_lock); ath12k_fw_stats_free(&ar->fw_stats); ar->fw_stats.num_vdev_recvd = 0; - ar->fw_stats.num_bcn_recvd = 0; spin_unlock_bh(&ar->data_lock); } diff --git a/drivers/net/wireless/ath/ath12k/core.h b/drivers/net/wireless/ath/ath12k/core.h index 3d1956966a485a..d7688b383f62c9 100644 --- a/drivers/net/wireless/ath/ath12k/core.h +++ b/drivers/net/wireless/ath/ath12k/core.h @@ -644,7 +644,6 @@ struct ath12k_fw_stats { struct list_head vdevs; struct list_head bcn; u32 num_vdev_recvd; - u32 num_bcn_recvd; }; struct ath12k_dbg_htt_stats { diff --git a/drivers/net/wireless/ath/ath12k/wmi.c b/drivers/net/wireless/ath/ath12k/wmi.c index e76275bd6916f7..e647b842a6a1cd 100644 --- a/drivers/net/wireless/ath/ath12k/wmi.c +++ b/drivers/net/wireless/ath/ath12k/wmi.c @@ -8418,18 +8418,10 @@ static void ath12k_wmi_fw_stats_process(struct ath12k *ar, ath12k_warn(ab, "empty beacon stats"); return; } - /* Mark end until we reached the count of all started VDEVs - * within the PDEV - */ - if (ar->num_started_vdevs) - is_end = ((++ar->fw_stats.num_bcn_recvd) == - ar->num_started_vdevs); list_splice_tail_init(&stats->bcn, &ar->fw_stats.bcn); - - if (is_end) - complete(&ar->fw_stats_done); + complete(&ar->fw_stats_done); } } From c94c069c0b120af7b698542d0d10597b618aa395 Mon Sep 17 00:00:00 2001 From: Zhang Yi Date: Mon, 13 Oct 2025 09:51:17 +0800 Subject: [PATCH 0583/3902] ext4: correct the checking of quota files before moving extents [ Upstream commit a2e5a3cea4b18f6e2575acc444a5e8cce1fc8260 ] The move extent operation should return -EOPNOTSUPP if any of the inodes is a quota inode, rather than requiring both to be quota inodes. Fixes: 02749a4c2082 ("ext4: add ext4_is_quota_file()") Signed-off-by: Zhang Yi Reviewed-by: Jan Kara Message-ID: <20251013015128.499308-2-yi.zhang@huaweicloud.com> Signed-off-by: Theodore Ts'o Signed-off-by: Sasha Levin --- fs/ext4/move_extent.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fs/ext4/move_extent.c b/fs/ext4/move_extent.c index 4b091c21908fda..0f4b7c89edd398 100644 --- a/fs/ext4/move_extent.c +++ b/fs/ext4/move_extent.c @@ -485,7 +485,7 @@ mext_check_arguments(struct inode *orig_inode, return -ETXTBSY; } - if (ext4_is_quota_file(orig_inode) && ext4_is_quota_file(donor_inode)) { + if (ext4_is_quota_file(orig_inode) || ext4_is_quota_file(donor_inode)) { ext4_debug("ext4 move extent: The argument files should not be quota files [ino:orig %lu, donor %lu]\n", orig_inode->i_ino, donor_inode->i_ino); return -EOPNOTSUPP; From ba2726d43d3b33c36a75707086c15c35afcbb82e Mon Sep 17 00:00:00 2001 From: Lizhi Hou Date: Wed, 5 Nov 2025 11:41:40 -0800 Subject: [PATCH 0584/3902] accel/amdxdna: Fix dma_fence leak when job is canceled [ Upstream commit dea9f84776b96a703f504631ebe9fea07bd2c181 ] Currently, dma_fence_put(job->fence) is called in job notification callback. However, if a job is canceled, the notification callback is never invoked, leading to a memory leak. Move dma_fence_put(job->fence) to the job cleanup function to ensure the fence is always released. Fixes: aac243092b70 ("accel/amdxdna: Add command execution") Reviewed-by: Mario Limonciello (AMD) Signed-off-by: Lizhi Hou Link: https://patch.msgid.link/20251105194140.1004314-1-lizhi.hou@amd.com Signed-off-by: Sasha Levin --- drivers/accel/amdxdna/aie2_ctx.c | 1 - drivers/accel/amdxdna/amdxdna_ctx.c | 1 + 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/accel/amdxdna/aie2_ctx.c b/drivers/accel/amdxdna/aie2_ctx.c index c9f712f0ec00cc..2e479a8d86c310 100644 --- a/drivers/accel/amdxdna/aie2_ctx.c +++ b/drivers/accel/amdxdna/aie2_ctx.c @@ -189,7 +189,6 @@ aie2_sched_notify(struct amdxdna_sched_job *job) up(&job->hwctx->priv->job_sem); job->job_done = true; - dma_fence_put(fence); mmput_async(job->mm); aie2_job_put(job); } diff --git a/drivers/accel/amdxdna/amdxdna_ctx.c b/drivers/accel/amdxdna/amdxdna_ctx.c index 4bfe4ef20550ff..856fb25086f126 100644 --- a/drivers/accel/amdxdna/amdxdna_ctx.c +++ b/drivers/accel/amdxdna/amdxdna_ctx.c @@ -389,6 +389,7 @@ void amdxdna_sched_job_cleanup(struct amdxdna_sched_job *job) trace_amdxdna_debug_point(job->hwctx->name, job->seq, "job release"); amdxdna_arg_bos_put(job); amdxdna_gem_put_obj(job->cmd_bo); + dma_fence_put(job->fence); } int amdxdna_cmd_submit(struct amdxdna_client *client, From 40a1e0142096dd7dd6cb5373841222b528698588 Mon Sep 17 00:00:00 2001 From: Dan Carpenter Date: Fri, 3 Oct 2025 12:30:43 +0300 Subject: [PATCH 0585/3902] hfs: fix potential use after free in hfs_correct_next_unused_CNID() [ Upstream commit c105e76bb17cf4b55fe89c6ad4f6a0e3972b5b08 ] This code calls hfs_bnode_put(node) which drops the refcount and then dreferences "node" on the next line. It's only safe to use "node" when we're holding a reference so flip these two lines around. Fixes: a06ec283e125 ("hfs: add logic of correcting a next unused CNID") Signed-off-by: Dan Carpenter Reviewed-by: Viacheslav Dubeyko Signed-off-by: Viacheslav Dubeyko Link: https://lore.kernel.org/r/aN-Xw8KnbSnuIcLk@stanley.mountain Signed-off-by: Viacheslav Dubeyko Signed-off-by: Sasha Levin --- fs/hfs/catalog.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fs/hfs/catalog.c b/fs/hfs/catalog.c index caebabb6642f16..b80ba40e387761 100644 --- a/fs/hfs/catalog.c +++ b/fs/hfs/catalog.c @@ -322,9 +322,9 @@ int hfs_correct_next_unused_CNID(struct super_block *sb, u32 cnid) } } + node_id = node->prev; hfs_bnode_put(node); - node_id = node->prev; } while (node_id >= leaf_head); return -ENOENT; From b9422f46d9c54d0b67ea650ae083f766e6e58e9f Mon Sep 17 00:00:00 2001 From: FUKAUMI Naoki Date: Tue, 4 Nov 2025 08:52:27 +0000 Subject: [PATCH 0586/3902] arm64: dts: rockchip: Fix USB Type-C host mode for Radxa ROCK 5B+/5T [ Upstream commit fbf90d1b697faf61bb8b3ed72be6a8ebeb09de3d ] The Radxa ROCK 5B+/5T USB Type-C port supports Dual Role Data and should also act as a host. However, currently, when acting as a host, only self-powered devices work. Since the ROCK 5B+ supports Dual Role Power, set the power-role property to "dual" and the try-power-role property to "sink". (along with related properties) The ROCK 5T should only support the "source" power-role. This allows the port to act as a host, supply power to the port, and allow bus-powered devices to work. Note that on the ROCK 5T, with this patch applied, it has been observed that some bus-powered devices do not work correctly. Also, it has been observed that after connecting a device (and the data-role switches to host), connecting a host device does not switch the data-role back to the device role. These issues should be addressed separately. Note that there is a separate known issue where USB 3.0 SuperSpeed devices do not work when oriented in reverse. This issue should also be addressed separately. (USB 2.0/1.1 devices work in both orientations) Fixes: 67b2c15d8fb3c ("arm64: dts: rockchip: add USB-C support for ROCK 5B/5B+/5T") Signed-off-by: FUKAUMI Naoki Link: https://patch.msgid.link/20251104085227.820-1-naoki@radxa.com Signed-off-by: Heiko Stuebner Signed-off-by: Sasha Levin --- arch/arm64/boot/dts/rockchip/rk3588-rock-5b-5bp-5t.dtsi | 4 ++-- arch/arm64/boot/dts/rockchip/rk3588-rock-5b-plus.dts | 5 +++++ arch/arm64/boot/dts/rockchip/rk3588-rock-5b.dts | 4 ++++ arch/arm64/boot/dts/rockchip/rk3588-rock-5t.dts | 4 ++++ 4 files changed, 15 insertions(+), 2 deletions(-) diff --git a/arch/arm64/boot/dts/rockchip/rk3588-rock-5b-5bp-5t.dtsi b/arch/arm64/boot/dts/rockchip/rk3588-rock-5b-5bp-5t.dtsi index 3bbe78810ec6f2..7aac77dfc5f165 100644 --- a/arch/arm64/boot/dts/rockchip/rk3588-rock-5b-5bp-5t.dtsi +++ b/arch/arm64/boot/dts/rockchip/rk3588-rock-5b-5bp-5t.dtsi @@ -331,12 +331,12 @@ data-role = "dual"; /* fusb302 supports PD Rev 2.0 Ver 1.2 */ pd-revision = /bits/ 8 <0x2 0x0 0x1 0x2>; - power-role = "sink"; - try-power-role = "sink"; op-sink-microwatt = <1000000>; sink-pdos = , ; + source-pdos = + ; altmodes { displayport { diff --git a/arch/arm64/boot/dts/rockchip/rk3588-rock-5b-plus.dts b/arch/arm64/boot/dts/rockchip/rk3588-rock-5b-plus.dts index 5e984a44120e40..07a840d9b38595 100644 --- a/arch/arm64/boot/dts/rockchip/rk3588-rock-5b-plus.dts +++ b/arch/arm64/boot/dts/rockchip/rk3588-rock-5b-plus.dts @@ -110,6 +110,11 @@ }; }; +&usb_con { + power-role = "dual"; + try-power-role = "sink"; +}; + &usbdp_phy0 { pinctrl-names = "default"; pinctrl-0 = <&usbc_sbu_dc>; diff --git a/arch/arm64/boot/dts/rockchip/rk3588-rock-5b.dts b/arch/arm64/boot/dts/rockchip/rk3588-rock-5b.dts index 8ef01010d985ba..da13dafcbc823b 100644 --- a/arch/arm64/boot/dts/rockchip/rk3588-rock-5b.dts +++ b/arch/arm64/boot/dts/rockchip/rk3588-rock-5b.dts @@ -49,6 +49,10 @@ }; }; +&usb_con { + power-role = "sink"; +}; + &usbdp_phy0 { pinctrl-names = "default"; pinctrl-0 = <&usbc_sbu_dc>; diff --git a/arch/arm64/boot/dts/rockchip/rk3588-rock-5t.dts b/arch/arm64/boot/dts/rockchip/rk3588-rock-5t.dts index c1763835f53d48..0dd90c744380b7 100644 --- a/arch/arm64/boot/dts/rockchip/rk3588-rock-5t.dts +++ b/arch/arm64/boot/dts/rockchip/rk3588-rock-5t.dts @@ -130,6 +130,10 @@ }; }; +&usb_con { + power-role = "source"; +}; + &usbdp_phy0 { pinctrl-names = "default"; pinctrl-0 = <&usbc_sbu_dc>; From e2a1f510e5990d0cc507ffc3d15f981b753ff7df Mon Sep 17 00:00:00 2001 From: Pavel Begunkov Date: Thu, 6 Nov 2025 12:58:19 +0000 Subject: [PATCH 0587/3902] io_uring: use WRITE_ONCE for user shared memory [ Upstream commit 93e197e524b14d185d011813b72773a1a49d932d ] IORING_SETUP_NO_MMAP rings remain user accessible even before the ctx setup is finalised, so use WRITE_ONCE consistently when initialising rings. Fixes: 03d89a2de25bb ("io_uring: support for user allocated memory for rings/sqes") Signed-off-by: Pavel Begunkov Signed-off-by: Jens Axboe Signed-off-by: Sasha Levin --- io_uring/io_uring.c | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/io_uring/io_uring.c b/io_uring/io_uring.c index 02339b74ba8d4e..765abec2571f03 100644 --- a/io_uring/io_uring.c +++ b/io_uring/io_uring.c @@ -3623,10 +3623,6 @@ static __cold int io_allocate_scq_urings(struct io_ring_ctx *ctx, if (!(ctx->flags & IORING_SETUP_NO_SQARRAY)) ctx->sq_array = (u32 *)((char *)rings + sq_array_offset); - rings->sq_ring_mask = p->sq_entries - 1; - rings->cq_ring_mask = p->cq_entries - 1; - rings->sq_ring_entries = p->sq_entries; - rings->cq_ring_entries = p->cq_entries; if (p->flags & IORING_SETUP_SQE128) size = array_size(2 * sizeof(struct io_uring_sqe), p->sq_entries); @@ -3649,6 +3645,12 @@ static __cold int io_allocate_scq_urings(struct io_ring_ctx *ctx, return ret; } ctx->sq_sqes = io_region_get_ptr(&ctx->sq_region); + + memset(rings, 0, sizeof(*rings)); + WRITE_ONCE(rings->sq_ring_mask, ctx->sq_entries - 1); + WRITE_ONCE(rings->cq_ring_mask, ctx->cq_entries - 1); + WRITE_ONCE(rings->sq_ring_entries, ctx->sq_entries); + WRITE_ONCE(rings->cq_ring_entries, ctx->cq_entries); return 0; } From 6b089028bff1f2ff9e0c62b8f1faca1a620e5d6e Mon Sep 17 00:00:00 2001 From: Dapeng Mi Date: Wed, 29 Oct 2025 18:21:26 +0800 Subject: [PATCH 0588/3902] perf/x86: Fix NULL event access and potential PEBS record loss [ Upstream commit 7e772a93eb61cb6265bdd1c5bde17d0f2718b452 ] When intel_pmu_drain_pebs_icl() is called to drain PEBS records, the perf_event_overflow() could be called to process the last PEBS record. While perf_event_overflow() could trigger the interrupt throttle and stop all events of the group, like what the below call-chain shows. perf_event_overflow() -> __perf_event_overflow() ->__perf_event_account_interrupt() -> perf_event_throttle_group() -> perf_event_throttle() -> event->pmu->stop() -> x86_pmu_stop() The side effect of stopping the events is that all corresponding event pointers in cpuc->events[] array are cleared to NULL. Assume there are two PEBS events (event a and event b) in a group. When intel_pmu_drain_pebs_icl() calls perf_event_overflow() to process the last PEBS record of PEBS event a, interrupt throttle is triggered and all pointers of event a and event b are cleared to NULL. Then intel_pmu_drain_pebs_icl() tries to process the last PEBS record of event b and encounters NULL pointer access. To avoid this issue, move cpuc->events[] clearing from x86_pmu_stop() to x86_pmu_del(). It's safe since cpuc->active_mask or cpuc->pebs_enabled is always checked before access the event pointer from cpuc->events[]. Closes: https://lore.kernel.org/oe-lkp/202507042103.a15d2923-lkp@intel.com Fixes: 9734e25fbf5a ("perf: Fix the throttle logic for a group") Reported-by: kernel test robot Suggested-by: Peter Zijlstra Signed-off-by: Dapeng Mi Signed-off-by: Peter Zijlstra (Intel) Link: https://patch.msgid.link/20251029102136.61364-3-dapeng1.mi@linux.intel.com Signed-off-by: Sasha Levin --- arch/x86/events/core.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/arch/x86/events/core.c b/arch/x86/events/core.c index fa6c47b509897a..dd9ff120ad437a 100644 --- a/arch/x86/events/core.c +++ b/arch/x86/events/core.c @@ -1344,6 +1344,7 @@ static void x86_pmu_enable(struct pmu *pmu) hwc->state |= PERF_HES_ARCH; x86_pmu_stop(event, PERF_EF_UPDATE); + cpuc->events[hwc->idx] = NULL; } /* @@ -1365,6 +1366,7 @@ static void x86_pmu_enable(struct pmu *pmu) * if cpuc->enabled = 0, then no wrmsr as * per x86_pmu_enable_event() */ + cpuc->events[hwc->idx] = event; x86_pmu_start(event, PERF_EF_RELOAD); } cpuc->n_added = 0; @@ -1531,7 +1533,6 @@ static void x86_pmu_start(struct perf_event *event, int flags) event->hw.state = 0; - cpuc->events[idx] = event; __set_bit(idx, cpuc->active_mask); static_call(x86_pmu_enable)(event); perf_event_update_userpage(event); @@ -1610,7 +1611,6 @@ void x86_pmu_stop(struct perf_event *event, int flags) if (test_bit(hwc->idx, cpuc->active_mask)) { static_call(x86_pmu_disable)(event); __clear_bit(hwc->idx, cpuc->active_mask); - cpuc->events[hwc->idx] = NULL; WARN_ON_ONCE(hwc->state & PERF_HES_STOPPED); hwc->state |= PERF_HES_STOPPED; } @@ -1648,6 +1648,7 @@ static void x86_pmu_del(struct perf_event *event, int flags) * Not a TXN, therefore cleanup properly. */ x86_pmu_stop(event, PERF_EF_UPDATE); + cpuc->events[event->hw.idx] = NULL; for (i = 0; i < cpuc->n_events; i++) { if (event == cpuc->event_list[i]) From 16d8e53a8f78f24f20b12feed26e7e1b1d056bb0 Mon Sep 17 00:00:00 2001 From: Dapeng Mi Date: Wed, 29 Oct 2025 18:21:28 +0800 Subject: [PATCH 0589/3902] perf/x86/intel: Correct large PEBS flag check [ Upstream commit 5e4e355ae7cdeb0fef5dbe908866e1f895abfacc ] current large PEBS flag check only checks if sample_regs_user contains unsupported GPRs but doesn't check if sample_regs_intr contains unsupported GPRs. Of course, currently PEBS HW supports to sample all perf supported GPRs, the missed check doesn't cause real issue. But it won't be true any more after the subsequent patches support to sample SSP register. SSP sampling is not supported by adaptive PEBS HW and it would be supported until arch-PEBS HW. So correct this issue. Fixes: a47ba4d77e12 ("perf/x86: Enable free running PEBS for REGS_USER/INTR") Signed-off-by: Dapeng Mi Signed-off-by: Peter Zijlstra (Intel) Link: https://patch.msgid.link/20251029102136.61364-5-dapeng1.mi@linux.intel.com Signed-off-by: Sasha Levin --- arch/x86/events/intel/core.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/arch/x86/events/intel/core.c b/arch/x86/events/intel/core.c index fe65be0b9d9c46..9b824ed6fc1de9 100644 --- a/arch/x86/events/intel/core.c +++ b/arch/x86/events/intel/core.c @@ -4029,7 +4029,9 @@ static unsigned long intel_pmu_large_pebs_flags(struct perf_event *event) if (!event->attr.exclude_kernel) flags &= ~PERF_SAMPLE_REGS_USER; if (event->attr.sample_regs_user & ~PEBS_GP_REGS) - flags &= ~(PERF_SAMPLE_REGS_USER | PERF_SAMPLE_REGS_INTR); + flags &= ~PERF_SAMPLE_REGS_USER; + if (event->attr.sample_regs_intr & ~PEBS_GP_REGS) + flags &= ~PERF_SAMPLE_REGS_INTR; return flags; } From 10be1301ae69df4db775b269cdf56dbb9d3d588b Mon Sep 17 00:00:00 2001 From: Gabor Juhos Date: Fri, 7 Nov 2025 18:10:08 +0100 Subject: [PATCH 0590/3902] regulator: core: disable supply if enabling main regulator fails [ Upstream commit fb1ebb10468da414d57153ddebaab29c38ef1a78 ] For 'always-on' and 'boot-on' regulators, the set_machine_constraints() may enable supply before enabling the main regulator, however if the latter fails, the function returns with an error but the supply remains enabled. When this happens, the regulator_register() function continues on the error path where it puts the supply regulator. Since enabling the supply is not balanced with a disable call, a warning similar to the following gets issued from _regulator_put(): [ 1.603889] WARNING: CPU: 2 PID: 44 at _regulator_put+0x8c/0xa0 [ 1.603908] Modules linked in: [ 1.603926] CPU: 2 UID: 0 PID: 44 Comm: kworker/u16:3 Not tainted 6.18.0-rc4 #0 NONE [ 1.603938] Hardware name: Qualcomm Technologies, Inc. IPQ9574/AP-AL02-C7 (DT) [ 1.603945] Workqueue: async async_run_entry_fn [ 1.603958] pstate: 60400005 (nZCv daif +PAN -UAO -TCO -DIT -SSBS BTYPE=--) [ 1.603967] pc : _regulator_put+0x8c/0xa0 [ 1.603976] lr : _regulator_put+0x7c/0xa0 ... [ 1.604140] Call trace: [ 1.604145] _regulator_put+0x8c/0xa0 (P) [ 1.604156] regulator_register+0x2ec/0xbf0 [ 1.604166] devm_regulator_register+0x60/0xb0 [ 1.604178] rpm_reg_probe+0x120/0x208 [ 1.604187] platform_probe+0x64/0xa8 ... In order to avoid this, change the set_machine_constraints() function to disable the supply if enabling the main regulator fails. Fixes: 05f224ca6693 ("regulator: core: Clean enabling always-on regulators + their supplies") Signed-off-by: Gabor Juhos Link: https://patch.msgid.link/20251107-regulator-disable-supply-v1-1-c95f0536f1b5@gmail.com Signed-off-by: Mark Brown Signed-off-by: Sasha Levin --- drivers/regulator/core.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/drivers/regulator/core.c b/drivers/regulator/core.c index dd7b10e768c06c..fc93612f4ec0c3 100644 --- a/drivers/regulator/core.c +++ b/drivers/regulator/core.c @@ -1618,6 +1618,8 @@ static int set_machine_constraints(struct regulator_dev *rdev) * and we have control then make sure it is enabled. */ if (rdev->constraints->always_on || rdev->constraints->boot_on) { + bool supply_enabled = false; + /* If we want to enable this regulator, make sure that we know * the supplying regulator. */ @@ -1637,11 +1639,14 @@ static int set_machine_constraints(struct regulator_dev *rdev) rdev->supply = NULL; return ret; } + supply_enabled = true; } ret = _regulator_do_enable(rdev); if (ret < 0 && ret != -EINVAL) { rdev_err(rdev, "failed to enable: %pe\n", ERR_PTR(ret)); + if (supply_enabled) + regulator_disable(rdev->supply); return ret; } From 59392f424f8442a4533abc1515e79ae5dc3b89a4 Mon Sep 17 00:00:00 2001 From: Xiao Ni Date: Sun, 28 Sep 2025 09:24:24 +0800 Subject: [PATCH 0591/3902] md: delete mddev kobj before deleting gendisk kobj [ Upstream commit cc394b94dc40b661efc9895665abf03640ffff2d ] In sync del gendisk path, it deletes gendisk first and the directory /sys/block/md is removed. Then it releases mddev kobj in a delayed work. If we enable debug log in sysfs_remove_group, we can see the debug log 'sysfs group bitmap not found for kobject md'. It's the reason that the parent kobj has been deleted, so it can't find parent directory. In creating path, it allocs gendisk first, then adds mddev kobj. So it should delete mddev kobj before deleting gendisk. Before commit 9e59d609763f ("md: call del_gendisk in control path"), it releases mddev kobj first. If the kobj hasn't been deleted, it does clean job and deletes the kobj. Then it calls del_gendisk and releases gendisk kobj. So it doesn't need to call kobject_del to delete mddev kobj. After this patch, in sync del gendisk path, the sequence changes. So it needs to call kobject_del to delete mddev kobj. After this patch, the sequence is: 1. kobject del mddev kobj 2. del_gendisk deletes gendisk kobj 3. mddev_delayed_delete releases mddev kobj 4. md_kobj_release releases gendisk kobj Link: https://lore.kernel.org/linux-raid/20250928012424.61370-1-xni@redhat.com Fixes: 9e59d609763f ("md: call del_gendisk in control path") Signed-off-by: Xiao Ni Reviewed-by: Li Nan Signed-off-by: Yu Kuai Signed-off-by: Sasha Levin --- drivers/md/md.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/drivers/md/md.c b/drivers/md/md.c index 41c476b40c7a30..8128c8839a0820 100644 --- a/drivers/md/md.c +++ b/drivers/md/md.c @@ -941,8 +941,10 @@ void mddev_unlock(struct mddev *mddev) * do_md_stop. dm raid only uses md_stop to stop. So dm raid * doesn't need to check MD_DELETED when getting reconfig lock */ - if (test_bit(MD_DELETED, &mddev->flags)) + if (test_bit(MD_DELETED, &mddev->flags)) { + kobject_del(&mddev->kobj); del_gendisk(mddev->gendisk); + } } } EXPORT_SYMBOL_GPL(mddev_unlock); From f98b191f78124405294481dea85f8a22a3eb0a59 Mon Sep 17 00:00:00 2001 From: Yun Zhou Date: Wed, 15 Oct 2025 16:32:27 +0800 Subject: [PATCH 0592/3902] md: fix rcu protection in md_wakeup_thread [ Upstream commit 0dc76205549b4c25705e54345f211b9f66e018a0 ] We attempted to use RCU to protect the pointer 'thread', but directly passed the value when calling md_wakeup_thread(). This means that the RCU pointer has been acquired before rcu_read_lock(), which renders rcu_read_lock() ineffective and could lead to a use-after-free. Link: https://lore.kernel.org/linux-raid/20251015083227.1079009-1-yun.zhou@windriver.com Fixes: 446931543982 ("md: protect md_thread with rcu") Signed-off-by: Yun Zhou Reviewed-by: Li Nan Reviewed-by: Yu Kuai Signed-off-by: Yu Kuai Signed-off-by: Sasha Levin --- drivers/md/md.c | 14 ++++++-------- drivers/md/md.h | 8 +++++++- 2 files changed, 13 insertions(+), 9 deletions(-) diff --git a/drivers/md/md.c b/drivers/md/md.c index 8128c8839a0820..6062e0deb61600 100644 --- a/drivers/md/md.c +++ b/drivers/md/md.c @@ -99,7 +99,7 @@ static int remove_and_add_spares(struct mddev *mddev, struct md_rdev *this); static void mddev_detach(struct mddev *mddev); static void export_rdev(struct md_rdev *rdev, struct mddev *mddev); -static void md_wakeup_thread_directly(struct md_thread __rcu *thread); +static void md_wakeup_thread_directly(struct md_thread __rcu **thread); /* * Default number of read corrections we'll attempt on an rdev @@ -5136,7 +5136,7 @@ static void stop_sync_thread(struct mddev *mddev, bool locked) * Thread might be blocked waiting for metadata update which will now * never happen */ - md_wakeup_thread_directly(mddev->sync_thread); + md_wakeup_thread_directly(&mddev->sync_thread); if (work_pending(&mddev->sync_work)) flush_work(&mddev->sync_work); @@ -8375,22 +8375,21 @@ static int md_thread(void *arg) return 0; } -static void md_wakeup_thread_directly(struct md_thread __rcu *thread) +static void md_wakeup_thread_directly(struct md_thread __rcu **thread) { struct md_thread *t; rcu_read_lock(); - t = rcu_dereference(thread); + t = rcu_dereference(*thread); if (t) wake_up_process(t->tsk); rcu_read_unlock(); } -void md_wakeup_thread(struct md_thread __rcu *thread) +void __md_wakeup_thread(struct md_thread __rcu *thread) { struct md_thread *t; - rcu_read_lock(); t = rcu_dereference(thread); if (t) { pr_debug("md: waking up MD thread %s.\n", t->tsk->comm); @@ -8398,9 +8397,8 @@ void md_wakeup_thread(struct md_thread __rcu *thread) if (wq_has_sleeper(&t->wqueue)) wake_up(&t->wqueue); } - rcu_read_unlock(); } -EXPORT_SYMBOL(md_wakeup_thread); +EXPORT_SYMBOL(__md_wakeup_thread); struct md_thread *md_register_thread(void (*run) (struct md_thread *), struct mddev *mddev, const char *name) diff --git a/drivers/md/md.h b/drivers/md/md.h index 1979c2d4fe89e9..5d5f780b84477c 100644 --- a/drivers/md/md.h +++ b/drivers/md/md.h @@ -882,6 +882,12 @@ struct md_io_clone { #define THREAD_WAKEUP 0 +#define md_wakeup_thread(thread) do { \ + rcu_read_lock(); \ + __md_wakeup_thread(thread); \ + rcu_read_unlock(); \ +} while (0) + static inline void safe_put_page(struct page *p) { if (p) put_page(p); @@ -895,7 +901,7 @@ extern struct md_thread *md_register_thread( struct mddev *mddev, const char *name); extern void md_unregister_thread(struct mddev *mddev, struct md_thread __rcu **threadp); -extern void md_wakeup_thread(struct md_thread __rcu *thread); +extern void __md_wakeup_thread(struct md_thread __rcu *thread); extern void md_check_recovery(struct mddev *mddev); extern void md_reap_sync_thread(struct mddev *mddev); extern enum sync_action md_sync_action(struct mddev *mddev); From f0fae1debeb9102398ddf2ef69b4f5d395afafed Mon Sep 17 00:00:00 2001 From: Xiao Ni Date: Wed, 29 Oct 2025 14:34:19 +0800 Subject: [PATCH 0593/3902] md: avoid repeated calls to del_gendisk [ Upstream commit 90e3bb44c0a86e245d8e5c6520206fa113acb1ee ] There is a uaf problem which is found by case 23rdev-lifetime: Oops: general protection fault, probably for non-canonical address 0xdead000000000122 RIP: 0010:bdi_unregister+0x4b/0x170 Call Trace: __del_gendisk+0x356/0x3e0 mddev_unlock+0x351/0x360 rdev_attr_store+0x217/0x280 kernfs_fop_write_iter+0x14a/0x210 vfs_write+0x29e/0x550 ksys_write+0x74/0xf0 do_syscall_64+0xbb/0x380 entry_SYSCALL_64_after_hwframe+0x77/0x7f RIP: 0033:0x7ff5250a177e The sequence is: 1. rdev remove path gets reconfig_mutex 2. rdev remove path release reconfig_mutex in mddev_unlock 3. md stop calls do_md_stop and sets MD_DELETED 4. rdev remove path calls del_gendisk because MD_DELETED is set 5. md stop path release reconfig_mutex and calls del_gendisk again So there is a race condition we should resolve. This patch adds a flag MD_DO_DELETE to avoid the race condition. Link: https://lore.kernel.org/linux-raid/20251029063419.21700-1-xni@redhat.com Fixes: 9e59d609763f ("md: call del_gendisk in control path") Signed-off-by: Xiao Ni Suggested-by: Yu Kuai Reviewed-by: Li Nan Signed-off-by: Yu Kuai Signed-off-by: Sasha Levin --- drivers/md/md.c | 3 ++- drivers/md/md.h | 1 + 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/drivers/md/md.c b/drivers/md/md.c index 6062e0deb61600..5d40beaecc9c7c 100644 --- a/drivers/md/md.c +++ b/drivers/md/md.c @@ -941,7 +941,8 @@ void mddev_unlock(struct mddev *mddev) * do_md_stop. dm raid only uses md_stop to stop. So dm raid * doesn't need to check MD_DELETED when getting reconfig lock */ - if (test_bit(MD_DELETED, &mddev->flags)) { + if (test_bit(MD_DELETED, &mddev->flags) && + !test_and_set_bit(MD_DO_DELETE, &mddev->flags)) { kobject_del(&mddev->kobj); del_gendisk(mddev->gendisk); } diff --git a/drivers/md/md.h b/drivers/md/md.h index 5d5f780b84477c..fd6e001c1d38f1 100644 --- a/drivers/md/md.h +++ b/drivers/md/md.h @@ -354,6 +354,7 @@ enum mddev_flags { MD_HAS_MULTIPLE_PPLS, MD_NOT_READY, MD_BROKEN, + MD_DO_DELETE, MD_DELETED, }; From 742012f6bf29553fdc460bf646a58df3a7b43d01 Mon Sep 17 00:00:00 2001 From: Zheng Qixing Date: Sat, 8 Nov 2025 15:02:02 +0800 Subject: [PATCH 0594/3902] nbd: defer config put in recv_work [ Upstream commit 9517b82d8d422d426a988b213fdd45c6b417b86d ] There is one uaf issue in recv_work when running NBD_CLEAR_SOCK and NBD_CMD_RECONFIGURE: nbd_genl_connect // conf_ref=2 (connect and recv_work A) nbd_open // conf_ref=3 recv_work A done // conf_ref=2 NBD_CLEAR_SOCK // conf_ref=1 nbd_genl_reconfigure // conf_ref=2 (trigger recv_work B) close nbd // conf_ref=1 recv_work B config_put // conf_ref=0 atomic_dec(&config->recv_threads); -> UAF Or only running NBD_CLEAR_SOCK: nbd_genl_connect // conf_ref=2 nbd_open // conf_ref=3 NBD_CLEAR_SOCK // conf_ref=2 close nbd nbd_release config_put // conf_ref=1 recv_work config_put // conf_ref=0 atomic_dec(&config->recv_threads); -> UAF Commit 87aac3a80af5 ("nbd: call nbd_config_put() before notifying the waiter") moved nbd_config_put() to run before waking up the waiter in recv_work, in order to ensure that nbd_start_device_ioctl() would not be woken up while nbd->task_recv was still uncleared. However, in nbd_start_device_ioctl(), after being woken up it explicitly calls flush_workqueue() to make sure all current works are finished. Therefore, there is no need to move the config put ahead of the wakeup. Move nbd_config_put() to the end of recv_work, so that the reference is held for the whole lifetime of the worker thread. This makes sure the config cannot be freed while recv_work is still running, even if clear + reconfigure interleave. In addition, we don't need to worry about recv_work dropping the last nbd_put (which causes deadlock): path A (netlink with NBD_CFLAG_DESTROY_ON_DISCONNECT): connect // nbd_refs=1 (trigger recv_work) open nbd // nbd_refs=2 NBD_CLEAR_SOCK close nbd nbd_release nbd_disconnect_and_put flush_workqueue // recv_work done nbd_config_put nbd_put // nbd_refs=1 nbd_put // nbd_refs=0 queue_work path B (netlink without NBD_CFLAG_DESTROY_ON_DISCONNECT): connect // nbd_refs=2 (trigger recv_work) open nbd // nbd_refs=3 NBD_CLEAR_SOCK // conf_refs=2 close nbd nbd_release nbd_config_put // conf_refs=1 nbd_put // nbd_refs=2 recv_work done // conf_refs=0, nbd_refs=1 rmmod // nbd_refs=0 Reported-by: syzbot+56fbf4c7ddf65e95c7cc@syzkaller.appspotmail.com Closes: https://lore.kernel.org/all/6907edce.a70a0220.37351b.0014.GAE@google.com/T/ Fixes: 87aac3a80af5 ("nbd: make the config put is called before the notifying the waiter") Depends-on: e2daec488c57 ("nbd: Fix hungtask when nbd_config_put") Signed-off-by: Zheng Qixing Signed-off-by: Jens Axboe Signed-off-by: Sasha Levin --- drivers/block/nbd.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/block/nbd.c b/drivers/block/nbd.c index a853c65ac65dfe..215fc18115b78c 100644 --- a/drivers/block/nbd.c +++ b/drivers/block/nbd.c @@ -1024,9 +1024,9 @@ static void recv_work(struct work_struct *work) nbd_mark_nsock_dead(nbd, nsock, 1); mutex_unlock(&nsock->tx_lock); - nbd_config_put(nbd); atomic_dec(&config->recv_threads); wake_up(&config->recv_wq); + nbd_config_put(nbd); kfree(args); } From f7c6c26a99779e4657ea83361b4042435de96d39 Mon Sep 17 00:00:00 2001 From: Haotian Zhang Date: Tue, 4 Nov 2025 17:48:47 +0800 Subject: [PATCH 0595/3902] scsi: stex: Fix reboot_notifier leak in probe error path [ Upstream commit 20da637eb545b04753e20c675cfe97b04c7b600b ] In stex_probe(), register_reboot_notifier() is called at the beginning, but if any subsequent initialization step fails, the function returns without unregistering the notifier, resulting in a resource leak. Add unregister_reboot_notifier() in the out_disable error path to ensure proper cleanup on all failure paths. Fixes: 61b745fa63db ("scsi: stex: Add S6 support") Signed-off-by: Haotian Zhang Link: https://patch.msgid.link/20251104094847.270-1-vulab@iscas.ac.cn Signed-off-by: Martin K. Petersen Signed-off-by: Sasha Levin --- drivers/scsi/stex.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/scsi/stex.c b/drivers/scsi/stex.c index d8ad02c2932058..e02f28e5a104e4 100644 --- a/drivers/scsi/stex.c +++ b/drivers/scsi/stex.c @@ -1844,6 +1844,7 @@ static int stex_probe(struct pci_dev *pdev, const struct pci_device_id *id) out_scsi_host_put: scsi_host_put(host); out_disable: + unregister_reboot_notifier(&stex_notifier); pci_disable_device(pdev); return err; From 1a5c5a2f88e839af5320216a02ffb075b668596a Mon Sep 17 00:00:00 2001 From: Mike McGowen Date: Thu, 6 Nov 2025 10:38:20 -0600 Subject: [PATCH 0596/3902] scsi: smartpqi: Fix device resources accessed after device removal [ Upstream commit b518e86d1a70a88f6592a7c396cf1b93493d1aab ] Correct possible race conditions during device removal. Previously, a scheduled work item to reset a LUN could still execute after the device was removed, leading to use-after-free and other resource access issues. This race condition occurs because the abort handler may schedule a LUN reset concurrently with device removal via sdev_destroy(), leading to use-after-free and improper access to freed resources. - Check in the device reset handler if the device is still present in the controller's SCSI device list before running; if not, the reset is skipped. - Cancel any pending TMF work that has not started in sdev_destroy(). - Ensure device freeing in sdev_destroy() is done while holding the LUN reset mutex to avoid races with ongoing resets. Fixes: 2d80f4054f7f ("scsi: smartpqi: Update deleting a LUN via sysfs") Reviewed-by: Scott Teel Reviewed-by: Scott Benesh Signed-off-by: Mike McGowen Signed-off-by: Don Brace Link: https://patch.msgid.link/20251106163823.786828-3-don.brace@microchip.com Signed-off-by: Martin K. Petersen Signed-off-by: Sasha Levin --- drivers/scsi/smartpqi/smartpqi_init.c | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/drivers/scsi/smartpqi/smartpqi_init.c b/drivers/scsi/smartpqi/smartpqi_init.c index 03c97e60d36f63..fdc856845a05d9 100644 --- a/drivers/scsi/smartpqi/smartpqi_init.c +++ b/drivers/scsi/smartpqi/smartpqi_init.c @@ -6410,10 +6410,22 @@ static int pqi_device_reset(struct pqi_ctrl_info *ctrl_info, struct pqi_scsi_dev static int pqi_device_reset_handler(struct pqi_ctrl_info *ctrl_info, struct pqi_scsi_dev *device, u8 lun, struct scsi_cmnd *scmd, u8 scsi_opcode) { + unsigned long flags; int rc; mutex_lock(&ctrl_info->lun_reset_mutex); + spin_lock_irqsave(&ctrl_info->scsi_device_list_lock, flags); + if (pqi_find_scsi_dev(ctrl_info, device->bus, device->target, device->lun) == NULL) { + dev_warn(&ctrl_info->pci_dev->dev, + "skipping reset of scsi %d:%d:%d:%u, device has been removed\n", + ctrl_info->scsi_host->host_no, device->bus, device->target, device->lun); + spin_unlock_irqrestore(&ctrl_info->scsi_device_list_lock, flags); + mutex_unlock(&ctrl_info->lun_reset_mutex); + return 0; + } + spin_unlock_irqrestore(&ctrl_info->scsi_device_list_lock, flags); + dev_err(&ctrl_info->pci_dev->dev, "resetting scsi %d:%d:%d:%u SCSI cmd at %p due to cmd opcode 0x%02x\n", ctrl_info->scsi_host->host_no, device->bus, device->target, lun, scmd, scsi_opcode); @@ -6594,7 +6606,9 @@ static void pqi_sdev_destroy(struct scsi_device *sdev) { struct pqi_ctrl_info *ctrl_info; struct pqi_scsi_dev *device; + struct pqi_tmf_work *tmf_work; int mutex_acquired; + unsigned int lun; unsigned long flags; ctrl_info = shost_to_hba(sdev->host); @@ -6621,8 +6635,13 @@ static void pqi_sdev_destroy(struct scsi_device *sdev) mutex_unlock(&ctrl_info->scan_mutex); + for (lun = 0, tmf_work = device->tmf_work; lun < PQI_MAX_LUNS_PER_DEVICE; lun++, tmf_work++) + cancel_work_sync(&tmf_work->work_struct); + + mutex_lock(&ctrl_info->lun_reset_mutex); pqi_dev_info(ctrl_info, "removed", device); pqi_free_device(device); + mutex_unlock(&ctrl_info->lun_reset_mutex); } static int pqi_getpciinfo_ioctl(struct pqi_ctrl_info *ctrl_info, void __user *arg) From e463548fd80e779efea1cb2d3049b8a7231e6925 Mon Sep 17 00:00:00 2001 From: Johan Hovold Date: Wed, 29 Oct 2025 10:34:42 +0100 Subject: [PATCH 0597/3902] staging: most: remove broken i2c driver [ Upstream commit 495df2da6944477d282d5cc0c13174d06e25b310 ] The MOST I2C driver has been completely broken for five years without anyone noticing so remove the driver from staging. Specifically, commit 723de0f9171e ("staging: most: remove device from interface structure") started requiring drivers to set the interface device pointer before registration, but the I2C driver was never updated which results in a NULL pointer dereference if anyone ever tries to probe it. Fixes: 723de0f9171e ("staging: most: remove device from interface structure") Cc: Christian Gromm Signed-off-by: Johan Hovold Link: https://patch.msgid.link/20251029093442.29256-1-johan@kernel.org Signed-off-by: Greg Kroah-Hartman Signed-off-by: Sasha Levin --- drivers/staging/most/Kconfig | 2 - drivers/staging/most/Makefile | 1 - drivers/staging/most/i2c/Kconfig | 13 -- drivers/staging/most/i2c/Makefile | 4 - drivers/staging/most/i2c/i2c.c | 374 ------------------------------ 5 files changed, 394 deletions(-) delete mode 100644 drivers/staging/most/i2c/Kconfig delete mode 100644 drivers/staging/most/i2c/Makefile delete mode 100644 drivers/staging/most/i2c/i2c.c diff --git a/drivers/staging/most/Kconfig b/drivers/staging/most/Kconfig index 6f420cbcdcfff6..e89658df6f124c 100644 --- a/drivers/staging/most/Kconfig +++ b/drivers/staging/most/Kconfig @@ -24,6 +24,4 @@ source "drivers/staging/most/video/Kconfig" source "drivers/staging/most/dim2/Kconfig" -source "drivers/staging/most/i2c/Kconfig" - endif diff --git a/drivers/staging/most/Makefile b/drivers/staging/most/Makefile index 8b3fc5a7af5148..e45084df7803a7 100644 --- a/drivers/staging/most/Makefile +++ b/drivers/staging/most/Makefile @@ -3,4 +3,3 @@ obj-$(CONFIG_MOST_NET) += net/ obj-$(CONFIG_MOST_VIDEO) += video/ obj-$(CONFIG_MOST_DIM2) += dim2/ -obj-$(CONFIG_MOST_I2C) += i2c/ diff --git a/drivers/staging/most/i2c/Kconfig b/drivers/staging/most/i2c/Kconfig deleted file mode 100644 index ff64283cbad18b..00000000000000 --- a/drivers/staging/most/i2c/Kconfig +++ /dev/null @@ -1,13 +0,0 @@ -# SPDX-License-Identifier: GPL-2.0 -# -# MOST I2C configuration -# - -config MOST_I2C - tristate "I2C" - depends on I2C - help - Say Y here if you want to connect via I2C to network transceiver. - - To compile this driver as a module, choose M here: the - module will be called most_i2c. diff --git a/drivers/staging/most/i2c/Makefile b/drivers/staging/most/i2c/Makefile deleted file mode 100644 index 71099dd0f85b9c..00000000000000 --- a/drivers/staging/most/i2c/Makefile +++ /dev/null @@ -1,4 +0,0 @@ -# SPDX-License-Identifier: GPL-2.0 -obj-$(CONFIG_MOST_I2C) += most_i2c.o - -most_i2c-objs := i2c.o diff --git a/drivers/staging/most/i2c/i2c.c b/drivers/staging/most/i2c/i2c.c deleted file mode 100644 index 184b2dd11fc34a..00000000000000 --- a/drivers/staging/most/i2c/i2c.c +++ /dev/null @@ -1,374 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0 -/* - * i2c.c - Hardware Dependent Module for I2C Interface - * - * Copyright (C) 2013-2015, Microchip Technology Germany II GmbH & Co. KG - */ - -#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt - -#include -#include -#include -#include -#include -#include -#include - -enum { CH_RX, CH_TX, NUM_CHANNELS }; - -#define MAX_BUFFERS_CONTROL 32 -#define MAX_BUF_SIZE_CONTROL 256 - -/** - * list_first_mbo - get the first mbo from a list - * @ptr: the list head to take the mbo from. - */ -#define list_first_mbo(ptr) \ - list_first_entry(ptr, struct mbo, list) - -static unsigned int polling_rate; -module_param(polling_rate, uint, 0644); -MODULE_PARM_DESC(polling_rate, "Polling rate [Hz]. Default = 0 (use IRQ)"); - -struct hdm_i2c { - struct most_interface most_iface; - struct most_channel_capability capabilities[NUM_CHANNELS]; - struct i2c_client *client; - struct rx { - struct delayed_work dwork; - struct list_head list; - bool int_disabled; - unsigned int delay; - } rx; - char name[64]; -}; - -static inline struct hdm_i2c *to_hdm(struct most_interface *iface) -{ - return container_of(iface, struct hdm_i2c, most_iface); -} - -static irqreturn_t most_irq_handler(int, void *); -static void pending_rx_work(struct work_struct *); - -/** - * configure_channel - called from MOST core to configure a channel - * @most_iface: interface the channel belongs to - * @ch_idx: channel to be configured - * @channel_config: structure that holds the configuration information - * - * Return 0 on success, negative on failure. - * - * Receives configuration information from MOST core and initialize the - * corresponding channel. - */ -static int configure_channel(struct most_interface *most_iface, - int ch_idx, - struct most_channel_config *channel_config) -{ - int ret; - struct hdm_i2c *dev = to_hdm(most_iface); - unsigned int delay, pr; - - BUG_ON(ch_idx < 0 || ch_idx >= NUM_CHANNELS); - - if (channel_config->data_type != MOST_CH_CONTROL) { - pr_err("bad data type for channel %d\n", ch_idx); - return -EPERM; - } - - if (channel_config->direction != dev->capabilities[ch_idx].direction) { - pr_err("bad direction for channel %d\n", ch_idx); - return -EPERM; - } - - if (channel_config->direction == MOST_CH_RX) { - if (!polling_rate) { - if (dev->client->irq <= 0) { - pr_err("bad irq: %d\n", dev->client->irq); - return -ENOENT; - } - dev->rx.int_disabled = false; - ret = request_irq(dev->client->irq, most_irq_handler, 0, - dev->client->name, dev); - if (ret) { - pr_err("request_irq(%d) failed: %d\n", - dev->client->irq, ret); - return ret; - } - } else { - delay = msecs_to_jiffies(MSEC_PER_SEC / polling_rate); - dev->rx.delay = delay ? delay : 1; - pr = MSEC_PER_SEC / jiffies_to_msecs(dev->rx.delay); - pr_info("polling rate is %u Hz\n", pr); - } - } - - return 0; -} - -/** - * enqueue - called from MOST core to enqueue a buffer for data transfer - * @most_iface: intended interface - * @ch_idx: ID of the channel the buffer is intended for - * @mbo: pointer to the buffer object - * - * Return 0 on success, negative on failure. - * - * Transmit the data over I2C if it is a "write" request or push the buffer into - * list if it is an "read" request - */ -static int enqueue(struct most_interface *most_iface, - int ch_idx, struct mbo *mbo) -{ - struct hdm_i2c *dev = to_hdm(most_iface); - int ret; - - BUG_ON(ch_idx < 0 || ch_idx >= NUM_CHANNELS); - - if (ch_idx == CH_RX) { - /* RX */ - if (!polling_rate) - disable_irq(dev->client->irq); - cancel_delayed_work_sync(&dev->rx.dwork); - list_add_tail(&mbo->list, &dev->rx.list); - if (dev->rx.int_disabled || polling_rate) - pending_rx_work(&dev->rx.dwork.work); - if (!polling_rate) - enable_irq(dev->client->irq); - } else { - /* TX */ - ret = i2c_master_send(dev->client, mbo->virt_address, - mbo->buffer_length); - if (ret <= 0) { - mbo->processed_length = 0; - mbo->status = MBO_E_INVAL; - } else { - mbo->processed_length = mbo->buffer_length; - mbo->status = MBO_SUCCESS; - } - mbo->complete(mbo); - } - - return 0; -} - -/** - * poison_channel - called from MOST core to poison buffers of a channel - * @most_iface: pointer to the interface the channel to be poisoned belongs to - * @ch_idx: corresponding channel ID - * - * Return 0 on success, negative on failure. - * - * If channel direction is RX, complete the buffers in list with - * status MBO_E_CLOSE - */ -static int poison_channel(struct most_interface *most_iface, - int ch_idx) -{ - struct hdm_i2c *dev = to_hdm(most_iface); - struct mbo *mbo; - - BUG_ON(ch_idx < 0 || ch_idx >= NUM_CHANNELS); - - if (ch_idx == CH_RX) { - if (!polling_rate) - free_irq(dev->client->irq, dev); - cancel_delayed_work_sync(&dev->rx.dwork); - - while (!list_empty(&dev->rx.list)) { - mbo = list_first_mbo(&dev->rx.list); - list_del(&mbo->list); - - mbo->processed_length = 0; - mbo->status = MBO_E_CLOSE; - mbo->complete(mbo); - } - } - - return 0; -} - -static void do_rx_work(struct hdm_i2c *dev) -{ - struct mbo *mbo; - unsigned char msg[MAX_BUF_SIZE_CONTROL]; - int ret; - u16 pml, data_size; - - /* Read PML (2 bytes) */ - ret = i2c_master_recv(dev->client, msg, 2); - if (ret <= 0) { - pr_err("Failed to receive PML\n"); - return; - } - - pml = (msg[0] << 8) | msg[1]; - if (!pml) - return; - - data_size = pml + 2; - - /* Read the whole message, including PML */ - ret = i2c_master_recv(dev->client, msg, data_size); - if (ret <= 0) { - pr_err("Failed to receive a Port Message\n"); - return; - } - - mbo = list_first_mbo(&dev->rx.list); - list_del(&mbo->list); - - mbo->processed_length = min(data_size, mbo->buffer_length); - memcpy(mbo->virt_address, msg, mbo->processed_length); - mbo->status = MBO_SUCCESS; - mbo->complete(mbo); -} - -/** - * pending_rx_work - Read pending messages through I2C - * @work: definition of this work item - * - * Invoked by the Interrupt Service Routine, most_irq_handler() - */ -static void pending_rx_work(struct work_struct *work) -{ - struct hdm_i2c *dev = container_of(work, struct hdm_i2c, rx.dwork.work); - - if (list_empty(&dev->rx.list)) - return; - - do_rx_work(dev); - - if (polling_rate) { - schedule_delayed_work(&dev->rx.dwork, dev->rx.delay); - } else { - dev->rx.int_disabled = false; - enable_irq(dev->client->irq); - } -} - -/* - * most_irq_handler - Interrupt Service Routine - * @irq: irq number - * @_dev: private data - * - * Schedules a delayed work - * - * By default the interrupt line behavior is Active Low. Once an interrupt is - * generated by the device, until driver clears the interrupt (by reading - * the PMP message), device keeps the interrupt line in low state. Since i2c - * read is done in work queue, the interrupt line must be disabled temporarily - * to avoid ISR being called repeatedly. Re-enable the interrupt in workqueue, - * after reading the message. - * - * Note: If we use the interrupt line in Falling edge mode, there is a - * possibility to miss interrupts when ISR is getting executed. - * - */ -static irqreturn_t most_irq_handler(int irq, void *_dev) -{ - struct hdm_i2c *dev = _dev; - - disable_irq_nosync(irq); - dev->rx.int_disabled = true; - schedule_delayed_work(&dev->rx.dwork, 0); - - return IRQ_HANDLED; -} - -/* - * i2c_probe - i2c probe handler - * @client: i2c client device structure - * @id: i2c client device id - * - * Return 0 on success, negative on failure. - * - * Register the i2c client device as a MOST interface - */ -static int i2c_probe(struct i2c_client *client) -{ - struct hdm_i2c *dev; - int ret, i; - - dev = kzalloc(sizeof(*dev), GFP_KERNEL); - if (!dev) - return -ENOMEM; - - /* ID format: i2c--
*/ - snprintf(dev->name, sizeof(dev->name), "i2c-%d-%04x", - client->adapter->nr, client->addr); - - for (i = 0; i < NUM_CHANNELS; i++) { - dev->capabilities[i].data_type = MOST_CH_CONTROL; - dev->capabilities[i].num_buffers_packet = MAX_BUFFERS_CONTROL; - dev->capabilities[i].buffer_size_packet = MAX_BUF_SIZE_CONTROL; - } - dev->capabilities[CH_RX].direction = MOST_CH_RX; - dev->capabilities[CH_RX].name_suffix = "rx"; - dev->capabilities[CH_TX].direction = MOST_CH_TX; - dev->capabilities[CH_TX].name_suffix = "tx"; - - dev->most_iface.interface = ITYPE_I2C; - dev->most_iface.description = dev->name; - dev->most_iface.num_channels = NUM_CHANNELS; - dev->most_iface.channel_vector = dev->capabilities; - dev->most_iface.configure = configure_channel; - dev->most_iface.enqueue = enqueue; - dev->most_iface.poison_channel = poison_channel; - - INIT_LIST_HEAD(&dev->rx.list); - - INIT_DELAYED_WORK(&dev->rx.dwork, pending_rx_work); - - dev->client = client; - i2c_set_clientdata(client, dev); - - ret = most_register_interface(&dev->most_iface); - if (ret) { - pr_err("Failed to register i2c as a MOST interface\n"); - kfree(dev); - return ret; - } - - return 0; -} - -/* - * i2c_remove - i2c remove handler - * @client: i2c client device structure - * - * Return 0 on success. - * - * Unregister the i2c client device as a MOST interface - */ -static void i2c_remove(struct i2c_client *client) -{ - struct hdm_i2c *dev = i2c_get_clientdata(client); - - most_deregister_interface(&dev->most_iface); - kfree(dev); -} - -static const struct i2c_device_id i2c_id[] = { - { "most_i2c" }, - { } /* Terminating entry */ -}; - -MODULE_DEVICE_TABLE(i2c, i2c_id); - -static struct i2c_driver i2c_driver = { - .driver = { - .name = "hdm_i2c", - }, - .probe = i2c_probe, - .remove = i2c_remove, - .id_table = i2c_id, -}; - -module_i2c_driver(i2c_driver); - -MODULE_AUTHOR("Andrey Shvetsov "); -MODULE_DESCRIPTION("I2C Hardware Dependent Module"); -MODULE_LICENSE("GPL"); From 986d67f2d72aa4ec7a31bd922f98346745f6ea49 Mon Sep 17 00:00:00 2001 From: Rodrigo Gobbi Date: Sun, 2 Nov 2025 19:30:18 -0300 Subject: [PATCH 0598/3902] iio: imu: bmi270: fix dev_err_probe error msg [ Upstream commit 02f86101e430cce9a99a044b483c4ed5b91bb3b8 ] The bmi270 can be connected to I2C or a SPI interface. If it is a SPI, during probe, if devm_regmap_init() fails, it should print the "spi" term rather "i2c". Fixes: 92cc50a00574 ("iio: imu: bmi270: Add spi driver for bmi270 imu") Signed-off-by: Rodrigo Gobbi Reviewed-by: Andy Shevchenko Signed-off-by: Jonathan Cameron Signed-off-by: Sasha Levin --- drivers/iio/imu/bmi270/bmi270_spi.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/iio/imu/bmi270/bmi270_spi.c b/drivers/iio/imu/bmi270/bmi270_spi.c index 19dd7734f9d07c..80c9fa1d685abc 100644 --- a/drivers/iio/imu/bmi270/bmi270_spi.c +++ b/drivers/iio/imu/bmi270/bmi270_spi.c @@ -60,7 +60,7 @@ static int bmi270_spi_probe(struct spi_device *spi) &bmi270_spi_regmap_config); if (IS_ERR(regmap)) return dev_err_probe(dev, PTR_ERR(regmap), - "Failed to init i2c regmap"); + "Failed to init spi regmap\n"); return bmi270_core_probe(dev, regmap, chip_info); } From 83f23bf28e1b27f35569ff3456961233248b304c Mon Sep 17 00:00:00 2001 From: Manivannan Sadhasivam Date: Sat, 1 Nov 2025 09:59:40 +0530 Subject: [PATCH 0599/3902] dt-bindings: PCI: amlogic: Fix the register name of the DBI region [ Upstream commit 4813dea9e272ba0a57c50b8d51d440dd8e3ccdd7 ] Binding incorrectly specifies the 'DBI' region as 'ELBI'. DBI is a must have region for DWC controllers as it has the Root Port and controller specific registers, while ELBI has optional registers. Hence, fix the binding. Though this is an ABI break, this change is needed to accurately describe the PCI memory map. Fixes: 7cd210391101 ("dt-bindings: PCI: meson: add DT bindings for Amlogic Meson PCIe controller") Signed-off-by: Manivannan Sadhasivam Signed-off-by: Manivannan Sadhasivam Link: https://patch.msgid.link/20251101-pci-meson-fix-v1-1-c50dcc56ed6a@oss.qualcomm.com Signed-off-by: Sasha Levin --- Documentation/devicetree/bindings/pci/amlogic,axg-pcie.yaml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Documentation/devicetree/bindings/pci/amlogic,axg-pcie.yaml b/Documentation/devicetree/bindings/pci/amlogic,axg-pcie.yaml index 79a21ba0f9fd62..c8258ef4032834 100644 --- a/Documentation/devicetree/bindings/pci/amlogic,axg-pcie.yaml +++ b/Documentation/devicetree/bindings/pci/amlogic,axg-pcie.yaml @@ -36,13 +36,13 @@ properties: reg: items: - - description: External local bus interface registers + - description: Data Bus Interface registers - description: Meson designed configuration registers - description: PCIe configuration space reg-names: items: - - const: elbi + - const: dbi - const: cfg - const: config @@ -113,7 +113,7 @@ examples: pcie: pcie@f9800000 { compatible = "amlogic,axg-pcie", "snps,dw-pcie"; reg = <0xf9800000 0x400000>, <0xff646000 0x2000>, <0xf9f00000 0x100000>; - reg-names = "elbi", "cfg", "config"; + reg-names = "dbi", "cfg", "config"; interrupts = ; clocks = <&pclk>, <&clk_port>, <&clk_phy>; clock-names = "pclk", "port", "general"; From 8ccdf3a6cde161ea349d63ec9e5bb4208e190098 Mon Sep 17 00:00:00 2001 From: Ma Ke Date: Mon, 10 Nov 2025 08:51:58 +0800 Subject: [PATCH 0600/3902] RDMA/rtrs: server: Fix error handling in get_or_create_srv [ Upstream commit a338d6e849ab31f32c08b4fcac11c0c72afbb150 ] After device_initialize() is called, use put_device() to release the device according to kernel device management rules. While direct kfree() work in this case, using put_device() is more correct. Found by code review. Fixes: 9cb837480424 ("RDMA/rtrs: server: main functionality") Signed-off-by: Ma Ke Link: https://patch.msgid.link/20251110005158.13394-1-make24@iscas.ac.cn Acked-by: Jack Wang Signed-off-by: Leon Romanovsky Signed-off-by: Sasha Levin --- drivers/infiniband/ulp/rtrs/rtrs-srv.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/infiniband/ulp/rtrs/rtrs-srv.c b/drivers/infiniband/ulp/rtrs/rtrs-srv.c index ef4abdea3c2d2e..9ecc6343455d6c 100644 --- a/drivers/infiniband/ulp/rtrs/rtrs-srv.c +++ b/drivers/infiniband/ulp/rtrs/rtrs-srv.c @@ -1450,7 +1450,7 @@ static struct rtrs_srv_sess *get_or_create_srv(struct rtrs_srv_ctx *ctx, kfree(srv->chunks); err_free_srv: - kfree(srv); + put_device(&srv->dev); return ERR_PTR(-ENOMEM); } From 131097fe8b7d0ddcf8fa29b02ae032b5e1bd20e5 Mon Sep 17 00:00:00 2001 From: Jihed Chaibi Date: Tue, 16 Sep 2025 00:46:11 +0200 Subject: [PATCH 0601/3902] ARM: dts: stm32: stm32mp157c-phycore: Fix STMPE811 touchscreen node properties [ Upstream commit e40b061cd379f4897e705d17cf1b4572ad0f3963 ] Move st,adc-freq, st,mod-12b, st,ref-sel, and st,sample-time properties from the touchscreen subnode to the parent touch@44 node. These properties are defined in the st,stmpe.yaml schema for the parent node, not the touchscreen subnode, resolving the validation error about unevaluated properties. Fixes: 27538a18a4fcc ("ARM: dts: stm32: add STM32MP1-based Phytec SoM") Signed-off-by: Jihed Chaibi Link: https://lore.kernel.org/r/20250915224611.169980-1-jihed.chaibi.dev@gmail.com Signed-off-by: Alexandre Torgue Signed-off-by: Sasha Levin --- .../boot/dts/st/stm32mp157c-phycore-stm32mp15-som.dtsi | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/arch/arm/boot/dts/st/stm32mp157c-phycore-stm32mp15-som.dtsi b/arch/arm/boot/dts/st/stm32mp157c-phycore-stm32mp15-som.dtsi index bf0c32027baf76..370b2afbf15bf0 100644 --- a/arch/arm/boot/dts/st/stm32mp157c-phycore-stm32mp15-som.dtsi +++ b/arch/arm/boot/dts/st/stm32mp157c-phycore-stm32mp15-som.dtsi @@ -185,13 +185,13 @@ interrupt-parent = <&gpioi>; vio-supply = <&v3v3>; vcc-supply = <&v3v3>; + st,sample-time = <4>; + st,mod-12b = <1>; + st,ref-sel = <0>; + st,adc-freq = <1>; touchscreen { compatible = "st,stmpe-ts"; - st,sample-time = <4>; - st,mod-12b = <1>; - st,ref-sel = <0>; - st,adc-freq = <1>; st,ave-ctrl = <1>; st,touch-det-delay = <2>; st,settling = <2>; From d0c9effd82f2c19b92acd07d357fac5f392d549a Mon Sep 17 00:00:00 2001 From: Carl Worth Date: Thu, 25 Sep 2025 18:42:31 +0800 Subject: [PATCH 0602/3902] coresight: tmc: add the handle of the event to the path [ Upstream commit aaa5abcc9d44d2c8484f779ab46d242d774cabcb ] The handle is essential for retrieving the AUX_EVENT of each CPU and is required in perf mode. It has been added to the coresight_path so that dependent devices can access it from the path when needed. The existing bug can be reproduced with: perf record -e cs_etm//k -C 0-9 dd if=/dev/zero of=/dev/null Showing an oops as follows: Unable to handle kernel paging request at virtual address 000f6e84934ed19e Call trace: tmc_etr_get_buffer+0x30/0x80 [coresight_tmc] (P) catu_enable_hw+0xbc/0x3d0 [coresight_catu] catu_enable+0x70/0xe0 [coresight_catu] coresight_enable_path+0xb0/0x258 [coresight] Fixes: 080ee83cc361 ("Coresight: Change functions to accept the coresight_path") Signed-off-by: Carl Worth Reviewed-by: Leo Yan Co-developed-by: Jie Gan Signed-off-by: Jie Gan Signed-off-by: Suzuki K Poulose Link: https://lore.kernel.org/r/20250925-fix_helper_data-v2-1-edd8a07c1646@oss.qualcomm.com Signed-off-by: Sasha Levin --- drivers/hwtracing/coresight/coresight-etm-perf.c | 1 + drivers/hwtracing/coresight/coresight-tmc-etr.c | 3 ++- include/linux/coresight.h | 10 ++++++---- 3 files changed, 9 insertions(+), 5 deletions(-) diff --git a/drivers/hwtracing/coresight/coresight-etm-perf.c b/drivers/hwtracing/coresight/coresight-etm-perf.c index f677c08233ba1a..5c256af6e54af0 100644 --- a/drivers/hwtracing/coresight/coresight-etm-perf.c +++ b/drivers/hwtracing/coresight/coresight-etm-perf.c @@ -520,6 +520,7 @@ static void etm_event_start(struct perf_event *event, int flags) goto out; path = etm_event_cpu_path(event_data, cpu); + path->handle = handle; /* We need a sink, no need to continue without one */ sink = coresight_get_sink(path); if (WARN_ON_ONCE(!sink)) diff --git a/drivers/hwtracing/coresight/coresight-tmc-etr.c b/drivers/hwtracing/coresight/coresight-tmc-etr.c index 800be06598c1b7..60b0e0a6da0575 100644 --- a/drivers/hwtracing/coresight/coresight-tmc-etr.c +++ b/drivers/hwtracing/coresight/coresight-tmc-etr.c @@ -1334,7 +1334,8 @@ static int tmc_enable_etr_sink_sysfs(struct coresight_device *csdev) struct etr_buf *tmc_etr_get_buffer(struct coresight_device *csdev, enum cs_mode mode, void *data) { - struct perf_output_handle *handle = data; + struct coresight_path *path = data; + struct perf_output_handle *handle = path->handle; struct etr_perf_buffer *etr_perf; switch (mode) { diff --git a/include/linux/coresight.h b/include/linux/coresight.h index 6de59ce8ef8ca4..2626105e371919 100644 --- a/include/linux/coresight.h +++ b/include/linux/coresight.h @@ -332,12 +332,14 @@ static struct coresight_dev_list (var) = { \ /** * struct coresight_path - data needed by enable/disable path - * @path_list: path from source to sink. - * @trace_id: trace_id of the whole path. + * @path_list: path from source to sink. + * @trace_id: trace_id of the whole path. + * @handle: handle of the aux_event. */ struct coresight_path { - struct list_head path_list; - u8 trace_id; + struct list_head path_list; + u8 trace_id; + struct perf_output_handle *handle; }; enum cs_mode { From ab5e8ebeee1caa4fcf8be7d8d62c0a7165469076 Mon Sep 17 00:00:00 2001 From: Edward Adam Davis Date: Tue, 16 Sep 2025 13:50:13 +0800 Subject: [PATCH 0603/3902] ntfs3: init run lock for extend inode [ Upstream commit be99c62ac7e7af514e4b13f83c891a3cccefaa48 ] After setting the inode mode of $Extend to a regular file, executing the truncate system call will enter the do_truncate() routine, causing the run_lock uninitialized error reported by syzbot. Prior to patch 4e8011ffec79, if the inode mode of $Extend was not set to a regular file, the do_truncate() routine would not be entered. Add the run_lock initialization when loading $Extend. syzbot reported: INFO: trying to register non-static key. Call Trace: dump_stack_lvl+0x189/0x250 lib/dump_stack.c:120 assign_lock_key+0x133/0x150 kernel/locking/lockdep.c:984 register_lock_class+0x105/0x320 kernel/locking/lockdep.c:1299 __lock_acquire+0x99/0xd20 kernel/locking/lockdep.c:5112 lock_acquire+0x120/0x360 kernel/locking/lockdep.c:5868 down_write+0x96/0x1f0 kernel/locking/rwsem.c:1590 ntfs_set_size+0x140/0x200 fs/ntfs3/inode.c:860 ntfs_extend+0x1d9/0x970 fs/ntfs3/file.c:387 ntfs_setattr+0x2e8/0xbe0 fs/ntfs3/file.c:808 Fixes: 4e8011ffec79 ("ntfs3: pretend $Extend records as regular files") Reported-by: syzbot+bdeb22a4b9a09ab9aa45@syzkaller.appspotmail.com Closes: https://syzkaller.appspot.com/bug?extid=bdeb22a4b9a09ab9aa45 Tested-by: syzbot+bdeb22a4b9a09ab9aa45@syzkaller.appspotmail.com Signed-off-by: Edward Adam Davis Signed-off-by: Konstantin Komarov Signed-off-by: Sasha Levin --- fs/ntfs3/inode.c | 1 + 1 file changed, 1 insertion(+) diff --git a/fs/ntfs3/inode.c b/fs/ntfs3/inode.c index 3a0676871badec..05004f8880ce6a 100644 --- a/fs/ntfs3/inode.c +++ b/fs/ntfs3/inode.c @@ -472,6 +472,7 @@ static struct inode *ntfs_read_mft(struct inode *inode, /* Records in $Extend are not a files or general directories. */ inode->i_op = &ntfs_file_inode_operations; mode = S_IFREG; + init_rwsem(&ni->file.run_lock); } else { err = -EINVAL; goto out; From ee8b0b284ccff4035f74367db721922f7a053ad8 Mon Sep 17 00:00:00 2001 From: Akash Goel Date: Tue, 21 Oct 2025 09:10:42 +0100 Subject: [PATCH 0604/3902] drm/panthor: Fix potential memleak of vma structure [ Upstream commit 4492d54d59872bb72e119ff9f77969ab4d8a0e6b ] This commit addresses a memleak issue of panthor_vma (or drm_gpuva) structure in Panthor driver, that can happen if the GPU page table update operation to map the pages fail. The issue is very unlikely to occur in practice. v2: Add panthor_vm_op_ctx_return_vma() helper (Boris) v3: Add WARN_ON_ONCE (Boris) Fixes: 647810ec2476 ("drm/panthor: Add the MMU/VM logical block") Signed-off-by: Akash Goel Reviewed-by: Boris Brezillon Reviewed-by: Steven Price Signed-off-by: Steven Price Link: https://patch.msgid.link/20251021081042.1377406-1-akash.goel@arm.com Signed-off-by: Sasha Levin --- drivers/gpu/drm/panthor/panthor_mmu.c | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/drivers/gpu/drm/panthor/panthor_mmu.c b/drivers/gpu/drm/panthor/panthor_mmu.c index 7870e7dbaa5d45..15961629872e1d 100644 --- a/drivers/gpu/drm/panthor/panthor_mmu.c +++ b/drivers/gpu/drm/panthor/panthor_mmu.c @@ -1146,6 +1146,20 @@ static void panthor_vm_cleanup_op_ctx(struct panthor_vm_op_ctx *op_ctx, } } +static void +panthor_vm_op_ctx_return_vma(struct panthor_vm_op_ctx *op_ctx, + struct panthor_vma *vma) +{ + for (u32 i = 0; i < ARRAY_SIZE(op_ctx->preallocated_vmas); i++) { + if (!op_ctx->preallocated_vmas[i]) { + op_ctx->preallocated_vmas[i] = vma; + return; + } + } + + WARN_ON_ONCE(1); +} + static struct panthor_vma * panthor_vm_op_ctx_get_vma(struct panthor_vm_op_ctx *op_ctx) { @@ -2085,8 +2099,10 @@ static int panthor_gpuva_sm_step_map(struct drm_gpuva_op *op, void *priv) ret = panthor_vm_map_pages(vm, op->map.va.addr, flags_to_prot(vma->flags), op_ctx->map.sgt, op->map.gem.offset, op->map.va.range); - if (ret) + if (ret) { + panthor_vm_op_ctx_return_vma(op_ctx, vma); return ret; + } /* Ref owned by the mapping now, clear the obj field so we don't release the * pinning/obj ref behind GPUVA's back. From cd2898f0fd8efa34874affdd87f99b06e9bff03e Mon Sep 17 00:00:00 2001 From: Bean Huo Date: Sat, 8 Nov 2025 00:05:17 +0100 Subject: [PATCH 0605/3902] scsi: ufs: core: fix incorrect buffer duplication in ufshcd_read_string_desc() [ Upstream commit d794b499f948801f54d67ddbc34a6eac5a6d150a ] The function ufshcd_read_string_desc() was duplicating memory starting from the beginning of struct uc_string_id, which included the length and type fields. As a result, the allocated buffer contained unwanted metadata in addition to the string itself. The correct behavior is to duplicate only the Unicode character array in the structure. Update the code so that only the actual string content is copied into the new buffer. Fixes: 5f57704dbcfe ("scsi: ufs: Use kmemdup in ufshcd_read_string_desc()") Reviewed-by: Avri Altman Reviewed-by: Bart Van Assche Signed-off-by: Bean Huo Link: https://patch.msgid.link/20251107230518.4060231-3-beanhuo@iokpp.de Signed-off-by: Martin K. Petersen Signed-off-by: Sasha Levin --- drivers/ufs/core/ufshcd.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/ufs/core/ufshcd.c b/drivers/ufs/core/ufshcd.c index d6a060a7246186..12f5a7a9731280 100644 --- a/drivers/ufs/core/ufshcd.c +++ b/drivers/ufs/core/ufshcd.c @@ -3837,7 +3837,7 @@ int ufshcd_read_string_desc(struct ufs_hba *hba, u8 desc_index, str[ret++] = '\0'; } else { - str = kmemdup(uc_str, uc_str->len, GFP_KERNEL); + str = kmemdup(uc_str->uc, uc_str->len, GFP_KERNEL); if (!str) { ret = -ENOMEM; goto out; From a66e5f556bd7aac4fb8c2bd3f0feb8b21215e580 Mon Sep 17 00:00:00 2001 From: Li Nan Date: Mon, 3 Nov 2025 20:57:53 +0800 Subject: [PATCH 0606/3902] md: delete md_redundancy_group when array is becoming inactive [ Upstream commit 0ce112d9171ad766d4c6716951e73f91a0bfc184 ] 'md_redundancy_group' are created in md_run() and deleted in del_gendisk(), but these are not paired. Writing inactive/active to sysfs array_state can trigger md_run() multiple times without del_gendisk(), leading to duplicate creation as below: sysfs: cannot create duplicate filename '/devices/virtual/block/md0/md/sync_action' Call Trace: dump_stack_lvl+0x9f/0x120 dump_stack+0x14/0x20 sysfs_warn_dup+0x96/0xc0 sysfs_add_file_mode_ns+0x19c/0x1b0 internal_create_group+0x213/0x830 sysfs_create_group+0x17/0x20 md_run+0x856/0xe60 ? __x64_sys_openat+0x23/0x30 do_md_run+0x26/0x1d0 array_state_store+0x559/0x760 md_attr_store+0xc9/0x1e0 sysfs_kf_write+0x6f/0xa0 kernfs_fop_write_iter+0x141/0x2a0 vfs_write+0x1fc/0x5a0 ksys_write+0x79/0x180 __x64_sys_write+0x1d/0x30 x64_sys_call+0x2818/0x2880 do_syscall_64+0xa9/0x580 entry_SYSCALL_64_after_hwframe+0x4b/0x53 md: cannot register extra attributes for md0 Creation of it depends on 'pers', its lifecycle cannot be aligned with gendisk. So fix this issue by triggering 'md_redundancy_group' deletion when the array is becoming inactive. Link: https://lore.kernel.org/linux-raid/20251103125757.1405796-2-linan666@huaweicloud.com Fixes: 790abe4d77af ("md: remove/add redundancy group only in level change") Signed-off-by: Li Nan Reviewed-by: Xiao Ni Signed-off-by: Yu Kuai Signed-off-by: Sasha Levin --- drivers/md/md.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/drivers/md/md.c b/drivers/md/md.c index 5d40beaecc9c7c..7b21eb1dbc533d 100644 --- a/drivers/md/md.c +++ b/drivers/md/md.c @@ -6872,6 +6872,10 @@ static int do_md_stop(struct mddev *mddev, int mode) if (!md_is_rdwr(mddev)) set_disk_ro(disk, 0); + if (mode == 2 && mddev->pers->sync_request && + mddev->to_remove == NULL) + mddev->to_remove = &md_redundancy_group; + __md_stop_writes(mddev); __md_stop(mddev); From 9d37fe37dfa0833a8768740f0575e0ffd793cb4a Mon Sep 17 00:00:00 2001 From: Li Nan Date: Mon, 3 Nov 2025 20:57:54 +0800 Subject: [PATCH 0607/3902] md: init bioset in mddev_init [ Upstream commit 381a3ce1c0ffed647c9b913e142b099c7e9d5afc ] IO operations may be needed before md_run(), such as updating metadata after writing sysfs. Without bioset, this triggers a NULL pointer dereference as below: BUG: kernel NULL pointer dereference, address: 0000000000000020 Call Trace: md_update_sb+0x658/0xe00 new_level_store+0xc5/0x120 md_attr_store+0xc9/0x1e0 sysfs_kf_write+0x6f/0xa0 kernfs_fop_write_iter+0x141/0x2a0 vfs_write+0x1fc/0x5a0 ksys_write+0x79/0x180 __x64_sys_write+0x1d/0x30 x64_sys_call+0x2818/0x2880 do_syscall_64+0xa9/0x580 entry_SYSCALL_64_after_hwframe+0x4b/0x53 Reproducer ``` mdadm -CR /dev/md0 -l1 -n2 /dev/sd[cd] echo inactive > /sys/block/md0/md/array_state echo 10 > /sys/block/md0/md/new_level ``` mddev_init() can only be called once per mddev, no need to test if bioset has been initialized anymore. Link: https://lore.kernel.org/linux-raid/20251103125757.1405796-3-linan666@huaweicloud.com Fixes: d981ed841930 ("md: Add new_level sysfs interface") Signed-off-by: Li Nan Reviewed-by: Xiao Ni Signed-off-by: Yu Kuai Signed-off-by: Sasha Levin --- drivers/md/md.c | 69 +++++++++++++++++++++++-------------------------- 1 file changed, 33 insertions(+), 36 deletions(-) diff --git a/drivers/md/md.c b/drivers/md/md.c index 7b21eb1dbc533d..cef5b2954ac5aa 100644 --- a/drivers/md/md.c +++ b/drivers/md/md.c @@ -730,6 +730,8 @@ static void mddev_clear_bitmap_ops(struct mddev *mddev) int mddev_init(struct mddev *mddev) { + int err = 0; + if (!IS_ENABLED(CONFIG_MD_BITMAP)) mddev->bitmap_id = ID_BITMAP_NONE; else @@ -741,10 +743,23 @@ int mddev_init(struct mddev *mddev) if (percpu_ref_init(&mddev->writes_pending, no_op, PERCPU_REF_ALLOW_REINIT, GFP_KERNEL)) { - percpu_ref_exit(&mddev->active_io); - return -ENOMEM; + err = -ENOMEM; + goto exit_acitve_io; } + err = bioset_init(&mddev->bio_set, BIO_POOL_SIZE, 0, BIOSET_NEED_BVECS); + if (err) + goto exit_writes_pending; + + err = bioset_init(&mddev->sync_set, BIO_POOL_SIZE, 0, BIOSET_NEED_BVECS); + if (err) + goto exit_bio_set; + + err = bioset_init(&mddev->io_clone_set, BIO_POOL_SIZE, + offsetof(struct md_io_clone, bio_clone), 0); + if (err) + goto exit_sync_set; + /* We want to start with the refcount at zero */ percpu_ref_put(&mddev->writes_pending); @@ -773,11 +788,24 @@ int mddev_init(struct mddev *mddev) INIT_WORK(&mddev->del_work, mddev_delayed_delete); return 0; + +exit_sync_set: + bioset_exit(&mddev->sync_set); +exit_bio_set: + bioset_exit(&mddev->bio_set); +exit_writes_pending: + percpu_ref_exit(&mddev->writes_pending); +exit_acitve_io: + percpu_ref_exit(&mddev->active_io); + return err; } EXPORT_SYMBOL_GPL(mddev_init); void mddev_destroy(struct mddev *mddev) { + bioset_exit(&mddev->bio_set); + bioset_exit(&mddev->sync_set); + bioset_exit(&mddev->io_clone_set); percpu_ref_exit(&mddev->active_io); percpu_ref_exit(&mddev->writes_pending); } @@ -6387,29 +6415,9 @@ int md_run(struct mddev *mddev) nowait = nowait && bdev_nowait(rdev->bdev); } - if (!bioset_initialized(&mddev->bio_set)) { - err = bioset_init(&mddev->bio_set, BIO_POOL_SIZE, 0, BIOSET_NEED_BVECS); - if (err) - return err; - } - if (!bioset_initialized(&mddev->sync_set)) { - err = bioset_init(&mddev->sync_set, BIO_POOL_SIZE, 0, BIOSET_NEED_BVECS); - if (err) - goto exit_bio_set; - } - - if (!bioset_initialized(&mddev->io_clone_set)) { - err = bioset_init(&mddev->io_clone_set, BIO_POOL_SIZE, - offsetof(struct md_io_clone, bio_clone), 0); - if (err) - goto exit_sync_set; - } - pers = get_pers(mddev->level, mddev->clevel); - if (!pers) { - err = -EINVAL; - goto abort; - } + if (!pers) + return -EINVAL; if (mddev->level != pers->head.id) { mddev->level = pers->head.id; mddev->new_level = pers->head.id; @@ -6420,8 +6428,7 @@ int md_run(struct mddev *mddev) pers->start_reshape == NULL) { /* This personality cannot handle reshaping... */ put_pers(pers); - err = -EINVAL; - goto abort; + return -EINVAL; } if (pers->sync_request) { @@ -6548,12 +6555,6 @@ int md_run(struct mddev *mddev) mddev->private = NULL; put_pers(pers); md_bitmap_destroy(mddev); -abort: - bioset_exit(&mddev->io_clone_set); -exit_sync_set: - bioset_exit(&mddev->sync_set); -exit_bio_set: - bioset_exit(&mddev->bio_set); return err; } EXPORT_SYMBOL_GPL(md_run); @@ -6778,10 +6779,6 @@ static void __md_stop(struct mddev *mddev) mddev->private = NULL; put_pers(pers); clear_bit(MD_RECOVERY_FROZEN, &mddev->recovery); - - bioset_exit(&mddev->bio_set); - bioset_exit(&mddev->sync_set); - bioset_exit(&mddev->io_clone_set); } void md_stop(struct mddev *mddev) From 990016bd5d02ad465706b1446acb2692e8e6c762 Mon Sep 17 00:00:00 2001 From: "Gautham R. Shenoy" Date: Fri, 7 Nov 2025 13:11:45 +0530 Subject: [PATCH 0608/3902] cpufreq/amd-pstate: Call cppc_set_auto_sel() only for online CPUs [ Upstream commit bb31fef0d03ed17d587b40e3458786be408fb9df ] amd_pstate_change_mode_without_dvr_change() calls cppc_set_auto_sel() for all the present CPUs. However, this callpath eventually calls cppc_set_reg_val() which accesses the per-cpu cpc_desc_ptr object. This object is initialized only for online CPUs via acpi_soft_cpu_online() --> __acpi_processor_start() --> acpi_cppc_processor_probe(). Hence, restrict calling cppc_set_auto_sel() to only the online CPUs. Fixes: 3ca7bc818d8c ("cpufreq: amd-pstate: Add guided mode control support via sysfs") Suggested-by: Mario Limonciello (AMD) (kernel.org) Signed-off-by: Gautham R. Shenoy Signed-off-by: Mario Limonciello (AMD) Signed-off-by: Sasha Levin --- drivers/cpufreq/amd-pstate.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/cpufreq/amd-pstate.c b/drivers/cpufreq/amd-pstate.c index b44f0f7a5ba1c7..602e4fa81d6c5e 100644 --- a/drivers/cpufreq/amd-pstate.c +++ b/drivers/cpufreq/amd-pstate.c @@ -1282,7 +1282,7 @@ static int amd_pstate_change_mode_without_dvr_change(int mode) if (cpu_feature_enabled(X86_FEATURE_CPPC) || cppc_state == AMD_PSTATE_ACTIVE) return 0; - for_each_present_cpu(cpu) { + for_each_online_cpu(cpu) { cppc_set_auto_sel(cpu, (cppc_state == AMD_PSTATE_PASSIVE) ? 0 : 1); } From f834bd9fb83ee396944a4458c9f8d1ea3ed8a027 Mon Sep 17 00:00:00 2001 From: Sourabh Jain Date: Wed, 5 Nov 2025 09:09:41 +0530 Subject: [PATCH 0609/3902] powerpc/kdump: Fix size calculation for hot-removed memory ranges [ Upstream commit 7afe2383eff05f76f4ce2cfda658b7889c89f101 ] The elfcorehdr segment in the kdump image stores information about the memory regions (called crash memory ranges) that the kdump kernel must capture. When a memory hot-remove event occurs, the kernel regenerates the elfcorehdr for the currently loaded kdump image to remove the hot-removed memory from the crash memory ranges. Call chain: remove_mem_range() update_crash_elfcorehdr() arch_crash_handle_hotplug_event() crash_handle_hotplug_event() While removing the hot-removed memory from the crash memory ranges in remove_mem_range(), if the removed memory lies within an existing crash range, that range is split into two. During this split, the size of the second range was being calculated incorrectly. This leads to dump capture failure with makedumpfile with below error: $ makedumpfile -l -d 31 /proc/vmcore /tmp/vmcore readpage_elf: Attempt to read non-existent page at 0xbbdab0000. readmem: type_addr: 0, addr:c000000bbdab7f00, size:16 validate_mem_section: Can't read mem_section array. readpage_elf: Attempt to read non-existent page at 0xbbdab0000. readmem: type_addr: 0, addr:c000000bbdab7f00, size:8 get_mm_sparsemem: Can't get the address of mem_section. The updated crash memory range in PT_LOAD entry is holding incorrect data (checkout FileSiz and MemSiz): readelf -a /proc/vmcore Type Offset VirtAddr PhysAddr FileSiz MemSiz Flags Align LOAD 0x0000000b013d0000 0xc000000b80000000 0x0000000b80000000 0xffffffffc0000000 0xffffffffc0000000 RWE 0x0 Update the size calculation for the new crash memory range to fix this issue. Note: This problem will not occur if the kdump kernel is loaded or reloaded after a memory hot-remove operation. Fixes: 849599b702ef ("powerpc/crash: add crash memory hotplug support") Reported-by: Shirisha G Signed-off-by: Sourabh Jain Signed-off-by: Madhavan Srinivasan Link: https://patch.msgid.link/20251105033941.1752287-1-sourabhjain@linux.ibm.com Signed-off-by: Sasha Levin --- arch/powerpc/kexec/ranges.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/arch/powerpc/kexec/ranges.c b/arch/powerpc/kexec/ranges.c index 3702b0bdab1410..426bdca4667e7b 100644 --- a/arch/powerpc/kexec/ranges.c +++ b/arch/powerpc/kexec/ranges.c @@ -697,8 +697,8 @@ int remove_mem_range(struct crash_mem **mem_ranges, u64 base, u64 size) * two half. */ else { + size = mem_rngs->ranges[i].end - end + 1; mem_rngs->ranges[i].end = base - 1; - size = mem_rngs->ranges[i].end - end; ret = add_mem_range(mem_ranges, end + 1, size); } } From 74e9f302f31fb5f94ba62ba8d781cbb45818692d Mon Sep 17 00:00:00 2001 From: Christophe Leroy Date: Fri, 12 Sep 2025 10:37:34 +0200 Subject: [PATCH 0610/3902] powerpc/32: Fix unpaired stwcx. on interrupt exit [ Upstream commit 10e1c77c3636d815db802ceef588522c2d2d947c ] Commit b96bae3ae2cb ("powerpc/32: Replace ASM exception exit by C exception exit from ppc64") erroneouly copied to powerpc/32 the logic from powerpc/64 based on feature CPU_FTR_STCX_CHECKS_ADDRESS which is always 0 on powerpc/32. Re-instate the logic implemented by commit b64f87c16f3c ("[POWERPC] Avoid unpaired stwcx. on some processors") which is based on CPU_FTR_NEED_PAIRED_STWCX feature. Fixes: b96bae3ae2cb ("powerpc/32: Replace ASM exception exit by C exception exit from ppc64") Signed-off-by: Christophe Leroy Signed-off-by: Madhavan Srinivasan Link: https://patch.msgid.link/6040b5dbcf5cdaa1cd919fcf0790f12974ea6e5a.1757666244.git.christophe.leroy@csgroup.eu Signed-off-by: Sasha Levin --- arch/powerpc/kernel/entry_32.S | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/arch/powerpc/kernel/entry_32.S b/arch/powerpc/kernel/entry_32.S index f4a8c98772491a..1beb578c641147 100644 --- a/arch/powerpc/kernel/entry_32.S +++ b/arch/powerpc/kernel/entry_32.S @@ -263,10 +263,9 @@ interrupt_return: mtspr SPRN_SRR1,r12 BEGIN_FTR_SECTION + lwarx r0,0,r1 +END_FTR_SECTION_IFSET(CPU_FTR_NEED_PAIRED_STWCX) stwcx. r0,0,r1 /* to clear the reservation */ -FTR_SECTION_ELSE - lwarx r0,0,r1 -ALT_FTR_SECTION_END_IFCLR(CPU_FTR_STCX_CHECKS_ADDRESS) lwz r3,_CCR(r1) lwz r4,_LINK(r1) @@ -306,10 +305,9 @@ ALT_FTR_SECTION_END_IFCLR(CPU_FTR_STCX_CHECKS_ADDRESS) mtspr SPRN_SRR1,r12 BEGIN_FTR_SECTION + lwarx r0,0,r1 +END_FTR_SECTION_IFSET(CPU_FTR_NEED_PAIRED_STWCX) stwcx. r0,0,r1 /* to clear the reservation */ -FTR_SECTION_ELSE - lwarx r0,0,r1 -ALT_FTR_SECTION_END_IFCLR(CPU_FTR_STCX_CHECKS_ADDRESS) lwz r3,_LINK(r1) lwz r4,_CTR(r1) From 48a7d427eb65922b3f17fbe00e2bbc7cb9eac381 Mon Sep 17 00:00:00 2001 From: Long Li Date: Tue, 19 Aug 2025 17:10:35 +0800 Subject: [PATCH 0611/3902] macintosh/mac_hid: fix race condition in mac_hid_toggle_emumouse [ Upstream commit 1e4b207ffe54cf33a4b7a2912c4110f89c73bf3f ] The following warning appears when running syzkaller, and this issue also exists in the mainline code. ------------[ cut here ]------------ list_add double add: new=ffffffffa57eee28, prev=ffffffffa57eee28, next=ffffffffa5e63100. WARNING: CPU: 0 PID: 1491 at lib/list_debug.c:35 __list_add_valid_or_report+0xf7/0x130 Modules linked in: CPU: 0 PID: 1491 Comm: syz.1.28 Not tainted 6.6.0+ #3 Hardware name: QEMU Standard PC (i440FX + PIIX, 1996), BIOS rel-1.16.0-0-gd239552ce722-prebuilt.qemu.org 04/01/2014 RIP: 0010:__list_add_valid_or_report+0xf7/0x130 RSP: 0018:ff1100010dfb7b78 EFLAGS: 00010282 RAX: 0000000000000000 RBX: ffffffffa57eee18 RCX: ffffffff97fc9817 RDX: 0000000000040000 RSI: ffa0000002383000 RDI: 0000000000000001 RBP: ffffffffa57eee28 R08: 0000000000000001 R09: ffe21c0021bf6f2c R10: 0000000000000001 R11: 6464615f7473696c R12: ffffffffa5e63100 R13: ffffffffa57eee28 R14: ffffffffa57eee28 R15: ff1100010dfb7d48 FS: 00007fb14398b640(0000) GS:ff11000119600000(0000) knlGS:0000000000000000 CS: 0010 DS: 0000 ES: 0000 CR0: 0000000080050033 CR2: 0000000000000000 CR3: 000000010d096005 CR4: 0000000000773ef0 DR0: 0000000000000000 DR1: 0000000000000000 DR2: 0000000000000000 DR3: 0000000000000000 DR6: 00000000fffe0ff0 DR7: 0000000000000400 PKRU: 80000000 Call Trace: input_register_handler+0xb3/0x210 mac_hid_start_emulation+0x1c5/0x290 mac_hid_toggle_emumouse+0x20a/0x240 proc_sys_call_handler+0x4c2/0x6e0 new_sync_write+0x1b1/0x2d0 vfs_write+0x709/0x950 ksys_write+0x12a/0x250 do_syscall_64+0x5a/0x110 entry_SYSCALL_64_after_hwframe+0x78/0xe2 The WARNING occurs when two processes concurrently write to the mac-hid emulation sysctl, causing a race condition in mac_hid_toggle_emumouse(). Both processes read old_val=0, then both try to register the input handler, leading to a double list_add of the same handler. CPU0 CPU1 ------------------------- ------------------------- vfs_write() //write 1 vfs_write() //write 1 proc_sys_write() proc_sys_write() mac_hid_toggle_emumouse() mac_hid_toggle_emumouse() old_val = *valp // old_val=0 old_val = *valp // old_val=0 mutex_lock_killable() proc_dointvec() // *valp=1 mac_hid_start_emulation() input_register_handler() mutex_unlock() mutex_lock_killable() proc_dointvec() mac_hid_start_emulation() input_register_handler() //Trigger Warning mutex_unlock() Fix this by moving the old_val read inside the mutex lock region. Fixes: 99b089c3c38a ("Input: Mac button emulation - implement as an input filter") Signed-off-by: Long Li Signed-off-by: Madhavan Srinivasan Link: https://patch.msgid.link/20250819091035.2263329-1-leo.lilong@huaweicloud.com Signed-off-by: Sasha Levin --- drivers/macintosh/mac_hid.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/drivers/macintosh/mac_hid.c b/drivers/macintosh/mac_hid.c index 369d72f59b3c10..06fd910b3fd1a0 100644 --- a/drivers/macintosh/mac_hid.c +++ b/drivers/macintosh/mac_hid.c @@ -187,13 +187,14 @@ static int mac_hid_toggle_emumouse(const struct ctl_table *table, int write, void *buffer, size_t *lenp, loff_t *ppos) { int *valp = table->data; - int old_val = *valp; + int old_val; int rc; rc = mutex_lock_killable(&mac_hid_emumouse_mutex); if (rc) return rc; + old_val = *valp; rc = proc_dointvec(table, write, buffer, lenp, ppos); if (rc == 0 && write && *valp != old_val) { From 51b823d7723f11b444607268a67502064898047c Mon Sep 17 00:00:00 2001 From: Abdun Nihaal Date: Mon, 10 Nov 2025 23:23:15 +0530 Subject: [PATCH 0612/3902] wifi: cw1200: Fix potential memory leak in cw1200_bh_rx_helper() [ Upstream commit 5e88e864118c20e63a1571d0ff0a152e5d684959 ] In one of the error paths, the memory allocated for skb_rx is not freed. Fix that by freeing it before returning. Fixes: a910e4a94f69 ("cw1200: add driver for the ST-E CW1100 & CW1200 WLAN chipsets") Signed-off-by: Abdun Nihaal Link: https://patch.msgid.link/20251110175316.106591-1-nihaal@cse.iitm.ac.in Signed-off-by: Johannes Berg Signed-off-by: Sasha Levin --- drivers/net/wireless/st/cw1200/bh.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/drivers/net/wireless/st/cw1200/bh.c b/drivers/net/wireless/st/cw1200/bh.c index 3b4ded2ac801cc..37232ee2203755 100644 --- a/drivers/net/wireless/st/cw1200/bh.c +++ b/drivers/net/wireless/st/cw1200/bh.c @@ -317,10 +317,12 @@ static int cw1200_bh_rx_helper(struct cw1200_common *priv, if (wsm_id & 0x0400) { int rc = wsm_release_tx_buffer(priv, 1); - if (WARN_ON(rc < 0)) + if (WARN_ON(rc < 0)) { + dev_kfree_skb(skb_rx); return rc; - else if (rc > 0) + } else if (rc > 0) { *tx = 1; + } } /* cw1200_wsm_rx takes care on SKB livetime */ From c9e805f6a35d1dd189a9345595a5c20e87611942 Mon Sep 17 00:00:00 2001 From: Zheng Qixing Date: Mon, 10 Nov 2025 20:49:20 +0800 Subject: [PATCH 0613/3902] nbd: defer config unlock in nbd_genl_connect [ Upstream commit 1649714b930f9ea6233ce0810ba885999da3b5d4 ] There is one use-after-free warning when running NBD_CMD_CONNECT and NBD_CLEAR_SOCK: nbd_genl_connect nbd_alloc_and_init_config // config_refs=1 nbd_start_device // config_refs=2 set NBD_RT_HAS_CONFIG_REF open nbd // config_refs=3 recv_work done // config_refs=2 NBD_CLEAR_SOCK // config_refs=1 close nbd // config_refs=0 refcount_inc -> uaf ------------[ cut here ]------------ refcount_t: addition on 0; use-after-free. WARNING: CPU: 24 PID: 1014 at lib/refcount.c:25 refcount_warn_saturate+0x12e/0x290 nbd_genl_connect+0x16d0/0x1ab0 genl_family_rcv_msg_doit+0x1f3/0x310 genl_rcv_msg+0x44a/0x790 The issue can be easily reproduced by adding a small delay before refcount_inc(&nbd->config_refs) in nbd_genl_connect(): mutex_unlock(&nbd->config_lock); if (!ret) { set_bit(NBD_RT_HAS_CONFIG_REF, &config->runtime_flags); + printk("before sleep\n"); + mdelay(5 * 1000); + printk("after sleep\n"); refcount_inc(&nbd->config_refs); nbd_connect_reply(info, nbd->index); } Fixes: e46c7287b1c2 ("nbd: add a basic netlink interface") Signed-off-by: Zheng Qixing Reviewed-by: Yu Kuai Signed-off-by: Jens Axboe Signed-off-by: Sasha Levin --- drivers/block/nbd.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/drivers/block/nbd.c b/drivers/block/nbd.c index 215fc18115b78c..a05ff68e58d063 100644 --- a/drivers/block/nbd.c +++ b/drivers/block/nbd.c @@ -2241,12 +2241,13 @@ static int nbd_genl_connect(struct sk_buff *skb, struct genl_info *info) ret = nbd_start_device(nbd); out: - mutex_unlock(&nbd->config_lock); if (!ret) { set_bit(NBD_RT_HAS_CONFIG_REF, &config->runtime_flags); refcount_inc(&nbd->config_refs); nbd_connect_reply(info, nbd->index); } + mutex_unlock(&nbd->config_lock); + nbd_config_put(nbd); if (put_dev) nbd_put(nbd); From de11c3e5908be46b9e87580b4aae3325c28e4520 Mon Sep 17 00:00:00 2001 From: David Wei Date: Fri, 31 Oct 2025 19:24:48 -0700 Subject: [PATCH 0614/3902] net: export netdev_get_by_index_lock() [ Upstream commit c07a491c1b735e0c27454ea5c27a446d43401b1e ] Need to call netdev_get_by_index_lock() from io_uring/zcrx.c, but it is currently private to net. Export the function in linux/netdevice.h. Signed-off-by: David Wei Acked-by: Jakub Kicinski Signed-off-by: Jens Axboe Stable-dep-of: b6c5f9454ef3 ("io_uring/zcrx: call netdev_queue_get_dma_dev() under instance lock") Signed-off-by: Sasha Levin --- include/linux/netdevice.h | 1 + net/core/dev.h | 1 - 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/include/linux/netdevice.h b/include/linux/netdevice.h index d1a687444b275d..77c46a2823eca1 100644 --- a/include/linux/netdevice.h +++ b/include/linux/netdevice.h @@ -3401,6 +3401,7 @@ struct net_device *dev_get_by_index(struct net *net, int ifindex); struct net_device *__dev_get_by_index(struct net *net, int ifindex); struct net_device *netdev_get_by_index(struct net *net, int ifindex, netdevice_tracker *tracker, gfp_t gfp); +struct net_device *netdev_get_by_index_lock(struct net *net, int ifindex); struct net_device *netdev_get_by_name(struct net *net, const char *name, netdevice_tracker *tracker, gfp_t gfp); struct net_device *netdev_get_by_flags_rcu(struct net *net, netdevice_tracker *tracker, diff --git a/net/core/dev.h b/net/core/dev.h index 900880e8b5b4b9..df8a90fe89f8fc 100644 --- a/net/core/dev.h +++ b/net/core/dev.h @@ -29,7 +29,6 @@ struct napi_struct * netdev_napi_by_id_lock(struct net *net, unsigned int napi_id); struct net_device *dev_get_by_napi_id(unsigned int napi_id); -struct net_device *netdev_get_by_index_lock(struct net *net, int ifindex); struct net_device *__netdev_put_lock(struct net_device *dev, struct net *net); struct net_device * netdev_xa_find_lock(struct net *net, struct net_device *dev, From 4717e7860649b846fab1b458e3339cac0eb4894e Mon Sep 17 00:00:00 2001 From: David Wei Date: Fri, 31 Oct 2025 19:24:49 -0700 Subject: [PATCH 0615/3902] io_uring/zcrx: call netdev_queue_get_dma_dev() under instance lock [ Upstream commit b6c5f9454ef34fd2753ba7843ef4d9a295c43eee ] netdev ops must be called under instance lock or rtnl_lock, but io_register_zcrx_ifq() isn't doing this for netdev_queue_get_dma_dev(). Fix this by taking the instance lock using netdev_get_by_index_lock(). Extended the instance lock section to include attaching a memory provider. Could not move io_zcrx_create_area() outside, since the dmabuf codepath IORING_ZCRX_AREA_DMABUF requires ifq->dev. Fixes: 59b8b32ac8d4 ("io_uring/zcrx: add support for custom DMA devices") Signed-off-by: David Wei Reviewed-by: Pavel Begunkov Reviewed-by: Jakub Kicinski Signed-off-by: Jens Axboe Signed-off-by: Sasha Levin --- io_uring/zcrx.c | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/io_uring/zcrx.c b/io_uring/zcrx.c index b1b723222cdb81..875ad40cf65911 100644 --- a/io_uring/zcrx.c +++ b/io_uring/zcrx.c @@ -599,29 +599,30 @@ int io_register_zcrx_ifq(struct io_ring_ctx *ctx, if (ret) goto err; - ifq->netdev = netdev_get_by_index(current->nsproxy->net_ns, reg.if_idx, - &ifq->netdev_tracker, GFP_KERNEL); + ifq->netdev = netdev_get_by_index_lock(current->nsproxy->net_ns, reg.if_idx); if (!ifq->netdev) { ret = -ENODEV; goto err; } + netdev_hold(ifq->netdev, &ifq->netdev_tracker, GFP_KERNEL); ifq->dev = netdev_queue_get_dma_dev(ifq->netdev, reg.if_rxq); if (!ifq->dev) { ret = -EOPNOTSUPP; - goto err; + goto netdev_put_unlock; } get_device(ifq->dev); ret = io_zcrx_create_area(ifq, &area); if (ret) - goto err; + goto netdev_put_unlock; mp_param.mp_ops = &io_uring_pp_zc_ops; mp_param.mp_priv = ifq; - ret = net_mp_open_rxq(ifq->netdev, reg.if_rxq, &mp_param); + ret = __net_mp_open_rxq(ifq->netdev, reg.if_rxq, &mp_param, NULL); if (ret) - goto err; + goto netdev_put_unlock; + netdev_unlock(ifq->netdev); ifq->if_rxq = reg.if_rxq; reg.zcrx_id = id; @@ -640,6 +641,9 @@ int io_register_zcrx_ifq(struct io_ring_ctx *ctx, goto err; } return 0; +netdev_put_unlock: + netdev_put(ifq->netdev, &ifq->netdev_tracker); + netdev_unlock(ifq->netdev); err: scoped_guard(mutex, &ctx->mmap_lock) xa_erase(&ctx->zcrx_ctxs, id); From 192e8ce302f14ac66259231dd10cede19858d742 Mon Sep 17 00:00:00 2001 From: Bartlomiej Kubik Date: Wed, 5 Nov 2025 22:18:08 +0100 Subject: [PATCH 0616/3902] fs/ntfs3: Initialize allocated memory before use [ Upstream commit a8a3ca23bbd9d849308a7921a049330dc6c91398 ] KMSAN reports: Multiple uninitialized values detected: - KMSAN: uninit-value in ntfs_read_hdr (3) - KMSAN: uninit-value in bcmp (3) Memory is allocated by __getname(), which is a wrapper for kmem_cache_alloc(). This memory is used before being properly cleared. Change kmem_cache_alloc() to kmem_cache_zalloc() to properly allocate and clear memory before use. Fixes: 82cae269cfa9 ("fs/ntfs3: Add initialization of super block") Fixes: 78ab59fee07f ("fs/ntfs3: Rework file operations") Tested-by: syzbot+332bd4e9d148f11a87dc@syzkaller.appspotmail.com Reported-by: syzbot+332bd4e9d148f11a87dc@syzkaller.appspotmail.com Closes: https://syzkaller.appspot.com/bug?extid=332bd4e9d148f11a87dc Fixes: 82cae269cfa9 ("fs/ntfs3: Add initialization of super block") Fixes: 78ab59fee07f ("fs/ntfs3: Rework file operations") Tested-by: syzbot+0399100e525dd9696764@syzkaller.appspotmail.com Reported-by: syzbot+0399100e525dd9696764@syzkaller.appspotmail.com Closes: https://syzkaller.appspot.com/bug?extid=0399100e525dd9696764 Reviewed-by: Khalid Aziz Signed-off-by: Bartlomiej Kubik Signed-off-by: Konstantin Komarov Signed-off-by: Sasha Levin --- fs/ntfs3/inode.c | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/fs/ntfs3/inode.c b/fs/ntfs3/inode.c index 05004f8880ce6a..164fd63dff40d6 100644 --- a/fs/ntfs3/inode.c +++ b/fs/ntfs3/inode.c @@ -1279,7 +1279,7 @@ int ntfs_create_inode(struct mnt_idmap *idmap, struct inode *dir, fa |= FILE_ATTRIBUTE_READONLY; /* Allocate PATH_MAX bytes. */ - new_de = __getname(); + new_de = kmem_cache_zalloc(names_cachep, GFP_KERNEL); if (!new_de) { err = -ENOMEM; goto out1; @@ -1720,10 +1720,9 @@ int ntfs_link_inode(struct inode *inode, struct dentry *dentry) struct NTFS_DE *de; /* Allocate PATH_MAX bytes. */ - de = __getname(); + de = kmem_cache_zalloc(names_cachep, GFP_KERNEL); if (!de) return -ENOMEM; - memset(de, 0, PATH_MAX); /* Mark rw ntfs as dirty. It will be cleared at umount. */ ntfs_set_state(sbi, NTFS_DIRTY_DIRTY); @@ -1759,7 +1758,7 @@ int ntfs_unlink_inode(struct inode *dir, const struct dentry *dentry) return -EINVAL; /* Allocate PATH_MAX bytes. */ - de = __getname(); + de = kmem_cache_zalloc(names_cachep, GFP_KERNEL); if (!de) return -ENOMEM; From 4305e94270fab6cdba356e446b816acaa854526f Mon Sep 17 00:00:00 2001 From: Leo Yan Date: Tue, 11 Nov 2025 18:58:35 +0000 Subject: [PATCH 0617/3902] coresight: Change device mode to atomic type [ Upstream commit 693d1eaca940f277af24c74873ef2313816ff444 ] The device mode is defined as local type. This type cannot promise SMP-safe access. Change to atomic type and impose relax ordering, which ensures the SMP-safe synchronisation and the ordering between the mode setting and relevant operations. Fixes: 22fd532eaa0c ("coresight: etm3x: adding operation mode for etm_enable()") Reviewed-by: Mike Leach Tested-by: James Clark Signed-off-by: Leo Yan Signed-off-by: Suzuki K Poulose Link: https://lore.kernel.org/r/20251111-arm_coresight_power_management_fix-v6-1-f55553b6c8b3@arm.com Signed-off-by: Sasha Levin --- include/linux/coresight.h | 25 +++++++++++-------------- 1 file changed, 11 insertions(+), 14 deletions(-) diff --git a/include/linux/coresight.h b/include/linux/coresight.h index 2626105e371919..fafc9ec13f9f43 100644 --- a/include/linux/coresight.h +++ b/include/linux/coresight.h @@ -251,15 +251,11 @@ struct coresight_trace_id_map { * by @coresight_ops. * @access: Device i/o access abstraction for this device. * @dev: The device entity associated to this component. - * @mode: This tracer's mode, i.e sysFS, Perf or disabled. This is - * actually an 'enum cs_mode', but is stored in an atomic type. - * This is always accessed through local_read() and local_set(), - * but wherever it's done from within the Coresight device's lock, - * a non-atomic read would also work. This is the main point of - * synchronisation between code happening inside the sysfs mode's - * coresight_mutex and outside when running in Perf mode. A compare - * and exchange swap is done to atomically claim one mode or the - * other. + * @mode: The device mode, i.e sysFS, Perf or disabled. This is actually + * an 'enum cs_mode' but stored in an atomic type. Access is always + * through atomic APIs, ensuring SMP-safe synchronisation between + * racing from sysFS and Perf mode. A compare-and-exchange + * operation is done to atomically claim one mode or the other. * @refcnt: keep track of what is in use. Only access this outside of the * device's spinlock when the coresight_mutex held and mode == * CS_MODE_SYSFS. Otherwise it must be accessed from inside the @@ -288,7 +284,7 @@ struct coresight_device { const struct coresight_ops *ops; struct csdev_access access; struct device dev; - local_t mode; + atomic_t mode; int refcnt; bool orphan; /* sink specific fields */ @@ -623,13 +619,14 @@ static inline bool coresight_is_percpu_sink(struct coresight_device *csdev) static inline bool coresight_take_mode(struct coresight_device *csdev, enum cs_mode new_mode) { - return local_cmpxchg(&csdev->mode, CS_MODE_DISABLED, new_mode) == - CS_MODE_DISABLED; + int curr = CS_MODE_DISABLED; + + return atomic_try_cmpxchg_acquire(&csdev->mode, &curr, new_mode); } static inline enum cs_mode coresight_get_mode(struct coresight_device *csdev) { - return local_read(&csdev->mode); + return atomic_read_acquire(&csdev->mode); } static inline void coresight_set_mode(struct coresight_device *csdev, @@ -645,7 +642,7 @@ static inline void coresight_set_mode(struct coresight_device *csdev, WARN(new_mode != CS_MODE_DISABLED && current_mode != CS_MODE_DISABLED && current_mode != new_mode, "Device already in use\n"); - local_set(&csdev->mode, new_mode); + atomic_set_release(&csdev->mode, new_mode); } struct coresight_device *coresight_register(struct coresight_desc *desc); From e45178e98848feaf1fddfa1b70d31ecc6ec9ea61 Mon Sep 17 00:00:00 2001 From: Leo Yan Date: Tue, 11 Nov 2025 18:58:36 +0000 Subject: [PATCH 0618/3902] coresight: etm4x: Always set tracer's device mode on target CPU [ Upstream commit 28eee2158575aea8fee7807adb9248ceaf9196f1 ] When enabling a tracer via SysFS interface, the device mode may be set by any CPU - not necessarily the target CPU. This can lead to race condition in SMP, and may result in incorrect mode values being read. Consider the following example, where CPU0 attempts to enable the tracer on CPU1 (the target CPU): CPU0 CPU1 etm4_enable() ` coresight_take_mode(SYSFS) ` etm4_enable_sysfs() ` smp_call_function_single() ----> etm4_enable_hw_smp_call() / / CPU idle: / etm4_cpu_save() / ` coresight_get_mode() Failed to enable h/w / ^^^ ` coresight_set_mode(DISABLED) <-' Read the intermediate SYSFS mode In this case, CPU0 initiates the operation by taking the SYSFS mode to avoid conflicts with the Perf mode. It then sends an IPI to CPU1 to configure the tracer registers. If any error occurs during this process, CPU0 rolls back by setting the mode to DISABLED. However, if CPU1 enters an idle state during this time, it might read the intermediate SYSFS mode. As a result, the CPU PM flow could wrongly save and restore tracer context that is actually disabled. To resolve the issue, this commit moves the device mode setting logic on the target CPU. This ensures that the device mode is only modified by the target CPU, eliminating race condition between mode writes and reads across CPUs. An additional change introduces the etm4_disable_sysfs_smp_call() function for SMP calls, which disables the tracer and explicitly set the mode to DISABLED during SysFS operations. Rename etm4_disable_hw_smp_call() to etm4_disable_sysfs_smp_call() for naming consistency. The flow is updated with this change: CPU0 CPU1 etm4_enable() ` etm4_enable_sysfs() ` smp_call_function_single() ----> etm4_enable_hw_smp_call() ` coresight_take_mode(SYSFS) Failed, set back to DISABLED ` coresight_set_mode(DISABLED) CPU idle: etm4_cpu_save() ` coresight_get_mode() ^^^ Read out the DISABLED mode Fixes: c38a9ec2b2c1 ("coresight: etm4x: moving etm_drvdata::enable to atomic field") Reviewed-by: Yeoreum Yun Reviewed-by: mike Leach Tested-by: James Clark Signed-off-by: Leo Yan Signed-off-by: Suzuki K Poulose Link: https://lore.kernel.org/r/20251111-arm_coresight_power_management_fix-v6-2-f55553b6c8b3@arm.com Signed-off-by: Sasha Levin --- .../coresight/coresight-etm4x-core.c | 60 ++++++++++++------- 1 file changed, 38 insertions(+), 22 deletions(-) diff --git a/drivers/hwtracing/coresight/coresight-etm4x-core.c b/drivers/hwtracing/coresight/coresight-etm4x-core.c index 020f070bf17dc0..1324b40d542100 100644 --- a/drivers/hwtracing/coresight/coresight-etm4x-core.c +++ b/drivers/hwtracing/coresight/coresight-etm4x-core.c @@ -589,13 +589,26 @@ static int etm4_enable_hw(struct etmv4_drvdata *drvdata) return rc; } -static void etm4_enable_hw_smp_call(void *info) +static void etm4_enable_sysfs_smp_call(void *info) { struct etm4_enable_arg *arg = info; + struct coresight_device *csdev; if (WARN_ON(!arg)) return; + + csdev = arg->drvdata->csdev; + if (!coresight_take_mode(csdev, CS_MODE_SYSFS)) { + /* Someone is already using the tracer */ + arg->rc = -EBUSY; + return; + } + arg->rc = etm4_enable_hw(arg->drvdata); + + /* The tracer didn't start */ + if (arg->rc) + coresight_set_mode(csdev, CS_MODE_DISABLED); } /* @@ -808,13 +821,14 @@ static int etm4_enable_perf(struct coresight_device *csdev, struct perf_event *event, struct coresight_path *path) { - int ret = 0; struct etmv4_drvdata *drvdata = dev_get_drvdata(csdev->dev.parent); + int ret; - if (WARN_ON_ONCE(drvdata->cpu != smp_processor_id())) { - ret = -EINVAL; - goto out; - } + if (WARN_ON_ONCE(drvdata->cpu != smp_processor_id())) + return -EINVAL; + + if (!coresight_take_mode(csdev, CS_MODE_PERF)) + return -EBUSY; /* Configure the tracer based on the session's specifics */ ret = etm4_parse_event_config(csdev, event); @@ -830,6 +844,9 @@ static int etm4_enable_perf(struct coresight_device *csdev, ret = etm4_enable_hw(drvdata); out: + /* Failed to start tracer; roll back to DISABLED mode */ + if (ret) + coresight_set_mode(csdev, CS_MODE_DISABLED); return ret; } @@ -861,7 +878,7 @@ static int etm4_enable_sysfs(struct coresight_device *csdev, struct coresight_pa */ arg.drvdata = drvdata; ret = smp_call_function_single(drvdata->cpu, - etm4_enable_hw_smp_call, &arg, 1); + etm4_enable_sysfs_smp_call, &arg, 1); if (!ret) ret = arg.rc; if (!ret) @@ -882,11 +899,6 @@ static int etm4_enable(struct coresight_device *csdev, struct perf_event *event, { int ret; - if (!coresight_take_mode(csdev, mode)) { - /* Someone is already using the tracer */ - return -EBUSY; - } - switch (mode) { case CS_MODE_SYSFS: ret = etm4_enable_sysfs(csdev, path); @@ -898,10 +910,6 @@ static int etm4_enable(struct coresight_device *csdev, struct perf_event *event, ret = -EINVAL; } - /* The tracer didn't start */ - if (ret) - coresight_set_mode(csdev, CS_MODE_DISABLED); - return ret; } @@ -953,10 +961,9 @@ static void etm4_disable_trace_unit(struct etmv4_drvdata *drvdata) isb(); } -static void etm4_disable_hw(void *info) +static void etm4_disable_hw(struct etmv4_drvdata *drvdata) { u32 control; - struct etmv4_drvdata *drvdata = info; struct etmv4_config *config = &drvdata->config; struct coresight_device *csdev = drvdata->csdev; struct csdev_access *csa = &csdev->access; @@ -993,6 +1000,15 @@ static void etm4_disable_hw(void *info) "cpu: %d disable smp call done\n", drvdata->cpu); } +static void etm4_disable_sysfs_smp_call(void *info) +{ + struct etmv4_drvdata *drvdata = info; + + etm4_disable_hw(drvdata); + + coresight_set_mode(drvdata->csdev, CS_MODE_DISABLED); +} + static int etm4_disable_perf(struct coresight_device *csdev, struct perf_event *event) { @@ -1022,6 +1038,8 @@ static int etm4_disable_perf(struct coresight_device *csdev, /* TRCVICTLR::SSSTATUS, bit[9] */ filters->ssstatus = (control & BIT(9)); + coresight_set_mode(drvdata->csdev, CS_MODE_DISABLED); + /* * perf will release trace ids when _free_aux() is * called at the end of the session. @@ -1047,7 +1065,8 @@ static void etm4_disable_sysfs(struct coresight_device *csdev) * Executing etm4_disable_hw on the cpu whose ETM is being disabled * ensures that register writes occur when cpu is powered. */ - smp_call_function_single(drvdata->cpu, etm4_disable_hw, drvdata, 1); + smp_call_function_single(drvdata->cpu, etm4_disable_sysfs_smp_call, + drvdata, 1); raw_spin_unlock(&drvdata->spinlock); @@ -1087,9 +1106,6 @@ static void etm4_disable(struct coresight_device *csdev, etm4_disable_perf(csdev, event); break; } - - if (mode) - coresight_set_mode(csdev, CS_MODE_DISABLED); } static int etm4_resume_perf(struct coresight_device *csdev) From 9698a89977fb885499b853ff16efecb14528d60d Mon Sep 17 00:00:00 2001 From: Leo Yan Date: Tue, 11 Nov 2025 18:58:37 +0000 Subject: [PATCH 0619/3902] coresight: etm3x: Always set tracer's device mode on target CPU [ Upstream commit ab3fde32afe6a77e5cc60f868e44e6e09424752b ] The ETMv3 driver shares the same issue as ETMv4 regarding race conditions when accessing the device mode. This commit applies the same fix: ensuring that the device mode is modified only by the target CPU to eliminate race conditions across CPUs. Fixes: 22fd532eaa0c ("coresight: etm3x: adding operation mode for etm_enable()") Reviewed-by: Mike Leach Tested-by: James Clark Signed-off-by: Leo Yan Signed-off-by: Suzuki K Poulose Link: https://lore.kernel.org/r/20251111-arm_coresight_power_management_fix-v6-3-f55553b6c8b3@arm.com Signed-off-by: Sasha Levin --- .../coresight/coresight-etm3x-core.c | 59 +++++++++++++------ 1 file changed, 40 insertions(+), 19 deletions(-) diff --git a/drivers/hwtracing/coresight/coresight-etm3x-core.c b/drivers/hwtracing/coresight/coresight-etm3x-core.c index 45630a1cd32fbd..a5e809589d3e38 100644 --- a/drivers/hwtracing/coresight/coresight-etm3x-core.c +++ b/drivers/hwtracing/coresight/coresight-etm3x-core.c @@ -439,13 +439,26 @@ struct etm_enable_arg { int rc; }; -static void etm_enable_hw_smp_call(void *info) +static void etm_enable_sysfs_smp_call(void *info) { struct etm_enable_arg *arg = info; + struct coresight_device *csdev; if (WARN_ON(!arg)) return; + + csdev = arg->drvdata->csdev; + if (!coresight_take_mode(csdev, CS_MODE_SYSFS)) { + /* Someone is already using the tracer */ + arg->rc = -EBUSY; + return; + } + arg->rc = etm_enable_hw(arg->drvdata); + + /* The tracer didn't start */ + if (arg->rc) + coresight_set_mode(csdev, CS_MODE_DISABLED); } static int etm_cpu_id(struct coresight_device *csdev) @@ -465,16 +478,26 @@ static int etm_enable_perf(struct coresight_device *csdev, struct coresight_path *path) { struct etm_drvdata *drvdata = dev_get_drvdata(csdev->dev.parent); + int ret; if (WARN_ON_ONCE(drvdata->cpu != smp_processor_id())) return -EINVAL; + if (!coresight_take_mode(csdev, CS_MODE_PERF)) + return -EBUSY; + /* Configure the tracer based on the session's specifics */ etm_parse_event_config(drvdata, event); drvdata->traceid = path->trace_id; /* And enable it */ - return etm_enable_hw(drvdata); + ret = etm_enable_hw(drvdata); + + /* Failed to start tracer; roll back to DISABLED mode */ + if (ret) + coresight_set_mode(csdev, CS_MODE_DISABLED); + + return ret; } static int etm_enable_sysfs(struct coresight_device *csdev, struct coresight_path *path) @@ -494,7 +517,7 @@ static int etm_enable_sysfs(struct coresight_device *csdev, struct coresight_pat if (cpu_online(drvdata->cpu)) { arg.drvdata = drvdata; ret = smp_call_function_single(drvdata->cpu, - etm_enable_hw_smp_call, &arg, 1); + etm_enable_sysfs_smp_call, &arg, 1); if (!ret) ret = arg.rc; if (!ret) @@ -517,12 +540,6 @@ static int etm_enable(struct coresight_device *csdev, struct perf_event *event, enum cs_mode mode, struct coresight_path *path) { int ret; - struct etm_drvdata *drvdata = dev_get_drvdata(csdev->dev.parent); - - if (!coresight_take_mode(csdev, mode)) { - /* Someone is already using the tracer */ - return -EBUSY; - } switch (mode) { case CS_MODE_SYSFS: @@ -535,17 +552,12 @@ static int etm_enable(struct coresight_device *csdev, struct perf_event *event, ret = -EINVAL; } - /* The tracer didn't start */ - if (ret) - coresight_set_mode(drvdata->csdev, CS_MODE_DISABLED); - return ret; } -static void etm_disable_hw(void *info) +static void etm_disable_hw(struct etm_drvdata *drvdata) { int i; - struct etm_drvdata *drvdata = info; struct etm_config *config = &drvdata->config; struct coresight_device *csdev = drvdata->csdev; @@ -567,6 +579,15 @@ static void etm_disable_hw(void *info) "cpu: %d disable smp call done\n", drvdata->cpu); } +static void etm_disable_sysfs_smp_call(void *info) +{ + struct etm_drvdata *drvdata = info; + + etm_disable_hw(drvdata); + + coresight_set_mode(drvdata->csdev, CS_MODE_DISABLED); +} + static void etm_disable_perf(struct coresight_device *csdev) { struct etm_drvdata *drvdata = dev_get_drvdata(csdev->dev.parent); @@ -588,6 +609,8 @@ static void etm_disable_perf(struct coresight_device *csdev) CS_LOCK(drvdata->csa.base); + coresight_set_mode(drvdata->csdev, CS_MODE_DISABLED); + /* * perf will release trace ids when _free_aux() * is called at the end of the session @@ -612,7 +635,8 @@ static void etm_disable_sysfs(struct coresight_device *csdev) * Executing etm_disable_hw on the cpu whose ETM is being disabled * ensures that register writes occur when cpu is powered. */ - smp_call_function_single(drvdata->cpu, etm_disable_hw, drvdata, 1); + smp_call_function_single(drvdata->cpu, etm_disable_sysfs_smp_call, + drvdata, 1); spin_unlock(&drvdata->spinlock); cpus_read_unlock(); @@ -652,9 +676,6 @@ static void etm_disable(struct coresight_device *csdev, WARN_ON_ONCE(mode); return; } - - if (mode) - coresight_set_mode(csdev, CS_MODE_DISABLED); } static const struct coresight_ops_source etm_source_ops = { From 352faa1d6387bf88b309252b9369803ac3365636 Mon Sep 17 00:00:00 2001 From: Leo Yan Date: Tue, 11 Nov 2025 18:58:38 +0000 Subject: [PATCH 0620/3902] coresight: etm4x: Correct polling IDLE bit [ Upstream commit 4dc4e22f9536341255f5de6047977a80ff47eaef ] Since commit 4ff6039ffb79 ("coresight-etm4x: add isb() before reading the TRCSTATR"), the code has incorrectly been polling the PMSTABLE bit instead of the IDLE bit. This commit corrects the typo. Fixes: 4ff6039ffb79 ("coresight-etm4x: add isb() before reading the TRCSTATR") Reviewed-by: Yeoreum Yun Reviewed-by: Mike Leach Tested-by: James Clark Signed-off-by: Leo Yan Signed-off-by: Suzuki K Poulose Link: https://lore.kernel.org/r/20251111-arm_coresight_power_management_fix-v6-4-f55553b6c8b3@arm.com Signed-off-by: Sasha Levin --- drivers/hwtracing/coresight/coresight-etm4x-core.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/hwtracing/coresight/coresight-etm4x-core.c b/drivers/hwtracing/coresight/coresight-etm4x-core.c index 1324b40d542100..c562f82985192a 100644 --- a/drivers/hwtracing/coresight/coresight-etm4x-core.c +++ b/drivers/hwtracing/coresight/coresight-etm4x-core.c @@ -1924,7 +1924,7 @@ static int __etm4_cpu_save(struct etmv4_drvdata *drvdata) state->trcpdcr = etm4x_read32(csa, TRCPDCR); /* wait for TRCSTATR.IDLE to go up */ - if (etm4x_wait_status(csa, TRCSTATR_PMSTABLE_BIT, 1)) { + if (etm4x_wait_status(csa, TRCSTATR_IDLE_BIT, 1)) { dev_err(etm_dev, "timeout while waiting for Idle Trace Status\n"); etm4_os_unlock(drvdata); From 2daa7706a0004d33734cb5ba56c5b3e095c8cfbe Mon Sep 17 00:00:00 2001 From: Leo Yan Date: Tue, 11 Nov 2025 18:58:39 +0000 Subject: [PATCH 0621/3902] coresight: etm4x: Add context synchronization before enabling trace [ Upstream commit 64eb04ae545294e105ad91714dc3167a0b660731 ] According to the software usage PKLXF in Arm ARM (ARM DDI 0487 L.a), a Context synchronization event is required before enabling the trace unit. An ISB is added to meet this requirement, particularly for guarding the operations in the flow: etm4x_allow_trace() `> kvm_tracing_set_el1_configuration() `> write_sysreg_s(trfcr_while_in_guest, SYS_TRFCR_EL12) Improved the barrier comments to provide more accurate information. Fixes: 1ab3bb9df5e3 ("coresight: etm4x: Add necessary synchronization for sysreg access") Reviewed-by: Mike Leach Reviewed-by: Yeoreun Yun Tested-by: James Clark Signed-off-by: Leo Yan Signed-off-by: Suzuki K Poulose Link: https://lore.kernel.org/r/20251111-arm_coresight_power_management_fix-v6-5-f55553b6c8b3@arm.com Signed-off-by: Sasha Levin --- .../coresight/coresight-etm4x-core.c | 27 ++++++++++++++++--- 1 file changed, 23 insertions(+), 4 deletions(-) diff --git a/drivers/hwtracing/coresight/coresight-etm4x-core.c b/drivers/hwtracing/coresight/coresight-etm4x-core.c index c562f82985192a..5e707d082537a4 100644 --- a/drivers/hwtracing/coresight/coresight-etm4x-core.c +++ b/drivers/hwtracing/coresight/coresight-etm4x-core.c @@ -446,10 +446,24 @@ static int etm4_enable_trace_unit(struct etmv4_drvdata *drvdata) etm4x_relaxed_write32(csa, TRCRSR_TA, TRCRSR); etm4x_allow_trace(drvdata); + + /* + * According to software usage PKLXF in Arm ARM (ARM DDI 0487 L.a), + * execute a Context synchronization event to guarantee the trace unit + * will observe the new values of the System registers. + */ + if (!csa->io_mem) + isb(); + /* Enable the trace unit */ etm4x_relaxed_write32(csa, 1, TRCPRGCTLR); - /* Synchronize the register updates for sysreg access */ + /* + * As recommended by section 4.3.7 ("Synchronization when using system + * instructions to progrom the trace unit") of ARM IHI 0064H.b, the + * self-hosted trace analyzer must perform a Context synchronization + * event between writing to the TRCPRGCTLR and reading the TRCSTATR. + */ if (!csa->io_mem) isb(); @@ -931,11 +945,16 @@ static void etm4_disable_trace_unit(struct etmv4_drvdata *drvdata) */ etm4x_prohibit_trace(drvdata); /* - * Make sure everything completes before disabling, as recommended - * by section 7.3.77 ("TRCVICTLR, ViewInst Main Control Register, - * SSTATUS") of ARM IHI 0064D + * Prevent being speculative at the point of disabling the trace unit, + * as recommended by section 7.3.77 ("TRCVICTLR, ViewInst Main Control + * Register, SSTATUS") of ARM IHI 0064D */ dsb(sy); + /* + * According to software usage VKHHY in Arm ARM (ARM DDI 0487 L.a), + * execute a Context synchronization event to guarantee no new + * program-flow trace is generated. + */ isb(); /* Trace synchronization barrier, is a nop if not supported */ tsb_csync(); From e485e76864b61670cd30884db98f8706b65d8248 Mon Sep 17 00:00:00 2001 From: Leo Yan Date: Tue, 11 Nov 2025 18:58:40 +0000 Subject: [PATCH 0622/3902] coresight: etm4x: Properly control filter in CPU idle with FEAT_TRF [ Upstream commit 1fdc2cd347a7bc58acacb6144404ee892cea6c2e ] If a CPU supports FEAT_TRF, as described in the section K5.5 "Context switching", Arm ARM (ARM DDI 0487 L.a), it defines a flow to prohibit program-flow trace, execute a TSB CSYNC instruction for flushing, followed by clearing TRCPRGCTLR.EN bit. To restore the state, the reverse sequence is required. This differs from the procedure described in the section 3.4.1 "The procedure when powering down the PE" of ARM IHI0064H.b, which involves the OS Lock to prevent external debugger accesses and implicitly disables trace. To be compatible with different ETM versions, explicitly control trace unit using etm4_disable_trace_unit() and etm4_enable_trace_unit() during CPU idle to comply with FEAT_TRF. As a result, the save states for TRFCR_ELx and trcprgctlr are redundant, remove them. Fixes: f188b5e76aae ("coresight: etm4x: Save/restore state across CPU low power states") Reviewed-by: Mike Leach Tested-by: James Clark Reviewed-by: Yeoreum Yun Signed-off-by: Leo Yan Signed-off-by: Suzuki K Poulose Link: https://lore.kernel.org/r/20251111-arm_coresight_power_management_fix-v6-6-f55553b6c8b3@arm.com Signed-off-by: Sasha Levin --- drivers/hwtracing/coresight/coresight-etm4x-core.c | 14 +++++++------- drivers/hwtracing/coresight/coresight-etm4x.h | 3 --- 2 files changed, 7 insertions(+), 10 deletions(-) diff --git a/drivers/hwtracing/coresight/coresight-etm4x-core.c b/drivers/hwtracing/coresight/coresight-etm4x-core.c index 5e707d082537a4..fdda924a2c7117 100644 --- a/drivers/hwtracing/coresight/coresight-etm4x-core.c +++ b/drivers/hwtracing/coresight/coresight-etm4x-core.c @@ -1858,9 +1858,11 @@ static int __etm4_cpu_save(struct etmv4_drvdata *drvdata) goto out; } + if (!drvdata->paused) + etm4_disable_trace_unit(drvdata); + state = drvdata->save_state; - state->trcprgctlr = etm4x_read32(csa, TRCPRGCTLR); if (drvdata->nr_pe) state->trcprocselr = etm4x_read32(csa, TRCPROCSELR); state->trcconfigr = etm4x_read32(csa, TRCCONFIGR); @@ -1970,9 +1972,6 @@ static int etm4_cpu_save(struct etmv4_drvdata *drvdata) { int ret = 0; - /* Save the TRFCR irrespective of whether the ETM is ON */ - if (drvdata->trfcr) - drvdata->save_trfcr = read_trfcr(); /* * Save and restore the ETM Trace registers only if * the ETM is active. @@ -1994,7 +1993,6 @@ static void __etm4_cpu_restore(struct etmv4_drvdata *drvdata) etm4_cs_unlock(drvdata, csa); etm4x_relaxed_write32(csa, state->trcclaimset, TRCCLAIMSET); - etm4x_relaxed_write32(csa, state->trcprgctlr, TRCPRGCTLR); if (drvdata->nr_pe) etm4x_relaxed_write32(csa, state->trcprocselr, TRCPROCSELR); etm4x_relaxed_write32(csa, state->trcconfigr, TRCCONFIGR); @@ -2079,13 +2077,15 @@ static void __etm4_cpu_restore(struct etmv4_drvdata *drvdata) /* Unlock the OS lock to re-enable trace and external debug access */ etm4_os_unlock(drvdata); + + if (!drvdata->paused) + etm4_enable_trace_unit(drvdata); + etm4_cs_lock(drvdata, csa); } static void etm4_cpu_restore(struct etmv4_drvdata *drvdata) { - if (drvdata->trfcr) - write_trfcr(drvdata->save_trfcr); if (drvdata->state_needs_restore) __etm4_cpu_restore(drvdata); } diff --git a/drivers/hwtracing/coresight/coresight-etm4x.h b/drivers/hwtracing/coresight/coresight-etm4x.h index 13ec9ecef46f5b..b8796b4271025f 100644 --- a/drivers/hwtracing/coresight/coresight-etm4x.h +++ b/drivers/hwtracing/coresight/coresight-etm4x.h @@ -866,7 +866,6 @@ struct etmv4_config { * struct etm4_save_state - state to be preserved when ETM is without power */ struct etmv4_save_state { - u32 trcprgctlr; u32 trcprocselr; u32 trcconfigr; u32 trcauxctlr; @@ -980,7 +979,6 @@ struct etmv4_save_state { * at runtime, due to the additional setting of TRFCR_CX when * in EL2. Otherwise, 0. * @config: structure holding configuration parameters. - * @save_trfcr: Saved TRFCR_EL1 register during a CPU PM event. * @save_state: State to be preserved across power loss * @state_needs_restore: True when there is context to restore after PM exit * @skip_power_up: Indicates if an implementation can skip powering up @@ -1037,7 +1035,6 @@ struct etmv4_drvdata { bool lpoverride; u64 trfcr; struct etmv4_config config; - u64 save_trfcr; struct etmv4_save_state *save_state; bool state_needs_restore; bool skip_power_up; From b2abcb89865b4fae66cb7a6c5f063fa4af0071f4 Mon Sep 17 00:00:00 2001 From: Namhyung Kim Date: Mon, 10 Nov 2025 23:59:44 -0800 Subject: [PATCH 0623/3902] perf tools: Fix missing feature check for inherit + SAMPLE_READ [ Upstream commit 367377f45c0b568882567f797b7b18b263505be7 ] It should also have PERF_SAMPLE_TID to enable inherit and PERF_SAMPLE_READ on recent kernels. Not having _TID makes the feature check wrongly detect the inherit and _READ support. It was reported that the following command failed due to the error in the missing feature check on Intel SPR machines. $ perf record -e '{cpu/mem-loads-aux/S,cpu/mem-loads,ldlat=3/PS}' -- ls Error: Failure to open event 'cpu/mem-loads,ldlat=3/PS' on PMU 'cpu' which will be removed. Invalid event (cpu/mem-loads,ldlat=3/PS) in per-thread mode, enable system wide with '-a'. Reviewed-by: Ian Rogers Fixes: 3b193a57baf15c468 ("perf tools: Detect missing kernel features properly") Reported-and-tested-by: Chen, Zide Closes: https://lore.kernel.org/lkml/20251022220802.1335131-1-zide.chen@intel.com/ Signed-off-by: Namhyung Kim Signed-off-by: Sasha Levin --- tools/perf/util/evsel.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/perf/util/evsel.c b/tools/perf/util/evsel.c index 56ebefd075f2e3..9df9818e370139 100644 --- a/tools/perf/util/evsel.c +++ b/tools/perf/util/evsel.c @@ -2473,7 +2473,7 @@ static bool evsel__detect_missing_features(struct evsel *evsel, struct perf_cpu /* Please add new feature detection here. */ attr.inherit = true; - attr.sample_type = PERF_SAMPLE_READ; + attr.sample_type = PERF_SAMPLE_READ | PERF_SAMPLE_TID; if (has_attr_feature(&attr, /*flags=*/0)) goto found; perf_missing_features.inherit_sample_read = true; From b1d4bb43691c9e04bc479e76ed03f623e7a4d392 Mon Sep 17 00:00:00 2001 From: Jayesh Choudhary Date: Tue, 4 Nov 2025 20:44:21 +0530 Subject: [PATCH 0624/3902] drm/tidss: Remove max_pclk_khz and min_pclk_khz from tidss display features [ Upstream commit 527e132573dfa793818a536b18eec49598a6f6f5 ] The TIDSS hardware does not have independent maximum or minimum pixel clock limits for each video port. Instead, these limits are determined by the SoC's clock architecture. Previously, this constraint was modeled using the 'max_pclk_khz' and 'min_pclk_khz' fields in 'dispc_features', but this approach is static and does not account for the dynamic behavior of PLLs. This patch removes the 'max_pclk_khz' and 'min_pclk_khz' fields from 'dispc_features'. The correct way to check if a requested mode's pixel clock is supported is by using 'clk_round_rate()' in the 'mode_valid()' hook. If the best frequency match for the mode clock falls within the supported tolerance, it is approved. TIDSS supports a 5% pixel clock tolerance, which is now reflected in the validation logic. This change allows existing DSS-compatible drivers to be reused across SoCs that only differ in their pixel clock characteristics. The validation uses 'clk_round_rate()' for each mode, which may introduce additional delay (about 3.5 ms for 30 modes), but this is generally negligible. Users desiring faster validation may bypass these calls selectively, for example, checking only the highest resolution mode, as shown here[1]. [1]: https://lore.kernel.org/all/20250704094851.182131-3-j-choudhary@ti.com/ Tested-by: Michael Walle Reviewed-by: Devarsh Thakkar Reviewed-by: Tomi Valkeinen Signed-off-by: Jayesh Choudhary Signed-off-by: Swamil Jain Link: https://patch.msgid.link/20251104151422.307162-2-s-jain1@ti.com [Tomi: dropped 'inline' from check_pixel_clock] Signed-off-by: Tomi Valkeinen Stable-dep-of: 86db652fc22f ("drm/tidss: Move OLDI mode validation to OLDI bridge mode_valid hook") Signed-off-by: Sasha Levin --- drivers/gpu/drm/tidss/tidss_dispc.c | 86 +++++++++++------------------ drivers/gpu/drm/tidss/tidss_dispc.h | 3 - 2 files changed, 31 insertions(+), 58 deletions(-) diff --git a/drivers/gpu/drm/tidss/tidss_dispc.c b/drivers/gpu/drm/tidss/tidss_dispc.c index 7c8c15a5c39b3f..55259b8e875107 100644 --- a/drivers/gpu/drm/tidss/tidss_dispc.c +++ b/drivers/gpu/drm/tidss/tidss_dispc.c @@ -57,12 +57,6 @@ static const u16 tidss_k2g_common_regs[DISPC_COMMON_REG_TABLE_LEN] = { }; const struct dispc_features dispc_k2g_feats = { - .min_pclk_khz = 4375, - - .max_pclk_khz = { - [DISPC_VP_DPI] = 150000, - }, - /* * XXX According TRM the RGB input buffer width up to 2560 should * work on 3 taps, but in practice it only works up to 1280. @@ -145,11 +139,6 @@ static const u16 tidss_am65x_common_regs[DISPC_COMMON_REG_TABLE_LEN] = { }; const struct dispc_features dispc_am65x_feats = { - .max_pclk_khz = { - [DISPC_VP_DPI] = 165000, - [DISPC_VP_OLDI_AM65X] = 165000, - }, - .scaling = { .in_width_max_5tap_rgb = 1280, .in_width_max_3tap_rgb = 2560, @@ -245,11 +234,6 @@ static const u16 tidss_j721e_common_regs[DISPC_COMMON_REG_TABLE_LEN] = { }; const struct dispc_features dispc_j721e_feats = { - .max_pclk_khz = { - [DISPC_VP_DPI] = 170000, - [DISPC_VP_INTERNAL] = 600000, - }, - .scaling = { .in_width_max_5tap_rgb = 2048, .in_width_max_3tap_rgb = 4096, @@ -316,11 +300,6 @@ const struct dispc_features dispc_j721e_feats = { }; const struct dispc_features dispc_am625_feats = { - .max_pclk_khz = { - [DISPC_VP_DPI] = 165000, - [DISPC_VP_INTERNAL] = 170000, - }, - .scaling = { .in_width_max_5tap_rgb = 1280, .in_width_max_3tap_rgb = 2560, @@ -377,15 +356,6 @@ const struct dispc_features dispc_am625_feats = { }; const struct dispc_features dispc_am62a7_feats = { - /* - * if the code reaches dispc_mode_valid with VP1, - * it should return MODE_BAD. - */ - .max_pclk_khz = { - [DISPC_VP_TIED_OFF] = 0, - [DISPC_VP_DPI] = 165000, - }, - .scaling = { .in_width_max_5tap_rgb = 1280, .in_width_max_3tap_rgb = 2560, @@ -442,10 +412,6 @@ const struct dispc_features dispc_am62a7_feats = { }; const struct dispc_features dispc_am62l_feats = { - .max_pclk_khz = { - [DISPC_VP_DPI] = 165000, - }, - .subrev = DISPC_AM62L, .common = "common", @@ -1331,33 +1297,54 @@ static void dispc_vp_set_default_color(struct dispc_device *dispc, DISPC_OVR_DEFAULT_COLOR2, (v >> 32) & 0xffff); } +/* + * Calculate the percentage difference between the requested pixel clock rate + * and the effective rate resulting from calculating the clock divider value. + */ +unsigned int dispc_pclk_diff(unsigned long rate, unsigned long real_rate) +{ + int r = rate / 100, rr = real_rate / 100; + + return (unsigned int)(abs(((rr - r) * 100) / r)); +} + +static int check_pixel_clock(struct dispc_device *dispc, u32 hw_videoport, + unsigned long clock) +{ + unsigned long round_clock; + + round_clock = clk_round_rate(dispc->vp_clk[hw_videoport], clock); + /* + * To keep the check consistent with dispc_vp_set_clk_rate(), we + * use the same 5% check here. + */ + if (dispc_pclk_diff(clock, round_clock) > 5) + return -EINVAL; + + return 0; +} + enum drm_mode_status dispc_vp_mode_valid(struct dispc_device *dispc, u32 hw_videoport, const struct drm_display_mode *mode) { u32 hsw, hfp, hbp, vsw, vfp, vbp; enum dispc_vp_bus_type bus_type; - int max_pclk; bus_type = dispc->feat->vp_bus_type[hw_videoport]; - max_pclk = dispc->feat->max_pclk_khz[bus_type]; - - if (WARN_ON(max_pclk == 0)) + if (WARN_ON(bus_type == DISPC_VP_TIED_OFF)) return MODE_BAD; - if (mode->clock < dispc->feat->min_pclk_khz) - return MODE_CLOCK_LOW; - - if (mode->clock > max_pclk) - return MODE_CLOCK_HIGH; - if (mode->hdisplay > 4096) return MODE_BAD; if (mode->vdisplay > 4096) return MODE_BAD; + if (check_pixel_clock(dispc, hw_videoport, mode->clock * 1000)) + return MODE_CLOCK_RANGE; + /* TODO: add interlace support */ if (mode->flags & DRM_MODE_FLAG_INTERLACE) return MODE_NO_INTERLACE; @@ -1421,17 +1408,6 @@ void dispc_vp_disable_clk(struct dispc_device *dispc, u32 hw_videoport) clk_disable_unprepare(dispc->vp_clk[hw_videoport]); } -/* - * Calculate the percentage difference between the requested pixel clock rate - * and the effective rate resulting from calculating the clock divider value. - */ -unsigned int dispc_pclk_diff(unsigned long rate, unsigned long real_rate) -{ - int r = rate / 100, rr = real_rate / 100; - - return (unsigned int)(abs(((rr - r) * 100) / r)); -} - int dispc_vp_set_clk_rate(struct dispc_device *dispc, u32 hw_videoport, unsigned long rate) { diff --git a/drivers/gpu/drm/tidss/tidss_dispc.h b/drivers/gpu/drm/tidss/tidss_dispc.h index 60c1b400eb8933..42279312dcc1b3 100644 --- a/drivers/gpu/drm/tidss/tidss_dispc.h +++ b/drivers/gpu/drm/tidss/tidss_dispc.h @@ -77,9 +77,6 @@ enum dispc_dss_subrevision { }; struct dispc_features { - int min_pclk_khz; - int max_pclk_khz[DISPC_VP_MAX_BUS_TYPE]; - struct dispc_features_scaling scaling; enum dispc_dss_subrevision subrev; From 4dd34a24b68eb3e985e223525ab747ed17d031af Mon Sep 17 00:00:00 2001 From: Jayesh Choudhary Date: Tue, 4 Nov 2025 20:44:22 +0530 Subject: [PATCH 0625/3902] drm/tidss: Move OLDI mode validation to OLDI bridge mode_valid hook [ Upstream commit 86db652fc22f5674ffe3b1f7c91c397c69d26d94 ] After integrating OLDI support[0], it is necessary to identify which VP instances use OLDI, since the OLDI driver owns the video port clock (as a serial clock). Clock operations on these VPs must be delegated to the OLDI driver, not handled by the TIDSS driver. This issue also emerged in upstream discussions when DSI-related clock management was attempted in the TIDSS driver[1]. To address this, add an 'is_ext_vp_clk' array to the 'tidss_device' structure, marking a VP as 'true' during 'tidss_oldi_init()' and as 'false' during 'tidss_oldi_deinit()'. TIDSS then uses 'is_ext_vp_clk' to skip clock validation checks in 'dispc_vp_mode_valid()' for VPs under OLDI control. Since OLDI uses the DSS VP clock directly as a serial interface and manages its own rate, mode validation should be implemented in the OLDI bridge's 'mode_valid' hook. This patch adds that logic, ensuring proper delegation and avoiding spurious clock handling in the TIDSS driver. [0]: https://lore.kernel.org/all/20250528122544.817829-1-aradhya.bhatia@linux.dev/ [1]: https://lore.kernel.org/all/DA6TT575Z82D.3MPK8HG5GRL8U@kernel.org/ Fixes: 7246e0929945 ("drm/tidss: Add OLDI bridge support") Tested-by: Michael Walle Reviewed-by: Devarsh Thakkar Reviewed-by: Tomi Valkeinen Signed-off-by: Jayesh Choudhary Signed-off-by: Swamil Jain Link: https://patch.msgid.link/20251104151422.307162-3-s-jain1@ti.com Signed-off-by: Tomi Valkeinen Link: https://patch.msgid.link/ffd5ebe03391b3c01e616c0c844a4b8ddecede36.1762513240.git.jani.nikula@intel.com Signed-off-by: Sasha Levin --- drivers/gpu/drm/tidss/tidss_dispc.c | 7 +++++++ drivers/gpu/drm/tidss/tidss_drv.h | 2 ++ drivers/gpu/drm/tidss/tidss_oldi.c | 22 ++++++++++++++++++++++ 3 files changed, 31 insertions(+) diff --git a/drivers/gpu/drm/tidss/tidss_dispc.c b/drivers/gpu/drm/tidss/tidss_dispc.c index 55259b8e875107..6bf1171d0bc2db 100644 --- a/drivers/gpu/drm/tidss/tidss_dispc.c +++ b/drivers/gpu/drm/tidss/tidss_dispc.c @@ -1313,6 +1313,13 @@ static int check_pixel_clock(struct dispc_device *dispc, u32 hw_videoport, { unsigned long round_clock; + /* + * For VP's with external clocking, clock operations must be + * delegated to respective driver, so we skip the check here. + */ + if (dispc->tidss->is_ext_vp_clk[hw_videoport]) + return 0; + round_clock = clk_round_rate(dispc->vp_clk[hw_videoport], clock); /* * To keep the check consistent with dispc_vp_set_clk_rate(), we diff --git a/drivers/gpu/drm/tidss/tidss_drv.h b/drivers/gpu/drm/tidss/tidss_drv.h index 84454a4855d115..e1c1f41d8b4beb 100644 --- a/drivers/gpu/drm/tidss/tidss_drv.h +++ b/drivers/gpu/drm/tidss/tidss_drv.h @@ -24,6 +24,8 @@ struct tidss_device { const struct dispc_features *feat; struct dispc_device *dispc; + bool is_ext_vp_clk[TIDSS_MAX_PORTS]; + unsigned int num_crtcs; struct drm_crtc *crtcs[TIDSS_MAX_PORTS]; diff --git a/drivers/gpu/drm/tidss/tidss_oldi.c b/drivers/gpu/drm/tidss/tidss_oldi.c index 7688251beba280..17c535bfa05765 100644 --- a/drivers/gpu/drm/tidss/tidss_oldi.c +++ b/drivers/gpu/drm/tidss/tidss_oldi.c @@ -309,6 +309,25 @@ static u32 *tidss_oldi_atomic_get_input_bus_fmts(struct drm_bridge *bridge, return input_fmts; } +static enum drm_mode_status +tidss_oldi_mode_valid(struct drm_bridge *bridge, + const struct drm_display_info *info, + const struct drm_display_mode *mode) +{ + struct tidss_oldi *oldi = drm_bridge_to_tidss_oldi(bridge); + unsigned long round_clock; + + round_clock = clk_round_rate(oldi->serial, mode->clock * 7 * 1000); + /* + * To keep the check consistent with dispc_vp_set_clk_rate(), + * we use the same 5% check here. + */ + if (dispc_pclk_diff(mode->clock * 7 * 1000, round_clock) > 5) + return -EINVAL; + + return 0; +} + static const struct drm_bridge_funcs tidss_oldi_bridge_funcs = { .attach = tidss_oldi_bridge_attach, .atomic_pre_enable = tidss_oldi_atomic_pre_enable, @@ -317,6 +336,7 @@ static const struct drm_bridge_funcs tidss_oldi_bridge_funcs = { .atomic_duplicate_state = drm_atomic_helper_bridge_duplicate_state, .atomic_destroy_state = drm_atomic_helper_bridge_destroy_state, .atomic_reset = drm_atomic_helper_bridge_reset, + .mode_valid = tidss_oldi_mode_valid, }; static int get_oldi_mode(struct device_node *oldi_tx, int *companion_instance) @@ -430,6 +450,7 @@ void tidss_oldi_deinit(struct tidss_device *tidss) for (int i = 0; i < tidss->num_oldis; i++) { if (tidss->oldis[i]) { drm_bridge_remove(&tidss->oldis[i]->bridge); + tidss->is_ext_vp_clk[tidss->oldis[i]->parent_vp] = false; tidss->oldis[i] = NULL; } } @@ -580,6 +601,7 @@ int tidss_oldi_init(struct tidss_device *tidss) oldi->bridge.timings = &default_tidss_oldi_timings; tidss->oldis[tidss->num_oldis++] = oldi; + tidss->is_ext_vp_clk[oldi->parent_vp] = true; oldi->tidss = tidss; drm_bridge_add(&oldi->bridge); From 0dcbf7d6e9015447dfed6b3616ecef163e5e0fd8 Mon Sep 17 00:00:00 2001 From: Lad Prabhakar Date: Tue, 28 Oct 2025 16:51:23 +0000 Subject: [PATCH 0626/3902] clk: renesas: r9a09g077: Propagate rate changes to parent clocks [ Upstream commit 145dfd70b9c70e5bc03494a7ce8fa3748ac01af3 ] Add the CLK_SET_RATE_PARENT flag to divider clock registration so that rate changes can propagate to parent clocks when needed. This allows the CPG divider clocks to request rate adjustments from their parent, ensuring correct frequency scaling and improved flexibility in clock rate selection. Fixes: 065fe720eec6e ("clk: renesas: Add support for R9A09G077 SoC") Signed-off-by: Lad Prabhakar Reviewed-by: Geert Uytterhoeven Link: https://patch.msgid.link/20251028165127.991351-2-prabhakar.mahadev-lad.rj@bp.renesas.com Signed-off-by: Geert Uytterhoeven Signed-off-by: Sasha Levin --- drivers/clk/renesas/r9a09g077-cpg.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/clk/renesas/r9a09g077-cpg.c b/drivers/clk/renesas/r9a09g077-cpg.c index af3ef6d58c87cd..d12975418a5681 100644 --- a/drivers/clk/renesas/r9a09g077-cpg.c +++ b/drivers/clk/renesas/r9a09g077-cpg.c @@ -217,7 +217,7 @@ r9a09g077_cpg_div_clk_register(struct device *dev, if (core->dtable) clk_hw = clk_hw_register_divider_table(dev, core->name, - parent_name, 0, + parent_name, CLK_SET_RATE_PARENT, addr, GET_SHIFT(core->conf), GET_WIDTH(core->conf), @@ -226,7 +226,7 @@ r9a09g077_cpg_div_clk_register(struct device *dev, &pub->rmw_lock); else clk_hw = clk_hw_register_divider(dev, core->name, - parent_name, 0, + parent_name, CLK_SET_RATE_PARENT, addr, GET_SHIFT(core->conf), GET_WIDTH(core->conf), From 8cacdb0f0bca048bcffd1b8c48a3daa40dcae3a6 Mon Sep 17 00:00:00 2001 From: Haotian Zhang Date: Thu, 30 Oct 2025 14:16:03 +0800 Subject: [PATCH 0627/3902] clk: renesas: r9a06g032: Fix memory leak in error path [ Upstream commit f8def051bbcf8677f64701e9699bf6d11e2780cd ] The current code uses of_iomap() to map registers but never calls iounmap() on any error path after the mapping. This causes a memory leak when probe fails after successful ioremap, for example when of_clk_add_provider() or r9a06g032_add_clk_domain() fails. Replace of_iomap() with devm_of_iomap() to automatically unmap the region on probe failure. Update the error check accordingly to use IS_ERR() and PTR_ERR() since devm_of_iomap() returns ERR_PTR on error. Fixes: 4c3d88526eba ("clk: renesas: Renesas R9A06G032 clock driver") Signed-off-by: Haotian Zhang Reviewed-by: Geert Uytterhoeven Link: https://patch.msgid.link/20251030061603.1954-1-vulab@iscas.ac.cn Signed-off-by: Geert Uytterhoeven Signed-off-by: Sasha Levin --- drivers/clk/renesas/r9a06g032-clocks.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/drivers/clk/renesas/r9a06g032-clocks.c b/drivers/clk/renesas/r9a06g032-clocks.c index dcda19318b2a9a..0f5c91b5dfa995 100644 --- a/drivers/clk/renesas/r9a06g032-clocks.c +++ b/drivers/clk/renesas/r9a06g032-clocks.c @@ -1333,9 +1333,9 @@ static int __init r9a06g032_clocks_probe(struct platform_device *pdev) if (IS_ERR(mclk)) return PTR_ERR(mclk); - clocks->reg = of_iomap(np, 0); - if (WARN_ON(!clocks->reg)) - return -ENOMEM; + clocks->reg = devm_of_iomap(dev, np, 0, NULL); + if (IS_ERR(clocks->reg)) + return PTR_ERR(clocks->reg); r9a06g032_init_h2mode(clocks); From e0a7eab9165a6a0dcaf1306c3f1aa6a151e4aa95 Mon Sep 17 00:00:00 2001 From: Andy Shevchenko Date: Mon, 10 Nov 2025 14:21:18 +0100 Subject: [PATCH 0628/3902] lib/vsprintf: Check pointer before dereferencing in time_and_date() [ Upstream commit 372a12bd5df0199aa234eaf8ef31ed7ecd61d40f ] The pointer may be invalid when gets to the printf(). In particular the time_and_date() dereferencing it in some cases without checking. Move the check from rtc_str() to time_and_date() to cover all cases. Fixes: 7daac5b2fdf8 ("lib/vsprintf: Print time64_t in human readable format") Signed-off-by: Andy Shevchenko Reviewed-by: Petr Mladek Link: https://patch.msgid.link/20251110132118.4113976-1-andriy.shevchenko@linux.intel.com Signed-off-by: Petr Mladek Signed-off-by: Sasha Levin --- lib/vsprintf.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/vsprintf.c b/lib/vsprintf.c index eb0cb11d0d126f..a356965c1d7348 100644 --- a/lib/vsprintf.c +++ b/lib/vsprintf.c @@ -1928,9 +1928,6 @@ char *rtc_str(char *buf, char *end, const struct rtc_time *tm, bool found = true; int count = 2; - if (check_pointer(&buf, end, tm, spec)) - return buf; - switch (fmt[count]) { case 'd': have_t = false; @@ -1996,6 +1993,9 @@ static noinline_for_stack char *time_and_date(char *buf, char *end, void *ptr, struct printf_spec spec, const char *fmt) { + if (check_pointer(&buf, end, ptr, spec)) + return buf; + switch (fmt[1]) { case 'R': return rtc_str(buf, end, (const struct rtc_time *)ptr, spec, fmt); From e5c52c320577cd405b251943ef77842dc6f303bf Mon Sep 17 00:00:00 2001 From: Dmitry Antipov Date: Thu, 9 Oct 2025 13:23:49 +0300 Subject: [PATCH 0629/3902] ocfs2: relax BUG() to ocfs2_error() in __ocfs2_move_extent() [ Upstream commit 8a7d58845fae061c62b50bc5eeb9bae4a1dedc3d ] In '__ocfs2_move_extent()', relax 'BUG()' to 'ocfs2_error()' just to avoid crashing the whole kernel due to a filesystem corruption. Fixes: 8f603e567aa7 ("Ocfs2/move_extents: move a range of extent.") Link: https://lkml.kernel.org/r/20251009102349.181126-2-dmantipov@yandex.ru Signed-off-by: Dmitry Antipov Closes: https://syzkaller.appspot.com/bug?extid=727d161855d11d81e411 Reported-by: syzbot+727d161855d11d81e411@syzkaller.appspotmail.com Reviewed-by: Joseph Qi Cc: Mark Fasheh Cc: Joel Becker Cc: Junxiao Bi Cc: Changwei Ge Cc: Jun Piao Signed-off-by: Andrew Morton Signed-off-by: Sasha Levin --- fs/ocfs2/move_extents.c | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/fs/ocfs2/move_extents.c b/fs/ocfs2/move_extents.c index 10923bf7c8b841..26e150c5f25eb0 100644 --- a/fs/ocfs2/move_extents.c +++ b/fs/ocfs2/move_extents.c @@ -98,7 +98,13 @@ static int __ocfs2_move_extent(handle_t *handle, rec = &el->l_recs[index]; - BUG_ON(ext_flags != rec->e_flags); + if (ext_flags != rec->e_flags) { + ret = ocfs2_error(inode->i_sb, + "Inode %llu has corrupted extent %d with flags 0x%x at cpos %u\n", + (unsigned long long)ino, index, rec->e_flags, cpos); + goto out; + } + /* * after moving/defraging to new location, the extent is not going * to be refcounted anymore. From d7ea2b4d0b703207bf09c9a912530b2edfb67bf7 Mon Sep 17 00:00:00 2001 From: Joseph Qi Date: Sat, 25 Oct 2025 20:32:17 +0800 Subject: [PATCH 0630/3902] ocfs2: use correct endian in ocfs2_dinode_has_extents [ Upstream commit c9dff86eb78a4b6b02b1e407993c946ccaf9bfb4 ] Fields in ocfs2_dinode is little endian, covert to host endian when checking those contents. Link: https://lkml.kernel.org/r/20251025123218.3997866-1-joseph.qi@linux.alibaba.com Fixes: fdbb6cd96ed5 ("ocfs2: correct l_next_free_rec in online check") Signed-off-by: Joseph Qi Reviewed-by: Heming Zhao Cc: Mark Fasheh Cc: Joel Becker Cc: Junxiao Bi Cc: Changwei Ge Cc: Jun Piao Signed-off-by: Andrew Morton Signed-off-by: Sasha Levin --- fs/ocfs2/inode.c | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/fs/ocfs2/inode.c b/fs/ocfs2/inode.c index fcc89856ab95b6..0a0a96054bfecb 100644 --- a/fs/ocfs2/inode.c +++ b/fs/ocfs2/inode.c @@ -201,13 +201,15 @@ struct inode *ocfs2_iget(struct ocfs2_super *osb, u64 blkno, unsigned flags, static int ocfs2_dinode_has_extents(struct ocfs2_dinode *di) { /* inodes flagged with other stuff in id2 */ - if (di->i_flags & (OCFS2_SUPER_BLOCK_FL | OCFS2_LOCAL_ALLOC_FL | - OCFS2_CHAIN_FL | OCFS2_DEALLOC_FL)) + if (le32_to_cpu(di->i_flags) & + (OCFS2_SUPER_BLOCK_FL | OCFS2_LOCAL_ALLOC_FL | OCFS2_CHAIN_FL | + OCFS2_DEALLOC_FL)) return 0; /* i_flags doesn't indicate when id2 is a fast symlink */ - if (S_ISLNK(di->i_mode) && di->i_size && di->i_clusters == 0) + if (S_ISLNK(le16_to_cpu(di->i_mode)) && le64_to_cpu(di->i_size) && + !le32_to_cpu(di->i_clusters)) return 0; - if (di->i_dyn_features & OCFS2_INLINE_DATA_FL) + if (le16_to_cpu(di->i_dyn_features) & OCFS2_INLINE_DATA_FL) return 0; return 1; From b47b1e1889c8bb33282160e7e17c8474cee0d6d2 Mon Sep 17 00:00:00 2001 From: Haotian Zhang Date: Tue, 11 Nov 2025 15:50:00 +0800 Subject: [PATCH 0631/3902] ACPI: property: Fix fwnode refcount leak in acpi_fwnode_graph_parse_endpoint() [ Upstream commit 593ee49222a0d751062fd9a5e4a963ade4ec028a ] acpi_fwnode_graph_parse_endpoint() calls fwnode_get_parent() to obtain the parent fwnode but returns without calling fwnode_handle_put() on it. This potentially leads to a fwnode refcount leak and prevents the parent node from being released properly. Call fwnode_handle_put() on the parent fwnode before returning to prevent the leak from occurring. Fixes: 3b27d00e7b6d ("device property: Move fwnode graph ops to firmware specific locations") Signed-off-by: Haotian Zhang Reviewed-by: Sakari Ailus [ rjw: Changelog edits ] Link: https://patch.msgid.link/20251111075000.1828-1-vulab@iscas.ac.cn Signed-off-by: Rafael J. Wysocki Signed-off-by: Sasha Levin --- drivers/acpi/property.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/acpi/property.c b/drivers/acpi/property.c index 43d5e457814e17..b12057baaae7b6 100644 --- a/drivers/acpi/property.c +++ b/drivers/acpi/property.c @@ -1714,6 +1714,7 @@ static int acpi_fwnode_graph_parse_endpoint(const struct fwnode_handle *fwnode, if (fwnode_property_read_u32(fwnode, "reg", &endpoint->id)) fwnode_property_read_u32(fwnode, "endpoint", &endpoint->id); + fwnode_handle_put(port_fwnode); return 0; } From 5c1fb3fd05da3d55b8cbc42d7d660b313cbdc936 Mon Sep 17 00:00:00 2001 From: Tony Battersby Date: Mon, 10 Nov 2025 11:07:37 -0500 Subject: [PATCH 0632/3902] scsi: qla2xxx: Clear cmds after chip reset [ Upstream commit d46c69a087aa3d1513f7a78f871b80251ea0c1ae ] Commit aefed3e5548f ("scsi: qla2xxx: target: Fix offline port handling and host reset handling") caused two problems: 1. Commands sent to FW, after chip reset got stuck and never freed as FW is not going to respond to them anymore. 2. BUG_ON(cmd->sg_mapped) in qlt_free_cmd(). Commit 26f9ce53817a ("scsi: qla2xxx: Fix missed DMA unmap for aborted commands") attempted to fix this, but introduced another bug under different circumstances when two different CPUs were racing to call qlt_unmap_sg() at the same time: BUG_ON(!valid_dma_direction(dir)) in dma_unmap_sg_attrs(). So revert "scsi: qla2xxx: Fix missed DMA unmap for aborted commands" and partially revert "scsi: qla2xxx: target: Fix offline port handling and host reset handling" at __qla2x00_abort_all_cmds. Fixes: aefed3e5548f ("scsi: qla2xxx: target: Fix offline port handling and host reset handling") Fixes: 26f9ce53817a ("scsi: qla2xxx: Fix missed DMA unmap for aborted commands") Co-developed-by: Dmitry Bogdanov Signed-off-by: Dmitry Bogdanov Signed-off-by: Tony Battersby Link: https://patch.msgid.link/0e7e5d26-e7a0-42d1-8235-40eeb27f3e98@cybernetics.com Signed-off-by: Martin K. Petersen Signed-off-by: Sasha Levin --- drivers/scsi/qla2xxx/qla_os.c | 20 ++++++++++++++++++-- drivers/scsi/qla2xxx/qla_target.c | 5 +---- drivers/scsi/qla2xxx/qla_target.h | 1 + 3 files changed, 20 insertions(+), 6 deletions(-) diff --git a/drivers/scsi/qla2xxx/qla_os.c b/drivers/scsi/qla2xxx/qla_os.c index 5ffd9458665274..70a579cf9c3fd5 100644 --- a/drivers/scsi/qla2xxx/qla_os.c +++ b/drivers/scsi/qla2xxx/qla_os.c @@ -1881,10 +1881,26 @@ __qla2x00_abort_all_cmds(struct qla_qpair *qp, int res) continue; } cmd = (struct qla_tgt_cmd *)sp; - cmd->aborted = 1; + + if (cmd->sg_mapped) + qlt_unmap_sg(vha, cmd); + + if (cmd->state == QLA_TGT_STATE_NEED_DATA) { + cmd->aborted = 1; + cmd->write_data_transferred = 0; + cmd->state = QLA_TGT_STATE_DATA_IN; + ha->tgt.tgt_ops->handle_data(cmd); + } else { + ha->tgt.tgt_ops->free_cmd(cmd); + } break; case TYPE_TGT_TMCMD: - /* Skip task management functions. */ + /* + * Currently, only ABTS response gets on the + * outstanding_cmds[] + */ + ha->tgt.tgt_ops->free_mcmd( + (struct qla_tgt_mgmt_cmd *) sp); break; default: break; diff --git a/drivers/scsi/qla2xxx/qla_target.c b/drivers/scsi/qla2xxx/qla_target.c index 1e81582085e384..4c6aff59fe3fb9 100644 --- a/drivers/scsi/qla2xxx/qla_target.c +++ b/drivers/scsi/qla2xxx/qla_target.c @@ -2443,7 +2443,7 @@ static int qlt_pci_map_calc_cnt(struct qla_tgt_prm *prm) return -1; } -static void qlt_unmap_sg(struct scsi_qla_host *vha, struct qla_tgt_cmd *cmd) +void qlt_unmap_sg(struct scsi_qla_host *vha, struct qla_tgt_cmd *cmd) { struct qla_hw_data *ha; struct qla_qpair *qpair; @@ -3773,9 +3773,6 @@ int qlt_abort_cmd(struct qla_tgt_cmd *cmd) spin_lock_irqsave(&cmd->cmd_lock, flags); if (cmd->aborted) { - if (cmd->sg_mapped) - qlt_unmap_sg(vha, cmd); - spin_unlock_irqrestore(&cmd->cmd_lock, flags); /* * It's normal to see 2 calls in this path: diff --git a/drivers/scsi/qla2xxx/qla_target.h b/drivers/scsi/qla2xxx/qla_target.h index 15a59c125c5324..c483966d0a8474 100644 --- a/drivers/scsi/qla2xxx/qla_target.h +++ b/drivers/scsi/qla2xxx/qla_target.h @@ -1058,6 +1058,7 @@ extern int qlt_abort_cmd(struct qla_tgt_cmd *); extern void qlt_xmit_tm_rsp(struct qla_tgt_mgmt_cmd *); extern void qlt_free_mcmd(struct qla_tgt_mgmt_cmd *); extern void qlt_free_cmd(struct qla_tgt_cmd *cmd); +extern void qlt_unmap_sg(struct scsi_qla_host *vha, struct qla_tgt_cmd *cmd); extern void qlt_async_event(uint16_t, struct scsi_qla_host *, uint16_t *); extern void qlt_enable_vha(struct scsi_qla_host *); extern void qlt_vport_create(struct scsi_qla_host *, struct qla_hw_data *); From 54a7e83112742d75c19226665a729113f16165ae Mon Sep 17 00:00:00 2001 From: Haotian Zhang Date: Wed, 29 Oct 2025 11:25:55 +0800 Subject: [PATCH 0633/3902] scsi: sim710: Fix resource leak by adding missing ioport_unmap() calls [ Upstream commit acd194d9b5bac419e04968ffa44351afabb50bac ] The driver calls ioport_map() to map I/O ports in sim710_probe_common() but never calls ioport_unmap() to release the mapping. This causes resource leaks in both the error path when request_irq() fails and in the normal device removal path via sim710_device_remove(). Add ioport_unmap() calls in the out_release error path and in sim710_device_remove(). Fixes: 56fece20086e ("[PATCH] finally fix 53c700 to use the generic iomem infrastructure") Signed-off-by: Haotian Zhang Link: https://patch.msgid.link/20251029032555.1476-1-vulab@iscas.ac.cn Signed-off-by: Martin K. Petersen Signed-off-by: Sasha Levin --- drivers/scsi/sim710.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/drivers/scsi/sim710.c b/drivers/scsi/sim710.c index e519df68d603d5..70c75ab1453a16 100644 --- a/drivers/scsi/sim710.c +++ b/drivers/scsi/sim710.c @@ -133,6 +133,7 @@ static int sim710_probe_common(struct device *dev, unsigned long base_addr, out_put_host: scsi_host_put(host); out_release: + ioport_unmap(hostdata->base); release_region(base_addr, 64); out_free: kfree(hostdata); @@ -148,6 +149,7 @@ static int sim710_device_remove(struct device *dev) scsi_remove_host(host); NCR_700_release(host); + ioport_unmap(hostdata->base); kfree(hostdata); free_irq(host->irq, host); release_region(host->base, 64); From 659dd9c8cd59d09481c3be17607171285c63dfee Mon Sep 17 00:00:00 2001 From: Haotian Zhang Date: Fri, 31 Oct 2025 10:16:20 +0800 Subject: [PATCH 0634/3902] leds: netxbig: Fix GPIO descriptor leak in error paths [ Upstream commit 03865dd8af52eb16c38062df2ed30a91b604780e ] The function netxbig_gpio_ext_get() acquires GPIO descriptors but fails to release them when errors occur mid-way through initialization. The cleanup callback registered by devm_add_action_or_reset() only runs on success, leaving acquired GPIOs leaked on error paths. Add goto-based error handling to release all acquired GPIOs before returning errors. Fixes: 9af512e81964 ("leds: netxbig: Convert to use GPIO descriptors") Suggested-by: Markus Elfring Signed-off-by: Haotian Zhang Link: https://patch.msgid.link/20251031021620.781-1-vulab@iscas.ac.cn Signed-off-by: Lee Jones Signed-off-by: Sasha Levin --- drivers/leds/leds-netxbig.c | 36 ++++++++++++++++++++++++++---------- 1 file changed, 26 insertions(+), 10 deletions(-) diff --git a/drivers/leds/leds-netxbig.c b/drivers/leds/leds-netxbig.c index e95287416ef879..99df46f2d9f528 100644 --- a/drivers/leds/leds-netxbig.c +++ b/drivers/leds/leds-netxbig.c @@ -364,6 +364,9 @@ static int netxbig_gpio_ext_get(struct device *dev, if (!addr) return -ENOMEM; + gpio_ext->addr = addr; + gpio_ext->num_addr = 0; + /* * We cannot use devm_ managed resources with these GPIO descriptors * since they are associated with the "GPIO extension device" which @@ -375,45 +378,58 @@ static int netxbig_gpio_ext_get(struct device *dev, gpiod = gpiod_get_index(gpio_ext_dev, "addr", i, GPIOD_OUT_LOW); if (IS_ERR(gpiod)) - return PTR_ERR(gpiod); + goto err_set_code; gpiod_set_consumer_name(gpiod, "GPIO extension addr"); addr[i] = gpiod; + gpio_ext->num_addr++; } - gpio_ext->addr = addr; - gpio_ext->num_addr = num_addr; ret = gpiod_count(gpio_ext_dev, "data"); if (ret < 0) { dev_err(dev, "Failed to count GPIOs in DT property data-gpios\n"); - return ret; + goto err_free_addr; } num_data = ret; data = devm_kcalloc(dev, num_data, sizeof(*data), GFP_KERNEL); - if (!data) - return -ENOMEM; + if (!data) { + ret = -ENOMEM; + goto err_free_addr; + } + + gpio_ext->data = data; + gpio_ext->num_data = 0; for (i = 0; i < num_data; i++) { gpiod = gpiod_get_index(gpio_ext_dev, "data", i, GPIOD_OUT_LOW); if (IS_ERR(gpiod)) - return PTR_ERR(gpiod); + goto err_free_data; gpiod_set_consumer_name(gpiod, "GPIO extension data"); data[i] = gpiod; + gpio_ext->num_data++; } - gpio_ext->data = data; - gpio_ext->num_data = num_data; gpiod = gpiod_get(gpio_ext_dev, "enable", GPIOD_OUT_LOW); if (IS_ERR(gpiod)) { dev_err(dev, "Failed to get GPIO from DT property enable-gpio\n"); - return PTR_ERR(gpiod); + goto err_free_data; } gpiod_set_consumer_name(gpiod, "GPIO extension enable"); gpio_ext->enable = gpiod; return devm_add_action_or_reset(dev, netxbig_gpio_ext_remove, gpio_ext); + +err_free_data: + for (i = 0; i < gpio_ext->num_data; i++) + gpiod_put(gpio_ext->data[i]); +err_set_code: + ret = PTR_ERR(gpiod); +err_free_addr: + for (i = 0; i < gpio_ext->num_addr; i++) + gpiod_put(gpio_ext->addr[i]); + return ret; } static int netxbig_leds_get_of_pdata(struct device *dev, From 8e2121a40c39e26e8ba8cffbe30a00f607379b65 Mon Sep 17 00:00:00 2001 From: Chaitanya S Prakash Date: Fri, 17 Oct 2025 10:44:36 +0530 Subject: [PATCH 0635/3902] arm64/mm: Allow __create_pgd_mapping() to propagate pgtable_alloc() errors [ Upstream commit bfc184cb1ba7226b21ab26f0b220581895c5ac9e ] arch_add_memory() is used to hotplug memory into a system but as a part of its implementation it calls __create_pgd_mapping(), which uses pgtable_alloc() in order to build intermediate page tables. As this path was initally only used during early boot pgtable_alloc() is designed to BUG_ON() on failure. However, in the event that memory hotplug is attempted when the system's memory is extremely tight and the allocation were to fail, it would lead to panicking the system, which is not desirable. Hence update __create_pgd_mapping and all it's callers to be non void and propagate -ENOMEM on allocation failure to allow system to fail gracefully. But during early boot if there is an allocation failure, we want the system to panic, hence create a wrapper around __create_pgd_mapping() called early_create_pgd_mapping() which is designed to panic, if ret is non zero value. All the init calls are updated to use this wrapper rather than the modified __create_pgd_mapping() to restore functionality. Fixes: 4ab215061554 ("arm64: Add memory hotplug support") Reviewed-by: Dev Jain Reviewed-by: Ryan Roberts Reviewed-by: Kevin Brodsky Signed-off-by: Chaitanya S Prakash Signed-off-by: Linu Cherian Reviewed-by: Anshuman Khandual Signed-off-by: Catalin Marinas Signed-off-by: Sasha Levin --- arch/arm64/mm/mmu.c | 214 ++++++++++++++++++++++++++++---------------- 1 file changed, 136 insertions(+), 78 deletions(-) diff --git a/arch/arm64/mm/mmu.c b/arch/arm64/mm/mmu.c index 2ba01dc8ef822a..aeb6fb25a951a1 100644 --- a/arch/arm64/mm/mmu.c +++ b/arch/arm64/mm/mmu.c @@ -49,6 +49,8 @@ #define NO_CONT_MAPPINGS BIT(1) #define NO_EXEC_MAPPINGS BIT(2) /* assumes FEAT_HPDS is not used */ +#define INVALID_PHYS_ADDR (-1ULL) + DEFINE_STATIC_KEY_FALSE(arm64_ptdump_lock_key); u64 kimage_voffset __ro_after_init; @@ -194,11 +196,11 @@ static void init_pte(pte_t *ptep, unsigned long addr, unsigned long end, } while (ptep++, addr += PAGE_SIZE, addr != end); } -static void alloc_init_cont_pte(pmd_t *pmdp, unsigned long addr, - unsigned long end, phys_addr_t phys, - pgprot_t prot, - phys_addr_t (*pgtable_alloc)(enum pgtable_type), - int flags) +static int alloc_init_cont_pte(pmd_t *pmdp, unsigned long addr, + unsigned long end, phys_addr_t phys, + pgprot_t prot, + phys_addr_t (*pgtable_alloc)(enum pgtable_type), + int flags) { unsigned long next; pmd_t pmd = READ_ONCE(*pmdp); @@ -213,6 +215,8 @@ static void alloc_init_cont_pte(pmd_t *pmdp, unsigned long addr, pmdval |= PMD_TABLE_PXN; BUG_ON(!pgtable_alloc); pte_phys = pgtable_alloc(TABLE_PTE); + if (pte_phys == INVALID_PHYS_ADDR) + return -ENOMEM; ptep = pte_set_fixmap(pte_phys); init_clear_pgtable(ptep); ptep += pte_index(addr); @@ -244,11 +248,13 @@ static void alloc_init_cont_pte(pmd_t *pmdp, unsigned long addr, * walker. */ pte_clear_fixmap(); + + return 0; } -static void init_pmd(pmd_t *pmdp, unsigned long addr, unsigned long end, - phys_addr_t phys, pgprot_t prot, - phys_addr_t (*pgtable_alloc)(enum pgtable_type), int flags) +static int init_pmd(pmd_t *pmdp, unsigned long addr, unsigned long end, + phys_addr_t phys, pgprot_t prot, + phys_addr_t (*pgtable_alloc)(enum pgtable_type), int flags) { unsigned long next; @@ -269,22 +275,29 @@ static void init_pmd(pmd_t *pmdp, unsigned long addr, unsigned long end, BUG_ON(!pgattr_change_is_safe(pmd_val(old_pmd), READ_ONCE(pmd_val(*pmdp)))); } else { - alloc_init_cont_pte(pmdp, addr, next, phys, prot, - pgtable_alloc, flags); + int ret; + + ret = alloc_init_cont_pte(pmdp, addr, next, phys, prot, + pgtable_alloc, flags); + if (ret) + return ret; BUG_ON(pmd_val(old_pmd) != 0 && pmd_val(old_pmd) != READ_ONCE(pmd_val(*pmdp))); } phys += next - addr; } while (pmdp++, addr = next, addr != end); + + return 0; } -static void alloc_init_cont_pmd(pud_t *pudp, unsigned long addr, - unsigned long end, phys_addr_t phys, - pgprot_t prot, - phys_addr_t (*pgtable_alloc)(enum pgtable_type), - int flags) +static int alloc_init_cont_pmd(pud_t *pudp, unsigned long addr, + unsigned long end, phys_addr_t phys, + pgprot_t prot, + phys_addr_t (*pgtable_alloc)(enum pgtable_type), + int flags) { + int ret; unsigned long next; pud_t pud = READ_ONCE(*pudp); pmd_t *pmdp; @@ -301,6 +314,8 @@ static void alloc_init_cont_pmd(pud_t *pudp, unsigned long addr, pudval |= PUD_TABLE_PXN; BUG_ON(!pgtable_alloc); pmd_phys = pgtable_alloc(TABLE_PMD); + if (pmd_phys == INVALID_PHYS_ADDR) + return -ENOMEM; pmdp = pmd_set_fixmap(pmd_phys); init_clear_pgtable(pmdp); pmdp += pmd_index(addr); @@ -320,20 +335,26 @@ static void alloc_init_cont_pmd(pud_t *pudp, unsigned long addr, (flags & NO_CONT_MAPPINGS) == 0) __prot = __pgprot(pgprot_val(prot) | PTE_CONT); - init_pmd(pmdp, addr, next, phys, __prot, pgtable_alloc, flags); + ret = init_pmd(pmdp, addr, next, phys, __prot, pgtable_alloc, flags); + if (ret) + goto out; pmdp += pmd_index(next) - pmd_index(addr); phys += next - addr; } while (addr = next, addr != end); +out: pmd_clear_fixmap(); + + return ret; } -static void alloc_init_pud(p4d_t *p4dp, unsigned long addr, unsigned long end, - phys_addr_t phys, pgprot_t prot, - phys_addr_t (*pgtable_alloc)(enum pgtable_type), - int flags) +static int alloc_init_pud(p4d_t *p4dp, unsigned long addr, unsigned long end, + phys_addr_t phys, pgprot_t prot, + phys_addr_t (*pgtable_alloc)(enum pgtable_type), + int flags) { + int ret = 0; unsigned long next; p4d_t p4d = READ_ONCE(*p4dp); pud_t *pudp; @@ -346,6 +367,8 @@ static void alloc_init_pud(p4d_t *p4dp, unsigned long addr, unsigned long end, p4dval |= P4D_TABLE_PXN; BUG_ON(!pgtable_alloc); pud_phys = pgtable_alloc(TABLE_PUD); + if (pud_phys == INVALID_PHYS_ADDR) + return -ENOMEM; pudp = pud_set_fixmap(pud_phys); init_clear_pgtable(pudp); pudp += pud_index(addr); @@ -375,8 +398,10 @@ static void alloc_init_pud(p4d_t *p4dp, unsigned long addr, unsigned long end, BUG_ON(!pgattr_change_is_safe(pud_val(old_pud), READ_ONCE(pud_val(*pudp)))); } else { - alloc_init_cont_pmd(pudp, addr, next, phys, prot, - pgtable_alloc, flags); + ret = alloc_init_cont_pmd(pudp, addr, next, phys, prot, + pgtable_alloc, flags); + if (ret) + goto out; BUG_ON(pud_val(old_pud) != 0 && pud_val(old_pud) != READ_ONCE(pud_val(*pudp))); @@ -384,14 +409,18 @@ static void alloc_init_pud(p4d_t *p4dp, unsigned long addr, unsigned long end, phys += next - addr; } while (pudp++, addr = next, addr != end); +out: pud_clear_fixmap(); + + return ret; } -static void alloc_init_p4d(pgd_t *pgdp, unsigned long addr, unsigned long end, - phys_addr_t phys, pgprot_t prot, - phys_addr_t (*pgtable_alloc)(enum pgtable_type), - int flags) +static int alloc_init_p4d(pgd_t *pgdp, unsigned long addr, unsigned long end, + phys_addr_t phys, pgprot_t prot, + phys_addr_t (*pgtable_alloc)(enum pgtable_type), + int flags) { + int ret; unsigned long next; pgd_t pgd = READ_ONCE(*pgdp); p4d_t *p4dp; @@ -404,6 +433,8 @@ static void alloc_init_p4d(pgd_t *pgdp, unsigned long addr, unsigned long end, pgdval |= PGD_TABLE_PXN; BUG_ON(!pgtable_alloc); p4d_phys = pgtable_alloc(TABLE_P4D); + if (p4d_phys == INVALID_PHYS_ADDR) + return -ENOMEM; p4dp = p4d_set_fixmap(p4d_phys); init_clear_pgtable(p4dp); p4dp += p4d_index(addr); @@ -418,8 +449,10 @@ static void alloc_init_p4d(pgd_t *pgdp, unsigned long addr, unsigned long end, next = p4d_addr_end(addr, end); - alloc_init_pud(p4dp, addr, next, phys, prot, - pgtable_alloc, flags); + ret = alloc_init_pud(p4dp, addr, next, phys, prot, + pgtable_alloc, flags); + if (ret) + goto out; BUG_ON(p4d_val(old_p4d) != 0 && p4d_val(old_p4d) != READ_ONCE(p4d_val(*p4dp))); @@ -427,15 +460,19 @@ static void alloc_init_p4d(pgd_t *pgdp, unsigned long addr, unsigned long end, phys += next - addr; } while (p4dp++, addr = next, addr != end); +out: p4d_clear_fixmap(); + + return ret; } -static void __create_pgd_mapping_locked(pgd_t *pgdir, phys_addr_t phys, - unsigned long virt, phys_addr_t size, - pgprot_t prot, - phys_addr_t (*pgtable_alloc)(enum pgtable_type), - int flags) +static int __create_pgd_mapping_locked(pgd_t *pgdir, phys_addr_t phys, + unsigned long virt, phys_addr_t size, + pgprot_t prot, + phys_addr_t (*pgtable_alloc)(enum pgtable_type), + int flags) { + int ret; unsigned long addr, end, next; pgd_t *pgdp = pgd_offset_pgd(pgdir, virt); @@ -444,7 +481,7 @@ static void __create_pgd_mapping_locked(pgd_t *pgdir, phys_addr_t phys, * within a page, we cannot map the region as the caller expects. */ if (WARN_ON((phys ^ virt) & ~PAGE_MASK)) - return; + return -EINVAL; phys &= PAGE_MASK; addr = virt & PAGE_MASK; @@ -452,25 +489,45 @@ static void __create_pgd_mapping_locked(pgd_t *pgdir, phys_addr_t phys, do { next = pgd_addr_end(addr, end); - alloc_init_p4d(pgdp, addr, next, phys, prot, pgtable_alloc, - flags); + ret = alloc_init_p4d(pgdp, addr, next, phys, prot, pgtable_alloc, + flags); + if (ret) + return ret; phys += next - addr; } while (pgdp++, addr = next, addr != end); + + return 0; } -static void __create_pgd_mapping(pgd_t *pgdir, phys_addr_t phys, - unsigned long virt, phys_addr_t size, - pgprot_t prot, - phys_addr_t (*pgtable_alloc)(enum pgtable_type), - int flags) +static int __create_pgd_mapping(pgd_t *pgdir, phys_addr_t phys, + unsigned long virt, phys_addr_t size, + pgprot_t prot, + phys_addr_t (*pgtable_alloc)(enum pgtable_type), + int flags) { + int ret; + mutex_lock(&fixmap_lock); - __create_pgd_mapping_locked(pgdir, phys, virt, size, prot, - pgtable_alloc, flags); + ret = __create_pgd_mapping_locked(pgdir, phys, virt, size, prot, + pgtable_alloc, flags); mutex_unlock(&fixmap_lock); + + return ret; } -#define INVALID_PHYS_ADDR (-1ULL) +static void early_create_pgd_mapping(pgd_t *pgdir, phys_addr_t phys, + unsigned long virt, phys_addr_t size, + pgprot_t prot, + phys_addr_t (*pgtable_alloc)(enum pgtable_type), + int flags) +{ + int ret; + + ret = __create_pgd_mapping(pgdir, phys, virt, size, prot, pgtable_alloc, + flags); + if (ret) + panic("Failed to create page tables\n"); +} static phys_addr_t __pgd_pgtable_alloc(struct mm_struct *mm, gfp_t gfp, enum pgtable_type pgtable_type) @@ -511,21 +568,13 @@ try_pgd_pgtable_alloc_init_mm(enum pgtable_type pgtable_type, gfp_t gfp) static phys_addr_t __maybe_unused pgd_pgtable_alloc_init_mm(enum pgtable_type pgtable_type) { - phys_addr_t pa; - - pa = __pgd_pgtable_alloc(&init_mm, GFP_PGTABLE_KERNEL, pgtable_type); - BUG_ON(pa == INVALID_PHYS_ADDR); - return pa; + return __pgd_pgtable_alloc(&init_mm, GFP_PGTABLE_KERNEL, pgtable_type); } static phys_addr_t pgd_pgtable_alloc_special_mm(enum pgtable_type pgtable_type) { - phys_addr_t pa; - - pa = __pgd_pgtable_alloc(NULL, GFP_PGTABLE_KERNEL, pgtable_type); - BUG_ON(pa == INVALID_PHYS_ADDR); - return pa; + return __pgd_pgtable_alloc(NULL, GFP_PGTABLE_KERNEL, pgtable_type); } static void split_contpte(pte_t *ptep) @@ -935,8 +984,8 @@ void __init create_mapping_noalloc(phys_addr_t phys, unsigned long virt, &phys, virt); return; } - __create_pgd_mapping(init_mm.pgd, phys, virt, size, prot, NULL, - NO_CONT_MAPPINGS); + early_create_pgd_mapping(init_mm.pgd, phys, virt, size, prot, NULL, + NO_CONT_MAPPINGS); } void __init create_pgd_mapping(struct mm_struct *mm, phys_addr_t phys, @@ -950,8 +999,8 @@ void __init create_pgd_mapping(struct mm_struct *mm, phys_addr_t phys, if (page_mappings_only) flags = NO_BLOCK_MAPPINGS | NO_CONT_MAPPINGS; - __create_pgd_mapping(mm->pgd, phys, virt, size, prot, - pgd_pgtable_alloc_special_mm, flags); + early_create_pgd_mapping(mm->pgd, phys, virt, size, prot, + pgd_pgtable_alloc_special_mm, flags); } static void update_mapping_prot(phys_addr_t phys, unsigned long virt, @@ -963,8 +1012,8 @@ static void update_mapping_prot(phys_addr_t phys, unsigned long virt, return; } - __create_pgd_mapping(init_mm.pgd, phys, virt, size, prot, NULL, - NO_CONT_MAPPINGS); + early_create_pgd_mapping(init_mm.pgd, phys, virt, size, prot, NULL, + NO_CONT_MAPPINGS); /* flush the TLBs after updating live kernel mappings */ flush_tlb_kernel_range(virt, virt + size); @@ -973,8 +1022,8 @@ static void update_mapping_prot(phys_addr_t phys, unsigned long virt, static void __init __map_memblock(pgd_t *pgdp, phys_addr_t start, phys_addr_t end, pgprot_t prot, int flags) { - __create_pgd_mapping(pgdp, start, __phys_to_virt(start), end - start, - prot, early_pgtable_alloc, flags); + early_create_pgd_mapping(pgdp, start, __phys_to_virt(start), end - start, + prot, early_pgtable_alloc, flags); } void __init mark_linear_text_alias_ro(void) @@ -1207,6 +1256,8 @@ static int __init __kpti_install_ng_mappings(void *__unused) remap_fn = (void *)__pa_symbol(idmap_kpti_install_ng_mappings); if (!cpu) { + int ret; + alloc = __get_free_pages(GFP_ATOMIC | __GFP_ZERO, order); kpti_ng_temp_pgd = (pgd_t *)(alloc + (levels - 1) * PAGE_SIZE); kpti_ng_temp_alloc = kpti_ng_temp_pgd_pa = __pa(kpti_ng_temp_pgd); @@ -1227,9 +1278,11 @@ static int __init __kpti_install_ng_mappings(void *__unused) // covers the PTE[] page itself, the remaining entries are free // to be used as a ad-hoc fixmap. // - __create_pgd_mapping_locked(kpti_ng_temp_pgd, __pa(alloc), - KPTI_NG_TEMP_VA, PAGE_SIZE, PAGE_KERNEL, - kpti_ng_pgd_alloc, 0); + ret = __create_pgd_mapping_locked(kpti_ng_temp_pgd, __pa(alloc), + KPTI_NG_TEMP_VA, PAGE_SIZE, PAGE_KERNEL, + kpti_ng_pgd_alloc, 0); + if (ret) + panic("Failed to create page tables\n"); } cpu_install_idmap(); @@ -1282,9 +1335,9 @@ static int __init map_entry_trampoline(void) /* Map only the text into the trampoline page table */ memset(tramp_pg_dir, 0, PGD_SIZE); - __create_pgd_mapping(tramp_pg_dir, pa_start, TRAMP_VALIAS, - entry_tramp_text_size(), prot, - pgd_pgtable_alloc_init_mm, NO_BLOCK_MAPPINGS); + early_create_pgd_mapping(tramp_pg_dir, pa_start, TRAMP_VALIAS, + entry_tramp_text_size(), prot, + pgd_pgtable_alloc_init_mm, NO_BLOCK_MAPPINGS); /* Map both the text and data into the kernel page table */ for (i = 0; i < DIV_ROUND_UP(entry_tramp_text_size(), PAGE_SIZE); i++) @@ -1926,23 +1979,28 @@ int arch_add_memory(int nid, u64 start, u64 size, if (force_pte_mapping()) flags |= NO_BLOCK_MAPPINGS | NO_CONT_MAPPINGS; - __create_pgd_mapping(swapper_pg_dir, start, __phys_to_virt(start), - size, params->pgprot, pgd_pgtable_alloc_init_mm, - flags); + ret = __create_pgd_mapping(swapper_pg_dir, start, __phys_to_virt(start), + size, params->pgprot, pgd_pgtable_alloc_init_mm, + flags); + if (ret) + goto err; memblock_clear_nomap(start, size); ret = __add_pages(nid, start >> PAGE_SHIFT, size >> PAGE_SHIFT, params); if (ret) - __remove_pgd_mapping(swapper_pg_dir, - __phys_to_virt(start), size); - else { - /* Address of hotplugged memory can be smaller */ - max_pfn = max(max_pfn, PFN_UP(start + size)); - max_low_pfn = max_pfn; - } + goto err; + + /* Address of hotplugged memory can be smaller */ + max_pfn = max(max_pfn, PFN_UP(start + size)); + max_low_pfn = max_pfn; + + return 0; +err: + __remove_pgd_mapping(swapper_pg_dir, + __phys_to_virt(start), size); return ret; } From 677b91705b351951bb50edee9cdd868757d39b09 Mon Sep 17 00:00:00 2001 From: Lizhi Hou Date: Fri, 7 Nov 2025 10:11:15 -0800 Subject: [PATCH 0636/3902] accel/amdxdna: Clear mailbox interrupt register during channel creation [ Upstream commit 6ff9385c07aa311f01f87307e6256231be7d8675 ] The mailbox interrupt register is not always cleared when a mailbox channel is created. This can leave stale interrupt states from previous operations. Fix this by explicitly clearing the interrupt register in the mailbox channel creation function. Fixes: b87f920b9344 ("accel/amdxdna: Support hardware mailbox") Reviewed-by: Maciej Falkowski Signed-off-by: Lizhi Hou Link: https://patch.msgid.link/20251107181115.1293158-1-lizhi.hou@amd.com Signed-off-by: Sasha Levin --- drivers/accel/amdxdna/amdxdna_mailbox.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/accel/amdxdna/amdxdna_mailbox.c b/drivers/accel/amdxdna/amdxdna_mailbox.c index da1ac89bb78f1f..6634a4d5717ffb 100644 --- a/drivers/accel/amdxdna/amdxdna_mailbox.c +++ b/drivers/accel/amdxdna/amdxdna_mailbox.c @@ -513,6 +513,7 @@ xdna_mailbox_create_channel(struct mailbox *mb, } mb_chann->bad_state = false; + mailbox_reg_write(mb_chann, mb_chann->iohub_int_addr, 0); MB_DBG(mb_chann, "Mailbox channel created (irq: %d)", mb_chann->msix_irq); return mb_chann; From 2f13baf62bbd1b38e82b04f062c86e75ee1155a6 Mon Sep 17 00:00:00 2001 From: Lizhi Hou Date: Fri, 7 Nov 2025 10:10:50 -0800 Subject: [PATCH 0637/3902] accel/amdxdna: Fix deadlock between context destroy and job timeout [ Upstream commit ca2583412306ceda9304a7c4302fd9efbf43e963 ] Hardware context destroy function holds dev_lock while waiting for all jobs to complete. The timeout job also needs to acquire dev_lock, this leads to a deadlock. Fix the issue by temporarily releasing dev_lock before waiting for all jobs to finish, and reacquiring it afterward. Fixes: 4fd6ca90fc7f ("accel/amdxdna: Refactor hardware context destroy routine") Reviewed-by: Maciej Falkowski Signed-off-by: Lizhi Hou Link: https://patch.msgid.link/20251107181050.1293125-1-lizhi.hou@amd.com Signed-off-by: Sasha Levin --- drivers/accel/amdxdna/aie2_ctx.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/drivers/accel/amdxdna/aie2_ctx.c b/drivers/accel/amdxdna/aie2_ctx.c index 2e479a8d86c310..75246c481fa50e 100644 --- a/drivers/accel/amdxdna/aie2_ctx.c +++ b/drivers/accel/amdxdna/aie2_ctx.c @@ -681,17 +681,19 @@ void aie2_hwctx_fini(struct amdxdna_hwctx *hwctx) ndev->hwctx_num--; XDNA_DBG(xdna, "%s sequence number %lld", hwctx->name, hwctx->priv->seq); - drm_sched_entity_destroy(&hwctx->priv->entity); - aie2_hwctx_wait_for_idle(hwctx); /* Request fw to destroy hwctx and cancel the rest pending requests */ aie2_release_resource(hwctx); + mutex_unlock(&xdna->dev_lock); + drm_sched_entity_destroy(&hwctx->priv->entity); + /* Wait for all submitted jobs to be completed or canceled */ wait_event(hwctx->priv->job_free_wq, atomic64_read(&hwctx->job_submit_cnt) == atomic64_read(&hwctx->job_free_cnt)); + mutex_lock(&xdna->dev_lock); drm_sched_fini(&hwctx->priv->sched); aie2_ctx_syncobj_destroy(hwctx); From 4a03d69cece145e4fb527464be29c3806aa3221e Mon Sep 17 00:00:00 2001 From: Leon Hwang Date: Wed, 5 Nov 2025 23:14:06 +0800 Subject: [PATCH 0638/3902] bpf: Free special fields when update [lru_,]percpu_hash maps [ Upstream commit 6af6e49a76c9af7d42eb923703e7648cb2bf401a ] As [lru_,]percpu_hash maps support BPF_KPTR_{REF,PERCPU}, missing calls to 'bpf_obj_free_fields()' in 'pcpu_copy_value()' could cause the memory referenced by BPF_KPTR_{REF,PERCPU} fields to be held until the map gets freed. Fix this by calling 'bpf_obj_free_fields()' after 'copy_map_value[,_long]()' in 'pcpu_copy_value()'. Fixes: 65334e64a493 ("bpf: Support kptrs in percpu hashmap and percpu LRU hashmap") Signed-off-by: Leon Hwang Acked-by: Yonghong Song Link: https://lore.kernel.org/r/20251105151407.12723-2-leon.hwang@linux.dev Signed-off-by: Alexei Starovoitov Signed-off-by: Sasha Levin --- kernel/bpf/hashtab.c | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/kernel/bpf/hashtab.c b/kernel/bpf/hashtab.c index c2fcd0cd51e51b..e7721f0776c722 100644 --- a/kernel/bpf/hashtab.c +++ b/kernel/bpf/hashtab.c @@ -947,15 +947,21 @@ static void free_htab_elem(struct bpf_htab *htab, struct htab_elem *l) static void pcpu_copy_value(struct bpf_htab *htab, void __percpu *pptr, void *value, bool onallcpus) { + void *ptr; + if (!onallcpus) { /* copy true value_size bytes */ - copy_map_value(&htab->map, this_cpu_ptr(pptr), value); + ptr = this_cpu_ptr(pptr); + copy_map_value(&htab->map, ptr, value); + bpf_obj_free_fields(htab->map.record, ptr); } else { u32 size = round_up(htab->map.value_size, 8); int off = 0, cpu; for_each_possible_cpu(cpu) { - copy_map_value_long(&htab->map, per_cpu_ptr(pptr, cpu), value + off); + ptr = per_cpu_ptr(pptr, cpu); + copy_map_value_long(&htab->map, ptr, value + off); + bpf_obj_free_fields(htab->map.record, ptr); off += size; } } From 59a8b59c5a49ea1827be080d5726436949f0cceb Mon Sep 17 00:00:00 2001 From: Siddharth Vadapalli Date: Wed, 29 Oct 2025 13:34:51 +0530 Subject: [PATCH 0639/3902] PCI: keystone: Exit ks_pcie_probe() for invalid mode [ Upstream commit 95d9c3f0e4546eaec0977f3b387549a8463cd49f ] Commit under Fixes introduced support for PCIe EP mode on AM654x platforms. When the mode happens to be either "DW_PCIE_RC_TYPE" or "DW_PCIE_EP_TYPE", the PCIe Controller is configured accordingly. However, when the mode is neither of them, an error message is displayed, but the driver probe succeeds. Since this "invalid" mode is not associated with a functional PCIe Controller, the probe should fail. Fix the behavior by exiting "ks_pcie_probe()" with the return value of "-EINVAL" in addition to displaying the existing error message when the mode is invalid. Fixes: 23284ad677a9 ("PCI: keystone: Add support for PCIe EP in AM654x Platforms") Signed-off-by: Siddharth Vadapalli Signed-off-by: Manivannan Sadhasivam Signed-off-by: Bjorn Helgaas Link: https://patch.msgid.link/20251029080547.1253757-4-s-vadapalli@ti.com Signed-off-by: Sasha Levin --- drivers/pci/controller/dwc/pci-keystone.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/drivers/pci/controller/dwc/pci-keystone.c b/drivers/pci/controller/dwc/pci-keystone.c index eb00aa38072201..25b8193ffbcf12 100644 --- a/drivers/pci/controller/dwc/pci-keystone.c +++ b/drivers/pci/controller/dwc/pci-keystone.c @@ -1337,6 +1337,8 @@ static int ks_pcie_probe(struct platform_device *pdev) break; default: dev_err(dev, "INVALID device type %d\n", mode); + ret = -EINVAL; + goto err_get_sync; } ks_pcie_enable_error_irq(ks_pcie); From 58f2087a7ccd1bcc37439cfef67ced0c09d9322b Mon Sep 17 00:00:00 2001 From: Claudiu Beznea Date: Wed, 5 Nov 2025 09:05:25 +0200 Subject: [PATCH 0640/3902] soc: renesas: r9a09g056-sys: Populate max_register [ Upstream commit 4ff787433ba6d564b00334b4bfd6350f5b6f4bb3 ] Populate max_register to avoid external aborts. Fixes: 2da2740fb9c8 ("soc: renesas: rz-sysc: Add syscon/regmap support") Signed-off-by: Claudiu Beznea Reviewed-by: Geert Uytterhoeven Link: https://patch.msgid.link/20251105070526.264445-2-claudiu.beznea.uj@bp.renesas.com Signed-off-by: Geert Uytterhoeven Signed-off-by: Sasha Levin --- drivers/soc/renesas/r9a09g056-sys.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/soc/renesas/r9a09g056-sys.c b/drivers/soc/renesas/r9a09g056-sys.c index 3ad1422eba36e6..16b4e433c33739 100644 --- a/drivers/soc/renesas/r9a09g056-sys.c +++ b/drivers/soc/renesas/r9a09g056-sys.c @@ -72,4 +72,5 @@ static const struct rz_sysc_soc_id_init_data rzv2n_sys_soc_id_init_data __initco const struct rz_sysc_init_data rzv2n_sys_init_data = { .soc_id_init_data = &rzv2n_sys_soc_id_init_data, + .max_register = 0x170c, }; From ee6dda36b499def7c2158310dc3608fa3dd6bf2e Mon Sep 17 00:00:00 2001 From: Claudiu Beznea Date: Wed, 5 Nov 2025 09:05:26 +0200 Subject: [PATCH 0641/3902] soc: renesas: rz-sysc: Populate readable_reg/writeable_reg in regmap config [ Upstream commit c432180a7d95081353a96fd6d5bd75b0fc8a27c3 ] Not all system controller registers are accessible from Linux. Accessing such registers generates synchronous external abort. Populate the readable_reg and writeable_reg members of the regmap config to inform the regmap core which registers can be accessed. The list will need to be updated whenever new system controller functionality is exported through regmap. Fixes: 2da2740fb9c8 ("soc: renesas: rz-sysc: Add syscon/regmap support") Signed-off-by: Claudiu Beznea Reviewed-by: Geert Uytterhoeven Link: https://patch.msgid.link/20251105070526.264445-3-claudiu.beznea.uj@bp.renesas.com Signed-off-by: Geert Uytterhoeven Signed-off-by: Sasha Levin --- drivers/soc/renesas/r9a08g045-sysc.c | 69 ++++++++++++++++++ drivers/soc/renesas/r9a09g047-sys.c | 79 +++++++++++++++++++++ drivers/soc/renesas/r9a09g056-sys.c | 68 ++++++++++++++++++ drivers/soc/renesas/r9a09g057-sys.c | 101 +++++++++++++++++++++++++++ drivers/soc/renesas/rz-sysc.c | 2 + drivers/soc/renesas/rz-sysc.h | 4 ++ 6 files changed, 323 insertions(+) diff --git a/drivers/soc/renesas/r9a08g045-sysc.c b/drivers/soc/renesas/r9a08g045-sysc.c index 0504d4e6876169..03d653d5cde554 100644 --- a/drivers/soc/renesas/r9a08g045-sysc.c +++ b/drivers/soc/renesas/r9a08g045-sysc.c @@ -6,10 +6,29 @@ */ #include +#include #include #include "rz-sysc.h" +#define SYS_XSPI_MAP_STAADD_CS0 0x348 +#define SYS_XSPI_MAP_ENDADD_CS0 0x34c +#define SYS_XSPI_MAP_STAADD_CS1 0x350 +#define SYS_XSPI_MAP_ENDADD_CS1 0x354 +#define SYS_GETH0_CFG 0x380 +#define SYS_GETH1_CFG 0x390 +#define SYS_PCIE_CFG 0x3a0 +#define SYS_PCIE_MON 0x3a4 +#define SYS_PCIE_ERR_MON 0x3ac +#define SYS_PCIE_PHY 0x3b4 +#define SYS_I2C0_CFG 0x400 +#define SYS_I2C1_CFG 0x410 +#define SYS_I2C2_CFG 0x420 +#define SYS_I2C3_CFG 0x430 +#define SYS_I3C_CFG 0x440 +#define SYS_USB_PWRRDY 0xd70 +#define SYS_PCIE_RST_RSM_B 0xd74 + static const struct rz_sysc_soc_id_init_data rzg3s_sysc_soc_id_init_data __initconst = { .family = "RZ/G3S", .id = 0x85e0447, @@ -18,7 +37,57 @@ static const struct rz_sysc_soc_id_init_data rzg3s_sysc_soc_id_init_data __initc .specific_id_mask = GENMASK(27, 0), }; +static bool rzg3s_regmap_readable_reg(struct device *dev, unsigned int reg) +{ + switch (reg) { + case SYS_XSPI_MAP_STAADD_CS0: + case SYS_XSPI_MAP_ENDADD_CS0: + case SYS_XSPI_MAP_STAADD_CS1: + case SYS_XSPI_MAP_ENDADD_CS1: + case SYS_GETH0_CFG: + case SYS_GETH1_CFG: + case SYS_PCIE_CFG: + case SYS_PCIE_MON: + case SYS_PCIE_ERR_MON: + case SYS_PCIE_PHY: + case SYS_I2C0_CFG: + case SYS_I2C1_CFG: + case SYS_I2C2_CFG: + case SYS_I2C3_CFG: + case SYS_I3C_CFG: + case SYS_USB_PWRRDY: + case SYS_PCIE_RST_RSM_B: + return true; + default: + return false; + } +} + +static bool rzg3s_regmap_writeable_reg(struct device *dev, unsigned int reg) +{ + switch (reg) { + case SYS_XSPI_MAP_STAADD_CS0: + case SYS_XSPI_MAP_ENDADD_CS0: + case SYS_XSPI_MAP_STAADD_CS1: + case SYS_XSPI_MAP_ENDADD_CS1: + case SYS_PCIE_CFG: + case SYS_PCIE_PHY: + case SYS_I2C0_CFG: + case SYS_I2C1_CFG: + case SYS_I2C2_CFG: + case SYS_I2C3_CFG: + case SYS_I3C_CFG: + case SYS_USB_PWRRDY: + case SYS_PCIE_RST_RSM_B: + return true; + default: + return false; + } +} + const struct rz_sysc_init_data rzg3s_sysc_init_data __initconst = { .soc_id_init_data = &rzg3s_sysc_soc_id_init_data, + .readable_reg = rzg3s_regmap_readable_reg, + .writeable_reg = rzg3s_regmap_writeable_reg, .max_register = 0xe20, }; diff --git a/drivers/soc/renesas/r9a09g047-sys.c b/drivers/soc/renesas/r9a09g047-sys.c index 2e8426c0305049..e413b0eff9bfd4 100644 --- a/drivers/soc/renesas/r9a09g047-sys.c +++ b/drivers/soc/renesas/r9a09g047-sys.c @@ -29,6 +29,27 @@ #define SYS_LSI_PRR_CA55_DIS BIT(8) #define SYS_LSI_PRR_NPU_DIS BIT(1) +#define SYS_LSI_OTPTSU1TRMVAL0 0x330 +#define SYS_LSI_OTPTSU1TRMVAL1 0x334 +#define SYS_SPI_STAADDCS0 0x900 +#define SYS_SPI_ENDADDCS0 0x904 +#define SYS_SPI_STAADDCS1 0x908 +#define SYS_SPI_ENDADDCS1 0x90c +#define SYS_VSP_CLK 0xe00 +#define SYS_GBETH0_CFG 0xf00 +#define SYS_GBETH1_CFG 0xf04 +#define SYS_PCIE_INTX_CH0 0x1000 +#define SYS_PCIE_MSI1_CH0 0x1004 +#define SYS_PCIE_MSI2_CH0 0x1008 +#define SYS_PCIE_MSI3_CH0 0x100c +#define SYS_PCIE_MSI4_CH0 0x1010 +#define SYS_PCIE_MSI5_CH0 0x1014 +#define SYS_PCIE_PME_CH0 0x1018 +#define SYS_PCIE_ACK_CH0 0x101c +#define SYS_PCIE_MISC_CH0 0x1020 +#define SYS_PCIE_MODE_CH0 0x1024 +#define SYS_ADC_CFG 0x1600 + static void rzg3e_sys_print_id(struct device *dev, void __iomem *sysc_base, struct soc_device_attribute *soc_dev_attr) @@ -62,7 +83,65 @@ static const struct rz_sysc_soc_id_init_data rzg3e_sys_soc_id_init_data __initco .print_id = rzg3e_sys_print_id, }; +static bool rzg3e_regmap_readable_reg(struct device *dev, unsigned int reg) +{ + switch (reg) { + case SYS_LSI_OTPTSU1TRMVAL0: + case SYS_LSI_OTPTSU1TRMVAL1: + case SYS_SPI_STAADDCS0: + case SYS_SPI_ENDADDCS0: + case SYS_SPI_STAADDCS1: + case SYS_SPI_ENDADDCS1: + case SYS_VSP_CLK: + case SYS_GBETH0_CFG: + case SYS_GBETH1_CFG: + case SYS_PCIE_INTX_CH0: + case SYS_PCIE_MSI1_CH0: + case SYS_PCIE_MSI2_CH0: + case SYS_PCIE_MSI3_CH0: + case SYS_PCIE_MSI4_CH0: + case SYS_PCIE_MSI5_CH0: + case SYS_PCIE_PME_CH0: + case SYS_PCIE_ACK_CH0: + case SYS_PCIE_MISC_CH0: + case SYS_PCIE_MODE_CH0: + case SYS_ADC_CFG: + return true; + default: + return false; + } +} + +static bool rzg3e_regmap_writeable_reg(struct device *dev, unsigned int reg) +{ + switch (reg) { + case SYS_SPI_STAADDCS0: + case SYS_SPI_ENDADDCS0: + case SYS_SPI_STAADDCS1: + case SYS_SPI_ENDADDCS1: + case SYS_VSP_CLK: + case SYS_GBETH0_CFG: + case SYS_GBETH1_CFG: + case SYS_PCIE_INTX_CH0: + case SYS_PCIE_MSI1_CH0: + case SYS_PCIE_MSI2_CH0: + case SYS_PCIE_MSI3_CH0: + case SYS_PCIE_MSI4_CH0: + case SYS_PCIE_MSI5_CH0: + case SYS_PCIE_PME_CH0: + case SYS_PCIE_ACK_CH0: + case SYS_PCIE_MISC_CH0: + case SYS_PCIE_MODE_CH0: + case SYS_ADC_CFG: + return true; + default: + return false; + } +} + const struct rz_sysc_init_data rzg3e_sys_init_data = { .soc_id_init_data = &rzg3e_sys_soc_id_init_data, + .readable_reg = rzg3e_regmap_readable_reg, + .writeable_reg = rzg3e_regmap_writeable_reg, .max_register = 0x170c, }; diff --git a/drivers/soc/renesas/r9a09g056-sys.c b/drivers/soc/renesas/r9a09g056-sys.c index 16b4e433c33739..42f5eff291fd1f 100644 --- a/drivers/soc/renesas/r9a09g056-sys.c +++ b/drivers/soc/renesas/r9a09g056-sys.c @@ -34,6 +34,24 @@ #define SYS_RZV2N_FEATURE_C55 BIT(1) #define SYS_RZV2N_FEATURE_SEC BIT(2) +#define SYS_LSI_OTPTSU0TRMVAL0 0x320 +#define SYS_LSI_OTPTSU0TRMVAL1 0x324 +#define SYS_LSI_OTPTSU1TRMVAL0 0x330 +#define SYS_LSI_OTPTSU1TRMVAL1 0x334 +#define SYS_GBETH0_CFG 0xf00 +#define SYS_GBETH1_CFG 0xf04 +#define SYS_PCIE_INTX_CH0 0x1000 +#define SYS_PCIE_MSI1_CH0 0x1004 +#define SYS_PCIE_MSI2_CH0 0x1008 +#define SYS_PCIE_MSI3_CH0 0x100c +#define SYS_PCIE_MSI4_CH0 0x1010 +#define SYS_PCIE_MSI5_CH0 0x1014 +#define SYS_PCIE_PME_CH0 0x1018 +#define SYS_PCIE_ACK_CH0 0x101c +#define SYS_PCIE_MISC_CH0 0x1020 +#define SYS_PCIE_MODE_CH0 0x1024 +#define SYS_ADC_CFG 0x1600 + static void rzv2n_sys_print_id(struct device *dev, void __iomem *sysc_base, struct soc_device_attribute *soc_dev_attr) @@ -70,7 +88,57 @@ static const struct rz_sysc_soc_id_init_data rzv2n_sys_soc_id_init_data __initco .print_id = rzv2n_sys_print_id, }; +static bool rzv2n_regmap_readable_reg(struct device *dev, unsigned int reg) +{ + switch (reg) { + case SYS_LSI_OTPTSU0TRMVAL0: + case SYS_LSI_OTPTSU0TRMVAL1: + case SYS_LSI_OTPTSU1TRMVAL0: + case SYS_LSI_OTPTSU1TRMVAL1: + case SYS_GBETH0_CFG: + case SYS_GBETH1_CFG: + case SYS_PCIE_INTX_CH0: + case SYS_PCIE_MSI1_CH0: + case SYS_PCIE_MSI2_CH0: + case SYS_PCIE_MSI3_CH0: + case SYS_PCIE_MSI4_CH0: + case SYS_PCIE_MSI5_CH0: + case SYS_PCIE_PME_CH0: + case SYS_PCIE_ACK_CH0: + case SYS_PCIE_MISC_CH0: + case SYS_PCIE_MODE_CH0: + case SYS_ADC_CFG: + return true; + default: + return false; + } +} + +static bool rzv2n_regmap_writeable_reg(struct device *dev, unsigned int reg) +{ + switch (reg) { + case SYS_GBETH0_CFG: + case SYS_GBETH1_CFG: + case SYS_PCIE_INTX_CH0: + case SYS_PCIE_MSI1_CH0: + case SYS_PCIE_MSI2_CH0: + case SYS_PCIE_MSI3_CH0: + case SYS_PCIE_MSI4_CH0: + case SYS_PCIE_MSI5_CH0: + case SYS_PCIE_PME_CH0: + case SYS_PCIE_ACK_CH0: + case SYS_PCIE_MISC_CH0: + case SYS_PCIE_MODE_CH0: + case SYS_ADC_CFG: + return true; + default: + return false; + } +} + const struct rz_sysc_init_data rzv2n_sys_init_data = { .soc_id_init_data = &rzv2n_sys_soc_id_init_data, + .readable_reg = rzv2n_regmap_readable_reg, + .writeable_reg = rzv2n_regmap_writeable_reg, .max_register = 0x170c, }; diff --git a/drivers/soc/renesas/r9a09g057-sys.c b/drivers/soc/renesas/r9a09g057-sys.c index e3390e7c7fe516..827c718ac7c545 100644 --- a/drivers/soc/renesas/r9a09g057-sys.c +++ b/drivers/soc/renesas/r9a09g057-sys.c @@ -29,6 +29,35 @@ #define SYS_LSI_PRR_GPU_DIS BIT(0) #define SYS_LSI_PRR_ISP_DIS BIT(4) +#define SYS_LSI_OTPTSU0TRMVAL0 0x320 +#define SYS_LSI_OTPTSU0TRMVAL1 0x324 +#define SYS_LSI_OTPTSU1TRMVAL0 0x330 +#define SYS_LSI_OTPTSU1TRMVAL1 0x334 +#define SYS_GBETH0_CFG 0xf00 +#define SYS_GBETH1_CFG 0xf04 +#define SYS_PCIE_INTX_CH0 0x1000 +#define SYS_PCIE_MSI1_CH0 0x1004 +#define SYS_PCIE_MSI2_CH0 0x1008 +#define SYS_PCIE_MSI3_CH0 0x100c +#define SYS_PCIE_MSI4_CH0 0x1010 +#define SYS_PCIE_MSI5_CH0 0x1014 +#define SYS_PCIE_PME_CH0 0x1018 +#define SYS_PCIE_ACK_CH0 0x101c +#define SYS_PCIE_MISC_CH0 0x1020 +#define SYS_PCIE_MODE_CH0 0x1024 +#define SYS_PCIE_INTX_CH1 0x1030 +#define SYS_PCIE_MSI1_CH1 0x1034 +#define SYS_PCIE_MSI2_CH1 0x1038 +#define SYS_PCIE_MSI3_CH1 0x103c +#define SYS_PCIE_MSI4_CH1 0x1040 +#define SYS_PCIE_MSI5_CH1 0x1044 +#define SYS_PCIE_PME_CH1 0x1048 +#define SYS_PCIE_ACK_CH1 0x104c +#define SYS_PCIE_MISC_CH1 0x1050 +#define SYS_PCIE_MODE_CH1 0x1054 +#define SYS_PCIE_MODE 0x1060 +#define SYS_ADC_CFG 0x1600 + static void rzv2h_sys_print_id(struct device *dev, void __iomem *sysc_base, struct soc_device_attribute *soc_dev_attr) @@ -62,7 +91,79 @@ static const struct rz_sysc_soc_id_init_data rzv2h_sys_soc_id_init_data __initco .print_id = rzv2h_sys_print_id, }; +static bool rzv2h_regmap_readable_reg(struct device *dev, unsigned int reg) +{ + switch (reg) { + case SYS_LSI_OTPTSU0TRMVAL0: + case SYS_LSI_OTPTSU0TRMVAL1: + case SYS_LSI_OTPTSU1TRMVAL0: + case SYS_LSI_OTPTSU1TRMVAL1: + case SYS_GBETH0_CFG: + case SYS_GBETH1_CFG: + case SYS_PCIE_INTX_CH0: + case SYS_PCIE_MSI1_CH0: + case SYS_PCIE_MSI2_CH0: + case SYS_PCIE_MSI3_CH0: + case SYS_PCIE_MSI4_CH0: + case SYS_PCIE_MSI5_CH0: + case SYS_PCIE_PME_CH0: + case SYS_PCIE_ACK_CH0: + case SYS_PCIE_MISC_CH0: + case SYS_PCIE_MODE_CH0: + case SYS_PCIE_INTX_CH1: + case SYS_PCIE_MSI1_CH1: + case SYS_PCIE_MSI2_CH1: + case SYS_PCIE_MSI3_CH1: + case SYS_PCIE_MSI4_CH1: + case SYS_PCIE_MSI5_CH1: + case SYS_PCIE_PME_CH1: + case SYS_PCIE_ACK_CH1: + case SYS_PCIE_MISC_CH1: + case SYS_PCIE_MODE_CH1: + case SYS_PCIE_MODE: + case SYS_ADC_CFG: + return true; + default: + return false; + } +} + +static bool rzv2h_regmap_writeable_reg(struct device *dev, unsigned int reg) +{ + switch (reg) { + case SYS_GBETH0_CFG: + case SYS_GBETH1_CFG: + case SYS_PCIE_INTX_CH0: + case SYS_PCIE_MSI1_CH0: + case SYS_PCIE_MSI2_CH0: + case SYS_PCIE_MSI3_CH0: + case SYS_PCIE_MSI4_CH0: + case SYS_PCIE_MSI5_CH0: + case SYS_PCIE_PME_CH0: + case SYS_PCIE_ACK_CH0: + case SYS_PCIE_MISC_CH0: + case SYS_PCIE_MODE_CH0: + case SYS_PCIE_INTX_CH1: + case SYS_PCIE_MSI1_CH1: + case SYS_PCIE_MSI2_CH1: + case SYS_PCIE_MSI3_CH1: + case SYS_PCIE_MSI4_CH1: + case SYS_PCIE_MSI5_CH1: + case SYS_PCIE_PME_CH1: + case SYS_PCIE_ACK_CH1: + case SYS_PCIE_MISC_CH1: + case SYS_PCIE_MODE_CH1: + case SYS_PCIE_MODE: + case SYS_ADC_CFG: + return true; + default: + return false; + } +} + const struct rz_sysc_init_data rzv2h_sys_init_data = { .soc_id_init_data = &rzv2h_sys_soc_id_init_data, + .readable_reg = rzv2h_regmap_readable_reg, + .writeable_reg = rzv2h_regmap_writeable_reg, .max_register = 0x170c, }; diff --git a/drivers/soc/renesas/rz-sysc.c b/drivers/soc/renesas/rz-sysc.c index 9f79e299e6f416..19c1e666279b75 100644 --- a/drivers/soc/renesas/rz-sysc.c +++ b/drivers/soc/renesas/rz-sysc.c @@ -140,6 +140,8 @@ static int rz_sysc_probe(struct platform_device *pdev) regmap_cfg->val_bits = 32; regmap_cfg->fast_io = true; regmap_cfg->max_register = data->max_register; + regmap_cfg->readable_reg = data->readable_reg; + regmap_cfg->writeable_reg = data->writeable_reg; regmap = devm_regmap_init_mmio(dev, sysc->base, regmap_cfg); if (IS_ERR(regmap)) diff --git a/drivers/soc/renesas/rz-sysc.h b/drivers/soc/renesas/rz-sysc.h index 8eec355d5d5620..88929bf21cb114 100644 --- a/drivers/soc/renesas/rz-sysc.h +++ b/drivers/soc/renesas/rz-sysc.h @@ -34,10 +34,14 @@ struct rz_sysc_soc_id_init_data { /** * struct rz_sysc_init_data - RZ SYSC initialization data * @soc_id_init_data: RZ SYSC SoC ID initialization data + * @writeable_reg: Regmap writeable register check function + * @readable_reg: Regmap readable register check function * @max_register: Maximum SYSC register offset to be used by the regmap config */ struct rz_sysc_init_data { const struct rz_sysc_soc_id_init_data *soc_id_init_data; + bool (*writeable_reg)(struct device *dev, unsigned int reg); + bool (*readable_reg)(struct device *dev, unsigned int reg); u32 max_register; }; From 3eeb44415063095da9a8c131dd34b9b86f46c55c Mon Sep 17 00:00:00 2001 From: FUKAUMI Naoki Date: Wed, 12 Nov 2025 03:51:29 +0000 Subject: [PATCH 0642/3902] arm64: dts: rockchip: Move the EEPROM to correct I2C bus on Radxa ROCK 5A [ Upstream commit 92e6e0b0e595afdda6296c760551ad3ffe9d5231 ] The BL24C16 EEPROM chip found on Radxa ROCK 5A is connected to the i2c0 bus, [1] so move the eeprom node from the i2c2 bus to the i2c0 bus. [1] Link: https://dl.radxa.com/rock5/5a/docs/hw/radxa_rock5a_V1.1_sch.pdf p.19 Fixes: 89c880808cff8 ("arm64: dts: rockchip: add I2C EEPROM to rock-5a") Signed-off-by: FUKAUMI Naoki Link: https://patch.msgid.link/20251112035133.28753-2-naoki@radxa.com Signed-off-by: Heiko Stuebner Signed-off-by: Sasha Levin --- arch/arm64/boot/dts/rockchip/rk3588s-rock-5a.dts | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/arch/arm64/boot/dts/rockchip/rk3588s-rock-5a.dts b/arch/arm64/boot/dts/rockchip/rk3588s-rock-5a.dts index 19a08f7794e675..428c6f0232a341 100644 --- a/arch/arm64/boot/dts/rockchip/rk3588s-rock-5a.dts +++ b/arch/arm64/boot/dts/rockchip/rk3588s-rock-5a.dts @@ -228,6 +228,12 @@ regulator-off-in-suspend; }; }; + + eeprom: eeprom@50 { + compatible = "belling,bl24c16a", "atmel,24c16"; + reg = <0x50>; + pagesize = <16>; + }; }; &i2c2 { @@ -249,12 +255,6 @@ regulator-off-in-suspend; }; }; - - eeprom: eeprom@50 { - compatible = "belling,bl24c16a", "atmel,24c16"; - reg = <0x50>; - pagesize = <16>; - }; }; &i2c3 { From 63d98a936f0f4b723596ae023ba180f495e7b43a Mon Sep 17 00:00:00 2001 From: FUKAUMI Naoki Date: Wed, 12 Nov 2025 03:51:30 +0000 Subject: [PATCH 0643/3902] arm64: dts: rockchip: Add eeprom vcc-supply for Radxa ROCK 5A [ Upstream commit 3069ff1930aa71e125874c780ffaa6caeda5800a ] The VCC supply for the BL24C16 EEPROM chip found on Radxa ROCK 5A is vcc_3v3_pmu, which is routed to vcc_3v3_s3 via a zero-ohm resistor. [1] Describe this supply. [1] https://dl.radxa.com/rock5/5a/docs/hw/radxa_rock5a_V1.1_sch.pdf p.4, p.19 Fixes: 89c880808cff8 ("arm64: dts: rockchip: add I2C EEPROM to rock-5a") Signed-off-by: FUKAUMI Naoki Link: https://patch.msgid.link/20251112035133.28753-3-naoki@radxa.com Signed-off-by: Heiko Stuebner Signed-off-by: Sasha Levin --- arch/arm64/boot/dts/rockchip/rk3588s-rock-5a.dts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/arch/arm64/boot/dts/rockchip/rk3588s-rock-5a.dts b/arch/arm64/boot/dts/rockchip/rk3588s-rock-5a.dts index 428c6f0232a341..041a0fff22ccbd 100644 --- a/arch/arm64/boot/dts/rockchip/rk3588s-rock-5a.dts +++ b/arch/arm64/boot/dts/rockchip/rk3588s-rock-5a.dts @@ -233,6 +233,7 @@ compatible = "belling,bl24c16a", "atmel,24c16"; reg = <0x50>; pagesize = <16>; + vcc-supply = <&vcc_3v3_pmu>; }; }; @@ -600,7 +601,7 @@ }; }; - vcc_3v3_s3: dcdc-reg8 { + vcc_3v3_pmu: vcc_3v3_s3: dcdc-reg8 { regulator-name = "vcc_3v3_s3"; regulator-always-on; regulator-boot-on; From 42a2a70a12e42d96d6cd8778705208bd77e4994e Mon Sep 17 00:00:00 2001 From: FUKAUMI Naoki Date: Wed, 12 Nov 2025 03:51:31 +0000 Subject: [PATCH 0644/3902] arm64: dts: rockchip: Add eeprom vcc-supply for Radxa ROCK 3C [ Upstream commit 260316d35cf8f8606c5ed7a349cc92e1e71d8150 ] The VCC supply for the BL24C16 EEPROM chip found on Radxa ROCK 3C is vcca1v8_pmu. [1] Describe this supply. [1] https://dl.radxa.com/rock3/docs/hw/3c/v1400/radxa_rock_3c_v1400_schematic.pdf p.13 Fixes: ee219017ddb50 ("arm64: dts: rockchip: Add Radxa ROCK 3C") Signed-off-by: FUKAUMI Naoki Link: https://patch.msgid.link/20251112035133.28753-4-naoki@radxa.com Signed-off-by: Heiko Stuebner Signed-off-by: Sasha Levin --- arch/arm64/boot/dts/rockchip/rk3566-rock-3c.dts | 1 + 1 file changed, 1 insertion(+) diff --git a/arch/arm64/boot/dts/rockchip/rk3566-rock-3c.dts b/arch/arm64/boot/dts/rockchip/rk3566-rock-3c.dts index 6224d72813e593..80ac40555e023a 100644 --- a/arch/arm64/boot/dts/rockchip/rk3566-rock-3c.dts +++ b/arch/arm64/boot/dts/rockchip/rk3566-rock-3c.dts @@ -466,6 +466,7 @@ compatible = "belling,bl24c16a", "atmel,24c16"; reg = <0x50>; pagesize = <16>; + vcc-supply = <&vcca1v8_pmu>; }; }; From 94a9c0c42cce51a53af36585328b44fb72a34f2d Mon Sep 17 00:00:00 2001 From: Zilin Guan Date: Sun, 9 Nov 2025 14:56:48 +0000 Subject: [PATCH 0645/3902] crypto: iaa - Fix incorrect return value in save_iaa_wq() [ Upstream commit 76ce17f6f7f78ab79b9741388bdb4dafa985b4e9 ] The save_iaa_wq() function unconditionally returns 0, even when an error is encountered. This prevents the error code from being propagated to the caller. Fix this by returning the 'ret' variable, which holds the actual status of the operations within the function. Fixes: ea7a5cbb43696 ("crypto: iaa - Add Intel IAA Compression Accelerator crypto driver core") Signed-off-by: Zilin Guan Signed-off-by: Herbert Xu Signed-off-by: Sasha Levin --- drivers/crypto/intel/iaa/iaa_crypto_main.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/crypto/intel/iaa/iaa_crypto_main.c b/drivers/crypto/intel/iaa/iaa_crypto_main.c index 23f585219fb4b2..d0058757b0000d 100644 --- a/drivers/crypto/intel/iaa/iaa_crypto_main.c +++ b/drivers/crypto/intel/iaa/iaa_crypto_main.c @@ -805,7 +805,7 @@ static int save_iaa_wq(struct idxd_wq *wq) if (!cpus_per_iaa) cpus_per_iaa = 1; out: - return 0; + return ret; } static void remove_iaa_wq(struct idxd_wq *wq) From 13dcd6308cb8f67134ee5d5d762b2a66363c695b Mon Sep 17 00:00:00 2001 From: Aleksei Nikiforov Date: Fri, 7 Nov 2025 16:59:16 +0100 Subject: [PATCH 0646/3902] s390/fpu: Fix false-positive kmsan report in fpu_vstl() [ Upstream commit 14e4e4175b64dd9216b522f6ece8af6997d063b2 ] A false-positive kmsan report is detected when running ping command. An inline assembly instruction 'vstl' can write varied amount of bytes depending on value of 'index' argument. If 'index' > 0, 'vstl' writes at least 2 bytes. clang generates kmsan write helper call depending on inline assembly constraints. Constraints are evaluated compile-time, but value of 'index' argument is known only at runtime. clang currently generates call to __msan_instrument_asm_store with 1 byte as size. Manually call kmsan function to indicate correct amount of bytes written and fix false-positive report. This change fixes following kmsan reports: [ 36.563119] ===================================================== [ 36.563594] BUG: KMSAN: uninit-value in virtqueue_add+0x35c6/0x7c70 [ 36.563852] virtqueue_add+0x35c6/0x7c70 [ 36.564016] virtqueue_add_outbuf+0xa0/0xb0 [ 36.564266] start_xmit+0x288c/0x4a20 [ 36.564460] dev_hard_start_xmit+0x302/0x900 [ 36.564649] sch_direct_xmit+0x340/0xea0 [ 36.564894] __dev_queue_xmit+0x2e94/0x59b0 [ 36.565058] neigh_resolve_output+0x936/0xb40 [ 36.565278] __neigh_update+0x2f66/0x3a60 [ 36.565499] neigh_update+0x52/0x60 [ 36.565683] arp_process+0x1588/0x2de0 [ 36.565916] NF_HOOK+0x1da/0x240 [ 36.566087] arp_rcv+0x3e4/0x6e0 [ 36.566306] __netif_receive_skb_list_core+0x1374/0x15a0 [ 36.566527] netif_receive_skb_list_internal+0x1116/0x17d0 [ 36.566710] napi_complete_done+0x376/0x740 [ 36.566918] virtnet_poll+0x1bae/0x2910 [ 36.567130] __napi_poll+0xf4/0x830 [ 36.567294] net_rx_action+0x97c/0x1ed0 [ 36.567556] handle_softirqs+0x306/0xe10 [ 36.567731] irq_exit_rcu+0x14c/0x2e0 [ 36.567910] do_io_irq+0xd4/0x120 [ 36.568139] io_int_handler+0xc2/0xe8 [ 36.568299] arch_cpu_idle+0xb0/0xc0 [ 36.568540] arch_cpu_idle+0x76/0xc0 [ 36.568726] default_idle_call+0x40/0x70 [ 36.568953] do_idle+0x1d6/0x390 [ 36.569486] cpu_startup_entry+0x9a/0xb0 [ 36.569745] rest_init+0x1ea/0x290 [ 36.570029] start_kernel+0x95e/0xb90 [ 36.570348] startup_continue+0x2e/0x40 [ 36.570703] [ 36.570798] Uninit was created at: [ 36.571002] kmem_cache_alloc_node_noprof+0x9e8/0x10e0 [ 36.571261] kmalloc_reserve+0x12a/0x470 [ 36.571553] __alloc_skb+0x310/0x860 [ 36.571844] __ip_append_data+0x483e/0x6a30 [ 36.572170] ip_append_data+0x11c/0x1e0 [ 36.572477] raw_sendmsg+0x1c8c/0x2180 [ 36.572818] inet_sendmsg+0xe6/0x190 [ 36.573142] __sys_sendto+0x55e/0x8e0 [ 36.573392] __s390x_sys_socketcall+0x19ae/0x2ba0 [ 36.573571] __do_syscall+0x12e/0x240 [ 36.573823] system_call+0x6e/0x90 [ 36.573976] [ 36.574017] Byte 35 of 98 is uninitialized [ 36.574082] Memory access of size 98 starts at 0000000007aa0012 [ 36.574218] [ 36.574325] CPU: 0 UID: 0 PID: 0 Comm: swapper/0 Tainted: G B N 6.17.0-dirty #16 NONE [ 36.574541] Tainted: [B]=BAD_PAGE, [N]=TEST [ 36.574617] Hardware name: IBM 3931 A01 703 (KVM/Linux) [ 36.574755] ===================================================== [ 63.532541] ===================================================== [ 63.533639] BUG: KMSAN: uninit-value in virtqueue_add+0x35c6/0x7c70 [ 63.533989] virtqueue_add+0x35c6/0x7c70 [ 63.534940] virtqueue_add_outbuf+0xa0/0xb0 [ 63.535861] start_xmit+0x288c/0x4a20 [ 63.536708] dev_hard_start_xmit+0x302/0x900 [ 63.537020] sch_direct_xmit+0x340/0xea0 [ 63.537997] __dev_queue_xmit+0x2e94/0x59b0 [ 63.538819] neigh_resolve_output+0x936/0xb40 [ 63.539793] ip_finish_output2+0x1ee2/0x2200 [ 63.540784] __ip_finish_output+0x272/0x7a0 [ 63.541765] ip_finish_output+0x4e/0x5e0 [ 63.542791] ip_output+0x166/0x410 [ 63.543771] ip_push_pending_frames+0x1a2/0x470 [ 63.544753] raw_sendmsg+0x1f06/0x2180 [ 63.545033] inet_sendmsg+0xe6/0x190 [ 63.546006] __sys_sendto+0x55e/0x8e0 [ 63.546859] __s390x_sys_socketcall+0x19ae/0x2ba0 [ 63.547730] __do_syscall+0x12e/0x240 [ 63.548019] system_call+0x6e/0x90 [ 63.548989] [ 63.549779] Uninit was created at: [ 63.550691] kmem_cache_alloc_node_noprof+0x9e8/0x10e0 [ 63.550975] kmalloc_reserve+0x12a/0x470 [ 63.551969] __alloc_skb+0x310/0x860 [ 63.552949] __ip_append_data+0x483e/0x6a30 [ 63.553902] ip_append_data+0x11c/0x1e0 [ 63.554912] raw_sendmsg+0x1c8c/0x2180 [ 63.556719] inet_sendmsg+0xe6/0x190 [ 63.557534] __sys_sendto+0x55e/0x8e0 [ 63.557875] __s390x_sys_socketcall+0x19ae/0x2ba0 [ 63.558869] __do_syscall+0x12e/0x240 [ 63.559832] system_call+0x6e/0x90 [ 63.560780] [ 63.560972] Byte 35 of 98 is uninitialized [ 63.561741] Memory access of size 98 starts at 0000000005704312 [ 63.561950] [ 63.562824] CPU: 3 UID: 0 PID: 192 Comm: ping Tainted: G B N 6.17.0-dirty #16 NONE [ 63.563868] Tainted: [B]=BAD_PAGE, [N]=TEST [ 63.564751] Hardware name: IBM 3931 A01 703 (KVM/Linux) [ 63.564986] ===================================================== Fixes: dcd3e1de9d17 ("s390/checksum: provide csum_partial_copy_nocheck()") Signed-off-by: Aleksei Nikiforov Signed-off-by: Heiko Carstens Signed-off-by: Sasha Levin --- arch/s390/include/asm/fpu-insn.h | 3 +++ 1 file changed, 3 insertions(+) diff --git a/arch/s390/include/asm/fpu-insn.h b/arch/s390/include/asm/fpu-insn.h index e99f8bca8e08cf..96727f3bd0dce0 100644 --- a/arch/s390/include/asm/fpu-insn.h +++ b/arch/s390/include/asm/fpu-insn.h @@ -12,6 +12,7 @@ #ifndef __ASSEMBLER__ #include +#include #include asm(".include \"asm/fpu-insn-asm.h\"\n"); @@ -393,6 +394,7 @@ static __always_inline void fpu_vstl(u8 v1, u32 index, const void *vxr) : [vxr] "=Q" (*(u8 *)vxr) : [index] "d" (index), [v1] "I" (v1) : "memory"); + kmsan_unpoison_memory(vxr, size); } #else /* CONFIG_CC_HAS_ASM_AOR_FORMAT_FLAGS */ @@ -409,6 +411,7 @@ static __always_inline void fpu_vstl(u8 v1, u32 index, const void *vxr) : [vxr] "=R" (*(u8 *)vxr) : [index] "d" (index), [v1] "I" (v1) : "memory", "1"); + kmsan_unpoison_memory(vxr, size); } #endif /* CONFIG_CC_HAS_ASM_AOR_FORMAT_FLAGS */ From a2fb63bc252a8c580cae4d1430bafdc779ab1239 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Uwe=20Kleine-K=C3=B6nig?= Date: Fri, 26 Sep 2025 18:57:03 +0200 Subject: [PATCH 0647/3902] pwm: Simplify printf to emit chip->npwm in $debugfs/pwm MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [ Upstream commit 3cf8e55894b51c14f8500cae5e68ed48b1b0f3fd ] Instead of caring to correctly pluralize "PWM device(s)" using (chip->npwm != 1) ? "s" : "" or str_plural(chip->npwm) just simplify the format to not need a plural-s. Signed-off-by: Uwe Kleine-König Link: https://patch.msgid.link/20250926165702.321514-2-u.kleine-koenig@baylibre.com Signed-off-by: Uwe Kleine-König Stable-dep-of: 5f7ff902e7f3 ("pwm: Use %u to printf unsigned int pwm_chip::npwm and pwm_chip::id") Signed-off-by: Sasha Levin --- drivers/pwm/core.c | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/drivers/pwm/core.c b/drivers/pwm/core.c index ea2ccf42e81441..5b75f4a0849670 100644 --- a/drivers/pwm/core.c +++ b/drivers/pwm/core.c @@ -2696,11 +2696,10 @@ static int pwm_seq_show(struct seq_file *s, void *v) { struct pwm_chip *chip = v; - seq_printf(s, "%s%d: %s/%s, %d PWM device%s\n", + seq_printf(s, "%s%d: %s/%s, npwm: %d\n", (char *)s->private, chip->id, pwmchip_parent(chip)->bus ? pwmchip_parent(chip)->bus->name : "no-bus", - dev_name(pwmchip_parent(chip)), chip->npwm, - (chip->npwm != 1) ? "s" : ""); + dev_name(pwmchip_parent(chip)), chip->npwm); pwm_dbg_show(chip, s); From 036bae52e5c1055d225fb2be19d4fedea4192c6e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Uwe=20Kleine-K=C3=B6nig?= Date: Mon, 6 Oct 2025 15:35:26 +0200 Subject: [PATCH 0648/3902] pwm: Use %u to printf unsigned int pwm_chip::npwm and pwm_chip::id MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [ Upstream commit 5f7ff902e7f324c10f2b64c5ba2e5e2d0bc4e07e ] %u is the right conversion specifier to emit an unsigned int value. Fixes: 62099abf67a2 ("pwm: Add debugfs interface") Fixes: 0360a4873372 ("pwm: Mention PWM chip ID in /sys/kernel/debug/pwm") Signed-off-by: Uwe Kleine-König Link: https://patch.msgid.link/20251006133525.2457171-2-u.kleine-koenig@baylibre.com Signed-off-by: Uwe Kleine-König Signed-off-by: Sasha Levin --- drivers/pwm/core.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/pwm/core.c b/drivers/pwm/core.c index 5b75f4a0849670..7dd1cf2ba40252 100644 --- a/drivers/pwm/core.c +++ b/drivers/pwm/core.c @@ -2696,7 +2696,7 @@ static int pwm_seq_show(struct seq_file *s, void *v) { struct pwm_chip *chip = v; - seq_printf(s, "%s%d: %s/%s, npwm: %d\n", + seq_printf(s, "%s%u: %s/%s, npwm: %u\n", (char *)s->private, chip->id, pwmchip_parent(chip)->bus ? pwmchip_parent(chip)->bus->name : "no-bus", dev_name(pwmchip_parent(chip)), chip->npwm); From 41ba9d25d965e52f45ffbb9e3a988d638256efbd Mon Sep 17 00:00:00 2001 From: Praveen Talari Date: Mon, 10 Nov 2025 15:40:40 +0530 Subject: [PATCH 0649/3902] arm64: dts: qcom: qrb2210-rb1: Fix UART3 wakeup IRQ storm [ Upstream commit 9c92d36b0b1ea8b2a19dbe0416434f3491dbfaaf ] For BT use cases, pins are configured with pull-up state in sleep state to avoid noise. If IRQ type is configured as level high and the GPIO line is also in a high state, it causes continuous interrupt assertions leading to an IRQ storm when wakeup irq enables at system suspend/runtime suspend. Switching to edge-triggered interrupt (IRQ_TYPE_EDGE_FALLING) resolves this by only triggering on state transitions (high-to-low) rather than maintaining sensitivity to the static level state, effectively preventing the continuous interrupt condition and eliminating the wakeup IRQ storm. Fixes: 9380e0a1d449 ("arm64: dts: qcom: qrb2210-rb1: add Bluetooth support") Signed-off-by: Praveen Talari Reviewed-by: Konrad Dybcio Link: https://lore.kernel.org/r/20251110101043.2108414-2-praveen.talari@oss.qualcomm.com Signed-off-by: Bjorn Andersson Signed-off-by: Sasha Levin --- arch/arm64/boot/dts/qcom/qrb2210-rb1.dts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/arch/arm64/boot/dts/qcom/qrb2210-rb1.dts b/arch/arm64/boot/dts/qcom/qrb2210-rb1.dts index 67ba508e92ba1f..3eedfb2cf4799c 100644 --- a/arch/arm64/boot/dts/qcom/qrb2210-rb1.dts +++ b/arch/arm64/boot/dts/qcom/qrb2210-rb1.dts @@ -649,7 +649,7 @@ &uart3 { /delete-property/ interrupts; interrupts-extended = <&intc GIC_SPI 330 IRQ_TYPE_LEVEL_HIGH>, - <&tlmm 11 IRQ_TYPE_LEVEL_HIGH>; + <&tlmm 11 IRQ_TYPE_EDGE_FALLING>; pinctrl-0 = <&uart3_default>; pinctrl-1 = <&uart3_sleep>; pinctrl-names = "default", "sleep"; From 7102ea1e816ac2a694d91b33233ddd63ae582b42 Mon Sep 17 00:00:00 2001 From: Dmitry Baryshkov Date: Mon, 27 Oct 2025 15:35:17 +0200 Subject: [PATCH 0650/3902] drm/msm/dpu: drop dpu_hw_dsc_destroy() prototype [ Upstream commit d9792823d18ff9895eaf5769a29a54804f24bc25 ] The commit a106ed98af68 ("drm/msm/dpu: use devres-managed allocation for HW blocks") dropped all dpu_hw_foo_destroy() functions, but the prototype for dpu_hw_dsc_destroy() was omitted. Drop it now to clean up the header. Fixes: a106ed98af68 ("drm/msm/dpu: use devres-managed allocation for HW blocks") Signed-off-by: Dmitry Baryshkov Reviewed-by: Konrad Dybcio Reviewed-by: Jessica Zhang Patchwork: https://patchwork.freedesktop.org/patch/683697/ Link: https://lore.kernel.org/r/20251027-dpu-drop-dsc-destroy-v1-1-968128de4bf6@oss.qualcomm.com Signed-off-by: Sasha Levin --- drivers/gpu/drm/msm/disp/dpu1/dpu_hw_dsc.h | 6 ------ 1 file changed, 6 deletions(-) diff --git a/drivers/gpu/drm/msm/disp/dpu1/dpu_hw_dsc.h b/drivers/gpu/drm/msm/disp/dpu1/dpu_hw_dsc.h index b7013c9822d232..cc7cc6f6f7cda6 100644 --- a/drivers/gpu/drm/msm/disp/dpu1/dpu_hw_dsc.h +++ b/drivers/gpu/drm/msm/disp/dpu1/dpu_hw_dsc.h @@ -71,12 +71,6 @@ struct dpu_hw_dsc *dpu_hw_dsc_init_1_2(struct drm_device *dev, const struct dpu_dsc_cfg *cfg, void __iomem *addr); -/** - * dpu_hw_dsc_destroy - destroys dsc driver context - * @dsc: Pointer to dsc driver context returned by dpu_hw_dsc_init - */ -void dpu_hw_dsc_destroy(struct dpu_hw_dsc *dsc); - static inline struct dpu_hw_dsc *to_dpu_hw_dsc(struct dpu_hw_blk *hw) { return container_of(hw, struct dpu_hw_dsc, base); From 9c58e23af95102948dc69d77cd4ef423269d7b06 Mon Sep 17 00:00:00 2001 From: Rene Rebe Date: Fri, 14 Nov 2025 15:30:33 +0100 Subject: [PATCH 0651/3902] ps3disk: use memcpy_{from,to}_bvec index MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [ Upstream commit 79bd8c9814a273fa7ba43399e1c07adec3fc95db ] With 6e0a48552b8c (ps3disk: use memcpy_{from,to}_bvec) converting ps3disk to new bvec helpers, incrementing the offset was accidently lost, corrupting consecutive buffers. Restore index for non-corrupted data transfers. Fixes: 6e0a48552b8c (ps3disk: use memcpy_{from,to}_bvec) Signed-off-by: René Rebe Reviewed-by: Christoph Hellwig Signed-off-by: Jens Axboe Signed-off-by: Sasha Levin --- drivers/block/ps3disk.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/drivers/block/ps3disk.c b/drivers/block/ps3disk.c index dc9e4a14b88545..8892f218a81479 100644 --- a/drivers/block/ps3disk.c +++ b/drivers/block/ps3disk.c @@ -85,10 +85,14 @@ static void ps3disk_scatter_gather(struct ps3_storage_device *dev, struct bio_vec bvec; rq_for_each_segment(bvec, req, iter) { + dev_dbg(&dev->sbd.core, "%s:%u: %u sectors from %llu\n", + __func__, __LINE__, bio_sectors(iter.bio), + iter.bio->bi_iter.bi_sector); if (gather) memcpy_from_bvec(dev->bounce_buf + offset, &bvec); else memcpy_to_bvec(&bvec, dev->bounce_buf + offset); + offset += bvec.bv_len; } } From 020ecb16e57889a7ab507c0a115f42be577782b3 Mon Sep 17 00:00:00 2001 From: Aaron Kling Date: Tue, 23 Sep 2025 11:58:05 -0500 Subject: [PATCH 0652/3902] soc/tegra: fuse: speedo-tegra210: Update speedo IDs [ Upstream commit ce27c9c2129679551c4e5fe71c1c5d42fff399c2 ] Existing code only sets CPU and GPU speedo IDs 0 and 1. The CPU DVFS code supports 11 IDs and nouveau supports 5. This aligns with what the downstream vendor kernel supports. Align SKUs with the downstream list. The Tegra210 CVB tables were added in the first referenced fixes commit. Since then, all Tegra210 SoCs have tried to scale to 1.9 GHz, when the supported devkits are only supposed to scale to 1.5 or 1.7 GHZ. Overclocking should not be the default state. Fixes: 2b2dbc2f94e5 ("clk: tegra: dfll: add CVB tables for Tegra210") Fixes: 579db6e5d9b8 ("arm64: tegra: Enable DFLL support on Jetson Nano") Signed-off-by: Aaron Kling Signed-off-by: Thierry Reding Signed-off-by: Sasha Levin --- drivers/soc/tegra/fuse/speedo-tegra210.c | 62 ++++++++++++++++-------- 1 file changed, 43 insertions(+), 19 deletions(-) diff --git a/drivers/soc/tegra/fuse/speedo-tegra210.c b/drivers/soc/tegra/fuse/speedo-tegra210.c index 695d0b7f9a8abe..a8cc3632977230 100644 --- a/drivers/soc/tegra/fuse/speedo-tegra210.c +++ b/drivers/soc/tegra/fuse/speedo-tegra210.c @@ -65,27 +65,51 @@ static void __init rev_sku_to_speedo_ids(struct tegra_sku_info *sku_info, sku_info->gpu_speedo_id = 0; *threshold = THRESHOLD_INDEX_0; - switch (sku) { - case 0x00: /* Engineering SKU */ - case 0x01: /* Engineering SKU */ - case 0x07: - case 0x17: - case 0x27: - if (speedo_rev >= 2) + if (sku_info->revision >= TEGRA_REVISION_A02) { + switch (sku) { + case 0x00: /* Engineering SKU */ + case 0x01: /* Engineering SKU */ + case 0x13: + sku_info->cpu_speedo_id = 5; + sku_info->gpu_speedo_id = 2; + break; + + case 0x07: + case 0x17: + case 0x1F: + sku_info->cpu_speedo_id = 7; + sku_info->gpu_speedo_id = 2; + break; + + case 0x27: + sku_info->cpu_speedo_id = 1; + sku_info->gpu_speedo_id = 2; + break; + + case 0x83: + sku_info->cpu_speedo_id = 3; + sku_info->gpu_speedo_id = 3; + break; + + case 0x87: + sku_info->cpu_speedo_id = 2; sku_info->gpu_speedo_id = 1; - break; - - case 0x13: - if (speedo_rev >= 2) - sku_info->gpu_speedo_id = 1; - - sku_info->cpu_speedo_id = 1; - break; - - default: + break; + + case 0x8F: + sku_info->cpu_speedo_id = 9; + sku_info->gpu_speedo_id = 2; + break; + + default: + pr_err("Tegra210: unknown revision 2 or newer SKU %#04x\n", sku); + /* Using the default for the error case */ + break; + } + } else if (sku == 0x00 || sku == 0x01 || sku == 0x07 || sku == 0x13 || sku == 0x17) { + sku_info->gpu_speedo_id = 1; + } else { pr_err("Tegra210: unknown SKU %#04x\n", sku); - /* Using the default for the error case */ - break; } } From 07cfb9c3f6d01a904329fd797d9a4b938b04b784 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ilpo=20J=C3=A4rvinen?= Date: Thu, 13 Nov 2025 18:26:18 +0200 Subject: [PATCH 0653/3902] PCI: Prevent resource tree corruption when BAR resize fails MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [ Upstream commit 91c4c89db41499eea1b29c56655f79c3bae66e93 ] pbus_reassign_bridge_resources() saves bridge windows into the saved list before attempting to adjust resource assignments to perform a BAR resize operation. If resource adjustments cannot be completed fully, rollback is attempted by restoring the resource from the saved list. The rollback, however, does not check whether the resources it restores were assigned by the partial resize attempt. If restore changes addresses of the resource, it can result in corrupting the resource tree. An example of a corrupted resource tree with overlapping addresses: 6200000000000-6203fbfffffff : pciex@620c3c0000000 6200000000000-6203fbff0ffff : PCI Bus 0030:01 6200020000000-62000207fffff : 0030:01:00.0 6200000000000-6203fbff0ffff : PCI Bus 0030:02 A resource that are assigned into the resource tree must remain unchanged. Thus, release such a resource before attempting to restore and claim it back. For simplicity, always do the release and claim back for the resource even in the cases where it is restored to the same address range. Note: this fix may "break" some cases where devices "worked" because the resource tree corruption allowed address space double counting to fit more resource than what can now be assigned without double counting. The upcoming changes to BAR resizing should address those scenarios (to the extent possible). Fixes: 8bb705e3e79d ("PCI: Add pci_resize_resource() for resizing BARs") Reported-by: Simon Richter Link: https://lore.kernel.org/linux-pci/67840a16-99b4-4d8c-9b5c-4721ab0970a2@hogyros.de/ Reported-by: Alex Bennée Link: https://lore.kernel.org/linux-pci/874irqop6b.fsf@draig.linaro.org/ Signed-off-by: Ilpo Järvinen Signed-off-by: Bjorn Helgaas Tested-by: Alex Bennée # AVA, AMD GPU Link: https://patch.msgid.link/20251113162628.5946-2-ilpo.jarvinen@linux.intel.com Signed-off-by: Sasha Levin --- drivers/pci/setup-bus.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/drivers/pci/setup-bus.c b/drivers/pci/setup-bus.c index 3645f392a9fd3a..5ba878f15db353 100644 --- a/drivers/pci/setup-bus.c +++ b/drivers/pci/setup-bus.c @@ -2504,6 +2504,11 @@ int pbus_reassign_bridge_resources(struct pci_bus *bus, struct resource *res) bridge = dev_res->dev; i = pci_resource_num(bridge, res); + if (res->parent) { + release_child_resources(res); + pci_release_resource(bridge, i); + } + restore_dev_resource(dev_res); pci_claim_resource(bridge, i); From 6394fc65d7bc3de439e6947d321b1d0917f8bda0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20Wei=C3=9Fschuh?= Date: Fri, 14 Nov 2025 14:43:56 +0100 Subject: [PATCH 0654/3902] kbuild: don't enable CC_CAN_LINK if the dummy program generates warnings MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [ Upstream commit d81d9d389b9b73acd68f300c8889c7fa1acd4977 ] It is possible that the kernel toolchain generates warnings when used together with the system toolchain. This happens for example when the older kernel toolchain does not handle new versions of sframe debug information. While these warnings where ignored during the evaluation of CC_CAN_LINK, together with CONFIG_WERROR the actual userprog build will later fail. Example warning: .../x86_64-linux/13.2.0/../../../../x86_64-linux/bin/ld: error in /lib/../lib64/crt1.o(.sframe); no .sframe will be created collect2: error: ld returned 1 exit status Make sure that the very simple example program does not generate warnings already to avoid breaking the userprog compilations. Fixes: ec4a3992bc0b ("kbuild: respect CONFIG_WERROR for linker and assembler") Fixes: 3f0ff4cc6ffb ("kbuild: respect CONFIG_WERROR for userprogs") Signed-off-by: Thomas Weißschuh Reviewed-by: Nicolas Schier Reviewed-by: Nathan Chancellor Link: https://patch.msgid.link/20251114-kbuild-userprogs-bits-v3-1-4dee0d74d439@linutronix.de Signed-off-by: Nicolas Schier Signed-off-by: Sasha Levin --- scripts/cc-can-link.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/cc-can-link.sh b/scripts/cc-can-link.sh index 6efcead3198989..e67fd8d7b6841e 100755 --- a/scripts/cc-can-link.sh +++ b/scripts/cc-can-link.sh @@ -1,7 +1,7 @@ #!/bin/sh # SPDX-License-Identifier: GPL-2.0 -cat << "END" | $@ -x c - -o /dev/null >/dev/null 2>&1 +cat << "END" | $@ -Werror -Wl,--fatal-warnings -x c - -o /dev/null >/dev/null 2>&1 #include int main(void) { From 4a9b0581757a97ed70805bd01b3c254890020be6 Mon Sep 17 00:00:00 2001 From: Sahil Chandna Date: Fri, 14 Nov 2025 12:19:22 +0530 Subject: [PATCH 0655/3902] bpf: Prevent nesting overflow in bpf_try_get_buffers [ Upstream commit c1da3df7191f1b4df9256bcd30d78f78201e1d17 ] bpf_try_get_buffers() returns one of multiple per-CPU buffers based on a per-CPU nesting counter. This mechanism expects that buffers are not endlessly acquired before being returned. migrate_disable() ensures that a task remains on the same CPU, but it does not prevent the task from being preempted by another task on that CPU. Without disabled preemption, a task may be preempted while holding a buffer, allowing another task to run on same CPU and acquire an additional buffer. Several such preemptions can cause the per-CPU nest counter to exceed MAX_BPRINTF_NEST_LEVEL and trigger the warning in bpf_try_get_buffers(). Adding preempt_disable()/preempt_enable() around buffer acquisition and release prevents this task preemption and preserves the intended bounded nesting behavior. Reported-by: syzbot+b0cff308140f79a9c4cb@syzkaller.appspotmail.com Closes: https://lore.kernel.org/all/68f6a4c8.050a0220.1be48.0011.GAE@google.com/ Fixes: 4223bf833c849 ("bpf: Remove preempt_disable in bpf_try_get_buffers") Suggested-by: Yonghong Song Reviewed-by: Sebastian Andrzej Siewior Signed-off-by: Sahil Chandna Link: https://lore.kernel.org/r/20251114064922.11650-1-chandna.sahil@gmail.com Signed-off-by: Alexei Starovoitov Signed-off-by: Sasha Levin --- kernel/bpf/helpers.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/kernel/bpf/helpers.c b/kernel/bpf/helpers.c index e4007fea49091c..81ef159ef89bd3 100644 --- a/kernel/bpf/helpers.c +++ b/kernel/bpf/helpers.c @@ -777,9 +777,11 @@ int bpf_try_get_buffers(struct bpf_bprintf_buffers **bufs) { int nest_level; + preempt_disable(); nest_level = this_cpu_inc_return(bpf_bprintf_nest_level); if (WARN_ON_ONCE(nest_level > MAX_BPRINTF_NEST_LEVEL)) { this_cpu_dec(bpf_bprintf_nest_level); + preempt_enable(); return -EBUSY; } *bufs = this_cpu_ptr(&bpf_bprintf_bufs[nest_level - 1]); @@ -792,6 +794,7 @@ void bpf_put_buffers(void) if (WARN_ON_ONCE(this_cpu_read(bpf_bprintf_nest_level) == 0)) return; this_cpu_dec(bpf_bprintf_nest_level); + preempt_enable(); } void bpf_bprintf_cleanup(struct bpf_bprintf_data *data) From 7547576c6209f510550ddcd342b6c52c85e1a612 Mon Sep 17 00:00:00 2001 From: Menglong Dong Date: Mon, 10 Nov 2025 20:07:05 +0800 Subject: [PATCH 0656/3902] bpf: Handle return value of ftrace_set_filter_ip in register_fentry [ Upstream commit fea3f5e83c5cd80a76d97343023a2f2e6bd862bf ] The error that returned by ftrace_set_filter_ip() in register_fentry() is not handled properly. Just fix it. Fixes: 00963a2e75a8 ("bpf: Support bpf_trampoline on functions with IPMODIFY (e.g. livepatch)") Signed-off-by: Menglong Dong Acked-by: Song Liu Link: https://lore.kernel.org/r/20251110120705.1553694-1-dongml2@chinatelecom.cn Signed-off-by: Alexei Starovoitov Signed-off-by: Sasha Levin --- kernel/bpf/trampoline.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/kernel/bpf/trampoline.c b/kernel/bpf/trampoline.c index f2cb0b09709330..04104397c432ea 100644 --- a/kernel/bpf/trampoline.c +++ b/kernel/bpf/trampoline.c @@ -220,7 +220,9 @@ static int register_fentry(struct bpf_trampoline *tr, void *new_addr) } if (tr->func.ftrace_managed) { - ftrace_set_filter_ip(tr->fops, (unsigned long)ip, 0, 1); + ret = ftrace_set_filter_ip(tr->fops, (unsigned long)ip, 0, 1); + if (ret) + return ret; ret = register_ftrace_direct(tr->fops, (long)new_addr); } else { ret = bpf_arch_text_poke(ip, BPF_MOD_CALL, NULL, new_addr); From 59bad3c98cad39585f19de742c3390c44679b9c2 Mon Sep 17 00:00:00 2001 From: Alexei Starovoitov Date: Thu, 13 Nov 2025 09:11:53 -0800 Subject: [PATCH 0657/3902] selftests/bpf: Fix failure paths in send_signal test [ Upstream commit c13339039891dbdfa6c1972f0483bd07f610b776 ] When test_send_signal_kern__open_and_load() fails parent closes the pipe which cases ASSERT_EQ(read(pipe_p2c...)) to fail, but child continues and enters infinite loop, while parent is stuck in wait(NULL). Other error paths have similar issue, so kill the child before waiting on it. The bug was discovered while compiling all of selftests with -O1 instead of -O2 which caused progs/test_send_signal_kern.c to fail to load. Fixes: ab8b7f0cb358 ("tools/bpf: Add self tests for bpf_send_signal_thread()") Signed-off-by: Alexei Starovoitov Signed-off-by: Andrii Nakryiko Acked-by: Eduard Zingerman Link: https://lore.kernel.org/bpf/20251113171153.2583-1-alexei.starovoitov@gmail.com Signed-off-by: Sasha Levin --- tools/testing/selftests/bpf/prog_tests/send_signal.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/tools/testing/selftests/bpf/prog_tests/send_signal.c b/tools/testing/selftests/bpf/prog_tests/send_signal.c index 1702aa592c2c21..7ac4d5a488aa54 100644 --- a/tools/testing/selftests/bpf/prog_tests/send_signal.c +++ b/tools/testing/selftests/bpf/prog_tests/send_signal.c @@ -206,6 +206,11 @@ static void test_send_signal_common(struct perf_event_attr *attr, skel_open_load_failure: close(pipe_c2p[0]); close(pipe_p2c[1]); + /* + * Child is either about to exit cleanly or stuck in case of errors. + * Nudge it to exit. + */ + kill(pid, SIGKILL); wait(NULL); } From 942268e2726ac7f16e3ec49dbfbbbe7cf5af9da5 Mon Sep 17 00:00:00 2001 From: Martin KaFai Lau Date: Wed, 12 Nov 2025 15:23:30 -0800 Subject: [PATCH 0658/3902] bpf: Check skb->transport_header is set in bpf_skb_check_mtu [ Upstream commit d946f3c98328171fa50ddb908593cf833587f725 ] The bpf_skb_check_mtu helper needs to use skb->transport_header when the BPF_MTU_CHK_SEGS flag is used: bpf_skb_check_mtu(skb, ifindex, &mtu_len, 0, BPF_MTU_CHK_SEGS) The transport_header is not always set. There is a WARN_ON_ONCE report when CONFIG_DEBUG_NET is enabled + skb->gso_size is set + bpf_prog_test_run is used: WARNING: CPU: 1 PID: 2216 at ./include/linux/skbuff.h:3071 skb_gso_validate_network_len bpf_skb_check_mtu bpf_prog_3920e25740a41171_tc_chk_segs_flag # A test in the next patch bpf_test_run bpf_prog_test_run_skb For a normal ingress skb (not test_run), skb_reset_transport_header is performed but there is plan to avoid setting it as described in commit 2170a1f09148 ("net: no longer reset transport_header in __netif_receive_skb_core()"). This patch fixes the bpf helper by checking skb_transport_header_was_set(). The check is done just before skb->transport_header is used, to avoid breaking the existing bpf prog. The WARN_ON_ONCE is limited to bpf_prog_test_run, so targeting bpf-next. Fixes: 34b2021cc616 ("bpf: Add BPF-helper for MTU checking") Cc: Jesper Dangaard Brouer Reported-by: Kaiyan Mei Reported-by: Yinhao Hu Signed-off-by: Martin KaFai Lau Link: https://lore.kernel.org/r/20251112232331.1566074-1-martin.lau@linux.dev Signed-off-by: Alexei Starovoitov Signed-off-by: Sasha Levin --- net/core/filter.c | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/net/core/filter.c b/net/core/filter.c index 1efec0d70d7839..df6ce85e48dcd6 100644 --- a/net/core/filter.c +++ b/net/core/filter.c @@ -6429,9 +6429,12 @@ BPF_CALL_5(bpf_skb_check_mtu, struct sk_buff *, skb, */ if (skb_is_gso(skb)) { ret = BPF_MTU_CHK_RET_SUCCESS; - if (flags & BPF_MTU_CHK_SEGS && - !skb_gso_validate_network_len(skb, mtu)) - ret = BPF_MTU_CHK_RET_SEGS_TOOBIG; + if (flags & BPF_MTU_CHK_SEGS) { + if (!skb_transport_header_was_set(skb)) + return -EINVAL; + if (!skb_gso_validate_network_len(skb, mtu)) + ret = BPF_MTU_CHK_RET_SEGS_TOOBIG; + } } out: *mtu_len = mtu; From 38080597380815897a8db07d682f739cf9738ad1 Mon Sep 17 00:00:00 2001 From: Nuno Das Neves Date: Fri, 17 Oct 2025 11:58:17 -0700 Subject: [PATCH 0659/3902] mshv: Fix deposit memory in MSHV_ROOT_HVCALL [ Upstream commit 4cc1aa469cd6b714adc958547a4866247bfd60a9 ] When the MSHV_ROOT_HVCALL ioctl is executing a hypercall, and gets HV_STATUS_INSUFFICIENT_MEMORY, it deposits memory and then returns -EAGAIN to userspace. The expectation is that the VMM will retry. However, some VMM code in the wild doesn't do this and simply fails. Rather than force the VMM to retry, change the ioctl to deposit memory on demand and immediately retry the hypercall as is done with all the other hypercall helper functions. In addition to making the ioctl easier to use, removing the need for multiple syscalls improves performance. There is a complication: unlike the other hypercall helper functions, in MSHV_ROOT_HVCALL the input is opaque to the kernel. This is problematic for rep hypercalls, because the next part of the input list can't be copied on each loop after depositing pages (this was the original reason for returning -EAGAIN in this case). Introduce hv_do_rep_hypercall_ex(), which adds a 'rep_start' parameter. This solves the issue, allowing the deposit loop in MSHV_ROOT_HVCALL to restart a rep hypercall after depositing pages partway through. Fixes: 621191d709b1 ("Drivers: hv: Introduce mshv_root module to expose /dev/mshv to VMMs") Signed-off-by: Nuno Das Neves Reviewed-by: Michael Kelley Signed-off-by: Wei Liu Signed-off-by: Sasha Levin --- drivers/hv/mshv_root_main.c | 58 ++++++++++++++++++---------------- include/asm-generic/mshyperv.h | 17 ++++++++-- 2 files changed, 44 insertions(+), 31 deletions(-) diff --git a/drivers/hv/mshv_root_main.c b/drivers/hv/mshv_root_main.c index e3b2bd417c4640..5156b8b0a39f4d 100644 --- a/drivers/hv/mshv_root_main.c +++ b/drivers/hv/mshv_root_main.c @@ -159,6 +159,7 @@ static int mshv_ioctl_passthru_hvcall(struct mshv_partition *partition, unsigned int pages_order; void *input_pg = NULL; void *output_pg = NULL; + u16 reps_completed; if (copy_from_user(&args, user_args, sizeof(args))) return -EFAULT; @@ -210,41 +211,42 @@ static int mshv_ioctl_passthru_hvcall(struct mshv_partition *partition, */ *(u64 *)input_pg = partition->pt_id; - if (args.reps) - status = hv_do_rep_hypercall(args.code, args.reps, 0, - input_pg, output_pg); - else - status = hv_do_hypercall(args.code, input_pg, output_pg); - - if (hv_result(status) == HV_STATUS_CALL_PENDING) { - if (is_async) { - mshv_async_hvcall_handler(partition, &status); - } else { /* Paranoia check. This shouldn't happen! */ - ret = -EBADFD; - goto free_pages_out; + reps_completed = 0; + do { + if (args.reps) { + status = hv_do_rep_hypercall_ex(args.code, args.reps, + 0, reps_completed, + input_pg, output_pg); + reps_completed = hv_repcomp(status); + } else { + status = hv_do_hypercall(args.code, input_pg, output_pg); } - } - if (hv_result(status) == HV_STATUS_INSUFFICIENT_MEMORY) { - ret = hv_call_deposit_pages(NUMA_NO_NODE, partition->pt_id, 1); - if (!ret) - ret = -EAGAIN; - } else if (!hv_result_success(status)) { - ret = hv_result_to_errno(status); - } + if (hv_result(status) == HV_STATUS_CALL_PENDING) { + if (is_async) { + mshv_async_hvcall_handler(partition, &status); + } else { /* Paranoia check. This shouldn't happen! */ + ret = -EBADFD; + goto free_pages_out; + } + } + + if (hv_result_success(status)) + break; + + if (hv_result(status) != HV_STATUS_INSUFFICIENT_MEMORY) + ret = hv_result_to_errno(status); + else + ret = hv_call_deposit_pages(NUMA_NO_NODE, + partition->pt_id, 1); + } while (!ret); - /* - * Always return the status and output data regardless of result. - * The VMM may need it to determine how to proceed. E.g. the status may - * contain the number of reps completed if a rep hypercall partially - * succeeded. - */ args.status = hv_result(status); - args.reps = args.reps ? hv_repcomp(status) : 0; + args.reps = reps_completed; if (copy_to_user(user_args, &args, sizeof(args))) ret = -EFAULT; - if (output_pg && + if (!ret && output_pg && copy_to_user((void __user *)args.out_ptr, output_pg, args.out_sz)) ret = -EFAULT; diff --git a/include/asm-generic/mshyperv.h b/include/asm-generic/mshyperv.h index 64ba6bc807d982..b89c7e3a20474f 100644 --- a/include/asm-generic/mshyperv.h +++ b/include/asm-generic/mshyperv.h @@ -124,10 +124,12 @@ static inline unsigned int hv_repcomp(u64 status) /* * Rep hypercalls. Callers of this functions are supposed to ensure that - * rep_count and varhead_size comply with Hyper-V hypercall definition. + * rep_count, varhead_size, and rep_start comply with Hyper-V hypercall + * definition. */ -static inline u64 hv_do_rep_hypercall(u16 code, u16 rep_count, u16 varhead_size, - void *input, void *output) +static inline u64 hv_do_rep_hypercall_ex(u16 code, u16 rep_count, + u16 varhead_size, u16 rep_start, + void *input, void *output) { u64 control = code; u64 status; @@ -135,6 +137,7 @@ static inline u64 hv_do_rep_hypercall(u16 code, u16 rep_count, u16 varhead_size, control |= (u64)varhead_size << HV_HYPERCALL_VARHEAD_OFFSET; control |= (u64)rep_count << HV_HYPERCALL_REP_COMP_OFFSET; + control |= (u64)rep_start << HV_HYPERCALL_REP_START_OFFSET; do { status = hv_do_hypercall(control, input, output); @@ -152,6 +155,14 @@ static inline u64 hv_do_rep_hypercall(u16 code, u16 rep_count, u16 varhead_size, return status; } +/* For the typical case where rep_start is 0 */ +static inline u64 hv_do_rep_hypercall(u16 code, u16 rep_count, u16 varhead_size, + void *input, void *output) +{ + return hv_do_rep_hypercall_ex(code, rep_count, varhead_size, 0, + input, output); +} + /* Generate the guest OS identifier as described in the Hyper-V TLFS */ static inline u64 hv_generate_guest_id(u64 kernel_version) { From ab3e7a78d83a61d335458cfe2e4d17eba69ae73d Mon Sep 17 00:00:00 2001 From: Nuno Das Neves Date: Thu, 6 Nov 2025 14:13:30 -0800 Subject: [PATCH 0660/3902] mshv: Fix create memory region overlap check [ Upstream commit ba9eb9b86d232854e983203dc2fb1ba18e316681 ] The current check is incorrect; it only checks if the beginning or end of a region is within an existing region. This doesn't account for userspace specifying a region that begins before and ends after an existing region. Change the logic to a range intersection check against gfns and uaddrs for each region. Remove mshv_partition_region_by_uaddr() as it is no longer used. Fixes: 621191d709b1 ("Drivers: hv: Introduce mshv_root module to expose /dev/mshv to VMMs") Reported-by: Michael Kelley Closes: https://lore.kernel.org/linux-hyperv/SN6PR02MB41575BE0406D3AB22E1D7DB5D4C2A@SN6PR02MB4157.namprd02.prod.outlook.com/ Signed-off-by: Nuno Das Neves Reviewed-by: Michael Kelley Signed-off-by: Wei Liu Signed-off-by: Sasha Levin --- drivers/hv/mshv_root_main.c | 31 +++++++++++-------------------- 1 file changed, 11 insertions(+), 20 deletions(-) diff --git a/drivers/hv/mshv_root_main.c b/drivers/hv/mshv_root_main.c index 5156b8b0a39f4d..4e04bef5443799 100644 --- a/drivers/hv/mshv_root_main.c +++ b/drivers/hv/mshv_root_main.c @@ -1174,21 +1174,6 @@ mshv_partition_region_by_gfn(struct mshv_partition *partition, u64 gfn) return NULL; } -static struct mshv_mem_region * -mshv_partition_region_by_uaddr(struct mshv_partition *partition, u64 uaddr) -{ - struct mshv_mem_region *region; - - hlist_for_each_entry(region, &partition->pt_mem_regions, hnode) { - if (uaddr >= region->start_uaddr && - uaddr < region->start_uaddr + - (region->nr_pages << HV_HYP_PAGE_SHIFT)) - return region; - } - - return NULL; -} - /* * NB: caller checks and makes sure mem->size is page aligned * Returns: 0 with regionpp updated on success, or -errno @@ -1198,15 +1183,21 @@ static int mshv_partition_create_region(struct mshv_partition *partition, struct mshv_mem_region **regionpp, bool is_mmio) { - struct mshv_mem_region *region; + struct mshv_mem_region *region, *rg; u64 nr_pages = HVPFN_DOWN(mem->size); /* Reject overlapping regions */ - if (mshv_partition_region_by_gfn(partition, mem->guest_pfn) || - mshv_partition_region_by_gfn(partition, mem->guest_pfn + nr_pages - 1) || - mshv_partition_region_by_uaddr(partition, mem->userspace_addr) || - mshv_partition_region_by_uaddr(partition, mem->userspace_addr + mem->size - 1)) + hlist_for_each_entry(rg, &partition->pt_mem_regions, hnode) { + u64 rg_size = rg->nr_pages << HV_HYP_PAGE_SHIFT; + + if ((mem->guest_pfn + nr_pages <= rg->start_gfn || + rg->start_gfn + rg->nr_pages <= mem->guest_pfn) && + (mem->userspace_addr + mem->size <= rg->start_uaddr || + rg->start_uaddr + rg_size <= mem->userspace_addr)) + continue; + return -EEXIST; + } region = vzalloc(sizeof(*region) + sizeof(struct page *) * nr_pages); if (!region) From e2b25922e5664ce2406b7ec0aa3a89c8406d1b4b Mon Sep 17 00:00:00 2001 From: Haotian Zhang Date: Thu, 13 Nov 2025 10:30:32 +0800 Subject: [PATCH 0661/3902] watchdog: wdat_wdt: Fix ACPI table leak in probe function [ Upstream commit 25c0b472eab8379683d4eef681185c104bed8ffd ] wdat_wdt_probe() calls acpi_get_table() to obtain the WDAT ACPI table but never calls acpi_put_table() on any paths. This causes a permanent ACPI table memory leak. Add a single cleanup path which calls acpi_put_table() to ensure the ACPI table is always released. Fixes: 058dfc767008 ("ACPI / watchdog: Add support for WDAT hardware watchdog") Suggested-by: Guenter Roeck Signed-off-by: Haotian Zhang Reviewed-by: Guenter Roeck Signed-off-by: Guenter Roeck Signed-off-by: Wim Van Sebroeck Signed-off-by: Sasha Levin --- drivers/watchdog/wdat_wdt.c | 64 +++++++++++++++++++++++++------------ 1 file changed, 43 insertions(+), 21 deletions(-) diff --git a/drivers/watchdog/wdat_wdt.c b/drivers/watchdog/wdat_wdt.c index 650fdc7996e1c2..dd3c2d69c9df12 100644 --- a/drivers/watchdog/wdat_wdt.c +++ b/drivers/watchdog/wdat_wdt.c @@ -326,19 +326,27 @@ static int wdat_wdt_probe(struct platform_device *pdev) return -ENODEV; wdat = devm_kzalloc(dev, sizeof(*wdat), GFP_KERNEL); - if (!wdat) - return -ENOMEM; + if (!wdat) { + ret = -ENOMEM; + goto out_put_table; + } regs = devm_kcalloc(dev, pdev->num_resources, sizeof(*regs), GFP_KERNEL); - if (!regs) - return -ENOMEM; + if (!regs) { + ret = -ENOMEM; + goto out_put_table; + } /* WDAT specification wants to have >= 1ms period */ - if (tbl->timer_period < 1) - return -EINVAL; - if (tbl->min_count > tbl->max_count) - return -EINVAL; + if (tbl->timer_period < 1) { + ret = -EINVAL; + goto out_put_table; + } + if (tbl->min_count > tbl->max_count) { + ret = -EINVAL; + goto out_put_table; + } wdat->period = tbl->timer_period; wdat->wdd.min_timeout = DIV_ROUND_UP(wdat->period * tbl->min_count, 1000); @@ -355,15 +363,20 @@ static int wdat_wdt_probe(struct platform_device *pdev) res = &pdev->resource[i]; if (resource_type(res) == IORESOURCE_MEM) { reg = devm_ioremap_resource(dev, res); - if (IS_ERR(reg)) - return PTR_ERR(reg); + if (IS_ERR(reg)) { + ret = PTR_ERR(reg); + goto out_put_table; + } } else if (resource_type(res) == IORESOURCE_IO) { reg = devm_ioport_map(dev, res->start, 1); - if (!reg) - return -ENOMEM; + if (!reg) { + ret = -ENOMEM; + goto out_put_table; + } } else { dev_err(dev, "Unsupported resource\n"); - return -EINVAL; + ret = -EINVAL; + goto out_put_table; } regs[i] = reg; @@ -385,8 +398,10 @@ static int wdat_wdt_probe(struct platform_device *pdev) } instr = devm_kzalloc(dev, sizeof(*instr), GFP_KERNEL); - if (!instr) - return -ENOMEM; + if (!instr) { + ret = -ENOMEM; + goto out_put_table; + } INIT_LIST_HEAD(&instr->node); instr->entry = entries[i]; @@ -417,7 +432,8 @@ static int wdat_wdt_probe(struct platform_device *pdev) if (!instr->reg) { dev_err(dev, "I/O resource not found\n"); - return -EINVAL; + ret = -EINVAL; + goto out_put_table; } instructions = wdat->instructions[action]; @@ -425,8 +441,10 @@ static int wdat_wdt_probe(struct platform_device *pdev) instructions = devm_kzalloc(dev, sizeof(*instructions), GFP_KERNEL); - if (!instructions) - return -ENOMEM; + if (!instructions) { + ret = -ENOMEM; + goto out_put_table; + } INIT_LIST_HEAD(instructions); wdat->instructions[action] = instructions; @@ -443,7 +461,7 @@ static int wdat_wdt_probe(struct platform_device *pdev) ret = wdat_wdt_enable_reboot(wdat); if (ret) - return ret; + goto out_put_table; platform_set_drvdata(pdev, wdat); @@ -460,12 +478,16 @@ static int wdat_wdt_probe(struct platform_device *pdev) ret = wdat_wdt_set_timeout(&wdat->wdd, timeout); if (ret) - return ret; + goto out_put_table; watchdog_set_nowayout(&wdat->wdd, nowayout); watchdog_stop_on_reboot(&wdat->wdd); watchdog_stop_on_unregister(&wdat->wdd); - return devm_watchdog_register_device(dev, &wdat->wdd); + ret = devm_watchdog_register_device(dev, &wdat->wdd); + +out_put_table: + acpi_put_table((struct acpi_table_header *)tbl); + return ret; } static int wdat_wdt_suspend_noirq(struct device *dev) From 4d9bebb2e9b6ddcca359e8701e09ae8f4cfdf9ae Mon Sep 17 00:00:00 2001 From: Haotian Zhang Date: Wed, 5 Nov 2025 16:42:20 +0800 Subject: [PATCH 0662/3902] watchdog: starfive: Fix resource leak in probe error path [ Upstream commit 5bcc5786a0cfa9249ccbe539833040a6285d0de3 ] If pm_runtime_put_sync() fails after watchdog_register_device() succeeds, the probe function jumps to err_exit without unregistering the watchdog device. This leaves the watchdog registered in the subsystem while the driver fails to load, resulting in a resource leak. Add a new error label err_unregister_wdt to properly unregister the watchdog device. Fixes: 8bc22a2f1bf0 ("watchdog: starfive: Check pm_runtime_enabled() before decrementing usage counter") Signed-off-by: Haotian Zhang Reviewed-by: Wim Van Sebroeck Signed-off-by: Guenter Roeck Signed-off-by: Wim Van Sebroeck Signed-off-by: Sasha Levin --- drivers/watchdog/starfive-wdt.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/drivers/watchdog/starfive-wdt.c b/drivers/watchdog/starfive-wdt.c index 355918d62f63d5..ed71d3960a0f26 100644 --- a/drivers/watchdog/starfive-wdt.c +++ b/drivers/watchdog/starfive-wdt.c @@ -500,12 +500,14 @@ static int starfive_wdt_probe(struct platform_device *pdev) if (pm_runtime_enabled(&pdev->dev)) { ret = pm_runtime_put_sync(&pdev->dev); if (ret) - goto err_exit; + goto err_unregister_wdt; } } return 0; +err_unregister_wdt: + watchdog_unregister_device(&wdt->wdd); err_exit: starfive_wdt_disable_clock(wdt); pm_runtime_disable(&pdev->dev); From 97d817c1145036f451cae939e71297f9fa9348da Mon Sep 17 00:00:00 2001 From: Andy Shevchenko Date: Wed, 12 Nov 2025 15:55:08 +0100 Subject: [PATCH 0663/3902] iio: core: add missing mutex_destroy in iio_dev_release() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [ Upstream commit f5d203467a31798191365efeb16cd619d2c8f23a ] Add missing mutex_destroy() call in iio_dev_release() to properly clean up the mutex initialized in iio_device_alloc(). Ensure proper resource cleanup and follows kernel practices. Found by code review. While at it, create a lockdep key before mutex initialisation. This will help with converting it to the better API in the future. Fixes: 847ec80bbaa7 ("Staging: IIO: core support for device registration and management") Fixes: ac917a81117c ("staging:iio:core set the iio_dev.info pointer to null on unregister under lock.") Signed-off-by: Andy Shevchenko Reviewed-by: Nuno Sá Signed-off-by: Jonathan Cameron Signed-off-by: Sasha Levin --- drivers/iio/industrialio-core.c | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/drivers/iio/industrialio-core.c b/drivers/iio/industrialio-core.c index 88c3d585a1bd0c..93d6e5b101cf18 100644 --- a/drivers/iio/industrialio-core.c +++ b/drivers/iio/industrialio-core.c @@ -1654,6 +1654,9 @@ static void iio_dev_release(struct device *device) iio_device_detach_buffers(indio_dev); + mutex_destroy(&iio_dev_opaque->info_exist_lock); + mutex_destroy(&iio_dev_opaque->mlock); + lockdep_unregister_key(&iio_dev_opaque->mlock_key); ida_free(&iio_ida, iio_dev_opaque->id); @@ -1698,8 +1701,7 @@ struct iio_dev *iio_device_alloc(struct device *parent, int sizeof_priv) indio_dev->dev.type = &iio_device_type; indio_dev->dev.bus = &iio_bus_type; device_initialize(&indio_dev->dev); - mutex_init(&iio_dev_opaque->mlock); - mutex_init(&iio_dev_opaque->info_exist_lock); + INIT_LIST_HEAD(&iio_dev_opaque->channel_attr_list); iio_dev_opaque->id = ida_alloc(&iio_ida, GFP_KERNEL); @@ -1722,6 +1724,9 @@ struct iio_dev *iio_device_alloc(struct device *parent, int sizeof_priv) lockdep_register_key(&iio_dev_opaque->mlock_key); lockdep_set_class(&iio_dev_opaque->mlock, &iio_dev_opaque->mlock_key); + mutex_init(&iio_dev_opaque->mlock); + mutex_init(&iio_dev_opaque->info_exist_lock); + return indio_dev; } EXPORT_SYMBOL(iio_device_alloc); From e1cc8886b628c4ce2dedef4b41383bb2e3fef16f Mon Sep 17 00:00:00 2001 From: Andy Shevchenko Date: Wed, 12 Nov 2025 15:55:09 +0100 Subject: [PATCH 0664/3902] iio: core: Clean up device correctly on iio_device_alloc() failure MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [ Upstream commit b0e6871415b25f5e84a79621834e3d0c9d4627a6 ] Once we called device_initialize() we have to call put_device() on it. Refactor the code to make it in the right order. Fixes: fe6f45f6ba22 ("iio: core: check return value when calling dev_set_name()") Fixes: 847ec80bbaa7 ("Staging: IIO: core support for device registration and management") Signed-off-by: Andy Shevchenko Reviewed-by: Nuno Sá Signed-off-by: Jonathan Cameron Signed-off-by: Sasha Levin --- drivers/iio/industrialio-core.c | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/drivers/iio/industrialio-core.c b/drivers/iio/industrialio-core.c index 93d6e5b101cf18..5d2f35cf18bc3f 100644 --- a/drivers/iio/industrialio-core.c +++ b/drivers/iio/industrialio-core.c @@ -1697,11 +1697,6 @@ struct iio_dev *iio_device_alloc(struct device *parent, int sizeof_priv) ACCESS_PRIVATE(indio_dev, priv) = (char *)iio_dev_opaque + ALIGN(sizeof(*iio_dev_opaque), IIO_DMA_MINALIGN); - indio_dev->dev.parent = parent; - indio_dev->dev.type = &iio_device_type; - indio_dev->dev.bus = &iio_bus_type; - device_initialize(&indio_dev->dev); - INIT_LIST_HEAD(&iio_dev_opaque->channel_attr_list); iio_dev_opaque->id = ida_alloc(&iio_ida, GFP_KERNEL); @@ -1727,6 +1722,11 @@ struct iio_dev *iio_device_alloc(struct device *parent, int sizeof_priv) mutex_init(&iio_dev_opaque->mlock); mutex_init(&iio_dev_opaque->info_exist_lock); + indio_dev->dev.parent = parent; + indio_dev->dev.type = &iio_device_type; + indio_dev->dev.bus = &iio_bus_type; + device_initialize(&indio_dev->dev); + return indio_dev; } EXPORT_SYMBOL(iio_device_alloc); From 30db8309b703a80b0e6afca1d0797635c96c5a87 Mon Sep 17 00:00:00 2001 From: Al Viro Date: Sun, 26 Oct 2025 02:38:43 -0400 Subject: [PATCH 0665/3902] fuse_ctl_add_conn(): fix nlink breakage in case of early failure [ Upstream commit c460192aae197df1b4db1dca493c35ad529f1b64 ] fuse_ctl_remove_conn() used to decrement the link count of root manually; that got subsumed by simple_recursive_removal(), but in case when subdirectory creation has failed the latter won't get called. Just move the modification of parent's link count into fuse_ctl_add_dentry() to keep the things simple. Allows to get rid of the nlink argument as well... Fixes: fcaac5b42768 "fuse_ctl: use simple_recursive_removal()" Acked-by: Miklos Szeredi Signed-off-by: Al Viro Signed-off-by: Sasha Levin --- fs/fuse/control.c | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/fs/fuse/control.c b/fs/fuse/control.c index bb407705603c29..5247df896c5d01 100644 --- a/fs/fuse/control.c +++ b/fs/fuse/control.c @@ -205,8 +205,7 @@ static const struct file_operations fuse_conn_congestion_threshold_ops = { static struct dentry *fuse_ctl_add_dentry(struct dentry *parent, struct fuse_conn *fc, - const char *name, - int mode, int nlink, + const char *name, int mode, const struct inode_operations *iop, const struct file_operations *fop) { @@ -232,7 +231,10 @@ static struct dentry *fuse_ctl_add_dentry(struct dentry *parent, if (iop) inode->i_op = iop; inode->i_fop = fop; - set_nlink(inode, nlink); + if (S_ISDIR(mode)) { + inc_nlink(d_inode(parent)); + inc_nlink(inode); + } inode->i_private = fc; d_add(dentry, inode); @@ -252,22 +254,21 @@ int fuse_ctl_add_conn(struct fuse_conn *fc) return 0; parent = fuse_control_sb->s_root; - inc_nlink(d_inode(parent)); sprintf(name, "%u", fc->dev); - parent = fuse_ctl_add_dentry(parent, fc, name, S_IFDIR | 0500, 2, + parent = fuse_ctl_add_dentry(parent, fc, name, S_IFDIR | 0500, &simple_dir_inode_operations, &simple_dir_operations); if (!parent) goto err; - if (!fuse_ctl_add_dentry(parent, fc, "waiting", S_IFREG | 0400, 1, + if (!fuse_ctl_add_dentry(parent, fc, "waiting", S_IFREG | 0400, NULL, &fuse_ctl_waiting_ops) || - !fuse_ctl_add_dentry(parent, fc, "abort", S_IFREG | 0200, 1, + !fuse_ctl_add_dentry(parent, fc, "abort", S_IFREG | 0200, NULL, &fuse_ctl_abort_ops) || !fuse_ctl_add_dentry(parent, fc, "max_background", S_IFREG | 0600, - 1, NULL, &fuse_conn_max_background_ops) || + NULL, &fuse_conn_max_background_ops) || !fuse_ctl_add_dentry(parent, fc, "congestion_threshold", - S_IFREG | 0600, 1, NULL, + S_IFREG | 0600, NULL, &fuse_conn_congestion_threshold_ops)) goto err; From bc2b1bbc0329900b97034b6ad3728111fb4f4f01 Mon Sep 17 00:00:00 2001 From: Al Viro Date: Sat, 25 Oct 2025 18:13:48 -0400 Subject: [PATCH 0666/3902] tracefs: fix a leak in eventfs_create_events_dir() [ Upstream commit 798a401660a151633cb171738a72a8f1efb9b0b4 ] If we have LOCKDOWN_TRACEFS, the function bails out - *after* having locked the parent directory and without bothering to undo that. Just check it before tracefs_start_creating()... Fixes: e24709454c45 "tracefs/eventfs: Add missing lockdown checks" Acked-by: Steven Rostedt (Google) Signed-off-by: Al Viro Signed-off-by: Sasha Levin --- fs/tracefs/event_inode.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/fs/tracefs/event_inode.c b/fs/tracefs/event_inode.c index 8705c77a9e75a1..93c231601c8e23 100644 --- a/fs/tracefs/event_inode.c +++ b/fs/tracefs/event_inode.c @@ -757,7 +757,7 @@ struct eventfs_inode *eventfs_create_events_dir(const char *name, struct dentry const struct eventfs_entry *entries, int size, void *data) { - struct dentry *dentry = tracefs_start_creating(name, parent); + struct dentry *dentry; struct eventfs_root_inode *rei; struct eventfs_inode *ei; struct tracefs_inode *ti; @@ -768,6 +768,7 @@ struct eventfs_inode *eventfs_create_events_dir(const char *name, struct dentry if (security_locked_down(LOCKDOWN_TRACEFS)) return NULL; + dentry = tracefs_start_creating(name, parent); if (IS_ERR(dentry)) return ERR_CAST(dentry); From f677cd77319bcb0b03db4c9a2417db5054c44221 Mon Sep 17 00:00:00 2001 From: Sergey Bashirov Date: Fri, 3 Oct 2025 12:11:03 +0300 Subject: [PATCH 0667/3902] NFSD/blocklayout: Fix minlength check in proc_layoutget [ Upstream commit 3524b021b0ec620a76c89aee78e9d4b4130fb711 ] The extent returned by the file system may have a smaller offset than the segment offset requested by the client. In this case, the minimum segment length must be checked against the requested range. Otherwise, the client may not be able to continue the read/write operation. Fixes: 8650b8a05850 ("nfsd: pNFS block layout driver") Signed-off-by: Sergey Bashirov Reviewed-by: Christoph Hellwig Signed-off-by: Chuck Lever Signed-off-by: Sasha Levin --- fs/nfsd/blocklayout.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/fs/nfsd/blocklayout.c b/fs/nfsd/blocklayout.c index fde5539cf6a690..425648565ab2d4 100644 --- a/fs/nfsd/blocklayout.c +++ b/fs/nfsd/blocklayout.c @@ -23,6 +23,7 @@ nfsd4_block_proc_layoutget(struct svc_rqst *rqstp, struct inode *inode, { struct nfsd4_layout_seg *seg = &args->lg_seg; struct super_block *sb = inode->i_sb; + u64 length; u32 block_size = i_blocksize(inode); struct pnfs_block_extent *bex; struct iomap iomap; @@ -56,7 +57,8 @@ nfsd4_block_proc_layoutget(struct svc_rqst *rqstp, struct inode *inode, goto out_error; } - if (iomap.length < args->lg_minlength) { + length = iomap.offset + iomap.length - seg->offset; + if (length < args->lg_minlength) { dprintk("pnfsd: extent smaller than minlength\n"); goto out_layoutunavailable; } From 8f1db86ea95c1a4521cf744d9e86a46cc8e9c553 Mon Sep 17 00:00:00 2001 From: Markus Niebel Date: Thu, 30 Oct 2025 13:49:08 +0100 Subject: [PATCH 0668/3902] arm64: dts: imx95-tqma9596sa: fix TPM5 pinctrl node name [ Upstream commit 046cb64923e8c05a8fb656baffcd8c3fc67fb688 ] tpm4grp will be overwritten. Fix node name Fixes: 91d1ff322c47 ("arm64: dt: imx95: Add TQMa95xxSA") Signed-off-by: Markus Niebel Signed-off-by: Alexander Stein Signed-off-by: Shawn Guo Signed-off-by: Sasha Levin --- arch/arm64/boot/dts/freescale/imx95-tqma9596sa.dtsi | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/arch/arm64/boot/dts/freescale/imx95-tqma9596sa.dtsi b/arch/arm64/boot/dts/freescale/imx95-tqma9596sa.dtsi index 180124cc5bce1f..c3bb61ea679617 100644 --- a/arch/arm64/boot/dts/freescale/imx95-tqma9596sa.dtsi +++ b/arch/arm64/boot/dts/freescale/imx95-tqma9596sa.dtsi @@ -617,7 +617,7 @@ fsl,pins = ; }; - pinctrl_tpm5: tpm4grp { + pinctrl_tpm5: tpm5grp { fsl,pins = ; }; From e5e0f94996f0c522de94b652ffacf65a6c681e8a Mon Sep 17 00:00:00 2001 From: Alexander Stein Date: Thu, 30 Oct 2025 13:49:09 +0100 Subject: [PATCH 0669/3902] arm64: dts: imx95-tqma9596sa: reduce maximum FlexSPI frequency to 66MHz [ Upstream commit 461be3802562b2d41250b40868310579a32f32c1 ] 66 MHz is the maximum FlexPI clock frequency in normal/overdrive mode when RXCLKSRC = 0 (Default) Fixes: 91d1ff322c47 ("arm64: dt: imx95: Add TQMa95xxSA") Signed-off-by: Alexander Stein Signed-off-by: Shawn Guo Signed-off-by: Sasha Levin --- arch/arm64/boot/dts/freescale/imx95-tqma9596sa.dtsi | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/arch/arm64/boot/dts/freescale/imx95-tqma9596sa.dtsi b/arch/arm64/boot/dts/freescale/imx95-tqma9596sa.dtsi index c3bb61ea679617..16c40d11d3b5d8 100644 --- a/arch/arm64/boot/dts/freescale/imx95-tqma9596sa.dtsi +++ b/arch/arm64/boot/dts/freescale/imx95-tqma9596sa.dtsi @@ -115,7 +115,7 @@ flash0: flash@0 { compatible = "jedec,spi-nor"; reg = <0>; - spi-max-frequency = <80000000>; + spi-max-frequency = <66000000>; spi-tx-bus-width = <4>; spi-rx-bus-width = <4>; vcc-supply = <®_1v8>; From d36f07b8e59054fe6d35b7c6eaa623d6004c8449 Mon Sep 17 00:00:00 2001 From: Guenter Roeck Date: Fri, 14 Nov 2025 15:54:32 -0800 Subject: [PATCH 0670/3902] block/blk-throttle: Fix throttle slice time for SSDs [ Upstream commit f76581f9f1d29e32e120b0242974ba266e79de58 ] Commit d61fcfa4bb18 ("blk-throttle: choose a small throtl_slice for SSD") introduced device type specific throttle slices if BLK_DEV_THROTTLING_LOW was enabled. Commit bf20ab538c81 ("blk-throttle: remove CONFIG_BLK_DEV_THROTTLING_LOW") removed support for BLK_DEV_THROTTLING_LOW, but left the device type specific throttle slices in place. This effectively changed throttling behavior on systems with SSD which now use a different and non-configurable slice time compared to non-SSD devices. Practical impact is that throughput tests with low configured throttle values (65536 bps) experience less than expected throughput on SSDs, presumably due to rounding errors associated with the small throttle slice time used for those devices. The same tests pass when setting the throttle values to 65536 * 4 = 262144 bps. The original code sets the throttle slice time to DFL_THROTL_SLICE_HD if CONFIG_BLK_DEV_THROTTLING_LOW is disabled. Restore that code to fix the problem. With that, DFL_THROTL_SLICE_SSD is no longer necessary. Revert to the original code and re-introduce DFL_THROTL_SLICE to replace both DFL_THROTL_SLICE_HD and DFL_THROTL_SLICE_SSD. This effectively reverts commit d61fcfa4bb18 ("blk-throttle: choose a small throtl_slice for SSD"). While at it, also remove MAX_THROTL_SLICE since it is not used anymore. Fixes: bf20ab538c81 ("blk-throttle: remove CONFIG_BLK_DEV_THROTTLING_LOW") Cc: Yu Kuai Cc: Tejun Heo Signed-off-by: Guenter Roeck Signed-off-by: Khazhismel Kumykov Reviewed-by: Yu Kuai Signed-off-by: Jens Axboe Signed-off-by: Sasha Levin --- block/blk-throttle.c | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/block/blk-throttle.c b/block/blk-throttle.c index 2c5b64b1a724a7..c19d052a8f2f12 100644 --- a/block/blk-throttle.c +++ b/block/blk-throttle.c @@ -22,9 +22,7 @@ #define THROTL_QUANTUM 32 /* Throttling is performed over a slice and after that slice is renewed */ -#define DFL_THROTL_SLICE_HD (HZ / 10) -#define DFL_THROTL_SLICE_SSD (HZ / 50) -#define MAX_THROTL_SLICE (HZ) +#define DFL_THROTL_SLICE (HZ / 10) /* A workqueue to queue throttle related work */ static struct workqueue_struct *kthrotld_workqueue; @@ -1341,10 +1339,7 @@ static int blk_throtl_init(struct gendisk *disk) goto out; } - if (blk_queue_nonrot(q)) - td->throtl_slice = DFL_THROTL_SLICE_SSD; - else - td->throtl_slice = DFL_THROTL_SLICE_HD; + td->throtl_slice = DFL_THROTL_SLICE; td->track_bio_latency = !queue_is_mq(q); if (!td->track_bio_latency) blk_stat_enable_accounting(q); From 0bc9149da210df7e9a191dde246b89d9ad23be8a Mon Sep 17 00:00:00 2001 From: Huiwen He Date: Thu, 13 Nov 2025 01:04:11 +0800 Subject: [PATCH 0671/3902] drm/msm: Fix NULL pointer dereference in crashstate_get_vm_logs() [ Upstream commit 3099e0247e3217e1b39c1c61766e06ec3d13835f ] crashstate_get_vm_logs() did not check the return value of kmalloc_array(). In low-memory situations, kmalloc_array() may return NULL, leading to a NULL pointer dereference when the function later accesses state->vm_logs. Fix this by checking the return value of kmalloc_array() and setting state->nr_vm_logs to 0 if allocation fails. Fixes: 9edc52967cc7 ("drm/msm: Add VM logging for VM_BIND updates") Signed-off-by: Huiwen He Patchwork: https://patchwork.freedesktop.org/patch/687555/ Signed-off-by: Rob Clark Signed-off-by: Sasha Levin --- drivers/gpu/drm/msm/msm_gpu.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/drivers/gpu/drm/msm/msm_gpu.c b/drivers/gpu/drm/msm/msm_gpu.c index 17759abc46d7d7..e23f70fbc8cb24 100644 --- a/drivers/gpu/drm/msm/msm_gpu.c +++ b/drivers/gpu/drm/msm/msm_gpu.c @@ -348,6 +348,10 @@ static void crashstate_get_vm_logs(struct msm_gpu_state *state, struct msm_gem_v state->vm_logs = kmalloc_array( state->nr_vm_logs, sizeof(vm->log[0]), GFP_KERNEL); + if (!state->vm_logs) { + state->nr_vm_logs = 0; + } + for (int i = 0; i < state->nr_vm_logs; i++) { int idx = (i + first) & vm_log_mask; From 966fcbb8c80318aa581e97da32056ffbab6138fe Mon Sep 17 00:00:00 2001 From: Huiwen He Date: Thu, 13 Nov 2025 01:19:47 +0800 Subject: [PATCH 0672/3902] drm/msm: fix missing NULL check after kcalloc in crashstate_get_bos() [ Upstream commit 3065e6a4d3594b42dae6176b3e2c0c3563cf94b8 ] The crashstate_get_bos() function allocates memory for `state->bos` using kcalloc(), but the vmbind path does not check for allocation failure before dereferencing it in the following drm_gpuvm_for_each_va() loop. This could lead to a NULL pointer dereference if memory allocation fails. Fix this by wrapping the drm_gpuvm_for_each_va() loop with a NULL check on state->bos, similar to the safety check in the non-vmbind path. Fixes: af9aa6f316b3d ("drm/msm: Crashdump support for sparse") Signed-off-by: Huiwen He Patchwork: https://patchwork.freedesktop.org/patch/687556/ Signed-off-by: Rob Clark Signed-off-by: Sasha Levin --- drivers/gpu/drm/msm/msm_gpu.c | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/drivers/gpu/drm/msm/msm_gpu.c b/drivers/gpu/drm/msm/msm_gpu.c index e23f70fbc8cb24..dd0605fe1243da 100644 --- a/drivers/gpu/drm/msm/msm_gpu.c +++ b/drivers/gpu/drm/msm/msm_gpu.c @@ -287,16 +287,17 @@ static void crashstate_get_bos(struct msm_gpu_state *state, struct msm_gem_submi state->bos = kcalloc(cnt, sizeof(struct msm_gpu_state_bo), GFP_KERNEL); - drm_gpuvm_for_each_va (vma, submit->vm) { - bool dump = rd_full || (vma->flags & MSM_VMA_DUMP); + if (state->bos) + drm_gpuvm_for_each_va(vma, submit->vm) { + bool dump = rd_full || (vma->flags & MSM_VMA_DUMP); - /* Skip MAP_NULL/PRR VMAs: */ - if (!vma->gem.obj) - continue; + /* Skip MAP_NULL/PRR VMAs: */ + if (!vma->gem.obj) + continue; - msm_gpu_crashstate_get_bo(state, vma->gem.obj, vma->va.addr, - dump, vma->gem.offset, vma->va.range); - } + msm_gpu_crashstate_get_bo(state, vma->gem.obj, vma->va.addr, + dump, vma->gem.offset, vma->va.range); + } drm_exec_fini(&exec); } else { From 7b1e3cb2070e2dc0bef3718fb483e1fdef587fab Mon Sep 17 00:00:00 2001 From: Dmitry Baryshkov Date: Thu, 13 Nov 2025 22:40:50 +0200 Subject: [PATCH 0673/3902] drm/msm/a2xx: stop over-complaining about the legacy firmware [ Upstream commit a3a22373fce576560757f5616eb48dbf85891d9c ] If the rootfs have a legacy A200 firmware, currently the driver will complain each time the hw is reinited (which can happen a lot). E.g. with GL testsuite the hw is reinited after each test, spamming the console. Make sure that the message is printed only once: when we detect the firmware that doesn't support protection. Fixes: 302295070d3c ("drm/msm/a2xx: support loading legacy (iMX) firmware") Signed-off-by: Dmitry Baryshkov Patchwork: https://patchwork.freedesktop.org/patch/688098/ Signed-off-by: Rob Clark Signed-off-by: Sasha Levin --- drivers/gpu/drm/msm/adreno/a2xx_gpu.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/gpu/drm/msm/adreno/a2xx_gpu.c b/drivers/gpu/drm/msm/adreno/a2xx_gpu.c index ec38db45d8a366..963c0f669ee50d 100644 --- a/drivers/gpu/drm/msm/adreno/a2xx_gpu.c +++ b/drivers/gpu/drm/msm/adreno/a2xx_gpu.c @@ -234,7 +234,7 @@ static int a2xx_hw_init(struct msm_gpu *gpu) * word (0x20xxxx for A200, 0x220xxx for A220, 0x225xxx for A225). * Older firmware files, which lack protection support, have 0 instead. */ - if (ptr[1] == 0) { + if (ptr[1] == 0 && !a2xx_gpu->protection_disabled) { dev_warn(gpu->dev->dev, "Legacy firmware detected, disabling protection support\n"); a2xx_gpu->protection_disabled = true; From 0e82816b68968ae3011386cdb60da0305842ee04 Mon Sep 17 00:00:00 2001 From: Christian Bruel Date: Fri, 14 Nov 2025 08:45:52 +0100 Subject: [PATCH 0674/3902] PCI: stm32: Fix LTSSM EP race with start link [ Upstream commit fa81d6099007728cae39c6f937d83903bbddab5e ] If the host has deasserted PERST# and started link training before the link is started on EP side, enabling LTSSM before the endpoint registers are initialized in the perst_irq handler results in probing incorrect values. Thus, wait for the PERST# level-triggered interrupt to start link training at the end of initialization and cleanup the stm32_pcie_[start stop]_link functions. Fixes: 151f3d29baf4 ("PCI: stm32-ep: Add PCIe Endpoint support for STM32MP25") Signed-off-by: Christian Bruel [mani: added fixes tag] Signed-off-by: Manivannan Sadhasivam [bhelgaas: wrap line] Signed-off-by: Bjorn Helgaas Link: https://patch.msgid.link/20251114-perst_ep-v1-1-e7976317a890@foss.st.com Signed-off-by: Sasha Levin --- drivers/pci/controller/dwc/pcie-stm32-ep.c | 39 +++++----------------- 1 file changed, 8 insertions(+), 31 deletions(-) diff --git a/drivers/pci/controller/dwc/pcie-stm32-ep.c b/drivers/pci/controller/dwc/pcie-stm32-ep.c index 3400c7cd2d88a2..faa6433a784f37 100644 --- a/drivers/pci/controller/dwc/pcie-stm32-ep.c +++ b/drivers/pci/controller/dwc/pcie-stm32-ep.c @@ -37,36 +37,9 @@ static void stm32_pcie_ep_init(struct dw_pcie_ep *ep) dw_pcie_ep_reset_bar(pci, bar); } -static int stm32_pcie_enable_link(struct dw_pcie *pci) -{ - struct stm32_pcie *stm32_pcie = to_stm32_pcie(pci); - - regmap_update_bits(stm32_pcie->regmap, SYSCFG_PCIECR, - STM32MP25_PCIECR_LTSSM_EN, - STM32MP25_PCIECR_LTSSM_EN); - - return dw_pcie_wait_for_link(pci); -} - -static void stm32_pcie_disable_link(struct dw_pcie *pci) -{ - struct stm32_pcie *stm32_pcie = to_stm32_pcie(pci); - - regmap_update_bits(stm32_pcie->regmap, SYSCFG_PCIECR, STM32MP25_PCIECR_LTSSM_EN, 0); -} - static int stm32_pcie_start_link(struct dw_pcie *pci) { struct stm32_pcie *stm32_pcie = to_stm32_pcie(pci); - int ret; - - dev_dbg(pci->dev, "Enable link\n"); - - ret = stm32_pcie_enable_link(pci); - if (ret) { - dev_err(pci->dev, "PCIe cannot establish link: %d\n", ret); - return ret; - } enable_irq(stm32_pcie->perst_irq); @@ -77,11 +50,7 @@ static void stm32_pcie_stop_link(struct dw_pcie *pci) { struct stm32_pcie *stm32_pcie = to_stm32_pcie(pci); - dev_dbg(pci->dev, "Disable link\n"); - disable_irq(stm32_pcie->perst_irq); - - stm32_pcie_disable_link(pci); } static int stm32_pcie_raise_irq(struct dw_pcie_ep *ep, u8 func_no, @@ -152,6 +121,9 @@ static void stm32_pcie_perst_assert(struct dw_pcie *pci) dev_dbg(dev, "PERST asserted by host\n"); + regmap_update_bits(stm32_pcie->regmap, SYSCFG_PCIECR, + STM32MP25_PCIECR_LTSSM_EN, 0); + pci_epc_deinit_notify(ep->epc); stm32_pcie_disable_resources(stm32_pcie); @@ -192,6 +164,11 @@ static void stm32_pcie_perst_deassert(struct dw_pcie *pci) pci_epc_init_notify(ep->epc); + /* Enable link training */ + regmap_update_bits(stm32_pcie->regmap, SYSCFG_PCIECR, + STM32MP25_PCIECR_LTSSM_EN, + STM32MP25_PCIECR_LTSSM_EN); + return; err_disable_resources: From 9ea941e8bba68f9cefd4ed18663a3a8c85e836e1 Mon Sep 17 00:00:00 2001 From: Christian Bruel Date: Fri, 14 Nov 2025 09:08:05 +0100 Subject: [PATCH 0675/3902] PCI: stm32: Fix EP page_size alignment [ Upstream commit ff529a9307a03ec03ed9751da053b57149300053 ] pci_epc_mem_alloc_addr() allocates a CPU address from the ATU window phys base and a page number. Set the ep->page_size so the resulting CPU address is correctly aligned with the ATU required alignment. Fixes: 151f3d29baf4 ("PCI: stm32-ep: Add PCIe Endpoint support for STM32MP25") Signed-off-by: Christian Bruel [mani: added fixes tag] Signed-off-by: Manivannan Sadhasivam Signed-off-by: Bjorn Helgaas Link: https://patch.msgid.link/20251114-atu_align_ep-v1-1-88da5366fa04@foss.st.com Signed-off-by: Sasha Levin --- drivers/pci/controller/dwc/pcie-stm32-ep.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/drivers/pci/controller/dwc/pcie-stm32-ep.c b/drivers/pci/controller/dwc/pcie-stm32-ep.c index faa6433a784f37..7d48038d576d90 100644 --- a/drivers/pci/controller/dwc/pcie-stm32-ep.c +++ b/drivers/pci/controller/dwc/pcie-stm32-ep.c @@ -214,6 +214,8 @@ static int stm32_add_pcie_ep(struct stm32_pcie *stm32_pcie, ep->ops = &stm32_pcie_ep_ops; + ep->page_size = stm32_pcie_epc_features.align; + ret = dw_pcie_ep_init(ep); if (ret) { dev_err(dev, "Failed to initialize ep: %d\n", ret); From c9d1c4152e6d32fa74034464854bee262a60bc43 Mon Sep 17 00:00:00 2001 From: Abdun Nihaal Date: Fri, 14 Nov 2025 15:15:26 +0530 Subject: [PATCH 0676/3902] wifi: rtl818x: Fix potential memory leaks in rtl8180_init_rx_ring() [ Upstream commit 9b5b9c042b30befc5b37e4539ace95af70843473 ] In rtl8180_init_rx_ring(), memory is allocated for skb packets and DMA allocations in a loop. When an allocation fails, the previously successful allocations are not freed on exit. Fix that by jumping to err_free_rings label on error, which calls rtl8180_free_rx_ring() to free the allocations. Remove the free of rx_ring in rtl8180_init_rx_ring() error path, and set the freed priv->rx_buf entry to null, to avoid double free. Fixes: f653211197f3 ("Add rtl8180 wireless driver") Signed-off-by: Abdun Nihaal Reviewed-by: Ping-Ke Shih Signed-off-by: Ping-Ke Shih Link: https://patch.msgid.link/20251114094527.79842-1-nihaal@cse.iitm.ac.in Signed-off-by: Sasha Levin --- drivers/net/wireless/realtek/rtl818x/rtl8180/dev.c | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/drivers/net/wireless/realtek/rtl818x/rtl8180/dev.c b/drivers/net/wireless/realtek/rtl818x/rtl8180/dev.c index 2905baea62390d..070c0431c4821c 100644 --- a/drivers/net/wireless/realtek/rtl818x/rtl8180/dev.c +++ b/drivers/net/wireless/realtek/rtl818x/rtl8180/dev.c @@ -1023,9 +1023,6 @@ static int rtl8180_init_rx_ring(struct ieee80211_hw *dev) dma_addr_t *mapping; entry = priv->rx_ring + priv->rx_ring_sz*i; if (!skb) { - dma_free_coherent(&priv->pdev->dev, - priv->rx_ring_sz * 32, - priv->rx_ring, priv->rx_ring_dma); wiphy_err(dev->wiphy, "Cannot allocate RX skb\n"); return -ENOMEM; } @@ -1037,9 +1034,7 @@ static int rtl8180_init_rx_ring(struct ieee80211_hw *dev) if (dma_mapping_error(&priv->pdev->dev, *mapping)) { kfree_skb(skb); - dma_free_coherent(&priv->pdev->dev, - priv->rx_ring_sz * 32, - priv->rx_ring, priv->rx_ring_dma); + priv->rx_buf[i] = NULL; wiphy_err(dev->wiphy, "Cannot map DMA for RX skb\n"); return -ENOMEM; } @@ -1130,7 +1125,7 @@ static int rtl8180_start(struct ieee80211_hw *dev) ret = rtl8180_init_rx_ring(dev); if (ret) - return ret; + goto err_free_rings; for (i = 0; i < (dev->queues + 1); i++) if ((ret = rtl8180_init_tx_ring(dev, i, 16))) From d2b57ba171648096b77c493d4e142e1b6f26e546 Mon Sep 17 00:00:00 2001 From: Inochi Amaoto Date: Fri, 14 Nov 2025 08:38:04 +0800 Subject: [PATCH 0677/3902] net: phy: Add helper for fixing RGMII PHY mode based on internal mac delay [ Upstream commit 24afd7827efb7c69adfc41835390470e3eec4740 ] The "phy-mode" property of devicetree indicates whether the PCB has delay now, which means the mac needs to modify the PHY mode based on whether there is an internal delay in the mac. This modification is similar for many ethernet drivers. To simplify code, define the helper phy_fix_phy_mode_for_mac_delays(speed, mac_txid, mac_rxid) to fix PHY mode based on whether mac adds internal delay. Suggested-by: Russell King (Oracle) Signed-off-by: Inochi Amaoto Reviewed-by: Maxime Chevallier Reviewed-by: Simon Horman Link: https://patch.msgid.link/20251114003805.494387-3-inochiama@gmail.com Signed-off-by: Jakub Kicinski Stable-dep-of: db37c6e510de ("net: stmmac: dwmac-sophgo: Add phy interface filter") Signed-off-by: Sasha Levin --- drivers/net/phy/phy-core.c | 43 ++++++++++++++++++++++++++++++++++++++ include/linux/phy.h | 3 +++ 2 files changed, 46 insertions(+) diff --git a/drivers/net/phy/phy-core.c b/drivers/net/phy/phy-core.c index 605ca20ae192d7..0c63e6ba2cb0cd 100644 --- a/drivers/net/phy/phy-core.c +++ b/drivers/net/phy/phy-core.c @@ -101,6 +101,49 @@ const char *phy_rate_matching_to_str(int rate_matching) } EXPORT_SYMBOL_GPL(phy_rate_matching_to_str); +/** + * phy_fix_phy_mode_for_mac_delays - Convenience function for fixing PHY + * mode based on whether mac adds internal delay + * + * @interface: The current interface mode of the port + * @mac_txid: True if the mac adds internal tx delay + * @mac_rxid: True if the mac adds internal rx delay + * + * Return: fixed PHY mode, or PHY_INTERFACE_MODE_NA if the interface can + * not apply the internal delay + */ +phy_interface_t phy_fix_phy_mode_for_mac_delays(phy_interface_t interface, + bool mac_txid, bool mac_rxid) +{ + if (!phy_interface_mode_is_rgmii(interface)) + return interface; + + if (mac_txid && mac_rxid) { + if (interface == PHY_INTERFACE_MODE_RGMII_ID) + return PHY_INTERFACE_MODE_RGMII; + return PHY_INTERFACE_MODE_NA; + } + + if (mac_txid) { + if (interface == PHY_INTERFACE_MODE_RGMII_ID) + return PHY_INTERFACE_MODE_RGMII_RXID; + if (interface == PHY_INTERFACE_MODE_RGMII_TXID) + return PHY_INTERFACE_MODE_RGMII; + return PHY_INTERFACE_MODE_NA; + } + + if (mac_rxid) { + if (interface == PHY_INTERFACE_MODE_RGMII_ID) + return PHY_INTERFACE_MODE_RGMII_TXID; + if (interface == PHY_INTERFACE_MODE_RGMII_RXID) + return PHY_INTERFACE_MODE_RGMII; + return PHY_INTERFACE_MODE_NA; + } + + return interface; +} +EXPORT_SYMBOL_GPL(phy_fix_phy_mode_for_mac_delays); + /** * phy_interface_num_ports - Return the number of links that can be carried by * a given MAC-PHY physical link. Returns 0 if this is diff --git a/include/linux/phy.h b/include/linux/phy.h index 3c7634482356e2..0bc00a4cceb241 100644 --- a/include/linux/phy.h +++ b/include/linux/phy.h @@ -1813,6 +1813,9 @@ static inline bool phy_is_pseudo_fixed_link(struct phy_device *phydev) return phydev->is_pseudo_fixed_link; } +phy_interface_t phy_fix_phy_mode_for_mac_delays(phy_interface_t interface, + bool mac_txid, bool mac_rxid); + int phy_save_page(struct phy_device *phydev); int phy_select_page(struct phy_device *phydev, int page); int phy_restore_page(struct phy_device *phydev, int oldpage, int ret); From 26815698b6fbdf97f74f0bed9747543adc7bbbf5 Mon Sep 17 00:00:00 2001 From: Inochi Amaoto Date: Fri, 14 Nov 2025 08:38:05 +0800 Subject: [PATCH 0678/3902] net: stmmac: dwmac-sophgo: Add phy interface filter [ Upstream commit db37c6e510deabc9b0ee27c08f1c5aaa19f2e8ef ] As the SG2042 has an internal rx delay, the delay should be removed when initializing the mac, otherwise the phy will be misconfigurated. Fixes: 543009e2d4cd ("net: stmmac: dwmac-sophgo: Add support for Sophgo SG2042 SoC") Signed-off-by: Inochi Amaoto Tested-by: Han Gao Reviewed-by: Andrew Lunn Reviewed-by: Simon Horman Link: https://patch.msgid.link/20251114003805.494387-4-inochiama@gmail.com Signed-off-by: Jakub Kicinski Signed-off-by: Sasha Levin --- .../ethernet/stmicro/stmmac/dwmac-sophgo.c | 20 ++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/drivers/net/ethernet/stmicro/stmmac/dwmac-sophgo.c b/drivers/net/ethernet/stmicro/stmmac/dwmac-sophgo.c index 3b7947a7a7ba70..fcdda2401968b5 100644 --- a/drivers/net/ethernet/stmicro/stmmac/dwmac-sophgo.c +++ b/drivers/net/ethernet/stmicro/stmmac/dwmac-sophgo.c @@ -7,11 +7,16 @@ #include #include +#include #include #include #include "stmmac_platform.h" +struct sophgo_dwmac_data { + bool has_internal_rx_delay; +}; + static int sophgo_sg2044_dwmac_init(struct platform_device *pdev, struct plat_stmmacenet_data *plat_dat, struct stmmac_resources *stmmac_res) @@ -32,6 +37,7 @@ static int sophgo_sg2044_dwmac_init(struct platform_device *pdev, static int sophgo_dwmac_probe(struct platform_device *pdev) { struct plat_stmmacenet_data *plat_dat; + const struct sophgo_dwmac_data *data; struct stmmac_resources stmmac_res; struct device *dev = &pdev->dev; int ret; @@ -50,11 +56,23 @@ static int sophgo_dwmac_probe(struct platform_device *pdev) if (ret) return ret; + data = device_get_match_data(&pdev->dev); + if (data && data->has_internal_rx_delay) { + plat_dat->phy_interface = phy_fix_phy_mode_for_mac_delays(plat_dat->phy_interface, + false, true); + if (plat_dat->phy_interface == PHY_INTERFACE_MODE_NA) + return -EINVAL; + } + return stmmac_dvr_probe(dev, plat_dat, &stmmac_res); } +static const struct sophgo_dwmac_data sg2042_dwmac_data = { + .has_internal_rx_delay = true, +}; + static const struct of_device_id sophgo_dwmac_match[] = { - { .compatible = "sophgo,sg2042-dwmac" }, + { .compatible = "sophgo,sg2042-dwmac", .data = &sg2042_dwmac_data }, { .compatible = "sophgo,sg2044-dwmac" }, { /* sentinel */ } }; From 2579c356ccd35d06238b176e4b460978186d804b Mon Sep 17 00:00:00 2001 From: Pu Lehui Date: Sat, 15 Nov 2025 10:23:43 +0000 Subject: [PATCH 0679/3902] bpf: Fix invalid prog->stats access when update_effective_progs fails [ Upstream commit 7dc211c1159d991db609bdf4b0fb9033c04adcbc ] Syzkaller triggers an invalid memory access issue following fault injection in update_effective_progs. The issue can be described as follows: __cgroup_bpf_detach update_effective_progs compute_effective_progs bpf_prog_array_alloc <-- fault inject purge_effective_progs /* change to dummy_bpf_prog */ array->items[index] = &dummy_bpf_prog.prog ---softirq start--- __do_softirq ... __cgroup_bpf_run_filter_skb __bpf_prog_run_save_cb bpf_prog_run stats = this_cpu_ptr(prog->stats) /* invalid memory access */ flags = u64_stats_update_begin_irqsave(&stats->syncp) ---softirq end--- static_branch_dec(&cgroup_bpf_enabled_key[atype]) The reason is that fault injection caused update_effective_progs to fail and then changed the original prog into dummy_bpf_prog.prog in purge_effective_progs. Then a softirq came, and accessing the members of dummy_bpf_prog.prog in the softirq triggers invalid mem access. To fix it, skip updating stats when stats is NULL. Fixes: 492ecee892c2 ("bpf: enable program stats") Signed-off-by: Pu Lehui Link: https://lore.kernel.org/r/20251115102343.2200727-1-pulehui@huaweicloud.com Signed-off-by: Alexei Starovoitov Signed-off-by: Sasha Levin --- include/linux/filter.h | 12 +++++++----- kernel/bpf/syscall.c | 3 +++ 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/include/linux/filter.h b/include/linux/filter.h index 973233b82dc1fd..569de3b14279af 100644 --- a/include/linux/filter.h +++ b/include/linux/filter.h @@ -712,11 +712,13 @@ static __always_inline u32 __bpf_prog_run(const struct bpf_prog *prog, ret = dfunc(ctx, prog->insnsi, prog->bpf_func); duration = sched_clock() - start; - stats = this_cpu_ptr(prog->stats); - flags = u64_stats_update_begin_irqsave(&stats->syncp); - u64_stats_inc(&stats->cnt); - u64_stats_add(&stats->nsecs, duration); - u64_stats_update_end_irqrestore(&stats->syncp, flags); + if (likely(prog->stats)) { + stats = this_cpu_ptr(prog->stats); + flags = u64_stats_update_begin_irqsave(&stats->syncp); + u64_stats_inc(&stats->cnt); + u64_stats_add(&stats->nsecs, duration); + u64_stats_update_end_irqrestore(&stats->syncp, flags); + } } else { ret = dfunc(ctx, prog->insnsi, prog->bpf_func); } diff --git a/kernel/bpf/syscall.c b/kernel/bpf/syscall.c index 8a129746bd6cc7..15f9afdbfc275a 100644 --- a/kernel/bpf/syscall.c +++ b/kernel/bpf/syscall.c @@ -2462,6 +2462,9 @@ void notrace bpf_prog_inc_misses_counter(struct bpf_prog *prog) struct bpf_prog_stats *stats; unsigned int flags; + if (unlikely(!prog->stats)) + return; + stats = this_cpu_ptr(prog->stats); flags = u64_stats_update_begin_irqsave(&stats->syncp); u64_stats_inc(&stats->misses); From 1fcbf18558c5df09ffb4d75c811471055edbcb62 Mon Sep 17 00:00:00 2001 From: "Ritesh Harjani (IBM)" Date: Thu, 30 Oct 2025 20:27:27 +0530 Subject: [PATCH 0680/3902] powerpc/64s/hash: Restrict stress_hpt_struct memblock region to within RMA limit [ Upstream commit 17b45ccf09882e0c808ad2cf62acdc90ad968746 ] When HV=0 & IR/DR=0, the Hash MMU is said to be in Virtual Real Addressing Mode during early boot. During this, we should ensure that memory region allocations for stress_hpt_struct should happen from within RMA region as otherwise the boot might get stuck while doing memset of this region. History behind why do we have RMA region limitation is better explained in these 2 patches [1] & [2]. This patch ensures that memset to stress_hpt_struct during early boot does not cross ppc64_rma_size boundary. [1]: https://lore.kernel.org/all/20190710052018.14628-1-sjitindarsingh@gmail.com/ [2]: https://lore.kernel.org/all/87wp54usvj.fsf@linux.vnet.ibm.com/ Fixes: 6b34a099faa12 ("powerpc/64s/hash: add stress_hpt kernel boot option to increase hash faults") Signed-off-by: Ritesh Harjani (IBM) Signed-off-by: Madhavan Srinivasan Link: https://patch.msgid.link/ada1173933ea7617a994d6ee3e54ced8797339fc.1761834163.git.ritesh.list@gmail.com Signed-off-by: Sasha Levin --- arch/powerpc/mm/book3s64/hash_utils.c | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/arch/powerpc/mm/book3s64/hash_utils.c b/arch/powerpc/mm/book3s64/hash_utils.c index 3aee3af614af89..c99be1286d517d 100644 --- a/arch/powerpc/mm/book3s64/hash_utils.c +++ b/arch/powerpc/mm/book3s64/hash_utils.c @@ -1302,11 +1302,14 @@ static void __init htab_initialize(void) unsigned long table; unsigned long pteg_count; unsigned long prot; - phys_addr_t base = 0, size = 0, end; + phys_addr_t base = 0, size = 0, end, limit = MEMBLOCK_ALLOC_ANYWHERE; u64 i; DBG(" -> htab_initialize()\n"); + if (firmware_has_feature(FW_FEATURE_LPAR)) + limit = ppc64_rma_size; + if (mmu_has_feature(MMU_FTR_1T_SEGMENT)) { mmu_kernel_ssize = MMU_SEGSIZE_1T; mmu_highuser_ssize = MMU_SEGSIZE_1T; @@ -1322,7 +1325,7 @@ static void __init htab_initialize(void) // Too early to use nr_cpu_ids, so use NR_CPUS tmp = memblock_phys_alloc_range(sizeof(struct stress_hpt_struct) * NR_CPUS, __alignof__(struct stress_hpt_struct), - 0, MEMBLOCK_ALLOC_ANYWHERE); + MEMBLOCK_LOW_LIMIT, limit); memset((void *)tmp, 0xff, sizeof(struct stress_hpt_struct) * NR_CPUS); stress_hpt_struct = __va(tmp); @@ -1356,11 +1359,10 @@ static void __init htab_initialize(void) mmu_hash_ops.hpte_clear_all(); #endif } else { - unsigned long limit = MEMBLOCK_ALLOC_ANYWHERE; table = memblock_phys_alloc_range(htab_size_bytes, htab_size_bytes, - 0, limit); + MEMBLOCK_LOW_LIMIT, limit); if (!table) panic("ERROR: Failed to allocate %pa bytes below %pa\n", &htab_size_bytes, &limit); From 6b405443b6ab2c1f4c2d10574ac66f4e00827ed9 Mon Sep 17 00:00:00 2001 From: "Ritesh Harjani (IBM)" Date: Thu, 30 Oct 2025 20:27:28 +0530 Subject: [PATCH 0681/3902] powerpc/64s/ptdump: Fix kernel_hash_pagetable dump for ISA v3.00 HPTE format [ Upstream commit eae40a6da63faa9fb63ff61f8fa2b3b57da78a84 ] HPTE format was changed since Power9 (ISA 3.0) onwards. While dumping kernel hash page tables, nothing gets printed on powernv P9+. This patch utilizes the helpers added in the patch tagged as fixes, to convert new format to old format and dump the hptes. This fix is only needed for native_find() (powernv), since pseries continues to work fine with the old format. Fixes: 6b243fcfb5f1e ("powerpc/64: Simplify adaptation to new ISA v3.00 HPTE format") Signed-off-by: Ritesh Harjani (IBM) Signed-off-by: Madhavan Srinivasan Link: https://patch.msgid.link/4c2bb9e5b3cfbc0dd80b61b67cdd3ccfc632684c.1761834163.git.ritesh.list@gmail.com Signed-off-by: Sasha Levin --- arch/powerpc/mm/ptdump/hashpagetable.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/arch/powerpc/mm/ptdump/hashpagetable.c b/arch/powerpc/mm/ptdump/hashpagetable.c index a6baa6166d9401..671d0dc00c6d07 100644 --- a/arch/powerpc/mm/ptdump/hashpagetable.c +++ b/arch/powerpc/mm/ptdump/hashpagetable.c @@ -216,6 +216,8 @@ static int native_find(unsigned long ea, int psize, bool primary, u64 *v, u64 vpn = hpt_vpn(ea, vsid, ssize); hash = hpt_hash(vpn, shift, ssize); want_v = hpte_encode_avpn(vpn, psize, ssize); + if (cpu_has_feature(CPU_FTR_ARCH_300)) + want_v = hpte_old_to_new_v(want_v); /* to check in the secondary hash table, we invert the hash */ if (!primary) @@ -229,6 +231,10 @@ static int native_find(unsigned long ea, int psize, bool primary, u64 *v, u64 /* HPTE matches */ *v = be64_to_cpu(hptep->v); *r = be64_to_cpu(hptep->r); + if (cpu_has_feature(CPU_FTR_ARCH_300)) { + *v = hpte_new_to_old_v(*v, *r); + *r = hpte_new_to_old_r(*r); + } return 0; } ++hpte_group; From 11e221124be0324e872d2fdf3d73e18a886214a4 Mon Sep 17 00:00:00 2001 From: Ovidiu Panait Date: Thu, 13 Nov 2025 11:27:20 +0000 Subject: [PATCH 0682/3902] net: stmmac: Fix VLAN 0 deletion in vlan_del_hw_rx_fltr() [ Upstream commit d9db25723677c3741a0cf3643f7f7429fc983921 ] When the "rx-vlan-filter" feature is enabled on a network device, the 8021q module automatically adds a VLAN 0 hardware filter when the device is brought administratively up. For stmmac, this causes vlan_add_hw_rx_fltr() to create a new entry for VID 0 in the mac_device_info->vlan_filter array, in the following format: VLAN_TAG_DATA_ETV | VLAN_TAG_DATA_VEN | vid Here, VLAN_TAG_DATA_VEN indicates that the hardware filter is enabled for that VID. However, on the delete path, vlan_del_hw_rx_fltr() searches the vlan_filter array by VID only, without verifying whether a VLAN entry is enabled. As a result, when the 8021q module attempts to remove VLAN 0, the function may mistakenly match a zero-initialized slot rather than the actual VLAN 0 entry, causing incorrect deletions and leaving stale entries in the hardware table. Fix this by verifying that the VLAN entry's enable bit (VLAN_TAG_DATA_VEN) is set before matching and deleting by VID. This ensures only active VLAN entries are removed and avoids leaving stale entries in the VLAN filter table, particularly for VLAN ID 0. Fixes: ed64639bc1e08 ("net: stmmac: Add support for VLAN Rx filtering") Signed-off-by: Ovidiu Panait Reviewed-by: Maxime Chevallier Link: https://patch.msgid.link/20251113112721.70500-2-ovidiu.panait.rb@renesas.com Signed-off-by: Paolo Abeni Signed-off-by: Sasha Levin --- drivers/net/ethernet/stmicro/stmmac/stmmac_vlan.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/drivers/net/ethernet/stmicro/stmmac/stmmac_vlan.c b/drivers/net/ethernet/stmicro/stmmac/stmmac_vlan.c index ff02a79c00d4f5..b18404dd5a8beb 100644 --- a/drivers/net/ethernet/stmicro/stmmac/stmmac_vlan.c +++ b/drivers/net/ethernet/stmicro/stmmac/stmmac_vlan.c @@ -122,7 +122,8 @@ static int vlan_del_hw_rx_fltr(struct net_device *dev, /* Extended Rx VLAN Filter Enable */ for (i = 0; i < hw->num_vlan; i++) { - if ((hw->vlan_filter[i] & VLAN_TAG_DATA_VID) == vid) { + if ((hw->vlan_filter[i] & VLAN_TAG_DATA_VEN) && + ((hw->vlan_filter[i] & VLAN_TAG_DATA_VID) == vid)) { ret = vlan_write_filter(dev, hw, i, 0); if (!ret) From 169361f0e0bca86883974441f981bb5296250415 Mon Sep 17 00:00:00 2001 From: Edward Adam Davis Date: Tue, 11 Nov 2025 19:13:56 +0800 Subject: [PATCH 0683/3902] fs/ntfs3: out1 also needs to put mi [ Upstream commit 4d78d1173a653acdaf7500a32b8dc530ca4ad075 ] After ntfs_look_free_mft() executes successfully, all subsequent code that fails to execute must put mi. Fixes: 4342306f0f0d ("fs/ntfs3: Add file operations and implementation") Signed-off-by: Edward Adam Davis Signed-off-by: Konstantin Komarov Signed-off-by: Sasha Levin --- fs/ntfs3/frecord.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fs/ntfs3/frecord.c b/fs/ntfs3/frecord.c index 8f9fe1d7a69081..a557e3ec0d4c49 100644 --- a/fs/ntfs3/frecord.c +++ b/fs/ntfs3/frecord.c @@ -1015,9 +1015,9 @@ static int ni_ins_attr_ext(struct ntfs_inode *ni, struct ATTR_LIST_ENTRY *le, out2: ni_remove_mi(ni, mi); - mi_put(mi); out1: + mi_put(mi); ntfs_mark_rec_free(sbi, rno, is_mft); out: From 4a3a5c2e93c71dcbe6ba00b87549365eb85130b6 Mon Sep 17 00:00:00 2001 From: Edward Adam Davis Date: Tue, 11 Nov 2025 19:05:42 +0800 Subject: [PATCH 0684/3902] fs/ntfs3: Prevent memory leaks in add sub record [ Upstream commit ccc4e86d1c24260c18ae94541198c3711c140da6 ] If a rb node with the same ino already exists in the rb tree, the newly alloced mft_inode in ni_add_subrecord() will not have its memory cleaned up, which leads to the memory leak issue reported by syzbot. The best option to avoid this issue is to put the newly alloced mft node when a rb node with the same ino already exists in the rb tree and return the rb node found in the rb tree to the parent layer. syzbot reported: BUG: memory leak unreferenced object 0xffff888110bef280 (size 128): backtrace (crc 126a088f): ni_add_subrecord+0x31/0x180 fs/ntfs3/frecord.c:317 ntfs_look_free_mft+0xf0/0x790 fs/ntfs3/fsntfs.c:715 BUG: memory leak unreferenced object 0xffff888109093400 (size 1024): backtrace (crc 7197c55e): mi_init+0x2b/0x50 fs/ntfs3/record.c:105 mi_format_new+0x40/0x220 fs/ntfs3/record.c:422 Fixes: 4342306f0f0d ("fs/ntfs3: Add file operations and implementation") Reported-by: syzbot+3932ccb896e06f7414c9@syzkaller.appspotmail.com Signed-off-by: Edward Adam Davis Signed-off-by: Konstantin Komarov Signed-off-by: Sasha Levin --- fs/ntfs3/frecord.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/fs/ntfs3/frecord.c b/fs/ntfs3/frecord.c index a557e3ec0d4c49..e5a005d216f316 100644 --- a/fs/ntfs3/frecord.c +++ b/fs/ntfs3/frecord.c @@ -325,8 +325,10 @@ bool ni_add_subrecord(struct ntfs_inode *ni, CLST rno, struct mft_inode **mi) mi_get_ref(&ni->mi, &m->mrec->parent_ref); - ni_add_mi(ni, m); - *mi = m; + *mi = ni_ins_mi(ni, &ni->mi_tree, m->rno, &m->node); + if (*mi != m) + mi_put(m); + return true; } From 295ba29e49f63f9d9b72b160ac466ffb174f7cb8 Mon Sep 17 00:00:00 2001 From: Jay Liu Date: Sun, 21 Sep 2025 13:53:05 +0800 Subject: [PATCH 0685/3902] drm/mediatek: Fix CCORR mtk_ctm_s31_32_to_s1_n function issue [ Upstream commit 20ac36b71c53b8c36c6903b5ca87c75226700a97 ] if matrixbit is 11, The range of color matrix is from 0 to (BIT(12) - 1). Values from 0 to (BIT(11) - 1) represent positive numbers, values from BIT(11) to (BIT(12) - 1) represent negative numbers. For example, -1 need converted to 8191. so convert S31.32 to HW Q2.11 format by drm_color_ctm_s31_32_to_qm_n, and set int_bits to 2. Fixes: 738ed4156fba ("drm/mediatek: Add matrix_bits private data for ccorr") Reviewed-by: AngeloGioacchino Del Regno Signed-off-by: Jay Liu Link: https://patchwork.kernel.org/project/dri-devel/patch/20250921055416.25588-2-jay.liu@mediatek.com/ Signed-off-by: Chun-Kuang Hu Signed-off-by: Sasha Levin --- drivers/gpu/drm/mediatek/mtk_disp_ccorr.c | 23 +---------------------- 1 file changed, 1 insertion(+), 22 deletions(-) diff --git a/drivers/gpu/drm/mediatek/mtk_disp_ccorr.c b/drivers/gpu/drm/mediatek/mtk_disp_ccorr.c index 10d60d2c2a568e..6d7bf4afa78d3a 100644 --- a/drivers/gpu/drm/mediatek/mtk_disp_ccorr.c +++ b/drivers/gpu/drm/mediatek/mtk_disp_ccorr.c @@ -80,27 +80,6 @@ void mtk_ccorr_stop(struct device *dev) writel_relaxed(0x0, ccorr->regs + DISP_CCORR_EN); } -/* Converts a DRM S31.32 value to the HW S1.n format. */ -static u16 mtk_ctm_s31_32_to_s1_n(u64 in, u32 n) -{ - u16 r; - - /* Sign bit. */ - r = in & BIT_ULL(63) ? BIT(n + 1) : 0; - - if ((in & GENMASK_ULL(62, 33)) > 0) { - /* identity value 0x100000000 -> 0x400(mt8183), */ - /* identity value 0x100000000 -> 0x800(mt8192), */ - /* if bigger this, set it to max 0x7ff. */ - r |= GENMASK(n, 0); - } else { - /* take the n+1 most important bits. */ - r |= (in >> (32 - n)) & GENMASK(n, 0); - } - - return r; -} - void mtk_ccorr_ctm_set(struct device *dev, struct drm_crtc_state *state) { struct mtk_disp_ccorr *ccorr = dev_get_drvdata(dev); @@ -119,7 +98,7 @@ void mtk_ccorr_ctm_set(struct device *dev, struct drm_crtc_state *state) input = ctm->matrix; for (i = 0; i < ARRAY_SIZE(coeffs); i++) - coeffs[i] = mtk_ctm_s31_32_to_s1_n(input[i], matrix_bits); + coeffs[i] = drm_color_ctm_s31_32_to_qm_n(input[i], 2, matrix_bits); mtk_ddp_write(cmdq_pkt, coeffs[0] << 16 | coeffs[1], &ccorr->cmdq_reg, ccorr->regs, DISP_CCORR_COEF_0); From 45ca97e4ec9bbf6bfba498e9156ed643e54eea39 Mon Sep 17 00:00:00 2001 From: Akhil P Oommen Date: Tue, 18 Nov 2025 14:20:29 +0530 Subject: [PATCH 0686/3902] drm/msm/a6xx: Flush LRZ cache before PT switch [ Upstream commit 180349b8407f3b268b2ceac0e590b8199e043081 ] As per the recommendation, A7x and newer GPUs should flush the LRZ cache before switching the pagetable. Update a6xx_set_pagetable() to do this. While we are at it, sync both BV and BR before issuing a CP_RESET_CONTEXT_STATE command, to match the downstream sequence. Fixes: af66706accdf ("drm/msm/a6xx: Add skeleton A7xx support") Reviewed-by: Konrad Dybcio Signed-off-by: Akhil P Oommen Patchwork: https://patchwork.freedesktop.org/patch/688995/ Message-ID: <20251118-kaana-gpu-support-v4-2-86eeb8e93fb6@oss.qualcomm.com> Signed-off-by: Rob Clark Signed-off-by: Sasha Levin --- drivers/gpu/drm/msm/adreno/a6xx_gpu.c | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/drivers/gpu/drm/msm/adreno/a6xx_gpu.c b/drivers/gpu/drm/msm/adreno/a6xx_gpu.c index b8f8ae940b55f5..6f7ed07670b12c 100644 --- a/drivers/gpu/drm/msm/adreno/a6xx_gpu.c +++ b/drivers/gpu/drm/msm/adreno/a6xx_gpu.c @@ -224,7 +224,7 @@ static void a6xx_set_pagetable(struct a6xx_gpu *a6xx_gpu, OUT_RING(ring, submit->seqno - 1); OUT_PKT7(ring, CP_THREAD_CONTROL, 1); - OUT_RING(ring, CP_SET_THREAD_BOTH); + OUT_RING(ring, CP_THREAD_CONTROL_0_SYNC_THREADS | CP_SET_THREAD_BOTH); /* Reset state used to synchronize BR and BV */ OUT_PKT7(ring, CP_RESET_CONTEXT_STATE, 1); @@ -235,7 +235,13 @@ static void a6xx_set_pagetable(struct a6xx_gpu *a6xx_gpu, CP_RESET_CONTEXT_STATE_0_RESET_GLOBAL_LOCAL_TS); OUT_PKT7(ring, CP_THREAD_CONTROL, 1); - OUT_RING(ring, CP_SET_THREAD_BR); + OUT_RING(ring, CP_THREAD_CONTROL_0_SYNC_THREADS | CP_SET_THREAD_BOTH); + + OUT_PKT7(ring, CP_EVENT_WRITE, 1); + OUT_RING(ring, LRZ_FLUSH); + + OUT_PKT7(ring, CP_THREAD_CONTROL, 1); + OUT_RING(ring, CP_THREAD_CONTROL_0_SYNC_THREADS | CP_SET_THREAD_BR); } if (!sysprof) { From e24720c823389017de8b58c4279ef321d14a04bc Mon Sep 17 00:00:00 2001 From: Akhil P Oommen Date: Tue, 18 Nov 2025 14:20:30 +0530 Subject: [PATCH 0687/3902] drm/msm/a6xx: Fix the gemnoc workaround [ Upstream commit ff7a6de043fce21ea5891311746b16121b385c59 ] Correct the register offset and enable this workaround for all A7x and newer GPUs to match the recommendation. Also, downstream does this w/a after moving the fence to allow mode. So do the same. Fixes: dbfbb376b50c ("drm/msm/a6xx: Add A621 support") Reviewed-by: Konrad Dybcio Signed-off-by: Akhil P Oommen Patchwork: https://patchwork.freedesktop.org/patch/688997/ Message-ID: <20251118-kaana-gpu-support-v4-3-86eeb8e93fb6@oss.qualcomm.com> Signed-off-by: Rob Clark Signed-off-by: Sasha Levin --- drivers/gpu/drm/msm/adreno/a6xx_gmu.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/drivers/gpu/drm/msm/adreno/a6xx_gmu.c b/drivers/gpu/drm/msm/adreno/a6xx_gmu.c index 4e6dc16e4a4c48..605bb55de8d529 100644 --- a/drivers/gpu/drm/msm/adreno/a6xx_gmu.c +++ b/drivers/gpu/drm/msm/adreno/a6xx_gmu.c @@ -485,8 +485,9 @@ static void a6xx_gemnoc_workaround(struct a6xx_gmu *gmu) * in the power down sequence not being fully executed. That in turn can * prevent CX_GDSC from collapsing. Assert Qactive to avoid this. */ - if (adreno_is_a621(adreno_gpu) || adreno_is_7c3(adreno_gpu)) - gmu_write(gmu, REG_A6XX_GMU_AO_AHB_FENCE_CTRL, BIT(0)); + if (adreno_is_a7xx(adreno_gpu) || (adreno_is_a621(adreno_gpu) || + adreno_is_7c3(adreno_gpu))) + gmu_write(gmu, REG_A6XX_GPU_GMU_CX_GMU_CX_FALNEXT_INTF, BIT(0)); } /* Let the GMU know that we are about to go into slumber */ @@ -522,10 +523,9 @@ static int a6xx_gmu_notify_slumber(struct a6xx_gmu *gmu) } out: - a6xx_gemnoc_workaround(gmu); - /* Put fence into allow mode */ gmu_write(gmu, REG_A6XX_GMU_AO_AHB_FENCE_CTRL, 0); + a6xx_gemnoc_workaround(gmu); return ret; } From d8995c4d86ac692dcc766531ee7c9b8c56ecf5a0 Mon Sep 17 00:00:00 2001 From: Akhil P Oommen Date: Tue, 18 Nov 2025 14:20:39 +0530 Subject: [PATCH 0688/3902] drm/msm/a6xx: Improve MX rail fallback in RPMH vote init [ Upstream commit ca04ce7a2f22652fdf6489fa7e02e7d2c08698f4 ] Current logic assumes that the voltage corners in both MxG and MxA are always same. This is not true for recent targets. So, rework the rpmh init sequence to probe and calculate the votes with the respective rails, ie, GX rails should use MxG as secondary rail and Cx rail should use MxA as the secondary rail. Fixes: d6225e0cd096 ("drm/msm/adreno: Add support for X185 GPU") Reviewed-by: Konrad Dybcio Signed-off-by: Akhil P Oommen Patchwork: https://patchwork.freedesktop.org/patch/689014/ Message-ID: <20251118-kaana-gpu-support-v4-12-86eeb8e93fb6@oss.qualcomm.com> Signed-off-by: Rob Clark Signed-off-by: Sasha Levin --- drivers/gpu/drm/msm/adreno/a6xx_gmu.c | 26 +++++++++++++++----------- 1 file changed, 15 insertions(+), 11 deletions(-) diff --git a/drivers/gpu/drm/msm/adreno/a6xx_gmu.c b/drivers/gpu/drm/msm/adreno/a6xx_gmu.c index 605bb55de8d529..21a3f9b0ab4c25 100644 --- a/drivers/gpu/drm/msm/adreno/a6xx_gmu.c +++ b/drivers/gpu/drm/msm/adreno/a6xx_gmu.c @@ -1492,13 +1492,14 @@ static unsigned int a6xx_gmu_get_arc_level(struct device *dev, } static int a6xx_gmu_rpmh_arc_votes_init(struct device *dev, u32 *votes, - unsigned long *freqs, int freqs_count, const char *id) + unsigned long *freqs, int freqs_count, + const char *pri_id, const char *sec_id) { int i, j; const u16 *pri, *sec; size_t pri_count, sec_count; - pri = cmd_db_read_aux_data(id, &pri_count); + pri = cmd_db_read_aux_data(pri_id, &pri_count); if (IS_ERR(pri)) return PTR_ERR(pri); /* @@ -1509,13 +1510,7 @@ static int a6xx_gmu_rpmh_arc_votes_init(struct device *dev, u32 *votes, if (!pri_count) return -EINVAL; - /* - * Some targets have a separate gfx mxc rail. So try to read that first and then fall back - * to regular mx rail if it is missing - */ - sec = cmd_db_read_aux_data("gmxc.lvl", &sec_count); - if (IS_ERR(sec) && sec != ERR_PTR(-EPROBE_DEFER)) - sec = cmd_db_read_aux_data("mx.lvl", &sec_count); + sec = cmd_db_read_aux_data(sec_id, &sec_count); if (IS_ERR(sec)) return PTR_ERR(sec); @@ -1583,15 +1578,24 @@ static int a6xx_gmu_rpmh_votes_init(struct a6xx_gmu *gmu) struct adreno_gpu *adreno_gpu = &a6xx_gpu->base; const struct a6xx_info *info = adreno_gpu->info->a6xx; struct msm_gpu *gpu = &adreno_gpu->base; + const char *sec_id; + const u16 *gmxc; int ret; + gmxc = cmd_db_read_aux_data("gmxc.lvl", NULL); + if (gmxc == ERR_PTR(-EPROBE_DEFER)) + return -EPROBE_DEFER; + + /* If GMxC is present, prefer that as secondary rail for GX votes */ + sec_id = IS_ERR_OR_NULL(gmxc) ? "mx.lvl" : "gmxc.lvl"; + /* Build the GX votes */ ret = a6xx_gmu_rpmh_arc_votes_init(&gpu->pdev->dev, gmu->gx_arc_votes, - gmu->gpu_freqs, gmu->nr_gpu_freqs, "gfx.lvl"); + gmu->gpu_freqs, gmu->nr_gpu_freqs, "gfx.lvl", sec_id); /* Build the CX votes */ ret |= a6xx_gmu_rpmh_arc_votes_init(gmu->dev, gmu->cx_arc_votes, - gmu->gmu_freqs, gmu->nr_gmu_freqs, "cx.lvl"); + gmu->gmu_freqs, gmu->nr_gmu_freqs, "cx.lvl", "mx.lvl"); /* Build the interconnect votes */ if (info->bcms && gmu->nr_gpu_bws > 1) From 77d179867bcc1f8f48bcfc5cdbcf40ba3ab86ead Mon Sep 17 00:00:00 2001 From: Longbin Li Date: Mon, 17 Nov 2025 17:05:39 +0800 Subject: [PATCH 0689/3902] spi: sophgo: Fix incorrect use of bus width value macros [ Upstream commit d9813cd23d5a7b254cc1b1c1ea042634d8da62e6 ] The previous code initialized the 'reg' value with specific bus-width values (BUS_WIDTH_2_BIT and BUS_WIDTH_4_BIT), which introduces ambiguity. Replace them with BUS_WIDTH_MASK to express the intention clearly. Fixes: de16c322eefb ("spi: sophgo: add SG2044 SPI NOR controller driver") Signed-off-by: Longbin Li Link: https://patch.msgid.link/20251117090559.78288-1-looong.bin@gmail.com Signed-off-by: Mark Brown Signed-off-by: Sasha Levin --- drivers/spi/spi-sg2044-nor.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/spi/spi-sg2044-nor.c b/drivers/spi/spi-sg2044-nor.c index af48b1fcda930f..37f1cfe10be460 100644 --- a/drivers/spi/spi-sg2044-nor.c +++ b/drivers/spi/spi-sg2044-nor.c @@ -42,6 +42,7 @@ #define SPIFMC_TRAN_CSR_TRAN_MODE_RX BIT(0) #define SPIFMC_TRAN_CSR_TRAN_MODE_TX BIT(1) #define SPIFMC_TRAN_CSR_FAST_MODE BIT(3) +#define SPIFMC_TRAN_CSR_BUS_WIDTH_MASK GENMASK(5, 4) #define SPIFMC_TRAN_CSR_BUS_WIDTH_1_BIT (0x00 << 4) #define SPIFMC_TRAN_CSR_BUS_WIDTH_2_BIT (0x01 << 4) #define SPIFMC_TRAN_CSR_BUS_WIDTH_4_BIT (0x02 << 4) @@ -122,8 +123,7 @@ static u32 sg2044_spifmc_init_reg(struct sg2044_spifmc *spifmc) reg = readl(spifmc->io_base + SPIFMC_TRAN_CSR); reg &= ~(SPIFMC_TRAN_CSR_TRAN_MODE_MASK | SPIFMC_TRAN_CSR_FAST_MODE | - SPIFMC_TRAN_CSR_BUS_WIDTH_2_BIT | - SPIFMC_TRAN_CSR_BUS_WIDTH_4_BIT | + SPIFMC_TRAN_CSR_BUS_WIDTH_MASK | SPIFMC_TRAN_CSR_DMA_EN | SPIFMC_TRAN_CSR_ADDR_BYTES_MASK | SPIFMC_TRAN_CSR_WITH_CMD | From 3e5b25da0b4109a3e063759735e6ec4236ea5a05 Mon Sep 17 00:00:00 2001 From: Fernando Fernandez Mancera Date: Sat, 15 Nov 2025 10:59:38 +0100 Subject: [PATCH 0690/3902] ipv6: clear RA flags when adding a static route [ Upstream commit f72514b3c5698e4b900b25345e09f9ed33123de6 ] When an IPv6 Router Advertisement (RA) is received for a prefix, the kernel creates the corresponding on-link route with flags RTF_ADDRCONF and RTF_PREFIX_RT configured and RTF_EXPIRES if lifetime is set. If later a user configures a static IPv6 address on the same prefix the kernel clears the RTF_EXPIRES flag but it doesn't clear the RTF_ADDRCONF and RTF_PREFIX_RT. When the next RA for that prefix is received, the kernel sees the route as RA-learned and wrongly configures back the lifetime. This is problematic because if the route expires, the static address won't have the corresponding on-link route. This fix clears the RTF_ADDRCONF and RTF_PREFIX_RT flags preventing that the lifetime is configured when the next RA arrives. If the static address is deleted, the route becomes RA-learned again. Fixes: 14ef37b6d00e ("ipv6: fix route lookup in addrconf_prefix_rcv()") Reported-by: Garri Djavadyan Closes: https://lore.kernel.org/netdev/ba807d39aca5b4dcf395cc11dca61a130a52cfd3.camel@gmail.com/ Signed-off-by: Fernando Fernandez Mancera Reviewed-by: David Ahern Link: https://patch.msgid.link/20251115095939.6967-1-fmancera@suse.de Signed-off-by: Jakub Kicinski Signed-off-by: Sasha Levin --- net/ipv6/ip6_fib.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/net/ipv6/ip6_fib.c b/net/ipv6/ip6_fib.c index 02c16909f61822..2111af022d946d 100644 --- a/net/ipv6/ip6_fib.c +++ b/net/ipv6/ip6_fib.c @@ -1138,6 +1138,10 @@ static int fib6_add_rt2node(struct fib6_node *fn, struct fib6_info *rt, fib6_set_expires(iter, rt->expires); fib6_add_gc_list(iter); } + if (!(rt->fib6_flags & (RTF_ADDRCONF | RTF_PREFIX_RT))) { + iter->fib6_flags &= ~RTF_ADDRCONF; + iter->fib6_flags &= ~RTF_PREFIX_RT; + } if (rt->fib6_pmtu) fib6_metric_set(iter, RTAX_MTU, From 1ade1edef373e87ba8d4e5dfe07fe420e77474a4 Mon Sep 17 00:00:00 2001 From: Leo Yan Date: Wed, 12 Nov 2025 18:24:27 +0000 Subject: [PATCH 0691/3902] perf arm_spe: Fix memset subclass in operation [ Upstream commit 33e1fffea492b7158a168914dc0da6aedf78d08e ] The operation subclass is extracted from bits [7..1] of the payload. Since bit [0] is not parsed, there is no chance to match the memset type (0x25). As a result, the memset payload is never parsed successfully. Instead of extracting a unified bit field, change to extract the specific bits for each operation subclass. Fixes: 34fb60400e32 ("perf arm-spe: Add raw decoding for SPEv1.3 MTE and MOPS load/store") Signed-off-by: Leo Yan Reviewed-by: Ian Rogers Reviewed-by: James Clark Signed-off-by: Namhyung Kim Signed-off-by: Sasha Levin --- .../arm-spe-decoder/arm-spe-pkt-decoder.c | 25 ++++++------------- .../arm-spe-decoder/arm-spe-pkt-decoder.h | 15 ++++++----- 2 files changed, 14 insertions(+), 26 deletions(-) diff --git a/tools/perf/util/arm-spe-decoder/arm-spe-pkt-decoder.c b/tools/perf/util/arm-spe-decoder/arm-spe-pkt-decoder.c index 80561630253dd5..1a1ffe50ee73ab 100644 --- a/tools/perf/util/arm-spe-decoder/arm-spe-pkt-decoder.c +++ b/tools/perf/util/arm-spe-decoder/arm-spe-pkt-decoder.c @@ -371,31 +371,20 @@ static int arm_spe_pkt_desc_op_type(const struct arm_spe_pkt *packet, arm_spe_pkt_out_string(&err, &buf, &buf_len, " AR"); } - switch (SPE_OP_PKT_LDST_SUBCLASS_GET(payload)) { - case SPE_OP_PKT_LDST_SUBCLASS_SIMD_FP: + if (SPE_OP_PKT_LDST_SUBCLASS_SIMD_FP(payload)) arm_spe_pkt_out_string(&err, &buf, &buf_len, " SIMD-FP"); - break; - case SPE_OP_PKT_LDST_SUBCLASS_GP_REG: + else if (SPE_OP_PKT_LDST_SUBCLASS_GP_REG(payload)) arm_spe_pkt_out_string(&err, &buf, &buf_len, " GP-REG"); - break; - case SPE_OP_PKT_LDST_SUBCLASS_UNSPEC_REG: + else if (SPE_OP_PKT_LDST_SUBCLASS_UNSPEC_REG(payload)) arm_spe_pkt_out_string(&err, &buf, &buf_len, " UNSPEC-REG"); - break; - case SPE_OP_PKT_LDST_SUBCLASS_NV_SYSREG: + else if (SPE_OP_PKT_LDST_SUBCLASS_NV_SYSREG(payload)) arm_spe_pkt_out_string(&err, &buf, &buf_len, " NV-SYSREG"); - break; - case SPE_OP_PKT_LDST_SUBCLASS_MTE_TAG: + else if (SPE_OP_PKT_LDST_SUBCLASS_MTE_TAG(payload)) arm_spe_pkt_out_string(&err, &buf, &buf_len, " MTE-TAG"); - break; - case SPE_OP_PKT_LDST_SUBCLASS_MEMCPY: + else if (SPE_OP_PKT_LDST_SUBCLASS_MEMCPY(payload)) arm_spe_pkt_out_string(&err, &buf, &buf_len, " MEMCPY"); - break; - case SPE_OP_PKT_LDST_SUBCLASS_MEMSET: + else if (SPE_OP_PKT_LDST_SUBCLASS_MEMSET(payload)) arm_spe_pkt_out_string(&err, &buf, &buf_len, " MEMSET"); - break; - default: - break; - } if (SPE_OP_PKT_IS_LDST_SVE(payload)) { /* SVE effective vector length */ diff --git a/tools/perf/util/arm-spe-decoder/arm-spe-pkt-decoder.h b/tools/perf/util/arm-spe-decoder/arm-spe-pkt-decoder.h index d00c2481712dcc..75e355fe3438cc 100644 --- a/tools/perf/util/arm-spe-decoder/arm-spe-pkt-decoder.h +++ b/tools/perf/util/arm-spe-decoder/arm-spe-pkt-decoder.h @@ -125,14 +125,13 @@ enum arm_spe_events { #define SPE_OP_PKT_IS_OTHER_SVE_OP(v) (((v) & (BIT(7) | BIT(3) | BIT(0))) == 0x8) -#define SPE_OP_PKT_LDST_SUBCLASS_GET(v) ((v) & GENMASK_ULL(7, 1)) -#define SPE_OP_PKT_LDST_SUBCLASS_GP_REG 0x0 -#define SPE_OP_PKT_LDST_SUBCLASS_SIMD_FP 0x4 -#define SPE_OP_PKT_LDST_SUBCLASS_UNSPEC_REG 0x10 -#define SPE_OP_PKT_LDST_SUBCLASS_NV_SYSREG 0x30 -#define SPE_OP_PKT_LDST_SUBCLASS_MTE_TAG 0x14 -#define SPE_OP_PKT_LDST_SUBCLASS_MEMCPY 0x20 -#define SPE_OP_PKT_LDST_SUBCLASS_MEMSET 0x25 +#define SPE_OP_PKT_LDST_SUBCLASS_GP_REG(v) (((v) & GENMASK_ULL(7, 1)) == 0x0) +#define SPE_OP_PKT_LDST_SUBCLASS_SIMD_FP(v) (((v) & GENMASK_ULL(7, 1)) == 0x4) +#define SPE_OP_PKT_LDST_SUBCLASS_UNSPEC_REG(v) (((v) & GENMASK_ULL(7, 1)) == 0x10) +#define SPE_OP_PKT_LDST_SUBCLASS_NV_SYSREG(v) (((v) & GENMASK_ULL(7, 1)) == 0x30) +#define SPE_OP_PKT_LDST_SUBCLASS_MTE_TAG(v) (((v) & GENMASK_ULL(7, 1)) == 0x14) +#define SPE_OP_PKT_LDST_SUBCLASS_MEMCPY(v) (((v) & GENMASK_ULL(7, 1)) == 0x20) +#define SPE_OP_PKT_LDST_SUBCLASS_MEMSET(v) (((v) & GENMASK_ULL(7, 0)) == 0x25) #define SPE_OP_PKT_IS_LDST_ATOMIC(v) (((v) & (GENMASK_ULL(7, 5) | BIT(1))) == 0x2) From f4b45dc5eeb3d5ae3b80566692a27256ba707140 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Uwe=20Kleine-K=C3=B6nig?= Date: Tue, 18 Nov 2025 18:43:02 +0100 Subject: [PATCH 0692/3902] pwm: bcm2835: Make sure the channel is enabled after pwm_request() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [ Upstream commit cda323dbda76600bf9761970d58517648f0de67d ] The .free callback cleared among others the enable bit PWENx in the control register. When the PWM is requested later again this bit isn't restored but the core assumes the PWM is enabled and thus skips a request to configure the same state as before. To fix that don't touch the hardware configuration in .free(). For symmetry also drop .request() and configure the mode completely in .apply(). Fixes: e5a06dc5ac1f ("pwm: Add BCM2835 PWM driver") Signed-off-by: Uwe Kleine-König Reviewed-by: Florian Fainelli Link: https://patch.msgid.link/20251118174303.1761577-2-u.kleine-koenig@baylibre.com Signed-off-by: Uwe Kleine-König Signed-off-by: Sasha Levin --- drivers/pwm/pwm-bcm2835.c | 28 +++------------------------- 1 file changed, 3 insertions(+), 25 deletions(-) diff --git a/drivers/pwm/pwm-bcm2835.c b/drivers/pwm/pwm-bcm2835.c index 578e95e0296c65..532903da521fd0 100644 --- a/drivers/pwm/pwm-bcm2835.c +++ b/drivers/pwm/pwm-bcm2835.c @@ -34,29 +34,6 @@ static inline struct bcm2835_pwm *to_bcm2835_pwm(struct pwm_chip *chip) return pwmchip_get_drvdata(chip); } -static int bcm2835_pwm_request(struct pwm_chip *chip, struct pwm_device *pwm) -{ - struct bcm2835_pwm *pc = to_bcm2835_pwm(chip); - u32 value; - - value = readl(pc->base + PWM_CONTROL); - value &= ~(PWM_CONTROL_MASK << PWM_CONTROL_SHIFT(pwm->hwpwm)); - value |= (PWM_MODE << PWM_CONTROL_SHIFT(pwm->hwpwm)); - writel(value, pc->base + PWM_CONTROL); - - return 0; -} - -static void bcm2835_pwm_free(struct pwm_chip *chip, struct pwm_device *pwm) -{ - struct bcm2835_pwm *pc = to_bcm2835_pwm(chip); - u32 value; - - value = readl(pc->base + PWM_CONTROL); - value &= ~(PWM_CONTROL_MASK << PWM_CONTROL_SHIFT(pwm->hwpwm)); - writel(value, pc->base + PWM_CONTROL); -} - static int bcm2835_pwm_apply(struct pwm_chip *chip, struct pwm_device *pwm, const struct pwm_state *state) { @@ -102,6 +79,9 @@ static int bcm2835_pwm_apply(struct pwm_chip *chip, struct pwm_device *pwm, /* set polarity */ val = readl(pc->base + PWM_CONTROL); + val &= ~(PWM_CONTROL_MASK << PWM_CONTROL_SHIFT(pwm->hwpwm)); + val |= PWM_MODE << PWM_CONTROL_SHIFT(pwm->hwpwm); + if (state->polarity == PWM_POLARITY_NORMAL) val &= ~(PWM_POLARITY << PWM_CONTROL_SHIFT(pwm->hwpwm)); else @@ -119,8 +99,6 @@ static int bcm2835_pwm_apply(struct pwm_chip *chip, struct pwm_device *pwm, } static const struct pwm_ops bcm2835_pwm_ops = { - .request = bcm2835_pwm_request, - .free = bcm2835_pwm_free, .apply = bcm2835_pwm_apply, }; From 638779b175216840e321319bd371ff89fdcd5e56 Mon Sep 17 00:00:00 2001 From: Shawn Lin Date: Thu, 13 Nov 2025 12:52:55 +0800 Subject: [PATCH 0693/3902] scsi: ufs: rockchip: Reset controller on PRE_CHANGE of hce enable notify [ Upstream commit b0ee72db9132bd19b1b80152b35e0cf6a6cbd9f2 ] This fixes the dme-reset failed when doing recovery. Because device reset is not enough, we could occasionally see the error below: ufshcd-rockchip 2a2d0000.ufs: uic cmd 0x14 with arg3 0x0 completion timeout ufshcd-rockchip 2a2d0000.ufs: dme-reset: error code -110 ufshcd-rockchip 2a2d0000.ufs: DME_RESET failed ufshcd-rockchip 2a2d0000.ufs: ufshcd_host_reset_and_restore: Host init failed -110 Fix this by resetting the controller on PRE_CHANGE stage of hce enable notify. Fixes: d3cbe455d6eb ("scsi: ufs: rockchip: Initial support for UFS") Signed-off-by: Shawn Lin Link: https://patch.msgid.link/1763009575-237552-1-git-send-email-shawn.lin@rock-chips.com Signed-off-by: Martin K. Petersen Signed-off-by: Sasha Levin --- drivers/ufs/host/ufs-rockchip.c | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/drivers/ufs/host/ufs-rockchip.c b/drivers/ufs/host/ufs-rockchip.c index 8754085dd0ccf4..8cecb28cdce411 100644 --- a/drivers/ufs/host/ufs-rockchip.c +++ b/drivers/ufs/host/ufs-rockchip.c @@ -20,9 +20,17 @@ #include "ufshcd-pltfrm.h" #include "ufs-rockchip.h" +static void ufs_rockchip_controller_reset(struct ufs_rockchip_host *host) +{ + reset_control_assert(host->rst); + udelay(1); + reset_control_deassert(host->rst); +} + static int ufs_rockchip_hce_enable_notify(struct ufs_hba *hba, enum ufs_notify_change_status status) { + struct ufs_rockchip_host *host = ufshcd_get_variant(hba); int err = 0; if (status == POST_CHANGE) { @@ -37,6 +45,9 @@ static int ufs_rockchip_hce_enable_notify(struct ufs_hba *hba, return ufshcd_vops_phy_initialization(hba); } + /* PRE_CHANGE */ + ufs_rockchip_controller_reset(host); + return 0; } @@ -156,9 +167,7 @@ static int ufs_rockchip_common_init(struct ufs_hba *hba) return dev_err_probe(dev, PTR_ERR(host->rst), "failed to get reset control\n"); - reset_control_assert(host->rst); - udelay(1); - reset_control_deassert(host->rst); + ufs_rockchip_controller_reset(host); host->ref_out_clk = devm_clk_get_enabled(dev, "ref_out"); if (IS_ERR(host->ref_out_clk)) @@ -282,9 +291,7 @@ static int ufs_rockchip_runtime_resume(struct device *dev) return err; } - reset_control_assert(host->rst); - udelay(1); - reset_control_deassert(host->rst); + ufs_rockchip_controller_reset(host); return ufshcd_runtime_resume(dev); } From 5fa1c8226b4532ad7011d295d3ab4ad45df105ae Mon Sep 17 00:00:00 2001 From: Zilin Guan Date: Thu, 13 Nov 2025 15:12:46 +0000 Subject: [PATCH 0694/3902] scsi: qla2xxx: Fix improper freeing of purex item [ Upstream commit 78b1a242fe612a755f2158fd206ee6bb577d18ca ] In qla2xxx_process_purls_iocb(), an item is allocated via qla27xx_copy_multiple_pkt(), which internally calls qla24xx_alloc_purex_item(). The qla24xx_alloc_purex_item() function may return a pre-allocated item from a per-adapter pool for small allocations, instead of dynamically allocating memory with kzalloc(). An error handling path in qla2xxx_process_purls_iocb() incorrectly uses kfree() to release the item. If the item was from the pre-allocated pool, calling kfree() on it is a bug that can lead to memory corruption. Fix this by using the correct deallocation function, qla24xx_free_purex_item(), which properly handles both dynamically allocated and pre-allocated items. Fixes: 875386b98857 ("scsi: qla2xxx: Add Unsolicited LS Request and Response Support for NVMe") Signed-off-by: Zilin Guan Reviewed-by: Himanshu Madhani Link: https://patch.msgid.link/20251113151246.762510-1-zilin@seu.edu.cn Signed-off-by: Martin K. Petersen Signed-off-by: Sasha Levin --- drivers/scsi/qla2xxx/qla_nvme.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/scsi/qla2xxx/qla_nvme.c b/drivers/scsi/qla2xxx/qla_nvme.c index 316594aa40cc5a..42eb65a62f1f3c 100644 --- a/drivers/scsi/qla2xxx/qla_nvme.c +++ b/drivers/scsi/qla2xxx/qla_nvme.c @@ -1292,7 +1292,7 @@ void qla2xxx_process_purls_iocb(void **pkt, struct rsp_que **rsp) a.reason = FCNVME_RJT_RC_LOGIC; a.explanation = FCNVME_RJT_EXP_NONE; xmt_reject = true; - kfree(item); + qla24xx_free_purex_item(item); goto out; } From 6fccca4dbebc99655e0101949af05d74c5b67d63 Mon Sep 17 00:00:00 2001 From: Vladimir Oltean Date: Tue, 18 Nov 2025 01:40:28 +0200 Subject: [PATCH 0695/3902] net: phy: realtek: create rtl8211f_config_rgmii_delay() [ Upstream commit 8e982441ba601d982dd0739972115d85ae01d99b ] The control flow in rtl8211f_config_init() has some pitfalls which were probably unintended. Specifically it has an early return: switch (phydev->interface) { ... default: /* the rest of the modes imply leaving delay as is. */ return 0; } which exits the entire config_init() function. This means it also skips doing things such as disabling CLKOUT or disabling PHY-mode EEE. For the RTL8211FS, which uses PHY_INTERFACE_MODE_SGMII, this might be a problem. However, I don't know that it is, so there is no Fixes: tag. The issue was observed through code inspection. Signed-off-by: Vladimir Oltean Reviewed-by: Andrew Lunn Link: https://patch.msgid.link/20251117234033.345679-2-vladimir.oltean@nxp.com Signed-off-by: Jakub Kicinski Signed-off-by: Sasha Levin --- drivers/net/phy/realtek/realtek_main.c | 65 +++++++++++++++----------- 1 file changed, 39 insertions(+), 26 deletions(-) diff --git a/drivers/net/phy/realtek/realtek_main.c b/drivers/net/phy/realtek/realtek_main.c index 16a347084293ef..b26f4d1c6bbfae 100644 --- a/drivers/net/phy/realtek/realtek_main.c +++ b/drivers/net/phy/realtek/realtek_main.c @@ -560,22 +560,11 @@ static int rtl8211c_config_init(struct phy_device *phydev) CTL1000_ENABLE_MASTER | CTL1000_AS_MASTER); } -static int rtl8211f_config_init(struct phy_device *phydev) +static int rtl8211f_config_rgmii_delay(struct phy_device *phydev) { - struct rtl821x_priv *priv = phydev->priv; - struct device *dev = &phydev->mdio.dev; u16 val_txdly, val_rxdly; int ret; - ret = phy_modify_paged_changed(phydev, RTL8211F_PHYCR_PAGE, RTL8211F_PHYCR1, - RTL8211F_ALDPS_PLL_OFF | RTL8211F_ALDPS_ENABLE | RTL8211F_ALDPS_XTAL_OFF, - priv->phycr1); - if (ret < 0) { - dev_err(dev, "aldps mode configuration failed: %pe\n", - ERR_PTR(ret)); - return ret; - } - switch (phydev->interface) { case PHY_INTERFACE_MODE_RGMII: val_txdly = 0; @@ -605,34 +594,58 @@ static int rtl8211f_config_init(struct phy_device *phydev) RTL8211F_TXCR, RTL8211F_TX_DELAY, val_txdly); if (ret < 0) { - dev_err(dev, "Failed to update the TX delay register\n"); + phydev_err(phydev, "Failed to update the TX delay register: %pe\n", + ERR_PTR(ret)); return ret; } else if (ret) { - dev_dbg(dev, - "%s 2ns TX delay (and changing the value from pin-strapping RXD1 or the bootloader)\n", - str_enable_disable(val_txdly)); + phydev_dbg(phydev, + "%s 2ns TX delay (and changing the value from pin-strapping RXD1 or the bootloader)\n", + str_enable_disable(val_txdly)); } else { - dev_dbg(dev, - "2ns TX delay was already %s (by pin-strapping RXD1 or bootloader configuration)\n", - str_enabled_disabled(val_txdly)); + phydev_dbg(phydev, + "2ns TX delay was already %s (by pin-strapping RXD1 or bootloader configuration)\n", + str_enabled_disabled(val_txdly)); } ret = phy_modify_paged_changed(phydev, RTL8211F_RGMII_PAGE, RTL8211F_RXCR, RTL8211F_RX_DELAY, val_rxdly); if (ret < 0) { - dev_err(dev, "Failed to update the RX delay register\n"); + phydev_err(phydev, "Failed to update the RX delay register: %pe\n", + ERR_PTR(ret)); return ret; } else if (ret) { - dev_dbg(dev, - "%s 2ns RX delay (and changing the value from pin-strapping RXD0 or the bootloader)\n", - str_enable_disable(val_rxdly)); + phydev_dbg(phydev, + "%s 2ns RX delay (and changing the value from pin-strapping RXD0 or the bootloader)\n", + str_enable_disable(val_rxdly)); } else { - dev_dbg(dev, - "2ns RX delay was already %s (by pin-strapping RXD0 or bootloader configuration)\n", - str_enabled_disabled(val_rxdly)); + phydev_dbg(phydev, + "2ns RX delay was already %s (by pin-strapping RXD0 or bootloader configuration)\n", + str_enabled_disabled(val_rxdly)); } + return 0; +} + +static int rtl8211f_config_init(struct phy_device *phydev) +{ + struct rtl821x_priv *priv = phydev->priv; + struct device *dev = &phydev->mdio.dev; + int ret; + + ret = phy_modify_paged_changed(phydev, RTL8211F_PHYCR_PAGE, RTL8211F_PHYCR1, + RTL8211F_ALDPS_PLL_OFF | RTL8211F_ALDPS_ENABLE | RTL8211F_ALDPS_XTAL_OFF, + priv->phycr1); + if (ret < 0) { + dev_err(dev, "aldps mode configuration failed: %pe\n", + ERR_PTR(ret)); + return ret; + } + + ret = rtl8211f_config_rgmii_delay(phydev); + if (ret) + return ret; + if (!priv->has_phycr2) return 0; From 3cf1f23648496703707f5049807b856df2696688 Mon Sep 17 00:00:00 2001 From: "Vineeth Pillai (Google)" Date: Wed, 19 Nov 2025 13:16:12 +0800 Subject: [PATCH 0696/3902] iommu/vt-d: Set INTEL_IOMMU_FLOPPY_WA depend on BLK_DEV_FD [ Upstream commit cb3db5a39e2a6b6396df1780d39a250f649d2e3a ] INTEL_IOMMU_FLOPPY_WA workaround was introduced to create direct mappings for first 16MB for floppy devices as the floppy drivers were not using dma apis. We need not do this direct map if floppy driver is not enabled. INTEL_IOMMU_FLOPPY_WA is generally not a good idea. Iommu will be mapping pages in this address range while kernel would also be allocating from this range(mostly on memory stress). A misbehaving device using this domain will have access to the pages that the kernel might be actively using. We noticed this while running a test that was trying to figure out if any pages used by kernel is in iommu page tables. This patch reduces the scope of the above issue by disabling the workaround when floppy driver is not enabled. But we would still need to fix the floppy driver to use dma apis so that we need not do direct map without reserving the pages. Or the other option is to reserve this memory range in firmware so that kernel will not use the pages. Fixes: d850c2ee5fe2 ("iommu/vt-d: Expose ISA direct mapping region via iommu_get_resv_regions") Fixes: 49a0429e53f2 ("Intel IOMMU: Iommu floppy workaround") Signed-off-by: Vineeth Pillai (Google) Link: https://lore.kernel.org/r/20251002161625.1155133-1-vineeth@bitbyteword.org Signed-off-by: Lu Baolu Signed-off-by: Joerg Roedel Signed-off-by: Sasha Levin --- drivers/iommu/intel/Kconfig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/iommu/intel/Kconfig b/drivers/iommu/intel/Kconfig index f2f538c7065032..17a91f881b2e9b 100644 --- a/drivers/iommu/intel/Kconfig +++ b/drivers/iommu/intel/Kconfig @@ -66,7 +66,7 @@ config INTEL_IOMMU_DEFAULT_ON config INTEL_IOMMU_FLOPPY_WA def_bool y - depends on X86 + depends on X86 && BLK_DEV_FD help Floppy disk drivers are known to bypass DMA API calls thereby failing to work when IOMMU is enabled. This From 055e892aac1414bd3a17d435361722e2a6b4b2db Mon Sep 17 00:00:00 2001 From: Aashish Sharma Date: Wed, 19 Nov 2025 13:16:13 +0800 Subject: [PATCH 0697/3902] iommu/vt-d: Fix unused invalidation hint in qi_desc_iotlb [ Upstream commit 6b38a108eeb3936b21643191db535a35dd7c890b ] Invalidation hint (ih) in the function 'qi_desc_iotlb' is initialized to zero and never used. It is embedded in the 0th bit of the 'addr' parameter. Get the correct 'ih' value from there. Fixes: f701c9f36bcb ("iommu/vt-d: Factor out invalidation descriptor composition") Signed-off-by: Aashish Sharma Link: https://lore.kernel.org/r/20251009010903.1323979-1-aashish@aashishsharma.net Signed-off-by: Lu Baolu Signed-off-by: Joerg Roedel Signed-off-by: Sasha Levin --- drivers/iommu/intel/iommu.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/iommu/intel/iommu.h b/drivers/iommu/intel/iommu.h index 3056583d7f56b2..dcc5466d35f933 100644 --- a/drivers/iommu/intel/iommu.h +++ b/drivers/iommu/intel/iommu.h @@ -1097,7 +1097,7 @@ static inline void qi_desc_iotlb(struct intel_iommu *iommu, u16 did, u64 addr, struct qi_desc *desc) { u8 dw = 0, dr = 0; - int ih = 0; + int ih = addr & 1; if (cap_write_drain(iommu->cap)) dw = 1; From a6fb8785b1796c42728cf7498cb045772efd7af6 Mon Sep 17 00:00:00 2001 From: Chien Wong Date: Thu, 13 Nov 2025 22:05:07 +0800 Subject: [PATCH 0698/3902] wifi: mac80211: fix CMAC functions not handling errors [ Upstream commit 353cda30d30e5dc7cacf8de5d2546724708ae3bb ] The called hash functions could fail thus we should check return values. Fixes: 26717828b75d ("mac80211: aes-cmac: switch to shash CMAC driver") Signed-off-by: Chien Wong Link: https://patch.msgid.link/20251113140511.48658-2-m@xv97.com Signed-off-by: Johannes Berg Signed-off-by: Sasha Levin --- net/mac80211/aes_cmac.c | 63 +++++++++++++++++++++++++++++------------ net/mac80211/aes_cmac.h | 8 +++--- net/mac80211/wpa.c | 20 +++++++------ 3 files changed, 61 insertions(+), 30 deletions(-) diff --git a/net/mac80211/aes_cmac.c b/net/mac80211/aes_cmac.c index 48c04f89de20af..65989c7dfc6809 100644 --- a/net/mac80211/aes_cmac.c +++ b/net/mac80211/aes_cmac.c @@ -22,50 +22,77 @@ static const u8 zero[CMAC_TLEN_256]; -void ieee80211_aes_cmac(struct crypto_shash *tfm, const u8 *aad, - const u8 *data, size_t data_len, u8 *mic) +int ieee80211_aes_cmac(struct crypto_shash *tfm, const u8 *aad, + const u8 *data, size_t data_len, u8 *mic) { + int err; SHASH_DESC_ON_STACK(desc, tfm); u8 out[AES_BLOCK_SIZE]; const __le16 *fc; desc->tfm = tfm; - crypto_shash_init(desc); - crypto_shash_update(desc, aad, AAD_LEN); + err = crypto_shash_init(desc); + if (err) + return err; + err = crypto_shash_update(desc, aad, AAD_LEN); + if (err) + return err; fc = (const __le16 *)aad; if (ieee80211_is_beacon(*fc)) { /* mask Timestamp field to zero */ - crypto_shash_update(desc, zero, 8); - crypto_shash_update(desc, data + 8, data_len - 8 - CMAC_TLEN); + err = crypto_shash_update(desc, zero, 8); + if (err) + return err; + err = crypto_shash_update(desc, data + 8, + data_len - 8 - CMAC_TLEN); + if (err) + return err; } else { - crypto_shash_update(desc, data, data_len - CMAC_TLEN); + err = crypto_shash_update(desc, data, + data_len - CMAC_TLEN); + if (err) + return err; } - crypto_shash_finup(desc, zero, CMAC_TLEN, out); - + err = crypto_shash_finup(desc, zero, CMAC_TLEN, out); + if (err) + return err; memcpy(mic, out, CMAC_TLEN); + + return 0; } -void ieee80211_aes_cmac_256(struct crypto_shash *tfm, const u8 *aad, - const u8 *data, size_t data_len, u8 *mic) +int ieee80211_aes_cmac_256(struct crypto_shash *tfm, const u8 *aad, + const u8 *data, size_t data_len, u8 *mic) { + int err; SHASH_DESC_ON_STACK(desc, tfm); const __le16 *fc; desc->tfm = tfm; - crypto_shash_init(desc); - crypto_shash_update(desc, aad, AAD_LEN); + err = crypto_shash_init(desc); + if (err) + return err; + err = crypto_shash_update(desc, aad, AAD_LEN); + if (err) + return err; fc = (const __le16 *)aad; if (ieee80211_is_beacon(*fc)) { /* mask Timestamp field to zero */ - crypto_shash_update(desc, zero, 8); - crypto_shash_update(desc, data + 8, - data_len - 8 - CMAC_TLEN_256); + err = crypto_shash_update(desc, zero, 8); + if (err) + return err; + err = crypto_shash_update(desc, data + 8, + data_len - 8 - CMAC_TLEN_256); + if (err) + return err; } else { - crypto_shash_update(desc, data, data_len - CMAC_TLEN_256); + err = crypto_shash_update(desc, data, data_len - CMAC_TLEN_256); + if (err) + return err; } - crypto_shash_finup(desc, zero, CMAC_TLEN_256, mic); + return crypto_shash_finup(desc, zero, CMAC_TLEN_256, mic); } struct crypto_shash *ieee80211_aes_cmac_key_setup(const u8 key[], diff --git a/net/mac80211/aes_cmac.h b/net/mac80211/aes_cmac.h index 76817446fb8384..f74150542142a6 100644 --- a/net/mac80211/aes_cmac.h +++ b/net/mac80211/aes_cmac.h @@ -11,10 +11,10 @@ struct crypto_shash *ieee80211_aes_cmac_key_setup(const u8 key[], size_t key_len); -void ieee80211_aes_cmac(struct crypto_shash *tfm, const u8 *aad, - const u8 *data, size_t data_len, u8 *mic); -void ieee80211_aes_cmac_256(struct crypto_shash *tfm, const u8 *aad, - const u8 *data, size_t data_len, u8 *mic); +int ieee80211_aes_cmac(struct crypto_shash *tfm, const u8 *aad, + const u8 *data, size_t data_len, u8 *mic); +int ieee80211_aes_cmac_256(struct crypto_shash *tfm, const u8 *aad, + const u8 *data, size_t data_len, u8 *mic); void ieee80211_aes_cmac_key_free(struct crypto_shash *tfm); #endif /* AES_CMAC_H */ diff --git a/net/mac80211/wpa.c b/net/mac80211/wpa.c index 40d5d9e4847916..bb0fa505cdcaee 100644 --- a/net/mac80211/wpa.c +++ b/net/mac80211/wpa.c @@ -869,8 +869,9 @@ ieee80211_crypto_aes_cmac_encrypt(struct ieee80211_tx_data *tx) /* * MIC = AES-128-CMAC(IGTK, AAD || Management Frame Body || MMIE, 64) */ - ieee80211_aes_cmac(key->u.aes_cmac.tfm, aad, - skb->data + 24, skb->len - 24, mmie->mic); + if (ieee80211_aes_cmac(key->u.aes_cmac.tfm, aad, + skb->data + 24, skb->len - 24, mmie->mic)) + return TX_DROP; return TX_CONTINUE; } @@ -916,8 +917,9 @@ ieee80211_crypto_aes_cmac_256_encrypt(struct ieee80211_tx_data *tx) /* MIC = AES-256-CMAC(IGTK, AAD || Management Frame Body || MMIE, 128) */ - ieee80211_aes_cmac_256(key->u.aes_cmac.tfm, aad, - skb->data + 24, skb->len - 24, mmie->mic); + if (ieee80211_aes_cmac_256(key->u.aes_cmac.tfm, aad, + skb->data + 24, skb->len - 24, mmie->mic)) + return TX_DROP; return TX_CONTINUE; } @@ -956,8 +958,9 @@ ieee80211_crypto_aes_cmac_decrypt(struct ieee80211_rx_data *rx) if (!(status->flag & RX_FLAG_DECRYPTED)) { /* hardware didn't decrypt/verify MIC */ bip_aad(skb, aad); - ieee80211_aes_cmac(key->u.aes_cmac.tfm, aad, - skb->data + 24, skb->len - 24, mic); + if (ieee80211_aes_cmac(key->u.aes_cmac.tfm, aad, + skb->data + 24, skb->len - 24, mic)) + return RX_DROP_U_DECRYPT_FAIL; if (crypto_memneq(mic, mmie->mic, sizeof(mmie->mic))) { key->u.aes_cmac.icverrors++; return RX_DROP_U_MIC_FAIL; @@ -1006,8 +1009,9 @@ ieee80211_crypto_aes_cmac_256_decrypt(struct ieee80211_rx_data *rx) if (!(status->flag & RX_FLAG_DECRYPTED)) { /* hardware didn't decrypt/verify MIC */ bip_aad(skb, aad); - ieee80211_aes_cmac_256(key->u.aes_cmac.tfm, aad, - skb->data + 24, skb->len - 24, mic); + if (ieee80211_aes_cmac_256(key->u.aes_cmac.tfm, aad, + skb->data + 24, skb->len - 24, mic)) + return RX_DROP_U_DECRYPT_FAIL; if (crypto_memneq(mic, mmie->mic, sizeof(mmie->mic))) { key->u.aes_cmac.icverrors++; return RX_DROP_U_MIC_FAIL; From efdb8e298907639746a283eefd699e4bfd94c100 Mon Sep 17 00:00:00 2001 From: Costa Shulyupin Date: Thu, 2 Oct 2025 20:08:45 +0300 Subject: [PATCH 0699/3902] tools/rtla: Fix unassigned nr_cpus [ Upstream commit b4275b23010df719ec6508ddbc84951dcd24adce ] In recently introduced timerlat_free(), the variable 'nr_cpus' is not assigned. Assign it with sysconf(_SC_NPROCESSORS_CONF) as done elsewhere. Remove the culprit: -Wno-maybe-uninitialized. The rest of the code is clean. Signed-off-by: Costa Shulyupin Reviewed-by: Tomas Glozar Fixes: 2f3172f9dd58 ("tools/rtla: Consolidate code between osnoise/timerlat and hist/top") Link: https://lore.kernel.org/r/20251002170846.437888-1-costa.shul@redhat.com Signed-off-by: Tomas Glozar Signed-off-by: Sasha Levin --- tools/tracing/rtla/Makefile.rtla | 2 +- tools/tracing/rtla/src/timerlat.c | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/tools/tracing/rtla/Makefile.rtla b/tools/tracing/rtla/Makefile.rtla index 08c1b40883d3aa..1743d91829d460 100644 --- a/tools/tracing/rtla/Makefile.rtla +++ b/tools/tracing/rtla/Makefile.rtla @@ -18,7 +18,7 @@ export CC AR STRIP PKG_CONFIG LD_SO_CONF_PATH LDCONFIG FOPTS := -flto=auto -ffat-lto-objects -fexceptions -fstack-protector-strong \ -fasynchronous-unwind-tables -fstack-clash-protection WOPTS := -O -Wall -Werror=format-security -Wp,-D_FORTIFY_SOURCE=2 \ - -Wp,-D_GLIBCXX_ASSERTIONS -Wno-maybe-uninitialized + -Wp,-D_GLIBCXX_ASSERTIONS ifeq ($(CC),clang) FOPTS := $(filter-out -flto=auto -ffat-lto-objects, $(FOPTS)) diff --git a/tools/tracing/rtla/src/timerlat.c b/tools/tracing/rtla/src/timerlat.c index b6921287412794..11ad447a8dd78b 100644 --- a/tools/tracing/rtla/src/timerlat.c +++ b/tools/tracing/rtla/src/timerlat.c @@ -215,7 +215,8 @@ void timerlat_analyze(struct osnoise_tool *tool, bool stopped) void timerlat_free(struct osnoise_tool *tool) { struct timerlat_params *params = to_timerlat_params(tool->params); - int nr_cpus, i; + int nr_cpus = sysconf(_SC_NPROCESSORS_CONF); + int i; timerlat_aa_destroy(); if (dma_latency_fd >= 0) From f75dc8cb4117067ed2bec2b27a61ccbc4ab65f33 Mon Sep 17 00:00:00 2001 From: Tomas Glozar Date: Tue, 7 Oct 2025 11:53:39 +0200 Subject: [PATCH 0700/3902] tools/rtla: Fix --on-threshold always triggering [ Upstream commit 417bd0d502f90a2e785e7299dae4f248b5ac0292 ] Commit 8d933d5c89e8 ("rtla/timerlat: Add continue action") moved the code performing on-threshold actions (enabled through --on-threshold option) to inside the RTLA main loop. The condition in the loop does not check whether the threshold was actually exceeded or if stop tracing was requested by the user through SIGINT or duration. This leads to a bug where on-threshold actions are always performed, even when the threshold was not hit. (BPF mode is not affected, since it uses a different condition in the while loop.) Add a condition that checks for !stop_tracing before executing the actions. Also, fix incorrect brackets in hist_main_loop to match the semantics of top_main_loop. Fixes: 8d933d5c89e8 ("rtla/timerlat: Add continue action") Fixes: 2f3172f9dd58 ("tools/rtla: Consolidate code between osnoise/timerlat and hist/top") Reviewed-by: Crystal Wood Reviewed-by: Wander Lairson Costa Link: https://lore.kernel.org/r/20251007095341.186923-1-tglozar@redhat.com Signed-off-by: Tomas Glozar Signed-off-by: Sasha Levin --- tools/tracing/rtla/src/common.c | 24 +++++++++++++++--------- 1 file changed, 15 insertions(+), 9 deletions(-) diff --git a/tools/tracing/rtla/src/common.c b/tools/tracing/rtla/src/common.c index 2e6e3dac1897f1..b197037fc58b37 100644 --- a/tools/tracing/rtla/src/common.c +++ b/tools/tracing/rtla/src/common.c @@ -268,6 +268,10 @@ int top_main_loop(struct osnoise_tool *tool) tool->ops->print_stats(tool); if (osnoise_trace_is_off(tool, record)) { + if (stop_tracing) + /* stop tracing requested, do not perform actions */ + return 0; + actions_perform(¶ms->threshold_actions); if (!params->threshold_actions.continue_flag) @@ -315,20 +319,22 @@ int hist_main_loop(struct osnoise_tool *tool) } if (osnoise_trace_is_off(tool, tool->record)) { + if (stop_tracing) + /* stop tracing requested, do not perform actions */ + break; + actions_perform(¶ms->threshold_actions); - if (!params->threshold_actions.continue_flag) { + if (!params->threshold_actions.continue_flag) /* continue flag not set, break */ break; - /* continue action reached, re-enable tracing */ - if (tool->record) - trace_instance_start(&tool->record->trace); - if (tool->aa) - trace_instance_start(&tool->aa->trace); - trace_instance_start(&tool->trace); - } - break; + /* continue action reached, re-enable tracing */ + if (tool->record) + trace_instance_start(&tool->record->trace); + if (tool->aa) + trace_instance_start(&tool->aa->trace); + trace_instance_start(&tool->trace); } /* is there still any user-threads ? */ From f620f2a12012a5bc9fa8c2688fdfd98fdae751ff Mon Sep 17 00:00:00 2001 From: Haotian Zhang Date: Tue, 18 Nov 2025 20:15:00 +0800 Subject: [PATCH 0701/3902] mfd: mt6397-irq: Fix missing irq_domain_remove() in error path [ Upstream commit b4b1bd1f330fdd13706382be6c90ce9f58cee3f5 ] If devm_request_threaded_irq() fails after irq_domain_create_linear() succeeds in mt6397_irq_init(), the function returns without removing the created IRQ domain, leading to a resource leak. Call irq_domain_remove() in the error path after a successful irq_domain_create_linear() to properly release the IRQ domain. Fixes: a4872e80ce7d ("mfd: mt6397: Extract IRQ related code from core driver") Signed-off-by: Haotian Zhang Link: https://patch.msgid.link/20251118121500.605-1-vulab@iscas.ac.cn Signed-off-by: Lee Jones Signed-off-by: Sasha Levin --- drivers/mfd/mt6397-irq.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/mfd/mt6397-irq.c b/drivers/mfd/mt6397-irq.c index 0e463026c5a918..5d2e5459f74449 100644 --- a/drivers/mfd/mt6397-irq.c +++ b/drivers/mfd/mt6397-irq.c @@ -229,6 +229,7 @@ int mt6397_irq_init(struct mt6397_chip *chip) if (ret) { dev_err(chip->dev, "failed to register irq=%d; err: %d\n", chip->irq, ret); + irq_domain_remove(chip->irq_domain); return ret; } From d0482ca91d76b337fd485a2437edabc530b3630f Mon Sep 17 00:00:00 2001 From: Haotian Zhang Date: Tue, 18 Nov 2025 20:14:27 +0800 Subject: [PATCH 0702/3902] mfd: mt6358-irq: Fix missing irq_domain_remove() in error path [ Upstream commit 384bd58bf7095e4c4c8fcdbcede316ef342c630c ] If devm_request_threaded_irq() fails after irq_domain_add_linear() succeeds in mt6358_irq_init(), the function returns without removing the created IRQ domain, leading to a resource leak. Call irq_domain_remove() in the error path after a successful irq_domain_add_linear() to properly release the IRQ domain. Fixes: 2b91c28f2abd ("mfd: Add support for the MediaTek MT6358 PMIC") Signed-off-by: Haotian Zhang Link: https://patch.msgid.link/20251118121427.583-1-vulab@iscas.ac.cn Signed-off-by: Lee Jones Signed-off-by: Sasha Levin --- drivers/mfd/mt6358-irq.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/mfd/mt6358-irq.c b/drivers/mfd/mt6358-irq.c index f467b00d236600..74cf2084304409 100644 --- a/drivers/mfd/mt6358-irq.c +++ b/drivers/mfd/mt6358-irq.c @@ -285,6 +285,7 @@ int mt6358_irq_init(struct mt6397_chip *chip) if (ret) { dev_err(chip->dev, "Failed to register IRQ=%d, ret=%d\n", chip->irq, ret); + irq_domain_remove(chip->irq_domain); return ret; } From 7045d5970e16393c1ad3fd1b27c433b6c22c6dee Mon Sep 17 00:00:00 2001 From: Yuntao Wang Date: Sat, 15 Nov 2025 21:47:46 +0800 Subject: [PATCH 0703/3902] of/fdt: Consolidate duplicate code into helper functions [ Upstream commit 8278cb72c60399f6dc6300c409879fb4c7291513 ] Currently, there are many pieces of nearly identical code scattered across different places. Consolidate the duplicate code into helper functions to improve maintainability and reduce the likelihood of errors. Signed-off-by: Yuntao Wang Link: https://patch.msgid.link/20251115134753.179931-2-yuntao.wang@linux.dev Signed-off-by: Rob Herring (Arm) Stable-dep-of: bec5f6092bc1 ("of/fdt: Fix the len check in early_init_dt_check_for_elfcorehdr()") Signed-off-by: Sasha Levin --- drivers/of/fdt.c | 41 +++++++++++++++++++++++++++++++++++++++++ include/linux/of_fdt.h | 9 +++++++++ 2 files changed, 50 insertions(+) diff --git a/drivers/of/fdt.c b/drivers/of/fdt.c index 0edd639898a63f..0c18bdefbbeea4 100644 --- a/drivers/of/fdt.c +++ b/drivers/of/fdt.c @@ -625,6 +625,47 @@ const void *__init of_get_flat_dt_prop(unsigned long node, const char *name, return fdt_getprop(initial_boot_params, node, name, size); } +const __be32 *__init of_flat_dt_get_addr_size_prop(unsigned long node, + const char *name, + int *entries) +{ + const __be32 *prop; + int len, elen = (dt_root_addr_cells + dt_root_size_cells) * sizeof(__be32); + + prop = of_get_flat_dt_prop(node, name, &len); + if (!prop || len % elen) { + *entries = 0; + return NULL; + } + + *entries = len / elen; + return prop; +} + +bool __init of_flat_dt_get_addr_size(unsigned long node, const char *name, + u64 *addr, u64 *size) +{ + const __be32 *prop; + int entries; + + prop = of_flat_dt_get_addr_size_prop(node, name, &entries); + if (!prop || entries != 1) + return false; + + of_flat_dt_read_addr_size(prop, 0, addr, size); + return true; +} + +void __init of_flat_dt_read_addr_size(const __be32 *prop, int entry_index, + u64 *addr, u64 *size) +{ + int entry_cells = dt_root_addr_cells + dt_root_size_cells; + prop += entry_cells * entry_index; + + *addr = dt_mem_next_cell(dt_root_addr_cells, &prop); + *size = dt_mem_next_cell(dt_root_size_cells, &prop); +} + /** * of_fdt_is_compatible - Return true if given node from the given blob has * compat in its compatible list diff --git a/include/linux/of_fdt.h b/include/linux/of_fdt.h index b8d6c0c208760a..51dadbaa3d63a3 100644 --- a/include/linux/of_fdt.h +++ b/include/linux/of_fdt.h @@ -55,6 +55,15 @@ extern int of_get_flat_dt_subnode_by_name(unsigned long node, const char *uname); extern const void *of_get_flat_dt_prop(unsigned long node, const char *name, int *size); + +extern const __be32 *of_flat_dt_get_addr_size_prop(unsigned long node, + const char *name, + int *entries); +extern bool of_flat_dt_get_addr_size(unsigned long node, const char *name, + u64 *addr, u64 *size); +extern void of_flat_dt_read_addr_size(const __be32 *prop, int entry_index, + u64 *addr, u64 *size); + extern int of_flat_dt_is_compatible(unsigned long node, const char *name); extern unsigned long of_get_flat_dt_root(void); extern uint32_t of_get_flat_dt_phandle(unsigned long node); From f326efdcab7f826ccd3a0a54cd737b7320444f10 Mon Sep 17 00:00:00 2001 From: Yuntao Wang Date: Sat, 15 Nov 2025 21:47:47 +0800 Subject: [PATCH 0704/3902] of/fdt: Fix the len check in early_init_dt_check_for_elfcorehdr() [ Upstream commit bec5f6092bc1328895992ff02b862ba34b45a0b7 ] The len value is in bytes, while `dt_root_addr_cells + dt_root_size_cells` is in cells (4 bytes per cell). Comparing them directly is incorrect. Use a helper function to simplify the code and address this issue. Fixes: f7e7ce93aac1 ("of: fdt: Add generic support for handling elf core headers property") Fixes: e62aaeac426ab1dd ("arm64: kdump: provide /proc/vmcore file") Signed-off-by: Yuntao Wang Link: https://patch.msgid.link/20251115134753.179931-3-yuntao.wang@linux.dev Signed-off-by: Rob Herring (Arm) Signed-off-by: Sasha Levin --- drivers/of/fdt.c | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/drivers/of/fdt.c b/drivers/of/fdt.c index 0c18bdefbbeea4..b45f60dccd7cf0 100644 --- a/drivers/of/fdt.c +++ b/drivers/of/fdt.c @@ -853,21 +853,15 @@ static void __init early_init_dt_check_for_initrd(unsigned long node) */ static void __init early_init_dt_check_for_elfcorehdr(unsigned long node) { - const __be32 *prop; - int len; - if (!IS_ENABLED(CONFIG_CRASH_DUMP)) return; pr_debug("Looking for elfcorehdr property... "); - prop = of_get_flat_dt_prop(node, "linux,elfcorehdr", &len); - if (!prop || (len < (dt_root_addr_cells + dt_root_size_cells))) + if (!of_flat_dt_get_addr_size(node, "linux,elfcorehdr", + &elfcorehdr_addr, &elfcorehdr_size)) return; - elfcorehdr_addr = dt_mem_next_cell(dt_root_addr_cells, &prop); - elfcorehdr_size = dt_mem_next_cell(dt_root_size_cells, &prop); - pr_debug("elfcorehdr_start=0x%llx elfcorehdr_size=0x%llx\n", elfcorehdr_addr, elfcorehdr_size); } From 38c8c945c2c2b404e7203b36023d2aa0a888c617 Mon Sep 17 00:00:00 2001 From: Yuntao Wang Date: Sat, 15 Nov 2025 21:47:48 +0800 Subject: [PATCH 0705/3902] of/fdt: Fix the len check in early_init_dt_check_for_usable_mem_range() [ Upstream commit 463942de13cd30fad5dba709f708483eab7efc2c ] The len value is in bytes, while `dt_root_addr_cells + dt_root_size_cells` is in cells (4 bytes per cell). Modulo calculation between them is incorrect, the units must be converted first. Use helper functions to simplify the code and fix this issue. Fixes: fb319e77a0e7 ("of: fdt: Add memory for devices by DT property "linux,usable-memory-range"") Fixes: 2af2b50acf9b9c38 ("of: fdt: Add generic support for handling usable memory range property") Fixes: 8f579b1c4e347b23 ("arm64: limit memory regions based on DT property, usable-memory-range") Signed-off-by: Yuntao Wang Link: https://patch.msgid.link/20251115134753.179931-4-yuntao.wang@linux.dev Signed-off-by: Rob Herring (Arm) Signed-off-by: Sasha Levin --- drivers/of/fdt.c | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/drivers/of/fdt.c b/drivers/of/fdt.c index b45f60dccd7cf0..ea37ba694bcd73 100644 --- a/drivers/of/fdt.c +++ b/drivers/of/fdt.c @@ -884,8 +884,9 @@ static unsigned long chosen_node_offset = -FDT_ERR_NOTFOUND; void __init early_init_dt_check_for_usable_mem_range(void) { struct memblock_region rgn[MAX_USABLE_RANGES] = {0}; - const __be32 *prop, *endp; + const __be32 *prop; int len, i; + u64 base, size; unsigned long node = chosen_node_offset; if ((long)node < 0) @@ -893,14 +894,17 @@ void __init early_init_dt_check_for_usable_mem_range(void) pr_debug("Looking for usable-memory-range property... "); - prop = of_get_flat_dt_prop(node, "linux,usable-memory-range", &len); - if (!prop || (len % (dt_root_addr_cells + dt_root_size_cells))) + prop = of_flat_dt_get_addr_size_prop(node, "linux,usable-memory-range", + &len); + if (!prop) return; - endp = prop + (len / sizeof(__be32)); - for (i = 0; i < MAX_USABLE_RANGES && prop < endp; i++) { - rgn[i].base = dt_mem_next_cell(dt_root_addr_cells, &prop); - rgn[i].size = dt_mem_next_cell(dt_root_size_cells, &prop); + len = min(len, MAX_USABLE_RANGES); + + for (i = 0; i < len; i++) { + of_flat_dt_read_addr_size(prop, i, &base, &size); + rgn[i].base = base; + rgn[i].size = size; pr_debug("cap_mem_regions[%d]: base=%pa, size=%pa\n", i, &rgn[i].base, &rgn[i].size); From 0d8e2c04a2ec53d93cc327e12975f611e5df314b Mon Sep 17 00:00:00 2001 From: Yuntao Wang Date: Sat, 15 Nov 2025 21:47:49 +0800 Subject: [PATCH 0706/3902] of/fdt: Fix incorrect use of dt_root_addr_cells in early_init_dt_check_kho() [ Upstream commit c85da64ce2c36bba469f6feede9ca768f0361741 ] When reading the fdt_size value, the argument passed to dt_mem_next_cell() is dt_root_addr_cells, but it should be dt_root_size_cells. The same issue occurs when reading the scratch_size value. Use a helper function to simplify the code and fix these issues. Fixes: 274cdcb1c004 ("arm64: add KHO support") Signed-off-by: Yuntao Wang Link: https://patch.msgid.link/20251115134753.179931-5-yuntao.wang@linux.dev Signed-off-by: Rob Herring (Arm) Signed-off-by: Sasha Levin --- drivers/of/fdt.c | 16 ++++------------ 1 file changed, 4 insertions(+), 12 deletions(-) diff --git a/drivers/of/fdt.c b/drivers/of/fdt.c index ea37ba694bcd73..fdaee4906836f9 100644 --- a/drivers/of/fdt.c +++ b/drivers/of/fdt.c @@ -922,26 +922,18 @@ static void __init early_init_dt_check_kho(void) { unsigned long node = chosen_node_offset; u64 fdt_start, fdt_size, scratch_start, scratch_size; - const __be32 *p; - int l; if (!IS_ENABLED(CONFIG_KEXEC_HANDOVER) || (long)node < 0) return; - p = of_get_flat_dt_prop(node, "linux,kho-fdt", &l); - if (l != (dt_root_addr_cells + dt_root_size_cells) * sizeof(__be32)) + if (!of_flat_dt_get_addr_size(node, "linux,kho-fdt", + &fdt_start, &fdt_size)) return; - fdt_start = dt_mem_next_cell(dt_root_addr_cells, &p); - fdt_size = dt_mem_next_cell(dt_root_addr_cells, &p); - - p = of_get_flat_dt_prop(node, "linux,kho-scratch", &l); - if (l != (dt_root_addr_cells + dt_root_size_cells) * sizeof(__be32)) + if (!of_flat_dt_get_addr_size(node, "linux,kho-scratch", + &scratch_start, &scratch_size)) return; - scratch_start = dt_mem_next_cell(dt_root_addr_cells, &p); - scratch_size = dt_mem_next_cell(dt_root_addr_cells, &p); - kho_populate(fdt_start, fdt_size, scratch_start, scratch_size); } From ae5f4c7397d0b7627df176dc2f9ac45dfa4e5d86 Mon Sep 17 00:00:00 2001 From: Fenglin Wu Date: Wed, 19 Nov 2025 14:06:43 +0800 Subject: [PATCH 0707/3902] leds: rgb: leds-qcom-lpg: Don't enable TRILED when configuring PWM [ Upstream commit 072cd5f458d76b9e15d89ebdaea8b5cb1312eeef ] The PWM signal from the LPG channel can be routed to PMIC GPIOs with proper GPIO configuration, and it is not necessary to enable the TRILED channel in that case. This also applies to the LPG channels that mapped to TRILED channels. Additionally, enabling the TRILED channel unnecessarily would cause a voltage increase in its power supply. Hence remove it. Fixes: 24e2d05d1b68 ("leds: Add driver for Qualcomm LPG") Signed-off-by: Fenglin Wu Reviewed-by: Bjorn Andersson Link: https://patch.msgid.link/20251119-lpg_triled_fix-v3-2-84b6dbdc774a@oss.qualcomm.com Signed-off-by: Lee Jones Signed-off-by: Sasha Levin --- drivers/leds/rgb/leds-qcom-lpg.c | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/drivers/leds/rgb/leds-qcom-lpg.c b/drivers/leds/rgb/leds-qcom-lpg.c index 4f2a178e3d265a..e197f548cddb03 100644 --- a/drivers/leds/rgb/leds-qcom-lpg.c +++ b/drivers/leds/rgb/leds-qcom-lpg.c @@ -2,7 +2,7 @@ /* * Copyright (c) 2017-2022 Linaro Ltd * Copyright (c) 2010-2012, The Linux Foundation. All rights reserved. - * Copyright (c) 2023-2024, Qualcomm Innovation Center, Inc. All rights reserved. + * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries. */ #include #include @@ -1247,8 +1247,6 @@ static int lpg_pwm_apply(struct pwm_chip *chip, struct pwm_device *pwm, lpg_apply(chan); - triled_set(lpg, chan->triled_mask, chan->enabled ? chan->triled_mask : 0); - out_unlock: mutex_unlock(&lpg->lock); From 97525d3a7f8b66e5eddd27ab2509aca177103704 Mon Sep 17 00:00:00 2001 From: Christophe JAILLET Date: Thu, 23 Oct 2025 16:58:05 +0300 Subject: [PATCH 0708/3902] phy: renesas: rcar-gen3-usb2: Fix an error handling path in rcar_gen3_phy_usb2_probe() [ Upstream commit 662bb179d3381c7c069e44bb177396bcaee31cc8 ] If an error occurs after the reset_control_deassert(), reset_control_assert() must be called, as already done in the remove function. Use devm_add_action_or_reset() to add the missing call and simplify the .remove() function accordingly. While at it, drop struct rcar_gen3_chan::rstc as it is not used aymore. [claudiu.beznea: removed "struct reset_control *rstc = data;" from rcar_gen3_reset_assert(), dropped struct rcar_gen3_chan::rstc] Fixes: 4eae16375357 ("phy: renesas: rcar-gen3-usb2: Add support to initialize the bus") Signed-off-by: Christophe JAILLET Reviewed-by: Biju Das Reviewed-by: Geert Uytterhoeven Tested-by: Wolfram Sang Signed-off-by: Claudiu Beznea Link: https://patch.msgid.link/20251023135810.1688415-3-claudiu.beznea.uj@bp.renesas.com Signed-off-by: Vinod Koul Signed-off-by: Sasha Levin --- drivers/phy/renesas/phy-rcar-gen3-usb2.c | 20 ++++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/drivers/phy/renesas/phy-rcar-gen3-usb2.c b/drivers/phy/renesas/phy-rcar-gen3-usb2.c index 3f6b480e109229..a38ead7c8055d0 100644 --- a/drivers/phy/renesas/phy-rcar-gen3-usb2.c +++ b/drivers/phy/renesas/phy-rcar-gen3-usb2.c @@ -134,7 +134,6 @@ struct rcar_gen3_chan { struct extcon_dev *extcon; struct rcar_gen3_phy rphys[NUM_OF_PHYS]; struct regulator *vbus; - struct reset_control *rstc; struct work_struct work; spinlock_t lock; /* protects access to hardware and driver data structure. */ enum usb_dr_mode dr_mode; @@ -771,21 +770,31 @@ static enum usb_dr_mode rcar_gen3_get_dr_mode(struct device_node *np) return candidate; } +static void rcar_gen3_reset_assert(void *data) +{ + reset_control_assert(data); +} + static int rcar_gen3_phy_usb2_init_bus(struct rcar_gen3_chan *channel) { struct device *dev = channel->dev; + struct reset_control *rstc; int ret; u32 val; - channel->rstc = devm_reset_control_array_get_shared(dev); - if (IS_ERR(channel->rstc)) - return PTR_ERR(channel->rstc); + rstc = devm_reset_control_array_get_shared(dev); + if (IS_ERR(rstc)) + return PTR_ERR(rstc); ret = pm_runtime_resume_and_get(dev); if (ret) return ret; - ret = reset_control_deassert(channel->rstc); + ret = reset_control_deassert(rstc); + if (ret) + goto rpm_put; + + ret = devm_add_action_or_reset(dev, rcar_gen3_reset_assert, rstc); if (ret) goto rpm_put; @@ -924,7 +933,6 @@ static void rcar_gen3_phy_usb2_remove(struct platform_device *pdev) if (channel->is_otg_channel) device_remove_file(&pdev->dev, &dev_attr_role); - reset_control_assert(channel->rstc); pm_runtime_disable(&pdev->dev); }; From c6dda44381eded529d8366fcf3a32730c6dffe45 Mon Sep 17 00:00:00 2001 From: Shawn Lin Date: Tue, 18 Nov 2025 17:52:05 +0800 Subject: [PATCH 0709/3902] phy: rockchip: naneng-combphy: Fix PCIe L1ss support RK3528 [ Upstream commit a2a18e5da64f8da306fa97c397b4c739ea776f37 ] When PCIe link enters L1 PM substates, the PHY will turn off its PLL for power-saving. However, it turns off the PLL too fast which leads the PHY to be broken. According to the PHY document, we need to delay PLL turnoff time. Fixes: bbcca4fac873 ("phy: rockchip: naneng-combphy: Add RK3528 support") Signed-off-by: Shawn Lin Reviewed-by: Neil Armstrong Link: https://patch.msgid.link/1763459526-35004-1-git-send-email-shawn.lin@rock-chips.com Signed-off-by: Vinod Koul Signed-off-by: Sasha Levin --- drivers/phy/rockchip/phy-rockchip-naneng-combphy.c | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/drivers/phy/rockchip/phy-rockchip-naneng-combphy.c b/drivers/phy/rockchip/phy-rockchip-naneng-combphy.c index a3ef19807b9ef4..e303bec8a996fc 100644 --- a/drivers/phy/rockchip/phy-rockchip-naneng-combphy.c +++ b/drivers/phy/rockchip/phy-rockchip-naneng-combphy.c @@ -21,6 +21,9 @@ #define REF_CLOCK_100MHz (100 * HZ_PER_MHZ) /* RK3528 COMBO PHY REG */ +#define RK3528_PHYREG5 0x14 +#define RK3528_PHYREG5_GATE_TX_PCK_SEL BIT(3) +#define RK3528_PHYREG5_GATE_TX_PCK_DLY_PLL_OFF BIT(3) #define RK3528_PHYREG6 0x18 #define RK3528_PHYREG6_PLL_KVCO GENMASK(12, 10) #define RK3528_PHYREG6_PLL_KVCO_VALUE 0x2 @@ -504,6 +507,10 @@ static int rk3528_combphy_cfg(struct rockchip_combphy_priv *priv) case REF_CLOCK_100MHz: rockchip_combphy_param_write(priv->phy_grf, &cfg->pipe_clk_100m, true); if (priv->type == PHY_TYPE_PCIE) { + /* Gate_tx_pck_sel length select for L1ss support */ + rockchip_combphy_updatel(priv, RK3528_PHYREG5_GATE_TX_PCK_SEL, + RK3528_PHYREG5_GATE_TX_PCK_DLY_PLL_OFF, RK3528_PHYREG5); + /* PLL KVCO tuning fine */ val = FIELD_PREP(RK3528_PHYREG6_PLL_KVCO, RK3528_PHYREG6_PLL_KVCO_VALUE); rockchip_combphy_updatel(priv, RK3528_PHYREG6_PLL_KVCO, val, From b36ba24f7d6d8bc669456742f01103629c139629 Mon Sep 17 00:00:00 2001 From: Shawn Lin Date: Tue, 18 Nov 2025 17:52:06 +0800 Subject: [PATCH 0710/3902] phy: rockchip: naneng-combphy: Fix PCIe L1ss support RK3562 [ Upstream commit be866e68966d20bcc4a73708093d577176f99c0c ] When PCIe link enters L1 PM substates, the PHY will turn off its PLL for power-saving. However, it turns off the PLL too fast which leads the PHY to be broken. According to the PHY document, we need to delay PLL turnoff time. Fixes: f13bff25161b ("phy: rockchip-naneng-combo: Support rk3562") Signed-off-by: Shawn Lin Reviewed-by: Neil Armstrong Link: https://patch.msgid.link/1763459526-35004-2-git-send-email-shawn.lin@rock-chips.com Signed-off-by: Vinod Koul Signed-off-by: Sasha Levin --- drivers/phy/rockchip/phy-rockchip-naneng-combphy.c | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/drivers/phy/rockchip/phy-rockchip-naneng-combphy.c b/drivers/phy/rockchip/phy-rockchip-naneng-combphy.c index e303bec8a996fc..7f8fc8e6d48905 100644 --- a/drivers/phy/rockchip/phy-rockchip-naneng-combphy.c +++ b/drivers/phy/rockchip/phy-rockchip-naneng-combphy.c @@ -106,6 +106,10 @@ #define RK3568_PHYREG18 0x44 #define RK3568_PHYREG18_PLL_LOOP 0x32 +#define RK3568_PHYREG30 0x74 +#define RK3568_PHYREG30_GATE_TX_PCK_SEL BIT(7) +#define RK3568_PHYREG30_GATE_TX_PCK_DLY_PLL_OFF BIT(7) + #define RK3568_PHYREG32 0x7C #define RK3568_PHYREG32_SSC_MASK GENMASK(7, 4) #define RK3568_PHYREG32_SSC_DIR_MASK GENMASK(5, 4) @@ -664,6 +668,10 @@ static int rk3562_combphy_cfg(struct rockchip_combphy_priv *priv) case REF_CLOCK_100MHz: rockchip_combphy_param_write(priv->phy_grf, &cfg->pipe_clk_100m, true); if (priv->type == PHY_TYPE_PCIE) { + /* Gate_tx_pck_sel length select for L1ss support */ + rockchip_combphy_updatel(priv, RK3568_PHYREG30_GATE_TX_PCK_SEL, + RK3568_PHYREG30_GATE_TX_PCK_DLY_PLL_OFF, + RK3568_PHYREG30); /* PLL KVCO tuning fine */ val = FIELD_PREP(RK3568_PHYREG33_PLL_KVCO_MASK, RK3568_PHYREG33_PLL_KVCO_VALUE); From a614c316a8c29ba35da15dad77155a972a04499e Mon Sep 17 00:00:00 2001 From: Xiaolei Wang Date: Thu, 25 Sep 2025 09:38:06 +0800 Subject: [PATCH 0711/3902] phy: freescale: Initialize priv->lock [ Upstream commit 95e5905698983df94069e185f9eb3c67c7cf75d5 ] Initialize priv->lock to fix the following warning. WARNING: CPU: 0 PID: 12 at kernel/locking/mutex.c:577 __mutex_lock+0x70c/0x8b8 Modules linked in: Hardware name: Freescale i.MX8QM MEK (DT) Call trace: __mutex_lock+0x70c/0x8b8 (P) mutex_lock_nested+0x24/0x30 imx_hsio_power_on+0x4c/0x764 phy_power_on+0x7c/0x12c imx_pcie_host_init+0x1d0/0x4d4 dw_pcie_host_init+0x188/0x4b0 imx_pcie_probe+0x324/0x6f4 platform_probe+0x5c/0x98 really_probe+0xbc/0x29c __driver_probe_device+0x78/0x12c driver_probe_device+0xd8/0x160 __device_attach_driver+0xb8/0x138 bus_for_each_drv+0x84/0xe4 __device_attach_async_helper+0xb8/0xdc async_run_entry_fn+0x34/0xe0 process_one_work+0x220/0x694 worker_thread+0x1c0/0x36c kthread+0x14c/0x224 Fixes: 82c56b6dd24f ("phy: freescale: imx8qm-hsio: Add i.MX8QM HSIO PHY driver support") Signed-off-by: Xiaolei Wang Reviewed-by: Frank Li Reviewed-by: Neil Armstrong Link: https://patch.msgid.link/20250925013806.569658-1-xiaolei.wang@windriver.com Signed-off-by: Vinod Koul Signed-off-by: Sasha Levin --- drivers/phy/freescale/phy-fsl-imx8qm-hsio.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/drivers/phy/freescale/phy-fsl-imx8qm-hsio.c b/drivers/phy/freescale/phy-fsl-imx8qm-hsio.c index 5dca93cd325c89..977d21d753a59a 100644 --- a/drivers/phy/freescale/phy-fsl-imx8qm-hsio.c +++ b/drivers/phy/freescale/phy-fsl-imx8qm-hsio.c @@ -533,7 +533,7 @@ static struct phy *imx_hsio_xlate(struct device *dev, static int imx_hsio_probe(struct platform_device *pdev) { - int i; + int i, ret; void __iomem *off; struct device *dev = &pdev->dev; struct device_node *np = dev->of_node; @@ -545,6 +545,9 @@ static int imx_hsio_probe(struct platform_device *pdev) return -ENOMEM; priv->dev = &pdev->dev; priv->drvdata = of_device_get_match_data(dev); + ret = devm_mutex_init(dev, &priv->lock); + if (ret) + return ret; /* Get HSIO configuration mode */ if (of_property_read_string(np, "fsl,hsio-cfg", &priv->hsio_cfg)) From 26efe0db47e5c07a5e6db10391422b71d1272c8e Mon Sep 17 00:00:00 2001 From: Cristian Ciocaltea Date: Tue, 28 Oct 2025 10:00:54 +0200 Subject: [PATCH 0712/3902] phy: rockchip: samsung-hdptx: Fix reported clock rate in high bpc mode [ Upstream commit 72126e9623e1696ea83c77ef6d0306a6263bdd6b ] When making use of the clock provider functionality, the output clock does normally match the TMDS character rate, which is what the PHY PLL gets configured to. However, this is only applicable for default color depth of 8 bpc. For higher depths, the output clock is further divided by the hardware according to the formula: output_clock_rate = tmds_char_rate * 8 / bpc Since the existence of the clock divider wasn't taken into account when support for high bpc has been introduced, make the necessary adjustments to report the correct clock rate. Fixes: 9d0ec51d7c22 ("phy: rockchip: samsung-hdptx: Add high color depth management") Reported-by: Andy Yan Signed-off-by: Cristian Ciocaltea Reviewed-by: Neil Armstrong Link: https://patch.msgid.link/20251028-phy-hdptx-fixes-v1-1-ecc642a59d94@collabora.com Signed-off-by: Vinod Koul Signed-off-by: Sasha Levin --- drivers/phy/rockchip/phy-rockchip-samsung-hdptx.c | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/drivers/phy/rockchip/phy-rockchip-samsung-hdptx.c b/drivers/phy/rockchip/phy-rockchip-samsung-hdptx.c index 01bbf668e05ef9..aee03e8655f66d 100644 --- a/drivers/phy/rockchip/phy-rockchip-samsung-hdptx.c +++ b/drivers/phy/rockchip/phy-rockchip-samsung-hdptx.c @@ -1037,7 +1037,8 @@ static int rk_hdptx_ropll_tmds_cmn_config(struct rk_hdptx_phy *hdptx) ret = rk_hdptx_post_enable_pll(hdptx); if (!ret) - hdptx->hw_rate = hdptx->hdmi_cfg.tmds_char_rate; + hdptx->hw_rate = DIV_ROUND_CLOSEST_ULL(hdptx->hdmi_cfg.tmds_char_rate * 8, + hdptx->hdmi_cfg.bpc); return ret; } @@ -1895,19 +1896,20 @@ static long rk_hdptx_phy_clk_round_rate(struct clk_hw *hw, unsigned long rate, * hence ensure rk_hdptx_phy_clk_set_rate() won't be invoked with * a different rate argument. */ - return hdptx->hdmi_cfg.tmds_char_rate; + return DIV_ROUND_CLOSEST_ULL(hdptx->hdmi_cfg.tmds_char_rate * 8, hdptx->hdmi_cfg.bpc); } static int rk_hdptx_phy_clk_set_rate(struct clk_hw *hw, unsigned long rate, unsigned long parent_rate) { struct rk_hdptx_phy *hdptx = to_rk_hdptx_phy(hw); + unsigned long long tmds_rate = DIV_ROUND_CLOSEST_ULL(rate * hdptx->hdmi_cfg.bpc, 8); /* Revert any unlikely TMDS char rate change since round_rate() */ - if (hdptx->hdmi_cfg.tmds_char_rate != rate) { - dev_warn(hdptx->dev, "Reverting unexpected rate change from %lu to %llu\n", - rate, hdptx->hdmi_cfg.tmds_char_rate); - hdptx->hdmi_cfg.tmds_char_rate = rate; + if (hdptx->hdmi_cfg.tmds_char_rate != tmds_rate) { + dev_warn(hdptx->dev, "Reverting unexpected rate change from %llu to %llu\n", + tmds_rate, hdptx->hdmi_cfg.tmds_char_rate); + hdptx->hdmi_cfg.tmds_char_rate = tmds_rate; } /* From 348c9b3d47a65081f47d74b49569ef28c903ac98 Mon Sep 17 00:00:00 2001 From: Cristian Ciocaltea Date: Tue, 28 Oct 2025 10:00:55 +0200 Subject: [PATCH 0713/3902] phy: rockchip: samsung-hdptx: Reduce ROPLL loop bandwidth [ Upstream commit 8daaced9f5eeb4a2c8ca08b0a8286b6a498a8387 ] Due to its relatively low frequency, a noise stemming from the 24MHz PLL reference clock may traverse the low-pass loop filter of ROPLL, which could potentially generate some HDMI flash artifacts. Reduce ROPLL loop bandwidth in an attempt to mitigate the problem. Fixes: 553be2830c5f ("phy: rockchip: Add Samsung HDMI/eDP Combo PHY driver") Co-developed-by: Algea Cao Signed-off-by: Algea Cao Signed-off-by: Cristian Ciocaltea Reviewed-by: Neil Armstrong Link: https://patch.msgid.link/20251028-phy-hdptx-fixes-v1-2-ecc642a59d94@collabora.com Signed-off-by: Vinod Koul Signed-off-by: Sasha Levin --- drivers/phy/rockchip/phy-rockchip-samsung-hdptx.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/phy/rockchip/phy-rockchip-samsung-hdptx.c b/drivers/phy/rockchip/phy-rockchip-samsung-hdptx.c index aee03e8655f66d..8ba9b53c2309b2 100644 --- a/drivers/phy/rockchip/phy-rockchip-samsung-hdptx.c +++ b/drivers/phy/rockchip/phy-rockchip-samsung-hdptx.c @@ -500,9 +500,7 @@ static const struct reg_sequence rk_hdtpx_common_cmn_init_seq[] = { REG_SEQ0(CMN_REG(0043), 0x00), REG_SEQ0(CMN_REG(0044), 0x46), REG_SEQ0(CMN_REG(0045), 0x24), - REG_SEQ0(CMN_REG(0046), 0xff), REG_SEQ0(CMN_REG(0047), 0x00), - REG_SEQ0(CMN_REG(0048), 0x44), REG_SEQ0(CMN_REG(0049), 0xfa), REG_SEQ0(CMN_REG(004a), 0x08), REG_SEQ0(CMN_REG(004b), 0x00), @@ -575,6 +573,8 @@ static const struct reg_sequence rk_hdtpx_tmds_cmn_init_seq[] = { REG_SEQ0(CMN_REG(0034), 0x00), REG_SEQ0(CMN_REG(003d), 0x40), REG_SEQ0(CMN_REG(0042), 0x78), + REG_SEQ0(CMN_REG(0046), 0xdd), + REG_SEQ0(CMN_REG(0048), 0x11), REG_SEQ0(CMN_REG(004e), 0x34), REG_SEQ0(CMN_REG(005c), 0x25), REG_SEQ0(CMN_REG(005e), 0x4f), From fd6835c98ea6669d7422d40b96a3710fa9157629 Mon Sep 17 00:00:00 2001 From: Cristian Ciocaltea Date: Tue, 28 Oct 2025 10:00:56 +0200 Subject: [PATCH 0714/3902] phy: rockchip: samsung-hdptx: Prevent Inter-Pair Skew from exceeding the limits [ Upstream commit 51023cf6cc5db3423dea6620746d9087e336e024 ] Fixup PHY deskew FIFO to prevent the phase of D2 lane going ahead of other lanes. It's worth noting this might only happen when dealing with HDMI 2.0 rates. Fixes: 553be2830c5f ("phy: rockchip: Add Samsung HDMI/eDP Combo PHY driver") Co-developed-by: Algea Cao Signed-off-by: Algea Cao Signed-off-by: Cristian Ciocaltea Reviewed-by: Neil Armstrong Link: https://patch.msgid.link/20251028-phy-hdptx-fixes-v1-3-ecc642a59d94@collabora.com Signed-off-by: Vinod Koul Signed-off-by: Sasha Levin --- drivers/phy/rockchip/phy-rockchip-samsung-hdptx.c | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/drivers/phy/rockchip/phy-rockchip-samsung-hdptx.c b/drivers/phy/rockchip/phy-rockchip-samsung-hdptx.c index 8ba9b53c2309b2..29de2f7bdae8a3 100644 --- a/drivers/phy/rockchip/phy-rockchip-samsung-hdptx.c +++ b/drivers/phy/rockchip/phy-rockchip-samsung-hdptx.c @@ -668,13 +668,9 @@ static const struct reg_sequence rk_hdtpx_common_lane_init_seq[] = { static const struct reg_sequence rk_hdtpx_tmds_lane_init_seq[] = { REG_SEQ0(LANE_REG(0312), 0x00), - REG_SEQ0(LANE_REG(031e), 0x00), REG_SEQ0(LANE_REG(0412), 0x00), - REG_SEQ0(LANE_REG(041e), 0x00), REG_SEQ0(LANE_REG(0512), 0x00), - REG_SEQ0(LANE_REG(051e), 0x00), REG_SEQ0(LANE_REG(0612), 0x00), - REG_SEQ0(LANE_REG(061e), 0x08), REG_SEQ0(LANE_REG(0303), 0x2f), REG_SEQ0(LANE_REG(0403), 0x2f), REG_SEQ0(LANE_REG(0503), 0x2f), @@ -687,6 +683,11 @@ static const struct reg_sequence rk_hdtpx_tmds_lane_init_seq[] = { REG_SEQ0(LANE_REG(0406), 0x1c), REG_SEQ0(LANE_REG(0506), 0x1c), REG_SEQ0(LANE_REG(0606), 0x1c), + /* Keep Inter-Pair Skew in the limits */ + REG_SEQ0(LANE_REG(031e), 0x02), + REG_SEQ0(LANE_REG(041e), 0x02), + REG_SEQ0(LANE_REG(051e), 0x02), + REG_SEQ0(LANE_REG(061e), 0x0a), }; static struct tx_drv_ctrl tx_drv_ctrl_rbr[4][4] = { From 340a73d8b6647a8e2404247cd0207337beb945c0 Mon Sep 17 00:00:00 2001 From: Charles Keepax Date: Thu, 20 Nov 2025 15:30:11 +0000 Subject: [PATCH 0715/3902] ASoC: SDCA: Fix missing dash in HIDE DisCo property [ Upstream commit 3508311f2e1c872b645f13c6fd52840418089d41 ] The property name is "mipi-sdca-RxUMP-ownership-transition-max-delay", with a dash between max and delay. Add the missing dash. Fixes: 13ef21dffe76 ("ASoC: SDCA: add support for HIDE entity properties and HID descriptor/report") Tested-by: Bard Liao Reviewed-by: Maciej Strozek Reviewed-by: Peter Ujfalusi Tested-by: Richard Fitzgerald Signed-off-by: Charles Keepax Link: https://patch.msgid.link/20251120153023.2105663-3-ckeepax@opensource.cirrus.com Reviewed-by: Vinod Koul Signed-off-by: Mark Brown Signed-off-by: Sasha Levin --- sound/soc/sdca/sdca_functions.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sound/soc/sdca/sdca_functions.c b/sound/soc/sdca/sdca_functions.c index 0ccb6775f4de3d..19b12564f822ef 100644 --- a/sound/soc/sdca/sdca_functions.c +++ b/sound/soc/sdca/sdca_functions.c @@ -1263,7 +1263,7 @@ find_sdca_entity_hide(struct device *dev, struct fwnode_handle *function_node, unsigned char *report_desc = NULL; ret = fwnode_property_read_u32(entity_node, - "mipi-sdca-RxUMP-ownership-transition-maxdelay", &delay); + "mipi-sdca-RxUMP-ownership-transition-max-delay", &delay); if (!ret) hide->max_delay = delay; From 59bea0e26b8cf54b3db89e0ef0de2c90696fd5a3 Mon Sep 17 00:00:00 2001 From: Matt Bobrowski Date: Tue, 18 Nov 2025 07:37:34 +0000 Subject: [PATCH 0716/3902] selftests/bpf: Use ASSERT_STRNEQ to factor in long slab cache names [ Upstream commit d088da904223e8f5e19c6d156cf372d5baec1a7c ] subtest_kmem_cache_iter_check_slabinfo() fundamentally compares slab cache names parsed out from /proc/slabinfo against those stored within struct kmem_cache_result. The current problem is that the slab cache name within struct kmem_cache_result is stored within a bounded fixed-length array (sized to SLAB_NAME_MAX(32)), whereas the name parsed out from /proc/slabinfo is not. Meaning, using ASSERT_STREQ() can certainly lead to test failures, particularly when dealing with slab cache names that are longer than SLAB_NAME_MAX(32) bytes. Notably, kmem_cache_create() allows callers to create slab caches with somewhat arbitrarily sized names via its __name identifier argument, so exceeding the SLAB_NAME_MAX(32) limit that is in place now can certainly happen. Make subtest_kmem_cache_iter_check_slabinfo() more reliable by only checking up to sizeof(struct kmem_cache_result.name) - 1 using ASSERT_STRNEQ(). Fixes: a496d0cdc84d ("selftests/bpf: Add a test for kmem_cache_iter") Signed-off-by: Matt Bobrowski Signed-off-by: Martin KaFai Lau Acked-by: Song Liu Link: https://patch.msgid.link/20251118073734.4188710-1-mattbobrowski@google.com Signed-off-by: Sasha Levin --- tools/testing/selftests/bpf/prog_tests/kmem_cache_iter.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tools/testing/selftests/bpf/prog_tests/kmem_cache_iter.c b/tools/testing/selftests/bpf/prog_tests/kmem_cache_iter.c index 1de14b111931aa..6e35e13c20220e 100644 --- a/tools/testing/selftests/bpf/prog_tests/kmem_cache_iter.c +++ b/tools/testing/selftests/bpf/prog_tests/kmem_cache_iter.c @@ -57,7 +57,8 @@ static void subtest_kmem_cache_iter_check_slabinfo(struct kmem_cache_iter *skel) if (!ASSERT_OK(ret, "kmem_cache_lookup")) break; - ASSERT_STREQ(r.name, name, "kmem_cache_name"); + ASSERT_STRNEQ(r.name, name, sizeof(r.name) - 1, + "kmem_cache_name"); ASSERT_EQ(r.obj_size, objsize, "kmem_cache_objsize"); seen++; From 872b3ffc5107b7486364efd9e213b8ed866d4179 Mon Sep 17 00:00:00 2001 From: Alexander Dahl Date: Wed, 19 Nov 2025 13:47:36 +0100 Subject: [PATCH 0717/3902] net: phy: adin1100: Fix software power-down ready condition MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [ Upstream commit bccaf1fe08f2c9f96f6bc38391d41e67f6bf38e3 ] Value CRSM_SFT_PD written to Software Power-Down Control Register (CRSM_SFT_PD_CNTRL) is 0x01 and therefor different to value CRSM_SFT_PD_RDY (0x02) read from System Status Register (CRSM_STAT) for confirmation powerdown has been reached. The condition could have only worked when disabling powerdown (both 0x00), but never when enabling it (0x01 != 0x02). Result is a timeout, like so: $ ifdown eth0 macb f802c000.ethernet eth0: Link is Down ADIN1100 f802c000.ethernet-ffffffff:01: adin_set_powerdown_mode failed: -110 ADIN1100 f802c000.ethernet-ffffffff:01: adin_set_powerdown_mode failed: -110 Fixes: 7eaf9132996a ("net: phy: adin1100: Add initial support for ADIN1100 industrial PHY") Signed-off-by: Alexander Dahl Reviewed-by: Russell King (Oracle) Acked-by: Nuno Sá Link: https://patch.msgid.link/20251119124737.280939-2-ada@thorsis.com Signed-off-by: Jakub Kicinski Signed-off-by: Sasha Levin --- drivers/net/phy/adin1100.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/net/phy/adin1100.c b/drivers/net/phy/adin1100.c index bd7a47a903acae..10b796c2daee7c 100644 --- a/drivers/net/phy/adin1100.c +++ b/drivers/net/phy/adin1100.c @@ -201,7 +201,7 @@ static int adin_set_powerdown_mode(struct phy_device *phydev, bool en) return ret; return phy_read_mmd_poll_timeout(phydev, MDIO_MMD_VEND1, ADIN_CRSM_STAT, ret, - (ret & ADIN_CRSM_SFT_PD_RDY) == val, + !!(ret & ADIN_CRSM_SFT_PD_RDY) == en, 1000, 30000, true); } From 04c79e05e13ce3cff44c4c4af8e8aa25e5086484 Mon Sep 17 00:00:00 2001 From: Chen Ridong Date: Fri, 14 Nov 2025 02:08:47 +0000 Subject: [PATCH 0718/3902] cpuset: Treat cpusets in attaching as populated [ Upstream commit b1bcaed1e39a9e0dfbe324a15d2ca4253deda316 ] Currently, the check for whether a partition is populated does not account for tasks in the cpuset of attaching. This is a corner case that can leave a task stuck in a partition with no effective CPUs. The race condition occurs as follows: cpu0 cpu1 //cpuset A with cpu N migrate task p to A cpuset_can_attach // with effective cpus // check ok // cpuset_mutex is not held // clear cpuset.cpus.exclusive // making effective cpus empty update_exclusive_cpumask // tasks_nocpu_error check ok // empty effective cpus, partition valid cpuset_attach ... // task p stays in A, with non-effective cpus. To fix this issue, this patch introduces cs_is_populated, which considers tasks in the attaching cpuset. This new helper is used in validate_change and partition_is_populated. Fixes: e2d59900d936 ("cgroup/cpuset: Allow no-task partition to have empty cpuset.cpus.effective") Signed-off-by: Chen Ridong Reviewed-by: Waiman Long Signed-off-by: Tejun Heo Signed-off-by: Sasha Levin --- kernel/cgroup/cpuset.c | 35 +++++++++++++++++++++++++++-------- 1 file changed, 27 insertions(+), 8 deletions(-) diff --git a/kernel/cgroup/cpuset.c b/kernel/cgroup/cpuset.c index 52468d2c178a3e..4dcd633fd6df5f 100644 --- a/kernel/cgroup/cpuset.c +++ b/kernel/cgroup/cpuset.c @@ -352,6 +352,15 @@ static inline bool is_in_v2_mode(void) (cpuset_cgrp_subsys.root->flags & CGRP_ROOT_CPUSET_V2_MODE); } +static inline bool cpuset_is_populated(struct cpuset *cs) +{ + lockdep_assert_held(&cpuset_mutex); + + /* Cpusets in the process of attaching should be considered as populated */ + return cgroup_is_populated(cs->css.cgroup) || + cs->attach_in_progress; +} + /** * partition_is_populated - check if partition has tasks * @cs: partition root to be checked @@ -364,21 +373,31 @@ static inline bool is_in_v2_mode(void) static inline bool partition_is_populated(struct cpuset *cs, struct cpuset *excluded_child) { - struct cgroup_subsys_state *css; - struct cpuset *child; + struct cpuset *cp; + struct cgroup_subsys_state *pos_css; - if (cs->css.cgroup->nr_populated_csets) + /* + * We cannot call cs_is_populated(cs) directly, as + * nr_populated_domain_children may include populated + * csets from descendants that are partitions. + */ + if (cs->css.cgroup->nr_populated_csets || + cs->attach_in_progress) return true; if (!excluded_child && !cs->nr_subparts) return cgroup_is_populated(cs->css.cgroup); rcu_read_lock(); - cpuset_for_each_child(child, css, cs) { - if (child == excluded_child) + cpuset_for_each_descendant_pre(cp, pos_css, cs) { + if (cp == cs || cp == excluded_child) continue; - if (is_partition_valid(child)) + + if (is_partition_valid(cp)) { + pos_css = css_rightmost_descendant(pos_css); continue; - if (cgroup_is_populated(child->css.cgroup)) { + } + + if (cpuset_is_populated(cp)) { rcu_read_unlock(); return true; } @@ -663,7 +682,7 @@ static int validate_change(struct cpuset *cur, struct cpuset *trial) * be changed to have empty cpus_allowed or mems_allowed. */ ret = -ENOSPC; - if ((cgroup_is_populated(cur->css.cgroup) || cur->attach_in_progress)) { + if (cpuset_is_populated(cur)) { if (!cpumask_empty(cur->cpus_allowed) && cpumask_empty(trial->cpus_allowed)) goto out; From ee5b0b28c9938b1cc88f23842aedef86e617e18b Mon Sep 17 00:00:00 2001 From: Charles Mirabile Date: Mon, 17 Nov 2025 14:03:29 -0500 Subject: [PATCH 0719/3902] clk: spacemit: Set clk_hw_onecell_data::num before using flex array [ Upstream commit 23b2d2fb136959fd0a8e309c70be83d9b8841c7e ] When booting with KASAN enabled the following splat is encountered during probe of the k1 clock driver: UBSAN: array-index-out-of-bounds in drivers/clk/spacemit/ccu-k1.c:1044:16 index 0 is out of range for type 'clk_hw *[*]' CPU: 0 UID: 0 PID: 1 Comm: swapper/0 Not tainted 6.18.0-rc5+ #1 PREEMPT(lazy) Hardware name: Unknown Unknown Product/Unknown Product, BIOS 2022.10spacemit 10/01/2022 Call Trace: [] dump_backtrace+0x28/0x38 [] show_stack+0x3a/0x50 [] dump_stack_lvl+0x5a/0x80 [] dump_stack+0x18/0x20 [] ubsan_epilogue+0x10/0x48 [] __ubsan_handle_out_of_bounds+0xa6/0xa8 [] k1_ccu_probe+0x37e/0x420 [] platform_probe+0x56/0x98 [] really_probe+0x9e/0x350 [] __driver_probe_device+0x80/0x138 [] driver_probe_device+0x3a/0xd0 [] __driver_attach+0xac/0x1b8 [] bus_for_each_dev+0x6c/0xc8 [] driver_attach+0x26/0x38 [] bus_add_driver+0x13e/0x268 [] driver_register+0x52/0x100 [] __platform_driver_register+0x28/0x38 [] k1_ccu_driver_init+0x22/0x38 [] do_one_initcall+0x62/0x2a0 [] do_initcalls+0x170/0x1a8 [] kernel_init_freeable+0x16a/0x1e0 [] kernel_init+0x2c/0x180 [] ret_from_fork_kernel+0x16/0x1d8 [] ret_from_fork_kernel_asm+0x16/0x18 ---[ end trace ]--- This is bogus and is simply a result of KASAN consulting the `.num` member of the struct for bounds information (as it should due to `__counted_by`) and finding 0 set by kzalloc() because it has not been initialized before the loop that fills in the array. The easy fix is to just move the line that sets `num` to before the loop that fills the array so that KASAN has the information it needs to accurately conclude that the access is valid. Fixes: 1b72c59db0add ("clk: spacemit: Add clock support for SpacemiT K1 SoC") Tested-by: Yanko Kaneti Signed-off-by: Charles Mirabile Reviewed-by: Alex Elder Reviewed-by: Troy Mitchell Reviewed-by: Yixun Lan Signed-off-by: Stephen Boyd Signed-off-by: Sasha Levin --- drivers/clk/spacemit/ccu-k1.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/clk/spacemit/ccu-k1.c b/drivers/clk/spacemit/ccu-k1.c index f5a9fe6ba18591..4761bc1e3b6e60 100644 --- a/drivers/clk/spacemit/ccu-k1.c +++ b/drivers/clk/spacemit/ccu-k1.c @@ -1018,6 +1018,8 @@ static int spacemit_ccu_register(struct device *dev, if (!clk_data) return -ENOMEM; + clk_data->num = data->num; + for (i = 0; i < data->num; i++) { struct clk_hw *hw = data->hws[i]; struct ccu_common *common; @@ -1044,8 +1046,6 @@ static int spacemit_ccu_register(struct device *dev, clk_data->hws[i] = hw; } - clk_data->num = data->num; - ret = devm_of_clk_add_hw_provider(dev, of_clk_hw_onecell_get, clk_data); if (ret) dev_err(dev, "failed to add clock hardware provider (%d)\n", ret); From 5ebf0fe7eaef9f6173a4c6ea77c5353e21645d15 Mon Sep 17 00:00:00 2001 From: Seungjin Bae Date: Mon, 17 Nov 2025 20:32:59 -0500 Subject: [PATCH 0720/3902] wifi: rtl818x: rtl8187: Fix potential buffer underflow in rtl8187_rx_cb() [ Upstream commit b647d2574e4583c2e3b0ab35568f60c88e910840 ] The rtl8187_rx_cb() calculates the rx descriptor header address by subtracting its size from the skb tail pointer. However, it does not validate if the received packet (skb->len from urb->actual_length) is large enough to contain this header. If a truncated packet is received, this will lead to a buffer underflow, reading memory before the start of the skb data area, and causing a kernel panic. Add length checks for both rtl8187 and rtl8187b descriptor headers before attempting to access them, dropping the packet cleanly if the check fails. Fixes: 6f7853f3cbe4 ("rtl8187: change rtl8187_dev.c to support RTL8187B (part 2)") Signed-off-by: Seungjin Bae Signed-off-by: Ping-Ke Shih Link: https://patch.msgid.link/20251118013258.1789949-2-eeodqql09@gmail.com Signed-off-by: Sasha Levin --- .../wireless/realtek/rtl818x/rtl8187/dev.c | 27 +++++++++++++------ 1 file changed, 19 insertions(+), 8 deletions(-) diff --git a/drivers/net/wireless/realtek/rtl818x/rtl8187/dev.c b/drivers/net/wireless/realtek/rtl818x/rtl8187/dev.c index 0c5c66401daa6d..7aa2da0cd63cc8 100644 --- a/drivers/net/wireless/realtek/rtl818x/rtl8187/dev.c +++ b/drivers/net/wireless/realtek/rtl818x/rtl8187/dev.c @@ -338,14 +338,16 @@ static void rtl8187_rx_cb(struct urb *urb) spin_unlock_irqrestore(&priv->rx_queue.lock, f); skb_put(skb, urb->actual_length); - if (unlikely(urb->status)) { - dev_kfree_skb_irq(skb); - return; - } + if (unlikely(urb->status)) + goto free_skb; if (!priv->is_rtl8187b) { - struct rtl8187_rx_hdr *hdr = - (typeof(hdr))(skb_tail_pointer(skb) - sizeof(*hdr)); + struct rtl8187_rx_hdr *hdr; + + if (skb->len < sizeof(struct rtl8187_rx_hdr)) + goto free_skb; + + hdr = (typeof(hdr))(skb_tail_pointer(skb) - sizeof(*hdr)); flags = le32_to_cpu(hdr->flags); /* As with the RTL8187B below, the AGC is used to calculate * signal strength. In this case, the scaling @@ -355,8 +357,12 @@ static void rtl8187_rx_cb(struct urb *urb) rx_status.antenna = (hdr->signal >> 7) & 1; rx_status.mactime = le64_to_cpu(hdr->mac_time); } else { - struct rtl8187b_rx_hdr *hdr = - (typeof(hdr))(skb_tail_pointer(skb) - sizeof(*hdr)); + struct rtl8187b_rx_hdr *hdr; + + if (skb->len < sizeof(struct rtl8187b_rx_hdr)) + goto free_skb; + + hdr = (typeof(hdr))(skb_tail_pointer(skb) - sizeof(*hdr)); /* The Realtek datasheet for the RTL8187B shows that the RX * header contains the following quantities: signal quality, * RSSI, AGC, the received power in dB, and the measured SNR. @@ -409,6 +415,11 @@ static void rtl8187_rx_cb(struct urb *urb) skb_unlink(skb, &priv->rx_queue); dev_kfree_skb_irq(skb); } + return; + +free_skb: + dev_kfree_skb_irq(skb); + return; } static int rtl8187_init_urbs(struct ieee80211_hw *dev) From 22b5096abc9824fb84f0bfe084f5be9f7ea5f2d9 Mon Sep 17 00:00:00 2001 From: Jason Tian Date: Thu, 14 Aug 2025 09:52:52 -0700 Subject: [PATCH 0721/3902] RAS: Report all ARM processor CPER information to userspace [ Upstream commit 05954511b73e748d0370549ad9dd9cd95297d97a ] The ARM processor CPER record was added in UEFI v2.6 and remained unchanged up to v2.10. Yet, the original arm_event trace code added by e9279e83ad1f ("trace, ras: add ARM processor error trace event") is incomplete, as it only traces some fields of UAPI 2.6 table N.16, not exporting any information from tables N.17 to N.29 of the record. This is not enough for the user to be able to figure out what has exactly happened or to take appropriate action. According to the UEFI v2.9 specification chapter N2.4.4, the ARM processor error section includes: - several (ERR_INFO_NUM) ARM processor error information structures (Tables N.17 to N.20); - several (CONTEXT_INFO_NUM) ARM processor context information structures (Tables N.21 to N.29); - several vendor specific error information structures. The size is given by Section Length minus the size of the other fields. In addition, it also exports two fields that are parsed by the GHES driver when firmware reports it, e.g.: - error severity - CPU logical index Report all of these information to userspace via a the ARM tracepoint so that userspace can properly record the error and take decisions related to CPU core isolation according to error severity and other info. The updated ARM trace event now contains the following fields: ====================================== ============================= UEFI field on table N.16 ARM Processor trace fields ====================================== ============================= Validation handled when filling data for affinity MPIDR and running state. ERR_INFO_NUM pei_len CONTEXT_INFO_NUM ctx_len Section Length indirectly reported by pei_len, ctx_len and oem_len Error affinity level affinity MPIDR_EL1 mpidr MIDR_EL1 midr Running State running_state PSCI State psci_state Processor Error Information Structure pei_err - count at pei_len Processor Context ctx_err- count at ctx_len Vendor Specific Error Info oem - count at oem_len ====================================== ============================= It should be noted that decoding of tables N.17 to N.29, if needed, will be handled in userspace. That gives more flexibility, as there won't be any need to flood the kernel with micro-architecture specific error decoding. Also, decoding the other fields require a complex logic, and should be done for each of the several values inside the record field. So, let userspace daemons like rasdaemon decode them, parsing such tables and having vendor-specific micro-architecture-specific decoders. [mchehab: modified description, solved merge conflicts and fixed coding style] Signed-off-by: Jason Tian Co-developed-by: Shengwei Luo Signed-off-by: Shengwei Luo Signed-off-by: Mauro Carvalho Chehab Signed-off-by: Daniel Ferguson # rebased Reviewed-by: Jonathan Cameron Tested-by: Shiju Jose Acked-by: Borislav Petkov (AMD) Fixes: e9279e83ad1f ("trace, ras: add ARM processor error trace event") Link: https://uefi.org/specs/UEFI/2.10/Apx_N_Common_Platform_Error_Record.html#arm-processor-error-section Signed-off-by: Ard Biesheuvel Signed-off-by: Sasha Levin --- drivers/acpi/apei/ghes.c | 11 ++++----- drivers/ras/ras.c | 40 ++++++++++++++++++++++++++++++-- include/linux/ras.h | 16 ++++++++++--- include/ras/ras_event.h | 49 ++++++++++++++++++++++++++++++++++++---- 4 files changed, 99 insertions(+), 17 deletions(-) diff --git a/drivers/acpi/apei/ghes.c b/drivers/acpi/apei/ghes.c index 97ee19f2cae060..7d2466b515046b 100644 --- a/drivers/acpi/apei/ghes.c +++ b/drivers/acpi/apei/ghes.c @@ -552,7 +552,7 @@ static bool ghes_handle_memory_failure(struct acpi_hest_generic_data *gdata, } static bool ghes_handle_arm_hw_error(struct acpi_hest_generic_data *gdata, - int sev, bool sync) + int sev, bool sync) { struct cper_sec_proc_arm *err = acpi_hest_get_payload(gdata); int flags = sync ? MF_ACTION_REQUIRED : 0; @@ -560,9 +560,8 @@ static bool ghes_handle_arm_hw_error(struct acpi_hest_generic_data *gdata, int sec_sev, i; char *p; - log_arm_hw_error(err); - sec_sev = ghes_severity(gdata->error_severity); + log_arm_hw_error(err, sec_sev); if (sev != GHES_SEV_RECOVERABLE || sec_sev != GHES_SEV_RECOVERABLE) return false; @@ -895,11 +894,9 @@ static void ghes_do_proc(struct ghes *ghes, arch_apei_report_mem_error(sev, mem_err); queued = ghes_handle_memory_failure(gdata, sev, sync); - } - else if (guid_equal(sec_type, &CPER_SEC_PCIE)) { + } else if (guid_equal(sec_type, &CPER_SEC_PCIE)) { ghes_handle_aer(gdata); - } - else if (guid_equal(sec_type, &CPER_SEC_PROC_ARM)) { + } else if (guid_equal(sec_type, &CPER_SEC_PROC_ARM)) { queued = ghes_handle_arm_hw_error(gdata, sev, sync); } else if (guid_equal(sec_type, &CPER_SEC_CXL_PROT_ERR)) { struct cxl_cper_sec_prot_err *prot_err = acpi_hest_get_payload(gdata); diff --git a/drivers/ras/ras.c b/drivers/ras/ras.c index ac0e132ccc3eb9..2a5b5a9fdcb36c 100644 --- a/drivers/ras/ras.c +++ b/drivers/ras/ras.c @@ -53,9 +53,45 @@ void log_non_standard_event(const guid_t *sec_type, const guid_t *fru_id, } EXPORT_SYMBOL_GPL(log_non_standard_event); -void log_arm_hw_error(struct cper_sec_proc_arm *err) +void log_arm_hw_error(struct cper_sec_proc_arm *err, const u8 sev) { - trace_arm_event(err); + struct cper_arm_err_info *err_info; + struct cper_arm_ctx_info *ctx_info; + u8 *ven_err_data; + u32 ctx_len = 0; + int n, sz, cpu; + s32 vsei_len; + u32 pei_len; + u8 *pei_err, *ctx_err; + + pei_len = sizeof(struct cper_arm_err_info) * err->err_info_num; + pei_err = (u8 *)(err + 1); + + err_info = (struct cper_arm_err_info *)(err + 1); + ctx_info = (struct cper_arm_ctx_info *)(err_info + err->err_info_num); + ctx_err = (u8 *)ctx_info; + + for (n = 0; n < err->context_info_num; n++) { + sz = sizeof(struct cper_arm_ctx_info) + ctx_info->size; + ctx_info = (struct cper_arm_ctx_info *)((long)ctx_info + sz); + ctx_len += sz; + } + + vsei_len = err->section_length - (sizeof(struct cper_sec_proc_arm) + pei_len + ctx_len); + if (vsei_len < 0) { + pr_warn(FW_BUG "section length: %d\n", err->section_length); + pr_warn(FW_BUG "section length is too small\n"); + pr_warn(FW_BUG "firmware-generated error record is incorrect\n"); + vsei_len = 0; + } + ven_err_data = (u8 *)ctx_info; + + cpu = GET_LOGICAL_INDEX(err->mpidr); + if (cpu < 0) + cpu = -1; + + trace_arm_event(err, pei_err, pei_len, ctx_err, ctx_len, + ven_err_data, (u32)vsei_len, sev, cpu); } static int __init ras_init(void) diff --git a/include/linux/ras.h b/include/linux/ras.h index a64182bc72ad3f..468941bfe855f6 100644 --- a/include/linux/ras.h +++ b/include/linux/ras.h @@ -24,8 +24,7 @@ int __init parse_cec_param(char *str); void log_non_standard_event(const guid_t *sec_type, const guid_t *fru_id, const char *fru_text, const u8 sev, const u8 *err, const u32 len); -void log_arm_hw_error(struct cper_sec_proc_arm *err); - +void log_arm_hw_error(struct cper_sec_proc_arm *err, const u8 sev); #else static inline void log_non_standard_event(const guid_t *sec_type, @@ -33,7 +32,7 @@ log_non_standard_event(const guid_t *sec_type, const u8 sev, const u8 *err, const u32 len) { return; } static inline void -log_arm_hw_error(struct cper_sec_proc_arm *err) { return; } +log_arm_hw_error(struct cper_sec_proc_arm *err, const u8 sev) { return; } #endif struct atl_err { @@ -53,4 +52,15 @@ static inline unsigned long amd_convert_umc_mca_addr_to_sys_addr(struct atl_err *err) { return -EINVAL; } #endif /* CONFIG_AMD_ATL */ +#if defined(CONFIG_ARM) || defined(CONFIG_ARM64) +#include +/* + * Include ARM-specific SMP header which provides a function mapping mpidr to + * CPU logical index. + */ +#define GET_LOGICAL_INDEX(mpidr) get_logical_index(mpidr & MPIDR_HWID_BITMASK) +#else +#define GET_LOGICAL_INDEX(mpidr) -EINVAL +#endif /* CONFIG_ARM || CONFIG_ARM64 */ + #endif /* __RAS_H__ */ diff --git a/include/ras/ras_event.h b/include/ras/ras_event.h index c8cd0f00c8454a..c9f0b1018bcce0 100644 --- a/include/ras/ras_event.h +++ b/include/ras/ras_event.h @@ -168,11 +168,25 @@ TRACE_EVENT(mc_event, * This event is generated when hardware detects an ARM processor error * has occurred. UEFI 2.6 spec section N.2.4.4. */ +#define APEIL "ARM Processor Err Info data len" +#define APEID "ARM Processor Err Info raw data" +#define APECIL "ARM Processor Err Context Info data len" +#define APECID "ARM Processor Err Context Info raw data" +#define VSEIL "Vendor Specific Err Info data len" +#define VSEID "Vendor Specific Err Info raw data" TRACE_EVENT(arm_event, - TP_PROTO(const struct cper_sec_proc_arm *proc), + TP_PROTO(const struct cper_sec_proc_arm *proc, + const u8 *pei_err, + const u32 pei_len, + const u8 *ctx_err, + const u32 ctx_len, + const u8 *oem, + const u32 oem_len, + u8 sev, + int cpu), - TP_ARGS(proc), + TP_ARGS(proc, pei_err, pei_len, ctx_err, ctx_len, oem, oem_len, sev, cpu), TP_STRUCT__entry( __field(u64, mpidr) @@ -180,6 +194,14 @@ TRACE_EVENT(arm_event, __field(u32, running_state) __field(u32, psci_state) __field(u8, affinity) + __field(u32, pei_len) + __dynamic_array(u8, pei_buf, pei_len) + __field(u32, ctx_len) + __dynamic_array(u8, ctx_buf, ctx_len) + __field(u32, oem_len) + __dynamic_array(u8, oem_buf, oem_len) + __field(u8, sev) + __field(int, cpu) ), TP_fast_assign( @@ -199,12 +221,29 @@ TRACE_EVENT(arm_event, __entry->running_state = ~0; __entry->psci_state = ~0; } + __entry->pei_len = pei_len; + memcpy(__get_dynamic_array(pei_buf), pei_err, pei_len); + __entry->ctx_len = ctx_len; + memcpy(__get_dynamic_array(ctx_buf), ctx_err, ctx_len); + __entry->oem_len = oem_len; + memcpy(__get_dynamic_array(oem_buf), oem, oem_len); + __entry->sev = sev; + __entry->cpu = cpu; ), - TP_printk("affinity level: %d; MPIDR: %016llx; MIDR: %016llx; " - "running state: %d; PSCI state: %d", + TP_printk("cpu: %d; error: %d; affinity level: %d; MPIDR: %016llx; MIDR: %016llx; " + "running state: %d; PSCI state: %d; " + "%s: %d; %s: %s; %s: %d; %s: %s; %s: %d; %s: %s", + __entry->cpu, + __entry->sev, __entry->affinity, __entry->mpidr, __entry->midr, - __entry->running_state, __entry->psci_state) + __entry->running_state, __entry->psci_state, + APEIL, __entry->pei_len, APEID, + __print_hex(__get_dynamic_array(pei_buf), __entry->pei_len), + APECIL, __entry->ctx_len, APECID, + __print_hex(__get_dynamic_array(ctx_buf), __entry->ctx_len), + VSEIL, __entry->oem_len, VSEID, + __print_hex(__get_dynamic_array(oem_buf), __entry->oem_len)) ); /* From 89f6fb920e2cf5f4632263c684d4f8c1199c5b9e Mon Sep 17 00:00:00 2001 From: Tomas Glozar Date: Tue, 7 Oct 2025 11:53:40 +0200 Subject: [PATCH 0722/3902] rtla/tests: Extend action tests to 5s [ Upstream commit d649e9f04cb0224817dac8190461ef1674e32b37 ] In non-BPF mode, it takes up to 1 second for RTLA to notice that tracing has been stopped. That means that action tests cannot have a 1 second duration, as the SIGALRM will be racing with the threshold overflow. Previously, non-BPF mode actions were buggy and always executed the action, even when stopping on duration or SIGINT, preventing this issue from manifesting. Now that this has been fixed, the tests have become flaky, and this has to be adjusted. Fixes: 4e26f84abfbb ("rtla/tests: Add tests for actions") Fixes: 05b7e10687c6 ("tools/rtla: Add remaining support for osnoise actions") Reviewed-by: Wander Lairson Costa Link: https://lore.kernel.org/r/20251007095341.186923-2-tglozar@redhat.com Signed-off-by: Tomas Glozar Signed-off-by: Sasha Levin --- tools/tracing/rtla/tests/osnoise.t | 4 ++-- tools/tracing/rtla/tests/timerlat.t | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/tools/tracing/rtla/tests/osnoise.t b/tools/tracing/rtla/tests/osnoise.t index e3c89d45a6bb04..08196443fef161 100644 --- a/tools/tracing/rtla/tests/osnoise.t +++ b/tools/tracing/rtla/tests/osnoise.t @@ -39,9 +39,9 @@ check "hist stop at failed action" \ check "top stop at failed action" \ "timerlat top -T 2 --on-threshold shell,command='echo -n abc; false' --on-threshold shell,command='echo -n defgh'" 2 "^abc" "defgh" check "hist with continue" \ - "osnoise hist -S 2 -d 1s --on-threshold shell,command='echo TestOutput' --on-threshold continue" 0 "^TestOutput$" + "osnoise hist -S 2 -d 5s --on-threshold shell,command='echo TestOutput' --on-threshold continue" 0 "^TestOutput$" check "top with continue" \ - "osnoise top -q -S 2 -d 1s --on-threshold shell,command='echo TestOutput' --on-threshold continue" 0 "^TestOutput$" + "osnoise top -q -S 2 -d 5s --on-threshold shell,command='echo TestOutput' --on-threshold continue" 0 "^TestOutput$" check "hist with trace output at end" \ "osnoise hist -d 1s --on-end trace" 0 "^ Saving trace to osnoise_trace.txt$" check "top with trace output at end" \ diff --git a/tools/tracing/rtla/tests/timerlat.t b/tools/tracing/rtla/tests/timerlat.t index b5d1e7260a9bed..b550a6ae24456f 100644 --- a/tools/tracing/rtla/tests/timerlat.t +++ b/tools/tracing/rtla/tests/timerlat.t @@ -60,9 +60,9 @@ check "hist stop at failed action" \ check "top stop at failed action" \ "timerlat top -T 2 --on-threshold shell,command='echo -n 1; false' --on-threshold shell,command='echo -n 2'" 2 "^1ALL" check "hist with continue" \ - "timerlat hist -T 2 -d 1s --on-threshold shell,command='echo TestOutput' --on-threshold continue" 0 "^TestOutput$" + "timerlat hist -T 2 -d 5s --on-threshold shell,command='echo TestOutput' --on-threshold continue" 0 "^TestOutput$" check "top with continue" \ - "timerlat top -q -T 2 -d 1s --on-threshold shell,command='echo TestOutput' --on-threshold continue" 0 "^TestOutput$" + "timerlat top -q -T 2 -d 5s --on-threshold shell,command='echo TestOutput' --on-threshold continue" 0 "^TestOutput$" check "hist with trace output at end" \ "timerlat hist -d 1s --on-end trace" 0 "^ Saving trace to timerlat_trace.txt$" check "top with trace output at end" \ From 93409bddc50bde57848854986c4f14e60f363df8 Mon Sep 17 00:00:00 2001 From: Tomas Glozar Date: Tue, 7 Oct 2025 11:53:41 +0200 Subject: [PATCH 0723/3902] rtla/tests: Fix osnoise test calling timerlat [ Upstream commit 34c170ae5c3036ef879567a37409a2859e327342 ] osnoise test "top stop at failed action" is calling timerlat instead of osnoise by mistake. Fix it so that it calls the correct RTLA subcommand. Fixes: 05b7e10687c6 ("tools/rtla: Add remaining support for osnoise actions") Reviewed-by: Wander Lairson Costa Link: https://lore.kernel.org/r/20251007095341.186923-3-tglozar@redhat.com Signed-off-by: Tomas Glozar Signed-off-by: Sasha Levin --- tools/tracing/rtla/tests/osnoise.t | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/tracing/rtla/tests/osnoise.t b/tools/tracing/rtla/tests/osnoise.t index 08196443fef161..39633460892074 100644 --- a/tools/tracing/rtla/tests/osnoise.t +++ b/tools/tracing/rtla/tests/osnoise.t @@ -37,7 +37,7 @@ check "multiple actions" \ check "hist stop at failed action" \ "osnoise hist -S 2 --on-threshold shell,command='echo -n 1; false' --on-threshold shell,command='echo -n 2'" 2 "^1# RTLA osnoise histogram$" check "top stop at failed action" \ - "timerlat top -T 2 --on-threshold shell,command='echo -n abc; false' --on-threshold shell,command='echo -n defgh'" 2 "^abc" "defgh" + "osnoise top -S 2 --on-threshold shell,command='echo -n abc; false' --on-threshold shell,command='echo -n defgh'" 2 "^abc" "defgh" check "hist with continue" \ "osnoise hist -S 2 -d 5s --on-threshold shell,command='echo TestOutput' --on-threshold continue" 0 "^TestOutput$" check "top with continue" \ From 97ee9b4f32c8a594f3d26a53baf03c3de9580361 Mon Sep 17 00:00:00 2001 From: Ivan Pravdin Date: Mon, 3 Nov 2025 11:19:08 -0500 Subject: [PATCH 0724/3902] rtla: Fix -a overriding -t argument [ Upstream commit ddb6e42494e5c48c17e64f29b7674b9add486a19 ] When running rtla as `rtla -t custom_file.txt -a 100` -a options override trace output filename specified by -t option. Running the command above will create _trace.txt file instead of custom_file.txt. Fix this by making sure that -a option does not override trace output filename even if it's passed after trace output filename is specified. Fixes: 173a3b014827 ("rtla/timerlat: Add the automatic trace option") Signed-off-by: Ivan Pravdin Reviewed-by: Tomas Glozar Link: https://lore.kernel.org/r/b6ae60424050b2c1c8709e18759adead6012b971.1762186418.git.ipravdin.official@gmail.com [ use capital letter in subject, as required by tracing subsystem ] Signed-off-by: Tomas Glozar Signed-off-by: Sasha Levin --- tools/tracing/rtla/src/osnoise_hist.c | 3 ++- tools/tracing/rtla/src/osnoise_top.c | 3 ++- tools/tracing/rtla/src/timerlat_hist.c | 3 ++- tools/tracing/rtla/src/timerlat_top.c | 3 ++- 4 files changed, 8 insertions(+), 4 deletions(-) diff --git a/tools/tracing/rtla/src/osnoise_hist.c b/tools/tracing/rtla/src/osnoise_hist.c index dffb6d0a98d7d9..d22feb4d6cc9dc 100644 --- a/tools/tracing/rtla/src/osnoise_hist.c +++ b/tools/tracing/rtla/src/osnoise_hist.c @@ -557,7 +557,8 @@ static struct common_params params->threshold = 1; /* set trace */ - trace_output = "osnoise_trace.txt"; + if (!trace_output) + trace_output = "osnoise_trace.txt"; break; case 'b': diff --git a/tools/tracing/rtla/src/osnoise_top.c b/tools/tracing/rtla/src/osnoise_top.c index 95418f7ecc9613..a8d31030c41229 100644 --- a/tools/tracing/rtla/src/osnoise_top.c +++ b/tools/tracing/rtla/src/osnoise_top.c @@ -397,7 +397,8 @@ struct common_params *osnoise_top_parse_args(int argc, char **argv) params->threshold = 1; /* set trace */ - trace_output = "osnoise_trace.txt"; + if (!trace_output) + trace_output = "osnoise_trace.txt"; break; case 'c': diff --git a/tools/tracing/rtla/src/timerlat_hist.c b/tools/tracing/rtla/src/timerlat_hist.c index 606c1688057b27..3d56df3d5fa0d4 100644 --- a/tools/tracing/rtla/src/timerlat_hist.c +++ b/tools/tracing/rtla/src/timerlat_hist.c @@ -878,7 +878,8 @@ static struct common_params params->print_stack = auto_thresh; /* set trace */ - trace_output = "timerlat_trace.txt"; + if (!trace_output) + trace_output = "timerlat_trace.txt"; break; case 'c': diff --git a/tools/tracing/rtla/src/timerlat_top.c b/tools/tracing/rtla/src/timerlat_top.c index fc479a0dcb597a..6cc9a3607c6653 100644 --- a/tools/tracing/rtla/src/timerlat_top.c +++ b/tools/tracing/rtla/src/timerlat_top.c @@ -628,7 +628,8 @@ static struct common_params params->print_stack = auto_thresh; /* set trace */ - trace_output = "timerlat_trace.txt"; + if (!trace_output) + trace_output = "timerlat_trace.txt"; break; case '5': From 32952c4f4d1b2deb30dce72ba109da808a9018e1 Mon Sep 17 00:00:00 2001 From: Zhao Yipeng Date: Thu, 20 Nov 2025 15:18:05 +0800 Subject: [PATCH 0725/3902] ima: Handle error code returned by ima_filter_rule_match() [ Upstream commit 738c9738e690f5cea24a3ad6fd2d9a323cf614f6 ] In ima_match_rules(), if ima_filter_rule_match() returns -ENOENT due to the rule being NULL, the function incorrectly skips the 'if (!rc)' check and sets 'result = true'. The LSM rule is considered a match, causing extra files to be measured by IMA. This issue can be reproduced in the following scenario: After unloading the SELinux policy module via 'semodule -d', if an IMA measurement is triggered before ima_lsm_rules is updated, in ima_match_rules(), the first call to ima_filter_rule_match() returns -ESTALE. This causes the code to enter the 'if (rc == -ESTALE && !rule_reinitialized)' block, perform ima_lsm_copy_rule() and retry. In ima_lsm_copy_rule(), since the SELinux module has been removed, the rule becomes NULL, and the second call to ima_filter_rule_match() returns -ENOENT. This bypasses the 'if (!rc)' check and results in a false match. Call trace: selinux_audit_rule_match+0x310/0x3b8 security_audit_rule_match+0x60/0xa0 ima_match_rules+0x2e4/0x4a0 ima_match_policy+0x9c/0x1e8 ima_get_action+0x48/0x60 process_measurement+0xf8/0xa98 ima_bprm_check+0x98/0xd8 security_bprm_check+0x5c/0x78 search_binary_handler+0x6c/0x318 exec_binprm+0x58/0x1b8 bprm_execve+0xb8/0x130 do_execveat_common.isra.0+0x1a8/0x258 __arm64_sys_execve+0x48/0x68 invoke_syscall+0x50/0x128 el0_svc_common.constprop.0+0xc8/0xf0 do_el0_svc+0x24/0x38 el0_svc+0x44/0x200 el0t_64_sync_handler+0x100/0x130 el0t_64_sync+0x3c8/0x3d0 Fix this by changing 'if (!rc)' to 'if (rc <= 0)' to ensure that error codes like -ENOENT do not bypass the check and accidentally result in a successful match. Fixes: 4af4662fa4a9d ("integrity: IMA policy") Signed-off-by: Zhao Yipeng Reviewed-by: Roberto Sassu Signed-off-by: Mimi Zohar Signed-off-by: Sasha Levin --- security/integrity/ima/ima_policy.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/security/integrity/ima/ima_policy.c b/security/integrity/ima/ima_policy.c index 128fab8979308a..db6d55af5a80b2 100644 --- a/security/integrity/ima/ima_policy.c +++ b/security/integrity/ima/ima_policy.c @@ -674,7 +674,7 @@ static bool ima_match_rules(struct ima_rule_entry *rule, goto retry; } } - if (!rc) { + if (rc <= 0) { result = false; goto out; } From 3681b60224e895c2c23da32506f365ef80301f78 Mon Sep 17 00:00:00 2001 From: Oliver Neukum Date: Thu, 30 Oct 2025 10:39:06 +0100 Subject: [PATCH 0726/3902] usb: chaoskey: fix locking for O_NONBLOCK [ Upstream commit a2fa8a12e6bc9d89c0505b8dd7ae38ec173d25de ] A failure to take a lock with O_NONBLOCK needs to result in -EAGAIN. Change it. Fixes: 66e3e591891da ("usb: Add driver for Altus Metrum ChaosKey device (v2)") Signed-off-by: Oliver Neukum Link: https://patch.msgid.link/20251030093918.2248104-1-oneukum@suse.com Signed-off-by: Greg Kroah-Hartman Signed-off-by: Sasha Levin --- drivers/usb/misc/chaoskey.c | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/drivers/usb/misc/chaoskey.c b/drivers/usb/misc/chaoskey.c index 225863321dc479..45cff32656c6e3 100644 --- a/drivers/usb/misc/chaoskey.c +++ b/drivers/usb/misc/chaoskey.c @@ -444,9 +444,19 @@ static ssize_t chaoskey_read(struct file *file, goto bail; mutex_unlock(&dev->rng_lock); - result = mutex_lock_interruptible(&dev->lock); - if (result) - goto bail; + if (file->f_flags & O_NONBLOCK) { + result = mutex_trylock(&dev->lock); + if (result == 0) { + result = -EAGAIN; + goto bail; + } else { + result = 0; + } + } else { + result = mutex_lock_interruptible(&dev->lock); + if (result) + goto bail; + } if (dev->valid == dev->used) { result = _chaoskey_fill(dev); if (result < 0) { From cc0d2b85447cfe3c520d5cfe02d62d5e43c46b37 Mon Sep 17 00:00:00 2001 From: Jisheng Zhang Date: Tue, 4 Nov 2025 08:25:02 +0800 Subject: [PATCH 0727/3902] usb: dwc2: fix hang during shutdown if set as peripheral [ Upstream commit b6ebcfdcac40a27953f052e4269ce75a18825ffc ] dwc2 on most platforms needs phy controller, clock and power supply. All of them must be enabled/activated to properly operate. If dwc2 is configured as peripheral mode, then all the above three hardware resources are disabled at the end of the probe: /* Gadget code manages lowlevel hw on its own */ if (hsotg->dr_mode == USB_DR_MODE_PERIPHERAL) dwc2_lowlevel_hw_disable(hsotg); But dwc2_driver_shutdown() tries to disable the interrupts on HW IP level. This would result in hang during shutdown if dwc2 is configured as peripheral mode. Fix this hang by only disable and sync irq when lowlevel hw is enabled. Fixes: 4fdf228cdf69 ("usb: dwc2: Fix shutdown callback in platform") Signed-off-by: Jisheng Zhang Link: https://patch.msgid.link/20251104002503.17158-2-jszhang@kernel.org Signed-off-by: Greg Kroah-Hartman Signed-off-by: Sasha Levin --- drivers/usb/dwc2/platform.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/drivers/usb/dwc2/platform.c b/drivers/usb/dwc2/platform.c index 3f83ecc9fc236e..b07bdf16326a40 100644 --- a/drivers/usb/dwc2/platform.c +++ b/drivers/usb/dwc2/platform.c @@ -369,11 +369,11 @@ static void dwc2_driver_shutdown(struct platform_device *dev) { struct dwc2_hsotg *hsotg = platform_get_drvdata(dev); - dwc2_disable_global_interrupts(hsotg); - synchronize_irq(hsotg->irq); - - if (hsotg->ll_hw_enabled) + if (hsotg->ll_hw_enabled) { + dwc2_disable_global_interrupts(hsotg); + synchronize_irq(hsotg->irq); dwc2_lowlevel_hw_disable(hsotg); + } } /** From dda165e3be9d5a65bb8a75f34abe02d1c37dbeec Mon Sep 17 00:00:00 2001 From: Jisheng Zhang Date: Tue, 4 Nov 2025 08:25:03 +0800 Subject: [PATCH 0728/3902] usb: dwc2: fix hang during suspend if set as peripheral [ Upstream commit 2b94b054ac4974ad2f89f7f7461840c851933adb ] dwc2 on most platforms needs phy controller, clock and power supply. All of them must be enabled/activated to properly operate. If dwc2 is configured as peripheral mode, then all the above three hardware resources are disabled at the end of the probe: /* Gadget code manages lowlevel hw on its own */ if (hsotg->dr_mode == USB_DR_MODE_PERIPHERAL) dwc2_lowlevel_hw_disable(hsotg); But the dwc2_suspend() tries to read the dwc2's reg to check whether is_device_mode or not, this would result in hang during suspend if dwc2 is configured as peripheral mode. Fix this hang by bypassing suspend/resume if lowlevel hw isn't enabled. Fixes: 09a75e857790 ("usb: dwc2: refactor common low-level hw code to platform.c") Signed-off-by: Jisheng Zhang Link: https://patch.msgid.link/20251104002503.17158-3-jszhang@kernel.org Signed-off-by: Greg Kroah-Hartman Signed-off-by: Sasha Levin --- drivers/usb/dwc2/platform.c | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/drivers/usb/dwc2/platform.c b/drivers/usb/dwc2/platform.c index b07bdf16326a40..ef0d7307703473 100644 --- a/drivers/usb/dwc2/platform.c +++ b/drivers/usb/dwc2/platform.c @@ -649,9 +649,13 @@ static int dwc2_driver_probe(struct platform_device *dev) static int __maybe_unused dwc2_suspend(struct device *dev) { struct dwc2_hsotg *dwc2 = dev_get_drvdata(dev); - bool is_device_mode = dwc2_is_device_mode(dwc2); + bool is_device_mode; int ret = 0; + if (!dwc2->ll_hw_enabled) + return 0; + + is_device_mode = dwc2_is_device_mode(dwc2); if (is_device_mode) dwc2_hsotg_suspend(dwc2); @@ -728,6 +732,9 @@ static int __maybe_unused dwc2_resume(struct device *dev) struct dwc2_hsotg *dwc2 = dev_get_drvdata(dev); int ret = 0; + if (!dwc2->ll_hw_enabled) + return 0; + if (dwc2->phy_off_for_suspend && dwc2->ll_hw_enabled) { ret = __dwc2_lowlevel_hw_enable(dwc2); if (ret) From 442f55d47d584e89f7c94223dfa8ec3dcfa017f9 Mon Sep 17 00:00:00 2001 From: Gopi Krishna Menon Date: Tue, 28 Oct 2025 22:26:57 +0530 Subject: [PATCH 0729/3902] usb: raw-gadget: cap raw_io transfer length to KMALLOC_MAX_SIZE [ Upstream commit a5160af78be7fcf3ade6caab0a14e349560c96d7 ] The previous commit removed the PAGE_SIZE limit on transfer length of raw_io buffer in order to avoid any problems with emulating USB devices whose full configuration descriptor exceeds PAGE_SIZE in length. However this also removes the upperbound on user supplied length, allowing very large values to be passed to the allocator. syzbot on fuzzing the transfer length with very large value (1.81GB) results in kmalloc() to fall back to the page allocator, which triggers a kernel warning as the page allocator cannot handle allocations more than MAX_PAGE_ORDER/KMALLOC_MAX_SIZE. Since there is no limit imposed on the size of buffer for both control and non control transfers, cap the raw_io transfer length to KMALLOC_MAX_SIZE and return -EINVAL for larger transfer length to prevent any warnings from the page allocator. Fixes: 37b9dd0d114a ("usb: raw-gadget: do not limit transfer length") Tested-by: syzbot+d8fd35fa6177afa8c92b@syzkaller.appspotmail.com Reported-by: syzbot+d8fd35fa6177afa8c92b@syzkaller.appspotmail.com Closes: https://lore.kernel.org/all/68fc07a0.a70a0220.3bf6c6.01ab.GAE@google.com/ Signed-off-by: Gopi Krishna Menon Reviewed-by: Andrey Konovalov Link: https://patch.msgid.link/20251028165659.50962-1-krishnagopi487@gmail.com Signed-off-by: Greg Kroah-Hartman Signed-off-by: Sasha Levin --- drivers/usb/gadget/legacy/raw_gadget.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/drivers/usb/gadget/legacy/raw_gadget.c b/drivers/usb/gadget/legacy/raw_gadget.c index b71680c58de6c7..46f343ba48b3da 100644 --- a/drivers/usb/gadget/legacy/raw_gadget.c +++ b/drivers/usb/gadget/legacy/raw_gadget.c @@ -40,6 +40,7 @@ MODULE_LICENSE("GPL"); static DEFINE_IDA(driver_id_numbers); #define DRIVER_DRIVER_NAME_LENGTH_MAX 32 +#define USB_RAW_IO_LENGTH_MAX KMALLOC_MAX_SIZE #define RAW_EVENT_QUEUE_SIZE 16 @@ -667,6 +668,8 @@ static void *raw_alloc_io_data(struct usb_raw_ep_io *io, void __user *ptr, return ERR_PTR(-EINVAL); if (!usb_raw_io_flags_valid(io->flags)) return ERR_PTR(-EINVAL); + if (io->length > USB_RAW_IO_LENGTH_MAX) + return ERR_PTR(-EINVAL); if (get_from_user) data = memdup_user(ptr + sizeof(*io), io->length); else { From 93787117dcf19e6b9febbb389062d1fcf8c90cdd Mon Sep 17 00:00:00 2001 From: Dan Carpenter Date: Fri, 21 Nov 2025 16:35:24 +0300 Subject: [PATCH 0730/3902] regulator: pca9450: Fix error code in probe() [ Upstream commit 670500b41e543c5cb09eb9f7f0e4e26c5b5fdf7e ] Return "PTR_ERR(pca9450->sd_vsel_gpio)" instead of "ret". The "ret" variable is success at this point. Fixes: 3ce6f4f943dd ("regulator: pca9450: Fix control register for LDO5") Signed-off-by: Dan Carpenter Link: https://patch.msgid.link/aSBqnPoBrsNB1Ale@stanley.mountain Signed-off-by: Mark Brown Signed-off-by: Sasha Levin --- drivers/regulator/pca9450-regulator.c | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/drivers/regulator/pca9450-regulator.c b/drivers/regulator/pca9450-regulator.c index 4be270f4d6c35a..91b96dbab328bd 100644 --- a/drivers/regulator/pca9450-regulator.c +++ b/drivers/regulator/pca9450-regulator.c @@ -1251,10 +1251,9 @@ static int pca9450_i2c_probe(struct i2c_client *i2c) * to this signal (if SION bit is set in IOMUX). */ pca9450->sd_vsel_gpio = gpiod_get_optional(&ldo5->dev, "sd-vsel", GPIOD_IN); - if (IS_ERR(pca9450->sd_vsel_gpio)) { - dev_err(&i2c->dev, "Failed to get SD_VSEL GPIO\n"); - return ret; - } + if (IS_ERR(pca9450->sd_vsel_gpio)) + return dev_err_probe(&i2c->dev, PTR_ERR(pca9450->sd_vsel_gpio), + "Failed to get SD_VSEL GPIO\n"); pca9450->sd_vsel_fixed_low = of_property_read_bool(ldo5->dev.of_node, "nxp,sd-vsel-fixed-low"); From 833e49fa2dd4c58333862060f08e7947ab6a19e6 Mon Sep 17 00:00:00 2001 From: Matt Bobrowski Date: Thu, 20 Nov 2025 14:20:59 +0000 Subject: [PATCH 0731/3902] selftests/bpf: skip test_perf_branches_hw() on unsupported platforms [ Upstream commit 27746aaf1b20172f0859546c4a3e82eca459f680 ] Gracefully skip the test_perf_branches_hw subtest on platforms that do not support LBR or require specialized perf event attributes to enable branch sampling. For example, AMD's Milan (Zen 3) supports BRS rather than traditional LBR. This requires specific configurations (attr.type = PERF_TYPE_RAW, attr.config = RETIRED_TAKEN_BRANCH_INSTRUCTIONS) that differ from the generic setup used within this test. Notably, it also probably doesn't hold much value to special case perf event configurations for selected micro architectures. Fixes: 67306f84ca78c ("selftests/bpf: Add bpf_read_branch_records() selftest") Signed-off-by: Matt Bobrowski Acked-by: Song Liu Link: https://lore.kernel.org/r/20251120142059.2836181-1-mattbobrowski@google.com Signed-off-by: Alexei Starovoitov Signed-off-by: Sasha Levin --- tools/testing/selftests/bpf/prog_tests/perf_branches.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tools/testing/selftests/bpf/prog_tests/perf_branches.c b/tools/testing/selftests/bpf/prog_tests/perf_branches.c index bc24f83339d642..06c7986131d96f 100644 --- a/tools/testing/selftests/bpf/prog_tests/perf_branches.c +++ b/tools/testing/selftests/bpf/prog_tests/perf_branches.c @@ -116,11 +116,11 @@ static void test_perf_branches_hw(void) pfd = syscall(__NR_perf_event_open, &attr, -1, 0, -1, PERF_FLAG_FD_CLOEXEC); /* - * Some setups don't support branch records (virtual machines, !x86), - * so skip test in this case. + * Some setups don't support LBR (virtual machines, !x86, AMD Milan Zen + * 3 which only supports BRS), so skip test in this case. */ if (pfd < 0) { - if (errno == ENOENT || errno == EOPNOTSUPP) { + if (errno == ENOENT || errno == EOPNOTSUPP || errno == EINVAL) { printf("%s:SKIP:no PERF_SAMPLE_BRANCH_STACK\n", __func__); test__skip(); From a8cf555fd77383574c4dcff7a2625fbcbd149362 Mon Sep 17 00:00:00 2001 From: Matt Bobrowski Date: Wed, 19 Nov 2025 14:35:40 +0000 Subject: [PATCH 0732/3902] selftests/bpf: Improve reliability of test_perf_branches_no_hw() [ Upstream commit ae24fc8a16b0481ea8c5acbc66453c49ec0431c4 ] Currently, test_perf_branches_no_hw() relies on the busy loop within test_perf_branches_common() being slow enough to allow at least one perf event sample tick to occur before starting to tear down the backing perf event BPF program. With a relatively small fixed iteration count of 1,000,000, this is not guaranteed on modern fast CPUs, resulting in the test run to subsequently fail with the following: bpf_testmod.ko is already unloaded. Loading bpf_testmod.ko... Successfully loaded bpf_testmod.ko. test_perf_branches_common:PASS:test_perf_branches_load 0 nsec test_perf_branches_common:PASS:attach_perf_event 0 nsec test_perf_branches_common:PASS:set_affinity 0 nsec check_good_sample:PASS:output not valid 0 nsec check_good_sample:PASS:read_branches_size 0 nsec check_good_sample:PASS:read_branches_stack 0 nsec check_good_sample:PASS:read_branches_stack 0 nsec check_good_sample:PASS:read_branches_global 0 nsec check_good_sample:PASS:read_branches_global 0 nsec check_good_sample:PASS:read_branches_size 0 nsec test_perf_branches_no_hw:PASS:perf_event_open 0 nsec test_perf_branches_common:PASS:test_perf_branches_load 0 nsec test_perf_branches_common:PASS:attach_perf_event 0 nsec test_perf_branches_common:PASS:set_affinity 0 nsec check_bad_sample:FAIL:output not valid no valid sample from prog Summary: 0/1 PASSED, 0 SKIPPED, 1 FAILED Successfully unloaded bpf_testmod.ko. On a modern CPU (i.e. one with a 3.5 GHz clock rate), executing 1 million increments of a volatile integer can take significantly less than 1 millisecond. If the spin loop and detachment of the perf event BPF program elapses before the first 1 ms sampling interval elapses, the perf event will never end up firing. Fix this by bumping the loop iteration counter a little within test_perf_branches_common(), along with ensuring adding another loop termination condition which is directly influenced by the backing perf event BPF program executing. Notably, a concious decision was made to not adjust the sample_freq value as that is just not a reliable way to go about fixing the problem. It effectively still leaves the race window open. Fixes: 67306f84ca78c ("selftests/bpf: Add bpf_read_branch_records() selftest") Signed-off-by: Matt Bobrowski Reviewed-by: Jiri Olsa Link: https://lore.kernel.org/r/20251119143540.2911424-1-mattbobrowski@google.com Signed-off-by: Alexei Starovoitov Signed-off-by: Sasha Levin --- .../selftests/bpf/prog_tests/perf_branches.c | 16 ++++++++++++++-- .../selftests/bpf/progs/test_perf_branches.c | 3 +++ 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/tools/testing/selftests/bpf/prog_tests/perf_branches.c b/tools/testing/selftests/bpf/prog_tests/perf_branches.c index 06c7986131d96f..0a7ef770c487c8 100644 --- a/tools/testing/selftests/bpf/prog_tests/perf_branches.c +++ b/tools/testing/selftests/bpf/prog_tests/perf_branches.c @@ -15,6 +15,10 @@ static void check_good_sample(struct test_perf_branches *skel) int pbe_size = sizeof(struct perf_branch_entry); int duration = 0; + if (CHECK(!skel->bss->run_cnt, "invalid run_cnt", + "checked sample validity before prog run")) + return; + if (CHECK(!skel->bss->valid, "output not valid", "no valid sample from prog")) return; @@ -45,6 +49,10 @@ static void check_bad_sample(struct test_perf_branches *skel) int written_stack = skel->bss->written_stack_out; int duration = 0; + if (CHECK(!skel->bss->run_cnt, "invalid run_cnt", + "checked sample validity before prog run")) + return; + if (CHECK(!skel->bss->valid, "output not valid", "no valid sample from prog")) return; @@ -83,8 +91,12 @@ static void test_perf_branches_common(int perf_fd, err = pthread_setaffinity_np(pthread_self(), sizeof(cpu_set), &cpu_set); if (CHECK(err, "set_affinity", "cpu #0, err %d\n", err)) goto out_destroy; - /* spin the loop for a while (random high number) */ - for (i = 0; i < 1000000; ++i) + + /* Spin the loop for a while by using a high iteration count, and by + * checking whether the specific run count marker has been explicitly + * incremented at least once by the backing perf_event BPF program. + */ + for (i = 0; i < 100000000 && !*(volatile int *)&skel->bss->run_cnt; ++i) ++j; test_perf_branches__detach(skel); diff --git a/tools/testing/selftests/bpf/progs/test_perf_branches.c b/tools/testing/selftests/bpf/progs/test_perf_branches.c index a1ccc831c882f6..05ac9410cd68c9 100644 --- a/tools/testing/selftests/bpf/progs/test_perf_branches.c +++ b/tools/testing/selftests/bpf/progs/test_perf_branches.c @@ -8,6 +8,7 @@ #include int valid = 0; +int run_cnt = 0; int required_size_out = 0; int written_stack_out = 0; int written_global_out = 0; @@ -24,6 +25,8 @@ int perf_branches(void *ctx) __u64 entries[4 * 3] = {0}; int required_size, written_stack, written_global; + ++run_cnt; + /* write to stack */ written_stack = bpf_read_branch_records(ctx, entries, sizeof(entries), 0); /* ignore spurious events */ From 982ef82af9980eb9e058320b9f9ce673d941c612 Mon Sep 17 00:00:00 2001 From: Xing Guo Date: Fri, 21 Nov 2025 14:14:58 +0800 Subject: [PATCH 0733/3902] selftests/bpf: Update test_tag to use sha256 [ Upstream commit b7f7d76d6e354a5acc711da37cb2829ccf40558f ] commit 603b44162325 ("bpf: Update the bpf_prog_calc_tag to use SHA256") changed digest of prog_tag to SHA256 but forgot to update tests correspondingly. Fix it. Fixes: 603b44162325 ("bpf: Update the bpf_prog_calc_tag to use SHA256") Signed-off-by: Xing Guo Link: https://lore.kernel.org/r/20251121061458.3145167-1-higuoxing@gmail.com Signed-off-by: Alexei Starovoitov Signed-off-by: Sasha Levin --- tools/testing/selftests/bpf/test_tag.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/testing/selftests/bpf/test_tag.c b/tools/testing/selftests/bpf/test_tag.c index 5546b05a048661..f1300047c1e0a9 100644 --- a/tools/testing/selftests/bpf/test_tag.c +++ b/tools/testing/selftests/bpf/test_tag.c @@ -116,7 +116,7 @@ static void tag_from_alg(int insns, uint8_t *tag, uint32_t len) static const struct sockaddr_alg alg = { .salg_family = AF_ALG, .salg_type = "hash", - .salg_name = "sha1", + .salg_name = "sha256", }; int fd_base, fd_alg, ret; ssize_t size; From a6c22c91b6284e4713c0cf5b20ab3c9c71b77ae8 Mon Sep 17 00:00:00 2001 From: Martin Teichmann Date: Wed, 19 Nov 2025 17:03:52 +0100 Subject: [PATCH 0734/3902] bpf: properly verify tail call behavior [ Upstream commit e3245f8990431950d20631c72236d4e8cb2dcde8 ] A successful ebpf tail call does not return to the caller, but to the caller-of-the-caller, often just finishing the ebpf program altogether. Any restrictions that the verifier needs to take into account - notably the fact that the tail call might have modified packet pointers - are to be checked on the caller-of-the-caller. Checking it on the caller made the verifier refuse perfectly fine programs that would use the packet pointers after a tail call, which is no problem as this code is only executed if the tail call was unsuccessful, i.e. nothing happened. This patch simulates the behavior of a tail call in the verifier. A conditional jump to the code after the tail call is added for the case of an unsucessful tail call, and a return to the caller is simulated for a successful tail call. For the successful case we assume that the tail call returns an int, as tail calls are currently only allowed in functions that return and int. We always assume that the tail call modified the packet pointers, as we do not know what the tail call did. For the unsuccessful case we know nothing happened, so we do not need to add new constraints. This approach also allows to check other problems that may occur with tail calls, namely we are now able to check that precision is properly propagated into subprograms using tail calls, as well as checking the live slots in such a subprogram. Fixes: 1a4607ffba35 ("bpf: consider that tail calls invalidate packet pointers") Link: https://lore.kernel.org/bpf/20251029105828.1488347-1-martin.teichmann@xfel.eu/ Signed-off-by: Martin Teichmann Acked-by: Eduard Zingerman Link: https://lore.kernel.org/r/20251119160355.1160932-2-martin.teichmann@xfel.eu Signed-off-by: Alexei Starovoitov Signed-off-by: Sasha Levin --- kernel/bpf/verifier.c | 31 ++++++++++++++++++++++++++++--- 1 file changed, 28 insertions(+), 3 deletions(-) diff --git a/kernel/bpf/verifier.c b/kernel/bpf/verifier.c index 52c01c011c6fb7..89560e455ce7b2 100644 --- a/kernel/bpf/verifier.c +++ b/kernel/bpf/verifier.c @@ -4408,6 +4408,11 @@ static int backtrack_insn(struct bpf_verifier_env *env, int idx, int subseq_idx, bt_reg_mask(bt)); return -EFAULT; } + if (insn->src_reg == BPF_REG_0 && insn->imm == BPF_FUNC_tail_call + && subseq_idx - idx != 1) { + if (bt_subprog_enter(bt)) + return -EFAULT; + } } else if (opcode == BPF_EXIT) { bool r0_precise; @@ -10995,6 +11000,10 @@ static int prepare_func_exit(struct bpf_verifier_env *env, int *insn_idx) bool in_callback_fn; int err; + err = bpf_update_live_stack(env); + if (err) + return err; + callee = state->frame[state->curframe]; r0 = &callee->regs[BPF_REG_0]; if (r0->type == PTR_TO_STACK) { @@ -11912,6 +11921,25 @@ static int check_helper_call(struct bpf_verifier_env *env, struct bpf_insn *insn env->prog->call_get_func_ip = true; } + if (func_id == BPF_FUNC_tail_call) { + if (env->cur_state->curframe) { + struct bpf_verifier_state *branch; + + mark_reg_scratched(env, BPF_REG_0); + branch = push_stack(env, env->insn_idx + 1, env->insn_idx, false); + if (IS_ERR(branch)) + return PTR_ERR(branch); + clear_all_pkt_pointers(env); + mark_reg_unknown(env, regs, BPF_REG_0); + err = prepare_func_exit(env, &env->insn_idx); + if (err) + return err; + env->insn_idx--; + } else { + changes_data = false; + } + } + if (changes_data) clear_all_pkt_pointers(env); return 0; @@ -19810,9 +19838,6 @@ static int process_bpf_exit_full(struct bpf_verifier_env *env, return PROCESS_BPF_EXIT; if (env->cur_state->curframe) { - err = bpf_update_live_stack(env); - if (err) - return err; /* exit from nested function */ err = prepare_func_exit(env, &env->insn_idx); if (err) From 9b3f71cf02e04cfaa482155e3078707fe7f8aef4 Mon Sep 17 00:00:00 2001 From: Haotian Zhang Date: Mon, 10 Nov 2025 14:54:38 +0800 Subject: [PATCH 0735/3902] crypto: starfive - Correctly handle return of sg_nents_for_len [ Upstream commit e9eb52037a529fbb307c290e9951a62dd728b03d ] The return value of sg_nents_for_len was assigned to an unsigned long in starfive_hash_digest, causing negative error codes to be converted to large positive integers. Add error checking for sg_nents_for_len and return immediately on failure to prevent potential buffer overflows. Fixes: 7883d1b28a2b ("crypto: starfive - Add hash and HMAC support") Signed-off-by: Haotian Zhang Signed-off-by: Herbert Xu Signed-off-by: Sasha Levin --- drivers/crypto/starfive/jh7110-hash.c | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/drivers/crypto/starfive/jh7110-hash.c b/drivers/crypto/starfive/jh7110-hash.c index e6839c7bfb73ac..54b7af4a7aee82 100644 --- a/drivers/crypto/starfive/jh7110-hash.c +++ b/drivers/crypto/starfive/jh7110-hash.c @@ -325,6 +325,7 @@ static int starfive_hash_digest(struct ahash_request *req) struct starfive_cryp_ctx *ctx = crypto_ahash_ctx(tfm); struct starfive_cryp_request_ctx *rctx = ahash_request_ctx(req); struct starfive_cryp_dev *cryp = ctx->cryp; + int sg_len; memset(rctx, 0, sizeof(struct starfive_cryp_request_ctx)); @@ -333,7 +334,10 @@ static int starfive_hash_digest(struct ahash_request *req) rctx->in_sg = req->src; rctx->blksize = crypto_tfm_alg_blocksize(crypto_ahash_tfm(tfm)); rctx->digsize = crypto_ahash_digestsize(tfm); - rctx->in_sg_len = sg_nents_for_len(rctx->in_sg, rctx->total); + sg_len = sg_nents_for_len(rctx->in_sg, rctx->total); + if (sg_len < 0) + return sg_len; + rctx->in_sg_len = sg_len; ctx->rctx = rctx; return crypto_transfer_hash_request_to_engine(cryp->engine, req); From 2873d804b4f342c92bd0ff1a151d19e72650430b Mon Sep 17 00:00:00 2001 From: Haotian Zhang Date: Mon, 10 Nov 2025 15:20:41 +0800 Subject: [PATCH 0736/3902] crypto: ccree - Correctly handle return of sg_nents_for_len [ Upstream commit 8700ce07c5c6bf27afa7b59a8d9cf58d783a7d5c ] Fix error handling in cc_map_hash_request_update where sg_nents_for_len return value was assigned to u32, converting negative errors to large positive values before passing to sg_copy_to_buffer. Check sg_nents_for_len return value and propagate errors before assigning to areq_ctx->in_nents. Fixes: b7ec8530687a ("crypto: ccree - use std api when possible") Signed-off-by: Haotian Zhang Signed-off-by: Herbert Xu Signed-off-by: Sasha Levin --- drivers/crypto/ccree/cc_buffer_mgr.c | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/drivers/crypto/ccree/cc_buffer_mgr.c b/drivers/crypto/ccree/cc_buffer_mgr.c index 3963bb91321fc1..dc7e0cd51c2596 100644 --- a/drivers/crypto/ccree/cc_buffer_mgr.c +++ b/drivers/crypto/ccree/cc_buffer_mgr.c @@ -1235,6 +1235,7 @@ int cc_map_hash_request_update(struct cc_drvdata *drvdata, void *ctx, int rc = 0; u32 dummy = 0; u32 mapped_nents = 0; + int sg_nents; dev_dbg(dev, " update params : curr_buff=%p curr_buff_cnt=0x%X nbytes=0x%X src=%p curr_index=%u\n", curr_buff, *curr_buff_cnt, nbytes, src, areq_ctx->buff_index); @@ -1248,7 +1249,10 @@ int cc_map_hash_request_update(struct cc_drvdata *drvdata, void *ctx, if (total_in_len < block_size) { dev_dbg(dev, " less than one block: curr_buff=%p *curr_buff_cnt=0x%X copy_to=%p\n", curr_buff, *curr_buff_cnt, &curr_buff[*curr_buff_cnt]); - areq_ctx->in_nents = sg_nents_for_len(src, nbytes); + sg_nents = sg_nents_for_len(src, nbytes); + if (sg_nents < 0) + return sg_nents; + areq_ctx->in_nents = sg_nents; sg_copy_to_buffer(src, areq_ctx->in_nents, &curr_buff[*curr_buff_cnt], nbytes); *curr_buff_cnt += nbytes; From 469b0b8ce08818f3e4f01d2fa8d0dadeab501e1f Mon Sep 17 00:00:00 2001 From: Pengjie Zhang Date: Mon, 15 Sep 2025 14:21:35 +0800 Subject: [PATCH 0737/3902] PM / devfreq: hisi: Fix potential UAF in OPP handling [ Upstream commit 26dd44a40096468396b6438985d8e44e0743f64c ] Ensure all required data is acquired before calling dev_pm_opp_put(opp) to maintain correct resource acquisition and release order. Fixes: 7da2fdaaa1e6 ("PM / devfreq: Add HiSilicon uncore frequency scaling driver") Signed-off-by: Pengjie Zhang Reviewed-by: Jie Zhan Acked-by: Chanwoo Choi Signed-off-by: Chanwoo Choi Link: https://patchwork.kernel.org/project/linux-pm/patch/20250915062135.748653-1-zhangpengjie2@huawei.com/ Signed-off-by: Sasha Levin --- drivers/devfreq/hisi_uncore_freq.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/drivers/devfreq/hisi_uncore_freq.c b/drivers/devfreq/hisi_uncore_freq.c index 96d1815059e32c..c1ed70fa0a4004 100644 --- a/drivers/devfreq/hisi_uncore_freq.c +++ b/drivers/devfreq/hisi_uncore_freq.c @@ -265,10 +265,11 @@ static int hisi_uncore_target(struct device *dev, unsigned long *freq, dev_err(dev, "Failed to get opp for freq %lu hz\n", *freq); return PTR_ERR(opp); } - dev_pm_opp_put(opp); data = (u32)(dev_pm_opp_get_freq(opp) / HZ_PER_MHZ); + dev_pm_opp_put(opp); + return hisi_uncore_cmd_send(uncore, HUCF_PCC_CMD_SET_FREQ, &data); } From 1fa7e6e78bd6ce61c00d749532639b9b4e20795b Mon Sep 17 00:00:00 2001 From: Fangyu Yu Date: Fri, 21 Nov 2025 21:35:43 +0800 Subject: [PATCH 0738/3902] RISC-V: KVM: Fix guest page fault within HLV* instructions [ Upstream commit 974555d6e417974e63444266e495a06d06c23af5 ] When executing HLV* instructions at the HS mode, a guest page fault may occur when a g-stage page table migration between triggering the virtual instruction exception and executing the HLV* instruction. This may be a corner case, and one simpler way to handle this is to re-execute the instruction where the virtual instruction exception occurred, and the guest page fault will be automatically handled. Fixes: b91f0e4cb8a3 ("RISC-V: KVM: Factor-out instruction emulation into separate sources") Signed-off-by: Fangyu Yu Reviewed-by: Anup Patel Link: https://lore.kernel.org/r/20251121133543.46822-1-fangyu.yu@linux.alibaba.com Signed-off-by: Anup Patel Signed-off-by: Sasha Levin --- arch/riscv/kvm/vcpu_insn.c | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/arch/riscv/kvm/vcpu_insn.c b/arch/riscv/kvm/vcpu_insn.c index de1f96ea62251f..4d89b94128aea8 100644 --- a/arch/riscv/kvm/vcpu_insn.c +++ b/arch/riscv/kvm/vcpu_insn.c @@ -298,6 +298,22 @@ static int system_opcode_insn(struct kvm_vcpu *vcpu, struct kvm_run *run, return (rc <= 0) ? rc : 1; } +static bool is_load_guest_page_fault(unsigned long scause) +{ + /** + * If a g-stage page fault occurs, the direct approach + * is to let the g-stage page fault handler handle it + * naturally, however, calling the g-stage page fault + * handler here seems rather strange. + * Considering this is a corner case, we can directly + * return to the guest and re-execute the same PC, this + * will trigger a g-stage page fault again and then the + * regular g-stage page fault handler will populate + * g-stage page table. + */ + return (scause == EXC_LOAD_GUEST_PAGE_FAULT); +} + /** * kvm_riscv_vcpu_virtual_insn -- Handle virtual instruction trap * @@ -323,6 +339,8 @@ int kvm_riscv_vcpu_virtual_insn(struct kvm_vcpu *vcpu, struct kvm_run *run, ct->sepc, &utrap); if (utrap.scause) { + if (is_load_guest_page_fault(utrap.scause)) + return 1; utrap.sepc = ct->sepc; kvm_riscv_vcpu_trap_redirect(vcpu, &utrap); return 1; @@ -378,6 +396,8 @@ int kvm_riscv_vcpu_mmio_load(struct kvm_vcpu *vcpu, struct kvm_run *run, insn = kvm_riscv_vcpu_unpriv_read(vcpu, true, ct->sepc, &utrap); if (utrap.scause) { + if (is_load_guest_page_fault(utrap.scause)) + return 1; /* Redirect trap if we failed to read instruction */ utrap.sepc = ct->sepc; kvm_riscv_vcpu_trap_redirect(vcpu, &utrap); @@ -504,6 +524,8 @@ int kvm_riscv_vcpu_mmio_store(struct kvm_vcpu *vcpu, struct kvm_run *run, insn = kvm_riscv_vcpu_unpriv_read(vcpu, true, ct->sepc, &utrap); if (utrap.scause) { + if (is_load_guest_page_fault(utrap.scause)) + return 1; /* Redirect trap if we failed to read instruction */ utrap.sepc = ct->sepc; kvm_riscv_vcpu_trap_redirect(vcpu, &utrap); From 46198ad004297e003b5e35a90aa841ecdb668bef Mon Sep 17 00:00:00 2001 From: Gao Xiang Date: Mon, 17 Nov 2025 19:57:29 +0800 Subject: [PATCH 0739/3902] erofs: correct FSDAX detection [ Upstream commit ebe4f3f6eb0c10f87c58e52a8912694c14fdeda6 ] The detection of the primary device is skipped incorrectly if the multiple or flattened feature is enabled. It also fixes the FSDAX misdetection for non-block extra blobs. Fixes: c6993c4cb918 ("erofs: Fallback to normal access if DAX is not supported on extra device") Reported-and-tested-by: syzbot+31b8fb02cb8a25bd5e78@syzkaller.appspotmail.com Closes: https://lore.kernel.org/r/691af9f6.a70a0220.3124cb.0097.GAE@google.com Cc: Yuezhang Mo Reviewed-by: Chao Yu Signed-off-by: Gao Xiang Signed-off-by: Sasha Levin --- fs/erofs/super.c | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/fs/erofs/super.c b/fs/erofs/super.c index f3f8d8c066e4e6..cd8ff98c29384f 100644 --- a/fs/erofs/super.c +++ b/fs/erofs/super.c @@ -174,15 +174,15 @@ static int erofs_init_device(struct erofs_buf *buf, struct super_block *sb, if (!erofs_is_fileio_mode(sbi)) { dif->dax_dev = fs_dax_get_by_bdev(file_bdev(file), &dif->dax_part_off, NULL, NULL); - if (!dif->dax_dev && test_opt(&sbi->opt, DAX_ALWAYS)) { - erofs_info(sb, "DAX unsupported by %s. Turning off DAX.", - dif->path); - clear_opt(&sbi->opt, DAX_ALWAYS); - } } else if (!S_ISREG(file_inode(file)->i_mode)) { fput(file); return -EINVAL; } + if (!dif->dax_dev && test_opt(&sbi->opt, DAX_ALWAYS)) { + erofs_info(sb, "DAX unsupported by %s. Turning off DAX.", + dif->path); + clear_opt(&sbi->opt, DAX_ALWAYS); + } dif->file = file; } @@ -215,13 +215,13 @@ static int erofs_scan_devices(struct super_block *sb, ondisk_extradevs, sbi->devs->extra_devices); return -EINVAL; } - if (!ondisk_extradevs) { - if (test_opt(&sbi->opt, DAX_ALWAYS) && !sbi->dif0.dax_dev) { - erofs_info(sb, "DAX unsupported by block device. Turning off DAX."); - clear_opt(&sbi->opt, DAX_ALWAYS); - } - return 0; + + if (test_opt(&sbi->opt, DAX_ALWAYS) && !sbi->dif0.dax_dev) { + erofs_info(sb, "DAX unsupported by block device. Turning off DAX."); + clear_opt(&sbi->opt, DAX_ALWAYS); } + if (!ondisk_extradevs) + return 0; if (!sbi->devs->extra_devices && !erofs_is_fscache_mode(sb)) sbi->devs->flatdev = true; From 620472e6b303c4dbcc7ecf1aba1cda4f3523e4a4 Mon Sep 17 00:00:00 2001 From: Gao Xiang Date: Sat, 22 Nov 2025 14:23:32 +0800 Subject: [PATCH 0740/3902] erofs: limit the level of fs stacking for file-backed mounts [ Upstream commit d53cd891f0e4311889349fff3a784dc552f814b9 ] Otherwise, it could cause potential kernel stack overflow (e.g., EROFS mounting itself). Reviewed-by: Sheng Yong Fixes: fb176750266a ("erofs: add file-backed mount support") Reviewed-by: Chao Yu Reviewed-by: Hongbo Li Signed-off-by: Gao Xiang Signed-off-by: Sasha Levin --- fs/erofs/super.c | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/fs/erofs/super.c b/fs/erofs/super.c index cd8ff98c29384f..937a215f626c1c 100644 --- a/fs/erofs/super.c +++ b/fs/erofs/super.c @@ -639,6 +639,22 @@ static int erofs_fc_fill_super(struct super_block *sb, struct fs_context *fc) sbi->blkszbits = PAGE_SHIFT; if (!sb->s_bdev) { + /* + * (File-backed mounts) EROFS claims it's safe to nest other + * fs contexts (including its own) due to self-controlled RO + * accesses/contexts and no side-effect changes that need to + * context save & restore so it can reuse the current thread + * context. However, it still needs to bump `s_stack_depth` to + * avoid kernel stack overflow from nested filesystems. + */ + if (erofs_is_fileio_mode(sbi)) { + sb->s_stack_depth = + file_inode(sbi->dif0.file)->i_sb->s_stack_depth + 1; + if (sb->s_stack_depth > FILESYSTEM_MAX_STACK_DEPTH) { + erofs_err(sb, "maximum fs stacking depth exceeded"); + return -ENOTBLK; + } + } sb->s_blocksize = PAGE_SIZE; sb->s_blocksize_bits = PAGE_SHIFT; From 32fdf021f6c90af7fb78597a8ef158abd2a99c62 Mon Sep 17 00:00:00 2001 From: Selvin Xavier Date: Wed, 19 Nov 2025 23:36:54 -0800 Subject: [PATCH 0741/3902] RDMA/bnxt_re: Fix the inline size for GenP7 devices [ Upstream commit 6afe40ff484a1155b71158b911c65299496e35c3 ] Inline size supported by the device is based on the number of SGEs supported by the adapter. Change the inline size calculation based on that. Fixes: de1d364c3815 ("RDMA/bnxt_re: Add support for Variable WQE in Genp7 adapters") Reviewed-by: Kashyap Desai Signed-off-by: Kalesh AP Signed-off-by: Selvin Xavier Link: https://patch.msgid.link/1763624215-10382-1-git-send-email-selvin.xavier@broadcom.com Signed-off-by: Leon Romanovsky Signed-off-by: Sasha Levin --- drivers/infiniband/hw/bnxt_re/qplib_sp.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/infiniband/hw/bnxt_re/qplib_sp.c b/drivers/infiniband/hw/bnxt_re/qplib_sp.c index 9ef581ed785c82..a9afac2cbb7cfd 100644 --- a/drivers/infiniband/hw/bnxt_re/qplib_sp.c +++ b/drivers/infiniband/hw/bnxt_re/qplib_sp.c @@ -162,7 +162,7 @@ int bnxt_qplib_get_dev_attr(struct bnxt_qplib_rcfw *rcfw) attr->max_srq_wqes = le32_to_cpu(sb->max_srq_wr) - 1; attr->max_srq_sges = sb->max_srq_sge; attr->max_pkey = 1; - attr->max_inline_data = le32_to_cpu(sb->max_inline_data); + attr->max_inline_data = attr->max_qp_sges * sizeof(struct sq_sge); if (!bnxt_qplib_is_chip_gen_p7(rcfw->res->cctx)) attr->l2_db_size = (sb->l2_db_space_size + 1) * (0x01 << RCFW_DBR_BASE_PAGE_SHIFT); From ce649d74650a9fd7890c2450a58c091103e5790d Mon Sep 17 00:00:00 2001 From: Selvin Xavier Date: Wed, 19 Nov 2025 23:36:55 -0800 Subject: [PATCH 0742/3902] RDMA/bnxt_re: Pass correct flag for dma mr creation [ Upstream commit a26c4c7cdb50247b8486f1caa1ea8ab5e5c37edf ] DMA MR doesn't use the unified MR model. So the lkey passed on to the reg_mr command to FW should contain the correct lkey. Driver is incorrectly over writing the lkey with pdid and firmware commands fails due to this. Avoid passing the wrong key for cases where the unified MR registration is not used. Fixes: f786eebbbefa ("RDMA/bnxt_re: Avoid an extra hwrm per MR creation") Signed-off-by: Kalesh AP Signed-off-by: Selvin Xavier Link: https://patch.msgid.link/1763624215-10382-2-git-send-email-selvin.xavier@broadcom.com Signed-off-by: Leon Romanovsky Signed-off-by: Sasha Levin --- drivers/infiniband/hw/bnxt_re/ib_verbs.c | 8 +++++--- drivers/infiniband/hw/bnxt_re/qplib_sp.c | 6 +++--- drivers/infiniband/hw/bnxt_re/qplib_sp.h | 2 +- 3 files changed, 9 insertions(+), 7 deletions(-) diff --git a/drivers/infiniband/hw/bnxt_re/ib_verbs.c b/drivers/infiniband/hw/bnxt_re/ib_verbs.c index 84ce3fce2826b7..f19b55c13d5809 100644 --- a/drivers/infiniband/hw/bnxt_re/ib_verbs.c +++ b/drivers/infiniband/hw/bnxt_re/ib_verbs.c @@ -601,7 +601,8 @@ static int bnxt_re_create_fence_mr(struct bnxt_re_pd *pd) mr->qplib_mr.va = (u64)(unsigned long)fence->va; mr->qplib_mr.total_size = BNXT_RE_FENCE_BYTES; rc = bnxt_qplib_reg_mr(&rdev->qplib_res, &mr->qplib_mr, NULL, - BNXT_RE_FENCE_PBL_SIZE, PAGE_SIZE); + BNXT_RE_FENCE_PBL_SIZE, PAGE_SIZE, + _is_alloc_mr_unified(rdev->dev_attr->dev_cap_flags)); if (rc) { ibdev_err(&rdev->ibdev, "Failed to register fence-MR\n"); goto fail; @@ -4027,7 +4028,7 @@ struct ib_mr *bnxt_re_get_dma_mr(struct ib_pd *ib_pd, int mr_access_flags) mr->qplib_mr.hwq.level = PBL_LVL_MAX; mr->qplib_mr.total_size = -1; /* Infinte length */ rc = bnxt_qplib_reg_mr(&rdev->qplib_res, &mr->qplib_mr, NULL, 0, - PAGE_SIZE); + PAGE_SIZE, false); if (rc) goto fail_mr; @@ -4257,7 +4258,8 @@ static struct ib_mr *__bnxt_re_user_reg_mr(struct ib_pd *ib_pd, u64 length, u64 umem_pgs = ib_umem_num_dma_blocks(umem, page_size); rc = bnxt_qplib_reg_mr(&rdev->qplib_res, &mr->qplib_mr, umem, - umem_pgs, page_size); + umem_pgs, page_size, + _is_alloc_mr_unified(rdev->dev_attr->dev_cap_flags)); if (rc) { ibdev_err(&rdev->ibdev, "Failed to register user MR - rc = %d\n", rc); rc = -EIO; diff --git a/drivers/infiniband/hw/bnxt_re/qplib_sp.c b/drivers/infiniband/hw/bnxt_re/qplib_sp.c index a9afac2cbb7cfd..408a34df266721 100644 --- a/drivers/infiniband/hw/bnxt_re/qplib_sp.c +++ b/drivers/infiniband/hw/bnxt_re/qplib_sp.c @@ -578,7 +578,7 @@ int bnxt_qplib_dereg_mrw(struct bnxt_qplib_res *res, struct bnxt_qplib_mrw *mrw, } int bnxt_qplib_reg_mr(struct bnxt_qplib_res *res, struct bnxt_qplib_mrw *mr, - struct ib_umem *umem, int num_pbls, u32 buf_pg_size) + struct ib_umem *umem, int num_pbls, u32 buf_pg_size, bool unified_mr) { struct bnxt_qplib_rcfw *rcfw = res->rcfw; struct bnxt_qplib_hwq_attr hwq_attr = {}; @@ -640,7 +640,7 @@ int bnxt_qplib_reg_mr(struct bnxt_qplib_res *res, struct bnxt_qplib_mrw *mr, req.access = (mr->access_flags & BNXT_QPLIB_MR_ACCESS_MASK); req.va = cpu_to_le64(mr->va); req.key = cpu_to_le32(mr->lkey); - if (_is_alloc_mr_unified(res->dattr->dev_cap_flags)) + if (unified_mr) req.key = cpu_to_le32(mr->pd->id); req.flags = cpu_to_le16(mr->flags); req.mr_size = cpu_to_le64(mr->total_size); @@ -651,7 +651,7 @@ int bnxt_qplib_reg_mr(struct bnxt_qplib_res *res, struct bnxt_qplib_mrw *mr, if (rc) goto fail; - if (_is_alloc_mr_unified(res->dattr->dev_cap_flags)) { + if (unified_mr) { mr->lkey = le32_to_cpu(resp.xid); mr->rkey = mr->lkey; } diff --git a/drivers/infiniband/hw/bnxt_re/qplib_sp.h b/drivers/infiniband/hw/bnxt_re/qplib_sp.h index 147b5d9c03138b..5a45c55c6464c9 100644 --- a/drivers/infiniband/hw/bnxt_re/qplib_sp.h +++ b/drivers/infiniband/hw/bnxt_re/qplib_sp.h @@ -341,7 +341,7 @@ int bnxt_qplib_alloc_mrw(struct bnxt_qplib_res *res, int bnxt_qplib_dereg_mrw(struct bnxt_qplib_res *res, struct bnxt_qplib_mrw *mrw, bool block); int bnxt_qplib_reg_mr(struct bnxt_qplib_res *res, struct bnxt_qplib_mrw *mr, - struct ib_umem *umem, int num_pbls, u32 buf_pg_size); + struct ib_umem *umem, int num_pbls, u32 buf_pg_size, bool unified_mr); int bnxt_qplib_free_mrw(struct bnxt_qplib_res *res, struct bnxt_qplib_mrw *mr); int bnxt_qplib_alloc_fast_reg_mr(struct bnxt_qplib_res *res, struct bnxt_qplib_mrw *mr, int max); From aa9dc3d80dd588c94f1abe9b9fa50576f2dd0ff6 Mon Sep 17 00:00:00 2001 From: Herbert Xu Date: Fri, 21 Nov 2025 13:36:03 +0800 Subject: [PATCH 0743/3902] crypto: ahash - Fix crypto_ahash_import with partial block data [ Upstream commit b0356b75f42fde15d4be268c5891f2cee6eb65bf ] Restore the partial block buffer in crypto_ahash_import by copying it. Check whether the partial block buffer exceeds the maximum size and return -EOVERFLOW if it does. Zero the partial block buffer in crypto_ahash_import_core. Reported-by: T Pratham Tested-by: T Pratham Fixes: 9d7a0ab1c753 ("crypto: ahash - Handle partial blocks in API") Signed-off-by: Herbert Xu Signed-off-by: Sasha Levin --- crypto/ahash.c | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/crypto/ahash.c b/crypto/ahash.c index dfb4f5476428fc..819b484a1a003c 100644 --- a/crypto/ahash.c +++ b/crypto/ahash.c @@ -661,6 +661,12 @@ int crypto_ahash_import_core(struct ahash_request *req, const void *in) in); if (crypto_ahash_get_flags(tfm) & CRYPTO_TFM_NEED_KEY) return -ENOKEY; + if (crypto_ahash_block_only(tfm)) { + unsigned int reqsize = crypto_ahash_reqsize(tfm); + u8 *buf = ahash_request_ctx(req); + + buf[reqsize - 1] = 0; + } return crypto_ahash_alg(tfm)->import_core(req, in); } EXPORT_SYMBOL_GPL(crypto_ahash_import_core); @@ -674,10 +680,14 @@ int crypto_ahash_import(struct ahash_request *req, const void *in) if (crypto_ahash_get_flags(tfm) & CRYPTO_TFM_NEED_KEY) return -ENOKEY; if (crypto_ahash_block_only(tfm)) { + unsigned int plen = crypto_ahash_blocksize(tfm) + 1; unsigned int reqsize = crypto_ahash_reqsize(tfm); + unsigned int ss = crypto_ahash_statesize(tfm); u8 *buf = ahash_request_ctx(req); - buf[reqsize - 1] = 0; + memcpy(buf + reqsize - plen, in + ss - plen, plen); + if (buf[reqsize - 1] >= plen) + return -EOVERFLOW; } return crypto_ahash_alg(tfm)->import(req, in); } From fd9c3a1c963f4bb92435df4fd538d917023aed86 Mon Sep 17 00:00:00 2001 From: Herbert Xu Date: Fri, 21 Nov 2025 13:54:20 +0800 Subject: [PATCH 0744/3902] crypto: ahash - Zero positive err value in ahash_update_finish [ Upstream commit ebbdf6466b30e3b37f3b360826efd21f0633fb9e ] The partial block length returned by a block-only driver should not be passed up to the caller since ahash itself deals with the partial block data. Set err to zero in ahash_update_finish if it was positive. Reported-by: T Pratham Tested-by: T Pratham Fixes: 9d7a0ab1c753 ("crypto: ahash - Handle partial blocks in API") Signed-off-by: Herbert Xu Signed-off-by: Sasha Levin --- crypto/ahash.c | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/crypto/ahash.c b/crypto/ahash.c index 819b484a1a003c..66492ae75fcfb6 100644 --- a/crypto/ahash.c +++ b/crypto/ahash.c @@ -423,7 +423,11 @@ static int ahash_update_finish(struct ahash_request *req, int err) req->nbytes += nonzero - blen; - blen = err < 0 ? 0 : err + nonzero; + blen = 0; + if (err >= 0) { + blen = err + nonzero; + err = 0; + } if (ahash_request_isvirt(req)) memcpy(buf, req->svirt + req->nbytes - blen, blen); else From 23ca40088490b535f473273b7e522c364cb1f471 Mon Sep 17 00:00:00 2001 From: Baojun Xu Date: Mon, 24 Nov 2025 11:15:42 +0800 Subject: [PATCH 0745/3902] ASoC: tas2781: Correct the wrong chip ID for reset variable check [ Upstream commit 34b78ddd78428e66a7f08f71763258723eae2306 ] The new variable of reset was added for TAS58XX on TAS5825 first. And TAS5802/5815... was added later, so this reset variable check should be changed to lowest chip of TAS58XX. Fixes: 53a3c6e22283 ("ASoC: tas2781: Support more newly-released amplifiers tas58xx in the driver") Signed-off-by: Baojun Xu Link: https://patch.msgid.link/20251124031542.2793-1-baojun.xu@ti.com Signed-off-by: Mark Brown Signed-off-by: Sasha Levin --- sound/soc/codecs/tas2781-comlib-i2c.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sound/soc/codecs/tas2781-comlib-i2c.c b/sound/soc/codecs/tas2781-comlib-i2c.c index b3fd7350143bdb..e24d56a14cfda1 100644 --- a/sound/soc/codecs/tas2781-comlib-i2c.c +++ b/sound/soc/codecs/tas2781-comlib-i2c.c @@ -320,7 +320,7 @@ void tasdevice_reset(struct tasdevice_priv *tas_dev) for (i = 0; i < tas_dev->ndev; i++) { ret = tasdevice_dev_write(tas_dev, i, TASDEVICE_REG_SWRESET, - tas_dev->chip_id >= TAS5825 ? + tas_dev->chip_id >= TAS5802 ? TAS5825_REG_SWRESET_RESET : TASDEVICE_REG_SWRESET_RESET); if (ret < 0) From 791520a8e54e2a3ed3c4ad09c764be865deaaf01 Mon Sep 17 00:00:00 2001 From: Shenghao Ding Date: Sat, 22 Nov 2025 07:44:27 +0800 Subject: [PATCH 0746/3902] ASoC: tas2781: correct the wrong period [ Upstream commit 950167a99dfd27eeaf177092908c598a31c79a7e ] A wrong preiod at the end of the sentence was reported by one of my customers. Their thorough code review is greatly appreciated. Fixes: 49e2e353fb0d ("ASoC: tas2781: Add Calibration Kcontrols for Chromebook") Signed-off-by: Shenghao Ding Link: https://patch.msgid.link/20251121234427.402-1-shenghao-ding@ti.com Signed-off-by: Mark Brown Signed-off-by: Sasha Levin --- sound/soc/codecs/tas2781-i2c.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sound/soc/codecs/tas2781-i2c.c b/sound/soc/codecs/tas2781-i2c.c index 8f37aa00e62ee7..a3b4d2c3b47892 100644 --- a/sound/soc/codecs/tas2781-i2c.c +++ b/sound/soc/codecs/tas2781-i2c.c @@ -1391,7 +1391,7 @@ static int tasdevice_create_cali_ctrls(struct tasdevice_priv *priv) /* * Alloc kcontrol via devm_kzalloc(), which don't manually - * free the kcontrol。 + * free the kcontrol. */ cali_ctrls = devm_kcalloc(priv->dev, nctrls, sizeof(cali_ctrls[0]), GFP_KERNEL); From b8f34c1c5c4f5130c20e3253c95ba1d844d402b9 Mon Sep 17 00:00:00 2001 From: Felix Fietkau Date: Mon, 29 Sep 2025 13:17:23 +0200 Subject: [PATCH 0747/3902] wifi: mt76: mt7996: fix null pointer deref in mt7996_conf_tx() [ Upstream commit 79277f8ad15ec5f255ed0e1427c7a8a3e94e7f52 ] If a link does not have an assigned channel yet, mt7996_vif_link returns NULL. We still need to store the updated queue settings in that case, and apply them later. Move the location of the queue params to within struct mt7996_vif_link. Fixes: c0df2f0caa8d ("wifi: mt76: mt7996: prepare mt7996_mcu_set_tx for MLO support") Acked-by: Lorenzo Bianconi Link: https://patch.msgid.link/20250929111723.52486-1-nbd@nbd.name Signed-off-by: Felix Fietkau Signed-off-by: Sasha Levin --- drivers/net/wireless/mediatek/mt76/mt7996/main.c | 6 +++--- drivers/net/wireless/mediatek/mt76/mt7996/mcu.c | 5 ++++- drivers/net/wireless/mediatek/mt76/mt7996/mt7996.h | 7 ++++++- 3 files changed, 13 insertions(+), 5 deletions(-) diff --git a/drivers/net/wireless/mediatek/mt76/mt7996/main.c b/drivers/net/wireless/mediatek/mt76/mt7996/main.c index 581314368c5ba5..b53ca702591c62 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7996/main.c +++ b/drivers/net/wireless/mediatek/mt76/mt7996/main.c @@ -665,8 +665,8 @@ mt7996_conf_tx(struct ieee80211_hw *hw, struct ieee80211_vif *vif, unsigned int link_id, u16 queue, const struct ieee80211_tx_queue_params *params) { - struct mt7996_dev *dev = mt7996_hw_dev(hw); - struct mt7996_vif_link *mlink = mt7996_vif_link(dev, vif, link_id); + struct mt7996_vif *mvif = (struct mt7996_vif *)vif->drv_priv; + struct mt7996_vif_link_info *link_info = &mvif->link_info[link_id]; static const u8 mq_to_aci[] = { [IEEE80211_AC_VO] = 3, [IEEE80211_AC_VI] = 2, @@ -675,7 +675,7 @@ mt7996_conf_tx(struct ieee80211_hw *hw, struct ieee80211_vif *vif, }; /* firmware uses access class index */ - mlink->queue_params[mq_to_aci[queue]] = *params; + link_info->queue_params[mq_to_aci[queue]] = *params; /* no need to update right away, we'll get BSS_CHANGED_QOS */ return 0; diff --git a/drivers/net/wireless/mediatek/mt76/mt7996/mcu.c b/drivers/net/wireless/mediatek/mt76/mt7996/mcu.c index 0347ee0c2dd75d..afa6a43bd51e55 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7996/mcu.c +++ b/drivers/net/wireless/mediatek/mt76/mt7996/mcu.c @@ -3414,6 +3414,9 @@ int mt7996_mcu_set_tx(struct mt7996_dev *dev, struct ieee80211_vif *vif, #define WMM_PARAM_SET (WMM_AIFS_SET | WMM_CW_MIN_SET | \ WMM_CW_MAX_SET | WMM_TXOP_SET) struct mt7996_vif_link *link = mt7996_vif_conf_link(dev, vif, link_conf); + struct mt7996_vif *mvif = (struct mt7996_vif *)vif->drv_priv; + unsigned int link_id = link_conf->link_id; + struct mt7996_vif_link_info *link_info = &mvif->link_info[link_id]; struct { u8 bss_idx; u8 __rsv[3]; @@ -3431,7 +3434,7 @@ int mt7996_mcu_set_tx(struct mt7996_dev *dev, struct ieee80211_vif *vif, skb_put_data(skb, &hdr, sizeof(hdr)); for (ac = 0; ac < IEEE80211_NUM_ACS; ac++) { - struct ieee80211_tx_queue_params *q = &link->queue_params[ac]; + struct ieee80211_tx_queue_params *q = &link_info->queue_params[ac]; struct edca *e; struct tlv *tlv; diff --git a/drivers/net/wireless/mediatek/mt76/mt7996/mt7996.h b/drivers/net/wireless/mediatek/mt76/mt7996/mt7996.h index 8ec2acdb331937..718e4d4ad85f27 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7996/mt7996.h +++ b/drivers/net/wireless/mediatek/mt76/mt7996/mt7996.h @@ -253,16 +253,21 @@ struct mt7996_vif_link { struct mt7996_sta_link msta_link; struct mt7996_phy *phy; - struct ieee80211_tx_queue_params queue_params[IEEE80211_NUM_ACS]; struct cfg80211_bitrate_mask bitrate_mask; u8 mld_idx; }; +struct mt7996_vif_link_info { + struct ieee80211_tx_queue_params queue_params[IEEE80211_NUM_ACS]; +}; + struct mt7996_vif { struct mt7996_vif_link deflink; /* must be first */ struct mt76_vif_data mt76; + struct mt7996_vif_link_info link_info[IEEE80211_MLD_MAX_NUM_LINKS]; + u8 mld_group_idx; u8 mld_remap_idx; }; From cbeca0b7ea207028036fa365ca7441c7720891c2 Mon Sep 17 00:00:00 2001 From: Lorenzo Bianconi Date: Wed, 24 Sep 2025 15:51:39 +0200 Subject: [PATCH 0748/3902] wifi: mt76: mt7996: Remove unnecessary link_id checks in mt7996_tx [ Upstream commit 084922069ceac4d594c06b76a80352139fd15f4d ] Remove unnecessary link_id checks in mt7996_tx routine since if the link identifier provided by mac80211 is unspecified the value will be overwritten at the beginning on the function. Fixes: f940c9b7aef6 ("wifi: mt76: mt7996: Set proper link destination address in mt7996_tx()") Signed-off-by: Lorenzo Bianconi Link: https://patch.msgid.link/20250924-mt76_tx_unnecessary-check-v1-1-e595930a5662@kernel.org Signed-off-by: Felix Fietkau Signed-off-by: Sasha Levin --- drivers/net/wireless/mediatek/mt76/mt7996/main.c | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/drivers/net/wireless/mediatek/mt76/mt7996/main.c b/drivers/net/wireless/mediatek/mt76/mt7996/main.c index b53ca702591c62..2b52d057287a1f 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7996/main.c +++ b/drivers/net/wireless/mediatek/mt76/mt7996/main.c @@ -1339,12 +1339,10 @@ static void mt7996_tx(struct ieee80211_hw *hw, } if (mvif) { - struct mt76_vif_link *mlink = &mvif->deflink.mt76; + struct mt76_vif_link *mlink; - if (link_id < IEEE80211_LINK_UNSPECIFIED) - mlink = rcu_dereference(mvif->mt76.link[link_id]); - - if (mlink->wcid) + mlink = rcu_dereference(mvif->mt76.link[link_id]); + if (mlink && mlink->wcid) wcid = mlink->wcid; if (mvif->mt76.roc_phy && @@ -1352,7 +1350,7 @@ static void mt7996_tx(struct ieee80211_hw *hw, mphy = mvif->mt76.roc_phy; if (mphy->roc_link) wcid = mphy->roc_link->wcid; - } else { + } else if (mlink) { mphy = mt76_vif_link_phy(mlink); } } @@ -1362,7 +1360,7 @@ static void mt7996_tx(struct ieee80211_hw *hw, goto unlock; } - if (msta && link_id < IEEE80211_LINK_UNSPECIFIED) { + if (msta) { struct mt7996_sta_link *msta_link; msta_link = rcu_dereference(msta->link[link_id]); From d582d0e988d696698c94edf097062bb987ae592c Mon Sep 17 00:00:00 2001 From: Lorenzo Bianconi Date: Wed, 8 Oct 2025 12:41:48 +0200 Subject: [PATCH 0749/3902] wifi: mt76: wed: use proper wed reference in mt76 wed driver callabacks [ Upstream commit 385aab8fccd7a8746b9f1a17f3c1e38498a14bc7 ] MT7996 driver can use both wed and wed_hif2 devices to offload traffic from/to the wireless NIC. In the current codebase we assume to always use the primary wed device in wed callbacks resulting in the following crash if the hw runs wed_hif2 (e.g. 6GHz link). [ 297.455876] Unable to handle kernel read from unreadable memory at virtual address 000000000000080a [ 297.464928] Mem abort info: [ 297.467722] ESR = 0x0000000096000005 [ 297.471461] EC = 0x25: DABT (current EL), IL = 32 bits [ 297.476766] SET = 0, FnV = 0 [ 297.479809] EA = 0, S1PTW = 0 [ 297.482940] FSC = 0x05: level 1 translation fault [ 297.487809] Data abort info: [ 297.490679] ISV = 0, ISS = 0x00000005, ISS2 = 0x00000000 [ 297.496156] CM = 0, WnR = 0, TnD = 0, TagAccess = 0 [ 297.501196] GCS = 0, Overlay = 0, DirtyBit = 0, Xs = 0 [ 297.506500] user pgtable: 4k pages, 39-bit VAs, pgdp=0000000107480000 [ 297.512927] [000000000000080a] pgd=08000001097fb003, p4d=08000001097fb003, pud=08000001097fb003, pmd=0000000000000000 [ 297.523532] Internal error: Oops: 0000000096000005 [#1] SMP [ 297.715393] CPU: 2 UID: 0 PID: 45 Comm: kworker/u16:2 Tainted: G O 6.12.50 #0 [ 297.723908] Tainted: [O]=OOT_MODULE [ 297.727384] Hardware name: Banana Pi BPI-R4 (2x SFP+) (DT) [ 297.732857] Workqueue: nf_ft_offload_del nf_flow_rule_route_ipv6 [nf_flow_table] [ 297.740254] pstate: 60400005 (nZCv daif +PAN -UAO -TCO -DIT -SSBS BTYPE=--) [ 297.747205] pc : mt76_wed_offload_disable+0x64/0xa0 [mt76] [ 297.752688] lr : mtk_wed_flow_remove+0x58/0x80 [ 297.757126] sp : ffffffc080fe3ae0 [ 297.760430] x29: ffffffc080fe3ae0 x28: ffffffc080fe3be0 x27: 00000000deadbef7 [ 297.767557] x26: ffffff80c5ebca00 x25: 0000000000000001 x24: ffffff80c85f4c00 [ 297.774683] x23: ffffff80c1875b78 x22: ffffffc080d42cd0 x21: ffffffc080660018 [ 297.781809] x20: ffffff80c6a076d0 x19: ffffff80c6a043c8 x18: 0000000000000000 [ 297.788935] x17: 0000000000000000 x16: 0000000000000001 x15: 0000000000000000 [ 297.796060] x14: 0000000000000019 x13: ffffff80c0ad8ec0 x12: 00000000fa83b2da [ 297.803185] x11: ffffff80c02700c0 x10: ffffff80c0ad8ec0 x9 : ffffff81fef96200 [ 297.810311] x8 : ffffff80c02700c0 x7 : ffffff80c02700d0 x6 : 0000000000000002 [ 297.817435] x5 : 0000000000000400 x4 : 0000000000000000 x3 : 0000000000000000 [ 297.824561] x2 : 0000000000000001 x1 : 0000000000000800 x0 : ffffff80c6a063c8 [ 297.831686] Call trace: [ 297.834123] mt76_wed_offload_disable+0x64/0xa0 [mt76] [ 297.839254] mtk_wed_flow_remove+0x58/0x80 [ 297.843342] mtk_flow_offload_cmd+0x434/0x574 [ 297.847689] mtk_wed_setup_tc_block_cb+0x30/0x40 [ 297.852295] nf_flow_offload_ipv6_hook+0x7f4/0x964 [nf_flow_table] [ 297.858466] nf_flow_rule_route_ipv6+0x438/0x4a4 [nf_flow_table] [ 297.864463] process_one_work+0x174/0x300 [ 297.868465] worker_thread+0x278/0x430 [ 297.872204] kthread+0xd8/0xdc [ 297.875251] ret_from_fork+0x10/0x20 [ 297.878820] Code: 928b5ae0 8b000273 91400a60 f943fa61 (79401421) [ 297.884901] ---[ end trace 0000000000000000 ]--- Fix the issue detecting the proper wed reference to use running wed callabacks. Fixes: 83eafc9251d6 ("wifi: mt76: mt7996: add wed tx support") Tested-by: Daniel Pawlik Tested-by: Matteo Croce Signed-off-by: Lorenzo Bianconi Link: https://patch.msgid.link/20251008-wed-fixes-v1-1-8f7678583385@kernel.org Signed-off-by: Felix Fietkau Signed-off-by: Sasha Levin --- drivers/net/wireless/mediatek/mt76/mt76.h | 9 +++++++++ drivers/net/wireless/mediatek/mt76/mt7996/mmio.c | 1 + drivers/net/wireless/mediatek/mt76/wed.c | 10 +++++----- include/linux/soc/mediatek/mtk_wed.h | 1 + 4 files changed, 16 insertions(+), 5 deletions(-) diff --git a/drivers/net/wireless/mediatek/mt76/mt76.h b/drivers/net/wireless/mediatek/mt76/mt76.h index e0d50b58cd0129..7753afa3d883d6 100644 --- a/drivers/net/wireless/mediatek/mt76/mt76.h +++ b/drivers/net/wireless/mediatek/mt76/mt76.h @@ -1252,6 +1252,15 @@ static inline int mt76_wed_dma_setup(struct mt76_dev *dev, struct mt76_queue *q, #define mt76_dereference(p, dev) \ rcu_dereference_protected(p, lockdep_is_held(&(dev)->mutex)) +static inline struct mt76_dev *mt76_wed_to_dev(struct mtk_wed_device *wed) +{ +#ifdef CONFIG_NET_MEDIATEK_SOC_WED + if (wed->wlan.hif2) + return container_of(wed, struct mt76_dev, mmio.wed_hif2); +#endif /* CONFIG_NET_MEDIATEK_SOC_WED */ + return container_of(wed, struct mt76_dev, mmio.wed); +} + static inline struct mt76_wcid * __mt76_wcid_ptr(struct mt76_dev *dev, u16 idx) { diff --git a/drivers/net/wireless/mediatek/mt76/mt7996/mmio.c b/drivers/net/wireless/mediatek/mt76/mt7996/mmio.c index d14b626ee51156..80db102ed809c3 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7996/mmio.c +++ b/drivers/net/wireless/mediatek/mt76/mt7996/mmio.c @@ -595,6 +595,7 @@ int mt7996_mmio_wed_init(struct mt7996_dev *dev, void *pdev_ptr, wed->wlan.nbuf = MT7996_HW_TOKEN_SIZE; wed->wlan.token_start = MT7996_TOKEN_SIZE - wed->wlan.nbuf; + wed->wlan.hif2 = hif2; wed->wlan.amsdu_max_subframes = 8; wed->wlan.amsdu_max_len = 1536; diff --git a/drivers/net/wireless/mediatek/mt76/wed.c b/drivers/net/wireless/mediatek/mt76/wed.c index 907a8e43e72ad1..fbd7e59c73aaf2 100644 --- a/drivers/net/wireless/mediatek/mt76/wed.c +++ b/drivers/net/wireless/mediatek/mt76/wed.c @@ -8,7 +8,7 @@ void mt76_wed_release_rx_buf(struct mtk_wed_device *wed) { - struct mt76_dev *dev = container_of(wed, struct mt76_dev, mmio.wed); + struct mt76_dev *dev = mt76_wed_to_dev(wed); int i; for (i = 0; i < dev->rx_token_size; i++) { @@ -31,8 +31,8 @@ EXPORT_SYMBOL_GPL(mt76_wed_release_rx_buf); #ifdef CONFIG_NET_MEDIATEK_SOC_WED u32 mt76_wed_init_rx_buf(struct mtk_wed_device *wed, int size) { - struct mt76_dev *dev = container_of(wed, struct mt76_dev, mmio.wed); struct mtk_wed_bm_desc *desc = wed->rx_buf_ring.desc; + struct mt76_dev *dev = mt76_wed_to_dev(wed); struct mt76_queue *q = &dev->q_rx[MT_RXQ_MAIN]; struct mt76_txwi_cache *t = NULL; int i; @@ -80,7 +80,7 @@ EXPORT_SYMBOL_GPL(mt76_wed_init_rx_buf); int mt76_wed_offload_enable(struct mtk_wed_device *wed) { - struct mt76_dev *dev = container_of(wed, struct mt76_dev, mmio.wed); + struct mt76_dev *dev = mt76_wed_to_dev(wed); spin_lock_bh(&dev->token_lock); dev->token_size = wed->wlan.token_start; @@ -164,7 +164,7 @@ EXPORT_SYMBOL_GPL(mt76_wed_dma_setup); void mt76_wed_offload_disable(struct mtk_wed_device *wed) { - struct mt76_dev *dev = container_of(wed, struct mt76_dev, mmio.wed); + struct mt76_dev *dev = mt76_wed_to_dev(wed); spin_lock_bh(&dev->token_lock); dev->token_size = dev->drv->token_size; @@ -174,7 +174,7 @@ EXPORT_SYMBOL_GPL(mt76_wed_offload_disable); void mt76_wed_reset_complete(struct mtk_wed_device *wed) { - struct mt76_dev *dev = container_of(wed, struct mt76_dev, mmio.wed); + struct mt76_dev *dev = mt76_wed_to_dev(wed); complete(&dev->mmio.wed_reset_complete); } diff --git a/include/linux/soc/mediatek/mtk_wed.h b/include/linux/soc/mediatek/mtk_wed.h index c4ff6bab176db1..3fa93bd650044b 100644 --- a/include/linux/soc/mediatek/mtk_wed.h +++ b/include/linux/soc/mediatek/mtk_wed.h @@ -154,6 +154,7 @@ struct mtk_wed_device { bool wcid_512; bool hw_rro; bool msi; + bool hif2; u16 token_start; unsigned int nbuf; From d2d11298dfc3eb35872fea9226e294fac445e55a Mon Sep 17 00:00:00 2001 From: Lorenzo Bianconi Date: Tue, 14 Oct 2025 15:28:53 +0200 Subject: [PATCH 0750/3902] wifi: mt76: mt7996: Remove useless check in mt7996_msdu_page_get_from_cache() [ Upstream commit 2157e49892c5eae210b8fa6ee8672bd9d0ffa4b5 ] Get rid of useless null-pointer check in mt7996_msdu_page_get_from_cache since we have already verfied the list is not empty. Fixes: b1e58e137b616 ("wifi: mt76: mt7996: Introduce RRO MSDU callbacks") Reported-by: kernel test robot Closes: https://lore.kernel.org/r/202510100155.MS0IXhzm-lkp@intel.com/ Signed-off-by: Lorenzo Bianconi Link: https://patch.msgid.link/20251014-mt7996_msdu_page_get_from_cache-remove-null-ptr-check-v1-1-fbeb7881e192@kernel.org Signed-off-by: Felix Fietkau Signed-off-by: Sasha Levin --- drivers/net/wireless/mediatek/mt76/mt7996/mac.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/drivers/net/wireless/mediatek/mt76/mt7996/mac.c b/drivers/net/wireless/mediatek/mt76/mt7996/mac.c index 9501def3e0e3e2..284f2eea71e5bf 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7996/mac.c +++ b/drivers/net/wireless/mediatek/mt76/mt7996/mac.c @@ -1681,8 +1681,7 @@ mt7996_msdu_page_get_from_cache(struct mt7996_dev *dev) if (!list_empty(&dev->wed_rro.page_cache)) { p = list_first_entry(&dev->wed_rro.page_cache, struct mt7996_msdu_page, list); - if (p) - list_del(&p->list); + list_del(&p->list); } spin_unlock(&dev->wed_rro.lock); From 2cde732373a734c1d68f38e83344a49f5f4a2f54 Mon Sep 17 00:00:00 2001 From: Fedor Pchelkin Date: Mon, 27 Oct 2025 14:18:39 +0300 Subject: [PATCH 0751/3902] Revert "wifi: mt76: mt792x: improve monitor interface handling" [ Upstream commit cdb2941a516cf06929293604e2e0f4c1d6f3541e ] This reverts commit 55e95ce469d0c61041bae48b2ebb7fcbf6d1ba7f. mt792x drivers don't seem to support multi-radio devices yet. At least they don't mess with `struct wiphy_radio` at the moment. Packet capturing on monitor interface doesn't work after the blamed patch: tcpdump -i wls6mon -n -vvv Revert the NO_VIRTUAL_MONITOR feature for now to resolve the issue. Found by Linux Verification Center (linuxtesting.org). Fixes: 55e95ce469d0 ("wifi: mt76: mt792x: improve monitor interface handling") Signed-off-by: Fedor Pchelkin Link: https://patch.msgid.link/20251027111843.38975-1-pchelkin@ispras.ru Signed-off-by: Felix Fietkau Signed-off-by: Sasha Levin --- drivers/net/wireless/mediatek/mt76/mt792x_core.c | 1 - 1 file changed, 1 deletion(-) diff --git a/drivers/net/wireless/mediatek/mt76/mt792x_core.c b/drivers/net/wireless/mediatek/mt76/mt792x_core.c index c0e56541a95476..9cad572c34a38a 100644 --- a/drivers/net/wireless/mediatek/mt76/mt792x_core.c +++ b/drivers/net/wireless/mediatek/mt76/mt792x_core.c @@ -688,7 +688,6 @@ int mt792x_init_wiphy(struct ieee80211_hw *hw) ieee80211_hw_set(hw, SUPPORTS_DYNAMIC_PS); ieee80211_hw_set(hw, SUPPORTS_VHT_EXT_NSS_BW); ieee80211_hw_set(hw, CONNECTION_MONITOR); - ieee80211_hw_set(hw, NO_VIRTUAL_MONITOR); ieee80211_hw_set(hw, SUPPORTS_MULTI_BSSID); ieee80211_hw_set(hw, SUPPORTS_ONLY_HE_MULTI_BSSID); From 74bb080717a3dc9f26cfc4874c69181910f54042 Mon Sep 17 00:00:00 2001 From: StanleyYP Wang Date: Thu, 6 Nov 2025 14:41:52 +0800 Subject: [PATCH 0752/3902] wifi: mt76: mt7996: fix max nss value when getting rx chainmask [ Upstream commit 361b59b6be7c33c43b619d5cada394efc0f3b398 ] Since wiphy->available_antennas_tx now accumulates the chainmask of all the radios of a wiphy, use phy->orig_antenna_mask to get the original max nss for comparison. Fixes: 69d54ce7491d ("wifi: mt76: mt7996: switch to single multi-radio wiphy") Signed-off-by: StanleyYP Wang Signed-off-by: Shayne Chen Link: https://patch.msgid.link/20251106064203.1000505-1-shayne.chen@mediatek.com Signed-off-by: Felix Fietkau Signed-off-by: Sasha Levin --- drivers/net/wireless/mediatek/mt76/mt7996/mt7996.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/net/wireless/mediatek/mt76/mt7996/mt7996.h b/drivers/net/wireless/mediatek/mt76/mt7996/mt7996.h index 718e4d4ad85f27..1727e73a99ce69 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7996/mt7996.h +++ b/drivers/net/wireless/mediatek/mt76/mt7996/mt7996.h @@ -786,7 +786,7 @@ void mt7996_memcpy_fromio(struct mt7996_dev *dev, void *buf, u32 offset, static inline u16 mt7996_rx_chainmask(struct mt7996_phy *phy) { - int max_nss = hweight8(phy->mt76->hw->wiphy->available_antennas_tx); + int max_nss = hweight16(phy->orig_antenna_mask); int cur_nss = hweight8(phy->mt76->antenna_mask); u16 tx_chainmask = phy->mt76->chainmask; From 95bb36f8d6b30829c4aaacddcbe3213e96acd04c Mon Sep 17 00:00:00 2001 From: Howard Hsu Date: Thu, 6 Nov 2025 14:41:54 +0800 Subject: [PATCH 0753/3902] wifi: mt76: mt7996: fix implicit beamforming support for mt7992 [ Upstream commit 5d86765828b47444908a8689f2625872e8dac48f ] Fix the ibf_timeout field for mt7996, mt7992 and mt7990 chipsets. For the mt7992, this value shall be set as 0xff, while the others shall be set as 0x18. Fixes: ad4c9a8a9803 ("wifi: mt76: mt7996: add implicit beamforming support for mt7992") Signed-off-by: Howard Hsu Signed-off-by: Shayne Chen Link: https://patch.msgid.link/20251106064203.1000505-3-shayne.chen@mediatek.com Signed-off-by: Felix Fietkau Signed-off-by: Sasha Levin --- drivers/net/wireless/mediatek/mt76/mt7996/mcu.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/net/wireless/mediatek/mt76/mt7996/mcu.c b/drivers/net/wireless/mediatek/mt76/mt7996/mcu.c index afa6a43bd51e55..9af3c48707ab71 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7996/mcu.c +++ b/drivers/net/wireless/mediatek/mt76/mt7996/mcu.c @@ -1822,8 +1822,8 @@ mt7996_mcu_sta_bfer_tlv(struct mt7996_dev *dev, struct sk_buff *skb, bf->ibf_nrow = tx_ant; if (link_sta->eht_cap.has_eht || link_sta->he_cap.has_he) - bf->ibf_timeout = is_mt7996(&dev->mt76) ? MT7996_IBF_TIMEOUT : - MT7992_IBF_TIMEOUT; + bf->ibf_timeout = is_mt7992(&dev->mt76) ? MT7992_IBF_TIMEOUT : + MT7996_IBF_TIMEOUT; else if (!ebf && link_sta->bandwidth <= IEEE80211_STA_RX_BW_40 && !bf->ncol) bf->ibf_timeout = MT7996_IBF_TIMEOUT_LEGACY; else From ee94375d06b5db5f7f4943ddc9f2f763eaf7a978 Mon Sep 17 00:00:00 2001 From: Shayne Chen Date: Thu, 6 Nov 2025 14:41:56 +0800 Subject: [PATCH 0754/3902] wifi: mt76: mt7996: fix several fields in mt7996_mcu_bss_basic_tlv() [ Upstream commit bb705a606734e1ce0ff17a4f368a896757ba686d ] Fix several fields in mt7996_mcu_bss_basic_tlv() that were not obtained from the correct link. Without this patch, the MLD station interface does not function properly. Fixes: 34a41bfbcb71 ("wifi: mt76: mt7996: prepare mt7996_mcu_add_dev/bss_info for MLO support") Signed-off-by: Shayne Chen Acked-by: Lorenzo Bianconi Link: https://patch.msgid.link/20251106064203.1000505-5-shayne.chen@mediatek.com Signed-off-by: Felix Fietkau Signed-off-by: Sasha Levin --- .../net/wireless/mediatek/mt76/mt7996/mcu.c | 21 ++++++++++--------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/drivers/net/wireless/mediatek/mt76/mt7996/mcu.c b/drivers/net/wireless/mediatek/mt76/mt7996/mcu.c index 9af3c48707ab71..21be88e76c6553 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7996/mcu.c +++ b/drivers/net/wireless/mediatek/mt76/mt7996/mcu.c @@ -1034,7 +1034,6 @@ mt7996_mcu_bss_basic_tlv(struct sk_buff *skb, struct mt76_connac_bss_basic_tlv *bss; u32 type = CONNECTION_INFRA_AP; u16 sta_wlan_idx = wlan_idx; - struct ieee80211_sta *sta; struct tlv *tlv; int idx; @@ -1045,14 +1044,18 @@ mt7996_mcu_bss_basic_tlv(struct sk_buff *skb, break; case NL80211_IFTYPE_STATION: if (enable) { + struct ieee80211_sta *sta; + rcu_read_lock(); - sta = ieee80211_find_sta(vif, vif->bss_conf.bssid); - /* TODO: enable BSS_INFO_UAPSD & BSS_INFO_PM */ + sta = ieee80211_find_sta(vif, link_conf->bssid); if (sta) { - struct mt76_wcid *wcid; + struct mt7996_sta *msta = (void *)sta->drv_priv; + struct mt7996_sta_link *msta_link; + int link_id = link_conf->link_id; - wcid = (struct mt76_wcid *)sta->drv_priv; - sta_wlan_idx = wcid->idx; + msta_link = rcu_dereference(msta->link[link_id]); + if (msta_link) + sta_wlan_idx = msta_link->wcid.idx; } rcu_read_unlock(); } @@ -1069,8 +1072,6 @@ mt7996_mcu_bss_basic_tlv(struct sk_buff *skb, tlv = mt7996_mcu_add_uni_tlv(skb, UNI_BSS_INFO_BASIC, sizeof(*bss)); bss = (struct mt76_connac_bss_basic_tlv *)tlv; - bss->bcn_interval = cpu_to_le16(link_conf->beacon_int); - bss->dtim_period = link_conf->dtim_period; bss->bmc_tx_wlan_idx = cpu_to_le16(wlan_idx); bss->sta_idx = cpu_to_le16(sta_wlan_idx); bss->conn_type = cpu_to_le32(type); @@ -1090,10 +1091,10 @@ mt7996_mcu_bss_basic_tlv(struct sk_buff *skb, memcpy(bss->bssid, link_conf->bssid, ETH_ALEN); bss->bcn_interval = cpu_to_le16(link_conf->beacon_int); - bss->dtim_period = vif->bss_conf.dtim_period; + bss->dtim_period = link_conf->dtim_period; bss->phymode = mt76_connac_get_phy_mode(phy, vif, chandef->chan->band, NULL); - bss->phymode_ext = mt76_connac_get_phy_mode_ext(phy, &vif->bss_conf, + bss->phymode_ext = mt76_connac_get_phy_mode_ext(phy, link_conf, chandef->chan->band); return 0; From d64e6f27260eef3df49c5c3950494b6e9d36caad Mon Sep 17 00:00:00 2001 From: Shayne Chen Date: Thu, 6 Nov 2025 14:41:57 +0800 Subject: [PATCH 0755/3902] wifi: mt76: mt7996: fix teardown command for an MLD peer [ Upstream commit e077071e7ac48d5453072f615d51629891c5b90d ] For an MLD peer, we only need to call the teardown command when removing the last link, and there's no need to call mt7996_mcu_add_sta() for the earlier links. Fixes: c1d6dd5d03eb ("wifi: mt76: mt7996: Add mt7996_mcu_teardown_mld_sta rouine") Signed-off-by: Shayne Chen Acked-by: Lorenzo Bianconi Link: https://patch.msgid.link/20251106064203.1000505-6-shayne.chen@mediatek.com Signed-off-by: Felix Fietkau Signed-off-by: Sasha Levin --- drivers/net/wireless/mediatek/mt76/mt7996/main.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/drivers/net/wireless/mediatek/mt76/mt7996/main.c b/drivers/net/wireless/mediatek/mt76/mt7996/main.c index 2b52d057287a1f..aa46ea707b406d 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7996/main.c +++ b/drivers/net/wireless/mediatek/mt76/mt7996/main.c @@ -1206,13 +1206,13 @@ mt7996_mac_sta_event(struct mt7996_dev *dev, struct ieee80211_vif *vif, mt7996_mac_twt_teardown_flow(dev, link, msta_link, i); - if (sta->mlo && links == BIT(link_id)) /* last link */ - mt7996_mcu_teardown_mld_sta(dev, link, - msta_link); - else + if (!sta->mlo) mt7996_mcu_add_sta(dev, link_conf, link_sta, link, msta_link, CONN_STATE_DISCONNECT, false); + else if (sta->mlo && links == BIT(link_id)) /* last link */ + mt7996_mcu_teardown_mld_sta(dev, link, + msta_link); msta_link->wcid.sta_disabled = 1; msta_link->wcid.sta = 0; links = links & ~BIT(link_id); From e5c42efeb162c56e25f53c51ec31ee54691667f2 Mon Sep 17 00:00:00 2001 From: Shayne Chen Date: Thu, 6 Nov 2025 14:41:58 +0800 Subject: [PATCH 0756/3902] wifi: mt76: mt7996: set link_valid field when initializing wcid [ Upstream commit 7eaea3a8ba1e9bb58f87e3030f6ce18537e57e1f ] This ensures the upper layer uses the correct link ID during packet processing. Fixes: dd82a9e02c05 ("wifi: mt76: mt7996: Rely on mt7996_sta_link in sta_add/sta_remove callbacks") Signed-off-by: Shayne Chen Acked-by: Lorenzo Bianconi Link: https://patch.msgid.link/20251106064203.1000505-7-shayne.chen@mediatek.com Signed-off-by: Felix Fietkau Signed-off-by: Sasha Levin --- drivers/net/wireless/mediatek/mt76/mt7996/main.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/drivers/net/wireless/mediatek/mt76/mt7996/main.c b/drivers/net/wireless/mediatek/mt76/mt7996/main.c index aa46ea707b406d..4e738545895582 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7996/main.c +++ b/drivers/net/wireless/mediatek/mt76/mt7996/main.c @@ -343,6 +343,7 @@ int mt7996_vif_link_add(struct mt76_phy *mphy, struct ieee80211_vif *vif, INIT_LIST_HEAD(&msta_link->rc_list); msta_link->wcid.idx = idx; msta_link->wcid.link_id = link_conf->link_id; + msta_link->wcid.link_valid = ieee80211_vif_is_mld(vif); msta_link->wcid.tx_info |= MT_WCID_TX_INFO_SET; mt76_wcid_init(&msta_link->wcid, band_idx); @@ -984,6 +985,7 @@ mt7996_mac_sta_init_link(struct mt7996_dev *dev, msta_link->wcid.sta = 1; msta_link->wcid.idx = idx; msta_link->wcid.link_id = link_id; + msta_link->wcid.link_valid = !!sta->valid_links; msta_link->wcid.def_wcid = &msta->deflink.wcid; ewma_avg_signal_init(&msta_link->avg_ack_signal); From 9a43ed9ab800a9935eac8b0d264ea8d659a47db0 Mon Sep 17 00:00:00 2001 From: Shayne Chen Date: Thu, 6 Nov 2025 14:42:00 +0800 Subject: [PATCH 0757/3902] wifi: mt76: mt7996: fix MLD group index assignment [ Upstream commit 4fb3b4e7d1ca5453c6167816230370afc15f26bf ] Fix extender mode and MBSS issues caused by incorrect assignment of the MLD group and remap indices. Fixes: ed01c310eca9 ("wifi: mt76: mt7996: Fix mt7996_mcu_bss_mld_tlv routine") Signed-off-by: Shayne Chen Acked-by: Lorenzo Bianconi Link: https://patch.msgid.link/20251106064203.1000505-9-shayne.chen@mediatek.com Signed-off-by: Felix Fietkau Signed-off-by: Sasha Levin --- .../net/wireless/mediatek/mt76/mt7996/main.c | 58 +++++++++++++------ 1 file changed, 40 insertions(+), 18 deletions(-) diff --git a/drivers/net/wireless/mediatek/mt76/mt7996/main.c b/drivers/net/wireless/mediatek/mt76/mt7996/main.c index 4e738545895582..dc0fcf5cb7fb1b 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7996/main.c +++ b/drivers/net/wireless/mediatek/mt76/mt7996/main.c @@ -90,9 +90,11 @@ static void mt7996_stop(struct ieee80211_hw *hw, bool suspend) { } -static inline int get_free_idx(u32 mask, u8 start, u8 end) +static inline int get_free_idx(u64 mask, u8 start, u8 end) { - return ffs(~mask & GENMASK(end, start)); + if (~mask & GENMASK_ULL(end, start)) + return __ffs64(~mask & GENMASK_ULL(end, start)) + 1; + return 0; } static int get_omac_idx(enum nl80211_iftype type, u64 mask) @@ -308,12 +310,6 @@ int mt7996_vif_link_add(struct mt76_phy *mphy, struct ieee80211_vif *vif, if (idx < 0) return -ENOSPC; - if (!dev->mld_idx_mask) { /* first link in the group */ - mvif->mld_group_idx = get_own_mld_idx(dev->mld_idx_mask, true); - mvif->mld_remap_idx = get_free_idx(dev->mld_remap_idx_mask, - 0, 15); - } - mld_idx = get_own_mld_idx(dev->mld_idx_mask, false); if (mld_idx < 0) return -ENOSPC; @@ -331,10 +327,6 @@ int mt7996_vif_link_add(struct mt76_phy *mphy, struct ieee80211_vif *vif, return ret; dev->mt76.vif_mask |= BIT_ULL(mlink->idx); - if (!dev->mld_idx_mask) { - dev->mld_idx_mask |= BIT_ULL(mvif->mld_group_idx); - dev->mld_remap_idx_mask |= BIT_ULL(mvif->mld_remap_idx); - } dev->mld_idx_mask |= BIT_ULL(link->mld_idx); phy->omac_mask |= BIT_ULL(mlink->omac_idx); @@ -424,11 +416,6 @@ void mt7996_vif_link_remove(struct mt76_phy *mphy, struct ieee80211_vif *vif, dev->mt76.vif_mask &= ~BIT_ULL(mlink->idx); dev->mld_idx_mask &= ~BIT_ULL(link->mld_idx); phy->omac_mask &= ~BIT_ULL(mlink->omac_idx); - if (!(dev->mld_idx_mask & ~BIT_ULL(mvif->mld_group_idx))) { - /* last link */ - dev->mld_idx_mask &= ~BIT_ULL(mvif->mld_group_idx); - dev->mld_remap_idx_mask &= ~BIT_ULL(mvif->mld_remap_idx); - } spin_lock_bh(&dev->mt76.sta_poll_lock); if (!list_empty(&msta_link->wcid.poll_list)) @@ -2217,7 +2204,42 @@ mt7996_change_vif_links(struct ieee80211_hw *hw, struct ieee80211_vif *vif, u16 old_links, u16 new_links, struct ieee80211_bss_conf *old[IEEE80211_MLD_MAX_NUM_LINKS]) { - return 0; + struct mt7996_dev *dev = mt7996_hw_dev(hw); + struct mt7996_vif *mvif = (struct mt7996_vif *)vif->drv_priv; + int ret = 0; + + mutex_lock(&dev->mt76.mutex); + + if (!old_links) { + int idx; + + idx = get_own_mld_idx(dev->mld_idx_mask, true); + if (idx < 0) { + ret = -ENOSPC; + goto out; + } + mvif->mld_group_idx = idx; + dev->mld_idx_mask |= BIT_ULL(mvif->mld_group_idx); + + idx = get_free_idx(dev->mld_remap_idx_mask, 0, 15) - 1; + if (idx < 0) { + ret = -ENOSPC; + goto out; + } + mvif->mld_remap_idx = idx; + dev->mld_remap_idx_mask |= BIT_ULL(mvif->mld_remap_idx); + } + + if (new_links) + goto out; + + dev->mld_idx_mask &= ~BIT_ULL(mvif->mld_group_idx); + dev->mld_remap_idx_mask &= ~BIT_ULL(mvif->mld_remap_idx); + +out: + mutex_unlock(&dev->mt76.mutex); + + return ret; } static void From 7d6cd8f51ad8a632fdcf12fbdc93b93a27aa6ddf Mon Sep 17 00:00:00 2001 From: Shayne Chen Date: Thu, 6 Nov 2025 14:42:01 +0800 Subject: [PATCH 0758/3902] wifi: mt76: mt7996: fix MLO set key and group key issues [ Upstream commit e11be918d91e7d33ac4bad41dbe666a9abf1cfaa ] This patch fixes the following key issues: - Pass correct link BSS to mt7996_mcu_add_key(), and use HW beacon protection mode for mt7990 chipset - Do not do group key deletion for GTK and IGTK due to FW design, the delete key command will delete all group keys of a link BSS - For deleting BIGTK, FW adds a new flow, but the "sec->add" field should be filled with "SET_KEY". Note that if BIGTK is not deleted, it will cause beacon decryption issue when switching from an AP interface to a station interface Fixes: 0c45d52276fd ("wifi: mt76: mt7996: fix setting beacon protection keys") Co-developed-by: Allen Ye Signed-off-by: Allen Ye Co-developed-by: Peter Chiu Signed-off-by: Peter Chiu Signed-off-by: Shayne Chen Link: https://patch.msgid.link/20251106064203.1000505-10-shayne.chen@mediatek.com Signed-off-by: Felix Fietkau Signed-off-by: Sasha Levin --- .../net/wireless/mediatek/mt76/mt7996/mac.c | 7 +++- .../net/wireless/mediatek/mt76/mt7996/main.c | 5 +-- .../net/wireless/mediatek/mt76/mt7996/mcu.c | 35 +++++++++++++------ .../wireless/mediatek/mt76/mt7996/mt7996.h | 2 +- 4 files changed, 34 insertions(+), 15 deletions(-) diff --git a/drivers/net/wireless/mediatek/mt76/mt7996/mac.c b/drivers/net/wireless/mediatek/mt76/mt7996/mac.c index 284f2eea71e5bf..fe31db5440a844 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7996/mac.c +++ b/drivers/net/wireless/mediatek/mt76/mt7996/mac.c @@ -794,6 +794,7 @@ mt7996_mac_write_txwi_80211(struct mt7996_dev *dev, __le32 *txwi, u8 tid = skb->priority & IEEE80211_QOS_CTL_TID_MASK; __le16 fc = hdr->frame_control, sc = hdr->seq_ctrl; u16 seqno = le16_to_cpu(sc); + bool hw_bigtk = false; u8 fc_type, fc_stype; u32 val; @@ -819,7 +820,11 @@ mt7996_mac_write_txwi_80211(struct mt7996_dev *dev, __le32 *txwi, info->flags & IEEE80211_TX_CTL_USE_MINRATE) val |= MT_TXD1_FIXED_RATE; - if (key && multicast && ieee80211_is_robust_mgmt_frame(skb)) { + if (is_mt7990(&dev->mt76) && ieee80211_is_beacon(fc) && + (wcid->hw_key_idx2 == 6 || wcid->hw_key_idx2 == 7)) + hw_bigtk = true; + + if ((key && multicast && ieee80211_is_robust_mgmt_frame(skb)) || hw_bigtk) { val |= MT_TXD1_BIP; txwi[3] &= ~cpu_to_le32(MT_TXD3_PROTECT_FRAME); } diff --git a/drivers/net/wireless/mediatek/mt76/mt7996/main.c b/drivers/net/wireless/mediatek/mt76/mt7996/main.c index dc0fcf5cb7fb1b..beb455185f9043 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7996/main.c +++ b/drivers/net/wireless/mediatek/mt76/mt7996/main.c @@ -249,12 +249,13 @@ mt7996_set_hw_key(struct ieee80211_hw *hw, enum set_key_cmd cmd, else if (idx == *wcid_keyidx) *wcid_keyidx = -1; - if (cmd != SET_KEY && sta) + /* only do remove key for BIGTK */ + if (cmd != SET_KEY && !is_bigtk) return 0; mt76_wcid_key_setup(&dev->mt76, &msta_link->wcid, key); - err = mt7996_mcu_add_key(&dev->mt76, vif, key, + err = mt7996_mcu_add_key(&dev->mt76, link, key, MCU_WMWA_UNI_CMD(STA_REC_UPDATE), &msta_link->wcid, cmd); diff --git a/drivers/net/wireless/mediatek/mt76/mt7996/mcu.c b/drivers/net/wireless/mediatek/mt76/mt7996/mcu.c index 21be88e76c6553..5bde9959bbb99d 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7996/mcu.c +++ b/drivers/net/wireless/mediatek/mt76/mt7996/mcu.c @@ -2527,7 +2527,7 @@ int mt7996_mcu_teardown_mld_sta(struct mt7996_dev *dev, } static int -mt7996_mcu_sta_key_tlv(struct mt76_wcid *wcid, +mt7996_mcu_sta_key_tlv(struct mt76_dev *dev, struct mt76_wcid *wcid, struct sk_buff *skb, struct ieee80211_key_conf *key, enum set_key_cmd cmd) @@ -2539,7 +2539,10 @@ mt7996_mcu_sta_key_tlv(struct mt76_wcid *wcid, tlv = mt76_connac_mcu_add_tlv(skb, STA_REC_KEY_V2, sizeof(*sec)); sec = (struct sta_rec_sec_uni *)tlv; - sec->add = 0; + /* due to connac3 FW design, we only do remove key for BIGTK; even for + * removal, the field should be filled with SET_KEY + */ + sec->add = SET_KEY; sec->n_cipher = 1; sec_key = &sec->key[0]; sec_key->wlan_idx = cpu_to_le16(wcid->idx); @@ -2579,29 +2582,33 @@ mt7996_mcu_sta_key_tlv(struct mt76_wcid *wcid, case WLAN_CIPHER_SUITE_BIP_GMAC_256: sec_key->cipher_id = MCU_CIPHER_BCN_PROT_GMAC_256; break; + case WLAN_CIPHER_SUITE_BIP_CMAC_256: + if (!is_mt7990(dev)) + return -EOPNOTSUPP; + sec_key->cipher_id = MCU_CIPHER_BCN_PROT_CMAC_256; + break; default: return -EOPNOTSUPP; } - sec_key->bcn_mode = BP_SW_MODE; + sec_key->bcn_mode = is_mt7990(dev) ? BP_HW_MODE : BP_SW_MODE; return 0; } -int mt7996_mcu_add_key(struct mt76_dev *dev, struct ieee80211_vif *vif, +int mt7996_mcu_add_key(struct mt76_dev *dev, struct mt7996_vif_link *link, struct ieee80211_key_conf *key, int mcu_cmd, struct mt76_wcid *wcid, enum set_key_cmd cmd) { - struct mt76_vif_link *mvif = (struct mt76_vif_link *)vif->drv_priv; struct sk_buff *skb; int ret; - skb = __mt76_connac_mcu_alloc_sta_req(dev, mvif, wcid, - MT7996_STA_UPDATE_MAX_SIZE); + skb = __mt76_connac_mcu_alloc_sta_req(dev, (struct mt76_vif_link *)link, + wcid, MT7996_STA_UPDATE_MAX_SIZE); if (IS_ERR(skb)) return PTR_ERR(skb); - ret = mt7996_mcu_sta_key_tlv(wcid, skb, key, cmd); + ret = mt7996_mcu_sta_key_tlv(dev, wcid, skb, key, cmd); if (ret) { dev_kfree_skb(skb); return ret; @@ -2721,12 +2728,18 @@ mt7996_mcu_beacon_mbss(struct sk_buff *rskb, struct sk_buff *skb, static void mt7996_mcu_beacon_cont(struct mt7996_dev *dev, struct ieee80211_bss_conf *link_conf, + struct mt7996_vif_link *link, struct sk_buff *rskb, struct sk_buff *skb, struct bss_bcn_content_tlv *bcn, struct ieee80211_mutable_offsets *offs) { - struct mt76_wcid *wcid = &dev->mt76.global_wcid; - u8 *buf; + u8 *buf, keyidx = link->msta_link.wcid.hw_key_idx2; + struct mt76_wcid *wcid; + + if (is_mt7990(&dev->mt76) && (keyidx == 6 || keyidx == 7)) + wcid = &link->msta_link.wcid; + else + wcid = &dev->mt76.global_wcid; bcn->pkt_len = cpu_to_le16(MT_TXD_SIZE + skb->len); bcn->tim_ie_pos = cpu_to_le16(offs->tim_offset); @@ -2801,7 +2814,7 @@ int mt7996_mcu_add_beacon(struct ieee80211_hw *hw, struct ieee80211_vif *vif, info = IEEE80211_SKB_CB(skb); info->hw_queue |= FIELD_PREP(MT_TX_HW_QUEUE_PHY, mlink->band_idx); - mt7996_mcu_beacon_cont(dev, link_conf, rskb, skb, bcn, &offs); + mt7996_mcu_beacon_cont(dev, link_conf, link, rskb, skb, bcn, &offs); if (link_conf->bssid_indicator) mt7996_mcu_beacon_mbss(rskb, skb, bcn, &offs); mt7996_mcu_beacon_cntdwn(rskb, skb, &offs, link_conf->csa_active); diff --git a/drivers/net/wireless/mediatek/mt76/mt7996/mt7996.h b/drivers/net/wireless/mediatek/mt76/mt7996/mt7996.h index 1727e73a99ce69..b942928c79e280 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7996/mt7996.h +++ b/drivers/net/wireless/mediatek/mt76/mt7996/mt7996.h @@ -848,7 +848,7 @@ void mt7996_update_channel(struct mt76_phy *mphy); int mt7996_init_debugfs(struct mt7996_dev *dev); void mt7996_debugfs_rx_fw_monitor(struct mt7996_dev *dev, const void *data, int len); bool mt7996_debugfs_rx_log(struct mt7996_dev *dev, const void *data, int len); -int mt7996_mcu_add_key(struct mt76_dev *dev, struct ieee80211_vif *vif, +int mt7996_mcu_add_key(struct mt76_dev *dev, struct mt7996_vif_link *link, struct ieee80211_key_conf *key, int mcu_cmd, struct mt76_wcid *wcid, enum set_key_cmd cmd); int mt7996_mcu_bcn_prot_enable(struct mt7996_dev *dev, From 1a4b481a21ae3a84b3862963091698d299d8e63c Mon Sep 17 00:00:00 2001 From: Shayne Chen Date: Thu, 6 Nov 2025 14:42:02 +0800 Subject: [PATCH 0759/3902] wifi: mt76: mt7996: fix using wrong phy to start in mt7996_mac_restart() [ Upstream commit f1e9f369ae42ee433836b24467e645192d046a51 ] Pass the correct mt7996_phy to mt7996_run(). Fixes: 0a5df0ec47f7 ("wifi: mt76: mt7996: remove redundant per-phy mac80211 calls during restart") Signed-off-by: Shayne Chen Acked-by: Lorenzo Bianconi Link: https://patch.msgid.link/20251106064203.1000505-11-shayne.chen@mediatek.com Signed-off-by: Felix Fietkau Signed-off-by: Sasha Levin --- drivers/net/wireless/mediatek/mt76/mt7996/mac.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/net/wireless/mediatek/mt76/mt7996/mac.c b/drivers/net/wireless/mediatek/mt76/mt7996/mac.c index fe31db5440a844..b06728a98a6917 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7996/mac.c +++ b/drivers/net/wireless/mediatek/mt76/mt7996/mac.c @@ -2341,7 +2341,7 @@ mt7996_mac_restart(struct mt7996_dev *dev) if (!test_bit(MT76_STATE_RUNNING, &phy->mt76->state)) continue; - ret = mt7996_run(&dev->phy); + ret = mt7996_run(phy); if (ret) goto out; } From b4b789d24319e9068bcb785f2ef6f8d487e2188b Mon Sep 17 00:00:00 2001 From: Shayne Chen Date: Thu, 6 Nov 2025 14:42:03 +0800 Subject: [PATCH 0760/3902] wifi: mt76: mt7996: fix EMI rings for RRO [ Upstream commit a4031fec9d0d230224a7edcefa3368c06c317148 ] The RRO EMI rings only need to be allocated when WED is not active. This patch fixes command timeout issue for the setting of WED off and RRO on. Fixes: 3a29164425e9 ("wifi: mt76: mt7996: Add SW path for HW-RRO v3.1") Co-developed-by: Rex Lu Signed-off-by: Rex Lu Signed-off-by: Shayne Chen Acked-by: Lorenzo Bianconi Link: https://patch.msgid.link/20251106064203.1000505-12-shayne.chen@mediatek.com Signed-off-by: Felix Fietkau Signed-off-by: Sasha Levin --- .../net/wireless/mediatek/mt76/mt7996/dma.c | 15 +++++++++------ .../net/wireless/mediatek/mt76/mt7996/init.c | 19 ++++++++++++++++--- 2 files changed, 25 insertions(+), 9 deletions(-) diff --git a/drivers/net/wireless/mediatek/mt76/mt7996/dma.c b/drivers/net/wireless/mediatek/mt76/mt7996/dma.c index 659015f93d3238..7ed2f21b0e6db4 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7996/dma.c +++ b/drivers/net/wireless/mediatek/mt76/mt7996/dma.c @@ -512,12 +512,15 @@ int mt7996_dma_rro_init(struct mt7996_dev *dev) if (ret) return ret; - /* We need to set cpu idx pointer before resetting the EMI - * queues. - */ - mdev->q_rx[MT_RXQ_RRO_RXDMAD_C].emi_cpu_idx = - &dev->wed_rro.emi_rings_cpu.ptr->ring[0].idx; - mt76_queue_reset(dev, &mdev->q_rx[MT_RXQ_RRO_RXDMAD_C], true); + if (!mtk_wed_device_active(&mdev->mmio.wed)) { + /* We need to set cpu idx pointer before resetting the + * EMI queues. + */ + mdev->q_rx[MT_RXQ_RRO_RXDMAD_C].emi_cpu_idx = + &dev->wed_rro.emi_rings_cpu.ptr->ring[0].idx; + mt76_queue_reset(dev, &mdev->q_rx[MT_RXQ_RRO_RXDMAD_C], + true); + } goto start_hw_rro; } diff --git a/drivers/net/wireless/mediatek/mt76/mt7996/init.c b/drivers/net/wireless/mediatek/mt76/mt7996/init.c index 5e95a36b42d164..b136b9a6697690 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7996/init.c +++ b/drivers/net/wireless/mediatek/mt76/mt7996/init.c @@ -959,9 +959,10 @@ static int mt7996_wed_rro_init(struct mt7996_dev *dev) MT7996_RRO_MSDU_PG_SIZE_PER_CR); } - if (dev->mt76.hwrro_mode == MT76_HWRRO_V3_1) { + if (!mtk_wed_device_active(&dev->mt76.mmio.wed) && + dev->mt76.hwrro_mode == MT76_HWRRO_V3_1) { ptr = dmam_alloc_coherent(dev->mt76.dma_dev, - sizeof(dev->wed_rro.emi_rings_cpu.ptr), + sizeof(*dev->wed_rro.emi_rings_cpu.ptr), &dev->wed_rro.emi_rings_cpu.phy_addr, GFP_KERNEL); if (!ptr) @@ -970,7 +971,7 @@ static int mt7996_wed_rro_init(struct mt7996_dev *dev) dev->wed_rro.emi_rings_cpu.ptr = ptr; ptr = dmam_alloc_coherent(dev->mt76.dma_dev, - sizeof(dev->wed_rro.emi_rings_dma.ptr), + sizeof(*dev->wed_rro.emi_rings_dma.ptr), &dev->wed_rro.emi_rings_dma.phy_addr, GFP_KERNEL); if (!ptr) @@ -1036,6 +1037,18 @@ static void mt7996_wed_rro_free(struct mt7996_dev *dev) dev->wed_rro.msdu_pg[i].phy_addr); } + if (dev->wed_rro.emi_rings_cpu.ptr) + dmam_free_coherent(dev->mt76.dma_dev, + sizeof(*dev->wed_rro.emi_rings_cpu.ptr), + dev->wed_rro.emi_rings_cpu.ptr, + dev->wed_rro.emi_rings_cpu.phy_addr); + + if (dev->wed_rro.emi_rings_dma.ptr) + dmam_free_coherent(dev->mt76.dma_dev, + sizeof(*dev->wed_rro.emi_rings_dma.ptr), + dev->wed_rro.emi_rings_dma.ptr, + dev->wed_rro.emi_rings_dma.phy_addr); + if (!dev->wed_rro.session.ptr) return; From 46f0648e261c3775194bed3eb764d37e95429a90 Mon Sep 17 00:00:00 2001 From: Lorenzo Bianconi Date: Fri, 14 Nov 2025 14:16:21 +0100 Subject: [PATCH 0761/3902] wifi: mt76: mt7996: grab mt76 mutex in mt7996_mac_sta_event() [ Upstream commit 5a4bcba26e9fbea87507a81ad891e70bb525014f ] Grab mt76 mutex in mt7996_mac_sta_event routine in order to rely on mt76_dereference() utility macro. Fixes: ecd72f9695e7e ("wifi: mt76: mt7996: Support MLO in mt7996_mac_sta_event()") Signed-off-by: Lorenzo Bianconi Tested-by: Ben Greear Link: https://patch.msgid.link/20251114-mt76-fix-missing-mtx-v1-1-259ebf11f654@kernel.org Signed-off-by: Felix Fietkau Signed-off-by: Sasha Levin --- drivers/net/wireless/mediatek/mt76/mt7996/main.c | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/drivers/net/wireless/mediatek/mt76/mt7996/main.c b/drivers/net/wireless/mediatek/mt76/mt7996/main.c index beb455185f9043..ead56ce4c0362e 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7996/main.c +++ b/drivers/net/wireless/mediatek/mt76/mt7996/main.c @@ -1150,12 +1150,15 @@ mt7996_mac_sta_event(struct mt7996_dev *dev, struct ieee80211_vif *vif, unsigned long links = sta->valid_links; struct ieee80211_link_sta *link_sta; unsigned int link_id; + int err = 0; + + mutex_lock(&dev->mt76.mutex); for_each_sta_active_link(vif, sta, link_sta, link_id) { struct ieee80211_bss_conf *link_conf; struct mt7996_sta_link *msta_link; struct mt7996_vif_link *link; - int i, err; + int i; link_conf = link_conf_dereference_protected(vif, link_id); if (!link_conf) @@ -1175,12 +1178,12 @@ mt7996_mac_sta_event(struct mt7996_dev *dev, struct ieee80211_vif *vif, link, msta_link, CONN_STATE_CONNECT, true); if (err) - return err; + goto unlock; err = mt7996_mcu_add_rate_ctrl(dev, msta_link->sta, vif, link_id, false); if (err) - return err; + goto unlock; msta_link->wcid.tx_info |= MT_WCID_TX_INFO_SET; break; @@ -1189,7 +1192,7 @@ mt7996_mac_sta_event(struct mt7996_dev *dev, struct ieee80211_vif *vif, link, msta_link, CONN_STATE_PORT_SECURE, false); if (err) - return err; + goto unlock; break; case MT76_STA_EVENT_DISASSOC: for (i = 0; i < ARRAY_SIZE(msta_link->twt.flow); i++) @@ -1209,8 +1212,10 @@ mt7996_mac_sta_event(struct mt7996_dev *dev, struct ieee80211_vif *vif, break; } } +unlock: + mutex_unlock(&dev->mt76.mutex); - return 0; + return err; } static void From 70656b45467ce09b9938e9b2e06442c853ba9e63 Mon Sep 17 00:00:00 2001 From: Lorenzo Bianconi Date: Fri, 14 Nov 2025 14:16:23 +0100 Subject: [PATCH 0762/3902] wifi: mt76: Move mt76_abort_scan out of mt76_reset_device() [ Upstream commit 6aaaaeacf18b2dc2b0f78f241800e0ea680938c7 ] Move mt76_abort_scan routine out of mt76_reset_device() in order to avoid a possible deadlock since mt76_reset_device routine is running with mt76 mutex help and mt76_abort_scan_complete() can grab mt76 mutex in some cases. Fixes: b36d55610215a ("wifi: mt76: abort scan/roc on hw restart") Signed-off-by: Lorenzo Bianconi Tested-by: Ben Greear Link: https://patch.msgid.link/20251114-mt76-fix-missing-mtx-v1-3-259ebf11f654@kernel.org Signed-off-by: Felix Fietkau Signed-off-by: Sasha Levin --- drivers/net/wireless/mediatek/mt76/mac80211.c | 2 -- drivers/net/wireless/mediatek/mt76/mt7915/mac.c | 2 ++ drivers/net/wireless/mediatek/mt76/mt7996/mac.c | 2 ++ 3 files changed, 4 insertions(+), 2 deletions(-) diff --git a/drivers/net/wireless/mediatek/mt76/mac80211.c b/drivers/net/wireless/mediatek/mt76/mac80211.c index 5ceaf78c9ea064..5e75861bf6f990 100644 --- a/drivers/net/wireless/mediatek/mt76/mac80211.c +++ b/drivers/net/wireless/mediatek/mt76/mac80211.c @@ -847,8 +847,6 @@ void mt76_reset_device(struct mt76_dev *dev) } rcu_read_unlock(); - mt76_abort_scan(dev); - INIT_LIST_HEAD(&dev->wcid_list); INIT_LIST_HEAD(&dev->sta_poll_list); dev->vif_mask = 0; diff --git a/drivers/net/wireless/mediatek/mt76/mt7915/mac.c b/drivers/net/wireless/mediatek/mt76/mt7915/mac.c index 1c0d310146d63b..5caf818e828343 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7915/mac.c +++ b/drivers/net/wireless/mediatek/mt76/mt7915/mac.c @@ -1451,6 +1451,8 @@ mt7915_mac_full_reset(struct mt7915_dev *dev) if (ext_phy) cancel_delayed_work_sync(&ext_phy->mac_work); + mt76_abort_scan(&dev->mt76); + mutex_lock(&dev->mt76.mutex); for (i = 0; i < 10; i++) { if (!mt7915_mac_restart(dev)) diff --git a/drivers/net/wireless/mediatek/mt76/mt7996/mac.c b/drivers/net/wireless/mediatek/mt76/mt7996/mac.c index b06728a98a6917..cfad46a532bb79 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7996/mac.c +++ b/drivers/net/wireless/mediatek/mt76/mt7996/mac.c @@ -2424,6 +2424,8 @@ mt7996_mac_full_reset(struct mt7996_dev *dev) mt7996_for_each_phy(dev, phy) cancel_delayed_work_sync(&phy->mt76->mac_work); + mt76_abort_scan(&dev->mt76); + mutex_lock(&dev->mt76.mutex); for (i = 0; i < 10; i++) { if (!mt7996_mac_restart(dev)) From c319967690d4257c6ed76bea1c928a6f6498facc Mon Sep 17 00:00:00 2001 From: Lorenzo Bianconi Date: Fri, 14 Nov 2025 14:16:24 +0100 Subject: [PATCH 0763/3902] wifi: mt76: mt7996: skip deflink accounting for offchannel links [ Upstream commit 4fe823b9ee0317b04ddc6d9e00fea892498aa0f2 ] Do not take into account offchannel links for deflink accounting. Fixes: a3316d2fc669f ("wifi: mt76: mt7996: set vif default link_id adding/removing vif links") Signed-off-by: Lorenzo Bianconi Tested-by: Ben Greear Link: https://patch.msgid.link/20251114-mt76-fix-missing-mtx-v1-4-259ebf11f654@kernel.org Signed-off-by: Felix Fietkau Signed-off-by: Sasha Levin --- drivers/net/wireless/mediatek/mt76/mt7996/main.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/drivers/net/wireless/mediatek/mt76/mt7996/main.c b/drivers/net/wireless/mediatek/mt76/mt7996/main.c index ead56ce4c0362e..5ff7ab596f88c5 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7996/main.c +++ b/drivers/net/wireless/mediatek/mt76/mt7996/main.c @@ -370,7 +370,8 @@ int mt7996_vif_link_add(struct mt76_phy *mphy, struct ieee80211_vif *vif, ieee80211_iter_keys(mphy->hw, vif, mt7996_key_iter, &it); - if (mvif->mt76.deflink_id == IEEE80211_LINK_UNSPECIFIED) + if (!mlink->wcid->offchannel && + mvif->mt76.deflink_id == IEEE80211_LINK_UNSPECIFIED) mvif->mt76.deflink_id = link_conf->link_id; return 0; @@ -401,7 +402,8 @@ void mt7996_vif_link_remove(struct mt76_phy *mphy, struct ieee80211_vif *vif, rcu_assign_pointer(dev->mt76.wcid[idx], NULL); - if (mvif->mt76.deflink_id == link_conf->link_id) { + if (!mlink->wcid->offchannel && + mvif->mt76.deflink_id == link_conf->link_id) { struct ieee80211_bss_conf *iter; unsigned int link_id; From abdedd46dc58c1bc3428c8eb430026591cd9f74b Mon Sep 17 00:00:00 2001 From: Lorenzo Bianconi Date: Sat, 15 Nov 2025 11:41:00 +0100 Subject: [PATCH 0764/3902] wifi: mt76: mt7996: skip ieee80211_iter_keys() on scanning link remove [ Upstream commit 2a432a6d0066d4ce05a2d0eec1da9e061eb70c49 ] mt7996_vif_link_remove routine is executed by mt76_scan_complete() without holding the wiphy mutex triggering the following lockdep warning. WARNING: CPU: 0 PID: 72 at net/mac80211/key.c:1029 ieee80211_iter_keys+0xe4/0x1a0 [mac80211] CPU: 0 UID: 0 PID: 72 Comm: kworker/u32:2 Tainted: G S 6.18.0-rc5+ #27 PREEMPT(full) Tainted: [S]=CPU_OUT_OF_SPEC Hardware name: Default string Default string/SKYBAY, BIOS 5.12 02/15/2023 Workqueue: phy3 mt76_scan_work [mt76] RIP: 0010:ieee80211_iter_keys+0xe4/0x1a0 [mac80211] Code: 4c 48 83 c4 10 5b 5d 41 5c 41 5d 41 5e 41 5f c3 48 8b 47 48 be ff ff ff ff 48 8d 78 68 e8 b4 eb 1e e1 85 c0 0f 85 49 ff ff ff 4c 8b ab 90 1a 00 00 48 8d 83 90 RSP: 0018:ffffc900002f7cb0 EFLAGS: 00010246 RAX: 0000000000000000 RBX: ffff888127e00ee0 RCX: 0000000000000000 RDX: 0000000000000000 RSI: ffff888127e00788 RDI: ffff88811132b5c8 RBP: ffffffffa0ddf400 R08: 0000000000000001 R09: 000000009dcc1dac R10: 0000000000000001 R11: ffff88811132b5a0 R12: ffffc900002f7d00 R13: ffff8882581e6a80 R14: ffff888127e0afc8 R15: ffff888158832038 FS: 0000000000000000(0000) GS:ffff8884da486000(0000) knlGS:0000000000000000 CS: 0010 DS: 0000 ES: 0000 CR0: 0000000080050033 CR2: 0000000030a0fd90 CR3: 0000000002c52004 CR4: 00000000003706f0 Call Trace: ? lock_acquire+0xc2/0x2c0 mt7996_vif_link_remove+0x64/0x2b0 [mt7996e] mt76_put_vif_phy_link+0x41/0x50 [mt76] mt76_scan_complete+0x77/0x100 [mt76] mt76_scan_work+0x2eb/0x3f0 [mt76] ? process_one_work+0x1e5/0x6d0 process_one_work+0x221/0x6d0 worker_thread+0x19a/0x340 ? rescuer_thread+0x450/0x450 kthread+0x108/0x220 ? kthreads_online_cpu+0x110/0x110 ret_from_fork+0x1c6/0x220 ? kthreads_online_cpu+0x110/0x110 ret_from_fork_asm+0x11/0x20 irq event stamp: 45471 hardirqs last enabled at (45477): [] __up_console_sem+0x5e/0x70 hardirqs last disabled at (45482): [] __up_console_sem+0x43/0x70 softirqs last enabled at (44500): [] napi_pp_put_page+0xac/0xd0 softirqs last disabled at (44498): [] page_pool_put_unrefed_netmem+0x290/0x3d0 ---[ end trace 0000000000000000 ]--- Fix the issue skipping ieee80211_iter_keys() for scanning links in mt7996_vif_link_remove routine since we have not uploaded any hw keys for these links. Fixes: 04414d7bba78 ("wifi: mt76: mt7996: delete vif keys when requested") Signed-off-by: Lorenzo Bianconi Tested-by: Ben Greear Link: https://patch.msgid.link/20251115-mt7996-key-iter-link-remove-fix-v1-1-4f3f4e1eaa78@kernel.org Signed-off-by: Felix Fietkau Signed-off-by: Sasha Levin --- drivers/net/wireless/mediatek/mt76/mt7996/main.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/drivers/net/wireless/mediatek/mt76/mt7996/main.c b/drivers/net/wireless/mediatek/mt76/mt7996/main.c index 5ff7ab596f88c5..2ad52ae2c5f55c 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7996/main.c +++ b/drivers/net/wireless/mediatek/mt76/mt7996/main.c @@ -392,7 +392,8 @@ void mt7996_vif_link_remove(struct mt76_phy *mphy, struct ieee80211_vif *vif, }; int idx = msta_link->wcid.idx; - ieee80211_iter_keys(mphy->hw, vif, mt7996_key_iter, &it); + if (!mlink->wcid->offchannel) + ieee80211_iter_keys(mphy->hw, vif, mt7996_key_iter, &it); mt7996_mcu_add_sta(dev, link_conf, NULL, link, NULL, CONN_STATE_DISCONNECT, false); From f4c57afff3b096e7237c51b9a55f52fd311e3348 Mon Sep 17 00:00:00 2001 From: Lorenzo Bianconi Date: Tue, 18 Nov 2025 10:30:26 +0100 Subject: [PATCH 0765/3902] wifi: mt76: mt7996: Add missing locking in mt7996_mac_sta_rc_work() [ Upstream commit 7545551631fa63101f97974f49ac0b564814f703 ] Grab the mt76 mutex running mt7996_mac_sta_rc_work() since it is required by mt7996_mcu_add_rate_ctrl routine. Fixes: 28d519d0d493a ("wifi: mt76: Move RCU section in mt7996_mcu_add_rate_ctrl_fixed()") Signed-off-by: Lorenzo Bianconi Link: https://patch.msgid.link/20251118-mt7996-rc-work-missing-mtx-v1-1-0739c493a6cb@kernel.org Signed-off-by: Felix Fietkau Signed-off-by: Sasha Levin --- drivers/net/wireless/mediatek/mt76/mt7996/mac.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/drivers/net/wireless/mediatek/mt76/mt7996/mac.c b/drivers/net/wireless/mediatek/mt76/mt7996/mac.c index cfad46a532bb79..502136691a69e4 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7996/mac.c +++ b/drivers/net/wireless/mediatek/mt76/mt7996/mac.c @@ -2860,6 +2860,8 @@ void mt7996_mac_sta_rc_work(struct work_struct *work) LIST_HEAD(list); u32 changed; + mutex_lock(&dev->mt76.mutex); + spin_lock_bh(&dev->mt76.sta_poll_lock); list_splice_init(&dev->sta_rc_list, &list); @@ -2892,6 +2894,8 @@ void mt7996_mac_sta_rc_work(struct work_struct *work) } spin_unlock_bh(&dev->mt76.sta_poll_lock); + + mutex_unlock(&dev->mt76.mutex); } void mt7996_mac_work(struct work_struct *work) From 4d42aba0ee49c0aa015c50c4f2a07cf8fa1c3a49 Mon Sep 17 00:00:00 2001 From: Zilin Guan Date: Thu, 13 Nov 2025 06:24:15 +0000 Subject: [PATCH 0766/3902] mt76: mt7615: Fix memory leak in mt7615_mcu_wtbl_sta_add() [ Upstream commit 53d1548612670aa8b5d89745116cc33d9d172863 ] In mt7615_mcu_wtbl_sta_add(), an skb sskb is allocated. If the subsequent call to mt76_connac_mcu_alloc_wtbl_req() fails, the function returns an error without freeing sskb, leading to a memory leak. Fix this by calling dev_kfree_skb() on sskb in the error handling path to ensure it is properly released. Fixes: 99c457d902cf9 ("mt76: mt7615: move mt7615_mcu_set_bmc to mt7615_mcu_ops") Signed-off-by: Zilin Guan Acked-by: Lorenzo Bianconi Link: https://patch.msgid.link/20251113062415.103611-1-zilin@seu.edu.cn Signed-off-by: Felix Fietkau Signed-off-by: Sasha Levin --- drivers/net/wireless/mediatek/mt76/mt7615/mcu.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/drivers/net/wireless/mediatek/mt76/mt7615/mcu.c b/drivers/net/wireless/mediatek/mt76/mt7615/mcu.c index 4064e193d4dec6..08ee2e861c4e21 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7615/mcu.c +++ b/drivers/net/wireless/mediatek/mt76/mt7615/mcu.c @@ -874,8 +874,10 @@ mt7615_mcu_wtbl_sta_add(struct mt7615_phy *phy, struct ieee80211_vif *vif, wtbl_hdr = mt76_connac_mcu_alloc_wtbl_req(&dev->mt76, &msta->wcid, WTBL_RESET_AND_SET, NULL, &wskb); - if (IS_ERR(wtbl_hdr)) + if (IS_ERR(wtbl_hdr)) { + dev_kfree_skb(sskb); return PTR_ERR(wtbl_hdr); + } if (enable) { mt76_connac_mcu_wtbl_generic_tlv(&dev->mt76, wskb, vif, sta, From 1bdf06f25a497bfb764e53dafb74de2a4b6e330b Mon Sep 17 00:00:00 2001 From: Dinh Nguyen Date: Fri, 14 Nov 2025 12:58:13 -0600 Subject: [PATCH 0767/3902] firmware: stratix10-svc: fix make htmldocs warning for stratix10_svc [ Upstream commit 377441d53a2df61b105e823b335010cd4f1a6e56 ] Fix this warning that was generated from "make htmldocs": WARNING: drivers/firmware/stratix10-svc.c:58 struct member 'intel_svc_fcs' not described in 'stratix10_svc' Fixes: e6281c26674e ("firmware: stratix10-svc: Add support for FCS") Reported-by: Stephen Rothwell Closes: https://lore.kernel.org/linux-next/20251106145941.37920e97@canb.auug.org.au/ Signed-off-by: Dinh Nguyen Link: https://patch.msgid.link/20251114185815.358423-1-dinguyen@kernel.org Signed-off-by: Greg Kroah-Hartman Signed-off-by: Sasha Levin --- drivers/firmware/stratix10-svc.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/firmware/stratix10-svc.c b/drivers/firmware/stratix10-svc.c index 00f58e27f6de53..deee0e7be34bd1 100644 --- a/drivers/firmware/stratix10-svc.c +++ b/drivers/firmware/stratix10-svc.c @@ -52,6 +52,7 @@ struct stratix10_svc_chan; /** * struct stratix10_svc - svc private data * @stratix10_svc_rsu: pointer to stratix10 RSU device + * @intel_svc_fcs: pointer to the FCS device */ struct stratix10_svc { struct platform_device *stratix10_svc_rsu; From 56296da5e6bf558cd8b91108a356cbd7cd35adbf Mon Sep 17 00:00:00 2001 From: Jianglei Nie Date: Wed, 12 Nov 2025 20:22:07 +0100 Subject: [PATCH 0768/3902] staging: fbtft: core: fix potential memory leak in fbtft_probe_common() [ Upstream commit 47d3949a9b04cbcb0e10abae30c2b53e98706e11 ] fbtft_probe_common() allocates a memory chunk for "info" with fbtft_framebuffer_alloc(). When "display->buswidth == 0" is true, the function returns without releasing the "info", which will lead to a memory leak. Fix it by calling fbtft_framebuffer_release() when "display->buswidth == 0" is true. Fixes: c296d5f9957c ("staging: fbtft: core support") Signed-off-by: Jianglei Nie Signed-off-by: Andy Shevchenko Acked-by: Abdun Nihaal Link: https://patch.msgid.link/20251112192235.2088654-1-andriy.shevchenko@linux.intel.com Signed-off-by: Greg Kroah-Hartman Signed-off-by: Sasha Levin --- drivers/staging/fbtft/fbtft-core.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/staging/fbtft/fbtft-core.c b/drivers/staging/fbtft/fbtft-core.c index 9e7b84071174cf..8a5ccc8ae0a188 100644 --- a/drivers/staging/fbtft/fbtft-core.c +++ b/drivers/staging/fbtft/fbtft-core.c @@ -1171,8 +1171,8 @@ int fbtft_probe_common(struct fbtft_display *display, par->pdev = pdev; if (display->buswidth == 0) { - dev_err(dev, "buswidth is not set\n"); - return -EINVAL; + ret = dev_err_probe(dev, -EINVAL, "buswidth is not set\n"); + goto out_release; } /* write register functions */ From 3d12cc5d13f6dc98b128e5c666417b6e2df6406a Mon Sep 17 00:00:00 2001 From: Ryan Huang Date: Fri, 7 Nov 2025 11:09:17 -0800 Subject: [PATCH 0769/3902] iommu/arm-smmu-v3: Fix error check in arm_smmu_alloc_cd_tables [ Upstream commit 5941f0e0c1e0be03ebc15b461f64208f5250d3d9 ] In arm_smmu_alloc_cd_tables(), the error check following the dma_alloc_coherent() for cd_table->l2.l1tab incorrectly tests cd_table->l2.l2ptrs. This means an allocation failure for l1tab goes undetected, causing the function to return 0 (success) erroneously. Correct the check to test cd_table->l2.l1tab. Fixes: e3b1be2e73db ("iommu/arm-smmu-v3: Reorganize struct arm_smmu_ctx_desc_cfg") Signed-off-by: Daniel Mentz Signed-off-by: Ryan Huang Reviewed-by: Nicolin Chen Reviewed-by: Pranjal Shrivastava Reviewed-by: Jason Gunthorpe Signed-off-by: Will Deacon Signed-off-by: Sasha Levin --- drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c b/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c index 2a8b46b948f050..9780f40ba3e654 100644 --- a/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c +++ b/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c @@ -1464,7 +1464,7 @@ static int arm_smmu_alloc_cd_tables(struct arm_smmu_master *master) cd_table->l2.l1tab = dma_alloc_coherent(smmu->dev, l1size, &cd_table->cdtab_dma, GFP_KERNEL); - if (!cd_table->l2.l2ptrs) { + if (!cd_table->l2.l1tab) { ret = -ENOMEM; goto err_free_l2ptrs; } From 489a8f559fafe6a72f44aeff81de44259d5d37fe Mon Sep 17 00:00:00 2001 From: Alan Maguire Date: Thu, 20 Nov 2025 08:47:53 +0000 Subject: [PATCH 0770/3902] bpftool: Allow bpftool to build with openssl < 3 [ Upstream commit 90ae54b4c7eca42d5ce006dd0a8cb0b5bfbf80d0 ] ERR_get_error_all()[1] is a openssl v3 API, so to make code compatible with openssl v1 utilize ERR_get_err_line_data instead. Since openssl is already a build requirement for the kernel (minimum requirement openssl 1.0.0), this will allow bpftool to compile where opensslv3 is not available. Signing-related BPF selftests pass with openssl v1. [1] https://docs.openssl.org/3.4/man3/ERR_get_error/ Fixes: 40863f4d6ef2 ("bpftool: Add support for signing BPF programs") Signed-off-by: Alan Maguire Acked-by: Song Liu Acked-by: Quentin Monnet Link: https://lore.kernel.org/r/20251120084754.640405-2-alan.maguire@oracle.com Signed-off-by: Alexei Starovoitov Signed-off-by: Sasha Levin --- tools/bpf/bpftool/sign.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/tools/bpf/bpftool/sign.c b/tools/bpf/bpftool/sign.c index b34f74d210e9cd..f9b742f4bb104b 100644 --- a/tools/bpf/bpftool/sign.c +++ b/tools/bpf/bpftool/sign.c @@ -28,6 +28,12 @@ #define OPEN_SSL_ERR_BUF_LEN 256 +/* Use deprecated in 3.0 ERR_get_error_line_data for openssl < 3 */ +#if !defined(OPENSSL_VERSION_MAJOR) || (OPENSSL_VERSION_MAJOR < 3) +#define ERR_get_error_all(file, line, func, data, flags) \ + ERR_get_error_line_data(file, line, data, flags) +#endif + static void display_openssl_errors(int l) { char buf[OPEN_SSL_ERR_BUF_LEN]; From 3b796d3f16c108fb287f7eb5f4eb66c8770898e7 Mon Sep 17 00:00:00 2001 From: Alan Maguire Date: Thu, 20 Nov 2025 08:47:54 +0000 Subject: [PATCH 0771/3902] selftests/bpf: Allow selftests to build with older xxd [ Upstream commit ad93ba02678eda5fc8e259cf4b52997e6fa570cf ] Currently selftests require xxd with the "-n " option which allows the user to specify a name not derived from the input object path. Instead of relying on this newer feature, older xxd can be used if we link our desired name ("test_progs_verification_cert") to the input object. Many distros ship xxd in vim-common package and do not have the latest xxd with -n support. Fixes: b720903e2b14d ("selftests/bpf: Enable signature verification for some lskel tests") Signed-off-by: Alan Maguire Link: https://lore.kernel.org/r/20251120084754.640405-3-alan.maguire@oracle.com Signed-off-by: Alexei Starovoitov Signed-off-by: Sasha Levin --- tools/testing/selftests/bpf/.gitignore | 1 + tools/testing/selftests/bpf/Makefile | 6 ++++-- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/tools/testing/selftests/bpf/.gitignore b/tools/testing/selftests/bpf/.gitignore index be1ee7ba7ce031..ca557e5668fd84 100644 --- a/tools/testing/selftests/bpf/.gitignore +++ b/tools/testing/selftests/bpf/.gitignore @@ -23,6 +23,7 @@ test_tcpnotify_user test_libbpf xdping test_cpp +test_progs_verification_cert *.d *.subskel.h *.skel.h diff --git a/tools/testing/selftests/bpf/Makefile b/tools/testing/selftests/bpf/Makefile index f00587d4ede68e..e59b2bbf8d9204 100644 --- a/tools/testing/selftests/bpf/Makefile +++ b/tools/testing/selftests/bpf/Makefile @@ -721,7 +721,8 @@ $(VERIFICATION_CERT) $(PRIVATE_KEY): $(VERIFY_SIG_SETUP) $(Q)$(VERIFY_SIG_SETUP) genkey $(BUILD_DIR) $(VERIFY_SIG_HDR): $(VERIFICATION_CERT) - $(Q)xxd -i -n test_progs_verification_cert $< > $@ + $(Q)ln -fs $< test_progs_verification_cert && \ + xxd -i test_progs_verification_cert > $@ # Define test_progs test runner. TRUNNER_TESTS_DIR := prog_tests @@ -893,7 +894,8 @@ EXTRA_CLEAN := $(SCRATCH_DIR) $(HOST_SCRATCH_DIR) \ $(addprefix $(OUTPUT)/,*.o *.d *.skel.h *.lskel.h *.subskel.h \ no_alu32 cpuv4 bpf_gcc \ liburandom_read.so) \ - $(OUTPUT)/FEATURE-DUMP.selftests + $(OUTPUT)/FEATURE-DUMP.selftests \ + test_progs_verification_cert .PHONY: docs docs-clean From 364685c4c2d9c9f4408d95451bcf42fdeebc3ebb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miquel=20Sabat=C3=A9=20Sol=C3=A0?= Date: Wed, 1 Oct 2025 20:05:03 +0200 Subject: [PATCH 0772/3902] btrfs: fix double free of qgroup record after failure to add delayed ref head MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [ Upstream commit 725e46298876a2cc1f1c3fb22ba69d29102c3ddf ] In the previous code it was possible to incur into a double kfree() scenario when calling add_delayed_ref_head(). This could happen if the record was reported to already exist in the btrfs_qgroup_trace_extent_nolock() call, but then there was an error later on add_delayed_ref_head(). In this case, since add_delayed_ref_head() returned an error, the caller went to free the record. Since add_delayed_ref_head() couldn't set this kfree'd pointer to NULL, then kfree() would have acted on a non-NULL 'record' object which was pointing to memory already freed by the callee. The problem comes from the fact that the responsibility to kfree the object is on both the caller and the callee at the same time. Hence, the fix for this is to shift the ownership of the 'qrecord' object out of the add_delayed_ref_head(). That is, we will never attempt to kfree() the given object inside of this function, and will expect the caller to act on the 'qrecord' object on its own. The only exception where the 'qrecord' object cannot be kfree'd is if it was inserted into the tracing logic, for which we already have the 'qrecord_inserted_ret' boolean to account for this. Hence, the caller has to kfree the object only if add_delayed_ref_head() reports not to have inserted it on the tracing logic. As a side-effect of the above, we must guarantee that 'qrecord_inserted_ret' is properly initialized at the start of the function, not at the end, and then set when an actual insert happens. This way we avoid 'qrecord_inserted_ret' having an invalid value on an early exit. The documentation from the add_delayed_ref_head() has also been updated to reflect on the exact ownership of the 'qrecord' object. Fixes: 6ef8fbce0104 ("btrfs: fix missing error handling when adding delayed ref with qgroups enabled") Reviewed-by: Filipe Manana Signed-off-by: Miquel Sabaté Solà Signed-off-by: Filipe Manana Signed-off-by: David Sterba Signed-off-by: Sasha Levin --- fs/btrfs/delayed-ref.c | 43 ++++++++++++++++++++++++++++++++---------- 1 file changed, 33 insertions(+), 10 deletions(-) diff --git a/fs/btrfs/delayed-ref.c b/fs/btrfs/delayed-ref.c index 481802efaa1436..f8fc26272f76c5 100644 --- a/fs/btrfs/delayed-ref.c +++ b/fs/btrfs/delayed-ref.c @@ -798,9 +798,13 @@ static void init_delayed_ref_head(struct btrfs_delayed_ref_head *head_ref, } /* - * helper function to actually insert a head node into the rbtree. - * this does all the dirty work in terms of maintaining the correct - * overall modification count. + * Helper function to actually insert a head node into the xarray. This does all + * the dirty work in terms of maintaining the correct overall modification + * count. + * + * The caller is responsible for calling kfree() on @qrecord. More specifically, + * if this function reports that it did not insert it as noted in + * @qrecord_inserted_ret, then it's safe to call kfree() on it. * * Returns an error pointer in case of an error. */ @@ -814,7 +818,14 @@ add_delayed_ref_head(struct btrfs_trans_handle *trans, struct btrfs_delayed_ref_head *existing; struct btrfs_delayed_ref_root *delayed_refs; const unsigned long index = (head_ref->bytenr >> fs_info->sectorsize_bits); - bool qrecord_inserted = false; + + /* + * If 'qrecord_inserted_ret' is provided, then the first thing we need + * to do is to initialize it to false just in case we have an exit + * before trying to insert the record. + */ + if (qrecord_inserted_ret) + *qrecord_inserted_ret = false; delayed_refs = &trans->transaction->delayed_refs; lockdep_assert_held(&delayed_refs->lock); @@ -833,6 +844,12 @@ add_delayed_ref_head(struct btrfs_trans_handle *trans, /* Record qgroup extent info if provided */ if (qrecord) { + /* + * Setting 'qrecord' but not 'qrecord_inserted_ret' will likely + * result in a memory leakage. + */ + ASSERT(qrecord_inserted_ret != NULL); + int ret; ret = btrfs_qgroup_trace_extent_nolock(fs_info, delayed_refs, qrecord, @@ -840,12 +857,10 @@ add_delayed_ref_head(struct btrfs_trans_handle *trans, if (ret) { /* Clean up if insertion fails or item exists. */ xa_release(&delayed_refs->dirty_extents, index); - /* Caller responsible for freeing qrecord on error. */ if (ret < 0) return ERR_PTR(ret); - kfree(qrecord); - } else { - qrecord_inserted = true; + } else if (qrecord_inserted_ret) { + *qrecord_inserted_ret = true; } } @@ -888,8 +903,6 @@ add_delayed_ref_head(struct btrfs_trans_handle *trans, delayed_refs->num_heads++; delayed_refs->num_heads_ready++; } - if (qrecord_inserted_ret) - *qrecord_inserted_ret = qrecord_inserted; return head_ref; } @@ -1049,6 +1062,14 @@ static int add_delayed_ref(struct btrfs_trans_handle *trans, xa_release(&delayed_refs->head_refs, index); spin_unlock(&delayed_refs->lock); ret = PTR_ERR(new_head_ref); + + /* + * It's only safe to call kfree() on 'qrecord' if + * add_delayed_ref_head() has _not_ inserted it for + * tracing. Otherwise we need to handle this here. + */ + if (!qrecord_reserved || qrecord_inserted) + goto free_head_ref; goto free_record; } head_ref = new_head_ref; @@ -1071,6 +1092,8 @@ static int add_delayed_ref(struct btrfs_trans_handle *trans, if (qrecord_inserted) return btrfs_qgroup_trace_extent_post(trans, record, generic_ref->bytenr); + + kfree(record); return 0; free_record: From 742b90eaf394f0018352c0e10dc89763b2dd5267 Mon Sep 17 00:00:00 2001 From: Boris Burkov Date: Wed, 1 Oct 2025 17:20:22 -0700 Subject: [PATCH 0773/3902] btrfs: fix racy bitfield write in btrfs_clear_space_info_full() [ Upstream commit 38e818718c5e04961eea0fa8feff3f100ce40408 ] From the memory-barriers.txt document regarding memory barrier ordering guarantees: (*) These guarantees do not apply to bitfields, because compilers often generate code to modify these using non-atomic read-modify-write sequences. Do not attempt to use bitfields to synchronize parallel algorithms. (*) Even in cases where bitfields are protected by locks, all fields in a given bitfield must be protected by one lock. If two fields in a given bitfield are protected by different locks, the compiler's non-atomic read-modify-write sequences can cause an update to one field to corrupt the value of an adjacent field. btrfs_space_info has a bitfield sharing an underlying word consisting of the fields full, chunk_alloc, and flush: struct btrfs_space_info { struct btrfs_fs_info * fs_info; /* 0 8 */ struct btrfs_space_info * parent; /* 8 8 */ ... int clamp; /* 172 4 */ unsigned int full:1; /* 176: 0 4 */ unsigned int chunk_alloc:1; /* 176: 1 4 */ unsigned int flush:1; /* 176: 2 4 */ ... Therefore, to be safe from parallel read-modify-writes losing a write to one of the bitfield members protected by a lock, all writes to all the bitfields must use the lock. They almost universally do, except for btrfs_clear_space_info_full() which iterates over the space_infos and writes out found->full = 0 without a lock. Imagine that we have one thread completing a transaction in which we finished deleting a block_group and are thus calling btrfs_clear_space_info_full() while simultaneously the data reclaim ticket infrastructure is running do_async_reclaim_data_space(): T1 T2 btrfs_commit_transaction btrfs_clear_space_info_full data_sinfo->full = 0 READ: full:0, chunk_alloc:0, flush:1 do_async_reclaim_data_space(data_sinfo) spin_lock(&space_info->lock); if(list_empty(tickets)) space_info->flush = 0; READ: full: 0, chunk_alloc:0, flush:1 MOD/WRITE: full: 0, chunk_alloc:0, flush:0 spin_unlock(&space_info->lock); return; MOD/WRITE: full:0, chunk_alloc:0, flush:1 and now data_sinfo->flush is 1 but the reclaim worker has exited. This breaks the invariant that flush is 0 iff there is no work queued or running. Once this invariant is violated, future allocations that go into __reserve_bytes() will add tickets to space_info->tickets but will see space_info->flush is set to 1 and not queue the work. After this, they will block forever on the resulting ticket, as it is now impossible to kick the worker again. I also confirmed by looking at the assembly of the affected kernel that it is doing RMW operations. For example, to set the flush (3rd) bit to 0, the assembly is: andb $0xfb,0x60(%rbx) and similarly for setting the full (1st) bit to 0: andb $0xfe,-0x20(%rax) So I think this is really a bug on practical systems. I have observed a number of systems in this exact state, but am currently unable to reproduce it. Rather than leaving this footgun lying around for the future, take advantage of the fact that there is room in the struct anyway, and that it is already quite large and simply change the three bitfield members to bools. This avoids writes to space_info->full having any effect on writes to space_info->flush, regardless of locking. Fixes: 957780eb2788 ("Btrfs: introduce ticketed enospc infrastructure") Reviewed-by: Qu Wenruo Signed-off-by: Boris Burkov Reviewed-by: David Sterba Signed-off-by: David Sterba Signed-off-by: Sasha Levin --- fs/btrfs/block-group.c | 6 +++--- fs/btrfs/space-info.c | 22 +++++++++++----------- fs/btrfs/space-info.h | 6 +++--- 3 files changed, 17 insertions(+), 17 deletions(-) diff --git a/fs/btrfs/block-group.c b/fs/btrfs/block-group.c index 5322ef2ae015e8..8bf501fbcc0bef 100644 --- a/fs/btrfs/block-group.c +++ b/fs/btrfs/block-group.c @@ -4215,7 +4215,7 @@ int btrfs_chunk_alloc(struct btrfs_trans_handle *trans, mutex_unlock(&fs_info->chunk_mutex); } else { /* Proceed with allocation */ - space_info->chunk_alloc = 1; + space_info->chunk_alloc = true; wait_for_alloc = false; spin_unlock(&space_info->lock); } @@ -4264,7 +4264,7 @@ int btrfs_chunk_alloc(struct btrfs_trans_handle *trans, spin_lock(&space_info->lock); if (ret < 0) { if (ret == -ENOSPC) - space_info->full = 1; + space_info->full = true; else goto out; } else { @@ -4274,7 +4274,7 @@ int btrfs_chunk_alloc(struct btrfs_trans_handle *trans, space_info->force_alloc = CHUNK_ALLOC_NO_FORCE; out: - space_info->chunk_alloc = 0; + space_info->chunk_alloc = false; spin_unlock(&space_info->lock); mutex_unlock(&fs_info->chunk_mutex); diff --git a/fs/btrfs/space-info.c b/fs/btrfs/space-info.c index 97452fb5d29b02..85c466c85910ac 100644 --- a/fs/btrfs/space-info.c +++ b/fs/btrfs/space-info.c @@ -192,7 +192,7 @@ void btrfs_clear_space_info_full(struct btrfs_fs_info *info) struct btrfs_space_info *found; list_for_each_entry(found, head, list) - found->full = 0; + found->full = false; } /* @@ -372,7 +372,7 @@ void btrfs_add_bg_to_space_info(struct btrfs_fs_info *info, space_info->bytes_readonly += block_group->bytes_super; btrfs_space_info_update_bytes_zone_unusable(space_info, block_group->zone_unusable); if (block_group->length > 0) - space_info->full = 0; + space_info->full = false; btrfs_try_granting_tickets(info, space_info); spin_unlock(&space_info->lock); @@ -1146,7 +1146,7 @@ static void do_async_reclaim_metadata_space(struct btrfs_space_info *space_info) spin_lock(&space_info->lock); to_reclaim = btrfs_calc_reclaim_metadata_size(fs_info, space_info); if (!to_reclaim) { - space_info->flush = 0; + space_info->flush = false; spin_unlock(&space_info->lock); return; } @@ -1158,7 +1158,7 @@ static void do_async_reclaim_metadata_space(struct btrfs_space_info *space_info) flush_space(fs_info, space_info, to_reclaim, flush_state, false); spin_lock(&space_info->lock); if (list_empty(&space_info->tickets)) { - space_info->flush = 0; + space_info->flush = false; spin_unlock(&space_info->lock); return; } @@ -1201,7 +1201,7 @@ static void do_async_reclaim_metadata_space(struct btrfs_space_info *space_info) flush_state = FLUSH_DELAYED_ITEMS_NR; commit_cycles--; } else { - space_info->flush = 0; + space_info->flush = false; } } else { flush_state = FLUSH_DELAYED_ITEMS_NR; @@ -1383,7 +1383,7 @@ static void do_async_reclaim_data_space(struct btrfs_space_info *space_info) spin_lock(&space_info->lock); if (list_empty(&space_info->tickets)) { - space_info->flush = 0; + space_info->flush = false; spin_unlock(&space_info->lock); return; } @@ -1394,7 +1394,7 @@ static void do_async_reclaim_data_space(struct btrfs_space_info *space_info) flush_space(fs_info, space_info, U64_MAX, ALLOC_CHUNK_FORCE, false); spin_lock(&space_info->lock); if (list_empty(&space_info->tickets)) { - space_info->flush = 0; + space_info->flush = false; spin_unlock(&space_info->lock); return; } @@ -1411,7 +1411,7 @@ static void do_async_reclaim_data_space(struct btrfs_space_info *space_info) data_flush_states[flush_state], false); spin_lock(&space_info->lock); if (list_empty(&space_info->tickets)) { - space_info->flush = 0; + space_info->flush = false; spin_unlock(&space_info->lock); return; } @@ -1428,7 +1428,7 @@ static void do_async_reclaim_data_space(struct btrfs_space_info *space_info) if (maybe_fail_all_tickets(fs_info, space_info)) flush_state = 0; else - space_info->flush = 0; + space_info->flush = false; } else { flush_state = 0; } @@ -1444,7 +1444,7 @@ static void do_async_reclaim_data_space(struct btrfs_space_info *space_info) aborted_fs: maybe_fail_all_tickets(fs_info, space_info); - space_info->flush = 0; + space_info->flush = false; spin_unlock(&space_info->lock); } @@ -1825,7 +1825,7 @@ static int __reserve_bytes(struct btrfs_fs_info *fs_info, */ maybe_clamp_preempt(fs_info, space_info); - space_info->flush = 1; + space_info->flush = true; trace_btrfs_trigger_flush(fs_info, space_info->flags, orig_bytes, flush, diff --git a/fs/btrfs/space-info.h b/fs/btrfs/space-info.h index 679f22efb4073f..a846f63585c950 100644 --- a/fs/btrfs/space-info.h +++ b/fs/btrfs/space-info.h @@ -142,11 +142,11 @@ struct btrfs_space_info { flushing. The value is >> clamp, so turns out to be a 2^clamp divisor. */ - unsigned int full:1; /* indicates that we cannot allocate any more + bool full; /* indicates that we cannot allocate any more chunks for this space */ - unsigned int chunk_alloc:1; /* set if we are allocating a chunk */ + bool chunk_alloc; /* set if we are allocating a chunk */ - unsigned int flush:1; /* set if we are trying to make space */ + bool flush; /* set if we are trying to make space */ unsigned int force_alloc; /* set if we need to force a chunk alloc for this space */ From d65f49f772cf51ea0fedacf96ae2b906876ea73c Mon Sep 17 00:00:00 2001 From: Qu Wenruo Date: Wed, 5 Nov 2025 20:28:12 +1030 Subject: [PATCH 0774/3902] btrfs: make sure extent and csum paths are always released in scrub_raid56_parity_stripe() [ Upstream commit d435c513652e6a90a13c881986a2cc6420c99cab ] Unlike queue_scrub_stripe() which uses the global sctx->extent_path and sctx->csum_path which are always released at the end of scrub_stripe(), scrub_raid56_parity_stripe() uses local extent_path and csum_path, as that function is going to handle the full stripe, whose bytenr may be smaller than the bytenr in the global sctx paths. However the cleanup of local extent/csum paths is only happening after we have successfully submitted an rbio. There are several error routes that we didn't release those two paths: - scrub_find_fill_first_stripe() errored out at csum tree search In that case extent_path is still valid, and that function itself will not release the extent_path passed in. And the function returns directly without releasing both paths. - The full stripe is empty - Some blocks failed to be recovered - btrfs_map_block() failed - raid56_parity_alloc_scrub_rbio() failed The function returns directly without releasing both paths. Fix it by covering btrfs_release_path() calls inside the out: tag. This is just a hot fix, in the long run we will go scoped based auto freeing for both local paths. Fixes: 1dc4888e725d ("btrfs: scrub: avoid unnecessary extent tree search preparing stripes") Fixes: 3c771c194402 ("btrfs: scrub: avoid unnecessary csum tree search preparing stripes") Signed-off-by: Qu Wenruo Reviewed-by: David Sterba Signed-off-by: David Sterba Signed-off-by: Sasha Levin --- fs/btrfs/scrub.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fs/btrfs/scrub.c b/fs/btrfs/scrub.c index ba20d9286a340c..65361af3023436 100644 --- a/fs/btrfs/scrub.c +++ b/fs/btrfs/scrub.c @@ -2230,9 +2230,9 @@ static int scrub_raid56_parity_stripe(struct scrub_ctx *sctx, bio_put(bio); btrfs_bio_counter_dec(fs_info); +out: btrfs_release_path(&extent_path); btrfs_release_path(&csum_path); -out: return ret; } From f5190da518bed09b1f82dec89cdf931a177243f3 Mon Sep 17 00:00:00 2001 From: Filipe Manana Date: Thu, 13 Nov 2025 12:52:45 +0000 Subject: [PATCH 0775/3902] btrfs: fix leaf leak in an error path in btrfs_del_items() [ Upstream commit e7dd1182fcedee7c6097c9f49eba8de94a4364e3 ] If the call to btrfs_del_leaf() fails we return without decrementing the extra ref we took on the leaf, therefore leaking it. Fix this by ensuring we drop the ref count before returning the error. Fixes: 751a27615dda ("btrfs: do not BUG_ON() on tree mod log failures at btrfs_del_ptr()") Reviewed-by: Qu Wenruo Signed-off-by: Filipe Manana Reviewed-by: David Sterba Signed-off-by: David Sterba Signed-off-by: Sasha Levin --- fs/btrfs/ctree.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fs/btrfs/ctree.c b/fs/btrfs/ctree.c index 561658aca018b4..6e053caa6e101b 100644 --- a/fs/btrfs/ctree.c +++ b/fs/btrfs/ctree.c @@ -4566,9 +4566,9 @@ int btrfs_del_items(struct btrfs_trans_handle *trans, struct btrfs_root *root, if (btrfs_header_nritems(leaf) == 0) { path->slots[1] = slot; ret = btrfs_del_leaf(trans, root, path, leaf); + free_extent_buffer(leaf); if (ret < 0) return ret; - free_extent_buffer(leaf); ret = 0; } else { /* if we're still in the path, make sure From 644ddd0d96d1d95d28d41566863348539c280610 Mon Sep 17 00:00:00 2001 From: Shawn Lin Date: Fri, 14 Nov 2025 20:09:00 +0800 Subject: [PATCH 0776/3902] PCI: dwc: Fix wrong PORT_LOGIC_LTSSM_STATE_MASK definition [ Upstream commit bcc9a4a0bca3aee4303fa4a20302e57b24ac8f68 ] As per DesignWare Cores PCI Express Controller Databook, section 5.50, SII: Debug Signals, cxpl_debug_info[63:0]: [5:0] smlh_ltssm_state: LTSSM current state. Encoding is same as the dedicated smlh_ltssm_state output. The mask should be 6 bits, from 0 to 5. Hence, fix the mask definition. Fixes: 23fe5bd4be90 ("PCI: keystone: Cleanup ks_pcie_link_up()") Signed-off-by: Shawn Lin [mani: reworded description] Signed-off-by: Manivannan Sadhasivam Signed-off-by: Bjorn Helgaas Link: https://patch.msgid.link/1763122140-203068-1-git-send-email-shawn.lin@rock-chips.com Signed-off-by: Sasha Levin --- drivers/pci/controller/dwc/pcie-designware.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/pci/controller/dwc/pcie-designware.h b/drivers/pci/controller/dwc/pcie-designware.h index e995f692a1ecd1..24bfa5231923a1 100644 --- a/drivers/pci/controller/dwc/pcie-designware.h +++ b/drivers/pci/controller/dwc/pcie-designware.h @@ -97,7 +97,7 @@ #define PORT_LANE_SKEW_INSERT_MASK GENMASK(23, 0) #define PCIE_PORT_DEBUG0 0x728 -#define PORT_LOGIC_LTSSM_STATE_MASK 0x1f +#define PORT_LOGIC_LTSSM_STATE_MASK 0x3f #define PORT_LOGIC_LTSSM_STATE_L0 0x11 #define PCIE_PORT_DEBUG1 0x72C #define PCIE_PORT_DEBUG1_LINK_UP BIT(4) From 60ac9e5820b9e6337acab7ad2155297e322f7e0f Mon Sep 17 00:00:00 2001 From: Timur Tabi Date: Thu, 13 Nov 2025 17:03:22 -0600 Subject: [PATCH 0777/3902] drm/nouveau: restrict the flush page to a 32-bit address [ Upstream commit 04d98b3452331fa53ec3b698b66273af6ef73288 ] The flush page DMA address is stored in a special register that is not associated with the GPU's standard DMA range. For example, on Turing, the GPU's MMU can handle 47-bit addresses, but the flush page address register is limited to 40 bits. At the point during device initialization when the flush page is allocated, the DMA mask is still at its default of 32 bits. So even though it's unlikely that the flush page could exist above a 40-bit address, the dma_map_page() call could fail, e.g. if IOMMU is disabled and the address is above 32 bits. The simplest way to achieve all constraints is to allocate the page in the DMA32 zone. Since the flush page is literally just a page, this is an acceptable limitation. The alternative is to temporarily set the DMA mask to 40 (or 52 for Hopper and later) bits, but that could have unforseen side effects. In situations where the flush page is allocated above 32 bits and IOMMU is disabled, you will get an error like this: nouveau 0000:65:00.0: DMA addr 0x0000000107c56000+4096 overflow (mask ffffffff, bus limit 0). Fixes: 5728d064190e ("drm/nouveau/fb: handle sysmem flush page from common code") Signed-off-by: Timur Tabi Reviewed-by: Lyude Paul Signed-off-by: Lyude Paul Link: https://patch.msgid.link/20251113230323.1271726-1-ttabi@nvidia.com Signed-off-by: Sasha Levin --- drivers/gpu/drm/nouveau/nvkm/subdev/fb/base.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/gpu/drm/nouveau/nvkm/subdev/fb/base.c b/drivers/gpu/drm/nouveau/nvkm/subdev/fb/base.c index 8a286a9349ac62..7ce1b65e2c1c29 100644 --- a/drivers/gpu/drm/nouveau/nvkm/subdev/fb/base.c +++ b/drivers/gpu/drm/nouveau/nvkm/subdev/fb/base.c @@ -279,7 +279,7 @@ nvkm_fb_ctor(const struct nvkm_fb_func *func, struct nvkm_device *device, mutex_init(&fb->tags.mutex); if (func->sysmem.flush_page_init) { - fb->sysmem.flush_page = alloc_page(GFP_KERNEL | __GFP_ZERO); + fb->sysmem.flush_page = alloc_page(GFP_KERNEL | GFP_DMA32 | __GFP_ZERO); if (!fb->sysmem.flush_page) return -ENOMEM; From f8a794ea740ddbd110d323121d50fc9cb1b36c5b Mon Sep 17 00:00:00 2001 From: David Gow Date: Sat, 22 Nov 2025 16:32:12 +0800 Subject: [PATCH 0778/3902] um: Don't rename vmap to kernel_vmap [ Upstream commit a74b6c0e53a6df8e8a096b50c06c4f872906368a ] In order to work around the existence of a vmap symbol in libpcap, the UML makefile unconditionally redefines vmap to kernel_vmap. However, this not only affects the actual vmap symbol, but also anything else named vmap, including a number of struct members in DRM. This would not be too much of a problem, since all uses are also updated, except we now have Rust DRM bindings, which expect the corresponding Rust structs to have 'vmap' names. Since the redefinition applies in bindgen, but not to Rust code, we end up with errors such as: error[E0560]: struct `drm_gem_object_funcs` has no fields named `vmap` --> rust/kernel/drm/gem/mod.rs:210:9 Since libpcap support was removed in commit 12b8e7e69aa7 ("um: Remove obsolete pcap driver"), remove the, now unnecessary, define as well. We also take this opportunity to update the comment. Signed-off-by: David Gow Acked-by: Miguel Ojeda Link: https://patch.msgid.link/20251122083213.3996586-1-davidgow@google.com Fixes: 12b8e7e69aa7 ("um: Remove obsolete pcap driver") [adjust commmit message a bit] Signed-off-by: Johannes Berg Signed-off-by: Sasha Levin --- arch/um/Makefile | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/arch/um/Makefile b/arch/um/Makefile index 7be0143b5ba350..721b652ffb6584 100644 --- a/arch/um/Makefile +++ b/arch/um/Makefile @@ -46,19 +46,17 @@ ARCH_INCLUDE := -I$(srctree)/$(SHARED_HEADERS) ARCH_INCLUDE += -I$(srctree)/$(HOST_DIR)/um/shared KBUILD_CPPFLAGS += -I$(srctree)/$(HOST_DIR)/um -# -Dvmap=kernel_vmap prevents anything from referencing the libpcap.o symbol so -# named - it's a common symbol in libpcap, so we get a binary which crashes. -# -# Same things for in6addr_loopback and mktime - found in libc. For these two we -# only get link-time error, luckily. +# -Dstrrchr=kernel_strrchr (as well as the various in6addr symbols) prevents +# anything from referencing +# libc symbols with the same name, which can cause a linker error. # # -Dlongjmp=kernel_longjmp prevents anything from referencing the libpthread.a # embedded copy of longjmp, same thing for setjmp. # -# These apply to USER_CFLAGS to. +# These apply to USER_CFLAGS too. KBUILD_CFLAGS += $(CFLAGS) $(CFLAGS-y) -D__arch_um__ \ - $(ARCH_INCLUDE) $(MODE_INCLUDE) -Dvmap=kernel_vmap \ + $(ARCH_INCLUDE) $(MODE_INCLUDE) \ -Dlongjmp=kernel_longjmp -Dsetjmp=kernel_setjmp \ -Din6addr_loopback=kernel_in6addr_loopback \ -Din6addr_any=kernel_in6addr_any -Dstrrchr=kernel_strrchr \ From 74c0c1af04ee6982b47237b1c12cff63ffb14460 Mon Sep 17 00:00:00 2001 From: Christoph Hellwig Date: Thu, 13 Nov 2025 18:06:27 +0100 Subject: [PATCH 0779/3902] iomap: always run error completions in user context [ Upstream commit ddb4873286e03e193c5a3bebb5fc6fa820e9ee3a ] At least zonefs expects error completions to be able to sleep. Because error completions aren't performance critical, just defer them to workqueue context unconditionally. Fixes: 8dcc1a9d90c1 ("fs: New zonefs file system") Signed-off-by: Christoph Hellwig Link: https://patch.msgid.link/20251113170633.1453259-3-hch@lst.de Reviewed-by: Jan Kara Reviewed-by: Chaitanya Kulkarni Signed-off-by: Christian Brauner Signed-off-by: Sasha Levin --- fs/iomap/direct-io.c | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/fs/iomap/direct-io.c b/fs/iomap/direct-io.c index 5d5d63efbd5767..143160042552b3 100644 --- a/fs/iomap/direct-io.c +++ b/fs/iomap/direct-io.c @@ -179,7 +179,18 @@ static void iomap_dio_done(struct iomap_dio *dio) WRITE_ONCE(dio->submit.waiter, NULL); blk_wake_io_task(waiter); - } else if (dio->flags & IOMAP_DIO_INLINE_COMP) { + return; + } + + /* + * Always run error completions in user context. These are not + * performance critical and some code relies on taking sleeping locks + * for error handling. + */ + if (dio->error) + dio->flags &= ~IOMAP_DIO_INLINE_COMP; + + if (dio->flags & IOMAP_DIO_INLINE_COMP) { WRITE_ONCE(iocb->private, NULL); iomap_dio_complete_work(&dio->aio.work); } else if (dio->flags & IOMAP_DIO_CALLER_COMP) { From c67775cf0da2407f113c1229e350758f4dca0f51 Mon Sep 17 00:00:00 2001 From: Christoph Hellwig Date: Mon, 24 Nov 2025 15:00:13 +0100 Subject: [PATCH 0780/3902] iomap: allocate s_dio_done_wq for async reads as well [ Upstream commit 7fd8720dff2d9c70cf5a1a13b7513af01952ec02 ] Since commit 222f2c7c6d14 ("iomap: always run error completions in user context"), read error completions are deferred to s_dio_done_wq. This means the workqueue also needs to be allocated for async reads. Fixes: 222f2c7c6d14 ("iomap: always run error completions in user context") Reported-by: syzbot+a2b9a4ed0d61b1efb3f5@syzkaller.appspotmail.com Signed-off-by: Christoph Hellwig Link: https://patch.msgid.link/20251124140013.902853-1-hch@lst.de Tested-by: syzbot+a2b9a4ed0d61b1efb3f5@syzkaller.appspotmail.com Reviewed-by: Dave Chinner Reviewed-by: Darrick J. Wong Signed-off-by: Christian Brauner Signed-off-by: Sasha Levin --- fs/iomap/direct-io.c | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/fs/iomap/direct-io.c b/fs/iomap/direct-io.c index 143160042552b3..6317e4cd42517d 100644 --- a/fs/iomap/direct-io.c +++ b/fs/iomap/direct-io.c @@ -728,12 +728,12 @@ __iomap_dio_rw(struct kiocb *iocb, struct iov_iter *iter, } goto out_free_dio; } + } - if (!wait_for_completion && !inode->i_sb->s_dio_done_wq) { - ret = sb_init_dio_done_wq(inode->i_sb); - if (ret < 0) - goto out_free_dio; - } + if (!wait_for_completion && !inode->i_sb->s_dio_done_wq) { + ret = sb_init_dio_done_wq(inode->i_sb); + if (ret < 0) + goto out_free_dio; } inode_dio_begin(inode); From ad26a7ef04882d26dfb365335baacfbd8be97f21 Mon Sep 17 00:00:00 2001 From: Ria Thomas Date: Mon, 24 Nov 2025 18:26:37 +0530 Subject: [PATCH 0781/3902] wifi: ieee80211: correct FILS status codes [ Upstream commit 24d4da5c2565313c2ad3c43449937a9351a64407 ] The FILS status codes are set to 108/109, but the IEEE 802.11-2020 spec defines them as 112/113. Update the enum so it matches the specification and keeps the kernel consistent with standard values. Fixes: a3caf7440ded ("cfg80211: Add support for FILS shared key authentication offload") Signed-off-by: Ria Thomas Reviewed-by: Jeff Johnson Link: https://patch.msgid.link/20251124125637.3936154-1-ria.thomas@morsemicro.com Signed-off-by: Johannes Berg Signed-off-by: Sasha Levin --- include/linux/ieee80211.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/include/linux/ieee80211.h b/include/linux/ieee80211.h index ddff9102f63329..1f4679092e69d4 100644 --- a/include/linux/ieee80211.h +++ b/include/linux/ieee80211.h @@ -3594,8 +3594,8 @@ enum ieee80211_statuscode { WLAN_STATUS_DENIED_WITH_SUGGESTED_BAND_AND_CHANNEL = 99, WLAN_STATUS_DENIED_DUE_TO_SPECTRUM_MANAGEMENT = 103, /* 802.11ai */ - WLAN_STATUS_FILS_AUTHENTICATION_FAILURE = 108, - WLAN_STATUS_UNKNOWN_AUTHENTICATION_SERVER = 109, + WLAN_STATUS_FILS_AUTHENTICATION_FAILURE = 112, + WLAN_STATUS_UNKNOWN_AUTHENTICATION_SERVER = 113, WLAN_STATUS_SAE_HASH_TO_ELEMENT = 126, WLAN_STATUS_SAE_PK = 127, WLAN_STATUS_DENIED_TID_TO_LINK_MAPPING = 133, From 08c9dc6b0f2c68e5e7c374ac4499e321e435d46c Mon Sep 17 00:00:00 2001 From: Luca Ceresoli Date: Mon, 19 May 2025 22:19:11 +0200 Subject: [PATCH 0782/3902] backlight: led-bl: Add devlink to supplier LEDs [ Upstream commit 9341d6698f4cfdfc374fb6944158d111ebe16a9d ] LED Backlight is a consumer of one or multiple LED class devices, but devlink is currently unable to create correct supplier-producer links when the supplier is a class device. It creates instead a link where the supplier is the parent of the expected device. One consequence is that removal order is not correctly enforced. Issues happen for example with the following sections in a device tree overlay: // An LED driver chip pca9632@62 { compatible = "nxp,pca9632"; reg = <0x62>; // ... addon_led_pwm: led-pwm@3 { reg = <3>; label = "addon:led:pwm"; }; }; backlight-addon { compatible = "led-backlight"; leds = <&addon_led_pwm>; brightness-levels = <255>; default-brightness-level = <255>; }; In this example, the devlink should be created between the backlight-addon (consumer) and the pca9632@62 (supplier). Instead it is created between the backlight-addon (consumer) and the parent of the pca9632@62, which is typically the I2C bus adapter. On removal of the above overlay, the LED driver can be removed before the backlight device, resulting in: Unable to handle kernel NULL pointer dereference at virtual address 0000000000000010 ... Call trace: led_put+0xe0/0x140 devm_led_release+0x6c/0x98 Another way to reproduce the bug without any device tree overlays is unbinding the LED class device (pca9632@62) before unbinding the consumer (backlight-addon): echo 11-0062 >/sys/bus/i2c/drivers/leds-pca963x/unbind echo ...backlight-dock >/sys/bus/platform/drivers/led-backlight/unbind Fix by adding a devlink between the consuming led-backlight device and the supplying LED device, as other drivers and subsystems do as well. Fixes: ae232e45acf9 ("backlight: add led-backlight driver") Signed-off-by: Luca Ceresoli Reviewed-by: Daniel Thompson (RISCstar) Reviewed-by: Herve Codina Tested-by: Alexander Sverdlin Link: https://patch.msgid.link/20250519-led-backlight-add-devlink-to-supplier-class-device-v6-1-845224aeb2ce@bootlin.com Signed-off-by: Lee Jones Signed-off-by: Sasha Levin --- drivers/video/backlight/led_bl.c | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/drivers/video/backlight/led_bl.c b/drivers/video/backlight/led_bl.c index efc5e380669aea..f7ab9b3607313c 100644 --- a/drivers/video/backlight/led_bl.c +++ b/drivers/video/backlight/led_bl.c @@ -210,6 +210,19 @@ static int led_bl_probe(struct platform_device *pdev) return PTR_ERR(priv->bl_dev); } + for (i = 0; i < priv->nb_leds; i++) { + struct device_link *link; + + link = device_link_add(&pdev->dev, priv->leds[i]->dev->parent, + DL_FLAG_AUTOREMOVE_CONSUMER); + if (!link) { + dev_err(&pdev->dev, "Failed to add devlink (consumer %s, supplier %s)\n", + dev_name(&pdev->dev), dev_name(priv->leds[i]->dev->parent)); + backlight_device_unregister(priv->bl_dev); + return -EINVAL; + } + } + for (i = 0; i < priv->nb_leds; i++) { mutex_lock(&priv->leds[i]->led_access); led_sysfs_disable(priv->leds[i]); From 57c6cea2073c4561487967476bd9c05a4d4e49e7 Mon Sep 17 00:00:00 2001 From: Randy Dunlap Date: Mon, 10 Nov 2025 22:09:16 -0800 Subject: [PATCH 0783/3902] backlight: lp855x: Fix lp855x.h kernel-doc warnings [ Upstream commit 2d45db63260c6ae3cf007361e04a1c41bd265084 ] Add a missing struct short description and a missing leading " *" to lp855x.h to avoid kernel-doc warnings: Warning: include/linux/platform_data/lp855x.h:126 missing initial short description on line: * struct lp855x_platform_data Warning: include/linux/platform_data/lp855x.h:131 bad line: Only valid when mode is PWM_BASED. Fixes: 7be865ab8634 ("backlight: new backlight driver for LP855x devices") Signed-off-by: Randy Dunlap Reviewed-by: Daniel Thompson (RISCstar) Link: https://patch.msgid.link/20251111060916.1995920-1-rdunlap@infradead.org Signed-off-by: Lee Jones Signed-off-by: Sasha Levin --- include/linux/platform_data/lp855x.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/include/linux/platform_data/lp855x.h b/include/linux/platform_data/lp855x.h index ab222dd05bbc25..3b4a891acefe93 100644 --- a/include/linux/platform_data/lp855x.h +++ b/include/linux/platform_data/lp855x.h @@ -124,12 +124,12 @@ struct lp855x_rom_data { }; /** - * struct lp855x_platform_data + * struct lp855x_platform_data - lp855 platform-specific data * @name : Backlight driver name. If it is not defined, default name is set. * @device_control : value of DEVICE CONTROL register * @initial_brightness : initial value of backlight brightness * @period_ns : platform specific pwm period value. unit is nano. - Only valid when mode is PWM_BASED. + * Only valid when mode is PWM_BASED. * @size_program : total size of lp855x_rom_data * @rom_data : list of new eeprom/eprom registers */ From cefb67ea27f95715a1e33e0e9ca0488f6495e051 Mon Sep 17 00:00:00 2001 From: Stephan Gerhold Date: Thu, 21 Aug 2025 10:33:53 +0200 Subject: [PATCH 0784/3902] iommu/arm-smmu-qcom: Enable use of all SMR groups when running bare-metal [ Upstream commit 5583a55e074b33ccd88ac0542fd7cd656a7e2c8c ] Some platforms (e.g. SC8280XP and X1E) support more than 128 stream matching groups. This is more than what is defined as maximum by the ARM SMMU architecture specification. Commit 122611347326 ("iommu/arm-smmu-qcom: Limit the SMR groups to 128") disabled use of the additional groups because they don't exhibit the same behavior as the architecture supported ones. It seems like this is just another quirk of the hypervisor: When running bare-metal without the hypervisor, the additional groups appear to behave just like all others. The boot firmware uses some of the additional groups, so ignoring them in this situation leads to stream match conflicts whenever we allocate a new SMR group for the same SID. The workaround exists primarily because the bypass quirk detection fails when using a S2CR register from the additional matching groups, so let's perform the test with the last reliable S2CR (127) and then limit the number of SMR groups only if we detect that we are running below the hypervisor (because of the bypass quirk). Fixes: 122611347326 ("iommu/arm-smmu-qcom: Limit the SMR groups to 128") Signed-off-by: Stephan Gerhold Signed-off-by: Will Deacon Signed-off-by: Sasha Levin --- drivers/iommu/arm/arm-smmu/arm-smmu-qcom.c | 27 ++++++++++++++-------- 1 file changed, 17 insertions(+), 10 deletions(-) diff --git a/drivers/iommu/arm/arm-smmu/arm-smmu-qcom.c b/drivers/iommu/arm/arm-smmu/arm-smmu-qcom.c index 57c097e8761308..c939d0856b719c 100644 --- a/drivers/iommu/arm/arm-smmu/arm-smmu-qcom.c +++ b/drivers/iommu/arm/arm-smmu/arm-smmu-qcom.c @@ -431,17 +431,19 @@ static int qcom_smmu_cfg_probe(struct arm_smmu_device *smmu) /* * Some platforms support more than the Arm SMMU architected maximum of - * 128 stream matching groups. For unknown reasons, the additional - * groups don't exhibit the same behavior as the architected registers, - * so limit the groups to 128 until the behavior is fixed for the other - * groups. + * 128 stream matching groups. The additional registers appear to have + * the same behavior as the architected registers in the hardware. + * However, on some firmware versions, the hypervisor does not + * correctly trap and emulate accesses to the additional registers, + * resulting in unexpected behavior. + * + * If there are more than 128 groups, use the last reliable group to + * detect if we need to apply the bypass quirk. */ - if (smmu->num_mapping_groups > 128) { - dev_notice(smmu->dev, "\tLimiting the stream matching groups to 128\n"); - smmu->num_mapping_groups = 128; - } - - last_s2cr = ARM_SMMU_GR0_S2CR(smmu->num_mapping_groups - 1); + if (smmu->num_mapping_groups > 128) + last_s2cr = ARM_SMMU_GR0_S2CR(127); + else + last_s2cr = ARM_SMMU_GR0_S2CR(smmu->num_mapping_groups - 1); /* * With some firmware versions writes to S2CR of type FAULT are @@ -464,6 +466,11 @@ static int qcom_smmu_cfg_probe(struct arm_smmu_device *smmu) reg = FIELD_PREP(ARM_SMMU_CBAR_TYPE, CBAR_TYPE_S1_TRANS_S2_BYPASS); arm_smmu_gr1_write(smmu, ARM_SMMU_GR1_CBAR(qsmmu->bypass_cbndx), reg); + + if (smmu->num_mapping_groups > 128) { + dev_notice(smmu->dev, "\tLimiting the stream matching groups to 128\n"); + smmu->num_mapping_groups = 128; + } } for (i = 0; i < smmu->num_mapping_groups; i++) { From 118894098d4e139afbaa397090ead292196d1ccb Mon Sep 17 00:00:00 2001 From: Krzysztof Czurylo Date: Mon, 24 Nov 2025 20:53:42 -0600 Subject: [PATCH 0785/3902] RDMA/irdma: Fix data race in irdma_sc_ccq_arm [ Upstream commit a521928164433de44fed5aaf5f49aeb3f1fb96f5 ] Adds a lock around irdma_sc_ccq_arm body to prevent inter-thread data race. Fixes data race in irdma_sc_ccq_arm() reported by KCSAN: BUG: KCSAN: data-race in irdma_sc_ccq_arm [irdma] / irdma_sc_ccq_arm [irdma] read to 0xffff9d51b4034220 of 8 bytes by task 255 on cpu 11: irdma_sc_ccq_arm+0x36/0xd0 [irdma] irdma_cqp_ce_handler+0x300/0x310 [irdma] cqp_compl_worker+0x2a/0x40 [irdma] process_one_work+0x402/0x7e0 worker_thread+0xb3/0x6d0 kthread+0x178/0x1a0 ret_from_fork+0x2c/0x50 write to 0xffff9d51b4034220 of 8 bytes by task 89 on cpu 3: irdma_sc_ccq_arm+0x7e/0xd0 [irdma] irdma_cqp_ce_handler+0x300/0x310 [irdma] irdma_wait_event+0xd4/0x3e0 [irdma] irdma_handle_cqp_op+0xa5/0x220 [irdma] irdma_hw_flush_wqes+0xb1/0x300 [irdma] irdma_flush_wqes+0x22e/0x3a0 [irdma] irdma_cm_disconn_true+0x4c7/0x5d0 [irdma] irdma_disconnect_worker+0x35/0x50 [irdma] process_one_work+0x402/0x7e0 worker_thread+0xb3/0x6d0 kthread+0x178/0x1a0 ret_from_fork+0x2c/0x50 value changed: 0x0000000000024000 -> 0x0000000000034000 Fixes: 3f49d6842569 ("RDMA/irdma: Implement HW Admin Queue OPs") Signed-off-by: Krzysztof Czurylo Signed-off-by: Tatyana Nikolova Link: https://patch.msgid.link/20251125025350.180-2-tatyana.e.nikolova@intel.com Signed-off-by: Leon Romanovsky Signed-off-by: Sasha Levin --- drivers/infiniband/hw/irdma/ctrl.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/drivers/infiniband/hw/irdma/ctrl.c b/drivers/infiniband/hw/irdma/ctrl.c index 4ef1c29032f771..991ce4890bfb27 100644 --- a/drivers/infiniband/hw/irdma/ctrl.c +++ b/drivers/infiniband/hw/irdma/ctrl.c @@ -3950,11 +3950,13 @@ int irdma_sc_cqp_destroy(struct irdma_sc_cqp *cqp) */ void irdma_sc_ccq_arm(struct irdma_sc_cq *ccq) { + unsigned long flags; u64 temp_val; u16 sw_cq_sel; u8 arm_next_se; u8 arm_seq_num; + spin_lock_irqsave(&ccq->dev->cqp_lock, flags); get_64bit_val(ccq->cq_uk.shadow_area, 32, &temp_val); sw_cq_sel = (u16)FIELD_GET(IRDMA_CQ_DBSA_SW_CQ_SELECT, temp_val); arm_next_se = (u8)FIELD_GET(IRDMA_CQ_DBSA_ARM_NEXT_SE, temp_val); @@ -3965,6 +3967,7 @@ void irdma_sc_ccq_arm(struct irdma_sc_cq *ccq) FIELD_PREP(IRDMA_CQ_DBSA_ARM_NEXT_SE, arm_next_se) | FIELD_PREP(IRDMA_CQ_DBSA_ARM_NEXT, 1); set_64bit_val(ccq->cq_uk.shadow_area, 32, temp_val); + spin_unlock_irqrestore(&ccq->dev->cqp_lock, flags); dma_wmb(); /* make sure shadow area is updated before arming */ From 4b8abcc87373c97c92512b86bd49bc8048e0a301 Mon Sep 17 00:00:00 2001 From: Krzysztof Czurylo Date: Mon, 24 Nov 2025 20:53:43 -0600 Subject: [PATCH 0786/3902] RDMA/irdma: Fix data race in irdma_free_pble [ Upstream commit 81f44409fb4f027d1e6d54edbeba5156ad94b214 ] Protects pble_rsrc counters with mutex to prevent data race. Fixes the following data race in irdma_free_pble reported by KCSAN: BUG: KCSAN: data-race in irdma_free_pble [irdma] / irdma_free_pble [irdma] write to 0xffff91430baa0078 of 8 bytes by task 16956 on cpu 5: irdma_free_pble+0x3b/0xb0 [irdma] irdma_dereg_mr+0x108/0x110 [irdma] ib_dereg_mr_user+0x74/0x160 [ib_core] uverbs_free_mr+0x26/0x30 [ib_uverbs] destroy_hw_idr_uobject+0x4a/0x90 [ib_uverbs] uverbs_destroy_uobject+0x7b/0x330 [ib_uverbs] uobj_destroy+0x61/0xb0 [ib_uverbs] ib_uverbs_run_method+0x1f2/0x380 [ib_uverbs] ib_uverbs_cmd_verbs+0x365/0x440 [ib_uverbs] ib_uverbs_ioctl+0x111/0x190 [ib_uverbs] __x64_sys_ioctl+0xc9/0x100 do_syscall_64+0x44/0xa0 entry_SYSCALL_64_after_hwframe+0x6e/0xd8 read to 0xffff91430baa0078 of 8 bytes by task 16953 on cpu 2: irdma_free_pble+0x23/0xb0 [irdma] irdma_dereg_mr+0x108/0x110 [irdma] ib_dereg_mr_user+0x74/0x160 [ib_core] uverbs_free_mr+0x26/0x30 [ib_uverbs] destroy_hw_idr_uobject+0x4a/0x90 [ib_uverbs] uverbs_destroy_uobject+0x7b/0x330 [ib_uverbs] uobj_destroy+0x61/0xb0 [ib_uverbs] ib_uverbs_run_method+0x1f2/0x380 [ib_uverbs] ib_uverbs_cmd_verbs+0x365/0x440 [ib_uverbs] ib_uverbs_ioctl+0x111/0x190 [ib_uverbs] __x64_sys_ioctl+0xc9/0x100 do_syscall_64+0x44/0xa0 entry_SYSCALL_64_after_hwframe+0x6e/0xd8 value changed: 0x0000000000005a62 -> 0x0000000000005a68 Fixes: e8c4dbc2fcac ("RDMA/irdma: Add PBLE resource manager") Signed-off-by: Krzysztof Czurylo Signed-off-by: Tatyana Nikolova Link: https://patch.msgid.link/20251125025350.180-3-tatyana.e.nikolova@intel.com Signed-off-by: Leon Romanovsky Signed-off-by: Sasha Levin --- drivers/infiniband/hw/irdma/pble.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/drivers/infiniband/hw/irdma/pble.c b/drivers/infiniband/hw/irdma/pble.c index fa6325adaedecb..28dfad7f940c24 100644 --- a/drivers/infiniband/hw/irdma/pble.c +++ b/drivers/infiniband/hw/irdma/pble.c @@ -506,12 +506,14 @@ int irdma_get_pble(struct irdma_hmc_pble_rsrc *pble_rsrc, void irdma_free_pble(struct irdma_hmc_pble_rsrc *pble_rsrc, struct irdma_pble_alloc *palloc) { - pble_rsrc->freedpbles += palloc->total_cnt; - if (palloc->level == PBLE_LEVEL_2) free_lvl2(pble_rsrc, palloc); else irdma_prm_return_pbles(&pble_rsrc->pinfo, &palloc->level1.chunkinfo); + + mutex_lock(&pble_rsrc->pble_mutex_lock); + pble_rsrc->freedpbles += palloc->total_cnt; pble_rsrc->stats_alloc_freed++; + mutex_unlock(&pble_rsrc->pble_mutex_lock); } From e0331f477c1925b1eaf5c7c385ff14f8efc86aad Mon Sep 17 00:00:00 2001 From: Tatyana Nikolova Date: Mon, 24 Nov 2025 20:53:44 -0600 Subject: [PATCH 0787/3902] RDMA/irdma: Add a missing kfree of struct irdma_pci_f for GEN2 [ Upstream commit 9e13d880ebae5da9b39ef2ed83a89737e927173f ] During a refactor of the irdma GEN2 code, the kfree of the irdma_pci_f struct in icrdma_remove(), which was originally introduced upstream as part of commit 80f2ab46c2ee ("irdma: free iwdev->rf after removing MSI-X") was accidentally removed. Fixes: 0c2b80cac96e ("RDMA/irdma: Refactor GEN2 auxiliary driver") Signed-off-by: Krzysztof Czurylo Signed-off-by: Tatyana Nikolova Link: https://patch.msgid.link/20251125025350.180-4-tatyana.e.nikolova@intel.com Signed-off-by: Leon Romanovsky Signed-off-by: Sasha Levin --- drivers/infiniband/hw/irdma/icrdma_if.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/drivers/infiniband/hw/irdma/icrdma_if.c b/drivers/infiniband/hw/irdma/icrdma_if.c index 27b191f61caf47..5d3fd118e4f819 100644 --- a/drivers/infiniband/hw/irdma/icrdma_if.c +++ b/drivers/infiniband/hw/irdma/icrdma_if.c @@ -320,6 +320,8 @@ static void icrdma_remove(struct auxiliary_device *aux_dev) irdma_ib_unregister_device(iwdev); icrdma_deinit_interrupts(iwdev->rf, cdev_info); + kfree(iwdev->rf); + pr_debug("INIT: Gen[%d] func[%d] device remove success\n", rdma_ver, PCI_FUNC(cdev_info->pdev->devfn)); } From b0258b9ecbc0a46fa6d4bdd2b9c3b546ca13c84f Mon Sep 17 00:00:00 2001 From: Krzysztof Czurylo Date: Mon, 24 Nov 2025 20:53:45 -0600 Subject: [PATCH 0788/3902] RDMA/irdma: Fix SIGBUS in AEQ destroy [ Upstream commit 5eff1ecce30143c3f8924d91770d81d44bd5abe5 ] Removes write to IRDMA_PFINT_AEQCTL register prior to destroying AEQ, as this register does not exist in GEN3+ hardware and this kind of IRQ configuration is no longer required. Fixes: b800e82feba7 ("RDMA/irdma: Add GEN3 support for AEQ and CEQ") Signed-off-by: Krzysztof Czurylo Signed-off-by: Tatyana Nikolova Link: https://patch.msgid.link/20251125025350.180-5-tatyana.e.nikolova@intel.com Signed-off-by: Leon Romanovsky Signed-off-by: Sasha Levin --- drivers/infiniband/hw/irdma/ctrl.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/drivers/infiniband/hw/irdma/ctrl.c b/drivers/infiniband/hw/irdma/ctrl.c index 991ce4890bfb27..cb970c9090ee0e 100644 --- a/drivers/infiniband/hw/irdma/ctrl.c +++ b/drivers/infiniband/hw/irdma/ctrl.c @@ -4734,7 +4734,8 @@ static int irdma_sc_aeq_destroy(struct irdma_sc_aeq *aeq, u64 scratch, u64 hdr; dev = aeq->dev; - if (dev->privileged) + + if (dev->hw_attrs.uk_attrs.hw_rev <= IRDMA_GEN_2) writel(0, dev->hw_regs[IRDMA_PFINT_AEQCTL]); cqp = dev->cqp; From 4b71f69c49141d6b13d05559cbac66f36aa6d644 Mon Sep 17 00:00:00 2001 From: Anil Samal Date: Mon, 24 Nov 2025 20:53:46 -0600 Subject: [PATCH 0789/3902] RDMA/irdma: Add missing mutex destroy [ Upstream commit 35bd787babd1f5a42641d0b1513edbed5d4e1624 ] Add missing destroy of ah_tbl_lock and vchnl_mutex. Fixes: d5edd33364a5 ("RDMA/irdma: RDMA/irdma: Add GEN3 core driver support") Signed-off-by: Anil Samal Signed-off-by: Krzysztof Czurylo Signed-off-by: Tatyana Nikolova Link: https://patch.msgid.link/20251125025350.180-6-tatyana.e.nikolova@intel.com Signed-off-by: Leon Romanovsky Signed-off-by: Sasha Levin --- drivers/infiniband/hw/irdma/icrdma_if.c | 4 +++- drivers/infiniband/hw/irdma/ig3rdma_if.c | 4 ++++ drivers/infiniband/hw/irdma/verbs.c | 4 +++- 3 files changed, 10 insertions(+), 2 deletions(-) diff --git a/drivers/infiniband/hw/irdma/icrdma_if.c b/drivers/infiniband/hw/irdma/icrdma_if.c index 5d3fd118e4f819..b49fd9cf247654 100644 --- a/drivers/infiniband/hw/irdma/icrdma_if.c +++ b/drivers/infiniband/hw/irdma/icrdma_if.c @@ -302,7 +302,8 @@ static int icrdma_probe(struct auxiliary_device *aux_dev, const struct auxiliary err_ctrl_init: icrdma_deinit_interrupts(rf, cdev_info); err_init_interrupts: - kfree(iwdev->rf); + mutex_destroy(&rf->ah_tbl_lock); + kfree(rf); ib_dealloc_device(&iwdev->ibdev); return err; @@ -319,6 +320,7 @@ static void icrdma_remove(struct auxiliary_device *aux_dev) ice_rdma_update_vsi_filter(cdev_info, iwdev->vsi_num, false); irdma_ib_unregister_device(iwdev); icrdma_deinit_interrupts(iwdev->rf, cdev_info); + mutex_destroy(&iwdev->rf->ah_tbl_lock); kfree(iwdev->rf); diff --git a/drivers/infiniband/hw/irdma/ig3rdma_if.c b/drivers/infiniband/hw/irdma/ig3rdma_if.c index 1bb42eb298ba69..e1d6670d9396eb 100644 --- a/drivers/infiniband/hw/irdma/ig3rdma_if.c +++ b/drivers/infiniband/hw/irdma/ig3rdma_if.c @@ -55,6 +55,7 @@ static int ig3rdma_vchnl_init(struct irdma_pci_f *rf, ret = irdma_sc_vchnl_init(&rf->sc_dev, &virt_info); if (ret) { destroy_workqueue(rf->vchnl_wq); + mutex_destroy(&rf->sc_dev.vchnl_mutex); return ret; } @@ -124,7 +125,9 @@ static void ig3rdma_decfg_rf(struct irdma_pci_f *rf) { struct irdma_hw *hw = &rf->hw; + mutex_destroy(&rf->ah_tbl_lock); destroy_workqueue(rf->vchnl_wq); + mutex_destroy(&rf->sc_dev.vchnl_mutex); kfree(hw->io_regs); iounmap(hw->rdma_reg.addr); } @@ -149,6 +152,7 @@ static int ig3rdma_cfg_rf(struct irdma_pci_f *rf, err = ig3rdma_cfg_regions(&rf->hw, cdev_info); if (err) { destroy_workqueue(rf->vchnl_wq); + mutex_destroy(&rf->sc_dev.vchnl_mutex); return err; } diff --git a/drivers/infiniband/hw/irdma/verbs.c b/drivers/infiniband/hw/irdma/verbs.c index c883c9ea5a831e..c884f2ce282f96 100644 --- a/drivers/infiniband/hw/irdma/verbs.c +++ b/drivers/infiniband/hw/irdma/verbs.c @@ -5500,7 +5500,9 @@ void irdma_ib_dealloc_device(struct ib_device *ibdev) irdma_rt_deinit_hw(iwdev); if (!iwdev->is_vport) { irdma_ctrl_deinit_hw(iwdev->rf); - if (iwdev->rf->vchnl_wq) + if (iwdev->rf->vchnl_wq) { destroy_workqueue(iwdev->rf->vchnl_wq); + mutex_destroy(&iwdev->rf->sc_dev.vchnl_mutex); + } } } From aac0552d66c2cc9fae23d5821ea75f54df94ac49 Mon Sep 17 00:00:00 2001 From: Jacob Moroni Date: Mon, 24 Nov 2025 20:53:47 -0600 Subject: [PATCH 0790/3902] RDMA/irdma: Do not directly rely on IB_PD_UNSAFE_GLOBAL_RKEY [ Upstream commit 71d3bdae5eab21cf8991a6f3cd914caa31d5a51f ] The HW disables bounds checking for MRs with a length of zero, so the driver will only allow a zero length MR if the "all_memory" flag is set, and this flag is only set if IB_PD_UNSAFE_GLOBAL_RKEY is set for the PD. This means that the "get_dma_mr" method will currently fail unless the IB_PD_UNSAFE_GLOBAL_RKEY flag is set. This has not been an issue because the "get_dma_mr" method is only ever invoked if the device does not support the local DMA key or if IB_PD_UNSAFE_GLOBAL_RKEY is set, and so far, all IRDMA HW supports the local DMA lkey. However, some new HW does not support the local DMA lkey, so the "get_dma_mr" method needs to work without IB_PD_UNSAFE_GLOBAL_RKEY being set. To support HW that does not allow the local DMA lkey, the logic has been changed to pass an explicit flag to indicate when a dma_mr is being created so that the zero length will be allowed. Also, the "all_memory" flag has been forced to false for normal MR allocation since these MRs are never supposed to provide global unsafe rkey semantics anyway; only the MR created with "get_dma_mr" should support this. Fixes: bb6d73d9add6 ("RDMA/irdma: Prevent zero-length STAG registration") Signed-off-by: Jacob Moroni Signed-off-by: Tatyana Nikolova Link: https://patch.msgid.link/20251125025350.180-7-tatyana.e.nikolova@intel.com Signed-off-by: Leon Romanovsky Signed-off-by: Sasha Levin --- drivers/infiniband/hw/irdma/cm.c | 2 +- drivers/infiniband/hw/irdma/main.h | 2 +- drivers/infiniband/hw/irdma/verbs.c | 15 ++++++++------- drivers/infiniband/hw/irdma/verbs.h | 3 ++- 4 files changed, 12 insertions(+), 10 deletions(-) diff --git a/drivers/infiniband/hw/irdma/cm.c b/drivers/infiniband/hw/irdma/cm.c index c6a0a661d6e7ee..f4f4f92ba63aca 100644 --- a/drivers/infiniband/hw/irdma/cm.c +++ b/drivers/infiniband/hw/irdma/cm.c @@ -3710,7 +3710,7 @@ int irdma_accept(struct iw_cm_id *cm_id, struct iw_cm_conn_param *conn_param) iwpd = iwqp->iwpd; tagged_offset = (uintptr_t)iwqp->ietf_mem.va; ibmr = irdma_reg_phys_mr(&iwpd->ibpd, iwqp->ietf_mem.pa, buf_len, - IB_ACCESS_LOCAL_WRITE, &tagged_offset); + IB_ACCESS_LOCAL_WRITE, &tagged_offset, false); if (IS_ERR(ibmr)) { ret = -ENOMEM; goto error; diff --git a/drivers/infiniband/hw/irdma/main.h b/drivers/infiniband/hw/irdma/main.h index 886b30da188aed..65ce4924dbfa61 100644 --- a/drivers/infiniband/hw/irdma/main.h +++ b/drivers/infiniband/hw/irdma/main.h @@ -556,7 +556,7 @@ void irdma_copy_ip_htonl(__be32 *dst, u32 *src); u16 irdma_get_vlan_ipv4(u32 *addr); void irdma_get_vlan_mac_ipv6(u32 *addr, u16 *vlan_id, u8 *mac); struct ib_mr *irdma_reg_phys_mr(struct ib_pd *ib_pd, u64 addr, u64 size, - int acc, u64 *iova_start); + int acc, u64 *iova_start, bool dma_mr); int irdma_upload_qp_context(struct irdma_qp *iwqp, bool freeze, bool raw); void irdma_cqp_ce_handler(struct irdma_pci_f *rf, struct irdma_sc_cq *cq); int irdma_ah_cqp_op(struct irdma_pci_f *rf, struct irdma_sc_ah *sc_ah, u8 cmd, diff --git a/drivers/infiniband/hw/irdma/verbs.c b/drivers/infiniband/hw/irdma/verbs.c index c884f2ce282f96..8c44c3fcf9731e 100644 --- a/drivers/infiniband/hw/irdma/verbs.c +++ b/drivers/infiniband/hw/irdma/verbs.c @@ -3108,7 +3108,6 @@ static int irdma_hw_alloc_stag(struct irdma_device *iwdev, info->stag_idx = iwmr->stag >> IRDMA_CQPSQ_STAG_IDX_S; info->pd_id = iwpd->sc_pd.pd_id; info->total_len = iwmr->len; - info->all_memory = pd->flags & IB_PD_UNSAFE_GLOBAL_RKEY; info->remote_access = true; cqp_info->cqp_cmd = IRDMA_OP_ALLOC_STAG; cqp_info->post_sq = 1; @@ -3119,7 +3118,7 @@ static int irdma_hw_alloc_stag(struct irdma_device *iwdev, if (status) return status; - iwmr->is_hwreg = 1; + iwmr->is_hwreg = true; return 0; } @@ -3263,7 +3262,7 @@ static int irdma_hwreg_mr(struct irdma_device *iwdev, struct irdma_mr *iwmr, if (iwdev->rf->sc_dev.hw_attrs.uk_attrs.feature_flags & IRDMA_FEATURE_ATOMIC_OPS) stag_info->remote_atomics_en = (access & IB_ACCESS_REMOTE_ATOMIC) ? 1 : 0; stag_info->pd_id = iwpd->sc_pd.pd_id; - stag_info->all_memory = pd->flags & IB_PD_UNSAFE_GLOBAL_RKEY; + stag_info->all_memory = iwmr->dma_mr; if (stag_info->access_rights & IRDMA_ACCESS_FLAGS_ZERO_BASED) stag_info->addr_type = IRDMA_ADDR_TYPE_ZERO_BASED; else @@ -3290,7 +3289,7 @@ static int irdma_hwreg_mr(struct irdma_device *iwdev, struct irdma_mr *iwmr, irdma_put_cqp_request(&iwdev->rf->cqp, cqp_request); if (!ret) - iwmr->is_hwreg = 1; + iwmr->is_hwreg = true; return ret; } @@ -3663,7 +3662,7 @@ static int irdma_hwdereg_mr(struct ib_mr *ib_mr) if (status) return status; - iwmr->is_hwreg = 0; + iwmr->is_hwreg = false; return 0; } @@ -3786,9 +3785,10 @@ static struct ib_mr *irdma_rereg_user_mr(struct ib_mr *ib_mr, int flags, * @size: size of memory to register * @access: Access rights * @iova_start: start of virtual address for physical buffers + * @dma_mr: Flag indicating whether this region is a PD DMA MR */ struct ib_mr *irdma_reg_phys_mr(struct ib_pd *pd, u64 addr, u64 size, int access, - u64 *iova_start) + u64 *iova_start, bool dma_mr) { struct irdma_device *iwdev = to_iwdev(pd->device); struct irdma_pbl *iwpbl; @@ -3805,6 +3805,7 @@ struct ib_mr *irdma_reg_phys_mr(struct ib_pd *pd, u64 addr, u64 size, int access iwpbl = &iwmr->iwpbl; iwpbl->iwmr = iwmr; iwmr->type = IRDMA_MEMREG_TYPE_MEM; + iwmr->dma_mr = dma_mr; iwpbl->user_base = *iova_start; stag = irdma_create_stag(iwdev); if (!stag) { @@ -3843,7 +3844,7 @@ static struct ib_mr *irdma_get_dma_mr(struct ib_pd *pd, int acc) { u64 kva = 0; - return irdma_reg_phys_mr(pd, 0, 0, acc, &kva); + return irdma_reg_phys_mr(pd, 0, 0, acc, &kva, true); } /** diff --git a/drivers/infiniband/hw/irdma/verbs.h b/drivers/infiniband/hw/irdma/verbs.h index ac8b3870183502..aabbb3442098bc 100644 --- a/drivers/infiniband/hw/irdma/verbs.h +++ b/drivers/infiniband/hw/irdma/verbs.h @@ -111,7 +111,8 @@ struct irdma_mr { }; struct ib_umem *region; int access; - u8 is_hwreg; + bool is_hwreg:1; + bool dma_mr:1; u16 type; u32 page_cnt; u64 page_size; From 951870eee2dc0eb11c6e62fc3fb8e43ec59654c3 Mon Sep 17 00:00:00 2001 From: Jacob Moroni Date: Mon, 24 Nov 2025 20:53:48 -0600 Subject: [PATCH 0791/3902] RDMA/irdma: Do not set IBK_LOCAL_DMA_LKEY for GEN3+ [ Upstream commit eef3ad030b08c0f100cb18de7f604442a1adb8c7 ] The GEN3 hardware does not appear to support IBK_LOCAL_DMA_LKEY. Attempts to use it will result in an AE. Fixes: eb31dfc2b41a ("RDMA/irdma: Restrict Memory Window and CQE Timestamping to GEN3") Signed-off-by: Jacob Moroni Signed-off-by: Tatyana Nikolova Link: https://patch.msgid.link/20251125025350.180-8-tatyana.e.nikolova@intel.com Signed-off-by: Leon Romanovsky Signed-off-by: Sasha Levin --- drivers/infiniband/hw/irdma/verbs.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/drivers/infiniband/hw/irdma/verbs.c b/drivers/infiniband/hw/irdma/verbs.c index 8c44c3fcf9731e..afccd9f08b8a57 100644 --- a/drivers/infiniband/hw/irdma/verbs.c +++ b/drivers/infiniband/hw/irdma/verbs.c @@ -27,7 +27,8 @@ static int irdma_query_device(struct ib_device *ibdev, irdma_fw_minor_ver(&rf->sc_dev); props->device_cap_flags = IB_DEVICE_MEM_WINDOW | IB_DEVICE_MEM_MGT_EXTENSIONS; - props->kernel_cap_flags = IBK_LOCAL_DMA_LKEY; + if (hw_attrs->uk_attrs.hw_rev < IRDMA_GEN_3) + props->kernel_cap_flags = IBK_LOCAL_DMA_LKEY; props->vendor_id = pcidev->vendor; props->vendor_part_id = pcidev->device; From 63f755a2ef55c041a9763a94500a7e66b13f838e Mon Sep 17 00:00:00 2001 From: Jacob Moroni Date: Mon, 24 Nov 2025 20:53:49 -0600 Subject: [PATCH 0792/3902] RDMA/irdma: Remove doorbell elision logic [ Upstream commit 62356fccb195f83d2ceafc787c5ba87ebbe5edfe ] In some cases, this logic can result in doorbell writes being skipped when they should not have been (at least on GEN3 HW), so remove it. This also means that the mb() can be safely downgraded to dma_wmb(). Fixes: 551c46edc769 ("RDMA/irdma: Add user/kernel shared libraries") Signed-off-by: Jacob Moroni Signed-off-by: Tatyana Nikolova Link: https://patch.msgid.link/20251125025350.180-9-tatyana.e.nikolova@intel.com Signed-off-by: Leon Romanovsky Signed-off-by: Sasha Levin --- drivers/infiniband/hw/irdma/puda.c | 1 - drivers/infiniband/hw/irdma/uk.c | 31 ++---------------------------- drivers/infiniband/hw/irdma/user.h | 1 - 3 files changed, 2 insertions(+), 31 deletions(-) diff --git a/drivers/infiniband/hw/irdma/puda.c b/drivers/infiniband/hw/irdma/puda.c index 694e5a9ed15d0f..9cd14a50f1a937 100644 --- a/drivers/infiniband/hw/irdma/puda.c +++ b/drivers/infiniband/hw/irdma/puda.c @@ -685,7 +685,6 @@ static int irdma_puda_qp_create(struct irdma_puda_rsrc *rsrc) ukqp->rq_size = rsrc->rq_size; IRDMA_RING_INIT(ukqp->sq_ring, ukqp->sq_size); - IRDMA_RING_INIT(ukqp->initial_ring, ukqp->sq_size); IRDMA_RING_INIT(ukqp->rq_ring, ukqp->rq_size); ukqp->wqe_alloc_db = qp->pd->dev->wqe_alloc_db; diff --git a/drivers/infiniband/hw/irdma/uk.c b/drivers/infiniband/hw/irdma/uk.c index ce1ae10c30fcad..d5568584ad5e32 100644 --- a/drivers/infiniband/hw/irdma/uk.c +++ b/drivers/infiniband/hw/irdma/uk.c @@ -114,33 +114,8 @@ void irdma_clr_wqes(struct irdma_qp_uk *qp, u32 qp_wqe_idx) */ void irdma_uk_qp_post_wr(struct irdma_qp_uk *qp) { - u64 temp; - u32 hw_sq_tail; - u32 sw_sq_head; - - /* valid bit is written and loads completed before reading shadow */ - mb(); - - /* read the doorbell shadow area */ - get_64bit_val(qp->shadow_area, 0, &temp); - - hw_sq_tail = (u32)FIELD_GET(IRDMA_QP_DBSA_HW_SQ_TAIL, temp); - sw_sq_head = IRDMA_RING_CURRENT_HEAD(qp->sq_ring); - if (sw_sq_head != qp->initial_ring.head) { - if (sw_sq_head != hw_sq_tail) { - if (sw_sq_head > qp->initial_ring.head) { - if (hw_sq_tail >= qp->initial_ring.head && - hw_sq_tail < sw_sq_head) - writel(qp->qp_id, qp->wqe_alloc_db); - } else { - if (hw_sq_tail >= qp->initial_ring.head || - hw_sq_tail < sw_sq_head) - writel(qp->qp_id, qp->wqe_alloc_db); - } - } - } - - qp->initial_ring.head = qp->sq_ring.head; + dma_wmb(); + writel(qp->qp_id, qp->wqe_alloc_db); } /** @@ -1574,7 +1549,6 @@ static void irdma_setup_connection_wqes(struct irdma_qp_uk *qp, qp->conn_wqes = move_cnt; IRDMA_RING_MOVE_HEAD_BY_COUNT_NOCHECK(qp->sq_ring, move_cnt); IRDMA_RING_MOVE_TAIL_BY_COUNT(qp->sq_ring, move_cnt); - IRDMA_RING_MOVE_HEAD_BY_COUNT_NOCHECK(qp->initial_ring, move_cnt); } /** @@ -1719,7 +1693,6 @@ int irdma_uk_qp_init(struct irdma_qp_uk *qp, struct irdma_qp_uk_init_info *info) qp->max_sq_frag_cnt = info->max_sq_frag_cnt; sq_ring_size = qp->sq_size << info->sq_shift; IRDMA_RING_INIT(qp->sq_ring, sq_ring_size); - IRDMA_RING_INIT(qp->initial_ring, sq_ring_size); if (info->first_sq_wq) { irdma_setup_connection_wqes(qp, info); qp->swqe_polarity = 1; diff --git a/drivers/infiniband/hw/irdma/user.h b/drivers/infiniband/hw/irdma/user.h index ab57f689827a02..aeebf768174abd 100644 --- a/drivers/infiniband/hw/irdma/user.h +++ b/drivers/infiniband/hw/irdma/user.h @@ -456,7 +456,6 @@ struct irdma_srq_uk { struct irdma_uk_attrs *uk_attrs; __le64 *shadow_area; struct irdma_ring srq_ring; - struct irdma_ring initial_ring; u32 srq_id; u32 srq_size; u32 max_srq_frag_cnt; From 73573f7e52842dfb1c205a6617afe5ae49335ea0 Mon Sep 17 00:00:00 2001 From: Jijun Wang Date: Mon, 24 Nov 2025 20:53:50 -0600 Subject: [PATCH 0793/3902] RDMA/irdma: Fix SRQ shadow area address initialization [ Upstream commit 01dad9ca37c60d08f71e2ef639875ae895deede6 ] Fix SRQ shadow area address initialization. Fixes: 563e1feb5f6e ("RDMA/irdma: Add SRQ support") Signed-off-by: Jijun Wang Signed-off-by: Jay Bhat Link: https://patch.msgid.link/20251125025350.180-10-tatyana.e.nikolova@intel.com Signed-off-by: Leon Romanovsky Signed-off-by: Sasha Levin --- drivers/infiniband/hw/irdma/verbs.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/infiniband/hw/irdma/verbs.c b/drivers/infiniband/hw/irdma/verbs.c index afccd9f08b8a57..b8655504da2907 100644 --- a/drivers/infiniband/hw/irdma/verbs.c +++ b/drivers/infiniband/hw/irdma/verbs.c @@ -2307,8 +2307,8 @@ static int irdma_setup_kmode_srq(struct irdma_device *iwdev, ukinfo->srq_size = depth >> shift; ukinfo->shadow_area = mem->va + ring_size; - info->shadow_area_pa = info->srq_pa + ring_size; info->srq_pa = mem->pa; + info->shadow_area_pa = info->srq_pa + ring_size; return 0; } From 46cd9849e5207e92b2f1d429a4688d4954c5918b Mon Sep 17 00:00:00 2001 From: Guillaume La Roque Date: Sun, 23 Nov 2025 18:14:10 +0100 Subject: [PATCH 0794/3902] arm64: dts: amlogic: meson-g12b: Fix L2 cache reference for S922X CPUs [ Upstream commit a7ab6f946683e065fa22db1cc2f2748d4584178a ] The original addition of cache information for the Amlogic S922X SoC used the wrong next-level cache node for CPU cores 100 and 101, incorrectly referencing `l2_cache_l`. These cores actually belong to the big cluster and should reference `l2_cache_b`. Update the device tree accordingly. Fixes: e7f85e6c155a ("arm64: dts: amlogic: Add cache information to the Amlogic S922X SoC") Signed-off-by: Guillaume La Roque Reviewed-by: Neil Armstrong Link: https://patch.msgid.link/20251123-fixkhadas-v1-1-045348f0a4c2@baylibre.com Signed-off-by: Neil Armstrong Signed-off-by: Sasha Levin --- arch/arm64/boot/dts/amlogic/meson-g12b.dtsi | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/arch/arm64/boot/dts/amlogic/meson-g12b.dtsi b/arch/arm64/boot/dts/amlogic/meson-g12b.dtsi index f04efa8282561c..23358d94844c94 100644 --- a/arch/arm64/boot/dts/amlogic/meson-g12b.dtsi +++ b/arch/arm64/boot/dts/amlogic/meson-g12b.dtsi @@ -87,7 +87,7 @@ i-cache-line-size = <32>; i-cache-size = <0x8000>; i-cache-sets = <32>; - next-level-cache = <&l2_cache_l>; + next-level-cache = <&l2_cache_b>; #cooling-cells = <2>; }; @@ -103,7 +103,7 @@ i-cache-line-size = <32>; i-cache-size = <0x8000>; i-cache-sets = <32>; - next-level-cache = <&l2_cache_l>; + next-level-cache = <&l2_cache_b>; #cooling-cells = <2>; }; From 0a3d0e94866352c9ecd75a50e361773ec06099f8 Mon Sep 17 00:00:00 2001 From: Akash Goel Date: Thu, 20 Nov 2025 17:21:18 +0000 Subject: [PATCH 0795/3902] drm/panthor: Avoid adding of kernel BOs to extobj list [ Upstream commit ce04ec03a9c2c4f3e60e26f21311b25d5a478208 ] The kernel BOs unnecessarily got added to the external objects list of drm_gpuvm, when mapping to GPU, which would have resulted in few extra CPU cycles being spent at the time of job submission as drm_exec_until_all_locked() loop iterates over all external objects. Kernel BOs are private to a VM and so they share the dma_resv object of the dummy GEM object created for a VM. Use of DRM_EXEC_IGNORE_DUPLICATES flag ensured the recursive locking of the dummy GEM object was ignored. Also no extra space got allocated to add fences to the dma_resv object of dummy GEM object. So no other impact apart from few extra CPU cycles. This commit sets the pointer to dma_resv object of GEM object of kernel BOs before they are mapped to GPU, to prevent them from being added to external objects list. v2: Add R-bs and fixes tags Fixes: 8a1cc07578bf ("drm/panthor: Add GEM logical block") Signed-off-by: Akash Goel Reviewed-by: Boris Brezillon Reviewed-by: Steven Price Link: https://patch.msgid.link/20251120172118.2741724-1-akash.goel@arm.com Signed-off-by: Boris Brezillon Signed-off-by: Sasha Levin --- drivers/gpu/drm/panthor/panthor_gem.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/drivers/gpu/drm/panthor/panthor_gem.c b/drivers/gpu/drm/panthor/panthor_gem.c index 14ed09d700f2f7..43471babff86cb 100644 --- a/drivers/gpu/drm/panthor/panthor_gem.c +++ b/drivers/gpu/drm/panthor/panthor_gem.c @@ -144,6 +144,9 @@ panthor_kernel_bo_create(struct panthor_device *ptdev, struct panthor_vm *vm, bo = to_panthor_bo(&obj->base); kbo->obj = &obj->base; bo->flags = bo_flags; + bo->exclusive_vm_root_gem = panthor_vm_root_gem(vm); + drm_gem_object_get(bo->exclusive_vm_root_gem); + bo->base.base.resv = bo->exclusive_vm_root_gem->resv; if (vm == panthor_fw_vm(ptdev)) debug_flags |= PANTHOR_DEBUGFS_GEM_USAGE_FLAG_FW_MAPPED; @@ -167,9 +170,6 @@ panthor_kernel_bo_create(struct panthor_device *ptdev, struct panthor_vm *vm, goto err_free_va; kbo->vm = panthor_vm_get(vm); - bo->exclusive_vm_root_gem = panthor_vm_root_gem(vm); - drm_gem_object_get(bo->exclusive_vm_root_gem); - bo->base.base.resv = bo->exclusive_vm_root_gem->resv; return kbo; err_free_va: From cbc8d54c7313be21f82c252bda35e2ba36c3b4b4 Mon Sep 17 00:00:00 2001 From: Haotian Zhang Date: Thu, 30 Oct 2025 17:07:10 +0800 Subject: [PATCH 0796/3902] clocksource/drivers/ralink: Fix resource leaks in init error path [ Upstream commit 2ba8e2aae1324704565a7d4d66f199d056c9e3c6 ] The ralink_systick_init() function does not release all acquired resources on its error paths. If irq_of_parse_and_map() or a subsequent call fails, the previously created I/O memory mapping and IRQ mapping are leaked. Add goto-based error handling labels to ensure that all allocated resources are correctly freed. Fixes: 1f2acc5a8a0a ("MIPS: ralink: Add support for systick timer found on newer ralink SoC") Signed-off-by: Haotian Zhang Signed-off-by: Daniel Lezcano Link: https://patch.msgid.link/20251030090710.1603-1-vulab@iscas.ac.cn Signed-off-by: Sasha Levin --- drivers/clocksource/timer-ralink.c | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/drivers/clocksource/timer-ralink.c b/drivers/clocksource/timer-ralink.c index 6ecdb4228f7637..68434d9ed91070 100644 --- a/drivers/clocksource/timer-ralink.c +++ b/drivers/clocksource/timer-ralink.c @@ -130,14 +130,15 @@ static int __init ralink_systick_init(struct device_node *np) systick.dev.irq = irq_of_parse_and_map(np, 0); if (!systick.dev.irq) { pr_err("%pOFn: request_irq failed", np); - return -EINVAL; + ret = -EINVAL; + goto err_iounmap; } ret = clocksource_mmio_init(systick.membase + SYSTICK_COUNT, np->name, SYSTICK_FREQ, 301, 16, clocksource_mmio_readl_up); if (ret) - return ret; + goto err_free_irq; clockevents_register_device(&systick.dev); @@ -145,6 +146,12 @@ static int __init ralink_systick_init(struct device_node *np) np, systick.dev.mult, systick.dev.shift); return 0; + +err_free_irq: + irq_dispose_mapping(systick.dev.irq); +err_iounmap: + iounmap(systick.membase); + return ret; } TIMER_OF_DECLARE(systick, "ralink,cevt-systick", ralink_systick_init); From 9876d55a04457317eede51d2bb43d0148790895d Mon Sep 17 00:00:00 2001 From: Johan Hovold Date: Fri, 17 Oct 2025 07:50:39 +0200 Subject: [PATCH 0797/3902] clocksource/drivers/stm: Fix double deregistration on probe failure [ Upstream commit 6b38a8b31e2c5c2c3fd5f9848850788c190f216d ] The purpose of the devm_add_action_or_reset() helper is to call the action function in case adding an action ever fails so drop the clock source deregistration from the error path to avoid deregistering twice. Fixes: cec32ac75827 ("clocksource/drivers/nxp-timer: Add the System Timer Module for the s32gx platforms") Signed-off-by: Johan Hovold Signed-off-by: Daniel Lezcano Cc: Daniel Lezcano Link: https://patch.msgid.link/20251017055039.7307-1-johan@kernel.org Signed-off-by: Sasha Levin --- drivers/clocksource/timer-nxp-stm.c | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/drivers/clocksource/timer-nxp-stm.c b/drivers/clocksource/timer-nxp-stm.c index bbc40623728fa5..16d52167e949be 100644 --- a/drivers/clocksource/timer-nxp-stm.c +++ b/drivers/clocksource/timer-nxp-stm.c @@ -208,10 +208,8 @@ static int __init nxp_stm_clocksource_init(struct device *dev, struct stm_timer return ret; ret = devm_add_action_or_reset(dev, devm_clocksource_unregister, stm_timer); - if (ret) { - clocksource_unregister(&stm_timer->cs); + if (ret) return ret; - } stm_sched_clock = stm_timer; From 364752785f7dfa2d1455c3e56fccb1cf08c3b0d8 Mon Sep 17 00:00:00 2001 From: Johan Hovold Date: Tue, 11 Nov 2025 16:32:24 +0100 Subject: [PATCH 0798/3902] clocksource/drivers/arm_arch_timer_mmio: Prevent driver unbind [ Upstream commit 6aa10f0e2ef9eba1955be6a9d0a8eaecf6bdb7ae ] Clockevents cannot be deregistered so suppress the bind attributes to prevent the driver from being unbound and releasing the underlying resources after registration. Fixes: 4891f01527bb ("clocksource/drivers/arm_arch_timer: Add standalone MMIO driver") Signed-off-by: Johan Hovold Signed-off-by: Daniel Lezcano Acked-by: Marc Zyngier Link: https://patch.msgid.link/20251111153226.579-2-johan@kernel.org Signed-off-by: Sasha Levin --- drivers/clocksource/arm_arch_timer_mmio.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/drivers/clocksource/arm_arch_timer_mmio.c b/drivers/clocksource/arm_arch_timer_mmio.c index ebe1987d651ebc..d10362692fdd32 100644 --- a/drivers/clocksource/arm_arch_timer_mmio.c +++ b/drivers/clocksource/arm_arch_timer_mmio.c @@ -426,6 +426,7 @@ static struct platform_driver arch_timer_mmio_drv = { .driver = { .name = "arch-timer-mmio", .of_match_table = arch_timer_mmio_of_table, + .suppress_bind_attrs = true, }, .probe = arch_timer_mmio_probe, }; @@ -434,6 +435,7 @@ builtin_platform_driver(arch_timer_mmio_drv); static struct platform_driver arch_timer_mmio_acpi_drv = { .driver = { .name = "gtdt-arm-mmio-timer", + .suppress_bind_attrs = true, }, .probe = arch_timer_mmio_probe, }; From 7da0b7f66a203a3e7707cb79d0308a43d7b67f2f Mon Sep 17 00:00:00 2001 From: Johan Hovold Date: Tue, 11 Nov 2025 16:32:25 +0100 Subject: [PATCH 0799/3902] clocksource/drivers/nxp-pit: Prevent driver unbind [ Upstream commit e25f964cf414dafa6bee5c9c2c0b1d1fb041dc92 ] The driver does not support unbinding (e.g. as clockevents cannot be deregistered) so suppress the bind attributes to prevent the driver from being unbound and rebound after registration (and disabling the timer when reprobing fails). Even if the driver can currently only be built-in, also switch to builtin_platform_driver() to prevent it from being unloaded should modular builds ever be enabled. Fixes: bee33f22d7c3 ("clocksource/drivers/nxp-pit: Add NXP Automotive s32g2 / s32g3 support") Signed-off-by: Johan Hovold Signed-off-by: Daniel Lezcano Link: https://patch.msgid.link/20251111153226.579-3-johan@kernel.org Signed-off-by: Sasha Levin --- drivers/clocksource/timer-nxp-pit.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/drivers/clocksource/timer-nxp-pit.c b/drivers/clocksource/timer-nxp-pit.c index 2d0a3554b6bf7d..d1740f18f71803 100644 --- a/drivers/clocksource/timer-nxp-pit.c +++ b/drivers/clocksource/timer-nxp-pit.c @@ -374,9 +374,10 @@ static struct platform_driver nxp_pit_driver = { .driver = { .name = "nxp-pit", .of_match_table = pit_timer_of_match, + .suppress_bind_attrs = true, }, .probe = pit_timer_probe, }; -module_platform_driver(nxp_pit_driver); +builtin_platform_driver(nxp_pit_driver); TIMER_OF_DECLARE(vf610, "fsl,vf610-pit", pit_timer_init); From 755927ae17691c29bb5c8568e0e96a7ee58a14e9 Mon Sep 17 00:00:00 2001 From: Johan Hovold Date: Fri, 17 Oct 2025 07:49:43 +0200 Subject: [PATCH 0800/3902] clocksource/drivers/nxp-stm: Fix section mismatches [ Upstream commit b452d2c97eeccbf9c7ac5b3d2d9e80bf6d8a23db ] Platform drivers can be probed after their init sections have been discarded (e.g. on probe deferral or manual rebind through sysfs) so the probe function must not live in init. Device managed resource actions similarly cannot be discarded. The "_probe" suffix of the driver structure name prevents modpost from warning about this so replace it to catch any similar future issues. Fixes: cec32ac75827 ("clocksource/drivers/nxp-timer: Add the System Timer Module for the s32gx platforms") Signed-off-by: Johan Hovold Signed-off-by: Daniel Lezcano Cc: stable@vger.kernel.org # 6.16 Cc: Daniel Lezcano Link: https://patch.msgid.link/20251017054943.7195-1-johan@kernel.org Stable-dep-of: 6a2416892e89 ("clocksource/drivers/nxp-stm: Prevent driver unbind") Signed-off-by: Sasha Levin --- drivers/clocksource/timer-nxp-stm.c | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/drivers/clocksource/timer-nxp-stm.c b/drivers/clocksource/timer-nxp-stm.c index 16d52167e949be..c320d764b12e21 100644 --- a/drivers/clocksource/timer-nxp-stm.c +++ b/drivers/clocksource/timer-nxp-stm.c @@ -177,15 +177,15 @@ static void nxp_stm_clocksource_resume(struct clocksource *cs) nxp_stm_clocksource_enable(cs); } -static void __init devm_clocksource_unregister(void *data) +static void devm_clocksource_unregister(void *data) { struct stm_timer *stm_timer = data; clocksource_unregister(&stm_timer->cs); } -static int __init nxp_stm_clocksource_init(struct device *dev, struct stm_timer *stm_timer, - const char *name, void __iomem *base, struct clk *clk) +static int nxp_stm_clocksource_init(struct device *dev, struct stm_timer *stm_timer, + const char *name, void __iomem *base, struct clk *clk) { int ret; @@ -296,9 +296,9 @@ static void nxp_stm_clockevent_resume(struct clock_event_device *ced) nxp_stm_module_get(stm_timer); } -static int __init nxp_stm_clockevent_per_cpu_init(struct device *dev, struct stm_timer *stm_timer, - const char *name, void __iomem *base, int irq, - struct clk *clk, int cpu) +static int nxp_stm_clockevent_per_cpu_init(struct device *dev, struct stm_timer *stm_timer, + const char *name, void __iomem *base, int irq, + struct clk *clk, int cpu) { stm_timer->base = base; stm_timer->rate = clk_get_rate(clk); @@ -386,7 +386,7 @@ static irqreturn_t nxp_stm_module_interrupt(int irq, void *dev_id) return IRQ_HANDLED; } -static int __init nxp_stm_timer_probe(struct platform_device *pdev) +static int nxp_stm_timer_probe(struct platform_device *pdev) { struct stm_timer *stm_timer; struct device *dev = &pdev->dev; @@ -482,14 +482,14 @@ static const struct of_device_id nxp_stm_of_match[] = { }; MODULE_DEVICE_TABLE(of, nxp_stm_of_match); -static struct platform_driver nxp_stm_probe = { +static struct platform_driver nxp_stm_driver = { .probe = nxp_stm_timer_probe, .driver = { .name = "nxp-stm", .of_match_table = nxp_stm_of_match, }, }; -module_platform_driver(nxp_stm_probe); +module_platform_driver(nxp_stm_driver); MODULE_DESCRIPTION("NXP System Timer Module driver"); MODULE_LICENSE("GPL"); From 110337fa5f06b7259035d70418eefa65ffdd3a94 Mon Sep 17 00:00:00 2001 From: Johan Hovold Date: Tue, 11 Nov 2025 16:32:26 +0100 Subject: [PATCH 0801/3902] clocksource/drivers/nxp-stm: Prevent driver unbind [ Upstream commit 6a2416892e8942f5e2bfe9b85c0164f410a53a2d ] Clockevents cannot be deregistered so suppress the bind attributes to prevent the driver from being unbound and releasing the underlying resources after registration. Even if the driver can currently only be built-in, also switch to builtin_platform_driver() to prevent it from being unloaded should modular builds ever be enabled. Fixes: cec32ac75827 ("clocksource/drivers/nxp-timer: Add the System Timer Module for the s32gx platforms") Signed-off-by: Johan Hovold Signed-off-by: Daniel Lezcano Link: https://patch.msgid.link/20251111153226.579-4-johan@kernel.org Signed-off-by: Sasha Levin --- drivers/clocksource/timer-nxp-stm.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/drivers/clocksource/timer-nxp-stm.c b/drivers/clocksource/timer-nxp-stm.c index c320d764b12e21..1ab907233f4818 100644 --- a/drivers/clocksource/timer-nxp-stm.c +++ b/drivers/clocksource/timer-nxp-stm.c @@ -487,9 +487,10 @@ static struct platform_driver nxp_stm_driver = { .driver = { .name = "nxp-stm", .of_match_table = nxp_stm_of_match, + .suppress_bind_attrs = true, }, }; -module_platform_driver(nxp_stm_driver); +builtin_platform_driver(nxp_stm_driver); MODULE_DESCRIPTION("NXP System Timer Module driver"); MODULE_LICENSE("GPL"); From 4876a36bc639deff734915bd8b53b76acaf2a6c9 Mon Sep 17 00:00:00 2001 From: Jaroslav Kysela Date: Wed, 26 Nov 2025 10:16:34 +0100 Subject: [PATCH 0802/3902] ASoC: nau8325: use simple i2c probe function [ Upstream commit b4d072c98e47c562834f2a050ca98a1c709ef4f9 ] The i2c probe functions here don't use the id information provided in their second argument, so the single-parameter i2c probe function ("probe_new") can be used instead. This avoids scanning the identifier tables during probes. Signed-off-by: Jaroslav Kysela Link: https://patch.msgid.link/20251126091759.2490019-2-perex@perex.cz Signed-off-by: Mark Brown Stable-dep-of: cd41d3420ef6 ("ASoC: nau8325: add missing build config") Signed-off-by: Sasha Levin --- sound/soc/codecs/nau8325.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/sound/soc/codecs/nau8325.c b/sound/soc/codecs/nau8325.c index 2266f320a8f228..5b3115b0a7e581 100644 --- a/sound/soc/codecs/nau8325.c +++ b/sound/soc/codecs/nau8325.c @@ -829,8 +829,7 @@ static int nau8325_read_device_properties(struct device *dev, return 0; } -static int nau8325_i2c_probe(struct i2c_client *i2c, - const struct i2c_device_id *id) +static int nau8325_i2c_probe(struct i2c_client *i2c) { struct device *dev = &i2c->dev; struct nau8325 *nau8325 = dev_get_platdata(dev); From 1123b37d894768b256a36273f9a397e1e23397a6 Mon Sep 17 00:00:00 2001 From: Krzysztof Kozlowski Date: Wed, 3 Dec 2025 15:06:12 +0100 Subject: [PATCH 0803/3902] ASoC: codecs: nau8325: Silence uninitialized variables warnings commit 2c7e5e17c05f1d5e10e63e1baff2b362cd08dcd6 upstream. clang W=1 builds warn: nau8325.c:430:13: error: variable 'n2_max' is uninitialized when used here [-Werror,-Wuninitialized] which are false positive, because the variables will be always initialized when used (guarded by mclk_max!=0 check). However initializing them upfront makes the code more obvious and easier, plus it silences the warning. Signed-off-by: Krzysztof Kozlowski Link: https://patch.msgid.link/20251203140611.87191-2-krzysztof.kozlowski@oss.qualcomm.com Signed-off-by: Mark Brown Signed-off-by: Greg Kroah-Hartman --- sound/soc/codecs/nau8325.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/sound/soc/codecs/nau8325.c b/sound/soc/codecs/nau8325.c index 5b3115b0a7e581..d396160213f5db 100644 --- a/sound/soc/codecs/nau8325.c +++ b/sound/soc/codecs/nau8325.c @@ -386,7 +386,8 @@ static int nau8325_clksrc_choose(struct nau8325 *nau8325, const struct nau8325_srate_attr **srate_table, int *n1_sel, int *mult_sel, int *n2_sel) { - int i, j, mclk, mclk_max, ratio, ratio_sel, n2_max; + int i, j, mclk, ratio; + int mclk_max = 0, ratio_sel = 0, n2_max = 0; if (!nau8325->mclk || !nau8325->fs) goto proc_err; @@ -408,7 +409,6 @@ static int nau8325_clksrc_choose(struct nau8325 *nau8325, } /* Get MCLK_SRC through 1/N, Multiplier, and then 1/N2. */ - mclk_max = 0; for (i = 0; i < ARRAY_SIZE(mclk_n1_div); i++) { for (j = 0; j < ARRAY_SIZE(mclk_n3_mult); j++) { mclk = nau8325->mclk << mclk_n3_mult[j].param; From 79bc3bdf36bc29037cb4a5fe0cd8e4d93101ef7f Mon Sep 17 00:00:00 2001 From: Jaroslav Kysela Date: Wed, 26 Nov 2025 10:16:35 +0100 Subject: [PATCH 0804/3902] ASoC: nau8325: add missing build config [ Upstream commit cd41d3420ef658b2ca902d7677536ec8e25b610a ] This configuration was missing from the initial commit. Found by Jiri Benc Fixes: c0a3873b9938 ("ASoC: nau8325: new driver") Cc: Seven Lee Signed-off-by: Jaroslav Kysela Link: https://patch.msgid.link/20251126091759.2490019-3-perex@perex.cz Signed-off-by: Mark Brown Signed-off-by: Sasha Levin --- sound/soc/codecs/Kconfig | 5 +++++ sound/soc/codecs/Makefile | 2 ++ 2 files changed, 7 insertions(+) diff --git a/sound/soc/codecs/Kconfig b/sound/soc/codecs/Kconfig index 160c07699a8b72..91ac99bc3edb2e 100644 --- a/sound/soc/codecs/Kconfig +++ b/sound/soc/codecs/Kconfig @@ -170,6 +170,7 @@ config SND_SOC_ALL_CODECS imply SND_SOC_MT6359 imply SND_SOC_MT6660 imply SND_SOC_NAU8315 + imply SND_SOC_NAU8325 imply SND_SOC_NAU8540 imply SND_SOC_NAU8810 imply SND_SOC_NAU8821 @@ -2715,6 +2716,10 @@ config SND_SOC_MT6660 config SND_SOC_NAU8315 tristate "Nuvoton Technology Corporation NAU8315 CODEC" +config SND_SOC_NAU8325 + tristate "Nuvoton Technology Corporation NAU8325 CODEC" + depends on I2C + config SND_SOC_NAU8540 tristate "Nuvoton Technology Corporation NAU85L40 CODEC" depends on I2C diff --git a/sound/soc/codecs/Makefile b/sound/soc/codecs/Makefile index bd95a7c911d5c1..da4077463278e3 100644 --- a/sound/soc/codecs/Makefile +++ b/sound/soc/codecs/Makefile @@ -192,6 +192,7 @@ snd-soc-mt6359-y := mt6359.o snd-soc-mt6359-accdet-y := mt6359-accdet.o snd-soc-mt6660-y := mt6660.o snd-soc-nau8315-y := nau8315.o +snd-soc-nau8325-y := nau8325.o snd-soc-nau8540-y := nau8540.o snd-soc-nau8810-y := nau8810.o snd-soc-nau8821-y := nau8821.o @@ -618,6 +619,7 @@ obj-$(CONFIG_SND_SOC_MT6359) += snd-soc-mt6359.o obj-$(CONFIG_SND_SOC_MT6359_ACCDET) += mt6359-accdet.o obj-$(CONFIG_SND_SOC_MT6660) += snd-soc-mt6660.o obj-$(CONFIG_SND_SOC_NAU8315) += snd-soc-nau8315.o +obj-$(CONFIG_SND_SOC_NAU8325) += snd-soc-nau8325.o obj-$(CONFIG_SND_SOC_NAU8540) += snd-soc-nau8540.o obj-$(CONFIG_SND_SOC_NAU8810) += snd-soc-nau8810.o obj-$(CONFIG_SND_SOC_NAU8821) += snd-soc-nau8821.o From 49e7347f4644d031306d56cb4d51e467cbdcbc69 Mon Sep 17 00:00:00 2001 From: Andreas Gruenbacher Date: Thu, 13 Nov 2025 12:05:37 +0000 Subject: [PATCH 0805/3902] gfs2: Prevent recursive memory reclaim [ Upstream commit 2c5f4a53476e3cab70adc77b38942c066bd2c17c ] Function new_inode() returns a new inode with inode->i_mapping->gfp_mask set to GFP_HIGHUSER_MOVABLE. This value includes the __GFP_FS flag, so allocations in that address space can recurse into filesystem memory reclaim. We don't want that to happen because it can consume a significant amount of stack memory. Worse than that is that it can also deadlock: for example, in several places, gfs2_unstuff_dinode() is called inside filesystem transactions. This calls filemap_grab_folio(), which can allocate a new folio, which can trigger memory reclaim. If memory reclaim recurses into the filesystem and starts another transaction, a deadlock will ensue. To fix these kinds of problems, prevent memory reclaim from recursing into filesystem code by making sure that the gfp_mask of inode address spaces doesn't include __GFP_FS. The "meta" and resource group address spaces were already using GFP_NOFS as their gfp_mask (which doesn't include __GFP_FS). The default value of GFP_HIGHUSER_MOVABLE is less restrictive than GFP_NOFS, though. To avoid being overly limiting, use the default value and only knock off the __GFP_FS flag. I'm not sure if this will actually make a difference, but it also shouldn't hurt. This patch is loosely based on commit ad22c7a043c2 ("xfs: prevent stack overflows from page cache allocation"). Fixes xfstest generic/273. Fixes: dc0b9435238c ("gfs: Don't use GFP_NOFS in gfs2_unstuff_dinode") Reviewed-by: Andrew Price Signed-off-by: Andreas Gruenbacher Signed-off-by: Sasha Levin --- fs/gfs2/glock.c | 5 ++++- fs/gfs2/inode.c | 15 +++++++++++++++ fs/gfs2/inode.h | 1 + fs/gfs2/ops_fstype.c | 2 +- 4 files changed, 21 insertions(+), 2 deletions(-) diff --git a/fs/gfs2/glock.c b/fs/gfs2/glock.c index b677c0e6b9ab30..9f2eb7e385695d 100644 --- a/fs/gfs2/glock.c +++ b/fs/gfs2/glock.c @@ -1211,10 +1211,13 @@ int gfs2_glock_get(struct gfs2_sbd *sdp, u64 number, mapping = gfs2_glock2aspace(gl); if (mapping) { + gfp_t gfp_mask; + mapping->a_ops = &gfs2_meta_aops; mapping->host = sdp->sd_inode; mapping->flags = 0; - mapping_set_gfp_mask(mapping, GFP_NOFS); + gfp_mask = mapping_gfp_mask(sdp->sd_inode->i_mapping); + mapping_set_gfp_mask(mapping, gfp_mask); mapping->i_private_data = NULL; mapping->writeback_index = 0; } diff --git a/fs/gfs2/inode.c b/fs/gfs2/inode.c index 8a7ed80d9f2d6e..d7e35a05c1610c 100644 --- a/fs/gfs2/inode.c +++ b/fs/gfs2/inode.c @@ -89,6 +89,19 @@ static int iget_set(struct inode *inode, void *opaque) return 0; } +void gfs2_setup_inode(struct inode *inode) +{ + gfp_t gfp_mask; + + /* + * Ensure all page cache allocations are done from GFP_NOFS context to + * prevent direct reclaim recursion back into the filesystem and blowing + * stacks or deadlocking. + */ + gfp_mask = mapping_gfp_mask(inode->i_mapping); + mapping_set_gfp_mask(inode->i_mapping, gfp_mask & ~__GFP_FS); +} + /** * gfs2_inode_lookup - Lookup an inode * @sb: The super block @@ -132,6 +145,7 @@ struct inode *gfs2_inode_lookup(struct super_block *sb, unsigned int type, struct gfs2_glock *io_gl; int extra_flags = 0; + gfs2_setup_inode(inode); error = gfs2_glock_get(sdp, no_addr, &gfs2_inode_glops, CREATE, &ip->i_gl); if (unlikely(error)) @@ -752,6 +766,7 @@ static int gfs2_create_inode(struct inode *dir, struct dentry *dentry, error = -ENOMEM; if (!inode) goto fail_gunlock; + gfs2_setup_inode(inode); ip = GFS2_I(inode); error = posix_acl_create(dir, &mode, &default_acl, &acl); diff --git a/fs/gfs2/inode.h b/fs/gfs2/inode.h index e43f08eb26e729..2fcd96dd136139 100644 --- a/fs/gfs2/inode.h +++ b/fs/gfs2/inode.h @@ -86,6 +86,7 @@ static inline int gfs2_check_internal_file_size(struct inode *inode, return -EIO; } +void gfs2_setup_inode(struct inode *inode); struct inode *gfs2_inode_lookup(struct super_block *sb, unsigned type, u64 no_addr, u64 no_formal_ino, unsigned int blktype); diff --git a/fs/gfs2/ops_fstype.c b/fs/gfs2/ops_fstype.c index aa15183f9a168e..1a2db8053da04c 100644 --- a/fs/gfs2/ops_fstype.c +++ b/fs/gfs2/ops_fstype.c @@ -1183,7 +1183,7 @@ static int gfs2_fill_super(struct super_block *sb, struct fs_context *fc) mapping = gfs2_aspace(sdp); mapping->a_ops = &gfs2_rgrp_aops; - mapping_set_gfp_mask(mapping, GFP_NOFS); + gfs2_setup_inode(sdp->sd_inode); error = init_names(sdp, silent); if (error) From ee0fa63171ba80f228141e2ba6743006f9e49437 Mon Sep 17 00:00:00 2001 From: Shengjiu Wang Date: Wed, 26 Nov 2025 14:45:09 +0800 Subject: [PATCH 0806/3902] ASoC: fsl_xcvr: clear the channel status control memory [ Upstream commit 73b97d46dde64fa184d47865d4a532d818c3a007 ] memset_io() writes memory byte by byte with __raw_writeb() on the arm platform if the size is word. but XCVR data RAM memory can't be accessed with byte address, so with memset_io() the channel status control memory is not really cleared, use writel_relaxed() instead. Fixes: 28564486866f ("ASoC: fsl_xcvr: Add XCVR ASoC CPU DAI driver") Signed-off-by: Shengjiu Wang Link: https://patch.msgid.link/20251126064509.1900974-1-shengjiu.wang@nxp.com Signed-off-by: Mark Brown Signed-off-by: Sasha Levin --- sound/soc/fsl/fsl_xcvr.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sound/soc/fsl/fsl_xcvr.c b/sound/soc/fsl/fsl_xcvr.c index 5d804860f7d8c8..58db4906a01d50 100644 --- a/sound/soc/fsl/fsl_xcvr.c +++ b/sound/soc/fsl/fsl_xcvr.c @@ -1421,7 +1421,7 @@ static irqreturn_t irq0_isr(int irq, void *devid) bitrev32(val); } /* clear CS control register */ - memset_io(reg_ctrl, 0, sizeof(val)); + writel_relaxed(0, reg_ctrl); } } else { regmap_read(xcvr->regmap, FSL_XCVR_RX_CS_DATA_0, From c9083b2fce6b3bddceba7c4166ce3013f2941e0e Mon Sep 17 00:00:00 2001 From: Alexandre Courbot Date: Thu, 6 Nov 2025 11:40:54 +0900 Subject: [PATCH 0807/3902] firmware_loader: make RUST_FW_LOADER_ABSTRACTIONS select FW_LOADER [ Upstream commit 9906efa545d1d2cf25a614eeb219d3f8d5a302cd ] The use of firmware_loader is an implementation detail of drivers rather than a dependency. FW_LOADER is typically selected rather than depended on; the Rust abstractions should do the same thing. Fixes: de6582833db0 ("rust: add firmware abstractions") Signed-off-by: Alexandre Courbot Link: https://patch.msgid.link/20251106-b4-select-rust-fw-v3-1-771172257755@nvidia.com Signed-off-by: Greg Kroah-Hartman Signed-off-by: Sasha Levin --- drivers/base/firmware_loader/Kconfig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/base/firmware_loader/Kconfig b/drivers/base/firmware_loader/Kconfig index 752b9a9bea0388..15eff8a4b50533 100644 --- a/drivers/base/firmware_loader/Kconfig +++ b/drivers/base/firmware_loader/Kconfig @@ -38,7 +38,7 @@ config FW_LOADER_DEBUG config RUST_FW_LOADER_ABSTRACTIONS bool "Rust Firmware Loader abstractions" depends on RUST - depends on FW_LOADER=y + select FW_LOADER help This enables the Rust abstractions for the firmware loader API. From 2f62a4e395883ec5a25b6c5a832373459e22f7f3 Mon Sep 17 00:00:00 2001 From: Haotian Zhang Date: Fri, 21 Nov 2025 14:40:27 +0800 Subject: [PATCH 0808/3902] greybus: gb-beagleplay: Fix timeout handling in bootloader functions [ Upstream commit e6df0f649cff08da7a2feb6d963b39076ca129f9 ] wait_for_completion_timeout() returns the remaining jiffies (at least 1) on success or 0 on timeout, but never negative error codes. The current code incorrectly checks for negative values, causing timeouts to be ignored and treated as success. Check for a zero return value to correctly identify and handle timeout events. Fixes: 0cf7befa3ea2 ("greybus: gb-beagleplay: Add firmware upload API") Signed-off-by: Haotian Zhang Link: https://patch.msgid.link/20251121064027.571-1-vulab@iscas.ac.cn Signed-off-by: Greg Kroah-Hartman Signed-off-by: Sasha Levin --- drivers/greybus/gb-beagleplay.c | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/drivers/greybus/gb-beagleplay.c b/drivers/greybus/gb-beagleplay.c index 9610f878da1b68..87186f891a6acb 100644 --- a/drivers/greybus/gb-beagleplay.c +++ b/drivers/greybus/gb-beagleplay.c @@ -644,8 +644,8 @@ static int cc1352_bootloader_wait_for_ack(struct gb_beagleplay *bg) ret = wait_for_completion_timeout( &bg->fwl_ack_com, msecs_to_jiffies(CC1352_BOOTLOADER_TIMEOUT)); - if (ret < 0) - return dev_err_probe(&bg->sd->dev, ret, + if (!ret) + return dev_err_probe(&bg->sd->dev, -ETIMEDOUT, "Failed to acquire ack semaphore"); switch (READ_ONCE(bg->fwl_ack)) { @@ -683,8 +683,8 @@ static int cc1352_bootloader_get_status(struct gb_beagleplay *bg) ret = wait_for_completion_timeout( &bg->fwl_cmd_response_com, msecs_to_jiffies(CC1352_BOOTLOADER_TIMEOUT)); - if (ret < 0) - return dev_err_probe(&bg->sd->dev, ret, + if (!ret) + return dev_err_probe(&bg->sd->dev, -ETIMEDOUT, "Failed to acquire last status semaphore"); switch (READ_ONCE(bg->fwl_cmd_response)) { @@ -768,8 +768,8 @@ static int cc1352_bootloader_crc32(struct gb_beagleplay *bg, u32 *crc32) ret = wait_for_completion_timeout( &bg->fwl_cmd_response_com, msecs_to_jiffies(CC1352_BOOTLOADER_TIMEOUT)); - if (ret < 0) - return dev_err_probe(&bg->sd->dev, ret, + if (!ret) + return dev_err_probe(&bg->sd->dev, -ETIMEDOUT, "Failed to acquire last status semaphore"); *crc32 = READ_ONCE(bg->fwl_cmd_response); From eda80f733fbd5744ce6c1b3b9e9b2daf705dcf0f Mon Sep 17 00:00:00 2001 From: Christoph Hellwig Date: Thu, 20 Nov 2025 07:47:22 +0100 Subject: [PATCH 0809/3902] fs: refactor file timestamp update logic [ Upstream commit 3cd9a42f1b5e34d3972237cbf8541af60844cbd4 ] Currently the two high-level APIs use two helper functions to implement almost all of the logic. Refactor the two helpers and the common logic into a new file_update_time_flags routine that gets the iocb flags or 0 in case of file_update_time passed so that the entire logic is contained in a single function and can be easily understood and modified. Signed-off-by: Christoph Hellwig Link: https://patch.msgid.link/20251120064859.2911749-2-hch@lst.de Reviewed-by: Chaitanya Kulkarni Reviewed-by: Jeff Layton Reviewed-by: Jan Kara Signed-off-by: Christian Brauner Stable-dep-of: 7f30e7a42371 ("fs: lift the FMODE_NOCMTIME check into file_update_time_flags") Signed-off-by: Sasha Levin --- fs/inode.c | 54 +++++++++++++++++------------------------------------- 1 file changed, 17 insertions(+), 37 deletions(-) diff --git a/fs/inode.c b/fs/inode.c index cff1d3af0d5772..540f4a28c202da 100644 --- a/fs/inode.c +++ b/fs/inode.c @@ -2322,10 +2322,12 @@ struct timespec64 current_time(struct inode *inode) } EXPORT_SYMBOL(current_time); -static int inode_needs_update_time(struct inode *inode) +static int file_update_time_flags(struct file *file, unsigned int flags) { + struct inode *inode = file_inode(file); struct timespec64 now, ts; - int sync_it = 0; + int sync_mode = 0; + int ret = 0; /* First try to exhaust all avenues to not sync */ if (IS_NOCMTIME(inode)) @@ -2335,29 +2337,23 @@ static int inode_needs_update_time(struct inode *inode) ts = inode_get_mtime(inode); if (!timespec64_equal(&ts, &now)) - sync_it |= S_MTIME; - + sync_mode |= S_MTIME; ts = inode_get_ctime(inode); if (!timespec64_equal(&ts, &now)) - sync_it |= S_CTIME; - + sync_mode |= S_CTIME; if (IS_I_VERSION(inode) && inode_iversion_need_inc(inode)) - sync_it |= S_VERSION; + sync_mode |= S_VERSION; - return sync_it; -} - -static int __file_update_time(struct file *file, int sync_mode) -{ - int ret = 0; - struct inode *inode = file_inode(file); + if (!sync_mode) + return 0; - /* try to update time settings */ - if (!mnt_get_write_access_file(file)) { - ret = inode_update_time(inode, sync_mode); - mnt_put_write_access_file(file); - } + if (flags & IOCB_NOWAIT) + return -EAGAIN; + if (mnt_get_write_access_file(file)) + return 0; + ret = inode_update_time(inode, sync_mode); + mnt_put_write_access_file(file); return ret; } @@ -2377,14 +2373,7 @@ static int __file_update_time(struct file *file, int sync_mode) */ int file_update_time(struct file *file) { - int ret; - struct inode *inode = file_inode(file); - - ret = inode_needs_update_time(inode); - if (ret <= 0) - return ret; - - return __file_update_time(file, ret); + return file_update_time_flags(file, 0); } EXPORT_SYMBOL(file_update_time); @@ -2406,7 +2395,6 @@ EXPORT_SYMBOL(file_update_time); static int file_modified_flags(struct file *file, int flags) { int ret; - struct inode *inode = file_inode(file); /* * Clear the security bits if the process is not being run by root. @@ -2415,17 +2403,9 @@ static int file_modified_flags(struct file *file, int flags) ret = file_remove_privs_flags(file, flags); if (ret) return ret; - if (unlikely(file->f_mode & FMODE_NOCMTIME)) return 0; - - ret = inode_needs_update_time(inode); - if (ret <= 0) - return ret; - if (flags & IOCB_NOWAIT) - return -EAGAIN; - - return __file_update_time(file, ret); + return file_update_time_flags(file, flags); } /** From e901d77f8994362a249714804d47560a92dff911 Mon Sep 17 00:00:00 2001 From: Christoph Hellwig Date: Thu, 20 Nov 2025 07:47:23 +0100 Subject: [PATCH 0810/3902] fs: lift the FMODE_NOCMTIME check into file_update_time_flags [ Upstream commit 7f30e7a42371af4bba53f9a875a0d320cead9f4b ] FMODE_NOCMTIME used to be just a hack for the legacy XFS handle-based "invisible I/O", but commit e5e9b24ab8fa ("nfsd: freeze c/mtime updates with outstanding WRITE_ATTRS delegation") started using it from generic callers. I'm not sure other file systems are actually read for this in general, so the above commit should get a closer look, but for it to make any sense, file_update_time needs to respect the flag. Lift the check from file_modified_flags to file_update_time so that users of file_update_time inherit the behavior and so that all the checks are done in one place. Fixes: e5e9b24ab8fa ("nfsd: freeze c/mtime updates with outstanding WRITE_ATTRS delegation") Signed-off-by: Christoph Hellwig Link: https://patch.msgid.link/20251120064859.2911749-3-hch@lst.de Reviewed-by: Chaitanya Kulkarni Reviewed-by: Jeff Layton Reviewed-by: Jan Kara Signed-off-by: Christian Brauner Signed-off-by: Sasha Levin --- fs/inode.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/fs/inode.c b/fs/inode.c index 540f4a28c202da..2c55ec49b02397 100644 --- a/fs/inode.c +++ b/fs/inode.c @@ -2332,6 +2332,8 @@ static int file_update_time_flags(struct file *file, unsigned int flags) /* First try to exhaust all avenues to not sync */ if (IS_NOCMTIME(inode)) return 0; + if (unlikely(file->f_mode & FMODE_NOCMTIME)) + return 0; now = current_time(inode); @@ -2403,8 +2405,6 @@ static int file_modified_flags(struct file *file, int flags) ret = file_remove_privs_flags(file, flags); if (ret) return ret; - if (unlikely(file->f_mode & FMODE_NOCMTIME)) - return 0; return file_update_time_flags(file, flags); } From 2f4f72e580b5c90a4171a2055afcdd9676c68248 Mon Sep 17 00:00:00 2001 From: Christophe JAILLET Date: Sat, 8 Nov 2025 08:14:04 +0100 Subject: [PATCH 0811/3902] misc: rp1: Fix an error handling path in rp1_probe() [ Upstream commit 43cd4b634ef90c4e2ff75eaeb361786fa04c8874 ] When DT is used to get the reference of 'rp1_node', it should be released when not needed anymore, otherwise it is leaking. In such a case, add the missing of_node_put() call at the end of the probe, as already done in the error handling path. Fixes: 49d63971f963 ("misc: rp1: RaspberryPi RP1 misc driver") Signed-off-by: Christophe JAILLET Reviewed-by: Andrea della Porta Link: https://patch.msgid.link/9bc1206de787fa86384f3e5ba0a8027947bc00ff.1762585959.git.christophe.jaillet@wanadoo.fr Signed-off-by: Greg Kroah-Hartman Signed-off-by: Sasha Levin --- 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 803832006ec876..a342bcc6164bb6 100644 --- a/drivers/misc/rp1/rp1_pci.c +++ b/drivers/misc/rp1/rp1_pci.c @@ -289,6 +289,9 @@ static int rp1_probe(struct pci_dev *pdev, const struct pci_device_id *id) goto err_unload_overlay; } + if (skip_ovl) + of_node_put(rp1_node); + return 0; err_unload_overlay: From 843bdaf0bfdd43b85b48f75845d5999c6e8a24ea Mon Sep 17 00:00:00 2001 From: Dan Carpenter Date: Fri, 31 Oct 2025 16:02:25 +0300 Subject: [PATCH 0812/3902] drm/amd/display: Fix logical vs bitwise bug in get_embedded_panel_info_v2_1() [ Upstream commit 1a79482699b4d1e43948d14f0c7193dc1dcad858 ] The .H_SYNC_POLARITY and .V_SYNC_POLARITY variables are 1 bit bitfields of a u32. The ATOM_HSYNC_POLARITY define is 0x2 and the ATOM_VSYNC_POLARITY is 0x4. When we do a bitwise negate of 0, 2, or 4 then the last bit is always 1 so this code always sets .H_SYNC_POLARITY and .V_SYNC_POLARITY to true. This code is instead intended to check if the ATOM_HSYNC_POLARITY or ATOM_VSYNC_POLARITY flags are set and reverse the result. In other words, it's supposed to be a logical negate instead of a bitwise negate. Fixes: ae79c310b1a6 ("drm/amd/display: Add DCE12 bios parser support") Signed-off-by: Dan Carpenter Reviewed-by: Alex Hung Signed-off-by: Alex Deucher Signed-off-by: Sasha Levin --- drivers/gpu/drm/amd/display/dc/bios/bios_parser2.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/drivers/gpu/drm/amd/display/dc/bios/bios_parser2.c b/drivers/gpu/drm/amd/display/dc/bios/bios_parser2.c index 04eb647acc4e1a..550a9f1d03f82d 100644 --- a/drivers/gpu/drm/amd/display/dc/bios/bios_parser2.c +++ b/drivers/gpu/drm/amd/display/dc/bios/bios_parser2.c @@ -1480,10 +1480,10 @@ static enum bp_result get_embedded_panel_info_v2_1( /* not provided by VBIOS */ info->lcd_timing.misc_info.HORIZONTAL_CUT_OFF = 0; - info->lcd_timing.misc_info.H_SYNC_POLARITY = ~(uint32_t) (lvds->lcd_timing.miscinfo - & ATOM_HSYNC_POLARITY); - info->lcd_timing.misc_info.V_SYNC_POLARITY = ~(uint32_t) (lvds->lcd_timing.miscinfo - & ATOM_VSYNC_POLARITY); + info->lcd_timing.misc_info.H_SYNC_POLARITY = !(lvds->lcd_timing.miscinfo & + ATOM_HSYNC_POLARITY); + info->lcd_timing.misc_info.V_SYNC_POLARITY = !(lvds->lcd_timing.miscinfo & + ATOM_VSYNC_POLARITY); /* not provided by VBIOS */ info->lcd_timing.misc_info.VERTICAL_CUT_OFF = 0; From 627a55ec0039a9e1aef71614016e8b2cc74a3917 Mon Sep 17 00:00:00 2001 From: Eric Huang Date: Wed, 19 Nov 2025 15:07:10 -0500 Subject: [PATCH 0813/3902] drm/amdkfd: assign AID to uuid in topology for SPX mode [ Upstream commit 089702632f1dd38fadc9af13816485d6aa518dbb ] XCD id is assigned to uuid, which causes some performance drop in SPX mode, assigning AID back will resolve the issue. Fixes: 3a75edf93aae ("drm/amdkfd: set uuid for each partition in topology") Signed-off-by: Eric Huang Reviewed-by: Harish Kasiviswanathan Signed-off-by: Alex Deucher Signed-off-by: Sasha Levin --- drivers/gpu/drm/amd/amdkfd/kfd_topology.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/drivers/gpu/drm/amd/amdkfd/kfd_topology.c b/drivers/gpu/drm/amd/amdkfd/kfd_topology.c index 5c98746eb72dfc..811636af14eaac 100644 --- a/drivers/gpu/drm/amd/amdkfd/kfd_topology.c +++ b/drivers/gpu/drm/amd/amdkfd/kfd_topology.c @@ -530,7 +530,9 @@ static ssize_t node_show(struct kobject *kobj, struct attribute *attr, sysfs_show_32bit_prop(buffer, offs, "sdma_fw_version", dev->gpu->kfd->sdma_fw_version); sysfs_show_64bit_prop(buffer, offs, "unique_id", - dev->gpu->xcp ? + dev->gpu->xcp && + (dev->gpu->xcp->xcp_mgr->mode != + AMDGPU_SPX_PARTITION_MODE) ? dev->gpu->xcp->unique_id : dev->gpu->adev->unique_id); sysfs_show_32bit_prop(buffer, offs, "num_xcc", From 17fc4443ee121b3703cb0a13710b296c68b674ca Mon Sep 17 00:00:00 2001 From: Haotian Zhang Date: Thu, 27 Nov 2025 00:26:02 +0800 Subject: [PATCH 0814/3902] hwmon: sy7636a: Fix regulator_enable resource leak on error path [ Upstream commit 2f88425ef590b7fcc2324334b342e048edc144a9 ] In sy7636a_sensor_probe(), regulator_enable() is called but if devm_hwmon_device_register_with_info() fails, the function returns without calling regulator_disable(), leaving the regulator enabled and leaking the reference count. Switch to devm_regulator_get_enable() to automatically manage the regulator resource. Fixes: de34a4053250 ("hwmon: sy7636a: Add temperature driver for sy7636a") Suggested-by: Guenter Roeck Signed-off-by: Haotian Zhang Link: https://lore.kernel.org/r/20251126162602.2086-1-vulab@iscas.ac.cn Signed-off-by: Guenter Roeck Signed-off-by: Sasha Levin --- drivers/hwmon/sy7636a-hwmon.c | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/drivers/hwmon/sy7636a-hwmon.c b/drivers/hwmon/sy7636a-hwmon.c index a12fc0ce70e76e..d51daaf63d632e 100644 --- a/drivers/hwmon/sy7636a-hwmon.c +++ b/drivers/hwmon/sy7636a-hwmon.c @@ -66,18 +66,13 @@ static const struct hwmon_chip_info sy7636a_chip_info = { static int sy7636a_sensor_probe(struct platform_device *pdev) { struct regmap *regmap = dev_get_regmap(pdev->dev.parent, NULL); - struct regulator *regulator; struct device *hwmon_dev; int err; if (!regmap) return -EPROBE_DEFER; - regulator = devm_regulator_get(&pdev->dev, "vcom"); - if (IS_ERR(regulator)) - return PTR_ERR(regulator); - - err = regulator_enable(regulator); + err = devm_regulator_get_enable(&pdev->dev, "vcom"); if (err) return err; From 7639d398316643e385ec27b4a49fa625f08a278b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20Rebe?= Date: Wed, 26 Nov 2025 16:55:13 +0100 Subject: [PATCH 0815/3902] ACPI: processor_core: fix map_x2apic_id for amd-pstate on am4 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [ Upstream commit 17e7972979e147cc51d4a165e6b6b0f93273ca68 ] On all AMD AM4 systems I have seen, e.g ASUS X470-i, Pro WS X570 Ace and equivalent Gigabyte, amd-pstate does not initialize when the x2apic is enabled in the BIOS. Kernel debug messages include: [ 0.315438] acpi LNXCPU:00: Failed to get CPU physical ID. [ 0.354756] ACPI CPPC: No CPC descriptor for CPU:0 [ 0.714951] amd_pstate: the _CPC object is not present in SBIOS or ACPI disabled I tracked this down to map_x2apic_id() checking device_declaration passed in via the type argument of acpi_get_phys_id() via map_madt_entry() while map_lapic_id() does not. It appears these BIOSes use Processor statements for declaring the CPUs in the ACPI namespace instead of processor device objects (which should have been used). CPU declarations via Processor statements were deprecated in ACPI 6.0 that was released 10 years ago. They should not be used any more in any contemporary platform firmware. I tried to contact Asus support multiple times, but never received a reply nor did any BIOS update ever change this. Fix amd-pstate w/ x2apic on am4 by allowing map_x2apic_id() to work with CPUs declared via Processor statements for IDs less than 255, which is consistent with ACPI 5.0 that still allowed Processor statements to be used for declaring CPUs. Fixes: 7237d3de78ff ("x86, ACPI: add support for x2apic ACPI extensions") Signed-off-by: René Rebe [ rjw: Changelog edits ] Link: https://patch.msgid.link/20251126.165513.1373131139292726554.rene@exactco.de Signed-off-by: Rafael J. Wysocki Signed-off-by: Sasha Levin --- drivers/acpi/processor_core.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/acpi/processor_core.c b/drivers/acpi/processor_core.c index 9b6b71a2ffb546..a4498357bd165a 100644 --- a/drivers/acpi/processor_core.c +++ b/drivers/acpi/processor_core.c @@ -54,7 +54,7 @@ static int map_x2apic_id(struct acpi_subtable_header *entry, if (!(apic->lapic_flags & ACPI_MADT_ENABLED)) return -ENODEV; - if (device_declaration && (apic->uid == acpi_id)) { + if (apic->uid == acpi_id && (device_declaration || acpi_id < 255)) { *apic_id = apic->local_apic_id; return 0; } From 4e2a8fb1ce6ce2483da25d056fd61cc25bdbaf2b Mon Sep 17 00:00:00 2001 From: Kevin Brodsky Date: Wed, 26 Nov 2025 12:48:35 +0000 Subject: [PATCH 0816/3902] ublk: prevent invalid access with DEBUG [ Upstream commit c6a45ee7607de3a350008630f4369b1b5ac80884 ] ublk_ch_uring_cmd_local() may jump to the out label before initialising the io pointer. This will cause trouble if DEBUG is defined, because the pr_devel() call dereferences io. Clang reports: drivers/block/ublk_drv.c:2403:6: error: variable 'io' is used uninitialized whenever 'if' condition is true [-Werror,-Wsometimes-uninitialized] 2403 | if (tag >= ub->dev_info.queue_depth) | ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ drivers/block/ublk_drv.c:2492:32: note: uninitialized use occurs here 2492 | __func__, cmd_op, tag, ret, io->flags); | Fix this by initialising io to NULL and checking it before dereferencing it. Signed-off-by: Kevin Brodsky Fixes: 71f28f3136af ("ublk_drv: add io_uring based userspace block driver") Reviewed-by: Caleb Sander Mateos Signed-off-by: Jens Axboe Signed-off-by: Sasha Levin --- drivers/block/ublk_drv.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/block/ublk_drv.c b/drivers/block/ublk_drv.c index 0c74a41a675300..359564c40cb50f 100644 --- a/drivers/block/ublk_drv.c +++ b/drivers/block/ublk_drv.c @@ -2367,7 +2367,7 @@ static int ublk_ch_uring_cmd_local(struct io_uring_cmd *cmd, u16 buf_idx = UBLK_INVALID_BUF_IDX; struct ublk_device *ub = cmd->file->private_data; struct ublk_queue *ubq; - struct ublk_io *io; + struct ublk_io *io = NULL; u32 cmd_op = cmd->cmd_op; u16 q_id = READ_ONCE(ub_src->q_id); u16 tag = READ_ONCE(ub_src->tag); @@ -2488,7 +2488,7 @@ static int ublk_ch_uring_cmd_local(struct io_uring_cmd *cmd, out: pr_devel("%s: complete: cmd op %d, tag %d ret %x io_flags %x\n", - __func__, cmd_op, tag, ret, io->flags); + __func__, cmd_op, tag, ret, io ? io->flags : 0); return ret; } From c1b149b698e29cf576744773fd18b4662cc4c47c Mon Sep 17 00:00:00 2001 From: Matthieu Buffet Date: Mon, 27 Oct 2025 02:14:40 +0100 Subject: [PATCH 0817/3902] selftests/landlock: Fix makefile header list MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [ Upstream commit e61462232a58bddd818fa6a913a9a2e76fd3634f ] Make all headers part of make's dependencies computations. Otherwise, updating audit.h, common.h, scoped_base_variants.h, scoped_common.h, scoped_multiple_domain_variants.h, or wrappers.h, re-running make and running selftests could lead to testing stale headers. Fixes: 6a500b22971c ("selftests/landlock: Add tests for audit flags and domain IDs") Fixes: fefcf0f7cf47 ("selftests/landlock: Test abstract UNIX socket scoping") Fixes: 5147779d5e1b ("selftests/landlock: Add wrappers.h") Signed-off-by: Matthieu Buffet Link: https://lore.kernel.org/r/20251027011440.1838514-1-matthieu@buffet.re Signed-off-by: Mickaël Salaün Signed-off-by: Sasha Levin --- tools/testing/selftests/landlock/Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/testing/selftests/landlock/Makefile b/tools/testing/selftests/landlock/Makefile index a3f449914bf93a..044b83bde16ebe 100644 --- a/tools/testing/selftests/landlock/Makefile +++ b/tools/testing/selftests/landlock/Makefile @@ -4,7 +4,7 @@ CFLAGS += -Wall -O2 $(KHDR_INCLUDES) -LOCAL_HDRS += common.h +LOCAL_HDRS += $(wildcard *.h) src_test := $(wildcard *_test.c) From f0022551745d72fc0e7bc8601234d690dee2178d Mon Sep 17 00:00:00 2001 From: Edward Adam Davis Date: Sun, 16 Nov 2025 22:58:13 +0800 Subject: [PATCH 0818/3902] bpf: Fix exclusive map memory leak [ Upstream commit 688b745401ab16e2e1a3b504863f0a45fd345638 ] When excl_prog_hash is 0 and excl_prog_hash_size is non-zero, the map also needs to be freed. Otherwise, the map memory will not be reclaimed, just like the memory leak problem reported by syzbot [1]. syzbot reported: BUG: memory leak backtrace (crc 7b9fb9b4): map_create+0x322/0x11e0 kernel/bpf/syscall.c:1512 __sys_bpf+0x3556/0x3610 kernel/bpf/syscall.c:6131 Fixes: baefdbdf6812 ("bpf: Implement exclusive map creation") Reported-by: syzbot+cf08c551fecea9fd1320@syzkaller.appspotmail.com Closes: https://syzkaller.appspot.com/bug?extid=cf08c551fecea9fd1320 Tested-by: syzbot+cf08c551fecea9fd1320@syzkaller.appspotmail.com Signed-off-by: Edward Adam Davis Acked-by: Yonghong Song Link: https://lore.kernel.org/r/tencent_3F226F882CE56DCC94ACE90EED1ECCFC780A@qq.com Signed-off-by: Alexei Starovoitov Signed-off-by: Sasha Levin --- kernel/bpf/syscall.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/kernel/bpf/syscall.c b/kernel/bpf/syscall.c index 15f9afdbfc275a..df219e72590997 100644 --- a/kernel/bpf/syscall.c +++ b/kernel/bpf/syscall.c @@ -1585,7 +1585,8 @@ static int map_create(union bpf_attr *attr, bpfptr_t uattr) goto free_map; } } else if (attr->excl_prog_hash_size) { - return -EINVAL; + err = -EINVAL; + goto free_map; } err = security_bpf_map_create(map, attr, token, uattr.is_kernel); From a537ffa7f44b5dcaa8a8f64c9bdeafc016b8a69c Mon Sep 17 00:00:00 2001 From: Yongjian Sun Date: Thu, 6 Nov 2025 14:06:14 +0800 Subject: [PATCH 0819/3902] ext4: improve integrity checking in __mb_check_buddy by enhancing order-0 validation [ Upstream commit d9ee3ff810f1cc0e253c9f2b17b668b973cb0e06 ] When the MB_CHECK_ASSERT macro is enabled, we found that the current validation logic in __mb_check_buddy has a gap in detecting certain invalid buddy states, particularly related to order-0 (bitmap) bits. The original logic consists of three steps: 1. Validates higher-order buddies: if a higher-order bit is set, at most one of the two corresponding lower-order bits may be free; if a higher-order bit is clear, both lower-order bits must be allocated (and their bitmap bits must be 0). 2. For any set bit in order-0, ensures all corresponding higher-order bits are not free. 3. Verifies that all preallocated blocks (pa) in the group have pa_pstart within bounds and their bitmap bits marked as allocated. However, this approach fails to properly validate cases where order-0 bits are incorrectly cleared (0), allowing some invalid configurations to pass: corrupt integral order 3 1 1 order 2 1 1 1 1 order 1 1 1 1 1 1 1 1 1 order 0 0 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 Here we get two adjacent free blocks at order-0 with inconsistent higher-order state, and the right one shows the correct scenario. The root cause is insufficient validation of order-0 zero bits. To fix this and improve completeness without significant performance cost, we refine the logic: 1. Maintain the top-down higher-order validation, but we no longer check the cases where the higher-order bit is 0, as this case will be covered in step 2. 2. Enhance order-0 checking by examining pairs of bits: - If either bit in a pair is set (1), all corresponding higher-order bits must not be free. - If both bits are clear (0), then exactly one of the corresponding higher-order bits must be free 3. Keep the preallocation (pa) validation unchanged. This change closes the validation gap, ensuring illegal buddy states involving order-0 are correctly detected, while removing redundant checks and maintaining efficiency. Fixes: c9de560ded61f ("ext4: Add multi block allocator for ext4") Suggested-by: Jan Kara Signed-off-by: Yongjian Sun Reviewed-by: Baokun Li Reviewed-by: Jan Kara Message-ID: <20251106060614.631382-3-sunyongjian@huaweicloud.com> Signed-off-by: Theodore Ts'o Signed-off-by: Sasha Levin --- fs/ext4/mballoc.c | 49 +++++++++++++++++++++++++++++++---------------- 1 file changed, 32 insertions(+), 17 deletions(-) diff --git a/fs/ext4/mballoc.c b/fs/ext4/mballoc.c index 9087183602e44e..c56b51e1162ebd 100644 --- a/fs/ext4/mballoc.c +++ b/fs/ext4/mballoc.c @@ -682,6 +682,24 @@ do { \ } \ } while (0) +/* + * Perform buddy integrity check with the following steps: + * + * 1. Top-down validation (from highest order down to order 1, excluding order-0 bitmap): + * For each pair of adjacent orders, if a higher-order bit is set (indicating a free block), + * at most one of the two corresponding lower-order bits may be clear (free). + * + * 2. Order-0 (bitmap) validation, performed on bit pairs: + * - If either bit in a pair is set (1, allocated), then all corresponding higher-order bits + * must not be free (0). + * - If both bits in a pair are clear (0, free), then exactly one of the corresponding + * higher-order bits must be free (0). + * + * 3. Preallocation (pa) list validation: + * For each preallocated block (pa) in the group: + * - Verify that pa_pstart falls within the bounds of this block group. + * - Ensure the corresponding bit(s) in the order-0 bitmap are marked as allocated (1). + */ static void __mb_check_buddy(struct ext4_buddy *e4b, char *file, const char *function, int line) { @@ -723,15 +741,6 @@ static void __mb_check_buddy(struct ext4_buddy *e4b, char *file, continue; } - /* both bits in buddy2 must be 1 */ - MB_CHECK_ASSERT(mb_test_bit(i << 1, buddy2)); - MB_CHECK_ASSERT(mb_test_bit((i << 1) + 1, buddy2)); - - for (j = 0; j < (1 << order); j++) { - k = (i * (1 << order)) + j; - MB_CHECK_ASSERT( - !mb_test_bit(k, e4b->bd_bitmap)); - } count++; } MB_CHECK_ASSERT(e4b->bd_info->bb_counters[order] == count); @@ -747,15 +756,21 @@ static void __mb_check_buddy(struct ext4_buddy *e4b, char *file, fragments++; fstart = i; } - continue; + } else { + fstart = -1; } - fstart = -1; - /* check used bits only */ - for (j = 0; j < e4b->bd_blkbits + 1; j++) { - buddy2 = mb_find_buddy(e4b, j, &max2); - k = i >> j; - MB_CHECK_ASSERT(k < max2); - MB_CHECK_ASSERT(mb_test_bit(k, buddy2)); + if (!(i & 1)) { + int in_use, zero_bit_count = 0; + + in_use = mb_test_bit(i, buddy) || mb_test_bit(i + 1, buddy); + for (j = 1; j < e4b->bd_blkbits + 2; j++) { + buddy2 = mb_find_buddy(e4b, j, &max2); + k = i >> j; + MB_CHECK_ASSERT(k < max2); + if (!mb_test_bit(k, buddy2)) + zero_bit_count++; + } + MB_CHECK_ASSERT(zero_bit_count == !in_use); } } MB_CHECK_ASSERT(!EXT4_MB_GRP_NEED_INIT(e4b->bd_info)); From 2a3f91ef20ec4bebdae49f945905e97f68705636 Mon Sep 17 00:00:00 2001 From: Willem de Bruijn Date: Tue, 25 Nov 2025 18:35:05 -0500 Subject: [PATCH 0820/3902] selftests/net: packetdrill: pass send_omit_free to MSG_ZEROCOPY tests [ Upstream commit c01a6e5b2e4f21d31cf725b9f3803cb0280b1b8d ] The --send_omit_free flag is needed for TCP zero copy tests, to ensure that packetdrill doesn't free the send() buffer after the send() call. Fixes: 1e42f73fd3c2 ("selftests/net: packetdrill: import tcp/zerocopy") Closes: https://lore.kernel.org/netdev/20251124071831.4cbbf412@kernel.org/ Suggested-by: Neal Cardwell Signed-off-by: Willem de Bruijn Link: https://patch.msgid.link/20251125234029.1320984-1-willemdebruijn.kernel@gmail.com Signed-off-by: Jakub Kicinski Signed-off-by: Sasha Levin --- .../net/packetdrill/tcp_syscall_bad_arg_sendmsg-empty-iov.pkt | 4 ++++ .../testing/selftests/net/packetdrill/tcp_zerocopy_basic.pkt | 2 ++ .../testing/selftests/net/packetdrill/tcp_zerocopy_batch.pkt | 2 ++ .../testing/selftests/net/packetdrill/tcp_zerocopy_client.pkt | 2 ++ .../testing/selftests/net/packetdrill/tcp_zerocopy_closed.pkt | 2 ++ .../selftests/net/packetdrill/tcp_zerocopy_epoll_edge.pkt | 3 +++ .../net/packetdrill/tcp_zerocopy_epoll_exclusive.pkt | 3 +++ .../selftests/net/packetdrill/tcp_zerocopy_epoll_oneshot.pkt | 3 +++ .../net/packetdrill/tcp_zerocopy_fastopen-client.pkt | 2 ++ .../net/packetdrill/tcp_zerocopy_fastopen-server.pkt | 2 ++ .../selftests/net/packetdrill/tcp_zerocopy_maxfrags.pkt | 2 ++ .../testing/selftests/net/packetdrill/tcp_zerocopy_small.pkt | 2 ++ 12 files changed, 29 insertions(+) diff --git a/tools/testing/selftests/net/packetdrill/tcp_syscall_bad_arg_sendmsg-empty-iov.pkt b/tools/testing/selftests/net/packetdrill/tcp_syscall_bad_arg_sendmsg-empty-iov.pkt index b2b2cdf27e20f6..454441e7ecff6b 100644 --- a/tools/testing/selftests/net/packetdrill/tcp_syscall_bad_arg_sendmsg-empty-iov.pkt +++ b/tools/testing/selftests/net/packetdrill/tcp_syscall_bad_arg_sendmsg-empty-iov.pkt @@ -1,6 +1,10 @@ // SPDX-License-Identifier: GPL-2.0 // Test that we correctly skip zero-length IOVs. + +--send_omit_free // do not reuse send buffers with zerocopy + `./defaults.sh` + 0 socket(..., SOCK_STREAM, IPPROTO_TCP) = 3 +0 setsockopt(3, SOL_SOCKET, SO_ZEROCOPY, [1], 4) = 0 +0 setsockopt(3, SOL_SOCKET, SO_REUSEADDR, [1], 4) = 0 diff --git a/tools/testing/selftests/net/packetdrill/tcp_zerocopy_basic.pkt b/tools/testing/selftests/net/packetdrill/tcp_zerocopy_basic.pkt index a82c8899d36bf4..0a0700afdaa38d 100644 --- a/tools/testing/selftests/net/packetdrill/tcp_zerocopy_basic.pkt +++ b/tools/testing/selftests/net/packetdrill/tcp_zerocopy_basic.pkt @@ -4,6 +4,8 @@ // send a packet with MSG_ZEROCOPY and receive the notification ID // repeat and verify IDs are consecutive +--send_omit_free // do not reuse send buffers with zerocopy + `./defaults.sh` 0 socket(..., SOCK_STREAM, IPPROTO_TCP) = 3 diff --git a/tools/testing/selftests/net/packetdrill/tcp_zerocopy_batch.pkt b/tools/testing/selftests/net/packetdrill/tcp_zerocopy_batch.pkt index c01915e7f4a15a..df91675d2991c3 100644 --- a/tools/testing/selftests/net/packetdrill/tcp_zerocopy_batch.pkt +++ b/tools/testing/selftests/net/packetdrill/tcp_zerocopy_batch.pkt @@ -3,6 +3,8 @@ // // send multiple packets, then read one range of all notifications. +--send_omit_free // do not reuse send buffers with zerocopy + `./defaults.sh` 0 socket(..., SOCK_STREAM, IPPROTO_TCP) = 3 diff --git a/tools/testing/selftests/net/packetdrill/tcp_zerocopy_client.pkt b/tools/testing/selftests/net/packetdrill/tcp_zerocopy_client.pkt index 6509882932e916..2963cfcb14dfd7 100644 --- a/tools/testing/selftests/net/packetdrill/tcp_zerocopy_client.pkt +++ b/tools/testing/selftests/net/packetdrill/tcp_zerocopy_client.pkt @@ -1,6 +1,8 @@ // SPDX-License-Identifier: GPL-2.0 // Minimal client-side zerocopy test +--send_omit_free // do not reuse send buffers with zerocopy + `./defaults.sh` 0 socket(..., SOCK_STREAM, IPPROTO_TCP) = 4 diff --git a/tools/testing/selftests/net/packetdrill/tcp_zerocopy_closed.pkt b/tools/testing/selftests/net/packetdrill/tcp_zerocopy_closed.pkt index 2cd78755cb2ac3..ea0c2fa73c2d66 100644 --- a/tools/testing/selftests/net/packetdrill/tcp_zerocopy_closed.pkt +++ b/tools/testing/selftests/net/packetdrill/tcp_zerocopy_closed.pkt @@ -7,6 +7,8 @@ // First send on a closed socket and wait for (absent) notification. // Then connect and send and verify that notification nr. is zero. +--send_omit_free // do not reuse send buffers with zerocopy + `./defaults.sh` 0 socket(..., SOCK_STREAM, IPPROTO_TCP) = 4 diff --git a/tools/testing/selftests/net/packetdrill/tcp_zerocopy_epoll_edge.pkt b/tools/testing/selftests/net/packetdrill/tcp_zerocopy_epoll_edge.pkt index 7671c20e01cf64..4df978a9b82e70 100644 --- a/tools/testing/selftests/net/packetdrill/tcp_zerocopy_epoll_edge.pkt +++ b/tools/testing/selftests/net/packetdrill/tcp_zerocopy_epoll_edge.pkt @@ -7,6 +7,9 @@ // fire two sends with MSG_ZEROCOPY and receive the acks. confirm that EPOLLERR // is correctly fired only once, when EPOLLET is set. send another packet with // MSG_ZEROCOPY. confirm that EPOLLERR is correctly fired again only once. + +--send_omit_free // do not reuse send buffers with zerocopy + `./defaults.sh` 0 socket(..., SOCK_STREAM, IPPROTO_TCP) = 3 diff --git a/tools/testing/selftests/net/packetdrill/tcp_zerocopy_epoll_exclusive.pkt b/tools/testing/selftests/net/packetdrill/tcp_zerocopy_epoll_exclusive.pkt index fadc480fdb7fe0..36b6edc4858c4d 100644 --- a/tools/testing/selftests/net/packetdrill/tcp_zerocopy_epoll_exclusive.pkt +++ b/tools/testing/selftests/net/packetdrill/tcp_zerocopy_epoll_exclusive.pkt @@ -8,6 +8,9 @@ // fire two sends with MSG_ZEROCOPY and receive the acks. confirm that EPOLLERR // is correctly fired only once, when EPOLLET is set. send another packet with // MSG_ZEROCOPY. confirm that EPOLLERR is correctly fired again only once. + +--send_omit_free // do not reuse send buffers with zerocopy + `./defaults.sh` 0 socket(..., SOCK_STREAM, IPPROTO_TCP) = 3 diff --git a/tools/testing/selftests/net/packetdrill/tcp_zerocopy_epoll_oneshot.pkt b/tools/testing/selftests/net/packetdrill/tcp_zerocopy_epoll_oneshot.pkt index 5bfa0d1d2f4a31..1bea6f3b4558df 100644 --- a/tools/testing/selftests/net/packetdrill/tcp_zerocopy_epoll_oneshot.pkt +++ b/tools/testing/selftests/net/packetdrill/tcp_zerocopy_epoll_oneshot.pkt @@ -8,6 +8,9 @@ // is correctly fired only once, when EPOLLONESHOT is set. send another packet // with MSG_ZEROCOPY. confirm that EPOLLERR is not fired. Rearm the FD and // confirm that EPOLLERR is correctly set. + +--send_omit_free // do not reuse send buffers with zerocopy + `./defaults.sh` 0 socket(..., SOCK_STREAM, IPPROTO_TCP) = 3 diff --git a/tools/testing/selftests/net/packetdrill/tcp_zerocopy_fastopen-client.pkt b/tools/testing/selftests/net/packetdrill/tcp_zerocopy_fastopen-client.pkt index 4a73bbf4696108..e27c21ff5d18d9 100644 --- a/tools/testing/selftests/net/packetdrill/tcp_zerocopy_fastopen-client.pkt +++ b/tools/testing/selftests/net/packetdrill/tcp_zerocopy_fastopen-client.pkt @@ -8,6 +8,8 @@ // one will have no data in the initial send. On return 0 the // zerocopy notification counter is not incremented. Verify this too. +--send_omit_free // do not reuse send buffers with zerocopy + `./defaults.sh` // Send a FastOpen request, no cookie yet so no data in SYN diff --git a/tools/testing/selftests/net/packetdrill/tcp_zerocopy_fastopen-server.pkt b/tools/testing/selftests/net/packetdrill/tcp_zerocopy_fastopen-server.pkt index 36086c5877ce7d..b1fa77c77dfa77 100644 --- a/tools/testing/selftests/net/packetdrill/tcp_zerocopy_fastopen-server.pkt +++ b/tools/testing/selftests/net/packetdrill/tcp_zerocopy_fastopen-server.pkt @@ -4,6 +4,8 @@ // send data with MSG_FASTOPEN | MSG_ZEROCOPY and verify that the // kernel returns the notification ID. +--send_omit_free // do not reuse send buffers with zerocopy + `./defaults.sh ./set_sysctls.py /proc/sys/net/ipv4/tcp_fastopen=0x207` diff --git a/tools/testing/selftests/net/packetdrill/tcp_zerocopy_maxfrags.pkt b/tools/testing/selftests/net/packetdrill/tcp_zerocopy_maxfrags.pkt index 672f817faca0d1..2f5317d0a9fab5 100644 --- a/tools/testing/selftests/net/packetdrill/tcp_zerocopy_maxfrags.pkt +++ b/tools/testing/selftests/net/packetdrill/tcp_zerocopy_maxfrags.pkt @@ -7,6 +7,8 @@ // because each iovec element becomes a frag // 3) the PSH bit is set on an skb when it runs out of fragments +--send_omit_free // do not reuse send buffers with zerocopy + `./defaults.sh` 0 socket(..., SOCK_STREAM, IPPROTO_TCP) = 3 diff --git a/tools/testing/selftests/net/packetdrill/tcp_zerocopy_small.pkt b/tools/testing/selftests/net/packetdrill/tcp_zerocopy_small.pkt index a9a1ac0aea4f4e..9d5272c6b20790 100644 --- a/tools/testing/selftests/net/packetdrill/tcp_zerocopy_small.pkt +++ b/tools/testing/selftests/net/packetdrill/tcp_zerocopy_small.pkt @@ -4,6 +4,8 @@ // verify that SO_EE_CODE_ZEROCOPY_COPIED is set on zerocopy // packets of all sizes, including the smallest payload, 1B. +--send_omit_free // do not reuse send buffers with zerocopy + `./defaults.sh` 0 socket(..., SOCK_STREAM, IPPROTO_TCP) = 3 From 1b70f280c39ede03b8027ead55df76d2ad4784e0 Mon Sep 17 00:00:00 2001 From: Guenter Roeck Date: Thu, 23 Oct 2025 09:04:14 -0700 Subject: [PATCH 0821/3902] of: Skip devicetree kunit tests when RISCV+ACPI doesn't populate root node [ Upstream commit 546dbb0223102813ffb5bbcb9443a47c3183f195 ] Starting with commit 69a8b62a7aa1 ("riscv: acpi: avoid errors caused by probing DT devices when ACPI is used"), riscv images no longer populate devicetree if ACPI is enabled. This causes unit tests to fail which require the root node to be set. # Subtest: of_dtb # module: of_test 1..2 # of_dtb_root_node_found_by_path: EXPECTATION FAILED at drivers/of/of_test.c:21 Expected np is not null, but is # of_dtb_root_node_found_by_path: pass:0 fail:1 skip:0 total:1 not ok 1 of_dtb_root_node_found_by_path # of_dtb_root_node_populates_of_root: EXPECTATION FAILED at drivers/of/of_test.c:31 Expected of_root is not null, but is # of_dtb_root_node_populates_of_root: pass:0 fail:1 skip:0 total:1 not ok 2 of_dtb_root_node_populates_of_root Skip those tests for RISCV if the root node is not populated. Fixes: 69a8b62a7aa1 ("riscv: acpi: avoid errors caused by probing DT devices when ACPI is used") Cc: Han Gao Cc: Paul Walmsley Signed-off-by: Guenter Roeck Reviewed-by: Paul Walmsley # arch/riscv Link: https://patch.msgid.link/20251023160415.705294-1-linux@roeck-us.net Signed-off-by: Rob Herring (Arm) Signed-off-by: Sasha Levin --- drivers/of/of_kunit_helpers.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/drivers/of/of_kunit_helpers.c b/drivers/of/of_kunit_helpers.c index 7b3ed5a382aaa5..f6ed1af8b62aaf 100644 --- a/drivers/of/of_kunit_helpers.c +++ b/drivers/of/of_kunit_helpers.c @@ -18,8 +18,9 @@ */ void of_root_kunit_skip(struct kunit *test) { - if (IS_ENABLED(CONFIG_ARM64) && IS_ENABLED(CONFIG_ACPI) && !of_root) - kunit_skip(test, "arm64+acpi doesn't populate a root node"); + if ((IS_ENABLED(CONFIG_ARM64) || IS_ENABLED(CONFIG_RISCV)) && + IS_ENABLED(CONFIG_ACPI) && !of_root) + kunit_skip(test, "arm64/riscv+acpi doesn't populate a root node"); } EXPORT_SYMBOL_GPL(of_root_kunit_skip); From 3c30975c9abaa8058716fedfc8828a6f2bd3a201 Mon Sep 17 00:00:00 2001 From: Alok Tiwari Date: Wed, 1 Oct 2025 12:16:50 -0700 Subject: [PATCH 0822/3902] virtio_vdpa: fix misleading return in void function [ Upstream commit e40b6abe0b1247d43bc61942aa7534fca7209e44 ] virtio_vdpa_set_status() is declared as returning void, but it used "return vdpa_set_status()" Since vdpa_set_status() also returns void, the return statement is unnecessary and misleading. Remove it. Fixes: c043b4a8cf3b ("virtio: introduce a vDPA based transport") Signed-off-by: Alok Tiwari Message-Id: <20251001191653.1713923-1-alok.a.tiwari@oracle.com> Signed-off-by: Michael S. Tsirkin Acked-by: Jason Wang Signed-off-by: Sasha Levin --- drivers/virtio/virtio_vdpa.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/virtio/virtio_vdpa.c b/drivers/virtio/virtio_vdpa.c index f9a29045eca0de..0a801f67b59962 100644 --- a/drivers/virtio/virtio_vdpa.c +++ b/drivers/virtio/virtio_vdpa.c @@ -80,7 +80,7 @@ static void virtio_vdpa_set_status(struct virtio_device *vdev, u8 status) { struct vdpa_device *vdpa = vd_get_vdpa(vdev); - return vdpa_set_status(vdpa, status); + vdpa_set_status(vdpa, status); } static void virtio_vdpa_reset(struct virtio_device *vdev) From a8d39a172781b7f9e5ae440dea97865499d0375c Mon Sep 17 00:00:00 2001 From: Kriish Sharma Date: Mon, 10 Nov 2025 20:29:20 +0000 Subject: [PATCH 0823/3902] virtio: fix kernel-doc for mapping/free_coherent functions [ Upstream commit f8113000855a8cc2c6d8e19b97a390f1c082285d ] Documentation build reported: WARNING: ./drivers/virtio/virtio_ring.c:3174 function parameter 'vaddr' not described in 'virtqueue_map_free_coherent' WARNING: ./drivers/virtio/virtio_ring.c:3308 expecting prototype for virtqueue_mapping_error(). Prototype was for virtqueue_map_mapping_error() instead The kernel-doc block for virtqueue_map_free_coherent() omitted the @vaddr parameter, and the kernel-doc header for virtqueue_map_mapping_error() used the wrong function name (virtqueue_mapping_error) instead of the actual function name. This change updates: - the function name in the comment to virtqueue_map_mapping_error() - adds the missing @vaddr description in the comment for virtqueue_map_free_coherent() Fixes: b41cb3bcf67f ("virtio: rename dma helpers") Signed-off-by: Kriish Sharma Reviewed-by: Chaitanya Kulkarni Signed-off-by: Michael S. Tsirkin Message-Id: <20251110202920.2250244-1-kriish.sharma2006@gmail.com> Signed-off-by: Sasha Levin --- drivers/virtio/virtio_ring.c | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/drivers/virtio/virtio_ring.c b/drivers/virtio/virtio_ring.c index 7b6205253b46b3..ddab6895967171 100644 --- a/drivers/virtio/virtio_ring.c +++ b/drivers/virtio/virtio_ring.c @@ -3166,6 +3166,7 @@ EXPORT_SYMBOL_GPL(virtqueue_map_alloc_coherent); * @vdev: the virtio device we are talking to * @map: metadata for performing mapping * @size: the size of the buffer + * @vaddr: the virtual address that needs to be freed * @map_handle: the mapped address that needs to be freed * */ @@ -3190,7 +3191,7 @@ EXPORT_SYMBOL_GPL(virtqueue_map_free_coherent); * @dir: mapping direction * @attrs: mapping attributes * - * Returns mapped address. Caller should check that by virtqueue_mapping_error(). + * Returns mapped address. Caller should check that by virtqueue_map_mapping_error(). */ dma_addr_t virtqueue_map_page_attrs(const struct virtqueue *_vq, struct page *page, @@ -3249,7 +3250,7 @@ EXPORT_SYMBOL_GPL(virtqueue_unmap_page_attrs); * The caller calls this to do dma mapping in advance. The DMA address can be * passed to this _vq when it is in pre-mapped mode. * - * return mapped address. Caller should check that by virtqueue_mapping_error(). + * return mapped address. Caller should check that by virtqueue_map_mapping_error(). */ dma_addr_t virtqueue_map_single_attrs(const struct virtqueue *_vq, void *ptr, size_t size, @@ -3299,7 +3300,7 @@ void virtqueue_unmap_single_attrs(const struct virtqueue *_vq, EXPORT_SYMBOL_GPL(virtqueue_unmap_single_attrs); /** - * virtqueue_mapping_error - check dma address + * virtqueue_map_mapping_error - check dma address * @_vq: the struct virtqueue we're talking about. * @addr: DMA address * From 201ca193b4928dba9da0d10b1c356b7f9172249e Mon Sep 17 00:00:00 2001 From: "Michael S. Tsirkin" Date: Thu, 13 Nov 2025 04:34:31 -0500 Subject: [PATCH 0824/3902] virtio: fix typo in virtio_device_ready() comment [ Upstream commit 361173f95ae4b726ebbbf0bd594274f5576c4abc ] "coherenct" -> "coherent" Fixes: 8b4ec69d7e09 ("virtio: harden vring IRQ") Message-Id: Acked-by: Jason Wang Signed-off-by: Michael S. Tsirkin Signed-off-by: Sasha Levin --- include/linux/virtio_config.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/linux/virtio_config.h b/include/linux/virtio_config.h index 16001e9f9b391e..1ea5baa62141fa 100644 --- a/include/linux/virtio_config.h +++ b/include/linux/virtio_config.h @@ -362,7 +362,7 @@ void virtio_device_ready(struct virtio_device *dev) * specific set_status() method. * * A well behaved device will only notify a virtqueue after - * DRIVER_OK, this means the device should "see" the coherenct + * DRIVER_OK, this means the device should "see" the coherent * memory write that set vq->broken as false which is done by * the driver when it sees DRIVER_OK, then the following * driver's vring_interrupt() will see vq->broken as false so From e3e6c543e6952e28cd218e15678f6a2a03e833eb Mon Sep 17 00:00:00 2001 From: "Michael S. Tsirkin" Date: Thu, 13 Nov 2025 04:34:34 -0500 Subject: [PATCH 0825/3902] virtio: fix whitespace in virtio_config_ops [ Upstream commit 7831791e77a1cd29528d4dc336ce14466aef5ba6 ] The finalize_features documentation uses a tab between words. Use space instead. Fixes: d16c0cd27331 ("docs: driver-api: virtio: virtio on Linux") Message-Id: <39d7685c82848dc6a876d175e33a1407f6ab3fc1.1763026134.git.mst@redhat.com> Acked-by: Jason Wang Signed-off-by: Michael S. Tsirkin Signed-off-by: Sasha Levin --- include/linux/virtio_config.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/linux/virtio_config.h b/include/linux/virtio_config.h index 1ea5baa62141fa..dbc7eff1f101f7 100644 --- a/include/linux/virtio_config.h +++ b/include/linux/virtio_config.h @@ -86,7 +86,7 @@ struct virtqueue_info { * vdev: the virtio_device * This sends the driver feature bits to the device: it can change * the dev->feature bits if it wants. - * Note that despite the name this can be called any number of + * Note that despite the name this can be called any number of * times. * Returns 0 on success or error status * @bus_name: return the bus name associated with the device (optional) From 6c5dfc6f33deeb9e8a397d3b10b8da8c6b6f5731 Mon Sep 17 00:00:00 2001 From: "Michael S. Tsirkin" Date: Thu, 13 Nov 2025 04:34:36 -0500 Subject: [PATCH 0826/3902] virtio: fix grammar in virtio_queue_info docs [ Upstream commit 63598fba55ab9d384818fed48dc04006cecf7be4 ] Fix grammar in the description of @ctx Fixes: c502eb85c34e ("virtio: introduce virtio_queue_info struct and find_vqs_info() config op") Message-Id: Acked-by: Jason Wang Signed-off-by: Michael S. Tsirkin Signed-off-by: Sasha Levin --- include/linux/virtio_config.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/linux/virtio_config.h b/include/linux/virtio_config.h index dbc7eff1f101f7..78cf4119f5674e 100644 --- a/include/linux/virtio_config.h +++ b/include/linux/virtio_config.h @@ -24,7 +24,7 @@ typedef void vq_callback_t(struct virtqueue *); * a virtqueue unused by the driver. * @callback: A callback to invoke on a used buffer notification. * NULL for a virtqueue that does not need a callback. - * @ctx: A flag to indicate to maintain an extra context per virtqueue. + * @ctx: whether to maintain an extra context per virtqueue. */ struct virtqueue_info { const char *name; From ed4dade6223d590e2d3b52a1b35f8e930738e165 Mon Sep 17 00:00:00 2001 From: "Michael S. Tsirkin" Date: Thu, 13 Nov 2025 04:34:38 -0500 Subject: [PATCH 0827/3902] virtio: fix grammar in virtio_map_ops docs [ Upstream commit c15f42e09178d2849744ccf064200f5e7f71e688 ] Fix grammar issues in the virtio_map_ops docs: - missing article before "transport" - "implements" -> "implement" to match subject Fixes: bee8c7c24b73 ("virtio: introduce map ops in virtio core") Message-Id: <3f7bcae5a984f14b72e67e82572b110acb06fa7e.1763026134.git.mst@redhat.com> Acked-by: Jason Wang Signed-off-by: Michael S. Tsirkin Signed-off-by: Sasha Levin --- include/linux/virtio_config.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/include/linux/virtio_config.h b/include/linux/virtio_config.h index 78cf4119f5674e..6660132258d408 100644 --- a/include/linux/virtio_config.h +++ b/include/linux/virtio_config.h @@ -141,8 +141,8 @@ struct virtio_config_ops { /** * struct virtio_map_ops - operations for mapping buffer for a virtio device - * Note: For transport that has its own mapping logic it must - * implements all of the operations + * Note: For a transport that has its own mapping logic it must + * implement all of the operations * @map_page: map a buffer to the device * map: metadata for performing mapping * page: the page that will be mapped by the device From 5fdd8443c0a4c4f03cfdbdb9255a43c5a1cb280d Mon Sep 17 00:00:00 2001 From: "Michael S. Tsirkin" Date: Thu, 13 Nov 2025 04:34:41 -0500 Subject: [PATCH 0828/3902] virtio: standardize Returns documentation style [ Upstream commit 5e88a5a97d113619b674ebfdd1d2065f2edd10eb ] Remove colons after "Returns" in virtio_map_ops function documentation - both to avoid triggering an htmldoc warning and for consistency with virtio_config_ops. This affects map_page, alloc, need_sync, and max_mapping_size. Fixes: bee8c7c24b73 ("virtio: introduce map ops in virtio core") Message-Id: Acked-by: Jason Wang Signed-off-by: Michael S. Tsirkin Signed-off-by: Sasha Levin --- include/linux/virtio_config.h | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/include/linux/virtio_config.h b/include/linux/virtio_config.h index 6660132258d408..e231147ff92dbe 100644 --- a/include/linux/virtio_config.h +++ b/include/linux/virtio_config.h @@ -150,7 +150,7 @@ struct virtio_config_ops { * size: the buffer size * dir: mapping direction * attrs: mapping attributes - * Returns: the mapped address + * Returns the mapped address * @unmap_page: unmap a buffer from the device * map: device specific mapping map * map_handle: the mapped address @@ -172,7 +172,7 @@ struct virtio_config_ops { * size: the size of the buffer * map_handle: the mapping address to sync * gfp: allocation flag (GFP_XXX) - * Returns: virtual address of the allocated buffer + * Returns virtual address of the allocated buffer * @free: free a coherent buffer mapping * map: metadata for performing mapping * size: the size of the buffer @@ -182,13 +182,13 @@ struct virtio_config_ops { * @need_sync: if the buffer needs synchronization * map: metadata for performing mapping * map_handle: the mapped address - * Returns: whether the buffer needs synchronization + * Returns whether the buffer needs synchronization * @mapping_error: if the mapping address is error * map: metadata for performing mapping * map_handle: the mapped address * @max_mapping_size: get the maximum buffer size that can be mapped * map: metadata for performing mapping - * Returns: the maximum buffer size that can be mapped + * Returns the maximum buffer size that can be mapped */ struct virtio_map_ops { dma_addr_t (*map_page)(union virtio_map map, struct page *page, From 99ae54bbb4751ec1ab8599151bf4871d845982fc Mon Sep 17 00:00:00 2001 From: "Michael S. Tsirkin" Date: Thu, 13 Nov 2025 04:34:43 -0500 Subject: [PATCH 0829/3902] virtio: fix virtqueue_set_affinity() docs [ Upstream commit 43236d8bbafff94b423afecc4a692dd90602d426 ] Rewrite the comment for better grammar and clarity. Fixes: 75a0a52be3c2 ("virtio: introduce an API to set affinity for a virtqueue") Message-Id: Acked-by: Jason Wang Signed-off-by: Michael S. Tsirkin Signed-off-by: Sasha Levin --- include/linux/virtio_config.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/linux/virtio_config.h b/include/linux/virtio_config.h index e231147ff92dbe..1a019a1f168d5d 100644 --- a/include/linux/virtio_config.h +++ b/include/linux/virtio_config.h @@ -384,7 +384,7 @@ const char *virtio_bus_name(struct virtio_device *vdev) * @vq: the virtqueue * @cpu_mask: the cpu mask * - * Pay attention the function are best-effort: the affinity hint may not be set + * Note that this function is best-effort: the affinity hint may not be set * due to config support, irq type and sharing. * */ From d94497989a9ce95f71b5e1108de07c75112e8f71 Mon Sep 17 00:00:00 2001 From: "Michael S. Tsirkin" Date: Thu, 13 Nov 2025 04:34:49 -0500 Subject: [PATCH 0830/3902] virtio: fix map ops comment [ Upstream commit deb55fc994e3dc38f139c0147c15fc2a9db27086 ] @free will free the map handle not sync it. Fix the doc to match. Fixes: bee8c7c24b73 ("virtio: introduce map ops in virtio core") Message-Id: Acked-by: Jason Wang Signed-off-by: Michael S. Tsirkin Signed-off-by: Sasha Levin --- include/linux/virtio_config.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/linux/virtio_config.h b/include/linux/virtio_config.h index 1a019a1f168d5d..a1af2676bbe6a8 100644 --- a/include/linux/virtio_config.h +++ b/include/linux/virtio_config.h @@ -177,7 +177,7 @@ struct virtio_config_ops { * map: metadata for performing mapping * size: the size of the buffer * vaddr: virtual address of the buffer - * map_handle: the mapping address to sync + * map_handle: the mapping address that needs to be freed * attrs: unmapping attributes * @need_sync: if the buffer needs synchronization * map: metadata for performing mapping From ba8a48b7be20f3818d4942b9611983a117f042b6 Mon Sep 17 00:00:00 2001 From: Alok Tiwari Date: Mon, 29 Sep 2025 06:42:53 -0700 Subject: [PATCH 0831/3902] vdpa/mlx5: Fix incorrect error code reporting in query_virtqueues [ Upstream commit f0ea2e91093ac979d07ebd033e0f45869b1d2608 ] When query_virtqueues() fails, the error log prints the variable err instead of cmd->err. Since err may still be zero at this point, the log message can misleadingly report a success value 0 even though the command actually failed. Even worse, once err is set to the first failure, subsequent logs print that same stale value. This makes the error reporting appear one step behind the actual failing queue index, which is confusing and misleading. Fix the log to report cmd->err, which reflects the real failure code returned by the firmware. Fixes: 1fcdf43ea69e ("vdpa/mlx5: Use async API for vq query command") Signed-off-by: Alok Tiwari Acked-by: Jason Wang Reviewed-by: Dragos Tatulea Signed-off-by: Michael S. Tsirkin Message-Id: <20250929134258.80956-1-alok.a.tiwari@oracle.com> Signed-off-by: Sasha Levin --- drivers/vdpa/mlx5/net/mlx5_vnet.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/vdpa/mlx5/net/mlx5_vnet.c b/drivers/vdpa/mlx5/net/mlx5_vnet.c index a7936bd1aabe17..ddaa1366704bb0 100644 --- a/drivers/vdpa/mlx5/net/mlx5_vnet.c +++ b/drivers/vdpa/mlx5/net/mlx5_vnet.c @@ -1256,7 +1256,7 @@ static int query_virtqueues(struct mlx5_vdpa_net *ndev, int vq_idx = start_vq + i; if (cmd->err) { - mlx5_vdpa_err(mvdev, "query vq %d failed, err: %d\n", vq_idx, err); + mlx5_vdpa_err(mvdev, "query vq %d failed, err: %d\n", vq_idx, cmd->err); if (!err) err = cmd->err; continue; From 09853ba0977f678561d93286d40e1ad62c80447b Mon Sep 17 00:00:00 2001 From: Mike Christie Date: Sat, 1 Nov 2025 14:43:58 -0500 Subject: [PATCH 0832/3902] vhost: Fix kthread worker cgroup failure handling [ Upstream commit f3f64c2eaffbc3169bbe1e5d1e897e6dacc839d1 ] If we fail to attach to a cgroup we are leaking the id. This adds a new goto to free the id. Fixes: 7d9896e9f6d0 ("vhost: Reintroduce kthread API and add mode selection") Signed-off-by: Mike Christie Acked-by: Jason Wang Reviewed-by: Chaitanya Kulkarni Signed-off-by: Michael S. Tsirkin Message-Id: <20251101194358.13605-1-michael.christie@oracle.com> Signed-off-by: Sasha Levin --- drivers/vhost/vhost.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/drivers/vhost/vhost.c b/drivers/vhost/vhost.c index a78226b37739d7..bccdc9eab267a7 100644 --- a/drivers/vhost/vhost.c +++ b/drivers/vhost/vhost.c @@ -804,11 +804,13 @@ static int vhost_kthread_worker_create(struct vhost_worker *worker, ret = vhost_attach_task_to_cgroups(worker); if (ret) - goto stop_worker; + goto free_id; worker->id = id; return 0; +free_id: + xa_erase(&dev->worker_xa, id); stop_worker: vhost_kthread_do_stop(worker); return ret; From ce46f6a8d62449c6cd0710756717c0c233014b13 Mon Sep 17 00:00:00 2001 From: Alok Tiwari Date: Sat, 18 Oct 2025 10:46:46 -0700 Subject: [PATCH 0833/3902] vdpa/pds: use %pe for ERR_PTR() in event handler registration [ Upstream commit 731ca4a4cc52fd5c5ae309edcfd2d7e54ece3321 ] Use %pe instead of %ps when printing ERR_PTR() values. %ps is intended for string pointers, while %pe correctly prints symbolic error names for error pointers returned via ERR_PTR(). This shows the returned error value more clearly. Fixes: 67f27b8b3a34 ("pds_vdpa: subscribe to the pds_core events") Signed-off-by: Alok Tiwari Reviewed-by: Brett Creeley Signed-off-by: Michael S. Tsirkin Message-Id: <20251018174705.1511982-1-alok.a.tiwari@oracle.com> Signed-off-by: Sasha Levin --- drivers/vdpa/pds/vdpa_dev.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/vdpa/pds/vdpa_dev.c b/drivers/vdpa/pds/vdpa_dev.c index 36f61cc96e2110..43426bd971acc9 100644 --- a/drivers/vdpa/pds/vdpa_dev.c +++ b/drivers/vdpa/pds/vdpa_dev.c @@ -51,7 +51,7 @@ static int pds_vdpa_register_event_handler(struct pds_vdpa_device *pdsv) err = pdsc_register_notify(nb); if (err) { nb->notifier_call = NULL; - dev_err(dev, "failed to register pds event handler: %ps\n", + dev_err(dev, "failed to register pds event handler: %pe\n", ERR_PTR(err)); return -EINVAL; } From 9223cdb994d9327ebce47a5749a763fb820bd2c1 Mon Sep 17 00:00:00 2001 From: "Michael S. Tsirkin" Date: Tue, 21 Oct 2025 10:56:57 -0400 Subject: [PATCH 0834/3902] virtio: clean up features qword/dword terms [ Upstream commit 9513f25056b22100ddffe24898c587873b0d022c ] virtio pci uses word to mean "16 bits". mmio uses it to mean "32 bits". To avoid confusion, let's avoid the term in core virtio altogether. Just say U64 to mean "64 bit". Fixes: e7d4c1c5a546 ("virtio: introduce extended features") Cc: Paolo Abeni Acked-by: Jason Wang Message-ID: Signed-off-by: Michael S. Tsirkin Signed-off-by: Sasha Levin --- drivers/vhost/net.c | 12 +++++------ drivers/virtio/virtio.c | 12 +++++------ drivers/virtio/virtio_debug.c | 10 ++++----- drivers/virtio/virtio_pci_modern_dev.c | 6 +++--- include/linux/virtio.h | 2 +- include/linux/virtio_config.h | 2 +- include/linux/virtio_features.h | 29 +++++++++++++------------- include/linux/virtio_pci_modern.h | 8 +++---- 8 files changed, 41 insertions(+), 40 deletions(-) diff --git a/drivers/vhost/net.c b/drivers/vhost/net.c index 8f7f50acb6d63d..1e77c0482b849c 100644 --- a/drivers/vhost/net.c +++ b/drivers/vhost/net.c @@ -69,7 +69,7 @@ MODULE_PARM_DESC(experimental_zcopytx, "Enable Zero Copy TX;" #define VHOST_DMA_IS_DONE(len) ((__force u32)(len) >= (__force u32)VHOST_DMA_DONE_LEN) -static const u64 vhost_net_features[VIRTIO_FEATURES_DWORDS] = { +static const u64 vhost_net_features[VIRTIO_FEATURES_U64S] = { VHOST_FEATURES | (1ULL << VHOST_NET_F_VIRTIO_NET_HDR) | (1ULL << VIRTIO_NET_F_MRG_RXBUF) | @@ -1731,7 +1731,7 @@ static long vhost_net_set_owner(struct vhost_net *n) static long vhost_net_ioctl(struct file *f, unsigned int ioctl, unsigned long arg) { - u64 all_features[VIRTIO_FEATURES_DWORDS]; + u64 all_features[VIRTIO_FEATURES_U64S]; struct vhost_net *n = f->private_data; void __user *argp = (void __user *)arg; u64 __user *featurep = argp; @@ -1763,7 +1763,7 @@ static long vhost_net_ioctl(struct file *f, unsigned int ioctl, /* Copy the net features, up to the user-provided buffer size */ argp += sizeof(u64); - copied = min(count, VIRTIO_FEATURES_DWORDS); + copied = min(count, (u64)VIRTIO_FEATURES_U64S); if (copy_to_user(argp, vhost_net_features, copied * sizeof(u64))) return -EFAULT; @@ -1778,13 +1778,13 @@ static long vhost_net_ioctl(struct file *f, unsigned int ioctl, virtio_features_zero(all_features); argp += sizeof(u64); - copied = min(count, VIRTIO_FEATURES_DWORDS); + copied = min(count, (u64)VIRTIO_FEATURES_U64S); if (copy_from_user(all_features, argp, copied * sizeof(u64))) return -EFAULT; /* * Any feature specified by user-space above - * VIRTIO_FEATURES_MAX is not supported by definition. + * VIRTIO_FEATURES_BITS is not supported by definition. */ for (i = copied; i < count; ++i) { if (copy_from_user(&features, featurep + 1 + i, @@ -1794,7 +1794,7 @@ static long vhost_net_ioctl(struct file *f, unsigned int ioctl, return -EOPNOTSUPP; } - for (i = 0; i < VIRTIO_FEATURES_DWORDS; i++) + for (i = 0; i < VIRTIO_FEATURES_U64S; i++) if (all_features[i] & ~vhost_net_features[i]) return -EOPNOTSUPP; diff --git a/drivers/virtio/virtio.c b/drivers/virtio/virtio.c index a09eb4d62f82df..5bdc6b82b30b48 100644 --- a/drivers/virtio/virtio.c +++ b/drivers/virtio/virtio.c @@ -53,7 +53,7 @@ static ssize_t features_show(struct device *_d, /* We actually represent this as a bitstring, as it could be * arbitrary length in future. */ - for (i = 0; i < VIRTIO_FEATURES_MAX; i++) + for (i = 0; i < VIRTIO_FEATURES_BITS; i++) len += sysfs_emit_at(buf, len, "%c", __virtio_test_bit(dev, i) ? '1' : '0'); len += sysfs_emit_at(buf, len, "\n"); @@ -272,8 +272,8 @@ static int virtio_dev_probe(struct device *_d) int err, i; struct virtio_device *dev = dev_to_virtio(_d); struct virtio_driver *drv = drv_to_virtio(dev->dev.driver); - u64 device_features[VIRTIO_FEATURES_DWORDS]; - u64 driver_features[VIRTIO_FEATURES_DWORDS]; + u64 device_features[VIRTIO_FEATURES_U64S]; + u64 driver_features[VIRTIO_FEATURES_U64S]; u64 driver_features_legacy; /* We have a driver! */ @@ -286,7 +286,7 @@ static int virtio_dev_probe(struct device *_d) virtio_features_zero(driver_features); for (i = 0; i < drv->feature_table_size; i++) { unsigned int f = drv->feature_table[i]; - if (!WARN_ON_ONCE(f >= VIRTIO_FEATURES_MAX)) + if (!WARN_ON_ONCE(f >= VIRTIO_FEATURES_BITS)) virtio_features_set_bit(driver_features, f); } @@ -303,7 +303,7 @@ static int virtio_dev_probe(struct device *_d) } if (virtio_features_test_bit(device_features, VIRTIO_F_VERSION_1)) { - for (i = 0; i < VIRTIO_FEATURES_DWORDS; ++i) + for (i = 0; i < VIRTIO_FEATURES_U64S; ++i) dev->features_array[i] = driver_features[i] & device_features[i]; } else { @@ -325,7 +325,7 @@ static int virtio_dev_probe(struct device *_d) goto err; if (drv->validate) { - u64 features[VIRTIO_FEATURES_DWORDS]; + u64 features[VIRTIO_FEATURES_U64S]; virtio_features_copy(features, dev->features_array); err = drv->validate(dev); diff --git a/drivers/virtio/virtio_debug.c b/drivers/virtio/virtio_debug.c index d58713ddf2e587..ccf1955a1183ab 100644 --- a/drivers/virtio/virtio_debug.c +++ b/drivers/virtio/virtio_debug.c @@ -8,12 +8,12 @@ static struct dentry *virtio_debugfs_dir; static int virtio_debug_device_features_show(struct seq_file *s, void *data) { - u64 device_features[VIRTIO_FEATURES_DWORDS]; + u64 device_features[VIRTIO_FEATURES_U64S]; struct virtio_device *dev = s->private; unsigned int i; virtio_get_features(dev, device_features); - for (i = 0; i < VIRTIO_FEATURES_MAX; i++) { + for (i = 0; i < VIRTIO_FEATURES_BITS; i++) { if (virtio_features_test_bit(device_features, i)) seq_printf(s, "%u\n", i); } @@ -26,7 +26,7 @@ static int virtio_debug_filter_features_show(struct seq_file *s, void *data) struct virtio_device *dev = s->private; unsigned int i; - for (i = 0; i < VIRTIO_FEATURES_MAX; i++) { + for (i = 0; i < VIRTIO_FEATURES_BITS; i++) { if (virtio_features_test_bit(dev->debugfs_filter_features, i)) seq_printf(s, "%u\n", i); } @@ -50,7 +50,7 @@ static int virtio_debug_filter_feature_add(void *data, u64 val) { struct virtio_device *dev = data; - if (val >= VIRTIO_FEATURES_MAX) + if (val >= VIRTIO_FEATURES_BITS) return -EINVAL; virtio_features_set_bit(dev->debugfs_filter_features, val); @@ -64,7 +64,7 @@ static int virtio_debug_filter_feature_del(void *data, u64 val) { struct virtio_device *dev = data; - if (val >= VIRTIO_FEATURES_MAX) + if (val >= VIRTIO_FEATURES_BITS) return -EINVAL; virtio_features_clear_bit(dev->debugfs_filter_features, val); diff --git a/drivers/virtio/virtio_pci_modern_dev.c b/drivers/virtio/virtio_pci_modern_dev.c index 9e503b7a58d813..413a8c35346380 100644 --- a/drivers/virtio/virtio_pci_modern_dev.c +++ b/drivers/virtio/virtio_pci_modern_dev.c @@ -401,7 +401,7 @@ void vp_modern_get_extended_features(struct virtio_pci_modern_device *mdev, int i; virtio_features_zero(features); - for (i = 0; i < VIRTIO_FEATURES_WORDS; i++) { + for (i = 0; i < VIRTIO_FEATURES_BITS / 32; i++) { u64 cur; vp_iowrite32(i, &cfg->device_feature_select); @@ -427,7 +427,7 @@ vp_modern_get_driver_extended_features(struct virtio_pci_modern_device *mdev, int i; virtio_features_zero(features); - for (i = 0; i < VIRTIO_FEATURES_WORDS; i++) { + for (i = 0; i < VIRTIO_FEATURES_BITS / 32; i++) { u64 cur; vp_iowrite32(i, &cfg->guest_feature_select); @@ -448,7 +448,7 @@ void vp_modern_set_extended_features(struct virtio_pci_modern_device *mdev, struct virtio_pci_common_cfg __iomem *cfg = mdev->common; int i; - for (i = 0; i < VIRTIO_FEATURES_WORDS; i++) { + for (i = 0; i < VIRTIO_FEATURES_BITS / 32; i++) { u32 cur = features[i >> 1] >> (32 * (i & 1)); vp_iowrite32(i, &cfg->guest_feature_select); diff --git a/include/linux/virtio.h b/include/linux/virtio.h index 96c66126c0741c..132a474e59140a 100644 --- a/include/linux/virtio.h +++ b/include/linux/virtio.h @@ -177,7 +177,7 @@ struct virtio_device { union virtio_map vmap; #ifdef CONFIG_VIRTIO_DEBUG struct dentry *debugfs_dir; - u64 debugfs_filter_features[VIRTIO_FEATURES_DWORDS]; + u64 debugfs_filter_features[VIRTIO_FEATURES_U64S]; #endif }; diff --git a/include/linux/virtio_config.h b/include/linux/virtio_config.h index a1af2676bbe6a8..69f84ea85d71a0 100644 --- a/include/linux/virtio_config.h +++ b/include/linux/virtio_config.h @@ -80,7 +80,7 @@ struct virtqueue_info { * Returns the first 64 feature bits. * @get_extended_features: * vdev: the virtio_device - * Returns the first VIRTIO_FEATURES_MAX feature bits (all we currently + * Returns the first VIRTIO_FEATURES_BITS feature bits (all we currently * need). * @finalize_features: confirm what device features we'll be using. * vdev: the virtio_device diff --git a/include/linux/virtio_features.h b/include/linux/virtio_features.h index f748f2f87de8d3..ea2ad8717882e0 100644 --- a/include/linux/virtio_features.h +++ b/include/linux/virtio_features.h @@ -4,15 +4,16 @@ #include -#define VIRTIO_FEATURES_DWORDS 2 -#define VIRTIO_FEATURES_MAX (VIRTIO_FEATURES_DWORDS * 64) -#define VIRTIO_FEATURES_WORDS (VIRTIO_FEATURES_DWORDS * 2) +#define VIRTIO_FEATURES_U64S 2 +#define VIRTIO_FEATURES_BITS (VIRTIO_FEATURES_U64S * 64) + #define VIRTIO_BIT(b) BIT_ULL((b) & 0x3f) -#define VIRTIO_DWORD(b) ((b) >> 6) +#define VIRTIO_U64(b) ((b) >> 6) + #define VIRTIO_DECLARE_FEATURES(name) \ union { \ u64 name; \ - u64 name##_array[VIRTIO_FEATURES_DWORDS];\ + u64 name##_array[VIRTIO_FEATURES_U64S];\ } static inline bool virtio_features_chk_bit(unsigned int bit) @@ -22,9 +23,9 @@ static inline bool virtio_features_chk_bit(unsigned int bit) * Don't care returning the correct value: the build * will fail before any bad features access */ - BUILD_BUG_ON(bit >= VIRTIO_FEATURES_MAX); + BUILD_BUG_ON(bit >= VIRTIO_FEATURES_BITS); } else { - if (WARN_ON_ONCE(bit >= VIRTIO_FEATURES_MAX)) + if (WARN_ON_ONCE(bit >= VIRTIO_FEATURES_BITS)) return false; } return true; @@ -34,26 +35,26 @@ static inline bool virtio_features_test_bit(const u64 *features, unsigned int bit) { return virtio_features_chk_bit(bit) && - !!(features[VIRTIO_DWORD(bit)] & VIRTIO_BIT(bit)); + !!(features[VIRTIO_U64(bit)] & VIRTIO_BIT(bit)); } static inline void virtio_features_set_bit(u64 *features, unsigned int bit) { if (virtio_features_chk_bit(bit)) - features[VIRTIO_DWORD(bit)] |= VIRTIO_BIT(bit); + features[VIRTIO_U64(bit)] |= VIRTIO_BIT(bit); } static inline void virtio_features_clear_bit(u64 *features, unsigned int bit) { if (virtio_features_chk_bit(bit)) - features[VIRTIO_DWORD(bit)] &= ~VIRTIO_BIT(bit); + features[VIRTIO_U64(bit)] &= ~VIRTIO_BIT(bit); } static inline void virtio_features_zero(u64 *features) { - memset(features, 0, sizeof(features[0]) * VIRTIO_FEATURES_DWORDS); + memset(features, 0, sizeof(features[0]) * VIRTIO_FEATURES_U64S); } static inline void virtio_features_from_u64(u64 *features, u64 from) @@ -66,7 +67,7 @@ static inline bool virtio_features_equal(const u64 *f1, const u64 *f2) { int i; - for (i = 0; i < VIRTIO_FEATURES_DWORDS; ++i) + for (i = 0; i < VIRTIO_FEATURES_U64S; ++i) if (f1[i] != f2[i]) return false; return true; @@ -74,14 +75,14 @@ static inline bool virtio_features_equal(const u64 *f1, const u64 *f2) static inline void virtio_features_copy(u64 *to, const u64 *from) { - memcpy(to, from, sizeof(to[0]) * VIRTIO_FEATURES_DWORDS); + memcpy(to, from, sizeof(to[0]) * VIRTIO_FEATURES_U64S); } static inline void virtio_features_andnot(u64 *to, const u64 *f1, const u64 *f2) { int i; - for (i = 0; i < VIRTIO_FEATURES_DWORDS; i++) + for (i = 0; i < VIRTIO_FEATURES_U64S; i++) to[i] = f1[i] & ~f2[i]; } diff --git a/include/linux/virtio_pci_modern.h b/include/linux/virtio_pci_modern.h index 48bc12d1045bd5..9a3f2fc53bd659 100644 --- a/include/linux/virtio_pci_modern.h +++ b/include/linux/virtio_pci_modern.h @@ -107,7 +107,7 @@ void vp_modern_set_extended_features(struct virtio_pci_modern_device *mdev, static inline u64 vp_modern_get_features(struct virtio_pci_modern_device *mdev) { - u64 features_array[VIRTIO_FEATURES_DWORDS]; + u64 features_array[VIRTIO_FEATURES_U64S]; vp_modern_get_extended_features(mdev, features_array); return features_array[0]; @@ -116,11 +116,11 @@ vp_modern_get_features(struct virtio_pci_modern_device *mdev) static inline u64 vp_modern_get_driver_features(struct virtio_pci_modern_device *mdev) { - u64 features_array[VIRTIO_FEATURES_DWORDS]; + u64 features_array[VIRTIO_FEATURES_U64S]; int i; vp_modern_get_driver_extended_features(mdev, features_array); - for (i = 1; i < VIRTIO_FEATURES_DWORDS; ++i) + for (i = 1; i < VIRTIO_FEATURES_U64S; ++i) WARN_ON_ONCE(features_array[i]); return features_array[0]; } @@ -128,7 +128,7 @@ vp_modern_get_driver_features(struct virtio_pci_modern_device *mdev) static inline void vp_modern_set_features(struct virtio_pci_modern_device *mdev, u64 features) { - u64 features_array[VIRTIO_FEATURES_DWORDS]; + u64 features_array[VIRTIO_FEATURES_U64S]; virtio_features_from_u64(features_array, features); vp_modern_set_extended_features(mdev, features_array); From a2f79598c6c1fd9b3a9544623c98e62194e2dd64 Mon Sep 17 00:00:00 2001 From: Cezary Rojewski Date: Wed, 26 Nov 2025 10:55:20 +0100 Subject: [PATCH 0835/3902] ASoC: Intel: catpt: Fix error path in hw_params() [ Upstream commit 86a5b621be658fc8fe594ca6db317d64de30cce1 ] Do not leave any resources hanging on the DSP side if applying user settings fails. Fixes: 768a3a3b327d ("ASoC: Intel: catpt: Optimize applying user settings") Signed-off-by: Cezary Rojewski Reviewed-by: Andy Shevchenko Link: https://patch.msgid.link/20251126095523.3925364-4-cezary.rojewski@intel.com Signed-off-by: Mark Brown Signed-off-by: Sasha Levin --- sound/soc/intel/catpt/pcm.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/sound/soc/intel/catpt/pcm.c b/sound/soc/intel/catpt/pcm.c index bf734c69c4e095..eb03cecdee281c 100644 --- a/sound/soc/intel/catpt/pcm.c +++ b/sound/soc/intel/catpt/pcm.c @@ -417,8 +417,10 @@ static int catpt_dai_hw_params(struct snd_pcm_substream *substream, return CATPT_IPC_ERROR(ret); ret = catpt_dai_apply_usettings(dai, stream); - if (ret) + if (ret) { + catpt_ipc_free_stream(cdev, stream->info.stream_hw_id); return ret; + } stream->allocated = true; return 0; From 749c2b72a2baebf023cbcdffc4c1bc50c4e6442a Mon Sep 17 00:00:00 2001 From: Mikhail Kshevetskiy Date: Wed, 26 Nov 2025 02:40:45 +0300 Subject: [PATCH 0836/3902] spi: airoha-snfi: en7523: workaround flash damaging if UART_TXD was short to GND [ Upstream commit 061795b345aff371df8f71d54ae7c7dc8ae630d0 ] Airoha EN7523 specific bug -------------------------- We found that some serial console may pull TX line to GROUND during board boot time. Airoha uses TX line as one of its bootstrap pins. On the EN7523 SoC this may lead to booting in RESERVED boot mode. It was found that some flashes operates incorrectly in RESERVED mode. Micron and Skyhigh flashes are definitely affected by the issue, Winbond flashes are not affected. Details: -------- DMA reading of odd pages on affected flashes operates incorrectly. Page reading offset (start of the page) on hardware level is replaced by 0x10. Thus results in incorrect data reading. As result OS loading becomes impossible. Usage of UBI make things even worse. On attaching, UBI will detects corruptions (because of wrong reading of odd pages) and will try to recover. For recovering UBI will erase and write 'damaged' blocks with a valid information. This will destroy all UBI data. Non-DMA reading is OK. This patch detects booting in reserved mode, turn off DMA and print big fat warning. It's worth noting that the boot configuration is preserved across reboots. Therefore, to boot normally, you should do the following: - disconnect the serial console from the board, - power cycle the board. Fixes: a403997c12019 ("spi: airoha: add SPI-NAND Flash controller driver") Signed-off-by: Mikhail Kshevetskiy Reviewed-by: AngeloGioacchino Del Regno Reviewed-by: Andy Shevchenko Link: https://patch.msgid.link/20251125234047.1101985-2-mikhail.kshevetskiy@iopsys.eu Signed-off-by: Mark Brown Signed-off-by: Sasha Levin --- drivers/spi/spi-airoha-snfi.c | 25 ++++++++++++++++++++++++- 1 file changed, 24 insertions(+), 1 deletion(-) diff --git a/drivers/spi/spi-airoha-snfi.c b/drivers/spi/spi-airoha-snfi.c index b78163eaed61d4..20b5d469d519a0 100644 --- a/drivers/spi/spi-airoha-snfi.c +++ b/drivers/spi/spi-airoha-snfi.c @@ -1030,6 +1030,11 @@ static const struct spi_controller_mem_ops airoha_snand_mem_ops = { .dirmap_write = airoha_snand_dirmap_write, }; +static const struct spi_controller_mem_ops airoha_snand_nodma_mem_ops = { + .supports_op = airoha_snand_supports_op, + .exec_op = airoha_snand_exec_op, +}; + static int airoha_snand_setup(struct spi_device *spi) { struct airoha_snand_ctrl *as_ctrl; @@ -1104,7 +1109,9 @@ static int airoha_snand_probe(struct platform_device *pdev) struct airoha_snand_ctrl *as_ctrl; struct device *dev = &pdev->dev; struct spi_controller *ctrl; + bool dma_enable = true; void __iomem *base; + u32 sfc_strap; int err; ctrl = devm_spi_alloc_host(dev, sizeof(*as_ctrl)); @@ -1139,12 +1146,28 @@ static int airoha_snand_probe(struct platform_device *pdev) return dev_err_probe(dev, PTR_ERR(as_ctrl->spi_clk), "unable to get spi clk\n"); + if (device_is_compatible(dev, "airoha,en7523-snand")) { + err = regmap_read(as_ctrl->regmap_ctrl, + REG_SPI_CTRL_SFC_STRAP, &sfc_strap); + if (err) + return err; + + if (!(sfc_strap & 0x04)) { + dma_enable = false; + dev_warn(dev, "Detected booting in RESERVED mode (UART_TXD was short to GND).\n"); + dev_warn(dev, "This mode is known for incorrect DMA reading of some flashes.\n"); + dev_warn(dev, "Much slower PIO mode will be used to prevent flash data damage.\n"); + dev_warn(dev, "Unplug UART cable and power cycle board to get full performance.\n"); + } + } + err = dma_set_mask(as_ctrl->dev, DMA_BIT_MASK(32)); if (err) return err; ctrl->num_chipselect = 2; - ctrl->mem_ops = &airoha_snand_mem_ops; + ctrl->mem_ops = dma_enable ? &airoha_snand_mem_ops + : &airoha_snand_nodma_mem_ops; ctrl->bits_per_word_mask = SPI_BPW_MASK(8); ctrl->mode_bits = SPI_RX_DUAL; ctrl->setup = airoha_snand_setup; From 1f11f4e8b7f78527c3a99cc8c2059a3e116438ef Mon Sep 17 00:00:00 2001 From: Marek Szyprowski Date: Wed, 26 Nov 2025 12:00:38 +0100 Subject: [PATCH 0837/3902] soc: samsung: exynos-pmu: Fix structure initialization [ Upstream commit 2224ea67c75d0a0b9eaf803d0dfdab8d0c601c35 ] Commit 78b72897a5c8 ("soc: samsung: exynos-pmu: Enable CPU Idle for gs101") added system wide suspend/resume callbacks to Exynos PMU driver, but some items used by these callbacks are initialized only on GS101-compatible boards. Move that initialization to exynos_pmu_probe() to avoid potential lockdep warnings like below observed during system suspend/resume cycle: INFO: trying to register non-static key. The code is fine but needs lockdep annotation, or maybe you didn't initialize this object before use? turning off the locking correctness validator. CPU: 0 UID: 0 PID: 2134 Comm: rtcwake Not tainted 6.18.0-rc7-next-20251126-00039-g1d656a1af243 #11794 PREEMPT Hardware name: Samsung Exynos (Flattened Device Tree) Call trace: unwind_backtrace from show_stack+0x10/0x14 show_stack from dump_stack_lvl+0x68/0x88 dump_stack_lvl from register_lock_class+0x970/0x988 register_lock_class from __lock_acquire+0xc8/0x29ec __lock_acquire from lock_acquire+0x134/0x39c lock_acquire from _raw_spin_lock+0x38/0x48 _raw_spin_lock from exynos_cpupm_suspend_noirq+0x18/0x34 exynos_cpupm_suspend_noirq from dpm_run_callback+0x98/0x2b8 dpm_run_callback from device_suspend_noirq+0x8c/0x310 Fixes: 78b72897a5c8 ("soc: samsung: exynos-pmu: Enable CPU Idle for gs101") Signed-off-by: Marek Szyprowski Link: https://patch.msgid.link/20251126110038.3326768-1-m.szyprowski@samsung.com [krzk: include calltrace into commit msg] Signed-off-by: Krzysztof Kozlowski Signed-off-by: Sasha Levin --- drivers/soc/samsung/exynos-pmu.c | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/drivers/soc/samsung/exynos-pmu.c b/drivers/soc/samsung/exynos-pmu.c index 22c50ca2aa79bf..df5b1f299a2604 100644 --- a/drivers/soc/samsung/exynos-pmu.c +++ b/drivers/soc/samsung/exynos-pmu.c @@ -585,10 +585,6 @@ static int setup_cpuhp_and_cpuidle(struct device *dev) if (!pmu_context->in_cpuhp) return -ENOMEM; - raw_spin_lock_init(&pmu_context->cpupm_lock); - pmu_context->sys_inreboot = false; - pmu_context->sys_insuspend = false; - /* set PMU to power on */ for_each_online_cpu(cpu) gs101_cpuhp_pmu_online(cpu); @@ -657,6 +653,9 @@ static int exynos_pmu_probe(struct platform_device *pdev) pmu_context->pmureg = regmap; pmu_context->dev = dev; + raw_spin_lock_init(&pmu_context->cpupm_lock); + pmu_context->sys_inreboot = false; + pmu_context->sys_insuspend = false; if (pmu_context->pmu_data && pmu_context->pmu_data->pmu_cpuhp) { ret = setup_cpuhp_and_cpuidle(dev); From 104a6ed7a4c0301cb75423b79b1f628e029a0713 Mon Sep 17 00:00:00 2001 From: Marek Szyprowski Date: Wed, 26 Nov 2025 11:26:15 +0100 Subject: [PATCH 0838/3902] ARM: dts: samsung: universal_c210: turn off SDIO WLAN chip during system suspend [ Upstream commit 97aee67e2406ea381408915e606c5f86448f3949 ] Commit 8c3170628a9c ("wifi: brcmfmac: keep power during suspend if board requires it") changed default behavior of the BRCMFMAC driver, which now keeps SDIO card powered during system suspend to enable optional support for WOWL. This feature is not supported by the legacy Exynos4 based boards and leads to WLAN disfunction after system suspend/resume cycle. Fix this by annotating SDIO host used by WLAN chip with 'cap-power-off-card' property, which should have been there from the beginning. Fixes: f1b0ffaa686f ("ARM: dts: exynos: Enable WLAN support for the UniversalC210 board") Signed-off-by: Marek Szyprowski Link: https://patch.msgid.link/20251126102618.3103517-2-m.szyprowski@samsung.com Signed-off-by: Krzysztof Kozlowski Signed-off-by: Sasha Levin --- arch/arm/boot/dts/samsung/exynos4210-universal_c210.dts | 1 + 1 file changed, 1 insertion(+) diff --git a/arch/arm/boot/dts/samsung/exynos4210-universal_c210.dts b/arch/arm/boot/dts/samsung/exynos4210-universal_c210.dts index bdc30f8cf748f5..91490693432b68 100644 --- a/arch/arm/boot/dts/samsung/exynos4210-universal_c210.dts +++ b/arch/arm/boot/dts/samsung/exynos4210-universal_c210.dts @@ -610,6 +610,7 @@ #size-cells = <0>; non-removable; + cap-power-off-card; bus-width = <4>; mmc-pwrseq = <&wlan_pwrseq>; vmmc-supply = <&ldo5_reg>; From ad3e7ec8e21e6087269db9c71b7fd42301f05821 Mon Sep 17 00:00:00 2001 From: Marek Szyprowski Date: Wed, 26 Nov 2025 11:26:16 +0100 Subject: [PATCH 0839/3902] ARM: dts: samsung: exynos4210-i9100: turn off SDIO WLAN chip during system suspend [ Upstream commit 863d69923bdb6f414d0a3f504f1dfaeacbc00b09 ] Commit 8c3170628a9c ("wifi: brcmfmac: keep power during suspend if board requires it") changed default behavior of the BRCMFMAC driver, which now keeps SDIO card powered during system suspend to enable optional support for WOWL. This feature is not supported by the legacy Exynos4 based boards and leads to WLAN disfunction after system suspend/resume cycle. Fix this by annotating SDIO host used by WLAN chip with 'cap-power-off-card' property, which should have been there from the beginning. Fixes: 8620cc2f99b7 ("ARM: dts: exynos: Add devicetree file for the Galaxy S2") Signed-off-by: Marek Szyprowski Link: https://patch.msgid.link/20251126102618.3103517-3-m.szyprowski@samsung.com Signed-off-by: Krzysztof Kozlowski Signed-off-by: Sasha Levin --- arch/arm/boot/dts/samsung/exynos4210-i9100.dts | 1 + 1 file changed, 1 insertion(+) diff --git a/arch/arm/boot/dts/samsung/exynos4210-i9100.dts b/arch/arm/boot/dts/samsung/exynos4210-i9100.dts index df229fb8a16beb..8a635bee59fa9b 100644 --- a/arch/arm/boot/dts/samsung/exynos4210-i9100.dts +++ b/arch/arm/boot/dts/samsung/exynos4210-i9100.dts @@ -853,6 +853,7 @@ #size-cells = <0>; non-removable; + cap-power-off-card; bus-width = <4>; mmc-pwrseq = <&wlan_pwrseq>; vmmc-supply = <&vtf_reg>; From b4eb208dee57dc51d3a7002334b3dfb23cf2ce16 Mon Sep 17 00:00:00 2001 From: Marek Szyprowski Date: Wed, 26 Nov 2025 11:26:17 +0100 Subject: [PATCH 0840/3902] ARM: dts: samsung: exynos4210-trats: turn off SDIO WLAN chip during system suspend [ Upstream commit 97cc9c346b2c9cde075b9420fc172137d2427711 ] Commit 8c3170628a9c ("wifi: brcmfmac: keep power during suspend if board requires it") changed default behavior of the BRCMFMAC driver, which now keeps SDIO card powered during system suspend to enable optional support for WOWL. This feature is not supported by the legacy Exynos4 based boards and leads to WLAN disfunction after system suspend/resume cycle. Fix this by annotating SDIO host used by WLAN chip with 'cap-power-off-card' property, which should have been there from the beginning. Fixes: a19f6efc01df ("ARM: dts: exynos: Enable WLAN support for the Trats board") Signed-off-by: Marek Szyprowski Link: https://patch.msgid.link/20251126102618.3103517-4-m.szyprowski@samsung.com Signed-off-by: Krzysztof Kozlowski Signed-off-by: Sasha Levin --- arch/arm/boot/dts/samsung/exynos4210-trats.dts | 1 + 1 file changed, 1 insertion(+) diff --git a/arch/arm/boot/dts/samsung/exynos4210-trats.dts b/arch/arm/boot/dts/samsung/exynos4210-trats.dts index 95e0e01b6ff6b7..6bd902cb8f4ad2 100644 --- a/arch/arm/boot/dts/samsung/exynos4210-trats.dts +++ b/arch/arm/boot/dts/samsung/exynos4210-trats.dts @@ -518,6 +518,7 @@ #size-cells = <0>; non-removable; + cap-power-off-card; bus-width = <4>; mmc-pwrseq = <&wlan_pwrseq>; vmmc-supply = <&tflash_reg>; From ed3cc640b4f1662e7b4a8941bead6779f2d48cb3 Mon Sep 17 00:00:00 2001 From: Marek Szyprowski Date: Wed, 26 Nov 2025 11:26:18 +0100 Subject: [PATCH 0841/3902] ARM: dts: samsung: exynos4412-midas: turn off SDIO WLAN chip during system suspend [ Upstream commit 2ff147fdfa99b8cbb8c2833e685fde7c42580ae6 ] Commit 8c3170628a9c ("wifi: brcmfmac: keep power during suspend if board requires it") changed default behavior of the BRCMFMAC driver, which now keeps SDIO card powered during system suspend to enable optional support for WOWL. This feature is not supported by the legacy Exynos4 based boards and leads to WLAN disfunction after system suspend/resume cycle. Fix this by annotating SDIO host used by WLAN chip with 'cap-power-off-card' property, which should have been there from the beginning. Fixes: f77cbb9a3e5d ("ARM: dts: exynos: Add bcm4334 device node to Trats2") Signed-off-by: Marek Szyprowski Link: https://patch.msgid.link/20251126102618.3103517-5-m.szyprowski@samsung.com Signed-off-by: Krzysztof Kozlowski Signed-off-by: Sasha Levin --- arch/arm/boot/dts/samsung/exynos4412-midas.dtsi | 1 + 1 file changed, 1 insertion(+) diff --git a/arch/arm/boot/dts/samsung/exynos4412-midas.dtsi b/arch/arm/boot/dts/samsung/exynos4412-midas.dtsi index 05ddddb565ee36..48245b1665a69c 100644 --- a/arch/arm/boot/dts/samsung/exynos4412-midas.dtsi +++ b/arch/arm/boot/dts/samsung/exynos4412-midas.dtsi @@ -1440,6 +1440,7 @@ #address-cells = <1>; #size-cells = <0>; non-removable; + cap-power-off-card; bus-width = <4>; mmc-pwrseq = <&wlan_pwrseq>; From 64099b5c0aeb70bc7cd5556eb7f59c5b4a5010bf Mon Sep 17 00:00:00 2001 From: sparkhuang Date: Thu, 27 Nov 2025 10:57:16 +0800 Subject: [PATCH 0842/3902] regulator: core: Protect regulator_supply_alias_list with regulator_list_mutex [ Upstream commit 0cc15a10c3b4ab14cd71b779fd5c9ca0cb2bc30d ] regulator_supply_alias_list was accessed without any locking in regulator_supply_alias(), regulator_register_supply_alias(), and regulator_unregister_supply_alias(). Concurrent registration, unregistration and lookups can race, leading to: 1 use-after-free if an alias entry is removed while being read, 2 duplicate entries when two threads register the same alias, 3 inconsistent alias mappings observed by consumers. Protect all traversals, insertions and deletions on regulator_supply_alias_list with the existing regulator_list_mutex. Fixes: a06ccd9c3785f ("regulator: core: Add ability to create a lookup alias for supply") Signed-off-by: sparkhuang Reviewed-by: Charles Keepax Link: https://patch.msgid.link/20251127025716.5440-1-huangshaobo3@xiaomi.com Signed-off-by: Mark Brown Signed-off-by: Sasha Levin --- drivers/regulator/core.c | 32 ++++++++++++++++++++------------ 1 file changed, 20 insertions(+), 12 deletions(-) diff --git a/drivers/regulator/core.c b/drivers/regulator/core.c index fc93612f4ec0c3..b38b087eccfd77 100644 --- a/drivers/regulator/core.c +++ b/drivers/regulator/core.c @@ -1947,6 +1947,7 @@ static void regulator_supply_alias(struct device **dev, const char **supply) { struct regulator_supply_alias *map; + mutex_lock(®ulator_list_mutex); map = regulator_find_supply_alias(*dev, *supply); if (map) { dev_dbg(*dev, "Mapping supply %s to %s,%s\n", @@ -1955,6 +1956,7 @@ static void regulator_supply_alias(struct device **dev, const char **supply) *dev = map->alias_dev; *supply = map->alias_supply; } + mutex_unlock(®ulator_list_mutex); } static int regulator_match(struct device *dev, const void *data) @@ -2497,22 +2499,26 @@ int regulator_register_supply_alias(struct device *dev, const char *id, const char *alias_id) { struct regulator_supply_alias *map; + struct regulator_supply_alias *new_map; - map = regulator_find_supply_alias(dev, id); - if (map) - return -EEXIST; - - map = kzalloc(sizeof(struct regulator_supply_alias), GFP_KERNEL); - if (!map) + new_map = kzalloc(sizeof(struct regulator_supply_alias), GFP_KERNEL); + if (!new_map) return -ENOMEM; - map->src_dev = dev; - map->src_supply = id; - map->alias_dev = alias_dev; - map->alias_supply = alias_id; - - list_add(&map->list, ®ulator_supply_alias_list); + mutex_lock(®ulator_list_mutex); + map = regulator_find_supply_alias(dev, id); + if (map) { + mutex_unlock(®ulator_list_mutex); + kfree(new_map); + return -EEXIST; + } + new_map->src_dev = dev; + new_map->src_supply = id; + new_map->alias_dev = alias_dev; + new_map->alias_supply = alias_id; + list_add(&new_map->list, ®ulator_supply_alias_list); + mutex_unlock(®ulator_list_mutex); pr_info("Adding alias for supply %s,%s -> %s,%s\n", id, dev_name(dev), alias_id, dev_name(alias_dev)); @@ -2532,11 +2538,13 @@ void regulator_unregister_supply_alias(struct device *dev, const char *id) { struct regulator_supply_alias *map; + mutex_lock(®ulator_list_mutex); map = regulator_find_supply_alias(dev, id); if (map) { list_del(&map->list); kfree(map); } + mutex_unlock(®ulator_list_mutex); } EXPORT_SYMBOL_GPL(regulator_unregister_supply_alias); From f2888bfb900332d5a06f9387a4af537da652d2ab Mon Sep 17 00:00:00 2001 From: Chen-Yu Tsai Date: Thu, 27 Nov 2025 18:00:42 +0800 Subject: [PATCH 0843/3902] arm64: dts: mediatek: mt8195: Fix address range for JPEG decoder core 1 [ Upstream commit ce48af13e6381772cc27676be63a6d9176c14a49 ] The base address of JPEG decoder core 1 should start at 0x10000, and have a size of 0x10000, i.e. it is right after core 0. Instead the core has the same base address as core 0, and with a crazy large size. This looks like a mixup of address and size cells when the ranges were converted. This causes the kernel to fail to register the second core due to sysfs name conflicts: sysfs: cannot create duplicate filename '/devices/platform/soc/soc:jpeg-decoder@1a040000/1a040000.jpgdec' Fix up the address range. Fixes: a9eac43d039f ("arm64: dts: mediatek: mt8195: Fix ranges for jpeg enc/decoder nodes") Signed-off-by: Chen-Yu Tsai Acked-by: AngeloGioacchino Del Regno Link: https://lore.kernel.org/r/20251127100044.612825-1-wenst@chromium.org Signed-off-by: Arnd Bergmann Signed-off-by: Sasha Levin --- arch/arm64/boot/dts/mediatek/mt8195.dtsi | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/arch/arm64/boot/dts/mediatek/mt8195.dtsi b/arch/arm64/boot/dts/mediatek/mt8195.dtsi index ec452d65703149..c7adafaa83288d 100644 --- a/arch/arm64/boot/dts/mediatek/mt8195.dtsi +++ b/arch/arm64/boot/dts/mediatek/mt8195.dtsi @@ -3067,7 +3067,7 @@ jpgdec@0,10000 { compatible = "mediatek,mt8195-jpgdec-hw"; - reg = <0 0 0x10000 0x10000>;/* JPGDEC_C1 */ + reg = <0 0x10000 0 0x10000>;/* JPGDEC_C1 */ iommus = <&iommu_vdo M4U_PORT_L19_JPGDEC_WDMA0>, <&iommu_vdo M4U_PORT_L19_JPGDEC_BSDMA0>, <&iommu_vdo M4U_PORT_L19_JPGDEC_WDMA1>, From 3c0493f81dfac8138f5a9a8c23a97671526b78ce Mon Sep 17 00:00:00 2001 From: Ilias Stamatis Date: Mon, 24 Nov 2025 16:53:49 +0000 Subject: [PATCH 0844/3902] Reinstate "resource: avoid unnecessary lookups in find_next_iomem_res()" MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [ Upstream commit 6fb3acdebf65a72df0a95f9fd2c901ff2bc9a3a2 ] Commit 97523a4edb7b ("kernel/resource: remove first_lvl / siblings_only logic") removed an optimization introduced by commit 756398750e11 ("resource: avoid unnecessary lookups in find_next_iomem_res()"). That was not called out in the message of the first commit explicitly so it's not entirely clear whether removing the optimization happened inadvertently or not. As the original commit message of the optimization explains there is no point considering the children of a subtree in find_next_iomem_res() if the top level range does not match. Reinstating the optimization results in performance improvements in systems where /proc/iomem is ~5k lines long. Calling mmap() on /dev/mem in such platforms takes 700-1500μs without the optimisation and 10-50μs with the optimisation. Note that even though commit 97523a4edb7b removed the 'sibling_only' parameter from next_resource(), newer kernels have basically reinstated it under the name 'skip_children'. Link: https://lore.kernel.org/all/20251124165349.3377826-1-ilstam@amazon.com/T/#u Fixes: 97523a4edb7b ("kernel/resource: remove first_lvl / siblings_only logic") Signed-off-by: Ilias Stamatis Acked-by: David Hildenbrand (Red Hat) Cc: Andriy Shevchenko Cc: Baoquan He Cc: "Huang, Ying" Cc: Nadav Amit Signed-off-by: Andrew Morton Signed-off-by: Sasha Levin --- kernel/resource.c | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/kernel/resource.c b/kernel/resource.c index b9fa2a4ce089cf..e4e9bac12e6e17 100644 --- a/kernel/resource.c +++ b/kernel/resource.c @@ -341,6 +341,8 @@ static int find_next_iomem_res(resource_size_t start, resource_size_t end, unsigned long flags, unsigned long desc, struct resource *res) { + /* Skip children until we find a top level range that matches */ + bool skip_children = true; struct resource *p; if (!res) @@ -351,7 +353,7 @@ static int find_next_iomem_res(resource_size_t start, resource_size_t end, read_lock(&resource_lock); - for_each_resource(&iomem_resource, p, false) { + for_each_resource(&iomem_resource, p, skip_children) { /* If we passed the resource we are looking for, stop */ if (p->start > end) { p = NULL; @@ -362,6 +364,12 @@ static int find_next_iomem_res(resource_size_t start, resource_size_t end, if (p->end < start) continue; + /* + * We found a top level range that matches what we are looking + * for. Time to start checking children too. + */ + skip_children = false; + /* Found a match, break */ if (is_type_match(p, flags, desc)) break; From 87a453f31c636de84420fcce68659abd37d269fd Mon Sep 17 00:00:00 2001 From: Pablo Neira Ayuso Date: Thu, 27 Nov 2025 23:26:22 +0000 Subject: [PATCH 0845/3902] netfilter: flowtable: check for maximum number of encapsulations in bridge vlan [ Upstream commit 634f3853cc98d73bdec8918010ee29b06981583e ] Add a sanity check to skip path discovery if the maximum number of encapsulation is reached. While at it, check for underflow too. Fixes: 26267bf9bb57 ("netfilter: flowtable: bridge vlan hardware offload and switchdev") Signed-off-by: Pablo Neira Ayuso Signed-off-by: Sasha Levin --- net/netfilter/nft_flow_offload.c | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/net/netfilter/nft_flow_offload.c b/net/netfilter/nft_flow_offload.c index 14dd1c0698c3c9..e95e5f59a3d6a3 100644 --- a/net/netfilter/nft_flow_offload.c +++ b/net/netfilter/nft_flow_offload.c @@ -141,12 +141,19 @@ static void nft_dev_path_info(const struct net_device_path_stack *stack, info->ingress_vlans |= BIT(info->num_encaps - 1); break; case DEV_PATH_BR_VLAN_TAG: + if (info->num_encaps >= NF_FLOW_TABLE_ENCAP_MAX) { + info->indev = NULL; + break; + } info->encap[info->num_encaps].id = path->bridge.vlan_id; info->encap[info->num_encaps].proto = path->bridge.vlan_proto; info->num_encaps++; break; case DEV_PATH_BR_VLAN_UNTAG: - info->num_encaps--; + if (WARN_ON_ONCE(info->num_encaps-- == 0)) { + info->indev = NULL; + break; + } break; case DEV_PATH_BR_VLAN_KEEP: break; From f6904ed15ed1a188543057e3cb0d02daa80edfc9 Mon Sep 17 00:00:00 2001 From: Fernando Fernandez Mancera Date: Fri, 21 Nov 2025 01:14:30 +0100 Subject: [PATCH 0846/3902] netfilter: nf_conncount: rework API to use sk_buff directly [ Upstream commit be102eb6a0e7c03db00e50540622f4e43b2d2844 ] When using nf_conncount infrastructure for non-confirmed connections a duplicated track is possible due to an optimization introduced since commit d265929930e2 ("netfilter: nf_conncount: reduce unnecessary GC"). In order to fix this introduce a new conncount API that receives directly an sk_buff struct. It fetches the tuple and zone and the corresponding ct from it. It comes with both existing conncount variants nf_conncount_count_skb() and nf_conncount_add_skb(). In addition remove the old API and adjust all the users to use the new one. This way, for each sk_buff struct it is possible to check if there is a ct present and already confirmed. If so, skip the add operation. Fixes: d265929930e2 ("netfilter: nf_conncount: reduce unnecessary GC") Signed-off-by: Fernando Fernandez Mancera Signed-off-by: Pablo Neira Ayuso Signed-off-by: Sasha Levin --- include/net/netfilter/nf_conntrack_count.h | 17 +- net/netfilter/nf_conncount.c | 177 ++++++++++++++------- net/netfilter/nft_connlimit.c | 21 +-- net/netfilter/xt_connlimit.c | 14 +- net/openvswitch/conntrack.c | 16 +- 5 files changed, 142 insertions(+), 103 deletions(-) diff --git a/include/net/netfilter/nf_conntrack_count.h b/include/net/netfilter/nf_conntrack_count.h index 1b58b5b91ff6a4..52a06de41aa0f9 100644 --- a/include/net/netfilter/nf_conntrack_count.h +++ b/include/net/netfilter/nf_conntrack_count.h @@ -18,15 +18,14 @@ struct nf_conncount_list { struct nf_conncount_data *nf_conncount_init(struct net *net, unsigned int keylen); void nf_conncount_destroy(struct net *net, struct nf_conncount_data *data); -unsigned int nf_conncount_count(struct net *net, - struct nf_conncount_data *data, - const u32 *key, - const struct nf_conntrack_tuple *tuple, - const struct nf_conntrack_zone *zone); - -int nf_conncount_add(struct net *net, struct nf_conncount_list *list, - const struct nf_conntrack_tuple *tuple, - const struct nf_conntrack_zone *zone); +unsigned int nf_conncount_count_skb(struct net *net, + const struct sk_buff *skb, + u16 l3num, + struct nf_conncount_data *data, + const u32 *key); + +int nf_conncount_add_skb(struct net *net, const struct sk_buff *skb, + u16 l3num, struct nf_conncount_list *list); void nf_conncount_list_init(struct nf_conncount_list *list); diff --git a/net/netfilter/nf_conncount.c b/net/netfilter/nf_conncount.c index 913ede2f57f9a9..0ffc5ff78a7142 100644 --- a/net/netfilter/nf_conncount.c +++ b/net/netfilter/nf_conncount.c @@ -122,15 +122,65 @@ find_or_evict(struct net *net, struct nf_conncount_list *list, return ERR_PTR(-EAGAIN); } +static bool get_ct_or_tuple_from_skb(struct net *net, + const struct sk_buff *skb, + u16 l3num, + struct nf_conn **ct, + struct nf_conntrack_tuple *tuple, + const struct nf_conntrack_zone **zone, + bool *refcounted) +{ + const struct nf_conntrack_tuple_hash *h; + enum ip_conntrack_info ctinfo; + struct nf_conn *found_ct; + + found_ct = nf_ct_get(skb, &ctinfo); + if (found_ct && !nf_ct_is_template(found_ct)) { + *tuple = found_ct->tuplehash[IP_CT_DIR_ORIGINAL].tuple; + *zone = nf_ct_zone(found_ct); + *ct = found_ct; + return true; + } + + if (!nf_ct_get_tuplepr(skb, skb_network_offset(skb), l3num, net, tuple)) + return false; + + if (found_ct) + *zone = nf_ct_zone(found_ct); + + h = nf_conntrack_find_get(net, *zone, tuple); + if (!h) + return true; + + found_ct = nf_ct_tuplehash_to_ctrack(h); + *refcounted = true; + *ct = found_ct; + + return true; +} + static int __nf_conncount_add(struct net *net, - struct nf_conncount_list *list, - const struct nf_conntrack_tuple *tuple, - const struct nf_conntrack_zone *zone) + const struct sk_buff *skb, + u16 l3num, + struct nf_conncount_list *list) { + const struct nf_conntrack_zone *zone = &nf_ct_zone_dflt; const struct nf_conntrack_tuple_hash *found; struct nf_conncount_tuple *conn, *conn_n; + struct nf_conntrack_tuple tuple; + struct nf_conn *ct = NULL; struct nf_conn *found_ct; unsigned int collect = 0; + bool refcounted = false; + + if (!get_ct_or_tuple_from_skb(net, skb, l3num, &ct, &tuple, &zone, &refcounted)) + return -ENOENT; + + if (ct && nf_ct_is_confirmed(ct)) { + if (refcounted) + nf_ct_put(ct); + return 0; + } if ((u32)jiffies == list->last_gc) goto add_new_node; @@ -144,10 +194,10 @@ static int __nf_conncount_add(struct net *net, if (IS_ERR(found)) { /* Not found, but might be about to be confirmed */ if (PTR_ERR(found) == -EAGAIN) { - if (nf_ct_tuple_equal(&conn->tuple, tuple) && + if (nf_ct_tuple_equal(&conn->tuple, &tuple) && nf_ct_zone_id(&conn->zone, conn->zone.dir) == nf_ct_zone_id(zone, zone->dir)) - return 0; /* already exists */ + goto out_put; /* already exists */ } else { collect++; } @@ -156,7 +206,7 @@ static int __nf_conncount_add(struct net *net, found_ct = nf_ct_tuplehash_to_ctrack(found); - if (nf_ct_tuple_equal(&conn->tuple, tuple) && + if (nf_ct_tuple_equal(&conn->tuple, &tuple) && nf_ct_zone_equal(found_ct, zone, zone->dir)) { /* * We should not see tuples twice unless someone hooks @@ -165,7 +215,7 @@ static int __nf_conncount_add(struct net *net, * Attempt to avoid a re-add in this case. */ nf_ct_put(found_ct); - return 0; + goto out_put; } else if (already_closed(found_ct)) { /* * we do not care about connections which are @@ -188,31 +238,35 @@ static int __nf_conncount_add(struct net *net, if (conn == NULL) return -ENOMEM; - conn->tuple = *tuple; + conn->tuple = tuple; conn->zone = *zone; conn->cpu = raw_smp_processor_id(); conn->jiffies32 = (u32)jiffies; list_add_tail(&conn->node, &list->head); list->count++; list->last_gc = (u32)jiffies; + +out_put: + if (refcounted) + nf_ct_put(ct); return 0; } -int nf_conncount_add(struct net *net, - struct nf_conncount_list *list, - const struct nf_conntrack_tuple *tuple, - const struct nf_conntrack_zone *zone) +int nf_conncount_add_skb(struct net *net, + const struct sk_buff *skb, + u16 l3num, + struct nf_conncount_list *list) { int ret; /* check the saved connections */ spin_lock_bh(&list->list_lock); - ret = __nf_conncount_add(net, list, tuple, zone); + ret = __nf_conncount_add(net, skb, l3num, list); spin_unlock_bh(&list->list_lock); return ret; } -EXPORT_SYMBOL_GPL(nf_conncount_add); +EXPORT_SYMBOL_GPL(nf_conncount_add_skb); void nf_conncount_list_init(struct nf_conncount_list *list) { @@ -309,19 +363,22 @@ static void schedule_gc_worker(struct nf_conncount_data *data, int tree) static unsigned int insert_tree(struct net *net, + const struct sk_buff *skb, + u16 l3num, struct nf_conncount_data *data, struct rb_root *root, unsigned int hash, - const u32 *key, - const struct nf_conntrack_tuple *tuple, - const struct nf_conntrack_zone *zone) + const u32 *key) { struct nf_conncount_rb *gc_nodes[CONNCOUNT_GC_MAX_NODES]; + const struct nf_conntrack_zone *zone = &nf_ct_zone_dflt; + bool do_gc = true, refcounted = false; + unsigned int count = 0, gc_count = 0; struct rb_node **rbnode, *parent; - struct nf_conncount_rb *rbconn; + struct nf_conntrack_tuple tuple; struct nf_conncount_tuple *conn; - unsigned int count = 0, gc_count = 0; - bool do_gc = true; + struct nf_conncount_rb *rbconn; + struct nf_conn *ct = NULL; spin_lock_bh(&nf_conncount_locks[hash]); restart: @@ -340,7 +397,7 @@ insert_tree(struct net *net, } else { int ret; - ret = nf_conncount_add(net, &rbconn->list, tuple, zone); + ret = nf_conncount_add_skb(net, skb, l3num, &rbconn->list); if (ret) count = 0; /* hotdrop */ else @@ -364,30 +421,35 @@ insert_tree(struct net *net, goto restart; } - /* expected case: match, insert new node */ - rbconn = kmem_cache_alloc(conncount_rb_cachep, GFP_ATOMIC); - if (rbconn == NULL) - goto out_unlock; + if (get_ct_or_tuple_from_skb(net, skb, l3num, &ct, &tuple, &zone, &refcounted)) { + /* expected case: match, insert new node */ + rbconn = kmem_cache_alloc(conncount_rb_cachep, GFP_ATOMIC); + if (rbconn == NULL) + goto out_unlock; - conn = kmem_cache_alloc(conncount_conn_cachep, GFP_ATOMIC); - if (conn == NULL) { - kmem_cache_free(conncount_rb_cachep, rbconn); - goto out_unlock; - } + conn = kmem_cache_alloc(conncount_conn_cachep, GFP_ATOMIC); + if (conn == NULL) { + kmem_cache_free(conncount_rb_cachep, rbconn); + goto out_unlock; + } - conn->tuple = *tuple; - conn->zone = *zone; - conn->cpu = raw_smp_processor_id(); - conn->jiffies32 = (u32)jiffies; - memcpy(rbconn->key, key, sizeof(u32) * data->keylen); + conn->tuple = tuple; + conn->zone = *zone; + conn->cpu = raw_smp_processor_id(); + conn->jiffies32 = (u32)jiffies; + memcpy(rbconn->key, key, sizeof(u32) * data->keylen); + + nf_conncount_list_init(&rbconn->list); + list_add(&conn->node, &rbconn->list.head); + count = 1; + rbconn->list.count = count; - nf_conncount_list_init(&rbconn->list); - list_add(&conn->node, &rbconn->list.head); - count = 1; - rbconn->list.count = count; + rb_link_node_rcu(&rbconn->node, parent, rbnode); + rb_insert_color(&rbconn->node, root); - rb_link_node_rcu(&rbconn->node, parent, rbnode); - rb_insert_color(&rbconn->node, root); + if (refcounted) + nf_ct_put(ct); + } out_unlock: spin_unlock_bh(&nf_conncount_locks[hash]); return count; @@ -395,10 +457,10 @@ insert_tree(struct net *net, static unsigned int count_tree(struct net *net, + const struct sk_buff *skb, + u16 l3num, struct nf_conncount_data *data, - const u32 *key, - const struct nf_conntrack_tuple *tuple, - const struct nf_conntrack_zone *zone) + const u32 *key) { struct rb_root *root; struct rb_node *parent; @@ -422,7 +484,7 @@ count_tree(struct net *net, } else { int ret; - if (!tuple) { + if (!skb) { nf_conncount_gc_list(net, &rbconn->list); return rbconn->list.count; } @@ -437,7 +499,7 @@ count_tree(struct net *net, } /* same source network -> be counted! */ - ret = __nf_conncount_add(net, &rbconn->list, tuple, zone); + ret = __nf_conncount_add(net, skb, l3num, &rbconn->list); spin_unlock_bh(&rbconn->list.list_lock); if (ret) return 0; /* hotdrop */ @@ -446,10 +508,10 @@ count_tree(struct net *net, } } - if (!tuple) + if (!skb) return 0; - return insert_tree(net, data, root, hash, key, tuple, zone); + return insert_tree(net, skb, l3num, data, root, hash, key); } static void tree_gc_worker(struct work_struct *work) @@ -511,18 +573,19 @@ static void tree_gc_worker(struct work_struct *work) } /* Count and return number of conntrack entries in 'net' with particular 'key'. - * If 'tuple' is not null, insert it into the accounting data structure. - * Call with RCU read lock. + * If 'skb' is not null, insert the corresponding tuple into the accounting + * data structure. Call with RCU read lock. */ -unsigned int nf_conncount_count(struct net *net, - struct nf_conncount_data *data, - const u32 *key, - const struct nf_conntrack_tuple *tuple, - const struct nf_conntrack_zone *zone) +unsigned int nf_conncount_count_skb(struct net *net, + const struct sk_buff *skb, + u16 l3num, + struct nf_conncount_data *data, + const u32 *key) { - return count_tree(net, data, key, tuple, zone); + return count_tree(net, skb, l3num, data, key); + } -EXPORT_SYMBOL_GPL(nf_conncount_count); +EXPORT_SYMBOL_GPL(nf_conncount_count_skb); struct nf_conncount_data *nf_conncount_init(struct net *net, unsigned int keylen) { diff --git a/net/netfilter/nft_connlimit.c b/net/netfilter/nft_connlimit.c index fc35a11cdca209..5df7134131d29f 100644 --- a/net/netfilter/nft_connlimit.c +++ b/net/netfilter/nft_connlimit.c @@ -24,26 +24,11 @@ static inline void nft_connlimit_do_eval(struct nft_connlimit *priv, const struct nft_pktinfo *pkt, const struct nft_set_ext *ext) { - const struct nf_conntrack_zone *zone = &nf_ct_zone_dflt; - const struct nf_conntrack_tuple *tuple_ptr; - struct nf_conntrack_tuple tuple; - enum ip_conntrack_info ctinfo; - const struct nf_conn *ct; unsigned int count; + int err; - tuple_ptr = &tuple; - - ct = nf_ct_get(pkt->skb, &ctinfo); - if (ct != NULL) { - tuple_ptr = &ct->tuplehash[IP_CT_DIR_ORIGINAL].tuple; - zone = nf_ct_zone(ct); - } else if (!nf_ct_get_tuplepr(pkt->skb, skb_network_offset(pkt->skb), - nft_pf(pkt), nft_net(pkt), &tuple)) { - regs->verdict.code = NF_DROP; - return; - } - - if (nf_conncount_add(nft_net(pkt), priv->list, tuple_ptr, zone)) { + err = nf_conncount_add_skb(nft_net(pkt), pkt->skb, nft_pf(pkt), priv->list); + if (err) { regs->verdict.code = NF_DROP; return; } diff --git a/net/netfilter/xt_connlimit.c b/net/netfilter/xt_connlimit.c index 0189f8b6b0bd15..848287ab79cfb1 100644 --- a/net/netfilter/xt_connlimit.c +++ b/net/netfilter/xt_connlimit.c @@ -31,8 +31,6 @@ connlimit_mt(const struct sk_buff *skb, struct xt_action_param *par) { struct net *net = xt_net(par); const struct xt_connlimit_info *info = par->matchinfo; - struct nf_conntrack_tuple tuple; - const struct nf_conntrack_tuple *tuple_ptr = &tuple; const struct nf_conntrack_zone *zone = &nf_ct_zone_dflt; enum ip_conntrack_info ctinfo; const struct nf_conn *ct; @@ -40,13 +38,8 @@ connlimit_mt(const struct sk_buff *skb, struct xt_action_param *par) u32 key[5]; ct = nf_ct_get(skb, &ctinfo); - if (ct != NULL) { - tuple_ptr = &ct->tuplehash[IP_CT_DIR_ORIGINAL].tuple; + if (ct) zone = nf_ct_zone(ct); - } else if (!nf_ct_get_tuplepr(skb, skb_network_offset(skb), - xt_family(par), net, &tuple)) { - goto hotdrop; - } if (xt_family(par) == NFPROTO_IPV6) { const struct ipv6hdr *iph = ipv6_hdr(skb); @@ -69,10 +62,9 @@ connlimit_mt(const struct sk_buff *skb, struct xt_action_param *par) key[1] = zone->id; } - connections = nf_conncount_count(net, info->data, key, tuple_ptr, - zone); + connections = nf_conncount_count_skb(net, skb, xt_family(par), info->data, key); if (connections == 0) - /* kmalloc failed, drop it entirely */ + /* kmalloc failed or tuple couldn't be found, drop it entirely */ goto hotdrop; return (connections > info->limit) ^ !!(info->flags & XT_CONNLIMIT_INVERT); diff --git a/net/openvswitch/conntrack.c b/net/openvswitch/conntrack.c index e573e92213029c..a0811e1fba6563 100644 --- a/net/openvswitch/conntrack.c +++ b/net/openvswitch/conntrack.c @@ -928,8 +928,8 @@ static u32 ct_limit_get(const struct ovs_ct_limit_info *info, u16 zone) } static int ovs_ct_check_limit(struct net *net, - const struct ovs_conntrack_info *info, - const struct nf_conntrack_tuple *tuple) + const struct sk_buff *skb, + const struct ovs_conntrack_info *info) { struct ovs_net *ovs_net = net_generic(net, ovs_net_id); const struct ovs_ct_limit_info *ct_limit_info = ovs_net->ct_limit_info; @@ -942,8 +942,9 @@ static int ovs_ct_check_limit(struct net *net, if (per_zone_limit == OVS_CT_LIMIT_UNLIMITED) return 0; - connections = nf_conncount_count(net, ct_limit_info->data, - &conncount_key, tuple, &info->zone); + connections = nf_conncount_count_skb(net, skb, info->family, + ct_limit_info->data, + &conncount_key); if (connections > per_zone_limit) return -ENOMEM; @@ -972,8 +973,7 @@ static int ovs_ct_commit(struct net *net, struct sw_flow_key *key, #if IS_ENABLED(CONFIG_NETFILTER_CONNCOUNT) if (static_branch_unlikely(&ovs_ct_limit_enabled)) { if (!nf_ct_is_confirmed(ct)) { - err = ovs_ct_check_limit(net, info, - &ct->tuplehash[IP_CT_DIR_ORIGINAL].tuple); + err = ovs_ct_check_limit(net, skb, info); if (err) { net_warn_ratelimited("openvswitch: zone: %u " "exceeds conntrack limit\n", @@ -1770,8 +1770,8 @@ static int __ovs_ct_limit_get_zone_limit(struct net *net, zone_limit.limit = limit; nf_ct_zone_init(&ct_zone, zone_id, NF_CT_DEFAULT_ZONE_DIR, 0); - zone_limit.count = nf_conncount_count(net, data, &conncount_key, NULL, - &ct_zone); + zone_limit.count = nf_conncount_count_skb(net, NULL, 0, data, + &conncount_key); return nla_put_nohdr(reply, sizeof(zone_limit), &zone_limit); } From 77ea3d8ac3d3d59b5ac9ad639e4ba107c0f2ff1e Mon Sep 17 00:00:00 2001 From: Fernando Fernandez Mancera Date: Fri, 21 Nov 2025 01:14:32 +0100 Subject: [PATCH 0847/3902] netfilter: nft_connlimit: update the count if add was skipped [ Upstream commit 69894e5b4c5e28cda5f32af33d4a92b7a4b93b0e ] Connlimit expression can be used for all kind of packets and not only for packets with connection state new. See this ruleset as example: table ip filter { chain input { type filter hook input priority filter; policy accept; tcp dport 22 ct count over 4 counter } } Currently, if the connection count goes over the limit the counter will count the packets. When a connection is closed, the connection count won't decrement as it should because it is only updated for new connections due to an optimization on __nf_conncount_add() that prevents updating the list if the connection is duplicated. To solve this problem, check whether the connection was skipped and if so, update the list. Adjust count_tree() too so the same fix is applied for xt_connlimit. Fixes: 976afca1ceba ("netfilter: nf_conncount: Early exit in nf_conncount_lookup() and cleanup") Closes: https://lore.kernel.org/netfilter/trinity-85c72a88-d762-46c3-be97-36f10e5d9796-1761173693813@3c-app-mailcom-bs12/ Signed-off-by: Fernando Fernandez Mancera Signed-off-by: Pablo Neira Ayuso Signed-off-by: Sasha Levin --- net/netfilter/nf_conncount.c | 12 ++++++++---- net/netfilter/nft_connlimit.c | 13 +++++++++++-- 2 files changed, 19 insertions(+), 6 deletions(-) diff --git a/net/netfilter/nf_conncount.c b/net/netfilter/nf_conncount.c index 0ffc5ff78a7142..b84cfb5616df4d 100644 --- a/net/netfilter/nf_conncount.c +++ b/net/netfilter/nf_conncount.c @@ -179,7 +179,7 @@ static int __nf_conncount_add(struct net *net, if (ct && nf_ct_is_confirmed(ct)) { if (refcounted) nf_ct_put(ct); - return 0; + return -EEXIST; } if ((u32)jiffies == list->last_gc) @@ -398,7 +398,7 @@ insert_tree(struct net *net, int ret; ret = nf_conncount_add_skb(net, skb, l3num, &rbconn->list); - if (ret) + if (ret && ret != -EEXIST) count = 0; /* hotdrop */ else count = rbconn->list.count; @@ -501,10 +501,14 @@ count_tree(struct net *net, /* same source network -> be counted! */ ret = __nf_conncount_add(net, skb, l3num, &rbconn->list); spin_unlock_bh(&rbconn->list.list_lock); - if (ret) + if (ret && ret != -EEXIST) { return 0; /* hotdrop */ - else + } else { + /* -EEXIST means add was skipped, update the list */ + if (ret == -EEXIST) + nf_conncount_gc_list(net, &rbconn->list); return rbconn->list.count; + } } } diff --git a/net/netfilter/nft_connlimit.c b/net/netfilter/nft_connlimit.c index 5df7134131d29f..d4964087bbc5e2 100644 --- a/net/netfilter/nft_connlimit.c +++ b/net/netfilter/nft_connlimit.c @@ -29,8 +29,17 @@ static inline void nft_connlimit_do_eval(struct nft_connlimit *priv, err = nf_conncount_add_skb(nft_net(pkt), pkt->skb, nft_pf(pkt), priv->list); if (err) { - regs->verdict.code = NF_DROP; - return; + if (err == -EEXIST) { + /* Call gc to update the list count if any connection has + * been closed already. This is useful for softlimit + * connections like limiting bandwidth based on a number + * of open connections. + */ + nf_conncount_gc_list(nft_net(pkt), priv->list); + } else { + regs->verdict.code = NF_DROP; + return; + } } count = READ_ONCE(priv->list->count); From 6d080f810ffd6b8e002ce5bee8b9c551ca2535c2 Mon Sep 17 00:00:00 2001 From: Michal Schmidt Date: Wed, 26 Nov 2025 10:48:49 +0100 Subject: [PATCH 0848/3902] iavf: Implement settime64 with -EOPNOTSUPP [ Upstream commit 1e43ebcd5152b3e681a334cc6542fb21770c3a2e ] ptp_clock_settime() assumes every ptp_clock has implemented settime64(). Stub it with -EOPNOTSUPP to prevent a NULL dereference. The fix is similar to commit 329d050bbe63 ("gve: Implement settime64 with -EOPNOTSUPP"). Fixes: d734223b2f0d ("iavf: add initial framework for registering PTP clock") Signed-off-by: Michal Schmidt Reviewed-by: Aleksandr Loktionov Reviewed-by: Tim Hostetler Link: https://patch.msgid.link/20251126094850.2842557-1-mschmidt@redhat.com Signed-off-by: Jakub Kicinski Signed-off-by: Sasha Levin --- drivers/net/ethernet/intel/iavf/iavf_ptp.c | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/drivers/net/ethernet/intel/iavf/iavf_ptp.c b/drivers/net/ethernet/intel/iavf/iavf_ptp.c index b4d5eda2e84fc1..9cbd8c15403183 100644 --- a/drivers/net/ethernet/intel/iavf/iavf_ptp.c +++ b/drivers/net/ethernet/intel/iavf/iavf_ptp.c @@ -252,6 +252,12 @@ static int iavf_ptp_gettimex64(struct ptp_clock_info *info, return iavf_read_phc_indirect(adapter, ts, sts); } +static int iavf_ptp_settime64(struct ptp_clock_info *info, + const struct timespec64 *ts) +{ + return -EOPNOTSUPP; +} + /** * iavf_ptp_cache_phc_time - Cache PHC time for performing timestamp extension * @adapter: private adapter structure @@ -320,6 +326,7 @@ static int iavf_ptp_register_clock(struct iavf_adapter *adapter) KBUILD_MODNAME, dev_name(dev)); ptp_info->owner = THIS_MODULE; ptp_info->gettimex64 = iavf_ptp_gettimex64; + ptp_info->settime64 = iavf_ptp_settime64; ptp_info->do_aux_work = iavf_ptp_do_aux_work; clock = ptp_clock_register(ptp_info, dev); From 4ac26aafdc8c7271414e2e7c0b2cb266a26591bc Mon Sep 17 00:00:00 2001 From: Antoine Tenart Date: Wed, 26 Nov 2025 11:26:25 +0100 Subject: [PATCH 0849/3902] net: vxlan: prevent NULL deref in vxlan_xmit_one [ Upstream commit 1f73a56f986005f0bc64ed23873930e2ee4f5911 ] Neither sock4 nor sock6 pointers are guaranteed to be non-NULL in vxlan_xmit_one, e.g. if the iface is brought down. This can lead to the following NULL dereference: BUG: kernel NULL pointer dereference, address: 0000000000000010 Oops: Oops: 0000 [#1] SMP NOPTI RIP: 0010:vxlan_xmit_one+0xbb3/0x1580 Call Trace: vxlan_xmit+0x429/0x610 dev_hard_start_xmit+0x55/0xa0 __dev_queue_xmit+0x6d0/0x7f0 ip_finish_output2+0x24b/0x590 ip_output+0x63/0x110 Mentioned commits changed the code path in vxlan_xmit_one and as a side effect the sock4/6 pointer validity checks in vxlan(6)_get_route were lost. Fix this by adding back checks. Since both commits being fixed were released in the same version (v6.7) and are strongly related, bundle the fixes in a single commit. Reported-by: Liang Li Fixes: 6f19b2c136d9 ("vxlan: use generic function for tunnel IPv4 route lookup") Fixes: 2aceb896ee18 ("vxlan: use generic function for tunnel IPv6 route lookup") Cc: Beniamino Galvani Signed-off-by: Antoine Tenart Reviewed-by: Ido Schimmel Tested-by: Ido Schimmel Link: https://patch.msgid.link/20251126102627.74223-1-atenart@kernel.org Signed-off-by: Jakub Kicinski Signed-off-by: Sasha Levin --- drivers/net/vxlan/vxlan_core.c | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/drivers/net/vxlan/vxlan_core.c b/drivers/net/vxlan/vxlan_core.c index a5c55e7e4d795f..e957aa12a8a44a 100644 --- a/drivers/net/vxlan/vxlan_core.c +++ b/drivers/net/vxlan/vxlan_core.c @@ -2349,7 +2349,7 @@ void vxlan_xmit_one(struct sk_buff *skb, struct net_device *dev, int addr_family; __u8 tos, ttl; int ifindex; - int err; + int err = 0; u32 flags = vxlan->cfg.flags; bool use_cache; bool udp_sum = false; @@ -2454,12 +2454,18 @@ void vxlan_xmit_one(struct sk_buff *skb, struct net_device *dev, rcu_read_lock(); if (addr_family == AF_INET) { - struct vxlan_sock *sock4 = rcu_dereference(vxlan->vn4_sock); + struct vxlan_sock *sock4; u16 ipcb_flags = 0; struct rtable *rt; __be16 df = 0; __be32 saddr; + sock4 = rcu_dereference(vxlan->vn4_sock); + if (unlikely(!sock4)) { + reason = SKB_DROP_REASON_DEV_READY; + goto tx_error; + } + if (!ifindex) ifindex = sock4->sock->sk->sk_bound_dev_if; @@ -2534,10 +2540,16 @@ void vxlan_xmit_one(struct sk_buff *skb, struct net_device *dev, ipcb_flags); #if IS_ENABLED(CONFIG_IPV6) } else { - struct vxlan_sock *sock6 = rcu_dereference(vxlan->vn6_sock); + struct vxlan_sock *sock6; struct in6_addr saddr; u16 ip6cb_flags = 0; + sock6 = rcu_dereference(vxlan->vn6_sock); + if (unlikely(!sock6)) { + reason = SKB_DROP_REASON_DEV_READY; + goto tx_error; + } + if (!ifindex) ifindex = sock6->sock->sk->sk_bound_dev_if; From 3a11d8ae00c9e4cda68f331fafd3a79549fea626 Mon Sep 17 00:00:00 2001 From: Alexey Kodanev Date: Wed, 26 Nov 2025 10:43:27 +0000 Subject: [PATCH 0850/3902] net: stmmac: fix rx limit check in stmmac_rx_zc() [ Upstream commit 8048168df56e225c94e50b04cb7b0514135d7a1c ] The extra "count >= limit" check in stmmac_rx_zc() is redundant and has no effect because the value of "count" doesn't change after the while condition at this point. However, it can change after "read_again:" label: while (count < limit) { ... if (count >= limit) break; read_again: ... /* XSK pool expects RX frame 1:1 mapped to XSK buffer */ if (likely(status & rx_not_ls)) { xsk_buff_free(buf->xdp); buf->xdp = NULL; dirty++; count++; goto read_again; } ... This patch addresses the same issue previously resolved in stmmac_rx() by commit fa02de9e7588 ("net: stmmac: fix rx budget limit check"). The fix is the same: move the check after the label to ensure that it bounds the goto loop. Fixes: bba2556efad6 ("net: stmmac: Enable RX via AF_XDP zero-copy") Signed-off-by: Alexey Kodanev Reviewed-by: Russell King (Oracle) Link: https://patch.msgid.link/20251126104327.175590-1-aleksei.kodanev@bell-sw.com Signed-off-by: Jakub Kicinski Signed-off-by: Sasha Levin --- drivers/net/ethernet/stmicro/stmmac/stmmac_main.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c b/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c index 7b90ecd3a55e60..86e912471dead2 100644 --- a/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c +++ b/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c @@ -5258,10 +5258,10 @@ static int stmmac_rx_zc(struct stmmac_priv *priv, int limit, u32 queue) len = 0; } +read_again: if (count >= limit) break; -read_again: buf1_len = 0; entry = next_entry; buf = &rx_q->buf_pool[entry]; From 44309fda9c60d792cdd4da96d35c4af0895dfbe7 Mon Sep 17 00:00:00 2001 From: Haotian Zhang Date: Mon, 24 Nov 2025 00:35:51 +0800 Subject: [PATCH 0851/3902] mtd: rawnand: renesas: Handle devm_pm_runtime_enable() errors [ Upstream commit a3623e1ae1ed6be4d49b2ccb9996a9d2b65c1828 ] devm_pm_runtime_enable() can fail due to memory allocation failures. The current code ignores its return value and proceeds with pm_runtime_resume_and_get(), which may operate on incorrectly initialized runtime PM state. Check the return value of devm_pm_runtime_enable() and return the error code if it fails. Fixes: 6a2277a0ebe7 ("mtd: rawnand: renesas: Use runtime PM instead of the raw clock API") Signed-off-by: Haotian Zhang Reviewed-by: Geert Uytterhoeven Signed-off-by: Miquel Raynal Signed-off-by: Sasha Levin --- drivers/mtd/nand/raw/renesas-nand-controller.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/drivers/mtd/nand/raw/renesas-nand-controller.c b/drivers/mtd/nand/raw/renesas-nand-controller.c index ac8c1b80d7be96..201dd62b99905a 100644 --- a/drivers/mtd/nand/raw/renesas-nand-controller.c +++ b/drivers/mtd/nand/raw/renesas-nand-controller.c @@ -1336,7 +1336,10 @@ static int rnandc_probe(struct platform_device *pdev) if (IS_ERR(rnandc->regs)) return PTR_ERR(rnandc->regs); - devm_pm_runtime_enable(&pdev->dev); + ret = devm_pm_runtime_enable(&pdev->dev); + if (ret) + return ret; + ret = pm_runtime_resume_and_get(&pdev->dev); if (ret < 0) return ret; From 81841da1f30f66a850cc8796d99ba330aad9d696 Mon Sep 17 00:00:00 2001 From: Tianchu Chen Date: Fri, 28 Nov 2025 16:06:30 +0800 Subject: [PATCH 0852/3902] spi: ch341: fix out-of-bounds memory access in ch341_transfer_one [ Upstream commit 545d1287e40a55242f6ab68bcc1ba3b74088b1bc ] Discovered by Atuin - Automated Vulnerability Discovery Engine. The 'len' variable is calculated as 'min(32, trans->len + 1)', which includes the 1-byte command header. When copying data from 'trans->tx_buf' to 'ch341->tx_buf + 1', using 'len' as the length is incorrect because: 1. It causes an out-of-bounds read from 'trans->tx_buf' (which has size 'trans->len', i.e., 'len - 1' in this context). 2. It can cause an out-of-bounds write to 'ch341->tx_buf' if 'len' is CH341_PACKET_LENGTH (32). Writing 32 bytes to ch341->tx_buf + 1 overflows the buffer. Fix this by copying 'len - 1' bytes. Fixes: 8846739f52af ("spi: add ch341a usb2spi driver") Signed-off-by: Tianchu Chen Link: https://patch.msgid.link/20251128160630.0f922c45ec6084a46fb57099@linux.dev Signed-off-by: Mark Brown Signed-off-by: Sasha Levin --- drivers/spi/spi-ch341.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/spi/spi-ch341.c b/drivers/spi/spi-ch341.c index 46bc208f2d0509..79d2f9ab4ef037 100644 --- a/drivers/spi/spi-ch341.c +++ b/drivers/spi/spi-ch341.c @@ -78,7 +78,7 @@ static int ch341_transfer_one(struct spi_controller *host, ch341->tx_buf[0] = CH341A_CMD_SPI_STREAM; - memcpy(ch341->tx_buf + 1, trans->tx_buf, len); + memcpy(ch341->tx_buf + 1, trans->tx_buf, len - 1); ret = usb_bulk_msg(ch341->udev, ch341->write_pipe, ch341->tx_buf, len, NULL, CH341_DEFAULT_TIMEOUT); From 3e2fc1e57a5361633a4bf4222640c6bfe41ff8ea Mon Sep 17 00:00:00 2001 From: Dev Jain Date: Wed, 12 Nov 2025 11:57:15 +0530 Subject: [PATCH 0853/3902] arm64/pageattr: Propagate return value from __change_memory_common [ Upstream commit e5efd56fa157d2e7d789949d1d64eccbac18a897 ] The rodata=on security measure requires that any code path which does vmalloc -> set_memory_ro/set_memory_rox must protect the linear map alias too. Therefore, if such a call fails, we must abort set_memory_* and caller must take appropriate action; currently we are suppressing the error, and there is a real chance of such an error arising post commit a166563e7ec3 ("arm64: mm: support large block mapping when rodata=full"). Therefore, propagate any error to the caller. Fixes: a166563e7ec3 ("arm64: mm: support large block mapping when rodata=full") Signed-off-by: Dev Jain Reviewed-by: Ryan Roberts Signed-off-by: Catalin Marinas Signed-off-by: Sasha Levin --- arch/arm64/mm/pageattr.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/arch/arm64/mm/pageattr.c b/arch/arm64/mm/pageattr.c index 5135f2d66958d9..b4ea86cd3a7199 100644 --- a/arch/arm64/mm/pageattr.c +++ b/arch/arm64/mm/pageattr.c @@ -148,6 +148,7 @@ static int change_memory_common(unsigned long addr, int numpages, unsigned long size = PAGE_SIZE * numpages; unsigned long end = start + size; struct vm_struct *area; + int ret; int i; if (!PAGE_ALIGNED(addr)) { @@ -185,8 +186,10 @@ static int change_memory_common(unsigned long addr, int numpages, if (rodata_full && (pgprot_val(set_mask) == PTE_RDONLY || pgprot_val(clear_mask) == PTE_RDONLY)) { for (i = 0; i < area->nr_pages; i++) { - __change_memory_common((u64)page_address(area->pages[i]), + ret = __change_memory_common((u64)page_address(area->pages[i]), PAGE_SIZE, set_mask, clear_mask); + if (ret) + return ret; } } From addbb8ddb443f68ccb97e5889c7931033de909c6 Mon Sep 17 00:00:00 2001 From: Alex Williamson Date: Mon, 24 Nov 2025 15:36:22 -0700 Subject: [PATCH 0854/3902] vfio/pci: Use RCU for error/request triggers to avoid circular locking [ Upstream commit 98693e0897f754e3f51ce6626ed5f785f625ba2b ] Thanks to a device generating an ACS violation during bus reset, lockdep reported the following circular locking issue: CPU0: SET_IRQS (MSI/X): holds igate, acquires memory_lock CPU1: HOT_RESET: holds memory_lock, acquires pci_bus_sem CPU2: AER: holds pci_bus_sem, acquires igate This results in a potential 3-way deadlock. Remove the pci_bus_sem->igate leg of the triangle by using RCU to peek at the eventfd rather than locking it with igate. Fixes: 3be3a074cf5b ("vfio-pci: Don't use device_lock around AER interrupt setup") Signed-off-by: Alex Williamson Reviewed-by: Jason Gunthorpe Link: https://lore.kernel.org/r/20251124223623.2770706-1-alex@shazbot.org Signed-off-by: Alex Williamson Signed-off-by: Sasha Levin --- drivers/vfio/pci/vfio_pci_core.c | 68 ++++++++++++++++++++++--------- drivers/vfio/pci/vfio_pci_intrs.c | 52 ++++++++++++++--------- drivers/vfio/pci/vfio_pci_priv.h | 4 ++ include/linux/vfio_pci_core.h | 10 ++++- 4 files changed, 93 insertions(+), 41 deletions(-) diff --git a/drivers/vfio/pci/vfio_pci_core.c b/drivers/vfio/pci/vfio_pci_core.c index 7dcf5439dedc9d..5efe7535f41ed8 100644 --- a/drivers/vfio/pci/vfio_pci_core.c +++ b/drivers/vfio/pci/vfio_pci_core.c @@ -41,6 +41,40 @@ static bool nointxmask; static bool disable_vga; static bool disable_idle_d3; +static void vfio_pci_eventfd_rcu_free(struct rcu_head *rcu) +{ + struct vfio_pci_eventfd *eventfd = + container_of(rcu, struct vfio_pci_eventfd, rcu); + + eventfd_ctx_put(eventfd->ctx); + kfree(eventfd); +} + +int vfio_pci_eventfd_replace_locked(struct vfio_pci_core_device *vdev, + struct vfio_pci_eventfd __rcu **peventfd, + struct eventfd_ctx *ctx) +{ + struct vfio_pci_eventfd *new = NULL; + struct vfio_pci_eventfd *old; + + lockdep_assert_held(&vdev->igate); + + if (ctx) { + new = kzalloc(sizeof(*new), GFP_KERNEL_ACCOUNT); + if (!new) + return -ENOMEM; + + new->ctx = ctx; + } + + old = rcu_replace_pointer(*peventfd, new, + lockdep_is_held(&vdev->igate)); + if (old) + call_rcu(&old->rcu, vfio_pci_eventfd_rcu_free); + + return 0; +} + /* List of PF's that vfio_pci_core_sriov_configure() has been called on */ static DEFINE_MUTEX(vfio_pci_sriov_pfs_mutex); static LIST_HEAD(vfio_pci_sriov_pfs); @@ -696,14 +730,8 @@ void vfio_pci_core_close_device(struct vfio_device *core_vdev) vfio_pci_core_disable(vdev); mutex_lock(&vdev->igate); - if (vdev->err_trigger) { - eventfd_ctx_put(vdev->err_trigger); - vdev->err_trigger = NULL; - } - if (vdev->req_trigger) { - eventfd_ctx_put(vdev->req_trigger); - vdev->req_trigger = NULL; - } + vfio_pci_eventfd_replace_locked(vdev, &vdev->err_trigger, NULL); + vfio_pci_eventfd_replace_locked(vdev, &vdev->req_trigger, NULL); mutex_unlock(&vdev->igate); } EXPORT_SYMBOL_GPL(vfio_pci_core_close_device); @@ -1800,21 +1828,21 @@ void vfio_pci_core_request(struct vfio_device *core_vdev, unsigned int count) struct vfio_pci_core_device *vdev = container_of(core_vdev, struct vfio_pci_core_device, vdev); struct pci_dev *pdev = vdev->pdev; + struct vfio_pci_eventfd *eventfd; - mutex_lock(&vdev->igate); - - if (vdev->req_trigger) { + rcu_read_lock(); + eventfd = rcu_dereference(vdev->req_trigger); + if (eventfd) { if (!(count % 10)) pci_notice_ratelimited(pdev, "Relaying device request to user (#%u)\n", count); - eventfd_signal(vdev->req_trigger); + eventfd_signal(eventfd->ctx); } else if (count == 0) { pci_warn(pdev, "No device request channel registered, blocked until released by user\n"); } - - mutex_unlock(&vdev->igate); + rcu_read_unlock(); } EXPORT_SYMBOL_GPL(vfio_pci_core_request); @@ -2227,13 +2255,13 @@ pci_ers_result_t vfio_pci_core_aer_err_detected(struct pci_dev *pdev, pci_channel_state_t state) { struct vfio_pci_core_device *vdev = dev_get_drvdata(&pdev->dev); + struct vfio_pci_eventfd *eventfd; - mutex_lock(&vdev->igate); - - if (vdev->err_trigger) - eventfd_signal(vdev->err_trigger); - - mutex_unlock(&vdev->igate); + rcu_read_lock(); + eventfd = rcu_dereference(vdev->err_trigger); + if (eventfd) + eventfd_signal(eventfd->ctx); + rcu_read_unlock(); return PCI_ERS_RESULT_CAN_RECOVER; } diff --git a/drivers/vfio/pci/vfio_pci_intrs.c b/drivers/vfio/pci/vfio_pci_intrs.c index 30d3e921cb0deb..c76e753b3cecd6 100644 --- a/drivers/vfio/pci/vfio_pci_intrs.c +++ b/drivers/vfio/pci/vfio_pci_intrs.c @@ -731,21 +731,27 @@ static int vfio_pci_set_msi_trigger(struct vfio_pci_core_device *vdev, return 0; } -static int vfio_pci_set_ctx_trigger_single(struct eventfd_ctx **ctx, +static int vfio_pci_set_ctx_trigger_single(struct vfio_pci_core_device *vdev, + struct vfio_pci_eventfd __rcu **peventfd, unsigned int count, uint32_t flags, void *data) { /* DATA_NONE/DATA_BOOL enables loopback testing */ if (flags & VFIO_IRQ_SET_DATA_NONE) { - if (*ctx) { - if (count) { - eventfd_signal(*ctx); - } else { - eventfd_ctx_put(*ctx); - *ctx = NULL; - } + struct vfio_pci_eventfd *eventfd; + + eventfd = rcu_dereference_protected(*peventfd, + lockdep_is_held(&vdev->igate)); + + if (!eventfd) + return -EINVAL; + + if (count) { + eventfd_signal(eventfd->ctx); return 0; } + + return vfio_pci_eventfd_replace_locked(vdev, peventfd, NULL); } else if (flags & VFIO_IRQ_SET_DATA_BOOL) { uint8_t trigger; @@ -753,8 +759,15 @@ static int vfio_pci_set_ctx_trigger_single(struct eventfd_ctx **ctx, return -EINVAL; trigger = *(uint8_t *)data; - if (trigger && *ctx) - eventfd_signal(*ctx); + + if (trigger) { + struct vfio_pci_eventfd *eventfd = + rcu_dereference_protected(*peventfd, + lockdep_is_held(&vdev->igate)); + + if (eventfd) + eventfd_signal(eventfd->ctx); + } return 0; } else if (flags & VFIO_IRQ_SET_DATA_EVENTFD) { @@ -765,22 +778,23 @@ static int vfio_pci_set_ctx_trigger_single(struct eventfd_ctx **ctx, fd = *(int32_t *)data; if (fd == -1) { - if (*ctx) - eventfd_ctx_put(*ctx); - *ctx = NULL; + return vfio_pci_eventfd_replace_locked(vdev, + peventfd, NULL); } else if (fd >= 0) { struct eventfd_ctx *efdctx; + int ret; efdctx = eventfd_ctx_fdget(fd); if (IS_ERR(efdctx)) return PTR_ERR(efdctx); - if (*ctx) - eventfd_ctx_put(*ctx); + ret = vfio_pci_eventfd_replace_locked(vdev, + peventfd, efdctx); + if (ret) + eventfd_ctx_put(efdctx); - *ctx = efdctx; + return ret; } - return 0; } return -EINVAL; @@ -793,7 +807,7 @@ static int vfio_pci_set_err_trigger(struct vfio_pci_core_device *vdev, if (index != VFIO_PCI_ERR_IRQ_INDEX || start != 0 || count > 1) return -EINVAL; - return vfio_pci_set_ctx_trigger_single(&vdev->err_trigger, + return vfio_pci_set_ctx_trigger_single(vdev, &vdev->err_trigger, count, flags, data); } @@ -804,7 +818,7 @@ static int vfio_pci_set_req_trigger(struct vfio_pci_core_device *vdev, if (index != VFIO_PCI_REQ_IRQ_INDEX || start != 0 || count > 1) return -EINVAL; - return vfio_pci_set_ctx_trigger_single(&vdev->req_trigger, + return vfio_pci_set_ctx_trigger_single(vdev, &vdev->req_trigger, count, flags, data); } diff --git a/drivers/vfio/pci/vfio_pci_priv.h b/drivers/vfio/pci/vfio_pci_priv.h index a9972eacb2936b..97d992c063229d 100644 --- a/drivers/vfio/pci/vfio_pci_priv.h +++ b/drivers/vfio/pci/vfio_pci_priv.h @@ -26,6 +26,10 @@ struct vfio_pci_ioeventfd { bool vfio_pci_intx_mask(struct vfio_pci_core_device *vdev); void vfio_pci_intx_unmask(struct vfio_pci_core_device *vdev); +int vfio_pci_eventfd_replace_locked(struct vfio_pci_core_device *vdev, + struct vfio_pci_eventfd __rcu **peventfd, + struct eventfd_ctx *ctx); + int vfio_pci_set_irqs_ioctl(struct vfio_pci_core_device *vdev, uint32_t flags, unsigned index, unsigned start, unsigned count, void *data); diff --git a/include/linux/vfio_pci_core.h b/include/linux/vfio_pci_core.h index f541044e42a2ad..f5c93787f8e0b5 100644 --- a/include/linux/vfio_pci_core.h +++ b/include/linux/vfio_pci_core.h @@ -12,6 +12,7 @@ #include #include #include +#include #include #include #include @@ -27,6 +28,11 @@ struct vfio_pci_core_device; struct vfio_pci_region; +struct vfio_pci_eventfd { + struct eventfd_ctx *ctx; + struct rcu_head rcu; +}; + struct vfio_pci_regops { ssize_t (*rw)(struct vfio_pci_core_device *vdev, char __user *buf, size_t count, loff_t *ppos, bool iswrite); @@ -83,8 +89,8 @@ struct vfio_pci_core_device { struct pci_saved_state *pci_saved_state; struct pci_saved_state *pm_save; int ioeventfds_nr; - struct eventfd_ctx *err_trigger; - struct eventfd_ctx *req_trigger; + struct vfio_pci_eventfd __rcu *err_trigger; + struct vfio_pci_eventfd __rcu *req_trigger; struct eventfd_ctx *pm_wake_eventfd_ctx; struct list_head dummy_resources_list; struct mutex ioeventfds_lock; From cadb28f8b3fd6908e3051e86158c65c3a8e1c907 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micka=C3=ABl=20Sala=C3=BCn?= Date: Fri, 28 Nov 2025 18:21:56 +0100 Subject: [PATCH 0855/3902] landlock: Fix handling of disconnected directories MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [ Upstream commit 49c9e09d961025b22e61ef9ad56aa1c21b6ce2f1 ] Disconnected files or directories can appear when they are visible and opened from a bind mount, but have been renamed or moved from the source of the bind mount in a way that makes them inaccessible from the mount point (i.e. out of scope). Previously, access rights tied to files or directories opened through a disconnected directory were collected by walking the related hierarchy down to the root of the filesystem, without taking into account the mount point because it couldn't be found. This could lead to inconsistent access results, potential access right widening, and hard-to-debug renames, especially since such paths cannot be printed. For a sandboxed task to create a disconnected directory, it needs to have write access (i.e. FS_MAKE_REG, FS_REMOVE_FILE, and FS_REFER) to the underlying source of the bind mount, and read access to the related mount point. Because a sandboxed task cannot acquire more access rights than those defined by its Landlock domain, this could lead to inconsistent access rights due to missing permissions that should be inherited from the mount point hierarchy, while inheriting permissions from the filesystem hierarchy hidden by this mount point instead. Landlock now handles files and directories opened from disconnected directories by taking into account the filesystem hierarchy when the mount point is not found in the hierarchy walk, and also always taking into account the mount point from which these disconnected directories were opened. This ensures that a rename is not allowed if it would widen access rights [1]. The rationale is that, even if disconnected hierarchies might not be visible or accessible to a sandboxed task, relying on the collected access rights from them improves the guarantee that access rights will not be widened during a rename because of the access right comparison between the source and the destination (see LANDLOCK_ACCESS_FS_REFER). It may look like this would grant more access on disconnected files and directories, but the security policies are always enforced for all the evaluated hierarchies. This new behavior should be less surprising to users and safer from an access control perspective. Remove a wrong WARN_ON_ONCE() canary in collect_domain_accesses() and fix the related comment. Because opened files have their access rights stored in the related file security properties, there is no impact for disconnected or unlinked files. Cc: Christian Brauner Cc: Günther Noack Cc: Song Liu Reported-by: Tingmao Wang Closes: https://lore.kernel.org/r/027d5190-b37a-40a8-84e9-4ccbc352bcdf@maowtm.org Closes: https://lore.kernel.org/r/09b24128f86973a6022e6aa8338945fcfb9a33e4.1749925391.git.m@maowtm.org Fixes: b91c3e4ea756 ("landlock: Add support for file reparenting with LANDLOCK_ACCESS_FS_REFER") Fixes: cb2c7d1a1776 ("landlock: Support filesystem access-control") Link: https://lore.kernel.org/r/b0f46246-f2c5-42ca-93ce-0d629702a987@maowtm.org [1] Reviewed-by: Tingmao Wang Link: https://lore.kernel.org/r/20251128172200.760753-2-mic@digikod.net Signed-off-by: Mickaël Salaün Signed-off-by: Sasha Levin --- security/landlock/errata/abi-1.h | 16 +++++++++++++ security/landlock/fs.c | 40 ++++++++++++++++++++++---------- 2 files changed, 44 insertions(+), 12 deletions(-) create mode 100644 security/landlock/errata/abi-1.h diff --git a/security/landlock/errata/abi-1.h b/security/landlock/errata/abi-1.h new file mode 100644 index 00000000000000..e8a2bff2e5b6a8 --- /dev/null +++ b/security/landlock/errata/abi-1.h @@ -0,0 +1,16 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ + +/** + * DOC: erratum_3 + * + * Erratum 3: Disconnected directory handling + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * + * This fix addresses an issue with disconnected directories that occur when a + * directory is moved outside the scope of a bind mount. The change ensures + * that evaluated access rights include both those from the disconnected file + * hierarchy down to its filesystem root and those from the related mount point + * hierarchy. This prevents access right widening through rename or link + * actions. + */ +LANDLOCK_ERRATUM(3) diff --git a/security/landlock/fs.c b/security/landlock/fs.c index d9c12b993fa7dd..a2ed0e76938aa1 100644 --- a/security/landlock/fs.c +++ b/security/landlock/fs.c @@ -909,21 +909,31 @@ static bool is_access_to_paths_allowed( break; } } + if (unlikely(IS_ROOT(walker_path.dentry))) { - /* - * Stops at disconnected root directories. Only allows - * access to internal filesystems (e.g. nsfs, which is - * reachable through /proc//ns/). - */ - if (walker_path.mnt->mnt_flags & MNT_INTERNAL) { + if (likely(walker_path.mnt->mnt_flags & MNT_INTERNAL)) { + /* + * Stops and allows access when reaching disconnected root + * directories that are part of internal filesystems (e.g. nsfs, + * which is reachable through /proc//ns/). + */ allowed_parent1 = true; allowed_parent2 = true; + break; } - break; + + /* + * We reached a disconnected root directory from a bind mount. + * Let's continue the walk with the mount point we missed. + */ + dput(walker_path.dentry); + walker_path.dentry = walker_path.mnt->mnt_root; + dget(walker_path.dentry); + } else { + parent_dentry = dget_parent(walker_path.dentry); + dput(walker_path.dentry); + walker_path.dentry = parent_dentry; } - parent_dentry = dget_parent(walker_path.dentry); - dput(walker_path.dentry); - walker_path.dentry = parent_dentry; } path_put(&walker_path); @@ -1021,6 +1031,9 @@ static access_mask_t maybe_remove(const struct dentry *const dentry) * file. While walking from @dir to @mnt_root, we record all the domain's * allowed accesses in @layer_masks_dom. * + * Because of disconnected directories, this walk may not reach @mnt_dir. In + * this case, the walk will continue to @mnt_dir after this call. + * * This is similar to is_access_to_paths_allowed() but much simpler because it * only handles walking on the same mount point and only checks one set of * accesses. @@ -1062,8 +1075,11 @@ static bool collect_domain_accesses( break; } - /* We should not reach a root other than @mnt_root. */ - if (dir == mnt_root || WARN_ON_ONCE(IS_ROOT(dir))) + /* + * Stops at the mount point or the filesystem root for a disconnected + * directory. + */ + if (dir == mnt_root || unlikely(IS_ROOT(dir))) break; parent_dentry = dget_parent(dir); From f61d6d4e1508c94d3d35428fb6229453d6926e8e Mon Sep 17 00:00:00 2001 From: Robert Marko Date: Thu, 27 Nov 2025 12:44:35 +0100 Subject: [PATCH 0856/3902] net: phy: aquantia: check for NVMEM deferral [ Upstream commit a6c121a2432eee2c4ebceb1483ccd4a50a52983d ] Currently, if NVMEM provider is probed later than Aquantia, loading the firmware will fail with -EINVAL. To fix this, simply check for -EPROBE_DEFER when NVMEM is attempted and return it. Fixes: e93984ebc1c8 ("net: phy: aquantia: add firmware load support") Signed-off-by: Robert Marko Reviewed-by: Russell King (Oracle) Link: https://patch.msgid.link/20251127114514.460924-1-robimarko@gmail.com Signed-off-by: Jakub Kicinski Signed-off-by: Sasha Levin --- drivers/net/phy/aquantia/aquantia_firmware.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/net/phy/aquantia/aquantia_firmware.c b/drivers/net/phy/aquantia/aquantia_firmware.c index bbbcc9736b00e1..569256152689f8 100644 --- a/drivers/net/phy/aquantia/aquantia_firmware.c +++ b/drivers/net/phy/aquantia/aquantia_firmware.c @@ -369,7 +369,7 @@ int aqr_firmware_load(struct phy_device *phydev) * assume that, and load a new image. */ ret = aqr_firmware_load_nvmem(phydev); - if (!ret) + if (ret == -EPROBE_DEFER || !ret) return ret; ret = aqr_firmware_load_fs(phydev); From 4cc8dd1aafacbd256a67feceff9b2daad5a95cc7 Mon Sep 17 00:00:00 2001 From: Hangbin Liu Date: Thu, 27 Nov 2025 14:33:10 +0000 Subject: [PATCH 0857/3902] selftests: bonding: add delay before each xvlan_over_bond connectivity check [ Upstream commit 2c28ee720ad14f58eb88a97ec3efe7c5c315ea5d ] Jakub reported increased flakiness in bond_macvlan_ipvlan.sh on regular kernel, while the tests consistently pass on a debug kernel. This suggests a timing-sensitive issue. To mitigate this, introduce a short sleep before each xvlan_over_bond connectivity check. The delay helps ensure neighbor and route cache have fully converged before verifying connectivity. The sleep interval is kept minimal since check_connection() is invoked nearly 100 times during the test. Fixes: 246af950b940 ("selftests: bonding: add macvlan over bond testing") Reported-by: Jakub Kicinski Closes: https://lore.kernel.org/netdev/20251114082014.750edfad@kernel.org Signed-off-by: Hangbin Liu Link: https://patch.msgid.link/20251127143310.47740-1-liuhangbin@gmail.com Signed-off-by: Jakub Kicinski Signed-off-by: Sasha Levin --- .../testing/selftests/drivers/net/bonding/bond_macvlan_ipvlan.sh | 1 + 1 file changed, 1 insertion(+) diff --git a/tools/testing/selftests/drivers/net/bonding/bond_macvlan_ipvlan.sh b/tools/testing/selftests/drivers/net/bonding/bond_macvlan_ipvlan.sh index c4711272fe45d5..559f300f965aa2 100755 --- a/tools/testing/selftests/drivers/net/bonding/bond_macvlan_ipvlan.sh +++ b/tools/testing/selftests/drivers/net/bonding/bond_macvlan_ipvlan.sh @@ -30,6 +30,7 @@ check_connection() local message=${3} RET=0 + sleep 0.25 ip netns exec ${ns} ping ${target} -c 4 -i 0.1 &>/dev/null check_err $? "ping failed" log_test "${bond_mode}/${xvlan_type}_${xvlan_mode}: ${message}" From 760bc6ceda8e2c273c0e2018ad2595967c3dd308 Mon Sep 17 00:00:00 2001 From: Breno Leitao Date: Thu, 27 Nov 2025 07:30:15 -0800 Subject: [PATCH 0858/3902] net: netpoll: initialize work queue before error checks [ Upstream commit e5235eb6cfe02a51256013a78f7b28779a7740d5 ] Prevent a kernel warning when netconsole setup fails on devices with IFF_DISABLE_NETPOLL flag. The warning (at kernel/workqueue.c:4242 in __flush_work) occurs because the cleanup path tries to cancel an uninitialized work queue. When __netpoll_setup() encounters a device with IFF_DISABLE_NETPOLL, it fails early and calls skb_pool_flush() for cleanup. This function calls cancel_work_sync(&np->refill_wq), but refill_wq hasn't been initialized yet, triggering the warning. Move INIT_WORK() to the beginning of __netpoll_setup(), ensuring the work queue is properly initialized before any potential failure points. This allows the cleanup path to safely cancel the work queue regardless of where the setup fails. Fixes: 248f6571fd4c5 ("netpoll: Optimize skb refilling on critical path") Signed-off-by: Breno Leitao Link: https://patch.msgid.link/20251127-netpoll_fix_init_work-v1-1-65c07806d736@debian.org Signed-off-by: Jakub Kicinski Signed-off-by: Sasha Levin --- net/core/netpoll.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/net/core/netpoll.c b/net/core/netpoll.c index 331764845e8fa5..09f72f10813cc6 100644 --- a/net/core/netpoll.c +++ b/net/core/netpoll.c @@ -554,6 +554,7 @@ int __netpoll_setup(struct netpoll *np, struct net_device *ndev) int err; skb_queue_head_init(&np->skb_pool); + INIT_WORK(&np->refill_wq, refill_skbs_work_handler); if (ndev->priv_flags & IFF_DISABLE_NETPOLL) { np_err(np, "%s doesn't support polling, aborting\n", @@ -591,7 +592,6 @@ int __netpoll_setup(struct netpoll *np, struct net_device *ndev) /* fill up the skb queue */ refill_skbs(np); - INIT_WORK(&np->refill_wq, refill_skbs_work_handler); /* last thing to do is link it to the net device structure */ rcu_assign_pointer(ndev->npinfo, npinfo); From 03ee076352cf625d7785fc43fce9caf806e7c665 Mon Sep 17 00:00:00 2001 From: Ivan Stepchenko Date: Fri, 21 Nov 2025 14:54:46 +0300 Subject: [PATCH 0859/3902] mtd: lpddr_cmds: fix signed shifts in lpddr_cmds [ Upstream commit c909fec69f84b39e63876c69b9df2c178c6b76ba ] There are several places where a value of type 'int' is shifted by lpddr->chipshift. lpddr->chipshift is derived from QINFO geometry and might reach 31 when QINFO reports a 2 GiB size - the maximum supported by LPDDR(1) compliant chips. This may cause unexpected sign-extensions when casting the integer value to the type of 'unsigned long'. Use '1UL << lpddr->chipshift' and cast 'j' to unsigned long before shifting so the computation is performed at the destination width. Found by Linux Verification Center (linuxtesting.org) with SVACE. Fixes: c68264711ca6 ("[MTD] LPDDR Command set driver") Signed-off-by: Ivan Stepchenko Signed-off-by: Miquel Raynal Signed-off-by: Sasha Levin --- drivers/mtd/lpddr/lpddr_cmds.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/drivers/mtd/lpddr/lpddr_cmds.c b/drivers/mtd/lpddr/lpddr_cmds.c index 290fd0119e9841..cd37d58abacb75 100644 --- a/drivers/mtd/lpddr/lpddr_cmds.c +++ b/drivers/mtd/lpddr/lpddr_cmds.c @@ -79,7 +79,7 @@ struct mtd_info *lpddr_cmdset(struct map_info *map) mutex_init(&shared[i].lock); for (j = 0; j < lpddr->qinfo->HWPartsNum; j++) { *chip = lpddr->chips[i]; - chip->start += j << lpddr->chipshift; + chip->start += (unsigned long)j << lpddr->chipshift; chip->oldstate = chip->state = FL_READY; chip->priv = &shared[i]; /* those should be reset too since @@ -559,7 +559,7 @@ static int lpddr_point(struct mtd_info *mtd, loff_t adr, size_t len, break; if ((len + ofs - 1) >> lpddr->chipshift) - thislen = (1<chipshift) - ofs; + thislen = (1UL << lpddr->chipshift) - ofs; else thislen = len; /* get the chip */ @@ -575,7 +575,7 @@ static int lpddr_point(struct mtd_info *mtd, loff_t adr, size_t len, len -= thislen; ofs = 0; - last_end += 1 << lpddr->chipshift; + last_end += 1UL << lpddr->chipshift; chipnum++; chip = &lpddr->chips[chipnum]; } @@ -601,7 +601,7 @@ static int lpddr_unpoint (struct mtd_info *mtd, loff_t adr, size_t len) break; if ((len + ofs - 1) >> lpddr->chipshift) - thislen = (1<chipshift) - ofs; + thislen = (1UL << lpddr->chipshift) - ofs; else thislen = len; From 43b6c312b8ff013258e21c2b4eca4887171ebbc3 Mon Sep 17 00:00:00 2001 From: Kumar Kartikeya Dwivedi Date: Fri, 28 Nov 2025 23:27:57 +0000 Subject: [PATCH 0860/3902] rqspinlock: Enclose lock/unlock within lock entry acquisitions [ Upstream commit beb7021a6003d9c6a463fffca0d6311efb8e0e66 ] Ritesh reported that timeouts occurred frequently for rqspinlock despite reentrancy on the same lock on the same CPU in [0]. This patch closes one of the races leading to this behavior, and reduces the frequency of timeouts. We currently have a tiny window between the fast-path cmpxchg and the grabbing of the lock entry where an NMI could land, attempt the same lock that was just acquired, and end up timing out. This is not ideal. Instead, move the lock entry acquisition from the fast path to before the cmpxchg, and remove the grabbing of the lock entry in the slow path, assuming it was already taken by the fast path. The TAS fallback is invoked directly without being preceded by the typical fast path, therefore we must continue to grab the deadlock detection entry in that case. Case on lock leading to missed AA: cmpxchg lock A ... rqspinlock acquisition of A ... timeout grab_held_lock_entry(A) There is a similar case when unlocking the lock. If the NMI lands between the WRITE_ONCE and smp_store_release, it is possible that we end up in a situation where the NMI fails to diagnose the AA condition, leading to a timeout. Case on unlock leading to missed AA: WRITE_ONCE(rqh->locks[rqh->cnt - 1], NULL) ... rqspinlock acquisition of A ... timeout smp_store_release(A->locked, 0) The patch changes the order on unlock to smp_store_release() succeeded by WRITE_ONCE() of NULL. This avoids the missed AA detection described above, but may lead to a false positive if the NMI lands between these two statements, which is acceptable (and preferred over a timeout). The original intention of the reverse order on unlock was to prevent the following possible misdiagnosis of an ABBA scenario: grab entry A lock A grab entry B lock B unlock B smp_store_release(B->locked, 0) grab entry B lock B grab entry A lock A ! WRITE_ONCE(rqh->locks[rqh->cnt - 1], NULL) If the store release were is after the WRITE_ONCE, the other CPU would not observe B in the table of the CPU unlocking the lock B. However, since the threads are obviously participating in an ABBA deadlock, it is no longer appealing to use the order above since it may lead to a 250 ms timeout due to missed AA detection. [0]: https://lore.kernel.org/bpf/CAH6OuBTjG+N=+GGwcpOUbeDN563oz4iVcU3rbse68egp9wj9_A@mail.gmail.com Fixes: 0d80e7f951be ("rqspinlock: Choose trylock fallback for NMI waiters") Reported-by: Ritesh Oedayrajsingh Varma Signed-off-by: Kumar Kartikeya Dwivedi Link: https://lore.kernel.org/r/20251128232802.1031906-2-memxor@gmail.com Signed-off-by: Alexei Starovoitov Signed-off-by: Sasha Levin --- include/asm-generic/rqspinlock.h | 60 +++++++++++++++++--------------- kernel/bpf/rqspinlock.c | 15 ++++---- 2 files changed, 38 insertions(+), 37 deletions(-) diff --git a/include/asm-generic/rqspinlock.h b/include/asm-generic/rqspinlock.h index 6d4244d643df32..0f2dcbbfee2f0a 100644 --- a/include/asm-generic/rqspinlock.h +++ b/include/asm-generic/rqspinlock.h @@ -129,8 +129,8 @@ static __always_inline void release_held_lock_entry(void) * for lock B * release_held_lock_entry * - * try_cmpxchg_acquire for lock A * grab_held_lock_entry + * try_cmpxchg_acquire for lock A * * Lack of any ordering means reordering may occur such that dec, inc * are done before entry is overwritten. This permits a remote lock @@ -139,13 +139,8 @@ static __always_inline void release_held_lock_entry(void) * CPU holds a lock it is attempting to acquire, leading to false ABBA * diagnosis). * - * In case of unlock, we will always do a release on the lock word after - * releasing the entry, ensuring that other CPUs cannot hold the lock - * (and make conclusions about deadlocks) until the entry has been - * cleared on the local CPU, preventing any anomalies. Reordering is - * still possible there, but a remote CPU cannot observe a lock in our - * table which it is already holding, since visibility entails our - * release store for the said lock has not retired. + * The case of unlock is treated differently due to NMI reentrancy, see + * comments in res_spin_unlock. * * In theory we don't have a problem if the dec and WRITE_ONCE above get * reordered with each other, we either notice an empty NULL entry on @@ -175,10 +170,22 @@ static __always_inline int res_spin_lock(rqspinlock_t *lock) { int val = 0; - if (likely(atomic_try_cmpxchg_acquire(&lock->val, &val, _Q_LOCKED_VAL))) { - grab_held_lock_entry(lock); + /* + * Grab the deadlock detection entry before doing the cmpxchg, so that + * reentrancy due to NMIs between the succeeding cmpxchg and creation of + * held lock entry can correctly detect an acquisition attempt in the + * interrupted context. + * + * cmpxchg lock A + * + * res_spin_lock(A) --> missed AA, leads to timeout + * + * grab_held_lock_entry(A) + */ + grab_held_lock_entry(lock); + + if (likely(atomic_try_cmpxchg_acquire(&lock->val, &val, _Q_LOCKED_VAL))) return 0; - } return resilient_queued_spin_lock_slowpath(lock, val); } @@ -192,28 +199,25 @@ static __always_inline void res_spin_unlock(rqspinlock_t *lock) { struct rqspinlock_held *rqh = this_cpu_ptr(&rqspinlock_held_locks); - if (unlikely(rqh->cnt > RES_NR_HELD)) - goto unlock; - WRITE_ONCE(rqh->locks[rqh->cnt - 1], NULL); -unlock: /* - * Release barrier, ensures correct ordering. See release_held_lock_entry - * for details. Perform release store instead of queued_spin_unlock, - * since we use this function for test-and-set fallback as well. When we - * have CONFIG_QUEUED_SPINLOCKS=n, we clear the full 4-byte lockword. + * Release barrier, ensures correct ordering. Perform release store + * instead of queued_spin_unlock, since we use this function for the TAS + * fallback as well. When we have CONFIG_QUEUED_SPINLOCKS=n, we clear + * the full 4-byte lockword. * - * Like release_held_lock_entry, we can do the release before the dec. - * We simply care about not seeing the 'lock' in our table from a remote - * CPU once the lock has been released, which doesn't rely on the dec. + * Perform the smp_store_release before clearing the lock entry so that + * NMIs landing in the unlock path can correctly detect AA issues. The + * opposite order shown below may lead to missed AA checks: * - * Unlike smp_wmb(), release is not a two way fence, hence it is - * possible for a inc to move up and reorder with our clearing of the - * entry. This isn't a problem however, as for a misdiagnosis of ABBA, - * the remote CPU needs to hold this lock, which won't be released until - * the store below is done, which would ensure the entry is overwritten - * to NULL, etc. + * WRITE_ONCE(rqh->locks[rqh->cnt - 1], NULL) + * + * res_spin_lock(A) --> missed AA, leads to timeout + * + * smp_store_release(A->locked, 0) */ smp_store_release(&lock->locked, 0); + if (likely(rqh->cnt <= RES_NR_HELD)) + WRITE_ONCE(rqh->locks[rqh->cnt - 1], NULL); this_cpu_dec(rqspinlock_held_locks.cnt); } diff --git a/kernel/bpf/rqspinlock.c b/kernel/bpf/rqspinlock.c index 21be48108e9620..f4c534fa4e87b6 100644 --- a/kernel/bpf/rqspinlock.c +++ b/kernel/bpf/rqspinlock.c @@ -275,6 +275,10 @@ int __lockfunc resilient_tas_spin_lock(rqspinlock_t *lock) int val, ret = 0; RES_INIT_TIMEOUT(ts); + /* + * The fast path is not invoked for the TAS fallback, so we must grab + * the deadlock detection entry here. + */ grab_held_lock_entry(lock); /* @@ -397,10 +401,7 @@ int __lockfunc resilient_queued_spin_lock_slowpath(rqspinlock_t *lock, u32 val) goto queue; } - /* - * Grab an entry in the held locks array, to enable deadlock detection. - */ - grab_held_lock_entry(lock); + /* Deadlock detection entry already held after failing fast path. */ /* * We're pending, wait for the owner to go away. @@ -448,11 +449,7 @@ int __lockfunc resilient_queued_spin_lock_slowpath(rqspinlock_t *lock, u32 val) */ queue: lockevent_inc(lock_slowpath); - /* - * Grab deadlock detection entry for the queue path. - */ - grab_held_lock_entry(lock); - + /* Deadlock detection entry already held after failing fast path. */ node = this_cpu_ptr(&rqnodes[0].mcs); idx = node->count++; tail = encode_tail(smp_processor_id(), idx); From 14980281ebff84f6b6ec9262ba6951c73031680c Mon Sep 17 00:00:00 2001 From: Kumar Kartikeya Dwivedi Date: Fri, 28 Nov 2025 23:27:59 +0000 Subject: [PATCH 0861/3902] rqspinlock: Use trylock fallback when per-CPU rqnode is busy [ Upstream commit 81d5a6a438595e46be191d602e5c2d6d73992fdc ] In addition to deferring to the trylock fallback in NMIs, only do so when an rqspinlock waiter is queued on the current CPU. This is detected by noticing a non-zero node index. This allows NMI waiters to join the waiter queue if it isn't interrupting an existing rqspinlock waiter, and increase the chances of fairly obtaining the lock, performing deadlock detection as the head, and not being starved while attempting the trylock. The trylock path in particular is unlikely to succeed under contention, as it relies on the lock word becoming 0, which indicates no contention. This means that the most likely result for NMIs attempting a trylock is a timeout under contention if they don't hit an AA or ABBA case. The core problem being addressed through the fixed commit was removing the dependency edge between an NMI queue waiter and the queue waiter it is interrupting. Whenever a circular dependency forms, and with no way to break it (as non-head waiters don't poll for deadlocks or timeouts), we would enter into a deadlock. A trylock either breaks such an edge by probing for deadlocks, and finally terminating the waiting loop using a timeout. By excluding queueing on CPUs where the node index is non-zero for NMIs, this sort of dependency is broken. The CPU enters the trylock path for those cases, and falls back to deadlock checks and timeouts. However, in other case where it doesn't interrupt the CPU in the slow path while its queued on the lock, it can join the queue as a normal waiter, and avoid trylock associated starvation and subsequent timeouts. There are a few remaining cases here that matter: the NMI can still preempt the owner in its critical section, and if it queues as a non-head waiter, it can end up impeding the progress of the owner. While this won't deadlock, since the head waiter will eventually signal the NMI waiter to either stop (due to a timeout), it can still lead to long timeouts. These gaps will be addressed in subsequent commits. Note that while the node count detection approach is less conservative than simply deferring NMIs to trylock, it is going to return errors where attempts to lock B in NMI happen while waiters for lock A are in a lower context on the same CPU. However, this only occurs when the lower context is queued in the slow path, and the NMI attempt can proceed without failure in all other cases. To continue to prevent AA deadlocks (or ABBA in a similar NMI interrupting lower context pattern), we'd need a more fleshed out algorithm to unlink NMI waiters after they queue and detect such cases. However, all that complexity isn't appealing yet to reduce the failure rate in the small window inside the slow path. It is important to note that reentrancy in the slow path can also happen through trace_contention_{begin,end}, but in those cases, unlike an NMI, the forward progress of the head waiter (or the predecessor in general) is not being blocked. Fixes: 0d80e7f951be ("rqspinlock: Choose trylock fallback for NMI waiters") Reported-by: Ritesh Oedayrajsingh Varma Suggested-by: Alexei Starovoitov Signed-off-by: Kumar Kartikeya Dwivedi Link: https://lore.kernel.org/r/20251128232802.1031906-4-memxor@gmail.com Signed-off-by: Alexei Starovoitov Signed-off-by: Sasha Levin --- kernel/bpf/rqspinlock.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/kernel/bpf/rqspinlock.c b/kernel/bpf/rqspinlock.c index f4c534fa4e87b6..3faf9cbd6c753b 100644 --- a/kernel/bpf/rqspinlock.c +++ b/kernel/bpf/rqspinlock.c @@ -465,7 +465,7 @@ int __lockfunc resilient_queued_spin_lock_slowpath(rqspinlock_t *lock, u32 val) * any MCS node. This is not the most elegant solution, but is * simple enough. */ - if (unlikely(idx >= _Q_MAX_NODES || in_nmi())) { + if (unlikely(idx >= _Q_MAX_NODES || (in_nmi() && idx > 0))) { lockevent_inc(lock_no_node); RES_RESET_TIMEOUT(ts, RES_DEF_TIMEOUT); while (!queued_spin_trylock(lock)) { From de4a27113099a8916bc715c322fb23965bdfe4d2 Mon Sep 17 00:00:00 2001 From: Alexandru Gagniuc Date: Fri, 28 Nov 2025 19:32:05 -0600 Subject: [PATCH 0862/3902] remoteproc: qcom_q6v5_wcss: fix parsing of qcom,halt-regs [ Upstream commit 7e81fa8d809ed1e67ae9ecd52d20a20c2c65d877 ] The "qcom,halt-regs" consists of a phandle reference followed by the three offsets within syscon for halt registers. Thus, we need to request 4 integers from of_property_read_variable_u32_array(), with the halt_reg ofsets at indexes 1, 2, and 3. Offset 0 is the phandle. With MAX_HALT_REG at 3, of_property_read_variable_u32_array() returns -EOVERFLOW, causing .probe() to fail. Increase MAX_HALT_REG to 4, and update the indexes accordingly. Fixes: 0af65b9b915e ("remoteproc: qcom: wcss: Add non pas wcss Q6 support for QCS404") Signed-off-by: Alexandru Gagniuc Link: https://lore.kernel.org/r/20251129013207.3981517-1-mr.nuke.me@gmail.com Signed-off-by: Bjorn Andersson Signed-off-by: Sasha Levin --- drivers/remoteproc/qcom_q6v5_wcss.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/drivers/remoteproc/qcom_q6v5_wcss.c b/drivers/remoteproc/qcom_q6v5_wcss.c index 07c88623f59785..23ec87827d4f81 100644 --- a/drivers/remoteproc/qcom_q6v5_wcss.c +++ b/drivers/remoteproc/qcom_q6v5_wcss.c @@ -85,7 +85,7 @@ #define TCSR_WCSS_CLK_MASK 0x1F #define TCSR_WCSS_CLK_ENABLE 0x14 -#define MAX_HALT_REG 3 +#define MAX_HALT_REG 4 enum { WCSS_IPQ8074, WCSS_QCS404, @@ -864,9 +864,9 @@ static int q6v5_wcss_init_mmio(struct q6v5_wcss *wcss, return -EINVAL; } - wcss->halt_q6 = halt_reg[0]; - wcss->halt_wcss = halt_reg[1]; - wcss->halt_nc = halt_reg[2]; + wcss->halt_q6 = halt_reg[1]; + wcss->halt_wcss = halt_reg[2]; + wcss->halt_nc = halt_reg[3]; return 0; } From 61e684ef4c2bc157c50bde5f2e842cacc26761ba Mon Sep 17 00:00:00 2001 From: Yu Kuai Date: Mon, 17 Nov 2025 16:55:57 +0800 Subject: [PATCH 0863/3902] md/raid5: fix IO hang when array is broken with IO inflight [ Upstream commit a913d1f6a7f607c110aeef8b58c8988f47a4b24e ] Following test can cause IO hang: mdadm -CvR /dev/md0 -l10 -n4 /dev/sd[abcd] --assume-clean --chunk=64K --bitmap=none sleep 5 echo 1 > /sys/block/sda/device/delete echo 1 > /sys/block/sdb/device/delete echo 1 > /sys/block/sdc/device/delete echo 1 > /sys/block/sdd/device/delete dd if=/dev/md0 of=/dev/null bs=8k count=1 iflag=direct Root cause: 1) all disks removed, however all rdevs in the array is still in sync, IO will be issued normally. 2) IO failure from sda, and set badblocks failed, sda will be faulty and MD_SB_CHANGING_PENDING will be set. 3) error recovery try to recover this IO from other disks, IO will be issued to sdb, sdc, and sdd. 4) IO failure from sdb, and set badblocks failed again, now array is broken and will become read-only. 5) IO failure from sdc and sdd, however, stripe can't be handled anymore because MD_SB_CHANGING_PENDING is set: handle_stripe handle_stripe if (test_bit MD_SB_CHANGING_PENDING) set_bit STRIPE_HANDLE goto finish // skip handling failed stripe release_stripe if (test_bit STRIPE_HANDLE) list_add_tail conf->hand_list 6) later raid5d can't handle failed stripe as well: raid5d md_check_recovery md_update_sb if (!md_is_rdwr()) // can't clear pending bit return if (test_bit MD_SB_CHANGING_PENDING) break; // can't handle failed stripe Since MD_SB_CHANGING_PENDING can never be cleared for read-only array, fix this problem by skip this checking for read-only array. Link: https://lore.kernel.org/linux-raid/20251117085557.770572-3-yukuai@fnnas.com Fixes: d87f064f5874 ("md: never update metadata when array is read-only.") Signed-off-by: Yu Kuai Reviewed-by: Li Nan Signed-off-by: Sasha Levin --- drivers/md/raid5.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/drivers/md/raid5.c b/drivers/md/raid5.c index 24b32a0c95b403..8b5f8a12d4179e 100644 --- a/drivers/md/raid5.c +++ b/drivers/md/raid5.c @@ -4956,7 +4956,8 @@ static void handle_stripe(struct stripe_head *sh) goto finish; if (s.handle_bad_blocks || - test_bit(MD_SB_CHANGE_PENDING, &conf->mddev->sb_flags)) { + (md_is_rdwr(conf->mddev) && + test_bit(MD_SB_CHANGE_PENDING, &conf->mddev->sb_flags))) { set_bit(STRIPE_HANDLE, &sh->state); goto finish; } @@ -6768,7 +6769,8 @@ static void raid5d(struct md_thread *thread) int batch_size, released; unsigned int offset; - if (test_bit(MD_SB_CHANGE_PENDING, &mddev->sb_flags)) + if (md_is_rdwr(mddev) && + test_bit(MD_SB_CHANGE_PENDING, &mddev->sb_flags)) break; released = release_stripe_list(conf, conf->temp_inactive_list); From 36c6c788173e72a26c61ed07a36541ad71e32639 Mon Sep 17 00:00:00 2001 From: Johan Hovold Date: Thu, 27 Nov 2025 14:53:25 +0100 Subject: [PATCH 0864/3902] clk: keystone: fix compile testing [ Upstream commit b276445e98fe28609688fb85b89a81b803910e63 ] Some keystone clock drivers can be selected when COMPILE_TEST is enabled but since commit b745c0794e2f ("clk: keystone: Add sci-clk driver support") they are never actually built. Enable compile testing by allowing the build system to process the keystone drivers. Fixes: b745c0794e2f ("clk: keystone: Add sci-clk driver support") Signed-off-by: Johan Hovold Signed-off-by: Stephen Boyd Signed-off-by: Sasha Levin --- drivers/clk/Makefile | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/drivers/clk/Makefile b/drivers/clk/Makefile index b74a1767ca2787..61ec08404442b4 100644 --- a/drivers/clk/Makefile +++ b/drivers/clk/Makefile @@ -125,8 +125,7 @@ obj-$(CONFIG_ARCH_HISI) += hisilicon/ obj-y += imgtec/ obj-y += imx/ obj-y += ingenic/ -obj-$(CONFIG_ARCH_K3) += keystone/ -obj-$(CONFIG_ARCH_KEYSTONE) += keystone/ +obj-y += keystone/ obj-y += mediatek/ obj-$(CONFIG_ARCH_MESON) += meson/ obj-y += microchip/ From c9ae2463c529d750c8977a3304354cadd5f5df58 Mon Sep 17 00:00:00 2001 From: Stefan Metzmacher Date: Tue, 25 Nov 2025 09:55:54 +0100 Subject: [PATCH 0865/3902] smb: smbdirect: introduce SMBDIRECT_DEBUG_ERR_PTR() helper [ Upstream commit 1f3fd108c5c5a9885c6c276a2489c49b60a6b90d ] This can be used like this: int err = somefunc(); pr_warn("err=%1pe\n", SMBDIRECT_DEBUG_ERR_PTR(err)); This will be used in the following fixes in order to be prepared to identify real world problems more easily. Cc: Steve French Cc: Tom Talpey Cc: Long Li Cc: Namjae Jeon Cc: Paulo Alcantara Cc: linux-cifs@vger.kernel.org Cc: samba-technical@lists.samba.org Signed-off-by: Stefan Metzmacher Acked-by: Namjae Jeon Signed-off-by: Steve French Stable-dep-of: 425c32750b48 ("smb: server: relax WARN_ON_ONCE(SMBDIRECT_SOCKET_*) checks in recv_done() and smb_direct_cm_handler()") Signed-off-by: Sasha Levin --- fs/smb/common/smbdirect/smbdirect_socket.h | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/fs/smb/common/smbdirect/smbdirect_socket.h b/fs/smb/common/smbdirect/smbdirect_socket.h index ee5a90d691c898..611986827a5e23 100644 --- a/fs/smb/common/smbdirect/smbdirect_socket.h +++ b/fs/smb/common/smbdirect/smbdirect_socket.h @@ -74,6 +74,19 @@ const char *smbdirect_socket_status_string(enum smbdirect_socket_status status) return ""; } +/* + * This can be used with %1pe to print errors as strings or '0' + * And it avoids warnings like: warn: passing zero to 'ERR_PTR' + * from smatch -p=kernel --pedantic + */ +static __always_inline +const void * __must_check SMBDIRECT_DEBUG_ERR_PTR(long error) +{ + if (error == 0) + return NULL; + return ERR_PTR(error); +} + enum smbdirect_keepalive_status { SMBDIRECT_KEEPALIVE_NONE, SMBDIRECT_KEEPALIVE_PENDING, From 7d077222555e2dd8e06870950c513510c57d1b10 Mon Sep 17 00:00:00 2001 From: Stefan Metzmacher Date: Tue, 25 Nov 2025 09:55:55 +0100 Subject: [PATCH 0866/3902] smb: smbdirect: introduce SMBDIRECT_CHECK_STATUS_{WARN,DISCONNECT}() [ Upstream commit 1adb2dab9727c5beaaf253f67bf4fc2c54ae70e7 ] These will be used in various places in order to assert the current status mostly during the connect and negotiation phase. It will replace the WARN_ON_ONCE(sc->status != ...) calls, which are very useless in order to identify the problem that happened. As a start client and server will need to define their own __SMBDIRECT_SOCKET_DISCONNECT(__sc) macro in order to use SMBDIRECT_CHECK_STATUS_DISCONNECT(). Cc: Steve French Cc: Tom Talpey Cc: Long Li Cc: Namjae Jeon Cc: Paulo Alcantara Cc: linux-cifs@vger.kernel.org Cc: samba-technical@lists.samba.org Signed-off-by: Stefan Metzmacher Acked-by: Namjae Jeon Signed-off-by: Steve French Stable-dep-of: 425c32750b48 ("smb: server: relax WARN_ON_ONCE(SMBDIRECT_SOCKET_*) checks in recv_done() and smb_direct_cm_handler()") Signed-off-by: Sasha Levin --- fs/smb/common/smbdirect/smbdirect_socket.h | 38 ++++++++++++++++++++++ 1 file changed, 38 insertions(+) diff --git a/fs/smb/common/smbdirect/smbdirect_socket.h b/fs/smb/common/smbdirect/smbdirect_socket.h index 611986827a5e23..384b19177e1c37 100644 --- a/fs/smb/common/smbdirect/smbdirect_socket.h +++ b/fs/smb/common/smbdirect/smbdirect_socket.h @@ -394,6 +394,44 @@ static __always_inline void smbdirect_socket_init(struct smbdirect_socket *sc) init_waitqueue_head(&sc->mr_io.cleanup.wait_queue); } +#define __SMBDIRECT_CHECK_STATUS_FAILED(__sc, __expected_status, __error_cmd, __unexpected_cmd) ({ \ + bool __failed = false; \ + if (unlikely((__sc)->first_error)) { \ + __failed = true; \ + __error_cmd \ + } else if (unlikely((__sc)->status != (__expected_status))) { \ + __failed = true; \ + __unexpected_cmd \ + } \ + __failed; \ +}) + +#define __SMBDIRECT_CHECK_STATUS_WARN(__sc, __expected_status, __unexpected_cmd) \ + __SMBDIRECT_CHECK_STATUS_FAILED(__sc, __expected_status, \ + , \ + { \ + const struct sockaddr_storage *__src = NULL; \ + const struct sockaddr_storage *__dst = NULL; \ + if ((__sc)->rdma.cm_id) { \ + __src = &(__sc)->rdma.cm_id->route.addr.src_addr; \ + __dst = &(__sc)->rdma.cm_id->route.addr.dst_addr; \ + } \ + WARN_ONCE(1, \ + "expected[%s] != %s first_error=%1pe local=%pISpsfc remote=%pISpsfc\n", \ + smbdirect_socket_status_string(__expected_status), \ + smbdirect_socket_status_string((__sc)->status), \ + SMBDIRECT_DEBUG_ERR_PTR((__sc)->first_error), \ + __src, __dst); \ + __unexpected_cmd \ + }) + +#define SMBDIRECT_CHECK_STATUS_WARN(__sc, __expected_status) \ + __SMBDIRECT_CHECK_STATUS_WARN(__sc, __expected_status, /* nothing */) + +#define SMBDIRECT_CHECK_STATUS_DISCONNECT(__sc, __expected_status) \ + __SMBDIRECT_CHECK_STATUS_WARN(__sc, __expected_status, \ + __SMBDIRECT_SOCKET_DISCONNECT(__sc);) + struct smbdirect_send_io { struct smbdirect_socket *socket; struct ib_cqe cqe; From 7c4f55905cfbebdde377b7582a682775c43e2ac3 Mon Sep 17 00:00:00 2001 From: Stefan Metzmacher Date: Tue, 25 Nov 2025 15:21:53 +0100 Subject: [PATCH 0867/3902] smb: server: relax WARN_ON_ONCE(SMBDIRECT_SOCKET_*) checks in recv_done() and smb_direct_cm_handler() [ Upstream commit 425c32750b48956a6e156b6a4609d281ee471359 ] Namjae reported the following: I have a simple file copy test with windows 11 client, and get the following error message. [ 894.140312] ------------[ cut here ]------------ [ 894.140316] WARNING: CPU: 1 PID: 116 at fs/smb/server/transport_rdma.c:642 recv_done+0x308/0x360 [ksmbd] [ 894.140335] Modules linked in: ksmbd cmac nls_utf8 nls_ucs2_utils libarc4 nls_iso8859_1 snd_hda_codec_intelhdmi snd_hda_codec_hdmi snd_hda_codec_alc882 snd_hda_codec_realtek_lib snd_hda_codec_generic rpcrdma intel_rapl_msr rdma_ucm intel_rapl_common snd_hda_intel ib_iser snd_hda_codec intel_uncore_frequency intel_uncore_frequency_common snd_hda_core intel_tcc_cooling x86_pkg_temp_thermal intel_powerclamp snd_intel_dspcfg libiscsi snd_intel_sdw_acpi coretemp scsi_transport_iscsi snd_hwdep kvm_intel i915 snd_pcm ib_umad rdma_cm snd_seq_midi ib_ipoib kvm snd_seq_midi_event iw_cm snd_rawmidi ghash_clmulni_intel ib_cm aesni_intel snd_seq mei_hdcp drm_buddy rapl snd_seq_device eeepc_wmi asus_wmi snd_timer intel_cstate ttm snd drm_client_lib drm_display_helper sparse_keymap soundcore platform_profile mxm_wmi wmi_bmof joydev mei_me cec acpi_pad mei rc_core drm_kms_helper input_leds i2c_algo_bit mac_hid sch_fq_codel msr parport_pc ppdev lp nfsd parport auth_rpcgss binfmt_misc nfs_acl lockd grace drm sunrpc ramoops efi_pstore [ 894.140414] reed_solomon pstore_blk pstore_zone autofs4 btrfs blake2b_generic xor raid6_pq mlx5_ib ib_uverbs ib_core hid_generic uas usbhid hid r8169 i2c_i801 usb_storage i2c_mux i2c_smbus mlx5_core realtek ahci mlxfw psample libahci video wmi [last unloaded: ksmbd] [ 894.140442] CPU: 1 UID: 0 PID: 116 Comm: kworker/1:1H Tainted: G W 6.18.0-rc5+ #1 PREEMPT(voluntary) [ 894.140447] Tainted: [W]=WARN [ 894.140448] Hardware name: System manufacturer System Product Name/H110M-K, BIOS 3601 12/12/2017 [ 894.140450] Workqueue: ib-comp-wq ib_cq_poll_work [ib_core] [ 894.140476] RIP: 0010:recv_done+0x308/0x360 [ksmbd] [ 894.140487] Code: 2e f2 ff ff 5b 41 5c 41 5d 41 5e 41 5f 5d c3 cc cc cc cc 41 8b 55 10 49 8b 75 08 b9 02 00 00 00 e8 ed f4 f2 c3 e9 59 fd ff ff <0f> 0b e9 02 ff ff ff 49 8b 74 24 28 49 8d 94 24 c8 00 00 00 bf 00 [ 894.140490] RSP: 0018:ffffa47ec03f3d78 EFLAGS: 00010293 [ 894.140492] RAX: 0000000000000001 RBX: ffff8eb84c818000 RCX: 000000010002ba00 [ 894.140494] RDX: 0000000037600001 RSI: 0000000000000083 RDI: ffff8eb92ec9ee40 [ 894.140496] RBP: ffffa47ec03f3da0 R08: 0000000000000000 R09: 0000000000000010 [ 894.140498] R10: ffff8eb801705680 R11: fefefefefefefeff R12: ffff8eb7454b8810 [ 894.140499] R13: ffff8eb746deb988 R14: ffff8eb746deb980 R15: ffff8eb84c818000 [ 894.140501] FS: 0000000000000000(0000) GS:ffff8eb9a7355000(0000) knlGS:0000000000000000 [ 894.140503] CS: 0010 DS: 0000 ES: 0000 CR0: 0000000080050033 [ 894.140505] CR2: 00002d9401d60018 CR3: 0000000010a40006 CR4: 00000000003726f0 [ 894.140507] Call Trace: [ 894.140509] [ 894.140512] __ib_process_cq+0x8e/0x190 [ib_core] [ 894.140530] ib_cq_poll_work+0x2f/0x90 [ib_core] [ 894.140545] process_scheduled_works+0xd4/0x430 [ 894.140554] worker_thread+0x12a/0x270 [ 894.140558] kthread+0x10d/0x250 [ 894.140564] ? __pfx_worker_thread+0x10/0x10 [ 894.140567] ? __pfx_kthread+0x10/0x10 [ 894.140571] ret_from_fork+0x11a/0x160 [ 894.140574] ? __pfx_kthread+0x10/0x10 [ 894.140577] ret_from_fork_asm+0x1a/0x30 [ 894.140584] [ 894.140585] ---[ end trace 0000000000000000 ]--- [ 894.154363] ------------[ cut here ]------------ [ 894.154367] WARNING: CPU: 3 PID: 5543 at fs/smb/server/transport_rdma.c:1728 smb_direct_cm_handler+0x121/0x130 [ksmbd] [ 894.154384] Modules linked in: ksmbd cmac nls_utf8 nls_ucs2_utils libarc4 nls_iso8859_1 snd_hda_codec_intelhdmi snd_hda_codec_hdmi snd_hda_codec_alc882 snd_hda_codec_realtek_lib snd_hda_codec_generic rpcrdma intel_rapl_msr rdma_ucm intel_rapl_common snd_hda_intel ib_iser snd_hda_codec intel_uncore_frequency intel_uncore_frequency_common snd_hda_core intel_tcc_cooling x86_pkg_temp_thermal intel_powerclamp snd_intel_dspcfg libiscsi snd_intel_sdw_acpi coretemp scsi_transport_iscsi snd_hwdep kvm_intel i915 snd_pcm ib_umad rdma_cm snd_seq_midi ib_ipoib kvm snd_seq_midi_event iw_cm snd_rawmidi ghash_clmulni_intel ib_cm aesni_intel snd_seq mei_hdcp drm_buddy rapl snd_seq_device eeepc_wmi asus_wmi snd_timer intel_cstate ttm snd drm_client_lib drm_display_helper sparse_keymap soundcore platform_profile mxm_wmi wmi_bmof joydev mei_me cec acpi_pad mei rc_core drm_kms_helper input_leds i2c_algo_bit mac_hid sch_fq_codel msr parport_pc ppdev lp nfsd parport auth_rpcgss binfmt_misc nfs_acl lockd grace drm sunrpc ramoops efi_pstore [ 894.154456] reed_solomon pstore_blk pstore_zone autofs4 btrfs blake2b_generic xor raid6_pq mlx5_ib ib_uverbs ib_core hid_generic uas usbhid hid r8169 i2c_i801 usb_storage i2c_mux i2c_smbus mlx5_core realtek ahci mlxfw psample libahci video wmi [last unloaded: ksmbd] [ 894.154483] CPU: 3 UID: 0 PID: 5543 Comm: kworker/3:6 Tainted: G W 6.18.0-rc5+ #1 PREEMPT(voluntary) [ 894.154487] Tainted: [W]=WARN [ 894.154488] Hardware name: System manufacturer System Product Name/H110M-K, BIOS 3601 12/12/2017 [ 894.154490] Workqueue: ib_cm cm_work_handler [ib_cm] [ 894.154499] RIP: 0010:smb_direct_cm_handler+0x121/0x130 [ksmbd] [ 894.154507] Code: e7 e8 13 b1 ef ff 44 89 e1 4c 89 ee 48 c7 c7 80 d7 59 c1 48 89 c2 e8 2e 4d ef c3 31 c0 5b 41 5c 41 5d 41 5e 5d c3 cc cc cc cc <0f> 0b eb a5 66 66 2e 0f 1f 84 00 00 00 00 00 90 90 90 90 90 90 90 [ 894.154510] RSP: 0018:ffffa47ec1b27c00 EFLAGS: 00010206 [ 894.154512] RAX: ffffffffc1304e00 RBX: ffff8eb89ae50880 RCX: 0000000000000000 [ 894.154514] RDX: ffff8eb730960000 RSI: ffffa47ec1b27c60 RDI: ffff8eb7454b9400 [ 894.154515] RBP: ffffa47ec1b27c20 R08: 0000000000000002 R09: ffff8eb730b8c18b [ 894.154517] R10: 0000000000000001 R11: 0000000000000001 R12: 0000000000000009 [ 894.154518] R13: ffff8eb7454b9400 R14: ffff8eb7454b8810 R15: ffff8eb815c43000 [ 894.154520] FS: 0000000000000000(0000) GS:ffff8eb9a7455000(0000) knlGS:0000000000000000 [ 894.154522] CS: 0010 DS: 0000 ES: 0000 CR0: 0000000080050033 [ 894.154523] CR2: 00007fe1310e99d0 CR3: 0000000010a40005 CR4: 00000000003726f0 [ 894.154525] Call Trace: [ 894.154527] [ 894.154530] cma_cm_event_handler+0x27/0xd0 [rdma_cm] [ 894.154541] cma_ib_handler+0x99/0x2e0 [rdma_cm] [ 894.154551] cm_process_work+0x28/0xf0 [ib_cm] [ 894.154557] cm_queue_work_unlock+0x41/0xf0 [ib_cm] [ 894.154563] cm_work_handler+0x2eb/0x25b0 [ib_cm] [ 894.154568] ? pwq_activate_first_inactive+0x52/0x70 [ 894.154572] ? pwq_dec_nr_in_flight+0x244/0x330 [ 894.154575] process_scheduled_works+0xd4/0x430 [ 894.154579] worker_thread+0x12a/0x270 [ 894.154581] kthread+0x10d/0x250 [ 894.154585] ? __pfx_worker_thread+0x10/0x10 [ 894.154587] ? __pfx_kthread+0x10/0x10 [ 894.154590] ret_from_fork+0x11a/0x160 [ 894.154593] ? __pfx_kthread+0x10/0x10 [ 894.154596] ret_from_fork_asm+0x1a/0x30 [ 894.154602] [ 894.154603] ---[ end trace 0000000000000000 ]--- [ 894.154931] ksmbd: smb_direct: disconnected [ 894.157278] ksmbd: smb_direct: disconnected I guess sc->first_error is already set and sc->status is thus unexpected, so this should avoid the WARN[_ON]_ONCE() if sc->first_error is already set and have a usable error path. While there set sc->first_error as soon as possible. v1 of this patch revealed the real problem with this message: [ 309.560973] expected[NEGOTIATE_NEEDED] != RDMA_CONNECT_RUNNING first_error=0 local=192.168.0.200:445 remote=192.168.0.100:60445 [ 309.561034] WARNING: CPU: 2 PID: 78 at transport_rdma.c:643 recv_done+0x2fa/0x3d0 [ksmbd] Some drivers (at least mlx5_ib) might post a recv completion before RDMA_CM_EVENT_ESTABLISHED, so we need to adjust our expectation in that case. Fixes: e2d5e516c663 ("smb: server: only turn into SMBDIRECT_SOCKET_CONNECTED when negotiation is done") Cc: Steve French Cc: Tom Talpey Cc: Long Li Cc: Namjae Jeon Cc: Paulo Alcantara Cc: linux-cifs@vger.kernel.org Cc: samba-technical@lists.samba.org Signed-off-by: Stefan Metzmacher Acked-by: Namjae Jeon Signed-off-by: Steve French Signed-off-by: Sasha Levin --- fs/smb/server/transport_rdma.c | 40 +++++++++++++++++++++++++++------- 1 file changed, 32 insertions(+), 8 deletions(-) diff --git a/fs/smb/server/transport_rdma.c b/fs/smb/server/transport_rdma.c index e2be9a49615461..4e7ab8d9314f61 100644 --- a/fs/smb/server/transport_rdma.c +++ b/fs/smb/server/transport_rdma.c @@ -19,6 +19,8 @@ #include #include +#define __SMBDIRECT_SOCKET_DISCONNECT(__sc) smb_direct_disconnect_rdma_connection(__sc) + #include "glob.h" #include "connection.h" #include "smb_common.h" @@ -231,6 +233,9 @@ static void smb_direct_disconnect_rdma_work(struct work_struct *work) struct smbdirect_socket *sc = container_of(work, struct smbdirect_socket, disconnect_work); + if (sc->first_error == 0) + sc->first_error = -ECONNABORTED; + /* * make sure this and other work is not queued again * but here we don't block and avoid @@ -241,9 +246,6 @@ static void smb_direct_disconnect_rdma_work(struct work_struct *work) disable_delayed_work(&sc->idle.timer_work); disable_work(&sc->idle.immediate_work); - if (sc->first_error == 0) - sc->first_error = -ECONNABORTED; - switch (sc->status) { case SMBDIRECT_SOCKET_NEGOTIATE_NEEDED: case SMBDIRECT_SOCKET_NEGOTIATE_RUNNING: @@ -287,6 +289,9 @@ static void smb_direct_disconnect_rdma_work(struct work_struct *work) static void smb_direct_disconnect_rdma_connection(struct smbdirect_socket *sc) { + if (sc->first_error == 0) + sc->first_error = -ECONNABORTED; + /* * make sure other work (than disconnect_work) is * not queued again but here we don't block and avoid @@ -296,9 +301,6 @@ smb_direct_disconnect_rdma_connection(struct smbdirect_socket *sc) disable_work(&sc->idle.immediate_work); disable_delayed_work(&sc->idle.timer_work); - if (sc->first_error == 0) - sc->first_error = -ECONNABORTED; - switch (sc->status) { case SMBDIRECT_SOCKET_RESOLVE_ADDR_FAILED: case SMBDIRECT_SOCKET_RESOLVE_ROUTE_FAILED: @@ -639,7 +641,18 @@ static void recv_done(struct ib_cq *cq, struct ib_wc *wc) return; } sc->recv_io.reassembly.full_packet_received = true; - WARN_ON_ONCE(sc->status != SMBDIRECT_SOCKET_NEGOTIATE_NEEDED); + /* + * Some drivers (at least mlx5_ib) might post a + * recv completion before RDMA_CM_EVENT_ESTABLISHED, + * we need to adjust our expectation in that case. + */ + if (!sc->first_error && sc->status == SMBDIRECT_SOCKET_RDMA_CONNECT_RUNNING) + sc->status = SMBDIRECT_SOCKET_NEGOTIATE_NEEDED; + if (SMBDIRECT_CHECK_STATUS_WARN(sc, SMBDIRECT_SOCKET_NEGOTIATE_NEEDED)) { + put_recvmsg(sc, recvmsg); + smb_direct_disconnect_rdma_connection(sc); + return; + } sc->status = SMBDIRECT_SOCKET_NEGOTIATE_RUNNING; enqueue_reassembly(sc, recvmsg, 0); wake_up(&sc->status_wait); @@ -1725,7 +1738,18 @@ static int smb_direct_cm_handler(struct rdma_cm_id *cm_id, switch (event->event) { case RDMA_CM_EVENT_ESTABLISHED: { - WARN_ON_ONCE(sc->status != SMBDIRECT_SOCKET_RDMA_CONNECT_RUNNING); + /* + * Some drivers (at least mlx5_ib) might post a + * recv completion before RDMA_CM_EVENT_ESTABLISHED, + * we need to adjust our expectation in that case. + * + * As we already started the negotiation, we just + * ignore RDMA_CM_EVENT_ESTABLISHED here. + */ + if (!sc->first_error && sc->status > SMBDIRECT_SOCKET_RDMA_CONNECT_RUNNING) + break; + if (SMBDIRECT_CHECK_STATUS_DISCONNECT(sc, SMBDIRECT_SOCKET_RDMA_CONNECT_RUNNING)) + break; sc->status = SMBDIRECT_SOCKET_NEGOTIATE_NEEDED; wake_up(&sc->status_wait); break; From 0a1254e59754f1564cc20b6d903a688cd0bd7eaa Mon Sep 17 00:00:00 2001 From: Stefan Metzmacher Date: Tue, 25 Nov 2025 15:21:54 +0100 Subject: [PATCH 0868/3902] smb: client: relax WARN_ON_ONCE(SMBDIRECT_SOCKET_*) checks in recv_done() and smbd_conn_upcall() [ Upstream commit dc10cf1368af8cb816dcaa2502ba7d44fff20612 ] sc->first_error might already be set and sc->status is thus unexpected, so this should avoid the WARN[_ON]_ONCE() if sc->first_error is already set and have a usable error path. While there set sc->first_error as soon as possible. This is done based on a problem seen in similar places on the server. And there it was already very useful in order to find the problem when we have a meaningful WARN_ONCE() that prints details about the connection. This is much more useful: [ 309.560973] expected[NEGOTIATE_NEEDED] != RDMA_CONNECT_RUNNING first_error=0 local=192.168.0.200:445 remote=192.168.0.100:60445 [ 309.561034] WARNING: CPU: 2 PID: 78 at transport_rdma.c:643 recv_done+0x2fa/0x3d0 [ksmbd] than what we had before (only): [ 894.140316] WARNING: CPU: 1 PID: 116 at fs/smb/server/transport_rdma.c:642 recv_done+0x308/0x360 [ksmbd] Fixes: 58dfba8a2d4e ("smb: client/smbdirect: replace SMBDIRECT_SOCKET_CONNECTING with more detailed states") Cc: Steve French Cc: Tom Talpey Cc: Long Li Cc: Namjae Jeon Cc: Paulo Alcantara Cc: linux-cifs@vger.kernel.org Cc: samba-technical@lists.samba.org Signed-off-by: Stefan Metzmacher Acked-by: Namjae Jeon Signed-off-by: Steve French Signed-off-by: Sasha Levin --- fs/smb/client/smbdirect.c | 28 +++++++++++++++------------- 1 file changed, 15 insertions(+), 13 deletions(-) diff --git a/fs/smb/client/smbdirect.c b/fs/smb/client/smbdirect.c index c6c428c2e08ddb..788a0670c4a8da 100644 --- a/fs/smb/client/smbdirect.c +++ b/fs/smb/client/smbdirect.c @@ -7,6 +7,7 @@ #include #include #include +#define __SMBDIRECT_SOCKET_DISCONNECT(__sc) smbd_disconnect_rdma_connection(__sc) #include "../common/smbdirect/smbdirect_pdu.h" #include "smbdirect.h" #include "cifs_debug.h" @@ -186,6 +187,9 @@ static void smbd_disconnect_rdma_work(struct work_struct *work) struct smbdirect_socket *sc = container_of(work, struct smbdirect_socket, disconnect_work); + if (sc->first_error == 0) + sc->first_error = -ECONNABORTED; + /* * make sure this and other work is not queued again * but here we don't block and avoid @@ -197,9 +201,6 @@ static void smbd_disconnect_rdma_work(struct work_struct *work) disable_work(&sc->idle.immediate_work); disable_delayed_work(&sc->idle.timer_work); - if (sc->first_error == 0) - sc->first_error = -ECONNABORTED; - switch (sc->status) { case SMBDIRECT_SOCKET_NEGOTIATE_NEEDED: case SMBDIRECT_SOCKET_NEGOTIATE_RUNNING: @@ -242,6 +243,9 @@ static void smbd_disconnect_rdma_work(struct work_struct *work) static void smbd_disconnect_rdma_connection(struct smbdirect_socket *sc) { + if (sc->first_error == 0) + sc->first_error = -ECONNABORTED; + /* * make sure other work (than disconnect_work) is * not queued again but here we don't block and avoid @@ -252,9 +256,6 @@ static void smbd_disconnect_rdma_connection(struct smbdirect_socket *sc) disable_work(&sc->idle.immediate_work); disable_delayed_work(&sc->idle.timer_work); - if (sc->first_error == 0) - sc->first_error = -ECONNABORTED; - switch (sc->status) { case SMBDIRECT_SOCKET_RESOLVE_ADDR_FAILED: case SMBDIRECT_SOCKET_RESOLVE_ROUTE_FAILED: @@ -322,27 +323,27 @@ static int smbd_conn_upcall( switch (event->event) { case RDMA_CM_EVENT_ADDR_RESOLVED: - WARN_ON_ONCE(sc->status != SMBDIRECT_SOCKET_RESOLVE_ADDR_RUNNING); + if (SMBDIRECT_CHECK_STATUS_DISCONNECT(sc, SMBDIRECT_SOCKET_RESOLVE_ADDR_RUNNING)) + break; sc->status = SMBDIRECT_SOCKET_RESOLVE_ROUTE_NEEDED; wake_up(&sc->status_wait); break; case RDMA_CM_EVENT_ROUTE_RESOLVED: - WARN_ON_ONCE(sc->status != SMBDIRECT_SOCKET_RESOLVE_ROUTE_RUNNING); + if (SMBDIRECT_CHECK_STATUS_DISCONNECT(sc, SMBDIRECT_SOCKET_RESOLVE_ROUTE_RUNNING)) + break; sc->status = SMBDIRECT_SOCKET_RDMA_CONNECT_NEEDED; wake_up(&sc->status_wait); break; case RDMA_CM_EVENT_ADDR_ERROR: log_rdma_event(ERR, "connecting failed event=%s\n", event_name); - WARN_ON_ONCE(sc->status != SMBDIRECT_SOCKET_RESOLVE_ADDR_RUNNING); sc->status = SMBDIRECT_SOCKET_RESOLVE_ADDR_FAILED; smbd_disconnect_rdma_work(&sc->disconnect_work); break; case RDMA_CM_EVENT_ROUTE_ERROR: log_rdma_event(ERR, "connecting failed event=%s\n", event_name); - WARN_ON_ONCE(sc->status != SMBDIRECT_SOCKET_RESOLVE_ROUTE_RUNNING); sc->status = SMBDIRECT_SOCKET_RESOLVE_ROUTE_FAILED; smbd_disconnect_rdma_work(&sc->disconnect_work); break; @@ -428,7 +429,8 @@ static int smbd_conn_upcall( min_t(u8, sp->responder_resources, peer_responder_resources); - WARN_ON_ONCE(sc->status != SMBDIRECT_SOCKET_RDMA_CONNECT_RUNNING); + if (SMBDIRECT_CHECK_STATUS_DISCONNECT(sc, SMBDIRECT_SOCKET_RDMA_CONNECT_RUNNING)) + break; sc->status = SMBDIRECT_SOCKET_NEGOTIATE_NEEDED; wake_up(&sc->status_wait); break; @@ -437,7 +439,6 @@ static int smbd_conn_upcall( case RDMA_CM_EVENT_UNREACHABLE: case RDMA_CM_EVENT_REJECTED: log_rdma_event(ERR, "connecting failed event=%s\n", event_name); - WARN_ON_ONCE(sc->status != SMBDIRECT_SOCKET_RDMA_CONNECT_RUNNING); sc->status = SMBDIRECT_SOCKET_RDMA_CONNECT_FAILED; smbd_disconnect_rdma_work(&sc->disconnect_work); break; @@ -699,7 +700,8 @@ static void recv_done(struct ib_cq *cq, struct ib_wc *wc) negotiate_done = process_negotiation_response(response, wc->byte_len); put_receive_buffer(sc, response); - WARN_ON_ONCE(sc->status != SMBDIRECT_SOCKET_NEGOTIATE_RUNNING); + if (SMBDIRECT_CHECK_STATUS_WARN(sc, SMBDIRECT_SOCKET_NEGOTIATE_RUNNING)) + negotiate_done = false; if (!negotiate_done) { sc->status = SMBDIRECT_SOCKET_NEGOTIATE_FAILED; smbd_disconnect_rdma_connection(sc); From 210226ce6ca6c14f0c673dd73ec46390f0ec082f Mon Sep 17 00:00:00 2001 From: "Christophe Leroy (CS GROUP)" Date: Sat, 29 Nov 2025 10:56:02 +0100 Subject: [PATCH 0869/3902] um: Disable KASAN_INLINE when STATIC_LINK is selected [ Upstream commit a3209bb94b36351f11e0d9e72ac44e5dd777a069 ] um doesn't support KASAN_INLINE together with STATIC_LINK. Instead of failing the build, disable KASAN_INLINE when STATIC_LINK is selected. Reported-by: kernel test robot Closes: https://lore.kernel.org/oe-kbuild-all/202511290451.x9GZVJ1l-lkp@intel.com/ Fixes: 1e338f4d99e6 ("kasan: introduce ARCH_DEFER_KASAN and unify static key across modes") Signed-off-by: Christophe Leroy (CS GROUP) Link: https://patch.msgid.link/2620ab0bbba640b6237c50b9c0dca1c7d1142f5d.1764410067.git.chleroy@kernel.org Signed-off-by: Johannes Berg Signed-off-by: Sasha Levin --- arch/um/Kconfig | 1 + arch/um/include/asm/kasan.h | 4 ---- 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/arch/um/Kconfig b/arch/um/Kconfig index 49781bee790580..93ed850d508ed2 100644 --- a/arch/um/Kconfig +++ b/arch/um/Kconfig @@ -5,6 +5,7 @@ menu "UML-specific options" config UML bool default y + select ARCH_DISABLE_KASAN_INLINE if STATIC_LINK select ARCH_NEEDS_DEFER_KASAN if STATIC_LINK select ARCH_WANTS_DYNAMIC_TASK_STRUCT select ARCH_HAS_CACHE_LINE_SIZE diff --git a/arch/um/include/asm/kasan.h b/arch/um/include/asm/kasan.h index b54a4e937fd12a..81bcdc0f962e67 100644 --- a/arch/um/include/asm/kasan.h +++ b/arch/um/include/asm/kasan.h @@ -24,10 +24,6 @@ #ifdef CONFIG_KASAN void kasan_init(void); - -#if defined(CONFIG_STATIC_LINK) && defined(CONFIG_KASAN_INLINE) -#error UML does not work in KASAN_INLINE mode with STATIC_LINK enabled! -#endif #else static inline void kasan_init(void) { } #endif /* CONFIG_KASAN */ From c25b004020809fb71aa712d55eb19b853c327e98 Mon Sep 17 00:00:00 2001 From: Jonas Gorski Date: Fri, 28 Nov 2025 09:06:19 +0100 Subject: [PATCH 0870/3902] net: dsa: b53: fix VLAN_ID_IDX write size for BCM5325/65 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [ Upstream commit 6f268e275c74dae0536e0b61982a8db25bcf4f16 ] Since BCM5325 and BCM5365 only support up to 256 VLANs, the VLAN_ID_IDX register is only 8 bit wide, not 16 bit, so use an appropriate accessor. Fixes: c45655386e53 ("net: dsa: b53: add support for FDB operations on 5325/5365") Reviewed-by: Florian Fainelli Tested-by: Álvaro Fernández Rojas Signed-off-by: Jonas Gorski Link: https://patch.msgid.link/20251128080625.27181-2-jonas.gorski@gmail.com Signed-off-by: Jakub Kicinski Signed-off-by: Sasha Levin --- drivers/net/dsa/b53/b53_common.c | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/drivers/net/dsa/b53/b53_common.c b/drivers/net/dsa/b53/b53_common.c index eb767edc4c135b..a09ed32dccc071 100644 --- a/drivers/net/dsa/b53/b53_common.c +++ b/drivers/net/dsa/b53/b53_common.c @@ -1924,8 +1924,12 @@ static int b53_arl_op(struct b53_device *dev, int op, int port, /* Perform a read for the given MAC and VID */ b53_write48(dev, B53_ARLIO_PAGE, B53_MAC_ADDR_IDX, mac); - if (!is5325m(dev)) - b53_write16(dev, B53_ARLIO_PAGE, B53_VLAN_ID_IDX, vid); + if (!is5325m(dev)) { + if (is5325(dev) || is5365(dev)) + b53_write8(dev, B53_ARLIO_PAGE, B53_VLAN_ID_IDX, vid); + else + b53_write16(dev, B53_ARLIO_PAGE, B53_VLAN_ID_IDX, vid); + } /* Issue a read operation for this MAC */ ret = b53_arl_rw_op(dev, 1); From 6f2311c6cb9f16b8fa50f4c2d5bef22d1a54f4aa Mon Sep 17 00:00:00 2001 From: Jonas Gorski Date: Fri, 28 Nov 2025 09:06:20 +0100 Subject: [PATCH 0871/3902] net: dsa: b53: fix extracting VID from entry for BCM5325/65 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [ Upstream commit 9316012dd01952f75e37035360138ccc786ef727 ] BCM5325/65's Entry register uses the highest three bits for VALID/STATIC/AGE, so shifting by 53 only will add these to b53_arl_entry::vid. So make sure to mask the vid value as well, to not get invalid VIDs. Fixes: c45655386e53 ("net: dsa: b53: add support for FDB operations on 5325/5365") Reviewed-by: Florian Fainelli Tested-by: Álvaro Fernández Rojas Signed-off-by: Jonas Gorski Link: https://patch.msgid.link/20251128080625.27181-3-jonas.gorski@gmail.com Signed-off-by: Jakub Kicinski Signed-off-by: Sasha Levin --- drivers/net/dsa/b53/b53_priv.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/net/dsa/b53/b53_priv.h b/drivers/net/dsa/b53/b53_priv.h index 458775f9516437..2f44b3b6a0d9fb 100644 --- a/drivers/net/dsa/b53/b53_priv.h +++ b/drivers/net/dsa/b53/b53_priv.h @@ -338,7 +338,7 @@ static inline void b53_arl_to_entry_25(struct b53_arl_entry *ent, ent->is_age = !!(mac_vid & ARLTBL_AGE_25); ent->is_static = !!(mac_vid & ARLTBL_STATIC_25); u64_to_ether_addr(mac_vid, ent->mac); - ent->vid = mac_vid >> ARLTBL_VID_S_65; + ent->vid = (mac_vid >> ARLTBL_VID_S_65) & ARLTBL_VID_MASK_25; } static inline void b53_arl_from_entry(u64 *mac_vid, u32 *fwd_entry, From 397fdaf790ceb3cfa2b3b11bbfc384a5526b6d9d Mon Sep 17 00:00:00 2001 From: Jonas Gorski Date: Fri, 7 Nov 2025 09:07:42 +0100 Subject: [PATCH 0872/3902] net: dsa: b53: b53_arl_read{,25}(): use the entry for comparision [ Upstream commit a6e4fd38bf2f2e2363b61c27f4e6c49b14e4bb07 ] Align the b53_arl_read{,25}() functions by consistently using the parsed arl entry instead of parsing the raw registers again. Signed-off-by: Jonas Gorski Reviewed-by: Florian Fainelli Link: https://patch.msgid.link/20251107080749.26936-2-jonas.gorski@gmail.com Signed-off-by: Jakub Kicinski Stable-dep-of: 8e46aacea426 ("net: dsa: b53: use same ARL search result offset for BCM5325/65") Signed-off-by: Sasha Levin --- drivers/net/dsa/b53/b53_common.c | 22 ++++++++++------------ 1 file changed, 10 insertions(+), 12 deletions(-) diff --git a/drivers/net/dsa/b53/b53_common.c b/drivers/net/dsa/b53/b53_common.c index a09ed32dccc071..4d8de90fb4ab84 100644 --- a/drivers/net/dsa/b53/b53_common.c +++ b/drivers/net/dsa/b53/b53_common.c @@ -1830,7 +1830,7 @@ static int b53_arl_rw_op(struct b53_device *dev, unsigned int op) return b53_arl_op_wait(dev); } -static int b53_arl_read(struct b53_device *dev, u64 mac, +static int b53_arl_read(struct b53_device *dev, const u8 *mac, u16 vid, struct b53_arl_entry *ent, u8 *idx) { DECLARE_BITMAP(free_bins, B53_ARLTBL_MAX_BIN_ENTRIES); @@ -1854,14 +1854,13 @@ static int b53_arl_read(struct b53_device *dev, u64 mac, B53_ARLTBL_DATA_ENTRY(i), &fwd_entry); b53_arl_to_entry(ent, mac_vid, fwd_entry); - if (!(fwd_entry & ARLTBL_VALID)) { + if (!ent->is_valid) { set_bit(i, free_bins); continue; } - if ((mac_vid & ARLTBL_MAC_MASK) != mac) + if (!ether_addr_equal(ent->mac, mac)) continue; - if (dev->vlan_enabled && - ((mac_vid >> ARLTBL_VID_S) & ARLTBL_VID_MASK) != vid) + if (dev->vlan_enabled && ent->vid != vid) continue; *idx = i; return 0; @@ -1871,7 +1870,7 @@ static int b53_arl_read(struct b53_device *dev, u64 mac, return *idx >= dev->num_arl_bins ? -ENOSPC : -ENOENT; } -static int b53_arl_read_25(struct b53_device *dev, u64 mac, +static int b53_arl_read_25(struct b53_device *dev, const u8 *mac, u16 vid, struct b53_arl_entry *ent, u8 *idx) { DECLARE_BITMAP(free_bins, B53_ARLTBL_MAX_BIN_ENTRIES); @@ -1893,14 +1892,13 @@ static int b53_arl_read_25(struct b53_device *dev, u64 mac, b53_arl_to_entry_25(ent, mac_vid); - if (!(mac_vid & ARLTBL_VALID_25)) { + if (!ent->is_valid) { set_bit(i, free_bins); continue; } - if ((mac_vid & ARLTBL_MAC_MASK) != mac) + if (!ether_addr_equal(ent->mac, mac)) continue; - if (dev->vlan_enabled && - ((mac_vid >> ARLTBL_VID_S_65) & ARLTBL_VID_MASK_25) != vid) + if (dev->vlan_enabled && ent->vid != vid) continue; *idx = i; return 0; @@ -1937,9 +1935,9 @@ static int b53_arl_op(struct b53_device *dev, int op, int port, return ret; if (is5325(dev) || is5365(dev)) - ret = b53_arl_read_25(dev, mac, vid, &ent, &idx); + ret = b53_arl_read_25(dev, addr, vid, &ent, &idx); else - ret = b53_arl_read(dev, mac, vid, &ent, &idx); + ret = b53_arl_read(dev, addr, vid, &ent, &idx); /* If this is a read, just finish now */ if (op) From ed2f26bf281bfd061d3b64bd6c23846839fee08a Mon Sep 17 00:00:00 2001 From: Jonas Gorski Date: Fri, 7 Nov 2025 09:07:43 +0100 Subject: [PATCH 0873/3902] net: dsa: b53: move reading ARL entries into their own function [ Upstream commit 4a291fe7226736a465ddb3fa93c21fcef7162ec7 ] Instead of duplicating the whole code iterating over all bins for BCM5325, factor out reading and parsing the entry into its own functions, and name it the modern one after the first chip with that ARL format, (BCM53)95. Signed-off-by: Jonas Gorski Reviewed-by: Florian Fainelli Link: https://patch.msgid.link/20251107080749.26936-3-jonas.gorski@gmail.com Signed-off-by: Jakub Kicinski Stable-dep-of: 8e46aacea426 ("net: dsa: b53: use same ARL search result offset for BCM5325/65") Signed-off-by: Sasha Levin --- drivers/net/dsa/b53/b53_common.c | 69 +++++++++++--------------------- 1 file changed, 23 insertions(+), 46 deletions(-) diff --git a/drivers/net/dsa/b53/b53_common.c b/drivers/net/dsa/b53/b53_common.c index 4d8de90fb4ab84..41dcd2d03230d3 100644 --- a/drivers/net/dsa/b53/b53_common.c +++ b/drivers/net/dsa/b53/b53_common.c @@ -1830,48 +1830,30 @@ static int b53_arl_rw_op(struct b53_device *dev, unsigned int op) return b53_arl_op_wait(dev); } -static int b53_arl_read(struct b53_device *dev, const u8 *mac, - u16 vid, struct b53_arl_entry *ent, u8 *idx) +static void b53_arl_read_entry_25(struct b53_device *dev, + struct b53_arl_entry *ent, u8 idx) { - DECLARE_BITMAP(free_bins, B53_ARLTBL_MAX_BIN_ENTRIES); - unsigned int i; - int ret; - - ret = b53_arl_op_wait(dev); - if (ret) - return ret; - - bitmap_zero(free_bins, dev->num_arl_bins); - - /* Read the bins */ - for (i = 0; i < dev->num_arl_bins; i++) { - u64 mac_vid; - u32 fwd_entry; + u64 mac_vid; - b53_read64(dev, B53_ARLIO_PAGE, - B53_ARLTBL_MAC_VID_ENTRY(i), &mac_vid); - b53_read32(dev, B53_ARLIO_PAGE, - B53_ARLTBL_DATA_ENTRY(i), &fwd_entry); - b53_arl_to_entry(ent, mac_vid, fwd_entry); + b53_read64(dev, B53_ARLIO_PAGE, B53_ARLTBL_MAC_VID_ENTRY(idx), + &mac_vid); + b53_arl_to_entry_25(ent, mac_vid); +} - if (!ent->is_valid) { - set_bit(i, free_bins); - continue; - } - if (!ether_addr_equal(ent->mac, mac)) - continue; - if (dev->vlan_enabled && ent->vid != vid) - continue; - *idx = i; - return 0; - } +static void b53_arl_read_entry_95(struct b53_device *dev, + struct b53_arl_entry *ent, u8 idx) +{ + u32 fwd_entry; + u64 mac_vid; - *idx = find_first_bit(free_bins, dev->num_arl_bins); - return *idx >= dev->num_arl_bins ? -ENOSPC : -ENOENT; + b53_read64(dev, B53_ARLIO_PAGE, B53_ARLTBL_MAC_VID_ENTRY(idx), + &mac_vid); + b53_read32(dev, B53_ARLIO_PAGE, B53_ARLTBL_DATA_ENTRY(idx), &fwd_entry); + b53_arl_to_entry(ent, mac_vid, fwd_entry); } -static int b53_arl_read_25(struct b53_device *dev, const u8 *mac, - u16 vid, struct b53_arl_entry *ent, u8 *idx) +static int b53_arl_read(struct b53_device *dev, const u8 *mac, + u16 vid, struct b53_arl_entry *ent, u8 *idx) { DECLARE_BITMAP(free_bins, B53_ARLTBL_MAX_BIN_ENTRIES); unsigned int i; @@ -1885,12 +1867,10 @@ static int b53_arl_read_25(struct b53_device *dev, const u8 *mac, /* Read the bins */ for (i = 0; i < dev->num_arl_bins; i++) { - u64 mac_vid; - - b53_read64(dev, B53_ARLIO_PAGE, - B53_ARLTBL_MAC_VID_ENTRY(i), &mac_vid); - - b53_arl_to_entry_25(ent, mac_vid); + if (is5325(dev) || is5365(dev)) + b53_arl_read_entry_25(dev, ent, i); + else + b53_arl_read_entry_95(dev, ent, i); if (!ent->is_valid) { set_bit(i, free_bins); @@ -1934,10 +1914,7 @@ static int b53_arl_op(struct b53_device *dev, int op, int port, if (ret) return ret; - if (is5325(dev) || is5365(dev)) - ret = b53_arl_read_25(dev, addr, vid, &ent, &idx); - else - ret = b53_arl_read(dev, addr, vid, &ent, &idx); + ret = b53_arl_read(dev, addr, vid, &ent, &idx); /* If this is a read, just finish now */ if (op) From 5e86ebfb43069d572c6f742aa60a8cb80a2f1b83 Mon Sep 17 00:00:00 2001 From: Jonas Gorski Date: Fri, 7 Nov 2025 09:07:44 +0100 Subject: [PATCH 0874/3902] net: dsa: b53: move writing ARL entries into their own functions [ Upstream commit bf6e9d2ae1dbafee53ec4ccd126595172e1e5278 ] Move writing ARL entries into individual functions for each format. Signed-off-by: Jonas Gorski Reviewed-by: Florian Fainelli Link: https://patch.msgid.link/20251107080749.26936-4-jonas.gorski@gmail.com Signed-off-by: Jakub Kicinski Stable-dep-of: 8e46aacea426 ("net: dsa: b53: use same ARL search result offset for BCM5325/65") Signed-off-by: Sasha Levin --- drivers/net/dsa/b53/b53_common.c | 38 ++++++++++++++++++++++---------- 1 file changed, 26 insertions(+), 12 deletions(-) diff --git a/drivers/net/dsa/b53/b53_common.c b/drivers/net/dsa/b53/b53_common.c index 41dcd2d03230d3..4c418e0531f738 100644 --- a/drivers/net/dsa/b53/b53_common.c +++ b/drivers/net/dsa/b53/b53_common.c @@ -1840,6 +1840,16 @@ static void b53_arl_read_entry_25(struct b53_device *dev, b53_arl_to_entry_25(ent, mac_vid); } +static void b53_arl_write_entry_25(struct b53_device *dev, + const struct b53_arl_entry *ent, u8 idx) +{ + u64 mac_vid; + + b53_arl_from_entry_25(&mac_vid, ent); + b53_write64(dev, B53_ARLIO_PAGE, B53_ARLTBL_MAC_VID_ENTRY(idx), + mac_vid); +} + static void b53_arl_read_entry_95(struct b53_device *dev, struct b53_arl_entry *ent, u8 idx) { @@ -1852,6 +1862,19 @@ static void b53_arl_read_entry_95(struct b53_device *dev, b53_arl_to_entry(ent, mac_vid, fwd_entry); } +static void b53_arl_write_entry_95(struct b53_device *dev, + const struct b53_arl_entry *ent, u8 idx) +{ + u32 fwd_entry; + u64 mac_vid; + + b53_arl_from_entry(&mac_vid, &fwd_entry, ent); + b53_write64(dev, B53_ARLIO_PAGE, B53_ARLTBL_MAC_VID_ENTRY(idx), + mac_vid); + b53_write32(dev, B53_ARLIO_PAGE, B53_ARLTBL_DATA_ENTRY(idx), + fwd_entry); +} + static int b53_arl_read(struct b53_device *dev, const u8 *mac, u16 vid, struct b53_arl_entry *ent, u8 *idx) { @@ -1892,9 +1915,8 @@ static int b53_arl_op(struct b53_device *dev, int op, int port, const unsigned char *addr, u16 vid, bool is_valid) { struct b53_arl_entry ent; - u32 fwd_entry; - u64 mac, mac_vid = 0; u8 idx = 0; + u64 mac; int ret; /* Convert the array into a 64-bit MAC */ @@ -1931,7 +1953,6 @@ static int b53_arl_op(struct b53_device *dev, int op, int port, /* We could not find a matching MAC, so reset to a new entry */ dev_dbg(dev->dev, "{%pM,%.4d} not found, using idx: %d\n", addr, vid, idx); - fwd_entry = 0; break; default: dev_dbg(dev->dev, "{%pM,%.4d} found, using idx: %d\n", @@ -1959,16 +1980,9 @@ static int b53_arl_op(struct b53_device *dev, int op, int port, ent.is_age = false; memcpy(ent.mac, addr, ETH_ALEN); if (is5325(dev) || is5365(dev)) - b53_arl_from_entry_25(&mac_vid, &ent); + b53_arl_write_entry_25(dev, &ent, idx); else - b53_arl_from_entry(&mac_vid, &fwd_entry, &ent); - - b53_write64(dev, B53_ARLIO_PAGE, - B53_ARLTBL_MAC_VID_ENTRY(idx), mac_vid); - - if (!is5325(dev) && !is5365(dev)) - b53_write32(dev, B53_ARLIO_PAGE, - B53_ARLTBL_DATA_ENTRY(idx), fwd_entry); + b53_arl_write_entry_95(dev, &ent, idx); return b53_arl_rw_op(dev, 0); } From f3affeb3c7d0fcb1ab577590a5a7ce7a36ddad43 Mon Sep 17 00:00:00 2001 From: Jonas Gorski Date: Fri, 7 Nov 2025 09:07:45 +0100 Subject: [PATCH 0875/3902] net: dsa: b53: provide accessors for accessing ARL_SRCH_CTL [ Upstream commit 1716be6db04af53bac9b869f01156a460595cf41 ] In order to more easily support more formats, move accessing ARL_SRCH_CTL into helper functions to contain the differences. Signed-off-by: Jonas Gorski Reviewed-by: Florian Fainelli Link: https://patch.msgid.link/20251107080749.26936-5-jonas.gorski@gmail.com Signed-off-by: Jakub Kicinski Stable-dep-of: 8e46aacea426 ("net: dsa: b53: use same ARL search result offset for BCM5325/65") Signed-off-by: Sasha Levin --- drivers/net/dsa/b53/b53_common.c | 37 +++++++++++++++++++++----------- 1 file changed, 24 insertions(+), 13 deletions(-) diff --git a/drivers/net/dsa/b53/b53_common.c b/drivers/net/dsa/b53/b53_common.c index 4c418e0531f738..38e6fa05042ca8 100644 --- a/drivers/net/dsa/b53/b53_common.c +++ b/drivers/net/dsa/b53/b53_common.c @@ -2017,18 +2017,37 @@ int b53_fdb_del(struct dsa_switch *ds, int port, } EXPORT_SYMBOL(b53_fdb_del); -static int b53_arl_search_wait(struct b53_device *dev) +static void b53_read_arl_srch_ctl(struct b53_device *dev, u8 *val) { - unsigned int timeout = 1000; - u8 reg, offset; + u8 offset; + + if (is5325(dev) || is5365(dev)) + offset = B53_ARL_SRCH_CTL_25; + else + offset = B53_ARL_SRCH_CTL; + + b53_read8(dev, B53_ARLIO_PAGE, offset, val); +} + +static void b53_write_arl_srch_ctl(struct b53_device *dev, u8 val) +{ + u8 offset; if (is5325(dev) || is5365(dev)) offset = B53_ARL_SRCH_CTL_25; else offset = B53_ARL_SRCH_CTL; + b53_write8(dev, B53_ARLIO_PAGE, offset, val); +} + +static int b53_arl_search_wait(struct b53_device *dev) +{ + unsigned int timeout = 1000; + u8 reg; + do { - b53_read8(dev, B53_ARLIO_PAGE, offset, ®); + b53_read_arl_srch_ctl(dev, ®); if (!(reg & ARL_SRCH_STDN)) return -ENOENT; @@ -2083,23 +2102,15 @@ int b53_fdb_dump(struct dsa_switch *ds, int port, unsigned int count = 0, results_per_hit = 1; struct b53_device *priv = ds->priv; struct b53_arl_entry results[2]; - u8 offset; int ret; - u8 reg; if (priv->num_arl_bins > 2) results_per_hit = 2; mutex_lock(&priv->arl_mutex); - if (is5325(priv) || is5365(priv)) - offset = B53_ARL_SRCH_CTL_25; - else - offset = B53_ARL_SRCH_CTL; - /* Start search operation */ - reg = ARL_SRCH_STDN; - b53_write8(priv, B53_ARLIO_PAGE, offset, reg); + b53_write_arl_srch_ctl(priv, ARL_SRCH_STDN); do { ret = b53_arl_search_wait(priv); From e0c63c85e00773c9102c092f92ac75f71b6856c8 Mon Sep 17 00:00:00 2001 From: Jonas Gorski Date: Fri, 7 Nov 2025 09:07:46 +0100 Subject: [PATCH 0876/3902] net: dsa: b53: split reading search entry into their own functions [ Upstream commit e0c476f325a8c9b961a3d446c24d3c8ecae7d186 ] Split reading search entries into a function for each format. Signed-off-by: Jonas Gorski Reviewed-by: Florian Fainelli Link: https://patch.msgid.link/20251107080749.26936-6-jonas.gorski@gmail.com Signed-off-by: Jakub Kicinski Stable-dep-of: 8e46aacea426 ("net: dsa: b53: use same ARL search result offset for BCM5325/65") Signed-off-by: Sasha Levin --- drivers/net/dsa/b53/b53_common.c | 56 ++++++++++++++++++++++---------- 1 file changed, 38 insertions(+), 18 deletions(-) diff --git a/drivers/net/dsa/b53/b53_common.c b/drivers/net/dsa/b53/b53_common.c index 38e6fa05042ca8..190eb11644917d 100644 --- a/drivers/net/dsa/b53/b53_common.c +++ b/drivers/net/dsa/b53/b53_common.c @@ -2060,28 +2060,48 @@ static int b53_arl_search_wait(struct b53_device *dev) return -ETIMEDOUT; } -static void b53_arl_search_rd(struct b53_device *dev, u8 idx, - struct b53_arl_entry *ent) +static void b53_arl_search_read_25(struct b53_device *dev, u8 idx, + struct b53_arl_entry *ent) { u64 mac_vid; - if (is5325(dev)) { - b53_read64(dev, B53_ARLIO_PAGE, B53_ARL_SRCH_RSTL_0_MACVID_25, - &mac_vid); - b53_arl_to_entry_25(ent, mac_vid); - } else if (is5365(dev)) { - b53_read64(dev, B53_ARLIO_PAGE, B53_ARL_SRCH_RSTL_0_MACVID_65, - &mac_vid); - b53_arl_to_entry_25(ent, mac_vid); - } else { - u32 fwd_entry; + b53_read64(dev, B53_ARLIO_PAGE, B53_ARL_SRCH_RSTL_0_MACVID_25, + &mac_vid); + b53_arl_to_entry_25(ent, mac_vid); +} - b53_read64(dev, B53_ARLIO_PAGE, B53_ARL_SRCH_RSTL_MACVID(idx), - &mac_vid); - b53_read32(dev, B53_ARLIO_PAGE, B53_ARL_SRCH_RSTL(idx), - &fwd_entry); - b53_arl_to_entry(ent, mac_vid, fwd_entry); - } +static void b53_arl_search_read_65(struct b53_device *dev, u8 idx, + struct b53_arl_entry *ent) +{ + u64 mac_vid; + + b53_read64(dev, B53_ARLIO_PAGE, B53_ARL_SRCH_RSTL_0_MACVID_65, + &mac_vid); + b53_arl_to_entry_25(ent, mac_vid); +} + +static void b53_arl_search_read_95(struct b53_device *dev, u8 idx, + struct b53_arl_entry *ent) +{ + u32 fwd_entry; + u64 mac_vid; + + b53_read64(dev, B53_ARLIO_PAGE, B53_ARL_SRCH_RSTL_MACVID(idx), + &mac_vid); + b53_read32(dev, B53_ARLIO_PAGE, B53_ARL_SRCH_RSTL(idx), + &fwd_entry); + b53_arl_to_entry(ent, mac_vid, fwd_entry); +} + +static void b53_arl_search_rd(struct b53_device *dev, u8 idx, + struct b53_arl_entry *ent) +{ + if (is5325(dev)) + b53_arl_search_read_25(dev, idx, ent); + else if (is5365(dev)) + b53_arl_search_read_65(dev, idx, ent); + else + b53_arl_search_read_95(dev, idx, ent); } static int b53_fdb_copy(int port, const struct b53_arl_entry *ent, From 4e8f43e63878e45c7efcc508236ae782d32f2e8a Mon Sep 17 00:00:00 2001 From: Jonas Gorski Date: Fri, 7 Nov 2025 09:07:47 +0100 Subject: [PATCH 0877/3902] net: dsa: b53: move ARL entry functions into ops struct [ Upstream commit a7e73339ad46ade76d29fb6cc7d7854222608c26 ] Now that the differences in ARL entry formats are neatly contained into functions per chip family, wrap them into an ops struct and add wrapper functions to access them. Signed-off-by: Jonas Gorski Reviewed-by: Florian Fainelli Link: https://patch.msgid.link/20251107080749.26936-7-jonas.gorski@gmail.com Signed-off-by: Jakub Kicinski Stable-dep-of: 8e46aacea426 ("net: dsa: b53: use same ARL search result offset for BCM5325/65") Signed-off-by: Sasha Levin --- drivers/net/dsa/b53/b53_common.c | 67 ++++++++++++++++++++++---------- drivers/net/dsa/b53/b53_priv.h | 30 ++++++++++++++ 2 files changed, 76 insertions(+), 21 deletions(-) diff --git a/drivers/net/dsa/b53/b53_common.c b/drivers/net/dsa/b53/b53_common.c index 190eb11644917d..50ed9b7157197c 100644 --- a/drivers/net/dsa/b53/b53_common.c +++ b/drivers/net/dsa/b53/b53_common.c @@ -1890,10 +1890,7 @@ static int b53_arl_read(struct b53_device *dev, const u8 *mac, /* Read the bins */ for (i = 0; i < dev->num_arl_bins; i++) { - if (is5325(dev) || is5365(dev)) - b53_arl_read_entry_25(dev, ent, i); - else - b53_arl_read_entry_95(dev, ent, i); + b53_arl_read_entry(dev, ent, i); if (!ent->is_valid) { set_bit(i, free_bins); @@ -1979,10 +1976,7 @@ static int b53_arl_op(struct b53_device *dev, int op, int port, ent.is_static = true; ent.is_age = false; memcpy(ent.mac, addr, ETH_ALEN); - if (is5325(dev) || is5365(dev)) - b53_arl_write_entry_25(dev, &ent, idx); - else - b53_arl_write_entry_95(dev, &ent, idx); + b53_arl_write_entry(dev, &ent, idx); return b53_arl_rw_op(dev, 0); } @@ -2093,17 +2087,6 @@ static void b53_arl_search_read_95(struct b53_device *dev, u8 idx, b53_arl_to_entry(ent, mac_vid, fwd_entry); } -static void b53_arl_search_rd(struct b53_device *dev, u8 idx, - struct b53_arl_entry *ent) -{ - if (is5325(dev)) - b53_arl_search_read_25(dev, idx, ent); - else if (is5365(dev)) - b53_arl_search_read_65(dev, idx, ent); - else - b53_arl_search_read_95(dev, idx, ent); -} - static int b53_fdb_copy(int port, const struct b53_arl_entry *ent, dsa_fdb_dump_cb_t *cb, void *data) { @@ -2137,13 +2120,13 @@ int b53_fdb_dump(struct dsa_switch *ds, int port, if (ret) break; - b53_arl_search_rd(priv, 0, &results[0]); + b53_arl_search_read(priv, 0, &results[0]); ret = b53_fdb_copy(port, &results[0], cb, data); if (ret) break; if (results_per_hit == 2) { - b53_arl_search_rd(priv, 1, &results[1]); + b53_arl_search_read(priv, 1, &results[1]); ret = b53_fdb_copy(port, &results[1], cb, data); if (ret) break; @@ -2669,6 +2652,24 @@ static const struct dsa_switch_ops b53_switch_ops = { .port_change_mtu = b53_change_mtu, }; +static const struct b53_arl_ops b53_arl_ops_25 = { + .arl_read_entry = b53_arl_read_entry_25, + .arl_write_entry = b53_arl_write_entry_25, + .arl_search_read = b53_arl_search_read_25, +}; + +static const struct b53_arl_ops b53_arl_ops_65 = { + .arl_read_entry = b53_arl_read_entry_25, + .arl_write_entry = b53_arl_write_entry_25, + .arl_search_read = b53_arl_search_read_65, +}; + +static const struct b53_arl_ops b53_arl_ops_95 = { + .arl_read_entry = b53_arl_read_entry_95, + .arl_write_entry = b53_arl_write_entry_95, + .arl_search_read = b53_arl_search_read_95, +}; + struct b53_chip_data { u32 chip_id; const char *dev_name; @@ -2682,6 +2683,7 @@ struct b53_chip_data { u8 duplex_reg; u8 jumbo_pm_reg; u8 jumbo_size_reg; + const struct b53_arl_ops *arl_ops; }; #define B53_VTA_REGS \ @@ -2701,6 +2703,7 @@ static const struct b53_chip_data b53_switch_chips[] = { .arl_buckets = 1024, .imp_port = 5, .duplex_reg = B53_DUPLEX_STAT_FE, + .arl_ops = &b53_arl_ops_25, }, { .chip_id = BCM5365_DEVICE_ID, @@ -2711,6 +2714,7 @@ static const struct b53_chip_data b53_switch_chips[] = { .arl_buckets = 1024, .imp_port = 5, .duplex_reg = B53_DUPLEX_STAT_FE, + .arl_ops = &b53_arl_ops_65, }, { .chip_id = BCM5389_DEVICE_ID, @@ -2724,6 +2728,7 @@ static const struct b53_chip_data b53_switch_chips[] = { .duplex_reg = B53_DUPLEX_STAT_GE, .jumbo_pm_reg = B53_JUMBO_PORT_MASK, .jumbo_size_reg = B53_JUMBO_MAX_SIZE, + .arl_ops = &b53_arl_ops_95, }, { .chip_id = BCM5395_DEVICE_ID, @@ -2737,6 +2742,7 @@ static const struct b53_chip_data b53_switch_chips[] = { .duplex_reg = B53_DUPLEX_STAT_GE, .jumbo_pm_reg = B53_JUMBO_PORT_MASK, .jumbo_size_reg = B53_JUMBO_MAX_SIZE, + .arl_ops = &b53_arl_ops_95, }, { .chip_id = BCM5397_DEVICE_ID, @@ -2750,6 +2756,7 @@ static const struct b53_chip_data b53_switch_chips[] = { .duplex_reg = B53_DUPLEX_STAT_GE, .jumbo_pm_reg = B53_JUMBO_PORT_MASK, .jumbo_size_reg = B53_JUMBO_MAX_SIZE, + .arl_ops = &b53_arl_ops_95, }, { .chip_id = BCM5398_DEVICE_ID, @@ -2763,6 +2770,7 @@ static const struct b53_chip_data b53_switch_chips[] = { .duplex_reg = B53_DUPLEX_STAT_GE, .jumbo_pm_reg = B53_JUMBO_PORT_MASK, .jumbo_size_reg = B53_JUMBO_MAX_SIZE, + .arl_ops = &b53_arl_ops_95, }, { .chip_id = BCM53101_DEVICE_ID, @@ -2776,6 +2784,7 @@ static const struct b53_chip_data b53_switch_chips[] = { .duplex_reg = B53_DUPLEX_STAT_GE, .jumbo_pm_reg = B53_JUMBO_PORT_MASK, .jumbo_size_reg = B53_JUMBO_MAX_SIZE, + .arl_ops = &b53_arl_ops_95, }, { .chip_id = BCM53115_DEVICE_ID, @@ -2789,6 +2798,7 @@ static const struct b53_chip_data b53_switch_chips[] = { .duplex_reg = B53_DUPLEX_STAT_GE, .jumbo_pm_reg = B53_JUMBO_PORT_MASK, .jumbo_size_reg = B53_JUMBO_MAX_SIZE, + .arl_ops = &b53_arl_ops_95, }, { .chip_id = BCM53125_DEVICE_ID, @@ -2802,6 +2812,7 @@ static const struct b53_chip_data b53_switch_chips[] = { .duplex_reg = B53_DUPLEX_STAT_GE, .jumbo_pm_reg = B53_JUMBO_PORT_MASK, .jumbo_size_reg = B53_JUMBO_MAX_SIZE, + .arl_ops = &b53_arl_ops_95, }, { .chip_id = BCM53128_DEVICE_ID, @@ -2815,6 +2826,7 @@ static const struct b53_chip_data b53_switch_chips[] = { .duplex_reg = B53_DUPLEX_STAT_GE, .jumbo_pm_reg = B53_JUMBO_PORT_MASK, .jumbo_size_reg = B53_JUMBO_MAX_SIZE, + .arl_ops = &b53_arl_ops_95, }, { .chip_id = BCM63XX_DEVICE_ID, @@ -2828,6 +2840,7 @@ static const struct b53_chip_data b53_switch_chips[] = { .duplex_reg = B53_DUPLEX_STAT_63XX, .jumbo_pm_reg = B53_JUMBO_PORT_MASK_63XX, .jumbo_size_reg = B53_JUMBO_MAX_SIZE_63XX, + .arl_ops = &b53_arl_ops_95, }, { .chip_id = BCM53010_DEVICE_ID, @@ -2841,6 +2854,7 @@ static const struct b53_chip_data b53_switch_chips[] = { .duplex_reg = B53_DUPLEX_STAT_GE, .jumbo_pm_reg = B53_JUMBO_PORT_MASK, .jumbo_size_reg = B53_JUMBO_MAX_SIZE, + .arl_ops = &b53_arl_ops_95, }, { .chip_id = BCM53011_DEVICE_ID, @@ -2854,6 +2868,7 @@ static const struct b53_chip_data b53_switch_chips[] = { .duplex_reg = B53_DUPLEX_STAT_GE, .jumbo_pm_reg = B53_JUMBO_PORT_MASK, .jumbo_size_reg = B53_JUMBO_MAX_SIZE, + .arl_ops = &b53_arl_ops_95, }, { .chip_id = BCM53012_DEVICE_ID, @@ -2867,6 +2882,7 @@ static const struct b53_chip_data b53_switch_chips[] = { .duplex_reg = B53_DUPLEX_STAT_GE, .jumbo_pm_reg = B53_JUMBO_PORT_MASK, .jumbo_size_reg = B53_JUMBO_MAX_SIZE, + .arl_ops = &b53_arl_ops_95, }, { .chip_id = BCM53018_DEVICE_ID, @@ -2880,6 +2896,7 @@ static const struct b53_chip_data b53_switch_chips[] = { .duplex_reg = B53_DUPLEX_STAT_GE, .jumbo_pm_reg = B53_JUMBO_PORT_MASK, .jumbo_size_reg = B53_JUMBO_MAX_SIZE, + .arl_ops = &b53_arl_ops_95, }, { .chip_id = BCM53019_DEVICE_ID, @@ -2893,6 +2910,7 @@ static const struct b53_chip_data b53_switch_chips[] = { .duplex_reg = B53_DUPLEX_STAT_GE, .jumbo_pm_reg = B53_JUMBO_PORT_MASK, .jumbo_size_reg = B53_JUMBO_MAX_SIZE, + .arl_ops = &b53_arl_ops_95, }, { .chip_id = BCM58XX_DEVICE_ID, @@ -2906,6 +2924,7 @@ static const struct b53_chip_data b53_switch_chips[] = { .duplex_reg = B53_DUPLEX_STAT_GE, .jumbo_pm_reg = B53_JUMBO_PORT_MASK, .jumbo_size_reg = B53_JUMBO_MAX_SIZE, + .arl_ops = &b53_arl_ops_95, }, { .chip_id = BCM583XX_DEVICE_ID, @@ -2919,6 +2938,7 @@ static const struct b53_chip_data b53_switch_chips[] = { .duplex_reg = B53_DUPLEX_STAT_GE, .jumbo_pm_reg = B53_JUMBO_PORT_MASK, .jumbo_size_reg = B53_JUMBO_MAX_SIZE, + .arl_ops = &b53_arl_ops_95, }, /* Starfighter 2 */ { @@ -2933,6 +2953,7 @@ static const struct b53_chip_data b53_switch_chips[] = { .duplex_reg = B53_DUPLEX_STAT_GE, .jumbo_pm_reg = B53_JUMBO_PORT_MASK, .jumbo_size_reg = B53_JUMBO_MAX_SIZE, + .arl_ops = &b53_arl_ops_95, }, { .chip_id = BCM7445_DEVICE_ID, @@ -2946,6 +2967,7 @@ static const struct b53_chip_data b53_switch_chips[] = { .duplex_reg = B53_DUPLEX_STAT_GE, .jumbo_pm_reg = B53_JUMBO_PORT_MASK, .jumbo_size_reg = B53_JUMBO_MAX_SIZE, + .arl_ops = &b53_arl_ops_95, }, { .chip_id = BCM7278_DEVICE_ID, @@ -2959,6 +2981,7 @@ static const struct b53_chip_data b53_switch_chips[] = { .duplex_reg = B53_DUPLEX_STAT_GE, .jumbo_pm_reg = B53_JUMBO_PORT_MASK, .jumbo_size_reg = B53_JUMBO_MAX_SIZE, + .arl_ops = &b53_arl_ops_95, }, { .chip_id = BCM53134_DEVICE_ID, @@ -2973,6 +2996,7 @@ static const struct b53_chip_data b53_switch_chips[] = { .duplex_reg = B53_DUPLEX_STAT_GE, .jumbo_pm_reg = B53_JUMBO_PORT_MASK, .jumbo_size_reg = B53_JUMBO_MAX_SIZE, + .arl_ops = &b53_arl_ops_95, }, }; @@ -3001,6 +3025,7 @@ static int b53_switch_init(struct b53_device *dev) dev->num_vlans = chip->vlans; dev->num_arl_bins = chip->arl_bins; dev->num_arl_buckets = chip->arl_buckets; + dev->arl_ops = chip->arl_ops; break; } } diff --git a/drivers/net/dsa/b53/b53_priv.h b/drivers/net/dsa/b53/b53_priv.h index 2f44b3b6a0d9fb..c6e2d5e41c758f 100644 --- a/drivers/net/dsa/b53/b53_priv.h +++ b/drivers/net/dsa/b53/b53_priv.h @@ -58,6 +58,17 @@ struct b53_io_ops { bool link_up); }; +struct b53_arl_entry; + +struct b53_arl_ops { + void (*arl_read_entry)(struct b53_device *dev, + struct b53_arl_entry *ent, u8 idx); + void (*arl_write_entry)(struct b53_device *dev, + const struct b53_arl_entry *ent, u8 idx); + void (*arl_search_read)(struct b53_device *dev, u8 idx, + struct b53_arl_entry *ent); +}; + #define B53_INVALID_LANE 0xff enum { @@ -127,6 +138,7 @@ struct b53_device { struct mutex stats_mutex; struct mutex arl_mutex; const struct b53_io_ops *ops; + const struct b53_arl_ops *arl_ops; /* chip specific data */ u32 chip_id; @@ -371,6 +383,24 @@ static inline void b53_arl_from_entry_25(u64 *mac_vid, *mac_vid |= ARLTBL_AGE_25; } +static inline void b53_arl_read_entry(struct b53_device *dev, + struct b53_arl_entry *ent, u8 idx) +{ + dev->arl_ops->arl_read_entry(dev, ent, idx); +} + +static inline void b53_arl_write_entry(struct b53_device *dev, + const struct b53_arl_entry *ent, u8 idx) +{ + dev->arl_ops->arl_write_entry(dev, ent, idx); +} + +static inline void b53_arl_search_read(struct b53_device *dev, u8 idx, + struct b53_arl_entry *ent) +{ + dev->arl_ops->arl_search_read(dev, idx, ent); +} + #ifdef CONFIG_BCM47XX #include From c15c2c2b72acd25b249a10295088b52ceb8c5e3e Mon Sep 17 00:00:00 2001 From: Jonas Gorski Date: Fri, 7 Nov 2025 09:07:48 +0100 Subject: [PATCH 0878/3902] net: dsa: b53: add support for 5389/5397/5398 ARL entry format [ Upstream commit 300f78e8b6b7be17c2c78afeded75be68acb1aa7 ] BCM5389, BCM5397 and BCM5398 use a different ARL entry format with just a 16 bit fwdentry register, as well as different search control and data offsets. So add appropriate ops for them and switch those chips to use them. Signed-off-by: Jonas Gorski Reviewed-by: Florian Fainelli Link: https://patch.msgid.link/20251107080749.26936-8-jonas.gorski@gmail.com Signed-off-by: Jakub Kicinski Stable-dep-of: 8e46aacea426 ("net: dsa: b53: use same ARL search result offset for BCM5325/65") Signed-off-by: Sasha Levin --- drivers/net/dsa/b53/b53_common.c | 53 ++++++++++++++++++++++++++++++-- drivers/net/dsa/b53/b53_priv.h | 26 ++++++++++++++++ drivers/net/dsa/b53/b53_regs.h | 13 ++++++++ 3 files changed, 89 insertions(+), 3 deletions(-) diff --git a/drivers/net/dsa/b53/b53_common.c b/drivers/net/dsa/b53/b53_common.c index 50ed9b7157197c..dedbd534128714 100644 --- a/drivers/net/dsa/b53/b53_common.c +++ b/drivers/net/dsa/b53/b53_common.c @@ -1850,6 +1850,31 @@ static void b53_arl_write_entry_25(struct b53_device *dev, mac_vid); } +static void b53_arl_read_entry_89(struct b53_device *dev, + struct b53_arl_entry *ent, u8 idx) +{ + u64 mac_vid; + u16 fwd_entry; + + b53_read64(dev, B53_ARLIO_PAGE, B53_ARLTBL_MAC_VID_ENTRY(idx), + &mac_vid); + b53_read16(dev, B53_ARLIO_PAGE, B53_ARLTBL_DATA_ENTRY(idx), &fwd_entry); + b53_arl_to_entry_89(ent, mac_vid, fwd_entry); +} + +static void b53_arl_write_entry_89(struct b53_device *dev, + const struct b53_arl_entry *ent, u8 idx) +{ + u32 fwd_entry; + u64 mac_vid; + + b53_arl_from_entry_89(&mac_vid, &fwd_entry, ent); + b53_write64(dev, B53_ARLIO_PAGE, + B53_ARLTBL_MAC_VID_ENTRY(idx), mac_vid); + b53_write16(dev, B53_ARLIO_PAGE, + B53_ARLTBL_DATA_ENTRY(idx), fwd_entry); +} + static void b53_arl_read_entry_95(struct b53_device *dev, struct b53_arl_entry *ent, u8 idx) { @@ -2017,6 +2042,8 @@ static void b53_read_arl_srch_ctl(struct b53_device *dev, u8 *val) if (is5325(dev) || is5365(dev)) offset = B53_ARL_SRCH_CTL_25; + else if (dev->chip_id == BCM5389_DEVICE_ID || is5397_98(dev)) + offset = B53_ARL_SRCH_CTL_89; else offset = B53_ARL_SRCH_CTL; @@ -2029,6 +2056,8 @@ static void b53_write_arl_srch_ctl(struct b53_device *dev, u8 val) if (is5325(dev) || is5365(dev)) offset = B53_ARL_SRCH_CTL_25; + else if (dev->chip_id == BCM5389_DEVICE_ID || is5397_98(dev)) + offset = B53_ARL_SRCH_CTL_89; else offset = B53_ARL_SRCH_CTL; @@ -2074,6 +2103,18 @@ static void b53_arl_search_read_65(struct b53_device *dev, u8 idx, b53_arl_to_entry_25(ent, mac_vid); } +static void b53_arl_search_read_89(struct b53_device *dev, u8 idx, + struct b53_arl_entry *ent) +{ + u16 fwd_entry; + u64 mac_vid; + + b53_read64(dev, B53_ARLIO_PAGE, B53_ARL_SRCH_RSLT_MACVID_89, + &mac_vid); + b53_read16(dev, B53_ARLIO_PAGE, B53_ARL_SRCH_RSLT_89, &fwd_entry); + b53_arl_to_entry_89(ent, mac_vid, fwd_entry); +} + static void b53_arl_search_read_95(struct b53_device *dev, u8 idx, struct b53_arl_entry *ent) { @@ -2664,6 +2705,12 @@ static const struct b53_arl_ops b53_arl_ops_65 = { .arl_search_read = b53_arl_search_read_65, }; +static const struct b53_arl_ops b53_arl_ops_89 = { + .arl_read_entry = b53_arl_read_entry_89, + .arl_write_entry = b53_arl_write_entry_89, + .arl_search_read = b53_arl_search_read_89, +}; + static const struct b53_arl_ops b53_arl_ops_95 = { .arl_read_entry = b53_arl_read_entry_95, .arl_write_entry = b53_arl_write_entry_95, @@ -2728,7 +2775,7 @@ static const struct b53_chip_data b53_switch_chips[] = { .duplex_reg = B53_DUPLEX_STAT_GE, .jumbo_pm_reg = B53_JUMBO_PORT_MASK, .jumbo_size_reg = B53_JUMBO_MAX_SIZE, - .arl_ops = &b53_arl_ops_95, + .arl_ops = &b53_arl_ops_89, }, { .chip_id = BCM5395_DEVICE_ID, @@ -2756,7 +2803,7 @@ static const struct b53_chip_data b53_switch_chips[] = { .duplex_reg = B53_DUPLEX_STAT_GE, .jumbo_pm_reg = B53_JUMBO_PORT_MASK, .jumbo_size_reg = B53_JUMBO_MAX_SIZE, - .arl_ops = &b53_arl_ops_95, + .arl_ops = &b53_arl_ops_89, }, { .chip_id = BCM5398_DEVICE_ID, @@ -2770,7 +2817,7 @@ static const struct b53_chip_data b53_switch_chips[] = { .duplex_reg = B53_DUPLEX_STAT_GE, .jumbo_pm_reg = B53_JUMBO_PORT_MASK, .jumbo_size_reg = B53_JUMBO_MAX_SIZE, - .arl_ops = &b53_arl_ops_95, + .arl_ops = &b53_arl_ops_89, }, { .chip_id = BCM53101_DEVICE_ID, diff --git a/drivers/net/dsa/b53/b53_priv.h b/drivers/net/dsa/b53/b53_priv.h index c6e2d5e41c758f..127ce7f6b16baa 100644 --- a/drivers/net/dsa/b53/b53_priv.h +++ b/drivers/net/dsa/b53/b53_priv.h @@ -353,6 +353,18 @@ static inline void b53_arl_to_entry_25(struct b53_arl_entry *ent, ent->vid = (mac_vid >> ARLTBL_VID_S_65) & ARLTBL_VID_MASK_25; } +static inline void b53_arl_to_entry_89(struct b53_arl_entry *ent, + u64 mac_vid, u16 fwd_entry) +{ + memset(ent, 0, sizeof(*ent)); + ent->port = fwd_entry & ARLTBL_DATA_PORT_ID_MASK_89; + ent->is_valid = !!(fwd_entry & ARLTBL_VALID_89); + ent->is_age = !!(fwd_entry & ARLTBL_AGE_89); + ent->is_static = !!(fwd_entry & ARLTBL_STATIC_89); + u64_to_ether_addr(mac_vid, ent->mac); + ent->vid = mac_vid >> ARLTBL_VID_S; +} + static inline void b53_arl_from_entry(u64 *mac_vid, u32 *fwd_entry, const struct b53_arl_entry *ent) { @@ -383,6 +395,20 @@ static inline void b53_arl_from_entry_25(u64 *mac_vid, *mac_vid |= ARLTBL_AGE_25; } +static inline void b53_arl_from_entry_89(u64 *mac_vid, u32 *fwd_entry, + const struct b53_arl_entry *ent) +{ + *mac_vid = ether_addr_to_u64(ent->mac); + *mac_vid |= (u64)(ent->vid & ARLTBL_VID_MASK) << ARLTBL_VID_S; + *fwd_entry = ent->port & ARLTBL_DATA_PORT_ID_MASK_89; + if (ent->is_valid) + *fwd_entry |= ARLTBL_VALID_89; + if (ent->is_static) + *fwd_entry |= ARLTBL_STATIC_89; + if (ent->is_age) + *fwd_entry |= ARLTBL_AGE_89; +} + static inline void b53_arl_read_entry(struct b53_device *dev, struct b53_arl_entry *ent, u8 idx) { diff --git a/drivers/net/dsa/b53/b53_regs.h b/drivers/net/dsa/b53/b53_regs.h index 8ce1ce72e93856..d9026cf8655494 100644 --- a/drivers/net/dsa/b53/b53_regs.h +++ b/drivers/net/dsa/b53/b53_regs.h @@ -342,12 +342,20 @@ #define ARLTBL_STATIC BIT(15) #define ARLTBL_VALID BIT(16) +/* BCM5389 ARL Table Data Entry N Register format (16 bit) */ +#define ARLTBL_DATA_PORT_ID_MASK_89 GENMASK(8, 0) +#define ARLTBL_TC_MASK_89 GENMASK(12, 10) +#define ARLTBL_AGE_89 BIT(13) +#define ARLTBL_STATIC_89 BIT(14) +#define ARLTBL_VALID_89 BIT(15) + /* Maximum number of bin entries in the ARL for all switches */ #define B53_ARLTBL_MAX_BIN_ENTRIES 4 /* ARL Search Control Register (8 bit) */ #define B53_ARL_SRCH_CTL 0x50 #define B53_ARL_SRCH_CTL_25 0x20 +#define B53_ARL_SRCH_CTL_89 0x30 #define ARL_SRCH_VLID BIT(0) #define ARL_SRCH_STDN BIT(7) @@ -355,10 +363,12 @@ #define B53_ARL_SRCH_ADDR 0x51 #define B53_ARL_SRCH_ADDR_25 0x22 #define B53_ARL_SRCH_ADDR_65 0x24 +#define B53_ARL_SRCH_ADDR_89 0x31 #define ARL_ADDR_MASK GENMASK(14, 0) /* ARL Search MAC/VID Result (64 bit) */ #define B53_ARL_SRCH_RSTL_0_MACVID 0x60 +#define B53_ARL_SRCH_RSLT_MACVID_89 0x33 /* Single register search result on 5325 */ #define B53_ARL_SRCH_RSTL_0_MACVID_25 0x24 @@ -368,6 +378,9 @@ /* ARL Search Data Result (32 bit) */ #define B53_ARL_SRCH_RSTL_0 0x68 +/* BCM5389 ARL Search Data Result (16 bit) */ +#define B53_ARL_SRCH_RSLT_89 0x3b + #define B53_ARL_SRCH_RSTL_MACVID(x) (B53_ARL_SRCH_RSTL_0_MACVID + ((x) * 0x10)) #define B53_ARL_SRCH_RSTL(x) (B53_ARL_SRCH_RSTL_0 + ((x) * 0x10)) From e23860a4bf1e675f36eb706adc6ce8dc2365b075 Mon Sep 17 00:00:00 2001 From: Jonas Gorski Date: Fri, 28 Nov 2025 09:06:21 +0100 Subject: [PATCH 0879/3902] net: dsa: b53: use same ARL search result offset for BCM5325/65 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [ Upstream commit 8e46aacea4264bcb8d4265fb07577afff58ae78d ] BCM5365's search result is at the same offset as BCM5325's search result, and they (mostly) share the same format, so switch BCM5365 to BCM5325's arl ops. Fixes: c45655386e53 ("net: dsa: b53: add support for FDB operations on 5325/5365") Reviewed-by: Florian Fainelli Tested-by: Álvaro Fernández Rojas Signed-off-by: Jonas Gorski Link: https://patch.msgid.link/20251128080625.27181-4-jonas.gorski@gmail.com Signed-off-by: Jakub Kicinski Signed-off-by: Sasha Levin --- drivers/net/dsa/b53/b53_common.c | 18 +----------------- drivers/net/dsa/b53/b53_regs.h | 4 +--- 2 files changed, 2 insertions(+), 20 deletions(-) diff --git a/drivers/net/dsa/b53/b53_common.c b/drivers/net/dsa/b53/b53_common.c index dedbd534128714..68e9162087ab49 100644 --- a/drivers/net/dsa/b53/b53_common.c +++ b/drivers/net/dsa/b53/b53_common.c @@ -2093,16 +2093,6 @@ static void b53_arl_search_read_25(struct b53_device *dev, u8 idx, b53_arl_to_entry_25(ent, mac_vid); } -static void b53_arl_search_read_65(struct b53_device *dev, u8 idx, - struct b53_arl_entry *ent) -{ - u64 mac_vid; - - b53_read64(dev, B53_ARLIO_PAGE, B53_ARL_SRCH_RSTL_0_MACVID_65, - &mac_vid); - b53_arl_to_entry_25(ent, mac_vid); -} - static void b53_arl_search_read_89(struct b53_device *dev, u8 idx, struct b53_arl_entry *ent) { @@ -2699,12 +2689,6 @@ static const struct b53_arl_ops b53_arl_ops_25 = { .arl_search_read = b53_arl_search_read_25, }; -static const struct b53_arl_ops b53_arl_ops_65 = { - .arl_read_entry = b53_arl_read_entry_25, - .arl_write_entry = b53_arl_write_entry_25, - .arl_search_read = b53_arl_search_read_65, -}; - static const struct b53_arl_ops b53_arl_ops_89 = { .arl_read_entry = b53_arl_read_entry_89, .arl_write_entry = b53_arl_write_entry_89, @@ -2761,7 +2745,7 @@ static const struct b53_chip_data b53_switch_chips[] = { .arl_buckets = 1024, .imp_port = 5, .duplex_reg = B53_DUPLEX_STAT_FE, - .arl_ops = &b53_arl_ops_65, + .arl_ops = &b53_arl_ops_25, }, { .chip_id = BCM5389_DEVICE_ID, diff --git a/drivers/net/dsa/b53/b53_regs.h b/drivers/net/dsa/b53/b53_regs.h index d9026cf8655494..f2a3696d122fa3 100644 --- a/drivers/net/dsa/b53/b53_regs.h +++ b/drivers/net/dsa/b53/b53_regs.h @@ -370,10 +370,8 @@ #define B53_ARL_SRCH_RSTL_0_MACVID 0x60 #define B53_ARL_SRCH_RSLT_MACVID_89 0x33 -/* Single register search result on 5325 */ +/* Single register search result on 5325/5365 */ #define B53_ARL_SRCH_RSTL_0_MACVID_25 0x24 -/* Single register search result on 5365 */ -#define B53_ARL_SRCH_RSTL_0_MACVID_65 0x30 /* ARL Search Data Result (32 bit) */ #define B53_ARL_SRCH_RSTL_0 0x68 From e5a6a2475901d66bcf06778e4c091c4ca927ae62 Mon Sep 17 00:00:00 2001 From: Jonas Gorski Date: Fri, 28 Nov 2025 09:06:22 +0100 Subject: [PATCH 0880/3902] net: dsa: b53: fix CPU port unicast ARL entries for BCM5325/65 [ Upstream commit 85132103f700b1340fc17df8a981509d17bf4872 ] On BCM5325 and BCM5365, unicast ARL entries use 8 as the value for the CPU port, so we need to translate it to/from 5 as used for the CPU port at most other places. Fixes: c45655386e53 ("net: dsa: b53: add support for FDB operations on 5325/5365") Signed-off-by: Jonas Gorski Reviewed-by: Florian Fainelli Link: https://patch.msgid.link/20251128080625.27181-5-jonas.gorski@gmail.com Signed-off-by: Jakub Kicinski Signed-off-by: Sasha Levin --- drivers/net/dsa/b53/b53_priv.h | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/drivers/net/dsa/b53/b53_priv.h b/drivers/net/dsa/b53/b53_priv.h index 127ce7f6b16baa..80e7dd6169b476 100644 --- a/drivers/net/dsa/b53/b53_priv.h +++ b/drivers/net/dsa/b53/b53_priv.h @@ -344,12 +344,14 @@ static inline void b53_arl_to_entry_25(struct b53_arl_entry *ent, u64 mac_vid) { memset(ent, 0, sizeof(*ent)); - ent->port = (mac_vid >> ARLTBL_DATA_PORT_ID_S_25) & - ARLTBL_DATA_PORT_ID_MASK_25; ent->is_valid = !!(mac_vid & ARLTBL_VALID_25); ent->is_age = !!(mac_vid & ARLTBL_AGE_25); ent->is_static = !!(mac_vid & ARLTBL_STATIC_25); u64_to_ether_addr(mac_vid, ent->mac); + ent->port = (mac_vid >> ARLTBL_DATA_PORT_ID_S_25) & + ARLTBL_DATA_PORT_ID_MASK_25; + if (is_unicast_ether_addr(ent->mac) && ent->port == B53_CPU_PORT) + ent->port = B53_CPU_PORT_25; ent->vid = (mac_vid >> ARLTBL_VID_S_65) & ARLTBL_VID_MASK_25; } @@ -383,8 +385,11 @@ static inline void b53_arl_from_entry_25(u64 *mac_vid, const struct b53_arl_entry *ent) { *mac_vid = ether_addr_to_u64(ent->mac); - *mac_vid |= (u64)(ent->port & ARLTBL_DATA_PORT_ID_MASK_25) << - ARLTBL_DATA_PORT_ID_S_25; + if (is_unicast_ether_addr(ent->mac) && ent->port == B53_CPU_PORT_25) + *mac_vid |= (u64)B53_CPU_PORT << ARLTBL_DATA_PORT_ID_S_25; + else + *mac_vid |= (u64)(ent->port & ARLTBL_DATA_PORT_ID_MASK_25) << + ARLTBL_DATA_PORT_ID_S_25; *mac_vid |= (u64)(ent->vid & ARLTBL_VID_MASK_25) << ARLTBL_VID_S_65; if (ent->is_valid) From 8e881dfd5255f7a435e3c31c6123fed35154451a Mon Sep 17 00:00:00 2001 From: Jonas Gorski Date: Fri, 7 Nov 2025 09:07:49 +0100 Subject: [PATCH 0881/3902] net: dsa: b53: add support for bcm63xx ARL entry format [ Upstream commit 2b3013ac03028a2364d8779719bb6bfbc0212435 ] The ARL registers of BCM63XX embedded switches are somewhat unique. The normal ARL table access registers have the same format as BCM5389, but the ARL search registers differ: * SRCH_CTL is at the same offset of BCM5389, but 16 bits wide. It does not have more fields, just needs to be accessed by a 16 bit read. * SRCH_RSLT_MACVID and SRCH_RSLT are aligned to 32 bit, and have shifted offsets. * SRCH_RSLT has a different format than the normal ARL data entry register. * There is only one set of ENTRY_N registers, implying a 1 bin layout. So add appropriate ops for bcm63xx and let it use it. Signed-off-by: Jonas Gorski Reviewed-by: Florian Fainelli Link: https://patch.msgid.link/20251107080749.26936-9-jonas.gorski@gmail.com Signed-off-by: Jakub Kicinski Stable-dep-of: 3b08863469aa ("net: dsa: b53: fix BCM5325/65 ARL entry multicast port masks") Signed-off-by: Sasha Levin --- drivers/net/dsa/b53/b53_common.c | 44 +++++++++++++++++++++++++++----- drivers/net/dsa/b53/b53_priv.h | 15 +++++++++++ drivers/net/dsa/b53/b53_regs.h | 9 +++++++ 3 files changed, 61 insertions(+), 7 deletions(-) diff --git a/drivers/net/dsa/b53/b53_common.c b/drivers/net/dsa/b53/b53_common.c index 68e9162087ab49..db1ed8c9c536e9 100644 --- a/drivers/net/dsa/b53/b53_common.c +++ b/drivers/net/dsa/b53/b53_common.c @@ -2042,12 +2042,20 @@ static void b53_read_arl_srch_ctl(struct b53_device *dev, u8 *val) if (is5325(dev) || is5365(dev)) offset = B53_ARL_SRCH_CTL_25; - else if (dev->chip_id == BCM5389_DEVICE_ID || is5397_98(dev)) + else if (dev->chip_id == BCM5389_DEVICE_ID || is5397_98(dev) || + is63xx(dev)) offset = B53_ARL_SRCH_CTL_89; else offset = B53_ARL_SRCH_CTL; - b53_read8(dev, B53_ARLIO_PAGE, offset, val); + if (is63xx(dev)) { + u16 val16; + + b53_read16(dev, B53_ARLIO_PAGE, offset, &val16); + *val = val16 & 0xff; + } else { + b53_read8(dev, B53_ARLIO_PAGE, offset, val); + } } static void b53_write_arl_srch_ctl(struct b53_device *dev, u8 val) @@ -2056,12 +2064,16 @@ static void b53_write_arl_srch_ctl(struct b53_device *dev, u8 val) if (is5325(dev) || is5365(dev)) offset = B53_ARL_SRCH_CTL_25; - else if (dev->chip_id == BCM5389_DEVICE_ID || is5397_98(dev)) + else if (dev->chip_id == BCM5389_DEVICE_ID || is5397_98(dev) || + is63xx(dev)) offset = B53_ARL_SRCH_CTL_89; else offset = B53_ARL_SRCH_CTL; - b53_write8(dev, B53_ARLIO_PAGE, offset, val); + if (is63xx(dev)) + b53_write16(dev, B53_ARLIO_PAGE, offset, val); + else + b53_write8(dev, B53_ARLIO_PAGE, offset, val); } static int b53_arl_search_wait(struct b53_device *dev) @@ -2105,6 +2117,18 @@ static void b53_arl_search_read_89(struct b53_device *dev, u8 idx, b53_arl_to_entry_89(ent, mac_vid, fwd_entry); } +static void b53_arl_search_read_63xx(struct b53_device *dev, u8 idx, + struct b53_arl_entry *ent) +{ + u16 fwd_entry; + u64 mac_vid; + + b53_read64(dev, B53_ARLIO_PAGE, B53_ARL_SRCH_RSLT_MACVID_63XX, + &mac_vid); + b53_read16(dev, B53_ARLIO_PAGE, B53_ARL_SRCH_RSLT_63XX, &fwd_entry); + b53_arl_search_to_entry_63xx(ent, mac_vid, fwd_entry); +} + static void b53_arl_search_read_95(struct b53_device *dev, u8 idx, struct b53_arl_entry *ent) { @@ -2695,6 +2719,12 @@ static const struct b53_arl_ops b53_arl_ops_89 = { .arl_search_read = b53_arl_search_read_89, }; +static const struct b53_arl_ops b53_arl_ops_63xx = { + .arl_read_entry = b53_arl_read_entry_89, + .arl_write_entry = b53_arl_write_entry_89, + .arl_search_read = b53_arl_search_read_63xx, +}; + static const struct b53_arl_ops b53_arl_ops_95 = { .arl_read_entry = b53_arl_read_entry_95, .arl_write_entry = b53_arl_write_entry_95, @@ -2864,14 +2894,14 @@ static const struct b53_chip_data b53_switch_chips[] = { .dev_name = "BCM63xx", .vlans = 4096, .enabled_ports = 0, /* pdata must provide them */ - .arl_bins = 4, - .arl_buckets = 1024, + .arl_bins = 1, + .arl_buckets = 4096, .imp_port = 8, .vta_regs = B53_VTA_REGS_63XX, .duplex_reg = B53_DUPLEX_STAT_63XX, .jumbo_pm_reg = B53_JUMBO_PORT_MASK_63XX, .jumbo_size_reg = B53_JUMBO_MAX_SIZE_63XX, - .arl_ops = &b53_arl_ops_95, + .arl_ops = &b53_arl_ops_63xx, }, { .chip_id = BCM53010_DEVICE_ID, diff --git a/drivers/net/dsa/b53/b53_priv.h b/drivers/net/dsa/b53/b53_priv.h index 80e7dd6169b476..ae2c615c088ede 100644 --- a/drivers/net/dsa/b53/b53_priv.h +++ b/drivers/net/dsa/b53/b53_priv.h @@ -414,6 +414,21 @@ static inline void b53_arl_from_entry_89(u64 *mac_vid, u32 *fwd_entry, *fwd_entry |= ARLTBL_AGE_89; } +static inline void b53_arl_search_to_entry_63xx(struct b53_arl_entry *ent, + u64 mac_vid, u16 fwd_entry) +{ + memset(ent, 0, sizeof(*ent)); + u64_to_ether_addr(mac_vid, ent->mac); + ent->vid = mac_vid >> ARLTBL_VID_S; + + ent->port = fwd_entry & ARL_SRST_PORT_ID_MASK_63XX; + ent->port >>= 1; + + ent->is_age = !!(fwd_entry & ARL_SRST_AGE_63XX); + ent->is_static = !!(fwd_entry & ARL_SRST_STATIC_63XX); + ent->is_valid = 1; +} + static inline void b53_arl_read_entry(struct b53_device *dev, struct b53_arl_entry *ent, u8 idx) { diff --git a/drivers/net/dsa/b53/b53_regs.h b/drivers/net/dsa/b53/b53_regs.h index f2a3696d122fa3..fcedd5fb00337e 100644 --- a/drivers/net/dsa/b53/b53_regs.h +++ b/drivers/net/dsa/b53/b53_regs.h @@ -364,11 +364,13 @@ #define B53_ARL_SRCH_ADDR_25 0x22 #define B53_ARL_SRCH_ADDR_65 0x24 #define B53_ARL_SRCH_ADDR_89 0x31 +#define B53_ARL_SRCH_ADDR_63XX 0x32 #define ARL_ADDR_MASK GENMASK(14, 0) /* ARL Search MAC/VID Result (64 bit) */ #define B53_ARL_SRCH_RSTL_0_MACVID 0x60 #define B53_ARL_SRCH_RSLT_MACVID_89 0x33 +#define B53_ARL_SRCH_RSLT_MACVID_63XX 0x34 /* Single register search result on 5325/5365 */ #define B53_ARL_SRCH_RSTL_0_MACVID_25 0x24 @@ -382,6 +384,13 @@ #define B53_ARL_SRCH_RSTL_MACVID(x) (B53_ARL_SRCH_RSTL_0_MACVID + ((x) * 0x10)) #define B53_ARL_SRCH_RSTL(x) (B53_ARL_SRCH_RSTL_0 + ((x) * 0x10)) +/* 63XX ARL Search Data Result (16 bit) */ +#define B53_ARL_SRCH_RSLT_63XX 0x3c +#define ARL_SRST_PORT_ID_MASK_63XX GENMASK(9, 1) +#define ARL_SRST_TC_MASK_63XX GENMASK(13, 11) +#define ARL_SRST_AGE_63XX BIT(14) +#define ARL_SRST_STATIC_63XX BIT(15) + /************************************************************************* * IEEE 802.1X Registers *************************************************************************/ From 0d0116335eba7f83fd756d2e301fbc0f64152961 Mon Sep 17 00:00:00 2001 From: Jonas Gorski Date: Fri, 28 Nov 2025 09:06:23 +0100 Subject: [PATCH 0882/3902] net: dsa: b53: fix BCM5325/65 ARL entry multicast port masks [ Upstream commit 3b08863469aa6028ac7c3120966f4e2f6051cf6b ] We currently use the mask 0xf for writing and reading b53_entry::port, but this is only correct for unicast ARL entries. Multicast ARL entries use a bitmask, and 0xf is not enough space for ports > 3, which includes the CPU port. So extend the mask accordingly to also fit port 4 (bit 4) and MII (bit 5). According to the datasheet the multicast port mask is [60:48], making it 12 bit wide, but bits 60-55 are reserved anyway, and collide with the priority field at [60:59], so I am not sure if this is valid. Therefore leave it at the actual used range, [53:48]. The ARL search result register differs a bit, and there the mask is only [52:48], so only spanning the user ports. The MII port bit is contained in the Search Result Extension register. So create a separate search result parse function that properly handles this. Fixes: c45655386e53 ("net: dsa: b53: add support for FDB operations on 5325/5365") Reviewed-by: Florian Fainelli Signed-off-by: Jonas Gorski Link: https://patch.msgid.link/20251128080625.27181-6-jonas.gorski@gmail.com Signed-off-by: Jakub Kicinski Signed-off-by: Sasha Levin --- drivers/net/dsa/b53/b53_common.c | 4 +++- drivers/net/dsa/b53/b53_priv.h | 25 +++++++++++++++++++++---- drivers/net/dsa/b53/b53_regs.h | 8 +++++++- 3 files changed, 31 insertions(+), 6 deletions(-) diff --git a/drivers/net/dsa/b53/b53_common.c b/drivers/net/dsa/b53/b53_common.c index db1ed8c9c536e9..5544e8e9c6446f 100644 --- a/drivers/net/dsa/b53/b53_common.c +++ b/drivers/net/dsa/b53/b53_common.c @@ -2099,10 +2099,12 @@ static void b53_arl_search_read_25(struct b53_device *dev, u8 idx, struct b53_arl_entry *ent) { u64 mac_vid; + u8 ext; + b53_read8(dev, B53_ARLIO_PAGE, B53_ARL_SRCH_RSLT_EXT_25, &ext); b53_read64(dev, B53_ARLIO_PAGE, B53_ARL_SRCH_RSTL_0_MACVID_25, &mac_vid); - b53_arl_to_entry_25(ent, mac_vid); + b53_arl_search_to_entry_25(ent, mac_vid, ext); } static void b53_arl_search_read_89(struct b53_device *dev, u8 idx, diff --git a/drivers/net/dsa/b53/b53_priv.h b/drivers/net/dsa/b53/b53_priv.h index ae2c615c088ede..f4afbfcc345e61 100644 --- a/drivers/net/dsa/b53/b53_priv.h +++ b/drivers/net/dsa/b53/b53_priv.h @@ -348,8 +348,8 @@ static inline void b53_arl_to_entry_25(struct b53_arl_entry *ent, ent->is_age = !!(mac_vid & ARLTBL_AGE_25); ent->is_static = !!(mac_vid & ARLTBL_STATIC_25); u64_to_ether_addr(mac_vid, ent->mac); - ent->port = (mac_vid >> ARLTBL_DATA_PORT_ID_S_25) & - ARLTBL_DATA_PORT_ID_MASK_25; + ent->port = (mac_vid & ARLTBL_DATA_PORT_ID_MASK_25) >> + ARLTBL_DATA_PORT_ID_S_25; if (is_unicast_ether_addr(ent->mac) && ent->port == B53_CPU_PORT) ent->port = B53_CPU_PORT_25; ent->vid = (mac_vid >> ARLTBL_VID_S_65) & ARLTBL_VID_MASK_25; @@ -388,8 +388,8 @@ static inline void b53_arl_from_entry_25(u64 *mac_vid, if (is_unicast_ether_addr(ent->mac) && ent->port == B53_CPU_PORT_25) *mac_vid |= (u64)B53_CPU_PORT << ARLTBL_DATA_PORT_ID_S_25; else - *mac_vid |= (u64)(ent->port & ARLTBL_DATA_PORT_ID_MASK_25) << - ARLTBL_DATA_PORT_ID_S_25; + *mac_vid |= ((u64)ent->port << ARLTBL_DATA_PORT_ID_S_25) & + ARLTBL_DATA_PORT_ID_MASK_25; *mac_vid |= (u64)(ent->vid & ARLTBL_VID_MASK_25) << ARLTBL_VID_S_65; if (ent->is_valid) @@ -414,6 +414,23 @@ static inline void b53_arl_from_entry_89(u64 *mac_vid, u32 *fwd_entry, *fwd_entry |= ARLTBL_AGE_89; } +static inline void b53_arl_search_to_entry_25(struct b53_arl_entry *ent, + u64 mac_vid, u8 ext) +{ + memset(ent, 0, sizeof(*ent)); + ent->is_valid = !!(mac_vid & ARLTBL_VALID_25); + ent->is_age = !!(mac_vid & ARLTBL_AGE_25); + ent->is_static = !!(mac_vid & ARLTBL_STATIC_25); + u64_to_ether_addr(mac_vid, ent->mac); + ent->vid = (mac_vid >> ARLTBL_VID_S_65) & ARLTBL_VID_MASK_25; + ent->port = (mac_vid & ARL_SRCH_RSLT_PORT_ID_MASK_25) >> + ARL_SRCH_RSLT_PORT_ID_S_25; + if (is_multicast_ether_addr(ent->mac) && (ext & ARL_SRCH_RSLT_EXT_MC_MII)) + ent->port |= BIT(B53_CPU_PORT_25); + else if (!is_multicast_ether_addr(ent->mac) && ent->port == B53_CPU_PORT) + ent->port = B53_CPU_PORT_25; +} + static inline void b53_arl_search_to_entry_63xx(struct b53_arl_entry *ent, u64 mac_vid, u16 fwd_entry) { diff --git a/drivers/net/dsa/b53/b53_regs.h b/drivers/net/dsa/b53/b53_regs.h index fcedd5fb00337e..e0379e70900b7f 100644 --- a/drivers/net/dsa/b53/b53_regs.h +++ b/drivers/net/dsa/b53/b53_regs.h @@ -328,7 +328,7 @@ #define ARLTBL_VID_MASK_25 0xff #define ARLTBL_VID_MASK 0xfff #define ARLTBL_DATA_PORT_ID_S_25 48 -#define ARLTBL_DATA_PORT_ID_MASK_25 0xf +#define ARLTBL_DATA_PORT_ID_MASK_25 GENMASK_ULL(53, 48) #define ARLTBL_VID_S_65 53 #define ARLTBL_AGE_25 BIT_ULL(61) #define ARLTBL_STATIC_25 BIT_ULL(62) @@ -374,6 +374,12 @@ /* Single register search result on 5325/5365 */ #define B53_ARL_SRCH_RSTL_0_MACVID_25 0x24 +#define ARL_SRCH_RSLT_PORT_ID_S_25 48 +#define ARL_SRCH_RSLT_PORT_ID_MASK_25 GENMASK_ULL(52, 48) + +/* BCM5325/5365 Search result extend register (8 bit) */ +#define B53_ARL_SRCH_RSLT_EXT_25 0x2c +#define ARL_SRCH_RSLT_EXT_MC_MII BIT(2) /* ARL Search Data Result (32 bit) */ #define B53_ARL_SRCH_RSTL_0 0x68 From b07e2f191d88d3007d53495b82fbd2d23e8273c4 Mon Sep 17 00:00:00 2001 From: Jonas Gorski Date: Fri, 28 Nov 2025 09:06:24 +0100 Subject: [PATCH 0883/3902] net: dsa: b53: fix BCM5325/65 ARL entry VIDs [ Upstream commit d39514e6a2d14f57830d649e2bf03b49612c2f73 ] BCM5325/65's ARL entry registers do not contain the VID, only the search result register does. ARL entries have a separate VID entry register for the index into the VLAN table. So make ARL entry accessors use the VID entry registers instead, and move the VLAN ID field definition to the search register definition. Fixes: c45655386e53 ("net: dsa: b53: add support for FDB operations on 5325/5365") Signed-off-by: Jonas Gorski Reviewed-by: Florian Fainelli Link: https://patch.msgid.link/20251128080625.27181-7-jonas.gorski@gmail.com Signed-off-by: Jakub Kicinski Signed-off-by: Sasha Levin --- drivers/net/dsa/b53/b53_common.c | 9 +++++++-- drivers/net/dsa/b53/b53_priv.h | 12 ++++++------ drivers/net/dsa/b53/b53_regs.h | 7 +++++-- 3 files changed, 18 insertions(+), 10 deletions(-) diff --git a/drivers/net/dsa/b53/b53_common.c b/drivers/net/dsa/b53/b53_common.c index 5544e8e9c6446f..62cafced758e77 100644 --- a/drivers/net/dsa/b53/b53_common.c +++ b/drivers/net/dsa/b53/b53_common.c @@ -1833,19 +1833,24 @@ static int b53_arl_rw_op(struct b53_device *dev, unsigned int op) static void b53_arl_read_entry_25(struct b53_device *dev, struct b53_arl_entry *ent, u8 idx) { + u8 vid_entry; u64 mac_vid; + b53_read8(dev, B53_ARLIO_PAGE, B53_ARLTBL_VID_ENTRY_25(idx), + &vid_entry); b53_read64(dev, B53_ARLIO_PAGE, B53_ARLTBL_MAC_VID_ENTRY(idx), &mac_vid); - b53_arl_to_entry_25(ent, mac_vid); + b53_arl_to_entry_25(ent, mac_vid, vid_entry); } static void b53_arl_write_entry_25(struct b53_device *dev, const struct b53_arl_entry *ent, u8 idx) { + u8 vid_entry; u64 mac_vid; - b53_arl_from_entry_25(&mac_vid, ent); + b53_arl_from_entry_25(&mac_vid, &vid_entry, ent); + b53_write8(dev, B53_ARLIO_PAGE, B53_ARLTBL_VID_ENTRY_25(idx), vid_entry); b53_write64(dev, B53_ARLIO_PAGE, B53_ARLTBL_MAC_VID_ENTRY(idx), mac_vid); } diff --git a/drivers/net/dsa/b53/b53_priv.h b/drivers/net/dsa/b53/b53_priv.h index f4afbfcc345e61..bd6849e5bb9390 100644 --- a/drivers/net/dsa/b53/b53_priv.h +++ b/drivers/net/dsa/b53/b53_priv.h @@ -341,7 +341,7 @@ static inline void b53_arl_to_entry(struct b53_arl_entry *ent, } static inline void b53_arl_to_entry_25(struct b53_arl_entry *ent, - u64 mac_vid) + u64 mac_vid, u8 vid_entry) { memset(ent, 0, sizeof(*ent)); ent->is_valid = !!(mac_vid & ARLTBL_VALID_25); @@ -352,7 +352,7 @@ static inline void b53_arl_to_entry_25(struct b53_arl_entry *ent, ARLTBL_DATA_PORT_ID_S_25; if (is_unicast_ether_addr(ent->mac) && ent->port == B53_CPU_PORT) ent->port = B53_CPU_PORT_25; - ent->vid = (mac_vid >> ARLTBL_VID_S_65) & ARLTBL_VID_MASK_25; + ent->vid = vid_entry; } static inline void b53_arl_to_entry_89(struct b53_arl_entry *ent, @@ -381,7 +381,7 @@ static inline void b53_arl_from_entry(u64 *mac_vid, u32 *fwd_entry, *fwd_entry |= ARLTBL_AGE; } -static inline void b53_arl_from_entry_25(u64 *mac_vid, +static inline void b53_arl_from_entry_25(u64 *mac_vid, u8 *vid_entry, const struct b53_arl_entry *ent) { *mac_vid = ether_addr_to_u64(ent->mac); @@ -390,14 +390,13 @@ static inline void b53_arl_from_entry_25(u64 *mac_vid, else *mac_vid |= ((u64)ent->port << ARLTBL_DATA_PORT_ID_S_25) & ARLTBL_DATA_PORT_ID_MASK_25; - *mac_vid |= (u64)(ent->vid & ARLTBL_VID_MASK_25) << - ARLTBL_VID_S_65; if (ent->is_valid) *mac_vid |= ARLTBL_VALID_25; if (ent->is_static) *mac_vid |= ARLTBL_STATIC_25; if (ent->is_age) *mac_vid |= ARLTBL_AGE_25; + *vid_entry = ent->vid; } static inline void b53_arl_from_entry_89(u64 *mac_vid, u32 *fwd_entry, @@ -422,7 +421,8 @@ static inline void b53_arl_search_to_entry_25(struct b53_arl_entry *ent, ent->is_age = !!(mac_vid & ARLTBL_AGE_25); ent->is_static = !!(mac_vid & ARLTBL_STATIC_25); u64_to_ether_addr(mac_vid, ent->mac); - ent->vid = (mac_vid >> ARLTBL_VID_S_65) & ARLTBL_VID_MASK_25; + ent->vid = (mac_vid & ARL_SRCH_RSLT_VID_MASK_25) >> + ARL_SRCH_RSLT_VID_S_25; ent->port = (mac_vid & ARL_SRCH_RSLT_PORT_ID_MASK_25) >> ARL_SRCH_RSLT_PORT_ID_S_25; if (is_multicast_ether_addr(ent->mac) && (ext & ARL_SRCH_RSLT_EXT_MC_MII)) diff --git a/drivers/net/dsa/b53/b53_regs.h b/drivers/net/dsa/b53/b53_regs.h index e0379e70900b7f..b6fe7d207a2c13 100644 --- a/drivers/net/dsa/b53/b53_regs.h +++ b/drivers/net/dsa/b53/b53_regs.h @@ -325,11 +325,9 @@ #define B53_ARLTBL_MAC_VID_ENTRY(n) ((0x10 * (n)) + 0x10) #define ARLTBL_MAC_MASK 0xffffffffffffULL #define ARLTBL_VID_S 48 -#define ARLTBL_VID_MASK_25 0xff #define ARLTBL_VID_MASK 0xfff #define ARLTBL_DATA_PORT_ID_S_25 48 #define ARLTBL_DATA_PORT_ID_MASK_25 GENMASK_ULL(53, 48) -#define ARLTBL_VID_S_65 53 #define ARLTBL_AGE_25 BIT_ULL(61) #define ARLTBL_STATIC_25 BIT_ULL(62) #define ARLTBL_VALID_25 BIT_ULL(63) @@ -349,6 +347,9 @@ #define ARLTBL_STATIC_89 BIT(14) #define ARLTBL_VALID_89 BIT(15) +/* BCM5325/BCM565 ARL Table VID Entry N Registers (8 bit) */ +#define B53_ARLTBL_VID_ENTRY_25(n) ((0x2 * (n)) + 0x30) + /* Maximum number of bin entries in the ARL for all switches */ #define B53_ARLTBL_MAX_BIN_ENTRIES 4 @@ -376,6 +377,8 @@ #define B53_ARL_SRCH_RSTL_0_MACVID_25 0x24 #define ARL_SRCH_RSLT_PORT_ID_S_25 48 #define ARL_SRCH_RSLT_PORT_ID_MASK_25 GENMASK_ULL(52, 48) +#define ARL_SRCH_RSLT_VID_S_25 53 +#define ARL_SRCH_RSLT_VID_MASK_25 GENMASK_ULL(60, 53) /* BCM5325/5365 Search result extend register (8 bit) */ #define B53_ARL_SRCH_RSLT_EXT_25 0x2c From 4a7f375f625d466c8305585f9201abda90006174 Mon Sep 17 00:00:00 2001 From: Xiaoliang Yang Date: Sun, 30 Nov 2025 15:16:44 +0200 Subject: [PATCH 0884/3902] net: hsr: create an API to get hsr port type MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [ Upstream commit a0244e76213980f3b9bb5d40b0b6705fcf24230d ] Since the introduction of HSR_PT_INTERLINK in commit 5055cccfc2d1 ("net: hsr: Provide RedBox support (HSR-SAN)"), we see that different port types require different settings for hardware offload, which was not the case before when we only had HSR_PT_SLAVE_A and HSR_PT_SLAVE_B. But there is currently no way to know which port is which type, so create the hsr_get_port_type() API function and export it. When hsr_get_port_type() is called from the device driver, the port can must be found in the HSR port list. An important use case is for this function to work from offloading drivers' NETDEV_CHANGEUPPER handler, which is triggered by hsr_portdev_setup() -> netdev_master_upper_dev_link(). Therefore, we need to move the addition of the hsr_port to the HSR port list prior to calling hsr_portdev_setup(). This makes the error restoration path also more similar to hsr_del_port(), where kfree_rcu(port) is already used. Cc: Sebastian Andrzej Siewior Cc: Lukasz Majewski Signed-off-by: Xiaoliang Yang Signed-off-by: Vladimir Oltean Reviewed-by: Łukasz Majewski Link: https://patch.msgid.link/20251130131657.65080-3-vladimir.oltean@nxp.com Signed-off-by: Jakub Kicinski Stable-dep-of: 30296ac76426 ("net: dsa: xrs700x: reject unsupported HSR configurations") Signed-off-by: Sasha Levin --- include/linux/if_hsr.h | 9 +++++++++ net/hsr/hsr_device.c | 20 ++++++++++++++++++++ net/hsr/hsr_slave.c | 7 ++++--- 3 files changed, 33 insertions(+), 3 deletions(-) diff --git a/include/linux/if_hsr.h b/include/linux/if_hsr.h index d7941fd8803295..f4cf2dd36d193d 100644 --- a/include/linux/if_hsr.h +++ b/include/linux/if_hsr.h @@ -43,6 +43,8 @@ extern bool is_hsr_master(struct net_device *dev); extern int hsr_get_version(struct net_device *dev, enum hsr_version *ver); struct net_device *hsr_get_port_ndev(struct net_device *ndev, enum hsr_port_type pt); +int hsr_get_port_type(struct net_device *hsr_dev, struct net_device *dev, + enum hsr_port_type *type); #else static inline bool is_hsr_master(struct net_device *dev) { @@ -59,6 +61,13 @@ static inline struct net_device *hsr_get_port_ndev(struct net_device *ndev, { return ERR_PTR(-EINVAL); } + +static inline int hsr_get_port_type(struct net_device *hsr_dev, + struct net_device *dev, + enum hsr_port_type *type) +{ + return -EINVAL; +} #endif /* CONFIG_HSR */ #endif /*_LINUX_IF_HSR_H_*/ diff --git a/net/hsr/hsr_device.c b/net/hsr/hsr_device.c index 492cbc78ab75a0..d1bfc49b5f017b 100644 --- a/net/hsr/hsr_device.c +++ b/net/hsr/hsr_device.c @@ -690,6 +690,26 @@ struct net_device *hsr_get_port_ndev(struct net_device *ndev, } EXPORT_SYMBOL(hsr_get_port_ndev); +int hsr_get_port_type(struct net_device *hsr_dev, struct net_device *dev, + enum hsr_port_type *type) +{ + struct hsr_priv *hsr = netdev_priv(hsr_dev); + struct hsr_port *port; + + rcu_read_lock(); + hsr_for_each_port(hsr, port) { + if (port->dev == dev) { + *type = port->type; + rcu_read_unlock(); + return 0; + } + } + rcu_read_unlock(); + + return -EINVAL; +} +EXPORT_SYMBOL(hsr_get_port_type); + /* Default multicast address for HSR Supervision frames */ static const unsigned char def_multicast_addr[ETH_ALEN] __aligned(2) = { 0x01, 0x15, 0x4e, 0x00, 0x01, 0x00 diff --git a/net/hsr/hsr_slave.c b/net/hsr/hsr_slave.c index 8177ac6c2d26de..afe06ba00ea447 100644 --- a/net/hsr/hsr_slave.c +++ b/net/hsr/hsr_slave.c @@ -207,14 +207,14 @@ int hsr_add_port(struct hsr_priv *hsr, struct net_device *dev, port->type = type; ether_addr_copy(port->original_macaddress, dev->dev_addr); + list_add_tail_rcu(&port->port_list, &hsr->ports); + if (type != HSR_PT_MASTER) { res = hsr_portdev_setup(hsr, dev, port, extack); if (res) goto fail_dev_setup; } - list_add_tail_rcu(&port->port_list, &hsr->ports); - master = hsr_port_get_hsr(hsr, HSR_PT_MASTER); netdev_update_features(master->dev); dev_set_mtu(master->dev, hsr_get_max_mtu(hsr)); @@ -222,7 +222,8 @@ int hsr_add_port(struct hsr_priv *hsr, struct net_device *dev, return 0; fail_dev_setup: - kfree(port); + list_del_rcu(&port->port_list); + kfree_rcu(port, rcu); return res; } From 1ce413fd4d0459a6e42a6efbeba634c1072bc00f Mon Sep 17 00:00:00 2001 From: Vladimir Oltean Date: Sun, 30 Nov 2025 15:16:46 +0200 Subject: [PATCH 0885/3902] net: dsa: xrs700x: reject unsupported HSR configurations [ Upstream commit 30296ac7642652428396222e720718f2661e9425 ] As discussed here: https://lore.kernel.org/netdev/20240620090210.drop6jwh7e5qw556@skbuf/ the fact is that the xrs700x.c driver only supports offloading HSR_PT_SLAVE_A and HSR_PT_SLAVE_B (which were the only port types at the time the offload was written, _for this driver_). Up until now, the API did not explicitly tell offloading drivers what port has what role. So xrs700x can get confused and think that it can support a configuration which it actually can't. There was a table in the attached link which gave an example: $ ip link add name hsr0 type hsr slave1 swp0 slave2 swp1 \ interlink swp2 supervision 45 version 1 HSR_PT_SLAVE_A HSR_PT_SLAVE_B HSR_PT_INTERLINK ---------------------------------------------------------------- user space 0 1 2 requests ---------------------------------------------------------------- XRS700X driver 1 2 - understands The switch would act as if the ring ports were swp1 and swp2. Now that we have explicit hsr_get_port_type() API, let's use that to work around the unintended semantical changes of the offloading API brought by the introduction of interlink ports in HSR. Fixes: 5055cccfc2d1 ("net: hsr: Provide RedBox support (HSR-SAN)") Cc: Lukasz Majewski Signed-off-by: Vladimir Oltean Reviewed-by: George McCollister Link: https://patch.msgid.link/20251130131657.65080-5-vladimir.oltean@nxp.com Signed-off-by: Jakub Kicinski Signed-off-by: Sasha Levin --- drivers/net/dsa/xrs700x/xrs700x.c | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/drivers/net/dsa/xrs700x/xrs700x.c b/drivers/net/dsa/xrs700x/xrs700x.c index 4dbcc49a9e526f..0a05f4156ef4d7 100644 --- a/drivers/net/dsa/xrs700x/xrs700x.c +++ b/drivers/net/dsa/xrs700x/xrs700x.c @@ -566,6 +566,7 @@ static int xrs700x_hsr_join(struct dsa_switch *ds, int port, struct xrs700x *priv = ds->priv; struct net_device *user; int ret, i, hsr_pair[2]; + enum hsr_port_type type; enum hsr_version ver; bool fwd = false; @@ -589,6 +590,16 @@ static int xrs700x_hsr_join(struct dsa_switch *ds, int port, return -EOPNOTSUPP; } + ret = hsr_get_port_type(hsr, dsa_to_port(ds, port)->user, &type); + if (ret) + return ret; + + if (type != HSR_PT_SLAVE_A && type != HSR_PT_SLAVE_B) { + NL_SET_ERR_MSG_MOD(extack, + "Only HSR slave ports can be offloaded"); + return -EOPNOTSUPP; + } + dsa_hsr_foreach_port(dp, ds, hsr) { if (dp->index != port) { partner = dp; From 3ed6c458530a547ed0c9ea0b02b19bab620be88b Mon Sep 17 00:00:00 2001 From: Xiang Mei Date: Thu, 27 Nov 2025 17:14:14 -0700 Subject: [PATCH 0886/3902] net/sched: sch_cake: Fix incorrect qlen reduction in cake_drop MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [ Upstream commit 9fefc78f7f02d71810776fdeb119a05a946a27cc ] In cake_drop(), qdisc_tree_reduce_backlog() is used to update the qlen and backlog of the qdisc hierarchy. Its caller, cake_enqueue(), assumes that the parent qdisc will enqueue the current packet. However, this assumption breaks when cake_enqueue() returns NET_XMIT_CN: the parent qdisc stops enqueuing current packet, leaving the tree qlen/backlog accounting inconsistent. This mismatch can lead to a NULL dereference (e.g., when the parent Qdisc is qfq_qdisc). This patch computes the qlen/backlog delta in a more robust way by observing the difference before and after the series of cake_drop() calls, and then compensates the qdisc tree accounting if cake_enqueue() returns NET_XMIT_CN. To ensure correct compensation when ACK thinning is enabled, a new variable is introduced to keep qlen unchanged. Fixes: 15de71d06a40 ("net/sched: Make cake_enqueue return NET_XMIT_CN when past buffer_limit") Signed-off-by: Xiang Mei Reviewed-by: Toke Høiland-Jørgensen Link: https://patch.msgid.link/20251128001415.377823-1-xmei5@asu.edu Signed-off-by: Paolo Abeni Signed-off-by: Sasha Levin --- net/sched/sch_cake.c | 58 ++++++++++++++++++++++++-------------------- 1 file changed, 32 insertions(+), 26 deletions(-) diff --git a/net/sched/sch_cake.c b/net/sched/sch_cake.c index 32bacfc314c260..d325a90cde9ee6 100644 --- a/net/sched/sch_cake.c +++ b/net/sched/sch_cake.c @@ -1597,7 +1597,6 @@ static unsigned int cake_drop(struct Qdisc *sch, struct sk_buff **to_free) qdisc_drop_reason(skb, sch, to_free, SKB_DROP_REASON_QDISC_OVERLIMIT); sch->q.qlen--; - qdisc_tree_reduce_backlog(sch, 1, len); cake_heapify(q, 0); @@ -1743,14 +1742,14 @@ static void cake_reconfigure(struct Qdisc *sch); static s32 cake_enqueue(struct sk_buff *skb, struct Qdisc *sch, struct sk_buff **to_free) { + u32 idx, tin, prev_qlen, prev_backlog, drop_id; struct cake_sched_data *q = qdisc_priv(sch); - int len = qdisc_pkt_len(skb); - int ret; + int len = qdisc_pkt_len(skb), ret; struct sk_buff *ack = NULL; ktime_t now = ktime_get(); struct cake_tin_data *b; struct cake_flow *flow; - u32 idx, tin; + bool same_flow = false; /* choose flow to insert into */ idx = cake_classify(sch, &b, skb, q->flow_mode, &ret); @@ -1823,6 +1822,8 @@ static s32 cake_enqueue(struct sk_buff *skb, struct Qdisc *sch, consume_skb(skb); } else { /* not splitting */ + int ack_pkt_len = 0; + cobalt_set_enqueue_time(skb, now); get_cobalt_cb(skb)->adjusted_len = cake_overhead(q, skb); flow_queue_add(flow, skb); @@ -1833,13 +1834,13 @@ static s32 cake_enqueue(struct sk_buff *skb, struct Qdisc *sch, if (ack) { b->ack_drops++; sch->qstats.drops++; - b->bytes += qdisc_pkt_len(ack); - len -= qdisc_pkt_len(ack); + ack_pkt_len = qdisc_pkt_len(ack); + b->bytes += ack_pkt_len; q->buffer_used += skb->truesize - ack->truesize; if (q->rate_flags & CAKE_FLAG_INGRESS) cake_advance_shaper(q, b, ack, now, true); - qdisc_tree_reduce_backlog(sch, 1, qdisc_pkt_len(ack)); + qdisc_tree_reduce_backlog(sch, 1, ack_pkt_len); consume_skb(ack); } else { sch->q.qlen++; @@ -1848,11 +1849,11 @@ static s32 cake_enqueue(struct sk_buff *skb, struct Qdisc *sch, /* stats */ b->packets++; - b->bytes += len; - b->backlogs[idx] += len; - b->tin_backlog += len; - sch->qstats.backlog += len; - q->avg_window_bytes += len; + b->bytes += len - ack_pkt_len; + b->backlogs[idx] += len - ack_pkt_len; + b->tin_backlog += len - ack_pkt_len; + sch->qstats.backlog += len - ack_pkt_len; + q->avg_window_bytes += len - ack_pkt_len; } if (q->overflow_timeout) @@ -1927,24 +1928,29 @@ static s32 cake_enqueue(struct sk_buff *skb, struct Qdisc *sch, if (q->buffer_used > q->buffer_max_used) q->buffer_max_used = q->buffer_used; - if (q->buffer_used > q->buffer_limit) { - bool same_flow = false; - u32 dropped = 0; - u32 drop_id; + if (q->buffer_used <= q->buffer_limit) + return NET_XMIT_SUCCESS; - while (q->buffer_used > q->buffer_limit) { - dropped++; - drop_id = cake_drop(sch, to_free); + prev_qlen = sch->q.qlen; + prev_backlog = sch->qstats.backlog; - if ((drop_id >> 16) == tin && - (drop_id & 0xFFFF) == idx) - same_flow = true; - } - b->drop_overlimit += dropped; + while (q->buffer_used > q->buffer_limit) { + drop_id = cake_drop(sch, to_free); + if ((drop_id >> 16) == tin && + (drop_id & 0xFFFF) == idx) + same_flow = true; + } + + prev_qlen -= sch->q.qlen; + prev_backlog -= sch->qstats.backlog; + b->drop_overlimit += prev_qlen; - if (same_flow) - return NET_XMIT_CN; + if (same_flow) { + qdisc_tree_reduce_backlog(sch, prev_qlen - 1, + prev_backlog - len); + return NET_XMIT_CN; } + qdisc_tree_reduce_backlog(sch, prev_qlen, prev_backlog); return NET_XMIT_SUCCESS; } From bd4da8f59dbbccee0841479e23481bc3f3f2e696 Mon Sep 17 00:00:00 2001 From: Namhyung Kim Date: Tue, 25 Nov 2025 00:07:46 -0800 Subject: [PATCH 0887/3902] perf jitdump: Add sym/str-tables to build-ID generation [ Upstream commit 25d498e636d1f8d138d65246cfb5b1fc3069ca56 ] It was reported that python backtrace with JIT dump was broken after the change to built-in SHA-1 implementation. It seems python generates the same JIT code for each function. They will become separate DSOs but the contents are the same. Only difference is in the symbol name. But this caused a problem that every JIT'ed DSOs will have the same build-ID which makes perf confused. And it resulted in no python symbols (from JIT) in the output. Looking back at the original code before the conversion, it used the load_addr as well as the code section to distinguish each DSO. But it'd be better to use contents of symtab and strtab instead as it aligns with some linker behaviors. This patch adds a buffer to save all the contents in a single place for SHA-1 calculation. Probably we need to add sha1_update() or similar to update the existing hash value with different contents and use it here. But it's out of scope for this change and I'd like something that can be backported to the stable trees easily. Reviewed-by: Ian Rogers Cc: Eric Biggers Cc: Pablo Galindo Cc: Fangrui Song Link: https://github.com/python/cpython/issues/139544 Fixes: e3f612c1d8f3945b ("perf genelf: Remove libcrypto dependency and use built-in sha1()") Signed-off-by: Namhyung Kim Signed-off-by: Sasha Levin --- tools/perf/util/genelf.c | 32 ++++++++++++++++++++++++++++++-- 1 file changed, 30 insertions(+), 2 deletions(-) diff --git a/tools/perf/util/genelf.c b/tools/perf/util/genelf.c index 591548b10e34ef..a1cd5196f4ec8f 100644 --- a/tools/perf/util/genelf.c +++ b/tools/perf/util/genelf.c @@ -173,6 +173,8 @@ jit_write_elf(int fd, uint64_t load_addr __maybe_unused, const char *sym, Elf_Shdr *shdr; uint64_t eh_frame_base_offset; char *strsym = NULL; + void *build_id_data = NULL, *tmp; + int build_id_data_len; int symlen; int retval = -1; @@ -251,6 +253,14 @@ jit_write_elf(int fd, uint64_t load_addr __maybe_unused, const char *sym, shdr->sh_flags = SHF_EXECINSTR | SHF_ALLOC; shdr->sh_entsize = 0; + build_id_data = malloc(csize); + if (build_id_data == NULL) { + warnx("cannot allocate build-id data"); + goto error; + } + memcpy(build_id_data, code, csize); + build_id_data_len = csize; + /* * Setup .eh_frame_hdr and .eh_frame */ @@ -334,6 +344,15 @@ jit_write_elf(int fd, uint64_t load_addr __maybe_unused, const char *sym, shdr->sh_entsize = sizeof(Elf_Sym); shdr->sh_link = unwinding ? 6 : 4; /* index of .strtab section */ + tmp = realloc(build_id_data, build_id_data_len + sizeof(symtab)); + if (tmp == NULL) { + warnx("cannot allocate build-id data"); + goto error; + } + memcpy(tmp + build_id_data_len, symtab, sizeof(symtab)); + build_id_data = tmp; + build_id_data_len += sizeof(symtab); + /* * setup symbols string table * 2 = 1 for 0 in 1st entry, 1 for the 0 at end of symbol for 2nd entry @@ -376,6 +395,15 @@ jit_write_elf(int fd, uint64_t load_addr __maybe_unused, const char *sym, shdr->sh_flags = 0; shdr->sh_entsize = 0; + tmp = realloc(build_id_data, build_id_data_len + symlen); + if (tmp == NULL) { + warnx("cannot allocate build-id data"); + goto error; + } + memcpy(tmp + build_id_data_len, strsym, symlen); + build_id_data = tmp; + build_id_data_len += symlen; + /* * setup build-id section */ @@ -394,7 +422,7 @@ jit_write_elf(int fd, uint64_t load_addr __maybe_unused, const char *sym, /* * build-id generation */ - sha1(code, csize, bnote.build_id); + sha1(build_id_data, build_id_data_len, bnote.build_id); bnote.desc.namesz = sizeof(bnote.name); /* must include 0 termination */ bnote.desc.descsz = sizeof(bnote.build_id); bnote.desc.type = NT_GNU_BUILD_ID; @@ -439,7 +467,7 @@ jit_write_elf(int fd, uint64_t load_addr __maybe_unused, const char *sym, (void)elf_end(e); free(strsym); - + free(build_id_data); return retval; } From d009ff8959d28d2a33aeb96a5f7e7161c421d78f Mon Sep 17 00:00:00 2001 From: Shuhao Fu Date: Tue, 21 Oct 2025 16:42:28 +0800 Subject: [PATCH 0888/3902] exfat: fix refcount leak in exfat_find [ Upstream commit 9aee8de970f18c2aaaa348e3de86c38e2d956c1d ] Fix refcount leaks in `exfat_find` related to `exfat_get_dentry_set`. Function `exfat_get_dentry_set` would increase the reference counter of `es->bh` on success. Therefore, `exfat_put_dentry_set` must be called after `exfat_get_dentry_set` to ensure refcount consistency. This patch relocate two checks to avoid possible leaks. Fixes: 82ebecdc74ff ("exfat: fix improper check of dentry.stream.valid_size") Fixes: 13940cef9549 ("exfat: add a check for invalid data size") Signed-off-by: Shuhao Fu Reviewed-by: Yuezhang Mo Signed-off-by: Namjae Jeon Signed-off-by: Sasha Levin --- fs/exfat/namei.c | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/fs/exfat/namei.c b/fs/exfat/namei.c index 745dce29ddb532..dfe957493d49ee 100644 --- a/fs/exfat/namei.c +++ b/fs/exfat/namei.c @@ -645,16 +645,6 @@ static int exfat_find(struct inode *dir, const struct qstr *qname, info->valid_size = le64_to_cpu(ep2->dentry.stream.valid_size); info->size = le64_to_cpu(ep2->dentry.stream.size); - if (info->valid_size < 0) { - exfat_fs_error(sb, "data valid size is invalid(%lld)", info->valid_size); - return -EIO; - } - - if (unlikely(EXFAT_B_TO_CLU_ROUND_UP(info->size, sbi) > sbi->used_clusters)) { - exfat_fs_error(sb, "data size is invalid(%lld)", info->size); - return -EIO; - } - info->start_clu = le32_to_cpu(ep2->dentry.stream.start_clu); if (!is_valid_cluster(sbi, info->start_clu) && info->size) { exfat_warn(sb, "start_clu is invalid cluster(0x%x)", @@ -692,6 +682,16 @@ static int exfat_find(struct inode *dir, const struct qstr *qname, 0); exfat_put_dentry_set(&es, false); + if (info->valid_size < 0) { + exfat_fs_error(sb, "data valid size is invalid(%lld)", info->valid_size); + return -EIO; + } + + if (unlikely(EXFAT_B_TO_CLU_ROUND_UP(info->size, sbi) > sbi->used_clusters)) { + exfat_fs_error(sb, "data size is invalid(%lld)", info->size); + return -EIO; + } + if (ei->start_clu == EXFAT_FREE_CLUSTER) { exfat_fs_error(sb, "non-zero size file starts with zero cluster (size : %llu, p_dir : %u, entry : 0x%08x)", From 88fc3dd6e631b3e2975f898c6c2b6bc6f7058b44 Mon Sep 17 00:00:00 2001 From: Namjae Jeon Date: Tue, 18 Nov 2025 11:10:26 +0900 Subject: [PATCH 0889/3902] exfat: fix divide-by-zero in exfat_allocate_bitmap [ Upstream commit d70a5804c563b5e34825353ba9927509df709651 ] The variable max_ra_count can be 0 in exfat_allocate_bitmap(), which causes a divide-by-zero error in the subsequent modulo operation (i % max_ra_count), leading to a system crash. When max_ra_count is 0, it means that readahead is not used. This patch load the bitmap without readahead. Fixes: 9fd688678dd8 ("exfat: optimize allocation bitmap loading time") Reported-by: Jiaming Zhang Signed-off-by: Namjae Jeon Signed-off-by: Sasha Levin --- fs/exfat/balloc.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fs/exfat/balloc.c b/fs/exfat/balloc.c index 2d2d510f2372cb..0b6466b3490a06 100644 --- a/fs/exfat/balloc.c +++ b/fs/exfat/balloc.c @@ -106,7 +106,7 @@ static int exfat_allocate_bitmap(struct super_block *sb, (PAGE_SHIFT - sb->s_blocksize_bits); for (i = 0; i < sbi->map_sectors; i++) { /* Trigger the next readahead in advance. */ - if (0 == (i % max_ra_count)) { + if (max_ra_count && 0 == (i % max_ra_count)) { blk_start_plug(&plug); for (j = i; j < min(max_ra_count, sbi->map_sectors - i) + i; j++) sb_breadahead(sb, sector + j); From 30f874b8a92ff71a275846d38f2e39a44ae0e4a8 Mon Sep 17 00:00:00 2001 From: Namhyung Kim Date: Tue, 2 Dec 2025 15:57:14 -0800 Subject: [PATCH 0890/3902] perf tools: Mark split kallsyms DSOs as loaded [ Upstream commit 7da4d60db33cccd8f4c445ab20bba71531435ee5 ] The maps__split_kallsyms() will split symbols to module DSOs if it comes from a module. It also handled some unusual kernel symbols after modules by creating new kernel maps like "[kernel].0". But they are pseudo DSOs to have those unexpected symbols. They should not be considered as unloaded kernel DSOs. Otherwise the dso__load() for them will end up calling dso__load_kallsyms() and then maps__split_kallsyms() again and again. Reviewed-by: Ian Rogers Fixes: 2e538c4a1847291cf ("perf tools: Improve kernel/modules symbol lookup") Signed-off-by: Namhyung Kim Signed-off-by: Sasha Levin --- tools/perf/util/symbol.c | 1 + 1 file changed, 1 insertion(+) diff --git a/tools/perf/util/symbol.c b/tools/perf/util/symbol.c index 948d3e8ad782bc..aed65b1abe6697 100644 --- a/tools/perf/util/symbol.c +++ b/tools/perf/util/symbol.c @@ -976,6 +976,7 @@ static int maps__split_kallsyms(struct maps *kmaps, struct dso *dso, u64 delta, return -1; dso__set_kernel(ndso, dso__kernel(dso)); + dso__set_loaded(ndso); curr_map = map__new2(pos->start, ndso); if (curr_map == NULL) { From 501a16c1ddb23beb27fa31a98c8054e34f9ea4b9 Mon Sep 17 00:00:00 2001 From: Namhyung Kim Date: Tue, 2 Dec 2025 15:57:15 -0800 Subject: [PATCH 0891/3902] perf tools: Fix split kallsyms DSO counting [ Upstream commit ad0b9c4865b98dc37f4d606d26b1c19808796805 ] It's counted twice as it's increased after calling maps__insert(). I guess we want to increase it only after it's added properly. Reviewed-by: Ian Rogers Fixes: 2e538c4a1847291cf ("perf tools: Improve kernel/modules symbol lookup") Signed-off-by: Namhyung Kim Signed-off-by: Sasha Levin --- tools/perf/util/symbol.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tools/perf/util/symbol.c b/tools/perf/util/symbol.c index aed65b1abe6697..f6c268c588a569 100644 --- a/tools/perf/util/symbol.c +++ b/tools/perf/util/symbol.c @@ -964,11 +964,11 @@ static int maps__split_kallsyms(struct maps *kmaps, struct dso *dso, u64 delta, if (dso__kernel(dso) == DSO_SPACE__KERNEL_GUEST) snprintf(dso_name, sizeof(dso_name), "[guest.kernel].%d", - kernel_range++); + kernel_range); else snprintf(dso_name, sizeof(dso_name), "[kernel].%d", - kernel_range++); + kernel_range); ndso = dso__new(dso_name); map__zput(curr_map); From badf751a785d624f2ab326f7d00d11a5a78842dc Mon Sep 17 00:00:00 2001 From: Ian Rogers Date: Sat, 22 Nov 2025 00:19:13 -0800 Subject: [PATCH 0892/3902] perf kvm: Fix debug assertion [ Upstream commit 27e711257902475097dea3f79cbdf241fe37ec00 ] There are 2 slots left for kvm_add_default_arch_event, fix the assertion so that debug builds don't fail the assert and to agree with the comment. Fixes: 45ff39f6e70aa55d0 ("perf tools kvm: Fix the potential out of range memory access issue") Signed-off-by: Ian Rogers Signed-off-by: Namhyung Kim Signed-off-by: Sasha Levin --- tools/perf/builtin-kvm.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/perf/builtin-kvm.c b/tools/perf/builtin-kvm.c index f0f285763f190a..c61369d54dd9d0 100644 --- a/tools/perf/builtin-kvm.c +++ b/tools/perf/builtin-kvm.c @@ -2014,7 +2014,7 @@ static int __cmd_record(const char *file_name, int argc, const char **argv) for (j = 1; j < argc; j++, i++) rec_argv[i] = STRDUP_FAIL_EXIT(argv[j]); - BUG_ON(i != rec_argc); + BUG_ON(i + 2 != rec_argc); ret = kvm_add_default_arch_event(&i, rec_argv); if (ret) From 654841dd211b006803a5f8a4005f03010e0fdbdc Mon Sep 17 00:00:00 2001 From: Ian Rogers Date: Sat, 22 Nov 2025 00:19:18 -0800 Subject: [PATCH 0893/3902] perf hist: In init, ensure mem_info is put on error paths [ Upstream commit f60efb4454b24cc944ff3eac164bb9dce9169f71 ] Rather than exit the internal map_symbols directly, put the mem-info that does this and also lowers the reference count on the mem-info itself otherwise the mem-info is being leaked. Fixes: 56e144fe98260a0f ("perf mem_info: Add and use map_symbol__exit and addr_map_symbol__exit") Signed-off-by: Ian Rogers Reviewed-by: Arnaldo Carvalho de Melo Signed-off-by: Namhyung Kim Signed-off-by: Sasha Levin --- tools/perf/util/hist.c | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/tools/perf/util/hist.c b/tools/perf/util/hist.c index 64ff427040c341..ef4b569f7df463 100644 --- a/tools/perf/util/hist.c +++ b/tools/perf/util/hist.c @@ -608,10 +608,8 @@ static int hist_entry__init(struct hist_entry *he, map_symbol__exit(&he->branch_info->to.ms); zfree(&he->branch_info); } - if (he->mem_info) { - map_symbol__exit(&mem_info__iaddr(he->mem_info)->ms); - map_symbol__exit(&mem_info__daddr(he->mem_info)->ms); - } + if (he->mem_info) + mem_info__zput(he->mem_info); err: map_symbol__exit(&he->ms); zfree(&he->stat_acc); From b9ceff1ca3ef9a3f7438e0825b6095da3905fc4c Mon Sep 17 00:00:00 2001 From: Haotian Zhang Date: Wed, 3 Dec 2025 14:13:47 +0800 Subject: [PATCH 0894/3902] pinctrl: single: Fix incorrect type for error return variable [ Upstream commit 61d1bb53547d42c6bdaec9da4496beb3a1a05264 ] pcs_pinconf_get() and pcs_pinconf_set() declare ret as unsigned int, but assign it the return values of pcs_get_function() that may return negative error codes. This causes negative error codes to be converted to large positive values. Change ret from unsigned int to int in both functions. Fixes: 9dddb4df90d1 ("pinctrl: single: support generic pinconf") Signed-off-by: Haotian Zhang Signed-off-by: Linus Walleij Signed-off-by: Sasha Levin --- drivers/pinctrl/pinctrl-single.c | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/drivers/pinctrl/pinctrl-single.c b/drivers/pinctrl/pinctrl-single.c index 6d580aa282ec98..998f23d6c3179e 100644 --- a/drivers/pinctrl/pinctrl-single.c +++ b/drivers/pinctrl/pinctrl-single.c @@ -485,7 +485,8 @@ static int pcs_pinconf_get(struct pinctrl_dev *pctldev, struct pcs_device *pcs = pinctrl_dev_get_drvdata(pctldev); struct pcs_function *func; enum pin_config_param param; - unsigned offset = 0, data = 0, i, j, ret; + unsigned offset = 0, data = 0, i, j; + int ret; ret = pcs_get_function(pctldev, pin, &func); if (ret) @@ -549,9 +550,9 @@ static int pcs_pinconf_set(struct pinctrl_dev *pctldev, { struct pcs_device *pcs = pinctrl_dev_get_drvdata(pctldev); struct pcs_function *func; - unsigned offset = 0, shift = 0, i, data, ret; + unsigned offset = 0, shift = 0, i, data; u32 arg; - int j; + int j, ret; enum pin_config_param param; ret = pcs_get_function(pctldev, pin, &func); From 850f134a4c81954f95f0eafe49e80fece82b11ba Mon Sep 17 00:00:00 2001 From: Ian Rogers Date: Wed, 3 Dec 2025 13:47:00 -0800 Subject: [PATCH 0895/3902] perf stat: Allow no events to open if this is a "--null" run [ Upstream commit 6744c0b182c1f371135bc3f4e62b96ad884c9f89 ] It is intended that a "--null" run doesn't open any events. Fixes: 2cc7aa995ce9 ("perf stat: Refactor retry/skip/fatal error handling") Tested-by: Ingo Molnar Signed-off-by: Ian Rogers Tested-by: Thomas Richter Signed-off-by: Namhyung Kim Signed-off-by: Sasha Levin --- tools/perf/builtin-stat.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/perf/builtin-stat.c b/tools/perf/builtin-stat.c index f1c9d6c94fc509..b6533dcf5465b1 100644 --- a/tools/perf/builtin-stat.c +++ b/tools/perf/builtin-stat.c @@ -856,7 +856,7 @@ static int __run_perf_stat(int argc, const char **argv, int run_idx) goto err_out; } } - if (!has_supported_counters) { + if (!has_supported_counters && !stat_config.null_run) { evsel__open_strerror(evlist__first(evsel_list), &target, open_err, msg, sizeof(msg)); ui__error("No supported events found.\n%s\n", msg); From 15a6572117f09674216377e05dc923c8283241f3 Mon Sep 17 00:00:00 2001 From: Abdun Nihaal Date: Wed, 3 Dec 2025 09:25:44 +0530 Subject: [PATCH 0896/3902] fbdev: ssd1307fb: fix potential page leak in ssd1307fb_probe() [ Upstream commit 164312662ae9764b83b84d97afb25c42eb2be473 ] The page allocated for vmem using __get_free_pages() is not freed on the error paths after it. Fix that by adding a corresponding __free_pages() call to the error path. Fixes: facd94bc458a ("fbdev: ssd1307fb: Allocate page aligned video memory.") Signed-off-by: Abdun Nihaal Signed-off-by: Helge Deller Signed-off-by: Sasha Levin --- drivers/video/fbdev/ssd1307fb.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/drivers/video/fbdev/ssd1307fb.c b/drivers/video/fbdev/ssd1307fb.c index aa6cc0a8151ac0..83dd31fa1fab53 100644 --- a/drivers/video/fbdev/ssd1307fb.c +++ b/drivers/video/fbdev/ssd1307fb.c @@ -680,7 +680,7 @@ static int ssd1307fb_probe(struct i2c_client *client) if (!ssd1307fb_defio) { dev_err(dev, "Couldn't allocate deferred io.\n"); ret = -ENOMEM; - goto fb_alloc_error; + goto fb_defio_error; } ssd1307fb_defio->delay = HZ / refreshrate; @@ -757,6 +757,8 @@ static int ssd1307fb_probe(struct i2c_client *client) regulator_disable(par->vbat_reg); reset_oled_error: fb_deferred_io_cleanup(info); +fb_defio_error: + __free_pages(vmem, get_order(vmem_size)); fb_alloc_error: framebuffer_release(info); return ret; From fe9a3154bb5b020f0fe9ddb661560f37ebaff5da Mon Sep 17 00:00:00 2001 From: Eric Sandeen Date: Tue, 2 Dec 2025 16:30:53 -0600 Subject: [PATCH 0897/3902] 9p: fix cache/debug options printing in v9fs_show_options [ Upstream commit f0445613314f474c1a0ec6fa8a5cd153a618f1b6 ] commit 4eb3117888a92 changed the cache= option to accept either string shortcuts or bitfield values. It also changed /proc/mounts to emit the option as the hexadecimal numeric value rather than the shortcut string. However, by printing "cache=%x" without the leading 0x, shortcuts such as "cache=loose" will emit "cache=f" and 'f' is not a string that is parseable by kstrtoint(), so remounting may fail if a remount with "cache=f" is attempted. debug=%x has had the same problem since options have been displayed in c4fac9100456 ("9p: Implement show_options") Fix these by adding the 0x prefix to the hexadecimal value shown in /proc/mounts. Fixes: 4eb3117888a92 ("fs/9p: Rework cache modes and add new options to Documentation") Signed-off-by: Eric Sandeen Message-ID: <54b93378-dcf1-4b04-922d-c8b4393da299@redhat.com> [Dominique: use %#x at Al Viro's suggestion, also handle debug] Tested-by: Remi Pommarel Signed-off-by: Dominique Martinet Signed-off-by: Sasha Levin --- fs/9p/v9fs.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/fs/9p/v9fs.c b/fs/9p/v9fs.c index a020a8f00a1ac0..bde3ffb0e319a5 100644 --- a/fs/9p/v9fs.c +++ b/fs/9p/v9fs.c @@ -101,7 +101,7 @@ int v9fs_show_options(struct seq_file *m, struct dentry *root) struct v9fs_session_info *v9ses = root->d_sb->s_fs_info; if (v9ses->debug) - seq_printf(m, ",debug=%x", v9ses->debug); + seq_printf(m, ",debug=%#x", v9ses->debug); if (!uid_eq(v9ses->dfltuid, V9FS_DEFUID)) seq_printf(m, ",dfltuid=%u", from_kuid_munged(&init_user_ns, v9ses->dfltuid)); @@ -117,7 +117,7 @@ int v9fs_show_options(struct seq_file *m, struct dentry *root) if (v9ses->nodev) seq_puts(m, ",nodevmap"); if (v9ses->cache) - seq_printf(m, ",cache=%x", v9ses->cache); + seq_printf(m, ",cache=%#x", v9ses->cache); #ifdef CONFIG_9P_FSCACHE if (v9ses->cachetag && (v9ses->cache & CACHE_FSCACHE)) seq_printf(m, ",cachetag=%s", v9ses->cachetag); From 8061d3b7bb917812c697f25495cb015d3a6c42e8 Mon Sep 17 00:00:00 2001 From: xupengbo Date: Wed, 27 Aug 2025 10:22:07 +0800 Subject: [PATCH 0898/3902] sched/fair: Fix unfairness caused by stalled tg_load_avg_contrib when the last task migrates out [ Upstream commit ca125231dd29fc0678dd3622e9cdea80a51dffe4 ] When a task is migrated out, there is a probability that the tg->load_avg value will become abnormal. The reason is as follows: 1. Due to the 1ms update period limitation in update_tg_load_avg(), there is a possibility that the reduced load_avg is not updated to tg->load_avg when a task migrates out. 2. Even though __update_blocked_fair() traverses the leaf_cfs_rq_list and calls update_tg_load_avg() for cfs_rqs that are not fully decayed, the key function cfs_rq_is_decayed() does not check whether cfs->tg_load_avg_contrib is null. Consequently, in some cases, __update_blocked_fair() removes cfs_rqs whose avg.load_avg has not been updated to tg->load_avg. Add a check of cfs_rq->tg_load_avg_contrib in cfs_rq_is_decayed(), which fixes the case (2.) mentioned above. Fixes: 1528c661c24b ("sched/fair: Ratelimit update to tg->load_avg") Signed-off-by: xupengbo Signed-off-by: Peter Zijlstra (Intel) Signed-off-by: Ingo Molnar Reviewed-by: Aaron Lu Reviewed-by: Vincent Guittot Tested-by: Aaron Lu Link: https://patch.msgid.link/20250827022208.14487-1-xupengbo@oppo.com Signed-off-by: Sasha Levin --- kernel/sched/fair.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/kernel/sched/fair.c b/kernel/sched/fair.c index 2a4a1c6e25da07..71d3f4125b0e73 100644 --- a/kernel/sched/fair.c +++ b/kernel/sched/fair.c @@ -4059,6 +4059,9 @@ static inline bool cfs_rq_is_decayed(struct cfs_rq *cfs_rq) if (child_cfs_rq_on_list(cfs_rq)) return false; + if (cfs_rq->tg_load_avg_contrib) + return false; + return true; } From 41a59772b5007e9f1fd4a11e8509cee3d5c8e6e4 Mon Sep 17 00:00:00 2001 From: John Stultz Date: Fri, 5 Dec 2025 01:27:09 +0000 Subject: [PATCH 0899/3902] sched/core: Fix psi_dequeue() for Proxy Execution [ Upstream commit c2ae8b0df2d1bb7a063f9e356e4e9a06cd4afe11 ] Currently, if the sleep flag is set, psi_dequeue() doesn't change any of the psi_flags. This is because psi_task_switch() will clear TSK_ONCPU as well as other potential flags (TSK_RUNNING), and the assumption is that a voluntary sleep always consists of a task being dequeued followed shortly there after with a psi_sched_switch() call. Proxy Execution changes this expectation, as mutex-blocked tasks that would normally sleep stay on the runqueue. But in the case where the mutex-owning task goes to sleep, or the owner is on a remote cpu, we will then deactivate the blocked task shortly after. In that situation, the mutex-blocked task will have had its TSK_ONCPU cleared when it was switched off the cpu, but it will stay TSK_RUNNING. Then if we later dequeue it (as currently done if we hit a case find_proxy_task() can't yet handle, such as the case of the owner being on another rq or a sleeping owner) psi_dequeue() won't change any state (leaving it TSK_RUNNING), as it incorrectly expects a psi_task_switch() call to immediately follow. Later on when the task get woken/re-enqueued, and psi_flags are set for TSK_RUNNING, we hit an error as the task is already TSK_RUNNING: psi: inconsistent task state! task=188:kworker/28:0 cpu=28 psi_flags=4 clear=0 set=4 To resolve this, extend the logic in psi_dequeue() so that if the sleep flag is set, we also check if psi_flags have TSK_ONCPU set (meaning the psi_task_switch is imminent) before we do the shortcut return. If TSK_ONCPU is not set, that means we've already switched away, and this psi_dequeue call needs to clear the flags. Fixes: be41bde4c3a8 ("sched: Add an initial sketch of the find_proxy_task() function") Reported-by: K Prateek Nayak Signed-off-by: John Stultz Signed-off-by: Ingo Molnar Tested-by: K Prateek Nayak Tested-by: Haiyue Wang Acked-by: Johannes Weiner Link: https://patch.msgid.link/20251205012721.756394-1-jstultz@google.com Closes: https://lore.kernel.org/lkml/20251117185550.365156-1-kprateek.nayak@amd.com/ Signed-off-by: Sasha Levin --- kernel/sched/stats.h | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/kernel/sched/stats.h b/kernel/sched/stats.h index 26f3fd4d34cead..73bd6bca4d3107 100644 --- a/kernel/sched/stats.h +++ b/kernel/sched/stats.h @@ -180,8 +180,13 @@ static inline void psi_dequeue(struct task_struct *p, int flags) * avoid walking all ancestors twice, psi_task_switch() handles * TSK_RUNNING and TSK_IOWAIT for us when it moves TSK_ONCPU. * Do nothing here. + * + * In the SCHED_PROXY_EXECUTION case we may do sleeping + * dequeues that are not followed by a task switch, so check + * TSK_ONCPU is set to ensure the task switch is imminent. + * Otherwise clear the flags as usual. */ - if (flags & DEQUEUE_SLEEP) + if ((flags & DEQUEUE_SLEEP) && (p->psi_flags & TSK_ONCPU)) return; /* From 52eb1c76c0dcd586bf0a8fa6e0a6e6b23881f4a2 Mon Sep 17 00:00:00 2001 From: Xi Pardee Date: Tue, 14 Oct 2025 14:45:29 -0700 Subject: [PATCH 0900/3902] platform/x86:intel/pmc: Update Arrow Lake telemetry GUID MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [ Upstream commit 644ab3bc98ee386f178d5209ae8170b3fac591aa ] Update ARL_PMT_DMU_GUID value. Arrow Lake PMT DMU GUID has been updated after it was add to the driver. This updates ensures that the die C6 value is available in the debug filesystem. Bugzilla Link: https://bugzilla.kernel.org/show_bug.cgi?id=220421 Fixes: 83f168a1a437 ("platform/x86/intel/pmc: Add Arrow Lake S support to intel_pmc_core driver") Tested-by: Mark Pearson Signed-off-by: Xi Pardee Link: https://patch.msgid.link/20251014214548.629023-2-xi.pardee@linux.intel.com Reviewed-by: Ilpo Järvinen Signed-off-by: Ilpo Järvinen Signed-off-by: Sasha Levin --- drivers/platform/x86/intel/pmc/core.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/platform/x86/intel/pmc/core.h b/drivers/platform/x86/intel/pmc/core.h index f4dadb696a3142..d6818bd34768ec 100644 --- a/drivers/platform/x86/intel/pmc/core.h +++ b/drivers/platform/x86/intel/pmc/core.h @@ -282,7 +282,7 @@ enum ppfear_regs { /* Die C6 from PUNIT telemetry */ #define MTL_PMT_DMU_DIE_C6_OFFSET 15 #define MTL_PMT_DMU_GUID 0x1A067102 -#define ARL_PMT_DMU_GUID 0x1A06A000 +#define ARL_PMT_DMU_GUID 0x1A06A102 #define LNL_PMC_MMIO_REG_LEN 0x2708 #define LNL_PMC_LTR_OSSE 0x1B88 From b18d3c3ce852372cd612901488436315aca6d02e Mon Sep 17 00:00:00 2001 From: Daeho Jeong Date: Fri, 3 Oct 2025 15:43:08 -0700 Subject: [PATCH 0901/3902] f2fs: maintain one time GC mode is enabled during whole zoned GC cycle [ Upstream commit e462fc48ceb8224811c3224650afed05cb7f0872 ] The current version missed setting one time GC for normal zoned GC cycle. So, valid threshold control is not working. Need to fix it to prevent excessive GC for zoned devices. Fixes: e791d00bd06c ("f2fs: add valid block ratio not to do excessive GC for one time GC") Signed-off-by: Daeho Jeong Reviewed-by: Chao Yu Signed-off-by: Jaegeuk Kim Signed-off-by: Sasha Levin --- fs/f2fs/gc.c | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/fs/f2fs/gc.c b/fs/f2fs/gc.c index a7708cf80c04ed..8abf521530ff3f 100644 --- a/fs/f2fs/gc.c +++ b/fs/f2fs/gc.c @@ -38,13 +38,14 @@ static int gc_thread_func(void *data) struct f2fs_gc_control gc_control = { .victim_segno = NULL_SEGNO, .should_migrate_blocks = false, - .err_gc_skipped = false }; + .err_gc_skipped = false, + .one_time = false }; wait_ms = gc_th->min_sleep_time; set_freezable(); do { - bool sync_mode, foreground = false; + bool sync_mode, foreground = false, gc_boost = false; wait_event_freezable_timeout(*wq, kthread_should_stop() || @@ -52,8 +53,12 @@ static int gc_thread_func(void *data) gc_th->gc_wake, msecs_to_jiffies(wait_ms)); - if (test_opt(sbi, GC_MERGE) && waitqueue_active(fggc_wq)) + if (test_opt(sbi, GC_MERGE) && waitqueue_active(fggc_wq)) { foreground = true; + gc_control.one_time = false; + } else if (f2fs_sb_has_blkzoned(sbi)) { + gc_control.one_time = true; + } /* give it a try one time */ if (gc_th->gc_wake) @@ -81,8 +86,6 @@ static int gc_thread_func(void *data) continue; } - gc_control.one_time = false; - /* * [GC triggering condition] * 0. GC is not conducted currently. @@ -132,7 +135,7 @@ static int gc_thread_func(void *data) if (need_to_boost_gc(sbi)) { decrease_sleep_time(gc_th, &wait_ms); if (f2fs_sb_has_blkzoned(sbi)) - gc_control.one_time = true; + gc_boost = true; } else { increase_sleep_time(gc_th, &wait_ms); } @@ -141,7 +144,7 @@ static int gc_thread_func(void *data) FOREGROUND : BACKGROUND); sync_mode = (F2FS_OPTION(sbi).bggc_mode == BGGC_MODE_SYNC) || - (gc_control.one_time && gc_th->boost_gc_greedy); + (gc_boost && gc_th->boost_gc_greedy); /* foreground GC was been triggered via f2fs_balance_fs() */ if (foreground && !f2fs_sb_has_blkzoned(sbi)) From 2e1c79299036614ac32b251d145fad5391f4bcab Mon Sep 17 00:00:00 2001 From: Haotian Zhang Date: Tue, 21 Oct 2025 18:35:59 +0800 Subject: [PATCH 0902/3902] rtc: amlogic-a4: fix double free caused by devm [ Upstream commit 384150d7a5b60c1086790a8ee07b0629f906cca2 ] The clock obtained via devm_clk_get_enabled() is automatically managed by devres and will be disabled and freed on driver detach. Manually calling clk_disable_unprepare() in error path and remove function causes double free. Remove the redundant clk_disable_unprepare() calls from the probe error path and aml_rtc_remove(), allowing the devm framework to automatically manage the clock lifecycle. Fixes: c89ac9182ee2 ("rtc: support for the Amlogic on-chip RTC") Signed-off-by: Haotian Zhang Reviewed-by: Xianwei Zhao Link: https://patch.msgid.link/20251021103559.1903-1-vulab@iscas.ac.cn Signed-off-by: Alexandre Belloni Signed-off-by: Sasha Levin --- drivers/rtc/rtc-amlogic-a4.c | 4 ---- 1 file changed, 4 deletions(-) diff --git a/drivers/rtc/rtc-amlogic-a4.c b/drivers/rtc/rtc-amlogic-a4.c index 1928b29c10454d..a993d35e1d6b09 100644 --- a/drivers/rtc/rtc-amlogic-a4.c +++ b/drivers/rtc/rtc-amlogic-a4.c @@ -390,7 +390,6 @@ static int aml_rtc_probe(struct platform_device *pdev) return 0; err_clk: - clk_disable_unprepare(rtc->sys_clk); device_init_wakeup(dev, false); return ret; @@ -423,9 +422,6 @@ static SIMPLE_DEV_PM_OPS(aml_rtc_pm_ops, static void aml_rtc_remove(struct platform_device *pdev) { - struct aml_rtc_data *rtc = dev_get_drvdata(&pdev->dev); - - clk_disable_unprepare(rtc->sys_clk); device_init_wakeup(&pdev->dev, false); } From 530f1ba287937726ff220de9c82566e74e35dcae Mon Sep 17 00:00:00 2001 From: Abel Vesa Date: Tue, 11 Nov 2025 08:43:51 +0200 Subject: [PATCH 0903/3902] kbuild: install-extmod-build: Properly fix CC expansion when ccache is used [ Upstream commit 4ab2ee307983548b29ddaab0ecaef82d526cf4c9 ] Currently, when cross-compiling and ccache is used, the expanding of CC turns out to be without any quotes, leading to the following error: make[4]: *** No rule to make target 'aarch64-linux-gnu-gcc'. Stop. make[3]: *** [Makefile:2164: run-command] Error 2 And it makes sense, because after expansion it ends up like this: make run-command KBUILD_RUN_COMMAND=+$(MAKE) \ HOSTCC=ccache aarch64-linux-gnu-gcc VPATH= srcroot=. $(build)= ... So add another set of double quotes to surround whatever CC expands to to make sure the aarch64-linux-gnu-gcc isn't expanded to something that looks like an entirely separate target. Fixes: 140332b6ed72 ("kbuild: fix linux-headers package build when $(CC) cannot link userspace") Signed-off-by: Abel Vesa Reviewed-by: Nicolas Schier Link: https://patch.msgid.link/20251111-kbuild-install-extmod-build-fix-cc-expand-third-try-v2-1-15ba1b37e71a@linaro.org Signed-off-by: Nathan Chancellor Signed-off-by: Sasha Levin --- scripts/package/install-extmod-build | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/package/install-extmod-build b/scripts/package/install-extmod-build index 054fdf45cc37a8..2576cf7902dbbf 100755 --- a/scripts/package/install-extmod-build +++ b/scripts/package/install-extmod-build @@ -63,7 +63,7 @@ if [ "${CC}" != "${HOSTCC}" ]; then # Clear VPATH and srcroot because the source files reside in the output # directory. # shellcheck disable=SC2016 # $(MAKE) and $(build) will be expanded by Make - "${MAKE}" run-command KBUILD_RUN_COMMAND='+$(MAKE) HOSTCC='"${CC}"' VPATH= srcroot=. $(build)='"$(realpath --relative-to=. "${destdir}")"/scripts + "${MAKE}" run-command KBUILD_RUN_COMMAND='+$(MAKE) HOSTCC="'"${CC}"'" VPATH= srcroot=. $(build)='"$(realpath --relative-to=. "${destdir}")"/scripts rm -f "${destdir}/scripts/Kbuild" fi From f0f456c327f5728ca632746cdfae0bf4f6bc5188 Mon Sep 17 00:00:00 2001 From: Trond Myklebust Date: Mon, 17 Nov 2025 15:28:17 -0500 Subject: [PATCH 0904/3902] NFS: Avoid changing nlink when file removes and attribute updates race [ Upstream commit bd4928ec799b31c492eb63f9f4a0c1e0bb4bb3f7 ] If a file removal races with another operation that updates its attributes, then skip the change to nlink, and just mark the attributes as being stale. Reported-by: Aiden Lambert Fixes: 59a707b0d42e ("NFS: Ensure we revalidate the inode correctly after remove or rename") Signed-off-by: Trond Myklebust Signed-off-by: Sasha Levin --- fs/nfs/dir.c | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/fs/nfs/dir.c b/fs/nfs/dir.c index ea9f6ca8f30fa2..d557b0443e8b04 100644 --- a/fs/nfs/dir.c +++ b/fs/nfs/dir.c @@ -1894,13 +1894,15 @@ static int nfs_dentry_delete(const struct dentry *dentry) } /* Ensure that we revalidate inode->i_nlink */ -static void nfs_drop_nlink(struct inode *inode) +static void nfs_drop_nlink(struct inode *inode, unsigned long gencount) { + struct nfs_inode *nfsi = NFS_I(inode); + spin_lock(&inode->i_lock); /* drop the inode if we're reasonably sure this is the last link */ - if (inode->i_nlink > 0) + if (inode->i_nlink > 0 && gencount == nfsi->attr_gencount) drop_nlink(inode); - NFS_I(inode)->attr_gencount = nfs_inc_attr_generation_counter(); + nfsi->attr_gencount = nfs_inc_attr_generation_counter(); nfs_set_cache_invalid( inode, NFS_INO_INVALID_CHANGE | NFS_INO_INVALID_CTIME | NFS_INO_INVALID_NLINK); @@ -1914,8 +1916,9 @@ static void nfs_drop_nlink(struct inode *inode) static void nfs_dentry_iput(struct dentry *dentry, struct inode *inode) { if (dentry->d_flags & DCACHE_NFSFS_RENAMED) { + unsigned long gencount = READ_ONCE(NFS_I(inode)->attr_gencount); nfs_complete_unlink(dentry, inode); - nfs_drop_nlink(inode); + nfs_drop_nlink(inode, gencount); } iput(inode); } @@ -2507,9 +2510,11 @@ static int nfs_safe_remove(struct dentry *dentry) trace_nfs_remove_enter(dir, dentry); if (inode != NULL) { + unsigned long gencount = READ_ONCE(NFS_I(inode)->attr_gencount); + error = NFS_PROTO(dir)->remove(dir, dentry); if (error == 0) - nfs_drop_nlink(inode); + nfs_drop_nlink(inode, gencount); } else error = NFS_PROTO(dir)->remove(dir, dentry); if (error == -ENOENT) @@ -2709,6 +2714,7 @@ int nfs_rename(struct mnt_idmap *idmap, struct inode *old_dir, { struct inode *old_inode = d_inode(old_dentry); struct inode *new_inode = d_inode(new_dentry); + unsigned long new_gencount = 0; struct dentry *dentry = NULL; struct rpc_task *task; bool must_unblock = false; @@ -2761,6 +2767,7 @@ int nfs_rename(struct mnt_idmap *idmap, struct inode *old_dir, } else { block_revalidate(new_dentry); must_unblock = true; + new_gencount = NFS_I(new_inode)->attr_gencount; spin_unlock(&new_dentry->d_lock); } @@ -2800,7 +2807,7 @@ int nfs_rename(struct mnt_idmap *idmap, struct inode *old_dir, new_dir, new_dentry, error); if (!error) { if (new_inode != NULL) - nfs_drop_nlink(new_inode); + nfs_drop_nlink(new_inode, new_gencount); /* * The d_move() should be here instead of in an async RPC completion * handler because we need the proper locks to move the dentry. If From 4836f912acc702d6e70eab3f1d796895906a540d Mon Sep 17 00:00:00 2001 From: Armin Wolf Date: Tue, 11 Nov 2025 14:11:22 +0100 Subject: [PATCH 0905/3902] fs/nls: Fix utf16 to utf8 conversion MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [ Upstream commit 25524b6190295577e4918c689644451365e6466d ] Currently the function responsible for converting between utf16 and utf8 strings will ignore any characters that cannot be converted. This however also includes multi-byte characters that do not fit into the provided string buffer. This can cause problems if such a multi-byte character is followed by a single-byte character. In such a case the multi-byte character might be ignored when the provided string buffer is too small, but the single-byte character might fit and is thus still copied into the resulting string. Fix this by stop filling the provided string buffer once a character does not fit. In order to be able to do this extend utf32_to_utf8() to return useful errno codes instead of -1. Fixes: 74675a58507e ("NLS: update handling of Unicode") Signed-off-by: Armin Wolf Link: https://patch.msgid.link/20251111131125.3379-2-W_Armin@gmx.de Reviewed-by: Ilpo Järvinen Signed-off-by: Ilpo Järvinen Signed-off-by: Sasha Levin --- fs/nls/nls_base.c | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/fs/nls/nls_base.c b/fs/nls/nls_base.c index 18d597e49a194e..d434c4463a8f72 100644 --- a/fs/nls/nls_base.c +++ b/fs/nls/nls_base.c @@ -94,7 +94,7 @@ int utf32_to_utf8(unicode_t u, u8 *s, int maxout) l = u; if (l > UNICODE_MAX || (l & SURROGATE_MASK) == SURROGATE_PAIR) - return -1; + return -EILSEQ; nc = 0; for (t = utf8_table; t->cmask && maxout; t++, maxout--) { @@ -110,7 +110,7 @@ int utf32_to_utf8(unicode_t u, u8 *s, int maxout) return nc; } } - return -1; + return -EOVERFLOW; } EXPORT_SYMBOL(utf32_to_utf8); @@ -217,8 +217,16 @@ int utf16s_to_utf8s(const wchar_t *pwcs, int inlen, enum utf16_endian endian, inlen--; } size = utf32_to_utf8(u, op, maxout); - if (size == -1) { - /* Ignore character and move on */ + if (size < 0) { + if (size == -EILSEQ) { + /* Ignore character and move on */ + continue; + } + /* + * Stop filling the buffer with data once a character + * does not fit anymore. + */ + break; } else { op += size; maxout -= size; From 32b7ba23b4df7f0d58cdc0d19485e8786242b591 Mon Sep 17 00:00:00 2001 From: Trond Myklebust Date: Wed, 19 Nov 2025 08:36:16 -0500 Subject: [PATCH 0906/3902] NFS: Initialise verifiers for visible dentries in readdir and lookup [ Upstream commit 9bd545539b233725a3416801f7c374bff0327d6e ] Ensure that the verifiers are initialised before calling d_splice_alias() in both nfs_prime_dcache() and nfs_lookup(). Reported-by: Michael Stoler Fixes: a1147b8281bd ("NFS: Fix up directory verifier races") Signed-off-by: Trond Myklebust Signed-off-by: Sasha Levin --- fs/nfs/dir.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/fs/nfs/dir.c b/fs/nfs/dir.c index d557b0443e8b04..2eead7e85be5bf 100644 --- a/fs/nfs/dir.c +++ b/fs/nfs/dir.c @@ -789,16 +789,17 @@ void nfs_prime_dcache(struct dentry *parent, struct nfs_entry *entry, goto out; } + nfs_set_verifier(dentry, dir_verifier); inode = nfs_fhget(dentry->d_sb, entry->fh, entry->fattr); alias = d_splice_alias(inode, dentry); d_lookup_done(dentry); if (alias) { if (IS_ERR(alias)) goto out; + nfs_set_verifier(alias, dir_verifier); dput(dentry); dentry = alias; } - nfs_set_verifier(dentry, dir_verifier); trace_nfs_readdir_lookup(d_inode(parent), dentry, 0); out: dput(dentry); @@ -1994,13 +1995,14 @@ struct dentry *nfs_lookup(struct inode *dir, struct dentry * dentry, unsigned in nfs_lookup_advise_force_readdirplus(dir, flags); no_entry: + nfs_set_verifier(dentry, dir_verifier); res = d_splice_alias(inode, dentry); if (res != NULL) { if (IS_ERR(res)) goto out; + nfs_set_verifier(res, dir_verifier); dentry = res; } - nfs_set_verifier(dentry, dir_verifier); out: trace_nfs_lookup_exit(dir, dentry, flags, PTR_ERR_OR_ZERO(res)); nfs_free_fattr(fattr); From 97b93a595be0e68880123ea2c4df39865b62c768 Mon Sep 17 00:00:00 2001 From: Trond Myklebust Date: Wed, 19 Nov 2025 08:39:50 -0500 Subject: [PATCH 0907/3902] NFS: Initialise verifiers for visible dentries in nfs_atomic_open() [ Upstream commit 518c32a1bc4f8df1a8442ee8cdfea3e2fcff20a0 ] Ensure that the verifiers are initialised before calling d_splice_alias() in nfs_atomic_open(). Reported-by: Michael Stoler Fixes: 809fd143de88 ("NFSv4: Ensure nfs_atomic_open set the dentry verifier on ENOENT") Signed-off-by: Trond Myklebust Signed-off-by: Sasha Levin --- fs/nfs/dir.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fs/nfs/dir.c b/fs/nfs/dir.c index 2eead7e85be5bf..3b8250ee014126 100644 --- a/fs/nfs/dir.c +++ b/fs/nfs/dir.c @@ -2144,12 +2144,12 @@ int nfs_atomic_open(struct inode *dir, struct dentry *dentry, d_drop(dentry); switch (err) { case -ENOENT: - d_splice_alias(NULL, dentry); if (nfs_server_capable(dir, NFS_CAP_CASE_INSENSITIVE)) dir_verifier = inode_peek_iversion_raw(dir); else dir_verifier = nfs_save_change_attribute(dir); nfs_set_verifier(dentry, dir_verifier); + d_splice_alias(NULL, dentry); break; case -EISDIR: case -ENOTDIR: From a7d914e1f9b4e487648f6d24900899a11ad6867b Mon Sep 17 00:00:00 2001 From: Trond Myklebust Date: Wed, 19 Nov 2025 08:43:21 -0500 Subject: [PATCH 0908/3902] NFS: Initialise verifiers for visible dentries in _nfs4_open_and_get_state [ Upstream commit 0f900f11002ff52391fc2aa4a75e59f26ed1c242 ] Ensure that the verifiers are initialised before calling d_splice_alias() in _nfs4_open_and_get_state(). Reported-by: Michael Stoler Fixes: cf5b4059ba71 ("NFSv4: Fix races between open and dentry revalidation") Signed-off-by: Trond Myklebust Signed-off-by: Sasha Levin --- fs/nfs/nfs4proc.c | 27 ++++++++++++++------------- 1 file changed, 14 insertions(+), 13 deletions(-) diff --git a/fs/nfs/nfs4proc.c b/fs/nfs/nfs4proc.c index 93c6ce04332b89..6f4e14fb7b9b8c 100644 --- a/fs/nfs/nfs4proc.c +++ b/fs/nfs/nfs4proc.c @@ -3174,18 +3174,6 @@ static int _nfs4_open_and_get_state(struct nfs4_opendata *opendata, if (opendata->o_res.rflags & NFS4_OPEN_RESULT_PRESERVE_UNLINKED) set_bit(NFS_INO_PRESERVE_UNLINKED, &NFS_I(state->inode)->flags); - dentry = opendata->dentry; - if (d_really_is_negative(dentry)) { - struct dentry *alias; - d_drop(dentry); - alias = d_splice_alias(igrab(state->inode), dentry); - /* d_splice_alias() can't fail here - it's a non-directory */ - if (alias) { - dput(ctx->dentry); - ctx->dentry = dentry = alias; - } - } - switch(opendata->o_arg.claim) { default: break; @@ -3196,7 +3184,20 @@ static int _nfs4_open_and_get_state(struct nfs4_opendata *opendata, break; if (opendata->o_res.delegation.type != 0) dir_verifier = nfs_save_change_attribute(dir); - nfs_set_verifier(dentry, dir_verifier); + } + + dentry = opendata->dentry; + nfs_set_verifier(dentry, dir_verifier); + if (d_really_is_negative(dentry)) { + struct dentry *alias; + d_drop(dentry); + alias = d_splice_alias(igrab(state->inode), dentry); + /* d_splice_alias() can't fail here - it's a non-directory */ + if (alias) { + dput(ctx->dentry); + nfs_set_verifier(alias, dir_verifier); + ctx->dentry = dentry = alias; + } } /* Parse layoutget results before we check for access */ From 38694f9aae00459ab443a7dc8b3949a6b33b560a Mon Sep 17 00:00:00 2001 From: Jonathan Curley Date: Wed, 12 Nov 2025 18:02:42 +0000 Subject: [PATCH 0909/3902] NFSv4/pNFS: Clear NFS_INO_LAYOUTCOMMIT in pnfs_mark_layout_stateid_invalid [ Upstream commit e0f8058f2cb56de0b7572f51cd563ca5debce746 ] Fixes a crash when layout is null during this call stack: write_inode -> nfs4_write_inode -> pnfs_layoutcommit_inode pnfs_set_layoutcommit relies on the lseg refcount to keep the layout around. Need to clear NFS_INO_LAYOUTCOMMIT otherwise we might attempt to reference a null layout. Fixes: fe1cf9469d7bc ("pNFS: Clear all layout segment state in pnfs_mark_layout_stateid_invalid") Signed-off-by: Jonathan Curley Signed-off-by: Trond Myklebust Signed-off-by: Sasha Levin --- fs/nfs/pnfs.c | 1 + 1 file changed, 1 insertion(+) diff --git a/fs/nfs/pnfs.c b/fs/nfs/pnfs.c index a3135b5af7eeca..7ce2e840217cfc 100644 --- a/fs/nfs/pnfs.c +++ b/fs/nfs/pnfs.c @@ -464,6 +464,7 @@ pnfs_mark_layout_stateid_invalid(struct pnfs_layout_hdr *lo, struct pnfs_layout_segment *lseg, *next; set_bit(NFS_LAYOUT_INVALID_STID, &lo->plh_flags); + clear_bit(NFS_INO_LAYOUTCOMMIT, &NFS_I(lo->plh_inode)->flags); list_for_each_entry_safe(lseg, next, &lo->plh_segs, pls_list) pnfs_clear_lseg_state(lseg, lseg_list); pnfs_clear_layoutreturn_info(lo); From c646ebff3fa571e7ea974235286fb9ed3edc260c Mon Sep 17 00:00:00 2001 From: Akash Goel Date: Thu, 27 Nov 2025 16:49:12 +0000 Subject: [PATCH 0910/3902] drm/panthor: Prevent potential UAF in group creation [ Upstream commit eec7e23d848d2194dd8791fcd0f4a54d4378eecd ] This commit prevents the possibility of a use after free issue in the GROUP_CREATE ioctl function, which arose as pointer to the group is accessed in that ioctl function after storing it in the Xarray. A malicious userspace can second guess the handle of a group and try to call GROUP_DESTROY ioctl from another thread around the same time as GROUP_CREATE ioctl. To prevent the use after free exploit, this commit uses a mark on an entry of group pool Xarray which is added just before returning from the GROUP_CREATE ioctl function. The mark is checked for all ioctls that specify the group handle and so userspace won't be abe to delete a group that isn't marked yet. v2: Add R-bs and fixes tags Fixes: de85488138247 ("drm/panthor: Add the scheduler logical block") Co-developed-by: Boris Brezillon Signed-off-by: Boris Brezillon Signed-off-by: Akash Goel Reviewed-by: Boris Brezillon Reviewed-by: Steven Price Reviewed-by: Chia-I Wu Link: https://patch.msgid.link/20251127164912.3788155-1-akash.goel@arm.com Signed-off-by: Sasha Levin --- drivers/gpu/drm/panthor/panthor_sched.c | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/drivers/gpu/drm/panthor/panthor_sched.c b/drivers/gpu/drm/panthor/panthor_sched.c index 0279e19aadae93..881a07ffbabc8b 100644 --- a/drivers/gpu/drm/panthor/panthor_sched.c +++ b/drivers/gpu/drm/panthor/panthor_sched.c @@ -772,6 +772,12 @@ struct panthor_job_profiling_data { */ #define MAX_GROUPS_PER_POOL 128 +/* + * Mark added on an entry of group pool Xarray to identify if the group has + * been fully initialized and can be accessed elsewhere in the driver code. + */ +#define GROUP_REGISTERED XA_MARK_1 + /** * struct panthor_group_pool - Group pool * @@ -2900,7 +2906,7 @@ void panthor_fdinfo_gather_group_samples(struct panthor_file *pfile) return; xa_lock(&gpool->xa); - xa_for_each(&gpool->xa, i, group) { + xa_for_each_marked(&gpool->xa, i, group, GROUP_REGISTERED) { guard(spinlock)(&group->fdinfo.lock); pfile->stats.cycles += group->fdinfo.data.cycles; pfile->stats.time += group->fdinfo.data.time; @@ -3575,6 +3581,8 @@ int panthor_group_create(struct panthor_file *pfile, group_init_task_info(group); + xa_set_mark(&gpool->xa, gid, GROUP_REGISTERED); + return gid; err_put_group: @@ -3589,6 +3597,9 @@ int panthor_group_destroy(struct panthor_file *pfile, u32 group_handle) struct panthor_scheduler *sched = ptdev->scheduler; struct panthor_group *group; + if (!xa_get_mark(&gpool->xa, group_handle, GROUP_REGISTERED)) + return -EINVAL; + group = xa_erase(&gpool->xa, group_handle); if (!group) return -EINVAL; @@ -3614,12 +3625,12 @@ int panthor_group_destroy(struct panthor_file *pfile, u32 group_handle) } static struct panthor_group *group_from_handle(struct panthor_group_pool *pool, - u32 group_handle) + unsigned long group_handle) { struct panthor_group *group; xa_lock(&pool->xa); - group = group_get(xa_load(&pool->xa, group_handle)); + group = group_get(xa_find(&pool->xa, &group_handle, group_handle, GROUP_REGISTERED)); xa_unlock(&pool->xa); return group; @@ -3706,7 +3717,7 @@ panthor_fdinfo_gather_group_mem_info(struct panthor_file *pfile, return; xa_lock(&gpool->xa); - xa_for_each(&gpool->xa, i, group) { + xa_for_each_marked(&gpool->xa, i, group, GROUP_REGISTERED) { stats->resident += group->fdinfo.kbo_sizes; if (group->csg_id >= 0) stats->active += group->fdinfo.kbo_sizes; From 699e32e42faf9b7948a19c84c1dc52b52f04aac5 Mon Sep 17 00:00:00 2001 From: Trond Myklebust Date: Fri, 28 Nov 2025 13:39:07 -0500 Subject: [PATCH 0911/3902] Revert "nfs: ignore SB_RDONLY when remounting nfs" [ Upstream commit 400fa37afbb11a601c204b72af0f0e5bc2db695c ] This reverts commit 80c4de6ab44c14e910117a02f2f8241ffc6ec54a. Silently ignoring the "ro" and "rw" mount options causes user confusion, and regressions. Reported-by: Alkis Georgopoulos Cc: Li Lingfeng Fixes: 80c4de6ab44c ("nfs: ignore SB_RDONLY when remounting nfs") Signed-off-by: Trond Myklebust Signed-off-by: Sasha Levin --- fs/nfs/super.c | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/fs/nfs/super.c b/fs/nfs/super.c index 72dee6f3050e67..527000f5d150cc 100644 --- a/fs/nfs/super.c +++ b/fs/nfs/super.c @@ -1051,16 +1051,6 @@ int nfs_reconfigure(struct fs_context *fc) sync_filesystem(sb); - /* - * The SB_RDONLY flag has been removed from the superblock during - * mounts to prevent interference between different filesystems. - * Similarly, it is also necessary to ignore the SB_RDONLY flag - * during reconfiguration; otherwise, it may also result in the - * creation of redundant superblocks when mounting a directory with - * different rw and ro flags multiple times. - */ - fc->sb_flags_mask &= ~SB_RDONLY; - /* * Userspace mount programs that send binary options generally send * them populated with default values. We have no way to know which From 10d58baf7d0e0f6add673d24d83c665d7b62c0c9 Mon Sep 17 00:00:00 2001 From: Trond Myklebust Date: Fri, 28 Nov 2025 13:39:38 -0500 Subject: [PATCH 0912/3902] Revert "nfs: clear SB_RDONLY before getting superblock" [ Upstream commit d216b698d44e33417ad4cc796cb04ccddbb8c0ee ] This reverts commit 8cd9b785943c57a136536250da80ba1eb6f8eb18. Silently ignoring the "ro" and "rw" mount options causes user confusion, and regressions. Reported-by: Alkis Georgopoulos Cc: Li Lingfeng Fixes: 8cd9b785943c ("nfs: clear SB_RDONLY before getting superblock") Signed-off-by: Trond Myklebust Signed-off-by: Sasha Levin --- fs/nfs/super.c | 9 --------- 1 file changed, 9 deletions(-) diff --git a/fs/nfs/super.c b/fs/nfs/super.c index 527000f5d150cc..9b9464e70a7f00 100644 --- a/fs/nfs/super.c +++ b/fs/nfs/super.c @@ -1308,17 +1308,8 @@ int nfs_get_tree_common(struct fs_context *fc) if (IS_ERR(server)) return PTR_ERR(server); - /* - * When NFS_MOUNT_UNSHARED is not set, NFS forces the sharing of a - * superblock among each filesystem that mounts sub-directories - * belonging to a single exported root path. - * To prevent interference between different filesystems, the - * SB_RDONLY flag should be removed from the superblock. - */ if (server->flags & NFS_MOUNT_UNSHARED) compare_super = NULL; - else - fc->sb_flags &= ~SB_RDONLY; /* -o noac implies -o sync */ if (server->flags & NFS_MOUNT_NOAC) From c30cb13edcbff4145fe1bbb5898126717fe07f6a Mon Sep 17 00:00:00 2001 From: Trond Myklebust Date: Fri, 28 Nov 2025 13:39:45 -0500 Subject: [PATCH 0913/3902] Revert "nfs: ignore SB_RDONLY when mounting nfs" [ Upstream commit d4a26d34f1946142f9d32e540490e4926ae9a46b ] This reverts commit 52cb7f8f177878b4f22397b9c4d2c8f743766be3. Silently ignoring the "ro" and "rw" mount options causes user confusion, and regressions. Reported-by: Alkis Georgopoulos Cc: Li Lingfeng Fixes: 52cb7f8f1778 ("nfs: ignore SB_RDONLY when mounting nfs") Signed-off-by: Trond Myklebust Signed-off-by: Sasha Levin --- fs/nfs/internal.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fs/nfs/internal.h b/fs/nfs/internal.h index 2ecd38e1d17a80..ffd382aa31ac0c 100644 --- a/fs/nfs/internal.h +++ b/fs/nfs/internal.h @@ -13,7 +13,7 @@ #include #include -#define NFS_SB_MASK (SB_NOSUID|SB_NODEV|SB_NOEXEC|SB_SYNCHRONOUS) +#define NFS_SB_MASK (SB_RDONLY|SB_NOSUID|SB_NODEV|SB_NOEXEC|SB_SYNCHRONOUS) extern const struct export_operations nfs_export_ops; From df9b003a2ecacc7218486fbb31fe008c93097d5f Mon Sep 17 00:00:00 2001 From: Trond Myklebust Date: Fri, 28 Nov 2025 14:22:44 -0500 Subject: [PATCH 0914/3902] NFS: Automounted filesystems should inherit ro,noexec,nodev,sync flags [ Upstream commit 8675c69816e4276b979ff475ee5fac4688f80125 ] When a filesystem is being automounted, it needs to preserve the user-set superblock mount options, such as the "ro" flag. Reported-by: Li Lingfeng Link: https://lore.kernel.org/all/20240604112636.236517-3-lilingfeng@huaweicloud.com/ Fixes: f2aedb713c28 ("NFS: Add fs_context support.") Signed-off-by: Trond Myklebust Signed-off-by: Sasha Levin --- fs/nfs/namespace.c | 6 ++++++ fs/nfs/super.c | 4 ---- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/fs/nfs/namespace.c b/fs/nfs/namespace.c index 5a4d193da1a98b..dca055676c4f31 100644 --- a/fs/nfs/namespace.c +++ b/fs/nfs/namespace.c @@ -149,6 +149,7 @@ struct vfsmount *nfs_d_automount(struct path *path) struct vfsmount *mnt = ERR_PTR(-ENOMEM); struct nfs_server *server = NFS_SB(path->dentry->d_sb); struct nfs_client *client = server->nfs_client; + unsigned long s_flags = path->dentry->d_sb->s_flags; int timeout = READ_ONCE(nfs_mountpoint_expiry_timeout); int ret; @@ -174,6 +175,11 @@ struct vfsmount *nfs_d_automount(struct path *path) fc->net_ns = get_net(client->cl_net); } + /* Inherit the flags covered by NFS_SB_MASK */ + fc->sb_flags_mask |= NFS_SB_MASK; + fc->sb_flags &= ~NFS_SB_MASK; + fc->sb_flags |= s_flags & NFS_SB_MASK; + /* for submounts we want the same server; referrals will reassign */ memcpy(&ctx->nfs_server._address, &client->cl_addr, client->cl_addrlen); ctx->nfs_server.addrlen = client->cl_addrlen; diff --git a/fs/nfs/super.c b/fs/nfs/super.c index 9b9464e70a7f00..66413133b43e3f 100644 --- a/fs/nfs/super.c +++ b/fs/nfs/super.c @@ -1315,10 +1315,6 @@ int nfs_get_tree_common(struct fs_context *fc) if (server->flags & NFS_MOUNT_NOAC) fc->sb_flags |= SB_SYNCHRONOUS; - if (ctx->clone_data.sb) - if (ctx->clone_data.sb->s_flags & SB_SYNCHRONOUS) - fc->sb_flags |= SB_SYNCHRONOUS; - /* Get a superblock - note that we may end up sharing one that already exists */ fc->s_fs_info = server; s = sget_fc(fc, compare_super, nfs_set_super); From 6d335d8a26bf79092d2fd3486546ec8a466470fd Mon Sep 17 00:00:00 2001 From: Trond Myklebust Date: Fri, 28 Nov 2025 16:06:41 -0500 Subject: [PATCH 0915/3902] NFS: Fix inheritance of the block sizes when automounting [ Upstream commit 2b092175f5e301cdaa935093edfef2be9defb6df ] Only inherit the block sizes that were actually specified as mount parameters for the parent mount. Fixes: 62a55d088cd8 ("NFS: Additional refactoring for fs_context conversion") Signed-off-by: Trond Myklebust Signed-off-by: Sasha Levin --- fs/nfs/client.c | 21 +++++++++++++++++---- fs/nfs/internal.h | 1 - fs/nfs/namespace.c | 5 ++++- fs/nfs/nfs4client.c | 18 ++++++++++++++---- fs/nfs/super.c | 10 +++------- include/linux/nfs_fs_sb.h | 5 +++++ 6 files changed, 43 insertions(+), 17 deletions(-) diff --git a/fs/nfs/client.c b/fs/nfs/client.c index 54699299d5b168..2aaea9c98c2cd0 100644 --- a/fs/nfs/client.c +++ b/fs/nfs/client.c @@ -784,10 +784,18 @@ static int nfs_init_server(struct nfs_server *server, server->fattr_valid = NFS_ATTR_FATTR_V4; } - if (ctx->rsize) + if (ctx->bsize) { + server->bsize = ctx->bsize; + server->automount_inherit |= NFS_AUTOMOUNT_INHERIT_BSIZE; + } + if (ctx->rsize) { server->rsize = nfs_io_size(ctx->rsize, clp->cl_proto); - if (ctx->wsize) + server->automount_inherit |= NFS_AUTOMOUNT_INHERIT_RSIZE; + } + if (ctx->wsize) { server->wsize = nfs_io_size(ctx->wsize, clp->cl_proto); + server->automount_inherit |= NFS_AUTOMOUNT_INHERIT_WSIZE; + } server->acregmin = ctx->acregmin * HZ; server->acregmax = ctx->acregmax * HZ; @@ -977,8 +985,13 @@ EXPORT_SYMBOL_GPL(nfs_probe_server); void nfs_server_copy_userdata(struct nfs_server *target, struct nfs_server *source) { target->flags = source->flags; - target->rsize = source->rsize; - target->wsize = source->wsize; + target->automount_inherit = source->automount_inherit; + if (source->automount_inherit & NFS_AUTOMOUNT_INHERIT_BSIZE) + target->bsize = source->bsize; + if (source->automount_inherit & NFS_AUTOMOUNT_INHERIT_RSIZE) + target->rsize = source->rsize; + if (source->automount_inherit & NFS_AUTOMOUNT_INHERIT_WSIZE) + target->wsize = source->wsize; target->acregmin = source->acregmin; target->acregmax = source->acregmax; target->acdirmin = source->acdirmin; diff --git a/fs/nfs/internal.h b/fs/nfs/internal.h index ffd382aa31ac0c..2e596244799f39 100644 --- a/fs/nfs/internal.h +++ b/fs/nfs/internal.h @@ -152,7 +152,6 @@ struct nfs_fs_context { struct super_block *sb; struct dentry *dentry; struct nfs_fattr *fattr; - unsigned int inherited_bsize; } clone_data; }; diff --git a/fs/nfs/namespace.c b/fs/nfs/namespace.c index dca055676c4f31..9e4d94f41fc674 100644 --- a/fs/nfs/namespace.c +++ b/fs/nfs/namespace.c @@ -190,6 +190,10 @@ struct vfsmount *nfs_d_automount(struct path *path) ctx->nfs_mod = client->cl_nfs_mod; get_nfs_version(ctx->nfs_mod); + /* Inherit block sizes if they were specified as mount parameters */ + if (server->automount_inherit & NFS_AUTOMOUNT_INHERIT_BSIZE) + ctx->bsize = server->bsize; + ret = client->rpc_ops->submount(fc, server); if (ret < 0) { mnt = ERR_PTR(ret); @@ -289,7 +293,6 @@ int nfs_do_submount(struct fs_context *fc) return -ENOMEM; ctx->internal = true; - ctx->clone_data.inherited_bsize = ctx->clone_data.sb->s_blocksize_bits; p = nfs_devname(dentry, buffer, 4096); if (IS_ERR(p)) { diff --git a/fs/nfs/nfs4client.c b/fs/nfs/nfs4client.c index 3a4baed993c96f..4ff0e9dd1145e2 100644 --- a/fs/nfs/nfs4client.c +++ b/fs/nfs/nfs4client.c @@ -1174,10 +1174,20 @@ static int nfs4_init_server(struct nfs_server *server, struct fs_context *fc) if (error < 0) return error; - if (ctx->rsize) - server->rsize = nfs_io_size(ctx->rsize, server->nfs_client->cl_proto); - if (ctx->wsize) - server->wsize = nfs_io_size(ctx->wsize, server->nfs_client->cl_proto); + if (ctx->bsize) { + server->bsize = ctx->bsize; + server->automount_inherit |= NFS_AUTOMOUNT_INHERIT_BSIZE; + } + if (ctx->rsize) { + server->rsize = + nfs_io_size(ctx->rsize, server->nfs_client->cl_proto); + server->automount_inherit |= NFS_AUTOMOUNT_INHERIT_RSIZE; + } + if (ctx->wsize) { + server->wsize = + nfs_io_size(ctx->wsize, server->nfs_client->cl_proto); + server->automount_inherit |= NFS_AUTOMOUNT_INHERIT_WSIZE; + } server->acregmin = ctx->acregmin * HZ; server->acregmax = ctx->acregmax * HZ; diff --git a/fs/nfs/super.c b/fs/nfs/super.c index 66413133b43e3f..57d372db03b936 100644 --- a/fs/nfs/super.c +++ b/fs/nfs/super.c @@ -1091,8 +1091,9 @@ static void nfs_fill_super(struct super_block *sb, struct nfs_fs_context *ctx) sb->s_blocksize = 0; sb->s_xattr = server->nfs_client->cl_nfs_mod->xattr; sb->s_op = server->nfs_client->cl_nfs_mod->sops; - if (ctx->bsize) - sb->s_blocksize = nfs_block_size(ctx->bsize, &sb->s_blocksize_bits); + if (server->bsize) + sb->s_blocksize = + nfs_block_size(server->bsize, &sb->s_blocksize_bits); switch (server->nfs_client->rpc_ops->version) { case 2: @@ -1338,13 +1339,8 @@ int nfs_get_tree_common(struct fs_context *fc) } if (!s->s_root) { - unsigned bsize = ctx->clone_data.inherited_bsize; /* initial superblock/root creation */ nfs_fill_super(s, ctx); - if (bsize) { - s->s_blocksize_bits = bsize; - s->s_blocksize = 1U << bsize; - } error = nfs_get_cache_cookie(s, ctx); if (error < 0) goto error_splat_super; diff --git a/include/linux/nfs_fs_sb.h b/include/linux/nfs_fs_sb.h index d30c0245031c07..30ac384e011a45 100644 --- a/include/linux/nfs_fs_sb.h +++ b/include/linux/nfs_fs_sb.h @@ -172,6 +172,11 @@ struct nfs_server { #define NFS_MOUNT_FORCE_RDIRPLUS 0x20000000 #define NFS_MOUNT_NETUNREACH_FATAL 0x40000000 + unsigned int automount_inherit; /* Properties inherited by automount */ +#define NFS_AUTOMOUNT_INHERIT_BSIZE 0x0001 +#define NFS_AUTOMOUNT_INHERIT_RSIZE 0x0002 +#define NFS_AUTOMOUNT_INHERIT_WSIZE 0x0004 + unsigned int caps; /* server capabilities */ __u64 fattr_valid; /* Valid attributes */ unsigned int rsize; /* read size */ From c3430e94a0a235928253d9d06fde7d2dd7300d64 Mon Sep 17 00:00:00 2001 From: Armin Wolf Date: Sat, 29 Nov 2025 12:15:35 +0100 Subject: [PATCH 0916/3902] fs/nls: Fix inconsistency between utf8_to_utf32() and utf32_to_utf8() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [ Upstream commit c36f9d7b2869a003a2f7d6ff2c6bac9e62fd7d68 ] After commit 25524b619029 ("fs/nls: Fix utf16 to utf8 conversion"), the return values of utf8_to_utf32() and utf32_to_utf8() are inconsistent when encountering an error: utf8_to_utf32() returns -1, while utf32_to_utf8() returns errno codes. Fix this inconsistency by modifying utf8_to_utf32() to return errno codes as well. Fixes: 25524b619029 ("fs/nls: Fix utf16 to utf8 conversion") Suggested-by: Andy Shevchenko Reviewed-by: Andy Shevchenko Signed-off-by: Armin Wolf Link: https://patch.msgid.link/20251129111535.8984-1-W_Armin@gmx.de Reviewed-by: Ilpo Järvinen Signed-off-by: Ilpo Järvinen Signed-off-by: Sasha Levin --- fs/nls/nls_base.c | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/fs/nls/nls_base.c b/fs/nls/nls_base.c index d434c4463a8f72..a5c3a9f1b8dc59 100644 --- a/fs/nls/nls_base.c +++ b/fs/nls/nls_base.c @@ -67,19 +67,22 @@ int utf8_to_utf32(const u8 *s, int inlen, unicode_t *pu) l &= t->lmask; if (l < t->lval || l > UNICODE_MAX || (l & SURROGATE_MASK) == SURROGATE_PAIR) - return -1; + return -EILSEQ; + *pu = (unicode_t) l; return nc; } if (inlen <= nc) - return -1; + return -EOVERFLOW; + s++; c = (*s ^ 0x80) & 0xFF; if (c & 0xC0) - return -1; + return -EILSEQ; + l = (l << 6) | c; } - return -1; + return -EILSEQ; } EXPORT_SYMBOL(utf8_to_utf32); From 2606e7e090fe19237cf8604e3445e1c4cf657c88 Mon Sep 17 00:00:00 2001 From: Anton Khirnov Date: Sat, 29 Nov 2025 11:13:08 +0100 Subject: [PATCH 0917/3902] platform/x86: asus-wmi: use brightness_set_blocking() for kbd led MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [ Upstream commit ccb61a328321ba3f8567e350664c9ca7a42b6c70 ] kbd_led_set() can sleep, and so may not be used as the brightness_set() callback. Otherwise using this led with a trigger leads to system hangs accompanied by: BUG: scheduling while atomic: acpi_fakekeyd/2588/0x00000003 CPU: 4 UID: 0 PID: 2588 Comm: acpi_fakekeyd Not tainted 6.17.9+deb14-amd64 #1 PREEMPT(lazy) Debian 6.17.9-1 Hardware name: ASUSTeK COMPUTER INC. ASUS EXPERTBOOK B9403CVAR/B9403CVAR, BIOS B9403CVAR.311 12/24/2024 Call Trace: [...] schedule_timeout+0xbd/0x100 __down_common+0x175/0x290 down_timeout+0x67/0x70 acpi_os_wait_semaphore+0x57/0x90 [...] asus_wmi_evaluate_method3+0x87/0x190 [asus_wmi] led_trigger_event+0x3f/0x60 [...] Fixes: 9fe44fc98ce4 ("platform/x86: asus-wmi: Simplify the keyboard brightness updating process") Signed-off-by: Anton Khirnov Reviewed-by: Andy Shevchenko Reviewed-by: Denis Benato Link: https://patch.msgid.link/20251129101307.18085-3-anton@khirnov.net Reviewed-by: Ilpo Järvinen Signed-off-by: Ilpo Järvinen Signed-off-by: Sasha Levin --- drivers/platform/x86/asus-wmi.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/drivers/platform/x86/asus-wmi.c b/drivers/platform/x86/asus-wmi.c index e72a2b5d158e9c..8e3300f5c2943a 100644 --- a/drivers/platform/x86/asus-wmi.c +++ b/drivers/platform/x86/asus-wmi.c @@ -1619,14 +1619,14 @@ static void do_kbd_led_set(struct led_classdev *led_cdev, int value) kbd_led_update(asus); } -static void kbd_led_set(struct led_classdev *led_cdev, - enum led_brightness value) +static int kbd_led_set(struct led_classdev *led_cdev, enum led_brightness value) { /* Prevent disabling keyboard backlight on module unregister */ if (led_cdev->flags & LED_UNREGISTERING) - return; + return 0; do_kbd_led_set(led_cdev, value); + return 0; } static void kbd_led_set_by_kbd(struct asus_wmi *asus, enum led_brightness value) @@ -1802,7 +1802,7 @@ static int asus_wmi_led_init(struct asus_wmi *asus) asus->kbd_led_wk = led_val; asus->kbd_led.name = "asus::kbd_backlight"; asus->kbd_led.flags = LED_BRIGHT_HW_CHANGED; - asus->kbd_led.brightness_set = kbd_led_set; + asus->kbd_led.brightness_set_blocking = kbd_led_set; asus->kbd_led.brightness_get = kbd_led_get; asus->kbd_led.max_brightness = 3; From 488ef9dd5c094a96d506a502aa467b983484c4b5 Mon Sep 17 00:00:00 2001 From: Haotian Zhang Date: Tue, 2 Dec 2025 18:16:42 +0800 Subject: [PATCH 0918/3902] ASoC: bcm: bcm63xx-pcm-whistler: Check return value of of_dma_configure() [ Upstream commit 0ebbd45c33d0049ebf5a22c1434567f0c420b333 ] bcm63xx_soc_pcm_new() does not check the return value of of_dma_configure(), which may fail with -EPROBE_DEFER or other errors, allowing PCM setup to continue with incomplete DMA configuration. Add error checking for of_dma_configure() and return on failure. Fixes: 88eb404ccc3e ("ASoC: brcm: Add DSL/PON SoC audio driver") Signed-off-by: Haotian Zhang Link: https://patch.msgid.link/20251202101642.492-1-vulab@iscas.ac.cn Signed-off-by: Mark Brown Signed-off-by: Sasha Levin --- sound/soc/bcm/bcm63xx-pcm-whistler.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/sound/soc/bcm/bcm63xx-pcm-whistler.c b/sound/soc/bcm/bcm63xx-pcm-whistler.c index e3a4fcc63a56dc..efeb06ddabeb3b 100644 --- a/sound/soc/bcm/bcm63xx-pcm-whistler.c +++ b/sound/soc/bcm/bcm63xx-pcm-whistler.c @@ -358,7 +358,9 @@ static int bcm63xx_soc_pcm_new(struct snd_soc_component *component, i2s_priv = dev_get_drvdata(snd_soc_rtd_to_cpu(rtd, 0)->dev); - of_dma_configure(pcm->card->dev, pcm->card->dev->of_node, 1); + ret = of_dma_configure(pcm->card->dev, pcm->card->dev->of_node, 1); + if (ret) + return ret; ret = dma_coerce_mask_and_coherent(pcm->card->dev, DMA_BIT_MASK(32)); if (ret) From a52d027b4d6bc818b17f2523be2c2464b9db79be Mon Sep 17 00:00:00 2001 From: Hemalatha Pinnamreddy Date: Wed, 3 Dec 2025 12:16:48 +0530 Subject: [PATCH 0919/3902] ASoC: amd: acp: Audio is not resuming after s0ix [ Upstream commit 3ee257aba1d56c3f0f1028669a8ad0f1a477f05b ] Audio fails to resume after system exits suspend mode due to accessing incorrect ring buffer address during resume. This patch resolves issue by selecting correct address based on the ACP version. Fixes: f6f7d25b11033 ("ASoC: amd: acp: Add pte configuration for ACP7.0 platform") Signed-off-by: Hemalatha Pinnamreddy Signed-off-by: Raghavendra Prasad Mallela Reviewed-by: Mario Limonciello (AMD) Link: https://patch.msgid.link/20251203064650.2554625-1-raghavendraprasad.mallela@amd.com Signed-off-by: Mark Brown Signed-off-by: Sasha Levin --- sound/soc/amd/acp/acp-legacy-common.c | 30 +++++++++++++++++++++------ 1 file changed, 24 insertions(+), 6 deletions(-) diff --git a/sound/soc/amd/acp/acp-legacy-common.c b/sound/soc/amd/acp/acp-legacy-common.c index 3078f459e0050b..4e477c48d4bdd1 100644 --- a/sound/soc/amd/acp/acp-legacy-common.c +++ b/sound/soc/amd/acp/acp-legacy-common.c @@ -219,7 +219,10 @@ static int set_acp_i2s_dma_fifo(struct snd_pcm_substream *substream, SP_PB_FIFO_ADDR_OFFSET; reg_fifo_addr = ACP_I2S_TX_FIFOADDR(chip); reg_fifo_size = ACP_I2S_TX_FIFOSIZE(chip); - phy_addr = I2S_SP_TX_MEM_WINDOW_START + stream->reg_offset; + if (chip->acp_rev >= ACP70_PCI_ID) + phy_addr = ACP7x_I2S_SP_TX_MEM_WINDOW_START; + else + phy_addr = I2S_SP_TX_MEM_WINDOW_START + stream->reg_offset; writel(phy_addr, chip->base + ACP_I2S_TX_RINGBUFADDR(chip)); } else { reg_dma_size = ACP_I2S_RX_DMA_SIZE(chip); @@ -227,7 +230,10 @@ static int set_acp_i2s_dma_fifo(struct snd_pcm_substream *substream, SP_CAPT_FIFO_ADDR_OFFSET; reg_fifo_addr = ACP_I2S_RX_FIFOADDR(chip); reg_fifo_size = ACP_I2S_RX_FIFOSIZE(chip); - phy_addr = I2S_SP_RX_MEM_WINDOW_START + stream->reg_offset; + if (chip->acp_rev >= ACP70_PCI_ID) + phy_addr = ACP7x_I2S_SP_RX_MEM_WINDOW_START; + else + phy_addr = I2S_SP_RX_MEM_WINDOW_START + stream->reg_offset; writel(phy_addr, chip->base + ACP_I2S_RX_RINGBUFADDR(chip)); } break; @@ -238,7 +244,10 @@ static int set_acp_i2s_dma_fifo(struct snd_pcm_substream *substream, BT_PB_FIFO_ADDR_OFFSET; reg_fifo_addr = ACP_BT_TX_FIFOADDR(chip); reg_fifo_size = ACP_BT_TX_FIFOSIZE(chip); - phy_addr = I2S_BT_TX_MEM_WINDOW_START + stream->reg_offset; + if (chip->acp_rev >= ACP70_PCI_ID) + phy_addr = ACP7x_I2S_BT_TX_MEM_WINDOW_START; + else + phy_addr = I2S_BT_TX_MEM_WINDOW_START + stream->reg_offset; writel(phy_addr, chip->base + ACP_BT_TX_RINGBUFADDR(chip)); } else { reg_dma_size = ACP_BT_RX_DMA_SIZE(chip); @@ -246,7 +255,10 @@ static int set_acp_i2s_dma_fifo(struct snd_pcm_substream *substream, BT_CAPT_FIFO_ADDR_OFFSET; reg_fifo_addr = ACP_BT_RX_FIFOADDR(chip); reg_fifo_size = ACP_BT_RX_FIFOSIZE(chip); - phy_addr = I2S_BT_TX_MEM_WINDOW_START + stream->reg_offset; + if (chip->acp_rev >= ACP70_PCI_ID) + phy_addr = ACP7x_I2S_BT_RX_MEM_WINDOW_START; + else + phy_addr = I2S_BT_RX_MEM_WINDOW_START + stream->reg_offset; writel(phy_addr, chip->base + ACP_BT_RX_RINGBUFADDR(chip)); } break; @@ -257,7 +269,10 @@ static int set_acp_i2s_dma_fifo(struct snd_pcm_substream *substream, HS_PB_FIFO_ADDR_OFFSET; reg_fifo_addr = ACP_HS_TX_FIFOADDR; reg_fifo_size = ACP_HS_TX_FIFOSIZE; - phy_addr = I2S_HS_TX_MEM_WINDOW_START + stream->reg_offset; + if (chip->acp_rev >= ACP70_PCI_ID) + phy_addr = ACP7x_I2S_HS_TX_MEM_WINDOW_START; + else + phy_addr = I2S_HS_TX_MEM_WINDOW_START + stream->reg_offset; writel(phy_addr, chip->base + ACP_HS_TX_RINGBUFADDR); } else { reg_dma_size = ACP_HS_RX_DMA_SIZE; @@ -265,7 +280,10 @@ static int set_acp_i2s_dma_fifo(struct snd_pcm_substream *substream, HS_CAPT_FIFO_ADDR_OFFSET; reg_fifo_addr = ACP_HS_RX_FIFOADDR; reg_fifo_size = ACP_HS_RX_FIFOSIZE; - phy_addr = I2S_HS_RX_MEM_WINDOW_START + stream->reg_offset; + if (chip->acp_rev >= ACP70_PCI_ID) + phy_addr = ACP7x_I2S_HS_RX_MEM_WINDOW_START; + else + phy_addr = I2S_HS_RX_MEM_WINDOW_START + stream->reg_offset; writel(phy_addr, chip->base + ACP_HS_RX_RINGBUFADDR); } break; From a15353ac167fad88af1e03781b869d51c5f54860 Mon Sep 17 00:00:00 2001 From: Shengjiu Wang Date: Wed, 3 Dec 2025 18:05:28 +0800 Subject: [PATCH 0920/3902] ASoC: ak4458: Disable regulator when error happens [ Upstream commit ae585fabb9713a43e358cf606451386757225c95 ] Disable regulator in runtime resume when error happens to balance the reference count of regulator. Fixes: 7e3096e8f823 ("ASoC: ak4458: Add regulator support") Signed-off-by: Shengjiu Wang Link: https://patch.msgid.link/20251203100529.3841203-2-shengjiu.wang@nxp.com Signed-off-by: Mark Brown Signed-off-by: Sasha Levin --- sound/soc/codecs/ak4458.c | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/sound/soc/codecs/ak4458.c b/sound/soc/codecs/ak4458.c index 57cf601d3df354..a6c04dd3de3ed7 100644 --- a/sound/soc/codecs/ak4458.c +++ b/sound/soc/codecs/ak4458.c @@ -671,7 +671,15 @@ static int ak4458_runtime_resume(struct device *dev) regcache_cache_only(ak4458->regmap, false); regcache_mark_dirty(ak4458->regmap); - return regcache_sync(ak4458->regmap); + ret = regcache_sync(ak4458->regmap); + if (ret) + goto err; + + return 0; +err: + regcache_cache_only(ak4458->regmap, true); + regulator_bulk_disable(ARRAY_SIZE(ak4458->supplies), ak4458->supplies); + return ret; } static const struct snd_soc_component_driver soc_codec_dev_ak4458 = { From 9a3192f81819da52f3f67b42955da873382dff57 Mon Sep 17 00:00:00 2001 From: Shengjiu Wang Date: Wed, 3 Dec 2025 18:05:29 +0800 Subject: [PATCH 0921/3902] ASoC: ak5558: Disable regulator when error happens [ Upstream commit 1f8f726a2a29c28f65b30880335a1610c5e63594 ] Disable regulator in runtime resume when error happens to balance the reference count of regulator. Fixes: 2ff6d5a108c6 ("ASoC: ak5558: Add regulator support") Signed-off-by: Shengjiu Wang Link: https://patch.msgid.link/20251203100529.3841203-3-shengjiu.wang@nxp.com Signed-off-by: Mark Brown Signed-off-by: Sasha Levin --- sound/soc/codecs/ak5558.c | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/sound/soc/codecs/ak5558.c b/sound/soc/codecs/ak5558.c index 683f3e472f5000..73684fc5beb1a7 100644 --- a/sound/soc/codecs/ak5558.c +++ b/sound/soc/codecs/ak5558.c @@ -372,7 +372,15 @@ static int ak5558_runtime_resume(struct device *dev) regcache_cache_only(ak5558->regmap, false); regcache_mark_dirty(ak5558->regmap); - return regcache_sync(ak5558->regmap); + ret = regcache_sync(ak5558->regmap); + if (ret) + goto err; + + return 0; +err: + regcache_cache_only(ak5558->regmap, true); + regulator_bulk_disable(ARRAY_SIZE(ak5558->supplies), ak5558->supplies); + return ret; } static const struct dev_pm_ops ak5558_pm = { From 6acd144fa8b115d4e7078e3882ac30151d01219a Mon Sep 17 00:00:00 2001 From: Daeho Jeong Date: Tue, 11 Nov 2025 09:52:46 -0800 Subject: [PATCH 0922/3902] f2fs: revert summary entry count from 2048 to 512 in 16kb block support [ Upstream commit 7ee8bc3942f20964ad730871b885688ea3a2961a ] The recent increase in the number of Segment Summary Area (SSA) entries from 512 to 2048 was an unintentional change in logic of 16kb block support. This commit corrects the issue. To better utilize the space available from the erroneous 2048-entry calculation, we are implementing a solution to share the currently unused SSA space with neighboring segments. This enhances overall SSA utilization without impacting the established 8MB segment size. Fixes: d7e9a9037de2 ("f2fs: Support Block Size == Page Size") Signed-off-by: Daeho Jeong Reviewed-by: Chao Yu Signed-off-by: Jaegeuk Kim Signed-off-by: Sasha Levin --- fs/f2fs/f2fs.h | 2 + fs/f2fs/gc.c | 117 +++++++++++++++++++++++----------------- fs/f2fs/recovery.c | 2 +- fs/f2fs/segment.c | 38 +++++++++---- fs/f2fs/segment.h | 8 ++- fs/f2fs/super.c | 14 +++++ fs/f2fs/sysfs.c | 7 +++ include/linux/f2fs_fs.h | 5 +- 8 files changed, 130 insertions(+), 63 deletions(-) diff --git a/fs/f2fs/f2fs.h b/fs/f2fs/f2fs.h index 5b4e9548a231f3..5149f351f03d27 100644 --- a/fs/f2fs/f2fs.h +++ b/fs/f2fs/f2fs.h @@ -245,6 +245,7 @@ struct f2fs_mount_info { #define F2FS_FEATURE_COMPRESSION 0x00002000 #define F2FS_FEATURE_RO 0x00004000 #define F2FS_FEATURE_DEVICE_ALIAS 0x00008000 +#define F2FS_FEATURE_PACKED_SSA 0x00010000 #define __F2FS_HAS_FEATURE(raw_super, mask) \ ((raw_super->feature & cpu_to_le32(mask)) != 0) @@ -4710,6 +4711,7 @@ F2FS_FEATURE_FUNCS(casefold, CASEFOLD); F2FS_FEATURE_FUNCS(compression, COMPRESSION); F2FS_FEATURE_FUNCS(readonly, RO); F2FS_FEATURE_FUNCS(device_alias, DEVICE_ALIAS); +F2FS_FEATURE_FUNCS(packed_ssa, PACKED_SSA); #ifdef CONFIG_BLK_DEV_ZONED static inline bool f2fs_zone_is_seq(struct f2fs_sb_info *sbi, int devi, diff --git a/fs/f2fs/gc.c b/fs/f2fs/gc.c index 8abf521530ff3f..af2f4d28462c0b 100644 --- a/fs/f2fs/gc.c +++ b/fs/f2fs/gc.c @@ -1732,7 +1732,7 @@ static int do_garbage_collect(struct f2fs_sb_info *sbi, unsigned char type = IS_DATASEG(get_seg_entry(sbi, segno)->type) ? SUM_TYPE_DATA : SUM_TYPE_NODE; unsigned char data_type = (type == SUM_TYPE_DATA) ? DATA : NODE; - int submitted = 0; + int submitted = 0, sum_blk_cnt; if (__is_large_section(sbi)) { sec_end_segno = rounddown(end_segno, SEGS_PER_SEC(sbi)); @@ -1766,22 +1766,28 @@ static int do_garbage_collect(struct f2fs_sb_info *sbi, sanity_check_seg_type(sbi, get_seg_entry(sbi, segno)->type); + segno = rounddown(segno, SUMS_PER_BLOCK); + sum_blk_cnt = DIV_ROUND_UP(end_segno - segno, SUMS_PER_BLOCK); /* readahead multi ssa blocks those have contiguous address */ if (__is_large_section(sbi)) f2fs_ra_meta_pages(sbi, GET_SUM_BLOCK(sbi, segno), - end_segno - segno, META_SSA, true); + sum_blk_cnt, META_SSA, true); /* reference all summary page */ while (segno < end_segno) { - struct folio *sum_folio = f2fs_get_sum_folio(sbi, segno++); + struct folio *sum_folio = f2fs_get_sum_folio(sbi, segno); + + segno += SUMS_PER_BLOCK; if (IS_ERR(sum_folio)) { int err = PTR_ERR(sum_folio); - end_segno = segno - 1; - for (segno = start_segno; segno < end_segno; segno++) { + end_segno = segno - SUMS_PER_BLOCK; + segno = rounddown(start_segno, SUMS_PER_BLOCK); + while (segno < end_segno) { sum_folio = filemap_get_folio(META_MAPPING(sbi), GET_SUM_BLOCK(sbi, segno)); folio_put_refs(sum_folio, 2); + segno += SUMS_PER_BLOCK; } return err; } @@ -1790,68 +1796,83 @@ static int do_garbage_collect(struct f2fs_sb_info *sbi, blk_start_plug(&plug); - for (segno = start_segno; segno < end_segno; segno++) { - struct f2fs_summary_block *sum; + segno = start_segno; + while (segno < end_segno) { + unsigned int cur_segno; /* find segment summary of victim */ struct folio *sum_folio = filemap_get_folio(META_MAPPING(sbi), GET_SUM_BLOCK(sbi, segno)); + unsigned int block_end_segno = rounddown(segno, SUMS_PER_BLOCK) + + SUMS_PER_BLOCK; + + if (block_end_segno > end_segno) + block_end_segno = end_segno; if (is_cursec(sbi, GET_SEC_FROM_SEG(sbi, segno))) { f2fs_err(sbi, "%s: segment %u is used by log", __func__, segno); f2fs_bug_on(sbi, 1); - goto skip; + goto next_block; } - if (get_valid_blocks(sbi, segno, false) == 0) - goto freed; - if (gc_type == BG_GC && __is_large_section(sbi) && - migrated >= sbi->migration_granularity) - goto skip; if (!folio_test_uptodate(sum_folio) || unlikely(f2fs_cp_error(sbi))) - goto skip; + goto next_block; - sum = folio_address(sum_folio); - if (type != GET_SUM_TYPE((&sum->footer))) { - f2fs_err(sbi, "Inconsistent segment (%u) type [%d, %d] in SIT and SSA", - segno, type, GET_SUM_TYPE((&sum->footer))); - f2fs_stop_checkpoint(sbi, false, - STOP_CP_REASON_CORRUPTED_SUMMARY); - goto skip; - } + for (cur_segno = segno; cur_segno < block_end_segno; + cur_segno++) { + struct f2fs_summary_block *sum; - /* - * this is to avoid deadlock: - * - lock_page(sum_page) - f2fs_replace_block - * - check_valid_map() - down_write(sentry_lock) - * - down_read(sentry_lock) - change_curseg() - * - lock_page(sum_page) - */ - if (type == SUM_TYPE_NODE) - submitted += gc_node_segment(sbi, sum->entries, segno, - gc_type); - else - submitted += gc_data_segment(sbi, sum->entries, gc_list, - segno, gc_type, - force_migrate); + if (get_valid_blocks(sbi, cur_segno, false) == 0) + goto freed; + if (gc_type == BG_GC && __is_large_section(sbi) && + migrated >= sbi->migration_granularity) + continue; - stat_inc_gc_seg_count(sbi, data_type, gc_type); - sbi->gc_reclaimed_segs[sbi->gc_mode]++; - migrated++; + sum = SUM_BLK_PAGE_ADDR(sum_folio, cur_segno); + if (type != GET_SUM_TYPE((&sum->footer))) { + f2fs_err(sbi, "Inconsistent segment (%u) type " + "[%d, %d] in SSA and SIT", + cur_segno, type, + GET_SUM_TYPE((&sum->footer))); + f2fs_stop_checkpoint(sbi, false, + STOP_CP_REASON_CORRUPTED_SUMMARY); + continue; + } -freed: - if (gc_type == FG_GC && - get_valid_blocks(sbi, segno, false) == 0) - seg_freed++; + /* + * this is to avoid deadlock: + * - lock_page(sum_page) - f2fs_replace_block + * - check_valid_map() - down_write(sentry_lock) + * - down_read(sentry_lock) - change_curseg() + * - lock_page(sum_page) + */ + if (type == SUM_TYPE_NODE) + submitted += gc_node_segment(sbi, sum->entries, + cur_segno, gc_type); + else + submitted += gc_data_segment(sbi, sum->entries, + gc_list, cur_segno, + gc_type, force_migrate); - if (__is_large_section(sbi)) - sbi->next_victim_seg[gc_type] = - (segno + 1 < sec_end_segno) ? - segno + 1 : NULL_SEGNO; -skip: + stat_inc_gc_seg_count(sbi, data_type, gc_type); + sbi->gc_reclaimed_segs[sbi->gc_mode]++; + migrated++; + +freed: + if (gc_type == FG_GC && + get_valid_blocks(sbi, cur_segno, false) == 0) + seg_freed++; + + if (__is_large_section(sbi)) + sbi->next_victim_seg[gc_type] = + (cur_segno + 1 < sec_end_segno) ? + cur_segno + 1 : NULL_SEGNO; + } +next_block: folio_put_refs(sum_folio, 2); + segno = block_end_segno; } if (submitted) diff --git a/fs/f2fs/recovery.c b/fs/f2fs/recovery.c index 215e442db72c82..af72309b9bfc6c 100644 --- a/fs/f2fs/recovery.c +++ b/fs/f2fs/recovery.c @@ -519,7 +519,7 @@ static int check_index_in_prev_nodes(struct f2fs_sb_info *sbi, sum_folio = f2fs_get_sum_folio(sbi, segno); if (IS_ERR(sum_folio)) return PTR_ERR(sum_folio); - sum_node = folio_address(sum_folio); + sum_node = SUM_BLK_PAGE_ADDR(sum_folio, segno); sum = sum_node->entries[blkoff]; f2fs_folio_put(sum_folio, true); got_it: diff --git a/fs/f2fs/segment.c b/fs/f2fs/segment.c index b45eace879d74b..ac84559dc26931 100644 --- a/fs/f2fs/segment.c +++ b/fs/f2fs/segment.c @@ -2712,7 +2712,15 @@ struct folio *f2fs_get_sum_folio(struct f2fs_sb_info *sbi, unsigned int segno) void f2fs_update_meta_page(struct f2fs_sb_info *sbi, void *src, block_t blk_addr) { - struct folio *folio = f2fs_grab_meta_folio(sbi, blk_addr); + struct folio *folio; + + if (SUMS_PER_BLOCK == 1) + folio = f2fs_grab_meta_folio(sbi, blk_addr); + else + folio = f2fs_get_meta_folio_retry(sbi, blk_addr); + + if (IS_ERR(folio)) + return; memcpy(folio_address(folio), src, PAGE_SIZE); folio_mark_dirty(folio); @@ -2720,9 +2728,21 @@ void f2fs_update_meta_page(struct f2fs_sb_info *sbi, } static void write_sum_page(struct f2fs_sb_info *sbi, - struct f2fs_summary_block *sum_blk, block_t blk_addr) + struct f2fs_summary_block *sum_blk, unsigned int segno) { - f2fs_update_meta_page(sbi, (void *)sum_blk, blk_addr); + struct folio *folio; + + if (SUMS_PER_BLOCK == 1) + return f2fs_update_meta_page(sbi, (void *)sum_blk, + GET_SUM_BLOCK(sbi, segno)); + + folio = f2fs_get_sum_folio(sbi, segno); + if (IS_ERR(folio)) + return; + + memcpy(SUM_BLK_PAGE_ADDR(folio, segno), sum_blk, sizeof(*sum_blk)); + folio_mark_dirty(folio); + f2fs_folio_put(folio, true); } static void write_current_sum_page(struct f2fs_sb_info *sbi, @@ -2987,7 +3007,7 @@ static int new_curseg(struct f2fs_sb_info *sbi, int type, bool new_sec) int ret; if (curseg->inited) - write_sum_page(sbi, curseg->sum_blk, GET_SUM_BLOCK(sbi, segno)); + write_sum_page(sbi, curseg->sum_blk, segno); segno = __get_next_segno(sbi, type); ret = get_new_segment(sbi, &segno, new_sec, pinning); @@ -3046,7 +3066,7 @@ static int change_curseg(struct f2fs_sb_info *sbi, int type) struct folio *sum_folio; if (curseg->inited) - write_sum_page(sbi, curseg->sum_blk, GET_SUM_BLOCK(sbi, curseg->segno)); + write_sum_page(sbi, curseg->sum_blk, curseg->segno); __set_test_and_inuse(sbi, new_segno); @@ -3065,7 +3085,7 @@ static int change_curseg(struct f2fs_sb_info *sbi, int type) memset(curseg->sum_blk, 0, SUM_ENTRY_SIZE); return PTR_ERR(sum_folio); } - sum_node = folio_address(sum_folio); + sum_node = SUM_BLK_PAGE_ADDR(sum_folio, new_segno); memcpy(curseg->sum_blk, sum_node, SUM_ENTRY_SIZE); f2fs_folio_put(sum_folio, true); return 0; @@ -3154,8 +3174,7 @@ static void __f2fs_save_inmem_curseg(struct f2fs_sb_info *sbi, int type) goto out; if (get_valid_blocks(sbi, curseg->segno, false)) { - write_sum_page(sbi, curseg->sum_blk, - GET_SUM_BLOCK(sbi, curseg->segno)); + write_sum_page(sbi, curseg->sum_blk, curseg->segno); } else { mutex_lock(&DIRTY_I(sbi)->seglist_lock); __set_test_and_free(sbi, curseg->segno, true); @@ -3833,8 +3852,7 @@ int f2fs_allocate_data_block(struct f2fs_sb_info *sbi, struct folio *folio, if (segment_full) { if (type == CURSEG_COLD_DATA_PINNED && !((curseg->segno + 1) % sbi->segs_per_sec)) { - write_sum_page(sbi, curseg->sum_blk, - GET_SUM_BLOCK(sbi, curseg->segno)); + write_sum_page(sbi, curseg->sum_blk, curseg->segno); reset_curseg_fields(curseg); goto skip_new_segment; } diff --git a/fs/f2fs/segment.h b/fs/f2fs/segment.h index 1ce2c8abaf4888..e883f14c228f26 100644 --- a/fs/f2fs/segment.h +++ b/fs/f2fs/segment.h @@ -85,8 +85,12 @@ static inline void sanity_check_seg_type(struct f2fs_sb_info *sbi, #define GET_ZONE_FROM_SEG(sbi, segno) \ GET_ZONE_FROM_SEC(sbi, GET_SEC_FROM_SEG(sbi, segno)) -#define GET_SUM_BLOCK(sbi, segno) \ - ((sbi)->sm_info->ssa_blkaddr + (segno)) +#define SUMS_PER_BLOCK (F2FS_BLKSIZE / F2FS_SUM_BLKSIZE) +#define GET_SUM_BLOCK(sbi, segno) \ + (SM_I(sbi)->ssa_blkaddr + (segno / SUMS_PER_BLOCK)) +#define GET_SUM_BLKOFF(segno) (segno % SUMS_PER_BLOCK) +#define SUM_BLK_PAGE_ADDR(folio, segno) \ + (folio_address(folio) + GET_SUM_BLKOFF(segno) * F2FS_SUM_BLKSIZE) #define GET_SUM_TYPE(footer) ((footer)->entry_type) #define SET_SUM_TYPE(footer, type) ((footer)->entry_type = (type)) diff --git a/fs/f2fs/super.c b/fs/f2fs/super.c index db7afb8064115c..9085b4a511a480 100644 --- a/fs/f2fs/super.c +++ b/fs/f2fs/super.c @@ -4051,6 +4051,20 @@ static int sanity_check_raw_super(struct f2fs_sb_info *sbi, if (sanity_check_area_boundary(sbi, folio, index)) return -EFSCORRUPTED; + /* + * Check for legacy summary layout on 16KB+ block devices. + * Modern f2fs-tools packs multiple 4KB summary areas into one block, + * whereas legacy versions used one block per summary, leading + * to a much larger SSA. + */ + if (SUMS_PER_BLOCK > 1 && + !(__F2FS_HAS_FEATURE(raw_super, F2FS_FEATURE_PACKED_SSA))) { + f2fs_info(sbi, "Error: Device formatted with a legacy version. " + "Please reformat with a tool supporting the packed ssa " + "feature for block sizes larger than 4kb."); + return -EOPNOTSUPP; + } + return 0; } diff --git a/fs/f2fs/sysfs.c b/fs/f2fs/sysfs.c index 6d2a4fba68a29f..5685b454bfd12d 100644 --- a/fs/f2fs/sysfs.c +++ b/fs/f2fs/sysfs.c @@ -235,6 +235,9 @@ static ssize_t features_show(struct f2fs_attr *a, if (f2fs_sb_has_compression(sbi)) len += sysfs_emit_at(buf, len, "%s%s", len ? ", " : "", "compression"); + if (f2fs_sb_has_packed_ssa(sbi)) + len += sysfs_emit_at(buf, len, "%s%s", + len ? ", " : "", "packed_ssa"); len += sysfs_emit_at(buf, len, "%s%s", len ? ", " : "", "pin_file"); len += sysfs_emit_at(buf, len, "\n"); @@ -1296,6 +1299,7 @@ F2FS_FEATURE_RO_ATTR(pin_file); #ifdef CONFIG_UNICODE F2FS_FEATURE_RO_ATTR(linear_lookup); #endif +F2FS_FEATURE_RO_ATTR(packed_ssa); #define ATTR_LIST(name) (&f2fs_attr_##name.attr) static struct attribute *f2fs_attrs[] = { @@ -1455,6 +1459,7 @@ static struct attribute *f2fs_feat_attrs[] = { #ifdef CONFIG_UNICODE BASE_ATTR_LIST(linear_lookup), #endif + BASE_ATTR_LIST(packed_ssa), NULL, }; ATTRIBUTE_GROUPS(f2fs_feat); @@ -1490,6 +1495,7 @@ F2FS_SB_FEATURE_RO_ATTR(casefold, CASEFOLD); F2FS_SB_FEATURE_RO_ATTR(compression, COMPRESSION); F2FS_SB_FEATURE_RO_ATTR(readonly, RO); F2FS_SB_FEATURE_RO_ATTR(device_alias, DEVICE_ALIAS); +F2FS_SB_FEATURE_RO_ATTR(packed_ssa, PACKED_SSA); static struct attribute *f2fs_sb_feat_attrs[] = { ATTR_LIST(sb_encryption), @@ -1507,6 +1513,7 @@ static struct attribute *f2fs_sb_feat_attrs[] = { ATTR_LIST(sb_compression), ATTR_LIST(sb_readonly), ATTR_LIST(sb_device_alias), + ATTR_LIST(sb_packed_ssa), NULL, }; ATTRIBUTE_GROUPS(f2fs_sb_feat); diff --git a/include/linux/f2fs_fs.h b/include/linux/f2fs_fs.h index 6afb4a13b81d65..a7880787cad366 100644 --- a/include/linux/f2fs_fs.h +++ b/include/linux/f2fs_fs.h @@ -17,6 +17,7 @@ #define F2FS_LOG_SECTORS_PER_BLOCK (PAGE_SHIFT - 9) /* log number for sector/blk */ #define F2FS_BLKSIZE PAGE_SIZE /* support only block == page */ #define F2FS_BLKSIZE_BITS PAGE_SHIFT /* bits for F2FS_BLKSIZE */ +#define F2FS_SUM_BLKSIZE 4096 /* only support 4096 byte sum block */ #define F2FS_MAX_EXTENSION 64 /* # of extension entries */ #define F2FS_EXTENSION_LEN 8 /* max size of extension */ @@ -441,7 +442,7 @@ struct f2fs_sit_block { * from node's page's beginning to get a data block address. * ex) data_blkaddr = (block_t)(nodepage_start_address + ofs_in_node) */ -#define ENTRIES_IN_SUM (F2FS_BLKSIZE / 8) +#define ENTRIES_IN_SUM (F2FS_SUM_BLKSIZE / 8) #define SUMMARY_SIZE (7) /* sizeof(struct f2fs_summary) */ #define SUM_FOOTER_SIZE (5) /* sizeof(struct summary_footer) */ #define SUM_ENTRY_SIZE (SUMMARY_SIZE * ENTRIES_IN_SUM) @@ -467,7 +468,7 @@ struct summary_footer { __le32 check_sum; /* summary checksum */ } __packed; -#define SUM_JOURNAL_SIZE (F2FS_BLKSIZE - SUM_FOOTER_SIZE -\ +#define SUM_JOURNAL_SIZE (F2FS_SUM_BLKSIZE - SUM_FOOTER_SIZE -\ SUM_ENTRY_SIZE) #define NAT_JOURNAL_ENTRIES ((SUM_JOURNAL_SIZE - 2) /\ sizeof(struct nat_journal_entry)) From 7b91877113945d4c300ff329b66aa566f72d0874 Mon Sep 17 00:00:00 2001 From: Cong Zhang Date: Wed, 3 Dec 2025 11:34:21 +0800 Subject: [PATCH 0923/3902] blk-mq: Abort suspend when wakeup events are pending [ Upstream commit c196bf43d706592d8801a7513603765080e495fb ] During system suspend, wakeup capable IRQs for block device can be delayed, which can cause blk_mq_hctx_notify_offline() to hang indefinitely while waiting for pending request to complete. Skip the request waiting loop and abort suspend when wakeup events are pending to prevent the deadlock. Fixes: bf0beec0607d ("blk-mq: drain I/O when all CPUs in a hctx are offline") Signed-off-by: Cong Zhang Signed-off-by: Jens Axboe Signed-off-by: Sasha Levin --- block/blk-mq.c | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/block/blk-mq.c b/block/blk-mq.c index d626d32f6e576f..33a0062f9e56d3 100644 --- a/block/blk-mq.c +++ b/block/blk-mq.c @@ -23,6 +23,7 @@ #include #include #include +#include #include #include #include @@ -3707,6 +3708,7 @@ static int blk_mq_hctx_notify_offline(unsigned int cpu, struct hlist_node *node) { struct blk_mq_hw_ctx *hctx = hlist_entry_safe(node, struct blk_mq_hw_ctx, cpuhp_online); + int ret = 0; if (blk_mq_hctx_has_online_cpu(hctx, cpu)) return 0; @@ -3727,12 +3729,24 @@ static int blk_mq_hctx_notify_offline(unsigned int cpu, struct hlist_node *node) * frozen and there are no requests. */ if (percpu_ref_tryget(&hctx->queue->q_usage_counter)) { - while (blk_mq_hctx_has_requests(hctx)) + while (blk_mq_hctx_has_requests(hctx)) { + /* + * The wakeup capable IRQ handler of block device is + * not called during suspend. Skip the loop by checking + * pm_wakeup_pending to prevent the deadlock and improve + * suspend latency. + */ + if (pm_wakeup_pending()) { + clear_bit(BLK_MQ_S_INACTIVE, &hctx->state); + ret = -EBUSY; + break; + } msleep(5); + } percpu_ref_put(&hctx->queue->q_usage_counter); } - return 0; + return ret; } /* From 0accacccba1943a32986b95225607f1994b0d26d Mon Sep 17 00:00:00 2001 From: Arnd Bergmann Date: Thu, 4 Dec 2025 10:45:45 +0100 Subject: [PATCH 0924/3902] drm/panel: novatek-nt35560: avoid on-stack device structure [ Upstream commit 1a7a7b80a22448dff55e1ad69a4681fd8b760b85 ] A cleanup patch apparently by accident used a local device structure instead of a pointer to one in the nt35560_read_id() function, causing a warning about stack usage: drivers/gpu/drm/panel/panel-novatek-nt35560.c: In function 'nt35560_read_id': drivers/gpu/drm/panel/panel-novatek-nt35560.c:249:1: error: the frame size of 1296 bytes is larger than 1280 bytes [-Werror=frame-larger-than=] Change this to a pointer as was liley intended here. Fixes: 5fbc0dbb92d6 ("drm/panel: novatek-nt35560: Clean up driver") Signed-off-by: Arnd Bergmann Reviewed-by: Douglas Anderson Signed-off-by: Douglas Anderson Link: https://patch.msgid.link/20251204094550.1030506-1-arnd@kernel.org Signed-off-by: Sasha Levin --- drivers/gpu/drm/panel/panel-novatek-nt35560.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/drivers/gpu/drm/panel/panel-novatek-nt35560.c b/drivers/gpu/drm/panel/panel-novatek-nt35560.c index 561e6643dcbb67..6e5173f98a2264 100644 --- a/drivers/gpu/drm/panel/panel-novatek-nt35560.c +++ b/drivers/gpu/drm/panel/panel-novatek-nt35560.c @@ -213,7 +213,7 @@ static const struct backlight_properties nt35560_bl_props = { static void nt35560_read_id(struct mipi_dsi_multi_context *dsi_ctx) { - struct device dev = dsi_ctx->dsi->dev; + struct device *dev = &dsi_ctx->dsi->dev; u8 vendor, version, panel; u16 val; @@ -225,7 +225,7 @@ static void nt35560_read_id(struct mipi_dsi_multi_context *dsi_ctx) return; if (vendor == 0x00) { - dev_err(&dev, "device vendor ID is zero\n"); + dev_err(dev, "device vendor ID is zero\n"); dsi_ctx->accum_err = -ENODEV; return; } @@ -236,12 +236,12 @@ static void nt35560_read_id(struct mipi_dsi_multi_context *dsi_ctx) case DISPLAY_SONY_ACX424AKP_ID2: case DISPLAY_SONY_ACX424AKP_ID3: case DISPLAY_SONY_ACX424AKP_ID4: - dev_info(&dev, + dev_info(dev, "MTP vendor: %02x, version: %02x, panel: %02x\n", vendor, version, panel); break; default: - dev_info(&dev, + dev_info(dev, "unknown vendor: %02x, version: %02x, panel: %02x\n", vendor, version, panel); break; From 2864db01b3e54ae441b15bfb65f342e69e51140b Mon Sep 17 00:00:00 2001 From: shechenglong Date: Wed, 3 Dec 2025 23:17:49 +0800 Subject: [PATCH 0925/3902] block: fix comment for op_is_zone_mgmt() to include RESET_ALL [ Upstream commit 8a32282175c964eb15638e8dfe199fc13c060f67 ] REQ_OP_ZONE_RESET_ALL is a zone management request, and op_is_zone_mgmt() has returned true for it. Update the comment to remove the misleading exception note so the documentation matches the implementation. Fixes: 12a1c9353c47 ("block: fix op_is_zone_mgmt() to handle REQ_OP_ZONE_RESET_ALL") Signed-off-by: shechenglong Reviewed-by: Damien Le Moal Reviewed-by: Johannes Thumshirn Signed-off-by: Jens Axboe Signed-off-by: Sasha Levin --- include/linux/blk_types.h | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/include/linux/blk_types.h b/include/linux/blk_types.h index 44c30183ecc344..4e2e3aed32f5f7 100644 --- a/include/linux/blk_types.h +++ b/include/linux/blk_types.h @@ -469,10 +469,7 @@ static inline bool op_is_discard(blk_opf_t op) } /* - * Check if a bio or request operation is a zone management operation, with - * the exception of REQ_OP_ZONE_RESET_ALL which is treated as a special case - * due to its different handling in the block layer and device response in - * case of command failure. + * Check if a bio or request operation is a zone management operation. */ static inline bool op_is_zone_mgmt(enum req_op op) { From 7193407bc4457212fa38ec3aff9c640e63a8dbef Mon Sep 17 00:00:00 2001 From: Shaurya Rane Date: Thu, 4 Dec 2025 23:42:59 +0530 Subject: [PATCH 0926/3902] block: fix memory leak in __blkdev_issue_zero_pages [ Upstream commit f7e3f852a42d7cd8f1af2c330d9d153e30c8adcf ] Move the fatal signal check before bio_alloc() to prevent a memory leak when BLKDEV_ZERO_KILLABLE is set and a fatal signal is pending. Previously, the bio was allocated before checking for a fatal signal. If a signal was pending, the code would break out of the loop without freeing or chaining the just-allocated bio, causing a memory leak. This matches the pattern already used in __blkdev_issue_write_zeroes() where the signal check precedes the allocation. Fixes: bf86bcdb4012 ("blk-lib: check for kill signal in ioctl BLKZEROOUT") Reported-by: syzbot+527a7e48a3d3d315d862@syzkaller.appspotmail.com Closes: https://syzkaller.appspot.com/bug?extid=527a7e48a3d3d315d862 Signed-off-by: Shaurya Rane Reviewed-by: Keith Busch Tested-by: syzbot+527a7e48a3d3d315d862@syzkaller.appspotmail.com Signed-off-by: Jens Axboe Signed-off-by: Sasha Levin --- block/blk-lib.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/block/blk-lib.c b/block/blk-lib.c index 3030a772d3aa01..352e3c0f8a7d7a 100644 --- a/block/blk-lib.c +++ b/block/blk-lib.c @@ -202,13 +202,13 @@ static void __blkdev_issue_zero_pages(struct block_device *bdev, unsigned int nr_vecs = __blkdev_sectors_to_bio_pages(nr_sects); struct bio *bio; - bio = bio_alloc(bdev, nr_vecs, REQ_OP_WRITE, gfp_mask); - bio->bi_iter.bi_sector = sector; - if ((flags & BLKDEV_ZERO_KILLABLE) && fatal_signal_pending(current)) break; + bio = bio_alloc(bdev, nr_vecs, REQ_OP_WRITE, gfp_mask); + bio->bi_iter.bi_sector = sector; + do { unsigned int len; From 6a960b58df612dec4ca8a85c53019b2bfb70f061 Mon Sep 17 00:00:00 2001 From: Israel Rukshin Date: Sun, 23 Nov 2025 16:46:48 +0200 Subject: [PATCH 0927/3902] nvme-auth: use kvfree() for memory allocated with kvcalloc() [ Upstream commit bb9f4cca7c031de6f0e85f7ba24abf0172829f85 ] Memory allocated by kvcalloc() may come from vmalloc or kmalloc, so use kvfree() instead of kfree() for proper deallocation. Fixes: aa36d711e945 ("nvme-auth: convert dhchap_auth_list to an array") Signed-off-by: Israel Rukshin Reviewed-by: Max Gurtovoy Reviewed-by: Christoph Hellwig Signed-off-by: Keith Busch Signed-off-by: Sasha Levin --- drivers/nvme/host/auth.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/nvme/host/auth.c b/drivers/nvme/host/auth.c index a01178caf15bb5..8f3ccb317e4de6 100644 --- a/drivers/nvme/host/auth.c +++ b/drivers/nvme/host/auth.c @@ -1122,7 +1122,7 @@ void nvme_auth_free(struct nvme_ctrl *ctrl) if (ctrl->dhchap_ctxs) { for (i = 0; i < ctrl_max_dhchaps(ctrl); i++) nvme_auth_free_dhchap(&ctrl->dhchap_ctxs[i]); - kfree(ctrl->dhchap_ctxs); + kvfree(ctrl->dhchap_ctxs); } if (ctrl->host_key) { nvme_auth_free_key(ctrl->host_key); From 0e777da54152fabd626828356f37505344c0ebb2 Mon Sep 17 00:00:00 2001 From: Caleb Sander Mateos Date: Thu, 4 Dec 2025 15:43:31 -0700 Subject: [PATCH 0928/3902] io_uring/kbuf: use READ_ONCE() for userspace-mapped memory [ Upstream commit 78385c7299f7514697d196b3233a91bd5e485591 ] The struct io_uring_buf elements in a buffer ring are in a memory region accessible from userspace. A malicious/buggy userspace program could therefore write to them at any time, so they should be accessed with READ_ONCE() in the kernel. Commit 98b6fa62c84f ("io_uring/kbuf: always use READ_ONCE() to read ring provided buffer lengths") already switched the reads of the len field to READ_ONCE(). Do the same for bid and addr. Signed-off-by: Caleb Sander Mateos Fixes: c7fb19428d67 ("io_uring: add support for ring mapped supplied buffers") Cc: Joanne Koong Signed-off-by: Jens Axboe Signed-off-by: Sasha Levin --- io_uring/kbuf.c | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/io_uring/kbuf.c b/io_uring/kbuf.c index a727e020fe036f..d974381d93ff75 100644 --- a/io_uring/kbuf.c +++ b/io_uring/kbuf.c @@ -44,7 +44,7 @@ static bool io_kbuf_inc_commit(struct io_buffer_list *bl, int len) buf_len -= this_len; /* Stop looping for invalid buffer length of 0 */ if (buf_len || !this_len) { - buf->addr += this_len; + buf->addr = READ_ONCE(buf->addr) + this_len; buf->len = buf_len; return false; } @@ -198,9 +198,9 @@ static struct io_br_sel io_ring_buffer_select(struct io_kiocb *req, size_t *len, if (*len == 0 || *len > buf_len) *len = buf_len; req->flags |= REQ_F_BUFFER_RING | REQ_F_BUFFERS_COMMIT; - req->buf_index = buf->bid; + req->buf_index = READ_ONCE(buf->bid); sel.buf_list = bl; - sel.addr = u64_to_user_ptr(buf->addr); + sel.addr = u64_to_user_ptr(READ_ONCE(buf->addr)); if (io_should_commit(req, issue_flags)) { io_kbuf_commit(req, sel.buf_list, *len, 1); @@ -280,7 +280,7 @@ static int io_ring_buffers_peek(struct io_kiocb *req, struct buf_sel_arg *arg, if (!arg->max_len) arg->max_len = INT_MAX; - req->buf_index = buf->bid; + req->buf_index = READ_ONCE(buf->bid); do { u32 len = READ_ONCE(buf->len); @@ -295,7 +295,7 @@ static int io_ring_buffers_peek(struct io_kiocb *req, struct buf_sel_arg *arg, } } - iov->iov_base = u64_to_user_ptr(buf->addr); + iov->iov_base = u64_to_user_ptr(READ_ONCE(buf->addr)); iov->iov_len = len; iov++; From bc6761c022558aa3f3eea012a28dd9624abfffac Mon Sep 17 00:00:00 2001 From: Dan Carpenter Date: Wed, 3 Dec 2025 20:35:23 +0300 Subject: [PATCH 0929/3902] drm/plane: Fix IS_ERR() vs NULL check in drm_plane_create_hotspot_properties() [ Upstream commit 479acb9db3199cdb70e5478a6f633b5f20c7d8df ] The drm_property_create_signed_range() function doesn't return error pointers it returns NULL on error. Fix the error checking to match. Fixes: 8f7179a1027d ("drm/atomic: Add support for mouse hotspots") Signed-off-by: Dan Carpenter Reviewed-by: Javier Martinez Canillas Reviewed-by: Zack Rusin Link: https://patch.msgid.link/aTB023cfcIPkCsFS@stanley.mountain Signed-off-by: Maxime Ripard Signed-off-by: Sasha Levin --- drivers/gpu/drm/drm_plane.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/drivers/gpu/drm/drm_plane.c b/drivers/gpu/drm/drm_plane.c index a30493ed97157f..4cadea997129da 100644 --- a/drivers/gpu/drm/drm_plane.c +++ b/drivers/gpu/drm/drm_plane.c @@ -338,14 +338,14 @@ static int drm_plane_create_hotspot_properties(struct drm_plane *plane) prop_x = drm_property_create_signed_range(plane->dev, 0, "HOTSPOT_X", INT_MIN, INT_MAX); - if (IS_ERR(prop_x)) - return PTR_ERR(prop_x); + if (!prop_x) + return -ENOMEM; prop_y = drm_property_create_signed_range(plane->dev, 0, "HOTSPOT_Y", INT_MIN, INT_MAX); - if (IS_ERR(prop_y)) { + if (!prop_y) { drm_property_destroy(plane->dev, prop_x); - return PTR_ERR(prop_y); + return -ENOMEM; } drm_object_attach_property(&plane->base, prop_x, 0); From 8fee481ee6c0d1e3ca64d0cabaa2653533d34a23 Mon Sep 17 00:00:00 2001 From: Mark Brown Date: Thu, 4 Dec 2025 19:39:34 +0000 Subject: [PATCH 0930/3902] regulator: fixed: Rely on the core freeing the enable GPIO [ Upstream commit 79a45ddcdbba330f5139c7c7ff7042d69cf147b2 ] In order to simplify ownership rules for enable GPIOs supplied by drivers regulator_register() always takes ownership of them, even if it ends up failing for some other reason. We therefore should not free the GPIO if registration fails but just let the core worry about things. Fixes: 636f4618b1cd (regulator: fixed: fix GPIO descriptor leak on register failure) Reported-by: Diederik de Haas Closes: https://lore.kernel.org/r/DEPEYUF5BRGY.UKFBWRRE8HNP@cknow-tech.com Tested-by: Diederik de Haas Signed-off-by: Mark Brown Link: https://patch.msgid.link/20251204-regulator-fixed-fix-gpiod-leak-v1-1-48efea5b82c2@kernel.org Signed-off-by: Mark Brown Signed-off-by: Sasha Levin --- drivers/regulator/fixed.c | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/drivers/regulator/fixed.c b/drivers/regulator/fixed.c index a2d16e9abfb58d..254c0a8a455594 100644 --- a/drivers/regulator/fixed.c +++ b/drivers/regulator/fixed.c @@ -330,13 +330,10 @@ static int reg_fixed_voltage_probe(struct platform_device *pdev) drvdata->dev = devm_regulator_register(&pdev->dev, &drvdata->desc, &cfg); - if (IS_ERR(drvdata->dev)) { - ret = dev_err_probe(&pdev->dev, PTR_ERR(drvdata->dev), - "Failed to register regulator: %ld\n", - PTR_ERR(drvdata->dev)); - gpiod_put(cfg.ena_gpiod); - return ret; - } + if (IS_ERR(drvdata->dev)) + return dev_err_probe(&pdev->dev, PTR_ERR(drvdata->dev), + "Failed to register regulator: %ld\n", + PTR_ERR(drvdata->dev)); platform_set_drvdata(pdev, drvdata); From cdda0d06f8650e33255f79839f188bbece44117c Mon Sep 17 00:00:00 2001 From: Junrui Luo Date: Wed, 3 Dec 2025 12:27:03 +0800 Subject: [PATCH 0931/3902] ALSA: firewire-motu: fix buffer overflow in hwdep read for DSP events [ Upstream commit 210d77cca3d0494ed30a5c628b20c1d95fa04fb1 ] The DSP event handling code in hwdep_read() could write more bytes to the user buffer than requested, when a user provides a buffer smaller than the event header size (8 bytes). Fix by using min_t() to clamp the copy size, This ensures we never copy more than the user requested. Reported-by: Yuhao Jiang Reported-by: Junrui Luo Fixes: 634ec0b2906e ("ALSA: firewire-motu: notify event for parameter change in register DSP model") Signed-off-by: Junrui Luo Link: https://patch.msgid.link/SYBPR01MB78810656377E79E58350D951AFD9A@SYBPR01MB7881.ausprd01.prod.outlook.com Signed-off-by: Takashi Iwai Signed-off-by: Sasha Levin --- sound/firewire/motu/motu-hwdep.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/sound/firewire/motu/motu-hwdep.c b/sound/firewire/motu/motu-hwdep.c index 981c19430cb0fe..6675b23aad69ef 100644 --- a/sound/firewire/motu/motu-hwdep.c +++ b/sound/firewire/motu/motu-hwdep.c @@ -83,10 +83,11 @@ static long hwdep_read(struct snd_hwdep *hwdep, char __user *buf, long count, event.motu_register_dsp_change.type = SNDRV_FIREWIRE_EVENT_MOTU_REGISTER_DSP_CHANGE; event.motu_register_dsp_change.count = (consumed - sizeof(event.motu_register_dsp_change)) / 4; - if (copy_to_user(buf, &event, sizeof(event.motu_register_dsp_change))) + if (copy_to_user(buf, &event, + min_t(long, count, sizeof(event.motu_register_dsp_change)))) return -EFAULT; - count = consumed; + count = min_t(long, count, consumed); } else { spin_unlock_irq(&motu->lock); From 731e1beb10c4e43b8dc70462814792049f8aa329 Mon Sep 17 00:00:00 2001 From: Madhur Kumar Date: Thu, 4 Dec 2025 17:38:22 +0530 Subject: [PATCH 0932/3902] drm/nouveau: refactor deprecated strcpy [ Upstream commit 2bdc2c0e12fac56e41ec05fb771ead986ea6dac0 ] strcpy() has been deprecated because it performs no bounds checking on the destination buffer, which can lead to buffer overflows. Use the safer strscpy() instead. Signed-off-by: Madhur Kumar Reviewed-by: Lyude Paul Fixes: 15a996bbb697 ("drm/nouveau: assign fence_chan->name correctly") Signed-off-by: Lyude Paul Link: https://patch.msgid.link/20251204120822.17502-1-madhurkumar004@gmail.com Signed-off-by: Sasha Levin --- drivers/gpu/drm/nouveau/nouveau_fence.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/drivers/gpu/drm/nouveau/nouveau_fence.c b/drivers/gpu/drm/nouveau/nouveau_fence.c index 869d4335c0f45c..4a193b7d6d9e40 100644 --- a/drivers/gpu/drm/nouveau/nouveau_fence.c +++ b/drivers/gpu/drm/nouveau/nouveau_fence.c @@ -183,11 +183,11 @@ nouveau_fence_context_new(struct nouveau_channel *chan, struct nouveau_fence_cha fctx->context = drm->runl[chan->runlist].context_base + chan->chid; if (chan == drm->cechan) - strcpy(fctx->name, "copy engine channel"); + strscpy(fctx->name, "copy engine channel"); else if (chan == drm->channel) - strcpy(fctx->name, "generic kernel channel"); + strscpy(fctx->name, "generic kernel channel"); else - strcpy(fctx->name, cli->name); + strscpy(fctx->name, cli->name); kref_init(&fctx->fence_ref); if (!priv->uevent) From ec8ccaa4d33e8b05f34de8f5139720e59b0ffc46 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20Rebe?= Date: Tue, 2 Dec 2025 16:49:52 +0100 Subject: [PATCH 0933/3902] drm/nouveau: fix circular dep oops from vendored i2c encoder MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [ Upstream commit d84e47edf156a953ed340ba6a202dcd3ea39ba0a ] Since commit a73583107af9 ("drm/nouveau: vendor in drm_encoder_slave API") nouveau appears to be broken for all dispnv04 GPUs (before NV50). Depending on the kernel version, either having no display output and hanging in kernel for a long time, or even oopsing in the cleanup path like: Hardware name: PowerMac11,2 PPC970MP 0x440101 PowerMac ... nouveau 0000:0a:00.0: drm: 0x14C5: Parsing digital output script table BUG: Unable to handle kernel data access on read at 0x00041520 Faulting instruction address: 0xc0003d0001be0844 Oops: Kernel access of bad area, sig: 11 [#1] BE PAGE_SIZE=4K MMU=Hash SMP NR_CPUS=8 NUMA PowerMac Modules linked in: windfarm_cpufreq_clamp windfarm_smu_sensors windfarm_smu_controls windfarm_pm112 snd_aoa_codec_onyx snd_aoa_fabric_layout snd_aoa windfarm_pid jo apple_mfi_fastcharge rndis_host cdc_ether usbnet mii snd_aoa_i2sbus snd_aoa_soundbus snd_pcm snd_timer snd soundcore rack_meter windfarm_smu_sat windfarm_max6690_s m75_sensor windfarm_core gpu_sched drm_gpuvm drm_exec drm_client_lib drm_ttm_helper ttm drm_display_helper drm_kms_helper drm drm_panel_orientation_quirks syscopyar _sys_fops i2c_algo_bit backlight uio_pdrv_genirq uio uninorth_agp agpgart zram dm_mod dax ipv6 nfsv4 dns_resolver nfs lockd grace sunrpc offb cfbfillrect cfbimgblt ont input_leds sr_mod cdrom sd_mod uas ata_generic hid_apple hid_generic usbhid hid usb_storage pata_macio sata_svw libata firewire_ohci scsi_mod firewire_core ohci ehci_pci ehci_hcd tg3 ohci_hcd libphy usbcore usb_common nls_base led_class CPU: 0 UID: 0 PID: 245 Comm: (udev-worker) Not tainted 6.14.0-09584-g7d06015d936c #7 PREEMPTLAZY Hardware name: PowerMac11,2 PPC970MP 0x440101 PowerMac NIP: c0003d0001be0844 LR: c0003d0001be0830 CTR: 0000000000000000 REGS: c0000000053f70e0 TRAP: 0300 Not tainted (6.14.0-09584-g7d06015d936c) MSR: 9000000000009032 CR: 24222220 XER: 00000000 DAR: 0000000000041520 DSISR: 40000000 IRQMASK: 0 \x0aGPR00: c0003d0001be0830 c0000000053f7380 c0003d0000911900 c000000007bc6800 \x0aGPR04: 0000000000000000 0000000000000000 c000000007bc6e70 0000000000000001 \x0aGPR08: 01f3040000000000 0000000000041520 0000000000000000 c0003d0000813958 \x0aGPR12: c000000000071a48 c000000000e28000 0000000000000020 0000000000000000 \x0aGPR16: 0000000000000000 0000000000f52630 0000000000000000 0000000000000000 \x0aGPR20: 0000000000000000 0000000000000000 0000000000000001 c0003d0000928528 \x0aGPR24: c0003d0000928598 0000000000000000 c000000007025480 c000000007025480 \x0aGPR28: c0000000010b4000 0000000000000000 c000000007bc1800 c000000007bc6800 NIP [c0003d0001be0844] nv_crtc_destroy+0x44/0xd4 [nouveau] LR [c0003d0001be0830] nv_crtc_destroy+0x30/0xd4 [nouveau] Call Trace: [c0000000053f7380] [c0003d0001be0830] nv_crtc_destroy+0x30/0xd4 [nouveau] (unreliable) [c0000000053f73c0] [c0003d00007f7bf4] drm_mode_config_cleanup+0x27c/0x30c [drm] [c0000000053f7490] [c0003d0001bdea50] nouveau_display_create+0x1cc/0x550 [nouveau] [c0000000053f7500] [c0003d0001bcc29c] nouveau_drm_device_init+0x1c8/0x844 [nouveau] [c0000000053f75e0] [c0003d0001bcc9ec] nouveau_drm_probe+0xd4/0x1e0 [nouveau] [c0000000053f7670] [c000000000557d24] local_pci_probe+0x50/0xa8 [c0000000053f76f0] [c000000000557fa8] pci_device_probe+0x22c/0x240 [c0000000053f7760] [c0000000005fff3c] really_probe+0x188/0x31c [c0000000053f77e0] [c000000000600204] __driver_probe_device+0x134/0x13c [c0000000053f7860] [c0000000006002c0] driver_probe_device+0x3c/0xb4 [c0000000053f78a0] [c000000000600534] __driver_attach+0x118/0x128 [c0000000053f78e0] [c0000000005fe038] bus_for_each_dev+0xa8/0xf4 [c0000000053f7950] [c0000000005ff460] driver_attach+0x2c/0x40 [c0000000053f7970] [c0000000005fea68] bus_add_driver+0x130/0x278 [c0000000053f7a00] [c00000000060117c] driver_register+0x9c/0x1a0 [c0000000053f7a80] [c00000000055623c] __pci_register_driver+0x5c/0x70 [c0000000053f7aa0] [c0003d0001c058a0] nouveau_drm_init+0x254/0x278 [nouveau] [c0000000053f7b10] [c00000000000e9bc] do_one_initcall+0x84/0x268 [c0000000053f7bf0] [c0000000001a0ba0] do_init_module+0x70/0x2d8 [c0000000053f7c70] [c0000000001a42bc] init_module_from_file+0xb4/0x108 [c0000000053f7d50] [c0000000001a4504] sys_finit_module+0x1ac/0x478 [c0000000053f7e10] [c000000000023230] system_call_exception+0x1a4/0x20c [c0000000053f7e50] [c00000000000c554] system_call_common+0xf4/0x258 --- interrupt: c00 at 0xfd5f988 NIP: 000000000fd5f988 LR: 000000000ff9b148 CTR: 0000000000000000 REGS: c0000000053f7e80 TRAP: 0c00 Not tainted (6.14.0-09584-g7d06015d936c) MSR: 100000000000d032 CR: 28222244 XER: 00000000 IRQMASK: 0 \x0aGPR00: 0000000000000161 00000000ffcdc2d0 00000000405db160 0000000000000020 \x0aGPR04: 000000000ffa2c9c 0000000000000000 000000000000001f 0000000000000045 \x0aGPR08: 0000000011a13770 0000000000000000 0000000000000000 0000000000000000 \x0aGPR12: 0000000000000000 0000000010249d8c 0000000000000020 0000000000000000 \x0aGPR16: 0000000000000000 0000000000f52630 0000000000000000 0000000000000000 \x0aGPR20: 0000000000000000 0000000000000000 0000000000000000 0000000011a11a70 \x0aGPR24: 0000000011a13580 0000000011a11950 0000000011a11a70 0000000000020000 \x0aGPR28: 000000000ffa2c9c 0000000000000000 000000000ffafc40 0000000011a11a70 NIP [000000000fd5f988] 0xfd5f988 LR [000000000ff9b148] 0xff9b148 --- interrupt: c00 Code: f821ffc1 418200ac e93f0000 e9290038 e9291468 eba90000 48026c0d e8410018 e93f06aa 3d290001 392982a4 79291f24 <7fdd482a> 2c3e0000 41820030 7fc3f378 ---[ end trace 0000000000000000 ]--- This is caused by the i2c encoder modules vendored into nouveau/ now depending on the equally vendored nouveau_i2c_encoder_destroy function. Trying to auto-load this modules hangs on nouveau initialization until timeout, and nouveau continues without i2c video encoders. Fix by avoiding nouveau dependency by __always_inlining that helper functions into those i2c video encoder modules. Fixes: a73583107af9 ("drm/nouveau: vendor in drm_encoder_slave API") Signed-off-by: René Rebe Reviewed-by: Lyude Paul [Lyude: fixed commit reference in description] Signed-off-by: Lyude Paul Link: https://patch.msgid.link/20251202.164952.2216481867721531616.rene@exactco.de Signed-off-by: Sasha Levin --- .../nouveau/dispnv04/nouveau_i2c_encoder.c | 20 ------------------- .../include/dispnv04/i2c/encoder_i2c.h | 19 +++++++++++++++++- 2 files changed, 18 insertions(+), 21 deletions(-) diff --git a/drivers/gpu/drm/nouveau/dispnv04/nouveau_i2c_encoder.c b/drivers/gpu/drm/nouveau/dispnv04/nouveau_i2c_encoder.c index e2bf99c433366b..a60209097a20a6 100644 --- a/drivers/gpu/drm/nouveau/dispnv04/nouveau_i2c_encoder.c +++ b/drivers/gpu/drm/nouveau/dispnv04/nouveau_i2c_encoder.c @@ -94,26 +94,6 @@ int nouveau_i2c_encoder_init(struct drm_device *dev, return err; } -/** - * nouveau_i2c_encoder_destroy - Unregister the I2C device backing an encoder - * @drm_encoder: Encoder to be unregistered. - * - * This should be called from the @destroy method of an I2C slave - * encoder driver once I2C access is no longer needed. - */ -void nouveau_i2c_encoder_destroy(struct drm_encoder *drm_encoder) -{ - struct nouveau_i2c_encoder *encoder = to_encoder_i2c(drm_encoder); - struct i2c_client *client = nouveau_i2c_encoder_get_client(drm_encoder); - struct module *module = client->dev.driver->owner; - - i2c_unregister_device(client); - encoder->i2c_client = NULL; - - module_put(module); -} -EXPORT_SYMBOL(nouveau_i2c_encoder_destroy); - /* * Wrapper fxns which can be plugged in to drm_encoder_helper_funcs: */ diff --git a/drivers/gpu/drm/nouveau/include/dispnv04/i2c/encoder_i2c.h b/drivers/gpu/drm/nouveau/include/dispnv04/i2c/encoder_i2c.h index 31334aa90781b3..869820701a56eb 100644 --- a/drivers/gpu/drm/nouveau/include/dispnv04/i2c/encoder_i2c.h +++ b/drivers/gpu/drm/nouveau/include/dispnv04/i2c/encoder_i2c.h @@ -202,7 +202,24 @@ static inline struct i2c_client *nouveau_i2c_encoder_get_client(struct drm_encod return to_encoder_i2c(encoder)->i2c_client; } -void nouveau_i2c_encoder_destroy(struct drm_encoder *encoder); +/** + * nouveau_i2c_encoder_destroy - Unregister the I2C device backing an encoder + * @drm_encoder: Encoder to be unregistered. + * + * This should be called from the @destroy method of an I2C slave + * encoder driver once I2C access is no longer needed. + */ +static __always_inline void nouveau_i2c_encoder_destroy(struct drm_encoder *drm_encoder) +{ + struct nouveau_i2c_encoder *encoder = to_encoder_i2c(drm_encoder); + struct i2c_client *client = nouveau_i2c_encoder_get_client(drm_encoder); + struct module *module = client->dev.driver->owner; + + i2c_unregister_device(client); + encoder->i2c_client = NULL; + + module_put(module); +} /* * Wrapper fxns which can be plugged in to drm_encoder_helper_funcs: From c61af12157ef9fe480ab84b4923e8ca3d8f86a62 Mon Sep 17 00:00:00 2001 From: David Howells Date: Tue, 2 Dec 2025 16:24:53 +0000 Subject: [PATCH 0934/3902] cifs: Fix handling of a beyond-EOF DIO/unbuffered read over SMB1 [ Upstream commit 9d85ac939d52e93d80efb01a299c6f0bedb30487 ] If a DIO read or an unbuffered read request extends beyond the EOF, the server will return a short read and a status code indicating that EOF was hit, which gets translated to -ENODATA. Note that the client does not cap the request at i_size, but asks for the amount requested in case there's a race on the server with a third party. Now, on the client side, the request will get split into multiple subrequests if rsize is smaller than the full request size. A subrequest that starts before or at the EOF and returns short data up to the EOF will be correctly handled, with the NETFS_SREQ_HIT_EOF flag being set, indicating to netfslib that we can't read more. If a subrequest, however, starts after the EOF and not at it, HIT_EOF will not be flagged, its error will be set to -ENODATA and it will be abandoned. This will cause the request as a whole to fail with -ENODATA. Fix this by setting NETFS_SREQ_HIT_EOF on any subrequest that lies beyond the EOF marker. This can be reproduced by mounting with "cache=none,sign,vers=1.0" and doing a read of a file that's significantly bigger than the size of the file (e.g. attempting to read 64KiB from a 16KiB file). Fixes: a68c74865f51 ("cifs: Fix SMB1 readv/writev callback in the same way as SMB2/3") Signed-off-by: David Howells Reviewed-by: Paulo Alcantara (Red Hat) cc: Shyam Prasad N cc: linux-cifs@vger.kernel.org cc: netfs@lists.linux.dev cc: linux-fsdevel@vger.kernel.org Signed-off-by: Steve French Signed-off-by: Sasha Levin --- fs/smb/client/cifssmb.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fs/smb/client/cifssmb.c b/fs/smb/client/cifssmb.c index dcc50a2bfa4b23..bfc9b1ea76fac9 100644 --- a/fs/smb/client/cifssmb.c +++ b/fs/smb/client/cifssmb.c @@ -1374,7 +1374,7 @@ cifs_readv_callback(struct mid_q_entry *mid) } else { size_t trans = rdata->subreq.transferred + rdata->got_bytes; if (trans < rdata->subreq.len && - rdata->subreq.start + trans == ictx->remote_i_size) { + rdata->subreq.start + trans >= ictx->remote_i_size) { rdata->result = 0; __set_bit(NETFS_SREQ_HIT_EOF, &rdata->subreq.flags); } else if (rdata->got_bytes > 0) { From 5dfdc0ba05441f17947682b11bfceeca45d451d6 Mon Sep 17 00:00:00 2001 From: David Howells Date: Wed, 3 Dec 2025 21:55:27 +0000 Subject: [PATCH 0935/3902] cifs: Fix handling of a beyond-EOF DIO/unbuffered read over SMB2 [ Upstream commit 4ae4dde6f34a4124c65468ae4fa1f915fb40f900 ] If a DIO read or an unbuffered read request extends beyond the EOF, the server will return a short read and a status code indicating that EOF was hit, which gets translated to -ENODATA. Note that the client does not cap the request at i_size, but asks for the amount requested in case there's a race on the server with a third party. Now, on the client side, the request will get split into multiple subrequests if rsize is smaller than the full request size. A subrequest that starts before or at the EOF and returns short data up to the EOF will be correctly handled, with the NETFS_SREQ_HIT_EOF flag being set, indicating to netfslib that we can't read more. If a subrequest, however, starts after the EOF and not at it, HIT_EOF will not be flagged, its error will be set to -ENODATA and it will be abandoned. This will cause the request as a whole to fail with -ENODATA. Fix this by setting NETFS_SREQ_HIT_EOF on any subrequest that lies beyond the EOF marker. Fixes: 1da29f2c39b6 ("netfs, cifs: Fix handling of short DIO read") Signed-off-by: David Howells Reviewed-by: Paulo Alcantara (Red Hat) cc: Shyam Prasad N cc: linux-cifs@vger.kernel.org cc: netfs@lists.linux.dev cc: linux-fsdevel@vger.kernel.org Signed-off-by: Steve French Signed-off-by: Sasha Levin --- fs/smb/client/smb2pdu.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fs/smb/client/smb2pdu.c b/fs/smb/client/smb2pdu.c index 8b4a4573e9c372..e661d40213eabc 100644 --- a/fs/smb/client/smb2pdu.c +++ b/fs/smb/client/smb2pdu.c @@ -4629,7 +4629,7 @@ smb2_readv_callback(struct mid_q_entry *mid) } else { size_t trans = rdata->subreq.transferred + rdata->got_bytes; if (trans < rdata->subreq.len && - rdata->subreq.start + trans == ictx->remote_i_size) { + rdata->subreq.start + trans >= ictx->remote_i_size) { __set_bit(NETFS_SREQ_HIT_EOF, &rdata->subreq.flags); rdata->result = 0; } From 1959d1265e24131265a7976648d0431c658ff4a5 Mon Sep 17 00:00:00 2001 From: Mike Snitzer Date: Wed, 26 Nov 2025 01:01:26 -0500 Subject: [PATCH 0936/3902] nfs/localio: remove alignment size checking in nfs_is_local_dio_possible [ Upstream commit f50d0328d02fe38ba196a73c143e5d87e341d4f7 ] This check to ensure dio_offset_align isn't larger than PAGE_SIZE is no longer relevant (older iterations of NFS Direct was allocating misaligned head and tail pages but no longer does, so this check isn't needed). Fixes: c817248fc831 ("nfs/localio: add proper O_DIRECT support for READ and WRITE") Signed-off-by: Mike Snitzer Signed-off-by: Trond Myklebust Signed-off-by: Sasha Levin --- fs/nfs/localio.c | 2 -- 1 file changed, 2 deletions(-) diff --git a/fs/nfs/localio.c b/fs/nfs/localio.c index 656976b4f42ce6..512d9c5ff608a6 100644 --- a/fs/nfs/localio.c +++ b/fs/nfs/localio.c @@ -339,8 +339,6 @@ nfs_is_local_dio_possible(struct nfs_local_kiocb *iocb, int rw, if (unlikely(!nf_dio_mem_align || !nf_dio_offset_align)) return false; - if (unlikely(nf_dio_offset_align > PAGE_SIZE)) - return false; if (unlikely(len < nf_dio_offset_align)) return false; From 829adc469a18a1893c93b23f2155efbee363a0df Mon Sep 17 00:00:00 2001 From: Mike Snitzer Date: Wed, 26 Nov 2025 01:01:27 -0500 Subject: [PATCH 0937/3902] nfs/localio: remove 61 byte hole from needless ____cacheline_aligned [ Upstream commit 0b873de2c02f9cc655bef6bee0eb9e404126ed6c ] struct nfs_local_kiocb used ____cacheline_aligned on its iters[] array and as the structure evolved it caused a 61 byte hole to form. Fix this by removing ____cacheline_aligned and reordering iters[] before iter_is_dio_aligned[]. Fixes: 6a218b9c3183 ("nfs/localio: do not issue misaligned DIO out-of-order") Signed-off-by: Mike Snitzer Signed-off-by: Trond Myklebust Signed-off-by: Sasha Levin --- fs/nfs/localio.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fs/nfs/localio.c b/fs/nfs/localio.c index 512d9c5ff608a6..b98bb292fef0cc 100644 --- a/fs/nfs/localio.c +++ b/fs/nfs/localio.c @@ -43,8 +43,8 @@ struct nfs_local_kiocb { size_t end_len; short int end_iter_index; atomic_t n_iters; + struct iov_iter iters[NFSLOCAL_MAX_IOS]; bool iter_is_dio_aligned[NFSLOCAL_MAX_IOS]; - struct iov_iter iters[NFSLOCAL_MAX_IOS] ____cacheline_aligned; /* End mostly DIO-specific members */ }; From f81f7e1344c43945c75129d740f434110b4f1f1e Mon Sep 17 00:00:00 2001 From: Arnd Bergmann Date: Fri, 5 Dec 2025 10:54:09 +0100 Subject: [PATCH 0938/3902] gpio: tb10x: fix OF_GPIO dependency [ Upstream commit dd44d4d0c55a4ecf5eabf7856f96ed47e0684780 ] Selecting OF_GPIO is generally not allowed, it always gets enabled when both GPIOLIB and OF are turned on. The tb10x driver now warns about this after it was enabled for compile-testing: WARNING: unmet direct dependencies detected for OF_GPIO Depends on [n]: GPIOLIB [=y] && OF [=n] && HAS_IOMEM [=y] Selected by [y]: - GPIO_TB10X [=y] && GPIOLIB [=y] && HAS_IOMEM [=y] && (ARC_PLAT_TB10X || COMPILE_TEST [=y]) OF_GPIO is not required for compile-testing and is already enabled when the driver is usable, so just drop the 'select' line. Fixes: 682fbb18e14c ("gpio: tb10x: allow building the module with COMPILE_TEST=y") Signed-off-by: Arnd Bergmann Link: https://lore.kernel.org/r/20251205095429.1291866-1-arnd@kernel.org Signed-off-by: Bartosz Golaszewski Signed-off-by: Sasha Levin --- drivers/gpio/Kconfig | 1 - 1 file changed, 1 deletion(-) diff --git a/drivers/gpio/Kconfig b/drivers/gpio/Kconfig index 7ee3afbc2b05da..e053524c5e35f1 100644 --- a/drivers/gpio/Kconfig +++ b/drivers/gpio/Kconfig @@ -738,7 +738,6 @@ config GPIO_TB10X depends on ARC_PLAT_TB10X || COMPILE_TEST select GPIO_GENERIC select GENERIC_IRQ_CHIP - select OF_GPIO config GPIO_TEGRA tristate "NVIDIA Tegra GPIO support" From 581a9f4c1cd859c968159d6e3a4e2c93b0faadef Mon Sep 17 00:00:00 2001 From: Kathara Sasikumar Date: Fri, 5 Dec 2025 21:58:35 +0000 Subject: [PATCH 0939/3902] docs: hwmon: fix link to g762 devicetree binding [ Upstream commit 08bfcf4ff9d39228150a757803fc02dffce84ab0 ] The devicetree binding for g762 was converted to YAML to match vendor prefix conventions. Update the reference accordingly. Signed-off-by: Kathara Sasikumar Link: https://lore.kernel.org/r/20251205215835.783273-1-katharasasikumar007@gmail.com Fixes: 3d8e25372417 ("dt-bindings: hwmon: g762: Convert to yaml schema") Signed-off-by: Guenter Roeck Signed-off-by: Sasha Levin --- Documentation/hwmon/g762.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Documentation/hwmon/g762.rst b/Documentation/hwmon/g762.rst index 0371b3365c48cf..f224552a2d3cc0 100644 --- a/Documentation/hwmon/g762.rst +++ b/Documentation/hwmon/g762.rst @@ -17,7 +17,7 @@ done via a userland daemon like fancontrol. Note that those entries do not provide ways to setup the specific hardware characteristics of the system (reference clock, pulses per fan revolution, ...); Those can be modified via devicetree bindings -documented in Documentation/devicetree/bindings/hwmon/g762.txt or +documented in Documentation/devicetree/bindings/hwmon/gmt,g762.yaml or using a specific platform_data structure in board initialization file (see include/linux/platform_data/g762.h). From bfef208916d724bb39554cba99679ef3ebcb1ac5 Mon Sep 17 00:00:00 2001 From: Troy Mitchell Date: Thu, 13 Nov 2025 21:21:50 +0800 Subject: [PATCH 0940/3902] i2c: spacemit: fix detect issue MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [ Upstream commit 25faa5364638b86ec0d0edb4486daa9d40a0be8f ] This commit addresses two issues causing i2c detect to fail. The identified issues are: 1. Incorrect error handling for BED (Bus Error No ACK/NAK): Before this commit, Both ALD (Arbitration Loss Detected) and BED returned -EAGAIN. 2. Missing interrupt status clear after initialization in xfer(): On the K1 SoC, simply fixing the first issue changed the error from -EAGAIN to -ETIMEOUT. Through tracing, it was determined that this is likely due to MSD (Master Stop Detected) latency issues. That means the MSD bit in the ISR may still be set on the next transfer. As a result, the controller won't work — we can see from the scope that it doesn't issue any signal. (This only occurs during rapid consecutive I2C transfers. That explains why the issue only shows up with i2cdetect.) With these two fixes, i2c device detection now functions correctly on the K1 SoC. Fixes: 5ea558473fa31 ("i2c: spacemit: add support for SpacemiT K1 SoC") Tested-by: Aurelien Jarno Signed-off-by: Troy Mitchell Reviewed-by: Aurelien Jarno Tested-by: Michael Opdenacker Signed-off-by: Andi Shyti Link: https://lore.kernel.org/r/20251113-fix-k1-detect-failure-v2-1-b02a9a74f65a@linux.spacemit.com Signed-off-by: Sasha Levin --- drivers/i2c/busses/i2c-k1.c | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/drivers/i2c/busses/i2c-k1.c b/drivers/i2c/busses/i2c-k1.c index 6b918770e612e0..d42c03ef5db598 100644 --- a/drivers/i2c/busses/i2c-k1.c +++ b/drivers/i2c/busses/i2c-k1.c @@ -158,11 +158,16 @@ static int spacemit_i2c_handle_err(struct spacemit_i2c_dev *i2c) { dev_dbg(i2c->dev, "i2c error status: 0x%08x\n", i2c->status); - if (i2c->status & (SPACEMIT_SR_BED | SPACEMIT_SR_ALD)) { + /* Arbitration Loss Detected */ + if (i2c->status & SPACEMIT_SR_ALD) { spacemit_i2c_reset(i2c); return -EAGAIN; } + /* Bus Error No ACK/NAK */ + if (i2c->status & SPACEMIT_SR_BED) + spacemit_i2c_reset(i2c); + return i2c->status & SPACEMIT_SR_ACKNAK ? -ENXIO : -EIO; } @@ -224,6 +229,12 @@ static void spacemit_i2c_check_bus_release(struct spacemit_i2c_dev *i2c) } } +static inline void +spacemit_i2c_clear_int_status(struct spacemit_i2c_dev *i2c, u32 mask) +{ + writel(mask & SPACEMIT_I2C_INT_STATUS_MASK, i2c->base + SPACEMIT_ISR); +} + static void spacemit_i2c_init(struct spacemit_i2c_dev *i2c) { u32 val; @@ -267,12 +278,8 @@ static void spacemit_i2c_init(struct spacemit_i2c_dev *i2c) val = readl(i2c->base + SPACEMIT_IRCR); val |= SPACEMIT_RCR_SDA_GLITCH_NOFIX; writel(val, i2c->base + SPACEMIT_IRCR); -} -static inline void -spacemit_i2c_clear_int_status(struct spacemit_i2c_dev *i2c, u32 mask) -{ - writel(mask & SPACEMIT_I2C_INT_STATUS_MASK, i2c->base + SPACEMIT_ISR); + spacemit_i2c_clear_int_status(i2c, SPACEMIT_I2C_INT_STATUS_MASK); } static void spacemit_i2c_start(struct spacemit_i2c_dev *i2c) From d8fae0475d95858833efe7d7e59bebe6da972032 Mon Sep 17 00:00:00 2001 From: Dave Kleikamp Date: Tue, 2 Dec 2025 09:28:10 -0600 Subject: [PATCH 0941/3902] dma/pool: eliminate alloc_pages warning in atomic_pool_expand [ Upstream commit 463d439becb81383f3a5a5d840800131f265a09c ] atomic_pool_expand iteratively tries the allocation while decrementing the page order. There is no need to issue a warning if an attempted allocation fails. Signed-off-by: Dave Kleikamp Reviewed-by: Robin Murphy Fixes: d7e673ec2c8e ("dma-pool: Only allocate from CMA when in same memory zone") [mszyprow: fixed typo] Signed-off-by: Marek Szyprowski Link: https://lore.kernel.org/r/20251202152810.142370-1-dave.kleikamp@oracle.com Signed-off-by: Sasha Levin --- kernel/dma/pool.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/kernel/dma/pool.c b/kernel/dma/pool.c index ee45dee33d4916..26392badc36b0f 100644 --- a/kernel/dma/pool.c +++ b/kernel/dma/pool.c @@ -93,7 +93,7 @@ static int atomic_pool_expand(struct gen_pool *pool, size_t pool_size, page = dma_alloc_from_contiguous(NULL, 1 << order, order, false); if (!page) - page = alloc_pages(gfp, order); + page = alloc_pages(gfp | __GFP_NOWARN, order); } while (!page && order-- > 0); if (!page) goto out; From 4058530b4294273ac298f955ea83c60b49927a55 Mon Sep 17 00:00:00 2001 From: Andres J Rosa Date: Wed, 3 Dec 2025 10:25:01 -0600 Subject: [PATCH 0942/3902] ALSA: uapi: Fix typo in asound.h comment [ Upstream commit 9a97857db0c5655b8932f86b5d18bb959079b0ee ] Fix 'level-shit' to 'level-shift' in struct snd_cea_861_aud_if comment. Fixes: 7ba1c40b536e ("ALSA: Add definitions for CEA-861 Audio InfoFrames") Signed-off-by: Andres J Rosa Link: https://patch.msgid.link/20251203162509.1822-1-andyrosa@gmail.com Signed-off-by: Takashi Iwai Signed-off-by: Sasha Levin --- include/uapi/sound/asound.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/uapi/sound/asound.h b/include/uapi/sound/asound.h index 5a049eeaeccea5..d3ce75ba938a84 100644 --- a/include/uapi/sound/asound.h +++ b/include/uapi/sound/asound.h @@ -60,7 +60,7 @@ struct snd_cea_861_aud_if { unsigned char db2_sf_ss; /* sample frequency and size */ unsigned char db3; /* not used, all zeros */ unsigned char db4_ca; /* channel allocation code */ - unsigned char db5_dminh_lsv; /* downmix inhibit & level-shit values */ + unsigned char db5_dminh_lsv; /* downmix inhibit & level-shift values */ }; /**************************************************************************** From 0c1239fb5c425d281264d3b6afa7335f47681ac8 Mon Sep 17 00:00:00 2001 From: Xiaogang Chen Date: Mon, 1 Dec 2025 14:12:29 -0600 Subject: [PATCH 0943/3902] drm/amdkfd: Use huge page size to check split svm range alignment [ Upstream commit bf2084a7b1d75d093b6a79df4c10142d49fbaa0e ] When split svm ranges that have been mapped using huge page should use huge page size(2MB) to check split range alignment, not prange->granularity that means migration granularity. Fixes: 7ef6b2d4b7e5 ("drm/amdkfd: remap unaligned svm ranges that have split") Signed-off-by: Xiaogang Chen Reviewed-by: Philip Yang Signed-off-by: Alex Deucher (cherry picked from commit 448ee45353ef9fb1a34f5f26eb3f48923c6f0898) Signed-off-by: Sasha Levin --- drivers/gpu/drm/amd/amdkfd/kfd_svm.c | 46 +++++++++++++++++++--------- 1 file changed, 32 insertions(+), 14 deletions(-) diff --git a/drivers/gpu/drm/amd/amdkfd/kfd_svm.c b/drivers/gpu/drm/amd/amdkfd/kfd_svm.c index 74a1d3e1d52bee..49dd0a81114e4c 100644 --- a/drivers/gpu/drm/amd/amdkfd/kfd_svm.c +++ b/drivers/gpu/drm/amd/amdkfd/kfd_svm.c @@ -1144,30 +1144,48 @@ static int svm_range_split_tail(struct svm_range *prange, uint64_t new_last, struct list_head *insert_list, struct list_head *remap_list) { + unsigned long last_align_down = ALIGN_DOWN(prange->last, 512); + unsigned long start_align = ALIGN(prange->start, 512); + bool huge_page_mapping = last_align_down > start_align; struct svm_range *tail = NULL; - int r = svm_range_split(prange, prange->start, new_last, &tail); + int r; - if (!r) { - list_add(&tail->list, insert_list); - if (!IS_ALIGNED(new_last + 1, 1UL << prange->granularity)) - list_add(&tail->update_list, remap_list); - } - return r; + r = svm_range_split(prange, prange->start, new_last, &tail); + + if (r) + return r; + + list_add(&tail->list, insert_list); + + if (huge_page_mapping && tail->start > start_align && + tail->start < last_align_down && (!IS_ALIGNED(tail->start, 512))) + list_add(&tail->update_list, remap_list); + + return 0; } static int svm_range_split_head(struct svm_range *prange, uint64_t new_start, struct list_head *insert_list, struct list_head *remap_list) { + unsigned long last_align_down = ALIGN_DOWN(prange->last, 512); + unsigned long start_align = ALIGN(prange->start, 512); + bool huge_page_mapping = last_align_down > start_align; struct svm_range *head = NULL; - int r = svm_range_split(prange, new_start, prange->last, &head); + int r; - if (!r) { - list_add(&head->list, insert_list); - if (!IS_ALIGNED(new_start, 1UL << prange->granularity)) - list_add(&head->update_list, remap_list); - } - return r; + r = svm_range_split(prange, new_start, prange->last, &head); + + if (r) + return r; + + list_add(&head->list, insert_list); + + if (huge_page_mapping && head->last + 1 > start_align && + head->last + 1 < last_align_down && (!IS_ALIGNED(head->last, 512))) + list_add(&head->update_list, remap_list); + + return 0; } static void From f67ae28e9ac348fb47c9b54f06bbb31fd3578f3e Mon Sep 17 00:00:00 2001 From: Haotian Zhang Date: Wed, 26 Nov 2025 16:06:25 +0800 Subject: [PATCH 0944/3902] rtc: gamecube: Check the return value of ioremap() [ Upstream commit d1220e47e4bd2be8b84bc158f4dea44f2f88b226 ] The function ioremap() in gamecube_rtc_read_offset_from_sram() can fail and return NULL, which is dereferenced without checking, leading to a NULL pointer dereference. Add a check for the return value of ioremap() and return -ENOMEM on failure. Fixes: 86559400b3ef ("rtc: gamecube: Add a RTC driver for the GameCube, Wii and Wii U") Signed-off-by: Haotian Zhang Reviewed-by: Link Mauve Link: https://patch.msgid.link/20251126080625.1752-1-vulab@iscas.ac.cn Signed-off-by: Alexandre Belloni Signed-off-by: Sasha Levin --- drivers/rtc/rtc-gamecube.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/drivers/rtc/rtc-gamecube.c b/drivers/rtc/rtc-gamecube.c index c828bc8e05b9cc..045d5d45ab4b07 100644 --- a/drivers/rtc/rtc-gamecube.c +++ b/drivers/rtc/rtc-gamecube.c @@ -242,6 +242,10 @@ static int gamecube_rtc_read_offset_from_sram(struct priv *d) } hw_srnprot = ioremap(res.start, resource_size(&res)); + if (!hw_srnprot) { + pr_err("failed to ioremap hw_srnprot\n"); + return -ENOMEM; + } old = ioread32be(hw_srnprot); /* TODO: figure out why we use this magic constant. I obtained it by From 4a7915a26d19e053828e0645c991770f271b2c89 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nuno=20S=C3=A1?= Date: Fri, 28 Nov 2025 16:36:38 +0000 Subject: [PATCH 0945/3902] rtc: max31335: Fix ignored return value in set_alarm MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [ Upstream commit f07640f9fb8df2158199da1da1f8282948385a84 ] Return the result from regmap_update_bits() instead of ignoring it and always returning 0. Fixes: dedaf03b99d6 ("rtc: max31335: add driver support") Signed-off-by: Nuno Sá Link: https://patch.msgid.link/20251128-max31335-handler-error-v1-1-6b6f7f78dbda@analog.com Signed-off-by: Alexandre Belloni Signed-off-by: Sasha Levin --- drivers/rtc/rtc-max31335.c | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/drivers/rtc/rtc-max31335.c b/drivers/rtc/rtc-max31335.c index dfb5bad3a3691d..23b7bf16b4cd5d 100644 --- a/drivers/rtc/rtc-max31335.c +++ b/drivers/rtc/rtc-max31335.c @@ -391,10 +391,8 @@ static int max31335_set_alarm(struct device *dev, struct rtc_wkalrm *alrm) if (ret) return ret; - ret = regmap_update_bits(max31335->regmap, max31335->chip->int_status_reg, - MAX31335_STATUS1_A1F, 0); - - return 0; + return regmap_update_bits(max31335->regmap, max31335->chip->int_status_reg, + MAX31335_STATUS1_A1F, 0); } static int max31335_alarm_irq_enable(struct device *dev, unsigned int enabled) From d48f75972d39537b837f569134e6fd1f05fcc27c Mon Sep 17 00:00:00 2001 From: Javier Martinez Canillas Date: Sat, 6 Dec 2025 14:38:48 +0100 Subject: [PATCH 0946/3902] regulator: spacemit: Align input supply name with the DT binding [ Upstream commit 99f0c3a654c4a762aca4fadc8d9f8636b36d570a ] The Device Tree binding schema for the SpacemiT P1 PMIC defines the main input supply property as "vin-supply", but the driver defines the supply name for BUCK and ALDO regulators as "vcc". This causes the regulator core to lookup for a non-existent "vcc-supply". Rename the supply from "vcc" to "vin", to match the DT binding and ensure that the regulators input supplies are correctly resolved. After this change, the regulators supply hierarchy is correctly reported: $ cat /sys/kernel/debug/regulator/regulator_summary regulator use open bypass opmode voltage current min max --------------------------------------------------------------------------------------- regulator-dummy 1 0 0 unknown 0mV 0mA 0mV 0mV dc_in_12v 2 1 0 unknown 12000mV 0mA 12000mV 12000mV vcc_4v 7 10 0 unknown 4000mV 0mA 4000mV 4000mV buck1 1 0 0 unknown 1050mV 0mA 500mV 3425mV buck2 1 0 0 unknown 900mV 0mA 500mV 3425mV buck3 1 0 0 unknown 1800mV 0mA 500mV 1800mV buck4 1 0 0 unknown 3300mV 0mA 500mV 3300mV buck5 3 7 0 unknown 2100mV 0mA 500mV 3425mV dldo1 0 0 0 unknown 1200mV 0mA 500mV 3125mV dldo2 0 0 0 unknown 500mV 0mA 500mV 3125mV dldo3 0 0 0 unknown 500mV 0mA 500mV 3125mV dldo4 1 0 0 unknown 1800mV 0mA 500mV 3125mV dldo5 0 0 0 unknown 500mV 0mA 500mV 3125mV dldo6 1 0 0 unknown 1800mV 0mA 500mV 3125mV dldo7 0 0 0 unknown 500mV 0mA 500mV 3125mV buck6 1 0 0 unknown 1100mV 0mA 500mV 3425mV aldo1 0 0 0 unknown 1800mV 0mA 500mV 3125mV aldo2 0 0 0 unknown 500mV 0mA 500mV 3125mV aldo3 0 0 0 unknown 500mV 0mA 500mV 3125mV aldo4 0 0 0 unknown 500mV 0mA 500mV 3125mV Fixes: 8b84d712ad84 ("regulator: spacemit: support SpacemiT P1 regulators") Signed-off-by: Javier Martinez Canillas Link: https://patch.msgid.link/20251206133852.1739475-1-javierm@redhat.com Signed-off-by: Mark Brown Signed-off-by: Sasha Levin --- drivers/regulator/spacemit-p1.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/regulator/spacemit-p1.c b/drivers/regulator/spacemit-p1.c index d437e6738ea1e3..2bf9137e12b1d0 100644 --- a/drivers/regulator/spacemit-p1.c +++ b/drivers/regulator/spacemit-p1.c @@ -87,10 +87,10 @@ static const struct linear_range p1_ldo_ranges[] = { } #define P1_BUCK_DESC(_n) \ - P1_REG_DESC(BUCK, buck, _n, "vcc", 0x47, BUCK_MASK, 254, p1_buck_ranges) + P1_REG_DESC(BUCK, buck, _n, "vin", 0x47, BUCK_MASK, 254, p1_buck_ranges) #define P1_ALDO_DESC(_n) \ - P1_REG_DESC(ALDO, aldo, _n, "vcc", 0x5b, LDO_MASK, 117, p1_ldo_ranges) + P1_REG_DESC(ALDO, aldo, _n, "vin", 0x5b, LDO_MASK, 117, p1_ldo_ranges) #define P1_DLDO_DESC(_n) \ P1_REG_DESC(DLDO, dldo, _n, "buck5", 0x67, LDO_MASK, 117, p1_ldo_ranges) From 8f9e51cf2a2a43d0cd72d3dc0b5ccea3f639c187 Mon Sep 17 00:00:00 2001 From: Junrui Luo Date: Tue, 9 Dec 2025 13:16:41 +0800 Subject: [PATCH 0947/3902] ALSA: firewire-motu: add bounds check in put_user loop for DSP events [ Upstream commit 298e753880b6ea99ac30df34959a7a03b0878eed ] In the DSP event handling code, a put_user() loop copies event data. When the user buffer size is not aligned to 4 bytes, it could overwrite beyond the buffer boundary. Fix by adding a bounds check before put_user(). Suggested-by: Takashi Iwai Fixes: 634ec0b2906e ("ALSA: firewire-motu: notify event for parameter change in register DSP model") Signed-off-by: Junrui Luo Link: https://patch.msgid.link/SYBPR01MB788112C72AF8A1C8C448B4B8AFA3A@SYBPR01MB7881.ausprd01.prod.outlook.com Signed-off-by: Takashi Iwai Signed-off-by: Sasha Levin --- sound/firewire/motu/motu-hwdep.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sound/firewire/motu/motu-hwdep.c b/sound/firewire/motu/motu-hwdep.c index 6675b23aad69ef..89dc436a065299 100644 --- a/sound/firewire/motu/motu-hwdep.c +++ b/sound/firewire/motu/motu-hwdep.c @@ -75,7 +75,7 @@ static long hwdep_read(struct snd_hwdep *hwdep, char __user *buf, long count, while (consumed < count && snd_motu_register_dsp_message_parser_copy_event(motu, &ev)) { ptr = (u32 __user *)(buf + consumed); - if (put_user(ev, ptr)) + if (consumed + sizeof(ev) > count || put_user(ev, ptr)) return -EFAULT; consumed += sizeof(ev); } From fb074e5c4f3eacda00b920a7192f12aa8fdbddad Mon Sep 17 00:00:00 2001 From: Liyuan Pang Date: Tue, 9 Dec 2025 03:19:45 +0100 Subject: [PATCH 0948/3902] ARM: 9464/1: fix input-only operand modification in load_unaligned_zeropad() [ Upstream commit edb924a7211c9aa7a4a415e03caee4d875e46b8e ] In the inline assembly inside load_unaligned_zeropad(), the "addr" is constrained as input-only operand. The compiler assumes that on exit from the asm statement these operands contain the same values as they had before executing the statement, but when kernel page fault happened, the assembly fixup code "bic %2 %2, #0x3" modify the value of "addr", which may lead to an unexpected behavior. Use a temporary variable "tmp" to handle it, instead of modifying the input-only operand, just like what arm64's load_unaligned_zeropad() does. Fixes: b9a50f74905a ("ARM: 7450/1: dcache: select DCACHE_WORD_ACCESS for little-endian ARMv6+ CPUs") Co-developed-by: Xie Yuanbin Signed-off-by: Xie Yuanbin Signed-off-by: Liyuan Pang Signed-off-by: Russell King (Oracle) Signed-off-by: Sasha Levin --- arch/arm/include/asm/word-at-a-time.h | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/arch/arm/include/asm/word-at-a-time.h b/arch/arm/include/asm/word-at-a-time.h index f9a3897b06e7ff..5023f98d8293d5 100644 --- a/arch/arm/include/asm/word-at-a-time.h +++ b/arch/arm/include/asm/word-at-a-time.h @@ -67,7 +67,7 @@ static inline unsigned long find_zero(unsigned long mask) */ static inline unsigned long load_unaligned_zeropad(const void *addr) { - unsigned long ret, offset; + unsigned long ret, tmp; /* Load word from unaligned pointer addr */ asm( @@ -75,9 +75,9 @@ static inline unsigned long load_unaligned_zeropad(const void *addr) "2:\n" " .pushsection .text.fixup,\"ax\"\n" " .align 2\n" - "3: and %1, %2, #0x3\n" - " bic %2, %2, #0x3\n" - " ldr %0, [%2]\n" + "3: bic %1, %2, #0x3\n" + " ldr %0, [%1]\n" + " and %1, %2, #0x3\n" " lsl %1, %1, #0x3\n" #ifndef __ARMEB__ " lsr %0, %0, %1\n" @@ -90,7 +90,7 @@ static inline unsigned long load_unaligned_zeropad(const void *addr) " .align 3\n" " .long 1b, 3b\n" " .popsection" - : "=&r" (ret), "=&r" (offset) + : "=&r" (ret), "=&r" (tmp) : "r" (addr), "Qo" (*(unsigned long *)addr)); return ret; From c287d34592cca02a91ad60449d88e5c713e9f57b Mon Sep 17 00:00:00 2001 From: Jani Nikula Date: Thu, 18 Sep 2025 11:40:51 +0300 Subject: [PATCH 0949/3902] drm/xe/fbdev: use the same 64-byte stride alignment as i915 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [ Upstream commit 4a36b339a14ae6f2a366125e3d64f0c165193293 ] For reasons unknown, xe uses XE_PAGE_SIZE alignment for stride. Presumably it's just a confusion between stride alignment and bo allocation size alignment. Switch to 64 byte alignment to, uh, align with i915. This will also be helpful in deduplicating and unifying the xe and i915 framebuffer allocation. Link: https://lore.kernel.org/r/aLqsC87Ol_zCXOkN@intel.com Suggested-by: Ville Syrjälä Reviewed-by: Ville Syrjälä Link: https://lore.kernel.org/r/7f4972104de8b179d5724ae83892ee294d3f3fd3.1758184771.git.jani.nikula@intel.com Signed-off-by: Jani Nikula Stable-dep-of: 460b31720369 ("drm/i915/fbdev: Hold runtime PM ref during fbdev BO creation") Signed-off-by: Sasha Levin --- drivers/gpu/drm/xe/display/intel_fbdev_fb.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/gpu/drm/xe/display/intel_fbdev_fb.c b/drivers/gpu/drm/xe/display/intel_fbdev_fb.c index 8ea9a472113c41..bce4cb16f68200 100644 --- a/drivers/gpu/drm/xe/display/intel_fbdev_fb.c +++ b/drivers/gpu/drm/xe/display/intel_fbdev_fb.c @@ -33,7 +33,7 @@ struct intel_framebuffer *intel_fbdev_fb_alloc(struct drm_fb_helper *helper, mode_cmd.height = sizes->surface_height; mode_cmd.pitches[0] = ALIGN(mode_cmd.width * - DIV_ROUND_UP(sizes->surface_bpp, 8), XE_PAGE_SIZE); + DIV_ROUND_UP(sizes->surface_bpp, 8), 64); mode_cmd.pixel_format = drm_mode_legacy_fb_format(sizes->surface_bpp, sizes->surface_depth); From bc600ef08326915039e61432501ea290940b0a57 Mon Sep 17 00:00:00 2001 From: Jani Nikula Date: Thu, 18 Sep 2025 11:40:52 +0300 Subject: [PATCH 0950/3902] drm/i915/fbdev: make intel_framebuffer_create() error return handling explicit MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [ Upstream commit 6979d2c80c2a5b1f04157c4d6eb038bb32861cfa ] It's sketchy to pass error pointers via to_intel_framebuffer(). It probably works as long as struct intel_framebuffer embeds struct drm_framebuffer at offset 0, but be explicit about it. Reviewed-by: Ville Syrjälä Link: https://lore.kernel.org/r/17631db227d527d6c67f5d6b67adec1ff8dc6f8d.1758184771.git.jani.nikula@intel.com Signed-off-by: Jani Nikula Stable-dep-of: 460b31720369 ("drm/i915/fbdev: Hold runtime PM ref during fbdev BO creation") Signed-off-by: Sasha Levin --- drivers/gpu/drm/i915/display/intel_fbdev_fb.c | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/drivers/gpu/drm/i915/display/intel_fbdev_fb.c b/drivers/gpu/drm/i915/display/intel_fbdev_fb.c index 210aee9ae88b88..b9dfd00a7d05b4 100644 --- a/drivers/gpu/drm/i915/display/intel_fbdev_fb.c +++ b/drivers/gpu/drm/i915/display/intel_fbdev_fb.c @@ -67,9 +67,16 @@ struct intel_framebuffer *intel_fbdev_fb_alloc(struct drm_fb_helper *helper, mode_cmd.pixel_format, mode_cmd.modifier[0]), &mode_cmd); + if (IS_ERR(fb)) { + i915_gem_object_put(obj); + goto err; + } + i915_gem_object_put(obj); return to_intel_framebuffer(fb); +err: + return ERR_CAST(fb); } int intel_fbdev_fb_fill_info(struct intel_display *display, struct fb_info *info, From 6f36ae55d245f4f88ca5d27218f252fd03104d2b Mon Sep 17 00:00:00 2001 From: Jani Nikula Date: Thu, 18 Sep 2025 11:40:53 +0300 Subject: [PATCH 0951/3902] drm/{i915, xe}/fbdev: pass struct drm_device to intel_fbdev_fb_alloc() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [ Upstream commit 9e5cf822a207ee8c9856024c047abaccb4d185e5 ] The function doesn't actually need struct drm_fb_helper for anything, just pass struct drm_device. Reviewed-by: Ville Syrjälä Link: https://lore.kernel.org/r/16360584f80cdc5ee35fd94cfd92fd3955588dfd.1758184771.git.jani.nikula@intel.com Signed-off-by: Jani Nikula Stable-dep-of: 460b31720369 ("drm/i915/fbdev: Hold runtime PM ref during fbdev BO creation") Signed-off-by: Sasha Levin --- drivers/gpu/drm/i915/display/intel_fbdev.c | 2 +- drivers/gpu/drm/i915/display/intel_fbdev_fb.c | 10 +++++----- drivers/gpu/drm/i915/display/intel_fbdev_fb.h | 4 ++-- drivers/gpu/drm/xe/display/intel_fbdev_fb.c | 7 +++---- 4 files changed, 11 insertions(+), 12 deletions(-) diff --git a/drivers/gpu/drm/i915/display/intel_fbdev.c b/drivers/gpu/drm/i915/display/intel_fbdev.c index 7c4709d58aa345..46c6de5f608881 100644 --- a/drivers/gpu/drm/i915/display/intel_fbdev.c +++ b/drivers/gpu/drm/i915/display/intel_fbdev.c @@ -237,7 +237,7 @@ int intel_fbdev_driver_fbdev_probe(struct drm_fb_helper *helper, if (!fb || drm_WARN_ON(display->drm, !intel_fb_bo(&fb->base))) { drm_dbg_kms(display->drm, "no BIOS fb, allocating a new one\n"); - fb = intel_fbdev_fb_alloc(helper, sizes); + fb = intel_fbdev_fb_alloc(display->drm, sizes); if (IS_ERR(fb)) return PTR_ERR(fb); } else { diff --git a/drivers/gpu/drm/i915/display/intel_fbdev_fb.c b/drivers/gpu/drm/i915/display/intel_fbdev_fb.c index b9dfd00a7d05b4..4de13d1a4c7a7b 100644 --- a/drivers/gpu/drm/i915/display/intel_fbdev_fb.c +++ b/drivers/gpu/drm/i915/display/intel_fbdev_fb.c @@ -13,11 +13,11 @@ #include "intel_fb.h" #include "intel_fbdev_fb.h" -struct intel_framebuffer *intel_fbdev_fb_alloc(struct drm_fb_helper *helper, +struct intel_framebuffer *intel_fbdev_fb_alloc(struct drm_device *drm, struct drm_fb_helper_surface_size *sizes) { - struct intel_display *display = to_intel_display(helper->dev); - struct drm_i915_private *dev_priv = to_i915(display->drm); + struct intel_display *display = to_intel_display(drm); + struct drm_i915_private *dev_priv = to_i915(drm); struct drm_framebuffer *fb; struct drm_mode_fb_cmd2 mode_cmd = {}; struct drm_i915_gem_object *obj; @@ -58,12 +58,12 @@ struct intel_framebuffer *intel_fbdev_fb_alloc(struct drm_fb_helper *helper, } if (IS_ERR(obj)) { - drm_err(display->drm, "failed to allocate framebuffer (%pe)\n", obj); + drm_err(drm, "failed to allocate framebuffer (%pe)\n", obj); return ERR_PTR(-ENOMEM); } fb = intel_framebuffer_create(intel_bo_to_drm_bo(obj), - drm_get_format_info(display->drm, + drm_get_format_info(drm, mode_cmd.pixel_format, mode_cmd.modifier[0]), &mode_cmd); diff --git a/drivers/gpu/drm/i915/display/intel_fbdev_fb.h b/drivers/gpu/drm/i915/display/intel_fbdev_fb.h index cb795727271502..668ae355f5e5b8 100644 --- a/drivers/gpu/drm/i915/display/intel_fbdev_fb.h +++ b/drivers/gpu/drm/i915/display/intel_fbdev_fb.h @@ -6,14 +6,14 @@ #ifndef __INTEL_FBDEV_FB_H__ #define __INTEL_FBDEV_FB_H__ -struct drm_fb_helper; +struct drm_device; struct drm_fb_helper_surface_size; struct drm_gem_object; struct fb_info; struct i915_vma; struct intel_display; -struct intel_framebuffer *intel_fbdev_fb_alloc(struct drm_fb_helper *helper, +struct intel_framebuffer *intel_fbdev_fb_alloc(struct drm_device *drm, struct drm_fb_helper_surface_size *sizes); int intel_fbdev_fb_fill_info(struct intel_display *display, struct fb_info *info, struct drm_gem_object *obj, struct i915_vma *vma); diff --git a/drivers/gpu/drm/xe/display/intel_fbdev_fb.c b/drivers/gpu/drm/xe/display/intel_fbdev_fb.c index bce4cb16f68200..5c0874bfa6ab10 100644 --- a/drivers/gpu/drm/xe/display/intel_fbdev_fb.c +++ b/drivers/gpu/drm/xe/display/intel_fbdev_fb.c @@ -15,12 +15,11 @@ #include -struct intel_framebuffer *intel_fbdev_fb_alloc(struct drm_fb_helper *helper, +struct intel_framebuffer *intel_fbdev_fb_alloc(struct drm_device *drm, struct drm_fb_helper_surface_size *sizes) { struct drm_framebuffer *fb; - struct drm_device *dev = helper->dev; - struct xe_device *xe = to_xe_device(dev); + struct xe_device *xe = to_xe_device(drm); struct drm_mode_fb_cmd2 mode_cmd = {}; struct xe_bo *obj; int size; @@ -67,7 +66,7 @@ struct intel_framebuffer *intel_fbdev_fb_alloc(struct drm_fb_helper *helper, } fb = intel_framebuffer_create(&obj->ttm.base, - drm_get_format_info(dev, + drm_get_format_info(drm, mode_cmd.pixel_format, mode_cmd.modifier[0]), &mode_cmd); From cabc9d26c06fec51a21702c20759a816755de131 Mon Sep 17 00:00:00 2001 From: Jani Nikula Date: Thu, 18 Sep 2025 11:40:54 +0300 Subject: [PATCH 0952/3902] drm/{i915, xe}/fbdev: deduplicate struct drm_mode_fb_cmd2 init MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [ Upstream commit f9ff39f940f5ddd1d4ffcff602de7206aa1ff05d ] Pull struct drm_mode_fb_cmd2 initialization out of the driver dependent code into shared display code. v2: Rebase on xe stride alignment change Reviewed-by: Ville Syrjälä Link: https://lore.kernel.org/r/e922e47bfd39f9c5777f869ff23c23309ebbb380.1758184771.git.jani.nikula@intel.com Signed-off-by: Jani Nikula Stable-dep-of: 460b31720369 ("drm/i915/fbdev: Hold runtime PM ref during fbdev BO creation") Signed-off-by: Sasha Levin --- drivers/gpu/drm/i915/display/intel_fbdev.c | 32 ++++++++++++++++++- drivers/gpu/drm/i915/display/intel_fbdev_fb.c | 25 ++++----------- drivers/gpu/drm/i915/display/intel_fbdev_fb.h | 4 +-- drivers/gpu/drm/xe/display/intel_fbdev_fb.c | 25 ++++----------- 4 files changed, 45 insertions(+), 41 deletions(-) diff --git a/drivers/gpu/drm/i915/display/intel_fbdev.c b/drivers/gpu/drm/i915/display/intel_fbdev.c index 46c6de5f608881..e46c08762b847d 100644 --- a/drivers/gpu/drm/i915/display/intel_fbdev.c +++ b/drivers/gpu/drm/i915/display/intel_fbdev.c @@ -207,6 +207,35 @@ static const struct drm_fb_helper_funcs intel_fb_helper_funcs = { .fb_set_suspend = intelfb_set_suspend, }; +static void intel_fbdev_fill_mode_cmd(struct drm_fb_helper_surface_size *sizes, + struct drm_mode_fb_cmd2 *mode_cmd) +{ + /* we don't do packed 24bpp */ + if (sizes->surface_bpp == 24) + sizes->surface_bpp = 32; + + mode_cmd->width = sizes->surface_width; + mode_cmd->height = sizes->surface_height; + + mode_cmd->pitches[0] = ALIGN(mode_cmd->width * DIV_ROUND_UP(sizes->surface_bpp, 8), 64); + mode_cmd->pixel_format = drm_mode_legacy_fb_format(sizes->surface_bpp, + sizes->surface_depth); +} + +static struct intel_framebuffer * +__intel_fbdev_fb_alloc(struct intel_display *display, + struct drm_fb_helper_surface_size *sizes) +{ + struct drm_mode_fb_cmd2 mode_cmd = {}; + struct intel_framebuffer *fb; + + intel_fbdev_fill_mode_cmd(sizes, &mode_cmd); + + fb = intel_fbdev_fb_alloc(display->drm, &mode_cmd); + + return fb; +} + int intel_fbdev_driver_fbdev_probe(struct drm_fb_helper *helper, struct drm_fb_helper_surface_size *sizes) { @@ -237,7 +266,8 @@ int intel_fbdev_driver_fbdev_probe(struct drm_fb_helper *helper, if (!fb || drm_WARN_ON(display->drm, !intel_fb_bo(&fb->base))) { drm_dbg_kms(display->drm, "no BIOS fb, allocating a new one\n"); - fb = intel_fbdev_fb_alloc(display->drm, sizes); + + fb = __intel_fbdev_fb_alloc(display, sizes); if (IS_ERR(fb)) return PTR_ERR(fb); } else { diff --git a/drivers/gpu/drm/i915/display/intel_fbdev_fb.c b/drivers/gpu/drm/i915/display/intel_fbdev_fb.c index 4de13d1a4c7a7b..685612e6afc53e 100644 --- a/drivers/gpu/drm/i915/display/intel_fbdev_fb.c +++ b/drivers/gpu/drm/i915/display/intel_fbdev_fb.c @@ -3,7 +3,7 @@ * Copyright © 2023 Intel Corporation */ -#include +#include #include "gem/i915_gem_lmem.h" @@ -14,28 +14,15 @@ #include "intel_fbdev_fb.h" struct intel_framebuffer *intel_fbdev_fb_alloc(struct drm_device *drm, - struct drm_fb_helper_surface_size *sizes) + struct drm_mode_fb_cmd2 *mode_cmd) { struct intel_display *display = to_intel_display(drm); struct drm_i915_private *dev_priv = to_i915(drm); struct drm_framebuffer *fb; - struct drm_mode_fb_cmd2 mode_cmd = {}; struct drm_i915_gem_object *obj; int size; - /* we don't do packed 24bpp */ - if (sizes->surface_bpp == 24) - sizes->surface_bpp = 32; - - mode_cmd.width = sizes->surface_width; - mode_cmd.height = sizes->surface_height; - - mode_cmd.pitches[0] = ALIGN(mode_cmd.width * - DIV_ROUND_UP(sizes->surface_bpp, 8), 64); - mode_cmd.pixel_format = drm_mode_legacy_fb_format(sizes->surface_bpp, - sizes->surface_depth); - - size = mode_cmd.pitches[0] * mode_cmd.height; + size = mode_cmd->pitches[0] * mode_cmd->height; size = PAGE_ALIGN(size); obj = ERR_PTR(-ENODEV); @@ -64,9 +51,9 @@ struct intel_framebuffer *intel_fbdev_fb_alloc(struct drm_device *drm, fb = intel_framebuffer_create(intel_bo_to_drm_bo(obj), drm_get_format_info(drm, - mode_cmd.pixel_format, - mode_cmd.modifier[0]), - &mode_cmd); + mode_cmd->pixel_format, + mode_cmd->modifier[0]), + mode_cmd); if (IS_ERR(fb)) { i915_gem_object_put(obj); goto err; diff --git a/drivers/gpu/drm/i915/display/intel_fbdev_fb.h b/drivers/gpu/drm/i915/display/intel_fbdev_fb.h index 668ae355f5e5b8..83454ffbf79cd6 100644 --- a/drivers/gpu/drm/i915/display/intel_fbdev_fb.h +++ b/drivers/gpu/drm/i915/display/intel_fbdev_fb.h @@ -7,14 +7,14 @@ #define __INTEL_FBDEV_FB_H__ struct drm_device; -struct drm_fb_helper_surface_size; struct drm_gem_object; +struct drm_mode_fb_cmd2; struct fb_info; struct i915_vma; struct intel_display; struct intel_framebuffer *intel_fbdev_fb_alloc(struct drm_device *drm, - struct drm_fb_helper_surface_size *sizes); + struct drm_mode_fb_cmd2 *mode_cmd); int intel_fbdev_fb_fill_info(struct intel_display *display, struct fb_info *info, struct drm_gem_object *obj, struct i915_vma *vma); diff --git a/drivers/gpu/drm/xe/display/intel_fbdev_fb.c b/drivers/gpu/drm/xe/display/intel_fbdev_fb.c index 5c0874bfa6ab10..8eaf1cc7fdf930 100644 --- a/drivers/gpu/drm/xe/display/intel_fbdev_fb.c +++ b/drivers/gpu/drm/xe/display/intel_fbdev_fb.c @@ -3,7 +3,7 @@ * Copyright © 2023 Intel Corporation */ -#include +#include #include "intel_display_core.h" #include "intel_display_types.h" @@ -16,27 +16,14 @@ #include struct intel_framebuffer *intel_fbdev_fb_alloc(struct drm_device *drm, - struct drm_fb_helper_surface_size *sizes) + struct drm_mode_fb_cmd2 *mode_cmd) { struct drm_framebuffer *fb; struct xe_device *xe = to_xe_device(drm); - struct drm_mode_fb_cmd2 mode_cmd = {}; struct xe_bo *obj; int size; - /* we don't do packed 24bpp */ - if (sizes->surface_bpp == 24) - sizes->surface_bpp = 32; - - mode_cmd.width = sizes->surface_width; - mode_cmd.height = sizes->surface_height; - - mode_cmd.pitches[0] = ALIGN(mode_cmd.width * - DIV_ROUND_UP(sizes->surface_bpp, 8), 64); - mode_cmd.pixel_format = drm_mode_legacy_fb_format(sizes->surface_bpp, - sizes->surface_depth); - - size = mode_cmd.pitches[0] * mode_cmd.height; + size = mode_cmd->pitches[0] * mode_cmd->height; size = PAGE_ALIGN(size); obj = ERR_PTR(-ENODEV); @@ -67,9 +54,9 @@ struct intel_framebuffer *intel_fbdev_fb_alloc(struct drm_device *drm, fb = intel_framebuffer_create(&obj->ttm.base, drm_get_format_info(drm, - mode_cmd.pixel_format, - mode_cmd.modifier[0]), - &mode_cmd); + mode_cmd->pixel_format, + mode_cmd->modifier[0]), + mode_cmd); if (IS_ERR(fb)) { xe_bo_unpin_map_no_vm(obj); goto err; From 97ee36e58a8cf608cc762b65aec27fd729857181 Mon Sep 17 00:00:00 2001 From: Dibin Moolakadan Subrahmanian Date: Tue, 11 Nov 2025 19:24:03 +0530 Subject: [PATCH 0953/3902] drm/i915/fbdev: Hold runtime PM ref during fbdev BO creation MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [ Upstream commit 460b31720369fc77c23301708641cfa1bf2fcb8f ] During fbdev probe, the xe driver allocates and pins a framebuffer BO (via xe_bo_create_pin_map_novm() → xe_ggtt_insert_bo()). Without a runtime PM reference, xe_pm_runtime_get_noresume() warns about missing outer PM protection as below: xe 0000:03:00.0: [drm] Missing outer runtime PM protection Acquire a runtime PM reference before framebuffer allocation to ensure xe_ggtt_insert_bo() executes under active runtime PM context. Changes in v2: - Update commit message to add Fixes tag (Jani Nikula) Closes: https://gitlab.freedesktop.org/drm/xe/kernel/-/issues/6350 Fixes: 44e694958b95 ("drm/xe/display: Implement display support") Cc: Jani Nikula Signed-off-by: Dibin Moolakadan Subrahmanian Reviewed-by: Jouni Högander Signed-off-by: Ankit Nautiyal Link: https://patch.msgid.link/20251111135403.3415947-1-dibin.moolakadan.subrahmanian@intel.com (cherry picked from commit 37fc7b7b3ab0e3bb900657199cd3770a4fda03fb) Signed-off-by: Jani Nikula Signed-off-by: Sasha Levin --- drivers/gpu/drm/i915/display/intel_fbdev.c | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/drivers/gpu/drm/i915/display/intel_fbdev.c b/drivers/gpu/drm/i915/display/intel_fbdev.c index e46c08762b847d..7daf72b69bae4f 100644 --- a/drivers/gpu/drm/i915/display/intel_fbdev.c +++ b/drivers/gpu/drm/i915/display/intel_fbdev.c @@ -263,13 +263,18 @@ int intel_fbdev_driver_fbdev_probe(struct drm_fb_helper *helper, drm_framebuffer_put(&fb->base); fb = NULL; } + + wakeref = intel_display_rpm_get(display); + if (!fb || drm_WARN_ON(display->drm, !intel_fb_bo(&fb->base))) { drm_dbg_kms(display->drm, "no BIOS fb, allocating a new one\n"); fb = __intel_fbdev_fb_alloc(display, sizes); - if (IS_ERR(fb)) - return PTR_ERR(fb); + if (IS_ERR(fb)) { + ret = PTR_ERR(fb); + goto out_unlock; + } } else { drm_dbg_kms(display->drm, "re-using BIOS fb\n"); prealloc = true; @@ -277,8 +282,6 @@ int intel_fbdev_driver_fbdev_probe(struct drm_fb_helper *helper, sizes->fb_height = fb->base.height; } - wakeref = intel_display_rpm_get(display); - /* Pin the GGTT vma for our access via info->screen_base. * This also validates that any existing fb inherited from the * BIOS is suitable for own access. From ef0cd7b694928573f6569e61c14f5f059253162e Mon Sep 17 00:00:00 2001 From: Mohamed Khalfella Date: Fri, 5 Dec 2025 13:17:02 -0800 Subject: [PATCH 0954/3902] block: Use RCU in blk_mq_[un]quiesce_tagset() instead of set->tag_list_lock [ Upstream commit 59e25ef2b413c72da6686d431e7759302cfccafa ] blk_mq_{add,del}_queue_tag_set() functions add and remove queues from tagset, the functions make sure that tagset and queues are marked as shared when two or more queues are attached to the same tagset. Initially a tagset starts as unshared and when the number of added queues reaches two, blk_mq_add_queue_tag_set() marks it as shared along with all the queues attached to it. When the number of attached queues drops to 1 blk_mq_del_queue_tag_set() need to mark both the tagset and the remaining queues as unshared. Both functions need to freeze current queues in tagset before setting on unsetting BLK_MQ_F_TAG_QUEUE_SHARED flag. While doing so, both functions hold set->tag_list_lock mutex, which makes sense as we do not want queues to be added or deleted in the process. This used to work fine until commit 98d81f0df70c ("nvme: use blk_mq_[un]quiesce_tagset") made the nvme driver quiesce tagset instead of quiscing individual queues. blk_mq_quiesce_tagset() does the job and quiesce the queues in set->tag_list while holding set->tag_list_lock also. This results in deadlock between two threads with these stacktraces: __schedule+0x47c/0xbb0 ? timerqueue_add+0x66/0xb0 schedule+0x1c/0xa0 schedule_preempt_disabled+0xa/0x10 __mutex_lock.constprop.0+0x271/0x600 blk_mq_quiesce_tagset+0x25/0xc0 nvme_dev_disable+0x9c/0x250 nvme_timeout+0x1fc/0x520 blk_mq_handle_expired+0x5c/0x90 bt_iter+0x7e/0x90 blk_mq_queue_tag_busy_iter+0x27e/0x550 ? __blk_mq_complete_request_remote+0x10/0x10 ? __blk_mq_complete_request_remote+0x10/0x10 ? __call_rcu_common.constprop.0+0x1c0/0x210 blk_mq_timeout_work+0x12d/0x170 process_one_work+0x12e/0x2d0 worker_thread+0x288/0x3a0 ? rescuer_thread+0x480/0x480 kthread+0xb8/0xe0 ? kthread_park+0x80/0x80 ret_from_fork+0x2d/0x50 ? kthread_park+0x80/0x80 ret_from_fork_asm+0x11/0x20 __schedule+0x47c/0xbb0 ? xas_find+0x161/0x1a0 schedule+0x1c/0xa0 blk_mq_freeze_queue_wait+0x3d/0x70 ? destroy_sched_domains_rcu+0x30/0x30 blk_mq_update_tag_set_shared+0x44/0x80 blk_mq_exit_queue+0x141/0x150 del_gendisk+0x25a/0x2d0 nvme_ns_remove+0xc9/0x170 nvme_remove_namespaces+0xc7/0x100 nvme_remove+0x62/0x150 pci_device_remove+0x23/0x60 device_release_driver_internal+0x159/0x200 unbind_store+0x99/0xa0 kernfs_fop_write_iter+0x112/0x1e0 vfs_write+0x2b1/0x3d0 ksys_write+0x4e/0xb0 do_syscall_64+0x5b/0x160 entry_SYSCALL_64_after_hwframe+0x4b/0x53 The top stacktrace is showing nvme_timeout() called to handle nvme command timeout. timeout handler is trying to disable the controller and as a first step, it needs to blk_mq_quiesce_tagset() to tell blk-mq not to call queue callback handlers. The thread is stuck waiting for set->tag_list_lock as it tries to walk the queues in set->tag_list. The lock is held by the second thread in the bottom stack which is waiting for one of queues to be frozen. The queue usage counter will drop to zero after nvme_timeout() finishes, and this will not happen because the thread will wait for this mutex forever. Given that [un]quiescing queue is an operation that does not need to sleep, update blk_mq_[un]quiesce_tagset() to use RCU instead of taking set->tag_list_lock, update blk_mq_{add,del}_queue_tag_set() to use RCU safe list operations. Also, delete INIT_LIST_HEAD(&q->tag_set_list) in blk_mq_del_queue_tag_set() because we can not re-initialize it while the list is being traversed under RCU. The deleted queue will not be added/deleted to/from a tagset and it will be freed in blk_free_queue() after the end of RCU grace period. Signed-off-by: Mohamed Khalfella Fixes: 98d81f0df70c ("nvme: use blk_mq_[un]quiesce_tagset") Reviewed-by: Ming Lei Reviewed-by: Bart Van Assche Signed-off-by: Jens Axboe Signed-off-by: Sasha Levin --- block/blk-mq.c | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/block/blk-mq.c b/block/blk-mq.c index 33a0062f9e56d3..f901aeba855229 100644 --- a/block/blk-mq.c +++ b/block/blk-mq.c @@ -336,12 +336,12 @@ void blk_mq_quiesce_tagset(struct blk_mq_tag_set *set) { struct request_queue *q; - mutex_lock(&set->tag_list_lock); - list_for_each_entry(q, &set->tag_list, tag_set_list) { + rcu_read_lock(); + list_for_each_entry_rcu(q, &set->tag_list, tag_set_list) { if (!blk_queue_skip_tagset_quiesce(q)) blk_mq_quiesce_queue_nowait(q); } - mutex_unlock(&set->tag_list_lock); + rcu_read_unlock(); blk_mq_wait_quiesce_done(set); } @@ -351,12 +351,12 @@ void blk_mq_unquiesce_tagset(struct blk_mq_tag_set *set) { struct request_queue *q; - mutex_lock(&set->tag_list_lock); - list_for_each_entry(q, &set->tag_list, tag_set_list) { + rcu_read_lock(); + list_for_each_entry_rcu(q, &set->tag_list, tag_set_list) { if (!blk_queue_skip_tagset_quiesce(q)) blk_mq_unquiesce_queue(q); } - mutex_unlock(&set->tag_list_lock); + rcu_read_unlock(); } EXPORT_SYMBOL_GPL(blk_mq_unquiesce_tagset); @@ -4308,7 +4308,7 @@ static void blk_mq_del_queue_tag_set(struct request_queue *q) struct blk_mq_tag_set *set = q->tag_set; mutex_lock(&set->tag_list_lock); - list_del(&q->tag_set_list); + list_del_rcu(&q->tag_set_list); if (list_is_singular(&set->tag_list)) { /* just transitioned to unshared */ set->flags &= ~BLK_MQ_F_TAG_QUEUE_SHARED; @@ -4316,7 +4316,6 @@ static void blk_mq_del_queue_tag_set(struct request_queue *q) blk_mq_update_tag_set_shared(set, false); } mutex_unlock(&set->tag_list_lock); - INIT_LIST_HEAD(&q->tag_set_list); } static void blk_mq_add_queue_tag_set(struct blk_mq_tag_set *set, @@ -4335,7 +4334,7 @@ static void blk_mq_add_queue_tag_set(struct blk_mq_tag_set *set, } if (set->flags & BLK_MQ_F_TAG_QUEUE_SHARED) queue_set_hctx_shared(q, true); - list_add_tail(&q->tag_set_list, &set->tag_list); + list_add_tail_rcu(&q->tag_set_list, &set->tag_list); mutex_unlock(&set->tag_list_lock); } From f80cf70de9121ea689e88a67c51e68fe2821ae29 Mon Sep 17 00:00:00 2001 From: Hemalatha Pinnamreddy Date: Wed, 3 Dec 2025 17:31:34 +0530 Subject: [PATCH 0955/3902] ASoC: amd: acp: update tdm channels for specific DAI [ Upstream commit f34836a8ddf9216ff919927cddb705022bf30aab ] TDM channel updates were applied to all DAIs, causing configurations to overwrite for unrelated streams. The logic is modified to update channels only for targeted DAI. This prevents corruption of other DAI settings and resolves audio issues observed during system suspend and resume cycles. Fixes: 12229b7e50cf ("ASoC: amd: acp: Add TDM support for acp i2s stream") Signed-off-by: Hemalatha Pinnamreddy Signed-off-by: Raghavendra Prasad Mallela Link: https://patch.msgid.link/20251203120136.2591395-1-raghavendraprasad.mallela@amd.com Signed-off-by: Mark Brown Signed-off-by: Sasha Levin --- sound/soc/amd/acp/acp-i2s.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/sound/soc/amd/acp/acp-i2s.c b/sound/soc/amd/acp/acp-i2s.c index 4ba0a66981ea9d..283a674c7e2c32 100644 --- a/sound/soc/amd/acp/acp-i2s.c +++ b/sound/soc/amd/acp/acp-i2s.c @@ -157,6 +157,8 @@ static int acp_i2s_set_tdm_slot(struct snd_soc_dai *dai, u32 tx_mask, u32 rx_mas spin_lock_irq(&chip->acp_lock); list_for_each_entry(stream, &chip->stream_list, list) { + if (dai->id != stream->dai_id) + continue; switch (chip->acp_rev) { case ACP_RN_PCI_ID: case ACP_RMB_PCI_ID: From 992710902de453ecd7c1a54352fcf3243ef5d855 Mon Sep 17 00:00:00 2001 From: Alexey Simakov Date: Tue, 2 Dec 2025 20:18:38 +0300 Subject: [PATCH 0956/3902] dm-raid: fix possible NULL dereference with undefined raid type [ Upstream commit 2f6cfd6d7cb165a7af8877b838a9f6aab4159324 ] rs->raid_type is assigned from get_raid_type_by_ll(), which may return NULL. This NULL value could be dereferenced later in the condition 'if (!(rs_is_raid10(rs) && rt_is_raid0(rs->raid_type)))'. Add a fail-fast check to return early with an error if raid_type is NULL, similar to other uses of this function. Found by Linux Verification Center (linuxtesting.org) with Svace. Fixes: 33e53f06850f ("dm raid: introduce extended superblock and new raid types to support takeover/reshaping") Signed-off-by: Alexey Simakov Signed-off-by: Mikulas Patocka Signed-off-by: Sasha Levin --- drivers/md/dm-raid.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/drivers/md/dm-raid.c b/drivers/md/dm-raid.c index c6f7129e43d347..4bacdc499984b5 100644 --- a/drivers/md/dm-raid.c +++ b/drivers/md/dm-raid.c @@ -2287,6 +2287,8 @@ static int super_init_validation(struct raid_set *rs, struct md_rdev *rdev) mddev->reshape_position = le64_to_cpu(sb->reshape_position); rs->raid_type = get_raid_type_by_ll(mddev->level, mddev->layout); + if (!rs->raid_type) + return -EINVAL; } } else { From ea98ced74f5034e0056ff551a03de485b2ca16b2 Mon Sep 17 00:00:00 2001 From: Haotian Zhang Date: Mon, 1 Dec 2025 15:41:03 +0800 Subject: [PATCH 0957/3902] dm log-writes: Add missing set_freezable() for freezable kthread [ Upstream commit ab08f9c8b363297cafaf45475b08f78bf19b88ef ] The log_writes_kthread() calls try_to_freeze() but lacks set_freezable(), rendering the freeze attempt ineffective since kernel threads are non-freezable by default. This prevents proper thread suspension during system suspend/hibernate. Add set_freezable() to explicitly mark the thread as freezable. Fixes: 0e9cebe72459 ("dm: add log writes target") Signed-off-by: Haotian Zhang Reviewed-by: Benjamin Marzinski Signed-off-by: Mikulas Patocka Signed-off-by: Sasha Levin --- drivers/md/dm-log-writes.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/md/dm-log-writes.c b/drivers/md/dm-log-writes.c index 7bb7174f8f4f87..f0c84e7a5daa63 100644 --- a/drivers/md/dm-log-writes.c +++ b/drivers/md/dm-log-writes.c @@ -432,6 +432,7 @@ static int log_writes_kthread(void *arg) struct log_writes_c *lc = arg; sector_t sector = 0; + set_freezable(); while (!kthread_should_stop()) { bool super = false; bool logging_enabled; From 9e434426cc23ad5e2aad649327b59aea00294b13 Mon Sep 17 00:00:00 2001 From: Duoming Zhou Date: Tue, 28 Oct 2025 18:01:49 +0800 Subject: [PATCH 0958/3902] scsi: imm: Fix use-after-free bug caused by unfinished delayed work [ Upstream commit ab58153ec64fa3fc9aea09ca09dc9322e0b54a7c ] The delayed work item 'imm_tq' is initialized in imm_attach() and scheduled via imm_queuecommand() for processing SCSI commands. When the IMM parallel port SCSI host adapter is detached through imm_detach(), the imm_struct device instance is deallocated. However, the delayed work might still be pending or executing when imm_detach() is called, leading to use-after-free bugs when the work function imm_interrupt() accesses the already freed imm_struct memory. The race condition can occur as follows: CPU 0(detach thread) | CPU 1 | imm_queuecommand() | imm_queuecommand_lck() imm_detach() | schedule_delayed_work() kfree(dev) //FREE | imm_interrupt() | dev = container_of(...) //USE dev-> //USE Add disable_delayed_work_sync() in imm_detach() to guarantee proper cancellation of the delayed work item before imm_struct is deallocated. Fixes: 1da177e4c3f4 ("Linux-2.6.12-rc2") Signed-off-by: Duoming Zhou Link: https://patch.msgid.link/20251028100149.40721-1-duoming@zju.edu.cn Signed-off-by: Martin K. Petersen Signed-off-by: Sasha Levin --- drivers/scsi/imm.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/scsi/imm.c b/drivers/scsi/imm.c index 5c602c0577989f..45b0e33293a591 100644 --- a/drivers/scsi/imm.c +++ b/drivers/scsi/imm.c @@ -1260,6 +1260,7 @@ static void imm_detach(struct parport *pb) imm_struct *dev; list_for_each_entry(dev, &imm_hosts, list) { if (dev->dev->port == pb) { + disable_delayed_work_sync(&dev->imm_tq); list_del_init(&dev->list); scsi_remove_host(dev->host); scsi_host_put(dev->host); From 35287d31e0834c7be21146ba4560d15da8a450f3 Mon Sep 17 00:00:00 2001 From: Bart Van Assche Date: Thu, 4 Dec 2025 07:04:52 -1000 Subject: [PATCH 0959/3902] scsi: ufs: core: Fix an error handler crash [ Upstream commit 14be351e5cd07349377010e457a58fac99201832 ] The UFS error handler may be activated before SCSI scanning has started and hence before hba->ufs_device_wlun has been set. Check the hba->ufs_device_wlun pointer before using it. Cc: Peter Wang Cc: Nitin Rawat Fixes: e23ef4f22db3 ("scsi: ufs: core: Fix error handler host_sem issue") Fixes: f966e02ae521 ("scsi: ufs: core: Fix runtime suspend error deadlock") Signed-off-by: Bart Van Assche Reviewed-by: Peter Wang Reviewed-by: Nitin Rawat Tested-by: Nitin Rawat #SM8750 Link: https://patch.msgid.link/20251204170457.994851-1-bvanassche@acm.org Signed-off-by: Martin K. Petersen Signed-off-by: Sasha Levin --- drivers/ufs/core/ufshcd.c | 25 ++++++++++++++----------- 1 file changed, 14 insertions(+), 11 deletions(-) diff --git a/drivers/ufs/core/ufshcd.c b/drivers/ufs/core/ufshcd.c index 12f5a7a9731280..a921a9098a291e 100644 --- a/drivers/ufs/core/ufshcd.c +++ b/drivers/ufs/core/ufshcd.c @@ -6670,19 +6670,22 @@ static void ufshcd_err_handler(struct work_struct *work) hba->saved_uic_err, hba->force_reset, ufshcd_is_link_broken(hba) ? "; link is broken" : ""); - /* - * Use ufshcd_rpm_get_noresume() here to safely perform link recovery - * even if an error occurs during runtime suspend or runtime resume. - * This avoids potential deadlocks that could happen if we tried to - * resume the device while a PM operation is already in progress. - */ - ufshcd_rpm_get_noresume(hba); - if (hba->pm_op_in_progress) { - ufshcd_link_recovery(hba); + if (hba->ufs_device_wlun) { + /* + * Use ufshcd_rpm_get_noresume() here to safely perform link + * recovery even if an error occurs during runtime suspend or + * runtime resume. This avoids potential deadlocks that could + * happen if we tried to resume the device while a PM operation + * is already in progress. + */ + ufshcd_rpm_get_noresume(hba); + if (hba->pm_op_in_progress) { + ufshcd_link_recovery(hba); + ufshcd_rpm_put(hba); + return; + } ufshcd_rpm_put(hba); - return; } - ufshcd_rpm_put(hba); down(&hba->host_sem); spin_lock_irqsave(hba->host->host_lock, flags); From da07fa8730596596f0156900337444806c52ef45 Mon Sep 17 00:00:00 2001 From: Thaumy Cheng Date: Tue, 9 Dec 2025 12:16:00 +0800 Subject: [PATCH 0960/3902] perf/core: Fix missing read event generation on task exit [ Upstream commit c418d8b4d7a43a86b82ee39cb52ece3034383530 ] For events with inherit_stat enabled, a "read" event will be generated to collect per task event counts on task exit. The call chain is as follows: do_exit -> perf_event_exit_task -> perf_event_exit_task_context -> perf_event_exit_event -> perf_remove_from_context -> perf_child_detach -> sync_child_event -> perf_event_read_event However, the child event context detaches the task too early in perf_event_exit_task_context, which causes sync_child_event to never generate the read event in this case, since child_event->ctx->task is always set to TASK_TOMBSTONE. Fix that by moving context lock section backward to ensure ctx->task is not set to TASK_TOMBSTONE before generating the read event. Because perf_event_free_task calls perf_event_exit_task_context with exit = false to tear down all child events from the context, and the task never lived, accessing the task PID can lead to a use-after-free. To fix that, let sync_child_event read task from argument and move the call to the only place it should be triggered to avoid the effect of setting ctx->task to TASK_TOMESTONE, and add a task parameter to perf_event_exit_event to trigger the sync_child_event properly when needed. This bug can be reproduced by running "perf record -s" and attaching to any program that generates perf events in its child tasks. If we check the result with "perf report -T", the last line of the report will leave an empty table like "# PID TID", which is expected to contain the per-task event counts by design. Fixes: ef54c1a476ae ("perf: Rework perf_event_exit_event()") Signed-off-by: Thaumy Cheng Signed-off-by: Ingo Molnar Acked-by: Peter Zijlstra Cc: Adrian Hunter Cc: Alexander Shishkin Cc: Arnaldo Carvalho de Melo Cc: Ian Rogers Cc: James Clark Cc: Jiri Olsa Cc: Mark Rutland Cc: Namhyung Kim Cc: linux-perf-users@vger.kernel.org Link: https://patch.msgid.link/20251209041600.963586-1-thaumy.love@gmail.com Signed-off-by: Sasha Levin --- kernel/events/core.c | 22 ++++++++++++---------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/kernel/events/core.c b/kernel/events/core.c index 2c35acc2722b0f..413b88a4e00fb9 100644 --- a/kernel/events/core.c +++ b/kernel/events/core.c @@ -2316,8 +2316,6 @@ static void perf_group_detach(struct perf_event *event) perf_event__header_size(leader); } -static void sync_child_event(struct perf_event *child_event); - static void perf_child_detach(struct perf_event *event) { struct perf_event *parent_event = event->parent; @@ -2336,7 +2334,6 @@ static void perf_child_detach(struct perf_event *event) lockdep_assert_held(&parent_event->child_mutex); */ - sync_child_event(event); list_del_init(&event->child_list); } @@ -4587,6 +4584,7 @@ static void perf_event_enable_on_exec(struct perf_event_context *ctx) static void perf_remove_from_owner(struct perf_event *event); static void perf_event_exit_event(struct perf_event *event, struct perf_event_context *ctx, + struct task_struct *task, bool revoke); /* @@ -4614,7 +4612,7 @@ static void perf_event_remove_on_exec(struct perf_event_context *ctx) modified = true; - perf_event_exit_event(event, ctx, false); + perf_event_exit_event(event, ctx, ctx->task, false); } raw_spin_lock_irqsave(&ctx->lock, flags); @@ -12447,7 +12445,7 @@ static void __pmu_detach_event(struct pmu *pmu, struct perf_event *event, /* * De-schedule the event and mark it REVOKED. */ - perf_event_exit_event(event, ctx, true); + perf_event_exit_event(event, ctx, ctx->task, true); /* * All _free_event() bits that rely on event->pmu: @@ -14004,14 +14002,13 @@ void perf_pmu_migrate_context(struct pmu *pmu, int src_cpu, int dst_cpu) } EXPORT_SYMBOL_GPL(perf_pmu_migrate_context); -static void sync_child_event(struct perf_event *child_event) +static void sync_child_event(struct perf_event *child_event, + struct task_struct *task) { struct perf_event *parent_event = child_event->parent; u64 child_val; if (child_event->attr.inherit_stat) { - struct task_struct *task = child_event->ctx->task; - if (task && task != TASK_TOMBSTONE) perf_event_read_event(child_event, task); } @@ -14030,7 +14027,9 @@ static void sync_child_event(struct perf_event *child_event) static void perf_event_exit_event(struct perf_event *event, - struct perf_event_context *ctx, bool revoke) + struct perf_event_context *ctx, + struct task_struct *task, + bool revoke) { struct perf_event *parent_event = event->parent; unsigned long detach_flags = DETACH_EXIT; @@ -14053,6 +14052,9 @@ perf_event_exit_event(struct perf_event *event, mutex_lock(&parent_event->child_mutex); /* PERF_ATTACH_ITRACE might be set concurrently */ attach_state = READ_ONCE(event->attach_state); + + if (attach_state & PERF_ATTACH_CHILD) + sync_child_event(event, task); } if (revoke) @@ -14144,7 +14146,7 @@ static void perf_event_exit_task_context(struct task_struct *task, bool exit) perf_event_task(task, ctx, 0); list_for_each_entry_safe(child_event, next, &ctx->event_list, event_entry) - perf_event_exit_event(child_event, ctx, false); + perf_event_exit_event(child_event, ctx, exit ? task : NULL, false); mutex_unlock(&ctx->mutex); From efd65e2e2fd96f7aaa5cb07d79bbbfcfc80aa552 Mon Sep 17 00:00:00 2001 From: Dan Carpenter Date: Tue, 9 Dec 2025 09:54:16 +0300 Subject: [PATCH 0961/3902] irqchip/mchp-eic: Fix error code in mchp_eic_domain_alloc() [ Upstream commit 7dbc0d40d8347bd9de55c904f59ea44bcc8dedb7 ] If irq_domain_translate_twocell() sets "hwirq" to >= MCHP_EIC_NIRQ (2) then it results in an out of bounds access. The code checks for invalid values, but doesn't set the error code. Return -EINVAL in that case, instead of returning success. Fixes: 00fa3461c86d ("irqchip/mchp-eic: Add support for the Microchip EIC") Signed-off-by: Dan Carpenter Signed-off-by: Thomas Gleixner Reviewed-by: Claudiu Beznea Link: https://patch.msgid.link/aTfHmOz6IBpTIPU5@stanley.mountain Signed-off-by: Sasha Levin --- drivers/irqchip/irq-mchp-eic.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/irqchip/irq-mchp-eic.c b/drivers/irqchip/irq-mchp-eic.c index b513a899c08537..979bb86929f8eb 100644 --- a/drivers/irqchip/irq-mchp-eic.c +++ b/drivers/irqchip/irq-mchp-eic.c @@ -166,7 +166,7 @@ static int mchp_eic_domain_alloc(struct irq_domain *domain, unsigned int virq, ret = irq_domain_translate_twocell(domain, fwspec, &hwirq, &type); if (ret || hwirq >= MCHP_EIC_NIRQ) - return ret; + return ret ?: -EINVAL; switch (type) { case IRQ_TYPE_EDGE_RISING: From d0fab03390ae1b2f56f9592bc79442d5ecac0dd2 Mon Sep 17 00:00:00 2001 From: Sebastian Andrzej Siewior Date: Thu, 27 Nov 2025 15:47:23 +0100 Subject: [PATCH 0962/3902] cpu: Make atomic hotplug callbacks run with interrupts disabled on UP [ Upstream commit c94291914b200e10c72cef23c8e4c67eb4fdbcd9 ] On SMP systems the CPU hotplug callbacks in the "starting" range are invoked while the CPU is brought up and interrupts are still disabled. Callbacks which are added later are invoked via the hotplug-thread on the target CPU and interrupts are explicitly disabled. In the UP case callbacks which are added later are invoked directly without the thread indirection. This is in principle okay since there is just one CPU but those callbacks are invoked with interrupt disabled code. That's incorrect as those callbacks assume interrupt disabled context. Disable interrupts before invoking the callbacks on UP if the state is atomic and interrupts are expected to be disabled. The "save" part is required because this is also invoked early in the boot process while interrupts are disabled and must not be enabled prematurely. Fixes: 06ddd17521bf1 ("sched/smp: Always define is_percpu_thread() and scheduler_ipi()") Signed-off-by: Sebastian Andrzej Siewior Signed-off-by: Thomas Gleixner Link: https://patch.msgid.link/20251127144723.ev9DuXXR@linutronix.de Signed-off-by: Sasha Levin --- kernel/cpu.c | 25 ++++++++++++++++--------- 1 file changed, 16 insertions(+), 9 deletions(-) diff --git a/kernel/cpu.c b/kernel/cpu.c index db9f6c539b28ce..15000c7abc6599 100644 --- a/kernel/cpu.c +++ b/kernel/cpu.c @@ -249,6 +249,14 @@ static int cpuhp_invoke_callback(unsigned int cpu, enum cpuhp_state state, return ret; } +/* + * The former STARTING/DYING states, ran with IRQs disabled and must not fail. + */ +static bool cpuhp_is_atomic_state(enum cpuhp_state state) +{ + return CPUHP_AP_IDLE_DEAD <= state && state < CPUHP_AP_ONLINE; +} + #ifdef CONFIG_SMP static bool cpuhp_is_ap_state(enum cpuhp_state state) { @@ -271,14 +279,6 @@ static inline void complete_ap_thread(struct cpuhp_cpu_state *st, bool bringup) complete(done); } -/* - * The former STARTING/DYING states, ran with IRQs disabled and must not fail. - */ -static bool cpuhp_is_atomic_state(enum cpuhp_state state) -{ - return CPUHP_AP_IDLE_DEAD <= state && state < CPUHP_AP_ONLINE; -} - /* Synchronization state management */ enum cpuhp_sync_state { SYNC_STATE_DEAD, @@ -2364,7 +2364,14 @@ static int cpuhp_issue_call(int cpu, enum cpuhp_state state, bool bringup, else ret = cpuhp_invoke_callback(cpu, state, bringup, node, NULL); #else - ret = cpuhp_invoke_callback(cpu, state, bringup, node, NULL); + if (cpuhp_is_atomic_state(state)) { + guard(irqsave)(); + ret = cpuhp_invoke_callback(cpu, state, bringup, node, NULL); + /* STARTING/DYING must not fail! */ + WARN_ON_ONCE(ret); + } else { + ret = cpuhp_invoke_callback(cpu, state, bringup, node, NULL); + } #endif BUG_ON(ret && !bringup); return ret; From f2091f479f5bd27e9b425d251e869f2a7580b1fa Mon Sep 17 00:00:00 2001 From: Dmitry Antipov Date: Fri, 5 Dec 2025 09:51:59 +0300 Subject: [PATCH 0963/3902] ocfs2: fix memory leak in ocfs2_merge_rec_left() [ Upstream commit 2214ec4bf89d0fd27717322d3983a2f3b469c7f3 ] In 'ocfs2_merge_rec_left()', do not reset 'left_path' to NULL after move, thus allowing 'ocfs2_free_path()' to free it before return. Link: https://lkml.kernel.org/r/20251205065159.392749-1-dmantipov@yandex.ru Fixes: 677b975282e4 ("ocfs2: Add support for cross extent block") Signed-off-by: Dmitry Antipov Reported-by: syzbot+cfc7cab3bb6eaa7c4de2@syzkaller.appspotmail.com Closes: https://syzkaller.appspot.com/bug?extid=cfc7cab3bb6eaa7c4de2 Reviewed-by: Heming Zhao Acked-by: Joseph Qi Cc: Mark Fasheh Cc: Joel Becker Cc: Junxiao Bi Cc: Changwei Ge Cc: Jun Piao Signed-off-by: Andrew Morton Signed-off-by: Sasha Levin --- fs/ocfs2/alloc.c | 1 - 1 file changed, 1 deletion(-) diff --git a/fs/ocfs2/alloc.c b/fs/ocfs2/alloc.c index 162711cc5b201f..a0ced11e0c24ab 100644 --- a/fs/ocfs2/alloc.c +++ b/fs/ocfs2/alloc.c @@ -3654,7 +3654,6 @@ static int ocfs2_merge_rec_left(struct ocfs2_path *right_path, * So we use the new rightmost path. */ ocfs2_mv_path(right_path, left_path); - left_path = NULL; } else ocfs2_complete_edge_insert(handle, left_path, right_path, subtree_index); From 67d5a5e20259414b5ad6ba8bdf1aff8e80ca9c49 Mon Sep 17 00:00:00 2001 From: Evan Li Date: Fri, 12 Dec 2025 16:49:43 +0800 Subject: [PATCH 0964/3902] perf/x86/intel: Fix NULL event dereference crash in handle_pmi_common() [ Upstream commit 9415f749d34b926b9e4853da1462f4d941f89a0d ] handle_pmi_common() may observe an active bit set in cpuc->active_mask while the corresponding cpuc->events[] entry has already been cleared, which leads to a NULL pointer dereference. This can happen when interrupt throttling stops all events in a group while PEBS processing is still in progress. perf_event_overflow() can trigger perf_event_throttle_group(), which stops the group and clears the cpuc->events[] entry, but the active bit may still be set when handle_pmi_common() iterates over the events. The following recent fix: 7e772a93eb61 ("perf/x86: Fix NULL event access and potential PEBS record loss") moved the cpuc->events[] clearing from x86_pmu_stop() to x86_pmu_del() and relied on cpuc->active_mask/pebs_enabled checks. However, handle_pmi_common() can still encounter a NULL cpuc->events[] entry despite the active bit being set. Add an explicit NULL check on the event pointer before using it, to cover this legitimate scenario and avoid the NULL dereference crash. Fixes: 7e772a93eb61 ("perf/x86: Fix NULL event access and potential PEBS record loss") Reported-by: kitta Co-developed-by: kitta Signed-off-by: Evan Li Signed-off-by: Ingo Molnar Link: https://patch.msgid.link/20251212084943.2124787-1-evan.li@linux.alibaba.com Closes: https://bugzilla.kernel.org/show_bug.cgi?id=220855 Signed-off-by: Sasha Levin --- arch/x86/events/intel/core.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/arch/x86/events/intel/core.c b/arch/x86/events/intel/core.c index 9b824ed6fc1de9..32d551f2646a72 100644 --- a/arch/x86/events/intel/core.c +++ b/arch/x86/events/intel/core.c @@ -3249,6 +3249,9 @@ static int handle_pmi_common(struct pt_regs *regs, u64 status) if (!test_bit(bit, cpuc->active_mask)) continue; + /* Event may have already been cleared: */ + if (!event) + continue; /* * There may be unprocessed PEBS records in the PEBS buffer, From a980fec3d5eba63a9b40f2ae3a7807ffc447cd6a Mon Sep 17 00:00:00 2001 From: Mauro Carvalho Chehab Date: Thu, 14 Aug 2025 09:52:54 -0700 Subject: [PATCH 0965/3902] efi/cper: Add a new helper function to print bitmasks [ Upstream commit a976d790f49499ccaa0f991788ad8ebf92e7fd5c ] Add a helper function to print a string with names associated to each bit field. A typical example is: const char * const bits[] = { "bit 3 name", "bit 4 name", "bit 5 name", }; char str[120]; unsigned int bitmask = BIT(3) | BIT(5); #define MASK GENMASK(5,3) cper_bits_to_str(str, sizeof(str), FIELD_GET(MASK, bitmask), bits, ARRAY_SIZE(bits)); The above code fills string "str" with "bit 3 name|bit 5 name". Reviewed-by: Jonathan Cameron Signed-off-by: Mauro Carvalho Chehab Acked-by: Borislav Petkov (AMD) Signed-off-by: Ard Biesheuvel Signed-off-by: Sasha Levin --- drivers/firmware/efi/cper.c | 60 +++++++++++++++++++++++++++++++++++++ include/linux/cper.h | 2 ++ 2 files changed, 62 insertions(+) diff --git a/drivers/firmware/efi/cper.c b/drivers/firmware/efi/cper.c index 928409199a1a40..79ba688a64f8da 100644 --- a/drivers/firmware/efi/cper.c +++ b/drivers/firmware/efi/cper.c @@ -12,6 +12,7 @@ * Specification version 2.4. */ +#include #include #include #include @@ -106,6 +107,65 @@ void cper_print_bits(const char *pfx, unsigned int bits, printk("%s\n", buf); } +/** + * cper_bits_to_str - return a string for set bits + * @buf: buffer to store the output string + * @buf_size: size of the output string buffer + * @bits: bit mask + * @strs: string array, indexed by bit position + * @strs_size: size of the string array: @strs + * + * Add to @buf the bitmask in hexadecimal. Then, for each set bit in @bits, + * add the corresponding string describing the bit in @strs to @buf. + * + * A typical example is:: + * + * const char * const bits[] = { + * "bit 3 name", + * "bit 4 name", + * "bit 5 name", + * }; + * char str[120]; + * unsigned int bitmask = BIT(3) | BIT(5); + * #define MASK GENMASK(5,3) + * + * cper_bits_to_str(str, sizeof(str), FIELD_GET(MASK, bitmask), + * bits, ARRAY_SIZE(bits)); + * + * The above code fills the string ``str`` with ``bit 3 name|bit 5 name``. + * + * Return: number of bytes stored or an error code if lower than zero. + */ +int cper_bits_to_str(char *buf, int buf_size, unsigned long bits, + const char * const strs[], unsigned int strs_size) +{ + int len = buf_size; + char *str = buf; + int i, size; + + *buf = '\0'; + + for_each_set_bit(i, &bits, strs_size) { + if (!(bits & BIT_ULL(i))) + continue; + + if (*buf && len > 0) { + *str = '|'; + len--; + str++; + } + + size = strscpy(str, strs[i], len); + if (size < 0) + return size; + + len -= size; + str += size; + } + return len - buf_size; +} +EXPORT_SYMBOL_GPL(cper_bits_to_str); + static const char * const proc_type_strs[] = { "IA32/X64", "IA64", diff --git a/include/linux/cper.h b/include/linux/cper.h index 0ed60a91eca9d6..58f40477c824e6 100644 --- a/include/linux/cper.h +++ b/include/linux/cper.h @@ -588,6 +588,8 @@ const char *cper_mem_err_type_str(unsigned int); const char *cper_mem_err_status_str(u64 status); void cper_print_bits(const char *prefix, unsigned int bits, const char * const strs[], unsigned int strs_size); +int cper_bits_to_str(char *buf, int buf_size, unsigned long bits, + const char * const strs[], unsigned int strs_size); void cper_mem_err_pack(const struct cper_sec_mem_err *, struct cper_mem_err_compact *); const char *cper_mem_err_unpack(struct trace_seq *, From 742082e59fcc7361e98686f76055115c29702521 Mon Sep 17 00:00:00 2001 From: Mauro Carvalho Chehab Date: Thu, 14 Aug 2025 09:52:53 -0700 Subject: [PATCH 0966/3902] efi/cper: Adjust infopfx size to accept an extra space MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [ Upstream commit 8ad2c72e21efb3dc76c5b14089fa7984cdd87898 ] Compiling with W=1 with werror enabled produces an error: drivers/firmware/efi/cper-arm.c: In function ‘cper_print_proc_arm’: drivers/firmware/efi/cper-arm.c:298:64: error: ‘snprintf’ output may be truncated before the last format character [-Werror=format-truncation=] 298 | snprintf(infopfx, sizeof(infopfx), "%s ", newpfx); | ^ drivers/firmware/efi/cper-arm.c:298:25: note: ‘snprintf’ output between 2 and 65 bytes into a destination of size 64 298 | snprintf(infopfx, sizeof(infopfx), "%s ", newpfx); | ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ As the logic there adds an space at the end of infopx buffer. Add an extra space to avoid such warning. Signed-off-by: Mauro Carvalho Chehab Reviewed-by: Jonathan Cameron Acked-by: Borislav Petkov (AMD) Signed-off-by: Ard Biesheuvel Signed-off-by: Sasha Levin --- drivers/firmware/efi/cper-arm.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/firmware/efi/cper-arm.c b/drivers/firmware/efi/cper-arm.c index f0a63d09d3c499..6ff781e47147c0 100644 --- a/drivers/firmware/efi/cper-arm.c +++ b/drivers/firmware/efi/cper-arm.c @@ -240,7 +240,7 @@ void cper_print_proc_arm(const char *pfx, int i, len, max_ctx_type; struct cper_arm_err_info *err_info; struct cper_arm_ctx_info *ctx_info; - char newpfx[64], infopfx[64]; + char newpfx[64], infopfx[ARRAY_SIZE(newpfx) + 1]; printk("%sMIDR: 0x%016llx\n", pfx, proc->midr); From 1f2f0ff3cacb12fad1c9829b2f31cac9095c696b Mon Sep 17 00:00:00 2001 From: Mauro Carvalho Chehab Date: Thu, 14 Aug 2025 09:52:55 -0700 Subject: [PATCH 0967/3902] efi/cper: align ARM CPER type with UEFI 2.9A/2.10 specs [ Upstream commit 96b010536ee020e716d28d9b359a4bcd18800aeb ] Up to UEFI spec 2.9, the type byte of CPER struct for ARM processor was defined simply as: Type at byte offset 4: - Cache error - TLB Error - Bus Error - Micro-architectural Error All other values are reserved Yet, there was no information about how this would be encoded. Spec 2.9A errata corrected it by defining: - Bit 1 - Cache Error - Bit 2 - TLB Error - Bit 3 - Bus Error - Bit 4 - Micro-architectural Error All other values are reserved That actually aligns with the values already defined on older versions at N.2.4.1. Generic Processor Error Section. Spec 2.10 also preserve the same encoding as 2.9A. Adjust CPER and GHES handling code for both generic and ARM processors to properly handle UEFI 2.9A and 2.10 encoding. Link: https://uefi.org/specs/UEFI/2.10/Apx_N_Common_Platform_Error_Record.html#arm-processor-error-information Signed-off-by: Mauro Carvalho Chehab Reviewed-by: Jonathan Cameron Acked-by: Borislav Petkov (AMD) Signed-off-by: Ard Biesheuvel Signed-off-by: Sasha Levin --- drivers/acpi/apei/ghes.c | 16 +++++++---- drivers/firmware/efi/cper-arm.c | 50 ++++++++++++++++----------------- include/linux/cper.h | 10 +++---- 3 files changed, 39 insertions(+), 37 deletions(-) diff --git a/drivers/acpi/apei/ghes.c b/drivers/acpi/apei/ghes.c index 7d2466b515046b..56107aa0027444 100644 --- a/drivers/acpi/apei/ghes.c +++ b/drivers/acpi/apei/ghes.c @@ -22,6 +22,7 @@ #include #include #include +#include #include #include #include @@ -556,6 +557,7 @@ static bool ghes_handle_arm_hw_error(struct acpi_hest_generic_data *gdata, { struct cper_sec_proc_arm *err = acpi_hest_get_payload(gdata); int flags = sync ? MF_ACTION_REQUIRED : 0; + char error_type[120]; bool queued = false; int sec_sev, i; char *p; @@ -568,9 +570,8 @@ static bool ghes_handle_arm_hw_error(struct acpi_hest_generic_data *gdata, p = (char *)(err + 1); for (i = 0; i < err->err_info_num; i++) { struct cper_arm_err_info *err_info = (struct cper_arm_err_info *)p; - bool is_cache = (err_info->type == CPER_ARM_CACHE_ERROR); + bool is_cache = err_info->type & CPER_ARM_CACHE_ERROR; bool has_pa = (err_info->validation_bits & CPER_ARM_INFO_VALID_PHYSICAL_ADDR); - const char *error_type = "unknown error"; /* * The field (err_info->error_info & BIT(26)) is fixed to set to @@ -584,12 +585,15 @@ static bool ghes_handle_arm_hw_error(struct acpi_hest_generic_data *gdata, continue; } - if (err_info->type < ARRAY_SIZE(cper_proc_error_type_strs)) - error_type = cper_proc_error_type_strs[err_info->type]; + cper_bits_to_str(error_type, sizeof(error_type), + FIELD_GET(CPER_ARM_ERR_TYPE_MASK, err_info->type), + cper_proc_error_type_strs, + ARRAY_SIZE(cper_proc_error_type_strs)); pr_warn_ratelimited(FW_WARN GHES_PFX - "Unhandled processor error type: %s\n", - error_type); + "Unhandled processor error type 0x%02x: %s%s\n", + err_info->type, error_type, + (err_info->type & ~CPER_ARM_ERR_TYPE_MASK) ? " with reserved bit(s)" : ""); p += err_info->length; } diff --git a/drivers/firmware/efi/cper-arm.c b/drivers/firmware/efi/cper-arm.c index 6ff781e47147c0..76542a53e20275 100644 --- a/drivers/firmware/efi/cper-arm.c +++ b/drivers/firmware/efi/cper-arm.c @@ -93,15 +93,11 @@ static void cper_print_arm_err_info(const char *pfx, u32 type, bool proc_context_corrupt, corrected, precise_pc, restartable_pc; bool time_out, access_mode; - /* If the type is unknown, bail. */ - if (type > CPER_ARM_MAX_TYPE) - return; - /* * Vendor type errors have error information values that are vendor * specific. */ - if (type == CPER_ARM_VENDOR_ERROR) + if (type & CPER_ARM_VENDOR_ERROR) return; if (error_info & CPER_ARM_ERR_VALID_TRANSACTION_TYPE) { @@ -116,43 +112,38 @@ static void cper_print_arm_err_info(const char *pfx, u32 type, if (error_info & CPER_ARM_ERR_VALID_OPERATION_TYPE) { op_type = ((error_info >> CPER_ARM_ERR_OPERATION_SHIFT) & CPER_ARM_ERR_OPERATION_MASK); - switch (type) { - case CPER_ARM_CACHE_ERROR: + if (type & CPER_ARM_CACHE_ERROR) { if (op_type < ARRAY_SIZE(arm_cache_err_op_strs)) { - printk("%soperation type: %s\n", pfx, + printk("%scache error, operation type: %s\n", pfx, arm_cache_err_op_strs[op_type]); } - break; - case CPER_ARM_TLB_ERROR: + } + if (type & CPER_ARM_TLB_ERROR) { if (op_type < ARRAY_SIZE(arm_tlb_err_op_strs)) { - printk("%soperation type: %s\n", pfx, + printk("%sTLB error, operation type: %s\n", pfx, arm_tlb_err_op_strs[op_type]); } - break; - case CPER_ARM_BUS_ERROR: + } + if (type & CPER_ARM_BUS_ERROR) { if (op_type < ARRAY_SIZE(arm_bus_err_op_strs)) { - printk("%soperation type: %s\n", pfx, + printk("%sbus error, operation type: %s\n", pfx, arm_bus_err_op_strs[op_type]); } - break; } } if (error_info & CPER_ARM_ERR_VALID_LEVEL) { level = ((error_info >> CPER_ARM_ERR_LEVEL_SHIFT) & CPER_ARM_ERR_LEVEL_MASK); - switch (type) { - case CPER_ARM_CACHE_ERROR: + if (type & CPER_ARM_CACHE_ERROR) printk("%scache level: %d\n", pfx, level); - break; - case CPER_ARM_TLB_ERROR: + + if (type & CPER_ARM_TLB_ERROR) printk("%sTLB level: %d\n", pfx, level); - break; - case CPER_ARM_BUS_ERROR: + + if (type & CPER_ARM_BUS_ERROR) printk("%saffinity level at which the bus error occurred: %d\n", pfx, level); - break; - } } if (error_info & CPER_ARM_ERR_VALID_PROC_CONTEXT_CORRUPT) { @@ -241,6 +232,7 @@ void cper_print_proc_arm(const char *pfx, struct cper_arm_err_info *err_info; struct cper_arm_ctx_info *ctx_info; char newpfx[64], infopfx[ARRAY_SIZE(newpfx) + 1]; + char error_type[120]; printk("%sMIDR: 0x%016llx\n", pfx, proc->midr); @@ -289,9 +281,15 @@ void cper_print_proc_arm(const char *pfx, newpfx); } - printk("%serror_type: %d, %s\n", newpfx, err_info->type, - err_info->type < ARRAY_SIZE(cper_proc_error_type_strs) ? - cper_proc_error_type_strs[err_info->type] : "unknown"); + cper_bits_to_str(error_type, sizeof(error_type), + FIELD_GET(CPER_ARM_ERR_TYPE_MASK, err_info->type), + cper_proc_error_type_strs, + ARRAY_SIZE(cper_proc_error_type_strs)); + + printk("%serror_type: 0x%02x: %s%s\n", newpfx, err_info->type, + error_type, + (err_info->type & ~CPER_ARM_ERR_TYPE_MASK) ? " with reserved bit(s)" : ""); + if (err_info->validation_bits & CPER_ARM_INFO_VALID_ERR_INFO) { printk("%serror_info: 0x%016llx\n", newpfx, err_info->error_info); diff --git a/include/linux/cper.h b/include/linux/cper.h index 58f40477c824e6..5b1236d8c65bb7 100644 --- a/include/linux/cper.h +++ b/include/linux/cper.h @@ -297,11 +297,11 @@ enum { #define CPER_ARM_INFO_FLAGS_PROPAGATED BIT(2) #define CPER_ARM_INFO_FLAGS_OVERFLOW BIT(3) -#define CPER_ARM_CACHE_ERROR 0 -#define CPER_ARM_TLB_ERROR 1 -#define CPER_ARM_BUS_ERROR 2 -#define CPER_ARM_VENDOR_ERROR 3 -#define CPER_ARM_MAX_TYPE CPER_ARM_VENDOR_ERROR +#define CPER_ARM_ERR_TYPE_MASK GENMASK(4,1) +#define CPER_ARM_CACHE_ERROR BIT(1) +#define CPER_ARM_TLB_ERROR BIT(2) +#define CPER_ARM_BUS_ERROR BIT(3) +#define CPER_ARM_VENDOR_ERROR BIT(4) #define CPER_ARM_ERR_VALID_TRANSACTION_TYPE BIT(0) #define CPER_ARM_ERR_VALID_OPERATION_TYPE BIT(1) From 93193a1a007750c864dfbbac732e31c7cc086872 Mon Sep 17 00:00:00 2001 From: Haotien Hsu Date: Thu, 27 Nov 2025 11:35:40 +0800 Subject: [PATCH 0968/3902] usb: gadget: tegra-xudc: Always reinitialize data toggle when clear halt commit 2585973c7f9ee31d21e5848c996fab2521fd383d upstream. The driver previously skipped handling ClearFeature(ENDPOINT_HALT) when the endpoint was already not halted. This prevented the controller from resetting the data sequence number and reinitializing the endpoint state. According to USB 3.2 specification Rev. 1.1, section 9.4.5, ClearFeature(ENDPOINT_HALT) must always reset the data sequence and set the stream state machine to Disabled, regardless of whether the endpoint was halted. Remove the early return so that ClearFeature(ENDPOINT_HALT) always resets the endpoint sequence state as required by the specification. Fixes: 49db427232fe ("usb: gadget: Add UDC driver for tegra XUSB device mode controller") Cc: stable Signed-off-by: Haotien Hsu Signed-off-by: Wayne Chang Link: https://patch.msgid.link/20251127033540.2287517-1-waynec@nvidia.com Signed-off-by: Greg Kroah-Hartman Signed-off-by: Greg Kroah-Hartman --- drivers/usb/gadget/udc/tegra-xudc.c | 6 ------ 1 file changed, 6 deletions(-) diff --git a/drivers/usb/gadget/udc/tegra-xudc.c b/drivers/usb/gadget/udc/tegra-xudc.c index 0c38fc37b6e66c..9d2007f448c049 100644 --- a/drivers/usb/gadget/udc/tegra-xudc.c +++ b/drivers/usb/gadget/udc/tegra-xudc.c @@ -1558,12 +1558,6 @@ static int __tegra_xudc_ep_set_halt(struct tegra_xudc_ep *ep, bool halt) return -ENOTSUPP; } - if (!!(xudc_readl(xudc, EP_HALT) & BIT(ep->index)) == halt) { - dev_dbg(xudc->dev, "EP %u already %s\n", ep->index, - halt ? "halted" : "not halted"); - return 0; - } - if (halt) { ep_halt(xudc, ep->index); } else { From a902fc01813a412f60e4b27caa10803f7deb7369 Mon Sep 17 00:00:00 2001 From: Duoming Zhou Date: Tue, 25 Nov 2025 18:36:26 +0800 Subject: [PATCH 0969/3902] usb: typec: ucsi: fix probe failure in gaokun_ucsi_probe() commit 6b120ef99fbcba9e413783561f8cc160719db589 upstream. The gaokun_ucsi_probe() uses ucsi_create() to allocate a UCSI instance. The ucsi_create() validates whether ops->poll_cci is defined, and if not, it directly returns -EINVAL. However, the gaokun_ucsi_ops structure does not define the poll_cci, causing ucsi_create() always fail with -EINVAL. This issue can be observed in the kernel log with the following error: ucsi_huawei_gaokun.ucsi huawei_gaokun_ec.ucsi.0: probe with driver ucsi_huawei_gaokun.ucsi failed with error -22 Fix the issue by adding the missing poll_cci callback to gaokun_ucsi_ops. Fixes: 00327d7f2c8c ("usb: typec: ucsi: add Huawei Matebook E Go ucsi driver") Cc: stable Signed-off-by: Duoming Zhou Reviewed-by: Heikki Krogerus Reviewed-by: Pengyu Luo Link: https://patch.msgid.link/4d077d6439d728be68646bb8c8678436a3a0885e.1764065838.git.duoming@zju.edu.cn Signed-off-by: Greg Kroah-Hartman --- drivers/usb/typec/ucsi/ucsi_huawei_gaokun.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/usb/typec/ucsi/ucsi_huawei_gaokun.c b/drivers/usb/typec/ucsi/ucsi_huawei_gaokun.c index 7b5222081bbb54..8401ab414bd927 100644 --- a/drivers/usb/typec/ucsi/ucsi_huawei_gaokun.c +++ b/drivers/usb/typec/ucsi/ucsi_huawei_gaokun.c @@ -196,6 +196,7 @@ static void gaokun_ucsi_connector_status(struct ucsi_connector *con) const struct ucsi_operations gaokun_ucsi_ops = { .read_version = gaokun_ucsi_read_version, .read_cci = gaokun_ucsi_read_cci, + .poll_cci = gaokun_ucsi_read_cci, .read_message_in = gaokun_ucsi_read_message_in, .sync_control = ucsi_sync_control_common, .async_control = gaokun_ucsi_async_control, From 6507f1810d3c8f758ee173b56a7d626256671819 Mon Sep 17 00:00:00 2001 From: Diogo Ivo Date: Fri, 21 Nov 2025 18:16:36 +0000 Subject: [PATCH 0970/3902] usb: phy: Initialize struct usb_phy list_head commit c69ff68b097b0f53333114f1b2c3dc128f389596 upstream. As part of the registration of a new 'struct usb_phy' with the USB PHY core via either usb_add_phy(struct usb_phy *x, ...) or usb_add_phy_dev(struct usb_phy *x) these functions call list_add_tail(&x->head, phy_list) in order for the new instance x to be stored in phy_list, a static list kept internally by the core. After 7d21114dc6a2 ("usb: phy: Introduce one extcon device into usb phy") when executing either of the registration functions above it is possible that usb_add_extcon() fails, leading to either function returning before the call to list_add_tail(), leaving x->head uninitialized. Then, when a driver tries to undo the failed registration by calling usb_remove_phy(struct usb_phy *x) there will be an unconditional call to list_del(&x->head) acting on an uninitialized variable, and thus a possible NULL pointer dereference. Fix this by initializing x->head before usb_add_extcon() has a chance to fail. Note that this was not needed before 7d21114dc6a2 since list_add_phy() was executed unconditionally and it guaranteed that x->head was initialized. Fixes: 7d21114dc6a2 ("usb: phy: Introduce one extcon device into usb phy") Cc: stable Signed-off-by: Diogo Ivo Link: https://patch.msgid.link/20251121-diogo-smaug_typec-v2-1-5c37c1169d57@tecnico.ulisboa.pt Signed-off-by: Greg Kroah-Hartman --- drivers/usb/phy/phy.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/drivers/usb/phy/phy.c b/drivers/usb/phy/phy.c index e1435bc596622c..5a9b9353f343d5 100644 --- a/drivers/usb/phy/phy.c +++ b/drivers/usb/phy/phy.c @@ -646,6 +646,8 @@ int usb_add_phy(struct usb_phy *x, enum usb_phy_type type) return -EINVAL; } + INIT_LIST_HEAD(&x->head); + usb_charger_init(x); ret = usb_add_extcon(x); if (ret) @@ -696,6 +698,8 @@ int usb_add_phy_dev(struct usb_phy *x) return -EINVAL; } + INIT_LIST_HEAD(&x->head); + usb_charger_init(x); ret = usb_add_extcon(x); if (ret) From a880ef71a1c8da266b88491213c37893e2126489 Mon Sep 17 00:00:00 2001 From: Duoming Zhou Date: Tue, 25 Nov 2025 18:36:27 +0800 Subject: [PATCH 0971/3902] usb: typec: ucsi: fix use-after-free caused by uec->work commit 2b7a0f47aaf2439d517ba0a6b29c66a535302154 upstream. The delayed work uec->work is scheduled in gaokun_ucsi_probe() but never properly canceled in gaokun_ucsi_remove(). This creates use-after-free scenarios where the ucsi and gaokun_ucsi structure are freed after ucsi_destroy() completes execution, while the gaokun_ucsi_register_worker() might be either currently executing or still pending in the work queue. The already-freed gaokun_ucsi or ucsi structure may then be accessed. Furthermore, the race window is 3 seconds, which is sufficiently long to make this bug easily reproducible. The following is the trace captured by KASAN: ================================================================== BUG: KASAN: slab-use-after-free in __run_timers+0x5ec/0x630 Write of size 8 at addr ffff00000ec28cc8 by task swapper/0/0 ... Call trace: show_stack+0x18/0x24 (C) dump_stack_lvl+0x78/0x90 print_report+0x114/0x580 kasan_report+0xa4/0xf0 __asan_report_store8_noabort+0x20/0x2c __run_timers+0x5ec/0x630 run_timer_softirq+0xe8/0x1cc handle_softirqs+0x294/0x720 __do_softirq+0x14/0x20 ____do_softirq+0x10/0x1c call_on_irq_stack+0x30/0x48 do_softirq_own_stack+0x1c/0x28 __irq_exit_rcu+0x27c/0x364 irq_exit_rcu+0x10/0x1c el1_interrupt+0x40/0x60 el1h_64_irq_handler+0x18/0x24 el1h_64_irq+0x6c/0x70 arch_local_irq_enable+0x4/0x8 (P) do_idle+0x334/0x458 cpu_startup_entry+0x60/0x70 rest_init+0x158/0x174 start_kernel+0x2f8/0x394 __primary_switched+0x8c/0x94 Allocated by task 72 on cpu 0 at 27.510341s: kasan_save_stack+0x2c/0x54 kasan_save_track+0x24/0x5c kasan_save_alloc_info+0x40/0x54 __kasan_kmalloc+0xa0/0xb8 __kmalloc_node_track_caller_noprof+0x1c0/0x588 devm_kmalloc+0x7c/0x1c8 gaokun_ucsi_probe+0xa0/0x840 auxiliary_bus_probe+0x94/0xf8 really_probe+0x17c/0x5b8 __driver_probe_device+0x158/0x2c4 driver_probe_device+0x10c/0x264 __device_attach_driver+0x168/0x2d0 bus_for_each_drv+0x100/0x188 __device_attach+0x174/0x368 device_initial_probe+0x14/0x20 bus_probe_device+0x120/0x150 device_add+0xb3c/0x10fc __auxiliary_device_add+0x88/0x130 ... Freed by task 73 on cpu 1 at 28.910627s: kasan_save_stack+0x2c/0x54 kasan_save_track+0x24/0x5c __kasan_save_free_info+0x4c/0x74 __kasan_slab_free+0x60/0x8c kfree+0xd4/0x410 devres_release_all+0x140/0x1f0 device_unbind_cleanup+0x20/0x190 device_release_driver_internal+0x344/0x460 device_release_driver+0x18/0x24 bus_remove_device+0x198/0x274 device_del+0x310/0xa84 ... The buggy address belongs to the object at ffff00000ec28c00 which belongs to the cache kmalloc-512 of size 512 The buggy address is located 200 bytes inside of freed 512-byte region The buggy address belongs to the physical page: page: refcount:0 mapcount:0 mapping:0000000000000000 index:0x0 pfn:0x4ec28 head: order:2 mapcount:0 entire_mapcount:0 nr_pages_mapped:0 pincount:0 flags: 0x3fffe0000000040(head|node=0|zone=0|lastcpupid=0x1ffff) page_type: f5(slab) raw: 03fffe0000000040 ffff000008801c80 dead000000000122 0000000000000000 raw: 0000000000000000 0000000080100010 00000000f5000000 0000000000000000 head: 03fffe0000000040 ffff000008801c80 dead000000000122 0000000000000000 head: 0000000000000000 0000000080100010 00000000f5000000 0000000000000000 head: 03fffe0000000002 fffffdffc03b0a01 00000000ffffffff 00000000ffffffff head: ffffffffffffffff 0000000000000000 00000000ffffffff 0000000000000004 page dumped because: kasan: bad access detected Memory state around the buggy address: ffff00000ec28b80: fc fc fc fc fc fc fc fc fc fc fc fc fc fc fc fc ffff00000ec28c00: fa fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb >ffff00000ec28c80: fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb ^ ffff00000ec28d00: fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb ffff00000ec28d80: fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb ================================================================== Add disable_delayed_work_sync() in gaokun_ucsi_remove() to ensure that uec->work is properly canceled and prevented from executing after the ucsi and gaokun_ucsi structure have been deallocated. Fixes: 00327d7f2c8c ("usb: typec: ucsi: add Huawei Matebook E Go ucsi driver") Cc: stable Signed-off-by: Duoming Zhou Reviewed-by: Heikki Krogerus Link: https://patch.msgid.link/cc31e12ef9ffbf86676585b02233165fd33f0d8e.1764065838.git.duoming@zju.edu.cn Signed-off-by: Greg Kroah-Hartman --- drivers/usb/typec/ucsi/ucsi_huawei_gaokun.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/usb/typec/ucsi/ucsi_huawei_gaokun.c b/drivers/usb/typec/ucsi/ucsi_huawei_gaokun.c index 8401ab414bd927..c5965656babadd 100644 --- a/drivers/usb/typec/ucsi/ucsi_huawei_gaokun.c +++ b/drivers/usb/typec/ucsi/ucsi_huawei_gaokun.c @@ -503,6 +503,7 @@ static void gaokun_ucsi_remove(struct auxiliary_device *adev) { struct gaokun_ucsi *uec = auxiliary_get_drvdata(adev); + disable_delayed_work_sync(&uec->work); gaokun_ec_unregister_notify(uec->ec, &uec->nb); ucsi_unregister(uec->ucsi); ucsi_destroy(uec->ucsi); From d6d0caff738fafc20eb242433685b398f9158314 Mon Sep 17 00:00:00 2001 From: Sven Peter Date: Wed, 15 Oct 2025 15:40:42 +0000 Subject: [PATCH 0972/3902] usb: dwc3: dwc3_power_off_all_roothub_ports: Use ioremap_np when required commit 5ed9cc71432a8adf3c42223c935f714aac29901b upstream. On Apple Silicon machines we can't use ioremap() / Device-nGnRE to map most regions but must use ioremap_np() / Device-nGnRnE whenever IORESOURCE_MEM_NONPOSTED is set. Make sure this is also done inside dwc3_power_off_all_roothub_ports to prevent SErrors. Fixes: 2d2a3349521d ("usb: dwc3: Add workaround for host mode VBUS glitch when boot") Cc: stable@kernel.org Acked-by: Thinh Nguyen Reviewed-by: Neal Gompa Signed-off-by: Sven Peter Link: https://patch.msgid.link/20251015-b4-aplpe-dwc3-v2-2-cbd65a2d511a@kernel.org Signed-off-by: Greg Kroah-Hartman --- drivers/usb/dwc3/host.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/drivers/usb/dwc3/host.c b/drivers/usb/dwc3/host.c index 1c513bf8002ec9..e77fd86d09cf0a 100644 --- a/drivers/usb/dwc3/host.c +++ b/drivers/usb/dwc3/host.c @@ -37,7 +37,10 @@ static void dwc3_power_off_all_roothub_ports(struct dwc3 *dwc) /* xhci regs are not mapped yet, do it temporarily here */ if (dwc->xhci_resources[0].start) { - xhci_regs = ioremap(dwc->xhci_resources[0].start, DWC3_XHCI_REGS_END); + if (dwc->xhci_resources[0].flags & IORESOURCE_MEM_NONPOSTED) + xhci_regs = ioremap_np(dwc->xhci_resources[0].start, DWC3_XHCI_REGS_END); + else + xhci_regs = ioremap(dwc->xhci_resources[0].start, DWC3_XHCI_REGS_END); if (!xhci_regs) { dev_err(dwc->dev, "Failed to ioremap xhci_regs\n"); return; From 1e1b3207a53e50d5a66289fffc1f7d52cd9c50f9 Mon Sep 17 00:00:00 2001 From: Junrui Luo Date: Fri, 28 Nov 2025 12:06:31 +0800 Subject: [PATCH 0973/3902] ALSA: dice: fix buffer overflow in detect_stream_formats() commit 324f3e03e8a85931ce0880654e3c3eb38b0f0bba upstream. The function detect_stream_formats() reads the stream_count value directly from a FireWire device without validating it. This can lead to out-of-bounds writes when a malicious device provides a stream_count value greater than MAX_STREAMS. Fix by applying the same validation to both TX and RX stream counts in detect_stream_formats(). Reported-by: Yuhao Jiang Reported-by: Junrui Luo Fixes: 58579c056c1c ("ALSA: dice: use extended protocol to detect available stream formats") Cc: stable@vger.kernel.org Reviewed-by: Takashi Sakamoto Signed-off-by: Junrui Luo Link: https://patch.msgid.link/SYBPR01MB7881B043FC68B4C0DA40B73DAFDCA@SYBPR01MB7881.ausprd01.prod.outlook.com Signed-off-by: Takashi Iwai Signed-off-by: Greg Kroah-Hartman --- sound/firewire/dice/dice-extension.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/sound/firewire/dice/dice-extension.c b/sound/firewire/dice/dice-extension.c index 02f4a8318e38e7..48bfb3ad93ce55 100644 --- a/sound/firewire/dice/dice-extension.c +++ b/sound/firewire/dice/dice-extension.c @@ -116,7 +116,7 @@ static int detect_stream_formats(struct snd_dice *dice, u64 section_addr) break; base_offset += EXT_APP_STREAM_ENTRIES; - stream_count = be32_to_cpu(reg[0]); + stream_count = min_t(unsigned int, be32_to_cpu(reg[0]), MAX_STREAMS); err = read_stream_entries(dice, section_addr, base_offset, stream_count, mode, dice->tx_pcm_chs, @@ -125,7 +125,7 @@ static int detect_stream_formats(struct snd_dice *dice, u64 section_addr) break; base_offset += stream_count * EXT_APP_STREAM_ENTRY_SIZE; - stream_count = be32_to_cpu(reg[1]); + stream_count = min_t(unsigned int, be32_to_cpu(reg[1]), MAX_STREAMS); err = read_stream_entries(dice, section_addr, base_offset, stream_count, mode, dice->rx_pcm_chs, From acacb5b7109acb83b6154b4654acdbec6aa5f54f Mon Sep 17 00:00:00 2001 From: Antheas Kapenekakis Date: Sun, 26 Oct 2025 20:16:35 +0100 Subject: [PATCH 0974/3902] ALSA: hda/realtek: Add match for ASUS Xbox Ally projects commit 18a4895370a79a3efb4a53ccd1efffef6c5b634e upstream. Bind the realtek codec to TAS2781 I2C audio amps on ASUS Xbox Ally projects. While these projects work without a quirk, adding it increases the output volume significantly. Cc: stable@vger.kernel.org # 6.17 Signed-off-by: Antheas Kapenekakis Signed-off-by: Takashi Iwai Link: https://patch.msgid.link/20251026191635.2447593-2-lkml@antheas.dev Signed-off-by: Greg Kroah-Hartman --- sound/hda/codecs/realtek/alc269.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/sound/hda/codecs/realtek/alc269.c b/sound/hda/codecs/realtek/alc269.c index b45fcc9a3785e9..a6d494dab5b6d7 100644 --- a/sound/hda/codecs/realtek/alc269.c +++ b/sound/hda/codecs/realtek/alc269.c @@ -6736,6 +6736,8 @@ static const struct hda_quirk alc269_fixup_tbl[] = { SND_PCI_QUIRK(0x1043, 0x12f0, "ASUS X541UV", ALC256_FIXUP_ASUS_MIC_NO_PRESENCE), SND_PCI_QUIRK(0x1043, 0x1313, "Asus K42JZ", ALC269VB_FIXUP_ASUS_MIC_NO_PRESENCE), SND_PCI_QUIRK(0x1043, 0x1314, "ASUS GA605K", ALC285_FIXUP_ASUS_GA605K_HEADSET_MIC), + SND_PCI_QUIRK(0x1043, 0x1384, "ASUS RC73XA", ALC287_FIXUP_TXNW2781_I2C), + SND_PCI_QUIRK(0x1043, 0x1394, "ASUS RC73YA", ALC287_FIXUP_TXNW2781_I2C), SND_PCI_QUIRK(0x1043, 0x13b0, "ASUS Z550SA", ALC256_FIXUP_ASUS_MIC_NO_PRESENCE), SND_PCI_QUIRK(0x1043, 0x1427, "Asus Zenbook UX31E", ALC269VB_FIXUP_ASUS_ZENBOOK), SND_PCI_QUIRK(0x1043, 0x1433, "ASUS GX650PY/PZ/PV/PU/PYV/PZV/PIV/PVV", ALC285_FIXUP_ASUS_I2C_HEADSET_MIC), From 3646c928bb77c28bd79f8e1893d32db49230b657 Mon Sep 17 00:00:00 2001 From: Antheas Kapenekakis Date: Sun, 26 Oct 2025 20:16:34 +0100 Subject: [PATCH 0975/3902] ALSA: hda/tas2781: fix speaker id retrieval for multiple probes commit 945865a0ddf3e3950aea32e23e10d815ee9b21bc upstream. Currently, on ASUS projects, the TAS2781 codec attaches the speaker GPIO to the first tasdevice_priv instance using devm. This causes tas2781_read_acpi to fail on subsequent probes since the GPIO is already managed by the first device. This causes a failure on Xbox Ally X, because it has two amplifiers, and prevents us from quirking both the Xbox Ally and Xbox Ally X in the realtek codec driver. It is unnecessary to attach the GPIO to a device as it is static. Therefore, instead of attaching it and then reading it when loading the firmware, read its value directly in tas2781_read_acpi and store it in the private data structure. Then, make reading the value non-fatal so that ASUS projects that miss a speaker pin can still work, perhaps using fallback firmware. Fixes: 4e7035a75da9 ("ALSA: hda/tas2781: Add speaker id check for ASUS projects") Cc: stable@vger.kernel.org # 6.17 Signed-off-by: Antheas Kapenekakis Reviewed-by: Baojun Xu Signed-off-by: Takashi Iwai Link: https://patch.msgid.link/20251026191635.2447593-1-lkml@antheas.dev Signed-off-by: Greg Kroah-Hartman --- include/sound/tas2781.h | 2 +- .../hda/codecs/side-codecs/tas2781_hda_i2c.c | 44 +++++++++++-------- 2 files changed, 26 insertions(+), 20 deletions(-) diff --git a/include/sound/tas2781.h b/include/sound/tas2781.h index 0fbcdb15c74b54..29d15ba65f04d3 100644 --- a/include/sound/tas2781.h +++ b/include/sound/tas2781.h @@ -197,7 +197,6 @@ struct tasdevice_priv { struct acoustic_data acou_data; #endif struct tasdevice_fw *fmw; - struct gpio_desc *speaker_id; struct gpio_desc *reset; struct mutex codec_lock; struct regmap *regmap; @@ -215,6 +214,7 @@ struct tasdevice_priv { unsigned int magic_num; unsigned int chip_id; unsigned int sysclk; + int speaker_id; int irq; int cur_prog; diff --git a/sound/hda/codecs/side-codecs/tas2781_hda_i2c.c b/sound/hda/codecs/side-codecs/tas2781_hda_i2c.c index 0357401a602349..c8619995b1d78c 100644 --- a/sound/hda/codecs/side-codecs/tas2781_hda_i2c.c +++ b/sound/hda/codecs/side-codecs/tas2781_hda_i2c.c @@ -87,6 +87,7 @@ static const struct acpi_gpio_mapping tas2781_speaker_id_gpios[] = { static int tas2781_read_acpi(struct tasdevice_priv *p, const char *hid) { + struct gpio_desc *speaker_id; struct acpi_device *adev; struct device *physdev; LIST_HEAD(resources); @@ -119,19 +120,31 @@ static int tas2781_read_acpi(struct tasdevice_priv *p, const char *hid) /* Speaker id was needed for ASUS projects. */ ret = kstrtou32(sub, 16, &subid); if (!ret && upper_16_bits(subid) == PCI_VENDOR_ID_ASUSTEK) { - ret = devm_acpi_dev_add_driver_gpios(p->dev, - tas2781_speaker_id_gpios); - if (ret < 0) + ret = acpi_dev_add_driver_gpios(adev, tas2781_speaker_id_gpios); + if (ret < 0) { dev_err(p->dev, "Failed to add driver gpio %d.\n", ret); - p->speaker_id = devm_gpiod_get(p->dev, "speakerid", GPIOD_IN); - if (IS_ERR(p->speaker_id)) { - dev_err(p->dev, "Failed to get Speaker id.\n"); - ret = PTR_ERR(p->speaker_id); - goto err; + p->speaker_id = -1; + goto end_2563; + } + + speaker_id = fwnode_gpiod_get_index(acpi_fwnode_handle(adev), + "speakerid", 0, GPIOD_IN, NULL); + if (!IS_ERR(speaker_id)) { + p->speaker_id = gpiod_get_value_cansleep(speaker_id); + dev_dbg(p->dev, "Got speaker id gpio from ACPI: %d.\n", + p->speaker_id); + gpiod_put(speaker_id); + } else { + p->speaker_id = -1; + ret = PTR_ERR(speaker_id); + dev_err(p->dev, "Get speaker id gpio failed %d.\n", + ret); } + + acpi_dev_remove_driver_gpios(adev); } else { - p->speaker_id = NULL; + p->speaker_id = -1; } end_2563: @@ -432,23 +445,16 @@ static void tasdevice_dspfw_init(void *context) struct tas2781_hda *tas_hda = dev_get_drvdata(tas_priv->dev); struct tas2781_hda_i2c_priv *hda_priv = tas_hda->hda_priv; struct hda_codec *codec = tas_priv->codec; - int ret, spk_id; + int ret; tasdevice_dsp_remove(tas_priv); tas_priv->fw_state = TASDEVICE_DSP_FW_PENDING; - if (tas_priv->speaker_id != NULL) { - // Speaker id need to be checked for ASUS only. - spk_id = gpiod_get_value(tas_priv->speaker_id); - if (spk_id < 0) { - // Speaker id is not valid, use default. - dev_dbg(tas_priv->dev, "Wrong spk_id = %d\n", spk_id); - spk_id = 0; - } + if (tas_priv->speaker_id >= 0) { snprintf(tas_priv->coef_binaryname, sizeof(tas_priv->coef_binaryname), "TAS2XXX%04X%d.bin", lower_16_bits(codec->core.subsystem_id), - spk_id); + tas_priv->speaker_id); } else { snprintf(tas_priv->coef_binaryname, sizeof(tas_priv->coef_binaryname), From 343fa9800cf9870ec681e21f0a6f2157b74ae520 Mon Sep 17 00:00:00 2001 From: Denis Arefev Date: Tue, 2 Dec 2025 13:13:36 +0300 Subject: [PATCH 0976/3902] ALSA: hda: cs35l41: Fix NULL pointer dereference in cs35l41_hda_read_acpi() commit c34b04cc6178f33c08331568c7fd25c5b9a39f66 upstream. The acpi_get_first_physical_node() function can return NULL, in which case the get_device() function also returns NULL, but this value is then dereferenced without checking,so add a check to prevent a crash. Found by Linux Verification Center (linuxtesting.org) with SVACE. Fixes: 7b2f3eb492da ("ALSA: hda: cs35l41: Add support for CS35L41 in HDA systems") Cc: stable@vger.kernel.org Signed-off-by: Denis Arefev Reviewed-by: Richard Fitzgerald Signed-off-by: Takashi Iwai Link: https://patch.msgid.link/20251202101338.11437-1-arefev@swemel.ru Signed-off-by: Greg Kroah-Hartman --- sound/hda/codecs/side-codecs/cs35l41_hda.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/sound/hda/codecs/side-codecs/cs35l41_hda.c b/sound/hda/codecs/side-codecs/cs35l41_hda.c index c0f2a3ff77a1b6..21e00055c0c443 100644 --- a/sound/hda/codecs/side-codecs/cs35l41_hda.c +++ b/sound/hda/codecs/side-codecs/cs35l41_hda.c @@ -1901,6 +1901,8 @@ static int cs35l41_hda_read_acpi(struct cs35l41_hda *cs35l41, const char *hid, i cs35l41->dacpi = adev; physdev = get_device(acpi_get_first_physical_node(adev)); + if (!physdev) + return -ENODEV; sub = acpi_get_subsystem_id(ACPI_HANDLE(physdev)); if (IS_ERR(sub)) From 590bd2a7acba27fda5e4e9d623780f977c0e1e14 Mon Sep 17 00:00:00 2001 From: Junrui Luo Date: Thu, 6 Nov 2025 10:24:57 +0800 Subject: [PATCH 0977/3902] ALSA: wavefront: Clear substream pointers on close commit e11c5c13ce0ab2325d38fe63500be1dd88b81e38 upstream. Clear substream pointers in close functions to avoid leaving dangling pointers, helping to improve code safety and prevents potential issues. Reported-by: Yuhao Jiang Reported-by: Junrui Luo Fixes: 1da177e4c3f4 ("Linux-2.6.12-rc2") Cc: stable@vger.kernel.org Signed-off-by: Junrui Luo Link: https://patch.msgid.link/SYBPR01MB7881DF762CAB45EE42F6D812AFC2A@SYBPR01MB7881.ausprd01.prod.outlook.com Signed-off-by: Takashi Iwai Signed-off-by: Greg Kroah-Hartman --- sound/isa/wavefront/wavefront_midi.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/sound/isa/wavefront/wavefront_midi.c b/sound/isa/wavefront/wavefront_midi.c index 1250ecba659a05..69d87c4cafaedf 100644 --- a/sound/isa/wavefront/wavefront_midi.c +++ b/sound/isa/wavefront/wavefront_midi.c @@ -278,6 +278,7 @@ static int snd_wavefront_midi_input_close(struct snd_rawmidi_substream *substrea return -EIO; guard(spinlock_irqsave)(&midi->open); + midi->substream_input[mpu] = NULL; midi->mode[mpu] &= ~MPU401_MODE_INPUT; return 0; @@ -300,6 +301,7 @@ static int snd_wavefront_midi_output_close(struct snd_rawmidi_substream *substre return -EIO; guard(spinlock_irqsave)(&midi->open); + midi->substream_output[mpu] = NULL; midi->mode[mpu] &= ~MPU401_MODE_OUTPUT; return 0; } From 1823e08f76c68b9e1d26f6d5ef831b96f61a62a0 Mon Sep 17 00:00:00 2001 From: Junrui Luo Date: Thu, 6 Nov 2025 10:49:46 +0800 Subject: [PATCH 0978/3902] ALSA: wavefront: Fix integer overflow in sample size validation commit 0c4a13ba88594fd4a27292853e736c6b4349823d upstream. The wavefront_send_sample() function has an integer overflow issue when validating sample size. The header->size field is u32 but gets cast to int for comparison with dev->freemem Fix by using unsigned comparison to avoid integer overflow. Fixes: 1da177e4c3f4 ("Linux-2.6.12-rc2") Cc: stable@vger.kernel.org Signed-off-by: Junrui Luo Link: https://patch.msgid.link/SYBPR01MB7881B47789D1B060CE8BF4C3AFC2A@SYBPR01MB7881.ausprd01.prod.outlook.com Signed-off-by: Takashi Iwai Signed-off-by: Greg Kroah-Hartman --- sound/isa/wavefront/wavefront_synth.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/sound/isa/wavefront/wavefront_synth.c b/sound/isa/wavefront/wavefront_synth.c index cd5c177943aa0d..0d78533e1cfd6b 100644 --- a/sound/isa/wavefront/wavefront_synth.c +++ b/sound/isa/wavefront/wavefront_synth.c @@ -950,9 +950,9 @@ wavefront_send_sample (snd_wavefront_t *dev, if (header->size) { dev->freemem = wavefront_freemem (dev); - if (dev->freemem < (int)header->size) { + if (dev->freemem < 0 || dev->freemem < header->size) { dev_err(dev->card->dev, - "insufficient memory to load %d byte sample.\n", + "insufficient memory to load %u byte sample.\n", header->size); return -ENOMEM; } From 78d82960b939df64cf7d26ca5ed34eb87f44c9e5 Mon Sep 17 00:00:00 2001 From: Greg Kroah-Hartman Date: Thu, 18 Dec 2025 14:03:43 +0100 Subject: [PATCH 0979/3902] Linux 6.18.2 Link: https://lore.kernel.org/r/20251216111401.280873349@linuxfoundation.org Tested-by: Ronald Warsow Tested-by: Brett A C Sheffield Tested-by: Achill Gilgenast = Tested-by: Dileep Malepu dileep.debian@gmail.com Tested-by: Salvatore Bonaccorso Tested-by: Florian Fainelli Tested-by: Ron Economos Tested-by: Jeffrin Jose T Tested-by: Takeshi Ogasawara Tested-by: Justin M. Forbes Tested-by: Jon Hunter Tested-by: Peter Schneider Tested-by: Mark Brown Signed-off-by: Greg Kroah-Hartman --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 23cc6c39819b11..c2b5cfd2fce01b 100644 --- a/Makefile +++ b/Makefile @@ -1,7 +1,7 @@ # SPDX-License-Identifier: GPL-2.0 VERSION = 6 PATCHLEVEL = 18 -SUBLEVEL = 1 +SUBLEVEL = 2 EXTRAVERSION = NAME = Baby Opossum Posse From ea4670c7e602bbd5d08cb2beef168aeb479bf195 Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Tue, 16 Dec 2025 22:37:54 +0100 Subject: [PATCH 0980/3902] fixup! arm64: configs: Add asahi.config fragment Signed-off-by: Janne Grunau --- arch/arm64/configs/asahi.config | 1 + 1 file changed, 1 insertion(+) diff --git a/arch/arm64/configs/asahi.config b/arch/arm64/configs/asahi.config index 6c287b81785c55..4f8780350ca842 100644 --- a/arch/arm64/configs/asahi.config +++ b/arch/arm64/configs/asahi.config @@ -43,6 +43,7 @@ CONFIG_SERIAL_SAMSUNG_CONSOLE=y CONFIG_HID_DOCKCHANNEL=m CONFIG_SPI_HID_APPLE_OF=m CONFIG_SPI_HID_APPLE_CORE=m +CONFIG_USB_DWC3_APPLE=m CONFIG_USB_XHCI_PCI_ASMEDIA=y CONFIG_RTC_DRV_MACSMC=m CONFIG_APPLE_ADMAC=m From 2ad915e90fe6363ab51622602be66d1b2b5659e7 Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Thu, 18 Dec 2025 22:19:24 +0100 Subject: [PATCH 0981/3902] usb: typec: tipd: Do not request duplicate role switches The cd321x on M2 and M1 and M2 Pro/Max/Ultra devices generate additional interrupts with data and power updates. Data and power status registers change but only in unknown bits. When connecting an USB3 device the bit 31 in TPS_REG_DATA_STATUS is set. The publically available TRMs for TPS65981, TPS65982, TPS65986, TPS65987DDH and TPS65988DH describe bit 31 as reserved. The updated power status has bits 8-11 set. The TPS65987DDH and TPS65988DH TRM specifies bits 8-9 but 0b11 is a reserved value. The power status update interrupt arrives 1 second after the initial plug status interrupt. This exceeds the debounce delay of 500 ms and results in a repeated USB role switch to host mode. The DWC3 apple glue driver disconnects the bus at this time. Signed-off-by: Janne Grunau --- drivers/usb/typec/tipd/core.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/drivers/usb/typec/tipd/core.c b/drivers/usb/typec/tipd/core.c index 923477105483b1..65575250443161 100644 --- a/drivers/usb/typec/tipd/core.c +++ b/drivers/usb/typec/tipd/core.c @@ -841,7 +841,8 @@ static void cd321x_update_work(struct work_struct *work) cd321x_typec_update_mode(tps, &st); /* Launch the USB role switch */ - usb_role_switch_set_role(tps->role_sw, new_role); + if ((new_role != old_role) || was_disconnected) + usb_role_switch_set_role(tps->role_sw, new_role); power_supply_changed(tps->psy); } From 6d81eb94268dcc3fe7e8aab8e1214b9f8c51c198 Mon Sep 17 00:00:00 2001 From: Alyssa Rosenzweig Date: Fri, 27 Aug 2021 11:19:51 -0400 Subject: [PATCH 0982/3902] WIP: drm/apple: Add DCP display driver Add a DRM/KMS driver for Apple system on chips using the DCP coprocessor, namely the Apple M1. The DCP was added in Apple A14; this driver does not apply to older iDevices. This driver targets the DCP firmware API shipped by macOS 12.1. Currently no incompatibilities with macOS 12.0.1 or 12.2.1 are known. Signed-off-by: Alyssa Rosenzweig Co-developed-by: Janne Grunau Signed-off-by: Janne Grunau --- MAINTAINERS | 7 + drivers/gpu/drm/Kconfig | 2 + drivers/gpu/drm/Makefile | 1 + drivers/gpu/drm/apple/Kconfig | 13 + drivers/gpu/drm/apple/Makefile | 9 + drivers/gpu/drm/apple/apple_drv.c | 442 ++++++++ drivers/gpu/drm/apple/dcp.c | 1393 ++++++++++++++++++++++++++ drivers/gpu/drm/apple/dcp.h | 44 + drivers/gpu/drm/apple/dcpep.h | 406 ++++++++ drivers/gpu/drm/apple/dummy-piodma.c | 31 + drivers/gpu/drm/apple/parser.c | 451 +++++++++ drivers/gpu/drm/apple/parser.h | 32 + 12 files changed, 2831 insertions(+) create mode 100644 drivers/gpu/drm/apple/Kconfig create mode 100644 drivers/gpu/drm/apple/Makefile create mode 100644 drivers/gpu/drm/apple/apple_drv.c create mode 100644 drivers/gpu/drm/apple/dcp.c create mode 100644 drivers/gpu/drm/apple/dcp.h create mode 100644 drivers/gpu/drm/apple/dcpep.h create mode 100644 drivers/gpu/drm/apple/dummy-piodma.c create mode 100644 drivers/gpu/drm/apple/parser.c create mode 100644 drivers/gpu/drm/apple/parser.h diff --git a/MAINTAINERS b/MAINTAINERS index e8f06145fb54c9..f7395a6ca65075 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -1871,6 +1871,13 @@ L: linux-input@vger.kernel.org S: Odd fixes F: drivers/input/mouse/bcm5974.c +APPLE DRM DISPLAY DRIVER +M: Janne Grunau +L: dri-devel@lists.freedesktop.org +S: Maintained +T: git git://anongit.freedesktop.org/drm/drm-misc +F: drivers/gpu/drm/apple/ + APPLE PCIE CONTROLLER DRIVER M: Marc Zyngier L: linux-pci@vger.kernel.org diff --git a/drivers/gpu/drm/Kconfig b/drivers/gpu/drm/Kconfig index 7e6bc0b3a589ce..5a6404909dea7d 100644 --- a/drivers/gpu/drm/Kconfig +++ b/drivers/gpu/drm/Kconfig @@ -394,6 +394,8 @@ source "drivers/gpu/drm/solomon/Kconfig" source "drivers/gpu/drm/sprd/Kconfig" +source "drivers/gpu/drm/apple/Kconfig" + source "drivers/gpu/drm/imagination/Kconfig" source "drivers/gpu/drm/tyr/Kconfig" diff --git a/drivers/gpu/drm/Makefile b/drivers/gpu/drm/Makefile index da2565e6de71d8..e49ea1e962640a 100644 --- a/drivers/gpu/drm/Makefile +++ b/drivers/gpu/drm/Makefile @@ -221,6 +221,7 @@ obj-$(CONFIG_DRM_LIMA) += lima/ obj-$(CONFIG_DRM_PANFROST) += panfrost/ obj-$(CONFIG_DRM_PANTHOR) += panthor/ obj-$(CONFIG_DRM_TYR) += tyr/ +obj-$(CONFIG_DRM_APPLE) += apple/ obj-$(CONFIG_DRM_ASPEED_GFX) += aspeed/ obj-$(CONFIG_DRM_MCDE) += mcde/ obj-$(CONFIG_DRM_TIDSS) += tidss/ diff --git a/drivers/gpu/drm/apple/Kconfig b/drivers/gpu/drm/apple/Kconfig new file mode 100644 index 00000000000000..c4db60413948fd --- /dev/null +++ b/drivers/gpu/drm/apple/Kconfig @@ -0,0 +1,13 @@ +# SPDX-License-Identifier: GPL-2.0-only +config DRM_APPLE + tristate "DRM Support for Apple display controllers" + depends on DRM && OF && ARM64 + depends on ARCH_APPLE || COMPILE_TEST + depends on OF_ADDRESS + select DRM_CLIENT_SELECTION + select DRM_KMS_HELPER + select DRM_KMS_DMA_HELPER + select DRM_GEM_DMA_HELPER + select VIDEOMODE_HELPERS + help + Say Y if you have an Apple Silicon chipset. diff --git a/drivers/gpu/drm/apple/Makefile b/drivers/gpu/drm/apple/Makefile new file mode 100644 index 00000000000000..db7ef359987d3d --- /dev/null +++ b/drivers/gpu/drm/apple/Makefile @@ -0,0 +1,9 @@ +# SPDX-License-Identifier: GPL-2.0-only + +appledrm-y := apple_drv.o +apple_dcp-y := dcp.o parser.o +apple_piodma-y := dummy-piodma.o + +obj-$(CONFIG_DRM_APPLE) += appledrm.o +obj-$(CONFIG_DRM_APPLE) += apple_dcp.o +obj-$(CONFIG_DRM_APPLE) += apple_piodma.o diff --git a/drivers/gpu/drm/apple/apple_drv.c b/drivers/gpu/drm/apple/apple_drv.c new file mode 100644 index 00000000000000..4f396c8225a29b --- /dev/null +++ b/drivers/gpu/drm/apple/apple_drv.c @@ -0,0 +1,442 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* Copyright 2021 Alyssa Rosenzweig */ +/* Based on meson driver which is + * Copyright (C) 2016 BayLibre, SAS + * Author: Neil Armstrong + * Copyright (C) 2015 Amlogic, Inc. All rights reserved. + * Copyright (C) 2014 Endless Mobile + */ + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "dcp.h" + +#define DRIVER_NAME "apple" +#define DRIVER_DESC "Apple display controller DRM driver" + +#define FRAC_16_16(mult, div) (((mult) << 16) / (div)) + +#define MAX_COPROCESSORS 2 + +struct apple_drm_private { + struct drm_device drm; +}; + +DEFINE_DRM_GEM_DMA_FOPS(apple_fops); + +static const struct drm_driver apple_drm_driver = { + DRM_GEM_DMA_DRIVER_OPS, + DRM_FBDEV_DMA_DRIVER_OPS, + .name = DRIVER_NAME, + .desc = DRIVER_DESC, + .major = 1, + .minor = 0, + .driver_features = DRIVER_MODESET | DRIVER_GEM | DRIVER_ATOMIC, + .fops = &apple_fops, +}; + +static int apple_plane_atomic_check(struct drm_plane *plane, + struct drm_atomic_state *state) +{ + struct drm_plane_state *new_plane_state; + struct drm_crtc_state *crtc_state; + + new_plane_state = drm_atomic_get_new_plane_state(state, plane); + + if (!new_plane_state->crtc) + return 0; + + crtc_state = drm_atomic_get_crtc_state(state, new_plane_state->crtc); + if (IS_ERR(crtc_state)) + return PTR_ERR(crtc_state); + + /* + * DCP limits downscaling to 2x and upscaling to 4x. Attempting to + * scale outside these bounds errors out when swapping. + * + * This function also takes care of clipping the src/dest rectangles, + * which is required for correct operation. Partially off-screen + * surfaces may appear corrupted. + * + * DCP does not distinguish plane types in the hardware, so we set + * can_position. If the primary plane does not fill the screen, the + * hardware will fill in zeroes (black). + */ + return drm_atomic_helper_check_plane_state(new_plane_state, + crtc_state, + FRAC_16_16(1, 4), + FRAC_16_16(2, 1), + true, true); +} + +static void apple_plane_atomic_update(struct drm_plane *plane, + struct drm_atomic_state *state) +{ + /* Handled in atomic_flush */ +} + +static const struct drm_plane_helper_funcs apple_plane_helper_funcs = { + .atomic_check = apple_plane_atomic_check, + .atomic_update = apple_plane_atomic_update, +}; + +static const struct drm_plane_funcs apple_plane_funcs = { + .update_plane = drm_atomic_helper_update_plane, + .disable_plane = drm_atomic_helper_disable_plane, + .destroy = drm_plane_cleanup, + .reset = drm_atomic_helper_plane_reset, + .atomic_duplicate_state = drm_atomic_helper_plane_duplicate_state, + .atomic_destroy_state = drm_atomic_helper_plane_destroy_state, +}; + +/* + * Table of supported formats, mapping from DRM fourccs to DCP fourccs. + * + * For future work, DCP supports more formats not listed, including YUV + * formats, an extra RGBA format, and a biplanar RGB10_A8 format (fourcc b3a8) + * used for HDR. + * + * Note: we don't have non-alpha formats but userspace breaks without XRGB. It + * doesn't matter for the primary plane, but cursors/overlays must not + * advertise formats without alpha. + */ +static const u32 dcp_formats[] = { + DRM_FORMAT_XRGB8888, + DRM_FORMAT_ARGB8888, + DRM_FORMAT_XBGR8888, + DRM_FORMAT_ABGR8888, +}; + +u64 apple_format_modifiers[] = { + DRM_FORMAT_MOD_LINEAR, + DRM_FORMAT_MOD_INVALID +}; + +static struct drm_plane *apple_plane_init(struct drm_device *dev, + enum drm_plane_type type) +{ + int ret; + struct drm_plane *plane; + + plane = devm_kzalloc(dev->dev, sizeof(*plane), GFP_KERNEL); + + ret = drm_universal_plane_init(dev, plane, 0x1, &apple_plane_funcs, + dcp_formats, ARRAY_SIZE(dcp_formats), + apple_format_modifiers, type, NULL); + if (ret) + return ERR_PTR(ret); + + drm_plane_helper_add(plane, &apple_plane_helper_funcs); + + return plane; +} + +static int apple_enable_vblank(struct drm_crtc *crtc) +{ + to_apple_crtc(crtc)->vsync_disabled = false; + + return 0; +} + +static void apple_disable_vblank(struct drm_crtc *crtc) +{ + to_apple_crtc(crtc)->vsync_disabled = true; +} + +static enum drm_connector_status +apple_connector_detect(struct drm_connector *connector, bool force) +{ + struct apple_connector *apple_connector = to_apple_connector(connector); + + return apple_connector->connected ? connector_status_connected : + connector_status_disconnected; +} + +static void apple_crtc_atomic_enable(struct drm_crtc *crtc, + struct drm_atomic_state *state) +{ + drm_crtc_vblank_on(crtc); +} + +static void apple_crtc_atomic_disable(struct drm_crtc *crtc, + struct drm_atomic_state *state) +{ + drm_crtc_vblank_off(crtc); + + if (crtc->state->event && !crtc->state->active) { + spin_lock_irq(&crtc->dev->event_lock); + drm_crtc_send_vblank_event(crtc, crtc->state->event); + spin_unlock_irq(&crtc->dev->event_lock); + + crtc->state->event = NULL; + } +} + +static void apple_crtc_atomic_begin(struct drm_crtc *crtc, + struct drm_atomic_state *state) +{ + struct apple_crtc *apple_crtc = to_apple_crtc(crtc); + unsigned long flags; + + if (crtc->state->event) { + WARN_ON(drm_crtc_vblank_get(crtc) != 0); + + spin_lock_irqsave(&crtc->dev->event_lock, flags); + apple_crtc->event = crtc->state->event; + spin_unlock_irqrestore(&crtc->dev->event_lock, flags); + crtc->state->event = NULL; + } +} + +void apple_crtc_vblank(struct apple_crtc *crtc) +{ + unsigned long flags; + + if (crtc->vsync_disabled) + return; + + drm_crtc_handle_vblank(&crtc->base); + + spin_lock_irqsave(&crtc->base.dev->event_lock, flags); + if (crtc->event) { + drm_crtc_send_vblank_event(&crtc->base, crtc->event); + drm_crtc_vblank_put(&crtc->base); + crtc->event = NULL; + } + spin_unlock_irqrestore(&crtc->base.dev->event_lock, flags); +} + +static const struct drm_crtc_funcs apple_crtc_funcs = { + .atomic_destroy_state = drm_atomic_helper_crtc_destroy_state, + .atomic_duplicate_state = drm_atomic_helper_crtc_duplicate_state, + .destroy = drm_crtc_cleanup, + .page_flip = drm_atomic_helper_page_flip, + .reset = drm_atomic_helper_crtc_reset, + .set_config = drm_atomic_helper_set_config, + .enable_vblank = apple_enable_vblank, + .disable_vblank = apple_disable_vblank, +}; + +static const struct drm_mode_config_funcs apple_mode_config_funcs = { + .atomic_check = drm_atomic_helper_check, + .atomic_commit = drm_atomic_helper_commit, + .fb_create = drm_gem_fb_create, +}; + +static const struct drm_mode_config_helper_funcs apple_mode_config_helpers = { + .atomic_commit_tail = drm_atomic_helper_commit_tail_rpm, +}; + +static const struct drm_connector_funcs apple_connector_funcs = { + .fill_modes = drm_helper_probe_single_connector_modes, + .destroy = drm_connector_cleanup, + .reset = drm_atomic_helper_connector_reset, + .atomic_duplicate_state = drm_atomic_helper_connector_duplicate_state, + .atomic_destroy_state = drm_atomic_helper_connector_destroy_state, + .detect = apple_connector_detect, +}; + +static const struct drm_connector_helper_funcs apple_connector_helper_funcs = { + .get_modes = dcp_get_modes, + .mode_valid = dcp_mode_valid, +}; + +static const struct drm_crtc_helper_funcs apple_crtc_helper_funcs = { + .atomic_begin = apple_crtc_atomic_begin, + .atomic_flush = dcp_flush, + .atomic_enable = apple_crtc_atomic_enable, + .atomic_disable = apple_crtc_atomic_disable, +}; + +static int apple_probe_per_dcp(struct device *dev, + struct drm_device *drm, + struct platform_device *dcp) +{ + struct apple_crtc *crtc; + struct apple_connector *connector; + struct drm_encoder *encoder; + struct drm_plane *primary; + int ret; + + primary = apple_plane_init(drm, DRM_PLANE_TYPE_PRIMARY); + + if (IS_ERR(primary)) + return PTR_ERR(primary); + + crtc = devm_kzalloc(dev, sizeof(*crtc), GFP_KERNEL); + ret = drm_crtc_init_with_planes(drm, &crtc->base, primary, NULL, + &apple_crtc_funcs, NULL); + if (ret) + return ret; + + drm_crtc_helper_add(&crtc->base, &apple_crtc_helper_funcs); + + encoder = devm_kzalloc(dev, sizeof(*encoder), GFP_KERNEL); + encoder->possible_crtcs = drm_crtc_mask(&crtc->base); + ret = drm_simple_encoder_init(drm, encoder, DRM_MODE_ENCODER_TMDS); + if (ret) + return ret; + + connector = devm_kzalloc(dev, sizeof(*connector), GFP_KERNEL); + drm_connector_helper_add(&connector->base, + &apple_connector_helper_funcs); + + ret = drm_connector_init(drm, &connector->base, &apple_connector_funcs, + DRM_MODE_CONNECTOR_HDMIA); + if (ret) + return ret; + + connector->base.polled = DRM_CONNECTOR_POLL_HPD; + connector->connected = true; /* XXX */ + connector->dcp = dcp; + + INIT_WORK(&connector->hotplug_wq, dcp_hotplug); + + crtc->dcp = dcp; + dcp_link(dcp, crtc, connector); + + return drm_connector_attach_encoder(&connector->base, encoder); +} + +static int apple_platform_probe(struct platform_device *pdev) +{ + struct apple_drm_private *apple; + struct platform_device *dcp[MAX_COPROCESSORS]; + int ret, nr_dcp, i; + + for (nr_dcp = 0; nr_dcp < MAX_COPROCESSORS; ++nr_dcp) { + struct device_node *np; + + np = of_parse_phandle(pdev->dev.of_node, "apple,coprocessors", + nr_dcp); + + if (!np) + break; + + dcp[nr_dcp] = of_find_device_by_node(np); + + if (!dcp[nr_dcp]) + return -ENODEV; + + /* DCP needs to be initialized before KMS can come online */ + if (!platform_get_drvdata(dcp[nr_dcp])) + return -EPROBE_DEFER; + + if (!dcp_is_initialized(dcp[nr_dcp])) + return -EPROBE_DEFER; + } + + /* Need at least 1 DCP for a display subsystem */ + if (nr_dcp < 1) + return -ENODEV; + + ret = dma_set_mask_and_coherent(&pdev->dev, DMA_BIT_MASK(32)); + if (ret) + return ret; + + apple = devm_drm_dev_alloc(&pdev->dev, &apple_drm_driver, + struct apple_drm_private, drm); + if (IS_ERR(apple)) + return PTR_ERR(apple); + + ret = drm_vblank_init(&apple->drm, 1); + if (ret) + return ret; + + ret = drmm_mode_config_init(&apple->drm); + if (ret) + goto err_unload; + + /* + * IOMFB::UPPipeDCP_H13P::verify_surfaces produces the error "plane + * requires a minimum of 32x32 for the source buffer" if smaller + */ + apple->drm.mode_config.min_width = 32; + apple->drm.mode_config.min_height = 32; + + /* Unknown maximum, use the iMac (24-inch, 2021) display resolution as + * maximum. + */ + apple->drm.mode_config.max_width = 4480; + apple->drm.mode_config.max_height = 2520; + + apple->drm.mode_config.funcs = &apple_mode_config_funcs; + apple->drm.mode_config.helper_private = &apple_mode_config_helpers; + + for (i = 0; i < nr_dcp; ++i) { + ret = apple_probe_per_dcp(&pdev->dev, &apple->drm, dcp[i]); + + if (ret) + goto err_unload; + } + + drm_mode_config_reset(&apple->drm); + + ret = drm_aperture_remove_framebuffers(false, &apple_drm_driver); + if (ret) + return ret; + + ret = drm_dev_register(&apple->drm, 0); + if (ret) + goto err_unload; + + drm_client_setup_with_fourcc(&apple->drm, DRM_FORMAT_XRGB8888); + + return 0; + +err_unload: + drm_dev_put(&apple->drm); + return ret; +} + +static int apple_platform_remove(struct platform_device *pdev) +{ + struct drm_device *drm = platform_get_drvdata(pdev); + + drm_dev_unregister(drm); + + return 0; +} + +static const struct of_device_id of_match[] = { + { .compatible = "apple,display-subsystem" }, + {} +}; +MODULE_DEVICE_TABLE(of, of_match); + +static struct platform_driver apple_platform_driver = { + .driver = { + .name = "apple-drm", + .of_match_table = of_match, + }, + .probe = apple_platform_probe, + .remove = apple_platform_remove, +}; + +module_platform_driver(apple_platform_driver); + +MODULE_AUTHOR("Alyssa Rosenzweig "); +MODULE_DESCRIPTION(DRIVER_DESC); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/gpu/drm/apple/dcp.c b/drivers/gpu/drm/apple/dcp.c new file mode 100644 index 00000000000000..e4e026445d8d10 --- /dev/null +++ b/drivers/gpu/drm/apple/dcp.c @@ -0,0 +1,1393 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* Copyright 2021 Alyssa Rosenzweig */ + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include "dcpep.h" +#include "dcp.h" +#include "parser.h" + +struct apple_dcp; + +/* Register defines used in bandwidth setup structure */ +#define REG_SCRATCH (0x14) +#define REG_DOORBELL (0x0) +#define REG_DOORBELL_BIT (2) + +#define DCP_BOOT_TIMEOUT msecs_to_jiffies(1000) + +/* Limit on call stack depth (arbitrary). Some nesting is required */ +#define DCP_MAX_CALL_DEPTH 8 + +typedef void (*dcp_callback_t)(struct apple_dcp *, void *, void *); + +struct dcp_call_channel { + dcp_callback_t callbacks[DCP_MAX_CALL_DEPTH]; + void *cookies[DCP_MAX_CALL_DEPTH]; + void *output[DCP_MAX_CALL_DEPTH]; + u16 end[DCP_MAX_CALL_DEPTH]; + + /* Current depth of the call stack. Less than DCP_MAX_CALL_DEPTH */ + u8 depth; +}; + +struct dcp_cb_channel { + u8 depth; + void *output[DCP_MAX_CALL_DEPTH]; +}; + +/* Temporary backing for a chunked transfer via setDCPAVPropStart/Chunk/End */ +struct dcp_chunks { + size_t length; + void *data; +}; + +#define DCP_MAX_MAPPINGS (128) /* should be enough */ +#define MAX_DISP_REGISTERS (7) + +struct apple_dcp { + struct device *dev; + struct platform_device *piodma; + struct apple_rtkit *rtk; + struct apple_crtc *crtc; + struct apple_connector *connector; + + /* DCP has crashed */ + bool crashed; + + /* DCP shared memory */ + void *shmem; + + /* Display registers mappable to the DCP */ + struct resource *disp_registers[MAX_DISP_REGISTERS]; + unsigned int nr_disp_registers; + + /* Number of memory mappings made by the DCP, used as an ID */ + u32 nr_mappings; + + /* Indexed table of mappings */ + struct sg_table mappings[DCP_MAX_MAPPINGS]; + + struct dcp_call_channel ch_cmd, ch_oobcmd; + struct dcp_cb_channel ch_cb, ch_oobcb, ch_async; + + /* Active chunked transfer. There can only be one at a time. */ + struct dcp_chunks chunks; + + /* Queued swap. Owned by the DCP to avoid per-swap memory allocation */ + struct dcp_swap_submit_req swap; + + /* Current display mode */ + bool valid_mode; + struct dcp_set_digital_out_mode_req mode; + + /* Is the DCP booted? */ + bool active; + + /* Modes valid for the connected display */ + struct dcp_display_mode *modes; + unsigned int nr_modes; + + /* Attributes of the connected display */ + int width_mm, height_mm; +}; + +/* + * A channel is busy if we have sent a message that has yet to be + * acked. The driver must not sent a message to a busy channel. + */ +static bool dcp_channel_busy(struct dcp_call_channel *ch) +{ + return (ch->depth != 0); +} + +/* Get a call channel for a context */ +static struct dcp_call_channel * +dcp_get_call_channel(struct apple_dcp *dcp, enum dcp_context_id context) +{ + switch (context) { + case DCP_CONTEXT_CMD: + case DCP_CONTEXT_CB: + return &dcp->ch_cmd; + case DCP_CONTEXT_OOBCMD: + case DCP_CONTEXT_OOBCB: + return &dcp->ch_oobcmd; + default: + return NULL; + } +} + +/* + * Get the context ID passed to the DCP for a command we push. The rule is + * simple: callback contexts are used when replying to the DCP, command + * contexts are used otherwise. That corresponds to a non/zero call stack + * depth. This rule frees the caller from tracking the call context manually. + */ +static enum dcp_context_id dcp_call_context(struct apple_dcp *dcp, bool oob) +{ + u8 depth = oob ? dcp->ch_oobcmd.depth : dcp->ch_cmd.depth; + + if (depth) + return oob ? DCP_CONTEXT_OOBCB : DCP_CONTEXT_CB; + else + return oob ? DCP_CONTEXT_OOBCMD : DCP_CONTEXT_CMD; +} + +/* Get a callback channel for a context */ +static struct dcp_cb_channel *dcp_get_cb_channel(struct apple_dcp *dcp, + enum dcp_context_id context) +{ + switch (context) { + case DCP_CONTEXT_CB: + return &dcp->ch_cb; + case DCP_CONTEXT_OOBCB: + return &dcp->ch_oobcb; + case DCP_CONTEXT_ASYNC: + return &dcp->ch_async; + default: + return NULL; + } +} + +/* Get the start of a packet: after the end of the previous packet */ +static u16 dcp_packet_start(struct dcp_call_channel *ch, u8 depth) +{ + if (depth > 0) + return ch->end[depth - 1]; + else + return 0; +} + +/* Pushes and pops the depth of the call stack with safety checks */ +static u8 dcp_push_depth(u8 *depth) +{ + u8 ret = (*depth)++; + + WARN_ON(ret >= DCP_MAX_CALL_DEPTH); + return ret; +} + +static u8 dcp_pop_depth(u8 *depth) +{ + WARN_ON((*depth) == 0); + + return --(*depth); +} + +#define DCP_METHOD(tag, name) [name] = { #name, tag } + +const struct dcp_method_entry dcp_methods[dcpep_num_methods] = { + DCP_METHOD("A000", dcpep_late_init_signal), + DCP_METHOD("A029", dcpep_setup_video_limits), + DCP_METHOD("A034", dcpep_update_notify_clients_dcp), + DCP_METHOD("A357", dcpep_set_create_dfb), + DCP_METHOD("A401", dcpep_start_signal), + DCP_METHOD("A407", dcpep_swap_start), + DCP_METHOD("A408", dcpep_swap_submit), + DCP_METHOD("A410", dcpep_set_display_device), + DCP_METHOD("A412", dcpep_set_digital_out_mode), + DCP_METHOD("A443", dcpep_create_default_fb), + DCP_METHOD("A454", dcpep_first_client_open), + DCP_METHOD("A460", dcpep_set_display_refresh_properties), + DCP_METHOD("A463", dcpep_flush_supports_power), + DCP_METHOD("A468", dcpep_set_power_state), +}; + +/* Call a DCP function given by a tag */ +static void dcp_push(struct apple_dcp *dcp, bool oob, enum dcpep_method method, + u32 in_len, u32 out_len, void *data, dcp_callback_t cb, + void *cookie) +{ + struct dcp_call_channel *ch = oob ? &dcp->ch_oobcmd : &dcp->ch_cmd; + enum dcp_context_id context = dcp_call_context(dcp, oob); + + struct dcp_packet_header header = { + .in_len = in_len, + .out_len = out_len, + + /* Tag is reversed due to endianness of the fourcc */ + .tag[0] = dcp_methods[method].tag[3], + .tag[1] = dcp_methods[method].tag[2], + .tag[2] = dcp_methods[method].tag[1], + .tag[3] = dcp_methods[method].tag[0], + }; + + u8 depth = dcp_push_depth(&ch->depth); + u16 offset = dcp_packet_start(ch, depth); + + void *out = dcp->shmem + dcp_tx_offset(context) + offset; + void *out_data = out + sizeof(header); + size_t data_len = sizeof(header) + in_len + out_len; + + memcpy(out, &header, sizeof(header)); + + if (in_len > 0) + memcpy(out_data, data, in_len); + + dev_dbg(dcp->dev, "---> %s: context %u, offset %u, depth %u\n", + dcp_methods[method].name, context, offset, depth); + + ch->callbacks[depth] = cb; + ch->cookies[depth] = cookie; + ch->output[depth] = out + sizeof(header) + in_len; + ch->end[depth] = offset + ALIGN(data_len, DCP_PACKET_ALIGNMENT); + + apple_rtkit_send_message(dcp->rtk, DCP_ENDPOINT, + dcpep_msg(context, data_len, offset), + NULL, false); +} + +#define DCP_THUNK_VOID(func, handle) \ + static void func(struct apple_dcp *dcp, bool oob, dcp_callback_t cb, \ + void *cookie) \ + { \ + dcp_push(dcp, oob, handle, 0, 0, NULL, cb, cookie); \ + } + +#define DCP_THUNK_OUT(func, handle, T) \ + static void func(struct apple_dcp *dcp, bool oob, dcp_callback_t cb, \ + void *cookie) \ + { \ + dcp_push(dcp, oob, handle, 0, sizeof(T), NULL, cb, cookie); \ + } + +#define DCP_THUNK_IN(func, handle, T) \ + static void func(struct apple_dcp *dcp, bool oob, T *data, \ + dcp_callback_t cb, void *cookie) \ + { \ + dcp_push(dcp, oob, handle, sizeof(T), 0, data, cb, cookie); \ + } + +#define DCP_THUNK_INOUT(func, handle, T_in, T_out) \ + static void func(struct apple_dcp *dcp, bool oob, T_in *data, \ + dcp_callback_t cb, void *cookie) \ + { \ + dcp_push(dcp, oob, handle, sizeof(T_in), sizeof(T_out), data, \ + cb, cookie); \ + } + +DCP_THUNK_INOUT(dcp_swap_submit, dcpep_swap_submit, struct dcp_swap_submit_req, + struct dcp_swap_submit_resp); + +DCP_THUNK_INOUT(dcp_swap_start, dcpep_swap_start, struct dcp_swap_start_req, + struct dcp_swap_start_resp); + +DCP_THUNK_INOUT(dcp_set_power_state, dcpep_set_power_state, + struct dcp_set_power_state_req, + struct dcp_set_power_state_resp); + +DCP_THUNK_INOUT(dcp_set_digital_out_mode, dcpep_set_digital_out_mode, + struct dcp_set_digital_out_mode_req, u32); + +DCP_THUNK_INOUT(dcp_set_display_device, dcpep_set_display_device, u32, u32); + +DCP_THUNK_OUT(dcp_set_display_refresh_properties, + dcpep_set_display_refresh_properties, u32); + +DCP_THUNK_OUT(dcp_late_init_signal, dcpep_late_init_signal, u32); +DCP_THUNK_IN(dcp_flush_supports_power, dcpep_flush_supports_power, u32); +DCP_THUNK_OUT(dcp_create_default_fb, dcpep_create_default_fb, u32); +DCP_THUNK_OUT(dcp_start_signal, dcpep_start_signal, u32); +DCP_THUNK_VOID(dcp_setup_video_limits, dcpep_setup_video_limits); +DCP_THUNK_VOID(dcp_set_create_dfb, dcpep_set_create_dfb); +DCP_THUNK_VOID(dcp_first_client_open, dcpep_first_client_open); + +__attribute__((unused)) +DCP_THUNK_IN(dcp_update_notify_clients_dcp, dcpep_update_notify_clients_dcp, + struct dcp_update_notify_clients_dcp); + +/* Parse a callback tag "D123" into the ID 123. Returns -EINVAL on failure. */ +static int dcp_parse_tag(char tag[4]) +{ + u32 d[3]; + int i; + + if (tag[3] != 'D') + return -EINVAL; + + for (i = 0; i < 3; ++i) { + d[i] = (u32)(tag[i] - '0'); + + if (d[i] > 9) + return -EINVAL; + } + + return d[0] + (d[1] * 10) + (d[2] * 100); +} + +/* Ack a callback from the DCP */ +static void dcp_ack(struct apple_dcp *dcp, enum dcp_context_id context) +{ + struct dcp_cb_channel *ch = dcp_get_cb_channel(dcp, context); + + dcp_pop_depth(&ch->depth); + apple_rtkit_send_message(dcp->rtk, DCP_ENDPOINT, dcpep_ack(context), + NULL, false); +} + +/* DCP callback handlers */ +static void dcpep_cb_nop(struct apple_dcp *dcp) +{ + /* No operation */ +} + +static u8 dcpep_cb_true(struct apple_dcp *dcp) +{ + return true; +} + +static u8 dcpep_cb_false(struct apple_dcp *dcp) +{ + return false; +} + +static u32 dcpep_cb_zero(struct apple_dcp *dcp) +{ + return 0; +} + +static void dcpep_cb_swap_complete(struct apple_dcp *dcp) +{ + apple_crtc_vblank(dcp->crtc); +} + +static struct dcp_get_uint_prop_resp +dcpep_cb_get_uint_prop(struct apple_dcp *dcp, struct dcp_get_uint_prop_req *req) +{ + /* unimplemented for now */ + return (struct dcp_get_uint_prop_resp) { + .value = 0 + }; +} + +/* + * Callback to map a buffer allocated with allocate_buf for PIODMA usage. + * PIODMA is separate from the main DCP and uses own IOVA space on a dedicated + * stream of the display DART, rather than the expected DCP DART. + * + * XXX: This relies on dma_get_sgtable in concert with dma_map_sgtable, which + * is a "fundamentally unsafe" operation according to the docs. And yet + * everyone does it... + */ +static struct dcp_map_buf_resp +dcpep_cb_map_piodma(struct apple_dcp *dcp, struct dcp_map_buf_req *req) +{ + struct sg_table *map; + int ret; + + if (req->buffer >= ARRAY_SIZE(dcp->mappings)) + goto reject; + + map = &dcp->mappings[req->buffer]; + + if (!map->sgl) + goto reject; + + /* Use PIODMA device instead of DCP to map against the right IOMMU. */ + ret = dma_map_sgtable(&dcp->piodma->dev, map, DMA_BIDIRECTIONAL, 0); + + if (ret) + goto reject; + + return (struct dcp_map_buf_resp) { + .dva = sg_dma_address(map->sgl) + }; + +reject: + dev_err(dcp->dev, "denying map of invalid buffer %llx for pidoma\n", + req->buffer); + return (struct dcp_map_buf_resp) { + .ret = EINVAL + }; +} + +/* + * Allocate an IOVA contiguous buffer mapped to the DCP. The buffer need not be + * physically contigiuous, however we should save the sgtable in case the + * buffer needs to be later mapped for PIODMA. + */ +static struct dcp_allocate_buffer_resp +dcpep_cb_allocate_buffer(struct apple_dcp *dcp, struct dcp_allocate_buffer_req *req) +{ + struct dcp_allocate_buffer_resp resp = { 0 }; + void *buf; + + resp.dva_size = ALIGN(req->size, 4096); + resp.mem_desc_id = ++dcp->nr_mappings; + + if (resp.mem_desc_id >= ARRAY_SIZE(dcp->mappings)) { + dev_warn(dcp->dev, "DCP overflowed mapping table, ignoring"); + return resp; + } + + buf = dma_alloc_coherent(dcp->dev, resp.dva_size, &resp.dva, + GFP_KERNEL); + + dma_get_sgtable(dcp->dev, &dcp->mappings[resp.mem_desc_id], buf, + resp.dva, resp.dva_size); + return resp; +} + +/* Validate that the specified region is a display register */ +static bool is_disp_register(struct apple_dcp *dcp, u64 start, u64 end) +{ + int i; + + for (i = 0; i < dcp->nr_disp_registers; ++i) { + struct resource *r = dcp->disp_registers[i]; + + if ((start >= r->start) && (end <= r->end)) + return true; + } + + return false; +} + +/* + * Map contiguous physical memory into the DCP's address space. The firmware + * uses this to map the display registers we advertise in + * sr_map_device_memory_with_index, so we bounds check against that to guard + * safe against malicious coprocessors. + */ +static struct dcp_map_physical_resp +dcpep_cb_map_physical(struct apple_dcp *dcp, struct dcp_map_physical_req *req) +{ + int size = ALIGN(req->size, 4096); + + if (!is_disp_register(dcp, req->paddr, req->paddr + size - 1)) { + dev_err(dcp->dev, "refusing to map phys address %llx size %llx", + req->paddr, req->size); + return (struct dcp_map_physical_resp) { }; + } + + return (struct dcp_map_physical_resp) { + .dva_size = size, + .mem_desc_id = ++dcp->nr_mappings, + .dva = dma_map_resource(dcp->dev, req->paddr, size, + DMA_BIDIRECTIONAL, 0), + }; +} + +static u64 dcpep_cb_get_frequency(struct apple_dcp *dcp) +{ + /* Pixel clock frequency in Hz (compare: 4K@60 VGA clock 533.250 MHz) */ + return 533333328; +} + +static struct dcp_map_reg_resp +dcpep_cb_map_reg(struct apple_dcp *dcp, struct dcp_map_reg_req *req) +{ + if (req->index >= dcp->nr_disp_registers) { + dev_warn(dcp->dev, "attempted to read invalid reg index %u", + req->index); + + return (struct dcp_map_reg_resp) { + .ret = 1 + }; + } else { + struct resource *rsrc = dcp->disp_registers[req->index]; + + return (struct dcp_map_reg_resp) { + .addr = rsrc->start, + .length = resource_size(rsrc) + }; + } +} + +/* Chunked data transfer for property dictionaries */ +static u8 dcpep_cb_prop_start(struct apple_dcp *dcp, u32 *length) +{ + if (dcp->chunks.data != NULL) { + dev_warn(dcp->dev, "ignoring spurious transfer start\n"); + return false; + } + + dcp->chunks.length = *length; + dcp->chunks.data = devm_kzalloc(dcp->dev, *length, GFP_KERNEL); + + if (!dcp->chunks.data) { + dev_warn(dcp->dev, "failed to allocate chunks\n"); + return false; + } + + return true; +} + +static u8 dcpep_cb_prop_chunk(struct apple_dcp *dcp, + struct dcp_set_dcpav_prop_chunk_req *req) +{ + if (!dcp->chunks.data) { + dev_warn(dcp->dev, "ignoring spurious chunk\n"); + return false; + } + + if (req->offset + req->length > dcp->chunks.length) { + dev_warn(dcp->dev, "ignoring overflowing chunk\n"); + return false; + } + + memcpy(dcp->chunks.data + req->offset, req->data, req->length); + return true; +} + +static void dcp_set_dimensions(struct apple_dcp *dcp) +{ + int i; + + /* Set the connector info */ + if (dcp->connector) { + struct drm_connector *connector = &dcp->connector->base; + + mutex_lock(&connector->dev->mode_config.mutex); + connector->display_info.width_mm = dcp->width_mm; + connector->display_info.height_mm = dcp->height_mm; + mutex_unlock(&connector->dev->mode_config.mutex); + } + + /* + * Fix up any probed modes. Modes are created when parsing + * TimingElements, dimensions are calculated when parsing + * DisplayAttributes, and TimingElements may be sent first + */ + for (i = 0; i < dcp->nr_modes; ++i) { + dcp->modes[i].mode.width_mm = dcp->width_mm; + dcp->modes[i].mode.height_mm = dcp->height_mm; + } +} + +static bool dcpep_process_chunks(struct apple_dcp *dcp, + struct dcp_set_dcpav_prop_end_req *req) +{ + struct dcp_parse_ctx ctx; + int ret; + + if (!dcp->chunks.data) { + dev_warn(dcp->dev, "ignoring spurious end\n"); + return false; + } + + ret = parse(dcp->chunks.data, dcp->chunks.length, &ctx); + + if (ret) { + dev_warn(dcp->dev, "bad header on dcpav props\n"); + return false; + } + + if (!strcmp(req->key, "TimingElements")) { + dcp->modes = enumerate_modes(&ctx, &dcp->nr_modes, + dcp->width_mm, dcp->height_mm); + + if (IS_ERR(dcp->modes)) { + dev_warn(dcp->dev, "failed to parse modes\n"); + dcp->modes = NULL; + dcp->nr_modes = 0; + return false; + } + } else if (!strcmp(req->key, "DisplayAttributes")) { + ret = parse_display_attributes(&ctx, &dcp->width_mm, + &dcp->height_mm); + + if (ret) { + dev_warn(dcp->dev, "failed to parse display attribs\n"); + return false; + } + + dcp_set_dimensions(dcp); + } + + return true; +} + +static u8 dcpep_cb_prop_end(struct apple_dcp *dcp, + struct dcp_set_dcpav_prop_end_req *req) +{ + u8 resp = dcpep_process_chunks(dcp, req); + + /* Reset for the next transfer */ + devm_kfree(dcp->dev, dcp->chunks.data); + dcp->chunks.data = NULL; + + return resp; +} + +/* Boot sequence */ +static void boot_done(struct apple_dcp *dcp, void *out, void *cookie) +{ + struct dcp_cb_channel *ch = &dcp->ch_cb; + u8 *succ = ch->output[ch->depth - 1]; + + *succ = true; + dcp_ack(dcp, DCP_CONTEXT_CB); +} + +static void boot_5(struct apple_dcp *dcp, void *out, void *cookie) +{ + dcp_set_display_refresh_properties(dcp, false, boot_done, NULL); +} + +static void boot_4(struct apple_dcp *dcp, void *out, void *cookie) +{ + dcp_late_init_signal(dcp, false, boot_5, NULL); +} + +static void boot_3(struct apple_dcp *dcp, void *out, void *cookie) +{ + u32 v_true = true; + + dcp_flush_supports_power(dcp, false, &v_true, boot_4, NULL); +} + +static void boot_2(struct apple_dcp *dcp, void *out, void *cookie) +{ + dcp_setup_video_limits(dcp, false, boot_3, NULL); +} + +static void boot_1_5(struct apple_dcp *dcp, void *out, void *cookie) +{ + dcp_create_default_fb(dcp, false, boot_2, NULL); +} + +/* Use special function signature to defer the ACK */ +static bool dcpep_cb_boot_1(struct apple_dcp *dcp, void *out, void *in) +{ + dcp_set_create_dfb(dcp, false, boot_1_5, NULL); + return false; +} + +static struct dcp_rt_bandwidth dcpep_cb_rt_bandwidth(struct apple_dcp *dcp) +{ + return (struct dcp_rt_bandwidth) { + .reg_scratch = dcp->disp_registers[5]->start + REG_SCRATCH, + .reg_doorbell = dcp->disp_registers[6]->start + REG_DOORBELL, + .doorbell_bit = REG_DOORBELL_BIT, + + .padding[3] = 0x4, // XXX: required by 11.x firmware + }; +} + +/* Callback to get the current time as milliseconds since the UNIX epoch */ +static u64 dcpep_cb_get_time(struct apple_dcp *dcp) +{ + return ktime_to_ms(ktime_get_real()); +} + +/* + * Helper to send a DRM hotplug event. The DCP is accessed from a single + * (RTKit) thread. To handle hotplug callbacks, we need to call + * drm_kms_helper_hotplug_event, which does an atomic commit (via DCP) and + * waits for vblank (a DCP callback). That means we deadlock if we call from + * the RTKit thread! Instead, move the call to another thread via a workqueue. + */ +void dcp_hotplug(struct work_struct *work) +{ + struct apple_connector *connector; + struct drm_device *dev; + + connector = container_of(work, struct apple_connector, hotplug_wq); + dev = connector->base.dev; + + /* + * DCP defers link training until we set a display mode. But we set + * display modes from atomic_flush, so userspace needs to trigger a + * flush, or the CRTC gets no signal. + */ + if (connector->connected) { + drm_connector_set_link_status_property( + &connector->base, DRM_MODE_LINK_STATUS_BAD); + } + + if (dev && dev->registered) + drm_kms_helper_hotplug_event(dev); +} +EXPORT_SYMBOL_GPL(dcp_hotplug); + +static void dcpep_cb_hotplug(struct apple_dcp *dcp, u64 *connected) +{ + struct apple_connector *connector = dcp->connector; + + /* Hotplug invalidates mode. DRM doesn't always handle this. */ + dcp->valid_mode = false; + + if (connector) { + connector->connected = !!(*connected); + schedule_work(&connector->hotplug_wq); + } +} + +#define DCPEP_MAX_CB (1000) + +/* + * Define type-safe trampolines. Define typedefs to enforce type-safety on the + * input data (so if the types don't match, gcc errors out). + */ + +#define TRAMPOLINE_VOID(func, handler) \ + static bool func(struct apple_dcp *dcp, void *out, void *in) \ + { \ + dev_dbg(dcp->dev, "received callback %s\n", #handler); \ + handler(dcp); \ + return true; \ + } + +#define TRAMPOLINE_IN(func, handler, T_in) \ + typedef void (*callback_##name)(struct apple_dcp *, T_in *); \ + \ + static bool func(struct apple_dcp *dcp, void *out, void *in) \ + { \ + callback_##name cb = handler; \ + \ + dev_dbg(dcp->dev, "received callback %s\n", #handler); \ + cb(dcp, in); \ + return true; \ + } + +#define TRAMPOLINE_INOUT(func, handler, T_in, T_out) \ + typedef T_out (*callback_##handler)(struct apple_dcp *, T_in *); \ + \ + static bool func(struct apple_dcp *dcp, void *out, void *in) \ + { \ + T_out *typed_out = out; \ + callback_##handler cb = handler; \ + \ + dev_dbg(dcp->dev, "received callback %s\n", #handler); \ + *typed_out = cb(dcp, in); \ + return true; \ + } + +#define TRAMPOLINE_OUT(func, handler, T_out) \ + static bool func(struct apple_dcp *dcp, void *out, void *in) \ + { \ + T_out *typed_out = out; \ + \ + dev_dbg(dcp->dev, "received callback %s\n", #handler); \ + *typed_out = handler(dcp); \ + return true; \ + } + +TRAMPOLINE_VOID(trampoline_nop, dcpep_cb_nop); +TRAMPOLINE_OUT(trampoline_true, dcpep_cb_true, u8); +TRAMPOLINE_OUT(trampoline_false, dcpep_cb_false, u8); +TRAMPOLINE_OUT(trampoline_zero, dcpep_cb_zero, u32); +TRAMPOLINE_VOID(trampoline_swap_complete, dcpep_cb_swap_complete); +TRAMPOLINE_INOUT(trampoline_get_uint_prop, dcpep_cb_get_uint_prop, + struct dcp_get_uint_prop_req, struct dcp_get_uint_prop_resp); +TRAMPOLINE_INOUT(trampoline_map_piodma, dcpep_cb_map_piodma, + struct dcp_map_buf_req, struct dcp_map_buf_resp); +TRAMPOLINE_INOUT(trampoline_allocate_buffer, dcpep_cb_allocate_buffer, + struct dcp_allocate_buffer_req, + struct dcp_allocate_buffer_resp); +TRAMPOLINE_INOUT(trampoline_map_physical, dcpep_cb_map_physical, + struct dcp_map_physical_req, struct dcp_map_physical_resp); +TRAMPOLINE_INOUT(trampoline_map_reg, dcpep_cb_map_reg, struct dcp_map_reg_req, + struct dcp_map_reg_resp); +TRAMPOLINE_INOUT(trampoline_prop_start, dcpep_cb_prop_start, u32, u8); +TRAMPOLINE_INOUT(trampoline_prop_chunk, dcpep_cb_prop_chunk, + struct dcp_set_dcpav_prop_chunk_req, u8); +TRAMPOLINE_INOUT(trampoline_prop_end, dcpep_cb_prop_end, + struct dcp_set_dcpav_prop_end_req, u8); +TRAMPOLINE_OUT(trampoline_rt_bandwidth, dcpep_cb_rt_bandwidth, + struct dcp_rt_bandwidth); +TRAMPOLINE_OUT(trampoline_get_frequency, dcpep_cb_get_frequency, u64); +TRAMPOLINE_OUT(trampoline_get_time, dcpep_cb_get_time, u64); +TRAMPOLINE_IN(trampoline_hotplug, dcpep_cb_hotplug, u64); + +bool (*const dcpep_cb_handlers[DCPEP_MAX_CB])(struct apple_dcp *, void *, void *) = { + [0] = trampoline_true, /* did_boot_signal */ + [1] = trampoline_true, /* did_power_on_signal */ + [2] = trampoline_nop, /* will_power_off_signal */ + [3] = trampoline_rt_bandwidth, + [100] = trampoline_nop, /* match_pmu_service */ + [101] = trampoline_zero, /* get_display_default_stride */ + [103] = trampoline_nop, /* set_boolean_property */ + [106] = trampoline_nop, /* remove_property */ + [107] = trampoline_true, /* create_provider_service */ + [108] = trampoline_true, /* create_product_service */ + [109] = trampoline_true, /* create_pmu_service */ + [110] = trampoline_true, /* create_iomfb_service */ + [111] = trampoline_false, /* create_backlight_service */ + [116] = dcpep_cb_boot_1, + [118] = trampoline_false, /* is_dark_boot / is_waking_from_hibernate*/ + [120] = trampoline_false, /* read_edt_data */ + [122] = trampoline_prop_start, + [123] = trampoline_prop_chunk, + [124] = trampoline_prop_end, + [201] = trampoline_map_piodma, + [206] = trampoline_true, /* match_pmu_service_2 */ + [207] = trampoline_true, /* match_backlight_service */ + [208] = trampoline_get_time, + [211] = trampoline_nop, /* update_backlight_factor_prop */ + [300] = trampoline_nop, /* pr_publish */ + [401] = trampoline_get_uint_prop, + [406] = trampoline_nop, /* set_fx_prop */ + [408] = trampoline_get_frequency, + [411] = trampoline_map_reg, + [413] = trampoline_true, /* sr_set_property_dict */ + [414] = trampoline_true, /* sr_set_property_int */ + [415] = trampoline_true, /* sr_set_property_bool */ + [451] = trampoline_allocate_buffer, + [452] = trampoline_map_physical, + [552] = trampoline_true, /* set_property_dict_0 */ + [561] = trampoline_true, /* set_property_dict */ + [563] = trampoline_true, /* set_property_int */ + [565] = trampoline_true, /* set_property_bool */ + [567] = trampoline_true, /* set_property_str */ + [574] = trampoline_zero, /* power_up_dart */ + [576] = trampoline_hotplug, + [577] = trampoline_nop, /* powerstate_notify */ + [582] = trampoline_true, /* create_default_fb_surface */ + [589] = trampoline_swap_complete, + [591] = trampoline_nop, /* swap_complete_intent_gated */ + [598] = trampoline_nop, /* find_swap_function_gated */ +}; + +static void dcpep_handle_cb(struct apple_dcp *dcp, enum dcp_context_id context, + void *data, u32 length) +{ + struct device *dev = dcp->dev; + struct dcp_packet_header *hdr = data; + void *in, *out; + int tag = dcp_parse_tag(hdr->tag); + struct dcp_cb_channel *ch = dcp_get_cb_channel(dcp, context); + u8 depth; + + if (tag < 0 || tag >= DCPEP_MAX_CB || !dcpep_cb_handlers[tag]) { + dev_warn(dev, "received unknown callback %c%c%c%c\n", + hdr->tag[3], hdr->tag[2], hdr->tag[1], hdr->tag[0]); + return; + } + + in = data + sizeof(*hdr); + out = in + hdr->in_len; + + depth = dcp_push_depth(&ch->depth); + ch->output[depth] = out; + + if (dcpep_cb_handlers[tag](dcp, out, in)) + dcp_ack(dcp, context); +} + +static void dcpep_handle_ack(struct apple_dcp *dcp, enum dcp_context_id context, + void *data, u32 length) +{ + struct dcp_packet_header *header = data; + struct dcp_call_channel *ch = dcp_get_call_channel(dcp, context); + void *cookie; + dcp_callback_t cb; + + if (!ch) { + dev_warn(dcp->dev, "ignoring ack on context %X\n", context); + return; + } + + dcp_pop_depth(&ch->depth); + + cb = ch->callbacks[ch->depth]; + cookie = ch->cookies[ch->depth]; + + if (cb) + cb(dcp, data + sizeof(*header) + header->in_len, cookie); +} + +static void dcpep_got_msg(struct apple_dcp *dcp, u64 message) +{ + enum dcp_context_id ctx_id; + u16 offset; + u32 length; + int channel_offset; + void *data; + + ctx_id = (message & DCPEP_CONTEXT_MASK) >> DCPEP_CONTEXT_SHIFT; + offset = (message & DCPEP_OFFSET_MASK) >> DCPEP_OFFSET_SHIFT; + length = (message >> DCPEP_LENGTH_SHIFT); + + channel_offset = dcp_channel_offset(ctx_id); + + if (channel_offset < 0) { + dev_warn(dcp->dev, "invalid context received %u", ctx_id); + return; + } + + data = dcp->shmem + channel_offset + offset; + + if (message & DCPEP_ACK) + dcpep_handle_ack(dcp, ctx_id, data, length); + else + dcpep_handle_cb(dcp, ctx_id, data, length); +} + +/* + * Callback for swap requests. If a swap failed, we'll never get a swap + * complete event so we need to fake a vblank event early to avoid a hang. + */ + +static void dcp_swapped(struct apple_dcp *dcp, void *data, void *cookie) +{ + struct dcp_swap_submit_resp *resp = data; + + if (resp->ret) { + dev_err(dcp->dev, "swap failed! status %u\n", resp->ret); + apple_crtc_vblank(dcp->crtc); + } +} + +static void dcp_swap_started(struct apple_dcp *dcp, void *data, void *cookie) +{ + struct dcp_swap_start_resp *resp = data; + + dcp->swap.swap.swap_id = resp->swap_id; + + dcp_swap_submit(dcp, false, &dcp->swap, dcp_swapped, NULL); +} + +/* + * DRM specifies rectangles as start and end coordinates. DCP specifies + * rectangles as a start coordinate and a width/height. Convert a DRM rectangle + * to a DCP rectangle. + */ +static struct dcp_rect drm_to_dcp_rect(struct drm_rect *rect) +{ + return (struct dcp_rect) { + .x = rect->x1, + .y = rect->y1, + .w = drm_rect_width(rect), + .h = drm_rect_height(rect) + }; +} + +static u32 drm_format_to_dcp(u32 drm) +{ + switch (drm) { + case DRM_FORMAT_XRGB8888: + case DRM_FORMAT_ARGB8888: + return fourcc_code('A', 'R', 'G', 'B'); + + case DRM_FORMAT_XBGR8888: + case DRM_FORMAT_ABGR8888: + return fourcc_code('A', 'B', 'G', 'R'); + } + + pr_warn("DRM format %X not supported in DCP\n", drm); + return 0; +} + +int dcp_get_modes(struct drm_connector *connector) +{ + struct apple_connector *apple_connector = to_apple_connector(connector); + struct platform_device *pdev = apple_connector->dcp; + struct apple_dcp *dcp = platform_get_drvdata(pdev); + + struct drm_device *dev = connector->dev; + struct drm_display_mode *mode; + int i; + + for (i = 0; i < dcp->nr_modes; ++i) { + mode = drm_mode_duplicate(dev, &dcp->modes[i].mode); + + if (!mode) { + dev_err(dev->dev, "Failed to duplicate display mode\n"); + return 0; + } + + drm_mode_probed_add(connector, mode); + } + + return dcp->nr_modes; +} +EXPORT_SYMBOL_GPL(dcp_get_modes); + +/* The user may own drm_display_mode, so we need to search for our copy */ +static struct dcp_display_mode *lookup_mode(struct apple_dcp *dcp, + struct drm_display_mode *mode) +{ + int i; + + for (i = 0; i < dcp->nr_modes; ++i) { + if (drm_mode_match(mode, &dcp->modes[i].mode, + DRM_MODE_MATCH_TIMINGS | + DRM_MODE_MATCH_CLOCK)) + return &dcp->modes[i]; + } + + return NULL; +} + +int dcp_mode_valid(struct drm_connector *connector, + struct drm_display_mode *mode) +{ + struct apple_connector *apple_connector = to_apple_connector(connector); + struct platform_device *pdev = apple_connector->dcp; + struct apple_dcp *dcp = platform_get_drvdata(pdev); + + return lookup_mode(dcp, mode) ? MODE_OK : MODE_BAD; +} +EXPORT_SYMBOL_GPL(dcp_mode_valid); + +/* Helpers to modeset and swap, used to flush */ +static void do_swap(struct apple_dcp *dcp, void *data, void *cookie) +{ + struct dcp_swap_start_req start_req = { 0 }; + + dcp_swap_start(dcp, false, &start_req, dcp_swap_started, NULL); +} + +static void dcp_modeset(struct apple_dcp *dcp, void *out, void *cookie) +{ + dcp_set_digital_out_mode(dcp, false, &dcp->mode, do_swap, NULL); +} + +void dcp_flush(struct drm_crtc *crtc, struct drm_atomic_state *state) +{ + struct platform_device *pdev = to_apple_crtc(crtc)->dcp; + struct apple_dcp *dcp = platform_get_drvdata(pdev); + struct drm_plane *plane; + struct drm_plane_state *new_state, *old_state; + struct drm_crtc_state *crtc_state; + struct dcp_swap_submit_req *req = &dcp->swap; + int l; + + crtc_state = drm_atomic_get_new_crtc_state(state, crtc); + + if (WARN(dcp_channel_busy(&dcp->ch_cmd), "unexpected busy channel") || + WARN(!dcp->connector->connected, "can't flush if disconnected")) { + apple_crtc_vblank(dcp->crtc); + return; + } + + /* Reset to defaults */ + memset(req, 0, sizeof(*req)); + for (l = 0; l < SWAP_SURFACES; l++) + req->surf_null[l] = true; + + for_each_oldnew_plane_in_state(state, plane, old_state, new_state, l) { + struct drm_framebuffer *fb = new_state->fb; + struct drm_rect src_rect; + + WARN_ON(l >= SWAP_SURFACES); + + req->swap.swap_enabled |= BIT(l); + + if (!new_state->fb) { + if (old_state->fb) + req->swap.swap_enabled |= DCP_REMOVE_LAYERS; + + continue; + } + req->surf_null[l] = false; + + // XXX: awful hack! race condition between a framebuffer unbind + // getting swapped out and GEM unreferencing a framebuffer. If + // we lose the race, the display gets IOVA faults and the DCP + // crashes. We need to extend the lifetime of the + // drm_framebuffer (and hence the GEM object) until after we + // get a swap complete for the swap unbinding it. + drm_framebuffer_get(fb); + + drm_rect_fp_to_int(&src_rect, &new_state->src); + + req->swap.src_rect[l] = drm_to_dcp_rect(&src_rect); + req->swap.dst_rect[l] = drm_to_dcp_rect(&new_state->dst); + + req->surf_iova[l] = drm_fb_dma_get_gem_addr(fb, new_state, 0); + + req->surf[l] = (struct dcp_surface) { + .format = drm_format_to_dcp(fb->format->format), + .xfer_func = 13, + .colorspace = 1, + .stride = fb->pitches[0], + .width = fb->width, + .height = fb->height, + .buf_size = fb->height * fb->pitches[0], + .surface_id = req->swap.surf_ids[l], + + /* Only used for compressed or multiplanar surfaces */ + .pix_size = 1, + .pel_w = 1, + .pel_h = 1, + .has_comp = 1, + .has_planes = 1, + }; + } + + /* These fields should be set together */ + req->swap.swap_completed = req->swap.swap_enabled; + + if (drm_atomic_crtc_needs_modeset(crtc_state) || !dcp->valid_mode) { + struct dcp_display_mode *mode; + u32 handle = 2; + + mode = lookup_mode(dcp, &crtc_state->mode); + if (!mode) { + dev_warn(dcp->dev, "no match for " DRM_MODE_FMT, + DRM_MODE_ARG(&crtc_state->mode)); + schedule_work(&dcp->vblank_wq); + return; + } + + dcp->mode = (struct dcp_set_digital_out_mode_req) { + .color_mode_id = mode->color_mode_id, + .timing_mode_id = mode->timing_mode_id + }; + + dcp->valid_mode = true; + + dcp_set_display_device(dcp, false, &handle, dcp_modeset, NULL); + } else + do_swap(dcp, NULL, NULL); +} +EXPORT_SYMBOL_GPL(dcp_flush); + +bool dcp_is_initialized(struct platform_device *pdev) +{ + struct apple_dcp *dcp = platform_get_drvdata(pdev); + + return dcp->active; +} +EXPORT_SYMBOL_GPL(dcp_is_initialized); + +static void init_done(struct apple_dcp *dcp, void *out, void *cookie) +{ +} + +static void init_3(struct apple_dcp *dcp, void *out, void *cookie) +{ + struct dcp_set_power_state_req req = { + .unklong = 1, + }; + dcp_set_power_state(dcp, false, &req, init_done, NULL); +} + +static void init_2(struct apple_dcp *dcp, void *out, void *cookie) +{ + dcp_first_client_open(dcp, false, init_3, NULL); +} + +static void dcp_started(struct apple_dcp *dcp, void *data, void *cookie) +{ + dev_info(dcp->dev, "DCP booted\n"); + + init_2(dcp, data, cookie); + + dcp->active = true; +} + +static void dcp_got_msg(void *cookie, u8 endpoint, u64 message) +{ + struct apple_dcp *dcp = cookie; + enum dcpep_type type = (message >> DCPEP_TYPE_SHIFT) & DCPEP_TYPE_MASK; + + WARN_ON(endpoint != DCP_ENDPOINT); + + if (type == DCPEP_TYPE_INITIALIZED) + dcp_start_signal(dcp, false, dcp_started, NULL); + else if (type == DCPEP_TYPE_MESSAGE) + dcpep_got_msg(dcp, message); + else + dev_warn(dcp->dev, "Ignoring unknown message %llx\n", message); +} + +static void dcp_rtk_crashed(void *cookie, const void *crashlog, size_t crashlog_size) +{ + struct apple_dcp *dcp = cookie; + + dcp->crashed = true; + dev_err(dcp->dev, "DCP has crashed"); +} + +static int dcp_rtk_shmem_setup(void *cookie, struct apple_rtkit_shmem *bfr) +{ + struct apple_dcp *dcp = cookie; + + if (bfr->iova) { + struct iommu_domain *domain = iommu_get_domain_for_dev(dcp->dev); + phys_addr_t phy_addr; + + if (!domain) + return -ENOMEM; + + // TODO: get map from device-tree + phy_addr = iommu_iova_to_phys(domain, bfr->iova & 0xFFFFFFFF); + if (!phy_addr) + return -ENOMEM; + + // TODO: verify phy_addr, cache attribute + bfr->buffer = memremap(phy_addr, bfr->size, MEMREMAP_WB); + if (!bfr->buffer) + return -ENOMEM; + + bfr->is_mapped = true; + dev_info(dcp->dev, "shmem_setup: iova: %lx -> pa: %lx -> iomem: %lx", + (uintptr_t)bfr->iova, (uintptr_t)phy_addr, (uintptr_t)bfr->buffer); + } else { + bfr->buffer = dma_alloc_coherent(dcp->dev, bfr->size, &bfr->iova, GFP_KERNEL); + if (!bfr->buffer) + return -ENOMEM; + + dev_info(dcp->dev, "shmem_setup: iova: %lx, buffer: %lx", + (uintptr_t)bfr->iova, (uintptr_t)bfr->buffer); + } + + return 0; +} + +static void dcp_rtk_shmem_destroy(void *cookie, struct apple_rtkit_shmem *bfr) +{ + struct apple_dcp *dcp = cookie; + + if (bfr->is_mapped) + memunmap(bfr->buffer); + else + dma_free_coherent(dcp->dev, bfr->size, bfr->buffer, bfr->iova); +} + +static struct apple_rtkit_ops rtkit_ops = { + .crashed = dcp_rtk_crashed, + .recv_message = dcp_got_msg, + .shmem_setup = dcp_rtk_shmem_setup, + .shmem_destroy = dcp_rtk_shmem_destroy, +}; + +void dcp_link(struct platform_device *pdev, struct apple_crtc *crtc, + struct apple_connector *connector) +{ + struct apple_dcp *dcp = platform_get_drvdata(pdev); + + dcp->crtc = crtc; + dcp->connector = connector; + + /* Dimensions might already be parsed */ + dcp_set_dimensions(dcp); +} +EXPORT_SYMBOL_GPL(dcp_link); + +static struct platform_device *dcp_get_dev(struct device *dev, const char *name) +{ + struct device_node *node = of_get_child_by_name(dev->of_node, name); + + if (!node) + return NULL; + + return of_find_device_by_node(node); +} + +static int dcp_get_disp_regs(struct apple_dcp *dcp) +{ + struct platform_device *pdev = to_platform_device(dcp->dev); + int count = pdev->num_resources - 1; + int i; + + if (count <= 0 || count > MAX_DISP_REGISTERS) + return -EINVAL; + + for (i = 0; i < count; ++i) { + dcp->disp_registers[i] = + platform_get_resource(pdev, IORESOURCE_MEM, 1 + i); + } + + dcp->nr_disp_registers = count; + return 0; +} + +static int dcp_platform_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct resource *res; + struct apple_dcp *dcp; + dma_addr_t shmem_iova; + int ret; + + dcp = devm_kzalloc(dev, sizeof(*dcp), GFP_KERNEL); + if (!dcp) + return -ENOMEM; + + platform_set_drvdata(pdev, dcp); + dcp->dev = dev; + + ret = dma_set_mask_and_coherent(dev, DMA_BIT_MASK(36)); + if (ret) + return ret; + + res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "coproc"); + if (!res) + return -EINVAL; + + of_platform_default_populate(dev->of_node, NULL, dev); + + dcp->piodma = dcp_get_dev(dev, "piodma"); + if (!dcp->piodma) { + dev_err(dev, "failed to find piodma\n"); + return -ENODEV; + } + + ret = dcp_get_disp_regs(dcp); + if (ret) { + dev_err(dev, "failed to find display registers\n"); + return ret; + } + + dcp->rtk = devm_apple_rtkit_init(dev, dcp, "mbox", 0, &rtkit_ops); + if (IS_ERR(dcp->rtk)) + return dev_err_probe(dev, PTR_ERR(dcp->rtk), + "Failed to intialize RTKit"); + + ret = apple_rtkit_wake(dcp->rtk); + if (ret) + return dev_err_probe(dev, ret, + "Failed to boot RTKit: %d", ret); + + apple_rtkit_start_ep(dcp->rtk, DCP_ENDPOINT); + + dcp->shmem = dma_alloc_coherent(dev, DCP_SHMEM_SIZE, &shmem_iova, + GFP_KERNEL); + + apple_rtkit_send_message(dcp->rtk, DCP_ENDPOINT, + dcpep_set_shmem(shmem_iova), NULL, false); + + return ret; +} + +/* + * We need to shutdown DCP before tearing down the display subsystem. Otherwise + * the DCP will crash and briefly flash a green screen of death. + */ +static void dcp_platform_shutdown(struct platform_device *pdev) +{ + struct apple_dcp *dcp = platform_get_drvdata(pdev); + + struct dcp_set_power_state_req req = { + /* defaults are ok */ + }; + + dcp_set_power_state(dcp, false, &req, NULL, NULL); +} + +static const struct of_device_id of_match[] = { + { .compatible = "apple,dcp" }, + {} +}; +MODULE_DEVICE_TABLE(of, of_match); + +static struct platform_driver apple_platform_driver = { + .probe = dcp_platform_probe, + .shutdown = dcp_platform_shutdown, + .driver = { + .name = "apple-dcp", + .of_match_table = of_match, + }, +}; + +module_platform_driver(apple_platform_driver); + +MODULE_AUTHOR("Alyssa Rosenzweig "); +MODULE_DESCRIPTION("Apple Display Controller DRM driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/gpu/drm/apple/dcp.h b/drivers/gpu/drm/apple/dcp.h new file mode 100644 index 00000000000000..4582fe984c8aa5 --- /dev/null +++ b/drivers/gpu/drm/apple/dcp.h @@ -0,0 +1,44 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* Copyright 2021 Alyssa Rosenzweig */ + +#ifndef __APPLE_DCP_H__ +#define __APPLE_DCP_H__ + +#include +#include "parser.h" + +struct apple_crtc { + struct drm_crtc base; + struct drm_pending_vblank_event *event; + bool vsync_disabled; + + /* Reference to the DCP device owning this CRTC */ + struct platform_device *dcp; +}; + +#define to_apple_crtc(x) container_of(x, struct apple_crtc, base) + +void dcp_hotplug(struct work_struct *work); + +struct apple_connector { + struct drm_connector base; + bool connected; + + struct platform_device *dcp; + + /* Workqueue for sending hotplug events to the associated device */ + struct work_struct hotplug_wq; +}; + +#define to_apple_connector(x) container_of(x, struct apple_connector, base) + +void dcp_link(struct platform_device *pdev, struct apple_crtc *apple, + struct apple_connector *connector); +void dcp_flush(struct drm_crtc *crtc, struct drm_atomic_state *state); +bool dcp_is_initialized(struct platform_device *pdev); +void apple_crtc_vblank(struct apple_crtc *apple); +int dcp_get_modes(struct drm_connector *connector); +int dcp_mode_valid(struct drm_connector *connector, + struct drm_display_mode *mode); + +#endif diff --git a/drivers/gpu/drm/apple/dcpep.h b/drivers/gpu/drm/apple/dcpep.h new file mode 100644 index 00000000000000..6301bf8f8c21d1 --- /dev/null +++ b/drivers/gpu/drm/apple/dcpep.h @@ -0,0 +1,406 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* Copyright 2021 Alyssa Rosenzweig */ + +#ifndef __APPLE_DCPEP_H__ +#define __APPLE_DCPEP_H__ + +/* Endpoint for general DCP traffic (dcpep in macOS) */ +#define DCP_ENDPOINT 0x37 + +/* Fixed size of shared memory between DCP and AP */ +#define DCP_SHMEM_SIZE 0x100000 + +/* DCP message contexts */ +enum dcp_context_id { + /* Callback */ + DCP_CONTEXT_CB = 0, + + /* Command */ + DCP_CONTEXT_CMD = 2, + + /* Asynchronous */ + DCP_CONTEXT_ASYNC = 3, + + /* Out-of-band callback */ + DCP_CONTEXT_OOBCB = 4, + + /* Out-of-band command */ + DCP_CONTEXT_OOBCMD = 6, + + DCP_NUM_CONTEXTS +}; + +static int dcp_tx_offset(enum dcp_context_id id) +{ + switch (id) { + case DCP_CONTEXT_CB: + case DCP_CONTEXT_CMD: return 0x00000; + case DCP_CONTEXT_OOBCB: + case DCP_CONTEXT_OOBCMD: return 0x08000; + default: return -EINVAL; + } +} + +static int dcp_channel_offset(enum dcp_context_id id) +{ + switch (id) { + case DCP_CONTEXT_ASYNC: return 0x40000; + case DCP_CONTEXT_CB: return 0x60000; + case DCP_CONTEXT_OOBCB: return 0x68000; + default: return dcp_tx_offset(id); + } +} + +/* RTKit endpoint message types */ +enum dcpep_type { + /* Set shared memory */ + DCPEP_TYPE_SET_SHMEM = 0, + + /* DCP is initialized */ + DCPEP_TYPE_INITIALIZED = 1, + + /* Remote procedure call */ + DCPEP_TYPE_MESSAGE = 2, +}; + +/* Message */ +#define DCPEP_TYPE_SHIFT (0) +#define DCPEP_TYPE_MASK GENMASK(1, 0) +#define DCPEP_ACK BIT_ULL(6) +#define DCPEP_CONTEXT_SHIFT (8) +#define DCPEP_CONTEXT_MASK GENMASK(11, 8) +#define DCPEP_OFFSET_SHIFT (16) +#define DCPEP_OFFSET_MASK GENMASK(31, 16) +#define DCPEP_LENGTH_SHIFT (32) + +/* Set shmem */ +#define DCPEP_DVA_SHIFT (16) +#define DCPEP_FLAG_SHIFT (4) +#define DCPEP_FLAG_VALUE (4) + +struct dcp_packet_header { + char tag[4]; + u32 in_len; + u32 out_len; +} __packed; + +#define DCP_IS_NULL(ptr) ((ptr) ? 1 : 0) +#define DCP_PACKET_ALIGNMENT (0x40) + +static inline u64 +dcpep_set_shmem(u64 dart_va) +{ + return (DCPEP_TYPE_SET_SHMEM << DCPEP_TYPE_SHIFT) | + (DCPEP_FLAG_VALUE << DCPEP_FLAG_SHIFT) | + (dart_va << DCPEP_DVA_SHIFT); +} + +static inline u64 +dcpep_msg(enum dcp_context_id id, u32 length, u16 offset) +{ + return (DCPEP_TYPE_MESSAGE << DCPEP_TYPE_SHIFT) | + ((u64) id << DCPEP_CONTEXT_SHIFT) | + ((u64) offset << DCPEP_OFFSET_SHIFT) | + ((u64) length << DCPEP_LENGTH_SHIFT); +} + +static inline u64 +dcpep_ack(enum dcp_context_id id) +{ + return dcpep_msg(id, 0, 0) | DCPEP_ACK; +} + +/* Structures used in v12.0 firmware */ + +#define SWAP_SURFACES 4 +#define MAX_PLANES 3 + +struct dcp_iouserclient { + /* Handle for the IOUserClient. macOS sets this to a kernel VA. */ + u64 handle; + u32 unk; + u8 flag1; + u8 flag2; + u8 padding[2]; +} __packed; + +struct dcp_rect { + u32 x; + u32 y; + u32 w; + u32 h; +} __packed; + +/* + * Set in the swap_{enabled,completed} field to remove missing + * layers. Without this flag, the DCP will assume missing layers have + * not changed since the previous frame and will preserve their + * content. + */ +#define DCP_REMOVE_LAYERS BIT(31) + +struct dcp_swap { + u64 ts1; + u64 ts2; + u64 unk_10[6]; + u64 flags1; + u64 flags2; + + u32 swap_id; + + u32 surf_ids[SWAP_SURFACES]; + struct dcp_rect src_rect[SWAP_SURFACES]; + u32 surf_flags[SWAP_SURFACES]; + u32 surf_unk[SWAP_SURFACES]; + struct dcp_rect dst_rect[SWAP_SURFACES]; + u32 swap_enabled; + u32 swap_completed; + + u32 unk_10c; + u8 unk_110[0x1b8]; + u32 unk_2c8; + u8 unk_2cc[0x14]; + u32 unk_2e0; + u8 unk_2e4[0x3c]; +} __packed; + +/* Information describing a plane of a planar compressed surface */ +struct dcp_plane_info { + u32 width; + u32 height; + u32 base; + u32 offset; + u32 stride; + u32 size; + u16 tile_size; + u8 tile_w; + u8 tile_h; + u32 unk[13]; +} __packed; + +struct dcp_component_types { + u8 count; + u8 types[7]; +} __packed; + +/* Information describing a surface */ +struct dcp_surface { + u8 is_tiled; + u8 unk_1; + u8 unk_2; + u32 plane_cnt; + u32 plane_cnt2; + u32 format; /* DCP fourcc */ + u32 unk_f; + u8 xfer_func; + u8 colorspace; + u32 stride; + u16 pix_size; + u8 pel_w; + u8 pel_h; + u32 offset; + u32 width; + u32 height; + u32 buf_size; + u32 unk_2d; + u32 unk_31; + u32 surface_id; + struct dcp_component_types comp_types[MAX_PLANES]; + u64 has_comp; + struct dcp_plane_info planes[MAX_PLANES]; + u64 has_planes; + u32 compression_info[MAX_PLANES][13]; + u64 has_compr_info; + u64 unk_1f5; + u8 padding[7]; +} __packed; + +struct dcp_rt_bandwidth { + u64 unk1; + u64 reg_scratch; + u64 reg_doorbell; + u32 unk2; + u32 doorbell_bit; + u32 padding[7]; +} __packed; + +/* Method calls */ + +enum dcpep_method { + dcpep_late_init_signal, + dcpep_setup_video_limits, + dcpep_set_create_dfb, + dcpep_start_signal, + dcpep_swap_start, + dcpep_swap_submit, + dcpep_set_display_device, + dcpep_set_digital_out_mode, + dcpep_create_default_fb, + dcpep_set_display_refresh_properties, + dcpep_flush_supports_power, + dcpep_set_power_state, + dcpep_first_client_open, + dcpep_update_notify_clients_dcp, + dcpep_num_methods +}; + +struct dcp_method_entry { + const char *name; + char tag[4]; +}; + +/* Prototypes */ + +struct dcp_set_digital_out_mode_req { + u32 color_mode_id; + u32 timing_mode_id; +} __packed; + +struct dcp_map_buf_req { + u64 buffer; + u8 unk; + u8 buf_null; + u8 vaddr_null; + u8 dva_null; +} __packed; + +struct dcp_map_buf_resp { + u64 vaddr; + u64 dva; + u32 ret; +} __packed; + +struct dcp_allocate_buffer_req { + u32 unk0; + u64 size; + u32 unk2; + u8 paddr_null; + u8 dva_null; + u8 dva_size_null; + u8 padding; +} __packed; + +struct dcp_allocate_buffer_resp { + u64 paddr; + u64 dva; + u64 dva_size; + u32 mem_desc_id; +} __packed; + +struct dcp_map_physical_req { + u64 paddr; + u64 size; + u32 flags; + u8 dva_null; + u8 dva_size_null; + u8 padding[2]; +} __packed; + +struct dcp_map_physical_resp { + u64 dva; + u64 dva_size; + u32 mem_desc_id; +} __packed; + +struct dcp_map_reg_req { + char obj[4]; + u32 index; + u32 flags; + u8 addr_null; + u8 length_null; + u8 padding[2]; +} __packed; + +struct dcp_map_reg_resp { + u64 addr; + u64 length; + u32 ret; +} __packed; + +struct dcp_swap_start_req { + u32 swap_id; + struct dcp_iouserclient client; + u8 swap_id_null; + u8 client_null; + u8 padding[2]; +} __packed; + +struct dcp_swap_start_resp { + u32 swap_id; + struct dcp_iouserclient client; + u32 ret; +} __packed; + +struct dcp_swap_submit_req { + struct dcp_swap swap; + struct dcp_surface surf[SWAP_SURFACES]; + u64 surf_iova[SWAP_SURFACES]; + u8 unkbool; + u64 unkdouble; + u32 unkint; + u8 swap_null; + u8 surf_null[SWAP_SURFACES]; + u8 unkoutbool_null; + u8 padding[1]; +} __packed; + +struct dcp_swap_submit_resp { + u8 unkoutbool; + u32 ret; + u8 padding[3]; +} __packed; + +struct dcp_get_uint_prop_req { + char obj[4]; + char key[0x40]; + u64 value; + u8 value_null; + u8 padding[3]; +} __packed; + +struct dcp_get_uint_prop_resp { + u64 value; + u8 ret; + u8 padding[3]; +} __packed; + +struct dcp_set_power_state_req { + u64 unklong; + u8 unkbool; + u8 unkint_null; + u8 padding[2]; +} __packed; + +struct dcp_set_power_state_resp { + u32 unkint; + u32 ret; +} __packed; + +struct dcp_set_dcpav_prop_chunk_req { + char data[0x1000]; + u32 offset; + u32 length; +} __packed; + +struct dcp_set_dcpav_prop_end_req { + char key[0x40]; +} __packed; + +struct dcp_update_notify_clients_dcp { + u32 client_0; + u32 client_1; + u32 client_2; + u32 client_3; + u32 client_4; + u32 client_5; + u32 client_6; + u32 client_7; + u32 client_8; + u32 client_9; + u32 client_a; + u32 client_b; + u32 client_c; + u32 client_d; +} __packed; + +#endif diff --git a/drivers/gpu/drm/apple/dummy-piodma.c b/drivers/gpu/drm/apple/dummy-piodma.c new file mode 100644 index 00000000000000..a84a28b4dc2ae0 --- /dev/null +++ b/drivers/gpu/drm/apple/dummy-piodma.c @@ -0,0 +1,31 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* Copyright 2021 Alyssa Rosenzweig */ + +#include +#include +#include + +static int dcp_piodma_probe(struct platform_device *pdev) +{ + return dma_set_mask_and_coherent(&pdev->dev, DMA_BIT_MASK(36)); +} + +static const struct of_device_id of_match[] = { + { .compatible = "apple,dcp-piodma" }, + {} +}; +MODULE_DEVICE_TABLE(of, of_match); + +static struct platform_driver dcp_piodma_platform_driver = { + .probe = dcp_piodma_probe, + .driver = { + .name = "apple,dcp-piodma", + .of_match_table = of_match, + }, +}; + +module_platform_driver(dcp_piodma_platform_driver); + +MODULE_AUTHOR("Alyssa Rosenzweig "); +MODULE_DESCRIPTION("[HACK] Apple DCP PIODMA shim"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/gpu/drm/apple/parser.c b/drivers/gpu/drm/apple/parser.c new file mode 100644 index 00000000000000..7205179e7785aa --- /dev/null +++ b/drivers/gpu/drm/apple/parser.c @@ -0,0 +1,451 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* Copyright 2021 Alyssa Rosenzweig */ + +#include +#include +#include +#include +#include +#include "parser.h" + +#define DCP_PARSE_HEADER 0xd3 + +enum dcp_parse_type { + DCP_TYPE_DICTIONARY = 1, + DCP_TYPE_ARRAY = 2, + DCP_TYPE_INT64 = 4, + DCP_TYPE_STRING = 9, + DCP_TYPE_BLOB = 10, + DCP_TYPE_BOOL = 11 +}; + +struct dcp_parse_tag { + unsigned int size : 24; + enum dcp_parse_type type : 5; + unsigned int padding : 2; + bool last : 1; +} __packed; + +static void *parse_bytes(struct dcp_parse_ctx *ctx, size_t count) +{ + void *ptr = ctx->blob + ctx->pos; + + if (ctx->pos + count > ctx->len) + return ERR_PTR(-EINVAL); + + ctx->pos += count; + return ptr; +} + +static u32 *parse_u32(struct dcp_parse_ctx *ctx) +{ + return parse_bytes(ctx, sizeof(u32)); +} + +static struct dcp_parse_tag *parse_tag(struct dcp_parse_ctx *ctx) +{ + struct dcp_parse_tag *tag; + + /* Align to 32-bits */ + ctx->pos = round_up(ctx->pos, 4); + + tag = parse_bytes(ctx, sizeof(struct dcp_parse_tag)); + + if (IS_ERR(tag)) + return tag; + + if (tag->padding) + return ERR_PTR(-EINVAL); + + return tag; +} + +static struct dcp_parse_tag *parse_tag_of_type(struct dcp_parse_ctx *ctx, + enum dcp_parse_type type) +{ + struct dcp_parse_tag *tag = parse_tag(ctx); + + if (IS_ERR(tag)) + return tag; + + if (tag->type != type) + return ERR_PTR(-EINVAL); + + return tag; +} + +static int skip(struct dcp_parse_ctx *handle) +{ + struct dcp_parse_tag *tag = parse_tag(handle); + int ret = 0; + int i; + + if (IS_ERR(tag)) + return PTR_ERR(tag); + + switch (tag->type) { + case DCP_TYPE_DICTIONARY: + for (i = 0; i < tag->size; ++i) { + ret |= skip(handle); /* key */ + ret |= skip(handle); /* value */ + } + + return ret; + + case DCP_TYPE_ARRAY: + for (i = 0; i < tag->size; ++i) + ret |= skip(handle); + + return ret; + + case DCP_TYPE_INT64: + handle->pos += sizeof(s64); + return 0; + + case DCP_TYPE_STRING: + case DCP_TYPE_BLOB: + handle->pos += tag->size; + return 0; + + case DCP_TYPE_BOOL: + return 0; + + default: + return -EINVAL; + } +} + +/* Caller must free the result */ +static char *parse_string(struct dcp_parse_ctx *handle) +{ + struct dcp_parse_tag *tag = parse_tag_of_type(handle, DCP_TYPE_STRING); + const char *in; + char *out; + + if (IS_ERR(tag)) + return (void *)tag; + + in = parse_bytes(handle, tag->size); + if (IS_ERR(in)) + return (void *)in; + + out = kmalloc(tag->size + 1, GFP_KERNEL); + + memcpy(out, in, tag->size); + out[tag->size] = '\0'; + return out; +} + +static int parse_int(struct dcp_parse_ctx *handle, s64 *value) +{ + void *tag = parse_tag_of_type(handle, DCP_TYPE_INT64); + s64 *in; + + if (IS_ERR(tag)) + return PTR_ERR(tag); + + in = parse_bytes(handle, sizeof(s64)); + + if (IS_ERR(in)) + return PTR_ERR(in); + + memcpy(value, in, sizeof(*value)); + return 0; +} + +static int parse_bool(struct dcp_parse_ctx *handle, bool *b) +{ + struct dcp_parse_tag *tag = parse_tag_of_type(handle, DCP_TYPE_BOOL); + + if (IS_ERR(tag)) + return PTR_ERR(tag); + + *b = !!tag->size; + return 0; +} + +struct iterator { + struct dcp_parse_ctx *handle; + u32 idx, len; +}; + +static int iterator_begin(struct dcp_parse_ctx *handle, struct iterator *it, + bool dict) +{ + struct dcp_parse_tag *tag; + enum dcp_parse_type type = dict ? DCP_TYPE_DICTIONARY : DCP_TYPE_ARRAY; + + *it = (struct iterator) { + .handle = handle, + .idx = 0 + }; + + tag = parse_tag_of_type(it->handle, type); + if (IS_ERR(tag)) + return PTR_ERR(tag); + + it->len = tag->size; + return 0; +} + +#define dcp_parse_foreach_in_array(handle, it) \ + for (iterator_begin(handle, &it, false); it.idx < it.len; ++it.idx) +#define dcp_parse_foreach_in_dict(handle, it) \ + for (iterator_begin(handle, &it, true); it.idx < it.len; ++it.idx) + +int parse(void *blob, size_t size, struct dcp_parse_ctx *ctx) +{ + u32 *header; + + *ctx = (struct dcp_parse_ctx) { + .blob = blob, + .len = size, + .pos = 0, + }; + + header = parse_u32(ctx); + if (IS_ERR(header)) + return PTR_ERR(header); + + if (*header != DCP_PARSE_HEADER) + return -EINVAL; + + return 0; +} + +struct dimension { + s64 total, front_porch, sync_width, active; + s64 precise_sync_rate; +}; + +static int parse_dimension(struct dcp_parse_ctx *handle, struct dimension *dim) +{ + struct iterator it; + int ret = 0; + + dcp_parse_foreach_in_dict(handle, it) { + char *key = parse_string(it.handle); + + if (IS_ERR(key)) + ret = PTR_ERR(handle); + else if (!strcmp(key, "Active")) + ret = parse_int(it.handle, &dim->active); + else if (!strcmp(key, "Total")) + ret = parse_int(it.handle, &dim->total); + else if (!strcmp(key, "FrontPorch")) + ret = parse_int(it.handle, &dim->front_porch); + else if (!strcmp(key, "SyncWidth")) + ret = parse_int(it.handle, &dim->sync_width); + else if (!strcmp(key, "PreciseSyncRate")) + ret = parse_int(it.handle, &dim->precise_sync_rate); + else + skip(it.handle); + + if (ret) + return ret; + } + + return 0; +} + +static int parse_color_modes(struct dcp_parse_ctx *handle, s64 *best_id) +{ + struct iterator outer_it; + int ret = 0; + s64 best_score = -1; + + *best_id = -1; + + dcp_parse_foreach_in_array(handle, outer_it) { + struct iterator it; + s64 score = -1, id = -1; + + dcp_parse_foreach_in_dict(handle, it) { + char *key = parse_string(it.handle); + + if (IS_ERR(key)) + ret = PTR_ERR(key); + else if (!strcmp(key, "Score")) + ret = parse_int(it.handle, &score); + else if (!strcmp(key, "ID")) + ret = parse_int(it.handle, &id); + else + skip(it.handle); + + if (ret) + return ret; + } + + /* Skip partial entries */ + if (score < 0 || id < 0) + continue; + + if (score > best_score) { + best_score = score; + *best_id = id; + } + } + + return 0; +} + +/* + * Calculate the pixel clock for a mode given the 16:16 fixed-point refresh + * rate. The pixel clock is the refresh rate times the pixel count. DRM + * specifies the clock in kHz. The intermediate result may overflow a u32, so + * use a u64 where required. + */ +static u32 calculate_clock(struct dimension *horiz, struct dimension *vert) +{ + u32 pixels = horiz->total * vert->total; + u64 clock = mul_u32_u32(pixels, vert->precise_sync_rate); + + return DIV_ROUND_CLOSEST_ULL(clock >> 16, 1000); +} + +static int parse_mode(struct dcp_parse_ctx *handle, + struct dcp_display_mode *out, s64 *score, int width_mm, + int height_mm) +{ + int ret = 0; + struct iterator it; + struct dimension horiz, vert; + s64 id = -1; + s64 best_color_mode = -1; + bool is_virtual = false; + struct drm_display_mode *mode = &out->mode; + + dcp_parse_foreach_in_dict(handle, it) { + char *key = parse_string(it.handle); + + if (IS_ERR(key)) + ret = PTR_ERR(key); + else if (!strcmp(key, "HorizontalAttributes")) + ret = parse_dimension(it.handle, &horiz); + else if (!strcmp(key, "VerticalAttributes")) + ret = parse_dimension(it.handle, &vert); + else if (!strcmp(key, "ColorModes")) + ret = parse_color_modes(it.handle, &best_color_mode); + else if (!strcmp(key, "ID")) + ret = parse_int(it.handle, &id); + else if (!strcmp(key, "IsVirtual")) + ret = parse_bool(it.handle, &is_virtual); + else if (!strcmp(key, "Score")) + ret = parse_int(it.handle, score); + else + skip(it.handle); + + if (ret) + return ret; + } + + /* + * We need to skip virtual modes. In some cases, virtual modes are "too + * big" for the monitor and can cause breakage. It is unclear why the + * DCP reports these modes at all. Treat as a recoverable error. + */ + if (is_virtual) + return -EINVAL; + + /* From here we must succeed. Start filling out the mode. */ + *mode = (struct drm_display_mode) { + .type = DRM_MODE_TYPE_DRIVER, + .clock = calculate_clock(&horiz, &vert), + + .vdisplay = vert.active, + .vsync_start = vert.active + vert.front_porch, + .vsync_end = vert.active + vert.front_porch + vert.sync_width, + .vtotal = vert.total, + + .hdisplay = horiz.active, + .hsync_start = horiz.active + horiz.front_porch, + .hsync_end = horiz.active + horiz.front_porch + + horiz.sync_width, + .htotal = horiz.total, + + .width_mm = width_mm, + .height_mm = height_mm, + }; + + drm_mode_set_name(mode); + + out->timing_mode_id = id; + out->color_mode_id = best_color_mode; + + return 0; +} + +struct dcp_display_mode *enumerate_modes(struct dcp_parse_ctx *handle, + unsigned int *count, int width_mm, + int height_mm) +{ + struct iterator it; + int ret; + struct dcp_display_mode *mode, *modes; + struct dcp_display_mode *best_mode = NULL; + s64 score, best_score = -1; + + ret = iterator_begin(handle, &it, false); + + if (ret) + return ERR_PTR(ret); + + /* Start with a worst case allocation */ + modes = kmalloc_array(it.len, sizeof(*modes), GFP_KERNEL); + *count = 0; + + if (!modes) + return ERR_PTR(-ENOMEM); + + for (; it.idx < it.len; ++it.idx) { + mode = &modes[*count]; + ret = parse_mode(it.handle, mode, &score, width_mm, height_mm); + + /* Errors for a single mode are recoverable -- just skip it. */ + if (ret) + continue; + + /* Process a successful mode */ + (*count)++; + + if (score > best_score) { + best_score = score; + best_mode = mode; + } + } + + if (best_mode != NULL) + best_mode->mode.type |= DRM_MODE_TYPE_PREFERRED; + + return modes; +} + +int parse_display_attributes(struct dcp_parse_ctx *handle, int *width_mm, + int *height_mm) +{ + int ret = 0; + struct iterator it; + s64 width_cm = 0, height_cm = 0; + + dcp_parse_foreach_in_dict(handle, it) { + char *key = parse_string(it.handle); + + if (IS_ERR(key)) + ret = PTR_ERR(key); + else if (!strcmp(key, "MaxHorizontalImageSize")) + ret = parse_int(it.handle, &width_cm); + else if (!strcmp(key, "MaxVerticalImageSize")) + ret = parse_int(it.handle, &height_cm); + else + skip(it.handle); + + if (ret) + return ret; + } + + /* 1cm = 10mm */ + *width_mm = 10 * width_cm; + *height_mm = 10 * height_cm; + + return 0; +} diff --git a/drivers/gpu/drm/apple/parser.h b/drivers/gpu/drm/apple/parser.h new file mode 100644 index 00000000000000..afe3ed4700add7 --- /dev/null +++ b/drivers/gpu/drm/apple/parser.h @@ -0,0 +1,32 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* Copyright 2021 Alyssa Rosenzweig */ + +#ifndef __APPLE_DCP_PARSER_H__ +#define __APPLE_DCP_PARSER_H__ + +/* For mode parsing */ +#include + +struct dcp_parse_ctx { + void *blob; + u32 pos, len; +}; + +/* + * Represents a single display mode. These mode objects are populated at + * runtime based on the TimingElements dictionary sent by the DCP. + */ +struct dcp_display_mode { + struct drm_display_mode mode; + u32 color_mode_id; + u32 timing_mode_id; +}; + +int parse(void *blob, size_t size, struct dcp_parse_ctx *ctx); +struct dcp_display_mode *enumerate_modes(struct dcp_parse_ctx *handle, + unsigned int *count, int width_mm, + int height_mm); +int parse_display_attributes(struct dcp_parse_ctx *handle, int *width_mm, + int *height_mm); + +#endif From 9f497ad0fdaf5b9340949b88bfd170ff5037a82a Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Sun, 20 Mar 2022 18:44:00 +0100 Subject: [PATCH 0983/3902] drm: apple: Relicense DCP driver as dual MIT / GPL v2.0 Link: https://oftc.irclog.whitequark.org/asahi-dev/2022-03-20#30747564 Link: https://oftc.irclog.whitequark.org/asahi-dev/2022-03-20#30747570 Signed-off-by: Alyssa Rosenzweig Signed-off-by: Janne Grunau --- drivers/gpu/drm/apple/Kconfig | 2 +- drivers/gpu/drm/apple/Makefile | 2 +- drivers/gpu/drm/apple/apple_drv.c | 4 ++-- drivers/gpu/drm/apple/dcp.c | 4 ++-- drivers/gpu/drm/apple/dcp.h | 2 +- drivers/gpu/drm/apple/dcpep.h | 2 +- drivers/gpu/drm/apple/dummy-piodma.c | 4 ++-- drivers/gpu/drm/apple/parser.c | 2 +- drivers/gpu/drm/apple/parser.h | 2 +- 9 files changed, 12 insertions(+), 12 deletions(-) diff --git a/drivers/gpu/drm/apple/Kconfig b/drivers/gpu/drm/apple/Kconfig index c4db60413948fd..805639cf94d571 100644 --- a/drivers/gpu/drm/apple/Kconfig +++ b/drivers/gpu/drm/apple/Kconfig @@ -1,4 +1,4 @@ -# SPDX-License-Identifier: GPL-2.0-only +# SPDX-License-Identifier: GPL-2.0-only OR MIT config DRM_APPLE tristate "DRM Support for Apple display controllers" depends on DRM && OF && ARM64 diff --git a/drivers/gpu/drm/apple/Makefile b/drivers/gpu/drm/apple/Makefile index db7ef359987d3d..8c758d5720b642 100644 --- a/drivers/gpu/drm/apple/Makefile +++ b/drivers/gpu/drm/apple/Makefile @@ -1,4 +1,4 @@ -# SPDX-License-Identifier: GPL-2.0-only +# SPDX-License-Identifier: GPL-2.0-only OR MIT appledrm-y := apple_drv.o apple_dcp-y := dcp.o parser.o diff --git a/drivers/gpu/drm/apple/apple_drv.c b/drivers/gpu/drm/apple/apple_drv.c index 4f396c8225a29b..0e1ac708d54969 100644 --- a/drivers/gpu/drm/apple/apple_drv.c +++ b/drivers/gpu/drm/apple/apple_drv.c @@ -1,4 +1,4 @@ -// SPDX-License-Identifier: GPL-2.0-only +// SPDX-License-Identifier: GPL-2.0-only OR MIT /* Copyright 2021 Alyssa Rosenzweig */ /* Based on meson driver which is * Copyright (C) 2016 BayLibre, SAS @@ -439,4 +439,4 @@ module_platform_driver(apple_platform_driver); MODULE_AUTHOR("Alyssa Rosenzweig "); MODULE_DESCRIPTION(DRIVER_DESC); -MODULE_LICENSE("GPL v2"); +MODULE_LICENSE("Dual MIT/GPL"); diff --git a/drivers/gpu/drm/apple/dcp.c b/drivers/gpu/drm/apple/dcp.c index e4e026445d8d10..83159c21fbf089 100644 --- a/drivers/gpu/drm/apple/dcp.c +++ b/drivers/gpu/drm/apple/dcp.c @@ -1,4 +1,4 @@ -// SPDX-License-Identifier: GPL-2.0-only +// SPDX-License-Identifier: GPL-2.0-only OR MIT /* Copyright 2021 Alyssa Rosenzweig */ #include @@ -1390,4 +1390,4 @@ module_platform_driver(apple_platform_driver); MODULE_AUTHOR("Alyssa Rosenzweig "); MODULE_DESCRIPTION("Apple Display Controller DRM driver"); -MODULE_LICENSE("GPL v2"); +MODULE_LICENSE("Dual MIT/GPL"); diff --git a/drivers/gpu/drm/apple/dcp.h b/drivers/gpu/drm/apple/dcp.h index 4582fe984c8aa5..794544456df963 100644 --- a/drivers/gpu/drm/apple/dcp.h +++ b/drivers/gpu/drm/apple/dcp.h @@ -1,4 +1,4 @@ -/* SPDX-License-Identifier: GPL-2.0-only */ +// SPDX-License-Identifier: GPL-2.0-only OR MIT /* Copyright 2021 Alyssa Rosenzweig */ #ifndef __APPLE_DCP_H__ diff --git a/drivers/gpu/drm/apple/dcpep.h b/drivers/gpu/drm/apple/dcpep.h index 6301bf8f8c21d1..f3d62d1d5f0328 100644 --- a/drivers/gpu/drm/apple/dcpep.h +++ b/drivers/gpu/drm/apple/dcpep.h @@ -1,4 +1,4 @@ -/* SPDX-License-Identifier: GPL-2.0-only */ +// SPDX-License-Identifier: GPL-2.0-only OR MIT /* Copyright 2021 Alyssa Rosenzweig */ #ifndef __APPLE_DCPEP_H__ diff --git a/drivers/gpu/drm/apple/dummy-piodma.c b/drivers/gpu/drm/apple/dummy-piodma.c index a84a28b4dc2ae0..3d4454df4a25da 100644 --- a/drivers/gpu/drm/apple/dummy-piodma.c +++ b/drivers/gpu/drm/apple/dummy-piodma.c @@ -1,4 +1,4 @@ -// SPDX-License-Identifier: GPL-2.0-only +// SPDX-License-Identifier: GPL-2.0-only OR MIT /* Copyright 2021 Alyssa Rosenzweig */ #include @@ -28,4 +28,4 @@ module_platform_driver(dcp_piodma_platform_driver); MODULE_AUTHOR("Alyssa Rosenzweig "); MODULE_DESCRIPTION("[HACK] Apple DCP PIODMA shim"); -MODULE_LICENSE("GPL v2"); +MODULE_LICENSE("Dual MIT/GPL"); diff --git a/drivers/gpu/drm/apple/parser.c b/drivers/gpu/drm/apple/parser.c index 7205179e7785aa..f0cd38c2a81048 100644 --- a/drivers/gpu/drm/apple/parser.c +++ b/drivers/gpu/drm/apple/parser.c @@ -1,4 +1,4 @@ -// SPDX-License-Identifier: GPL-2.0-only +// SPDX-License-Identifier: GPL-2.0-only OR MIT /* Copyright 2021 Alyssa Rosenzweig */ #include diff --git a/drivers/gpu/drm/apple/parser.h b/drivers/gpu/drm/apple/parser.h index afe3ed4700add7..66a675079dc164 100644 --- a/drivers/gpu/drm/apple/parser.h +++ b/drivers/gpu/drm/apple/parser.h @@ -1,4 +1,4 @@ -/* SPDX-License-Identifier: GPL-2.0-only */ +// SPDX-License-Identifier: GPL-2.0-only OR MIT /* Copyright 2021 Alyssa Rosenzweig */ #ifndef __APPLE_DCP_PARSER_H__ From cffb7bd1637a5d15b3cf19bcf1f806d40121588a Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Mon, 25 Jul 2022 21:36:38 +0200 Subject: [PATCH 0984/3902] drm/apple: Start coprocessor on probe Signed-off-by: Janne Grunau --- drivers/gpu/drm/apple/dcp.c | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/drivers/gpu/drm/apple/dcp.c b/drivers/gpu/drm/apple/dcp.c index 83159c21fbf089..2fc57b1bb39f32 100644 --- a/drivers/gpu/drm/apple/dcp.c +++ b/drivers/gpu/drm/apple/dcp.c @@ -20,6 +20,9 @@ struct apple_dcp; +#define APPLE_DCP_COPROC_CPU_CONTROL 0x44 +#define APPLE_DCP_COPROC_CPU_CONTROL_RUN BIT(4) + /* Register defines used in bandwidth setup structure */ #define REG_SCRATCH (0x14) #define REG_DOORBELL (0x0) @@ -69,6 +72,9 @@ struct apple_dcp { /* DCP shared memory */ void *shmem; + /* Coprocessor control register */ + void __iomem *coproc_reg; + /* Display registers mappable to the DCP */ struct resource *disp_registers[MAX_DISP_REGISTERS]; unsigned int nr_disp_registers; @@ -1301,9 +1307,9 @@ static int dcp_get_disp_regs(struct apple_dcp *dcp) static int dcp_platform_probe(struct platform_device *pdev) { struct device *dev = &pdev->dev; - struct resource *res; struct apple_dcp *dcp; dma_addr_t shmem_iova; + u32 cpu_ctrl; int ret; dcp = devm_kzalloc(dev, sizeof(*dcp), GFP_KERNEL); @@ -1317,9 +1323,9 @@ static int dcp_platform_probe(struct platform_device *pdev) if (ret) return ret; - res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "coproc"); - if (!res) - return -EINVAL; + dcp->coproc_reg = devm_platform_ioremap_resource_byname(pdev, "coproc"); + if (IS_ERR(dcp->coproc_reg)) + return PTR_ERR(dcp->coproc_reg); of_platform_default_populate(dev->of_node, NULL, dev); @@ -1335,6 +1341,10 @@ static int dcp_platform_probe(struct platform_device *pdev) return ret; } + cpu_ctrl = readl_relaxed(dcp->coproc_reg + APPLE_DCP_COPROC_CPU_CONTROL); + writel_relaxed(cpu_ctrl | APPLE_DCP_COPROC_CPU_CONTROL_RUN, + dcp->coproc_reg + APPLE_DCP_COPROC_CPU_CONTROL); + dcp->rtk = devm_apple_rtkit_init(dev, dcp, "mbox", 0, &rtkit_ops); if (IS_ERR(dcp->rtk)) return dev_err_probe(dev, PTR_ERR(dcp->rtk), From 60cfbb186aaadd7b9bc670bc61513841b48c9494 Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Sat, 15 Jan 2022 18:29:55 +0100 Subject: [PATCH 0985/3902] HACK: drm/apple: avoid DCP swaps without attached surfaces Xorg startup with modesetting driver triggers this. Move vblank signalling to dcp to avoid a circular dependency between apple_drv and dcp. Signed-off-by: Janne Grunau --- drivers/gpu/drm/apple/apple_drv.c | 18 ---------- drivers/gpu/drm/apple/dcp.c | 60 +++++++++++++++++++++++++++++-- 2 files changed, 57 insertions(+), 21 deletions(-) diff --git a/drivers/gpu/drm/apple/apple_drv.c b/drivers/gpu/drm/apple/apple_drv.c index 0e1ac708d54969..2c26636d76390e 100644 --- a/drivers/gpu/drm/apple/apple_drv.c +++ b/drivers/gpu/drm/apple/apple_drv.c @@ -210,24 +210,6 @@ static void apple_crtc_atomic_begin(struct drm_crtc *crtc, } } -void apple_crtc_vblank(struct apple_crtc *crtc) -{ - unsigned long flags; - - if (crtc->vsync_disabled) - return; - - drm_crtc_handle_vblank(&crtc->base); - - spin_lock_irqsave(&crtc->base.dev->event_lock, flags); - if (crtc->event) { - drm_crtc_send_vblank_event(&crtc->base, crtc->event); - drm_crtc_vblank_put(&crtc->base); - crtc->event = NULL; - } - spin_unlock_irqrestore(&crtc->base.dev->event_lock, flags); -} - static const struct drm_crtc_funcs apple_crtc_funcs = { .atomic_destroy_state = drm_atomic_helper_crtc_destroy_state, .atomic_duplicate_state = drm_atomic_helper_crtc_duplicate_state, diff --git a/drivers/gpu/drm/apple/dcp.c b/drivers/gpu/drm/apple/dcp.c index 2fc57b1bb39f32..def2ae6ca4047d 100644 --- a/drivers/gpu/drm/apple/dcp.c +++ b/drivers/gpu/drm/apple/dcp.c @@ -4,6 +4,7 @@ #include #include #include +#include #include #include #include @@ -13,6 +14,7 @@ #include #include #include +#include #include "dcpep.h" #include "dcp.h" @@ -107,6 +109,9 @@ struct apple_dcp { /* Attributes of the connected display */ int width_mm, height_mm; + + /* Workqueue for sending vblank events when a dcp swap is not possible */ + struct work_struct vblank_wq; }; /* @@ -363,9 +368,28 @@ static u32 dcpep_cb_zero(struct apple_dcp *dcp) return 0; } +/* HACK: moved here to avoid circular dependency between apple_drv and dcp */ +void dcp_drm_crtc_vblank(struct apple_crtc *crtc) +{ + unsigned long flags; + + if (crtc->vsync_disabled) + return; + + drm_crtc_handle_vblank(&crtc->base); + + spin_lock_irqsave(&crtc->base.dev->event_lock, flags); + if (crtc->event) { + drm_crtc_send_vblank_event(&crtc->base, crtc->event); + drm_crtc_vblank_put(&crtc->base); + crtc->event = NULL; + } + spin_unlock_irqrestore(&crtc->base.dev->event_lock, flags); +} + static void dcpep_cb_swap_complete(struct apple_dcp *dcp) { - apple_crtc_vblank(dcp->crtc); + dcp_drm_crtc_vblank(dcp->crtc); } static struct dcp_get_uint_prop_resp @@ -731,6 +755,21 @@ static void dcpep_cb_hotplug(struct apple_dcp *dcp, u64 *connected) } } +/* + * Helper to send a DRM vblank event. We do not know how call swap_submit_dcp + * without surfaces. To avoid timeouts in drm_atomic_helper_wait_for_vblanks + * send a vblank event via a workqueue. + */ +static void dcp_delayed_vblank(struct work_struct *work) +{ + struct apple_dcp *dcp; + + dcp = container_of(work, struct apple_dcp, vblank_wq); + mdelay(5); + dcp_drm_crtc_vblank(dcp->crtc); +} + + #define DCPEP_MAX_CB (1000) /* @@ -943,7 +982,7 @@ static void dcp_swapped(struct apple_dcp *dcp, void *data, void *cookie) if (resp->ret) { dev_err(dcp->dev, "swap failed! status %u\n", resp->ret); - apple_crtc_vblank(dcp->crtc); + dcp_drm_crtc_vblank(dcp->crtc); } } @@ -1061,12 +1100,16 @@ void dcp_flush(struct drm_crtc *crtc, struct drm_atomic_state *state) struct drm_crtc_state *crtc_state; struct dcp_swap_submit_req *req = &dcp->swap; int l; + int has_surface = 0; crtc_state = drm_atomic_get_new_crtc_state(state, crtc); if (WARN(dcp_channel_busy(&dcp->ch_cmd), "unexpected busy channel") || WARN(!dcp->connector->connected, "can't flush if disconnected")) { - apple_crtc_vblank(dcp->crtc); + /* HACK: issue a delayed vblank event to avoid timeouts in + * drm_atomic_helper_wait_for_vblanks(). + */ + schedule_work(&dcp->vblank_wq); return; } @@ -1090,6 +1133,7 @@ void dcp_flush(struct drm_crtc *crtc, struct drm_atomic_state *state) continue; } req->surf_null[l] = false; + has_surface = 1; // XXX: awful hack! race condition between a framebuffer unbind // getting swapped out and GEM unreferencing a framebuffer. If @@ -1148,6 +1192,14 @@ void dcp_flush(struct drm_crtc *crtc, struct drm_atomic_state *state) dcp->valid_mode = true; dcp_set_display_device(dcp, false, &handle, dcp_modeset, NULL); + } + else if (!has_surface) { + dev_warn(dcp->dev, "can't flush without surfaces, vsync:%d", dcp->crtc->vsync_disabled); + /* HACK: issue a delayed vblank event to avoid timeouts in + * drm_atomic_helper_wait_for_vblanks(). It's currently unkown + * if and how DCP supports swaps without attached surfaces. + */ + schedule_work(&dcp->vblank_wq); } else do_swap(dcp, NULL, NULL); } @@ -1341,6 +1393,8 @@ static int dcp_platform_probe(struct platform_device *pdev) return ret; } + INIT_WORK(&dcp->vblank_wq, dcp_delayed_vblank); + cpu_ctrl = readl_relaxed(dcp->coproc_reg + APPLE_DCP_COPROC_CPU_CONTROL); writel_relaxed(cpu_ctrl | APPLE_DCP_COPROC_CPU_CONTROL_RUN, dcp->coproc_reg + APPLE_DCP_COPROC_CPU_CONTROL); From 042b8a8985556e13142b2fdc75567bdc28a6d276 Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Sun, 31 Jul 2022 18:34:58 +0200 Subject: [PATCH 0986/3902] drm/apple: Use a device tree defined clock for dcpep_cb_get_frequency Frequency differs between M1 and M1 Pro/Max/Ultra. Signed-off-by: Janne Grunau --- drivers/gpu/drm/apple/dcp.c | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/drivers/gpu/drm/apple/dcp.c b/drivers/gpu/drm/apple/dcp.c index def2ae6ca4047d..8b542950dcaed2 100644 --- a/drivers/gpu/drm/apple/dcp.c +++ b/drivers/gpu/drm/apple/dcp.c @@ -1,6 +1,7 @@ // SPDX-License-Identifier: GPL-2.0-only OR MIT /* Copyright 2021 Alyssa Rosenzweig */ +#include #include #include #include @@ -71,6 +72,9 @@ struct apple_dcp { /* DCP has crashed */ bool crashed; + /* clock rate request by dcp in */ + struct clk *clk; + /* DCP shared memory */ void *shmem; @@ -511,8 +515,7 @@ dcpep_cb_map_physical(struct apple_dcp *dcp, struct dcp_map_physical_req *req) static u64 dcpep_cb_get_frequency(struct apple_dcp *dcp) { - /* Pixel clock frequency in Hz (compare: 4K@60 VGA clock 533.250 MHz) */ - return 533333328; + return clk_get_rate(dcp->clk); } static struct dcp_map_reg_resp @@ -1393,6 +1396,10 @@ static int dcp_platform_probe(struct platform_device *pdev) return ret; } + dcp->clk = devm_clk_get(dev, NULL); + if (IS_ERR(dcp->clk)) + return dev_err_probe(dev, PTR_ERR(dcp->clk), "Unable to find clock\n"); + INIT_WORK(&dcp->vblank_wq, dcp_delayed_vblank); cpu_ctrl = readl_relaxed(dcp->coproc_reg + APPLE_DCP_COPROC_CPU_CONTROL); From 2c679b3a98e5e298147d595efa9e771bc383d873 Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Sat, 12 Mar 2022 11:40:32 +0100 Subject: [PATCH 0987/3902] drm/apple: Fix rt_bandwidth for t600x Signed-off-by: Janne Grunau --- drivers/gpu/drm/apple/dcp.c | 26 ++++++++++++++++++++------ 1 file changed, 20 insertions(+), 6 deletions(-) diff --git a/drivers/gpu/drm/apple/dcp.c b/drivers/gpu/drm/apple/dcp.c index 8b542950dcaed2..4e2bfa44992fc4 100644 --- a/drivers/gpu/drm/apple/dcp.c +++ b/drivers/gpu/drm/apple/dcp.c @@ -28,6 +28,7 @@ struct apple_dcp; /* Register defines used in bandwidth setup structure */ #define REG_SCRATCH (0x14) +#define REG_SCRATCH_T600X (0x988) #define REG_DOORBELL (0x0) #define REG_DOORBELL_BIT (2) @@ -700,13 +701,26 @@ static bool dcpep_cb_boot_1(struct apple_dcp *dcp, void *out, void *in) static struct dcp_rt_bandwidth dcpep_cb_rt_bandwidth(struct apple_dcp *dcp) { - return (struct dcp_rt_bandwidth) { - .reg_scratch = dcp->disp_registers[5]->start + REG_SCRATCH, - .reg_doorbell = dcp->disp_registers[6]->start + REG_DOORBELL, - .doorbell_bit = REG_DOORBELL_BIT, + if (dcp->disp_registers[5] && dcp->disp_registers[6]) + return (struct dcp_rt_bandwidth) { + .reg_scratch = dcp->disp_registers[5]->start + REG_SCRATCH, + .reg_doorbell = dcp->disp_registers[6]->start + REG_DOORBELL, + .doorbell_bit = REG_DOORBELL_BIT, - .padding[3] = 0x4, // XXX: required by 11.x firmware - }; + .padding[3] = 0x4, // XXX: required by 11.x firmware + }; + else if (dcp->disp_registers[4]) + return (struct dcp_rt_bandwidth) { + .reg_scratch = dcp->disp_registers[4]->start + REG_SCRATCH_T600X, + .reg_doorbell = 0, + .doorbell_bit = 0, + }; + else + return (struct dcp_rt_bandwidth) { + .reg_scratch = 0, + .reg_doorbell = 0, + .doorbell_bit = 0, + }; } /* Callback to get the current time as milliseconds since the UNIX epoch */ From 8bfc95ac54ddf66101c42383e84eb05e9858b9e5 Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Sat, 12 Mar 2022 11:43:04 +0100 Subject: [PATCH 0988/3902] drm/apple: Add nop sr_set_uint_prop callback for t600x-dcp Signed-off-by: Janne Grunau --- drivers/gpu/drm/apple/dcp.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/gpu/drm/apple/dcp.c b/drivers/gpu/drm/apple/dcp.c index 4e2bfa44992fc4..a0df724737c2cc 100644 --- a/drivers/gpu/drm/apple/dcp.c +++ b/drivers/gpu/drm/apple/dcp.c @@ -891,6 +891,7 @@ bool (*const dcpep_cb_handlers[DCPEP_MAX_CB])(struct apple_dcp *, void *, void * [211] = trampoline_nop, /* update_backlight_factor_prop */ [300] = trampoline_nop, /* pr_publish */ [401] = trampoline_get_uint_prop, + [404] = trampoline_nop, /* sr_set_uint_prop */ [406] = trampoline_nop, /* set_fx_prop */ [408] = trampoline_get_frequency, [411] = trampoline_map_reg, From 74b4f8ad292f04683216d6b28c930959505153a7 Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Sat, 12 Mar 2022 11:46:11 +0100 Subject: [PATCH 0989/3902] drm/apple: Reference only swapped out framebuffers The framebuffer can be unreferenced by GEM while the display controller is still using it for scanout resulting in IOVA faults and crashed dcp. dcp has to hold a reference until the swap is complete. Signed-off-by: Janne Grunau --- drivers/gpu/drm/apple/dcp.c | 48 +++++++++++++++++++++++++++++++------ 1 file changed, 41 insertions(+), 7 deletions(-) diff --git a/drivers/gpu/drm/apple/dcp.c b/drivers/gpu/drm/apple/dcp.c index a0df724737c2cc..9d5514415a79d7 100644 --- a/drivers/gpu/drm/apple/dcp.c +++ b/drivers/gpu/drm/apple/dcp.c @@ -117,6 +117,16 @@ struct apple_dcp { /* Workqueue for sending vblank events when a dcp swap is not possible */ struct work_struct vblank_wq; + + /* List of referenced drm_framebuffers which can be unreferenced + * on the next successfully completed swap. + */ + struct list_head swapped_out_fbs; +}; + +struct dcp_fb_reference { + struct list_head head; + struct drm_framebuffer *fb; }; /* @@ -1001,6 +1011,17 @@ static void dcp_swapped(struct apple_dcp *dcp, void *data, void *cookie) if (resp->ret) { dev_err(dcp->dev, "swap failed! status %u\n", resp->ret); dcp_drm_crtc_vblank(dcp->crtc); + return; + } + + while (!list_empty(&dcp->swapped_out_fbs)) { + struct dcp_fb_reference *entry; + entry = list_first_entry(&dcp->swapped_out_fbs, + struct dcp_fb_reference, head); + if (entry->fb) + drm_framebuffer_put(entry->fb); + list_del(&entry->head); + kfree(entry); } } @@ -1144,6 +1165,24 @@ void dcp_flush(struct drm_crtc *crtc, struct drm_atomic_state *state) req->swap.swap_enabled |= BIT(l); + if (old_state->fb && fb != old_state->fb) { + /* + * Race condition between a framebuffer unbind getting + * swapped out and GEM unreferencing a framebuffer. If + * we lose the race, the display gets IOVA faults and + * the DCP crashes. We need to extend the lifetime of + * the drm_framebuffer (and hence the GEM object) until + * after we get a swap complete for the swap unbinding + * it. + */ + struct dcp_fb_reference *entry = kzalloc(sizeof(*entry), GFP_KERNEL); + if (entry) { + entry->fb = old_state->fb; + list_add_tail(&entry->head, &dcp->swapped_out_fbs); + } + drm_framebuffer_get(old_state->fb); + } + if (!new_state->fb) { if (old_state->fb) req->swap.swap_enabled |= DCP_REMOVE_LAYERS; @@ -1153,13 +1192,6 @@ void dcp_flush(struct drm_crtc *crtc, struct drm_atomic_state *state) req->surf_null[l] = false; has_surface = 1; - // XXX: awful hack! race condition between a framebuffer unbind - // getting swapped out and GEM unreferencing a framebuffer. If - // we lose the race, the display gets IOVA faults and the DCP - // crashes. We need to extend the lifetime of the - // drm_framebuffer (and hence the GEM object) until after we - // get a swap complete for the swap unbinding it. - drm_framebuffer_get(fb); drm_rect_fp_to_int(&src_rect, &new_state->src); @@ -1417,6 +1449,8 @@ static int dcp_platform_probe(struct platform_device *pdev) INIT_WORK(&dcp->vblank_wq, dcp_delayed_vblank); + dcp->swapped_out_fbs = (struct list_head)LIST_HEAD_INIT(dcp->swapped_out_fbs); + cpu_ctrl = readl_relaxed(dcp->coproc_reg + APPLE_DCP_COPROC_CPU_CONTROL); writel_relaxed(cpu_ctrl | APPLE_DCP_COPROC_CPU_CONTROL_RUN, dcp->coproc_reg + APPLE_DCP_COPROC_CPU_CONTROL); From 4768fd24354f1c1c915e55044e61fb9fedecc177 Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Sat, 12 Mar 2022 12:48:34 +0100 Subject: [PATCH 0990/3902] drm/apple: Use "apple,asc-dram-mask" for rtkit iovas Signed-off-by: Janne Grunau --- drivers/gpu/drm/apple/dcp.c | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/drivers/gpu/drm/apple/dcp.c b/drivers/gpu/drm/apple/dcp.c index 9d5514415a79d7..abf103e2967e7a 100644 --- a/drivers/gpu/drm/apple/dcp.c +++ b/drivers/gpu/drm/apple/dcp.c @@ -76,6 +76,9 @@ struct apple_dcp { /* clock rate request by dcp in */ struct clk *clk; + /* mask for DCP IO virtual addresses shared over rtkit */ + u64 asc_dram_mask; + /* DCP shared memory */ void *shmem; @@ -481,6 +484,7 @@ dcpep_cb_allocate_buffer(struct apple_dcp *dcp, struct dcp_allocate_buffer_req * dma_get_sgtable(dcp->dev, &dcp->mappings[resp.mem_desc_id], buf, resp.dva, resp.dva_size); + resp.dva |= dcp->asc_dram_mask; return resp; } @@ -520,7 +524,7 @@ dcpep_cb_map_physical(struct apple_dcp *dcp, struct dcp_map_physical_req *req) .dva_size = size, .mem_desc_id = ++dcp->nr_mappings, .dva = dma_map_resource(dcp->dev, req->paddr, size, - DMA_BIDIRECTIONAL, 0), + DMA_BIDIRECTIONAL, 0) | dcp->asc_dram_mask, }; } @@ -1324,7 +1328,7 @@ static int dcp_rtk_shmem_setup(void *cookie, struct apple_rtkit_shmem *bfr) return -ENOMEM; // TODO: get map from device-tree - phy_addr = iommu_iova_to_phys(domain, bfr->iova & 0xFFFFFFFF); + phy_addr = iommu_iova_to_phys(domain, bfr->iova & ~dcp->asc_dram_mask); if (!phy_addr) return -ENOMEM; @@ -1341,6 +1345,8 @@ static int dcp_rtk_shmem_setup(void *cookie, struct apple_rtkit_shmem *bfr) if (!bfr->buffer) return -ENOMEM; + bfr->iova |= dcp->asc_dram_mask; + dev_info(dcp->dev, "shmem_setup: iova: %lx, buffer: %lx", (uintptr_t)bfr->iova, (uintptr_t)bfr->buffer); } @@ -1355,7 +1361,7 @@ static void dcp_rtk_shmem_destroy(void *cookie, struct apple_rtkit_shmem *bfr) if (bfr->is_mapped) memunmap(bfr->buffer); else - dma_free_coherent(dcp->dev, bfr->size, bfr->buffer, bfr->iova); + dma_free_coherent(dcp->dev, bfr->size, bfr->buffer, bfr->iova & ~dcp->asc_dram_mask); } static struct apple_rtkit_ops rtkit_ops = { @@ -1447,6 +1453,12 @@ static int dcp_platform_probe(struct platform_device *pdev) if (IS_ERR(dcp->clk)) return dev_err_probe(dev, PTR_ERR(dcp->clk), "Unable to find clock\n"); + ret = of_property_read_u64(dev->of_node, "apple,asc-dram-mask", + &dcp->asc_dram_mask); + if (ret) + dev_warn(dev, "failed read 'apple,asc-dram-mask': %d\n", ret); + dev_dbg(dev, "'apple,asc-dram-mask': 0x%011llx\n", dcp->asc_dram_mask); + INIT_WORK(&dcp->vblank_wq, dcp_delayed_vblank); dcp->swapped_out_fbs = (struct list_head)LIST_HEAD_INIT(dcp->swapped_out_fbs); @@ -1470,6 +1482,7 @@ static int dcp_platform_probe(struct platform_device *pdev) dcp->shmem = dma_alloc_coherent(dev, DCP_SHMEM_SIZE, &shmem_iova, GFP_KERNEL); + shmem_iova |= dcp->asc_dram_mask; apple_rtkit_send_message(dcp->rtk, DCP_ENDPOINT, dcpep_set_shmem(shmem_iova), NULL, false); From 7a25bd3e023a8ed583f22fa956eff607f291a8cc Mon Sep 17 00:00:00 2001 From: Alyssa Rosenzweig Date: Tue, 22 Mar 2022 16:24:53 -0400 Subject: [PATCH 0991/3902] drm/apple: Implement suspend/resume for DCP Use the firmware setPowerState callback. Signed-off-by: Alyssa Rosenzweig --- drivers/gpu/drm/apple/dcp.c | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/drivers/gpu/drm/apple/dcp.c b/drivers/gpu/drm/apple/dcp.c index abf103e2967e7a..6cde16fa4bef25 100644 --- a/drivers/gpu/drm/apple/dcp.c +++ b/drivers/gpu/drm/apple/dcp.c @@ -1501,6 +1501,10 @@ static void dcp_platform_shutdown(struct platform_device *pdev) /* defaults are ok */ }; + /* We're going down */ + dcp->active = false; + dcp->valid_mode = false; + dcp_set_power_state(dcp, false, &req, NULL, NULL); } @@ -1510,12 +1514,41 @@ static const struct of_device_id of_match[] = { }; MODULE_DEVICE_TABLE(of, of_match); +#ifdef CONFIG_PM_SLEEP +/* + * We don't hold any useful persistent state, so for suspend/resume it suffices + * to power off/on the entire DCP. The firmware will sort out the details for + * us. + */ +static int dcp_suspend(struct device *dev) +{ + dcp_platform_shutdown(to_platform_device(dev)); + return 0; +} + +static int dcp_resume(struct device *dev) +{ + struct apple_dcp *dcp = platform_get_drvdata(to_platform_device(dev)); + + dcp_start_signal(dcp, false, dcp_started, NULL); + return 0; +} + +static const struct dev_pm_ops dcp_pm_ops = { + .suspend = dcp_suspend, + .resume = dcp_resume, +}; +#endif + static struct platform_driver apple_platform_driver = { .probe = dcp_platform_probe, .shutdown = dcp_platform_shutdown, .driver = { .name = "apple-dcp", .of_match_table = of_match, +#ifdef CONFIG_PM_SLEEP + .pm = &dcp_pm_ops, +#endif }, }; From 82523577be485b80aab77022ce3a7ff7c08fa19a Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Thu, 14 Apr 2022 18:57:20 +0200 Subject: [PATCH 0992/3902] drm/apple: dcp: fix TRAMPOLINE_IN macro fixup! WIP: drm/apple: Add DCP display driver Signed-off-by: Janne Grunau --- drivers/gpu/drm/apple/dcp.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/gpu/drm/apple/dcp.c b/drivers/gpu/drm/apple/dcp.c index 6cde16fa4bef25..125f4f059fdf32 100644 --- a/drivers/gpu/drm/apple/dcp.c +++ b/drivers/gpu/drm/apple/dcp.c @@ -817,11 +817,11 @@ static void dcp_delayed_vblank(struct work_struct *work) } #define TRAMPOLINE_IN(func, handler, T_in) \ - typedef void (*callback_##name)(struct apple_dcp *, T_in *); \ + typedef void (*callback_##handler)(struct apple_dcp *, T_in *); \ \ static bool func(struct apple_dcp *dcp, void *out, void *in) \ { \ - callback_##name cb = handler; \ + callback_##handler cb = handler; \ \ dev_dbg(dcp->dev, "received callback %s\n", #handler); \ cb(dcp, in); \ From 6f8ba98ac2d9a2e2ea54e6c39f52740cfe850da7 Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Sun, 31 Jul 2022 18:02:23 +0200 Subject: [PATCH 0993/3902] drm/apple: Switch to nonblocking commit handling The swap completes only after the async reply from DCP. Uses drm_atomic_helper_wait_for_flip_done instead of drm_atomic_helper_wait_for_vblanks. This should allow ius to get rid of the scheduled fake vblanks. Signed-off-by: Janne Grunau --- drivers/gpu/drm/apple/apple_drv.c | 23 ++++++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/drivers/gpu/drm/apple/apple_drv.c b/drivers/gpu/drm/apple/apple_drv.c index 2c26636d76390e..94b1fc3a09fe67 100644 --- a/drivers/gpu/drm/apple/apple_drv.c +++ b/drivers/gpu/drm/apple/apple_drv.c @@ -210,6 +210,27 @@ static void apple_crtc_atomic_begin(struct drm_crtc *crtc, } } +static void dcp_atomic_commit_tail(struct drm_atomic_state *old_state) +{ + struct drm_device *dev = old_state->dev; + + drm_atomic_helper_commit_modeset_disables(dev, old_state); + + drm_atomic_helper_commit_modeset_enables(dev, old_state); + + drm_atomic_helper_commit_planes(dev, old_state, + DRM_PLANE_COMMIT_ACTIVE_ONLY); + + drm_atomic_helper_fake_vblank(old_state); + + drm_atomic_helper_commit_hw_done(old_state); + + drm_atomic_helper_wait_for_flip_done(dev, old_state); + + drm_atomic_helper_cleanup_planes(dev, old_state); +} + + static const struct drm_crtc_funcs apple_crtc_funcs = { .atomic_destroy_state = drm_atomic_helper_crtc_destroy_state, .atomic_duplicate_state = drm_atomic_helper_crtc_duplicate_state, @@ -228,7 +249,7 @@ static const struct drm_mode_config_funcs apple_mode_config_funcs = { }; static const struct drm_mode_config_helper_funcs apple_mode_config_helpers = { - .atomic_commit_tail = drm_atomic_helper_commit_tail_rpm, + .atomic_commit_tail = dcp_atomic_commit_tail, }; static const struct drm_connector_funcs apple_connector_funcs = { From 762df4cf0e77b0355550e60f3c0765d2b8370764 Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Sun, 31 Jul 2022 18:15:30 +0200 Subject: [PATCH 0994/3902] drm/apple: Log callbacks with their tag as debug output Mostly for the generic callbacks nop, true, false. Signed-off-by: Janne Grunau --- drivers/gpu/drm/apple/dcp.c | 23 ++++++++++++----------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/drivers/gpu/drm/apple/dcp.c b/drivers/gpu/drm/apple/dcp.c index 125f4f059fdf32..1ab7bef174512c 100644 --- a/drivers/gpu/drm/apple/dcp.c +++ b/drivers/gpu/drm/apple/dcp.c @@ -707,8 +707,9 @@ static void boot_1_5(struct apple_dcp *dcp, void *out, void *cookie) } /* Use special function signature to defer the ACK */ -static bool dcpep_cb_boot_1(struct apple_dcp *dcp, void *out, void *in) +static bool dcpep_cb_boot_1(struct apple_dcp *dcp, int tag, void *out, void *in) { + dev_dbg(dcp->dev, "Callback D%03d %s\n", tag, __func__); dcp_set_create_dfb(dcp, false, boot_1_5, NULL); return false; } @@ -809,9 +810,9 @@ static void dcp_delayed_vblank(struct work_struct *work) */ #define TRAMPOLINE_VOID(func, handler) \ - static bool func(struct apple_dcp *dcp, void *out, void *in) \ + static bool func(struct apple_dcp *dcp, int tag, void *out, void *in) \ { \ - dev_dbg(dcp->dev, "received callback %s\n", #handler); \ + dev_dbg(dcp->dev, "Callback D%03d %s\n", tag, #handler); \ handler(dcp); \ return true; \ } @@ -819,11 +820,11 @@ static void dcp_delayed_vblank(struct work_struct *work) #define TRAMPOLINE_IN(func, handler, T_in) \ typedef void (*callback_##handler)(struct apple_dcp *, T_in *); \ \ - static bool func(struct apple_dcp *dcp, void *out, void *in) \ + static bool func(struct apple_dcp *dcp, int tag, void *out, void *in) \ { \ callback_##handler cb = handler; \ \ - dev_dbg(dcp->dev, "received callback %s\n", #handler); \ + dev_dbg(dcp->dev, "Callback D%03d %s\n", tag, #handler); \ cb(dcp, in); \ return true; \ } @@ -831,22 +832,22 @@ static void dcp_delayed_vblank(struct work_struct *work) #define TRAMPOLINE_INOUT(func, handler, T_in, T_out) \ typedef T_out (*callback_##handler)(struct apple_dcp *, T_in *); \ \ - static bool func(struct apple_dcp *dcp, void *out, void *in) \ + static bool func(struct apple_dcp *dcp, int tag, void *out, void *in) \ { \ T_out *typed_out = out; \ callback_##handler cb = handler; \ \ - dev_dbg(dcp->dev, "received callback %s\n", #handler); \ + dev_dbg(dcp->dev, "Callback D%03d %s\n", tag, #handler); \ *typed_out = cb(dcp, in); \ return true; \ } #define TRAMPOLINE_OUT(func, handler, T_out) \ - static bool func(struct apple_dcp *dcp, void *out, void *in) \ + static bool func(struct apple_dcp *dcp, int tag, void *out, void *in) \ { \ T_out *typed_out = out; \ \ - dev_dbg(dcp->dev, "received callback %s\n", #handler); \ + dev_dbg(dcp->dev, "Callback D%03d %s\n", tag, #handler); \ *typed_out = handler(dcp); \ return true; \ } @@ -878,7 +879,7 @@ TRAMPOLINE_OUT(trampoline_get_frequency, dcpep_cb_get_frequency, u64); TRAMPOLINE_OUT(trampoline_get_time, dcpep_cb_get_time, u64); TRAMPOLINE_IN(trampoline_hotplug, dcpep_cb_hotplug, u64); -bool (*const dcpep_cb_handlers[DCPEP_MAX_CB])(struct apple_dcp *, void *, void *) = { +bool (*const dcpep_cb_handlers[DCPEP_MAX_CB])(struct apple_dcp *, int, void *, void *) = { [0] = trampoline_true, /* did_boot_signal */ [1] = trampoline_true, /* did_power_on_signal */ [2] = trampoline_nop, /* will_power_off_signal */ @@ -950,7 +951,7 @@ static void dcpep_handle_cb(struct apple_dcp *dcp, enum dcp_context_id context, depth = dcp_push_depth(&ch->depth); ch->output[depth] = out; - if (dcpep_cb_handlers[tag](dcp, out, in)) + if (dcpep_cb_handlers[tag](dcp, tag, out, in)) dcp_ack(dcp, context); } From cf82dd5c48cb1a18fd6a74bde09077f0a88b6a25 Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Sun, 31 Jul 2022 18:19:28 +0200 Subject: [PATCH 0995/3902] drm/apple: Add DCP interface definitions used on t600x Signed-off-by: Janne Grunau --- drivers/gpu/drm/apple/dcp.c | 12 ++++++++++++ drivers/gpu/drm/apple/dcpep.h | 26 ++++++++++++++++++++++++++ 2 files changed, 38 insertions(+) diff --git a/drivers/gpu/drm/apple/dcp.c b/drivers/gpu/drm/apple/dcp.c index 1ab7bef174512c..75714aa3400338 100644 --- a/drivers/gpu/drm/apple/dcp.c +++ b/drivers/gpu/drm/apple/dcp.c @@ -225,8 +225,11 @@ const struct dcp_method_entry dcp_methods[dcpep_num_methods] = { DCP_METHOD("A407", dcpep_swap_start), DCP_METHOD("A408", dcpep_swap_submit), DCP_METHOD("A410", dcpep_set_display_device), + DCP_METHOD("A411", dcpep_is_main_display), DCP_METHOD("A412", dcpep_set_digital_out_mode), + DCP_METHOD("A439", dcpep_set_parameter_dcp), DCP_METHOD("A443", dcpep_create_default_fb), + DCP_METHOD("A447", dcpep_enable_disable_video_power_savings), DCP_METHOD("A454", dcpep_first_client_open), DCP_METHOD("A460", dcpep_set_display_refresh_properties), DCP_METHOD("A463", dcpep_flush_supports_power), @@ -336,6 +339,15 @@ __attribute__((unused)) DCP_THUNK_IN(dcp_update_notify_clients_dcp, dcpep_update_notify_clients_dcp, struct dcp_update_notify_clients_dcp); +DCP_THUNK_INOUT(dcp_set_parameter_dcp, dcpep_set_parameter_dcp, + struct dcp_set_parameter_dcp, u32); + +DCP_THUNK_INOUT(dcp_enable_disable_video_power_savings, + dcpep_enable_disable_video_power_savings, + u32, int); + +DCP_THUNK_OUT(dcp_is_main_display, dcpep_is_main_display, u32); + /* Parse a callback tag "D123" into the ID 123. Returns -EINVAL on failure. */ static int dcp_parse_tag(char tag[4]) { diff --git a/drivers/gpu/drm/apple/dcpep.h b/drivers/gpu/drm/apple/dcpep.h index f3d62d1d5f0328..d04a1b9ca9c55f 100644 --- a/drivers/gpu/drm/apple/dcpep.h +++ b/drivers/gpu/drm/apple/dcpep.h @@ -241,6 +241,9 @@ enum dcpep_method { dcpep_set_power_state, dcpep_first_client_open, dcpep_update_notify_clients_dcp, + dcpep_set_parameter_dcp, + dcpep_enable_disable_video_power_savings, + dcpep_is_main_display, dcpep_num_methods }; @@ -350,6 +353,15 @@ struct dcp_swap_submit_resp { u8 padding[3]; } __packed; +struct dc_swap_complete_resp { + u32 swap_id; + u8 unkbool; + u64 swap_data; + u8 swap_info[0x6c4]; + u32 unkint; + u8 swap_info_null; +} __packed; + struct dcp_get_uint_prop_req { char obj[4]; char key[0x40]; @@ -403,4 +415,18 @@ struct dcp_update_notify_clients_dcp { u32 client_d; } __packed; +struct dcp_set_parameter_dcp { + u32 param; + u32 value[8]; + u32 count; +} __packed; + +struct dcp_swap_complete_intent_gated { + u32 swap_id; + u8 unkBool; + u32 unkInt; + u32 width; + u32 height; +} __packed; + #endif From dc9a53b9270431fc09246a2fb6fe6ed5963eecfa Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Sun, 31 Jul 2022 18:43:01 +0200 Subject: [PATCH 0996/3902] drm/apple: Clear used callback/cookie on dcp_ack Avoids unexpected callbacks on nesting state errors. Encountered when making DCP calls from a second thread for the backlight. Signed-off-by: Janne Grunau --- drivers/gpu/drm/apple/dcp.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/drivers/gpu/drm/apple/dcp.c b/drivers/gpu/drm/apple/dcp.c index 75714aa3400338..fac0aab23112dc 100644 --- a/drivers/gpu/drm/apple/dcp.c +++ b/drivers/gpu/drm/apple/dcp.c @@ -985,6 +985,9 @@ static void dcpep_handle_ack(struct apple_dcp *dcp, enum dcp_context_id context, cb = ch->callbacks[ch->depth]; cookie = ch->cookies[ch->depth]; + ch->callbacks[ch->depth] = NULL; + ch->cookies[ch->depth] = NULL; + if (cb) cb(dcp, data + sizeof(*header) + header->in_len, cookie); } From 843ff8014a3626e91f04f9785b099031de169e7e Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Sun, 31 Jul 2022 19:31:16 +0200 Subject: [PATCH 0997/3902] drm/apple: Add t600x support Call power-on/-off handling explicitly from apple_crtc_atomic_enable / apple_crtc_atomic_disable. This makes DPMS work. Signed-off-by: Janne Grunau --- drivers/gpu/drm/apple/apple_drv.c | 4 + drivers/gpu/drm/apple/dcp.c | 302 +++++++++++++++++++++++++++--- drivers/gpu/drm/apple/dcp.h | 2 + 3 files changed, 280 insertions(+), 28 deletions(-) diff --git a/drivers/gpu/drm/apple/apple_drv.c b/drivers/gpu/drm/apple/apple_drv.c index 94b1fc3a09fe67..c933f929158fb5 100644 --- a/drivers/gpu/drm/apple/apple_drv.c +++ b/drivers/gpu/drm/apple/apple_drv.c @@ -177,13 +177,17 @@ apple_connector_detect(struct drm_connector *connector, bool force) static void apple_crtc_atomic_enable(struct drm_crtc *crtc, struct drm_atomic_state *state) { + struct apple_crtc *apple_crtc = to_apple_crtc(crtc); + dcp_poweron(apple_crtc->dcp); drm_crtc_vblank_on(crtc); } static void apple_crtc_atomic_disable(struct drm_crtc *crtc, struct drm_atomic_state *state) { + struct apple_crtc *apple_crtc = to_apple_crtc(crtc); drm_crtc_vblank_off(crtc); + dcp_poweroff(apple_crtc->dcp); if (crtc->state->event && !crtc->state->active) { spin_lock_irq(&crtc->dev->event_lock); diff --git a/drivers/gpu/drm/apple/dcp.c b/drivers/gpu/drm/apple/dcp.c index fac0aab23112dc..7aa83bdcbe7dc3 100644 --- a/drivers/gpu/drm/apple/dcp.c +++ b/drivers/gpu/drm/apple/dcp.c @@ -10,6 +10,7 @@ #include #include #include +#include #include #include @@ -111,6 +112,11 @@ struct apple_dcp { /* Is the DCP booted? */ bool active; + /* eDP display without DP-HDMI conversion */ + bool main_display; + + bool ignore_swap_complete; + /* Modes valid for the connected display */ struct dcp_display_mode *modes; unsigned int nr_modes; @@ -132,6 +138,11 @@ struct dcp_fb_reference { struct drm_framebuffer *fb; }; +struct dcp_wait_cookie { + struct completion done; + atomic_t refcount; +}; + /* * A channel is busy if we have sent a message that has yet to be * acked. The driver must not sent a message to a busy channel. @@ -417,9 +428,11 @@ void dcp_drm_crtc_vblank(struct apple_crtc *crtc) spin_unlock_irqrestore(&crtc->base.dev->event_lock, flags); } -static void dcpep_cb_swap_complete(struct apple_dcp *dcp) +static void dcpep_cb_swap_complete(struct apple_dcp *dcp, + struct dc_swap_complete_resp *resp) { - dcp_drm_crtc_vblank(dcp->crtc); + if (!dcp->ignore_swap_complete) + dcp_drm_crtc_vblank(dcp->crtc); } static struct dcp_get_uint_prop_resp @@ -756,6 +769,182 @@ static u64 dcpep_cb_get_time(struct apple_dcp *dcp) return ktime_to_ms(ktime_get_real()); } +struct dcp_swap_cookie { + struct completion done; + atomic_t refcount; + u32 swap_id; +}; + +static void dcp_swap_cleared(struct apple_dcp *dcp, void *data, void *cookie) +{ + struct dcp_swap_submit_resp *resp = data; + + if (cookie) { + struct dcp_swap_cookie *info = cookie; + complete(&info->done); + if (atomic_dec_and_test(&info->refcount)) + kfree(info); + } + + if (resp->ret) { + dev_err(dcp->dev, "swap_clear failed! status %u\n", resp->ret); + dcp_drm_crtc_vblank(dcp->crtc); + return; + } + + while (!list_empty(&dcp->swapped_out_fbs)) { + struct dcp_fb_reference *entry; + entry = list_first_entry(&dcp->swapped_out_fbs, + struct dcp_fb_reference, head); + if (entry->fb) + drm_framebuffer_put(entry->fb); + list_del(&entry->head); + kfree(entry); + } +} + +static void dcp_swap_clear_started(struct apple_dcp *dcp, void *data, + void *cookie) +{ + struct dcp_swap_start_resp *resp = data; + dcp->swap.swap.swap_id = resp->swap_id; + + if (cookie) { + struct dcp_swap_cookie *info = cookie; + info->swap_id = resp->swap_id; + } + + dcp_swap_submit(dcp, false, &dcp->swap, dcp_swap_cleared, cookie); +} + +static void dcp_on_final(struct apple_dcp *dcp, void *out, void *cookie) +{ + struct dcp_wait_cookie *wait = cookie; + + if (wait) { + complete(&wait->done); + if (atomic_dec_and_test(&wait->refcount)) + kfree(wait); + } +} + +static void dcp_on_set_parameter(struct apple_dcp *dcp, void *out, void *cookie) +{ + struct dcp_set_parameter_dcp param = { + .param = 14, + .value = { 0 }, + .count = 1, + }; + + dcp_set_parameter_dcp(dcp, false, ¶m, dcp_on_final, cookie); +} + +void dcp_poweron(struct platform_device *pdev) +{ + struct apple_dcp *dcp = platform_get_drvdata(pdev); + struct dcp_wait_cookie * cookie; + struct dcp_set_power_state_req req = { + .unklong = 1, + }; + int ret; + u32 handle; + + cookie = kzalloc(sizeof(*cookie), GFP_KERNEL); + if (!cookie) + return; + + init_completion(&cookie->done); + atomic_set(&cookie->refcount, 2); + + if (dcp->main_display) { + handle = 0; + dcp_set_display_device(dcp, false, &handle, dcp_on_final, cookie); + } else { + handle = 2; + dcp_set_display_device(dcp, false, &handle, dcp_on_set_parameter, cookie); + } + dcp_set_power_state(dcp, true, &req, NULL, NULL); + + ret = wait_for_completion_timeout(&cookie->done, msecs_to_jiffies(500)); + + if (ret == 0) + dev_warn(dcp->dev, "wait for power timed out"); + + if (atomic_dec_and_test(&cookie->refcount)) + kfree(cookie); +} +EXPORT_SYMBOL(dcp_poweron); + +static void complete_set_powerstate(struct apple_dcp *dcp, void *out, void *cookie) +{ + struct dcp_wait_cookie *wait = cookie; + + if (wait) { + complete(&wait->done); + if (atomic_dec_and_test(&wait->refcount)) + kfree(wait); + } +} + +void dcp_poweroff(struct platform_device *pdev) +{ + struct apple_dcp *dcp = platform_get_drvdata(pdev); + int ret, swap_id; + struct dcp_set_power_state_req power_req = { + .unklong = 0, + }; + struct dcp_swap_cookie *cookie; + struct dcp_wait_cookie *poff_cookie; + struct dcp_swap_start_req swap_req= { 0 }; + + cookie = kzalloc(sizeof(*cookie), GFP_KERNEL); + if (!cookie) + return; + init_completion(&cookie->done); + atomic_set(&cookie->refcount, 2); + + // clear surfaces + memset(&dcp->swap, 0, sizeof(dcp->swap)); + + dcp->swap.swap.swap_enabled = DCP_REMOVE_LAYERS | 0x7; + dcp->swap.swap.swap_completed = DCP_REMOVE_LAYERS | 0x7; + dcp->swap.swap.unk_10c = 0xFF000000; + + for (int l = 0; l < SWAP_SURFACES; l++) + dcp->swap.surf_null[l] = true; + + dcp_swap_start(dcp, false, &swap_req, dcp_swap_clear_started, cookie); + + ret = wait_for_completion_timeout(&cookie->done, msecs_to_jiffies(50)); + swap_id = cookie->swap_id; + if (atomic_dec_and_test(&cookie->refcount)) + kfree(cookie); + if (ret <= 0) { + dcp->crashed = true; + return; + } + + poff_cookie = kzalloc(sizeof(*poff_cookie), GFP_KERNEL); + if (!poff_cookie) + return; + init_completion(&poff_cookie->done); + atomic_set(&poff_cookie->refcount, 2); + + dcp_set_power_state(dcp, false, &power_req, complete_set_powerstate, poff_cookie); + ret = wait_for_completion_timeout(&cookie->done, msecs_to_jiffies(1000)); + + if (ret == 0) + dev_warn(dcp->dev, "setPowerState(0) timeout %u ms", 1000); + else if (ret > 0) + dev_dbg(dcp->dev, "setPowerState(0) finished with %d ms to spare", + jiffies_to_msecs(ret)); + + if (atomic_dec_and_test(&poff_cookie->refcount)) + kfree(poff_cookie); + dev_dbg(dcp->dev, "%s: setPowerState(0) done", __func__); +} +EXPORT_SYMBOL(dcp_poweroff); + /* * Helper to send a DRM hotplug event. The DCP is accessed from a single * (RTKit) thread. To handle hotplug callbacks, we need to call @@ -767,16 +956,19 @@ void dcp_hotplug(struct work_struct *work) { struct apple_connector *connector; struct drm_device *dev; + struct apple_dcp *dcp; connector = container_of(work, struct apple_connector, hotplug_wq); dev = connector->base.dev; + dcp = platform_get_drvdata(connector->dcp); + /* * DCP defers link training until we set a display mode. But we set * display modes from atomic_flush, so userspace needs to trigger a * flush, or the CRTC gets no signal. */ - if (connector->connected) { + if (!dcp->valid_mode && connector->connected) { drm_connector_set_link_status_property( &connector->base, DRM_MODE_LINK_STATUS_BAD); } @@ -791,10 +983,16 @@ static void dcpep_cb_hotplug(struct apple_dcp *dcp, u64 *connected) struct apple_connector *connector = dcp->connector; /* Hotplug invalidates mode. DRM doesn't always handle this. */ - dcp->valid_mode = false; + if (!(*connected)) { + dcp->valid_mode = false; + /* after unplug swap will not complete until the next + * set_digital_out_mode */ + schedule_work(&dcp->vblank_wq); + } - if (connector) { + if (connector && connector->connected != !!(*connected)) { connector->connected = !!(*connected); + dcp->valid_mode = false; schedule_work(&connector->hotplug_wq); } } @@ -868,7 +1066,8 @@ TRAMPOLINE_VOID(trampoline_nop, dcpep_cb_nop); TRAMPOLINE_OUT(trampoline_true, dcpep_cb_true, u8); TRAMPOLINE_OUT(trampoline_false, dcpep_cb_false, u8); TRAMPOLINE_OUT(trampoline_zero, dcpep_cb_zero, u32); -TRAMPOLINE_VOID(trampoline_swap_complete, dcpep_cb_swap_complete); +TRAMPOLINE_IN(trampoline_swap_complete, dcpep_cb_swap_complete, + struct dc_swap_complete_resp); TRAMPOLINE_INOUT(trampoline_get_uint_prop, dcpep_cb_get_uint_prop, struct dcp_get_uint_prop_req, struct dcp_get_uint_prop_resp); TRAMPOLINE_INOUT(trampoline_map_piodma, dcpep_cb_map_piodma, @@ -906,6 +1105,7 @@ bool (*const dcpep_cb_handlers[DCPEP_MAX_CB])(struct apple_dcp *, int, void *, v [110] = trampoline_true, /* create_iomfb_service */ [111] = trampoline_false, /* create_backlight_service */ [116] = dcpep_cb_boot_1, + [117] = trampoline_false, /* is_dark_boot */ [118] = trampoline_false, /* is_dark_boot / is_waking_from_hibernate*/ [120] = trampoline_false, /* read_edt_data */ [122] = trampoline_prop_start, @@ -938,6 +1138,7 @@ bool (*const dcpep_cb_handlers[DCPEP_MAX_CB])(struct apple_dcp *, int, void *, v [582] = trampoline_true, /* create_default_fb_surface */ [589] = trampoline_swap_complete, [591] = trampoline_nop, /* swap_complete_intent_gated */ + [593] = trampoline_nop, /* enable_backlight_message_ap_gated */ [598] = trampoline_nop, /* find_swap_function_gated */ }; @@ -1142,12 +1343,24 @@ static void do_swap(struct apple_dcp *dcp, void *data, void *cookie) { struct dcp_swap_start_req start_req = { 0 }; - dcp_swap_start(dcp, false, &start_req, dcp_swap_started, NULL); + if (dcp->connector && dcp->connector->connected) + dcp_swap_start(dcp, false, &start_req, dcp_swap_started, NULL); + else + dcp_drm_crtc_vblank(dcp->crtc); } -static void dcp_modeset(struct apple_dcp *dcp, void *out, void *cookie) +static void complete_set_digital_out_mode(struct apple_dcp *dcp, void *data, + void *cookie) { - dcp_set_digital_out_mode(dcp, false, &dcp->mode, do_swap, NULL); + struct dcp_wait_cookie *wait = cookie; + + dcp->ignore_swap_complete = false; + + if (wait) { + complete(&wait->done); + if (atomic_dec_and_test(&wait->refcount)) + kfree(wait); + } } void dcp_flush(struct drm_crtc *crtc, struct drm_atomic_state *state) @@ -1244,7 +1457,8 @@ void dcp_flush(struct drm_crtc *crtc, struct drm_atomic_state *state) if (drm_atomic_crtc_needs_modeset(crtc_state) || !dcp->valid_mode) { struct dcp_display_mode *mode; - u32 handle = 2; + struct dcp_wait_cookie *cookie; + int ret; mode = lookup_mode(dcp, &crtc_state->mode); if (!mode) { @@ -1259,19 +1473,43 @@ void dcp_flush(struct drm_crtc *crtc, struct drm_atomic_state *state) .timing_mode_id = mode->timing_mode_id }; - dcp->valid_mode = true; + cookie = kzalloc(sizeof(cookie), GFP_KERNEL); + if (!cookie) { + schedule_work(&dcp->vblank_wq); + return; + } + + init_completion(&cookie->done); + atomic_set(&cookie->refcount, 2); + + dcp_set_digital_out_mode(dcp, false, &dcp->mode, + complete_set_digital_out_mode, cookie); + + ret = wait_for_completion_timeout(&cookie->done, msecs_to_jiffies(500)); - dcp_set_display_device(dcp, false, &handle, dcp_modeset, NULL); + if (atomic_dec_and_test(&cookie->refcount)) + kfree(cookie); + + if (ret == 0) { + dev_dbg(dcp->dev, "set_digital_out_mode 200 ms"); + schedule_work(&dcp->vblank_wq); + return; + } + else if (ret > 0) { + dev_dbg(dcp->dev, "set_digital_out_mode finished with %d to spare", + jiffies_to_msecs(ret)); + } + + dcp->valid_mode = true; } - else if (!has_surface) { - dev_warn(dcp->dev, "can't flush without surfaces, vsync:%d", dcp->crtc->vsync_disabled); - /* HACK: issue a delayed vblank event to avoid timeouts in - * drm_atomic_helper_wait_for_vblanks(). It's currently unkown - * if and how DCP supports swaps without attached surfaces. - */ + + if (!has_surface) { + dev_warn(dcp->dev, "flush without surfaces, vsync:%d", + dcp->crtc->vsync_disabled); schedule_work(&dcp->vblank_wq); - } else - do_swap(dcp, NULL, NULL); + return; + } + do_swap(dcp, NULL, NULL); } EXPORT_SYMBOL_GPL(dcp_flush); @@ -1283,16 +1521,20 @@ bool dcp_is_initialized(struct platform_device *pdev) } EXPORT_SYMBOL_GPL(dcp_is_initialized); -static void init_done(struct apple_dcp *dcp, void *out, void *cookie) + +static void res_is_main_display(struct apple_dcp *dcp, void *out, void *cookie) { + int result = *(int *)out; + dev_info(dcp->dev, "DCP is_main_display: %d\n", result); + + dcp->main_display = result != 0; + + dcp->active = true; } static void init_3(struct apple_dcp *dcp, void *out, void *cookie) { - struct dcp_set_power_state_req req = { - .unklong = 1, - }; - dcp_set_power_state(dcp, false, &req, init_done, NULL); + dcp_is_main_display(dcp, false, res_is_main_display, NULL); } static void init_2(struct apple_dcp *dcp, void *out, void *cookie) @@ -1300,13 +1542,17 @@ static void init_2(struct apple_dcp *dcp, void *out, void *cookie) dcp_first_client_open(dcp, false, init_3, NULL); } +static void init_1(struct apple_dcp *dcp, void *out, void *cookie) +{ + u32 val = 0; + dcp_enable_disable_video_power_savings(dcp, false, &val, init_2, NULL); +} + static void dcp_started(struct apple_dcp *dcp, void *data, void *cookie) { dev_info(dcp->dev, "DCP booted\n"); - init_2(dcp, data, cookie); - - dcp->active = true; + init_1(dcp, data, cookie); } static void dcp_got_msg(void *cookie, u8 endpoint, u64 message) diff --git a/drivers/gpu/drm/apple/dcp.h b/drivers/gpu/drm/apple/dcp.h index 794544456df963..9e3e3738a39377 100644 --- a/drivers/gpu/drm/apple/dcp.h +++ b/drivers/gpu/drm/apple/dcp.h @@ -32,6 +32,8 @@ struct apple_connector { #define to_apple_connector(x) container_of(x, struct apple_connector, base) +void dcp_poweroff(struct platform_device *pdev); +void dcp_poweron(struct platform_device *pdev); void dcp_link(struct platform_device *pdev, struct apple_crtc *apple, struct apple_connector *connector); void dcp_flush(struct drm_crtc *crtc, struct drm_atomic_state *state); From 842ecaca0a94e52a44f76107389bcfa90eedab2a Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Mon, 6 Jun 2022 22:14:27 +0200 Subject: [PATCH 0998/3902] drm/apple: toggle power only when active state changes squash! WIP: GPU/apple: t600x work in progress Signed-off-by: Janne Grunau --- drivers/gpu/drm/apple/apple_drv.c | 23 +++++++++++++++++++---- 1 file changed, 19 insertions(+), 4 deletions(-) diff --git a/drivers/gpu/drm/apple/apple_drv.c b/drivers/gpu/drm/apple/apple_drv.c index c933f929158fb5..a7b90daa39d917 100644 --- a/drivers/gpu/drm/apple/apple_drv.c +++ b/drivers/gpu/drm/apple/apple_drv.c @@ -177,17 +177,32 @@ apple_connector_detect(struct drm_connector *connector, bool force) static void apple_crtc_atomic_enable(struct drm_crtc *crtc, struct drm_atomic_state *state) { - struct apple_crtc *apple_crtc = to_apple_crtc(crtc); - dcp_poweron(apple_crtc->dcp); + struct drm_crtc_state *crtc_state; + crtc_state = drm_atomic_get_new_crtc_state(state, crtc); + + if (crtc_state->active_changed && crtc_state->active) { + struct apple_crtc *apple_crtc = to_apple_crtc(crtc); + dev_dbg(&apple_crtc->dcp->dev, "%s", __func__); + dcp_poweron(apple_crtc->dcp); + dev_dbg(&apple_crtc->dcp->dev, "%s finished", __func__); + } drm_crtc_vblank_on(crtc); } static void apple_crtc_atomic_disable(struct drm_crtc *crtc, struct drm_atomic_state *state) { - struct apple_crtc *apple_crtc = to_apple_crtc(crtc); + struct drm_crtc_state *crtc_state; + crtc_state = drm_atomic_get_new_crtc_state(state, crtc); + drm_crtc_vblank_off(crtc); - dcp_poweroff(apple_crtc->dcp); + + if (crtc_state->active_changed && !crtc_state->active) { + struct apple_crtc *apple_crtc = to_apple_crtc(crtc); + dev_dbg(&apple_crtc->dcp->dev, "%s", __func__); + dcp_poweroff(apple_crtc->dcp); + dev_dbg(&apple_crtc->dcp->dev, "%s finished", __func__); + } if (crtc->state->event && !crtc->state->active) { spin_lock_irq(&crtc->dev->event_lock); From 5d422c91662c89ab0ca5558bfa432db0da1d8fdf Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Sun, 31 Jul 2022 19:39:10 +0200 Subject: [PATCH 0999/3902] drm/apple: Add somewhat useful debug prints Signed-off-by: Janne Grunau --- drivers/gpu/drm/apple/dcp.c | 24 +++++++++++++++++++++++- 1 file changed, 23 insertions(+), 1 deletion(-) diff --git a/drivers/gpu/drm/apple/dcp.c b/drivers/gpu/drm/apple/dcp.c index 7aa83bdcbe7dc3..6ed7a31591d2f7 100644 --- a/drivers/gpu/drm/apple/dcp.c +++ b/drivers/gpu/drm/apple/dcp.c @@ -431,6 +431,9 @@ void dcp_drm_crtc_vblank(struct apple_crtc *crtc) static void dcpep_cb_swap_complete(struct apple_dcp *dcp, struct dc_swap_complete_resp *resp) { + dev_dbg(dcp->dev, "swap complete for swap_id: %u vblank: %u", + resp->swap_id, dcp->ignore_swap_complete); + if (!dcp->ignore_swap_complete) dcp_drm_crtc_vblank(dcp->crtc); } @@ -699,6 +702,7 @@ static void boot_done(struct apple_dcp *dcp, void *out, void *cookie) { struct dcp_cb_channel *ch = &dcp->ch_cb; u8 *succ = ch->output[ch->depth - 1]; + dev_dbg(dcp->dev, "boot done"); *succ = true; dcp_ack(dcp, DCP_CONTEXT_CB); @@ -807,6 +811,7 @@ static void dcp_swap_clear_started(struct apple_dcp *dcp, void *data, void *cookie) { struct dcp_swap_start_resp *resp = data; + dev_dbg(dcp->dev, "%s swap_id: %u", __func__, resp->swap_id); dcp->swap.swap.swap_id = resp->swap_id; if (cookie) { @@ -924,6 +929,8 @@ void dcp_poweroff(struct platform_device *pdev) return; } + dev_dbg(dcp->dev, "%s: clear swap submitted: %u", __func__, swap_id); + poff_cookie = kzalloc(sizeof(*poff_cookie), GFP_KERNEL); if (!poff_cookie) return; @@ -962,6 +969,7 @@ void dcp_hotplug(struct work_struct *work) dev = connector->base.dev; dcp = platform_get_drvdata(connector->dcp); + dev_info(dcp->dev, "%s: connected: %d", __func__, connector->connected); /* * DCP defers link training until we set a display mode. But we set @@ -997,6 +1005,14 @@ static void dcpep_cb_hotplug(struct apple_dcp *dcp, u64 *connected) } } +static void +dcpep_cb_swap_complete_intent_gated(struct apple_dcp *dcp, + struct dcp_swap_complete_intent_gated *info) +{ + dev_dbg(dcp->dev, "swap_id:%u width:%u height:%u", info->swap_id, + info->width, info->height); +} + /* * Helper to send a DRM vblank event. We do not know how call swap_submit_dcp * without surfaces. To avoid timeouts in drm_atomic_helper_wait_for_vblanks @@ -1089,6 +1105,9 @@ TRAMPOLINE_OUT(trampoline_rt_bandwidth, dcpep_cb_rt_bandwidth, TRAMPOLINE_OUT(trampoline_get_frequency, dcpep_cb_get_frequency, u64); TRAMPOLINE_OUT(trampoline_get_time, dcpep_cb_get_time, u64); TRAMPOLINE_IN(trampoline_hotplug, dcpep_cb_hotplug, u64); +TRAMPOLINE_IN(trampoline_swap_complete_intent_gated, + dcpep_cb_swap_complete_intent_gated, + struct dcp_swap_complete_intent_gated); bool (*const dcpep_cb_handlers[DCPEP_MAX_CB])(struct apple_dcp *, int, void *, void *) = { [0] = trampoline_true, /* did_boot_signal */ @@ -1137,7 +1156,7 @@ bool (*const dcpep_cb_handlers[DCPEP_MAX_CB])(struct apple_dcp *, int, void *, v [577] = trampoline_nop, /* powerstate_notify */ [582] = trampoline_true, /* create_default_fb_surface */ [589] = trampoline_swap_complete, - [591] = trampoline_nop, /* swap_complete_intent_gated */ + [591] = trampoline_swap_complete_intent_gated, [593] = trampoline_nop, /* enable_backlight_message_ap_gated */ [598] = trampoline_nop, /* find_swap_function_gated */ }; @@ -1468,6 +1487,8 @@ void dcp_flush(struct drm_crtc *crtc, struct drm_atomic_state *state) return; } + dev_info(dcp->dev, "set_digital_out_mode(color:%d timing:%d)", + mode->color_mode_id, mode->timing_mode_id); dcp->mode = (struct dcp_set_digital_out_mode_req) { .color_mode_id = mode->color_mode_id, .timing_mode_id = mode->timing_mode_id @@ -1485,6 +1506,7 @@ void dcp_flush(struct drm_crtc *crtc, struct drm_atomic_state *state) dcp_set_digital_out_mode(dcp, false, &dcp->mode, complete_set_digital_out_mode, cookie); + dev_dbg(dcp->dev, "%s - wait for modeset", __func__); ret = wait_for_completion_timeout(&cookie->done, msecs_to_jiffies(500)); if (atomic_dec_and_test(&cookie->refcount)) From 152dc8ad2c1adffbf6b52b46c0e8df6cc9f90941 Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Sun, 31 Jul 2022 19:40:50 +0200 Subject: [PATCH 1000/3902] drm/apple: Add less tons of questionable debug prints Signed-off-by: Janne Grunau --- drivers/gpu/drm/apple/dcp.c | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/drivers/gpu/drm/apple/dcp.c b/drivers/gpu/drm/apple/dcp.c index 6ed7a31591d2f7..efab2f2086e9a8 100644 --- a/drivers/gpu/drm/apple/dcp.c +++ b/drivers/gpu/drm/apple/dcp.c @@ -782,6 +782,7 @@ struct dcp_swap_cookie { static void dcp_swap_cleared(struct apple_dcp *dcp, void *data, void *cookie) { struct dcp_swap_submit_resp *resp = data; + dev_dbg(dcp->dev, "%s", __func__); if (cookie) { struct dcp_swap_cookie *info = cookie; @@ -825,6 +826,7 @@ static void dcp_swap_clear_started(struct apple_dcp *dcp, void *data, static void dcp_on_final(struct apple_dcp *dcp, void *out, void *cookie) { struct dcp_wait_cookie *wait = cookie; + dev_dbg(dcp->dev, "%s", __func__); if (wait) { complete(&wait->done); @@ -840,6 +842,7 @@ static void dcp_on_set_parameter(struct apple_dcp *dcp, void *out, void *cookie) .value = { 0 }, .count = 1, }; + dev_dbg(dcp->dev, "%s", __func__); dcp_set_parameter_dcp(dcp, false, ¶m, dcp_on_final, cookie); } @@ -853,6 +856,7 @@ void dcp_poweron(struct platform_device *pdev) }; int ret; u32 handle; + dev_dbg(dcp->dev, "%s", __func__); cookie = kzalloc(sizeof(*cookie), GFP_KERNEL); if (!cookie) @@ -902,6 +906,8 @@ void dcp_poweroff(struct platform_device *pdev) struct dcp_wait_cookie *poff_cookie; struct dcp_swap_start_req swap_req= { 0 }; + dev_dbg(dcp->dev, "%s", __func__); + cookie = kzalloc(sizeof(*cookie), GFP_KERNEL); if (!cookie) return; @@ -1361,6 +1367,7 @@ EXPORT_SYMBOL_GPL(dcp_mode_valid); static void do_swap(struct apple_dcp *dcp, void *data, void *cookie) { struct dcp_swap_start_req start_req = { 0 }; + dev_dbg(dcp->dev, "%s", __func__); if (dcp->connector && dcp->connector->connected) dcp_swap_start(dcp, false, &start_req, dcp_swap_started, NULL); @@ -1372,6 +1379,7 @@ static void complete_set_digital_out_mode(struct apple_dcp *dcp, void *data, void *cookie) { struct dcp_wait_cookie *wait = cookie; + dev_dbg(dcp->dev, "%s", __func__); dcp->ignore_swap_complete = false; @@ -1392,6 +1400,7 @@ void dcp_flush(struct drm_crtc *crtc, struct drm_atomic_state *state) struct dcp_swap_submit_req *req = &dcp->swap; int l; int has_surface = 0; + dev_dbg(dcp->dev, "%s", __func__); crtc_state = drm_atomic_get_new_crtc_state(state, crtc); From 6474c54a33affa8f363d3165f407b6669e787f35 Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Sun, 5 Jun 2022 19:32:01 +0200 Subject: [PATCH 1001/3902] drm/apple: implement read_edt_data Handling it with trampoline_false can result in errors due to uninitialized data. Signed-off-by: Janne Grunau --- drivers/gpu/drm/apple/dcp.c | 13 ++++++++++++- drivers/gpu/drm/apple/dcpep.h | 11 +++++++++++ 2 files changed, 23 insertions(+), 1 deletion(-) diff --git a/drivers/gpu/drm/apple/dcp.c b/drivers/gpu/drm/apple/dcp.c index efab2f2086e9a8..757c6317115d2f 100644 --- a/drivers/gpu/drm/apple/dcp.c +++ b/drivers/gpu/drm/apple/dcp.c @@ -581,6 +581,15 @@ dcpep_cb_map_reg(struct apple_dcp *dcp, struct dcp_map_reg_req *req) } } +static struct dcp_read_edt_data_resp +dcpep_cb_read_edt_data(struct apple_dcp *dcp, struct dcp_read_edt_data_req *req) +{ + return (struct dcp_read_edt_data_resp) { + .value[0] = req->value[0], + .ret = 0, + }; +} + /* Chunked data transfer for property dictionaries */ static u8 dcpep_cb_prop_start(struct apple_dcp *dcp, u32 *length) { @@ -1101,6 +1110,8 @@ TRAMPOLINE_INOUT(trampoline_map_physical, dcpep_cb_map_physical, struct dcp_map_physical_req, struct dcp_map_physical_resp); TRAMPOLINE_INOUT(trampoline_map_reg, dcpep_cb_map_reg, struct dcp_map_reg_req, struct dcp_map_reg_resp); +TRAMPOLINE_INOUT(trampoline_read_edt_data, dcpep_cb_read_edt_data, + struct dcp_read_edt_data_req, struct dcp_read_edt_data_resp); TRAMPOLINE_INOUT(trampoline_prop_start, dcpep_cb_prop_start, u32, u8); TRAMPOLINE_INOUT(trampoline_prop_chunk, dcpep_cb_prop_chunk, struct dcp_set_dcpav_prop_chunk_req, u8); @@ -1132,7 +1143,7 @@ bool (*const dcpep_cb_handlers[DCPEP_MAX_CB])(struct apple_dcp *, int, void *, v [116] = dcpep_cb_boot_1, [117] = trampoline_false, /* is_dark_boot */ [118] = trampoline_false, /* is_dark_boot / is_waking_from_hibernate*/ - [120] = trampoline_false, /* read_edt_data */ + [120] = trampoline_read_edt_data, [122] = trampoline_prop_start, [123] = trampoline_prop_chunk, [124] = trampoline_prop_end, diff --git a/drivers/gpu/drm/apple/dcpep.h b/drivers/gpu/drm/apple/dcpep.h index d04a1b9ca9c55f..12d81c7b4e2734 100644 --- a/drivers/gpu/drm/apple/dcpep.h +++ b/drivers/gpu/drm/apple/dcpep.h @@ -429,4 +429,15 @@ struct dcp_swap_complete_intent_gated { u32 height; } __packed; +struct dcp_read_edt_data_req { + char key[0x40]; + u32 count; + u32 value[8]; +} __packed; + +struct dcp_read_edt_data_resp { + u32 value[8]; + u8 ret; +} __packed; + #endif From d07adc725c1a4a4e6f2e56349a25d74d100800d3 Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Sun, 5 Jun 2022 19:47:25 +0200 Subject: [PATCH 1002/3902] drm/apple: clear callback's output data If there is a mismatch in the output size this way we have at leas consistant results. Signed-off-by: Janne Grunau --- drivers/gpu/drm/apple/dcp.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/drivers/gpu/drm/apple/dcp.c b/drivers/gpu/drm/apple/dcp.c index 757c6317115d2f..f0b7c3d1d48fa0 100644 --- a/drivers/gpu/drm/apple/dcp.c +++ b/drivers/gpu/drm/apple/dcp.c @@ -1197,6 +1197,11 @@ static void dcpep_handle_cb(struct apple_dcp *dcp, enum dcp_context_id context, in = data + sizeof(*hdr); out = in + hdr->in_len; + // TODO: verify that in_len and out_len match our prototypes + // for now just clear the out data to have at least consistant results + if (hdr->out_len) + memset(out, 0, hdr->out_len); + depth = dcp_push_depth(&ch->depth); ch->output[depth] = out; From 4591faf2f075abd47f564c02c074eb23eff4cf73 Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Sun, 5 Jun 2022 20:00:13 +0200 Subject: [PATCH 1003/3902] drm/apple: Support memory unmapping/freeing Used by dcp on mode changes on the Macbook Pro 14" (M1 Max). Signed-off-by: Janne Grunau --- drivers/gpu/drm/apple/dcp.c | 129 ++++++++++++++++++++++++++++++---- drivers/gpu/drm/apple/dcpep.h | 8 +++ 2 files changed, 122 insertions(+), 15 deletions(-) diff --git a/drivers/gpu/drm/apple/dcp.c b/drivers/gpu/drm/apple/dcp.c index f0b7c3d1d48fa0..e7e82545367195 100644 --- a/drivers/gpu/drm/apple/dcp.c +++ b/drivers/gpu/drm/apple/dcp.c @@ -1,6 +1,7 @@ // SPDX-License-Identifier: GPL-2.0-only OR MIT /* Copyright 2021 Alyssa Rosenzweig */ +#include #include #include #include @@ -64,6 +65,14 @@ struct dcp_chunks { #define DCP_MAX_MAPPINGS (128) /* should be enough */ #define MAX_DISP_REGISTERS (7) +struct dcp_mem_descriptor { + size_t size; + void *buf; + dma_addr_t dva; + struct sg_table map; + u64 reg; +}; + struct apple_dcp { struct device *dev; struct platform_device *piodma; @@ -90,11 +99,11 @@ struct apple_dcp { struct resource *disp_registers[MAX_DISP_REGISTERS]; unsigned int nr_disp_registers; - /* Number of memory mappings made by the DCP, used as an ID */ - u32 nr_mappings; + /* Bitmap of memory descriptors used for mappings made by the DCP */ + DECLARE_BITMAP(memdesc_map, DCP_MAX_MAPPINGS); - /* Indexed table of mappings */ - struct sg_table mappings[DCP_MAX_MAPPINGS]; + /* Indexed table of memory descriptors */ + struct dcp_mem_descriptor memdesc[DCP_MAX_MAPPINGS]; struct dcp_call_channel ch_cmd, ch_oobcmd; struct dcp_cb_channel ch_cb, ch_oobcb, ch_async; @@ -462,10 +471,10 @@ dcpep_cb_map_piodma(struct apple_dcp *dcp, struct dcp_map_buf_req *req) struct sg_table *map; int ret; - if (req->buffer >= ARRAY_SIZE(dcp->mappings)) + if (req->buffer >= ARRAY_SIZE(dcp->memdesc)) goto reject; - map = &dcp->mappings[req->buffer]; + map = &dcp->memdesc[req->buffer].map; if (!map->sgl) goto reject; @@ -488,6 +497,37 @@ dcpep_cb_map_piodma(struct apple_dcp *dcp, struct dcp_map_buf_req *req) }; } +static void +dcpep_cb_unmap_piodma(struct apple_dcp *dcp, struct dcp_unmap_buf_resp *resp) +{ + struct sg_table *map; + dma_addr_t dma_addr; + + if (resp->buffer >= ARRAY_SIZE(dcp->memdesc)) { + dev_warn(dcp->dev, "unmap request for out of range buffer %llu", + resp->buffer); + return; + } + + map = &dcp->memdesc[resp->buffer].map; + + if (!map->sgl) { + dev_warn(dcp->dev, "unmap for non-mapped buffer %llu iova:0x%08llx", + resp->buffer, resp->dva); + return; + } + + dma_addr = sg_dma_address(map->sgl); + if (dma_addr != resp->dva) { + dev_warn(dcp->dev, "unmap buffer %llu address mismatch dma_addr:%llx dva:%llx", + resp->buffer, dma_addr, resp->dva); + return; + } + + /* Use PIODMA device instead of DCP to unmap from the right IOMMU. */ + dma_unmap_sgtable(&dcp->piodma->dev, map, DMA_BIDIRECTIONAL, 0); +} + /* * Allocate an IOVA contiguous buffer mapped to the DCP. The buffer need not be * physically contigiuous, however we should save the sgtable in case the @@ -497,25 +537,68 @@ static struct dcp_allocate_buffer_resp dcpep_cb_allocate_buffer(struct apple_dcp *dcp, struct dcp_allocate_buffer_req *req) { struct dcp_allocate_buffer_resp resp = { 0 }; - void *buf; + struct dcp_mem_descriptor *memdesc; + u32 id; resp.dva_size = ALIGN(req->size, 4096); - resp.mem_desc_id = ++dcp->nr_mappings; + resp.mem_desc_id = find_first_zero_bit(dcp->memdesc_map, DCP_MAX_MAPPINGS); - if (resp.mem_desc_id >= ARRAY_SIZE(dcp->mappings)) { + if (resp.mem_desc_id >= DCP_MAX_MAPPINGS) { dev_warn(dcp->dev, "DCP overflowed mapping table, ignoring"); + resp.dva_size = 0; + resp.mem_desc_id = 0; return resp; } + id = resp.mem_desc_id; + set_bit(id, dcp->memdesc_map); - buf = dma_alloc_coherent(dcp->dev, resp.dva_size, &resp.dva, + memdesc = &dcp->memdesc[id]; + + memdesc->size = resp.dva_size; + memdesc->buf = dma_alloc_coherent(dcp->dev, memdesc->size, &memdesc->dva, GFP_KERNEL); - dma_get_sgtable(dcp->dev, &dcp->mappings[resp.mem_desc_id], buf, - resp.dva, resp.dva_size); - resp.dva |= dcp->asc_dram_mask; + dma_get_sgtable(dcp->dev, &memdesc->map, memdesc->buf, + memdesc->dva, memdesc->size); + resp.dva = memdesc->dva; + return resp; } +static u8 dcpep_cb_release_mem_desc(struct apple_dcp *dcp, u32 *mem_desc_id) +{ + struct dcp_mem_descriptor *memdesc; + u32 id = *mem_desc_id; + + if (id >= DCP_MAX_MAPPINGS) { + dev_warn(dcp->dev, "unmap request for out of range mem_desc_id %u", + id); + return 0; + } + + if (!test_and_clear_bit(id, dcp->memdesc_map)) { + dev_warn(dcp->dev, "unmap request for unused mem_desc_id %u", + id); + return 0; + } + + memdesc = &dcp->memdesc[id]; + if (memdesc->buf) { + + dma_free_coherent(dcp->dev, memdesc->size, memdesc->buf, + memdesc->dva); + + memdesc->buf = NULL; + memset(&memdesc->map, 0, sizeof(memdesc->map)); + } else { + memdesc->reg = 0; + } + + memdesc->size = 0; + + return 1; +} + /* Validate that the specified region is a display register */ static bool is_disp_register(struct apple_dcp *dcp, u64 start, u64 end) { @@ -541,6 +624,7 @@ static struct dcp_map_physical_resp dcpep_cb_map_physical(struct apple_dcp *dcp, struct dcp_map_physical_req *req) { int size = ALIGN(req->size, 4096); + u32 id; if (!is_disp_register(dcp, req->paddr, req->paddr + size - 1)) { dev_err(dcp->dev, "refusing to map phys address %llx size %llx", @@ -548,11 +632,16 @@ dcpep_cb_map_physical(struct apple_dcp *dcp, struct dcp_map_physical_req *req) return (struct dcp_map_physical_resp) { }; } + id = find_first_zero_bit(dcp->memdesc_map, DCP_MAX_MAPPINGS); + set_bit(id, dcp->memdesc_map); + dcp->memdesc[id].size = size; + dcp->memdesc[id].reg = req->paddr; + return (struct dcp_map_physical_resp) { .dva_size = size, - .mem_desc_id = ++dcp->nr_mappings, + .mem_desc_id = id, .dva = dma_map_resource(dcp->dev, req->paddr, size, - DMA_BIDIRECTIONAL, 0) | dcp->asc_dram_mask, + DMA_BIDIRECTIONAL, 0), }; } @@ -1103,11 +1192,15 @@ TRAMPOLINE_INOUT(trampoline_get_uint_prop, dcpep_cb_get_uint_prop, struct dcp_get_uint_prop_req, struct dcp_get_uint_prop_resp); TRAMPOLINE_INOUT(trampoline_map_piodma, dcpep_cb_map_piodma, struct dcp_map_buf_req, struct dcp_map_buf_resp); +TRAMPOLINE_IN(trampoline_unmap_piodma, dcpep_cb_unmap_piodma, + struct dcp_unmap_buf_resp); TRAMPOLINE_INOUT(trampoline_allocate_buffer, dcpep_cb_allocate_buffer, struct dcp_allocate_buffer_req, struct dcp_allocate_buffer_resp); TRAMPOLINE_INOUT(trampoline_map_physical, dcpep_cb_map_physical, struct dcp_map_physical_req, struct dcp_map_physical_resp); +TRAMPOLINE_INOUT(trampoline_release_mem_desc, dcpep_cb_release_mem_desc, + u32, u8); TRAMPOLINE_INOUT(trampoline_map_reg, dcpep_cb_map_reg, struct dcp_map_reg_req, struct dcp_map_reg_resp); TRAMPOLINE_INOUT(trampoline_read_edt_data, dcpep_cb_read_edt_data, @@ -1148,6 +1241,7 @@ bool (*const dcpep_cb_handlers[DCPEP_MAX_CB])(struct apple_dcp *, int, void *, v [123] = trampoline_prop_chunk, [124] = trampoline_prop_end, [201] = trampoline_map_piodma, + [202] = trampoline_unmap_piodma, [206] = trampoline_true, /* match_pmu_service_2 */ [207] = trampoline_true, /* match_backlight_service */ [208] = trampoline_get_time, @@ -1163,6 +1257,7 @@ bool (*const dcpep_cb_handlers[DCPEP_MAX_CB])(struct apple_dcp *, int, void *, v [415] = trampoline_true, /* sr_set_property_bool */ [451] = trampoline_allocate_buffer, [452] = trampoline_map_physical, + [456] = trampoline_release_mem_desc, [552] = trampoline_true, /* set_property_dict_0 */ [561] = trampoline_true, /* set_property_dict */ [563] = trampoline_true, /* set_property_int */ @@ -1768,6 +1863,10 @@ static int dcp_platform_probe(struct platform_device *pdev) dev_warn(dev, "failed read 'apple,asc-dram-mask': %d\n", ret); dev_dbg(dev, "'apple,asc-dram-mask': 0x%011llx\n", dcp->asc_dram_mask); + bitmap_zero(dcp->memdesc_map, DCP_MAX_MAPPINGS); + // TDOD: mem_desc IDs start at 1, for simplicity just skip '0' entry + set_bit(0, dcp->memdesc_map); + INIT_WORK(&dcp->vblank_wq, dcp_delayed_vblank); dcp->swapped_out_fbs = (struct list_head)LIST_HEAD_INIT(dcp->swapped_out_fbs); diff --git a/drivers/gpu/drm/apple/dcpep.h b/drivers/gpu/drm/apple/dcpep.h index 12d81c7b4e2734..7c4fd97c45e54e 100644 --- a/drivers/gpu/drm/apple/dcpep.h +++ b/drivers/gpu/drm/apple/dcpep.h @@ -273,6 +273,14 @@ struct dcp_map_buf_resp { u32 ret; } __packed; +struct dcp_unmap_buf_resp { + u64 buffer; + u64 vaddr; + u64 dva; + u8 unk; + u8 buf_null; +} __packed; + struct dcp_allocate_buffer_req { u32 unk0; u64 size; From ea3eba66766d2eff4e5454c4522d6c5616225007 Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Mon, 6 Jun 2022 12:53:18 +0200 Subject: [PATCH 1004/3902] WIP: drm/apple: Change the way to clear unused surfaces This seems to be incorrect but the flag seems to be related to clearing. Allows unmapping surfaces after the swap finished. Signed-off-by: Janne Grunau --- drivers/gpu/drm/apple/dcp.c | 10 ++++++---- drivers/gpu/drm/apple/dcpep.h | 2 +- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/drivers/gpu/drm/apple/dcp.c b/drivers/gpu/drm/apple/dcp.c index e7e82545367195..41f906d9203e18 100644 --- a/drivers/gpu/drm/apple/dcp.c +++ b/drivers/gpu/drm/apple/dcp.c @@ -1646,10 +1646,12 @@ void dcp_flush(struct drm_crtc *crtc, struct drm_atomic_state *state) } if (!has_surface) { - dev_warn(dcp->dev, "flush without surfaces, vsync:%d", - dcp->crtc->vsync_disabled); - schedule_work(&dcp->vblank_wq); - return; + if (crtc_state->enable && crtc_state->active && !crtc_state->planes_changed) { + schedule_work(&dcp->vblank_wq); + return; + } + + req->clear = 1; } do_swap(dcp, NULL, NULL); } diff --git a/drivers/gpu/drm/apple/dcpep.h b/drivers/gpu/drm/apple/dcpep.h index 7c4fd97c45e54e..a796b16bd55ad7 100644 --- a/drivers/gpu/drm/apple/dcpep.h +++ b/drivers/gpu/drm/apple/dcpep.h @@ -348,7 +348,7 @@ struct dcp_swap_submit_req { u64 surf_iova[SWAP_SURFACES]; u8 unkbool; u64 unkdouble; - u32 unkint; + u32 clear; // or maybe switch to default fb? u8 swap_null; u8 surf_null[SWAP_SURFACES]; u8 unkoutbool_null; From c3463871420e10cc811d3ed940f433b18f7c41cd Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Tue, 27 Sep 2022 00:02:20 +0200 Subject: [PATCH 1005/3902] drm/apple: laod piodma dev via explicit phandle Use a device_link to ensure piodma has is bound to its DART. fixup! WIP: drm/apple: Add DCP display driver Signed-off-by: Janne Grunau --- drivers/gpu/drm/apple/dcp.c | 20 +++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-) diff --git a/drivers/gpu/drm/apple/dcp.c b/drivers/gpu/drm/apple/dcp.c index 41f906d9203e18..6b95b00261df86 100644 --- a/drivers/gpu/drm/apple/dcp.c +++ b/drivers/gpu/drm/apple/dcp.c @@ -76,6 +76,7 @@ struct dcp_mem_descriptor { struct apple_dcp { struct device *dev; struct platform_device *piodma; + struct device_link *piodma_link; struct apple_rtkit *rtk; struct apple_crtc *crtc; struct apple_connector *connector; @@ -1792,12 +1793,15 @@ EXPORT_SYMBOL_GPL(dcp_link); static struct platform_device *dcp_get_dev(struct device *dev, const char *name) { - struct device_node *node = of_get_child_by_name(dev->of_node, name); + struct platform_device *pdev; + struct device_node *node = of_parse_phandle(dev->of_node, name, 0); if (!node) return NULL; - return of_find_device_by_node(node); + pdev = of_find_device_by_node(node); + of_node_put(node); + return pdev; } static int dcp_get_disp_regs(struct apple_dcp *dcp) @@ -1843,12 +1847,22 @@ static int dcp_platform_probe(struct platform_device *pdev) of_platform_default_populate(dev->of_node, NULL, dev); - dcp->piodma = dcp_get_dev(dev, "piodma"); + dcp->piodma = dcp_get_dev(dev, "apple,piodma-mapper"); if (!dcp->piodma) { dev_err(dev, "failed to find piodma\n"); return -ENODEV; } + dcp->piodma_link = device_link_add(dev, &dcp->piodma->dev, + DL_FLAG_AUTOREMOVE_CONSUMER); + if (!dcp->piodma_link) { + dev_err(dev, "Failed to link to piodma device"); + return -EINVAL; + } + + if (dcp->piodma_link->supplier->links.status != DL_DEV_DRIVER_BOUND) + return -EPROBE_DEFER; + ret = dcp_get_disp_regs(dcp); if (ret) { dev_err(dev, "failed to find display registers\n"); From bdab3059e9365e3756d8ca262777c0d460783767 Mon Sep 17 00:00:00 2001 From: Asahi Lina Date: Wed, 28 Sep 2022 17:47:01 +0900 Subject: [PATCH 1006/3902] drm/apple: Fix kzalloc in dcp_flush() Signed-off-by: Asahi Lina --- drivers/gpu/drm/apple/dcp.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/gpu/drm/apple/dcp.c b/drivers/gpu/drm/apple/dcp.c index 6b95b00261df86..edc492f9d8d903 100644 --- a/drivers/gpu/drm/apple/dcp.c +++ b/drivers/gpu/drm/apple/dcp.c @@ -1615,7 +1615,7 @@ void dcp_flush(struct drm_crtc *crtc, struct drm_atomic_state *state) .timing_mode_id = mode->timing_mode_id }; - cookie = kzalloc(sizeof(cookie), GFP_KERNEL); + cookie = kzalloc(sizeof(*cookie), GFP_KERNEL); if (!cookie) { schedule_work(&dcp->vblank_wq); return; From 2396861bba393fd268a50de8040d1d41de3af477 Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Sat, 1 Oct 2022 09:48:08 +0200 Subject: [PATCH 1007/3902] drm/apple: Allow modesets even when disconnected Fixes a display wakeup issue seen with Plasma/X11. Signed-off-by: Janne Grunau --- drivers/gpu/drm/apple/dcp.c | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/drivers/gpu/drm/apple/dcp.c b/drivers/gpu/drm/apple/dcp.c index edc492f9d8d903..622d2ce2dfc6e5 100644 --- a/drivers/gpu/drm/apple/dcp.c +++ b/drivers/gpu/drm/apple/dcp.c @@ -1512,12 +1512,15 @@ void dcp_flush(struct drm_crtc *crtc, struct drm_atomic_state *state) struct dcp_swap_submit_req *req = &dcp->swap; int l; int has_surface = 0; + bool modeset; dev_dbg(dcp->dev, "%s", __func__); crtc_state = drm_atomic_get_new_crtc_state(state, crtc); + modeset = drm_atomic_crtc_needs_modeset(crtc_state) || !dcp->valid_mode; + if (WARN(dcp_channel_busy(&dcp->ch_cmd), "unexpected busy channel") || - WARN(!dcp->connector->connected, "can't flush if disconnected")) { + WARN(!modeset && !dcp->connector->connected, "can't flush if disconnected")) { /* HACK: issue a delayed vblank event to avoid timeouts in * drm_atomic_helper_wait_for_vblanks(). */ @@ -1595,7 +1598,7 @@ void dcp_flush(struct drm_crtc *crtc, struct drm_atomic_state *state) /* These fields should be set together */ req->swap.swap_completed = req->swap.swap_enabled; - if (drm_atomic_crtc_needs_modeset(crtc_state) || !dcp->valid_mode) { + if (modeset) { struct dcp_display_mode *mode; struct dcp_wait_cookie *cookie; int ret; From 6919e09efb38b3476fc02ae9bfd6c9f9e29c5182 Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Sat, 1 Oct 2022 11:05:56 +0200 Subject: [PATCH 1008/3902] drm/apple: Mark the connecter on init only with modes as connected Signed-off-by: Janne Grunau --- drivers/gpu/drm/apple/apple_drv.c | 2 +- drivers/gpu/drm/apple/dcp.c | 10 ++++++++++ 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/drivers/gpu/drm/apple/apple_drv.c b/drivers/gpu/drm/apple/apple_drv.c index a7b90daa39d917..b394bf52720d1a 100644 --- a/drivers/gpu/drm/apple/apple_drv.c +++ b/drivers/gpu/drm/apple/apple_drv.c @@ -331,7 +331,7 @@ static int apple_probe_per_dcp(struct device *dev, return ret; connector->base.polled = DRM_CONNECTOR_POLL_HPD; - connector->connected = true; /* XXX */ + connector->connected = false; connector->dcp = dcp; INIT_WORK(&connector->hotplug_wq, dcp_hotplug); diff --git a/drivers/gpu/drm/apple/dcp.c b/drivers/gpu/drm/apple/dcp.c index 622d2ce2dfc6e5..73801996e7f5fa 100644 --- a/drivers/gpu/drm/apple/dcp.c +++ b/drivers/gpu/drm/apple/dcp.c @@ -1672,12 +1672,19 @@ EXPORT_SYMBOL_GPL(dcp_is_initialized); static void res_is_main_display(struct apple_dcp *dcp, void *out, void *cookie) { + struct apple_connector *connector; int result = *(int *)out; dev_info(dcp->dev, "DCP is_main_display: %d\n", result); dcp->main_display = result != 0; dcp->active = true; + + connector = dcp->connector; + if (connector) { + connector->connected = dcp->nr_modes > 0; + schedule_work(&connector->hotplug_wq); + } } static void init_3(struct apple_dcp *dcp, void *out, void *cookie) @@ -1789,6 +1796,9 @@ void dcp_link(struct platform_device *pdev, struct apple_crtc *crtc, dcp->crtc = crtc; dcp->connector = connector; + /* init connector status by modes offered by dcp */ + connector->connected = dcp->nr_modes > 0; + /* Dimensions might already be parsed */ dcp_set_dimensions(dcp); } From 95822fdccfe74d2a97e568b99cf9bdf53933671f Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Sat, 1 Oct 2022 14:46:11 +0200 Subject: [PATCH 1009/3902] drm/apple: make note about drm.mode_config.max_width/height Signed-off-by: Janne Grunau --- drivers/gpu/drm/apple/apple_drv.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/drivers/gpu/drm/apple/apple_drv.c b/drivers/gpu/drm/apple/apple_drv.c index b394bf52720d1a..620d44719b45fd 100644 --- a/drivers/gpu/drm/apple/apple_drv.c +++ b/drivers/gpu/drm/apple/apple_drv.c @@ -400,6 +400,8 @@ static int apple_platform_probe(struct platform_device *pdev) /* Unknown maximum, use the iMac (24-inch, 2021) display resolution as * maximum. + * TODO: this is the max framebuffer size not the maximal supported output + * resolution. DCP reports the maximal framebuffer size take it from there. */ apple->drm.mode_config.max_width = 4480; apple->drm.mode_config.max_height = 2520; From 3d6b0e9b6c18d1b33337f4ae8c4c641a93342422 Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Sun, 2 Oct 2022 18:22:32 +0200 Subject: [PATCH 1010/3902] drm/apple: Split dcpep/iomfb out of dcp.c For external display support DCP will use more endpoints. Signed-off-by: Janne Grunau --- drivers/gpu/drm/apple/Makefile | 2 +- drivers/gpu/drm/apple/dcp-internal.h | 133 ++ drivers/gpu/drm/apple/dcp.c | 1758 +------------------- drivers/gpu/drm/apple/dcp.h | 11 + drivers/gpu/drm/apple/iomfb.c | 1626 ++++++++++++++++++ drivers/gpu/drm/apple/{dcpep.h => iomfb.h} | 48 +- 6 files changed, 1837 insertions(+), 1741 deletions(-) create mode 100644 drivers/gpu/drm/apple/dcp-internal.h create mode 100644 drivers/gpu/drm/apple/iomfb.c rename drivers/gpu/drm/apple/{dcpep.h => iomfb.h} (86%) diff --git a/drivers/gpu/drm/apple/Makefile b/drivers/gpu/drm/apple/Makefile index 8c758d5720b642..d1f909792229e5 100644 --- a/drivers/gpu/drm/apple/Makefile +++ b/drivers/gpu/drm/apple/Makefile @@ -1,7 +1,7 @@ # SPDX-License-Identifier: GPL-2.0-only OR MIT appledrm-y := apple_drv.o -apple_dcp-y := dcp.o parser.o +apple_dcp-y := dcp.o iomfb.o parser.o apple_piodma-y := dummy-piodma.o obj-$(CONFIG_DRM_APPLE) += appledrm.o diff --git a/drivers/gpu/drm/apple/dcp-internal.h b/drivers/gpu/drm/apple/dcp-internal.h new file mode 100644 index 00000000000000..648d543b9cd91a --- /dev/null +++ b/drivers/gpu/drm/apple/dcp-internal.h @@ -0,0 +1,133 @@ +// SPDX-License-Identifier: GPL-2.0-only OR MIT +/* Copyright 2021 Alyssa Rosenzweig */ + +#ifndef __APPLE_DCP_INTERNAL_H__ +#define __APPLE_DCP_INTERNAL_H__ + +#include +#include +#include + +#include "iomfb.h" + +struct apple_dcp; + +/* Temporary backing for a chunked transfer via setDCPAVPropStart/Chunk/End */ +struct dcp_chunks { + size_t length; + void *data; +}; + +#define DCP_MAX_MAPPINGS (128) /* should be enough */ +#define MAX_DISP_REGISTERS (7) + +struct dcp_mem_descriptor { + size_t size; + void *buf; + dma_addr_t dva; + struct sg_table map; + u64 reg; +}; + +/* Limit on call stack depth (arbitrary). Some nesting is required */ +#define DCP_MAX_CALL_DEPTH 8 + +typedef void (*dcp_callback_t)(struct apple_dcp *, void *, void *); + +struct dcp_call_channel { + dcp_callback_t callbacks[DCP_MAX_CALL_DEPTH]; + void *cookies[DCP_MAX_CALL_DEPTH]; + void *output[DCP_MAX_CALL_DEPTH]; + u16 end[DCP_MAX_CALL_DEPTH]; + + /* Current depth of the call stack. Less than DCP_MAX_CALL_DEPTH */ + u8 depth; +}; + +struct dcp_cb_channel { + u8 depth; + void *output[DCP_MAX_CALL_DEPTH]; +}; + +struct dcp_fb_reference { + struct list_head head; + struct drm_framebuffer *fb; +}; + +/* TODO: move IOMFB members to its own struct */ +struct apple_dcp { + struct device *dev; + struct platform_device *piodma; + struct device_link *piodma_link; + struct apple_rtkit *rtk; + struct apple_crtc *crtc; + struct apple_connector *connector; + + /* Coprocessor control register */ + void __iomem *coproc_reg; + + /* mask for DCP IO virtual addresses shared over rtkit */ + u64 asc_dram_mask; + + /* DCP has crashed */ + bool crashed; + + /************* IOMFB ************************************************** + * everything below is mostly used inside IOMFB but it could make * + * sense keep some of the the members in apple_dcp. * + **********************************************************************/ + + /* clock rate request by dcp in */ + struct clk *clk; + + /* DCP shared memory */ + void *shmem; + + /* Display registers mappable to the DCP */ + struct resource *disp_registers[MAX_DISP_REGISTERS]; + unsigned int nr_disp_registers; + + /* Bitmap of memory descriptors used for mappings made by the DCP */ + DECLARE_BITMAP(memdesc_map, DCP_MAX_MAPPINGS); + + /* Indexed table of memory descriptors */ + struct dcp_mem_descriptor memdesc[DCP_MAX_MAPPINGS]; + + struct dcp_call_channel ch_cmd, ch_oobcmd; + struct dcp_cb_channel ch_cb, ch_oobcb, ch_async; + + /* Active chunked transfer. There can only be one at a time. */ + struct dcp_chunks chunks; + + /* Queued swap. Owned by the DCP to avoid per-swap memory allocation */ + struct dcp_swap_submit_req swap; + + /* Current display mode */ + bool valid_mode; + struct dcp_set_digital_out_mode_req mode; + + /* Is the DCP booted? */ + bool active; + + /* eDP display without DP-HDMI conversion */ + bool main_display; + + bool ignore_swap_complete; + + /* Modes valid for the connected display */ + struct dcp_display_mode *modes; + unsigned int nr_modes; + + /* Attributes of the connected display */ + int width_mm, height_mm; + + /* Workqueue for sending vblank events when a dcp swap is not possible */ + struct work_struct vblank_wq; + + /* List of referenced drm_framebuffers which can be unreferenced + * on the next successfully completed swap. + */ + struct list_head swapped_out_fbs; +}; + +#endif /* __APPLE_DCP_INTERNAL_H__ */ diff --git a/drivers/gpu/drm/apple/dcp.c b/drivers/gpu/drm/apple/dcp.c index 73801996e7f5fa..60bbedcca8f589 100644 --- a/drivers/gpu/drm/apple/dcp.c +++ b/drivers/gpu/drm/apple/dcp.c @@ -19,1105 +19,59 @@ #include #include -#include "dcpep.h" #include "dcp.h" +#include "dcp-internal.h" #include "parser.h" -struct apple_dcp; - -#define APPLE_DCP_COPROC_CPU_CONTROL 0x44 -#define APPLE_DCP_COPROC_CPU_CONTROL_RUN BIT(4) - -/* Register defines used in bandwidth setup structure */ -#define REG_SCRATCH (0x14) -#define REG_SCRATCH_T600X (0x988) -#define REG_DOORBELL (0x0) -#define REG_DOORBELL_BIT (2) - -#define DCP_BOOT_TIMEOUT msecs_to_jiffies(1000) - -/* Limit on call stack depth (arbitrary). Some nesting is required */ -#define DCP_MAX_CALL_DEPTH 8 - -typedef void (*dcp_callback_t)(struct apple_dcp *, void *, void *); - -struct dcp_call_channel { - dcp_callback_t callbacks[DCP_MAX_CALL_DEPTH]; - void *cookies[DCP_MAX_CALL_DEPTH]; - void *output[DCP_MAX_CALL_DEPTH]; - u16 end[DCP_MAX_CALL_DEPTH]; - - /* Current depth of the call stack. Less than DCP_MAX_CALL_DEPTH */ - u8 depth; -}; - -struct dcp_cb_channel { - u8 depth; - void *output[DCP_MAX_CALL_DEPTH]; -}; - -/* Temporary backing for a chunked transfer via setDCPAVPropStart/Chunk/End */ -struct dcp_chunks { - size_t length; - void *data; -}; - -#define DCP_MAX_MAPPINGS (128) /* should be enough */ -#define MAX_DISP_REGISTERS (7) - -struct dcp_mem_descriptor { - size_t size; - void *buf; - dma_addr_t dva; - struct sg_table map; - u64 reg; -}; - -struct apple_dcp { - struct device *dev; - struct platform_device *piodma; - struct device_link *piodma_link; - struct apple_rtkit *rtk; - struct apple_crtc *crtc; - struct apple_connector *connector; - - /* DCP has crashed */ - bool crashed; - - /* clock rate request by dcp in */ - struct clk *clk; - - /* mask for DCP IO virtual addresses shared over rtkit */ - u64 asc_dram_mask; - - /* DCP shared memory */ - void *shmem; - - /* Coprocessor control register */ - void __iomem *coproc_reg; - - /* Display registers mappable to the DCP */ - struct resource *disp_registers[MAX_DISP_REGISTERS]; - unsigned int nr_disp_registers; - - /* Bitmap of memory descriptors used for mappings made by the DCP */ - DECLARE_BITMAP(memdesc_map, DCP_MAX_MAPPINGS); - - /* Indexed table of memory descriptors */ - struct dcp_mem_descriptor memdesc[DCP_MAX_MAPPINGS]; - - struct dcp_call_channel ch_cmd, ch_oobcmd; - struct dcp_cb_channel ch_cb, ch_oobcb, ch_async; - - /* Active chunked transfer. There can only be one at a time. */ - struct dcp_chunks chunks; - - /* Queued swap. Owned by the DCP to avoid per-swap memory allocation */ - struct dcp_swap_submit_req swap; - - /* Current display mode */ - bool valid_mode; - struct dcp_set_digital_out_mode_req mode; - - /* Is the DCP booted? */ - bool active; - - /* eDP display without DP-HDMI conversion */ - bool main_display; - - bool ignore_swap_complete; - - /* Modes valid for the connected display */ - struct dcp_display_mode *modes; - unsigned int nr_modes; - - /* Attributes of the connected display */ - int width_mm, height_mm; - - /* Workqueue for sending vblank events when a dcp swap is not possible */ - struct work_struct vblank_wq; - - /* List of referenced drm_framebuffers which can be unreferenced - * on the next successfully completed swap. - */ - struct list_head swapped_out_fbs; -}; - -struct dcp_fb_reference { - struct list_head head; - struct drm_framebuffer *fb; -}; - -struct dcp_wait_cookie { - struct completion done; - atomic_t refcount; -}; - -/* - * A channel is busy if we have sent a message that has yet to be - * acked. The driver must not sent a message to a busy channel. - */ -static bool dcp_channel_busy(struct dcp_call_channel *ch) -{ - return (ch->depth != 0); -} - -/* Get a call channel for a context */ -static struct dcp_call_channel * -dcp_get_call_channel(struct apple_dcp *dcp, enum dcp_context_id context) -{ - switch (context) { - case DCP_CONTEXT_CMD: - case DCP_CONTEXT_CB: - return &dcp->ch_cmd; - case DCP_CONTEXT_OOBCMD: - case DCP_CONTEXT_OOBCB: - return &dcp->ch_oobcmd; - default: - return NULL; - } -} - -/* - * Get the context ID passed to the DCP for a command we push. The rule is - * simple: callback contexts are used when replying to the DCP, command - * contexts are used otherwise. That corresponds to a non/zero call stack - * depth. This rule frees the caller from tracking the call context manually. - */ -static enum dcp_context_id dcp_call_context(struct apple_dcp *dcp, bool oob) -{ - u8 depth = oob ? dcp->ch_oobcmd.depth : dcp->ch_cmd.depth; - - if (depth) - return oob ? DCP_CONTEXT_OOBCB : DCP_CONTEXT_CB; - else - return oob ? DCP_CONTEXT_OOBCMD : DCP_CONTEXT_CMD; -} - -/* Get a callback channel for a context */ -static struct dcp_cb_channel *dcp_get_cb_channel(struct apple_dcp *dcp, - enum dcp_context_id context) -{ - switch (context) { - case DCP_CONTEXT_CB: - return &dcp->ch_cb; - case DCP_CONTEXT_OOBCB: - return &dcp->ch_oobcb; - case DCP_CONTEXT_ASYNC: - return &dcp->ch_async; - default: - return NULL; - } -} - -/* Get the start of a packet: after the end of the previous packet */ -static u16 dcp_packet_start(struct dcp_call_channel *ch, u8 depth) -{ - if (depth > 0) - return ch->end[depth - 1]; - else - return 0; -} - -/* Pushes and pops the depth of the call stack with safety checks */ -static u8 dcp_push_depth(u8 *depth) -{ - u8 ret = (*depth)++; - - WARN_ON(ret >= DCP_MAX_CALL_DEPTH); - return ret; -} - -static u8 dcp_pop_depth(u8 *depth) -{ - WARN_ON((*depth) == 0); - - return --(*depth); -} - -#define DCP_METHOD(tag, name) [name] = { #name, tag } - -const struct dcp_method_entry dcp_methods[dcpep_num_methods] = { - DCP_METHOD("A000", dcpep_late_init_signal), - DCP_METHOD("A029", dcpep_setup_video_limits), - DCP_METHOD("A034", dcpep_update_notify_clients_dcp), - DCP_METHOD("A357", dcpep_set_create_dfb), - DCP_METHOD("A401", dcpep_start_signal), - DCP_METHOD("A407", dcpep_swap_start), - DCP_METHOD("A408", dcpep_swap_submit), - DCP_METHOD("A410", dcpep_set_display_device), - DCP_METHOD("A411", dcpep_is_main_display), - DCP_METHOD("A412", dcpep_set_digital_out_mode), - DCP_METHOD("A439", dcpep_set_parameter_dcp), - DCP_METHOD("A443", dcpep_create_default_fb), - DCP_METHOD("A447", dcpep_enable_disable_video_power_savings), - DCP_METHOD("A454", dcpep_first_client_open), - DCP_METHOD("A460", dcpep_set_display_refresh_properties), - DCP_METHOD("A463", dcpep_flush_supports_power), - DCP_METHOD("A468", dcpep_set_power_state), -}; - -/* Call a DCP function given by a tag */ -static void dcp_push(struct apple_dcp *dcp, bool oob, enum dcpep_method method, - u32 in_len, u32 out_len, void *data, dcp_callback_t cb, - void *cookie) -{ - struct dcp_call_channel *ch = oob ? &dcp->ch_oobcmd : &dcp->ch_cmd; - enum dcp_context_id context = dcp_call_context(dcp, oob); - - struct dcp_packet_header header = { - .in_len = in_len, - .out_len = out_len, - - /* Tag is reversed due to endianness of the fourcc */ - .tag[0] = dcp_methods[method].tag[3], - .tag[1] = dcp_methods[method].tag[2], - .tag[2] = dcp_methods[method].tag[1], - .tag[3] = dcp_methods[method].tag[0], - }; - - u8 depth = dcp_push_depth(&ch->depth); - u16 offset = dcp_packet_start(ch, depth); - - void *out = dcp->shmem + dcp_tx_offset(context) + offset; - void *out_data = out + sizeof(header); - size_t data_len = sizeof(header) + in_len + out_len; - - memcpy(out, &header, sizeof(header)); - - if (in_len > 0) - memcpy(out_data, data, in_len); - - dev_dbg(dcp->dev, "---> %s: context %u, offset %u, depth %u\n", - dcp_methods[method].name, context, offset, depth); - - ch->callbacks[depth] = cb; - ch->cookies[depth] = cookie; - ch->output[depth] = out + sizeof(header) + in_len; - ch->end[depth] = offset + ALIGN(data_len, DCP_PACKET_ALIGNMENT); - - apple_rtkit_send_message(dcp->rtk, DCP_ENDPOINT, - dcpep_msg(context, data_len, offset), - NULL, false); -} - -#define DCP_THUNK_VOID(func, handle) \ - static void func(struct apple_dcp *dcp, bool oob, dcp_callback_t cb, \ - void *cookie) \ - { \ - dcp_push(dcp, oob, handle, 0, 0, NULL, cb, cookie); \ - } - -#define DCP_THUNK_OUT(func, handle, T) \ - static void func(struct apple_dcp *dcp, bool oob, dcp_callback_t cb, \ - void *cookie) \ - { \ - dcp_push(dcp, oob, handle, 0, sizeof(T), NULL, cb, cookie); \ - } - -#define DCP_THUNK_IN(func, handle, T) \ - static void func(struct apple_dcp *dcp, bool oob, T *data, \ - dcp_callback_t cb, void *cookie) \ - { \ - dcp_push(dcp, oob, handle, sizeof(T), 0, data, cb, cookie); \ - } - -#define DCP_THUNK_INOUT(func, handle, T_in, T_out) \ - static void func(struct apple_dcp *dcp, bool oob, T_in *data, \ - dcp_callback_t cb, void *cookie) \ - { \ - dcp_push(dcp, oob, handle, sizeof(T_in), sizeof(T_out), data, \ - cb, cookie); \ - } - -DCP_THUNK_INOUT(dcp_swap_submit, dcpep_swap_submit, struct dcp_swap_submit_req, - struct dcp_swap_submit_resp); - -DCP_THUNK_INOUT(dcp_swap_start, dcpep_swap_start, struct dcp_swap_start_req, - struct dcp_swap_start_resp); - -DCP_THUNK_INOUT(dcp_set_power_state, dcpep_set_power_state, - struct dcp_set_power_state_req, - struct dcp_set_power_state_resp); - -DCP_THUNK_INOUT(dcp_set_digital_out_mode, dcpep_set_digital_out_mode, - struct dcp_set_digital_out_mode_req, u32); - -DCP_THUNK_INOUT(dcp_set_display_device, dcpep_set_display_device, u32, u32); - -DCP_THUNK_OUT(dcp_set_display_refresh_properties, - dcpep_set_display_refresh_properties, u32); - -DCP_THUNK_OUT(dcp_late_init_signal, dcpep_late_init_signal, u32); -DCP_THUNK_IN(dcp_flush_supports_power, dcpep_flush_supports_power, u32); -DCP_THUNK_OUT(dcp_create_default_fb, dcpep_create_default_fb, u32); -DCP_THUNK_OUT(dcp_start_signal, dcpep_start_signal, u32); -DCP_THUNK_VOID(dcp_setup_video_limits, dcpep_setup_video_limits); -DCP_THUNK_VOID(dcp_set_create_dfb, dcpep_set_create_dfb); -DCP_THUNK_VOID(dcp_first_client_open, dcpep_first_client_open); - -__attribute__((unused)) -DCP_THUNK_IN(dcp_update_notify_clients_dcp, dcpep_update_notify_clients_dcp, - struct dcp_update_notify_clients_dcp); - -DCP_THUNK_INOUT(dcp_set_parameter_dcp, dcpep_set_parameter_dcp, - struct dcp_set_parameter_dcp, u32); - -DCP_THUNK_INOUT(dcp_enable_disable_video_power_savings, - dcpep_enable_disable_video_power_savings, - u32, int); - -DCP_THUNK_OUT(dcp_is_main_display, dcpep_is_main_display, u32); - -/* Parse a callback tag "D123" into the ID 123. Returns -EINVAL on failure. */ -static int dcp_parse_tag(char tag[4]) -{ - u32 d[3]; - int i; - - if (tag[3] != 'D') - return -EINVAL; - - for (i = 0; i < 3; ++i) { - d[i] = (u32)(tag[i] - '0'); - - if (d[i] > 9) - return -EINVAL; - } - - return d[0] + (d[1] * 10) + (d[2] * 100); -} - -/* Ack a callback from the DCP */ -static void dcp_ack(struct apple_dcp *dcp, enum dcp_context_id context) -{ - struct dcp_cb_channel *ch = dcp_get_cb_channel(dcp, context); - - dcp_pop_depth(&ch->depth); - apple_rtkit_send_message(dcp->rtk, DCP_ENDPOINT, dcpep_ack(context), - NULL, false); -} - -/* DCP callback handlers */ -static void dcpep_cb_nop(struct apple_dcp *dcp) -{ - /* No operation */ -} - -static u8 dcpep_cb_true(struct apple_dcp *dcp) -{ - return true; -} - -static u8 dcpep_cb_false(struct apple_dcp *dcp) -{ - return false; -} - -static u32 dcpep_cb_zero(struct apple_dcp *dcp) -{ - return 0; -} - -/* HACK: moved here to avoid circular dependency between apple_drv and dcp */ -void dcp_drm_crtc_vblank(struct apple_crtc *crtc) -{ - unsigned long flags; - - if (crtc->vsync_disabled) - return; - - drm_crtc_handle_vblank(&crtc->base); - - spin_lock_irqsave(&crtc->base.dev->event_lock, flags); - if (crtc->event) { - drm_crtc_send_vblank_event(&crtc->base, crtc->event); - drm_crtc_vblank_put(&crtc->base); - crtc->event = NULL; - } - spin_unlock_irqrestore(&crtc->base.dev->event_lock, flags); -} - -static void dcpep_cb_swap_complete(struct apple_dcp *dcp, - struct dc_swap_complete_resp *resp) -{ - dev_dbg(dcp->dev, "swap complete for swap_id: %u vblank: %u", - resp->swap_id, dcp->ignore_swap_complete); - - if (!dcp->ignore_swap_complete) - dcp_drm_crtc_vblank(dcp->crtc); -} - -static struct dcp_get_uint_prop_resp -dcpep_cb_get_uint_prop(struct apple_dcp *dcp, struct dcp_get_uint_prop_req *req) -{ - /* unimplemented for now */ - return (struct dcp_get_uint_prop_resp) { - .value = 0 - }; -} - -/* - * Callback to map a buffer allocated with allocate_buf for PIODMA usage. - * PIODMA is separate from the main DCP and uses own IOVA space on a dedicated - * stream of the display DART, rather than the expected DCP DART. - * - * XXX: This relies on dma_get_sgtable in concert with dma_map_sgtable, which - * is a "fundamentally unsafe" operation according to the docs. And yet - * everyone does it... - */ -static struct dcp_map_buf_resp -dcpep_cb_map_piodma(struct apple_dcp *dcp, struct dcp_map_buf_req *req) -{ - struct sg_table *map; - int ret; - - if (req->buffer >= ARRAY_SIZE(dcp->memdesc)) - goto reject; - - map = &dcp->memdesc[req->buffer].map; - - if (!map->sgl) - goto reject; - - /* Use PIODMA device instead of DCP to map against the right IOMMU. */ - ret = dma_map_sgtable(&dcp->piodma->dev, map, DMA_BIDIRECTIONAL, 0); - - if (ret) - goto reject; - - return (struct dcp_map_buf_resp) { - .dva = sg_dma_address(map->sgl) - }; - -reject: - dev_err(dcp->dev, "denying map of invalid buffer %llx for pidoma\n", - req->buffer); - return (struct dcp_map_buf_resp) { - .ret = EINVAL - }; -} - -static void -dcpep_cb_unmap_piodma(struct apple_dcp *dcp, struct dcp_unmap_buf_resp *resp) -{ - struct sg_table *map; - dma_addr_t dma_addr; - - if (resp->buffer >= ARRAY_SIZE(dcp->memdesc)) { - dev_warn(dcp->dev, "unmap request for out of range buffer %llu", - resp->buffer); - return; - } - - map = &dcp->memdesc[resp->buffer].map; - - if (!map->sgl) { - dev_warn(dcp->dev, "unmap for non-mapped buffer %llu iova:0x%08llx", - resp->buffer, resp->dva); - return; - } - - dma_addr = sg_dma_address(map->sgl); - if (dma_addr != resp->dva) { - dev_warn(dcp->dev, "unmap buffer %llu address mismatch dma_addr:%llx dva:%llx", - resp->buffer, dma_addr, resp->dva); - return; - } - - /* Use PIODMA device instead of DCP to unmap from the right IOMMU. */ - dma_unmap_sgtable(&dcp->piodma->dev, map, DMA_BIDIRECTIONAL, 0); -} - -/* - * Allocate an IOVA contiguous buffer mapped to the DCP. The buffer need not be - * physically contigiuous, however we should save the sgtable in case the - * buffer needs to be later mapped for PIODMA. - */ -static struct dcp_allocate_buffer_resp -dcpep_cb_allocate_buffer(struct apple_dcp *dcp, struct dcp_allocate_buffer_req *req) -{ - struct dcp_allocate_buffer_resp resp = { 0 }; - struct dcp_mem_descriptor *memdesc; - u32 id; - - resp.dva_size = ALIGN(req->size, 4096); - resp.mem_desc_id = find_first_zero_bit(dcp->memdesc_map, DCP_MAX_MAPPINGS); - - if (resp.mem_desc_id >= DCP_MAX_MAPPINGS) { - dev_warn(dcp->dev, "DCP overflowed mapping table, ignoring"); - resp.dva_size = 0; - resp.mem_desc_id = 0; - return resp; - } - id = resp.mem_desc_id; - set_bit(id, dcp->memdesc_map); - - memdesc = &dcp->memdesc[id]; - - memdesc->size = resp.dva_size; - memdesc->buf = dma_alloc_coherent(dcp->dev, memdesc->size, &memdesc->dva, - GFP_KERNEL); - - dma_get_sgtable(dcp->dev, &memdesc->map, memdesc->buf, - memdesc->dva, memdesc->size); - resp.dva = memdesc->dva; - - return resp; -} - -static u8 dcpep_cb_release_mem_desc(struct apple_dcp *dcp, u32 *mem_desc_id) -{ - struct dcp_mem_descriptor *memdesc; - u32 id = *mem_desc_id; - - if (id >= DCP_MAX_MAPPINGS) { - dev_warn(dcp->dev, "unmap request for out of range mem_desc_id %u", - id); - return 0; - } - - if (!test_and_clear_bit(id, dcp->memdesc_map)) { - dev_warn(dcp->dev, "unmap request for unused mem_desc_id %u", - id); - return 0; - } - - memdesc = &dcp->memdesc[id]; - if (memdesc->buf) { - - dma_free_coherent(dcp->dev, memdesc->size, memdesc->buf, - memdesc->dva); - - memdesc->buf = NULL; - memset(&memdesc->map, 0, sizeof(memdesc->map)); - } else { - memdesc->reg = 0; - } - - memdesc->size = 0; - - return 1; -} - -/* Validate that the specified region is a display register */ -static bool is_disp_register(struct apple_dcp *dcp, u64 start, u64 end) -{ - int i; - - for (i = 0; i < dcp->nr_disp_registers; ++i) { - struct resource *r = dcp->disp_registers[i]; - - if ((start >= r->start) && (end <= r->end)) - return true; - } - - return false; -} - -/* - * Map contiguous physical memory into the DCP's address space. The firmware - * uses this to map the display registers we advertise in - * sr_map_device_memory_with_index, so we bounds check against that to guard - * safe against malicious coprocessors. - */ -static struct dcp_map_physical_resp -dcpep_cb_map_physical(struct apple_dcp *dcp, struct dcp_map_physical_req *req) -{ - int size = ALIGN(req->size, 4096); - u32 id; - - if (!is_disp_register(dcp, req->paddr, req->paddr + size - 1)) { - dev_err(dcp->dev, "refusing to map phys address %llx size %llx", - req->paddr, req->size); - return (struct dcp_map_physical_resp) { }; - } - - id = find_first_zero_bit(dcp->memdesc_map, DCP_MAX_MAPPINGS); - set_bit(id, dcp->memdesc_map); - dcp->memdesc[id].size = size; - dcp->memdesc[id].reg = req->paddr; - - return (struct dcp_map_physical_resp) { - .dva_size = size, - .mem_desc_id = id, - .dva = dma_map_resource(dcp->dev, req->paddr, size, - DMA_BIDIRECTIONAL, 0), - }; -} - -static u64 dcpep_cb_get_frequency(struct apple_dcp *dcp) -{ - return clk_get_rate(dcp->clk); -} - -static struct dcp_map_reg_resp -dcpep_cb_map_reg(struct apple_dcp *dcp, struct dcp_map_reg_req *req) -{ - if (req->index >= dcp->nr_disp_registers) { - dev_warn(dcp->dev, "attempted to read invalid reg index %u", - req->index); - - return (struct dcp_map_reg_resp) { - .ret = 1 - }; - } else { - struct resource *rsrc = dcp->disp_registers[req->index]; - - return (struct dcp_map_reg_resp) { - .addr = rsrc->start, - .length = resource_size(rsrc) - }; - } -} - -static struct dcp_read_edt_data_resp -dcpep_cb_read_edt_data(struct apple_dcp *dcp, struct dcp_read_edt_data_req *req) -{ - return (struct dcp_read_edt_data_resp) { - .value[0] = req->value[0], - .ret = 0, - }; -} - -/* Chunked data transfer for property dictionaries */ -static u8 dcpep_cb_prop_start(struct apple_dcp *dcp, u32 *length) -{ - if (dcp->chunks.data != NULL) { - dev_warn(dcp->dev, "ignoring spurious transfer start\n"); - return false; - } - - dcp->chunks.length = *length; - dcp->chunks.data = devm_kzalloc(dcp->dev, *length, GFP_KERNEL); - - if (!dcp->chunks.data) { - dev_warn(dcp->dev, "failed to allocate chunks\n"); - return false; - } - - return true; -} - -static u8 dcpep_cb_prop_chunk(struct apple_dcp *dcp, - struct dcp_set_dcpav_prop_chunk_req *req) -{ - if (!dcp->chunks.data) { - dev_warn(dcp->dev, "ignoring spurious chunk\n"); - return false; - } - - if (req->offset + req->length > dcp->chunks.length) { - dev_warn(dcp->dev, "ignoring overflowing chunk\n"); - return false; - } - - memcpy(dcp->chunks.data + req->offset, req->data, req->length); - return true; -} - -static void dcp_set_dimensions(struct apple_dcp *dcp) -{ - int i; - - /* Set the connector info */ - if (dcp->connector) { - struct drm_connector *connector = &dcp->connector->base; - - mutex_lock(&connector->dev->mode_config.mutex); - connector->display_info.width_mm = dcp->width_mm; - connector->display_info.height_mm = dcp->height_mm; - mutex_unlock(&connector->dev->mode_config.mutex); - } - - /* - * Fix up any probed modes. Modes are created when parsing - * TimingElements, dimensions are calculated when parsing - * DisplayAttributes, and TimingElements may be sent first - */ - for (i = 0; i < dcp->nr_modes; ++i) { - dcp->modes[i].mode.width_mm = dcp->width_mm; - dcp->modes[i].mode.height_mm = dcp->height_mm; - } -} - -static bool dcpep_process_chunks(struct apple_dcp *dcp, - struct dcp_set_dcpav_prop_end_req *req) -{ - struct dcp_parse_ctx ctx; - int ret; - - if (!dcp->chunks.data) { - dev_warn(dcp->dev, "ignoring spurious end\n"); - return false; - } - - ret = parse(dcp->chunks.data, dcp->chunks.length, &ctx); - - if (ret) { - dev_warn(dcp->dev, "bad header on dcpav props\n"); - return false; - } - - if (!strcmp(req->key, "TimingElements")) { - dcp->modes = enumerate_modes(&ctx, &dcp->nr_modes, - dcp->width_mm, dcp->height_mm); - - if (IS_ERR(dcp->modes)) { - dev_warn(dcp->dev, "failed to parse modes\n"); - dcp->modes = NULL; - dcp->nr_modes = 0; - return false; - } - } else if (!strcmp(req->key, "DisplayAttributes")) { - ret = parse_display_attributes(&ctx, &dcp->width_mm, - &dcp->height_mm); - - if (ret) { - dev_warn(dcp->dev, "failed to parse display attribs\n"); - return false; - } - - dcp_set_dimensions(dcp); - } - - return true; -} - -static u8 dcpep_cb_prop_end(struct apple_dcp *dcp, - struct dcp_set_dcpav_prop_end_req *req) -{ - u8 resp = dcpep_process_chunks(dcp, req); - - /* Reset for the next transfer */ - devm_kfree(dcp->dev, dcp->chunks.data); - dcp->chunks.data = NULL; - - return resp; -} - -/* Boot sequence */ -static void boot_done(struct apple_dcp *dcp, void *out, void *cookie) -{ - struct dcp_cb_channel *ch = &dcp->ch_cb; - u8 *succ = ch->output[ch->depth - 1]; - dev_dbg(dcp->dev, "boot done"); - - *succ = true; - dcp_ack(dcp, DCP_CONTEXT_CB); -} - -static void boot_5(struct apple_dcp *dcp, void *out, void *cookie) -{ - dcp_set_display_refresh_properties(dcp, false, boot_done, NULL); -} - -static void boot_4(struct apple_dcp *dcp, void *out, void *cookie) -{ - dcp_late_init_signal(dcp, false, boot_5, NULL); -} - -static void boot_3(struct apple_dcp *dcp, void *out, void *cookie) -{ - u32 v_true = true; - - dcp_flush_supports_power(dcp, false, &v_true, boot_4, NULL); -} - -static void boot_2(struct apple_dcp *dcp, void *out, void *cookie) -{ - dcp_setup_video_limits(dcp, false, boot_3, NULL); -} - -static void boot_1_5(struct apple_dcp *dcp, void *out, void *cookie) -{ - dcp_create_default_fb(dcp, false, boot_2, NULL); -} - -/* Use special function signature to defer the ACK */ -static bool dcpep_cb_boot_1(struct apple_dcp *dcp, int tag, void *out, void *in) -{ - dev_dbg(dcp->dev, "Callback D%03d %s\n", tag, __func__); - dcp_set_create_dfb(dcp, false, boot_1_5, NULL); - return false; -} - -static struct dcp_rt_bandwidth dcpep_cb_rt_bandwidth(struct apple_dcp *dcp) -{ - if (dcp->disp_registers[5] && dcp->disp_registers[6]) - return (struct dcp_rt_bandwidth) { - .reg_scratch = dcp->disp_registers[5]->start + REG_SCRATCH, - .reg_doorbell = dcp->disp_registers[6]->start + REG_DOORBELL, - .doorbell_bit = REG_DOORBELL_BIT, - - .padding[3] = 0x4, // XXX: required by 11.x firmware - }; - else if (dcp->disp_registers[4]) - return (struct dcp_rt_bandwidth) { - .reg_scratch = dcp->disp_registers[4]->start + REG_SCRATCH_T600X, - .reg_doorbell = 0, - .doorbell_bit = 0, - }; - else - return (struct dcp_rt_bandwidth) { - .reg_scratch = 0, - .reg_doorbell = 0, - .doorbell_bit = 0, - }; -} - -/* Callback to get the current time as milliseconds since the UNIX epoch */ -static u64 dcpep_cb_get_time(struct apple_dcp *dcp) -{ - return ktime_to_ms(ktime_get_real()); -} - -struct dcp_swap_cookie { - struct completion done; - atomic_t refcount; - u32 swap_id; -}; - -static void dcp_swap_cleared(struct apple_dcp *dcp, void *data, void *cookie) -{ - struct dcp_swap_submit_resp *resp = data; - dev_dbg(dcp->dev, "%s", __func__); - - if (cookie) { - struct dcp_swap_cookie *info = cookie; - complete(&info->done); - if (atomic_dec_and_test(&info->refcount)) - kfree(info); - } - - if (resp->ret) { - dev_err(dcp->dev, "swap_clear failed! status %u\n", resp->ret); - dcp_drm_crtc_vblank(dcp->crtc); - return; - } - - while (!list_empty(&dcp->swapped_out_fbs)) { - struct dcp_fb_reference *entry; - entry = list_first_entry(&dcp->swapped_out_fbs, - struct dcp_fb_reference, head); - if (entry->fb) - drm_framebuffer_put(entry->fb); - list_del(&entry->head); - kfree(entry); - } -} - -static void dcp_swap_clear_started(struct apple_dcp *dcp, void *data, - void *cookie) -{ - struct dcp_swap_start_resp *resp = data; - dev_dbg(dcp->dev, "%s swap_id: %u", __func__, resp->swap_id); - dcp->swap.swap.swap_id = resp->swap_id; - - if (cookie) { - struct dcp_swap_cookie *info = cookie; - info->swap_id = resp->swap_id; - } - - dcp_swap_submit(dcp, false, &dcp->swap, dcp_swap_cleared, cookie); -} - -static void dcp_on_final(struct apple_dcp *dcp, void *out, void *cookie) -{ - struct dcp_wait_cookie *wait = cookie; - dev_dbg(dcp->dev, "%s", __func__); - - if (wait) { - complete(&wait->done); - if (atomic_dec_and_test(&wait->refcount)) - kfree(wait); - } -} +#define APPLE_DCP_COPROC_CPU_CONTROL 0x44 +#define APPLE_DCP_COPROC_CPU_CONTROL_RUN BIT(4) -static void dcp_on_set_parameter(struct apple_dcp *dcp, void *out, void *cookie) -{ - struct dcp_set_parameter_dcp param = { - .param = 14, - .value = { 0 }, - .count = 1, - }; - dev_dbg(dcp->dev, "%s", __func__); - - dcp_set_parameter_dcp(dcp, false, ¶m, dcp_on_final, cookie); -} +#define DCP_BOOT_TIMEOUT msecs_to_jiffies(1000) -void dcp_poweron(struct platform_device *pdev) +/* HACK: moved here to avoid circular dependency between apple_drv and dcp */ +void dcp_drm_crtc_vblank(struct apple_crtc *crtc) { - struct apple_dcp *dcp = platform_get_drvdata(pdev); - struct dcp_wait_cookie * cookie; - struct dcp_set_power_state_req req = { - .unklong = 1, - }; - int ret; - u32 handle; - dev_dbg(dcp->dev, "%s", __func__); + unsigned long flags; - cookie = kzalloc(sizeof(*cookie), GFP_KERNEL); - if (!cookie) + if (crtc->vsync_disabled) return; - init_completion(&cookie->done); - atomic_set(&cookie->refcount, 2); - - if (dcp->main_display) { - handle = 0; - dcp_set_display_device(dcp, false, &handle, dcp_on_final, cookie); - } else { - handle = 2; - dcp_set_display_device(dcp, false, &handle, dcp_on_set_parameter, cookie); - } - dcp_set_power_state(dcp, true, &req, NULL, NULL); - - ret = wait_for_completion_timeout(&cookie->done, msecs_to_jiffies(500)); - - if (ret == 0) - dev_warn(dcp->dev, "wait for power timed out"); - - if (atomic_dec_and_test(&cookie->refcount)) - kfree(cookie); -} -EXPORT_SYMBOL(dcp_poweron); - -static void complete_set_powerstate(struct apple_dcp *dcp, void *out, void *cookie) -{ - struct dcp_wait_cookie *wait = cookie; + drm_crtc_handle_vblank(&crtc->base); - if (wait) { - complete(&wait->done); - if (atomic_dec_and_test(&wait->refcount)) - kfree(wait); + spin_lock_irqsave(&crtc->base.dev->event_lock, flags); + if (crtc->event) { + drm_crtc_send_vblank_event(&crtc->base, crtc->event); + drm_crtc_vblank_put(&crtc->base); + crtc->event = NULL; } + spin_unlock_irqrestore(&crtc->base.dev->event_lock, flags); } -void dcp_poweroff(struct platform_device *pdev) +void dcp_set_dimensions(struct apple_dcp *dcp) { - struct apple_dcp *dcp = platform_get_drvdata(pdev); - int ret, swap_id; - struct dcp_set_power_state_req power_req = { - .unklong = 0, - }; - struct dcp_swap_cookie *cookie; - struct dcp_wait_cookie *poff_cookie; - struct dcp_swap_start_req swap_req= { 0 }; - - dev_dbg(dcp->dev, "%s", __func__); - - cookie = kzalloc(sizeof(*cookie), GFP_KERNEL); - if (!cookie) - return; - init_completion(&cookie->done); - atomic_set(&cookie->refcount, 2); - - // clear surfaces - memset(&dcp->swap, 0, sizeof(dcp->swap)); - - dcp->swap.swap.swap_enabled = DCP_REMOVE_LAYERS | 0x7; - dcp->swap.swap.swap_completed = DCP_REMOVE_LAYERS | 0x7; - dcp->swap.swap.unk_10c = 0xFF000000; - - for (int l = 0; l < SWAP_SURFACES; l++) - dcp->swap.surf_null[l] = true; + int i; - dcp_swap_start(dcp, false, &swap_req, dcp_swap_clear_started, cookie); + /* Set the connector info */ + if (dcp->connector) { + struct drm_connector *connector = &dcp->connector->base; - ret = wait_for_completion_timeout(&cookie->done, msecs_to_jiffies(50)); - swap_id = cookie->swap_id; - if (atomic_dec_and_test(&cookie->refcount)) - kfree(cookie); - if (ret <= 0) { - dcp->crashed = true; - return; + mutex_lock(&connector->dev->mode_config.mutex); + connector->display_info.width_mm = dcp->width_mm; + connector->display_info.height_mm = dcp->height_mm; + mutex_unlock(&connector->dev->mode_config.mutex); } - dev_dbg(dcp->dev, "%s: clear swap submitted: %u", __func__, swap_id); - - poff_cookie = kzalloc(sizeof(*poff_cookie), GFP_KERNEL); - if (!poff_cookie) - return; - init_completion(&poff_cookie->done); - atomic_set(&poff_cookie->refcount, 2); - - dcp_set_power_state(dcp, false, &power_req, complete_set_powerstate, poff_cookie); - ret = wait_for_completion_timeout(&cookie->done, msecs_to_jiffies(1000)); - - if (ret == 0) - dev_warn(dcp->dev, "setPowerState(0) timeout %u ms", 1000); - else if (ret > 0) - dev_dbg(dcp->dev, "setPowerState(0) finished with %d ms to spare", - jiffies_to_msecs(ret)); - - if (atomic_dec_and_test(&poff_cookie->refcount)) - kfree(poff_cookie); - dev_dbg(dcp->dev, "%s: setPowerState(0) done", __func__); -} -EXPORT_SYMBOL(dcp_poweroff); - -/* - * Helper to send a DRM hotplug event. The DCP is accessed from a single - * (RTKit) thread. To handle hotplug callbacks, we need to call - * drm_kms_helper_hotplug_event, which does an atomic commit (via DCP) and - * waits for vblank (a DCP callback). That means we deadlock if we call from - * the RTKit thread! Instead, move the call to another thread via a workqueue. - */ -void dcp_hotplug(struct work_struct *work) -{ - struct apple_connector *connector; - struct drm_device *dev; - struct apple_dcp *dcp; - - connector = container_of(work, struct apple_connector, hotplug_wq); - dev = connector->base.dev; - - dcp = platform_get_drvdata(connector->dcp); - dev_info(dcp->dev, "%s: connected: %d", __func__, connector->connected); - /* - * DCP defers link training until we set a display mode. But we set - * display modes from atomic_flush, so userspace needs to trigger a - * flush, or the CRTC gets no signal. + * Fix up any probed modes. Modes are created when parsing + * TimingElements, dimensions are calculated when parsing + * DisplayAttributes, and TimingElements may be sent first */ - if (!dcp->valid_mode && connector->connected) { - drm_connector_set_link_status_property( - &connector->base, DRM_MODE_LINK_STATUS_BAD); - } - - if (dev && dev->registered) - drm_kms_helper_hotplug_event(dev); -} -EXPORT_SYMBOL_GPL(dcp_hotplug); - -static void dcpep_cb_hotplug(struct apple_dcp *dcp, u64 *connected) -{ - struct apple_connector *connector = dcp->connector; - - /* Hotplug invalidates mode. DRM doesn't always handle this. */ - if (!(*connected)) { - dcp->valid_mode = false; - /* after unplug swap will not complete until the next - * set_digital_out_mode */ - schedule_work(&dcp->vblank_wq); - } - - if (connector && connector->connected != !!(*connected)) { - connector->connected = !!(*connected); - dcp->valid_mode = false; - schedule_work(&connector->hotplug_wq); + for (i = 0; i < dcp->nr_modes; ++i) { + dcp->modes[i].mode.width_mm = dcp->width_mm; + dcp->modes[i].mode.height_mm = dcp->height_mm; } } -static void -dcpep_cb_swap_complete_intent_gated(struct apple_dcp *dcp, - struct dcp_swap_complete_intent_gated *info) -{ - dev_dbg(dcp->dev, "swap_id:%u width:%u height:%u", info->swap_id, - info->width, info->height); -} - /* * Helper to send a DRM vblank event. We do not know how call swap_submit_dcp * without surfaces. To avoid timeouts in drm_atomic_helper_wait_for_vblanks @@ -1132,597 +86,16 @@ static void dcp_delayed_vblank(struct work_struct *work) dcp_drm_crtc_vblank(dcp->crtc); } - -#define DCPEP_MAX_CB (1000) - -/* - * Define type-safe trampolines. Define typedefs to enforce type-safety on the - * input data (so if the types don't match, gcc errors out). - */ - -#define TRAMPOLINE_VOID(func, handler) \ - static bool func(struct apple_dcp *dcp, int tag, void *out, void *in) \ - { \ - dev_dbg(dcp->dev, "Callback D%03d %s\n", tag, #handler); \ - handler(dcp); \ - return true; \ - } - -#define TRAMPOLINE_IN(func, handler, T_in) \ - typedef void (*callback_##handler)(struct apple_dcp *, T_in *); \ - \ - static bool func(struct apple_dcp *dcp, int tag, void *out, void *in) \ - { \ - callback_##handler cb = handler; \ - \ - dev_dbg(dcp->dev, "Callback D%03d %s\n", tag, #handler); \ - cb(dcp, in); \ - return true; \ - } - -#define TRAMPOLINE_INOUT(func, handler, T_in, T_out) \ - typedef T_out (*callback_##handler)(struct apple_dcp *, T_in *); \ - \ - static bool func(struct apple_dcp *dcp, int tag, void *out, void *in) \ - { \ - T_out *typed_out = out; \ - callback_##handler cb = handler; \ - \ - dev_dbg(dcp->dev, "Callback D%03d %s\n", tag, #handler); \ - *typed_out = cb(dcp, in); \ - return true; \ - } - -#define TRAMPOLINE_OUT(func, handler, T_out) \ - static bool func(struct apple_dcp *dcp, int tag, void *out, void *in) \ - { \ - T_out *typed_out = out; \ - \ - dev_dbg(dcp->dev, "Callback D%03d %s\n", tag, #handler); \ - *typed_out = handler(dcp); \ - return true; \ - } - -TRAMPOLINE_VOID(trampoline_nop, dcpep_cb_nop); -TRAMPOLINE_OUT(trampoline_true, dcpep_cb_true, u8); -TRAMPOLINE_OUT(trampoline_false, dcpep_cb_false, u8); -TRAMPOLINE_OUT(trampoline_zero, dcpep_cb_zero, u32); -TRAMPOLINE_IN(trampoline_swap_complete, dcpep_cb_swap_complete, - struct dc_swap_complete_resp); -TRAMPOLINE_INOUT(trampoline_get_uint_prop, dcpep_cb_get_uint_prop, - struct dcp_get_uint_prop_req, struct dcp_get_uint_prop_resp); -TRAMPOLINE_INOUT(trampoline_map_piodma, dcpep_cb_map_piodma, - struct dcp_map_buf_req, struct dcp_map_buf_resp); -TRAMPOLINE_IN(trampoline_unmap_piodma, dcpep_cb_unmap_piodma, - struct dcp_unmap_buf_resp); -TRAMPOLINE_INOUT(trampoline_allocate_buffer, dcpep_cb_allocate_buffer, - struct dcp_allocate_buffer_req, - struct dcp_allocate_buffer_resp); -TRAMPOLINE_INOUT(trampoline_map_physical, dcpep_cb_map_physical, - struct dcp_map_physical_req, struct dcp_map_physical_resp); -TRAMPOLINE_INOUT(trampoline_release_mem_desc, dcpep_cb_release_mem_desc, - u32, u8); -TRAMPOLINE_INOUT(trampoline_map_reg, dcpep_cb_map_reg, struct dcp_map_reg_req, - struct dcp_map_reg_resp); -TRAMPOLINE_INOUT(trampoline_read_edt_data, dcpep_cb_read_edt_data, - struct dcp_read_edt_data_req, struct dcp_read_edt_data_resp); -TRAMPOLINE_INOUT(trampoline_prop_start, dcpep_cb_prop_start, u32, u8); -TRAMPOLINE_INOUT(trampoline_prop_chunk, dcpep_cb_prop_chunk, - struct dcp_set_dcpav_prop_chunk_req, u8); -TRAMPOLINE_INOUT(trampoline_prop_end, dcpep_cb_prop_end, - struct dcp_set_dcpav_prop_end_req, u8); -TRAMPOLINE_OUT(trampoline_rt_bandwidth, dcpep_cb_rt_bandwidth, - struct dcp_rt_bandwidth); -TRAMPOLINE_OUT(trampoline_get_frequency, dcpep_cb_get_frequency, u64); -TRAMPOLINE_OUT(trampoline_get_time, dcpep_cb_get_time, u64); -TRAMPOLINE_IN(trampoline_hotplug, dcpep_cb_hotplug, u64); -TRAMPOLINE_IN(trampoline_swap_complete_intent_gated, - dcpep_cb_swap_complete_intent_gated, - struct dcp_swap_complete_intent_gated); - -bool (*const dcpep_cb_handlers[DCPEP_MAX_CB])(struct apple_dcp *, int, void *, void *) = { - [0] = trampoline_true, /* did_boot_signal */ - [1] = trampoline_true, /* did_power_on_signal */ - [2] = trampoline_nop, /* will_power_off_signal */ - [3] = trampoline_rt_bandwidth, - [100] = trampoline_nop, /* match_pmu_service */ - [101] = trampoline_zero, /* get_display_default_stride */ - [103] = trampoline_nop, /* set_boolean_property */ - [106] = trampoline_nop, /* remove_property */ - [107] = trampoline_true, /* create_provider_service */ - [108] = trampoline_true, /* create_product_service */ - [109] = trampoline_true, /* create_pmu_service */ - [110] = trampoline_true, /* create_iomfb_service */ - [111] = trampoline_false, /* create_backlight_service */ - [116] = dcpep_cb_boot_1, - [117] = trampoline_false, /* is_dark_boot */ - [118] = trampoline_false, /* is_dark_boot / is_waking_from_hibernate*/ - [120] = trampoline_read_edt_data, - [122] = trampoline_prop_start, - [123] = trampoline_prop_chunk, - [124] = trampoline_prop_end, - [201] = trampoline_map_piodma, - [202] = trampoline_unmap_piodma, - [206] = trampoline_true, /* match_pmu_service_2 */ - [207] = trampoline_true, /* match_backlight_service */ - [208] = trampoline_get_time, - [211] = trampoline_nop, /* update_backlight_factor_prop */ - [300] = trampoline_nop, /* pr_publish */ - [401] = trampoline_get_uint_prop, - [404] = trampoline_nop, /* sr_set_uint_prop */ - [406] = trampoline_nop, /* set_fx_prop */ - [408] = trampoline_get_frequency, - [411] = trampoline_map_reg, - [413] = trampoline_true, /* sr_set_property_dict */ - [414] = trampoline_true, /* sr_set_property_int */ - [415] = trampoline_true, /* sr_set_property_bool */ - [451] = trampoline_allocate_buffer, - [452] = trampoline_map_physical, - [456] = trampoline_release_mem_desc, - [552] = trampoline_true, /* set_property_dict_0 */ - [561] = trampoline_true, /* set_property_dict */ - [563] = trampoline_true, /* set_property_int */ - [565] = trampoline_true, /* set_property_bool */ - [567] = trampoline_true, /* set_property_str */ - [574] = trampoline_zero, /* power_up_dart */ - [576] = trampoline_hotplug, - [577] = trampoline_nop, /* powerstate_notify */ - [582] = trampoline_true, /* create_default_fb_surface */ - [589] = trampoline_swap_complete, - [591] = trampoline_swap_complete_intent_gated, - [593] = trampoline_nop, /* enable_backlight_message_ap_gated */ - [598] = trampoline_nop, /* find_swap_function_gated */ -}; - -static void dcpep_handle_cb(struct apple_dcp *dcp, enum dcp_context_id context, - void *data, u32 length) -{ - struct device *dev = dcp->dev; - struct dcp_packet_header *hdr = data; - void *in, *out; - int tag = dcp_parse_tag(hdr->tag); - struct dcp_cb_channel *ch = dcp_get_cb_channel(dcp, context); - u8 depth; - - if (tag < 0 || tag >= DCPEP_MAX_CB || !dcpep_cb_handlers[tag]) { - dev_warn(dev, "received unknown callback %c%c%c%c\n", - hdr->tag[3], hdr->tag[2], hdr->tag[1], hdr->tag[0]); - return; - } - - in = data + sizeof(*hdr); - out = in + hdr->in_len; - - // TODO: verify that in_len and out_len match our prototypes - // for now just clear the out data to have at least consistant results - if (hdr->out_len) - memset(out, 0, hdr->out_len); - - depth = dcp_push_depth(&ch->depth); - ch->output[depth] = out; - - if (dcpep_cb_handlers[tag](dcp, tag, out, in)) - dcp_ack(dcp, context); -} - -static void dcpep_handle_ack(struct apple_dcp *dcp, enum dcp_context_id context, - void *data, u32 length) -{ - struct dcp_packet_header *header = data; - struct dcp_call_channel *ch = dcp_get_call_channel(dcp, context); - void *cookie; - dcp_callback_t cb; - - if (!ch) { - dev_warn(dcp->dev, "ignoring ack on context %X\n", context); - return; - } - - dcp_pop_depth(&ch->depth); - - cb = ch->callbacks[ch->depth]; - cookie = ch->cookies[ch->depth]; - - ch->callbacks[ch->depth] = NULL; - ch->cookies[ch->depth] = NULL; - - if (cb) - cb(dcp, data + sizeof(*header) + header->in_len, cookie); -} - -static void dcpep_got_msg(struct apple_dcp *dcp, u64 message) -{ - enum dcp_context_id ctx_id; - u16 offset; - u32 length; - int channel_offset; - void *data; - - ctx_id = (message & DCPEP_CONTEXT_MASK) >> DCPEP_CONTEXT_SHIFT; - offset = (message & DCPEP_OFFSET_MASK) >> DCPEP_OFFSET_SHIFT; - length = (message >> DCPEP_LENGTH_SHIFT); - - channel_offset = dcp_channel_offset(ctx_id); - - if (channel_offset < 0) { - dev_warn(dcp->dev, "invalid context received %u", ctx_id); - return; - } - - data = dcp->shmem + channel_offset + offset; - - if (message & DCPEP_ACK) - dcpep_handle_ack(dcp, ctx_id, data, length); - else - dcpep_handle_cb(dcp, ctx_id, data, length); -} - -/* - * Callback for swap requests. If a swap failed, we'll never get a swap - * complete event so we need to fake a vblank event early to avoid a hang. - */ - -static void dcp_swapped(struct apple_dcp *dcp, void *data, void *cookie) -{ - struct dcp_swap_submit_resp *resp = data; - - if (resp->ret) { - dev_err(dcp->dev, "swap failed! status %u\n", resp->ret); - dcp_drm_crtc_vblank(dcp->crtc); - return; - } - - while (!list_empty(&dcp->swapped_out_fbs)) { - struct dcp_fb_reference *entry; - entry = list_first_entry(&dcp->swapped_out_fbs, - struct dcp_fb_reference, head); - if (entry->fb) - drm_framebuffer_put(entry->fb); - list_del(&entry->head); - kfree(entry); - } -} - -static void dcp_swap_started(struct apple_dcp *dcp, void *data, void *cookie) -{ - struct dcp_swap_start_resp *resp = data; - - dcp->swap.swap.swap_id = resp->swap_id; - - dcp_swap_submit(dcp, false, &dcp->swap, dcp_swapped, NULL); -} - -/* - * DRM specifies rectangles as start and end coordinates. DCP specifies - * rectangles as a start coordinate and a width/height. Convert a DRM rectangle - * to a DCP rectangle. - */ -static struct dcp_rect drm_to_dcp_rect(struct drm_rect *rect) -{ - return (struct dcp_rect) { - .x = rect->x1, - .y = rect->y1, - .w = drm_rect_width(rect), - .h = drm_rect_height(rect) - }; -} - -static u32 drm_format_to_dcp(u32 drm) -{ - switch (drm) { - case DRM_FORMAT_XRGB8888: - case DRM_FORMAT_ARGB8888: - return fourcc_code('A', 'R', 'G', 'B'); - - case DRM_FORMAT_XBGR8888: - case DRM_FORMAT_ABGR8888: - return fourcc_code('A', 'B', 'G', 'R'); - } - - pr_warn("DRM format %X not supported in DCP\n", drm); - return 0; -} - -int dcp_get_modes(struct drm_connector *connector) -{ - struct apple_connector *apple_connector = to_apple_connector(connector); - struct platform_device *pdev = apple_connector->dcp; - struct apple_dcp *dcp = platform_get_drvdata(pdev); - - struct drm_device *dev = connector->dev; - struct drm_display_mode *mode; - int i; - - for (i = 0; i < dcp->nr_modes; ++i) { - mode = drm_mode_duplicate(dev, &dcp->modes[i].mode); - - if (!mode) { - dev_err(dev->dev, "Failed to duplicate display mode\n"); - return 0; - } - - drm_mode_probed_add(connector, mode); - } - - return dcp->nr_modes; -} -EXPORT_SYMBOL_GPL(dcp_get_modes); - -/* The user may own drm_display_mode, so we need to search for our copy */ -static struct dcp_display_mode *lookup_mode(struct apple_dcp *dcp, - struct drm_display_mode *mode) -{ - int i; - - for (i = 0; i < dcp->nr_modes; ++i) { - if (drm_mode_match(mode, &dcp->modes[i].mode, - DRM_MODE_MATCH_TIMINGS | - DRM_MODE_MATCH_CLOCK)) - return &dcp->modes[i]; - } - - return NULL; -} - -int dcp_mode_valid(struct drm_connector *connector, - struct drm_display_mode *mode) -{ - struct apple_connector *apple_connector = to_apple_connector(connector); - struct platform_device *pdev = apple_connector->dcp; - struct apple_dcp *dcp = platform_get_drvdata(pdev); - - return lookup_mode(dcp, mode) ? MODE_OK : MODE_BAD; -} -EXPORT_SYMBOL_GPL(dcp_mode_valid); - -/* Helpers to modeset and swap, used to flush */ -static void do_swap(struct apple_dcp *dcp, void *data, void *cookie) -{ - struct dcp_swap_start_req start_req = { 0 }; - dev_dbg(dcp->dev, "%s", __func__); - - if (dcp->connector && dcp->connector->connected) - dcp_swap_start(dcp, false, &start_req, dcp_swap_started, NULL); - else - dcp_drm_crtc_vblank(dcp->crtc); -} - -static void complete_set_digital_out_mode(struct apple_dcp *dcp, void *data, - void *cookie) -{ - struct dcp_wait_cookie *wait = cookie; - dev_dbg(dcp->dev, "%s", __func__); - - dcp->ignore_swap_complete = false; - - if (wait) { - complete(&wait->done); - if (atomic_dec_and_test(&wait->refcount)) - kfree(wait); - } -} - -void dcp_flush(struct drm_crtc *crtc, struct drm_atomic_state *state) -{ - struct platform_device *pdev = to_apple_crtc(crtc)->dcp; - struct apple_dcp *dcp = platform_get_drvdata(pdev); - struct drm_plane *plane; - struct drm_plane_state *new_state, *old_state; - struct drm_crtc_state *crtc_state; - struct dcp_swap_submit_req *req = &dcp->swap; - int l; - int has_surface = 0; - bool modeset; - dev_dbg(dcp->dev, "%s", __func__); - - crtc_state = drm_atomic_get_new_crtc_state(state, crtc); - - modeset = drm_atomic_crtc_needs_modeset(crtc_state) || !dcp->valid_mode; - - if (WARN(dcp_channel_busy(&dcp->ch_cmd), "unexpected busy channel") || - WARN(!modeset && !dcp->connector->connected, "can't flush if disconnected")) { - /* HACK: issue a delayed vblank event to avoid timeouts in - * drm_atomic_helper_wait_for_vblanks(). - */ - schedule_work(&dcp->vblank_wq); - return; - } - - /* Reset to defaults */ - memset(req, 0, sizeof(*req)); - for (l = 0; l < SWAP_SURFACES; l++) - req->surf_null[l] = true; - - for_each_oldnew_plane_in_state(state, plane, old_state, new_state, l) { - struct drm_framebuffer *fb = new_state->fb; - struct drm_rect src_rect; - - WARN_ON(l >= SWAP_SURFACES); - - req->swap.swap_enabled |= BIT(l); - - if (old_state->fb && fb != old_state->fb) { - /* - * Race condition between a framebuffer unbind getting - * swapped out and GEM unreferencing a framebuffer. If - * we lose the race, the display gets IOVA faults and - * the DCP crashes. We need to extend the lifetime of - * the drm_framebuffer (and hence the GEM object) until - * after we get a swap complete for the swap unbinding - * it. - */ - struct dcp_fb_reference *entry = kzalloc(sizeof(*entry), GFP_KERNEL); - if (entry) { - entry->fb = old_state->fb; - list_add_tail(&entry->head, &dcp->swapped_out_fbs); - } - drm_framebuffer_get(old_state->fb); - } - - if (!new_state->fb) { - if (old_state->fb) - req->swap.swap_enabled |= DCP_REMOVE_LAYERS; - - continue; - } - req->surf_null[l] = false; - has_surface = 1; - - - drm_rect_fp_to_int(&src_rect, &new_state->src); - - req->swap.src_rect[l] = drm_to_dcp_rect(&src_rect); - req->swap.dst_rect[l] = drm_to_dcp_rect(&new_state->dst); - - req->surf_iova[l] = drm_fb_dma_get_gem_addr(fb, new_state, 0); - - req->surf[l] = (struct dcp_surface) { - .format = drm_format_to_dcp(fb->format->format), - .xfer_func = 13, - .colorspace = 1, - .stride = fb->pitches[0], - .width = fb->width, - .height = fb->height, - .buf_size = fb->height * fb->pitches[0], - .surface_id = req->swap.surf_ids[l], - - /* Only used for compressed or multiplanar surfaces */ - .pix_size = 1, - .pel_w = 1, - .pel_h = 1, - .has_comp = 1, - .has_planes = 1, - }; - } - - /* These fields should be set together */ - req->swap.swap_completed = req->swap.swap_enabled; - - if (modeset) { - struct dcp_display_mode *mode; - struct dcp_wait_cookie *cookie; - int ret; - - mode = lookup_mode(dcp, &crtc_state->mode); - if (!mode) { - dev_warn(dcp->dev, "no match for " DRM_MODE_FMT, - DRM_MODE_ARG(&crtc_state->mode)); - schedule_work(&dcp->vblank_wq); - return; - } - - dev_info(dcp->dev, "set_digital_out_mode(color:%d timing:%d)", - mode->color_mode_id, mode->timing_mode_id); - dcp->mode = (struct dcp_set_digital_out_mode_req) { - .color_mode_id = mode->color_mode_id, - .timing_mode_id = mode->timing_mode_id - }; - - cookie = kzalloc(sizeof(*cookie), GFP_KERNEL); - if (!cookie) { - schedule_work(&dcp->vblank_wq); - return; - } - - init_completion(&cookie->done); - atomic_set(&cookie->refcount, 2); - - dcp_set_digital_out_mode(dcp, false, &dcp->mode, - complete_set_digital_out_mode, cookie); - - dev_dbg(dcp->dev, "%s - wait for modeset", __func__); - ret = wait_for_completion_timeout(&cookie->done, msecs_to_jiffies(500)); - - if (atomic_dec_and_test(&cookie->refcount)) - kfree(cookie); - - if (ret == 0) { - dev_dbg(dcp->dev, "set_digital_out_mode 200 ms"); - schedule_work(&dcp->vblank_wq); - return; - } - else if (ret > 0) { - dev_dbg(dcp->dev, "set_digital_out_mode finished with %d to spare", - jiffies_to_msecs(ret)); - } - - dcp->valid_mode = true; - } - - if (!has_surface) { - if (crtc_state->enable && crtc_state->active && !crtc_state->planes_changed) { - schedule_work(&dcp->vblank_wq); - return; - } - - req->clear = 1; - } - do_swap(dcp, NULL, NULL); -} -EXPORT_SYMBOL_GPL(dcp_flush); - -bool dcp_is_initialized(struct platform_device *pdev) -{ - struct apple_dcp *dcp = platform_get_drvdata(pdev); - - return dcp->active; -} -EXPORT_SYMBOL_GPL(dcp_is_initialized); - - -static void res_is_main_display(struct apple_dcp *dcp, void *out, void *cookie) -{ - struct apple_connector *connector; - int result = *(int *)out; - dev_info(dcp->dev, "DCP is_main_display: %d\n", result); - - dcp->main_display = result != 0; - - dcp->active = true; - - connector = dcp->connector; - if (connector) { - connector->connected = dcp->nr_modes > 0; - schedule_work(&connector->hotplug_wq); - } -} - -static void init_3(struct apple_dcp *dcp, void *out, void *cookie) -{ - dcp_is_main_display(dcp, false, res_is_main_display, NULL); -} - -static void init_2(struct apple_dcp *dcp, void *out, void *cookie) -{ - dcp_first_client_open(dcp, false, init_3, NULL); -} - -static void init_1(struct apple_dcp *dcp, void *out, void *cookie) -{ - u32 val = 0; - dcp_enable_disable_video_power_savings(dcp, false, &val, init_2, NULL); -} - -static void dcp_started(struct apple_dcp *dcp, void *data, void *cookie) -{ - dev_info(dcp->dev, "DCP booted\n"); - - init_1(dcp, data, cookie); -} - -static void dcp_got_msg(void *cookie, u8 endpoint, u64 message) +static void dcp_recv_msg(void *cookie, u8 endpoint, u64 message) { struct apple_dcp *dcp = cookie; - enum dcpep_type type = (message >> DCPEP_TYPE_SHIFT) & DCPEP_TYPE_MASK; - - WARN_ON(endpoint != DCP_ENDPOINT); - if (type == DCPEP_TYPE_INITIALIZED) - dcp_start_signal(dcp, false, dcp_started, NULL); - else if (type == DCPEP_TYPE_MESSAGE) - dcpep_got_msg(dcp, message); - else - dev_warn(dcp->dev, "Ignoring unknown message %llx\n", message); + switch (endpoint) { + case IOMFB_ENDPOINT: + return iomfb_recv_msg(dcp, message); + default: + WARN(endpoint, "unknown DCP endpoint %hhu", endpoint); + } } static void dcp_rtk_crashed(void *cookie, const void *crashlog, size_t crashlog_size) @@ -1738,14 +111,16 @@ static int dcp_rtk_shmem_setup(void *cookie, struct apple_rtkit_shmem *bfr) struct apple_dcp *dcp = cookie; if (bfr->iova) { - struct iommu_domain *domain = iommu_get_domain_for_dev(dcp->dev); + struct iommu_domain *domain = + iommu_get_domain_for_dev(dcp->dev); phys_addr_t phy_addr; if (!domain) return -ENOMEM; // TODO: get map from device-tree - phy_addr = iommu_iova_to_phys(domain, bfr->iova & ~dcp->asc_dram_mask); + phy_addr = iommu_iova_to_phys(domain, + bfr->iova & ~dcp->asc_dram_mask); if (!phy_addr) return -ENOMEM; @@ -1755,10 +130,13 @@ static int dcp_rtk_shmem_setup(void *cookie, struct apple_rtkit_shmem *bfr) return -ENOMEM; bfr->is_mapped = true; - dev_info(dcp->dev, "shmem_setup: iova: %lx -> pa: %lx -> iomem: %lx", - (uintptr_t)bfr->iova, (uintptr_t)phy_addr, (uintptr_t)bfr->buffer); + dev_info(dcp->dev, + "shmem_setup: iova: %lx -> pa: %lx -> iomem: %lx", + (uintptr_t)bfr->iova, (uintptr_t)phy_addr, + (uintptr_t)bfr->buffer); } else { - bfr->buffer = dma_alloc_coherent(dcp->dev, bfr->size, &bfr->iova, GFP_KERNEL); + bfr->buffer = dma_alloc_coherent(dcp->dev, bfr->size, + &bfr->iova, GFP_KERNEL); if (!bfr->buffer) return -ENOMEM; @@ -1778,12 +156,13 @@ static void dcp_rtk_shmem_destroy(void *cookie, struct apple_rtkit_shmem *bfr) if (bfr->is_mapped) memunmap(bfr->buffer); else - dma_free_coherent(dcp->dev, bfr->size, bfr->buffer, bfr->iova & ~dcp->asc_dram_mask); + dma_free_coherent(dcp->dev, bfr->size, bfr->buffer, + bfr->iova & ~dcp->asc_dram_mask); } static struct apple_rtkit_ops rtkit_ops = { .crashed = dcp_rtk_crashed, - .recv_message = dcp_got_msg, + .recv_message = dcp_recv_msg, .shmem_setup = dcp_rtk_shmem_setup, .shmem_destroy = dcp_rtk_shmem_destroy, }; @@ -1839,7 +218,6 @@ static int dcp_platform_probe(struct platform_device *pdev) { struct device *dev = &pdev->dev; struct apple_dcp *dcp; - dma_addr_t shmem_iova; u32 cpu_ctrl; int ret; @@ -1884,7 +262,8 @@ static int dcp_platform_probe(struct platform_device *pdev) dcp->clk = devm_clk_get(dev, NULL); if (IS_ERR(dcp->clk)) - return dev_err_probe(dev, PTR_ERR(dcp->clk), "Unable to find clock\n"); + return dev_err_probe(dev, PTR_ERR(dcp->clk), + "Unable to find clock\n"); ret = of_property_read_u64(dev->of_node, "apple,asc-dram-mask", &dcp->asc_dram_mask); @@ -1898,9 +277,11 @@ static int dcp_platform_probe(struct platform_device *pdev) INIT_WORK(&dcp->vblank_wq, dcp_delayed_vblank); - dcp->swapped_out_fbs = (struct list_head)LIST_HEAD_INIT(dcp->swapped_out_fbs); + dcp->swapped_out_fbs = + (struct list_head)LIST_HEAD_INIT(dcp->swapped_out_fbs); - cpu_ctrl = readl_relaxed(dcp->coproc_reg + APPLE_DCP_COPROC_CPU_CONTROL); + cpu_ctrl = + readl_relaxed(dcp->coproc_reg + APPLE_DCP_COPROC_CPU_CONTROL); writel_relaxed(cpu_ctrl | APPLE_DCP_COPROC_CPU_CONTROL_RUN, dcp->coproc_reg + APPLE_DCP_COPROC_CPU_CONTROL); @@ -1914,14 +295,11 @@ static int dcp_platform_probe(struct platform_device *pdev) return dev_err_probe(dev, ret, "Failed to boot RTKit: %d", ret); - apple_rtkit_start_ep(dcp->rtk, DCP_ENDPOINT); - - dcp->shmem = dma_alloc_coherent(dev, DCP_SHMEM_SIZE, &shmem_iova, - GFP_KERNEL); - - shmem_iova |= dcp->asc_dram_mask; - apple_rtkit_send_message(dcp->rtk, DCP_ENDPOINT, - dcpep_set_shmem(shmem_iova), NULL, false); + /* start RTKit endpoints */ + ret = iomfb_start_rtkit(dcp); + if (ret) + return dev_err_probe(dev, ret, + "Failed to start IOMFB endpoint: %d", ret); return ret; } @@ -1934,15 +312,7 @@ static void dcp_platform_shutdown(struct platform_device *pdev) { struct apple_dcp *dcp = platform_get_drvdata(pdev); - struct dcp_set_power_state_req req = { - /* defaults are ok */ - }; - - /* We're going down */ - dcp->active = false; - dcp->valid_mode = false; - - dcp_set_power_state(dcp, false, &req, NULL, NULL); + iomfb_shutdown(dcp); } static const struct of_device_id of_match[] = { @@ -1965,9 +335,7 @@ static int dcp_suspend(struct device *dev) static int dcp_resume(struct device *dev) { - struct apple_dcp *dcp = platform_get_drvdata(to_platform_device(dev)); - - dcp_start_signal(dcp, false, dcp_started, NULL); + dcp_poweron(to_platform_device(dev)); return 0; } diff --git a/drivers/gpu/drm/apple/dcp.h b/drivers/gpu/drm/apple/dcp.h index 9e3e3738a39377..18d71afaf6b72e 100644 --- a/drivers/gpu/drm/apple/dcp.h +++ b/drivers/gpu/drm/apple/dcp.h @@ -5,6 +5,9 @@ #define __APPLE_DCP_H__ #include +#include + +#include "dcp-internal.h" #include "parser.h" struct apple_crtc { @@ -39,8 +42,16 @@ void dcp_link(struct platform_device *pdev, struct apple_crtc *apple, void dcp_flush(struct drm_crtc *crtc, struct drm_atomic_state *state); bool dcp_is_initialized(struct platform_device *pdev); void apple_crtc_vblank(struct apple_crtc *apple); +void dcp_drm_crtc_vblank(struct apple_crtc *crtc); int dcp_get_modes(struct drm_connector *connector); int dcp_mode_valid(struct drm_connector *connector, struct drm_display_mode *mode); +void dcp_set_dimensions(struct apple_dcp *dcp); + + +int iomfb_start_rtkit(struct apple_dcp *dcp); +void iomfb_shutdown(struct apple_dcp *dcp); +/* rtkit message handler for IOMFB messages */ +void iomfb_recv_msg(struct apple_dcp *dcp, u64 message); #endif diff --git a/drivers/gpu/drm/apple/iomfb.c b/drivers/gpu/drm/apple/iomfb.c new file mode 100644 index 00000000000000..0da3de1aa27e78 --- /dev/null +++ b/drivers/gpu/drm/apple/iomfb.c @@ -0,0 +1,1626 @@ +// SPDX-License-Identifier: GPL-2.0-only OR MIT +/* Copyright 2021 Alyssa Rosenzweig */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include "dcp.h" +#include "dcp-internal.h" +#include "iomfb.h" +#include "parser.h" + +/* Register defines used in bandwidth setup structure */ +#define REG_SCRATCH (0x14) +#define REG_SCRATCH_T600X (0x988) +#define REG_DOORBELL (0x0) +#define REG_DOORBELL_BIT (2) + +struct dcp_wait_cookie { + struct completion done; + atomic_t refcount; +}; + +static int dcp_tx_offset(enum dcp_context_id id) +{ + switch (id) { + case DCP_CONTEXT_CB: + case DCP_CONTEXT_CMD: + return 0x00000; + case DCP_CONTEXT_OOBCB: + case DCP_CONTEXT_OOBCMD: + return 0x08000; + default: + return -EINVAL; + } +} + +static int dcp_channel_offset(enum dcp_context_id id) +{ + switch (id) { + case DCP_CONTEXT_ASYNC: + return 0x40000; + case DCP_CONTEXT_CB: + return 0x60000; + case DCP_CONTEXT_OOBCB: + return 0x68000; + default: + return dcp_tx_offset(id); + } +} + +static inline u64 dcpep_set_shmem(u64 dart_va) +{ + return (DCPEP_TYPE_SET_SHMEM << DCPEP_TYPE_SHIFT) | + (DCPEP_FLAG_VALUE << DCPEP_FLAG_SHIFT) | + (dart_va << DCPEP_DVA_SHIFT); +} + +static inline u64 dcpep_msg(enum dcp_context_id id, u32 length, u16 offset) +{ + return (DCPEP_TYPE_MESSAGE << DCPEP_TYPE_SHIFT) | + ((u64)id << DCPEP_CONTEXT_SHIFT) | + ((u64)offset << DCPEP_OFFSET_SHIFT) | + ((u64)length << DCPEP_LENGTH_SHIFT); +} + +static inline u64 dcpep_ack(enum dcp_context_id id) +{ + return dcpep_msg(id, 0, 0) | DCPEP_ACK; +} + +/* + * A channel is busy if we have sent a message that has yet to be + * acked. The driver must not sent a message to a busy channel. + */ +static bool dcp_channel_busy(struct dcp_call_channel *ch) +{ + return (ch->depth != 0); +} + +/* Get a call channel for a context */ +static struct dcp_call_channel * +dcp_get_call_channel(struct apple_dcp *dcp, enum dcp_context_id context) +{ + switch (context) { + case DCP_CONTEXT_CMD: + case DCP_CONTEXT_CB: + return &dcp->ch_cmd; + case DCP_CONTEXT_OOBCMD: + case DCP_CONTEXT_OOBCB: + return &dcp->ch_oobcmd; + default: + return NULL; + } +} + +/* + * Get the context ID passed to the DCP for a command we push. The rule is + * simple: callback contexts are used when replying to the DCP, command + * contexts are used otherwise. That corresponds to a non/zero call stack + * depth. This rule frees the caller from tracking the call context manually. + */ +static enum dcp_context_id dcp_call_context(struct apple_dcp *dcp, bool oob) +{ + u8 depth = oob ? dcp->ch_oobcmd.depth : dcp->ch_cmd.depth; + + if (depth) + return oob ? DCP_CONTEXT_OOBCB : DCP_CONTEXT_CB; + else + return oob ? DCP_CONTEXT_OOBCMD : DCP_CONTEXT_CMD; +} + +/* Get a callback channel for a context */ +static struct dcp_cb_channel *dcp_get_cb_channel(struct apple_dcp *dcp, + enum dcp_context_id context) +{ + switch (context) { + case DCP_CONTEXT_CB: + return &dcp->ch_cb; + case DCP_CONTEXT_OOBCB: + return &dcp->ch_oobcb; + case DCP_CONTEXT_ASYNC: + return &dcp->ch_async; + default: + return NULL; + } +} + +/* Get the start of a packet: after the end of the previous packet */ +static u16 dcp_packet_start(struct dcp_call_channel *ch, u8 depth) +{ + if (depth > 0) + return ch->end[depth - 1]; + else + return 0; +} + +/* Pushes and pops the depth of the call stack with safety checks */ +static u8 dcp_push_depth(u8 *depth) +{ + u8 ret = (*depth)++; + + WARN_ON(ret >= DCP_MAX_CALL_DEPTH); + return ret; +} + +static u8 dcp_pop_depth(u8 *depth) +{ + WARN_ON((*depth) == 0); + + return --(*depth); +} + +#define DCP_METHOD(tag, name) [name] = { #name, tag } + +const struct dcp_method_entry dcp_methods[dcpep_num_methods] = { + DCP_METHOD("A000", dcpep_late_init_signal), + DCP_METHOD("A029", dcpep_setup_video_limits), + DCP_METHOD("A034", dcpep_update_notify_clients_dcp), + DCP_METHOD("A357", dcpep_set_create_dfb), + DCP_METHOD("A401", dcpep_start_signal), + DCP_METHOD("A407", dcpep_swap_start), + DCP_METHOD("A408", dcpep_swap_submit), + DCP_METHOD("A410", dcpep_set_display_device), + DCP_METHOD("A411", dcpep_is_main_display), + DCP_METHOD("A412", dcpep_set_digital_out_mode), + DCP_METHOD("A439", dcpep_set_parameter_dcp), + DCP_METHOD("A443", dcpep_create_default_fb), + DCP_METHOD("A447", dcpep_enable_disable_video_power_savings), + DCP_METHOD("A454", dcpep_first_client_open), + DCP_METHOD("A460", dcpep_set_display_refresh_properties), + DCP_METHOD("A463", dcpep_flush_supports_power), + DCP_METHOD("A468", dcpep_set_power_state), +}; + +/* Call a DCP function given by a tag */ +static void dcp_push(struct apple_dcp *dcp, bool oob, enum dcpep_method method, + u32 in_len, u32 out_len, void *data, dcp_callback_t cb, + void *cookie) +{ + struct dcp_call_channel *ch = oob ? &dcp->ch_oobcmd : &dcp->ch_cmd; + enum dcp_context_id context = dcp_call_context(dcp, oob); + + struct dcp_packet_header header = { + .in_len = in_len, + .out_len = out_len, + + /* Tag is reversed due to endianness of the fourcc */ + .tag[0] = dcp_methods[method].tag[3], + .tag[1] = dcp_methods[method].tag[2], + .tag[2] = dcp_methods[method].tag[1], + .tag[3] = dcp_methods[method].tag[0], + }; + + u8 depth = dcp_push_depth(&ch->depth); + u16 offset = dcp_packet_start(ch, depth); + + void *out = dcp->shmem + dcp_tx_offset(context) + offset; + void *out_data = out + sizeof(header); + size_t data_len = sizeof(header) + in_len + out_len; + + memcpy(out, &header, sizeof(header)); + + if (in_len > 0) + memcpy(out_data, data, in_len); + + dev_dbg(dcp->dev, "---> %s: context %u, offset %u, depth %u\n", + dcp_methods[method].name, context, offset, depth); + + ch->callbacks[depth] = cb; + ch->cookies[depth] = cookie; + ch->output[depth] = out + sizeof(header) + in_len; + ch->end[depth] = offset + ALIGN(data_len, DCP_PACKET_ALIGNMENT); + + apple_rtkit_send_message(dcp->rtk, IOMFB_ENDPOINT, + dcpep_msg(context, data_len, offset), NULL, + false); +} + +#define DCP_THUNK_VOID(func, handle) \ + static void func(struct apple_dcp *dcp, bool oob, dcp_callback_t cb, \ + void *cookie) \ + { \ + dcp_push(dcp, oob, handle, 0, 0, NULL, cb, cookie); \ + } + +#define DCP_THUNK_OUT(func, handle, T) \ + static void func(struct apple_dcp *dcp, bool oob, dcp_callback_t cb, \ + void *cookie) \ + { \ + dcp_push(dcp, oob, handle, 0, sizeof(T), NULL, cb, cookie); \ + } + +#define DCP_THUNK_IN(func, handle, T) \ + static void func(struct apple_dcp *dcp, bool oob, T *data, \ + dcp_callback_t cb, void *cookie) \ + { \ + dcp_push(dcp, oob, handle, sizeof(T), 0, data, cb, cookie); \ + } + +#define DCP_THUNK_INOUT(func, handle, T_in, T_out) \ + static void func(struct apple_dcp *dcp, bool oob, T_in *data, \ + dcp_callback_t cb, void *cookie) \ + { \ + dcp_push(dcp, oob, handle, sizeof(T_in), sizeof(T_out), data, \ + cb, cookie); \ + } + +DCP_THUNK_INOUT(dcp_swap_submit, dcpep_swap_submit, struct dcp_swap_submit_req, + struct dcp_swap_submit_resp); + +DCP_THUNK_INOUT(dcp_swap_start, dcpep_swap_start, struct dcp_swap_start_req, + struct dcp_swap_start_resp); + +DCP_THUNK_INOUT(dcp_set_power_state, dcpep_set_power_state, + struct dcp_set_power_state_req, + struct dcp_set_power_state_resp); + +DCP_THUNK_INOUT(dcp_set_digital_out_mode, dcpep_set_digital_out_mode, + struct dcp_set_digital_out_mode_req, u32); + +DCP_THUNK_INOUT(dcp_set_display_device, dcpep_set_display_device, u32, u32); + +DCP_THUNK_OUT(dcp_set_display_refresh_properties, + dcpep_set_display_refresh_properties, u32); + +DCP_THUNK_OUT(dcp_late_init_signal, dcpep_late_init_signal, u32); +DCP_THUNK_IN(dcp_flush_supports_power, dcpep_flush_supports_power, u32); +DCP_THUNK_OUT(dcp_create_default_fb, dcpep_create_default_fb, u32); +DCP_THUNK_OUT(dcp_start_signal, dcpep_start_signal, u32); +DCP_THUNK_VOID(dcp_setup_video_limits, dcpep_setup_video_limits); +DCP_THUNK_VOID(dcp_set_create_dfb, dcpep_set_create_dfb); +DCP_THUNK_VOID(dcp_first_client_open, dcpep_first_client_open); + +__attribute__((unused)) +DCP_THUNK_IN(dcp_update_notify_clients_dcp, dcpep_update_notify_clients_dcp, + struct dcp_update_notify_clients_dcp); + +DCP_THUNK_INOUT(dcp_set_parameter_dcp, dcpep_set_parameter_dcp, + struct dcp_set_parameter_dcp, u32); + +DCP_THUNK_INOUT(dcp_enable_disable_video_power_savings, + dcpep_enable_disable_video_power_savings, u32, int); + +DCP_THUNK_OUT(dcp_is_main_display, dcpep_is_main_display, u32); + +/* Parse a callback tag "D123" into the ID 123. Returns -EINVAL on failure. */ +static int dcp_parse_tag(char tag[4]) +{ + u32 d[3]; + int i; + + if (tag[3] != 'D') + return -EINVAL; + + for (i = 0; i < 3; ++i) { + d[i] = (u32)(tag[i] - '0'); + + if (d[i] > 9) + return -EINVAL; + } + + return d[0] + (d[1] * 10) + (d[2] * 100); +} + +/* Ack a callback from the DCP */ +static void dcp_ack(struct apple_dcp *dcp, enum dcp_context_id context) +{ + struct dcp_cb_channel *ch = dcp_get_cb_channel(dcp, context); + + dcp_pop_depth(&ch->depth); + apple_rtkit_send_message(dcp->rtk, IOMFB_ENDPOINT, dcpep_ack(context), + NULL, false); +} + +/* DCP callback handlers */ +static void dcpep_cb_nop(struct apple_dcp *dcp) +{ + /* No operation */ +} + +static u8 dcpep_cb_true(struct apple_dcp *dcp) +{ + return true; +} + +static u8 dcpep_cb_false(struct apple_dcp *dcp) +{ + return false; +} + +static u32 dcpep_cb_zero(struct apple_dcp *dcp) +{ + return 0; +} + +static void dcpep_cb_swap_complete(struct apple_dcp *dcp, + struct dc_swap_complete_resp *resp) +{ + dev_dbg(dcp->dev, "swap complete for swap_id: %u vblank: %u", + resp->swap_id, dcp->ignore_swap_complete); + + if (!dcp->ignore_swap_complete) + dcp_drm_crtc_vblank(dcp->crtc); +} + +static struct dcp_get_uint_prop_resp +dcpep_cb_get_uint_prop(struct apple_dcp *dcp, struct dcp_get_uint_prop_req *req) +{ + /* unimplemented for now */ + return (struct dcp_get_uint_prop_resp){ .value = 0 }; +} + +/* + * Callback to map a buffer allocated with allocate_buf for PIODMA usage. + * PIODMA is separate from the main DCP and uses own IOVA space on a dedicated + * stream of the display DART, rather than the expected DCP DART. + * + * XXX: This relies on dma_get_sgtable in concert with dma_map_sgtable, which + * is a "fundamentally unsafe" operation according to the docs. And yet + * everyone does it... + */ +static struct dcp_map_buf_resp dcpep_cb_map_piodma(struct apple_dcp *dcp, + struct dcp_map_buf_req *req) +{ + struct sg_table *map; + int ret; + + if (req->buffer >= ARRAY_SIZE(dcp->memdesc)) + goto reject; + + map = &dcp->memdesc[req->buffer].map; + + if (!map->sgl) + goto reject; + + /* Use PIODMA device instead of DCP to map against the right IOMMU. */ + ret = dma_map_sgtable(&dcp->piodma->dev, map, DMA_BIDIRECTIONAL, 0); + + if (ret) + goto reject; + + return (struct dcp_map_buf_resp){ .dva = sg_dma_address(map->sgl) }; + +reject: + dev_err(dcp->dev, "denying map of invalid buffer %llx for pidoma\n", + req->buffer); + return (struct dcp_map_buf_resp){ .ret = EINVAL }; +} + +static void dcpep_cb_unmap_piodma(struct apple_dcp *dcp, + struct dcp_unmap_buf_resp *resp) +{ + struct sg_table *map; + dma_addr_t dma_addr; + + if (resp->buffer >= ARRAY_SIZE(dcp->memdesc)) { + dev_warn(dcp->dev, "unmap request for out of range buffer %llu", + resp->buffer); + return; + } + + map = &dcp->memdesc[resp->buffer].map; + + if (!map->sgl) { + dev_warn(dcp->dev, + "unmap for non-mapped buffer %llu iova:0x%08llx", + resp->buffer, resp->dva); + return; + } + + dma_addr = sg_dma_address(map->sgl); + if (dma_addr != resp->dva) { + dev_warn(dcp->dev, "unmap buffer %llu address mismatch dma_addr:%llx dva:%llx", + resp->buffer, dma_addr, resp->dva); + return; + } + + /* Use PIODMA device instead of DCP to unmap from the right IOMMU. */ + dma_unmap_sgtable(&dcp->piodma->dev, map, DMA_BIDIRECTIONAL, 0); +} + +/* + * Allocate an IOVA contiguous buffer mapped to the DCP. The buffer need not be + * physically contigiuous, however we should save the sgtable in case the + * buffer needs to be later mapped for PIODMA. + */ +static struct dcp_allocate_buffer_resp +dcpep_cb_allocate_buffer(struct apple_dcp *dcp, + struct dcp_allocate_buffer_req *req) +{ + struct dcp_allocate_buffer_resp resp = { 0 }; + struct dcp_mem_descriptor *memdesc; + u32 id; + + resp.dva_size = ALIGN(req->size, 4096); + resp.mem_desc_id = + find_first_zero_bit(dcp->memdesc_map, DCP_MAX_MAPPINGS); + + if (resp.mem_desc_id >= DCP_MAX_MAPPINGS) { + dev_warn(dcp->dev, "DCP overflowed mapping table, ignoring"); + resp.dva_size = 0; + resp.mem_desc_id = 0; + return resp; + } + id = resp.mem_desc_id; + set_bit(id, dcp->memdesc_map); + + memdesc = &dcp->memdesc[id]; + + memdesc->size = resp.dva_size; + memdesc->buf = dma_alloc_coherent(dcp->dev, memdesc->size, + &memdesc->dva, GFP_KERNEL); + + dma_get_sgtable(dcp->dev, &memdesc->map, memdesc->buf, memdesc->dva, + memdesc->size); + resp.dva = memdesc->dva; + + return resp; +} + +static u8 dcpep_cb_release_mem_desc(struct apple_dcp *dcp, u32 *mem_desc_id) +{ + struct dcp_mem_descriptor *memdesc; + u32 id = *mem_desc_id; + + if (id >= DCP_MAX_MAPPINGS) { + dev_warn(dcp->dev, + "unmap request for out of range mem_desc_id %u", id); + return 0; + } + + if (!test_and_clear_bit(id, dcp->memdesc_map)) { + dev_warn(dcp->dev, "unmap request for unused mem_desc_id %u", + id); + return 0; + } + + memdesc = &dcp->memdesc[id]; + if (memdesc->buf) { + dma_free_coherent(dcp->dev, memdesc->size, memdesc->buf, + memdesc->dva); + + memdesc->buf = NULL; + memset(&memdesc->map, 0, sizeof(memdesc->map)); + } else { + memdesc->reg = 0; + } + + memdesc->size = 0; + + return 1; +} + +/* Validate that the specified region is a display register */ +static bool is_disp_register(struct apple_dcp *dcp, u64 start, u64 end) +{ + int i; + + for (i = 0; i < dcp->nr_disp_registers; ++i) { + struct resource *r = dcp->disp_registers[i]; + + if ((start >= r->start) && (end <= r->end)) + return true; + } + + return false; +} + +/* + * Map contiguous physical memory into the DCP's address space. The firmware + * uses this to map the display registers we advertise in + * sr_map_device_memory_with_index, so we bounds check against that to guard + * safe against malicious coprocessors. + */ +static struct dcp_map_physical_resp +dcpep_cb_map_physical(struct apple_dcp *dcp, struct dcp_map_physical_req *req) +{ + int size = ALIGN(req->size, 4096); + u32 id; + + if (!is_disp_register(dcp, req->paddr, req->paddr + size - 1)) { + dev_err(dcp->dev, "refusing to map phys address %llx size %llx", + req->paddr, req->size); + return (struct dcp_map_physical_resp){}; + } + + id = find_first_zero_bit(dcp->memdesc_map, DCP_MAX_MAPPINGS); + set_bit(id, dcp->memdesc_map); + dcp->memdesc[id].size = size; + dcp->memdesc[id].reg = req->paddr; + + return (struct dcp_map_physical_resp){ + .dva_size = size, + .mem_desc_id = id, + .dva = dma_map_resource(dcp->dev, req->paddr, size, + DMA_BIDIRECTIONAL, 0), + }; +} + +static u64 dcpep_cb_get_frequency(struct apple_dcp *dcp) +{ + return clk_get_rate(dcp->clk); +} + +static struct dcp_map_reg_resp dcpep_cb_map_reg(struct apple_dcp *dcp, + struct dcp_map_reg_req *req) +{ + if (req->index >= dcp->nr_disp_registers) { + dev_warn(dcp->dev, "attempted to read invalid reg index %u", + req->index); + + return (struct dcp_map_reg_resp){ .ret = 1 }; + } else { + struct resource *rsrc = dcp->disp_registers[req->index]; + + return (struct dcp_map_reg_resp){ + .addr = rsrc->start, .length = resource_size(rsrc) + }; + } +} + +static struct dcp_read_edt_data_resp +dcpep_cb_read_edt_data(struct apple_dcp *dcp, struct dcp_read_edt_data_req *req) +{ + return (struct dcp_read_edt_data_resp){ + .value[0] = req->value[0], + .ret = 0, + }; +} + +/* Chunked data transfer for property dictionaries */ +static u8 dcpep_cb_prop_start(struct apple_dcp *dcp, u32 *length) +{ + if (dcp->chunks.data != NULL) { + dev_warn(dcp->dev, "ignoring spurious transfer start\n"); + return false; + } + + dcp->chunks.length = *length; + dcp->chunks.data = devm_kzalloc(dcp->dev, *length, GFP_KERNEL); + + if (!dcp->chunks.data) { + dev_warn(dcp->dev, "failed to allocate chunks\n"); + return false; + } + + return true; +} + +static u8 dcpep_cb_prop_chunk(struct apple_dcp *dcp, + struct dcp_set_dcpav_prop_chunk_req *req) +{ + if (!dcp->chunks.data) { + dev_warn(dcp->dev, "ignoring spurious chunk\n"); + return false; + } + + if (req->offset + req->length > dcp->chunks.length) { + dev_warn(dcp->dev, "ignoring overflowing chunk\n"); + return false; + } + + memcpy(dcp->chunks.data + req->offset, req->data, req->length); + return true; +} + +static bool dcpep_process_chunks(struct apple_dcp *dcp, + struct dcp_set_dcpav_prop_end_req *req) +{ + struct dcp_parse_ctx ctx; + int ret; + + if (!dcp->chunks.data) { + dev_warn(dcp->dev, "ignoring spurious end\n"); + return false; + } + + ret = parse(dcp->chunks.data, dcp->chunks.length, &ctx); + + if (ret) { + dev_warn(dcp->dev, "bad header on dcpav props\n"); + return false; + } + + if (!strcmp(req->key, "TimingElements")) { + dcp->modes = enumerate_modes(&ctx, &dcp->nr_modes, + dcp->width_mm, dcp->height_mm); + + if (IS_ERR(dcp->modes)) { + dev_warn(dcp->dev, "failed to parse modes\n"); + dcp->modes = NULL; + dcp->nr_modes = 0; + return false; + } + } else if (!strcmp(req->key, "DisplayAttributes")) { + ret = parse_display_attributes(&ctx, &dcp->width_mm, + &dcp->height_mm); + + if (ret) { + dev_warn(dcp->dev, "failed to parse display attribs\n"); + return false; + } + + dcp_set_dimensions(dcp); + } + + return true; +} + +static u8 dcpep_cb_prop_end(struct apple_dcp *dcp, + struct dcp_set_dcpav_prop_end_req *req) +{ + u8 resp = dcpep_process_chunks(dcp, req); + + /* Reset for the next transfer */ + devm_kfree(dcp->dev, dcp->chunks.data); + dcp->chunks.data = NULL; + + return resp; +} + +/* Boot sequence */ +static void boot_done(struct apple_dcp *dcp, void *out, void *cookie) +{ + struct dcp_cb_channel *ch = &dcp->ch_cb; + u8 *succ = ch->output[ch->depth - 1]; + dev_dbg(dcp->dev, "boot done"); + + *succ = true; + dcp_ack(dcp, DCP_CONTEXT_CB); +} + +static void boot_5(struct apple_dcp *dcp, void *out, void *cookie) +{ + dcp_set_display_refresh_properties(dcp, false, boot_done, NULL); +} + +static void boot_4(struct apple_dcp *dcp, void *out, void *cookie) +{ + dcp_late_init_signal(dcp, false, boot_5, NULL); +} + +static void boot_3(struct apple_dcp *dcp, void *out, void *cookie) +{ + u32 v_true = true; + + dcp_flush_supports_power(dcp, false, &v_true, boot_4, NULL); +} + +static void boot_2(struct apple_dcp *dcp, void *out, void *cookie) +{ + dcp_setup_video_limits(dcp, false, boot_3, NULL); +} + +static void boot_1_5(struct apple_dcp *dcp, void *out, void *cookie) +{ + dcp_create_default_fb(dcp, false, boot_2, NULL); +} + +/* Use special function signature to defer the ACK */ +static bool dcpep_cb_boot_1(struct apple_dcp *dcp, int tag, void *out, void *in) +{ + dev_dbg(dcp->dev, "Callback D%03d %s\n", tag, __func__); + dcp_set_create_dfb(dcp, false, boot_1_5, NULL); + return false; +} + +static struct dcp_rt_bandwidth dcpep_cb_rt_bandwidth(struct apple_dcp *dcp) +{ + if (dcp->disp_registers[5] && dcp->disp_registers[6]) + return (struct dcp_rt_bandwidth){ + .reg_scratch = + dcp->disp_registers[5]->start + REG_SCRATCH, + .reg_doorbell = + dcp->disp_registers[6]->start + REG_DOORBELL, + .doorbell_bit = REG_DOORBELL_BIT, + + .padding[3] = 0x4, // XXX: required by 11.x firmware + }; + else if (dcp->disp_registers[4]) + return (struct dcp_rt_bandwidth){ + .reg_scratch = dcp->disp_registers[4]->start + + REG_SCRATCH_T600X, + .reg_doorbell = 0, + .doorbell_bit = 0, + }; + else + return (struct dcp_rt_bandwidth){ + .reg_scratch = 0, + .reg_doorbell = 0, + .doorbell_bit = 0, + }; +} + +/* Callback to get the current time as milliseconds since the UNIX epoch */ +static u64 dcpep_cb_get_time(struct apple_dcp *dcp) +{ + return ktime_to_ms(ktime_get_real()); +} + +struct dcp_swap_cookie { + struct completion done; + atomic_t refcount; + u32 swap_id; +}; + +static void dcp_swap_cleared(struct apple_dcp *dcp, void *data, void *cookie) +{ + struct dcp_swap_submit_resp *resp = data; + dev_dbg(dcp->dev, "%s", __func__); + + if (cookie) { + struct dcp_swap_cookie *info = cookie; + complete(&info->done); + if (atomic_dec_and_test(&info->refcount)) + kfree(info); + } + + if (resp->ret) { + dev_err(dcp->dev, "swap_clear failed! status %u\n", resp->ret); + dcp_drm_crtc_vblank(dcp->crtc); + return; + } + + while (!list_empty(&dcp->swapped_out_fbs)) { + struct dcp_fb_reference *entry; + entry = list_first_entry(&dcp->swapped_out_fbs, + struct dcp_fb_reference, head); + if (entry->fb) + drm_framebuffer_put(entry->fb); + list_del(&entry->head); + kfree(entry); + } +} + +static void dcp_swap_clear_started(struct apple_dcp *dcp, void *data, + void *cookie) +{ + struct dcp_swap_start_resp *resp = data; + dev_dbg(dcp->dev, "%s swap_id: %u", __func__, resp->swap_id); + dcp->swap.swap.swap_id = resp->swap_id; + + if (cookie) { + struct dcp_swap_cookie *info = cookie; + info->swap_id = resp->swap_id; + } + + dcp_swap_submit(dcp, false, &dcp->swap, dcp_swap_cleared, cookie); +} + +static void dcp_on_final(struct apple_dcp *dcp, void *out, void *cookie) +{ + struct dcp_wait_cookie *wait = cookie; + dev_dbg(dcp->dev, "%s", __func__); + + if (wait) { + complete(&wait->done); + if (atomic_dec_and_test(&wait->refcount)) + kfree(wait); + } +} + +static void dcp_on_set_parameter(struct apple_dcp *dcp, void *out, void *cookie) +{ + struct dcp_set_parameter_dcp param = { + .param = 14, + .value = { 0 }, + .count = 1, + }; + dev_dbg(dcp->dev, "%s", __func__); + + dcp_set_parameter_dcp(dcp, false, ¶m, dcp_on_final, cookie); +} + +void dcp_poweron(struct platform_device *pdev) +{ + struct apple_dcp *dcp = platform_get_drvdata(pdev); + struct dcp_wait_cookie *cookie; + struct dcp_set_power_state_req req = { + .unklong = 1, + }; + int ret; + u32 handle; + dev_dbg(dcp->dev, "%s", __func__); + + cookie = kzalloc(sizeof(*cookie), GFP_KERNEL); + if (!cookie) + return; + + init_completion(&cookie->done); + atomic_set(&cookie->refcount, 2); + + if (dcp->main_display) { + handle = 0; + dcp_set_display_device(dcp, false, &handle, dcp_on_final, + cookie); + } else { + handle = 2; + dcp_set_display_device(dcp, false, &handle, + dcp_on_set_parameter, cookie); + } + dcp_set_power_state(dcp, true, &req, NULL, NULL); + + ret = wait_for_completion_timeout(&cookie->done, msecs_to_jiffies(500)); + + if (ret == 0) + dev_warn(dcp->dev, "wait for power timed out"); + + if (atomic_dec_and_test(&cookie->refcount)) + kfree(cookie); +} +EXPORT_SYMBOL(dcp_poweron); + +static void complete_set_powerstate(struct apple_dcp *dcp, void *out, + void *cookie) +{ + struct dcp_wait_cookie *wait = cookie; + + if (wait) { + complete(&wait->done); + if (atomic_dec_and_test(&wait->refcount)) + kfree(wait); + } +} + +void dcp_poweroff(struct platform_device *pdev) +{ + struct apple_dcp *dcp = platform_get_drvdata(pdev); + int ret, swap_id; + struct dcp_set_power_state_req power_req = { + .unklong = 0, + }; + struct dcp_swap_cookie *cookie; + struct dcp_wait_cookie *poff_cookie; + struct dcp_swap_start_req swap_req = { 0 }; + + dev_dbg(dcp->dev, "%s", __func__); + + cookie = kzalloc(sizeof(*cookie), GFP_KERNEL); + if (!cookie) + return; + init_completion(&cookie->done); + atomic_set(&cookie->refcount, 2); + + // clear surfaces + memset(&dcp->swap, 0, sizeof(dcp->swap)); + + dcp->swap.swap.swap_enabled = DCP_REMOVE_LAYERS | 0x7; + dcp->swap.swap.swap_completed = DCP_REMOVE_LAYERS | 0x7; + dcp->swap.swap.unk_10c = 0xFF000000; + + for (int l = 0; l < SWAP_SURFACES; l++) + dcp->swap.surf_null[l] = true; + + dcp_swap_start(dcp, false, &swap_req, dcp_swap_clear_started, cookie); + + ret = wait_for_completion_timeout(&cookie->done, msecs_to_jiffies(50)); + swap_id = cookie->swap_id; + if (atomic_dec_and_test(&cookie->refcount)) + kfree(cookie); + if (ret <= 0) { + dcp->crashed = true; + return; + } + + dev_dbg(dcp->dev, "%s: clear swap submitted: %u", __func__, swap_id); + + poff_cookie = kzalloc(sizeof(*poff_cookie), GFP_KERNEL); + if (!poff_cookie) + return; + init_completion(&poff_cookie->done); + atomic_set(&poff_cookie->refcount, 2); + + dcp_set_power_state(dcp, false, &power_req, complete_set_powerstate, + poff_cookie); + ret = wait_for_completion_timeout(&poff_cookie->done, + msecs_to_jiffies(1000)); + + if (ret == 0) + dev_warn(dcp->dev, "setPowerState(0) timeout %u ms", 1000); + else if (ret > 0) + dev_dbg(dcp->dev, + "setPowerState(0) finished with %d ms to spare", + jiffies_to_msecs(ret)); + + if (atomic_dec_and_test(&poff_cookie->refcount)) + kfree(poff_cookie); + dev_dbg(dcp->dev, "%s: setPowerState(0) done", __func__); +} +EXPORT_SYMBOL(dcp_poweroff); + +/* + * Helper to send a DRM hotplug event. The DCP is accessed from a single + * (RTKit) thread. To handle hotplug callbacks, we need to call + * drm_kms_helper_hotplug_event, which does an atomic commit (via DCP) and + * waits for vblank (a DCP callback). That means we deadlock if we call from + * the RTKit thread! Instead, move the call to another thread via a workqueue. + */ +void dcp_hotplug(struct work_struct *work) +{ + struct apple_connector *connector; + struct drm_device *dev; + struct apple_dcp *dcp; + + connector = container_of(work, struct apple_connector, hotplug_wq); + dev = connector->base.dev; + + dcp = platform_get_drvdata(connector->dcp); + dev_info(dcp->dev, "%s: connected: %d", __func__, connector->connected); + + /* + * DCP defers link training until we set a display mode. But we set + * display modes from atomic_flush, so userspace needs to trigger a + * flush, or the CRTC gets no signal. + */ + if (!dcp->valid_mode && connector->connected) { + drm_connector_set_link_status_property( + &connector->base, DRM_MODE_LINK_STATUS_BAD); + } + + if (dev && dev->registered) + drm_kms_helper_hotplug_event(dev); +} +EXPORT_SYMBOL_GPL(dcp_hotplug); + +static void dcpep_cb_hotplug(struct apple_dcp *dcp, u64 *connected) +{ + struct apple_connector *connector = dcp->connector; + + /* Hotplug invalidates mode. DRM doesn't always handle this. */ + if (!(*connected)) { + dcp->valid_mode = false; + /* after unplug swap will not complete until the next + * set_digital_out_mode */ + schedule_work(&dcp->vblank_wq); + } + + if (connector && connector->connected != !!(*connected)) { + connector->connected = !!(*connected); + dcp->valid_mode = false; + schedule_work(&connector->hotplug_wq); + } +} + +static void +dcpep_cb_swap_complete_intent_gated(struct apple_dcp *dcp, + struct dcp_swap_complete_intent_gated *info) +{ + dev_dbg(dcp->dev, "swap_id:%u width:%u height:%u", info->swap_id, + info->width, info->height); +} + +#define DCPEP_MAX_CB (1000) + +/* + * Define type-safe trampolines. Define typedefs to enforce type-safety on the + * input data (so if the types don't match, gcc errors out). + */ + +#define TRAMPOLINE_VOID(func, handler) \ + static bool func(struct apple_dcp *dcp, int tag, void *out, void *in) \ + { \ + dev_dbg(dcp->dev, "Callback D%03d %s\n", tag, #handler); \ + handler(dcp); \ + return true; \ + } + +#define TRAMPOLINE_IN(func, handler, T_in) \ + typedef void (*callback_##handler)(struct apple_dcp *, T_in *); \ + \ + static bool func(struct apple_dcp *dcp, int tag, void *out, void *in) \ + { \ + callback_##handler cb = handler; \ + \ + dev_dbg(dcp->dev, "Callback D%03d %s\n", tag, #handler); \ + cb(dcp, in); \ + return true; \ + } + +#define TRAMPOLINE_INOUT(func, handler, T_in, T_out) \ + typedef T_out (*callback_##handler)(struct apple_dcp *, T_in *); \ + \ + static bool func(struct apple_dcp *dcp, int tag, void *out, void *in) \ + { \ + T_out *typed_out = out; \ + callback_##handler cb = handler; \ + \ + dev_dbg(dcp->dev, "Callback D%03d %s\n", tag, #handler); \ + *typed_out = cb(dcp, in); \ + return true; \ + } + +#define TRAMPOLINE_OUT(func, handler, T_out) \ + static bool func(struct apple_dcp *dcp, int tag, void *out, void *in) \ + { \ + T_out *typed_out = out; \ + \ + dev_dbg(dcp->dev, "Callback D%03d %s\n", tag, #handler); \ + *typed_out = handler(dcp); \ + return true; \ + } + +TRAMPOLINE_VOID(trampoline_nop, dcpep_cb_nop); +TRAMPOLINE_OUT(trampoline_true, dcpep_cb_true, u8); +TRAMPOLINE_OUT(trampoline_false, dcpep_cb_false, u8); +TRAMPOLINE_OUT(trampoline_zero, dcpep_cb_zero, u32); +TRAMPOLINE_IN(trampoline_swap_complete, dcpep_cb_swap_complete, + struct dc_swap_complete_resp); +TRAMPOLINE_INOUT(trampoline_get_uint_prop, dcpep_cb_get_uint_prop, + struct dcp_get_uint_prop_req, struct dcp_get_uint_prop_resp); +TRAMPOLINE_INOUT(trampoline_map_piodma, dcpep_cb_map_piodma, + struct dcp_map_buf_req, struct dcp_map_buf_resp); +TRAMPOLINE_IN(trampoline_unmap_piodma, dcpep_cb_unmap_piodma, + struct dcp_unmap_buf_resp); +TRAMPOLINE_INOUT(trampoline_allocate_buffer, dcpep_cb_allocate_buffer, + struct dcp_allocate_buffer_req, + struct dcp_allocate_buffer_resp); +TRAMPOLINE_INOUT(trampoline_map_physical, dcpep_cb_map_physical, + struct dcp_map_physical_req, struct dcp_map_physical_resp); +TRAMPOLINE_INOUT(trampoline_release_mem_desc, dcpep_cb_release_mem_desc, u32, + u8); +TRAMPOLINE_INOUT(trampoline_map_reg, dcpep_cb_map_reg, struct dcp_map_reg_req, + struct dcp_map_reg_resp); +TRAMPOLINE_INOUT(trampoline_read_edt_data, dcpep_cb_read_edt_data, + struct dcp_read_edt_data_req, struct dcp_read_edt_data_resp); +TRAMPOLINE_INOUT(trampoline_prop_start, dcpep_cb_prop_start, u32, u8); +TRAMPOLINE_INOUT(trampoline_prop_chunk, dcpep_cb_prop_chunk, + struct dcp_set_dcpav_prop_chunk_req, u8); +TRAMPOLINE_INOUT(trampoline_prop_end, dcpep_cb_prop_end, + struct dcp_set_dcpav_prop_end_req, u8); +TRAMPOLINE_OUT(trampoline_rt_bandwidth, dcpep_cb_rt_bandwidth, + struct dcp_rt_bandwidth); +TRAMPOLINE_OUT(trampoline_get_frequency, dcpep_cb_get_frequency, u64); +TRAMPOLINE_OUT(trampoline_get_time, dcpep_cb_get_time, u64); +TRAMPOLINE_IN(trampoline_hotplug, dcpep_cb_hotplug, u64); +TRAMPOLINE_IN(trampoline_swap_complete_intent_gated, + dcpep_cb_swap_complete_intent_gated, + struct dcp_swap_complete_intent_gated); + +bool (*const dcpep_cb_handlers[DCPEP_MAX_CB])(struct apple_dcp *, int, void *, + void *) = { + [0] = trampoline_true, /* did_boot_signal */ + [1] = trampoline_true, /* did_power_on_signal */ + [2] = trampoline_nop, /* will_power_off_signal */ + [3] = trampoline_rt_bandwidth, + [100] = trampoline_nop, /* match_pmu_service */ + [101] = trampoline_zero, /* get_display_default_stride */ + [103] = trampoline_nop, /* set_boolean_property */ + [106] = trampoline_nop, /* remove_property */ + [107] = trampoline_true, /* create_provider_service */ + [108] = trampoline_true, /* create_product_service */ + [109] = trampoline_true, /* create_pmu_service */ + [110] = trampoline_true, /* create_iomfb_service */ + [111] = trampoline_false, /* create_backlight_service */ + [116] = dcpep_cb_boot_1, + [117] = trampoline_false, /* is_dark_boot */ + [118] = trampoline_false, /* is_dark_boot / is_waking_from_hibernate*/ + [120] = trampoline_read_edt_data, + [122] = trampoline_prop_start, + [123] = trampoline_prop_chunk, + [124] = trampoline_prop_end, + [201] = trampoline_map_piodma, + [202] = trampoline_unmap_piodma, + [206] = trampoline_true, /* match_pmu_service_2 */ + [207] = trampoline_true, /* match_backlight_service */ + [208] = trampoline_get_time, + [211] = trampoline_nop, /* update_backlight_factor_prop */ + [300] = trampoline_nop, /* pr_publish */ + [401] = trampoline_get_uint_prop, + [404] = trampoline_nop, /* sr_set_uint_prop */ + [406] = trampoline_nop, /* set_fx_prop */ + [408] = trampoline_get_frequency, + [411] = trampoline_map_reg, + [413] = trampoline_true, /* sr_set_property_dict */ + [414] = trampoline_true, /* sr_set_property_int */ + [415] = trampoline_true, /* sr_set_property_bool */ + [451] = trampoline_allocate_buffer, + [452] = trampoline_map_physical, + [456] = trampoline_release_mem_desc, + [552] = trampoline_true, /* set_property_dict_0 */ + [561] = trampoline_true, /* set_property_dict */ + [563] = trampoline_true, /* set_property_int */ + [565] = trampoline_true, /* set_property_bool */ + [567] = trampoline_true, /* set_property_str */ + [574] = trampoline_zero, /* power_up_dart */ + [576] = trampoline_hotplug, + [577] = trampoline_nop, /* powerstate_notify */ + [582] = trampoline_true, /* create_default_fb_surface */ + [589] = trampoline_swap_complete, + [591] = trampoline_swap_complete_intent_gated, + [593] = trampoline_nop, /* enable_backlight_message_ap_gated */ + [598] = trampoline_nop, /* find_swap_function_gated */ +}; + +static void dcpep_handle_cb(struct apple_dcp *dcp, enum dcp_context_id context, + void *data, u32 length) +{ + struct device *dev = dcp->dev; + struct dcp_packet_header *hdr = data; + void *in, *out; + int tag = dcp_parse_tag(hdr->tag); + struct dcp_cb_channel *ch = dcp_get_cb_channel(dcp, context); + u8 depth; + + if (tag < 0 || tag >= DCPEP_MAX_CB || !dcpep_cb_handlers[tag]) { + dev_warn(dev, "received unknown callback %c%c%c%c\n", + hdr->tag[3], hdr->tag[2], hdr->tag[1], hdr->tag[0]); + return; + } + + in = data + sizeof(*hdr); + out = in + hdr->in_len; + + // TODO: verify that in_len and out_len match our prototypes + // for now just clear the out data to have at least consistant results + if (hdr->out_len) + memset(out, 0, hdr->out_len); + + depth = dcp_push_depth(&ch->depth); + ch->output[depth] = out; + + if (dcpep_cb_handlers[tag](dcp, tag, out, in)) + dcp_ack(dcp, context); +} + +static void dcpep_handle_ack(struct apple_dcp *dcp, enum dcp_context_id context, + void *data, u32 length) +{ + struct dcp_packet_header *header = data; + struct dcp_call_channel *ch = dcp_get_call_channel(dcp, context); + void *cookie; + dcp_callback_t cb; + + if (!ch) { + dev_warn(dcp->dev, "ignoring ack on context %X\n", context); + return; + } + + dcp_pop_depth(&ch->depth); + + cb = ch->callbacks[ch->depth]; + cookie = ch->cookies[ch->depth]; + + ch->callbacks[ch->depth] = NULL; + ch->cookies[ch->depth] = NULL; + + if (cb) + cb(dcp, data + sizeof(*header) + header->in_len, cookie); +} + +static void dcpep_got_msg(struct apple_dcp *dcp, u64 message) +{ + enum dcp_context_id ctx_id; + u16 offset; + u32 length; + int channel_offset; + void *data; + + ctx_id = (message & DCPEP_CONTEXT_MASK) >> DCPEP_CONTEXT_SHIFT; + offset = (message & DCPEP_OFFSET_MASK) >> DCPEP_OFFSET_SHIFT; + length = (message >> DCPEP_LENGTH_SHIFT); + + channel_offset = dcp_channel_offset(ctx_id); + + if (channel_offset < 0) { + dev_warn(dcp->dev, "invalid context received %u", ctx_id); + return; + } + + data = dcp->shmem + channel_offset + offset; + + if (message & DCPEP_ACK) + dcpep_handle_ack(dcp, ctx_id, data, length); + else + dcpep_handle_cb(dcp, ctx_id, data, length); +} + +/* + * Callback for swap requests. If a swap failed, we'll never get a swap + * complete event so we need to fake a vblank event early to avoid a hang. + */ + +static void dcp_swapped(struct apple_dcp *dcp, void *data, void *cookie) +{ + struct dcp_swap_submit_resp *resp = data; + + if (resp->ret) { + dev_err(dcp->dev, "swap failed! status %u\n", resp->ret); + dcp_drm_crtc_vblank(dcp->crtc); + return; + } + + while (!list_empty(&dcp->swapped_out_fbs)) { + struct dcp_fb_reference *entry; + entry = list_first_entry(&dcp->swapped_out_fbs, + struct dcp_fb_reference, head); + if (entry->fb) + drm_framebuffer_put(entry->fb); + list_del(&entry->head); + kfree(entry); + } +} + +static void dcp_swap_started(struct apple_dcp *dcp, void *data, void *cookie) +{ + struct dcp_swap_start_resp *resp = data; + + dcp->swap.swap.swap_id = resp->swap_id; + + dcp_swap_submit(dcp, false, &dcp->swap, dcp_swapped, NULL); +} + +/* + * DRM specifies rectangles as start and end coordinates. DCP specifies + * rectangles as a start coordinate and a width/height. Convert a DRM rectangle + * to a DCP rectangle. + */ +static struct dcp_rect drm_to_dcp_rect(struct drm_rect *rect) +{ + return (struct dcp_rect){ .x = rect->x1, + .y = rect->y1, + .w = drm_rect_width(rect), + .h = drm_rect_height(rect) }; +} + +static u32 drm_format_to_dcp(u32 drm) +{ + switch (drm) { + case DRM_FORMAT_XRGB8888: + case DRM_FORMAT_ARGB8888: + return fourcc_code('A', 'R', 'G', 'B'); + + case DRM_FORMAT_XBGR8888: + case DRM_FORMAT_ABGR8888: + return fourcc_code('A', 'B', 'G', 'R'); + } + + pr_warn("DRM format %X not supported in DCP\n", drm); + return 0; +} + +int dcp_get_modes(struct drm_connector *connector) +{ + struct apple_connector *apple_connector = to_apple_connector(connector); + struct platform_device *pdev = apple_connector->dcp; + struct apple_dcp *dcp = platform_get_drvdata(pdev); + + struct drm_device *dev = connector->dev; + struct drm_display_mode *mode; + int i; + + for (i = 0; i < dcp->nr_modes; ++i) { + mode = drm_mode_duplicate(dev, &dcp->modes[i].mode); + + if (!mode) { + dev_err(dev->dev, "Failed to duplicate display mode\n"); + return 0; + } + + drm_mode_probed_add(connector, mode); + } + + return dcp->nr_modes; +} +EXPORT_SYMBOL_GPL(dcp_get_modes); + +/* The user may own drm_display_mode, so we need to search for our copy */ +static struct dcp_display_mode *lookup_mode(struct apple_dcp *dcp, + struct drm_display_mode *mode) +{ + int i; + + for (i = 0; i < dcp->nr_modes; ++i) { + if (drm_mode_match(mode, &dcp->modes[i].mode, + DRM_MODE_MATCH_TIMINGS | + DRM_MODE_MATCH_CLOCK)) + return &dcp->modes[i]; + } + + return NULL; +} + +int dcp_mode_valid(struct drm_connector *connector, + struct drm_display_mode *mode) +{ + struct apple_connector *apple_connector = to_apple_connector(connector); + struct platform_device *pdev = apple_connector->dcp; + struct apple_dcp *dcp = platform_get_drvdata(pdev); + + return lookup_mode(dcp, mode) ? MODE_OK : MODE_BAD; +} +EXPORT_SYMBOL_GPL(dcp_mode_valid); + +/* Helpers to modeset and swap, used to flush */ +static void do_swap(struct apple_dcp *dcp, void *data, void *cookie) +{ + struct dcp_swap_start_req start_req = { 0 }; + dev_dbg(dcp->dev, "%s", __func__); + + if (dcp->connector && dcp->connector->connected) + dcp_swap_start(dcp, false, &start_req, dcp_swap_started, NULL); + else + dcp_drm_crtc_vblank(dcp->crtc); +} + +static void complete_set_digital_out_mode(struct apple_dcp *dcp, void *data, + void *cookie) +{ + struct dcp_wait_cookie *wait = cookie; + dev_dbg(dcp->dev, "%s", __func__); + + dcp->ignore_swap_complete = false; + + if (wait) { + complete(&wait->done); + if (atomic_dec_and_test(&wait->refcount)) + kfree(wait); + } +} + +void dcp_flush(struct drm_crtc *crtc, struct drm_atomic_state *state) +{ + struct platform_device *pdev = to_apple_crtc(crtc)->dcp; + struct apple_dcp *dcp = platform_get_drvdata(pdev); + struct drm_plane *plane; + struct drm_plane_state *new_state, *old_state; + struct drm_crtc_state *crtc_state; + struct dcp_swap_submit_req *req = &dcp->swap; + int l; + int has_surface = 0; + bool modeset; + dev_dbg(dcp->dev, "%s", __func__); + + crtc_state = drm_atomic_get_new_crtc_state(state, crtc); + + modeset = drm_atomic_crtc_needs_modeset(crtc_state) || !dcp->valid_mode; + + if (WARN(dcp_channel_busy(&dcp->ch_cmd), "unexpected busy channel") || + WARN(!modeset && !dcp->connector->connected, + "can't flush if disconnected")) { + /* HACK: issue a delayed vblank event to avoid timeouts in + * drm_atomic_helper_wait_for_vblanks(). + */ + schedule_work(&dcp->vblank_wq); + return; + } + + /* Reset to defaults */ + memset(req, 0, sizeof(*req)); + for (l = 0; l < SWAP_SURFACES; l++) + req->surf_null[l] = true; + + for_each_oldnew_plane_in_state(state, plane, old_state, new_state, l) { + struct drm_framebuffer *fb = new_state->fb; + struct drm_rect src_rect; + + WARN_ON(l >= SWAP_SURFACES); + + req->swap.swap_enabled |= BIT(l); + + if (old_state->fb && fb != old_state->fb) { + /* + * Race condition between a framebuffer unbind getting + * swapped out and GEM unreferencing a framebuffer. If + * we lose the race, the display gets IOVA faults and + * the DCP crashes. We need to extend the lifetime of + * the drm_framebuffer (and hence the GEM object) until + * after we get a swap complete for the swap unbinding + * it. + */ + struct dcp_fb_reference *entry = + kzalloc(sizeof(*entry), GFP_KERNEL); + if (entry) { + entry->fb = old_state->fb; + list_add_tail(&entry->head, + &dcp->swapped_out_fbs); + } + drm_framebuffer_get(old_state->fb); + } + + if (!new_state->fb) { + if (old_state->fb) + req->swap.swap_enabled |= DCP_REMOVE_LAYERS; + + continue; + } + req->surf_null[l] = false; + has_surface = 1; + + drm_rect_fp_to_int(&src_rect, &new_state->src); + + req->swap.src_rect[l] = drm_to_dcp_rect(&src_rect); + req->swap.dst_rect[l] = drm_to_dcp_rect(&new_state->dst); + + req->surf_iova[l] = drm_fb_dma_get_gem_addr(fb, new_state, 0); + + req->surf[l] = (struct dcp_surface){ + .format = drm_format_to_dcp(fb->format->format), + .xfer_func = 13, + .colorspace = 1, + .stride = fb->pitches[0], + .width = fb->width, + .height = fb->height, + .buf_size = fb->height * fb->pitches[0], + .surface_id = req->swap.surf_ids[l], + + /* Only used for compressed or multiplanar surfaces */ + .pix_size = 1, + .pel_w = 1, + .pel_h = 1, + .has_comp = 1, + .has_planes = 1, + }; + } + + /* These fields should be set together */ + req->swap.swap_completed = req->swap.swap_enabled; + + if (modeset) { + struct dcp_display_mode *mode; + struct dcp_wait_cookie *cookie; + int ret; + + mode = lookup_mode(dcp, &crtc_state->mode); + if (!mode) { + dev_warn(dcp->dev, "no match for " DRM_MODE_FMT, + DRM_MODE_ARG(&crtc_state->mode)); + schedule_work(&dcp->vblank_wq); + return; + } + + dev_info(dcp->dev, "set_digital_out_mode(color:%d timing:%d)", + mode->color_mode_id, mode->timing_mode_id); + dcp->mode = (struct dcp_set_digital_out_mode_req){ + .color_mode_id = mode->color_mode_id, + .timing_mode_id = mode->timing_mode_id + }; + + cookie = kzalloc(sizeof(*cookie), GFP_KERNEL); + if (!cookie) { + schedule_work(&dcp->vblank_wq); + return; + } + + init_completion(&cookie->done); + atomic_set(&cookie->refcount, 2); + + dcp_set_digital_out_mode(dcp, false, &dcp->mode, + complete_set_digital_out_mode, cookie); + + dev_dbg(dcp->dev, "%s - wait for modeset", __func__); + ret = wait_for_completion_timeout(&cookie->done, + msecs_to_jiffies(500)); + + if (atomic_dec_and_test(&cookie->refcount)) + kfree(cookie); + + if (ret == 0) { + dev_dbg(dcp->dev, "set_digital_out_mode 200 ms"); + schedule_work(&dcp->vblank_wq); + return; + } else if (ret > 0) { + dev_dbg(dcp->dev, + "set_digital_out_mode finished with %d to spare", + jiffies_to_msecs(ret)); + } + + dcp->valid_mode = true; + } + + if (!has_surface) { + if (crtc_state->enable && crtc_state->active && + !crtc_state->planes_changed) { + schedule_work(&dcp->vblank_wq); + return; + } + + req->clear = 1; + } + do_swap(dcp, NULL, NULL); +} +EXPORT_SYMBOL_GPL(dcp_flush); + +bool dcp_is_initialized(struct platform_device *pdev) +{ + struct apple_dcp *dcp = platform_get_drvdata(pdev); + + return dcp->active; +} +EXPORT_SYMBOL_GPL(dcp_is_initialized); + +static void res_is_main_display(struct apple_dcp *dcp, void *out, void *cookie) +{ + struct apple_connector *connector; + int result = *(int *)out; + dev_info(dcp->dev, "DCP is_main_display: %d\n", result); + + dcp->main_display = result != 0; + + dcp->active = true; + + connector = dcp->connector; + if (connector) { + connector->connected = dcp->nr_modes > 0; + schedule_work(&connector->hotplug_wq); + } +} + +static void init_3(struct apple_dcp *dcp, void *out, void *cookie) +{ + dcp_is_main_display(dcp, false, res_is_main_display, NULL); +} + +static void init_2(struct apple_dcp *dcp, void *out, void *cookie) +{ + dcp_first_client_open(dcp, false, init_3, NULL); +} + +static void init_1(struct apple_dcp *dcp, void *out, void *cookie) +{ + u32 val = 0; + dcp_enable_disable_video_power_savings(dcp, false, &val, init_2, NULL); +} + +static void dcp_started(struct apple_dcp *dcp, void *data, void *cookie) +{ + dev_info(dcp->dev, "DCP booted\n"); + + init_1(dcp, data, cookie); +} + +void iomfb_recv_msg(struct apple_dcp *dcp, u64 message) +{ + enum dcpep_type type = (message >> DCPEP_TYPE_SHIFT) & DCPEP_TYPE_MASK; + + if (type == DCPEP_TYPE_INITIALIZED) + dcp_start_signal(dcp, false, dcp_started, NULL); + else if (type == DCPEP_TYPE_MESSAGE) + dcpep_got_msg(dcp, message); + else + dev_warn(dcp->dev, "Ignoring unknown message %llx\n", message); +} + +int iomfb_start_rtkit(struct apple_dcp *dcp) +{ + dma_addr_t shmem_iova; + apple_rtkit_start_ep(dcp->rtk, IOMFB_ENDPOINT); + + dcp->shmem = dma_alloc_coherent(dcp->dev, DCP_SHMEM_SIZE, &shmem_iova, + GFP_KERNEL); + + shmem_iova |= dcp->asc_dram_mask; + apple_rtkit_send_message(dcp->rtk, IOMFB_ENDPOINT, + dcpep_set_shmem(shmem_iova), NULL, false); + + return 0; +} + +void iomfb_shutdown(struct apple_dcp *dcp) +{ + struct dcp_set_power_state_req req = { + /* defaults are ok */ + }; + + /* We're going down */ + dcp->active = false; + dcp->valid_mode = false; + + dcp_set_power_state(dcp, false, &req, NULL, NULL); +} diff --git a/drivers/gpu/drm/apple/dcpep.h b/drivers/gpu/drm/apple/iomfb.h similarity index 86% rename from drivers/gpu/drm/apple/dcpep.h rename to drivers/gpu/drm/apple/iomfb.h index a796b16bd55ad7..96dfd170b8e330 100644 --- a/drivers/gpu/drm/apple/dcpep.h +++ b/drivers/gpu/drm/apple/iomfb.h @@ -4,8 +4,10 @@ #ifndef __APPLE_DCPEP_H__ #define __APPLE_DCPEP_H__ +#include + /* Endpoint for general DCP traffic (dcpep in macOS) */ -#define DCP_ENDPOINT 0x37 +#define IOMFB_ENDPOINT 0x37 /* Fixed size of shared memory between DCP and AP */ #define DCP_SHMEM_SIZE 0x100000 @@ -30,27 +32,6 @@ enum dcp_context_id { DCP_NUM_CONTEXTS }; -static int dcp_tx_offset(enum dcp_context_id id) -{ - switch (id) { - case DCP_CONTEXT_CB: - case DCP_CONTEXT_CMD: return 0x00000; - case DCP_CONTEXT_OOBCB: - case DCP_CONTEXT_OOBCMD: return 0x08000; - default: return -EINVAL; - } -} - -static int dcp_channel_offset(enum dcp_context_id id) -{ - switch (id) { - case DCP_CONTEXT_ASYNC: return 0x40000; - case DCP_CONTEXT_CB: return 0x60000; - case DCP_CONTEXT_OOBCB: return 0x68000; - default: return dcp_tx_offset(id); - } -} - /* RTKit endpoint message types */ enum dcpep_type { /* Set shared memory */ @@ -87,29 +68,6 @@ struct dcp_packet_header { #define DCP_IS_NULL(ptr) ((ptr) ? 1 : 0) #define DCP_PACKET_ALIGNMENT (0x40) -static inline u64 -dcpep_set_shmem(u64 dart_va) -{ - return (DCPEP_TYPE_SET_SHMEM << DCPEP_TYPE_SHIFT) | - (DCPEP_FLAG_VALUE << DCPEP_FLAG_SHIFT) | - (dart_va << DCPEP_DVA_SHIFT); -} - -static inline u64 -dcpep_msg(enum dcp_context_id id, u32 length, u16 offset) -{ - return (DCPEP_TYPE_MESSAGE << DCPEP_TYPE_SHIFT) | - ((u64) id << DCPEP_CONTEXT_SHIFT) | - ((u64) offset << DCPEP_OFFSET_SHIFT) | - ((u64) length << DCPEP_LENGTH_SHIFT); -} - -static inline u64 -dcpep_ack(enum dcp_context_id id) -{ - return dcpep_msg(id, 0, 0) | DCPEP_ACK; -} - /* Structures used in v12.0 firmware */ #define SWAP_SURFACES 4 From 7dca136d2141d217569791fcc6e6102df07f6d2f Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Mon, 3 Oct 2022 10:43:02 +0200 Subject: [PATCH 1011/3902] WIP: add header test target copied from i915 Signed-off-by: Janne Grunau --- drivers/gpu/drm/apple/.gitignore | 1 + drivers/gpu/drm/apple/Makefile | 15 +++++++++++++++ 2 files changed, 16 insertions(+) create mode 100644 drivers/gpu/drm/apple/.gitignore diff --git a/drivers/gpu/drm/apple/.gitignore b/drivers/gpu/drm/apple/.gitignore new file mode 100644 index 00000000000000..d9a77f3b59b21a --- /dev/null +++ b/drivers/gpu/drm/apple/.gitignore @@ -0,0 +1 @@ +*.hdrtest diff --git a/drivers/gpu/drm/apple/Makefile b/drivers/gpu/drm/apple/Makefile index d1f909792229e5..e6c517bca2b07d 100644 --- a/drivers/gpu/drm/apple/Makefile +++ b/drivers/gpu/drm/apple/Makefile @@ -7,3 +7,18 @@ apple_piodma-y := dummy-piodma.o obj-$(CONFIG_DRM_APPLE) += appledrm.o obj-$(CONFIG_DRM_APPLE) += apple_dcp.o obj-$(CONFIG_DRM_APPLE) += apple_piodma.o + +# header test + +# exclude some broken headers from the test coverage +no-header-test := \ + +always-y += \ + $(patsubst %.h,%.hdrtest, $(filter-out $(no-header-test), \ + $(shell cd $(src) && find * -name '*.h'))) + +quiet_cmd_hdrtest = HDRTEST $(patsubst %.hdrtest,%.h,$@) + cmd_hdrtest = $(CC) $(filter-out $(CFLAGS_GCOV), $(c_flags)) -S -o /dev/null -x c /dev/null -include $<; touch $@ + +$(obj)/%.hdrtest: $(src)/%.h FORCE + $(call if_changed_dep,hdrtest) From 9d997fdd0560304d7ec10d24137f9766946ff99d Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Sat, 22 Oct 2022 09:27:51 +0200 Subject: [PATCH 1012/3902] drm: apple: Use connector types from devicetree Needs to be re-done for upstream submission but the exisiting port, bridge, connector and display bindings are a bad fit since DCP manages everything. Signed-off-by: Janne Grunau --- drivers/gpu/drm/apple/apple_drv.c | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/drivers/gpu/drm/apple/apple_drv.c b/drivers/gpu/drm/apple/apple_drv.c index 620d44719b45fd..5bb5b48e89c595 100644 --- a/drivers/gpu/drm/apple/apple_drv.c +++ b/drivers/gpu/drm/apple/apple_drv.c @@ -25,6 +25,7 @@ #include #include #include +#include #include #include #include @@ -300,6 +301,7 @@ static int apple_probe_per_dcp(struct device *dev, struct apple_connector *connector; struct drm_encoder *encoder; struct drm_plane *primary; + int con_type; int ret; primary = apple_plane_init(drm, DRM_PLANE_TYPE_PRIMARY); @@ -325,8 +327,17 @@ static int apple_probe_per_dcp(struct device *dev, drm_connector_helper_add(&connector->base, &apple_connector_helper_funcs); + if (of_property_match_string(dcp->dev.of_node, "apple,connector-type", "eDP") >= 0) + con_type = DRM_MODE_CONNECTOR_eDP; + else if (of_property_match_string(dcp->dev.of_node, "apple,connector-type", "HDMI-A") >= 0) + con_type = DRM_MODE_CONNECTOR_HDMIA; + else if (of_property_match_string(dcp->dev.of_node, "apple,connector-type", "USB-C") >= 0) + con_type = DRM_MODE_CONNECTOR_USB; + else + con_type = DRM_MODE_CONNECTOR_Unknown; + ret = drm_connector_init(drm, &connector->base, &apple_connector_funcs, - DRM_MODE_CONNECTOR_HDMIA); + con_type); if (ret) return ret; From 119918fe6d3b48b791309f325b8276b8951bcead Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Wed, 5 Oct 2022 22:30:58 +0200 Subject: [PATCH 1013/3902] drm: apple: Fix connector state on devices with integrated display DCP issues hotplug_gated callbacks after SetPowerState() calls on devices with display (macbooks, imacs). This must not result in connector state changes on DRM side. Weston will not re-enable the CRTC after DPMS off if the connector is not in connected state. DCP provides with dcp_is_main_display() a call to query if the device has an integrated display. Signed-off-by: Janne Grunau --- drivers/gpu/drm/apple/iomfb.c | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/drivers/gpu/drm/apple/iomfb.c b/drivers/gpu/drm/apple/iomfb.c index 0da3de1aa27e78..3c930209382685 100644 --- a/drivers/gpu/drm/apple/iomfb.c +++ b/drivers/gpu/drm/apple/iomfb.c @@ -983,6 +983,16 @@ static void dcpep_cb_hotplug(struct apple_dcp *dcp, u64 *connected) { struct apple_connector *connector = dcp->connector; + /* DCP issues hotplug_gated callbacks after SetPowerState() calls on + * devices with display (macbooks, imacs). This must not result in + * connector state changes on DRM side. Some applications won't enable + * a CRTC with a connector in disconnected state. Weston after DPMS off + * is one example. dcp_is_main_display() returns true on devices with + * integrated display. Ignore the hotplug_gated() callbacks there. + */ + if (dcp->main_display) + return; + /* Hotplug invalidates mode. DRM doesn't always handle this. */ if (!(*connected)) { dcp->valid_mode = false; From c21f41b07c807f46ab6f9d6d77cfa860999c63cf Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Wed, 5 Oct 2022 22:59:54 +0200 Subject: [PATCH 1014/3902] drm: apple: Replace atomic refcount with kref fixup! drm/apple: Add t600x support Signed-off-by: Janne Grunau --- drivers/gpu/drm/apple/iomfb.c | 61 ++++++++++++++++++++++------------- 1 file changed, 39 insertions(+), 22 deletions(-) diff --git a/drivers/gpu/drm/apple/iomfb.c b/drivers/gpu/drm/apple/iomfb.c index 3c930209382685..68ca1c2aa8354e 100644 --- a/drivers/gpu/drm/apple/iomfb.c +++ b/drivers/gpu/drm/apple/iomfb.c @@ -9,6 +9,7 @@ #include #include #include +#include #include #include #include @@ -32,10 +33,18 @@ #define REG_DOORBELL_BIT (2) struct dcp_wait_cookie { + struct kref refcount; struct completion done; - atomic_t refcount; }; +static void release_wait_cookie(struct kref *ref) +{ + struct dcp_wait_cookie *cookie; + cookie = container_of(ref, struct dcp_wait_cookie, refcount); + + kfree(cookie); +} + static int dcp_tx_offset(enum dcp_context_id id) { switch (id) { @@ -755,11 +764,19 @@ static u64 dcpep_cb_get_time(struct apple_dcp *dcp) } struct dcp_swap_cookie { + struct kref refcount; struct completion done; - atomic_t refcount; u32 swap_id; }; +static void release_swap_cookie(struct kref *ref) +{ + struct dcp_swap_cookie *cookie; + cookie = container_of(ref, struct dcp_swap_cookie, refcount); + + kfree(cookie); +} + static void dcp_swap_cleared(struct apple_dcp *dcp, void *data, void *cookie) { struct dcp_swap_submit_resp *resp = data; @@ -768,8 +785,7 @@ static void dcp_swap_cleared(struct apple_dcp *dcp, void *data, void *cookie) if (cookie) { struct dcp_swap_cookie *info = cookie; complete(&info->done); - if (atomic_dec_and_test(&info->refcount)) - kfree(info); + kref_put(&info->refcount, release_swap_cookie); } if (resp->ret) { @@ -811,8 +827,7 @@ static void dcp_on_final(struct apple_dcp *dcp, void *out, void *cookie) if (wait) { complete(&wait->done); - if (atomic_dec_and_test(&wait->refcount)) - kfree(wait); + kref_put(&wait->refcount, release_wait_cookie); } } @@ -844,7 +859,9 @@ void dcp_poweron(struct platform_device *pdev) return; init_completion(&cookie->done); - atomic_set(&cookie->refcount, 2); + kref_init(&cookie->refcount); + /* increase refcount to ensure the receiver has a reference */ + kref_get(&cookie->refcount); if (dcp->main_display) { handle = 0; @@ -862,8 +879,7 @@ void dcp_poweron(struct platform_device *pdev) if (ret == 0) dev_warn(dcp->dev, "wait for power timed out"); - if (atomic_dec_and_test(&cookie->refcount)) - kfree(cookie); + kref_put(&cookie->refcount, release_wait_cookie);; } EXPORT_SYMBOL(dcp_poweron); @@ -874,8 +890,7 @@ static void complete_set_powerstate(struct apple_dcp *dcp, void *out, if (wait) { complete(&wait->done); - if (atomic_dec_and_test(&wait->refcount)) - kfree(wait); + kref_put(&wait->refcount, release_wait_cookie); } } @@ -896,7 +911,9 @@ void dcp_poweroff(struct platform_device *pdev) if (!cookie) return; init_completion(&cookie->done); - atomic_set(&cookie->refcount, 2); + kref_init(&cookie->refcount); + /* increase refcount to ensure the receiver has a reference */ + kref_get(&cookie->refcount); // clear surfaces memset(&dcp->swap, 0, sizeof(dcp->swap)); @@ -912,8 +929,7 @@ void dcp_poweroff(struct platform_device *pdev) ret = wait_for_completion_timeout(&cookie->done, msecs_to_jiffies(50)); swap_id = cookie->swap_id; - if (atomic_dec_and_test(&cookie->refcount)) - kfree(cookie); + kref_put(&cookie->refcount, release_swap_cookie); if (ret <= 0) { dcp->crashed = true; return; @@ -925,7 +941,9 @@ void dcp_poweroff(struct platform_device *pdev) if (!poff_cookie) return; init_completion(&poff_cookie->done); - atomic_set(&poff_cookie->refcount, 2); + kref_init(&poff_cookie->refcount); + /* increase refcount to ensure the receiver has a reference */ + kref_get(&poff_cookie->refcount); dcp_set_power_state(dcp, false, &power_req, complete_set_powerstate, poff_cookie); @@ -939,8 +957,7 @@ void dcp_poweroff(struct platform_device *pdev) "setPowerState(0) finished with %d ms to spare", jiffies_to_msecs(ret)); - if (atomic_dec_and_test(&poff_cookie->refcount)) - kfree(poff_cookie); + kref_put(&poff_cookie->refcount, release_wait_cookie); dev_dbg(dcp->dev, "%s: setPowerState(0) done", __func__); } EXPORT_SYMBOL(dcp_poweroff); @@ -1379,8 +1396,7 @@ static void complete_set_digital_out_mode(struct apple_dcp *dcp, void *data, if (wait) { complete(&wait->done); - if (atomic_dec_and_test(&wait->refcount)) - kfree(wait); + kref_put(&wait->refcount, release_wait_cookie); } } @@ -1509,7 +1525,9 @@ void dcp_flush(struct drm_crtc *crtc, struct drm_atomic_state *state) } init_completion(&cookie->done); - atomic_set(&cookie->refcount, 2); + kref_init(&cookie->refcount); + /* increase refcount to ensure the receiver has a reference */ + kref_get(&cookie->refcount); dcp_set_digital_out_mode(dcp, false, &dcp->mode, complete_set_digital_out_mode, cookie); @@ -1518,8 +1536,7 @@ void dcp_flush(struct drm_crtc *crtc, struct drm_atomic_state *state) ret = wait_for_completion_timeout(&cookie->done, msecs_to_jiffies(500)); - if (atomic_dec_and_test(&cookie->refcount)) - kfree(cookie); + kref_put(&cookie->refcount, release_wait_cookie); if (ret == 0) { dev_dbg(dcp->dev, "set_digital_out_mode 200 ms"); From 279e31c04369c1ed0007857ab653e0bfc17fb937 Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Sat, 8 Oct 2022 18:30:41 +0200 Subject: [PATCH 1015/3902] drm: apple: Start using tracepoints Signed-off-by: Janne Grunau --- drivers/gpu/drm/apple/Makefile | 5 + drivers/gpu/drm/apple/dcp-internal.h | 11 ++ drivers/gpu/drm/apple/dcp.c | 10 ++ drivers/gpu/drm/apple/dcp.h | 2 +- drivers/gpu/drm/apple/iomfb.c | 32 +++--- drivers/gpu/drm/apple/iomfb.h | 3 - drivers/gpu/drm/apple/trace.c | 9 ++ drivers/gpu/drm/apple/trace.h | 166 +++++++++++++++++++++++++++ 8 files changed, 217 insertions(+), 21 deletions(-) create mode 100644 drivers/gpu/drm/apple/trace.c create mode 100644 drivers/gpu/drm/apple/trace.h diff --git a/drivers/gpu/drm/apple/Makefile b/drivers/gpu/drm/apple/Makefile index e6c517bca2b07d..082c8c58bb87fe 100644 --- a/drivers/gpu/drm/apple/Makefile +++ b/drivers/gpu/drm/apple/Makefile @@ -1,7 +1,12 @@ # SPDX-License-Identifier: GPL-2.0-only OR MIT +CFLAGS_trace.o = -I$(src) + appledrm-y := apple_drv.o + apple_dcp-y := dcp.o iomfb.o parser.o +apple_dcp-$(CONFIG_TRACING) += trace.o + apple_piodma-y := dummy-piodma.o obj-$(CONFIG_DRM_APPLE) += appledrm.o diff --git a/drivers/gpu/drm/apple/dcp-internal.h b/drivers/gpu/drm/apple/dcp-internal.h index 648d543b9cd91a..fd7c3aac603e35 100644 --- a/drivers/gpu/drm/apple/dcp-internal.h +++ b/drivers/gpu/drm/apple/dcp-internal.h @@ -12,6 +12,17 @@ struct apple_dcp; +enum { + SYSTEM_ENDPOINT = 0x20, + TEST_ENDPOINT = 0x21, + DCP_EXPERT_ENDPOINT = 0x22, + DISP0_ENDPOINT = 0x23, + DPTX_ENDPOINT = 0x2a, + HDCP_ENDPOINT = 0x2b, + REMOTE_ALLOC_ENDPOINT = 0x2d, + IOMFB_ENDPOINT = 0x37, +}; + /* Temporary backing for a chunked transfer via setDCPAVPropStart/Chunk/End */ struct dcp_chunks { size_t length; diff --git a/drivers/gpu/drm/apple/dcp.c b/drivers/gpu/drm/apple/dcp.c index 60bbedcca8f589..645df6d1b76912 100644 --- a/drivers/gpu/drm/apple/dcp.c +++ b/drivers/gpu/drm/apple/dcp.c @@ -22,6 +22,7 @@ #include "dcp.h" #include "dcp-internal.h" #include "parser.h" +#include "trace.h" #define APPLE_DCP_COPROC_CPU_CONTROL 0x44 #define APPLE_DCP_COPROC_CPU_CONTROL_RUN BIT(4) @@ -90,6 +91,8 @@ static void dcp_recv_msg(void *cookie, u8 endpoint, u64 message) { struct apple_dcp *dcp = cookie; + trace_dcp_recv_msg(dcp, endpoint, message); + switch (endpoint) { case IOMFB_ENDPOINT: return iomfb_recv_msg(dcp, message); @@ -167,6 +170,13 @@ static struct apple_rtkit_ops rtkit_ops = { .shmem_destroy = dcp_rtk_shmem_destroy, }; +void dcp_send_message(struct apple_dcp *dcp, u8 endpoint, u64 message) +{ + trace_dcp_send_msg(dcp, endpoint, message); + apple_rtkit_send_message(dcp->rtk, endpoint, message, NULL, + false); +} + void dcp_link(struct platform_device *pdev, struct apple_crtc *crtc, struct apple_connector *connector) { diff --git a/drivers/gpu/drm/apple/dcp.h b/drivers/gpu/drm/apple/dcp.h index 18d71afaf6b72e..047c1b3a160457 100644 --- a/drivers/gpu/drm/apple/dcp.h +++ b/drivers/gpu/drm/apple/dcp.h @@ -47,7 +47,7 @@ int dcp_get_modes(struct drm_connector *connector); int dcp_mode_valid(struct drm_connector *connector, struct drm_display_mode *mode); void dcp_set_dimensions(struct apple_dcp *dcp); - +void dcp_send_message(struct apple_dcp *dcp, u8 endpoint, u64 message); int iomfb_start_rtkit(struct apple_dcp *dcp); void iomfb_shutdown(struct apple_dcp *dcp); diff --git a/drivers/gpu/drm/apple/iomfb.c b/drivers/gpu/drm/apple/iomfb.c index 68ca1c2aa8354e..5bab8825df5a60 100644 --- a/drivers/gpu/drm/apple/iomfb.c +++ b/drivers/gpu/drm/apple/iomfb.c @@ -25,6 +25,7 @@ #include "dcp-internal.h" #include "iomfb.h" #include "parser.h" +#include "trace.h" /* Register defines used in bandwidth setup structure */ #define REG_SCRATCH (0x14) @@ -228,17 +229,15 @@ static void dcp_push(struct apple_dcp *dcp, bool oob, enum dcpep_method method, if (in_len > 0) memcpy(out_data, data, in_len); - dev_dbg(dcp->dev, "---> %s: context %u, offset %u, depth %u\n", - dcp_methods[method].name, context, offset, depth); + trace_iomfb_push(dcp, &dcp_methods[method], context, offset, depth); ch->callbacks[depth] = cb; ch->cookies[depth] = cookie; ch->output[depth] = out + sizeof(header) + in_len; ch->end[depth] = offset + ALIGN(data_len, DCP_PACKET_ALIGNMENT); - apple_rtkit_send_message(dcp->rtk, IOMFB_ENDPOINT, - dcpep_msg(context, data_len, offset), NULL, - false); + dcp_send_message(dcp, IOMFB_ENDPOINT, + dcpep_msg(context, data_len, offset)); } #define DCP_THUNK_VOID(func, handle) \ @@ -333,8 +332,8 @@ static void dcp_ack(struct apple_dcp *dcp, enum dcp_context_id context) struct dcp_cb_channel *ch = dcp_get_cb_channel(dcp, context); dcp_pop_depth(&ch->depth); - apple_rtkit_send_message(dcp->rtk, IOMFB_ENDPOINT, dcpep_ack(context), - NULL, false); + dcp_send_message(dcp, IOMFB_ENDPOINT, + dcpep_ack(context)); } /* DCP callback handlers */ @@ -361,8 +360,7 @@ static u32 dcpep_cb_zero(struct apple_dcp *dcp) static void dcpep_cb_swap_complete(struct apple_dcp *dcp, struct dc_swap_complete_resp *resp) { - dev_dbg(dcp->dev, "swap complete for swap_id: %u vblank: %u", - resp->swap_id, dcp->ignore_swap_complete); + trace_iomfb_swap_complete(dcp, resp->swap_id); if (!dcp->ignore_swap_complete) dcp_drm_crtc_vblank(dcp->crtc); @@ -725,7 +723,7 @@ static void boot_1_5(struct apple_dcp *dcp, void *out, void *cookie) /* Use special function signature to defer the ACK */ static bool dcpep_cb_boot_1(struct apple_dcp *dcp, int tag, void *out, void *in) { - dev_dbg(dcp->dev, "Callback D%03d %s\n", tag, __func__); + trace_iomfb_callback(dcp, tag, __func__); dcp_set_create_dfb(dcp, false, boot_1_5, NULL); return false; } @@ -1029,7 +1027,7 @@ static void dcpep_cb_swap_complete_intent_gated(struct apple_dcp *dcp, struct dcp_swap_complete_intent_gated *info) { - dev_dbg(dcp->dev, "swap_id:%u width:%u height:%u", info->swap_id, + trace_iomfb_swap_complete_intent_gated(dcp, info->swap_id, info->width, info->height); } @@ -1043,7 +1041,7 @@ dcpep_cb_swap_complete_intent_gated(struct apple_dcp *dcp, #define TRAMPOLINE_VOID(func, handler) \ static bool func(struct apple_dcp *dcp, int tag, void *out, void *in) \ { \ - dev_dbg(dcp->dev, "Callback D%03d %s\n", tag, #handler); \ + trace_iomfb_callback(dcp, tag, #handler); \ handler(dcp); \ return true; \ } @@ -1055,7 +1053,7 @@ dcpep_cb_swap_complete_intent_gated(struct apple_dcp *dcp, { \ callback_##handler cb = handler; \ \ - dev_dbg(dcp->dev, "Callback D%03d %s\n", tag, #handler); \ + trace_iomfb_callback(dcp, tag, #handler); \ cb(dcp, in); \ return true; \ } @@ -1068,7 +1066,7 @@ dcpep_cb_swap_complete_intent_gated(struct apple_dcp *dcp, T_out *typed_out = out; \ callback_##handler cb = handler; \ \ - dev_dbg(dcp->dev, "Callback D%03d %s\n", tag, #handler); \ + trace_iomfb_callback(dcp, tag, #handler); \ *typed_out = cb(dcp, in); \ return true; \ } @@ -1078,7 +1076,7 @@ dcpep_cb_swap_complete_intent_gated(struct apple_dcp *dcp, { \ T_out *typed_out = out; \ \ - dev_dbg(dcp->dev, "Callback D%03d %s\n", tag, #handler); \ + trace_iomfb_callback(dcp, tag, #handler); \ *typed_out = handler(dcp); \ return true; \ } @@ -1290,6 +1288,7 @@ static void dcp_swap_started(struct apple_dcp *dcp, void *data, void *cookie) dcp->swap.swap.swap_id = resp->swap_id; + trace_iomfb_swap_submit(dcp, resp->swap_id); dcp_swap_submit(dcp, false, &dcp->swap, dcp_swapped, NULL); } @@ -1633,8 +1632,7 @@ int iomfb_start_rtkit(struct apple_dcp *dcp) GFP_KERNEL); shmem_iova |= dcp->asc_dram_mask; - apple_rtkit_send_message(dcp->rtk, IOMFB_ENDPOINT, - dcpep_set_shmem(shmem_iova), NULL, false); + dcp_send_message(dcp, IOMFB_ENDPOINT, dcpep_set_shmem(shmem_iova)); return 0; } diff --git a/drivers/gpu/drm/apple/iomfb.h b/drivers/gpu/drm/apple/iomfb.h index 96dfd170b8e330..5b1f4ba789bccf 100644 --- a/drivers/gpu/drm/apple/iomfb.h +++ b/drivers/gpu/drm/apple/iomfb.h @@ -6,9 +6,6 @@ #include -/* Endpoint for general DCP traffic (dcpep in macOS) */ -#define IOMFB_ENDPOINT 0x37 - /* Fixed size of shared memory between DCP and AP */ #define DCP_SHMEM_SIZE 0x100000 diff --git a/drivers/gpu/drm/apple/trace.c b/drivers/gpu/drm/apple/trace.c new file mode 100644 index 00000000000000..6f40d5a583df01 --- /dev/null +++ b/drivers/gpu/drm/apple/trace.c @@ -0,0 +1,9 @@ +// SPDX-License-Identifier: GPL-2.0+ OR MIT +/* + * Tracepoints for Apple DCP driver + * + * Copyright (C) The Asahi Linux Contributors + */ + +#define CREATE_TRACE_POINTS +#include "trace.h" diff --git a/drivers/gpu/drm/apple/trace.h b/drivers/gpu/drm/apple/trace.h new file mode 100644 index 00000000000000..25a23c1586e259 --- /dev/null +++ b/drivers/gpu/drm/apple/trace.h @@ -0,0 +1,166 @@ +// SPDX-License-Identifier: GPL-2.0-only OR MIT +/* Copyright (C) The Asahi Linux Contributors */ + +#undef TRACE_SYSTEM +#define TRACE_SYSTEM dcp + +#if !defined(_TRACE_DCP_H) || defined(TRACE_HEADER_MULTI_READ) +#define _TRACE_DCP_H + +#include "dcp-internal.h" + +#include +#include +#include + +#define show_dcp_endpoint(ep) \ + __print_symbolic(ep, { SYSTEM_ENDPOINT, "system" }, \ + { TEST_ENDPOINT, "test" }, \ + { DCP_EXPERT_ENDPOINT, "dcpexpert" }, \ + { DISP0_ENDPOINT, "disp0" }, \ + { DPTX_ENDPOINT, "dptxport" }, \ + { HDCP_ENDPOINT, "hdcp" }, \ + { REMOTE_ALLOC_ENDPOINT, "remotealloc" }, \ + { IOMFB_ENDPOINT, "iomfb" }) + +TRACE_EVENT(dcp_recv_msg, + TP_PROTO(struct apple_dcp *dcp, u8 endpoint, u64 message), + TP_ARGS(dcp, endpoint, message), + + TP_STRUCT__entry(__string(devname, dev_name(dcp->dev)) + __field(u8, endpoint) + __field(u64, message)), + + TP_fast_assign(__assign_str(devname); + __entry->endpoint = endpoint; + __entry->message = message;), + + TP_printk("%s: endpoint 0x%x (%s): received message 0x%016llx", + __get_str(devname), __entry->endpoint, + show_dcp_endpoint(__entry->endpoint), __entry->message)); + +TRACE_EVENT(dcp_send_msg, + TP_PROTO(struct apple_dcp *dcp, u8 endpoint, u64 message), + TP_ARGS(dcp, endpoint, message), + + TP_STRUCT__entry(__string(devname, dev_name(dcp->dev)) + __field(u8, endpoint) + __field(u64, message)), + + TP_fast_assign(__assign_str(devname); + __entry->endpoint = endpoint; + __entry->message = message;), + + TP_printk("%s: endpoint 0x%x (%s): will send message 0x%016llx", + __get_str(devname), __entry->endpoint, + show_dcp_endpoint(__entry->endpoint), __entry->message)); + +TRACE_EVENT(iomfb_callback, + TP_PROTO(struct apple_dcp *dcp, int tag, const char *name), + TP_ARGS(dcp, tag, name), + + TP_STRUCT__entry( + __string(devname, dev_name(dcp->dev)) + __field(int, tag) + __field(const char *, name) + ), + + TP_fast_assign( + __assign_str(devname); + __entry->tag = tag; __entry->name = name; + ), + + TP_printk("%s: Callback D%03d %s", __get_str(devname), __entry->tag, + __entry->name)); + +TRACE_EVENT(iomfb_push, + TP_PROTO(struct apple_dcp *dcp, + const struct dcp_method_entry *method, int context, + int offset, int depth), + TP_ARGS(dcp, method, context, offset, depth), + + TP_STRUCT__entry( + __string(devname, dev_name(dcp->dev)) + __string(name, method->name) + __field(int, context) + __field(int, offset) + __field(int, depth)), + + TP_fast_assign( + __assign_str(devname); + __assign_str(name); + __entry->context = context; __entry->offset = offset; + __entry->depth = depth; + ), + + TP_printk("%s: Method %s: context %u, offset %u, depth %u", + __get_str(devname), __get_str(name), __entry->context, + __entry->offset, __entry->depth)); + +TRACE_EVENT(iomfb_swap_submit, + TP_PROTO(struct apple_dcp *dcp, u32 swap_id), + TP_ARGS(dcp, swap_id), + TP_STRUCT__entry( + __field(u64, dcp) + __field(u32, swap_id) + ), + TP_fast_assign( + __entry->dcp = (u64)dcp; + __entry->swap_id = swap_id; + ), + TP_printk("dcp=%llx, swap_id=%d", + __entry->dcp, + __entry->swap_id) +); + +TRACE_EVENT(iomfb_swap_complete, + TP_PROTO(struct apple_dcp *dcp, u32 swap_id), + TP_ARGS(dcp, swap_id), + TP_STRUCT__entry( + __field(u64, dcp) + __field(u32, swap_id) + ), + TP_fast_assign( + __entry->dcp = (u64)dcp; + __entry->swap_id = swap_id; + ), + TP_printk("dcp=%llx, swap_id=%d", + __entry->dcp, + __entry->swap_id + ) +); + +TRACE_EVENT(iomfb_swap_complete_intent_gated, + TP_PROTO(struct apple_dcp *dcp, u32 swap_id, u32 width, u32 height), + TP_ARGS(dcp, swap_id, width, height), + TP_STRUCT__entry( + __field(u64, dcp) + __field(u32, swap_id) + __field(u32, width) + __field(u32, height) + ), + TP_fast_assign( + __entry->dcp = (u64)dcp; + __entry->swap_id = swap_id; + __entry->height = height; + __entry->width = width; + ), + TP_printk("dcp=%llx, swap_id=%u %ux%u", + __entry->dcp, + __entry->swap_id, + __entry->width, + __entry->height + ) +); + +#endif /* _TRACE_DCP_H */ + +/* This part must be outside protection */ + +#undef TRACE_INCLUDE_FILE +#define TRACE_INCLUDE_FILE trace + +#undef TRACE_INCLUDE_PATH +#define TRACE_INCLUDE_PATH . + +#include From 3757d0e744ab3743f745b35301340c75cfe71e88 Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Tue, 11 Oct 2022 08:34:03 +0200 Subject: [PATCH 1016/3902] drm: apple: Unbreak multiple DCP plane <-> crtc matching Still untested so this changes the status from definitively broken to probably broken. Signed-off-by: Janne Grunau --- drivers/gpu/drm/apple/apple_drv.c | 11 +++++++---- drivers/gpu/drm/apple/iomfb.c | 12 ++++++++++-- 2 files changed, 17 insertions(+), 6 deletions(-) diff --git a/drivers/gpu/drm/apple/apple_drv.c b/drivers/gpu/drm/apple/apple_drv.c index 5bb5b48e89c595..375631cc7f687e 100644 --- a/drivers/gpu/drm/apple/apple_drv.c +++ b/drivers/gpu/drm/apple/apple_drv.c @@ -136,6 +136,7 @@ u64 apple_format_modifiers[] = { }; static struct drm_plane *apple_plane_init(struct drm_device *dev, + unsigned long possible_crtcs, enum drm_plane_type type) { int ret; @@ -143,7 +144,8 @@ static struct drm_plane *apple_plane_init(struct drm_device *dev, plane = devm_kzalloc(dev->dev, sizeof(*plane), GFP_KERNEL); - ret = drm_universal_plane_init(dev, plane, 0x1, &apple_plane_funcs, + ret = drm_universal_plane_init(dev, plane, possible_crtcs, + &apple_plane_funcs, dcp_formats, ARRAY_SIZE(dcp_formats), apple_format_modifiers, type, NULL); if (ret) @@ -295,7 +297,8 @@ static const struct drm_crtc_helper_funcs apple_crtc_helper_funcs = { static int apple_probe_per_dcp(struct device *dev, struct drm_device *drm, - struct platform_device *dcp) + struct platform_device *dcp, + int num) { struct apple_crtc *crtc; struct apple_connector *connector; @@ -304,7 +307,7 @@ static int apple_probe_per_dcp(struct device *dev, int con_type; int ret; - primary = apple_plane_init(drm, DRM_PLANE_TYPE_PRIMARY); + primary = apple_plane_init(drm, 1U << num, DRM_PLANE_TYPE_PRIMARY); if (IS_ERR(primary)) return PTR_ERR(primary); @@ -421,7 +424,7 @@ static int apple_platform_probe(struct platform_device *pdev) apple->drm.mode_config.helper_private = &apple_mode_config_helpers; for (i = 0; i < nr_dcp; ++i) { - ret = apple_probe_per_dcp(&pdev->dev, &apple->drm, dcp[i]); + ret = apple_probe_per_dcp(&pdev->dev, &apple->drm, dcp[i], i); if (ret) goto err_unload; diff --git a/drivers/gpu/drm/apple/iomfb.c b/drivers/gpu/drm/apple/iomfb.c index 5bab8825df5a60..d4b3dc8d3e6dd5 100644 --- a/drivers/gpu/drm/apple/iomfb.c +++ b/drivers/gpu/drm/apple/iomfb.c @@ -1407,7 +1407,7 @@ void dcp_flush(struct drm_crtc *crtc, struct drm_atomic_state *state) struct drm_plane_state *new_state, *old_state; struct drm_crtc_state *crtc_state; struct dcp_swap_submit_req *req = &dcp->swap; - int l; + int plane_idx, l; int has_surface = 0; bool modeset; dev_dbg(dcp->dev, "%s", __func__); @@ -1431,10 +1431,15 @@ void dcp_flush(struct drm_crtc *crtc, struct drm_atomic_state *state) for (l = 0; l < SWAP_SURFACES; l++) req->surf_null[l] = true; - for_each_oldnew_plane_in_state(state, plane, old_state, new_state, l) { + l = 0; + for_each_oldnew_plane_in_state(state, plane, old_state, new_state, plane_idx) { struct drm_framebuffer *fb = new_state->fb; struct drm_rect src_rect; + /* skip planes not for this crtc */ + if (old_state->crtc != crtc && new_state->crtc != crtc) + continue; + WARN_ON(l >= SWAP_SURFACES); req->swap.swap_enabled |= BIT(l); @@ -1463,6 +1468,7 @@ void dcp_flush(struct drm_crtc *crtc, struct drm_atomic_state *state) if (old_state->fb) req->swap.swap_enabled |= DCP_REMOVE_LAYERS; + l += 1; continue; } req->surf_null[l] = false; @@ -1492,6 +1498,8 @@ void dcp_flush(struct drm_crtc *crtc, struct drm_atomic_state *state) .has_comp = 1, .has_planes = 1, }; + + l += 1; } /* These fields should be set together */ From 758485e237bb6786bebb3c813510953cdc7d1940 Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Tue, 11 Oct 2022 08:38:32 +0200 Subject: [PATCH 1017/3902] drm: apple: Add support for DRM_FORMAT_XRGB2101010 iboot uses DRM_FORMAT_XRGB2101010 at boot announce preference by listing it as first format. Disabled for now since it results in oversaturated colors. Might be fixed by using "IOMFB::UPPipe2::set_matrix()". Signed-off-by: Janne Grunau --- drivers/gpu/drm/apple/apple_drv.c | 2 ++ drivers/gpu/drm/apple/iomfb.c | 23 ++++++++++++++++++++++- 2 files changed, 24 insertions(+), 1 deletion(-) diff --git a/drivers/gpu/drm/apple/apple_drv.c b/drivers/gpu/drm/apple/apple_drv.c index 375631cc7f687e..25aee043e1b412 100644 --- a/drivers/gpu/drm/apple/apple_drv.c +++ b/drivers/gpu/drm/apple/apple_drv.c @@ -124,6 +124,8 @@ static const struct drm_plane_funcs apple_plane_funcs = { * advertise formats without alpha. */ static const u32 dcp_formats[] = { + // DRM_FORMAT_XRGB2101010, + // DRM_FORMAT_ARGB2101010, DRM_FORMAT_XRGB8888, DRM_FORMAT_ARGB8888, DRM_FORMAT_XBGR8888, diff --git a/drivers/gpu/drm/apple/iomfb.c b/drivers/gpu/drm/apple/iomfb.c index d4b3dc8d3e6dd5..56622c9e463173 100644 --- a/drivers/gpu/drm/apple/iomfb.c +++ b/drivers/gpu/drm/apple/iomfb.c @@ -1315,12 +1315,33 @@ static u32 drm_format_to_dcp(u32 drm) case DRM_FORMAT_XBGR8888: case DRM_FORMAT_ABGR8888: return fourcc_code('A', 'B', 'G', 'R'); + + case DRM_FORMAT_ARGB2101010: + case DRM_FORMAT_XRGB2101010: + return fourcc_code('r', '0', '3', 'w'); } pr_warn("DRM format %X not supported in DCP\n", drm); return 0; } +static u8 drm_format_to_colorspace(u32 drm) +{ + switch (drm) { + case DRM_FORMAT_XRGB8888: + case DRM_FORMAT_ARGB8888: + case DRM_FORMAT_XBGR8888: + case DRM_FORMAT_ABGR8888: + return 1; + + case DRM_FORMAT_ARGB2101010: + case DRM_FORMAT_XRGB2101010: + return 2; + } + + return 1; +} + int dcp_get_modes(struct drm_connector *connector) { struct apple_connector *apple_connector = to_apple_connector(connector); @@ -1484,7 +1505,7 @@ void dcp_flush(struct drm_crtc *crtc, struct drm_atomic_state *state) req->surf[l] = (struct dcp_surface){ .format = drm_format_to_dcp(fb->format->format), .xfer_func = 13, - .colorspace = 1, + .colorspace = drm_format_to_colorspace(fb->format->format), .stride = fb->pitches[0], .width = fb->width, .height = fb->height, From 0f27ba328875947a513fb45120cbe142297b4120 Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Tue, 11 Oct 2022 08:40:46 +0200 Subject: [PATCH 1018/3902] drm: apple: Add apple_drm_gem_dumb_create() DCP needs a 64-byte aligned pitch, override drm_gem_dma_dumb_create() to align the pitch as necessary and use drm_gem_dma_dumb_create_internal() to allocate the GEM object. Signed-off-by: Janne Grunau --- drivers/gpu/drm/apple/apple_drv.c | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/drivers/gpu/drm/apple/apple_drv.c b/drivers/gpu/drm/apple/apple_drv.c index 25aee043e1b412..13bc729c9582ef 100644 --- a/drivers/gpu/drm/apple/apple_drv.c +++ b/drivers/gpu/drm/apple/apple_drv.c @@ -47,8 +47,18 @@ struct apple_drm_private { DEFINE_DRM_GEM_DMA_FOPS(apple_fops); +static int apple_drm_gem_dumb_create(struct drm_file *file_priv, + struct drm_device *drm, + struct drm_mode_create_dumb *args) +{ + args->pitch = ALIGN(DIV_ROUND_UP(args->width * args->bpp, 8), 64); + args->size = args->pitch * args->height; + + return drm_gem_dma_dumb_create_internal(file_priv, drm, args); +} + static const struct drm_driver apple_drm_driver = { - DRM_GEM_DMA_DRIVER_OPS, + DRM_GEM_DMA_DRIVER_OPS_WITH_DUMB_CREATE(apple_drm_gem_dumb_create), DRM_FBDEV_DMA_DRIVER_OPS, .name = DRIVER_NAME, .desc = DRIVER_DESC, From 10864424d16a3cadd2949feace99a616ecd7ce01 Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Wed, 12 Oct 2022 23:12:07 +0200 Subject: [PATCH 1019/3902] drm: apple: Reject modes without valid color mode Signed-off-by: Janne Grunau --- drivers/gpu/drm/apple/parser.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/drivers/gpu/drm/apple/parser.c b/drivers/gpu/drm/apple/parser.c index f0cd38c2a81048..bb7d57f272ddb9 100644 --- a/drivers/gpu/drm/apple/parser.c +++ b/drivers/gpu/drm/apple/parser.c @@ -339,6 +339,12 @@ static int parse_mode(struct dcp_parse_ctx *handle, return ret; } + /* + * Reject modes without valid color mode. + */ + if (best_color_mode < 0) + return -EINVAL; + /* * We need to skip virtual modes. In some cases, virtual modes are "too * big" for the monitor and can cause breakage. It is unclear why the From da35cdf3fd4e6a9d27dcb545e72f573ddcb672bb Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Wed, 12 Oct 2022 23:25:49 +0200 Subject: [PATCH 1020/3902] drm: apple: Convert 2 non-assert WARN()s to dev_err() Signed-off-by: Janne Grunau --- drivers/gpu/drm/apple/iomfb.c | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/drivers/gpu/drm/apple/iomfb.c b/drivers/gpu/drm/apple/iomfb.c index 56622c9e463173..3893c0f86ef296 100644 --- a/drivers/gpu/drm/apple/iomfb.c +++ b/drivers/gpu/drm/apple/iomfb.c @@ -1437,9 +1437,18 @@ void dcp_flush(struct drm_crtc *crtc, struct drm_atomic_state *state) modeset = drm_atomic_crtc_needs_modeset(crtc_state) || !dcp->valid_mode; - if (WARN(dcp_channel_busy(&dcp->ch_cmd), "unexpected busy channel") || - WARN(!modeset && !dcp->connector->connected, - "can't flush if disconnected")) { + if (dcp_channel_busy(&dcp->ch_cmd)) + { + dev_err(dcp->dev, "unexpected busy command channel"); + /* HACK: issue a delayed vblank event to avoid timeouts in + * drm_atomic_helper_wait_for_vblanks(). + */ + schedule_work(&dcp->vblank_wq); + return; + } + if (!modeset && !dcp->connector->connected) + { + dev_err(dcp->dev, "dcp_flush while disconnected"); /* HACK: issue a delayed vblank event to avoid timeouts in * drm_atomic_helper_wait_for_vblanks(). */ From 7bbb7939602e4d976729c5994c3e956f542be00b Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Thu, 20 Oct 2022 20:42:09 +0200 Subject: [PATCH 1021/3902] drm: apple: Send an disconnected hotplug event on ASC crash Signed-off-by: Janne Grunau --- drivers/gpu/drm/apple/dcp.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/drivers/gpu/drm/apple/dcp.c b/drivers/gpu/drm/apple/dcp.c index 645df6d1b76912..8c37d84d31acca 100644 --- a/drivers/gpu/drm/apple/dcp.c +++ b/drivers/gpu/drm/apple/dcp.c @@ -107,6 +107,10 @@ static void dcp_rtk_crashed(void *cookie, const void *crashlog, size_t crashlog_ dcp->crashed = true; dev_err(dcp->dev, "DCP has crashed"); + if (dcp->connector) { + dcp->connector->connected = 0; + schedule_work(&dcp->connector->hotplug_wq); + } } static int dcp_rtk_shmem_setup(void *cookie, struct apple_rtkit_shmem *bfr) From feb7e9c921f58e8cfa2d1d3e9d2ff4be67fa63ac Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Thu, 20 Oct 2022 23:02:50 +0200 Subject: [PATCH 1022/3902] drm: apple: Add dcp_crtc_atomic_check Signed-off-by: Janne Grunau --- drivers/gpu/drm/apple/apple_drv.c | 1 + drivers/gpu/drm/apple/dcp-internal.h | 2 ++ drivers/gpu/drm/apple/dcp.c | 38 ++++++++++++++++++++++++++++ drivers/gpu/drm/apple/dcp.h | 1 + drivers/gpu/drm/apple/iomfb.c | 9 ------- 5 files changed, 42 insertions(+), 9 deletions(-) diff --git a/drivers/gpu/drm/apple/apple_drv.c b/drivers/gpu/drm/apple/apple_drv.c index 13bc729c9582ef..3db2eae2dd80cd 100644 --- a/drivers/gpu/drm/apple/apple_drv.c +++ b/drivers/gpu/drm/apple/apple_drv.c @@ -302,6 +302,7 @@ static const struct drm_connector_helper_funcs apple_connector_helper_funcs = { static const struct drm_crtc_helper_funcs apple_crtc_helper_funcs = { .atomic_begin = apple_crtc_atomic_begin, + .atomic_check = dcp_crtc_atomic_check, .atomic_flush = dcp_flush, .atomic_enable = apple_crtc_atomic_enable, .atomic_disable = apple_crtc_atomic_disable, diff --git a/drivers/gpu/drm/apple/dcp-internal.h b/drivers/gpu/drm/apple/dcp-internal.h index fd7c3aac603e35..c7a7b9563a156f 100644 --- a/drivers/gpu/drm/apple/dcp-internal.h +++ b/drivers/gpu/drm/apple/dcp-internal.h @@ -10,6 +10,8 @@ #include "iomfb.h" +#define DCP_MAX_PLANES 2 + struct apple_dcp; enum { diff --git a/drivers/gpu/drm/apple/dcp.c b/drivers/gpu/drm/apple/dcp.c index 8c37d84d31acca..39b0ba000a26b0 100644 --- a/drivers/gpu/drm/apple/dcp.c +++ b/drivers/gpu/drm/apple/dcp.c @@ -181,6 +181,44 @@ void dcp_send_message(struct apple_dcp *dcp, u8 endpoint, u64 message) false); } +int dcp_crtc_atomic_check(struct drm_crtc *crtc, struct drm_atomic_state *state) +{ + struct platform_device *pdev = to_apple_crtc(crtc)->dcp; + struct apple_dcp *dcp = platform_get_drvdata(pdev); + struct drm_plane_state *new_state; + struct drm_plane *plane; + struct drm_crtc_state *crtc_state; + int plane_idx, plane_count = 0; + bool needs_modeset; + + if (dcp->crashed) + return -EINVAL; + + crtc_state = drm_atomic_get_new_crtc_state(state, crtc); + + needs_modeset = drm_atomic_crtc_needs_modeset(crtc_state) || !dcp->valid_mode; + if (!needs_modeset && !dcp->connector->connected) { + dev_err(dcp->dev, "crtc_atomic_check: disconnected but no modeset"); + return -EINVAL; + } + + for_each_new_plane_in_state(state, plane, new_state, plane_idx) { + /* skip planes not for this crtc */ + if (new_state->crtc != crtc) + continue; + + plane_count += 1; + } + + if (plane_count > DCP_MAX_PLANES) { + dev_err(dcp->dev, "crtc_atomic_check: Blend supports only 2 layers!"); + return -EINVAL; + } + + return 0; +} +EXPORT_SYMBOL_GPL(dcp_crtc_atomic_check); + void dcp_link(struct platform_device *pdev, struct apple_crtc *crtc, struct apple_connector *connector) { diff --git a/drivers/gpu/drm/apple/dcp.h b/drivers/gpu/drm/apple/dcp.h index 047c1b3a160457..72ac1315372e5c 100644 --- a/drivers/gpu/drm/apple/dcp.h +++ b/drivers/gpu/drm/apple/dcp.h @@ -37,6 +37,7 @@ struct apple_connector { void dcp_poweroff(struct platform_device *pdev); void dcp_poweron(struct platform_device *pdev); +int dcp_crtc_atomic_check(struct drm_crtc *crtc, struct drm_atomic_state *state); void dcp_link(struct platform_device *pdev, struct apple_crtc *apple, struct apple_connector *connector); void dcp_flush(struct drm_crtc *crtc, struct drm_atomic_state *state); diff --git a/drivers/gpu/drm/apple/iomfb.c b/drivers/gpu/drm/apple/iomfb.c index 3893c0f86ef296..da6d34b6d7d146 100644 --- a/drivers/gpu/drm/apple/iomfb.c +++ b/drivers/gpu/drm/apple/iomfb.c @@ -1446,15 +1446,6 @@ void dcp_flush(struct drm_crtc *crtc, struct drm_atomic_state *state) schedule_work(&dcp->vblank_wq); return; } - if (!modeset && !dcp->connector->connected) - { - dev_err(dcp->dev, "dcp_flush while disconnected"); - /* HACK: issue a delayed vblank event to avoid timeouts in - * drm_atomic_helper_wait_for_vblanks(). - */ - schedule_work(&dcp->vblank_wq); - return; - } /* Reset to defaults */ memset(req, 0, sizeof(*req)); From fc2d8b698478347998128d409f1a3eae12988d9e Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Fri, 21 Oct 2022 22:31:42 +0200 Subject: [PATCH 1023/3902] drm: apple: Fix DCP run time PM Signed-off-by: Janne Grunau --- drivers/gpu/drm/apple/dcp.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/gpu/drm/apple/dcp.c b/drivers/gpu/drm/apple/dcp.c index 39b0ba000a26b0..4dfde9f6c661f5 100644 --- a/drivers/gpu/drm/apple/dcp.c +++ b/drivers/gpu/drm/apple/dcp.c @@ -381,7 +381,7 @@ MODULE_DEVICE_TABLE(of, of_match); */ static int dcp_suspend(struct device *dev) { - dcp_platform_shutdown(to_platform_device(dev)); + dcp_poweroff(to_platform_device(dev)); return 0; } From 27059d15db0d972a5ed828fe36a3066f698d80b3 Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Fri, 21 Oct 2022 22:34:23 +0200 Subject: [PATCH 1024/3902] drm: apple: Fix DCP initialisation Try to avoid races between DRM driver and DCP(ext) probing and initialisation: - do not start RTKit endpoints in dcp probe, iomfb excepts DRM objects to be already initialized on startup - ensure in apple_drv that all DCPs are probe via device links - initialize DRM planes, connectors, encoders - start DCP RTKit endpoints synchronously Signed-off-by: Janne Grunau --- drivers/gpu/drm/apple/apple_drv.c | 26 ++++++++++++++++++-------- drivers/gpu/drm/apple/dcp.c | 24 +++++++++++++----------- drivers/gpu/drm/apple/dcp.h | 1 + 3 files changed, 32 insertions(+), 19 deletions(-) diff --git a/drivers/gpu/drm/apple/apple_drv.c b/drivers/gpu/drm/apple/apple_drv.c index 3db2eae2dd80cd..b6dbfd86f9004f 100644 --- a/drivers/gpu/drm/apple/apple_drv.c +++ b/drivers/gpu/drm/apple/apple_drv.c @@ -371,14 +371,16 @@ static int apple_probe_per_dcp(struct device *dev, static int apple_platform_probe(struct platform_device *pdev) { + struct device *dev = &pdev->dev; struct apple_drm_private *apple; struct platform_device *dcp[MAX_COPROCESSORS]; int ret, nr_dcp, i; for (nr_dcp = 0; nr_dcp < MAX_COPROCESSORS; ++nr_dcp) { struct device_node *np; + struct device_link *dcp_link; - np = of_parse_phandle(pdev->dev.of_node, "apple,coprocessors", + np = of_parse_phandle(dev->of_node, "apple,coprocessors", nr_dcp); if (!np) @@ -389,11 +391,14 @@ static int apple_platform_probe(struct platform_device *pdev) if (!dcp[nr_dcp]) return -ENODEV; - /* DCP needs to be initialized before KMS can come online */ - if (!platform_get_drvdata(dcp[nr_dcp])) - return -EPROBE_DEFER; + dcp_link = device_link_add(dev, &dcp[nr_dcp]->dev, + DL_FLAG_AUTOREMOVE_CONSUMER); + if (!dcp_link) { + dev_err(dev, "Failed to link to DCP %d device", nr_dcp); + return -EINVAL; + } - if (!dcp_is_initialized(dcp[nr_dcp])) + if (dcp_link->supplier->links.status != DL_DEV_DRIVER_BOUND) return -EPROBE_DEFER; } @@ -401,11 +406,11 @@ static int apple_platform_probe(struct platform_device *pdev) if (nr_dcp < 1) return -ENODEV; - ret = dma_set_mask_and_coherent(&pdev->dev, DMA_BIT_MASK(32)); + ret = dma_set_mask_and_coherent(dev, DMA_BIT_MASK(36)); if (ret) return ret; - apple = devm_drm_dev_alloc(&pdev->dev, &apple_drm_driver, + apple = devm_drm_dev_alloc(dev, &apple_drm_driver, struct apple_drm_private, drm); if (IS_ERR(apple)) return PTR_ERR(apple); @@ -437,7 +442,12 @@ static int apple_platform_probe(struct platform_device *pdev) apple->drm.mode_config.helper_private = &apple_mode_config_helpers; for (i = 0; i < nr_dcp; ++i) { - ret = apple_probe_per_dcp(&pdev->dev, &apple->drm, dcp[i], i); + ret = apple_probe_per_dcp(dev, &apple->drm, dcp[i], i); + + if (ret) + goto err_unload; + + ret = dcp_start(dcp[i]); if (ret) goto err_unload; diff --git a/drivers/gpu/drm/apple/dcp.c b/drivers/gpu/drm/apple/dcp.c index 4dfde9f6c661f5..88de7588be8c97 100644 --- a/drivers/gpu/drm/apple/dcp.c +++ b/drivers/gpu/drm/apple/dcp.c @@ -226,14 +226,22 @@ void dcp_link(struct platform_device *pdev, struct apple_crtc *crtc, dcp->crtc = crtc; dcp->connector = connector; +} +EXPORT_SYMBOL_GPL(dcp_link); + +int dcp_start(struct platform_device *pdev) +{ + struct apple_dcp *dcp = platform_get_drvdata(pdev); + int ret; - /* init connector status by modes offered by dcp */ - connector->connected = dcp->nr_modes > 0; + /* start RTKit endpoints */ + ret = iomfb_start_rtkit(dcp); + if (ret) + dev_err(dcp->dev, "Failed to start IOMFB endpoint: %d", ret); - /* Dimensions might already be parsed */ - dcp_set_dimensions(dcp); + return ret; } -EXPORT_SYMBOL_GPL(dcp_link); +EXPORT_SYMBOL(dcp_start); static struct platform_device *dcp_get_dev(struct device *dev, const char *name) { @@ -347,12 +355,6 @@ static int dcp_platform_probe(struct platform_device *pdev) return dev_err_probe(dev, ret, "Failed to boot RTKit: %d", ret); - /* start RTKit endpoints */ - ret = iomfb_start_rtkit(dcp); - if (ret) - return dev_err_probe(dev, ret, - "Failed to start IOMFB endpoint: %d", ret); - return ret; } diff --git a/drivers/gpu/drm/apple/dcp.h b/drivers/gpu/drm/apple/dcp.h index 72ac1315372e5c..60e9bcfa4714e0 100644 --- a/drivers/gpu/drm/apple/dcp.h +++ b/drivers/gpu/drm/apple/dcp.h @@ -40,6 +40,7 @@ void dcp_poweron(struct platform_device *pdev); int dcp_crtc_atomic_check(struct drm_crtc *crtc, struct drm_atomic_state *state); void dcp_link(struct platform_device *pdev, struct apple_crtc *apple, struct apple_connector *connector); +int dcp_start(struct platform_device *pdev); void dcp_flush(struct drm_crtc *crtc, struct drm_atomic_state *state); bool dcp_is_initialized(struct platform_device *pdev); void apple_crtc_vblank(struct apple_crtc *apple); From ba9de50598eb091f11d13fe2ddb946c5501fc9c4 Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Fri, 21 Oct 2022 23:43:57 +0200 Subject: [PATCH 1025/3902] drm: apple: Specify correct number of DCP*s for drm_vblank_init Unbreaks dcpext a little further. fixup! WIP: drm/apple: Add DCP display driver Signed-off-by: Janne Grunau --- drivers/gpu/drm/apple/apple_drv.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/gpu/drm/apple/apple_drv.c b/drivers/gpu/drm/apple/apple_drv.c index b6dbfd86f9004f..1c9867a21678dc 100644 --- a/drivers/gpu/drm/apple/apple_drv.c +++ b/drivers/gpu/drm/apple/apple_drv.c @@ -415,7 +415,7 @@ static int apple_platform_probe(struct platform_device *pdev) if (IS_ERR(apple)) return PTR_ERR(apple); - ret = drm_vblank_init(&apple->drm, 1); + ret = drm_vblank_init(&apple->drm, nr_dcp); if (ret) return ret; From ef70583e879dc70e856ff5fc3c1af5032bcf5839 Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Tue, 25 Oct 2022 08:17:10 +0200 Subject: [PATCH 1026/3902] drm: apple: Remove other framebuffers before DRM setup Allows taking over the device node from simpledrm and appaers to be the common pattern in DRM drivers replacing boot framebuffers. Signed-off-by: Janne Grunau --- drivers/gpu/drm/apple/apple_drv.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/gpu/drm/apple/apple_drv.c b/drivers/gpu/drm/apple/apple_drv.c index 1c9867a21678dc..52c62f2368f30a 100644 --- a/drivers/gpu/drm/apple/apple_drv.c +++ b/drivers/gpu/drm/apple/apple_drv.c @@ -455,6 +455,7 @@ static int apple_platform_probe(struct platform_device *pdev) drm_mode_config_reset(&apple->drm); + // remove before registering our DRM device ret = drm_aperture_remove_framebuffers(false, &apple_drm_driver); if (ret) return ret; From 5e7fab51fbcd6906d50a9786591b0588b28b4335 Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Thu, 27 Oct 2022 02:09:45 +0200 Subject: [PATCH 1027/3902] drm: apple: Support opaque pixel formats Opaque pixel formats such as XRGB8888 or YUV formats require flag in struct dcp_surface to be set to be displayed properly. Fixes display issues with fbcon after the handover from simpledrm on j314c with 16:10 modes (notch hiding). Signed-off-by: Janne Grunau --- drivers/gpu/drm/apple/iomfb.c | 5 +++++ drivers/gpu/drm/apple/iomfb.h | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/drivers/gpu/drm/apple/iomfb.c b/drivers/gpu/drm/apple/iomfb.c index da6d34b6d7d146..2358f17ab509bf 100644 --- a/drivers/gpu/drm/apple/iomfb.c +++ b/drivers/gpu/drm/apple/iomfb.c @@ -1456,6 +1456,7 @@ void dcp_flush(struct drm_crtc *crtc, struct drm_atomic_state *state) for_each_oldnew_plane_in_state(state, plane, old_state, new_state, plane_idx) { struct drm_framebuffer *fb = new_state->fb; struct drm_rect src_rect; + bool opaque = false; /* skip planes not for this crtc */ if (old_state->crtc != crtc && new_state->crtc != crtc) @@ -1495,6 +1496,9 @@ void dcp_flush(struct drm_crtc *crtc, struct drm_atomic_state *state) req->surf_null[l] = false; has_surface = 1; + if (!fb->format->has_alpha || + new_state->plane->type == DRM_PLANE_TYPE_PRIMARY) + opaque = true; drm_rect_fp_to_int(&src_rect, &new_state->src); req->swap.src_rect[l] = drm_to_dcp_rect(&src_rect); @@ -1503,6 +1507,7 @@ void dcp_flush(struct drm_crtc *crtc, struct drm_atomic_state *state) req->surf_iova[l] = drm_fb_dma_get_gem_addr(fb, new_state, 0); req->surf[l] = (struct dcp_surface){ + .opaque = opaque, .format = drm_format_to_dcp(fb->format->format), .xfer_func = 13, .colorspace = drm_format_to_colorspace(fb->format->format), diff --git a/drivers/gpu/drm/apple/iomfb.h b/drivers/gpu/drm/apple/iomfb.h index 5b1f4ba789bccf..f9ead84c21f255 100644 --- a/drivers/gpu/drm/apple/iomfb.h +++ b/drivers/gpu/drm/apple/iomfb.h @@ -142,7 +142,7 @@ struct dcp_component_types { struct dcp_surface { u8 is_tiled; u8 unk_1; - u8 unk_2; + u8 opaque; /** ignore alpha, also required YUV overlays */ u32 plane_cnt; u32 plane_cnt2; u32 format; /* DCP fourcc */ From 0c354c348008437b9557fcd17e6ea62e314de804 Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Tue, 25 Oct 2022 08:24:01 +0200 Subject: [PATCH 1028/3902] drm: apple: Provide notch-less modes If the device tree caries a "apple,notch-height" property subtract it from all modes and render all framebuffers offsetted by it. Signed-off-by: Janne Grunau --- drivers/gpu/drm/apple/dcp-internal.h | 4 ++++ drivers/gpu/drm/apple/dcp.c | 7 +++++++ drivers/gpu/drm/apple/iomfb.c | 6 +++++- drivers/gpu/drm/apple/parser.c | 9 ++++++--- drivers/gpu/drm/apple/parser.h | 2 +- 5 files changed, 23 insertions(+), 5 deletions(-) diff --git a/drivers/gpu/drm/apple/dcp-internal.h b/drivers/gpu/drm/apple/dcp-internal.h index c7a7b9563a156f..6624672109c33e 100644 --- a/drivers/gpu/drm/apple/dcp-internal.h +++ b/drivers/gpu/drm/apple/dcp-internal.h @@ -67,6 +67,8 @@ struct dcp_fb_reference { struct drm_framebuffer *fb; }; +#define MAX_NOTCH_HEIGHT 160 + /* TODO: move IOMFB members to its own struct */ struct apple_dcp { struct device *dev; @@ -134,6 +136,8 @@ struct apple_dcp { /* Attributes of the connected display */ int width_mm, height_mm; + unsigned notch_height; + /* Workqueue for sending vblank events when a dcp swap is not possible */ struct work_struct vblank_wq; diff --git a/drivers/gpu/drm/apple/dcp.c b/drivers/gpu/drm/apple/dcp.c index 88de7588be8c97..68873981e3794c 100644 --- a/drivers/gpu/drm/apple/dcp.c +++ b/drivers/gpu/drm/apple/dcp.c @@ -331,6 +331,13 @@ static int dcp_platform_probe(struct platform_device *pdev) dev_warn(dev, "failed read 'apple,asc-dram-mask': %d\n", ret); dev_dbg(dev, "'apple,asc-dram-mask': 0x%011llx\n", dcp->asc_dram_mask); + ret = of_property_read_u32(dev->of_node, "apple,notch-height", + &dcp->notch_height); + if (dcp->notch_height > MAX_NOTCH_HEIGHT) + dcp->notch_height = MAX_NOTCH_HEIGHT; + if (dcp->notch_height > 0) + dev_info(dev, "Detected display with notch of %u pixel\n", dcp->notch_height); + bitmap_zero(dcp->memdesc_map, DCP_MAX_MAPPINGS); // TDOD: mem_desc IDs start at 1, for simplicity just skip '0' entry set_bit(0, dcp->memdesc_map); diff --git a/drivers/gpu/drm/apple/iomfb.c b/drivers/gpu/drm/apple/iomfb.c index 2358f17ab509bf..404f7638cfb2c0 100644 --- a/drivers/gpu/drm/apple/iomfb.c +++ b/drivers/gpu/drm/apple/iomfb.c @@ -647,7 +647,8 @@ static bool dcpep_process_chunks(struct apple_dcp *dcp, if (!strcmp(req->key, "TimingElements")) { dcp->modes = enumerate_modes(&ctx, &dcp->nr_modes, - dcp->width_mm, dcp->height_mm); + dcp->width_mm, dcp->height_mm, + dcp->notch_height); if (IS_ERR(dcp->modes)) { dev_warn(dcp->dev, "failed to parse modes\n"); @@ -1504,6 +1505,9 @@ void dcp_flush(struct drm_crtc *crtc, struct drm_atomic_state *state) req->swap.src_rect[l] = drm_to_dcp_rect(&src_rect); req->swap.dst_rect[l] = drm_to_dcp_rect(&new_state->dst); + if (dcp->notch_height > 0) + req->swap.dst_rect[l].y += dcp->notch_height; + req->surf_iova[l] = drm_fb_dma_get_gem_addr(fb, new_state, 0); req->surf[l] = (struct dcp_surface){ diff --git a/drivers/gpu/drm/apple/parser.c b/drivers/gpu/drm/apple/parser.c index bb7d57f272ddb9..fc52c26490ec80 100644 --- a/drivers/gpu/drm/apple/parser.c +++ b/drivers/gpu/drm/apple/parser.c @@ -305,7 +305,7 @@ static u32 calculate_clock(struct dimension *horiz, struct dimension *vert) static int parse_mode(struct dcp_parse_ctx *handle, struct dcp_display_mode *out, s64 *score, int width_mm, - int height_mm) + int height_mm, unsigned notch_height) { int ret = 0; struct iterator it; @@ -353,6 +353,9 @@ static int parse_mode(struct dcp_parse_ctx *handle, if (is_virtual) return -EINVAL; + vert.active -= notch_height; + vert.sync_width += notch_height; + /* From here we must succeed. Start filling out the mode. */ *mode = (struct drm_display_mode) { .type = DRM_MODE_TYPE_DRIVER, @@ -383,7 +386,7 @@ static int parse_mode(struct dcp_parse_ctx *handle, struct dcp_display_mode *enumerate_modes(struct dcp_parse_ctx *handle, unsigned int *count, int width_mm, - int height_mm) + int height_mm, unsigned notch_height) { struct iterator it; int ret; @@ -405,7 +408,7 @@ struct dcp_display_mode *enumerate_modes(struct dcp_parse_ctx *handle, for (; it.idx < it.len; ++it.idx) { mode = &modes[*count]; - ret = parse_mode(it.handle, mode, &score, width_mm, height_mm); + ret = parse_mode(it.handle, mode, &score, width_mm, height_mm, notch_height); /* Errors for a single mode are recoverable -- just skip it. */ if (ret) diff --git a/drivers/gpu/drm/apple/parser.h b/drivers/gpu/drm/apple/parser.h index 66a675079dc164..a2d479258ed0eb 100644 --- a/drivers/gpu/drm/apple/parser.h +++ b/drivers/gpu/drm/apple/parser.h @@ -25,7 +25,7 @@ struct dcp_display_mode { int parse(void *blob, size_t size, struct dcp_parse_ctx *ctx); struct dcp_display_mode *enumerate_modes(struct dcp_parse_ctx *handle, unsigned int *count, int width_mm, - int height_mm); + int height_mm, unsigned notch_height); int parse_display_attributes(struct dcp_parse_ctx *handle, int *width_mm, int *height_mm); From 2c9f956e4d7922a0ab6c96b5b4b79fb6bf8e5f15 Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Sun, 6 Nov 2022 10:39:05 +0100 Subject: [PATCH 1029/3902] drm: apple: Fix shutdown of partially probed dcp No need to shut the co-processor down if it wasn't booted to begin with. Signed-off-by: Janne Grunau --- drivers/gpu/drm/apple/dcp.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/drivers/gpu/drm/apple/dcp.c b/drivers/gpu/drm/apple/dcp.c index 68873981e3794c..255b5e34b72a73 100644 --- a/drivers/gpu/drm/apple/dcp.c +++ b/drivers/gpu/drm/apple/dcp.c @@ -373,7 +373,8 @@ static void dcp_platform_shutdown(struct platform_device *pdev) { struct apple_dcp *dcp = platform_get_drvdata(pdev); - iomfb_shutdown(dcp); + if (dcp->shmem) + iomfb_shutdown(dcp); } static const struct of_device_id of_match[] = { From 6ba186b6af726fbce0d9ad10f0dcfe66d75ccfe3 Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Sun, 6 Nov 2022 19:23:08 +0100 Subject: [PATCH 1030/3902] drm: apple: Set maximal framebuffer size correctly DCP reports this in the IOMFBMaxSrcPixels dictionary. Use that instead of the hardcoded values from M1 Max. Fixes multiscreen X11 setups. Signed-off-by: Janne Grunau --- drivers/gpu/drm/apple/apple_drv.c | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/drivers/gpu/drm/apple/apple_drv.c b/drivers/gpu/drm/apple/apple_drv.c index 52c62f2368f30a..117c6b312cd782 100644 --- a/drivers/gpu/drm/apple/apple_drv.c +++ b/drivers/gpu/drm/apple/apple_drv.c @@ -430,13 +430,15 @@ static int apple_platform_probe(struct platform_device *pdev) apple->drm.mode_config.min_width = 32; apple->drm.mode_config.min_height = 32; - /* Unknown maximum, use the iMac (24-inch, 2021) display resolution as - * maximum. - * TODO: this is the max framebuffer size not the maximal supported output - * resolution. DCP reports the maximal framebuffer size take it from there. + /* + * TODO: this is the max framebuffer size not the maximal supported + * output resolution. DCP reports the maximal framebuffer size take it + * from there. + * Hardcode it for now to the M1 Max DCP reported 'MaxSrcBufferWidth' + * and 'MaxSrcBufferHeight' of 16384. */ - apple->drm.mode_config.max_width = 4480; - apple->drm.mode_config.max_height = 2520; + apple->drm.mode_config.max_width = 16384; + apple->drm.mode_config.max_height = 16384; apple->drm.mode_config.funcs = &apple_mode_config_funcs; apple->drm.mode_config.helper_private = &apple_mode_config_helpers; From 8a4c73d7164bb328775a0f83f52ec7a347c2249c Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Sun, 6 Nov 2022 20:35:41 +0100 Subject: [PATCH 1031/3902] drm: apple: Prevent NULL pointer in dcp_hotplug A bit hackish, probably missing something in the setup or simply calling this too early. Signed-off-by: Janne Grunau --- drivers/gpu/drm/apple/iomfb.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/gpu/drm/apple/iomfb.c b/drivers/gpu/drm/apple/iomfb.c index 404f7638cfb2c0..ee00db453da00b 100644 --- a/drivers/gpu/drm/apple/iomfb.c +++ b/drivers/gpu/drm/apple/iomfb.c @@ -985,7 +985,7 @@ void dcp_hotplug(struct work_struct *work) * display modes from atomic_flush, so userspace needs to trigger a * flush, or the CRTC gets no signal. */ - if (!dcp->valid_mode && connector->connected) { + if (connector->base.state && !dcp->valid_mode && connector->connected) { drm_connector_set_link_status_property( &connector->base, DRM_MODE_LINK_STATUS_BAD); } From e740e31681dade5e8c6cdd1904f584c27144765c Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Sun, 30 Oct 2022 13:11:06 +0100 Subject: [PATCH 1032/3902] drm: apple: iomfb: Use FIELD_{GET,PREP} Signed-off-by: Janne Grunau --- drivers/gpu/drm/apple/iomfb.c | 31 ++++++++++++++++--------------- drivers/gpu/drm/apple/iomfb.h | 26 ++++++++++++-------------- 2 files changed, 28 insertions(+), 29 deletions(-) diff --git a/drivers/gpu/drm/apple/iomfb.c b/drivers/gpu/drm/apple/iomfb.c index ee00db453da00b..72924983eefa0b 100644 --- a/drivers/gpu/drm/apple/iomfb.c +++ b/drivers/gpu/drm/apple/iomfb.c @@ -1,6 +1,7 @@ // SPDX-License-Identifier: GPL-2.0-only OR MIT /* Copyright 2021 Alyssa Rosenzweig */ +#include #include #include #include @@ -76,22 +77,22 @@ static int dcp_channel_offset(enum dcp_context_id id) static inline u64 dcpep_set_shmem(u64 dart_va) { - return (DCPEP_TYPE_SET_SHMEM << DCPEP_TYPE_SHIFT) | - (DCPEP_FLAG_VALUE << DCPEP_FLAG_SHIFT) | - (dart_va << DCPEP_DVA_SHIFT); + return FIELD_PREP(IOMFB_MESSAGE_TYPE, IOMFB_MESSAGE_TYPE_SET_SHMEM) | + FIELD_PREP(IOMFB_SHMEM_FLAG, IOMFB_SHMEM_FLAG_VALUE) | + FIELD_PREP(IOMFB_SHMEM_DVA, dart_va); } static inline u64 dcpep_msg(enum dcp_context_id id, u32 length, u16 offset) { - return (DCPEP_TYPE_MESSAGE << DCPEP_TYPE_SHIFT) | - ((u64)id << DCPEP_CONTEXT_SHIFT) | - ((u64)offset << DCPEP_OFFSET_SHIFT) | - ((u64)length << DCPEP_LENGTH_SHIFT); + return FIELD_PREP(IOMFB_MESSAGE_TYPE, IOMFB_MESSAGE_TYPE_MSG) | + FIELD_PREP(IOMFB_MSG_CONTEXT, id) | + FIELD_PREP(IOMFB_MSG_OFFSET, offset) | + FIELD_PREP(IOMFB_MSG_LENGTH, length); } static inline u64 dcpep_ack(enum dcp_context_id id) { - return dcpep_msg(id, 0, 0) | DCPEP_ACK; + return dcpep_msg(id, 0, 0) | IOMFB_MSG_ACK; } /* @@ -1238,9 +1239,9 @@ static void dcpep_got_msg(struct apple_dcp *dcp, u64 message) int channel_offset; void *data; - ctx_id = (message & DCPEP_CONTEXT_MASK) >> DCPEP_CONTEXT_SHIFT; - offset = (message & DCPEP_OFFSET_MASK) >> DCPEP_OFFSET_SHIFT; - length = (message >> DCPEP_LENGTH_SHIFT); + ctx_id = FIELD_GET(IOMFB_MSG_CONTEXT, message); + offset = FIELD_GET(IOMFB_MSG_OFFSET, message); + length = FIELD_GET(IOMFB_MSG_LENGTH, message); channel_offset = dcp_channel_offset(ctx_id); @@ -1251,7 +1252,7 @@ static void dcpep_got_msg(struct apple_dcp *dcp, u64 message) data = dcp->shmem + channel_offset + offset; - if (message & DCPEP_ACK) + if (FIELD_GET(IOMFB_MSG_ACK, message)) dcpep_handle_ack(dcp, ctx_id, data, length); else dcpep_handle_cb(dcp, ctx_id, data, length); @@ -1651,11 +1652,11 @@ static void dcp_started(struct apple_dcp *dcp, void *data, void *cookie) void iomfb_recv_msg(struct apple_dcp *dcp, u64 message) { - enum dcpep_type type = (message >> DCPEP_TYPE_SHIFT) & DCPEP_TYPE_MASK; + enum dcpep_type type = FIELD_GET(IOMFB_MESSAGE_TYPE, message); - if (type == DCPEP_TYPE_INITIALIZED) + if (type == IOMFB_MESSAGE_TYPE_INITIALIZED) dcp_start_signal(dcp, false, dcp_started, NULL); - else if (type == DCPEP_TYPE_MESSAGE) + else if (type == IOMFB_MESSAGE_TYPE_MSG) dcpep_got_msg(dcp, message); else dev_warn(dcp->dev, "Ignoring unknown message %llx\n", message); diff --git a/drivers/gpu/drm/apple/iomfb.h b/drivers/gpu/drm/apple/iomfb.h index f9ead84c21f255..a82d960512bfd1 100644 --- a/drivers/gpu/drm/apple/iomfb.h +++ b/drivers/gpu/drm/apple/iomfb.h @@ -32,29 +32,27 @@ enum dcp_context_id { /* RTKit endpoint message types */ enum dcpep_type { /* Set shared memory */ - DCPEP_TYPE_SET_SHMEM = 0, + IOMFB_MESSAGE_TYPE_SET_SHMEM = 0, /* DCP is initialized */ - DCPEP_TYPE_INITIALIZED = 1, + IOMFB_MESSAGE_TYPE_INITIALIZED = 1, /* Remote procedure call */ - DCPEP_TYPE_MESSAGE = 2, + IOMFB_MESSAGE_TYPE_MSG = 2, }; +#define IOMFB_MESSAGE_TYPE GENMASK_ULL( 3, 0) + /* Message */ -#define DCPEP_TYPE_SHIFT (0) -#define DCPEP_TYPE_MASK GENMASK(1, 0) -#define DCPEP_ACK BIT_ULL(6) -#define DCPEP_CONTEXT_SHIFT (8) -#define DCPEP_CONTEXT_MASK GENMASK(11, 8) -#define DCPEP_OFFSET_SHIFT (16) -#define DCPEP_OFFSET_MASK GENMASK(31, 16) -#define DCPEP_LENGTH_SHIFT (32) +#define IOMFB_MSG_LENGTH GENMASK_ULL(63, 32) +#define IOMFB_MSG_OFFSET GENMASK_ULL(31, 16) +#define IOMFB_MSG_CONTEXT GENMASK_ULL(11, 8) +#define IOMFB_MSG_ACK BIT_ULL(6) /* Set shmem */ -#define DCPEP_DVA_SHIFT (16) -#define DCPEP_FLAG_SHIFT (4) -#define DCPEP_FLAG_VALUE (4) +#define IOMFB_SHMEM_DVA GENMASK_ULL(63, 16) +#define IOMFB_SHMEM_FLAG GENMASK_ULL( 7, 4) +#define IOMFB_SHMEM_FLAG_VALUE 4 struct dcp_packet_header { char tag[4]; From 5bc7849fc04f55394b91471f8365714b6b4db4e8 Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Sun, 30 Oct 2022 13:17:28 +0100 Subject: [PATCH 1033/3902] drm: apple: iomfb: Unify call and callback channels AP calls initiated inside callbacks are using the callback channel and context. Signed-off-by: Janne Grunau --- drivers/gpu/drm/apple/dcp-internal.h | 11 ++----- drivers/gpu/drm/apple/iomfb.c | 45 +++++++++++----------------- 2 files changed, 20 insertions(+), 36 deletions(-) diff --git a/drivers/gpu/drm/apple/dcp-internal.h b/drivers/gpu/drm/apple/dcp-internal.h index 6624672109c33e..8c12382e281b42 100644 --- a/drivers/gpu/drm/apple/dcp-internal.h +++ b/drivers/gpu/drm/apple/dcp-internal.h @@ -47,7 +47,7 @@ struct dcp_mem_descriptor { typedef void (*dcp_callback_t)(struct apple_dcp *, void *, void *); -struct dcp_call_channel { +struct dcp_channel { dcp_callback_t callbacks[DCP_MAX_CALL_DEPTH]; void *cookies[DCP_MAX_CALL_DEPTH]; void *output[DCP_MAX_CALL_DEPTH]; @@ -57,11 +57,6 @@ struct dcp_call_channel { u8 depth; }; -struct dcp_cb_channel { - u8 depth; - void *output[DCP_MAX_CALL_DEPTH]; -}; - struct dcp_fb_reference { struct list_head head; struct drm_framebuffer *fb; @@ -108,8 +103,8 @@ struct apple_dcp { /* Indexed table of memory descriptors */ struct dcp_mem_descriptor memdesc[DCP_MAX_MAPPINGS]; - struct dcp_call_channel ch_cmd, ch_oobcmd; - struct dcp_cb_channel ch_cb, ch_oobcb, ch_async; + struct dcp_channel ch_cmd, ch_oobcmd; + struct dcp_channel ch_cb, ch_oobcb, ch_async; /* Active chunked transfer. There can only be one at a time. */ struct dcp_chunks chunks; diff --git a/drivers/gpu/drm/apple/iomfb.c b/drivers/gpu/drm/apple/iomfb.c index 72924983eefa0b..aae242eecee726 100644 --- a/drivers/gpu/drm/apple/iomfb.c +++ b/drivers/gpu/drm/apple/iomfb.c @@ -99,27 +99,11 @@ static inline u64 dcpep_ack(enum dcp_context_id id) * A channel is busy if we have sent a message that has yet to be * acked. The driver must not sent a message to a busy channel. */ -static bool dcp_channel_busy(struct dcp_call_channel *ch) +static bool dcp_channel_busy(struct dcp_channel *ch) { return (ch->depth != 0); } -/* Get a call channel for a context */ -static struct dcp_call_channel * -dcp_get_call_channel(struct apple_dcp *dcp, enum dcp_context_id context) -{ - switch (context) { - case DCP_CONTEXT_CMD: - case DCP_CONTEXT_CB: - return &dcp->ch_cmd; - case DCP_CONTEXT_OOBCMD: - case DCP_CONTEXT_OOBCB: - return &dcp->ch_oobcmd; - default: - return NULL; - } -} - /* * Get the context ID passed to the DCP for a command we push. The rule is * simple: callback contexts are used when replying to the DCP, command @@ -136,15 +120,19 @@ static enum dcp_context_id dcp_call_context(struct apple_dcp *dcp, bool oob) return oob ? DCP_CONTEXT_OOBCMD : DCP_CONTEXT_CMD; } -/* Get a callback channel for a context */ -static struct dcp_cb_channel *dcp_get_cb_channel(struct apple_dcp *dcp, - enum dcp_context_id context) +/* Get a channel for a context */ +static struct dcp_channel *dcp_get_channel(struct apple_dcp *dcp, + enum dcp_context_id context) { switch (context) { case DCP_CONTEXT_CB: return &dcp->ch_cb; + case DCP_CONTEXT_CMD: + return &dcp->ch_cmd; case DCP_CONTEXT_OOBCB: return &dcp->ch_oobcb; + case DCP_CONTEXT_OOBCMD: + return &dcp->ch_oobcmd; case DCP_CONTEXT_ASYNC: return &dcp->ch_async; default: @@ -153,7 +141,7 @@ static struct dcp_cb_channel *dcp_get_cb_channel(struct apple_dcp *dcp, } /* Get the start of a packet: after the end of the previous packet */ -static u16 dcp_packet_start(struct dcp_call_channel *ch, u8 depth) +static u16 dcp_packet_start(struct dcp_channel *ch, u8 depth) { if (depth > 0) return ch->end[depth - 1]; @@ -204,8 +192,8 @@ static void dcp_push(struct apple_dcp *dcp, bool oob, enum dcpep_method method, u32 in_len, u32 out_len, void *data, dcp_callback_t cb, void *cookie) { - struct dcp_call_channel *ch = oob ? &dcp->ch_oobcmd : &dcp->ch_cmd; enum dcp_context_id context = dcp_call_context(dcp, oob); + struct dcp_channel *ch = dcp_get_channel(dcp, context); struct dcp_packet_header header = { .in_len = in_len, @@ -330,7 +318,7 @@ static int dcp_parse_tag(char tag[4]) /* Ack a callback from the DCP */ static void dcp_ack(struct apple_dcp *dcp, enum dcp_context_id context) { - struct dcp_cb_channel *ch = dcp_get_cb_channel(dcp, context); + struct dcp_channel *ch = dcp_get_channel(dcp, context); dcp_pop_depth(&ch->depth); dcp_send_message(dcp, IOMFB_ENDPOINT, @@ -687,7 +675,7 @@ static u8 dcpep_cb_prop_end(struct apple_dcp *dcp, /* Boot sequence */ static void boot_done(struct apple_dcp *dcp, void *out, void *cookie) { - struct dcp_cb_channel *ch = &dcp->ch_cb; + struct dcp_channel *ch = &dcp->ch_cb; u8 *succ = ch->output[ch->depth - 1]; dev_dbg(dcp->dev, "boot done"); @@ -1176,13 +1164,13 @@ bool (*const dcpep_cb_handlers[DCPEP_MAX_CB])(struct apple_dcp *, int, void *, }; static void dcpep_handle_cb(struct apple_dcp *dcp, enum dcp_context_id context, - void *data, u32 length) + void *data, u32 length, u16 offset) { struct device *dev = dcp->dev; struct dcp_packet_header *hdr = data; void *in, *out; int tag = dcp_parse_tag(hdr->tag); - struct dcp_cb_channel *ch = dcp_get_cb_channel(dcp, context); + struct dcp_channel *ch = dcp_get_channel(dcp, context); u8 depth; if (tag < 0 || tag >= DCPEP_MAX_CB || !dcpep_cb_handlers[tag]) { @@ -1201,6 +1189,7 @@ static void dcpep_handle_cb(struct apple_dcp *dcp, enum dcp_context_id context, depth = dcp_push_depth(&ch->depth); ch->output[depth] = out; + ch->end[depth] = offset + ALIGN(length, DCP_PACKET_ALIGNMENT); if (dcpep_cb_handlers[tag](dcp, tag, out, in)) dcp_ack(dcp, context); @@ -1210,7 +1199,7 @@ static void dcpep_handle_ack(struct apple_dcp *dcp, enum dcp_context_id context, void *data, u32 length) { struct dcp_packet_header *header = data; - struct dcp_call_channel *ch = dcp_get_call_channel(dcp, context); + struct dcp_channel *ch = dcp_get_channel(dcp, context); void *cookie; dcp_callback_t cb; @@ -1255,7 +1244,7 @@ static void dcpep_got_msg(struct apple_dcp *dcp, u64 message) if (FIELD_GET(IOMFB_MSG_ACK, message)) dcpep_handle_ack(dcp, ctx_id, data, length); else - dcpep_handle_cb(dcp, ctx_id, data, length); + dcpep_handle_cb(dcp, ctx_id, data, length, offset); } /* From 3ead738fd87ce1e4d918e17103d10cfbfc189950 Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Sun, 30 Oct 2022 13:29:23 +0100 Subject: [PATCH 1034/3902] drm: apple: "match" PMU/backlight services on init Verify that this still works on HDMI/USB-C. Signed-off-by: Janne Grunau --- drivers/gpu/drm/apple/iomfb.c | 103 +++++++++++++++++++++++++++++++--- drivers/gpu/drm/apple/iomfb.h | 9 +++ 2 files changed, 105 insertions(+), 7 deletions(-) diff --git a/drivers/gpu/drm/apple/iomfb.c b/drivers/gpu/drm/apple/iomfb.c index aae242eecee726..34a08fa86edf34 100644 --- a/drivers/gpu/drm/apple/iomfb.c +++ b/drivers/gpu/drm/apple/iomfb.c @@ -171,7 +171,10 @@ const struct dcp_method_entry dcp_methods[dcpep_num_methods] = { DCP_METHOD("A000", dcpep_late_init_signal), DCP_METHOD("A029", dcpep_setup_video_limits), DCP_METHOD("A034", dcpep_update_notify_clients_dcp), + DCP_METHOD("A131", iomfbep_a131_pmu_service_matched), + DCP_METHOD("A132", iomfbep_a132_backlight_service_matched), DCP_METHOD("A357", dcpep_set_create_dfb), + DCP_METHOD("A358", iomfbep_a358_vi_set_temperature_hint), DCP_METHOD("A401", dcpep_start_signal), DCP_METHOD("A407", dcpep_swap_start), DCP_METHOD("A408", dcpep_swap_submit), @@ -258,6 +261,10 @@ static void dcp_push(struct apple_dcp *dcp, bool oob, enum dcpep_method method, cb, cookie); \ } +DCP_THUNK_OUT(iomfb_a131_pmu_service_matched, iomfbep_a131_pmu_service_matched, u32); +DCP_THUNK_OUT(iomfb_a132_backlight_service_matched, iomfbep_a132_backlight_service_matched, u32); +DCP_THUNK_OUT(iomfb_a358_vi_set_temperature_hint, iomfbep_a358_vi_set_temperature_hint, u32); + DCP_THUNK_INOUT(dcp_swap_submit, dcpep_swap_submit, struct dcp_swap_submit_req, struct dcp_swap_submit_resp); @@ -355,11 +362,91 @@ static void dcpep_cb_swap_complete(struct apple_dcp *dcp, dcp_drm_crtc_vblank(dcp->crtc); } +/* special */ +static void complete_vi_set_temperature_hint(struct apple_dcp *dcp, void *out, void *cookie) +{ + // ack D100 cb_match_pmu_service + dcp_ack(dcp, DCP_CONTEXT_CB); +} + +static bool iomfbep_cb_match_pmu_service(struct apple_dcp *dcp, int tag, void *out, void *in) +{ + trace_iomfb_callback(dcp, tag, __func__); + iomfb_a358_vi_set_temperature_hint(dcp, false, + complete_vi_set_temperature_hint, + NULL); + + // return false for deferred ACK + return false; +} + +static void complete_pmu_service_matched(struct apple_dcp *dcp, void *out, void *cookie) +{ + struct dcp_channel *ch = &dcp->ch_cb; + u8 *succ = ch->output[ch->depth - 1]; + + *succ = true; + + // ack D206 cb_match_pmu_service_2 + dcp_ack(dcp, DCP_CONTEXT_CB); +} + +static bool iomfbep_cb_match_pmu_service_2(struct apple_dcp *dcp, int tag, void *out, void *in) +{ + trace_iomfb_callback(dcp, tag, __func__); + + iomfb_a131_pmu_service_matched(dcp, false, complete_pmu_service_matched, + out); + + // return false for deferred ACK + return false; +} + +static void complete_backlight_service_matched(struct apple_dcp *dcp, void *out, void *cookie) +{ + struct dcp_channel *ch = &dcp->ch_cb; + u8 *succ = ch->output[ch->depth - 1]; + + *succ = true; + + // ack D206 cb_match_backlight_service + dcp_ack(dcp, DCP_CONTEXT_CB); +} + +static bool iomfbep_cb_match_backlight_service(struct apple_dcp *dcp, int tag, void *out, void *in) +{ + trace_iomfb_callback(dcp, tag, __func__); + + iomfb_a132_backlight_service_matched(dcp, false, complete_backlight_service_matched, out); + + // return false for deferred ACK + return false; +} + static struct dcp_get_uint_prop_resp dcpep_cb_get_uint_prop(struct apple_dcp *dcp, struct dcp_get_uint_prop_req *req) { - /* unimplemented for now */ - return (struct dcp_get_uint_prop_resp){ .value = 0 }; + struct dcp_get_uint_prop_resp resp = (struct dcp_get_uint_prop_resp){ + .value = 0 + }; + + if (memcmp(req->obj, "SUMP", sizeof(req->obj)) == 0) { /* "PMUS */ + if (strncmp(req->key, "Temperature", sizeof(req->key)) == 0) { + /* + * TODO: value from j314c, find out if it is temperature in + * centigrade C and which temperature sensor reports it + */ + resp.value = 3029; + resp.ret = true; + } + } + + return resp; +} + +static void iomfbep_cb_set_fx_prop(struct apple_dcp *dcp, struct iomfb_set_fx_prop_req *req) +{ + // TODO: trace this, see if there properties which needs to used later } /* @@ -1079,6 +1166,8 @@ TRAMPOLINE_IN(trampoline_swap_complete, dcpep_cb_swap_complete, struct dc_swap_complete_resp); TRAMPOLINE_INOUT(trampoline_get_uint_prop, dcpep_cb_get_uint_prop, struct dcp_get_uint_prop_req, struct dcp_get_uint_prop_resp); +TRAMPOLINE_IN(trampoline_set_fx_prop, iomfbep_cb_set_fx_prop, + struct iomfb_set_fx_prop_req) TRAMPOLINE_INOUT(trampoline_map_piodma, dcpep_cb_map_piodma, struct dcp_map_buf_req, struct dcp_map_buf_resp); TRAMPOLINE_IN(trampoline_unmap_piodma, dcpep_cb_unmap_piodma, @@ -1114,7 +1203,7 @@ bool (*const dcpep_cb_handlers[DCPEP_MAX_CB])(struct apple_dcp *, int, void *, [1] = trampoline_true, /* did_power_on_signal */ [2] = trampoline_nop, /* will_power_off_signal */ [3] = trampoline_rt_bandwidth, - [100] = trampoline_nop, /* match_pmu_service */ + [100] = iomfbep_cb_match_pmu_service, [101] = trampoline_zero, /* get_display_default_stride */ [103] = trampoline_nop, /* set_boolean_property */ [106] = trampoline_nop, /* remove_property */ @@ -1122,7 +1211,7 @@ bool (*const dcpep_cb_handlers[DCPEP_MAX_CB])(struct apple_dcp *, int, void *, [108] = trampoline_true, /* create_product_service */ [109] = trampoline_true, /* create_pmu_service */ [110] = trampoline_true, /* create_iomfb_service */ - [111] = trampoline_false, /* create_backlight_service */ + [111] = trampoline_true, /* create_backlight_service */ [116] = dcpep_cb_boot_1, [117] = trampoline_false, /* is_dark_boot */ [118] = trampoline_false, /* is_dark_boot / is_waking_from_hibernate*/ @@ -1132,14 +1221,14 @@ bool (*const dcpep_cb_handlers[DCPEP_MAX_CB])(struct apple_dcp *, int, void *, [124] = trampoline_prop_end, [201] = trampoline_map_piodma, [202] = trampoline_unmap_piodma, - [206] = trampoline_true, /* match_pmu_service_2 */ - [207] = trampoline_true, /* match_backlight_service */ + [206] = iomfbep_cb_match_pmu_service_2, + [207] = iomfbep_cb_match_backlight_service, [208] = trampoline_get_time, [211] = trampoline_nop, /* update_backlight_factor_prop */ [300] = trampoline_nop, /* pr_publish */ [401] = trampoline_get_uint_prop, [404] = trampoline_nop, /* sr_set_uint_prop */ - [406] = trampoline_nop, /* set_fx_prop */ + [406] = trampoline_set_fx_prop, [408] = trampoline_get_frequency, [411] = trampoline_map_reg, [413] = trampoline_true, /* sr_set_property_dict */ diff --git a/drivers/gpu/drm/apple/iomfb.h b/drivers/gpu/drm/apple/iomfb.h index a82d960512bfd1..68fdc654d597f5 100644 --- a/drivers/gpu/drm/apple/iomfb.h +++ b/drivers/gpu/drm/apple/iomfb.h @@ -197,6 +197,9 @@ enum dcpep_method { dcpep_set_parameter_dcp, dcpep_enable_disable_video_power_savings, dcpep_is_main_display, + iomfbep_a131_pmu_service_matched, + iomfbep_a132_backlight_service_matched, + iomfbep_a358_vi_set_temperature_hint, dcpep_num_methods }; @@ -337,6 +340,12 @@ struct dcp_get_uint_prop_resp { u8 padding[3]; } __packed; +struct iomfb_set_fx_prop_req { + char obj[4]; + char key[0x40]; + u32 value; +} __packed; + struct dcp_set_power_state_req { u64 unklong; u8 unkbool; From 980d026f82ff86a077e4db9598161a4012759368 Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Mon, 31 Oct 2022 01:11:36 +0100 Subject: [PATCH 1035/3902] drm: apple: Brightness control via atomic commits This abuses color_mgnt_change in drm_crtc_state and will be changed once phase 2 of the "drm/kms: control display brightness through drm_connector properties" RfC (linked below) is implemented. The lookup of DAC values from brightness (nits) is not fully understood. Since IOMFB reports te brightness back the easiest solution would be to create our own lookup table or find a approximation which works. DCP appears to report the brightness in nits by "PropRelay::pr_publish(prop_id=15, value=...)" (scaled by "Brightness_scale"). Link: https://lore.kernel.org/dri-devel/b61d3eeb-6213-afac-2e70-7b9791c86d2e@redhat.com/ Signed-off-by: Janne Grunau --- drivers/gpu/drm/apple/Makefile | 2 +- drivers/gpu/drm/apple/apple_drv.c | 12 +- drivers/gpu/drm/apple/dcp-internal.h | 15 ++ drivers/gpu/drm/apple/dcp.c | 31 ++++ drivers/gpu/drm/apple/dcp.h | 1 + drivers/gpu/drm/apple/dcp_backlight.c | 232 ++++++++++++++++++++++++++ drivers/gpu/drm/apple/iomfb.c | 47 +++++- drivers/gpu/drm/apple/iomfb.h | 25 ++- 8 files changed, 349 insertions(+), 16 deletions(-) create mode 100644 drivers/gpu/drm/apple/dcp_backlight.c diff --git a/drivers/gpu/drm/apple/Makefile b/drivers/gpu/drm/apple/Makefile index 082c8c58bb87fe..a61024ddce4c38 100644 --- a/drivers/gpu/drm/apple/Makefile +++ b/drivers/gpu/drm/apple/Makefile @@ -4,7 +4,7 @@ CFLAGS_trace.o = -I$(src) appledrm-y := apple_drv.o -apple_dcp-y := dcp.o iomfb.o parser.o +apple_dcp-y := dcp.o dcp_backlight.o iomfb.o parser.o apple_dcp-$(CONFIG_TRACING) += trace.o apple_piodma-y := dummy-piodma.o diff --git a/drivers/gpu/drm/apple/apple_drv.c b/drivers/gpu/drm/apple/apple_drv.c index 117c6b312cd782..9d0d029605bf6d 100644 --- a/drivers/gpu/drm/apple/apple_drv.c +++ b/drivers/gpu/drm/apple/apple_drv.c @@ -317,7 +317,6 @@ static int apple_probe_per_dcp(struct device *dev, struct apple_connector *connector; struct drm_encoder *encoder; struct drm_plane *primary; - int con_type; int ret; primary = apple_plane_init(drm, 1U << num, DRM_PLANE_TYPE_PRIMARY); @@ -343,17 +342,8 @@ static int apple_probe_per_dcp(struct device *dev, drm_connector_helper_add(&connector->base, &apple_connector_helper_funcs); - if (of_property_match_string(dcp->dev.of_node, "apple,connector-type", "eDP") >= 0) - con_type = DRM_MODE_CONNECTOR_eDP; - else if (of_property_match_string(dcp->dev.of_node, "apple,connector-type", "HDMI-A") >= 0) - con_type = DRM_MODE_CONNECTOR_HDMIA; - else if (of_property_match_string(dcp->dev.of_node, "apple,connector-type", "USB-C") >= 0) - con_type = DRM_MODE_CONNECTOR_USB; - else - con_type = DRM_MODE_CONNECTOR_Unknown; - ret = drm_connector_init(drm, &connector->base, &apple_connector_funcs, - con_type); + dcp_get_connector_type(dcp)); if (ret) return ret; diff --git a/drivers/gpu/drm/apple/dcp-internal.h b/drivers/gpu/drm/apple/dcp-internal.h index 8c12382e281b42..c2fa002b45d5de 100644 --- a/drivers/gpu/drm/apple/dcp-internal.h +++ b/drivers/gpu/drm/apple/dcp-internal.h @@ -64,6 +64,14 @@ struct dcp_fb_reference { #define MAX_NOTCH_HEIGHT 160 +struct dcp_brightness { + u32 dac; + int nits; + int set; + int scale; + bool update; +}; + /* TODO: move IOMFB members to its own struct */ struct apple_dcp { struct device *dev; @@ -128,6 +136,9 @@ struct apple_dcp { struct dcp_display_mode *modes; unsigned int nr_modes; + /* Attributes of the connector */ + int connector_type; + /* Attributes of the connected display */ int width_mm, height_mm; @@ -140,6 +151,10 @@ struct apple_dcp { * on the next successfully completed swap. */ struct list_head swapped_out_fbs; + + struct dcp_brightness brightness; }; +int dcp_backlight_register(struct apple_dcp *dcp); + #endif /* __APPLE_DCP_INTERNAL_H__ */ diff --git a/drivers/gpu/drm/apple/dcp.c b/drivers/gpu/drm/apple/dcp.c index 255b5e34b72a73..ec46503857009e 100644 --- a/drivers/gpu/drm/apple/dcp.c +++ b/drivers/gpu/drm/apple/dcp.c @@ -21,6 +21,7 @@ #include "dcp.h" #include "dcp-internal.h" +#include "iomfb.h" #include "parser.h" #include "trace.h" @@ -219,6 +220,14 @@ int dcp_crtc_atomic_check(struct drm_crtc *crtc, struct drm_atomic_state *state) } EXPORT_SYMBOL_GPL(dcp_crtc_atomic_check); +int dcp_get_connector_type(struct platform_device *pdev) +{ + struct apple_dcp *dcp = platform_get_drvdata(pdev); + + return (dcp->connector_type); +} +EXPORT_SYMBOL_GPL(dcp_get_connector_type); + void dcp_link(struct platform_device *pdev, struct apple_crtc *crtc, struct apple_connector *connector) { @@ -277,6 +286,7 @@ static int dcp_get_disp_regs(struct apple_dcp *dcp) static int dcp_platform_probe(struct platform_device *pdev) { struct device *dev = &pdev->dev; + struct device_node *panel_np; struct apple_dcp *dcp; u32 cpu_ctrl; int ret; @@ -298,6 +308,27 @@ static int dcp_platform_probe(struct platform_device *pdev) of_platform_default_populate(dev->of_node, NULL, dev); + /* intialize brightness scale to a sensible default to avoid divide by 0*/ + dcp->brightness.scale = 65536; + panel_np = of_get_compatible_child(dev->of_node, "apple,panel"); + if (panel_np) { + of_node_put(panel_np); + dcp->connector_type = DRM_MODE_CONNECTOR_eDP; + + /* try to register backlight device, */ + ret = dcp_backlight_register(dcp); + if (ret) + return dev_err_probe(dev, ret, + "Unable to register backlight device\n"); + } else if (of_property_match_string(dev->of_node, "apple,connector-type", "HDMI-A") >= 0) + dcp->connector_type = DRM_MODE_CONNECTOR_HDMIA; + else if (of_property_match_string(dev->of_node, "apple,connector-type", "DP") >= 0) + dcp->connector_type = DRM_MODE_CONNECTOR_DisplayPort; + else if (of_property_match_string(dev->of_node, "apple,connector-type", "USB-C") >= 0) + dcp->connector_type = DRM_MODE_CONNECTOR_USB; + else + dcp->connector_type = DRM_MODE_CONNECTOR_Unknown; + dcp->piodma = dcp_get_dev(dev, "apple,piodma-mapper"); if (!dcp->piodma) { dev_err(dev, "failed to find piodma\n"); diff --git a/drivers/gpu/drm/apple/dcp.h b/drivers/gpu/drm/apple/dcp.h index 60e9bcfa4714e0..dfe014f3f5d1da 100644 --- a/drivers/gpu/drm/apple/dcp.h +++ b/drivers/gpu/drm/apple/dcp.h @@ -38,6 +38,7 @@ struct apple_connector { void dcp_poweroff(struct platform_device *pdev); void dcp_poweron(struct platform_device *pdev); int dcp_crtc_atomic_check(struct drm_crtc *crtc, struct drm_atomic_state *state); +int dcp_get_connector_type(struct platform_device *pdev); void dcp_link(struct platform_device *pdev, struct apple_crtc *apple, struct apple_connector *connector); int dcp_start(struct platform_device *pdev); diff --git a/drivers/gpu/drm/apple/dcp_backlight.c b/drivers/gpu/drm/apple/dcp_backlight.c new file mode 100644 index 00000000000000..c695e3bad7db91 --- /dev/null +++ b/drivers/gpu/drm/apple/dcp_backlight.c @@ -0,0 +1,232 @@ +// SPDX-License-Identifier: GPL-2.0-only OR MIT +/* Copyright (C) The Asahi Linux Contributors */ + +#include +#include +#include +#include + +#include +#include +#include +#include "linux/jiffies.h" + +#include "dcp.h" +#include "dcp-internal.h" + +#define MIN_BRIGHTNESS_PART1 2U +#define MAX_BRIGHTNESS_PART1 99U +#define MIN_BRIGHTNESS_PART2 103U +#define MAX_BRIGHTNESS_PART2 510U + +/* + * lookup for display brightness 2 to 99 nits + * */ +static u32 brightness_part1[] = { + 0x0000000, 0x0810038, 0x0f000bd, 0x143011c, + 0x1850165, 0x1bc01a1, 0x1eb01d4, 0x2140200, + 0x2380227, 0x2590249, 0x2770269, 0x2930285, + 0x2ac02a0, 0x2c402b8, 0x2d902cf, 0x2ee02e4, + 0x30102f8, 0x314030b, 0x325031c, 0x335032d, + 0x345033d, 0x354034d, 0x362035b, 0x3700369, + 0x37d0377, 0x38a0384, 0x3960390, 0x3a2039c, + 0x3ad03a7, 0x3b803b3, 0x3c303bd, 0x3cd03c8, + 0x3d703d2, 0x3e103dc, 0x3ea03e5, 0x3f303ef, + 0x3fc03f8, 0x4050400, 0x40d0409, 0x4150411, + 0x41d0419, 0x4250421, 0x42d0429, 0x4340431, + 0x43c0438, 0x443043f, 0x44a0446, 0x451044d, + 0x4570454, 0x45e045b, 0x4640461, 0x46b0468, + 0x471046e, 0x4770474, 0x47d047a, 0x4830480, + 0x4890486, 0x48e048b, 0x4940491, 0x4990497, + 0x49f049c, 0x4a404a1, 0x4a904a7, 0x4ae04ac, + 0x4b304b1, 0x4b804b6, 0x4bd04bb, 0x4c204c0, + 0x4c704c5, 0x4cc04c9, 0x4d004ce, 0x4d504d3, + 0x4d904d7, 0x4de04dc, 0x4e204e0, 0x4e704e4, + 0x4eb04e9, 0x4ef04ed, 0x4f304f1, 0x4f704f5, + 0x4fb04f9, 0x4ff04fd, 0x5030501, 0x5070505, + 0x50b0509, 0x50f050d, 0x5130511, 0x5160515, + 0x51a0518, 0x51e051c, 0x5210520, 0x5250523, + 0x5290527, 0x52c052a, 0x52f052e, 0x5330531, + 0x5360535, 0x53a0538, 0x53d053b, 0x540053f, + 0x5440542, 0x5470545, 0x54a0548, 0x54d054c, + 0x550054f, 0x5530552, 0x5560555, 0x5590558, + 0x55c055b, 0x55f055e, 0x5620561, 0x5650564, + 0x5680567, 0x56b056a, 0x56e056d, 0x571056f, + 0x5740572, 0x5760575, 0x5790578, 0x57c057b, + 0x57f057d, 0x5810580, 0x5840583, 0x5870585, + 0x5890588, 0x58c058b, 0x58f058d +}; + +static u32 brightness_part12[] = { 0x58f058d, 0x59d058f }; + +/* + * lookup table for display brightness 103.3 to 510 nits + * */ +static u32 brightness_part2[] = { + 0x59d058f, 0x5b805ab, 0x5d105c5, 0x5e805dd, + 0x5fe05f3, 0x6120608, 0x625061c, 0x637062e, + 0x6480640, 0x6580650, 0x6680660, 0x677066f, + 0x685067e, 0x693068c, 0x6a00699, 0x6ac06a6, + 0x6b806b2, 0x6c406be, 0x6cf06ca, 0x6da06d5, + 0x6e506df, 0x6ef06ea, 0x6f906f4, 0x70206fe, + 0x70c0707, 0x7150710, 0x71e0719, 0x7260722, + 0x72f072a, 0x7370733, 0x73f073b, 0x7470743, + 0x74e074a, 0x7560752, 0x75d0759, 0x7640760, + 0x76b0768, 0x772076e, 0x7780775, 0x77f077c, + 0x7850782, 0x78c0789, 0x792078f, 0x7980795, + 0x79e079b, 0x7a407a1, 0x7aa07a7, 0x7af07ac, + 0x7b507b2, 0x7ba07b8, 0x7c007bd, 0x7c507c2, + 0x7ca07c8, 0x7cf07cd, 0x7d407d2, 0x7d907d7, + 0x7de07dc, 0x7e307e1, 0x7e807e5, 0x7ec07ea, + 0x7f107ef, 0x7f607f3, 0x7fa07f8, 0x7fe07fc +}; + + +static int dcp_get_brightness(struct backlight_device *bd) +{ + struct apple_dcp *dcp = bl_get_data(bd); + + return dcp->brightness.nits; +} + +#define SCALE_FACTOR (1 << 10) + +static u32 interpolate(int val, int min, int max, u32 *tbl, size_t tbl_size) +{ + u32 frac; + u64 low, high; + u32 interpolated = (tbl_size - 1) * ((val - min) * SCALE_FACTOR) / (max - min); + + size_t index = interpolated / SCALE_FACTOR; + + if (WARN(index + 1 >= tbl_size, "invalid index %zu for brightness %u", index, val)) + return tbl[tbl_size / 2]; + + frac = interpolated & (SCALE_FACTOR - 1); + low = tbl[index]; + high = tbl[index + 1]; + + return ((frac * high) + ((SCALE_FACTOR - frac) * low)) / SCALE_FACTOR; +} + +static u32 calculate_dac(struct apple_dcp *dcp, int val) +{ + u32 dac; + + if (val <= MIN_BRIGHTNESS_PART1) + return 16 * brightness_part1[0]; + else if (val == MAX_BRIGHTNESS_PART1) + return 16 * brightness_part1[ARRAY_SIZE(brightness_part1) - 1]; + else if (val == MIN_BRIGHTNESS_PART2) + return 16 * brightness_part2[0]; + else if (val >= MAX_BRIGHTNESS_PART2) + return brightness_part2[ARRAY_SIZE(brightness_part2) - 1]; + + if (val < MAX_BRIGHTNESS_PART1) { + dac = interpolate(val, MIN_BRIGHTNESS_PART1, MAX_BRIGHTNESS_PART1, + brightness_part1, ARRAY_SIZE(brightness_part1)); + } else if (val > MIN_BRIGHTNESS_PART2) { + dac = interpolate(val, MIN_BRIGHTNESS_PART2, MAX_BRIGHTNESS_PART2, + brightness_part2, ARRAY_SIZE(brightness_part2)); + } else { + dac = interpolate(val, MAX_BRIGHTNESS_PART1, MIN_BRIGHTNESS_PART2, + brightness_part12, ARRAY_SIZE(brightness_part12)); + } + + return 16 * dac; +} + +static int drm_crtc_set_brightness(struct drm_crtc *crtc, + struct drm_modeset_acquire_ctx *ctx) +{ + struct drm_atomic_state *state; + struct drm_crtc_state *crtc_state; + int ret = 0; + + state = drm_atomic_state_alloc(crtc->dev); + if (!state) + return -ENOMEM; + + state->acquire_ctx = ctx; + crtc_state = drm_atomic_get_crtc_state(state, crtc); + if (IS_ERR(crtc_state)) { + ret = PTR_ERR(crtc_state); + goto fail; + } + + crtc_state->color_mgmt_changed |= true; + + ret = drm_atomic_commit(state); + +fail: + drm_atomic_state_put(state); + return ret; +} + +static int dcp_set_brightness(struct backlight_device *bd) +{ + int ret = 0; + struct apple_dcp *dcp = bl_get_data(bd); + + bd->props.power = FB_BLANK_UNBLANK; + if (dcp->brightness.set != bd->props.brightness) { + dcp->brightness.dac = calculate_dac(dcp, bd->props.brightness); + dcp->brightness.set = bd->props.brightness; + dcp->brightness.update = true; + } + + if (dcp->brightness.update && dcp->crtc) { + struct drm_modeset_acquire_ctx ctx; + struct drm_device *drm_dev = dcp->crtc->base.dev; + + DRM_MODESET_LOCK_ALL_BEGIN(drm_dev, ctx, 0, ret); + ret = drm_crtc_set_brightness(&dcp->crtc->base, &ctx); + DRM_MODESET_LOCK_ALL_END(drm_dev, ctx, ret); + } + + return ret; +} + +static const struct backlight_ops dcp_backlight_ops = { + .get_brightness = dcp_get_brightness, + .update_status = dcp_set_brightness, +}; + +int dcp_backlight_register(struct apple_dcp *dcp) +{ + struct device *dev = dcp->dev; + struct backlight_device *bd; + struct device_node *panel_np; + struct backlight_properties props = { + .type = BACKLIGHT_PLATFORM, + .brightness = dcp->brightness.nits, + .max_brightness = 0, + .scale = BACKLIGHT_SCALE_LINEAR, + }; + u32 max_brightness; + int ret = 0; + + panel_np = of_get_compatible_child(dev->of_node, "apple,panel"); + if (!panel_np) + return 0; + + if (!of_device_is_available(panel_np)) + goto out_put; + + ret = of_property_read_u32(panel_np, "apple,max-brightness", &max_brightness); + if (ret) { + dev_err(dev, "Missing property 'apple,max-brightness'\n"); + goto out_put; + } + props.max_brightness = min(max_brightness, MAX_BRIGHTNESS_PART2 - 1); + + bd = devm_backlight_device_register(dev, "apple-panel-bl", dev, dcp, + &dcp_backlight_ops, &props); + if (IS_ERR(bd)) + ret = PTR_ERR(bd); + +out_put: + of_node_put(panel_np); + + return ret; +} diff --git a/drivers/gpu/drm/apple/iomfb.c b/drivers/gpu/drm/apple/iomfb.c index 34a08fa86edf34..ca29658cfc8e99 100644 --- a/drivers/gpu/drm/apple/iomfb.c +++ b/drivers/gpu/drm/apple/iomfb.c @@ -423,6 +423,21 @@ static bool iomfbep_cb_match_backlight_service(struct apple_dcp *dcp, int tag, v return false; } +static void iomfb_cb_pr_publish(struct apple_dcp *dcp, struct iomfb_property *prop) +{ + switch (prop->id) { + case IOMFB_PROPERTY_NITS: + dcp->brightness.nits = prop->value / dcp->brightness.scale; + /* temporary for user debugging during tesing */ + dev_info(dcp->dev, "Backlight updated to %u nits\n", + dcp->brightness.nits); + dcp->brightness.update = false; + break; + default: + dev_dbg(dcp->dev, "pr_publish: id: %d = %u\n", prop->id, prop->value); + } +} + static struct dcp_get_uint_prop_resp dcpep_cb_get_uint_prop(struct apple_dcp *dcp, struct dcp_get_uint_prop_req *req) { @@ -444,6 +459,19 @@ dcpep_cb_get_uint_prop(struct apple_dcp *dcp, struct dcp_get_uint_prop_req *req) return resp; } +static u8 iomfbep_cb_sr_set_property_int(struct apple_dcp *dcp, + struct iomfb_sr_set_property_int_req *req) +{ + if (memcmp(req->obj, "FMOI", sizeof(req->obj)) == 0) { /* "IOMF */ + if (strncmp(req->key, "Brightness_Scale", sizeof(req->key)) == 0) { + if (!req->value_null) + dcp->brightness.scale = req->value; + } + } + + return 1; +} + static void iomfbep_cb_set_fx_prop(struct apple_dcp *dcp, struct iomfb_set_fx_prop_req *req) { // TODO: trace this, see if there properties which needs to used later @@ -1172,6 +1200,8 @@ TRAMPOLINE_INOUT(trampoline_map_piodma, dcpep_cb_map_piodma, struct dcp_map_buf_req, struct dcp_map_buf_resp); TRAMPOLINE_IN(trampoline_unmap_piodma, dcpep_cb_unmap_piodma, struct dcp_unmap_buf_resp); +TRAMPOLINE_INOUT(trampoline_sr_set_property_int, iomfbep_cb_sr_set_property_int, + struct iomfb_sr_set_property_int_req, u8); TRAMPOLINE_INOUT(trampoline_allocate_buffer, dcpep_cb_allocate_buffer, struct dcp_allocate_buffer_req, struct dcp_allocate_buffer_resp); @@ -1196,6 +1226,8 @@ TRAMPOLINE_IN(trampoline_hotplug, dcpep_cb_hotplug, u64); TRAMPOLINE_IN(trampoline_swap_complete_intent_gated, dcpep_cb_swap_complete_intent_gated, struct dcp_swap_complete_intent_gated); +TRAMPOLINE_IN(trampoline_pr_publish, iomfb_cb_pr_publish, + struct iomfb_property); bool (*const dcpep_cb_handlers[DCPEP_MAX_CB])(struct apple_dcp *, int, void *, void *) = { @@ -1205,6 +1237,7 @@ bool (*const dcpep_cb_handlers[DCPEP_MAX_CB])(struct apple_dcp *, int, void *, [3] = trampoline_rt_bandwidth, [100] = iomfbep_cb_match_pmu_service, [101] = trampoline_zero, /* get_display_default_stride */ + [102] = trampoline_nop, /* set_number_property */ [103] = trampoline_nop, /* set_boolean_property */ [106] = trampoline_nop, /* remove_property */ [107] = trampoline_true, /* create_provider_service */ @@ -1225,14 +1258,14 @@ bool (*const dcpep_cb_handlers[DCPEP_MAX_CB])(struct apple_dcp *, int, void *, [207] = iomfbep_cb_match_backlight_service, [208] = trampoline_get_time, [211] = trampoline_nop, /* update_backlight_factor_prop */ - [300] = trampoline_nop, /* pr_publish */ + [300] = trampoline_pr_publish, [401] = trampoline_get_uint_prop, [404] = trampoline_nop, /* sr_set_uint_prop */ [406] = trampoline_set_fx_prop, [408] = trampoline_get_frequency, [411] = trampoline_map_reg, [413] = trampoline_true, /* sr_set_property_dict */ - [414] = trampoline_true, /* sr_set_property_int */ + [414] = trampoline_sr_set_property_int, [415] = trampoline_true, /* sr_set_property_bool */ [451] = trampoline_allocate_buffer, [452] = trampoline_map_physical, @@ -1614,6 +1647,14 @@ void dcp_flush(struct drm_crtc *crtc, struct drm_atomic_state *state) /* These fields should be set together */ req->swap.swap_completed = req->swap.swap_enabled; + /* update brightness if changed */ + if (dcp->brightness.update) { + req->swap.bl_unk = 1; + req->swap.bl_value = dcp->brightness.dac; + req->swap.bl_power = 0x40; + dcp->brightness.update = false; + } + if (modeset) { struct dcp_display_mode *mode; struct dcp_wait_cookie *cookie; @@ -1667,7 +1708,7 @@ void dcp_flush(struct drm_crtc *crtc, struct drm_atomic_state *state) dcp->valid_mode = true; } - if (!has_surface) { + if (!has_surface && !crtc_state->color_mgmt_changed) { if (crtc_state->enable && crtc_state->active && !crtc_state->planes_changed) { schedule_work(&dcp->vblank_wq); diff --git a/drivers/gpu/drm/apple/iomfb.h b/drivers/gpu/drm/apple/iomfb.h index 68fdc654d597f5..386a84cfcc5cd1 100644 --- a/drivers/gpu/drm/apple/iomfb.h +++ b/drivers/gpu/drm/apple/iomfb.h @@ -63,6 +63,12 @@ struct dcp_packet_header { #define DCP_IS_NULL(ptr) ((ptr) ? 1 : 0) #define DCP_PACKET_ALIGNMENT (0x40) +enum iomfb_property_id { + IOMFB_PROPERTY_NITS = 15, // divide by Brightness_Scale +}; + +#define IOMFB_BRIGHTNESS_MIN 0x10000000 + /* Structures used in v12.0 firmware */ #define SWAP_SURFACES 4 @@ -114,7 +120,11 @@ struct dcp_swap { u32 unk_2c8; u8 unk_2cc[0x14]; u32 unk_2e0; - u8 unk_2e4[0x3c]; + u16 unk_2e2; + u64 bl_unk; + u32 bl_value; // min value is 0x10000000 + u8 bl_power; // constant 0x40 for on + u8 unk_2f3[0x2d]; } __packed; /* Information describing a plane of a planar compressed surface */ @@ -340,6 +350,14 @@ struct dcp_get_uint_prop_resp { u8 padding[3]; } __packed; +struct iomfb_sr_set_property_int_req { + char obj[4]; + char key[0x40]; + u64 value; + u8 value_null; + u8 padding[3]; +} __packed; + struct iomfb_set_fx_prop_req { char obj[4]; char key[0x40]; @@ -410,4 +428,9 @@ struct dcp_read_edt_data_resp { u8 ret; } __packed; +struct iomfb_property { + u32 id; + u32 value; +} __packed; + #endif From 2d549c24052f311daf5f5cacca0fdf15b2fd74e8 Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Wed, 16 Nov 2022 00:10:31 +0100 Subject: [PATCH 1036/3902] HACK: gpu: drm: apple: j314/j316: Ignore 120 Hz mode for integrated display It's currently not useful as DCP limits the swap rate to 60 Hz anyway and marcan reported pointer choppiness with 120 Hz. Signed-off-by: Janne Grunau --- drivers/gpu/drm/apple/parser.c | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/drivers/gpu/drm/apple/parser.c b/drivers/gpu/drm/apple/parser.c index fc52c26490ec80..31aecd4b2fc195 100644 --- a/drivers/gpu/drm/apple/parser.c +++ b/drivers/gpu/drm/apple/parser.c @@ -353,6 +353,19 @@ static int parse_mode(struct dcp_parse_ctx *handle, if (is_virtual) return -EINVAL; + /* + * HACK: + * Ignore the 120 Hz mode on j314/j316 (identified by resolution). + * DCP limits normal swaps to 60 Hz anyway and the 120 Hz mode might + * cause choppiness with X11. + * Just downscoring it and thus making the 60 Hz mode the preferred mode + * seems not enough for some user space. + */ + if (vert.precise_sync_rate >> 16 == 120 && + ((horiz.active == 3024 && vert.active == 1964) || + (horiz.active == 3456 && vert.active == 2234))) + return -EINVAL; + vert.active -= notch_height; vert.sync_width += notch_height; From c23bb24b01a853049e44d6bc9173e0860bdead6e Mon Sep 17 00:00:00 2001 From: Hector Martin Date: Thu, 17 Nov 2022 21:51:09 +0900 Subject: [PATCH 1037/3902] drm/apple: Fix suspend/resume handling Use the drm_modeset helpers for suspend/resume at the subsystem level, which actually do the proper save/restore dance and work, instead of open-coding calls to dcp_poweroff/dcp_poweron which is clearly wrong and doesn't restore properly (nor would it be correct if the display was already off when suspended). Also fix apple_platform_remove while I'm here, since drvdata wasn't getting set so that would never work. Signed-off-by: Hector Martin --- drivers/gpu/drm/apple/apple_drv.c | 31 +++++++++++++++++++++++++++++-- drivers/gpu/drm/apple/dcp.c | 27 --------------------------- 2 files changed, 29 insertions(+), 29 deletions(-) diff --git a/drivers/gpu/drm/apple/apple_drv.c b/drivers/gpu/drm/apple/apple_drv.c index 9d0d029605bf6d..2a3fa61bbb3b2f 100644 --- a/drivers/gpu/drm/apple/apple_drv.c +++ b/drivers/gpu/drm/apple/apple_drv.c @@ -405,6 +405,8 @@ static int apple_platform_probe(struct platform_device *pdev) if (IS_ERR(apple)) return PTR_ERR(apple); + dev_set_drvdata(dev, apple); + ret = drm_vblank_init(&apple->drm, nr_dcp); if (ret) return ret; @@ -467,9 +469,9 @@ static int apple_platform_probe(struct platform_device *pdev) static int apple_platform_remove(struct platform_device *pdev) { - struct drm_device *drm = platform_get_drvdata(pdev); + struct apple_drm_private *apple = platform_get_drvdata(pdev); - drm_dev_unregister(drm); + drm_dev_unregister(&apple->drm); return 0; } @@ -480,10 +482,35 @@ static const struct of_device_id of_match[] = { }; MODULE_DEVICE_TABLE(of, of_match); +#ifdef CONFIG_PM_SLEEP +static int apple_platform_suspend(struct device *dev) +{ + struct apple_drm_private *apple = dev_get_drvdata(dev); + + return drm_mode_config_helper_suspend(&apple->drm); +} + +static int apple_platform_resume(struct device *dev) +{ + struct apple_drm_private *apple = dev_get_drvdata(dev); + + drm_mode_config_helper_resume(&apple->drm); + return 0; +} + +static const struct dev_pm_ops apple_platform_pm_ops = { + .suspend = apple_platform_suspend, + .resume = apple_platform_resume, +}; +#endif + static struct platform_driver apple_platform_driver = { .driver = { .name = "apple-drm", .of_match_table = of_match, +#ifdef CONFIG_PM_SLEEP + .pm = &apple_platform_pm_ops, +#endif }, .probe = apple_platform_probe, .remove = apple_platform_remove, diff --git a/drivers/gpu/drm/apple/dcp.c b/drivers/gpu/drm/apple/dcp.c index ec46503857009e..5b5ee40750961e 100644 --- a/drivers/gpu/drm/apple/dcp.c +++ b/drivers/gpu/drm/apple/dcp.c @@ -414,39 +414,12 @@ static const struct of_device_id of_match[] = { }; MODULE_DEVICE_TABLE(of, of_match); -#ifdef CONFIG_PM_SLEEP -/* - * We don't hold any useful persistent state, so for suspend/resume it suffices - * to power off/on the entire DCP. The firmware will sort out the details for - * us. - */ -static int dcp_suspend(struct device *dev) -{ - dcp_poweroff(to_platform_device(dev)); - return 0; -} - -static int dcp_resume(struct device *dev) -{ - dcp_poweron(to_platform_device(dev)); - return 0; -} - -static const struct dev_pm_ops dcp_pm_ops = { - .suspend = dcp_suspend, - .resume = dcp_resume, -}; -#endif - static struct platform_driver apple_platform_driver = { .probe = dcp_platform_probe, .shutdown = dcp_platform_shutdown, .driver = { .name = "apple-dcp", .of_match_table = of_match, -#ifdef CONFIG_PM_SLEEP - .pm = &dcp_pm_ops, -#endif }, }; From 965abb816a8fd9040a6b3031d9f1465bde8535b2 Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Sun, 20 Nov 2022 09:20:14 +0100 Subject: [PATCH 1038/3902] drm: apple: Avoid drm_fb_dma_get_gem_addr It adjust the address by the source position duplicating setting the source postion in IOMFB's swap_submit struct. Prefer the later since it is more explicit. Signed-off-by: Janne Grunau --- drivers/gpu/drm/apple/iomfb.c | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/drivers/gpu/drm/apple/iomfb.c b/drivers/gpu/drm/apple/iomfb.c index ca29658cfc8e99..0b31b8a0ee7bd7 100644 --- a/drivers/gpu/drm/apple/iomfb.c +++ b/drivers/gpu/drm/apple/iomfb.c @@ -19,6 +19,7 @@ #include #include #include +#include #include #include @@ -1568,6 +1569,7 @@ void dcp_flush(struct drm_crtc *crtc, struct drm_atomic_state *state) l = 0; for_each_oldnew_plane_in_state(state, plane, old_state, new_state, plane_idx) { struct drm_framebuffer *fb = new_state->fb; + struct drm_gem_dma_object *obj; struct drm_rect src_rect; bool opaque = false; @@ -1620,7 +1622,13 @@ void dcp_flush(struct drm_crtc *crtc, struct drm_atomic_state *state) if (dcp->notch_height > 0) req->swap.dst_rect[l].y += dcp->notch_height; - req->surf_iova[l] = drm_fb_dma_get_gem_addr(fb, new_state, 0); + /* the obvious helper call drm_fb_dma_get_gem_addr() adjusts + * the address for source x/y offsets. Since IOMFB has a direct + * support source position prefer that. + */ + obj = drm_fb_dma_get_gem_obj(fb, 0); + if (obj) + req->surf_iova[l] = obj->dma_addr + fb->offsets[0]; req->surf[l] = (struct dcp_surface){ .opaque = opaque, From 1585ec25b7145474db9e176218c00fa16fd4689d Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Wed, 23 Nov 2022 00:19:11 +0100 Subject: [PATCH 1039/3902] drm/apple: register backlight device after IOMFB start This allows us to specify the boot display brightness as initial brightness of the baclight device. Signed-off-by: Janne Grunau --- drivers/gpu/drm/apple/dcp-internal.h | 8 +++- drivers/gpu/drm/apple/dcp.c | 37 +++++++++++++--- drivers/gpu/drm/apple/dcp_backlight.c | 61 +++++++++------------------ drivers/gpu/drm/apple/iomfb.c | 9 ++-- 4 files changed, 64 insertions(+), 51 deletions(-) diff --git a/drivers/gpu/drm/apple/dcp-internal.h b/drivers/gpu/drm/apple/dcp-internal.h index c2fa002b45d5de..9f32fd9d0182ed 100644 --- a/drivers/gpu/drm/apple/dcp-internal.h +++ b/drivers/gpu/drm/apple/dcp-internal.h @@ -4,7 +4,9 @@ #ifndef __APPLE_DCP_INTERNAL_H__ #define __APPLE_DCP_INTERNAL_H__ +#include #include +#include #include #include @@ -65,9 +67,10 @@ struct dcp_fb_reference { #define MAX_NOTCH_HEIGHT 160 struct dcp_brightness { + struct backlight_device *bl_dev; + u32 maximum; u32 dac; int nits; - int set; int scale; bool update; }; @@ -153,6 +156,9 @@ struct apple_dcp { struct list_head swapped_out_fbs; struct dcp_brightness brightness; + /* Workqueue for updating the initial initial brightness */ + struct work_struct bl_register_wq; + struct mutex bl_register_mutex; }; int dcp_backlight_register(struct apple_dcp *dcp); diff --git a/drivers/gpu/drm/apple/dcp.c b/drivers/gpu/drm/apple/dcp.c index 5b5ee40750961e..0538f2b03969d0 100644 --- a/drivers/gpu/drm/apple/dcp.c +++ b/drivers/gpu/drm/apple/dcp.c @@ -12,6 +12,7 @@ #include #include #include +#include "linux/workqueue.h" #include #include @@ -252,6 +253,28 @@ int dcp_start(struct platform_device *pdev) } EXPORT_SYMBOL(dcp_start); +static void dcp_work_register_backlight(struct work_struct *work) +{ + int ret; + struct apple_dcp *dcp; + + dcp = container_of(work, struct apple_dcp, bl_register_wq); + + mutex_lock(&dcp->bl_register_mutex); + if (dcp->brightness.bl_dev) + goto out_unlock; + + /* try to register backlight device, */ + ret = dcp_backlight_register(dcp); + if (ret) { + dev_err(dcp->dev, "Unable to register backlight device\n"); + dcp->brightness.maximum = 0; + } + +out_unlock: + mutex_unlock(&dcp->bl_register_mutex); +} + static struct platform_device *dcp_get_dev(struct device *dev, const char *name) { struct platform_device *pdev; @@ -312,14 +335,16 @@ static int dcp_platform_probe(struct platform_device *pdev) dcp->brightness.scale = 65536; panel_np = of_get_compatible_child(dev->of_node, "apple,panel"); if (panel_np) { + if (of_device_is_available(panel_np)) { + ret = of_property_read_u32(panel_np, "apple,max-brightness", + &dcp->brightness.maximum); + if (ret) + dev_err(dev, "Missing property 'apple,max-brightness'\n"); + } of_node_put(panel_np); dcp->connector_type = DRM_MODE_CONNECTOR_eDP; - - /* try to register backlight device, */ - ret = dcp_backlight_register(dcp); - if (ret) - return dev_err_probe(dev, ret, - "Unable to register backlight device\n"); + INIT_WORK(&dcp->bl_register_wq, dcp_work_register_backlight); + mutex_init(&dcp->bl_register_mutex); } else if (of_property_match_string(dev->of_node, "apple,connector-type", "HDMI-A") >= 0) dcp->connector_type = DRM_MODE_CONNECTOR_HDMIA; else if (of_property_match_string(dev->of_node, "apple,connector-type", "DP") >= 0) diff --git a/drivers/gpu/drm/apple/dcp_backlight.c b/drivers/gpu/drm/apple/dcp_backlight.c index c695e3bad7db91..5b4a41c53ca21b 100644 --- a/drivers/gpu/drm/apple/dcp_backlight.c +++ b/drivers/gpu/drm/apple/dcp_backlight.c @@ -165,29 +165,28 @@ static int drm_crtc_set_brightness(struct drm_crtc *crtc, static int dcp_set_brightness(struct backlight_device *bd) { - int ret = 0; + int ret; struct apple_dcp *dcp = bl_get_data(bd); + struct drm_modeset_acquire_ctx ctx; - bd->props.power = FB_BLANK_UNBLANK; - if (dcp->brightness.set != bd->props.brightness) { - dcp->brightness.dac = calculate_dac(dcp, bd->props.brightness); - dcp->brightness.set = bd->props.brightness; - dcp->brightness.update = true; - } + if (bd->props.state & BL_CORE_SUSPENDED) + return 0; - if (dcp->brightness.update && dcp->crtc) { - struct drm_modeset_acquire_ctx ctx; - struct drm_device *drm_dev = dcp->crtc->base.dev; + if (!dcp->crtc) + return -EAGAIN; - DRM_MODESET_LOCK_ALL_BEGIN(drm_dev, ctx, 0, ret); - ret = drm_crtc_set_brightness(&dcp->crtc->base, &ctx); - DRM_MODESET_LOCK_ALL_END(drm_dev, ctx, ret); - } + dcp->brightness.dac = calculate_dac(dcp, bd->props.brightness); + dcp->brightness.update = true; + + DRM_MODESET_LOCK_ALL_BEGIN(dcp->crtc->base.dev, ctx, 0, ret); + ret = drm_crtc_set_brightness(&dcp->crtc->base, &ctx); + DRM_MODESET_LOCK_ALL_END(dcp->crtc->base.dev, ctx, ret); return ret; } static const struct backlight_ops dcp_backlight_ops = { + .options = BL_CORE_SUSPENDRESUME, .get_brightness = dcp_get_brightness, .update_status = dcp_set_brightness, }; @@ -195,38 +194,20 @@ static const struct backlight_ops dcp_backlight_ops = { int dcp_backlight_register(struct apple_dcp *dcp) { struct device *dev = dcp->dev; - struct backlight_device *bd; - struct device_node *panel_np; + struct backlight_device *bl_dev; struct backlight_properties props = { .type = BACKLIGHT_PLATFORM, .brightness = dcp->brightness.nits, - .max_brightness = 0, .scale = BACKLIGHT_SCALE_LINEAR, }; - u32 max_brightness; - int ret = 0; + props.max_brightness = min(dcp->brightness.maximum, MAX_BRIGHTNESS_PART2 - 1); - panel_np = of_get_compatible_child(dev->of_node, "apple,panel"); - if (!panel_np) - return 0; - - if (!of_device_is_available(panel_np)) - goto out_put; - - ret = of_property_read_u32(panel_np, "apple,max-brightness", &max_brightness); - if (ret) { - dev_err(dev, "Missing property 'apple,max-brightness'\n"); - goto out_put; - } - props.max_brightness = min(max_brightness, MAX_BRIGHTNESS_PART2 - 1); - - bd = devm_backlight_device_register(dev, "apple-panel-bl", dev, dcp, - &dcp_backlight_ops, &props); - if (IS_ERR(bd)) - ret = PTR_ERR(bd); + bl_dev = devm_backlight_device_register(dev, "apple-panel-bl", dev, dcp, + &dcp_backlight_ops, &props); + if (IS_ERR(bl_dev)) + return PTR_ERR(bl_dev); -out_put: - of_node_put(panel_np); + dcp->brightness.bl_dev = bl_dev; - return ret; + return 0; } diff --git a/drivers/gpu/drm/apple/iomfb.c b/drivers/gpu/drm/apple/iomfb.c index 0b31b8a0ee7bd7..735addf784fd8a 100644 --- a/drivers/gpu/drm/apple/iomfb.c +++ b/drivers/gpu/drm/apple/iomfb.c @@ -428,12 +428,13 @@ static void iomfb_cb_pr_publish(struct apple_dcp *dcp, struct iomfb_property *pr { switch (prop->id) { case IOMFB_PROPERTY_NITS: + { dcp->brightness.nits = prop->value / dcp->brightness.scale; - /* temporary for user debugging during tesing */ - dev_info(dcp->dev, "Backlight updated to %u nits\n", - dcp->brightness.nits); - dcp->brightness.update = false; + /* notify backlight device of the initial brightness */ + if (!dcp->brightness.bl_dev && dcp->brightness.maximum > 0) + schedule_work(&dcp->bl_register_wq); break; + } default: dev_dbg(dcp->dev, "pr_publish: id: %d = %u\n", prop->id, prop->value); } From f79cba79512a61d5461cfd7c22ab7a279b417ea1 Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Wed, 23 Nov 2022 00:27:38 +0100 Subject: [PATCH 1040/3902] drm/apple: Add trace point for display brightness Signed-off-by: Janne Grunau --- drivers/gpu/drm/apple/iomfb.c | 1 + drivers/gpu/drm/apple/trace.h | 18 ++++++++++++++++++ 2 files changed, 19 insertions(+) diff --git a/drivers/gpu/drm/apple/iomfb.c b/drivers/gpu/drm/apple/iomfb.c index 735addf784fd8a..9effd013729676 100644 --- a/drivers/gpu/drm/apple/iomfb.c +++ b/drivers/gpu/drm/apple/iomfb.c @@ -433,6 +433,7 @@ static void iomfb_cb_pr_publish(struct apple_dcp *dcp, struct iomfb_property *pr /* notify backlight device of the initial brightness */ if (!dcp->brightness.bl_dev && dcp->brightness.maximum > 0) schedule_work(&dcp->bl_register_wq); + trace_iomfb_brightness(dcp, prop->value); break; } default: diff --git a/drivers/gpu/drm/apple/trace.h b/drivers/gpu/drm/apple/trace.h index 25a23c1586e259..c7b5dee11ed07d 100644 --- a/drivers/gpu/drm/apple/trace.h +++ b/drivers/gpu/drm/apple/trace.h @@ -153,6 +153,24 @@ TRACE_EVENT(iomfb_swap_complete_intent_gated, ) ); +TRACE_EVENT(iomfb_brightness, + TP_PROTO(struct apple_dcp *dcp, u32 nits), + TP_ARGS(dcp, nits), + TP_STRUCT__entry( + __field(u64, dcp) + __field(u32, nits) + ), + TP_fast_assign( + __entry->dcp = (u64)dcp; + __entry->nits = nits; + ), + TP_printk("dcp=%llx, nits=%u (raw=0x%05x)", + __entry->dcp, + __entry->nits >> 16, + __entry->nits + ) +); + #endif /* _TRACE_DCP_H */ /* This part must be outside protection */ From 988f59c99542454d4b8b30464c48f12842e0d97f Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Wed, 23 Nov 2022 23:21:54 +0100 Subject: [PATCH 1041/3902] drm/apple: Implement drm_crtc_helper_funcs.mode_fixup Prevents KDE for now to set modes not reported by IOMFB. Seen on j493 with the common display resolution of 2560x1600. Signed-off-by: Janne Grunau --- drivers/gpu/drm/apple/apple_drv.c | 1 + drivers/gpu/drm/apple/dcp.h | 3 +++ drivers/gpu/drm/apple/iomfb.c | 15 ++++++++++++++- 3 files changed, 18 insertions(+), 1 deletion(-) diff --git a/drivers/gpu/drm/apple/apple_drv.c b/drivers/gpu/drm/apple/apple_drv.c index 2a3fa61bbb3b2f..47503192e86242 100644 --- a/drivers/gpu/drm/apple/apple_drv.c +++ b/drivers/gpu/drm/apple/apple_drv.c @@ -306,6 +306,7 @@ static const struct drm_crtc_helper_funcs apple_crtc_helper_funcs = { .atomic_flush = dcp_flush, .atomic_enable = apple_crtc_atomic_enable, .atomic_disable = apple_crtc_atomic_disable, + .mode_fixup = dcp_crtc_mode_fixup, }; static int apple_probe_per_dcp(struct device *dev, diff --git a/drivers/gpu/drm/apple/dcp.h b/drivers/gpu/drm/apple/dcp.h index dfe014f3f5d1da..fb4397e7b390fe 100644 --- a/drivers/gpu/drm/apple/dcp.h +++ b/drivers/gpu/drm/apple/dcp.h @@ -49,6 +49,9 @@ void dcp_drm_crtc_vblank(struct apple_crtc *crtc); int dcp_get_modes(struct drm_connector *connector); int dcp_mode_valid(struct drm_connector *connector, struct drm_display_mode *mode); +bool dcp_crtc_mode_fixup(struct drm_crtc *crtc, + const struct drm_display_mode *mode, + struct drm_display_mode *adjusted_mode); void dcp_set_dimensions(struct apple_dcp *dcp); void dcp_send_message(struct apple_dcp *dcp, u8 endpoint, u64 message); diff --git a/drivers/gpu/drm/apple/iomfb.c b/drivers/gpu/drm/apple/iomfb.c index 9effd013729676..37ea896122ccd8 100644 --- a/drivers/gpu/drm/apple/iomfb.c +++ b/drivers/gpu/drm/apple/iomfb.c @@ -1485,7 +1485,7 @@ EXPORT_SYMBOL_GPL(dcp_get_modes); /* The user may own drm_display_mode, so we need to search for our copy */ static struct dcp_display_mode *lookup_mode(struct apple_dcp *dcp, - struct drm_display_mode *mode) + const struct drm_display_mode *mode) { int i; @@ -1510,6 +1510,19 @@ int dcp_mode_valid(struct drm_connector *connector, } EXPORT_SYMBOL_GPL(dcp_mode_valid); +bool dcp_crtc_mode_fixup(struct drm_crtc *crtc, + const struct drm_display_mode *mode, + struct drm_display_mode *adjusted_mode) +{ + struct apple_crtc *apple_crtc = to_apple_crtc(crtc); + struct platform_device *pdev = apple_crtc->dcp; + struct apple_dcp *dcp = platform_get_drvdata(pdev); + + /* TODO: support synthesized modes through scaling */ + return lookup_mode(dcp, mode) != NULL; +} +EXPORT_SYMBOL(dcp_crtc_mode_fixup); + /* Helpers to modeset and swap, used to flush */ static void do_swap(struct apple_dcp *dcp, void *data, void *cookie) { From 9e2c2989aa3ee57e1af353f4e0e817eed77c6790 Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Thu, 24 Nov 2022 21:44:36 +0100 Subject: [PATCH 1042/3902] drm/apple: Read display dimensions from devicetree Signed-off-by: Janne Grunau --- drivers/gpu/drm/apple/dcp.c | 22 +++++++++++++++------- drivers/gpu/drm/apple/iomfb.c | 15 ++++++++++----- 2 files changed, 25 insertions(+), 12 deletions(-) diff --git a/drivers/gpu/drm/apple/dcp.c b/drivers/gpu/drm/apple/dcp.c index 0538f2b03969d0..ce0a395129e750 100644 --- a/drivers/gpu/drm/apple/dcp.c +++ b/drivers/gpu/drm/apple/dcp.c @@ -331,16 +331,31 @@ static int dcp_platform_probe(struct platform_device *pdev) of_platform_default_populate(dev->of_node, NULL, dev); + ret = of_property_read_u32(dev->of_node, "apple,notch-height", + &dcp->notch_height); + if (dcp->notch_height > MAX_NOTCH_HEIGHT) + dcp->notch_height = MAX_NOTCH_HEIGHT; + if (dcp->notch_height > 0) + dev_info(dev, "Detected display with notch of %u pixel\n", dcp->notch_height); + /* intialize brightness scale to a sensible default to avoid divide by 0*/ dcp->brightness.scale = 65536; panel_np = of_get_compatible_child(dev->of_node, "apple,panel"); if (panel_np) { + const char height_prop[2][16] = { "adj-height-mm", "height-mm" }; + if (of_device_is_available(panel_np)) { ret = of_property_read_u32(panel_np, "apple,max-brightness", &dcp->brightness.maximum); if (ret) dev_err(dev, "Missing property 'apple,max-brightness'\n"); } + + of_property_read_u32(panel_np, "width-mm", &dcp->width_mm); + /* use adjusted height as long as the notch is hidden */ + of_property_read_u32(panel_np, height_prop[!dcp->notch_height], + &dcp->height_mm); + of_node_put(panel_np); dcp->connector_type = DRM_MODE_CONNECTOR_eDP; INIT_WORK(&dcp->bl_register_wq, dcp_work_register_backlight); @@ -387,13 +402,6 @@ static int dcp_platform_probe(struct platform_device *pdev) dev_warn(dev, "failed read 'apple,asc-dram-mask': %d\n", ret); dev_dbg(dev, "'apple,asc-dram-mask': 0x%011llx\n", dcp->asc_dram_mask); - ret = of_property_read_u32(dev->of_node, "apple,notch-height", - &dcp->notch_height); - if (dcp->notch_height > MAX_NOTCH_HEIGHT) - dcp->notch_height = MAX_NOTCH_HEIGHT; - if (dcp->notch_height > 0) - dev_info(dev, "Detected display with notch of %u pixel\n", dcp->notch_height); - bitmap_zero(dcp->memdesc_map, DCP_MAX_MAPPINGS); // TDOD: mem_desc IDs start at 1, for simplicity just skip '0' entry set_bit(0, dcp->memdesc_map); diff --git a/drivers/gpu/drm/apple/iomfb.c b/drivers/gpu/drm/apple/iomfb.c index 37ea896122ccd8..1892dc55ae4281 100644 --- a/drivers/gpu/drm/apple/iomfb.c +++ b/drivers/gpu/drm/apple/iomfb.c @@ -764,12 +764,17 @@ static bool dcpep_process_chunks(struct apple_dcp *dcp, return false; } } else if (!strcmp(req->key, "DisplayAttributes")) { - ret = parse_display_attributes(&ctx, &dcp->width_mm, - &dcp->height_mm); + /* DisplayAttributes are empty for integrated displays, use + * display dimensions read from the devicetree + */ + if (dcp->main_display) { + ret = parse_display_attributes(&ctx, &dcp->width_mm, + &dcp->height_mm); - if (ret) { - dev_warn(dcp->dev, "failed to parse display attribs\n"); - return false; + if (ret) { + dev_warn(dcp->dev, "failed to parse display attribs\n"); + return false; + } } dcp_set_dimensions(dcp); From f608c0276aeb915e765dce4c193ada335b537330 Mon Sep 17 00:00:00 2001 From: Asahi Lina Date: Mon, 28 Nov 2022 00:42:25 +0900 Subject: [PATCH 1043/3902] drm/apple: Wait for power on request to complete synchronously Signed-off-by: Asahi Lina --- drivers/gpu/drm/apple/iomfb.c | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/drivers/gpu/drm/apple/iomfb.c b/drivers/gpu/drm/apple/iomfb.c index 1892dc55ae4281..361c597b36f966 100644 --- a/drivers/gpu/drm/apple/iomfb.c +++ b/drivers/gpu/drm/apple/iomfb.c @@ -942,6 +942,16 @@ static void dcp_on_final(struct apple_dcp *dcp, void *out, void *cookie) } } +static void dcp_on_set_power_state(struct apple_dcp *dcp, void *out, void *cookie) +{ + struct dcp_set_power_state_req req = { + .unklong = 1, + }; + dev_dbg(dcp->dev, "%s", __func__); + + dcp_set_power_state(dcp, false, &req, dcp_on_final, cookie); +} + static void dcp_on_set_parameter(struct apple_dcp *dcp, void *out, void *cookie) { struct dcp_set_parameter_dcp param = { @@ -951,16 +961,13 @@ static void dcp_on_set_parameter(struct apple_dcp *dcp, void *out, void *cookie) }; dev_dbg(dcp->dev, "%s", __func__); - dcp_set_parameter_dcp(dcp, false, ¶m, dcp_on_final, cookie); + dcp_set_parameter_dcp(dcp, false, ¶m, dcp_on_set_power_state, cookie); } void dcp_poweron(struct platform_device *pdev) { struct apple_dcp *dcp = platform_get_drvdata(pdev); struct dcp_wait_cookie *cookie; - struct dcp_set_power_state_req req = { - .unklong = 1, - }; int ret; u32 handle; dev_dbg(dcp->dev, "%s", __func__); @@ -976,15 +983,13 @@ void dcp_poweron(struct platform_device *pdev) if (dcp->main_display) { handle = 0; - dcp_set_display_device(dcp, false, &handle, dcp_on_final, + dcp_set_display_device(dcp, false, &handle, dcp_on_set_power_state, cookie); } else { handle = 2; dcp_set_display_device(dcp, false, &handle, dcp_on_set_parameter, cookie); } - dcp_set_power_state(dcp, true, &req, NULL, NULL); - ret = wait_for_completion_timeout(&cookie->done, msecs_to_jiffies(500)); if (ret == 0) From de4c0a6bf08ba81e7758964e176fd06841beb47c Mon Sep 17 00:00:00 2001 From: Asahi Lina Date: Mon, 28 Nov 2022 00:44:41 +0900 Subject: [PATCH 1044/3902] drm/apple: Remove obsolete ignore_swap_complete Signed-off-by: Asahi Lina --- drivers/gpu/drm/apple/dcp-internal.h | 2 -- drivers/gpu/drm/apple/iomfb.c | 5 +---- 2 files changed, 1 insertion(+), 6 deletions(-) diff --git a/drivers/gpu/drm/apple/dcp-internal.h b/drivers/gpu/drm/apple/dcp-internal.h index 9f32fd9d0182ed..24db9f39d78e2a 100644 --- a/drivers/gpu/drm/apple/dcp-internal.h +++ b/drivers/gpu/drm/apple/dcp-internal.h @@ -133,8 +133,6 @@ struct apple_dcp { /* eDP display without DP-HDMI conversion */ bool main_display; - bool ignore_swap_complete; - /* Modes valid for the connected display */ struct dcp_display_mode *modes; unsigned int nr_modes; diff --git a/drivers/gpu/drm/apple/iomfb.c b/drivers/gpu/drm/apple/iomfb.c index 361c597b36f966..e0687117e898d4 100644 --- a/drivers/gpu/drm/apple/iomfb.c +++ b/drivers/gpu/drm/apple/iomfb.c @@ -359,8 +359,7 @@ static void dcpep_cb_swap_complete(struct apple_dcp *dcp, { trace_iomfb_swap_complete(dcp, resp->swap_id); - if (!dcp->ignore_swap_complete) - dcp_drm_crtc_vblank(dcp->crtc); + dcp_drm_crtc_vblank(dcp->crtc); } /* special */ @@ -1551,8 +1550,6 @@ static void complete_set_digital_out_mode(struct apple_dcp *dcp, void *data, struct dcp_wait_cookie *wait = cookie; dev_dbg(dcp->dev, "%s", __func__); - dcp->ignore_swap_complete = false; - if (wait) { complete(&wait->done); kref_put(&wait->refcount, release_wait_cookie); From b814b275a52f155189303caedf29e2cf62298b61 Mon Sep 17 00:00:00 2001 From: Asahi Lina Date: Mon, 28 Nov 2022 01:02:24 +0900 Subject: [PATCH 1045/3902] drm/asahi: Fix backlight restores on non-microLED devices Apparently what happens here is that the DCP's idea of backlight brightness is desynced with the real brightness across power cycles. This means that even if we just force an update after a power cycle, it doesn't work since it considers it unchanged. To fix this, we need to both force an update on poweron and also explicitly turn the backlight off on poweroff, which makes DCP listen to us and actually update the backlight state properly. Signed-off-by: Asahi Lina --- drivers/gpu/drm/apple/dcp_backlight.c | 1 + drivers/gpu/drm/apple/iomfb.c | 31 ++++++++++++++++++++------- 2 files changed, 24 insertions(+), 8 deletions(-) diff --git a/drivers/gpu/drm/apple/dcp_backlight.c b/drivers/gpu/drm/apple/dcp_backlight.c index 5b4a41c53ca21b..42b1097eaa0180 100644 --- a/drivers/gpu/drm/apple/dcp_backlight.c +++ b/drivers/gpu/drm/apple/dcp_backlight.c @@ -208,6 +208,7 @@ int dcp_backlight_register(struct apple_dcp *dcp) return PTR_ERR(bl_dev); dcp->brightness.bl_dev = bl_dev; + dcp->brightness.dac = calculate_dac(dcp, dcp->brightness.nits); return 0; } diff --git a/drivers/gpu/drm/apple/iomfb.c b/drivers/gpu/drm/apple/iomfb.c index e0687117e898d4..2f2fb3e4b5d26b 100644 --- a/drivers/gpu/drm/apple/iomfb.c +++ b/drivers/gpu/drm/apple/iomfb.c @@ -995,6 +995,9 @@ void dcp_poweron(struct platform_device *pdev) dev_warn(dcp->dev, "wait for power timed out"); kref_put(&cookie->refcount, release_wait_cookie);; + + /* Force a brightness update after poweron, to restore the brightness */ + dcp->brightness.update = true; } EXPORT_SYMBOL(dcp_poweron); @@ -1037,6 +1040,17 @@ void dcp_poweroff(struct platform_device *pdev) dcp->swap.swap.swap_completed = DCP_REMOVE_LAYERS | 0x7; dcp->swap.swap.unk_10c = 0xFF000000; + /* + * Turn off the backlight. This matters because the DCP's idea of + * backlight brightness gets desynced after a power change, and it + * needs to be told it's going to turn off so it will consider the + * subsequent update on poweron an actual change and restore the + * brightness. + */ + dcp->swap.swap.bl_unk = 1; + dcp->swap.swap.bl_value = 0; + dcp->swap.swap.bl_power = 0; + for (int l = 0; l < SWAP_SURFACES; l++) dcp->swap.surf_null[l] = true; @@ -1677,14 +1691,6 @@ void dcp_flush(struct drm_crtc *crtc, struct drm_atomic_state *state) /* These fields should be set together */ req->swap.swap_completed = req->swap.swap_enabled; - /* update brightness if changed */ - if (dcp->brightness.update) { - req->swap.bl_unk = 1; - req->swap.bl_value = dcp->brightness.dac; - req->swap.bl_power = 0x40; - dcp->brightness.update = false; - } - if (modeset) { struct dcp_display_mode *mode; struct dcp_wait_cookie *cookie; @@ -1747,6 +1753,15 @@ void dcp_flush(struct drm_crtc *crtc, struct drm_atomic_state *state) req->clear = 1; } + + /* update brightness if changed */ + if (dcp->brightness.update) { + req->swap.bl_unk = 1; + req->swap.bl_value = dcp->brightness.dac; + req->swap.bl_power = 0x40; + dcp->brightness.update = false; + } + do_swap(dcp, NULL, NULL); } EXPORT_SYMBOL_GPL(dcp_flush); From 150ed1ca6251172b5266495c770eadf538288990 Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Sun, 27 Nov 2022 21:48:44 +0100 Subject: [PATCH 1046/3902] drm/apple: Schedule backlight update on enable_backlight_message_ap_gated On non mini-LED displays the backlight comes out of power-off (DPMS) with minimal backlight brightness. This seems to be a DCP firmware issue. It logs "[BrightnessLCD.cpp:743][AFK]nitsToDBV: iDAC out of range" to syslog although the brightness in the swap_submit call is valid. This fixes the issue only for clients using swap. For other clients an atomic backlight update has to be scheduled via a work queue. Signed-off-by: Janne Grunau --- drivers/gpu/drm/apple/iomfb.c | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/drivers/gpu/drm/apple/iomfb.c b/drivers/gpu/drm/apple/iomfb.c index 2f2fb3e4b5d26b..2275afb2ada926 100644 --- a/drivers/gpu/drm/apple/iomfb.c +++ b/drivers/gpu/drm/apple/iomfb.c @@ -697,6 +697,17 @@ dcpep_cb_read_edt_data(struct apple_dcp *dcp, struct dcp_read_edt_data_req *req) }; } +static void iomfbep_cb_enable_backlight_message_ap_gated(struct apple_dcp *dcp, + u8 *enabled) +{ + /* + * update backlight brightness on next swap, on non mini-LED displays + * DCP seems to set an invalid iDAC value after coming out of DPMS. + * syslog: "[BrightnessLCD.cpp:743][AFK]nitsToDBV: iDAC out of range" + */ + dcp->brightness.update = true; +} + /* Chunked data transfer for property dictionaries */ static u8 dcpep_cb_prop_start(struct apple_dcp *dcp, u32 *length) { @@ -1252,6 +1263,8 @@ TRAMPOLINE_IN(trampoline_hotplug, dcpep_cb_hotplug, u64); TRAMPOLINE_IN(trampoline_swap_complete_intent_gated, dcpep_cb_swap_complete_intent_gated, struct dcp_swap_complete_intent_gated); +TRAMPOLINE_IN(trampoline_enable_backlight_message_ap_gated, + iomfbep_cb_enable_backlight_message_ap_gated, u8); TRAMPOLINE_IN(trampoline_pr_publish, iomfb_cb_pr_publish, struct iomfb_property); @@ -1307,7 +1320,7 @@ bool (*const dcpep_cb_handlers[DCPEP_MAX_CB])(struct apple_dcp *, int, void *, [582] = trampoline_true, /* create_default_fb_surface */ [589] = trampoline_swap_complete, [591] = trampoline_swap_complete_intent_gated, - [593] = trampoline_nop, /* enable_backlight_message_ap_gated */ + [593] = trampoline_enable_backlight_message_ap_gated, [598] = trampoline_nop, /* find_swap_function_gated */ }; From e9f534a45bfa7bf04fb5bb53217f34cb6106a401 Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Sun, 27 Nov 2022 22:45:22 +0100 Subject: [PATCH 1047/3902] drm/apple: Report "PMUS.Temperature" only for mini-LED backlights Signed-off-by: Janne Grunau --- drivers/gpu/drm/apple/dcp-internal.h | 3 +++ drivers/gpu/drm/apple/dcp.c | 7 ++++++- drivers/gpu/drm/apple/iomfb.c | 3 ++- 3 files changed, 11 insertions(+), 2 deletions(-) diff --git a/drivers/gpu/drm/apple/dcp-internal.h b/drivers/gpu/drm/apple/dcp-internal.h index 24db9f39d78e2a..7fe6490509f754 100644 --- a/drivers/gpu/drm/apple/dcp-internal.h +++ b/drivers/gpu/drm/apple/dcp-internal.h @@ -133,6 +133,9 @@ struct apple_dcp { /* eDP display without DP-HDMI conversion */ bool main_display; + /* panel has a mini-LED backllight */ + bool has_mini_led; + /* Modes valid for the connected display */ struct dcp_display_mode *modes; unsigned int nr_modes; diff --git a/drivers/gpu/drm/apple/dcp.c b/drivers/gpu/drm/apple/dcp.c index ce0a395129e750..2909475238adf4 100644 --- a/drivers/gpu/drm/apple/dcp.c +++ b/drivers/gpu/drm/apple/dcp.c @@ -340,7 +340,12 @@ static int dcp_platform_probe(struct platform_device *pdev) /* intialize brightness scale to a sensible default to avoid divide by 0*/ dcp->brightness.scale = 65536; - panel_np = of_get_compatible_child(dev->of_node, "apple,panel"); + panel_np = of_get_compatible_child(dev->of_node, "apple,panel-mini-led"); + if (panel_np) + dcp->has_mini_led = true; + else + panel_np = of_get_compatible_child(dev->of_node, "apple,panel"); + if (panel_np) { const char height_prop[2][16] = { "adj-height-mm", "height-mm" }; diff --git a/drivers/gpu/drm/apple/iomfb.c b/drivers/gpu/drm/apple/iomfb.c index 2275afb2ada926..f7da26c391d532 100644 --- a/drivers/gpu/drm/apple/iomfb.c +++ b/drivers/gpu/drm/apple/iomfb.c @@ -447,7 +447,8 @@ dcpep_cb_get_uint_prop(struct apple_dcp *dcp, struct dcp_get_uint_prop_req *req) .value = 0 }; - if (memcmp(req->obj, "SUMP", sizeof(req->obj)) == 0) { /* "PMUS */ + if (dcp->has_mini_led && + memcmp(req->obj, "SUMP", sizeof(req->obj)) == 0) { /* "PMUS */ if (strncmp(req->key, "Temperature", sizeof(req->key)) == 0) { /* * TODO: value from j314c, find out if it is temperature in From 787db5bf05bf1aa01fa926019b852f979d93a771 Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Sun, 4 Dec 2022 11:36:05 +0100 Subject: [PATCH 1048/3902] drm/apple: Check if DCP firmware is supported Signed-off-by: Janne Grunau --- drivers/gpu/drm/apple/dcp-internal.h | 8 +++ drivers/gpu/drm/apple/dcp.c | 77 ++++++++++++++++++++++++++++ 2 files changed, 85 insertions(+) diff --git a/drivers/gpu/drm/apple/dcp-internal.h b/drivers/gpu/drm/apple/dcp-internal.h index 7fe6490509f754..18969ab18e8319 100644 --- a/drivers/gpu/drm/apple/dcp-internal.h +++ b/drivers/gpu/drm/apple/dcp-internal.h @@ -16,6 +16,11 @@ struct apple_dcp; +enum dcp_firmware_version { + DCP_FIRMWARE_UNKNOWN, + DCP_FIRMWARE_V_12_3, +}; + enum { SYSTEM_ENDPOINT = 0x20, TEST_ENDPOINT = 0x21, @@ -84,6 +89,9 @@ struct apple_dcp { struct apple_crtc *crtc; struct apple_connector *connector; + /* firmware version and compatible firmware version */ + enum dcp_firmware_version fw_compat; + /* Coprocessor control register */ void __iomem *coproc_reg; diff --git a/drivers/gpu/drm/apple/dcp.c b/drivers/gpu/drm/apple/dcp.c index 2909475238adf4..b72d971d12bed2 100644 --- a/drivers/gpu/drm/apple/dcp.c +++ b/drivers/gpu/drm/apple/dcp.c @@ -3,8 +3,10 @@ #include #include +#include #include #include +#include #include #include #include @@ -306,18 +308,93 @@ static int dcp_get_disp_regs(struct apple_dcp *dcp) return 0; } +#define DCP_FW_VERSION_MIN_LEN 3 +#define DCP_FW_VERSION_MAX_LEN 5 +#define DCP_FW_VERSION_STR_LEN (DCP_FW_VERSION_MAX_LEN * 4) + +static int dcp_read_fw_version(struct device *dev, const char *name, + char *version_str) +{ + u32 ver[DCP_FW_VERSION_MAX_LEN]; + int len_str; + int len; + + len = of_property_read_variable_u32_array(dev->of_node, name, ver, + DCP_FW_VERSION_MIN_LEN, + DCP_FW_VERSION_MAX_LEN); + + switch (len) { + case 3: + len_str = scnprintf(version_str, DCP_FW_VERSION_STR_LEN, + "%d.%d.%d", ver[0], ver[1], ver[2]); + break; + case 4: + len_str = scnprintf(version_str, DCP_FW_VERSION_STR_LEN, + "%d.%d.%d.%d", ver[0], ver[1], ver[2], + ver[3]); + break; + case 5: + len_str = scnprintf(version_str, DCP_FW_VERSION_STR_LEN, + "%d.%d.%d.%d.%d", ver[0], ver[1], ver[2], + ver[3], ver[4]); + break; + default: + len_str = strscpy(version_str, "UNKNOWN", + DCP_FW_VERSION_STR_LEN); + if (len >= 0) + len = -EOVERFLOW; + break; + } + + if (len_str >= DCP_FW_VERSION_STR_LEN) + dev_warn(dev, "'%s' truncated: '%s'\n", name, version_str); + + return len; +} + +static enum dcp_firmware_version dcp_check_firmware_version(struct device *dev) +{ + char compat_str[DCP_FW_VERSION_STR_LEN]; + char fw_str[DCP_FW_VERSION_STR_LEN]; + int ret; + + /* firmware version is just informative */ + dcp_read_fw_version(dev, "apple,firmware-version", fw_str); + + ret = dcp_read_fw_version(dev, "apple,firmware-compat", compat_str); + if (ret < 0) { + dev_err(dev, "Could not read 'apple,firmware-compat': %d\n", ret); + return DCP_FIRMWARE_UNKNOWN; + } + + if (strncmp(compat_str, "12.3.0", sizeof(compat_str)) == 0) + return DCP_FIRMWARE_V_12_3; + + dev_err(dev, "DCP firmware-compat %s (FW: %s) is not supported\n", + compat_str, fw_str); + + return DCP_FIRMWARE_UNKNOWN; +} + static int dcp_platform_probe(struct platform_device *pdev) { struct device *dev = &pdev->dev; struct device_node *panel_np; struct apple_dcp *dcp; + enum dcp_firmware_version fw_compat; u32 cpu_ctrl; int ret; + fw_compat = dcp_check_firmware_version(dev); + if (fw_compat == DCP_FIRMWARE_UNKNOWN) + return -ENODEV; + dcp = devm_kzalloc(dev, sizeof(*dcp), GFP_KERNEL); if (!dcp) return -ENOMEM; + dcp->fw_compat = fw_compat; + platform_set_drvdata(pdev, dcp); dcp->dev = dev; From d6e983f3e00c6e949d0c5161a120813b3f43cd57 Mon Sep 17 00:00:00 2001 From: Asahi Lina Date: Sun, 27 Nov 2022 19:32:55 +0900 Subject: [PATCH 1049/3902] drm/apple: Disable fake vblank IRQ machinery The hardware does not have a vblank IRQ and drm already knows how to deal with that appropriately, so don't pretend it does. Fixes Xorg. Signed-off-by: Asahi Lina --- drivers/gpu/drm/apple/apple_drv.c | 23 ----------------------- drivers/gpu/drm/apple/dcp.c | 6 ------ 2 files changed, 29 deletions(-) diff --git a/drivers/gpu/drm/apple/apple_drv.c b/drivers/gpu/drm/apple/apple_drv.c index 47503192e86242..24fc33dc4a3779 100644 --- a/drivers/gpu/drm/apple/apple_drv.c +++ b/drivers/gpu/drm/apple/apple_drv.c @@ -168,18 +168,6 @@ static struct drm_plane *apple_plane_init(struct drm_device *dev, return plane; } -static int apple_enable_vblank(struct drm_crtc *crtc) -{ - to_apple_crtc(crtc)->vsync_disabled = false; - - return 0; -} - -static void apple_disable_vblank(struct drm_crtc *crtc) -{ - to_apple_crtc(crtc)->vsync_disabled = true; -} - static enum drm_connector_status apple_connector_detect(struct drm_connector *connector, bool force) { @@ -201,7 +189,6 @@ static void apple_crtc_atomic_enable(struct drm_crtc *crtc, dcp_poweron(apple_crtc->dcp); dev_dbg(&apple_crtc->dcp->dev, "%s finished", __func__); } - drm_crtc_vblank_on(crtc); } static void apple_crtc_atomic_disable(struct drm_crtc *crtc, @@ -210,8 +197,6 @@ static void apple_crtc_atomic_disable(struct drm_crtc *crtc, struct drm_crtc_state *crtc_state; crtc_state = drm_atomic_get_new_crtc_state(state, crtc); - drm_crtc_vblank_off(crtc); - if (crtc_state->active_changed && !crtc_state->active) { struct apple_crtc *apple_crtc = to_apple_crtc(crtc); dev_dbg(&apple_crtc->dcp->dev, "%s", __func__); @@ -235,8 +220,6 @@ static void apple_crtc_atomic_begin(struct drm_crtc *crtc, unsigned long flags; if (crtc->state->event) { - WARN_ON(drm_crtc_vblank_get(crtc) != 0); - spin_lock_irqsave(&crtc->dev->event_lock, flags); apple_crtc->event = crtc->state->event; spin_unlock_irqrestore(&crtc->dev->event_lock, flags); @@ -272,8 +255,6 @@ static const struct drm_crtc_funcs apple_crtc_funcs = { .page_flip = drm_atomic_helper_page_flip, .reset = drm_atomic_helper_crtc_reset, .set_config = drm_atomic_helper_set_config, - .enable_vblank = apple_enable_vblank, - .disable_vblank = apple_disable_vblank, }; static const struct drm_mode_config_funcs apple_mode_config_funcs = { @@ -408,10 +389,6 @@ static int apple_platform_probe(struct platform_device *pdev) dev_set_drvdata(dev, apple); - ret = drm_vblank_init(&apple->drm, nr_dcp); - if (ret) - return ret; - ret = drmm_mode_config_init(&apple->drm); if (ret) goto err_unload; diff --git a/drivers/gpu/drm/apple/dcp.c b/drivers/gpu/drm/apple/dcp.c index b72d971d12bed2..31d4849a2f076f 100644 --- a/drivers/gpu/drm/apple/dcp.c +++ b/drivers/gpu/drm/apple/dcp.c @@ -38,15 +38,9 @@ void dcp_drm_crtc_vblank(struct apple_crtc *crtc) { unsigned long flags; - if (crtc->vsync_disabled) - return; - - drm_crtc_handle_vblank(&crtc->base); - spin_lock_irqsave(&crtc->base.dev->event_lock, flags); if (crtc->event) { drm_crtc_send_vblank_event(&crtc->base, crtc->event); - drm_crtc_vblank_put(&crtc->base); crtc->event = NULL; } spin_unlock_irqrestore(&crtc->base.dev->event_lock, flags); From 55630466bfd693382d13d6881da60dc6c2877ab9 Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Sun, 11 Dec 2022 13:42:49 +0100 Subject: [PATCH 1050/3902] drm: apple: Parse color modes completely Selecting the mode with the highest score may result in HDR mode for some displays. Since HDR is not support on driver side this produces an unexpected color representation. Full parsing allows us to reject virtual color modes (should already be invalid due to missing "Score"). Preparation for color and timing mode tracing. Signed-off-by: Janne Grunau --- drivers/gpu/drm/apple/parser.c | 41 ++++++++++++++++++++++++++-------- 1 file changed, 32 insertions(+), 9 deletions(-) diff --git a/drivers/gpu/drm/apple/parser.c b/drivers/gpu/drm/apple/parser.c index 31aecd4b2fc195..17060bd5e87ba6 100644 --- a/drivers/gpu/drm/apple/parser.c +++ b/drivers/gpu/drm/apple/parser.c @@ -248,6 +248,16 @@ static int parse_dimension(struct dcp_parse_ctx *handle, struct dimension *dim) return 0; } +struct color_mode { + s64 colorimetry; + s64 depth; + s64 dynamic_range; + s64 eotf; + s64 id; + s64 pixel_encoding; + s64 score; +}; + static int parse_color_modes(struct dcp_parse_ctx *handle, s64 *best_id) { struct iterator outer_it; @@ -258,17 +268,30 @@ static int parse_color_modes(struct dcp_parse_ctx *handle, s64 *best_id) dcp_parse_foreach_in_array(handle, outer_it) { struct iterator it; - s64 score = -1, id = -1; + bool is_virtual = true; + struct color_mode cmode; dcp_parse_foreach_in_dict(handle, it) { char *key = parse_string(it.handle); if (IS_ERR(key)) ret = PTR_ERR(key); - else if (!strcmp(key, "Score")) - ret = parse_int(it.handle, &score); + else if (!strcmp(key, "Colorimetry")) + ret = parse_int(it.handle, &cmode.colorimetry); + else if (!strcmp(key, "Depth")) + ret = parse_int(it.handle, &cmode.depth); + else if (!strcmp(key, "DynamicRange")) + ret = parse_int(it.handle, &cmode.dynamic_range); + else if (!strcmp(key, "EOTF")) + ret = parse_int(it.handle, &cmode.eotf); else if (!strcmp(key, "ID")) - ret = parse_int(it.handle, &id); + ret = parse_int(it.handle, &cmode.id); + else if (!strcmp(key, "IsVirtual")) + ret = parse_bool(it.handle, &is_virtual); + else if (!strcmp(key, "PixelEncoding")) + ret = parse_int(it.handle, &cmode.pixel_encoding); + else if (!strcmp(key, "Score")) + ret = parse_int(it.handle, &cmode.score); else skip(it.handle); @@ -276,13 +299,13 @@ static int parse_color_modes(struct dcp_parse_ctx *handle, s64 *best_id) return ret; } - /* Skip partial entries */ - if (score < 0 || id < 0) + /* Skip virtual or partial entries */ + if (is_virtual || cmode.score < 0 || cmode.id < 0) continue; - if (score > best_score) { - best_score = score; - *best_id = id; + if (cmode.score > best_score) { + best_score = cmode.score; + *best_id = cmode.id; } } From acfddda0de230f5d8066a5058f9a3f1f7b7fd93a Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Sun, 11 Dec 2022 14:11:30 +0100 Subject: [PATCH 1051/3902] drm: apple: Skip parsing elements of virtual timing modes Prevents trace points of unused color modes. Signed-off-by: Janne Grunau --- drivers/gpu/drm/apple/parser.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/drivers/gpu/drm/apple/parser.c b/drivers/gpu/drm/apple/parser.c index 17060bd5e87ba6..d84c77be5fd78b 100644 --- a/drivers/gpu/drm/apple/parser.c +++ b/drivers/gpu/drm/apple/parser.c @@ -343,6 +343,8 @@ static int parse_mode(struct dcp_parse_ctx *handle, if (IS_ERR(key)) ret = PTR_ERR(key); + else if (is_virtual) + skip(it.handle); else if (!strcmp(key, "HorizontalAttributes")) ret = parse_dimension(it.handle, &horiz); else if (!strcmp(key, "VerticalAttributes")) From dcb43cbc10a119a224984219543794234ae07e02 Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Sun, 11 Dec 2022 13:58:53 +0100 Subject: [PATCH 1052/3902] drm: apple: Add tracing for color and timing modes Restrict symbol mapping for EOTF, pixel encoding and colorimetry to tracing due to low confidence that it is correct. Main concern is the colorimetry mapping. Signed-off-by: Janne Grunau --- drivers/gpu/drm/apple/iomfb.c | 3 + drivers/gpu/drm/apple/parser.c | 11 +++ drivers/gpu/drm/apple/parser.h | 3 + drivers/gpu/drm/apple/trace.h | 120 +++++++++++++++++++++++++++++++++ 4 files changed, 137 insertions(+) diff --git a/drivers/gpu/drm/apple/iomfb.c b/drivers/gpu/drm/apple/iomfb.c index f7da26c391d532..533835de89756c 100644 --- a/drivers/gpu/drm/apple/iomfb.c +++ b/drivers/gpu/drm/apple/iomfb.c @@ -756,6 +756,9 @@ static bool dcpep_process_chunks(struct apple_dcp *dcp, return false; } + /* used just as opaque pointer for tracing */ + ctx.dcp = dcp; + ret = parse(dcp->chunks.data, dcp->chunks.length, &ctx); if (ret) { diff --git a/drivers/gpu/drm/apple/parser.c b/drivers/gpu/drm/apple/parser.c index d84c77be5fd78b..a253ec62640ba5 100644 --- a/drivers/gpu/drm/apple/parser.c +++ b/drivers/gpu/drm/apple/parser.c @@ -6,7 +6,9 @@ #include #include #include + #include "parser.h" +#include "trace.h" #define DCP_PARSE_HEADER 0xd3 @@ -303,6 +305,11 @@ static int parse_color_modes(struct dcp_parse_ctx *handle, s64 *best_id) if (is_virtual || cmode.score < 0 || cmode.id < 0) continue; + trace_iomfb_color_mode(handle->dcp, cmode.id, cmode.score, + cmode.depth, cmode.colorimetry, + cmode.eotf, cmode.dynamic_range, + cmode.pixel_encoding); + if (cmode.score > best_score) { best_score = cmode.score; *best_id = cmode.id; @@ -419,6 +426,10 @@ static int parse_mode(struct dcp_parse_ctx *handle, out->timing_mode_id = id; out->color_mode_id = best_color_mode; + trace_iomfb_timing_mode(handle->dcp, id, *score, horiz.active, + vert.active, vert.precise_sync_rate, + best_color_mode); + return 0; } diff --git a/drivers/gpu/drm/apple/parser.h b/drivers/gpu/drm/apple/parser.h index a2d479258ed0eb..92fe9473d56718 100644 --- a/drivers/gpu/drm/apple/parser.h +++ b/drivers/gpu/drm/apple/parser.h @@ -7,7 +7,10 @@ /* For mode parsing */ #include +struct apple_dcp; + struct dcp_parse_ctx { + struct apple_dcp *dcp; void *blob; u32 pos, len; }; diff --git a/drivers/gpu/drm/apple/trace.h b/drivers/gpu/drm/apple/trace.h index c7b5dee11ed07d..127bda420592a0 100644 --- a/drivers/gpu/drm/apple/trace.h +++ b/drivers/gpu/drm/apple/trace.h @@ -171,6 +171,126 @@ TRACE_EVENT(iomfb_brightness, ) ); +#define show_eotf(eotf) \ + __print_symbolic(eotf, { 0, "SDR gamma"}, \ + { 1, "HDR gamma"}, \ + { 2, "ST 2084 (PQ)"}, \ + { 3, "BT.2100 (HLG)"}, \ + { 4, "unexpected"}) + +#define show_encoding(enc) \ + __print_symbolic(enc, { 0, "RGB"}, \ + { 1, "YUV 4:2:0"}, \ + { 3, "YUV 4:2:2"}, \ + { 2, "YUV 4:4:4"}, \ + { 4, "DolbyVision (native)"}, \ + { 5, "DolbyVision (HDMI)"}, \ + { 6, "YCbCr 4:2:2 (DP tunnel)"}, \ + { 7, "YCbCr 4:2:2 (HDMI tunnel)"}, \ + { 8, "DolbyVision LL YCbCr 4:2:2"}, \ + { 9, "DolbyVision LL YCbCr 4:2:2 (DP)"}, \ + {10, "DolbyVision LL YCbCr 4:2:2 (HDMI)"}, \ + {11, "DolbyVision LL YCbCr 4:4:4"}, \ + {12, "DolbyVision LL RGB 4:2:2"}, \ + {13, "GRGB as YCbCr422 (Even line blue)"}, \ + {14, "GRGB as YCbCr422 (Even line red)"}, \ + {15, "unexpected"}) + +#define show_colorimetry(col) \ + __print_symbolic(col, { 0, "SMPTE 170M/BT.601"}, \ + { 1, "BT.701"}, \ + { 2, "xvYCC601"}, \ + { 3, "xvYCC709"}, \ + { 4, "sYCC601"}, \ + { 5, "AdobeYCC601"}, \ + { 6, "BT.2020 (c)"}, \ + { 7, "BT.2020 (nc)"}, \ + { 8, "DolbyVision VSVDB"}, \ + { 9, "BT.2020 (RGB)"}, \ + {10, "sRGB"}, \ + {11, "scRGB"}, \ + {12, "scRGBfixed"}, \ + {13, "AdobeRGB"}, \ + {14, "DCI-P3 (D65)"}, \ + {15, "DCI-P3 (Theater)"}, \ + {16, "Default RGB"}, \ + {17, "unexpected"}) + +#define show_range(range) \ + __print_symbolic(range, { 0, "Full"}, \ + { 1, "Limited"}, \ + { 2, "unexpected"}) + +TRACE_EVENT(iomfb_color_mode, + TP_PROTO(struct apple_dcp *dcp, u32 id, u32 score, u32 depth, + u32 colorimetry, u32 eotf, u32 range, u32 pixel_enc), + TP_ARGS(dcp, id, score, depth, colorimetry, eotf, range, pixel_enc), + TP_STRUCT__entry( + __field(u64, dcp) + __field(u32, id) + __field(u32, score) + __field(u32, depth) + __field(u32, colorimetry) + __field(u32, eotf) + __field(u32, range) + __field(u32, pixel_enc) + ), + TP_fast_assign( + __entry->dcp = (u64)dcp; + __entry->id = id; + __entry->score = score; + __entry->depth = depth; + __entry->colorimetry = min_t(u32, colorimetry, 17U); + __entry->eotf = min_t(u32, eotf, 4U); + __entry->range = min_t(u32, range, 2U); + __entry->pixel_enc = min_t(u32, pixel_enc, 15U); + ), + TP_printk("dcp=%llx, id=%u, score=%u, depth=%u, colorimetry=%s, eotf=%s, range=%s, pixel_enc=%s", + __entry->dcp, + __entry->id, + __entry->score, + __entry->depth, + show_colorimetry(__entry->colorimetry), + show_eotf(__entry->eotf), + show_range(__entry->range), + show_encoding(__entry->pixel_enc) + ) +); + +TRACE_EVENT(iomfb_timing_mode, + TP_PROTO(struct apple_dcp *dcp, u32 id, u32 score, u32 width, + u32 height, u32 clock, u32 color_mode), + TP_ARGS(dcp, id, score, width, height, clock, color_mode), + TP_STRUCT__entry( + __field(u64, dcp) + __field(u32, id) + __field(u32, score) + __field(u32, width) + __field(u32, height) + __field(u32, clock) + __field(u32, color_mode) + ), + TP_fast_assign( + __entry->dcp = (u64)dcp; + __entry->id = id; + __entry->score = score; + __entry->width = width; + __entry->height = height; + __entry->clock = clock; + __entry->color_mode = color_mode; + ), + TP_printk("dcp=%llx, id=%u, score=%u, %ux%u@%u.%u, color_mode=%u", + __entry->dcp, + __entry->id, + __entry->score, + __entry->width, + __entry->height, + __entry->clock >> 16, + ((__entry->clock & 0xffff) * 1000) >> 16, + __entry->color_mode + ) +); + #endif /* _TRACE_DCP_H */ /* This part must be outside protection */ From 58b2aee67e060695e6273cb671ce52651f25dc88 Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Sun, 11 Dec 2022 14:27:13 +0100 Subject: [PATCH 1053/3902] drm: apple: Prefer SDR color modes DCP best scored color mode might result in an HDR mode. As long as the driver (and DRM) is not ready for HDR try to avoid such modes. Signed-off-by: Janne Grunau --- drivers/gpu/drm/apple/parser.c | 26 ++++++++++++++++++++------ 1 file changed, 20 insertions(+), 6 deletions(-) diff --git a/drivers/gpu/drm/apple/parser.c b/drivers/gpu/drm/apple/parser.c index a253ec62640ba5..678c0e42e10682 100644 --- a/drivers/gpu/drm/apple/parser.c +++ b/drivers/gpu/drm/apple/parser.c @@ -260,13 +260,14 @@ struct color_mode { s64 score; }; -static int parse_color_modes(struct dcp_parse_ctx *handle, s64 *best_id) +static int parse_color_modes(struct dcp_parse_ctx *handle, s64 *preferred_id) { struct iterator outer_it; int ret = 0; - s64 best_score = -1; + s64 best_score = -1, best_score_sdr = -1; + s64 best_id = -1, best_id_sdr = -1; - *best_id = -1; + *preferred_id = -1; dcp_parse_foreach_in_array(handle, outer_it) { struct iterator it; @@ -310,12 +311,25 @@ static int parse_color_modes(struct dcp_parse_ctx *handle, s64 *best_id) cmode.eotf, cmode.dynamic_range, cmode.pixel_encoding); - if (cmode.score > best_score) { - best_score = cmode.score; - *best_id = cmode.id; + if (cmode.eotf == 0) { + if (cmode.score > best_score_sdr) { + best_score_sdr = cmode.score; + best_id_sdr = cmode.id; + } + } else { + if (cmode.score > best_score) { + best_score = cmode.score; + best_id = cmode.id; + } } } + /* prefer SDR color modes as long as HDR is not supported */ + if (best_score_sdr >= 0) + *preferred_id = best_id_sdr; + else if (best_score >= 0) + *preferred_id = best_id; + return 0; } From 6b1ec289b47226289df64d7d48f71cad674fb223 Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Sun, 11 Dec 2022 17:54:40 +0100 Subject: [PATCH 1054/3902] drm: apple: Add IOMobileFramebufferAP::get_color_remap_mode Probably not important but avoids an unnecessary difference compared to macOS. Signed-off-by: Janne Grunau --- drivers/gpu/drm/apple/iomfb.c | 19 ++++++++++++++++++- drivers/gpu/drm/apple/iomfb.h | 12 ++++++++++++ 2 files changed, 30 insertions(+), 1 deletion(-) diff --git a/drivers/gpu/drm/apple/iomfb.c b/drivers/gpu/drm/apple/iomfb.c index 533835de89756c..258eeeecb23a4f 100644 --- a/drivers/gpu/drm/apple/iomfb.c +++ b/drivers/gpu/drm/apple/iomfb.c @@ -182,6 +182,7 @@ const struct dcp_method_entry dcp_methods[dcpep_num_methods] = { DCP_METHOD("A410", dcpep_set_display_device), DCP_METHOD("A411", dcpep_is_main_display), DCP_METHOD("A412", dcpep_set_digital_out_mode), + DCP_METHOD("A426", iomfbep_get_color_remap_mode), DCP_METHOD("A439", dcpep_set_parameter_dcp), DCP_METHOD("A443", dcpep_create_default_fb), DCP_METHOD("A447", dcpep_enable_disable_video_power_savings), @@ -262,10 +263,21 @@ static void dcp_push(struct apple_dcp *dcp, bool oob, enum dcpep_method method, cb, cookie); \ } +#define IOMFB_THUNK_INOUT(name, T_in, T_out) \ + static void iomfb_ ## name(struct apple_dcp *dcp, bool oob, T_in *data, \ + dcp_callback_t cb, void *cookie) \ + { \ + dcp_push(dcp, oob, iomfbep_ ## name, sizeof(T_in), sizeof(T_out), \ + data, cb, cookie); \ + } + DCP_THUNK_OUT(iomfb_a131_pmu_service_matched, iomfbep_a131_pmu_service_matched, u32); DCP_THUNK_OUT(iomfb_a132_backlight_service_matched, iomfbep_a132_backlight_service_matched, u32); DCP_THUNK_OUT(iomfb_a358_vi_set_temperature_hint, iomfbep_a358_vi_set_temperature_hint, u32); +IOMFB_THUNK_INOUT(get_color_remap_mode, struct iomfb_get_color_remap_mode_req, + struct iomfb_get_color_remap_mode_resp); + DCP_THUNK_INOUT(dcp_swap_submit, dcpep_swap_submit, struct dcp_swap_submit_req, struct dcp_swap_submit_resp); @@ -1826,9 +1838,14 @@ static void init_1(struct apple_dcp *dcp, void *out, void *cookie) static void dcp_started(struct apple_dcp *dcp, void *data, void *cookie) { + struct iomfb_get_color_remap_mode_req color_remap = + (struct iomfb_get_color_remap_mode_req){ + .mode = 6, + }; + dev_info(dcp->dev, "DCP booted\n"); - init_1(dcp, data, cookie); + iomfb_get_color_remap_mode(dcp, false, &color_remap, init_1, cookie); } void iomfb_recv_msg(struct apple_dcp *dcp, u64 message) diff --git a/drivers/gpu/drm/apple/iomfb.h b/drivers/gpu/drm/apple/iomfb.h index 386a84cfcc5cd1..083ebd4e0be33f 100644 --- a/drivers/gpu/drm/apple/iomfb.h +++ b/drivers/gpu/drm/apple/iomfb.h @@ -210,6 +210,7 @@ enum dcpep_method { iomfbep_a131_pmu_service_matched, iomfbep_a132_backlight_service_matched, iomfbep_a358_vi_set_temperature_hint, + iomfbep_get_color_remap_mode, dcpep_num_methods }; @@ -433,4 +434,15 @@ struct iomfb_property { u32 value; } __packed; +struct iomfb_get_color_remap_mode_req { + u32 mode; + u8 mode_null; + u8 padding[3]; +} __packed; + +struct iomfb_get_color_remap_mode_resp { + u32 mode; + u32 ret; +} __packed; + #endif From 2be879d8f1dc67bc9cab07f88c6f6776e1377afa Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Sun, 11 Dec 2022 23:48:37 +0100 Subject: [PATCH 1055/3902] drm: apple: Add show_notch module parameter Can be used on devices with camera notch to use the full display height and thus show the notch. Signed-off-by: Janne Grunau --- drivers/gpu/drm/apple/dcp.c | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/drivers/gpu/drm/apple/dcp.c b/drivers/gpu/drm/apple/dcp.c index 31d4849a2f076f..0226c26ffa36e0 100644 --- a/drivers/gpu/drm/apple/dcp.c +++ b/drivers/gpu/drm/apple/dcp.c @@ -5,6 +5,7 @@ #include #include #include +#include #include #include #include @@ -33,6 +34,10 @@ #define DCP_BOOT_TIMEOUT msecs_to_jiffies(1000) +static bool show_notch; +module_param(show_notch, bool, 0644); +MODULE_PARM_DESC(show_notch, "Use the full display height and shows the notch"); + /* HACK: moved here to avoid circular dependency between apple_drv and dcp */ void dcp_drm_crtc_vblank(struct apple_crtc *crtc) { @@ -402,8 +407,10 @@ static int dcp_platform_probe(struct platform_device *pdev) of_platform_default_populate(dev->of_node, NULL, dev); - ret = of_property_read_u32(dev->of_node, "apple,notch-height", - &dcp->notch_height); + if (!show_notch) + ret = of_property_read_u32(dev->of_node, "apple,notch-height", + &dcp->notch_height); + if (dcp->notch_height > MAX_NOTCH_HEIGHT) dcp->notch_height = MAX_NOTCH_HEIGHT; if (dcp->notch_height > 0) From 312dc146eb0c6cdc9b2446e312348c63345df475 Mon Sep 17 00:00:00 2001 From: Hector Martin Date: Mon, 26 Dec 2022 00:26:05 +0900 Subject: [PATCH 1056/3902] drm/apple: Enable 10-bit mode & set colorspace to native This works on both 8-bit and 10-bit modes without any weirdness, and gives us the native colorspace without any conversion. Color correction should probably be handled in software anyway. However, we need to use surface 1 (at least on t600x), since 0 seems stuck in bg-sRGB mode for some reason... Signed-off-by: Hector Martin --- drivers/gpu/drm/apple/apple_drv.c | 4 ++-- drivers/gpu/drm/apple/iomfb.c | 24 ++++-------------------- drivers/gpu/drm/apple/iomfb.h | 11 +++++++++++ 3 files changed, 17 insertions(+), 22 deletions(-) diff --git a/drivers/gpu/drm/apple/apple_drv.c b/drivers/gpu/drm/apple/apple_drv.c index 24fc33dc4a3779..c3f64f58b55eb1 100644 --- a/drivers/gpu/drm/apple/apple_drv.c +++ b/drivers/gpu/drm/apple/apple_drv.c @@ -134,8 +134,8 @@ static const struct drm_plane_funcs apple_plane_funcs = { * advertise formats without alpha. */ static const u32 dcp_formats[] = { - // DRM_FORMAT_XRGB2101010, - // DRM_FORMAT_ARGB2101010, + DRM_FORMAT_XRGB2101010, + DRM_FORMAT_ARGB2101010, DRM_FORMAT_XRGB8888, DRM_FORMAT_ARGB8888, DRM_FORMAT_XBGR8888, diff --git a/drivers/gpu/drm/apple/iomfb.c b/drivers/gpu/drm/apple/iomfb.c index 258eeeecb23a4f..4a7d4809d53415 100644 --- a/drivers/gpu/drm/apple/iomfb.c +++ b/drivers/gpu/drm/apple/iomfb.c @@ -1493,23 +1493,6 @@ static u32 drm_format_to_dcp(u32 drm) return 0; } -static u8 drm_format_to_colorspace(u32 drm) -{ - switch (drm) { - case DRM_FORMAT_XRGB8888: - case DRM_FORMAT_ARGB8888: - case DRM_FORMAT_XBGR8888: - case DRM_FORMAT_ABGR8888: - return 1; - - case DRM_FORMAT_ARGB2101010: - case DRM_FORMAT_XRGB2101010: - return 2; - } - - return 1; -} - int dcp_get_modes(struct drm_connector *connector) { struct apple_connector *apple_connector = to_apple_connector(connector); @@ -1631,7 +1614,8 @@ void dcp_flush(struct drm_crtc *crtc, struct drm_atomic_state *state) for (l = 0; l < SWAP_SURFACES; l++) req->surf_null[l] = true; - l = 0; + // Surface 0 has limitations at least on t600x. + l = 1; for_each_oldnew_plane_in_state(state, plane, old_state, new_state, plane_idx) { struct drm_framebuffer *fb = new_state->fb; struct drm_gem_dma_object *obj; @@ -1698,8 +1682,8 @@ void dcp_flush(struct drm_crtc *crtc, struct drm_atomic_state *state) req->surf[l] = (struct dcp_surface){ .opaque = opaque, .format = drm_format_to_dcp(fb->format->format), - .xfer_func = 13, - .colorspace = drm_format_to_colorspace(fb->format->format), + .xfer_func = DCP_XFER_FUNC_SDR, + .colorspace = DCP_COLORSPACE_NATIVE, .stride = fb->pitches[0], .width = fb->width, .height = fb->height, diff --git a/drivers/gpu/drm/apple/iomfb.h b/drivers/gpu/drm/apple/iomfb.h index 083ebd4e0be33f..90b6668b075000 100644 --- a/drivers/gpu/drm/apple/iomfb.h +++ b/drivers/gpu/drm/apple/iomfb.h @@ -74,6 +74,17 @@ enum iomfb_property_id { #define SWAP_SURFACES 4 #define MAX_PLANES 3 +enum dcp_colorspace { + DCP_COLORSPACE_BG_SRGB = 0, + DCP_COLORSPACE_BG_BT2020 = 9, + DCP_COLORSPACE_NATIVE = 12, +}; + +enum dcp_xfer_func { + DCP_XFER_FUNC_SDR = 13, + DCP_XFER_FUNC_HDR = 16, +}; + struct dcp_iouserclient { /* Handle for the IOUserClient. macOS sets this to a kernel VA. */ u64 handle; From 051b5205d89547585fb21188f49487cf113a1976 Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Sat, 31 Dec 2022 14:53:59 +0100 Subject: [PATCH 1057/3902] drm: apple: Clear all surfaces on startup With "drm/apple: Enable 10-bit mode & set colorspace to native" kernel log messages are shown in an Apple logo shaped region in the middle of the display when using BGRA. The field currently identified as "opaque" is mislabeled and has to be investigated further. Signed-off-by: Janne Grunau --- drivers/gpu/drm/apple/dcp-internal.h | 3 +++ drivers/gpu/drm/apple/iomfb.c | 9 +++++++++ 2 files changed, 12 insertions(+) diff --git a/drivers/gpu/drm/apple/dcp-internal.h b/drivers/gpu/drm/apple/dcp-internal.h index 18969ab18e8319..8ca2324cb038c5 100644 --- a/drivers/gpu/drm/apple/dcp-internal.h +++ b/drivers/gpu/drm/apple/dcp-internal.h @@ -141,6 +141,9 @@ struct apple_dcp { /* eDP display without DP-HDMI conversion */ bool main_display; + /* clear all surfaces on init */ + bool surfaces_cleared; + /* panel has a mini-LED backllight */ bool has_mini_led; diff --git a/drivers/gpu/drm/apple/iomfb.c b/drivers/gpu/drm/apple/iomfb.c index 4a7d4809d53415..6898ad4c65e82d 100644 --- a/drivers/gpu/drm/apple/iomfb.c +++ b/drivers/gpu/drm/apple/iomfb.c @@ -1614,6 +1614,15 @@ void dcp_flush(struct drm_crtc *crtc, struct drm_atomic_state *state) for (l = 0; l < SWAP_SURFACES; l++) req->surf_null[l] = true; + /* + * Clear all surfaces on startup. The boot framebuffer in surface 0 + * sticks around. + */ + if (!dcp->surfaces_cleared) { + req->swap.swap_enabled = DCP_REMOVE_LAYERS | 0xF; + dcp->surfaces_cleared = true; + } + // Surface 0 has limitations at least on t600x. l = 1; for_each_oldnew_plane_in_state(state, plane, old_state, new_state, plane_idx) { From aee52c5b912467c6a1fff9309d9a37727687d5bf Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Mon, 2 Jan 2023 23:37:51 +0100 Subject: [PATCH 1058/3902] drm/apple: Update swap handling - opaque -> is_premultiplied - swap_enabled BIT(31) seems to be update background with dcp_swap.bg_color - add unused fields is_tearing_allowed, ycbcr_matrix, protection_opts, unk_num, unk_denom Changes: use is_premultiplied only for XRGB8/XBGR8, Update background only when necessary. Signed-off-by: Janne Grunau --- drivers/gpu/drm/apple/iomfb.c | 37 +++++++++++++++++++++-------------- drivers/gpu/drm/apple/iomfb.h | 23 ++++++++++------------ 2 files changed, 32 insertions(+), 28 deletions(-) diff --git a/drivers/gpu/drm/apple/iomfb.c b/drivers/gpu/drm/apple/iomfb.c index 6898ad4c65e82d..e328769ee49fb4 100644 --- a/drivers/gpu/drm/apple/iomfb.c +++ b/drivers/gpu/drm/apple/iomfb.c @@ -1063,9 +1063,9 @@ void dcp_poweroff(struct platform_device *pdev) // clear surfaces memset(&dcp->swap, 0, sizeof(dcp->swap)); - dcp->swap.swap.swap_enabled = DCP_REMOVE_LAYERS | 0x7; - dcp->swap.swap.swap_completed = DCP_REMOVE_LAYERS | 0x7; - dcp->swap.swap.unk_10c = 0xFF000000; + dcp->swap.swap.swap_enabled = + dcp->swap.swap.swap_completed = IOMFB_SET_BACKGROUND | 0xF; + dcp->swap.swap.bg_color = 0xFF000000; /* * Turn off the backlight. This matters because the DCP's idea of @@ -1619,7 +1619,8 @@ void dcp_flush(struct drm_crtc *crtc, struct drm_atomic_state *state) * sticks around. */ if (!dcp->surfaces_cleared) { - req->swap.swap_enabled = DCP_REMOVE_LAYERS | 0xF; + req->swap.swap_enabled = IOMFB_SET_BACKGROUND | 0xF; + req->swap.bg_color = 0xFF000000; dcp->surfaces_cleared = true; } @@ -1629,7 +1630,7 @@ void dcp_flush(struct drm_crtc *crtc, struct drm_atomic_state *state) struct drm_framebuffer *fb = new_state->fb; struct drm_gem_dma_object *obj; struct drm_rect src_rect; - bool opaque = false; + bool is_premultiplied = false; /* skip planes not for this crtc */ if (old_state->crtc != crtc && new_state->crtc != crtc) @@ -1660,18 +1661,21 @@ void dcp_flush(struct drm_crtc *crtc, struct drm_atomic_state *state) } if (!new_state->fb) { - if (old_state->fb) - req->swap.swap_enabled |= DCP_REMOVE_LAYERS; - l += 1; continue; } req->surf_null[l] = false; has_surface = 1; - if (!fb->format->has_alpha || - new_state->plane->type == DRM_PLANE_TYPE_PRIMARY) - opaque = true; + /* + * DCP doesn't support XBGR8 / XRGB8 natively. Blending as + * pre-multiplied alpha with a black background can be used as + * workaround for the bottommost plane. + */ + if (fb->format->format == DRM_FORMAT_XRGB8888 || + fb->format->format == DRM_FORMAT_XBGR8888) + is_premultiplied = true; + drm_rect_fp_to_int(&src_rect, &new_state->src); req->swap.src_rect[l] = drm_to_dcp_rect(&src_rect); @@ -1689,7 +1693,7 @@ void dcp_flush(struct drm_crtc *crtc, struct drm_atomic_state *state) req->surf_iova[l] = obj->dma_addr + fb->offsets[0]; req->surf[l] = (struct dcp_surface){ - .opaque = opaque, + .is_premultiplied = is_premultiplied, .format = drm_format_to_dcp(fb->format->format), .xfer_func = DCP_XFER_FUNC_SDR, .colorspace = DCP_COLORSPACE_NATIVE, @@ -1710,9 +1714,6 @@ void dcp_flush(struct drm_crtc *crtc, struct drm_atomic_state *state) l += 1; } - /* These fields should be set together */ - req->swap.swap_completed = req->swap.swap_enabled; - if (modeset) { struct dcp_display_mode *mode; struct dcp_wait_cookie *cookie; @@ -1773,9 +1774,15 @@ void dcp_flush(struct drm_crtc *crtc, struct drm_atomic_state *state) return; } + /* Set black background */ + req->swap.swap_enabled |= IOMFB_SET_BACKGROUND; + req->swap.bg_color = 0xFF000000; req->clear = 1; } + /* These fields should be set together */ + req->swap.swap_completed = req->swap.swap_enabled; + /* update brightness if changed */ if (dcp->brightness.update) { req->swap.bl_unk = 1; diff --git a/drivers/gpu/drm/apple/iomfb.h b/drivers/gpu/drm/apple/iomfb.h index 90b6668b075000..a7e9b62425b2c4 100644 --- a/drivers/gpu/drm/apple/iomfb.h +++ b/drivers/gpu/drm/apple/iomfb.h @@ -102,12 +102,9 @@ struct dcp_rect { } __packed; /* - * Set in the swap_{enabled,completed} field to remove missing - * layers. Without this flag, the DCP will assume missing layers have - * not changed since the previous frame and will preserve their - * content. - */ -#define DCP_REMOVE_LAYERS BIT(31) + * Update background color to struct dcp_swap.bg_color + */ +#define IOMFB_SET_BACKGROUND BIT(31) struct dcp_swap { u64 ts1; @@ -126,7 +123,7 @@ struct dcp_swap { u32 swap_enabled; u32 swap_completed; - u32 unk_10c; + u32 bg_color; u8 unk_110[0x1b8]; u32 unk_2c8; u8 unk_2cc[0x14]; @@ -160,12 +157,12 @@ struct dcp_component_types { /* Information describing a surface */ struct dcp_surface { u8 is_tiled; - u8 unk_1; - u8 opaque; /** ignore alpha, also required YUV overlays */ + u8 is_tearing_allowed; + u8 is_premultiplied; u32 plane_cnt; u32 plane_cnt2; u32 format; /* DCP fourcc */ - u32 unk_f; + u32 ycbcr_matrix; u8 xfer_func; u8 colorspace; u32 stride; @@ -176,8 +173,7 @@ struct dcp_surface { u32 width; u32 height; u32 buf_size; - u32 unk_2d; - u32 unk_31; + u64 protection_opts; u32 surface_id; struct dcp_component_types comp_types[MAX_PLANES]; u64 has_comp; @@ -185,7 +181,8 @@ struct dcp_surface { u64 has_planes; u32 compression_info[MAX_PLANES][13]; u64 has_compr_info; - u64 unk_1f5; + u32 unk_num; + u32 unk_denom; u8 padding[7]; } __packed; From 9e158acdfd4085faab2a6b3102591f210dc246a4 Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Thu, 22 Dec 2022 23:58:44 +0100 Subject: [PATCH 1059/3902] drm: apple: Use aperture_remove_conflicting_devices This does not seem to be as racy as drm_aperture_remove_framebuffers() and seems to reliably takes over simpledrm's device node. Signed-off-by: Janne Grunau --- drivers/gpu/drm/apple/apple_drv.c | 55 +++++++++++++++++++++++++++---- 1 file changed, 49 insertions(+), 6 deletions(-) diff --git a/drivers/gpu/drm/apple/apple_drv.c b/drivers/gpu/drm/apple/apple_drv.c index c3f64f58b55eb1..bf686e70a16104 100644 --- a/drivers/gpu/drm/apple/apple_drv.c +++ b/drivers/gpu/drm/apple/apple_drv.c @@ -7,12 +7,13 @@ * Copyright (C) 2014 Endless Mobile */ +#include #include #include +#include #include #include -#include #include #include #include @@ -341,10 +342,45 @@ static int apple_probe_per_dcp(struct device *dev, return drm_connector_attach_encoder(&connector->base, encoder); } +static int apple_get_fb_resource(struct device *dev, const char *name, + struct resource *fb_r) +{ + int idx, ret = -ENODEV; + struct device_node *node; + + idx = of_property_match_string(dev->of_node, "memory-region-names", name); + + node = of_parse_phandle(dev->of_node, "memory-region", idx); + if (!node) { + dev_err(dev, "reserved-memory node '%s' not found\n", name); + return -ENODEV; + } + + if (!of_device_is_available(node)) { + dev_err(dev, "reserved-memory node '%s' is unavailable\n", name); + goto err; + } + + if (!of_device_is_compatible(node, "framebuffer")) { + dev_err(dev, "reserved-memory node '%s' is incompatible\n", + node->full_name); + goto err; + } + + ret = of_address_to_resource(node, 0, fb_r); + +err: + of_node_put(node); + return ret; +} + + static int apple_platform_probe(struct platform_device *pdev) { struct device *dev = &pdev->dev; struct apple_drm_private *apple; + struct resource fb_r; + resource_size_t fb_size; struct platform_device *dcp[MAX_COPROCESSORS]; int ret, nr_dcp, i; @@ -382,6 +418,18 @@ static int apple_platform_probe(struct platform_device *pdev) if (ret) return ret; + ret = apple_get_fb_resource(dev, "framebuffer", &fb_r); + if (ret) + return ret; + + fb_size = fb_r.end - fb_r.start + 1; + ret = aperture_remove_conflicting_devices(fb_r.start, fb_size, + apple_drm_driver.name); + if (ret) { + dev_err(dev, "Failed remove fb: %d\n", ret); + return ret; + } + apple = devm_drm_dev_alloc(dev, &apple_drm_driver, struct apple_drm_private, drm); if (IS_ERR(apple)) @@ -427,11 +475,6 @@ static int apple_platform_probe(struct platform_device *pdev) drm_mode_config_reset(&apple->drm); - // remove before registering our DRM device - ret = drm_aperture_remove_framebuffers(false, &apple_drm_driver); - if (ret) - return ret; - ret = drm_dev_register(&apple->drm, 0); if (ret) goto err_unload; From fdb9c791e05115e050c9799424a5b9c3fb8a1072 Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Sat, 31 Dec 2022 15:27:07 +0100 Subject: [PATCH 1060/3902] drm/apple: Use drm_module_platform_driver This check for the "nomodeset" kernel command line parameter in its register method. Signed-off-by: Janne Grunau --- drivers/gpu/drm/apple/apple_drv.c | 3 ++- drivers/gpu/drm/apple/dcp.c | 3 ++- drivers/gpu/drm/apple/dummy-piodma.c | 4 +++- 3 files changed, 7 insertions(+), 3 deletions(-) diff --git a/drivers/gpu/drm/apple/apple_drv.c b/drivers/gpu/drm/apple/apple_drv.c index bf686e70a16104..dcdb35e6f716b9 100644 --- a/drivers/gpu/drm/apple/apple_drv.c +++ b/drivers/gpu/drm/apple/apple_drv.c @@ -28,6 +28,7 @@ #include #include #include +#include #include #include #include @@ -537,7 +538,7 @@ static struct platform_driver apple_platform_driver = { .remove = apple_platform_remove, }; -module_platform_driver(apple_platform_driver); +drm_module_platform_driver(apple_platform_driver); MODULE_AUTHOR("Alyssa Rosenzweig "); MODULE_DESCRIPTION(DRIVER_DESC); diff --git a/drivers/gpu/drm/apple/dcp.c b/drivers/gpu/drm/apple/dcp.c index 0226c26ffa36e0..4c890a1dae44dd 100644 --- a/drivers/gpu/drm/apple/dcp.c +++ b/drivers/gpu/drm/apple/dcp.c @@ -20,6 +20,7 @@ #include #include #include +#include #include #include @@ -539,7 +540,7 @@ static struct platform_driver apple_platform_driver = { }, }; -module_platform_driver(apple_platform_driver); +drm_module_platform_driver(apple_platform_driver); MODULE_AUTHOR("Alyssa Rosenzweig "); MODULE_DESCRIPTION("Apple Display Controller DRM driver"); diff --git a/drivers/gpu/drm/apple/dummy-piodma.c b/drivers/gpu/drm/apple/dummy-piodma.c index 3d4454df4a25da..daf4327592a041 100644 --- a/drivers/gpu/drm/apple/dummy-piodma.c +++ b/drivers/gpu/drm/apple/dummy-piodma.c @@ -1,6 +1,8 @@ // SPDX-License-Identifier: GPL-2.0-only OR MIT /* Copyright 2021 Alyssa Rosenzweig */ +#include + #include #include #include @@ -24,7 +26,7 @@ static struct platform_driver dcp_piodma_platform_driver = { }, }; -module_platform_driver(dcp_piodma_platform_driver); +drm_module_platform_driver(dcp_piodma_platform_driver); MODULE_AUTHOR("Alyssa Rosenzweig "); MODULE_DESCRIPTION("[HACK] Apple DCP PIODMA shim"); From 7fbbff4faf0af26271a322180b8628514f3f0e37 Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Mon, 2 Jan 2023 19:32:07 +0100 Subject: [PATCH 1061/3902] drm/apple: Allocate drm objects according to drm's expectations drm's documentaion explicitly tells us not to use devm_kzalloc(). drm device structures might out live the device when they are in use by userspace while the device vanishes. --- drivers/gpu/drm/apple/apple_drv.c | 43 +++++++++++++++++++++---------- drivers/gpu/drm/apple/dcp.h | 7 +++++ 2 files changed, 37 insertions(+), 13 deletions(-) diff --git a/drivers/gpu/drm/apple/apple_drv.c b/drivers/gpu/drm/apple/apple_drv.c index dcdb35e6f716b9..c23278ad9f88f4 100644 --- a/drivers/gpu/drm/apple/apple_drv.c +++ b/drivers/gpu/drm/apple/apple_drv.c @@ -115,10 +115,16 @@ static const struct drm_plane_helper_funcs apple_plane_helper_funcs = { .atomic_update = apple_plane_atomic_update, }; +static void apple_plane_cleanup(struct drm_plane *plane) +{ + drm_plane_cleanup(plane); + kfree(plane); +} + static const struct drm_plane_funcs apple_plane_funcs = { .update_plane = drm_atomic_helper_update_plane, .disable_plane = drm_atomic_helper_disable_plane, - .destroy = drm_plane_cleanup, + .destroy = apple_plane_cleanup, .reset = drm_atomic_helper_plane_reset, .atomic_duplicate_state = drm_atomic_helper_plane_duplicate_state, .atomic_destroy_state = drm_atomic_helper_plane_destroy_state, @@ -156,7 +162,7 @@ static struct drm_plane *apple_plane_init(struct drm_device *dev, int ret; struct drm_plane *plane; - plane = devm_kzalloc(dev->dev, sizeof(*plane), GFP_KERNEL); + plane = kzalloc(sizeof(*plane), GFP_KERNEL); ret = drm_universal_plane_init(dev, plane, possible_crtcs, &apple_plane_funcs, @@ -249,11 +255,16 @@ static void dcp_atomic_commit_tail(struct drm_atomic_state *old_state) drm_atomic_helper_cleanup_planes(dev, old_state); } +static void apple_crtc_cleanup(struct drm_crtc *crtc) +{ + drm_crtc_cleanup(crtc); + kfree(to_apple_crtc(crtc)); +} static const struct drm_crtc_funcs apple_crtc_funcs = { .atomic_destroy_state = drm_atomic_helper_crtc_destroy_state, .atomic_duplicate_state = drm_atomic_helper_crtc_duplicate_state, - .destroy = drm_crtc_cleanup, + .destroy = apple_crtc_cleanup, .page_flip = drm_atomic_helper_page_flip, .reset = drm_atomic_helper_crtc_reset, .set_config = drm_atomic_helper_set_config, @@ -269,9 +280,15 @@ static const struct drm_mode_config_helper_funcs apple_mode_config_helpers = { .atomic_commit_tail = dcp_atomic_commit_tail, }; +static void appledrm_connector_cleanup(struct drm_connector *connector) +{ + drm_connector_cleanup(connector); + kfree(to_apple_connector(connector)); +} + static const struct drm_connector_funcs apple_connector_funcs = { .fill_modes = drm_helper_probe_single_connector_modes, - .destroy = drm_connector_cleanup, + .destroy = appledrm_connector_cleanup, .reset = drm_atomic_helper_connector_reset, .atomic_duplicate_state = drm_atomic_helper_connector_duplicate_state, .atomic_destroy_state = drm_atomic_helper_connector_destroy_state, @@ -299,7 +316,7 @@ static int apple_probe_per_dcp(struct device *dev, { struct apple_crtc *crtc; struct apple_connector *connector; - struct drm_encoder *encoder; + struct apple_encoder *enc; struct drm_plane *primary; int ret; @@ -308,7 +325,7 @@ static int apple_probe_per_dcp(struct device *dev, if (IS_ERR(primary)) return PTR_ERR(primary); - crtc = devm_kzalloc(dev, sizeof(*crtc), GFP_KERNEL); + crtc = kzalloc(sizeof(*crtc), GFP_KERNEL); ret = drm_crtc_init_with_planes(drm, &crtc->base, primary, NULL, &apple_crtc_funcs, NULL); if (ret) @@ -316,13 +333,13 @@ static int apple_probe_per_dcp(struct device *dev, drm_crtc_helper_add(&crtc->base, &apple_crtc_helper_funcs); - encoder = devm_kzalloc(dev, sizeof(*encoder), GFP_KERNEL); - encoder->possible_crtcs = drm_crtc_mask(&crtc->base); - ret = drm_simple_encoder_init(drm, encoder, DRM_MODE_ENCODER_TMDS); - if (ret) - return ret; + enc = drmm_simple_encoder_alloc(drm, struct apple_encoder, base, + DRM_MODE_ENCODER_TMDS); + if (IS_ERR(enc)) + return PTR_ERR(enc); + enc->base.possible_crtcs = drm_crtc_mask(&crtc->base); - connector = devm_kzalloc(dev, sizeof(*connector), GFP_KERNEL); + connector = kzalloc(sizeof(*connector), GFP_KERNEL); drm_connector_helper_add(&connector->base, &apple_connector_helper_funcs); @@ -340,7 +357,7 @@ static int apple_probe_per_dcp(struct device *dev, crtc->dcp = dcp; dcp_link(dcp, crtc, connector); - return drm_connector_attach_encoder(&connector->base, encoder); + return drm_connector_attach_encoder(&connector->base, &enc->base); } static int apple_get_fb_resource(struct device *dev, const char *name, diff --git a/drivers/gpu/drm/apple/dcp.h b/drivers/gpu/drm/apple/dcp.h index fb4397e7b390fe..f4476f4acaf265 100644 --- a/drivers/gpu/drm/apple/dcp.h +++ b/drivers/gpu/drm/apple/dcp.h @@ -5,6 +5,7 @@ #define __APPLE_DCP_H__ #include +#include #include #include "dcp-internal.h" @@ -35,6 +36,12 @@ struct apple_connector { #define to_apple_connector(x) container_of(x, struct apple_connector, base) +struct apple_encoder { + struct drm_encoder base; +}; + +#define to_apple_encoder(x) container_of(x, struct apple_encoder, base) + void dcp_poweroff(struct platform_device *pdev); void dcp_poweron(struct platform_device *pdev); int dcp_crtc_atomic_check(struct drm_crtc *crtc, struct drm_atomic_state *state); From 86c429711692e8858d49399f3d01566e1de632c0 Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Thu, 29 Dec 2022 21:05:40 +0100 Subject: [PATCH 1062/3902] drm: apple: Use components to avoid deferred probing There was a report of a race between DRM device registration (and removal of the simpledrm device) and GDM startup. The component based device binding ensures that all necessary devices are bind in the probe method of the last missing component. Technically the piodma-mapper should be a component of dcp but since it is only used for its iommu it can be a component of the display subsystem. Signed-off-by: Janne Grunau --- drivers/gpu/drm/apple/apple_drv.c | 202 +++++++++++++++++++-------- drivers/gpu/drm/apple/dcp-internal.h | 1 - drivers/gpu/drm/apple/dcp.c | 103 ++++++++------ drivers/gpu/drm/apple/dummy-piodma.c | 39 +++++- 4 files changed, 247 insertions(+), 98 deletions(-) diff --git a/drivers/gpu/drm/apple/apple_drv.c b/drivers/gpu/drm/apple/apple_drv.c index c23278ad9f88f4..b76d84cb832ca2 100644 --- a/drivers/gpu/drm/apple/apple_drv.c +++ b/drivers/gpu/drm/apple/apple_drv.c @@ -8,8 +8,9 @@ */ #include -#include +#include #include +#include #include #include #include @@ -392,46 +393,55 @@ static int apple_get_fb_resource(struct device *dev, const char *name, return ret; } +static const struct of_device_id apple_dcp_id_tbl[] = { + { .compatible = "apple,dcp" }, + {}, +}; -static int apple_platform_probe(struct platform_device *pdev) +static int apple_drm_init_dcp(struct device *dev) { - struct device *dev = &pdev->dev; - struct apple_drm_private *apple; - struct resource fb_r; - resource_size_t fb_size; + struct apple_drm_private *apple = dev_get_drvdata(dev); struct platform_device *dcp[MAX_COPROCESSORS]; - int ret, nr_dcp, i; - - for (nr_dcp = 0; nr_dcp < MAX_COPROCESSORS; ++nr_dcp) { - struct device_node *np; - struct device_link *dcp_link; + struct device_node *np; + int ret, num_dcp = 0; - np = of_parse_phandle(dev->of_node, "apple,coprocessors", - nr_dcp); - - if (!np) - break; + for_each_matching_node(np, apple_dcp_id_tbl) { + if (!of_device_is_available(np)) { + of_node_put(np); + continue; + } - dcp[nr_dcp] = of_find_device_by_node(np); + dcp[num_dcp] = of_find_device_by_node(np); + of_node_put(np); + if (!dcp[num_dcp]) + continue; - if (!dcp[nr_dcp]) - return -ENODEV; + ret = apple_probe_per_dcp(dev, &apple->drm, dcp[num_dcp], + num_dcp); + if (ret) + continue; - dcp_link = device_link_add(dev, &dcp[nr_dcp]->dev, - DL_FLAG_AUTOREMOVE_CONSUMER); - if (!dcp_link) { - dev_err(dev, "Failed to link to DCP %d device", nr_dcp); - return -EINVAL; - } + ret = dcp_start(dcp[num_dcp]); + if (ret) + continue; - if (dcp_link->supplier->links.status != DL_DEV_DRIVER_BOUND) - return -EPROBE_DEFER; + num_dcp++; } - /* Need at least 1 DCP for a display subsystem */ - if (nr_dcp < 1) + if (num_dcp < 1) return -ENODEV; + + return 0; +} + +static int apple_drm_init(struct device *dev) +{ + struct apple_drm_private *apple; + struct resource fb_r; + resource_size_t fb_size; + int ret; + ret = dma_set_mask_and_coherent(dev, DMA_BIT_MASK(36)); if (ret) return ret; @@ -440,14 +450,6 @@ static int apple_platform_probe(struct platform_device *pdev) if (ret) return ret; - fb_size = fb_r.end - fb_r.start + 1; - ret = aperture_remove_conflicting_devices(fb_r.start, fb_size, - apple_drm_driver.name); - if (ret) { - dev_err(dev, "Failed remove fb: %d\n", ret); - return ret; - } - apple = devm_drm_dev_alloc(dev, &apple_drm_driver, struct apple_drm_private, drm); if (IS_ERR(apple)) @@ -455,9 +457,22 @@ static int apple_platform_probe(struct platform_device *pdev) dev_set_drvdata(dev, apple); + ret = component_bind_all(dev, apple); + if (ret) + return ret; + + fb_size = fb_r.end - fb_r.start + 1; + ret = aperture_remove_conflicting_devices(fb_r.start, fb_size, + apple_drm_driver.name); + + if (ret) { + dev_err(dev, "Failed remove fb: %d\n", ret); + goto err_unbind; + } + ret = drmm_mode_config_init(&apple->drm); if (ret) - goto err_unload; + goto err_unbind; /* * IOMFB::UPPipeDCP_H13P::verify_surfaces produces the error "plane @@ -479,42 +494,112 @@ static int apple_platform_probe(struct platform_device *pdev) apple->drm.mode_config.funcs = &apple_mode_config_funcs; apple->drm.mode_config.helper_private = &apple_mode_config_helpers; - for (i = 0; i < nr_dcp; ++i) { - ret = apple_probe_per_dcp(dev, &apple->drm, dcp[i], i); - - if (ret) - goto err_unload; - - ret = dcp_start(dcp[i]); - - if (ret) - goto err_unload; - } + ret = apple_drm_init_dcp(dev); + if (ret) + goto err_unbind; drm_mode_config_reset(&apple->drm); ret = drm_dev_register(&apple->drm, 0); if (ret) - goto err_unload; + goto err_unbind; drm_client_setup_with_fourcc(&apple->drm, DRM_FORMAT_XRGB8888); return 0; -err_unload: - drm_dev_put(&apple->drm); +err_unbind: + component_unbind_all(dev, NULL); return ret; } -static int apple_platform_remove(struct platform_device *pdev) +static void apple_drm_uninit(struct device *dev) { - struct apple_drm_private *apple = platform_get_drvdata(pdev); + struct apple_drm_private *apple = dev_get_drvdata(dev); drm_dev_unregister(&apple->drm); + drm_atomic_helper_shutdown(&apple->drm); + + component_unbind_all(dev, NULL); + + dev_set_drvdata(dev, NULL); +} + +static int apple_drm_bind(struct device *dev) +{ + return apple_drm_init(dev); +} + +static void apple_drm_unbind(struct device *dev) +{ + apple_drm_uninit(dev); +} + +const struct component_master_ops apple_drm_ops = { + .bind = apple_drm_bind, + .unbind = apple_drm_unbind, +}; + +static const struct of_device_id apple_component_id_tbl[] = { + { .compatible = "apple,dcp-piodma" }, + {}, +}; + +static int add_display_components(struct device *dev, + struct component_match **matchptr) +{ + struct device_node *np; + + for_each_matching_node(np, apple_component_id_tbl) { + if (of_device_is_available(np)) + drm_of_component_match_add(dev, matchptr, + component_compare_of, np); + of_node_put(np); + } return 0; } +static int add_dcp_components(struct device *dev, + struct component_match **matchptr) +{ + struct device_node *np; + int num = 0; + + for_each_matching_node(np, apple_dcp_id_tbl) { + if (of_device_is_available(np)) { + drm_of_component_match_add(dev, matchptr, + component_compare_of, np); + num++; + } + of_node_put(np); + } + + return num; +} + +static int apple_platform_probe(struct platform_device *pdev) +{ + struct device *mdev = &pdev->dev; + struct component_match *match = NULL; + int num_dcp; + + /* add PIODMA mapper components */ + add_display_components(mdev, &match); + + /* add DCP components, handle less than 1 as probe error */ + num_dcp = add_dcp_components(mdev, &match); + if (num_dcp < 1) + return -ENODEV; + + return component_master_add_with_match(mdev, &apple_drm_ops, match); +} + +static void apple_platform_remove(struct platform_device *pdev) +{ + component_master_del(&pdev->dev, &apple_drm_ops); +} + static const struct of_device_id of_match[] = { { .compatible = "apple,display-subsystem" }, {} @@ -526,14 +611,19 @@ static int apple_platform_suspend(struct device *dev) { struct apple_drm_private *apple = dev_get_drvdata(dev); - return drm_mode_config_helper_suspend(&apple->drm); + if (apple) + return drm_mode_config_helper_suspend(&apple->drm); + + return 0; } static int apple_platform_resume(struct device *dev) { struct apple_drm_private *apple = dev_get_drvdata(dev); - drm_mode_config_helper_resume(&apple->drm); + if (apple) + drm_mode_config_helper_resume(&apple->drm); + return 0; } diff --git a/drivers/gpu/drm/apple/dcp-internal.h b/drivers/gpu/drm/apple/dcp-internal.h index 8ca2324cb038c5..ed11d38f80bb59 100644 --- a/drivers/gpu/drm/apple/dcp-internal.h +++ b/drivers/gpu/drm/apple/dcp-internal.h @@ -84,7 +84,6 @@ struct dcp_brightness { struct apple_dcp { struct device *dev; struct platform_device *piodma; - struct device_link *piodma_link; struct apple_rtkit *rtk; struct apple_crtc *crtc; struct apple_connector *connector; diff --git a/drivers/gpu/drm/apple/dcp.c b/drivers/gpu/drm/apple/dcp.c index 4c890a1dae44dd..ab748ca188b9aa 100644 --- a/drivers/gpu/drm/apple/dcp.c +++ b/drivers/gpu/drm/apple/dcp.c @@ -1,21 +1,22 @@ // SPDX-License-Identifier: GPL-2.0-only OR MIT /* Copyright 2021 Alyssa Rosenzweig */ +#include #include #include +#include +#include +#include +#include +#include #include #include #include -#include -#include #include -#include -#include -#include -#include +#include #include -#include -#include "linux/workqueue.h" +#include +#include #include #include @@ -376,33 +377,18 @@ static enum dcp_firmware_version dcp_check_firmware_version(struct device *dev) return DCP_FIRMWARE_UNKNOWN; } -static int dcp_platform_probe(struct platform_device *pdev) +static int dcp_comp_bind(struct device *dev, struct device *main, void *data) { - struct device *dev = &pdev->dev; struct device_node *panel_np; - struct apple_dcp *dcp; - enum dcp_firmware_version fw_compat; + struct apple_dcp *dcp = dev_get_drvdata(dev); u32 cpu_ctrl; int ret; - fw_compat = dcp_check_firmware_version(dev); - if (fw_compat == DCP_FIRMWARE_UNKNOWN) - return -ENODEV; - - dcp = devm_kzalloc(dev, sizeof(*dcp), GFP_KERNEL); - if (!dcp) - return -ENOMEM; - - dcp->fw_compat = fw_compat; - - platform_set_drvdata(pdev, dcp); - dcp->dev = dev; - ret = dma_set_mask_and_coherent(dev, DMA_BIT_MASK(36)); if (ret) return ret; - dcp->coproc_reg = devm_platform_ioremap_resource_byname(pdev, "coproc"); + dcp->coproc_reg = devm_platform_ioremap_resource_byname(to_platform_device(dev), "coproc"); if (IS_ERR(dcp->coproc_reg)) return PTR_ERR(dcp->coproc_reg); @@ -453,22 +439,17 @@ static int dcp_platform_probe(struct platform_device *pdev) else dcp->connector_type = DRM_MODE_CONNECTOR_Unknown; + /* + * Components do not ensure the bind order of sub components but + * the piodma device is only used for its iommu. The iommu is fully + * initialized by the time dcp_piodma_probe() calls component_add(). + */ dcp->piodma = dcp_get_dev(dev, "apple,piodma-mapper"); if (!dcp->piodma) { dev_err(dev, "failed to find piodma\n"); return -ENODEV; } - dcp->piodma_link = device_link_add(dev, &dcp->piodma->dev, - DL_FLAG_AUTOREMOVE_CONSUMER); - if (!dcp->piodma_link) { - dev_err(dev, "Failed to link to piodma device"); - return -EINVAL; - } - - if (dcp->piodma_link->supplier->links.status != DL_DEV_DRIVER_BOUND) - return -EPROBE_DEFER; - ret = dcp_get_disp_regs(dcp); if (ret) { dev_err(dev, "failed to find display registers\n"); @@ -517,12 +498,55 @@ static int dcp_platform_probe(struct platform_device *pdev) * We need to shutdown DCP before tearing down the display subsystem. Otherwise * the DCP will crash and briefly flash a green screen of death. */ -static void dcp_platform_shutdown(struct platform_device *pdev) +static void dcp_comp_unbind(struct device *dev, struct device *main, void *data) { - struct apple_dcp *dcp = platform_get_drvdata(pdev); + struct apple_dcp *dcp = dev_get_drvdata(dev); - if (dcp->shmem) + if (dcp && dcp->shmem) iomfb_shutdown(dcp); + + platform_device_put(dcp->piodma); + dcp->piodma = NULL; + + devm_clk_put(dev, dcp->clk); + dcp->clk = NULL; +} + +static const struct component_ops dcp_comp_ops = { + .bind = dcp_comp_bind, + .unbind = dcp_comp_unbind, +}; + +static int dcp_platform_probe(struct platform_device *pdev) +{ + enum dcp_firmware_version fw_compat; + struct device *dev = &pdev->dev; + struct apple_dcp *dcp; + + fw_compat = dcp_check_firmware_version(dev); + if (fw_compat == DCP_FIRMWARE_UNKNOWN) + return -ENODEV; + + dcp = devm_kzalloc(dev, sizeof(*dcp), GFP_KERNEL); + if (!dcp) + return -ENOMEM; + + dcp->fw_compat = fw_compat; + dcp->dev = dev; + + platform_set_drvdata(pdev, dcp); + + return component_add(&pdev->dev, &dcp_comp_ops); +} + +static void dcp_platform_remove(struct platform_device *pdev) +{ + component_del(&pdev->dev, &dcp_comp_ops); +} + +static void dcp_platform_shutdown(struct platform_device *pdev) +{ + component_del(&pdev->dev, &dcp_comp_ops); } static const struct of_device_id of_match[] = { @@ -533,6 +557,7 @@ MODULE_DEVICE_TABLE(of, of_match); static struct platform_driver apple_platform_driver = { .probe = dcp_platform_probe, + .remove = dcp_platform_remove, .shutdown = dcp_platform_shutdown, .driver = { .name = "apple-dcp", diff --git a/drivers/gpu/drm/apple/dummy-piodma.c b/drivers/gpu/drm/apple/dummy-piodma.c index daf4327592a041..ec5d4fecf356c0 100644 --- a/drivers/gpu/drm/apple/dummy-piodma.c +++ b/drivers/gpu/drm/apple/dummy-piodma.c @@ -3,13 +3,46 @@ #include -#include +#include #include +#include #include +static int dcp_piodma_comp_bind(struct device *dev, struct device *main, + void *data) +{ + return 0; +} + +static void dcp_piodma_comp_unbind(struct device *dev, struct device *main, + void *data) +{ + /* nothing to do */ +} + +static const struct component_ops dcp_piodma_comp_ops = { + .bind = dcp_piodma_comp_bind, + .unbind = dcp_piodma_comp_unbind, +}; static int dcp_piodma_probe(struct platform_device *pdev) { - return dma_set_mask_and_coherent(&pdev->dev, DMA_BIT_MASK(36)); + int ret = dma_set_mask_and_coherent(&pdev->dev, DMA_BIT_MASK(36)); + if (ret) + return ret; + + return component_add(&pdev->dev, &dcp_piodma_comp_ops); +} + +static int dcp_piodma_remove(struct platform_device *pdev) +{ + component_del(&pdev->dev, &dcp_piodma_comp_ops); + + return 0; +} + +static void dcp_piodma_shutdown(struct platform_device *pdev) +{ + component_del(&pdev->dev, &dcp_piodma_comp_ops); } static const struct of_device_id of_match[] = { @@ -20,6 +53,8 @@ MODULE_DEVICE_TABLE(of, of_match); static struct platform_driver dcp_piodma_platform_driver = { .probe = dcp_piodma_probe, + .remove = dcp_piodma_remove, + .shutdown = dcp_piodma_shutdown, .driver = { .name = "apple,dcp-piodma", .of_match_table = of_match, From bd87765079f0e5b396ff23f007cfebc24489d4cf Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Thu, 29 Dec 2022 21:38:44 +0100 Subject: [PATCH 1063/3902] drm: apple: Wait for iomfb initialization Avoids "[drm] Cannot find any crtc or sizes" during fbdev initialization if a display is connected. Signed-off-by: Janne Grunau --- drivers/gpu/drm/apple/apple_drv.c | 19 ++++++++++++++++++- drivers/gpu/drm/apple/dcp-internal.h | 3 +++ drivers/gpu/drm/apple/dcp.c | 26 ++++++++++++++++++++++++++ drivers/gpu/drm/apple/dcp.h | 1 + drivers/gpu/drm/apple/iomfb.c | 5 +++-- 5 files changed, 51 insertions(+), 3 deletions(-) diff --git a/drivers/gpu/drm/apple/apple_drv.c b/drivers/gpu/drm/apple/apple_drv.c index b76d84cb832ca2..693a8c59d8a965 100644 --- a/drivers/gpu/drm/apple/apple_drv.c +++ b/drivers/gpu/drm/apple/apple_drv.c @@ -10,6 +10,7 @@ #include #include #include +#include #include #include #include @@ -403,7 +404,8 @@ static int apple_drm_init_dcp(struct device *dev) struct apple_drm_private *apple = dev_get_drvdata(dev); struct platform_device *dcp[MAX_COPROCESSORS]; struct device_node *np; - int ret, num_dcp = 0; + u64 timeout; + int i, ret, num_dcp = 0; for_each_matching_node(np, apple_dcp_id_tbl) { if (!of_device_is_available(np)) { @@ -431,6 +433,21 @@ static int apple_drm_init_dcp(struct device *dev) if (num_dcp < 1) return -ENODEV; + timeout = get_jiffies_64() + msecs_to_jiffies(500); + + for (i = 0; i < num_dcp; ++i) { + u64 jiffies = get_jiffies_64(); + u64 wait = time_after_eq64(jiffies, timeout) ? + 0 : + timeout - jiffies; + ret = dcp_wait_ready(dcp[i], wait); + /* There is nothing we can do if a dcp/dcpext does not boot + * (successfully). Ignoring it should not do any harm now. + * Needs to reevaluated whenn adding dcpext support. + */ + if (ret) + dev_warn(dev, "DCP[%d] not ready: %d\n", i, ret); + } return 0; } diff --git a/drivers/gpu/drm/apple/dcp-internal.h b/drivers/gpu/drm/apple/dcp-internal.h index ed11d38f80bb59..85e9137bbdf10e 100644 --- a/drivers/gpu/drm/apple/dcp-internal.h +++ b/drivers/gpu/drm/apple/dcp-internal.h @@ -134,6 +134,9 @@ struct apple_dcp { bool valid_mode; struct dcp_set_digital_out_mode_req mode; + /* completion for active turning true */ + struct completion start_done; + /* Is the DCP booted? */ bool active; diff --git a/drivers/gpu/drm/apple/dcp.c b/drivers/gpu/drm/apple/dcp.c index ab748ca188b9aa..d5dbe2e47e8b99 100644 --- a/drivers/gpu/drm/apple/dcp.c +++ b/drivers/gpu/drm/apple/dcp.c @@ -116,6 +116,7 @@ static void dcp_rtk_crashed(void *cookie, const void *crashlog, size_t crashlog_ dcp->connector->connected = 0; schedule_work(&dcp->connector->hotplug_wq); } + complete(&dcp->start_done); } static int dcp_rtk_shmem_setup(void *cookie, struct apple_rtkit_shmem *bfr) @@ -247,6 +248,8 @@ int dcp_start(struct platform_device *pdev) struct apple_dcp *dcp = platform_get_drvdata(pdev); int ret; + init_completion(&dcp->start_done); + /* start RTKit endpoints */ ret = iomfb_start_rtkit(dcp); if (ret) @@ -256,6 +259,29 @@ int dcp_start(struct platform_device *pdev) } EXPORT_SYMBOL(dcp_start); +int dcp_wait_ready(struct platform_device *pdev, u64 timeout) +{ + struct apple_dcp *dcp = platform_get_drvdata(pdev); + int ret; + + if (dcp->crashed) + return -ENODEV; + if (dcp->active) + return 0; + if (timeout <= 0) + return -ETIMEDOUT; + + ret = wait_for_completion_timeout(&dcp->start_done, timeout); + if (ret < 0) + return ret; + + if (dcp->crashed) + return -ENODEV; + + return dcp->active ? 0 : -ETIMEDOUT; +} +EXPORT_SYMBOL(dcp_wait_ready); + static void dcp_work_register_backlight(struct work_struct *work) { int ret; diff --git a/drivers/gpu/drm/apple/dcp.h b/drivers/gpu/drm/apple/dcp.h index f4476f4acaf265..2011d27e809d53 100644 --- a/drivers/gpu/drm/apple/dcp.h +++ b/drivers/gpu/drm/apple/dcp.h @@ -49,6 +49,7 @@ int dcp_get_connector_type(struct platform_device *pdev); void dcp_link(struct platform_device *pdev, struct apple_crtc *apple, struct apple_connector *connector); int dcp_start(struct platform_device *pdev); +int dcp_wait_ready(struct platform_device *pdev, u64 timeout); void dcp_flush(struct drm_crtc *crtc, struct drm_atomic_state *state); bool dcp_is_initialized(struct platform_device *pdev); void apple_crtc_vblank(struct apple_crtc *apple); diff --git a/drivers/gpu/drm/apple/iomfb.c b/drivers/gpu/drm/apple/iomfb.c index e328769ee49fb4..4382fe12b0592b 100644 --- a/drivers/gpu/drm/apple/iomfb.c +++ b/drivers/gpu/drm/apple/iomfb.c @@ -1811,13 +1811,14 @@ static void res_is_main_display(struct apple_dcp *dcp, void *out, void *cookie) dcp->main_display = result != 0; - dcp->active = true; - connector = dcp->connector; if (connector) { connector->connected = dcp->nr_modes > 0; schedule_work(&connector->hotplug_wq); } + + dcp->active = true; + complete(&dcp->start_done); } static void init_3(struct apple_dcp *dcp, void *out, void *cookie) From 10b3782e3bed4cf1ed92d04a2b0ee3f2fe31ab1a Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Sun, 8 Jan 2023 21:24:51 +0100 Subject: [PATCH 1064/3902] drm/apple: simplify IOMFB_THUNK_INOUT Signed-off-by: Janne Grunau --- drivers/gpu/drm/apple/iomfb.c | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/drivers/gpu/drm/apple/iomfb.c b/drivers/gpu/drm/apple/iomfb.c index 4382fe12b0592b..640e869386d44a 100644 --- a/drivers/gpu/drm/apple/iomfb.c +++ b/drivers/gpu/drm/apple/iomfb.c @@ -263,20 +263,22 @@ static void dcp_push(struct apple_dcp *dcp, bool oob, enum dcpep_method method, cb, cookie); \ } -#define IOMFB_THUNK_INOUT(name, T_in, T_out) \ - static void iomfb_ ## name(struct apple_dcp *dcp, bool oob, T_in *data, \ - dcp_callback_t cb, void *cookie) \ - { \ - dcp_push(dcp, oob, iomfbep_ ## name, sizeof(T_in), sizeof(T_out), \ - data, cb, cookie); \ +#define IOMFB_THUNK_INOUT(name) \ + static void iomfb_ ## name(struct apple_dcp *dcp, bool oob, \ + struct iomfb_ ## name ## _req *data, \ + dcp_callback_t cb, void *cookie) \ + { \ + dcp_push(dcp, oob, iomfbep_ ## name, \ + sizeof(struct iomfb_ ## name ## _req), \ + sizeof(struct iomfb_ ## name ## _resp), \ + data, cb, cookie); \ } DCP_THUNK_OUT(iomfb_a131_pmu_service_matched, iomfbep_a131_pmu_service_matched, u32); DCP_THUNK_OUT(iomfb_a132_backlight_service_matched, iomfbep_a132_backlight_service_matched, u32); DCP_THUNK_OUT(iomfb_a358_vi_set_temperature_hint, iomfbep_a358_vi_set_temperature_hint, u32); -IOMFB_THUNK_INOUT(get_color_remap_mode, struct iomfb_get_color_remap_mode_req, - struct iomfb_get_color_remap_mode_resp); +IOMFB_THUNK_INOUT(get_color_remap_mode); DCP_THUNK_INOUT(dcp_swap_submit, dcpep_swap_submit, struct dcp_swap_submit_req, struct dcp_swap_submit_resp); From d1ca777c36cc3cb1df14fcf1df0b8808dff88236 Mon Sep 17 00:00:00 2001 From: Asahi Lina Date: Wed, 15 Feb 2023 20:57:56 +0900 Subject: [PATCH 1065/3902] drm/apple: Fix parse_string() memory leaks Signed-off-by: Asahi Lina --- drivers/gpu/drm/apple/parser.c | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/drivers/gpu/drm/apple/parser.c b/drivers/gpu/drm/apple/parser.c index 678c0e42e10682..1d85d76ae0e16f 100644 --- a/drivers/gpu/drm/apple/parser.c +++ b/drivers/gpu/drm/apple/parser.c @@ -243,6 +243,9 @@ static int parse_dimension(struct dcp_parse_ctx *handle, struct dimension *dim) else skip(it.handle); + if (!IS_ERR_OR_NULL(key)) + kfree(key); + if (ret) return ret; } @@ -298,6 +301,9 @@ static int parse_color_modes(struct dcp_parse_ctx *handle, s64 *preferred_id) else skip(it.handle); + if (!IS_ERR_OR_NULL(key)) + kfree(key); + if (ret) return ret; } @@ -381,6 +387,9 @@ static int parse_mode(struct dcp_parse_ctx *handle, else skip(it.handle); + if (!IS_ERR_OR_NULL(key)) + kfree(key); + if (ret) return ret; } @@ -511,6 +520,9 @@ int parse_display_attributes(struct dcp_parse_ctx *handle, int *width_mm, else skip(it.handle); + if (!IS_ERR_OR_NULL(key)) + kfree(key); + if (ret) return ret; } From 8234b78802c324b524297559c2dc531b725cf80c Mon Sep 17 00:00:00 2001 From: Asahi Lina Date: Wed, 15 Feb 2023 20:57:47 +0900 Subject: [PATCH 1066/3902] drm/apple: Fix bad error return Signed-off-by: Asahi Lina --- drivers/gpu/drm/apple/parser.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/gpu/drm/apple/parser.c b/drivers/gpu/drm/apple/parser.c index 1d85d76ae0e16f..5665c2ba19682f 100644 --- a/drivers/gpu/drm/apple/parser.c +++ b/drivers/gpu/drm/apple/parser.c @@ -229,7 +229,7 @@ static int parse_dimension(struct dcp_parse_ctx *handle, struct dimension *dim) char *key = parse_string(it.handle); if (IS_ERR(key)) - ret = PTR_ERR(handle); + ret = PTR_ERR(key); else if (!strcmp(key, "Active")) ret = parse_int(it.handle, &dim->active); else if (!strcmp(key, "Total")) From 3010d746917518e7f85fff2e80e81b0e4186cabc Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Sun, 22 Jan 2023 20:16:28 +0100 Subject: [PATCH 1067/3902] drm/apple: Set backlight level indirectly if no mode is set Fixes following warning when systemd-backlight restores the backlight level on boot before a mode is set: Call trace: drm_atomic_helper_crtc_duplicate_state+0x58/0x74 drm_atomic_get_crtc_state+0x84/0x120 dcp_set_brightness+0xd8/0x21c [apple_dcp] backlight_device_set_brightness+0x78/0x130 ... Signed-off-by: Janne Grunau --- drivers/gpu/drm/apple/dcp_backlight.c | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/drivers/gpu/drm/apple/dcp_backlight.c b/drivers/gpu/drm/apple/dcp_backlight.c index 42b1097eaa0180..45827fe6041a27 100644 --- a/drivers/gpu/drm/apple/dcp_backlight.c +++ b/drivers/gpu/drm/apple/dcp_backlight.c @@ -165,21 +165,30 @@ static int drm_crtc_set_brightness(struct drm_crtc *crtc, static int dcp_set_brightness(struct backlight_device *bd) { - int ret; + int ret = 0; struct apple_dcp *dcp = bl_get_data(bd); struct drm_modeset_acquire_ctx ctx; if (bd->props.state & BL_CORE_SUSPENDED) return 0; - if (!dcp->crtc) - return -EAGAIN; + DRM_MODESET_LOCK_ALL_BEGIN(dcp->crtc->base.dev, ctx, 0, ret); dcp->brightness.dac = calculate_dac(dcp, bd->props.brightness); dcp->brightness.update = true; - DRM_MODESET_LOCK_ALL_BEGIN(dcp->crtc->base.dev, ctx, 0, ret); + /* + * Do not actively try to change brightness if no mode is set. + * TODO: should this be reflected the in backlight's power property? + * defer this hopefully until it becomes irrelevant due to proper + * drm integrated backlight handling + */ + if (!dcp->valid_mode) + goto out; + ret = drm_crtc_set_brightness(&dcp->crtc->base, &ctx); + +out: DRM_MODESET_LOCK_ALL_END(dcp->crtc->base.dev, ctx, ret); return ret; From a46c5b8c30e439162095b759b4d829be9a5dbe02 Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Sun, 22 Jan 2023 19:43:35 +0100 Subject: [PATCH 1068/3902] drm/apple: Use backlight_get_brightness() Backlight drivers are expected to use this instead of accessing backlight properties. Signed-off-by: Janne Grunau --- drivers/gpu/drm/apple/dcp_backlight.c | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/drivers/gpu/drm/apple/dcp_backlight.c b/drivers/gpu/drm/apple/dcp_backlight.c index 45827fe6041a27..d063ecd7ad2068 100644 --- a/drivers/gpu/drm/apple/dcp_backlight.c +++ b/drivers/gpu/drm/apple/dcp_backlight.c @@ -168,13 +168,11 @@ static int dcp_set_brightness(struct backlight_device *bd) int ret = 0; struct apple_dcp *dcp = bl_get_data(bd); struct drm_modeset_acquire_ctx ctx; - - if (bd->props.state & BL_CORE_SUSPENDED) - return 0; + int brightness = backlight_get_brightness(bd); DRM_MODESET_LOCK_ALL_BEGIN(dcp->crtc->base.dev, ctx, 0, ret); - dcp->brightness.dac = calculate_dac(dcp, bd->props.brightness); + dcp->brightness.dac = calculate_dac(dcp, brightness); dcp->brightness.update = true; /* From 91adbf2e6d2a3d447da4274dd8f4a63a09ae7ab3 Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Sun, 26 Mar 2023 15:56:03 +0200 Subject: [PATCH 1069/3902] drm/apple: Move panel options to its own sub-struct Fixes overwriting the panel's physical dimensions on poweroff/sleep. Signed-off-by: Janne Grunau --- drivers/gpu/drm/apple/dcp-internal.h | 16 +++++++++++++--- drivers/gpu/drm/apple/dcp.c | 21 ++++++++++++++------- drivers/gpu/drm/apple/iomfb.c | 2 +- 3 files changed, 28 insertions(+), 11 deletions(-) diff --git a/drivers/gpu/drm/apple/dcp-internal.h b/drivers/gpu/drm/apple/dcp-internal.h index 85e9137bbdf10e..b34294816ec1ff 100644 --- a/drivers/gpu/drm/apple/dcp-internal.h +++ b/drivers/gpu/drm/apple/dcp-internal.h @@ -80,6 +80,16 @@ struct dcp_brightness { bool update; }; +/** laptop/AiO integrated panel parameters from DT */ +struct dcp_panel { + /// panel width in millimeter + int width_mm; + /// panel height in millimeter + int height_mm; + /// panel has a mini-LED backllight + bool has_mini_led; +}; + /* TODO: move IOMFB members to its own struct */ struct apple_dcp { struct device *dev; @@ -146,9 +156,6 @@ struct apple_dcp { /* clear all surfaces on init */ bool surfaces_cleared; - /* panel has a mini-LED backllight */ - bool has_mini_led; - /* Modes valid for the connected display */ struct dcp_display_mode *modes; unsigned int nr_modes; @@ -173,6 +180,9 @@ struct apple_dcp { /* Workqueue for updating the initial initial brightness */ struct work_struct bl_register_wq; struct mutex bl_register_mutex; + + /* integrated panel if present */ + struct dcp_panel panel; }; int dcp_backlight_register(struct apple_dcp *dcp); diff --git a/drivers/gpu/drm/apple/dcp.c b/drivers/gpu/drm/apple/dcp.c index d5dbe2e47e8b99..ba586206dbe60b 100644 --- a/drivers/gpu/drm/apple/dcp.c +++ b/drivers/gpu/drm/apple/dcp.c @@ -56,14 +56,21 @@ void dcp_drm_crtc_vblank(struct apple_crtc *crtc) void dcp_set_dimensions(struct apple_dcp *dcp) { int i; + int width_mm = dcp->width_mm; + int height_mm = dcp->height_mm; + + if (width_mm == 0 || height_mm == 0) { + width_mm = dcp->panel.width_mm; + height_mm = dcp->panel.height_mm; + } /* Set the connector info */ if (dcp->connector) { struct drm_connector *connector = &dcp->connector->base; mutex_lock(&connector->dev->mode_config.mutex); - connector->display_info.width_mm = dcp->width_mm; - connector->display_info.height_mm = dcp->height_mm; + connector->display_info.width_mm = width_mm; + connector->display_info.height_mm = height_mm; mutex_unlock(&connector->dev->mode_config.mutex); } @@ -73,8 +80,8 @@ void dcp_set_dimensions(struct apple_dcp *dcp) * DisplayAttributes, and TimingElements may be sent first */ for (i = 0; i < dcp->nr_modes; ++i) { - dcp->modes[i].mode.width_mm = dcp->width_mm; - dcp->modes[i].mode.height_mm = dcp->height_mm; + dcp->modes[i].mode.width_mm = width_mm; + dcp->modes[i].mode.height_mm = height_mm; } } @@ -433,7 +440,7 @@ static int dcp_comp_bind(struct device *dev, struct device *main, void *data) dcp->brightness.scale = 65536; panel_np = of_get_compatible_child(dev->of_node, "apple,panel-mini-led"); if (panel_np) - dcp->has_mini_led = true; + dcp->panel.has_mini_led = true; else panel_np = of_get_compatible_child(dev->of_node, "apple,panel"); @@ -447,10 +454,10 @@ static int dcp_comp_bind(struct device *dev, struct device *main, void *data) dev_err(dev, "Missing property 'apple,max-brightness'\n"); } - of_property_read_u32(panel_np, "width-mm", &dcp->width_mm); + of_property_read_u32(panel_np, "width-mm", &dcp->panel.width_mm); /* use adjusted height as long as the notch is hidden */ of_property_read_u32(panel_np, height_prop[!dcp->notch_height], - &dcp->height_mm); + &dcp->panel.height_mm); of_node_put(panel_np); dcp->connector_type = DRM_MODE_CONNECTOR_eDP; diff --git a/drivers/gpu/drm/apple/iomfb.c b/drivers/gpu/drm/apple/iomfb.c index 640e869386d44a..ee88261661f318 100644 --- a/drivers/gpu/drm/apple/iomfb.c +++ b/drivers/gpu/drm/apple/iomfb.c @@ -461,7 +461,7 @@ dcpep_cb_get_uint_prop(struct apple_dcp *dcp, struct dcp_get_uint_prop_req *req) .value = 0 }; - if (dcp->has_mini_led && + if (dcp->panel.has_mini_led && memcmp(req->obj, "SUMP", sizeof(req->obj)) == 0) { /* "PMUS */ if (strncmp(req->key, "Temperature", sizeof(req->key)) == 0) { /* From e04c90207f7a1ab712cb2e8c0ae03a6487863dfa Mon Sep 17 00:00:00 2001 From: Asahi Lina Date: Wed, 22 Mar 2023 16:09:09 +0900 Subject: [PATCH 1070/3902] drm/apple: Align buffers to 16K page size Signed-off-by: Asahi Lina --- drivers/gpu/drm/apple/apple_drv.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/drivers/gpu/drm/apple/apple_drv.c b/drivers/gpu/drm/apple/apple_drv.c index 693a8c59d8a965..d070832f7b1cec 100644 --- a/drivers/gpu/drm/apple/apple_drv.c +++ b/drivers/gpu/drm/apple/apple_drv.c @@ -51,12 +51,14 @@ struct apple_drm_private { DEFINE_DRM_GEM_DMA_FOPS(apple_fops); +#define DART_PAGE_SIZE 16384 + static int apple_drm_gem_dumb_create(struct drm_file *file_priv, struct drm_device *drm, struct drm_mode_create_dumb *args) { args->pitch = ALIGN(DIV_ROUND_UP(args->width * args->bpp, 8), 64); - args->size = args->pitch * args->height; + args->size = round_up(args->pitch * args->height, DART_PAGE_SIZE); return drm_gem_dma_dumb_create_internal(file_priv, drm, args); } From 6bf1b422876b3ba3a7f2c865f45b8e24024c4c16 Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Tue, 28 Feb 2023 20:34:03 +0100 Subject: [PATCH 1071/3902] drm/apple: purge unused dcp_update_notify_clients_dcp Signed-off-by: Janne Grunau --- drivers/gpu/drm/apple/iomfb.c | 5 ----- drivers/gpu/drm/apple/iomfb.h | 18 ------------------ 2 files changed, 23 deletions(-) diff --git a/drivers/gpu/drm/apple/iomfb.c b/drivers/gpu/drm/apple/iomfb.c index ee88261661f318..b7a3c8cb454b3f 100644 --- a/drivers/gpu/drm/apple/iomfb.c +++ b/drivers/gpu/drm/apple/iomfb.c @@ -171,7 +171,6 @@ static u8 dcp_pop_depth(u8 *depth) const struct dcp_method_entry dcp_methods[dcpep_num_methods] = { DCP_METHOD("A000", dcpep_late_init_signal), DCP_METHOD("A029", dcpep_setup_video_limits), - DCP_METHOD("A034", dcpep_update_notify_clients_dcp), DCP_METHOD("A131", iomfbep_a131_pmu_service_matched), DCP_METHOD("A132", iomfbep_a132_backlight_service_matched), DCP_METHOD("A357", dcpep_set_create_dfb), @@ -306,10 +305,6 @@ DCP_THUNK_VOID(dcp_setup_video_limits, dcpep_setup_video_limits); DCP_THUNK_VOID(dcp_set_create_dfb, dcpep_set_create_dfb); DCP_THUNK_VOID(dcp_first_client_open, dcpep_first_client_open); -__attribute__((unused)) -DCP_THUNK_IN(dcp_update_notify_clients_dcp, dcpep_update_notify_clients_dcp, - struct dcp_update_notify_clients_dcp); - DCP_THUNK_INOUT(dcp_set_parameter_dcp, dcpep_set_parameter_dcp, struct dcp_set_parameter_dcp, u32); diff --git a/drivers/gpu/drm/apple/iomfb.h b/drivers/gpu/drm/apple/iomfb.h index a7e9b62425b2c4..54b34fbba94894 100644 --- a/drivers/gpu/drm/apple/iomfb.h +++ b/drivers/gpu/drm/apple/iomfb.h @@ -211,7 +211,6 @@ enum dcpep_method { dcpep_flush_supports_power, dcpep_set_power_state, dcpep_first_client_open, - dcpep_update_notify_clients_dcp, dcpep_set_parameter_dcp, dcpep_enable_disable_video_power_savings, dcpep_is_main_display, @@ -395,23 +394,6 @@ struct dcp_set_dcpav_prop_end_req { char key[0x40]; } __packed; -struct dcp_update_notify_clients_dcp { - u32 client_0; - u32 client_1; - u32 client_2; - u32 client_3; - u32 client_4; - u32 client_5; - u32 client_6; - u32 client_7; - u32 client_8; - u32 client_9; - u32 client_a; - u32 client_b; - u32 client_c; - u32 client_d; -} __packed; - struct dcp_set_parameter_dcp { u32 param; u32 value[8]; From eafcbb246c93d282cd35c061a44efb023972ea35 Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Sun, 8 Jan 2023 21:30:22 +0100 Subject: [PATCH 1072/3902] drm/apple: Add callbacks triggered by last_client_close_dcp() Signed-off-by: Janne Grunau --- drivers/gpu/drm/apple/iomfb.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/drivers/gpu/drm/apple/iomfb.c b/drivers/gpu/drm/apple/iomfb.c index b7a3c8cb454b3f..3691f7bfa4332d 100644 --- a/drivers/gpu/drm/apple/iomfb.c +++ b/drivers/gpu/drm/apple/iomfb.c @@ -1331,9 +1331,14 @@ bool (*const dcpep_cb_handlers[DCPEP_MAX_CB])(struct apple_dcp *, int, void *, [576] = trampoline_hotplug, [577] = trampoline_nop, /* powerstate_notify */ [582] = trampoline_true, /* create_default_fb_surface */ + [584] = trampoline_nop, /* IOMobileFramebufferAP::clear_default_surface */ + [588] = trampoline_nop, /* resize_default_fb_surface_gated */ [589] = trampoline_swap_complete, [591] = trampoline_swap_complete_intent_gated, [593] = trampoline_enable_backlight_message_ap_gated, + [594] = trampoline_nop, /* IOMobileFramebufferAP::setSystemConsoleMode */ + [596] = trampoline_false, /* IOMobileFramebufferAP::isDFBAllocated */ + [597] = trampoline_false, /* IOMobileFramebufferAP::preserveContents */ [598] = trampoline_nop, /* find_swap_function_gated */ }; From 6fc6b865cd37381426e5b2afbd00f5d2953a1ae7 Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Fri, 17 Feb 2023 23:17:10 +0100 Subject: [PATCH 1073/3902] drm/apple: Add support for the macOS 13.2 DCP firmware This adds support for multiple incompatible DCP firmware versions. The approach taken here duplicates more than necessary. Unmodified calls do not need to be templated. For simplicity and in the expectation that more calls and callbacks are modified in the future everything is templated. Signed-off-by: Janne Grunau --- drivers/gpu/drm/apple/Makefile | 2 + drivers/gpu/drm/apple/dcp-internal.h | 11 +- drivers/gpu/drm/apple/dcp.c | 2 + drivers/gpu/drm/apple/iomfb.c | 1476 ++---------------------- drivers/gpu/drm/apple/iomfb.h | 123 +- drivers/gpu/drm/apple/iomfb_internal.h | 123 ++ drivers/gpu/drm/apple/iomfb_template.c | 1344 +++++++++++++++++++++ drivers/gpu/drm/apple/iomfb_template.h | 181 +++ drivers/gpu/drm/apple/iomfb_v12_3.c | 105 ++ drivers/gpu/drm/apple/iomfb_v12_3.h | 17 + drivers/gpu/drm/apple/iomfb_v13_2.c | 105 ++ drivers/gpu/drm/apple/iomfb_v13_2.h | 17 + drivers/gpu/drm/apple/version_utils.h | 15 + 13 files changed, 2025 insertions(+), 1496 deletions(-) create mode 100644 drivers/gpu/drm/apple/iomfb_internal.h create mode 100644 drivers/gpu/drm/apple/iomfb_template.c create mode 100644 drivers/gpu/drm/apple/iomfb_template.h create mode 100644 drivers/gpu/drm/apple/iomfb_v12_3.c create mode 100644 drivers/gpu/drm/apple/iomfb_v12_3.h create mode 100644 drivers/gpu/drm/apple/iomfb_v13_2.c create mode 100644 drivers/gpu/drm/apple/iomfb_v13_2.h create mode 100644 drivers/gpu/drm/apple/version_utils.h diff --git a/drivers/gpu/drm/apple/Makefile b/drivers/gpu/drm/apple/Makefile index a61024ddce4c38..3ee61beeb7857b 100644 --- a/drivers/gpu/drm/apple/Makefile +++ b/drivers/gpu/drm/apple/Makefile @@ -5,6 +5,8 @@ CFLAGS_trace.o = -I$(src) appledrm-y := apple_drv.o apple_dcp-y := dcp.o dcp_backlight.o iomfb.o parser.o +apple_dcp-y += iomfb_v12_3.o +apple_dcp-y += iomfb_v13_2.o apple_dcp-$(CONFIG_TRACING) += trace.o apple_piodma-y := dummy-piodma.o diff --git a/drivers/gpu/drm/apple/dcp-internal.h b/drivers/gpu/drm/apple/dcp-internal.h index b34294816ec1ff..59777809a7f370 100644 --- a/drivers/gpu/drm/apple/dcp-internal.h +++ b/drivers/gpu/drm/apple/dcp-internal.h @@ -11,6 +11,8 @@ #include #include "iomfb.h" +#include "iomfb_v12_3.h" +#include "iomfb_v13_2.h" #define DCP_MAX_PLANES 2 @@ -19,6 +21,7 @@ struct apple_dcp; enum dcp_firmware_version { DCP_FIRMWARE_UNKNOWN, DCP_FIRMWARE_V_12_3, + DCP_FIRMWARE_V_13_2, }; enum { @@ -134,11 +137,17 @@ struct apple_dcp { struct dcp_channel ch_cmd, ch_oobcmd; struct dcp_channel ch_cb, ch_oobcb, ch_async; + /* iomfb EP callback handlers */ + const iomfb_cb_handler *cb_handlers; + /* Active chunked transfer. There can only be one at a time. */ struct dcp_chunks chunks; /* Queued swap. Owned by the DCP to avoid per-swap memory allocation */ - struct dcp_swap_submit_req swap; + union { + struct dcp_swap_submit_req_v12_3 v12_3; + struct dcp_swap_submit_req_v13_2 v13_2; + } swap; /* Current display mode */ bool valid_mode; diff --git a/drivers/gpu/drm/apple/dcp.c b/drivers/gpu/drm/apple/dcp.c index ba586206dbe60b..ec0fab66d67f76 100644 --- a/drivers/gpu/drm/apple/dcp.c +++ b/drivers/gpu/drm/apple/dcp.c @@ -403,6 +403,8 @@ static enum dcp_firmware_version dcp_check_firmware_version(struct device *dev) if (strncmp(compat_str, "12.3.0", sizeof(compat_str)) == 0) return DCP_FIRMWARE_V_12_3; + if (strncmp(compat_str, "13.2.0", sizeof(compat_str)) == 0) + return DCP_FIRMWARE_V_13_2; dev_err(dev, "DCP firmware-compat %s (FW: %s) is not supported\n", compat_str, fw_str); diff --git a/drivers/gpu/drm/apple/iomfb.c b/drivers/gpu/drm/apple/iomfb.c index 3691f7bfa4332d..447de730af721f 100644 --- a/drivers/gpu/drm/apple/iomfb.c +++ b/drivers/gpu/drm/apple/iomfb.c @@ -1,20 +1,20 @@ // SPDX-License-Identifier: GPL-2.0-only OR MIT /* Copyright 2021 Alyssa Rosenzweig */ +#include #include #include #include -#include -#include -#include +#include #include #include #include #include -#include -#include +#include +#include +#include +#include #include -#include #include #include @@ -26,28 +26,10 @@ #include "dcp.h" #include "dcp-internal.h" #include "iomfb.h" +#include "iomfb_internal.h" #include "parser.h" #include "trace.h" -/* Register defines used in bandwidth setup structure */ -#define REG_SCRATCH (0x14) -#define REG_SCRATCH_T600X (0x988) -#define REG_DOORBELL (0x0) -#define REG_DOORBELL_BIT (2) - -struct dcp_wait_cookie { - struct kref refcount; - struct completion done; -}; - -static void release_wait_cookie(struct kref *ref) -{ - struct dcp_wait_cookie *cookie; - cookie = container_of(ref, struct dcp_wait_cookie, refcount); - - kfree(cookie); -} - static int dcp_tx_offset(enum dcp_context_id id) { switch (id) { @@ -166,33 +148,8 @@ static u8 dcp_pop_depth(u8 *depth) return --(*depth); } -#define DCP_METHOD(tag, name) [name] = { #name, tag } - -const struct dcp_method_entry dcp_methods[dcpep_num_methods] = { - DCP_METHOD("A000", dcpep_late_init_signal), - DCP_METHOD("A029", dcpep_setup_video_limits), - DCP_METHOD("A131", iomfbep_a131_pmu_service_matched), - DCP_METHOD("A132", iomfbep_a132_backlight_service_matched), - DCP_METHOD("A357", dcpep_set_create_dfb), - DCP_METHOD("A358", iomfbep_a358_vi_set_temperature_hint), - DCP_METHOD("A401", dcpep_start_signal), - DCP_METHOD("A407", dcpep_swap_start), - DCP_METHOD("A408", dcpep_swap_submit), - DCP_METHOD("A410", dcpep_set_display_device), - DCP_METHOD("A411", dcpep_is_main_display), - DCP_METHOD("A412", dcpep_set_digital_out_mode), - DCP_METHOD("A426", iomfbep_get_color_remap_mode), - DCP_METHOD("A439", dcpep_set_parameter_dcp), - DCP_METHOD("A443", dcpep_create_default_fb), - DCP_METHOD("A447", dcpep_enable_disable_video_power_savings), - DCP_METHOD("A454", dcpep_first_client_open), - DCP_METHOD("A460", dcpep_set_display_refresh_properties), - DCP_METHOD("A463", dcpep_flush_supports_power), - DCP_METHOD("A468", dcpep_set_power_state), -}; - /* Call a DCP function given by a tag */ -static void dcp_push(struct apple_dcp *dcp, bool oob, enum dcpep_method method, +void dcp_push(struct apple_dcp *dcp, bool oob, const struct dcp_method_entry *call, u32 in_len, u32 out_len, void *data, dcp_callback_t cb, void *cookie) { @@ -204,10 +161,10 @@ static void dcp_push(struct apple_dcp *dcp, bool oob, enum dcpep_method method, .out_len = out_len, /* Tag is reversed due to endianness of the fourcc */ - .tag[0] = dcp_methods[method].tag[3], - .tag[1] = dcp_methods[method].tag[2], - .tag[2] = dcp_methods[method].tag[1], - .tag[3] = dcp_methods[method].tag[0], + .tag[0] = call->tag[3], + .tag[1] = call->tag[2], + .tag[2] = call->tag[1], + .tag[3] = call->tag[0], }; u8 depth = dcp_push_depth(&ch->depth); @@ -222,7 +179,7 @@ static void dcp_push(struct apple_dcp *dcp, bool oob, enum dcpep_method method, if (in_len > 0) memcpy(out_data, data, in_len); - trace_iomfb_push(dcp, &dcp_methods[method], context, offset, depth); + trace_iomfb_push(dcp, call, context, offset, depth); ch->callbacks[depth] = cb; ch->cookies[depth] = cookie; @@ -233,88 +190,8 @@ static void dcp_push(struct apple_dcp *dcp, bool oob, enum dcpep_method method, dcpep_msg(context, data_len, offset)); } -#define DCP_THUNK_VOID(func, handle) \ - static void func(struct apple_dcp *dcp, bool oob, dcp_callback_t cb, \ - void *cookie) \ - { \ - dcp_push(dcp, oob, handle, 0, 0, NULL, cb, cookie); \ - } - -#define DCP_THUNK_OUT(func, handle, T) \ - static void func(struct apple_dcp *dcp, bool oob, dcp_callback_t cb, \ - void *cookie) \ - { \ - dcp_push(dcp, oob, handle, 0, sizeof(T), NULL, cb, cookie); \ - } - -#define DCP_THUNK_IN(func, handle, T) \ - static void func(struct apple_dcp *dcp, bool oob, T *data, \ - dcp_callback_t cb, void *cookie) \ - { \ - dcp_push(dcp, oob, handle, sizeof(T), 0, data, cb, cookie); \ - } - -#define DCP_THUNK_INOUT(func, handle, T_in, T_out) \ - static void func(struct apple_dcp *dcp, bool oob, T_in *data, \ - dcp_callback_t cb, void *cookie) \ - { \ - dcp_push(dcp, oob, handle, sizeof(T_in), sizeof(T_out), data, \ - cb, cookie); \ - } - -#define IOMFB_THUNK_INOUT(name) \ - static void iomfb_ ## name(struct apple_dcp *dcp, bool oob, \ - struct iomfb_ ## name ## _req *data, \ - dcp_callback_t cb, void *cookie) \ - { \ - dcp_push(dcp, oob, iomfbep_ ## name, \ - sizeof(struct iomfb_ ## name ## _req), \ - sizeof(struct iomfb_ ## name ## _resp), \ - data, cb, cookie); \ - } - -DCP_THUNK_OUT(iomfb_a131_pmu_service_matched, iomfbep_a131_pmu_service_matched, u32); -DCP_THUNK_OUT(iomfb_a132_backlight_service_matched, iomfbep_a132_backlight_service_matched, u32); -DCP_THUNK_OUT(iomfb_a358_vi_set_temperature_hint, iomfbep_a358_vi_set_temperature_hint, u32); - -IOMFB_THUNK_INOUT(get_color_remap_mode); - -DCP_THUNK_INOUT(dcp_swap_submit, dcpep_swap_submit, struct dcp_swap_submit_req, - struct dcp_swap_submit_resp); - -DCP_THUNK_INOUT(dcp_swap_start, dcpep_swap_start, struct dcp_swap_start_req, - struct dcp_swap_start_resp); - -DCP_THUNK_INOUT(dcp_set_power_state, dcpep_set_power_state, - struct dcp_set_power_state_req, - struct dcp_set_power_state_resp); - -DCP_THUNK_INOUT(dcp_set_digital_out_mode, dcpep_set_digital_out_mode, - struct dcp_set_digital_out_mode_req, u32); - -DCP_THUNK_INOUT(dcp_set_display_device, dcpep_set_display_device, u32, u32); - -DCP_THUNK_OUT(dcp_set_display_refresh_properties, - dcpep_set_display_refresh_properties, u32); - -DCP_THUNK_OUT(dcp_late_init_signal, dcpep_late_init_signal, u32); -DCP_THUNK_IN(dcp_flush_supports_power, dcpep_flush_supports_power, u32); -DCP_THUNK_OUT(dcp_create_default_fb, dcpep_create_default_fb, u32); -DCP_THUNK_OUT(dcp_start_signal, dcpep_start_signal, u32); -DCP_THUNK_VOID(dcp_setup_video_limits, dcpep_setup_video_limits); -DCP_THUNK_VOID(dcp_set_create_dfb, dcpep_set_create_dfb); -DCP_THUNK_VOID(dcp_first_client_open, dcpep_first_client_open); - -DCP_THUNK_INOUT(dcp_set_parameter_dcp, dcpep_set_parameter_dcp, - struct dcp_set_parameter_dcp, u32); - -DCP_THUNK_INOUT(dcp_enable_disable_video_power_savings, - dcpep_enable_disable_video_power_savings, u32, int); - -DCP_THUNK_OUT(dcp_is_main_display, dcpep_is_main_display, u32); - /* Parse a callback tag "D123" into the ID 123. Returns -EINVAL on failure. */ -static int dcp_parse_tag(char tag[4]) +int dcp_parse_tag(char tag[4]) { u32 d[3]; int i; @@ -333,7 +210,7 @@ static int dcp_parse_tag(char tag[4]) } /* Ack a callback from the DCP */ -static void dcp_ack(struct apple_dcp *dcp, enum dcp_context_id context) +void dcp_ack(struct apple_dcp *dcp, enum dcp_context_id context) { struct dcp_channel *ch = dcp_get_channel(dcp, context); @@ -342,776 +219,54 @@ static void dcp_ack(struct apple_dcp *dcp, enum dcp_context_id context) dcpep_ack(context)); } -/* DCP callback handlers */ -static void dcpep_cb_nop(struct apple_dcp *dcp) -{ - /* No operation */ -} - -static u8 dcpep_cb_true(struct apple_dcp *dcp) -{ - return true; -} - -static u8 dcpep_cb_false(struct apple_dcp *dcp) -{ - return false; -} - -static u32 dcpep_cb_zero(struct apple_dcp *dcp) -{ - return 0; -} - -static void dcpep_cb_swap_complete(struct apple_dcp *dcp, - struct dc_swap_complete_resp *resp) -{ - trace_iomfb_swap_complete(dcp, resp->swap_id); - - dcp_drm_crtc_vblank(dcp->crtc); -} - -/* special */ -static void complete_vi_set_temperature_hint(struct apple_dcp *dcp, void *out, void *cookie) -{ - // ack D100 cb_match_pmu_service - dcp_ack(dcp, DCP_CONTEXT_CB); -} - -static bool iomfbep_cb_match_pmu_service(struct apple_dcp *dcp, int tag, void *out, void *in) -{ - trace_iomfb_callback(dcp, tag, __func__); - iomfb_a358_vi_set_temperature_hint(dcp, false, - complete_vi_set_temperature_hint, - NULL); - - // return false for deferred ACK - return false; -} - -static void complete_pmu_service_matched(struct apple_dcp *dcp, void *out, void *cookie) +void dcp_sleep(struct apple_dcp *dcp) { - struct dcp_channel *ch = &dcp->ch_cb; - u8 *succ = ch->output[ch->depth - 1]; - - *succ = true; - - // ack D206 cb_match_pmu_service_2 - dcp_ack(dcp, DCP_CONTEXT_CB); -} - -static bool iomfbep_cb_match_pmu_service_2(struct apple_dcp *dcp, int tag, void *out, void *in) -{ - trace_iomfb_callback(dcp, tag, __func__); - - iomfb_a131_pmu_service_matched(dcp, false, complete_pmu_service_matched, - out); - - // return false for deferred ACK - return false; -} - -static void complete_backlight_service_matched(struct apple_dcp *dcp, void *out, void *cookie) -{ - struct dcp_channel *ch = &dcp->ch_cb; - u8 *succ = ch->output[ch->depth - 1]; - - *succ = true; - - // ack D206 cb_match_backlight_service - dcp_ack(dcp, DCP_CONTEXT_CB); -} - -static bool iomfbep_cb_match_backlight_service(struct apple_dcp *dcp, int tag, void *out, void *in) -{ - trace_iomfb_callback(dcp, tag, __func__); - - iomfb_a132_backlight_service_matched(dcp, false, complete_backlight_service_matched, out); - - // return false for deferred ACK - return false; -} - -static void iomfb_cb_pr_publish(struct apple_dcp *dcp, struct iomfb_property *prop) -{ - switch (prop->id) { - case IOMFB_PROPERTY_NITS: - { - dcp->brightness.nits = prop->value / dcp->brightness.scale; - /* notify backlight device of the initial brightness */ - if (!dcp->brightness.bl_dev && dcp->brightness.maximum > 0) - schedule_work(&dcp->bl_register_wq); - trace_iomfb_brightness(dcp, prop->value); + switch (dcp->fw_compat) { + case DCP_FIRMWARE_V_12_3: + iomfb_sleep_v12_3(dcp); + break; + case DCP_FIRMWARE_V_13_2: + iomfb_sleep_v13_2(dcp); break; - } default: - dev_dbg(dcp->dev, "pr_publish: id: %d = %u\n", prop->id, prop->value); - } -} - -static struct dcp_get_uint_prop_resp -dcpep_cb_get_uint_prop(struct apple_dcp *dcp, struct dcp_get_uint_prop_req *req) -{ - struct dcp_get_uint_prop_resp resp = (struct dcp_get_uint_prop_resp){ - .value = 0 - }; - - if (dcp->panel.has_mini_led && - memcmp(req->obj, "SUMP", sizeof(req->obj)) == 0) { /* "PMUS */ - if (strncmp(req->key, "Temperature", sizeof(req->key)) == 0) { - /* - * TODO: value from j314c, find out if it is temperature in - * centigrade C and which temperature sensor reports it - */ - resp.value = 3029; - resp.ret = true; - } - } - - return resp; -} - -static u8 iomfbep_cb_sr_set_property_int(struct apple_dcp *dcp, - struct iomfb_sr_set_property_int_req *req) -{ - if (memcmp(req->obj, "FMOI", sizeof(req->obj)) == 0) { /* "IOMF */ - if (strncmp(req->key, "Brightness_Scale", sizeof(req->key)) == 0) { - if (!req->value_null) - dcp->brightness.scale = req->value; - } - } - - return 1; -} - -static void iomfbep_cb_set_fx_prop(struct apple_dcp *dcp, struct iomfb_set_fx_prop_req *req) -{ - // TODO: trace this, see if there properties which needs to used later -} - -/* - * Callback to map a buffer allocated with allocate_buf for PIODMA usage. - * PIODMA is separate from the main DCP and uses own IOVA space on a dedicated - * stream of the display DART, rather than the expected DCP DART. - * - * XXX: This relies on dma_get_sgtable in concert with dma_map_sgtable, which - * is a "fundamentally unsafe" operation according to the docs. And yet - * everyone does it... - */ -static struct dcp_map_buf_resp dcpep_cb_map_piodma(struct apple_dcp *dcp, - struct dcp_map_buf_req *req) -{ - struct sg_table *map; - int ret; - - if (req->buffer >= ARRAY_SIZE(dcp->memdesc)) - goto reject; - - map = &dcp->memdesc[req->buffer].map; - - if (!map->sgl) - goto reject; - - /* Use PIODMA device instead of DCP to map against the right IOMMU. */ - ret = dma_map_sgtable(&dcp->piodma->dev, map, DMA_BIDIRECTIONAL, 0); - - if (ret) - goto reject; - - return (struct dcp_map_buf_resp){ .dva = sg_dma_address(map->sgl) }; - -reject: - dev_err(dcp->dev, "denying map of invalid buffer %llx for pidoma\n", - req->buffer); - return (struct dcp_map_buf_resp){ .ret = EINVAL }; -} - -static void dcpep_cb_unmap_piodma(struct apple_dcp *dcp, - struct dcp_unmap_buf_resp *resp) -{ - struct sg_table *map; - dma_addr_t dma_addr; - - if (resp->buffer >= ARRAY_SIZE(dcp->memdesc)) { - dev_warn(dcp->dev, "unmap request for out of range buffer %llu", - resp->buffer); - return; - } - - map = &dcp->memdesc[resp->buffer].map; - - if (!map->sgl) { - dev_warn(dcp->dev, - "unmap for non-mapped buffer %llu iova:0x%08llx", - resp->buffer, resp->dva); - return; - } - - dma_addr = sg_dma_address(map->sgl); - if (dma_addr != resp->dva) { - dev_warn(dcp->dev, "unmap buffer %llu address mismatch dma_addr:%llx dva:%llx", - resp->buffer, dma_addr, resp->dva); - return; - } - - /* Use PIODMA device instead of DCP to unmap from the right IOMMU. */ - dma_unmap_sgtable(&dcp->piodma->dev, map, DMA_BIDIRECTIONAL, 0); -} - -/* - * Allocate an IOVA contiguous buffer mapped to the DCP. The buffer need not be - * physically contigiuous, however we should save the sgtable in case the - * buffer needs to be later mapped for PIODMA. - */ -static struct dcp_allocate_buffer_resp -dcpep_cb_allocate_buffer(struct apple_dcp *dcp, - struct dcp_allocate_buffer_req *req) -{ - struct dcp_allocate_buffer_resp resp = { 0 }; - struct dcp_mem_descriptor *memdesc; - u32 id; - - resp.dva_size = ALIGN(req->size, 4096); - resp.mem_desc_id = - find_first_zero_bit(dcp->memdesc_map, DCP_MAX_MAPPINGS); - - if (resp.mem_desc_id >= DCP_MAX_MAPPINGS) { - dev_warn(dcp->dev, "DCP overflowed mapping table, ignoring"); - resp.dva_size = 0; - resp.mem_desc_id = 0; - return resp; - } - id = resp.mem_desc_id; - set_bit(id, dcp->memdesc_map); - - memdesc = &dcp->memdesc[id]; - - memdesc->size = resp.dva_size; - memdesc->buf = dma_alloc_coherent(dcp->dev, memdesc->size, - &memdesc->dva, GFP_KERNEL); - - dma_get_sgtable(dcp->dev, &memdesc->map, memdesc->buf, memdesc->dva, - memdesc->size); - resp.dva = memdesc->dva; - - return resp; -} - -static u8 dcpep_cb_release_mem_desc(struct apple_dcp *dcp, u32 *mem_desc_id) -{ - struct dcp_mem_descriptor *memdesc; - u32 id = *mem_desc_id; - - if (id >= DCP_MAX_MAPPINGS) { - dev_warn(dcp->dev, - "unmap request for out of range mem_desc_id %u", id); - return 0; - } - - if (!test_and_clear_bit(id, dcp->memdesc_map)) { - dev_warn(dcp->dev, "unmap request for unused mem_desc_id %u", - id); - return 0; - } - - memdesc = &dcp->memdesc[id]; - if (memdesc->buf) { - dma_free_coherent(dcp->dev, memdesc->size, memdesc->buf, - memdesc->dva); - - memdesc->buf = NULL; - memset(&memdesc->map, 0, sizeof(memdesc->map)); - } else { - memdesc->reg = 0; - } - - memdesc->size = 0; - - return 1; -} - -/* Validate that the specified region is a display register */ -static bool is_disp_register(struct apple_dcp *dcp, u64 start, u64 end) -{ - int i; - - for (i = 0; i < dcp->nr_disp_registers; ++i) { - struct resource *r = dcp->disp_registers[i]; - - if ((start >= r->start) && (end <= r->end)) - return true; - } - - return false; -} - -/* - * Map contiguous physical memory into the DCP's address space. The firmware - * uses this to map the display registers we advertise in - * sr_map_device_memory_with_index, so we bounds check against that to guard - * safe against malicious coprocessors. - */ -static struct dcp_map_physical_resp -dcpep_cb_map_physical(struct apple_dcp *dcp, struct dcp_map_physical_req *req) -{ - int size = ALIGN(req->size, 4096); - u32 id; - - if (!is_disp_register(dcp, req->paddr, req->paddr + size - 1)) { - dev_err(dcp->dev, "refusing to map phys address %llx size %llx", - req->paddr, req->size); - return (struct dcp_map_physical_resp){}; - } - - id = find_first_zero_bit(dcp->memdesc_map, DCP_MAX_MAPPINGS); - set_bit(id, dcp->memdesc_map); - dcp->memdesc[id].size = size; - dcp->memdesc[id].reg = req->paddr; - - return (struct dcp_map_physical_resp){ - .dva_size = size, - .mem_desc_id = id, - .dva = dma_map_resource(dcp->dev, req->paddr, size, - DMA_BIDIRECTIONAL, 0), - }; -} - -static u64 dcpep_cb_get_frequency(struct apple_dcp *dcp) -{ - return clk_get_rate(dcp->clk); -} - -static struct dcp_map_reg_resp dcpep_cb_map_reg(struct apple_dcp *dcp, - struct dcp_map_reg_req *req) -{ - if (req->index >= dcp->nr_disp_registers) { - dev_warn(dcp->dev, "attempted to read invalid reg index %u", - req->index); - - return (struct dcp_map_reg_resp){ .ret = 1 }; - } else { - struct resource *rsrc = dcp->disp_registers[req->index]; - - return (struct dcp_map_reg_resp){ - .addr = rsrc->start, .length = resource_size(rsrc) - }; - } -} - -static struct dcp_read_edt_data_resp -dcpep_cb_read_edt_data(struct apple_dcp *dcp, struct dcp_read_edt_data_req *req) -{ - return (struct dcp_read_edt_data_resp){ - .value[0] = req->value[0], - .ret = 0, - }; -} - -static void iomfbep_cb_enable_backlight_message_ap_gated(struct apple_dcp *dcp, - u8 *enabled) -{ - /* - * update backlight brightness on next swap, on non mini-LED displays - * DCP seems to set an invalid iDAC value after coming out of DPMS. - * syslog: "[BrightnessLCD.cpp:743][AFK]nitsToDBV: iDAC out of range" - */ - dcp->brightness.update = true; -} - -/* Chunked data transfer for property dictionaries */ -static u8 dcpep_cb_prop_start(struct apple_dcp *dcp, u32 *length) -{ - if (dcp->chunks.data != NULL) { - dev_warn(dcp->dev, "ignoring spurious transfer start\n"); - return false; - } - - dcp->chunks.length = *length; - dcp->chunks.data = devm_kzalloc(dcp->dev, *length, GFP_KERNEL); - - if (!dcp->chunks.data) { - dev_warn(dcp->dev, "failed to allocate chunks\n"); - return false; - } - - return true; -} - -static u8 dcpep_cb_prop_chunk(struct apple_dcp *dcp, - struct dcp_set_dcpav_prop_chunk_req *req) -{ - if (!dcp->chunks.data) { - dev_warn(dcp->dev, "ignoring spurious chunk\n"); - return false; - } - - if (req->offset + req->length > dcp->chunks.length) { - dev_warn(dcp->dev, "ignoring overflowing chunk\n"); - return false; - } - - memcpy(dcp->chunks.data + req->offset, req->data, req->length); - return true; -} - -static bool dcpep_process_chunks(struct apple_dcp *dcp, - struct dcp_set_dcpav_prop_end_req *req) -{ - struct dcp_parse_ctx ctx; - int ret; - - if (!dcp->chunks.data) { - dev_warn(dcp->dev, "ignoring spurious end\n"); - return false; - } - - /* used just as opaque pointer for tracing */ - ctx.dcp = dcp; - - ret = parse(dcp->chunks.data, dcp->chunks.length, &ctx); - - if (ret) { - dev_warn(dcp->dev, "bad header on dcpav props\n"); - return false; - } - - if (!strcmp(req->key, "TimingElements")) { - dcp->modes = enumerate_modes(&ctx, &dcp->nr_modes, - dcp->width_mm, dcp->height_mm, - dcp->notch_height); - - if (IS_ERR(dcp->modes)) { - dev_warn(dcp->dev, "failed to parse modes\n"); - dcp->modes = NULL; - dcp->nr_modes = 0; - return false; - } - } else if (!strcmp(req->key, "DisplayAttributes")) { - /* DisplayAttributes are empty for integrated displays, use - * display dimensions read from the devicetree - */ - if (dcp->main_display) { - ret = parse_display_attributes(&ctx, &dcp->width_mm, - &dcp->height_mm); - - if (ret) { - dev_warn(dcp->dev, "failed to parse display attribs\n"); - return false; - } - } - - dcp_set_dimensions(dcp); - } - - return true; -} - -static u8 dcpep_cb_prop_end(struct apple_dcp *dcp, - struct dcp_set_dcpav_prop_end_req *req) -{ - u8 resp = dcpep_process_chunks(dcp, req); - - /* Reset for the next transfer */ - devm_kfree(dcp->dev, dcp->chunks.data); - dcp->chunks.data = NULL; - - return resp; -} - -/* Boot sequence */ -static void boot_done(struct apple_dcp *dcp, void *out, void *cookie) -{ - struct dcp_channel *ch = &dcp->ch_cb; - u8 *succ = ch->output[ch->depth - 1]; - dev_dbg(dcp->dev, "boot done"); - - *succ = true; - dcp_ack(dcp, DCP_CONTEXT_CB); -} - -static void boot_5(struct apple_dcp *dcp, void *out, void *cookie) -{ - dcp_set_display_refresh_properties(dcp, false, boot_done, NULL); -} - -static void boot_4(struct apple_dcp *dcp, void *out, void *cookie) -{ - dcp_late_init_signal(dcp, false, boot_5, NULL); -} - -static void boot_3(struct apple_dcp *dcp, void *out, void *cookie) -{ - u32 v_true = true; - - dcp_flush_supports_power(dcp, false, &v_true, boot_4, NULL); -} - -static void boot_2(struct apple_dcp *dcp, void *out, void *cookie) -{ - dcp_setup_video_limits(dcp, false, boot_3, NULL); -} - -static void boot_1_5(struct apple_dcp *dcp, void *out, void *cookie) -{ - dcp_create_default_fb(dcp, false, boot_2, NULL); -} - -/* Use special function signature to defer the ACK */ -static bool dcpep_cb_boot_1(struct apple_dcp *dcp, int tag, void *out, void *in) -{ - trace_iomfb_callback(dcp, tag, __func__); - dcp_set_create_dfb(dcp, false, boot_1_5, NULL); - return false; -} - -static struct dcp_rt_bandwidth dcpep_cb_rt_bandwidth(struct apple_dcp *dcp) -{ - if (dcp->disp_registers[5] && dcp->disp_registers[6]) - return (struct dcp_rt_bandwidth){ - .reg_scratch = - dcp->disp_registers[5]->start + REG_SCRATCH, - .reg_doorbell = - dcp->disp_registers[6]->start + REG_DOORBELL, - .doorbell_bit = REG_DOORBELL_BIT, - - .padding[3] = 0x4, // XXX: required by 11.x firmware - }; - else if (dcp->disp_registers[4]) - return (struct dcp_rt_bandwidth){ - .reg_scratch = dcp->disp_registers[4]->start + - REG_SCRATCH_T600X, - .reg_doorbell = 0, - .doorbell_bit = 0, - }; - else - return (struct dcp_rt_bandwidth){ - .reg_scratch = 0, - .reg_doorbell = 0, - .doorbell_bit = 0, - }; -} - -/* Callback to get the current time as milliseconds since the UNIX epoch */ -static u64 dcpep_cb_get_time(struct apple_dcp *dcp) -{ - return ktime_to_ms(ktime_get_real()); -} - -struct dcp_swap_cookie { - struct kref refcount; - struct completion done; - u32 swap_id; -}; - -static void release_swap_cookie(struct kref *ref) -{ - struct dcp_swap_cookie *cookie; - cookie = container_of(ref, struct dcp_swap_cookie, refcount); - - kfree(cookie); -} - -static void dcp_swap_cleared(struct apple_dcp *dcp, void *data, void *cookie) -{ - struct dcp_swap_submit_resp *resp = data; - dev_dbg(dcp->dev, "%s", __func__); - - if (cookie) { - struct dcp_swap_cookie *info = cookie; - complete(&info->done); - kref_put(&info->refcount, release_swap_cookie); - } - - if (resp->ret) { - dev_err(dcp->dev, "swap_clear failed! status %u\n", resp->ret); - dcp_drm_crtc_vblank(dcp->crtc); - return; - } - - while (!list_empty(&dcp->swapped_out_fbs)) { - struct dcp_fb_reference *entry; - entry = list_first_entry(&dcp->swapped_out_fbs, - struct dcp_fb_reference, head); - if (entry->fb) - drm_framebuffer_put(entry->fb); - list_del(&entry->head); - kfree(entry); - } -} - -static void dcp_swap_clear_started(struct apple_dcp *dcp, void *data, - void *cookie) -{ - struct dcp_swap_start_resp *resp = data; - dev_dbg(dcp->dev, "%s swap_id: %u", __func__, resp->swap_id); - dcp->swap.swap.swap_id = resp->swap_id; - - if (cookie) { - struct dcp_swap_cookie *info = cookie; - info->swap_id = resp->swap_id; - } - - dcp_swap_submit(dcp, false, &dcp->swap, dcp_swap_cleared, cookie); -} - -static void dcp_on_final(struct apple_dcp *dcp, void *out, void *cookie) -{ - struct dcp_wait_cookie *wait = cookie; - dev_dbg(dcp->dev, "%s", __func__); - - if (wait) { - complete(&wait->done); - kref_put(&wait->refcount, release_wait_cookie); + WARN_ONCE(true, "Unexpected firmware version: %u\n", dcp->fw_compat); + break; } } -static void dcp_on_set_power_state(struct apple_dcp *dcp, void *out, void *cookie) -{ - struct dcp_set_power_state_req req = { - .unklong = 1, - }; - dev_dbg(dcp->dev, "%s", __func__); - - dcp_set_power_state(dcp, false, &req, dcp_on_final, cookie); -} - -static void dcp_on_set_parameter(struct apple_dcp *dcp, void *out, void *cookie) -{ - struct dcp_set_parameter_dcp param = { - .param = 14, - .value = { 0 }, - .count = 1, - }; - dev_dbg(dcp->dev, "%s", __func__); - - dcp_set_parameter_dcp(dcp, false, ¶m, dcp_on_set_power_state, cookie); -} - void dcp_poweron(struct platform_device *pdev) { struct apple_dcp *dcp = platform_get_drvdata(pdev); - struct dcp_wait_cookie *cookie; - int ret; - u32 handle; - dev_dbg(dcp->dev, "%s", __func__); - cookie = kzalloc(sizeof(*cookie), GFP_KERNEL); - if (!cookie) - return; - - init_completion(&cookie->done); - kref_init(&cookie->refcount); - /* increase refcount to ensure the receiver has a reference */ - kref_get(&cookie->refcount); - - if (dcp->main_display) { - handle = 0; - dcp_set_display_device(dcp, false, &handle, dcp_on_set_power_state, - cookie); - } else { - handle = 2; - dcp_set_display_device(dcp, false, &handle, - dcp_on_set_parameter, cookie); + switch (dcp->fw_compat) { + case DCP_FIRMWARE_V_12_3: + iomfb_poweron_v12_3(dcp); + break; + case DCP_FIRMWARE_V_13_2: + iomfb_poweron_v13_2(dcp); + break; + default: + WARN_ONCE(true, "Unexpected firmware version: %u\n", dcp->fw_compat); + break; } - ret = wait_for_completion_timeout(&cookie->done, msecs_to_jiffies(500)); - - if (ret == 0) - dev_warn(dcp->dev, "wait for power timed out"); - - kref_put(&cookie->refcount, release_wait_cookie);; - - /* Force a brightness update after poweron, to restore the brightness */ - dcp->brightness.update = true; } EXPORT_SYMBOL(dcp_poweron); -static void complete_set_powerstate(struct apple_dcp *dcp, void *out, - void *cookie) -{ - struct dcp_wait_cookie *wait = cookie; - - if (wait) { - complete(&wait->done); - kref_put(&wait->refcount, release_wait_cookie); - } -} - void dcp_poweroff(struct platform_device *pdev) { struct apple_dcp *dcp = platform_get_drvdata(pdev); - int ret, swap_id; - struct dcp_set_power_state_req power_req = { - .unklong = 0, - }; - struct dcp_swap_cookie *cookie; - struct dcp_wait_cookie *poff_cookie; - struct dcp_swap_start_req swap_req = { 0 }; - - dev_dbg(dcp->dev, "%s", __func__); - - cookie = kzalloc(sizeof(*cookie), GFP_KERNEL); - if (!cookie) - return; - init_completion(&cookie->done); - kref_init(&cookie->refcount); - /* increase refcount to ensure the receiver has a reference */ - kref_get(&cookie->refcount); - // clear surfaces - memset(&dcp->swap, 0, sizeof(dcp->swap)); - - dcp->swap.swap.swap_enabled = - dcp->swap.swap.swap_completed = IOMFB_SET_BACKGROUND | 0xF; - dcp->swap.swap.bg_color = 0xFF000000; - - /* - * Turn off the backlight. This matters because the DCP's idea of - * backlight brightness gets desynced after a power change, and it - * needs to be told it's going to turn off so it will consider the - * subsequent update on poweron an actual change and restore the - * brightness. - */ - dcp->swap.swap.bl_unk = 1; - dcp->swap.swap.bl_value = 0; - dcp->swap.swap.bl_power = 0; - - for (int l = 0; l < SWAP_SURFACES; l++) - dcp->swap.surf_null[l] = true; - - dcp_swap_start(dcp, false, &swap_req, dcp_swap_clear_started, cookie); - - ret = wait_for_completion_timeout(&cookie->done, msecs_to_jiffies(50)); - swap_id = cookie->swap_id; - kref_put(&cookie->refcount, release_swap_cookie); - if (ret <= 0) { - dcp->crashed = true; - return; + switch (dcp->fw_compat) { + case DCP_FIRMWARE_V_12_3: + iomfb_poweroff_v12_3(dcp); + break; + case DCP_FIRMWARE_V_13_2: + iomfb_poweroff_v13_2(dcp); + break; + default: + WARN_ONCE(true, "Unexpected firmware version: %u\n", dcp->fw_compat); + break; } - - dev_dbg(dcp->dev, "%s: clear swap submitted: %u", __func__, swap_id); - - poff_cookie = kzalloc(sizeof(*poff_cookie), GFP_KERNEL); - if (!poff_cookie) - return; - init_completion(&poff_cookie->done); - kref_init(&poff_cookie->refcount); - /* increase refcount to ensure the receiver has a reference */ - kref_get(&poff_cookie->refcount); - - dcp_set_power_state(dcp, false, &power_req, complete_set_powerstate, - poff_cookie); - ret = wait_for_completion_timeout(&poff_cookie->done, - msecs_to_jiffies(1000)); - - if (ret == 0) - dev_warn(dcp->dev, "setPowerState(0) timeout %u ms", 1000); - else if (ret > 0) - dev_dbg(dcp->dev, - "setPowerState(0) finished with %d ms to spare", - jiffies_to_msecs(ret)); - - kref_put(&poff_cookie->refcount, release_wait_cookie); - dev_dbg(dcp->dev, "%s: setPowerState(0) done", __func__); } EXPORT_SYMBOL(dcp_poweroff); @@ -1149,199 +304,6 @@ void dcp_hotplug(struct work_struct *work) } EXPORT_SYMBOL_GPL(dcp_hotplug); -static void dcpep_cb_hotplug(struct apple_dcp *dcp, u64 *connected) -{ - struct apple_connector *connector = dcp->connector; - - /* DCP issues hotplug_gated callbacks after SetPowerState() calls on - * devices with display (macbooks, imacs). This must not result in - * connector state changes on DRM side. Some applications won't enable - * a CRTC with a connector in disconnected state. Weston after DPMS off - * is one example. dcp_is_main_display() returns true on devices with - * integrated display. Ignore the hotplug_gated() callbacks there. - */ - if (dcp->main_display) - return; - - /* Hotplug invalidates mode. DRM doesn't always handle this. */ - if (!(*connected)) { - dcp->valid_mode = false; - /* after unplug swap will not complete until the next - * set_digital_out_mode */ - schedule_work(&dcp->vblank_wq); - } - - if (connector && connector->connected != !!(*connected)) { - connector->connected = !!(*connected); - dcp->valid_mode = false; - schedule_work(&connector->hotplug_wq); - } -} - -static void -dcpep_cb_swap_complete_intent_gated(struct apple_dcp *dcp, - struct dcp_swap_complete_intent_gated *info) -{ - trace_iomfb_swap_complete_intent_gated(dcp, info->swap_id, - info->width, info->height); -} - -#define DCPEP_MAX_CB (1000) - -/* - * Define type-safe trampolines. Define typedefs to enforce type-safety on the - * input data (so if the types don't match, gcc errors out). - */ - -#define TRAMPOLINE_VOID(func, handler) \ - static bool func(struct apple_dcp *dcp, int tag, void *out, void *in) \ - { \ - trace_iomfb_callback(dcp, tag, #handler); \ - handler(dcp); \ - return true; \ - } - -#define TRAMPOLINE_IN(func, handler, T_in) \ - typedef void (*callback_##handler)(struct apple_dcp *, T_in *); \ - \ - static bool func(struct apple_dcp *dcp, int tag, void *out, void *in) \ - { \ - callback_##handler cb = handler; \ - \ - trace_iomfb_callback(dcp, tag, #handler); \ - cb(dcp, in); \ - return true; \ - } - -#define TRAMPOLINE_INOUT(func, handler, T_in, T_out) \ - typedef T_out (*callback_##handler)(struct apple_dcp *, T_in *); \ - \ - static bool func(struct apple_dcp *dcp, int tag, void *out, void *in) \ - { \ - T_out *typed_out = out; \ - callback_##handler cb = handler; \ - \ - trace_iomfb_callback(dcp, tag, #handler); \ - *typed_out = cb(dcp, in); \ - return true; \ - } - -#define TRAMPOLINE_OUT(func, handler, T_out) \ - static bool func(struct apple_dcp *dcp, int tag, void *out, void *in) \ - { \ - T_out *typed_out = out; \ - \ - trace_iomfb_callback(dcp, tag, #handler); \ - *typed_out = handler(dcp); \ - return true; \ - } - -TRAMPOLINE_VOID(trampoline_nop, dcpep_cb_nop); -TRAMPOLINE_OUT(trampoline_true, dcpep_cb_true, u8); -TRAMPOLINE_OUT(trampoline_false, dcpep_cb_false, u8); -TRAMPOLINE_OUT(trampoline_zero, dcpep_cb_zero, u32); -TRAMPOLINE_IN(trampoline_swap_complete, dcpep_cb_swap_complete, - struct dc_swap_complete_resp); -TRAMPOLINE_INOUT(trampoline_get_uint_prop, dcpep_cb_get_uint_prop, - struct dcp_get_uint_prop_req, struct dcp_get_uint_prop_resp); -TRAMPOLINE_IN(trampoline_set_fx_prop, iomfbep_cb_set_fx_prop, - struct iomfb_set_fx_prop_req) -TRAMPOLINE_INOUT(trampoline_map_piodma, dcpep_cb_map_piodma, - struct dcp_map_buf_req, struct dcp_map_buf_resp); -TRAMPOLINE_IN(trampoline_unmap_piodma, dcpep_cb_unmap_piodma, - struct dcp_unmap_buf_resp); -TRAMPOLINE_INOUT(trampoline_sr_set_property_int, iomfbep_cb_sr_set_property_int, - struct iomfb_sr_set_property_int_req, u8); -TRAMPOLINE_INOUT(trampoline_allocate_buffer, dcpep_cb_allocate_buffer, - struct dcp_allocate_buffer_req, - struct dcp_allocate_buffer_resp); -TRAMPOLINE_INOUT(trampoline_map_physical, dcpep_cb_map_physical, - struct dcp_map_physical_req, struct dcp_map_physical_resp); -TRAMPOLINE_INOUT(trampoline_release_mem_desc, dcpep_cb_release_mem_desc, u32, - u8); -TRAMPOLINE_INOUT(trampoline_map_reg, dcpep_cb_map_reg, struct dcp_map_reg_req, - struct dcp_map_reg_resp); -TRAMPOLINE_INOUT(trampoline_read_edt_data, dcpep_cb_read_edt_data, - struct dcp_read_edt_data_req, struct dcp_read_edt_data_resp); -TRAMPOLINE_INOUT(trampoline_prop_start, dcpep_cb_prop_start, u32, u8); -TRAMPOLINE_INOUT(trampoline_prop_chunk, dcpep_cb_prop_chunk, - struct dcp_set_dcpav_prop_chunk_req, u8); -TRAMPOLINE_INOUT(trampoline_prop_end, dcpep_cb_prop_end, - struct dcp_set_dcpav_prop_end_req, u8); -TRAMPOLINE_OUT(trampoline_rt_bandwidth, dcpep_cb_rt_bandwidth, - struct dcp_rt_bandwidth); -TRAMPOLINE_OUT(trampoline_get_frequency, dcpep_cb_get_frequency, u64); -TRAMPOLINE_OUT(trampoline_get_time, dcpep_cb_get_time, u64); -TRAMPOLINE_IN(trampoline_hotplug, dcpep_cb_hotplug, u64); -TRAMPOLINE_IN(trampoline_swap_complete_intent_gated, - dcpep_cb_swap_complete_intent_gated, - struct dcp_swap_complete_intent_gated); -TRAMPOLINE_IN(trampoline_enable_backlight_message_ap_gated, - iomfbep_cb_enable_backlight_message_ap_gated, u8); -TRAMPOLINE_IN(trampoline_pr_publish, iomfb_cb_pr_publish, - struct iomfb_property); - -bool (*const dcpep_cb_handlers[DCPEP_MAX_CB])(struct apple_dcp *, int, void *, - void *) = { - [0] = trampoline_true, /* did_boot_signal */ - [1] = trampoline_true, /* did_power_on_signal */ - [2] = trampoline_nop, /* will_power_off_signal */ - [3] = trampoline_rt_bandwidth, - [100] = iomfbep_cb_match_pmu_service, - [101] = trampoline_zero, /* get_display_default_stride */ - [102] = trampoline_nop, /* set_number_property */ - [103] = trampoline_nop, /* set_boolean_property */ - [106] = trampoline_nop, /* remove_property */ - [107] = trampoline_true, /* create_provider_service */ - [108] = trampoline_true, /* create_product_service */ - [109] = trampoline_true, /* create_pmu_service */ - [110] = trampoline_true, /* create_iomfb_service */ - [111] = trampoline_true, /* create_backlight_service */ - [116] = dcpep_cb_boot_1, - [117] = trampoline_false, /* is_dark_boot */ - [118] = trampoline_false, /* is_dark_boot / is_waking_from_hibernate*/ - [120] = trampoline_read_edt_data, - [122] = trampoline_prop_start, - [123] = trampoline_prop_chunk, - [124] = trampoline_prop_end, - [201] = trampoline_map_piodma, - [202] = trampoline_unmap_piodma, - [206] = iomfbep_cb_match_pmu_service_2, - [207] = iomfbep_cb_match_backlight_service, - [208] = trampoline_get_time, - [211] = trampoline_nop, /* update_backlight_factor_prop */ - [300] = trampoline_pr_publish, - [401] = trampoline_get_uint_prop, - [404] = trampoline_nop, /* sr_set_uint_prop */ - [406] = trampoline_set_fx_prop, - [408] = trampoline_get_frequency, - [411] = trampoline_map_reg, - [413] = trampoline_true, /* sr_set_property_dict */ - [414] = trampoline_sr_set_property_int, - [415] = trampoline_true, /* sr_set_property_bool */ - [451] = trampoline_allocate_buffer, - [452] = trampoline_map_physical, - [456] = trampoline_release_mem_desc, - [552] = trampoline_true, /* set_property_dict_0 */ - [561] = trampoline_true, /* set_property_dict */ - [563] = trampoline_true, /* set_property_int */ - [565] = trampoline_true, /* set_property_bool */ - [567] = trampoline_true, /* set_property_str */ - [574] = trampoline_zero, /* power_up_dart */ - [576] = trampoline_hotplug, - [577] = trampoline_nop, /* powerstate_notify */ - [582] = trampoline_true, /* create_default_fb_surface */ - [584] = trampoline_nop, /* IOMobileFramebufferAP::clear_default_surface */ - [588] = trampoline_nop, /* resize_default_fb_surface_gated */ - [589] = trampoline_swap_complete, - [591] = trampoline_swap_complete_intent_gated, - [593] = trampoline_enable_backlight_message_ap_gated, - [594] = trampoline_nop, /* IOMobileFramebufferAP::setSystemConsoleMode */ - [596] = trampoline_false, /* IOMobileFramebufferAP::isDFBAllocated */ - [597] = trampoline_false, /* IOMobileFramebufferAP::preserveContents */ - [598] = trampoline_nop, /* find_swap_function_gated */ -}; - static void dcpep_handle_cb(struct apple_dcp *dcp, enum dcp_context_id context, void *data, u32 length, u16 offset) { @@ -1352,7 +314,7 @@ static void dcpep_handle_cb(struct apple_dcp *dcp, enum dcp_context_id context, struct dcp_channel *ch = dcp_get_channel(dcp, context); u8 depth; - if (tag < 0 || tag >= DCPEP_MAX_CB || !dcpep_cb_handlers[tag]) { + if (tag < 0 || tag >= IOMFB_MAX_CB || !dcp->cb_handlers || !dcp->cb_handlers[tag]) { dev_warn(dev, "received unknown callback %c%c%c%c\n", hdr->tag[3], hdr->tag[2], hdr->tag[1], hdr->tag[0]); return; @@ -1370,7 +332,7 @@ static void dcpep_handle_cb(struct apple_dcp *dcp, enum dcp_context_id context, ch->output[depth] = out; ch->end[depth] = offset + ALIGN(length, DCP_PACKET_ALIGNMENT); - if (dcpep_cb_handlers[tag](dcp, tag, out, in)) + if (dcp->cb_handlers[tag](dcp, tag, out, in)) dcp_ack(dcp, context); } @@ -1426,48 +388,12 @@ static void dcpep_got_msg(struct apple_dcp *dcp, u64 message) dcpep_handle_cb(dcp, ctx_id, data, length, offset); } -/* - * Callback for swap requests. If a swap failed, we'll never get a swap - * complete event so we need to fake a vblank event early to avoid a hang. - */ - -static void dcp_swapped(struct apple_dcp *dcp, void *data, void *cookie) -{ - struct dcp_swap_submit_resp *resp = data; - - if (resp->ret) { - dev_err(dcp->dev, "swap failed! status %u\n", resp->ret); - dcp_drm_crtc_vblank(dcp->crtc); - return; - } - - while (!list_empty(&dcp->swapped_out_fbs)) { - struct dcp_fb_reference *entry; - entry = list_first_entry(&dcp->swapped_out_fbs, - struct dcp_fb_reference, head); - if (entry->fb) - drm_framebuffer_put(entry->fb); - list_del(&entry->head); - kfree(entry); - } -} - -static void dcp_swap_started(struct apple_dcp *dcp, void *data, void *cookie) -{ - struct dcp_swap_start_resp *resp = data; - - dcp->swap.swap.swap_id = resp->swap_id; - - trace_iomfb_swap_submit(dcp, resp->swap_id); - dcp_swap_submit(dcp, false, &dcp->swap, dcp_swapped, NULL); -} - /* * DRM specifies rectangles as start and end coordinates. DCP specifies * rectangles as a start coordinate and a width/height. Convert a DRM rectangle * to a DCP rectangle. */ -static struct dcp_rect drm_to_dcp_rect(struct drm_rect *rect) +struct dcp_rect drm_to_dcp_rect(struct drm_rect *rect) { return (struct dcp_rect){ .x = rect->x1, .y = rect->y1, @@ -1475,7 +401,7 @@ static struct dcp_rect drm_to_dcp_rect(struct drm_rect *rect) .h = drm_rect_height(rect) }; } -static u32 drm_format_to_dcp(u32 drm) +u32 drm_format_to_dcp(u32 drm) { switch (drm) { case DRM_FORMAT_XRGB8888: @@ -1521,7 +447,7 @@ int dcp_get_modes(struct drm_connector *connector) EXPORT_SYMBOL_GPL(dcp_get_modes); /* The user may own drm_display_mode, so we need to search for our copy */ -static struct dcp_display_mode *lookup_mode(struct apple_dcp *dcp, +struct dcp_display_mode *lookup_mode(struct apple_dcp *dcp, const struct drm_display_mode *mode) { int i; @@ -1560,46 +486,11 @@ bool dcp_crtc_mode_fixup(struct drm_crtc *crtc, } EXPORT_SYMBOL(dcp_crtc_mode_fixup); -/* Helpers to modeset and swap, used to flush */ -static void do_swap(struct apple_dcp *dcp, void *data, void *cookie) -{ - struct dcp_swap_start_req start_req = { 0 }; - dev_dbg(dcp->dev, "%s", __func__); - - if (dcp->connector && dcp->connector->connected) - dcp_swap_start(dcp, false, &start_req, dcp_swap_started, NULL); - else - dcp_drm_crtc_vblank(dcp->crtc); -} - -static void complete_set_digital_out_mode(struct apple_dcp *dcp, void *data, - void *cookie) -{ - struct dcp_wait_cookie *wait = cookie; - dev_dbg(dcp->dev, "%s", __func__); - - if (wait) { - complete(&wait->done); - kref_put(&wait->refcount, release_wait_cookie); - } -} void dcp_flush(struct drm_crtc *crtc, struct drm_atomic_state *state) { struct platform_device *pdev = to_apple_crtc(crtc)->dcp; struct apple_dcp *dcp = platform_get_drvdata(pdev); - struct drm_plane *plane; - struct drm_plane_state *new_state, *old_state; - struct drm_crtc_state *crtc_state; - struct dcp_swap_submit_req *req = &dcp->swap; - int plane_idx, l; - int has_surface = 0; - bool modeset; - dev_dbg(dcp->dev, "%s", __func__); - - crtc_state = drm_atomic_get_new_crtc_state(state, crtc); - - modeset = drm_atomic_crtc_needs_modeset(crtc_state) || !dcp->valid_mode; if (dcp_channel_busy(&dcp->ch_cmd)) { @@ -1611,191 +502,34 @@ void dcp_flush(struct drm_crtc *crtc, struct drm_atomic_state *state) return; } - /* Reset to defaults */ - memset(req, 0, sizeof(*req)); - for (l = 0; l < SWAP_SURFACES; l++) - req->surf_null[l] = true; - - /* - * Clear all surfaces on startup. The boot framebuffer in surface 0 - * sticks around. - */ - if (!dcp->surfaces_cleared) { - req->swap.swap_enabled = IOMFB_SET_BACKGROUND | 0xF; - req->swap.bg_color = 0xFF000000; - dcp->surfaces_cleared = true; - } - - // Surface 0 has limitations at least on t600x. - l = 1; - for_each_oldnew_plane_in_state(state, plane, old_state, new_state, plane_idx) { - struct drm_framebuffer *fb = new_state->fb; - struct drm_gem_dma_object *obj; - struct drm_rect src_rect; - bool is_premultiplied = false; - - /* skip planes not for this crtc */ - if (old_state->crtc != crtc && new_state->crtc != crtc) - continue; - - WARN_ON(l >= SWAP_SURFACES); - - req->swap.swap_enabled |= BIT(l); - - if (old_state->fb && fb != old_state->fb) { - /* - * Race condition between a framebuffer unbind getting - * swapped out and GEM unreferencing a framebuffer. If - * we lose the race, the display gets IOVA faults and - * the DCP crashes. We need to extend the lifetime of - * the drm_framebuffer (and hence the GEM object) until - * after we get a swap complete for the swap unbinding - * it. - */ - struct dcp_fb_reference *entry = - kzalloc(sizeof(*entry), GFP_KERNEL); - if (entry) { - entry->fb = old_state->fb; - list_add_tail(&entry->head, - &dcp->swapped_out_fbs); - } - drm_framebuffer_get(old_state->fb); - } - - if (!new_state->fb) { - l += 1; - continue; - } - req->surf_null[l] = false; - has_surface = 1; - - /* - * DCP doesn't support XBGR8 / XRGB8 natively. Blending as - * pre-multiplied alpha with a black background can be used as - * workaround for the bottommost plane. - */ - if (fb->format->format == DRM_FORMAT_XRGB8888 || - fb->format->format == DRM_FORMAT_XBGR8888) - is_premultiplied = true; - - drm_rect_fp_to_int(&src_rect, &new_state->src); - - req->swap.src_rect[l] = drm_to_dcp_rect(&src_rect); - req->swap.dst_rect[l] = drm_to_dcp_rect(&new_state->dst); - - if (dcp->notch_height > 0) - req->swap.dst_rect[l].y += dcp->notch_height; - - /* the obvious helper call drm_fb_dma_get_gem_addr() adjusts - * the address for source x/y offsets. Since IOMFB has a direct - * support source position prefer that. - */ - obj = drm_fb_dma_get_gem_obj(fb, 0); - if (obj) - req->surf_iova[l] = obj->dma_addr + fb->offsets[0]; - - req->surf[l] = (struct dcp_surface){ - .is_premultiplied = is_premultiplied, - .format = drm_format_to_dcp(fb->format->format), - .xfer_func = DCP_XFER_FUNC_SDR, - .colorspace = DCP_COLORSPACE_NATIVE, - .stride = fb->pitches[0], - .width = fb->width, - .height = fb->height, - .buf_size = fb->height * fb->pitches[0], - .surface_id = req->swap.surf_ids[l], - - /* Only used for compressed or multiplanar surfaces */ - .pix_size = 1, - .pel_w = 1, - .pel_h = 1, - .has_comp = 1, - .has_planes = 1, - }; - - l += 1; - } - - if (modeset) { - struct dcp_display_mode *mode; - struct dcp_wait_cookie *cookie; - int ret; - - mode = lookup_mode(dcp, &crtc_state->mode); - if (!mode) { - dev_warn(dcp->dev, "no match for " DRM_MODE_FMT, - DRM_MODE_ARG(&crtc_state->mode)); - schedule_work(&dcp->vblank_wq); - return; - } - - dev_info(dcp->dev, "set_digital_out_mode(color:%d timing:%d)", - mode->color_mode_id, mode->timing_mode_id); - dcp->mode = (struct dcp_set_digital_out_mode_req){ - .color_mode_id = mode->color_mode_id, - .timing_mode_id = mode->timing_mode_id - }; - - cookie = kzalloc(sizeof(*cookie), GFP_KERNEL); - if (!cookie) { - schedule_work(&dcp->vblank_wq); - return; - } - - init_completion(&cookie->done); - kref_init(&cookie->refcount); - /* increase refcount to ensure the receiver has a reference */ - kref_get(&cookie->refcount); - - dcp_set_digital_out_mode(dcp, false, &dcp->mode, - complete_set_digital_out_mode, cookie); - - dev_dbg(dcp->dev, "%s - wait for modeset", __func__); - ret = wait_for_completion_timeout(&cookie->done, - msecs_to_jiffies(500)); - - kref_put(&cookie->refcount, release_wait_cookie); - - if (ret == 0) { - dev_dbg(dcp->dev, "set_digital_out_mode 200 ms"); - schedule_work(&dcp->vblank_wq); - return; - } else if (ret > 0) { - dev_dbg(dcp->dev, - "set_digital_out_mode finished with %d to spare", - jiffies_to_msecs(ret)); - } - - dcp->valid_mode = true; - } - - if (!has_surface && !crtc_state->color_mgmt_changed) { - if (crtc_state->enable && crtc_state->active && - !crtc_state->planes_changed) { - schedule_work(&dcp->vblank_wq); - return; - } - - /* Set black background */ - req->swap.swap_enabled |= IOMFB_SET_BACKGROUND; - req->swap.bg_color = 0xFF000000; - req->clear = 1; + switch (dcp->fw_compat) { + case DCP_FIRMWARE_V_12_3: + iomfb_flush_v12_3(dcp, crtc, state); + break; + case DCP_FIRMWARE_V_13_2: + iomfb_flush_v13_2(dcp, crtc, state); + break; + default: + WARN_ONCE(true, "Unexpected firmware version: %u\n", dcp->fw_compat); + break; } +} +EXPORT_SYMBOL_GPL(dcp_flush); - /* These fields should be set together */ - req->swap.swap_completed = req->swap.swap_enabled; - - /* update brightness if changed */ - if (dcp->brightness.update) { - req->swap.bl_unk = 1; - req->swap.bl_value = dcp->brightness.dac; - req->swap.bl_power = 0x40; - dcp->brightness.update = false; +static void iomfb_start(struct apple_dcp *dcp) +{ + switch (dcp->fw_compat) { + case DCP_FIRMWARE_V_12_3: + iomfb_start_v12_3(dcp); + break; + case DCP_FIRMWARE_V_13_2: + iomfb_start_v13_2(dcp); + break; + default: + WARN_ONCE(true, "Unexpected firmware version: %u\n", dcp->fw_compat); + break; } - - do_swap(dcp, NULL, NULL); } -EXPORT_SYMBOL_GPL(dcp_flush); bool dcp_is_initialized(struct platform_device *pdev) { @@ -1805,58 +539,12 @@ bool dcp_is_initialized(struct platform_device *pdev) } EXPORT_SYMBOL_GPL(dcp_is_initialized); -static void res_is_main_display(struct apple_dcp *dcp, void *out, void *cookie) -{ - struct apple_connector *connector; - int result = *(int *)out; - dev_info(dcp->dev, "DCP is_main_display: %d\n", result); - - dcp->main_display = result != 0; - - connector = dcp->connector; - if (connector) { - connector->connected = dcp->nr_modes > 0; - schedule_work(&connector->hotplug_wq); - } - - dcp->active = true; - complete(&dcp->start_done); -} - -static void init_3(struct apple_dcp *dcp, void *out, void *cookie) -{ - dcp_is_main_display(dcp, false, res_is_main_display, NULL); -} - -static void init_2(struct apple_dcp *dcp, void *out, void *cookie) -{ - dcp_first_client_open(dcp, false, init_3, NULL); -} - -static void init_1(struct apple_dcp *dcp, void *out, void *cookie) -{ - u32 val = 0; - dcp_enable_disable_video_power_savings(dcp, false, &val, init_2, NULL); -} - -static void dcp_started(struct apple_dcp *dcp, void *data, void *cookie) -{ - struct iomfb_get_color_remap_mode_req color_remap = - (struct iomfb_get_color_remap_mode_req){ - .mode = 6, - }; - - dev_info(dcp->dev, "DCP booted\n"); - - iomfb_get_color_remap_mode(dcp, false, &color_remap, init_1, cookie); -} - void iomfb_recv_msg(struct apple_dcp *dcp, u64 message) { enum dcpep_type type = FIELD_GET(IOMFB_MESSAGE_TYPE, message); if (type == IOMFB_MESSAGE_TYPE_INITIALIZED) - dcp_start_signal(dcp, false, dcp_started, NULL); + iomfb_start(dcp); else if (type == IOMFB_MESSAGE_TYPE_MSG) dcpep_got_msg(dcp, message); else @@ -1879,13 +567,19 @@ int iomfb_start_rtkit(struct apple_dcp *dcp) void iomfb_shutdown(struct apple_dcp *dcp) { - struct dcp_set_power_state_req req = { - /* defaults are ok */ - }; - /* We're going down */ dcp->active = false; dcp->valid_mode = false; - dcp_set_power_state(dcp, false, &req, NULL, NULL); + switch (dcp->fw_compat) { + case DCP_FIRMWARE_V_12_3: + iomfb_shutdown_v12_3(dcp); + break; + case DCP_FIRMWARE_V_13_2: + iomfb_shutdown_v13_2(dcp); + break; + default: + WARN_ONCE(true, "Unexpected firmware version: %u\n", dcp->fw_compat); + break; + } } diff --git a/drivers/gpu/drm/apple/iomfb.h b/drivers/gpu/drm/apple/iomfb.h index 54b34fbba94894..0c8061bb96e3e0 100644 --- a/drivers/gpu/drm/apple/iomfb.h +++ b/drivers/gpu/drm/apple/iomfb.h @@ -6,6 +6,8 @@ #include +#include "version_utils.h" + /* Fixed size of shared memory between DCP and AP */ #define DCP_SHMEM_SIZE 0x100000 @@ -106,35 +108,6 @@ struct dcp_rect { */ #define IOMFB_SET_BACKGROUND BIT(31) -struct dcp_swap { - u64 ts1; - u64 ts2; - u64 unk_10[6]; - u64 flags1; - u64 flags2; - - u32 swap_id; - - u32 surf_ids[SWAP_SURFACES]; - struct dcp_rect src_rect[SWAP_SURFACES]; - u32 surf_flags[SWAP_SURFACES]; - u32 surf_unk[SWAP_SURFACES]; - struct dcp_rect dst_rect[SWAP_SURFACES]; - u32 swap_enabled; - u32 swap_completed; - - u32 bg_color; - u8 unk_110[0x1b8]; - u32 unk_2c8; - u8 unk_2cc[0x14]; - u32 unk_2e0; - u16 unk_2e2; - u64 bl_unk; - u32 bl_value; // min value is 0x10000000 - u8 bl_power; // constant 0x40 for on - u8 unk_2f3[0x2d]; -} __packed; - /* Information describing a plane of a planar compressed surface */ struct dcp_plane_info { u32 width; @@ -154,38 +127,6 @@ struct dcp_component_types { u8 types[7]; } __packed; -/* Information describing a surface */ -struct dcp_surface { - u8 is_tiled; - u8 is_tearing_allowed; - u8 is_premultiplied; - u32 plane_cnt; - u32 plane_cnt2; - u32 format; /* DCP fourcc */ - u32 ycbcr_matrix; - u8 xfer_func; - u8 colorspace; - u32 stride; - u16 pix_size; - u8 pel_w; - u8 pel_h; - u32 offset; - u32 width; - u32 height; - u32 buf_size; - u64 protection_opts; - u32 surface_id; - struct dcp_component_types comp_types[MAX_PLANES]; - u64 has_comp; - struct dcp_plane_info planes[MAX_PLANES]; - u64 has_planes; - u32 compression_info[MAX_PLANES][13]; - u64 has_compr_info; - u32 unk_num; - u32 unk_denom; - u8 padding[7]; -} __packed; - struct dcp_rt_bandwidth { u64 unk1; u64 reg_scratch; @@ -218,14 +159,22 @@ enum dcpep_method { iomfbep_a132_backlight_service_matched, iomfbep_a358_vi_set_temperature_hint, iomfbep_get_color_remap_mode, + iomfbep_last_client_close, dcpep_num_methods }; +#define IOMFB_METHOD(tag, name) [name] = { #name, tag } + struct dcp_method_entry { const char *name; char tag[4]; }; +#define IOMFB_MAX_CB (1000) +struct apple_dcp; + +typedef bool (*iomfb_cb_handler)(struct apple_dcp *, int, void *, void *); + /* Prototypes */ struct dcp_set_digital_out_mode_req { @@ -287,21 +236,6 @@ struct dcp_map_physical_resp { u32 mem_desc_id; } __packed; -struct dcp_map_reg_req { - char obj[4]; - u32 index; - u32 flags; - u8 addr_null; - u8 length_null; - u8 padding[2]; -} __packed; - -struct dcp_map_reg_resp { - u64 addr; - u64 length; - u32 ret; -} __packed; - struct dcp_swap_start_req { u32 swap_id; struct dcp_iouserclient client; @@ -316,34 +250,6 @@ struct dcp_swap_start_resp { u32 ret; } __packed; -struct dcp_swap_submit_req { - struct dcp_swap swap; - struct dcp_surface surf[SWAP_SURFACES]; - u64 surf_iova[SWAP_SURFACES]; - u8 unkbool; - u64 unkdouble; - u32 clear; // or maybe switch to default fb? - u8 swap_null; - u8 surf_null[SWAP_SURFACES]; - u8 unkoutbool_null; - u8 padding[1]; -} __packed; - -struct dcp_swap_submit_resp { - u8 unkoutbool; - u32 ret; - u8 padding[3]; -} __packed; - -struct dc_swap_complete_resp { - u32 swap_id; - u8 unkbool; - u64 swap_data; - u8 swap_info[0x6c4]; - u32 unkint; - u8 swap_info_null; -} __packed; - struct dcp_get_uint_prop_req { char obj[4]; char key[0x40]; @@ -435,4 +341,13 @@ struct iomfb_get_color_remap_mode_resp { u32 ret; } __packed; +struct iomfb_last_client_close_req { + u8 unkint_null; + u8 padding[3]; +} __packed; + +struct iomfb_last_client_close_resp { + u32 unkint; +} __packed; + #endif diff --git a/drivers/gpu/drm/apple/iomfb_internal.h b/drivers/gpu/drm/apple/iomfb_internal.h new file mode 100644 index 00000000000000..401b6ec32848d3 --- /dev/null +++ b/drivers/gpu/drm/apple/iomfb_internal.h @@ -0,0 +1,123 @@ +// SPDX-License-Identifier: GPL-2.0-only OR MIT +/* Copyright The Asahi Linux Contributors */ + +#include +#include + +#include "dcp-internal.h" + +struct apple_dcp; + +typedef void (*dcp_callback_t)(struct apple_dcp *, void *, void *); + + +#define DCP_THUNK_VOID(func, handle) \ + static void func(struct apple_dcp *dcp, bool oob, dcp_callback_t cb, \ + void *cookie) \ + { \ + dcp_push(dcp, oob, &dcp_methods[handle], 0, 0, NULL, cb, cookie); \ + } + +#define DCP_THUNK_OUT(func, handle, T) \ + static void func(struct apple_dcp *dcp, bool oob, dcp_callback_t cb, \ + void *cookie) \ + { \ + dcp_push(dcp, oob, &dcp_methods[handle], 0, sizeof(T), NULL, cb, cookie); \ + } + +#define DCP_THUNK_IN(func, handle, T) \ + static void func(struct apple_dcp *dcp, bool oob, T *data, \ + dcp_callback_t cb, void *cookie) \ + { \ + dcp_push(dcp, oob, &dcp_methods[handle], sizeof(T), 0, data, cb, cookie); \ + } + +#define DCP_THUNK_INOUT(func, handle, T_in, T_out) \ + static void func(struct apple_dcp *dcp, bool oob, T_in *data, \ + dcp_callback_t cb, void *cookie) \ + { \ + dcp_push(dcp, oob, &dcp_methods[handle], sizeof(T_in), sizeof(T_out), data, \ + cb, cookie); \ + } + +#define IOMFB_THUNK_INOUT(name) \ + static void iomfb_ ## name(struct apple_dcp *dcp, bool oob, \ + struct iomfb_ ## name ## _req *data, \ + dcp_callback_t cb, void *cookie) \ + { \ + dcp_push(dcp, oob, &dcp_methods[iomfbep_ ## name], \ + sizeof(struct iomfb_ ## name ## _req), \ + sizeof(struct iomfb_ ## name ## _resp), \ + data, cb, cookie); \ + } + +/* + * Define type-safe trampolines. Define typedefs to enforce type-safety on the + * input data (so if the types don't match, gcc errors out). + */ + +#define TRAMPOLINE_VOID(func, handler) \ + static bool func(struct apple_dcp *dcp, int tag, void *out, void *in) \ + { \ + trace_iomfb_callback(dcp, tag, #handler); \ + handler(dcp); \ + return true; \ + } + +#define TRAMPOLINE_IN(func, handler, T_in) \ + typedef void (*callback_##handler)(struct apple_dcp *, T_in *); \ + \ + static bool func(struct apple_dcp *dcp, int tag, void *out, void *in) \ + { \ + callback_##handler cb = handler; \ + \ + trace_iomfb_callback(dcp, tag, #handler); \ + cb(dcp, in); \ + return true; \ + } + +#define TRAMPOLINE_INOUT(func, handler, T_in, T_out) \ + typedef T_out (*callback_##handler)(struct apple_dcp *, T_in *); \ + \ + static bool func(struct apple_dcp *dcp, int tag, void *out, void *in) \ + { \ + T_out *typed_out = out; \ + callback_##handler cb = handler; \ + \ + trace_iomfb_callback(dcp, tag, #handler); \ + *typed_out = cb(dcp, in); \ + return true; \ + } + +#define TRAMPOLINE_OUT(func, handler, T_out) \ + static bool func(struct apple_dcp *dcp, int tag, void *out, void *in) \ + { \ + T_out *typed_out = out; \ + \ + trace_iomfb_callback(dcp, tag, #handler); \ + *typed_out = handler(dcp); \ + return true; \ + } + +/* Call a DCP function given by a tag */ +void dcp_push(struct apple_dcp *dcp, bool oob, const struct dcp_method_entry *call, + u32 in_len, u32 out_len, void *data, dcp_callback_t cb, + void *cookie); + +/* Parse a callback tag "D123" into the ID 123. Returns -EINVAL on failure. */ +int dcp_parse_tag(char tag[4]); + +void dcp_ack(struct apple_dcp *dcp, enum dcp_context_id context); + +/* + * DRM specifies rectangles as start and end coordinates. DCP specifies + * rectangles as a start coordinate and a width/height. Convert a DRM rectangle + * to a DCP rectangle. + */ +struct dcp_rect drm_to_dcp_rect(struct drm_rect *rect); + +u32 drm_format_to_dcp(u32 drm); + +/* The user may own drm_display_mode, so we need to search for our copy */ +struct dcp_display_mode *lookup_mode(struct apple_dcp *dcp, + const struct drm_display_mode *mode); diff --git a/drivers/gpu/drm/apple/iomfb_template.c b/drivers/gpu/drm/apple/iomfb_template.c new file mode 100644 index 00000000000000..25ec66a2f24f53 --- /dev/null +++ b/drivers/gpu/drm/apple/iomfb_template.c @@ -0,0 +1,1344 @@ +// SPDX-License-Identifier: GPL-2.0-only OR MIT +/* + * Copyright 2021 Alyssa Rosenzweig + * Copyright The Asahi Linux Contributors + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include "dcp.h" +#include "dcp-internal.h" +#include "iomfb.h" +#include "iomfb_internal.h" +#include "parser.h" +#include "trace.h" +#include "version_utils.h" + +/* Register defines used in bandwidth setup structure */ +#define REG_SCRATCH (0x14) +#define REG_SCRATCH_T600X (0x988) +#define REG_DOORBELL (0x0) +#define REG_DOORBELL_BIT (2) + +struct dcp_wait_cookie { + struct kref refcount; + struct completion done; +}; + +static void release_wait_cookie(struct kref *ref) +{ + struct dcp_wait_cookie *cookie; + cookie = container_of(ref, struct dcp_wait_cookie, refcount); + + kfree(cookie); +} + +DCP_THUNK_OUT(iomfb_a131_pmu_service_matched, iomfbep_a131_pmu_service_matched, u32); +DCP_THUNK_OUT(iomfb_a132_backlight_service_matched, iomfbep_a132_backlight_service_matched, u32); +DCP_THUNK_OUT(iomfb_a358_vi_set_temperature_hint, iomfbep_a358_vi_set_temperature_hint, u32); + +IOMFB_THUNK_INOUT(get_color_remap_mode); +IOMFB_THUNK_INOUT(last_client_close); + +DCP_THUNK_INOUT(dcp_swap_submit, dcpep_swap_submit, + struct DCP_FW_NAME(dcp_swap_submit_req), + struct DCP_FW_NAME(dcp_swap_submit_resp)); + +DCP_THUNK_INOUT(dcp_swap_start, dcpep_swap_start, struct dcp_swap_start_req, + struct dcp_swap_start_resp); + +DCP_THUNK_INOUT(dcp_set_power_state, dcpep_set_power_state, + struct dcp_set_power_state_req, + struct dcp_set_power_state_resp); + +DCP_THUNK_INOUT(dcp_set_digital_out_mode, dcpep_set_digital_out_mode, + struct dcp_set_digital_out_mode_req, u32); + +DCP_THUNK_INOUT(dcp_set_display_device, dcpep_set_display_device, u32, u32); + +DCP_THUNK_OUT(dcp_set_display_refresh_properties, + dcpep_set_display_refresh_properties, u32); + +#if DCP_FW_VER >= DCP_FW_VERSION(13, 2, 0) +DCP_THUNK_INOUT(dcp_late_init_signal, dcpep_late_init_signal, u32, u32); +#else +DCP_THUNK_OUT(dcp_late_init_signal, dcpep_late_init_signal, u32); +#endif +DCP_THUNK_IN(dcp_flush_supports_power, dcpep_flush_supports_power, u32); +DCP_THUNK_OUT(dcp_create_default_fb, dcpep_create_default_fb, u32); +DCP_THUNK_OUT(dcp_start_signal, dcpep_start_signal, u32); +DCP_THUNK_VOID(dcp_setup_video_limits, dcpep_setup_video_limits); +DCP_THUNK_VOID(dcp_set_create_dfb, dcpep_set_create_dfb); +DCP_THUNK_VOID(dcp_first_client_open, dcpep_first_client_open); + +DCP_THUNK_INOUT(dcp_set_parameter_dcp, dcpep_set_parameter_dcp, + struct dcp_set_parameter_dcp, u32); + +DCP_THUNK_INOUT(dcp_enable_disable_video_power_savings, + dcpep_enable_disable_video_power_savings, u32, int); + +DCP_THUNK_OUT(dcp_is_main_display, dcpep_is_main_display, u32); + +/* DCP callback handlers */ +static void dcpep_cb_nop(struct apple_dcp *dcp) +{ + /* No operation */ +} + +static u8 dcpep_cb_true(struct apple_dcp *dcp) +{ + return true; +} + +static u8 dcpep_cb_false(struct apple_dcp *dcp) +{ + return false; +} + +static u32 dcpep_cb_zero(struct apple_dcp *dcp) +{ + return 0; +} + +static void dcpep_cb_swap_complete(struct apple_dcp *dcp, + struct DCP_FW_NAME(dc_swap_complete_resp) *resp) +{ + trace_iomfb_swap_complete(dcp, resp->swap_id); + + dcp_drm_crtc_vblank(dcp->crtc); +} + +/* special */ +static void complete_vi_set_temperature_hint(struct apple_dcp *dcp, void *out, void *cookie) +{ + // ack D100 cb_match_pmu_service + dcp_ack(dcp, DCP_CONTEXT_CB); +} + +static bool iomfbep_cb_match_pmu_service(struct apple_dcp *dcp, int tag, void *out, void *in) +{ + trace_iomfb_callback(dcp, tag, __func__); + iomfb_a358_vi_set_temperature_hint(dcp, false, + complete_vi_set_temperature_hint, + NULL); + + // return false for deferred ACK + return false; +} + +static void complete_pmu_service_matched(struct apple_dcp *dcp, void *out, void *cookie) +{ + struct dcp_channel *ch = &dcp->ch_cb; + u8 *succ = ch->output[ch->depth - 1]; + + *succ = true; + + // ack D206 cb_match_pmu_service_2 + dcp_ack(dcp, DCP_CONTEXT_CB); +} + +static bool iomfbep_cb_match_pmu_service_2(struct apple_dcp *dcp, int tag, void *out, void *in) +{ + trace_iomfb_callback(dcp, tag, __func__); + + iomfb_a131_pmu_service_matched(dcp, false, complete_pmu_service_matched, + out); + + // return false for deferred ACK + return false; +} + +static void complete_backlight_service_matched(struct apple_dcp *dcp, void *out, void *cookie) +{ + struct dcp_channel *ch = &dcp->ch_cb; + u8 *succ = ch->output[ch->depth - 1]; + + *succ = true; + + // ack D206 cb_match_backlight_service + dcp_ack(dcp, DCP_CONTEXT_CB); +} + +static bool iomfbep_cb_match_backlight_service(struct apple_dcp *dcp, int tag, void *out, void *in) +{ + trace_iomfb_callback(dcp, tag, __func__); + + iomfb_a132_backlight_service_matched(dcp, false, complete_backlight_service_matched, out); + + // return false for deferred ACK + return false; +} + +static void iomfb_cb_pr_publish(struct apple_dcp *dcp, struct iomfb_property *prop) +{ + switch (prop->id) { + case IOMFB_PROPERTY_NITS: + { + dcp->brightness.nits = prop->value / dcp->brightness.scale; + /* notify backlight device of the initial brightness */ + if (!dcp->brightness.bl_dev && dcp->brightness.maximum > 0) + schedule_work(&dcp->bl_register_wq); + trace_iomfb_brightness(dcp, prop->value); + break; + } + default: + dev_dbg(dcp->dev, "pr_publish: id: %d = %u\n", prop->id, prop->value); + } +} + +static struct dcp_get_uint_prop_resp +dcpep_cb_get_uint_prop(struct apple_dcp *dcp, struct dcp_get_uint_prop_req *req) +{ + struct dcp_get_uint_prop_resp resp = (struct dcp_get_uint_prop_resp){ + .value = 0 + }; + + if (dcp->panel.has_mini_led && + memcmp(req->obj, "SUMP", sizeof(req->obj)) == 0) { /* "PMUS */ + if (strncmp(req->key, "Temperature", sizeof(req->key)) == 0) { + /* + * TODO: value from j314c, find out if it is temperature in + * centigrade C and which temperature sensor reports it + */ + resp.value = 3029; + resp.ret = true; + } + } + + return resp; +} + +static u8 iomfbep_cb_sr_set_property_int(struct apple_dcp *dcp, + struct iomfb_sr_set_property_int_req *req) +{ + if (memcmp(req->obj, "FMOI", sizeof(req->obj)) == 0) { /* "IOMF */ + if (strncmp(req->key, "Brightness_Scale", sizeof(req->key)) == 0) { + if (!req->value_null) + dcp->brightness.scale = req->value; + } + } + + return 1; +} + +static void iomfbep_cb_set_fx_prop(struct apple_dcp *dcp, struct iomfb_set_fx_prop_req *req) +{ + // TODO: trace this, see if there properties which needs to used later +} + +/* + * Callback to map a buffer allocated with allocate_buf for PIODMA usage. + * PIODMA is separate from the main DCP and uses own IOVA space on a dedicated + * stream of the display DART, rather than the expected DCP DART. + * + * XXX: This relies on dma_get_sgtable in concert with dma_map_sgtable, which + * is a "fundamentally unsafe" operation according to the docs. And yet + * everyone does it... + */ +static struct dcp_map_buf_resp dcpep_cb_map_piodma(struct apple_dcp *dcp, + struct dcp_map_buf_req *req) +{ + struct sg_table *map; + int ret; + + if (req->buffer >= ARRAY_SIZE(dcp->memdesc)) + goto reject; + + map = &dcp->memdesc[req->buffer].map; + + if (!map->sgl) + goto reject; + + /* Use PIODMA device instead of DCP to map against the right IOMMU. */ + ret = dma_map_sgtable(&dcp->piodma->dev, map, DMA_BIDIRECTIONAL, 0); + + if (ret) + goto reject; + + return (struct dcp_map_buf_resp){ .dva = sg_dma_address(map->sgl) }; + +reject: + dev_err(dcp->dev, "denying map of invalid buffer %llx for piodma\n", + req->buffer); + return (struct dcp_map_buf_resp){ .ret = EINVAL }; +} + +static void dcpep_cb_unmap_piodma(struct apple_dcp *dcp, + struct dcp_unmap_buf_resp *resp) +{ + struct sg_table *map; + dma_addr_t dma_addr; + + if (resp->buffer >= ARRAY_SIZE(dcp->memdesc)) { + dev_warn(dcp->dev, "unmap request for out of range buffer %llu", + resp->buffer); + return; + } + + map = &dcp->memdesc[resp->buffer].map; + + if (!map->sgl) { + dev_warn(dcp->dev, + "unmap for non-mapped buffer %llu iova:0x%08llx", + resp->buffer, resp->dva); + return; + } + + dma_addr = sg_dma_address(map->sgl); + if (dma_addr != resp->dva) { + dev_warn(dcp->dev, "unmap buffer %llu address mismatch dma_addr:%llx dva:%llx", + resp->buffer, dma_addr, resp->dva); + return; + } + + /* Use PIODMA device instead of DCP to unmap from the right IOMMU. */ + dma_unmap_sgtable(&dcp->piodma->dev, map, DMA_BIDIRECTIONAL, 0); +} + +/* + * Allocate an IOVA contiguous buffer mapped to the DCP. The buffer need not be + * physically contigiuous, however we should save the sgtable in case the + * buffer needs to be later mapped for PIODMA. + */ +static struct dcp_allocate_buffer_resp +dcpep_cb_allocate_buffer(struct apple_dcp *dcp, + struct dcp_allocate_buffer_req *req) +{ + struct dcp_allocate_buffer_resp resp = { 0 }; + struct dcp_mem_descriptor *memdesc; + u32 id; + + resp.dva_size = ALIGN(req->size, 4096); + resp.mem_desc_id = + find_first_zero_bit(dcp->memdesc_map, DCP_MAX_MAPPINGS); + + if (resp.mem_desc_id >= DCP_MAX_MAPPINGS) { + dev_warn(dcp->dev, "DCP overflowed mapping table, ignoring"); + resp.dva_size = 0; + resp.mem_desc_id = 0; + return resp; + } + id = resp.mem_desc_id; + set_bit(id, dcp->memdesc_map); + + memdesc = &dcp->memdesc[id]; + + memdesc->size = resp.dva_size; + memdesc->buf = dma_alloc_coherent(dcp->dev, memdesc->size, + &memdesc->dva, GFP_KERNEL); + + dma_get_sgtable(dcp->dev, &memdesc->map, memdesc->buf, memdesc->dva, + memdesc->size); + resp.dva = memdesc->dva; + + return resp; +} + +static u8 dcpep_cb_release_mem_desc(struct apple_dcp *dcp, u32 *mem_desc_id) +{ + struct dcp_mem_descriptor *memdesc; + u32 id = *mem_desc_id; + + if (id >= DCP_MAX_MAPPINGS) { + dev_warn(dcp->dev, + "unmap request for out of range mem_desc_id %u", id); + return 0; + } + + if (!test_and_clear_bit(id, dcp->memdesc_map)) { + dev_warn(dcp->dev, "unmap request for unused mem_desc_id %u", + id); + return 0; + } + + memdesc = &dcp->memdesc[id]; + if (memdesc->buf) { + dma_free_coherent(dcp->dev, memdesc->size, memdesc->buf, + memdesc->dva); + + memdesc->buf = NULL; + memset(&memdesc->map, 0, sizeof(memdesc->map)); + } else { + memdesc->reg = 0; + } + + memdesc->size = 0; + + return 1; +} + +/* Validate that the specified region is a display register */ +static bool is_disp_register(struct apple_dcp *dcp, u64 start, u64 end) +{ + int i; + + for (i = 0; i < dcp->nr_disp_registers; ++i) { + struct resource *r = dcp->disp_registers[i]; + + if ((start >= r->start) && (end <= r->end)) + return true; + } + + return false; +} + +/* + * Map contiguous physical memory into the DCP's address space. The firmware + * uses this to map the display registers we advertise in + * sr_map_device_memory_with_index, so we bounds check against that to guard + * safe against malicious coprocessors. + */ +static struct dcp_map_physical_resp +dcpep_cb_map_physical(struct apple_dcp *dcp, struct dcp_map_physical_req *req) +{ + int size = ALIGN(req->size, 4096); + u32 id; + + if (!is_disp_register(dcp, req->paddr, req->paddr + size - 1)) { + dev_err(dcp->dev, "refusing to map phys address %llx size %llx", + req->paddr, req->size); + return (struct dcp_map_physical_resp){}; + } + + id = find_first_zero_bit(dcp->memdesc_map, DCP_MAX_MAPPINGS); + set_bit(id, dcp->memdesc_map); + dcp->memdesc[id].size = size; + dcp->memdesc[id].reg = req->paddr; + + return (struct dcp_map_physical_resp){ + .dva_size = size, + .mem_desc_id = id, + .dva = dma_map_resource(dcp->dev, req->paddr, size, + DMA_BIDIRECTIONAL, 0), + }; +} + +static u64 dcpep_cb_get_frequency(struct apple_dcp *dcp) +{ + return clk_get_rate(dcp->clk); +} + +static struct DCP_FW_NAME(dcp_map_reg_resp) dcpep_cb_map_reg(struct apple_dcp *dcp, + struct DCP_FW_NAME(dcp_map_reg_req) *req) +{ + if (req->index >= dcp->nr_disp_registers) { + dev_warn(dcp->dev, "attempted to read invalid reg index %u", + req->index); + + return (struct DCP_FW_NAME(dcp_map_reg_resp)){ .ret = 1 }; + } else { + struct resource *rsrc = dcp->disp_registers[req->index]; +#if DCP_FW_VER >= DCP_FW_VERSION(13, 2, 0) + dma_addr_t dva = dma_map_resource(dcp->dev, rsrc->start, resource_size(rsrc), + DMA_BIDIRECTIONAL, 0); + WARN_ON(dva == DMA_MAPPING_ERROR); +#endif + + return (struct DCP_FW_NAME(dcp_map_reg_resp)){ + .addr = rsrc->start, + .length = resource_size(rsrc), +#if DCP_FW_VER >= DCP_FW_VERSION(13, 2, 0) + .dva = dva, +#endif + }; + } +} + +static struct dcp_read_edt_data_resp +dcpep_cb_read_edt_data(struct apple_dcp *dcp, struct dcp_read_edt_data_req *req) +{ + return (struct dcp_read_edt_data_resp){ + .value[0] = req->value[0], + .ret = 0, + }; +} + +static void iomfbep_cb_enable_backlight_message_ap_gated(struct apple_dcp *dcp, + u8 *enabled) +{ + /* + * update backlight brightness on next swap, on non mini-LED displays + * DCP seems to set an invalid iDAC value after coming out of DPMS. + * syslog: "[BrightnessLCD.cpp:743][AFK]nitsToDBV: iDAC out of range" + */ + dcp->brightness.update = true; +} + +/* Chunked data transfer for property dictionaries */ +static u8 dcpep_cb_prop_start(struct apple_dcp *dcp, u32 *length) +{ + if (dcp->chunks.data != NULL) { + dev_warn(dcp->dev, "ignoring spurious transfer start\n"); + return false; + } + + dcp->chunks.length = *length; + dcp->chunks.data = devm_kzalloc(dcp->dev, *length, GFP_KERNEL); + + if (!dcp->chunks.data) { + dev_warn(dcp->dev, "failed to allocate chunks\n"); + return false; + } + + return true; +} + +static u8 dcpep_cb_prop_chunk(struct apple_dcp *dcp, + struct dcp_set_dcpav_prop_chunk_req *req) +{ + if (!dcp->chunks.data) { + dev_warn(dcp->dev, "ignoring spurious chunk\n"); + return false; + } + + if (req->offset + req->length > dcp->chunks.length) { + dev_warn(dcp->dev, "ignoring overflowing chunk\n"); + return false; + } + + memcpy(dcp->chunks.data + req->offset, req->data, req->length); + return true; +} + +static bool dcpep_process_chunks(struct apple_dcp *dcp, + struct dcp_set_dcpav_prop_end_req *req) +{ + struct dcp_parse_ctx ctx; + int ret; + + if (!dcp->chunks.data) { + dev_warn(dcp->dev, "ignoring spurious end\n"); + return false; + } + + /* used just as opaque pointer for tracing */ + ctx.dcp = dcp; + + ret = parse(dcp->chunks.data, dcp->chunks.length, &ctx); + + if (ret) { + dev_warn(dcp->dev, "bad header on dcpav props\n"); + return false; + } + + if (!strcmp(req->key, "TimingElements")) { + dcp->modes = enumerate_modes(&ctx, &dcp->nr_modes, + dcp->width_mm, dcp->height_mm, + dcp->notch_height); + + if (IS_ERR(dcp->modes)) { + dev_warn(dcp->dev, "failed to parse modes\n"); + dcp->modes = NULL; + dcp->nr_modes = 0; + return false; + } + } else if (!strcmp(req->key, "DisplayAttributes")) { + /* DisplayAttributes are empty for integrated displays, use + * display dimensions read from the devicetree + */ + if (dcp->main_display) { + ret = parse_display_attributes(&ctx, &dcp->width_mm, + &dcp->height_mm); + + if (ret) { + dev_warn(dcp->dev, "failed to parse display attribs\n"); + return false; + } + } + + dcp_set_dimensions(dcp); + } + + return true; +} + +static u8 dcpep_cb_prop_end(struct apple_dcp *dcp, + struct dcp_set_dcpav_prop_end_req *req) +{ + u8 resp = dcpep_process_chunks(dcp, req); + + /* Reset for the next transfer */ + devm_kfree(dcp->dev, dcp->chunks.data); + dcp->chunks.data = NULL; + + return resp; +} + +/* Boot sequence */ +static void boot_done(struct apple_dcp *dcp, void *out, void *cookie) +{ + struct dcp_channel *ch = &dcp->ch_cb; + u8 *succ = ch->output[ch->depth - 1]; + dev_dbg(dcp->dev, "boot done"); + + *succ = true; + dcp_ack(dcp, DCP_CONTEXT_CB); +} + +static void boot_5(struct apple_dcp *dcp, void *out, void *cookie) +{ + dcp_set_display_refresh_properties(dcp, false, boot_done, NULL); +} + +static void boot_4(struct apple_dcp *dcp, void *out, void *cookie) +{ +#if DCP_FW_VER >= DCP_FW_VERSION(13, 2, 0) + u32 v_true = 1; + dcp_late_init_signal(dcp, false, &v_true, boot_5, NULL); +#else + dcp_late_init_signal(dcp, false, boot_5, NULL); +#endif +} + +static void boot_3(struct apple_dcp *dcp, void *out, void *cookie) +{ + u32 v_true = true; + + dcp_flush_supports_power(dcp, false, &v_true, boot_4, NULL); +} + +static void boot_2(struct apple_dcp *dcp, void *out, void *cookie) +{ + dcp_setup_video_limits(dcp, false, boot_3, NULL); +} + +static void boot_1_5(struct apple_dcp *dcp, void *out, void *cookie) +{ + dcp_create_default_fb(dcp, false, boot_2, NULL); +} + +/* Use special function signature to defer the ACK */ +static bool dcpep_cb_boot_1(struct apple_dcp *dcp, int tag, void *out, void *in) +{ + trace_iomfb_callback(dcp, tag, __func__); + dcp_set_create_dfb(dcp, false, boot_1_5, NULL); + return false; +} + +static struct dcp_rt_bandwidth dcpep_cb_rt_bandwidth(struct apple_dcp *dcp) +{ + if (dcp->disp_registers[5] && dcp->disp_registers[6]) + return (struct dcp_rt_bandwidth){ + .reg_scratch = + dcp->disp_registers[5]->start + REG_SCRATCH, + .reg_doorbell = + dcp->disp_registers[6]->start + REG_DOORBELL, + .doorbell_bit = REG_DOORBELL_BIT, + + .padding[3] = 0x4, // XXX: required by 11.x firmware + }; + else if (dcp->disp_registers[4]) + return (struct dcp_rt_bandwidth){ + .reg_scratch = dcp->disp_registers[4]->start + + REG_SCRATCH_T600X, + .reg_doorbell = 0, + .doorbell_bit = 0, + }; + else + return (struct dcp_rt_bandwidth){ + .reg_scratch = 0, + .reg_doorbell = 0, + .doorbell_bit = 0, + }; +} + +/* Callback to get the current time as milliseconds since the UNIX epoch */ +static u64 dcpep_cb_get_time(struct apple_dcp *dcp) +{ + return ktime_to_ms(ktime_get_real()); +} + +struct dcp_swap_cookie { + struct kref refcount; + struct completion done; + u32 swap_id; +}; + +static void release_swap_cookie(struct kref *ref) +{ + struct dcp_swap_cookie *cookie; + cookie = container_of(ref, struct dcp_swap_cookie, refcount); + + kfree(cookie); +} + +static void dcp_swap_cleared(struct apple_dcp *dcp, void *data, void *cookie) +{ + struct DCP_FW_NAME(dcp_swap_submit_resp) *resp = data; + dev_dbg(dcp->dev, "%s", __func__); + + if (cookie) { + struct dcp_swap_cookie *info = cookie; + complete(&info->done); + kref_put(&info->refcount, release_swap_cookie); + } + + if (resp->ret) { + dev_err(dcp->dev, "swap_clear failed! status %u\n", resp->ret); + dcp_drm_crtc_vblank(dcp->crtc); + return; + } + + while (!list_empty(&dcp->swapped_out_fbs)) { + struct dcp_fb_reference *entry; + entry = list_first_entry(&dcp->swapped_out_fbs, + struct dcp_fb_reference, head); + if (entry->fb) + drm_framebuffer_put(entry->fb); + list_del(&entry->head); + kfree(entry); + } +} + +static void dcp_swap_clear_started(struct apple_dcp *dcp, void *data, + void *cookie) +{ + struct dcp_swap_start_resp *resp = data; + dev_dbg(dcp->dev, "%s swap_id: %u", __func__, resp->swap_id); + DCP_FW_UNION(dcp->swap).swap.swap_id = resp->swap_id; + + if (cookie) { + struct dcp_swap_cookie *info = cookie; + info->swap_id = resp->swap_id; + } + + dcp_swap_submit(dcp, false, &DCP_FW_UNION(dcp->swap), dcp_swap_cleared, cookie); +} + +static void dcp_on_final(struct apple_dcp *dcp, void *out, void *cookie) +{ + struct dcp_wait_cookie *wait = cookie; + dev_dbg(dcp->dev, "%s", __func__); + + if (wait) { + complete(&wait->done); + kref_put(&wait->refcount, release_wait_cookie); + } +} + +static void dcp_on_set_power_state(struct apple_dcp *dcp, void *out, void *cookie) +{ + struct dcp_set_power_state_req req = { + .unklong = 1, + }; + dev_dbg(dcp->dev, "%s", __func__); + + dcp_set_power_state(dcp, false, &req, dcp_on_final, cookie); +} + +static void dcp_on_set_parameter(struct apple_dcp *dcp, void *out, void *cookie) +{ + struct dcp_set_parameter_dcp param = { + .param = 14, + .value = { 0 }, +#if DCP_FW_VER >= DCP_FW_VERSION(13, 2, 0) + .count = 3, +#else + .count = 1, +#endif + }; + dev_dbg(dcp->dev, "%s", __func__); + + dcp_set_parameter_dcp(dcp, false, ¶m, dcp_on_set_power_state, cookie); +} + +void DCP_FW_NAME(iomfb_poweron)(struct apple_dcp *dcp) +{ + struct dcp_wait_cookie *cookie; + int ret; + u32 handle; + dev_info(dcp->dev, "dcp_poweron() starting\n"); + + dev_dbg(dcp->dev, "%s", __func__); + + cookie = kzalloc(sizeof(*cookie), GFP_KERNEL); + if (!cookie) + return; + + init_completion(&cookie->done); + kref_init(&cookie->refcount); + /* increase refcount to ensure the receiver has a reference */ + kref_get(&cookie->refcount); + + if (dcp->main_display) { + handle = 0; + dcp_set_display_device(dcp, false, &handle, dcp_on_set_power_state, + cookie); + } else { + handle = 2; + dcp_set_display_device(dcp, false, &handle, + dcp_on_set_parameter, cookie); + } + ret = wait_for_completion_timeout(&cookie->done, msecs_to_jiffies(500)); + + if (ret == 0) + dev_warn(dcp->dev, "wait for power timed out"); + + kref_put(&cookie->refcount, release_wait_cookie);; + + /* Force a brightness update after poweron, to restore the brightness */ + dcp->brightness.update = true; +} + +static void complete_set_powerstate(struct apple_dcp *dcp, void *out, + void *cookie) +{ + struct dcp_wait_cookie *wait = cookie; + + if (wait) { + complete(&wait->done); + kref_put(&wait->refcount, release_wait_cookie); + } +} + +static void last_client_closed_poff(struct apple_dcp *dcp, void *out, void *cookie) +{ + struct dcp_set_power_state_req power_req = { + .unklong = 0, + }; + dcp_set_power_state(dcp, false, &power_req, complete_set_powerstate, + cookie); +} + +void DCP_FW_NAME(iomfb_poweroff)(struct apple_dcp *dcp) +{ + int ret, swap_id; + struct iomfb_last_client_close_req last_client_req = {}; + struct dcp_swap_cookie *cookie; + struct dcp_wait_cookie *poff_cookie; + struct dcp_swap_start_req swap_req = { 0 }; + struct DCP_FW_NAME(dcp_swap_submit_req) *swap = &DCP_FW_UNION(dcp->swap); + + dev_dbg(dcp->dev, "%s", __func__); + + cookie = kzalloc(sizeof(*cookie), GFP_KERNEL); + if (!cookie) + return; + init_completion(&cookie->done); + kref_init(&cookie->refcount); + /* increase refcount to ensure the receiver has a reference */ + kref_get(&cookie->refcount); + + // clear surfaces + memset(swap, 0, sizeof(*swap)); + + swap->swap.swap_enabled = + swap->swap.swap_completed = IOMFB_SET_BACKGROUND | 0xF; + swap->swap.bg_color = 0xFF000000; + + /* + * Turn off the backlight. This matters because the DCP's idea of + * backlight brightness gets desynced after a power change, and it + * needs to be told it's going to turn off so it will consider the + * subsequent update on poweron an actual change and restore the + * brightness. + */ + swap->swap.bl_unk = 1; + swap->swap.bl_value = 0; + swap->swap.bl_power = 0; + + for (int l = 0; l < SWAP_SURFACES; l++) + swap->surf_null[l] = true; +#if DCP_FW_VER >= DCP_FW_VERSION(13, 2, 0) + for (int l = 0; l < 5; l++) + swap->surf2_null[l] = true; + swap->unkU32Ptr_null = true; + swap->unkU32out_null = true; +#endif + + dcp_swap_start(dcp, false, &swap_req, dcp_swap_clear_started, cookie); + + ret = wait_for_completion_timeout(&cookie->done, msecs_to_jiffies(50)); + swap_id = cookie->swap_id; + kref_put(&cookie->refcount, release_swap_cookie); + if (ret <= 0) { + dcp->crashed = true; + return; + } + + dev_dbg(dcp->dev, "%s: clear swap submitted: %u", __func__, swap_id); + + poff_cookie = kzalloc(sizeof(*poff_cookie), GFP_KERNEL); + if (!poff_cookie) + return; + init_completion(&poff_cookie->done); + kref_init(&poff_cookie->refcount); + /* increase refcount to ensure the receiver has a reference */ + kref_get(&poff_cookie->refcount); + + iomfb_last_client_close(dcp, false, &last_client_req, + last_client_closed_poff, poff_cookie); + ret = wait_for_completion_timeout(&poff_cookie->done, + msecs_to_jiffies(1000)); + + if (ret == 0) + dev_warn(dcp->dev, "setPowerState(0) timeout %u ms", 1000); + else if (ret > 0) + dev_dbg(dcp->dev, + "setPowerState(0) finished with %d ms to spare", + jiffies_to_msecs(ret)); + + kref_put(&poff_cookie->refcount, release_wait_cookie); + dev_dbg(dcp->dev, "%s: setPowerState(0) done", __func__); + + dev_info(dcp->dev, "dcp_poweroff() done\n"); +} + +static void last_client_closed_sleep(struct apple_dcp *dcp, void *out, void *cookie) +{ + struct dcp_set_power_state_req power_req = { + .unklong = 0, + }; + dcp_set_power_state(dcp, false, &power_req, complete_set_powerstate, cookie); +} + +void DCP_FW_NAME(iomfb_sleep)(struct apple_dcp *dcp) +{ + int ret; + struct iomfb_last_client_close_req req = {}; + + struct dcp_wait_cookie *cookie; + + cookie = kzalloc(sizeof(*cookie), GFP_KERNEL); + if (!cookie) + return; + init_completion(&cookie->done); + kref_init(&cookie->refcount); + /* increase refcount to ensure the receiver has a reference */ + kref_get(&cookie->refcount); + + iomfb_last_client_close(dcp, false, &req, last_client_closed_sleep, + cookie); + ret = wait_for_completion_timeout(&cookie->done, + msecs_to_jiffies(1000)); + + if (ret == 0) + dev_warn(dcp->dev, "setDCPPower(0) timeout %u ms", 1000); + + kref_put(&cookie->refcount, release_wait_cookie); + dev_dbg(dcp->dev, "%s: setDCPPower(0) done", __func__); + + dev_info(dcp->dev, "dcp_sleep() done\n"); +} + +static void dcpep_cb_hotplug(struct apple_dcp *dcp, u64 *connected) +{ + struct apple_connector *connector = dcp->connector; + + /* DCP issues hotplug_gated callbacks after SetPowerState() calls on + * devices with display (macbooks, imacs). This must not result in + * connector state changes on DRM side. Some applications won't enable + * a CRTC with a connector in disconnected state. Weston after DPMS off + * is one example. dcp_is_main_display() returns true on devices with + * integrated display. Ignore the hotplug_gated() callbacks there. + */ + if (dcp->main_display) + return; + + /* Hotplug invalidates mode. DRM doesn't always handle this. */ + if (!(*connected)) { + dcp->valid_mode = false; + /* after unplug swap will not complete until the next + * set_digital_out_mode */ + schedule_work(&dcp->vblank_wq); + } + + if (connector && connector->connected != !!(*connected)) { + connector->connected = !!(*connected); + dcp->valid_mode = false; + schedule_work(&connector->hotplug_wq); + } +} + +static void +dcpep_cb_swap_complete_intent_gated(struct apple_dcp *dcp, + struct dcp_swap_complete_intent_gated *info) +{ + trace_iomfb_swap_complete_intent_gated(dcp, info->swap_id, + info->width, info->height); +} + +TRAMPOLINE_VOID(trampoline_nop, dcpep_cb_nop); +TRAMPOLINE_OUT(trampoline_true, dcpep_cb_true, u8); +TRAMPOLINE_OUT(trampoline_false, dcpep_cb_false, u8); +TRAMPOLINE_OUT(trampoline_zero, dcpep_cb_zero, u32); +TRAMPOLINE_IN(trampoline_swap_complete, dcpep_cb_swap_complete, + struct DCP_FW_NAME(dc_swap_complete_resp)); +TRAMPOLINE_INOUT(trampoline_get_uint_prop, dcpep_cb_get_uint_prop, + struct dcp_get_uint_prop_req, struct dcp_get_uint_prop_resp); +TRAMPOLINE_IN(trampoline_set_fx_prop, iomfbep_cb_set_fx_prop, + struct iomfb_set_fx_prop_req) +TRAMPOLINE_INOUT(trampoline_map_piodma, dcpep_cb_map_piodma, + struct dcp_map_buf_req, struct dcp_map_buf_resp); +TRAMPOLINE_IN(trampoline_unmap_piodma, dcpep_cb_unmap_piodma, + struct dcp_unmap_buf_resp); +TRAMPOLINE_INOUT(trampoline_sr_set_property_int, iomfbep_cb_sr_set_property_int, + struct iomfb_sr_set_property_int_req, u8); +TRAMPOLINE_INOUT(trampoline_allocate_buffer, dcpep_cb_allocate_buffer, + struct dcp_allocate_buffer_req, + struct dcp_allocate_buffer_resp); +TRAMPOLINE_INOUT(trampoline_map_physical, dcpep_cb_map_physical, + struct dcp_map_physical_req, struct dcp_map_physical_resp); +TRAMPOLINE_INOUT(trampoline_release_mem_desc, dcpep_cb_release_mem_desc, u32, + u8); +TRAMPOLINE_INOUT(trampoline_map_reg, dcpep_cb_map_reg, + struct DCP_FW_NAME(dcp_map_reg_req), + struct DCP_FW_NAME(dcp_map_reg_resp)); +TRAMPOLINE_INOUT(trampoline_read_edt_data, dcpep_cb_read_edt_data, + struct dcp_read_edt_data_req, struct dcp_read_edt_data_resp); +TRAMPOLINE_INOUT(trampoline_prop_start, dcpep_cb_prop_start, u32, u8); +TRAMPOLINE_INOUT(trampoline_prop_chunk, dcpep_cb_prop_chunk, + struct dcp_set_dcpav_prop_chunk_req, u8); +TRAMPOLINE_INOUT(trampoline_prop_end, dcpep_cb_prop_end, + struct dcp_set_dcpav_prop_end_req, u8); +TRAMPOLINE_OUT(trampoline_rt_bandwidth, dcpep_cb_rt_bandwidth, + struct dcp_rt_bandwidth); +TRAMPOLINE_OUT(trampoline_get_frequency, dcpep_cb_get_frequency, u64); +TRAMPOLINE_OUT(trampoline_get_time, dcpep_cb_get_time, u64); +TRAMPOLINE_IN(trampoline_hotplug, dcpep_cb_hotplug, u64); +TRAMPOLINE_IN(trampoline_swap_complete_intent_gated, + dcpep_cb_swap_complete_intent_gated, + struct dcp_swap_complete_intent_gated); +TRAMPOLINE_IN(trampoline_enable_backlight_message_ap_gated, + iomfbep_cb_enable_backlight_message_ap_gated, u8); +TRAMPOLINE_IN(trampoline_pr_publish, iomfb_cb_pr_publish, + struct iomfb_property); + +/* + * Callback for swap requests. If a swap failed, we'll never get a swap + * complete event so we need to fake a vblank event early to avoid a hang. + */ + +static void dcp_swapped(struct apple_dcp *dcp, void *data, void *cookie) +{ + struct DCP_FW_NAME(dcp_swap_submit_resp) *resp = data; + + if (resp->ret) { + dev_err(dcp->dev, "swap failed! status %u\n", resp->ret); + dcp_drm_crtc_vblank(dcp->crtc); + return; + } + + while (!list_empty(&dcp->swapped_out_fbs)) { + struct dcp_fb_reference *entry; + entry = list_first_entry(&dcp->swapped_out_fbs, + struct dcp_fb_reference, head); + if (entry->fb) + drm_framebuffer_put(entry->fb); + list_del(&entry->head); + kfree(entry); + } +} + +static void dcp_swap_started(struct apple_dcp *dcp, void *data, void *cookie) +{ + struct dcp_swap_start_resp *resp = data; + + DCP_FW_UNION(dcp->swap).swap.swap_id = resp->swap_id; + + trace_iomfb_swap_submit(dcp, resp->swap_id); + dcp_swap_submit(dcp, false, &DCP_FW_UNION(dcp->swap), dcp_swapped, NULL); +} + +/* Helpers to modeset and swap, used to flush */ +static void do_swap(struct apple_dcp *dcp, void *data, void *cookie) +{ + struct dcp_swap_start_req start_req = { 0 }; + dev_dbg(dcp->dev, "%s", __func__); + + if (dcp->connector && dcp->connector->connected) + dcp_swap_start(dcp, false, &start_req, dcp_swap_started, NULL); + else + dcp_drm_crtc_vblank(dcp->crtc); +} + +static void complete_set_digital_out_mode(struct apple_dcp *dcp, void *data, + void *cookie) +{ + struct dcp_wait_cookie *wait = cookie; + dev_dbg(dcp->dev, "%s", __func__); + + if (wait) { + complete(&wait->done); + kref_put(&wait->refcount, release_wait_cookie); + } +} + +void DCP_FW_NAME(iomfb_flush)(struct apple_dcp *dcp, struct drm_crtc *crtc, struct drm_atomic_state *state) +{ + struct drm_plane *plane; + struct drm_plane_state *new_state, *old_state; + struct drm_crtc_state *crtc_state; + struct DCP_FW_NAME(dcp_swap_submit_req) *req = &DCP_FW_UNION(dcp->swap); + int plane_idx, l; + int has_surface = 0; + bool modeset; + dev_dbg(dcp->dev, "%s", __func__); + + crtc_state = drm_atomic_get_new_crtc_state(state, crtc); + + modeset = drm_atomic_crtc_needs_modeset(crtc_state) || !dcp->valid_mode; + + /* Reset to defaults */ + memset(req, 0, sizeof(*req)); + for (l = 0; l < SWAP_SURFACES; l++) + req->surf_null[l] = true; +#if DCP_FW_VER >= DCP_FW_VERSION(13, 2, 0) + for (l = 0; l < 5; l++) + req->surf2_null[l] = true; + req->unkU32Ptr_null = true; + req->unkU32out_null = true; +#endif + + /* + * Clear all surfaces on startup. The boot framebuffer in surface 0 + * sticks around. + */ + if (!dcp->surfaces_cleared) { + req->swap.swap_enabled = IOMFB_SET_BACKGROUND | 0xF; + req->swap.bg_color = 0xFF000000; + dcp->surfaces_cleared = true; + } + + // Surface 0 has limitations at least on t600x. + l = 1; + for_each_oldnew_plane_in_state(state, plane, old_state, new_state, plane_idx) { + struct drm_framebuffer *fb = new_state->fb; + struct drm_gem_dma_object *obj; + struct drm_rect src_rect; + bool is_premultiplied = false; + + /* skip planes not for this crtc */ + if (old_state->crtc != crtc && new_state->crtc != crtc) + continue; + + WARN_ON(l >= SWAP_SURFACES); + + req->swap.swap_enabled |= BIT(l); + + if (old_state->fb && fb != old_state->fb) { + /* + * Race condition between a framebuffer unbind getting + * swapped out and GEM unreferencing a framebuffer. If + * we lose the race, the display gets IOVA faults and + * the DCP crashes. We need to extend the lifetime of + * the drm_framebuffer (and hence the GEM object) until + * after we get a swap complete for the swap unbinding + * it. + */ + struct dcp_fb_reference *entry = + kzalloc(sizeof(*entry), GFP_KERNEL); + if (entry) { + entry->fb = old_state->fb; + list_add_tail(&entry->head, + &dcp->swapped_out_fbs); + } + drm_framebuffer_get(old_state->fb); + } + + if (!new_state->fb) { + l += 1; + continue; + } + req->surf_null[l] = false; + has_surface = 1; + + /* + * DCP doesn't support XBGR8 / XRGB8 natively. Blending as + * pre-multiplied alpha with a black background can be used as + * workaround for the bottommost plane. + */ + if (fb->format->format == DRM_FORMAT_XRGB8888 || + fb->format->format == DRM_FORMAT_XBGR8888) + is_premultiplied = true; + + drm_rect_fp_to_int(&src_rect, &new_state->src); + + req->swap.src_rect[l] = drm_to_dcp_rect(&src_rect); + req->swap.dst_rect[l] = drm_to_dcp_rect(&new_state->dst); + + if (dcp->notch_height > 0) + req->swap.dst_rect[l].y += dcp->notch_height; + + /* the obvious helper call drm_fb_dma_get_gem_addr() adjusts + * the address for source x/y offsets. Since IOMFB has a direct + * support source position prefer that. + */ + obj = drm_fb_dma_get_gem_obj(fb, 0); + if (obj) + req->surf_iova[l] = obj->dma_addr + fb->offsets[0]; + + req->surf[l] = (struct DCP_FW_NAME(dcp_surface)){ + .is_premultiplied = is_premultiplied, + .format = drm_format_to_dcp(fb->format->format), + .xfer_func = DCP_XFER_FUNC_SDR, + .colorspace = DCP_COLORSPACE_NATIVE, + .stride = fb->pitches[0], + .width = fb->width, + .height = fb->height, + .buf_size = fb->height * fb->pitches[0], + .surface_id = req->swap.surf_ids[l], + + /* Only used for compressed or multiplanar surfaces */ + .pix_size = 1, + .pel_w = 1, + .pel_h = 1, + .has_comp = 1, + .has_planes = 1, + }; + + l += 1; + } + + if (modeset) { + struct dcp_display_mode *mode; + struct dcp_wait_cookie *cookie; + int ret; + + mode = lookup_mode(dcp, &crtc_state->mode); + if (!mode) { + dev_warn(dcp->dev, "no match for " DRM_MODE_FMT, + DRM_MODE_ARG(&crtc_state->mode)); + schedule_work(&dcp->vblank_wq); + return; + } + + dev_info(dcp->dev, "set_digital_out_mode(color:%d timing:%d)", + mode->color_mode_id, mode->timing_mode_id); + dcp->mode = (struct dcp_set_digital_out_mode_req){ + .color_mode_id = mode->color_mode_id, + .timing_mode_id = mode->timing_mode_id + }; + + cookie = kzalloc(sizeof(*cookie), GFP_KERNEL); + if (!cookie) { + schedule_work(&dcp->vblank_wq); + return; + } + + init_completion(&cookie->done); + kref_init(&cookie->refcount); + /* increase refcount to ensure the receiver has a reference */ + kref_get(&cookie->refcount); + + dcp_set_digital_out_mode(dcp, false, &dcp->mode, + complete_set_digital_out_mode, cookie); + + dev_dbg(dcp->dev, "%s - wait for modeset", __func__); + ret = wait_for_completion_timeout(&cookie->done, + msecs_to_jiffies(500)); + + kref_put(&cookie->refcount, release_wait_cookie); + + if (ret == 0) { + dev_dbg(dcp->dev, "set_digital_out_mode 200 ms"); + schedule_work(&dcp->vblank_wq); + return; + } else if (ret > 0) { + dev_dbg(dcp->dev, + "set_digital_out_mode finished with %d to spare", + jiffies_to_msecs(ret)); + } + + dcp->valid_mode = true; + } + + if (!has_surface && !crtc_state->color_mgmt_changed) { + if (crtc_state->enable && crtc_state->active && + !crtc_state->planes_changed) { + schedule_work(&dcp->vblank_wq); + return; + } + + /* Set black background */ + req->swap.swap_enabled |= IOMFB_SET_BACKGROUND; + req->swap.bg_color = 0xFF000000; + req->clear = 1; + } + + /* These fields should be set together */ + req->swap.swap_completed = req->swap.swap_enabled; + + /* update brightness if changed */ + if (dcp->brightness.update) { + req->swap.bl_unk = 1; + req->swap.bl_value = dcp->brightness.dac; + req->swap.bl_power = 0x40; + dcp->brightness.update = false; + } + + do_swap(dcp, NULL, NULL); +} + +static void res_is_main_display(struct apple_dcp *dcp, void *out, void *cookie) +{ + struct apple_connector *connector; + int result = *(int *)out; + dev_info(dcp->dev, "DCP is_main_display: %d\n", result); + + dcp->main_display = result != 0; + + connector = dcp->connector; + if (connector) { + connector->connected = dcp->nr_modes > 0; + schedule_work(&connector->hotplug_wq); + } + + dcp->active = true; + complete(&dcp->start_done); +} + +static void init_3(struct apple_dcp *dcp, void *out, void *cookie) +{ + dcp_is_main_display(dcp, false, res_is_main_display, NULL); +} + +static void init_2(struct apple_dcp *dcp, void *out, void *cookie) +{ + dcp_first_client_open(dcp, false, init_3, NULL); +} + +static void init_1(struct apple_dcp *dcp, void *out, void *cookie) +{ + u32 val = 0; + dcp_enable_disable_video_power_savings(dcp, false, &val, init_2, NULL); +} + +static void dcp_started(struct apple_dcp *dcp, void *data, void *cookie) +{ + struct iomfb_get_color_remap_mode_req color_remap = + (struct iomfb_get_color_remap_mode_req){ + .mode = 6, + }; + + dev_info(dcp->dev, "DCP booted\n"); + + iomfb_get_color_remap_mode(dcp, false, &color_remap, init_1, cookie); +} + +void DCP_FW_NAME(iomfb_shutdown)(struct apple_dcp *dcp) +{ + struct dcp_set_power_state_req req = { + /* defaults are ok */ + }; + + dcp_set_power_state(dcp, false, &req, NULL, NULL); +} diff --git a/drivers/gpu/drm/apple/iomfb_template.h b/drivers/gpu/drm/apple/iomfb_template.h new file mode 100644 index 00000000000000..539ec65e5825f4 --- /dev/null +++ b/drivers/gpu/drm/apple/iomfb_template.h @@ -0,0 +1,181 @@ +// SPDX-License-Identifier: GPL-2.0-only OR MIT +/* Copyright 2021 Alyssa Rosenzweig */ + +/* + * This file is intended to be included multiple times with IOMFB_VER + * defined to declare DCP firmware version dependent structs. + */ + +#ifdef DCP_FW_VER + +#include + +#include + +#include "iomfb.h" +#include "version_utils.h" + +struct DCP_FW_NAME(dcp_swap) { + u64 ts1; + u64 ts2; + u64 unk_10[6]; + u64 flags1; + u64 flags2; + + u32 swap_id; + + u32 surf_ids[SWAP_SURFACES]; + struct dcp_rect src_rect[SWAP_SURFACES]; + u32 surf_flags[SWAP_SURFACES]; + u32 surf_unk[SWAP_SURFACES]; + struct dcp_rect dst_rect[SWAP_SURFACES]; + u32 swap_enabled; + u32 swap_completed; + + u32 bg_color; + u8 unk_110[0x1b8]; + u32 unk_2c8; + u8 unk_2cc[0x14]; + u32 unk_2e0; +#if DCP_FW_VER < DCP_FW_VERSION(13, 2, 0) + u16 unk_2e2; +#else + u8 unk_2e2[3]; +#endif + u64 bl_unk; + u32 bl_value; // min value is 0x10000000 + u8 bl_power; // constant 0x40 for on + u8 unk_2f3[0x2d]; +#if DCP_FW_VER >= DCP_FW_VERSION(13, 2, 0) + u8 unk_320[0x13f]; +#endif +} __packed; + +/* Information describing a surface */ +struct DCP_FW_NAME(dcp_surface) { + u8 is_tiled; + u8 is_tearing_allowed; + u8 is_premultiplied; + u32 plane_cnt; + u32 plane_cnt2; + u32 format; /* DCP fourcc */ + u32 ycbcr_matrix; + u8 xfer_func; + u8 colorspace; + u32 stride; + u16 pix_size; + u8 pel_w; + u8 pel_h; + u32 offset; + u32 width; + u32 height; + u32 buf_size; + u64 protection_opts; + u32 surface_id; + struct dcp_component_types comp_types[MAX_PLANES]; + u64 has_comp; + struct dcp_plane_info planes[MAX_PLANES]; + u64 has_planes; + u32 compression_info[MAX_PLANES][13]; + u64 has_compr_info; + u32 unk_num; + u32 unk_denom; +#if DCP_FW_VER < DCP_FW_VERSION(13, 2, 0) + u8 padding[7]; +#else + u8 padding[47]; +#endif +} __packed; + +/* Prototypes */ + +struct DCP_FW_NAME(dcp_swap_submit_req) { + struct DCP_FW_NAME(dcp_swap) swap; + struct DCP_FW_NAME(dcp_surface) surf[SWAP_SURFACES]; + u64 surf_iova[SWAP_SURFACES]; +#if DCP_FW_VER >= DCP_FW_VERSION(13, 2, 0) + u64 unk_u64_a[SWAP_SURFACES]; + struct DCP_FW_NAME(dcp_surface) surf2[5]; + u64 surf2_iova[5]; +#endif + u8 unkbool; + u64 unkdouble; +#if DCP_FW_VER >= DCP_FW_VERSION(13, 2, 0) + u64 unkU64; + u8 unkbool2; +#endif + u32 clear; // or maybe switch to default fb? +#if DCP_FW_VER >= DCP_FW_VERSION(13, 2, 0) + u32 unkU32Ptr; +#endif + u8 swap_null; + u8 surf_null[SWAP_SURFACES]; +#if DCP_FW_VER >= DCP_FW_VERSION(13, 2, 0) + u8 surf2_null[5]; +#endif + u8 unkoutbool_null; +#if DCP_FW_VER >= DCP_FW_VERSION(13, 2, 0) + u8 unkU32Ptr_null; + u8 unkU32out_null; +#endif + u8 padding[1]; +} __packed; + +struct DCP_FW_NAME(dcp_swap_submit_resp) { + u8 unkoutbool; +#if DCP_FW_VER >= DCP_FW_VERSION(13, 2, 0) + u32 unkU32out; +#endif + u32 ret; + u8 padding[3]; +} __packed; + +struct DCP_FW_NAME(dc_swap_complete_resp) { + u32 swap_id; + u8 unkbool; + u64 swap_data; +#if DCP_FW_VER < DCP_FW_VERSION(13, 2, 0) + u8 swap_info[0x6c4]; +#else + u8 swap_info[0x6c5]; +#endif + u32 unkint; + u8 swap_info_null; +} __packed; + +struct DCP_FW_NAME(dcp_map_reg_req) { + char obj[4]; + u32 index; + u32 flags; +#if DCP_FW_VER >= DCP_FW_VERSION(13, 2, 0) + u8 unk_u64_null; +#endif + u8 addr_null; + u8 length_null; +#if DCP_FW_VER >= DCP_FW_VERSION(13, 2, 0) + u8 padding[1]; +#else + u8 padding[2]; +#endif +} __packed; + +struct DCP_FW_NAME(dcp_map_reg_resp) { +#if DCP_FW_VER >= DCP_FW_VERSION(13, 2, 0) + u64 dva; +#endif + u64 addr; + u64 length; + u32 ret; +} __packed; + + +struct apple_dcp; + +void DCP_FW_NAME(iomfb_flush)(struct apple_dcp *dcp, struct drm_crtc *crtc, struct drm_atomic_state *state); +void DCP_FW_NAME(iomfb_poweron)(struct apple_dcp *dcp); +void DCP_FW_NAME(iomfb_poweroff)(struct apple_dcp *dcp); +void DCP_FW_NAME(iomfb_sleep)(struct apple_dcp *dcp); +void DCP_FW_NAME(iomfb_start)(struct apple_dcp *dcp); +void DCP_FW_NAME(iomfb_shutdown)(struct apple_dcp *dcp); + +#endif diff --git a/drivers/gpu/drm/apple/iomfb_v12_3.c b/drivers/gpu/drm/apple/iomfb_v12_3.c new file mode 100644 index 00000000000000..354abbfdb24c36 --- /dev/null +++ b/drivers/gpu/drm/apple/iomfb_v12_3.c @@ -0,0 +1,105 @@ +// SPDX-License-Identifier: GPL-2.0-only OR MIT +/* Copyright The Asahi Linux Contributors */ + +#include "iomfb_v12_3.h" +#include "iomfb_v13_2.h" +#include "version_utils.h" + +static const struct dcp_method_entry dcp_methods[dcpep_num_methods] = { + IOMFB_METHOD("A000", dcpep_late_init_signal), + IOMFB_METHOD("A029", dcpep_setup_video_limits), + IOMFB_METHOD("A131", iomfbep_a131_pmu_service_matched), + IOMFB_METHOD("A132", iomfbep_a132_backlight_service_matched), + IOMFB_METHOD("A357", dcpep_set_create_dfb), + IOMFB_METHOD("A358", iomfbep_a358_vi_set_temperature_hint), + IOMFB_METHOD("A401", dcpep_start_signal), + IOMFB_METHOD("A407", dcpep_swap_start), + IOMFB_METHOD("A408", dcpep_swap_submit), + IOMFB_METHOD("A410", dcpep_set_display_device), + IOMFB_METHOD("A411", dcpep_is_main_display), + IOMFB_METHOD("A412", dcpep_set_digital_out_mode), + IOMFB_METHOD("A426", iomfbep_get_color_remap_mode), + IOMFB_METHOD("A439", dcpep_set_parameter_dcp), + IOMFB_METHOD("A443", dcpep_create_default_fb), + IOMFB_METHOD("A447", dcpep_enable_disable_video_power_savings), + IOMFB_METHOD("A454", dcpep_first_client_open), + IOMFB_METHOD("A455", iomfbep_last_client_close), + IOMFB_METHOD("A460", dcpep_set_display_refresh_properties), + IOMFB_METHOD("A463", dcpep_flush_supports_power), + IOMFB_METHOD("A468", dcpep_set_power_state), +}; + +#define DCP_FW v12_3 +#define DCP_FW_VER DCP_FW_VERSION(12, 3, 0) + +#include "iomfb_template.c" + +static const iomfb_cb_handler cb_handlers[IOMFB_MAX_CB] = { + [0] = trampoline_true, /* did_boot_signal */ + [1] = trampoline_true, /* did_power_on_signal */ + [2] = trampoline_nop, /* will_power_off_signal */ + [3] = trampoline_rt_bandwidth, + [100] = iomfbep_cb_match_pmu_service, + [101] = trampoline_zero, /* get_display_default_stride */ + [102] = trampoline_nop, /* set_number_property */ + [103] = trampoline_nop, /* set_boolean_property */ + [106] = trampoline_nop, /* remove_property */ + [107] = trampoline_true, /* create_provider_service */ + [108] = trampoline_true, /* create_product_service */ + [109] = trampoline_true, /* create_pmu_service */ + [110] = trampoline_true, /* create_iomfb_service */ + [111] = trampoline_true, /* create_backlight_service */ + [116] = dcpep_cb_boot_1, + [117] = trampoline_false, /* is_dark_boot */ + [118] = trampoline_false, /* is_dark_boot / is_waking_from_hibernate*/ + [120] = trampoline_read_edt_data, + [122] = trampoline_prop_start, + [123] = trampoline_prop_chunk, + [124] = trampoline_prop_end, + [201] = trampoline_map_piodma, + [202] = trampoline_unmap_piodma, + [206] = iomfbep_cb_match_pmu_service_2, + [207] = iomfbep_cb_match_backlight_service, + [208] = trampoline_get_time, + [211] = trampoline_nop, /* update_backlight_factor_prop */ + [300] = trampoline_pr_publish, + [401] = trampoline_get_uint_prop, + [404] = trampoline_nop, /* sr_set_uint_prop */ + [406] = trampoline_set_fx_prop, + [408] = trampoline_get_frequency, + [411] = trampoline_map_reg, + [413] = trampoline_true, /* sr_set_property_dict */ + [414] = trampoline_sr_set_property_int, + [415] = trampoline_true, /* sr_set_property_bool */ + [451] = trampoline_allocate_buffer, + [452] = trampoline_map_physical, + [456] = trampoline_release_mem_desc, + [552] = trampoline_true, /* set_property_dict_0 */ + [561] = trampoline_true, /* set_property_dict */ + [563] = trampoline_true, /* set_property_int */ + [565] = trampoline_true, /* set_property_bool */ + [567] = trampoline_true, /* set_property_str */ + [574] = trampoline_zero, /* power_up_dart */ + [576] = trampoline_hotplug, + [577] = trampoline_nop, /* powerstate_notify */ + [582] = trampoline_true, /* create_default_fb_surface */ + [584] = trampoline_nop, /* IOMobileFramebufferAP::clear_default_surface */ + [588] = trampoline_nop, /* resize_default_fb_surface_gated */ + [589] = trampoline_swap_complete, + [591] = trampoline_swap_complete_intent_gated, + [593] = trampoline_enable_backlight_message_ap_gated, + [594] = trampoline_nop, /* IOMobileFramebufferAP::setSystemConsoleMode */ + [596] = trampoline_false, /* IOMobileFramebufferAP::isDFBAllocated */ + [597] = trampoline_false, /* IOMobileFramebufferAP::preserveContents */ + [598] = trampoline_nop, /* find_swap_function_gated */ +}; + +void DCP_FW_NAME(iomfb_start)(struct apple_dcp *dcp) +{ + dcp->cb_handlers = cb_handlers; + + dcp_start_signal(dcp, false, dcp_started, NULL); +} + +#undef DCP_FW_VER +#undef DCP_FW diff --git a/drivers/gpu/drm/apple/iomfb_v12_3.h b/drivers/gpu/drm/apple/iomfb_v12_3.h new file mode 100644 index 00000000000000..7359685d981fe5 --- /dev/null +++ b/drivers/gpu/drm/apple/iomfb_v12_3.h @@ -0,0 +1,17 @@ +// SPDX-License-Identifier: GPL-2.0-only OR MIT +/* Copyright The Asahi Linux Contributors */ + +#ifndef __APPLE_IOMFB_V12_3_H__ +#define __APPLE_IOMFB_V12_3_H__ + +#include "version_utils.h" + +#define DCP_FW v12_3 +#define DCP_FW_VER DCP_FW_VERSION(12, 3, 0) + +#include "iomfb_template.h" + +#undef DCP_FW_VER +#undef DCP_FW + +#endif /* __APPLE_IOMFB_V12_3_H__ */ diff --git a/drivers/gpu/drm/apple/iomfb_v13_2.c b/drivers/gpu/drm/apple/iomfb_v13_2.c new file mode 100644 index 00000000000000..27f1d84e928a69 --- /dev/null +++ b/drivers/gpu/drm/apple/iomfb_v13_2.c @@ -0,0 +1,105 @@ +// SPDX-License-Identifier: GPL-2.0-only OR MIT +/* Copyright The Asahi Linux Contributors */ + +#include "iomfb_v12_3.h" +#include "iomfb_v13_2.h" +#include "version_utils.h" + +static const struct dcp_method_entry dcp_methods[dcpep_num_methods] = { + IOMFB_METHOD("A000", dcpep_late_init_signal), + IOMFB_METHOD("A029", dcpep_setup_video_limits), + IOMFB_METHOD("A131", iomfbep_a131_pmu_service_matched), + IOMFB_METHOD("A132", iomfbep_a132_backlight_service_matched), + IOMFB_METHOD("A373", dcpep_set_create_dfb), + IOMFB_METHOD("A374", iomfbep_a358_vi_set_temperature_hint), + IOMFB_METHOD("A401", dcpep_start_signal), + IOMFB_METHOD("A407", dcpep_swap_start), + IOMFB_METHOD("A408", dcpep_swap_submit), + IOMFB_METHOD("A410", dcpep_set_display_device), + IOMFB_METHOD("A411", dcpep_is_main_display), + IOMFB_METHOD("A412", dcpep_set_digital_out_mode), + IOMFB_METHOD("A426", iomfbep_get_color_remap_mode), + IOMFB_METHOD("A441", dcpep_set_parameter_dcp), + IOMFB_METHOD("A445", dcpep_create_default_fb), + IOMFB_METHOD("A449", dcpep_enable_disable_video_power_savings), + IOMFB_METHOD("A456", dcpep_first_client_open), + IOMFB_METHOD("A457", iomfbep_last_client_close), + IOMFB_METHOD("A462", dcpep_set_display_refresh_properties), + IOMFB_METHOD("A465", dcpep_flush_supports_power), + IOMFB_METHOD("A471", dcpep_set_power_state), +}; + +#define DCP_FW v13_2 +#define DCP_FW_VER DCP_FW_VERSION(13, 2, 0) + +#include "iomfb_template.c" + +static const iomfb_cb_handler cb_handlers[IOMFB_MAX_CB] = { + [0] = trampoline_true, /* did_boot_signal */ + [1] = trampoline_true, /* did_power_on_signal */ + [2] = trampoline_nop, /* will_power_off_signal */ + [3] = trampoline_rt_bandwidth, + [100] = iomfbep_cb_match_pmu_service, + [101] = trampoline_zero, /* get_display_default_stride */ + [102] = trampoline_nop, /* set_number_property */ + [103] = trampoline_nop, /* set_boolean_property */ + [106] = trampoline_nop, /* remove_property */ + [107] = trampoline_true, /* create_provider_service */ + [108] = trampoline_true, /* create_product_service */ + [109] = trampoline_true, /* create_pmu_service */ + [110] = trampoline_true, /* create_iomfb_service */ + [111] = trampoline_true, /* create_backlight_service */ + [112] = trampoline_true, /* create_nvram_servce? */ + [119] = dcpep_cb_boot_1, + [120] = trampoline_false, /* is_dark_boot */ + [121] = trampoline_false, /* is_dark_boot / is_waking_from_hibernate*/ + [123] = trampoline_read_edt_data, + [125] = trampoline_prop_start, + [126] = trampoline_prop_chunk, + [127] = trampoline_prop_end, + [201] = trampoline_map_piodma, + [202] = trampoline_unmap_piodma, + [206] = iomfbep_cb_match_pmu_service_2, + [207] = iomfbep_cb_match_backlight_service, + [208] = trampoline_get_time, + [211] = trampoline_nop, /* update_backlight_factor_prop */ + [300] = trampoline_pr_publish, + [401] = trampoline_get_uint_prop, + [404] = trampoline_nop, /* sr_set_uint_prop */ + [406] = trampoline_set_fx_prop, + [408] = trampoline_get_frequency, + [411] = trampoline_map_reg, + [413] = trampoline_true, /* sr_set_property_dict */ + [414] = trampoline_sr_set_property_int, + [415] = trampoline_true, /* sr_set_property_bool */ + [451] = trampoline_allocate_buffer, + [452] = trampoline_map_physical, + [456] = trampoline_release_mem_desc, + [552] = trampoline_true, /* set_property_dict_0 */ + [561] = trampoline_true, /* set_property_dict */ + [563] = trampoline_true, /* set_property_int */ + [565] = trampoline_true, /* set_property_bool */ + [567] = trampoline_true, /* set_property_str */ + [574] = trampoline_zero, /* power_up_dart */ + [576] = trampoline_hotplug, + [577] = trampoline_nop, /* powerstate_notify */ + [582] = trampoline_true, /* create_default_fb_surface */ + [584] = trampoline_nop, /* IOMobileFramebufferAP::clear_default_surface */ + [588] = trampoline_nop, /* resize_default_fb_surface_gated */ + [589] = trampoline_swap_complete, + [591] = trampoline_swap_complete_intent_gated, + [593] = trampoline_enable_backlight_message_ap_gated, + [594] = trampoline_nop, /* IOMobileFramebufferAP::setSystemConsoleMode */ + [596] = trampoline_false, /* IOMobileFramebufferAP::isDFBAllocated */ + [597] = trampoline_false, /* IOMobileFramebufferAP::preserveContents */ + [598] = trampoline_nop, /* find_swap_function_gated */ +}; +void DCP_FW_NAME(iomfb_start)(struct apple_dcp *dcp) +{ + dcp->cb_handlers = cb_handlers; + + dcp_start_signal(dcp, false, dcp_started, NULL); +} + +#undef DCP_FW_VER +#undef DCP_FW diff --git a/drivers/gpu/drm/apple/iomfb_v13_2.h b/drivers/gpu/drm/apple/iomfb_v13_2.h new file mode 100644 index 00000000000000..f3810b727235bc --- /dev/null +++ b/drivers/gpu/drm/apple/iomfb_v13_2.h @@ -0,0 +1,17 @@ +// SPDX-License-Identifier: GPL-2.0-only OR MIT +/* Copyright The Asahi Linux Contributors */ + +#ifndef __APPLE_IOMFB_V13_2_H__ +#define __APPLE_IOMFB_V13_2_H__ + +#include "version_utils.h" + +#define DCP_FW v13_2 +#define DCP_FW_VER DCP_FW_VERSION(13, 2, 0) + +#include "iomfb_template.h" + +#undef DCP_FW_VER +#undef DCP_FW + +#endif /* __APPLE_IOMFB_V13_2_H__ */ diff --git a/drivers/gpu/drm/apple/version_utils.h b/drivers/gpu/drm/apple/version_utils.h new file mode 100644 index 00000000000000..5a33ce1db61c47 --- /dev/null +++ b/drivers/gpu/drm/apple/version_utils.h @@ -0,0 +1,15 @@ +// SPDX-License-Identifier: GPL-2.0-only OR MIT +/* Copyright The Asahi Linux Contributors */ + +#ifndef __APPLE_VERSION_UTILS_H__ +#define __APPLE_VERSION_UTILS_H__ + +#include +#include + +#define DCP_FW_UNION(u) (u).DCP_FW +#define DCP_FW_SUFFIX CONCATENATE(_, DCP_FW) +#define DCP_FW_NAME(name) CONCATENATE(name, DCP_FW_SUFFIX) +#define DCP_FW_VERSION(x, y, z) ( ((x) << 16) | ((y) << 8) | (z) ) + +#endif /*__APPLE_VERSION_UTILS_H__*/ From 3e0c229df4bb406c3cfd937ba5a1ef78b71da36a Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Thu, 9 Mar 2023 12:44:51 +0100 Subject: [PATCH 1074/3902] drm/apple: ignore surf[3] in clear swap calls MacOS 13.2 does the same and it is unclear if surf[3] can be used at all. PRobably not necessary but found during debugging to firmware 13.2. Signed-off-by: Janne Grunau --- drivers/gpu/drm/apple/iomfb_template.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/gpu/drm/apple/iomfb_template.c b/drivers/gpu/drm/apple/iomfb_template.c index 25ec66a2f24f53..ed49088953bb64 100644 --- a/drivers/gpu/drm/apple/iomfb_template.c +++ b/drivers/gpu/drm/apple/iomfb_template.c @@ -841,7 +841,7 @@ void DCP_FW_NAME(iomfb_poweroff)(struct apple_dcp *dcp) memset(swap, 0, sizeof(*swap)); swap->swap.swap_enabled = - swap->swap.swap_completed = IOMFB_SET_BACKGROUND | 0xF; + swap->swap.swap_completed = IOMFB_SET_BACKGROUND | 0x7; swap->swap.bg_color = 0xFF000000; /* @@ -1113,7 +1113,7 @@ void DCP_FW_NAME(iomfb_flush)(struct apple_dcp *dcp, struct drm_crtc *crtc, stru * sticks around. */ if (!dcp->surfaces_cleared) { - req->swap.swap_enabled = IOMFB_SET_BACKGROUND | 0xF; + req->swap.swap_enabled = IOMFB_SET_BACKGROUND | 0x7; req->swap.bg_color = 0xFF000000; dcp->surfaces_cleared = true; } From 8ae91d75123ca069571d2a82dee98993678b8c48 Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Sun, 12 Mar 2023 21:38:50 +0100 Subject: [PATCH 1075/3902] drm/apple: Support color transformation matrices kwin 5.27.3 adds support for "Night Color" via drm "CTM" properties. Wire CTM support up via the "set_matrix" iomfb call. Link: https://bugs.kde.org/show_bug.cgi?id=455720 Signed-off-by: Janne Grunau --- drivers/gpu/drm/apple/apple_drv.c | 1 + drivers/gpu/drm/apple/iomfb.h | 14 ++++++++++++++ drivers/gpu/drm/apple/iomfb_template.c | 20 +++++++++++++++++++- drivers/gpu/drm/apple/iomfb_v12_3.c | 1 + drivers/gpu/drm/apple/iomfb_v13_2.c | 1 + 5 files changed, 36 insertions(+), 1 deletion(-) diff --git a/drivers/gpu/drm/apple/apple_drv.c b/drivers/gpu/drm/apple/apple_drv.c index d070832f7b1cec..057ed785d8115a 100644 --- a/drivers/gpu/drm/apple/apple_drv.c +++ b/drivers/gpu/drm/apple/apple_drv.c @@ -336,6 +336,7 @@ static int apple_probe_per_dcp(struct device *dev, return ret; drm_crtc_helper_add(&crtc->base, &apple_crtc_helper_funcs); + drm_crtc_enable_color_mgmt(&crtc->base, 0, true, 0); enc = drmm_simple_encoder_alloc(drm, struct apple_encoder, base, DRM_MODE_ENCODER_TMDS); diff --git a/drivers/gpu/drm/apple/iomfb.h b/drivers/gpu/drm/apple/iomfb.h index 0c8061bb96e3e0..7cda9124bcf96b 100644 --- a/drivers/gpu/drm/apple/iomfb.h +++ b/drivers/gpu/drm/apple/iomfb.h @@ -160,6 +160,7 @@ enum dcpep_method { iomfbep_a358_vi_set_temperature_hint, iomfbep_get_color_remap_mode, iomfbep_last_client_close, + iomfbep_set_matrix, dcpep_num_methods }; @@ -350,4 +351,17 @@ struct iomfb_last_client_close_resp { u32 unkint; } __packed; +struct iomfb_set_matrix_req { + u32 unk_u32; // maybe length? + u64 r[3]; + u64 g[3]; + u64 b[3]; + u8 matrix_null; + u8 padding[3]; +} __packed; + +struct iomfb_set_matrix_resp { + u32 ret; +} __packed; + #endif diff --git a/drivers/gpu/drm/apple/iomfb_template.c b/drivers/gpu/drm/apple/iomfb_template.c index ed49088953bb64..10286b1ad0f96d 100644 --- a/drivers/gpu/drm/apple/iomfb_template.c +++ b/drivers/gpu/drm/apple/iomfb_template.c @@ -55,6 +55,7 @@ DCP_THUNK_OUT(iomfb_a131_pmu_service_matched, iomfbep_a131_pmu_service_matched, DCP_THUNK_OUT(iomfb_a132_backlight_service_matched, iomfbep_a132_backlight_service_matched, u32); DCP_THUNK_OUT(iomfb_a358_vi_set_temperature_hint, iomfbep_a358_vi_set_temperature_hint, u32); +IOMFB_THUNK_INOUT(set_matrix); IOMFB_THUNK_INOUT(get_color_remap_mode); IOMFB_THUNK_INOUT(last_client_close); @@ -1285,7 +1286,24 @@ void DCP_FW_NAME(iomfb_flush)(struct apple_dcp *dcp, struct drm_crtc *crtc, stru dcp->brightness.update = false; } - do_swap(dcp, NULL, NULL); + if (crtc_state->color_mgmt_changed && crtc_state->ctm) { + struct iomfb_set_matrix_req mat; + struct drm_color_ctm *ctm = (struct drm_color_ctm *)crtc_state->ctm->data; + + mat.unk_u32 = 9; + mat.r[0] = ctm->matrix[0]; + mat.r[1] = ctm->matrix[1]; + mat.r[2] = ctm->matrix[2]; + mat.g[0] = ctm->matrix[3]; + mat.g[1] = ctm->matrix[4]; + mat.g[2] = ctm->matrix[5]; + mat.b[0] = ctm->matrix[6]; + mat.b[1] = ctm->matrix[7]; + mat.b[2] = ctm->matrix[8]; + + iomfb_set_matrix(dcp, false, &mat, do_swap, NULL); + } else + do_swap(dcp, NULL, NULL); } static void res_is_main_display(struct apple_dcp *dcp, void *out, void *cookie) diff --git a/drivers/gpu/drm/apple/iomfb_v12_3.c b/drivers/gpu/drm/apple/iomfb_v12_3.c index 354abbfdb24c36..c226a1139a84c8 100644 --- a/drivers/gpu/drm/apple/iomfb_v12_3.c +++ b/drivers/gpu/drm/apple/iomfb_v12_3.c @@ -18,6 +18,7 @@ static const struct dcp_method_entry dcp_methods[dcpep_num_methods] = { IOMFB_METHOD("A410", dcpep_set_display_device), IOMFB_METHOD("A411", dcpep_is_main_display), IOMFB_METHOD("A412", dcpep_set_digital_out_mode), + IOMFB_METHOD("A422", iomfbep_set_matrix), IOMFB_METHOD("A426", iomfbep_get_color_remap_mode), IOMFB_METHOD("A439", dcpep_set_parameter_dcp), IOMFB_METHOD("A443", dcpep_create_default_fb), diff --git a/drivers/gpu/drm/apple/iomfb_v13_2.c b/drivers/gpu/drm/apple/iomfb_v13_2.c index 27f1d84e928a69..63ae1e79adda10 100644 --- a/drivers/gpu/drm/apple/iomfb_v13_2.c +++ b/drivers/gpu/drm/apple/iomfb_v13_2.c @@ -18,6 +18,7 @@ static const struct dcp_method_entry dcp_methods[dcpep_num_methods] = { IOMFB_METHOD("A410", dcpep_set_display_device), IOMFB_METHOD("A411", dcpep_is_main_display), IOMFB_METHOD("A412", dcpep_set_digital_out_mode), + IOMFB_METHOD("A422", iomfbep_set_matrix), IOMFB_METHOD("A426", iomfbep_get_color_remap_mode), IOMFB_METHOD("A441", dcpep_set_parameter_dcp), IOMFB_METHOD("A445", dcpep_create_default_fb), From 3805a516e4862d4b365dcccbf10f1fd23fbc46bb Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Thu, 23 Mar 2023 08:40:42 +0100 Subject: [PATCH 1076/3902] drm/apple: Drop unsupported DRM_FORMAT_ARGB2101010 Depends on https://gitlab.freedesktop.org/asahi/mesa/-/merge_requests/5 Signed-off-by: Janne Grunau --- drivers/gpu/drm/apple/apple_drv.c | 1 - drivers/gpu/drm/apple/iomfb.c | 1 - 2 files changed, 2 deletions(-) diff --git a/drivers/gpu/drm/apple/apple_drv.c b/drivers/gpu/drm/apple/apple_drv.c index 057ed785d8115a..97a9ca7db6a31f 100644 --- a/drivers/gpu/drm/apple/apple_drv.c +++ b/drivers/gpu/drm/apple/apple_drv.c @@ -147,7 +147,6 @@ static const struct drm_plane_funcs apple_plane_funcs = { */ static const u32 dcp_formats[] = { DRM_FORMAT_XRGB2101010, - DRM_FORMAT_ARGB2101010, DRM_FORMAT_XRGB8888, DRM_FORMAT_ARGB8888, DRM_FORMAT_XBGR8888, diff --git a/drivers/gpu/drm/apple/iomfb.c b/drivers/gpu/drm/apple/iomfb.c index 447de730af721f..1ce32eae212ddb 100644 --- a/drivers/gpu/drm/apple/iomfb.c +++ b/drivers/gpu/drm/apple/iomfb.c @@ -412,7 +412,6 @@ u32 drm_format_to_dcp(u32 drm) case DRM_FORMAT_ABGR8888: return fourcc_code('A', 'B', 'G', 'R'); - case DRM_FORMAT_ARGB2101010: case DRM_FORMAT_XRGB2101010: return fourcc_code('r', '0', '3', 'w'); } From 025caf9cf88f03bab94556af8231168bf20b4066 Mon Sep 17 00:00:00 2001 From: Hector Martin Date: Tue, 11 Apr 2023 21:57:22 +0900 Subject: [PATCH 1077/3902] dcp: Allow unused trampolines Signed-off-by: Hector Martin --- drivers/gpu/drm/apple/iomfb_internal.h | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/drivers/gpu/drm/apple/iomfb_internal.h b/drivers/gpu/drm/apple/iomfb_internal.h index 401b6ec32848d3..09f8857d30c341 100644 --- a/drivers/gpu/drm/apple/iomfb_internal.h +++ b/drivers/gpu/drm/apple/iomfb_internal.h @@ -57,7 +57,7 @@ typedef void (*dcp_callback_t)(struct apple_dcp *, void *, void *); */ #define TRAMPOLINE_VOID(func, handler) \ - static bool func(struct apple_dcp *dcp, int tag, void *out, void *in) \ + static bool __maybe_unused func(struct apple_dcp *dcp, int tag, void *out, void *in) \ { \ trace_iomfb_callback(dcp, tag, #handler); \ handler(dcp); \ @@ -67,7 +67,7 @@ typedef void (*dcp_callback_t)(struct apple_dcp *, void *, void *); #define TRAMPOLINE_IN(func, handler, T_in) \ typedef void (*callback_##handler)(struct apple_dcp *, T_in *); \ \ - static bool func(struct apple_dcp *dcp, int tag, void *out, void *in) \ + static bool __maybe_unused func(struct apple_dcp *dcp, int tag, void *out, void *in) \ { \ callback_##handler cb = handler; \ \ @@ -79,7 +79,7 @@ typedef void (*dcp_callback_t)(struct apple_dcp *, void *, void *); #define TRAMPOLINE_INOUT(func, handler, T_in, T_out) \ typedef T_out (*callback_##handler)(struct apple_dcp *, T_in *); \ \ - static bool func(struct apple_dcp *dcp, int tag, void *out, void *in) \ + static bool __maybe_unused func(struct apple_dcp *dcp, int tag, void *out, void *in) \ { \ T_out *typed_out = out; \ callback_##handler cb = handler; \ @@ -90,7 +90,7 @@ typedef void (*dcp_callback_t)(struct apple_dcp *, void *, void *); } #define TRAMPOLINE_OUT(func, handler, T_out) \ - static bool func(struct apple_dcp *dcp, int tag, void *out, void *in) \ + static bool __maybe_unused func(struct apple_dcp *dcp, int tag, void *out, void *in) \ { \ T_out *typed_out = out; \ \ From 687244af6bc7cffbf98c94fbc268b5a21836db7d Mon Sep 17 00:00:00 2001 From: Hector Martin Date: Tue, 11 Apr 2023 21:57:38 +0900 Subject: [PATCH 1078/3902] dcp: Add get_tiling_state Signed-off-by: Hector Martin --- drivers/gpu/drm/apple/iomfb.h | 13 +++++++++++++ drivers/gpu/drm/apple/iomfb_template.c | 12 ++++++++++++ drivers/gpu/drm/apple/iomfb_v13_2.c | 2 ++ 3 files changed, 27 insertions(+) diff --git a/drivers/gpu/drm/apple/iomfb.h b/drivers/gpu/drm/apple/iomfb.h index 7cda9124bcf96b..6602bf19107d43 100644 --- a/drivers/gpu/drm/apple/iomfb.h +++ b/drivers/gpu/drm/apple/iomfb.h @@ -364,4 +364,17 @@ struct iomfb_set_matrix_resp { u32 ret; } __packed; +struct dcpep_get_tiling_state_req { + u32 event; + u32 param; + u32 value; + u8 value_null; + u8 padding[3]; +} __packed; + +struct dcpep_get_tiling_state_resp { + u32 value; + u32 ret; +} __packed; + #endif diff --git a/drivers/gpu/drm/apple/iomfb_template.c b/drivers/gpu/drm/apple/iomfb_template.c index 10286b1ad0f96d..9f8186e4ae6846 100644 --- a/drivers/gpu/drm/apple/iomfb_template.c +++ b/drivers/gpu/drm/apple/iomfb_template.c @@ -977,6 +977,16 @@ dcpep_cb_swap_complete_intent_gated(struct apple_dcp *dcp, info->width, info->height); } +static struct dcpep_get_tiling_state_resp +dcpep_cb_get_tiling_state(struct apple_dcp *dcp, + struct dcpep_get_tiling_state_req *req) +{ + return (struct dcpep_get_tiling_state_resp){ + .value = 0, + .ret = 1, + }; +} + TRAMPOLINE_VOID(trampoline_nop, dcpep_cb_nop); TRAMPOLINE_OUT(trampoline_true, dcpep_cb_true, u8); TRAMPOLINE_OUT(trampoline_false, dcpep_cb_false, u8); @@ -1022,6 +1032,8 @@ TRAMPOLINE_IN(trampoline_enable_backlight_message_ap_gated, iomfbep_cb_enable_backlight_message_ap_gated, u8); TRAMPOLINE_IN(trampoline_pr_publish, iomfb_cb_pr_publish, struct iomfb_property); +TRAMPOLINE_INOUT(trampoline_get_tiling_state, dcpep_cb_get_tiling_state, + struct dcpep_get_tiling_state_req, struct dcpep_get_tiling_state_resp); /* * Callback for swap requests. If a swap failed, we'll never get a swap diff --git a/drivers/gpu/drm/apple/iomfb_v13_2.c b/drivers/gpu/drm/apple/iomfb_v13_2.c index 63ae1e79adda10..356a2aa2433be0 100644 --- a/drivers/gpu/drm/apple/iomfb_v13_2.c +++ b/drivers/gpu/drm/apple/iomfb_v13_2.c @@ -51,6 +51,8 @@ static const iomfb_cb_handler cb_handlers[IOMFB_MAX_CB] = { [110] = trampoline_true, /* create_iomfb_service */ [111] = trampoline_true, /* create_backlight_service */ [112] = trampoline_true, /* create_nvram_servce? */ + [113] = trampoline_get_tiling_state, + [114] = trampoline_false, /* set_tiling_state */ [119] = dcpep_cb_boot_1, [120] = trampoline_false, /* is_dark_boot */ [121] = trampoline_false, /* is_dark_boot / is_waking_from_hibernate*/ From 5f686ae58bde90e7560bfcacb3ffa61b93ebd78f Mon Sep 17 00:00:00 2001 From: Hector Martin Date: Tue, 11 Apr 2023 22:54:28 +0900 Subject: [PATCH 1079/3902] dcp: 42-bit DMA masks Signed-off-by: Hector Martin --- drivers/gpu/drm/apple/apple_drv.c | 2 +- drivers/gpu/drm/apple/dcp.c | 2 +- drivers/gpu/drm/apple/dummy-piodma.c | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/drivers/gpu/drm/apple/apple_drv.c b/drivers/gpu/drm/apple/apple_drv.c index 97a9ca7db6a31f..ed56f433d822e6 100644 --- a/drivers/gpu/drm/apple/apple_drv.c +++ b/drivers/gpu/drm/apple/apple_drv.c @@ -461,7 +461,7 @@ static int apple_drm_init(struct device *dev) resource_size_t fb_size; int ret; - ret = dma_set_mask_and_coherent(dev, DMA_BIT_MASK(36)); + ret = dma_set_mask_and_coherent(dev, DMA_BIT_MASK(42)); if (ret) return ret; diff --git a/drivers/gpu/drm/apple/dcp.c b/drivers/gpu/drm/apple/dcp.c index ec0fab66d67f76..6373358a9dfa5f 100644 --- a/drivers/gpu/drm/apple/dcp.c +++ b/drivers/gpu/drm/apple/dcp.c @@ -419,7 +419,7 @@ static int dcp_comp_bind(struct device *dev, struct device *main, void *data) u32 cpu_ctrl; int ret; - ret = dma_set_mask_and_coherent(dev, DMA_BIT_MASK(36)); + ret = dma_set_mask_and_coherent(dev, DMA_BIT_MASK(42)); if (ret) return ret; diff --git a/drivers/gpu/drm/apple/dummy-piodma.c b/drivers/gpu/drm/apple/dummy-piodma.c index ec5d4fecf356c0..eaa0476854a603 100644 --- a/drivers/gpu/drm/apple/dummy-piodma.c +++ b/drivers/gpu/drm/apple/dummy-piodma.c @@ -26,7 +26,7 @@ static const struct component_ops dcp_piodma_comp_ops = { }; static int dcp_piodma_probe(struct platform_device *pdev) { - int ret = dma_set_mask_and_coherent(&pdev->dev, DMA_BIT_MASK(36)); + int ret = dma_set_mask_and_coherent(&pdev->dev, DMA_BIT_MASK(42)); if (ret) return ret; From 4a5c0a5632081745ddc8461fe72b2573e5d85dde Mon Sep 17 00:00:00 2001 From: Hector Martin Date: Tue, 11 Apr 2023 22:55:25 +0900 Subject: [PATCH 1080/3902] dcp: T602X bwreq support Signed-off-by: Hector Martin --- drivers/gpu/drm/apple/iomfb_template.c | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/drivers/gpu/drm/apple/iomfb_template.c b/drivers/gpu/drm/apple/iomfb_template.c index 9f8186e4ae6846..72b4429fb53b49 100644 --- a/drivers/gpu/drm/apple/iomfb_template.c +++ b/drivers/gpu/drm/apple/iomfb_template.c @@ -35,6 +35,7 @@ /* Register defines used in bandwidth setup structure */ #define REG_SCRATCH (0x14) #define REG_SCRATCH_T600X (0x988) +#define REG_SCRATCH_T602X (0x1208) #define REG_DOORBELL (0x0) #define REG_DOORBELL_BIT (2) @@ -636,7 +637,7 @@ static bool dcpep_cb_boot_1(struct apple_dcp *dcp, int tag, void *out, void *in) static struct dcp_rt_bandwidth dcpep_cb_rt_bandwidth(struct apple_dcp *dcp) { - if (dcp->disp_registers[5] && dcp->disp_registers[6]) + if (dcp->disp_registers[5] && dcp->disp_registers[6]) { return (struct dcp_rt_bandwidth){ .reg_scratch = dcp->disp_registers[5]->start + REG_SCRATCH, @@ -646,19 +647,24 @@ static struct dcp_rt_bandwidth dcpep_cb_rt_bandwidth(struct apple_dcp *dcp) .padding[3] = 0x4, // XXX: required by 11.x firmware }; - else if (dcp->disp_registers[4]) + } else if (dcp->disp_registers[4]) { + u32 offset = REG_SCRATCH_T600X; + if (of_device_is_compatible(dcp->dev->of_node, "apple,t6020-dcp")) + offset = REG_SCRATCH_T602X; + return (struct dcp_rt_bandwidth){ .reg_scratch = dcp->disp_registers[4]->start + - REG_SCRATCH_T600X, + offset, .reg_doorbell = 0, .doorbell_bit = 0, }; - else + } else { return (struct dcp_rt_bandwidth){ .reg_scratch = 0, .reg_doorbell = 0, .doorbell_bit = 0, }; + } } /* Callback to get the current time as milliseconds since the UNIX epoch */ From f6360f1b6f37f987e3d2eceda53861b478540eba Mon Sep 17 00:00:00 2001 From: Hector Martin Date: Tue, 11 Apr 2023 22:55:46 +0900 Subject: [PATCH 1081/3902] dcp: Warn if DMA mapping fails Signed-off-by: Hector Martin --- drivers/gpu/drm/apple/iomfb_template.c | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/drivers/gpu/drm/apple/iomfb_template.c b/drivers/gpu/drm/apple/iomfb_template.c index 72b4429fb53b49..66dad06ea995e2 100644 --- a/drivers/gpu/drm/apple/iomfb_template.c +++ b/drivers/gpu/drm/apple/iomfb_template.c @@ -412,6 +412,7 @@ static struct dcp_map_physical_resp dcpep_cb_map_physical(struct apple_dcp *dcp, struct dcp_map_physical_req *req) { int size = ALIGN(req->size, 4096); + dma_addr_t dva; u32 id; if (!is_disp_register(dcp, req->paddr, req->paddr + size - 1)) { @@ -425,11 +426,13 @@ dcpep_cb_map_physical(struct apple_dcp *dcp, struct dcp_map_physical_req *req) dcp->memdesc[id].size = size; dcp->memdesc[id].reg = req->paddr; + dva = dma_map_resource(dcp->dev, req->paddr, size, DMA_BIDIRECTIONAL, 0); + WARN_ON(dva == DMA_MAPPING_ERROR); + return (struct dcp_map_physical_resp){ .dva_size = size, .mem_desc_id = id, - .dva = dma_map_resource(dcp->dev, req->paddr, size, - DMA_BIDIRECTIONAL, 0), + .dva = dva, }; } From 65e66cfaae4ea5c12593a1a8878067a0c5e563c6 Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Fri, 14 Apr 2023 08:07:52 +0200 Subject: [PATCH 1082/3902] WIP: drm/apple: Port to incompatible V13.3 firmware interface Signed-off-by: Janne Grunau --- drivers/gpu/drm/apple/Makefile | 2 +- drivers/gpu/drm/apple/dcp-internal.h | 6 +-- drivers/gpu/drm/apple/dcp.c | 4 +- drivers/gpu/drm/apple/iomfb.c | 24 ++++----- drivers/gpu/drm/apple/iomfb.h | 14 +++++ drivers/gpu/drm/apple/iomfb_template.c | 10 ++++ drivers/gpu/drm/apple/iomfb_template.h | 1 + drivers/gpu/drm/apple/iomfb_v12_3.c | 2 +- .../apple/{iomfb_v13_2.c => iomfb_v13_3.c} | 52 ++++++++++--------- .../apple/{iomfb_v13_2.h => iomfb_v13_3.h} | 10 ++-- 10 files changed, 76 insertions(+), 49 deletions(-) rename drivers/gpu/drm/apple/{iomfb_v13_2.c => iomfb_v13_3.c} (73%) rename drivers/gpu/drm/apple/{iomfb_v13_2.h => iomfb_v13_3.h} (52%) diff --git a/drivers/gpu/drm/apple/Makefile b/drivers/gpu/drm/apple/Makefile index 3ee61beeb7857b..67e0bae6caf004 100644 --- a/drivers/gpu/drm/apple/Makefile +++ b/drivers/gpu/drm/apple/Makefile @@ -6,7 +6,7 @@ appledrm-y := apple_drv.o apple_dcp-y := dcp.o dcp_backlight.o iomfb.o parser.o apple_dcp-y += iomfb_v12_3.o -apple_dcp-y += iomfb_v13_2.o +apple_dcp-y += iomfb_v13_3.o apple_dcp-$(CONFIG_TRACING) += trace.o apple_piodma-y := dummy-piodma.o diff --git a/drivers/gpu/drm/apple/dcp-internal.h b/drivers/gpu/drm/apple/dcp-internal.h index 59777809a7f370..fee15867b5c0cf 100644 --- a/drivers/gpu/drm/apple/dcp-internal.h +++ b/drivers/gpu/drm/apple/dcp-internal.h @@ -12,7 +12,7 @@ #include "iomfb.h" #include "iomfb_v12_3.h" -#include "iomfb_v13_2.h" +#include "iomfb_v13_3.h" #define DCP_MAX_PLANES 2 @@ -21,7 +21,7 @@ struct apple_dcp; enum dcp_firmware_version { DCP_FIRMWARE_UNKNOWN, DCP_FIRMWARE_V_12_3, - DCP_FIRMWARE_V_13_2, + DCP_FIRMWARE_V_13_3, }; enum { @@ -146,7 +146,7 @@ struct apple_dcp { /* Queued swap. Owned by the DCP to avoid per-swap memory allocation */ union { struct dcp_swap_submit_req_v12_3 v12_3; - struct dcp_swap_submit_req_v13_2 v13_2; + struct dcp_swap_submit_req_v13_3 v13_3; } swap; /* Current display mode */ diff --git a/drivers/gpu/drm/apple/dcp.c b/drivers/gpu/drm/apple/dcp.c index 6373358a9dfa5f..89f8d553d5f9ec 100644 --- a/drivers/gpu/drm/apple/dcp.c +++ b/drivers/gpu/drm/apple/dcp.c @@ -403,8 +403,8 @@ static enum dcp_firmware_version dcp_check_firmware_version(struct device *dev) if (strncmp(compat_str, "12.3.0", sizeof(compat_str)) == 0) return DCP_FIRMWARE_V_12_3; - if (strncmp(compat_str, "13.2.0", sizeof(compat_str)) == 0) - return DCP_FIRMWARE_V_13_2; + if (strncmp(compat_str, "13.3.0", sizeof(compat_str)) == 0) + return DCP_FIRMWARE_V_13_3; dev_err(dev, "DCP firmware-compat %s (FW: %s) is not supported\n", compat_str, fw_str); diff --git a/drivers/gpu/drm/apple/iomfb.c b/drivers/gpu/drm/apple/iomfb.c index 1ce32eae212ddb..335fa804ee24aa 100644 --- a/drivers/gpu/drm/apple/iomfb.c +++ b/drivers/gpu/drm/apple/iomfb.c @@ -225,8 +225,8 @@ void dcp_sleep(struct apple_dcp *dcp) case DCP_FIRMWARE_V_12_3: iomfb_sleep_v12_3(dcp); break; - case DCP_FIRMWARE_V_13_2: - iomfb_sleep_v13_2(dcp); + case DCP_FIRMWARE_V_13_3: + iomfb_sleep_v13_3(dcp); break; default: WARN_ONCE(true, "Unexpected firmware version: %u\n", dcp->fw_compat); @@ -242,8 +242,8 @@ void dcp_poweron(struct platform_device *pdev) case DCP_FIRMWARE_V_12_3: iomfb_poweron_v12_3(dcp); break; - case DCP_FIRMWARE_V_13_2: - iomfb_poweron_v13_2(dcp); + case DCP_FIRMWARE_V_13_3: + iomfb_poweron_v13_3(dcp); break; default: WARN_ONCE(true, "Unexpected firmware version: %u\n", dcp->fw_compat); @@ -260,8 +260,8 @@ void dcp_poweroff(struct platform_device *pdev) case DCP_FIRMWARE_V_12_3: iomfb_poweroff_v12_3(dcp); break; - case DCP_FIRMWARE_V_13_2: - iomfb_poweroff_v13_2(dcp); + case DCP_FIRMWARE_V_13_3: + iomfb_poweroff_v13_3(dcp); break; default: WARN_ONCE(true, "Unexpected firmware version: %u\n", dcp->fw_compat); @@ -505,8 +505,8 @@ void dcp_flush(struct drm_crtc *crtc, struct drm_atomic_state *state) case DCP_FIRMWARE_V_12_3: iomfb_flush_v12_3(dcp, crtc, state); break; - case DCP_FIRMWARE_V_13_2: - iomfb_flush_v13_2(dcp, crtc, state); + case DCP_FIRMWARE_V_13_3: + iomfb_flush_v13_3(dcp, crtc, state); break; default: WARN_ONCE(true, "Unexpected firmware version: %u\n", dcp->fw_compat); @@ -521,8 +521,8 @@ static void iomfb_start(struct apple_dcp *dcp) case DCP_FIRMWARE_V_12_3: iomfb_start_v12_3(dcp); break; - case DCP_FIRMWARE_V_13_2: - iomfb_start_v13_2(dcp); + case DCP_FIRMWARE_V_13_3: + iomfb_start_v13_3(dcp); break; default: WARN_ONCE(true, "Unexpected firmware version: %u\n", dcp->fw_compat); @@ -574,8 +574,8 @@ void iomfb_shutdown(struct apple_dcp *dcp) case DCP_FIRMWARE_V_12_3: iomfb_shutdown_v12_3(dcp); break; - case DCP_FIRMWARE_V_13_2: - iomfb_shutdown_v13_2(dcp); + case DCP_FIRMWARE_V_13_3: + iomfb_shutdown_v13_3(dcp); break; default: WARN_ONCE(true, "Unexpected firmware version: %u\n", dcp->fw_compat); diff --git a/drivers/gpu/drm/apple/iomfb.h b/drivers/gpu/drm/apple/iomfb.h index 6602bf19107d43..e8168338d374ef 100644 --- a/drivers/gpu/drm/apple/iomfb.h +++ b/drivers/gpu/drm/apple/iomfb.h @@ -136,6 +136,20 @@ struct dcp_rt_bandwidth { u32 padding[7]; } __packed; +struct frame_sync_props { + u8 unk[28]; +}; + +struct dcp_set_frame_sync_props_req { + struct frame_sync_props props; + u8 frame_sync_props_null; + u8 padding[3]; +} __packed; + +struct dcp_set_frame_sync_props_resp { + struct frame_sync_props props; +} __packed; + /* Method calls */ enum dcpep_method { diff --git a/drivers/gpu/drm/apple/iomfb_template.c b/drivers/gpu/drm/apple/iomfb_template.c index 66dad06ea995e2..67cf1499a86707 100644 --- a/drivers/gpu/drm/apple/iomfb_template.c +++ b/drivers/gpu/drm/apple/iomfb_template.c @@ -670,6 +670,13 @@ static struct dcp_rt_bandwidth dcpep_cb_rt_bandwidth(struct apple_dcp *dcp) } } +static struct dcp_set_frame_sync_props_resp +dcpep_cb_set_frame_sync_props(struct apple_dcp *dcp, + struct dcp_set_frame_sync_props_req *req) +{ + return (struct dcp_set_frame_sync_props_resp){}; +} + /* Callback to get the current time as milliseconds since the UNIX epoch */ static u64 dcpep_cb_get_time(struct apple_dcp *dcp) { @@ -1031,6 +1038,9 @@ TRAMPOLINE_INOUT(trampoline_prop_end, dcpep_cb_prop_end, struct dcp_set_dcpav_prop_end_req, u8); TRAMPOLINE_OUT(trampoline_rt_bandwidth, dcpep_cb_rt_bandwidth, struct dcp_rt_bandwidth); +TRAMPOLINE_INOUT(trampoline_set_frame_sync_props, dcpep_cb_set_frame_sync_props, + struct dcp_set_frame_sync_props_req, + struct dcp_set_frame_sync_props_resp); TRAMPOLINE_OUT(trampoline_get_frequency, dcpep_cb_get_frequency, u64); TRAMPOLINE_OUT(trampoline_get_time, dcpep_cb_get_time, u64); TRAMPOLINE_IN(trampoline_hotplug, dcpep_cb_hotplug, u64); diff --git a/drivers/gpu/drm/apple/iomfb_template.h b/drivers/gpu/drm/apple/iomfb_template.h index 539ec65e5825f4..e9c249609f46cb 100644 --- a/drivers/gpu/drm/apple/iomfb_template.h +++ b/drivers/gpu/drm/apple/iomfb_template.h @@ -48,6 +48,7 @@ struct DCP_FW_NAME(dcp_swap) { u8 unk_2f3[0x2d]; #if DCP_FW_VER >= DCP_FW_VERSION(13, 2, 0) u8 unk_320[0x13f]; + u64 unk_1; #endif } __packed; diff --git a/drivers/gpu/drm/apple/iomfb_v12_3.c b/drivers/gpu/drm/apple/iomfb_v12_3.c index c226a1139a84c8..8188321004a63f 100644 --- a/drivers/gpu/drm/apple/iomfb_v12_3.c +++ b/drivers/gpu/drm/apple/iomfb_v12_3.c @@ -2,7 +2,7 @@ /* Copyright The Asahi Linux Contributors */ #include "iomfb_v12_3.h" -#include "iomfb_v13_2.h" +#include "iomfb_v13_3.h" #include "version_utils.h" static const struct dcp_method_entry dcp_methods[dcpep_num_methods] = { diff --git a/drivers/gpu/drm/apple/iomfb_v13_2.c b/drivers/gpu/drm/apple/iomfb_v13_3.c similarity index 73% rename from drivers/gpu/drm/apple/iomfb_v13_2.c rename to drivers/gpu/drm/apple/iomfb_v13_3.c index 356a2aa2433be0..18020c6cd39493 100644 --- a/drivers/gpu/drm/apple/iomfb_v13_2.c +++ b/drivers/gpu/drm/apple/iomfb_v13_3.c @@ -2,7 +2,7 @@ /* Copyright The Asahi Linux Contributors */ #include "iomfb_v12_3.h" -#include "iomfb_v13_2.h" +#include "iomfb_v13_3.h" #include "version_utils.h" static const struct dcp_method_entry dcp_methods[dcpep_num_methods] = { @@ -25,13 +25,13 @@ static const struct dcp_method_entry dcp_methods[dcpep_num_methods] = { IOMFB_METHOD("A449", dcpep_enable_disable_video_power_savings), IOMFB_METHOD("A456", dcpep_first_client_open), IOMFB_METHOD("A457", iomfbep_last_client_close), - IOMFB_METHOD("A462", dcpep_set_display_refresh_properties), - IOMFB_METHOD("A465", dcpep_flush_supports_power), - IOMFB_METHOD("A471", dcpep_set_power_state), + IOMFB_METHOD("A463", dcpep_set_display_refresh_properties), + IOMFB_METHOD("A466", dcpep_flush_supports_power), + IOMFB_METHOD("A472", dcpep_set_power_state), }; -#define DCP_FW v13_2 -#define DCP_FW_VER DCP_FW_VERSION(13, 2, 0) +#define DCP_FW v13_3 +#define DCP_FW_VER DCP_FW_VERSION(13, 3, 0) #include "iomfb_template.c" @@ -40,32 +40,34 @@ static const iomfb_cb_handler cb_handlers[IOMFB_MAX_CB] = { [1] = trampoline_true, /* did_power_on_signal */ [2] = trampoline_nop, /* will_power_off_signal */ [3] = trampoline_rt_bandwidth, + [6] = trampoline_set_frame_sync_props, [100] = iomfbep_cb_match_pmu_service, [101] = trampoline_zero, /* get_display_default_stride */ [102] = trampoline_nop, /* set_number_property */ - [103] = trampoline_nop, /* set_boolean_property */ - [106] = trampoline_nop, /* remove_property */ - [107] = trampoline_true, /* create_provider_service */ - [108] = trampoline_true, /* create_product_service */ - [109] = trampoline_true, /* create_pmu_service */ - [110] = trampoline_true, /* create_iomfb_service */ - [111] = trampoline_true, /* create_backlight_service */ - [112] = trampoline_true, /* create_nvram_servce? */ - [113] = trampoline_get_tiling_state, - [114] = trampoline_false, /* set_tiling_state */ - [119] = dcpep_cb_boot_1, - [120] = trampoline_false, /* is_dark_boot */ - [121] = trampoline_false, /* is_dark_boot / is_waking_from_hibernate*/ - [123] = trampoline_read_edt_data, - [125] = trampoline_prop_start, - [126] = trampoline_prop_chunk, - [127] = trampoline_prop_end, + [103] = trampoline_nop, /* trigger_user_cal_loader */ + [104] = trampoline_nop, /* set_boolean_property */ + [107] = trampoline_nop, /* remove_property */ + [108] = trampoline_true, /* create_provider_service */ + [109] = trampoline_true, /* create_product_service */ + [110] = trampoline_true, /* create_pmu_service */ + [111] = trampoline_true, /* create_iomfb_service */ + [112] = trampoline_true, /* create_backlight_service */ + [113] = trampoline_true, /* create_nvram_servce? */ + [114] = trampoline_get_tiling_state, + [115] = trampoline_false, /* set_tiling_state */ + [120] = dcpep_cb_boot_1, + [121] = trampoline_false, /* is_dark_boot */ + [122] = trampoline_false, /* is_dark_boot / is_waking_from_hibernate*/ + [124] = trampoline_read_edt_data, + [126] = trampoline_prop_start, + [127] = trampoline_prop_chunk, + [128] = trampoline_prop_end, [201] = trampoline_map_piodma, [202] = trampoline_unmap_piodma, [206] = iomfbep_cb_match_pmu_service_2, [207] = iomfbep_cb_match_backlight_service, - [208] = trampoline_get_time, - [211] = trampoline_nop, /* update_backlight_factor_prop */ + [208] = trampoline_nop, /* update_backlight_factor_prop */ + [209] = trampoline_get_time, [300] = trampoline_pr_publish, [401] = trampoline_get_uint_prop, [404] = trampoline_nop, /* sr_set_uint_prop */ diff --git a/drivers/gpu/drm/apple/iomfb_v13_2.h b/drivers/gpu/drm/apple/iomfb_v13_3.h similarity index 52% rename from drivers/gpu/drm/apple/iomfb_v13_2.h rename to drivers/gpu/drm/apple/iomfb_v13_3.h index f3810b727235bc..bbb3156b40f893 100644 --- a/drivers/gpu/drm/apple/iomfb_v13_2.h +++ b/drivers/gpu/drm/apple/iomfb_v13_3.h @@ -1,17 +1,17 @@ // SPDX-License-Identifier: GPL-2.0-only OR MIT /* Copyright The Asahi Linux Contributors */ -#ifndef __APPLE_IOMFB_V13_2_H__ -#define __APPLE_IOMFB_V13_2_H__ +#ifndef __APPLE_IOMFB_V13_3_H__ +#define __APPLE_IOMFB_V13_3_H__ #include "version_utils.h" -#define DCP_FW v13_2 -#define DCP_FW_VER DCP_FW_VERSION(13, 2, 0) +#define DCP_FW v13_3 +#define DCP_FW_VER DCP_FW_VERSION(13, 3, 0) #include "iomfb_template.h" #undef DCP_FW_VER #undef DCP_FW -#endif /* __APPLE_IOMFB_V13_2_H__ */ +#endif /* __APPLE_IOMFB_V13_3_H__ */ From 6416a55a0f9a0c1db3cddb5cf2cc49f3ba1b9966 Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Sat, 15 Apr 2023 16:43:39 +0200 Subject: [PATCH 1083/3902] drm/apple: Remove simpledrm framebuffer before DRM device alloc Should result in drm apple to be registered as first DRM device replacing simpledrm. Should resolve problems with userspace assuming that card0 is the main displays device. Signed-off-by: Janne Grunau --- drivers/gpu/drm/apple/apple_drv.c | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/drivers/gpu/drm/apple/apple_drv.c b/drivers/gpu/drm/apple/apple_drv.c index ed56f433d822e6..77e937567ddc3a 100644 --- a/drivers/gpu/drm/apple/apple_drv.c +++ b/drivers/gpu/drm/apple/apple_drv.c @@ -469,6 +469,14 @@ static int apple_drm_init(struct device *dev) if (ret) return ret; + fb_size = fb_r.end - fb_r.start + 1; + ret = aperture_remove_conflicting_devices(fb_r.start, fb_size, + apple_drm_driver.name); + if (ret) { + dev_err(dev, "Failed remove fb: %d\n", ret); + goto err_unbind; + } + apple = devm_drm_dev_alloc(dev, &apple_drm_driver, struct apple_drm_private, drm); if (IS_ERR(apple)) @@ -480,15 +488,6 @@ static int apple_drm_init(struct device *dev) if (ret) return ret; - fb_size = fb_r.end - fb_r.start + 1; - ret = aperture_remove_conflicting_devices(fb_r.start, fb_size, - apple_drm_driver.name); - - if (ret) { - dev_err(dev, "Failed remove fb: %d\n", ret); - goto err_unbind; - } - ret = drmm_mode_config_init(&apple->drm); if (ret) goto err_unbind; From 6d8a79895370da861cd09c0944b50cd998b8b9e0 Mon Sep 17 00:00:00 2001 From: Hector Martin Date: Tue, 25 Apr 2023 01:49:14 +0900 Subject: [PATCH 1084/3902] drm/apple: Mark DCP as being in the wakeup path This prevents the PD from being shut down on suspend, which we need until we support runtime PM properly again. Signed-off-by: Hector Martin --- drivers/gpu/drm/apple/dcp.c | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/drivers/gpu/drm/apple/dcp.c b/drivers/gpu/drm/apple/dcp.c index 89f8d553d5f9ec..9a588954041c78 100644 --- a/drivers/gpu/drm/apple/dcp.c +++ b/drivers/gpu/drm/apple/dcp.c @@ -584,6 +584,26 @@ static void dcp_platform_shutdown(struct platform_device *pdev) component_del(&pdev->dev, &dcp_comp_ops); } +static __maybe_unused int dcp_platform_suspend(struct device *dev) +{ + /* + * Set the device as a wakeup device, which forces its power + * domains to stay on. We need this as we do not support full + * shutdown properly yet. + */ + device_set_wakeup_path(dev); + + return 0; +} + +static __maybe_unused int dcp_platform_resume(struct device *dev) +{ + return 0; +} + +static SIMPLE_DEV_PM_OPS(dcp_platform_pm_ops, + dcp_platform_suspend, dcp_platform_resume); + static const struct of_device_id of_match[] = { { .compatible = "apple,dcp" }, {} @@ -597,6 +617,7 @@ static struct platform_driver apple_platform_driver = { .driver = { .name = "apple-dcp", .of_match_table = of_match, + .pm = pm_sleep_ptr(&dcp_platform_pm_ops), }, }; From 2508effea8cd6c5e50580c46042750eebfd6036a Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Sun, 30 Apr 2023 16:36:33 +0200 Subject: [PATCH 1085/3902] drm: apple: iomfb: Increase modeset timeout to 2.5 seconds Signed-off-by: Janne Grunau --- drivers/gpu/drm/apple/iomfb_template.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/gpu/drm/apple/iomfb_template.c b/drivers/gpu/drm/apple/iomfb_template.c index 67cf1499a86707..e22688d7d9bab9 100644 --- a/drivers/gpu/drm/apple/iomfb_template.c +++ b/drivers/gpu/drm/apple/iomfb_template.c @@ -1276,12 +1276,12 @@ void DCP_FW_NAME(iomfb_flush)(struct apple_dcp *dcp, struct drm_crtc *crtc, stru dev_dbg(dcp->dev, "%s - wait for modeset", __func__); ret = wait_for_completion_timeout(&cookie->done, - msecs_to_jiffies(500)); + msecs_to_jiffies(2500)); kref_put(&cookie->refcount, release_wait_cookie); if (ret == 0) { - dev_dbg(dcp->dev, "set_digital_out_mode 200 ms"); + dev_info(dcp->dev, "set_digital_out_mode timed out"); schedule_work(&dcp->vblank_wq); return; } else if (ret > 0) { From f4ccf4d44dff82b19f5239bd322bdb975cc34e61 Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Sun, 30 Apr 2023 16:01:01 +0200 Subject: [PATCH 1086/3902] drm: apple: Only match backlight service on DCP with panel Signed-off-by: Janne Grunau --- drivers/gpu/drm/apple/dcp-internal.h | 1 + drivers/gpu/drm/apple/dcp.c | 5 +++++ drivers/gpu/drm/apple/iomfb_template.c | 24 +++++++++++++++++++----- drivers/gpu/drm/apple/iomfb_v12_3.c | 2 +- drivers/gpu/drm/apple/iomfb_v13_3.c | 2 +- 5 files changed, 27 insertions(+), 7 deletions(-) diff --git a/drivers/gpu/drm/apple/dcp-internal.h b/drivers/gpu/drm/apple/dcp-internal.h index fee15867b5c0cf..e4857e16616b8c 100644 --- a/drivers/gpu/drm/apple/dcp-internal.h +++ b/drivers/gpu/drm/apple/dcp-internal.h @@ -195,5 +195,6 @@ struct apple_dcp { }; int dcp_backlight_register(struct apple_dcp *dcp); +bool dcp_has_panel(struct apple_dcp *dcp); #endif /* __APPLE_DCP_INTERNAL_H__ */ diff --git a/drivers/gpu/drm/apple/dcp.c b/drivers/gpu/drm/apple/dcp.c index 9a588954041c78..ebe4f1bfb8a916 100644 --- a/drivers/gpu/drm/apple/dcp.c +++ b/drivers/gpu/drm/apple/dcp.c @@ -85,6 +85,11 @@ void dcp_set_dimensions(struct apple_dcp *dcp) } } +bool dcp_has_panel(struct apple_dcp *dcp) +{ + return dcp->panel.width_mm > 0; +} + /* * Helper to send a DRM vblank event. We do not know how call swap_submit_dcp * without surfaces. To avoid timeouts in drm_atomic_helper_wait_for_vblanks diff --git a/drivers/gpu/drm/apple/iomfb_template.c b/drivers/gpu/drm/apple/iomfb_template.c index e22688d7d9bab9..e522da82a01f52 100644 --- a/drivers/gpu/drm/apple/iomfb_template.c +++ b/drivers/gpu/drm/apple/iomfb_template.c @@ -183,6 +183,12 @@ static bool iomfbep_cb_match_backlight_service(struct apple_dcp *dcp, int tag, v { trace_iomfb_callback(dcp, tag, __func__); + if (!dcp_has_panel(dcp)) { + u8 *succ = out; + *succ = true; + return true; + } + iomfb_a132_backlight_service_matched(dcp, false, complete_backlight_service_matched, out); // return false for deferred ACK @@ -194,11 +200,13 @@ static void iomfb_cb_pr_publish(struct apple_dcp *dcp, struct iomfb_property *pr switch (prop->id) { case IOMFB_PROPERTY_NITS: { - dcp->brightness.nits = prop->value / dcp->brightness.scale; - /* notify backlight device of the initial brightness */ - if (!dcp->brightness.bl_dev && dcp->brightness.maximum > 0) - schedule_work(&dcp->bl_register_wq); - trace_iomfb_brightness(dcp, prop->value); + if (dcp_has_panel(dcp)) { + dcp->brightness.nits = prop->value / dcp->brightness.scale; + /* notify backlight device of the initial brightness */ + if (!dcp->brightness.bl_dev && dcp->brightness.maximum > 0) + schedule_work(&dcp->bl_register_wq); + trace_iomfb_brightness(dcp, prop->value); + } break; } default: @@ -1003,6 +1011,11 @@ dcpep_cb_get_tiling_state(struct apple_dcp *dcp, }; } +static u8 dcpep_cb_create_backlight_service(struct apple_dcp *dcp) +{ + return dcp_has_panel(dcp); +} + TRAMPOLINE_VOID(trampoline_nop, dcpep_cb_nop); TRAMPOLINE_OUT(trampoline_true, dcpep_cb_true, u8); TRAMPOLINE_OUT(trampoline_false, dcpep_cb_false, u8); @@ -1053,6 +1066,7 @@ TRAMPOLINE_IN(trampoline_pr_publish, iomfb_cb_pr_publish, struct iomfb_property); TRAMPOLINE_INOUT(trampoline_get_tiling_state, dcpep_cb_get_tiling_state, struct dcpep_get_tiling_state_req, struct dcpep_get_tiling_state_resp); +TRAMPOLINE_OUT(trampoline_create_backlight_service, dcpep_cb_create_backlight_service, u8); /* * Callback for swap requests. If a swap failed, we'll never get a swap diff --git a/drivers/gpu/drm/apple/iomfb_v12_3.c b/drivers/gpu/drm/apple/iomfb_v12_3.c index 8188321004a63f..5bc8bc2f8bd290 100644 --- a/drivers/gpu/drm/apple/iomfb_v12_3.c +++ b/drivers/gpu/drm/apple/iomfb_v12_3.c @@ -49,7 +49,7 @@ static const iomfb_cb_handler cb_handlers[IOMFB_MAX_CB] = { [108] = trampoline_true, /* create_product_service */ [109] = trampoline_true, /* create_pmu_service */ [110] = trampoline_true, /* create_iomfb_service */ - [111] = trampoline_true, /* create_backlight_service */ + [111] = trampoline_create_backlight_service, [116] = dcpep_cb_boot_1, [117] = trampoline_false, /* is_dark_boot */ [118] = trampoline_false, /* is_dark_boot / is_waking_from_hibernate*/ diff --git a/drivers/gpu/drm/apple/iomfb_v13_3.c b/drivers/gpu/drm/apple/iomfb_v13_3.c index 18020c6cd39493..b82ed1f32e0e8e 100644 --- a/drivers/gpu/drm/apple/iomfb_v13_3.c +++ b/drivers/gpu/drm/apple/iomfb_v13_3.c @@ -51,7 +51,7 @@ static const iomfb_cb_handler cb_handlers[IOMFB_MAX_CB] = { [109] = trampoline_true, /* create_product_service */ [110] = trampoline_true, /* create_pmu_service */ [111] = trampoline_true, /* create_iomfb_service */ - [112] = trampoline_true, /* create_backlight_service */ + [112] = trampoline_create_backlight_service, [113] = trampoline_true, /* create_nvram_servce? */ [114] = trampoline_get_tiling_state, [115] = trampoline_false, /* set_tiling_state */ From 1fbcd126f13276136d43301c61ea67c53cea513a Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Sun, 30 Apr 2023 16:34:14 +0200 Subject: [PATCH 1087/3902] drm: apple: iomfb: limit backlight updates to integrated panels Signed-off-by: Janne Grunau --- drivers/gpu/drm/apple/iomfb_template.c | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/drivers/gpu/drm/apple/iomfb_template.c b/drivers/gpu/drm/apple/iomfb_template.c index e522da82a01f52..ec4d13f62822fa 100644 --- a/drivers/gpu/drm/apple/iomfb_template.c +++ b/drivers/gpu/drm/apple/iomfb_template.c @@ -876,9 +876,11 @@ void DCP_FW_NAME(iomfb_poweroff)(struct apple_dcp *dcp) * subsequent update on poweron an actual change and restore the * brightness. */ - swap->swap.bl_unk = 1; - swap->swap.bl_value = 0; - swap->swap.bl_power = 0; + if (dcp_has_panel(dcp)) { + swap->swap.bl_unk = 1; + swap->swap.bl_value = 0; + swap->swap.bl_power = 0; + } for (int l = 0; l < SWAP_SURFACES; l++) swap->surf_null[l] = true; @@ -1324,7 +1326,7 @@ void DCP_FW_NAME(iomfb_flush)(struct apple_dcp *dcp, struct drm_crtc *crtc, stru req->swap.swap_completed = req->swap.swap_enabled; /* update brightness if changed */ - if (dcp->brightness.update) { + if (dcp_has_panel(dcp) && dcp->brightness.update) { req->swap.bl_unk = 1; req->swap.bl_value = dcp->brightness.dac; req->swap.bl_power = 0x40; From eae33e38aec80b10ac8b25750add470120d372f5 Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Sun, 16 Jul 2023 17:51:10 +0200 Subject: [PATCH 1088/3902] drm: apple: backlight: avoid updating the brightness with a commit An atomic_commit for brightness changes will consume a DCP swap without frame buffer updates and will result in a lost frame. After updating the next brightness values wait for 1 frame duration (at 23.976 fps). Check if the brightness update still needs to be send to DVCP or if a swap did that in the meintime. Signed-off-by: Janne Grunau --- drivers/gpu/drm/apple/dcp_backlight.c | 28 ++++++++++++++++++--------- 1 file changed, 19 insertions(+), 9 deletions(-) diff --git a/drivers/gpu/drm/apple/dcp_backlight.c b/drivers/gpu/drm/apple/dcp_backlight.c index d063ecd7ad2068..0eeb3d6d92c5a2 100644 --- a/drivers/gpu/drm/apple/dcp_backlight.c +++ b/drivers/gpu/drm/apple/dcp_backlight.c @@ -136,18 +136,24 @@ static u32 calculate_dac(struct apple_dcp *dcp, int val) return 16 * dac; } -static int drm_crtc_set_brightness(struct drm_crtc *crtc, - struct drm_modeset_acquire_ctx *ctx) +static int drm_crtc_set_brightness(struct apple_dcp *dcp) { struct drm_atomic_state *state; struct drm_crtc_state *crtc_state; + struct drm_modeset_acquire_ctx ctx; + struct drm_crtc *crtc = &dcp->crtc->base; int ret = 0; + DRM_MODESET_LOCK_ALL_BEGIN(crtc->dev, ctx, 0, ret); + + if (!dcp->brightness.update) + goto done; + state = drm_atomic_state_alloc(crtc->dev); if (!state) return -ENOMEM; - state->acquire_ctx = ctx; + state->acquire_ctx = &ctx; crtc_state = drm_atomic_get_crtc_state(state, crtc); if (IS_ERR(crtc_state)) { ret = PTR_ERR(crtc_state); @@ -160,6 +166,9 @@ static int drm_crtc_set_brightness(struct drm_crtc *crtc, fail: drm_atomic_state_put(state); +done: + DRM_MODESET_LOCK_ALL_END(crtc->dev, ctx, ret); + return ret; } @@ -175,6 +184,8 @@ static int dcp_set_brightness(struct backlight_device *bd) dcp->brightness.dac = calculate_dac(dcp, brightness); dcp->brightness.update = true; + DRM_MODESET_LOCK_ALL_END(dcp->crtc->base.dev, ctx, ret); + /* * Do not actively try to change brightness if no mode is set. * TODO: should this be reflected the in backlight's power property? @@ -182,14 +193,13 @@ static int dcp_set_brightness(struct backlight_device *bd) * drm integrated backlight handling */ if (!dcp->valid_mode) - goto out; - - ret = drm_crtc_set_brightness(&dcp->crtc->base, &ctx); + return 0; -out: - DRM_MODESET_LOCK_ALL_END(dcp->crtc->base.dev, ctx, ret); + /* Wait 1 vblank cycle in the hope an atomic swap has already updated + * the brightness */ + msleep((1001 + 23) / 24); // 42ms for 23.976 fps - return ret; + return drm_crtc_set_brightness(dcp); } static const struct backlight_ops dcp_backlight_ops = { From 1136ebea3440750dc996b5089b74b80dafcd34b1 Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Sat, 15 Jul 2023 18:59:10 +0200 Subject: [PATCH 1089/3902] drm/apple: Get rid of the piodma dummy driver It's only needed to configure the display contoller's iommu to share buffers between the DCP co-processor and the display controller. Possible concern is runtime PM for it and its iommu. If we don't set it up the power domain might never go to lower power states even if it could. Signed-off-by: Janne Grunau --- drivers/gpu/drm/apple/Makefile | 2 - drivers/gpu/drm/apple/apple_drv.c | 23 ---------- drivers/gpu/drm/apple/dcp.c | 64 ++++++++++++++++++-------- drivers/gpu/drm/apple/dummy-piodma.c | 68 ---------------------------- 4 files changed, 44 insertions(+), 113 deletions(-) delete mode 100644 drivers/gpu/drm/apple/dummy-piodma.c diff --git a/drivers/gpu/drm/apple/Makefile b/drivers/gpu/drm/apple/Makefile index 67e0bae6caf004..910095e5839c35 100644 --- a/drivers/gpu/drm/apple/Makefile +++ b/drivers/gpu/drm/apple/Makefile @@ -9,11 +9,9 @@ apple_dcp-y += iomfb_v12_3.o apple_dcp-y += iomfb_v13_3.o apple_dcp-$(CONFIG_TRACING) += trace.o -apple_piodma-y := dummy-piodma.o obj-$(CONFIG_DRM_APPLE) += appledrm.o obj-$(CONFIG_DRM_APPLE) += apple_dcp.o -obj-$(CONFIG_DRM_APPLE) += apple_piodma.o # header test diff --git a/drivers/gpu/drm/apple/apple_drv.c b/drivers/gpu/drm/apple/apple_drv.c index 77e937567ddc3a..5233b8fb50d097 100644 --- a/drivers/gpu/drm/apple/apple_drv.c +++ b/drivers/gpu/drm/apple/apple_drv.c @@ -558,26 +558,6 @@ const struct component_master_ops apple_drm_ops = { .unbind = apple_drm_unbind, }; -static const struct of_device_id apple_component_id_tbl[] = { - { .compatible = "apple,dcp-piodma" }, - {}, -}; - -static int add_display_components(struct device *dev, - struct component_match **matchptr) -{ - struct device_node *np; - - for_each_matching_node(np, apple_component_id_tbl) { - if (of_device_is_available(np)) - drm_of_component_match_add(dev, matchptr, - component_compare_of, np); - of_node_put(np); - } - - return 0; -} - static int add_dcp_components(struct device *dev, struct component_match **matchptr) { @@ -602,9 +582,6 @@ static int apple_platform_probe(struct platform_device *pdev) struct component_match *match = NULL; int num_dcp; - /* add PIODMA mapper components */ - add_display_components(mdev, &match); - /* add DCP components, handle less than 1 as probe error */ num_dcp = add_dcp_components(mdev, &match); if (num_dcp < 1) diff --git a/drivers/gpu/drm/apple/dcp.c b/drivers/gpu/drm/apple/dcp.c index ebe4f1bfb8a916..ad5f46762b7774 100644 --- a/drivers/gpu/drm/apple/dcp.c +++ b/drivers/gpu/drm/apple/dcp.c @@ -13,6 +13,7 @@ #include #include #include +#include #include #include #include @@ -316,17 +317,39 @@ static void dcp_work_register_backlight(struct work_struct *work) mutex_unlock(&dcp->bl_register_mutex); } -static struct platform_device *dcp_get_dev(struct device *dev, const char *name) +static int dcp_create_piodma_iommu_dev(struct apple_dcp *dcp) { - struct platform_device *pdev; - struct device_node *node = of_parse_phandle(dev->of_node, name, 0); + int ret; + struct device_node *node = of_get_child_by_name(dcp->dev->of_node, "piodma"); if (!node) - return NULL; + return dev_err_probe(dcp->dev, -ENODEV, + "Failed to get piodma child DT node\n"); + + dcp->piodma = of_platform_device_create(node, NULL, dcp->dev); + if (!dcp->piodma) { + of_node_put(node); + return dev_err_probe(dcp->dev, -ENODEV, "Failed to create piodma pdev for %pOF\n", node); + } + + ret = dma_set_mask_and_coherent(&dcp->piodma->dev, DMA_BIT_MASK(42)); + if (ret) + goto err_destroy_pdev; + + ret = of_dma_configure(&dcp->piodma->dev, node, true); + if (ret) { + ret = dev_err_probe(dcp->dev, ret, + "Failed to configure IOMMU child DMA\n"); + goto err_destroy_pdev; + } + of_node_put(node); - pdev = of_find_device_by_node(node); + return 0; + +err_destroy_pdev: of_node_put(node); - return pdev; + of_platform_device_destroy(&dcp->piodma->dev, NULL); + return ret; } static int dcp_get_disp_regs(struct apple_dcp *dcp) @@ -432,8 +455,6 @@ static int dcp_comp_bind(struct device *dev, struct device *main, void *data) if (IS_ERR(dcp->coproc_reg)) return PTR_ERR(dcp->coproc_reg); - of_platform_default_populate(dev->of_node, NULL, dev); - if (!show_notch) ret = of_property_read_u32(dev->of_node, "apple,notch-height", &dcp->notch_height); @@ -479,16 +500,10 @@ static int dcp_comp_bind(struct device *dev, struct device *main, void *data) else dcp->connector_type = DRM_MODE_CONNECTOR_Unknown; - /* - * Components do not ensure the bind order of sub components but - * the piodma device is only used for its iommu. The iommu is fully - * initialized by the time dcp_piodma_probe() calls component_add(). - */ - dcp->piodma = dcp_get_dev(dev, "apple,piodma-mapper"); - if (!dcp->piodma) { - dev_err(dev, "failed to find piodma\n"); - return -ENODEV; - } + ret = dcp_create_piodma_iommu_dev(dcp); + if (ret) + return dev_err_probe(dev, ret, + "Failed to created PIODMA iommu child device"); ret = dcp_get_disp_regs(dcp); if (ret) { @@ -545,8 +560,10 @@ static void dcp_comp_unbind(struct device *dev, struct device *main, void *data) if (dcp && dcp->shmem) iomfb_shutdown(dcp); - platform_device_put(dcp->piodma); - dcp->piodma = NULL; + if (dcp->piodma) { + of_platform_device_destroy(&dcp->piodma->dev, NULL); + dcp->piodma = NULL; + } devm_clk_put(dev, dcp->clk); dcp->clk = NULL; @@ -562,6 +579,7 @@ static int dcp_platform_probe(struct platform_device *pdev) enum dcp_firmware_version fw_compat; struct device *dev = &pdev->dev; struct apple_dcp *dcp; + int ret; fw_compat = dcp_check_firmware_version(dev); if (fw_compat == DCP_FIRMWARE_UNKNOWN) @@ -576,6 +594,12 @@ static int dcp_platform_probe(struct platform_device *pdev) platform_set_drvdata(pdev, dcp); + ret = devm_of_platform_populate(dev); + if (ret) { + dev_err(dev, "failed to populate child devices: %d\n", ret); + return ret; + } + return component_add(&pdev->dev, &dcp_comp_ops); } diff --git a/drivers/gpu/drm/apple/dummy-piodma.c b/drivers/gpu/drm/apple/dummy-piodma.c deleted file mode 100644 index eaa0476854a603..00000000000000 --- a/drivers/gpu/drm/apple/dummy-piodma.c +++ /dev/null @@ -1,68 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-only OR MIT -/* Copyright 2021 Alyssa Rosenzweig */ - -#include - -#include -#include -#include -#include - -static int dcp_piodma_comp_bind(struct device *dev, struct device *main, - void *data) -{ - return 0; -} - -static void dcp_piodma_comp_unbind(struct device *dev, struct device *main, - void *data) -{ - /* nothing to do */ -} - -static const struct component_ops dcp_piodma_comp_ops = { - .bind = dcp_piodma_comp_bind, - .unbind = dcp_piodma_comp_unbind, -}; -static int dcp_piodma_probe(struct platform_device *pdev) -{ - int ret = dma_set_mask_and_coherent(&pdev->dev, DMA_BIT_MASK(42)); - if (ret) - return ret; - - return component_add(&pdev->dev, &dcp_piodma_comp_ops); -} - -static int dcp_piodma_remove(struct platform_device *pdev) -{ - component_del(&pdev->dev, &dcp_piodma_comp_ops); - - return 0; -} - -static void dcp_piodma_shutdown(struct platform_device *pdev) -{ - component_del(&pdev->dev, &dcp_piodma_comp_ops); -} - -static const struct of_device_id of_match[] = { - { .compatible = "apple,dcp-piodma" }, - {} -}; -MODULE_DEVICE_TABLE(of, of_match); - -static struct platform_driver dcp_piodma_platform_driver = { - .probe = dcp_piodma_probe, - .remove = dcp_piodma_remove, - .shutdown = dcp_piodma_shutdown, - .driver = { - .name = "apple,dcp-piodma", - .of_match_table = of_match, - }, -}; - -drm_module_platform_driver(dcp_piodma_platform_driver); - -MODULE_AUTHOR("Alyssa Rosenzweig "); -MODULE_DESCRIPTION("[HACK] Apple DCP PIODMA shim"); -MODULE_LICENSE("Dual MIT/GPL"); From c2bbd9d97d5001edea75e2b7e98df85c27a84740 Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Wed, 19 Jul 2023 09:22:24 +0200 Subject: [PATCH 1090/3902] drm/apple: Use iommu domain for piodma maps The current use of of dma_get_sgtable/dma_map_sgtable is deemed unsafe. Replace it with an unmanaged iommu domain for the piodma iommu to map the buffers. Signed-off-by: Janne Grunau --- drivers/gpu/drm/apple/dcp-internal.h | 1 + drivers/gpu/drm/apple/dcp.c | 18 +++++++++++- drivers/gpu/drm/apple/iomfb_template.c | 40 +++++++++++++------------- 3 files changed, 38 insertions(+), 21 deletions(-) diff --git a/drivers/gpu/drm/apple/dcp-internal.h b/drivers/gpu/drm/apple/dcp-internal.h index e4857e16616b8c..8baf529f1463c7 100644 --- a/drivers/gpu/drm/apple/dcp-internal.h +++ b/drivers/gpu/drm/apple/dcp-internal.h @@ -97,6 +97,7 @@ struct dcp_panel { struct apple_dcp { struct device *dev; struct platform_device *piodma; + struct iommu_domain *iommu_dom; struct apple_rtkit *rtk; struct apple_crtc *crtc; struct apple_connector *connector; diff --git a/drivers/gpu/drm/apple/dcp.c b/drivers/gpu/drm/apple/dcp.c index ad5f46762b7774..e5a7f98a045a68 100644 --- a/drivers/gpu/drm/apple/dcp.c +++ b/drivers/gpu/drm/apple/dcp.c @@ -344,8 +344,22 @@ static int dcp_create_piodma_iommu_dev(struct apple_dcp *dcp) } of_node_put(node); - return 0; + dcp->iommu_dom = iommu_paging_domain_alloc(&dcp->piodma->dev); + if (IS_ERR(dcp->iommu_dom)) { + ret = PTR_ERR(dcp->iommu_dom); + goto err_destroy_pdev; + } + + ret = iommu_attach_device(dcp->iommu_dom, &dcp->piodma->dev); + if (ret) { + ret = dev_err_probe(dcp->dev, ret, + "Failed to attach IOMMU child domain\n"); + goto err_free_domain; + } + return 0; +err_free_domain: + iommu_domain_free(dcp->iommu_dom); err_destroy_pdev: of_node_put(node); of_platform_device_destroy(&dcp->piodma->dev, NULL); @@ -561,6 +575,8 @@ static void dcp_comp_unbind(struct device *dev, struct device *main, void *data) iomfb_shutdown(dcp); if (dcp->piodma) { + iommu_detach_device(dcp->iommu_dom, &dcp->piodma->dev); + iommu_domain_free(dcp->iommu_dom); of_platform_device_destroy(&dcp->piodma->dev, NULL); dcp->piodma = NULL; } diff --git a/drivers/gpu/drm/apple/iomfb_template.c b/drivers/gpu/drm/apple/iomfb_template.c index ec4d13f62822fa..8a4d28d370bf67 100644 --- a/drivers/gpu/drm/apple/iomfb_template.c +++ b/drivers/gpu/drm/apple/iomfb_template.c @@ -258,32 +258,33 @@ static void iomfbep_cb_set_fx_prop(struct apple_dcp *dcp, struct iomfb_set_fx_pr * Callback to map a buffer allocated with allocate_buf for PIODMA usage. * PIODMA is separate from the main DCP and uses own IOVA space on a dedicated * stream of the display DART, rather than the expected DCP DART. - * - * XXX: This relies on dma_get_sgtable in concert with dma_map_sgtable, which - * is a "fundamentally unsafe" operation according to the docs. And yet - * everyone does it... */ static struct dcp_map_buf_resp dcpep_cb_map_piodma(struct apple_dcp *dcp, struct dcp_map_buf_req *req) { + struct dcp_mem_descriptor *memdesc; struct sg_table *map; - int ret; + ssize_t ret; if (req->buffer >= ARRAY_SIZE(dcp->memdesc)) goto reject; - map = &dcp->memdesc[req->buffer].map; + memdesc = &dcp->memdesc[req->buffer]; + map = &memdesc->map; if (!map->sgl) goto reject; - /* Use PIODMA device instead of DCP to map against the right IOMMU. */ - ret = dma_map_sgtable(&dcp->piodma->dev, map, DMA_BIDIRECTIONAL, 0); + /* use the piodma iommu domain to map against the right IOMMU */ + ret = iommu_map_sgtable(dcp->iommu_dom, memdesc->dva, map, + IOMMU_READ | IOMMU_WRITE); - if (ret) + if (ret != memdesc->size) { + dev_err(dcp->dev, "iommu_map_sgtable() returned %zd instead of expected buffer size of %zu\n", ret, memdesc->size); goto reject; + } - return (struct dcp_map_buf_resp){ .dva = sg_dma_address(map->sgl) }; + return (struct dcp_map_buf_resp){ .dva = memdesc->dva }; reject: dev_err(dcp->dev, "denying map of invalid buffer %llx for piodma\n", @@ -294,8 +295,7 @@ static struct dcp_map_buf_resp dcpep_cb_map_piodma(struct apple_dcp *dcp, static void dcpep_cb_unmap_piodma(struct apple_dcp *dcp, struct dcp_unmap_buf_resp *resp) { - struct sg_table *map; - dma_addr_t dma_addr; + struct dcp_mem_descriptor *memdesc; if (resp->buffer >= ARRAY_SIZE(dcp->memdesc)) { dev_warn(dcp->dev, "unmap request for out of range buffer %llu", @@ -303,24 +303,24 @@ static void dcpep_cb_unmap_piodma(struct apple_dcp *dcp, return; } - map = &dcp->memdesc[resp->buffer].map; + memdesc = &dcp->memdesc[resp->buffer]; - if (!map->sgl) { + if (!memdesc->buf) { dev_warn(dcp->dev, "unmap for non-mapped buffer %llu iova:0x%08llx", resp->buffer, resp->dva); return; } - dma_addr = sg_dma_address(map->sgl); - if (dma_addr != resp->dva) { - dev_warn(dcp->dev, "unmap buffer %llu address mismatch dma_addr:%llx dva:%llx", - resp->buffer, dma_addr, resp->dva); + if (memdesc->dva != resp->dva) { + dev_warn(dcp->dev, "unmap buffer %llu address mismatch " + "memdesc.dva:%llx dva:%llx", resp->buffer, + memdesc->dva, resp->dva); return; } - /* Use PIODMA device instead of DCP to unmap from the right IOMMU. */ - dma_unmap_sgtable(&dcp->piodma->dev, map, DMA_BIDIRECTIONAL, 0); + /* use the piodma iommu domain to unmap from the right IOMMU */ + iommu_unmap(dcp->iommu_dom, memdesc->dva, memdesc->size); } /* From a074d4f8dba9b8c9761a35da999c4a277f91bdc5 Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Thu, 20 Jul 2023 00:36:51 +0200 Subject: [PATCH 1091/3902] drm: apple: Align PIODMA buffers to SZ_16K The iommu scatter table/list mapping can only map full iommu page size extents. Just align the actual the allocation to the iommu page size. This could be handled differently using DARTs subpage protection but there's no easy way to integrate that. Signed-off-by: Janne Grunau --- drivers/gpu/drm/apple/iomfb_template.c | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/drivers/gpu/drm/apple/iomfb_template.c b/drivers/gpu/drm/apple/iomfb_template.c index 8a4d28d370bf67..76ebb2f0d7426b 100644 --- a/drivers/gpu/drm/apple/iomfb_template.c +++ b/drivers/gpu/drm/apple/iomfb_template.c @@ -279,7 +279,10 @@ static struct dcp_map_buf_resp dcpep_cb_map_piodma(struct apple_dcp *dcp, ret = iommu_map_sgtable(dcp->iommu_dom, memdesc->dva, map, IOMMU_READ | IOMMU_WRITE); - if (ret != memdesc->size) { + /* HACK: expect size to be 16K aligned since the iommu API only maps + * full pages + */ + if (ret < 0 || ret != ALIGN(memdesc->size, SZ_16K)) { dev_err(dcp->dev, "iommu_map_sgtable() returned %zd instead of expected buffer size of %zu\n", ret, memdesc->size); goto reject; } @@ -334,6 +337,7 @@ dcpep_cb_allocate_buffer(struct apple_dcp *dcp, { struct dcp_allocate_buffer_resp resp = { 0 }; struct dcp_mem_descriptor *memdesc; + size_t size; u32 id; resp.dva_size = ALIGN(req->size, 4096); @@ -352,11 +356,13 @@ dcpep_cb_allocate_buffer(struct apple_dcp *dcp, memdesc = &dcp->memdesc[id]; memdesc->size = resp.dva_size; - memdesc->buf = dma_alloc_coherent(dcp->dev, memdesc->size, + /* HACK: align size to 16K since the iommu API only maps full pages */ + size = ALIGN(resp.dva_size, SZ_16K); + memdesc->buf = dma_alloc_coherent(dcp->dev, size, &memdesc->dva, GFP_KERNEL); dma_get_sgtable(dcp->dev, &memdesc->map, memdesc->buf, memdesc->dva, - memdesc->size); + size); resp.dva = memdesc->dva; return resp; From 1814eb9d9ce0b9e0cc0793e35f3be40827ea541d Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Wed, 23 Aug 2023 20:50:35 +0200 Subject: [PATCH 1092/3902] drm: apple: Add D129 allocate_bandwidth iomfb callback Used on M2 Ultra During startup. Units are unclear. Signed-off-by: Janne Grunau --- drivers/gpu/drm/apple/iomfb.h | 15 +++++++++++++++ drivers/gpu/drm/apple/iomfb_template.c | 12 ++++++++++++ drivers/gpu/drm/apple/iomfb_v13_3.c | 1 + 3 files changed, 28 insertions(+) diff --git a/drivers/gpu/drm/apple/iomfb.h b/drivers/gpu/drm/apple/iomfb.h index e8168338d374ef..e8d7fef09f9f86 100644 --- a/drivers/gpu/drm/apple/iomfb.h +++ b/drivers/gpu/drm/apple/iomfb.h @@ -127,6 +127,21 @@ struct dcp_component_types { u8 types[7]; } __packed; +struct dcp_allocate_bandwidth_req { + u64 unk1; + u64 unk2; + u64 unk3; + u8 unk1_null; + u8 unk2_null; + u8 padding[8]; +} __packed; + +struct dcp_allocate_bandwidth_resp { + u64 unk1; + u64 unk2; + u32 ret; +} __packed; + struct dcp_rt_bandwidth { u64 unk1; u64 reg_scratch; diff --git a/drivers/gpu/drm/apple/iomfb_template.c b/drivers/gpu/drm/apple/iomfb_template.c index 76ebb2f0d7426b..9c9011981f84ba 100644 --- a/drivers/gpu/drm/apple/iomfb_template.c +++ b/drivers/gpu/drm/apple/iomfb_template.c @@ -652,6 +652,16 @@ static bool dcpep_cb_boot_1(struct apple_dcp *dcp, int tag, void *out, void *in) return false; } +static struct dcp_allocate_bandwidth_resp dcpep_cb_allocate_bandwidth(struct apple_dcp *dcp, + struct dcp_allocate_bandwidth_req *req) +{ + return (struct dcp_allocate_bandwidth_resp){ + .unk1 = req->unk1, + .unk2 = req->unk2, + .ret = 1, + }; +} + static struct dcp_rt_bandwidth dcpep_cb_rt_bandwidth(struct apple_dcp *dcp) { if (dcp->disp_registers[5] && dcp->disp_registers[6]) { @@ -1057,6 +1067,8 @@ TRAMPOLINE_INOUT(trampoline_prop_chunk, dcpep_cb_prop_chunk, struct dcp_set_dcpav_prop_chunk_req, u8); TRAMPOLINE_INOUT(trampoline_prop_end, dcpep_cb_prop_end, struct dcp_set_dcpav_prop_end_req, u8); +TRAMPOLINE_INOUT(trampoline_allocate_bandwidth, dcpep_cb_allocate_bandwidth, + struct dcp_allocate_bandwidth_req, struct dcp_allocate_bandwidth_resp); TRAMPOLINE_OUT(trampoline_rt_bandwidth, dcpep_cb_rt_bandwidth, struct dcp_rt_bandwidth); TRAMPOLINE_INOUT(trampoline_set_frame_sync_props, dcpep_cb_set_frame_sync_props, diff --git a/drivers/gpu/drm/apple/iomfb_v13_3.c b/drivers/gpu/drm/apple/iomfb_v13_3.c index b82ed1f32e0e8e..8e45fca918c320 100644 --- a/drivers/gpu/drm/apple/iomfb_v13_3.c +++ b/drivers/gpu/drm/apple/iomfb_v13_3.c @@ -62,6 +62,7 @@ static const iomfb_cb_handler cb_handlers[IOMFB_MAX_CB] = { [126] = trampoline_prop_start, [127] = trampoline_prop_chunk, [128] = trampoline_prop_end, + [129] = trampoline_allocate_bandwidth, [201] = trampoline_map_piodma, [202] = trampoline_unmap_piodma, [206] = iomfbep_cb_match_pmu_service_2, From 82ed441a9444938ce21bb543104fe1fe7c987997 Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Mon, 4 Sep 2023 23:07:45 +0200 Subject: [PATCH 1093/3902] drm: apple: Update supported firmware versions to 12.3 and 13.5 Removes support for all firmware versions which report as compatible to 13.3 except 13.5. This will be removed after m1n1 reports firmware 13.5 as "apple,firmware-compat" for a while. The files with "v13_3" will be renamed at a later point to avoid conflicts with development trees. Signed-off-by: Janne Grunau --- drivers/gpu/drm/apple/dcp-internal.h | 2 +- drivers/gpu/drm/apple/dcp.c | 14 ++++++++++++-- drivers/gpu/drm/apple/iomfb.c | 12 ++++++------ 3 files changed, 19 insertions(+), 9 deletions(-) diff --git a/drivers/gpu/drm/apple/dcp-internal.h b/drivers/gpu/drm/apple/dcp-internal.h index 8baf529f1463c7..7d54d28913f331 100644 --- a/drivers/gpu/drm/apple/dcp-internal.h +++ b/drivers/gpu/drm/apple/dcp-internal.h @@ -21,7 +21,7 @@ struct apple_dcp; enum dcp_firmware_version { DCP_FIRMWARE_UNKNOWN, DCP_FIRMWARE_V_12_3, - DCP_FIRMWARE_V_13_3, + DCP_FIRMWARE_V_13_5, }; enum { diff --git a/drivers/gpu/drm/apple/dcp.c b/drivers/gpu/drm/apple/dcp.c index e5a7f98a045a68..5274b7100fba6a 100644 --- a/drivers/gpu/drm/apple/dcp.c +++ b/drivers/gpu/drm/apple/dcp.c @@ -445,8 +445,18 @@ static enum dcp_firmware_version dcp_check_firmware_version(struct device *dev) if (strncmp(compat_str, "12.3.0", sizeof(compat_str)) == 0) return DCP_FIRMWARE_V_12_3; - if (strncmp(compat_str, "13.3.0", sizeof(compat_str)) == 0) - return DCP_FIRMWARE_V_13_3; + /* + * m1n1 reports firmware version 13.5 as compatible with 13.3. This is + * only true for the iomfb endpoint. The interface for the dptx-port + * endpoint changed between 13.3 and 13.5. The driver will only support + * firmware 13.5. Check the actual firmware version for compat version + * 13.3 until m1n1 reports 13.5 as "firmware-compat". + */ + else if ((strncmp(compat_str, "13.3.0", sizeof(compat_str)) == 0) && + (strncmp(fw_str, "13.5.0", sizeof(compat_str)) == 0)) + return DCP_FIRMWARE_V_13_5; + else if (strncmp(compat_str, "13.5.0", sizeof(compat_str)) == 0) + return DCP_FIRMWARE_V_13_5; dev_err(dev, "DCP firmware-compat %s (FW: %s) is not supported\n", compat_str, fw_str); diff --git a/drivers/gpu/drm/apple/iomfb.c b/drivers/gpu/drm/apple/iomfb.c index 335fa804ee24aa..6e68d4eda8f491 100644 --- a/drivers/gpu/drm/apple/iomfb.c +++ b/drivers/gpu/drm/apple/iomfb.c @@ -225,7 +225,7 @@ void dcp_sleep(struct apple_dcp *dcp) case DCP_FIRMWARE_V_12_3: iomfb_sleep_v12_3(dcp); break; - case DCP_FIRMWARE_V_13_3: + case DCP_FIRMWARE_V_13_5: iomfb_sleep_v13_3(dcp); break; default: @@ -242,7 +242,7 @@ void dcp_poweron(struct platform_device *pdev) case DCP_FIRMWARE_V_12_3: iomfb_poweron_v12_3(dcp); break; - case DCP_FIRMWARE_V_13_3: + case DCP_FIRMWARE_V_13_5: iomfb_poweron_v13_3(dcp); break; default: @@ -260,7 +260,7 @@ void dcp_poweroff(struct platform_device *pdev) case DCP_FIRMWARE_V_12_3: iomfb_poweroff_v12_3(dcp); break; - case DCP_FIRMWARE_V_13_3: + case DCP_FIRMWARE_V_13_5: iomfb_poweroff_v13_3(dcp); break; default: @@ -505,7 +505,7 @@ void dcp_flush(struct drm_crtc *crtc, struct drm_atomic_state *state) case DCP_FIRMWARE_V_12_3: iomfb_flush_v12_3(dcp, crtc, state); break; - case DCP_FIRMWARE_V_13_3: + case DCP_FIRMWARE_V_13_5: iomfb_flush_v13_3(dcp, crtc, state); break; default: @@ -521,7 +521,7 @@ static void iomfb_start(struct apple_dcp *dcp) case DCP_FIRMWARE_V_12_3: iomfb_start_v12_3(dcp); break; - case DCP_FIRMWARE_V_13_3: + case DCP_FIRMWARE_V_13_5: iomfb_start_v13_3(dcp); break; default: @@ -574,7 +574,7 @@ void iomfb_shutdown(struct apple_dcp *dcp) case DCP_FIRMWARE_V_12_3: iomfb_shutdown_v12_3(dcp); break; - case DCP_FIRMWARE_V_13_3: + case DCP_FIRMWARE_V_13_5: iomfb_shutdown_v13_3(dcp); break; default: From 2ae64acf5f6ae8fe4c39873ce9b85cc3a2a307ce Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Tue, 7 Nov 2023 00:14:55 +0100 Subject: [PATCH 1094/3902] drm: apple: dcp: Port over to DEFINE_SIMPLE_DEV_PM_OPS Avoids ugly "__maybe_unused". Signed-off-by: Janne Grunau --- drivers/gpu/drm/apple/dcp.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/drivers/gpu/drm/apple/dcp.c b/drivers/gpu/drm/apple/dcp.c index 5274b7100fba6a..ce6df4f8a0687d 100644 --- a/drivers/gpu/drm/apple/dcp.c +++ b/drivers/gpu/drm/apple/dcp.c @@ -639,7 +639,7 @@ static void dcp_platform_shutdown(struct platform_device *pdev) component_del(&pdev->dev, &dcp_comp_ops); } -static __maybe_unused int dcp_platform_suspend(struct device *dev) +static int dcp_platform_suspend(struct device *dev) { /* * Set the device as a wakeup device, which forces its power @@ -651,13 +651,13 @@ static __maybe_unused int dcp_platform_suspend(struct device *dev) return 0; } -static __maybe_unused int dcp_platform_resume(struct device *dev) +static int dcp_platform_resume(struct device *dev) { return 0; } -static SIMPLE_DEV_PM_OPS(dcp_platform_pm_ops, - dcp_platform_suspend, dcp_platform_resume); +static DEFINE_SIMPLE_DEV_PM_OPS(dcp_platform_pm_ops, + dcp_platform_suspend, dcp_platform_resume); static const struct of_device_id of_match[] = { { .compatible = "apple,dcp" }, From 12c6db61f8b733031789dd7afd187efc59fda3be Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Tue, 7 Nov 2023 00:30:54 +0100 Subject: [PATCH 1095/3902] drm: apple: dcp: Remove cargo-culted devm_of_platform_populate It does not do anything for dcp and its iommu only child node. Signed-off-by: Janne Grunau --- drivers/gpu/drm/apple/dcp.c | 7 ------- 1 file changed, 7 deletions(-) diff --git a/drivers/gpu/drm/apple/dcp.c b/drivers/gpu/drm/apple/dcp.c index ce6df4f8a0687d..dab37834ab64b6 100644 --- a/drivers/gpu/drm/apple/dcp.c +++ b/drivers/gpu/drm/apple/dcp.c @@ -605,7 +605,6 @@ static int dcp_platform_probe(struct platform_device *pdev) enum dcp_firmware_version fw_compat; struct device *dev = &pdev->dev; struct apple_dcp *dcp; - int ret; fw_compat = dcp_check_firmware_version(dev); if (fw_compat == DCP_FIRMWARE_UNKNOWN) @@ -620,12 +619,6 @@ static int dcp_platform_probe(struct platform_device *pdev) platform_set_drvdata(pdev, dcp); - ret = devm_of_platform_populate(dev); - if (ret) { - dev_err(dev, "failed to populate child devices: %d\n", ret); - return ret; - } - return component_add(&pdev->dev, &dcp_comp_ops); } From aac000003f51bceea5cafeabd44a85076ea37b46 Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Sun, 30 Apr 2023 16:26:20 +0200 Subject: [PATCH 1096/3902] drm: apple: iomfb: implement abort_swaps_dcp To match macOS behavior and in the hope to fix dcpext crashes on t8112. Crashes still occur but let's keep this. Shouldn;t make a difference since we're on the swaps to finish. Signed-off-by: Janne Grunau --- drivers/gpu/drm/apple/iomfb.h | 20 ++++++++++++++++ drivers/gpu/drm/apple/iomfb_template.c | 32 ++++++++++++++++++++++---- drivers/gpu/drm/apple/iomfb_v12_3.c | 1 + drivers/gpu/drm/apple/iomfb_v13_3.c | 1 + 4 files changed, 49 insertions(+), 5 deletions(-) diff --git a/drivers/gpu/drm/apple/iomfb.h b/drivers/gpu/drm/apple/iomfb.h index e8d7fef09f9f86..5d54a59c7ced45 100644 --- a/drivers/gpu/drm/apple/iomfb.h +++ b/drivers/gpu/drm/apple/iomfb.h @@ -189,6 +189,7 @@ enum dcpep_method { iomfbep_a358_vi_set_temperature_hint, iomfbep_get_color_remap_mode, iomfbep_last_client_close, + iomfbep_abort_swaps_dcp, iomfbep_set_matrix, dcpep_num_methods }; @@ -380,6 +381,25 @@ struct iomfb_last_client_close_resp { u32 unkint; } __packed; +struct io_user_client { + u64 addr; + u32 unk; + u8 flag1; + u8 flag2; + u8 pad[2]; +} __packed; + +struct iomfb_abort_swaps_dcp_req { + struct io_user_client client; + u8 client_null; + u8 pad[3]; +} __packed; + +struct iomfb_abort_swaps_dcp_resp { + struct io_user_client client; + u32 ret; +} __packed; + struct iomfb_set_matrix_req { u32 unk_u32; // maybe length? u64 r[3]; diff --git a/drivers/gpu/drm/apple/iomfb_template.c b/drivers/gpu/drm/apple/iomfb_template.c index 9c9011981f84ba..3aedd2fd4a1140 100644 --- a/drivers/gpu/drm/apple/iomfb_template.c +++ b/drivers/gpu/drm/apple/iomfb_template.c @@ -59,6 +59,7 @@ DCP_THUNK_OUT(iomfb_a358_vi_set_temperature_hint, iomfbep_a358_vi_set_temperatur IOMFB_THUNK_INOUT(set_matrix); IOMFB_THUNK_INOUT(get_color_remap_mode); IOMFB_THUNK_INOUT(last_client_close); +IOMFB_THUNK_INOUT(abort_swaps_dcp); DCP_THUNK_INOUT(dcp_swap_submit, dcpep_swap_submit, struct DCP_FW_NAME(dcp_swap_submit_req), @@ -859,10 +860,21 @@ static void last_client_closed_poff(struct apple_dcp *dcp, void *out, void *cook cookie); } +static void aborted_swaps_dcp_poff(struct apple_dcp *dcp, void *out, void *cookie) +{ + struct iomfb_last_client_close_req last_client_req = {}; + iomfb_last_client_close(dcp, false, &last_client_req, + last_client_closed_poff, cookie); +} + void DCP_FW_NAME(iomfb_poweroff)(struct apple_dcp *dcp) { int ret, swap_id; - struct iomfb_last_client_close_req last_client_req = {}; + struct iomfb_abort_swaps_dcp_req abort_req = { + .client = { + .flag2 = 1, + }, + }; struct dcp_swap_cookie *cookie; struct dcp_wait_cookie *poff_cookie; struct dcp_swap_start_req swap_req = { 0 }; @@ -927,8 +939,8 @@ void DCP_FW_NAME(iomfb_poweroff)(struct apple_dcp *dcp) /* increase refcount to ensure the receiver has a reference */ kref_get(&poff_cookie->refcount); - iomfb_last_client_close(dcp, false, &last_client_req, - last_client_closed_poff, poff_cookie); + iomfb_abort_swaps_dcp(dcp, false, &abort_req, + aborted_swaps_dcp_poff, poff_cookie); ret = wait_for_completion_timeout(&poff_cookie->done, msecs_to_jiffies(1000)); @@ -953,10 +965,20 @@ static void last_client_closed_sleep(struct apple_dcp *dcp, void *out, void *coo dcp_set_power_state(dcp, false, &power_req, complete_set_powerstate, cookie); } +static void aborted_swaps_dcp_sleep(struct apple_dcp *dcp, void *out, void *cookie) +{ + struct iomfb_last_client_close_req req = { 0 }; + iomfb_last_client_close(dcp, false, &req, last_client_closed_sleep, cookie); +} + void DCP_FW_NAME(iomfb_sleep)(struct apple_dcp *dcp) { int ret; - struct iomfb_last_client_close_req req = {}; + struct iomfb_abort_swaps_dcp_req req = { + .client = { + .flag2 = 1, + }, + }; struct dcp_wait_cookie *cookie; @@ -968,7 +990,7 @@ void DCP_FW_NAME(iomfb_sleep)(struct apple_dcp *dcp) /* increase refcount to ensure the receiver has a reference */ kref_get(&cookie->refcount); - iomfb_last_client_close(dcp, false, &req, last_client_closed_sleep, + iomfb_abort_swaps_dcp(dcp, false, &req, aborted_swaps_dcp_sleep, cookie); ret = wait_for_completion_timeout(&cookie->done, msecs_to_jiffies(1000)); diff --git a/drivers/gpu/drm/apple/iomfb_v12_3.c b/drivers/gpu/drm/apple/iomfb_v12_3.c index 5bc8bc2f8bd290..abcd1e4aab3ff8 100644 --- a/drivers/gpu/drm/apple/iomfb_v12_3.c +++ b/drivers/gpu/drm/apple/iomfb_v12_3.c @@ -27,6 +27,7 @@ static const struct dcp_method_entry dcp_methods[dcpep_num_methods] = { IOMFB_METHOD("A455", iomfbep_last_client_close), IOMFB_METHOD("A460", dcpep_set_display_refresh_properties), IOMFB_METHOD("A463", dcpep_flush_supports_power), + IOMFB_METHOD("A464", iomfbep_abort_swaps_dcp), IOMFB_METHOD("A468", dcpep_set_power_state), }; diff --git a/drivers/gpu/drm/apple/iomfb_v13_3.c b/drivers/gpu/drm/apple/iomfb_v13_3.c index 8e45fca918c320..9c692ba3c81b92 100644 --- a/drivers/gpu/drm/apple/iomfb_v13_3.c +++ b/drivers/gpu/drm/apple/iomfb_v13_3.c @@ -27,6 +27,7 @@ static const struct dcp_method_entry dcp_methods[dcpep_num_methods] = { IOMFB_METHOD("A457", iomfbep_last_client_close), IOMFB_METHOD("A463", dcpep_set_display_refresh_properties), IOMFB_METHOD("A466", dcpep_flush_supports_power), + IOMFB_METHOD("A467", iomfbep_abort_swaps_dcp), IOMFB_METHOD("A472", dcpep_set_power_state), }; From d43d7e275655901baa5862e3ed58cf01f18b7647 Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Mon, 6 Nov 2023 23:05:41 +0100 Subject: [PATCH 1097/3902] drm: apple: iomfb: Increase modeset tiemout to 8.5 seconds DCP itself uses with the 13.5 firmware a timeout of 8 seconds for modesets. Using a longer timeout prevents overlapping calls to dcp and might improve reliabilty with slower displays. Signed-off-by: Janne Grunau --- drivers/gpu/drm/apple/iomfb_template.c | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/drivers/gpu/drm/apple/iomfb_template.c b/drivers/gpu/drm/apple/iomfb_template.c index 3aedd2fd4a1140..64fe703061fe7d 100644 --- a/drivers/gpu/drm/apple/iomfb_template.c +++ b/drivers/gpu/drm/apple/iomfb_template.c @@ -1330,9 +1330,14 @@ void DCP_FW_NAME(iomfb_flush)(struct apple_dcp *dcp, struct drm_crtc *crtc, stru dcp_set_digital_out_mode(dcp, false, &dcp->mode, complete_set_digital_out_mode, cookie); + /* + * The DCP firmware has an internal timeout of ~8 seconds for + * modesets. Add an extra 500ms to safe side that the modeset + * call has returned. + */ dev_dbg(dcp->dev, "%s - wait for modeset", __func__); ret = wait_for_completion_timeout(&cookie->done, - msecs_to_jiffies(2500)); + msecs_to_jiffies(8500)); kref_put(&cookie->refcount, release_wait_cookie); From 836aebeb217303fd37fed021c508f82f2ce19fd5 Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Mon, 6 Nov 2023 21:13:29 +0100 Subject: [PATCH 1098/3902] drm: apple: Remove explicit asc-dram-mask handling This is no longer necessary after introducing "apple,dma-range" for the dart driver. Signed-off-by: Janne Grunau --- drivers/gpu/drm/apple/dcp-internal.h | 3 --- drivers/gpu/drm/apple/dcp.c | 14 ++------------ drivers/gpu/drm/apple/iomfb.c | 1 - 3 files changed, 2 insertions(+), 16 deletions(-) diff --git a/drivers/gpu/drm/apple/dcp-internal.h b/drivers/gpu/drm/apple/dcp-internal.h index 7d54d28913f331..76c0cf5fae31f4 100644 --- a/drivers/gpu/drm/apple/dcp-internal.h +++ b/drivers/gpu/drm/apple/dcp-internal.h @@ -108,9 +108,6 @@ struct apple_dcp { /* Coprocessor control register */ void __iomem *coproc_reg; - /* mask for DCP IO virtual addresses shared over rtkit */ - u64 asc_dram_mask; - /* DCP has crashed */ bool crashed; diff --git a/drivers/gpu/drm/apple/dcp.c b/drivers/gpu/drm/apple/dcp.c index dab37834ab64b6..93c2ad8a31e39d 100644 --- a/drivers/gpu/drm/apple/dcp.c +++ b/drivers/gpu/drm/apple/dcp.c @@ -145,8 +145,7 @@ static int dcp_rtk_shmem_setup(void *cookie, struct apple_rtkit_shmem *bfr) return -ENOMEM; // TODO: get map from device-tree - phy_addr = iommu_iova_to_phys(domain, - bfr->iova & ~dcp->asc_dram_mask); + phy_addr = iommu_iova_to_phys(domain, bfr->iova); if (!phy_addr) return -ENOMEM; @@ -166,8 +165,6 @@ static int dcp_rtk_shmem_setup(void *cookie, struct apple_rtkit_shmem *bfr) if (!bfr->buffer) return -ENOMEM; - bfr->iova |= dcp->asc_dram_mask; - dev_info(dcp->dev, "shmem_setup: iova: %lx, buffer: %lx", (uintptr_t)bfr->iova, (uintptr_t)bfr->buffer); } @@ -182,8 +179,7 @@ static void dcp_rtk_shmem_destroy(void *cookie, struct apple_rtkit_shmem *bfr) if (bfr->is_mapped) memunmap(bfr->buffer); else - dma_free_coherent(dcp->dev, bfr->size, bfr->buffer, - bfr->iova & ~dcp->asc_dram_mask); + dma_free_coherent(dcp->dev, bfr->size, bfr->buffer, bfr->iova); } static struct apple_rtkit_ops rtkit_ops = { @@ -540,12 +536,6 @@ static int dcp_comp_bind(struct device *dev, struct device *main, void *data) return dev_err_probe(dev, PTR_ERR(dcp->clk), "Unable to find clock\n"); - ret = of_property_read_u64(dev->of_node, "apple,asc-dram-mask", - &dcp->asc_dram_mask); - if (ret) - dev_warn(dev, "failed read 'apple,asc-dram-mask': %d\n", ret); - dev_dbg(dev, "'apple,asc-dram-mask': 0x%011llx\n", dcp->asc_dram_mask); - bitmap_zero(dcp->memdesc_map, DCP_MAX_MAPPINGS); // TDOD: mem_desc IDs start at 1, for simplicity just skip '0' entry set_bit(0, dcp->memdesc_map); diff --git a/drivers/gpu/drm/apple/iomfb.c b/drivers/gpu/drm/apple/iomfb.c index 6e68d4eda8f491..70bfcdf4a96bc8 100644 --- a/drivers/gpu/drm/apple/iomfb.c +++ b/drivers/gpu/drm/apple/iomfb.c @@ -558,7 +558,6 @@ int iomfb_start_rtkit(struct apple_dcp *dcp) dcp->shmem = dma_alloc_coherent(dcp->dev, DCP_SHMEM_SIZE, &shmem_iova, GFP_KERNEL); - shmem_iova |= dcp->asc_dram_mask; dcp_send_message(dcp, IOMFB_ENDPOINT, dcpep_set_shmem(shmem_iova)); return 0; From b1b0af2d2c86096d25080d1e46e9360bcb6e9d8c Mon Sep 17 00:00:00 2001 From: Sven Peter Date: Sat, 5 Nov 2022 13:15:33 +0100 Subject: [PATCH 1099/3902] mux: apple DP xbar: Add Apple silicon DisplayPort crossbar This drivers adds support for the display crossbar used to route display controller streams to the three different modes (DP AltMode, USB4 Tunnel #0/#1) of the Type-C ports. Signed-off-by: Sven Peter --- drivers/mux/Kconfig | 13 ++ drivers/mux/Makefile | 2 + drivers/mux/apple-display-crossbar.c | 305 +++++++++++++++++++++++++++ 3 files changed, 320 insertions(+) create mode 100644 drivers/mux/apple-display-crossbar.c diff --git a/drivers/mux/Kconfig b/drivers/mux/Kconfig index c68132e38138ef..281d3bad07448f 100644 --- a/drivers/mux/Kconfig +++ b/drivers/mux/Kconfig @@ -31,6 +31,19 @@ config MUX_ADGS1408 To compile the driver as a module, choose M here: the module will be called mux-adgs1408. +config MUX_APPLE_DPXBAR + tristate "Apple Silicon Display Crossbar" + depends on ARCH_APPLE + help + Apple Silicon Display Crossbar multiplexer. + + This drivers adds support for the display crossbar used to route + display controller streams to the three different modes + (DP AltMode, USB4 Tunnel #0/#1) of the Type-C ports. + + To compile this driver as a module, chose M here: the module will be + called mux-apple-display-crossbar. + config MUX_GPIO tristate "GPIO-controlled Multiplexer" depends on GPIOLIB || COMPILE_TEST diff --git a/drivers/mux/Makefile b/drivers/mux/Makefile index 6e9fa47daf5663..7b5b3325068010 100644 --- a/drivers/mux/Makefile +++ b/drivers/mux/Makefile @@ -8,9 +8,11 @@ mux-adg792a-objs := adg792a.o mux-adgs1408-objs := adgs1408.o mux-gpio-objs := gpio.o mux-mmio-objs := mmio.o +mux-apple-display-crossbar-objs := apple-display-crossbar.o obj-$(CONFIG_MULTIPLEXER) += mux-core.o obj-$(CONFIG_MUX_ADG792A) += mux-adg792a.o obj-$(CONFIG_MUX_ADGS1408) += mux-adgs1408.o +obj-$(CONFIG_MUX_APPLE_DPXBAR) += mux-apple-display-crossbar.o obj-$(CONFIG_MUX_GPIO) += mux-gpio.o obj-$(CONFIG_MUX_MMIO) += mux-mmio.o diff --git a/drivers/mux/apple-display-crossbar.c b/drivers/mux/apple-display-crossbar.c new file mode 100644 index 00000000000000..a241cba718c842 --- /dev/null +++ b/drivers/mux/apple-display-crossbar.c @@ -0,0 +1,305 @@ +// SPDX-License-Identifier: GPL-2.0-only OR MIT +/* + * Apple Silicon Display Crossbar multiplexer driver + * + * Copyright (C) Asahi Linux Contributors + * + * Author: Sven Peter + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define FIFO_WR_DPTX_CLK_EN 0x000 +#define FIFO_WR_N_CLK_EN 0x004 +#define FIFO_WR_UNK_EN 0x008 +#define FIFO_RD_PCLK1_EN 0x020 +#define FIFO_RD_PCLK2_EN 0x024 +#define FIFO_RD_N_CLK_EN 0x028 +#define FIFO_RD_UNK_EN 0x02c + +#define OUT_PCLK1_EN 0x040 +#define OUT_PCLK2_EN 0x044 +#define OUT_N_CLK_EN 0x048 +#define OUT_UNK_EN 0x04c + +#define CROSSBAR_DISPEXT_EN 0x050 +#define CROSSBAR_MUX_CTRL 0x060 +#define CROSSBAR_MUX_CTRL_DPPHY_SELECT0 GENMASK(23, 20) +#define CROSSBAR_MUX_CTRL_DPIN1_SELECT0 GENMASK(19, 16) +#define CROSSBAR_MUX_CTRL_DPIN0_SELECT0 GENMASK(15, 12) +#define CROSSBAR_MUX_CTRL_DPPHY_SELECT1 GENMASK(11, 8) +#define CROSSBAR_MUX_CTRL_DPIN1_SELECT1 GENMASK(7, 4) +#define CROSSBAR_MUX_CTRL_DPIN0_SELECT1 GENMASK(3, 0) +#define CROSSBAR_ATC_EN 0x070 + +#define FIFO_WR_DPTX_CLK_EN_STAT 0x800 +#define FIFO_WR_N_CLK_EN_STAT 0x804 +#define FIFO_RD_PCLK1_EN_STAT 0x820 +#define FIFO_RD_PCLK2_EN_STAT 0x824 +#define FIFO_RD_N_CLK_EN_STAT 0x828 + +#define OUT_PCLK1_EN_STAT 0x840 +#define OUT_PCLK2_EN_STAT 0x844 +#define OUT_N_CLK_EN_STAT 0x848 + +#define UNK_TUNABLE 0xc00 + +#define ATC_DPIN0 BIT(0) +#define ATC_DPIN1 BIT(4) +#define ATC_DPPHY BIT(8) + +enum { MUX_DPPHY = 0, MUX_DPIN0 = 1, MUX_DPIN1 = 2, MUX_MAX = 3 }; +static const char *apple_dpxbar_names[MUX_MAX] = { "dpphy", "dpin0", "dpin1" }; + +struct apple_dpxbar_hw { + unsigned int n_ufp; + u32 tunable; +}; + +struct apple_dpxbar { + struct device *dev; + void __iomem *regs; + int selected_dispext[MUX_MAX]; + spinlock_t lock; +}; + +static inline void dpxbar_mask32(struct apple_dpxbar *xbar, u32 reg, u32 mask, + u32 set) +{ + u32 value = readl(xbar->regs + reg); + value &= ~mask; + value |= set; + writel(value, xbar->regs + reg); +} + +static inline void dpxbar_set32(struct apple_dpxbar *xbar, u32 reg, u32 set) +{ + dpxbar_mask32(xbar, reg, 0, set); +} + +static inline void dpxbar_clear32(struct apple_dpxbar *xbar, u32 reg, u32 clear) +{ + dpxbar_mask32(xbar, reg, clear, 0); +} + +static int apple_dpxbar_set(struct mux_control *mux, int state) +{ + struct apple_dpxbar *dpxbar = mux_chip_priv(mux->chip); + unsigned int index = mux_control_get_index(mux); + unsigned long flags; + unsigned int mux_state; + unsigned int dispext_bit; + unsigned int atc_bit; + bool enable; + int ret = 0; + u32 mux_mask, mux_set; + + if (state == MUX_IDLE_DISCONNECT) { + /* + * Technically this will select dispext0,0 in the mux control + * register. Practically that doesn't matter since everything + * else is disabled. + */ + mux_state = 0; + enable = false; + } else if (state >= 0 && state < 9) { + dispext_bit = 1 << state; + mux_state = state; + enable = true; + } else { + return -EINVAL; + } + + switch (index) { + case MUX_DPPHY: + mux_mask = CROSSBAR_MUX_CTRL_DPPHY_SELECT0 | + CROSSBAR_MUX_CTRL_DPPHY_SELECT1; + mux_set = + FIELD_PREP(CROSSBAR_MUX_CTRL_DPPHY_SELECT0, mux_state) | + FIELD_PREP(CROSSBAR_MUX_CTRL_DPPHY_SELECT1, mux_state); + atc_bit = ATC_DPPHY; + break; + case MUX_DPIN0: + mux_mask = CROSSBAR_MUX_CTRL_DPIN0_SELECT0 | + CROSSBAR_MUX_CTRL_DPIN0_SELECT1; + mux_set = + FIELD_PREP(CROSSBAR_MUX_CTRL_DPIN0_SELECT0, mux_state) | + FIELD_PREP(CROSSBAR_MUX_CTRL_DPIN0_SELECT1, mux_state); + atc_bit = ATC_DPIN0; + break; + case MUX_DPIN1: + mux_mask = CROSSBAR_MUX_CTRL_DPIN1_SELECT0 | + CROSSBAR_MUX_CTRL_DPIN1_SELECT1; + mux_set = + FIELD_PREP(CROSSBAR_MUX_CTRL_DPIN1_SELECT0, mux_state) | + FIELD_PREP(CROSSBAR_MUX_CTRL_DPIN1_SELECT1, mux_state); + atc_bit = ATC_DPIN1; + break; + default: + return -EINVAL; + } + + spin_lock_irqsave(&dpxbar->lock, flags); + + /* ensure the selected dispext isn't already used in this crossbar */ + if (enable) { + for (int i = 0; i < MUX_MAX; ++i) { + if (i == index) + continue; + if (dpxbar->selected_dispext[i] == state) { + spin_unlock_irqrestore(&dpxbar->lock, flags); + return -EBUSY; + } + } + } + + dpxbar_set32(dpxbar, OUT_N_CLK_EN, atc_bit); + dpxbar_clear32(dpxbar, OUT_UNK_EN, atc_bit); + dpxbar_clear32(dpxbar, OUT_PCLK1_EN, atc_bit); + dpxbar_clear32(dpxbar, CROSSBAR_ATC_EN, atc_bit); + + if (dpxbar->selected_dispext[index] >= 0) { + u32 prev_dispext_bit = 1 << dpxbar->selected_dispext[index]; + + dpxbar_set32(dpxbar, FIFO_WR_N_CLK_EN, prev_dispext_bit); + dpxbar_set32(dpxbar, FIFO_RD_N_CLK_EN, prev_dispext_bit); + dpxbar_clear32(dpxbar, FIFO_WR_UNK_EN, prev_dispext_bit); + dpxbar_clear32(dpxbar, FIFO_RD_UNK_EN, prev_dispext_bit); + dpxbar_clear32(dpxbar, FIFO_WR_DPTX_CLK_EN, prev_dispext_bit); + dpxbar_clear32(dpxbar, FIFO_RD_PCLK1_EN, prev_dispext_bit); + dpxbar_clear32(dpxbar, CROSSBAR_DISPEXT_EN, prev_dispext_bit); + + dpxbar->selected_dispext[index] = -1; + } + + dpxbar_mask32(dpxbar, CROSSBAR_MUX_CTRL, mux_mask, mux_set); + + if (enable) { + dpxbar_clear32(dpxbar, FIFO_WR_N_CLK_EN, dispext_bit); + dpxbar_clear32(dpxbar, FIFO_RD_N_CLK_EN, dispext_bit); + dpxbar_clear32(dpxbar, OUT_N_CLK_EN, atc_bit); + dpxbar_set32(dpxbar, FIFO_WR_UNK_EN, dispext_bit); + dpxbar_set32(dpxbar, FIFO_RD_UNK_EN, dispext_bit); + dpxbar_set32(dpxbar, OUT_UNK_EN, atc_bit); + dpxbar_set32(dpxbar, FIFO_WR_DPTX_CLK_EN, dispext_bit); + dpxbar_set32(dpxbar, FIFO_RD_PCLK1_EN, dispext_bit); + dpxbar_set32(dpxbar, OUT_PCLK1_EN, atc_bit); + dpxbar_set32(dpxbar, CROSSBAR_ATC_EN, atc_bit); + dpxbar_set32(dpxbar, CROSSBAR_DISPEXT_EN, dispext_bit); + + /* + * Work around some HW quirk: + * Without toggling the RD_PCLK enable here the connection + * doesn't come up. Testing has shown that a delay of about + * 5 usec is required which is doubled here to be on the + * safe side. + */ + dpxbar_clear32(dpxbar, FIFO_RD_PCLK1_EN, dispext_bit); + udelay(10); + dpxbar_set32(dpxbar, FIFO_RD_PCLK1_EN, dispext_bit); + + dpxbar->selected_dispext[index] = state; + } + + spin_unlock_irqrestore(&dpxbar->lock, flags); + + if (enable) + dev_info(dpxbar->dev, "Switched %s to dispext%u,%u\n", + apple_dpxbar_names[index], mux_state >> 1, + mux_state & 1); + else + dev_info(dpxbar->dev, "Switched %s to disconnected state\n", + apple_dpxbar_names[index]); + + return ret; +} + +static const struct mux_control_ops apple_dpxbar_ops = { + .set = apple_dpxbar_set, +}; + +static int apple_dpxbar_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct mux_chip *mux_chip; + struct apple_dpxbar *dpxbar; + const struct apple_dpxbar_hw *hw; + int ret; + + hw = of_device_get_match_data(dev); + mux_chip = devm_mux_chip_alloc(dev, MUX_MAX, sizeof(*dpxbar)); + if (IS_ERR(mux_chip)) + return PTR_ERR(mux_chip); + + dpxbar = mux_chip_priv(mux_chip); + mux_chip->ops = &apple_dpxbar_ops; + spin_lock_init(&dpxbar->lock); + + dpxbar->dev = dev; + dpxbar->regs = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(dpxbar->regs)) + return PTR_ERR(dpxbar->regs); + + writel(hw->tunable, dpxbar->regs + UNK_TUNABLE); + + for (unsigned int i = 0; i < MUX_MAX; ++i) { + mux_chip->mux[i].states = hw->n_ufp; + mux_chip->mux[i].idle_state = MUX_IDLE_DISCONNECT; + dpxbar->selected_dispext[i] = -1; + } + + ret = devm_mux_chip_register(dev, mux_chip); + if (ret < 0) + return ret; + + return 0; +} + +const static struct apple_dpxbar_hw apple_dpxbar_hw_t8103 = { + .n_ufp = 2, + .tunable = 0, +}; + +const static struct apple_dpxbar_hw apple_dpxbar_hw_t6000 = { + .n_ufp = 9, + .tunable = 5, +}; + +static const struct of_device_id apple_dpxbar_ids[] = { + { + .compatible = "apple,t8103-display-crossbar", + .data = &apple_dpxbar_hw_t8103, + }, + { + .compatible = "apple,t8112-display-crossbar", + .data = &apple_dpxbar_hw_t8103, + }, + { + .compatible = "apple,t6000-display-crossbar", + .data = &apple_dpxbar_hw_t6000, + }, + {} +}; +MODULE_DEVICE_TABLE(of, apple_dpxbar_ids); + +static struct platform_driver apple_dpxbar_driver = { + .driver = { + .name = "apple-display-crossbar", + .of_match_table = apple_dpxbar_ids, + }, + .probe = apple_dpxbar_probe, +}; +module_platform_driver(apple_dpxbar_driver); + +MODULE_DESCRIPTION("Apple Silicon display crossbar multiplexer driver"); +MODULE_AUTHOR("Sven Peter "); +MODULE_LICENSE("GPL v2"); From ce263fd73143a8747a9b58eb52cb2c3237f0c2ea Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Sun, 30 Apr 2023 15:04:35 +0200 Subject: [PATCH 1100/3902] mux: apple dp crossbar: Support t8112 varient Signed-off-by: Janne Grunau --- drivers/mux/apple-display-crossbar.c | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/drivers/mux/apple-display-crossbar.c b/drivers/mux/apple-display-crossbar.c index a241cba718c842..0801c12949e394 100644 --- a/drivers/mux/apple-display-crossbar.c +++ b/drivers/mux/apple-display-crossbar.c @@ -269,6 +269,11 @@ const static struct apple_dpxbar_hw apple_dpxbar_hw_t8103 = { .tunable = 0, }; +const static struct apple_dpxbar_hw apple_dpxbar_hw_t8112 = { + .n_ufp = 4, + .tunable = 4278196325, +}; + const static struct apple_dpxbar_hw apple_dpxbar_hw_t6000 = { .n_ufp = 9, .tunable = 5, @@ -281,7 +286,7 @@ static const struct of_device_id apple_dpxbar_ids[] = { }, { .compatible = "apple,t8112-display-crossbar", - .data = &apple_dpxbar_hw_t8103, + .data = &apple_dpxbar_hw_t8112, }, { .compatible = "apple,t6000-display-crossbar", From 93372e987dad929701bdd3a434469d802e107e83 Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Sun, 30 Apr 2023 15:05:36 +0200 Subject: [PATCH 1101/3902] mux: apple dp crossbar: FIFO_RD_UNK_EN seems to use 2 bits per dispext* Signed-off-by: Janne Grunau --- drivers/mux/apple-display-crossbar.c | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/drivers/mux/apple-display-crossbar.c b/drivers/mux/apple-display-crossbar.c index 0801c12949e394..8901ad2b1b2d3b 100644 --- a/drivers/mux/apple-display-crossbar.c +++ b/drivers/mux/apple-display-crossbar.c @@ -98,6 +98,7 @@ static int apple_dpxbar_set(struct mux_control *mux, int state) unsigned long flags; unsigned int mux_state; unsigned int dispext_bit; + unsigned int dispext_bit_en; unsigned int atc_bit; bool enable; int ret = 0; @@ -113,6 +114,7 @@ static int apple_dpxbar_set(struct mux_control *mux, int state) enable = false; } else if (state >= 0 && state < 9) { dispext_bit = 1 << state; + dispext_bit_en = 1 << (2 * state); mux_state = state; enable = true; } else { @@ -169,11 +171,12 @@ static int apple_dpxbar_set(struct mux_control *mux, int state) if (dpxbar->selected_dispext[index] >= 0) { u32 prev_dispext_bit = 1 << dpxbar->selected_dispext[index]; + u32 prev_dispext_bit_en = 1 << (2 * dpxbar->selected_dispext[index]); dpxbar_set32(dpxbar, FIFO_WR_N_CLK_EN, prev_dispext_bit); dpxbar_set32(dpxbar, FIFO_RD_N_CLK_EN, prev_dispext_bit); dpxbar_clear32(dpxbar, FIFO_WR_UNK_EN, prev_dispext_bit); - dpxbar_clear32(dpxbar, FIFO_RD_UNK_EN, prev_dispext_bit); + dpxbar_clear32(dpxbar, FIFO_RD_UNK_EN, prev_dispext_bit_en); dpxbar_clear32(dpxbar, FIFO_WR_DPTX_CLK_EN, prev_dispext_bit); dpxbar_clear32(dpxbar, FIFO_RD_PCLK1_EN, prev_dispext_bit); dpxbar_clear32(dpxbar, CROSSBAR_DISPEXT_EN, prev_dispext_bit); @@ -188,7 +191,7 @@ static int apple_dpxbar_set(struct mux_control *mux, int state) dpxbar_clear32(dpxbar, FIFO_RD_N_CLK_EN, dispext_bit); dpxbar_clear32(dpxbar, OUT_N_CLK_EN, atc_bit); dpxbar_set32(dpxbar, FIFO_WR_UNK_EN, dispext_bit); - dpxbar_set32(dpxbar, FIFO_RD_UNK_EN, dispext_bit); + dpxbar_set32(dpxbar, FIFO_RD_UNK_EN, dispext_bit_en); dpxbar_set32(dpxbar, OUT_UNK_EN, atc_bit); dpxbar_set32(dpxbar, FIFO_WR_DPTX_CLK_EN, dispext_bit); dpxbar_set32(dpxbar, FIFO_RD_PCLK1_EN, dispext_bit); From 394012125c24087ddfeb11e82c1855b3d1c0ce08 Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Sun, 30 Apr 2023 15:26:44 +0200 Subject: [PATCH 1102/3902] mux: apple dp crossbar: Read UNK_TUNABLE before and after writing it Makes traces easier to compare with macOS. Signed-off-by: Janne Grunau --- drivers/mux/apple-display-crossbar.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/drivers/mux/apple-display-crossbar.c b/drivers/mux/apple-display-crossbar.c index 8901ad2b1b2d3b..6acd5a87bd7dbd 100644 --- a/drivers/mux/apple-display-crossbar.c +++ b/drivers/mux/apple-display-crossbar.c @@ -252,7 +252,9 @@ static int apple_dpxbar_probe(struct platform_device *pdev) if (IS_ERR(dpxbar->regs)) return PTR_ERR(dpxbar->regs); + readl(dpxbar->regs + UNK_TUNABLE); writel(hw->tunable, dpxbar->regs + UNK_TUNABLE); + readl(dpxbar->regs + UNK_TUNABLE); for (unsigned int i = 0; i < MUX_MAX; ++i) { mux_chip->mux[i].states = hw->n_ufp; From bfd169aba8d2cb1f20c4ed7628ec79e750f2a1c6 Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Thu, 17 Aug 2023 23:00:08 +0200 Subject: [PATCH 1103/3902] mux: apple dp crossbar: Support t602x DP cross bar variant This is a simplified version and probably should live in a separate file. Even the shared registers are quite different. Signed-off-by: Janne Grunau --- drivers/mux/apple-display-crossbar.c | 162 +++++++++++++++++++++++++-- 1 file changed, 155 insertions(+), 7 deletions(-) diff --git a/drivers/mux/apple-display-crossbar.c b/drivers/mux/apple-display-crossbar.c index 6acd5a87bd7dbd..9b17371d92c3ba 100644 --- a/drivers/mux/apple-display-crossbar.c +++ b/drivers/mux/apple-display-crossbar.c @@ -18,6 +18,32 @@ #include #include +/* + * T602x register interface is cleary different so most of the names below are + * probably wrong. + */ + +#define T602X_FIFO_WR_DPTX_CLK_EN 0x000 +#define T602X_FIFO_WR_N_CLK_EN 0x004 +#define T602X_FIFO_WR_UNK_EN 0x008 +#define T602X_REG_00C 0x00c +#define T602X_REG_014 0x014 +#define T602X_REG_018 0x018 +#define T602X_REG_01C 0x01c +#define T602X_FIFO_RD_PCLK2_EN 0x024 +#define T602X_FIFO_RD_N_CLK_EN 0x028 +#define T602X_FIFO_RD_UNK_EN 0x02c +#define T602X_REG_030 0x030 +#define T602X_REG_034 0x034 + +#define T602X_REG_804_STAT 0x804 // status of 0x004 +#define T602X_REG_810_STAT 0x810 // status of 0x014 +#define T602X_REG_81C_STAT 0x81c // status of 0x024 + +/* + * T8013, T600x, T8112 dp crossbar registers. + */ + #define FIFO_WR_DPTX_CLK_EN 0x000 #define FIFO_WR_N_CLK_EN 0x004 #define FIFO_WR_UNK_EN 0x008 @@ -63,6 +89,7 @@ static const char *apple_dpxbar_names[MUX_MAX] = { "dpphy", "dpin0", "dpin1" }; struct apple_dpxbar_hw { unsigned int n_ufp; u32 tunable; + const struct mux_control_ops *ops; }; struct apple_dpxbar { @@ -91,6 +118,109 @@ static inline void dpxbar_clear32(struct apple_dpxbar *xbar, u32 reg, u32 clear) dpxbar_mask32(xbar, reg, clear, 0); } +static int apple_dpxbar_set_t602x(struct mux_control *mux, int state) +{ + struct apple_dpxbar *dpxbar = mux_chip_priv(mux->chip); + unsigned int index = mux_control_get_index(mux); + unsigned long flags; + unsigned int mux_state; + unsigned int dispext_bit; + unsigned int dispext_bit_en; + bool enable; + int ret = 0; + + if (state == MUX_IDLE_DISCONNECT) { + /* + * Technically this will select dispext0,0 in the mux control + * register. Practically that doesn't matter since everything + * else is disabled. + */ + mux_state = 0; + enable = false; + } else if (state >= 0 && state < 9) { + dispext_bit = 1 << state; + dispext_bit_en = 1 << (2 * state); + mux_state = state; + enable = true; + } else { + return -EINVAL; + } + + spin_lock_irqsave(&dpxbar->lock, flags); + + /* ensure the selected dispext isn't already used in this crossbar */ + if (enable) { + for (int i = 0; i < MUX_MAX; ++i) { + if (i == index) + continue; + if (dpxbar->selected_dispext[i] == state) { + spin_unlock_irqrestore(&dpxbar->lock, flags); + return -EBUSY; + } + } + } + + if (dpxbar->selected_dispext[index] >= 0) { + u32 prev_dispext_bit = 1 << dpxbar->selected_dispext[index]; + u32 prev_dispext_bit_en = 1 << (2 * dpxbar->selected_dispext[index]); + + dpxbar_clear32(dpxbar, T602X_FIFO_RD_UNK_EN, prev_dispext_bit); + dpxbar_clear32(dpxbar, T602X_FIFO_WR_DPTX_CLK_EN, prev_dispext_bit); + dpxbar_clear32(dpxbar, T602X_REG_00C, prev_dispext_bit_en); + + dpxbar_clear32(dpxbar, T602X_REG_01C, 0x100); + + dpxbar_clear32(dpxbar, T602X_FIFO_WR_UNK_EN, prev_dispext_bit); + dpxbar_clear32(dpxbar, T602X_REG_018, prev_dispext_bit_en); + + dpxbar_clear32(dpxbar, T602X_FIFO_RD_N_CLK_EN, 0x100); + + dpxbar_set32(dpxbar, T602X_FIFO_WR_N_CLK_EN, prev_dispext_bit); + dpxbar_set32(dpxbar, T602X_REG_014, 0x4); + + dpxbar_set32(dpxbar, FIFO_RD_PCLK1_EN, 0x100); + + dpxbar->selected_dispext[index] = -1; + } + + if (enable) { + dpxbar_set32(dpxbar, T602X_REG_030, state << 20); + dpxbar_set32(dpxbar, T602X_REG_030, state << 8); + udelay(10); + + dpxbar_clear32(dpxbar, T602X_FIFO_WR_N_CLK_EN, dispext_bit); + dpxbar_clear32(dpxbar, T602X_REG_014, 0x4); + + dpxbar_clear32(dpxbar, T602X_FIFO_RD_PCLK2_EN, 0x100); + + dpxbar_set32(dpxbar, T602X_FIFO_WR_UNK_EN, dispext_bit); + dpxbar_set32(dpxbar, T602X_REG_018, dispext_bit_en); + + dpxbar_set32(dpxbar, T602X_FIFO_RD_N_CLK_EN, 0x100); + dpxbar_set32(dpxbar, T602X_FIFO_WR_DPTX_CLK_EN, dispext_bit); + dpxbar_set32(dpxbar, T602X_REG_00C, dispext_bit); + + dpxbar_set32(dpxbar, T602X_REG_01C, 0x100); + dpxbar_set32(dpxbar, T602X_REG_034, 0x100); + + dpxbar_set32(dpxbar, T602X_FIFO_RD_UNK_EN, dispext_bit); + + dpxbar->selected_dispext[index] = state; + } + + spin_unlock_irqrestore(&dpxbar->lock, flags); + + if (enable) + dev_info(dpxbar->dev, "Switched %s to dispext%u,%u\n", + apple_dpxbar_names[index], mux_state >> 1, + mux_state & 1); + else + dev_info(dpxbar->dev, "Switched %s to disconnected state\n", + apple_dpxbar_names[index]); + + return ret; +} + static int apple_dpxbar_set(struct mux_control *mux, int state) { struct apple_dpxbar *dpxbar = mux_chip_priv(mux->chip); @@ -230,6 +360,10 @@ static const struct mux_control_ops apple_dpxbar_ops = { .set = apple_dpxbar_set, }; +static const struct mux_control_ops apple_dpxbar_t602x_ops = { + .set = apple_dpxbar_set_t602x, +}; + static int apple_dpxbar_probe(struct platform_device *pdev) { struct device *dev = &pdev->dev; @@ -244,7 +378,7 @@ static int apple_dpxbar_probe(struct platform_device *pdev) return PTR_ERR(mux_chip); dpxbar = mux_chip_priv(mux_chip); - mux_chip->ops = &apple_dpxbar_ops; + mux_chip->ops = hw->ops; spin_lock_init(&dpxbar->lock); dpxbar->dev = dev; @@ -252,9 +386,11 @@ static int apple_dpxbar_probe(struct platform_device *pdev) if (IS_ERR(dpxbar->regs)) return PTR_ERR(dpxbar->regs); - readl(dpxbar->regs + UNK_TUNABLE); - writel(hw->tunable, dpxbar->regs + UNK_TUNABLE); - readl(dpxbar->regs + UNK_TUNABLE); + if (!of_device_is_compatible(dev->of_node, "apple,t6020-display-crossbar")) { + readl(dpxbar->regs + UNK_TUNABLE); + writel(hw->tunable, dpxbar->regs + UNK_TUNABLE); + readl(dpxbar->regs + UNK_TUNABLE); + } for (unsigned int i = 0; i < MUX_MAX; ++i) { mux_chip->mux[i].states = hw->n_ufp; @@ -269,19 +405,27 @@ static int apple_dpxbar_probe(struct platform_device *pdev) return 0; } -const static struct apple_dpxbar_hw apple_dpxbar_hw_t8103 = { +static const struct apple_dpxbar_hw apple_dpxbar_hw_t8103 = { .n_ufp = 2, .tunable = 0, + .ops = &apple_dpxbar_ops, }; -const static struct apple_dpxbar_hw apple_dpxbar_hw_t8112 = { +static const struct apple_dpxbar_hw apple_dpxbar_hw_t8112 = { .n_ufp = 4, .tunable = 4278196325, + .ops = &apple_dpxbar_ops, }; -const static struct apple_dpxbar_hw apple_dpxbar_hw_t6000 = { +static const struct apple_dpxbar_hw apple_dpxbar_hw_t6000 = { .n_ufp = 9, .tunable = 5, + .ops = &apple_dpxbar_ops, +}; + +static const struct apple_dpxbar_hw apple_dpxbar_hw_t6020 = { + .n_ufp = 9, + .ops = &apple_dpxbar_t602x_ops, }; static const struct of_device_id apple_dpxbar_ids[] = { @@ -297,6 +441,10 @@ static const struct of_device_id apple_dpxbar_ids[] = { .compatible = "apple,t6000-display-crossbar", .data = &apple_dpxbar_hw_t6000, }, + { + .compatible = "apple,t6020-display-crossbar", + .data = &apple_dpxbar_hw_t6020, + }, {} }; MODULE_DEVICE_TABLE(of, apple_dpxbar_ids); From a985038fb58dedc49e1e629df0df4f7f7dfa97c0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20Povi=C5=A1er?= Date: Wed, 15 Feb 2023 16:20:22 +0100 Subject: [PATCH 1104/3902] drm: apple: Add utility functions for matching on dict keys MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Martin Povišer --- drivers/gpu/drm/apple/parser.c | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/drivers/gpu/drm/apple/parser.c b/drivers/gpu/drm/apple/parser.c index 5665c2ba19682f..84a76440c6e7e2 100644 --- a/drivers/gpu/drm/apple/parser.c +++ b/drivers/gpu/drm/apple/parser.c @@ -117,6 +117,39 @@ static int skip(struct dcp_parse_ctx *handle) } } +static int skip_pair(struct dcp_parse_ctx *handle) +{ + int ret; + + ret = skip(handle); + if (ret) + return ret; + + return skip(handle); +} + +static bool consume_string(struct dcp_parse_ctx *ctx, const char *specimen) +{ + struct dcp_parse_tag *tag; + const char *key; + ctx->pos = round_up(ctx->pos, 4); + + if (ctx->pos + sizeof(*tag) + strlen(specimen) - 1 > ctx->len) + return false; + tag = ctx->blob + ctx->pos; + key = ctx->blob + ctx->pos + sizeof(*tag); + if (tag->padding) + return false; + + if (tag->type != DCP_TYPE_STRING || + tag->size != strlen(specimen) || + strncmp(key, specimen, tag->size)) + return false; + + skip(ctx); + return true; +} + /* Caller must free the result */ static char *parse_string(struct dcp_parse_ctx *handle) { From 495643fc31503b08ec7dae4d98a4e3ee4e5f85e9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20Povi=C5=A1er?= Date: Thu, 23 Feb 2023 13:07:49 +0100 Subject: [PATCH 1105/3902] drm: apple: Add 'parse_blob' MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Martin Povišer --- drivers/gpu/drm/apple/parser.c | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/drivers/gpu/drm/apple/parser.c b/drivers/gpu/drm/apple/parser.c index 84a76440c6e7e2..4f16e5505c3367 100644 --- a/drivers/gpu/drm/apple/parser.c +++ b/drivers/gpu/drm/apple/parser.c @@ -199,6 +199,26 @@ static int parse_bool(struct dcp_parse_ctx *handle, bool *b) return 0; } +static int parse_blob(struct dcp_parse_ctx *handle, size_t size, u8 **blob) +{ + struct dcp_parse_tag *tag = parse_tag_of_type(handle, DCP_TYPE_BLOB); + u8 *out; + + if (IS_ERR(tag)) + return PTR_ERR(tag); + + if (tag->size < size) + return -EINVAL; + + out = parse_bytes(handle, tag->size); + + if (IS_ERR(out)) + return PTR_ERR(out); + + *blob = out; + return 0; +} + struct iterator { struct dcp_parse_ctx *handle; u32 idx, len; From 72e389c92d902055dbe18103f0333c95b6fdf97b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20Povi=C5=A1er?= Date: Wed, 15 Feb 2023 16:22:17 +0100 Subject: [PATCH 1106/3902] drm: apple: Add sound mode parsing MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Martin Povišer --- drivers/gpu/drm/apple/dcp-internal.h | 2 + drivers/gpu/drm/apple/parser.c | 306 +++++++++++++++++++++++++++ drivers/gpu/drm/apple/parser.h | 20 ++ drivers/gpu/drm/apple/trace.h | 23 ++ 4 files changed, 351 insertions(+) diff --git a/drivers/gpu/drm/apple/dcp-internal.h b/drivers/gpu/drm/apple/dcp-internal.h index 76c0cf5fae31f4..c04585c3374991 100644 --- a/drivers/gpu/drm/apple/dcp-internal.h +++ b/drivers/gpu/drm/apple/dcp-internal.h @@ -195,4 +195,6 @@ struct apple_dcp { int dcp_backlight_register(struct apple_dcp *dcp); bool dcp_has_panel(struct apple_dcp *dcp); +#define DCP_AUDIO_MAX_CHANS 15 + #endif /* __APPLE_DCP_INTERNAL_H__ */ diff --git a/drivers/gpu/drm/apple/parser.c b/drivers/gpu/drm/apple/parser.c index 4f16e5505c3367..50a4fc31cd06ce 100644 --- a/drivers/gpu/drm/apple/parser.c +++ b/drivers/gpu/drm/apple/parser.c @@ -7,6 +7,8 @@ #include #include +#include // for sound format masks + #include "parser.h" #include "trace.h" @@ -586,3 +588,307 @@ int parse_display_attributes(struct dcp_parse_ctx *handle, int *width_mm, return 0; } + +int parse_sample_rate_bit(struct dcp_parse_ctx *handle, unsigned int *ratebit) +{ + s64 rate; + int ret = parse_int(handle, &rate); + + if (ret) + return ret; + + *ratebit = snd_pcm_rate_to_rate_bit(rate); + if (*ratebit == SNDRV_PCM_RATE_KNOT) { + /* + * The rate wasn't recognized, and unless we supply + * a supplementary constraint, the SNDRV_PCM_RATE_KNOT bit + * will allow any rate. So clear it. + */ + *ratebit = 0; + } + + return 0; +} + +int parse_sample_fmtbit(struct dcp_parse_ctx *handle, u64 *fmtbit) +{ + s64 sample_size; + int ret = parse_int(handle, &sample_size); + + if (ret) + return ret; + + switch (sample_size) { + case 16: + *fmtbit = SNDRV_PCM_FMTBIT_S16; + break; + case 20: + *fmtbit = SNDRV_PCM_FMTBIT_S20; + break; + case 24: + *fmtbit = SNDRV_PCM_FMTBIT_S24; + break; + case 32: + *fmtbit = SNDRV_PCM_FMTBIT_S32; + break; + default: + *fmtbit = 0; + break; + } + + return 0; +} + +static struct { + const char *label; + u8 type; +} chan_position_names[] = { + { "Front Left", SNDRV_CHMAP_FL }, + { "Front Right", SNDRV_CHMAP_FR }, + { "Rear Left", SNDRV_CHMAP_RL }, + { "Rear Right", SNDRV_CHMAP_RR }, + { "Front Center", SNDRV_CHMAP_FC }, + { "Low Frequency Effects", SNDRV_CHMAP_LFE }, + { "Rear Center", SNDRV_CHMAP_RC }, + { "Front Left Center", SNDRV_CHMAP_FLC }, + { "Front Right Center", SNDRV_CHMAP_FRC }, + { "Rear Left Center", SNDRV_CHMAP_RLC }, + { "Rear Right Center", SNDRV_CHMAP_RRC }, + { "Front Left Wide", SNDRV_CHMAP_FLW }, + { "Front Right Wide", SNDRV_CHMAP_FRW }, + { "Front Left High", SNDRV_CHMAP_FLH }, + { "Front Center High", SNDRV_CHMAP_FCH }, + { "Front Right High", SNDRV_CHMAP_FRH }, + { "Top Center", SNDRV_CHMAP_TC }, +}; + +static void append_chmap(struct snd_pcm_chmap_elem *chmap, u8 type) +{ + if (!chmap || chmap->channels >= ARRAY_SIZE(chmap->map)) + return; + + chmap->map[chmap->channels] = type; + chmap->channels++; +} + +static int parse_chmap(struct dcp_parse_ctx *handle, struct snd_pcm_chmap_elem *chmap) +{ + struct iterator it; + int i, ret; + + if (!chmap) { + skip(handle); + return 0; + } + + chmap->channels = 0; + + dcp_parse_foreach_in_array(handle, it) { + for (i = 0; i < ARRAY_SIZE(chan_position_names); i++) + if (consume_string(it.handle, chan_position_names[i].label)) + break; + + if (i == ARRAY_SIZE(chan_position_names)) { + ret = skip(it.handle); + if (ret) + return ret; + + append_chmap(chmap, SNDRV_CHMAP_UNKNOWN); + continue; + } + + append_chmap(chmap, chan_position_names[i].type); + } + + return 0; +} + +static int parse_chan_layout_element(struct dcp_parse_ctx *handle, + unsigned int *nchans_out, + struct snd_pcm_chmap_elem *chmap) +{ + struct iterator it; + int ret; + s64 nchans = 0; + + dcp_parse_foreach_in_dict(handle, it) { + if (consume_string(it.handle, "ActiveChannelCount")) + ret = parse_int(it.handle, &nchans); + else if (consume_string(it.handle, "ChannelLayout")) + ret = parse_chmap(it.handle, chmap); + else + ret = skip_pair(it.handle); + + if (ret) + return ret; + } + + if (nchans_out) + *nchans_out = nchans; + + return 0; +} + +static int parse_nchans_mask(struct dcp_parse_ctx *handle, unsigned int *mask) +{ + struct iterator it; + int ret; + + *mask = 0; + + dcp_parse_foreach_in_array(handle, it) { + int nchans; + + ret = parse_chan_layout_element(it.handle, &nchans, NULL); + if (ret) + return ret; + *mask |= 1 << nchans; + } + + return 0; +} + +static int parse_avep_element(struct dcp_parse_ctx *handle, + struct dcp_sound_format_mask *sieve, + struct dcp_sound_format_mask *hits) +{ + struct dcp_sound_format_mask mask = {0, 0, 0}; + struct iterator it; + int ret; + + dcp_parse_foreach_in_dict(handle, it) { + if (consume_string(handle, "StreamSampleRate")) + ret = parse_sample_rate_bit(it.handle, &mask.rates); + else if (consume_string(handle, "SampleSize")) + ret = parse_sample_fmtbit(it.handle, &mask.formats); + else if (consume_string(handle, "AudioChannelLayoutElements")) + ret = parse_nchans_mask(it.handle, &mask.nchans); + else + ret = skip_pair(it.handle); + + if (ret) + return ret; + } + + trace_avep_sound_mode(handle->dcp, mask.rates, mask.formats, mask.nchans); + + if (!(mask.rates & sieve->rates) || !(mask.formats & sieve->formats) || + !(mask.nchans & sieve->nchans)) + return 0; + + if (hits) { + hits->rates |= mask.rates; + hits->formats |= mask.formats; + hits->nchans |= mask.nchans; + } + + return 1; +} + +static int parse_mode_in_avep_element(struct dcp_parse_ctx *handle, + unsigned int selected_nchans, + struct snd_pcm_chmap_elem *chmap, + struct dcp_sound_cookie *cookie) +{ + struct iterator it; + struct dcp_parse_ctx save_handle; + int ret; + + dcp_parse_foreach_in_dict(handle, it) { + if (consume_string(it.handle, "AudioChannelLayoutElements")) { + struct iterator inner_it; + int nchans; + + dcp_parse_foreach_in_array(it.handle, inner_it) { + save_handle = *it.handle; + ret = parse_chan_layout_element(inner_it.handle, + &nchans, NULL); + if (ret) + return ret; + + if (nchans != selected_nchans) + continue; + + /* + * Now that we know this layout matches the + * selected channel number, reread the element + * and fill in the channel map. + */ + *inner_it.handle = save_handle; + ret = parse_chan_layout_element(inner_it.handle, + NULL, chmap); + if (ret) + return ret; + } + } else if (consume_string(it.handle, "ElementData")) { + u8 *blob; + + ret = parse_blob(it.handle, sizeof(*cookie), &blob); + if (ret) + return ret; + + if (cookie) + memcpy(cookie, blob, sizeof(*cookie)); + } else { + ret = skip_pair(it.handle); + if (ret) + return ret; + } + } + + return 0; +} + +int parse_sound_constraints(struct dcp_parse_ctx *handle, + struct dcp_sound_format_mask *sieve, + struct dcp_sound_format_mask *hits) +{ + int ret; + struct iterator it; + + if (hits) { + hits->rates = 0; + hits->formats = 0; + hits->nchans = 0; + } + + dcp_parse_foreach_in_array(handle, it) { + ret = parse_avep_element(it.handle, sieve, hits); + + if (ret < 0) + return ret; + } + + return 0; +} +EXPORT_SYMBOL_GPL(parse_sound_constraints); + +int parse_sound_mode(struct dcp_parse_ctx *handle, + struct dcp_sound_format_mask *sieve, + struct snd_pcm_chmap_elem *chmap, + struct dcp_sound_cookie *cookie) +{ + struct dcp_parse_ctx save_handle; + struct iterator it; + int ret; + + dcp_parse_foreach_in_array(handle, it) { + save_handle = *it.handle; + ret = parse_avep_element(it.handle, sieve, NULL); + + if (!ret) + continue; + + if (ret < 0) + return ret; + + ret = parse_mode_in_avep_element(&save_handle, __ffs(sieve->nchans), + chmap, cookie); + if (ret < 0) + return ret; + return 1; + } + + return 0; +} +EXPORT_SYMBOL_GPL(parse_sound_mode); diff --git a/drivers/gpu/drm/apple/parser.h b/drivers/gpu/drm/apple/parser.h index 92fe9473d56718..030e3a33b4877d 100644 --- a/drivers/gpu/drm/apple/parser.h +++ b/drivers/gpu/drm/apple/parser.h @@ -32,4 +32,24 @@ struct dcp_display_mode *enumerate_modes(struct dcp_parse_ctx *handle, int parse_display_attributes(struct dcp_parse_ctx *handle, int *width_mm, int *height_mm); + +struct dcp_sound_format_mask { + u64 formats; /* SNDRV_PCM_FMTBIT_* */ + unsigned int rates; /* SNDRV_PCM_RATE_* */ + unsigned int nchans; +}; + +struct dcp_sound_cookie { + u8 data[24]; +}; + +struct snd_pcm_chmap_elem; +int parse_sound_constraints(struct dcp_parse_ctx *handle, + struct dcp_sound_format_mask *sieve, + struct dcp_sound_format_mask *hits); +int parse_sound_mode(struct dcp_parse_ctx *handle, + struct dcp_sound_format_mask *sieve, + struct snd_pcm_chmap_elem *chmap, + struct dcp_sound_cookie *cookie); + #endif diff --git a/drivers/gpu/drm/apple/trace.h b/drivers/gpu/drm/apple/trace.h index 127bda420592a0..c482b66ffca132 100644 --- a/drivers/gpu/drm/apple/trace.h +++ b/drivers/gpu/drm/apple/trace.h @@ -291,6 +291,29 @@ TRACE_EVENT(iomfb_timing_mode, ) ); +TRACE_EVENT(avep_sound_mode, + TP_PROTO(struct apple_dcp *dcp, u32 rates, u64 formats, unsigned int nchans), + TP_ARGS(dcp, rates, formats, nchans), + TP_STRUCT__entry( + __field(u64, dcp) + __field(u32, rates) + __field(u64, formats) + __field(unsigned int, nchans) + ), + TP_fast_assign( + __entry->dcp = (u64)dcp; + __entry->rates = rates; + __entry->formats = formats; + __entry->nchans = nchans; + ), + TP_printk("dcp=%llx, rates=%#x, formats=%#llx, nchans=%#x", + __entry->dcp, + __entry->rates, + __entry->formats, + __entry->nchans + ) +); + #endif /* _TRACE_DCP_H */ /* This part must be outside protection */ From 14fa2476266cc4b1eedcf8c4f87b9299809a8a0f Mon Sep 17 00:00:00 2001 From: Sven Peter Date: Sun, 12 Feb 2023 15:51:58 +0100 Subject: [PATCH 1107/3902] drm: apple: DCP AFK/EPIC support MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Sven Peter Co-developed-by: Martin Povišer Signed-off-by: Martin Povišer Co-developed-by: Janne Grunau Signed-off-by: Janne Grunau --- drivers/gpu/drm/apple/Makefile | 2 +- drivers/gpu/drm/apple/afk.c | 950 +++++++++++++++++++++++++++ drivers/gpu/drm/apple/afk.h | 187 ++++++ drivers/gpu/drm/apple/dcp-internal.h | 1 + drivers/gpu/drm/apple/dcp.c | 1 + drivers/gpu/drm/apple/parser.c | 62 ++ drivers/gpu/drm/apple/parser.h | 3 +- drivers/gpu/drm/apple/trace.h | 110 ++++ 8 files changed, 1314 insertions(+), 2 deletions(-) create mode 100644 drivers/gpu/drm/apple/afk.c create mode 100644 drivers/gpu/drm/apple/afk.h diff --git a/drivers/gpu/drm/apple/Makefile b/drivers/gpu/drm/apple/Makefile index 910095e5839c35..2cc9cfa9fb9ef9 100644 --- a/drivers/gpu/drm/apple/Makefile +++ b/drivers/gpu/drm/apple/Makefile @@ -4,7 +4,7 @@ CFLAGS_trace.o = -I$(src) appledrm-y := apple_drv.o -apple_dcp-y := dcp.o dcp_backlight.o iomfb.o parser.o +apple_dcp-y := afk.o dcp.o dcp_backlight.o iomfb.o parser.o apple_dcp-y += iomfb_v12_3.o apple_dcp-y += iomfb_v13_3.o apple_dcp-$(CONFIG_TRACING) += trace.o diff --git a/drivers/gpu/drm/apple/afk.c b/drivers/gpu/drm/apple/afk.c new file mode 100644 index 00000000000000..9f2f0b646ac6e0 --- /dev/null +++ b/drivers/gpu/drm/apple/afk.c @@ -0,0 +1,950 @@ +// SPDX-License-Identifier: GPL-2.0-only OR MIT +/* Copyright 2022 Sven Peter */ + +#include +#include +#include +#include +#include +#include + +#include "afk.h" +#include "trace.h" + +struct afk_receive_message_work { + struct apple_dcp_afkep *ep; + u64 message; + struct work_struct work; +}; + +#define RBEP_TYPE GENMASK(63, 48) + +enum rbep_msg_type { + RBEP_INIT = 0x80, + RBEP_INIT_ACK = 0xa0, + RBEP_GETBUF = 0x89, + RBEP_GETBUF_ACK = 0xa1, + RBEP_INIT_TX = 0x8a, + RBEP_INIT_RX = 0x8b, + RBEP_START = 0xa3, + RBEP_START_ACK = 0x86, + RBEP_SEND = 0xa2, + RBEP_RECV = 0x85, + RBEP_SHUTDOWN = 0xc0, + RBEP_SHUTDOWN_ACK = 0xc1, +}; + +#define BLOCK_SHIFT 6 + +#define GETBUF_SIZE GENMASK(31, 16) +#define GETBUF_TAG GENMASK(15, 0) +#define GETBUF_ACK_DVA GENMASK(47, 0) + +#define INITRB_OFFSET GENMASK(47, 32) +#define INITRB_SIZE GENMASK(31, 16) +#define INITRB_TAG GENMASK(15, 0) + +#define SEND_WPTR GENMASK(31, 0) + +static void afk_send(struct apple_dcp_afkep *ep, u64 message) +{ + dcp_send_message(ep->dcp, ep->endpoint, message); +} + +struct apple_dcp_afkep *afk_init(struct apple_dcp *dcp, u32 endpoint, + const struct apple_epic_service_ops *ops) +{ + struct apple_dcp_afkep *afkep; + int ret; + + afkep = devm_kzalloc(dcp->dev, sizeof(*afkep), GFP_KERNEL); + if (!afkep) + return ERR_PTR(-ENOMEM); + + afkep->ops = ops; + afkep->dcp = dcp; + afkep->endpoint = endpoint; + afkep->wq = alloc_ordered_workqueue("apple-dcp-afkep%02x", + WQ_MEM_RECLAIM, endpoint); + if (!afkep->wq) { + ret = -ENOMEM; + goto out_free_afkep; + } + + // TODO: devm_ for wq + + init_completion(&afkep->started); + init_completion(&afkep->stopped); + spin_lock_init(&afkep->lock); + + return afkep; + +out_free_afkep: + devm_kfree(dcp->dev, afkep); + return ERR_PTR(ret); +} + +int afk_start(struct apple_dcp_afkep *ep) +{ + int ret; + + reinit_completion(&ep->started); + apple_rtkit_start_ep(ep->dcp->rtk, ep->endpoint); + afk_send(ep, FIELD_PREP(RBEP_TYPE, RBEP_INIT)); + + ret = wait_for_completion_timeout(&ep->started, msecs_to_jiffies(1000)); + if (ret <= 0) + return -ETIMEDOUT; + else + return 0; +} + +static void afk_getbuf(struct apple_dcp_afkep *ep, u64 message) +{ + u16 size = FIELD_GET(GETBUF_SIZE, message) << BLOCK_SHIFT; + u16 tag = FIELD_GET(GETBUF_TAG, message); + u64 reply; + + trace_afk_getbuf(ep, size, tag); + + if (ep->bfr) { + dev_err(ep->dcp->dev, + "Got GETBUF message but buffer already exists\n"); + return; + } + + ep->bfr = dmam_alloc_coherent(ep->dcp->dev, size, &ep->bfr_dma, + GFP_KERNEL); + if (!ep->bfr) { + dev_err(ep->dcp->dev, "Failed to allocate %d bytes buffer\n", + size); + return; + } + + ep->bfr_size = size; + ep->bfr_tag = tag; + + reply = FIELD_PREP(RBEP_TYPE, RBEP_GETBUF_ACK); + reply |= FIELD_PREP(GETBUF_ACK_DVA, ep->bfr_dma); + afk_send(ep, reply); +} + +static void afk_init_rxtx(struct apple_dcp_afkep *ep, u64 message, + struct afk_ringbuffer *bfr) +{ + u16 base = FIELD_GET(INITRB_OFFSET, message) << BLOCK_SHIFT; + u16 size = FIELD_GET(INITRB_SIZE, message) << BLOCK_SHIFT; + u16 tag = FIELD_GET(INITRB_TAG, message); + u32 bufsz, end; + + if (tag != ep->bfr_tag) { + dev_err(ep->dcp->dev, "AFK[ep:%02x]: expected tag 0x%x but got 0x%x", + ep->endpoint, ep->bfr_tag, tag); + return; + } + + if (bfr->ready) { + dev_err(ep->dcp->dev, "AFK[ep:%02x]: buffer is already initialized\n", + ep->endpoint); + return; + } + + if (base >= ep->bfr_size) { + dev_err(ep->dcp->dev, + "AFK[ep:%02x]: requested base 0x%x >= max size 0x%lx", + ep->endpoint, base, ep->bfr_size); + return; + } + + end = base + size; + if (end > ep->bfr_size) { + dev_err(ep->dcp->dev, + "AFK[ep:%02x]: requested end 0x%x > max size 0x%lx", + ep->endpoint, end, ep->bfr_size); + return; + } + + bfr->hdr = ep->bfr + base; + bufsz = le32_to_cpu(bfr->hdr->bufsz); + if (bufsz + sizeof(*bfr->hdr) != size) { + dev_err(ep->dcp->dev, + "AFK[ep:%02x]: ring buffer size 0x%x != expected 0x%lx", + ep->endpoint, bufsz, sizeof(*bfr->hdr)); + return; + } + + bfr->buf = bfr->hdr + 1; + bfr->bufsz = bufsz; + bfr->ready = true; + + if (ep->rxbfr.ready && ep->txbfr.ready) + afk_send(ep, FIELD_PREP(RBEP_TYPE, RBEP_START)); +} + +static const struct apple_epic_service_ops * +afk_match_service(struct apple_dcp_afkep *ep, const char *name) +{ + const struct apple_epic_service_ops *ops; + + if (!name[0]) + return NULL; + if (!ep->ops) + return NULL; + + for (ops = ep->ops; ops->name[0]; ops++) { + if (strcmp(ops->name, name)) + continue; + + return ops; + } + + return NULL; +} + +static void afk_recv_handle_init(struct apple_dcp_afkep *ep, u32 channel, + u8 *payload, size_t payload_size) +{ + char name[32]; + s64 epic_unit = -1; + const char *service_name = name; + const char *epic_name = NULL, *epic_class = NULL; + const struct apple_epic_service_ops *ops; + struct dcp_parse_ctx ctx; + u8 *props = payload + sizeof(name); + size_t props_size = payload_size - sizeof(name); + + WARN_ON(ep->services[channel].enabled); + + if (payload_size < sizeof(name)) { + dev_err(ep->dcp->dev, "AFK[ep:%02x]: payload too small: %lx\n", + ep->endpoint, payload_size); + return; + } + + strlcpy(name, payload, sizeof(name)); + + /* + * in DCP firmware 13.2 DCP reports interface-name as name which starts + * with "dispext%d" using -1 s ID for "dcp". In the 12.3 firmware + * EPICProviderClass was used. If the init call has props parse them and + * use EPICProviderClass to match the service. + */ + if (props_size > 36) { + int ret = parse(props, props_size, &ctx); + if (ret) { + dev_err(ep->dcp->dev, + "AFK[ep:%02x]: Failed to parse service init props for %s\n", + ep->endpoint, name); + return; + } + ret = parse_epic_service_init(&ctx, &epic_name, &epic_class, &epic_unit); + if (ret) { + dev_err(ep->dcp->dev, + "AFK[ep:%02x]: failed to extract init props: %d\n", + ep->endpoint, ret); + return; + } + service_name = epic_class; + } else { + service_name = name; + } + + ops = afk_match_service(ep, service_name); + if (!ops) { + dev_err(ep->dcp->dev, + "AFK[ep:%02x]: unable to match service %s on channel %d\n", + ep->endpoint, service_name, channel); + goto free; + } + + spin_lock_init(&ep->services[channel].lock); + ep->services[channel].enabled = true; + ep->services[channel].ops = ops; + ep->services[channel].ep = ep; + ep->services[channel].channel = channel; + ep->services[channel].cmd_tag = 0; + ops->init(&ep->services[channel], epic_name, epic_class, epic_unit); + dev_info(ep->dcp->dev, "AFK[ep:%02x]: new service %s on channel %d\n", + ep->endpoint, service_name, channel); +free: + kfree(epic_name); + kfree(epic_class); +} + +static void afk_recv_handle_teardown(struct apple_dcp_afkep *ep, u32 channel) +{ + struct apple_epic_service *service = &ep->services[channel]; + const struct apple_epic_service_ops *ops; + unsigned long flags; + + WARN_ON(!service->enabled); + + // TODO: think through what locking is necessary + spin_lock_irqsave(&service->lock, flags); + service->enabled = false; + ops = service->ops; + spin_unlock_irqrestore(&service->lock, flags); + + if (ops->teardown) + ops->teardown(service); +} + +static void afk_recv_handle_reply(struct apple_dcp_afkep *ep, u32 channel, + u16 tag, void *payload, size_t payload_size) +{ + struct epic_cmd *cmd = payload; + struct apple_epic_service *service = &ep->services[channel]; + unsigned long flags; + u8 idx = tag & 0xff; + void *rxbuf, *txbuf; + dma_addr_t rxbuf_dma, txbuf_dma; + size_t rxlen, txlen; + + if (payload_size < sizeof(*cmd)) { + dev_err(ep->dcp->dev, + "AFK[ep:%02x]: command reply on channel %d too small: %ld\n", + ep->endpoint, channel, payload_size); + return; + } + + if (idx >= MAX_PENDING_CMDS) { + dev_err(ep->dcp->dev, + "AFK[ep:%02x]: command reply on channel %d out of range: %d\n", + ep->endpoint, channel, idx); + return; + } + + spin_lock_irqsave(&service->lock, flags); + if (service->cmds[idx].done) { + dev_err(ep->dcp->dev, + "AFK[ep:%02x]: command reply on channel %d already handled\n", + ep->endpoint, channel); + spin_unlock_irqrestore(&service->lock, flags); + return; + } + + if (tag != service->cmds[idx].tag) { + dev_err(ep->dcp->dev, + "AFK[ep:%02x]: command reply on channel %d has invalid tag: expected 0x%04x != 0x%04x\n", + ep->endpoint, channel, tag, service->cmds[idx].tag); + spin_unlock_irqrestore(&service->lock, flags); + return; + } + + service->cmds[idx].done = true; + service->cmds[idx].retcode = le32_to_cpu(cmd->retcode); + if (service->cmds[idx].free_on_ack) { + /* defer freeing until we're no longer in atomic context */ + rxbuf = service->cmds[idx].rxbuf; + txbuf = service->cmds[idx].txbuf; + rxlen = service->cmds[idx].rxlen; + txlen = service->cmds[idx].txlen; + rxbuf_dma = service->cmds[idx].rxbuf_dma; + txbuf_dma = service->cmds[idx].txbuf_dma; + bitmap_release_region(service->cmd_map, idx, 0); + } else { + rxbuf = txbuf = NULL; + rxlen = txlen = 0; + } + if (service->cmds[idx].completion) + complete(service->cmds[idx].completion); + + spin_unlock_irqrestore(&service->lock, flags); + + if (rxbuf && rxlen) + dma_free_coherent(ep->dcp->dev, rxlen, rxbuf, rxbuf_dma); + if (txbuf && txlen) + dma_free_coherent(ep->dcp->dev, txlen, txbuf, txbuf_dma); +} + +struct epic_std_service_ap_call { + __le32 unk0; + __le32 unk1; + __le32 type; + __le32 len; + __le32 magic; + u8 _unk[48]; +} __attribute__((packed)); + +static void afk_recv_handle_std_service(struct apple_dcp_afkep *ep, u32 channel, + u32 type, struct epic_hdr *ehdr, + struct epic_sub_hdr *eshdr, + void *payload, size_t payload_size) +{ + struct apple_epic_service *service = &ep->services[channel]; + + if (type == EPIC_TYPE_NOTIFY && eshdr->category == EPIC_CAT_NOTIFY) { + struct epic_std_service_ap_call *call = payload; + size_t call_size; + void *reply; + int ret; + + if (payload_size < sizeof(*call)) + return; + + call_size = le32_to_cpu(call->len); + if (payload_size < sizeof(*call) + call_size) + return; + + if (!service->ops->call) + return; + reply = kzalloc(payload_size, GFP_KERNEL); + if (!reply) + return; + + ret = service->ops->call(service, le32_to_cpu(call->type), + payload + sizeof(*call), call_size, + reply + sizeof(*call), call_size); + if (ret) { + kfree(reply); + return; + } + + memcpy(reply, call, sizeof(*call)); + afk_send_epic(ep, channel, le16_to_cpu(eshdr->tag), + EPIC_TYPE_NOTIFY_ACK, EPIC_CAT_REPLY, + EPIC_SUBTYPE_STD_SERVICE, reply, payload_size); + kfree(reply); + + return; + } + + if (type == EPIC_TYPE_NOTIFY && eshdr->category == EPIC_CAT_REPORT) { + struct epic_std_service_ap_call *call = payload; + size_t call_size; + + if (payload_size < sizeof(*call)) + return; + + call_size = le32_to_cpu(call->len); + if (payload_size < sizeof(*call) + call_size) + return; + + if (!service->ops->report) + return; + + service->ops->report(service, le32_to_cpu(call->type), + payload + sizeof(*call), call_size); + return; + } + + dev_err(ep->dcp->dev, + "AFK[ep:%02x]: channel %d received unhandled standard service message: %x / %x\n", + ep->endpoint, channel, type, eshdr->category); + print_hex_dump(KERN_INFO, "AFK: ", DUMP_PREFIX_NONE, 16, 1, payload, + payload_size, true); +} + +static void afk_recv_handle(struct apple_dcp_afkep *ep, u32 channel, u32 type, + u8 *data, size_t data_size) +{ + struct epic_hdr *ehdr = (struct epic_hdr *)data; + struct epic_sub_hdr *eshdr = + (struct epic_sub_hdr *)(data + sizeof(*ehdr)); + u16 subtype = le16_to_cpu(eshdr->type); + u8 *payload = data + sizeof(*ehdr) + sizeof(*eshdr); + size_t payload_size; + + if (data_size < sizeof(*ehdr) + sizeof(*eshdr)) { + dev_err(ep->dcp->dev, "AFK[ep:%02x]: payload too small: %lx\n", + ep->endpoint, data_size); + return; + } + payload_size = data_size - sizeof(*ehdr) - sizeof(*eshdr); + + trace_afk_recv_handle(ep, channel, type, data_size, ehdr, eshdr); + + if (channel >= AFK_MAX_CHANNEL) { + dev_err(ep->dcp->dev, "AFK[ep:%02x]: channel %d out of bounds\n", + ep->endpoint, channel); + return; + } + + if (!ep->services[channel].enabled) { + if (type != EPIC_TYPE_NOTIFY) { + dev_err(ep->dcp->dev, + "AFK[ep:%02x]: expected notify but got 0x%x on channel %d\n", + ep->endpoint, type, channel); + return; + } + if (eshdr->category != EPIC_CAT_REPORT) { + dev_err(ep->dcp->dev, + "AFK[ep:%02x]: expected report but got 0x%x on channel %d\n", + ep->endpoint, eshdr->category, channel); + return; + } + if (subtype != EPIC_SUBTYPE_ANNOUNCE) { + dev_err(ep->dcp->dev, + "AFK[ep:%02x]: expected announce but got 0x%x on channel %d\n", + ep->endpoint, subtype, channel); + return; + } + + return afk_recv_handle_init(ep, channel, payload, payload_size); + } + + if (!ep->services[channel].enabled) { + dev_err(ep->dcp->dev, "AFK[ep:%02x]: channel %d has no service\n", + ep->endpoint, channel); + return; + } + + if (type == EPIC_TYPE_NOTIFY && eshdr->category == EPIC_CAT_REPORT && + subtype == EPIC_SUBTYPE_TEARDOWN) + return afk_recv_handle_teardown(ep, channel); + + if (type == EPIC_TYPE_REPLY && eshdr->category == EPIC_CAT_REPLY) + return afk_recv_handle_reply(ep, channel, + le16_to_cpu(eshdr->tag), payload, + payload_size); + + if (subtype == EPIC_SUBTYPE_STD_SERVICE) + return afk_recv_handle_std_service( + ep, channel, type, ehdr, eshdr, payload, payload_size); + + dev_err(ep->dcp->dev, "AFK[ep:%02x]: channel %d received unhandled message " + "(type %x subtype %x)\n", ep->endpoint, channel, type, subtype); + print_hex_dump(KERN_INFO, "AFK: ", DUMP_PREFIX_NONE, 16, 1, payload, + payload_size, true); +} + +static bool afk_recv(struct apple_dcp_afkep *ep) +{ + struct afk_qe *hdr; + u32 rptr, wptr; + u32 magic, size, channel, type; + + if (!ep->rxbfr.ready) { + dev_err(ep->dcp->dev, "AFK[ep:%02x]: got RECV but not ready\n", + ep->endpoint); + return false; + } + + rptr = le32_to_cpu(ep->rxbfr.hdr->rptr); + wptr = le32_to_cpu(ep->rxbfr.hdr->wptr); + trace_afk_recv_rwptr_pre(ep, rptr, wptr); + + if (rptr == wptr) + return false; + + if (rptr > (ep->rxbfr.bufsz - sizeof(*hdr))) { + dev_warn(ep->dcp->dev, + "AFK[ep:%02x]: rptr out of bounds: 0x%x > 0x%lx\n", + ep->endpoint, rptr, ep->rxbfr.bufsz - sizeof(*hdr)); + return false; + } + + dma_rmb(); + + hdr = ep->rxbfr.buf + rptr; + magic = le32_to_cpu(hdr->magic); + size = le32_to_cpu(hdr->size); + trace_afk_recv_qe(ep, rptr, magic, size); + + if (magic != QE_MAGIC) { + dev_warn(ep->dcp->dev, "AFK[ep:%02x]: invalid queue entry magic: 0x%x\n", + ep->endpoint, magic); + return false; + } + + /* + * If there's not enough space for the payload the co-processor inserted + * the current dummy queue entry and we have to advance to the next one + * which will contain the real data. + */ + if (rptr + size + sizeof(*hdr) > ep->rxbfr.bufsz) { + rptr = 0; + hdr = ep->rxbfr.buf + rptr; + magic = le32_to_cpu(hdr->magic); + size = le32_to_cpu(hdr->size); + trace_afk_recv_qe(ep, rptr, magic, size); + + if (magic != QE_MAGIC) { + dev_warn(ep->dcp->dev, + "AFK[ep:%02x]: invalid next queue entry magic: 0x%x\n", + ep->endpoint, magic); + return false; + } + + ep->rxbfr.hdr->rptr = cpu_to_le32(rptr); + } + + if (rptr + size + sizeof(*hdr) > ep->rxbfr.bufsz) { + dev_warn(ep->dcp->dev, + "AFK[ep:%02x]: queue entry out of bounds: 0x%lx > 0x%lx\n", + ep->endpoint, rptr + size + sizeof(*hdr), ep->rxbfr.bufsz); + return false; + } + + channel = le32_to_cpu(hdr->channel); + type = le32_to_cpu(hdr->type); + + afk_recv_handle(ep, channel, type, hdr->data, size); + + rptr = ALIGN(rptr + sizeof(*hdr) + size, 1 << BLOCK_SHIFT); + if (WARN_ON(rptr > ep->rxbfr.bufsz)) + rptr = 0; + if (rptr == ep->rxbfr.bufsz) + rptr = 0; + + dma_mb(); + + ep->rxbfr.hdr->rptr = cpu_to_le32(rptr); + trace_afk_recv_rwptr_post(ep, rptr, wptr); + + return true; +} + +static void afk_receive_message_worker(struct work_struct *work_) +{ + struct afk_receive_message_work *work; + u16 type; + + work = container_of(work_, struct afk_receive_message_work, work); + + type = FIELD_GET(RBEP_TYPE, work->message); + switch (type) { + case RBEP_INIT_ACK: + break; + + case RBEP_START_ACK: + complete_all(&work->ep->started); + break; + + case RBEP_SHUTDOWN_ACK: + complete_all(&work->ep->stopped); + break; + + case RBEP_GETBUF: + afk_getbuf(work->ep, work->message); + break; + + case RBEP_INIT_TX: + afk_init_rxtx(work->ep, work->message, &work->ep->txbfr); + break; + + case RBEP_INIT_RX: + afk_init_rxtx(work->ep, work->message, &work->ep->rxbfr); + break; + + case RBEP_RECV: + while (afk_recv(work->ep)) + ; + break; + + default: + dev_err(work->ep->dcp->dev, + "Received unknown AFK message type: 0x%x\n", type); + } + + kfree(work); +} + +int afk_receive_message(struct apple_dcp_afkep *ep, u64 message) +{ + struct afk_receive_message_work *work; + + // TODO: comment why decoupling from rtkit thread is required here + work = kzalloc(sizeof(*work), GFP_KERNEL); + if (!work) + return -ENOMEM; + + work->ep = ep; + work->message = message; + INIT_WORK(&work->work, afk_receive_message_worker); + queue_work(ep->wq, &work->work); + + return 0; +} + +int afk_send_epic(struct apple_dcp_afkep *ep, u32 channel, u16 tag, + enum epic_type etype, enum epic_category ecat, u8 stype, + const void *payload, size_t payload_len) +{ + u32 rptr, wptr; + struct afk_qe *hdr, *hdr2; + struct epic_hdr *ehdr; + struct epic_sub_hdr *eshdr; + unsigned long flags; + size_t total_epic_size, total_size; + int ret; + + spin_lock_irqsave(&ep->lock, flags); + + dma_rmb(); + rptr = le32_to_cpu(ep->txbfr.hdr->rptr); + wptr = le32_to_cpu(ep->txbfr.hdr->wptr); + trace_afk_send_rwptr_pre(ep, rptr, wptr); + total_epic_size = sizeof(*ehdr) + sizeof(*eshdr) + payload_len; + total_size = sizeof(*hdr) + total_epic_size; + + hdr = hdr2 = NULL; + + /* + * We need to figure out how to place the entire headers and payload + * into the ring buffer: + * - If the write pointer is in front of the read pointer we just need + * enough space inbetween to store everything. + * - If the read pointer has already wrapper around the end of the + * buffer we can + * a) either store the entire payload at the writer pointer if + * there's enough space until the end, + * b) or just store the queue entry at the write pointer to indicate + * that we need to wrap to the start and then store the headers + * and the payload at the beginning of the buffer. The queue + * header has to be store twice in this case. + * In either case we have to ensure that there's always enough space + * so that we don't accidentally overwrite other buffers. + */ + if (wptr < rptr) { + /* + * If wptr < rptr we can't wrap around and only have to make + * sure that there's enough space for the entire payload. + */ + if (wptr + total_size > rptr) { + ret = -ENOMEM; + goto out; + } + + hdr = ep->txbfr.buf + wptr; + wptr += sizeof(*hdr); + } else { + /* We need enough space to place at least a queue entry */ + if (wptr + sizeof(*hdr) > ep->txbfr.bufsz) { + ret = -ENOMEM; + goto out; + } + + /* + * If we can place a single queue entry but not the full payload + * we need to place one queue entry at the end of the ring + * buffer and then another one together with the entire + * payload at the beginning. + */ + if (wptr + total_size > ep->txbfr.bufsz) { + /* + * Ensure there's space for the queue entry at the + * beginning + */ + if (sizeof(*hdr) > rptr) { + ret = -ENOMEM; + goto out; + } + + /* + * Place two queue entries to indicate we want to wrap + * around to the firmware. + */ + hdr = ep->txbfr.buf + wptr; + hdr2 = ep->txbfr.buf; + wptr = sizeof(*hdr); + + /* Ensure there's enough space for the entire payload */ + if (wptr + total_epic_size > rptr) { + ret = -ENOMEM; + goto out; + } + } else { + /* We have enough space to place the entire payload */ + hdr = ep->txbfr.buf + wptr; + wptr += sizeof(*hdr); + } + } + /* + * At this point we're guaranteed that hdr (and possibly hdr2) point + * to a buffer large enough to fit the queue entry and that we have + * enough space at wptr to store the payload. + */ + + hdr->magic = cpu_to_le32(QE_MAGIC); + hdr->size = cpu_to_le32(total_epic_size); + hdr->channel = cpu_to_le32(channel); + hdr->type = cpu_to_le32(etype); + if (hdr2) + memcpy(hdr2, hdr, sizeof(*hdr)); + + ehdr = ep->txbfr.buf + wptr; + memset(ehdr, 0, sizeof(*ehdr)); + ehdr->version = 2; + ehdr->seq = cpu_to_le16(ep->qe_seq++); + ehdr->timestamp = cpu_to_le64(0); + wptr += sizeof(*ehdr); + + eshdr = ep->txbfr.buf + wptr; + memset(eshdr, 0, sizeof(*eshdr)); + eshdr->length = cpu_to_le32(payload_len); + eshdr->version = 3; + eshdr->category = ecat; + eshdr->type = cpu_to_le16(stype); + eshdr->timestamp = cpu_to_le64(0); + eshdr->tag = cpu_to_le16(tag); + eshdr->inline_len = cpu_to_le16(0); + wptr += sizeof(*eshdr); + + memcpy(ep->txbfr.buf + wptr, payload, payload_len); + wptr += payload_len; + wptr = ALIGN(wptr, 1 << BLOCK_SHIFT); + if (wptr == ep->txbfr.bufsz) + wptr = 0; + trace_afk_send_rwptr_post(ep, rptr, wptr); + + ep->txbfr.hdr->wptr = cpu_to_le32(wptr); + afk_send(ep, FIELD_PREP(RBEP_TYPE, RBEP_SEND) | + FIELD_PREP(SEND_WPTR, wptr)); + ret = 0; + +out: + spin_unlock_irqrestore(&ep->lock, flags); + return ret; +} + +int afk_send_command(struct apple_epic_service *service, u8 type, + const void *payload, size_t payload_len, void *output, + size_t output_len, u32 *retcode) +{ + struct epic_cmd cmd; + void *rxbuf, *txbuf; + dma_addr_t rxbuf_dma, txbuf_dma; + unsigned long flags; + int ret, idx; + u16 tag; + struct apple_dcp_afkep *ep = service->ep; + DECLARE_COMPLETION_ONSTACK(completion); + + rxbuf = dma_alloc_coherent(ep->dcp->dev, output_len, &rxbuf_dma, + GFP_KERNEL); + if (!rxbuf) + return -ENOMEM; + txbuf = dma_alloc_coherent(ep->dcp->dev, payload_len, &txbuf_dma, + GFP_KERNEL); + if (!txbuf) { + ret = -ENOMEM; + goto err_free_rxbuf; + } + + memcpy(txbuf, payload, payload_len); + + cmd.retcode = cpu_to_le32(0); + cmd.rxbuf = cpu_to_le64(rxbuf_dma); + cmd.rxlen = cpu_to_le32(output_len); + cmd.txbuf = cpu_to_le64(txbuf_dma); + cmd.txlen = cpu_to_le32(payload_len); + + spin_lock_irqsave(&service->lock, flags); + idx = bitmap_find_free_region(service->cmd_map, MAX_PENDING_CMDS, 0); + if (idx < 0) { + ret = -ENOSPC; + goto err_unlock; + } + + tag = (service->cmd_tag & 0xff) << 8; + tag |= idx & 0xff; + service->cmd_tag++; + + service->cmds[idx].tag = tag; + service->cmds[idx].rxbuf = rxbuf; + service->cmds[idx].txbuf = txbuf; + service->cmds[idx].rxbuf_dma = rxbuf_dma; + service->cmds[idx].txbuf_dma = txbuf_dma; + service->cmds[idx].rxlen = output_len; + service->cmds[idx].txlen = payload_len; + service->cmds[idx].free_on_ack = false; + service->cmds[idx].done = false; + service->cmds[idx].completion = &completion; + init_completion(&completion); + + spin_unlock_irqrestore(&service->lock, flags); + + ret = afk_send_epic(service->ep, service->channel, tag, + EPIC_TYPE_COMMAND, EPIC_CAT_COMMAND, type, &cmd, + sizeof(cmd)); + if (ret) + goto err_free_cmd; + + ret = wait_for_completion_timeout(&completion, + msecs_to_jiffies(MSEC_PER_SEC)); + + if (ret <= 0) { + spin_lock_irqsave(&service->lock, flags); + /* + * Check again while we're inside the lock to make sure + * the command wasn't completed just after + * wait_for_completion_timeout returned. + */ + if (!service->cmds[idx].done) { + service->cmds[idx].completion = NULL; + service->cmds[idx].free_on_ack = true; + spin_unlock_irqrestore(&service->lock, flags); + return -ETIMEDOUT; + } + spin_unlock_irqrestore(&service->lock, flags); + } + + ret = 0; + if (retcode) + *retcode = service->cmds[idx].retcode; + if (output && output_len) + memcpy(output, rxbuf, output_len); + +err_free_cmd: + spin_lock_irqsave(&service->lock, flags); + bitmap_release_region(service->cmd_map, idx, 0); +err_unlock: + spin_unlock_irqrestore(&service->lock, flags); + dma_free_coherent(ep->dcp->dev, payload_len, txbuf, txbuf_dma); +err_free_rxbuf: + dma_free_coherent(ep->dcp->dev, output_len, rxbuf, rxbuf_dma); + return ret; +} + +int afk_service_call(struct apple_epic_service *service, u16 group, u32 command, + const void *data, size_t data_len, size_t data_pad, + void *output, size_t output_len, size_t output_pad) +{ + struct epic_service_call *call; + void *bfr; + size_t bfr_len = max(data_len + data_pad, output_len + output_pad) + + sizeof(*call); + int ret; + u32 retcode; + u32 retlen; + + bfr = kzalloc(bfr_len, GFP_KERNEL); + if (!bfr) + return -ENOMEM; + + call = bfr; + call->group = cpu_to_le16(group); + call->command = cpu_to_le32(command); + call->data_len = cpu_to_le32(data_len + data_pad); + call->magic = cpu_to_le32(EPIC_SERVICE_CALL_MAGIC); + + memcpy(bfr + sizeof(*call), data, data_len); + + ret = afk_send_command(service, EPIC_SUBTYPE_STD_SERVICE, bfr, bfr_len, + bfr, bfr_len, &retcode); + if (ret) + goto out; + if (retcode) { + ret = -EINVAL; + goto out; + } + if (le32_to_cpu(call->magic) != EPIC_SERVICE_CALL_MAGIC || + le16_to_cpu(call->group) != group || + le32_to_cpu(call->command) != command) { + ret = -EINVAL; + goto out; + } + + retlen = le32_to_cpu(call->data_len); + if (output_len < retlen) + retlen = output_len; + if (output && output_len) { + memset(output, 0, output_len); + memcpy(output, bfr + sizeof(*call), retlen); + } + +out: + kfree(bfr); + return ret; +} diff --git a/drivers/gpu/drm/apple/afk.h b/drivers/gpu/drm/apple/afk.h new file mode 100644 index 00000000000000..b800840b4f4a3a --- /dev/null +++ b/drivers/gpu/drm/apple/afk.h @@ -0,0 +1,187 @@ +// SPDX-License-Identifier: GPL-2.0-only OR MIT +/* + * AFK (Apple Firmware Kit) EPIC (EndPoint Interface Client) support + */ +/* Copyright 2022 Sven Peter */ + +#ifndef _DRM_APPLE_DCP_AFK_H +#define _DRM_APPLE_DCP_AFK_H + +#include +#include + +#include "dcp.h" + +#define AFK_MAX_CHANNEL 16 +#define MAX_PENDING_CMDS 16 + +struct apple_epic_service_ops; +struct apple_dcp_afkep; + +struct epic_cmd_info { + u16 tag; + + void *rxbuf; + void *txbuf; + dma_addr_t rxbuf_dma; + dma_addr_t txbuf_dma; + size_t rxlen; + size_t txlen; + + u32 retcode; + bool done; + bool free_on_ack; + struct completion *completion; +}; + +struct apple_epic_service { + const struct apple_epic_service_ops *ops; + struct apple_dcp_afkep *ep; + + struct epic_cmd_info cmds[MAX_PENDING_CMDS]; + DECLARE_BITMAP(cmd_map, MAX_PENDING_CMDS); + u8 cmd_tag; + spinlock_t lock; + + u32 channel; + bool enabled; + + void *cookie; +}; + +struct apple_epic_service_ops { + const char name[32]; + + void (*init)(struct apple_epic_service *service, const char *name, + const char *class, s64 unit); + int (*call)(struct apple_epic_service *service, u32 idx, + const void *data, size_t data_size, void *reply, + size_t reply_size); + int (*report)(struct apple_epic_service *service, u32 idx, + const void *data, size_t data_size); + void (*teardown)(struct apple_epic_service *service); +}; + +struct afk_ringbuffer_header { + __le32 bufsz; + u32 unk; + u32 _pad1[14]; + __le32 rptr; + u32 _pad2[15]; + __le32 wptr; + u32 _pad3[15]; +}; + +struct afk_qe { +#define QE_MAGIC 0x20504f49 // ' POI' + __le32 magic; + __le32 size; + __le32 channel; + __le32 type; + u8 data[]; +}; + +struct epic_hdr { + u8 version; + __le16 seq; + u8 _pad; + __le32 unk; + __le64 timestamp; +} __attribute__((packed)); + +struct epic_sub_hdr { + __le32 length; + u8 version; + u8 category; + __le16 type; + __le64 timestamp; + __le16 tag; + __le16 unk; + __le32 inline_len; +} __attribute__((packed)); + +struct epic_cmd { + __le32 retcode; + __le64 rxbuf; + __le64 txbuf; + __le32 rxlen; + __le32 txlen; +} __attribute__((packed)); + +struct epic_service_call { + u8 _pad0[2]; + __le16 group; + __le32 command; + __le32 data_len; +#define EPIC_SERVICE_CALL_MAGIC 0x69706378 + __le32 magic; + u8 _pad1[48]; +} __attribute__((packed)); +static_assert(sizeof(struct epic_service_call) == 64); + +enum epic_type { + EPIC_TYPE_NOTIFY = 0, + EPIC_TYPE_COMMAND = 3, + EPIC_TYPE_REPLY = 4, + EPIC_TYPE_NOTIFY_ACK = 8, +}; + +enum epic_category { + EPIC_CAT_REPORT = 0x00, + EPIC_CAT_NOTIFY = 0x10, + EPIC_CAT_REPLY = 0x20, + EPIC_CAT_COMMAND = 0x30, +}; + +enum epic_subtype { + EPIC_SUBTYPE_ANNOUNCE = 0x30, + EPIC_SUBTYPE_TEARDOWN = 0x32, + EPIC_SUBTYPE_STD_SERVICE = 0xc0, +}; + +struct afk_ringbuffer { + bool ready; + struct afk_ringbuffer_header *hdr; + u32 rptr; + void *buf; + size_t bufsz; +}; + +struct apple_dcp_afkep { + struct apple_dcp *dcp; + + u32 endpoint; + struct workqueue_struct *wq; + + struct completion started; + struct completion stopped; + + void *bfr; + u16 bfr_tag; + size_t bfr_size; + dma_addr_t bfr_dma; + + struct afk_ringbuffer txbfr; + struct afk_ringbuffer rxbfr; + + spinlock_t lock; + u16 qe_seq; + + const struct apple_epic_service_ops *ops; + struct apple_epic_service services[AFK_MAX_CHANNEL]; +}; + +struct apple_dcp_afkep *afk_init(struct apple_dcp *dcp, u32 endpoint, + const struct apple_epic_service_ops *ops); +int afk_start(struct apple_dcp_afkep *ep); +int afk_receive_message(struct apple_dcp_afkep *ep, u64 message); +int afk_send_epic(struct apple_dcp_afkep *ep, u32 channel, u16 tag, + enum epic_type etype, enum epic_category ecat, u8 stype, + const void *payload, size_t payload_len); +int afk_send_command(struct apple_epic_service *service, u8 type, + const void *payload, size_t payload_len, void *output, + size_t output_len, u32 *retcode); +int afk_service_call(struct apple_epic_service *service, u16 group, u32 command, + const void *data, size_t data_len, size_t data_pad, + void *output, size_t output_len, size_t output_pad); +#endif diff --git a/drivers/gpu/drm/apple/dcp-internal.h b/drivers/gpu/drm/apple/dcp-internal.h index c04585c3374991..aa28ac54651a8a 100644 --- a/drivers/gpu/drm/apple/dcp-internal.h +++ b/drivers/gpu/drm/apple/dcp-internal.h @@ -17,6 +17,7 @@ #define DCP_MAX_PLANES 2 struct apple_dcp; +struct apple_dcp_afkep; enum dcp_firmware_version { DCP_FIRMWARE_UNKNOWN, diff --git a/drivers/gpu/drm/apple/dcp.c b/drivers/gpu/drm/apple/dcp.c index 93c2ad8a31e39d..c77384f80ffc22 100644 --- a/drivers/gpu/drm/apple/dcp.c +++ b/drivers/gpu/drm/apple/dcp.c @@ -26,6 +26,7 @@ #include #include +#include "afk.h" #include "dcp.h" #include "dcp-internal.h" #include "iomfb.h" diff --git a/drivers/gpu/drm/apple/parser.c b/drivers/gpu/drm/apple/parser.c index 50a4fc31cd06ce..dde87b5d4052d5 100644 --- a/drivers/gpu/drm/apple/parser.c +++ b/drivers/gpu/drm/apple/parser.c @@ -589,6 +589,68 @@ int parse_display_attributes(struct dcp_parse_ctx *handle, int *width_mm, return 0; } +int parse_epic_service_init(struct dcp_parse_ctx *handle, const char **name, + const char **class, s64 *unit) +{ + int ret = 0; + struct iterator it; + bool parsed_unit = false; + bool parsed_name = false; + bool parsed_class = false; + + *name = ERR_PTR(-ENOENT); + *class = ERR_PTR(-ENOENT); + + dcp_parse_foreach_in_dict(handle, it) { + char *key = parse_string(it.handle); + + if (IS_ERR(key)) { + ret = PTR_ERR(key); + break; + } + + if (!strcmp(key, "EPICName")) { + *name = parse_string(it.handle); + if (IS_ERR(*name)) + ret = PTR_ERR(*name); + else + parsed_name = true; + } else if (!strcmp(key, "EPICProviderClass")) { + *class = parse_string(it.handle); + if (IS_ERR(*class)) + ret = PTR_ERR(*class); + else + parsed_class = true; + } else if (!strcmp(key, "EPICUnit")) { + ret = parse_int(it.handle, unit); + if (!ret) + parsed_unit = true; + } else { + skip(it.handle); + } + + kfree(key); + if (ret) + break; + } + + if (!parsed_unit || !parsed_name || !parsed_class) + ret = -ENOENT; + + if (ret) { + if (!IS_ERR(*name)) { + kfree(*name); + *name = ERR_PTR(ret); + } + if (!IS_ERR(*class)) { + kfree(*class); + *class = ERR_PTR(ret); + } + } + + return ret; +} + int parse_sample_rate_bit(struct dcp_parse_ctx *handle, unsigned int *ratebit) { s64 rate; diff --git a/drivers/gpu/drm/apple/parser.h b/drivers/gpu/drm/apple/parser.h index 030e3a33b4877d..6d8717873cdd6c 100644 --- a/drivers/gpu/drm/apple/parser.h +++ b/drivers/gpu/drm/apple/parser.h @@ -31,7 +31,8 @@ struct dcp_display_mode *enumerate_modes(struct dcp_parse_ctx *handle, int height_mm, unsigned notch_height); int parse_display_attributes(struct dcp_parse_ctx *handle, int *width_mm, int *height_mm); - +int parse_epic_service_init(struct dcp_parse_ctx *handle, const char **name, + const char **class, s64 *unit); struct dcp_sound_format_mask { u64 formats; /* SNDRV_PCM_FMTBIT_* */ diff --git a/drivers/gpu/drm/apple/trace.h b/drivers/gpu/drm/apple/trace.h index c482b66ffca132..6b3d9886a4164e 100644 --- a/drivers/gpu/drm/apple/trace.h +++ b/drivers/gpu/drm/apple/trace.h @@ -7,7 +7,9 @@ #if !defined(_TRACE_DCP_H) || defined(TRACE_HEADER_MULTI_READ) #define _TRACE_DCP_H +#include "afk.h" #include "dcp-internal.h" +#include "parser.h" #include #include @@ -22,6 +24,17 @@ { HDCP_ENDPOINT, "hdcp" }, \ { REMOTE_ALLOC_ENDPOINT, "remotealloc" }, \ { IOMFB_ENDPOINT, "iomfb" }) +#define print_epic_type(etype) \ + __print_symbolic(etype, { EPIC_TYPE_NOTIFY, "notify" }, \ + { EPIC_TYPE_COMMAND, "command" }, \ + { EPIC_TYPE_REPLY, "reply" }, \ + { EPIC_TYPE_NOTIFY_ACK, "notify-ack" }) + +#define print_epic_category(ecat) \ + __print_symbolic(ecat, { EPIC_CAT_REPORT, "report" }, \ + { EPIC_CAT_NOTIFY, "notify" }, \ + { EPIC_CAT_REPLY, "reply" }, \ + { EPIC_CAT_COMMAND, "command" }) TRACE_EVENT(dcp_recv_msg, TP_PROTO(struct apple_dcp *dcp, u8 endpoint, u64 message), @@ -55,6 +68,103 @@ TRACE_EVENT(dcp_send_msg, __get_str(devname), __entry->endpoint, show_dcp_endpoint(__entry->endpoint), __entry->message)); +TRACE_EVENT( + afk_getbuf, TP_PROTO(struct apple_dcp_afkep *ep, u16 size, u16 tag), + TP_ARGS(ep, size, tag), + + TP_STRUCT__entry(__string(devname, dev_name(ep->dcp->dev)) + __field(u8, endpoint) __field(u16, size) + __field(u16, tag)), + + TP_fast_assign(__assign_str(devname); + __entry->endpoint = ep->endpoint; __entry->size = size; + __entry->tag = tag;), + + TP_printk( + "%s: endpoint 0x%x (%s): get buffer with size 0x%x and tag 0x%x", + __get_str(devname), __entry->endpoint, + show_dcp_endpoint(__entry->endpoint), __entry->size, + __entry->tag)); + +DECLARE_EVENT_CLASS(afk_rwptr_template, + TP_PROTO(struct apple_dcp_afkep *ep, u32 rptr, u32 wptr), + TP_ARGS(ep, rptr, wptr), + + TP_STRUCT__entry(__string(devname, dev_name(ep->dcp->dev)) + __field(u8, endpoint) __field(u32, rptr) + __field(u32, wptr)), + + TP_fast_assign(__assign_str(devname); + __entry->endpoint = ep->endpoint; + __entry->rptr = rptr; __entry->wptr = wptr;), + + TP_printk("%s: endpoint 0x%x (%s): rptr 0x%x, wptr 0x%x", + __get_str(devname), __entry->endpoint, + show_dcp_endpoint(__entry->endpoint), __entry->rptr, + __entry->wptr)); + +DEFINE_EVENT(afk_rwptr_template, afk_recv_rwptr_pre, + TP_PROTO(struct apple_dcp_afkep *ep, u32 rptr, u32 wptr), + TP_ARGS(ep, rptr, wptr)); +DEFINE_EVENT(afk_rwptr_template, afk_recv_rwptr_post, + TP_PROTO(struct apple_dcp_afkep *ep, u32 rptr, u32 wptr), + TP_ARGS(ep, rptr, wptr)); +DEFINE_EVENT(afk_rwptr_template, afk_send_rwptr_pre, + TP_PROTO(struct apple_dcp_afkep *ep, u32 rptr, u32 wptr), + TP_ARGS(ep, rptr, wptr)); +DEFINE_EVENT(afk_rwptr_template, afk_send_rwptr_post, + TP_PROTO(struct apple_dcp_afkep *ep, u32 rptr, u32 wptr), + TP_ARGS(ep, rptr, wptr)); + +TRACE_EVENT( + afk_recv_qe, + TP_PROTO(struct apple_dcp_afkep *ep, u32 rptr, u32 magic, u32 size), + TP_ARGS(ep, rptr, magic, size), + + TP_STRUCT__entry(__string(devname, dev_name(ep->dcp->dev)) + __field(u8, endpoint) __field(u32, rptr) + __field(u32, magic) + __field(u32, size)), + + TP_fast_assign(__assign_str(devname); + __entry->endpoint = ep->endpoint; __entry->rptr = rptr; + __entry->magic = magic; __entry->size = size;), + + TP_printk("%s: endpoint 0x%x (%s): QE rptr 0x%x, magic 0x%x, size 0x%x", + __get_str(devname), __entry->endpoint, + show_dcp_endpoint(__entry->endpoint), __entry->rptr, + __entry->magic, __entry->size)); + +TRACE_EVENT( + afk_recv_handle, + TP_PROTO(struct apple_dcp_afkep *ep, u32 channel, u32 type, + u32 data_size, struct epic_hdr *ehdr, + struct epic_sub_hdr *eshdr), + TP_ARGS(ep, channel, type, data_size, ehdr, eshdr), + + TP_STRUCT__entry(__string(devname, dev_name(ep->dcp->dev)) __field( + u8, endpoint) __field(u32, channel) __field(u32, type) + __field(u32, data_size) __field(u8, category) + __field(u16, subtype) + __field(u16, tag)), + + TP_fast_assign(__assign_str(devname); + __entry->endpoint = ep->endpoint; + __entry->channel = channel; __entry->type = type; + __entry->data_size = data_size; + __entry->category = eshdr->category, + __entry->subtype = le16_to_cpu(eshdr->type), + __entry->tag = le16_to_cpu(eshdr->tag)), + + TP_printk( + "%s: endpoint 0x%x (%s): channel 0x%x, type 0x%x (%s), data_size 0x%x, category: 0x%x (%s), subtype: 0x%x, seq: 0x%x", + __get_str(devname), __entry->endpoint, + show_dcp_endpoint(__entry->endpoint), __entry->channel, + __entry->type, print_epic_type(__entry->type), + __entry->data_size, __entry->category, + print_epic_category(__entry->category), __entry->subtype, + __entry->tag)); + TRACE_EVENT(iomfb_callback, TP_PROTO(struct apple_dcp *dcp, int tag, const char *name), TP_ARGS(dcp, tag, name), From e79e81f41a48045e9cde81d9102e69c2209977d0 Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Sun, 12 Nov 2023 12:35:51 +0100 Subject: [PATCH 1108/3902] drm: apple: afk: Use linear array of services "Channel numbers" as received by AFK/EPIC are constantly increasing over restarts of the endpoint. Use a linear array of services and match based on the channel number. The number of services per endpoint is too small to make a difference. Signed-off-by: Janne Grunau --- drivers/gpu/drm/apple/afk.c | 74 +++++++++++++++++++++++++++---------- drivers/gpu/drm/apple/afk.h | 1 + 2 files changed, 55 insertions(+), 20 deletions(-) diff --git a/drivers/gpu/drm/apple/afk.c b/drivers/gpu/drm/apple/afk.c index 9f2f0b646ac6e0..d577f4ec055b03 100644 --- a/drivers/gpu/drm/apple/afk.c +++ b/drivers/gpu/drm/apple/afk.c @@ -201,11 +201,22 @@ afk_match_service(struct apple_dcp_afkep *ep, const char *name) return NULL; } +static struct apple_epic_service *afk_epic_find_service(struct apple_dcp_afkep *ep, + u32 channel) +{ + for (u32 i = 0; i < ep->num_channels; i++) + if (ep->services[i].enabled && ep->services[i].channel == channel) + return &ep->services[i]; + + return NULL; +} + static void afk_recv_handle_init(struct apple_dcp_afkep *ep, u32 channel, u8 *payload, size_t payload_size) { char name[32]; s64 epic_unit = -1; + u32 ch_idx; const char *service_name = name; const char *epic_name = NULL, *epic_class = NULL; const struct apple_epic_service_ops *ops; @@ -213,7 +224,7 @@ static void afk_recv_handle_init(struct apple_dcp_afkep *ep, u32 channel, u8 *props = payload + sizeof(name); size_t props_size = payload_size - sizeof(name); - WARN_ON(ep->services[channel].enabled); + WARN_ON(afk_epic_find_service(ep, channel)); if (payload_size < sizeof(name)) { dev_err(ep->dcp->dev, "AFK[ep:%02x]: payload too small: %lx\n", @@ -221,7 +232,13 @@ static void afk_recv_handle_init(struct apple_dcp_afkep *ep, u32 channel, return; } - strlcpy(name, payload, sizeof(name)); + if (ep->num_channels >= AFK_MAX_CHANNEL) { + dev_err(ep->dcp->dev, "AFK[ep:%02x]: too many enabled services!\n", + ep->endpoint); + return; + } + + strscpy(name, payload, sizeof(name)); /* * in DCP firmware 13.2 DCP reports interface-name as name which starts @@ -257,13 +274,14 @@ static void afk_recv_handle_init(struct apple_dcp_afkep *ep, u32 channel, goto free; } - spin_lock_init(&ep->services[channel].lock); - ep->services[channel].enabled = true; - ep->services[channel].ops = ops; - ep->services[channel].ep = ep; - ep->services[channel].channel = channel; - ep->services[channel].cmd_tag = 0; - ops->init(&ep->services[channel], epic_name, epic_class, epic_unit); + ch_idx = ep->num_channels++; + spin_lock_init(&ep->services[ch_idx].lock); + ep->services[ch_idx].enabled = true; + ep->services[ch_idx].ops = ops; + ep->services[ch_idx].ep = ep; + ep->services[ch_idx].channel = channel; + ep->services[ch_idx].cmd_tag = 0; + ops->init(&ep->services[ch_idx], epic_name, epic_class, epic_unit); dev_info(ep->dcp->dev, "AFK[ep:%02x]: new service %s on channel %d\n", ep->endpoint, service_name, channel); free: @@ -273,11 +291,16 @@ static void afk_recv_handle_init(struct apple_dcp_afkep *ep, u32 channel, static void afk_recv_handle_teardown(struct apple_dcp_afkep *ep, u32 channel) { - struct apple_epic_service *service = &ep->services[channel]; + struct apple_epic_service *service; const struct apple_epic_service_ops *ops; unsigned long flags; - WARN_ON(!service->enabled); + service = afk_epic_find_service(ep, channel); + if (!service) { + dev_warn(ep->dcp->dev, "AFK[ep:%02x]: teardown for disabled channel %u\n", + ep->endpoint, channel); + return; + } // TODO: think through what locking is necessary spin_lock_irqsave(&service->lock, flags); @@ -293,13 +316,20 @@ static void afk_recv_handle_reply(struct apple_dcp_afkep *ep, u32 channel, u16 tag, void *payload, size_t payload_size) { struct epic_cmd *cmd = payload; - struct apple_epic_service *service = &ep->services[channel]; + struct apple_epic_service *service; unsigned long flags; u8 idx = tag & 0xff; void *rxbuf, *txbuf; dma_addr_t rxbuf_dma, txbuf_dma; size_t rxlen, txlen; + service = afk_epic_find_service(ep, channel); + if (!service) { + dev_warn(ep->dcp->dev, "AFK[ep:%02x]: command reply on disabled channel %u\n", + ep->endpoint, channel); + return; + } + if (payload_size < sizeof(*cmd)) { dev_err(ep->dcp->dev, "AFK[ep:%02x]: command reply on channel %d too small: %ld\n", @@ -371,7 +401,14 @@ static void afk_recv_handle_std_service(struct apple_dcp_afkep *ep, u32 channel, struct epic_sub_hdr *eshdr, void *payload, size_t payload_size) { - struct apple_epic_service *service = &ep->services[channel]; + struct apple_epic_service *service = afk_epic_find_service(ep, channel); + + if (!service) { + dev_warn(ep->dcp->dev, + "AFK[ep:%02x]: std service notify on disabled channel %u\n", + ep->endpoint, channel); + return; + } if (type == EPIC_TYPE_NOTIFY && eshdr->category == EPIC_CAT_NOTIFY) { struct epic_std_service_ap_call *call = payload; @@ -438,6 +475,7 @@ static void afk_recv_handle_std_service(struct apple_dcp_afkep *ep, u32 channel, static void afk_recv_handle(struct apple_dcp_afkep *ep, u32 channel, u32 type, u8 *data, size_t data_size) { + struct apple_epic_service *service; struct epic_hdr *ehdr = (struct epic_hdr *)data; struct epic_sub_hdr *eshdr = (struct epic_sub_hdr *)(data + sizeof(*ehdr)); @@ -454,13 +492,9 @@ static void afk_recv_handle(struct apple_dcp_afkep *ep, u32 channel, u32 type, trace_afk_recv_handle(ep, channel, type, data_size, ehdr, eshdr); - if (channel >= AFK_MAX_CHANNEL) { - dev_err(ep->dcp->dev, "AFK[ep:%02x]: channel %d out of bounds\n", - ep->endpoint, channel); - return; - } + service = afk_epic_find_service(ep, channel); - if (!ep->services[channel].enabled) { + if (!service) { if (type != EPIC_TYPE_NOTIFY) { dev_err(ep->dcp->dev, "AFK[ep:%02x]: expected notify but got 0x%x on channel %d\n", @@ -483,7 +517,7 @@ static void afk_recv_handle(struct apple_dcp_afkep *ep, u32 channel, u32 type, return afk_recv_handle_init(ep, channel, payload, payload_size); } - if (!ep->services[channel].enabled) { + if (!service) { dev_err(ep->dcp->dev, "AFK[ep:%02x]: channel %d has no service\n", ep->endpoint, channel); return; diff --git a/drivers/gpu/drm/apple/afk.h b/drivers/gpu/drm/apple/afk.h index b800840b4f4a3a..fe4ed35159ace0 100644 --- a/drivers/gpu/drm/apple/afk.h +++ b/drivers/gpu/drm/apple/afk.h @@ -169,6 +169,7 @@ struct apple_dcp_afkep { const struct apple_epic_service_ops *ops; struct apple_epic_service services[AFK_MAX_CHANNEL]; + u32 num_channels; }; struct apple_dcp_afkep *afk_init(struct apple_dcp *dcp, u32 endpoint, From 288adaf45c38b124a2eab4cee11f148a76f78b2e Mon Sep 17 00:00:00 2001 From: Sven Peter Date: Sat, 5 Nov 2022 13:15:34 +0100 Subject: [PATCH 1109/3902] drm: apple: Add DPTX support This is required for DP Altmode, DP Thunderbolt tunneling and HDMI output on 14/16-inch Macbook Pros and M2* desktop devices. M2* desktops and 14 and 16 inch Macbook Pros expose a DisplayPort to HDMI converter which is driven by the DP output of one of the DCP/DCPext display coprocessor/controller blocks. Two gpio pins are used for power control. Another gpio pin acts as HDMI hpd. Do not use the hpd as direct drm_connector interrupt since that is already wired to DCPs hotplug notification. Instead use it to trigger link setup via the dptx endpoint. Signed-off-by: Sven Peter Co-developed-by: Janne Grunau Signed-off-by: Janne Grunau --- drivers/gpu/drm/apple/Kconfig | 1 + drivers/gpu/drm/apple/Makefile | 3 +- drivers/gpu/drm/apple/apple_drv.c | 11 +- drivers/gpu/drm/apple/dcp-internal.h | 34 +++ drivers/gpu/drm/apple/dcp.c | 225 ++++++++++++++- drivers/gpu/drm/apple/dcp.h | 3 + drivers/gpu/drm/apple/dcp_trace.c | 3 + drivers/gpu/drm/apple/dptxep.c | 408 +++++++++++++++++++++++++++ drivers/gpu/drm/apple/dptxep.h | 66 +++++ drivers/gpu/drm/apple/ibootep.c | 29 ++ drivers/gpu/drm/apple/parser.c | 11 +- drivers/gpu/drm/apple/parser.h | 5 + drivers/gpu/drm/apple/systemep.c | 100 +++++++ drivers/gpu/drm/apple/trace.h | 140 +++++++++ 14 files changed, 1026 insertions(+), 13 deletions(-) create mode 100644 drivers/gpu/drm/apple/dcp_trace.c create mode 100644 drivers/gpu/drm/apple/dptxep.c create mode 100644 drivers/gpu/drm/apple/dptxep.h create mode 100644 drivers/gpu/drm/apple/ibootep.c create mode 100644 drivers/gpu/drm/apple/systemep.c diff --git a/drivers/gpu/drm/apple/Kconfig b/drivers/gpu/drm/apple/Kconfig index 805639cf94d571..b28b84cef961b1 100644 --- a/drivers/gpu/drm/apple/Kconfig +++ b/drivers/gpu/drm/apple/Kconfig @@ -9,5 +9,6 @@ config DRM_APPLE select DRM_KMS_DMA_HELPER select DRM_GEM_DMA_HELPER select VIDEOMODE_HELPERS + select MULTIPLEXER help Say Y if you have an Apple Silicon chipset. diff --git a/drivers/gpu/drm/apple/Makefile b/drivers/gpu/drm/apple/Makefile index 2cc9cfa9fb9ef9..50b7682ee4ccdb 100644 --- a/drivers/gpu/drm/apple/Makefile +++ b/drivers/gpu/drm/apple/Makefile @@ -4,7 +4,8 @@ CFLAGS_trace.o = -I$(src) appledrm-y := apple_drv.o -apple_dcp-y := afk.o dcp.o dcp_backlight.o iomfb.o parser.o +apple_dcp-y := afk.o dcp.o dcp_backlight.o dptxep.o iomfb.o parser.o systemep.o +apple_dcp-y += ibootep.o apple_dcp-y += iomfb_v12_3.o apple_dcp-y += iomfb_v13_3.o apple_dcp-$(CONFIG_TRACING) += trace.o diff --git a/drivers/gpu/drm/apple/apple_drv.c b/drivers/gpu/drm/apple/apple_drv.c index 5233b8fb50d097..882263b3ef6b71 100644 --- a/drivers/gpu/drm/apple/apple_drv.c +++ b/drivers/gpu/drm/apple/apple_drv.c @@ -315,7 +315,7 @@ static const struct drm_crtc_helper_funcs apple_crtc_helper_funcs = { static int apple_probe_per_dcp(struct device *dev, struct drm_device *drm, struct platform_device *dcp, - int num) + int num, bool dcp_ext) { struct apple_crtc *crtc; struct apple_connector *connector; @@ -347,6 +347,10 @@ static int apple_probe_per_dcp(struct device *dev, drm_connector_helper_add(&connector->base, &apple_connector_helper_funcs); + // HACK: + if (dcp_ext) + connector->base.fwnode = fwnode_handle_get(dcp->dev.fwnode); + ret = drm_connector_init(drm, &connector->base, &apple_connector_funcs, dcp_get_connector_type(dcp)); if (ret) @@ -398,6 +402,7 @@ static int apple_get_fb_resource(struct device *dev, const char *name, static const struct of_device_id apple_dcp_id_tbl[] = { { .compatible = "apple,dcp" }, + { .compatible = "apple,dcpext" }, {}, }; @@ -410,10 +415,12 @@ static int apple_drm_init_dcp(struct device *dev) int i, ret, num_dcp = 0; for_each_matching_node(np, apple_dcp_id_tbl) { + bool dcp_ext; if (!of_device_is_available(np)) { of_node_put(np); continue; } + dcp_ext = of_device_is_compatible(np, "apple,dcpext"); dcp[num_dcp] = of_find_device_by_node(np); of_node_put(np); @@ -421,7 +428,7 @@ static int apple_drm_init_dcp(struct device *dev) continue; ret = apple_probe_per_dcp(dev, &apple->drm, dcp[num_dcp], - num_dcp); + num_dcp, dcp_ext); if (ret) continue; diff --git a/drivers/gpu/drm/apple/dcp-internal.h b/drivers/gpu/drm/apple/dcp-internal.h index aa28ac54651a8a..849bd2937ebb9d 100644 --- a/drivers/gpu/drm/apple/dcp-internal.h +++ b/drivers/gpu/drm/apple/dcp-internal.h @@ -7,9 +7,12 @@ #include #include #include +#include +#include #include #include +#include "dptxep.h" #include "iomfb.h" #include "iomfb_v12_3.h" #include "iomfb_v13_3.h" @@ -94,6 +97,10 @@ struct dcp_panel { bool has_mini_led; }; +struct apple_dcp_hw_data { + u32 num_dptx_ports; +}; + /* TODO: move IOMFB members to its own struct */ struct apple_dcp { struct device *dev; @@ -103,6 +110,8 @@ struct apple_dcp { struct apple_crtc *crtc; struct apple_connector *connector; + struct apple_dcp_hw_data hw; + /* firmware version and compatible firmware version */ enum dcp_firmware_version fw_compat; @@ -127,6 +136,8 @@ struct apple_dcp { struct resource *disp_registers[MAX_DISP_REGISTERS]; unsigned int nr_disp_registers; + u32 index; + /* Bitmap of memory descriptors used for mappings made by the DCP */ DECLARE_BITMAP(memdesc_map, DCP_MAX_MAPPINGS); @@ -191,6 +202,29 @@ struct apple_dcp { /* integrated panel if present */ struct dcp_panel panel; + + struct apple_dcp_afkep *systemep; + struct completion systemep_done; + + struct apple_dcp_afkep *ibootep; + + struct apple_dcp_afkep *dptxep; + + struct dptx_port dptxport[2]; + + /* these fields are output port specific */ + struct phy *phy; + struct mux_control *xbar; + + struct gpio_desc *hdmi_hpd; + struct gpio_desc *hdmi_pwren; + struct gpio_desc *dp2hdmi_pwren; + + struct mutex hpd_mutex; + + u32 dptx_phy; + u32 dptx_die; + int hdmi_hpd_irq; }; int dcp_backlight_register(struct apple_dcp *dcp); diff --git a/drivers/gpu/drm/apple/dcp.c b/drivers/gpu/drm/apple/dcp.c index c77384f80ffc22..02d19c248ab106 100644 --- a/drivers/gpu/drm/apple/dcp.c +++ b/drivers/gpu/drm/apple/dcp.c @@ -8,6 +8,7 @@ #include #include #include +#include #include #include #include @@ -115,6 +116,15 @@ static void dcp_recv_msg(void *cookie, u8 endpoint, u64 message) switch (endpoint) { case IOMFB_ENDPOINT: return iomfb_recv_msg(dcp, message); + case SYSTEM_ENDPOINT: + afk_receive_message(dcp->systemep, message); + return; + case DISP0_ENDPOINT: + afk_receive_message(dcp->ibootep, message); + return; + case DPTX_ENDPOINT: + afk_receive_message(dcp->dptxep, message); + return; default: WARN(endpoint, "unknown DCP endpoint %hhu", endpoint); } @@ -194,7 +204,7 @@ void dcp_send_message(struct apple_dcp *dcp, u8 endpoint, u64 message) { trace_dcp_send_msg(dcp, endpoint, message); apple_rtkit_send_message(dcp->rtk, endpoint, message, NULL, - false); + true); } int dcp_crtc_atomic_check(struct drm_crtc *crtc, struct drm_atomic_state *state) @@ -243,6 +253,66 @@ int dcp_get_connector_type(struct platform_device *pdev) } EXPORT_SYMBOL_GPL(dcp_get_connector_type); +static int dcp_dptx_connect(struct apple_dcp *dcp, u32 port) +{ + if (!dcp->phy) { + dev_warn(dcp->dev, "dcp_dptx_connect: missing phy\n"); + return -ENODEV; + } + + mutex_lock(&dcp->hpd_mutex); + if (!dcp->dptxport[port].enabled) { + dev_warn(dcp->dev, "dcp_dptx_connect: dptx service for port %d not enabled\n", port); + mutex_unlock(&dcp->hpd_mutex); + return -ENODEV; + } + + if (dcp->dptxport[port].connected) + return 0; + + dcp->dptxport[port].atcphy = dcp->phy; + dptxport_connect(dcp->dptxport[port].service, 0, dcp->dptx_phy, dcp->dptx_die); + dptxport_request_display(dcp->dptxport[port].service); + dcp->dptxport[port].connected = true; + mutex_unlock(&dcp->hpd_mutex); + + return 0; +} + +static int dcp_dptx_disconnect(struct apple_dcp *dcp, u32 port) +{ + struct apple_connector *connector = dcp->connector; + + mutex_lock(&dcp->hpd_mutex); + if (connector && connector->connected) { + dcp->valid_mode = false; + schedule_work(&connector->hotplug_wq); + } + + if (dcp->dptxport[port].enabled && dcp->dptxport[port].connected) { + dptxport_release_display(dcp->dptxport[port].service); + dcp->dptxport[port].connected = false; + } + mutex_unlock(&dcp->hpd_mutex); + + return 0; +} + +static irqreturn_t dcp_dp2hdmi_hpd(int irq, void *data) +{ + struct apple_dcp *dcp = data; + bool connected = gpiod_get_value_cansleep(dcp->hdmi_hpd); + + dev_info(dcp->dev, "DP2HDMI HPD connected:%d\n", connected); + + if (connected) + dcp_dptx_connect(dcp, 0); + else + dcp_dptx_disconnect(dcp, 0); + + return IRQ_HANDLED; +} + void dcp_link(struct platform_device *pdev, struct apple_crtc *crtc, struct apple_connector *connector) { @@ -261,6 +331,28 @@ int dcp_start(struct platform_device *pdev) init_completion(&dcp->start_done); /* start RTKit endpoints */ + ret = systemep_init(dcp); + if (ret) + dev_warn(dcp->dev, "Failed to start system endpoint: %d", ret); + + if (dcp->phy) { + if (dcp->fw_compat >= DCP_FIRMWARE_V_13_5) { + ret = ibootep_init(dcp); + if (ret) + dev_warn(dcp->dev, + "Failed to start IBOOT endpoint: %d", + ret); + + ret = dptxep_init(dcp); + if (ret) + dev_warn(dcp->dev, + "Failed to start DPTX endpoint: %d", + ret); + } else + dev_warn(dcp->dev, + "OS firmware incompatible with dptxport EP\n"); + } + ret = iomfb_start_rtkit(dcp); if (ret) dev_err(dcp->dev, "Failed to start IOMFB endpoint: %d", ret); @@ -269,6 +361,23 @@ int dcp_start(struct platform_device *pdev) } EXPORT_SYMBOL(dcp_start); +static int dcp_enable_dp2hdmi_hpd(struct apple_dcp *dcp) +{ + if (dcp->hdmi_hpd) { + bool connected = gpiod_get_value_cansleep(dcp->hdmi_hpd); + dev_info(dcp->dev, "%s: DP2HDMI HPD connected:%d\n", __func__, connected); + + // necessary on j473/j474 but not on j314c + if (connected) + dcp_dptx_connect(dcp, 0); + + if (dcp->hdmi_hpd_irq) + enable_irq(dcp->hdmi_hpd_irq); + } + + return 0; +} + int dcp_wait_ready(struct platform_device *pdev, u64 timeout) { struct apple_dcp *dcp = platform_get_drvdata(pdev); @@ -277,7 +386,7 @@ int dcp_wait_ready(struct platform_device *pdev, u64 timeout) if (dcp->crashed) return -ENODEV; if (dcp->active) - return 0; + return dcp_enable_dp2hdmi_hpd(dcp); if (timeout <= 0) return -ETIMEDOUT; @@ -288,6 +397,9 @@ int dcp_wait_ready(struct platform_device *pdev, u64 timeout) if (dcp->crashed) return -ENODEV; + if (dcp->active) + dcp_enable_dp2hdmi_hpd(dcp); + return dcp->active ? 0 : -ETIMEDOUT; } EXPORT_SYMBOL(dcp_wait_ready); @@ -476,6 +588,17 @@ static int dcp_comp_bind(struct device *dev, struct device *main, void *data) if (IS_ERR(dcp->coproc_reg)) return PTR_ERR(dcp->coproc_reg); + of_property_read_u32(dev->of_node, "apple,dcp-index", + &dcp->index); + of_property_read_u32(dev->of_node, "apple,dptx-phy", + &dcp->dptx_phy); + of_property_read_u32(dev->of_node, "apple,dptx-die", + &dcp->dptx_die); + if (dcp->index || dcp->dptx_phy || dcp->dptx_die) + dev_info(dev, "DCP index:%u dptx target phy: %u dptx die: %u\n", + dcp->index, dcp->dptx_phy, dcp->dptx_die); + mutex_init(&dcp->hpd_mutex); + if (!show_notch) ret = of_property_read_u32(dev->of_node, "apple,notch-height", &dcp->notch_height); @@ -560,7 +683,6 @@ static int dcp_comp_bind(struct device *dev, struct device *main, void *data) if (ret) return dev_err_probe(dev, ret, "Failed to boot RTKit: %d", ret); - return ret; } @@ -572,6 +694,9 @@ static void dcp_comp_unbind(struct device *dev, struct device *main, void *data) { struct apple_dcp *dcp = dev_get_drvdata(dev); + if (dcp->hdmi_hpd_irq) + disable_irq(dcp->hdmi_hpd_irq); + if (dcp && dcp->shmem) iomfb_shutdown(dcp); @@ -596,6 +721,7 @@ static int dcp_platform_probe(struct platform_device *pdev) enum dcp_firmware_version fw_compat; struct device *dev = &pdev->dev; struct apple_dcp *dcp; + u32 mux_index; fw_compat = dcp_check_firmware_version(dev); if (fw_compat == DCP_FIRMWARE_UNKNOWN) @@ -607,9 +733,71 @@ static int dcp_platform_probe(struct platform_device *pdev) dcp->fw_compat = fw_compat; dcp->dev = dev; + dcp->hw = *(struct apple_dcp_hw_data *)of_device_get_match_data(dev); platform_set_drvdata(pdev, dcp); + dcp->phy = devm_phy_optional_get(dev, "dp-phy"); + if (IS_ERR(dcp->phy)) { + dev_err(dev, "Failed to get dp-phy: %ld", PTR_ERR(dcp->phy)); + return PTR_ERR(dcp->phy); + } + if (dcp->phy) { + int ret; + /* + * Request DP2HDMI related GPIOs as optional for DP-altmode + * compatibility. J180D misses a dp2hdmi-pwren GPIO in the + * template ADT. TODO: check device ADT + */ + dcp->hdmi_hpd = devm_gpiod_get_optional(dev, "hdmi-hpd", GPIOD_IN); + if (IS_ERR(dcp->hdmi_hpd)) + return PTR_ERR(dcp->hdmi_hpd); + if (dcp->hdmi_hpd) { + int irq = gpiod_to_irq(dcp->hdmi_hpd); + if (irq < 0) { + dev_err(dev, "failed to translate HDMI hpd GPIO to IRQ\n"); + return irq; + } + dcp->hdmi_hpd_irq = irq; + + ret = devm_request_threaded_irq(dev, dcp->hdmi_hpd_irq, + NULL, dcp_dp2hdmi_hpd, + IRQF_ONESHOT | IRQF_NO_AUTOEN | + IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING, + "dp2hdmi-hpd-irq", dcp); + if (ret < 0) { + dev_err(dev, "failed to request HDMI hpd irq %d: %d", + irq, ret); + return ret; + } + } + + /* + * Power DP2HDMI on as it is required for the HPD irq. + * TODO: check if one is sufficient for the hpd to save power + * on battery powered Macbooks. + */ + dcp->hdmi_pwren = devm_gpiod_get_optional(dev, "hdmi-pwren", GPIOD_OUT_HIGH); + if (IS_ERR(dcp->hdmi_pwren)) + return PTR_ERR(dcp->hdmi_pwren); + + dcp->dp2hdmi_pwren = devm_gpiod_get_optional(dev, "dp2hdmi-pwren", GPIOD_OUT_HIGH); + if (IS_ERR(dcp->dp2hdmi_pwren)) + return PTR_ERR(dcp->dp2hdmi_pwren); + + ret = of_property_read_u32(dev->of_node, "mux-index", &mux_index); + if (!ret) { + dcp->xbar = devm_mux_control_get(dev, "dp-xbar"); + if (IS_ERR(dcp->xbar)) { + dev_err(dev, "Failed to get dp-xbar: %ld", PTR_ERR(dcp->xbar)); + return PTR_ERR(dcp->xbar); + } + ret = mux_control_select(dcp->xbar, mux_index); + if (ret) + dev_warn(dev, "mux_control_select failed: %d\n", ret); + } + } + return component_add(&pdev->dev, &dcp_comp_ops); } @@ -625,6 +813,10 @@ static void dcp_platform_shutdown(struct platform_device *pdev) static int dcp_platform_suspend(struct device *dev) { + struct apple_dcp *dcp = dev_get_drvdata(dev); + + if (dcp->hdmi_hpd_irq) + disable_irq(dcp->hdmi_hpd_irq); /* * Set the device as a wakeup device, which forces its power * domains to stay on. We need this as we do not support full @@ -637,14 +829,39 @@ static int dcp_platform_suspend(struct device *dev) static int dcp_platform_resume(struct device *dev) { + struct apple_dcp *dcp = dev_get_drvdata(dev); + + if (dcp->hdmi_hpd_irq) + enable_irq(dcp->hdmi_hpd_irq); + return 0; } static DEFINE_SIMPLE_DEV_PM_OPS(dcp_platform_pm_ops, dcp_platform_suspend, dcp_platform_resume); + +static const struct apple_dcp_hw_data apple_dcp_hw_t6020 = { + .num_dptx_ports = 1, +}; + +static const struct apple_dcp_hw_data apple_dcp_hw_t8112 = { + .num_dptx_ports = 2, +}; + +static const struct apple_dcp_hw_data apple_dcp_hw_dcp = { + .num_dptx_ports = 0, +}; + +static const struct apple_dcp_hw_data apple_dcp_hw_dcpext = { + .num_dptx_ports = 2, +}; + static const struct of_device_id of_match[] = { - { .compatible = "apple,dcp" }, + { .compatible = "apple,t6020-dcp", .data = &apple_dcp_hw_t6020, }, + { .compatible = "apple,t8112-dcp", .data = &apple_dcp_hw_t8112, }, + { .compatible = "apple,dcp", .data = &apple_dcp_hw_dcp, }, + { .compatible = "apple,dcpext", .data = &apple_dcp_hw_dcpext, }, {} }; MODULE_DEVICE_TABLE(of, of_match); diff --git a/drivers/gpu/drm/apple/dcp.h b/drivers/gpu/drm/apple/dcp.h index 2011d27e809d53..e0dc96109b9d2b 100644 --- a/drivers/gpu/drm/apple/dcp.h +++ b/drivers/gpu/drm/apple/dcp.h @@ -68,4 +68,7 @@ void iomfb_shutdown(struct apple_dcp *dcp); /* rtkit message handler for IOMFB messages */ void iomfb_recv_msg(struct apple_dcp *dcp, u64 message); +int systemep_init(struct apple_dcp *dcp); +int dptxep_init(struct apple_dcp *dcp); +int ibootep_init(struct apple_dcp *dcp); #endif diff --git a/drivers/gpu/drm/apple/dcp_trace.c b/drivers/gpu/drm/apple/dcp_trace.c new file mode 100644 index 00000000000000..d18e71af73a74d --- /dev/null +++ b/drivers/gpu/drm/apple/dcp_trace.c @@ -0,0 +1,3 @@ +// SPDX-License-Identifier: GPL-2.0 +#define CREATE_TRACE_POINTS +#include "dcp_trace.h" \ No newline at end of file diff --git a/drivers/gpu/drm/apple/dptxep.c b/drivers/gpu/drm/apple/dptxep.c new file mode 100644 index 00000000000000..2002f540d0e729 --- /dev/null +++ b/drivers/gpu/drm/apple/dptxep.c @@ -0,0 +1,408 @@ +// SPDX-License-Identifier: GPL-2.0-only OR MIT +/* Copyright 2022 Sven Peter */ + +#include +#include +#include +#include + +#include "afk.h" +#include "dcp.h" +#include "dptxep.h" +#include "parser.h" +#include "trace.h" + +struct dcpdptx_connection_cmd { + __le32 unk; + __le32 target; +} __attribute__((packed)); + +struct dcpdptx_hotplug_cmd { + u8 _pad0[16]; + __le32 unk; +} __attribute__((packed)); + +struct dptxport_apcall_link_rate { + __le32 retcode; + u8 _unk0[12]; + __le32 link_rate; + u8 _unk1[12]; +} __attribute__((packed)); + +struct dptxport_apcall_get_support { + __le32 retcode; + u8 _unk0[12]; + __le32 supported; + u8 _unk1[12]; +} __attribute__((packed)); + +struct dptxport_apcall_max_drive_settings { + __le32 retcode; + u8 _unk0[12]; + __le32 max_drive_settings[2]; + u8 _unk1[8]; +}; + +int dptxport_validate_connection(struct apple_epic_service *service, u8 core, + u8 atc, u8 die) +{ + struct dptx_port *dptx = service->cookie; + struct dcpdptx_connection_cmd cmd, resp; + int ret; + u32 target = FIELD_PREP(DCPDPTX_REMOTE_PORT_CORE, core) | + FIELD_PREP(DCPDPTX_REMOTE_PORT_ATC, atc) | + FIELD_PREP(DCPDPTX_REMOTE_PORT_DIE, die) | + DCPDPTX_REMOTE_PORT_CONNECTED; + + trace_dptxport_validate_connection(dptx, core, atc, die); + + cmd.target = cpu_to_le32(target); + cmd.unk = cpu_to_le32(0x100); + ret = afk_service_call(service, 0, 14, &cmd, sizeof(cmd), 40, &resp, + sizeof(resp), 40); + if (ret) + return ret; + + if (le32_to_cpu(resp.target) != target) + return -EINVAL; + if (le32_to_cpu(resp.unk) != 0x100) + return -EINVAL; + + return 0; +} + +int dptxport_connect(struct apple_epic_service *service, u8 core, u8 atc, + u8 die) +{ + struct dptx_port *dptx = service->cookie; + struct dcpdptx_connection_cmd cmd, resp; + int ret; + u32 target = FIELD_PREP(DCPDPTX_REMOTE_PORT_CORE, core) | + FIELD_PREP(DCPDPTX_REMOTE_PORT_ATC, atc) | + FIELD_PREP(DCPDPTX_REMOTE_PORT_DIE, die) | + DCPDPTX_REMOTE_PORT_CONNECTED; + + trace_dptxport_connect(dptx, core, atc, die); + + cmd.target = cpu_to_le32(target); + cmd.unk = cpu_to_le32(0x100); + ret = afk_service_call(service, 0, 13, &cmd, sizeof(cmd), 24, &resp, + sizeof(resp), 24); + if (ret) + return ret; + + if (le32_to_cpu(resp.target) != target) + return -EINVAL; + if (le32_to_cpu(resp.unk) != 0x100) + return -EINVAL; + + return 0; +} + +int dptxport_request_display(struct apple_epic_service *service) +{ + return afk_service_call(service, 0, 8, NULL, 0, 16, NULL, 0, 16); +} + +int dptxport_release_display(struct apple_epic_service *service) +{ + return afk_service_call(service, 0, 9, NULL, 0, 16, NULL, 0, 16); +} + +int dptxport_set_hpd(struct apple_epic_service *service, bool hpd) +{ + struct dcpdptx_hotplug_cmd cmd, resp; + int ret; + + memset(&cmd, 0, sizeof(cmd)); + + if (hpd) + cmd.unk = cpu_to_le32(1); + + ret = afk_service_call(service, 8, 10, &cmd, sizeof(cmd), 12, &resp, + sizeof(resp), 12); + if (ret) + return ret; + if (le32_to_cpu(resp.unk) != 1) + return -EINVAL; + return 0; +} + +static int +dptxport_call_get_max_drive_settings(struct apple_epic_service *service, + void *reply_, size_t reply_size) +{ + struct dptxport_apcall_max_drive_settings *reply = reply_; + + if (reply_size < sizeof(*reply)) + return -EINVAL; + + reply->retcode = cpu_to_le32(0); + reply->max_drive_settings[0] = cpu_to_le32(0x3); + reply->max_drive_settings[1] = cpu_to_le32(0x3); + + return 0; +} + +static int dptxport_call_get_max_link_rate(struct apple_epic_service *service, + void *reply_, size_t reply_size) +{ + struct dptxport_apcall_link_rate *reply = reply_; + + if (reply_size < sizeof(*reply)) + return -EINVAL; + + reply->retcode = cpu_to_le32(0); + reply->link_rate = cpu_to_le32(LINK_RATE_HBR3); + + return 0; +} + +static int dptxport_call_get_link_rate(struct apple_epic_service *service, + void *reply_, size_t reply_size) +{ + struct dptx_port *dptx = service->cookie; + struct dptxport_apcall_link_rate *reply = reply_; + + if (reply_size < sizeof(*reply)) + return -EINVAL; + + reply->retcode = cpu_to_le32(0); + reply->link_rate = cpu_to_le32(dptx->link_rate); + + return 0; +} + +static int +dptxport_call_will_change_link_config(struct apple_epic_service *service) +{ + struct dptx_port *dptx = service->cookie; + + dptx->phy_ops.dp.set_lanes = 0; + dptx->phy_ops.dp.set_rate = 0; + dptx->phy_ops.dp.set_voltages = 0; + + return 0; +} + +static int +dptxport_call_did_change_link_config(struct apple_epic_service *service) +{ + /* assume the link config did change and wait a little bit */ + mdelay(10); + return 0; +} + +static int dptxport_call_set_link_rate(struct apple_epic_service *service, + const void *data, size_t data_size, + void *reply_, size_t reply_size) +{ + struct dptx_port *dptx = service->cookie; + const struct dptxport_apcall_link_rate *request = data; + struct dptxport_apcall_link_rate *reply = reply_; + u32 link_rate, phy_link_rate; + bool phy_set_rate = false; + int ret; + + if (reply_size < sizeof(*reply)) + return -EINVAL; + if (data_size < sizeof(*request)) + return -EINVAL; + + link_rate = le32_to_cpu(request->link_rate); + trace_dptxport_call_set_link_rate(dptx, link_rate); + + switch (link_rate) { + case LINK_RATE_RBR: + phy_link_rate = 1620; + phy_set_rate = true; + break; + case LINK_RATE_HBR: + phy_link_rate = 2700; + phy_set_rate = true; + break; + case LINK_RATE_HBR2: + phy_link_rate = 5400; + phy_set_rate = true; + break; + case LINK_RATE_HBR3: + phy_link_rate = 8100; + phy_set_rate = true; + break; + case 0: + phy_link_rate = 0; + phy_set_rate = true; + break; + default: + dev_err(service->ep->dcp->dev, + "DPTXPort: Unsupported link rate 0x%x requested\n", + link_rate); + link_rate = 0; + phy_set_rate = false; + break; + } + + if (phy_set_rate) { + dptx->phy_ops.dp.link_rate = phy_link_rate; + dptx->phy_ops.dp.set_rate = 1; + + if (dptx->atcphy) { + ret = phy_configure(dptx->atcphy, &dptx->phy_ops); + if (ret) + return ret; + } + + //if (dptx->phy_ops.dp.set_rate) + dptx->link_rate = dptx->pending_link_rate = link_rate; + + } + + //dptx->pending_link_rate = link_rate; + reply->retcode = cpu_to_le32(0); + reply->link_rate = cpu_to_le32(link_rate); + + return 0; +} + +static int dptxport_call_get_supports_hpd(struct apple_epic_service *service, + void *reply_, size_t reply_size) +{ + struct dptxport_apcall_get_support *reply = reply_; + + if (reply_size < sizeof(*reply)) + return -EINVAL; + + reply->retcode = cpu_to_le32(0); + reply->supported = cpu_to_le32(0); + return 0; +} + +static int +dptxport_call_get_supports_downspread(struct apple_epic_service *service, + void *reply_, size_t reply_size) +{ + struct dptxport_apcall_get_support *reply = reply_; + + if (reply_size < sizeof(*reply)) + return -EINVAL; + + reply->retcode = cpu_to_le32(0); + reply->supported = cpu_to_le32(0); + return 0; +} + +static int dptxport_call(struct apple_epic_service *service, u32 idx, + const void *data, size_t data_size, void *reply, + size_t reply_size) +{ + struct dptx_port *dptx = service->cookie; + trace_dptxport_apcall(dptx, idx, data_size); + + switch (idx) { + case DPTX_APCALL_WILL_CHANGE_LINKG_CONFIG: + return dptxport_call_will_change_link_config(service); + case DPTX_APCALL_DID_CHANGE_LINK_CONFIG: + return dptxport_call_did_change_link_config(service); + case DPTX_APCALL_GET_MAX_LINK_RATE: + return dptxport_call_get_max_link_rate(service, reply, + reply_size); + case DPTX_APCALL_GET_LINK_RATE: + return dptxport_call_get_link_rate(service, reply, reply_size); + case DPTX_APCALL_SET_LINK_RATE: + return dptxport_call_set_link_rate(service, data, data_size, + reply, reply_size); + case DPTX_APCALL_GET_SUPPORTS_HPD: + return dptxport_call_get_supports_hpd(service, reply, + reply_size); + case DPTX_APCALL_GET_SUPPORTS_DOWN_SPREAD: + return dptxport_call_get_supports_downspread(service, reply, + reply_size); + case DPTX_APCALL_GET_MAX_DRIVE_SETTINGS: + return dptxport_call_get_max_drive_settings(service, reply, + reply_size); + default: + /* just try to ACK and hope for the best... */ + dev_info(service->ep->dcp->dev, "DPTXPort: acking unhandled call %u\n", + idx); + fallthrough; + /* we can silently ignore and just ACK these calls */ + case DPTX_APCALL_ACTIVATE: + case DPTX_APCALL_DEACTIVATE: + case DPTX_APCALL_SET_DRIVE_SETTINGS: + case DPTX_APCALL_GET_DRIVE_SETTINGS: + memcpy(reply, data, min(reply_size, data_size)); + if (reply_size > 4) + memset(reply, 0, 4); + return 0; + } +} + +static void dptxport_init(struct apple_epic_service *service, const char *name, + const char *class, s64 unit) +{ + + if (strcmp(name, "dcpdptx-port-epic")) + return; + if (strcmp(class, "AppleDCPDPTXRemotePort")) + return; + + trace_dptxport_init(service->ep->dcp, unit); + + switch (unit) { + case 0: + case 1: + if (service->ep->dcp->dptxport[unit].enabled) { + dev_err(service->ep->dcp->dev, + "DPTXPort: unit %lld already exists\n", unit); + return; + } + service->ep->dcp->dptxport[unit].unit = unit; + service->ep->dcp->dptxport[unit].service = service; + service->ep->dcp->dptxport[unit].enabled = true; + service->cookie = (void *)&service->ep->dcp->dptxport[unit]; + complete(&service->ep->dcp->dptxport[unit].enable_completion); + break; + default: + dev_err(service->ep->dcp->dev, "DPTXPort: invalid unit %lld\n", + unit); + } +} + +static const struct apple_epic_service_ops dptxep_ops[] = { + { + .name = "AppleDCPDPTXRemotePort", + .init = dptxport_init, + .call = dptxport_call, + }, + {} +}; + +int dptxep_init(struct apple_dcp *dcp) +{ + int ret; + u32 port; + unsigned long timeout = msecs_to_jiffies(1000); + + init_completion(&dcp->dptxport[0].enable_completion); + init_completion(&dcp->dptxport[1].enable_completion); + + dcp->dptxep = afk_init(dcp, DPTX_ENDPOINT, dptxep_ops); + if (IS_ERR(dcp->dptxep)) + return PTR_ERR(dcp->dptxep); + + ret = afk_start(dcp->dptxep); + if (ret) + return ret; + + for (port = 0; port < dcp->hw.num_dptx_ports; port++) { + ret = wait_for_completion_timeout(&dcp->dptxport[port].enable_completion, + timeout); + if (!ret) + return -ETIMEDOUT; + else if (ret < 0) + return ret; + timeout = ret; + } + + return 0; +} diff --git a/drivers/gpu/drm/apple/dptxep.h b/drivers/gpu/drm/apple/dptxep.h new file mode 100644 index 00000000000000..efd1d5005f56da --- /dev/null +++ b/drivers/gpu/drm/apple/dptxep.h @@ -0,0 +1,66 @@ +#ifndef __APPLE_DCP_DPTXEP_H__ +#define __APPLE_DCP_DPTXEP_H__ + +#include +#include + +enum dptx_apcall { + DPTX_APCALL_ACTIVATE = 0, + DPTX_APCALL_DEACTIVATE = 1, + DPTX_APCALL_GET_MAX_DRIVE_SETTINGS = 2, + DPTX_APCALL_SET_DRIVE_SETTINGS = 3, + DPTX_APCALL_GET_DRIVE_SETTINGS = 4, + DPTX_APCALL_WILL_CHANGE_LINKG_CONFIG = 5, + DPTX_APCALL_DID_CHANGE_LINK_CONFIG = 6, + DPTX_APCALL_GET_MAX_LINK_RATE = 7, + DPTX_APCALL_GET_LINK_RATE = 8, + DPTX_APCALL_SET_LINK_RATE = 9, + DPTX_APCALL_GET_ACTIVE_LANE_COUNT = 10, + DPTX_APCALL_SET_ACTIVE_LANE_COUNT = 11, + DPTX_APCALL_GET_SUPPORTS_DOWN_SPREAD = 12, + DPTX_APCALL_GET_DOWN_SPREAD = 13, + DPTX_APCALL_SET_DOWN_SPREAD = 14, + DPTX_APCALL_GET_SUPPORTS_LANE_MAPPING = 15, + DPTX_APCALL_SET_LANE_MAP = 16, + DPTX_APCALL_GET_SUPPORTS_HPD = 17, + DPTX_APCALL_FORCE_HOTPLUG_DETECT = 18, + DPTX_APCALL_INACTIVE_SINK_DETECTED = 19, + DPTX_APCALL_SET_TILED_DISPLAY_HINTS = 20, + DPTX_APCALL_DEVICE_NOT_RESPONDING = 21, + DPTX_APCALL_DEVICE_BUSY_TIMEOUT = 22, + DPTX_APCALL_DEVICE_NOT_STARTED = 23, +}; + +#define DCPDPTX_REMOTE_PORT_CORE GENMASK(3, 0) +#define DCPDPTX_REMOTE_PORT_ATC GENMASK(7, 4) +#define DCPDPTX_REMOTE_PORT_DIE GENMASK(11, 8) +#define DCPDPTX_REMOTE_PORT_CONNECTED BIT(15) + +enum dptx_link_rate { + LINK_RATE_RBR = 0x06, + LINK_RATE_HBR = 0x0a, + LINK_RATE_HBR2 = 0x14, + LINK_RATE_HBR3 = 0x1e, +}; + +struct apple_epic_service; + +struct dptx_port { + bool enabled, connected; + struct completion enable_completion; + u32 unit; + struct apple_epic_service *service; + union phy_configure_opts phy_ops; + struct phy *atcphy; + struct mux_control *mux; + u32 link_rate, pending_link_rate; +}; + +int dptxport_validate_connection(struct apple_epic_service *service, u8 core, + u8 atc, u8 die); +int dptxport_connect(struct apple_epic_service *service, u8 core, u8 atc, + u8 die); +int dptxport_request_display(struct apple_epic_service *service); +int dptxport_release_display(struct apple_epic_service *service); +int dptxport_set_hpd(struct apple_epic_service *service, bool hpd); +#endif diff --git a/drivers/gpu/drm/apple/ibootep.c b/drivers/gpu/drm/apple/ibootep.c new file mode 100644 index 00000000000000..ae4bc8a69f2a8d --- /dev/null +++ b/drivers/gpu/drm/apple/ibootep.c @@ -0,0 +1,29 @@ +// SPDX-License-Identifier: GPL-2.0-only OR MIT +/* Copyright 2023 */ + +#include + +#include "afk.h" +#include "dcp.h" + +static void disp_service_init(struct apple_epic_service *service, const char *name, + const char *class, s64 unit) +{ +} + + +static const struct apple_epic_service_ops ibootep_ops[] = { + { + .name = "disp0-service", + .init = disp_service_init, + }, + {} +}; + +int ibootep_init(struct apple_dcp *dcp) +{ + dcp->ibootep = afk_init(dcp, DISP0_ENDPOINT, ibootep_ops); + afk_start(dcp->ibootep); + + return 0; +} diff --git a/drivers/gpu/drm/apple/parser.c b/drivers/gpu/drm/apple/parser.c index dde87b5d4052d5..a837af1bd0a5e8 100644 --- a/drivers/gpu/drm/apple/parser.c +++ b/drivers/gpu/drm/apple/parser.c @@ -270,11 +270,6 @@ int parse(void *blob, size_t size, struct dcp_parse_ctx *ctx) return 0; } -struct dimension { - s64 total, front_porch, sync_width, active; - s64 precise_sync_rate; -}; - static int parse_dimension(struct dcp_parse_ctx *handle, struct dimension *dim) { struct iterator it; @@ -445,10 +440,14 @@ static int parse_mode(struct dcp_parse_ctx *handle, if (!IS_ERR_OR_NULL(key)) kfree(key); - if (ret) + if (ret) { + trace_iomfb_parse_mode_fail(id, &horiz, &vert, best_color_mode, is_virtual, *score); return ret; + } } + trace_iomfb_parse_mode_success(id, &horiz, &vert, best_color_mode, is_virtual, *score); + /* * Reject modes without valid color mode. */ diff --git a/drivers/gpu/drm/apple/parser.h b/drivers/gpu/drm/apple/parser.h index 6d8717873cdd6c..f9cc3718fa84f7 100644 --- a/drivers/gpu/drm/apple/parser.h +++ b/drivers/gpu/drm/apple/parser.h @@ -25,6 +25,11 @@ struct dcp_display_mode { u32 timing_mode_id; }; +struct dimension { + s64 total, front_porch, sync_width, active; + s64 precise_sync_rate; +}; + int parse(void *blob, size_t size, struct dcp_parse_ctx *ctx); struct dcp_display_mode *enumerate_modes(struct dcp_parse_ctx *handle, unsigned int *count, int width_mm, diff --git a/drivers/gpu/drm/apple/systemep.c b/drivers/gpu/drm/apple/systemep.c new file mode 100644 index 00000000000000..5383a83f1e6c28 --- /dev/null +++ b/drivers/gpu/drm/apple/systemep.c @@ -0,0 +1,100 @@ +// SPDX-License-Identifier: GPL-2.0-only OR MIT +/* Copyright 2022 Sven Peter */ + +#include + +#include "afk.h" +#include "dcp.h" + +static bool enable_verbose_logging; +module_param(enable_verbose_logging, bool, 0644); +MODULE_PARM_DESC(enable_verbose_logging, "Enable DCP firmware verbose logging"); + +/* + * Serialized setProperty("gAFKConfigLogMask", 0xffff) IPC call which + * will set the DCP firmware log level to the most verbose setting + */ +#define SYSTEM_SET_PROPERTY 0x43 +static const u8 setprop_gAFKConfigLogMask_ffff[] = { + 0x14, 0x00, 0x00, 0x00, 0x67, 0x41, 0x46, 0x4b, 0x43, 0x6f, + 0x6e, 0x66, 0x69, 0x67, 0x4c, 0x6f, 0x67, 0x4d, 0x61, 0x73, + 0x6b, 0x00, 0x00, 0x00, 0xd3, 0x00, 0x00, 0x00, 0x40, 0x00, + 0x00, 0x84, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 +}; + +struct systemep_work { + struct apple_epic_service *service; + struct work_struct work; +}; + +static void system_log_work(struct work_struct *work_) +{ + struct systemep_work *work = + container_of(work_, struct systemep_work, work); + + afk_send_command(work->service, SYSTEM_SET_PROPERTY, + setprop_gAFKConfigLogMask_ffff, + sizeof(setprop_gAFKConfigLogMask_ffff), NULL, + sizeof(setprop_gAFKConfigLogMask_ffff), NULL); + complete(&work->service->ep->dcp->systemep_done); + kfree(work); +} + +static void system_init(struct apple_epic_service *service, const char *name, + const char *class, s64 unit) +{ + struct systemep_work *work; + + if (!enable_verbose_logging) + return; + + /* + * We're called from the service message handler thread and can't + * dispatch blocking message from there. + */ + work = kzalloc(sizeof(*work), GFP_KERNEL); + if (!work) + return; + + work->service = service; + INIT_WORK(&work->work, system_log_work); + schedule_work(&work->work); +} + +static void powerlog_init(struct apple_epic_service *service, const char *name, + const char *class, s64 unit) +{ +} + +static const struct apple_epic_service_ops systemep_ops[] = { + { + .name = "system", + .init = system_init, + }, + { + .name = "powerlog-service", + .init = powerlog_init, + }, + {} +}; + +int systemep_init(struct apple_dcp *dcp) +{ + init_completion(&dcp->systemep_done); + + dcp->systemep = afk_init(dcp, SYSTEM_ENDPOINT, systemep_ops); + afk_start(dcp->systemep); + + if (!enable_verbose_logging) + return 0; + + /* + * Timeouts aren't really fatal here: in the worst case we just weren't + * able to enable additional debug prints inside DCP + */ + if (!wait_for_completion_timeout(&dcp->systemep_done, + msecs_to_jiffies(MSEC_PER_SEC))) + dev_err(dcp->dev, "systemep: couldn't enable verbose logs\n"); + + return 0; +} diff --git a/drivers/gpu/drm/apple/trace.h b/drivers/gpu/drm/apple/trace.h index 6b3d9886a4164e..6edc9f1d5db919 100644 --- a/drivers/gpu/drm/apple/trace.h +++ b/drivers/gpu/drm/apple/trace.h @@ -8,6 +8,7 @@ #define _TRACE_DCP_H #include "afk.h" +#include "dptxep.h" #include "dcp-internal.h" #include "parser.h" @@ -36,6 +37,43 @@ { EPIC_CAT_REPLY, "reply" }, \ { EPIC_CAT_COMMAND, "command" }) +#define show_dptxport_apcall(idx) \ + __print_symbolic( \ + idx, { DPTX_APCALL_ACTIVATE, "activate" }, \ + { DPTX_APCALL_DEACTIVATE, "deactivate" }, \ + { DPTX_APCALL_GET_MAX_DRIVE_SETTINGS, \ + "get_max_drive_settings" }, \ + { DPTX_APCALL_SET_DRIVE_SETTINGS, "set_drive_settings" }, \ + { DPTX_APCALL_GET_DRIVE_SETTINGS, "get_drive_settings" }, \ + { DPTX_APCALL_WILL_CHANGE_LINKG_CONFIG, \ + "will_change_link_config" }, \ + { DPTX_APCALL_DID_CHANGE_LINK_CONFIG, \ + "did_change_link_config" }, \ + { DPTX_APCALL_GET_MAX_LINK_RATE, "get_max_link_rate" }, \ + { DPTX_APCALL_GET_LINK_RATE, "get_link_rate" }, \ + { DPTX_APCALL_SET_LINK_RATE, "set_link_rate" }, \ + { DPTX_APCALL_GET_ACTIVE_LANE_COUNT, \ + "get_active_lane_count" }, \ + { DPTX_APCALL_SET_ACTIVE_LANE_COUNT, \ + "set_active_lane_count" }, \ + { DPTX_APCALL_GET_SUPPORTS_DOWN_SPREAD, \ + "get_supports_downspread" }, \ + { DPTX_APCALL_GET_DOWN_SPREAD, "get_downspread" }, \ + { DPTX_APCALL_SET_DOWN_SPREAD, "set_downspread" }, \ + { DPTX_APCALL_GET_SUPPORTS_LANE_MAPPING, \ + "get_supports_lane_mapping" }, \ + { DPTX_APCALL_SET_LANE_MAP, "set_lane_map" }, \ + { DPTX_APCALL_GET_SUPPORTS_HPD, "get_supports_hpd" }, \ + { DPTX_APCALL_FORCE_HOTPLUG_DETECT, "force_hotplug_detect" }, \ + { DPTX_APCALL_INACTIVE_SINK_DETECTED, \ + "inactive_sink_detected" }, \ + { DPTX_APCALL_SET_TILED_DISPLAY_HINTS, \ + "set_tiled_display_hints" }, \ + { DPTX_APCALL_DEVICE_NOT_RESPONDING, \ + "device_not_responding" }, \ + { DPTX_APCALL_DEVICE_BUSY_TIMEOUT, "device_busy_timeout" }, \ + { DPTX_APCALL_DEVICE_NOT_STARTED, "device_not_started" }) + TRACE_EVENT(dcp_recv_msg, TP_PROTO(struct apple_dcp *dcp, u8 endpoint, u64 message), TP_ARGS(dcp, endpoint, message), @@ -263,6 +301,108 @@ TRACE_EVENT(iomfb_swap_complete_intent_gated, ) ); +DECLARE_EVENT_CLASS(iomfb_parse_mode_template, + TP_PROTO(s64 id, struct dimension *horiz, struct dimension *vert, s64 best_color_mode, bool is_virtual, s64 score), + TP_ARGS(id, horiz, vert, best_color_mode, is_virtual, score), + + TP_STRUCT__entry(__field(s64, id) + __field_struct(struct dimension, horiz) + __field_struct(struct dimension, vert) + __field(s64, best_color_mode) + __field(bool, is_virtual) + __field(s64, score)), + + TP_fast_assign(__entry->id = id; + __entry->horiz = *horiz; + __entry->vert = *vert; + __entry->best_color_mode = best_color_mode; + __entry->is_virtual = is_virtual; + __entry->score = score;), + + TP_printk("id: %lld, best_color_mode: %lld, resolution:%lldx%lld virtual: %d, score: %lld", + __entry->id, __entry->best_color_mode, + __entry->horiz.active, __entry->vert.active, + __entry->is_virtual, __entry->score)); + +DEFINE_EVENT(iomfb_parse_mode_template, iomfb_parse_mode_success, + TP_PROTO(s64 id, struct dimension *horiz, struct dimension *vert, s64 best_color_mode, bool is_virtual, s64 score), + TP_ARGS(id, horiz, vert, best_color_mode, is_virtual, score)); + +DEFINE_EVENT(iomfb_parse_mode_template, iomfb_parse_mode_fail, + TP_PROTO(s64 id, struct dimension *horiz, struct dimension *vert, s64 best_color_mode, bool is_virtual, s64 score), + TP_ARGS(id, horiz, vert, best_color_mode, is_virtual, score)); + +TRACE_EVENT(dptxport_init, TP_PROTO(struct apple_dcp *dcp, u64 unit), + TP_ARGS(dcp, unit), + + TP_STRUCT__entry(__string(devname, dev_name(dcp->dev)) + __field(u64, unit)), + + TP_fast_assign(__assign_str(devname); + __entry->unit = unit;), + + TP_printk("%s: dptxport unit %lld initialized", __get_str(devname), + __entry->unit)); + +TRACE_EVENT( + dptxport_apcall, + TP_PROTO(struct dptx_port *dptx, int idx, size_t len), + TP_ARGS(dptx, idx, len), + + TP_STRUCT__entry(__string(devname, dev_name(dptx->service->ep->dcp->dev)) + __field(u32, unit) __field(int, idx) __field(size_t, len)), + + TP_fast_assign(__assign_str(devname); + __entry->unit = dptx->unit; __entry->idx = idx; __entry->len = len;), + + TP_printk("%s: dptx%d: AP Call %d (%s) with len %lu", __get_str(devname), + __entry->unit, + __entry->idx, show_dptxport_apcall(__entry->idx), __entry->len)); + +TRACE_EVENT( + dptxport_validate_connection, + TP_PROTO(struct dptx_port *dptx, u8 core, u8 atc, u8 die), + TP_ARGS(dptx, core, atc, die), + + TP_STRUCT__entry(__string(devname, dev_name(dptx->service->ep->dcp->dev)) + __field(u32, unit) __field(u8, core) __field(u8, atc) __field(u8, die)), + + TP_fast_assign(__assign_str(devname); + __entry->unit = dptx->unit; __entry->core = core; __entry->atc = atc; __entry->die = die;), + + TP_printk("%s: dptx%d: core %d, atc %d, die %d", __get_str(devname), + __entry->unit, __entry->core, __entry->atc, __entry->die)); + +TRACE_EVENT( + dptxport_connect, + TP_PROTO(struct dptx_port *dptx, u8 core, u8 atc, u8 die), + TP_ARGS(dptx, core, atc, die), + + TP_STRUCT__entry(__string(devname, dev_name(dptx->service->ep->dcp->dev)) + __field(u32, unit) __field(u8, core) __field(u8, atc) __field(u8, die)), + + TP_fast_assign(__assign_str(devname); + __entry->unit = dptx->unit; __entry->core = core; __entry->atc = atc; __entry->die = die;), + + TP_printk("%s: dptx%d: core %d, atc %d, die %d", __get_str(devname), + __entry->unit, __entry->core, __entry->atc, __entry->die)); + +TRACE_EVENT( + dptxport_call_set_link_rate, + TP_PROTO(struct dptx_port *dptx, u32 link_rate), + TP_ARGS(dptx, link_rate), + + TP_STRUCT__entry(__string(devname, dev_name(dptx->service->ep->dcp->dev)) + __field(u32, unit) + __field(u32, link_rate)), + + TP_fast_assign(__assign_str(devname); + __entry->unit = dptx->unit; + __entry->link_rate = link_rate;), + + TP_printk("%s: dptx%d: link rate 0x%x", __get_str(devname), __entry->unit, + __entry->link_rate)); + TRACE_EVENT(iomfb_brightness, TP_PROTO(struct apple_dcp *dcp, u32 nits), TP_ARGS(dcp, nits), From 6786c79c14d0e266893d07ffe862f266ba35433d Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Sun, 12 Nov 2023 10:06:45 +0100 Subject: [PATCH 1110/3902] drm: apple: Move offsets for rt_bandwidth callback to DT The offsets differ for every DCP instance. Instead of hardcoding offsets for each SoC family offsets and calculate the instance offset move everything to the device tree. This helps multi die SoCs since there is and unexpected offset between both dies. On multi die SoCs device tree changes were necessary to avoid translating the PMGR reg via the seconds die "ranges" property. Signed-off-by: Janne Grunau --- drivers/gpu/drm/apple/dcp-internal.h | 8 ++ drivers/gpu/drm/apple/dcp.c | 122 ++++++++++++++++++++++++- drivers/gpu/drm/apple/iomfb_template.c | 51 +++++------ 3 files changed, 151 insertions(+), 30 deletions(-) diff --git a/drivers/gpu/drm/apple/dcp-internal.h b/drivers/gpu/drm/apple/dcp-internal.h index 849bd2937ebb9d..7fe2c3f98aa377 100644 --- a/drivers/gpu/drm/apple/dcp-internal.h +++ b/drivers/gpu/drm/apple/dcp-internal.h @@ -6,6 +6,7 @@ #include #include +#include #include #include #include @@ -136,6 +137,13 @@ struct apple_dcp { struct resource *disp_registers[MAX_DISP_REGISTERS]; unsigned int nr_disp_registers; + struct resource disp_bw_scratch_res; + struct resource disp_bw_doorbell_res; + u32 disp_bw_scratch_index; + u32 disp_bw_scratch_offset; + u32 disp_bw_doorbell_index; + u32 disp_bw_doorbell_offset; + u32 index; /* Bitmap of memory descriptors used for mappings made by the DCP */ diff --git a/drivers/gpu/drm/apple/dcp.c b/drivers/gpu/drm/apple/dcp.c index 02d19c248ab106..eb1c4cbae40b16 100644 --- a/drivers/gpu/drm/apple/dcp.c +++ b/drivers/gpu/drm/apple/dcp.c @@ -13,6 +13,7 @@ #include #include #include +#include #include #include #include @@ -475,11 +476,108 @@ static int dcp_create_piodma_iommu_dev(struct apple_dcp *dcp) return ret; } +static int dcp_get_bw_scratch_reg(struct apple_dcp *dcp, u32 expected) +{ + struct of_phandle_args ph_args; + u32 addr_idx, disp_idx, offset; + int ret; + + ret = of_parse_phandle_with_args(dcp->dev->of_node, "apple,bw-scratch", + "#apple,bw-scratch-cells", 0, &ph_args); + if (ret < 0) { + dev_err(dcp->dev, "Failed to read 'apple,bw-scratch': %d\n", ret); + return ret; + } + + if (ph_args.args_count != 3) { + dev_err(dcp->dev, "Unexpected 'apple,bw-scratch' arg count %d\n", + ph_args.args_count); + ret = -EINVAL; + goto err_of_node_put; + } + + addr_idx = ph_args.args[0]; + disp_idx = ph_args.args[1]; + offset = ph_args.args[2]; + + if (disp_idx != expected || disp_idx >= MAX_DISP_REGISTERS) { + dev_err(dcp->dev, "Unexpected disp_reg value in 'apple,bw-scratch': %d\n", + disp_idx); + ret = -EINVAL; + goto err_of_node_put; + } + + ret = of_address_to_resource(ph_args.np, addr_idx, &dcp->disp_bw_scratch_res); + if (ret < 0) { + dev_err(dcp->dev, "Failed to get 'apple,bw-scratch' resource %d from %pOF\n", + addr_idx, ph_args.np); + goto err_of_node_put; + } + if (offset > resource_size(&dcp->disp_bw_scratch_res) - 4) { + ret = -EINVAL; + goto err_of_node_put; + } + + dcp->disp_registers[disp_idx] = &dcp->disp_bw_scratch_res; + dcp->disp_bw_scratch_index = disp_idx; + dcp->disp_bw_scratch_offset = offset; + ret = 0; + +err_of_node_put: + of_node_put(ph_args.np); + return ret; +} + +static int dcp_get_bw_doorbell_reg(struct apple_dcp *dcp, u32 expected) +{ + struct of_phandle_args ph_args; + u32 addr_idx, disp_idx; + int ret; + + ret = of_parse_phandle_with_args(dcp->dev->of_node, "apple,bw-doorbell", + "#apple,bw-doorbell-cells", 0, &ph_args); + if (ret < 0) { + dev_err(dcp->dev, "Failed to read 'apple,bw-doorbell': %d\n", ret); + return ret; + } + + if (ph_args.args_count != 2) { + dev_err(dcp->dev, "Unexpected 'apple,bw-doorbell' arg count %d\n", + ph_args.args_count); + ret = -EINVAL; + goto err_of_node_put; + } + + addr_idx = ph_args.args[0]; + disp_idx = ph_args.args[1]; + + if (disp_idx != expected || disp_idx >= MAX_DISP_REGISTERS) { + dev_err(dcp->dev, "Unexpected disp_reg value in 'apple,bw-doorbell': %d\n", + disp_idx); + ret = -EINVAL; + goto err_of_node_put; + } + + ret = of_address_to_resource(ph_args.np, addr_idx, &dcp->disp_bw_doorbell_res); + if (ret < 0) { + dev_err(dcp->dev, "Failed to get 'apple,bw-doorbell' resource %d from %pOF\n", + addr_idx, ph_args.np); + goto err_of_node_put; + } + dcp->disp_bw_doorbell_index = disp_idx; + dcp->disp_registers[disp_idx] = &dcp->disp_bw_doorbell_res; + ret = 0; + +err_of_node_put: + of_node_put(ph_args.np); + return ret; +} + static int dcp_get_disp_regs(struct apple_dcp *dcp) { struct platform_device *pdev = to_platform_device(dcp->dev); int count = pdev->num_resources - 1; - int i; + int i, ret; if (count <= 0 || count > MAX_DISP_REGISTERS) return -EINVAL; @@ -489,6 +587,20 @@ static int dcp_get_disp_regs(struct apple_dcp *dcp) platform_get_resource(pdev, IORESOURCE_MEM, 1 + i); } + /* load pmgr bandwidth scratch resource and offset */ + ret = dcp_get_bw_scratch_reg(dcp, count); + if (ret < 0) + return ret; + count += 1; + + /* load pmgr bandwidth doorbell resource if present (only on t8103) */ + if (of_property_present(dcp->dev->of_node, "apple,bw-doorbell")) { + ret = dcp_get_bw_doorbell_reg(dcp, count); + if (ret < 0) + return ret; + count += 1; + } + dcp->nr_disp_registers = count; return 0; } @@ -727,6 +839,14 @@ static int dcp_platform_probe(struct platform_device *pdev) if (fw_compat == DCP_FIRMWARE_UNKNOWN) return -ENODEV; + /* Check for "apple,bw-scratch" to avoid probing appledrm with outdated + * device trees. This prevents replacing simpledrm and ending up without + * display. + */ + if (!of_property_present(dev->of_node, "apple,bw-scratch")) + return dev_err_probe(dev, -ENODEV, "Incompatible devicetree! " + "Use devicetree matching this kernel.\n"); + dcp = devm_kzalloc(dev, sizeof(*dcp), GFP_KERNEL); if (!dcp) return -ENOMEM; diff --git a/drivers/gpu/drm/apple/iomfb_template.c b/drivers/gpu/drm/apple/iomfb_template.c index 64fe703061fe7d..3590bcffbdaacb 100644 --- a/drivers/gpu/drm/apple/iomfb_template.c +++ b/drivers/gpu/drm/apple/iomfb_template.c @@ -33,11 +33,7 @@ #include "version_utils.h" /* Register defines used in bandwidth setup structure */ -#define REG_SCRATCH (0x14) -#define REG_SCRATCH_T600X (0x988) -#define REG_SCRATCH_T602X (0x1208) -#define REG_DOORBELL (0x0) -#define REG_DOORBELL_BIT (2) +#define REG_DOORBELL_BIT(idx) (2 + (idx)) struct dcp_wait_cookie { struct kref refcount; @@ -665,34 +661,31 @@ static struct dcp_allocate_bandwidth_resp dcpep_cb_allocate_bandwidth(struct app static struct dcp_rt_bandwidth dcpep_cb_rt_bandwidth(struct apple_dcp *dcp) { - if (dcp->disp_registers[5] && dcp->disp_registers[6]) { - return (struct dcp_rt_bandwidth){ - .reg_scratch = - dcp->disp_registers[5]->start + REG_SCRATCH, - .reg_doorbell = - dcp->disp_registers[6]->start + REG_DOORBELL, - .doorbell_bit = REG_DOORBELL_BIT, - - .padding[3] = 0x4, // XXX: required by 11.x firmware - }; - } else if (dcp->disp_registers[4]) { - u32 offset = REG_SCRATCH_T600X; - if (of_device_is_compatible(dcp->dev->of_node, "apple,t6020-dcp")) - offset = REG_SCRATCH_T602X; - - return (struct dcp_rt_bandwidth){ - .reg_scratch = dcp->disp_registers[4]->start + - offset, - .reg_doorbell = 0, - .doorbell_bit = 0, - }; - } else { - return (struct dcp_rt_bandwidth){ + struct dcp_rt_bandwidth rt_bw = (struct dcp_rt_bandwidth){ .reg_scratch = 0, .reg_doorbell = 0, .doorbell_bit = 0, - }; + }; + + if (dcp->disp_bw_scratch_index) { + u32 offset = dcp->disp_bw_scratch_offset; + u32 index = dcp->disp_bw_scratch_index; + rt_bw.reg_scratch = dcp->disp_registers[index]->start + offset; } + + if (dcp->disp_bw_doorbell_index) { + u32 index = dcp->disp_bw_doorbell_index; + rt_bw.reg_doorbell = dcp->disp_registers[index]->start; + rt_bw.doorbell_bit = REG_DOORBELL_BIT(dcp->index); + /* + * This is most certainly not padding. t8103-dcp crashes without + * setting this immediately during modeset on 12.3 and 13.5 + * firmware. + */ + rt_bw.padding[3] = 0x4; + } + + return rt_bw; } static struct dcp_set_frame_sync_props_resp From 4f8155cbf4288c1229047c578daa47801c2db1cd Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Thu, 17 Aug 2023 23:52:39 +0200 Subject: [PATCH 1111/3902] drm: apple: iomfb: Do not match/create PMU service for dcpext Signed-off-by: Janne Grunau --- drivers/gpu/drm/apple/dcp-internal.h | 3 +++ drivers/gpu/drm/apple/dcp.c | 2 ++ drivers/gpu/drm/apple/iomfb_template.c | 16 ++++++++++++++++ drivers/gpu/drm/apple/iomfb_v12_3.c | 2 +- drivers/gpu/drm/apple/iomfb_v13_3.c | 2 +- 5 files changed, 23 insertions(+), 2 deletions(-) diff --git a/drivers/gpu/drm/apple/dcp-internal.h b/drivers/gpu/drm/apple/dcp-internal.h index 7fe2c3f98aa377..64025d23282d1c 100644 --- a/drivers/gpu/drm/apple/dcp-internal.h +++ b/drivers/gpu/drm/apple/dcp-internal.h @@ -183,6 +183,9 @@ struct apple_dcp { /* clear all surfaces on init */ bool surfaces_cleared; + /* is dcpext / requires dptx */ + bool is_dptx; + /* Modes valid for the connected display */ struct dcp_display_mode *modes; unsigned int nr_modes; diff --git a/drivers/gpu/drm/apple/dcp.c b/drivers/gpu/drm/apple/dcp.c index eb1c4cbae40b16..62dc1340bcb734 100644 --- a/drivers/gpu/drm/apple/dcp.c +++ b/drivers/gpu/drm/apple/dcp.c @@ -700,6 +700,8 @@ static int dcp_comp_bind(struct device *dev, struct device *main, void *data) if (IS_ERR(dcp->coproc_reg)) return PTR_ERR(dcp->coproc_reg); + dcp->is_dptx = dcp->phy != NULL; + of_property_read_u32(dev->of_node, "apple,dcp-index", &dcp->index); of_property_read_u32(dev->of_node, "apple,dptx-phy", diff --git a/drivers/gpu/drm/apple/iomfb_template.c b/drivers/gpu/drm/apple/iomfb_template.c index 3590bcffbdaacb..945e972a7c4679 100644 --- a/drivers/gpu/drm/apple/iomfb_template.c +++ b/drivers/gpu/drm/apple/iomfb_template.c @@ -135,6 +135,10 @@ static void complete_vi_set_temperature_hint(struct apple_dcp *dcp, void *out, v static bool iomfbep_cb_match_pmu_service(struct apple_dcp *dcp, int tag, void *out, void *in) { trace_iomfb_callback(dcp, tag, __func__); + + if (dcp->is_dptx) + return true; + iomfb_a358_vi_set_temperature_hint(dcp, false, complete_vi_set_temperature_hint, NULL); @@ -158,6 +162,12 @@ static bool iomfbep_cb_match_pmu_service_2(struct apple_dcp *dcp, int tag, void { trace_iomfb_callback(dcp, tag, __func__); + if (dcp->is_dptx) { + u8 *ret = out; + ret[0] = 1; + return true; + } + iomfb_a131_pmu_service_matched(dcp, false, complete_pmu_service_matched, out); @@ -1044,6 +1054,11 @@ dcpep_cb_get_tiling_state(struct apple_dcp *dcp, }; } +static u8 dcpep_cb_create_pmu_service(struct apple_dcp *dcp) +{ + return !dcp->is_dptx; +} + static u8 dcpep_cb_create_backlight_service(struct apple_dcp *dcp) { return dcp_has_panel(dcp); @@ -1101,6 +1116,7 @@ TRAMPOLINE_IN(trampoline_pr_publish, iomfb_cb_pr_publish, struct iomfb_property); TRAMPOLINE_INOUT(trampoline_get_tiling_state, dcpep_cb_get_tiling_state, struct dcpep_get_tiling_state_req, struct dcpep_get_tiling_state_resp); +TRAMPOLINE_OUT(trampoline_create_pmu_service, dcpep_cb_create_pmu_service, u8); TRAMPOLINE_OUT(trampoline_create_backlight_service, dcpep_cb_create_backlight_service, u8); /* diff --git a/drivers/gpu/drm/apple/iomfb_v12_3.c b/drivers/gpu/drm/apple/iomfb_v12_3.c index abcd1e4aab3ff8..8b4d87ad9012bd 100644 --- a/drivers/gpu/drm/apple/iomfb_v12_3.c +++ b/drivers/gpu/drm/apple/iomfb_v12_3.c @@ -48,7 +48,7 @@ static const iomfb_cb_handler cb_handlers[IOMFB_MAX_CB] = { [106] = trampoline_nop, /* remove_property */ [107] = trampoline_true, /* create_provider_service */ [108] = trampoline_true, /* create_product_service */ - [109] = trampoline_true, /* create_pmu_service */ + [109] = trampoline_create_pmu_service, [110] = trampoline_true, /* create_iomfb_service */ [111] = trampoline_create_backlight_service, [116] = dcpep_cb_boot_1, diff --git a/drivers/gpu/drm/apple/iomfb_v13_3.c b/drivers/gpu/drm/apple/iomfb_v13_3.c index 9c692ba3c81b92..0689c0a593f784 100644 --- a/drivers/gpu/drm/apple/iomfb_v13_3.c +++ b/drivers/gpu/drm/apple/iomfb_v13_3.c @@ -50,7 +50,7 @@ static const iomfb_cb_handler cb_handlers[IOMFB_MAX_CB] = { [107] = trampoline_nop, /* remove_property */ [108] = trampoline_true, /* create_provider_service */ [109] = trampoline_true, /* create_product_service */ - [110] = trampoline_true, /* create_pmu_service */ + [110] = trampoline_create_pmu_service, [111] = trampoline_true, /* create_iomfb_service */ [112] = trampoline_create_backlight_service, [113] = trampoline_true, /* create_nvram_servce? */ From 13621eb9a28e8cdf65dac50abfbbeade8d7e5d9b Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Sun, 9 Apr 2023 22:44:35 +0200 Subject: [PATCH 1112/3902] drm: apple: afk: Adapt to macOS 13.3 firmware Signed-off-by: Janne Grunau --- drivers/gpu/drm/apple/afk.c | 9 ++++++--- drivers/gpu/drm/apple/afk.h | 2 ++ 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/drivers/gpu/drm/apple/afk.c b/drivers/gpu/drm/apple/afk.c index d577f4ec055b03..f1e8bdfcc319a2 100644 --- a/drivers/gpu/drm/apple/afk.c +++ b/drivers/gpu/drm/apple/afk.c @@ -495,7 +495,7 @@ static void afk_recv_handle(struct apple_dcp_afkep *ep, u32 channel, u32 type, service = afk_epic_find_service(ep, channel); if (!service) { - if (type != EPIC_TYPE_NOTIFY) { + if (type != EPIC_TYPE_NOTIFY && type != EPIC_TYPE_REPLY) { dev_err(ep->dcp->dev, "AFK[ep:%02x]: expected notify but got 0x%x on channel %d\n", ep->endpoint, type, channel); @@ -807,12 +807,15 @@ int afk_send_epic(struct apple_dcp_afkep *ep, u32 channel, u16 tag, eshdr = ep->txbfr.buf + wptr; memset(eshdr, 0, sizeof(*eshdr)); eshdr->length = cpu_to_le32(payload_len); - eshdr->version = 3; + eshdr->version = 4; eshdr->category = ecat; eshdr->type = cpu_to_le16(stype); eshdr->timestamp = cpu_to_le64(0); eshdr->tag = cpu_to_le16(tag); - eshdr->inline_len = cpu_to_le16(0); + if (ecat == EPIC_CAT_REPLY) + eshdr->inline_len = cpu_to_le16(payload_len - 4); + else + eshdr->inline_len = cpu_to_le16(0); wptr += sizeof(*eshdr); memcpy(ep->txbfr.buf + wptr, payload, payload_len); diff --git a/drivers/gpu/drm/apple/afk.h b/drivers/gpu/drm/apple/afk.h index fe4ed35159ace0..1fdb4100352b25 100644 --- a/drivers/gpu/drm/apple/afk.h +++ b/drivers/gpu/drm/apple/afk.h @@ -106,6 +106,8 @@ struct epic_cmd { __le64 txbuf; __le32 rxlen; __le32 txlen; + u8 rxcookie; + u8 txcookie; } __attribute__((packed)); struct epic_service_call { From 435f8c677db235f525e0769c421734f3c80814ff Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Fri, 28 Apr 2023 22:24:59 +0200 Subject: [PATCH 1113/3902] drm: apple: dptx: Port APCALL to macOS 13.3 firmware The 13.3 firmware has an additional get_max_lane_count call inserted with ID 10. Signed-off-by: Janne Grunau --- drivers/gpu/drm/apple/dptxep.c | 23 +++++++++++++++++++++++ drivers/gpu/drm/apple/dptxep.h | 29 +++++++++++++++-------------- drivers/gpu/drm/apple/trace.h | 2 ++ 3 files changed, 40 insertions(+), 14 deletions(-) diff --git a/drivers/gpu/drm/apple/dptxep.c b/drivers/gpu/drm/apple/dptxep.c index 2002f540d0e729..7179cc35991d3d 100644 --- a/drivers/gpu/drm/apple/dptxep.c +++ b/drivers/gpu/drm/apple/dptxep.c @@ -29,6 +29,13 @@ struct dptxport_apcall_link_rate { u8 _unk1[12]; } __attribute__((packed)); +struct dptxport_apcall_lane_count { + __le32 retcode; + u8 _unk0[12]; + __le64 lane_count; + u8 _unk1[8]; +} __attribute__((packed)); + struct dptxport_apcall_get_support { __le32 retcode; u8 _unk0[12]; @@ -158,6 +165,20 @@ static int dptxport_call_get_max_link_rate(struct apple_epic_service *service, return 0; } +static int dptxport_call_get_max_lane_count(struct apple_epic_service *service, + void *reply_, size_t reply_size) +{ + struct dptxport_apcall_lane_count *reply = reply_; + + if (reply_size < sizeof(*reply)) + return -EINVAL; + + reply->retcode = cpu_to_le32(0); + reply->lane_count = cpu_to_le64(4); + + return 0; +} + static int dptxport_call_get_link_rate(struct apple_epic_service *service, void *reply_, size_t reply_size) { @@ -311,6 +332,8 @@ static int dptxport_call(struct apple_epic_service *service, u32 idx, case DPTX_APCALL_SET_LINK_RATE: return dptxport_call_set_link_rate(service, data, data_size, reply, reply_size); + case DPTX_APCALL_GET_MAX_LANE_COUNT: + return dptxport_call_get_max_lane_count(service, reply, reply_size); case DPTX_APCALL_GET_SUPPORTS_HPD: return dptxport_call_get_supports_hpd(service, reply, reply_size); diff --git a/drivers/gpu/drm/apple/dptxep.h b/drivers/gpu/drm/apple/dptxep.h index efd1d5005f56da..8f0483e7030b7a 100644 --- a/drivers/gpu/drm/apple/dptxep.h +++ b/drivers/gpu/drm/apple/dptxep.h @@ -15,20 +15,21 @@ enum dptx_apcall { DPTX_APCALL_GET_MAX_LINK_RATE = 7, DPTX_APCALL_GET_LINK_RATE = 8, DPTX_APCALL_SET_LINK_RATE = 9, - DPTX_APCALL_GET_ACTIVE_LANE_COUNT = 10, - DPTX_APCALL_SET_ACTIVE_LANE_COUNT = 11, - DPTX_APCALL_GET_SUPPORTS_DOWN_SPREAD = 12, - DPTX_APCALL_GET_DOWN_SPREAD = 13, - DPTX_APCALL_SET_DOWN_SPREAD = 14, - DPTX_APCALL_GET_SUPPORTS_LANE_MAPPING = 15, - DPTX_APCALL_SET_LANE_MAP = 16, - DPTX_APCALL_GET_SUPPORTS_HPD = 17, - DPTX_APCALL_FORCE_HOTPLUG_DETECT = 18, - DPTX_APCALL_INACTIVE_SINK_DETECTED = 19, - DPTX_APCALL_SET_TILED_DISPLAY_HINTS = 20, - DPTX_APCALL_DEVICE_NOT_RESPONDING = 21, - DPTX_APCALL_DEVICE_BUSY_TIMEOUT = 22, - DPTX_APCALL_DEVICE_NOT_STARTED = 23, + DPTX_APCALL_GET_MAX_LANE_COUNT = 10, + DPTX_APCALL_GET_ACTIVE_LANE_COUNT = 11, + DPTX_APCALL_SET_ACTIVE_LANE_COUNT = 12, + DPTX_APCALL_GET_SUPPORTS_DOWN_SPREAD = 13, + DPTX_APCALL_GET_DOWN_SPREAD = 14, + DPTX_APCALL_SET_DOWN_SPREAD = 15, + DPTX_APCALL_GET_SUPPORTS_LANE_MAPPING = 16, + DPTX_APCALL_SET_LANE_MAP = 17, + DPTX_APCALL_GET_SUPPORTS_HPD = 18, + DPTX_APCALL_FORCE_HOTPLUG_DETECT = 19, + DPTX_APCALL_INACTIVE_SINK_DETECTED = 20, + DPTX_APCALL_SET_TILED_DISPLAY_HINTS = 21, + DPTX_APCALL_DEVICE_NOT_RESPONDING = 22, + DPTX_APCALL_DEVICE_BUSY_TIMEOUT = 23, + DPTX_APCALL_DEVICE_NOT_STARTED = 24, }; #define DCPDPTX_REMOTE_PORT_CORE GENMASK(3, 0) diff --git a/drivers/gpu/drm/apple/trace.h b/drivers/gpu/drm/apple/trace.h index 6edc9f1d5db919..814bc7f0864475 100644 --- a/drivers/gpu/drm/apple/trace.h +++ b/drivers/gpu/drm/apple/trace.h @@ -52,6 +52,8 @@ { DPTX_APCALL_GET_MAX_LINK_RATE, "get_max_link_rate" }, \ { DPTX_APCALL_GET_LINK_RATE, "get_link_rate" }, \ { DPTX_APCALL_SET_LINK_RATE, "set_link_rate" }, \ + { DPTX_APCALL_GET_MAX_LANE_COUNT, \ + "get_max_lane_count" }, \ { DPTX_APCALL_GET_ACTIVE_LANE_COUNT, \ "get_active_lane_count" }, \ { DPTX_APCALL_SET_ACTIVE_LANE_COUNT, \ From 4836b0b14dac47b385454486e3318567d6dec635 Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Fri, 18 Aug 2023 00:05:15 +0200 Subject: [PATCH 1114/3902] drm: apple: dptx: port interface to macOS 13.5 firmware Signed-off-by: Janne Grunau --- drivers/gpu/drm/apple/dptxep.c | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/drivers/gpu/drm/apple/dptxep.c b/drivers/gpu/drm/apple/dptxep.c index 7179cc35991d3d..0ffcde99d0c070 100644 --- a/drivers/gpu/drm/apple/dptxep.c +++ b/drivers/gpu/drm/apple/dptxep.c @@ -65,7 +65,7 @@ int dptxport_validate_connection(struct apple_epic_service *service, u8 core, cmd.target = cpu_to_le32(target); cmd.unk = cpu_to_le32(0x100); - ret = afk_service_call(service, 0, 14, &cmd, sizeof(cmd), 40, &resp, + ret = afk_service_call(service, 0, 12, &cmd, sizeof(cmd), 40, &resp, sizeof(resp), 40); if (ret) return ret; @@ -93,7 +93,7 @@ int dptxport_connect(struct apple_epic_service *service, u8 core, u8 atc, cmd.target = cpu_to_le32(target); cmd.unk = cpu_to_le32(0x100); - ret = afk_service_call(service, 0, 13, &cmd, sizeof(cmd), 24, &resp, + ret = afk_service_call(service, 0, 11, &cmd, sizeof(cmd), 24, &resp, sizeof(resp), 24); if (ret) return ret; @@ -108,12 +108,12 @@ int dptxport_connect(struct apple_epic_service *service, u8 core, u8 atc, int dptxport_request_display(struct apple_epic_service *service) { - return afk_service_call(service, 0, 8, NULL, 0, 16, NULL, 0, 16); + return afk_service_call(service, 0, 6, NULL, 0, 16, NULL, 0, 16); } int dptxport_release_display(struct apple_epic_service *service) { - return afk_service_call(service, 0, 9, NULL, 0, 16, NULL, 0, 16); + return afk_service_call(service, 0, 7, NULL, 0, 16, NULL, 0, 16); } int dptxport_set_hpd(struct apple_epic_service *service, bool hpd) @@ -126,7 +126,7 @@ int dptxport_set_hpd(struct apple_epic_service *service, bool hpd) if (hpd) cmd.unk = cpu_to_le32(1); - ret = afk_service_call(service, 8, 10, &cmd, sizeof(cmd), 12, &resp, + ret = afk_service_call(service, 8, 8, &cmd, sizeof(cmd), 12, &resp, sizeof(resp), 12); if (ret) return ret; From 86059a687c5437891ef17d551d9746fb4f1dfd9b Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Mon, 6 Nov 2023 23:36:20 +0100 Subject: [PATCH 1115/3902] drm: apple: dptx: Add set_active_lanes APCALL Signed-off-by: Janne Grunau --- drivers/gpu/drm/apple/dptxep.c | 55 ++++++++++++++++++++++++++++++++++ 1 file changed, 55 insertions(+) diff --git a/drivers/gpu/drm/apple/dptxep.c b/drivers/gpu/drm/apple/dptxep.c index 0ffcde99d0c070..23599f8c4c9c77 100644 --- a/drivers/gpu/drm/apple/dptxep.c +++ b/drivers/gpu/drm/apple/dptxep.c @@ -36,6 +36,13 @@ struct dptxport_apcall_lane_count { u8 _unk1[8]; } __attribute__((packed)); +struct dptxport_apcall_set_active_lane_count { + __le32 retcode; + u8 _unk0[12]; + __le64 lane_count; + u8 _unk1[8]; +} __packed; + struct dptxport_apcall_get_support { __le32 retcode; u8 _unk0[12]; @@ -179,6 +186,51 @@ static int dptxport_call_get_max_lane_count(struct apple_epic_service *service, return 0; } +static int dptxport_call_set_active_lane_count(struct apple_epic_service *service, + const void *data, size_t data_size, + void *reply_, size_t reply_size) +{ + struct dptx_port *dptx = service->cookie; + const struct dptxport_apcall_set_active_lane_count *request = data; + struct dptxport_apcall_set_active_lane_count *reply = reply_; + int ret = 0; + int retcode = 0; + + if (reply_size < sizeof(*reply)) + return -1; + if (data_size < sizeof(*request)) + return -1; + + u64 lane_count = cpu_to_le64(request->lane_count); + + switch (lane_count) { + case 0 ... 2: + case 4: + dptx->phy_ops.dp.lanes = lane_count; + dptx->phy_ops.dp.set_lanes = 1; + break; + default: + dev_err(service->ep->dcp->dev, "set_active_lane_count: invalid lane count:%llu\n", lane_count); + retcode = 1; + lane_count = 0; + break; + } + + if (dptx->phy_ops.dp.set_lanes) { + if (dptx->atcphy) { + ret = phy_configure(dptx->atcphy, &dptx->phy_ops); + if (ret) + return ret; + } + dptx->phy_ops.dp.set_lanes = 0; + } + + reply->retcode = cpu_to_le32(retcode); + reply->lane_count = cpu_to_le64(lane_count); + + return ret; +} + static int dptxport_call_get_link_rate(struct apple_epic_service *service, void *reply_, size_t reply_size) { @@ -334,6 +386,9 @@ static int dptxport_call(struct apple_epic_service *service, u32 idx, reply, reply_size); case DPTX_APCALL_GET_MAX_LANE_COUNT: return dptxport_call_get_max_lane_count(service, reply, reply_size); + case DPTX_APCALL_SET_ACTIVE_LANE_COUNT: + return dptxport_call_set_active_lane_count(service, data, data_size, + reply, reply_size); case DPTX_APCALL_GET_SUPPORTS_HPD: return dptxport_call_get_supports_hpd(service, reply, reply_size); From 7857e96efe7801e5356e662de1c393e767c7e76e Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Mon, 6 Nov 2023 23:37:27 +0100 Subject: [PATCH 1116/3902] drm: apple: dptx: Add DPTX_APCALL_ACTIVATE Configures the phy to the correct dcp(ext) source by abusing submode in the phy_set_mode_ext() call. Signed-off-by: Janne Grunau --- drivers/gpu/drm/apple/dptxep.c | 22 +++++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/drivers/gpu/drm/apple/dptxep.c b/drivers/gpu/drm/apple/dptxep.c index 23599f8c4c9c77..a0f90f7153fccd 100644 --- a/drivers/gpu/drm/apple/dptxep.c +++ b/drivers/gpu/drm/apple/dptxep.c @@ -364,6 +364,24 @@ dptxport_call_get_supports_downspread(struct apple_epic_service *service, return 0; } +static int +dptxport_call_activate(struct apple_epic_service *service, + const void *data, size_t data_size, + void *reply, size_t reply_size) +{ + struct dptx_port *dptx = service->cookie; + const struct apple_dcp *dcp = service->ep->dcp; + + // TODO: hack, use phy_set_mode to select the correct DCP(EXT) input + phy_set_mode_ext(dptx->atcphy, PHY_MODE_DP, dcp->index); + + memcpy(reply, data, min(reply_size, data_size)); + if (reply_size > 4) + memset(reply, 0, 4); + + return 0; +} + static int dptxport_call(struct apple_epic_service *service, u32 idx, const void *data, size_t data_size, void *reply, size_t reply_size) @@ -398,13 +416,15 @@ static int dptxport_call(struct apple_epic_service *service, u32 idx, case DPTX_APCALL_GET_MAX_DRIVE_SETTINGS: return dptxport_call_get_max_drive_settings(service, reply, reply_size); + case DPTX_APCALL_ACTIVATE: + return dptxport_call_activate(service, data, data_size, + reply, reply_size); default: /* just try to ACK and hope for the best... */ dev_info(service->ep->dcp->dev, "DPTXPort: acking unhandled call %u\n", idx); fallthrough; /* we can silently ignore and just ACK these calls */ - case DPTX_APCALL_ACTIVATE: case DPTX_APCALL_DEACTIVATE: case DPTX_APCALL_SET_DRIVE_SETTINGS: case DPTX_APCALL_GET_DRIVE_SETTINGS: From 153aa5746d30c3b96240909796eca005a774372b Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Mon, 6 Nov 2023 23:44:08 +0100 Subject: [PATCH 1117/3902] drm: apple: dptx: Adapt dptxport_connect() to observed behavior Adapt to behavior seen on j474s with dcp0 driving lpdptx-phy and dp2hdmi using the macOS 13.5 firmware. Signed-off-by: Janne Grunau --- drivers/gpu/drm/apple/dptxep.c | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/drivers/gpu/drm/apple/dptxep.c b/drivers/gpu/drm/apple/dptxep.c index a0f90f7153fccd..2c751c630a122d 100644 --- a/drivers/gpu/drm/apple/dptxep.c +++ b/drivers/gpu/drm/apple/dptxep.c @@ -90,6 +90,7 @@ int dptxport_connect(struct apple_epic_service *service, u8 core, u8 atc, { struct dptx_port *dptx = service->cookie; struct dcpdptx_connection_cmd cmd, resp; + u32 unk_field = 0x0; // seen as 0x100 under some conditions int ret; u32 target = FIELD_PREP(DCPDPTX_REMOTE_PORT_CORE, core) | FIELD_PREP(DCPDPTX_REMOTE_PORT_ATC, atc) | @@ -99,7 +100,7 @@ int dptxport_connect(struct apple_epic_service *service, u8 core, u8 atc, trace_dptxport_connect(dptx, core, atc, die); cmd.target = cpu_to_le32(target); - cmd.unk = cpu_to_le32(0x100); + cmd.unk = cpu_to_le32(unk_field); ret = afk_service_call(service, 0, 11, &cmd, sizeof(cmd), 24, &resp, sizeof(resp), 24); if (ret) @@ -107,8 +108,9 @@ int dptxport_connect(struct apple_epic_service *service, u8 core, u8 atc, if (le32_to_cpu(resp.target) != target) return -EINVAL; - if (le32_to_cpu(resp.unk) != 0x100) - return -EINVAL; + if (le32_to_cpu(resp.unk) != unk_field) + dev_notice(service->ep->dcp->dev, "unexpected unk field in reply: 0x%x (0x%x)\n", + le32_to_cpu(resp.unk), unk_field); return 0; } From 336cf153e38c831ec79dce1351c10490c1c78dfe Mon Sep 17 00:00:00 2001 From: Hector Martin Date: Thu, 16 Nov 2023 19:38:49 +0900 Subject: [PATCH 1118/3902] drm: apple: afk: Clear commands before sending them Signed-off-by: Hector Martin --- drivers/gpu/drm/apple/afk.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/drivers/gpu/drm/apple/afk.c b/drivers/gpu/drm/apple/afk.c index f1e8bdfcc319a2..10255f2e15ee4d 100644 --- a/drivers/gpu/drm/apple/afk.c +++ b/drivers/gpu/drm/apple/afk.c @@ -861,6 +861,7 @@ int afk_send_command(struct apple_epic_service *service, u8 type, memcpy(txbuf, payload, payload_len); + memset(&cmd, 0, sizeof(cmd)); cmd.retcode = cpu_to_le32(0); cmd.rxbuf = cpu_to_le64(rxbuf_dma); cmd.rxlen = cpu_to_le32(output_len); @@ -951,6 +952,8 @@ int afk_service_call(struct apple_epic_service *service, u16 group, u32 command, return -ENOMEM; call = bfr; + + memset(call, 0, sizeof(*call)); call->group = cpu_to_le16(group); call->command = cpu_to_le32(command); call->data_len = cpu_to_le32(data_len + data_pad); From 9938ec594dc9bb7aec8252ac604503f182d4f1d6 Mon Sep 17 00:00:00 2001 From: Hector Martin Date: Fri, 17 Nov 2023 00:02:27 +0900 Subject: [PATCH 1119/3902] drm: apple: Fix missing unlock path in dcp_dptx_connect Signed-off-by: Hector Martin --- drivers/gpu/drm/apple/dcp.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/drivers/gpu/drm/apple/dcp.c b/drivers/gpu/drm/apple/dcp.c index 62dc1340bcb734..96c8cd6e8275c2 100644 --- a/drivers/gpu/drm/apple/dcp.c +++ b/drivers/gpu/drm/apple/dcp.c @@ -269,12 +269,14 @@ static int dcp_dptx_connect(struct apple_dcp *dcp, u32 port) } if (dcp->dptxport[port].connected) - return 0; + goto ret; dcp->dptxport[port].atcphy = dcp->phy; dptxport_connect(dcp->dptxport[port].service, 0, dcp->dptx_phy, dcp->dptx_die); dptxport_request_display(dcp->dptxport[port].service); dcp->dptxport[port].connected = true; + +ret: mutex_unlock(&dcp->hpd_mutex); return 0; From c33614ded3130a9df7b17db693dde3d870e96b7a Mon Sep 17 00:00:00 2001 From: Hector Martin Date: Fri, 17 Nov 2023 00:03:36 +0900 Subject: [PATCH 1120/3902] drm: apple: dptxep: Fix reply size check Signed-off-by: Hector Martin --- drivers/gpu/drm/apple/dptxep.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/gpu/drm/apple/dptxep.c b/drivers/gpu/drm/apple/dptxep.c index 2c751c630a122d..50d14741e66da7 100644 --- a/drivers/gpu/drm/apple/dptxep.c +++ b/drivers/gpu/drm/apple/dptxep.c @@ -378,7 +378,7 @@ dptxport_call_activate(struct apple_epic_service *service, phy_set_mode_ext(dptx->atcphy, PHY_MODE_DP, dcp->index); memcpy(reply, data, min(reply_size, data_size)); - if (reply_size > 4) + if (reply_size >= 4) memset(reply, 0, 4); return 0; @@ -431,7 +431,7 @@ static int dptxport_call(struct apple_epic_service *service, u32 idx, case DPTX_APCALL_SET_DRIVE_SETTINGS: case DPTX_APCALL_GET_DRIVE_SETTINGS: memcpy(reply, data, min(reply_size, data_size)); - if (reply_size > 4) + if (reply_size >= 4) memset(reply, 0, 4); return 0; } From 2a4d1fa5aa2f3456dc45477949d06c4098478294 Mon Sep 17 00:00:00 2001 From: Hector Martin Date: Fri, 17 Nov 2023 00:03:51 +0900 Subject: [PATCH 1121/3902] drm: apple: dptxep: Implement drive settings stuff Just in case, for consistency with macOS. Signed-off-by: Hector Martin --- drivers/gpu/drm/apple/dptxep.c | 75 +++++++++++++++++++++++++++++++++- drivers/gpu/drm/apple/dptxep.h | 1 + 2 files changed, 74 insertions(+), 2 deletions(-) diff --git a/drivers/gpu/drm/apple/dptxep.c b/drivers/gpu/drm/apple/dptxep.c index 50d14741e66da7..83d4a3925af0ac 100644 --- a/drivers/gpu/drm/apple/dptxep.c +++ b/drivers/gpu/drm/apple/dptxep.c @@ -57,6 +57,18 @@ struct dptxport_apcall_max_drive_settings { u8 _unk1[8]; }; +struct dptxport_apcall_drive_settings { + __le32 retcode; + u8 _unk0[12]; + __le32 unk1; + __le32 unk2; + __le32 unk3; + __le32 unk4; + __le32 unk5; + __le32 unk6; + __le32 unk7; +}; + int dptxport_validate_connection(struct apple_epic_service *service, u8 core, u8 atc, u8 die) { @@ -160,6 +172,61 @@ dptxport_call_get_max_drive_settings(struct apple_epic_service *service, return 0; } +static int +dptxport_call_get_drive_settings(struct apple_epic_service *service, + const void *request_, size_t request_size, + void *reply_, size_t reply_size) +{ + struct dptx_port *dptx = service->cookie; + const struct dptxport_apcall_drive_settings *request = request_; + struct dptxport_apcall_drive_settings *reply = reply_; + + if (reply_size < sizeof(*reply) || request_size < sizeof(*request)) + return -EINVAL; + + *reply = *request; + + /* Clear the rest of the buffer */ + memset(reply_ + sizeof(*reply), 0, reply_size - sizeof(*reply)); + + if (reply->retcode != 4) + dev_err(service->ep->dcp->dev, + "get_drive_settings: unexpected retcode %d\n", + reply->retcode); + + reply->retcode = 4; /* Should already be 4? */ + reply->unk5 = dptx->drive_settings[0]; + reply->unk6 = 0; + reply->unk7 = dptx->drive_settings[1]; + + return 0; +} + +static int +dptxport_call_set_drive_settings(struct apple_epic_service *service, + const void *request_, size_t request_size, + void *reply_, size_t reply_size) +{ + struct dptx_port *dptx = service->cookie; + const struct dptxport_apcall_drive_settings *request = request_; + struct dptxport_apcall_drive_settings *reply = reply_; + + if (reply_size < sizeof(*reply) || request_size < sizeof(*request)) + return -EINVAL; + + *reply = *request; + reply->retcode = cpu_to_le32(0); + + dev_info(service->ep->dcp->dev, "set_drive_settings: %d:%d:%d:%d:%d:%d:%d\n", + request->unk1, request->unk2, request->unk3, request->unk4, + request->unk5, request->unk6, request->unk7); + + dptx->drive_settings[0] = reply->unk5; + dptx->drive_settings[1] = reply->unk7; + + return 0; +} + static int dptxport_call_get_max_link_rate(struct apple_epic_service *service, void *reply_, size_t reply_size) { @@ -418,6 +485,12 @@ static int dptxport_call(struct apple_epic_service *service, u32 idx, case DPTX_APCALL_GET_MAX_DRIVE_SETTINGS: return dptxport_call_get_max_drive_settings(service, reply, reply_size); + case DPTX_APCALL_GET_DRIVE_SETTINGS: + return dptxport_call_get_drive_settings(service, data, data_size, + reply, reply_size); + case DPTX_APCALL_SET_DRIVE_SETTINGS: + return dptxport_call_set_drive_settings(service, data, data_size, + reply, reply_size); case DPTX_APCALL_ACTIVATE: return dptxport_call_activate(service, data, data_size, reply, reply_size); @@ -428,8 +501,6 @@ static int dptxport_call(struct apple_epic_service *service, u32 idx, fallthrough; /* we can silently ignore and just ACK these calls */ case DPTX_APCALL_DEACTIVATE: - case DPTX_APCALL_SET_DRIVE_SETTINGS: - case DPTX_APCALL_GET_DRIVE_SETTINGS: memcpy(reply, data, min(reply_size, data_size)); if (reply_size >= 4) memset(reply, 0, 4); diff --git a/drivers/gpu/drm/apple/dptxep.h b/drivers/gpu/drm/apple/dptxep.h index 8f0483e7030b7a..481ebbc97bf38d 100644 --- a/drivers/gpu/drm/apple/dptxep.h +++ b/drivers/gpu/drm/apple/dptxep.h @@ -55,6 +55,7 @@ struct dptx_port { struct phy *atcphy; struct mux_control *mux; u32 link_rate, pending_link_rate; + u32 drive_settings[2]; }; int dptxport_validate_connection(struct apple_epic_service *service, u8 core, From db57996127770b7973b021dd399bd2500f9c3d1e Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Mon, 20 Nov 2023 22:43:48 +0100 Subject: [PATCH 1122/3902] drm: apple: HACK: Do not delete piodma platform device of_platform_device_destroy() can trigger several NULL pointer dereference which have been elusive so far. Comment this for now since the oopses causes the shutdown to hang. Since dcp can not be reloaded this leaks the platform device on shutdown and reboot. Signed-off-by: Janne Grunau --- drivers/gpu/drm/apple/dcp.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/drivers/gpu/drm/apple/dcp.c b/drivers/gpu/drm/apple/dcp.c index 96c8cd6e8275c2..f27a71b3d392e9 100644 --- a/drivers/gpu/drm/apple/dcp.c +++ b/drivers/gpu/drm/apple/dcp.c @@ -819,7 +819,10 @@ static void dcp_comp_unbind(struct device *dev, struct device *main, void *data) if (dcp->piodma) { iommu_detach_device(dcp->iommu_dom, &dcp->piodma->dev); iommu_domain_free(dcp->iommu_dom); - of_platform_device_destroy(&dcp->piodma->dev, NULL); + /* TODO: the piodma platform device has to be destroyed but + * doing so leads to all kind of breakage. + */ + // of_platform_device_destroy(&dcp->piodma->dev, NULL); dcp->piodma = NULL; } From 4479e08aeb384189575d95fdead5452e92399b44 Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Mon, 6 Nov 2023 22:27:54 +0100 Subject: [PATCH 1123/3902] drm: apple: afk: Update read pointer before processing message Avoids out of order messages and already unmapped buffers while tracing with hv/trace_dcp.py. Signed-off-by: Janne Grunau --- drivers/gpu/drm/apple/afk.c | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/drivers/gpu/drm/apple/afk.c b/drivers/gpu/drm/apple/afk.c index 10255f2e15ee4d..fc90150bb7b5ab 100644 --- a/drivers/gpu/drm/apple/afk.c +++ b/drivers/gpu/drm/apple/afk.c @@ -613,8 +613,6 @@ static bool afk_recv(struct apple_dcp_afkep *ep) channel = le32_to_cpu(hdr->channel); type = le32_to_cpu(hdr->type); - afk_recv_handle(ep, channel, type, hdr->data, size); - rptr = ALIGN(rptr + sizeof(*hdr) + size, 1 << BLOCK_SHIFT); if (WARN_ON(rptr > ep->rxbfr.bufsz)) rptr = 0; @@ -626,6 +624,15 @@ static bool afk_recv(struct apple_dcp_afkep *ep) ep->rxbfr.hdr->rptr = cpu_to_le32(rptr); trace_afk_recv_rwptr_post(ep, rptr, wptr); + /* + * TODO: this is theoretically unsafe since DCP could overwrite data + * after the read pointer was updated above. Do it anyway since + * it avoids 2 problems in the DCP tracer: + * 1. the tracer sees replies before the the notifies from dcp + * 2. the tracer tries to read buffers after they are unmapped. + */ + afk_recv_handle(ep, channel, type, hdr->data, size); + return true; } From ab11d2e74b8cb82b48c594d67bd28fedc836cf72 Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Sun, 19 Nov 2023 18:07:41 +0100 Subject: [PATCH 1124/3902] drm: apple: Implement D592 callback This callback is occasionally seen around (failed) modesets. There seems to be no need to handle it so just trace it. Signed-off-by: Janne Grunau --- drivers/gpu/drm/apple/iomfb_template.c | 7 +++++++ drivers/gpu/drm/apple/iomfb_v12_3.c | 1 + drivers/gpu/drm/apple/iomfb_v13_3.c | 1 + drivers/gpu/drm/apple/trace.h | 17 +++++++++++++++++ 4 files changed, 26 insertions(+) diff --git a/drivers/gpu/drm/apple/iomfb_template.c b/drivers/gpu/drm/apple/iomfb_template.c index 945e972a7c4679..d60b143c040b9d 100644 --- a/drivers/gpu/drm/apple/iomfb_template.c +++ b/drivers/gpu/drm/apple/iomfb_template.c @@ -1044,6 +1044,12 @@ dcpep_cb_swap_complete_intent_gated(struct apple_dcp *dcp, info->width, info->height); } +static void +dcpep_cb_abort_swap_ap_gated(struct apple_dcp *dcp, u32 *swap_id) +{ + trace_iomfb_abort_swap_ap_gated(dcp, *swap_id); +} + static struct dcpep_get_tiling_state_resp dcpep_cb_get_tiling_state(struct apple_dcp *dcp, struct dcpep_get_tiling_state_req *req) @@ -1110,6 +1116,7 @@ TRAMPOLINE_IN(trampoline_hotplug, dcpep_cb_hotplug, u64); TRAMPOLINE_IN(trampoline_swap_complete_intent_gated, dcpep_cb_swap_complete_intent_gated, struct dcp_swap_complete_intent_gated); +TRAMPOLINE_IN(trampoline_abort_swap_ap_gated, dcpep_cb_abort_swap_ap_gated, u32); TRAMPOLINE_IN(trampoline_enable_backlight_message_ap_gated, iomfbep_cb_enable_backlight_message_ap_gated, u8); TRAMPOLINE_IN(trampoline_pr_publish, iomfb_cb_pr_publish, diff --git a/drivers/gpu/drm/apple/iomfb_v12_3.c b/drivers/gpu/drm/apple/iomfb_v12_3.c index 8b4d87ad9012bd..ad3cbf576cfdcf 100644 --- a/drivers/gpu/drm/apple/iomfb_v12_3.c +++ b/drivers/gpu/drm/apple/iomfb_v12_3.c @@ -89,6 +89,7 @@ static const iomfb_cb_handler cb_handlers[IOMFB_MAX_CB] = { [588] = trampoline_nop, /* resize_default_fb_surface_gated */ [589] = trampoline_swap_complete, [591] = trampoline_swap_complete_intent_gated, + [592] = trampoline_abort_swap_ap_gated, [593] = trampoline_enable_backlight_message_ap_gated, [594] = trampoline_nop, /* IOMobileFramebufferAP::setSystemConsoleMode */ [596] = trampoline_false, /* IOMobileFramebufferAP::isDFBAllocated */ diff --git a/drivers/gpu/drm/apple/iomfb_v13_3.c b/drivers/gpu/drm/apple/iomfb_v13_3.c index 0689c0a593f784..0311e1c8c39874 100644 --- a/drivers/gpu/drm/apple/iomfb_v13_3.c +++ b/drivers/gpu/drm/apple/iomfb_v13_3.c @@ -95,6 +95,7 @@ static const iomfb_cb_handler cb_handlers[IOMFB_MAX_CB] = { [588] = trampoline_nop, /* resize_default_fb_surface_gated */ [589] = trampoline_swap_complete, [591] = trampoline_swap_complete_intent_gated, + [592] = trampoline_abort_swap_ap_gated, [593] = trampoline_enable_backlight_message_ap_gated, [594] = trampoline_nop, /* IOMobileFramebufferAP::setSystemConsoleMode */ [596] = trampoline_false, /* IOMobileFramebufferAP::isDFBAllocated */ diff --git a/drivers/gpu/drm/apple/trace.h b/drivers/gpu/drm/apple/trace.h index 814bc7f0864475..e03bf8b199c88f 100644 --- a/drivers/gpu/drm/apple/trace.h +++ b/drivers/gpu/drm/apple/trace.h @@ -303,6 +303,23 @@ TRACE_EVENT(iomfb_swap_complete_intent_gated, ) ); +TRACE_EVENT(iomfb_abort_swap_ap_gated, + TP_PROTO(struct apple_dcp *dcp, u32 swap_id), + TP_ARGS(dcp, swap_id), + TP_STRUCT__entry( + __field(u64, dcp) + __field(u32, swap_id) + ), + TP_fast_assign( + __entry->dcp = (u64)dcp; + __entry->swap_id = swap_id; + ), + TP_printk("dcp=%llx, swap_id=%u", + __entry->dcp, + __entry->swap_id + ) +); + DECLARE_EVENT_CLASS(iomfb_parse_mode_template, TP_PROTO(s64 id, struct dimension *horiz, struct dimension *vert, s64 best_color_mode, bool is_virtual, s64 score), TP_ARGS(id, horiz, vert, best_color_mode, is_virtual, score), From 04d1688ffa9ebe7aece55eac810cf9707b019fbb Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Sun, 19 Nov 2023 18:25:22 +0100 Subject: [PATCH 1125/3902] drm: apple: Keep information at which swap_id fb are still referenced Signed-off-by: Janne Grunau --- drivers/gpu/drm/apple/dcp-internal.h | 4 ++++ drivers/gpu/drm/apple/iomfb_template.c | 6 ++++++ 2 files changed, 10 insertions(+) diff --git a/drivers/gpu/drm/apple/dcp-internal.h b/drivers/gpu/drm/apple/dcp-internal.h index 64025d23282d1c..5ac2fcfa455cec 100644 --- a/drivers/gpu/drm/apple/dcp-internal.h +++ b/drivers/gpu/drm/apple/dcp-internal.h @@ -75,6 +75,7 @@ struct dcp_channel { struct dcp_fb_reference { struct list_head head; struct drm_framebuffer *fb; + u32 swap_id; }; #define MAX_NOTCH_HEIGHT 160 @@ -167,6 +168,9 @@ struct apple_dcp { struct dcp_swap_submit_req_v13_3 v13_3; } swap; + /* swap id of the last completed swap */ + u32 last_swap_id; + /* Current display mode */ bool valid_mode; struct dcp_set_digital_out_mode_req mode; diff --git a/drivers/gpu/drm/apple/iomfb_template.c b/drivers/gpu/drm/apple/iomfb_template.c index d60b143c040b9d..6d7c0c614b54b5 100644 --- a/drivers/gpu/drm/apple/iomfb_template.c +++ b/drivers/gpu/drm/apple/iomfb_template.c @@ -121,6 +121,7 @@ static void dcpep_cb_swap_complete(struct apple_dcp *dcp, struct DCP_FW_NAME(dc_swap_complete_resp) *resp) { trace_iomfb_swap_complete(dcp, resp->swap_id); + dcp->last_swap_id = resp->swap_id; dcp_drm_crtc_vblank(dcp->crtc); } @@ -746,6 +747,8 @@ static void dcp_swap_cleared(struct apple_dcp *dcp, void *data, void *cookie) struct dcp_fb_reference *entry; entry = list_first_entry(&dcp->swapped_out_fbs, struct dcp_fb_reference, head); + if (entry->swap_id == dcp->last_swap_id) + break; if (entry->fb) drm_framebuffer_put(entry->fb); list_del(&entry->head); @@ -1145,6 +1148,8 @@ static void dcp_swapped(struct apple_dcp *dcp, void *data, void *cookie) struct dcp_fb_reference *entry; entry = list_first_entry(&dcp->swapped_out_fbs, struct dcp_fb_reference, head); + if (entry->swap_id == dcp->last_swap_id) + break; if (entry->fb) drm_framebuffer_put(entry->fb); list_del(&entry->head); @@ -1252,6 +1257,7 @@ void DCP_FW_NAME(iomfb_flush)(struct apple_dcp *dcp, struct drm_crtc *crtc, stru kzalloc(sizeof(*entry), GFP_KERNEL); if (entry) { entry->fb = old_state->fb; + entry->swap_id = dcp->last_swap_id; list_add_tail(&entry->head, &dcp->swapped_out_fbs); } From 6b31d61f2d3b5f88bd3372c44fe7bcdd01d7249d Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Mon, 20 Nov 2023 20:09:25 +0100 Subject: [PATCH 1126/3902] Revert "drm: apple: iomfb: Do not match/create PMU service for dcpext" This reverts commit ab69434d230f9951644e10c9142dbc43ea0516c4. --- drivers/gpu/drm/apple/dcp-internal.h | 3 --- drivers/gpu/drm/apple/dcp.c | 2 -- drivers/gpu/drm/apple/iomfb_template.c | 16 ---------------- drivers/gpu/drm/apple/iomfb_v12_3.c | 2 +- drivers/gpu/drm/apple/iomfb_v13_3.c | 2 +- 5 files changed, 2 insertions(+), 23 deletions(-) diff --git a/drivers/gpu/drm/apple/dcp-internal.h b/drivers/gpu/drm/apple/dcp-internal.h index 5ac2fcfa455cec..1041aecb211822 100644 --- a/drivers/gpu/drm/apple/dcp-internal.h +++ b/drivers/gpu/drm/apple/dcp-internal.h @@ -187,9 +187,6 @@ struct apple_dcp { /* clear all surfaces on init */ bool surfaces_cleared; - /* is dcpext / requires dptx */ - bool is_dptx; - /* Modes valid for the connected display */ struct dcp_display_mode *modes; unsigned int nr_modes; diff --git a/drivers/gpu/drm/apple/dcp.c b/drivers/gpu/drm/apple/dcp.c index f27a71b3d392e9..b3a760cd97298e 100644 --- a/drivers/gpu/drm/apple/dcp.c +++ b/drivers/gpu/drm/apple/dcp.c @@ -702,8 +702,6 @@ static int dcp_comp_bind(struct device *dev, struct device *main, void *data) if (IS_ERR(dcp->coproc_reg)) return PTR_ERR(dcp->coproc_reg); - dcp->is_dptx = dcp->phy != NULL; - of_property_read_u32(dev->of_node, "apple,dcp-index", &dcp->index); of_property_read_u32(dev->of_node, "apple,dptx-phy", diff --git a/drivers/gpu/drm/apple/iomfb_template.c b/drivers/gpu/drm/apple/iomfb_template.c index 6d7c0c614b54b5..8ade4368b6a32c 100644 --- a/drivers/gpu/drm/apple/iomfb_template.c +++ b/drivers/gpu/drm/apple/iomfb_template.c @@ -136,10 +136,6 @@ static void complete_vi_set_temperature_hint(struct apple_dcp *dcp, void *out, v static bool iomfbep_cb_match_pmu_service(struct apple_dcp *dcp, int tag, void *out, void *in) { trace_iomfb_callback(dcp, tag, __func__); - - if (dcp->is_dptx) - return true; - iomfb_a358_vi_set_temperature_hint(dcp, false, complete_vi_set_temperature_hint, NULL); @@ -163,12 +159,6 @@ static bool iomfbep_cb_match_pmu_service_2(struct apple_dcp *dcp, int tag, void { trace_iomfb_callback(dcp, tag, __func__); - if (dcp->is_dptx) { - u8 *ret = out; - ret[0] = 1; - return true; - } - iomfb_a131_pmu_service_matched(dcp, false, complete_pmu_service_matched, out); @@ -1063,11 +1053,6 @@ dcpep_cb_get_tiling_state(struct apple_dcp *dcp, }; } -static u8 dcpep_cb_create_pmu_service(struct apple_dcp *dcp) -{ - return !dcp->is_dptx; -} - static u8 dcpep_cb_create_backlight_service(struct apple_dcp *dcp) { return dcp_has_panel(dcp); @@ -1126,7 +1111,6 @@ TRAMPOLINE_IN(trampoline_pr_publish, iomfb_cb_pr_publish, struct iomfb_property); TRAMPOLINE_INOUT(trampoline_get_tiling_state, dcpep_cb_get_tiling_state, struct dcpep_get_tiling_state_req, struct dcpep_get_tiling_state_resp); -TRAMPOLINE_OUT(trampoline_create_pmu_service, dcpep_cb_create_pmu_service, u8); TRAMPOLINE_OUT(trampoline_create_backlight_service, dcpep_cb_create_backlight_service, u8); /* diff --git a/drivers/gpu/drm/apple/iomfb_v12_3.c b/drivers/gpu/drm/apple/iomfb_v12_3.c index ad3cbf576cfdcf..0fe08c42d64659 100644 --- a/drivers/gpu/drm/apple/iomfb_v12_3.c +++ b/drivers/gpu/drm/apple/iomfb_v12_3.c @@ -48,7 +48,7 @@ static const iomfb_cb_handler cb_handlers[IOMFB_MAX_CB] = { [106] = trampoline_nop, /* remove_property */ [107] = trampoline_true, /* create_provider_service */ [108] = trampoline_true, /* create_product_service */ - [109] = trampoline_create_pmu_service, + [109] = trampoline_true, /* create_pmu_service */ [110] = trampoline_true, /* create_iomfb_service */ [111] = trampoline_create_backlight_service, [116] = dcpep_cb_boot_1, diff --git a/drivers/gpu/drm/apple/iomfb_v13_3.c b/drivers/gpu/drm/apple/iomfb_v13_3.c index 0311e1c8c39874..1ee29112be4543 100644 --- a/drivers/gpu/drm/apple/iomfb_v13_3.c +++ b/drivers/gpu/drm/apple/iomfb_v13_3.c @@ -50,7 +50,7 @@ static const iomfb_cb_handler cb_handlers[IOMFB_MAX_CB] = { [107] = trampoline_nop, /* remove_property */ [108] = trampoline_true, /* create_provider_service */ [109] = trampoline_true, /* create_product_service */ - [110] = trampoline_create_pmu_service, + [110] = trampoline_true, /* create_pmu_service */ [111] = trampoline_true, /* create_iomfb_service */ [112] = trampoline_create_backlight_service, [113] = trampoline_true, /* create_nvram_servce? */ From b42235dbac9a1200dff84c44ccb70f1d9ffd0cdf Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Mon, 20 Nov 2023 22:48:02 +0100 Subject: [PATCH 1127/3902] drm: apple: dptx: Implement APCALL_DEACTIVATE and reset the phy This mirrors what macOS does and should make reconnections more reliable. Signed-off-by: Janne Grunau --- drivers/gpu/drm/apple/dptxep.c | 23 ++++++++++++++++++++--- 1 file changed, 20 insertions(+), 3 deletions(-) diff --git a/drivers/gpu/drm/apple/dptxep.c b/drivers/gpu/drm/apple/dptxep.c index 83d4a3925af0ac..328ff41aee7dd0 100644 --- a/drivers/gpu/drm/apple/dptxep.c +++ b/drivers/gpu/drm/apple/dptxep.c @@ -451,6 +451,23 @@ dptxport_call_activate(struct apple_epic_service *service, return 0; } +static int +dptxport_call_deactivate(struct apple_epic_service *service, + const void *data, size_t data_size, + void *reply, size_t reply_size) +{ + struct dptx_port *dptx = service->cookie; + + /* deactivate phy */ + phy_set_mode_ext(dptx->atcphy, PHY_MODE_INVALID, 0); + + memcpy(reply, data, min(reply_size, data_size)); + if (reply_size >= 4) + memset(reply, 0, 4); + + return 0; +} + static int dptxport_call(struct apple_epic_service *service, u32 idx, const void *data, size_t data_size, void *reply, size_t reply_size) @@ -494,13 +511,13 @@ static int dptxport_call(struct apple_epic_service *service, u32 idx, case DPTX_APCALL_ACTIVATE: return dptxport_call_activate(service, data, data_size, reply, reply_size); + case DPTX_APCALL_DEACTIVATE: + return dptxport_call_deactivate(service, data, data_size, + reply, reply_size); default: /* just try to ACK and hope for the best... */ dev_info(service->ep->dcp->dev, "DPTXPort: acking unhandled call %u\n", idx); - fallthrough; - /* we can silently ignore and just ACK these calls */ - case DPTX_APCALL_DEACTIVATE: memcpy(reply, data, min(reply_size, data_size)); if (reply_size >= 4) memset(reply, 0, 4); From 2d257b6ea65dd32559ac9eaeddddae21cfe5decc Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Mon, 20 Nov 2023 22:56:43 +0100 Subject: [PATCH 1128/3902] drm: apple: Disconnect dptx When the CRTC is powered down Seems to make disconnect / reconnect more reliable and almost fixes suspend/resume. The drm device tries to modeset too early on resume which leaves the screen blank. This should reduce power consumption after disconnecting the HDMI port. Signed-off-by: Janne Grunau --- drivers/gpu/drm/apple/dcp.c | 64 +++++++++++++++++++++++++++++++++++ drivers/gpu/drm/apple/iomfb.c | 51 ---------------------------- 2 files changed, 64 insertions(+), 51 deletions(-) diff --git a/drivers/gpu/drm/apple/dcp.c b/drivers/gpu/drm/apple/dcp.c index b3a760cd97298e..3dc3271659f6c0 100644 --- a/drivers/gpu/drm/apple/dcp.c +++ b/drivers/gpu/drm/apple/dcp.c @@ -287,6 +287,7 @@ static int dcp_dptx_disconnect(struct apple_dcp *dcp, u32 port) struct apple_connector *connector = dcp->connector; mutex_lock(&dcp->hpd_mutex); + if (connector && connector->connected) { dcp->valid_mode = false; schedule_work(&connector->hotplug_wq); @@ -407,6 +408,69 @@ int dcp_wait_ready(struct platform_device *pdev, u64 timeout) } EXPORT_SYMBOL(dcp_wait_ready); +static void __maybe_unused dcp_sleep(struct apple_dcp *dcp) +{ + switch (dcp->fw_compat) { + case DCP_FIRMWARE_V_12_3: + iomfb_sleep_v12_3(dcp); + break; + case DCP_FIRMWARE_V_13_5: + iomfb_sleep_v13_3(dcp); + break; + default: + WARN_ONCE(true, "Unexpected firmware version: %u\n", dcp->fw_compat); + break; + } +} + +void dcp_poweron(struct platform_device *pdev) +{ + struct apple_dcp *dcp = platform_get_drvdata(pdev); + + if (dcp->hdmi_hpd) { + bool connected = gpiod_get_value_cansleep(dcp->hdmi_hpd); + dev_info(dcp->dev, "%s: DP2HDMI HPD connected:%d\n", __func__, connected); + + if (connected) + dcp_dptx_connect(dcp, 0); + } + + switch (dcp->fw_compat) { + case DCP_FIRMWARE_V_12_3: + iomfb_poweron_v12_3(dcp); + break; + case DCP_FIRMWARE_V_13_5: + iomfb_poweron_v13_3(dcp); + break; + default: + WARN_ONCE(true, "Unexpected firmware version: %u\n", dcp->fw_compat); + break; + } +} +EXPORT_SYMBOL(dcp_poweron); + +void dcp_poweroff(struct platform_device *pdev) +{ + struct apple_dcp *dcp = platform_get_drvdata(pdev); + + switch (dcp->fw_compat) { + case DCP_FIRMWARE_V_12_3: + iomfb_poweroff_v12_3(dcp); + break; + case DCP_FIRMWARE_V_13_5: + iomfb_poweroff_v13_3(dcp); + break; + default: + WARN_ONCE(true, "Unexpected firmware version: %u\n", dcp->fw_compat); + break; + } + + if (dcp->phy) + dcp_dptx_disconnect(dcp, 0); + +} +EXPORT_SYMBOL(dcp_poweroff); + static void dcp_work_register_backlight(struct work_struct *work) { int ret; diff --git a/drivers/gpu/drm/apple/iomfb.c b/drivers/gpu/drm/apple/iomfb.c index 70bfcdf4a96bc8..b9236794193f69 100644 --- a/drivers/gpu/drm/apple/iomfb.c +++ b/drivers/gpu/drm/apple/iomfb.c @@ -219,57 +219,6 @@ void dcp_ack(struct apple_dcp *dcp, enum dcp_context_id context) dcpep_ack(context)); } -void dcp_sleep(struct apple_dcp *dcp) -{ - switch (dcp->fw_compat) { - case DCP_FIRMWARE_V_12_3: - iomfb_sleep_v12_3(dcp); - break; - case DCP_FIRMWARE_V_13_5: - iomfb_sleep_v13_3(dcp); - break; - default: - WARN_ONCE(true, "Unexpected firmware version: %u\n", dcp->fw_compat); - break; - } -} - -void dcp_poweron(struct platform_device *pdev) -{ - struct apple_dcp *dcp = platform_get_drvdata(pdev); - - switch (dcp->fw_compat) { - case DCP_FIRMWARE_V_12_3: - iomfb_poweron_v12_3(dcp); - break; - case DCP_FIRMWARE_V_13_5: - iomfb_poweron_v13_3(dcp); - break; - default: - WARN_ONCE(true, "Unexpected firmware version: %u\n", dcp->fw_compat); - break; - } -} -EXPORT_SYMBOL(dcp_poweron); - -void dcp_poweroff(struct platform_device *pdev) -{ - struct apple_dcp *dcp = platform_get_drvdata(pdev); - - switch (dcp->fw_compat) { - case DCP_FIRMWARE_V_12_3: - iomfb_poweroff_v12_3(dcp); - break; - case DCP_FIRMWARE_V_13_5: - iomfb_poweroff_v13_3(dcp); - break; - default: - WARN_ONCE(true, "Unexpected firmware version: %u\n", dcp->fw_compat); - break; - } -} -EXPORT_SYMBOL(dcp_poweroff); - /* * Helper to send a DRM hotplug event. The DCP is accessed from a single * (RTKit) thread. To handle hotplug callbacks, we need to call From 9b2685c1661fde3602d24437bfc6c9709c7a91db Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Tue, 21 Nov 2023 23:32:07 +0100 Subject: [PATCH 1129/3902] drm: apple: dptx: Wait for completion of dptx_connect. Makes connects more reliable. Signed-off-by: Janne Grunau --- drivers/gpu/drm/apple/dcp.c | 17 ++++++++++++----- drivers/gpu/drm/apple/dptxep.c | 4 ++++ drivers/gpu/drm/apple/dptxep.h | 1 + 3 files changed, 17 insertions(+), 5 deletions(-) diff --git a/drivers/gpu/drm/apple/dcp.c b/drivers/gpu/drm/apple/dcp.c index 3dc3271659f6c0..b1b112b16fd805 100644 --- a/drivers/gpu/drm/apple/dcp.c +++ b/drivers/gpu/drm/apple/dcp.c @@ -256,6 +256,8 @@ EXPORT_SYMBOL_GPL(dcp_get_connector_type); static int dcp_dptx_connect(struct apple_dcp *dcp, u32 port) { + int ret = 0; + if (!dcp->phy) { dev_warn(dcp->dev, "dcp_dptx_connect: missing phy\n"); return -ENODEV; @@ -264,22 +266,27 @@ static int dcp_dptx_connect(struct apple_dcp *dcp, u32 port) mutex_lock(&dcp->hpd_mutex); if (!dcp->dptxport[port].enabled) { dev_warn(dcp->dev, "dcp_dptx_connect: dptx service for port %d not enabled\n", port); - mutex_unlock(&dcp->hpd_mutex); - return -ENODEV; + ret = -ENODEV; + goto out_unlock; } if (dcp->dptxport[port].connected) - goto ret; + goto out_unlock; + reinit_completion(&dcp->dptxport[port].linkcfg_completion); dcp->dptxport[port].atcphy = dcp->phy; dptxport_connect(dcp->dptxport[port].service, 0, dcp->dptx_phy, dcp->dptx_die); dptxport_request_display(dcp->dptxport[port].service); dcp->dptxport[port].connected = true; -ret: mutex_unlock(&dcp->hpd_mutex); - + wait_for_completion_timeout(&dcp->dptxport[port].linkcfg_completion, + msecs_to_jiffies(1000)); return 0; + +out_unlock: + mutex_unlock(&dcp->hpd_mutex); + return ret; } static int dcp_dptx_disconnect(struct apple_dcp *dcp, u32 port) diff --git a/drivers/gpu/drm/apple/dptxep.c b/drivers/gpu/drm/apple/dptxep.c index 328ff41aee7dd0..0a3ab4abd074c6 100644 --- a/drivers/gpu/drm/apple/dptxep.c +++ b/drivers/gpu/drm/apple/dptxep.c @@ -330,8 +330,10 @@ dptxport_call_will_change_link_config(struct apple_epic_service *service) static int dptxport_call_did_change_link_config(struct apple_epic_service *service) { + struct dptx_port *dptx = service->cookie; /* assume the link config did change and wait a little bit */ mdelay(10); + complete(&dptx->linkcfg_completion); return 0; } @@ -573,6 +575,8 @@ int dptxep_init(struct apple_dcp *dcp) init_completion(&dcp->dptxport[0].enable_completion); init_completion(&dcp->dptxport[1].enable_completion); + init_completion(&dcp->dptxport[0].linkcfg_completion); + init_completion(&dcp->dptxport[1].linkcfg_completion); dcp->dptxep = afk_init(dcp, DPTX_ENDPOINT, dptxep_ops); if (IS_ERR(dcp->dptxep)) diff --git a/drivers/gpu/drm/apple/dptxep.h b/drivers/gpu/drm/apple/dptxep.h index 481ebbc97bf38d..4a0770d43c954c 100644 --- a/drivers/gpu/drm/apple/dptxep.h +++ b/drivers/gpu/drm/apple/dptxep.h @@ -49,6 +49,7 @@ struct apple_epic_service; struct dptx_port { bool enabled, connected; struct completion enable_completion; + struct completion linkcfg_completion; u32 unit; struct apple_epic_service *service; union phy_configure_opts phy_ops; From 37e80a31853c692a00ab5d943af02001cae00db2 Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Tue, 21 Nov 2023 23:50:49 +0100 Subject: [PATCH 1130/3902] drm: apple: HPD: Only act on connect IRQs DCP notices the disconnects on its own and the parallel handling just results in confusion (both on DRM and developer side). Signed-off-by: Janne Grunau --- drivers/gpu/drm/apple/dcp.c | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/drivers/gpu/drm/apple/dcp.c b/drivers/gpu/drm/apple/dcp.c index b1b112b16fd805..9def4d8def7f05 100644 --- a/drivers/gpu/drm/apple/dcp.c +++ b/drivers/gpu/drm/apple/dcp.c @@ -314,12 +314,16 @@ static irqreturn_t dcp_dp2hdmi_hpd(int irq, void *data) struct apple_dcp *dcp = data; bool connected = gpiod_get_value_cansleep(dcp->hdmi_hpd); - dev_info(dcp->dev, "DP2HDMI HPD connected:%d\n", connected); + /* do nothing on disconnect and trust that dcp detects it itself. + * Parallel disconnect HPDs result drm disabling the CRTC even when it + * should not. + * The interrupt should be changed to rising but for now the disconnect + * IRQs might be helpful for debugging. + */ + dev_info(dcp->dev, "DP2HDMI HPD irq, connected:%d\n", connected); if (connected) dcp_dptx_connect(dcp, 0); - else - dcp_dptx_disconnect(dcp, 0); return IRQ_HANDLED; } From 343f99e23f91c93524e9c13655bb1c897367128e Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Tue, 21 Nov 2023 23:57:07 +0100 Subject: [PATCH 1131/3902] drm: apple: iomfb: Improve hotplug related logging Signed-off-by: Janne Grunau --- drivers/gpu/drm/apple/iomfb.c | 3 ++- drivers/gpu/drm/apple/iomfb_template.c | 3 +++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/drivers/gpu/drm/apple/iomfb.c b/drivers/gpu/drm/apple/iomfb.c index b9236794193f69..5c6b968830fbec 100644 --- a/drivers/gpu/drm/apple/iomfb.c +++ b/drivers/gpu/drm/apple/iomfb.c @@ -236,7 +236,8 @@ void dcp_hotplug(struct work_struct *work) dev = connector->base.dev; dcp = platform_get_drvdata(connector->dcp); - dev_info(dcp->dev, "%s: connected: %d", __func__, connector->connected); + dev_info(dcp->dev, "%s() connected:%d valid_mode:%d\n", __func__, + connector->connected, dcp->valid_mode); /* * DCP defers link training until we set a display mode. But we set diff --git a/drivers/gpu/drm/apple/iomfb_template.c b/drivers/gpu/drm/apple/iomfb_template.c index 8ade4368b6a32c..435c5a2bf9718c 100644 --- a/drivers/gpu/drm/apple/iomfb_template.c +++ b/drivers/gpu/drm/apple/iomfb_template.c @@ -1014,6 +1014,9 @@ static void dcpep_cb_hotplug(struct apple_dcp *dcp, u64 *connected) if (dcp->main_display) return; + dev_info(dcp->dev, "cb_hotplug() connected:%llu, valid_mode:%d\n", + *connected, dcp->valid_mode); + /* Hotplug invalidates mode. DRM doesn't always handle this. */ if (!(*connected)) { dcp->valid_mode = false; From 219ed0b0b481b391ae286db994f0bfaf30f5bd93 Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Wed, 22 Nov 2023 09:41:29 +0100 Subject: [PATCH 1132/3902] drm: apple: Extract modeset crtc's atomic_flush() Triggering modesets from drm_connector_helper_funcs.atomic_check is more in line with DRM/KMS' design and allows returning errors from failed modesets. Ignore hotplug callbacks from DCP during modeset. DCP always does disconnected -> connected on (at least the initial) modeset. Shield drm helpers from this. This improves reliability with externel (dptx based) displays. Signed-off-by: Janne Grunau --- drivers/gpu/drm/apple/apple_drv.c | 2 + drivers/gpu/drm/apple/dcp-internal.h | 1 + drivers/gpu/drm/apple/dcp.h | 2 + drivers/gpu/drm/apple/iomfb.c | 41 ++++++++ drivers/gpu/drm/apple/iomfb_template.c | 137 ++++++++++++++----------- drivers/gpu/drm/apple/iomfb_template.h | 2 + 6 files changed, 124 insertions(+), 61 deletions(-) diff --git a/drivers/gpu/drm/apple/apple_drv.c b/drivers/gpu/drm/apple/apple_drv.c index 882263b3ef6b71..94b9789661fa8d 100644 --- a/drivers/gpu/drm/apple/apple_drv.c +++ b/drivers/gpu/drm/apple/apple_drv.c @@ -301,6 +301,8 @@ static const struct drm_connector_funcs apple_connector_funcs = { static const struct drm_connector_helper_funcs apple_connector_helper_funcs = { .get_modes = dcp_get_modes, .mode_valid = dcp_mode_valid, + .atomic_check = dcp_connector_atomic_check, + }; static const struct drm_crtc_helper_funcs apple_crtc_helper_funcs = { diff --git a/drivers/gpu/drm/apple/dcp-internal.h b/drivers/gpu/drm/apple/dcp-internal.h index 1041aecb211822..e975ae5be371fd 100644 --- a/drivers/gpu/drm/apple/dcp-internal.h +++ b/drivers/gpu/drm/apple/dcp-internal.h @@ -172,6 +172,7 @@ struct apple_dcp { u32 last_swap_id; /* Current display mode */ + bool during_modeset; bool valid_mode; struct dcp_set_digital_out_mode_req mode; diff --git a/drivers/gpu/drm/apple/dcp.h b/drivers/gpu/drm/apple/dcp.h index e0dc96109b9d2b..039dcf3ab319d8 100644 --- a/drivers/gpu/drm/apple/dcp.h +++ b/drivers/gpu/drm/apple/dcp.h @@ -57,6 +57,8 @@ void dcp_drm_crtc_vblank(struct apple_crtc *crtc); int dcp_get_modes(struct drm_connector *connector); int dcp_mode_valid(struct drm_connector *connector, struct drm_display_mode *mode); +int dcp_connector_atomic_check(struct drm_connector *connector, + struct drm_atomic_state *state); bool dcp_crtc_mode_fixup(struct drm_crtc *crtc, const struct drm_display_mode *mode, struct drm_display_mode *adjusted_mode); diff --git a/drivers/gpu/drm/apple/iomfb.c b/drivers/gpu/drm/apple/iomfb.c index 5c6b968830fbec..5884f30426d6d6 100644 --- a/drivers/gpu/drm/apple/iomfb.c +++ b/drivers/gpu/drm/apple/iomfb.c @@ -422,6 +422,47 @@ int dcp_mode_valid(struct drm_connector *connector, } EXPORT_SYMBOL_GPL(dcp_mode_valid); +int dcp_connector_atomic_check(struct drm_connector *connector, + struct drm_atomic_state *state) +{ + struct apple_connector *apple_connector = to_apple_connector(connector); + struct platform_device *pdev = apple_connector->dcp; + struct apple_dcp *dcp = platform_get_drvdata(pdev); + struct drm_crtc *crtc = &dcp->crtc->base; + struct drm_crtc_state *crtc_state; + int ret = -EIO; + bool modeset; + + crtc_state = drm_atomic_get_new_crtc_state(state, crtc); + if (!crtc_state) + return 0; + + modeset = drm_atomic_crtc_needs_modeset(crtc_state) || !dcp->valid_mode; + + if (!modeset) + return 0; + + /* ignore no mode, poweroff is handled elsewhere */ + if (crtc_state->mode.hdisplay == 0 && crtc_state->mode.vdisplay == 0) + return 0; + + switch (dcp->fw_compat) { + case DCP_FIRMWARE_V_12_3: + ret = iomfb_modeset_v12_3(dcp, crtc_state); + break; + case DCP_FIRMWARE_V_13_5: + ret = iomfb_modeset_v13_3(dcp, crtc_state); + break; + default: + WARN_ONCE(true, "Unexpected firmware version: %u\n", + dcp->fw_compat); + break; + } + + return ret; +} +EXPORT_SYMBOL_GPL(dcp_connector_atomic_check); + bool dcp_crtc_mode_fixup(struct drm_crtc *crtc, const struct drm_display_mode *mode, struct drm_display_mode *adjusted_mode) diff --git a/drivers/gpu/drm/apple/iomfb_template.c b/drivers/gpu/drm/apple/iomfb_template.c index 435c5a2bf9718c..0895efe3a8472d 100644 --- a/drivers/gpu/drm/apple/iomfb_template.c +++ b/drivers/gpu/drm/apple/iomfb_template.c @@ -1014,6 +1014,13 @@ static void dcpep_cb_hotplug(struct apple_dcp *dcp, u64 *connected) if (dcp->main_display) return; + if (dcp->during_modeset) { + dev_info(dcp->dev, + "cb_hotplug() ignored during modeset connected:%llu\n", + *connected); + return; + } + dev_info(dcp->dev, "cb_hotplug() connected:%llu, valid_mode:%d\n", *connected, dcp->valid_mode); @@ -1178,6 +1185,75 @@ static void complete_set_digital_out_mode(struct apple_dcp *dcp, void *data, } } +int DCP_FW_NAME(iomfb_modeset)(struct apple_dcp *dcp, + struct drm_crtc_state *crtc_state) +{ + struct dcp_display_mode *mode; + struct dcp_wait_cookie *cookie; + int ret; + + mode = lookup_mode(dcp, &crtc_state->mode); + if (!mode) { + dev_err(dcp->dev, "no match for " DRM_MODE_FMT "\n", + DRM_MODE_ARG(&crtc_state->mode)); + return -EIO; + } + + dev_info(dcp->dev, + "set_digital_out_mode(color:%d timing:%d) " DRM_MODE_FMT "\n", + mode->color_mode_id, mode->timing_mode_id, + DRM_MODE_ARG(&crtc_state->mode)); + dcp->mode = (struct dcp_set_digital_out_mode_req){ + .color_mode_id = mode->color_mode_id, + .timing_mode_id = mode->timing_mode_id + }; + + cookie = kzalloc(sizeof(*cookie), GFP_KERNEL); + if (!cookie) { + return -ENOMEM; + } + + init_completion(&cookie->done); + kref_init(&cookie->refcount); + /* increase refcount to ensure the receiver has a reference */ + kref_get(&cookie->refcount); + + dcp->during_modeset = true; + + dcp_set_digital_out_mode(dcp, false, &dcp->mode, + complete_set_digital_out_mode, cookie); + + /* + * The DCP firmware has an internal timeout of ~8 seconds for + * modesets. Add an extra 500ms to safe side that the modeset + * call has returned. + */ + dev_dbg(dcp->dev, "%s - wait for modeset", __func__); + ret = wait_for_completion_timeout(&cookie->done, + msecs_to_jiffies(8500)); + + kref_put(&cookie->refcount, release_wait_cookie); + dcp->during_modeset = false; + dev_info(dcp->dev, "set_digital_out_mode finished:%d\n", ret); + + if (ret == 0) { + dev_info(dcp->dev, "set_digital_out_mode timed out\n"); + return -EIO; + } else if (ret < 0) { + dev_info(dcp->dev, + "waiting on set_digital_out_mode failed:%d\n", ret); + return -EIO; + + } else if (ret > 0) { + dev_dbg(dcp->dev, + "set_digital_out_mode finished with %d to spare\n", + jiffies_to_msecs(ret)); + } + dcp->valid_mode = true; + + return 0; +} + void DCP_FW_NAME(iomfb_flush)(struct apple_dcp *dcp, struct drm_crtc *crtc, struct drm_atomic_state *state) { struct drm_plane *plane; @@ -1186,13 +1262,10 @@ void DCP_FW_NAME(iomfb_flush)(struct apple_dcp *dcp, struct drm_crtc *crtc, stru struct DCP_FW_NAME(dcp_swap_submit_req) *req = &DCP_FW_UNION(dcp->swap); int plane_idx, l; int has_surface = 0; - bool modeset; dev_dbg(dcp->dev, "%s", __func__); crtc_state = drm_atomic_get_new_crtc_state(state, crtc); - modeset = drm_atomic_crtc_needs_modeset(crtc_state) || !dcp->valid_mode; - /* Reset to defaults */ memset(req, 0, sizeof(*req)); for (l = 0; l < SWAP_SURFACES; l++) @@ -1305,64 +1378,6 @@ void DCP_FW_NAME(iomfb_flush)(struct apple_dcp *dcp, struct drm_crtc *crtc, stru l += 1; } - if (modeset) { - struct dcp_display_mode *mode; - struct dcp_wait_cookie *cookie; - int ret; - - mode = lookup_mode(dcp, &crtc_state->mode); - if (!mode) { - dev_warn(dcp->dev, "no match for " DRM_MODE_FMT, - DRM_MODE_ARG(&crtc_state->mode)); - schedule_work(&dcp->vblank_wq); - return; - } - - dev_info(dcp->dev, "set_digital_out_mode(color:%d timing:%d)", - mode->color_mode_id, mode->timing_mode_id); - dcp->mode = (struct dcp_set_digital_out_mode_req){ - .color_mode_id = mode->color_mode_id, - .timing_mode_id = mode->timing_mode_id - }; - - cookie = kzalloc(sizeof(*cookie), GFP_KERNEL); - if (!cookie) { - schedule_work(&dcp->vblank_wq); - return; - } - - init_completion(&cookie->done); - kref_init(&cookie->refcount); - /* increase refcount to ensure the receiver has a reference */ - kref_get(&cookie->refcount); - - dcp_set_digital_out_mode(dcp, false, &dcp->mode, - complete_set_digital_out_mode, cookie); - - /* - * The DCP firmware has an internal timeout of ~8 seconds for - * modesets. Add an extra 500ms to safe side that the modeset - * call has returned. - */ - dev_dbg(dcp->dev, "%s - wait for modeset", __func__); - ret = wait_for_completion_timeout(&cookie->done, - msecs_to_jiffies(8500)); - - kref_put(&cookie->refcount, release_wait_cookie); - - if (ret == 0) { - dev_info(dcp->dev, "set_digital_out_mode timed out"); - schedule_work(&dcp->vblank_wq); - return; - } else if (ret > 0) { - dev_dbg(dcp->dev, - "set_digital_out_mode finished with %d to spare", - jiffies_to_msecs(ret)); - } - - dcp->valid_mode = true; - } - if (!has_surface && !crtc_state->color_mgmt_changed) { if (crtc_state->enable && crtc_state->active && !crtc_state->planes_changed) { diff --git a/drivers/gpu/drm/apple/iomfb_template.h b/drivers/gpu/drm/apple/iomfb_template.h index e9c249609f46cb..f446a4d8f38b90 100644 --- a/drivers/gpu/drm/apple/iomfb_template.h +++ b/drivers/gpu/drm/apple/iomfb_template.h @@ -172,6 +172,8 @@ struct DCP_FW_NAME(dcp_map_reg_resp) { struct apple_dcp; +int DCP_FW_NAME(iomfb_modeset)(struct apple_dcp *dcp, + struct drm_crtc_state *crtc_state); void DCP_FW_NAME(iomfb_flush)(struct apple_dcp *dcp, struct drm_crtc *crtc, struct drm_atomic_state *state); void DCP_FW_NAME(iomfb_poweron)(struct apple_dcp *dcp); void DCP_FW_NAME(iomfb_poweroff)(struct apple_dcp *dcp); From 55aec2051d9b3b00069c735e5668af7f287d5512 Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Wed, 22 Nov 2023 09:53:09 +0100 Subject: [PATCH 1133/3902] drm: apple: dptx: Log connect/disconnect calls Signed-off-by: Janne Grunau --- drivers/gpu/drm/apple/dcp.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/drivers/gpu/drm/apple/dcp.c b/drivers/gpu/drm/apple/dcp.c index 9def4d8def7f05..f3891846f18200 100644 --- a/drivers/gpu/drm/apple/dcp.c +++ b/drivers/gpu/drm/apple/dcp.c @@ -262,6 +262,7 @@ static int dcp_dptx_connect(struct apple_dcp *dcp, u32 port) dev_warn(dcp->dev, "dcp_dptx_connect: missing phy\n"); return -ENODEV; } + dev_info(dcp->dev, "%s(port=%d)\n", __func__, port); mutex_lock(&dcp->hpd_mutex); if (!dcp->dptxport[port].enabled) { @@ -292,6 +293,7 @@ static int dcp_dptx_connect(struct apple_dcp *dcp, u32 port) static int dcp_dptx_disconnect(struct apple_dcp *dcp, u32 port) { struct apple_connector *connector = dcp->connector; + dev_info(dcp->dev, "%s(port=%d)\n", __func__, port); mutex_lock(&dcp->hpd_mutex); From 8fd927e5e2238cfb9ff0a63a3e7583b09decfa59 Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Thu, 23 Nov 2023 22:58:16 +0100 Subject: [PATCH 1134/3902] drm: apple: Move modeset into drm_crtc's atomic_enable squash! drm: apple: Extract modeset crtc's atomic_flush() Fixes: 99d7bb861908 ("drm: apple: Extract modeset crtc's atomic_flush()") Signed-off-by: Janne Grunau --- drivers/gpu/drm/apple/apple_drv.c | 5 +++-- drivers/gpu/drm/apple/dcp.h | 4 ++-- drivers/gpu/drm/apple/iomfb.c | 12 +++++------- 3 files changed, 10 insertions(+), 11 deletions(-) diff --git a/drivers/gpu/drm/apple/apple_drv.c b/drivers/gpu/drm/apple/apple_drv.c index 94b9789661fa8d..582b86f894b394 100644 --- a/drivers/gpu/drm/apple/apple_drv.c +++ b/drivers/gpu/drm/apple/apple_drv.c @@ -200,6 +200,9 @@ static void apple_crtc_atomic_enable(struct drm_crtc *crtc, dcp_poweron(apple_crtc->dcp); dev_dbg(&apple_crtc->dcp->dev, "%s finished", __func__); } + + if (crtc_state->active) + dcp_crtc_atomic_modeset(crtc, state); } static void apple_crtc_atomic_disable(struct drm_crtc *crtc, @@ -301,8 +304,6 @@ static const struct drm_connector_funcs apple_connector_funcs = { static const struct drm_connector_helper_funcs apple_connector_helper_funcs = { .get_modes = dcp_get_modes, .mode_valid = dcp_mode_valid, - .atomic_check = dcp_connector_atomic_check, - }; static const struct drm_crtc_helper_funcs apple_crtc_helper_funcs = { diff --git a/drivers/gpu/drm/apple/dcp.h b/drivers/gpu/drm/apple/dcp.h index 039dcf3ab319d8..298520c0832e93 100644 --- a/drivers/gpu/drm/apple/dcp.h +++ b/drivers/gpu/drm/apple/dcp.h @@ -57,8 +57,8 @@ void dcp_drm_crtc_vblank(struct apple_crtc *crtc); int dcp_get_modes(struct drm_connector *connector); int dcp_mode_valid(struct drm_connector *connector, struct drm_display_mode *mode); -int dcp_connector_atomic_check(struct drm_connector *connector, - struct drm_atomic_state *state); +int dcp_crtc_atomic_modeset(struct drm_crtc *crtc, + struct drm_atomic_state *state); bool dcp_crtc_mode_fixup(struct drm_crtc *crtc, const struct drm_display_mode *mode, struct drm_display_mode *adjusted_mode); diff --git a/drivers/gpu/drm/apple/iomfb.c b/drivers/gpu/drm/apple/iomfb.c index 5884f30426d6d6..f0aecd92dae348 100644 --- a/drivers/gpu/drm/apple/iomfb.c +++ b/drivers/gpu/drm/apple/iomfb.c @@ -422,13 +422,11 @@ int dcp_mode_valid(struct drm_connector *connector, } EXPORT_SYMBOL_GPL(dcp_mode_valid); -int dcp_connector_atomic_check(struct drm_connector *connector, - struct drm_atomic_state *state) +int dcp_crtc_atomic_modeset(struct drm_crtc *crtc, + struct drm_atomic_state *state) { - struct apple_connector *apple_connector = to_apple_connector(connector); - struct platform_device *pdev = apple_connector->dcp; - struct apple_dcp *dcp = platform_get_drvdata(pdev); - struct drm_crtc *crtc = &dcp->crtc->base; + struct apple_crtc *apple_crtc = to_apple_crtc(crtc); + struct apple_dcp *dcp = platform_get_drvdata(apple_crtc->dcp); struct drm_crtc_state *crtc_state; int ret = -EIO; bool modeset; @@ -461,7 +459,7 @@ int dcp_connector_atomic_check(struct drm_connector *connector, return ret; } -EXPORT_SYMBOL_GPL(dcp_connector_atomic_check); +EXPORT_SYMBOL_GPL(dcp_crtc_atomic_modeset); bool dcp_crtc_mode_fixup(struct drm_crtc *crtc, const struct drm_display_mode *mode, From 81153d593396fd4e7dbc5d5715757b34ff301e15 Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Thu, 23 Nov 2023 22:58:51 +0100 Subject: [PATCH 1135/3902] drm: apple: Fix DPTX hotplug handling - Do not trigger an hotplug event from disconnect. DCP/iomfb notices that itself. - Check HPD status before disconnecting DPTX in the crtc disable path. - disconnect on suspend to allow an orderly re-connect on resume Signed-off-by: Janne Grunau --- drivers/gpu/drm/apple/dcp.c | 19 ++++++++----------- 1 file changed, 8 insertions(+), 11 deletions(-) diff --git a/drivers/gpu/drm/apple/dcp.c b/drivers/gpu/drm/apple/dcp.c index f3891846f18200..c579d2079e6d49 100644 --- a/drivers/gpu/drm/apple/dcp.c +++ b/drivers/gpu/drm/apple/dcp.c @@ -292,16 +292,9 @@ static int dcp_dptx_connect(struct apple_dcp *dcp, u32 port) static int dcp_dptx_disconnect(struct apple_dcp *dcp, u32 port) { - struct apple_connector *connector = dcp->connector; dev_info(dcp->dev, "%s(port=%d)\n", __func__, port); mutex_lock(&dcp->hpd_mutex); - - if (connector && connector->connected) { - dcp->valid_mode = false; - schedule_work(&connector->hotplug_wq); - } - if (dcp->dptxport[port].enabled && dcp->dptxport[port].connected) { dptxport_release_display(dcp->dptxport[port].service); dcp->dptxport[port].connected = false; @@ -478,9 +471,11 @@ void dcp_poweroff(struct platform_device *pdev) break; } - if (dcp->phy) - dcp_dptx_disconnect(dcp, 0); - + if (dcp->hdmi_hpd) { + bool connected = gpiod_get_value_cansleep(dcp->hdmi_hpd); + if (!connected) + dcp_dptx_disconnect(dcp, 0); + } } EXPORT_SYMBOL(dcp_poweroff); @@ -1017,8 +1012,10 @@ static int dcp_platform_suspend(struct device *dev) { struct apple_dcp *dcp = dev_get_drvdata(dev); - if (dcp->hdmi_hpd_irq) + if (dcp->hdmi_hpd_irq) { disable_irq(dcp->hdmi_hpd_irq); + dcp_dptx_disconnect(dcp, 0); + } /* * Set the device as a wakeup device, which forces its power * domains to stay on. We need this as we do not support full From fe8ab74e258e353254031586516cfeb8ff54fc91 Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Thu, 23 Nov 2023 23:04:47 +0100 Subject: [PATCH 1136/3902] drm: apple: iomfb: Use drm_kms_helper_connector_hotplug_event Avoid device wide hotplugs as DCP knowns the affected connector. Signed-off-by: Janne Grunau --- drivers/gpu/drm/apple/iomfb.c | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/drivers/gpu/drm/apple/iomfb.c b/drivers/gpu/drm/apple/iomfb.c index f0aecd92dae348..cb2cb6a89a4133 100644 --- a/drivers/gpu/drm/apple/iomfb.c +++ b/drivers/gpu/drm/apple/iomfb.c @@ -229,11 +229,9 @@ void dcp_ack(struct apple_dcp *dcp, enum dcp_context_id context) void dcp_hotplug(struct work_struct *work) { struct apple_connector *connector; - struct drm_device *dev; struct apple_dcp *dcp; connector = container_of(work, struct apple_connector, hotplug_wq); - dev = connector->base.dev; dcp = platform_get_drvdata(connector->dcp); dev_info(dcp->dev, "%s() connected:%d valid_mode:%d\n", __func__, @@ -244,13 +242,11 @@ void dcp_hotplug(struct work_struct *work) * display modes from atomic_flush, so userspace needs to trigger a * flush, or the CRTC gets no signal. */ - if (connector->base.state && !dcp->valid_mode && connector->connected) { - drm_connector_set_link_status_property( - &connector->base, DRM_MODE_LINK_STATUS_BAD); - } + if (connector->base.state && !dcp->valid_mode && connector->connected) + drm_connector_set_link_status_property(&connector->base, + DRM_MODE_LINK_STATUS_BAD); - if (dev && dev->registered) - drm_kms_helper_hotplug_event(dev); + drm_kms_helper_connector_hotplug_event(&connector->base); } EXPORT_SYMBOL_GPL(dcp_hotplug); From c458683b3ee0c340fe0adafa89dfd356aa3e2a67 Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Sun, 26 Nov 2023 18:30:59 +0100 Subject: [PATCH 1137/3902] drm: apple: iomfb: Handle OOB ASYNC/CB context Only observed with dcp/dptx in linux after initialisation and reset in m1n1. On the initial startup dcp sends two D576 (hotPlug_notify_gated) presumendly due to state confusion due to the multiple dptx connections. Signed-off-by: Janne Grunau --- drivers/gpu/drm/apple/dcp-internal.h | 2 +- drivers/gpu/drm/apple/iomfb.c | 4 ++++ drivers/gpu/drm/apple/iomfb.h | 3 +++ 3 files changed, 8 insertions(+), 1 deletion(-) diff --git a/drivers/gpu/drm/apple/dcp-internal.h b/drivers/gpu/drm/apple/dcp-internal.h index e975ae5be371fd..24d23364e8f415 100644 --- a/drivers/gpu/drm/apple/dcp-internal.h +++ b/drivers/gpu/drm/apple/dcp-internal.h @@ -154,7 +154,7 @@ struct apple_dcp { struct dcp_mem_descriptor memdesc[DCP_MAX_MAPPINGS]; struct dcp_channel ch_cmd, ch_oobcmd; - struct dcp_channel ch_cb, ch_oobcb, ch_async; + struct dcp_channel ch_cb, ch_oobcb, ch_async, ch_oobasync; /* iomfb EP callback handlers */ const iomfb_cb_handler *cb_handlers; diff --git a/drivers/gpu/drm/apple/iomfb.c b/drivers/gpu/drm/apple/iomfb.c index cb2cb6a89a4133..eb475c7d2d441c 100644 --- a/drivers/gpu/drm/apple/iomfb.c +++ b/drivers/gpu/drm/apple/iomfb.c @@ -49,6 +49,8 @@ static int dcp_channel_offset(enum dcp_context_id id) switch (id) { case DCP_CONTEXT_ASYNC: return 0x40000; + case DCP_CONTEXT_OOBASYNC: + return 0x48000; case DCP_CONTEXT_CB: return 0x60000; case DCP_CONTEXT_OOBCB: @@ -118,6 +120,8 @@ static struct dcp_channel *dcp_get_channel(struct apple_dcp *dcp, return &dcp->ch_oobcmd; case DCP_CONTEXT_ASYNC: return &dcp->ch_async; + case DCP_CONTEXT_OOBASYNC: + return &dcp->ch_oobasync; default: return NULL; } diff --git a/drivers/gpu/drm/apple/iomfb.h b/drivers/gpu/drm/apple/iomfb.h index 5d54a59c7ced45..8fb225e6169e42 100644 --- a/drivers/gpu/drm/apple/iomfb.h +++ b/drivers/gpu/drm/apple/iomfb.h @@ -28,6 +28,9 @@ enum dcp_context_id { /* Out-of-band command */ DCP_CONTEXT_OOBCMD = 6, + /* Out-of-band Asynchronous */ + DCP_CONTEXT_OOBASYNC = 7, + DCP_NUM_CONTEXTS }; From 84e10450948a69249b81555dd7414e1a76397bda Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Sun, 26 Nov 2023 18:57:07 +0100 Subject: [PATCH 1138/3902] drm: apple: iomfb: Extend hotplug/mode parsing logging Under unknown but slightly broken conditions dcp sends timing modes without linked color modes. Log a warning when this happens and log the number of valid modes before emitting HPD events. Signed-off-by: Janne Grunau --- drivers/gpu/drm/apple/iomfb.c | 4 ++-- drivers/gpu/drm/apple/iomfb_template.c | 2 ++ 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/drivers/gpu/drm/apple/iomfb.c b/drivers/gpu/drm/apple/iomfb.c index eb475c7d2d441c..b179c183f46529 100644 --- a/drivers/gpu/drm/apple/iomfb.c +++ b/drivers/gpu/drm/apple/iomfb.c @@ -238,8 +238,8 @@ void dcp_hotplug(struct work_struct *work) connector = container_of(work, struct apple_connector, hotplug_wq); dcp = platform_get_drvdata(connector->dcp); - dev_info(dcp->dev, "%s() connected:%d valid_mode:%d\n", __func__, - connector->connected, dcp->valid_mode); + dev_info(dcp->dev, "%s() connected:%d valid_mode:%d nr_modes:%u\n", __func__, + connector->connected, dcp->valid_mode, dcp->nr_modes); /* * DCP defers link training until we set a display mode. But we set diff --git a/drivers/gpu/drm/apple/iomfb_template.c b/drivers/gpu/drm/apple/iomfb_template.c index 0895efe3a8472d..52d97e380d5b3c 100644 --- a/drivers/gpu/drm/apple/iomfb_template.c +++ b/drivers/gpu/drm/apple/iomfb_template.c @@ -567,6 +567,8 @@ static bool dcpep_process_chunks(struct apple_dcp *dcp, dcp->nr_modes = 0; return false; } + if (dcp->nr_modes == 0) + dev_warn(dcp->dev, "TimingElements without valid modes!\n"); } else if (!strcmp(req->key, "DisplayAttributes")) { /* DisplayAttributes are empty for integrated displays, use * display dimensions read from the devicetree From a717422457f9e444e93468d9000c4aa3792069b6 Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Mon, 27 Nov 2023 00:11:12 +0100 Subject: [PATCH 1139/3902] drm: apple: Adjust startup sequence and timing for dptx DPTX setup from an initialized connection and display with sleeping and reset dcp is unfortunately quite fragile. The display connection has to be stopped and reestablished. Goodbye flicker free boot. If the IOMFB endpoint is started too early dcp might provide incomplete timing modes which prevent modesets. On display standby a HPD is triggered should result in a fully initialized dcp. If not a display cable unplug and plug should help. MacOS doesn't handle this at all and just gives up. Signed-off-by: Janne Grunau --- drivers/gpu/drm/apple/apple_drv.c | 8 +++- drivers/gpu/drm/apple/dcp.c | 64 +++++++++++++++++-------------- 2 files changed, 43 insertions(+), 29 deletions(-) diff --git a/drivers/gpu/drm/apple/apple_drv.c b/drivers/gpu/drm/apple/apple_drv.c index 582b86f894b394..de5abab31d7c37 100644 --- a/drivers/gpu/drm/apple/apple_drv.c +++ b/drivers/gpu/drm/apple/apple_drv.c @@ -9,6 +9,7 @@ #include #include +#include #include #include #include @@ -445,7 +446,10 @@ static int apple_drm_init_dcp(struct device *dev) if (num_dcp < 1) return -ENODEV; - timeout = get_jiffies_64() + msecs_to_jiffies(500); + /* + * Starting DPTX might take some time. + */ + timeout = get_jiffies_64() + msecs_to_jiffies(3000); for (i = 0; i < num_dcp; ++i) { u64 jiffies = get_jiffies_64(); @@ -460,6 +464,8 @@ static int apple_drm_init_dcp(struct device *dev) if (ret) dev_warn(dev, "DCP[%d] not ready: %d\n", i, ret); } + /* HACK: Wait for dcp* to settle before a modeset */ + msleep(100); return 0; } diff --git a/drivers/gpu/drm/apple/dcp.c b/drivers/gpu/drm/apple/dcp.c index c579d2079e6d49..9b8196a3e67177 100644 --- a/drivers/gpu/drm/apple/dcp.c +++ b/drivers/gpu/drm/apple/dcp.c @@ -345,23 +345,40 @@ int dcp_start(struct platform_device *pdev) if (ret) dev_warn(dcp->dev, "Failed to start system endpoint: %d", ret); - if (dcp->phy) { - if (dcp->fw_compat >= DCP_FIRMWARE_V_13_5) { - ret = ibootep_init(dcp); - if (ret) - dev_warn(dcp->dev, - "Failed to start IBOOT endpoint: %d", - ret); - - ret = dptxep_init(dcp); - if (ret) - dev_warn(dcp->dev, - "Failed to start DPTX endpoint: %d", - ret); - } else - dev_warn(dcp->dev, - "OS firmware incompatible with dptxport EP\n"); - } + if (dcp->phy && dcp->fw_compat >= DCP_FIRMWARE_V_13_5) { + ret = ibootep_init(dcp); + if (ret) + dev_warn(dcp->dev, "Failed to start IBOOT endpoint: %d", + ret); + + ret = dptxep_init(dcp); + if (ret) + dev_warn(dcp->dev, "Failed to start DPTX endpoint: %d", + ret); + else if (dcp->dptxport[0].enabled) { + bool connected; + /* force disconnect on start - necessary if the display + * is already up from m1n1 + */ + dptxport_set_hpd(dcp->dptxport[0].service, false); + dptxport_release_display(dcp->dptxport[0].service); + usleep_range(10 * USEC_PER_MSEC, 25 * USEC_PER_MSEC); + + connected = gpiod_get_value_cansleep(dcp->hdmi_hpd); + dev_info(dcp->dev, "%s: DP2HDMI HPD connected:%d\n", __func__, connected); + + // necessary on j473/j474 but not on j314c + if (connected) + dcp_dptx_connect(dcp, 0); + /* + * Long sleep necessary to ensure dcp delivers timing + * modes with matched color modes. + * 400ms was sufficient on j473 + */ + msleep(500); + } + } else if (dcp->phy) + dev_warn(dcp->dev, "OS firmware incompatible with dptxport EP\n"); ret = iomfb_start_rtkit(dcp); if (ret) @@ -373,17 +390,8 @@ EXPORT_SYMBOL(dcp_start); static int dcp_enable_dp2hdmi_hpd(struct apple_dcp *dcp) { - if (dcp->hdmi_hpd) { - bool connected = gpiod_get_value_cansleep(dcp->hdmi_hpd); - dev_info(dcp->dev, "%s: DP2HDMI HPD connected:%d\n", __func__, connected); - - // necessary on j473/j474 but not on j314c - if (connected) - dcp_dptx_connect(dcp, 0); - - if (dcp->hdmi_hpd_irq) - enable_irq(dcp->hdmi_hpd_irq); - } + if (dcp->hdmi_hpd_irq) + enable_irq(dcp->hdmi_hpd_irq); return 0; } From e0d67c14971ffe060f00ffe17ab5a3df7e0431a3 Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Tue, 28 Nov 2023 14:27:18 +0100 Subject: [PATCH 1140/3902] drm: apple: dcp: Fix resume with DPTX based display outputs Signed-off-by: Janne Grunau --- drivers/gpu/drm/apple/dcp.c | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/drivers/gpu/drm/apple/dcp.c b/drivers/gpu/drm/apple/dcp.c index 9b8196a3e67177..eab7625d14199d 100644 --- a/drivers/gpu/drm/apple/dcp.c +++ b/drivers/gpu/drm/apple/dcp.c @@ -1041,6 +1041,13 @@ static int dcp_platform_resume(struct device *dev) if (dcp->hdmi_hpd_irq) enable_irq(dcp->hdmi_hpd_irq); + if (dcp->hdmi_hpd) { + bool connected = gpiod_get_value_cansleep(dcp->hdmi_hpd); + dev_info(dcp->dev, "resume: HPD connected:%d\n", connected); + if (connected) + dcp_dptx_connect(dcp, 0); + } + return 0; } From ecb79d804d09c1e14c8a881efd06e06471c7116e Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Sat, 2 Dec 2023 10:26:13 +0100 Subject: [PATCH 1141/3902] drm: apple: Be less noisy about teardown notifies without service Signed-off-by: Janne Grunau --- drivers/gpu/drm/apple/afk.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/drivers/gpu/drm/apple/afk.c b/drivers/gpu/drm/apple/afk.c index fc90150bb7b5ab..a11e9f1f5be4d3 100644 --- a/drivers/gpu/drm/apple/afk.c +++ b/drivers/gpu/drm/apple/afk.c @@ -507,6 +507,12 @@ static void afk_recv_handle(struct apple_dcp_afkep *ep, u32 channel, u32 type, ep->endpoint, eshdr->category, channel); return; } + if (subtype == EPIC_SUBTYPE_TEARDOWN) { + dev_dbg(ep->dcp->dev, + "AFK[ep:%02x]: teardown without service on channel %d\n", + ep->endpoint, channel); + return; + } if (subtype != EPIC_SUBTYPE_ANNOUNCE) { dev_err(ep->dcp->dev, "AFK[ep:%02x]: expected announce but got 0x%x on channel %d\n", From 26b7cca6cb171ed3215ca2742c102981789277b1 Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Sun, 3 Dec 2023 23:57:25 +0100 Subject: [PATCH 1142/3902] drm: apple: dptx: Wait for link config on connect Should make connect more reliable by avoiding hardcoded waits which are either to long or too short. In the second case the display can't be brought up since dcp fails to report any modes during start. Signed-off-by: Janne Grunau --- drivers/gpu/drm/apple/dcp.c | 22 ++++++++++++++-------- drivers/gpu/drm/apple/dptxep.c | 8 ++++++-- drivers/gpu/drm/apple/dptxep.h | 1 + 3 files changed, 21 insertions(+), 10 deletions(-) diff --git a/drivers/gpu/drm/apple/dcp.c b/drivers/gpu/drm/apple/dcp.c index eab7625d14199d..4402e7008ac821 100644 --- a/drivers/gpu/drm/apple/dcp.c +++ b/drivers/gpu/drm/apple/dcp.c @@ -10,6 +10,7 @@ #include #include #include +#include #include #include #include @@ -254,6 +255,8 @@ int dcp_get_connector_type(struct platform_device *pdev) } EXPORT_SYMBOL_GPL(dcp_get_connector_type); +#define DPTX_CONNECT_TIMEOUT msecs_to_jiffies(1000) + static int dcp_dptx_connect(struct apple_dcp *dcp, u32 port) { int ret = 0; @@ -281,8 +284,17 @@ static int dcp_dptx_connect(struct apple_dcp *dcp, u32 port) dcp->dptxport[port].connected = true; mutex_unlock(&dcp->hpd_mutex); - wait_for_completion_timeout(&dcp->dptxport[port].linkcfg_completion, - msecs_to_jiffies(1000)); + ret = wait_for_completion_timeout(&dcp->dptxport[port].linkcfg_completion, + DPTX_CONNECT_TIMEOUT); + if (ret < 0) + dev_warn(dcp->dev, "dcp_dptx_connect: port %d link complete failed:%d\n", + port, ret); + else + dev_dbg(dcp->dev, "dcp_dptx_connect: waited %d ms for link\n", + jiffies_to_msecs(DPTX_CONNECT_TIMEOUT - ret)); + + usleep_range(5, 10); + return 0; out_unlock: @@ -370,12 +382,6 @@ int dcp_start(struct platform_device *pdev) // necessary on j473/j474 but not on j314c if (connected) dcp_dptx_connect(dcp, 0); - /* - * Long sleep necessary to ensure dcp delivers timing - * modes with matched color modes. - * 400ms was sufficient on j473 - */ - msleep(500); } } else if (dcp->phy) dev_warn(dcp->dev, "OS firmware incompatible with dptxport EP\n"); diff --git a/drivers/gpu/drm/apple/dptxep.c b/drivers/gpu/drm/apple/dptxep.c index 0a3ab4abd074c6..56b86966e807a7 100644 --- a/drivers/gpu/drm/apple/dptxep.c +++ b/drivers/gpu/drm/apple/dptxep.c @@ -294,9 +294,14 @@ static int dptxport_call_set_active_lane_count(struct apple_epic_service *servic dptx->phy_ops.dp.set_lanes = 0; } + dptx->lane_count = lane_count; + reply->retcode = cpu_to_le32(retcode); reply->lane_count = cpu_to_le64(lane_count); + if (dptx->lane_count > 0) + complete(&dptx->linkcfg_completion); + return ret; } @@ -330,10 +335,9 @@ dptxport_call_will_change_link_config(struct apple_epic_service *service) static int dptxport_call_did_change_link_config(struct apple_epic_service *service) { - struct dptx_port *dptx = service->cookie; /* assume the link config did change and wait a little bit */ mdelay(10); - complete(&dptx->linkcfg_completion); + return 0; } diff --git a/drivers/gpu/drm/apple/dptxep.h b/drivers/gpu/drm/apple/dptxep.h index 4a0770d43c954c..0bf2534054fd7b 100644 --- a/drivers/gpu/drm/apple/dptxep.h +++ b/drivers/gpu/drm/apple/dptxep.h @@ -55,6 +55,7 @@ struct dptx_port { union phy_configure_opts phy_ops; struct phy *atcphy; struct mux_control *mux; + u32 lane_count; u32 link_rate, pending_link_rate; u32 drive_settings[2]; }; From f10c3330c87c10694e3f8f83de099fb3ae7b0e47 Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Fri, 1 Dec 2023 23:41:53 +0100 Subject: [PATCH 1143/3902] drm: apple: Prefer RGB SDR modes DCP color mode scoring seems to prefer high bit depth color modes even when it it would require DSC. For example 12-bit 4k 60 Hz YCbCr 4:4:4 over a 600 MHz HDMI 2.0 link. Prefer 8-/10-bit RGB or YCbCr 4:4:4 modes if available. Signed-off-by: Janne Grunau --- drivers/gpu/drm/apple/iomfb_template.c | 16 ++++++ drivers/gpu/drm/apple/parser.c | 79 ++++++++++++++++++-------- drivers/gpu/drm/apple/parser.h | 68 ++++++++++++++++++++++ 3 files changed, 139 insertions(+), 24 deletions(-) diff --git a/drivers/gpu/drm/apple/iomfb_template.c b/drivers/gpu/drm/apple/iomfb_template.c index 52d97e380d5b3c..888659a18690c6 100644 --- a/drivers/gpu/drm/apple/iomfb_template.c +++ b/drivers/gpu/drm/apple/iomfb_template.c @@ -1192,6 +1192,7 @@ int DCP_FW_NAME(iomfb_modeset)(struct apple_dcp *dcp, { struct dcp_display_mode *mode; struct dcp_wait_cookie *cookie; + struct dcp_color_mode *cmode = NULL; int ret; mode = lookup_mode(dcp, &crtc_state->mode); @@ -1205,6 +1206,21 @@ int DCP_FW_NAME(iomfb_modeset)(struct apple_dcp *dcp, "set_digital_out_mode(color:%d timing:%d) " DRM_MODE_FMT "\n", mode->color_mode_id, mode->timing_mode_id, DRM_MODE_ARG(&crtc_state->mode)); + if (mode->color_mode_id == mode->sdr_rgb.id) + cmode = &mode->sdr_rgb; + else if (mode->color_mode_id == mode->sdr_444.id) + cmode = &mode->sdr_444; + else if (mode->color_mode_id == mode->sdr.id) + cmode = &mode->sdr; + else if (mode->color_mode_id == mode->best.id) + cmode = &mode->best; + if (cmode) + dev_info(dcp->dev, + "set_digital_out_mode() color mode depth:%hhu format:%u " + "colorimetry:%u eotf:%u range:%u\n", cmode->depth, + cmode->format, cmode->colorimetry, cmode->eotf, + cmode->range); + dcp->mode = (struct dcp_set_digital_out_mode_req){ .color_mode_id = mode->color_mode_id, .timing_mode_id = mode->timing_mode_id diff --git a/drivers/gpu/drm/apple/parser.c b/drivers/gpu/drm/apple/parser.c index a837af1bd0a5e8..0ac7845c1f23d7 100644 --- a/drivers/gpu/drm/apple/parser.c +++ b/drivers/gpu/drm/apple/parser.c @@ -313,14 +313,43 @@ struct color_mode { s64 score; }; -static int parse_color_modes(struct dcp_parse_ctx *handle, s64 *preferred_id) +static int fill_color_mode(struct dcp_color_mode *color, + struct color_mode *cmode) +{ + if (color->score >= cmode->score) + return 0; + + if (cmode->colorimetry < 0 || cmode->colorimetry >= DCP_COLORIMETRY_COUNT) + return -EINVAL; + if (cmode->depth < 8 || cmode->depth > 12) + return -EINVAL; + if (cmode->dynamic_range < 0 || cmode->dynamic_range >= DCP_COLOR_YCBCR_RANGE_COUNT) + return -EINVAL; + if (cmode->eotf < 0 || cmode->eotf >= DCP_EOTF_COUNT) + return -EINVAL; + if (cmode->pixel_encoding < 0 || cmode->pixel_encoding >= DCP_COLOR_FORMAT_COUNT) + return -EINVAL; + + color->score = cmode->score; + color->id = cmode->id; + color->eotf = cmode->eotf; + color->format = cmode->pixel_encoding; + color->colorimetry = cmode->colorimetry; + color->range = cmode->dynamic_range; + color->depth = cmode->depth; + + return 0; +} + +static int parse_color_modes(struct dcp_parse_ctx *handle, + struct dcp_display_mode *out) { struct iterator outer_it; int ret = 0; - s64 best_score = -1, best_score_sdr = -1; - s64 best_id = -1, best_id_sdr = -1; - - *preferred_id = -1; + out->sdr_444.score = -1; + out->sdr_rgb.score = -1; + out->sdr.score = -1; + out->best.score = -1; dcp_parse_foreach_in_array(handle, outer_it) { struct iterator it; @@ -367,25 +396,18 @@ static int parse_color_modes(struct dcp_parse_ctx *handle, s64 *preferred_id) cmode.eotf, cmode.dynamic_range, cmode.pixel_encoding); - if (cmode.eotf == 0) { - if (cmode.score > best_score_sdr) { - best_score_sdr = cmode.score; - best_id_sdr = cmode.id; - } - } else { - if (cmode.score > best_score) { - best_score = cmode.score; - best_id = cmode.id; - } + if (cmode.eotf == DCP_EOTF_SDR_GAMMA) { + if (cmode.pixel_encoding == DCP_COLOR_FORMAT_RGB && + cmode.depth <= 10) + fill_color_mode(&out->sdr_rgb, &cmode); + else if (cmode.pixel_encoding == DCP_COLOR_FORMAT_YCBCR444 && + cmode.depth <= 10) + fill_color_mode(&out->sdr_444, &cmode); + fill_color_mode(&out->sdr, &cmode); } + fill_color_mode(&out->best, &cmode); } - /* prefer SDR color modes as long as HDR is not supported */ - if (best_score_sdr >= 0) - *preferred_id = best_id_sdr; - else if (best_score >= 0) - *preferred_id = best_id; - return 0; } @@ -427,7 +449,7 @@ static int parse_mode(struct dcp_parse_ctx *handle, else if (!strcmp(key, "VerticalAttributes")) ret = parse_dimension(it.handle, &vert); else if (!strcmp(key, "ColorModes")) - ret = parse_color_modes(it.handle, &best_color_mode); + ret = parse_color_modes(it.handle, out); else if (!strcmp(key, "ID")) ret = parse_int(it.handle, &id); else if (!strcmp(key, "IsVirtual")) @@ -445,8 +467,17 @@ static int parse_mode(struct dcp_parse_ctx *handle, return ret; } } - - trace_iomfb_parse_mode_success(id, &horiz, &vert, best_color_mode, is_virtual, *score); + if (out->sdr_rgb.score >= 0) + best_color_mode = out->sdr_rgb.id; + else if (out->sdr_444.score >= 0) + best_color_mode = out->sdr_444.id; + else if (out->sdr.score >= 0) + best_color_mode = out->sdr.id; + else if (out->best.score >= 0) + best_color_mode = out->best.id; + + trace_iomfb_parse_mode_success(id, &horiz, &vert, best_color_mode, + is_virtual, *score); /* * Reject modes without valid color mode. diff --git a/drivers/gpu/drm/apple/parser.h b/drivers/gpu/drm/apple/parser.h index f9cc3718fa84f7..f867416070e49d 100644 --- a/drivers/gpu/drm/apple/parser.h +++ b/drivers/gpu/drm/apple/parser.h @@ -15,6 +15,70 @@ struct dcp_parse_ctx { u32 pos, len; }; +enum dcp_color_eotf { + DCP_EOTF_SDR_GAMMA = 0, // "SDR gamma" + DCP_EOTF_HDR_GAMMA = 1, // "HDR gamma" + DCP_EOTF_ST_2084 = 2, // "ST 2084 (PQ)" + DCP_EOTF_BT_2100 = 3, // "BT.2100 (HLG)" + DCP_EOTF_COUNT +}; + +enum dcp_color_format { + DCP_COLOR_FORMAT_RGB = 0, // "RGB" + DCP_COLOR_FORMAT_YCBCR420 = 1, // "YUV 4:2:0" + DCP_COLOR_FORMAT_YCBCR422 = 3, // "YUV 4:2:2" + DCP_COLOR_FORMAT_YCBCR444 = 2, // "YUV 4:4:4" + DCP_COLOR_FORMAT_DV_NATIVE = 4, // "DolbyVision (native)" + DCP_COLOR_FORMAT_DV_HDMI = 5, // "DolbyVision (HDMI)" + DCP_COLOR_FORMAT_YCBCR422_DP = 6, // "YCbCr 4:2:2 (DP tunnel)" + DCP_COLOR_FORMAT_YCBCR422_HDMI = 7, // "YCbCr 4:2:2 (HDMI tunnel)" + DCP_COLOR_FORMAT_DV_LL_YCBCR422 = 8, // "DolbyVision LL YCbCr 4:2:2" + DCP_COLOR_FORMAT_DV_LL_YCBCR422_DP = 9, // "DolbyVision LL YCbCr 4:2:2 (DP)" + DCP_COLOR_FORMAT_DV_LL_YCBCR422_HDMI = 10, // "DolbyVision LL YCbCr 4:2:2 (HDMI)" + DCP_COLOR_FORMAT_DV_LL_YCBCR444 = 11, // "DolbyVision LL YCbCr 4:4:4" + DCP_COLOR_FORMAT_DV_LL_RGB422 = 12, // "DolbyVision LL RGB 4:2:2" + DCP_COLOR_FORMAT_GRGB_BLUE_422 = 13, // "GRGB as YCbCr422 (Even line blue)" + DCP_COLOR_FORMAT_GRGB_RED_422 = 14, // "GRGB as YCbCr422 (Even line red)" + DCP_COLOR_FORMAT_COUNT +}; + +enum dcp_colorimetry { + DCP_COLORIMETRY_BT601 = 0, // "SMPTE 170M/BT.601" + DCP_COLORIMETRY_BT709 = 1, // "BT.701" + DCP_COLORIMETRY_XVYCC_601 = 2, // "xvYCC601" + DCP_COLORIMETRY_XVYCC_709 = 3, // "xvYCC709" + DCP_COLORIMETRY_SYCC_601 = 4, // "sYCC601" + DCP_COLORIMETRY_ADOBE_YCC_601 = 5, // "AdobeYCC601" + DCP_COLORIMETRY_BT2020_CYCC = 6, // "BT.2020 (c)" + DCP_COLORIMETRY_BT2020_YCC = 7, // "BT.2020 (nc)" + DCP_COLORIMETRY_VSVDB = 8, // "DolbyVision VSVDB" + DCP_COLORIMETRY_BT2020_RGB = 9, // "BT.2020 (RGB)" + DCP_COLORIMETRY_SRGB = 10, // "sRGB" + DCP_COLORIMETRY_SCRGB = 11, // "scRGB" + DCP_COLORIMETRY_SCRGB_FIXED = 12, // "scRGBfixed" + DCP_COLORIMETRY_ADOBE_RGB = 13, // "AdobeRGB" + DCP_COLORIMETRY_DCI_P3_RGB_D65 = 14, // "DCI-P3 (D65)" + DCP_COLORIMETRY_DCI_P3_RGB_THEATER = 15, // "DCI-P3 (Theater)" + DCP_COLORIMETRY_RGB = 16, // "Default RGB" + DCP_COLORIMETRY_COUNT +}; + +enum dcp_color_range { + DCP_COLOR_YCBCR_RANGE_FULL = 0, + DCP_COLOR_YCBCR_RANGE_LIMITED = 1, + DCP_COLOR_YCBCR_RANGE_COUNT +}; + +struct dcp_color_mode { + s64 score; + u32 id; + enum dcp_color_eotf eotf; + enum dcp_color_format format; + enum dcp_colorimetry colorimetry; + enum dcp_color_range range; + u8 depth; +}; + /* * Represents a single display mode. These mode objects are populated at * runtime based on the TimingElements dictionary sent by the DCP. @@ -23,6 +87,10 @@ struct dcp_display_mode { struct drm_display_mode mode; u32 color_mode_id; u32 timing_mode_id; + struct dcp_color_mode sdr_rgb; + struct dcp_color_mode sdr_444; + struct dcp_color_mode sdr; + struct dcp_color_mode best; }; struct dimension { From 60176bb5d48a924a3494ba37303700232a79a361 Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Mon, 4 Dec 2023 23:27:29 +0100 Subject: [PATCH 1144/3902] drm: apple: iomfb: Always parse DisplayAttributes Fixes missing physical display dimensions for HDMI display on Macbook Pros. Signed-off-by: Janne Grunau --- drivers/gpu/drm/apple/iomfb_template.c | 15 +++++---------- 1 file changed, 5 insertions(+), 10 deletions(-) diff --git a/drivers/gpu/drm/apple/iomfb_template.c b/drivers/gpu/drm/apple/iomfb_template.c index 888659a18690c6..dc99a4d9f8694b 100644 --- a/drivers/gpu/drm/apple/iomfb_template.c +++ b/drivers/gpu/drm/apple/iomfb_template.c @@ -570,17 +570,12 @@ static bool dcpep_process_chunks(struct apple_dcp *dcp, if (dcp->nr_modes == 0) dev_warn(dcp->dev, "TimingElements without valid modes!\n"); } else if (!strcmp(req->key, "DisplayAttributes")) { - /* DisplayAttributes are empty for integrated displays, use - * display dimensions read from the devicetree - */ - if (dcp->main_display) { - ret = parse_display_attributes(&ctx, &dcp->width_mm, - &dcp->height_mm); + ret = parse_display_attributes(&ctx, &dcp->width_mm, + &dcp->height_mm); - if (ret) { - dev_warn(dcp->dev, "failed to parse display attribs\n"); - return false; - } + if (ret) { + dev_warn(dcp->dev, "failed to parse display attribs\n"); + return false; } dcp_set_dimensions(dcp); From 4b9bf79b49745b1851c2c7a20d1c11f8f2c5fb00 Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Sun, 10 Dec 2023 13:01:22 +0100 Subject: [PATCH 1145/3902] drm: apple: parser: constify parser data Signed-off-by: Janne Grunau --- drivers/gpu/drm/apple/parser.c | 40 +++++++++++++++++----------------- drivers/gpu/drm/apple/parser.h | 4 ++-- 2 files changed, 22 insertions(+), 22 deletions(-) diff --git a/drivers/gpu/drm/apple/parser.c b/drivers/gpu/drm/apple/parser.c index 0ac7845c1f23d7..a7697ba4f8e64f 100644 --- a/drivers/gpu/drm/apple/parser.c +++ b/drivers/gpu/drm/apple/parser.c @@ -30,9 +30,9 @@ struct dcp_parse_tag { bool last : 1; } __packed; -static void *parse_bytes(struct dcp_parse_ctx *ctx, size_t count) +static const void *parse_bytes(struct dcp_parse_ctx *ctx, size_t count) { - void *ptr = ctx->blob + ctx->pos; + const void *ptr = ctx->blob + ctx->pos; if (ctx->pos + count > ctx->len) return ERR_PTR(-EINVAL); @@ -41,14 +41,14 @@ static void *parse_bytes(struct dcp_parse_ctx *ctx, size_t count) return ptr; } -static u32 *parse_u32(struct dcp_parse_ctx *ctx) +static const u32 *parse_u32(struct dcp_parse_ctx *ctx) { return parse_bytes(ctx, sizeof(u32)); } -static struct dcp_parse_tag *parse_tag(struct dcp_parse_ctx *ctx) +static const struct dcp_parse_tag *parse_tag(struct dcp_parse_ctx *ctx) { - struct dcp_parse_tag *tag; + const struct dcp_parse_tag *tag; /* Align to 32-bits */ ctx->pos = round_up(ctx->pos, 4); @@ -64,10 +64,10 @@ static struct dcp_parse_tag *parse_tag(struct dcp_parse_ctx *ctx) return tag; } -static struct dcp_parse_tag *parse_tag_of_type(struct dcp_parse_ctx *ctx, +static const struct dcp_parse_tag *parse_tag_of_type(struct dcp_parse_ctx *ctx, enum dcp_parse_type type) { - struct dcp_parse_tag *tag = parse_tag(ctx); + const struct dcp_parse_tag *tag = parse_tag(ctx); if (IS_ERR(tag)) return tag; @@ -80,7 +80,7 @@ static struct dcp_parse_tag *parse_tag_of_type(struct dcp_parse_ctx *ctx, static int skip(struct dcp_parse_ctx *handle) { - struct dcp_parse_tag *tag = parse_tag(handle); + const struct dcp_parse_tag *tag = parse_tag(handle); int ret = 0; int i; @@ -132,7 +132,7 @@ static int skip_pair(struct dcp_parse_ctx *handle) static bool consume_string(struct dcp_parse_ctx *ctx, const char *specimen) { - struct dcp_parse_tag *tag; + const struct dcp_parse_tag *tag; const char *key; ctx->pos = round_up(ctx->pos, 4); @@ -155,7 +155,7 @@ static bool consume_string(struct dcp_parse_ctx *ctx, const char *specimen) /* Caller must free the result */ static char *parse_string(struct dcp_parse_ctx *handle) { - struct dcp_parse_tag *tag = parse_tag_of_type(handle, DCP_TYPE_STRING); + const struct dcp_parse_tag *tag = parse_tag_of_type(handle, DCP_TYPE_STRING); const char *in; char *out; @@ -175,8 +175,8 @@ static char *parse_string(struct dcp_parse_ctx *handle) static int parse_int(struct dcp_parse_ctx *handle, s64 *value) { - void *tag = parse_tag_of_type(handle, DCP_TYPE_INT64); - s64 *in; + const void *tag = parse_tag_of_type(handle, DCP_TYPE_INT64); + const s64 *in; if (IS_ERR(tag)) return PTR_ERR(tag); @@ -192,7 +192,7 @@ static int parse_int(struct dcp_parse_ctx *handle, s64 *value) static int parse_bool(struct dcp_parse_ctx *handle, bool *b) { - struct dcp_parse_tag *tag = parse_tag_of_type(handle, DCP_TYPE_BOOL); + const struct dcp_parse_tag *tag = parse_tag_of_type(handle, DCP_TYPE_BOOL); if (IS_ERR(tag)) return PTR_ERR(tag); @@ -201,10 +201,10 @@ static int parse_bool(struct dcp_parse_ctx *handle, bool *b) return 0; } -static int parse_blob(struct dcp_parse_ctx *handle, size_t size, u8 **blob) +static int parse_blob(struct dcp_parse_ctx *handle, size_t size, u8 const **blob) { - struct dcp_parse_tag *tag = parse_tag_of_type(handle, DCP_TYPE_BLOB); - u8 *out; + const struct dcp_parse_tag *tag = parse_tag_of_type(handle, DCP_TYPE_BLOB); + const u8 *out; if (IS_ERR(tag)) return PTR_ERR(tag); @@ -229,7 +229,7 @@ struct iterator { static int iterator_begin(struct dcp_parse_ctx *handle, struct iterator *it, bool dict) { - struct dcp_parse_tag *tag; + const struct dcp_parse_tag *tag; enum dcp_parse_type type = dict ? DCP_TYPE_DICTIONARY : DCP_TYPE_ARRAY; *it = (struct iterator) { @@ -250,9 +250,9 @@ static int iterator_begin(struct dcp_parse_ctx *handle, struct iterator *it, #define dcp_parse_foreach_in_dict(handle, it) \ for (iterator_begin(handle, &it, true); it.idx < it.len; ++it.idx) -int parse(void *blob, size_t size, struct dcp_parse_ctx *ctx) +int parse(const void *blob, size_t size, struct dcp_parse_ctx *ctx) { - u32 *header; + const u32 *header; *ctx = (struct dcp_parse_ctx) { .blob = blob, @@ -913,7 +913,7 @@ static int parse_mode_in_avep_element(struct dcp_parse_ctx *handle, return ret; } } else if (consume_string(it.handle, "ElementData")) { - u8 *blob; + const u8 *blob; ret = parse_blob(it.handle, sizeof(*cookie), &blob); if (ret) diff --git a/drivers/gpu/drm/apple/parser.h b/drivers/gpu/drm/apple/parser.h index f867416070e49d..a008e220cec3dd 100644 --- a/drivers/gpu/drm/apple/parser.h +++ b/drivers/gpu/drm/apple/parser.h @@ -11,7 +11,7 @@ struct apple_dcp; struct dcp_parse_ctx { struct apple_dcp *dcp; - void *blob; + const void *blob; u32 pos, len; }; @@ -98,7 +98,7 @@ struct dimension { s64 precise_sync_rate; }; -int parse(void *blob, size_t size, struct dcp_parse_ctx *ctx); +int parse(const void *blob, size_t size, struct dcp_parse_ctx *ctx); struct dcp_display_mode *enumerate_modes(struct dcp_parse_ctx *handle, unsigned int *count, int width_mm, int height_mm, unsigned notch_height); From 28ab131b5b043c23ed6198409eb743525ec0f220 Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Sun, 10 Dec 2023 13:27:12 +0100 Subject: [PATCH 1146/3902] drm: apple: epic: Pass full notfiy/report payload to handler The payload is not necessarily epic_std_service_ap_call. The powerlog service on the system endpoint passes serialized dictionaries as payload. Signed-off-by: Janne Grunau --- drivers/gpu/drm/apple/afk.c | 18 +++--------------- drivers/gpu/drm/apple/afk.h | 4 +++- 2 files changed, 6 insertions(+), 16 deletions(-) diff --git a/drivers/gpu/drm/apple/afk.c b/drivers/gpu/drm/apple/afk.c index a11e9f1f5be4d3..52a5bf5f8a6479 100644 --- a/drivers/gpu/drm/apple/afk.c +++ b/drivers/gpu/drm/apple/afk.c @@ -447,21 +447,9 @@ static void afk_recv_handle_std_service(struct apple_dcp_afkep *ep, u32 channel, } if (type == EPIC_TYPE_NOTIFY && eshdr->category == EPIC_CAT_REPORT) { - struct epic_std_service_ap_call *call = payload; - size_t call_size; - - if (payload_size < sizeof(*call)) - return; - - call_size = le32_to_cpu(call->len); - if (payload_size < sizeof(*call) + call_size) - return; - - if (!service->ops->report) - return; - - service->ops->report(service, le32_to_cpu(call->type), - payload + sizeof(*call), call_size); + if (service->ops->report) + service->ops->report(service, le16_to_cpu(eshdr->type), + payload, payload_size); return; } diff --git a/drivers/gpu/drm/apple/afk.h b/drivers/gpu/drm/apple/afk.h index 1fdb4100352b25..737288b1346b28 100644 --- a/drivers/gpu/drm/apple/afk.h +++ b/drivers/gpu/drm/apple/afk.h @@ -49,6 +49,8 @@ struct apple_epic_service { void *cookie; }; +enum epic_subtype; + struct apple_epic_service_ops { const char name[32]; @@ -57,7 +59,7 @@ struct apple_epic_service_ops { int (*call)(struct apple_epic_service *service, u32 idx, const void *data, size_t data_size, void *reply, size_t reply_size); - int (*report)(struct apple_epic_service *service, u32 idx, + int (*report)(struct apple_epic_service *service, enum epic_subtype type, const void *data, size_t data_size); void (*teardown)(struct apple_epic_service *service); }; From 60b52390a3779043d25de630b8615701d98340a8 Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Sun, 10 Dec 2023 13:40:14 +0100 Subject: [PATCH 1147/3902] drm: apple: epic: systemep: Parse "mNits" log events The 13.5 firmware has stopped updating the NITS property on backlight brightness changes. Parse system log events instead which report backlight's brightness in millinits. Fixes the backlight device's "actual_brightness" property used by the systemd backlight service to save and restore brightness. Signed-off-by: Janne Grunau --- drivers/gpu/drm/apple/parser.c | 48 ++++++++++++++++++++++++++++++++ drivers/gpu/drm/apple/parser.h | 9 ++++++ drivers/gpu/drm/apple/systemep.c | 37 ++++++++++++++++++++++++ 3 files changed, 94 insertions(+) diff --git a/drivers/gpu/drm/apple/parser.c b/drivers/gpu/drm/apple/parser.c index a7697ba4f8e64f..dad94e7932e73e 100644 --- a/drivers/gpu/drm/apple/parser.c +++ b/drivers/gpu/drm/apple/parser.c @@ -984,3 +984,51 @@ int parse_sound_mode(struct dcp_parse_ctx *handle, return 0; } EXPORT_SYMBOL_GPL(parse_sound_mode); + +int parse_system_log_mnits(struct dcp_parse_ctx *handle, struct dcp_system_ev_mnits *entry) +{ + struct iterator it; + int ret; + s64 mnits = -1; + s64 idac = -1; + s64 timestamp = -1; + bool type_match = false; + + dcp_parse_foreach_in_dict(handle, it) { + char *key = parse_string(it.handle); + if (IS_ERR(key)) { + ret = PTR_ERR(key); + } else if (!strcmp(key, "mNits")) { + ret = parse_int(it.handle, &mnits); + } else if (!strcmp(key, "iDAC")) { + ret = parse_int(it.handle, &idac); + } else if (!strcmp(key, "logEvent")) { + const char * value = parse_string(it.handle); + if (!IS_ERR_OR_NULL(value)) { + type_match = strcmp(value, "Display (Event Forward)") == 0; + kfree(value); + } + } else if (!strcmp(key, "timestamp")) { + ret = parse_int(it.handle, ×tamp); + } else { + skip(it.handle); + } + + if (!IS_ERR_OR_NULL(key)) + kfree(key); + + if (ret) { + pr_err("dcp parser: failed to parse mNits sys event\n"); + return ret; + } + } + + if (!type_match || mnits < 0 || idac < 0 || timestamp < 0) + return -EINVAL; + + entry->millinits = mnits; + entry->idac = idac; + entry->timestamp = timestamp; + + return 0; +} diff --git a/drivers/gpu/drm/apple/parser.h b/drivers/gpu/drm/apple/parser.h index a008e220cec3dd..2f52e063bbd426 100644 --- a/drivers/gpu/drm/apple/parser.h +++ b/drivers/gpu/drm/apple/parser.h @@ -126,4 +126,13 @@ int parse_sound_mode(struct dcp_parse_ctx *handle, struct snd_pcm_chmap_elem *chmap, struct dcp_sound_cookie *cookie); +struct dcp_system_ev_mnits { + u32 timestamp; + u32 millinits; + u32 idac; +}; + +int parse_system_log_mnits(struct dcp_parse_ctx *handle, + struct dcp_system_ev_mnits *entry); + #endif diff --git a/drivers/gpu/drm/apple/systemep.c b/drivers/gpu/drm/apple/systemep.c index 5383a83f1e6c28..9fe7a0ce495aab 100644 --- a/drivers/gpu/drm/apple/systemep.c +++ b/drivers/gpu/drm/apple/systemep.c @@ -5,6 +5,7 @@ #include "afk.h" #include "dcp.h" +#include "parser.h" static bool enable_verbose_logging; module_param(enable_verbose_logging, bool, 0644); @@ -66,6 +67,41 @@ static void powerlog_init(struct apple_epic_service *service, const char *name, { } +static int powerlog_report(struct apple_epic_service *service, enum epic_subtype type, + const void *data, size_t data_size) +{ + struct dcp_system_ev_mnits mnits; + struct dcp_parse_ctx parse_ctx; + struct apple_dcp *dcp = service->ep->dcp; + int ret; + + dev_dbg(dcp->dev, "systemep[ch:%u]: report type:%02x len:%zu\n", + service->channel, type, data_size); + + if (type != EPIC_SUBTYPE_STD_SERVICE) + return 0; + + ret = parse(data, data_size, &parse_ctx); + if (ret) { + dev_warn(service->ep->dcp->dev, "systemep: failed to parse report: %d\n", ret); + return ret; + } + + ret = parse_system_log_mnits(&parse_ctx, &mnits); + if (ret) { + /* ignore parse errors in the case dcp sends unknown log events */ + dev_dbg(dcp->dev, "systemep: failed to parse mNits event: %d\n", ret); + return 0; + } + + dev_dbg(dcp->dev, "systemep: mNits event: Nits: %u.%03u, iDAC: %u\n", + mnits.millinits / 1000, mnits.millinits % 1000, mnits.idac); + + dcp->brightness.nits = mnits.millinits / 1000; + + return 0; +} + static const struct apple_epic_service_ops systemep_ops[] = { { .name = "system", @@ -74,6 +110,7 @@ static const struct apple_epic_service_ops systemep_ops[] = { { .name = "powerlog-service", .init = powerlog_init, + .report = powerlog_report, }, {} }; From e8236c4dd81b06bb675991dd15dba5281be1a3ea Mon Sep 17 00:00:00 2001 From: Arnd Bergmann Date: Wed, 17 Jan 2024 11:44:10 +0100 Subject: [PATCH 1148/3902] drm: apple: mark local functions static With linux-6.8, the kernel warns about functions that have no extern declaration, so mark both of these static. Fixes: 2d782b0d007d ("gpu: drm: apple: Add sound mode parsing") Signed-off-by: Arnd Bergmann --- drivers/gpu/drm/apple/parser.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/gpu/drm/apple/parser.c b/drivers/gpu/drm/apple/parser.c index dad94e7932e73e..012040b6d3d536 100644 --- a/drivers/gpu/drm/apple/parser.c +++ b/drivers/gpu/drm/apple/parser.c @@ -681,7 +681,7 @@ int parse_epic_service_init(struct dcp_parse_ctx *handle, const char **name, return ret; } -int parse_sample_rate_bit(struct dcp_parse_ctx *handle, unsigned int *ratebit) +static int parse_sample_rate_bit(struct dcp_parse_ctx *handle, unsigned int *ratebit) { s64 rate; int ret = parse_int(handle, &rate); @@ -702,7 +702,7 @@ int parse_sample_rate_bit(struct dcp_parse_ctx *handle, unsigned int *ratebit) return 0; } -int parse_sample_fmtbit(struct dcp_parse_ctx *handle, u64 *fmtbit) +static int parse_sample_fmtbit(struct dcp_parse_ctx *handle, u64 *fmtbit) { s64 sample_size; int ret = parse_int(handle, &sample_size); From ce3ac0f3b10e1623c3510e6d345db1d724e976a6 Mon Sep 17 00:00:00 2001 From: Alyssa Ross Date: Thu, 11 Jan 2024 11:39:10 +0100 Subject: [PATCH 1149/3902] drm/apple: Add missing RTKit Kconfig dependency Signed-off-by: Alyssa Ross --- drivers/gpu/drm/apple/Kconfig | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/gpu/drm/apple/Kconfig b/drivers/gpu/drm/apple/Kconfig index b28b84cef961b1..e2d424b983314a 100644 --- a/drivers/gpu/drm/apple/Kconfig +++ b/drivers/gpu/drm/apple/Kconfig @@ -3,6 +3,7 @@ config DRM_APPLE tristate "DRM Support for Apple display controllers" depends on DRM && OF && ARM64 depends on ARCH_APPLE || COMPILE_TEST + depends on APPLE_RTKIT depends on OF_ADDRESS select DRM_CLIENT_SELECTION select DRM_KMS_HELPER From 8ab40d9a213560e2b1dda82904dae68017c2104a Mon Sep 17 00:00:00 2001 From: Jonathan Gray Date: Mon, 22 Jan 2024 18:54:31 +1100 Subject: [PATCH 1150/3902] drm/apple: spelling fixes Signed-off-by: Jonathan Gray --- drivers/gpu/drm/apple/apple_drv.c | 2 +- drivers/gpu/drm/apple/dcp-internal.h | 2 +- drivers/gpu/drm/apple/dcp.c | 4 ++-- drivers/gpu/drm/apple/iomfb.c | 2 +- drivers/gpu/drm/apple/iomfb_template.c | 2 +- drivers/gpu/drm/apple/iomfb_v13_3.c | 2 +- 6 files changed, 7 insertions(+), 7 deletions(-) diff --git a/drivers/gpu/drm/apple/apple_drv.c b/drivers/gpu/drm/apple/apple_drv.c index de5abab31d7c37..34ce2931b159e8 100644 --- a/drivers/gpu/drm/apple/apple_drv.c +++ b/drivers/gpu/drm/apple/apple_drv.c @@ -459,7 +459,7 @@ static int apple_drm_init_dcp(struct device *dev) ret = dcp_wait_ready(dcp[i], wait); /* There is nothing we can do if a dcp/dcpext does not boot * (successfully). Ignoring it should not do any harm now. - * Needs to reevaluated whenn adding dcpext support. + * Needs to reevaluated when adding dcpext support. */ if (ret) dev_warn(dev, "DCP[%d] not ready: %d\n", i, ret); diff --git a/drivers/gpu/drm/apple/dcp-internal.h b/drivers/gpu/drm/apple/dcp-internal.h index 24d23364e8f415..e625ea574b88ae 100644 --- a/drivers/gpu/drm/apple/dcp-internal.h +++ b/drivers/gpu/drm/apple/dcp-internal.h @@ -95,7 +95,7 @@ struct dcp_panel { int width_mm; /// panel height in millimeter int height_mm; - /// panel has a mini-LED backllight + /// panel has a mini-LED backlight bool has_mini_led; }; diff --git a/drivers/gpu/drm/apple/dcp.c b/drivers/gpu/drm/apple/dcp.c index 4402e7008ac821..dd14c1d94d9f0d 100644 --- a/drivers/gpu/drm/apple/dcp.c +++ b/drivers/gpu/drm/apple/dcp.c @@ -808,7 +808,7 @@ static int dcp_comp_bind(struct device *dev, struct device *main, void *data) if (dcp->notch_height > 0) dev_info(dev, "Detected display with notch of %u pixel\n", dcp->notch_height); - /* intialize brightness scale to a sensible default to avoid divide by 0*/ + /* initialize brightness scale to a sensible default to avoid divide by 0*/ dcp->brightness.scale = 65536; panel_np = of_get_compatible_child(dev->of_node, "apple,panel-mini-led"); if (panel_np) @@ -877,7 +877,7 @@ static int dcp_comp_bind(struct device *dev, struct device *main, void *data) dcp->rtk = devm_apple_rtkit_init(dev, dcp, "mbox", 0, &rtkit_ops); if (IS_ERR(dcp->rtk)) return dev_err_probe(dev, PTR_ERR(dcp->rtk), - "Failed to intialize RTKit"); + "Failed to initialize RTKit"); ret = apple_rtkit_wake(dcp->rtk); if (ret) diff --git a/drivers/gpu/drm/apple/iomfb.c b/drivers/gpu/drm/apple/iomfb.c index b179c183f46529..2944911ced770a 100644 --- a/drivers/gpu/drm/apple/iomfb.c +++ b/drivers/gpu/drm/apple/iomfb.c @@ -274,7 +274,7 @@ static void dcpep_handle_cb(struct apple_dcp *dcp, enum dcp_context_id context, out = in + hdr->in_len; // TODO: verify that in_len and out_len match our prototypes - // for now just clear the out data to have at least consistant results + // for now just clear the out data to have at least consistent results if (hdr->out_len) memset(out, 0, hdr->out_len); diff --git a/drivers/gpu/drm/apple/iomfb_template.c b/drivers/gpu/drm/apple/iomfb_template.c index dc99a4d9f8694b..d2139cd3db2e9d 100644 --- a/drivers/gpu/drm/apple/iomfb_template.c +++ b/drivers/gpu/drm/apple/iomfb_template.c @@ -326,7 +326,7 @@ static void dcpep_cb_unmap_piodma(struct apple_dcp *dcp, /* * Allocate an IOVA contiguous buffer mapped to the DCP. The buffer need not be - * physically contigiuous, however we should save the sgtable in case the + * physically contiguous, however we should save the sgtable in case the * buffer needs to be later mapped for PIODMA. */ static struct dcp_allocate_buffer_resp diff --git a/drivers/gpu/drm/apple/iomfb_v13_3.c b/drivers/gpu/drm/apple/iomfb_v13_3.c index 1ee29112be4543..115490fd9cc6e3 100644 --- a/drivers/gpu/drm/apple/iomfb_v13_3.c +++ b/drivers/gpu/drm/apple/iomfb_v13_3.c @@ -53,7 +53,7 @@ static const iomfb_cb_handler cb_handlers[IOMFB_MAX_CB] = { [110] = trampoline_true, /* create_pmu_service */ [111] = trampoline_true, /* create_iomfb_service */ [112] = trampoline_create_backlight_service, - [113] = trampoline_true, /* create_nvram_servce? */ + [113] = trampoline_true, /* create_nvram_service? */ [114] = trampoline_get_tiling_state, [115] = trampoline_false, /* set_tiling_state */ [120] = dcpep_cb_boot_1, From 8c3b7ff1798da9a09a36c42c771242f0c1696b3b Mon Sep 17 00:00:00 2001 From: Mark Kettenis Date: Thu, 28 Dec 2023 11:41:55 +0100 Subject: [PATCH 1151/3902] drm: apple: backlight: force backlight update after resume If the DCP firmware indicates that it didn't restore the brightness, schedule an update. Wait for 1 frame duration and check if the brightness update has been taken care of by a swap that happened in the meantime. Fixes restoring the brightness after resume when running on a dumb framebuffer where swaps may not happen for a very long time. Signed-off-by: Mark Kettenis --- drivers/gpu/drm/apple/dcp-internal.h | 3 +++ drivers/gpu/drm/apple/dcp.c | 10 +++++++++ drivers/gpu/drm/apple/dcp_backlight.c | 31 +++++++++++++++----------- drivers/gpu/drm/apple/iomfb_template.c | 1 + 4 files changed, 32 insertions(+), 13 deletions(-) diff --git a/drivers/gpu/drm/apple/dcp-internal.h b/drivers/gpu/drm/apple/dcp-internal.h index e625ea574b88ae..7fbca0b6bb9778 100644 --- a/drivers/gpu/drm/apple/dcp-internal.h +++ b/drivers/gpu/drm/apple/dcp-internal.h @@ -212,6 +212,8 @@ struct apple_dcp { /* Workqueue for updating the initial initial brightness */ struct work_struct bl_register_wq; struct mutex bl_register_mutex; + /* Workqueue for updating the brightness */ + struct work_struct bl_update_wq; /* integrated panel if present */ struct dcp_panel panel; @@ -241,6 +243,7 @@ struct apple_dcp { }; int dcp_backlight_register(struct apple_dcp *dcp); +int dcp_backlight_update(struct apple_dcp *dcp); bool dcp_has_panel(struct apple_dcp *dcp); #define DCP_AUDIO_MAX_CHANS 15 diff --git a/drivers/gpu/drm/apple/dcp.c b/drivers/gpu/drm/apple/dcp.c index dd14c1d94d9f0d..166b4d186d62d3 100644 --- a/drivers/gpu/drm/apple/dcp.c +++ b/drivers/gpu/drm/apple/dcp.c @@ -515,6 +515,15 @@ static void dcp_work_register_backlight(struct work_struct *work) mutex_unlock(&dcp->bl_register_mutex); } +static void dcp_work_update_backlight(struct work_struct *work) +{ + struct apple_dcp *dcp; + + dcp = container_of(work, struct apple_dcp, bl_update_wq); + + dcp_backlight_update(dcp); +} + static int dcp_create_piodma_iommu_dev(struct apple_dcp *dcp) { int ret; @@ -835,6 +844,7 @@ static int dcp_comp_bind(struct device *dev, struct device *main, void *data) dcp->connector_type = DRM_MODE_CONNECTOR_eDP; INIT_WORK(&dcp->bl_register_wq, dcp_work_register_backlight); mutex_init(&dcp->bl_register_mutex); + INIT_WORK(&dcp->bl_update_wq, dcp_work_update_backlight); } else if (of_property_match_string(dev->of_node, "apple,connector-type", "HDMI-A") >= 0) dcp->connector_type = DRM_MODE_CONNECTOR_HDMIA; else if (of_property_match_string(dev->of_node, "apple,connector-type", "DP") >= 0) diff --git a/drivers/gpu/drm/apple/dcp_backlight.c b/drivers/gpu/drm/apple/dcp_backlight.c index 0eeb3d6d92c5a2..dfc78f3ce37b0d 100644 --- a/drivers/gpu/drm/apple/dcp_backlight.c +++ b/drivers/gpu/drm/apple/dcp_backlight.c @@ -172,20 +172,8 @@ static int drm_crtc_set_brightness(struct apple_dcp *dcp) return ret; } -static int dcp_set_brightness(struct backlight_device *bd) +int dcp_backlight_update(struct apple_dcp *dcp) { - int ret = 0; - struct apple_dcp *dcp = bl_get_data(bd); - struct drm_modeset_acquire_ctx ctx; - int brightness = backlight_get_brightness(bd); - - DRM_MODESET_LOCK_ALL_BEGIN(dcp->crtc->base.dev, ctx, 0, ret); - - dcp->brightness.dac = calculate_dac(dcp, brightness); - dcp->brightness.update = true; - - DRM_MODESET_LOCK_ALL_END(dcp->crtc->base.dev, ctx, ret); - /* * Do not actively try to change brightness if no mode is set. * TODO: should this be reflected the in backlight's power property? @@ -202,6 +190,23 @@ static int dcp_set_brightness(struct backlight_device *bd) return drm_crtc_set_brightness(dcp); } +static int dcp_set_brightness(struct backlight_device *bd) +{ + int ret = 0; + struct apple_dcp *dcp = bl_get_data(bd); + struct drm_modeset_acquire_ctx ctx; + int brightness = backlight_get_brightness(bd); + + DRM_MODESET_LOCK_ALL_BEGIN(dcp->crtc->base.dev, ctx, 0, ret); + + dcp->brightness.dac = calculate_dac(dcp, brightness); + dcp->brightness.update = true; + + DRM_MODESET_LOCK_ALL_END(dcp->crtc->base.dev, ctx, ret); + + return dcp_backlight_update(dcp); +} + static const struct backlight_ops dcp_backlight_ops = { .options = BL_CORE_SUSPENDRESUME, .get_brightness = dcp_get_brightness, diff --git a/drivers/gpu/drm/apple/iomfb_template.c b/drivers/gpu/drm/apple/iomfb_template.c index d2139cd3db2e9d..72685462a36d6d 100644 --- a/drivers/gpu/drm/apple/iomfb_template.c +++ b/drivers/gpu/drm/apple/iomfb_template.c @@ -497,6 +497,7 @@ static void iomfbep_cb_enable_backlight_message_ap_gated(struct apple_dcp *dcp, * syslog: "[BrightnessLCD.cpp:743][AFK]nitsToDBV: iDAC out of range" */ dcp->brightness.update = true; + schedule_work(&dcp->bl_update_wq); } /* Chunked data transfer for property dictionaries */ From bdc13a82a90a4a45c4488c6b80fe1c2e17052ba3 Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Mon, 22 Jan 2024 22:24:11 +0100 Subject: [PATCH 1152/3902] drm: apple: Fix/remove log messages Add missing training '\n' and remove leftover dev_dbg() statements. Signed-off-by: Janne Grunau --- drivers/gpu/drm/apple/afk.c | 8 +++--- drivers/gpu/drm/apple/apple_drv.c | 4 --- drivers/gpu/drm/apple/dcp.c | 30 +++++++++---------- drivers/gpu/drm/apple/dcp_backlight.c | 2 +- drivers/gpu/drm/apple/iomfb.c | 4 +-- drivers/gpu/drm/apple/iomfb_template.c | 40 ++++++++------------------ 6 files changed, 34 insertions(+), 54 deletions(-) diff --git a/drivers/gpu/drm/apple/afk.c b/drivers/gpu/drm/apple/afk.c index 52a5bf5f8a6479..b3a5cf74e817e9 100644 --- a/drivers/gpu/drm/apple/afk.c +++ b/drivers/gpu/drm/apple/afk.c @@ -138,7 +138,7 @@ static void afk_init_rxtx(struct apple_dcp_afkep *ep, u64 message, u32 bufsz, end; if (tag != ep->bfr_tag) { - dev_err(ep->dcp->dev, "AFK[ep:%02x]: expected tag 0x%x but got 0x%x", + dev_err(ep->dcp->dev, "AFK[ep:%02x]: expected tag 0x%x but got 0x%x\n", ep->endpoint, ep->bfr_tag, tag); return; } @@ -151,7 +151,7 @@ static void afk_init_rxtx(struct apple_dcp_afkep *ep, u64 message, if (base >= ep->bfr_size) { dev_err(ep->dcp->dev, - "AFK[ep:%02x]: requested base 0x%x >= max size 0x%lx", + "AFK[ep:%02x]: requested base 0x%x >= max size 0x%lx\n", ep->endpoint, base, ep->bfr_size); return; } @@ -159,7 +159,7 @@ static void afk_init_rxtx(struct apple_dcp_afkep *ep, u64 message, end = base + size; if (end > ep->bfr_size) { dev_err(ep->dcp->dev, - "AFK[ep:%02x]: requested end 0x%x > max size 0x%lx", + "AFK[ep:%02x]: requested end 0x%x > max size 0x%lx\n", ep->endpoint, end, ep->bfr_size); return; } @@ -168,7 +168,7 @@ static void afk_init_rxtx(struct apple_dcp_afkep *ep, u64 message, bufsz = le32_to_cpu(bfr->hdr->bufsz); if (bufsz + sizeof(*bfr->hdr) != size) { dev_err(ep->dcp->dev, - "AFK[ep:%02x]: ring buffer size 0x%x != expected 0x%lx", + "AFK[ep:%02x]: ring buffer size 0x%x != expected 0x%lx\n", ep->endpoint, bufsz, sizeof(*bfr->hdr)); return; } diff --git a/drivers/gpu/drm/apple/apple_drv.c b/drivers/gpu/drm/apple/apple_drv.c index 34ce2931b159e8..d27a811445920c 100644 --- a/drivers/gpu/drm/apple/apple_drv.c +++ b/drivers/gpu/drm/apple/apple_drv.c @@ -197,9 +197,7 @@ static void apple_crtc_atomic_enable(struct drm_crtc *crtc, if (crtc_state->active_changed && crtc_state->active) { struct apple_crtc *apple_crtc = to_apple_crtc(crtc); - dev_dbg(&apple_crtc->dcp->dev, "%s", __func__); dcp_poweron(apple_crtc->dcp); - dev_dbg(&apple_crtc->dcp->dev, "%s finished", __func__); } if (crtc_state->active) @@ -214,9 +212,7 @@ static void apple_crtc_atomic_disable(struct drm_crtc *crtc, if (crtc_state->active_changed && !crtc_state->active) { struct apple_crtc *apple_crtc = to_apple_crtc(crtc); - dev_dbg(&apple_crtc->dcp->dev, "%s", __func__); dcp_poweroff(apple_crtc->dcp); - dev_dbg(&apple_crtc->dcp->dev, "%s finished", __func__); } if (crtc->state->event && !crtc->state->active) { diff --git a/drivers/gpu/drm/apple/dcp.c b/drivers/gpu/drm/apple/dcp.c index 166b4d186d62d3..1a59081e500807 100644 --- a/drivers/gpu/drm/apple/dcp.c +++ b/drivers/gpu/drm/apple/dcp.c @@ -128,7 +128,7 @@ static void dcp_recv_msg(void *cookie, u8 endpoint, u64 message) afk_receive_message(dcp->dptxep, message); return; default: - WARN(endpoint, "unknown DCP endpoint %hhu", endpoint); + WARN(endpoint, "unknown DCP endpoint %hhu\n", endpoint); } } @@ -137,7 +137,7 @@ static void dcp_rtk_crashed(void *cookie, const void *crashlog, size_t crashlog_ struct apple_dcp *dcp = cookie; dcp->crashed = true; - dev_err(dcp->dev, "DCP has crashed"); + dev_err(dcp->dev, "DCP has crashed\n"); if (dcp->connector) { dcp->connector->connected = 0; schedule_work(&dcp->connector->hotplug_wq); @@ -169,7 +169,7 @@ static int dcp_rtk_shmem_setup(void *cookie, struct apple_rtkit_shmem *bfr) bfr->is_mapped = true; dev_info(dcp->dev, - "shmem_setup: iova: %lx -> pa: %lx -> iomem: %lx", + "shmem_setup: iova: %lx -> pa: %lx -> iomem: %lx\n", (uintptr_t)bfr->iova, (uintptr_t)phy_addr, (uintptr_t)bfr->buffer); } else { @@ -178,7 +178,7 @@ static int dcp_rtk_shmem_setup(void *cookie, struct apple_rtkit_shmem *bfr) if (!bfr->buffer) return -ENOMEM; - dev_info(dcp->dev, "shmem_setup: iova: %lx, buffer: %lx", + dev_info(dcp->dev, "shmem_setup: iova: %lx, buffer: %lx\n", (uintptr_t)bfr->iova, (uintptr_t)bfr->buffer); } @@ -226,7 +226,7 @@ int dcp_crtc_atomic_check(struct drm_crtc *crtc, struct drm_atomic_state *state) needs_modeset = drm_atomic_crtc_needs_modeset(crtc_state) || !dcp->valid_mode; if (!needs_modeset && !dcp->connector->connected) { - dev_err(dcp->dev, "crtc_atomic_check: disconnected but no modeset"); + dev_err(dcp->dev, "crtc_atomic_check: disconnected but no modeset\n"); return -EINVAL; } @@ -239,7 +239,7 @@ int dcp_crtc_atomic_check(struct drm_crtc *crtc, struct drm_atomic_state *state) } if (plane_count > DCP_MAX_PLANES) { - dev_err(dcp->dev, "crtc_atomic_check: Blend supports only 2 layers!"); + dev_err(dcp->dev, "crtc_atomic_check: Blend supports only 2 layers!\n"); return -EINVAL; } @@ -355,17 +355,17 @@ int dcp_start(struct platform_device *pdev) /* start RTKit endpoints */ ret = systemep_init(dcp); if (ret) - dev_warn(dcp->dev, "Failed to start system endpoint: %d", ret); + dev_warn(dcp->dev, "Failed to start system endpoint: %d\n", ret); if (dcp->phy && dcp->fw_compat >= DCP_FIRMWARE_V_13_5) { ret = ibootep_init(dcp); if (ret) - dev_warn(dcp->dev, "Failed to start IBOOT endpoint: %d", + dev_warn(dcp->dev, "Failed to start IBOOT endpoint: %d\n", ret); ret = dptxep_init(dcp); if (ret) - dev_warn(dcp->dev, "Failed to start DPTX endpoint: %d", + dev_warn(dcp->dev, "Failed to start DPTX endpoint: %d\n", ret); else if (dcp->dptxport[0].enabled) { bool connected; @@ -388,7 +388,7 @@ int dcp_start(struct platform_device *pdev) ret = iomfb_start_rtkit(dcp); if (ret) - dev_err(dcp->dev, "Failed to start IOMFB endpoint: %d", ret); + dev_err(dcp->dev, "Failed to start IOMFB endpoint: %d\n", ret); return ret; } @@ -887,12 +887,12 @@ static int dcp_comp_bind(struct device *dev, struct device *main, void *data) dcp->rtk = devm_apple_rtkit_init(dev, dcp, "mbox", 0, &rtkit_ops); if (IS_ERR(dcp->rtk)) return dev_err_probe(dev, PTR_ERR(dcp->rtk), - "Failed to initialize RTKit"); + "Failed to initialize RTKit\n"); ret = apple_rtkit_wake(dcp->rtk); if (ret) return dev_err_probe(dev, ret, - "Failed to boot RTKit: %d", ret); + "Failed to boot RTKit: %d\n", ret); return ret; } @@ -960,7 +960,7 @@ static int dcp_platform_probe(struct platform_device *pdev) dcp->phy = devm_phy_optional_get(dev, "dp-phy"); if (IS_ERR(dcp->phy)) { - dev_err(dev, "Failed to get dp-phy: %ld", PTR_ERR(dcp->phy)); + dev_err(dev, "Failed to get dp-phy: %ld\n", PTR_ERR(dcp->phy)); return PTR_ERR(dcp->phy); } if (dcp->phy) { @@ -987,7 +987,7 @@ static int dcp_platform_probe(struct platform_device *pdev) IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING, "dp2hdmi-hpd-irq", dcp); if (ret < 0) { - dev_err(dev, "failed to request HDMI hpd irq %d: %d", + dev_err(dev, "failed to request HDMI hpd irq %d: %d\n", irq, ret); return ret; } @@ -1010,7 +1010,7 @@ static int dcp_platform_probe(struct platform_device *pdev) if (!ret) { dcp->xbar = devm_mux_control_get(dev, "dp-xbar"); if (IS_ERR(dcp->xbar)) { - dev_err(dev, "Failed to get dp-xbar: %ld", PTR_ERR(dcp->xbar)); + dev_err(dev, "Failed to get dp-xbar: %ld\n", PTR_ERR(dcp->xbar)); return PTR_ERR(dcp->xbar); } ret = mux_control_select(dcp->xbar, mux_index); diff --git a/drivers/gpu/drm/apple/dcp_backlight.c b/drivers/gpu/drm/apple/dcp_backlight.c index dfc78f3ce37b0d..ed3b240ead8557 100644 --- a/drivers/gpu/drm/apple/dcp_backlight.c +++ b/drivers/gpu/drm/apple/dcp_backlight.c @@ -99,7 +99,7 @@ static u32 interpolate(int val, int min, int max, u32 *tbl, size_t tbl_size) size_t index = interpolated / SCALE_FACTOR; - if (WARN(index + 1 >= tbl_size, "invalid index %zu for brightness %u", index, val)) + if (WARN(index + 1 >= tbl_size, "invalid index %zu for brightness %u\n", index, val)) return tbl[tbl_size / 2]; frac = interpolated & (SCALE_FACTOR - 1); diff --git a/drivers/gpu/drm/apple/iomfb.c b/drivers/gpu/drm/apple/iomfb.c index 2944911ced770a..9fe8487053efeb 100644 --- a/drivers/gpu/drm/apple/iomfb.c +++ b/drivers/gpu/drm/apple/iomfb.c @@ -326,7 +326,7 @@ static void dcpep_got_msg(struct apple_dcp *dcp, u64 message) channel_offset = dcp_channel_offset(ctx_id); if (channel_offset < 0) { - dev_warn(dcp->dev, "invalid context received %u", ctx_id); + dev_warn(dcp->dev, "invalid context received %u\n", ctx_id); return; } @@ -482,7 +482,7 @@ void dcp_flush(struct drm_crtc *crtc, struct drm_atomic_state *state) if (dcp_channel_busy(&dcp->ch_cmd)) { - dev_err(dcp->dev, "unexpected busy command channel"); + dev_err(dcp->dev, "unexpected busy command channel\n"); /* HACK: issue a delayed vblank event to avoid timeouts in * drm_atomic_helper_wait_for_vblanks(). */ diff --git a/drivers/gpu/drm/apple/iomfb_template.c b/drivers/gpu/drm/apple/iomfb_template.c index 72685462a36d6d..d081a3c83dfebe 100644 --- a/drivers/gpu/drm/apple/iomfb_template.c +++ b/drivers/gpu/drm/apple/iomfb_template.c @@ -299,7 +299,7 @@ static void dcpep_cb_unmap_piodma(struct apple_dcp *dcp, struct dcp_mem_descriptor *memdesc; if (resp->buffer >= ARRAY_SIZE(dcp->memdesc)) { - dev_warn(dcp->dev, "unmap request for out of range buffer %llu", + dev_warn(dcp->dev, "unmap request for out of range buffer %llu\n", resp->buffer); return; } @@ -308,14 +308,14 @@ static void dcpep_cb_unmap_piodma(struct apple_dcp *dcp, if (!memdesc->buf) { dev_warn(dcp->dev, - "unmap for non-mapped buffer %llu iova:0x%08llx", + "unmap for non-mapped buffer %llu iova:0x%08llx\n", resp->buffer, resp->dva); return; } if (memdesc->dva != resp->dva) { dev_warn(dcp->dev, "unmap buffer %llu address mismatch " - "memdesc.dva:%llx dva:%llx", resp->buffer, + "memdesc.dva:%llx dva:%llx\n", resp->buffer, memdesc->dva, resp->dva); return; } @@ -343,7 +343,7 @@ dcpep_cb_allocate_buffer(struct apple_dcp *dcp, find_first_zero_bit(dcp->memdesc_map, DCP_MAX_MAPPINGS); if (resp.mem_desc_id >= DCP_MAX_MAPPINGS) { - dev_warn(dcp->dev, "DCP overflowed mapping table, ignoring"); + dev_warn(dcp->dev, "DCP overflowed mapping table, ignoring\n"); resp.dva_size = 0; resp.mem_desc_id = 0; return resp; @@ -378,7 +378,7 @@ static u8 dcpep_cb_release_mem_desc(struct apple_dcp *dcp, u32 *mem_desc_id) } if (!test_and_clear_bit(id, dcp->memdesc_map)) { - dev_warn(dcp->dev, "unmap request for unused mem_desc_id %u", + dev_warn(dcp->dev, "unmap request for unused mem_desc_id %u\n", id); return 0; } @@ -428,7 +428,7 @@ dcpep_cb_map_physical(struct apple_dcp *dcp, struct dcp_map_physical_req *req) u32 id; if (!is_disp_register(dcp, req->paddr, req->paddr + size - 1)) { - dev_err(dcp->dev, "refusing to map phys address %llx size %llx", + dev_err(dcp->dev, "refusing to map phys address %llx size %llx\n", req->paddr, req->size); return (struct dcp_map_physical_resp){}; } @@ -457,7 +457,7 @@ static struct DCP_FW_NAME(dcp_map_reg_resp) dcpep_cb_map_reg(struct apple_dcp *d struct DCP_FW_NAME(dcp_map_reg_req) *req) { if (req->index >= dcp->nr_disp_registers) { - dev_warn(dcp->dev, "attempted to read invalid reg index %u", + dev_warn(dcp->dev, "attempted to read invalid reg index %u\n", req->index); return (struct DCP_FW_NAME(dcp_map_reg_resp)){ .ret = 1 }; @@ -602,7 +602,7 @@ static void boot_done(struct apple_dcp *dcp, void *out, void *cookie) { struct dcp_channel *ch = &dcp->ch_cb; u8 *succ = ch->output[ch->depth - 1]; - dev_dbg(dcp->dev, "boot done"); + dev_dbg(dcp->dev, "boot done\n"); *succ = true; dcp_ack(dcp, DCP_CONTEXT_CB); @@ -717,7 +717,6 @@ static void release_swap_cookie(struct kref *ref) static void dcp_swap_cleared(struct apple_dcp *dcp, void *data, void *cookie) { struct DCP_FW_NAME(dcp_swap_submit_resp) *resp = data; - dev_dbg(dcp->dev, "%s", __func__); if (cookie) { struct dcp_swap_cookie *info = cookie; @@ -748,7 +747,6 @@ static void dcp_swap_clear_started(struct apple_dcp *dcp, void *data, void *cookie) { struct dcp_swap_start_resp *resp = data; - dev_dbg(dcp->dev, "%s swap_id: %u", __func__, resp->swap_id); DCP_FW_UNION(dcp->swap).swap.swap_id = resp->swap_id; if (cookie) { @@ -762,7 +760,6 @@ static void dcp_swap_clear_started(struct apple_dcp *dcp, void *data, static void dcp_on_final(struct apple_dcp *dcp, void *out, void *cookie) { struct dcp_wait_cookie *wait = cookie; - dev_dbg(dcp->dev, "%s", __func__); if (wait) { complete(&wait->done); @@ -775,7 +772,6 @@ static void dcp_on_set_power_state(struct apple_dcp *dcp, void *out, void *cooki struct dcp_set_power_state_req req = { .unklong = 1, }; - dev_dbg(dcp->dev, "%s", __func__); dcp_set_power_state(dcp, false, &req, dcp_on_final, cookie); } @@ -791,7 +787,6 @@ static void dcp_on_set_parameter(struct apple_dcp *dcp, void *out, void *cookie) .count = 1, #endif }; - dev_dbg(dcp->dev, "%s", __func__); dcp_set_parameter_dcp(dcp, false, ¶m, dcp_on_set_power_state, cookie); } @@ -803,8 +798,6 @@ void DCP_FW_NAME(iomfb_poweron)(struct apple_dcp *dcp) u32 handle; dev_info(dcp->dev, "dcp_poweron() starting\n"); - dev_dbg(dcp->dev, "%s", __func__); - cookie = kzalloc(sizeof(*cookie), GFP_KERNEL); if (!cookie) return; @@ -826,7 +819,7 @@ void DCP_FW_NAME(iomfb_poweron)(struct apple_dcp *dcp) ret = wait_for_completion_timeout(&cookie->done, msecs_to_jiffies(500)); if (ret == 0) - dev_warn(dcp->dev, "wait for power timed out"); + dev_warn(dcp->dev, "wait for power timed out\n"); kref_put(&cookie->refcount, release_wait_cookie);; @@ -874,8 +867,6 @@ void DCP_FW_NAME(iomfb_poweroff)(struct apple_dcp *dcp) struct dcp_swap_start_req swap_req = { 0 }; struct DCP_FW_NAME(dcp_swap_submit_req) *swap = &DCP_FW_UNION(dcp->swap); - dev_dbg(dcp->dev, "%s", __func__); - cookie = kzalloc(sizeof(*cookie), GFP_KERNEL); if (!cookie) return; @@ -923,7 +914,7 @@ void DCP_FW_NAME(iomfb_poweroff)(struct apple_dcp *dcp) return; } - dev_dbg(dcp->dev, "%s: clear swap submitted: %u", __func__, swap_id); + dev_dbg(dcp->dev, "%s: clear swap submitted: %u\n", __func__, swap_id); poff_cookie = kzalloc(sizeof(*poff_cookie), GFP_KERNEL); if (!poff_cookie) @@ -939,14 +930,13 @@ void DCP_FW_NAME(iomfb_poweroff)(struct apple_dcp *dcp) msecs_to_jiffies(1000)); if (ret == 0) - dev_warn(dcp->dev, "setPowerState(0) timeout %u ms", 1000); + dev_warn(dcp->dev, "setPowerState(0) timeout %u ms\n", 1000); else if (ret > 0) dev_dbg(dcp->dev, "setPowerState(0) finished with %d ms to spare", jiffies_to_msecs(ret)); kref_put(&poff_cookie->refcount, release_wait_cookie); - dev_dbg(dcp->dev, "%s: setPowerState(0) done", __func__); dev_info(dcp->dev, "dcp_poweroff() done\n"); } @@ -990,11 +980,9 @@ void DCP_FW_NAME(iomfb_sleep)(struct apple_dcp *dcp) msecs_to_jiffies(1000)); if (ret == 0) - dev_warn(dcp->dev, "setDCPPower(0) timeout %u ms", 1000); + dev_warn(dcp->dev, "setDCPPower(0) timeout %u ms\n", 1000); kref_put(&cookie->refcount, release_wait_cookie); - dev_dbg(dcp->dev, "%s: setDCPPower(0) done", __func__); - dev_info(dcp->dev, "dcp_sleep() done\n"); } @@ -1163,7 +1151,6 @@ static void dcp_swap_started(struct apple_dcp *dcp, void *data, void *cookie) static void do_swap(struct apple_dcp *dcp, void *data, void *cookie) { struct dcp_swap_start_req start_req = { 0 }; - dev_dbg(dcp->dev, "%s", __func__); if (dcp->connector && dcp->connector->connected) dcp_swap_start(dcp, false, &start_req, dcp_swap_started, NULL); @@ -1175,7 +1162,6 @@ static void complete_set_digital_out_mode(struct apple_dcp *dcp, void *data, void *cookie) { struct dcp_wait_cookie *wait = cookie; - dev_dbg(dcp->dev, "%s", __func__); if (wait) { complete(&wait->done); @@ -1242,7 +1228,6 @@ int DCP_FW_NAME(iomfb_modeset)(struct apple_dcp *dcp, * modesets. Add an extra 500ms to safe side that the modeset * call has returned. */ - dev_dbg(dcp->dev, "%s - wait for modeset", __func__); ret = wait_for_completion_timeout(&cookie->done, msecs_to_jiffies(8500)); @@ -1276,7 +1261,6 @@ void DCP_FW_NAME(iomfb_flush)(struct apple_dcp *dcp, struct drm_crtc *crtc, stru struct DCP_FW_NAME(dcp_swap_submit_req) *req = &DCP_FW_UNION(dcp->swap); int plane_idx, l; int has_surface = 0; - dev_dbg(dcp->dev, "%s", __func__); crtc_state = drm_atomic_get_new_crtc_state(state, crtc); From d8868f36322771d90756ccf86a28be9d1ade63a8 Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Tue, 26 Mar 2024 22:05:50 +0100 Subject: [PATCH 1153/3902] drm: apple: dptx: Debounce HPD by simple msleep() Not necessarily only a debounce but 500ms sleep in the HPD interrupt handler seems to make the modeset more reliable on M2* desktop devices. Signed-off-by: Janne Grunau --- drivers/gpu/drm/apple/dcp.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/drivers/gpu/drm/apple/dcp.c b/drivers/gpu/drm/apple/dcp.c index 1a59081e500807..b4d7009b5990e1 100644 --- a/drivers/gpu/drm/apple/dcp.c +++ b/drivers/gpu/drm/apple/dcp.c @@ -329,6 +329,12 @@ static irqreturn_t dcp_dp2hdmi_hpd(int irq, void *data) */ dev_info(dcp->dev, "DP2HDMI HPD irq, connected:%d\n", connected); + if (connected) { + msleep(500); + connected = gpiod_get_value_cansleep(dcp->hdmi_hpd); + dev_info(dcp->dev, "DP2HDMI HPD irq, 500ms debounce: connected:%d\n", connected); + } + if (connected) dcp_dptx_connect(dcp, 0); From a07381a074835626116f12f9f3ef48ca723789b4 Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Sun, 21 Jan 2024 14:47:56 +0100 Subject: [PATCH 1154/3902] drm: apple: Add Kconfig option for audio Signed-off-by: Janne Grunau --- drivers/gpu/drm/apple/Kconfig | 7 +++++++ drivers/gpu/drm/apple/parser.c | 8 ++++++++ 2 files changed, 15 insertions(+) diff --git a/drivers/gpu/drm/apple/Kconfig b/drivers/gpu/drm/apple/Kconfig index e2d424b983314a..bc7c875ac10b1b 100644 --- a/drivers/gpu/drm/apple/Kconfig +++ b/drivers/gpu/drm/apple/Kconfig @@ -13,3 +13,10 @@ config DRM_APPLE select MULTIPLEXER help Say Y if you have an Apple Silicon chipset. + +config DRM_APPLE_AUDIO + bool "DisplayPort/HDMI Audio support" + default y + depends on DRM_APPLE + depends on SND + select SND_PCM diff --git a/drivers/gpu/drm/apple/parser.c b/drivers/gpu/drm/apple/parser.c index 012040b6d3d536..700a31883eac8e 100644 --- a/drivers/gpu/drm/apple/parser.c +++ b/drivers/gpu/drm/apple/parser.c @@ -7,7 +7,9 @@ #include #include +#if IS_ENABLED(CONFIG_DRM_APPLE_AUDIO) #include // for sound format masks +#endif #include "parser.h" #include "trace.h" @@ -119,6 +121,7 @@ static int skip(struct dcp_parse_ctx *handle) } } +#if IS_ENABLED(CONFIG_DRM_APPLE_AUDIO) static int skip_pair(struct dcp_parse_ctx *handle) { int ret; @@ -151,6 +154,7 @@ static bool consume_string(struct dcp_parse_ctx *ctx, const char *specimen) skip(ctx); return true; } +#endif /* Caller must free the result */ static char *parse_string(struct dcp_parse_ctx *handle) @@ -201,6 +205,7 @@ static int parse_bool(struct dcp_parse_ctx *handle, bool *b) return 0; } +#if IS_ENABLED(CONFIG_DRM_APPLE_AUDIO) static int parse_blob(struct dcp_parse_ctx *handle, size_t size, u8 const **blob) { const struct dcp_parse_tag *tag = parse_tag_of_type(handle, DCP_TYPE_BLOB); @@ -220,6 +225,7 @@ static int parse_blob(struct dcp_parse_ctx *handle, size_t size, u8 const **blob *blob = out; return 0; } +#endif struct iterator { struct dcp_parse_ctx *handle; @@ -681,6 +687,7 @@ int parse_epic_service_init(struct dcp_parse_ctx *handle, const char **name, return ret; } +#if IS_ENABLED(CONFIG_DRM_APPLE_AUDIO) static int parse_sample_rate_bit(struct dcp_parse_ctx *handle, unsigned int *ratebit) { s64 rate; @@ -984,6 +991,7 @@ int parse_sound_mode(struct dcp_parse_ctx *handle, return 0; } EXPORT_SYMBOL_GPL(parse_sound_mode); +#endif int parse_system_log_mnits(struct dcp_parse_ctx *handle, struct dcp_system_ev_mnits *entry) { From b2df14e004ae1fef17f8ce5be89df28f8ef073d7 Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Mon, 25 Dec 2023 18:03:37 +0100 Subject: [PATCH 1155/3902] drm: apple: iomfb: export property dicts in connector debugfs Signed-off-by: Janne Grunau --- drivers/gpu/drm/apple/Makefile | 1 + drivers/gpu/drm/apple/apple_drv.c | 2 + drivers/gpu/drm/apple/connector.c | 122 +++++++++++++++++++++++++ drivers/gpu/drm/apple/connector.h | 39 ++++++++ drivers/gpu/drm/apple/dcp.h | 15 +-- drivers/gpu/drm/apple/iomfb_template.c | 5 +- 6 files changed, 168 insertions(+), 16 deletions(-) create mode 100644 drivers/gpu/drm/apple/connector.c create mode 100644 drivers/gpu/drm/apple/connector.h diff --git a/drivers/gpu/drm/apple/Makefile b/drivers/gpu/drm/apple/Makefile index 50b7682ee4ccdb..f4ad014c0d21d4 100644 --- a/drivers/gpu/drm/apple/Makefile +++ b/drivers/gpu/drm/apple/Makefile @@ -5,6 +5,7 @@ CFLAGS_trace.o = -I$(src) appledrm-y := apple_drv.o apple_dcp-y := afk.o dcp.o dcp_backlight.o dptxep.o iomfb.o parser.o systemep.o +apple_dcp-y += connector.o apple_dcp-y += ibootep.o apple_dcp-y += iomfb_v12_3.o apple_dcp-y += iomfb_v13_3.o diff --git a/drivers/gpu/drm/apple/apple_drv.c b/drivers/gpu/drm/apple/apple_drv.c index d27a811445920c..063ff1410d5d25 100644 --- a/drivers/gpu/drm/apple/apple_drv.c +++ b/drivers/gpu/drm/apple/apple_drv.c @@ -296,6 +296,7 @@ static const struct drm_connector_funcs apple_connector_funcs = { .atomic_duplicate_state = drm_atomic_helper_connector_duplicate_state, .atomic_destroy_state = drm_atomic_helper_connector_destroy_state, .detect = apple_connector_detect, + .debugfs_init = apple_connector_debugfs_init, }; static const struct drm_connector_helper_funcs apple_connector_helper_funcs = { @@ -344,6 +345,7 @@ static int apple_probe_per_dcp(struct device *dev, enc->base.possible_crtcs = drm_crtc_mask(&crtc->base); connector = kzalloc(sizeof(*connector), GFP_KERNEL); + mutex_init(&connector->chunk_lock); drm_connector_helper_add(&connector->base, &apple_connector_helper_funcs); diff --git a/drivers/gpu/drm/apple/connector.c b/drivers/gpu/drm/apple/connector.c new file mode 100644 index 00000000000000..a39bd249697d90 --- /dev/null +++ b/drivers/gpu/drm/apple/connector.c @@ -0,0 +1,122 @@ +// SPDX-License-Identifier: GPL-2.0+ OR MIT +/* + * Copyright (C) The Asahi Linux Contributors + */ + +#include +#include +#include +#include +#include + +#include + +#include "connector.h" +#include "dcp-internal.h" + +enum dcp_chunk_type { + DCP_CHUNK_COLOR_ELEMENTS, + DCP_CHUNK_TIMING_ELELMENTS, + DCP_CHUNK_DISPLAY_ATTRIBUTES, + DCP_CHUNK_TRANSPORT, + DCP_CHUNK_NUM_TYPES, +}; + +static int chunk_show(struct seq_file *m, + enum dcp_chunk_type chunk_type) +{ + struct apple_connector *apple_con = m->private; + struct dcp_chunks *chunk = NULL; + + mutex_lock(&apple_con->chunk_lock); + + switch (chunk_type) { + case DCP_CHUNK_COLOR_ELEMENTS: + chunk = &apple_con->color_elements; + break; + case DCP_CHUNK_TIMING_ELELMENTS: + chunk = &apple_con->timing_elements; + break; + case DCP_CHUNK_DISPLAY_ATTRIBUTES: + chunk = &apple_con->display_attributes; + break; + case DCP_CHUNK_TRANSPORT: + chunk = &apple_con->transport; + break; + default: + break; + } + + if (chunk) + seq_write(m, chunk->data, chunk->length); + + mutex_unlock(&apple_con->chunk_lock); + + return 0; +} + +#define CONNECTOR_DEBUGFS_ENTRY(name, type) \ +static int chunk_ ## name ## _show(struct seq_file *m, void *data) \ +{ \ + return chunk_show(m, type); \ +} \ +static int chunk_ ## name ## _open(struct inode *inode, struct file *file) \ +{ \ + return single_open(file, chunk_ ## name ## _show, inode->i_private); \ +} \ +static const struct file_operations chunk_ ## name ## _fops = { \ + .owner = THIS_MODULE, \ + .open = chunk_ ## name ## _open, \ + .read = seq_read, \ + .llseek = seq_lseek, \ + .release = single_release, \ +} + +CONNECTOR_DEBUGFS_ENTRY(color, DCP_CHUNK_COLOR_ELEMENTS); +CONNECTOR_DEBUGFS_ENTRY(timing, DCP_CHUNK_TIMING_ELELMENTS); +CONNECTOR_DEBUGFS_ENTRY(display_attribs, DCP_CHUNK_DISPLAY_ATTRIBUTES); +CONNECTOR_DEBUGFS_ENTRY(transport, DCP_CHUNK_TRANSPORT); + +void apple_connector_debugfs_init(struct drm_connector *connector, struct dentry *root) +{ + struct apple_connector *apple_con = to_apple_connector(connector); + + debugfs_create_file("ColorElements", 0444, root, apple_con, + &chunk_color_fops); + debugfs_create_file("TimingElements", 0444, root, apple_con, + &chunk_timing_fops); + debugfs_create_file("DisplayAttributes", 0444, root, apple_con, + &chunk_display_attribs_fops); + debugfs_create_file("Transport", 0444, root, apple_con, + &chunk_transport_fops); +} +EXPORT_SYMBOL(apple_connector_debugfs_init); + +static void dcp_connector_set_dict(struct apple_connector *connector, + struct dcp_chunks *dict, + struct dcp_chunks *chunks) +{ + if (dict->data) + devm_kfree(&connector->dcp->dev, dict->data); + + *dict = *chunks; +} + +void dcp_connector_update_dict(struct apple_connector *connector, const char *key, + struct dcp_chunks *chunks) +{ + mutex_lock(&connector->chunk_lock); + if (!strcmp(key, "ColorElements")) + dcp_connector_set_dict(connector, &connector->color_elements, chunks); + else if (!strcmp(key, "TimingElements")) + dcp_connector_set_dict(connector, &connector->timing_elements, chunks); + else if (!strcmp(key, "DisplayAttributes")) + dcp_connector_set_dict(connector, &connector->display_attributes, chunks); + else if (!strcmp(key, "Transport")) + dcp_connector_set_dict(connector, &connector->transport, chunks); + + chunks->data = NULL; + chunks->length = 0; + + mutex_unlock(&connector->chunk_lock); +} diff --git a/drivers/gpu/drm/apple/connector.h b/drivers/gpu/drm/apple/connector.h new file mode 100644 index 00000000000000..79a1eef8c32da8 --- /dev/null +++ b/drivers/gpu/drm/apple/connector.h @@ -0,0 +1,39 @@ +// SPDX-License-Identifier: GPL-2.0-only OR MIT +/* "Copyright" 2021 Alyssa Rosenzweig */ + +#ifndef __APPLE_CONNECTOR_H__ +#define __APPLE_CONNECTOR_H__ + +#include + +#include +#include "drm/drm_connector.h" + +#include "dcp-internal.h" + +void dcp_hotplug(struct work_struct *work); + +struct apple_connector { + struct drm_connector base; + bool connected; + + struct platform_device *dcp; + + /* Workqueue for sending hotplug events to the associated device */ + struct work_struct hotplug_wq; + + struct mutex chunk_lock; + + struct dcp_chunks color_elements; + struct dcp_chunks timing_elements; + struct dcp_chunks display_attributes; + struct dcp_chunks transport; +}; + +#define to_apple_connector(x) container_of(x, struct apple_connector, base) + +void apple_connector_debugfs_init(struct drm_connector *connector, struct dentry *root); + +void dcp_connector_update_dict(struct apple_connector *connector, const char *key, + struct dcp_chunks *chunks); +#endif diff --git a/drivers/gpu/drm/apple/dcp.h b/drivers/gpu/drm/apple/dcp.h index 298520c0832e93..52c5c94a2a8bbe 100644 --- a/drivers/gpu/drm/apple/dcp.h +++ b/drivers/gpu/drm/apple/dcp.h @@ -8,6 +8,7 @@ #include #include +#include "connector.h" #include "dcp-internal.h" #include "parser.h" @@ -22,20 +23,6 @@ struct apple_crtc { #define to_apple_crtc(x) container_of(x, struct apple_crtc, base) -void dcp_hotplug(struct work_struct *work); - -struct apple_connector { - struct drm_connector base; - bool connected; - - struct platform_device *dcp; - - /* Workqueue for sending hotplug events to the associated device */ - struct work_struct hotplug_wq; -}; - -#define to_apple_connector(x) container_of(x, struct apple_connector, base) - struct apple_encoder { struct drm_encoder base; }; diff --git a/drivers/gpu/drm/apple/iomfb_template.c b/drivers/gpu/drm/apple/iomfb_template.c index d081a3c83dfebe..d74f2b502821a1 100644 --- a/drivers/gpu/drm/apple/iomfb_template.c +++ b/drivers/gpu/drm/apple/iomfb_template.c @@ -590,9 +590,10 @@ static u8 dcpep_cb_prop_end(struct apple_dcp *dcp, { u8 resp = dcpep_process_chunks(dcp, req); - /* Reset for the next transfer */ - devm_kfree(dcp->dev, dcp->chunks.data); + /* move chunked data to connector to provide it via debugfs */ + dcp_connector_update_dict(dcp->connector, req->key, &dcp->chunks); dcp->chunks.data = NULL; + dcp->chunks.length = 0; return resp; } From 3ff44534f3457ae2e5792a03c3abf1b507824fae Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20Povi=C5=A1er?= Date: Mon, 13 Feb 2023 14:55:13 +0100 Subject: [PATCH 1156/3902] gpu: drm: apple: Expose injecting of EPIC calls via debugfs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Martin Povišer Co-developed-by: Janne Grunau Signed-off-by: Janne Grunau --- drivers/gpu/drm/apple/Kconfig | 5 + drivers/gpu/drm/apple/afk.c | 161 +++++++++++++++++++++++++++ drivers/gpu/drm/apple/afk.h | 8 ++ drivers/gpu/drm/apple/connector.c | 29 +++++ drivers/gpu/drm/apple/dcp-internal.h | 3 + 5 files changed, 206 insertions(+) diff --git a/drivers/gpu/drm/apple/Kconfig b/drivers/gpu/drm/apple/Kconfig index bc7c875ac10b1b..d8ae51282e5300 100644 --- a/drivers/gpu/drm/apple/Kconfig +++ b/drivers/gpu/drm/apple/Kconfig @@ -20,3 +20,8 @@ config DRM_APPLE_AUDIO depends on DRM_APPLE depends on SND select SND_PCM + +config DRM_APPLE_DEBUG + bool "Enable additional driver debugging" + depends on DRM_APPLE + depends on EXPERT # only for developers diff --git a/drivers/gpu/drm/apple/afk.c b/drivers/gpu/drm/apple/afk.c index b3a5cf74e817e9..218c28dfe84249 100644 --- a/drivers/gpu/drm/apple/afk.c +++ b/drivers/gpu/drm/apple/afk.c @@ -2,7 +2,9 @@ /* Copyright 2022 Sven Peter */ #include +#include #include +#include #include #include #include @@ -181,6 +183,18 @@ static void afk_init_rxtx(struct apple_dcp_afkep *ep, u64 message, afk_send(ep, FIELD_PREP(RBEP_TYPE, RBEP_START)); } +#if IS_ENABLED(CONFIG_DRM_APPLE_DEBUG) +static void afk_populate_service_debugfs(struct apple_epic_service *srv); +static void afk_remove_service_debugfs(struct apple_epic_service *srv); +#else +static void afk_populate_service_debugfs(struct apple_epic_service *srv) +{ +} +static void afk_remove_service_debugfs(struct apple_epic_service *srv) +{ +} +#endif + static const struct apple_epic_service_ops * afk_match_service(struct apple_dcp_afkep *ep, const char *name) { @@ -284,6 +298,9 @@ static void afk_recv_handle_init(struct apple_dcp_afkep *ep, u32 channel, ops->init(&ep->services[ch_idx], epic_name, epic_class, epic_unit); dev_info(ep->dcp->dev, "AFK[ep:%02x]: new service %s on channel %d\n", ep->endpoint, service_name, channel); + + afk_populate_service_debugfs(&ep->services[ch_idx]); + free: kfree(epic_name); kfree(epic_class); @@ -302,6 +319,8 @@ static void afk_recv_handle_teardown(struct apple_dcp_afkep *ep, u32 channel) return; } + afk_remove_service_debugfs(service); + // TODO: think through what locking is necessary spin_lock_irqsave(&service->lock, flags); service->enabled = false; @@ -989,3 +1008,145 @@ int afk_service_call(struct apple_epic_service *service, u16 group, u32 command, kfree(bfr); return ret; } + +#if IS_ENABLED(CONFIG_DRM_APPLE_DEBUG) + +#define AFK_DEBUGFS_MAX_REPLY 8192 + +static ssize_t service_call_write_file(struct file *file, const char __user *user_buf, + size_t count, loff_t *ppos) +{ + struct apple_epic_service *srv = file->private_data; + void *buf; + int ret; + struct { + u32 group; + u32 command; + } call_info; + + if (count < sizeof(call_info)) + return -EINVAL; + if (!srv->debugfs.scratch) { + srv->debugfs.scratch = \ + devm_kzalloc(srv->ep->dcp->dev, AFK_DEBUGFS_MAX_REPLY, GFP_KERNEL); + if (!srv->debugfs.scratch) + return -ENOMEM; + } + + ret = copy_from_user(&call_info, user_buf, sizeof(call_info)); + if (ret == sizeof(call_info)) + return -EFAULT; + user_buf += sizeof(call_info); + count -= sizeof(call_info); + + buf = kmalloc(count, GFP_KERNEL); + if (!buf) + return -ENOMEM; + ret = copy_from_user(buf, user_buf, count); + if (ret == count) { + kfree(buf); + return -EFAULT; + } + + memset(srv->debugfs.scratch, 0, AFK_DEBUGFS_MAX_REPLY); + dma_mb(); + + ret = afk_service_call(srv, call_info.group, call_info.command, buf, count, 0, + srv->debugfs.scratch, AFK_DEBUGFS_MAX_REPLY, 0); + kfree(buf); + + if (ret < 0) + return ret; + + return count + sizeof(call_info); +} + +static ssize_t service_call_read_file(struct file *file, char __user *user_buf, + size_t count, loff_t *ppos) +{ + struct apple_epic_service *srv = file->private_data; + + if (!srv->debugfs.scratch) + return -EINVAL; + + return simple_read_from_buffer(user_buf, count, ppos, + srv->debugfs.scratch, AFK_DEBUGFS_MAX_REPLY); +} + +static const struct file_operations service_call_fops = { + .open = simple_open, + .write = service_call_write_file, + .read = service_call_read_file, +}; + +static ssize_t service_raw_call_write_file(struct file *file, const char __user *user_buf, + size_t count, loff_t *ppos) +{ + struct apple_epic_service *srv = file->private_data; + u32 retcode; + int ret; + + if (!srv->debugfs.scratch) { + srv->debugfs.scratch = \ + devm_kzalloc(srv->ep->dcp->dev, AFK_DEBUGFS_MAX_REPLY, GFP_KERNEL); + if (!srv->debugfs.scratch) + return -ENOMEM; + } + + memset(srv->debugfs.scratch, 0, AFK_DEBUGFS_MAX_REPLY); + ret = copy_from_user(srv->debugfs.scratch, user_buf, count); + if (ret == count) + return -EFAULT; + + ret = afk_send_command(srv, EPIC_SUBTYPE_STD_SERVICE, srv->debugfs.scratch, count, + srv->debugfs.scratch, AFK_DEBUGFS_MAX_REPLY, &retcode); + if (ret < 0) + return ret; + if (retcode) + return -EINVAL; + + return count; +} + +static ssize_t service_raw_call_read_file(struct file *file, char __user *user_buf, + size_t count, loff_t *ppos) +{ + struct apple_epic_service *srv = file->private_data; + + if (!srv->debugfs.scratch) + return -EINVAL; + + return simple_read_from_buffer(user_buf, count, ppos, + srv->debugfs.scratch, AFK_DEBUGFS_MAX_REPLY); +} + +static const struct file_operations service_raw_call_fops = { + .open = simple_open, + .write = service_raw_call_write_file, + .read = service_raw_call_read_file, +}; + +static void afk_populate_service_debugfs(struct apple_epic_service *srv) +{ + if (!srv->ep->debugfs_entry || !srv->ops) + return; + + if (strcmp(srv->ops->name, "DCPAVAudioInterface") == 0) { + srv->debugfs.entry = debugfs_create_dir(srv->ops->name, + srv->ep->debugfs_entry); + debugfs_create_file("call", 0600, srv->debugfs.entry, srv, + &service_call_fops); + debugfs_create_file("raw_call", 0600, srv->debugfs.entry, srv, + &service_raw_call_fops); + } +} + +static void afk_remove_service_debugfs(struct apple_epic_service *srv) +{ + if (srv->debugfs.entry) { + debugfs_remove_recursive(srv->debugfs.entry); + srv->debugfs.entry = NULL; + } +} + +#endif diff --git a/drivers/gpu/drm/apple/afk.h b/drivers/gpu/drm/apple/afk.h index 737288b1346b28..0f91f32e08e301 100644 --- a/drivers/gpu/drm/apple/afk.h +++ b/drivers/gpu/drm/apple/afk.h @@ -8,6 +8,7 @@ #define _DRM_APPLE_DCP_AFK_H #include +#include #include #include "dcp.h" @@ -47,6 +48,11 @@ struct apple_epic_service { bool enabled; void *cookie; + + struct { + struct dentry *entry; + u8 *scratch; + } debugfs; }; enum epic_subtype; @@ -174,6 +180,8 @@ struct apple_dcp_afkep { const struct apple_epic_service_ops *ops; struct apple_epic_service services[AFK_MAX_CHANNEL]; u32 num_channels; + + struct dentry *debugfs_entry; }; struct apple_dcp_afkep *afk_init(struct apple_dcp *dcp, u32 endpoint, diff --git a/drivers/gpu/drm/apple/connector.c b/drivers/gpu/drm/apple/connector.c index a39bd249697d90..46de8e8756f1ed 100644 --- a/drivers/gpu/drm/apple/connector.c +++ b/drivers/gpu/drm/apple/connector.c @@ -3,6 +3,7 @@ * Copyright (C) The Asahi Linux Contributors */ +#include "linux/err.h" #include #include #include @@ -77,6 +78,25 @@ CONNECTOR_DEBUGFS_ENTRY(timing, DCP_CHUNK_TIMING_ELELMENTS); CONNECTOR_DEBUGFS_ENTRY(display_attribs, DCP_CHUNK_DISPLAY_ATTRIBUTES); CONNECTOR_DEBUGFS_ENTRY(transport, DCP_CHUNK_TRANSPORT); +static void dcp_afk_debugfs_root(struct platform_device *pdev, int ep, struct dentry *root) +{ +#if IS_ENABLED(CONFIG_DRM_APPLE_DEBUG) + struct dentry *entry = NULL; + struct apple_dcp *dcp = platform_get_drvdata(pdev); + + switch (ep) { + case AV_ENDPOINT: + entry = debugfs_create_dir("avep", root); + break; + default: + break; + } + + if (!IS_ERR_OR_NULL(entry)) + dcp->ep_debugfs[ep - 0x20] = entry; +#endif +} + void apple_connector_debugfs_init(struct drm_connector *connector, struct dentry *root) { struct apple_connector *apple_con = to_apple_connector(connector); @@ -89,6 +109,15 @@ void apple_connector_debugfs_init(struct drm_connector *connector, struct dentry &chunk_display_attribs_fops); debugfs_create_file("Transport", 0444, root, apple_con, &chunk_transport_fops); + + switch (connector->connector_type) { + case DRM_MODE_CONNECTOR_DisplayPort: + case DRM_MODE_CONNECTOR_HDMIA: + dcp_afk_debugfs_root(apple_con->dcp, AV_ENDPOINT, root); + break; + default: + break; + } } EXPORT_SYMBOL(apple_connector_debugfs_init); diff --git a/drivers/gpu/drm/apple/dcp-internal.h b/drivers/gpu/drm/apple/dcp-internal.h index 7fbca0b6bb9778..9f9832133e73ac 100644 --- a/drivers/gpu/drm/apple/dcp-internal.h +++ b/drivers/gpu/drm/apple/dcp-internal.h @@ -227,6 +227,9 @@ struct apple_dcp { struct dptx_port dptxport[2]; + /* debugfs entries */ + struct dentry *ep_debugfs[0x20]; + /* these fields are output port specific */ struct phy *phy; struct mux_control *xbar; From 0fcdd3e7cd05260167d7de991557f1aee2ef83ba Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20Povi=C5=A1er?= Date: Mon, 13 Feb 2023 14:56:24 +0100 Subject: [PATCH 1157/3902] gpu: drm: apple: Set up client of AV endpoint MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Martin Povišer --- drivers/gpu/drm/apple/Makefile | 1 + drivers/gpu/drm/apple/audio.h | 26 +++ drivers/gpu/drm/apple/av.c | 284 +++++++++++++++++++++++++++ drivers/gpu/drm/apple/av.h | 9 + drivers/gpu/drm/apple/dcp-internal.h | 6 + drivers/gpu/drm/apple/dcp.c | 16 ++ drivers/gpu/drm/apple/dcp.h | 2 + 7 files changed, 344 insertions(+) create mode 100644 drivers/gpu/drm/apple/audio.h create mode 100644 drivers/gpu/drm/apple/av.c create mode 100644 drivers/gpu/drm/apple/av.h diff --git a/drivers/gpu/drm/apple/Makefile b/drivers/gpu/drm/apple/Makefile index f4ad014c0d21d4..5963d3cde31316 100644 --- a/drivers/gpu/drm/apple/Makefile +++ b/drivers/gpu/drm/apple/Makefile @@ -5,6 +5,7 @@ CFLAGS_trace.o = -I$(src) appledrm-y := apple_drv.o apple_dcp-y := afk.o dcp.o dcp_backlight.o dptxep.o iomfb.o parser.o systemep.o +apple_dcp-$(CONFIG_DRM_APPLE_AUDIO) += av.o apple_dcp-y += connector.o apple_dcp-y += ibootep.o apple_dcp-y += iomfb_v12_3.o diff --git a/drivers/gpu/drm/apple/audio.h b/drivers/gpu/drm/apple/audio.h new file mode 100644 index 00000000000000..3cf4d31417694e --- /dev/null +++ b/drivers/gpu/drm/apple/audio.h @@ -0,0 +1,26 @@ +#ifndef __AUDIO_H__ +#define __AUDIO_H__ + +#include + +struct device; +struct device_node; +struct dcp_sound_cookie; + +typedef void (*dcp_audio_hotplug_callback)(struct device *dev, bool connected); + +struct dcp_audio_pdata { + struct device *dcp_dev; + struct device_node *dpaudio_node; +}; + +void dcp_audiosrv_set_hotplug_cb(struct device *dev, struct device *audio_dev, + dcp_audio_hotplug_callback cb); +int dcp_audiosrv_prepare(struct device *dev, struct dcp_sound_cookie *cookie); +int dcp_audiosrv_startlink(struct device *dev, struct dcp_sound_cookie *cookie); +int dcp_audiosrv_stoplink(struct device *dev); +int dcp_audiosrv_unprepare(struct device *dev); +int dcp_audiosrv_get_elements(struct device *dev, void *elements, size_t maxsize); +int dcp_audiosrv_get_product_attrs(struct device *dev, void *attrs, size_t maxsize); + +#endif /* __AUDIO_H__ */ diff --git a/drivers/gpu/drm/apple/av.c b/drivers/gpu/drm/apple/av.c new file mode 100644 index 00000000000000..bd4c7ec51bdb7d --- /dev/null +++ b/drivers/gpu/drm/apple/av.c @@ -0,0 +1,284 @@ +// SPDX-License-Identifier: GPL-2.0-only OR MIT +/* Copyright 2023 Martin Povišer */ + +// #define DEBUG + +#include +#include +#include +#include + +#include "audio.h" +#include "afk.h" +#include "dcp.h" + +struct audiosrv_data { + struct device *audio_dev; + dcp_audio_hotplug_callback hotplug_cb; + bool plugged; + struct mutex plug_lock; + + struct apple_epic_service *srv; + struct rw_semaphore srv_rwsem; +}; + +static void av_interface_init(struct apple_epic_service *service, const char *name, + const char *class, s64 unit) +{ +} + +static void av_audiosrv_init(struct apple_epic_service *service, const char *name, + const char *class, s64 unit) +{ + struct apple_dcp *dcp = service->ep->dcp; + struct audiosrv_data *asrv = dcp->audiosrv; + int err; + + mutex_lock(&asrv->plug_lock); + + down_write(&asrv->srv_rwsem); + asrv->srv = service; + up_write(&asrv->srv_rwsem); + + /* TODO: this must be done elsewhere */ + err = afk_service_call(asrv->srv, 0, 6, NULL, 0, 32, NULL, 0, 32); + if (err) + dev_err(dcp->dev, "error opening audio service: %d\n", err); + + asrv->plugged = true; + if (asrv->hotplug_cb) + asrv->hotplug_cb(asrv->audio_dev, true); + + mutex_unlock(&asrv->plug_lock); +} + +static void av_audiosrv_teardown(struct apple_epic_service *service) +{ + struct apple_dcp *dcp = service->ep->dcp; + struct audiosrv_data *asrv = dcp->audiosrv; + + mutex_lock(&asrv->plug_lock); + + down_write(&asrv->srv_rwsem); + asrv->srv = NULL; + up_write(&asrv->srv_rwsem); + + asrv->plugged = false; + if (asrv->hotplug_cb) + asrv->hotplug_cb(asrv->audio_dev, false); + + mutex_unlock(&asrv->plug_lock); +} + +void dcp_audiosrv_set_hotplug_cb(struct device *dev, struct device *audio_dev, + dcp_audio_hotplug_callback cb) +{ + struct apple_dcp *dcp = dev_get_drvdata(dev); + struct audiosrv_data *asrv = dcp->audiosrv; + + mutex_lock(&asrv->plug_lock); + asrv->audio_dev = audio_dev; + asrv->hotplug_cb = cb; + + if (cb) + cb(audio_dev, asrv->plugged); + mutex_unlock(&asrv->plug_lock); +} +EXPORT_SYMBOL_GPL(dcp_audiosrv_set_hotplug_cb); + +int dcp_audiosrv_prepare(struct device *dev, struct dcp_sound_cookie *cookie) +{ + struct apple_dcp *dcp = dev_get_drvdata(dev); + struct audiosrv_data *asrv = dcp->audiosrv; + int ret; + + down_write(&asrv->srv_rwsem); + ret = afk_service_call(asrv->srv, 0, 8, cookie, sizeof(*cookie), + 64 - sizeof(*cookie), NULL, 0, 64); + up_write(&asrv->srv_rwsem); + + return ret; +} +EXPORT_SYMBOL_GPL(dcp_audiosrv_prepare); + +int dcp_audiosrv_startlink(struct device *dev, struct dcp_sound_cookie *cookie) +{ + struct apple_dcp *dcp = dev_get_drvdata(dev); + struct audiosrv_data *asrv = dcp->audiosrv; + int ret; + + down_write(&asrv->srv_rwsem); + ret = afk_service_call(asrv->srv, 0, 9, cookie, sizeof(*cookie), + 64 - sizeof(*cookie), NULL, 0, 64); + up_write(&asrv->srv_rwsem); + + return ret; +} +EXPORT_SYMBOL_GPL(dcp_audiosrv_startlink); + +int dcp_audiosrv_stoplink(struct device *dev) +{ + struct apple_dcp *dcp = dev_get_drvdata(dev); + struct audiosrv_data *asrv = dcp->audiosrv; + int ret; + + down_write(&asrv->srv_rwsem); + ret = afk_service_call(asrv->srv, 0, 12, NULL, 0, 64, NULL, 0, 64); + up_write(&asrv->srv_rwsem); + + return ret; +} +EXPORT_SYMBOL_GPL(dcp_audiosrv_stoplink); + +int dcp_audiosrv_unprepare(struct device *dev) +{ + struct apple_dcp *dcp = dev_get_drvdata(dev); + struct audiosrv_data *asrv = dcp->audiosrv; + int ret; + + down_write(&asrv->srv_rwsem); + ret = afk_service_call(asrv->srv, 0, 13, NULL, 0, 64, NULL, 0, 64); + up_write(&asrv->srv_rwsem); + + return ret; +} +EXPORT_SYMBOL_GPL(dcp_audiosrv_unprepare); + +static int +dcp_audiosrv_osobject_call(struct apple_epic_service *service, u16 group, + u32 command, void *output, size_t output_maxsize, + size_t *output_size) +{ + struct { + __le64 max_size; + u8 _pad1[24]; + __le64 used_size; + u8 _pad2[8]; + } __attribute__((packed)) *hdr; + static_assert(sizeof(*hdr) == 48); + size_t bfr_len = output_maxsize + sizeof(*hdr); + void *bfr; + int ret; + + bfr = kzalloc(bfr_len, GFP_KERNEL); + if (!bfr) + return -ENOMEM; + + hdr = bfr; + hdr->max_size = cpu_to_le64(output_maxsize); + ret = afk_service_call(service, group, command, hdr, sizeof(*hdr), output_maxsize, + bfr, sizeof(*hdr) + output_maxsize, 0); + if (ret) + return ret; + + if (output) + memcpy(output, bfr + sizeof(*hdr), output_maxsize); + + if (output_size) + *output_size = le64_to_cpu(hdr->used_size); + + return 0; +} + +int dcp_audiosrv_get_elements(struct device *dev, void *elements, size_t maxsize) +{ + struct apple_dcp *dcp = dev_get_drvdata(dev); + struct audiosrv_data *asrv = dcp->audiosrv; + size_t size; + int ret; + + down_write(&asrv->srv_rwsem); + ret = dcp_audiosrv_osobject_call(asrv->srv, 1, 18, elements, maxsize, &size); + up_write(&asrv->srv_rwsem); + + if (ret) + dev_err(dev, "audiosrv: error getting elements: %d\n", ret); + else + dev_dbg(dev, "audiosrv: got %zd bytes worth of elements\n", size); + + return ret; +} +EXPORT_SYMBOL_GPL(dcp_audiosrv_get_elements); + +int dcp_audiosrv_get_product_attrs(struct device *dev, void *attrs, size_t maxsize) +{ + struct apple_dcp *dcp = dev_get_drvdata(dev); + struct audiosrv_data *asrv = dcp->audiosrv; + size_t size; + int ret; + + down_write(&asrv->srv_rwsem); + ret = dcp_audiosrv_osobject_call(asrv->srv, 1, 20, attrs, maxsize, &size); + up_write(&asrv->srv_rwsem); + + if (ret) + dev_err(dev, "audiosrv: error getting product attributes: %d\n", ret); + else + dev_dbg(dev, "audiosrv: got %zd bytes worth of product attributes\n", size); + + return ret; +} +EXPORT_SYMBOL_GPL(dcp_audiosrv_get_product_attrs); + +static int av_audiosrv_report(struct apple_epic_service *service, u32 idx, + const void *data, size_t data_size) +{ + dev_dbg(service->ep->dcp->dev, "got audio report %d size %zx\n", idx, data_size); +#ifdef DEBUG + print_hex_dump(KERN_DEBUG, "audio report: ", DUMP_PREFIX_NONE, 16, 1, data, data_size, true); +#endif + + return 0; +} + +static const struct apple_epic_service_ops avep_ops[] = { + { + .name = "DCPAVSimpleVideoInterface", + .init = av_interface_init, + }, + { + .name = "DCPAVAudioInterface", + .init = av_audiosrv_init, + .report = av_audiosrv_report, + .teardown = av_audiosrv_teardown, + }, + {} +}; + +int avep_init(struct apple_dcp *dcp) +{ + struct dcp_audio_pdata *audio_pdata; + struct platform_device *audio_pdev; + struct audiosrv_data *audiosrv_data; + struct device *dev = dcp->dev; + + audiosrv_data = devm_kzalloc(dcp->dev, sizeof(*audiosrv_data), GFP_KERNEL); + audio_pdata = devm_kzalloc(dcp->dev, sizeof(*audio_pdata), GFP_KERNEL); + if (!audiosrv_data || !audio_pdata) + return -ENOMEM; + init_rwsem(&audiosrv_data->srv_rwsem); + mutex_init(&audiosrv_data->plug_lock); + dcp->audiosrv = audiosrv_data; + + audio_pdata->dcp_dev = dcp->dev; + /* TODO: free OF reference */ + audio_pdata->dpaudio_node = \ + of_parse_phandle(dev->of_node, "apple,audio-xmitter", 0); + if (!audio_pdata->dpaudio_node || + !of_device_is_available(audio_pdata->dpaudio_node)) { + dev_info(dev, "No audio support\n"); + return 0; + } + + audio_pdev = platform_device_register_data(dev, "dcp-hdmi-audio", + PLATFORM_DEVID_AUTO, + audio_pdata, sizeof(*audio_pdata)); + if (IS_ERR(audio_pdev)) + return dev_err_probe(dev, PTR_ERR(audio_pdev), "registering audio device\n"); + + dcp->avep = afk_init(dcp, AV_ENDPOINT, avep_ops); + if (IS_ERR(dcp->avep)) + return PTR_ERR(dcp->avep); + dcp->avep->debugfs_entry = dcp->ep_debugfs[AV_ENDPOINT - 0x20]; + return afk_start(dcp->avep); +} diff --git a/drivers/gpu/drm/apple/av.h b/drivers/gpu/drm/apple/av.h new file mode 100644 index 00000000000000..b1f92fb5d07f90 --- /dev/null +++ b/drivers/gpu/drm/apple/av.h @@ -0,0 +1,9 @@ +#ifndef __AV_H__ +#define __AV_H__ + +#include "parser.h" + +//int avep_audiosrv_startlink(struct apple_dcp *dcp, struct dcp_sound_cookie *cookie); +//int avep_audiosrv_stoplink(struct apple_dcp *dcp); + +#endif /* __AV_H__ */ diff --git a/drivers/gpu/drm/apple/dcp-internal.h b/drivers/gpu/drm/apple/dcp-internal.h index 9f9832133e73ac..4c352fd7791dec 100644 --- a/drivers/gpu/drm/apple/dcp-internal.h +++ b/drivers/gpu/drm/apple/dcp-internal.h @@ -34,6 +34,7 @@ enum { TEST_ENDPOINT = 0x21, DCP_EXPERT_ENDPOINT = 0x22, DISP0_ENDPOINT = 0x23, + AV_ENDPOINT = 0x29, DPTX_ENDPOINT = 0x2a, HDCP_ENDPOINT = 0x2b, REMOTE_ALLOC_ENDPOINT = 0x2d, @@ -89,6 +90,8 @@ struct dcp_brightness { bool update; }; +struct audiosrv_data; + /** laptop/AiO integrated panel parameters from DT */ struct dcp_panel { /// panel width in millimeter @@ -223,6 +226,9 @@ struct apple_dcp { struct apple_dcp_afkep *ibootep; + struct apple_dcp_afkep *avep; + struct audiosrv_data *audiosrv; + struct apple_dcp_afkep *dptxep; struct dptx_port dptxport[2]; diff --git a/drivers/gpu/drm/apple/dcp.c b/drivers/gpu/drm/apple/dcp.c index b4d7009b5990e1..1dfd2808c00c6f 100644 --- a/drivers/gpu/drm/apple/dcp.c +++ b/drivers/gpu/drm/apple/dcp.c @@ -11,6 +11,7 @@ #include #include #include +#include #include #include #include @@ -45,6 +46,10 @@ static bool show_notch; module_param(show_notch, bool, 0644); MODULE_PARM_DESC(show_notch, "Use the full display height and shows the notch"); +static bool noaudio; +module_param(noaudio, bool, 0644); +MODULE_PARM_DESC(noaudio, "Skip audio support"); + /* HACK: moved here to avoid circular dependency between apple_drv and dcp */ void dcp_drm_crtc_vblank(struct apple_crtc *crtc) { @@ -118,6 +123,9 @@ static void dcp_recv_msg(void *cookie, u8 endpoint, u64 message) switch (endpoint) { case IOMFB_ENDPOINT: return iomfb_recv_msg(dcp, message); + case AV_ENDPOINT: + afk_receive_message(dcp->avep, message); + return; case SYSTEM_ENDPOINT: afk_receive_message(dcp->systemep, message); return; @@ -363,6 +371,14 @@ int dcp_start(struct platform_device *pdev) if (ret) dev_warn(dcp->dev, "Failed to start system endpoint: %d\n", ret); +#if IS_ENABLED(CONFIG_DRM_APPLE_AUDIO) + if (!noaudio) { + ret = avep_init(dcp); + if (ret) + dev_warn(dcp->dev, "Failed to start AV endpoint: %d", ret); + } +#endif + if (dcp->phy && dcp->fw_compat >= DCP_FIRMWARE_V_13_5) { ret = ibootep_init(dcp); if (ret) diff --git a/drivers/gpu/drm/apple/dcp.h b/drivers/gpu/drm/apple/dcp.h index 52c5c94a2a8bbe..0a1981040996dc 100644 --- a/drivers/gpu/drm/apple/dcp.h +++ b/drivers/gpu/drm/apple/dcp.h @@ -60,4 +60,6 @@ void iomfb_recv_msg(struct apple_dcp *dcp, u64 message); int systemep_init(struct apple_dcp *dcp); int dptxep_init(struct apple_dcp *dcp); int ibootep_init(struct apple_dcp *dcp); +int avep_init(struct apple_dcp *dcp); + #endif From dc076fdb92ef4c9ad931bde02430494797b4ec09 Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Mon, 13 Nov 2023 21:59:46 +0100 Subject: [PATCH 1158/3902] drm: apple: av: Support macOS 12.3 and 13.5 firmware APIs Signed-off-by: Janne Grunau --- drivers/gpu/drm/apple/av.c | 74 +++++++++++++++++++++++++++++++++----- 1 file changed, 65 insertions(+), 9 deletions(-) diff --git a/drivers/gpu/drm/apple/av.c b/drivers/gpu/drm/apple/av.c index bd4c7ec51bdb7d..a00932476da3ab 100644 --- a/drivers/gpu/drm/apple/av.c +++ b/drivers/gpu/drm/apple/av.c @@ -11,6 +11,39 @@ #include "audio.h" #include "afk.h" #include "dcp.h" +#include "dcp-internal.h" + +struct dcp_av_audio_cmds { + /* commands in group 0*/ + u32 open; + u32 prepare; + u32 start_link; + u32 stop_link; + u32 unprepare; + /* commands in group 1*/ + u32 get_elements; + u32 get_product_attrs; +}; + +static const struct dcp_av_audio_cmds dcp_av_audio_cmds_v12_3 = { + .open = 6, + .prepare = 8, + .start_link = 9, + .stop_link = 12, + .unprepare = 13, + .get_elements = 18, + .get_product_attrs = 20, +}; + +static const struct dcp_av_audio_cmds dcp_av_audio_cmds_v13_5 = { + .open = 4, + .prepare = 6, + .start_link = 7, + .stop_link = 10, + .unprepare = 11, + .get_elements = 16, + .get_product_attrs = 18, +}; struct audiosrv_data { struct device *audio_dev; @@ -20,6 +53,8 @@ struct audiosrv_data { struct apple_epic_service *srv; struct rw_semaphore srv_rwsem; + + struct dcp_av_audio_cmds cmds; }; static void av_interface_init(struct apple_epic_service *service, const char *name, @@ -41,7 +76,8 @@ static void av_audiosrv_init(struct apple_epic_service *service, const char *nam up_write(&asrv->srv_rwsem); /* TODO: this must be done elsewhere */ - err = afk_service_call(asrv->srv, 0, 6, NULL, 0, 32, NULL, 0, 32); + err = afk_service_call(asrv->srv, 0, asrv->cmds.open, NULL, 0, 32, NULL, + 0, 32); if (err) dev_err(dcp->dev, "error opening audio service: %d\n", err); @@ -93,8 +129,9 @@ int dcp_audiosrv_prepare(struct device *dev, struct dcp_sound_cookie *cookie) int ret; down_write(&asrv->srv_rwsem); - ret = afk_service_call(asrv->srv, 0, 8, cookie, sizeof(*cookie), - 64 - sizeof(*cookie), NULL, 0, 64); + ret = afk_service_call(asrv->srv, 0, asrv->cmds.prepare, cookie, + sizeof(*cookie), 64 - sizeof(*cookie), NULL, 0, + 64); up_write(&asrv->srv_rwsem); return ret; @@ -108,8 +145,9 @@ int dcp_audiosrv_startlink(struct device *dev, struct dcp_sound_cookie *cookie) int ret; down_write(&asrv->srv_rwsem); - ret = afk_service_call(asrv->srv, 0, 9, cookie, sizeof(*cookie), - 64 - sizeof(*cookie), NULL, 0, 64); + ret = afk_service_call(asrv->srv, 0, asrv->cmds.start_link, cookie, + sizeof(*cookie), 64 - sizeof(*cookie), NULL, 0, + 64); up_write(&asrv->srv_rwsem); return ret; @@ -123,7 +161,8 @@ int dcp_audiosrv_stoplink(struct device *dev) int ret; down_write(&asrv->srv_rwsem); - ret = afk_service_call(asrv->srv, 0, 12, NULL, 0, 64, NULL, 0, 64); + ret = afk_service_call(asrv->srv, 0, asrv->cmds.stop_link, NULL, 0, 64, + NULL, 0, 64); up_write(&asrv->srv_rwsem); return ret; @@ -137,7 +176,8 @@ int dcp_audiosrv_unprepare(struct device *dev) int ret; down_write(&asrv->srv_rwsem); - ret = afk_service_call(asrv->srv, 0, 13, NULL, 0, 64, NULL, 0, 64); + ret = afk_service_call(asrv->srv, 0, asrv->cmds.unprepare, NULL, 0, 64, + NULL, 0, 64); up_write(&asrv->srv_rwsem); return ret; @@ -188,7 +228,8 @@ int dcp_audiosrv_get_elements(struct device *dev, void *elements, size_t maxsize int ret; down_write(&asrv->srv_rwsem); - ret = dcp_audiosrv_osobject_call(asrv->srv, 1, 18, elements, maxsize, &size); + ret = dcp_audiosrv_osobject_call(asrv->srv, 1, asrv->cmds.get_elements, + elements, maxsize, &size); up_write(&asrv->srv_rwsem); if (ret) @@ -208,7 +249,9 @@ int dcp_audiosrv_get_product_attrs(struct device *dev, void *attrs, size_t maxsi int ret; down_write(&asrv->srv_rwsem); - ret = dcp_audiosrv_osobject_call(asrv->srv, 1, 20, attrs, maxsize, &size); + ret = dcp_audiosrv_osobject_call(asrv->srv, 1, + asrv->cmds.get_product_attrs, attrs, + maxsize, &size); up_write(&asrv->srv_rwsem); if (ret) @@ -258,6 +301,19 @@ int avep_init(struct apple_dcp *dcp) return -ENOMEM; init_rwsem(&audiosrv_data->srv_rwsem); mutex_init(&audiosrv_data->plug_lock); + + switch (dcp->fw_compat) { + case DCP_FIRMWARE_V_12_3: + audiosrv_data->cmds = dcp_av_audio_cmds_v12_3; + break; + case DCP_FIRMWARE_V_13_5: + audiosrv_data->cmds = dcp_av_audio_cmds_v13_5; + break; + default: + dev_err(dcp->dev, "Audio not supported for firmware\n"); + return -ENODEV; + } + dcp->audiosrv = audiosrv_data; audio_pdata->dcp_dev = dcp->dev; From 3538cb1796d4813048142e3832fa2cc47984bd78 Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Mon, 13 Nov 2023 23:00:13 +0100 Subject: [PATCH 1159/3902] drm: apple: av: Do not open AV service from afk receive handler Use a completion to do it from avep_init() instead. Signed-off-by: Janne Grunau --- drivers/gpu/drm/apple/av.c | 37 +++++++++++++++++++++++++++---------- 1 file changed, 27 insertions(+), 10 deletions(-) diff --git a/drivers/gpu/drm/apple/av.c b/drivers/gpu/drm/apple/av.c index a00932476da3ab..5f3783221ac400 100644 --- a/drivers/gpu/drm/apple/av.c +++ b/drivers/gpu/drm/apple/av.c @@ -51,6 +51,7 @@ struct audiosrv_data { bool plugged; struct mutex plug_lock; + struct completion init_completion; struct apple_epic_service *srv; struct rw_semaphore srv_rwsem; @@ -67,7 +68,6 @@ static void av_audiosrv_init(struct apple_epic_service *service, const char *nam { struct apple_dcp *dcp = service->ep->dcp; struct audiosrv_data *asrv = dcp->audiosrv; - int err; mutex_lock(&asrv->plug_lock); @@ -75,16 +75,8 @@ static void av_audiosrv_init(struct apple_epic_service *service, const char *nam asrv->srv = service; up_write(&asrv->srv_rwsem); - /* TODO: this must be done elsewhere */ - err = afk_service_call(asrv->srv, 0, asrv->cmds.open, NULL, 0, 32, NULL, - 0, 32); - if (err) - dev_err(dcp->dev, "error opening audio service: %d\n", err); - + complete(&asrv->init_completion); asrv->plugged = true; - if (asrv->hotplug_cb) - asrv->hotplug_cb(asrv->audio_dev, true); - mutex_unlock(&asrv->plug_lock); } @@ -313,6 +305,7 @@ int avep_init(struct apple_dcp *dcp) dev_err(dcp->dev, "Audio not supported for firmware\n"); return -ENODEV; } + init_completion(&audiosrv_data->init_completion); dcp->audiosrv = audiosrv_data; @@ -337,4 +330,28 @@ int avep_init(struct apple_dcp *dcp) return PTR_ERR(dcp->avep); dcp->avep->debugfs_entry = dcp->ep_debugfs[AV_ENDPOINT - 0x20]; return afk_start(dcp->avep); + + ret = wait_for_completion_timeout(&dcp->audiosrv->init_completion, + msecs_to_jiffies(500)); + if (ret < 0) { + dev_err(dcp->dev, "error waiting on audio service init: %d\n", ret); + return ret; + } else if (!ret) { + dev_err(dcp->dev, "timeout while waiting for audio service init\n"); + return -ETIMEDOUT; + } + + /* open AV audio service */ + ret = afk_service_call(dcp->audiosrv->srv, 0, dcp->audiosrv->cmds.open, + NULL, 0, 32, NULL, 0, 32); + if (ret) { + dev_err(dcp->dev, "error opening audio service: %d\n", ret); + return ret; + } + + mutex_lock(&dcp->audiosrv->plug_lock); + if (dcp->audiosrv->hotplug_cb) + dcp->audiosrv->hotplug_cb(dcp->audiosrv->audio_dev, + dcp->audiosrv->plugged); + mutex_unlock(&dcp->audiosrv->plug_lock); } From 5e45406c43c0e4d57a0eed46c2f35db38ec2def4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20Povi=C5=A1er?= Date: Thu, 23 Feb 2023 12:49:43 +0100 Subject: [PATCH 1160/3902] gpu: drm: apple: Add DCP audio driver MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Martin Povišer --- drivers/gpu/drm/apple/Kconfig | 1 + drivers/gpu/drm/apple/Makefile | 5 + drivers/gpu/drm/apple/audio.c | 608 +++++++++++++++++++++++ drivers/gpu/drm/apple/hdmi-codec-chmap.h | 123 +++++ 4 files changed, 737 insertions(+) create mode 100644 drivers/gpu/drm/apple/audio.c create mode 100644 drivers/gpu/drm/apple/hdmi-codec-chmap.h diff --git a/drivers/gpu/drm/apple/Kconfig b/drivers/gpu/drm/apple/Kconfig index d8ae51282e5300..9828a5fa193284 100644 --- a/drivers/gpu/drm/apple/Kconfig +++ b/drivers/gpu/drm/apple/Kconfig @@ -20,6 +20,7 @@ config DRM_APPLE_AUDIO depends on DRM_APPLE depends on SND select SND_PCM + select SND_DMAENGINE_PCM config DRM_APPLE_DEBUG bool "Enable additional driver debugging" diff --git a/drivers/gpu/drm/apple/Makefile b/drivers/gpu/drm/apple/Makefile index 5963d3cde31316..88067ba9c32ec8 100644 --- a/drivers/gpu/drm/apple/Makefile +++ b/drivers/gpu/drm/apple/Makefile @@ -12,14 +12,19 @@ apple_dcp-y += iomfb_v12_3.o apple_dcp-y += iomfb_v13_3.o apple_dcp-$(CONFIG_TRACING) += trace.o +apple_dcp_audio-y := audio.o obj-$(CONFIG_DRM_APPLE) += appledrm.o obj-$(CONFIG_DRM_APPLE) += apple_dcp.o +ifeq ($(CONFIG_DRM_APPLE_AUDIO),y) +obj-$(CONFIG_DRM_APPLE) += apple_dcp_audio.o +endif # header test # exclude some broken headers from the test coverage no-header-test := \ + hdmi-codec-chmap.h always-y += \ $(patsubst %.h,%.hdrtest, $(filter-out $(no-header-test), \ diff --git a/drivers/gpu/drm/apple/audio.c b/drivers/gpu/drm/apple/audio.c new file mode 100644 index 00000000000000..223b033732216e --- /dev/null +++ b/drivers/gpu/drm/apple/audio.c @@ -0,0 +1,608 @@ +// SPDX-License-Identifier: GPL-2.0-only OR MIT +/* + * DCP Audio Bits + * + * Copyright (C) The Asahi Linux Contributors + * + * TODO: + * - figure some nice identification of the sound card (in case + * there's many DCP instances) + */ + +#define DEBUG + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "av.h" +#include "audio.h" +#include "parser.h" + +#define DCPAUD_ELEMENTS_MAXSIZE 16384 +#define DCPAUD_PRODUCTATTRS_MAXSIZE 1024 + +#define DRV_NAME "dcp-hdmi-audio" + +struct dcp_audio { + struct device *dev; + struct dcp_audio_pdata *pdata; + struct dma_chan *chan; + struct snd_card *card; + struct snd_jack *jack; + struct snd_pcm_substream *substream; + unsigned int open_cookie; + + struct mutex data_lock; + bool connected; + unsigned int connection_cookie; + + struct snd_pcm_chmap_elem selected_chmap; + struct dcp_sound_cookie selected_cookie; + void *elements; + void *productattrs; + + struct snd_pcm_chmap *chmap_info; +}; + +static const struct snd_pcm_hardware dcp_pcm_hw = { + .info = SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_MMAP_VALID | + SNDRV_PCM_INFO_INTERLEAVED, + .formats = SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_LE | + SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_S32_LE, + .rates = SNDRV_PCM_RATE_CONTINUOUS, + .rate_min = 0, + .rate_max = UINT_MAX, + .channels_min = 1, + .channels_max = 16, + .buffer_bytes_max = SIZE_MAX, + .period_bytes_min = 4096, /* TODO */ + .period_bytes_max = SIZE_MAX, + .periods_min = 2, + .periods_max = UINT_MAX, +}; + +static int dcpaud_read_remote_info(struct dcp_audio *dcpaud) +{ + int ret; + + ret = dcp_audiosrv_get_elements(dcpaud->pdata->dcp_dev, dcpaud->elements, + DCPAUD_ELEMENTS_MAXSIZE); + if (ret < 0) + return ret; + + ret = dcp_audiosrv_get_product_attrs(dcpaud->pdata->dcp_dev, dcpaud->productattrs, + DCPAUD_PRODUCTATTRS_MAXSIZE); + if (ret < 0) + return ret; + + return 0; +} + +static int dcpaud_interval_bitmask(struct snd_interval *i, + unsigned int mask) +{ + struct snd_interval range; + if (!mask) + return -EINVAL; + + snd_interval_any(&range); + range.min = __ffs(mask); + range.max = __fls(mask); + return snd_interval_refine(i, &range); +} + +extern const struct snd_pcm_hw_constraint_list snd_pcm_known_rates; + +static void dcpaud_fill_fmt_sieve(struct snd_pcm_hw_params *params, + struct dcp_sound_format_mask *sieve) +{ + struct snd_interval *c = hw_param_interval(params, + SNDRV_PCM_HW_PARAM_CHANNELS); + struct snd_interval *r = hw_param_interval(params, + SNDRV_PCM_HW_PARAM_RATE); + struct snd_mask *f = hw_param_mask(params, + SNDRV_PCM_HW_PARAM_FORMAT); + int i; + + sieve->nchans = GENMASK(c->max, c->min); + sieve->formats = f->bits[0] | ((u64) f->bits[1]) << 32; /* TODO: don't open-code */ + + for (i = 0; i < snd_pcm_known_rates.count; i++) { + unsigned int rate = snd_pcm_known_rates.list[i]; + + if (snd_interval_test(r, rate)) + sieve->rates |= 1u << i; + } +} + +static void dcpaud_consult_elements(struct dcp_audio *dcpaud, + struct snd_pcm_hw_params *params, + struct dcp_sound_format_mask *hits) +{ + struct dcp_sound_format_mask sieve; + struct dcp_parse_ctx elements = { + .dcp = dev_get_drvdata(dcpaud->pdata->dcp_dev), + .blob = dcpaud->elements + 4, + .len = DCPAUD_ELEMENTS_MAXSIZE - 4, + .pos = 0, + }; + + dcpaud_fill_fmt_sieve(params, &sieve); + dev_dbg(dcpaud->dev, "elements in: %llx %x %x\n", sieve.formats, sieve.nchans, sieve.rates); + parse_sound_constraints(&elements, &sieve, hits); + dev_dbg(dcpaud->dev, "elements out: %llx %x %x\n", hits->formats, hits->nchans, hits->rates); +} + +static int dcpaud_select_cookie(struct dcp_audio *dcpaud, + struct snd_pcm_hw_params *params) +{ + struct dcp_sound_format_mask sieve; + struct dcp_parse_ctx elements = { + .dcp = dev_get_drvdata(dcpaud->pdata->dcp_dev), + .blob = dcpaud->elements + 4, + .len = DCPAUD_ELEMENTS_MAXSIZE - 4, + .pos = 0, + }; + + dcpaud_fill_fmt_sieve(params, &sieve); + return parse_sound_mode(&elements, &sieve, &dcpaud->selected_chmap, + &dcpaud->selected_cookie); +} + +static int dcpaud_rule_channels(struct snd_pcm_hw_params *params, + struct snd_pcm_hw_rule *rule) +{ + struct dcp_audio *dcpaud = rule->private; + struct snd_interval *c = hw_param_interval(params, + SNDRV_PCM_HW_PARAM_CHANNELS); + struct dcp_sound_format_mask hits = {0, 0, 0}; + + dcpaud_consult_elements(dcpaud, params, &hits); + + return dcpaud_interval_bitmask(c, hits.nchans); +} + +static int dcpaud_refine_fmt_mask(struct snd_mask *m, u64 mask) +{ + struct snd_mask mask_mask; + + if (!mask) + return -EINVAL; + mask_mask.bits[0] = mask; + mask_mask.bits[1] = mask >> 32; + + return snd_mask_refine(m, &mask_mask); +} + +static int dcpaud_rule_format(struct snd_pcm_hw_params *params, + struct snd_pcm_hw_rule *rule) +{ + struct dcp_audio *dcpaud = rule->private; + struct snd_mask *f = hw_param_mask(params, + SNDRV_PCM_HW_PARAM_FORMAT); + struct dcp_sound_format_mask hits; + + dcpaud_consult_elements(dcpaud, params, &hits); + + return dcpaud_refine_fmt_mask(f, hits.formats); +} + +static int dcpaud_rule_rate(struct snd_pcm_hw_params *params, + struct snd_pcm_hw_rule *rule) +{ + struct dcp_audio *dcpaud = rule->private; + struct snd_interval *r = hw_param_interval(params, + SNDRV_PCM_HW_PARAM_RATE); + struct dcp_sound_format_mask hits; + + dcpaud_consult_elements(dcpaud, params, &hits); + + return snd_interval_rate_bits(r, hits.rates); +} + +static int dcp_pcm_open(struct snd_pcm_substream *substream) +{ + struct dcp_audio *dcpaud = substream->pcm->private_data; + struct dma_chan *chan = dcpaud->chan; + struct snd_dmaengine_dai_dma_data dma_data = { + .flags = SND_DMAENGINE_PCM_DAI_FLAG_PACK, + }; + struct snd_pcm_hardware hw; + int ret; + + mutex_lock(&dcpaud->data_lock); + if (!dcpaud->connected) { + mutex_unlock(&dcpaud->data_lock); + return -ENXIO; + } + dcpaud->open_cookie = dcpaud->connection_cookie; + mutex_unlock(&dcpaud->data_lock); + + ret = dcpaud_read_remote_info(dcpaud); + if (ret < 0) + return ret; + + snd_pcm_hw_rule_add(substream->runtime, 0, SNDRV_PCM_HW_PARAM_FORMAT, + dcpaud_rule_format, dcpaud, + SNDRV_PCM_HW_PARAM_CHANNELS, SNDRV_PCM_HW_PARAM_RATE, -1); + snd_pcm_hw_rule_add(substream->runtime, 0, SNDRV_PCM_HW_PARAM_CHANNELS, + dcpaud_rule_channels, dcpaud, + SNDRV_PCM_HW_PARAM_FORMAT, SNDRV_PCM_HW_PARAM_RATE, -1); + snd_pcm_hw_rule_add(substream->runtime, 0, SNDRV_PCM_HW_PARAM_RATE, + dcpaud_rule_rate, dcpaud, + SNDRV_PCM_HW_PARAM_FORMAT, SNDRV_PCM_HW_PARAM_CHANNELS, -1); + + hw = dcp_pcm_hw; + hw.info = SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_MMAP_VALID | + SNDRV_PCM_INFO_INTERLEAVED; + hw.periods_min = 2; + hw.periods_max = UINT_MAX; + hw.period_bytes_min = 256; + hw.period_bytes_max = SIZE_MAX; // TODO dma_get_max_seg_size(dma_dev); + hw.buffer_bytes_max = SIZE_MAX; + hw.fifo_size = 16; + ret = snd_dmaengine_pcm_refine_runtime_hwparams(substream, &dma_data, + &hw, chan); + if (ret) + return ret; + substream->runtime->hw = hw; + + return snd_dmaengine_pcm_open(substream, chan); +} + +static int dcp_pcm_close(struct snd_pcm_substream *substream) +{ + struct dcp_audio *dcpaud = substream->pcm->private_data; + dcpaud->selected_chmap.channels = 0; + + return snd_dmaengine_pcm_close(substream); +} + +static int dcpaud_connection_up(struct dcp_audio *dcpaud) +{ + bool ret; + mutex_lock(&dcpaud->data_lock); + ret = dcpaud->connected && + dcpaud->open_cookie == dcpaud->connection_cookie; + mutex_unlock(&dcpaud->data_lock); + return ret; +} + +static int dcp_pcm_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct dcp_audio *dcpaud = substream->pcm->private_data; + struct dma_slave_config slave_config; + struct dma_chan *chan = snd_dmaengine_pcm_get_chan(substream); + int ret; + + if (!dcpaud_connection_up(dcpaud)) + return -ENXIO; + + ret = dcpaud_select_cookie(dcpaud, params); + if (ret < 0) + return ret; + if (!ret) + return -EINVAL; + + memset(&slave_config, 0, sizeof(slave_config)); + ret = snd_hwparams_to_dma_slave_config(substream, params, &slave_config); + dev_info(dcpaud->dev, "snd_hwparams_to_dma_slave_config: %d\n", ret); + if (ret < 0) + return ret; + + slave_config.direction = DMA_MEM_TO_DEV; + /* + * The data entry from the DMA controller to the DPA peripheral + * is 32-bit wide no matter the actual sample size. + */ + slave_config.dst_addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES; + + ret = dmaengine_slave_config(chan, &slave_config); + dev_info(dcpaud->dev, "dmaengine_slave_config: %d\n", ret); + return ret; +} + +static int dcp_pcm_hw_free(struct snd_pcm_substream *substream) +{ + struct dcp_audio *dcpaud = substream->pcm->private_data; + + if (!dcpaud_connection_up(dcpaud)) + return 0; + + return dcp_audiosrv_unprepare(dcpaud->pdata->dcp_dev); +} + +static int dcp_pcm_prepare(struct snd_pcm_substream *substream) +{ + struct dcp_audio *dcpaud = substream->pcm->private_data; + + if (!dcpaud_connection_up(dcpaud)) + return -ENXIO; + + return dcp_audiosrv_prepare(dcpaud->pdata->dcp_dev, + &dcpaud->selected_cookie); +} + +static int dcp_pcm_trigger(struct snd_pcm_substream *substream, int cmd) +{ + struct dcp_audio *dcpaud = substream->pcm->private_data; + int ret; + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + case SNDRV_PCM_TRIGGER_RESUME: + if (!dcpaud_connection_up(dcpaud)) + return -ENXIO; + + ret = dcp_audiosrv_startlink(dcpaud->pdata->dcp_dev, + &dcpaud->selected_cookie); + if (ret < 0) + return ret; + break; + + case SNDRV_PCM_TRIGGER_STOP: + case SNDRV_PCM_TRIGGER_SUSPEND: + break; + + default: + return -EINVAL; + } + + ret = snd_dmaengine_pcm_trigger(substream, cmd); + if (ret < 0) + return ret; + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + case SNDRV_PCM_TRIGGER_RESUME: + break; + + case SNDRV_PCM_TRIGGER_STOP: + case SNDRV_PCM_TRIGGER_SUSPEND: + ret = dcp_audiosrv_stoplink(dcpaud->pdata->dcp_dev); + if (ret < 0) + return ret; + break; + } + + return 0; +} + +struct snd_pcm_ops dcp_playback_ops = { + .open = dcp_pcm_open, + .close = dcp_pcm_close, + .hw_params = dcp_pcm_hw_params, + .hw_free = dcp_pcm_hw_free, + .prepare = dcp_pcm_prepare, + .trigger = dcp_pcm_trigger, + .pointer = snd_dmaengine_pcm_pointer, +}; + +// Transitional workaround: for the chmap control TLV, advertise options +// copied from hdmi-codec.c +#include "hdmi-codec-chmap.h" + +static int dcpaud_chmap_ctl_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_pcm_chmap *info = snd_kcontrol_chip(kcontrol); + struct dcp_audio *dcpaud = info->private_data; + unsigned int i; + + for (i = 0; i < info->max_channels; i++) + ucontrol->value.integer.value[i] = \ + (i < dcpaud->selected_chmap.channels) ? + dcpaud->selected_chmap.map[i] : SNDRV_CHMAP_UNKNOWN; + + return 0; +} + + +static int dcpaud_create_chmap_ctl(struct dcp_audio *dcpaud) +{ + struct snd_pcm *pcm = dcpaud->substream->pcm; + struct snd_pcm_chmap *chmap_info; + int ret; + + ret = snd_pcm_add_chmap_ctls(pcm, SNDRV_PCM_STREAM_PLAYBACK, NULL, + dcp_pcm_hw.channels_max, 0, &chmap_info); + if (ret < 0) + return ret; + + chmap_info->kctl->get = dcpaud_chmap_ctl_get; + chmap_info->chmap = hdmi_codec_8ch_chmaps; + chmap_info->private_data = dcpaud; + + return 0; +} + +static int dcpaud_create_pcm(struct dcp_audio *dcpaud) +{ + struct snd_card *card = dcpaud->card; + struct snd_pcm *pcm; + struct dma_chan *chan; + int ret; + + chan = of_dma_request_slave_channel(dcpaud->pdata->dpaudio_node, "tx"); + if (IS_ERR_OR_NULL(chan)) { + if (!chan) + return -EINVAL; + + dev_err(dcpaud->dev, "can't request audio TX DMA channel: %pE\n", chan); + return PTR_ERR(chan); + } + dcpaud->chan = chan; + +#define NUM_PLAYBACK 1 +#define NUM_CAPTURE 0 + + ret = snd_pcm_new(card, card->shortname, 0, NUM_PLAYBACK, NUM_CAPTURE, &pcm); + if (ret) + return ret; + + snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &dcp_playback_ops); + dcpaud->substream = pcm->streams[SNDRV_PCM_STREAM_PLAYBACK].substream; + snd_pcm_set_managed_buffer(dcpaud->substream, SNDRV_DMA_TYPE_DEV_IRAM, + chan->device->dev, 1024 * 1024, + SIZE_MAX); + + pcm->nonatomic = true; + pcm->private_data = dcpaud; + strscpy(pcm->name, card->shortname, sizeof(pcm->name)); + + return 0; +} + +static void dcpaud_report_hotplug(struct device *dev, bool connected) +{ + struct dcp_audio *dcpaud = dev_get_drvdata(dev); + struct snd_pcm_substream *substream = dcpaud->substream; + + mutex_lock(&dcpaud->data_lock); + if (dcpaud->connected == connected) { + mutex_unlock(&dcpaud->data_lock); + return; + } + + dcpaud->connected = connected; + if (connected) + dcpaud->connection_cookie++; + mutex_unlock(&dcpaud->data_lock); + + snd_jack_report(dcpaud->jack, connected ? SND_JACK_AVOUT : 0); + + if (!connected) { + snd_pcm_stream_lock(substream); + snd_pcm_stop(substream, SNDRV_PCM_STATE_DISCONNECTED); + snd_pcm_stream_unlock(substream); + } +} + +static int dcpaud_create_jack(struct dcp_audio *dcpaud) +{ + struct snd_card *card = dcpaud->card; + + return snd_jack_new(card, "HDMI/DP", SND_JACK_AVOUT, + &dcpaud->jack, true, false); +} + +static void dcpaud_set_card_names(struct dcp_audio *dcpaud) +{ + struct snd_card *card = dcpaud->card; + + strscpy(card->driver, "apple_dcp", sizeof(card->driver)); + strscpy(card->longname, "Apple DisplayPort", sizeof(card->longname)); + strscpy(card->shortname, "Apple DisplayPort", sizeof(card->shortname)); +} + +#ifdef CONFIG_SND_DEBUG +static void dcpaud_expose_debugfs_blob(struct dcp_audio *dcpaud, const char *name, void *base, size_t size) +{ + struct debugfs_blob_wrapper *wrapper; + wrapper = devm_kzalloc(dcpaud->dev, sizeof(*wrapper), GFP_KERNEL); + if (!wrapper) + return; + wrapper->data = base; + wrapper->size = size; + debugfs_create_blob(name, 0600, dcpaud->card->debugfs_root, wrapper); +} +#else +static void dcpaud_expose_debugfs_blob(struct dcp_audio *dcpaud, const char *name, void *base, size_t size) {} +#endif + +static int dcpaud_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct dcp_audio_pdata *pdata = dev->platform_data; + struct dcp_audio *dcpaud; + int ret; + + dcpaud = devm_kzalloc(dev, sizeof(*dcpaud), GFP_KERNEL); + if (!dcpaud) + return -ENOMEM; + dcpaud->dev = dev; + dcpaud->pdata = pdata; + mutex_init(&dcpaud->data_lock); + platform_set_drvdata(pdev, dcpaud); + + dcpaud->elements = devm_kzalloc(dev, DCPAUD_ELEMENTS_MAXSIZE, + GFP_KERNEL); + if (!dcpaud->elements) + return -ENOMEM; + + dcpaud->productattrs = devm_kzalloc(dev, DCPAUD_PRODUCTATTRS_MAXSIZE, + GFP_KERNEL); + if (!dcpaud->productattrs) + return -ENOMEM; + + ret = snd_card_new(dev, SNDRV_DEFAULT_IDX1, SNDRV_DEFAULT_STR1, + THIS_MODULE, 0, &dcpaud->card); + if (ret) + return ret; + + dcpaud_set_card_names(dcpaud); + + ret = dcpaud_create_pcm(dcpaud); + if (ret) + goto err_free_card; + + ret = dcpaud_create_chmap_ctl(dcpaud); + if (ret) + goto err_free_card; + + ret = dcpaud_create_jack(dcpaud); + if (ret) + goto err_free_card; + + ret = snd_card_register(dcpaud->card); + if (ret) + goto err_free_card; + + dcpaud_expose_debugfs_blob(dcpaud, "selected_cookie", &dcpaud->selected_cookie, + sizeof(dcpaud->selected_cookie)); + dcpaud_expose_debugfs_blob(dcpaud, "elements", dcpaud->elements, + DCPAUD_ELEMENTS_MAXSIZE); + dcpaud_expose_debugfs_blob(dcpaud, "product_attrs", dcpaud->productattrs, + DCPAUD_PRODUCTATTRS_MAXSIZE); + + dcp_audiosrv_set_hotplug_cb(pdata->dcp_dev, dev, dcpaud_report_hotplug); + + return 0; + +err_free_card: + snd_card_free(dcpaud->card); + return ret; +} + +static int dcpaud_remove(struct platform_device *dev) +{ + struct dcp_audio *dcpaud = platform_get_drvdata(dev); + + dcp_audiosrv_set_hotplug_cb(dcpaud->pdata->dcp_dev, NULL, NULL); + snd_card_free(dcpaud->card); + + return 0; +} + +static struct platform_driver dcpaud_driver = { + .driver = { + .name = DRV_NAME, + }, + .probe = dcpaud_probe, + .remove = dcpaud_remove, +}; + +module_platform_driver(dcpaud_driver); + +MODULE_AUTHOR("Martin Povišer "); +MODULE_DESCRIPTION("Apple DCP HDMI Audio Driver"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:" DRV_NAME); diff --git a/drivers/gpu/drm/apple/hdmi-codec-chmap.h b/drivers/gpu/drm/apple/hdmi-codec-chmap.h new file mode 100644 index 00000000000000..f98e1e86b89602 --- /dev/null +++ b/drivers/gpu/drm/apple/hdmi-codec-chmap.h @@ -0,0 +1,123 @@ +// copied from sound/soc/codecs/hdmi-codec.c + +#include + +/* Channel maps for multi-channel playbacks, up to 8 n_ch */ +static const struct snd_pcm_chmap_elem hdmi_codec_8ch_chmaps[] = { + { .channels = 2, /* CA_ID 0x00 */ + .map = { SNDRV_CHMAP_FL, SNDRV_CHMAP_FR } }, + { .channels = 4, /* CA_ID 0x01 */ + .map = { SNDRV_CHMAP_FL, SNDRV_CHMAP_FR, SNDRV_CHMAP_LFE, + SNDRV_CHMAP_NA } }, + { .channels = 4, /* CA_ID 0x02 */ + .map = { SNDRV_CHMAP_FL, SNDRV_CHMAP_FR, SNDRV_CHMAP_NA, + SNDRV_CHMAP_FC } }, + { .channels = 4, /* CA_ID 0x03 */ + .map = { SNDRV_CHMAP_FL, SNDRV_CHMAP_FR, SNDRV_CHMAP_LFE, + SNDRV_CHMAP_FC } }, + { .channels = 6, /* CA_ID 0x04 */ + .map = { SNDRV_CHMAP_FL, SNDRV_CHMAP_FR, SNDRV_CHMAP_NA, + SNDRV_CHMAP_NA, SNDRV_CHMAP_RC, SNDRV_CHMAP_NA } }, + { .channels = 6, /* CA_ID 0x05 */ + .map = { SNDRV_CHMAP_FL, SNDRV_CHMAP_FR, SNDRV_CHMAP_LFE, + SNDRV_CHMAP_NA, SNDRV_CHMAP_RC, SNDRV_CHMAP_NA } }, + { .channels = 6, /* CA_ID 0x06 */ + .map = { SNDRV_CHMAP_FL, SNDRV_CHMAP_FR, SNDRV_CHMAP_NA, + SNDRV_CHMAP_FC, SNDRV_CHMAP_RC, SNDRV_CHMAP_NA } }, + { .channels = 6, /* CA_ID 0x07 */ + .map = { SNDRV_CHMAP_FL, SNDRV_CHMAP_FR, SNDRV_CHMAP_LFE, + SNDRV_CHMAP_FC, SNDRV_CHMAP_RC, SNDRV_CHMAP_NA } }, + { .channels = 6, /* CA_ID 0x08 */ + .map = { SNDRV_CHMAP_FL, SNDRV_CHMAP_FR, SNDRV_CHMAP_NA, + SNDRV_CHMAP_NA, SNDRV_CHMAP_RL, SNDRV_CHMAP_RR } }, + { .channels = 6, /* CA_ID 0x09 */ + .map = { SNDRV_CHMAP_FL, SNDRV_CHMAP_FR, SNDRV_CHMAP_LFE, + SNDRV_CHMAP_NA, SNDRV_CHMAP_RL, SNDRV_CHMAP_RR } }, + { .channels = 6, /* CA_ID 0x0A */ + .map = { SNDRV_CHMAP_FL, SNDRV_CHMAP_FR, SNDRV_CHMAP_NA, + SNDRV_CHMAP_FC, SNDRV_CHMAP_RL, SNDRV_CHMAP_RR } }, + { .channels = 6, /* CA_ID 0x0B */ + .map = { SNDRV_CHMAP_FL, SNDRV_CHMAP_FR, SNDRV_CHMAP_LFE, + SNDRV_CHMAP_FC, SNDRV_CHMAP_RL, SNDRV_CHMAP_RR } }, + { .channels = 8, /* CA_ID 0x0C */ + .map = { SNDRV_CHMAP_FL, SNDRV_CHMAP_FR, SNDRV_CHMAP_NA, + SNDRV_CHMAP_NA, SNDRV_CHMAP_RL, SNDRV_CHMAP_RR, + SNDRV_CHMAP_RC, SNDRV_CHMAP_NA } }, + { .channels = 8, /* CA_ID 0x0D */ + .map = { SNDRV_CHMAP_FL, SNDRV_CHMAP_FR, SNDRV_CHMAP_LFE, + SNDRV_CHMAP_NA, SNDRV_CHMAP_RL, SNDRV_CHMAP_RR, + SNDRV_CHMAP_RC, SNDRV_CHMAP_NA } }, + { .channels = 8, /* CA_ID 0x0E */ + .map = { SNDRV_CHMAP_FL, SNDRV_CHMAP_FR, SNDRV_CHMAP_NA, + SNDRV_CHMAP_FC, SNDRV_CHMAP_RL, SNDRV_CHMAP_RR, + SNDRV_CHMAP_RC, SNDRV_CHMAP_NA } }, + { .channels = 8, /* CA_ID 0x0F */ + .map = { SNDRV_CHMAP_FL, SNDRV_CHMAP_FR, SNDRV_CHMAP_LFE, + SNDRV_CHMAP_FC, SNDRV_CHMAP_RL, SNDRV_CHMAP_RR, + SNDRV_CHMAP_RC, SNDRV_CHMAP_NA } }, + { .channels = 8, /* CA_ID 0x10 */ + .map = { SNDRV_CHMAP_FL, SNDRV_CHMAP_FR, SNDRV_CHMAP_NA, + SNDRV_CHMAP_NA, SNDRV_CHMAP_RL, SNDRV_CHMAP_RR, + SNDRV_CHMAP_RLC, SNDRV_CHMAP_RRC } }, + { .channels = 8, /* CA_ID 0x11 */ + .map = { SNDRV_CHMAP_FL, SNDRV_CHMAP_FR, SNDRV_CHMAP_LFE, + SNDRV_CHMAP_NA, SNDRV_CHMAP_RL, SNDRV_CHMAP_RR, + SNDRV_CHMAP_RLC, SNDRV_CHMAP_RRC } }, + { .channels = 8, /* CA_ID 0x12 */ + .map = { SNDRV_CHMAP_FL, SNDRV_CHMAP_FR, SNDRV_CHMAP_NA, + SNDRV_CHMAP_FC, SNDRV_CHMAP_RL, SNDRV_CHMAP_RR, + SNDRV_CHMAP_RLC, SNDRV_CHMAP_RRC } }, + { .channels = 8, /* CA_ID 0x13 */ + .map = { SNDRV_CHMAP_FL, SNDRV_CHMAP_FR, SNDRV_CHMAP_LFE, + SNDRV_CHMAP_FC, SNDRV_CHMAP_RL, SNDRV_CHMAP_RR, + SNDRV_CHMAP_RLC, SNDRV_CHMAP_RRC } }, + { .channels = 8, /* CA_ID 0x14 */ + .map = { SNDRV_CHMAP_FL, SNDRV_CHMAP_FR, SNDRV_CHMAP_NA, + SNDRV_CHMAP_NA, SNDRV_CHMAP_NA, SNDRV_CHMAP_NA, + SNDRV_CHMAP_FLC, SNDRV_CHMAP_FRC } }, + { .channels = 8, /* CA_ID 0x15 */ + .map = { SNDRV_CHMAP_FL, SNDRV_CHMAP_FR, SNDRV_CHMAP_LFE, + SNDRV_CHMAP_NA, SNDRV_CHMAP_NA, SNDRV_CHMAP_NA, + SNDRV_CHMAP_FLC, SNDRV_CHMAP_FRC } }, + { .channels = 8, /* CA_ID 0x16 */ + .map = { SNDRV_CHMAP_FL, SNDRV_CHMAP_FR, SNDRV_CHMAP_NA, + SNDRV_CHMAP_FC, SNDRV_CHMAP_NA, SNDRV_CHMAP_NA, + SNDRV_CHMAP_FLC, SNDRV_CHMAP_FRC } }, + { .channels = 8, /* CA_ID 0x17 */ + .map = { SNDRV_CHMAP_FL, SNDRV_CHMAP_FR, SNDRV_CHMAP_LFE, + SNDRV_CHMAP_FC, SNDRV_CHMAP_NA, SNDRV_CHMAP_NA, + SNDRV_CHMAP_FLC, SNDRV_CHMAP_FRC } }, + { .channels = 8, /* CA_ID 0x18 */ + .map = { SNDRV_CHMAP_FL, SNDRV_CHMAP_FR, SNDRV_CHMAP_NA, + SNDRV_CHMAP_NA, SNDRV_CHMAP_NA, SNDRV_CHMAP_NA, + SNDRV_CHMAP_FLC, SNDRV_CHMAP_FRC } }, + { .channels = 8, /* CA_ID 0x19 */ + .map = { SNDRV_CHMAP_FL, SNDRV_CHMAP_FR, SNDRV_CHMAP_LFE, + SNDRV_CHMAP_NA, SNDRV_CHMAP_NA, SNDRV_CHMAP_NA, + SNDRV_CHMAP_FLC, SNDRV_CHMAP_FRC } }, + { .channels = 8, /* CA_ID 0x1A */ + .map = { SNDRV_CHMAP_FL, SNDRV_CHMAP_FR, SNDRV_CHMAP_NA, + SNDRV_CHMAP_FC, SNDRV_CHMAP_NA, SNDRV_CHMAP_NA, + SNDRV_CHMAP_FLC, SNDRV_CHMAP_FRC } }, + { .channels = 8, /* CA_ID 0x1B */ + .map = { SNDRV_CHMAP_FL, SNDRV_CHMAP_FR, SNDRV_CHMAP_LFE, + SNDRV_CHMAP_FC, SNDRV_CHMAP_NA, SNDRV_CHMAP_NA, + SNDRV_CHMAP_FLC, SNDRV_CHMAP_FRC } }, + { .channels = 8, /* CA_ID 0x1C */ + .map = { SNDRV_CHMAP_FL, SNDRV_CHMAP_FR, SNDRV_CHMAP_NA, + SNDRV_CHMAP_NA, SNDRV_CHMAP_NA, SNDRV_CHMAP_NA, + SNDRV_CHMAP_FLC, SNDRV_CHMAP_FRC } }, + { .channels = 8, /* CA_ID 0x1D */ + .map = { SNDRV_CHMAP_FL, SNDRV_CHMAP_FR, SNDRV_CHMAP_LFE, + SNDRV_CHMAP_NA, SNDRV_CHMAP_NA, SNDRV_CHMAP_NA, + SNDRV_CHMAP_FLC, SNDRV_CHMAP_FRC } }, + { .channels = 8, /* CA_ID 0x1E */ + .map = { SNDRV_CHMAP_FL, SNDRV_CHMAP_FR, SNDRV_CHMAP_NA, + SNDRV_CHMAP_FC, SNDRV_CHMAP_NA, SNDRV_CHMAP_NA, + SNDRV_CHMAP_FLC, SNDRV_CHMAP_FRC } }, + { .channels = 8, /* CA_ID 0x1F */ + .map = { SNDRV_CHMAP_FL, SNDRV_CHMAP_FR, SNDRV_CHMAP_LFE, + SNDRV_CHMAP_FC, SNDRV_CHMAP_NA, SNDRV_CHMAP_NA, + SNDRV_CHMAP_FLC, SNDRV_CHMAP_FRC } }, + { } +}; From 8ceb649df88b7d363255c768cbb07300db4f3570 Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Sun, 14 Apr 2024 16:22:25 +0200 Subject: [PATCH 1161/3902] drm: apple: dptx: Remove DPTX disconnect/connect on init This was only necessary for dcp0 on M2* devices presumably because the reset in m1n1 doesn't work as intended. Signed-off-by: Janne Grunau --- drivers/gpu/drm/apple/dcp.c | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/drivers/gpu/drm/apple/dcp.c b/drivers/gpu/drm/apple/dcp.c index 1dfd2808c00c6f..d905cd2fa82a03 100644 --- a/drivers/gpu/drm/apple/dcp.c +++ b/drivers/gpu/drm/apple/dcp.c @@ -386,10 +386,17 @@ int dcp_start(struct platform_device *pdev) ret); ret = dptxep_init(dcp); - if (ret) + if (ret) { dev_warn(dcp->dev, "Failed to start DPTX endpoint: %d\n", ret); - else if (dcp->dptxport[0].enabled) { +#ifdef DCP_DPTX_DISCONNECT_ON_INIT + /* + * This disconnect / connect cycle on init is only necessary + * when using dcp0 on j473, j474s and presumedly j475c. + * Since dcp0 is not used at the moment let's avoid this + * since it is possibly the cause for startup issues. + */ + } else if (dcp->dptxport[0].enabled) { bool connected; /* force disconnect on start - necessary if the display * is already up from m1n1 @@ -404,10 +411,11 @@ int dcp_start(struct platform_device *pdev) // necessary on j473/j474 but not on j314c if (connected) dcp_dptx_connect(dcp, 0); +#endif } - } else if (dcp->phy) + } else if (dcp->phy) { dev_warn(dcp->dev, "OS firmware incompatible with dptxport EP\n"); - + } ret = iomfb_start_rtkit(dcp); if (ret) dev_err(dcp->dev, "Failed to start IOMFB endpoint: %d\n", ret); From 7dc0ed2026c5ad30334c7cd5e6a48070392de8e8 Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Sun, 14 Apr 2024 16:47:01 +0200 Subject: [PATCH 1162/3902] drm: apple: audio: init AV endpoint later This seems to get rid of initialization timeouts / failures. Signed-off-by: Janne Grunau --- drivers/gpu/drm/apple/dcp.c | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/drivers/gpu/drm/apple/dcp.c b/drivers/gpu/drm/apple/dcp.c index d905cd2fa82a03..cf79cc0058cf92 100644 --- a/drivers/gpu/drm/apple/dcp.c +++ b/drivers/gpu/drm/apple/dcp.c @@ -371,14 +371,6 @@ int dcp_start(struct platform_device *pdev) if (ret) dev_warn(dcp->dev, "Failed to start system endpoint: %d\n", ret); -#if IS_ENABLED(CONFIG_DRM_APPLE_AUDIO) - if (!noaudio) { - ret = avep_init(dcp); - if (ret) - dev_warn(dcp->dev, "Failed to start AV endpoint: %d", ret); - } -#endif - if (dcp->phy && dcp->fw_compat >= DCP_FIRMWARE_V_13_5) { ret = ibootep_init(dcp); if (ret) @@ -420,6 +412,15 @@ int dcp_start(struct platform_device *pdev) if (ret) dev_err(dcp->dev, "Failed to start IOMFB endpoint: %d\n", ret); +#if IS_ENABLED(CONFIG_DRM_APPLE_AUDIO) + if (!noaudio) { + ret = avep_init(dcp); + if (ret) + dev_warn(dcp->dev, "Failed to start AV endpoint: %d", ret); + ret = 0; + } +#endif + return ret; } EXPORT_SYMBOL(dcp_start); From c3758ece18aa9b00d40f553733efc7d8ab734cd6 Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Sat, 20 Apr 2024 21:00:37 +0200 Subject: [PATCH 1163/3902] drm: apple: av: Use a workqueue Functionally a revert of "drm: apple: av: Do not open AV service from afk receive handler" with more workqueues. Signed-off-by: Janne Grunau --- drivers/gpu/drm/apple/av.c | 63 ++++++++++++++++++++++---------------- 1 file changed, 36 insertions(+), 27 deletions(-) diff --git a/drivers/gpu/drm/apple/av.c b/drivers/gpu/drm/apple/av.c index 5f3783221ac400..926c4b238227b1 100644 --- a/drivers/gpu/drm/apple/av.c +++ b/drivers/gpu/drm/apple/av.c @@ -7,6 +7,7 @@ #include #include #include +#include #include "audio.h" #include "afk.h" @@ -51,9 +52,10 @@ struct audiosrv_data { bool plugged; struct mutex plug_lock; - struct completion init_completion; struct apple_epic_service *srv; struct rw_semaphore srv_rwsem; + /* Workqueue for starting the audio service */ + struct work_struct start_av_service_wq; struct dcp_av_audio_cmds cmds; }; @@ -75,9 +77,9 @@ static void av_audiosrv_init(struct apple_epic_service *service, const char *nam asrv->srv = service; up_write(&asrv->srv_rwsem); - complete(&asrv->init_completion); asrv->plugged = true; mutex_unlock(&asrv->plug_lock); + schedule_work(&asrv->start_av_service_wq); } static void av_audiosrv_teardown(struct apple_epic_service *service) @@ -280,6 +282,37 @@ static const struct apple_epic_service_ops avep_ops[] = { {} }; +static void av_work_service_start(struct work_struct *work) +{ + int ret; + struct audiosrv_data *audiosrv_data; + struct apple_dcp *dcp; + + audiosrv_data = container_of(work, struct audiosrv_data, start_av_service_wq); + if (!audiosrv_data->srv || + !audiosrv_data->srv->ep || + !audiosrv_data->srv->ep->dcp) { + pr_err("%s: dcp: av: NULL ptr during startup\n", __func__); + return; + } + dcp = audiosrv_data->srv->ep->dcp; + + /* open AV audio service */ + dev_info(dcp->dev, "%s: starting audio service\n", __func__); + ret = afk_service_call(dcp->audiosrv->srv, 0, dcp->audiosrv->cmds.open, + NULL, 0, 32, NULL, 0, 32); + if (ret) { + dev_err(dcp->dev, "error opening audio service: %d\n", ret); + return; + } + + mutex_lock(&dcp->audiosrv->plug_lock); + if (dcp->audiosrv->hotplug_cb) + dcp->audiosrv->hotplug_cb(dcp->audiosrv->audio_dev, + dcp->audiosrv->plugged); + mutex_unlock(&dcp->audiosrv->plug_lock); +} + int avep_init(struct apple_dcp *dcp) { struct dcp_audio_pdata *audio_pdata; @@ -305,7 +338,7 @@ int avep_init(struct apple_dcp *dcp) dev_err(dcp->dev, "Audio not supported for firmware\n"); return -ENODEV; } - init_completion(&audiosrv_data->init_completion); + INIT_WORK(&audiosrv_data->start_av_service_wq, av_work_service_start); dcp->audiosrv = audiosrv_data; @@ -330,28 +363,4 @@ int avep_init(struct apple_dcp *dcp) return PTR_ERR(dcp->avep); dcp->avep->debugfs_entry = dcp->ep_debugfs[AV_ENDPOINT - 0x20]; return afk_start(dcp->avep); - - ret = wait_for_completion_timeout(&dcp->audiosrv->init_completion, - msecs_to_jiffies(500)); - if (ret < 0) { - dev_err(dcp->dev, "error waiting on audio service init: %d\n", ret); - return ret; - } else if (!ret) { - dev_err(dcp->dev, "timeout while waiting for audio service init\n"); - return -ETIMEDOUT; - } - - /* open AV audio service */ - ret = afk_service_call(dcp->audiosrv->srv, 0, dcp->audiosrv->cmds.open, - NULL, 0, 32, NULL, 0, 32); - if (ret) { - dev_err(dcp->dev, "error opening audio service: %d\n", ret); - return ret; - } - - mutex_lock(&dcp->audiosrv->plug_lock); - if (dcp->audiosrv->hotplug_cb) - dcp->audiosrv->hotplug_cb(dcp->audiosrv->audio_dev, - dcp->audiosrv->plugged); - mutex_unlock(&dcp->audiosrv->plug_lock); } From cee00a82d2e154ee2e49370722d352063bf40b61 Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Sat, 20 Apr 2024 21:06:19 +0200 Subject: [PATCH 1164/3902] drm: apple: audio: move the audio driver into the DCP module Those two drivers are closely linked and should always exists together. Signed-off-by: Janne Grunau --- drivers/gpu/drm/apple/Makefile | 6 +----- drivers/gpu/drm/apple/audio.c | 14 +++++++++----- drivers/gpu/drm/apple/dcp.c | 22 +++++++++++++++++++++- drivers/gpu/drm/apple/dcp.h | 4 ++++ 4 files changed, 35 insertions(+), 11 deletions(-) diff --git a/drivers/gpu/drm/apple/Makefile b/drivers/gpu/drm/apple/Makefile index 88067ba9c32ec8..519303016b292a 100644 --- a/drivers/gpu/drm/apple/Makefile +++ b/drivers/gpu/drm/apple/Makefile @@ -5,6 +5,7 @@ CFLAGS_trace.o = -I$(src) appledrm-y := apple_drv.o apple_dcp-y := afk.o dcp.o dcp_backlight.o dptxep.o iomfb.o parser.o systemep.o +apple_dcp-$(CONFIG_DRM_APPLE_AUDIO) += audio.o apple_dcp-$(CONFIG_DRM_APPLE_AUDIO) += av.o apple_dcp-y += connector.o apple_dcp-y += ibootep.o @@ -12,13 +13,8 @@ apple_dcp-y += iomfb_v12_3.o apple_dcp-y += iomfb_v13_3.o apple_dcp-$(CONFIG_TRACING) += trace.o -apple_dcp_audio-y := audio.o - obj-$(CONFIG_DRM_APPLE) += appledrm.o obj-$(CONFIG_DRM_APPLE) += apple_dcp.o -ifeq ($(CONFIG_DRM_APPLE_AUDIO),y) -obj-$(CONFIG_DRM_APPLE) += apple_dcp_audio.o -endif # header test diff --git a/drivers/gpu/drm/apple/audio.c b/drivers/gpu/drm/apple/audio.c index 223b033732216e..e997a6deae7b69 100644 --- a/drivers/gpu/drm/apple/audio.c +++ b/drivers/gpu/drm/apple/audio.c @@ -600,9 +600,13 @@ static struct platform_driver dcpaud_driver = { .remove = dcpaud_remove, }; -module_platform_driver(dcpaud_driver); +void __init dcp_audio_register(void) +{ + platform_driver_register(&dcpaud_driver); +} + +void __exit dcp_audio_unregister(void) +{ + platform_driver_unregister(&dcpaud_driver); +} -MODULE_AUTHOR("Martin Povišer "); -MODULE_DESCRIPTION("Apple DCP HDMI Audio Driver"); -MODULE_LICENSE("GPL"); -MODULE_ALIAS("platform:" DRV_NAME); diff --git a/drivers/gpu/drm/apple/dcp.c b/drivers/gpu/drm/apple/dcp.c index cf79cc0058cf92..01163e0a90e319 100644 --- a/drivers/gpu/drm/apple/dcp.c +++ b/drivers/gpu/drm/apple/dcp.c @@ -1138,7 +1138,27 @@ static struct platform_driver apple_platform_driver = { }, }; -drm_module_platform_driver(apple_platform_driver); +static int __init apple_dcp_register(void) +{ + if (drm_firmware_drivers_only()) + return -ENODEV; + +#if IS_ENABLED(CONFIG_DRM_APPLE_AUDIO) + dcp_audio_register(); +#endif + return platform_driver_register(&apple_platform_driver); +} + +static void __exit apple_dcp_unregister(void) +{ + platform_driver_unregister(&apple_platform_driver); +#if IS_ENABLED(CONFIG_DRM_APPLE_AUDIO) + dcp_audio_unregister(); +#endif +} + +module_init(apple_dcp_register); +module_exit(apple_dcp_unregister); MODULE_AUTHOR("Alyssa Rosenzweig "); MODULE_DESCRIPTION("Apple Display Controller DRM driver"); diff --git a/drivers/gpu/drm/apple/dcp.h b/drivers/gpu/drm/apple/dcp.h index 0a1981040996dc..c284a089b6e0bf 100644 --- a/drivers/gpu/drm/apple/dcp.h +++ b/drivers/gpu/drm/apple/dcp.h @@ -62,4 +62,8 @@ int dptxep_init(struct apple_dcp *dcp); int ibootep_init(struct apple_dcp *dcp); int avep_init(struct apple_dcp *dcp); + +void __init dcp_audio_register(void); +void __exit dcp_audio_unregister(void); + #endif From 2c89d5351afbf1b9660b115face82a41b274c35e Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Sun, 21 Apr 2024 15:47:20 +0200 Subject: [PATCH 1165/3902] drm: apple: audio: Make the DP/HDMI audio driver a full driver The main advantage is that it allows runtime PM which would have been manually implemented with the ad-hoc instantiated platform driver. This also probes the devices as component of the DRM driver which allows to simplify the the interface between the av endpoint and the audio driver. Signed-off-by: Janne Grunau --- drivers/gpu/drm/apple/apple_drv.c | 20 +++- drivers/gpu/drm/apple/audio.c | 149 +++++++++++++++++++++--------- drivers/gpu/drm/apple/audio.h | 14 +-- drivers/gpu/drm/apple/av.c | 71 ++++++-------- 4 files changed, 155 insertions(+), 99 deletions(-) diff --git a/drivers/gpu/drm/apple/apple_drv.c b/drivers/gpu/drm/apple/apple_drv.c index 063ff1410d5d25..f06dd333ad1053 100644 --- a/drivers/gpu/drm/apple/apple_drv.c +++ b/drivers/gpu/drm/apple/apple_drv.c @@ -15,6 +15,7 @@ #include #include #include +#include #include #include @@ -575,7 +576,7 @@ const struct component_master_ops apple_drm_ops = { static int add_dcp_components(struct device *dev, struct component_match **matchptr) { - struct device_node *np; + struct device_node *np, *endpoint, *port; int num = 0; for_each_matching_node(np, apple_dcp_id_tbl) { @@ -583,6 +584,23 @@ static int add_dcp_components(struct device *dev, drm_of_component_match_add(dev, matchptr, component_compare_of, np); num++; + for_each_endpoint_of_node(np, endpoint) { + port = of_graph_get_remote_port_parent(endpoint); + if (!port) + continue; + +#if !IS_ENABLED(CONFIG_DRM_APPLE_AUDIO) + if (of_device_is_compatible(port, "apple,dpaudio")) { + of_node_put(port); + continue; + } +#endif + if (of_device_is_available(port)) + drm_of_component_match_add(dev, matchptr, + component_compare_of, + port); + of_node_put(port); + } } of_node_put(np); } diff --git a/drivers/gpu/drm/apple/audio.c b/drivers/gpu/drm/apple/audio.c index e997a6deae7b69..b4a860d198c32b 100644 --- a/drivers/gpu/drm/apple/audio.c +++ b/drivers/gpu/drm/apple/audio.c @@ -11,9 +11,12 @@ #define DEBUG +#include #include #include #include +#include +#include #include #include #include @@ -22,17 +25,16 @@ #include #include "av.h" +#include "dcp.h" #include "audio.h" #include "parser.h" #define DCPAUD_ELEMENTS_MAXSIZE 16384 #define DCPAUD_PRODUCTATTRS_MAXSIZE 1024 -#define DRV_NAME "dcp-hdmi-audio" - struct dcp_audio { struct device *dev; - struct dcp_audio_pdata *pdata; + struct device *dcp_dev; struct dma_chan *chan; struct snd_card *card; struct snd_jack *jack; @@ -72,12 +74,12 @@ static int dcpaud_read_remote_info(struct dcp_audio *dcpaud) { int ret; - ret = dcp_audiosrv_get_elements(dcpaud->pdata->dcp_dev, dcpaud->elements, + ret = dcp_audiosrv_get_elements(dcpaud->dcp_dev, dcpaud->elements, DCPAUD_ELEMENTS_MAXSIZE); if (ret < 0) return ret; - ret = dcp_audiosrv_get_product_attrs(dcpaud->pdata->dcp_dev, dcpaud->productattrs, + ret = dcp_audiosrv_get_product_attrs(dcpaud->dcp_dev, dcpaud->productattrs, DCPAUD_PRODUCTATTRS_MAXSIZE); if (ret < 0) return ret; @@ -128,7 +130,7 @@ static void dcpaud_consult_elements(struct dcp_audio *dcpaud, { struct dcp_sound_format_mask sieve; struct dcp_parse_ctx elements = { - .dcp = dev_get_drvdata(dcpaud->pdata->dcp_dev), + .dcp = dev_get_drvdata(dcpaud->dcp_dev), .blob = dcpaud->elements + 4, .len = DCPAUD_ELEMENTS_MAXSIZE - 4, .pos = 0, @@ -145,7 +147,7 @@ static int dcpaud_select_cookie(struct dcp_audio *dcpaud, { struct dcp_sound_format_mask sieve; struct dcp_parse_ctx elements = { - .dcp = dev_get_drvdata(dcpaud->pdata->dcp_dev), + .dcp = dev_get_drvdata(dcpaud->dcp_dev), .blob = dcpaud->elements + 4, .len = DCPAUD_ELEMENTS_MAXSIZE - 4, .pos = 0, @@ -317,7 +319,7 @@ static int dcp_pcm_hw_free(struct snd_pcm_substream *substream) if (!dcpaud_connection_up(dcpaud)) return 0; - return dcp_audiosrv_unprepare(dcpaud->pdata->dcp_dev); + return dcp_audiosrv_unprepare(dcpaud->dcp_dev); } static int dcp_pcm_prepare(struct snd_pcm_substream *substream) @@ -327,7 +329,7 @@ static int dcp_pcm_prepare(struct snd_pcm_substream *substream) if (!dcpaud_connection_up(dcpaud)) return -ENXIO; - return dcp_audiosrv_prepare(dcpaud->pdata->dcp_dev, + return dcp_audiosrv_prepare(dcpaud->dcp_dev, &dcpaud->selected_cookie); } @@ -342,7 +344,7 @@ static int dcp_pcm_trigger(struct snd_pcm_substream *substream, int cmd) if (!dcpaud_connection_up(dcpaud)) return -ENXIO; - ret = dcp_audiosrv_startlink(dcpaud->pdata->dcp_dev, + ret = dcp_audiosrv_startlink(dcpaud->dcp_dev, &dcpaud->selected_cookie); if (ret < 0) return ret; @@ -367,7 +369,7 @@ static int dcp_pcm_trigger(struct snd_pcm_substream *substream, int cmd) case SNDRV_PCM_TRIGGER_STOP: case SNDRV_PCM_TRIGGER_SUSPEND: - ret = dcp_audiosrv_stoplink(dcpaud->pdata->dcp_dev); + ret = dcp_audiosrv_stoplink(dcpaud->dcp_dev); if (ret < 0) return ret; break; @@ -431,7 +433,7 @@ static int dcpaud_create_pcm(struct dcp_audio *dcpaud) struct dma_chan *chan; int ret; - chan = of_dma_request_slave_channel(dcpaud->pdata->dpaudio_node, "tx"); + chan = of_dma_request_slave_channel(dcpaud->dev->of_node, "tx"); if (IS_ERR_OR_NULL(chan)) { if (!chan) return -EINVAL; @@ -461,9 +463,8 @@ static int dcpaud_create_pcm(struct dcp_audio *dcpaud) return 0; } -static void dcpaud_report_hotplug(struct device *dev, bool connected) +static void dcpaud_report_hotplug(struct dcp_audio *dcpaud, bool connected) { - struct dcp_audio *dcpaud = dev_get_drvdata(dev); struct snd_pcm_substream *substream = dcpaud->substream; mutex_lock(&dcpaud->data_lock); @@ -518,30 +519,44 @@ static void dcpaud_expose_debugfs_blob(struct dcp_audio *dcpaud, const char *nam static void dcpaud_expose_debugfs_blob(struct dcp_audio *dcpaud, const char *name, void *base, size_t size) {} #endif -static int dcpaud_probe(struct platform_device *pdev) +void dcpaud_connect(struct platform_device *pdev, bool connected) { - struct device *dev = &pdev->dev; - struct dcp_audio_pdata *pdata = dev->platform_data; - struct dcp_audio *dcpaud; - int ret; + struct dcp_audio *dcpaud = platform_get_drvdata(pdev); + dcpaud_report_hotplug(dcpaud, connected); +} - dcpaud = devm_kzalloc(dev, sizeof(*dcpaud), GFP_KERNEL); - if (!dcpaud) - return -ENOMEM; - dcpaud->dev = dev; - dcpaud->pdata = pdata; - mutex_init(&dcpaud->data_lock); - platform_set_drvdata(pdev, dcpaud); +void dcpaud_disconnect(struct platform_device *pdev) +{ + struct dcp_audio *dcpaud = platform_get_drvdata(pdev); + dcpaud_report_hotplug(dcpaud, false); +} - dcpaud->elements = devm_kzalloc(dev, DCPAUD_ELEMENTS_MAXSIZE, - GFP_KERNEL); - if (!dcpaud->elements) - return -ENOMEM; +static int dcpaud_comp_bind(struct device *dev, struct device *main, void *data) +{ + struct dcp_audio *dcpaud = dev_get_drvdata(dev); + struct device_node *endpoint, *dcp_node = NULL; + struct platform_device *dcp_pdev; + int ret; - dcpaud->productattrs = devm_kzalloc(dev, DCPAUD_PRODUCTATTRS_MAXSIZE, - GFP_KERNEL); - if (!dcpaud->productattrs) - return -ENOMEM; + /* find linked DCP instance */ + endpoint = of_graph_get_endpoint_by_regs(dev->of_node, 0, 0); + if (endpoint) { + dcp_node = of_graph_get_remote_port_parent(endpoint); + of_node_put(endpoint); + } + if (!dcp_node || !of_device_is_available(dcp_node)) { + of_node_put(dcp_node); + dev_info(dev, "No audio support\n"); + return 0; + } + + dcp_pdev = of_find_device_by_node(dcp_node); + of_node_put(dcp_node); + if (!dcp_pdev) { + dev_info(dev, "No DP/HDMI audio device not ready\n"); + return 0; + } + dcpaud->dcp_dev = &dcp_pdev->dev; ret = snd_card_new(dev, SNDRV_DEFAULT_IDX1, SNDRV_DEFAULT_STR1, THIS_MODULE, 0, &dcpaud->card); @@ -573,8 +588,6 @@ static int dcpaud_probe(struct platform_device *pdev) dcpaud_expose_debugfs_blob(dcpaud, "product_attrs", dcpaud->productattrs, DCPAUD_PRODUCTATTRS_MAXSIZE); - dcp_audiosrv_set_hotplug_cb(pdata->dcp_dev, dev, dcpaud_report_hotplug); - return 0; err_free_card: @@ -582,22 +595,70 @@ static int dcpaud_probe(struct platform_device *pdev) return ret; } -static int dcpaud_remove(struct platform_device *dev) +static void dcpaud_comp_unbind(struct device *dev, struct device *main, + void *data) { - struct dcp_audio *dcpaud = platform_get_drvdata(dev); + struct dcp_audio *dcpaud = dev_get_drvdata(dev); - dcp_audiosrv_set_hotplug_cb(dcpaud->pdata->dcp_dev, NULL, NULL); - snd_card_free(dcpaud->card); + /* snd_card_free_when_closed() checks for NULL */ + snd_card_free_when_closed(dcpaud->card); +} - return 0; +static const struct component_ops dcpaud_comp_ops = { + .bind = dcpaud_comp_bind, + .unbind = dcpaud_comp_unbind, +}; + +static int dcpaud_probe(struct platform_device *pdev) +{ + struct dcp_audio *dcpaud; + + dcpaud = devm_kzalloc(&pdev->dev, sizeof(*dcpaud), GFP_KERNEL); + if (!dcpaud) + return -ENOMEM; + + dcpaud->elements = devm_kzalloc(&pdev->dev, DCPAUD_ELEMENTS_MAXSIZE, + GFP_KERNEL); + if (!dcpaud->elements) + return -ENOMEM; + + dcpaud->productattrs = devm_kzalloc(&pdev->dev, DCPAUD_PRODUCTATTRS_MAXSIZE, + GFP_KERNEL); + if (!dcpaud->productattrs) + return -ENOMEM; + + dcpaud->dev = &pdev->dev; + mutex_init(&dcpaud->data_lock); + platform_set_drvdata(pdev, dcpaud); + + return component_add(&pdev->dev, &dcpaud_comp_ops); } +static void dcpaud_remove(struct platform_device *pdev) +{ + component_del(&pdev->dev, &dcpaud_comp_ops); +} + +static void dcpaud_shutdown(struct platform_device *pdev) +{ + component_del(&pdev->dev, &dcpaud_comp_ops); +} + +// static DEFINE_SIMPLE_DEV_PM_OPS(dcpaud_pm_ops, dcpaud_suspend, dcpaud_resume); + +static const struct of_device_id dcpaud_of_match[] = { + { .compatible = "apple,dpaudio" }, + {} +}; + static struct platform_driver dcpaud_driver = { .driver = { - .name = DRV_NAME, + .name = "dcp-dp-audio", + .of_match_table = dcpaud_of_match, }, - .probe = dcpaud_probe, - .remove = dcpaud_remove, + .probe = dcpaud_probe, + .remove = dcpaud_remove, + .shutdown = dcpaud_shutdown, }; void __init dcp_audio_register(void) diff --git a/drivers/gpu/drm/apple/audio.h b/drivers/gpu/drm/apple/audio.h index 3cf4d31417694e..83b990dc6c343f 100644 --- a/drivers/gpu/drm/apple/audio.h +++ b/drivers/gpu/drm/apple/audio.h @@ -4,18 +4,9 @@ #include struct device; -struct device_node; +struct platform_device; struct dcp_sound_cookie; -typedef void (*dcp_audio_hotplug_callback)(struct device *dev, bool connected); - -struct dcp_audio_pdata { - struct device *dcp_dev; - struct device_node *dpaudio_node; -}; - -void dcp_audiosrv_set_hotplug_cb(struct device *dev, struct device *audio_dev, - dcp_audio_hotplug_callback cb); int dcp_audiosrv_prepare(struct device *dev, struct dcp_sound_cookie *cookie); int dcp_audiosrv_startlink(struct device *dev, struct dcp_sound_cookie *cookie); int dcp_audiosrv_stoplink(struct device *dev); @@ -23,4 +14,7 @@ int dcp_audiosrv_unprepare(struct device *dev); int dcp_audiosrv_get_elements(struct device *dev, void *elements, size_t maxsize); int dcp_audiosrv_get_product_attrs(struct device *dev, void *attrs, size_t maxsize); +void dcpaud_connect(struct platform_device *pdev, bool connected); +void dcpaud_disconnect(struct platform_device *pdev); + #endif /* __AUDIO_H__ */ diff --git a/drivers/gpu/drm/apple/av.c b/drivers/gpu/drm/apple/av.c index 926c4b238227b1..66a99cb2ed7b0f 100644 --- a/drivers/gpu/drm/apple/av.c +++ b/drivers/gpu/drm/apple/av.c @@ -5,6 +5,8 @@ #include #include +#include +#include #include #include #include @@ -47,8 +49,7 @@ static const struct dcp_av_audio_cmds dcp_av_audio_cmds_v13_5 = { }; struct audiosrv_data { - struct device *audio_dev; - dcp_audio_hotplug_callback hotplug_cb; + struct platform_device *audio_dev; bool plugged; struct mutex plug_lock; @@ -94,28 +95,12 @@ static void av_audiosrv_teardown(struct apple_epic_service *service) up_write(&asrv->srv_rwsem); asrv->plugged = false; - if (asrv->hotplug_cb) - asrv->hotplug_cb(asrv->audio_dev, false); + if (asrv->audio_dev) + dcpaud_disconnect(asrv->audio_dev); mutex_unlock(&asrv->plug_lock); } -void dcp_audiosrv_set_hotplug_cb(struct device *dev, struct device *audio_dev, - dcp_audio_hotplug_callback cb) -{ - struct apple_dcp *dcp = dev_get_drvdata(dev); - struct audiosrv_data *asrv = dcp->audiosrv; - - mutex_lock(&asrv->plug_lock); - asrv->audio_dev = audio_dev; - asrv->hotplug_cb = cb; - - if (cb) - cb(audio_dev, asrv->plugged); - mutex_unlock(&asrv->plug_lock); -} -EXPORT_SYMBOL_GPL(dcp_audiosrv_set_hotplug_cb); - int dcp_audiosrv_prepare(struct device *dev, struct dcp_sound_cookie *cookie) { struct apple_dcp *dcp = dev_get_drvdata(dev); @@ -130,7 +115,6 @@ int dcp_audiosrv_prepare(struct device *dev, struct dcp_sound_cookie *cookie) return ret; } -EXPORT_SYMBOL_GPL(dcp_audiosrv_prepare); int dcp_audiosrv_startlink(struct device *dev, struct dcp_sound_cookie *cookie) { @@ -146,7 +130,6 @@ int dcp_audiosrv_startlink(struct device *dev, struct dcp_sound_cookie *cookie) return ret; } -EXPORT_SYMBOL_GPL(dcp_audiosrv_startlink); int dcp_audiosrv_stoplink(struct device *dev) { @@ -161,7 +144,6 @@ int dcp_audiosrv_stoplink(struct device *dev) return ret; } -EXPORT_SYMBOL_GPL(dcp_audiosrv_stoplink); int dcp_audiosrv_unprepare(struct device *dev) { @@ -176,7 +158,6 @@ int dcp_audiosrv_unprepare(struct device *dev) return ret; } -EXPORT_SYMBOL_GPL(dcp_audiosrv_unprepare); static int dcp_audiosrv_osobject_call(struct apple_epic_service *service, u16 group, @@ -233,7 +214,6 @@ int dcp_audiosrv_get_elements(struct device *dev, void *elements, size_t maxsize return ret; } -EXPORT_SYMBOL_GPL(dcp_audiosrv_get_elements); int dcp_audiosrv_get_product_attrs(struct device *dev, void *attrs, size_t maxsize) { @@ -255,7 +235,6 @@ int dcp_audiosrv_get_product_attrs(struct device *dev, void *attrs, size_t maxsi return ret; } -EXPORT_SYMBOL_GPL(dcp_audiosrv_get_product_attrs); static int av_audiosrv_report(struct apple_epic_service *service, u32 idx, const void *data, size_t data_size) @@ -307,22 +286,20 @@ static void av_work_service_start(struct work_struct *work) } mutex_lock(&dcp->audiosrv->plug_lock); - if (dcp->audiosrv->hotplug_cb) - dcp->audiosrv->hotplug_cb(dcp->audiosrv->audio_dev, - dcp->audiosrv->plugged); + if (dcp->audiosrv->audio_dev) + dcpaud_connect(dcp->audiosrv->audio_dev, dcp->audiosrv->plugged); mutex_unlock(&dcp->audiosrv->plug_lock); } int avep_init(struct apple_dcp *dcp) { - struct dcp_audio_pdata *audio_pdata; - struct platform_device *audio_pdev; struct audiosrv_data *audiosrv_data; + struct platform_device *audio_pdev; struct device *dev = dcp->dev; + struct device_node *endpoint, *audio_node = NULL; audiosrv_data = devm_kzalloc(dcp->dev, sizeof(*audiosrv_data), GFP_KERNEL); - audio_pdata = devm_kzalloc(dcp->dev, sizeof(*audio_pdata), GFP_KERNEL); - if (!audiosrv_data || !audio_pdata) + if (!audiosrv_data) return -ENOMEM; init_rwsem(&audiosrv_data->srv_rwsem); mutex_init(&audiosrv_data->plug_lock); @@ -342,21 +319,27 @@ int avep_init(struct apple_dcp *dcp) dcp->audiosrv = audiosrv_data; - audio_pdata->dcp_dev = dcp->dev; - /* TODO: free OF reference */ - audio_pdata->dpaudio_node = \ - of_parse_phandle(dev->of_node, "apple,audio-xmitter", 0); - if (!audio_pdata->dpaudio_node || - !of_device_is_available(audio_pdata->dpaudio_node)) { + endpoint = of_graph_get_endpoint_by_regs(dev->of_node, 0, 0); + if (endpoint) { + audio_node = of_graph_get_remote_port_parent(endpoint); + of_node_put(endpoint); + } + if (!audio_node || !of_device_is_available(audio_node)) { + of_node_put(audio_node); dev_info(dev, "No audio support\n"); return 0; } - audio_pdev = platform_device_register_data(dev, "dcp-hdmi-audio", - PLATFORM_DEVID_AUTO, - audio_pdata, sizeof(*audio_pdata)); - if (IS_ERR(audio_pdev)) - return dev_err_probe(dev, PTR_ERR(audio_pdev), "registering audio device\n"); + audio_pdev = of_find_device_by_node(audio_node); + of_node_put(audio_node); + if (!audio_pdev) { + dev_info(dev, "No DP/HDMI audio device not ready\n"); + return 0; + } + dcp->audiosrv->audio_dev = audio_pdev; + + device_link_add(&audio_pdev->dev, dev, + DL_FLAG_STATELESS | DL_FLAG_PM_RUNTIME); dcp->avep = afk_init(dcp, AV_ENDPOINT, avep_ops); if (IS_ERR(dcp->avep)) From 027dede740d5d45c4113bdf45b8ca29f7e632976 Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Mon, 22 Apr 2024 19:47:22 +0200 Subject: [PATCH 1166/3902] drm: apple: audio: Avoid probe errors Now that the DP audio driver is a component of the display sub-system probe errors will bring down the whole display initialization. To prevent that the audio driver must not fail. Allow delayed sound card initialization if the DMA controller is not ready, for example because the apple-sio module is missing (at all or just in the initeramfs). In the case apple-sio is available later provide as sysfs file "probe_snd_card" to trigger initialization. Signed-off-by: Janne Grunau --- drivers/gpu/drm/apple/audio.c | 147 ++++++++++++++++++++++++---------- 1 file changed, 105 insertions(+), 42 deletions(-) diff --git a/drivers/gpu/drm/apple/audio.c b/drivers/gpu/drm/apple/audio.c index b4a860d198c32b..9266af8038083d 100644 --- a/drivers/gpu/drm/apple/audio.c +++ b/drivers/gpu/drm/apple/audio.c @@ -42,6 +42,7 @@ struct dcp_audio { unsigned int open_cookie; struct mutex data_lock; + bool dcp_connected; /// dcp status keep for delayed initialization bool connected; unsigned int connection_cookie; @@ -430,19 +431,8 @@ static int dcpaud_create_pcm(struct dcp_audio *dcpaud) { struct snd_card *card = dcpaud->card; struct snd_pcm *pcm; - struct dma_chan *chan; int ret; - chan = of_dma_request_slave_channel(dcpaud->dev->of_node, "tx"); - if (IS_ERR_OR_NULL(chan)) { - if (!chan) - return -EINVAL; - - dev_err(dcpaud->dev, "can't request audio TX DMA channel: %pE\n", chan); - return PTR_ERR(chan); - } - dcpaud->chan = chan; - #define NUM_PLAYBACK 1 #define NUM_CAPTURE 0 @@ -453,7 +443,7 @@ static int dcpaud_create_pcm(struct dcp_audio *dcpaud) snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &dcp_playback_ops); dcpaud->substream = pcm->streams[SNDRV_PCM_STREAM_PLAYBACK].substream; snd_pcm_set_managed_buffer(dcpaud->substream, SNDRV_DMA_TYPE_DEV_IRAM, - chan->device->dev, 1024 * 1024, + dcpaud->chan->device->dev, 1024 * 1024, SIZE_MAX); pcm->nonatomic = true; @@ -463,12 +453,12 @@ static int dcpaud_create_pcm(struct dcp_audio *dcpaud) return 0; } +/* expects to be called with data_lock locked and unlocks it */ static void dcpaud_report_hotplug(struct dcp_audio *dcpaud, bool connected) { struct snd_pcm_substream *substream = dcpaud->substream; - mutex_lock(&dcpaud->data_lock); - if (dcpaud->connected == connected) { + if (!dcpaud->card || dcpaud->connected == connected) { mutex_unlock(&dcpaud->data_lock); return; } @@ -504,6 +494,53 @@ static void dcpaud_set_card_names(struct dcp_audio *dcpaud) strscpy(card->shortname, "Apple DisplayPort", sizeof(card->shortname)); } +static int dcpaud_init_snd_card(struct dcp_audio *dcpaud) +{ + int ret; + struct dma_chan *chan; + + chan = of_dma_request_slave_channel(dcpaud->dev->of_node, "tx"); + /* squelch dma channel request errors, the driver will try again alter */ + if (!chan) { + dev_warn(dcpaud->dev, "audio TX DMA channel request failed\n"); + return 0; + } else if (IS_ERR(chan)) { + dev_warn(dcpaud->dev, "audio TX DMA channel request failed: %pE\n", chan); + return 0; + } + dcpaud->chan = chan; + + ret = snd_card_new(dcpaud->dev, SNDRV_DEFAULT_IDX1, SNDRV_DEFAULT_STR1, + THIS_MODULE, 0, &dcpaud->card); + if (ret) + return ret; + + dcpaud_set_card_names(dcpaud); + + ret = dcpaud_create_pcm(dcpaud); + if (ret) + goto err_free_card; + + ret = dcpaud_create_chmap_ctl(dcpaud); + if (ret) + goto err_free_card; + + ret = dcpaud_create_jack(dcpaud); + if (ret) + goto err_free_card; + + ret = snd_card_register(dcpaud->card); + if (ret) + goto err_free_card; + + return 0; +err_free_card: + dev_warn(dcpaud->dev, "Failed to initialize sound card: %d\n", ret); + snd_card_free(dcpaud->card); + dcpaud->card = NULL; + return ret; +} + #ifdef CONFIG_SND_DEBUG static void dcpaud_expose_debugfs_blob(struct dcp_audio *dcpaud, const char *name, void *base, size_t size) { @@ -522,15 +559,59 @@ static void dcpaud_expose_debugfs_blob(struct dcp_audio *dcpaud, const char *nam void dcpaud_connect(struct platform_device *pdev, bool connected) { struct dcp_audio *dcpaud = platform_get_drvdata(pdev); + + mutex_lock(&dcpaud->data_lock); + + if (!dcpaud->chan) { + int ret = dcpaud_init_snd_card(dcpaud); + if (ret) { + dcpaud->dcp_connected = connected; + mutex_unlock(&dcpaud->data_lock); + return; + } + } dcpaud_report_hotplug(dcpaud, connected); } void dcpaud_disconnect(struct platform_device *pdev) { struct dcp_audio *dcpaud = platform_get_drvdata(pdev); + + mutex_lock(&dcpaud->data_lock); + + dcpaud->dcp_connected = false; dcpaud_report_hotplug(dcpaud, false); } +static ssize_t probe_snd_card_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + int ret; + bool connected = false; + struct dcp_audio *dcpaud = dev_get_drvdata(dev); + + mutex_lock(&dcpaud->data_lock); + + if (!dcpaud->chan) { + ret = dcpaud_init_snd_card(dcpaud); + if (ret) + goto out_unlock; + + connected = dcpaud->dcp_connected; + if (connected) { + dcpaud_report_hotplug(dcpaud, connected); + goto out; + } + } +out_unlock: + mutex_unlock(&dcpaud->data_lock); +out: + return count; +} + +static const DEVICE_ATTR_WO(probe_snd_card); + static int dcpaud_comp_bind(struct device *dev, struct device *main, void *data) { struct dcp_audio *dcpaud = dev_get_drvdata(dev); @@ -553,34 +634,11 @@ static int dcpaud_comp_bind(struct device *dev, struct device *main, void *data) dcp_pdev = of_find_device_by_node(dcp_node); of_node_put(dcp_node); if (!dcp_pdev) { - dev_info(dev, "No DP/HDMI audio device not ready\n"); + dev_info(dev, "No DP/HDMI audio device, dcp not ready\n"); return 0; } dcpaud->dcp_dev = &dcp_pdev->dev; - ret = snd_card_new(dev, SNDRV_DEFAULT_IDX1, SNDRV_DEFAULT_STR1, - THIS_MODULE, 0, &dcpaud->card); - if (ret) - return ret; - - dcpaud_set_card_names(dcpaud); - - ret = dcpaud_create_pcm(dcpaud); - if (ret) - goto err_free_card; - - ret = dcpaud_create_chmap_ctl(dcpaud); - if (ret) - goto err_free_card; - - ret = dcpaud_create_jack(dcpaud); - if (ret) - goto err_free_card; - - ret = snd_card_register(dcpaud->card); - if (ret) - goto err_free_card; - dcpaud_expose_debugfs_blob(dcpaud, "selected_cookie", &dcpaud->selected_cookie, sizeof(dcpaud->selected_cookie)); dcpaud_expose_debugfs_blob(dcpaud, "elements", dcpaud->elements, @@ -588,11 +646,16 @@ static int dcpaud_comp_bind(struct device *dev, struct device *main, void *data) dcpaud_expose_debugfs_blob(dcpaud, "product_attrs", dcpaud->productattrs, DCPAUD_PRODUCTATTRS_MAXSIZE); - return 0; + mutex_lock(&dcpaud->data_lock); + /* ignore errors to prevent audio issues affecting the display side */ + dcpaud_init_snd_card(dcpaud); + mutex_unlock(&dcpaud->data_lock); -err_free_card: - snd_card_free(dcpaud->card); - return ret; + ret = device_create_file(dev, &dev_attr_probe_snd_card); + if (ret) + dev_info(dev, "creating force probe sysfs file failed: %d\n", ret); + + return 0; } static void dcpaud_comp_unbind(struct device *dev, struct device *main, From 82c92c3430601c1dfba1c872983a710aee008683 Mon Sep 17 00:00:00 2001 From: Jonathan Gray Date: Sun, 21 Apr 2024 11:15:04 +1000 Subject: [PATCH 1167/3902] drm/apple: fix double words in comments Signed-off-by: Jonathan Gray --- drivers/gpu/drm/apple/afk.c | 2 +- drivers/gpu/drm/apple/dcp-internal.h | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/drivers/gpu/drm/apple/afk.c b/drivers/gpu/drm/apple/afk.c index 218c28dfe84249..d3e45a6180af69 100644 --- a/drivers/gpu/drm/apple/afk.c +++ b/drivers/gpu/drm/apple/afk.c @@ -641,7 +641,7 @@ static bool afk_recv(struct apple_dcp_afkep *ep) * TODO: this is theoretically unsafe since DCP could overwrite data * after the read pointer was updated above. Do it anyway since * it avoids 2 problems in the DCP tracer: - * 1. the tracer sees replies before the the notifies from dcp + * 1. the tracer sees replies before the notifies from dcp * 2. the tracer tries to read buffers after they are unmapped. */ afk_recv_handle(ep, channel, type, hdr->data, size); diff --git a/drivers/gpu/drm/apple/dcp-internal.h b/drivers/gpu/drm/apple/dcp-internal.h index 4c352fd7791dec..8f1a55279597b3 100644 --- a/drivers/gpu/drm/apple/dcp-internal.h +++ b/drivers/gpu/drm/apple/dcp-internal.h @@ -128,7 +128,7 @@ struct apple_dcp { /************* IOMFB ************************************************** * everything below is mostly used inside IOMFB but it could make * - * sense keep some of the the members in apple_dcp. * + * sense to keep some of the members in apple_dcp. * **********************************************************************/ /* clock rate request by dcp in */ @@ -212,7 +212,7 @@ struct apple_dcp { struct list_head swapped_out_fbs; struct dcp_brightness brightness; - /* Workqueue for updating the initial initial brightness */ + /* Workqueue for updating the initial brightness */ struct work_struct bl_register_wq; struct mutex bl_register_mutex; /* Workqueue for updating the brightness */ From 14359247dda29ddbed8ff5253beac4231e62f985 Mon Sep 17 00:00:00 2001 From: Caspar Schutijser Date: Thu, 18 Apr 2024 22:26:58 +0100 Subject: [PATCH 1168/3902] drm: apple: backlight: release lock in error path Signed-off-by: Caspar Schutijser --- drivers/gpu/drm/apple/dcp_backlight.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/drivers/gpu/drm/apple/dcp_backlight.c b/drivers/gpu/drm/apple/dcp_backlight.c index ed3b240ead8557..1397000c27935c 100644 --- a/drivers/gpu/drm/apple/dcp_backlight.c +++ b/drivers/gpu/drm/apple/dcp_backlight.c @@ -150,8 +150,10 @@ static int drm_crtc_set_brightness(struct apple_dcp *dcp) goto done; state = drm_atomic_state_alloc(crtc->dev); - if (!state) - return -ENOMEM; + if (!state) { + ret = -ENOMEM; + goto done; + } state->acquire_ctx = &ctx; crtc_state = drm_atomic_get_crtc_state(state, crtc); From 8d91a71aae9d662ece0aafccc23a3dee6e031c2c Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Sat, 27 Apr 2024 17:55:25 +0200 Subject: [PATCH 1169/3902] drm: apple: Switch back to drm_atomic_helper_commit_tail_rpm() The custom commit_tail implementation stopped making after "drm/apple: Disable fake vblank IRQ machinery" which stopped calling drm_vblank_init(). Revert back to the standard helper implementation. Avoids or at least significantly reduces page flips taking approximately one frame time in kwin_wayland 6. Fixes: ("drm/apple: Switch to nonblocking commit handling") Signed-off-by: Janne Grunau --- drivers/gpu/drm/apple/apple_drv.c | 22 +--------------------- 1 file changed, 1 insertion(+), 21 deletions(-) diff --git a/drivers/gpu/drm/apple/apple_drv.c b/drivers/gpu/drm/apple/apple_drv.c index f06dd333ad1053..44c9ea73f4a02f 100644 --- a/drivers/gpu/drm/apple/apple_drv.c +++ b/drivers/gpu/drm/apple/apple_drv.c @@ -239,26 +239,6 @@ static void apple_crtc_atomic_begin(struct drm_crtc *crtc, } } -static void dcp_atomic_commit_tail(struct drm_atomic_state *old_state) -{ - struct drm_device *dev = old_state->dev; - - drm_atomic_helper_commit_modeset_disables(dev, old_state); - - drm_atomic_helper_commit_modeset_enables(dev, old_state); - - drm_atomic_helper_commit_planes(dev, old_state, - DRM_PLANE_COMMIT_ACTIVE_ONLY); - - drm_atomic_helper_fake_vblank(old_state); - - drm_atomic_helper_commit_hw_done(old_state); - - drm_atomic_helper_wait_for_flip_done(dev, old_state); - - drm_atomic_helper_cleanup_planes(dev, old_state); -} - static void apple_crtc_cleanup(struct drm_crtc *crtc) { drm_crtc_cleanup(crtc); @@ -281,7 +261,7 @@ static const struct drm_mode_config_funcs apple_mode_config_funcs = { }; static const struct drm_mode_config_helper_funcs apple_mode_config_helpers = { - .atomic_commit_tail = dcp_atomic_commit_tail, + .atomic_commit_tail = drm_atomic_helper_commit_tail_rpm, }; static void appledrm_connector_cleanup(struct drm_connector *connector) From 7b585a806741546cdca54c4e3bdaf66c0a06df3e Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Sat, 4 May 2024 13:09:15 +0200 Subject: [PATCH 1170/3902] drm: apple: Fix broken MemDescRelay::release_descriptor callback number Two callbacks for IOMFB::MemDescRelay seems to be dropped between 12.3 and 13.5 DCP firmware. This results in the renumbering of MemDescRelay::release_descriptor from D456 to D454. Noticed while when switching the display refresh rate to 50 Hz with a 14.5 system firmware on a M1 Max Macbook Pro. Signed-off-by: Janne Grunau --- drivers/gpu/drm/apple/iomfb_v13_3.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/gpu/drm/apple/iomfb_v13_3.c b/drivers/gpu/drm/apple/iomfb_v13_3.c index 115490fd9cc6e3..0ac869d24eb01b 100644 --- a/drivers/gpu/drm/apple/iomfb_v13_3.c +++ b/drivers/gpu/drm/apple/iomfb_v13_3.c @@ -81,7 +81,7 @@ static const iomfb_cb_handler cb_handlers[IOMFB_MAX_CB] = { [415] = trampoline_true, /* sr_set_property_bool */ [451] = trampoline_allocate_buffer, [452] = trampoline_map_physical, - [456] = trampoline_release_mem_desc, + [454] = trampoline_release_mem_desc, [552] = trampoline_true, /* set_property_dict_0 */ [561] = trampoline_true, /* set_property_dict */ [563] = trampoline_true, /* set_property_int */ From d7b78f5dff754f4eaa4719bd2fa9fb0d8e6e57d0 Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Sat, 4 May 2024 13:23:03 +0200 Subject: [PATCH 1171/3902] drm: apple: Reduce log spam about busy command channel The most likely cause for this is an unexpected callback form which the current driver doesn't recover. Warn only once about it. Signed-off-by: Janne Grunau --- drivers/gpu/drm/apple/dcp-internal.h | 2 ++ drivers/gpu/drm/apple/iomfb.c | 7 ++++++- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/drivers/gpu/drm/apple/dcp-internal.h b/drivers/gpu/drm/apple/dcp-internal.h index 8f1a55279597b3..1c82201f6e934c 100644 --- a/drivers/gpu/drm/apple/dcp-internal.h +++ b/drivers/gpu/drm/apple/dcp-internal.h @@ -71,6 +71,8 @@ struct dcp_channel { /* Current depth of the call stack. Less than DCP_MAX_CALL_DEPTH */ u8 depth; + /* Already warned about busy channel */ + bool warned_busy; }; struct dcp_fb_reference { diff --git a/drivers/gpu/drm/apple/iomfb.c b/drivers/gpu/drm/apple/iomfb.c index 9fe8487053efeb..0941ff69873235 100644 --- a/drivers/gpu/drm/apple/iomfb.c +++ b/drivers/gpu/drm/apple/iomfb.c @@ -482,12 +482,17 @@ void dcp_flush(struct drm_crtc *crtc, struct drm_atomic_state *state) if (dcp_channel_busy(&dcp->ch_cmd)) { - dev_err(dcp->dev, "unexpected busy command channel\n"); + if (!dcp->ch_cmd.warned_busy) { + dev_err(dcp->dev, "unexpected busy command channel\n"); + dcp->ch_cmd.warned_busy = true; + } /* HACK: issue a delayed vblank event to avoid timeouts in * drm_atomic_helper_wait_for_vblanks(). */ schedule_work(&dcp->vblank_wq); return; + } else if (dcp->ch_cmd.warned_busy) { + dcp->ch_cmd.warned_busy = false; } switch (dcp->fw_compat) { From 177b1a084384edda3d1cb81b1b6c7826d8e0147a Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Sat, 4 May 2024 13:33:57 +0200 Subject: [PATCH 1172/3902] drm: apple: av: Warn only once about failed calls Reduce log spam while errors are still likely due missing state checks. --- drivers/gpu/drm/apple/av.c | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/drivers/gpu/drm/apple/av.c b/drivers/gpu/drm/apple/av.c index 66a99cb2ed7b0f..8a2c1126f5adea 100644 --- a/drivers/gpu/drm/apple/av.c +++ b/drivers/gpu/drm/apple/av.c @@ -59,6 +59,9 @@ struct audiosrv_data { struct work_struct start_av_service_wq; struct dcp_av_audio_cmds cmds; + + bool warned_get_elements; + bool warned_get_product_attrs; }; static void av_interface_init(struct apple_epic_service *service, const char *name, @@ -207,10 +210,12 @@ int dcp_audiosrv_get_elements(struct device *dev, void *elements, size_t maxsize elements, maxsize, &size); up_write(&asrv->srv_rwsem); - if (ret) + if (ret && asrv->warned_get_elements) { dev_err(dev, "audiosrv: error getting elements: %d\n", ret); - else + asrv->warned_get_elements = true; + } else { dev_dbg(dev, "audiosrv: got %zd bytes worth of elements\n", size); + } return ret; } @@ -228,10 +233,12 @@ int dcp_audiosrv_get_product_attrs(struct device *dev, void *attrs, size_t maxsi maxsize, &size); up_write(&asrv->srv_rwsem); - if (ret) + if (ret && asrv->warned_get_product_attrs) { dev_err(dev, "audiosrv: error getting product attributes: %d\n", ret); - else + asrv->warned_get_product_attrs = true; + } else { dev_dbg(dev, "audiosrv: got %zd bytes worth of product attributes\n", size); + } return ret; } From 9d8f259d189a54c9584c537cdcbab2cb1fba69e0 Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Wed, 8 May 2024 16:55:11 +0200 Subject: [PATCH 1173/3902] drm: apple: disable HDMI audio by default Can be still enabled by adding `apple_dcp.hdmi_audio` the kernel command line. Signed-off-by: Janne Grunau --- drivers/gpu/drm/apple/audio.c | 5 +++++ drivers/gpu/drm/apple/dcp.c | 8 ++++---- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/drivers/gpu/drm/apple/audio.c b/drivers/gpu/drm/apple/audio.c index 9266af8038083d..923f5421298305 100644 --- a/drivers/gpu/drm/apple/audio.c +++ b/drivers/gpu/drm/apple/audio.c @@ -494,11 +494,16 @@ static void dcpaud_set_card_names(struct dcp_audio *dcpaud) strscpy(card->shortname, "Apple DisplayPort", sizeof(card->shortname)); } +extern bool hdmi_audio; + static int dcpaud_init_snd_card(struct dcp_audio *dcpaud) { int ret; struct dma_chan *chan; + if (!hdmi_audio) + return -ENODEV; + chan = of_dma_request_slave_channel(dcpaud->dev->of_node, "tx"); /* squelch dma channel request errors, the driver will try again alter */ if (!chan) { diff --git a/drivers/gpu/drm/apple/dcp.c b/drivers/gpu/drm/apple/dcp.c index 01163e0a90e319..2216e3c4633059 100644 --- a/drivers/gpu/drm/apple/dcp.c +++ b/drivers/gpu/drm/apple/dcp.c @@ -46,9 +46,9 @@ static bool show_notch; module_param(show_notch, bool, 0644); MODULE_PARM_DESC(show_notch, "Use the full display height and shows the notch"); -static bool noaudio; -module_param(noaudio, bool, 0644); -MODULE_PARM_DESC(noaudio, "Skip audio support"); +bool hdmi_audio; +module_param(hdmi_audio, bool, 0644); +MODULE_PARM_DESC(hdmi_audio, "Enable unstable HDMI audio support"); /* HACK: moved here to avoid circular dependency between apple_drv and dcp */ void dcp_drm_crtc_vblank(struct apple_crtc *crtc) @@ -413,7 +413,7 @@ int dcp_start(struct platform_device *pdev) dev_err(dcp->dev, "Failed to start IOMFB endpoint: %d\n", ret); #if IS_ENABLED(CONFIG_DRM_APPLE_AUDIO) - if (!noaudio) { + if (hdmi_audio) { ret = avep_init(dcp); if (ret) dev_warn(dcp->dev, "Failed to start AV endpoint: %d", ret); From d4315d81c8103aa2ef83fb767f5d3f78c45f102a Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Sun, 12 May 2024 09:39:59 +0200 Subject: [PATCH 1174/3902] drm: apple: Override drm_vblank's page flip event handling [HACK] Since we don't init/uses drm's vblank support our page flip timestamps are CLOCK_MONOTONIC timestamps during the event generation. Since compositors use the timestamp to schedule their next kms commit this is timing sensitive sop move it under the drivers control. Take the timestamp directly in the swap_complete callback. Framebuffer swaps are unfortunately not fast with DCP. Measured time from swap_submit to swap_complete is ~1.5 ms for dcp and ~2.3 ms for dcpext. This warrants further investigation. Presentation timestamps might help if delay on dcp firmware side occurs after the actual swap. In the meantime doctor the time stamps and move the page flip completion up to 1 ms earler. This fixes half rate refresh on external displays displays using dcpext. Signed-off-by: Janne Grunau --- drivers/gpu/drm/apple/dcp-internal.h | 3 + drivers/gpu/drm/apple/dcp.c | 87 ++++++++++++++++++++++++++ drivers/gpu/drm/apple/iomfb_template.c | 4 +- 3 files changed, 93 insertions(+), 1 deletion(-) diff --git a/drivers/gpu/drm/apple/dcp-internal.h b/drivers/gpu/drm/apple/dcp-internal.h index 1c82201f6e934c..1a465138104d57 100644 --- a/drivers/gpu/drm/apple/dcp-internal.h +++ b/drivers/gpu/drm/apple/dcp-internal.h @@ -175,6 +175,7 @@ struct apple_dcp { /* swap id of the last completed swap */ u32 last_swap_id; + ktime_t swap_start; /* Current display mode */ bool during_modeset; @@ -253,6 +254,8 @@ struct apple_dcp { int hdmi_hpd_irq; }; +void dcp_drm_crtc_page_flip(struct apple_dcp *dcp, ktime_t now); + int dcp_backlight_register(struct apple_dcp *dcp); int dcp_backlight_update(struct apple_dcp *dcp); bool dcp_has_panel(struct apple_dcp *dcp); diff --git a/drivers/gpu/drm/apple/dcp.c b/drivers/gpu/drm/apple/dcp.c index 2216e3c4633059..53fc722bf833c3 100644 --- a/drivers/gpu/drm/apple/dcp.c +++ b/drivers/gpu/drm/apple/dcp.c @@ -50,6 +50,76 @@ bool hdmi_audio; module_param(hdmi_audio, bool, 0644); MODULE_PARM_DESC(hdmi_audio, "Enable unstable HDMI audio support"); +/* copied and simplified from drm_vblank.c */ +static void send_vblank_event(struct drm_device *dev, + struct drm_pending_vblank_event *e, + u64 seq, ktime_t now) +{ + struct timespec64 tv; + + if (e->event.base.type != DRM_EVENT_FLIP_COMPLETE) + return; + + tv = ktime_to_timespec64(now); + e->event.vbl.sequence = seq; + /* + * e->event is a user space structure, with hardcoded unsigned + * 32-bit seconds/microseconds. This is safe as we always use + * monotonic timestamps since linux-4.15 + */ + e->event.vbl.tv_sec = tv.tv_sec; + e->event.vbl.tv_usec = tv.tv_nsec / 1000; + + /* + * Use the same timestamp for any associated fence signal to avoid + * mismatch in timestamps for vsync & fence events triggered by the + * same HW event. Frameworks like SurfaceFlinger in Android expects the + * retire-fence timestamp to match exactly with HW vsync as it uses it + * for its software vsync modeling. + */ + drm_send_event_timestamp_locked(dev, &e->base, now); +} + +/** + * dcp_crtc_send_page_flip_event - helper to send vblank event after pageflip + * + * Compensate for unknown slack between page flip and arrival of the + * swap_complete callback. Minimal observed duration on DCP with HDMI output + * was around 2.3 ms. If the fb swap was submitted closer to the expected + * swap_complete it gets a penalty of one frame duration. This is on the border + * of unreasonable considering that Apple advertises support for 240 Hz (frame + * duration of 4.167 ms). + * It is unreasonable considering kwin's kms commit scheduling. Kwin commits + * 1.5 ms + the mode's vblank time before the expected next page flip + * completion. This results in presenting at half the display's rate for HDMI + * outputs. + * This might be a difference between dcp and dcpext. + */ +static void dcp_crtc_send_page_flip_event(struct apple_crtc *crtc, + struct drm_pending_vblank_event *e, + ktime_t now, ktime_t start) +{ + struct drm_device *dev = crtc->base.dev; + u64 seq; + unsigned int pipe = drm_crtc_index(&crtc->base); + ktime_t flip; + + seq = 0; + if (start != KTIME_MIN) { + s64 delta = ktime_us_delta(now, start); + if (delta <= 500) + flip = now; + else if (delta >= 2500) + flip = ktime_sub_us(now, 1000); + else + flip = ktime_sub_us(now, (delta - 500) / 2); + } else { + flip = now; + } + e->pipe = pipe; + send_vblank_event(dev, e, seq, flip); +} + /* HACK: moved here to avoid circular dependency between apple_drv and dcp */ void dcp_drm_crtc_vblank(struct apple_crtc *crtc) { @@ -63,6 +133,23 @@ void dcp_drm_crtc_vblank(struct apple_crtc *crtc) spin_unlock_irqrestore(&crtc->base.dev->event_lock, flags); } +void dcp_drm_crtc_page_flip(struct apple_dcp *dcp, ktime_t now) +{ + unsigned long flags; + struct apple_crtc *crtc = dcp->crtc; + + spin_lock_irqsave(&crtc->base.dev->event_lock, flags); + if (crtc->event) { + if (crtc->event->event.base.type == DRM_EVENT_FLIP_COMPLETE) + dcp_crtc_send_page_flip_event(crtc, crtc->event, now, dcp->swap_start); + else + drm_crtc_send_vblank_event(&crtc->base, crtc->event); + crtc->event = NULL; + dcp->swap_start = KTIME_MIN; + } + spin_unlock_irqrestore(&crtc->base.dev->event_lock, flags); +} + void dcp_set_dimensions(struct apple_dcp *dcp) { int i; diff --git a/drivers/gpu/drm/apple/iomfb_template.c b/drivers/gpu/drm/apple/iomfb_template.c index d74f2b502821a1..bc3df16ebac542 100644 --- a/drivers/gpu/drm/apple/iomfb_template.c +++ b/drivers/gpu/drm/apple/iomfb_template.c @@ -120,10 +120,11 @@ static u32 dcpep_cb_zero(struct apple_dcp *dcp) static void dcpep_cb_swap_complete(struct apple_dcp *dcp, struct DCP_FW_NAME(dc_swap_complete_resp) *resp) { + ktime_t now = ktime_get(); trace_iomfb_swap_complete(dcp, resp->swap_id); dcp->last_swap_id = resp->swap_id; - dcp_drm_crtc_vblank(dcp->crtc); + dcp_drm_crtc_page_flip(dcp, now); } /* special */ @@ -1124,6 +1125,7 @@ static void dcp_swapped(struct apple_dcp *dcp, void *data, void *cookie) dcp_drm_crtc_vblank(dcp->crtc); return; } + dcp->swap_start = ktime_get(); while (!list_empty(&dcp->swapped_out_fbs)) { struct dcp_fb_reference *entry; From 0b0524af2aec99f91c58f31f43c1c6dbbb53c1bc Mon Sep 17 00:00:00 2001 From: Asahi Lina Date: Sat, 15 Jun 2024 14:29:48 +0900 Subject: [PATCH 1175/3902] drm/apple: Explicitly stop AFK endpoints on shutdown Signed-off-by: Asahi Lina --- drivers/gpu/drm/apple/afk.c | 13 +++++++++++++ drivers/gpu/drm/apple/afk.h | 1 + drivers/gpu/drm/apple/dcp.c | 31 ++++++++++++++++++++++++++++++- 3 files changed, 44 insertions(+), 1 deletion(-) diff --git a/drivers/gpu/drm/apple/afk.c b/drivers/gpu/drm/apple/afk.c index d3e45a6180af69..83afb51883048f 100644 --- a/drivers/gpu/drm/apple/afk.c +++ b/drivers/gpu/drm/apple/afk.c @@ -86,6 +86,19 @@ struct apple_dcp_afkep *afk_init(struct apple_dcp *dcp, u32 endpoint, return ERR_PTR(ret); } +void afk_shutdown(struct apple_dcp_afkep *afkep) +{ + afk_send(afkep, FIELD_PREP(RBEP_TYPE, RBEP_SHUTDOWN)); + int ret; + + ret = wait_for_completion_timeout(&afkep->stopped, msecs_to_jiffies(1000)); + if (ret <= 0) { + dev_err(afkep->dcp->dev, "Timed out shutting down AFK endpoint %02x", afkep->endpoint); + } + + destroy_workqueue(afkep->wq); +} + int afk_start(struct apple_dcp_afkep *ep) { int ret; diff --git a/drivers/gpu/drm/apple/afk.h b/drivers/gpu/drm/apple/afk.h index 0f91f32e08e301..be3f0b105de581 100644 --- a/drivers/gpu/drm/apple/afk.h +++ b/drivers/gpu/drm/apple/afk.h @@ -187,6 +187,7 @@ struct apple_dcp_afkep { struct apple_dcp_afkep *afk_init(struct apple_dcp *dcp, u32 endpoint, const struct apple_epic_service_ops *ops); int afk_start(struct apple_dcp_afkep *ep); +void afk_shutdown(struct apple_dcp_afkep *ep); int afk_receive_message(struct apple_dcp_afkep *ep, u64 message); int afk_send_epic(struct apple_dcp_afkep *ep, u32 channel, u16 tag, enum epic_type etype, enum epic_category ecat, u8 stype, diff --git a/drivers/gpu/drm/apple/dcp.c b/drivers/gpu/drm/apple/dcp.c index 53fc722bf833c3..068115b35c0757 100644 --- a/drivers/gpu/drm/apple/dcp.c +++ b/drivers/gpu/drm/apple/dcp.c @@ -1022,10 +1022,33 @@ static void dcp_comp_unbind(struct device *dev, struct device *main, void *data) { struct apple_dcp *dcp = dev_get_drvdata(dev); + if (!dcp) + return; + if (dcp->hdmi_hpd_irq) disable_irq(dcp->hdmi_hpd_irq); - if (dcp && dcp->shmem) + if (dcp->avep) { + afk_shutdown(dcp->avep); + dcp->avep = NULL; + } + + if (dcp->dptxep) { + afk_shutdown(dcp->dptxep); + dcp->dptxep = NULL; + } + + if (dcp->ibootep) { + afk_shutdown(dcp->ibootep); + dcp->ibootep = NULL; + } + + if (dcp->systemep) { + afk_shutdown(dcp->systemep); + dcp->systemep = NULL; + } + + if (dcp->shmem) iomfb_shutdown(dcp); if (dcp->piodma) { @@ -1038,6 +1061,12 @@ static void dcp_comp_unbind(struct device *dev, struct device *main, void *data) dcp->piodma = NULL; } + if (dcp->connector_type == DRM_MODE_CONNECTOR_eDP) { + cancel_work_sync(&dcp->bl_register_wq); + cancel_work_sync(&dcp->bl_update_wq); + } + cancel_work_sync(&dcp->vblank_wq); + devm_clk_put(dev, dcp->clk); dcp->clk = NULL; } From 992dd32b8d44e5e943545cc69513c7b87a0beab2 Mon Sep 17 00:00:00 2001 From: Asahi Lina Date: Sat, 15 Jun 2024 19:51:12 +0900 Subject: [PATCH 1176/3902] drm/apple: audio: Create a device link to the DMA device This works even before the DMA device probes. Might help deal with runtime-pm ordering, though it doesn't solve the deferred ordering problem (since we're creating the link while already probing)... Signed-off-by: Asahi Lina --- drivers/gpu/drm/apple/audio.c | 69 +++++++++++++++++------------------ 1 file changed, 33 insertions(+), 36 deletions(-) diff --git a/drivers/gpu/drm/apple/audio.c b/drivers/gpu/drm/apple/audio.c index 923f5421298305..8c6018fa36bf3d 100644 --- a/drivers/gpu/drm/apple/audio.c +++ b/drivers/gpu/drm/apple/audio.c @@ -35,6 +35,8 @@ struct dcp_audio { struct device *dev; struct device *dcp_dev; + struct device *dma_dev; + struct device_link *dma_link; struct dma_chan *chan; struct snd_card *card; struct snd_jack *jack; @@ -588,40 +590,13 @@ void dcpaud_disconnect(struct platform_device *pdev) dcpaud_report_hotplug(dcpaud, false); } -static ssize_t probe_snd_card_store(struct device *dev, - struct device_attribute *attr, - const char *buf, size_t count) -{ - int ret; - bool connected = false; - struct dcp_audio *dcpaud = dev_get_drvdata(dev); - - mutex_lock(&dcpaud->data_lock); - - if (!dcpaud->chan) { - ret = dcpaud_init_snd_card(dcpaud); - if (ret) - goto out_unlock; - - connected = dcpaud->dcp_connected; - if (connected) { - dcpaud_report_hotplug(dcpaud, connected); - goto out; - } - } -out_unlock: - mutex_unlock(&dcpaud->data_lock); -out: - return count; -} - -static const DEVICE_ATTR_WO(probe_snd_card); - static int dcpaud_comp_bind(struct device *dev, struct device *main, void *data) { struct dcp_audio *dcpaud = dev_get_drvdata(dev); struct device_node *endpoint, *dcp_node = NULL; - struct platform_device *dcp_pdev; + struct platform_device *dcp_pdev, *dma_pdev; + struct of_phandle_args dma_spec; + int index; int ret; /* find linked DCP instance */ @@ -636,6 +611,18 @@ static int dcpaud_comp_bind(struct device *dev, struct device *main, void *data) return 0; } + index = of_property_match_string(dev->of_node, "dma-names", "tx"); + if (index < 0) { + dev_err(dev, "No dma-names property\n"); + return 0; + } + + if (of_parse_phandle_with_args(dev->of_node, "dmas", "#dma-cells", index, + &dma_spec) || !dma_spec.np) { + dev_err(dev, "Failed to parse dmas property\n"); + return 0; + } + dcp_pdev = of_find_device_by_node(dcp_node); of_node_put(dcp_node); if (!dcp_pdev) { @@ -644,12 +631,19 @@ static int dcpaud_comp_bind(struct device *dev, struct device *main, void *data) } dcpaud->dcp_dev = &dcp_pdev->dev; - dcpaud_expose_debugfs_blob(dcpaud, "selected_cookie", &dcpaud->selected_cookie, - sizeof(dcpaud->selected_cookie)); - dcpaud_expose_debugfs_blob(dcpaud, "elements", dcpaud->elements, - DCPAUD_ELEMENTS_MAXSIZE); - dcpaud_expose_debugfs_blob(dcpaud, "product_attrs", dcpaud->productattrs, - DCPAUD_PRODUCTATTRS_MAXSIZE); + + dma_pdev = of_find_device_by_node(dma_spec.np); + of_node_put(dma_spec.np); + if (!dma_pdev) { + dev_info(dev, "No DMA device\n"); + return 0; + } + dcpaud->dma_dev = &dma_pdev->dev; + + dcpaud->dma_link = device_link_add(dev, dcpaud->dma_dev, + DL_FLAG_PM_RUNTIME | + DL_FLAG_RPM_ACTIVE | + DL_FLAG_STATELESS); mutex_lock(&dcpaud->data_lock); /* ignore errors to prevent audio issues affecting the display side */ @@ -670,6 +664,9 @@ static void dcpaud_comp_unbind(struct device *dev, struct device *main, /* snd_card_free_when_closed() checks for NULL */ snd_card_free_when_closed(dcpaud->card); + + if (dcpaud->dma_link) + device_link_del(dcpaud->dma_link); } static const struct component_ops dcpaud_comp_ops = { From 279067779beaad04f422fc3b2f7fb86d76401329 Mon Sep 17 00:00:00 2001 From: Asahi Lina Date: Sat, 15 Jun 2024 19:52:16 +0900 Subject: [PATCH 1177/3902] drm/apple: audio: Defer DMA channel acquisition to device open Allow the DMA device driver to probe late, and still create the sound device upfront. Instead try to request the DMA channel on first PCM open. This should be safe as long as we bail early and don't allow the process to continue to configuring buffers (since that requires the DMA to be configured). Signed-off-by: Asahi Lina --- drivers/gpu/drm/apple/audio.c | 105 ++++++++++++++++++---------------- 1 file changed, 57 insertions(+), 48 deletions(-) diff --git a/drivers/gpu/drm/apple/audio.c b/drivers/gpu/drm/apple/audio.c index 8c6018fa36bf3d..eee1109780b061 100644 --- a/drivers/gpu/drm/apple/audio.c +++ b/drivers/gpu/drm/apple/audio.c @@ -212,10 +212,36 @@ static int dcpaud_rule_rate(struct snd_pcm_hw_params *params, return snd_interval_rate_bits(r, hits.rates); } +static int dcpaud_init_dma(struct dcp_audio *dcpaud) +{ + struct dma_chan *chan; + if (dcpaud->chan) + return 0; + + chan = of_dma_request_slave_channel(dcpaud->dev->of_node, "tx"); + /* squelch dma channel request errors, the driver will try again alter */ + if (!chan) { + dev_warn(dcpaud->dev, "audio TX DMA channel request failed\n"); + return -ENXIO; + } else if (chan == ERR_PTR(-EPROBE_DEFER)) { + dev_info(dcpaud->dev, "audio TX DMA channel is not ready yet\n"); + return -ENXIO; + } else if (IS_ERR(chan)) { + dev_warn(dcpaud->dev, "audio TX DMA channel request failed: %ld\n", PTR_ERR(chan)); + return PTR_ERR(chan); + } + dcpaud->chan = chan; + + snd_pcm_set_managed_buffer(dcpaud->substream, SNDRV_DMA_TYPE_DEV_IRAM, + dcpaud->chan->device->dev, 1024 * 1024, + SIZE_MAX); + + return 0; +} + static int dcp_pcm_open(struct snd_pcm_substream *substream) { struct dcp_audio *dcpaud = substream->pcm->private_data; - struct dma_chan *chan = dcpaud->chan; struct snd_dmaengine_dai_dma_data dma_data = { .flags = SND_DMAENGINE_PCM_DAI_FLAG_PACK, }; @@ -223,6 +249,10 @@ static int dcp_pcm_open(struct snd_pcm_substream *substream) int ret; mutex_lock(&dcpaud->data_lock); + ret = dcpaud_init_dma(dcpaud); + if (ret < 0) + return ret; + if (!dcpaud->connected) { mutex_unlock(&dcpaud->data_lock); return -ENXIO; @@ -254,12 +284,12 @@ static int dcp_pcm_open(struct snd_pcm_substream *substream) hw.buffer_bytes_max = SIZE_MAX; hw.fifo_size = 16; ret = snd_dmaengine_pcm_refine_runtime_hwparams(substream, &dma_data, - &hw, chan); + &hw, dcpaud->chan); if (ret) return ret; substream->runtime->hw = hw; - return snd_dmaengine_pcm_open(substream, chan); + return snd_dmaengine_pcm_open(substream, dcpaud->chan); } static int dcp_pcm_close(struct snd_pcm_substream *substream) @@ -444,10 +474,6 @@ static int dcpaud_create_pcm(struct dcp_audio *dcpaud) snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &dcp_playback_ops); dcpaud->substream = pcm->streams[SNDRV_PCM_STREAM_PLAYBACK].substream; - snd_pcm_set_managed_buffer(dcpaud->substream, SNDRV_DMA_TYPE_DEV_IRAM, - dcpaud->chan->device->dev, 1024 * 1024, - SIZE_MAX); - pcm->nonatomic = true; pcm->private_data = dcpaud; strscpy(pcm->name, card->shortname, sizeof(pcm->name)); @@ -496,26 +522,29 @@ static void dcpaud_set_card_names(struct dcp_audio *dcpaud) strscpy(card->shortname, "Apple DisplayPort", sizeof(card->shortname)); } +#ifdef CONFIG_SND_DEBUG +static void dcpaud_expose_debugfs_blob(struct dcp_audio *dcpaud, const char *name, void *base, size_t size) +{ + struct debugfs_blob_wrapper *wrapper; + wrapper = devm_kzalloc(dcpaud->dev, sizeof(*wrapper), GFP_KERNEL); + if (!wrapper) + return; + wrapper->data = base; + wrapper->size = size; + debugfs_create_blob(name, 0600, dcpaud->card->debugfs_root, wrapper); +} +#else +static void dcpaud_expose_debugfs_blob(struct dcp_audio *dcpaud, const char *name, void *base, size_t size) {} +#endif + extern bool hdmi_audio; static int dcpaud_init_snd_card(struct dcp_audio *dcpaud) { int ret; - struct dma_chan *chan; - if (!hdmi_audio) return -ENODEV; - chan = of_dma_request_slave_channel(dcpaud->dev->of_node, "tx"); - /* squelch dma channel request errors, the driver will try again alter */ - if (!chan) { - dev_warn(dcpaud->dev, "audio TX DMA channel request failed\n"); - return 0; - } else if (IS_ERR(chan)) { - dev_warn(dcpaud->dev, "audio TX DMA channel request failed: %pE\n", chan); - return 0; - } - dcpaud->chan = chan; ret = snd_card_new(dcpaud->dev, SNDRV_DEFAULT_IDX1, SNDRV_DEFAULT_STR1, THIS_MODULE, 0, &dcpaud->card); @@ -548,35 +577,12 @@ static int dcpaud_init_snd_card(struct dcp_audio *dcpaud) return ret; } -#ifdef CONFIG_SND_DEBUG -static void dcpaud_expose_debugfs_blob(struct dcp_audio *dcpaud, const char *name, void *base, size_t size) -{ - struct debugfs_blob_wrapper *wrapper; - wrapper = devm_kzalloc(dcpaud->dev, sizeof(*wrapper), GFP_KERNEL); - if (!wrapper) - return; - wrapper->data = base; - wrapper->size = size; - debugfs_create_blob(name, 0600, dcpaud->card->debugfs_root, wrapper); -} -#else -static void dcpaud_expose_debugfs_blob(struct dcp_audio *dcpaud, const char *name, void *base, size_t size) {} -#endif - void dcpaud_connect(struct platform_device *pdev, bool connected) { struct dcp_audio *dcpaud = platform_get_drvdata(pdev); mutex_lock(&dcpaud->data_lock); - if (!dcpaud->chan) { - int ret = dcpaud_init_snd_card(dcpaud); - if (ret) { - dcpaud->dcp_connected = connected; - mutex_unlock(&dcpaud->data_lock); - return; - } - } dcpaud_report_hotplug(dcpaud, connected); } @@ -645,14 +651,17 @@ static int dcpaud_comp_bind(struct device *dev, struct device *main, void *data) DL_FLAG_RPM_ACTIVE | DL_FLAG_STATELESS); - mutex_lock(&dcpaud->data_lock); /* ignore errors to prevent audio issues affecting the display side */ - dcpaud_init_snd_card(dcpaud); - mutex_unlock(&dcpaud->data_lock); + ret = dcpaud_init_snd_card(dcpaud); - ret = device_create_file(dev, &dev_attr_probe_snd_card); - if (ret) - dev_info(dev, "creating force probe sysfs file failed: %d\n", ret); + if (!ret) { + dcpaud_expose_debugfs_blob(dcpaud, "selected_cookie", &dcpaud->selected_cookie, + sizeof(dcpaud->selected_cookie)); + dcpaud_expose_debugfs_blob(dcpaud, "elements", dcpaud->elements, + DCPAUD_ELEMENTS_MAXSIZE); + dcpaud_expose_debugfs_blob(dcpaud, "product_attrs", dcpaud->productattrs, + DCPAUD_PRODUCTATTRS_MAXSIZE); + } return 0; } From 518a2c0cbb47eaed883fe4555722379a02be383f Mon Sep 17 00:00:00 2001 From: Asahi Lina Date: Sat, 15 Jun 2024 21:12:46 +0900 Subject: [PATCH 1178/3902] drm/apple: audio: Fix hotplug notifications Signed-off-by: Asahi Lina --- drivers/gpu/drm/apple/audio.c | 4 ++-- drivers/gpu/drm/apple/av.c | 15 +++++++++++++++ 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/drivers/gpu/drm/apple/audio.c b/drivers/gpu/drm/apple/audio.c index eee1109780b061..b78e3895987103 100644 --- a/drivers/gpu/drm/apple/audio.c +++ b/drivers/gpu/drm/apple/audio.c @@ -500,7 +500,8 @@ static void dcpaud_report_hotplug(struct dcp_audio *dcpaud, bool connected) if (!connected) { snd_pcm_stream_lock(substream); - snd_pcm_stop(substream, SNDRV_PCM_STATE_DISCONNECTED); + if (substream->runtime) + snd_pcm_stop(substream, SNDRV_PCM_STATE_DISCONNECTED); snd_pcm_stream_unlock(substream); } } @@ -592,7 +593,6 @@ void dcpaud_disconnect(struct platform_device *pdev) mutex_lock(&dcpaud->data_lock); - dcpaud->dcp_connected = false; dcpaud_report_hotplug(dcpaud, false); } diff --git a/drivers/gpu/drm/apple/av.c b/drivers/gpu/drm/apple/av.c index 8a2c1126f5adea..586f39cc11ca11 100644 --- a/drivers/gpu/drm/apple/av.c +++ b/drivers/gpu/drm/apple/av.c @@ -69,6 +69,20 @@ static void av_interface_init(struct apple_epic_service *service, const char *na { } +static void av_interface_teardown(struct apple_epic_service *service) +{ + struct apple_dcp *dcp = service->ep->dcp; + struct audiosrv_data *asrv = dcp->audiosrv; + + mutex_lock(&asrv->plug_lock); + + asrv->plugged = false; + if (asrv->audio_dev) + dcpaud_disconnect(asrv->audio_dev); + + mutex_unlock(&asrv->plug_lock); +} + static void av_audiosrv_init(struct apple_epic_service *service, const char *name, const char *class, s64 unit) { @@ -258,6 +272,7 @@ static const struct apple_epic_service_ops avep_ops[] = { { .name = "DCPAVSimpleVideoInterface", .init = av_interface_init, + .teardown = av_interface_teardown, }, { .name = "DCPAVAudioInterface", From b8fed2ebcf2f367caf5e2fee74d32cd52963783f Mon Sep 17 00:00:00 2001 From: Sven Peter Date: Sat, 5 Nov 2022 13:15:34 +0100 Subject: [PATCH 1179/3902] drm: apple: Add oob hotplug event Signed-off-by: Sven Peter --- drivers/gpu/drm/apple/apple_drv.c | 17 +++++++++++++++++ drivers/gpu/drm/apple/dcp.c | 22 ++++++++++++++++++++++ drivers/gpu/drm/apple/dcp.h | 3 +++ 3 files changed, 42 insertions(+) diff --git a/drivers/gpu/drm/apple/apple_drv.c b/drivers/gpu/drm/apple/apple_drv.c index 44c9ea73f4a02f..bbf419cb29ba92 100644 --- a/drivers/gpu/drm/apple/apple_drv.c +++ b/drivers/gpu/drm/apple/apple_drv.c @@ -190,6 +190,22 @@ apple_connector_detect(struct drm_connector *connector, bool force) connector_status_disconnected; } +static void apple_connector_oob_hotplug(struct drm_connector *connector, + enum drm_connector_status status) +{ + struct apple_connector *apple_connector = to_apple_connector(connector); + + printk("#### oob_hotplug status:0x%x ####\n", (u32)status); + + if (status == connector_status_connected) + dcp_dptx_connect_oob(apple_connector->dcp, 0); + else if (status == connector_status_disconnected) + dcp_dptx_disconnect_oob(apple_connector->dcp, 0); + else + dev_err(&apple_connector->dcp->dev, "unexpected connector status" + ":0x%x in oob_hotplug event\n", (u32)status); +} + static void apple_crtc_atomic_enable(struct drm_crtc *crtc, struct drm_atomic_state *state) { @@ -278,6 +294,7 @@ static const struct drm_connector_funcs apple_connector_funcs = { .atomic_destroy_state = drm_atomic_helper_connector_destroy_state, .detect = apple_connector_detect, .debugfs_init = apple_connector_debugfs_init, + .oob_hotplug_event = apple_connector_oob_hotplug, }; static const struct drm_connector_helper_funcs apple_connector_helper_funcs = { diff --git a/drivers/gpu/drm/apple/dcp.c b/drivers/gpu/drm/apple/dcp.c index 068115b35c0757..0c16f67ba3612b 100644 --- a/drivers/gpu/drm/apple/dcp.c +++ b/drivers/gpu/drm/apple/dcp.c @@ -397,6 +397,17 @@ static int dcp_dptx_connect(struct apple_dcp *dcp, u32 port) return ret; } +int dcp_dptx_connect_oob(struct platform_device *pdev, u32 port) +{ + struct apple_dcp *dcp = platform_get_drvdata(pdev); + int err = dcp_dptx_connect(dcp, port); + if (err < 0) + return err; + dptxport_set_hpd(dcp->dptxport[port].service, true); + return 0; +} +EXPORT_SYMBOL_GPL(dcp_dptx_connect_oob); + static int dcp_dptx_disconnect(struct apple_dcp *dcp, u32 port) { dev_info(dcp->dev, "%s(port=%d)\n", __func__, port); @@ -411,6 +422,17 @@ static int dcp_dptx_disconnect(struct apple_dcp *dcp, u32 port) return 0; } +int dcp_dptx_disconnect_oob(struct platform_device *pdev, u32 port) +{ + struct apple_dcp *dcp = platform_get_drvdata(pdev); + + if (dcp->dptxport[port].enabled) + dptxport_set_hpd(dcp->dptxport[port].service, false); + + return dcp_dptx_disconnect(dcp, port); +} +EXPORT_SYMBOL_GPL(dcp_dptx_disconnect_oob); + static irqreturn_t dcp_dp2hdmi_hpd(int irq, void *data) { struct apple_dcp *dcp = data; diff --git a/drivers/gpu/drm/apple/dcp.h b/drivers/gpu/drm/apple/dcp.h index c284a089b6e0bf..de322ed02c72d5 100644 --- a/drivers/gpu/drm/apple/dcp.h +++ b/drivers/gpu/drm/apple/dcp.h @@ -52,6 +52,9 @@ bool dcp_crtc_mode_fixup(struct drm_crtc *crtc, void dcp_set_dimensions(struct apple_dcp *dcp); void dcp_send_message(struct apple_dcp *dcp, u8 endpoint, u64 message); +int dcp_dptx_connect_oob(struct platform_device *pdev, u32 port); +int dcp_dptx_disconnect_oob(struct platform_device *pdev, u32 port); + int iomfb_start_rtkit(struct apple_dcp *dcp); void iomfb_shutdown(struct apple_dcp *dcp); /* rtkit message handler for IOMFB messages */ From c499a0f57e9ed225d1144be5f994f13d526df5e6 Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Sun, 9 Jun 2024 21:49:43 +0200 Subject: [PATCH 1180/3902] drm: apple: dptx: Fix get_drive_settings retcode This appears to be lane count as "2" is observed for USB-C DP alt mode in shared DP/USB3 mode. Signed-off-by: Janne Grunau --- drivers/gpu/drm/apple/dptxep.c | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/drivers/gpu/drm/apple/dptxep.c b/drivers/gpu/drm/apple/dptxep.c index 56b86966e807a7..fd54f69b19919b 100644 --- a/drivers/gpu/drm/apple/dptxep.c +++ b/drivers/gpu/drm/apple/dptxep.c @@ -189,12 +189,16 @@ dptxport_call_get_drive_settings(struct apple_epic_service *service, /* Clear the rest of the buffer */ memset(reply_ + sizeof(*reply), 0, reply_size - sizeof(*reply)); - if (reply->retcode != 4) + /* + * retcode appears to be lane count, seeing 2 for USB-C dp alt mode + * with lanes splitted for DP/USB3. + */ + if (reply->retcode != dptx->lane_count) dev_err(service->ep->dcp->dev, "get_drive_settings: unexpected retcode %d\n", reply->retcode); - reply->retcode = 4; /* Should already be 4? */ + reply->retcode = dptx->lane_count; reply->unk5 = dptx->drive_settings[0]; reply->unk6 = 0; reply->unk7 = dptx->drive_settings[1]; From 2e7fac088a704c611e07bd37cb7c514ca96a3c9c Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Sun, 9 Jun 2024 22:01:59 +0200 Subject: [PATCH 1181/3902] drm: apple: dptxport: get_max_lane_count: Retrieve lane count from phy This unfortunately doesn't work relieably with typec-altmode-displayport since the oob hotplug notification arrives before atc-phy is configured to the appropiate DP mode. Signed-off-by: Janne Grunau --- drivers/gpu/drm/apple/dptxep.c | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/drivers/gpu/drm/apple/dptxep.c b/drivers/gpu/drm/apple/dptxep.c index fd54f69b19919b..b8cb7f00133760 100644 --- a/drivers/gpu/drm/apple/dptxep.c +++ b/drivers/gpu/drm/apple/dptxep.c @@ -249,6 +249,9 @@ static int dptxport_call_get_max_lane_count(struct apple_epic_service *service, void *reply_, size_t reply_size) { struct dptxport_apcall_lane_count *reply = reply_; + struct dptx_port *dptx = service->cookie; + union phy_configure_opts phy_ops; + int ret; if (reply_size < sizeof(*reply)) return -EINVAL; @@ -256,6 +259,17 @@ static int dptxport_call_get_max_lane_count(struct apple_epic_service *service, reply->retcode = cpu_to_le32(0); reply->lane_count = cpu_to_le64(4); + ret = phy_validate(dptx->atcphy, PHY_MODE_DP, 0, &phy_ops); + if (ret < 0 || phy_ops.dp.lanes < 2) { + // phy_validate might return 0 lines if atc-phy is not yet + // switched to DP alt mode + dev_dbg(service->ep->dcp->dev, "get_max_lane_count: " + "phy_validate ret:%d lanes:%d\n", ret, phy_ops.dp.lanes); + } else { + reply->retcode = cpu_to_le32(0); + reply->lane_count = cpu_to_le64(phy_ops.dp.lanes); + } + return 0; } From 815fc375b0db4cd85654c7e64ed8effde3a27e80 Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Sun, 14 Jul 2024 00:01:08 +0200 Subject: [PATCH 1182/3902] drm: apple: iomfb: Align buffer size on unmap/free as well Fixes failure to unmap buffers in dcpep_cb_unmap_piodma() due to the unaligned size. Further along this causes kernel log splat when DCP tries to map the buffers again since thye IOVA is still in use. This causes no apparent issue although map_piodma callback signals an errror and returns 0 (unmapped as DVA). It's not clear why this presents only randomly. Possibly some build or uninitialized memory triggers this unmap/free and immediate allocate/map cycle in the DCP firmware. I never notices this with a clang-built kernel on j314c. It showed with gcc build with the Fedora config at least on 6.8.8 based kernels. This did not reproduce on j375d. Signed-off-by: Janne Grunau --- drivers/gpu/drm/apple/iomfb_template.c | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/drivers/gpu/drm/apple/iomfb_template.c b/drivers/gpu/drm/apple/iomfb_template.c index bc3df16ebac542..5df04979f573eb 100644 --- a/drivers/gpu/drm/apple/iomfb_template.c +++ b/drivers/gpu/drm/apple/iomfb_template.c @@ -322,7 +322,10 @@ static void dcpep_cb_unmap_piodma(struct apple_dcp *dcp, } /* use the piodma iommu domain to unmap from the right IOMMU */ - iommu_unmap(dcp->iommu_dom, memdesc->dva, memdesc->size); + /* HACK: expect size to be 16K aligned since the iommu API only maps + * full pages + */ + iommu_unmap(dcp->iommu_dom, memdesc->dva, ALIGN(memdesc->size, SZ_16K)); } /* @@ -370,6 +373,7 @@ dcpep_cb_allocate_buffer(struct apple_dcp *dcp, static u8 dcpep_cb_release_mem_desc(struct apple_dcp *dcp, u32 *mem_desc_id) { struct dcp_mem_descriptor *memdesc; + size_t size; u32 id = *mem_desc_id; if (id >= DCP_MAX_MAPPINGS) { @@ -385,10 +389,9 @@ static u8 dcpep_cb_release_mem_desc(struct apple_dcp *dcp, u32 *mem_desc_id) } memdesc = &dcp->memdesc[id]; + size = ALIGN(memdesc->size, SZ_16K); if (memdesc->buf) { - dma_free_coherent(dcp->dev, memdesc->size, memdesc->buf, - memdesc->dva); - + dma_free_coherent(dcp->dev, size, memdesc->buf, memdesc->dva); memdesc->buf = NULL; memset(&memdesc->map, 0, sizeof(memdesc->map)); } else { From 340f1de618bfd13ce65cece37e10a8bdb81e1492 Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Wed, 21 Aug 2024 21:51:11 +0200 Subject: [PATCH 1183/3902] Revert "drm: apple: HACK: Do not delete piodma platform device" This reverts commit fa86f31f64a691eb65a217c66468b3e9e58cc9e1. Signed-off-by: Janne Grunau --- drivers/gpu/drm/apple/dcp.c | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/drivers/gpu/drm/apple/dcp.c b/drivers/gpu/drm/apple/dcp.c index 0c16f67ba3612b..8106431207e02f 100644 --- a/drivers/gpu/drm/apple/dcp.c +++ b/drivers/gpu/drm/apple/dcp.c @@ -1076,10 +1076,7 @@ static void dcp_comp_unbind(struct device *dev, struct device *main, void *data) if (dcp->piodma) { iommu_detach_device(dcp->iommu_dom, &dcp->piodma->dev); iommu_domain_free(dcp->iommu_dom); - /* TODO: the piodma platform device has to be destroyed but - * doing so leads to all kind of breakage. - */ - // of_platform_device_destroy(&dcp->piodma->dev, NULL); + of_platform_device_destroy(&dcp->piodma->dev, NULL); dcp->piodma = NULL; } From 49188b053f7ca3da2d7d3a529bf36aec71688628 Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Tue, 20 Aug 2024 23:04:29 +0200 Subject: [PATCH 1184/3902] drm: apple: afk: Optionally match against EPICName The dpavserv endpoint uses various EPICProviderClass depending on the connected display. Observed values: - "AppleDCPAgileCDIDPDisplay" (j134c, dcp, panel) - "AppleDCPMCDP29XX" (j274, dcp, hdmi) - "AppleDCPPS190" (j474s, dcpext0, hdmi) - "DCPDPService" (j474s, dcpext1, typec) So match against against EPICName which is consistent in all cases. This also allows the distinction between 'dcpav-service-epic' and 'dcpdp-service-epic'. Not sure what the second EPIC service is used for. Signed-off-by: Janne Grunau --- drivers/gpu/drm/apple/afk.c | 6 +++++- drivers/gpu/drm/apple/afk.h | 2 ++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/drivers/gpu/drm/apple/afk.c b/drivers/gpu/drm/apple/afk.c index 83afb51883048f..bd1f16e8937c74 100644 --- a/drivers/gpu/drm/apple/afk.c +++ b/drivers/gpu/drm/apple/afk.c @@ -293,7 +293,11 @@ static void afk_recv_handle_init(struct apple_dcp_afkep *ep, u32 channel, service_name = name; } - ops = afk_match_service(ep, service_name); + if (ep->match_epic_name) + ops = afk_match_service(ep, epic_name); + else + ops = afk_match_service(ep, service_name); + if (!ops) { dev_err(ep->dcp->dev, "AFK[ep:%02x]: unable to match service %s on channel %d\n", diff --git a/drivers/gpu/drm/apple/afk.h b/drivers/gpu/drm/apple/afk.h index be3f0b105de581..5a286799835248 100644 --- a/drivers/gpu/drm/apple/afk.h +++ b/drivers/gpu/drm/apple/afk.h @@ -182,6 +182,8 @@ struct apple_dcp_afkep { u32 num_channels; struct dentry *debugfs_entry; + + bool match_epic_name; }; struct apple_dcp_afkep *afk_init(struct apple_dcp *dcp, u32 endpoint, From d441b937d0d76645db9c0298a651d2c046a13e68 Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Sun, 3 Dec 2023 23:24:11 +0100 Subject: [PATCH 1185/3902] drm: apple: Add dcpav-service-ep Known uses EDID retrieval and raw I2C access. Signed-off-by: Janne Grunau --- drivers/gpu/drm/apple/Makefile | 2 + drivers/gpu/drm/apple/connector.c | 3 +- drivers/gpu/drm/apple/connector.h | 2 + drivers/gpu/drm/apple/dcp-internal.h | 6 + drivers/gpu/drm/apple/dcp.c | 19 ++ drivers/gpu/drm/apple/dcp.h | 1 + drivers/gpu/drm/apple/epic/dpavservep.c | 230 ++++++++++++++++++++++++ drivers/gpu/drm/apple/epic/dpavservep.h | 22 +++ drivers/gpu/drm/apple/trace.h | 12 ++ 9 files changed, 296 insertions(+), 1 deletion(-) create mode 100644 drivers/gpu/drm/apple/epic/dpavservep.c create mode 100644 drivers/gpu/drm/apple/epic/dpavservep.h diff --git a/drivers/gpu/drm/apple/Makefile b/drivers/gpu/drm/apple/Makefile index 519303016b292a..045183c63bc129 100644 --- a/drivers/gpu/drm/apple/Makefile +++ b/drivers/gpu/drm/apple/Makefile @@ -11,6 +11,8 @@ apple_dcp-y += connector.o apple_dcp-y += ibootep.o apple_dcp-y += iomfb_v12_3.o apple_dcp-y += iomfb_v13_3.o +apple_dcp-y += epic/dpavservep.o + apple_dcp-$(CONFIG_TRACING) += trace.o obj-$(CONFIG_DRM_APPLE) += appledrm.o diff --git a/drivers/gpu/drm/apple/connector.c b/drivers/gpu/drm/apple/connector.c index 46de8e8756f1ed..9e786670893387 100644 --- a/drivers/gpu/drm/apple/connector.c +++ b/drivers/gpu/drm/apple/connector.c @@ -3,6 +3,8 @@ * Copyright (C) The Asahi Linux Contributors */ +#include "connector.h" + #include "linux/err.h" #include #include @@ -12,7 +14,6 @@ #include -#include "connector.h" #include "dcp-internal.h" enum dcp_chunk_type { diff --git a/drivers/gpu/drm/apple/connector.h b/drivers/gpu/drm/apple/connector.h index 79a1eef8c32da8..616ad8fceefae4 100644 --- a/drivers/gpu/drm/apple/connector.h +++ b/drivers/gpu/drm/apple/connector.h @@ -9,6 +9,8 @@ #include #include "drm/drm_connector.h" +struct apple_connector; + #include "dcp-internal.h" void dcp_hotplug(struct work_struct *work); diff --git a/drivers/gpu/drm/apple/dcp-internal.h b/drivers/gpu/drm/apple/dcp-internal.h index 1a465138104d57..d762c6bffbdee4 100644 --- a/drivers/gpu/drm/apple/dcp-internal.h +++ b/drivers/gpu/drm/apple/dcp-internal.h @@ -17,12 +17,15 @@ #include "iomfb.h" #include "iomfb_v12_3.h" #include "iomfb_v13_3.h" +#include "epic/dpavservep.h" #define DCP_MAX_PLANES 2 struct apple_dcp; struct apple_dcp_afkep; +struct dcpav_service_epic; + enum dcp_firmware_version { DCP_FIRMWARE_UNKNOWN, DCP_FIRMWARE_V_12_3, @@ -34,6 +37,7 @@ enum { TEST_ENDPOINT = 0x21, DCP_EXPERT_ENDPOINT = 0x22, DISP0_ENDPOINT = 0x23, + DPAVSERV_ENDPOINT = 0x28, AV_ENDPOINT = 0x29, DPTX_ENDPOINT = 0x2a, HDCP_ENDPOINT = 0x2b, @@ -228,6 +232,8 @@ struct apple_dcp { struct completion systemep_done; struct apple_dcp_afkep *ibootep; + struct apple_dcp_afkep *dcpavservep; + struct dcpavserv dcpavserv; struct apple_dcp_afkep *avep; struct audiosrv_data *audiosrv; diff --git a/drivers/gpu/drm/apple/dcp.c b/drivers/gpu/drm/apple/dcp.c index 8106431207e02f..115d6c74b1c258 100644 --- a/drivers/gpu/drm/apple/dcp.c +++ b/drivers/gpu/drm/apple/dcp.c @@ -50,6 +50,10 @@ bool hdmi_audio; module_param(hdmi_audio, bool, 0644); MODULE_PARM_DESC(hdmi_audio, "Enable unstable HDMI audio support"); +static bool unstable_edid; +module_param(unstable_edid, bool, 0644); +MODULE_PARM_DESC(unstable_edid, "Enable unstable EDID retrival support"); + /* copied and simplified from drm_vblank.c */ static void send_vblank_event(struct drm_device *dev, struct drm_pending_vblank_event *e, @@ -219,6 +223,9 @@ static void dcp_recv_msg(void *cookie, u8 endpoint, u64 message) case DISP0_ENDPOINT: afk_receive_message(dcp->ibootep, message); return; + case DPAVSERV_ENDPOINT: + afk_receive_message(dcp->dcpavservep, message); + return; case DPTX_ENDPOINT: afk_receive_message(dcp->dptxep, message); return; @@ -480,6 +487,13 @@ int dcp_start(struct platform_device *pdev) if (ret) dev_warn(dcp->dev, "Failed to start system endpoint: %d\n", ret); + if (unstable_edid && !dcp_has_panel(dcp)) { + ret = dpavservep_init(dcp); + if (ret) + dev_warn(dcp->dev, "Failed to start DPAVSERV endpoint: %d", + ret); + } + if (dcp->phy && dcp->fw_compat >= DCP_FIRMWARE_V_13_5) { ret = ibootep_init(dcp); if (ret) @@ -1070,6 +1084,11 @@ static void dcp_comp_unbind(struct device *dev, struct device *main, void *data) dcp->systemep = NULL; } + if (dcp->dcpavservep) { + afk_shutdown(dcp->dcpavservep); + dcp->dcpavservep = NULL; + } + if (dcp->shmem) iomfb_shutdown(dcp); diff --git a/drivers/gpu/drm/apple/dcp.h b/drivers/gpu/drm/apple/dcp.h index de322ed02c72d5..0c33fac0ee28f0 100644 --- a/drivers/gpu/drm/apple/dcp.h +++ b/drivers/gpu/drm/apple/dcp.h @@ -63,6 +63,7 @@ void iomfb_recv_msg(struct apple_dcp *dcp, u64 message); int systemep_init(struct apple_dcp *dcp); int dptxep_init(struct apple_dcp *dcp); int ibootep_init(struct apple_dcp *dcp); +int dpavservep_init(struct apple_dcp *dcp); int avep_init(struct apple_dcp *dcp); diff --git a/drivers/gpu/drm/apple/epic/dpavservep.c b/drivers/gpu/drm/apple/epic/dpavservep.c new file mode 100644 index 00000000000000..aa2cbc729a37d4 --- /dev/null +++ b/drivers/gpu/drm/apple/epic/dpavservep.c @@ -0,0 +1,230 @@ +// SPDX-License-Identifier: GPL-2.0-only OR MIT +/* Copyright The Asahi Linux Contributors */ + +#include "dpavservep.h" + +#include + +#include +#include +#include + +#include "../afk.h" +#include "../dcp.h" +#include "../dcp-internal.h" +#include "../trace.h" + +static void dcpavserv_init(struct apple_epic_service *service, const char *name, + const char *class, s64 unit) +{ + struct apple_dcp *dcp = service->ep->dcp; + trace_dcpavserv_init(dcp, unit); + + if (unit == 0 && name && !strcmp(name, "dcpav-service-epic")) { + if (dcp->dcpavserv.enabled) { + dev_err(dcp->dev, + "DCPAVSERV: unit %lld already exists\n", unit); + return; + } + dcp->dcpavserv.service = service; + dcp->dcpavserv.enabled = true; + service->cookie = &dcp->dcpavserv; + complete(&dcp->dcpavserv.enable_completion); + } +} + +static void dcpavserv_teardown(struct apple_epic_service *service) +{ + struct apple_dcp *dcp = service->ep->dcp; + if (dcp->dcpavserv.enabled) { + dcp->dcpavserv.enabled = false; + dcp->dcpavserv.service = NULL; + service->cookie = NULL; + reinit_completion(&dcp->dcpavserv.enable_completion); + } +} + +static void dcpdpserv_init(struct apple_epic_service *service, const char *name, + const char *class, s64 unit) +{ +} + +static void dcpdpserv_teardown(struct apple_epic_service *service) +{ +} + +struct dcpavserv_status_report { + u32 unk00[4]; + u8 flag0; + u8 flag1; + u8 flag2; + u8 flag3; + u32 unk14[3]; + u32 status; + u32 unk24[3]; +} __packed; + +struct dpavserv_copy_edid_cmd { + __le64 max_size; + u8 _pad1[24]; + __le64 used_size; + u8 _pad2[8]; +} __packed; + +#define EDID_LEADING_DATA_SIZE 8 +#define EDID_BLOCK_SIZE 128 +#define EDID_EXT_BLOCK_COUNT_OFFSET 0x7E +#define EDID_MAX_SIZE SZ_32K +#define EDID_BUF_SIZE (EDID_LEADING_DATA_SIZE + EDID_MAX_SIZE) + +struct dpavserv_copy_edid_resp { + __le64 max_size; + u8 _pad1[24]; + __le64 used_size; + u8 _pad2[8]; + u8 data[]; +} __packed; + +static int parse_report(struct apple_epic_service *service, enum epic_subtype type, + const void *data, size_t data_size) +{ +#if defined(DEBUG) + struct apple_dcp *dcp = service->ep->dcp; + const struct epic_service_call *call; + const void *payload; + size_t payload_size; + + dev_dbg(dcp->dev, "dcpavserv[ch:%u]: report type:%02x len:%zu\n", + service->channel, type, data_size); + + if (type != EPIC_SUBTYPE_STD_SERVICE) + return 0; + + if (data_size < sizeof(*call)) + return 0; + + call = data; + + if (le32_to_cpu(call->magic) != EPIC_SERVICE_CALL_MAGIC) { + dev_warn(dcp->dev, "dcpavserv[ch:%u]: report magic 0x%08x != 0x%08x\n", + service->channel, le32_to_cpu(call->magic), EPIC_SERVICE_CALL_MAGIC); + return 0; + } + + payload_size = data_size - sizeof(*call); + if (payload_size < le32_to_cpu(call->data_len)) { + dev_warn(dcp->dev, "dcpavserv[ch:%u]: report payload size %zu call len %u\n", + service->channel, payload_size, le32_to_cpu(call->data_len)); + return 0; + } + payload_size = le32_to_cpu(call->data_len); + payload = data + sizeof(*call); + + if (le16_to_cpu(call->group) == 2 && le16_to_cpu(call->command) == 0) { + if (payload_size == sizeof(struct dcpavserv_status_report)) { + const struct dcpavserv_status_report *stat = payload; + dev_info(dcp->dev, "dcpavserv[ch:%u]: flags: 0x%02x,0x%02x,0x%02x,0x%02x status:%u\n", + service->channel, stat->flag0, stat->flag1, + stat->flag2, stat->flag3, stat->status); + } else { + dev_dbg(dcp->dev, "dcpavserv[ch:%u]: report payload size %zu\n", service->channel, payload_size); + } + } else { + print_hex_dump(KERN_DEBUG, "dcpavserv report: ", DUMP_PREFIX_NONE, + 16, 1, payload, payload_size, true); + } +#endif + + return 0; +} + +static int dcpavserv_report(struct apple_epic_service *service, + enum epic_subtype type, const void *data, + size_t data_size) +{ + return parse_report(service, type, data, data_size); +} + +static int dcpdpserv_report(struct apple_epic_service *service, + enum epic_subtype type, const void *data, + size_t data_size) +{ + return parse_report(service, type, data, data_size); +} + +const struct drm_edid *dcpavserv_copy_edid(struct apple_epic_service *service) +{ + struct dpavserv_copy_edid_cmd cmd; + struct dpavserv_copy_edid_resp *resp __free(kfree) = NULL; + int num_blocks; + u64 data_size; + int ret; + + memset(&cmd, 0, sizeof(cmd)); + cmd.max_size = cpu_to_le64(EDID_BUF_SIZE); + resp = kzalloc(sizeof(*resp) + EDID_BUF_SIZE, GFP_KERNEL); + if (!resp) + return ERR_PTR(-ENOMEM); + + ret = afk_service_call(service, 1, 7, &cmd, sizeof(cmd), EDID_BUF_SIZE, resp, + sizeof(resp) + EDID_BUF_SIZE, 0); + if (ret < 0) + return ERR_PTR(ret); + + if (le64_to_cpu(resp->max_size) != EDID_BUF_SIZE) + return ERR_PTR(-EIO); + + // print_hex_dump(KERN_DEBUG, "dpavserv EDID cmd: ", DUMP_PREFIX_NONE, + // 16, 1, resp, 192, true); + + data_size = le64_to_cpu(resp->used_size); + if (data_size < EDID_LEADING_DATA_SIZE + EDID_BLOCK_SIZE) + return ERR_PTR(-EIO); + + num_blocks = resp->data[EDID_LEADING_DATA_SIZE + EDID_EXT_BLOCK_COUNT_OFFSET]; + if ((1 + num_blocks) * EDID_BLOCK_SIZE != data_size - EDID_LEADING_DATA_SIZE) + return ERR_PTR(-EIO); + + return drm_edid_alloc(resp->data + EDID_LEADING_DATA_SIZE, + data_size - EDID_LEADING_DATA_SIZE); +} + +static const struct apple_epic_service_ops dpavservep_ops[] = { + { + .name = "dcpav-service-epic", + .init = dcpavserv_init, + .teardown = dcpavserv_teardown, + .report = dcpavserv_report, + }, + { + .name = "dcpdp-service-epic", + .init = dcpdpserv_init, + .teardown = dcpdpserv_teardown, + .report = dcpdpserv_report, + }, + {}, +}; + +int dpavservep_init(struct apple_dcp *dcp) +{ + int ret; + + init_completion(&dcp->dcpavserv.enable_completion); + + dcp->dcpavservep = afk_init(dcp, DPAVSERV_ENDPOINT, dpavservep_ops); + if (IS_ERR(dcp->dcpavservep)) + return PTR_ERR(dcp->dcpavservep); + + dcp->dcpavservep->match_epic_name = true; + + ret = afk_start(dcp->dcpavservep); + if (ret) + return ret; + + ret = wait_for_completion_timeout(&dcp->dcpavserv.enable_completion, + msecs_to_jiffies(1000)); + if (ret >= 0) + return 0; + + return ret; +} diff --git a/drivers/gpu/drm/apple/epic/dpavservep.h b/drivers/gpu/drm/apple/epic/dpavservep.h new file mode 100644 index 00000000000000..858ff14b0bd7be --- /dev/null +++ b/drivers/gpu/drm/apple/epic/dpavservep.h @@ -0,0 +1,22 @@ +// SPDX-License-Identifier: GPL-2.0-only OR MIT +/* Copyright The Asahi Linux Contributors */ + +#ifndef _DRM_APPLE_EPIC_DPAVSERV_H +#define _DRM_APPLE_EPIC_DPAVSERV_H + +#include +#include + +struct drm_edid; +struct apple_epic_service; + +struct dcpavserv { + bool enabled; + struct completion enable_completion; + u32 unit; + struct apple_epic_service *service; +}; + +const struct drm_edid *dcpavserv_copy_edid(struct apple_epic_service *service); + +#endif /* _DRM_APPLE_EPIC_DPAVSERV_H */ diff --git a/drivers/gpu/drm/apple/trace.h b/drivers/gpu/drm/apple/trace.h index e03bf8b199c88f..a13dd34fb7aab1 100644 --- a/drivers/gpu/drm/apple/trace.h +++ b/drivers/gpu/drm/apple/trace.h @@ -351,6 +351,18 @@ DEFINE_EVENT(iomfb_parse_mode_template, iomfb_parse_mode_fail, TP_PROTO(s64 id, struct dimension *horiz, struct dimension *vert, s64 best_color_mode, bool is_virtual, s64 score), TP_ARGS(id, horiz, vert, best_color_mode, is_virtual, score)); +TRACE_EVENT(dcpavserv_init, TP_PROTO(struct apple_dcp *dcp, u64 unit), + TP_ARGS(dcp, unit), + + TP_STRUCT__entry(__string(devname, dev_name(dcp->dev)) + __field(u64, unit)), + + TP_fast_assign(__assign_str(devname); + __entry->unit = unit;), + + TP_printk("%s: dcpav-service unit %lld initialized", __get_str(devname), + __entry->unit)); + TRACE_EVENT(dptxport_init, TP_PROTO(struct apple_dcp *dcp, u64 unit), TP_ARGS(dcp, unit), From 6445f5e34d44880bf3af9c6dae089aec7eba84c9 Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Tue, 20 Aug 2024 22:39:06 +0200 Subject: [PATCH 1186/3902] drm: apple: iomfb: Provide the EDID as connector property External display only since the EDID provided by integrated panels holds no useful / correct information. Signed-off-by: Janne Grunau --- drivers/gpu/drm/apple/connector.h | 3 +++ drivers/gpu/drm/apple/dcp.c | 2 ++ drivers/gpu/drm/apple/iomfb.c | 20 ++++++++++++++++++++ 3 files changed, 25 insertions(+) diff --git a/drivers/gpu/drm/apple/connector.h b/drivers/gpu/drm/apple/connector.h index 616ad8fceefae4..ff643552c77d4c 100644 --- a/drivers/gpu/drm/apple/connector.h +++ b/drivers/gpu/drm/apple/connector.h @@ -8,6 +8,7 @@ #include #include "drm/drm_connector.h" +#include "drm/drm_edid.h" struct apple_connector; @@ -21,6 +22,8 @@ struct apple_connector { struct platform_device *dcp; + const struct drm_edid *drm_edid; + /* Workqueue for sending hotplug events to the associated device */ struct work_struct hotplug_wq; diff --git a/drivers/gpu/drm/apple/dcp.c b/drivers/gpu/drm/apple/dcp.c index 115d6c74b1c258..bc513752fb1189 100644 --- a/drivers/gpu/drm/apple/dcp.c +++ b/drivers/gpu/drm/apple/dcp.c @@ -242,6 +242,8 @@ static void dcp_rtk_crashed(void *cookie, const void *crashlog, size_t crashlog_ dev_err(dcp->dev, "DCP has crashed\n"); if (dcp->connector) { dcp->connector->connected = 0; + drm_edid_free(dcp->connector->drm_edid); + dcp->connector->drm_edid = NULL; schedule_work(&dcp->connector->hotplug_wq); } complete(&dcp->start_done); diff --git a/drivers/gpu/drm/apple/iomfb.c b/drivers/gpu/drm/apple/iomfb.c index 0941ff69873235..398118933e801e 100644 --- a/drivers/gpu/drm/apple/iomfb.c +++ b/drivers/gpu/drm/apple/iomfb.c @@ -16,6 +16,7 @@ #include #include +#include #include #include #include @@ -241,6 +242,11 @@ void dcp_hotplug(struct work_struct *work) dev_info(dcp->dev, "%s() connected:%d valid_mode:%d nr_modes:%u\n", __func__, connector->connected, dcp->valid_mode, dcp->nr_modes); + if (!connector->connected) { + drm_edid_free(connector->drm_edid); + connector->drm_edid = NULL; + } + /* * DCP defers link training until we set a display mode. But we set * display modes from atomic_flush, so userspace needs to trigger a @@ -391,6 +397,20 @@ int dcp_get_modes(struct drm_connector *connector) drm_mode_probed_add(connector, mode); } + if (dcp->nr_modes && dcp->dcpavserv.enabled && + !apple_connector->drm_edid) { + const struct drm_edid *edid; + edid = dcpavserv_copy_edid(dcp->dcpavserv.service); + if (IS_ERR_OR_NULL(edid)) { + dev_info(dcp->dev, "copy_edid failed: %pe\n", edid); + } else { + drm_edid_free(apple_connector->drm_edid); + apple_connector->drm_edid = edid; + } + } + if (dcp->nr_modes && apple_connector->drm_edid) + drm_edid_connector_update(connector, apple_connector->drm_edid); + return dcp->nr_modes; } EXPORT_SYMBOL_GPL(dcp_get_modes); From 475d37f22f79aa9181f1760df4033130da9f64a0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20Povi=C5=A1er?= Date: Thu, 23 Feb 2023 12:34:28 +0100 Subject: [PATCH 1187/3902] ALSA: Introduce 'snd_interval_rate_bits' MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Martin Povišer --- include/sound/pcm.h | 1 + sound/core/pcm_lib.c | 37 +++++++++++++++++++++++++++++++++++++ 2 files changed, 38 insertions(+) diff --git a/include/sound/pcm.h b/include/sound/pcm.h index 58fd6e84f9613f..50f9180aeff278 100644 --- a/include/sound/pcm.h +++ b/include/sound/pcm.h @@ -1073,6 +1073,7 @@ int snd_interval_ranges(struct snd_interval *i, unsigned int count, int snd_interval_ratnum(struct snd_interval *i, unsigned int rats_count, const struct snd_ratnum *rats, unsigned int *nump, unsigned int *denp); +int snd_interval_rate_bits(struct snd_interval *i, unsigned int rate_bits); void _snd_pcm_hw_params_any(struct snd_pcm_hw_params *params); void _snd_pcm_hw_param_setempty(struct snd_pcm_hw_params *params, snd_pcm_hw_param_t var); diff --git a/sound/core/pcm_lib.c b/sound/core/pcm_lib.c index 6eaa950504cfc0..d0df847152f0dd 100644 --- a/sound/core/pcm_lib.c +++ b/sound/core/pcm_lib.c @@ -1149,6 +1149,43 @@ static int snd_interval_step(struct snd_interval *i, unsigned int step) return changed; } +/** + * snd_interval_rate_bits - refine the rate interval from a rate bitmask + * @i: the rate interval to refine + * @mask: the rate bitmask + * + * Refines the interval value, assumed to be the sample rate, according to + * a bitmask of available rates (an ORed combination of SNDRV_PCM_RATE_*). + * + * Return: Positive if the value is changed, zero if it's not changed, or a + * negative error code. + */ +int snd_interval_rate_bits(struct snd_interval *i, unsigned int mask) +{ + unsigned int k; + struct snd_interval mask_range; + + if (!mask) + return -EINVAL; + + snd_interval_any(&mask_range); + mask_range.min = UINT_MAX; + mask_range.max = 0; + for (k = 0; k < snd_pcm_known_rates.count; k++) { + unsigned int rate = snd_pcm_known_rates.list[k]; + if (!(mask & (1 << k))) + continue; + + if (rate > mask_range.max) + mask_range.max = rate; + + if (rate < mask_range.min) + mask_range.min = rate; + } + return snd_interval_refine(i, &mask_range); +} +EXPORT_SYMBOL(snd_interval_rate_bits); + /* Info constraints helpers */ /** From 5a1fb16c610de41b0e79c8ec3475b7bf6e643505 Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Sat, 9 Nov 2024 10:17:06 +0100 Subject: [PATCH 1188/3902] drm: apple: Enable EDID support by default Signed-off-by: Janne Grunau --- drivers/gpu/drm/apple/dcp.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/gpu/drm/apple/dcp.c b/drivers/gpu/drm/apple/dcp.c index bc513752fb1189..1323f239141bf8 100644 --- a/drivers/gpu/drm/apple/dcp.c +++ b/drivers/gpu/drm/apple/dcp.c @@ -50,7 +50,7 @@ bool hdmi_audio; module_param(hdmi_audio, bool, 0644); MODULE_PARM_DESC(hdmi_audio, "Enable unstable HDMI audio support"); -static bool unstable_edid; +static bool unstable_edid = true; module_param(unstable_edid, bool, 0644); MODULE_PARM_DESC(unstable_edid, "Enable unstable EDID retrival support"); From e93557055a704b99f94788589e0500dc91adbc48 Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Sat, 31 Aug 2024 14:26:01 +0200 Subject: [PATCH 1189/3902] drm: apple: audio: Implement runtime PM support Signed-off-by: Janne Grunau --- drivers/gpu/drm/apple/audio.c | 45 ++++++++++++++++++++++++++++------- 1 file changed, 37 insertions(+), 8 deletions(-) diff --git a/drivers/gpu/drm/apple/audio.c b/drivers/gpu/drm/apple/audio.c index b78e3895987103..38718e2f56117b 100644 --- a/drivers/gpu/drm/apple/audio.c +++ b/drivers/gpu/drm/apple/audio.c @@ -18,6 +18,7 @@ #include #include #include +#include #include #include #include @@ -377,6 +378,7 @@ static int dcp_pcm_trigger(struct snd_pcm_substream *substream, int cmd) if (!dcpaud_connection_up(dcpaud)) return -ENXIO; + WARN_ON(pm_runtime_get_sync(dcpaud->dev) < 0); ret = dcp_audiosrv_startlink(dcpaud->dcp_dev, &dcpaud->selected_cookie); if (ret < 0) @@ -403,6 +405,8 @@ static int dcp_pcm_trigger(struct snd_pcm_substream *substream, int cmd) case SNDRV_PCM_TRIGGER_STOP: case SNDRV_PCM_TRIGGER_SUSPEND: ret = dcp_audiosrv_stoplink(dcpaud->dcp_dev); + pm_runtime_mark_last_busy(dcpaud->dev); + __pm_runtime_put_autosuspend(dcpaud->dev); if (ret < 0) return ret; break; @@ -605,6 +609,13 @@ static int dcpaud_comp_bind(struct device *dev, struct device *main, void *data) int index; int ret; + pm_runtime_get_noresume(dev); + pm_runtime_set_active(dev); + + ret = devm_pm_runtime_enable(dev); + if (ret) + return dev_err_probe(dev, ret, "Failed to enable runtime PM: %d\n", ret); + /* find linked DCP instance */ endpoint = of_graph_get_endpoint_by_regs(dev->of_node, 0, 0); if (endpoint) { @@ -614,35 +625,34 @@ static int dcpaud_comp_bind(struct device *dev, struct device *main, void *data) if (!dcp_node || !of_device_is_available(dcp_node)) { of_node_put(dcp_node); dev_info(dev, "No audio support\n"); - return 0; + goto rpm_put; } index = of_property_match_string(dev->of_node, "dma-names", "tx"); if (index < 0) { dev_err(dev, "No dma-names property\n"); - return 0; + goto rpm_put; } if (of_parse_phandle_with_args(dev->of_node, "dmas", "#dma-cells", index, &dma_spec) || !dma_spec.np) { dev_err(dev, "Failed to parse dmas property\n"); - return 0; + goto rpm_put; } dcp_pdev = of_find_device_by_node(dcp_node); of_node_put(dcp_node); if (!dcp_pdev) { dev_info(dev, "No DP/HDMI audio device, dcp not ready\n"); - return 0; + goto rpm_put; } dcpaud->dcp_dev = &dcp_pdev->dev; - dma_pdev = of_find_device_by_node(dma_spec.np); of_node_put(dma_spec.np); if (!dma_pdev) { dev_info(dev, "No DMA device\n"); - return 0; + goto rpm_put; } dcpaud->dma_dev = &dma_pdev->dev; @@ -663,6 +673,9 @@ static int dcpaud_comp_bind(struct device *dev, struct device *main, void *data) DCPAUD_PRODUCTATTRS_MAXSIZE); } +rpm_put: + pm_runtime_put(dev); + return 0; } @@ -718,7 +731,22 @@ static void dcpaud_shutdown(struct platform_device *pdev) component_del(&pdev->dev, &dcpaud_comp_ops); } -// static DEFINE_SIMPLE_DEV_PM_OPS(dcpaud_pm_ops, dcpaud_suspend, dcpaud_resume); +static __maybe_unused int dcpaud_suspend(struct device *dev) +{ + /* + * Using snd_power_change_state() does not work since the sound card + * is what resumes runtime PM. + */ + + return 0; +} + +static __maybe_unused int dcpaud_resume(struct device *dev) +{ + return 0; +} + +static DEFINE_RUNTIME_DEV_PM_OPS(dcpaud_pm_ops, dcpaud_suspend, dcpaud_resume, NULL); static const struct of_device_id dcpaud_of_match[] = { { .compatible = "apple,dpaudio" }, @@ -728,7 +756,8 @@ static const struct of_device_id dcpaud_of_match[] = { static struct platform_driver dcpaud_driver = { .driver = { .name = "dcp-dp-audio", - .of_match_table = dcpaud_of_match, + .of_match_table = dcpaud_of_match, + .pm = pm_ptr(&dcpaud_pm_ops), }, .probe = dcpaud_probe, .remove = dcpaud_remove, From c86a3cac4e7e32387e7732a5989338d0b679dc28 Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Mon, 22 Jul 2024 20:26:55 +0200 Subject: [PATCH 1190/3902] drm: apple: Add CRTC CRC support The DCP firmware has CRC support. While this is not yet reverse engineering report always 0 to at least be able to run tests from igt-gpu-tools with "--skip-crc-compare". Signed-off-by: Janne Grunau --- drivers/gpu/drm/apple/apple_drv.c | 57 ++++++++++++++++++++++++++ drivers/gpu/drm/apple/dcp-internal.h | 3 ++ drivers/gpu/drm/apple/dcp.c | 11 +++++ drivers/gpu/drm/apple/dcp.h | 1 + drivers/gpu/drm/apple/iomfb_template.c | 4 ++ 5 files changed, 76 insertions(+) diff --git a/drivers/gpu/drm/apple/apple_drv.c b/drivers/gpu/drm/apple/apple_drv.c index bbf419cb29ba92..0f995371e2f13c 100644 --- a/drivers/gpu/drm/apple/apple_drv.c +++ b/drivers/gpu/drm/apple/apple_drv.c @@ -261,6 +261,59 @@ static void apple_crtc_cleanup(struct drm_crtc *crtc) kfree(to_apple_crtc(crtc)); } +static int apple_crtc_parse_crc_source(const char *source, bool *enabled) +{ + int ret = 0; + + if (!source) { + *enabled = false; + } else if (strcmp(source, "auto") == 0) { + *enabled = true; + } else { + *enabled = false; + ret = -EINVAL; + } + + return ret; +} + +static int apple_crtc_set_crc_source(struct drm_crtc *crtc, const char *source) +{ + bool enabled = false; + + int ret = apple_crtc_parse_crc_source(source, &enabled); + + if (!ret) + dcp_set_crc(crtc, enabled); + + return ret; +} + +static int apple_crtc_verify_crc_source(struct drm_crtc *crtc, + const char *source, + size_t *values_cnt) +{ + bool enabled; + + if (apple_crtc_parse_crc_source(source, &enabled) < 0) { + pr_warn("dcp: Invalid CRC source name %s\n", source); + return -EINVAL; + } + + *values_cnt = 1; + + return 0; +} + +static const char * const apple_crtc_crc_sources[] = {"auto"}; + +static const char *const * apple_crtc_get_crc_sources(struct drm_crtc *crtc, + size_t *count) +{ + *count = ARRAY_SIZE(apple_crtc_crc_sources); + return apple_crtc_crc_sources; +} + static const struct drm_crtc_funcs apple_crtc_funcs = { .atomic_destroy_state = drm_atomic_helper_crtc_destroy_state, .atomic_duplicate_state = drm_atomic_helper_crtc_duplicate_state, @@ -268,6 +321,10 @@ static const struct drm_crtc_funcs apple_crtc_funcs = { .page_flip = drm_atomic_helper_page_flip, .reset = drm_atomic_helper_crtc_reset, .set_config = drm_atomic_helper_set_config, + .set_crc_source = apple_crtc_set_crc_source, + .verify_crc_source = apple_crtc_verify_crc_source, + .get_crc_sources = apple_crtc_get_crc_sources, + }; static const struct drm_mode_config_funcs apple_mode_config_funcs = { diff --git a/drivers/gpu/drm/apple/dcp-internal.h b/drivers/gpu/drm/apple/dcp-internal.h index d762c6bffbdee4..2c31d2a8cef09d 100644 --- a/drivers/gpu/drm/apple/dcp-internal.h +++ b/drivers/gpu/drm/apple/dcp-internal.h @@ -198,6 +198,9 @@ struct apple_dcp { /* clear all surfaces on init */ bool surfaces_cleared; + /* enable CRC calculation */ + bool crc_enabled; + /* Modes valid for the connected display */ struct dcp_display_mode *modes; unsigned int nr_modes; diff --git a/drivers/gpu/drm/apple/dcp.c b/drivers/gpu/drm/apple/dcp.c index 1323f239141bf8..5243888510b97a 100644 --- a/drivers/gpu/drm/apple/dcp.c +++ b/drivers/gpu/drm/apple/dcp.c @@ -191,6 +191,17 @@ bool dcp_has_panel(struct apple_dcp *dcp) return dcp->panel.width_mm > 0; } +int dcp_set_crc(struct drm_crtc *crtc, bool enabled) +{ + struct apple_crtc *ac = to_apple_crtc(crtc); + struct apple_dcp *dcp = platform_get_drvdata(ac->dcp); + + dcp->crc_enabled = enabled; + + return 0; +} +EXPORT_SYMBOL_GPL(dcp_set_crc); + /* * Helper to send a DRM vblank event. We do not know how call swap_submit_dcp * without surfaces. To avoid timeouts in drm_atomic_helper_wait_for_vblanks diff --git a/drivers/gpu/drm/apple/dcp.h b/drivers/gpu/drm/apple/dcp.h index 0c33fac0ee28f0..e34bc495fd3973 100644 --- a/drivers/gpu/drm/apple/dcp.h +++ b/drivers/gpu/drm/apple/dcp.h @@ -31,6 +31,7 @@ struct apple_encoder { void dcp_poweroff(struct platform_device *pdev); void dcp_poweron(struct platform_device *pdev); +int dcp_set_crc(struct drm_crtc *crtc, bool enabled); int dcp_crtc_atomic_check(struct drm_crtc *crtc, struct drm_atomic_state *state); int dcp_get_connector_type(struct platform_device *pdev); void dcp_link(struct platform_device *pdev, struct apple_crtc *apple, diff --git a/drivers/gpu/drm/apple/iomfb_template.c b/drivers/gpu/drm/apple/iomfb_template.c index 5df04979f573eb..546ac3d03996d4 100644 --- a/drivers/gpu/drm/apple/iomfb_template.c +++ b/drivers/gpu/drm/apple/iomfb_template.c @@ -125,6 +125,10 @@ static void dcpep_cb_swap_complete(struct apple_dcp *dcp, dcp->last_swap_id = resp->swap_id; dcp_drm_crtc_page_flip(dcp, now); + if (dcp->crc_enabled) { + u32 crc32 = 0; + drm_crtc_add_crc_entry(&dcp->crtc->base, true, resp->swap_id, &crc32); + } } /* special */ From 96a3572799a2d1f210314a783406de22930ebb6f Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Sat, 7 Dec 2024 21:50:40 +0100 Subject: [PATCH 1191/3902] drm: apple: Add .get_scanout_buffer for drm_panic support Signed-off-by: Janne Grunau --- drivers/gpu/drm/apple/apple_drv.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/gpu/drm/apple/apple_drv.c b/drivers/gpu/drm/apple/apple_drv.c index 0f995371e2f13c..58ba80fdc97144 100644 --- a/drivers/gpu/drm/apple/apple_drv.c +++ b/drivers/gpu/drm/apple/apple_drv.c @@ -119,6 +119,7 @@ static void apple_plane_atomic_update(struct drm_plane *plane, static const struct drm_plane_helper_funcs apple_plane_helper_funcs = { .atomic_check = apple_plane_atomic_check, .atomic_update = apple_plane_atomic_update, + .get_scanout_buffer = drm_fb_dma_get_scanout_buffer, }; static void apple_plane_cleanup(struct drm_plane *plane) From 6de4cd22708d0957bfb38da61c0765fe6d00e1cd Mon Sep 17 00:00:00 2001 From: James Calligeros Date: Sun, 12 May 2024 21:02:40 +1000 Subject: [PATCH 1192/3902] drm: apple: respect drm_plane_state zpos The for_each_oldnew_plane_in_state iterator is nondeterministic in terms of the order of planes. DCP expects surfaces to be fed to it in the correct order. Relying on the iterator to lazily increment the index into surf[] means we cannot meet this expectation. The constant reordering of planes in the surf[] array seems to cause DCP to crash under certain circumstances. Cursors will also often be drawn under the main plane, which is less than ideal. Populate surf[] in the order everyone expects us to. This fixes a whole host of odd behaviour when wiring up multiple DRM universal planes. Signed-off-by: James Calligeros --- drivers/gpu/drm/apple/iomfb_template.c | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/drivers/gpu/drm/apple/iomfb_template.c b/drivers/gpu/drm/apple/iomfb_template.c index 546ac3d03996d4..da0aef1180d898 100644 --- a/drivers/gpu/drm/apple/iomfb_template.c +++ b/drivers/gpu/drm/apple/iomfb_template.c @@ -1295,8 +1295,6 @@ void DCP_FW_NAME(iomfb_flush)(struct apple_dcp *dcp, struct drm_crtc *crtc, stru dcp->surfaces_cleared = true; } - // Surface 0 has limitations at least on t600x. - l = 1; for_each_oldnew_plane_in_state(state, plane, old_state, new_state, plane_idx) { struct drm_framebuffer *fb = new_state->fb; struct drm_gem_dma_object *obj; @@ -1307,6 +1305,17 @@ void DCP_FW_NAME(iomfb_flush)(struct apple_dcp *dcp, struct drm_crtc *crtc, stru if (old_state->crtc != crtc && new_state->crtc != crtc) continue; + /* + * Plane order is nondeterministic for this iterator. DCP will + * almost always crash at some point if the z order of planes + * flip-flops around. Make sure we are always blending them + * in the correct order. + * + * Despite having 4 surfaces, we can only blend two. Surface 0 is + * also unusable on some machines, so ignore it. + */ + l = 2 - new_state->zpos; + WARN_ON(l >= SWAP_SURFACES); req->swap.swap_enabled |= BIT(l); @@ -1333,7 +1342,6 @@ void DCP_FW_NAME(iomfb_flush)(struct apple_dcp *dcp, struct drm_crtc *crtc, stru } if (!new_state->fb) { - l += 1; continue; } req->surf_null[l] = false; @@ -1383,7 +1391,6 @@ void DCP_FW_NAME(iomfb_flush)(struct apple_dcp *dcp, struct drm_crtc *crtc, stru .has_planes = 1, }; - l += 1; } if (!has_surface && !crtc_state->color_mgmt_changed) { From 81a2bde61c5eeb0fea04878eb85b25d5935cc462 Mon Sep 17 00:00:00 2001 From: James Calligeros Date: Tue, 14 May 2024 19:23:04 +1000 Subject: [PATCH 1193/3902] drm: apple: constrain swaps to maximum blendable surfaces Despite having 4 surfaces, DCP can only blend two of them at once. Constrain swaps to two surfaces, and warn if userspace somehow tries to give us more to swap. Signed-off-by: James Calligeros --- drivers/gpu/drm/apple/iomfb.h | 2 ++ drivers/gpu/drm/apple/iomfb_template.c | 8 +++++--- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/drivers/gpu/drm/apple/iomfb.h b/drivers/gpu/drm/apple/iomfb.h index 8fb225e6169e42..025f292d532a77 100644 --- a/drivers/gpu/drm/apple/iomfb.h +++ b/drivers/gpu/drm/apple/iomfb.h @@ -77,6 +77,8 @@ enum iomfb_property_id { /* Structures used in v12.0 firmware */ #define SWAP_SURFACES 4 +/* We have 4 surfaces, but we can only ever blend two */ +#define MAX_BLEND_SURFACES 2 #define MAX_PLANES 3 enum dcp_colorspace { diff --git a/drivers/gpu/drm/apple/iomfb_template.c b/drivers/gpu/drm/apple/iomfb_template.c index da0aef1180d898..f3e1d401639ed9 100644 --- a/drivers/gpu/drm/apple/iomfb_template.c +++ b/drivers/gpu/drm/apple/iomfb_template.c @@ -904,6 +904,7 @@ void DCP_FW_NAME(iomfb_poweroff)(struct apple_dcp *dcp) swap->swap.bl_power = 0; } + /* Null all surfaces */ for (int l = 0; l < SWAP_SURFACES; l++) swap->surf_null[l] = true; #if DCP_FW_VER >= DCP_FW_VERSION(13, 2, 0) @@ -1274,7 +1275,7 @@ void DCP_FW_NAME(iomfb_flush)(struct apple_dcp *dcp, struct drm_crtc *crtc, stru crtc_state = drm_atomic_get_new_crtc_state(state, crtc); - /* Reset to defaults */ + /* Reset all surfaces to defaults */ memset(req, 0, sizeof(*req)); for (l = 0; l < SWAP_SURFACES; l++) req->surf_null[l] = true; @@ -1314,9 +1315,10 @@ void DCP_FW_NAME(iomfb_flush)(struct apple_dcp *dcp, struct drm_crtc *crtc, stru * Despite having 4 surfaces, we can only blend two. Surface 0 is * also unusable on some machines, so ignore it. */ - l = 2 - new_state->zpos; - WARN_ON(l >= SWAP_SURFACES); + l = MAX_BLEND_SURFACES - new_state->zpos; + + WARN_ON(l > MAX_BLEND_SURFACES); req->swap.swap_enabled |= BIT(l); From a6bf4d7067f748ef11a71866ecb7869c640128e9 Mon Sep 17 00:00:00 2001 From: James Calligeros Date: Tue, 14 May 2024 20:42:20 +1000 Subject: [PATCH 1194/3902] drm: apple: reject plane commit if it will crash DCP Owing to its origin in mobile devices and the Apple TV, DCP seems to have been designed under the assumption that no one could possibly want a rectangle to clip the screen. If a rectangle's bottom-right edge clips the screen, DCP will instead try to scale the destination rectangle to the best of its ability... until it can't anymore. DCP is not tolerant to faults and will crash if the onscreen portion of the framebuffer ends up smaller than 32x32, or if any dimension ends up entirely offscreen. Use apple_plane_atomic_check() to reject requested plane states that could crash DCP. This is the final piece of the puzzle required to enable preliminary support for overlay planes on Apple Silicon devices. Signed-off-by: James Calligeros --- drivers/gpu/drm/apple/apple_drv.c | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/drivers/gpu/drm/apple/apple_drv.c b/drivers/gpu/drm/apple/apple_drv.c index 58ba80fdc97144..81fd4ed7166558 100644 --- a/drivers/gpu/drm/apple/apple_drv.c +++ b/drivers/gpu/drm/apple/apple_drv.c @@ -91,6 +91,19 @@ static int apple_plane_atomic_check(struct drm_plane *plane, if (IS_ERR(crtc_state)) return PTR_ERR(crtc_state); + /* + * DCP does not allow a surface to clip off the screen, and will crash + * if any blended surface is smaller than 32x32. Reject the atomic op + * if the plane will crash DCP. + * + * This is most pertinent to cursors. Userspace should fall back to + * software cursors if the plane check is rejected. + */ + if ((new_plane_state->crtc_x + 32) > crtc_state->mode.hdisplay || + (new_plane_state->crtc_y + 32) > crtc_state->mode.vdisplay) { + return -EINVAL; + } + /* * DCP limits downscaling to 2x and upscaling to 4x. Attempting to * scale outside these bounds errors out when swapping. From 915da1d5abbacac802784619cfe331fe31a8a990 Mon Sep 17 00:00:00 2001 From: James Calligeros Date: Tue, 14 May 2024 21:03:25 +1000 Subject: [PATCH 1195/3902] drm: apple: add support for overlay planes DCP is capable of compositing two surfaces in hardware. This is important for zero-copy video playback, etc. Set up an overlay plane so that userspace can do cool things with it. Signed-off-by: James Calligeros --- drivers/gpu/drm/apple/apple_drv.c | 62 +++++++++++++++++++++++++------ 1 file changed, 51 insertions(+), 11 deletions(-) diff --git a/drivers/gpu/drm/apple/apple_drv.c b/drivers/gpu/drm/apple/apple_drv.c index 81fd4ed7166558..b326e3d7a2f9bc 100644 --- a/drivers/gpu/drm/apple/apple_drv.c +++ b/drivers/gpu/drm/apple/apple_drv.c @@ -129,12 +129,17 @@ static void apple_plane_atomic_update(struct drm_plane *plane, /* Handled in atomic_flush */ } -static const struct drm_plane_helper_funcs apple_plane_helper_funcs = { +static const struct drm_plane_helper_funcs apple_primary_plane_helper_funcs = { .atomic_check = apple_plane_atomic_check, .atomic_update = apple_plane_atomic_update, .get_scanout_buffer = drm_fb_dma_get_scanout_buffer, }; +static const struct drm_plane_helper_funcs apple_plane_helper_funcs = { + .atomic_check = apple_plane_atomic_check, + .atomic_update = apple_plane_atomic_update, +}; + static void apple_plane_cleanup(struct drm_plane *plane) { drm_plane_cleanup(plane); @@ -161,7 +166,7 @@ static const struct drm_plane_funcs apple_plane_funcs = { * doesn't matter for the primary plane, but cursors/overlays must not * advertise formats without alpha. */ -static const u32 dcp_formats[] = { +static const u32 dcp_primary_formats[] = { DRM_FORMAT_XRGB2101010, DRM_FORMAT_XRGB8888, DRM_FORMAT_ARGB8888, @@ -169,6 +174,11 @@ static const u32 dcp_formats[] = { DRM_FORMAT_ABGR8888, }; +static const u32 dcp_overlay_formats[] = { + DRM_FORMAT_ARGB8888, + DRM_FORMAT_ABGR8888, +}; + u64 apple_format_modifiers[] = { DRM_FORMAT_MOD_LINEAR, DRM_FORMAT_MOD_INVALID @@ -183,14 +193,31 @@ static struct drm_plane *apple_plane_init(struct drm_device *dev, plane = kzalloc(sizeof(*plane), GFP_KERNEL); - ret = drm_universal_plane_init(dev, plane, possible_crtcs, + switch (type) { + case DRM_PLANE_TYPE_PRIMARY: + ret = drm_universal_plane_init(dev, plane, possible_crtcs, &apple_plane_funcs, - dcp_formats, ARRAY_SIZE(dcp_formats), + dcp_primary_formats, ARRAY_SIZE(dcp_primary_formats), apple_format_modifiers, type, NULL); + break; + case DRM_PLANE_TYPE_OVERLAY: + case DRM_PLANE_TYPE_CURSOR: + ret = drm_universal_plane_init(dev, plane, possible_crtcs, + &apple_plane_funcs, + dcp_overlay_formats, ARRAY_SIZE(dcp_overlay_formats), + apple_format_modifiers, type, NULL); + break; + default: + return NULL; + } + if (ret) return ERR_PTR(ret); - drm_plane_helper_add(plane, &apple_plane_helper_funcs); + if (type == DRM_PLANE_TYPE_PRIMARY) + drm_plane_helper_add(plane, &apple_primary_plane_helper_funcs); + else + drm_plane_helper_add(plane, &apple_plane_helper_funcs); return plane; } @@ -390,16 +417,29 @@ static int apple_probe_per_dcp(struct device *dev, struct apple_crtc *crtc; struct apple_connector *connector; struct apple_encoder *enc; - struct drm_plane *primary; - int ret; + struct drm_plane *planes[DCP_MAX_PLANES]; + int ret, i; + + planes[0] = apple_plane_init(drm, 1U << num, DRM_PLANE_TYPE_PRIMARY); + if (IS_ERR(planes[0])) + return PTR_ERR(planes[0]); - primary = apple_plane_init(drm, 1U << num, DRM_PLANE_TYPE_PRIMARY); - if (IS_ERR(primary)) - return PTR_ERR(primary); + /* Set up our other planes */ + for (i = 1; i < DCP_MAX_PLANES; i++) { + planes[i] = apple_plane_init(drm, 1U << num, DRM_PLANE_TYPE_OVERLAY); + if (IS_ERR(planes[i])) + return PTR_ERR(planes[i]); + } + /* + * Even though we have an overlay plane, we cannot expose it to legacy + * userspace for cursors as we cannot make the same guarantees as ye olde + * hardware cursor planes such userspace would expect us to. Modern userspace + * knows what to do with overlays. + */ crtc = kzalloc(sizeof(*crtc), GFP_KERNEL); - ret = drm_crtc_init_with_planes(drm, &crtc->base, primary, NULL, + ret = drm_crtc_init_with_planes(drm, &crtc->base, planes[0], NULL, &apple_crtc_funcs, NULL); if (ret) return ret; From 1d4f2c27fc984fe0a9ec37d08410f9b9400f308c Mon Sep 17 00:00:00 2001 From: James Calligeros Date: Tue, 14 May 2024 22:04:28 +1000 Subject: [PATCH 1196/3902] drm: apple: use correct min/max plane scaling factors Fix the call to drm_atomic_helper_check_plane_state to use the correct scaling factors. Signed-off-by: James Calligeros --- drivers/gpu/drm/apple/apple_drv.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/gpu/drm/apple/apple_drv.c b/drivers/gpu/drm/apple/apple_drv.c index b326e3d7a2f9bc..796ca90929d098 100644 --- a/drivers/gpu/drm/apple/apple_drv.c +++ b/drivers/gpu/drm/apple/apple_drv.c @@ -118,8 +118,8 @@ static int apple_plane_atomic_check(struct drm_plane *plane, */ return drm_atomic_helper_check_plane_state(new_plane_state, crtc_state, - FRAC_16_16(1, 4), - FRAC_16_16(2, 1), + FRAC_16_16(1, 2), + FRAC_16_16(4, 1), true, true); } From 353435390eeae3d8734c330fed59e82d21a15a7d Mon Sep 17 00:00:00 2001 From: James Calligeros Date: Wed, 15 May 2024 20:42:07 +1000 Subject: [PATCH 1197/3902] drm: apple: warn about broken sw cursor fallback Some userspace may not handle invalid plane checks gracefully when falling back to a software cursor. This will manifest as the screen freezing, recoverable by moving the cursor away from a screen edge. Throw a warning once to let the user know why this has happened. Signed-off-by: James Calligeros --- drivers/gpu/drm/apple/apple_drv.c | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/drivers/gpu/drm/apple/apple_drv.c b/drivers/gpu/drm/apple/apple_drv.c index 796ca90929d098..a2ea332d84ebbb 100644 --- a/drivers/gpu/drm/apple/apple_drv.c +++ b/drivers/gpu/drm/apple/apple_drv.c @@ -101,6 +101,18 @@ static int apple_plane_atomic_check(struct drm_plane *plane, */ if ((new_plane_state->crtc_x + 32) > crtc_state->mode.hdisplay || (new_plane_state->crtc_y + 32) > crtc_state->mode.vdisplay) { + dev_err_once(state->dev->dev, + "Plane operation would have crashed DCP! Rejected!\n\ + DCP requires 32x32 of every plane to be within screen space.\n\ + Your compositor asked for a screen space area of [%d, %d].\n\ + This is not supported, and your compositor should have\n\ + switched to software compositing when this operation failed.\n\ + You should not have noticed this at all. If your screen\n\ + froze/hitched, or your compositor crashed, please report\n\ + this to the your compositor's developers. We will not\n\ + throw this error again until you next reboot.\n", + crtc_state->mode.hdisplay - new_plane_state->crtc_x, + crtc_state->mode.vdisplay - new_plane_state->crtc_y); return -EINVAL; } From 483d79d27561c68a53a1b630f02376af75b48670 Mon Sep 17 00:00:00 2001 From: James Calligeros Date: Mon, 1 Jul 2024 17:27:05 +1000 Subject: [PATCH 1198/3902] drm: apple: make plane zpos immutable Userspace cannot be trusted to give us a sane zpos value, but given DCP's requirement that the primary plane always be the bottommost surface, we can't rely on drm_atomic_normalize_zpos() to do the job for us either. Make the zpos property immutable, and keep the primary plane at zpos 0. Signed-off-by: James Calligeros --- drivers/gpu/drm/apple/apple_drv.c | 11 +++++++++++ drivers/gpu/drm/apple/iomfb_template.c | 2 +- 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/drivers/gpu/drm/apple/apple_drv.c b/drivers/gpu/drm/apple/apple_drv.c index a2ea332d84ebbb..f0c0cf835e0e86 100644 --- a/drivers/gpu/drm/apple/apple_drv.c +++ b/drivers/gpu/drm/apple/apple_drv.c @@ -20,6 +20,7 @@ #include #include +#include #include #include #include @@ -431,10 +432,15 @@ static int apple_probe_per_dcp(struct device *dev, struct apple_encoder *enc; struct drm_plane *planes[DCP_MAX_PLANES]; int ret, i; + int immutable_zpos = 0; planes[0] = apple_plane_init(drm, 1U << num, DRM_PLANE_TYPE_PRIMARY); if (IS_ERR(planes[0])) return PTR_ERR(planes[0]); + ret = drm_plane_create_zpos_immutable_property(planes[0], immutable_zpos); + if (ret) { + return ret; + } /* Set up our other planes */ @@ -442,6 +448,11 @@ static int apple_probe_per_dcp(struct device *dev, planes[i] = apple_plane_init(drm, 1U << num, DRM_PLANE_TYPE_OVERLAY); if (IS_ERR(planes[i])) return PTR_ERR(planes[i]); + immutable_zpos++; + ret = drm_plane_create_zpos_immutable_property(planes[i], immutable_zpos); + if (ret) { + return ret; + } } /* diff --git a/drivers/gpu/drm/apple/iomfb_template.c b/drivers/gpu/drm/apple/iomfb_template.c index f3e1d401639ed9..999cc34b4fd1af 100644 --- a/drivers/gpu/drm/apple/iomfb_template.c +++ b/drivers/gpu/drm/apple/iomfb_template.c @@ -1316,7 +1316,7 @@ void DCP_FW_NAME(iomfb_flush)(struct apple_dcp *dcp, struct drm_crtc *crtc, stru * also unusable on some machines, so ignore it. */ - l = MAX_BLEND_SURFACES - new_state->zpos; + l = MAX_BLEND_SURFACES - new_state->normalized_zpos; WARN_ON(l > MAX_BLEND_SURFACES); From 36f34a28f7ff101ded2514f32f9f998208d76ea0 Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Sun, 8 Dec 2024 15:51:37 +0100 Subject: [PATCH 1199/3902] drm: apple: refactor apple_plane_atomic_check Call drm_atomic_helper_check_plane_state() first as this allows using the dst rectangle in the new plane state for the off-screen render check. Signed-off-by: Janne Grunau --- drivers/gpu/drm/apple/apple_drv.c | 41 ++++++++++++++++++------------- 1 file changed, 24 insertions(+), 17 deletions(-) diff --git a/drivers/gpu/drm/apple/apple_drv.c b/drivers/gpu/drm/apple/apple_drv.c index f0c0cf835e0e86..1fb8d7031cbd15 100644 --- a/drivers/gpu/drm/apple/apple_drv.c +++ b/drivers/gpu/drm/apple/apple_drv.c @@ -82,6 +82,7 @@ static int apple_plane_atomic_check(struct drm_plane *plane, { struct drm_plane_state *new_plane_state; struct drm_crtc_state *crtc_state; + int ret; new_plane_state = drm_atomic_get_new_plane_state(state, plane); @@ -92,6 +93,28 @@ static int apple_plane_atomic_check(struct drm_plane *plane, if (IS_ERR(crtc_state)) return PTR_ERR(crtc_state); + /* + * DCP limits downscaling to 2x and upscaling to 4x. Attempting to + * scale outside these bounds errors out when swapping. + * + * This function also takes care of clipping the src/dest rectangles, + * which is required for correct operation. Partially off-screen + * surfaces may appear corrupted. + * + * DCP does not distinguish plane types in the hardware, so we set + * can_position. If the primary plane does not fill the screen, the + * hardware will fill in zeroes (black). + */ + ret = drm_atomic_helper_check_plane_state(new_plane_state, crtc_state, + FRAC_16_16(1, 2), + FRAC_16_16(4, 1), + true, true); + if (ret < 0) + return ret; + + if (!new_plane_state->visible) + return 0; + /* * DCP does not allow a surface to clip off the screen, and will crash * if any blended surface is smaller than 32x32. Reject the atomic op @@ -117,23 +140,7 @@ static int apple_plane_atomic_check(struct drm_plane *plane, return -EINVAL; } - /* - * DCP limits downscaling to 2x and upscaling to 4x. Attempting to - * scale outside these bounds errors out when swapping. - * - * This function also takes care of clipping the src/dest rectangles, - * which is required for correct operation. Partially off-screen - * surfaces may appear corrupted. - * - * DCP does not distinguish plane types in the hardware, so we set - * can_position. If the primary plane does not fill the screen, the - * hardware will fill in zeroes (black). - */ - return drm_atomic_helper_check_plane_state(new_plane_state, - crtc_state, - FRAC_16_16(1, 2), - FRAC_16_16(4, 1), - true, true); + return 0; } static void apple_plane_atomic_update(struct drm_plane *plane, From 601917be69f8d428a8387c15bedccc17d9d1c696 Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Sun, 8 Dec 2024 15:54:35 +0100 Subject: [PATCH 1200/3902] drm: apple: Use dest rct in offscreen test The plane state's dst rectangle is what's used to set dcp parameters and the KMS documentation actively recommends that over crtc_x / crtc_y. Signed-off-by: Janne Grunau --- drivers/gpu/drm/apple/apple_drv.c | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/drivers/gpu/drm/apple/apple_drv.c b/drivers/gpu/drm/apple/apple_drv.c index 1fb8d7031cbd15..06c6352ed27733 100644 --- a/drivers/gpu/drm/apple/apple_drv.c +++ b/drivers/gpu/drm/apple/apple_drv.c @@ -82,6 +82,7 @@ static int apple_plane_atomic_check(struct drm_plane *plane, { struct drm_plane_state *new_plane_state; struct drm_crtc_state *crtc_state; + struct drm_rect *dst; int ret; new_plane_state = drm_atomic_get_new_plane_state(state, plane); @@ -123,20 +124,20 @@ static int apple_plane_atomic_check(struct drm_plane *plane, * This is most pertinent to cursors. Userspace should fall back to * software cursors if the plane check is rejected. */ - if ((new_plane_state->crtc_x + 32) > crtc_state->mode.hdisplay || - (new_plane_state->crtc_y + 32) > crtc_state->mode.vdisplay) { + dst = &new_plane_state->dst; + if (drm_rect_width(dst) < 32 || drm_rect_height(dst) < 32) { dev_err_once(state->dev->dev, "Plane operation would have crashed DCP! Rejected!\n\ DCP requires 32x32 of every plane to be within screen space.\n\ - Your compositor asked for a screen space area of [%d, %d].\n\ + Your compositor asked to overlay [%dx%d, %dx%d] on %dx%d.\n\ This is not supported, and your compositor should have\n\ switched to software compositing when this operation failed.\n\ You should not have noticed this at all. If your screen\n\ froze/hitched, or your compositor crashed, please report\n\ this to the your compositor's developers. We will not\n\ throw this error again until you next reboot.\n", - crtc_state->mode.hdisplay - new_plane_state->crtc_x, - crtc_state->mode.vdisplay - new_plane_state->crtc_y); + dst->x1, dst->y1, dst->x2, dst->y2, + crtc_state->mode.hdisplay, crtc_state->mode.vdisplay); return -EINVAL; } From f93c26741a056e211ae5c35d8c2fa00b16b0a07b Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Sat, 18 Jan 2025 09:10:09 +0100 Subject: [PATCH 1201/3902] drm: apple: iomfb: Clear non-visible planes Fixes failed DCP swap validity checks and subsequent DCP crashes. Fixes: 5536a93235a3c ("drm: apple: refactor apple_plane_atomic_check") Signed-off-by: Janne Grunau --- drivers/gpu/drm/apple/iomfb_template.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/gpu/drm/apple/iomfb_template.c b/drivers/gpu/drm/apple/iomfb_template.c index 999cc34b4fd1af..0a1b9495128562 100644 --- a/drivers/gpu/drm/apple/iomfb_template.c +++ b/drivers/gpu/drm/apple/iomfb_template.c @@ -1343,7 +1343,7 @@ void DCP_FW_NAME(iomfb_flush)(struct apple_dcp *dcp, struct drm_crtc *crtc, stru drm_framebuffer_get(old_state->fb); } - if (!new_state->fb) { + if (!new_state->fb || !new_state->visible) { continue; } req->surf_null[l] = false; From da10577f33944886b4f115085f2f9e592c0733d0 Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Sun, 4 Aug 2024 18:46:04 +0200 Subject: [PATCH 1202/3902] drm: apple: Call dptxport_set_hpd in dcp_dptx_connect Also increases the connection timeout to 2 seconds. Signed-off-by: Janne Grunau --- drivers/gpu/drm/apple/dcp.c | 23 +++++++++++------------ 1 file changed, 11 insertions(+), 12 deletions(-) diff --git a/drivers/gpu/drm/apple/dcp.c b/drivers/gpu/drm/apple/dcp.c index 5243888510b97a..deb3e2e6f94f4f 100644 --- a/drivers/gpu/drm/apple/dcp.c +++ b/drivers/gpu/drm/apple/dcp.c @@ -370,7 +370,7 @@ int dcp_get_connector_type(struct platform_device *pdev) } EXPORT_SYMBOL_GPL(dcp_get_connector_type); -#define DPTX_CONNECT_TIMEOUT msecs_to_jiffies(1000) +#define DPTX_CONNECT_TIMEOUT msecs_to_jiffies(2000) static int dcp_dptx_connect(struct apple_dcp *dcp, u32 port) { @@ -410,6 +410,9 @@ static int dcp_dptx_connect(struct apple_dcp *dcp, u32 port) usleep_range(5, 10); + if (dcp->connector_type == DRM_MODE_CONNECTOR_DisplayPort) + dptxport_set_hpd(dcp->dptxport[port].service, true); + return 0; out_unlock: @@ -417,17 +420,6 @@ static int dcp_dptx_connect(struct apple_dcp *dcp, u32 port) return ret; } -int dcp_dptx_connect_oob(struct platform_device *pdev, u32 port) -{ - struct apple_dcp *dcp = platform_get_drvdata(pdev); - int err = dcp_dptx_connect(dcp, port); - if (err < 0) - return err; - dptxport_set_hpd(dcp->dptxport[port].service, true); - return 0; -} -EXPORT_SYMBOL_GPL(dcp_dptx_connect_oob); - static int dcp_dptx_disconnect(struct apple_dcp *dcp, u32 port) { dev_info(dcp->dev, "%s(port=%d)\n", __func__, port); @@ -442,6 +434,13 @@ static int dcp_dptx_disconnect(struct apple_dcp *dcp, u32 port) return 0; } +int dcp_dptx_connect_oob(struct platform_device *pdev, u32 port) +{ + struct apple_dcp *dcp = platform_get_drvdata(pdev); + return dcp_dptx_connect(dcp, port); +} +EXPORT_SYMBOL_GPL(dcp_dptx_connect_oob); + int dcp_dptx_disconnect_oob(struct platform_device *pdev, u32 port) { struct apple_dcp *dcp = platform_get_drvdata(pdev); From c496fcf77b920f2d74bd0f992faef62a0970a1cb Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Tue, 30 Jul 2024 22:00:45 +0200 Subject: [PATCH 1203/3902] drm: apple: Support up to 3 DCP instances. Signed-off-by: Janne Grunau --- drivers/gpu/drm/apple/apple_drv.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/gpu/drm/apple/apple_drv.c b/drivers/gpu/drm/apple/apple_drv.c index 06c6352ed27733..65e7d7ccb6e70c 100644 --- a/drivers/gpu/drm/apple/apple_drv.c +++ b/drivers/gpu/drm/apple/apple_drv.c @@ -46,7 +46,7 @@ #define FRAC_16_16(mult, div) (((mult) << 16) / (div)) -#define MAX_COPROCESSORS 2 +#define MAX_COPROCESSORS 3 struct apple_drm_private { struct drm_device drm; From 74fb393ee01303f765ff51b8fac9f0f62c3ba39e Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Sat, 3 Aug 2024 15:41:00 +0200 Subject: [PATCH 1204/3902] drm: apple: Handle dcps with "phys" property as dcpext Required for dp-altmode on M2 Mac Mini which will use dcp to drive dp-altmode. Signed-off-by: Janne Grunau --- drivers/gpu/drm/apple/apple_drv.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/drivers/gpu/drm/apple/apple_drv.c b/drivers/gpu/drm/apple/apple_drv.c index 65e7d7ccb6e70c..b4527d6f9ce110 100644 --- a/drivers/gpu/drm/apple/apple_drv.c +++ b/drivers/gpu/drm/apple/apple_drv.c @@ -562,7 +562,8 @@ static int apple_drm_init_dcp(struct device *dev) of_node_put(np); continue; } - dcp_ext = of_device_is_compatible(np, "apple,dcpext"); + dcp_ext = of_device_is_compatible(np, "apple,dcpext") || + of_property_present(np, "phys"); dcp[num_dcp] = of_find_device_by_node(np); of_node_put(np); From 49f94bb887d847a0391f048b599d72327d5956a6 Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Thu, 16 Jan 2025 23:29:40 +0100 Subject: [PATCH 1205/3902] drm: apple: dptx: Silence DPTX_APCALL_{GET,SET}_DOWN_SPREAD Signed-off-by: Janne Grunau --- drivers/gpu/drm/apple/dptxep.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/drivers/gpu/drm/apple/dptxep.c b/drivers/gpu/drm/apple/dptxep.c index b8cb7f00133760..0f286401448ff7 100644 --- a/drivers/gpu/drm/apple/dptxep.c +++ b/drivers/gpu/drm/apple/dptxep.c @@ -542,6 +542,9 @@ static int dptxport_call(struct apple_epic_service *service, u32 idx, /* just try to ACK and hope for the best... */ dev_info(service->ep->dcp->dev, "DPTXPort: acking unhandled call %u\n", idx); + fallthrough; + case DPTX_APCALL_GET_DOWN_SPREAD: + case DPTX_APCALL_SET_DOWN_SPREAD: memcpy(reply, data, min(reply_size, data_size)); if (reply_size >= 4) memset(reply, 0, 4); From 207461e283108c0a2939d72a0ead9dc57871bc51 Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Thu, 16 Jan 2025 23:23:15 +0100 Subject: [PATCH 1206/3902] drm: apple: dptx: Tidy up lane count handling Do not try to configure the DP phy's lane count as this is configured by cd321x via the USB type-c mux. Signed-off-by: Janne Grunau --- drivers/gpu/drm/apple/dptxep.c | 25 ++++++++++--------------- 1 file changed, 10 insertions(+), 15 deletions(-) diff --git a/drivers/gpu/drm/apple/dptxep.c b/drivers/gpu/drm/apple/dptxep.c index 0f286401448ff7..2669cbecd05ffa 100644 --- a/drivers/gpu/drm/apple/dptxep.c +++ b/drivers/gpu/drm/apple/dptxep.c @@ -257,7 +257,7 @@ static int dptxport_call_get_max_lane_count(struct apple_epic_service *service, return -EINVAL; reply->retcode = cpu_to_le32(0); - reply->lane_count = cpu_to_le64(4); + reply->lane_count = cpu_to_le64(2); ret = phy_validate(dptx->atcphy, PHY_MODE_DP, 0, &phy_ops); if (ret < 0 || phy_ops.dp.lanes < 2) { @@ -265,9 +265,11 @@ static int dptxport_call_get_max_lane_count(struct apple_epic_service *service, // switched to DP alt mode dev_dbg(service->ep->dcp->dev, "get_max_lane_count: " "phy_validate ret:%d lanes:%d\n", ret, phy_ops.dp.lanes); + dptx->lane_count = 0; } else { reply->retcode = cpu_to_le32(0); reply->lane_count = cpu_to_le64(phy_ops.dp.lanes); + dptx->lane_count = phy_ops.dp.lanes; } return 0; @@ -278,6 +280,7 @@ static int dptxport_call_set_active_lane_count(struct apple_epic_service *servic void *reply_, size_t reply_size) { struct dptx_port *dptx = service->cookie; + struct apple_dcp *dcp = service->ep->dcp; const struct dptxport_apcall_set_active_lane_count *request = data; struct dptxport_apcall_set_active_lane_count *reply = reply_; int ret = 0; @@ -290,34 +293,26 @@ static int dptxport_call_set_active_lane_count(struct apple_epic_service *servic u64 lane_count = cpu_to_le64(request->lane_count); + if (dptx->lane_count < lane_count) + dev_err(dcp->dev, "set_active_lane_count: unexpected lane " + "count:%llu phy: %d\n", lane_count, dptx->lane_count); + switch (lane_count) { case 0 ... 2: case 4: dptx->phy_ops.dp.lanes = lane_count; - dptx->phy_ops.dp.set_lanes = 1; break; default: - dev_err(service->ep->dcp->dev, "set_active_lane_count: invalid lane count:%llu\n", lane_count); + dev_err(dcp->dev, "set_active_lane_count: invalid lane count:%llu\n", lane_count); retcode = 1; lane_count = 0; break; } - if (dptx->phy_ops.dp.set_lanes) { - if (dptx->atcphy) { - ret = phy_configure(dptx->atcphy, &dptx->phy_ops); - if (ret) - return ret; - } - dptx->phy_ops.dp.set_lanes = 0; - } - - dptx->lane_count = lane_count; - reply->retcode = cpu_to_le32(retcode); reply->lane_count = cpu_to_le64(lane_count); - if (dptx->lane_count > 0) + if (lane_count > 0) complete(&dptx->linkcfg_completion); return ret; From 162d12d9f16c7165e93d5e86e5686f64e7fd6406 Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Sat, 18 Jan 2025 10:04:57 +0100 Subject: [PATCH 1207/3902] drm: apple: afk: Allow replies after service 'teardown' 'teardown' on DCP's 'av' endpoint's DCPAVAudioInterface is send during the close afk_service_call. Signed-off-by: Janne Grunau --- drivers/gpu/drm/apple/afk.c | 14 +++++++++++++- drivers/gpu/drm/apple/afk.h | 1 + drivers/gpu/drm/apple/av.c | 2 ++ drivers/gpu/drm/apple/epic/dpavservep.c | 3 +++ 4 files changed, 19 insertions(+), 1 deletion(-) diff --git a/drivers/gpu/drm/apple/afk.c b/drivers/gpu/drm/apple/afk.c index bd1f16e8937c74..d0de72072877b8 100644 --- a/drivers/gpu/drm/apple/afk.c +++ b/drivers/gpu/drm/apple/afk.c @@ -308,6 +308,7 @@ static void afk_recv_handle_init(struct apple_dcp_afkep *ep, u32 channel, ch_idx = ep->num_channels++; spin_lock_init(&ep->services[ch_idx].lock); ep->services[ch_idx].enabled = true; + ep->services[ch_idx].torndown = false; ep->services[ch_idx].ops = ops; ep->services[ch_idx].ep = ep; ep->services[ch_idx].channel = channel; @@ -340,7 +341,12 @@ static void afk_recv_handle_teardown(struct apple_dcp_afkep *ep, u32 channel) // TODO: think through what locking is necessary spin_lock_irqsave(&service->lock, flags); - service->enabled = false; + /* + * teardown must not disable the service since since it may be sent as + * side effect of a COMMAND which for which a reply is expected. + * Seen with DCP's "av" endpoint during the close afk_service_call. + */ + service->torndown = true; ops = service->ops; spin_unlock_irqrestore(&service->lock, flags); @@ -445,6 +451,12 @@ static void afk_recv_handle_std_service(struct apple_dcp_afkep *ep, u32 channel, ep->endpoint, channel); return; } + if (service->torndown) { + dev_warn(ep->dcp->dev, + "AFK[ep:%02x]: std service notify on torn down service " + "(chan:%u)\n", ep->endpoint, channel); + return; + } if (type == EPIC_TYPE_NOTIFY && eshdr->category == EPIC_CAT_NOTIFY) { struct epic_std_service_ap_call *call = payload; diff --git a/drivers/gpu/drm/apple/afk.h b/drivers/gpu/drm/apple/afk.h index 5a286799835248..a339c00a2a0138 100644 --- a/drivers/gpu/drm/apple/afk.h +++ b/drivers/gpu/drm/apple/afk.h @@ -46,6 +46,7 @@ struct apple_epic_service { u32 channel; bool enabled; + bool torndown; void *cookie; diff --git a/drivers/gpu/drm/apple/av.c b/drivers/gpu/drm/apple/av.c index 586f39cc11ca11..f498271da9081c 100644 --- a/drivers/gpu/drm/apple/av.c +++ b/drivers/gpu/drm/apple/av.c @@ -74,6 +74,8 @@ static void av_interface_teardown(struct apple_epic_service *service) struct apple_dcp *dcp = service->ep->dcp; struct audiosrv_data *asrv = dcp->audiosrv; + service->enabled = false; + mutex_lock(&asrv->plug_lock); asrv->plugged = false; diff --git a/drivers/gpu/drm/apple/epic/dpavservep.c b/drivers/gpu/drm/apple/epic/dpavservep.c index aa2cbc729a37d4..2de9d2fe4c24a3 100644 --- a/drivers/gpu/drm/apple/epic/dpavservep.c +++ b/drivers/gpu/drm/apple/epic/dpavservep.c @@ -36,6 +36,8 @@ static void dcpavserv_init(struct apple_epic_service *service, const char *name, static void dcpavserv_teardown(struct apple_epic_service *service) { struct apple_dcp *dcp = service->ep->dcp; + service->enabled = false; + if (dcp->dcpavserv.enabled) { dcp->dcpavserv.enabled = false; dcp->dcpavserv.service = NULL; @@ -51,6 +53,7 @@ static void dcpdpserv_init(struct apple_epic_service *service, const char *name, static void dcpdpserv_teardown(struct apple_epic_service *service) { + service->enabled = false; } struct dcpavserv_status_report { From 4250264bb6402b37ba131e97945f647e6db10d36 Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Tue, 14 Jan 2025 23:10:18 +0100 Subject: [PATCH 1208/3902] drm: apple: audio: Rework audio service handling 'open' and 'close' the service/link in iomfb's power-on and shutdown and on HPD deassert. This avoids leaking DCPAVAudioInterface services over display power cycles and tears the service properly down. For unknown reasons this is only observed with DCPs connected to atc phys as for DP altmode and the HDMI ports on Macbook Pros. Signed-off-by: Janne Grunau drm: apple: Rework audio service initialization Signed-off-by: Janne Grunau --- drivers/gpu/drm/apple/av.c | 96 ++++++++++++++++++++++++++++++------- drivers/gpu/drm/apple/av.h | 3 ++ drivers/gpu/drm/apple/dcp.c | 20 ++++++++ 3 files changed, 101 insertions(+), 18 deletions(-) diff --git a/drivers/gpu/drm/apple/av.c b/drivers/gpu/drm/apple/av.c index f498271da9081c..0d3c752f62d5f5 100644 --- a/drivers/gpu/drm/apple/av.c +++ b/drivers/gpu/drm/apple/av.c @@ -13,12 +13,14 @@ #include "audio.h" #include "afk.h" +#include "av.h" #include "dcp.h" #include "dcp-internal.h" struct dcp_av_audio_cmds { /* commands in group 0*/ u32 open; + u32 close; u32 prepare; u32 start_link; u32 stop_link; @@ -30,6 +32,7 @@ struct dcp_av_audio_cmds { static const struct dcp_av_audio_cmds dcp_av_audio_cmds_v12_3 = { .open = 6, + .close = 7, .prepare = 8, .start_link = 9, .stop_link = 12, @@ -40,6 +43,7 @@ static const struct dcp_av_audio_cmds dcp_av_audio_cmds_v12_3 = { static const struct dcp_av_audio_cmds dcp_av_audio_cmds_v13_5 = { .open = 4, + .close = 5, .prepare = 6, .start_link = 7, .stop_link = 10, @@ -62,6 +66,7 @@ struct audiosrv_data { bool warned_get_elements; bool warned_get_product_attrs; + bool is_open; }; static void av_interface_init(struct apple_epic_service *service, const char *name, @@ -285,34 +290,89 @@ static const struct apple_epic_service_ops avep_ops[] = { {} }; -static void av_work_service_start(struct work_struct *work) +void av_service_connect(struct apple_dcp *dcp) { + struct apple_epic_service *service; + struct audiosrv_data *asrv = dcp->audiosrv; int ret; - struct audiosrv_data *audiosrv_data; - struct apple_dcp *dcp; - audiosrv_data = container_of(work, struct audiosrv_data, start_av_service_wq); - if (!audiosrv_data->srv || - !audiosrv_data->srv->ep || - !audiosrv_data->srv->ep->dcp) { - pr_err("%s: dcp: av: NULL ptr during startup\n", __func__); - return; + scoped_guard(rwsem_write, &asrv->srv_rwsem) { + if (!asrv->srv) + return; + service = asrv->srv; } - dcp = audiosrv_data->srv->ep->dcp; /* open AV audio service */ - dev_info(dcp->dev, "%s: starting audio service\n", __func__); - ret = afk_service_call(dcp->audiosrv->srv, 0, dcp->audiosrv->cmds.open, - NULL, 0, 32, NULL, 0, 32); + dev_info(dcp->dev, "%s: starting audio service, plugged:%d\n", __func__, asrv->plugged); + if (asrv->is_open) + return; + + ret = afk_service_call(service, 0, asrv->cmds.open, NULL, 0, 32, + NULL, 0, 32); if (ret) { dev_err(dcp->dev, "error opening audio service: %d\n", ret); return; } + mutex_lock(&asrv->plug_lock); + asrv->is_open = true; - mutex_lock(&dcp->audiosrv->plug_lock); - if (dcp->audiosrv->audio_dev) - dcpaud_connect(dcp->audiosrv->audio_dev, dcp->audiosrv->plugged); - mutex_unlock(&dcp->audiosrv->plug_lock); + if (asrv->audio_dev) + dcpaud_connect(asrv->audio_dev, asrv->plugged); + mutex_unlock(&asrv->plug_lock); +} + +void av_service_disconnect(struct apple_dcp *dcp) +{ + struct apple_epic_service *service; + struct audiosrv_data *asrv = dcp->audiosrv; + int ret; + + scoped_guard(rwsem_write, &asrv->srv_rwsem) { + if (!asrv->srv) + return; + service = asrv->srv; + } + + /* close AV audio service */ + dev_info(dcp->dev, "%s: stopping audio service\n", __func__); + if (!asrv->is_open) + return; + + mutex_lock(&asrv->plug_lock); + + if (asrv->audio_dev) + dcpaud_disconnect(asrv->audio_dev); + + mutex_unlock(&asrv->plug_lock); + + ret = afk_service_call(service, 0, asrv->cmds.close, NULL, 0, 16, + NULL, 0, 16); + if (ret) { + dev_err(dcp->dev, "error closing audio service: %d\n", ret); + } + if (service->torndown) + service->enabled = false; + asrv->is_open = false; +} + +static void av_work_service_start(struct work_struct *work) +{ + struct audiosrv_data *audiosrv_data; + struct apple_dcp *dcp; + + audiosrv_data = container_of(work, struct audiosrv_data, start_av_service_wq); + + scoped_guard(rwsem_read, &audiosrv_data->srv_rwsem) { + if (!audiosrv_data->srv || + !audiosrv_data->srv->ep || + !audiosrv_data->srv->ep->dcp) { + pr_err("%s: dcp: av: NULL ptr during startup\n", __func__); + return; + } + dcp = audiosrv_data->srv->ep->dcp; + } + + av_service_connect(dcp); } int avep_init(struct apple_dcp *dcp) @@ -339,9 +399,9 @@ int avep_init(struct apple_dcp *dcp) dev_err(dcp->dev, "Audio not supported for firmware\n"); return -ENODEV; } - INIT_WORK(&audiosrv_data->start_av_service_wq, av_work_service_start); dcp->audiosrv = audiosrv_data; + INIT_WORK(&audiosrv_data->start_av_service_wq, av_work_service_start); endpoint = of_graph_get_endpoint_by_regs(dev->of_node, 0, 0); if (endpoint) { diff --git a/drivers/gpu/drm/apple/av.h b/drivers/gpu/drm/apple/av.h index b1f92fb5d07f90..baeefeca0a334d 100644 --- a/drivers/gpu/drm/apple/av.h +++ b/drivers/gpu/drm/apple/av.h @@ -6,4 +6,7 @@ //int avep_audiosrv_startlink(struct apple_dcp *dcp, struct dcp_sound_cookie *cookie); //int avep_audiosrv_stoplink(struct apple_dcp *dcp); +void av_service_connect(struct apple_dcp *dcp); +void av_service_disconnect(struct apple_dcp *dcp); + #endif /* __AV_H__ */ diff --git a/drivers/gpu/drm/apple/dcp.c b/drivers/gpu/drm/apple/dcp.c index deb3e2e6f94f4f..4a9869d53a8c8b 100644 --- a/drivers/gpu/drm/apple/dcp.c +++ b/drivers/gpu/drm/apple/dcp.c @@ -31,6 +31,7 @@ #include #include "afk.h" +#include "av.h" #include "dcp.h" #include "dcp-internal.h" #include "iomfb.h" @@ -413,6 +414,9 @@ static int dcp_dptx_connect(struct apple_dcp *dcp, u32 port) if (dcp->connector_type == DRM_MODE_CONNECTOR_DisplayPort) dptxport_set_hpd(dcp->dptxport[port].service, true); + if (dcp->avep) + av_service_connect(dcp); + return 0; out_unlock: @@ -445,6 +449,9 @@ int dcp_dptx_disconnect_oob(struct platform_device *pdev, u32 port) { struct apple_dcp *dcp = platform_get_drvdata(pdev); + if (dcp->avep) + av_service_disconnect(dcp); + if (dcp->dptxport[port].enabled) dptxport_set_hpd(dcp->dptxport[port].service, false); @@ -632,6 +639,9 @@ void dcp_poweron(struct platform_device *pdev) WARN_ONCE(true, "Unexpected firmware version: %u\n", dcp->fw_compat); break; } + + if (dcp->avep) + av_service_connect(dcp); } EXPORT_SYMBOL(dcp_poweron); @@ -639,6 +649,9 @@ void dcp_poweroff(struct platform_device *pdev) { struct apple_dcp *dcp = platform_get_drvdata(pdev); + if (dcp->avep) + av_service_disconnect(dcp); + switch (dcp->fw_compat) { case DCP_FIRMWARE_V_12_3: iomfb_poweroff_v12_3(dcp); @@ -1077,6 +1090,7 @@ static void dcp_comp_unbind(struct device *dev, struct device *main, void *data) disable_irq(dcp->hdmi_hpd_irq); if (dcp->avep) { + av_service_disconnect(dcp); afk_shutdown(dcp->avep); dcp->avep = NULL; } @@ -1233,6 +1247,9 @@ static int dcp_platform_suspend(struct device *dev) { struct apple_dcp *dcp = dev_get_drvdata(dev); + if (dcp->avep) + av_service_disconnect(dcp); + if (dcp->hdmi_hpd_irq) { disable_irq(dcp->hdmi_hpd_irq); dcp_dptx_disconnect(dcp, 0); @@ -1261,6 +1278,9 @@ static int dcp_platform_resume(struct device *dev) dcp_dptx_connect(dcp, 0); } + if (dcp->avep) + av_service_connect(dcp); + return 0; } From 775ab4437ac28cbb4184e66b2d1c6b1e7163b56c Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Mon, 27 Jan 2025 22:47:06 +0100 Subject: [PATCH 1209/3902] drm: apple: iomfb: Adapt `IOMFB_METHOD` for gcc 15 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fixes "error: initializer-string for array of ‘char’ is too long" errors while compiling with Fedora's gcc 15. Signed-off-by: Janne Grunau --- drivers/gpu/drm/apple/iomfb.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/gpu/drm/apple/iomfb.h b/drivers/gpu/drm/apple/iomfb.h index 025f292d532a77..c92d4c087168c1 100644 --- a/drivers/gpu/drm/apple/iomfb.h +++ b/drivers/gpu/drm/apple/iomfb.h @@ -199,7 +199,7 @@ enum dcpep_method { dcpep_num_methods }; -#define IOMFB_METHOD(tag, name) [name] = { #name, tag } +#define IOMFB_METHOD(tag, name) [name] = { #name, { tag[0], tag[1], tag[2], tag[3] } } struct dcp_method_entry { const char *name; From 8322ff2cc38a0f25968e3fa1f64baf192db5b404 Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Tue, 21 Jan 2025 20:20:20 +0100 Subject: [PATCH 1210/3902] drm: apple: dptx: Rework/document get_max_lane_count() phy_validate() on the DP only ATC phy returns 0 lanes if it happens before the phy_set_mode(PHY_MODE_DP). Since this is the only known case default to 4 lanes as the phy is used exclusively for DP. Fixes: https://github.com/AsahiLinux/linux/issues/367 Signed-off-by: Janne Grunau --- drivers/gpu/drm/apple/dptxep.c | 27 ++++++++++++++++----------- 1 file changed, 16 insertions(+), 11 deletions(-) diff --git a/drivers/gpu/drm/apple/dptxep.c b/drivers/gpu/drm/apple/dptxep.c index 2669cbecd05ffa..e1723f16aa58ff 100644 --- a/drivers/gpu/drm/apple/dptxep.c +++ b/drivers/gpu/drm/apple/dptxep.c @@ -250,26 +250,31 @@ static int dptxport_call_get_max_lane_count(struct apple_epic_service *service, { struct dptxport_apcall_lane_count *reply = reply_; struct dptx_port *dptx = service->cookie; + struct apple_dcp *dcp = service->ep->dcp; union phy_configure_opts phy_ops; int ret; if (reply_size < sizeof(*reply)) return -EINVAL; - reply->retcode = cpu_to_le32(0); - reply->lane_count = cpu_to_le64(2); - ret = phy_validate(dptx->atcphy, PHY_MODE_DP, 0, &phy_ops); - if (ret < 0 || phy_ops.dp.lanes < 2) { - // phy_validate might return 0 lines if atc-phy is not yet - // switched to DP alt mode - dev_dbg(service->ep->dcp->dev, "get_max_lane_count: " - "phy_validate ret:%d lanes:%d\n", ret, phy_ops.dp.lanes); - dptx->lane_count = 0; + if (ret < 0) { + dev_err(dcp->dev, "phy_validate failed: %d\n", ret); + reply->retcode = cpu_to_le32(1); + reply->lane_count = cpu_to_le64(0); } else { + if (phy_ops.dp.lanes < 2) { + // phy_validate might return 0 lanes if atc phy is not + // yet switched to DP mode + dev_dbg(dcp->dev, "get_max_lane_count: phy lanes: %d\n", + phy_ops.dp.lanes); + // default to 4 lanes + dptx->lane_count = 4; + } else { + dptx->lane_count = phy_ops.dp.lanes; + } reply->retcode = cpu_to_le32(0); - reply->lane_count = cpu_to_le64(phy_ops.dp.lanes); - dptx->lane_count = phy_ops.dp.lanes; + reply->lane_count = cpu_to_le64(dptx->lane_count); } return 0; From 08cf298027245d9815bdf52b32dac00131e0b5c3 Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Tue, 21 Jan 2025 20:32:16 +0100 Subject: [PATCH 1211/3902] drm: apple: HDMI: Check HPD state before enabling the IRQ The HPD IRQ is edge triggered so its state needs to queried explicitly to detect the current state. Signed-off-by: Janne Grunau --- drivers/gpu/drm/apple/dcp.c | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/drivers/gpu/drm/apple/dcp.c b/drivers/gpu/drm/apple/dcp.c index 4a9869d53a8c8b..33168a718d15ab 100644 --- a/drivers/gpu/drm/apple/dcp.c +++ b/drivers/gpu/drm/apple/dcp.c @@ -569,6 +569,15 @@ EXPORT_SYMBOL(dcp_start); static int dcp_enable_dp2hdmi_hpd(struct apple_dcp *dcp) { + // check HPD state before enabling the edge triggered IRQ + if (dcp->hdmi_hpd) { + bool connected = gpiod_get_value_cansleep(dcp->hdmi_hpd); + dev_info(dcp->dev, "%s: DP2HDMI HPD connected:%d\n", __func__, connected); + + if (connected) + dcp_dptx_connect(dcp, 0); + } + if (dcp->hdmi_hpd_irq) enable_irq(dcp->hdmi_hpd_irq); From 07b105c641b51490678dcdf2dfa23070593fb2e3 Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Tue, 21 Jan 2025 23:35:27 +0100 Subject: [PATCH 1212/3902] drm: apple: dptx: Configure number of lanes for dptx-phy Configuring the number of lanes is required for M2* desktop devices either if those were not initialized by m1n1 or after hotplug. Fixes: 07e4bfb1599bc ("drm: apple: dptx: Tidy up lane count handling") Signed-off-by: Janne Grunau --- drivers/gpu/drm/apple/dptxep.c | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/drivers/gpu/drm/apple/dptxep.c b/drivers/gpu/drm/apple/dptxep.c index e1723f16aa58ff..e6e863dea76887 100644 --- a/drivers/gpu/drm/apple/dptxep.c +++ b/drivers/gpu/drm/apple/dptxep.c @@ -306,6 +306,9 @@ static int dptxport_call_set_active_lane_count(struct apple_epic_service *servic case 0 ... 2: case 4: dptx->phy_ops.dp.lanes = lane_count; + // Use dptx phy index > 3 as indication for dptx-phy or + // lpdptx-phy and configure the number of lanes for those + dptx->phy_ops.dp.set_lanes = (dcp->dptx_phy > 3); break; default: dev_err(dcp->dev, "set_active_lane_count: invalid lane count:%llu\n", lane_count); @@ -314,6 +317,16 @@ static int dptxport_call_set_active_lane_count(struct apple_epic_service *servic break; } + if (dptx->phy_ops.dp.set_lanes) { + if (dptx->atcphy) { + ret = phy_configure(dptx->atcphy, &dptx->phy_ops); + if (ret) + return ret; + } + dptx->phy_ops.dp.set_lanes = 0; + dptx->lane_count = lane_count; + } + reply->retcode = cpu_to_le32(retcode); reply->lane_count = cpu_to_le64(lane_count); From 0d99d0fec6a57084adedf421eece28561485f3fc Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Sun, 9 Feb 2025 21:29:55 +0100 Subject: [PATCH 1213/3902] drm: apple: dptx: Issue HPD event early on gpio/type-c disconnect Atomic modesets during a display disconnect may result in unrecoverable state if the set_digital_out_mode() DCP firmware call fails. Mark the connector as early as possible as disconnected to make this more unlikely. TODO: investigate set_digital_out_mode() failure handling Signed-off-by: Janne Grunau --- drivers/gpu/drm/apple/dcp.c | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/drivers/gpu/drm/apple/dcp.c b/drivers/gpu/drm/apple/dcp.c index 33168a718d15ab..91f4cba0036c92 100644 --- a/drivers/gpu/drm/apple/dcp.c +++ b/drivers/gpu/drm/apple/dcp.c @@ -424,6 +424,14 @@ static int dcp_dptx_connect(struct apple_dcp *dcp, u32 port) return ret; } +static void disconnected_hpd_event(struct apple_connector *con) +{ + if (con) { + con->connected = 0; + drm_kms_helper_connector_hotplug_event(&con->base); + } +} + static int dcp_dptx_disconnect(struct apple_dcp *dcp, u32 port) { dev_info(dcp->dev, "%s(port=%d)\n", __func__, port); @@ -449,6 +457,8 @@ int dcp_dptx_disconnect_oob(struct platform_device *pdev, u32 port) { struct apple_dcp *dcp = platform_get_drvdata(pdev); + disconnected_hpd_event(dcp->connector); + if (dcp->avep) av_service_disconnect(dcp); @@ -675,8 +685,10 @@ void dcp_poweroff(struct platform_device *pdev) if (dcp->hdmi_hpd) { bool connected = gpiod_get_value_cansleep(dcp->hdmi_hpd); - if (!connected) + if (!connected) { + disconnected_hpd_event(dcp->connector); dcp_dptx_disconnect(dcp, 0); + } } } EXPORT_SYMBOL(dcp_poweroff); @@ -1261,6 +1273,7 @@ static int dcp_platform_suspend(struct device *dev) if (dcp->hdmi_hpd_irq) { disable_irq(dcp->hdmi_hpd_irq); + disconnected_hpd_event(dcp->connector); dcp_dptx_disconnect(dcp, 0); } /* From 84048dff1f6d63a839bb8e7eff2a17be24d1af71 Mon Sep 17 00:00:00 2001 From: Alyssa Rosenzweig Date: Mon, 17 Feb 2025 11:45:49 -0500 Subject: [PATCH 1214/3902] drm/apple: fix audioless build Signed-off-by: Alyssa Rosenzweig --- drivers/gpu/drm/apple/av.h | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/drivers/gpu/drm/apple/av.h b/drivers/gpu/drm/apple/av.h index baeefeca0a334d..c00cbef549fd2e 100644 --- a/drivers/gpu/drm/apple/av.h +++ b/drivers/gpu/drm/apple/av.h @@ -6,7 +6,12 @@ //int avep_audiosrv_startlink(struct apple_dcp *dcp, struct dcp_sound_cookie *cookie); //int avep_audiosrv_stoplink(struct apple_dcp *dcp); +#if IS_ENABLED(CONFIG_DRM_APPLE_AUDIO) void av_service_connect(struct apple_dcp *dcp); void av_service_disconnect(struct apple_dcp *dcp); +#else +static inline void av_service_connect(struct apple_dcp *dcp) { } +static inline void av_service_disconnect(struct apple_dcp *dcp) { } +#endif #endif /* __AV_H__ */ From 395b3ef6a650fce4edbc654eff530b99a6072f81 Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Wed, 9 Apr 2025 18:40:16 +0200 Subject: [PATCH 1215/3902] drm: apple: Use piodma default iommu domain Required to keep the bootloader mappings. iommu_paging_domain_alloc() will end up with an empty domain and remapping the boot loader mapping from reserved-memory is quite verbose to type. Signed-off-by: Janne Grunau --- drivers/gpu/drm/apple/dcp.c | 19 ++++++------------- 1 file changed, 6 insertions(+), 13 deletions(-) diff --git a/drivers/gpu/drm/apple/dcp.c b/drivers/gpu/drm/apple/dcp.c index 91f4cba0036c92..84fdff9fbedb4f 100644 --- a/drivers/gpu/drm/apple/dcp.c +++ b/drivers/gpu/drm/apple/dcp.c @@ -751,22 +751,16 @@ static int dcp_create_piodma_iommu_dev(struct apple_dcp *dcp) } of_node_put(node); - dcp->iommu_dom = iommu_paging_domain_alloc(&dcp->piodma->dev); + dcp->iommu_dom = iommu_get_domain_for_dev(&dcp->piodma->dev); if (IS_ERR(dcp->iommu_dom)) { - ret = PTR_ERR(dcp->iommu_dom); + ret = dev_err_probe(dcp->dev, PTR_ERR(dcp->iommu_dom), + "Failed to get default iommu domain for " + "piodma device\n"); + dcp->iommu_dom = NULL; goto err_destroy_pdev; } - ret = iommu_attach_device(dcp->iommu_dom, &dcp->piodma->dev); - if (ret) { - ret = dev_err_probe(dcp->dev, ret, - "Failed to attach IOMMU child domain\n"); - goto err_free_domain; - } - return 0; -err_free_domain: - iommu_domain_free(dcp->iommu_dom); err_destroy_pdev: of_node_put(node); of_platform_device_destroy(&dcp->piodma->dev, NULL); @@ -1140,8 +1134,7 @@ static void dcp_comp_unbind(struct device *dev, struct device *main, void *data) iomfb_shutdown(dcp); if (dcp->piodma) { - iommu_detach_device(dcp->iommu_dom, &dcp->piodma->dev); - iommu_domain_free(dcp->iommu_dom); + dcp->iommu_dom = NULL; of_platform_device_destroy(&dcp->piodma->dev, NULL); dcp->piodma = NULL; } From b7b037e17bded423ffb67ceaf8c6e6ce641598c7 Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Sun, 25 May 2025 17:39:50 +0200 Subject: [PATCH 1216/3902] drm: dcp: Adjust .mode_valid signature Signed-off-by: Janne Grunau --- drivers/gpu/drm/apple/dcp.h | 4 ++-- drivers/gpu/drm/apple/iomfb.c | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/drivers/gpu/drm/apple/dcp.h b/drivers/gpu/drm/apple/dcp.h index e34bc495fd3973..e708d1c44169aa 100644 --- a/drivers/gpu/drm/apple/dcp.h +++ b/drivers/gpu/drm/apple/dcp.h @@ -43,8 +43,8 @@ bool dcp_is_initialized(struct platform_device *pdev); void apple_crtc_vblank(struct apple_crtc *apple); void dcp_drm_crtc_vblank(struct apple_crtc *crtc); int dcp_get_modes(struct drm_connector *connector); -int dcp_mode_valid(struct drm_connector *connector, - struct drm_display_mode *mode); +enum drm_mode_status dcp_mode_valid(struct drm_connector *connector, + const struct drm_display_mode *mode); int dcp_crtc_atomic_modeset(struct drm_crtc *crtc, struct drm_atomic_state *state); bool dcp_crtc_mode_fixup(struct drm_crtc *crtc, diff --git a/drivers/gpu/drm/apple/iomfb.c b/drivers/gpu/drm/apple/iomfb.c index 398118933e801e..5b0f97253e3fc0 100644 --- a/drivers/gpu/drm/apple/iomfb.c +++ b/drivers/gpu/drm/apple/iomfb.c @@ -431,8 +431,8 @@ struct dcp_display_mode *lookup_mode(struct apple_dcp *dcp, return NULL; } -int dcp_mode_valid(struct drm_connector *connector, - struct drm_display_mode *mode) +enum drm_mode_status dcp_mode_valid(struct drm_connector *connector, + const struct drm_display_mode *mode) { struct apple_connector *apple_connector = to_apple_connector(connector); struct platform_device *pdev = apple_connector->dcp; From 7410819f7c9864ebd139fce63cdbded43c03c097 Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Sat, 26 Jul 2025 15:44:57 +0200 Subject: [PATCH 1217/3902] drm: apple: Support sync objects Both mutter[0] and KWin[1] are using the KMS drm device for explicit sync in their screen casting implementation. This fails in both cases since the KMS device does not provide DRM_CAP_SYNCOBJ_TIMELINE. Support for this is implemented in generic DRM so setting the two necessary feature flags. 0: https://gitlab.gnome.org/GNOME/mutter/-/issues/4224 1: https://invent.kde.org/plasma/kwin/-/merge_requests/7941 Signed-off-by: Janne Grunau --- drivers/gpu/drm/apple/apple_drv.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/gpu/drm/apple/apple_drv.c b/drivers/gpu/drm/apple/apple_drv.c index b4527d6f9ce110..fd4201a4036cd1 100644 --- a/drivers/gpu/drm/apple/apple_drv.c +++ b/drivers/gpu/drm/apple/apple_drv.c @@ -73,7 +73,7 @@ static const struct drm_driver apple_drm_driver = { .desc = DRIVER_DESC, .major = 1, .minor = 0, - .driver_features = DRIVER_MODESET | DRIVER_GEM | DRIVER_ATOMIC, + .driver_features = DRIVER_MODESET | DRIVER_GEM | DRIVER_ATOMIC | DRIVER_SYNCOBJ | DRIVER_SYNCOBJ_TIMELINE, .fops = &apple_fops, }; From 1940ec6b5a55bb06cc1c11b2ac8f0a6ecbe1c6ff Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Mon, 25 Aug 2025 22:14:01 +0200 Subject: [PATCH 1218/3902] drm: apple: Remove conflicting devices as late as possible Call aperture_remove_conflicting_devices() just before drm_dev_register(). This reduces the the time at startup without KMS drm device to a minimum. sddm/kwin(-wayland) fails with "kwin_wayland_drm: No suitable DRM devices have been found" in this case and never retries. Reverts commit "drm/apple: Remove simpledrm framebuffer before DRM device alloc". User space needs to deal with KMS device not being card0. The attempt to take card0 over from simpledrm was futile as the GPU driver is racing for this and won in many cases. Signed-off-by: Janne Grunau --- drivers/gpu/drm/apple/apple_drv.c | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/drivers/gpu/drm/apple/apple_drv.c b/drivers/gpu/drm/apple/apple_drv.c index fd4201a4036cd1..345bcf4eb769fe 100644 --- a/drivers/gpu/drm/apple/apple_drv.c +++ b/drivers/gpu/drm/apple/apple_drv.c @@ -624,14 +624,6 @@ static int apple_drm_init(struct device *dev) if (ret) return ret; - fb_size = fb_r.end - fb_r.start + 1; - ret = aperture_remove_conflicting_devices(fb_r.start, fb_size, - apple_drm_driver.name); - if (ret) { - dev_err(dev, "Failed remove fb: %d\n", ret); - goto err_unbind; - } - apple = devm_drm_dev_alloc(dev, &apple_drm_driver, struct apple_drm_private, drm); if (IS_ERR(apple)) @@ -673,6 +665,14 @@ static int apple_drm_init(struct device *dev) drm_mode_config_reset(&apple->drm); + fb_size = fb_r.end - fb_r.start + 1; + ret = aperture_remove_conflicting_devices(fb_r.start, fb_size, + apple_drm_driver.name); + if (ret) { + dev_err(dev, "Failed remove fb: %d\n", ret); + goto err_unbind; + } + ret = drm_dev_register(&apple->drm, 0); if (ret) goto err_unbind; From 03b5e181a1ab7d690d324188ca2104bd9bd6c691 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20Povi=C5=A1er?= Date: Thu, 23 Feb 2023 17:57:56 +0100 Subject: [PATCH 1219/3902] HACK: ALSA: Export 'snd_pcm_known_rates' MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Martin Povišer --- sound/core/pcm_native.c | 1 + 1 file changed, 1 insertion(+) diff --git a/sound/core/pcm_native.c b/sound/core/pcm_native.c index 68bee40c9adafd..13ad5e9d23cce9 100644 --- a/sound/core/pcm_native.c +++ b/sound/core/pcm_native.c @@ -2452,6 +2452,7 @@ const struct snd_pcm_hw_constraint_list snd_pcm_known_rates = { .count = ARRAY_SIZE(rates), .list = rates, }; +EXPORT_SYMBOL_GPL(snd_pcm_known_rates); static int snd_pcm_hw_rule_rate(struct snd_pcm_hw_params *params, struct snd_pcm_hw_rule *rule) From ca49a7369f42b94ab98e69dcb152184e6f3a130f Mon Sep 17 00:00:00 2001 From: Oliver Bestmann Date: Tue, 16 Dec 2025 11:59:22 +0100 Subject: [PATCH 1220/3902] drm: apple: set timestamps for 120hz The dcp does not seem to care much about the values in ts1, ts2 and ts3, as long as they are non zero. This commit fills the timestamp with a dummy value of 120 if a refresh-rate of 120hz is selected. This is enough to get a refresh rate of 120hz. MacOS also sets flags1 and flags2. I have no idea what exactly those values indicate, but I did do not need to set any of them to get 120hz. Signed-off-by: Oliver Bestmann --- drivers/gpu/drm/apple/dcp-internal.h | 1 + drivers/gpu/drm/apple/iomfb_template.c | 13 +++++++++++++ drivers/gpu/drm/apple/iomfb_template.h | 9 ++++++++- drivers/gpu/drm/apple/parser.c | 14 +------------- drivers/gpu/drm/apple/parser.h | 1 + 5 files changed, 24 insertions(+), 14 deletions(-) diff --git a/drivers/gpu/drm/apple/dcp-internal.h b/drivers/gpu/drm/apple/dcp-internal.h index 2c31d2a8cef09d..3b6a14339cddef 100644 --- a/drivers/gpu/drm/apple/dcp-internal.h +++ b/drivers/gpu/drm/apple/dcp-internal.h @@ -184,6 +184,7 @@ struct apple_dcp { /* Current display mode */ bool during_modeset; bool valid_mode; + s64 precise_sync_rate; struct dcp_set_digital_out_mode_req mode; /* completion for active turning true */ diff --git a/drivers/gpu/drm/apple/iomfb_template.c b/drivers/gpu/drm/apple/iomfb_template.c index 0a1b9495128562..94f50768add034 100644 --- a/drivers/gpu/drm/apple/iomfb_template.c +++ b/drivers/gpu/drm/apple/iomfb_template.c @@ -1219,6 +1219,9 @@ int DCP_FW_NAME(iomfb_modeset)(struct apple_dcp *dcp, .timing_mode_id = mode->timing_mode_id }; + /* keep around to fake timestamps in dcp_swap_submit */ + dcp->precise_sync_rate = mode->precise_sync_rate; + cookie = kzalloc(sizeof(*cookie), GFP_KERNEL); if (!cookie) { return -ENOMEM; @@ -1408,6 +1411,16 @@ void DCP_FW_NAME(iomfb_flush)(struct apple_dcp *dcp, struct drm_crtc *crtc, stru req->clear = 1; } + if (has_surface && dcp->main_display && (dcp->precise_sync_rate >> 16) == 120) { + /* + * Fake timstamps to get 120hz refresh rate. It looks + * like the actual value does not matter, as long as it is non zero. + */ + req->swap.ts1 = 120; + req->swap.ts2 = 120; + req->swap.ts3 = 120; + } + /* These fields should be set together */ req->swap.swap_completed = req->swap.swap_enabled; diff --git a/drivers/gpu/drm/apple/iomfb_template.h b/drivers/gpu/drm/apple/iomfb_template.h index f446a4d8f38b90..878ffdf9e644cd 100644 --- a/drivers/gpu/drm/apple/iomfb_template.h +++ b/drivers/gpu/drm/apple/iomfb_template.h @@ -18,7 +18,14 @@ struct DCP_FW_NAME(dcp_swap) { u64 ts1; u64 ts2; - u64 unk_10[6]; + + u64 unk_10; + u64 unk_18; + u64 ts64_unk; + u64 unk_28; + u64 ts3; + u64 unk_38; + u64 flags1; u64 flags2; diff --git a/drivers/gpu/drm/apple/parser.c b/drivers/gpu/drm/apple/parser.c index 700a31883eac8e..e44b4e17758a55 100644 --- a/drivers/gpu/drm/apple/parser.c +++ b/drivers/gpu/drm/apple/parser.c @@ -499,19 +499,6 @@ static int parse_mode(struct dcp_parse_ctx *handle, if (is_virtual) return -EINVAL; - /* - * HACK: - * Ignore the 120 Hz mode on j314/j316 (identified by resolution). - * DCP limits normal swaps to 60 Hz anyway and the 120 Hz mode might - * cause choppiness with X11. - * Just downscoring it and thus making the 60 Hz mode the preferred mode - * seems not enough for some user space. - */ - if (vert.precise_sync_rate >> 16 == 120 && - ((horiz.active == 3024 && vert.active == 1964) || - (horiz.active == 3456 && vert.active == 2234))) - return -EINVAL; - vert.active -= notch_height; vert.sync_width += notch_height; @@ -539,6 +526,7 @@ static int parse_mode(struct dcp_parse_ctx *handle, out->timing_mode_id = id; out->color_mode_id = best_color_mode; + out->precise_sync_rate = vert.precise_sync_rate; trace_iomfb_timing_mode(handle->dcp, id, *score, horiz.active, vert.active, vert.precise_sync_rate, diff --git a/drivers/gpu/drm/apple/parser.h b/drivers/gpu/drm/apple/parser.h index 2f52e063bbd426..a8add87a3d438c 100644 --- a/drivers/gpu/drm/apple/parser.h +++ b/drivers/gpu/drm/apple/parser.h @@ -87,6 +87,7 @@ struct dcp_display_mode { struct drm_display_mode mode; u32 color_mode_id; u32 timing_mode_id; + s64 precise_sync_rate; struct dcp_color_mode sdr_rgb; struct dcp_color_mode sdr_444; struct dcp_color_mode sdr; From bd6a8f6ec16c7e91b9d17272d2ac6f81e187d946 Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Sun, 21 Dec 2025 11:01:16 +0100 Subject: [PATCH 1221/3902] fixup! drm: apple: set timestamps for 120hz Signed-off-by: Janne Grunau --- drivers/gpu/drm/apple/dcp-internal.h | 2 +- drivers/gpu/drm/apple/iomfb_template.c | 10 +++++----- drivers/gpu/drm/apple/parser.c | 13 ++++++++++++- drivers/gpu/drm/apple/parser.h | 2 +- 4 files changed, 19 insertions(+), 8 deletions(-) diff --git a/drivers/gpu/drm/apple/dcp-internal.h b/drivers/gpu/drm/apple/dcp-internal.h index 3b6a14339cddef..420e68f2e8e174 100644 --- a/drivers/gpu/drm/apple/dcp-internal.h +++ b/drivers/gpu/drm/apple/dcp-internal.h @@ -184,7 +184,7 @@ struct apple_dcp { /* Current display mode */ bool during_modeset; bool valid_mode; - s64 precise_sync_rate; + bool use_timestamps; struct dcp_set_digital_out_mode_req mode; /* completion for active turning true */ diff --git a/drivers/gpu/drm/apple/iomfb_template.c b/drivers/gpu/drm/apple/iomfb_template.c index 94f50768add034..373d58f171240c 100644 --- a/drivers/gpu/drm/apple/iomfb_template.c +++ b/drivers/gpu/drm/apple/iomfb_template.c @@ -1210,17 +1210,17 @@ int DCP_FW_NAME(iomfb_modeset)(struct apple_dcp *dcp, if (cmode) dev_info(dcp->dev, "set_digital_out_mode() color mode depth:%hhu format:%u " - "colorimetry:%u eotf:%u range:%u\n", cmode->depth, + "colorimetry:%u eotf:%u range:%u vrr:%u\n", cmode->depth, cmode->format, cmode->colorimetry, cmode->eotf, - cmode->range); + cmode->range, mode->vrr); dcp->mode = (struct dcp_set_digital_out_mode_req){ .color_mode_id = mode->color_mode_id, .timing_mode_id = mode->timing_mode_id }; - /* keep around to fake timestamps in dcp_swap_submit */ - dcp->precise_sync_rate = mode->precise_sync_rate; + /* Keep track of suspected vrr modes */ + dcp->use_timestamps = mode->vrr; cookie = kzalloc(sizeof(*cookie), GFP_KERNEL); if (!cookie) { @@ -1411,7 +1411,7 @@ void DCP_FW_NAME(iomfb_flush)(struct apple_dcp *dcp, struct drm_crtc *crtc, stru req->clear = 1; } - if (has_surface && dcp->main_display && (dcp->precise_sync_rate >> 16) == 120) { + if (has_surface && dcp->use_timestamps) { /* * Fake timstamps to get 120hz refresh rate. It looks * like the actual value does not matter, as long as it is non zero. diff --git a/drivers/gpu/drm/apple/parser.c b/drivers/gpu/drm/apple/parser.c index e44b4e17758a55..ae9965e537f1aa 100644 --- a/drivers/gpu/drm/apple/parser.c +++ b/drivers/gpu/drm/apple/parser.c @@ -499,6 +499,18 @@ static int parse_mode(struct dcp_parse_ctx *handle, if (is_virtual) return -EINVAL; + /* + * HACK: + * Mark the 120 Hz mode on j314/j316 (identified by resolution) as vrr. + * We still do not know how to drive VRR but at least seetinng timestamps + * in the the swap_surface message to non-zero values drives the display + * at 120 fps. + */ + if (vert.precise_sync_rate >> 16 == 120 && + ((horiz.active == 3024 && vert.active == 1964) || + (horiz.active == 3456 && vert.active == 2234))) + out->vrr = true; + vert.active -= notch_height; vert.sync_width += notch_height; @@ -526,7 +538,6 @@ static int parse_mode(struct dcp_parse_ctx *handle, out->timing_mode_id = id; out->color_mode_id = best_color_mode; - out->precise_sync_rate = vert.precise_sync_rate; trace_iomfb_timing_mode(handle->dcp, id, *score, horiz.active, vert.active, vert.precise_sync_rate, diff --git a/drivers/gpu/drm/apple/parser.h b/drivers/gpu/drm/apple/parser.h index a8add87a3d438c..f3cb661de9ce5e 100644 --- a/drivers/gpu/drm/apple/parser.h +++ b/drivers/gpu/drm/apple/parser.h @@ -87,11 +87,11 @@ struct dcp_display_mode { struct drm_display_mode mode; u32 color_mode_id; u32 timing_mode_id; - s64 precise_sync_rate; struct dcp_color_mode sdr_rgb; struct dcp_color_mode sdr_444; struct dcp_color_mode sdr; struct dcp_color_mode best; + bool vrr; }; struct dimension { From 1a697da422ba6be01c1800e30b5d00d22f596bb0 Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Wed, 10 Sep 2025 17:56:28 +0200 Subject: [PATCH 1222/3902] drm/apple: Unify driver into a single module Signed-off-by: Janne Grunau --- drivers/gpu/drm/apple/Makefile | 19 +++++++++--------- drivers/gpu/drm/apple/apple_drv.c | 28 ++++++++++++++++++++++++++- drivers/gpu/drm/apple/connector.c | 1 - drivers/gpu/drm/apple/dcp.c | 32 +++---------------------------- drivers/gpu/drm/apple/dcp.h | 3 +++ drivers/gpu/drm/apple/iomfb.c | 7 ------- drivers/gpu/drm/apple/parser.c | 2 -- 7 files changed, 42 insertions(+), 50 deletions(-) diff --git a/drivers/gpu/drm/apple/Makefile b/drivers/gpu/drm/apple/Makefile index 045183c63bc129..3d5faaeed7dbb6 100644 --- a/drivers/gpu/drm/apple/Makefile +++ b/drivers/gpu/drm/apple/Makefile @@ -3,20 +3,19 @@ CFLAGS_trace.o = -I$(src) appledrm-y := apple_drv.o +appledrm-y += afk.o dcp.o dcp_backlight.o dptxep.o iomfb.o parser.o systemep.o +appledrm-$(CONFIG_DRM_APPLE_AUDIO) += audio.o +appledrm-$(CONFIG_DRM_APPLE_AUDIO) += av.o +appledrm-y += connector.o +appledrm-y += ibootep.o +appledrm-y += iomfb_v12_3.o +appledrm-y += iomfb_v13_3.o +appledrm-y += epic/dpavservep.o -apple_dcp-y := afk.o dcp.o dcp_backlight.o dptxep.o iomfb.o parser.o systemep.o -apple_dcp-$(CONFIG_DRM_APPLE_AUDIO) += audio.o -apple_dcp-$(CONFIG_DRM_APPLE_AUDIO) += av.o -apple_dcp-y += connector.o -apple_dcp-y += ibootep.o -apple_dcp-y += iomfb_v12_3.o -apple_dcp-y += iomfb_v13_3.o -apple_dcp-y += epic/dpavservep.o -apple_dcp-$(CONFIG_TRACING) += trace.o +appledrm-$(CONFIG_TRACING) += trace.o obj-$(CONFIG_DRM_APPLE) += appledrm.o -obj-$(CONFIG_DRM_APPLE) += apple_dcp.o # header test diff --git a/drivers/gpu/drm/apple/apple_drv.c b/drivers/gpu/drm/apple/apple_drv.c index 345bcf4eb769fe..21c7f8c18f4531 100644 --- a/drivers/gpu/drm/apple/apple_drv.c +++ b/drivers/gpu/drm/apple/apple_drv.c @@ -812,7 +812,33 @@ static struct platform_driver apple_platform_driver = { .remove = apple_platform_remove, }; -drm_module_platform_driver(apple_platform_driver); + + +static int __init appledrm_register(void) +{ + if (drm_firmware_drivers_only()) + return -ENODEV; + +#if IS_ENABLED(CONFIG_DRM_APPLE_AUDIO) + dcp_audio_register(); +#endif + dcp_register(); + platform_driver_register(&apple_platform_driver); + + return 0; +} + +static void __exit appledrm_unregister(void) +{ +#if IS_ENABLED(CONFIG_DRM_APPLE_AUDIO) + dcp_audio_unregister(); +#endif + dcp_unregister(); + platform_driver_unregister(&apple_platform_driver); +} + +module_init(appledrm_register); +module_exit(appledrm_unregister); MODULE_AUTHOR("Alyssa Rosenzweig "); MODULE_DESCRIPTION(DRIVER_DESC); diff --git a/drivers/gpu/drm/apple/connector.c b/drivers/gpu/drm/apple/connector.c index 9e786670893387..15b3664d85631e 100644 --- a/drivers/gpu/drm/apple/connector.c +++ b/drivers/gpu/drm/apple/connector.c @@ -120,7 +120,6 @@ void apple_connector_debugfs_init(struct drm_connector *connector, struct dentry break; } } -EXPORT_SYMBOL(apple_connector_debugfs_init); static void dcp_connector_set_dict(struct apple_connector *connector, struct dcp_chunks *dict, diff --git a/drivers/gpu/drm/apple/dcp.c b/drivers/gpu/drm/apple/dcp.c index 84fdff9fbedb4f..16a82c274f3343 100644 --- a/drivers/gpu/drm/apple/dcp.c +++ b/drivers/gpu/drm/apple/dcp.c @@ -201,7 +201,6 @@ int dcp_set_crc(struct drm_crtc *crtc, bool enabled) return 0; } -EXPORT_SYMBOL_GPL(dcp_set_crc); /* * Helper to send a DRM vblank event. We do not know how call swap_submit_dcp @@ -361,7 +360,6 @@ int dcp_crtc_atomic_check(struct drm_crtc *crtc, struct drm_atomic_state *state) return 0; } -EXPORT_SYMBOL_GPL(dcp_crtc_atomic_check); int dcp_get_connector_type(struct platform_device *pdev) { @@ -369,7 +367,6 @@ int dcp_get_connector_type(struct platform_device *pdev) return (dcp->connector_type); } -EXPORT_SYMBOL_GPL(dcp_get_connector_type); #define DPTX_CONNECT_TIMEOUT msecs_to_jiffies(2000) @@ -451,7 +448,6 @@ int dcp_dptx_connect_oob(struct platform_device *pdev, u32 port) struct apple_dcp *dcp = platform_get_drvdata(pdev); return dcp_dptx_connect(dcp, port); } -EXPORT_SYMBOL_GPL(dcp_dptx_connect_oob); int dcp_dptx_disconnect_oob(struct platform_device *pdev, u32 port) { @@ -467,7 +463,6 @@ int dcp_dptx_disconnect_oob(struct platform_device *pdev, u32 port) return dcp_dptx_disconnect(dcp, port); } -EXPORT_SYMBOL_GPL(dcp_dptx_disconnect_oob); static irqreturn_t dcp_dp2hdmi_hpd(int irq, void *data) { @@ -502,7 +497,6 @@ void dcp_link(struct platform_device *pdev, struct apple_crtc *crtc, dcp->crtc = crtc; dcp->connector = connector; } -EXPORT_SYMBOL_GPL(dcp_link); int dcp_start(struct platform_device *pdev) { @@ -575,7 +569,6 @@ int dcp_start(struct platform_device *pdev) return ret; } -EXPORT_SYMBOL(dcp_start); static int dcp_enable_dp2hdmi_hpd(struct apple_dcp *dcp) { @@ -618,7 +611,6 @@ int dcp_wait_ready(struct platform_device *pdev, u64 timeout) return dcp->active ? 0 : -ETIMEDOUT; } -EXPORT_SYMBOL(dcp_wait_ready); static void __maybe_unused dcp_sleep(struct apple_dcp *dcp) { @@ -662,7 +654,6 @@ void dcp_poweron(struct platform_device *pdev) if (dcp->avep) av_service_connect(dcp); } -EXPORT_SYMBOL(dcp_poweron); void dcp_poweroff(struct platform_device *pdev) { @@ -691,7 +682,6 @@ void dcp_poweroff(struct platform_device *pdev) } } } -EXPORT_SYMBOL(dcp_poweroff); static void dcp_work_register_backlight(struct work_struct *work) { @@ -1339,28 +1329,12 @@ static struct platform_driver apple_platform_driver = { }, }; -static int __init apple_dcp_register(void) +void __init dcp_register(void) { - if (drm_firmware_drivers_only()) - return -ENODEV; - -#if IS_ENABLED(CONFIG_DRM_APPLE_AUDIO) - dcp_audio_register(); -#endif - return platform_driver_register(&apple_platform_driver); + platform_driver_register(&apple_platform_driver); } -static void __exit apple_dcp_unregister(void) +void __exit dcp_unregister(void) { platform_driver_unregister(&apple_platform_driver); -#if IS_ENABLED(CONFIG_DRM_APPLE_AUDIO) - dcp_audio_unregister(); -#endif } - -module_init(apple_dcp_register); -module_exit(apple_dcp_unregister); - -MODULE_AUTHOR("Alyssa Rosenzweig "); -MODULE_DESCRIPTION("Apple Display Controller DRM driver"); -MODULE_LICENSE("Dual MIT/GPL"); diff --git a/drivers/gpu/drm/apple/dcp.h b/drivers/gpu/drm/apple/dcp.h index e708d1c44169aa..bb93ef8c3cd5f3 100644 --- a/drivers/gpu/drm/apple/dcp.h +++ b/drivers/gpu/drm/apple/dcp.h @@ -68,6 +68,9 @@ int dpavservep_init(struct apple_dcp *dcp); int avep_init(struct apple_dcp *dcp); +void __init dcp_register(void); +void __exit dcp_unregister(void); + void __init dcp_audio_register(void); void __exit dcp_audio_unregister(void); diff --git a/drivers/gpu/drm/apple/iomfb.c b/drivers/gpu/drm/apple/iomfb.c index 5b0f97253e3fc0..3f82cdcdb0700e 100644 --- a/drivers/gpu/drm/apple/iomfb.c +++ b/drivers/gpu/drm/apple/iomfb.c @@ -258,7 +258,6 @@ void dcp_hotplug(struct work_struct *work) drm_kms_helper_connector_hotplug_event(&connector->base); } -EXPORT_SYMBOL_GPL(dcp_hotplug); static void dcpep_handle_cb(struct apple_dcp *dcp, enum dcp_context_id context, void *data, u32 length, u16 offset) @@ -413,7 +412,6 @@ int dcp_get_modes(struct drm_connector *connector) return dcp->nr_modes; } -EXPORT_SYMBOL_GPL(dcp_get_modes); /* The user may own drm_display_mode, so we need to search for our copy */ struct dcp_display_mode *lookup_mode(struct apple_dcp *dcp, @@ -440,7 +438,6 @@ enum drm_mode_status dcp_mode_valid(struct drm_connector *connector, return lookup_mode(dcp, mode) ? MODE_OK : MODE_BAD; } -EXPORT_SYMBOL_GPL(dcp_mode_valid); int dcp_crtc_atomic_modeset(struct drm_crtc *crtc, struct drm_atomic_state *state) @@ -479,7 +476,6 @@ int dcp_crtc_atomic_modeset(struct drm_crtc *crtc, return ret; } -EXPORT_SYMBOL_GPL(dcp_crtc_atomic_modeset); bool dcp_crtc_mode_fixup(struct drm_crtc *crtc, const struct drm_display_mode *mode, @@ -492,7 +488,6 @@ bool dcp_crtc_mode_fixup(struct drm_crtc *crtc, /* TODO: support synthesized modes through scaling */ return lookup_mode(dcp, mode) != NULL; } -EXPORT_SYMBOL(dcp_crtc_mode_fixup); void dcp_flush(struct drm_crtc *crtc, struct drm_atomic_state *state) @@ -527,7 +522,6 @@ void dcp_flush(struct drm_crtc *crtc, struct drm_atomic_state *state) break; } } -EXPORT_SYMBOL_GPL(dcp_flush); static void iomfb_start(struct apple_dcp *dcp) { @@ -550,7 +544,6 @@ bool dcp_is_initialized(struct platform_device *pdev) return dcp->active; } -EXPORT_SYMBOL_GPL(dcp_is_initialized); void iomfb_recv_msg(struct apple_dcp *dcp, u64 message) { diff --git a/drivers/gpu/drm/apple/parser.c b/drivers/gpu/drm/apple/parser.c index ae9965e537f1aa..8e69f639ddde5a 100644 --- a/drivers/gpu/drm/apple/parser.c +++ b/drivers/gpu/drm/apple/parser.c @@ -959,7 +959,6 @@ int parse_sound_constraints(struct dcp_parse_ctx *handle, return 0; } -EXPORT_SYMBOL_GPL(parse_sound_constraints); int parse_sound_mode(struct dcp_parse_ctx *handle, struct dcp_sound_format_mask *sieve, @@ -989,7 +988,6 @@ int parse_sound_mode(struct dcp_parse_ctx *handle, return 0; } -EXPORT_SYMBOL_GPL(parse_sound_mode); #endif int parse_system_log_mnits(struct dcp_parse_ctx *handle, struct dcp_system_ev_mnits *entry) From 6354e3da260793f90e3288781d8faf8af4807efb Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Sun, 21 Dec 2025 20:14:16 +0100 Subject: [PATCH 1223/3902] drm: apple: Use typec mux to switch atc-phy into DP The upstream atc phy driver has no longer special handling for the phy only use case on 14/16-inch Macbook Pros. So simply let dcp handle this and switch the type-c mux to full 4 lane DisplayPort mode. This requires devicetree changes in the form of a graph based connection between dcpext0 and atc-phy. Signed-off-by: Janne Grunau --- drivers/gpu/drm/apple/apple_drv.c | 11 +++++++++++ drivers/gpu/drm/apple/dcp-internal.h | 2 ++ drivers/gpu/drm/apple/dcp.c | 26 ++++++++++++++++++++++++++ drivers/gpu/drm/apple/dptxep.c | 4 +++- 4 files changed, 42 insertions(+), 1 deletion(-) diff --git a/drivers/gpu/drm/apple/apple_drv.c b/drivers/gpu/drm/apple/apple_drv.c index 21c7f8c18f4531..6bb65833077954 100644 --- a/drivers/gpu/drm/apple/apple_drv.c +++ b/drivers/gpu/drm/apple/apple_drv.c @@ -735,6 +735,17 @@ static int add_dcp_components(struct device *dev, continue; } #endif + + /* + * The ATC phy driver is not part of the component + * collection for the Apple display-subsystem so + * ignore it here. + */ + if (of_device_is_compatible(port, "apple,t8103-atcphy")) { + of_node_put(port); + continue; + } + if (of_device_is_available(port)) drm_of_component_match_add(dev, matchptr, component_compare_of, diff --git a/drivers/gpu/drm/apple/dcp-internal.h b/drivers/gpu/drm/apple/dcp-internal.h index 420e68f2e8e174..083d044f4f5791 100644 --- a/drivers/gpu/drm/apple/dcp-internal.h +++ b/drivers/gpu/drm/apple/dcp-internal.h @@ -12,6 +12,7 @@ #include #include #include +#include #include "dptxep.h" #include "iomfb.h" @@ -252,6 +253,7 @@ struct apple_dcp { /* these fields are output port specific */ struct phy *phy; struct mux_control *xbar; + struct typec_mux *typec_mux; struct gpio_desc *hdmi_hpd; struct gpio_desc *hdmi_pwren; diff --git a/drivers/gpu/drm/apple/dcp.c b/drivers/gpu/drm/apple/dcp.c index 16a82c274f3343..e7ff930d4bdc1c 100644 --- a/drivers/gpu/drm/apple/dcp.c +++ b/drivers/gpu/drm/apple/dcp.c @@ -21,6 +21,9 @@ #include #include #include +#include +#include +#include #include #include @@ -1094,6 +1097,8 @@ static void dcp_comp_unbind(struct device *dev, struct device *main, void *data) if (dcp->hdmi_hpd_irq) disable_irq(dcp->hdmi_hpd_irq); + typec_mux_put(dcp->typec_mux); + if (dcp->avep) { av_service_disconnect(dcp); afk_shutdown(dcp->avep); @@ -1231,6 +1236,27 @@ static int dcp_platform_probe(struct platform_device *pdev) ret = mux_control_select(dcp->xbar, mux_index); if (ret) dev_warn(dev, "mux_control_select failed: %d\n", ret); + + /* + * Switch atcphy to DP-only. should move to a Macbook Pro + * 14-/16-inch specific DP-to-HDMI drm_bridge. + */ + dcp->typec_mux = fwnode_typec_mux_get(dev_fwnode(dcp->dev)); + if (!IS_ERR_OR_NULL(dcp->typec_mux)) { + struct typec_altmode alt = { + .svid = USB_TYPEC_DP_SID, + }; + struct typec_mux_state state = { + .alt = &alt, + .mode = TYPEC_DP_STATE_C, + }; + int ret = typec_mux_set(dcp->typec_mux, &state); + dev_info(dev, "typec_mux_set() returned: %d\n", ret); + } else { + dev_info(dev, "fwnode_typec_mux_get() returned: %ld\n", + IS_ERR(dcp->typec_mux) ? PTR_ERR(dcp->typec_mux) : 0); + dcp->typec_mux = NULL; + } } } diff --git a/drivers/gpu/drm/apple/dptxep.c b/drivers/gpu/drm/apple/dptxep.c index e6e863dea76887..e21299e0124035 100644 --- a/drivers/gpu/drm/apple/dptxep.c +++ b/drivers/gpu/drm/apple/dptxep.c @@ -479,7 +479,9 @@ dptxport_call_activate(struct apple_epic_service *service, const struct apple_dcp *dcp = service->ep->dcp; // TODO: hack, use phy_set_mode to select the correct DCP(EXT) input - phy_set_mode_ext(dptx->atcphy, PHY_MODE_DP, dcp->index); + // for standalone phy (i.e. not atc phy). + if (!dcp->typec_mux) + phy_set_mode_ext(dptx->atcphy, PHY_MODE_DP, dcp->index); memcpy(reply, data, min(reply_size, data_size)); if (reply_size >= 4) From 01bb8400cb55a4efa820dabe1023142b143ab627 Mon Sep 17 00:00:00 2001 From: Asahi Lina Date: Thu, 18 Aug 2022 02:13:54 +0900 Subject: [PATCH 1224/3902] rust: soc: apple: rtkit: Add Apple RTKit abstraction RTKit is Apple's proprietary real-time operating system framework, used across many subdevices on Apple Silicon platforms including NVMe, system management, GPU, etc. Add Rust abstractions for this subsystem, so that it can be used by upcoming Rust drivers. FIXME: order in drivers/soc/apple/Kconfig to avoid merge conflicts in asahi tree Signed-off-by: Asahi Lina --- drivers/soc/apple/Kconfig | 6 + rust/bindings/bindings_helper.h | 1 + rust/kernel/lib.rs | 1 + rust/kernel/soc/apple/mod.rs | 6 + rust/kernel/soc/apple/rtkit.rs | 279 ++++++++++++++++++++++++++++++++ rust/kernel/soc/mod.rs | 5 + 6 files changed, 298 insertions(+) create mode 100644 rust/kernel/soc/apple/mod.rs create mode 100644 rust/kernel/soc/apple/rtkit.rs create mode 100644 rust/kernel/soc/mod.rs diff --git a/drivers/soc/apple/Kconfig b/drivers/soc/apple/Kconfig index ad67368892311b..662da18b4e3595 100644 --- a/drivers/soc/apple/Kconfig +++ b/drivers/soc/apple/Kconfig @@ -38,6 +38,12 @@ config APPLE_SART Say 'y' here if you have an Apple SoC. +config RUST_APPLE_RTKIT + bool + depends on PM + depends on RUST + select APPLE_RTKIT + endmenu endif diff --git a/rust/bindings/bindings_helper.h b/rust/bindings/bindings_helper.h index 2e43c66635a2c9..d3491fb51a4963 100644 --- a/rust/bindings/bindings_helper.h +++ b/rust/bindings/bindings_helper.h @@ -78,6 +78,7 @@ #include #include #include +#include #include #include #include diff --git a/rust/kernel/lib.rs b/rust/kernel/lib.rs index 0f3ff1039c0f9e..5568ad0ccc755d 100644 --- a/rust/kernel/lib.rs +++ b/rust/kernel/lib.rs @@ -144,6 +144,7 @@ pub mod scatterlist; pub mod security; pub mod seq_file; pub mod sizes; +pub mod soc; mod static_assert; #[doc(hidden)] pub mod std_vendor; diff --git a/rust/kernel/soc/apple/mod.rs b/rust/kernel/soc/apple/mod.rs new file mode 100644 index 00000000000000..964a5267bafb92 --- /dev/null +++ b/rust/kernel/soc/apple/mod.rs @@ -0,0 +1,6 @@ +// SPDX-License-Identifier: GPL-2.0-only OR MIT + +//! Apple SoC drivers + +#[cfg(CONFIG_RUST_APPLE_RTKIT = "y")] +pub mod rtkit; diff --git a/rust/kernel/soc/apple/rtkit.rs b/rust/kernel/soc/apple/rtkit.rs new file mode 100644 index 00000000000000..57d2fb01540b42 --- /dev/null +++ b/rust/kernel/soc/apple/rtkit.rs @@ -0,0 +1,279 @@ +// SPDX-License-Identifier: GPL-2.0-only OR MIT + +//! Support for Apple RTKit coprocessors. +//! +//! C header: [`include/linux/soc/apple/rtkit.h`](../../../../include/linux/gpio/driver.h) + +use crate::{ + alloc::flags::*, + bindings, device, + error::{code::*, from_err_ptr, from_result, to_result, Result}, + prelude::KBox, + str::CStr, + types::{ForeignOwnable, ScopeGuard}, +}; + +use core::marker::PhantomData; +use core::ptr; +use macros::vtable; + +/// Trait to represent allocatable buffers for the RTKit core. +/// +/// Users must implement this trait for their own representation of those allocations. +pub trait Buffer { + /// Returns the IOVA (virtual address) of the buffer from RTKit's point of view, or an error if + /// unavailable. + fn iova(&self) -> Result; + + /// Returns a mutable byte slice of the buffer contents, or an + /// error if unavailable. + fn buf(&mut self) -> Result<&mut [u8]>; +} + +/// Callback operations for an RTKit client. +#[vtable] +pub trait Operations { + /// Arbitrary user context type. + type Data: ForeignOwnable + Send + Sync; + + /// Type representing an allocated buffer for RTKit. + type Buffer: Buffer; + + /// Called when RTKit crashes. + fn crashed(_data: ::Borrowed<'_>, _crashlog: Option<&[u8]>) {} + + /// Called when a message was received on a non-system endpoint. Called in non-IRQ context. + fn recv_message( + _data: ::Borrowed<'_>, + _endpoint: u8, + _message: u64, + ) { + } + + /// Called in IRQ context when a message was received on a non-system endpoint. + /// + /// Must return `true` if the message is handled, or `false` to process it in + /// the handling thread. + fn recv_message_early( + _data: ::Borrowed<'_>, + _endpoint: u8, + _message: u64, + ) -> bool { + false + } + + /// Allocate a buffer for use by RTKit. + fn shmem_alloc( + _data: ::Borrowed<'_>, + _size: usize, + ) -> Result { + Err(EINVAL) + } + + /// Map an existing buffer used by RTKit at a device-specified virtual address. + fn shmem_map( + _data: ::Borrowed<'_>, + _iova: usize, + _size: usize, + ) -> Result { + Err(EINVAL) + } +} + +/// Represents `struct apple_rtkit *`. +/// +/// # Invariants +/// +/// The rtk pointer is valid. +/// The data pointer is a valid pointer from T::Data::into_foreign(). +pub struct RtKit { + rtk: *mut bindings::apple_rtkit, + data: *mut core::ffi::c_void, + _p: PhantomData, +} + +unsafe extern "C" fn crashed_callback( + cookie: *mut core::ffi::c_void, + crashlog: *const core::ffi::c_void, + crashlog_size: usize, +) { + let crashlog = if !crashlog.is_null() && crashlog_size > 0 { + // SAFETY: The crashlog is either missing or a byte buffer of the specified size + Some(unsafe { core::slice::from_raw_parts(crashlog as *const u8, crashlog_size) }) + } else { + None + }; + // SAFETY: cookie is always a T::Data in this API + T::crashed(unsafe { T::Data::borrow(cookie.cast()) }, crashlog); +} + +unsafe extern "C" fn recv_message_callback( + cookie: *mut core::ffi::c_void, + endpoint: u8, + message: u64, +) { + // SAFETY: cookie is always a T::Data in this API + T::recv_message(unsafe { T::Data::borrow(cookie.cast()) }, endpoint, message); +} + +unsafe extern "C" fn recv_message_early_callback( + cookie: *mut core::ffi::c_void, + endpoint: u8, + message: u64, +) -> bool { + // SAFETY: cookie is always a T::Data in this API + T::recv_message_early(unsafe { T::Data::borrow(cookie.cast()) }, endpoint, message) +} + +unsafe extern "C" fn shmem_setup_callback( + cookie: *mut core::ffi::c_void, + bfr: *mut bindings::apple_rtkit_shmem, +) -> core::ffi::c_int { + // SAFETY: `bfr` is a valid buffer + let bfr_mut = unsafe { &mut *bfr }; + + from_result(|| { + let mut buf = if bfr_mut.iova != 0 { + bfr_mut.is_mapped = true; + T::shmem_map( + // SAFETY: `cookie` came from a previous call to `into_foreign`. + unsafe { T::Data::borrow(cookie.cast()) }, + bfr_mut.iova as usize, + bfr_mut.size, + )? + } else { + bfr_mut.is_mapped = false; + // SAFETY: `cookie` came from a previous call to `into_foreign`. + T::shmem_alloc(unsafe { T::Data::borrow(cookie.cast()) }, bfr_mut.size)? + }; + + let iova = buf.iova()?; + let slice = buf.buf()?; + + if slice.len() < bfr_mut.size { + return Err(ENOMEM); + } + + bfr_mut.iova = iova as u64; + bfr_mut.buffer = slice.as_mut_ptr() as *mut _; + + // Now box the returned buffer type and stash it in the private pointer of the + // `apple_rtkit_shmem` struct for safekeeping. + let boxed = KBox::new(buf, GFP_KERNEL)?; + bfr_mut.private = KBox::into_raw(boxed) as *mut _; + Ok(0) + }) +} + +unsafe extern "C" fn shmem_destroy_callback( + _cookie: *mut core::ffi::c_void, + bfr: *mut bindings::apple_rtkit_shmem, +) { + // SAFETY: `bfr` is a valid buffer + let bfr_mut = unsafe { &mut *bfr }; + if !bfr_mut.private.is_null() { + // SAFETY: Per shmem_setup_callback, this has to be a pointer to a Buffer if it is set. + unsafe { + core::mem::drop(KBox::from_raw(bfr_mut.private as *mut T::Buffer)); + } + bfr_mut.private = core::ptr::null_mut(); + } +} + +impl RtKit { + const VTABLE: bindings::apple_rtkit_ops = bindings::apple_rtkit_ops { + crashed: Some(crashed_callback::), + recv_message: Some(recv_message_callback::), + recv_message_early: Some(recv_message_early_callback::), + shmem_setup: if T::HAS_SHMEM_ALLOC || T::HAS_SHMEM_MAP { + Some(shmem_setup_callback::) + } else { + None + }, + shmem_destroy: if T::HAS_SHMEM_ALLOC || T::HAS_SHMEM_MAP { + Some(shmem_destroy_callback::) + } else { + None + }, + }; + + /// Creates a new RTKit client for a given device and optional mailbox name or index. + pub fn new( + dev: &device::Device, + mbox_name: Option<&'static CStr>, + mbox_idx: usize, + data: T::Data, + ) -> Result { + let ptr: *mut crate::ffi::c_void = data.into_foreign().cast(); + let guard = ScopeGuard::new(|| { + // SAFETY: `ptr` came from a previous call to `into_foreign`. + unsafe { T::Data::from_foreign(ptr.cast()) }; + }); + // SAFETY: `dev` is valid by its type invarants and otherwise his just + // calls the C init function. + let rtk = unsafe { + from_err_ptr(bindings::apple_rtkit_init( + dev.as_raw(), + ptr, + match mbox_name { + Some(s) => s.as_char_ptr(), + None => ptr::null(), + }, + mbox_idx.try_into()?, + &Self::VTABLE, + )) + }?; + + guard.dismiss(); + // INVARIANT: `rtk` and `data` are valid here. + Ok(Self { + rtk, + data: ptr, + _p: PhantomData, + }) + } + + /// Boots (wakes up) the RTKit coprocessor. + pub fn wake(&mut self) -> Result { + // SAFETY: `rtk` is valid per the type invariant. + to_result(unsafe { bindings::apple_rtkit_wake(self.rtk) }) + } + + /// Waits for the RTKit coprocessor to finish booting. + pub fn boot(&mut self) -> Result { + // SAFETY: `rtk` is valid per the type invariant. + to_result(unsafe { bindings::apple_rtkit_boot(self.rtk) }) + } + + /// Starts a non-system endpoint. + pub fn start_endpoint(&mut self, endpoint: u8) -> Result { + // SAFETY: `rtk` is valid per the type invariant. + to_result(unsafe { bindings::apple_rtkit_start_ep(self.rtk, endpoint) }) + } + + /// Sends a message to a given endpoint. + pub fn send_message(&mut self, endpoint: u8, message: u64) -> Result { + // SAFETY: `rtk` is valid per the type invariant. + to_result(unsafe { + bindings::apple_rtkit_send_message(self.rtk, endpoint, message, ptr::null_mut(), false) + }) + } +} + +// SAFETY: `RtKit` operations require a mutable reference +unsafe impl Sync for RtKit {} + +// SAFETY: `RtKit` operations require a mutable reference +unsafe impl Send for RtKit {} + +impl Drop for RtKit { + fn drop(&mut self) { + // SAFETY: The pointer is valid by the type invariant. + unsafe { bindings::apple_rtkit_free(self.rtk) }; + + // Free context data. + // + // SAFETY: This matches the call to `into_foreign` from `new` in the success case. + unsafe { T::Data::from_foreign(self.data.cast()) }; + } +} diff --git a/rust/kernel/soc/mod.rs b/rust/kernel/soc/mod.rs new file mode 100644 index 00000000000000..e3024042e74f0d --- /dev/null +++ b/rust/kernel/soc/mod.rs @@ -0,0 +1,5 @@ +// SPDX-License-Identifier: GPL-2.0 + +//! SoC drivers + +pub mod apple; From ae61455961bf6cf93b893ec3031fb36635a62926 Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Wed, 3 Dec 2025 21:30:50 +0100 Subject: [PATCH 1225/3902] fixup! rust: soc: apple: rtkit: Add Apple RTKit abstraction Switch to iosys_map Depends on "rust: Introduce iosys_map bindings" from "[PATCH v6 0/8] Rust bindings for gem shmem + iosys_map" Signed-off-by: Janne Grunau --- rust/kernel/soc/apple/rtkit.rs | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/rust/kernel/soc/apple/rtkit.rs b/rust/kernel/soc/apple/rtkit.rs index 57d2fb01540b42..a5c2a949803e12 100644 --- a/rust/kernel/soc/apple/rtkit.rs +++ b/rust/kernel/soc/apple/rtkit.rs @@ -8,6 +8,7 @@ use crate::{ alloc::flags::*, bindings, device, error::{code::*, from_err_ptr, from_result, to_result, Result}, + iosys_map::IoSysMapRef, prelude::KBox, str::CStr, types::{ForeignOwnable, ScopeGuard}, @@ -27,7 +28,7 @@ pub trait Buffer { /// Returns a mutable byte slice of the buffer contents, or an /// error if unavailable. - fn buf(&mut self) -> Result<&mut [u8]>; + fn buf(&mut self) -> Result>; } /// Callback operations for an RTKit client. @@ -148,14 +149,14 @@ unsafe extern "C" fn shmem_setup_callback( }; let iova = buf.iova()?; - let slice = buf.buf()?; + let iosys_map = buf.buf()?; - if slice.len() < bfr_mut.size { + if iosys_map.size() < bfr_mut.size { return Err(ENOMEM); } bfr_mut.iova = iova as u64; - bfr_mut.buffer = slice.as_mut_ptr() as *mut _; + bfr_mut.buffer = iosys_map.as_mut_ptr() as *mut _; // Now box the returned buffer type and stash it in the private pointer of the // `apple_rtkit_shmem` struct for safekeeping. From deef13d1aaaf648662a805eb2cfb5f616d816179 Mon Sep 17 00:00:00 2001 From: Asahi Lina Date: Sat, 22 Oct 2022 00:10:30 +0900 Subject: [PATCH 1226/3902] rust: of: Add OF node abstraction This abstraction enables Rust drivers to walk Device Tree nodes and query their properties. Signed-off-by: Asahi Lina --- rust/bindings/bindings_helper.h | 2 + rust/helpers/of.c | 21 ++ rust/kernel/device.rs | 9 + rust/kernel/of.rs | 496 ++++++++++++++++++++++++++++++++ 4 files changed, 528 insertions(+) diff --git a/rust/bindings/bindings_helper.h b/rust/bindings/bindings_helper.h index d3491fb51a4963..3eb91e61c6d24c 100644 --- a/rust/bindings/bindings_helper.h +++ b/rust/bindings/bindings_helper.h @@ -64,6 +64,8 @@ #include #include #include +#include +#include #include #include #include diff --git a/rust/helpers/of.c b/rust/helpers/of.c index 86b51167c913f9..8c3958795357c0 100644 --- a/rust/helpers/of.c +++ b/rust/helpers/of.c @@ -1,8 +1,29 @@ // SPDX-License-Identifier: GPL-2.0 #include +#include bool rust_helper_is_of_node(const struct fwnode_handle *fwnode) { return is_of_node(fwnode); } + +const struct of_device_id *rust_helper_of_match_device( + const struct of_device_id *matches, const struct device *dev) +{ + return of_match_device(matches, dev); +} + +#ifdef CONFIG_OF +bool rust_helper_of_node_is_root(const struct device_node *np) +{ + return of_node_is_root(np); +} +#endif + +struct device_node *rust_helper_of_parse_phandle(const struct device_node *np, + const char *phandle_name, + int index) +{ + return of_parse_phandle(np, phandle_name, index); +} diff --git a/rust/kernel/device.rs b/rust/kernel/device.rs index 6d2d5e222f02bb..56aff98c3f274e 100644 --- a/rust/kernel/device.rs +++ b/rust/kernel/device.rs @@ -6,6 +6,8 @@ use crate::{ bindings, fmt, + of, + prelude::*, sync::aref::ARef, types::{ForeignOwnable, NotThreadSafe, Opaque}, }; @@ -281,6 +283,13 @@ impl Device { unsafe { &*ptr.cast() } } + /// Gets the OpenFirmware node attached to this device + pub fn of_node(&self) -> Option { + let ptr = self.0.get(); + // SAFETY: This is safe as long as of_node is NULL or valid. + unsafe { of::Node::get_from_raw((*ptr).of_node) } + } + /// Prints an emergency-level message (level 0) prefixed with device information. /// /// More details are available from [`dev_emerg`]. diff --git a/rust/kernel/of.rs b/rust/kernel/of.rs index 58b20c367f993f..fa06293f1f9aaf 100644 --- a/rust/kernel/of.rs +++ b/rust/kernel/of.rs @@ -8,6 +8,14 @@ use crate::{ prelude::*, }; +// Note: Most OF functions turn into inline dummies with CONFIG_OF(_*) disabled. +// We have to either add config conditionals to helpers.c or here; let's do it +// here for now. In the future, once bindgen can auto-generate static inline +// helpers, this can go away if desired. + +use core::marker::PhantomData; +use core::num::NonZeroU32; + /// IdTable type for OF drivers. pub type IdTable = &'static dyn kernel::device_id::IdTable; @@ -50,6 +58,494 @@ impl DeviceId { } } +/// Type alias for an OF phandle +pub type PHandle = bindings::phandle; + +/// An OF device tree node. +/// +/// # Invariants +/// +/// `raw_node` points to a valid OF node, and we hold a reference to it. +pub struct Node { + raw_node: *mut bindings::device_node, +} + +#[allow(dead_code)] +impl Node { + /// Creates a `Node` from a raw C pointer. The pointer must be owned (the caller + /// gives up its reference). If the pointer is NULL, returns None. + pub(crate) unsafe fn from_raw(raw_node: *mut bindings::device_node) -> Option { + if raw_node.is_null() { + None + } else { + // INVARIANT: `raw_node` is valid per the above contract, and non-null per the + // above check. + Some(Node { raw_node }) + } + } + + /// Creates a `Node` from a raw C pointer. The pointer must be borrowed (the caller + /// retains its reference, which must be valid for the duration of the call). If the + /// pointer is NULL, returns None. + pub(crate) unsafe fn get_from_raw(raw_node: *mut bindings::device_node) -> Option { + // SAFETY: `raw_node` is valid or NULL per the above contract. `of_node_get` can handle + // NULL. + unsafe { + #[cfg(CONFIG_OF_DYNAMIC)] + bindings::of_node_get(raw_node); + Node::from_raw(raw_node) + } + } + + /// Returns a reference to the underlying C `device_node` structure. + pub(crate) fn node(&self) -> &bindings::device_node { + // SAFETY: `raw_node` is valid per the type invariant. + unsafe { &*self.raw_node } + } + + /// Returns the name of the node. + pub fn name(&self) -> &CStr { + // SAFETY: The lifetime of the `CStr` is the same as the lifetime of this `Node`. + unsafe { CStr::from_char_ptr(self.node().name) } + } + + /// Returns the phandle for this node. + pub fn phandle(&self) -> PHandle { + self.node().phandle + } + + /// Returns the full name (with address) for this node. + pub fn full_name(&self) -> &CStr { + // SAFETY: The lifetime of the `CStr` is the same as the lifetime of this `Node`. + unsafe { CStr::from_char_ptr(self.node().full_name) } + } + + /// Returns `true` if the node is the root node. + pub fn is_root(&self) -> bool { + #[cfg(not(CONFIG_OF))] + { + false + } + #[cfg(CONFIG_OF)] + // SAFETY: `raw_node` is valid per the type invariant + unsafe { + bindings::of_node_is_root(self.raw_node) + } + } + + /// Returns the parent node, if any. + pub fn parent(&self) -> Option { + #[cfg(not(CONFIG_OF))] + { + None + } + #[cfg(CONFIG_OF)] + // SAFETY: `raw_node` is valid per the type invariant, and `of_get_parent()` takes a + // new reference to the parent (or returns NULL). + unsafe { + Node::from_raw(bindings::of_get_parent(self.raw_node)) + } + } + + /// Returns an iterator over the node's children. + // TODO: use type alias for return type once type_alias_impl_trait is stable + pub fn children( + &self, + ) -> NodeIterator<'_, impl Fn(*mut bindings::device_node) -> *mut bindings::device_node + '_> + { + #[cfg(not(CONFIG_OF))] + { + NodeIterator::new(|_prev| core::ptr::null_mut()) + } + #[cfg(CONFIG_OF)] + // SAFETY: `raw_node` is valid per the type invariant, and the lifetime of the `NodeIterator` + // does not exceed the lifetime of the `Node` so it can borrow its reference. + NodeIterator::new(|prev| unsafe { bindings::of_get_next_child(self.raw_node, prev) }) + } + + /// Find a child by its name and return it, or None if not found. + #[allow(unused_variables)] + pub fn get_child_by_name(&self, name: &CStr) -> Option { + #[cfg(not(CONFIG_OF))] + { + None + } + #[cfg(CONFIG_OF)] + // SAFETY: `raw_node` is valid per the type invariant. + unsafe { + Node::from_raw(bindings::of_get_child_by_name( + self.raw_node, + name.as_char_ptr(), + )) + } + } + + /// Checks whether the node is compatible with the given compatible string. + /// + /// Returns `None` if there is no match, or `Some` if there is, with the value + /// representing as match score (higher values for more specific compatible matches). + #[allow(unused_variables)] + pub fn is_compatible(&self, compatible: &CStr) -> Option { + #[cfg(not(CONFIG_OF))] + let ret = 0; + #[cfg(CONFIG_OF)] + let ret = + // SAFETY: `raw_node` is valid per the type invariant. + unsafe { bindings::of_device_is_compatible(self.raw_node, compatible.as_char_ptr()) }; + + NonZeroU32::new(ret.try_into().ok()?) + } + + /// Parse a phandle property and return the Node referenced at a given index, if any. + /// + /// Used only for phandle properties with no arguments. + #[allow(unused_variables)] + pub fn parse_phandle(&self, name: &CStr, index: usize) -> Option { + #[cfg(not(CONFIG_OF))] + { + None + } + #[cfg(CONFIG_OF)] + // SAFETY: `raw_node` is valid per the type invariant. `of_parse_phandle` returns an + // owned reference. + unsafe { + Node::from_raw(bindings::of_parse_phandle( + self.raw_node, + name.as_char_ptr(), + index.try_into().ok()?, + )) + } + } + + #[allow(unused_variables)] + /// Look up a node property by name, returning a `Property` object if found. + pub fn find_property(&self, propname: &CStr) -> Option> { + #[cfg(not(CONFIG_OF))] + { + None + } + #[cfg(CONFIG_OF)] + // SAFETY: `raw_node` is valid per the type invariant. The property structure + // returned borrows the reference to the owning node, and so has the same + // lifetime. + unsafe { + Property::from_raw(bindings::of_find_property( + self.raw_node, + propname.as_char_ptr(), + core::ptr::null_mut(), + )) + } + } + + /// Look up a mandatory node property by name, and decode it into a value type. + /// + /// Returns `Err(ENOENT)` if the property is not found. + /// + /// The type `T` must implement `TryFrom>`. + pub fn get_property<'a, T: TryFrom>>(&'a self, propname: &CStr) -> Result + where + crate::error::Error: From<>>::Error>, + { + Ok(self.find_property(propname).ok_or(ENOENT)?.try_into()?) + } + + /// Look up an optional node property by name, and decode it into a value type. + /// + /// Returns `Ok(None)` if the property is not found. + /// + /// The type `T` must implement `TryFrom>`. + pub fn get_opt_property<'a, T: TryFrom>>( + &'a self, + propname: &CStr, + ) -> Result> + where + crate::error::Error: From<>>::Error>, + { + self.find_property(propname) + .map_or(Ok(None), |p| Ok(Some(p.try_into()?))) + } +} + +/// A property attached to a device tree `Node`. +/// +/// # Invariants +/// +/// `raw` must be valid and point to a property that outlives the lifetime of this object. +#[derive(Copy, Clone)] +pub struct Property<'a> { + raw: *mut bindings::property, + _p: PhantomData<&'a Node>, +} + +impl<'a> Property<'a> { + #[cfg(CONFIG_OF)] + /// Create a `Property` object from a raw C pointer. Returns `None` if NULL. + /// + /// The passed pointer must be valid and outlive the lifetime argument, or NULL. + unsafe fn from_raw(raw: *mut bindings::property) -> Option> { + if raw.is_null() { + None + } else { + Some(Property { + raw, + _p: PhantomData, + }) + } + } + + /// Returns the name of the property as a `CStr`. + pub fn name(&self) -> &CStr { + // SAFETY: `raw` is valid per the type invariant, and the lifetime of the `CStr` does not + // outlive it. + unsafe { CStr::from_char_ptr((*self.raw).name) } + } + + /// Returns the name of the property as a `&[u8]`. + pub fn value(&self) -> &[u8] { + // SAFETY: `raw` is valid per the type invariant, and the lifetime of the slice does not + // outlive it. + unsafe { core::slice::from_raw_parts((*self.raw).value as *const u8, self.len()) } + } + + /// Returns the length of the property in bytes. + pub fn len(&self) -> usize { + // SAFETY: `raw` is valid per the type invariant. + unsafe { (*self.raw).length.try_into().unwrap() } + } + + /// Returns true if the property is empty (zero-length), which typically represents boolean true. + pub fn is_empty(&self) -> bool { + self.len() == 0 + } +} + +/// A trait that represents a value decodable from a property with a fixed unit size. +/// +/// This allows us to auto-derive property decode implementations for `Vec`. +pub trait PropertyUnit: Sized { + /// The size in bytes of a single data unit. + const UNIT_SIZE: usize; + + /// Decode this data unit from a byte slice. The passed slice will have a length of `UNIT_SIZE`. + fn from_bytes(data: &[u8]) -> Result; +} + +// This doesn't work... +// impl<'a, T: PropertyUnit> TryFrom> for T { +// type Error = Error; +// +// fn try_from(p: Property<'_>) -> core::result::Result { +// if p.value().len() != T::UNIT_SIZE { +// Err(EINVAL) +// } else { +// Ok(T::from_bytes(p.value())?) +// } +// } +// } + +impl<'a, T: PropertyUnit> TryFrom> for KVec { + type Error = Error; + + fn try_from(p: Property<'_>) -> core::result::Result, Self::Error> { + if p.len() % T::UNIT_SIZE != 0 { + return Err(EINVAL); + } + + let mut v = Vec::new(); + let val = p.value(); + for off in (0..p.len()).step_by(T::UNIT_SIZE) { + v.push(T::from_bytes(&val[off..off + T::UNIT_SIZE])?, GFP_KERNEL)?; + } + Ok(v) + } +} + +macro_rules! prop_int_type ( + ($type:ty) => { + impl<'a> TryFrom> for $type { + type Error = Error; + + fn try_from(p: Property<'_>) -> core::result::Result<$type, Self::Error> { + Ok(<$type>::from_be_bytes(p.value().try_into().or(Err(EINVAL))?)) + } + } + + impl PropertyUnit for $type { + const UNIT_SIZE: usize = <$type>::BITS as usize / 8; + + fn from_bytes(data: &[u8]) -> Result { + Ok(<$type>::from_be_bytes(data.try_into().or(Err(EINVAL))?)) + } + } + } +); + +prop_int_type!(u8); +prop_int_type!(u16); +prop_int_type!(u32); +prop_int_type!(u64); +prop_int_type!(i8); +prop_int_type!(i16); +prop_int_type!(i32); +prop_int_type!(i64); + +/// An iterator across a collection of Node objects. +/// +/// # Invariants +/// +/// `cur` must be NULL or a valid node owned reference. If NULL, it represents either the first +/// or last position of the iterator. +/// +/// If `done` is true, `cur` must be NULL. +/// +/// fn_next must be a callback that iterates from one node to the next, and it must not capture +/// values that exceed the lifetime of the iterator. It must return owned references and also +/// take owned references. +pub struct NodeIterator<'a, T> +where + T: Fn(*mut bindings::device_node) -> *mut bindings::device_node, +{ + cur: *mut bindings::device_node, + done: bool, + fn_next: T, + _p: PhantomData<&'a T>, +} + +impl<'a, T> NodeIterator<'a, T> +where + T: Fn(*mut bindings::device_node) -> *mut bindings::device_node, +{ + fn new(next: T) -> NodeIterator<'a, T> { + // INVARIANT: `cur` is initialized to NULL to represent the initial state. + NodeIterator { + cur: core::ptr::null_mut(), + done: false, + fn_next: next, + _p: PhantomData, + } + } +} + +impl<'a, T> Iterator for NodeIterator<'a, T> +where + T: Fn(*mut bindings::device_node) -> *mut bindings::device_node, +{ + type Item = Node; + + fn next(&mut self) -> Option { + if self.done { + None + } else { + // INVARIANT: if the new `cur` is NULL, then the iterator has reached its end and we + // set `done` to `true`. + self.cur = (self.fn_next)(self.cur); + self.done = self.cur.is_null(); + // SAFETY: `fn_next` must return an owned reference per the iterator contract. + // The iterator itself is considered to own this reference, so we take another one. + unsafe { Node::get_from_raw(self.cur) } + } + } +} + +// Drop impl to ensure we drop the current node being iterated on, if any. +impl<'a, T> Drop for NodeIterator<'a, T> +where + T: Fn(*mut bindings::device_node) -> *mut bindings::device_node, +{ + fn drop(&mut self) { + // SAFETY: `cur` is valid or NULL, and `of_node_put()` can handle NULL. + #[cfg(CONFIG_OF_DYNAMIC)] + unsafe { + bindings::of_node_put(self.cur) + }; + } +} + +/// Returns the root node of the OF device tree (if any). +pub fn root() -> Option { + #[cfg(not(CONFIG_OF))] + { + None + } + #[cfg(CONFIG_OF)] + // SAFETY: bindings::of_root is always valid or NULL + unsafe { + Node::get_from_raw(bindings::of_root) + } +} + +/// Returns the /chosen node of the OF device tree (if any). +pub fn chosen() -> Option { + #[cfg(not(CONFIG_OF))] + { + None + } + #[cfg(CONFIG_OF)] + // SAFETY: bindings::of_chosen is always valid or NULL + unsafe { + Node::get_from_raw(bindings::of_chosen) + } +} + +/// Returns the /aliases node of the OF device tree (if any). +pub fn aliases() -> Option { + #[cfg(not(CONFIG_OF))] + { + None + } + #[cfg(CONFIG_OF)] + // SAFETY: bindings::of_aliases is always valid or NULL + unsafe { + Node::get_from_raw(bindings::of_aliases) + } +} + +/// Returns the system stdout node of the OF device tree (if any). +pub fn stdout() -> Option { + #[cfg(not(CONFIG_OF))] + { + None + } + #[cfg(CONFIG_OF)] + // SAFETY: bindings::of_stdout is always valid or NULL + unsafe { + Node::get_from_raw(bindings::of_stdout) + } +} + +#[allow(unused_variables)] +/// Looks up a node in the device tree by phandle. +pub fn find_node_by_phandle(handle: PHandle) -> Option { + #[cfg(not(CONFIG_OF))] + { + None + } + #[cfg(CONFIG_OF)] + // SAFETY: bindings::of_find_node_by_phandle always returns a valid pointer or NULL + unsafe { + #[allow(dead_code)] + Node::from_raw(bindings::of_find_node_by_phandle(handle)) + } +} + +impl Clone for Node { + fn clone(&self) -> Node { + // SAFETY: `raw_node` is valid and non-NULL per the type invariant, + // so this can never return None. + unsafe { Node::get_from_raw(self.raw_node).unwrap() } + } +} + +impl Drop for Node { + fn drop(&mut self) { + #[cfg(CONFIG_OF_DYNAMIC)] + // SAFETY: `raw_node` is valid per the type invariant. + unsafe { + bindings::of_node_put(self.raw_node) + }; + } +} + /// Create an OF `IdTable` with an "alias" for modpost. #[macro_export] macro_rules! of_device_table { From 4556e97e93c2b560d655f40e074ad05e356c1cf3 Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Sat, 12 Jul 2025 12:37:30 +0200 Subject: [PATCH 1227/3902] rust: io: resource: Add owned Resource initialiser Some C functions like of_reserved_mem_region_to_resource_byname() expect a pointer to a `struct resource` so provide ::zeroed() as initialiser and ::as_raw() so other parts in the kernel crate can use functions which expect such a pointer. Signed-off-by: Janne Grunau --- rust/kernel/io/resource.rs | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/rust/kernel/io/resource.rs b/rust/kernel/io/resource.rs index bea3ee0ed87b51..a4ec302f34a168 100644 --- a/rust/kernel/io/resource.rs +++ b/rust/kernel/io/resource.rs @@ -76,6 +76,18 @@ unsafe impl Sync for Region {} pub struct Resource(Opaque); impl Resource { + /// Create a new zeroed [`Resource`] + pub(crate) fn zeroed() -> Self { + Resource { + 0: Opaque::::zeroed(), + } + } + + /// Gets the raw pointer to the wrapped `bindings::resource`. + pub(crate) fn as_raw(&self) -> *mut bindings::resource { + self.0.get() + } + /// Creates a reference to a [`Resource`] from a valid pointer. /// /// # Safety From 12127abbd68381d96525ccd963ce0b261825db17 Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Fri, 11 Jul 2025 20:23:49 +0200 Subject: [PATCH 1228/3902] rust: of: Add reserved_mem_region_to_resource_byname() Creates Resource from a reserved memory region. Depends on commit f4fcfdda2fd8 ("of: reserved_mem: Add functions to parse "memory-region"") from v6.16-rc1. Signed-off-by: Janne Grunau --- rust/bindings/bindings_helper.h | 1 + rust/kernel/of.rs | 27 +++++++++++++++++++++++++++ 2 files changed, 28 insertions(+) diff --git a/rust/bindings/bindings_helper.h b/rust/bindings/bindings_helper.h index 3eb91e61c6d24c..b97b86ef6d098c 100644 --- a/rust/bindings/bindings_helper.h +++ b/rust/bindings/bindings_helper.h @@ -67,6 +67,7 @@ #include #include #include +#include #include #include #include diff --git a/rust/kernel/of.rs b/rust/kernel/of.rs index fa06293f1f9aaf..22f8efa6d47ae8 100644 --- a/rust/kernel/of.rs +++ b/rust/kernel/of.rs @@ -16,6 +16,9 @@ use crate::{ use core::marker::PhantomData; use core::num::NonZeroU32; +use crate::error::to_result; +use crate::io::resource::Resource; + /// IdTable type for OF drivers. pub type IdTable = &'static dyn kernel::device_id::IdTable; @@ -217,6 +220,30 @@ impl Node { } } + #[allow(unused_variables)] + /// Get a reserved memory region as a resource + pub fn reserved_mem_region_to_resource_byname(&self, name: &CStr) -> Result { + #[cfg(not(CONFIG_OF))] + { + Err(ENOENT) + } + #[cfg(CONFIG_OF)] + { + let res = Resource::zeroed(); + // SAFETY: This function is safe to call as long as the arguments are valid pointers. + let ret = unsafe { + bindings::of_reserved_mem_region_to_resource_byname( + self.raw_node, + name.as_char_ptr(), + res.as_raw(), + ) + }; + to_result(ret)?; + + Ok(res) + } + } + #[allow(unused_variables)] /// Look up a node property by name, returning a `Property` object if found. pub fn find_property(&self, propname: &CStr) -> Option> { From a29c333a118c4d04f75ea5eee0de096feb770157 Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Sun, 13 Jul 2025 10:44:51 +0200 Subject: [PATCH 1229/3902] rust: of: Discourage us of "of" properties Use FwNode based device properties instead. Signed-off-by: Janne Grunau --- rust/kernel/of.rs | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/rust/kernel/of.rs b/rust/kernel/of.rs index 22f8efa6d47ae8..80c604ee831452 100644 --- a/rust/kernel/of.rs +++ b/rust/kernel/of.rs @@ -107,24 +107,24 @@ impl Node { } /// Returns the name of the node. - pub fn name(&self) -> &CStr { + pub(crate) fn name(&self) -> &CStr { // SAFETY: The lifetime of the `CStr` is the same as the lifetime of this `Node`. unsafe { CStr::from_char_ptr(self.node().name) } } /// Returns the phandle for this node. - pub fn phandle(&self) -> PHandle { + pub(crate) fn phandle(&self) -> PHandle { self.node().phandle } /// Returns the full name (with address) for this node. - pub fn full_name(&self) -> &CStr { + pub(crate) fn full_name(&self) -> &CStr { // SAFETY: The lifetime of the `CStr` is the same as the lifetime of this `Node`. unsafe { CStr::from_char_ptr(self.node().full_name) } } /// Returns `true` if the node is the root node. - pub fn is_root(&self) -> bool { + pub(crate) fn is_root(&self) -> bool { #[cfg(not(CONFIG_OF))] { false @@ -137,7 +137,7 @@ impl Node { } /// Returns the parent node, if any. - pub fn parent(&self) -> Option { + pub(crate) fn parent(&self) -> Option { #[cfg(not(CONFIG_OF))] { None @@ -168,7 +168,7 @@ impl Node { /// Find a child by its name and return it, or None if not found. #[allow(unused_variables)] - pub fn get_child_by_name(&self, name: &CStr) -> Option { + pub(crate) fn get_child_by_name(&self, name: &CStr) -> Option { #[cfg(not(CONFIG_OF))] { None @@ -188,7 +188,7 @@ impl Node { /// Returns `None` if there is no match, or `Some` if there is, with the value /// representing as match score (higher values for more specific compatible matches). #[allow(unused_variables)] - pub fn is_compatible(&self, compatible: &CStr) -> Option { + pub(crate) fn is_compatible(&self, compatible: &CStr) -> Option { #[cfg(not(CONFIG_OF))] let ret = 0; #[cfg(CONFIG_OF)] @@ -246,7 +246,7 @@ impl Node { #[allow(unused_variables)] /// Look up a node property by name, returning a `Property` object if found. - pub fn find_property(&self, propname: &CStr) -> Option> { + pub(crate) fn find_property(&self, propname: &CStr) -> Option> { #[cfg(not(CONFIG_OF))] { None From 8b84b12e0de56d5be8653f350041398cbb3c7681 Mon Sep 17 00:00:00 2001 From: Asahi Lina Date: Tue, 10 Dec 2024 03:19:54 +0900 Subject: [PATCH 1230/3902] rust: Add Ownable/Owned types By analogy to AlwaysRefCounted and ARef, an Ownable type is a (typically C FFI) type that *may* be owned by Rust, but need not be. Unlike AlwaysRefCounted, this mechanism expects the reference to be unique within Rust, and does not allow cloning. Conceptually, this is similar to a KBox, except that it delegates resource management to the T instead of using a generic allocator. Signed-off-by: Asahi Lina --- rust/kernel/types.rs | 111 ++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 110 insertions(+), 1 deletion(-) diff --git a/rust/kernel/types.rs b/rust/kernel/types.rs index 8be6daa291a7a8..05387d41727cc0 100644 --- a/rust/kernel/types.rs +++ b/rust/kernel/types.rs @@ -6,8 +6,9 @@ use crate::ffi::c_void; use core::{ cell::UnsafeCell, marker::{PhantomData, PhantomPinned}, - mem::MaybeUninit, + mem::{ManuallyDrop, MaybeUninit}, ops::{Deref, DerefMut}, + ptr::NonNull, }; use pin_init::{PinInit, Wrapper, Zeroable}; @@ -423,6 +424,114 @@ impl Wrapper for Opaque { } } +/// Types that may be owned by Rust code or borrowed, but have a lifetime managed by C code. +/// +/// It allows such types to define their own custom destructor function to be called when +/// a Rust-owned reference is dropped. +/// +/// This is usually implemented by wrappers to existing structures on the C side of the code. +/// +/// # Safety +/// +/// Implementers must ensure that any objects borrowed directly stay alive for the duration +/// of the borrow lifetime, and that any objects deemed owned by Rust stay alive while +/// that owned reference exists, until the [`Ownable::release()`] function is called. +pub unsafe trait Ownable { + /// Releases the object (frees it or returns it to foreign ownership). + /// + /// # Safety + /// + /// Callers must ensure that the object is no longer referenced after this call. + unsafe fn release(this: NonNull); +} + +/// A subtrait of Ownable that asserts that an Owned Rust reference is not only unique +/// within Rust, but also follows the same rules in kernel C code. That is, the kernel +/// will never mutate the contents of the object while Rust owns it. +/// +/// When this type is implemented for an Ownable type, it allows Owned to be dereferenced +/// into a &mut T. + +/// # Safety +/// +/// Implementers must ensure that the kernel never mutates the underlying type while +/// Rust owns it. +pub unsafe trait OwnableMut: Ownable {} + +/// An owned reference to an ownable kernel object. +/// +/// The object is automatically freed or released when an instance of [`Owned`] is +/// dropped. +/// +/// # Invariants +/// +/// The pointer stored in `ptr` is non-null and valid for the lifetime of the [`Owned`] instance. +pub struct Owned { + ptr: NonNull, + _p: PhantomData, +} + +// SAFETY: It is safe to send `Owned` to another thread when the underlying `T` is `Send` because +// it effectively means sharing `&mut T` (which is safe because `T` is `Send`). +unsafe impl Send for Owned {} + +// SAFETY: It is safe to send `&Owned` to another thread when the underlying `T` is `Sync` +// because it effectively means sharing `&T` (which is safe because `T` is `Sync`). +unsafe impl Sync for Owned {} + +impl Owned { + /// Creates a new instance of [`Owned`]. + /// + /// It takes over ownership of the underlying object. + /// + /// # Safety + /// + /// Callers must ensure that the underlying object is acquired and can be considered owned by + /// Rust. + pub unsafe fn from_raw(ptr: NonNull) -> Self { + // INVARIANT: The safety requirements guarantee that the new instance now owns the + // reference. + Self { + ptr, + _p: PhantomData, + } + } + + /// Consumes the `Owned`, returning a raw pointer. + /// + /// This function does not actually relinquish ownership of the object. + /// After calling this function, the caller is responsible for ownership previously managed + /// by the `Owned`. + pub fn into_raw(me: Self) -> NonNull { + ManuallyDrop::new(me).ptr + } +} + +impl Deref for Owned { + type Target = T; + + fn deref(&self) -> &Self::Target { + // SAFETY: The type invariants guarantee that the object is valid. + unsafe { self.ptr.as_ref() } + } +} + +impl DerefMut for Owned { + fn deref_mut(&mut self) -> &mut Self::Target { + // SAFETY: The type invariants guarantee that the object is valid, + // and that we can safely return a mutable reference to it. + unsafe { self.ptr.as_mut() } + } +} + +impl Drop for Owned { + fn drop(&mut self) { + // SAFETY: The type invariants guarantee that the `Owned` owns the object we're about to + // release. + unsafe { T::release(self.ptr) }; + } +} + /// Zero-sized type to mark types not [`Send`]. /// /// Add this type as a field to your struct if your type should not be sent to a different task. From 2cbcfb9638e4d82c818501abc7c2b8d39e30bc88 Mon Sep 17 00:00:00 2001 From: Asahi Lina Date: Thu, 16 Feb 2023 20:20:17 +0900 Subject: [PATCH 1231/3902] Rust: io: Add memcpy_fromio wrapper Adapted from *RFL import: kernel::io_mem Commit reference: 3dfc5ebff103 Signed-off-by: Janne Grunau --- rust/helpers/io.c | 5 +++++ rust/kernel/io.rs | 49 +++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 54 insertions(+) diff --git a/rust/helpers/io.c b/rust/helpers/io.c index c475913c69e647..69fe3641539e6f 100644 --- a/rust/helpers/io.c +++ b/rust/helpers/io.c @@ -18,6 +18,11 @@ void rust_helper_iounmap(void __iomem *addr) iounmap(addr); } +void rust_helper_memcpy_fromio(void *to, const void __iomem *from, long count) +{ + memcpy_fromio(to, from, count); +} + u8 rust_helper_readb(const void __iomem *addr) { return readb(addr); diff --git a/rust/kernel/io.rs b/rust/kernel/io.rs index ee182b0b5452df..e79e0af06bfac6 100644 --- a/rust/kernel/io.rs +++ b/rust/kernel/io.rs @@ -206,6 +206,15 @@ impl Io { } } + #[inline] + const fn length_valid(offset: usize, length: usize, size: usize) -> bool { + if let Some(end) = offset.checked_add(length) { + end <= size + } else { + false + } + } + #[inline] fn io_addr(&self, offset: usize) -> Result { if !Self::offset_valid::(offset, self.maxsize()) { @@ -224,6 +233,46 @@ impl Io { self.addr() + offset } + /// Copy memory block from an i/o memory by filling the specified buffer with it. + /// + /// # Examples + /// ``` + /// use kernel::io::mem::IoMem; + /// use kernel::io::mem::Resource; + /// + /// fn test(device: &Device, res: Resource) -> Result { + /// // Create an i/o memory block of at least 100 bytes. + /// let devres_mem = IoMem::<100>::new(res, device)?; + /// // aquire access to memory block + /// let mem = devres_mem.try_access()?; + /// + /// let mut buffer: [u8; 32] = [0; 32]; + /// + /// // Memcpy 16 bytes from an offset 10 of i/o memory block into the buffer. + /// mem.try_memcpy_fromio(&mut buffer[..16], 10)?; + /// + /// Ok(()) + /// } + /// ``` + pub fn try_memcpy_fromio(&self, buffer: &mut [u8], offset: usize) -> Result { + if buffer.len() == 0 || !Self::length_valid(offset, buffer.len(), self.maxsize()) { + return Err(EINVAL); + } + let addr = self.io_addr::(offset)?; + + // SAFETY: + // - The type invariants guarantee that `adr` is a valid pointer. + // - The bounds of `buffer` are checked with a call to `length_valid`. + unsafe { + bindings::memcpy_fromio( + buffer.as_mut_ptr() as *mut _, + addr as *const _, + buffer.len() as _, + ) + }; + Ok(()) + } + define_read!(read8, try_read8, readb -> u8); define_read!(read16, try_read16, readw -> u16); define_read!(read32, try_read32, readl -> u32); From 957c64fc5af4d606665ee537f33acc3beaf94236 Mon Sep 17 00:00:00 2001 From: Sasha Finkelstein Date: Sat, 9 Nov 2024 18:12:48 +0100 Subject: [PATCH 1232/3902] rust: io: Add helper for memcpy_toio Signed-off-by: Sasha Finkelstein Signed-off-by: Janne Grunau --- rust/helpers/io.c | 5 +++++ rust/kernel/io.rs | 21 +++++++++++++++++++++ 2 files changed, 26 insertions(+) diff --git a/rust/helpers/io.c b/rust/helpers/io.c index 69fe3641539e6f..6b1b05ab977b0c 100644 --- a/rust/helpers/io.c +++ b/rust/helpers/io.c @@ -23,6 +23,11 @@ void rust_helper_memcpy_fromio(void *to, const void __iomem *from, long count) memcpy_fromio(to, from, count); } +void rust_helper_memcpy_toio(void __iomem *to, const void *from, size_t count) +{ + memcpy_toio(to, from, count); +} + u8 rust_helper_readb(const void __iomem *addr) { return readb(addr); diff --git a/rust/kernel/io.rs b/rust/kernel/io.rs index e79e0af06bfac6..fb25d15ca9119f 100644 --- a/rust/kernel/io.rs +++ b/rust/kernel/io.rs @@ -273,6 +273,27 @@ impl Io { Ok(()) } + /// Copy memory block to i/o memory from the specified buffer. + pub fn try_memcpy_toio(&self, offset: usize, buffer: &[u8]) -> Result { + if buffer.len() == 0 || !Self::length_valid(offset, buffer.len(), self.maxsize()) { + return Err(EINVAL); + } + // no need to check since offset + buffer.len() - 1 is valid + let addr = self.io_addr::(offset)?; + + // SAFETY: + // - The type invariants guarantee that `adr` is a valid pointer. + // - The bounds of `buffer` are checked with a call to `length_valid`. + unsafe { + bindings::memcpy_toio( + addr as *mut _, + buffer.as_ptr() as *const _, + buffer.len() as _, + ) + }; + Ok(()) + } + define_read!(read8, try_read8, readb -> u8); define_read!(read16, try_read16, readw -> u16); define_read!(read32, try_read32, readl -> u32); From 691821ba6a9651e3d4af1738d210c7a4070b3c27 Mon Sep 17 00:00:00 2001 From: Lyude Paul Date: Thu, 16 Oct 2025 17:08:14 -0400 Subject: [PATCH 1233/3902] Partially revert "rust: drm: gem: Implement AlwaysRefCounted for all gem objects automatically" MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Currently in order to implement AlwaysRefCounted for gem objects, we use a blanket implementation: unsafe impl AlwaysRefCounted for T { … } While this technically works, it comes with the rather unfortunate downside that attempting to create a similar blanket implementation in any other kernel crate will now fail in a rather confusing way. Using an example from the (not yet upstream) rust DRM KMS bindings, if we were to add: unsafe impl AlwaysRefCounted for T { … } Then the moment that both blanket implementations are present in the same kernel tree, compilation fails with the following: error[E0119]: conflicting implementations of trait `types::AlwaysRefCounted` --> rust/kernel/drm/kms.rs:504:1 | 504 | unsafe impl AlwaysRefCounted for T { | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ conflicting implementation | ::: rust/kernel/drm/gem/mod.rs:97:1 | 97 | unsafe impl AlwaysRefCounted for T { | ---------------------------------------------------- first implementation here So, revert these changes for now. The proper fix for this is to introduce a macro for copy/pasting the same implementation of AlwaysRefCounted around. This reverts commit 38cb08c3fcd3f3b1d0225dcec8ae50fab5751549. Signed-off-by: Lyude Paul Reviewed-by: Alice Ryhl Link: https://lore.kernel.org/r/20251016210955.2813186-2-lyude@redhat.com --- rust/kernel/drm/gem/mod.rs | 36 ++++++++++++++++-------------------- 1 file changed, 16 insertions(+), 20 deletions(-) diff --git a/rust/kernel/drm/gem/mod.rs b/rust/kernel/drm/gem/mod.rs index 30c853988b9420..20c2769a8c9d6e 100644 --- a/rust/kernel/drm/gem/mod.rs +++ b/rust/kernel/drm/gem/mod.rs @@ -55,26 +55,6 @@ pub trait IntoGEMObject: Sized + super::private::Sealed + AlwaysRefCounted { unsafe fn from_raw<'a>(self_ptr: *mut bindings::drm_gem_object) -> &'a Self; } -// SAFETY: All gem objects are refcounted. -unsafe impl AlwaysRefCounted for T { - fn inc_ref(&self) { - // SAFETY: The existence of a shared reference guarantees that the refcount is non-zero. - unsafe { bindings::drm_gem_object_get(self.as_raw()) }; - } - - unsafe fn dec_ref(obj: NonNull) { - // SAFETY: We either hold the only refcount on `obj`, or one of many - meaning that no one - // else could possibly hold a mutable reference to `obj` and thus this immutable reference - // is safe. - let obj = unsafe { obj.as_ref() }.as_raw(); - - // SAFETY: - // - The safety requirements guarantee that the refcount is non-zero. - // - We hold no references to `obj` now, making it safe for us to potentially deallocate it. - unsafe { bindings::drm_gem_object_put(obj) }; - } -} - extern "C" fn open_callback( raw_obj: *mut bindings::drm_gem_object, raw_file: *mut bindings::drm_file, @@ -273,6 +253,22 @@ impl Object { } } +// SAFETY: Instances of `Object` are always reference-counted. +unsafe impl crate::types::AlwaysRefCounted for Object { + fn inc_ref(&self) { + // SAFETY: The existence of a shared reference guarantees that the refcount is non-zero. + unsafe { bindings::drm_gem_object_get(self.as_raw()) }; + } + + unsafe fn dec_ref(obj: NonNull) { + // SAFETY: `obj` is a valid pointer to an `Object`. + let obj = unsafe { obj.as_ref() }; + + // SAFETY: The safety requirements guarantee that the refcount is non-zero. + unsafe { bindings::drm_gem_object_put(obj.as_raw()) } + } +} + impl super::private::Sealed for Object {} impl Deref for Object { From 860490fd4d5acc388f2df200c74f65fc67dc6632 Mon Sep 17 00:00:00 2001 From: Lyude Paul Date: Tue, 21 Oct 2025 13:21:36 -0400 Subject: [PATCH 1234/3902] rust: drm/gem: Remove Object.dev I noticed by chance that there's actually already a pointer to this in struct drm_gem_object. So, no use in carrying this around! Signed-off-by: Lyude Paul Acked-by: Danilo Krummrich Reviewed-by: Alice Ryhl Link: https://lore.kernel.org/r/20251021172220.252558-1-lyude@redhat.com --- rust/kernel/drm/gem/mod.rs | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/rust/kernel/drm/gem/mod.rs b/rust/kernel/drm/gem/mod.rs index 20c2769a8c9d6e..eb5f3feac89075 100644 --- a/rust/kernel/drm/gem/mod.rs +++ b/rust/kernel/drm/gem/mod.rs @@ -167,12 +167,10 @@ impl BaseObject for T {} /// Invariants /// /// - `self.obj` is a valid instance of a `struct drm_gem_object`. -/// - `self.dev` is always a valid pointer to a `struct drm_device`. #[repr(C)] #[pin_data] pub struct Object { obj: Opaque, - dev: NonNull>, #[pin] data: T, } @@ -202,9 +200,6 @@ impl Object { try_pin_init!(Self { obj: Opaque::new(bindings::drm_gem_object::default()), data <- T::new(dev, size), - // INVARIANT: The drm subsystem guarantees that the `struct drm_device` will live - // as long as the GEM object lives. - dev: dev.into(), }), GFP_KERNEL, )?; @@ -227,9 +222,13 @@ impl Object { /// Returns the `Device` that owns this GEM object. pub fn dev(&self) -> &drm::Device { - // SAFETY: The DRM subsystem guarantees that the `struct drm_device` will live as long as - // the GEM object lives, hence the pointer must be valid. - unsafe { self.dev.as_ref() } + // SAFETY: + // - `struct drm_gem_object.dev` is initialized and valid for as long as the GEM + // object lives. + // - The device we used for creating the gem object is passed as &drm::Device to + // Object::::new(), so we know that `T::Driver` is the right generic parameter to use + // here. + unsafe { drm::Device::from_raw((*self.as_raw()).dev) } } fn as_raw(&self) -> *mut bindings::drm_gem_object { From d025fdb01dc443c498ee40910afdcf1459de2fb7 Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Mon, 23 Jun 2025 20:50:58 +0200 Subject: [PATCH 1235/3902] rust: drm: driver: Add feature flags used by asahi Signed-off-by: Janne Grunau --- rust/kernel/drm/driver.rs | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/rust/kernel/drm/driver.rs b/rust/kernel/drm/driver.rs index f30ee4c6245cda..334ac933c229c6 100644 --- a/rust/kernel/drm/driver.rs +++ b/rust/kernel/drm/driver.rs @@ -14,6 +14,15 @@ use macros::vtable; /// Driver use the GEM memory manager. This should be set for all modern drivers. pub(crate) const FEAT_GEM: u32 = bindings::drm_driver_feature_DRIVER_GEM; +/// Driver supports dedicated render nodes. +pub const FEAT_RENDER: u32 = bindings::drm_driver_feature_DRIVER_RENDER; +/// Driver supports DRM sync objects for explicit synchronization of command submission. +pub const FEAT_SYNCOBJ: u32 = bindings::drm_driver_feature_DRIVER_SYNCOBJ; +/// Driver supports the timeline flavor of DRM sync objects for explicit synchronization of command +/// submission. +pub const FEAT_SYNCOBJ_TIMELINE: u32 = bindings::drm_driver_feature_DRIVER_SYNCOBJ_TIMELINE; +/// Driver supports user defined GPU VA bindings for GEM objects. +pub const FEAT_GEM_GPUVA: u32 = bindings::drm_driver_feature_DRIVER_GEM_GPUVA; /// Information data for a DRM Driver. pub struct DriverInfo { From 4906480eaa1555cd5188807fd2f94412fd867848 Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Sun, 29 Jun 2025 09:49:53 +0200 Subject: [PATCH 1236/3902] rust: drm: Move FEATURES back to drivers This can be used in an unsafe way but required for the asahi driver. Signed-off-by: Janne Grunau --- drivers/gpu/drm/nova/driver.rs | 2 ++ drivers/gpu/drm/tyr/driver.rs | 2 ++ rust/kernel/drm/device.rs | 2 +- rust/kernel/drm/driver.rs | 5 ++++- 4 files changed, 9 insertions(+), 2 deletions(-) diff --git a/drivers/gpu/drm/nova/driver.rs b/drivers/gpu/drm/nova/driver.rs index 91b7380f83ab4a..2fd6863ae683db 100644 --- a/drivers/gpu/drm/nova/driver.rs +++ b/drivers/gpu/drm/nova/driver.rs @@ -63,6 +63,8 @@ impl drm::Driver for NovaDriver { const INFO: drm::DriverInfo = INFO; + const FEATURES: u32 = drm::driver::FEAT_GEM; + kernel::declare_drm_ioctls! { (NOVA_GETPARAM, drm_nova_getparam, ioctl::RENDER_ALLOW, File::get_param), (NOVA_GEM_CREATE, drm_nova_gem_create, ioctl::AUTH | ioctl::RENDER_ALLOW, File::gem_create), diff --git a/drivers/gpu/drm/tyr/driver.rs b/drivers/gpu/drm/tyr/driver.rs index d5625dd1e41c84..d14d5a9fdfb01e 100644 --- a/drivers/gpu/drm/tyr/driver.rs +++ b/drivers/gpu/drm/tyr/driver.rs @@ -186,6 +186,8 @@ impl drm::Driver for TyrDriver { const INFO: drm::DriverInfo = INFO; + const FEATURES: u32 = drm::driver::FEAT_GEM; + kernel::declare_drm_ioctls! { (PANTHOR_DEV_QUERY, drm_panthor_dev_query, ioctl::RENDER_ALLOW, File::dev_query), } diff --git a/rust/kernel/drm/device.rs b/rust/kernel/drm/device.rs index 3ce8f62a005696..38258c24e7e18d 100644 --- a/rust/kernel/drm/device.rs +++ b/rust/kernel/drm/device.rs @@ -86,7 +86,7 @@ impl Device { name: crate::str::as_char_ptr_in_const_context(T::INFO.name).cast_mut(), desc: crate::str::as_char_ptr_in_const_context(T::INFO.desc).cast_mut(), - driver_features: drm::driver::FEAT_GEM, + driver_features: T::FEATURES, ioctls: T::IOCTLS.as_ptr(), num_ioctls: T::IOCTLS.len() as i32, fops: &Self::GEM_FOPS, diff --git a/rust/kernel/drm/driver.rs b/rust/kernel/drm/driver.rs index 334ac933c229c6..bb072b3a01817e 100644 --- a/rust/kernel/drm/driver.rs +++ b/rust/kernel/drm/driver.rs @@ -13,7 +13,7 @@ use crate::{ use macros::vtable; /// Driver use the GEM memory manager. This should be set for all modern drivers. -pub(crate) const FEAT_GEM: u32 = bindings::drm_driver_feature_DRIVER_GEM; +pub const FEAT_GEM: u32 = bindings::drm_driver_feature_DRIVER_GEM; /// Driver supports dedicated render nodes. pub const FEAT_RENDER: u32 = bindings::drm_driver_feature_DRIVER_RENDER; /// Driver supports DRM sync objects for explicit synchronization of command submission. @@ -120,6 +120,9 @@ pub trait Driver { /// Driver metadata const INFO: DriverInfo; + /// Feature flags + const FEATURES: u32; + /// IOCTL list. See `kernel::drm::ioctl::declare_drm_ioctls!{}`. const IOCTLS: &'static [drm::ioctl::DrmIoctlDescriptor]; } From d2fd2e171696dcfeb026f671a2615dafa637c814 Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Wed, 17 Dec 2025 08:41:50 +0100 Subject: [PATCH 1237/3902] HACK: rust: drm: Leak the DRM device in release The driver's data might not be initialized and dropping the uninitialized data will crash. Since the DRM device is expected to be released only once at reboot or poweroff leaking the device is not an issue in practice. Signed-off-by: Janne Grunau --- rust/kernel/drm/device.rs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/rust/kernel/drm/device.rs b/rust/kernel/drm/device.rs index 38258c24e7e18d..76625537f38023 100644 --- a/rust/kernel/drm/device.rs +++ b/rust/kernel/drm/device.rs @@ -184,7 +184,10 @@ impl Device { // SAFETY: // - When `release` runs it is guaranteed that there is no further access to `this`. // - `this` is valid for dropping. - unsafe { core::ptr::drop_in_place(this) }; + // unsafe { core::ptr::drop_in_place(this) }; + // HACK: data might be uninitialized so leak the DRM device instead. The expected number + // of times the asahi device gets released is once at poweroff or reboot. + let _ = core::mem::ManuallyDrop::new(this); } } From d90b28c10ae5c5c47355ab2c00a394172ebbf691 Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Mon, 23 Jun 2025 17:06:51 +0200 Subject: [PATCH 1238/3902] rust: drm: file: Add as_raw() Signed-off-by: Janne Grunau --- drivers/gpu/drm/nova/file.rs | 3 +++ drivers/gpu/drm/tyr/file.rs | 3 +++ rust/kernel/drm/file.rs | 3 +++ 3 files changed, 9 insertions(+) diff --git a/drivers/gpu/drm/nova/file.rs b/drivers/gpu/drm/nova/file.rs index 90b9d2d0ec4ae0..acc63e6bc63389 100644 --- a/drivers/gpu/drm/nova/file.rs +++ b/drivers/gpu/drm/nova/file.rs @@ -4,6 +4,7 @@ use crate::driver::{NovaDevice, NovaDriver}; use crate::gem::NovaObject; use kernel::{ alloc::flags::*, + bindings, drm::{self, gem::BaseObject}, pci, prelude::*, @@ -18,6 +19,8 @@ impl drm::file::DriverFile for File { fn open(_dev: &NovaDevice) -> Result>> { Ok(KBox::new(Self, GFP_KERNEL)?.into()) } + + fn as_raw(&self) -> *mut bindings::drm_file { todo!() } } impl File { diff --git a/drivers/gpu/drm/tyr/file.rs b/drivers/gpu/drm/tyr/file.rs index 0ef432947b73d5..bac43d701b5bed 100644 --- a/drivers/gpu/drm/tyr/file.rs +++ b/drivers/gpu/drm/tyr/file.rs @@ -1,5 +1,6 @@ // SPDX-License-Identifier: GPL-2.0 or MIT +use kernel::bindings; use kernel::drm; use kernel::prelude::*; use kernel::uaccess::UserSlice; @@ -20,6 +21,8 @@ impl drm::file::DriverFile for File { fn open(_dev: &drm::Device) -> Result>> { KBox::try_pin_init(try_pin_init!(Self {}), GFP_KERNEL) } + + fn as_raw(&self) -> *mut bindings::drm_file { todo!() } } impl File { diff --git a/rust/kernel/drm/file.rs b/rust/kernel/drm/file.rs index 8c46f8d519516a..9c7bcace70eef5 100644 --- a/rust/kernel/drm/file.rs +++ b/rust/kernel/drm/file.rs @@ -15,6 +15,9 @@ pub trait DriverFile { /// Open a new file (called when a client opens the DRM device). fn open(device: &drm::Device) -> Result>>; + + /// Get raw drm_file pointer + fn as_raw(&self) -> *mut bindings::drm_file; } /// An open DRM File. From 0aa7e8ad1c405b204d34e8b4874fa0d65c26a180 Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Sat, 6 Dec 2025 10:34:29 +0100 Subject: [PATCH 1239/3902] HACK: rust: pin-init: Disable references to previously initialized fields aop_audio uses pin-init on `#[repr(C, packed)]` embedded into other structs. This fails with "error[E0793]: reference to packed field is unaligned" since packed struct have an alingment of 1 byte. Fixes: 42415d163e5d ("rust: pin-init: add references to previously initialized fields") --- rust/pin-init/src/macros.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/rust/pin-init/src/macros.rs b/rust/pin-init/src/macros.rs index d6acf2cd291e03..130f571fdb4d70 100644 --- a/rust/pin-init/src/macros.rs +++ b/rust/pin-init/src/macros.rs @@ -1353,8 +1353,8 @@ macro_rules! __init_internal { // - the field is not structurally pinned, since the line above must compile, // - the field has been initialized, // - the reference is only valid until the end of the initializer. - #[allow(unused_variables)] - let $field = unsafe { &mut (*$slot).$field }; + // #[allow(unused_variables)] + // let $field = unsafe { &mut (*$slot).$field }; // Create the drop guard: // @@ -1395,7 +1395,7 @@ macro_rules! __init_internal { // initializer, // - the field has been initialized, // - the reference is only valid until the end of the initializer. - let $field = unsafe { &mut (*$slot).$field }; + // let $field = unsafe { &mut (*$slot).$field }; // Create the drop guard: // From dd6a48fd6ea62dd5f78c36779169f6b71c0c873a Mon Sep 17 00:00:00 2001 From: Lyude Paul Date: Thu, 8 May 2025 14:52:43 -0400 Subject: [PATCH 1240/3902] drm/gem/shmem: Extract drm_gem_shmem_init() from drm_gem_shmem_create() With gem objects in rust, the most ideal way for us to be able to handle gem shmem object creation is to be able to handle the memory allocation of a gem object ourselves - and then have the DRM gem shmem helpers initialize the object we've allocated afterwards. So, let's spit out drm_gem_shmem_init() from drm_gem_shmem_create() to allow for doing this. Signed-off-by: Lyude Paul --- drivers/gpu/drm/drm_gem_shmem_helper.c | 75 +++++++++++++++++--------- include/drm/drm_gem_shmem_helper.h | 1 + 2 files changed, 51 insertions(+), 25 deletions(-) diff --git a/drivers/gpu/drm/drm_gem_shmem_helper.c b/drivers/gpu/drm/drm_gem_shmem_helper.c index 5d1349c34afd3d..b20a7b75c72288 100644 --- a/drivers/gpu/drm/drm_gem_shmem_helper.c +++ b/drivers/gpu/drm/drm_gem_shmem_helper.c @@ -48,28 +48,12 @@ static const struct drm_gem_object_funcs drm_gem_shmem_funcs = { .vm_ops = &drm_gem_shmem_vm_ops, }; -static struct drm_gem_shmem_object * -__drm_gem_shmem_create(struct drm_device *dev, size_t size, bool private, - struct vfsmount *gemfs) +static int __drm_gem_shmem_init(struct drm_device *dev, struct drm_gem_shmem_object *shmem, + size_t size, bool private, struct vfsmount *gemfs) { - struct drm_gem_shmem_object *shmem; - struct drm_gem_object *obj; + struct drm_gem_object *obj = &shmem->base; int ret = 0; - size = PAGE_ALIGN(size); - - if (dev->driver->gem_create_object) { - obj = dev->driver->gem_create_object(dev, size); - if (IS_ERR(obj)) - return ERR_CAST(obj); - shmem = to_drm_gem_shmem_obj(obj); - } else { - shmem = kzalloc(sizeof(*shmem), GFP_KERNEL); - if (!shmem) - return ERR_PTR(-ENOMEM); - obj = &shmem->base; - } - if (!obj->funcs) obj->funcs = &drm_gem_shmem_funcs; @@ -81,7 +65,7 @@ __drm_gem_shmem_create(struct drm_device *dev, size_t size, bool private, } if (ret) { drm_gem_private_object_fini(obj); - goto err_free; + return ret; } ret = drm_gem_create_mmap_offset(obj); @@ -102,14 +86,55 @@ __drm_gem_shmem_create(struct drm_device *dev, size_t size, bool private, __GFP_RETRY_MAYFAIL | __GFP_NOWARN); } - return shmem; - + return 0; err_release: drm_gem_object_release(obj); -err_free: - kfree(obj); + return ret; +} - return ERR_PTR(ret); +/** + * drm_gem_shmem_init - Initialize an allocated object. + * @dev: DRM device + * @obj: The allocated shmem GEM object. + * + * Returns: + * 0 on success, or a negative error code on failure. + */ +int drm_gem_shmem_init(struct drm_device *dev, struct drm_gem_shmem_object *shmem, size_t size) +{ + return __drm_gem_shmem_init(dev, shmem, size, false, NULL); +} +EXPORT_SYMBOL_GPL(drm_gem_shmem_init); + +static struct drm_gem_shmem_object * +__drm_gem_shmem_create(struct drm_device *dev, size_t size, bool private, + struct vfsmount *gemfs) +{ + struct drm_gem_shmem_object *shmem; + struct drm_gem_object *obj; + int ret = 0; + + size = PAGE_ALIGN(size); + + if (dev->driver->gem_create_object) { + obj = dev->driver->gem_create_object(dev, size); + if (IS_ERR(obj)) + return ERR_CAST(obj); + shmem = to_drm_gem_shmem_obj(obj); + } else { + shmem = kzalloc(sizeof(*shmem), GFP_KERNEL); + if (!shmem) + return ERR_PTR(-ENOMEM); + obj = &shmem->base; + } + + ret = __drm_gem_shmem_init(dev, shmem, size, private, gemfs); + if (ret) { + kfree(obj); + return ERR_PTR(ret); + } + + return shmem; } /** * drm_gem_shmem_create - Allocate an object with the given size diff --git a/include/drm/drm_gem_shmem_helper.h b/include/drm/drm_gem_shmem_helper.h index 92f5db84b9c22e..235dc33127b9a8 100644 --- a/include/drm/drm_gem_shmem_helper.h +++ b/include/drm/drm_gem_shmem_helper.h @@ -107,6 +107,7 @@ struct drm_gem_shmem_object { #define to_drm_gem_shmem_obj(obj) \ container_of(obj, struct drm_gem_shmem_object, base) +int drm_gem_shmem_init(struct drm_device *dev, struct drm_gem_shmem_object *shmem, size_t size); struct drm_gem_shmem_object *drm_gem_shmem_create(struct drm_device *dev, size_t size); struct drm_gem_shmem_object *drm_gem_shmem_create_with_mnt(struct drm_device *dev, size_t size, From 559294a3c84fdd60f8e4bf7c0e37d4ef1a1e051e Mon Sep 17 00:00:00 2001 From: Sasha Finkelstein Date: Sun, 10 Nov 2024 23:23:21 +0100 Subject: [PATCH 1241/3902] rust: helpers: Add dma_mapping_error() helper Used by Apple SEP driver. Signed-off-by: Sasha Finkelstein --- rust/helpers/dma-mapping.c | 8 ++++++++ rust/helpers/helpers.c | 1 + 2 files changed, 9 insertions(+) create mode 100644 rust/helpers/dma-mapping.c diff --git a/rust/helpers/dma-mapping.c b/rust/helpers/dma-mapping.c new file mode 100644 index 00000000000000..0d795b1b0738dc --- /dev/null +++ b/rust/helpers/dma-mapping.c @@ -0,0 +1,8 @@ +// SPDX-License-Identifier: GPL-2.0 + +#include + +int rust_helper_dma_mapping_error(struct device *dev, dma_addr_t dma_addr) +{ + return dma_mapping_error(dev, dma_addr); +} diff --git a/rust/helpers/helpers.c b/rust/helpers/helpers.c index 551da6c9b5064c..a9e6de587bf657 100644 --- a/rust/helpers/helpers.c +++ b/rust/helpers/helpers.c @@ -25,6 +25,7 @@ #include "cred.c" #include "device.c" #include "dma.c" +#include "dma-mapping.c" #include "drm.c" #include "err.c" #include "irq.c" From 22b9e8e7540164a8e4912819f16883dd90be9bb1 Mon Sep 17 00:00:00 2001 From: Lyude Paul Date: Thu, 1 May 2025 12:57:20 -0400 Subject: [PATCH 1242/3902] drm/gem/shmem: Extract drm_gem_shmem_release() from drm_gem_shmem_free() At the moment the way that freeing gem shmem objects is not ideal for rust bindings. drm_gem_shmem_free() releases all of the associated memory with a gem shmem object with kfree(), which means that for us to correctly release a gem shmem object in rust we have to manually drop all of the contents of our gem object structure in-place by hand before finally calling drm_gem_shmem_free() to release the shmem resources and the allocation for the gem object. Since the only reason this is an issue is because of drm_gem_shmem_free() calling kfree(), we can fix this by splitting drm_gem_shmem_free() out into itself and drm_gem_shmem_release(), where drm_gem_shmem_release() releases the various gem shmem resources without freeing the structure itself. With this, we can safely re-acquire the KBox for the gem object's memory allocation and let rust handle cleaning up all of the other struct members automatically. Signed-off-by: Lyude Paul --- drivers/gpu/drm/drm_gem_shmem_helper.c | 23 ++++++++++++++++++----- include/drm/drm_gem_shmem_helper.h | 1 + 2 files changed, 19 insertions(+), 5 deletions(-) diff --git a/drivers/gpu/drm/drm_gem_shmem_helper.c b/drivers/gpu/drm/drm_gem_shmem_helper.c index b20a7b75c72288..50594cf8e17cc7 100644 --- a/drivers/gpu/drm/drm_gem_shmem_helper.c +++ b/drivers/gpu/drm/drm_gem_shmem_helper.c @@ -175,13 +175,13 @@ struct drm_gem_shmem_object *drm_gem_shmem_create_with_mnt(struct drm_device *de EXPORT_SYMBOL_GPL(drm_gem_shmem_create_with_mnt); /** - * drm_gem_shmem_free - Free resources associated with a shmem GEM object - * @shmem: shmem GEM object to free + * drm_gem_shmem_release - Release resources associated with a shmem GEM object. + * @shmem: shmem GEM object * - * This function cleans up the GEM object state and frees the memory used to - * store the object itself. + * This function cleans up the GEM object state, but does not free the memory used to store the + * object itself. This function is meant to be a dedicated helper for the Rust GEM bindings. */ -void drm_gem_shmem_free(struct drm_gem_shmem_object *shmem) +void drm_gem_shmem_release(struct drm_gem_shmem_object *shmem) { struct drm_gem_object *obj = &shmem->base; @@ -208,6 +208,19 @@ void drm_gem_shmem_free(struct drm_gem_shmem_object *shmem) } drm_gem_object_release(obj); +} +EXPORT_SYMBOL_GPL(drm_gem_shmem_release); + +/** + * drm_gem_shmem_free - Free resources associated with a shmem GEM object + * @shmem: shmem GEM object to free + * + * This function cleans up the GEM object state and frees the memory used to + * store the object itself. + */ +void drm_gem_shmem_free(struct drm_gem_shmem_object *shmem) +{ + drm_gem_shmem_release(shmem); kfree(shmem); } EXPORT_SYMBOL_GPL(drm_gem_shmem_free); diff --git a/include/drm/drm_gem_shmem_helper.h b/include/drm/drm_gem_shmem_helper.h index 235dc33127b9a8..589f7bfe7506eb 100644 --- a/include/drm/drm_gem_shmem_helper.h +++ b/include/drm/drm_gem_shmem_helper.h @@ -112,6 +112,7 @@ struct drm_gem_shmem_object *drm_gem_shmem_create(struct drm_device *dev, size_t struct drm_gem_shmem_object *drm_gem_shmem_create_with_mnt(struct drm_device *dev, size_t size, struct vfsmount *gemfs); +void drm_gem_shmem_release(struct drm_gem_shmem_object *shmem); void drm_gem_shmem_free(struct drm_gem_shmem_object *shmem); void drm_gem_shmem_put_pages_locked(struct drm_gem_shmem_object *shmem); From 203c375ebbe3d08f067bef64fb6ec8ae2ea6c192 Mon Sep 17 00:00:00 2001 From: Lyude Paul Date: Tue, 2 Dec 2025 17:03:33 -0500 Subject: [PATCH 1243/3902] rust: Introduce iosys_map bindings This introduces a set of bindings for working with iosys_map in rust code. The design of this is heavily based off the design for both the io and dma_map bindings for Rust. Signed-off-by: Lyude Paul --- rust/helpers/helpers.c | 1 + rust/helpers/iosys_map.c | 15 + rust/kernel/iosys_map.rs | 614 +++++++++++++++++++++++++++++++++++++++ rust/kernel/lib.rs | 1 + 4 files changed, 631 insertions(+) create mode 100644 rust/helpers/iosys_map.c create mode 100644 rust/kernel/iosys_map.rs diff --git a/rust/helpers/helpers.c b/rust/helpers/helpers.c index a9e6de587bf657..341bd26903a283 100644 --- a/rust/helpers/helpers.c +++ b/rust/helpers/helpers.c @@ -31,6 +31,7 @@ #include "irq.c" #include "fs.c" #include "io.c" +#include "iosys_map.c" #include "jump_label.c" #include "kunit.c" #include "maple_tree.c" diff --git a/rust/helpers/iosys_map.c b/rust/helpers/iosys_map.c new file mode 100644 index 00000000000000..b105261c3cf8aa --- /dev/null +++ b/rust/helpers/iosys_map.c @@ -0,0 +1,15 @@ +// SPDX-License-Identifier: GPL-2.0 + +#include + +void rust_helper_iosys_map_memcpy_to(struct iosys_map *dst, size_t dst_offset, + const void *src, size_t len) +{ + iosys_map_memcpy_to(dst, dst_offset, src, len); +} + +void rust_helper_iosys_map_memcpy_from(void *dst, const struct iosys_map *src, + size_t src_offset, size_t len) +{ + iosys_map_memcpy_from(dst, src, src_offset, len); +} diff --git a/rust/kernel/iosys_map.rs b/rust/kernel/iosys_map.rs new file mode 100644 index 00000000000000..884a3d2be3348d --- /dev/null +++ b/rust/kernel/iosys_map.rs @@ -0,0 +1,614 @@ +// SPDX-License-Identifier: GPL-2.0 + +//! IO-agnostic memory mapping interfaces. +//! +//! This crate provides bindings for the `struct iosys_map` type, which provides a common interface +//! for memory mappings which can reside within coherent memory, or within IO memory. +//! +//! C header: [`include/linux/iosys-map.h`](srctree/include/linux/pci.h) + +use crate::{ + prelude::*, + transmute::{AsBytes, FromBytes}, +}; +use bindings; +use core::{ + marker::PhantomData, + mem::{self, MaybeUninit}, + ops::{Deref, DerefMut, Range}, +}; + +/// Raw unsized representation of a `struct iosys_map`. +/// +/// This struct is a transparent wrapper around `struct iosys_map`. The C API does not provide the +/// size of the mapping by default, and thus this type also does not include the size of the +/// mapping. As such, it cannot be used for actually accessing the underlying data pointed to by the +/// mapping. +/// +/// With the exception of kernel crates which may provide their own wrappers around `RawIoSysMap`, +/// users will typically not interact with this type directly. +pub struct RawIoSysMap(bindings::iosys_map, PhantomData); + +impl RawIoSysMap { + /// Convert from a raw `bindings::iosys_map`. + #[expect(unused)] + #[inline] + pub(crate) fn from_raw(val: bindings::iosys_map) -> Self { + Self(val, PhantomData) + } + + /// Convert from a `RawIoSysMap` to a raw `bindings::iosys_map` ref. + #[inline] + pub(crate) fn as_raw(&self) -> &bindings::iosys_map { + &self.0 + } + + /// Convert from a `RawIoSysMap` to a raw mutable `bindings::iosys_map` ref. + #[inline] + pub(crate) fn as_raw_mut(&mut self) -> &mut bindings::iosys_map { + &mut self.0 + } + + /// Returns whether the mapping is within IO memory space or not. + #[inline] + pub fn is_iomem(&self) -> bool { + self.0.is_iomem + } + + /// Returns the size of a single item in this mapping. + pub const fn item_size(&self) -> usize { + mem::size_of::() + } + + /// Returns a mutable address to the memory pointed to by this iosys map. + /// + /// Note that this address is not guaranteed to reside in system memory, and may reside in IO + /// memory. + #[inline] + pub fn as_mut_ptr(&self) -> *mut T { + if self.is_iomem() { + // SAFETY: We confirmed above that this iosys map is contained within iomem, so it's + // safe to read vaddr_iomem + unsafe { self.0.__bindgen_anon_1.vaddr_iomem } + } else { + // SAFETY: We confirmed above that this iosys map is not contaned within iomem, so it's + // safe to read vaddr. + unsafe { self.0.__bindgen_anon_1.vaddr } + } + .cast() + } + + /// Returns an immutable address to the memory pointed to by this iosys map. + /// + /// Note that this address is not guaranteed to reside in system memory, and may reside in IO + /// memory. + #[inline] + pub fn as_ptr(&self) -> *const T { + self.as_mut_ptr().cast_const() + } +} + +// SAFETY: As we make no guarantees about the validity of the mapping, there's no issue with sending +// this type between threads. +unsafe impl Send for RawIoSysMap {} + +impl Clone for RawIoSysMap { + fn clone(&self) -> Self { + Self(self.0, PhantomData) + } +} + +/// A sized version of a [`RawIoSysMap`]. +/// +/// Since this type includes the size of the [`RawIoSysMap`], it can be used for accessing the +/// underlying data pointed to by it. +/// +/// # Invariants +/// +/// - The iosys mapping referenced by this type is guaranteed to be of at least `size` bytes in +/// size +/// - The iosys mapping referenced by this type is valid for the lifetime `'a`. +#[derive(Clone)] +pub struct IoSysMapRef<'a, T: AsBytes + FromBytes> { + map: RawIoSysMap, + size: usize, + _p: PhantomData<&'a T>, +} + +impl<'a, T: AsBytes + FromBytes> IoSysMapRef<'a, T> { + /// Create a new [`IoSysMapRef`] from a [`RawIoSysMap`]. + /// + /// # Safety + /// + /// - The caller guarantees that the mapping referenced by `map` is of at least `size` bytes in + /// size. + /// - The caller guarantees that the mapping referenced by `map` remains valid for the lifetime + /// of `'a`. + #[allow(unused)] + pub(crate) unsafe fn new(map: RawIoSysMap, size: usize) -> IoSysMapRef<'a, T> { + // INVARIANT: Our safety contract fulfills the type invariants of `IoSysMapRef`. + IoSysMapRef { + map, + size, + _p: PhantomData, + } + } + + /// Return the size of the `IoSysMapRef`. + #[inline] + pub fn size(&self) -> usize { + self.size + } + + /// Writes `src` to the region starting from `offset`. + /// + /// `offset` is in units of `T`, not the number of bytes. + /// + /// This function can return the following errors: + /// + /// * [`EOVERFLOW`] if calculating the length of the slice results in an overflow. + /// * [`EINVAL`] if the slice would go out of bounds of the memory region. + /// + /// # Examples + /// + /// ``` + /// use kernel::iosys_map::*; + /// + /// # fn test() -> Result { + /// # let mut map = tests::VecIoSysMap::new(&[0; 3])?; + /// # { + /// # let mut map = map.get(); + /// map.write(&[1, 2, 3], 0)?; // (now [1, 2, 3]) + /// map.write(&[4], 2)?; // (now [1, 2, 4]) + /// # } + /// # + /// # map.assert_eq(&[1, 2, 4]); + /// # + /// # Ok::<(), Error>(()) } + /// # assert!(test().is_ok()); + /// ``` + pub fn write(&mut self, src: &[T], offset: usize) -> Result { + let range = self.compute_range(offset, src.len())?; + + // SAFETY: + // - The address pointed to by this iosys_map is guaranteed to be valid via IoSysMapRef's + // type invariants. + // - We checked that this range of memory is within bounds above + unsafe { + bindings::iosys_map_memcpy_to( + self.as_raw_mut(), + range.start, + src.as_ptr().cast(), + range.len(), + ) + }; + + Ok(()) + } + + /// Attempt to compute the offset of an item within the iosys map using its index. + /// + /// Returns an error if an overflow occurs. + /// + /// # Safety + /// + /// This function checks for overflows, but it explicitly does not check if the offset goes out + /// of bounds. It is the caller's responsibility to check for this before using the returned + /// offset with the iosys_map API. + unsafe fn item_from_index(&self, idx: usize) -> Result { + self.item_size().checked_mul(idx).ok_or(EOVERFLOW) + } + + /// Compute the range within this mapping a specific data type at a given offset would occupy. + /// + /// This function returns the computed range if it doesn't overflow, but does not check whether + /// or not the range is within the bounds of the allocated region pointed to by this iosys + /// mapping. + /// + /// On success, the range returned by this function is guaranteed: + /// + /// * To be a valid range of memory within the virtual mapping for this gem object. + /// * To be properly aligned to [`RawIoSysMap::item_size()`]. + fn compute_range(&self, offset: usize, count: usize) -> Result> { + // SAFETY: If the offset is out of bounds, we'll catch this via overflow checks or when + // checking range_end. + let offset = unsafe { self.item_from_index(offset)? }; + let range_size = count.checked_mul(self.item_size()).ok_or(EOVERFLOW)?; + let range_end = offset.checked_add(range_size).ok_or(EOVERFLOW)?; + + if range_end > self.size { + return Err(EINVAL); + } + + // INVARIANT: Since `offset` and `count` are both in units of `T`, we're guaranteed that the + // range returned here is properly aligned to `T`. + Ok(offset..range_end) + } + + /// Common helper to compute the memory address of an item within the iosys mapping. + /// + /// Public but hidden, since it should only be used from [`iosys_map_read`] and + /// [`iosys_map_write`]. + #[doc(hidden)] + pub fn ptr_from_index(&self, offset: usize) -> Result<*mut T> { + // SAFETY: We check if the resulting offset goes out of bounds below. + let offset = unsafe { self.item_from_index(offset)? }; + + if offset.checked_add(self.item_size()).ok_or(EOVERFLOW)? > self.size() { + return Err(EINVAL); + } + + // SAFETY: We confirmed that `offset` + the item size does not go out of bounds above. + Ok(unsafe { self.as_mut_ptr().byte_add(offset) }) + } + + // TODO: + // This function is currently needed for making the iosys_map_read!() and iosys_map_write!() + // macros work due to a combination of a few limitations: + // + // * The current C API for iosys_map requires that we use offsets for reading/writing + // iosys_maps. + // * Calculating the offset of a field within a struct requires that we either: + // * Use field projection for calculating the offset of the field. We don't have this yet. + // * Explicitly specify the type of the struct, which would be cumbersome to require in the + // read/write macros. + // * Provide a typed pointer (or other reference) to the struct in question, allowing the + // use of &raw const and &raw mut. + // * Keep in mind: we can't simply cast the offset of an item in the iosys map into a typed + // pointer to fulfill the third option. While having invalid memory addresses as pointers + // is ok, adding an offset to a pointer in rust requires that the resulting memory address + // is within the same allocation. Since an invalid pointer has no allocation, we can't + // make that guarantee. + // + // So, until we have field projection the way we workaround this: + // + // * Calculate the offset (self.item_from_index()) of the struct within the iosys map + // * Calculate the memory address of the struct using the offset from the last step + // (self.ptr_from_index()). + // * Use that memory address with &raw const/&raw mut in order to calculate the memory address + // of the desired field, ensuring it remains in the same allocation (happens within the + // macros). + // * Convert the address from the last step back into an offset within the iosys map + // (offset_from_ptr()). + // + // Once we do get field projection, this silly code should be removed. + // + /// Convert a pointer to an item within the iosys map back into an offset. + /// + /// # Safety + /// + /// `ptr` must be a valid pointer to data within the iosys map. + unsafe fn offset_from_ptr(&self, ptr: *const F) -> usize { + // SAFETY: `ptr` always points to data within the memory pointed to by the iosys map, + // meaning it is within the same memory allocation. + // + // Additionally, since `ptr` is within the iosys mapping, the offset here will always be + // positive and safe to cast to a usize. + // (TODO: replace this with byte_offset_from_unsigned once it's available in the kernel) + unsafe { ptr.byte_offset_from(self.as_ptr()) as usize } + } + + /// Reads the value of `field` and ensures that its type is [`FromBytes`]. + /// + /// # Safety + /// + /// This must be called from the [`iosys_map_read`] macro which ensures that the `field` + /// pointer is validated beforehand. + /// + /// Public but hidden since it should only be used from the [`iosys_map_read`] macro. + #[doc(hidden)] + pub unsafe fn field_read(&self, field: *const F) -> F { + let mut field_val = MaybeUninit::::uninit(); + + // SAFETY: `field` is guaranteed valid via our safety contract. + let offset = unsafe { self.offset_from_ptr(field) }; + + // SAFETY: Since we verified `field` is valid above, `offset_from_ptr` will always return a + // valid offset within the iosys map. + unsafe { + bindings::iosys_map_memcpy_from( + field_val.as_mut_ptr().cast(), + self.as_raw(), + offset, + mem::size_of::(), + ) + } + + // SAFETY: We just initialized `field_val` above. + unsafe { field_val.assume_init() } + } + + /// Writes the value of `field` and ensures that its type is [`AsBytes`]. + /// + /// # Safety + /// + /// This must be called from the [`iosys_map_write`] macro which ensures that the `field` + /// pointers validated beforehand. + /// + /// Public but hidden since it should only be used from the [`iosys_map_write`] macro. + #[doc(hidden)] + pub unsafe fn field_write(&mut self, field: *mut F, val: F) { + // SAFETY: `field` is guaranteed valid via our safety contract. + let offset = unsafe { self.offset_from_ptr(field) }; + + // SAFETY: `offset_from_ptr` always returns a valid offset within the iosys map. + unsafe { + bindings::iosys_map_memcpy_to( + self.as_raw_mut(), + offset, + core::ptr::from_ref(&val).cast(), + mem::size_of::(), + ) + } + } +} + +impl<'a, T: AsBytes + FromBytes> Deref for IoSysMapRef<'a, T> { + type Target = RawIoSysMap; + + fn deref(&self) -> &Self::Target { + &self.map + } +} + +impl<'a, T: AsBytes + FromBytes> DerefMut for IoSysMapRef<'a, T> { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.map + } +} + +/// Reads from a field of an item from an iosys map ref. +/// +/// # Examples +/// +/// ``` +/// use kernel::{iosys_map::*, transmute::*}; +/// +/// #[derive(Copy, Clone, Debug, PartialEq, Eq)] +/// struct MyStruct { a: u32, b: u16 } +/// +/// // SAFETY: All bit patterns are acceptable values for `MyStruct`. +/// unsafe impl FromBytes for MyStruct {}; +/// // SAFETY: Instances of `MyStruct` have no uninitialized portions. +/// unsafe impl AsBytes for MyStruct {}; +/// +/// # fn test() -> Result { +/// # let mut map = tests::VecIoSysMap::new(&[MyStruct { a: 42, b: 2 }; 3])?; +/// # let map = map.get(); +/// let whole = kernel::iosys_map_read!(map[2])?; +/// assert_eq!(whole, MyStruct { a: 42, b: 2 }); +/// +/// let field = kernel::iosys_map_read!(map[1].b)?; +/// assert_eq!(field, 2); +/// # Ok::<(), Error>(()) } +/// # assert!(test().is_ok()); +/// ``` +#[macro_export] +macro_rules! iosys_map_read { + ($map:expr, $idx:expr, $($field:tt)*) => {{ + (|| -> ::core::result::Result<_, $crate::error::Error> { + let map = &$map; + let item = $crate::iosys_map::IoSysMapRef::ptr_from_index(map, $idx)?; + + // SAFETY: `ptr_from_index()` ensures that `item` is always a valid (although + // potentially not dereferenceable, which is fine here) pointer to within the iosys + // mapping. + unsafe { + let ptr_field = &raw const (*item) $($field)*; + ::core::result::Result::Ok( + $crate::iosys_map::IoSysMapRef::field_read(map, ptr_field) + ) + } + })() + }}; + ($map:ident [ $idx: expr ] $($field:tt)* ) => { + $crate::iosys_map_read!($map, $idx, $($field)*) + }; + ($($map:ident).* [ $idx:expr ] $($field:tt)* ) => { + $crate::iosys_map_read!($($map).*, $idx, $($field)*) + }; +} + +/// Writes to a field of an item from an iosys map ref. +/// +/// # Examples +/// +/// ``` +/// use kernel::{iosys_map::*, transmute::*}; +/// +/// #[derive(Copy, Clone, Debug, PartialEq, Eq)] +/// struct MyStruct { a: u32, b: u16 }; +/// +/// // SAFETY: All bit patterns are acceptable values for `MyStruct`. +/// unsafe impl FromBytes for MyStruct {}; +/// // SAFETY: Instances of `MyStruct` have no uninitialized portions. +/// unsafe impl AsBytes for MyStruct {}; +/// +/// # fn test() -> Result { +/// # let mut map = tests::VecIoSysMap::new(&[MyStruct { a: 42, b: 2 }; 3])?; +/// # let mut map = map.get(); +/// kernel::iosys_map_write!(map[2].b = 1337)?; +/// # assert_eq!(kernel::iosys_map_read!(map[2].b)?, 1337); +/// +/// kernel::iosys_map_write!(map[1] = MyStruct { a: 10, b: 20 })?; +/// # assert_eq!(kernel::iosys_map_read!(map[1])?, MyStruct { a: 10, b: 20 }); +/// # Ok::<(), Error>(()) } +/// # assert!(test().is_ok()); +/// ``` +#[macro_export] +macro_rules! iosys_map_write { + ($map:ident [ $idx:expr ] $($field:tt)*) => {{ + $crate::iosys_map_write!($map, $idx, $($field)*) + }}; + ($($map:ident).* [ $idx:expr ] $($field:tt)* ) => {{ + $crate::iosys_map_write!($($map).*, $idx, $($field)*) + }}; + ($map:expr, $idx:expr, = $val:expr) => { + (|| -> ::core::result::Result<_, $crate::error::Error> { + // (expand these outside of the unsafe block (clippy::macro-metavars-in-unsafe) + let map = &mut $map; + let val = $val; + + let item = $crate::iosys_map::IoSysMapRef::ptr_from_index(map, $idx)?; + // SAFETY: `item_from_index` ensures that `item` is always a valid item. + unsafe { $crate::iosys_map::IoSysMapRef::field_write(map, item, val) }; + ::core::result::Result::Ok(()) + })() + }; + ($map:expr, $idx:expr, $(.$field:ident)* = $val:expr) => { + (|| -> ::core::result::Result<_, $crate::error::Error> { + // (expand these outside of the unsafe block (clippy::macro-metavars-in-unsafe) + let map = &mut $map; + let val = $val; + + let item = $crate::iosys_map::IoSysMapRef::ptr_from_index(map, $idx)?; + + // SAFETY: `ptr_from_index()` ensures that `item` is always a valid (although + // potentially not dereferenceable, which is fine here) pointer to within the iosys + // mapping. + unsafe { + let ptr_field = &raw mut (*item) $(.$field)*; + $crate::iosys_map::IoSysMapRef::field_write(map, ptr_field, val) + }; + ::core::result::Result::Ok(()) + })() + }; +} + +#[doc(hidden)] +#[kunit_tests(rust_iosys_map)] +pub mod tests { + use super::*; + + /// A helper struct for managed IoSysMapRef structs which point to a [`Vec`]. + pub struct VecIoSysMap { + map: RawIoSysMap, + vec: KVec, + } + + impl VecIoSysMap { + pub fn new(src: &[T]) -> Result { + let mut vec = KVec::::new(); + + vec.extend_from_slice(src, GFP_KERNEL)?; + + let map = RawIoSysMap( + bindings::iosys_map { + is_iomem: false, + __bindgen_anon_1: bindings::iosys_map__bindgen_ty_1 { + vaddr: vec.as_mut_ptr().cast(), + }, + }, + PhantomData, + ); + + Ok(Self { map, vec }) + } + + pub fn get(&mut self) -> IoSysMapRef<'_, T> { + // SAFETY: + // * `map` points to `vec`, so the size of `map` is the size of the `vec`. + unsafe { IoSysMapRef::new(self.map.clone(), self.vec.len() * self.map.item_size()) } + } + + /// Assert whether or not the contents of this struct match src. + pub fn assert_eq(&self, src: &[T]) { + assert_eq!(*self.vec.as_ref(), *src) + } + } + + #[test] + fn basic() -> Result { + let mut map = VecIoSysMap::new(&[0; 3])?; + + map.get().write(&[1, 2, 3], 0)?; + map.assert_eq(&[1, 2, 3]); + + map.get().write(&[42], 1)?; + map.assert_eq(&[1, 42, 3]); + + Ok(()) + } + + #[test] + fn oob_accesses() -> Result { + let mut map = VecIoSysMap::new(&[0; 3])?; + + assert!(map.get().write(&[1, 2, 3, 69], 0).is_err()); + assert!(map.get().write(&[1, 2, 3], 69).is_err()); + map.assert_eq(&[0; 3]); + + Ok(()) + } + + #[test] + fn overflows() -> Result { + let mut map = VecIoSysMap::new(&[0; 3])?; + + assert!(map.get().write(&[1], usize::MAX).is_err()); + map.assert_eq(&[0; 3]); + + Ok(()) + } + + #[derive(Copy, Clone, Debug, PartialEq, Eq)] + struct TestStruct { + a: u32, + b: u64, + } + + // SAFETY: All bit patterns are acceptable values for `TestStruct`. + unsafe impl FromBytes for TestStruct {} + // SAFETY: Instances of `TestStruct` have no uninitialized portions. + unsafe impl AsBytes for TestStruct {} + + #[test] + fn basic_macro() -> Result { + let mut expected = [TestStruct { a: 1, b: 2 }; 5]; + let mut map = VecIoSysMap::new(&expected)?; + + { + let mut map_ref = map.get(); + + iosys_map_write!(map_ref[3].a = u32::MAX)?; + expected[3].a = u32::MAX; + + assert_eq!(iosys_map_read!(map_ref[3].a)?, u32::MAX); + assert_eq!( + iosys_map_read!(map_ref[3])?, + TestStruct { a: u32::MAX, b: 2 } + ); + } + + // Compare the entire array, so that we catch any mis-sized writes. + map.assert_eq(&expected); + + Ok(()) + } + + #[test] + fn macro_oob_accesses() -> Result { + let mut map = VecIoSysMap::new(&[TestStruct { a: 1, b: 2 }; 3])?; + let mut map = map.get(); + + assert!(iosys_map_read!(map[5].b).is_err()); + assert!(iosys_map_read!(map[1000]).is_err()); + assert!(iosys_map_write!(map[6969].a = 999).is_err()); + assert!(iosys_map_write!(map[243] = TestStruct { a: 99, b: 22 }).is_err()); + + Ok(()) + } + + #[test] + fn macro_overflows() -> Result { + let mut map = VecIoSysMap::new(&[TestStruct { a: 1, b: 2 }; 3])?; + let mut map = map.get(); + + assert!(iosys_map_read!(map[usize::MAX]).is_err()); + assert!(iosys_map_read!(map[usize::MAX].b).is_err()); + assert!(iosys_map_write!(map[usize::MAX] = TestStruct { a: 1, b: 1 }).is_err()); + assert!(iosys_map_write!(map[usize::MAX].b = 1).is_err()); + + Ok(()) + } +} diff --git a/rust/kernel/lib.rs b/rust/kernel/lib.rs index 5568ad0ccc755d..0a1bc71776b3b7 100644 --- a/rust/kernel/lib.rs +++ b/rust/kernel/lib.rs @@ -113,6 +113,7 @@ pub mod id_pool; pub mod init; pub mod io; pub mod ioctl; +pub mod iosys_map; pub mod iov; pub mod irq; pub mod jump_label; From e4b0895ffdb5e73a7ce9fc7c814b4d97cc4e629e Mon Sep 17 00:00:00 2001 From: Lyude Paul Date: Tue, 2 Dec 2025 17:03:27 -0500 Subject: [PATCH 1244/3902] rust/drm: Add gem::impl_aref_for_gem_obj! In the future we're going to be introducing more GEM object types in rust then just gem::Object. Since all types of GEM objects have refcounting, let's introduce a macro that we can use in the gem crate in order to copy this boilerplate implementation for each type: impl_aref_for_gem_obj!(). Signed-off-by: Lyude Paul Reviewed-by: Daniel Almeida --- rust/kernel/drm/gem/mod.rs | 53 +++++++++++++++++++++++++++----------- 1 file changed, 38 insertions(+), 15 deletions(-) diff --git a/rust/kernel/drm/gem/mod.rs b/rust/kernel/drm/gem/mod.rs index eb5f3feac89075..807e0693b028ff 100644 --- a/rust/kernel/drm/gem/mod.rs +++ b/rust/kernel/drm/gem/mod.rs @@ -15,6 +15,43 @@ use crate::{ }; use core::{ops::Deref, ptr::NonNull}; +/// A macro for implementing [`AlwaysRefCounted`] for any GEM object type. +/// +/// Since all GEM objects use the same refcounting scheme. +#[macro_export] +macro_rules! impl_aref_for_gem_obj { + ( + impl $( <$( $tparam_id:ident ),+> )? for $type:ty + $( + where + $( $bind_param:path : $bind_trait:path ),+ + )? + ) => { + // SAFETY: All gem objects are refcounted + unsafe impl $( <$( $tparam_id ),+> )? $crate::types::AlwaysRefCounted for $type + where + Self: IntoGEMObject, + $( $( $bind_param : $bind_trait ),+ )? + { + fn inc_ref(&self) { + // SAFETY: The existence of a shared reference guarantees that the refcount is + // non-zero. + unsafe { bindings::drm_gem_object_get(self.as_raw()) }; + } + + unsafe fn dec_ref(obj: core::ptr::NonNull) { + // SAFETY: `obj` is a valid pointer to an `Object`. + let obj = unsafe { obj.as_ref() }.as_raw(); + + // SAFETY: The safety requirements guarantee that the refcount is non-zero. + unsafe { bindings::drm_gem_object_put(obj) }; + } + } + }; +} + +pub(crate) use impl_aref_for_gem_obj; + /// A type alias for retrieving a [`Driver`]s [`DriverFile`] implementation from its /// [`DriverObject`] implementation. /// @@ -252,21 +289,7 @@ impl Object { } } -// SAFETY: Instances of `Object` are always reference-counted. -unsafe impl crate::types::AlwaysRefCounted for Object { - fn inc_ref(&self) { - // SAFETY: The existence of a shared reference guarantees that the refcount is non-zero. - unsafe { bindings::drm_gem_object_get(self.as_raw()) }; - } - - unsafe fn dec_ref(obj: NonNull) { - // SAFETY: `obj` is a valid pointer to an `Object`. - let obj = unsafe { obj.as_ref() }; - - // SAFETY: The safety requirements guarantee that the refcount is non-zero. - unsafe { bindings::drm_gem_object_put(obj.as_raw()) } - } -} +impl_aref_for_gem_obj!(impl for Object where T: DriverObject); impl super::private::Sealed for Object {} From a8d61fad126b09b04cd46c2b84453f1fe41ca739 Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Sat, 6 Dec 2025 12:19:58 +0100 Subject: [PATCH 1245/3902] rust: kernel: iosys_map: Wrap iosys_map_memset() This is strange that in-so-far that it works on byte level and doesn't use IoSysMapRef. It will be used to initialize mappings in the asahi driver either to zero or for debugging purposes to special byte patterns. Signed-off-by: Janne Grunau --- rust/helpers/iosys_map.c | 6 ++++++ rust/kernel/iosys_map.rs | 33 +++++++++++++++++++++++++++++++++ 2 files changed, 39 insertions(+) diff --git a/rust/helpers/iosys_map.c b/rust/helpers/iosys_map.c index b105261c3cf8aa..f99598367147ff 100644 --- a/rust/helpers/iosys_map.c +++ b/rust/helpers/iosys_map.c @@ -13,3 +13,9 @@ void rust_helper_iosys_map_memcpy_from(void *dst, const struct iosys_map *src, { iosys_map_memcpy_from(dst, src, src_offset, len); } + +void rust_helper_iosys_map_memset(struct iosys_map *dst, size_t offset, + int value, size_t len) +{ + iosys_map_memset(dst, offset, value, len); +} diff --git a/rust/kernel/iosys_map.rs b/rust/kernel/iosys_map.rs index 884a3d2be3348d..4fe881aea55312 100644 --- a/rust/kernel/iosys_map.rs +++ b/rust/kernel/iosys_map.rs @@ -186,6 +186,39 @@ impl<'a, T: AsBytes + FromBytes> IoSysMapRef<'a, T> { Ok(()) } + /// Memset the region starting from `offset`. + /// + /// `offset` and `len` are in units of `T`, not the number of bytes. + /// + /// This function can return the following errors: + /// + /// * [`EOVERFLOW`] if calculating the length of the slice results in an overflow. + /// * [`EINVAL`] if the slice would go out of bounds of the memory region. + /// + /// # Examples + /// + /// ``` + /// use kernel::iosys_map::*; + /// + /// # fn test() -> Result { + /// # let mut map = tests::VecIoSysMap::new(&[0u8; 3])?; + /// # { + /// # let mut map = map.get(); + /// map.memset(7)?; // (now [7, 7, 7]) + /// # } + /// # + /// # map.assert_eq(&[7, 7, 7]); + /// # + /// # Ok::<(), Error>(()) } + /// # assert!(test().is_ok()); + /// ``` + pub fn memset(&mut self, value: i32) { + // SAFETY: + // - The address pointed to by this iosys_map is guaranteed to be valid via IoSysMapRef's + // type invariants. + unsafe { bindings::iosys_map_memset(self.as_raw_mut(), 0, value, self.size()) }; + } + /// Attempt to compute the offset of an item within the iosys map using its index. /// /// Returns an error if an overflow occurs. From 6a516d079ea2b0b7104ccafdc44c9bec17f8b59c Mon Sep 17 00:00:00 2001 From: Asahi Lina Date: Tue, 2 Dec 2025 17:03:28 -0500 Subject: [PATCH 1246/3902] rust: helpers: Add bindings/wrappers for dma_resv_lock This is just for basic usage in the DRM shmem abstractions for implied locking, not intended as a full DMA Reservation abstraction yet. Signed-off-by: Asahi Lina Signed-off-by: Daniel Almeida Reviewed-by: Alice Ryhl Signed-off-by: Lyude Paul --- rust/bindings/bindings_helper.h | 1 + rust/helpers/dma-resv.c | 13 +++++++++++++ rust/helpers/helpers.c | 1 + 3 files changed, 15 insertions(+) create mode 100644 rust/helpers/dma-resv.c diff --git a/rust/bindings/bindings_helper.h b/rust/bindings/bindings_helper.h index b97b86ef6d098c..4d98b0c6db5f30 100644 --- a/rust/bindings/bindings_helper.h +++ b/rust/bindings/bindings_helper.h @@ -51,6 +51,7 @@ #include #include #include +#include #include #include #include diff --git a/rust/helpers/dma-resv.c b/rust/helpers/dma-resv.c new file mode 100644 index 00000000000000..05501cb814513b --- /dev/null +++ b/rust/helpers/dma-resv.c @@ -0,0 +1,13 @@ +// SPDX-License-Identifier: GPL-2.0 + +#include + +int rust_helper_dma_resv_lock(struct dma_resv *obj, struct ww_acquire_ctx *ctx) +{ + return dma_resv_lock(obj, ctx); +} + +void rust_helper_dma_resv_unlock(struct dma_resv *obj) +{ + dma_resv_unlock(obj); +} diff --git a/rust/helpers/helpers.c b/rust/helpers/helpers.c index 341bd26903a283..a8ce4612b2a9e9 100644 --- a/rust/helpers/helpers.c +++ b/rust/helpers/helpers.c @@ -26,6 +26,7 @@ #include "device.c" #include "dma.c" #include "dma-mapping.c" +#include "dma-resv.c" #include "drm.c" #include "err.c" #include "irq.c" From 342b6ff47a97145c17dd1780d0a48acf773bdd78 Mon Sep 17 00:00:00 2001 From: Lyude Paul Date: Tue, 2 Dec 2025 17:03:29 -0500 Subject: [PATCH 1247/3902] rust: drm: gem: Add raw_dma_resv() function For retrieving a pointer to the struct dma_resv for a given GEM object. We also introduce it in a new trait, BaseObjectPrivate, which we automatically implement for all gem objects and don't expose to users outside of the crate. Signed-off-by: Lyude Paul --- rust/kernel/drm/gem/mod.rs | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/rust/kernel/drm/gem/mod.rs b/rust/kernel/drm/gem/mod.rs index 807e0693b028ff..f5c4985eab9c2c 100644 --- a/rust/kernel/drm/gem/mod.rs +++ b/rust/kernel/drm/gem/mod.rs @@ -199,6 +199,18 @@ pub trait BaseObject: IntoGEMObject { impl BaseObject for T {} +/// Crate-private base operations shared by all GEM object classes. +#[expect(unused)] +pub(crate) trait BaseObjectPrivate: IntoGEMObject { + /// Return a pointer to this object's dma_resv. + fn raw_dma_resv(&self) -> *mut bindings::dma_resv { + // SAFETY: `as_gem_obj()` always returns a valid pointer to the base DRM gem object + unsafe { (*self.as_raw()).resv } + } +} + +impl BaseObjectPrivate for T {} + /// A base GEM object. /// /// Invariants From 45092df21d3656952361b57c44184a1503d6b574 Mon Sep 17 00:00:00 2001 From: Lyude Paul Date: Tue, 2 Dec 2025 17:03:30 -0500 Subject: [PATCH 1248/3902] rust: gem: Introduce DriverObject::Args This is an associated type that may be used in order to specify a data-type to pass to gem objects when construction them, allowing for drivers to more easily initialize their private-data for gem objects. Signed-off-by: Lyude Paul Reviewed-by: Alice Ryhl Reviewed-by: Daniel Almeida --- drivers/gpu/drm/nova/gem.rs | 5 +++-- drivers/gpu/drm/tyr/gem.rs | 3 ++- rust/kernel/drm/gem/mod.rs | 13 ++++++++++--- 3 files changed, 15 insertions(+), 6 deletions(-) diff --git a/drivers/gpu/drm/nova/gem.rs b/drivers/gpu/drm/nova/gem.rs index 2760ba4f3450be..173077eeb2def1 100644 --- a/drivers/gpu/drm/nova/gem.rs +++ b/drivers/gpu/drm/nova/gem.rs @@ -18,8 +18,9 @@ pub(crate) struct NovaObject {} impl gem::DriverObject for NovaObject { type Driver = NovaDriver; + type Args = (); - fn new(_dev: &NovaDevice, _size: usize) -> impl PinInit { + fn new(_dev: &NovaDevice, _size: usize, _args: Self::Args) -> impl PinInit { try_pin_init!(NovaObject {}) } } @@ -33,7 +34,7 @@ impl NovaObject { return Err(EINVAL); } - gem::Object::new(dev, aligned_size) + gem::Object::new(dev, aligned_size, ()) } /// Look up a GEM object handle for a `File` and return an `ObjectRef` for it. diff --git a/drivers/gpu/drm/tyr/gem.rs b/drivers/gpu/drm/tyr/gem.rs index 1273bf89dbd5d7..bb5e7871efa940 100644 --- a/drivers/gpu/drm/tyr/gem.rs +++ b/drivers/gpu/drm/tyr/gem.rs @@ -11,8 +11,9 @@ pub(crate) struct TyrObject {} impl gem::DriverObject for TyrObject { type Driver = TyrDriver; + type Args = (); - fn new(_dev: &TyrDevice, _size: usize) -> impl PinInit { + fn new(_dev: &TyrDevice, _size: usize, _args: ()) -> impl PinInit { try_pin_init!(TyrObject {}) } } diff --git a/rust/kernel/drm/gem/mod.rs b/rust/kernel/drm/gem/mod.rs index f5c4985eab9c2c..57c7ddd0ffaa84 100644 --- a/rust/kernel/drm/gem/mod.rs +++ b/rust/kernel/drm/gem/mod.rs @@ -64,8 +64,15 @@ pub trait DriverObject: Sync + Send + Sized { /// Parent `Driver` for this object. type Driver: drm::Driver; + /// The data type to use for passing arguments to [`DriverObject::new`]. + type Args; + /// Create a new driver data object for a GEM object of a given size. - fn new(dev: &drm::Device, size: usize) -> impl PinInit; + fn new( + dev: &drm::Device, + size: usize, + args: Self::Args, + ) -> impl PinInit; /// Open a new handle to an existing object, associated with a File. fn open(_obj: &::Object, _file: &DriverFile) -> Result { @@ -244,11 +251,11 @@ impl Object { }; /// Create a new GEM object. - pub fn new(dev: &drm::Device, size: usize) -> Result> { + pub fn new(dev: &drm::Device, size: usize, args: T::Args) -> Result> { let obj: Pin> = KBox::pin_init( try_pin_init!(Self { obj: Opaque::new(bindings::drm_gem_object::default()), - data <- T::new(dev, size), + data <- T::new(dev, size, args), }), GFP_KERNEL, )?; From 49a1a71fe8d41aeefc76aa6e0d24e8f68b72b638 Mon Sep 17 00:00:00 2001 From: Asahi Lina Date: Tue, 2 Dec 2025 17:03:31 -0500 Subject: [PATCH 1249/3902] rust: drm: gem: shmem: Add DRM shmem helper abstraction The DRM shmem helper includes common code useful for drivers which allocate GEM objects as anonymous shmem. Add a Rust abstraction for this. Drivers can choose the raw GEM implementation or the shmem layer, depending on their needs. Signed-off-by: Asahi Lina Signed-off-by: Daniel Almeida Signed-off-by: Lyude Paul --- rust/bindings/bindings_helper.h | 2 + rust/helpers/drm.c | 48 ++++++- rust/kernel/drm/gem/mod.rs | 3 +- rust/kernel/drm/gem/shmem.rs | 225 ++++++++++++++++++++++++++++++++ 4 files changed, 276 insertions(+), 2 deletions(-) create mode 100644 rust/kernel/drm/gem/shmem.rs diff --git a/rust/bindings/bindings_helper.h b/rust/bindings/bindings_helper.h index 4d98b0c6db5f30..60a7fa3c9a9a9b 100644 --- a/rust/bindings/bindings_helper.h +++ b/rust/bindings/bindings_helper.h @@ -33,6 +33,7 @@ #include #include #include +#include #include #include #include @@ -60,6 +61,7 @@ #include #include #include +#include #include #include #include diff --git a/rust/helpers/drm.c b/rust/helpers/drm.c index 450b406c6f2739..a4e997d0b47320 100644 --- a/rust/helpers/drm.c +++ b/rust/helpers/drm.c @@ -1,6 +1,7 @@ // SPDX-License-Identifier: GPL-2.0 #include +#include #include #ifdef CONFIG_DRM @@ -20,4 +21,49 @@ __u64 rust_helper_drm_vma_node_offset_addr(struct drm_vma_offset_node *node) return drm_vma_node_offset_addr(node); } -#endif +#ifdef CONFIG_DRM_GEM_SHMEM_HELPER +void rust_helper_drm_gem_shmem_object_free(struct drm_gem_object *obj) +{ + return drm_gem_shmem_object_free(obj); +} + +void rust_helper_drm_gem_shmem_object_print_info(struct drm_printer *p, unsigned int indent, + const struct drm_gem_object *obj) +{ + drm_gem_shmem_object_print_info(p, indent, obj); +} + +int rust_helper_drm_gem_shmem_object_pin(struct drm_gem_object *obj) +{ + return drm_gem_shmem_object_pin(obj); +} + +void rust_helper_drm_gem_shmem_object_unpin(struct drm_gem_object *obj) +{ + drm_gem_shmem_object_unpin(obj); +} + +struct sg_table *rust_helper_drm_gem_shmem_object_get_sg_table(struct drm_gem_object *obj) +{ + return drm_gem_shmem_object_get_sg_table(obj); +} + +int rust_helper_drm_gem_shmem_object_vmap(struct drm_gem_object *obj, + struct iosys_map *map) +{ + return drm_gem_shmem_object_vmap(obj, map); +} + +void rust_helper_drm_gem_shmem_object_vunmap(struct drm_gem_object *obj, + struct iosys_map *map) +{ + drm_gem_shmem_object_vunmap(obj, map); +} + +int rust_helper_drm_gem_shmem_object_mmap(struct drm_gem_object *obj, struct vm_area_struct *vma) +{ + return drm_gem_shmem_object_mmap(obj, vma); +} + +#endif /* CONFIG_DRM_GEM_SHMEM_HELPER */ +#endif /* CONFIG_DRM */ diff --git a/rust/kernel/drm/gem/mod.rs b/rust/kernel/drm/gem/mod.rs index 57c7ddd0ffaa84..bf29e889548c3e 100644 --- a/rust/kernel/drm/gem/mod.rs +++ b/rust/kernel/drm/gem/mod.rs @@ -3,6 +3,8 @@ //! DRM GEM API //! //! C header: [`include/drm/drm_gem.h`](srctree/include/drm/drm_gem.h) +#[cfg(CONFIG_DRM_GEM_SHMEM_HELPER = "y")] +pub mod shmem; use crate::{ alloc::flags::*, @@ -207,7 +209,6 @@ pub trait BaseObject: IntoGEMObject { impl BaseObject for T {} /// Crate-private base operations shared by all GEM object classes. -#[expect(unused)] pub(crate) trait BaseObjectPrivate: IntoGEMObject { /// Return a pointer to this object's dma_resv. fn raw_dma_resv(&self) -> *mut bindings::dma_resv { diff --git a/rust/kernel/drm/gem/shmem.rs b/rust/kernel/drm/gem/shmem.rs new file mode 100644 index 00000000000000..45b95d60a3ec7d --- /dev/null +++ b/rust/kernel/drm/gem/shmem.rs @@ -0,0 +1,225 @@ +// SPDX-License-Identifier: GPL-2.0 + +//! DRM GEM shmem helper objects +//! +//! C header: [`include/linux/drm/drm_gem_shmem_helper.h`](srctree/include/linux/drm/drm_gem_shmem_helper.h) + +// TODO: +// - There are a number of spots here that manually acquire/release the DMA reservation lock using +// dma_resv_(un)lock(). In the future we should add support for ww mutex, expose a method to +// acquire a reference to the WwMutex, and then use that directly instead of the C functions here. + +use crate::{ + container_of, + drm::{device, driver, gem, private::Sealed}, + error::{from_err_ptr, to_result}, + prelude::*, + scatterlist, + types::{ARef, Opaque}, +}; +use core::{ + ops::{Deref, DerefMut}, + ptr::NonNull, +}; +use gem::{BaseObjectPrivate, DriverObject, IntoGEMObject}; + +/// A struct for controlling the creation of shmem-backed GEM objects. +/// +/// This is used with [`Object::new()`] to control various properties that can only be set when +/// initially creating a shmem-backed GEM object. +#[derive(Default)] +pub struct ObjectConfig<'a, T: DriverObject> { + /// Whether to set the write-combine map flag. + pub map_wc: bool, + + /// Reuse the DMA reservation from another GEM object. + /// + /// The newly created [`Object`] will hold an owned refcount to `parent_resv_obj` if specified. + pub parent_resv_obj: Option<&'a Object>, +} + +/// A shmem-backed GEM object. +/// +/// # Invariants +/// +/// `obj` contains a valid initialized `struct drm_gem_shmem_object` for the lifetime of this +/// object. +#[repr(C)] +#[pin_data] +pub struct Object { + #[pin] + obj: Opaque, + // Parent object that owns this object's DMA reservation object + parent_resv_obj: Option>>, + #[pin] + inner: T, +} + +super::impl_aref_for_gem_obj!(impl for Object where T: DriverObject); + +impl Object { + /// `drm_gem_object_funcs` vtable suitable for GEM shmem objects. + const VTABLE: bindings::drm_gem_object_funcs = bindings::drm_gem_object_funcs { + free: Some(Self::free_callback), + open: Some(super::open_callback::), + close: Some(super::close_callback::), + print_info: Some(bindings::drm_gem_shmem_object_print_info), + export: None, + pin: Some(bindings::drm_gem_shmem_object_pin), + unpin: Some(bindings::drm_gem_shmem_object_unpin), + get_sg_table: Some(bindings::drm_gem_shmem_object_get_sg_table), + vmap: Some(bindings::drm_gem_shmem_object_vmap), + vunmap: Some(bindings::drm_gem_shmem_object_vunmap), + mmap: Some(bindings::drm_gem_shmem_object_mmap), + status: None, + rss: None, + // SAFETY: `drm_gem_shmem_vm_ops` is static const on the C side, so immutable references are + // safe here and such references shall be valid forever + vm_ops: unsafe { &bindings::drm_gem_shmem_vm_ops }, + evict: None, + }; + + /// Return a raw pointer to the embedded drm_gem_shmem_object. + fn as_shmem(&self) -> *mut bindings::drm_gem_shmem_object { + self.obj.get() + } + + /// Create a new shmem-backed DRM object of the given size. + /// + /// Additional config options can be specified using `config`. + pub fn new( + dev: &device::Device, + size: usize, + config: ObjectConfig<'_, T>, + args: T::Args, + ) -> Result> { + let new: Pin> = KBox::try_pin_init( + try_pin_init!(Self { + obj <- Opaque::init_zeroed(), + parent_resv_obj: config.parent_resv_obj.map(|p| p.into()), + inner <- T::new(dev, size, args), + }), + GFP_KERNEL, + )?; + + // SAFETY: `obj.as_raw()` is guaranteed to be valid by the initialization above. + unsafe { (*new.as_raw()).funcs = &Self::VTABLE }; + + // SAFETY: The arguments are all valid via the type invariants. + to_result(unsafe { bindings::drm_gem_shmem_init(dev.as_raw(), new.as_shmem(), size) })?; + + // SAFETY: We never move out of `self`. + let new = KBox::into_raw(unsafe { Pin::into_inner_unchecked(new) }); + + // SAFETY: We're taking over the owned refcount from `drm_gem_shmem_init`. + let obj = unsafe { ARef::from_raw(NonNull::new_unchecked(new)) }; + + // Start filling out values from `config` + if let Some(parent_resv) = config.parent_resv_obj { + // SAFETY: We have yet to expose the new gem object outside of this function, so it is + // safe to modify this field. + unsafe { (*obj.obj.get()).base.resv = parent_resv.raw_dma_resv() }; + } + + // SAFETY: We have yet to expose this object outside of this function, so we're guaranteed + // to have exclusive access - thus making this safe to hold a mutable reference to. + let shmem = unsafe { &mut *obj.as_shmem() }; + shmem.set_map_wc(config.map_wc); + + Ok(obj) + } + + /// Returns the `Device` that owns this GEM object. + pub fn dev(&self) -> &device::Device { + // SAFETY: `dev` will have been initialized in `Self::new()` by `drm_gem_shmem_init()`. + unsafe { device::Device::from_raw((*self.as_raw()).dev) } + } + + extern "C" fn free_callback(obj: *mut bindings::drm_gem_object) { + // SAFETY: + // - DRM always passes a valid gem object here + // - We used drm_gem_shmem_create() in our create_gem_object callback, so we know that + // `obj` is contained within a drm_gem_shmem_object + let this = unsafe { container_of!(obj, bindings::drm_gem_shmem_object, base) }; + + // SAFETY: + // - We're in free_callback - so this function is safe to call. + // - We won't be using the gem resources on `this` after this call. + unsafe { bindings::drm_gem_shmem_release(this) }; + + // SAFETY: + // - We verified above that `obj` is valid, which makes `this` valid + // - This function is set in AllocOps, so we know that `this` is contained within a + // `Object` + let this = unsafe { container_of!(Opaque::cast_from(this), Self, obj) }.cast_mut(); + + // SAFETY: We're recovering the Kbox<> we created in gem_create_object() + let _ = unsafe { KBox::from_raw(this) }; + } + + /// Creates (if necessary) and returns an immutable reference to a scatter-gather table of DMA + /// pages for this object. + /// + /// This will pin the object in memory. + #[inline] + pub fn sg_table(&self) -> Result<&scatterlist::SGTable> { + // SAFETY: + // - drm_gem_shmem_get_pages_sgt is thread-safe. + // - drm_gem_shmem_get_pages_sgt returns either a valid pointer to a scatterlist, or an + // error pointer. + let sgt = from_err_ptr(unsafe { bindings::drm_gem_shmem_get_pages_sgt(self.as_shmem()) })?; + + // SAFETY: We checked above that `sgt` is not an error pointer, so it must be a valid + // pointer to a scatterlist + Ok(unsafe { scatterlist::SGTable::from_raw(sgt) }) + } +} + +impl Deref for Object { + type Target = T; + + fn deref(&self) -> &Self::Target { + &self.inner + } +} + +impl DerefMut for Object { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.inner + } +} + +impl Sealed for Object {} + +impl gem::IntoGEMObject for Object { + fn as_raw(&self) -> *mut bindings::drm_gem_object { + // SAFETY: + // - Our immutable reference is proof that this is safe to dereference. + // - `obj` is always a valid drm_gem_shmem_object via our type invariants. + unsafe { &raw mut (*self.obj.get()).base } + } + + unsafe fn from_raw<'a>(obj: *mut bindings::drm_gem_object) -> &'a Object { + // SAFETY: The safety contract of from_gem_obj() guarantees that `obj` is contained within + // `Self` + unsafe { + let obj = Opaque::cast_from(container_of!(obj, bindings::drm_gem_shmem_object, base)); + + &*container_of!(obj, Object, obj) + } + } +} + +impl driver::AllocImpl for Object { + type Driver = T::Driver; + + const ALLOC_OPS: driver::AllocOps = driver::AllocOps { + gem_create_object: None, + prime_handle_to_fd: None, + prime_fd_to_handle: None, + gem_prime_import: None, + gem_prime_import_sg_table: Some(bindings::drm_gem_shmem_prime_import_sg_table), + dumb_create: Some(bindings::drm_gem_shmem_dumb_create), + dumb_map_offset: None, + }; +} From a6d63d6e5ea0d7bfcd7e1e4058dcda65d769f03d Mon Sep 17 00:00:00 2001 From: Lyude Paul Date: Tue, 2 Dec 2025 17:03:32 -0500 Subject: [PATCH 1250/3902] rust: drm: gem: Introduce shmem::SGTable Currently we expose the ability to retrieve an SGTable for an shmem gem object using gem::shmem::Object::::sg_table(). However, this only gives us a borrowed reference. This being said - retrieving an SGTable is a fallible operation, and as such it's reasonable that a driver may want to hold onto an SGTable for longer then a reference would allow in order to avoid having to deal with fallibility every time they want to access the SGTable. One such driver with this usecase is the Asahi driver. So to support this, let's introduce shmem::SGTable - which both holds a pointer to the SGTable and a reference to its respective GEM object in order to keep the GEM object alive for as long as the shmem::SGTable. The type can be used identically to a normal SGTable. Signed-off-by: Lyude Paul --- rust/kernel/drm/gem/shmem.rs | 50 ++++++++++++++++++++++++++++++++++++ 1 file changed, 50 insertions(+) diff --git a/rust/kernel/drm/gem/shmem.rs b/rust/kernel/drm/gem/shmem.rs index 45b95d60a3ec7d..21ccb6c1824be9 100644 --- a/rust/kernel/drm/gem/shmem.rs +++ b/rust/kernel/drm/gem/shmem.rs @@ -173,6 +173,25 @@ impl Object { // pointer to a scatterlist Ok(unsafe { scatterlist::SGTable::from_raw(sgt) }) } + + /// Creates (if necessary) and returns an owned reference to a scatter-gather table of DMA pages + /// for this object. + /// + /// This is the same as [`sg_table`](Self::sg_table), except that it instead returns an + /// [`shmem::SGTable`] which holds a reference to the associated gem object, instead of a + /// reference to an [`scatterlist::SGTable`]. + /// + /// This will pin the object in memory. + /// + /// [`shmem::SGTable`]: SGTable + pub fn owned_sg_table(&self) -> Result> { + Ok(SGTable { + sgt: self.sg_table()?.into(), + // INVARIANT: We take an owned refcount to `self` here, ensuring that `sgt` remains + // valid for as long as this `SGTable`. + _owner: self.into(), + }) + } } impl Deref for Object { @@ -223,3 +242,34 @@ impl driver::AllocImpl for Object { dumb_map_offset: None, }; } + +/// An owned reference to a scatter-gather table of DMA address spans for a GEM shmem object. +/// +/// This object holds an owned reference to the underlying GEM shmem object, ensuring that the +/// [`scatterlist::SGTable`] referenced by this type remains valid for the lifetime of this object. +/// +/// # Invariants +/// +/// - `sgt` is kept alive by `_owner`, ensuring it remains valid for as long as `Self`. +/// - `sgt` corresponds to the owned object in `_owner`. +/// - This object is only exposed in situations where we know the underlying `SGTable` will not be +/// modified for the lifetime of this object. Thus, it is safe to send/access this type across +/// threads. +pub struct SGTable { + sgt: NonNull, + _owner: ARef>, +} + +// SAFETY: This object is thread-safe via our type invariants. +unsafe impl Send for SGTable {} +// SAFETY: This object is thread-safe via our type invariants. +unsafe impl Sync for SGTable {} + +impl Deref for SGTable { + type Target = scatterlist::SGTable; + + fn deref(&self) -> &Self::Target { + // SAFETY: Creating an immutable reference to this is safe via our type invariants. + unsafe { self.sgt.as_ref() } + } +} From eb42153cd32f8ed3bf0a7e08384db3dd4ff9997f Mon Sep 17 00:00:00 2001 From: Lyude Paul Date: Tue, 2 Dec 2025 17:03:34 -0500 Subject: [PATCH 1251/3902] rust: drm/gem: Add vmap functions to shmem bindings One of the more obvious use cases for gem shmem objects is the ability to create mappings into their contents, specifically iosys mappings. Now that we've added iosys_map rust bindings to the kernel, let's hook these up in gem shmem. Similar to how we handle SGTables, we make sure there's two different types of mappings: owned mappings (kernel::drm::gem::shmem::VMap) and borrowed mappings (kernel::drm::gem::shmem::VMapRef). One last note: we change the #[expect(unused)] for RawIoSysMap::from_raw() to an #[allow(unused)]. Normally we would simply remove the lint assertion, however - since shmem is conditionally built, we need allow to avoid hitting warnings in certain kernel configurations. Signed-off-by: Lyude Paul --- rust/kernel/drm/gem/shmem.rs | 160 ++++++++++++++++++++++++++++++++++- rust/kernel/iosys_map.rs | 2 +- 2 files changed, 160 insertions(+), 2 deletions(-) diff --git a/rust/kernel/drm/gem/shmem.rs b/rust/kernel/drm/gem/shmem.rs index 21ccb6c1824be9..62a2c12b9fe2aa 100644 --- a/rust/kernel/drm/gem/shmem.rs +++ b/rust/kernel/drm/gem/shmem.rs @@ -13,15 +13,18 @@ use crate::{ container_of, drm::{device, driver, gem, private::Sealed}, error::{from_err_ptr, to_result}, + iosys_map::*, prelude::*, scatterlist, + transmute::*, types::{ARef, Opaque}, }; use core::{ + mem::{self, MaybeUninit}, ops::{Deref, DerefMut}, ptr::NonNull, }; -use gem::{BaseObjectPrivate, DriverObject, IntoGEMObject}; +use gem::{BaseObject, BaseObjectPrivate, DriverObject, IntoGEMObject}; /// A struct for controlling the creation of shmem-backed GEM objects. /// @@ -192,6 +195,72 @@ impl Object { _owner: self.into(), }) } + + /// Attempt to create a [`RawIoSysMap`] from the gem object. + fn raw_vmap(&self) -> Result> { + build_assert!( + mem::size_of::() > 0, + "It doesn't make sense for the mapping type to be a ZST" + ); + + let mut map: MaybeUninit = MaybeUninit::uninit(); + + // SAFETY: drm_gem_shmem_vmap can be called with the DMA reservation lock held + to_result(unsafe { + // TODO: see top of file + bindings::dma_resv_lock(self.raw_dma_resv(), core::ptr::null_mut()); + let ret = bindings::drm_gem_shmem_vmap_locked(self.as_shmem(), map.as_mut_ptr()); + bindings::dma_resv_unlock(self.raw_dma_resv()); + ret + })?; + + // SAFETY: if drm_gem_shmem_vmap did not fail, map is initialized now + Ok(unsafe { RawIoSysMap::from_raw(map.assume_init()) }) + } + + /// Unmap a [`RawIoSysMap`] from the gem object. + /// + /// # Safety + /// + /// - The caller promises that `map` came from a prior call to [`Self::raw_vmap`] on this gem + /// object. + /// - The caller promises that the memory pointed to by `map` will no longer be accesed through + /// this instance. + unsafe fn raw_vunmap(&self, map: &mut RawIoSysMap) { + let resv = self.raw_dma_resv(); + + // SAFETY: + // - This function is safe to call with the DMA reservation lock held + // - Our `ARef` is proof that the underlying gem object here is initialized and thus safe to + // dereference. + unsafe { + // TODO: see top of file + bindings::dma_resv_lock(resv, core::ptr::null_mut()); + bindings::drm_gem_shmem_vunmap_locked(self.as_shmem(), map.as_raw_mut()); + bindings::dma_resv_unlock(resv); + } + } + + /// Creates and returns a virtual kernel memory mapping for this object. + pub fn vmap(&self) -> Result> { + let map = self.raw_vmap()?; + + Ok(VMapRef { + // SAFETY: + // - The size of the vmap is the same as the size of the gem + // - The vmap will remain alive until this object is dropped. + map: unsafe { IoSysMapRef::new(map, self.size()) }, + owner: self, + }) + } + + /// Creates and returns an owned reference to a virtual kernel memory mapping for this object. + pub fn owned_vmap(&self) -> Result> { + Ok(VMap { + map: self.raw_vmap()?, + owner: self.into(), + }) + } } impl Deref for Object { @@ -243,6 +312,95 @@ impl driver::AllocImpl for Object { }; } +/// A borrowed reference to a virtual mapping for a shmem-based GEM object in kernel address space. +pub struct VMapRef<'a, D: DriverObject, T: AsBytes + FromBytes> { + map: IoSysMapRef<'a, T>, + owner: &'a Object, +} + +impl<'a, D: DriverObject, T: AsBytes + FromBytes> Clone for VMapRef<'a, D, T> { + fn clone(&self) -> Self { + // SAFETY: We have a successful vmap already, so this can't fail + unsafe { self.owner.vmap().unwrap_unchecked() } + } +} + +impl<'a, D: DriverObject, T: AsBytes + FromBytes> Deref for VMapRef<'a, D, T> { + type Target = IoSysMapRef<'a, T>; + + fn deref(&self) -> &Self::Target { + &self.map + } +} + +impl<'a, D: DriverObject, T: AsBytes + FromBytes> DerefMut for VMapRef<'a, D, T> { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.map + } +} + +impl<'a, D: DriverObject, T: AsBytes + FromBytes> Drop for VMapRef<'a, D, T> { + fn drop(&mut self) { + // SAFETY: Our existence is proof that this map was previously created using self.owner. + unsafe { self.owner.raw_vunmap(&mut self.map) }; + } +} + +/// An owned reference to a virtual mapping for a shmem-based GEM object in kernel address space. +/// +/// # Invariants +/// +/// - The memory pointed to by `map` is at least as large as `T`. +/// - The memory pointed to by `map` remains valid at least until this object is dropped. +pub struct VMap { + map: RawIoSysMap, + owner: ARef>, +} + +impl Clone for VMap { + fn clone(&self) -> Self { + // SAFETY: We have a successful vmap already, so this can't fail + unsafe { self.owner.owned_vmap().unwrap_unchecked() } + } +} + +impl<'a, D: DriverObject, T: AsBytes + FromBytes> From> for VMap { + fn from(value: VMapRef<'a, D, T>) -> Self { + let this = Self { + map: value.map.clone(), + owner: value.owner.into(), + }; + + mem::forget(value); + this + } +} + +impl VMap { + /// Return a reference to the iosys map for this `VMap`. + pub fn get(&self) -> IoSysMapRef<'_, T> { + // SAFETY: The size of the iosys_map is equivalent to the size of the gem object. + unsafe { IoSysMapRef::new(self.map.clone(), self.owner.size()) } + } + + /// Borrows a reference to the object that owns this virtual mapping. + pub fn owner(&self) -> &Object { + &self.owner + } +} + +impl Drop for VMap { + fn drop(&mut self) { + // SAFETY: Our existence is proof that this map was previously created using self.owner + unsafe { self.owner.raw_vunmap(&mut self.map) }; + } +} + +/// SAFETY: `iosys_map` objects are safe to send across threads. +unsafe impl Send for VMap {} +/// SAFETY: `iosys_map` objects are safe to send across threads. +unsafe impl Sync for VMap {} + /// An owned reference to a scatter-gather table of DMA address spans for a GEM shmem object. /// /// This object holds an owned reference to the underlying GEM shmem object, ensuring that the diff --git a/rust/kernel/iosys_map.rs b/rust/kernel/iosys_map.rs index 4fe881aea55312..039137c051a2f1 100644 --- a/rust/kernel/iosys_map.rs +++ b/rust/kernel/iosys_map.rs @@ -31,7 +31,7 @@ pub struct RawIoSysMap(bindings::iosys_map, PhantomData< impl RawIoSysMap { /// Convert from a raw `bindings::iosys_map`. - #[expect(unused)] + #[allow(unused)] #[inline] pub(crate) fn from_raw(val: bindings::iosys_map) -> Self { Self(val, PhantomData) From 6c64d4f2bedf9801c24fb751fd97bb5330dfa438 Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Tue, 18 Nov 2025 22:17:19 +0100 Subject: [PATCH 1252/3902] rust: drm: gem: shmem: Implement Send + Sync for Object Signed-off-by: Janne Grunau --- rust/kernel/drm/gem/shmem.rs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/rust/kernel/drm/gem/shmem.rs b/rust/kernel/drm/gem/shmem.rs index 62a2c12b9fe2aa..5098c71aa33ac6 100644 --- a/rust/kernel/drm/gem/shmem.rs +++ b/rust/kernel/drm/gem/shmem.rs @@ -60,6 +60,11 @@ pub struct Object { super::impl_aref_for_gem_obj!(impl for Object where T: DriverObject); +// SAFETY: This object is thread-safe via our type invariants. +unsafe impl Send for Object {} +// SAFETY: This object is thread-safe via our type invariants. +unsafe impl Sync for Object {} + impl Object { /// `drm_gem_object_funcs` vtable suitable for GEM shmem objects. const VTABLE: bindings::drm_gem_object_funcs = bindings::drm_gem_object_funcs { From 95c0537b2e9c5b10fb7fed132df9fbb5a9177174 Mon Sep 17 00:00:00 2001 From: Lyude Paul Date: Fri, 16 May 2025 12:28:30 -0400 Subject: [PATCH 1253/3902] rust: drm: gem: Add export() callback This introduces an optional export() callback for GEM objects, which is used to implement the drm_gem_object_funcs->export function. Signed-off-by: Lyude Paul --- drivers/gpu/drm/nova/gem.rs | 1 + drivers/gpu/drm/tyr/gem.rs | 1 + rust/kernel/drm/gem/mod.rs | 72 +++++++++++++++++++++++++++++++++++- rust/kernel/drm/gem/shmem.rs | 6 ++- 4 files changed, 78 insertions(+), 2 deletions(-) diff --git a/drivers/gpu/drm/nova/gem.rs b/drivers/gpu/drm/nova/gem.rs index 173077eeb2def1..680f91919e8421 100644 --- a/drivers/gpu/drm/nova/gem.rs +++ b/drivers/gpu/drm/nova/gem.rs @@ -16,6 +16,7 @@ use crate::{ #[pin_data] pub(crate) struct NovaObject {} +#[vtable] impl gem::DriverObject for NovaObject { type Driver = NovaDriver; type Args = (); diff --git a/drivers/gpu/drm/tyr/gem.rs b/drivers/gpu/drm/tyr/gem.rs index bb5e7871efa940..83493904a13f5e 100644 --- a/drivers/gpu/drm/tyr/gem.rs +++ b/drivers/gpu/drm/tyr/gem.rs @@ -9,6 +9,7 @@ use kernel::prelude::*; #[pin_data] pub(crate) struct TyrObject {} +#[vtable] impl gem::DriverObject for TyrObject { type Driver = TyrDriver; type Args = (); diff --git a/rust/kernel/drm/gem/mod.rs b/rust/kernel/drm/gem/mod.rs index bf29e889548c3e..e7f7c9d2b8f841 100644 --- a/rust/kernel/drm/gem/mod.rs +++ b/rust/kernel/drm/gem/mod.rs @@ -10,11 +10,13 @@ use crate::{ alloc::flags::*, bindings, drm, drm::driver::{AllocImpl, AllocOps}, + dma_buf, error::{to_result, Result}, prelude::*, sync::aref::{ARef, AlwaysRefCounted}, types::Opaque, }; +use core::marker::PhantomData; use core::{ops::Deref, ptr::NonNull}; /// A macro for implementing [`AlwaysRefCounted`] for any GEM object type. @@ -62,6 +64,7 @@ pub(crate) use impl_aref_for_gem_obj; pub type DriverFile = drm::File<<::Driver as drm::Driver>::File>; /// GEM object functions, which must be implemented by drivers. +#[vtable] pub trait DriverObject: Sync + Send + Sized { /// Parent `Driver` for this object. type Driver: drm::Driver; @@ -83,6 +86,11 @@ pub trait DriverObject: Sync + Send + Sized { /// Close a handle to an existing object, associated with a File. fn close(_obj: &::Object, _file: &DriverFile) {} + + /// Optional handle for exporting a gem object. + fn export(_obj: &::Object, _flags: u32) -> Result::Object>> { + unimplemented!() + } } /// Trait that represents a GEM object subtype @@ -132,6 +140,21 @@ extern "C" fn close_callback( T::close(obj, file); } +extern "C" fn export_callback( + raw_obj: *mut bindings::drm_gem_object, + flags: i32, +) -> *mut bindings::dma_buf { + // SAFETY: `export_callback` is specified in the AllocOps structure for `Object`, ensuring + // that `raw_obj` is contained within a `Object`. + let obj = unsafe { <::Object as IntoGEMObject>::from_raw(raw_obj) }; + + match T::export(obj, flags as _) { + // DRM takes a hold of the reference + Ok(buf) => buf.into_raw(), + Err(e) => e.to_ptr(), + } +} + impl IntoGEMObject for Object { fn as_raw(&self) -> *mut bindings::drm_gem_object { self.obj.get() @@ -238,7 +261,11 @@ impl Object { open: Some(open_callback::), close: Some(close_callback::), print_info: None, - export: None, + export: if T::HAS_EXPORT { + Some(export_callback::) + } else { + None + }, pin: None, unpin: None, get_sg_table: None, @@ -335,6 +362,49 @@ impl AllocImpl for Object { }; } +/// A [`dma_buf::DmaBuf`] which has been exported from a GEM object. +/// +/// The [`dma_buf::DmaBuf`] will be released when this type is dropped. +/// +/// # Invariants +/// +/// - `self.0` points to a valid initialized [`dma_buf::DmaBuf`] for the lifetime of this object. +/// - The GEM object from which this [`dma_buf::DmaBuf`] was exported from is guaranteed to be of +/// type `T`. +pub struct DmaBuf(NonNull, PhantomData); + +impl Deref for DmaBuf { + type Target = dma_buf::DmaBuf; + + #[inline] + fn deref(&self) -> &Self::Target { + // SAFETY: This pointer is guaranteed to be valid by our type invariants. + unsafe { self.0.as_ref() } + } +} + +impl Drop for DmaBuf { + #[inline] + fn drop(&mut self) { + // SAFETY: + // - `dma_buf::DmaBuf` is guaranteed to have an identical layout to `struct dma_buf` + // by its type invariants. + // - We hold the last reference to this `DmaBuf`, making it safe to destroy. + unsafe { bindings::drm_gem_dmabuf_release(self.0.cast().as_ptr()) } + } +} + +impl DmaBuf { + /// Leak the reference for this [`DmaBuf`] and return a raw pointer to it. + #[inline] + pub(crate) fn into_raw(self) -> *mut bindings::dma_buf { + let dma_ptr = self.as_raw(); + + core::mem::forget(self); + dma_ptr + } +} + pub(super) const fn create_fops() -> bindings::file_operations { // SAFETY: As by the type invariant, it is safe to initialize `bindings::file_operations` // zeroed. diff --git a/rust/kernel/drm/gem/shmem.rs b/rust/kernel/drm/gem/shmem.rs index 5098c71aa33ac6..bfb05e19cc4500 100644 --- a/rust/kernel/drm/gem/shmem.rs +++ b/rust/kernel/drm/gem/shmem.rs @@ -72,7 +72,11 @@ impl Object { open: Some(super::open_callback::), close: Some(super::close_callback::), print_info: Some(bindings::drm_gem_shmem_object_print_info), - export: None, + export: if T::HAS_EXPORT { + Some(super::export_callback::) + } else { + None + }, pin: Some(bindings::drm_gem_shmem_object_pin), unpin: Some(bindings::drm_gem_shmem_object_unpin), get_sg_table: Some(bindings::drm_gem_shmem_object_get_sg_table), From cc8e7843b5008da791b55fa4baadc951337ba77d Mon Sep 17 00:00:00 2001 From: Lyude Paul Date: Fri, 16 May 2025 17:26:11 -0400 Subject: [PATCH 1254/3902] rust: drm: gem: Add BaseObject::prime_export() We just added an export() callback that GEM objects can implement, but without any way of actually exporting a DmaBuf. So let's add one by introducing bindings for drm_gem_prime_export(). Signed-off-by: Lyude Paul --- rust/kernel/drm/gem/mod.rs | 25 ++++++++++++++++++++++++- 1 file changed, 24 insertions(+), 1 deletion(-) diff --git a/rust/kernel/drm/gem/mod.rs b/rust/kernel/drm/gem/mod.rs index e7f7c9d2b8f841..f88d3589da4087 100644 --- a/rust/kernel/drm/gem/mod.rs +++ b/rust/kernel/drm/gem/mod.rs @@ -11,7 +11,7 @@ use crate::{ bindings, drm, drm::driver::{AllocImpl, AllocOps}, dma_buf, - error::{to_result, Result}, + error::{to_result, from_err_ptr, Result}, prelude::*, sync::aref::{ARef, AlwaysRefCounted}, types::Opaque, @@ -219,6 +219,29 @@ pub trait BaseObject: IntoGEMObject { Ok(unsafe { ARef::from_raw(obj.into()) }) } + /// Export a [`DmaBuf`] for this GEM object using the DRM prime helper library. + /// + /// `flags` should be a set of flags from [`fs::file::flags`](kernel::fs::file::flags). + fn prime_export(&self, flags: u32) -> Result> { + // SAFETY: + // - `as_raw()` always returns a valid pointer to a `drm_gem_object`. + // - `drm_gem_prime_export()` returns either an error pointer, or a valid pointer to an + // initialized `dma_buf` on success. + let dma_ptr = from_err_ptr(unsafe { + bindings::drm_gem_prime_export(self.as_raw(), flags as _) + })?; + + // SAFETY: + // - We checked that dma_ptr is not an error, so it must point to an initialized dma_buf + // - We used drm_gem_prime_export(), so `dma_ptr` will remain valid until a call to + // `drm_gem_prime_release()` which we don't call here. + let dma_buf = unsafe { dma_buf::DmaBuf::as_ref(dma_ptr) }; + + // INVARIANT: We used drm_gem_prime_export() to create this dma_buf, fulfilling the + // invariant that this dma_buf came from a GEM object of type `Self`. + Ok(DmaBuf(dma_buf.into(), PhantomData)) + } + /// Creates an mmap offset to map the object from userspace. fn create_mmap_offset(&self) -> Result { // SAFETY: The arguments are valid per the type invariant. From 23b71c188a6428e237c6326e42a81067c0ef5e4e Mon Sep 17 00:00:00 2001 From: Lyude Paul Date: Fri, 16 May 2025 12:25:57 -0400 Subject: [PATCH 1255/3902] rust: Add dma_buf stub bindings In order to implement the gem export callback, we need a type to represent struct dma_buf. So - this commit introduces a set of stub bindings for dma_buf. These bindings provide a ref-counted DmaBuf object, but don't currently implement any functionality for using the DmaBuf. Signed-off-by: Lyude Paul --- rust/kernel/dma_buf.rs | 39 +++++++++++++++++++++++++++++++++++++++ rust/kernel/lib.rs | 3 +++ 2 files changed, 42 insertions(+) create mode 100644 rust/kernel/dma_buf.rs diff --git a/rust/kernel/dma_buf.rs b/rust/kernel/dma_buf.rs new file mode 100644 index 00000000000000..318518ff0b28f9 --- /dev/null +++ b/rust/kernel/dma_buf.rs @@ -0,0 +1,39 @@ +// SPDX-License-Identifier: GPL-2.0 + +//! DMA buffer API +//! +//! C header: [`include/linux/dma-buf.h`](srctree/include/linux/dma-buf.h) + +use bindings; +use kernel::types::*; + +/// A DMA buffer object. +/// +/// # Invariants +/// +/// The data layout of this type is equivalent to that of `struct dma_buf`. +#[repr(transparent)] +pub struct DmaBuf(Opaque); + +// SAFETY: `struct dma_buf` is thread-safe +unsafe impl Send for DmaBuf {} +// SAFETY: `struct dma_buf` is thread-safe +unsafe impl Sync for DmaBuf {} + +impl DmaBuf { + /// Convert from a `*mut bindings::dma_buf` to a [`DmaBuf`]. + /// + /// # Safety + /// + /// The caller guarantees that `self_ptr` points to a valid initialized `struct dma_buf` for the + /// duration of the lifetime of `'a`, and promises to not violate rust's data aliasing rules + /// using the reference provided by this function. + pub(crate) unsafe fn as_ref<'a>(self_ptr: *mut bindings::dma_buf) -> &'a Self { + // SAFETY: Our data layout is equivalent to `dma_buf` . + unsafe { &*self_ptr.cast() } + } + + pub(crate) fn as_raw(&self) -> *mut bindings::dma_buf { + self.0.get() + } +} diff --git a/rust/kernel/lib.rs b/rust/kernel/lib.rs index 0a1bc71776b3b7..1b4c3adda8e5d9 100644 --- a/rust/kernel/lib.rs +++ b/rust/kernel/lib.rs @@ -100,6 +100,9 @@ pub mod device; pub mod device_id; pub mod devres; pub mod dma; +pub mod dma_buf; +#[cfg(CONFIG_DMA_SHARED_BUFFER)] +pub mod dma_fence; pub mod driver; #[cfg(CONFIG_DRM = "y")] pub mod drm; From 87b59fd5088a31c7327631795447fd9d11cbac65 Mon Sep 17 00:00:00 2001 From: Asahi Lina Date: Sat, 11 Feb 2023 16:50:51 +0900 Subject: [PATCH 1256/3902] rust: drm: mm: Add DRM MM Range Allocator abstraction drm_mm provides a simple range allocator, useful for managing virtual address ranges. Add a Rust abstraction to expose this module to Rust drivers. Signed-off-by: Asahi Lina --- rust/kernel/drm/mm.rs | 310 +++++++++++++++++++++++++++++++++++++++++ rust/kernel/drm/mod.rs | 1 + 2 files changed, 311 insertions(+) create mode 100644 rust/kernel/drm/mm.rs diff --git a/rust/kernel/drm/mm.rs b/rust/kernel/drm/mm.rs new file mode 100644 index 00000000000000..7b13cfd7d53095 --- /dev/null +++ b/rust/kernel/drm/mm.rs @@ -0,0 +1,310 @@ +// SPDX-License-Identifier: GPL-2.0 OR MIT + +//! DRM MM range allocator +//! +//! C header: [`include/drm/drm_mm.h`](../../../../include/drm/drm_mm.h) + +use crate::{ + alloc::flags::*, + bindings, + error::{to_result, Result}, + sync::{new_mutex, Arc, Mutex, UniqueArc}, + types::Opaque, +}; + +use crate::init::InPlaceInit; +use crate::prelude::KBox; + +use core::{ + marker::{PhantomData, PhantomPinned}, + ops::Deref, + pin::Pin, +}; + +/// Type alias representing a DRM MM node. +pub type Node = Pin>>; + +/// Trait which must be implemented by the inner allocator state type provided by the user. +pub trait AllocInner { + /// Notification that a node was dropped from the allocator. + fn drop_object(&mut self, _start: u64, _size: u64, _color: usize, _object: &mut T) {} +} + +impl AllocInner for () {} + +/// Wrapper type for a `struct drm_mm` plus user AllocInner object. +/// +/// # Invariants +/// The `drm_mm` struct is valid and initialized. +struct MmInner, T>(Opaque, A, PhantomData); + +/// Represents a single allocated node in the MM allocator +pub struct NodeData, T> { + node: bindings::drm_mm_node, + mm: Arc>>, + valid: bool, + /// A drm_mm_node needs to be pinned because nodes reference each other in a linked list. + _pin: PhantomPinned, + inner: T, +} + +// SAFETY: Allocator ops take the mutex, and there are no mutable actions on the node. +unsafe impl, T: Send> Send for NodeData {} +// SAFETY: Allocator ops take the mutex, and there are no mutable actions on the node. +unsafe impl, T: Sync> Sync for NodeData {} + +/// Available MM node insertion modes +#[repr(u32)] +pub enum InsertMode { + /// Search for the smallest hole (within the search range) that fits the desired node. + /// + /// Allocates the node from the bottom of the found hole. + Best = bindings::drm_mm_insert_mode_DRM_MM_INSERT_BEST, + + /// Search for the lowest hole (address closest to 0, within the search range) that fits the + /// desired node. + /// + /// Allocates the node from the bottom of the found hole. + Low = bindings::drm_mm_insert_mode_DRM_MM_INSERT_LOW, + + /// Search for the highest hole (address closest to U64_MAX, within the search range) that fits + /// the desired node. + /// + /// Allocates the node from the top of the found hole. The specified alignment for the node is + /// applied to the base of the node (`Node.start()`). + High = bindings::drm_mm_insert_mode_DRM_MM_INSERT_HIGH, + + /// Search for the most recently evicted hole (within the search range) that fits the desired + /// node. This is appropriate for use immediately after performing an eviction scan and removing + /// the selected nodes to form a hole. + /// + /// Allocates the node from the bottom of the found hole. + Evict = bindings::drm_mm_insert_mode_DRM_MM_INSERT_EVICT, +} + +/// A clonable, interlocked reference to the allocator state. +/// +/// This is useful to perform actions on the user-supplied `AllocInner` type given just a Node, +/// without immediately taking the lock. +#[derive(Clone)] +pub struct InnerRef, T>(Arc>>); + +impl, T> InnerRef { + /// Operate on the user `AllocInner` implementation, taking the lock. + pub fn with(&self, cb: impl FnOnce(&mut A) -> RetVal) -> RetVal { + let mut l = self.0.lock(); + cb(&mut l.1) + } +} + +impl, T> NodeData { + /// Returns the color of the node (an opaque value) + pub fn color(&self) -> usize { + self.node.color as usize + } + + /// Returns the start address of the node + pub fn start(&self) -> u64 { + self.node.start + } + + /// Returns the size of the node in bytes + pub fn size(&self) -> u64 { + self.node.size + } + + /// Operate on the user `AllocInner` implementation associated with this node's allocator. + pub fn with_inner(&self, cb: impl FnOnce(&mut A) -> RetVal) -> RetVal { + let mut l = self.mm.lock(); + cb(&mut l.1) + } + + /// Return a clonable, detached reference to the allocator inner data. + pub fn alloc_ref(&self) -> InnerRef { + InnerRef(self.mm.clone()) + } + + /// Return a mutable reference to the inner data. + pub fn inner_mut(self: Pin<&mut Self>) -> &mut T { + // SAFETY: This is okay because inner is not structural + unsafe { &mut self.get_unchecked_mut().inner } + } +} + +impl, T> Deref for NodeData { + type Target = T; + + fn deref(&self) -> &Self::Target { + &self.inner + } +} + +impl, T> Drop for NodeData { + fn drop(&mut self) { + if self.valid { + let mut guard = self.mm.lock(); + + // Inform the user allocator that a node is being dropped. + guard + .1 + .drop_object(self.start(), self.size(), self.color(), &mut self.inner); + // SAFETY: The MM lock is still taken, so we can safely remove the node. + unsafe { bindings::drm_mm_remove_node(&mut self.node) }; + } + } +} + +/// An instance of a DRM MM range allocator. +pub struct Allocator, T> { + mm: Arc>>, + _p: PhantomData, +} + +impl, T> Allocator { + /// Create a new range allocator for the given start and size range of addresses. + /// + /// The user may optionally provide an inner object representing allocator state, which will + /// be protected by the same lock. If not required, `()` can be used. + #[track_caller] + pub fn new(start: u64, size: u64, inner: A) -> Result> { + // SAFETY: We call `Mutex::init_lock` below. + let mm = UniqueArc::pin_init( + new_mutex!(MmInner(Opaque::uninit(), inner, PhantomData)), + GFP_KERNEL, + )?; + + // SAFETY: The Opaque instance provides a valid pointer, and it is initialized after + // this call. + unsafe { + bindings::drm_mm_init(mm.lock().0.get(), start, size); + } + + Ok(Allocator { + mm: mm.into(), + _p: PhantomData, + }) + } + + /// Insert a new node into the allocator of a given size. + /// + /// `node` is the user `T` type data to store into the node. + pub fn insert_node(&mut self, node: T, size: u64) -> Result> { + self.insert_node_generic(node, size, 0, 0, InsertMode::Best) + } + + /// Insert a new node into the allocator of a given size, with configurable alignment, + /// color, and insertion mode. + /// + /// `node` is the user `T` type data to store into the node. + pub fn insert_node_generic( + &mut self, + node: T, + size: u64, + alignment: u64, + color: usize, + mode: InsertMode, + ) -> Result> { + self.insert_node_in_range(node, size, alignment, color, 0, u64::MAX, mode) + } + + /// Insert a new node into the allocator of a given size, with configurable alignment, + /// color, insertion mode, and sub-range to allocate from. + /// + /// `node` is the user `T` type data to store into the node. + #[allow(clippy::too_many_arguments)] + pub fn insert_node_in_range( + &mut self, + node: T, + size: u64, + alignment: u64, + color: usize, + start: u64, + end: u64, + mode: InsertMode, + ) -> Result> { + let mut mm_node = KBox::new( + NodeData { + // SAFETY: This C struct should be zero-initialized. + node: unsafe { core::mem::zeroed() }, + valid: false, + inner: node, + mm: self.mm.clone(), + _pin: PhantomPinned, + }, + GFP_KERNEL, + )?; + + let guard = self.mm.lock(); + // SAFETY: We hold the lock and all pointers are valid. + to_result(unsafe { + bindings::drm_mm_insert_node_in_range( + guard.0.get(), + &mut mm_node.node, + size, + alignment, + color, + start, + end, + mode as u32, + ) + })?; + + mm_node.valid = true; + + Ok(Pin::from(mm_node)) + } + + /// Insert a node into the allocator at a fixed start address. + /// + /// `node` is the user `T` type data to store into the node. + pub fn reserve_node( + &mut self, + node: T, + start: u64, + size: u64, + color: usize, + ) -> Result> { + let mut mm_node = KBox::new( + NodeData { + // SAFETY: This C struct should be zero-initialized. + node: unsafe { core::mem::zeroed() }, + valid: false, + inner: node, + mm: self.mm.clone(), + _pin: PhantomPinned, + }, + GFP_KERNEL, + )?; + + mm_node.node.start = start; + mm_node.node.size = size; + mm_node.node.color = color as crate::ffi::c_ulong; + + let guard = self.mm.lock(); + // SAFETY: We hold the lock and all pointers are valid. + to_result(unsafe { bindings::drm_mm_reserve_node(guard.0.get(), &mut mm_node.node) })?; + + mm_node.valid = true; + + Ok(Pin::from(mm_node)) + } + + /// Operate on the inner user type `A`, taking the allocator lock + pub fn with_inner(&self, cb: impl FnOnce(&mut A) -> RetVal) -> RetVal { + let mut guard = self.mm.lock(); + cb(&mut guard.1) + } +} + +impl, T> Drop for MmInner { + fn drop(&mut self) { + // SAFETY: If the MmInner is dropped then all nodes are gone (since they hold references), + // so it is safe to tear down the allocator. + unsafe { + bindings::drm_mm_takedown(self.0.get()); + } + } +} + +// SAFETY: MmInner is safely Send if the AllocInner user type is Send. +unsafe impl, T> Send for MmInner {} diff --git a/rust/kernel/drm/mod.rs b/rust/kernel/drm/mod.rs index 1b82b6945edf25..f369da5b12fb87 100644 --- a/rust/kernel/drm/mod.rs +++ b/rust/kernel/drm/mod.rs @@ -7,6 +7,7 @@ pub mod driver; pub mod file; pub mod gem; pub mod ioctl; +pub mod mm; pub use self::device::Device; pub use self::driver::Driver; From 6706c83a4b95e6aa36aa06fe03f0110bc9a94881 Mon Sep 17 00:00:00 2001 From: Asahi Lina Date: Sat, 11 Feb 2023 16:56:21 +0900 Subject: [PATCH 1257/3902] rust: dma_fence: Add DMA Fence abstraction DMA fences are the internal synchronization primitive used for DMA operations like GPU rendering, video en/decoding, etc. Add an abstraction to allow Rust drivers to interact with this subsystem. Note: This uses a raw spinlock living next to the fence, since we do not interact with it other than for initialization. TODO: Expose this to the user at some point with a safe abstraction. Signed-off-by: Asahi Lina --- rust/bindings/bindings_helper.h | 2 + rust/helpers/dma-fence.c | 33 +++ rust/helpers/helpers.c | 1 + rust/kernel/dma_fence.rs | 480 ++++++++++++++++++++++++++++++++ 4 files changed, 516 insertions(+) create mode 100644 rust/helpers/dma-fence.c create mode 100644 rust/kernel/dma_fence.rs diff --git a/rust/bindings/bindings_helper.h b/rust/bindings/bindings_helper.h index 60a7fa3c9a9a9b..5e0cfaf3b1b694 100644 --- a/rust/bindings/bindings_helper.h +++ b/rust/bindings/bindings_helper.h @@ -51,6 +51,8 @@ #include #include #include +#include +#include #include #include #include diff --git a/rust/helpers/dma-fence.c b/rust/helpers/dma-fence.c new file mode 100644 index 00000000000000..6491016262934b --- /dev/null +++ b/rust/helpers/dma-fence.c @@ -0,0 +1,33 @@ +// SPDX-License-Identifier: GPL-2.0 + +#include +#include + +#ifdef CONFIG_DMA_SHARED_BUFFER + +void rust_helper_dma_fence_get(struct dma_fence *fence) +{ + dma_fence_get(fence); +} + +void rust_helper_dma_fence_put(struct dma_fence *fence) +{ + dma_fence_put(fence); +} + +struct dma_fence_chain *rust_helper_dma_fence_chain_alloc(void) +{ + return dma_fence_chain_alloc(); +} + +void rust_helper_dma_fence_chain_free(struct dma_fence_chain *chain) +{ + dma_fence_chain_free(chain); +} + +void rust_helper_dma_fence_set_error(struct dma_fence *fence, int error) +{ + dma_fence_set_error(fence, error); +} + +#endif diff --git a/rust/helpers/helpers.c b/rust/helpers/helpers.c index a8ce4612b2a9e9..0121c118107c44 100644 --- a/rust/helpers/helpers.c +++ b/rust/helpers/helpers.c @@ -25,6 +25,7 @@ #include "cred.c" #include "device.c" #include "dma.c" +#include "dma-fence.c" #include "dma-mapping.c" #include "dma-resv.c" #include "drm.c" diff --git a/rust/kernel/dma_fence.rs b/rust/kernel/dma_fence.rs new file mode 100644 index 00000000000000..139451e75685f9 --- /dev/null +++ b/rust/kernel/dma_fence.rs @@ -0,0 +1,480 @@ +// SPDX-License-Identifier: GPL-2.0 + +//! DMA fence abstraction. +//! +//! C header: [`include/linux/dma_fence.h`](../../include/linux/dma_fence.h) + +use crate::{ + bindings, + error::{to_result, Result}, + prelude::*, + sync::LockClassKey, + types::Opaque, +}; +use core::ops::{Deref, DerefMut}; +use core::ptr::addr_of_mut; +use core::sync::atomic::{AtomicU64, Ordering}; + +mod private { + /// Marker that a trait cannot be implemented outside of this mod + pub trait Sealed {} +} + +/// Any kind of DMA Fence Object +/// +/// # Invariants +/// raw() returns a valid pointer to a dma_fence and we own a reference to it. +pub trait RawDmaFence: private::Sealed { + /// Returns the raw `struct dma_fence` pointer. + fn raw(&self) -> *mut bindings::dma_fence; + + /// Returns the raw `struct dma_fence` pointer and consumes the object. + /// + /// The caller is responsible for dropping the reference. + fn into_raw(self) -> *mut bindings::dma_fence + where + Self: Sized, + { + let ptr = self.raw(); + core::mem::forget(self); + ptr + } + + /// Advances this fence to the chain node which will signal this sequence number. + /// If no sequence number is provided, this returns `self` again. + /// If the seqno has already been signaled, returns None. + fn chain_find_seqno(self, seqno: u64) -> Result> + where + Self: Sized, + { + let mut ptr = self.into_raw(); + + // SAFETY: This will safely fail if this DmaFence is not a chain. + // `ptr` is valid per the type invariant. + let ret = unsafe { bindings::dma_fence_chain_find_seqno(&mut ptr, seqno) }; + + if ret != 0 { + // SAFETY: This is either an owned reference or NULL, dma_fence_put can handle both. + unsafe { bindings::dma_fence_put(ptr) }; + Err(Error::from_errno(ret)) + } else if ptr.is_null() { + Ok(None) + } else { + // SAFETY: ptr is valid and non-NULL as checked above. + Ok(Some(unsafe { Fence::from_raw(ptr) })) + } + } + + /// Signal completion of this fence + fn signal(&self) -> Result { + // SAFETY: Safe to call on any valid dma_fence object + to_result(unsafe { bindings::dma_fence_signal(self.raw()) }) + } + + /// Set the error flag on this fence + fn set_error(&self, err: Error) { + // SAFETY: Safe to call on any valid dma_fence object + unsafe { bindings::dma_fence_set_error(self.raw(), err.to_errno()) }; + } +} + +/// A generic DMA Fence Object +/// +/// # Invariants +/// ptr is a valid pointer to a dma_fence and we own a reference to it. +pub struct Fence { + ptr: *mut bindings::dma_fence, +} + +impl Fence { + /// Create a new Fence object from a raw pointer to a dma_fence. + /// + /// # Safety + /// The caller must own a reference to the dma_fence, which is transferred to the new object. + pub(crate) unsafe fn from_raw(ptr: *mut bindings::dma_fence) -> Fence { + Fence { ptr } + } + + /// Create a new Fence object from a raw pointer to a dma_fence. + /// + /// # Safety + /// Takes a borrowed reference to the dma_fence, and increments the reference count. + pub(crate) unsafe fn get_raw(ptr: *mut bindings::dma_fence) -> Fence { + // SAFETY: Pointer is valid per the safety contract + unsafe { bindings::dma_fence_get(ptr) }; + Fence { ptr } + } + + /// Create a new Fence object from a RawDmaFence. + pub fn from_fence(fence: &dyn RawDmaFence) -> Fence { + // SAFETY: Pointer is valid per the RawDmaFence contract + unsafe { Self::get_raw(fence.raw()) } + } +} + +impl private::Sealed for Fence {} + +impl RawDmaFence for Fence { + fn raw(&self) -> *mut bindings::dma_fence { + self.ptr + } +} + +impl Drop for Fence { + fn drop(&mut self) { + // SAFETY: We own a reference to this syncobj. + unsafe { bindings::dma_fence_put(self.ptr) }; + } +} + +impl Clone for Fence { + fn clone(&self) -> Self { + // SAFETY: `ptr` is valid per the type invariant and we own a reference to it. + unsafe { + bindings::dma_fence_get(self.ptr); + Self::from_raw(self.ptr) + } + } +} + +// SAFETY: The API for these objects is thread safe +unsafe impl Sync for Fence {} +// SAFETY: The API for these objects is thread safe +unsafe impl Send for Fence {} + +/// Trait which must be implemented by driver-specific fence objects. +#[vtable] +pub trait FenceOps: Sized + Send + Sync { + /// Returns the driver name. This is a callback to allow drivers to compute the name at + /// runtime, without having it to store permanently for each fence, or build a cache of + /// some sort. + fn get_driver_name<'a>(self: &'a FenceObject) -> &'a CStr; + + /// Return the name of the context this fence belongs to. This is a callback to allow drivers + /// to compute the name at runtime, without having it to store permanently for each fence, or + /// build a cache of some sort. + fn get_timeline_name<'a>(self: &'a FenceObject) -> &'a CStr; + + /// Enable software signaling of fence. + fn enable_signaling(self: &FenceObject) -> bool { + false + } + + /// Peek whether the fence is signaled, as a fastpath optimization for e.g. dma_fence_wait() or + /// dma_fence_add_callback(). + fn signaled(self: &FenceObject) -> bool { + false + } +} + +unsafe extern "C" fn get_driver_name_cb( + fence: *mut bindings::dma_fence, +) -> *const crate::ffi::c_char { + // SAFETY: All of our fences are FenceObject. + let p = unsafe { crate::container_of!(fence, FenceObject, fence) as *mut FenceObject }; + + // SAFETY: The caller is responsible for passing a valid dma_fence subtype + T::get_driver_name(unsafe { &mut *p }).as_char_ptr() +} + +unsafe extern "C" fn get_timeline_name_cb( + fence: *mut bindings::dma_fence, +) -> *const crate::ffi::c_char { + // SAFETY: All of our fences are FenceObject. + let p = unsafe { crate::container_of!(fence, FenceObject, fence) as *mut FenceObject }; + + // SAFETY: The caller is responsible for passing a valid dma_fence subtype + T::get_timeline_name(unsafe { &mut *p }).as_char_ptr() +} + +unsafe extern "C" fn enable_signaling_cb(fence: *mut bindings::dma_fence) -> bool { + // SAFETY: All of our fences are FenceObject. + let p = unsafe { crate::container_of!(fence, FenceObject, fence) as *mut FenceObject }; + + // SAFETY: The caller is responsible for passing a valid dma_fence subtype + T::enable_signaling(unsafe { &mut *p }) +} + +unsafe extern "C" fn signaled_cb(fence: *mut bindings::dma_fence) -> bool { + // SAFETY: All of our fences are FenceObject. + let p = unsafe { crate::container_of!(fence, FenceObject, fence) as *mut FenceObject }; + + // SAFETY: The caller is responsible for passing a valid dma_fence subtype + T::signaled(unsafe { &mut *p }) +} + +unsafe extern "C" fn release_cb(fence: *mut bindings::dma_fence) { + // SAFETY: All of our fences are FenceObject. + let p = unsafe { crate::container_of!(fence, FenceObject, fence) as *mut FenceObject }; + + // SAFETY: p is never used after this + unsafe { + core::ptr::drop_in_place(&mut (*p).inner); + } + + // SAFETY: All of our fences are allocated using kmalloc, so this is safe. + unsafe { bindings::dma_fence_free(fence) }; +} + +/// A driver-specific DMA Fence Object +/// +/// # Invariants +/// ptr is a valid pointer to a dma_fence and we own a reference to it. +#[repr(C)] +pub struct FenceObject { + fence: bindings::dma_fence, + lock: Opaque, + inner: T, +} + +impl FenceObject { + const SIZE: usize = core::mem::size_of::(); + + const VTABLE: bindings::dma_fence_ops = bindings::dma_fence_ops { + get_driver_name: Some(get_driver_name_cb::), + get_timeline_name: Some(get_timeline_name_cb::), + enable_signaling: if T::HAS_ENABLE_SIGNALING { + Some(enable_signaling_cb::) + } else { + None + }, + signaled: if T::HAS_SIGNALED { + Some(signaled_cb::) + } else { + None + }, + wait: None, // Deprecated + release: Some(release_cb::), + set_deadline: None, + }; +} + +impl Deref for FenceObject { + type Target = T; + + fn deref(&self) -> &T { + &self.inner + } +} + +impl DerefMut for FenceObject { + fn deref_mut(&mut self) -> &mut T { + &mut self.inner + } +} + +impl private::Sealed for FenceObject {} +impl RawDmaFence for FenceObject { + fn raw(&self) -> *mut bindings::dma_fence { + &self.fence as *const _ as *mut _ + } +} + +/// A unique reference to a driver-specific fence object +pub struct UniqueFence(*mut FenceObject); + +impl Deref for UniqueFence { + type Target = FenceObject; + + fn deref(&self) -> &FenceObject { + // SAFETY: The pointer is always valid for UniqueFence objects + unsafe { &*self.0 } + } +} + +impl DerefMut for UniqueFence { + fn deref_mut(&mut self) -> &mut FenceObject { + // SAFETY: The pointer is always valid for UniqueFence objects + unsafe { &mut *self.0 } + } +} + +impl private::Sealed for UniqueFence {} +impl RawDmaFence for UniqueFence { + fn raw(&self) -> *mut bindings::dma_fence { + // SAFETY: The pointer is always valid for UniqueFence objects + unsafe { addr_of_mut!((*self.0).fence) } + } +} + +impl From> for UserFence { + fn from(value: UniqueFence) -> Self { + let ptr = value.0; + core::mem::forget(value); + + UserFence(ptr) + } +} + +impl Drop for UniqueFence { + fn drop(&mut self) { + // SAFETY: We own a reference to this fence. + unsafe { bindings::dma_fence_put(self.raw()) }; + } +} + +// SAFETY: The API for these objects is thread safe +unsafe impl Sync for UniqueFence {} +// SAFETY: The API for these objects is thread safe +unsafe impl Send for UniqueFence {} + +/// A shared reference to a driver-specific fence object +pub struct UserFence(*mut FenceObject); + +impl Deref for UserFence { + type Target = FenceObject; + + fn deref(&self) -> &FenceObject { + // SAFETY: The pointer is always valid for UserFence objects + unsafe { &*self.0 } + } +} + +impl Clone for UserFence { + fn clone(&self) -> Self { + // SAFETY: `ptr` is valid per the type invariant and we own a reference to it. + unsafe { + bindings::dma_fence_get(self.raw()); + Self(self.0) + } + } +} + +impl private::Sealed for UserFence {} +impl RawDmaFence for UserFence { + fn raw(&self) -> *mut bindings::dma_fence { + // SAFETY: The pointer is always valid for UserFence objects + unsafe { addr_of_mut!((*self.0).fence) } + } +} + +impl Drop for UserFence { + fn drop(&mut self) { + // SAFETY: We own a reference to this fence. + unsafe { bindings::dma_fence_put(self.raw()) }; + } +} + +// SAFETY: The API for these objects is thread safe +unsafe impl Sync for UserFence {} +// SAFETY: The API for these objects is thread safe +unsafe impl Send for UserFence {} + +/// An array of fence contexts, out of which fences can be created. +pub struct FenceContexts { + start: u64, + count: u32, + seqnos: KVec, + lock_name: &'static CStr, + lock_key: Pin<&'static LockClassKey>, +} + +impl FenceContexts { + /// Create a new set of fence contexts. + pub fn new( + count: u32, + name: &'static CStr, + key: Pin<&'static LockClassKey>, + ) -> Result { + let mut seqnos: KVec = KVec::new(); + + seqnos.reserve(count as usize, GFP_KERNEL)?; + + for _ in 0..count { + seqnos.push(Default::default(), GFP_KERNEL)?; + } + + // SAFETY: This is always safe to call + let start = unsafe { bindings::dma_fence_context_alloc(count as crate::ffi::c_uint) }; + + Ok(FenceContexts { + start, + count, + seqnos, + lock_name: name, + lock_key: key, + }) + } + + /// Create a new fence in a given context index. + pub fn new_fence(&self, context: u32, inner: T) -> Result> { + if context > self.count { + return Err(EINVAL); + } + + // SAFETY: krealloc is always safe to call like this + let p = unsafe { + bindings::krealloc_node_align( + core::ptr::null_mut(), + FenceObject::::SIZE, + 1, + bindings::GFP_KERNEL | bindings::__GFP_ZERO, + bindings::NUMA_NO_NODE, + ) as *mut FenceObject + }; + + if p.is_null() { + return Err(ENOMEM); + } + + let seqno = self.seqnos[context as usize].fetch_add(1, Ordering::Relaxed); + + // SAFETY: The pointer is valid, so pointers to members are too. + // After this, all fields are initialized. + unsafe { + addr_of_mut!((*p).inner).write(inner); + bindings::__spin_lock_init( + addr_of_mut!((*p).lock) as *mut _, + self.lock_name.as_char_ptr(), + self.lock_key.as_ptr(), + ); + bindings::dma_fence_init64( + addr_of_mut!((*p).fence), + &FenceObject::::VTABLE, + addr_of_mut!((*p).lock) as *mut _, + self.start + context as u64, + seqno, + ); + }; + + Ok(UniqueFence(p)) + } +} + +/// A DMA Fence Chain Object +/// +/// # Invariants +/// ptr is a valid pointer to a dma_fence_chain which we own. +pub struct FenceChain { + ptr: *mut bindings::dma_fence_chain, +} + +impl FenceChain { + /// Create a new DmaFenceChain object. + pub fn new() -> Result { + // SAFETY: This function is safe to call and takes no arguments. + let ptr = unsafe { bindings::dma_fence_chain_alloc() }; + + if ptr.is_null() { + Err(ENOMEM) + } else { + Ok(FenceChain { ptr }) + } + } + + /// Convert the DmaFenceChain into the underlying raw pointer. + /// + /// This assumes the caller will take ownership of the object. + pub(crate) fn into_raw(self) -> *mut bindings::dma_fence_chain { + let ptr = self.ptr; + core::mem::forget(self); + ptr + } +} + +impl Drop for FenceChain { + fn drop(&mut self) { + // SAFETY: We own this dma_fence_chain. + unsafe { bindings::dma_fence_chain_free(self.ptr) }; + } +} From 57445645013ac8035999488097e197ab2d4c68e5 Mon Sep 17 00:00:00 2001 From: Asahi Lina Date: Sat, 11 Feb 2023 16:59:20 +0900 Subject: [PATCH 1258/3902] rust: drm: syncobj: Add DRM Sync Object abstraction DRM Sync Objects are a container for a DMA fence, and can be waited on signaled, exported, and imported from userspace. Add a Rust abstraction so Rust DRM drivers can support this functionality. Signed-off-by: Asahi Lina --- rust/bindings/bindings_helper.h | 1 + rust/helpers/drm_syncobj.c | 22 +++++++++ rust/helpers/helpers.c | 1 + rust/kernel/drm/mod.rs | 1 + rust/kernel/drm/syncobj.rs | 83 +++++++++++++++++++++++++++++++++ 5 files changed, 108 insertions(+) create mode 100644 rust/helpers/drm_syncobj.c create mode 100644 rust/kernel/drm/syncobj.rs diff --git a/rust/bindings/bindings_helper.h b/rust/bindings/bindings_helper.h index 5e0cfaf3b1b694..531e4016deabc0 100644 --- a/rust/bindings/bindings_helper.h +++ b/rust/bindings/bindings_helper.h @@ -35,6 +35,7 @@ #include #include #include +#include #include #include #include diff --git a/rust/helpers/drm_syncobj.c b/rust/helpers/drm_syncobj.c new file mode 100644 index 00000000000000..9e14c989edfd72 --- /dev/null +++ b/rust/helpers/drm_syncobj.c @@ -0,0 +1,22 @@ +// SPDX-License-Identifier: GPL-2.0 + +#include + +#ifdef CONFIG_DRM + +void rust_helper_drm_syncobj_get(struct drm_syncobj *obj) +{ + drm_syncobj_get(obj); +} + +void rust_helper_drm_syncobj_put(struct drm_syncobj *obj) +{ + drm_syncobj_put(obj); +} + +struct dma_fence *rust_helper_drm_syncobj_fence_get(struct drm_syncobj *syncobj) +{ + return drm_syncobj_fence_get(syncobj); +} + +#endif diff --git a/rust/helpers/helpers.c b/rust/helpers/helpers.c index 0121c118107c44..fcf6b9bafdf11c 100644 --- a/rust/helpers/helpers.c +++ b/rust/helpers/helpers.c @@ -29,6 +29,7 @@ #include "dma-mapping.c" #include "dma-resv.c" #include "drm.c" +#include "drm_syncobj.c" #include "err.c" #include "irq.c" #include "fs.c" diff --git a/rust/kernel/drm/mod.rs b/rust/kernel/drm/mod.rs index f369da5b12fb87..44f30f389ed041 100644 --- a/rust/kernel/drm/mod.rs +++ b/rust/kernel/drm/mod.rs @@ -8,6 +8,7 @@ pub mod file; pub mod gem; pub mod ioctl; pub mod mm; +pub mod syncobj; pub use self::device::Device; pub use self::driver::Driver; diff --git a/rust/kernel/drm/syncobj.rs b/rust/kernel/drm/syncobj.rs new file mode 100644 index 00000000000000..a022e08223588b --- /dev/null +++ b/rust/kernel/drm/syncobj.rs @@ -0,0 +1,83 @@ +// SPDX-License-Identifier: GPL-2.0 OR MIT + +//! DRM Sync Objects +//! +//! C header: [`include/drm/drm_syncobj.h`](../../../../include/drm/drm_syncobj.h) + +use crate::{bindings, dma_fence::*, drm, error::Result, prelude::*}; + +/// A DRM Sync Object +/// +/// # Invariants +/// ptr is a valid pointer to a drm_syncobj and we own a reference to it. +pub struct SyncObj { + ptr: *mut bindings::drm_syncobj, +} + +impl SyncObj { + /// Looks up a sync object by its handle for a given `File`. + pub fn lookup_handle( + file: &drm::File, + handle: u32, + ) -> Result { + // SAFETY: The arguments are all valid per the type invariants. + let ptr = unsafe { bindings::drm_syncobj_find(file.as_raw() as *mut _, handle) }; + + if ptr.is_null() { + Err(ENOENT) + } else { + Ok(SyncObj { ptr }) + } + } + + /// Returns the DMA fence associated with this sync object, if any. + pub fn fence_get(&self) -> Option { + // SAFETY: self.ptr is always valid + let fence = unsafe { bindings::drm_syncobj_fence_get(self.ptr) }; + if fence.is_null() { + None + } else { + // SAFETY: The pointer is non-NULL and drm_syncobj_fence_get acquired an + // additional reference. + Some(unsafe { Fence::from_raw(fence) }) + } + } + + /// Replaces the DMA fence with a new one, or removes it if fence is None. + pub fn replace_fence(&self, fence: Option<&Fence>) { + // SAFETY: All arguments should be valid per the respective type invariants. + unsafe { + bindings::drm_syncobj_replace_fence( + self.ptr, + fence.map_or(core::ptr::null_mut(), |a| a.raw()), + ) + }; + } + + /// Adds a new timeline point to the syncobj. + pub fn add_point(&self, chain: FenceChain, fence: &Fence, point: u64) { + // SAFETY: All arguments should be valid per the respective type invariants. + // This takes over the FenceChain ownership. + unsafe { bindings::drm_syncobj_add_point(self.ptr, chain.into_raw(), fence.raw(), point) }; + } +} + +impl Drop for SyncObj { + fn drop(&mut self) { + // SAFETY: We own a reference to this syncobj. + unsafe { bindings::drm_syncobj_put(self.ptr) }; + } +} + +impl Clone for SyncObj { + fn clone(&self) -> Self { + // SAFETY: `ptr` is valid per the type invariant and we own a reference to it. + unsafe { bindings::drm_syncobj_get(self.ptr) }; + SyncObj { ptr: self.ptr } + } +} + +// SAFETY: drm_syncobj operations are internally locked. +unsafe impl Sync for SyncObj {} +// SAFETY: drm_syncobj operations are internally locked. +unsafe impl Send for SyncObj {} From 8fee96abc63ed7b8a9716d73d2974f2ae99b3d85 Mon Sep 17 00:00:00 2001 From: Asahi Lina Date: Wed, 5 Apr 2023 17:44:13 +0900 Subject: [PATCH 1259/3902] drm/scheduler: Fix UAF in drm_sched_fence_get_timeline_name A signaled scheduler fence can outlive its scheduler, since fences are independencly reference counted. Therefore, we can't reference the scheduler in the get_timeline_name() implementation. Fixes oopses on `cat /sys/kernel/debug/dma_buf/bufinfo` when shared dma-bufs reference fences from GPU schedulers that no longer exist. Signed-off-by: Asahi Lina --- drivers/gpu/drm/scheduler/sched_entity.c | 7 ++++++- drivers/gpu/drm/scheduler/sched_fence.c | 4 +++- include/drm/gpu_scheduler.h | 5 +++++ 3 files changed, 14 insertions(+), 2 deletions(-) diff --git a/drivers/gpu/drm/scheduler/sched_entity.c b/drivers/gpu/drm/scheduler/sched_entity.c index fe174a4857be7f..e1920f3f920aee 100644 --- a/drivers/gpu/drm/scheduler/sched_entity.c +++ b/drivers/gpu/drm/scheduler/sched_entity.c @@ -420,7 +420,12 @@ static bool drm_sched_entity_add_dependency_cb(struct drm_sched_entity *entity, /* * Fence is from the same scheduler, only need to wait for - * it to be scheduled + * it to be scheduled. + * + * Note: s_fence->sched could have been freed and reallocated + * as another scheduler. This false positive case is okay, as if + * the old scheduler was freed all of its jobs must have + * signaled their completion fences. */ fence = dma_fence_get(&s_fence->scheduled); dma_fence_put(entity->dependency); diff --git a/drivers/gpu/drm/scheduler/sched_fence.c b/drivers/gpu/drm/scheduler/sched_fence.c index 9391d6f0dc01d7..d05ab041a581a6 100644 --- a/drivers/gpu/drm/scheduler/sched_fence.c +++ b/drivers/gpu/drm/scheduler/sched_fence.c @@ -92,7 +92,7 @@ static const char *drm_sched_fence_get_driver_name(struct dma_fence *fence) static const char *drm_sched_fence_get_timeline_name(struct dma_fence *f) { struct drm_sched_fence *fence = to_drm_sched_fence(f); - return (const char *)fence->sched->name; + return (const char *)fence->sched_name; } static void drm_sched_fence_free_rcu(struct rcu_head *rcu) @@ -228,6 +228,8 @@ void drm_sched_fence_init(struct drm_sched_fence *fence, unsigned seq; fence->sched = entity->rq->sched; + strscpy(fence->sched_name, entity->rq->sched->name, + sizeof(fence->sched_name)); seq = atomic_inc_return(&entity->fence_seq); dma_fence_init(&fence->scheduled, &drm_sched_fence_ops_scheduled, &fence->lock, entity->fence_context, seq); diff --git a/include/drm/gpu_scheduler.h b/include/drm/gpu_scheduler.h index 323a505e6e6ae0..71349c5779425a 100644 --- a/include/drm/gpu_scheduler.h +++ b/include/drm/gpu_scheduler.h @@ -301,6 +301,11 @@ struct drm_sched_fence { * @lock: the lock used by the scheduled and the finished fences. */ spinlock_t lock; + /** + * @sched_name: the name of the scheduler that owns this fence. We + * keep a copy here since fences can outlive their scheduler. + */ + char sched_name[16]; /** * @owner: job owner for debugging */ From e71363c13d51e2ceca00043838ce48fea853b77a Mon Sep 17 00:00:00 2001 From: Asahi Lina Date: Sat, 11 Feb 2023 17:08:36 +0900 Subject: [PATCH 1260/3902] rust: drm: sched: Add GPU scheduler abstraction The GPU scheduler manages scheduling GPU jobs and dependencies between them. This Rust abstraction allows Rust DRM drivers to use this functionality. Signed-off-by: Asahi Lina --- rust/bindings/bindings_helper.h | 1 + rust/kernel/drm/mod.rs | 1 + rust/kernel/drm/sched.rs | 391 ++++++++++++++++++++++++++++++++ 3 files changed, 393 insertions(+) create mode 100644 rust/kernel/drm/sched.rs diff --git a/rust/bindings/bindings_helper.h b/rust/bindings/bindings_helper.h index 531e4016deabc0..eb61b086f6c622 100644 --- a/rust/bindings/bindings_helper.h +++ b/rust/bindings/bindings_helper.h @@ -36,6 +36,7 @@ #include #include #include +#include #include #include #include diff --git a/rust/kernel/drm/mod.rs b/rust/kernel/drm/mod.rs index 44f30f389ed041..f3e93bfe919cd4 100644 --- a/rust/kernel/drm/mod.rs +++ b/rust/kernel/drm/mod.rs @@ -8,6 +8,7 @@ pub mod file; pub mod gem; pub mod ioctl; pub mod mm; +pub mod sched; pub mod syncobj; pub use self::device::Device; diff --git a/rust/kernel/drm/sched.rs b/rust/kernel/drm/sched.rs new file mode 100644 index 00000000000000..e2f5cd96014f93 --- /dev/null +++ b/rust/kernel/drm/sched.rs @@ -0,0 +1,391 @@ +// SPDX-License-Identifier: GPL-2.0 OR MIT + +//! DRM Scheduler +//! +//! C header: [`include/drm/gpu_scheduler.h`](../../../../include/drm/gpu_scheduler.h) + +use crate::{ + bindings, device, + dma_fence::*, + error::{to_result, Result}, + prelude::*, + sync::{Arc, UniqueArc}, + time::{self, msecs_to_jiffies}, +}; +use core::marker::PhantomData; +use core::mem::MaybeUninit; +use core::ops::{Deref, DerefMut}; +use core::ptr::{addr_of, addr_of_mut}; + +/// Scheduler status after timeout recovery +#[repr(u32)] +pub enum Status { + /// Device recovered from the timeout and can execute jobs again + Nominal = bindings::drm_gpu_sched_stat_DRM_GPU_SCHED_STAT_RESET, + /// Device is no longer available + NoDevice = bindings::drm_gpu_sched_stat_DRM_GPU_SCHED_STAT_ENODEV, +} + +/// Scheduler priorities +#[repr(u32)] +pub enum Priority { + /// Low userspace priority + Low = bindings::drm_sched_priority_DRM_SCHED_PRIORITY_LOW, + /// Normal userspace priority + Normal = bindings::drm_sched_priority_DRM_SCHED_PRIORITY_NORMAL, + /// High userspace priority + High = bindings::drm_sched_priority_DRM_SCHED_PRIORITY_HIGH, + /// Kernel priority (highest) + Kernel = bindings::drm_sched_priority_DRM_SCHED_PRIORITY_KERNEL, +} + +/// Trait to be implemented by driver job objects. +pub trait JobImpl: Sized { + /// Called when the scheduler is considering scheduling this job next, to get another Fence + /// for this job to block on. Once it returns None, run() may be called. + fn prepare(_job: &mut Job) -> Option { + None // Equivalent to NULL function pointer + } + + /// Called to execute the job once all of the dependencies have been resolved. This may be + /// called multiple times, if timed_out() has happened and drm_sched_job_recovery() decides + /// to try it again. + fn run(job: &mut Job) -> Result>; + + /// Called when a job has taken too long to execute, to trigger GPU recovery. + /// + /// This method is called in a workqueue context. + fn timed_out(job: &mut Job) -> Status; + + /// Called for remaining jobs in drm_sched_fini() to ensure the job's fences + /// get signalled before the scheduler is torn down. + fn cancel(job: &mut Job); +} + +unsafe extern "C" fn prepare_job_cb( + sched_job: *mut bindings::drm_sched_job, + _s_entity: *mut bindings::drm_sched_entity, +) -> *mut bindings::dma_fence { + // SAFETY: All of our jobs are Job. + let p = unsafe { crate::container_of!(sched_job, Job, job) as *mut Job }; + + // SAFETY: All of our jobs are Job. + match T::prepare(unsafe { &mut *p }) { + None => core::ptr::null_mut(), + Some(fence) => fence.into_raw(), + } +} + +unsafe extern "C" fn run_job_cb( + sched_job: *mut bindings::drm_sched_job, +) -> *mut bindings::dma_fence { + // SAFETY: All of our jobs are Job. + let p = unsafe { crate::container_of!(sched_job, Job, job) as *mut Job }; + + // SAFETY: All of our jobs are Job. + match T::run(unsafe { &mut *p }) { + Err(e) => e.to_ptr(), + Ok(None) => core::ptr::null_mut(), + Ok(Some(fence)) => fence.into_raw(), + } +} + +unsafe extern "C" fn timedout_job_cb( + sched_job: *mut bindings::drm_sched_job, +) -> bindings::drm_gpu_sched_stat { + // SAFETY: All of our jobs are Job. + let p = unsafe { crate::container_of!(sched_job, Job, job) as *mut Job }; + + // SAFETY: All of our jobs are Job. + T::timed_out(unsafe { &mut *p }) as bindings::drm_gpu_sched_stat +} + +unsafe extern "C" fn free_job_cb(sched_job: *mut bindings::drm_sched_job) { + // SAFETY: All of our jobs are Job. + let p = unsafe { crate::container_of!(sched_job, Job, job) as *mut Job }; + + // Convert the job back to a Box and drop it + // SAFETY: All of our Jobs are created inside a box. + unsafe { drop(KBox::from_raw(p)) }; +} + +unsafe extern "C" fn cancel_job_cb(sched_job: *mut bindings::drm_sched_job) { + // SAFETY: All of our jobs are Job. + let p = unsafe { crate::container_of!(sched_job, Job, job) as *mut Job }; + + // SAFETY: All of our jobs are Job. + T::cancel(unsafe { &mut *p }); + + let fence = unsafe { Fence::get_raw(&mut (*(*sched_job).s_fence).finished) }; + fence.set_error(ECANCELED); + let _ = fence.signal(); +} + +/// A DRM scheduler job. +pub struct Job { + job: bindings::drm_sched_job, + inner: T, +} + +impl Deref for Job { + type Target = T; + + fn deref(&self) -> &Self::Target { + &self.inner + } +} + +impl DerefMut for Job { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.inner + } +} + +impl Drop for Job { + fn drop(&mut self) { + // SAFETY: At this point the job has either been submitted and this is being called from + // `free_job_cb` above, or it hasn't and it is safe to call `drm_sched_job_cleanup`. + unsafe { bindings::drm_sched_job_cleanup(&mut self.job) }; + } +} + +/// A pending DRM scheduler job (not yet armed) +pub struct PendingJob<'a, T: JobImpl>(KBox>, PhantomData<&'a T>); + +impl<'a, T: JobImpl> PendingJob<'a, T> { + /// Add a fence as a dependency to the job + pub fn add_dependency(&mut self, fence: Fence) -> Result { + // SAFETY: C call with correct arguments + to_result(unsafe { + bindings::drm_sched_job_add_dependency(&mut self.0.job, fence.into_raw()) + }) + } + + /// Arm the job to make it ready for execution + pub fn arm(mut self) -> ArmedJob<'a, T> { + // SAFETY: C call with correct arguments + unsafe { bindings::drm_sched_job_arm(&mut self.0.job) }; + ArmedJob(self.0, PhantomData) + } +} + +impl<'a, T: JobImpl> Deref for PendingJob<'a, T> { + type Target = Job; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl<'a, T: JobImpl> DerefMut for PendingJob<'a, T> { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.0 + } +} + +/// An armed DRM scheduler job (not yet submitted) +pub struct ArmedJob<'a, T: JobImpl>(KBox>, PhantomData<&'a T>); + +impl<'a, T: JobImpl> ArmedJob<'a, T> { + /// Returns the job fences + pub fn fences(&mut self) -> JobFences<'_> { + // SAFETY: s_fence is always a valid drm_sched_fence pointer + JobFences(unsafe { &mut *self.0.job.s_fence }) + } + + /// Push the job for execution into the scheduler + pub fn push(self) { + // After this point, the job is submitted and owned by the scheduler + let ptr = match self { + ArmedJob(job, _) => KBox::>::into_raw(job), + }; + + // SAFETY: We are passing in ownership of a valid Box raw pointer. + unsafe { bindings::drm_sched_entity_push_job(addr_of_mut!((*ptr).job)) }; + } +} +impl<'a, T: JobImpl> Deref for ArmedJob<'a, T> { + type Target = Job; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl<'a, T: JobImpl> DerefMut for ArmedJob<'a, T> { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.0 + } +} + +/// Reference to the bundle of fences attached to a DRM scheduler job +pub struct JobFences<'a>(&'a mut bindings::drm_sched_fence); + +impl<'a> JobFences<'a> { + /// Returns a new reference to the job scheduled fence. + pub fn scheduled(&mut self) -> Fence { + // SAFETY: self.0.scheduled is always a valid fence + unsafe { Fence::get_raw(&mut self.0.scheduled) } + } + + /// Returns a new reference to the job finished fence. + pub fn finished(&mut self) -> Fence { + // SAFETY: self.0.finished is always a valid fence + unsafe { Fence::get_raw(&mut self.0.finished) } + } +} + +struct EntityInner { + entity: bindings::drm_sched_entity, + // TODO: Allow users to share guilty flag between entities + sched: Arc>, + guilty: bindings::atomic_t, + _p: PhantomData, +} + +impl Drop for EntityInner { + fn drop(&mut self) { + // SAFETY: The EntityInner is initialized. This will cancel/free all jobs. + unsafe { bindings::drm_sched_entity_destroy(&mut self.entity) }; + } +} + +// SAFETY: TODO +unsafe impl Sync for EntityInner {} +// SAFETY: TODO +unsafe impl Send for EntityInner {} + +/// A DRM scheduler entity. +pub struct Entity(Pin>>); + +impl Entity { + /// Create a new scheduler entity. + pub fn new(sched: &Scheduler, priority: Priority) -> Result { + let mut entity: KBox>> = + KBox::new_uninit(GFP_KERNEL | __GFP_ZERO)?; + + let mut sched_ptr = &sched.0.sched as *const _ as *mut _; + + // SAFETY: The Box is allocated above and valid. + unsafe { + bindings::drm_sched_entity_init( + addr_of_mut!((*entity.as_mut_ptr()).entity), + priority as _, + &mut sched_ptr, + 1, + addr_of_mut!((*entity.as_mut_ptr()).guilty), + ) + }; + + // SAFETY: The Box is allocated above and valid. + unsafe { addr_of_mut!((*entity.as_mut_ptr()).sched).write(sched.0.clone()) }; + + // SAFETY: entity is now initialized. + Ok(Self(Pin::from(unsafe { entity.assume_init() }))) + } + + /// Create a new job on this entity. + /// + /// The entity must outlive the pending job until it transitions into the submitted state, + /// after which the scheduler owns it. Since jobs must be submitted in creation order, + /// this requires a mutable reference to the entity, ensuring that only one new job can be + /// in flight at once. + pub fn new_job(&mut self, credits: u32, inner: T) -> Result> { + let mut job: KBox>> = Box::new_uninit(GFP_KERNEL | __GFP_ZERO)?; + + // SAFETY: We hold a reference to the entity (which is a valid pointer), + // and the job object was just allocated above. + to_result(unsafe { + bindings::drm_sched_job_init( + addr_of_mut!((*job.as_mut_ptr()).job), + &self.0.as_ref().get_ref().entity as *const _ as *mut _, + credits, + core::ptr::null_mut(), + 0, + ) + })?; + + // SAFETY: The Box pointer is valid, and this initializes the inner member. + unsafe { addr_of_mut!((*job.as_mut_ptr()).inner).write(inner) }; + + // SAFETY: All fields of the Job are now initialized. + Ok(PendingJob(unsafe { job.assume_init() }, PhantomData)) + } +} + +/// DRM scheduler inner data +pub struct SchedulerInner { + sched: bindings::drm_gpu_scheduler, + _p: PhantomData, +} + +impl Drop for SchedulerInner { + fn drop(&mut self) { + // SAFETY: The scheduler is valid. This assumes drm_sched_fini() will take care of + // freeing all in-progress jobs. + unsafe { bindings::drm_sched_stop(&mut self.sched, core::ptr::null_mut()) }; + unsafe { bindings::drm_sched_fini(&mut self.sched) }; + } +} + +// SAFETY: TODO +unsafe impl Sync for SchedulerInner {} +// SAFETY: TODO +unsafe impl Send for SchedulerInner {} + +/// A DRM Scheduler +pub struct Scheduler(Arc>); + +impl Scheduler { + const OPS: bindings::drm_sched_backend_ops = bindings::drm_sched_backend_ops { + prepare_job: Some(prepare_job_cb::), + run_job: Some(run_job_cb::), + timedout_job: Some(timedout_job_cb::), + free_job: Some(free_job_cb::), + cancel_job: Some(cancel_job_cb::), + }; + /// Creates a new DRM Scheduler object + // TODO: Shared timeout workqueues & scores + pub fn new( + device: &device::Device, + num_rqs: u32, + credit_limit: u32, + hang_limit: u32, + timeout_ms: time::Msecs, + name: &'static CStr, + ) -> Result> { + let mut sched: UniqueArc>> = + UniqueArc::new_uninit(GFP_KERNEL)?; + + // SAFETY: zero sched->sched_rq as drm_sched_init() uses it to exit early withoput initialisation + // TODO: allocate sched zzeroed instead + unsafe { + (*sched.as_mut_ptr()).sched.sched_rq = core::ptr::null_mut(); + }; + + let init_ops = bindings::drm_sched_init_args { + ops: &Self::OPS, + submit_wq: core::ptr::null_mut(), + timeout_wq: core::ptr::null_mut(), + num_rqs, + credit_limit, + hang_limit, + timeout: msecs_to_jiffies(timeout_ms).try_into()?, + score: core::ptr::null_mut(), + name: name.as_char_ptr(), + dev: device.as_raw(), + }; + + // SAFETY: The drm_sched pointer is valid and pinned as it was just allocated above. + // `device` is valid by its type invarants + to_result(unsafe { + bindings::drm_sched_init( + addr_of_mut!((*sched.as_mut_ptr()).sched), + addr_of!(init_ops), + ) + })?; + + // SAFETY: All fields of SchedulerInner are now initialized. + Ok(Scheduler(unsafe { sched.assume_init() }.into())) + } +} From a4547e18fcfd14890e8eeeef925308cd32b4aba0 Mon Sep 17 00:00:00 2001 From: Asahi Lina Date: Wed, 8 May 2024 19:50:07 +0900 Subject: [PATCH 1261/3902] drm/gpuvm: Add drm_gpuvm_bo_unmap() Analogous to drm_gpuvm_bo_unmap_ops_create, this is a callback-driven unmap function for a given BO. Signed-off-by: Asahi Lina --- drivers/gpu/drm/drm_gpuvm.c | 49 +++++++++++++++++++++++++++++++++++++ include/drm/drm_gpuvm.h | 1 + 2 files changed, 50 insertions(+) diff --git a/drivers/gpu/drm/drm_gpuvm.c b/drivers/gpu/drm/drm_gpuvm.c index af63f4d003155d..d3127ae218ec8e 100644 --- a/drivers/gpu/drm/drm_gpuvm.c +++ b/drivers/gpu/drm/drm_gpuvm.c @@ -2928,6 +2928,55 @@ drm_gpuvm_prefetch_ops_create(struct drm_gpuvm *gpuvm, } EXPORT_SYMBOL_GPL(drm_gpuvm_prefetch_ops_create); +/** + * drm_gpuvm_bo_unmap() - unmaps a GEM + * @vm_bo: the &drm_gpuvm_bo abstraction + * + * This function calls the unmap callback for every GPUVA attached to a GEM. + * + * It is the callers responsibility to protect the GEMs GPUVA list against + * concurrent access using the GEMs dma_resv lock. + * + * Returns: a pointer to the &drm_gpuva_ops on success, an ERR_PTR on failure + */ +int +drm_gpuvm_bo_unmap(struct drm_gpuvm_bo *vm_bo, void *priv) +{ + struct drm_gpuva_ops *ops; + struct drm_gpuva_op *op; + int ret; + + if (unlikely(!vm_bo->vm)) + return -EINVAL; + + const struct drm_gpuvm_ops *vm_ops = vm_bo->vm->ops; + + if (unlikely(!(vm_ops && vm_ops->sm_step_unmap))) + return -EINVAL; + + if (drm_gpuvm_immediate_mode(vm_bo->vm)) { + guard(mutex)(&vm_bo->obj->gpuva.lock); + ops = drm_gpuvm_bo_unmap_ops_create(vm_bo); + } else { + ops = drm_gpuvm_bo_unmap_ops_create(vm_bo); + } + if (IS_ERR(ops)) + return PTR_ERR(ops); + + drm_gpuva_for_each_op(op, ops) { + drm_WARN_ON(vm_bo->vm->drm, op->op != DRM_GPUVA_OP_UNMAP); + + ret = op_unmap_cb(vm_ops, priv, op->unmap.va, false, false); + if (ret) + goto cleanup; + } + +cleanup: + drm_gpuva_ops_free(vm_bo->vm, ops); + return ret; +} +EXPORT_SYMBOL_GPL(drm_gpuvm_bo_unmap); + /** * drm_gpuvm_bo_unmap_ops_create() - creates the &drm_gpuva_ops to unmap a GEM * @vm_bo: the &drm_gpuvm_bo abstraction diff --git a/include/drm/drm_gpuvm.h b/include/drm/drm_gpuvm.h index 476990e761f869..aa294d22d02596 100644 --- a/include/drm/drm_gpuvm.h +++ b/include/drm/drm_gpuvm.h @@ -1239,6 +1239,7 @@ int drm_gpuvm_sm_map(struct drm_gpuvm *gpuvm, void *priv, int drm_gpuvm_sm_unmap(struct drm_gpuvm *gpuvm, void *priv, u64 addr, u64 range); +int drm_gpuvm_bo_unmap(struct drm_gpuvm_bo *bo, void *priv); int drm_gpuvm_sm_map_exec_lock(struct drm_gpuvm *gpuvm, struct drm_exec *exec, unsigned int num_fences, From 56cf531e971aa2ea21cee35db579cc1779d8ba2d Mon Sep 17 00:00:00 2001 From: Caterina Shablia Date: Tue, 2 Sep 2025 17:27:37 +0000 Subject: [PATCH 1262/3902] drm/gpuvm: Add a helper to check if two VA can be merged We are going to add flags/properties that will impact the VA merging ability. Instead of sprinkling tests all over the place in __drm_gpuvm_sm_map(), let's add a helper aggregating all these checks can call it for every existing VA we walk through in the __drm_gpuvm_sm_map() loop. Signed-off-by: Boris Brezillon Signed-off-by: Caterina Shablia --- drivers/gpu/drm/drm_gpuvm.c | 40 ++++++++++++++++++++++++++++++++++++- 1 file changed, 39 insertions(+), 1 deletion(-) diff --git a/drivers/gpu/drm/drm_gpuvm.c b/drivers/gpu/drm/drm_gpuvm.c index d3127ae218ec8e..08655afb8eff50 100644 --- a/drivers/gpu/drm/drm_gpuvm.c +++ b/drivers/gpu/drm/drm_gpuvm.c @@ -2172,6 +2172,44 @@ op_unmap_cb(const struct drm_gpuvm_ops *fn, void *priv, return fn->sm_step_unmap(&op, priv); } +static bool __can_merge(struct drm_gpuvm *gpuvm, const struct drm_gpuva_op_map *a, + const struct drm_gpuva_op_map *b) +{ + /* Only GEM-based mappings can be merged, and they must point to + * the same GEM object. + */ + if (a->gem.obj != b->gem.obj || !a->gem.obj) + return false; + + /* Order VAs for the rest of the checks. */ + if (a->va.addr > b->va.addr) + swap(a, b); + + /* We assume the caller already checked that VAs overlap or are + * contiguous. + */ + if (drm_WARN_ON(gpuvm->drm, b->va.addr > a->va.addr + a->va.range)) + return false; + + /* We intentionally ignore u64 underflows because all we care about + * here is whether the VA diff matches the GEM offset diff. + */ + return b->va.addr - a->va.addr == b->gem.offset - a->gem.offset; +} + +static bool can_merge(struct drm_gpuvm *gpuvm, const struct drm_gpuva *a, + const struct drm_gpuva_op_map *b) +{ + struct drm_gpuva_op_map tmp = { + .va.addr = a->va.addr, + .va.range = a->va.range, + .gem.offset = a->gem.offset, + .gem.obj = a->gem.obj, + }; + + return __can_merge(gpuvm, &tmp, b); +} + static int __drm_gpuvm_sm_map(struct drm_gpuvm *gpuvm, const struct drm_gpuvm_ops *ops, void *priv, @@ -2196,7 +2234,7 @@ __drm_gpuvm_sm_map(struct drm_gpuvm *gpuvm, u64 addr = va->va.addr; u64 range = va->va.range; u64 end = addr + range; - bool merge = !!va->gem.obj; + bool merge = can_merge(gpuvm, va, &req->map); if (madvise && obj) continue; From a08eeb2e7f844c05df34cdfc4b2f7737c4558f2f Mon Sep 17 00:00:00 2001 From: Caterina Shablia Date: Tue, 2 Sep 2025 17:33:38 +0000 Subject: [PATCH 1263/3902] drm/gpuvm: Add a flags field to drm_gpuva_op_map drm_gpuva objects have a flags field. Currently, this can be managed by drivers out-of-band, without any special handling in drm_gpuvm. To be able to introduce flags that do affect the logic in the drm_gpuvm core, we need to plumb it through the map calls. This will allow the core to check the flags on map and alter the merge/split logic depending on the requested flags and the flags of the existing drm_gpuva ranges that are being split. Signed-off-by: Asahi Lina Signed-off-by: Caterina Shablia --- drivers/gpu/drm/drm_gpuvm.c | 18 ++++++++++++++++++ include/drm/drm_gpuvm.h | 16 ++++++++++++++++ 2 files changed, 34 insertions(+) diff --git a/drivers/gpu/drm/drm_gpuvm.c b/drivers/gpu/drm/drm_gpuvm.c index 08655afb8eff50..466c4f62e1f655 100644 --- a/drivers/gpu/drm/drm_gpuvm.c +++ b/drivers/gpu/drm/drm_gpuvm.c @@ -2172,6 +2172,14 @@ op_unmap_cb(const struct drm_gpuvm_ops *fn, void *priv, return fn->sm_step_unmap(&op, priv); } +static bool can_merge_flags(struct drm_gpuvm *gpuvm, enum drm_gpuva_flags a, + enum drm_gpuva_flags b) +{ + if (gpuvm->ops->sm_can_merge_flags) + return gpuvm->ops->sm_can_merge_flags(a, b); + return a == b; +} + static bool __can_merge(struct drm_gpuvm *gpuvm, const struct drm_gpuva_op_map *a, const struct drm_gpuva_op_map *b) { @@ -2181,6 +2189,9 @@ static bool __can_merge(struct drm_gpuvm *gpuvm, const struct drm_gpuva_op_map * if (a->gem.obj != b->gem.obj || !a->gem.obj) return false; + if (can_merge_flags(gpuvm, a->flags, b->flags)) + return false; + /* Order VAs for the rest of the checks. */ if (a->va.addr > b->va.addr) swap(a, b); @@ -2205,6 +2216,7 @@ static bool can_merge(struct drm_gpuvm *gpuvm, const struct drm_gpuva *a, .va.range = a->va.range, .gem.offset = a->gem.offset, .gem.obj = a->gem.obj, + .flags = a->flags, }; return __can_merge(gpuvm, &tmp, b); @@ -2263,6 +2275,7 @@ __drm_gpuvm_sm_map(struct drm_gpuvm *gpuvm, .va.range = range - req_range, .gem.obj = obj, .gem.offset = offset + req_range, + .flags = va->flags, }; struct drm_gpuva_op_unmap u = { .va = va, @@ -2284,6 +2297,7 @@ __drm_gpuvm_sm_map(struct drm_gpuvm *gpuvm, .va.range = ls_range, .gem.obj = obj, .gem.offset = offset, + .flags = va->flags, }; struct drm_gpuva_op_unmap u = { .va = va }; @@ -2327,6 +2341,7 @@ __drm_gpuvm_sm_map(struct drm_gpuvm *gpuvm, .gem.obj = obj, .gem.offset = offset + ls_range + req_range, + .flags = va->flags, }; ret = op_remap_cb(ops, priv, &p, &n, &u); @@ -2364,6 +2379,7 @@ __drm_gpuvm_sm_map(struct drm_gpuvm *gpuvm, .va.range = end - req_end, .gem.obj = obj, .gem.offset = offset + req_end - addr, + .flags = va->flags, }; struct drm_gpuva_op_unmap u = { .va = va, @@ -2415,6 +2431,7 @@ __drm_gpuvm_sm_unmap(struct drm_gpuvm *gpuvm, prev.va.range = req_addr - addr; prev.gem.obj = obj; prev.gem.offset = offset; + prev.flags = va->flags; prev_split = true; } @@ -2424,6 +2441,7 @@ __drm_gpuvm_sm_unmap(struct drm_gpuvm *gpuvm, next.va.range = end - req_end; next.gem.obj = obj; next.gem.offset = offset + (req_end - addr); + next.flags = va->flags; next_split = true; } diff --git a/include/drm/drm_gpuvm.h b/include/drm/drm_gpuvm.h index aa294d22d02596..522deef9924209 100644 --- a/include/drm/drm_gpuvm.h +++ b/include/drm/drm_gpuvm.h @@ -871,6 +871,11 @@ struct drm_gpuva_op_map { */ struct drm_gem_object *obj; } gem; + + /** + * @flags: requested flags for the &drm_gpuva for this mapping + */ + enum drm_gpuva_flags flags; }; /** @@ -1107,6 +1112,7 @@ void drm_gpuva_ops_free(struct drm_gpuvm *gpuvm, static inline void drm_gpuva_init_from_op(struct drm_gpuva *va, struct drm_gpuva_op_map *op) { + va->flags = op->flags; va->va.addr = op->va.addr; va->va.range = op->va.range; va->gem.obj = op->gem.obj; @@ -1232,6 +1238,16 @@ struct drm_gpuvm_ops { * used. */ int (*sm_step_unmap)(struct drm_gpuva_op *op, void *priv); + + /** + * @sm_can_merge_flags: called during &drm_gpuvm_sm_map + * + * This callback is called to determine whether two va ranges can be merged, + * based on their flags. + * + * If NULL, va ranges can only be merged if their flags are equal. + */ + bool (*sm_can_merge_flags)(enum drm_gpuva_flags a, enum drm_gpuva_flags b); }; int drm_gpuvm_sm_map(struct drm_gpuvm *gpuvm, void *priv, From b3c584121479a429727d73c677c668024b2e7d06 Mon Sep 17 00:00:00 2001 From: Caterina Shablia Date: Tue, 2 Sep 2025 17:43:06 +0000 Subject: [PATCH 1264/3902] drm/gpuvm: Add DRM_GPUVA_REPEAT flag and logic To be able to support "fake sparse" mappings without relying on GPU page fault handling, drivers may need to create large (e.g. 4GiB) mappings of the same page repeatedly (or same range of pages). Doing this through individual mappings would be very wasteful. This can be handled better by using a flag on map creation, but to do it safely, drm_gpuvm needs to be aware of this special case. Add a flag that signals that a given mapping is a page mapping, which is repeated all over the entire requested VA range. This tweaks the sm_map() logic to treat the GEM offsets differently when mappings are a repeated ones so they are not incremented as they would be with regular mappings. The size of the GEM portion to repeat is passed through drm_gpuva::gem::range. Most of the time it will be a page size, but it can be bigger as long as it's less than drm_gpuva::va::range, and drm_gpuva::va::range is a multiple of drm_gpuva::gem::range. Signed-off-by: Asahi Lina Signed-off-by: Caterina Shablia --- drivers/gpu/drm/drm_gpuvm.c | 63 +++++++++++++++++++++++++++++++++---- include/drm/drm_gpuvm.h | 34 +++++++++++++++++++- 2 files changed, 90 insertions(+), 7 deletions(-) diff --git a/drivers/gpu/drm/drm_gpuvm.c b/drivers/gpu/drm/drm_gpuvm.c index 466c4f62e1f655..ebfd9b1b683e19 100644 --- a/drivers/gpu/drm/drm_gpuvm.c +++ b/drivers/gpu/drm/drm_gpuvm.c @@ -2202,6 +2202,26 @@ static bool __can_merge(struct drm_gpuvm *gpuvm, const struct drm_gpuva_op_map * if (drm_WARN_ON(gpuvm->drm, b->va.addr > a->va.addr + a->va.range)) return false; + if (a->flags & DRM_GPUVA_REPEAT) { + u64 va_diff = b->va.addr - a->va.addr; + + /* If this is a repeated mapping, both the GEM range + * and offset must match. + */ + if (a->gem.range != b->gem.range || + a->gem.offset != b->gem.offset) + return false; + + /* The difference between the VA addresses must be a + * multiple of the repeated range, otherwise there's + * a shift. + */ + if (do_div(va_diff, a->gem.range)) + return false; + + return true; + } + /* We intentionally ignore u64 underflows because all we care about * here is whether the VA diff matches the GEM offset diff. */ @@ -2222,6 +2242,27 @@ static bool can_merge(struct drm_gpuvm *gpuvm, const struct drm_gpuva *a, return __can_merge(gpuvm, &tmp, b); } +static int validate_map_request(struct drm_gpuvm *gpuvm, + const struct drm_gpuva_op_map *req) +{ + if (unlikely(!drm_gpuvm_range_valid(gpuvm, req->va.addr, req->va.range))) + return -EINVAL; + + if (req->flags & DRM_GPUVA_REPEAT) { + u64 va_range = req->va.range; + + /* For a repeated mapping, GEM range must be > 0 + * and a multiple of the VA range. + */ + if (unlikely(!req->gem.range || + va_range < req->gem.range || + do_div(va_range, req->gem.range))) + return -EINVAL; + } + + return 0; +} + static int __drm_gpuvm_sm_map(struct drm_gpuvm *gpuvm, const struct drm_gpuvm_ops *ops, void *priv, @@ -2237,7 +2278,8 @@ __drm_gpuvm_sm_map(struct drm_gpuvm *gpuvm, u64 req_end = req_addr + req_range; int ret; - if (unlikely(!drm_gpuvm_range_valid(gpuvm, req_addr, req_range))) + ret = validate_map_request(gpuvm, &req->map); + if (unlikely(ret)) return -EINVAL; drm_gpuvm_for_each_va_range_safe(va, next, gpuvm, req_addr, req_end) { @@ -2274,7 +2316,9 @@ __drm_gpuvm_sm_map(struct drm_gpuvm *gpuvm, .va.addr = req_end, .va.range = range - req_range, .gem.obj = obj, - .gem.offset = offset + req_range, + .gem.range = va->gem.range, + .gem.offset = offset + + (va->flags & DRM_GPUVA_REPEAT ? 0 : req_range), .flags = va->flags, }; struct drm_gpuva_op_unmap u = { @@ -2296,6 +2340,7 @@ __drm_gpuvm_sm_map(struct drm_gpuvm *gpuvm, .va.addr = addr, .va.range = ls_range, .gem.obj = obj, + .gem.range = va->gem.range, .gem.offset = offset, .flags = va->flags, }; @@ -2339,8 +2384,9 @@ __drm_gpuvm_sm_map(struct drm_gpuvm *gpuvm, .va.addr = req_end, .va.range = end - req_end, .gem.obj = obj, - .gem.offset = offset + ls_range + - req_range, + .gem.range = va->gem.range, + .gem.offset = offset + + (va->flags & DRM_GPUVA_REPEAT ? 0 : ls_range + req_range), .flags = va->flags, }; @@ -2378,7 +2424,9 @@ __drm_gpuvm_sm_map(struct drm_gpuvm *gpuvm, .va.addr = req_end, .va.range = end - req_end, .gem.obj = obj, - .gem.offset = offset + req_end - addr, + .gem.range = va->gem.range, + .gem.offset = offset + + (va->flags & DRM_GPUVA_REPEAT ? 0 : req_end - addr), .flags = va->flags, }; struct drm_gpuva_op_unmap u = { @@ -2430,6 +2478,7 @@ __drm_gpuvm_sm_unmap(struct drm_gpuvm *gpuvm, prev.va.addr = addr; prev.va.range = req_addr - addr; prev.gem.obj = obj; + prev.gem.range = va->gem.range; prev.gem.offset = offset; prev.flags = va->flags; @@ -2440,7 +2489,9 @@ __drm_gpuvm_sm_unmap(struct drm_gpuvm *gpuvm, next.va.addr = req_end; next.va.range = end - req_end; next.gem.obj = obj; - next.gem.offset = offset + (req_end - addr); + prev.gem.range = va->gem.range; + next.gem.offset = offset + + (va->flags & DRM_GPUVA_REPEAT ? 0 : req_end - addr); next.flags = va->flags; next_split = true; diff --git a/include/drm/drm_gpuvm.h b/include/drm/drm_gpuvm.h index 522deef9924209..bed3dd74e64cdb 100644 --- a/include/drm/drm_gpuvm.h +++ b/include/drm/drm_gpuvm.h @@ -56,10 +56,19 @@ enum drm_gpuva_flags { */ DRM_GPUVA_SPARSE = (1 << 1), + /** + * @DRM_GPUVA_REPEAT: + * + * Flag indicating that the &drm_gpuva is a mapping of a GEM + * object with a certain range that is repeated multiple times to + * fill the virtual address range. + */ + DRM_GPUVA_REPEAT = (1 << 2), + /** * @DRM_GPUVA_USERBITS: user defined bits */ - DRM_GPUVA_USERBITS = (1 << 2), + DRM_GPUVA_USERBITS = (1 << 3), }; /** @@ -111,6 +120,18 @@ struct drm_gpuva { */ u64 offset; + /* + * @gem.range: the range of the GEM that is mapped + * + * When dealing with normal mappings, this must be zero. + * When flags has DRM_GPUVA_REPEAT set, this field must be + * smaller than va.range and va.range must be a multiple of + * gem.range. + * This is a u32 not a u64 because we expect repeated mappings + * to be pointing to relatively small portions of a GEM object. + */ + u32 range; + /** * @gem.obj: the mapped &drm_gem_object */ @@ -866,6 +887,17 @@ struct drm_gpuva_op_map { */ u64 offset; + /* + * @gem.range: the range of the GEM that is mapped + * + * When dealing with normal mappings, this must be zero. + * When flags has DRM_GPUVA_REPEAT set, it must be a multiple + * of va.range. This is a u32 not a u64 because we expect + * repeated mappings to be pointing to a relatively small + * portion of a GEM object. + */ + u32 range; + /** * @gem.obj: the &drm_gem_object to map */ From 4f102966f87f89a660b09c49761f4a7babb483a4 Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Sun, 21 Dec 2025 13:04:32 +0100 Subject: [PATCH 1265/3902] fixup! drm/gpuvm: Add DRM_GPUVA_REPEAT flag and logic Signed-off-by: Janne Grunau --- drivers/gpu/drm/drm_gpuvm.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/gpu/drm/drm_gpuvm.c b/drivers/gpu/drm/drm_gpuvm.c index ebfd9b1b683e19..726bae0eaeb2d2 100644 --- a/drivers/gpu/drm/drm_gpuvm.c +++ b/drivers/gpu/drm/drm_gpuvm.c @@ -2134,6 +2134,7 @@ op_map_cb(const struct drm_gpuvm_ops *fn, void *priv, op.map.va.range = req->map.va.range; op.map.gem.obj = req->map.gem.obj; op.map.gem.offset = req->map.gem.offset; + op.map.flags = req->map.flags; return fn->sm_step_map(&op, priv); } From e13d2ee4e60efdfb9a3a14ff33ad5bf3e4701fd6 Mon Sep 17 00:00:00 2001 From: Alice Ryhl Date: Mon, 6 Oct 2025 12:05:55 +0000 Subject: [PATCH 1266/3902] drm/gpuvm: add deferred vm_bo cleanup When using GPUVM in immediate mode, it is necessary to call drm_gpuvm_unlink() from the fence signalling critical path. However, unlink may call drm_gpuvm_bo_put(), which causes some challenges: 1. drm_gpuvm_bo_put() often requires you to take resv locks, which you can't do from the fence signalling critical path. 2. drm_gpuvm_bo_put() calls drm_gem_object_put(), which is often going to be unsafe to call from the fence signalling critical path. To solve these issues, add a deferred version of drm_gpuvm_unlink() that adds the vm_bo to a deferred cleanup list, and then clean it up later. The new methods take the GEMs GPUVA lock internally rather than letting the caller do it because it also needs to perform an operation after releasing the mutex again. This is to prevent freeing the GEM while holding the mutex (more info as comments in the patch). This means that the new methods can only be used with DRM_GPUVM_IMMEDIATE_MODE. Reviewed-by: Boris Brezillon Acked-by: Danilo Krummrich Link: https://lore.kernel.org/r/20251006-vmbo-defer-v4-1-30cbd2c05adb@google.com [aliceryhl: fix formatting of vm_bo = llist_entry(...) line] Signed-off-by: Alice Ryhl --- drivers/gpu/drm/drm_gpuvm.c | 190 ++++++++++++++++++++++++++++++++++++ include/drm/drm_gpuvm.h | 16 +++ 2 files changed, 206 insertions(+) diff --git a/drivers/gpu/drm/drm_gpuvm.c b/drivers/gpu/drm/drm_gpuvm.c index 726bae0eaeb2d2..65d67356e449dd 100644 --- a/drivers/gpu/drm/drm_gpuvm.c +++ b/drivers/gpu/drm/drm_gpuvm.c @@ -876,6 +876,31 @@ __drm_gpuvm_bo_list_add(struct drm_gpuvm *gpuvm, spinlock_t *lock, cond_spin_unlock(lock, !!lock); } +/** + * drm_gpuvm_bo_is_zombie() - check whether this vm_bo is scheduled for cleanup + * @vm_bo: the &drm_gpuvm_bo + * + * When a vm_bo is scheduled for cleanup using the bo_defer list, it is not + * immediately removed from the evict and extobj lists. Therefore, anyone + * iterating these lists should skip entries that are being destroyed. + * + * Checking the refcount without incrementing it is okay as long as the lock + * protecting the evict/extobj list is held for as long as you are using the + * vm_bo, because even if the refcount hits zero while you are using it, freeing + * the vm_bo requires taking the list's lock. + * + * Zombie entries can be observed on the evict and extobj lists regardless of + * whether DRM_GPUVM_RESV_PROTECTED is used, but they remain on the lists for a + * longer time when the resv lock is used because we can't take the resv lock + * during run_job() in immediate mode, meaning that they need to remain on the + * lists until drm_gpuvm_bo_deferred_cleanup() is called. + */ +static bool +drm_gpuvm_bo_is_zombie(struct drm_gpuvm_bo *vm_bo) +{ + return !kref_read(&vm_bo->kref); +} + /** * drm_gpuvm_bo_list_add() - insert a vm_bo into the given list * @__vm_bo: the &drm_gpuvm_bo @@ -1081,6 +1106,8 @@ drm_gpuvm_init(struct drm_gpuvm *gpuvm, const char *name, INIT_LIST_HEAD(&gpuvm->evict.list); spin_lock_init(&gpuvm->evict.lock); + init_llist_head(&gpuvm->bo_defer); + kref_init(&gpuvm->kref); gpuvm->name = name ? name : "unknown"; @@ -1122,6 +1149,8 @@ drm_gpuvm_fini(struct drm_gpuvm *gpuvm) "Extobj list should be empty.\n"); drm_WARN(gpuvm->drm, !list_empty(&gpuvm->evict.list), "Evict list should be empty.\n"); + drm_WARN(gpuvm->drm, !llist_empty(&gpuvm->bo_defer), + "VM BO cleanup list should be empty.\n"); drm_gem_object_put(gpuvm->r_obj); } @@ -1217,6 +1246,9 @@ drm_gpuvm_prepare_objects_locked(struct drm_gpuvm *gpuvm, drm_gpuvm_resv_assert_held(gpuvm); list_for_each_entry(vm_bo, &gpuvm->extobj.list, list.entry.extobj) { + if (drm_gpuvm_bo_is_zombie(vm_bo)) + continue; + ret = exec_prepare_obj(exec, vm_bo->obj, num_fences); if (ret) break; @@ -1460,6 +1492,9 @@ drm_gpuvm_validate_locked(struct drm_gpuvm *gpuvm, struct drm_exec *exec) list_for_each_entry_safe(vm_bo, next, &gpuvm->evict.list, list.entry.evict) { + if (drm_gpuvm_bo_is_zombie(vm_bo)) + continue; + ret = ops->vm_bo_validate(vm_bo, exec); if (ret) break; @@ -1560,6 +1595,7 @@ drm_gpuvm_bo_create(struct drm_gpuvm *gpuvm, INIT_LIST_HEAD(&vm_bo->list.entry.extobj); INIT_LIST_HEAD(&vm_bo->list.entry.evict); + init_llist_node(&vm_bo->list.entry.bo_defer); return vm_bo; } @@ -1621,6 +1657,126 @@ drm_gpuvm_bo_put(struct drm_gpuvm_bo *vm_bo) } EXPORT_SYMBOL_GPL(drm_gpuvm_bo_put); +/* + * drm_gpuvm_bo_into_zombie() - called when the vm_bo becomes a zombie due to + * deferred cleanup + * + * If deferred cleanup is used, then this must be called right after the vm_bo + * refcount drops to zero. Must be called with GEM mutex held. After releasing + * the GEM mutex, drm_gpuvm_bo_defer_zombie_cleanup() must be called. + */ +static void +drm_gpuvm_bo_into_zombie(struct kref *kref) +{ + struct drm_gpuvm_bo *vm_bo = container_of(kref, struct drm_gpuvm_bo, + kref); + + if (!drm_gpuvm_resv_protected(vm_bo->vm)) { + drm_gpuvm_bo_list_del(vm_bo, extobj, true); + drm_gpuvm_bo_list_del(vm_bo, evict, true); + } + + list_del(&vm_bo->list.entry.gem); +} + +/* + * drm_gpuvm_bo_defer_zombie_cleanup() - adds a new zombie vm_bo to the + * bo_defer list + * + * Called after drm_gpuvm_bo_into_zombie(). GEM mutex must not be held. + * + * It's important that the GEM stays alive for the duration in which we hold + * the mutex, but the instant we add the vm_bo to bo_defer, another thread + * might call drm_gpuvm_bo_deferred_cleanup() and put the GEM. Therefore, to + * avoid kfreeing a mutex we are holding, the GEM mutex must be released + * *before* calling this function. + */ +static void +drm_gpuvm_bo_defer_zombie_cleanup(struct drm_gpuvm_bo *vm_bo) +{ + llist_add(&vm_bo->list.entry.bo_defer, &vm_bo->vm->bo_defer); +} + +static void +drm_gpuvm_bo_defer_free(struct kref *kref) +{ + struct drm_gpuvm_bo *vm_bo = container_of(kref, struct drm_gpuvm_bo, + kref); + + drm_gpuvm_bo_into_zombie(kref); + mutex_unlock(&vm_bo->obj->gpuva.lock); + drm_gpuvm_bo_defer_zombie_cleanup(vm_bo); +} + +/** + * drm_gpuvm_bo_put_deferred() - drop a struct drm_gpuvm_bo reference with + * deferred cleanup + * @vm_bo: the &drm_gpuvm_bo to release the reference of + * + * This releases a reference to @vm_bo. + * + * This might take and release the GEMs GPUVA lock. You should call + * drm_gpuvm_bo_deferred_cleanup() later to complete the cleanup process. + * + * Returns: true if vm_bo is being destroyed, false otherwise. + */ +bool +drm_gpuvm_bo_put_deferred(struct drm_gpuvm_bo *vm_bo) +{ + if (!vm_bo) + return false; + + drm_WARN_ON(vm_bo->vm->drm, !drm_gpuvm_immediate_mode(vm_bo->vm)); + + return !!kref_put_mutex(&vm_bo->kref, + drm_gpuvm_bo_defer_free, + &vm_bo->obj->gpuva.lock); +} +EXPORT_SYMBOL_GPL(drm_gpuvm_bo_put_deferred); + +/** + * drm_gpuvm_bo_deferred_cleanup() - clean up BOs in the deferred list + * deferred cleanup + * @gpuvm: the VM to clean up + * + * Cleans up &drm_gpuvm_bo instances in the deferred cleanup list. + */ +void +drm_gpuvm_bo_deferred_cleanup(struct drm_gpuvm *gpuvm) +{ + const struct drm_gpuvm_ops *ops = gpuvm->ops; + struct drm_gpuvm_bo *vm_bo; + struct drm_gem_object *obj; + struct llist_node *bo_defer; + + bo_defer = llist_del_all(&gpuvm->bo_defer); + if (!bo_defer) + return; + + if (drm_gpuvm_resv_protected(gpuvm)) { + dma_resv_lock(drm_gpuvm_resv(gpuvm), NULL); + llist_for_each_entry(vm_bo, bo_defer, list.entry.bo_defer) { + drm_gpuvm_bo_list_del(vm_bo, extobj, false); + drm_gpuvm_bo_list_del(vm_bo, evict, false); + } + dma_resv_unlock(drm_gpuvm_resv(gpuvm)); + } + + while (bo_defer) { + vm_bo = llist_entry(bo_defer, struct drm_gpuvm_bo, list.entry.bo_defer); + bo_defer = bo_defer->next; + obj = vm_bo->obj; + if (ops && ops->vm_bo_free) + ops->vm_bo_free(vm_bo); + else + kfree(vm_bo); + + drm_gpuvm_put(gpuvm); + drm_gem_object_put(obj); + } +} +EXPORT_SYMBOL_GPL(drm_gpuvm_bo_deferred_cleanup); + static struct drm_gpuvm_bo * __drm_gpuvm_bo_find(struct drm_gpuvm *gpuvm, struct drm_gem_object *obj) @@ -1948,6 +2104,40 @@ drm_gpuva_unlink(struct drm_gpuva *va) } EXPORT_SYMBOL_GPL(drm_gpuva_unlink); +/** + * drm_gpuva_unlink_defer() - unlink a &drm_gpuva with deferred vm_bo cleanup + * @va: the &drm_gpuva to unlink + * + * Similar to drm_gpuva_unlink(), but uses drm_gpuvm_bo_put_deferred() and takes + * the lock for the caller. + */ +void +drm_gpuva_unlink_defer(struct drm_gpuva *va) +{ + struct drm_gem_object *obj = va->gem.obj; + struct drm_gpuvm_bo *vm_bo = va->vm_bo; + bool should_defer_bo; + + if (unlikely(!obj)) + return; + + drm_WARN_ON(vm_bo->vm->drm, !drm_gpuvm_immediate_mode(vm_bo->vm)); + + mutex_lock(&obj->gpuva.lock); + list_del_init(&va->gem.entry); + + /* + * This is drm_gpuvm_bo_put_deferred() except we already hold the mutex. + */ + should_defer_bo = kref_put(&vm_bo->kref, drm_gpuvm_bo_into_zombie); + mutex_unlock(&obj->gpuva.lock); + if (should_defer_bo) + drm_gpuvm_bo_defer_zombie_cleanup(vm_bo); + + va->vm_bo = NULL; +} +EXPORT_SYMBOL_GPL(drm_gpuva_unlink_defer); + /** * drm_gpuva_find_first() - find the first &drm_gpuva in the given range * @gpuvm: the &drm_gpuvm to search in diff --git a/include/drm/drm_gpuvm.h b/include/drm/drm_gpuvm.h index bed3dd74e64cdb..75c48345677b0f 100644 --- a/include/drm/drm_gpuvm.h +++ b/include/drm/drm_gpuvm.h @@ -27,6 +27,7 @@ #include #include +#include #include #include @@ -173,6 +174,7 @@ void drm_gpuva_remove(struct drm_gpuva *va); void drm_gpuva_link(struct drm_gpuva *va, struct drm_gpuvm_bo *vm_bo); void drm_gpuva_unlink(struct drm_gpuva *va); +void drm_gpuva_unlink_defer(struct drm_gpuva *va); struct drm_gpuva *drm_gpuva_find(struct drm_gpuvm *gpuvm, u64 addr, u64 range); @@ -352,6 +354,11 @@ struct drm_gpuvm { */ spinlock_t lock; } evict; + + /** + * @bo_defer: structure holding vm_bos that need to be destroyed + */ + struct llist_head bo_defer; }; void drm_gpuvm_init(struct drm_gpuvm *gpuvm, const char *name, @@ -735,6 +742,12 @@ struct drm_gpuvm_bo { * &drm_gpuvms evict list. */ struct list_head evict; + + /** + * @list.entry.bo_defer: List entry to attach to + * the &drm_gpuvms bo_defer list. + */ + struct llist_node bo_defer; } entry; } list; }; @@ -767,6 +780,9 @@ drm_gpuvm_bo_get(struct drm_gpuvm_bo *vm_bo) bool drm_gpuvm_bo_put(struct drm_gpuvm_bo *vm_bo); +bool drm_gpuvm_bo_put_deferred(struct drm_gpuvm_bo *vm_bo); +void drm_gpuvm_bo_deferred_cleanup(struct drm_gpuvm *gpuvm); + struct drm_gpuvm_bo * drm_gpuvm_bo_find(struct drm_gpuvm *gpuvm, struct drm_gem_object *obj); From a00839f61aa765ab3a1223efe3e1067c27192324 Mon Sep 17 00:00:00 2001 From: Alice Ryhl Date: Mon, 6 Oct 2025 12:05:56 +0000 Subject: [PATCH 1267/3902] panthor: use drm_gpuva_unlink_defer() Instead of manually deferring cleanup of vm_bos, use the new GPUVM infrastructure for doing so. To avoid manual management of vm_bo refcounts, the panthor_vma_link() and panthor_vma_unlink() methods are changed to get and put a vm_bo refcount on the vm_bo. This simplifies the code a lot. I preserved the behavior where panthor_gpuva_sm_step_map() drops the refcount right away rather than letting panthor_vm_cleanup_op_ctx() do it later. Reviewed-by: Boris Brezillon Link: https://lore.kernel.org/r/20251006-vmbo-defer-v4-2-30cbd2c05adb@google.com Signed-off-by: Alice Ryhl --- drivers/gpu/drm/panthor/panthor_mmu.c | 110 +++++--------------------- 1 file changed, 19 insertions(+), 91 deletions(-) diff --git a/drivers/gpu/drm/panthor/panthor_mmu.c b/drivers/gpu/drm/panthor/panthor_mmu.c index 7870e7dbaa5d45..5bfdc0ebf8fb8f 100644 --- a/drivers/gpu/drm/panthor/panthor_mmu.c +++ b/drivers/gpu/drm/panthor/panthor_mmu.c @@ -181,20 +181,6 @@ struct panthor_vm_op_ctx { u64 range; } va; - /** - * @returned_vmas: List of panthor_vma objects returned after a VM operation. - * - * For unmap operations, this will contain all VMAs that were covered by the - * specified VA range. - * - * For map operations, this will contain all VMAs that previously mapped to - * the specified VA range. - * - * Those VMAs, and the resources they point to will be released as part of - * the op_ctx cleanup operation. - */ - struct list_head returned_vmas; - /** @map: Fields specific to a map operation. */ struct { /** @map.vm_bo: Buffer object to map. */ @@ -1081,47 +1067,18 @@ void panthor_vm_free_va(struct panthor_vm *vm, struct drm_mm_node *va_node) mutex_unlock(&vm->mm_lock); } -static void panthor_vm_bo_put(struct drm_gpuvm_bo *vm_bo) +static void panthor_vm_bo_free(struct drm_gpuvm_bo *vm_bo) { struct panthor_gem_object *bo = to_panthor_bo(vm_bo->obj); - struct drm_gpuvm *vm = vm_bo->vm; - bool unpin; - - /* We must retain the GEM before calling drm_gpuvm_bo_put(), - * otherwise the mutex might be destroyed while we hold it. - * Same goes for the VM, since we take the VM resv lock. - */ - drm_gem_object_get(&bo->base.base); - drm_gpuvm_get(vm); - - /* We take the resv lock to protect against concurrent accesses to the - * gpuvm evicted/extobj lists that are modified in - * drm_gpuvm_bo_destroy(), which is called if drm_gpuvm_bo_put() - * releases sthe last vm_bo reference. - * We take the BO GPUVA list lock to protect the vm_bo removal from the - * GEM vm_bo list. - */ - dma_resv_lock(drm_gpuvm_resv(vm), NULL); - mutex_lock(&bo->base.base.gpuva.lock); - unpin = drm_gpuvm_bo_put(vm_bo); - mutex_unlock(&bo->base.base.gpuva.lock); - dma_resv_unlock(drm_gpuvm_resv(vm)); - /* If the vm_bo object was destroyed, release the pin reference that - * was hold by this object. - */ - if (unpin && !drm_gem_is_imported(&bo->base.base)) + if (!drm_gem_is_imported(&bo->base.base)) drm_gem_shmem_unpin(&bo->base); - - drm_gpuvm_put(vm); - drm_gem_object_put(&bo->base.base); + kfree(vm_bo); } static void panthor_vm_cleanup_op_ctx(struct panthor_vm_op_ctx *op_ctx, struct panthor_vm *vm) { - struct panthor_vma *vma, *tmp_vma; - u32 remaining_pt_count = op_ctx->rsvd_page_tables.count - op_ctx->rsvd_page_tables.ptr; @@ -1134,16 +1091,12 @@ static void panthor_vm_cleanup_op_ctx(struct panthor_vm_op_ctx *op_ctx, kfree(op_ctx->rsvd_page_tables.pages); if (op_ctx->map.vm_bo) - panthor_vm_bo_put(op_ctx->map.vm_bo); + drm_gpuvm_bo_put_deferred(op_ctx->map.vm_bo); for (u32 i = 0; i < ARRAY_SIZE(op_ctx->preallocated_vmas); i++) kfree(op_ctx->preallocated_vmas[i]); - list_for_each_entry_safe(vma, tmp_vma, &op_ctx->returned_vmas, node) { - list_del(&vma->node); - panthor_vm_bo_put(vma->base.vm_bo); - kfree(vma); - } + drm_gpuvm_bo_deferred_cleanup(&vm->base); } static struct panthor_vma * @@ -1236,7 +1189,6 @@ static int panthor_vm_prepare_map_op_ctx(struct panthor_vm_op_ctx *op_ctx, return -EINVAL; memset(op_ctx, 0, sizeof(*op_ctx)); - INIT_LIST_HEAD(&op_ctx->returned_vmas); op_ctx->flags = flags; op_ctx->va.range = size; op_ctx->va.addr = va; @@ -1247,7 +1199,9 @@ static int panthor_vm_prepare_map_op_ctx(struct panthor_vm_op_ctx *op_ctx, if (!drm_gem_is_imported(&bo->base.base)) { /* Pre-reserve the BO pages, so the map operation doesn't have to - * allocate. + * allocate. This pin is dropped in panthor_vm_bo_free(), so + * once we have successfully called drm_gpuvm_bo_create(), + * GPUVM will take care of dropping the pin for us. */ ret = drm_gem_shmem_pin(&bo->base); if (ret) @@ -1286,16 +1240,6 @@ static int panthor_vm_prepare_map_op_ctx(struct panthor_vm_op_ctx *op_ctx, mutex_unlock(&bo->base.base.gpuva.lock); dma_resv_unlock(panthor_vm_resv(vm)); - /* If the a vm_bo for this combination exists, it already - * retains a pin ref, and we can release the one we took earlier. - * - * If our pre-allocated vm_bo is picked, it now retains the pin ref, - * which will be released in panthor_vm_bo_put(). - */ - if (preallocated_vm_bo != op_ctx->map.vm_bo && - !drm_gem_is_imported(&bo->base.base)) - drm_gem_shmem_unpin(&bo->base); - op_ctx->map.bo_offset = offset; /* L1, L2 and L3 page tables. @@ -1343,7 +1287,6 @@ static int panthor_vm_prepare_unmap_op_ctx(struct panthor_vm_op_ctx *op_ctx, int ret; memset(op_ctx, 0, sizeof(*op_ctx)); - INIT_LIST_HEAD(&op_ctx->returned_vmas); op_ctx->va.range = size; op_ctx->va.addr = va; op_ctx->flags = DRM_PANTHOR_VM_BIND_OP_TYPE_UNMAP; @@ -1391,7 +1334,6 @@ static void panthor_vm_prepare_sync_only_op_ctx(struct panthor_vm_op_ctx *op_ctx struct panthor_vm *vm) { memset(op_ctx, 0, sizeof(*op_ctx)); - INIT_LIST_HEAD(&op_ctx->returned_vmas); op_ctx->flags = DRM_PANTHOR_VM_BIND_OP_TYPE_SYNC_ONLY; } @@ -2037,26 +1979,13 @@ static void panthor_vma_link(struct panthor_vm *vm, mutex_lock(&bo->base.base.gpuva.lock); drm_gpuva_link(&vma->base, vm_bo); - drm_WARN_ON(&vm->ptdev->base, drm_gpuvm_bo_put(vm_bo)); mutex_unlock(&bo->base.base.gpuva.lock); } -static void panthor_vma_unlink(struct panthor_vm *vm, - struct panthor_vma *vma) +static void panthor_vma_unlink(struct panthor_vma *vma) { - struct panthor_gem_object *bo = to_panthor_bo(vma->base.gem.obj); - struct drm_gpuvm_bo *vm_bo = drm_gpuvm_bo_get(vma->base.vm_bo); - - mutex_lock(&bo->base.base.gpuva.lock); - drm_gpuva_unlink(&vma->base); - mutex_unlock(&bo->base.base.gpuva.lock); - - /* drm_gpuva_unlink() release the vm_bo, but we manually retained it - * when entering this function, so we can implement deferred VMA - * destruction. Re-assign it here. - */ - vma->base.vm_bo = vm_bo; - list_add_tail(&vma->node, &vm->op_ctx->returned_vmas); + drm_gpuva_unlink_defer(&vma->base); + kfree(vma); } static void panthor_vma_init(struct panthor_vma *vma, u32 flags) @@ -2088,12 +2017,12 @@ static int panthor_gpuva_sm_step_map(struct drm_gpuva_op *op, void *priv) if (ret) return ret; - /* Ref owned by the mapping now, clear the obj field so we don't release the - * pinning/obj ref behind GPUVA's back. - */ drm_gpuva_map(&vm->base, &vma->base, &op->map); panthor_vma_link(vm, vma, op_ctx->map.vm_bo); + + drm_gpuvm_bo_put_deferred(op_ctx->map.vm_bo); op_ctx->map.vm_bo = NULL; + return 0; } @@ -2132,16 +2061,14 @@ static int panthor_gpuva_sm_step_remap(struct drm_gpuva_op *op, * owned by the old mapping which will be released when this * mapping is destroyed, we need to grab a ref here. */ - panthor_vma_link(vm, prev_vma, - drm_gpuvm_bo_get(op->remap.unmap->va->vm_bo)); + panthor_vma_link(vm, prev_vma, op->remap.unmap->va->vm_bo); } if (next_vma) { - panthor_vma_link(vm, next_vma, - drm_gpuvm_bo_get(op->remap.unmap->va->vm_bo)); + panthor_vma_link(vm, next_vma, op->remap.unmap->va->vm_bo); } - panthor_vma_unlink(vm, unmap_vma); + panthor_vma_unlink(unmap_vma); return 0; } @@ -2158,12 +2085,13 @@ static int panthor_gpuva_sm_step_unmap(struct drm_gpuva_op *op, return ret; drm_gpuva_unmap(&op->unmap); - panthor_vma_unlink(vm, unmap_vma); + panthor_vma_unlink(unmap_vma); return 0; } static const struct drm_gpuvm_ops panthor_gpuvm_ops = { .vm_free = panthor_vm_free, + .vm_bo_free = panthor_vm_bo_free, .sm_step_map = panthor_gpuva_sm_step_map, .sm_step_remap = panthor_gpuva_sm_step_remap, .sm_step_unmap = panthor_gpuva_sm_step_unmap, From 435479d3e2abe79171a9b3146a5af0d9dcbcae94 Mon Sep 17 00:00:00 2001 From: Asahi Lina Date: Wed, 8 May 2024 14:17:05 +0900 Subject: [PATCH 1268/3902] rust: drm: Add GPUVM Manager abstraction rust: drm/gpuvm: Take &GpuVmBo for map_and_link_va() rust: drm/gpuvm: Pass vm_bo explicitly to step_remap() We cannot drop ARef> references within the step_*() calls, since the destructore takes the object lock but that is already locked here. Instead of providing a method that the callback can use to obtain a reference (which, when dropped, would deadlock), grab a reference ourselves and pass it explicitly into the callback as a &ref. Thus, we can drop it without locking again. rust: drm/gpuvm: bo_unmap() should take &GpuVmBo, not ARef. rust: drm/gpuvm: Add interruptible flag to exec_lock() Signed-off-by: Asahi Lina --- rust/bindings/bindings_helper.h | 3 + rust/helpers/drm_gpuvm.c | 34 ++ rust/helpers/helpers.c | 1 + rust/kernel/drm/gpuvm.rs | 670 ++++++++++++++++++++++++++++++++ rust/kernel/drm/mod.rs | 2 + 5 files changed, 710 insertions(+) create mode 100644 rust/helpers/drm_gpuvm.c create mode 100644 rust/kernel/drm/gpuvm.rs diff --git a/rust/bindings/bindings_helper.h b/rust/bindings/bindings_helper.h index eb61b086f6c622..71076935d31d93 100644 --- a/rust/bindings/bindings_helper.h +++ b/rust/bindings/bindings_helper.h @@ -31,9 +31,11 @@ #include #include #include +#include #include #include #include +#include #include #include #include @@ -113,6 +115,7 @@ const gfp_t RUST_CONST_HELPER___GFP_ZERO = __GFP_ZERO; const gfp_t RUST_CONST_HELPER___GFP_HIGHMEM = ___GFP_HIGHMEM; const gfp_t RUST_CONST_HELPER___GFP_NOWARN = ___GFP_NOWARN; const blk_features_t RUST_CONST_HELPER_BLK_FEAT_ROTATIONAL = BLK_FEAT_ROTATIONAL; +const uint32_t RUST_CONST_HELPER_DRM_EXEC_INTERRUPTIBLE_WAIT = DRM_EXEC_INTERRUPTIBLE_WAIT; const fop_flags_t RUST_CONST_HELPER_FOP_UNSIGNED_OFFSET = FOP_UNSIGNED_OFFSET; const xa_mark_t RUST_CONST_HELPER_XA_PRESENT = XA_PRESENT; diff --git a/rust/helpers/drm_gpuvm.c b/rust/helpers/drm_gpuvm.c new file mode 100644 index 00000000000000..f4f4ea2c4ec897 --- /dev/null +++ b/rust/helpers/drm_gpuvm.c @@ -0,0 +1,34 @@ +// SPDX-License-Identifier: GPL-2.0 + +#include + +#ifdef CONFIG_DRM +#ifdef CONFIG_DRM_GPUVM + +struct drm_gpuvm *rust_helper_drm_gpuvm_get(struct drm_gpuvm *obj) +{ + return drm_gpuvm_get(obj); +} + +void rust_helper_drm_gpuvm_exec_unlock(struct drm_gpuvm_exec *vm_exec) +{ + return drm_gpuvm_exec_unlock(vm_exec); +} + +void rust_helper_drm_gpuva_init_from_op(struct drm_gpuva *va, struct drm_gpuva_op_map *op) +{ + drm_gpuva_init_from_op(va, op); +} + +struct drm_gpuvm_bo *rust_helper_drm_gpuvm_bo_get(struct drm_gpuvm_bo *vm_bo) +{ + return drm_gpuvm_bo_get(vm_bo); +} + +bool rust_helper_drm_gpuvm_is_extobj(struct drm_gpuvm *gpuvm, struct drm_gem_object *obj) +{ + return drm_gpuvm_is_extobj(gpuvm, obj); +} + +#endif +#endif diff --git a/rust/helpers/helpers.c b/rust/helpers/helpers.c index fcf6b9bafdf11c..81c69e7ac46bf2 100644 --- a/rust/helpers/helpers.c +++ b/rust/helpers/helpers.c @@ -29,6 +29,7 @@ #include "dma-mapping.c" #include "dma-resv.c" #include "drm.c" +#include "drm_gpuvm.c" #include "drm_syncobj.c" #include "err.c" #include "irq.c" diff --git a/rust/kernel/drm/gpuvm.rs b/rust/kernel/drm/gpuvm.rs new file mode 100644 index 00000000000000..5c455d822ff14b --- /dev/null +++ b/rust/kernel/drm/gpuvm.rs @@ -0,0 +1,670 @@ +// SPDX-License-Identifier: GPL-2.0 OR MIT + +//! DRM Sync Objects +//! +//! C header: [`include/drm/drm_gpuvm.h`](../../../../include/drm/drm_gpuvm.h) + +#![allow(missing_docs)] + +use crate::{ + bindings, drm, + drm::device, + error::{ + code::{EINVAL, ENOMEM}, + from_result, to_result, Error, Result, + }, + prelude::*, + types::{ARef, AlwaysRefCounted, Opaque}, +}; + +use crate::drm::gem::BaseDriverObject; +use crate::drm::gem::IntoGEMObject; +use core::cell::UnsafeCell; +use core::marker::{PhantomData, PhantomPinned}; +use core::mem::ManuallyDrop; +use core::ops::{Deref, DerefMut, Range}; +use core::ptr::NonNull; +use pin_init; + +/// Trait that must be implemented by DRM drivers to represent a DRM GpuVm (a GPU address space). +pub trait DriverGpuVm: Sized { + /// The parent `Driver` implementation for this `DriverGpuVm`. + type Driver: drm::Driver; + type GpuVa: DriverGpuVa = (); + type GpuVmBo: DriverGpuVmBo = (); + type StepContext = (); + + fn step_map( + self: &mut UpdatingGpuVm<'_, Self>, + op: &mut OpMap, + ctx: &mut Self::StepContext, + ) -> Result; + fn step_unmap( + self: &mut UpdatingGpuVm<'_, Self>, + op: &mut OpUnMap, + ctx: &mut Self::StepContext, + ) -> Result; + fn step_remap( + self: &mut UpdatingGpuVm<'_, Self>, + op: &mut OpReMap, + vm_bo: &GpuVmBo, + ctx: &mut Self::StepContext, + ) -> Result; +} + +struct StepContext<'a, T: DriverGpuVm> { + gpuvm: &'a GpuVm, + ctx: &'a mut T::StepContext, +} + +/// Trait that must be implemented by DRM drivers to represent a DRM GpuVa (a mapping in GPU address space). +pub trait DriverGpuVa: Sized {} + +impl DriverGpuVa for () {} + +/// Trait that must be implemented by DRM drivers to represent a DRM GpuVmBo (a connection between a BO and a VM). +pub trait DriverGpuVmBo: Sized { + fn new() -> impl PinInit; +} + +/// Provide a default implementation for trivial types +impl DriverGpuVmBo for T { + fn new() -> impl PinInit { + pin_init::default() + } +} + +#[repr(transparent)] +pub struct OpMap(bindings::drm_gpuva_op_map, PhantomData); +#[repr(transparent)] +pub struct OpUnMap(bindings::drm_gpuva_op_unmap, PhantomData); +#[repr(transparent)] +pub struct OpReMap(bindings::drm_gpuva_op_remap, PhantomData); + +impl OpMap { + pub fn addr(&self) -> u64 { + self.0.va.addr + } + pub fn range(&self) -> u64 { + self.0.va.range + } + pub fn offset(&self) -> u64 { + self.0.gem.offset + } + pub fn object(&self) -> &<::Object as BaseDriverObject>::Object { + let p = unsafe { + <<::Object as BaseDriverObject>::Object as IntoGEMObject>::from_raw(self.0.gem.obj) + }; + // SAFETY: The GEM object has an active reference for the lifetime of this op + &*p + } + pub fn map_and_link_va( + &mut self, + gpuvm: &mut UpdatingGpuVm<'_, T>, + gpuva: Pin>>, + gpuvmbo: &GpuVmBo, + ) -> Result<(), Pin>>> { + // SAFETY: We are handing off the GpuVa ownership and it will not be moved. + let p = KBox::leak(unsafe { Pin::into_inner_unchecked(gpuva) }); + // SAFETY: These C functions are called with the correct invariants + unsafe { + bindings::drm_gpuva_init_from_op(&mut p.gpuva, &mut self.0); + if bindings::drm_gpuva_insert(gpuvm.0.gpuvm() as *mut _, &mut p.gpuva) != 0 { + // EEXIST, return the GpuVa to the caller as an error + return Err(Pin::new_unchecked(KBox::from_raw(p))); + }; + // SAFETY: This takes a new reference to the gpuvmbo. + bindings::drm_gpuva_link(&mut p.gpuva, &gpuvmbo.bo as *const _ as *mut _); + } + Ok(()) + } +} + +impl OpUnMap { + pub fn va(&self) -> Option<&GpuVa> { + if self.0.va.is_null() { + return None; + } + // SAFETY: Container invariant is guaranteed for ops structs created for our types. + let p = unsafe { crate::container_of!(self.0.va, GpuVa, gpuva) as *mut GpuVa }; + // SAFETY: The GpuVa object reference is valid per the op_unmap contract + Some(unsafe { &*p }) + } + pub fn unmap_and_unlink_va(&mut self) -> Option>>> { + if self.0.va.is_null() { + return None; + } + // SAFETY: Container invariant is guaranteed for ops structs created for our types. + let p = unsafe { crate::container_of!(self.0.va, GpuVa, gpuva) as *mut GpuVa }; + + // SAFETY: The GpuVa object reference is valid per the op_unmap contract + unsafe { + bindings::drm_gpuva_unmap(&mut self.0); + bindings::drm_gpuva_unlink(self.0.va); + } + + // Unlinking/unmapping relinquishes ownership of the GpuVa object, + // so clear the pointer + self.0.va = core::ptr::null_mut(); + // SAFETY: The GpuVa object reference is valid per the op_unmap contract + Some(unsafe { Pin::new_unchecked(KBox::from_raw(p)) }) + } +} + +impl OpReMap { + pub fn prev_map(&mut self) -> Option<&mut OpMap> { + // SAFETY: The prev pointer must be valid if not-NULL per the op_remap contract + unsafe { (self.0.prev as *mut OpMap).as_mut() } + } + pub fn next_map(&mut self) -> Option<&mut OpMap> { + // SAFETY: The next pointer must be valid if not-NULL per the op_remap contract + unsafe { (self.0.next as *mut OpMap).as_mut() } + } + pub fn unmap(&mut self) -> &mut OpUnMap { + // SAFETY: The unmap pointer is always valid per the op_remap contract + unsafe { (self.0.unmap as *mut OpUnMap).as_mut().unwrap() } + } +} + +/// A base GPU VA. +#[repr(C)] +#[pin_data] +pub struct GpuVa { + #[pin] + gpuva: bindings::drm_gpuva, + #[pin] + inner: T::GpuVa, + #[pin] + _p: PhantomPinned, +} + +impl GpuVa { + pub fn new(inner: impl PinInit) -> Result>>> + where + Error: From, + { + KBox::try_pin_init( + try_pin_init!(Self { + gpuva <- pin_init::init_zeroed(), + inner <- inner, + _p: PhantomPinned + }), + GFP_KERNEL, + ) + } + + pub fn addr(&self) -> u64 { + self.gpuva.va.addr + } + pub fn range(&self) -> u64 { + self.gpuva.va.range + } + pub fn offset(&self) -> u64 { + self.gpuva.gem.offset + } +} + +/// A base GpuVm BO. +#[repr(C)] +#[pin_data] +pub struct GpuVmBo { + #[pin] + bo: bindings::drm_gpuvm_bo, + #[pin] + inner: T::GpuVmBo, + #[pin] + _p: PhantomPinned, +} + +impl GpuVmBo { + /// Return a reference to the inner driver data for this GpuVmBo + pub fn inner(&self) -> &T::GpuVmBo { + &self.inner + } +} + +// SAFETY: DRM GpuVmBo objects are always reference counted and the get/put functions +// satisfy the requirements. +unsafe impl AlwaysRefCounted for GpuVmBo { + fn inc_ref(&self) { + // SAFETY: The drm_gpuvm_get function satisfies the requirements for inc_ref(). + unsafe { bindings::drm_gpuvm_bo_get(&self.bo as *const _ as *mut _) }; + } + + unsafe fn dec_ref(mut obj: NonNull) { + // SAFETY: drm_gpuvm_bo_put() requires holding the gpuva lock, which is the dma_resv lock by default. + // The drm_gpuvm_put function satisfies the requirements for dec_ref(). + // (We do not support custom locks yet.) + unsafe { + let resv = (*obj.as_mut().bo.obj).resv; + bindings::dma_resv_lock(resv, core::ptr::null_mut()); + bindings::drm_gpuvm_bo_put(&mut obj.as_mut().bo); + bindings::dma_resv_unlock(resv); + } + } +} + +/// A base GPU VM. +#[repr(C)] +#[pin_data] +pub struct GpuVm { + #[pin] + gpuvm: Opaque, + #[pin] + inner: UnsafeCell, + #[pin] + _p: PhantomPinned, +} + +pub(super) unsafe extern "C" fn vm_free_callback( + raw_gpuvm: *mut bindings::drm_gpuvm, +) { + // SAFETY: Container invariant is guaranteed for objects using our callback. + let p = unsafe { + crate::container_of!( + raw_gpuvm as *mut Opaque, + GpuVm, + gpuvm + ) as *mut GpuVm + }; + + // SAFETY: p is guaranteed to be valid for drm_gpuvm objects using this callback. + unsafe { drop(KBox::from_raw(p)) }; +} + +pub(super) unsafe extern "C" fn vm_bo_alloc_callback() -> *mut bindings::drm_gpuvm_bo +{ + let obj: Result>>> = KBox::try_pin_init( + try_pin_init!(GpuVmBo:: { + bo <- pin_init::default(), + inner <- T::GpuVmBo::new(), + _p: PhantomPinned + }), + GFP_KERNEL, + ); + + match obj { + Ok(obj) => + // SAFETY: The DRM core will keep this object pinned + unsafe { + let p = KBox::leak(Pin::into_inner_unchecked(obj)); + &mut p.bo + }, + Err(_) => core::ptr::null_mut(), + } +} + +pub(super) unsafe extern "C" fn vm_bo_free_callback( + raw_vm_bo: *mut bindings::drm_gpuvm_bo, +) { + // SAFETY: Container invariant is guaranteed for objects using this callback. + let p = unsafe { crate::container_of!(raw_vm_bo, GpuVmBo, bo) as *mut GpuVmBo }; + + // SAFETY: p is guaranteed to be valid for drm_gpuvm_bo objects using this callback. + unsafe { drop(KBox::from_raw(p)) }; +} + +pub(super) unsafe extern "C" fn step_map_callback( + op: *mut bindings::drm_gpuva_op, + _priv: *mut core::ffi::c_void, +) -> core::ffi::c_int { + // SAFETY: We know this is a map op, and OpMap is a transparent wrapper. + let map = unsafe { &mut *((&mut (*op).__bindgen_anon_1.map) as *mut _ as *mut OpMap) }; + // SAFETY: This is a pointer to a StepContext created inline in sm_map(), which is + // guaranteed to outlive this function. + let ctx = unsafe { &mut *(_priv as *mut StepContext<'_, T>) }; + + from_result(|| { + UpdatingGpuVm(ctx.gpuvm).step_map(map, ctx.ctx)?; + Ok(0) + }) +} + +pub(super) unsafe extern "C" fn step_remap_callback( + op: *mut bindings::drm_gpuva_op, + _priv: *mut core::ffi::c_void, +) -> core::ffi::c_int { + // SAFETY: We know this is a map op, and OpReMap is a transparent wrapper. + let remap = unsafe { &mut *((&mut (*op).__bindgen_anon_1.remap) as *mut _ as *mut OpReMap) }; + // SAFETY: This is a pointer to a StepContext created inline in sm_map(), which is + // guaranteed to outlive this function. + let ctx = unsafe { &mut *(_priv as *mut StepContext<'_, T>) }; + + let p_vm_bo = remap.unmap().va().unwrap().gpuva.vm_bo; + + let res = { + // SAFETY: vm_bo pointer must be valid and non-null by the step_remap invariants. + // Since we grab a ref, this reference's lifetime is until the decref. + let vm_bo_ref = unsafe { + bindings::drm_gpuvm_bo_get(p_vm_bo); + &*(crate::container_of!(p_vm_bo, GpuVmBo, bo) as *mut GpuVmBo) + }; + + from_result(|| { + UpdatingGpuVm(ctx.gpuvm).step_remap(remap, vm_bo_ref, ctx.ctx)?; + Ok(0) + }) + }; + + // SAFETY: We incremented the refcount above, and the Rust reference we took is + // no longer in scope. + unsafe { bindings::drm_gpuvm_bo_put(p_vm_bo) }; + + res +} +pub(super) unsafe extern "C" fn step_unmap_callback( + op: *mut bindings::drm_gpuva_op, + _priv: *mut core::ffi::c_void, +) -> core::ffi::c_int { + // SAFETY: We know this is a map op, and OpUnMap is a transparent wrapper. + let unmap = unsafe { &mut *((&mut (*op).__bindgen_anon_1.unmap) as *mut _ as *mut OpUnMap) }; + // SAFETY: This is a pointer to a StepContext created inline in sm_map(), which is + // guaranteed to outlive this function. + let ctx = unsafe { &mut *(_priv as *mut StepContext<'_, T>) }; + + from_result(|| { + UpdatingGpuVm(ctx.gpuvm).step_unmap(unmap, ctx.ctx)?; + Ok(0) + }) +} + +pub(super) unsafe extern "C" fn exec_lock_gem_object( + vm_exec: *mut bindings::drm_gpuvm_exec, +) -> core::ffi::c_int { + // SAFETY: The gpuvm_exec object is valid and priv_ is a GEM object pointer + // when this callback is used + unsafe { bindings::drm_exec_lock_obj(&mut (*vm_exec).exec, (*vm_exec).extra.priv_ as *mut _) } +} + +impl GpuVm { + const OPS: bindings::drm_gpuvm_ops = bindings::drm_gpuvm_ops { + vm_free: Some(vm_free_callback::), + op_alloc: None, + op_free: None, + vm_bo_alloc: Some(vm_bo_alloc_callback::), + vm_bo_free: Some(vm_bo_free_callback::), + vm_bo_validate: None, + sm_step_map: Some(step_map_callback::), + sm_step_remap: Some(step_remap_callback::), + sm_step_unmap: Some(step_unmap_callback::), + }; + + fn gpuvm(&self) -> *const bindings::drm_gpuvm { + self.gpuvm.get() + } + + pub fn new( + name: &'static CStr, + dev: &device::Device, + r_obj: ARef<<::Object as BaseDriverObject>::Object>, + range: Range, + reserve_range: Range, + inner: impl PinInit, + ) -> Result>> + where + Error: From, + { + let obj: Pin> = KBox::try_pin_init( + try_pin_init!(Self { + // SAFETY: drm_gpuvm_init cannot fail and always initializes the member + gpuvm <- unsafe { + pin_init::pin_init_from_closure(move |slot: *mut Opaque | { + // Zero-init required by drm_gpuvm_init + *slot = Opaque::zeroed(); + bindings::drm_gpuvm_init( + Opaque::cast_into(slot), + name.as_char_ptr(), + 0, + dev.as_raw(), + r_obj.as_raw() as *const _ as *mut _, + range.start, + range.end - range.start, + reserve_range.start, + reserve_range.end - reserve_range.start, + &Self::OPS + ); + Ok(()) + }) + }, + // SAFETY: Just passing through to the initializer argument + inner <- unsafe { + pin_init::pin_init_from_closure(move |slot: *mut UnsafeCell | { + inner.__pinned_init(slot as *mut _) + }) + }, + _p: PhantomPinned + }), + GFP_KERNEL, + )?; + + // SAFETY: We never move out of the object + let vm_ref = unsafe { + ARef::from_raw(NonNull::new_unchecked(KBox::leak( + Pin::into_inner_unchecked(obj), + ))) + }; + + Ok(vm_ref) + } + + pub fn exec_lock<'a, 'b>( + &'a self, + obj: Option<&'b <::Object as BaseDriverObject>::Object>, + interruptible: bool, + ) -> Result> { + // Do not try to lock the object if it is internal (since it is already locked). + let is_ext = obj.map(|a| self.is_extobj(a)).unwrap_or(false); + + let mut guard = ManuallyDrop::new(LockedGpuVm { + gpuvm: self, + // vm_exec needs to be pinned, so stick it in a Box. + vm_exec: KBox::init( + init!(bindings::drm_gpuvm_exec { + vm: self.gpuvm() as *mut _, + flags: if interruptible { + bindings::DRM_EXEC_INTERRUPTIBLE_WAIT + } else { + 0 + }, + exec: Default::default(), + extra: match (is_ext, obj) { + (true, Some(obj)) => bindings::drm_gpuvm_exec__bindgen_ty_1 { + fn_: Some(exec_lock_gem_object), + priv_: obj.as_raw() as *const _ as *mut _, + }, + _ => Default::default(), + }, + num_fences: 0, + }), + GFP_KERNEL, + )?, + obj, + }); + + // SAFETY: The object is valid and was initialized above + to_result(unsafe { bindings::drm_gpuvm_exec_lock(&mut *guard.vm_exec) })?; + + Ok(ManuallyDrop::into_inner(guard)) + } + + /// Returns true if the given object is external to the GPUVM + /// (that is, if it does not share the DMA reservation object of the GPUVM). + pub fn is_extobj(&self, obj: &impl IntoGEMObject) -> bool { + let gem = obj.as_raw() as *const _ as *mut _; + // SAFETY: This is safe to call as long as the arguments are valid pointers. + unsafe { bindings::drm_gpuvm_is_extobj(self.gpuvm() as *mut _, gem) } + } +} + +// SAFETY: DRM GpuVm objects are always reference counted and the get/put functions +// satisfy the requirements. +unsafe impl AlwaysRefCounted for GpuVm { + fn inc_ref(&self) { + // SAFETY: The drm_gpuvm_get function satisfies the requirements for inc_ref(). + unsafe { bindings::drm_gpuvm_get(&self.gpuvm as *const _ as *mut _) }; + } + + unsafe fn dec_ref(obj: NonNull) { + // SAFETY: The drm_gpuvm_put function satisfies the requirements for dec_ref(). + unsafe { bindings::drm_gpuvm_put(Opaque::cast_into(&(*obj.as_ptr()).gpuvm)) }; + } +} + +pub struct LockedGpuVm<'a, 'b, T: DriverGpuVm> { + gpuvm: &'a GpuVm, + vm_exec: KBox, + obj: Option<&'b <::Object as BaseDriverObject>::Object>, +} + +impl LockedGpuVm<'_, '_, T> { + pub fn find_bo(&mut self) -> Option>> { + let obj = self.obj?; + // SAFETY: LockedGpuVm implies the right locks are held. + let p = unsafe { + bindings::drm_gpuvm_bo_find( + self.gpuvm.gpuvm() as *mut _, + obj.as_raw() as *const _ as *mut _, + ) + }; + if p.is_null() { + None + } else { + // SAFETY: All the drm_gpuvm_bo objects in this GpuVm are always allocated by us as GpuVmBo. + let p = unsafe { crate::container_of!(p, GpuVmBo, bo) as *mut GpuVmBo }; + // SAFETY: We checked for NULL above, and the types ensure that + // this object was created by vm_bo_alloc_callback. + Some(unsafe { ARef::from_raw(NonNull::new_unchecked(p)) }) + } + } + + pub fn obtain_bo(&mut self) -> Result>> { + let obj = self.obj.ok_or(EINVAL)?; + // SAFETY: LockedGpuVm implies the right locks are held. + let p = unsafe { + bindings::drm_gpuvm_bo_obtain( + self.gpuvm.gpuvm() as *mut _, + obj.as_raw() as *const _ as *mut _, + ) + }; + if p.is_null() { + Err(ENOMEM) + } else { + // SAFETY: Container invariant is guaranteed for GpuVmBo objects for this GpuVm. + let p = unsafe { crate::container_of!(p, GpuVmBo, bo) as *mut GpuVmBo }; + // SAFETY: We checked for NULL above, and the types ensure that + // this object was created by vm_bo_alloc_callback. + Ok(unsafe { ARef::from_raw(NonNull::new_unchecked(p)) }) + } + } + + pub fn sm_map( + &mut self, + ctx: &mut T::StepContext, + req_addr: u64, + req_range: u64, + req_offset: u64, + ) -> Result { + let obj = self.obj.ok_or(EINVAL)?; + let mut ctx = StepContext { + ctx, + gpuvm: self.gpuvm, + }; + // SAFETY: LockedGpuVm implies the right locks are held. + to_result(unsafe { + bindings::drm_gpuvm_sm_map( + self.gpuvm.gpuvm() as *mut _, + &mut ctx as *mut _ as *mut _, + req_addr, + req_range, + obj.as_raw() as *const _ as *mut _, + req_offset, + ) + }) + } + + pub fn sm_unmap(&mut self, ctx: &mut T::StepContext, req_addr: u64, req_range: u64) -> Result { + let mut ctx = StepContext { + ctx, + gpuvm: self.gpuvm, + }; + // SAFETY: LockedGpuVm implies the right locks are held. + to_result(unsafe { + bindings::drm_gpuvm_sm_unmap( + self.gpuvm.gpuvm() as *mut _, + &mut ctx as *mut _ as *mut _, + req_addr, + req_range, + ) + }) + } + + pub fn bo_unmap(&mut self, ctx: &mut T::StepContext, bo: &GpuVmBo) -> Result { + let mut ctx = StepContext { + ctx, + gpuvm: self.gpuvm, + }; + // SAFETY: LockedGpuVm implies the right locks are held. + to_result(unsafe { + bindings::drm_gpuvm_bo_unmap(&bo.bo as *const _ as *mut _, &mut ctx as *mut _ as *mut _) + }) + } +} + +impl Deref for LockedGpuVm<'_, '_, T> { + type Target = T; + + fn deref(&self) -> &T { + // SAFETY: The existence of this LockedGpuVm implies the lock is held, + // so this is the only reference + unsafe { &*self.gpuvm.inner.get() } + } +} + +impl DerefMut for LockedGpuVm<'_, '_, T> { + fn deref_mut(&mut self) -> &mut T { + // SAFETY: The existence of this UpdatingGpuVm implies the lock is held, + // so this is the only reference + unsafe { &mut *self.gpuvm.inner.get() } + } +} + +impl Drop for LockedGpuVm<'_, '_, T> { + fn drop(&mut self) { + // SAFETY: We hold the lock, so it's safe to unlock + unsafe { + bindings::drm_gpuvm_exec_unlock(&mut *self.vm_exec); + } + } +} + +pub struct UpdatingGpuVm<'a, T: DriverGpuVm>(&'a GpuVm); + +impl UpdatingGpuVm<'_, T> {} + +impl Deref for UpdatingGpuVm<'_, T> { + type Target = T; + + fn deref(&self) -> &T { + // SAFETY: The existence of this UpdatingGpuVm implies the lock is held, + // so this is the only reference + unsafe { &*self.0.inner.get() } + } +} + +impl DerefMut for UpdatingGpuVm<'_, T> { + fn deref_mut(&mut self) -> &mut T { + // SAFETY: The existence of this UpdatingGpuVm implies the lock is held, + // so this is the only reference + unsafe { &mut *self.0.inner.get() } + } +} + +// SAFETY: All our trait methods take locks +unsafe impl Sync for GpuVm {} +// SAFETY: All our trait methods take locks +unsafe impl Send for GpuVm {} + +// SAFETY: All our trait methods take locks +unsafe impl Sync for GpuVmBo {} +// SAFETY: All our trait methods take locks +unsafe impl Send for GpuVmBo {} diff --git a/rust/kernel/drm/mod.rs b/rust/kernel/drm/mod.rs index f3e93bfe919cd4..882841415aa414 100644 --- a/rust/kernel/drm/mod.rs +++ b/rust/kernel/drm/mod.rs @@ -6,6 +6,8 @@ pub mod device; pub mod driver; pub mod file; pub mod gem; +#[cfg(CONFIG_DRM_GPUVM = "y")] +pub mod gpuvm; pub mod ioctl; pub mod mm; pub mod sched; From 46f5f1c4388746b45d3f0b27365cb0e4e3c93bb8 Mon Sep 17 00:00:00 2001 From: Asahi Lina Date: Tue, 21 Jan 2025 23:51:38 +0900 Subject: [PATCH 1269/3902] rust: drm/gpuvm: Add GpuVaFlags support Signed-off-by: Asahi Lina --- rust/kernel/drm/gpuvm.rs | 68 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 68 insertions(+) diff --git a/rust/kernel/drm/gpuvm.rs b/rust/kernel/drm/gpuvm.rs index 5c455d822ff14b..03f25f9bdd706c 100644 --- a/rust/kernel/drm/gpuvm.rs +++ b/rust/kernel/drm/gpuvm.rs @@ -26,6 +26,66 @@ use core::ops::{Deref, DerefMut, Range}; use core::ptr::NonNull; use pin_init; +/// GpuVaFlags to be used for a GpuVa. +/// +/// They can be combined with the operators `|`, `&`, and `!`. +#[derive(Clone, Copy, PartialEq, Default)] +pub struct GpuVaFlags(u32); + +impl GpuVaFlags { + /// No GpuVaFlags (zero) + pub const NONE: GpuVaFlags = GpuVaFlags(0); + + /// The backing GEM is invalidated. + pub const INVALIDATED: GpuVaFlags = GpuVaFlags(bindings::drm_gpuva_flags_DRM_GPUVA_INVALIDATED); + + /// The GpuVa is a sparse mapping. + pub const SPARSE: GpuVaFlags = GpuVaFlags(bindings::drm_gpuva_flags_DRM_GPUVA_SPARSE); + + /// The GpuVa is a repeat mapping. + pub const REPEAT: GpuVaFlags = GpuVaFlags(bindings::drm_gpuva_flags_DRM_GPUVA_REPEAT); + + /// Construct a driver-specific GpuVaFlag. + /// + /// The argument must be a flag index in the range [0..28]. + pub const fn user_flag(index: u32) -> GpuVaFlags { + let flags = bindings::drm_gpuva_flags_DRM_GPUVA_USERBITS << index; + assert!(flags != 0); + GpuVaFlags(flags) + } + + /// Get the raw representation of this flag. + pub(crate) fn as_raw(self) -> u32 { + self.0 + } + + /// Check whether `flags` is contained in `self`. + pub fn contains(self, flags: GpuVaFlags) -> bool { + (self & flags) == flags + } +} + +impl core::ops::BitOr for GpuVaFlags { + type Output = Self; + fn bitor(self, rhs: Self) -> Self::Output { + Self(self.0 | rhs.0) + } +} + +impl core::ops::BitAnd for GpuVaFlags { + type Output = Self; + fn bitand(self, rhs: Self) -> Self::Output { + Self(self.0 & rhs.0) + } +} + +impl core::ops::Not for GpuVaFlags { + type Output = Self; + fn not(self) -> Self::Output { + Self(!self.0) + } +} + /// Trait that must be implemented by DRM drivers to represent a DRM GpuVm (a GPU address space). pub trait DriverGpuVm: Sized { /// The parent `Driver` implementation for this `DriverGpuVm`. @@ -91,6 +151,9 @@ impl OpMap { pub fn offset(&self) -> u64 { self.0.gem.offset } + pub fn flags(&self) -> GpuVaFlags { + GpuVaFlags(self.0.flags) + } pub fn object(&self) -> &<::Object as BaseDriverObject>::Object { let p = unsafe { <<::Object as BaseDriverObject>::Object as IntoGEMObject>::from_raw(self.0.gem.obj) @@ -202,6 +265,9 @@ impl GpuVa { pub fn offset(&self) -> u64 { self.gpuva.gem.offset } + pub fn flags(&self) -> GpuVaFlags { + GpuVaFlags(self.gpuva.flags) + } } /// A base GpuVm BO. @@ -563,6 +629,7 @@ impl LockedGpuVm<'_, '_, T> { req_addr: u64, req_range: u64, req_offset: u64, + flags: GpuVaFlags, ) -> Result { let obj = self.obj.ok_or(EINVAL)?; let mut ctx = StepContext { @@ -578,6 +645,7 @@ impl LockedGpuVm<'_, '_, T> { req_range, obj.as_raw() as *const _ as *mut _, req_offset, + flags.as_raw(), ) }) } From 5d6788d4318bb128dd5711248eac20a2ea4f8ffb Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Sun, 2 Nov 2025 10:09:11 +0100 Subject: [PATCH 1270/3902] rust: drm: gem: Support locking gpuva.lock Signed-off-by: Janne Grunau --- rust/kernel/drm/gem/mod.rs | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/rust/kernel/drm/gem/mod.rs b/rust/kernel/drm/gem/mod.rs index f88d3589da4087..e294f8372a67cd 100644 --- a/rust/kernel/drm/gem/mod.rs +++ b/rust/kernel/drm/gem/mod.rs @@ -250,6 +250,20 @@ pub trait BaseObject: IntoGEMObject { // SAFETY: The arguments are valid per the type invariant. Ok(unsafe { bindings::drm_vma_node_offset_addr(&raw mut (*self.as_raw()).vma_node) }) } + + /// Lock the gpuva lock + fn lock_gpuva(&self) { + unsafe { + bindings::mutex_lock(&raw mut (*self.as_raw()).gpuva.lock); + } + } + + /// Lock the gpuva lock + fn unlock_gpuva(&self) { + unsafe { + bindings::mutex_unlock(&raw mut (*self.as_raw()).gpuva.lock); + } + } } impl BaseObject for T {} From 80de0b4b79de769cdbe22d605cc5a4c88d6d1935 Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Sun, 2 Nov 2025 10:11:11 +0100 Subject: [PATCH 1271/3902] rust: drm: gpuvm: Switch to DRM_GPUVM_IMMEDIATE_MODE DRM_GPUVM_IMMEDIATE_MODE allows for deferred gpuva unlink and gpuvm bo release. Signed-off-by: Janne Grunau --- rust/helpers/drm_gpuvm.c | 5 ++ rust/kernel/drm/gpuvm.rs | 101 +++++++++++++++++++++++++++++++++++++-- 2 files changed, 102 insertions(+), 4 deletions(-) diff --git a/rust/helpers/drm_gpuvm.c b/rust/helpers/drm_gpuvm.c index f4f4ea2c4ec897..1ed5b5841f68c2 100644 --- a/rust/helpers/drm_gpuvm.c +++ b/rust/helpers/drm_gpuvm.c @@ -25,6 +25,11 @@ struct drm_gpuvm_bo *rust_helper_drm_gpuvm_bo_get(struct drm_gpuvm_bo *vm_bo) return drm_gpuvm_bo_get(vm_bo); } +bool rust_helper_drm_gpuvm_immediate_mode(struct drm_gpuvm *gpuvm) +{ + return drm_gpuvm_immediate_mode(gpuvm); +} + bool rust_helper_drm_gpuvm_is_extobj(struct drm_gpuvm *gpuvm, struct drm_gem_object *obj) { return drm_gpuvm_is_extobj(gpuvm, obj); diff --git a/rust/kernel/drm/gpuvm.rs b/rust/kernel/drm/gpuvm.rs index 03f25f9bdd706c..71e9b4a5494125 100644 --- a/rust/kernel/drm/gpuvm.rs +++ b/rust/kernel/drm/gpuvm.rs @@ -8,7 +8,13 @@ use crate::{ bindings, drm, - drm::device, + drm::{ + device, + gem::{ + BaseObject, + IntoGEMObject, // + }, + }, error::{ code::{EINVAL, ENOMEM}, from_result, to_result, Error, Result, @@ -177,7 +183,9 @@ impl OpMap { return Err(Pin::new_unchecked(KBox::from_raw(p))); }; // SAFETY: This takes a new reference to the gpuvmbo. + gpuvmbo.lock_gpuva(); bindings::drm_gpuva_link(&mut p.gpuva, &gpuvmbo.bo as *const _ as *mut _); + gpuvmbo.unlock_gpuva(); } Ok(()) } @@ -194,6 +202,12 @@ impl OpUnMap { Some(unsafe { &*p }) } pub fn unmap_and_unlink_va(&mut self) -> Option>>> { + self.do_unmap_and_unlink_va(false) + } + pub fn unmap_and_unlink_va_defer(&mut self) -> Option>>> { + self.do_unmap_and_unlink_va(true) + } + fn do_unmap_and_unlink_va(&mut self, defer: bool) -> Option>>> { if self.0.va.is_null() { return None; } @@ -203,7 +217,11 @@ impl OpUnMap { // SAFETY: The GpuVa object reference is valid per the op_unmap contract unsafe { bindings::drm_gpuva_unmap(&mut self.0); - bindings::drm_gpuva_unlink(self.0.va); + if defer { + bindings::drm_gpuva_unlink_defer(self.0.va); + } else { + bindings::drm_gpuva_unlink(self.0.va); + } } // Unlinking/unmapping relinquishes ownership of the GpuVa object, @@ -287,6 +305,20 @@ impl GpuVmBo { pub fn inner(&self) -> &T::GpuVmBo { &self.inner } + /// Lock the GpuVmBo's gem boject gpuva lock + pub fn lock_gpuva(&self) { + unsafe { + let lock = &raw mut (*self.bo.obj).gpuva.lock; + bindings::mutex_lock(lock); + } + } + /// Unlock the GpuVmBo's gem boject gpuva lock + pub fn unlock_gpuva(&self) { + unsafe { + let lock = &raw mut (*self.bo.obj).gpuva.lock; + bindings::mutex_unlock(lock); + } + } } // SAFETY: DRM GpuVmBo objects are always reference counted and the get/put functions @@ -414,7 +446,7 @@ pub(super) unsafe extern "C" fn step_remap_callback( // SAFETY: We incremented the refcount above, and the Rust reference we took is // no longer in scope. - unsafe { bindings::drm_gpuvm_bo_put(p_vm_bo) }; + unsafe { bindings::drm_gpuvm_bo_put_deferred(p_vm_bo) }; res } @@ -461,6 +493,7 @@ impl GpuVm { pub fn new( name: &'static CStr, + flags: bindings::drm_gpuvm_flags, dev: &device::Device, r_obj: ARef<<::Object as BaseDriverObject>::Object>, range: Range, @@ -480,7 +513,7 @@ impl GpuVm { bindings::drm_gpuvm_init( Opaque::cast_into(slot), name.as_char_ptr(), - 0, + flags, dev.as_raw(), r_obj.as_raw() as *const _ as *mut _, range.start, @@ -560,6 +593,66 @@ impl GpuVm { // SAFETY: This is safe to call as long as the arguments are valid pointers. unsafe { bindings::drm_gpuvm_is_extobj(self.gpuvm() as *mut _, gem) } } + + pub fn bo_deferred_cleanup(&self) { + unsafe { bindings::drm_gpuvm_bo_deferred_cleanup(self.gpuvm() as *mut _) } + } + + pub fn find_bo(& self, + obj: &Object + ) -> Option>> { + obj.lock_gpuva(); + // SAFETY: drm_gem_object.gpuva.lock was just locked. + let p = unsafe { + bindings::drm_gpuvm_bo_find( + self.gpuvm() as *mut _, + obj.as_raw() as *const _ as *mut _, + ) + }; + obj.unlock_gpuva(); + if p.is_null() { + None + } else { + // SAFETY: All the drm_gpuvm_bo objects in this GpuVm are always allocated by us as GpuVmBo. + let p = unsafe { crate::container_of!(p, GpuVmBo, bo) as *mut GpuVmBo }; + // SAFETY: We checked for NULL above, and the types ensure that + // this object was created by vm_bo_alloc_callback. + Some(unsafe { ARef::from_raw(NonNull::new_unchecked(p)) }) + } + } + + pub fn obtain_bo(& self, + obj: &Object) -> Result>> { + obj.lock_gpuva(); + // SAFETY: drm_gem_object.gpuva.lock was just locked. + let p = unsafe { + bindings::drm_gpuvm_bo_obtain( + self.gpuvm() as *mut _, + obj.as_raw() as *const _ as *mut _, + ) + }; + obj.unlock_gpuva(); + if p.is_null() { + Err(ENOMEM) + } else { + // SAFETY: Container invariant is guaranteed for GpuVmBo objects for this GpuVm. + let p = unsafe { crate::container_of!(p, GpuVmBo, bo) as *mut GpuVmBo }; + // SAFETY: We checked for NULL above, and the types ensure that + // this object was created by vm_bo_alloc_callback. + Ok(unsafe { ARef::from_raw(NonNull::new_unchecked(p)) }) + } + } + + pub fn bo_unmap(& self, ctx: &mut T::StepContext, bo: &GpuVmBo) -> Result { + let mut ctx = StepContext { + ctx, + gpuvm: self, + }; + // SAFETY: LockedGpuVm implies the right locks are held. + to_result(unsafe { + bindings::drm_gpuvm_bo_unmap(&bo.bo as *const _ as *mut _, &mut ctx as *mut _ as *mut _) + }) + } } // SAFETY: DRM GpuVm objects are always reference counted and the get/put functions From b14caffc32861aa3960de08577ee303260d68378 Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Sat, 20 Dec 2025 22:41:26 +0100 Subject: [PATCH 1272/3902] fixup! rust: drm: Add GPUVM Manager abstraction --- rust/kernel/drm/gpuvm.rs | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/rust/kernel/drm/gpuvm.rs b/rust/kernel/drm/gpuvm.rs index 71e9b4a5494125..04ab41bb27dd71 100644 --- a/rust/kernel/drm/gpuvm.rs +++ b/rust/kernel/drm/gpuvm.rs @@ -23,8 +23,6 @@ use crate::{ types::{ARef, AlwaysRefCounted, Opaque}, }; -use crate::drm::gem::BaseDriverObject; -use crate::drm::gem::IntoGEMObject; use core::cell::UnsafeCell; use core::marker::{PhantomData, PhantomPinned}; use core::mem::ManuallyDrop; @@ -140,6 +138,9 @@ impl DriverGpuVmBo for T { } } +/// A convenience type for the driver's GEM object. +type Object = <::Driver as drm::driver::Driver>::Object; + #[repr(transparent)] pub struct OpMap(bindings::drm_gpuva_op_map, PhantomData); #[repr(transparent)] @@ -160,9 +161,9 @@ impl OpMap { pub fn flags(&self) -> GpuVaFlags { GpuVaFlags(self.0.flags) } - pub fn object(&self) -> &<::Object as BaseDriverObject>::Object { + pub fn object(&self) -> &Object { let p = unsafe { - <<::Object as BaseDriverObject>::Object as IntoGEMObject>::from_raw(self.0.gem.obj) + as IntoGEMObject>::from_raw(self.0.gem.obj) }; // SAFETY: The GEM object has an active reference for the lifetime of this op &*p @@ -495,7 +496,7 @@ impl GpuVm { name: &'static CStr, flags: bindings::drm_gpuvm_flags, dev: &device::Device, - r_obj: ARef<<::Object as BaseDriverObject>::Object>, + r_obj: ARef>, range: Range, reserve_range: Range, inner: impl PinInit, @@ -548,7 +549,7 @@ impl GpuVm { pub fn exec_lock<'a, 'b>( &'a self, - obj: Option<&'b <::Object as BaseDriverObject>::Object>, + obj: Option<&'b Object>, interruptible: bool, ) -> Result> { // Do not try to lock the object if it is internal (since it is already locked). @@ -672,7 +673,7 @@ unsafe impl AlwaysRefCounted for GpuVm { pub struct LockedGpuVm<'a, 'b, T: DriverGpuVm> { gpuvm: &'a GpuVm, vm_exec: KBox, - obj: Option<&'b <::Object as BaseDriverObject>::Object>, + obj: Option<&'b Object>, } impl LockedGpuVm<'_, '_, T> { From 6024c7bb7476d15528a0a90d5645398e626c92fc Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Sat, 20 Dec 2025 22:42:41 +0100 Subject: [PATCH 1273/3902] fixup! rust: drm: Add GPUVM Manager abstraction --- rust/kernel/drm/gpuvm.rs | 23 ++++++++++++++++++----- 1 file changed, 18 insertions(+), 5 deletions(-) diff --git a/rust/kernel/drm/gpuvm.rs b/rust/kernel/drm/gpuvm.rs index 04ab41bb27dd71..7164c798ff90fc 100644 --- a/rust/kernel/drm/gpuvm.rs +++ b/rust/kernel/drm/gpuvm.rs @@ -723,6 +723,7 @@ impl LockedGpuVm<'_, '_, T> { req_addr: u64, req_range: u64, req_offset: u64, + req_gem_range: u32, flags: GpuVaFlags, ) -> Result { let obj = self.obj.ok_or(EINVAL)?; @@ -730,16 +731,28 @@ impl LockedGpuVm<'_, '_, T> { ctx, gpuvm: self.gpuvm, }; + + let req = bindings::drm_gpuvm_map_req { + map: bindings::drm_gpuva_op_map { + va: bindings::drm_gpuva_op_map__bindgen_ty_1 { + addr: req_addr, + range: req_range, + }, + gem: bindings::drm_gpuva_op_map__bindgen_ty_2 { + offset: req_offset, + range: req_gem_range, + obj: obj.as_raw(), + }, + flags: flags.as_raw(), + } + }; + // SAFETY: LockedGpuVm implies the right locks are held. to_result(unsafe { bindings::drm_gpuvm_sm_map( self.gpuvm.gpuvm() as *mut _, &mut ctx as *mut _ as *mut _, - req_addr, - req_range, - obj.as_raw() as *const _ as *mut _, - req_offset, - flags.as_raw(), + &raw const req, ) }) } From 836affa98b52db1619412ca5f4d112d310ef4797 Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Sat, 20 Dec 2025 23:44:23 +0100 Subject: [PATCH 1274/3902] rust: drm: gpuvm: Add sm_can_merge_flags Signed-off-by: Janne Grunau --- rust/kernel/drm/gpuvm.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/rust/kernel/drm/gpuvm.rs b/rust/kernel/drm/gpuvm.rs index 7164c798ff90fc..e7b073cffe0a58 100644 --- a/rust/kernel/drm/gpuvm.rs +++ b/rust/kernel/drm/gpuvm.rs @@ -486,6 +486,7 @@ impl GpuVm { sm_step_map: Some(step_map_callback::), sm_step_remap: Some(step_remap_callback::), sm_step_unmap: Some(step_unmap_callback::), + sm_can_merge_flags: None, }; fn gpuvm(&self) -> *const bindings::drm_gpuvm { From 22947f1f960f6b5323554d5774c74e39504be49f Mon Sep 17 00:00:00 2001 From: Asahi Lina Date: Fri, 17 Feb 2023 00:28:27 +0900 Subject: [PATCH 1275/3902] rust: macros: Add versions macro Signed-off-by: Asahi Lina --- rust/macros/lib.rs | 7 + rust/macros/versions.rs | 341 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 348 insertions(+) create mode 100644 rust/macros/versions.rs diff --git a/rust/macros/lib.rs b/rust/macros/lib.rs index 2fb520dc930af4..65fc6691a0c94a 100644 --- a/rust/macros/lib.rs +++ b/rust/macros/lib.rs @@ -19,6 +19,7 @@ mod helpers; mod kunit; mod module; mod paste; +mod versions; mod vtable; use proc_macro::TokenStream; @@ -134,6 +135,12 @@ pub fn module(ts: TokenStream) -> TokenStream { module::module(ts) } +/// Declares multiple variants of a structure or impl code +#[proc_macro_attribute] +pub fn versions(attr: TokenStream, item: TokenStream) -> TokenStream { + versions::versions(attr, item) +} + /// Declares or implements a vtable trait. /// /// Linux's use of pure vtables is very close to Rust traits, but they differ diff --git a/rust/macros/versions.rs b/rust/macros/versions.rs new file mode 100644 index 00000000000000..b13a5d55c0e17b --- /dev/null +++ b/rust/macros/versions.rs @@ -0,0 +1,341 @@ +use proc_macro::{Delimiter, Group, Ident, Punct, Spacing, Span, TokenStream, TokenTree}; + +//use crate::helpers::expect_punct; + +fn expect_group(it: &mut impl Iterator) -> Group { + if let Some(TokenTree::Group(group)) = it.next() { + group + } else { + panic!("Expected Group") + } +} + +fn expect_punct(it: &mut impl Iterator) -> String { + if let Some(TokenTree::Punct(punct)) = it.next() { + punct.to_string() + } else { + panic!("Expected Group") + } +} + +fn drop_until_punct(it: &mut impl Iterator, delimiter: &str, is_struct: bool) { + let mut depth: isize = 0; + let mut colons: isize = 0; + for token in it.by_ref() { + if let TokenTree::Punct(punct) = token { + match punct.as_char() { + ':' => { + colons += 1; + } + '<' => { + if depth > 0 || colons == 2 || is_struct { + depth += 1; + } + colons = 0; + } + '>' => { + if depth > 0 { + depth -= 1; + } + colons = 0; + } + _ => { + colons = 0; + if depth == 0 && delimiter.contains(&punct.to_string()) { + break; + } + } + } + } + } +} + +fn drop_until_braces(it: &mut impl Iterator) { + let mut depth: isize = 0; + let mut colons: isize = 0; + for token in it.by_ref() { + match token { + TokenTree::Punct(punct) => match punct.as_char() { + ':' => { + colons += 1; + } + '<' => { + if depth > 0 || colons == 2 { + depth += 1; + } + colons = 0; + } + '>' => { + if depth > 0 { + depth -= 1; + } + colons = 0; + } + _ => colons = 0, + }, + TokenTree::Group(group) if group.delimiter() == Delimiter::Brace => { + if depth == 0 { + break; + } + } + _ => (), + } + } +} + +struct VersionConfig { + fields: &'static [&'static str], + enums: &'static [&'static [&'static str]], + versions: &'static [&'static [&'static str]], +} + +static AGX_VERSIONS: VersionConfig = VersionConfig { + fields: &["G", "V"], + enums: &[ + &["G13", "G14", "G14X"], + &["V12_3", "V12_4", "V13_0B4", "V13_2", "V13_3", "V13_5"], + ], + versions: &[ + &["G13", "V12_3"], + &["G14", "V12_4"], + &["G13", "V13_5"], + &["G14", "V13_5"], + &["G14X", "V13_5"], + ], +}; + +fn check_version( + config: &VersionConfig, + ver: &[usize], + it: &mut impl Iterator, +) -> bool { + let first = it.next().unwrap(); + let val: bool = match &first { + TokenTree::Group(group) => check_version(config, ver, &mut group.stream().into_iter()), + TokenTree::Ident(ident) => { + let key = config + .fields + .iter() + .position(|&r| r == ident.to_string()) + .unwrap_or_else(|| panic!("Unknown field {}", ident)); + let mut operator = expect_punct(it); + let mut rhs_token = it.next().unwrap(); + if let TokenTree::Punct(punct) = &rhs_token { + operator.extend(std::iter::once(punct.as_char())); + rhs_token = it.next().unwrap(); + } + let rhs_name = if let TokenTree::Ident(ident) = &rhs_token { + ident.to_string() + } else { + panic!("Unexpected token {}", ident) + }; + + let rhs = config.enums[key] + .iter() + .position(|&r| r == rhs_name) + .unwrap_or_else(|| panic!("Unknown value for {}:{}", ident, rhs_name)); + let lhs = ver[key]; + + match operator.as_str() { + "==" => lhs == rhs, + "!=" => lhs != rhs, + ">" => lhs > rhs, + ">=" => lhs >= rhs, + "<" => lhs < rhs, + "<=" => lhs <= rhs, + _ => panic!("Unknown operator {}", operator), + } + } + _ => { + panic!("Unknown token {}", first) + } + }; + + let boolop = it.next(); + match boolop { + Some(TokenTree::Punct(punct)) => { + let right = expect_punct(it); + if right != punct.to_string() { + panic!("Unexpected op {}{}", punct, right); + } + match punct.as_char() { + '&' => val && check_version(config, ver, it), + '|' => val || check_version(config, ver, it), + _ => panic!("Unexpected op {}{}", right, right), + } + } + Some(a) => panic!("Unexpected op {}", a), + None => val, + } +} + +fn filter_versions( + config: &VersionConfig, + tag: &str, + ver: &[usize], + tree: impl IntoIterator, + is_struct: bool, +) -> Vec { + let mut out = Vec::::new(); + let mut it = tree.into_iter(); + + while let Some(token) = it.next() { + let mut tail: Option = None; + match &token { + TokenTree::Punct(punct) if punct.to_string() == "#" => { + let group = expect_group(&mut it); + let mut grp_it = group.stream().into_iter(); + let attr = grp_it.next().unwrap(); + match attr { + TokenTree::Ident(ident) if ident.to_string() == "ver" => { + if check_version(config, ver, &mut grp_it) { + } else if is_struct { + drop_until_punct(&mut it, ",", true); + } else { + let first = it.next().unwrap(); + match &first { + TokenTree::Ident(ident) + if ["while", "for", "loop", "if", "match", "unsafe", "fn"] + .contains(&ident.to_string().as_str()) => + { + drop_until_braces(&mut it); + } + TokenTree::Group(_) => (), + _ => { + drop_until_punct(&mut it, ",;", false); + } + } + } + } + _ => { + out.push(token.clone()); + out.push(TokenTree::Group(group.clone())); + } + } + continue; + } + TokenTree::Punct(punct) if punct.to_string() == ":" => { + let next = it.next(); + match next { + Some(TokenTree::Punct(punct)) if punct.to_string() == ":" => { + let next = it.next(); + match next { + Some(TokenTree::Ident(idtag)) if idtag.to_string() == "ver" => { + let ident = match out.pop() { + Some(TokenTree::Ident(ident)) => ident, + a => panic!("$ver not following ident: {:?}", a), + }; + let name = ident.to_string() + tag; + let new_ident = Ident::new(name.as_str(), ident.span()); + out.push(TokenTree::Ident(new_ident)); + continue; + } + Some(a) => { + out.push(token.clone()); + out.push(token.clone()); + tail = Some(a); + } + None => { + out.push(token.clone()); + out.push(token.clone()); + } + } + } + Some(a) => { + out.push(token.clone()); + tail = Some(a); + } + None => { + out.push(token.clone()); + continue; + } + } + } + _ => { + tail = Some(token); + } + } + match &tail { + Some(TokenTree::Group(group)) => { + let new_body = + filter_versions(config, tag, ver, group.stream().into_iter(), is_struct); + let mut stream = TokenStream::new(); + stream.extend(new_body); + let mut filtered_group = Group::new(group.delimiter(), stream); + filtered_group.set_span(group.span()); + out.push(TokenTree::Group(filtered_group)); + } + Some(token) => { + out.push(token.clone()); + } + None => {} + } + } + + out +} + +pub(crate) fn versions(attr: TokenStream, item: TokenStream) -> TokenStream { + let config = match attr.to_string().as_str() { + "AGX" => &AGX_VERSIONS, + _ => panic!("Unknown version group {}", attr), + }; + + let mut it = item.into_iter(); + let mut out = TokenStream::new(); + let mut body: Vec = Vec::new(); + let mut is_struct = false; + + while let Some(token) = it.next() { + match token { + TokenTree::Punct(punct) if punct.to_string() == "#" => { + body.push(TokenTree::Punct(punct)); + body.push(it.next().unwrap()); + } + TokenTree::Ident(ident) + if ["struct", "enum", "union", "const", "type"] + .contains(&ident.to_string().as_str()) => + { + is_struct = ident.to_string() != "const"; + body.push(TokenTree::Ident(ident)); + body.push(it.next().unwrap()); + // This isn't valid syntax in a struct definition, so add it for the user + body.push(TokenTree::Punct(Punct::new(':', Spacing::Joint))); + body.push(TokenTree::Punct(Punct::new(':', Spacing::Alone))); + body.push(TokenTree::Ident(Ident::new("ver", Span::call_site()))); + break; + } + TokenTree::Ident(ident) if ident.to_string() == "impl" => { + body.push(TokenTree::Ident(ident)); + break; + } + TokenTree::Ident(ident) if ident.to_string() == "fn" => { + body.push(TokenTree::Ident(ident)); + break; + } + _ => { + body.push(token); + } + } + } + + body.extend(it); + + for ver in config.versions { + let tag = ver.join(""); + let mut ver_num = Vec::::new(); + for (i, comp) in ver.iter().enumerate() { + let idx = config.enums[i].iter().position(|&r| r == *comp).unwrap(); + ver_num.push(idx); + } + out.extend(filter_versions( + config, + &tag, + &ver_num, + body.clone(), + is_struct, + )); + } + + out +} From af0d4e13d2da7336ec4655406a39825a13c95a62 Mon Sep 17 00:00:00 2001 From: Asahi Lina Date: Fri, 17 Feb 2023 00:20:55 +0900 Subject: [PATCH 1276/3902] rust: bindings: Bind the Asahi DRM UAPI Signed-off-by: Asahi Lina --- rust/uapi/uapi_helper.h | 1 + 1 file changed, 1 insertion(+) diff --git a/rust/uapi/uapi_helper.h b/rust/uapi/uapi_helper.h index 06d7d1a2e8daba..8d0b4293cd2f19 100644 --- a/rust/uapi/uapi_helper.h +++ b/rust/uapi/uapi_helper.h @@ -7,6 +7,7 @@ */ #include +#include #include #include #include From 9a9b86256b065c8de78728a17d894325a27c8c72 Mon Sep 17 00:00:00 2001 From: Asahi Lina Date: Fri, 17 Feb 2023 00:31:51 +0900 Subject: [PATCH 1277/3902] drm/asahi: Add the Asahi driver for Apple AGX GPUs drm/asahi: alloc: Support tagging array allocs It's hard to tell what a given array buffer is just from the type, so add support for explicitly adding a u32 tag. This can help us differentiate between allocs in the debug codepaths or when dumping memory. To more easily debug GPU/FW-side overreads, use the alloc tag to fill the padding instead of using a constant. drm/asahi: buffer,render: Identify and provide layer meta buf It looks like one of the "heapmeta" pointers is actually a layer metadata pointer, that macOS just allocates contiguously with the tilemap headers and heap meta buffers. Size seems to always be 0x100. Let's allocate it after the heapmeta, which will make debugging easier. drm/asahi: compute/render: Implement bindless samplers drm/asahi: fw,queue: Implement helper programs Also expose no preemption flag (?) separately. drm/asahi: render: Identify and set Z/S strides for layered rendering drm/asahi: Add verbose UAPI error reporting drm/asahi: Identify and allocate clustered layering metadata buf Turns out multi-cluster machines also need a clustered buffer for layered rendering. Fixes layered rendering on G13X with barriers (I guess if you don't flush memory this stays in some kind of cache and somehow doesn't matter?). drm/asahi: Identify and implement helper config register drm/asahi: alloc: Do not allocate memory to free memory The existing garbage mechanism could allocate a relatively unbounded vec when freeing garbage, which was hurting memory exhaustion scenarios. The only reason we need that buffer is to move garbage out of the lock so we can drop it without deadlocks. Replace it with a 128-size pre-allocated garbage buffer, and loop around reusing it. drm/asahi: Don't lock up when unmapping PTEs fails If a bug causes PTEs to be unmapped twice, the unmap loop gets stuck spamming WARNs forever. Just skip a page and try again so we can make forward progress. drm/asahi: Convert to GPUVM and implement more VM_BIND ops drm/asahi: Refactor address types VAs are u64, PAs and sizes are usize. drm/asahi: util: Add RangeExt helpers for Range drm/asahi: mmu: Convert to using Range drm/asahi: Move the unknown dummy page to the top of the address space drm/asahi: Convert more ranges to Range<> drm/asahi: mmu: Fix lockdep issues with GpuVm drm/asahi: Implement GEM objects sharing a single DMA resv drm/asahi: queue: Split into Queue and QueueInner Work around mutability issues when entity.new_job() takes a mutable reference to the entity by moving all the fields used by the submit_render() and submit_compute() functions to an inner struct, eliminating the double-mutable-borrow. drm/asahi: file: Update to newer VM_BIND API drm/asahi: Signal soft fault support to userspace drm/asahi: Fix u32 mult overflow on large tilebufs/TPCs drm/asahi: Fix event tracking when JobSubmission is dropped drm/asahi: gpu: Show unknown field in timeouts drm/asahi: Handle channel errors drm/asahi: event: Initialize stamps to different values Makes debugging a bit easier. drm/asahi: workqueue: Fix "Cannot submit, but queue is empty?" bug drm/asahi: Clean up jobs in a workqueue This eliminates a potential deadlock under load and improves the fence signaling situation (for when we have a shrinker). drm/asahi: Add robust_isolation kernel parameter This only allows binding one VM context at once, which serializes GPU usage between VMs and therefore prevents one faulting VM from affecting others. drm/asahi: HACK: Disable compute preemption for now Possibly because we don't have support in the helper program, this is broken and causes channel errors. Hack in high priority for now, which works around it. Use debug_flags 0x1000000000000 to re-enable for testing. drm/asahi: Align kernel range to buffer::PAGE_SIZE We only require alignment to the UAT page size from userspace, but internally we need more, so just align it if userspace gives us lower alignment. drm/asahi: Implement missing ASAHI_BIND_OP_UNBIND Trivial now that we have GPUVM. drm/asahi: Implement ASAHI_GET_TIME drm/asahi: gpu: Force Box move with manual Box::into_inner() TODO: Investigate why this doesn't work automatically. drm/asahi: gpu: Collect garbage for private/gpuro together Avoids double firmware flushes drm/asahi: alloc: Be more verbose about failures drm/asahi: gpu: Add a max object count garbage limit This ensures the garbage Vec does not grow beyond what is reasonable, and probably reduces jank by doing more smaller GCs instead of big ones. drm/asahi: Document timestamp ops better, refactor fields drm/asahi: workqueue: Restrict command objects to only job commands drm/asahi: gpu: Implement mapping timestamp buffers drm/asahi: file: Implement ASAHI_GEM_BIND_OBJECT drm/asahi: fw, queue: Add UserTimestamp object to job structs drm/asahi: queue: Plumb through objects XArray and add timestamp getter drm/asahi: fw, queue: Plumb through UserTimestamps -> TimestampPointers drm/asahi: queue/render,compute: Plumb through timestamps extension drm/asahi: file: Add user_timestamp_frequency_hz to params drm/asahi: Set a bit for internal non-render barriers on G14X drm/asahi: Add the USER_TIMESTAMPS feature drm/asahi: mmu: Change step_remap() to new api Fixes deadlock. Also fix missing TLB inval drm/asahi: file: Reject gem_bind past the end of the object drm/asahi: mmu: Fix 2x step_remap case drm/asahi: workqueue: Defer freeing the last completed work item Maybe helps with firmware crashes? drm/asahi: mmu: Fix deadlock on remap ops drm/asahi: mmu: Change step_remap() to new api drm/asahi: mmu: UAT change for rust page table rewrite Originally from: arm64: dts: apple: Remove no-map from pagetables region This should still be compatible with older kernels, since this region is always mapped cached. drm/asahi: debug: Add PgTable debug category drm/asahi: mmu: Add some barriers Just being paranoid. drm/asahi: Implement ASAHI_BIND_SINGLE_PAGE (uapi) drm/asahi: port to new UAPI Signed-off-by: Asahi Lina Co-developed-by: Alyssa Rosenzweig Signed-off-by: Alyssa Rosenzweig Signed-off-by: Janne Grunau --- drivers/gpu/drm/Kconfig | 2 + drivers/gpu/drm/Makefile | 1 + drivers/gpu/drm/asahi/Kconfig | 40 + drivers/gpu/drm/asahi/Makefile | 3 + drivers/gpu/drm/asahi/alloc.rs | 1087 +++++++++++++++ drivers/gpu/drm/asahi/asahi.rs | 56 + drivers/gpu/drm/asahi/buffer.rs | 809 ++++++++++++ drivers/gpu/drm/asahi/channel.rs | 631 +++++++++ drivers/gpu/drm/asahi/debug.rs | 142 ++ drivers/gpu/drm/asahi/driver.rs | 227 ++++ drivers/gpu/drm/asahi/event.rs | 257 ++++ drivers/gpu/drm/asahi/file.rs | 1088 ++++++++++++++++ drivers/gpu/drm/asahi/float.rs | 392 ++++++ drivers/gpu/drm/asahi/fw/buffer.rs | 184 +++ drivers/gpu/drm/asahi/fw/channels.rs | 443 +++++++ drivers/gpu/drm/asahi/fw/compute.rs | 120 ++ drivers/gpu/drm/asahi/fw/event.rs | 104 ++ drivers/gpu/drm/asahi/fw/fragment.rs | 296 +++++ drivers/gpu/drm/asahi/fw/initdata.rs | 1359 +++++++++++++++++++ drivers/gpu/drm/asahi/fw/job.rs | 160 +++ drivers/gpu/drm/asahi/fw/microseq.rs | 412 ++++++ drivers/gpu/drm/asahi/fw/mod.rs | 15 + drivers/gpu/drm/asahi/fw/types.rs | 224 ++++ drivers/gpu/drm/asahi/fw/vertex.rs | 193 +++ drivers/gpu/drm/asahi/fw/workqueue.rs | 187 +++ drivers/gpu/drm/asahi/gem.rs | 248 ++++ drivers/gpu/drm/asahi/gpu.rs | 1556 ++++++++++++++++++++++ drivers/gpu/drm/asahi/hw/mod.rs | 653 ++++++++++ drivers/gpu/drm/asahi/hw/t600x.rs | 161 +++ drivers/gpu/drm/asahi/hw/t602x.rs | 179 +++ drivers/gpu/drm/asahi/hw/t8103.rs | 92 ++ drivers/gpu/drm/asahi/hw/t8112.rs | 105 ++ drivers/gpu/drm/asahi/initdata.rs | 933 +++++++++++++ drivers/gpu/drm/asahi/mem.rs | 144 ++ drivers/gpu/drm/asahi/microseq.rs | 63 + drivers/gpu/drm/asahi/mmu.rs | 1667 ++++++++++++++++++++++++ drivers/gpu/drm/asahi/object.rs | 733 +++++++++++ drivers/gpu/drm/asahi/queue/common.rs | 42 + drivers/gpu/drm/asahi/queue/compute.rs | 385 ++++++ drivers/gpu/drm/asahi/queue/mod.rs | 937 +++++++++++++ drivers/gpu/drm/asahi/queue/render.rs | 1400 ++++++++++++++++++++ drivers/gpu/drm/asahi/regs.rs | 491 +++++++ drivers/gpu/drm/asahi/slotalloc.rs | 323 +++++ drivers/gpu/drm/asahi/util.rs | 141 ++ drivers/gpu/drm/asahi/workqueue.rs | 1032 +++++++++++++++ 45 files changed, 19717 insertions(+) create mode 100644 drivers/gpu/drm/asahi/Kconfig create mode 100644 drivers/gpu/drm/asahi/Makefile create mode 100644 drivers/gpu/drm/asahi/alloc.rs create mode 100644 drivers/gpu/drm/asahi/asahi.rs create mode 100644 drivers/gpu/drm/asahi/buffer.rs create mode 100644 drivers/gpu/drm/asahi/channel.rs create mode 100644 drivers/gpu/drm/asahi/debug.rs create mode 100644 drivers/gpu/drm/asahi/driver.rs create mode 100644 drivers/gpu/drm/asahi/event.rs create mode 100644 drivers/gpu/drm/asahi/file.rs create mode 100644 drivers/gpu/drm/asahi/float.rs create mode 100644 drivers/gpu/drm/asahi/fw/buffer.rs create mode 100644 drivers/gpu/drm/asahi/fw/channels.rs create mode 100644 drivers/gpu/drm/asahi/fw/compute.rs create mode 100644 drivers/gpu/drm/asahi/fw/event.rs create mode 100644 drivers/gpu/drm/asahi/fw/fragment.rs create mode 100644 drivers/gpu/drm/asahi/fw/initdata.rs create mode 100644 drivers/gpu/drm/asahi/fw/job.rs create mode 100644 drivers/gpu/drm/asahi/fw/microseq.rs create mode 100644 drivers/gpu/drm/asahi/fw/mod.rs create mode 100644 drivers/gpu/drm/asahi/fw/types.rs create mode 100644 drivers/gpu/drm/asahi/fw/vertex.rs create mode 100644 drivers/gpu/drm/asahi/fw/workqueue.rs create mode 100644 drivers/gpu/drm/asahi/gem.rs create mode 100644 drivers/gpu/drm/asahi/gpu.rs create mode 100644 drivers/gpu/drm/asahi/hw/mod.rs create mode 100644 drivers/gpu/drm/asahi/hw/t600x.rs create mode 100644 drivers/gpu/drm/asahi/hw/t602x.rs create mode 100644 drivers/gpu/drm/asahi/hw/t8103.rs create mode 100644 drivers/gpu/drm/asahi/hw/t8112.rs create mode 100644 drivers/gpu/drm/asahi/initdata.rs create mode 100644 drivers/gpu/drm/asahi/mem.rs create mode 100644 drivers/gpu/drm/asahi/microseq.rs create mode 100644 drivers/gpu/drm/asahi/mmu.rs create mode 100644 drivers/gpu/drm/asahi/object.rs create mode 100644 drivers/gpu/drm/asahi/queue/common.rs create mode 100644 drivers/gpu/drm/asahi/queue/compute.rs create mode 100644 drivers/gpu/drm/asahi/queue/mod.rs create mode 100644 drivers/gpu/drm/asahi/queue/render.rs create mode 100644 drivers/gpu/drm/asahi/regs.rs create mode 100644 drivers/gpu/drm/asahi/slotalloc.rs create mode 100644 drivers/gpu/drm/asahi/util.rs create mode 100644 drivers/gpu/drm/asahi/workqueue.rs diff --git a/drivers/gpu/drm/Kconfig b/drivers/gpu/drm/Kconfig index 7e6bc0b3a589ce..43101676ba1799 100644 --- a/drivers/gpu/drm/Kconfig +++ b/drivers/gpu/drm/Kconfig @@ -294,6 +294,8 @@ config DRM_VGEM source "drivers/gpu/drm/vkms/Kconfig" +source "drivers/gpu/drm/asahi/Kconfig" + source "drivers/gpu/drm/exynos/Kconfig" source "drivers/gpu/drm/rockchip/Kconfig" diff --git a/drivers/gpu/drm/Makefile b/drivers/gpu/drm/Makefile index da2565e6de71d8..d582d6c451a972 100644 --- a/drivers/gpu/drm/Makefile +++ b/drivers/gpu/drm/Makefile @@ -215,6 +215,7 @@ obj-y += tiny/ obj-$(CONFIG_DRM_PL111) += pl111/ obj-$(CONFIG_DRM_TVE200) += tve200/ obj-$(CONFIG_DRM_ADP) += adp/ +obj-$(CONFIG_DRM_ASAHI) += asahi/ obj-$(CONFIG_DRM_XEN) += xen/ obj-$(CONFIG_DRM_VBOXVIDEO) += vboxvideo/ obj-$(CONFIG_DRM_LIMA) += lima/ diff --git a/drivers/gpu/drm/asahi/Kconfig b/drivers/gpu/drm/asahi/Kconfig new file mode 100644 index 00000000000000..254f214a67c3e8 --- /dev/null +++ b/drivers/gpu/drm/asahi/Kconfig @@ -0,0 +1,40 @@ +# SPDX-License-Identifier: GPL-2.0 + +config RUST_DRM_SCHED + bool + select DRM_SCHED + +config RUST_DRM_GEM_SHMEM_HELPER + bool + select DRM_GEM_SHMEM_HELPER + +config RUST_DRM_GPUVM + bool + select DRM_GPUVM + +config DRM_ASAHI + tristate "Asahi (DRM support for Apple AGX GPUs)" + depends on RUST + depends on DRM=y + depends on (ARM64 && ARCH_APPLE) || (COMPILE_TEST && !GENERIC_ATOMIC64) + depends on MMU + depends on IOMMU_SUPPORT + depends on PAGE_SIZE_16KB + select RUST_DRM_SCHED + select RUST_DRM_GEM_SHMEM_HELPER + select RUST_DRM_GPUVM + select RUST_APPLE_RTKIT + help + DRM driver for Apple AGX GPUs (G13x, found in the M1 SoC family) + +config DRM_ASAHI_DEBUG_ALLOCATOR + bool "Use debug allocator" + depends on DRM_ASAHI + help + Use an alternate, simpler allocator which significantly reduces + performance, but can help find firmware- or GPU-side memory safety + issues. However, it can also trigger firmware bugs more easily, + so expect GPU crashes. + + Say N unless you are debugging firmware structures or porting to a + new firmware version. diff --git a/drivers/gpu/drm/asahi/Makefile b/drivers/gpu/drm/asahi/Makefile new file mode 100644 index 00000000000000..e6724866798760 --- /dev/null +++ b/drivers/gpu/drm/asahi/Makefile @@ -0,0 +1,3 @@ +# SPDX-License-Identifier: GPL-2.0 + +obj-$(CONFIG_DRM_ASAHI) += asahi.o diff --git a/drivers/gpu/drm/asahi/alloc.rs b/drivers/gpu/drm/asahi/alloc.rs new file mode 100644 index 00000000000000..cf3908960e5f74 --- /dev/null +++ b/drivers/gpu/drm/asahi/alloc.rs @@ -0,0 +1,1087 @@ +// SPDX-License-Identifier: GPL-2.0-only OR MIT + +//! GPU kernel object allocator. +//! +//! This kernel driver needs to manage a large number of GPU objects, in both firmware/kernel +//! address space and user address space. This module implements a simple grow-only heap allocator +//! based on the DRM MM range allocator, and a debug allocator that allocates each object as a +//! separate GEM object. +//! +//! Allocations may optionally have debugging enabled, which adds preambles that store metadata +//! about the allocation. This is useful for live debugging using the hypervisor or postmortem +//! debugging with a GPU memory snapshot, since it makes it easier to identify use-after-free and +//! caching issues. + +use kernel::{ + drm::mm, + error::Result, + prelude::*, + str::CString, // +}; + +use crate::debug::*; +use crate::driver::{ + AsahiDevRef, + AsahiDevice, // +}; +use crate::fw::types::Zeroable; +use crate::mmu; +use crate::object::{ + GpuArray, + GpuObject, + GpuOnlyArray, + GpuStruct, + GpuWeakPointer, // +}; +use crate::util::RangeExt; + +use core::cmp::Ordering; +use core::fmt::{ + self, + Debug, + Formatter, // +}; +use core::marker::PhantomData; +use core::mem; +use core::ops::Range; +use core::ptr::NonNull; + +const DEBUG_CLASS: DebugFlags = DebugFlags::Alloc; + +#[cfg(not(CONFIG_DRM_ASAHI_DEBUG_ALLOCATOR))] +/// The driver-global allocator type +pub(crate) type DefaultAllocator = HeapAllocator; + +#[cfg(not(CONFIG_DRM_ASAHI_DEBUG_ALLOCATOR))] +/// The driver-global allocation type +pub(crate) type DefaultAllocation = HeapAllocation; + +#[cfg(CONFIG_DRM_ASAHI_DEBUG_ALLOCATOR)] +/// The driver-global allocator type +pub(crate) type DefaultAllocator = SimpleAllocator; + +#[cfg(CONFIG_DRM_ASAHI_DEBUG_ALLOCATOR)] +/// The driver-global allocation type +pub(crate) type DefaultAllocation = SimpleAllocation; + +/// Represents a raw allocation (without any type information). +pub(crate) trait RawAllocation { + /// Returns the CPU-side pointer (if CPU mapping is enabled) as a byte non-null pointer. + fn ptr(&self) -> Option>; + /// Returns the GPU VA pointer as a u64. + fn gpu_ptr(&self) -> u64; + /// Returns the AsahiDevice that owns this allocation. + fn device(&self) -> &AsahiDevice; +} + +/// Represents a typed allocation. +pub(crate) trait Allocation: Debug { + /// Returns the typed CPU-side pointer (if CPU mapping is enabled). + fn ptr(&self) -> Option>; + /// Returns the GPU VA pointer as a u64. + fn gpu_ptr(&self) -> u64; + /// Returns the size of the allocation in bytes. + fn size(&self) -> usize; + /// Returns the AsahiDevice that owns this allocation. + fn device(&self) -> &AsahiDevice; +} + +/// A generic typed allocation wrapping a RawAllocation. +/// +/// This is currently the only Allocation implementation, since it is shared by all allocators. +/// +/// # Invariants +/// The alloaction at `alloc` must have a size equal or greater than `alloc_size` plus `debug_offset` plus `padding`. +pub(crate) struct GenericAlloc { + alloc: U, + alloc_size: usize, + debug_offset: usize, + padding: usize, + tag: u32, + pad_word: u32, + _p: PhantomData, +} + +impl Allocation for GenericAlloc { + /// Returns a pointer to the inner (usable) part of the allocation. + fn ptr(&self) -> Option> { + // SAFETY: self.debug_offset is always within the allocation per the invariant, so is safe to add + // to the base pointer. + unsafe { self.alloc.ptr().map(|p| p.add(self.debug_offset).cast()) } + } + /// Returns the GPU pointer to the inner (usable) part of the allocation. + fn gpu_ptr(&self) -> u64 { + self.alloc.gpu_ptr() + self.debug_offset as u64 + } + /// Returns the size of the inner (usable) part of the allocation. + fn size(&self) -> usize { + self.alloc_size + } + fn device(&self) -> &AsahiDevice { + self.alloc.device() + } +} + +impl Debug for GenericAlloc { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + f.debug_struct(core::any::type_name::>()) + .field("ptr", &format_args!("{:?}", self.ptr())) + .field("gpu_ptr", &format_args!("{:#X?}", self.gpu_ptr())) + .field("size", &format_args!("{:#X?}", self.size())) + .finish() + } +} + +/// Debugging data associated with an allocation, when debugging is enabled. +#[repr(C)] +struct AllocDebugData { + state: u32, + tag: u32, + size: u64, + base_gpuva: u64, + obj_gpuva: u64, + name: [u8; 0x20], +} + +/// Magic flag indicating a live allocation. +const STATE_LIVE: u32 = u32::from_le_bytes(*b"LIVE"); +/// Magic flag indicating a freed allocation. +const STATE_DEAD: u32 = u32::from_le_bytes(*b"DEAD"); + +/// Marker byte to identify when firmware/GPU write beyond the end of an allocation. +const GUARD_MARKER: u32 = 0x93939393; + +impl Drop for GenericAlloc { + fn drop(&mut self) { + let debug_len = mem::size_of::(); + if self.debug_offset >= debug_len { + if let Some(p) = self.alloc.ptr() { + // SAFETY: self.debug_offset is always greater than the alloc size per + // the invariant, and greater than debug_len as checked above. + unsafe { + let p = p.as_ptr().add(self.debug_offset - debug_len); + (p as *mut u32).write(STATE_DEAD); + } + } + } + if debug_enabled(DebugFlags::FillAllocations) { + if let Some(p) = self.ptr() { + // SAFETY: Writing to our inner base pointer with our known inner size is safe. + unsafe { (p.as_ptr() as *mut u8).write_bytes(0xde, self.size()) }; + } + } + if self.padding != 0 { + if let Some(p) = self.ptr() { + // SAFETY: Per the invariant, we have at least `self.padding` bytes trailing + // the inner base pointer, after `size()` bytes. + let guard = unsafe { + core::slice::from_raw_parts( + (p.as_ptr() as *mut u8 as *const u8).add(self.size()), + self.padding, + ) + }; + let mut first_err = None; + let mut last_err = 0; + for (i, p) in guard.iter().enumerate() { + if *p != (self.pad_word >> (8 * (i & 3))) as u8 { + if first_err.is_none() { + first_err = Some(i); + } + last_err = i; + } + } + if let Some(start) = first_err { + dev_warn!( + self.device().as_ref(), + "Allocator: Corruption after object of type {}/{:#x} at {:#x}:{:#x} + {:#x}..={:#x}\n", + core::any::type_name::(), + self.tag, + self.gpu_ptr(), + self.size(), + start, + last_err, + ); + } + } + } + } +} + +static_assert!(mem::size_of::() == 0x40); + +/// A trait representing an allocator. +pub(crate) trait Allocator { + /// The raw allocation type used by this allocator. + type Raw: RawAllocation; + // TODO: Needs associated_type_defaults + // type Allocation = GenericAlloc; + + /// Returns whether CPU-side mapping is enabled. + fn cpu_maps(&self) -> bool; + /// Returns the minimum alignment for allocations. + fn min_align(&self) -> usize; + /// Allocate an object of the given size in bytes with the given alignment. + fn alloc(&mut self, size: usize, align: usize) -> Result; + + /// Returns a tuple of (count, size) of how much garbage (freed but not yet reusable objects) + /// exists in this allocator. Optional. + fn garbage(&self) -> (usize, usize) { + (0, 0) + } + /// Collect garbage for this allocator, up to the given object count. Optional. + fn collect_garbage(&mut self, _count: usize) {} + + /// Allocate a new GpuStruct object. See [`GpuObject::new`]. + #[inline(never)] + fn new_object( + &mut self, + inner: T, + callback: impl for<'a> FnOnce(&'a T) -> T::Raw<'a>, + ) -> Result>> { + GpuObject::>::new(self.alloc_object()?, inner, callback) + } + + /// Allocate a new GpuStruct object. See [`GpuObject::new_default`]. + #[inline(never)] + fn new_default( + &mut self, + ) -> Result>> + where + for<'a> ::Raw<'a>: Default + Zeroable, + { + GpuObject::>::new_default(self.alloc_object()?) + } + + /// Allocate a new GpuStruct object. See [`GpuObject::new_init`]. + #[inline(never)] + fn new_init<'a, T: GpuStruct, R: PinInit, F>, E, F>( + &mut self, + inner_init: impl Init, + raw_init: impl FnOnce(&'a T, GpuWeakPointer) -> R, + ) -> Result>> + where + kernel::error::Error: core::convert::From, + kernel::error::Error: core::convert::From, + { + GpuObject::>::new_init_prealloc( + self.alloc_object()?, + |_p| inner_init, + raw_init, + ) + } + + /// Allocate a generic buffer of the given size and alignment, applying the debug features if + /// enabled to tag it and detect overflows. + fn alloc_generic( + &mut self, + size: usize, + align: usize, + tag: Option, + ) -> Result> { + let padding = if debug_enabled(DebugFlags::DetectOverflows) { + size + } else { + 0 + }; + + let ret: GenericAlloc = + if self.cpu_maps() && debug_enabled(debug::DebugFlags::DebugAllocations) { + let debug_align = self.min_align().max(align); + let debug_len = mem::size_of::(); + let debug_offset = (debug_len * 2 + debug_align - 1) & !(debug_align - 1); + + let alloc = self.alloc(size + debug_offset + padding, align)?; + + let mut debug = AllocDebugData { + state: STATE_LIVE, + tag: tag.unwrap_or(0), + size: size as u64, + base_gpuva: alloc.gpu_ptr(), + obj_gpuva: alloc.gpu_ptr() + debug_offset as u64, + name: [0; 0x20], + }; + + let name = core::any::type_name::().as_bytes(); + let len = name.len().min(debug.name.len() - 1); + debug.name[..len].copy_from_slice(&name[..len]); + + if let Some(p) = alloc.ptr() { + // SAFETY: Per the size calculations above, this pointer math and the + // writes never exceed the allocation size. + unsafe { + let p = p.as_ptr(); + p.write_bytes(0x42, debug_offset - 2 * debug_len); + let cur = p.add(debug_offset - debug_len) as *mut AllocDebugData; + let prev = p.add(debug_offset - 2 * debug_len) as *mut AllocDebugData; + prev.copy_from(cur, 1); + cur.copy_from(&debug, 1); + }; + } + + GenericAlloc { + alloc, + alloc_size: size, + debug_offset, + tag: tag.unwrap_or(0), + pad_word: tag.unwrap_or(GUARD_MARKER) | 0x81818181, + padding, + _p: PhantomData, + } + } else { + GenericAlloc { + alloc: self.alloc(size + padding, align)?, + alloc_size: size, + debug_offset: 0, + tag: tag.unwrap_or(0), + pad_word: tag.unwrap_or(GUARD_MARKER) | 0x81818181, + padding, + _p: PhantomData, + } + }; + + if debug_enabled(DebugFlags::FillAllocations) { + if let Some(p) = ret.ptr() { + // SAFETY: Writing to our inner base pointer with our known inner size is safe. + unsafe { (p.as_ptr() as *mut u8).write_bytes(0xaa, ret.size()) }; + } + } + + if padding != 0 { + if let Some(p) = ret.ptr() { + // SAFETY: Per the invariant, we have at least `self.padding` bytes trailing + // the inner base pointer, after `size()` bytes. + let guard = unsafe { + core::slice::from_raw_parts_mut( + (p.as_ptr() as *mut u8).add(ret.size()), + padding, + ) + }; + for (i, p) in guard.iter_mut().enumerate() { + *p = (ret.pad_word >> (8 * (i & 3))) as u8; + } + } + } + + Ok(ret) + } + + /// Allocate an object of a given type, without actually initializing the allocation. + /// + /// This is useful to directly call [`GpuObject::new_*`], without borrowing a reference to the + /// allocator for the entire duration (e.g. if further allocations need to happen inside the + /// callbacks). + fn alloc_object(&mut self) -> Result> { + let size = mem::size_of::>(); + let align = mem::align_of::>(); + + self.alloc_generic(size, align, None) + } + + /// Allocate an empty `GpuArray` of a given type and length. + fn array_empty( + &mut self, + count: usize, + ) -> Result>> { + let size = mem::size_of::() * count; + let align = mem::align_of::(); + + let alloc = self.alloc_generic(size, align, None)?; + GpuArray::>::empty(alloc, count) + } + + /// Allocate an empty `GpuArray` of a given type and length. + fn array_empty_tagged( + &mut self, + count: usize, + tag: &[u8; 4], + ) -> Result>> { + let size = mem::size_of::() * count; + let align = mem::align_of::(); + + let alloc = self.alloc_generic(size, align, Some(u32::from_le_bytes(*tag)))?; + GpuArray::>::empty(alloc, count) + } + + /// Allocate an empty `GpuOnlyArray` of a given type and length. + fn array_gpuonly( + &mut self, + count: usize, + ) -> Result>> { + let size = mem::size_of::() * count; + let align = mem::align_of::(); + + let alloc = self.alloc_generic(size, align, None)?; + GpuOnlyArray::>::new(alloc, count) + } +} + +/// A simple allocation backed by a separate GEM object. +/// +/// # Invariants +/// `ptr` is either None or a valid, non-null pointer to the CPU view of the object. +/// `gpu_ptr` is the GPU-side VA of the object. +pub(crate) struct SimpleAllocation { + dev: AsahiDevRef, + ptr: Option>, + gpu_ptr: u64, + _mapping: mmu::KernelMapping, + obj: crate::gem::ObjectRef, +} + +/// SAFETY: `SimpleAllocation` just points to raw memory and should be safe to send across threads. +unsafe impl Send for SimpleAllocation {} +/// SAFETY: `SimpleAllocation` just points to raw memory and should be safe to share across threads. +unsafe impl Sync for SimpleAllocation {} + +impl Drop for SimpleAllocation { + fn drop(&mut self) { + mod_dev_dbg!( + self.device(), + "SimpleAllocator: drop object @ {:#x}\n", + self.gpu_ptr() + ); + if debug_enabled(DebugFlags::FillAllocations) { + if let Ok(mut vmap) = self.obj.vmap() { + vmap.memset(0x42); + } + } + } +} + +impl RawAllocation for SimpleAllocation { + fn ptr(&self) -> Option> { + self.ptr + } + fn gpu_ptr(&self) -> u64 { + self.gpu_ptr + } + fn device(&self) -> &AsahiDevice { + &self.dev + } +} + +/// A simple allocator that allocates each object as its own GEM object, aligned to the end of a +/// page. +/// +/// This is very slow, but it has the advantage that over-reads by the firmware or GPU will fault on +/// the guard page after the allocation, which can be useful to validate that the firmware's or +/// GPU's idea of object size what we expect. +pub(crate) struct SimpleAllocator { + dev: AsahiDevRef, + range: Range, + prot: u32, + vm: mmu::Vm, + min_align: usize, + cpu_maps: bool, +} + +impl SimpleAllocator { + /// Create a new `SimpleAllocator` for a given address range and `Vm`. + #[allow(dead_code)] + #[allow(clippy::too_many_arguments)] + pub(crate) fn new( + dev: &AsahiDevice, + vm: &mmu::Vm, + range: Range, + min_align: usize, + prot: u32, + _block_size: usize, + mut cpu_maps: bool, + _name: fmt::Arguments<'_>, + _keep_garbage: bool, + ) -> Result { + if debug_enabled(DebugFlags::ForceCPUMaps) { + cpu_maps = true; + } + Ok(SimpleAllocator { + dev: dev.into(), + vm: vm.clone(), + range, + prot, + min_align, + cpu_maps, + }) + } +} + +impl Allocator for SimpleAllocator { + type Raw = SimpleAllocation; + + fn cpu_maps(&self) -> bool { + self.cpu_maps + } + + fn min_align(&self) -> usize { + self.min_align + } + + #[inline(never)] + fn alloc(&mut self, size: usize, align: usize) -> Result { + let size_aligned = (size + mmu::UAT_PGSZ - 1) & !mmu::UAT_PGMSK; + let align = self.min_align.max(align); + let offset = (size_aligned - size) & !(align - 1); + + mod_dev_dbg!( + &self.dev, + "SimpleAllocator::new: size={:#x} size_al={:#x} al={:#x} off={:#x}\n", + size, + size_aligned, + align, + offset + ); + + let mut obj = crate::gem::new_kernel_object(&self.dev, size_aligned)?; + let p = obj.vmap()?.as_mut_ptr() as *mut u8; + if debug_enabled(DebugFlags::FillAllocations) { + obj.vmap()?.memset(0xde); + } + let mapping = obj.map_into_range( + &self.vm, + self.range.clone(), + self.min_align.max(mmu::UAT_PGSZ) as u64, + self.prot, + true, + )?; + + let iova = mapping.iova(); + + // SAFETY: Per the math above to calculate `size_aligned`, this can never overflow. + let ptr = unsafe { p.add(offset) }; + let gpu_ptr = iova + offset as u64; + + mod_dev_dbg!( + &self.dev, + "SimpleAllocator::new -> {:#?} / {:#?} | {:#x} / {:#x}\n", + p, + ptr, + iova, + gpu_ptr + ); + + Ok(SimpleAllocation { + dev: self.dev.clone(), + ptr: NonNull::new(ptr), + gpu_ptr, + _mapping: mapping, + obj, + }) + } +} + +/// Inner data for an allocation from the heap allocator. +/// +/// This is wrapped in an `mm::Node`. +pub(crate) struct HeapAllocationInner { + dev: AsahiDevRef, + ptr: Option>, + real_size: usize, +} + +/// SAFETY: `HeapAllocationInner` just points to raw memory and should be safe to send across threads. +unsafe impl Send for HeapAllocationInner {} +/// SAFETY: `HeapAllocationInner` just points to raw memory and should be safe to share between threads. +unsafe impl Sync for HeapAllocationInner {} + +/// Outer view of a heap allocation. +/// +/// This uses an Option<> so we can move the internal `Node` into the garbage pool when it gets +/// dropped. +/// +/// # Invariants +/// The `Option` must always be `Some(...)` while this object is alive. +pub(crate) struct HeapAllocation(Option>); + +impl Drop for HeapAllocation { + fn drop(&mut self) { + let node = self.0.take().unwrap(); + let size = node.size(); + let alloc = node.alloc_ref(); + + alloc.with(|a| { + if let Some(garbage) = a.garbage.as_mut() { + if garbage.push(node, GFP_KERNEL).is_err() { + dev_err!( + &a.dev.as_ref(), + "HeapAllocation[{}]::drop: Failed to keep garbage\n", + &*a.name, + ); + } + a.total_garbage += size as usize; + None + } else { + // We need to ensure node survives this scope, since dropping it + // will try to take the mm lock and deadlock us + Some(node) + } + }); + } +} + +impl mm::AllocInner for HeapAllocatorInner { + fn drop_object( + &mut self, + start: u64, + _size: u64, + _color: usize, + obj: &mut HeapAllocationInner, + ) { + /* real_size == 0 means it's a guard node */ + if obj.real_size > 0 { + mod_dev_dbg!( + obj.dev, + "HeapAllocator[{}]: drop object @ {:#x} ({} bytes)\n", + &*self.name, + start, + obj.real_size, + ); + self.allocated -= obj.real_size; + } + } +} + +impl RawAllocation for HeapAllocation { + // SAFETY: This function must always return a valid pointer. + // Since the HeapAllocation contains a reference to the + // backing_objects array that contains the object backing this pointer, + // and objects are only ever added to it, this pointer is guaranteed to + // remain valid for the lifetime of the HeapAllocation. + fn ptr(&self) -> Option> { + self.0.as_ref().unwrap().ptr + } + // SAFETY: This function must always return a valid GPU pointer. + // See the explanation in ptr(). + fn gpu_ptr(&self) -> u64 { + self.0.as_ref().unwrap().start() + } + fn device(&self) -> &AsahiDevice { + &self.0.as_ref().unwrap().dev + } +} + +/// Inner data for a heap allocator which uses the DRM MM range allocator to manage the heap. +/// +/// This is wrapped by an `mm::Allocator`. +struct HeapAllocatorInner { + dev: AsahiDevRef, + allocated: usize, + backing_objects: KVec<(crate::gem::ObjectRef, mmu::KernelMapping, u64)>, + garbage: Option>>, + total_garbage: usize, + name: CString, +} + +/// A heap allocator which uses the DRM MM range allocator to manage its objects. +/// +/// The heap is composed of a series of GEM objects. This implementation only ever grows the heap, +/// never shrinks it. +pub(crate) struct HeapAllocator { + dev: AsahiDevRef, + range: Range, + top: u64, + prot: u32, + vm: mmu::Vm, + min_align: usize, + block_size: usize, + cpu_maps: bool, + guard_nodes: KVec>, + mm: mm::Allocator, + name: CString, + garbage: Option>>, +} + +impl HeapAllocator { + /// Create a new HeapAllocator for a given `Vm` and address range. + #[allow(dead_code)] + #[allow(clippy::too_many_arguments)] + pub(crate) fn new( + dev: &AsahiDevice, + vm: &mmu::Vm, + range: Range, + min_align: usize, + prot: u32, + block_size: usize, + mut cpu_maps: bool, + name: fmt::Arguments<'_>, + keep_garbage: bool, + ) -> Result { + if !min_align.is_power_of_two() { + return Err(EINVAL); + } + if debug_enabled(DebugFlags::ForceCPUMaps) { + cpu_maps = true; + } + + let name = CString::try_from_fmt(name)?; + + let inner = HeapAllocatorInner { + dev: dev.into(), + allocated: 0, + backing_objects: KVec::new(), + // TODO: This clearly needs a try_clone() or similar + name: CString::try_from_fmt(fmt!("{}", &*name))?, + garbage: if keep_garbage { + Some(KVec::new()) + } else { + None + }, + total_garbage: 0, + }; + + let mm = mm::Allocator::new(range.start, range.range(), inner)?; + + Ok(HeapAllocator { + dev: dev.into(), + vm: vm.clone(), + top: range.start, + range, + prot, + min_align, + block_size: block_size.max(min_align), + cpu_maps, + guard_nodes: KVec::new(), + mm, + name, + garbage: if keep_garbage { + Some({ + let mut v = KVec::new(); + v.reserve(128, GFP_KERNEL)?; + v + }) + } else { + None + }, + }) + } + + /// Add a new backing block of the given size to this heap. + /// + /// If CPU mapping is enabled, this also adds a guard node to the range allocator to ensure that + /// objects cannot straddle backing block boundaries, since we cannot easily create a contiguous + /// CPU VA mapping for them. This can create some fragmentation. If CPU mapping is disabled, we + /// skip the guard blocks, since the GPU view of the heap is always contiguous. + #[inline(never)] + fn add_block(&mut self, size: usize) -> Result { + let size_aligned = (size + mmu::UAT_PGSZ - 1) & !mmu::UAT_PGMSK; + + mod_dev_dbg!( + &self.dev, + "HeapAllocator[{}]::add_block: size={:#x} size_al={:#x}\n", + &*self.name, + size, + size_aligned, + ); + + if self.top.saturating_add(size_aligned as u64) > self.range.end { + dev_err!( + self.dev.as_ref(), + "HeapAllocator[{}]::add_block: Exhausted VA space\n", + &*self.name, + ); + } + + let mut obj = crate::gem::new_kernel_object(&self.dev, size_aligned)?; + if self.cpu_maps && debug_enabled(DebugFlags::FillAllocations) { + obj.vmap()?.memset(0xde); + } + + let gpu_ptr = self.top; + let mapping = obj + .map_at(&self.vm, gpu_ptr, self.prot, self.cpu_maps) + .inspect_err(|err| { + dev_err!( + self.dev.as_ref(), + "HeapAllocator[{}]::add_block: Failed to map at {:#x} ({:?})\n", + &*self.name, + gpu_ptr, + err + ); + })?; + + if self.cpu_maps { + // Create virtual mapping here ahead of time so that the vmap() in + // alloc_inner() does not take the the object's dma_resv lock while + // the mm lock is locked. mmu::Vm requires the opposite lock order. + obj.vmap()?; + } + + self.mm + .with_inner(|inner| inner.backing_objects.reserve(1, GFP_KERNEL))?; + + let mut new_top = self.top + size_aligned as u64; + if self.cpu_maps { + let guard = self.min_align.max(mmu::UAT_PGSZ); + mod_dev_dbg!( + self.dev, + "HeapAllocator[{}]::add_block: Adding guard node {:#x}:{:#x}\n", + &*self.name, + new_top, + guard + ); + + let inner = HeapAllocationInner { + dev: self.dev.clone(), + ptr: None, + real_size: 0, + }; + + let node = match self.mm.reserve_node(inner, new_top, guard as u64, 0) { + Ok(a) => a, + Err(a) => { + dev_err!( + self.dev.as_ref(), + "HeapAllocator[{}]::add_block: Failed to reserve guard node {:#x}:{:#x}: {:?}\n", + &*self.name, + guard, + new_top, + a + ); + return Err(EIO); + } + }; + + self.guard_nodes.push(node, GFP_KERNEL)?; + + new_top += guard as u64; + } + mod_dev_dbg!( + &self.dev, + "HeapAllocator[{}]::add_block: top={:#x}\n", + &*self.name, + new_top + ); + + self.mm.with_inner(|inner| { + inner + .backing_objects + .push((obj, mapping, gpu_ptr), GFP_KERNEL) + })?; + + self.top = new_top; + + cls_dev_dbg!( + MemStats, + &self.dev, + "{} Heap: grow to {} bytes\n", + &*self.name, + self.top - self.range.start + ); + + Ok(()) + } + + /// Find the backing object index that backs a given GPU address. + fn find_obj(&mut self, addr: u64) -> Result { + self.mm.with_inner(|inner| { + inner + .backing_objects + .binary_search_by(|obj| { + let start = obj.2; + let end = obj.2 + obj.0.size() as u64; + if start > addr { + Ordering::Greater + } else if end <= addr { + Ordering::Less + } else { + Ordering::Equal + } + }) + .or(Err(ENOENT)) + }) + } + + fn alloc_inner(&mut self, size: usize, align: usize) -> Result { + if align != 0 && !align.is_power_of_two() { + return Err(EINVAL); + } + let align = self.min_align.max(align); + let size_aligned = (size + align - 1) & !(align - 1); + + mod_dev_dbg!( + &self.dev, + "HeapAllocator[{}]::new: size={:#x} size_al={:#x}\n", + &*self.name, + size, + size_aligned, + ); + + let inner = HeapAllocationInner { + dev: self.dev.clone(), + ptr: None, + real_size: size, + }; + + let mut node = match self.mm.insert_node_generic( + inner, + size_aligned as u64, + align as u64, + 0, + mm::InsertMode::Best, + ) { + Ok(a) => a, + Err(a) => { + dev_err!( + &self.dev.as_ref(), + "HeapAllocator[{}]::new: Failed to insert node of size {:#x} / align {:#x}: {:?}\n", + &*self.name, size_aligned, align, a + ); + return Err(a); + } + }; + + self.mm.with_inner(|inner| inner.allocated += size); + + let mut new_object = false; + let start = node.start(); + let end = start + node.size(); + if end > self.top { + if start > self.top { + dev_warn!( + self.dev.as_ref(), + "HeapAllocator[{}]::alloc: top={:#x}, start={:#x}\n", + &*self.name, + self.top, + start + ); + } + let block_size = self.block_size.max((end - self.top) as usize); + self.add_block(block_size)?; + new_object = true; + } + assert!(end <= self.top); + + if self.cpu_maps { + mod_dev_dbg!( + self.dev, + "HeapAllocator[{}]::alloc: mapping to CPU\n", + &*self.name + ); + + let idx = if new_object { + None + } else { + Some(match self.find_obj(start) { + Ok(a) => a, + Err(_) => { + dev_warn!( + self.dev.as_ref(), + "HeapAllocator[{}]::alloc: Failed to find object at {:#x}\n", + &*self.name, + start + ); + return Err(EIO); + } + }) + }; + let (obj_start, obj_size, p) = self.mm.with_inner(|inner| -> Result<_> { + let idx = idx.unwrap_or(inner.backing_objects.len() - 1); + let obj = &mut inner.backing_objects[idx]; + let p = obj.0.vmap()?.as_mut_ptr() as *mut u8; + Ok((obj.2, obj.0.size(), p)) + })?; + assert!(obj_start <= start); + assert!(obj_start + obj_size as u64 >= end); + node.as_mut().inner_mut().ptr = + // SAFETY: Per the asserts above, this offset is always within the allocation. + NonNull::new(unsafe { p.add((start - obj_start) as usize) }); + mod_dev_dbg!( + self.dev, + "HeapAllocator[{}]::alloc: CPU pointer = {:?}\n", + &*self.name, + node.ptr + ); + } + + mod_dev_dbg!( + self.dev, + "HeapAllocator[{}]::alloc: Allocated {:#x} bytes @ {:#x}\n", + &*self.name, + end - start, + start + ); + + Ok(HeapAllocation(Some(node))) + } +} + +impl Allocator for HeapAllocator { + type Raw = HeapAllocation; + + fn cpu_maps(&self) -> bool { + self.cpu_maps + } + + fn min_align(&self) -> usize { + self.min_align + } + + fn alloc(&mut self, size: usize, align: usize) -> Result { + let ret = self.alloc_inner(size, align); + + if ret.is_err() { + dev_warn!( + self.dev.as_ref(), + "HeapAllocator[{}]::alloc: Allocation of {:#x}({:#x}) size object failed\n", + &*self.name, + size, + align + ); + } + ret + } + + fn garbage(&self) -> (usize, usize) { + self.mm.with_inner(|inner| { + if let Some(g) = inner.garbage.as_ref() { + (g.len(), inner.total_garbage) + } else { + (0, 0) + } + }) + } + + fn collect_garbage(&mut self, mut count: usize) { + if let Some(garbage) = self.garbage.as_mut() { + garbage.clear(); + + while count > 0 { + let block = count.min(garbage.capacity()); + assert!(block > 0); + + // Take the garbage out of the inner block, so we can safely drop it without deadlocking + self.mm.with_inner(|inner| { + if let Some(g) = inner.garbage.as_mut() { + for node in g.drain(0..block) { + inner.total_garbage -= node.size() as usize; + garbage + .push(node, GFP_KERNEL) + .expect("push() failed after reserve()"); + } + } + }); + + count -= block; + // Now drop it + garbage.clear(); + } + } + } +} + +impl Drop for HeapAllocatorInner { + fn drop(&mut self) { + mod_dev_dbg!( + self.dev, + "HeapAllocator[{}]: dropping allocator\n", + &*self.name + ); + if self.allocated > 0 { + // This should never happen + dev_crit!( + self.dev.as_ref(), + "HeapAllocator[{}]: dropping with {} bytes allocated\n", + &*self.name, + self.allocated + ); + } + } +} diff --git a/drivers/gpu/drm/asahi/asahi.rs b/drivers/gpu/drm/asahi/asahi.rs new file mode 100644 index 00000000000000..85325ccfb6e74b --- /dev/null +++ b/drivers/gpu/drm/asahi/asahi.rs @@ -0,0 +1,56 @@ +// SPDX-License-Identifier: GPL-2.0-only OR MIT +#![recursion_limit = "2048"] + +//! Driver for the Apple AGX GPUs found in Apple Silicon SoCs. + +mod alloc; +mod buffer; +mod channel; +mod debug; +mod driver; +mod event; +mod file; +mod float; +mod fw; +mod gem; +mod gpu; +mod hw; +mod initdata; +mod mem; +mod microseq; +mod mmu; +mod object; +mod queue; +mod regs; +mod slotalloc; +mod util; +mod workqueue; + +kernel::module_platform_driver! { + type: driver::AsahiDriver, + name: "asahi", + description: "AGX GPU driver for Apple silicon SoCs", + license: "Dual MIT/GPL", + params: { + debug_flags: u64 { + default: 0, + // permissions: 0o644, + description: "Debug flags", + }, + fault_control: u32 { + default: 0xb, + // permissions: 0, + description: "Fault control (0x0: hard faults, 0xb: macOS default)", + }, + initial_tvb_size: usize { + default: 0x8, + // permissions: 0o644, + description: "Initial TVB size in blocks", + }, + robust_isolation: u32 { + default: 0, + // permissions: 0o644, + description: "Fully isolate GPU contexts (limits performance)", + }, + }, +} diff --git a/drivers/gpu/drm/asahi/buffer.rs b/drivers/gpu/drm/asahi/buffer.rs new file mode 100644 index 00000000000000..62b12c2f2da167 --- /dev/null +++ b/drivers/gpu/drm/asahi/buffer.rs @@ -0,0 +1,809 @@ +// SPDX-License-Identifier: GPL-2.0-only OR MIT + +//! Tiled Vertex Buffer management +//! +//! This module manages the Tiled Vertex Buffer, also known as the Parameter Buffer (in imgtec +//! parlance) or the tiler heap (on other architectures). This buffer holds transformed primitive +//! data between the vertex/tiling stage and the fragment stage. +//! +//! On AGX, the buffer is a heap of 128K blocks split into 32K pages (which must be aligned to a +//! multiple of 32K in VA space). The buffer can be shared between multiple render jobs, and each +//! will allocate pages from it during vertex processing and return them during fragment processing. +//! +//! If the buffer runs out of free pages, the vertex pass stops and a partial fragment pass occurs, +//! spilling the intermediate render target state to RAM (a partial render). This is all managed +//! transparently by the firmware. Since partial renders are less efficient, the kernel must grow +//! the heap in response to feedback from the firmware to avoid partial renders in the future. +//! Currently, we only ever grow the heap, and never shrink it. +//! +//! AGX also supports memoryless render targets, which can be used for intermediate results within +//! a render pass. To support partial renders, it seems the GPU/firmware has the ability to borrow +//! pages from the TVB buffer as a temporary render target buffer. Since this happens during a +//! partial render itself, if the buffer runs out of space, it requires synchronous growth in +//! response to a firmware interrupt. This is not currently supported, but may be in the future, +//! though it is unclear whether it is worth the effort. +//! +//! This module is also in charge of managing the temporary objects associated with a single render +//! pass, which includes the top-level tile array, the tail pointer cache, preemption buffers, and +//! other miscellaneous structures collectively managed as a "scene". +//! +//! To avoid runaway memory usage, there is a maximum size for buffers (at that point it's unlikely +//! that partial renders will incur much overhead over the buffer data access itself). This is +//! different depending on whether memoryless render targets are in use, and is currently hardcoded. +//! to the most common value used by macOS. + +use crate::debug::*; +use crate::fw::buffer; +use crate::fw::types::*; +use crate::util::*; +use crate::{ + alloc, + fw, + gpu, + hw, + mmu, + slotalloc, // +}; +use core::sync::atomic::Ordering; +use kernel::new_mutex; +use kernel::prelude::*; +use kernel::sync::{ + Arc, + Mutex, // +}; +use kernel::{ + c_str, + static_lock_class, // +}; + +const DEBUG_CLASS: DebugFlags = DebugFlags::Buffer; + +/// There are 127 GPU/firmware-side buffer manager slots (yes, 127, not 128). +const NUM_BUFFERS: u32 = 127; + +/// Page size bits for buffer pages (32K). VAs must be aligned to this size. +pub(crate) const PAGE_SHIFT: usize = 15; +/// Page size for buffer pages. +pub(crate) const PAGE_SIZE: usize = 1 << PAGE_SHIFT; +/// Number of pages in a buffer block, which should be contiguous in VA space. +pub(crate) const PAGES_PER_BLOCK: usize = 4; +/// Size of a buffer block. +pub(crate) const BLOCK_SIZE: usize = PAGE_SIZE * PAGES_PER_BLOCK; + +/// Metadata about the tiling configuration for a scene. This is computed in the `render` module. +/// based on dimensions, tile size, and other info. +pub(crate) struct TileInfo { + /// Tile count in the X dimension. Tiles are always 32x32. + pub(crate) tiles_x: u32, + /// Tile count in the Y dimension. Tiles are always 32x32. + pub(crate) tiles_y: u32, + /// Total tile count. + pub(crate) tiles: u32, + /// Micro-tile width (16 or 32). + pub(crate) utile_width: u32, + /// Micro-tile height (16 or 32). + pub(crate) utile_height: u32, + // Macro-tiles in the X dimension. Always 4. + //pub(crate) mtiles_x: u32, + // Macro-tiles in the Y dimension. Always 4. + //pub(crate) mtiles_y: u32, + /// Tiles per macro-tile in the X dimension. + pub(crate) tiles_per_mtile_x: u32, + /// Tiles per macro-tile in the Y dimension. + pub(crate) tiles_per_mtile_y: u32, + // Total tiles per macro-tile. + //pub(crate) tiles_per_mtile: u32, + /// Micro-tiles per macro-tile in the X dimension. + pub(crate) utiles_per_mtile_x: u32, + /// Micro-tiles per macro-tile in the Y dimension. + pub(crate) utiles_per_mtile_y: u32, + // Total micro-tiles per macro-tile. + //pub(crate) utiles_per_mtile: u32, + /// Size of the top-level tilemap, in bytes (for all layers, one cluster). + pub(crate) tilemap_size: usize, + /// Size of the Tail Pointer Cache, in bytes (for all layers * clusters). + pub(crate) tpc_size: usize, + /// Number of blocks in the clustering meta buffer (for clustering) per layer. + pub(crate) meta1_layer_stride: u32, + /// Number of blocks in the clustering meta buffer (for clustering). + pub(crate) meta1_blocks: u32, + /// Layering metadata size. + pub(crate) layermeta_size: usize, + /// Minimum number of TVB blocks for this render. + pub(crate) min_tvb_blocks: usize, + /// Tiling parameter structure passed to firmware. + pub(crate) params: fw::vertex::raw::TilingParameters, +} + +/// A single scene, representing a render pass and its required buffers. +#[versions(AGX)] +#[derive(Debug)] +pub(crate) struct Scene { + object: GpuObject, + slot: u32, + rebind: bool, + preempt2_off: usize, + preempt3_off: usize, + // Note: these are dead code only on some version variants. + // It's easier to do this than to propagate the version conditionals everywhere. + #[allow(dead_code)] + meta1_off: usize, + #[allow(dead_code)] + meta2_off: usize, + #[allow(dead_code)] + meta3_off: usize, + #[allow(dead_code)] + meta4_off: usize, +} + +#[versions(AGX)] +impl Scene::ver { + /// Returns true if the buffer was bound to a fresh manager slot, and therefore needs an init + /// command before a render. + pub(crate) fn rebind(&self) -> bool { + self.rebind + } + + /// Returns the buffer manager slot this scene's buffer was bound to. + pub(crate) fn slot(&self) -> u32 { + self.slot + } + + /// Returns the GPU pointer to the [`buffer::Scene::ver`]. + pub(crate) fn gpu_pointer(&self) -> GpuPointer<'_, buffer::Scene::ver> { + self.object.gpu_pointer() + } + + /// Returns the GPU weak pointer to the [`buffer::Scene::ver`]. + pub(crate) fn weak_pointer(&self) -> GpuWeakPointer { + self.object.weak_pointer() + } + + /// Returns the GPU weak pointer to the kernel-side temp buffer. + /// (purpose unknown...) + pub(crate) fn kernel_buffer_pointer(&self) -> GpuWeakPointer<[u8]> { + self.object.buffer.inner.lock().kernel_buffer.weak_pointer() + } + + /// Returns the GPU pointer to the `buffer::Info::ver` object associated with this Scene. + pub(crate) fn buffer_pointer(&self) -> GpuPointer<'_, buffer::Info::ver> { + // SAFETY: We can't return the strong pointer directly since its lifetime crosses a lock, + // but we know its lifetime will be valid as long as &self since we hold a reference to the + // buffer, so just construct the strong pointer with the right lifetime here. + unsafe { self.weak_buffer_pointer().upgrade() } + } + + /// Returns the GPU weak pointer to the `buffer::Info::ver` object associated with this Scene. + pub(crate) fn weak_buffer_pointer(&self) -> GpuWeakPointer { + self.object.buffer.inner.lock().info.weak_pointer() + } + + /// Returns the GPU pointer to the TVB heap metadata buffer. + pub(crate) fn tvb_heapmeta_pointer(&self) -> GpuPointer<'_, &'_ [u8]> { + self.object.tvb_heapmeta.gpu_pointer() + } + + /// Returns the GPU pointer to the layer metadata buffer. + pub(crate) fn tvb_layermeta_pointer(&self) -> GpuPointer<'_, &'_ [u8]> { + self.object.tvb_heapmeta.gpu_offset_pointer(0x200) + } + + /// Returns the GPU pointer to the top-level TVB tilemap buffer. + pub(crate) fn tvb_tilemap_pointer(&self) -> GpuPointer<'_, &'_ [u8]> { + self.object.tvb_tilemap.gpu_pointer() + } + + /// Returns the GPU pointer to the Tail Pointer Cache buffer. + pub(crate) fn tpc_pointer(&self) -> GpuPointer<'_, &'_ [u8]> { + self.object.tpc.gpu_pointer() + } + + /// Returns the GPU pointer to the first preemption scratch buffer. + pub(crate) fn preempt_buf_1_pointer(&self) -> GpuPointer<'_, &'_ [u8]> { + self.object.preempt_buf.gpu_pointer() + } + + /// Returns the GPU pointer to the second preemption scratch buffer. + pub(crate) fn preempt_buf_2_pointer(&self) -> GpuPointer<'_, &'_ [u8]> { + self.object + .preempt_buf + .gpu_offset_pointer(self.preempt2_off) + } + + /// Returns the GPU pointer to the third preemption scratch buffer. + pub(crate) fn preempt_buf_3_pointer(&self) -> GpuPointer<'_, &'_ [u8]> { + self.object + .preempt_buf + .gpu_offset_pointer(self.preempt3_off) + } + + /// Returns the GPU pointer to the per-cluster tilemap buffer, if clustering is enabled. + #[allow(dead_code)] + pub(crate) fn cluster_tilemaps_pointer(&self) -> Option> { + self.object + .clustering + .as_ref() + .map(|c| c.tilemaps.gpu_pointer()) + } + + /// Returns the GPU pointer to the clustering layer metadata buffer, if clustering is enabled. + #[allow(dead_code)] + pub(crate) fn tvb_cluster_layermeta_pointer(&self) -> Option> { + self.object + .clustering + .as_ref() + .map(|c| c.meta.gpu_pointer()) + } + + /// Returns the GPU pointer to the clustering metadata 1 buffer, if clustering is enabled. + #[allow(dead_code)] + pub(crate) fn meta_1_pointer(&self) -> Option> { + self.object + .clustering + .as_ref() + .map(|c| c.meta.gpu_offset_pointer(self.meta1_off)) + } + + /// Returns the GPU pointer to the clustering metadata 2 buffer, if clustering is enabled. + #[allow(dead_code)] + pub(crate) fn meta_2_pointer(&self) -> Option> { + self.object + .clustering + .as_ref() + .map(|c| c.meta.gpu_offset_pointer(self.meta2_off)) + } + + /// Returns the GPU pointer to the clustering metadata 3 buffer, if clustering is enabled. + #[allow(dead_code)] + pub(crate) fn meta_3_pointer(&self) -> Option> { + self.object + .clustering + .as_ref() + .map(|c| c.meta.gpu_offset_pointer(self.meta3_off)) + } + + /// Returns the GPU pointer to the clustering metadata 4 buffer, if clustering is enabled. + #[allow(dead_code)] + pub(crate) fn meta_4_pointer(&self) -> Option> { + self.object + .clustering + .as_ref() + .map(|c| c.meta.gpu_offset_pointer(self.meta4_off)) + } +} + +#[versions(AGX)] +impl Drop for Scene::ver { + fn drop(&mut self) { + let mut inner = self.object.buffer.inner.lock(); + assert_ne!(inner.active_scenes, 0); + inner.active_scenes -= 1; + + if inner.active_scenes == 0 { + mod_pr_debug!( + "Buffer: no scenes left, dropping slot {}", + inner.active_slot.take().unwrap().slot() + ); + inner.active_slot = None; + } + } +} + +/// Inner data for a single TVB buffer object. +#[versions(AGX)] +struct BufferInner { + info: GpuObject, + ualloc: Arc>, + ualloc_priv: Arc>, + blocks: KVec>, + max_blocks: usize, + max_blocks_nomemless: usize, + mgr: BufferManager::ver, + active_scenes: usize, + active_slot: Option>, + last_token: Option, + tpc: Option>>, + kernel_buffer: GpuArray, + stats: GpuObject, + cfg: &'static hw::HwConfig, + preempt1_size: usize, + preempt2_size: usize, + preempt3_size: usize, + num_clusters: usize, +} + +/// Locked and reference counted TVB buffer. +#[versions(AGX)] +pub(crate) struct Buffer { + inner: Arc>, +} + +#[versions(AGX)] +impl Buffer::ver { + /// Create a new Buffer for a given VM, given the per-VM allocators. + pub(crate) fn new( + gpu: &dyn gpu::GpuManager, + alloc: &mut gpu::KernelAllocators, + ualloc: Arc>, + ualloc_priv: Arc>, + mgr: &BufferManager::ver, + ) -> Result { + // These are the typical max numbers on macOS. + // 8GB machines have this halved. + let max_size: usize = 862_322_688; // bytes + let max_size_nomemless = max_size / 3; + + let max_blocks = max_size / BLOCK_SIZE; + let max_blocks_nomemless = max_size_nomemless / BLOCK_SIZE; + let max_pages = max_blocks * PAGES_PER_BLOCK; + let max_pages_nomemless = max_blocks_nomemless * PAGES_PER_BLOCK; + + let num_clusters = gpu.get_dyncfg().id.num_clusters as usize; + let num_clusters_adj = if num_clusters > 1 { + num_clusters + 1 + } else { + 1 + }; + + let preempt1_size = num_clusters_adj * gpu.get_cfg().preempt1_size; + let preempt2_size = num_clusters_adj * gpu.get_cfg().preempt2_size; + let preempt3_size = num_clusters_adj * gpu.get_cfg().preempt3_size; + + let shared = &mut alloc.shared; + let info = alloc.private.new_init( + { + let ualloc_priv = &ualloc_priv; + try_init!(buffer::Info::ver { + block_ctl: shared.new_default::()?, + counter: shared.new_default::()?, + page_list: ualloc_priv.lock().array_empty_tagged(max_pages, b"PLST")?, + block_list: ualloc_priv + .lock() + .array_empty_tagged(max_blocks * 2, b"BLST")?, + }) + }, + |inner, _p| { + try_init!(buffer::raw::Info::ver { + gpu_counter: 0x0, + unk_4: 0, + last_id: 0x0, + cur_id: -1, + unk_10: 0x0, + gpu_counter2: 0x0, + unk_18: 0x0, + #[ver(V < V13_0B4 || G >= G14X)] + unk_1c: 0x0, + page_list: inner.page_list.gpu_pointer(), + page_list_size: (4 * max_pages).try_into()?, + page_count: AtomicU32::new(0), + max_blocks: max_blocks.try_into()?, + block_count: AtomicU32::new(0), + unk_38: 0x0, + block_list: inner.block_list.gpu_pointer(), + block_ctl: inner.block_ctl.gpu_pointer(), + last_page: AtomicU32::new(0), + gpu_page_ptr1: 0x0, + gpu_page_ptr2: 0x0, + unk_58: 0x0, + block_size: BLOCK_SIZE as u32, + unk_60: U64(0x0), + counter: inner.counter.gpu_pointer(), + unk_70: 0x0, + unk_74: 0x0, + unk_78: 0x0, + unk_7c: 0x0, + unk_80: 0x1, + max_pages: max_pages.try_into()?, + max_pages_nomemless: max_pages_nomemless.try_into()?, + unk_8c: 0x0, + unk_90: Default::default(), + }) + }, + )?; + + // Technically similar to Scene below, let's play it safe. + let kernel_buffer = alloc.shared.array_empty_tagged(0x40, b"KBUF")?; + let stats = alloc + .shared + .new_object(Default::default(), |_inner| buffer::raw::Stats { + reset: AtomicU32::from(1), + ..Default::default() + })?; + + Ok(Buffer::ver { + inner: Arc::pin_init( + new_mutex!(BufferInner::ver { + info, + ualloc, + ualloc_priv, + blocks: KVec::new(), + max_blocks, + max_blocks_nomemless, + mgr: mgr.clone(), + active_scenes: 0, + active_slot: None, + last_token: None, + tpc: None, + kernel_buffer, + stats, + cfg: gpu.get_cfg(), + preempt1_size, + preempt2_size, + preempt3_size, + num_clusters, + }), + GFP_KERNEL, + )?, + }) + } + + /// Returns the total block count allocated to this Buffer. + pub(crate) fn block_count(&self) -> u32 { + self.inner.lock().blocks.len() as u32 + } + + /// Automatically grow the Buffer based on feedback from the statistics. + pub(crate) fn auto_grow(&self) -> Result { + let inner = self.inner.lock(); + + let used_pages = inner.stats.with(|raw, _inner| { + let used = raw.max_pages.load(Ordering::Relaxed); + raw.reset.store(1, Ordering::Release); + used as usize + }); + + let need_blocks = (used_pages * 2) + .div_ceil(PAGES_PER_BLOCK) + .min(inner.max_blocks_nomemless); + let want_blocks = (used_pages * 3) + .div_ceil(PAGES_PER_BLOCK) + .min(inner.max_blocks_nomemless); + + let cur_count = inner.blocks.len(); + + if need_blocks <= cur_count { + Ok(false) + } else { + // Grow to 3x requested size (same logic as macOS) + core::mem::drop(inner); + self.ensure_blocks(want_blocks)?; + Ok(true) + } + } + + /// Synchronously grow the Buffer. + pub(crate) fn sync_grow(&self) { + let inner = self.inner.lock(); + + let cur_count = inner.blocks.len(); + core::mem::drop(inner); + if self.ensure_blocks(cur_count + 10).is_err() { + pr_err!("BufferManager: Failed to grow buffer synchronously\n"); + } + } + + /// Ensure that the buffer has at least a certain minimum size in blocks. + pub(crate) fn ensure_blocks(&self, min_blocks: usize) -> Result { + let mut inner = self.inner.lock(); + + let cur_count = inner.blocks.len(); + if cur_count >= min_blocks { + return Ok(false); + } + if min_blocks > inner.max_blocks { + return Err(ENOMEM); + } + + let add_blocks = min_blocks - cur_count; + let new_count = min_blocks; + + let mut new_blocks: KVec> = KVec::new(); + + // Allocate the new blocks first, so if it fails they will be dropped + let mut ualloc = inner.ualloc.lock(); + for _i in 0..add_blocks { + new_blocks.push(ualloc.array_gpuonly(BLOCK_SIZE)?, GFP_KERNEL)?; + } + core::mem::drop(ualloc); + + // Then actually commit them + inner.blocks.reserve(add_blocks, GFP_KERNEL)?; + + for (i, block) in new_blocks.into_iter().enumerate() { + let page_num = (block.gpu_va().get() >> PAGE_SHIFT) as u32; + + inner + .blocks + .push(block, GFP_KERNEL) + .expect("push() failed after reserve()"); + inner.info.block_list[2 * (cur_count + i)] = page_num; + for j in 0..PAGES_PER_BLOCK { + inner.info.page_list[(cur_count + i) * PAGES_PER_BLOCK + j] = page_num + j as u32; + } + } + + inner.info.block_ctl.with(|raw, _inner| { + raw.total.store(new_count as u32, Ordering::SeqCst); + raw.wptr.store(new_count as u32, Ordering::SeqCst); + }); + + /* Only do this update if the buffer manager is idle (which means we own it) */ + if inner.active_scenes == 0 { + let page_count = (new_count * PAGES_PER_BLOCK) as u32; + inner.info.with(|raw, _inner| { + raw.page_count.store(page_count, Ordering::Relaxed); + raw.block_count.store(new_count as u32, Ordering::Relaxed); + raw.last_page.store(page_count - 1, Ordering::Relaxed); + }); + } + + Ok(true) + } + + /// Create a new [`Scene::ver`] (render pass) using this buffer. + pub(crate) fn new_scene( + &self, + alloc: &mut gpu::KernelAllocators, + tile_info: &TileInfo, + ) -> Result { + let mut inner = self.inner.lock(); + + let tilemap_size = tile_info.tilemap_size; + let tpc_size = tile_info.tpc_size; + + // TODO: what is this exactly? + mod_pr_debug!("Buffer: Allocating TVB buffers\n"); + + // This seems to be a list, with 4x2 bytes of headers and 8 bytes per entry. + // On single-cluster devices, the used length always seems to be 1. + // On M1 Ultra, it can grow and usually doesn't exceed 64 entries. + // macOS allocates a whole 64K * 0x80 for this, so let's go with + // that to be safe... + let user_buffer = inner.ualloc.lock().array_empty_tagged( + if inner.num_clusters > 1 { + 0x10080 + } else { + 0x80 + }, + b"UBUF", + )?; + + let tvb_heapmeta = inner + .ualloc + .lock() + .array_empty_tagged(0x200 + tile_info.layermeta_size, b"HMTA")?; + let tvb_tilemap = inner + .ualloc + .lock() + .array_empty_tagged(tilemap_size, b"TMAP")?; + + mod_pr_debug!("Buffer: Allocating misc buffers\n"); + let preempt_buf = inner.ualloc.lock().array_empty_tagged( + inner.preempt1_size + inner.preempt2_size + inner.preempt3_size, + b"PRMT", + )?; + + let tpc = match inner.tpc.as_ref() { + Some(buf) if buf.len() >= tpc_size => buf.clone(), + _ => { + // MacOS allocates this as shared GPU+FW, but + // priv seems to work and might be faster? + // Needs to be FW-writable anyway, so ualloc + // won't work. + let buf = Arc::new( + inner.ualloc_priv.lock().array_empty_tagged( + (tpc_size + mmu::UAT_PGMSK) & !mmu::UAT_PGMSK, + b"TPC ", + )?, + GFP_KERNEL, + )?; + inner.tpc = Some(buf.clone()); + buf + } + }; + + let mut clmeta_size = 0; + let mut meta1_size = 0; + let mut meta2_size = 0; + let mut meta3_size = 0; + + let clustering = if inner.num_clusters > 1 { + let cfg = inner.cfg.clustering.as_ref().unwrap(); + + clmeta_size = tile_info.layermeta_size * cfg.max_splits; + // Maybe: (4x4 macro tiles + 1 global page)*n, 32bit each (17*4*n) + // Unused on t602x? + meta1_size = align(tile_info.meta1_blocks as usize * cfg.meta1_blocksize, 0x80); + meta2_size = align(cfg.meta2_size, 0x80); + meta3_size = align(cfg.meta3_size, 0x80); + let meta4_size = cfg.meta4_size; + + let meta_size = clmeta_size + meta1_size + meta2_size + meta3_size + meta4_size; + + mod_pr_debug!("Buffer: Allocating clustering buffers\n"); + let tilemaps = inner + .ualloc + .lock() + .array_empty_tagged(cfg.max_splits * tilemap_size, b"CTMP")?; + let meta = inner.ualloc.lock().array_empty_tagged(meta_size, b"CMTA")?; + Some(buffer::ClusterBuffers { tilemaps, meta }) + } else { + None + }; + + // Could be made strong, but we wind up with a deadlock if we try to grab the + // pointer through the inner.buffer path inside the closure. + let stats_pointer = inner.stats.weak_pointer(); + + let _gpu = &mut alloc.gpu; + + // macOS allocates this as private. However, the firmware does not + // DC CIVAC this before reading it (like it does most other things), + // which causes odd cache incoherency bugs when combined with + // speculation on the firmware side (maybe). This doesn't happen + // on macOS because these structs are a circular pool that is mapped + // already initialized. Just mark this shared for now. + let scene = alloc.shared.new_init( + try_init!(buffer::Scene::ver { + user_buffer: user_buffer, + buffer: self.clone(), + tvb_heapmeta: tvb_heapmeta, + tvb_tilemap: tvb_tilemap, + tpc: tpc, + clustering: clustering, + preempt_buf: preempt_buf, + #[ver(G >= G14X)] + control_word: _gpu.array_empty_tagged(1, b"CWRD")?, + }), + |inner, _p| { + try_init!(buffer::raw::Scene::ver { + #[ver(G >= G14X)] + control_word: inner.control_word.gpu_pointer(), + #[ver(G >= G14X)] + control_word2: inner.control_word.gpu_pointer(), + pass_page_count: AtomicU32::new(0), + unk_4: 0, + unk_8: U64(0), + unk_10: U64(0), + user_buffer: inner.user_buffer.gpu_pointer(), + unk_20: 0, + #[ver(V >= V13_3)] + unk_28: U64(0), + stats: stats_pointer, + total_page_count: AtomicU32::new(0), + #[ver(G < G14X)] + unk_30: U64(0), + #[ver(G < G14X)] + unk_38: U64(0), + }) + }, + )?; + + let mut rebind = false; + + if inner.active_slot.is_none() { + assert_eq!(inner.active_scenes, 0); + + let slot = inner.mgr.0.get_inner(inner.last_token, |inner, mgr| { + inner.owners[mgr.slot() as usize] = Some(self.clone()); + Ok(()) + })?; + rebind = slot.changed(); + + mod_pr_debug!("Buffer: assigning slot {} (rebind={})", slot.slot(), rebind); + + inner.last_token = Some(slot.token()); + inner.active_slot = Some(slot); + } + + inner.active_scenes += 1; + + Ok(Scene::ver { + object: scene, + slot: inner.active_slot.as_ref().unwrap().slot(), + rebind, + preempt2_off: inner.preempt1_size, + preempt3_off: inner.preempt1_size + inner.preempt2_size, + meta1_off: clmeta_size, + meta2_off: clmeta_size + meta1_size, + meta3_off: clmeta_size + meta1_size + meta2_size, + meta4_off: clmeta_size + meta1_size + meta2_size + meta3_size, + }) + } + + /// Increment the buffer manager usage count. Should we done once we know the Scene is ready + /// to be committed and used in commands submitted to the GPU. + pub(crate) fn increment(&self) { + let inner = self.inner.lock(); + inner.info.counter.with(|raw, _inner| { + // We could use fetch_add, but the non-LSE atomic + // sequence Rust produces confuses the hypervisor. + // We have inner locked anyway, so this is not racy. + let v = raw.count.load(Ordering::Relaxed); + raw.count.store(v + 1, Ordering::Relaxed); + }); + } + + pub(crate) fn any_ref(&self) -> Arc { + self.inner.clone() + } +} + +#[versions(AGX)] +impl Clone for Buffer::ver { + fn clone(&self) -> Self { + Buffer::ver { + inner: self.inner.clone(), + } + } +} + +#[versions(AGX)] +struct BufferSlotInner(); + +#[versions(AGX)] +impl slotalloc::SlotItem for BufferSlotInner::ver { + type Data = BufferManagerInner::ver; + + fn release(&mut self, data: &mut Self::Data, slot: u32) { + mod_pr_debug!("EventManager: Released slot {}\n", slot); + data.owners[slot as usize] = None; + } +} + +/// Inner data for the event manager, to be protected by the SlotAllocator lock. +#[versions(AGX)] +pub(crate) struct BufferManagerInner { + owners: KVec>, +} + +/// The GPU-global buffer manager, used to allocate and release buffer slots from the pool. +#[versions(AGX)] +pub(crate) struct BufferManager(slotalloc::SlotAllocator); + +#[versions(AGX)] +impl BufferManager::ver { + pub(crate) fn new() -> Result { + let mut owners = KVec::new(); + for _i in 0..(NUM_BUFFERS as usize) { + owners.push(None, GFP_KERNEL)?; + } + Ok(BufferManager::ver(slotalloc::SlotAllocator::new( + NUM_BUFFERS, + BufferManagerInner::ver { owners }, + |_inner, _slot| Some(BufferSlotInner::ver()), + c_str!("BufferManager::SlotAllocator"), + static_lock_class!(), + static_lock_class!(), + )?)) + } + + /// Signals a Buffer to synchronously grow. + pub(crate) fn grow(&self, slot: u32) { + match self + .0 + .with_inner(|inner| inner.owners[slot as usize].as_ref().cloned()) + { + Some(owner) => { + pr_err!( + "BufferManager: Unexpected grow request for slot {}. This might deadlock. Please report this bug.\n", + slot + ); + owner.sync_grow(); + } + None => { + pr_err!( + "BufferManager: Received grow request for empty slot {}\n", + slot + ); + } + } + } +} + +#[versions(AGX)] +impl Clone for BufferManager::ver { + fn clone(&self) -> Self { + BufferManager::ver(self.0.clone()) + } +} diff --git a/drivers/gpu/drm/asahi/channel.rs b/drivers/gpu/drm/asahi/channel.rs new file mode 100644 index 00000000000000..30cc0efbf3ce22 --- /dev/null +++ b/drivers/gpu/drm/asahi/channel.rs @@ -0,0 +1,631 @@ +// SPDX-License-Identifier: GPL-2.0-only OR MIT + +//! GPU ring buffer channels +//! +//! The GPU firmware use a set of ring buffer channels to receive commands from the driver and send +//! it notifications and status messages. +//! +//! These ring buffers mostly follow uniform conventions, so they share the same base +//! implementation. + +use crate::debug::*; +use crate::driver::{ + AsahiDevRef, + AsahiDevice, // +}; +use crate::fw::channels::*; +use crate::fw::initdata::{ + raw, + ChannelRing, // +}; +use crate::fw::types::*; +use crate::{ + buffer, + event, + gpu, + mem, // +}; +use kernel::{ + c_str, + prelude::*, + sync::Arc, + time::{ + delay::fsleep, + Delta, + Instant, + Monotonic, // + }, +}; + +pub(crate) use crate::fw::channels::PipeType; + +/// A receive (FW->driver) channel. +pub(crate) struct RxChannel +where + for<'a> ::Raw<'a>: Debug + Default + Zeroable, +{ + ring: ChannelRing, + // FIXME: needs feature(generic_const_exprs) + //rptr: [u32; T::SUB_CHANNELS], + rptr: [u32; 6], + count: u32, +} + +impl RxChannel +where + for<'a> ::Raw<'a>: Debug + Default + Zeroable, +{ + /// Allocates a new receive channel with a given message count. + pub(crate) fn new(alloc: &mut gpu::KernelAllocators, count: usize) -> Result> { + Ok(RxChannel { + ring: ChannelRing { + state: alloc.shared.new_default()?, + ring: alloc.shared.array_empty(T::SUB_CHANNELS * count)?, + }, + rptr: Default::default(), + count: count as u32, + }) + } + + /// Receives a message on the specified sub-channel index, optionally leaving in the ring + /// buffer. + /// + /// Returns None if the channel is empty. + fn get_or_peek(&mut self, index: usize, peek: bool) -> Option { + self.ring.state.with(|raw, _inner| { + let wptr = T::wptr(raw, index); + let rptr = &mut self.rptr[index]; + if wptr == *rptr { + None + } else { + let off = self.count as usize * index; + let msg = self.ring.ring[off + *rptr as usize]; + if !peek { + *rptr = (*rptr + 1) % self.count; + T::set_rptr(raw, index, *rptr); + } + Some(msg) + } + }) + } + + /// Receives a message on the specified sub-channel index, and dequeues it from the ring buffer. + /// + /// Returns None if the channel is empty. + pub(crate) fn get(&mut self, index: usize) -> Option { + self.get_or_peek(index, false) + } + + /// Peeks a message on the specified sub-channel index, leaving it in the ring buffer. + /// + /// Returns None if the channel is empty. + pub(crate) fn peek(&mut self, index: usize) -> Option { + self.get_or_peek(index, true) + } +} + +/// A transmit (driver->FW) channel. +pub(crate) struct TxChannel +where + for<'a> ::Raw<'a>: Debug + Default + Zeroable, +{ + ring: ChannelRing, + wptr: u32, + count: u32, +} + +impl TxChannel +where + for<'a> ::Raw<'a>: Debug + Default + Zeroable, +{ + /// Allocates a new cached transmit channel with a given message count. + pub(crate) fn new(alloc: &mut gpu::KernelAllocators, count: usize) -> Result> { + Ok(TxChannel { + ring: ChannelRing { + state: alloc.shared.new_default()?, + ring: alloc.private.array_empty(count)?, + }, + wptr: 0, + count: count as u32, + }) + } + + /// Allocates a new uncached transmit channel with a given message count. + pub(crate) fn new_uncached( + alloc: &mut gpu::KernelAllocators, + count: usize, + ) -> Result> { + Ok(TxChannel { + ring: ChannelRing { + state: alloc.shared.new_default()?, + ring: alloc.shared.array_empty(count)?, + }, + wptr: 0, + count: count as u32, + }) + } + + /// Send a message to the ring, returning a cookie with the ring buffer position. + /// + /// This will poll/block if the ring is full, which we don't really expect to happen. + pub(crate) fn put(&mut self, msg: &U) -> u32 { + self.ring.state.with(|raw, _inner| { + let next_wptr = (self.wptr + 1) % self.count; + let mut rptr = T::rptr(raw); + if next_wptr == rptr { + pr_err!( + "TX ring buffer is full! Waiting... ({}, {})\n", + next_wptr, + rptr + ); + // TODO: block properly on incoming messages? + while next_wptr == rptr { + fsleep(Delta::from_millis(8)); + rptr = T::rptr(raw); + } + } + self.ring.ring[self.wptr as usize] = *msg; + mem::sync(); + T::set_wptr(raw, next_wptr); + self.wptr = next_wptr; + }); + self.wptr + } + + /// Wait for a previously submitted message to be popped off of the ring by the GPU firmware. + /// + /// This busy-loops, and is intended to be used for rare cases when we need to block for + /// completion of a cache management or invalidation operation synchronously (which + /// the firmware normally completes fast enough not to be worth sleeping for). + /// If the poll takes longer than 10ms, this switches to sleeping between polls. + pub(crate) fn wait_for(&mut self, wptr: u32, timeout_ms: i64) -> Result { + const MAX_FAST_POLL: i64 = 10; + let start = Instant::::now(); + let timeout_ms = timeout_ms.max(1); + let timeout_fast = Delta::from_millis(timeout_ms.min(MAX_FAST_POLL)); + let timeout_slow = Delta::from_millis(timeout_ms); + self.ring.state.with(|raw, _inner| { + while start.elapsed() < timeout_fast { + if T::rptr(raw) == wptr { + return Ok(()); + } + mem::sync(); + } + while start.elapsed() < timeout_slow { + if T::rptr(raw) == wptr { + return Ok(()); + } + fsleep(Delta::from_millis(5)); + mem::sync(); + } + Err(ETIMEDOUT) + }) + } +} + +/// Device Control channel for global device management commands. +#[versions(AGX)] +pub(crate) struct DeviceControlChannel { + dev: AsahiDevRef, + ch: TxChannel, +} + +#[versions(AGX)] +impl DeviceControlChannel::ver { + const COMMAND_TIMEOUT_MS: i64 = 1000; + + /// Allocate a new Device Control channel. + pub(crate) fn new( + dev: &AsahiDevice, + alloc: &mut gpu::KernelAllocators, + ) -> Result { + Ok(DeviceControlChannel::ver { + dev: dev.into(), + ch: TxChannel::::new(alloc, 0x100)?, + }) + } + + /// Returns the raw `ChannelRing` structure to pass to firmware. + pub(crate) fn to_raw(&self) -> raw::ChannelRing { + self.ch.ring.to_raw() + } + + /// Submits a Device Control command. + pub(crate) fn send(&mut self, msg: &DeviceControlMsg::ver) -> u32 { + cls_dev_dbg!(DeviceControlCh, self.dev, "DeviceControl: {:?}\n", msg); + self.ch.put(msg) + } + + /// Waits for a previously submitted Device Control command to complete. + pub(crate) fn wait_for(&mut self, wptr: u32) -> Result { + self.ch.wait_for(wptr, Self::COMMAND_TIMEOUT_MS) + } +} + +/// Pipe channel to submit WorkQueue execution requests. +#[versions(AGX)] +pub(crate) struct PipeChannel { + dev: AsahiDevRef, + ch: TxChannel, +} + +#[versions(AGX)] +impl PipeChannel::ver { + /// Allocate a new Pipe submission channel. + pub(crate) fn new( + dev: &AsahiDevice, + alloc: &mut gpu::KernelAllocators, + ) -> Result { + Ok(PipeChannel::ver { + dev: dev.into(), + ch: TxChannel::::new(alloc, 0x100)?, + }) + } + + /// Returns the raw `ChannelRing` structure to pass to firmware. + pub(crate) fn to_raw(&self) -> raw::ChannelRing { + self.ch.ring.to_raw() + } + + /// Submits a Pipe kick command to the firmware. + pub(crate) fn send(&mut self, msg: &PipeMsg::ver) { + cls_dev_dbg!(PipeCh, self.dev, "Pipe: {:?}\n", msg); + self.ch.put(msg); + } +} + +/// Firmware Control channel, used for secure cache flush requests. +pub(crate) struct FwCtlChannel { + dev: AsahiDevRef, + ch: TxChannel, +} + +impl FwCtlChannel { + const COMMAND_TIMEOUT_MS: i64 = 1000; + + /// Allocate a new Firmware Control channel. + pub(crate) fn new( + dev: &AsahiDevice, + alloc: &mut gpu::KernelAllocators, + ) -> Result { + Ok(FwCtlChannel { + dev: dev.into(), + ch: TxChannel::::new_uncached(alloc, 0x100)?, + }) + } + + /// Returns the raw `ChannelRing` structure to pass to firmware. + pub(crate) fn to_raw(&self) -> raw::ChannelRing { + self.ch.ring.to_raw() + } + + /// Submits a Firmware Control command to the firmware. + pub(crate) fn send(&mut self, msg: &FwCtlMsg) -> u32 { + cls_dev_dbg!(FwCtlCh, self.dev, "FwCtl: {:?}\n", msg); + self.ch.put(msg) + } + + /// Waits for a previously submitted Firmware Control command to complete. + pub(crate) fn wait_for(&mut self, wptr: u32) -> Result { + self.ch.wait_for(wptr, Self::COMMAND_TIMEOUT_MS) + } +} + +/// Event channel, used to notify the driver of command completions, GPU faults and errors, and +/// other events. +#[versions(AGX)] +pub(crate) struct EventChannel { + dev: AsahiDevRef, + ch: RxChannel, + ev_mgr: Arc, + buf_mgr: buffer::BufferManager::ver, + gpu: Option>, +} + +#[versions(AGX)] +impl EventChannel::ver { + /// Allocate a new Event channel. + pub(crate) fn new( + dev: &AsahiDevice, + alloc: &mut gpu::KernelAllocators, + ev_mgr: Arc, + buf_mgr: buffer::BufferManager::ver, + ) -> Result { + Ok(EventChannel::ver { + dev: dev.into(), + ch: RxChannel::::new(alloc, 0x100)?, + ev_mgr, + buf_mgr, + gpu: None, + }) + } + + /// Registers the managing `Gpu` instance that will handle events on this channel. + pub(crate) fn set_manager(&mut self, gpu: Arc) { + self.gpu = Some(gpu); + } + + /// Returns the raw `ChannelRing` structure to pass to firmware. + pub(crate) fn to_raw(&self) -> raw::ChannelRing { + self.ch.ring.to_raw() + } + + /// Polls for new Event messages on this ring. + pub(crate) fn poll(&mut self) { + while let Some(msg) = self.ch.get(0) { + // SAFETY: The raw view is always valid for all bit patterns. + let tag = unsafe { msg.raw.0 }; + match tag { + 0..=EVENT_MAX => { + // SAFETY: Since we have checked the tag to be in range, + // accessing the enum view is valid. + let msg = unsafe { msg.msg }; + + cls_dev_dbg!(EventCh, self.dev, "Event: {:?}\n", msg); + match msg { + EventMsg::Fault => match self.gpu.as_ref() { + Some(gpu) => gpu.handle_fault(), + None => { + dev_crit!( + self.dev.as_ref(), + "EventChannel: No GPU manager available!\n" + ) + } + }, + EventMsg::Timeout { + counter, + unk_8, + event_slot, + } => match self.gpu.as_ref() { + Some(gpu) => gpu.handle_timeout(counter, event_slot, unk_8), + None => { + dev_crit!( + self.dev.as_ref(), + "EventChannel: No GPU manager available!\n" + ) + } + }, + EventMsg::Flag { firing, .. } => { + for (i, flags) in firing.iter().enumerate() { + for j in 0..32 { + if flags & (1u32 << j) != 0 { + self.ev_mgr.signal((i * 32 + j) as u32); + } + } + } + } + EventMsg::GrowTVB { + vm_slot, + buffer_slot, + counter, + } => match self.gpu.as_ref() { + Some(gpu) => { + self.buf_mgr.grow(buffer_slot); + gpu.ack_grow(buffer_slot, vm_slot, counter); + } + None => { + dev_crit!( + self.dev.as_ref(), + "EventChannel: No GPU manager available!\n" + ) + } + }, + EventMsg::ChannelError { + error_type, + pipe_type, + event_slot, + event_value, + } => match self.gpu.as_ref() { + Some(gpu) => { + let error_type = match error_type { + 0 => ChannelErrorType::MemoryError, + 1 => ChannelErrorType::DMKill, + 2 => ChannelErrorType::Aborted, + 3 => ChannelErrorType::Unk3, + a => ChannelErrorType::Unknown(a), + }; + gpu.handle_channel_error( + error_type, + pipe_type, + event_slot, + event_value, + ); + } + None => { + dev_crit!( + self.dev.as_ref(), + "EventChannel: No GPU manager available!\n" + ) + } + }, + msg => { + dev_crit!(self.dev.as_ref(), "Unknown event message: {:?}\n", msg); + } + } + } + _ => { + // SAFETY: The raw view is always valid for all bit patterns. + dev_warn!(self.dev.as_ref(), "Unknown event message: {:?}\n", unsafe { + msg.raw + }); + } + } + } + } +} + +/// Firmware Log channel. This one is pretty special, since it has 6 sub-channels (for different log +/// levels), and it also uses a side buffer to actually hold the log messages, only passing around +/// pointers in the main buffer. +pub(crate) struct FwLogChannel { + dev: AsahiDevRef, + ch: RxChannel, + payload_buf: GpuArray, +} + +impl FwLogChannel { + const RING_SIZE: usize = 0x100; + const BUF_SIZE: usize = 0x100; + + /// Allocate a new Firmware Log channel. + pub(crate) fn new( + dev: &AsahiDevice, + alloc: &mut gpu::KernelAllocators, + ) -> Result { + Ok(FwLogChannel { + dev: dev.into(), + ch: RxChannel::::new(alloc, Self::RING_SIZE)?, + payload_buf: alloc + .shared + .array_empty(Self::BUF_SIZE * FwLogChannelState::SUB_CHANNELS)?, + }) + } + + /// Returns the raw `ChannelRing` structure to pass to firmware. + pub(crate) fn to_raw(&self) -> raw::ChannelRing { + self.ch.ring.to_raw() + } + + /// Returns the GPU pointers to the firmware log payload buffer. + pub(crate) fn get_buf(&self) -> GpuWeakPointer<[RawFwLogPayloadMsg]> { + self.payload_buf.weak_pointer() + } + + /// Polls for new log messages on all sub-rings. + pub(crate) fn poll(&mut self) { + for i in 0..=FwLogChannelState::SUB_CHANNELS - 1 { + while let Some(msg) = self.ch.peek(i) { + cls_dev_dbg!(FwLogCh, self.dev, "FwLog{}: {:?}\n", i, msg); + if msg.msg_type != 2 { + dev_warn!(self.dev.as_ref(), "Unknown FWLog{} message: {:?}\n", i, msg); + self.ch.get(i); + continue; + } + if msg.msg_index.0 as usize >= Self::BUF_SIZE { + dev_warn!( + self.dev.as_ref(), + "FWLog{} message index out of bounds: {:?}\n", + i, + msg + ); + self.ch.get(i); + continue; + } + let index = Self::BUF_SIZE * i + msg.msg_index.0 as usize; + let payload = &self.payload_buf.as_slice()[index]; + if payload.msg_type != 3 { + dev_warn!( + self.dev.as_ref(), + "Unknown FWLog{} payload: {:?}\n", + i, + payload + ); + self.ch.get(i); + continue; + } + let msg = if let Some(end) = payload.msg.iter().position(|&r| r == 0) { + CStr::from_bytes_with_nul(&(*payload.msg)[..end + 1]) + .unwrap_or(c_str!("cstr_err")) + } else { + dev_warn!( + self.dev.as_ref(), + "FWLog{} payload not NUL-terminated: {:?}\n", + i, + payload + ); + self.ch.get(i); + continue; + }; + match i { + 0 => dev_dbg!(self.dev.as_ref(), "FWLog: {}\n", msg), + 1 => dev_info!(self.dev.as_ref(), "FWLog: {}\n", msg), + 2 => dev_notice!(self.dev.as_ref(), "FWLog: {}\n", msg), + 3 => dev_warn!(self.dev.as_ref(), "FWLog: {}\n", msg), + 4 => dev_err!(self.dev.as_ref(), "FWLog: {}\n", msg), + 5 => dev_crit!(self.dev.as_ref(), "FWLog: {}\n", msg), + _ => (), + }; + self.ch.get(i); + } + } + } +} + +pub(crate) struct KTraceChannel { + dev: AsahiDevRef, + ch: RxChannel, +} + +/// KTrace channel, used to receive detailed execution trace markers from the firmware. +/// We currently disable this in initdata, so no messages are expected here at this time. +impl KTraceChannel { + /// Allocate a new KTrace channel. + pub(crate) fn new( + dev: &AsahiDevice, + alloc: &mut gpu::KernelAllocators, + ) -> Result { + Ok(KTraceChannel { + dev: dev.into(), + ch: RxChannel::::new(alloc, 0x200)?, + }) + } + + /// Returns the raw `ChannelRing` structure to pass to firmware. + pub(crate) fn to_raw(&self) -> raw::ChannelRing { + self.ch.ring.to_raw() + } + + /// Polls for new KTrace messages on this ring. + pub(crate) fn poll(&mut self) { + while let Some(msg) = self.ch.get(0) { + cls_dev_dbg!(KTraceCh, self.dev, "KTrace: {:?}\n", msg); + } + } +} + +/// Statistics channel, reporting power-related statistics to the driver. +/// Not really implemented other than debug logs yet... +#[versions(AGX)] +pub(crate) struct StatsChannel { + dev: AsahiDevRef, + ch: RxChannel, +} + +#[versions(AGX)] +impl StatsChannel::ver { + /// Allocate a new Statistics channel. + pub(crate) fn new( + dev: &AsahiDevice, + alloc: &mut gpu::KernelAllocators, + ) -> Result { + Ok(StatsChannel::ver { + dev: dev.into(), + ch: RxChannel::::new(alloc, 0x100)?, + }) + } + + /// Returns the raw `ChannelRing` structure to pass to firmware. + pub(crate) fn to_raw(&self) -> raw::ChannelRing { + self.ch.ring.to_raw() + } + + /// Polls for new statistics messages on this ring. + pub(crate) fn poll(&mut self) { + while let Some(msg) = self.ch.get(0) { + // SAFETY: The raw view is always valid for all bit patterns. + let tag = unsafe { msg.raw.0 }; + match tag { + 0..=STATS_MAX::ver => { + // SAFETY: Since we have checked the tag to be in range, + // accessing the enum view is valid. + let msg = unsafe { msg.msg }; + cls_dev_dbg!(StatsCh, self.dev, "Stats: {:?}\n", msg); + } + _ => { + // SAFETY: The raw view is always valid for all bit patterns. + pr_warn!("Unknown stats message: {:?}\n", unsafe { msg.raw }); + } + } + } + } +} diff --git a/drivers/gpu/drm/asahi/debug.rs b/drivers/gpu/drm/asahi/debug.rs new file mode 100644 index 00000000000000..50628ade5ab8e4 --- /dev/null +++ b/drivers/gpu/drm/asahi/debug.rs @@ -0,0 +1,142 @@ +// SPDX-License-Identifier: GPL-2.0-only OR MIT +#![allow(dead_code)] + +//! Debug enable/disable flags and convenience macros + +#[allow(unused_imports)] +pub(crate) use super::{ + cls_dev_dbg, + cls_pr_debug, + debug, + mod_dev_dbg, + mod_pr_debug, // +}; +use crate::module_parameters; +use core::sync::atomic::{ + AtomicU64, + Ordering, // +}; + +static DEBUG_FLAGS: AtomicU64 = AtomicU64::new(0); + +/// Debug flag bit indices +pub(crate) enum DebugFlags { + // 0-4: Memory-related debug + Mmu = 0, + PgTable = 1, + Alloc = 2, + Gem = 3, + Object = 4, + + // 5-7: Firmware objects and resources + Event = 5, + Buffer = 6, + WorkQueue = 7, + + // 8-13: DRM interface, rendering, compute, GPU globals + Gpu = 8, + File = 9, + Queue = 10, + Render = 11, + Compute = 12, + Errors = 13, + + // 14-15: Misc stats + MemStats = 14, + TVBStats = 15, + + // 16-22: Channels + FwLogCh = 16, + KTraceCh = 17, + StatsCh = 18, + EventCh = 19, + PipeCh = 20, + DeviceControlCh = 21, + FwCtlCh = 22, + + // 32-35: Allocator debugging + FillAllocations = 32, + DebugAllocations = 33, + DetectOverflows = 34, + ForceCPUMaps = 35, + + // 36-: Behavior flags + ConservativeTlbi = 36, + KeepGpuPowered = 37, + WaitForPowerOff = 38, + NoGpuRecovery = 39, + DisableClustering = 40, + + // 48-: Misc + Debug0 = 48, + Debug1 = 49, + Debug2 = 50, + Debug3 = 51, + Debug4 = 52, + Debug5 = 53, + Debug6 = 54, + Debug7 = 55, + + VerboseFaults = 61, + AllowUnknownOverrides = 62, + OopsOnGpuCrash = 63, +} + +/// Update the cached global debug flags from the module parameter +pub(crate) fn update_debug_flags() { + let flags = *module_parameters::debug_flags.value(); + + DEBUG_FLAGS.store(flags, Ordering::Relaxed); +} + +/// Check whether debug is enabled for a given flag +#[inline(always)] +pub(crate) fn debug_enabled(flag: DebugFlags) -> bool { + DEBUG_FLAGS.load(Ordering::Relaxed) & 1 << (flag as usize) != 0 +} + +/// Run some code only if debug is enabled for the calling module +#[macro_export] +macro_rules! debug { + ($($arg:tt)*) => { + if $crate::debug::debug_enabled(DEBUG_CLASS) { + $($arg)* + } + }; +} + +/// pr_info!() if debug is enabled for the calling module +#[macro_export] +macro_rules! mod_pr_debug ( + ($($arg:tt)*) => ( + $crate::debug! { ::kernel::pr_info! ( $($arg)* ); } + ) +); + +/// dev_info!() if debug is enabled for the calling module +#[macro_export] +macro_rules! mod_dev_dbg ( + ($dev:expr, $($arg:tt)*) => ( + $crate::debug! { ::kernel::dev_info! ( $dev.as_ref(), $($arg)* ); } + ) +); + +/// pr_info!() if debug is enabled for a specific module +#[macro_export] +macro_rules! cls_pr_debug ( + ($cls:ident, $($arg:tt)*) => ( + if $crate::debug::debug_enabled($crate::debug::DebugFlags::$cls) { + ::kernel::pr_info! ( $($arg)* ); + } + ) +); + +/// dev_info!() if debug is enabled for a specific module +#[macro_export] +macro_rules! cls_dev_dbg ( + ($cls:ident, $dev:expr, $($arg:tt)*) => ( + if $crate::debug::debug_enabled($crate::debug::DebugFlags::$cls) { + ::kernel::dev_info! ( $dev.as_ref(), $($arg)* ); + } + ) +); diff --git a/drivers/gpu/drm/asahi/driver.rs b/drivers/gpu/drm/asahi/driver.rs new file mode 100644 index 00000000000000..14bfc7cb4253f4 --- /dev/null +++ b/drivers/gpu/drm/asahi/driver.rs @@ -0,0 +1,227 @@ +// SPDX-License-Identifier: GPL-2.0-only OR MIT + +//! Top-level GPU driver implementation. + +use kernel::{ + c_str, + device::Core, + dma::{ + Device, + DmaMask, // + }, + drm, + drm::ioctl, + error::Result, + of, + platform, + prelude::*, + sync::Arc, // +}; + +use crate::{ + debug, + file, + gem::AsahiObject, + gpu, + hw, + regs, // +}; + +use kernel::macros::vtable; +use kernel::types::ARef; + +/// Holds a reference to the top-level `GpuManager` object. +#[pin_data] +pub(crate) struct AsahiData { + #[pin] + pub(crate) gpu: Arc, + pub(crate) pdev: ARef, + pub(crate) resources: regs::Resources, +} + +unsafe impl Send for AsahiData {} +unsafe impl Sync for AsahiData {} + +pub(crate) struct AsahiDriver { + #[expect(unused)] + drm: ARef>, +} + +unsafe impl Send for AsahiDriver {} +unsafe impl Sync for AsahiDriver {} + +/// Convenience type alias for the DRM device type for this driver. +pub(crate) type AsahiDevice = drm::device::Device; +pub(crate) type AsahiDevRef = ARef; + +/// DRM Driver metadata +const INFO: drm::driver::DriverInfo = drm::driver::DriverInfo { + major: 0, + minor: 0, + patchlevel: 0, + name: c_str!("asahi"), + desc: c_str!("Apple AGX Graphics"), +}; + +/// DRM Driver implementation for `AsahiDriver`. +#[vtable] +impl drm::driver::Driver for AsahiDriver { + /// Our `DeviceData` type, reference-counted + type Data = AsahiData; + /// Our `File` type. + type File = file::File; + /// Our `Object` type. + type Object = drm::gem::shmem::Object; + + const INFO: drm::driver::DriverInfo = INFO; + const FEATURES: u32 = drm::driver::FEAT_GEM + | drm::driver::FEAT_RENDER + | drm::driver::FEAT_SYNCOBJ + | drm::driver::FEAT_SYNCOBJ_TIMELINE + | drm::driver::FEAT_GEM_GPUVA; + + kernel::declare_drm_ioctls! { + (ASAHI_GET_PARAMS, drm_asahi_get_params, + ioctl::RENDER_ALLOW, crate::file::File::get_params), + (ASAHI_GET_TIME, drm_asahi_get_time, + ioctl::AUTH | ioctl::RENDER_ALLOW, crate::file::File::get_time), + (ASAHI_VM_CREATE, drm_asahi_vm_create, + ioctl::AUTH | ioctl::RENDER_ALLOW, crate::file::File::vm_create), + (ASAHI_VM_DESTROY, drm_asahi_vm_destroy, + ioctl::AUTH | ioctl::RENDER_ALLOW, crate::file::File::vm_destroy), + (ASAHI_VM_BIND, drm_asahi_vm_bind, + ioctl::AUTH | ioctl::RENDER_ALLOW, crate::file::File::vm_bind), + (ASAHI_GEM_CREATE, drm_asahi_gem_create, + ioctl::AUTH | ioctl::RENDER_ALLOW, crate::file::File::gem_create), + (ASAHI_GEM_MMAP_OFFSET, drm_asahi_gem_mmap_offset, + ioctl::AUTH | ioctl::RENDER_ALLOW, crate::file::File::gem_mmap_offset), + (ASAHI_GEM_BIND_OBJECT, drm_asahi_gem_bind_object, + ioctl::AUTH | ioctl::RENDER_ALLOW, crate::file::File::gem_bind_object), + (ASAHI_QUEUE_CREATE, drm_asahi_queue_create, + ioctl::AUTH | ioctl::RENDER_ALLOW, crate::file::File::queue_create), + (ASAHI_QUEUE_DESTROY, drm_asahi_queue_destroy, + ioctl::AUTH | ioctl::RENDER_ALLOW, crate::file::File::queue_destroy), + (ASAHI_SUBMIT, drm_asahi_submit, + ioctl::AUTH | ioctl::RENDER_ALLOW, crate::file::File::submit), + } +} + +// OF Device ID table.s +kernel::of_device_table!( + OF_TABLE, + MODULE_OF_TABLE, + ::IdInfo, + [ + ( + of::DeviceId::new(c_str!("apple,agx-t8103")), + &hw::t8103::HWCONFIG + ), + ( + of::DeviceId::new(c_str!("apple,agx-t8112")), + &hw::t8112::HWCONFIG + ), + ( + of::DeviceId::new(c_str!("apple,agx-t6000")), + &hw::t600x::HWCONFIG_T6000 + ), + ( + of::DeviceId::new(c_str!("apple,agx-t6001")), + &hw::t600x::HWCONFIG_T6001 + ), + ( + of::DeviceId::new(c_str!("apple,agx-t6002")), + &hw::t600x::HWCONFIG_T6002 + ), + ( + of::DeviceId::new(c_str!("apple,agx-t6020")), + &hw::t602x::HWCONFIG_T6020 + ), + ( + of::DeviceId::new(c_str!("apple,agx-t6021")), + &hw::t602x::HWCONFIG_T6021 + ), + ( + of::DeviceId::new(c_str!("apple,agx-t6022")), + &hw::t602x::HWCONFIG_T6022 + ), + ] +); + +/// Platform Driver implementation for `AsahiDriver`. +impl platform::Driver for AsahiDriver { + type IdInfo = &'static hw::HwConfig; + const OF_ID_TABLE: Option> = Some(&OF_TABLE); + + /// Device probe function. + fn probe( + pdev: &platform::Device, + info: Option<&Self::IdInfo>, + ) -> Result>> { + debug::update_debug_flags(); + + dev_info!(pdev.as_ref(), "Probing...\n"); + + let cfg = info.ok_or(ENODEV)?; + + unsafe { pdev.dma_set_mask_and_coherent(DmaMask::try_new(cfg.uat_oas)?)? }; + + let res = regs::Resources::new(pdev)?; + + // Initialize misc MMIO + res.init_mmio()?; + + // Start the coprocessor CPU, so UAT can initialize the handoff + regs::Resources::start_cpu(pdev)?; + + let fwnode = pdev.as_ref().fwnode().ok_or(EIO)?; + let compat: KVec = fwnode + .property_read_array_vec(c_str!("apple,firmware-compat"), 3)? + .required_by(pdev.as_ref())?; + + let raw_drm = unsafe { drm::device::Device::::new_uninit(pdev.as_ref())? }; + + let drm: AsahiDevRef = unsafe { ARef::from_raw(raw_drm) }; + + let gpu = match (cfg.gpu_gen, cfg.gpu_variant, compat.as_slice()) { + (hw::GpuGen::G13, _, &[12, 3, 0]) => { + gpu::GpuManagerG13V12_3::new(&drm, &res, cfg)? as Arc + } + (hw::GpuGen::G14, hw::GpuVariant::G, &[12, 4, 0]) => { + gpu::GpuManagerG14V12_4::new(&drm, &res, cfg)? as Arc + } + (hw::GpuGen::G13, _, &[13, 5, 0]) => { + gpu::GpuManagerG13V13_5::new(&drm, &res, cfg)? as Arc + } + (hw::GpuGen::G14, hw::GpuVariant::G, &[13, 5, 0]) => { + gpu::GpuManagerG14V13_5::new(&drm, &res, cfg)? as Arc + } + (hw::GpuGen::G14, _, &[13, 5, 0]) => { + gpu::GpuManagerG14XV13_5::new(&drm, &res, cfg)? as Arc + } + _ => { + dev_info!( + pdev.as_ref(), + "Unsupported GPU/firmware combination ({:?}, {:?}, {:?})\n", + cfg.gpu_gen, + cfg.gpu_variant, + compat + ); + return Err(ENODEV); + } + }; + + let data = try_pin_init!(AsahiData { + gpu, + pdev: pdev.into(), + resources: res, + }); + + let drm = unsafe { AsahiDevice::init_data(raw_drm, data)? }; + + (*drm).gpu.init()?; + + drm::driver::Registration::new_foreign_owned(&drm, pdev.as_ref(), 0)?; + + Ok(KBox::new(Self { drm }, GFP_KERNEL)?.into()) + } +} diff --git a/drivers/gpu/drm/asahi/event.rs b/drivers/gpu/drm/asahi/event.rs new file mode 100644 index 00000000000000..edd7d701e665cd --- /dev/null +++ b/drivers/gpu/drm/asahi/event.rs @@ -0,0 +1,257 @@ +// SPDX-License-Identifier: GPL-2.0-only OR MIT + +//! GPU event manager +//! +//! The GPU firmware manages work completion by using event objects (Apple calls them "stamps"), +//! which are monotonically incrementing counters. There are a fixed number of objects, and +//! they are managed with a `SlotAllocator`. +//! +//! This module manages the set of available events and lets users compute expected values. +//! It also manages signaling owners when the GPU firmware reports that an event fired. + +use crate::debug::*; +use crate::fw::types::*; +use crate::{ + gpu, + slotalloc, + workqueue, // +}; +use core::cmp; +use core::sync::atomic::Ordering; +use kernel::prelude::*; +use kernel::sync::Arc; +use kernel::{ + c_str, + static_lock_class, // +}; + +const DEBUG_CLASS: DebugFlags = DebugFlags::Event; + +/// Number of events managed by the firmware. +const NUM_EVENTS: u32 = 128; + +/// Inner data associated with a given event slot. +pub(crate) struct EventInner { + /// CPU pointer to the driver notification event stamp + stamp: *const AtomicU32, + /// GPU pointer to the driver notification event stamp + gpu_stamp: GpuWeakPointer, + /// GPU pointer to the firmware-internal event stamp + gpu_fw_stamp: GpuWeakPointer, +} + +/// SAFETY: The event slots are safe to send across threads. +unsafe impl Send for EventInner {} + +/// Alias for an event token, which allows requesting the same event. +pub(crate) type Token = slotalloc::SlotToken; +/// Alias for an allocated `Event` that has a slot. +pub(crate) type Event = slotalloc::Guard; + +/// Represents a given stamp value for an event. +#[derive(Eq, PartialEq, Copy, Clone, Debug)] +#[repr(transparent)] +pub(crate) struct EventValue(u32); + +impl EventValue { + /// Returns the `EventValue` that succeeds this one. + pub(crate) fn next(&self) -> EventValue { + EventValue(self.0.wrapping_add(0x100)) + } + + /// Increments this `EventValue` in place. + pub(crate) fn increment(&mut self) { + self.0 = self.0.wrapping_add(0x100); + } + + /* Not used + /// Increments this `EventValue` in place by a certain count. + pub(crate) fn add(&mut self, val: u32) { + self.0 = self + .0 + .wrapping_add(val.checked_mul(0x100).expect("Adding too many events")); + } + */ + + /// Increments this `EventValue` in place by a certain count. + pub(crate) fn sub(&mut self, val: u32) { + self.0 = self + .0 + .wrapping_sub(val.checked_mul(0x100).expect("Subtracting too many events")); + } + + /// Computes the delta between this event and another event. + pub(crate) fn delta(&self, other: &EventValue) -> i32 { + (self.0.wrapping_sub(other.0) as i32) >> 8 + } +} + +impl PartialOrd for EventValue { + fn partial_cmp(&self, other: &Self) -> Option { + Some(self.cmp(other)) + } +} + +impl Ord for EventValue { + fn cmp(&self, other: &Self) -> cmp::Ordering { + self.delta(other).cmp(&0) + } +} + +impl EventInner { + /// Returns the GPU pointer to the driver notification stamp + pub(crate) fn stamp_pointer(&self) -> GpuWeakPointer { + self.gpu_stamp + } + + /// Returns the GPU pointer to the firmware internal stamp + pub(crate) fn fw_stamp_pointer(&self) -> GpuWeakPointer { + self.gpu_fw_stamp + } + + /// Fetches the current event value from shared memory + pub(crate) fn current(&self) -> EventValue { + // SAFETY: The pointer is always valid as constructed in + // EventManager below, and outside users cannot construct + // new EventInners, nor move or copy them, and Guards as + // returned by the SlotAllocator hold a reference to the + // SlotAllocator containing the EventManagerInner, which + // keeps the GpuObject the stamp is contained within alive. + EventValue(unsafe { &*self.stamp }.load(Ordering::Acquire)) + } +} + +impl slotalloc::SlotItem for EventInner { + type Data = EventManagerInner; + + fn release(&mut self, data: &mut Self::Data, slot: u32) { + mod_pr_debug!("EventManager: Released slot {}\n", slot); + data.owners[slot as usize] = None; + } +} + +/// Inner data for the event manager, to be protected by the SlotAllocator lock. +pub(crate) struct EventManagerInner { + stamps: GpuArray, + fw_stamps: GpuArray, + // Note: Use dyn to avoid having to version this entire module. + owners: KVec>>, +} + +/// Top-level EventManager object. +pub(crate) struct EventManager { + alloc: slotalloc::SlotAllocator, +} + +impl EventManager { + /// Create a new EventManager. + #[inline(never)] + pub(crate) fn new(alloc: &mut gpu::KernelAllocators) -> Result { + let mut owners = KVec::new(); + for _i in 0..(NUM_EVENTS as usize) { + owners.push(None, GFP_KERNEL)?; + } + let inner = EventManagerInner { + stamps: alloc.shared.array_empty(NUM_EVENTS as usize)?, + fw_stamps: alloc.private.array_empty(NUM_EVENTS as usize)?, + owners, + }; + + for slot in 0..NUM_EVENTS { + inner.stamps[slot as usize] + .0 + .store(slot << 24, Ordering::Relaxed); + } + + Ok(EventManager { + alloc: slotalloc::SlotAllocator::new( + NUM_EVENTS, + inner, + |inner: &mut EventManagerInner, slot| { + Some(EventInner { + stamp: &inner.stamps[slot as usize].0, + gpu_stamp: inner.stamps.weak_item_pointer(slot as usize), + gpu_fw_stamp: inner.fw_stamps.weak_item_pointer(slot as usize), + }) + }, + c_str!("EventManager::SlotAllocator"), + static_lock_class!(), + static_lock_class!(), + )?, + }) + } + + /// Gets a free `Event`, optionally trying to reuse the last one allocated by this caller. + pub(crate) fn get( + &self, + token: Option, + owner: Arc, + ) -> Result { + let ev = self.alloc.get_inner(token, |inner, ev| { + mod_pr_debug!( + "EventManager: Registered owner {:p} on slot {}\n", + &*owner, + ev.slot() + ); + inner.owners[ev.slot() as usize] = Some(owner); + Ok(()) + })?; + Ok(ev) + } + + /// Signals an event by slot, indicating completion (of one or more commands). + pub(crate) fn signal(&self, slot: u32) { + match self + .alloc + .with_inner(|inner| inner.owners[slot as usize].as_ref().cloned()) + { + Some(owner) => { + owner.signal(); + } + None => { + mod_pr_debug!("EventManager: Received event for empty slot {}\n", slot); + } + } + } + + /// Marks the owner of an event as having lost its work due to a GPU error. + pub(crate) fn mark_error(&self, slot: u32, wait_value: u32, error: workqueue::WorkError) { + match self + .alloc + .with_inner(|inner| inner.owners[slot as usize].as_ref().cloned()) + { + Some(owner) => { + owner.mark_error(EventValue(wait_value), error); + } + None => { + pr_err!("Received error for empty slot {}\n", slot); + } + } + } + + /// Returns a reference to the workqueue owning an event. + pub(crate) fn get_owner( + &self, + slot: u32, + ) -> Option> { + self.alloc + .with_inner(|inner| inner.owners[slot as usize].as_ref().cloned()) + } + + /// Fail all commands, used when the GPU crashes. + pub(crate) fn fail_all(&self, error: workqueue::WorkError) { + let mut owners: KVec> = KVec::new(); + + self.alloc.with_inner(|inner| { + for wq in inner.owners.iter().filter_map(|o| o.as_ref()).cloned() { + if owners.push(wq, GFP_KERNEL).is_err() { + pr_err!("Failed to signal failure to WorkQueue\n"); + } + } + }); + + for wq in owners { + wq.fail_all(error); + } + } +} diff --git a/drivers/gpu/drm/asahi/file.rs b/drivers/gpu/drm/asahi/file.rs new file mode 100644 index 00000000000000..3a5460c091a728 --- /dev/null +++ b/drivers/gpu/drm/asahi/file.rs @@ -0,0 +1,1088 @@ +// SPDX-License-Identifier: GPL-2.0-only OR MIT +#![allow(clippy::unusual_byte_groupings)] + +//! File implementation, which represents a single DRM client. +//! +//! This is in charge of managing the resources associated with one GPU client, including an +//! arbitrary number of submission queues and Vm objects, and reporting hardware/driver +//! information to userspace and accepting submissions. + +use crate::debug::*; +use crate::driver::AsahiDevice; +use crate::{ + alloc, + buffer, + driver, + gem, + mmu, + module_parameters, + queue, + util::{ + align, + align_down, + gcd, + AnyBitPattern, + RangeExt, + Reader, // + }, // +}; +use core::mem::MaybeUninit; +use core::ops::Deref; +use core::ops::Range; +use core::ptr::addr_of_mut; +use kernel::bindings; +use kernel::dma_fence::RawDmaFence; +use kernel::drm::gem::BaseObject; +use kernel::error::code::*; +use kernel::new_mutex; +use kernel::prelude::*; +use kernel::sync::{ + Arc, + Mutex, // +}; +use kernel::time::NSEC_PER_SEC; +use kernel::uaccess::{ + UserPtr, + UserSlice, // +}; +use kernel::{ + dma_fence, + drm, + uapi, + xarray, // +}; + +const DEBUG_CLASS: DebugFlags = DebugFlags::File; + +pub(crate) const MAX_COMMANDS_PER_SUBMISSION: u32 = 64; + +/// A client instance of an `mmu::Vm` address space. +struct Vm { + ualloc: Arc>, + ualloc_priv: Arc>, + vm: mmu::Vm, + kernel_range: Range, + _dummy_mapping: mmu::KernelMapping, +} + +impl Drop for Vm { + fn drop(&mut self) { + // When the user Vm is dropped, unmap everything in the user range + let left_range = VM_USER_RANGE.start..self.kernel_range.start; + let right_range = self.kernel_range.end..VM_USER_RANGE.end; + + if !left_range.is_empty() + && self + .vm + .unmap_range(left_range.start, left_range.range()) + .is_err() + { + pr_err!("Vm::Drop: vm.unmap_range() failed\n"); + } + if !right_range.is_empty() + && self + .vm + .unmap_range(right_range.start, right_range.range()) + .is_err() + { + pr_err!("Vm::Drop: vm.unmap_range() failed\n"); + } + } +} + +/// Sync object from userspace. +pub(crate) struct SyncItem { + pub(crate) syncobj: drm::syncobj::SyncObj, + pub(crate) fence: Option, + pub(crate) chain_fence: Option, + pub(crate) timeline_value: u64, +} + +impl SyncItem { + fn parse_one(file: &DrmFile, data: uapi::drm_asahi_sync, out: bool) -> Result { + match data.sync_type { + uapi::drm_asahi_sync_type_DRM_ASAHI_SYNC_SYNCOBJ => { + if data.timeline_value != 0 { + cls_pr_debug!(Errors, "Non-timeline sync object with a nonzero value\n"); + return Err(EINVAL); + } + let syncobj = drm::syncobj::SyncObj::lookup_handle(file, data.handle)?; + + Ok(SyncItem { + fence: if out { + None + } else { + Some(syncobj.fence_get().ok_or_else(|| { + cls_pr_debug!(Errors, "Failed to get fence from sync object\n"); + EINVAL + })?) + }, + syncobj, + chain_fence: None, + timeline_value: data.timeline_value, + }) + } + uapi::drm_asahi_sync_type_DRM_ASAHI_SYNC_TIMELINE_SYNCOBJ => { + let syncobj = drm::syncobj::SyncObj::lookup_handle(file, data.handle)?; + let fence = if out { + None + } else { + syncobj + .fence_get() + .ok_or_else(|| { + cls_pr_debug!( + Errors, + "Failed to get fence from timeline sync object\n" + ); + EINVAL + })? + .chain_find_seqno(data.timeline_value)? + }; + + Ok(SyncItem { + fence, + syncobj, + chain_fence: if out { + Some(dma_fence::FenceChain::new()?) + } else { + None + }, + timeline_value: data.timeline_value, + }) + } + _ => { + cls_pr_debug!(Errors, "Invalid sync type {}\n", data.sync_type); + Err(EINVAL) + } + } + } + + fn parse_array( + file: &DrmFile, + ptr: u64, + in_count: u32, + out_count: u32, + ) -> Result> { + let count = in_count + out_count; + let mut vec = KVec::with_capacity(count as usize, GFP_KERNEL)?; + + const STRIDE: usize = core::mem::size_of::(); + let size = STRIDE * count as usize; + + // SAFETY: We only read this once, so there are no TOCTOU issues. + let mut reader = UserSlice::new(UserPtr::from_addr(ptr as _), size).reader(); + + for i in 0..count { + let mut sync: MaybeUninit = MaybeUninit::uninit(); + + // SAFETY: The size of `sync` is STRIDE + reader.read_raw(unsafe { + core::slice::from_raw_parts_mut(sync.as_mut_ptr() as *mut MaybeUninit, STRIDE) + })?; + + // SAFETY: All bit patterns in the struct are valid + let sync = unsafe { sync.assume_init() }; + + vec.push(SyncItem::parse_one(file, sync, i >= in_count)?, GFP_KERNEL)?; + } + + Ok(vec) + } +} + +#[derive(Clone)] +pub(crate) enum Object { + TimestampBuffer(Arc), +} + +/// State associated with a client. +// #[pin_data] +pub(crate) struct File { + id: u64, + // #[pin] + vms: xarray::XArray>, + // #[pin] + queues: xarray::XArray>>>, + // #[pin] + objects: xarray::XArray>, +} + +/// Convenience type alias for our DRM `File` type. +pub(crate) type DrmFile = drm::File; + +/// Available VM range for the user +const VM_USER_RANGE: Range = mmu::IOVA_USER_USABLE_RANGE; + +/// Minimum reserved AS for kernel mappings +const VM_KERNEL_MIN_SIZE: u64 = 0x20000000; + +impl drm::file::DriverFile for File { + type Driver = driver::AsahiDriver; + + /// Create a new `File` instance for a fresh client. + fn open(device: &AsahiDevice) -> Result>> { + debug::update_debug_flags(); + + let gpu = &device.gpu; + let id = gpu.ids().file.next(); + + mod_dev_dbg!(device, "[File {}]: DRM device opened\n", id); + Ok(KBox::pin_init(File::new(id), GFP_KERNEL)?) + } + + fn as_raw(&self) -> *mut bindings::drm_file { + todo!() + } +} + +// SAFETY: All bit patterns are valid by construction. +unsafe impl AnyBitPattern for uapi::drm_asahi_gem_bind_op {} + +impl File { + fn new(id: u64) -> impl PinInit { + unsafe { + pin_init::pin_init_from_closure(move |slot: *mut Self| { + let raw_vms = addr_of_mut!((*slot).vms); + xarray::XArray::>::new(xarray::AllocKind::Alloc1) + .__pinned_init(raw_vms)?; + + let raw_queues = addr_of_mut!((*slot).queues); + xarray::XArray::>>>::new( + xarray::AllocKind::Alloc1, + ) + .__pinned_init(raw_queues)?; + + let raw_objects = addr_of_mut!((*slot).objects); + xarray::XArray::>::new(xarray::AllocKind::Alloc1) + .__pinned_init(raw_objects)?; + + (*slot).id = id; + Ok(()) + }) + } + } + + fn vms(self: Pin<&Self>) -> Pin<&xarray::XArray>> { + // SAFETY: Structural pinned projection for vms. + // We never move out of this field. + unsafe { self.map_unchecked(|s| &s.vms) } + } + + #[allow(clippy::type_complexity)] + fn queues(self: Pin<&Self>) -> Pin<&xarray::XArray>>>> { + // SAFETY: Structural pinned projection for queues. + // We never move out of this field. + unsafe { self.map_unchecked(|s| &s.queues) } + } + + fn objects(self: Pin<&Self>) -> Pin<&xarray::XArray>> { + // SAFETY: Structural pinned projection for objects. + // We never move out of this field. + unsafe { self.map_unchecked(|s| &s.objects) } + } + + /// IOCTL: get_param: Get a driver parameter value. + pub(crate) fn get_params( + device: &AsahiDevice, + data: &uapi::drm_asahi_get_params, + file: &DrmFile, + ) -> Result { + mod_dev_dbg!(device, "[File {}]: IOCTL: get_params\n", file.inner().id); + + let gpu = &device.gpu; + + if data.param_group != 0 || data.pad != 0 { + cls_pr_debug!(Errors, "get_params: Invalid arguments\n"); + return Err(EINVAL); + } + + if gpu.is_crashed() { + return Err(ENODEV); + } + + let mut params = uapi::drm_asahi_params_global { + features: 0, + + gpu_generation: gpu.get_dyncfg().id.gpu_gen as u32, + gpu_variant: gpu.get_dyncfg().id.gpu_variant as u32, + gpu_revision: gpu.get_dyncfg().id.gpu_rev as u32, + chip_id: gpu.get_cfg().chip_id, + + num_dies: gpu.get_cfg().num_dies, + num_clusters_total: gpu.get_dyncfg().id.num_clusters, + num_cores_per_cluster: gpu.get_dyncfg().id.num_cores, + core_masks: [0; uapi::DRM_ASAHI_MAX_CLUSTERS as usize], + + vm_start: VM_USER_RANGE.start, + vm_end: VM_USER_RANGE.end, + vm_kernel_min_size: VM_KERNEL_MIN_SIZE, + + max_commands_per_submission: MAX_COMMANDS_PER_SUBMISSION, + max_attachments: crate::microseq::MAX_ATTACHMENTS as u32, + max_frequency_khz: gpu.get_dyncfg().pwr.max_frequency_khz(), + + command_timestamp_frequency_hz: 1_000_000_000, // User timestamps always in nanoseconds + }; + + for (i, mask) in gpu.get_dyncfg().id.core_masks.iter().enumerate() { + *(params.core_masks.get_mut(i).ok_or(EIO)?) = (*mask).into(); + } + + if *module_parameters::fault_control.value() == 0xb { + params.features |= uapi::drm_asahi_feature_DRM_ASAHI_FEATURE_SOFT_FAULTS as u64; + } + + let size = core::mem::size_of::().min(data.size.try_into()?); + + // SAFETY: We only write to this userptr once, so there are no TOCTOU issues. + let mut params_writer = + UserSlice::new(UserPtr::from_addr(data.pointer as _), size).writer(); + + // SAFETY: `size` is at most the sizeof of `params` + params_writer.write_slice(unsafe { + core::slice::from_raw_parts(¶ms as *const _ as *const u8, size) + })?; + + Ok(0) + } + + /// IOCTL: vm_create: Create a new `Vm`. + pub(crate) fn vm_create( + device: &AsahiDevice, + data: &mut uapi::drm_asahi_vm_create, + file: &DrmFile, + ) -> Result { + let kernel_range = data.kernel_start..data.kernel_end; + + // Validate requested kernel range + if !VM_USER_RANGE.is_superset(kernel_range.clone()) + || kernel_range.range() < VM_KERNEL_MIN_SIZE + || kernel_range.start & (mmu::UAT_PGMSK as u64) != 0 + || kernel_range.end & (mmu::UAT_PGMSK as u64) != 0 + { + cls_pr_debug!(Errors, "vm_create: Invalid kernel range\n"); + return Err(EINVAL); + } + + // Align to buffer::PAGE_SIZE so the allocators are happy + let kernel_range = align(kernel_range.start, buffer::PAGE_SIZE as u64) + ..align_down(kernel_range.end, buffer::PAGE_SIZE as u64); + + let kernel_half_size = align_down(kernel_range.range() >> 1, buffer::PAGE_SIZE as u64); + let kernel_gpu_range = kernel_range.start..(kernel_range.start + kernel_half_size); + let kernel_gpufw_range = kernel_gpu_range.end..kernel_range.end; + + let gpu = &device.gpu; + let file_id = file.inner().id; + let vm = gpu.new_vm(kernel_range.clone())?; + + let vm_xa = file.inner().vms(); + let resv = vm_xa.lock().reserve_limit(1..=u32::MAX, GFP_KERNEL)?; + let id: u32 = resv.index().try_into()?; + + mod_dev_dbg!(device, "[File {} VM {}]: VM Create\n", file_id, id); + mod_dev_dbg!( + device, + "[File {} VM {}]: Creating allocators\n", + file_id, + id + ); + let ualloc = Arc::pin_init( + new_mutex!(alloc::DefaultAllocator::new( + device, + &vm, + kernel_gpu_range, + buffer::PAGE_SIZE, + mmu::PROT_GPU_SHARED_RW, + 512 * 1024, + true, + fmt!("File {} VM {} GPU Shared", file_id, id), + false, + )?), + GFP_KERNEL, + )?; + let ualloc_priv = Arc::pin_init( + new_mutex!(alloc::DefaultAllocator::new( + device, + &vm, + kernel_gpufw_range, + buffer::PAGE_SIZE, + mmu::PROT_GPU_FW_PRIV_RW, + 64 * 1024, + true, + fmt!("File {} VM {} GPU FW Private", file_id, id), + false, + )?), + GFP_KERNEL, + )?; + + mod_dev_dbg!( + device, + "[File {} VM {}]: Creating dummy object\n", + file_id, + id + ); + let mut dummy_obj = gem::new_kernel_object(device, 0x4000)?; + dummy_obj.vmap()?.memset(0); + let dummy_mapping = + dummy_obj.map_at(&vm, mmu::IOVA_UNK_PAGE, mmu::PROT_GPU_SHARED_RW, true)?; + + mod_dev_dbg!(device, "[File {} VM {}]: VM created\n", file_id, id); + resv.fill(KBox::new( + Vm { + ualloc, + ualloc_priv, + vm, + kernel_range, + _dummy_mapping: dummy_mapping, + }, + GFP_KERNEL, + )?)?; + + data.vm_id = id; + + Ok(0) + } + + /// IOCTL: vm_destroy: Destroy a `Vm`. + pub(crate) fn vm_destroy( + _device: &AsahiDevice, + data: &mut uapi::drm_asahi_vm_destroy, + file: &DrmFile, + ) -> Result { + let vm = file.inner().vms().remove(data.vm_id as usize); + if vm.is_none() { + Err(ENOENT) + } else { + Ok(0) + } + } + + /// IOCTL: gem_create: Create a new GEM object. + pub(crate) fn gem_create( + device: &AsahiDevice, + data: &mut uapi::drm_asahi_gem_create, + file: &DrmFile, + ) -> Result { + mod_dev_dbg!( + device, + "[File {}]: IOCTL: gem_create size={:#x?}\n", + file.inner().id, + data.size + ); + + if (data.flags + & !(uapi::drm_asahi_gem_flags_DRM_ASAHI_GEM_WRITEBACK + | uapi::drm_asahi_gem_flags_DRM_ASAHI_GEM_VM_PRIVATE)) + != 0 + || (data.flags & uapi::drm_asahi_gem_flags_DRM_ASAHI_GEM_VM_PRIVATE == 0 + && data.vm_id != 0) + { + cls_pr_debug!(Errors, "gem_create: Invalid arguments\n"); + return Err(EINVAL); + } + + let resv_gem; + let resv_obj = if data.flags & uapi::drm_asahi_gem_flags_DRM_ASAHI_GEM_VM_PRIVATE != 0 { + resv_gem = file + .inner() + .vms() + .lock() + .get(data.vm_id.try_into()?) + .ok_or(ENOENT)? + .vm + .get_resv_obj(); + Some(resv_gem.deref()) + } else { + None + }; + + let gem = gem::new_object(device, data.size.try_into()?, data.flags, resv_obj)?; + + let handle = gem.create_handle(file)?; + data.handle = handle; + + mod_dev_dbg!( + device, + "[File {}]: IOCTL: gem_create size={:#x} handle={:#x?}\n", + file.inner().id, + data.size, + data.handle + ); + + Ok(0) + } + + /// IOCTL: gem_mmap_offset: Assign an mmap offset to a GEM object. + pub(crate) fn gem_mmap_offset( + device: &AsahiDevice, + data: &mut uapi::drm_asahi_gem_mmap_offset, + file: &DrmFile, + ) -> Result { + mod_dev_dbg!( + device, + "[File {}]: IOCTL: gem_mmap_offset handle={:#x?}\n", + file.inner().id, + data.handle + ); + + if data.flags != 0 { + cls_pr_debug!(Errors, "gem_mmap_offset: Unexpected flags\n"); + return Err(EINVAL); + } + + let gem = gem::Object::lookup_handle(file, data.handle)?; + data.offset = gem.create_mmap_offset()?; + Ok(0) + } + + /// IOCTL: vm_bind: Map or unmap memory into a Vm. + pub(crate) fn vm_bind( + device: &AsahiDevice, + data: &uapi::drm_asahi_vm_bind, + file: &DrmFile, + ) -> Result { + mod_dev_dbg!( + device, + "[File {} VM {}]: IOCTL: vm_bind\n", + file.inner().id, + data.vm_id, + ); + + if data.stride == 0 || data.pad != 0 { + cls_pr_debug!(Errors, "vm_bind: Unexpected headers\n"); + return Err(EINVAL); + } + + let vm_id = data.vm_id.try_into()?; + + let mut vec = KVec::new(); + let size = (data.stride * data.num_binds) as usize; + let reader = UserSlice::new(UserPtr::from_addr(data.userptr as _), size).reader(); + reader.read_all(&mut vec, GFP_KERNEL)?; + let mut reader = Reader::new(&vec); + + for _i in 0..data.num_binds { + let bind: uapi::drm_asahi_gem_bind_op = reader.read_up_to(data.stride as usize)?; + Self::do_gem_bind_unbind(vm_id, &bind, file)?; + } + + Ok(0) + } + + pub(crate) fn do_gem_bind_unbind( + vm_id: usize, + data: &uapi::drm_asahi_gem_bind_op, + file: &DrmFile, + ) -> Result { + if (data.flags & uapi::drm_asahi_bind_flags_DRM_ASAHI_BIND_UNBIND) != 0 { + Self::do_gem_unbind(vm_id, data, file) + } else { + Self::do_gem_bind(vm_id, data, file) + } + } + + pub(crate) fn do_gem_bind( + vm_id: usize, + data: &uapi::drm_asahi_gem_bind_op, + file: &DrmFile, + ) -> Result { + if (data.addr | data.range | data.offset) as usize & mmu::UAT_PGMSK != 0 { + cls_pr_debug!( + Errors, + "gem_bind: Addr/range/offset not page aligned: {:#x} {:#x}\n", + data.addr, + data.range + ); + return Err(EINVAL); // Must be page aligned + } + + if (data.flags + & !(uapi::drm_asahi_bind_flags_DRM_ASAHI_BIND_READ + | uapi::drm_asahi_bind_flags_DRM_ASAHI_BIND_WRITE + | uapi::drm_asahi_bind_flags_DRM_ASAHI_BIND_SINGLE_PAGE)) + != 0 + { + cls_pr_debug!(Errors, "gem_bind: Invalid flags {:#x}\n", data.flags); + return Err(EINVAL); + } + + let single_page = data.flags & uapi::drm_asahi_bind_flags_DRM_ASAHI_BIND_SINGLE_PAGE != 0; + + let bo = gem::Object::lookup_handle(file, data.handle)?; + + let start = data.addr; + let end = data.addr.checked_add(data.range).ok_or(EINVAL)?; + let range = start..end; + + let bo_accessed_size = if single_page { + mmu::UAT_PGMSK as u64 + } else { + data.range + }; + let end_off = data.offset.checked_add(bo_accessed_size).ok_or(EINVAL)?; + if end_off as usize > bo.size() { + return Err(EINVAL); + } + + if !VM_USER_RANGE.is_superset(range.clone()) { + cls_pr_debug!( + Errors, + "gem_bind: Invalid map range {:#x}..{:#x} (not contained in user range)\n", + start, + end + ); + return Err(EINVAL); // Invalid map range + } + + let prot = if data.flags & uapi::drm_asahi_bind_flags_DRM_ASAHI_BIND_READ != 0 { + if data.flags & uapi::drm_asahi_bind_flags_DRM_ASAHI_BIND_WRITE != 0 { + mmu::PROT_GPU_SHARED_RW + } else { + mmu::PROT_GPU_SHARED_RO + } + } else if data.flags & uapi::drm_asahi_bind_flags_DRM_ASAHI_BIND_WRITE != 0 { + mmu::PROT_GPU_SHARED_WO + } else { + cls_pr_debug!( + Errors, + "gem_bind: Must specify read or write (flags: {:#x})\n", + data.flags + ); + return Err(EINVAL); // Must specify one of DRM_ASAHI_BIND_{READ,WRITE} + }; + + let vms_xa = file.inner().vms(); + let guard = vms_xa.lock(); + let guarded_vm = guard.get(vm_id).ok_or(ENOENT)?; + + // Clone it immediately so we aren't holding the XArray lock + let vm = guarded_vm.vm.clone(); + let kernel_range = guarded_vm.kernel_range.clone(); + let _ = guarded_vm; + core::mem::drop(guard); + + if kernel_range.overlaps(range) { + cls_pr_debug!( + Errors, + "gem_bind: Invalid map range {:#x}..{:#x} (intrudes in kernel range)\n", + start, + end + ); + return Err(EINVAL); + } + + vm.bind_object(&bo, data.addr, data.range, data.offset, prot, single_page)?; + + Ok(0) + } + + pub(crate) fn do_gem_unbind( + vm_id: usize, + data: &uapi::drm_asahi_gem_bind_op, + file: &DrmFile, + ) -> Result { + if data.offset != 0 + || data.flags != uapi::drm_asahi_bind_flags_DRM_ASAHI_BIND_UNBIND + || data.handle != 0 + { + cls_pr_debug!(Errors, "gem_unbind: offset/flags/handle not zero\n"); + return Err(EINVAL); + } + + if (data.addr | data.range) as usize & mmu::UAT_PGMSK != 0 { + cls_pr_debug!( + Errors, + "gem_bind: Addr/range/offset not page aligned: {:#x} {:#x}\n", + data.addr, + data.range + ); + return Err(EINVAL); // Must be page aligned + } + + let start = data.addr; + let end = data.addr.checked_add(data.range).ok_or(EINVAL)?; + let range = start..end; + + if !VM_USER_RANGE.is_superset(range.clone()) { + cls_pr_debug!( + Errors, + "gem_bind: Invalid unmap range {:#x}..{:#x} (not contained in user range)\n", + start, + end + ); + return Err(EINVAL); // Invalid map range + } + + let vms_xa = file.inner().vms(); + let guard = vms_xa.lock(); + let guarded_vm = guard.get(vm_id).ok_or(ENOENT)?; + + // Clone it immediately so we aren't holding the XArray lock + let vm = guarded_vm.vm.clone(); + let kernel_range = guarded_vm.kernel_range.clone(); + let _ = guarded_vm; + core::mem::drop(guard); + + if kernel_range.overlaps(range.clone()) { + cls_pr_debug!( + Errors, + "gem_bind: Invalid unmap range {:#x}..{:#x} (intrudes in kernel range)\n", + start, + end + ); + return Err(EINVAL); + } + + vm.unmap_range(range.start, range.range())?; + + Ok(0) + } + + pub(crate) fn unbind_gem_object(file: &DrmFile, bo: &gem::Object) -> Result { + // TODO: use iter() + let mut index = 0; + loop { + let vms = file.inner().vms(); + let item = vms.find(index, usize::MAX); + match item { + Some((idx, file_vm)) => { + // Clone since we can't hold the xarray spinlock while + // calling drop_mappings() + let vm = file_vm.borrow().vm.clone(); + core::mem::drop(file_vm); + vm.drop_mappings(bo)?; + if idx == usize::MAX { + break; + } + index = idx + 1; + } + None => break, + } + } + Ok(()) + } + + /// IOCTL: gem_bind_object: Map or unmap a GEM object as a special object. + pub(crate) fn gem_bind_object( + device: &AsahiDevice, + data: &mut uapi::drm_asahi_gem_bind_object, + file: &DrmFile, + ) -> Result { + mod_dev_dbg!( + device, + "[File {} VM {}]: IOCTL: gem_bind_object op={:?} handle={:#x?} flags={:#x?} {:#x?}:{:#x?} object_handle={:#x?}\n", + file.inner().id, + data.vm_id, + data.op, + data.handle, + data.flags, + data.offset, + data.range, + data.object_handle + ); + + if data.pad != 0 { + cls_pr_debug!(Errors, "gem_bind_object: Unexpected pad\n"); + return Err(EINVAL); + } + + if data.vm_id != 0 { + cls_pr_debug!(Errors, "gem_bind_object: Unexpected vm_id\n"); + return Err(EINVAL); + } + + match data.op { + uapi::drm_asahi_bind_object_op_DRM_ASAHI_BIND_OBJECT_OP_BIND => { + Self::do_gem_bind_object(device, data, file) + } + uapi::drm_asahi_bind_object_op_DRM_ASAHI_BIND_OBJECT_OP_UNBIND => { + Self::do_gem_unbind_object(device, data, file) + } + _ => { + cls_pr_debug!(Errors, "gem_bind_object: Invalid op {}\n", data.op); + Err(EINVAL) + } + } + } + + pub(crate) fn do_gem_bind_object( + device: &AsahiDevice, + data: &mut uapi::drm_asahi_gem_bind_object, + file: &DrmFile, + ) -> Result { + if (data.range | data.offset) as usize & mmu::UAT_PGMSK != 0 { + cls_pr_debug!( + Errors, + "gem_bind_object: Range/offset not page aligned: {:#x} {:#x}\n", + data.range, + data.offset + ); + return Err(EINVAL); // Must be page aligned + } + + if data.flags != uapi::drm_asahi_bind_object_flags_DRM_ASAHI_BIND_OBJECT_USAGE_TIMESTAMPS { + cls_pr_debug!(Errors, "gem_bind_object: Invalid flags {:#x}\n", data.flags); + return Err(EINVAL); + } + + let offset = data.offset.try_into()?; + let end_offset = data + .offset + .checked_add(data.range) + .ok_or(EINVAL)? + .try_into()?; + let bo = gem::ObjectRef::new(gem::Object::lookup_handle(file, data.handle)?); + + let mapping = Arc::new( + device.gpu.map_timestamp_buffer(bo, offset..end_offset)?, + GFP_KERNEL, + )?; + let obj = KBox::new(Object::TimestampBuffer(mapping), GFP_KERNEL)?; + let handle = file + .inner() + .objects() + .lock() + .insert_limit(1..=u32::MAX, obj, GFP_KERNEL)? as u64; + + data.object_handle = handle as u32; + Ok(0) + } + + pub(crate) fn do_gem_unbind_object( + _device: &AsahiDevice, + data: &mut uapi::drm_asahi_gem_bind_object, + file: &DrmFile, + ) -> Result { + if data.range != 0 || data.offset != 0 { + cls_pr_debug!( + Errors, + "gem_unbind_object: Range/offset not zero: {:#x} {:#x}\n", + data.range, + data.offset + ); + return Err(EINVAL); + } + + if data.flags != 0 { + cls_pr_debug!( + Errors, + "gem_unbind_object: Invalid flags {:#x}\n", + data.flags + ); + return Err(EINVAL); + } + + if data.handle != 0 { + cls_pr_debug!( + Errors, + "gem_unbind_object: Invalid handle {}\n", + data.handle + ); + return Err(EINVAL); + } + + let object = file.inner().objects().remove(data.object_handle as usize); + if object.is_none() { + Err(ENOENT) + } else { + Ok(0) + } + } + + /// IOCTL: queue_create: Create a new command submission queue of a given type. + pub(crate) fn queue_create( + device: &AsahiDevice, + data: &mut uapi::drm_asahi_queue_create, + file: &DrmFile, + ) -> Result { + let file_id = file.inner().id; + + mod_dev_dbg!( + device, + "[File {} VM {}]: Creating queue prio={:?} flags={:#x?}\n", + file_id, + data.vm_id, + data.priority, + data.flags, + ); + + if data.flags != 0 || data.priority > uapi::drm_asahi_priority_DRM_ASAHI_PRIORITY_REALTIME { + cls_pr_debug!(Errors, "queue_create: Invalid arguments\n"); + return Err(EINVAL); + } + + // TODO: Allow with CAP_SYS_NICE + if data.priority >= uapi::drm_asahi_priority_DRM_ASAHI_PRIORITY_HIGH { + cls_pr_debug!(Errors, "queue_create: Invalid priority\n"); + return Err(EINVAL); + } + + let queues_xa = file.inner().queues(); + let resv = queues_xa.lock().reserve_limit(1..=u32::MAX, GFP_KERNEL)?; + let vms_xa = file.inner().vms(); + let guard = vms_xa.lock(); + let file_vm = guard.get(data.vm_id.try_into()?).ok_or(ENOENT)?; + let vm = file_vm.vm.clone(); + let ualloc = file_vm.ualloc.clone(); + let ualloc_priv = file_vm.ualloc_priv.clone(); + // Drop the vms lock eagerly + let _ = file_vm; + core::mem::drop(guard); + + let queue = device.gpu.new_queue( + vm, + ualloc, + ualloc_priv, + // TODO: Plumb deeper the enum + uapi::drm_asahi_priority_DRM_ASAHI_PRIORITY_REALTIME - data.priority, + data.usc_exec_base, + )?; + + data.queue_id = resv.index().try_into()?; + resv.fill(Arc::pin_init(new_mutex!(queue), GFP_KERNEL)?)?; + + Ok(0) + } + + /// IOCTL: queue_destroy: Destroy a command submission queue. + pub(crate) fn queue_destroy( + _device: &AsahiDevice, + data: &mut uapi::drm_asahi_queue_destroy, + file: &DrmFile, + ) -> Result { + // grab the queue so the xarray spinlock is dropped first + let queue = file.inner().queues().remove(data.queue_id as usize); + if queue.is_none() { + Err(ENOENT) + } else { + Ok(0) + } + } + + /// IOCTL: submit: Submit GPU work to a command submission queue. + pub(crate) fn submit( + device: &AsahiDevice, + data: &mut uapi::drm_asahi_submit, + file: &DrmFile, + ) -> Result { + debug::update_debug_flags(); + + if data.flags != 0 || data.pad != 0 { + cls_pr_debug!(Errors, "submit: Invalid arguments\n"); + return Err(EINVAL); + } + + let gpu = &device.gpu; + gpu.update_globals(); + + // Upgrade to Arc to drop the XArray lock early + let queue: Arc>> = file + .inner() + .queues() + .lock() + .get(data.queue_id.try_into()?) + .ok_or(ENOENT)? + .into(); + + let id = gpu.ids().submission.next(); + mod_dev_dbg!( + device, + "[File {} Queue {}]: IOCTL: submit (submission ID: {})\n", + file.inner().id, + data.queue_id, + id + ); + + mod_dev_dbg!( + device, + "[File {} Queue {}]: IOCTL: submit({}): Parsing syncs\n", + file.inner().id, + data.queue_id, + id + ); + let syncs = + SyncItem::parse_array(file, data.syncs, data.in_sync_count, data.out_sync_count)?; + + mod_dev_dbg!( + device, + "[File {} Queue {}]: IOCTL: submit({}): Parsing commands\n", + file.inner().id, + data.queue_id, + id + ); + + let mut vec = KVec::new(); + + // Copy the command buffer into the kernel. Because we need to iterate + // the command buffer twice, we do this in one big copy_from_user to + // avoid TOCTOU issues. + let reader = UserSlice::new( + UserPtr::from_addr(data.cmdbuf as _), + data.cmdbuf_size as usize, + ) + .reader(); + reader.read_all(&mut vec, GFP_KERNEL)?; + + let objects = file.inner().objects(); + let ret = queue + .lock() + .submit(id, syncs, data.in_sync_count as usize, &vec, objects); + + match ret { + Err(ERESTARTSYS) => Err(ERESTARTSYS), + Err(e) => { + dev_info!( + device.as_ref(), + "[File {} Queue {}]: IOCTL: submit failed! (submission ID: {} err: {:?})\n", + file.inner().id, + data.queue_id, + id, + e + ); + Err(e) + } + Ok(()) => Ok(0), + } + } + + /// IOCTL: get_time: Get the current GPU timer value. + pub(crate) fn get_time( + device: &AsahiDevice, + data: &mut uapi::drm_asahi_get_time, + _file: &DrmFile, + ) -> Result { + if data.flags != 0 { + cls_pr_debug!(Errors, "get_time: Unexpected flags\n"); + return Err(EINVAL); + } + + // TODO: Do this on device-init for perf. + let gpu = &device.gpu; + let frequency_hz = gpu.get_cfg().base_clock_hz as u64; + let ts_gcd = gcd(frequency_hz, NSEC_PER_SEC as u64); + + let num = (NSEC_PER_SEC as u64) / ts_gcd; + let den = frequency_hz / ts_gcd; + + let raw: u64; + + // SAFETY: Assembly only loads the timer + unsafe { + core::arch::asm!( + "mrs {x}, CNTPCT_EL0", + x = out(reg) raw + ); + } + + data.gpu_timestamp = (raw * num) / den; + + Ok(0) + } +} + +impl Drop for File { + fn drop(&mut self) { + mod_pr_debug!("[File {}]: Closing...\n", self.id); + } +} diff --git a/drivers/gpu/drm/asahi/float.rs b/drivers/gpu/drm/asahi/float.rs new file mode 100644 index 00000000000000..d58a3d284da124 --- /dev/null +++ b/drivers/gpu/drm/asahi/float.rs @@ -0,0 +1,392 @@ +// SPDX-License-Identifier: GPL-2.0-only OR MIT + +//! Basic soft floating-point support +//! +//! The GPU firmware requires a large number of power-related configuration values, many of which +//! are IEEE 754 32-bit floating point values. These values change not only between GPU/SoC +//! variants, but also between specific hardware platforms using these SoCs, so they must be +//! derived from device tree properties. There are many redundant values computed from the same +//! inputs with simple add/sub/mul/div calculations, plus a few values that are actually specific +//! to each individual device depending on its binning and fused voltage configuration, so it +//! doesn't make sense to store the final values to be passed to the firmware in the device tree. +//! +//! Therefore, we need a way to perform floating-point calculations in the kernel. +//! +//! Using the actual FPU from kernel mode is asking for trouble, since there is no way to bound +//! the execution of FPU instructions to a controlled section of code without outright putting it +//! in its own compilation unit, which is quite painful for Rust. Since these calculations only +//! have to happen at initialization time and there is no need for performance, let's use a simple +//! software float implementation instead. +//! +//! This implementation makes no attempt to be fully IEEE754 compliant, but it's good enough and +//! gives bit-identical results to macOS in the vast majority of cases, with one or two exceptions +//! related to slightly non-compliant rounding. + +use core::ops; +use kernel::{ + of, + prelude::*, // +}; + +/// An IEEE754-compatible floating point number implemented in software. +#[derive(Default, Debug, Copy, Clone)] +#[repr(transparent)] +pub(crate) struct F32(u32); + +// SAFETY: F32 is a transparent repr of `u32` and therefore zeroable +unsafe impl Zeroable for F32 {} + +#[derive(Default, Debug, Copy, Clone)] +struct F32U { + sign: bool, + exp: i32, + frac: i64, +} + +impl F32 { + /// Convert a raw 32-bit representation into an F32 + pub(crate) const fn from_bits(u: u32) -> F32 { + F32(u) + } + + // Convert a `f32` value into an F32 + // + // This must ONLY be used in const context. Use the `f32!{}` macro to do it safely. + #[doc(hidden)] + pub(crate) const fn from_f32(v: f32) -> F32 { + // Replace with to_bits() after kernel Rust minreq is >= 1.83.0 + #[allow(clippy::transmute_float_to_int)] + #[allow(unnecessary_transmutes)] + // SAFETY: Transmuting f32 to u32 is always safe + F32(unsafe { core::mem::transmute::(v) }) + } + + // Convert an F32 into a `f32` value + // + // For testing only. + #[doc(hidden)] + #[cfg(test)] + pub(crate) fn to_f32(self) -> f32 { + f32::from_bits(self.0) + } + + const fn unpack(&self) -> F32U { + F32U { + sign: self.0 & (1 << 31) != 0, + exp: ((self.0 >> 23) & 0xff) as i32 - 127, + frac: (((self.0 & 0x7fffff) | 0x800000) as i64) << 9, + } + .norm() + } +} + +/// Safely construct an `F32` out of a constant floating-point value. +/// +/// This ensures that the conversion happens in const context, so no floating point operations are +/// emitted. +#[macro_export] +macro_rules! f32 { + ([$($val:expr),*]) => {{ + [$(f32!($val)),*] + }}; + ($val:expr) => {{ + const _K: $crate::float::F32 = $crate::float::F32::from_f32($val); + _K + }}; +} + +impl ops::Neg for F32 { + type Output = F32; + + fn neg(self) -> F32 { + F32(self.0 ^ (1 << 31)) + } +} + +impl ops::Add for F32 { + type Output = F32; + + fn add(self, rhs: F32) -> F32 { + self.unpack().add(rhs.unpack()).pack() + } +} + +impl ops::Sub for F32 { + type Output = F32; + + fn sub(self, rhs: F32) -> F32 { + self.unpack().add((-rhs).unpack()).pack() + } +} + +impl ops::Mul for F32 { + type Output = F32; + + fn mul(self, rhs: F32) -> F32 { + self.unpack().mul(rhs.unpack()).pack() + } +} + +impl ops::Div for F32 { + type Output = F32; + + fn div(self, rhs: F32) -> F32 { + self.unpack().div(rhs.unpack()).pack() + } +} + +macro_rules! from_ints { + ($u:ty, $i:ty) => { + impl From<$i> for F32 { + fn from(v: $i) -> F32 { + F32U::from_i64(v as i64).pack() + } + } + impl From<$u> for F32 { + fn from(v: $u) -> F32 { + F32U::from_u64(v as u64).pack() + } + } + }; +} + +from_ints!(u8, i8); +from_ints!(u16, i16); +from_ints!(u32, i32); +from_ints!(u64, i64); + +impl F32U { + const INFINITY: F32U = f32!(f32::INFINITY).unpack(); + const NEG_INFINITY: F32U = f32!(f32::NEG_INFINITY).unpack(); + + fn from_i64(v: i64) -> F32U { + F32U { + sign: v < 0, + exp: 32, + frac: v.abs(), + } + .norm() + } + + fn from_u64(mut v: u64) -> F32U { + let mut exp = 32; + if v >= (1 << 63) { + exp = 31; + v >>= 1; + } + F32U { + sign: false, + exp, + frac: v as i64, + } + .norm() + } + + fn shr(&mut self, shift: i32) { + if shift > 63 { + self.exp = 0; + self.frac = 0; + } else { + self.frac >>= shift; + } + } + + fn align(a: &mut F32U, b: &mut F32U) { + if a.exp > b.exp { + b.shr(a.exp - b.exp); + b.exp = a.exp; + } else { + a.shr(b.exp - a.exp); + a.exp = b.exp; + } + } + + fn mul(self, other: F32U) -> F32U { + F32U { + sign: self.sign != other.sign, + exp: self.exp + other.exp, + frac: ((self.frac >> 8) * (other.frac >> 8)) >> 16, + } + } + + fn div(self, other: F32U) -> F32U { + if other.frac == 0 || self.is_inf() { + if self.sign { + F32U::NEG_INFINITY + } else { + F32U::INFINITY + } + } else { + F32U { + sign: self.sign != other.sign, + exp: self.exp - other.exp, + frac: ((self.frac << 24) / (other.frac >> 8)), + } + } + } + + fn add(mut self, mut other: F32U) -> F32U { + F32U::align(&mut self, &mut other); + if self.sign == other.sign { + self.frac += other.frac; + } else { + self.frac -= other.frac; + } + if self.frac < 0 { + self.sign = !self.sign; + self.frac = -self.frac; + } + self + } + + const fn norm(mut self) -> F32U { + let lz = self.frac.leading_zeros() as i32; + if lz > 31 { + self.frac <<= lz - 31; + self.exp -= lz - 31; + } else if lz < 31 { + self.frac >>= 31 - lz; + self.exp += 31 - lz; + } + + if self.is_zero() { + return F32U { + sign: self.sign, + frac: 0, + exp: 0, + }; + } + self + } + + const fn is_zero(&self) -> bool { + self.frac == 0 || self.exp < -126 + } + + const fn is_inf(&self) -> bool { + self.exp > 127 + } + + const fn pack(mut self) -> F32 { + self = self.norm(); + if !self.is_zero() { + self.frac += 0x100; + self = self.norm(); + } + + if self.is_inf() { + if self.sign { + return f32!(f32::NEG_INFINITY); + } else { + return f32!(f32::INFINITY); + } + } else if self.is_zero() { + if self.sign { + return f32!(-0.0); + } else { + return f32!(0.0); + } + } + + F32(if self.sign { 1u32 << 31 } else { 0u32 } + | ((self.exp + 127) as u32) << 23 + | ((self.frac >> 9) & 0x7fffff) as u32) + } +} + +impl<'a> TryFrom> for F32 { + type Error = Error; + + fn try_from(p: of::Property<'_>) -> core::result::Result { + let bits: u32 = p.try_into()?; + Ok(F32::from_bits(bits)) + } +} + +impl of::PropertyUnit for F32 { + const UNIT_SIZE: usize = 4; + + fn from_bytes(data: &[u8]) -> Result { + Ok(F32::from_bits(::from_bytes(data)?)) + } +} + +// TODO: Make this an actual test and figure out how to make it run. +#[cfg(test)] +mod tests { + #[test] + fn test_all() { + fn add(a: f32, b: f32) { + println!( + "{} + {} = {} {}", + a, + b, + (F32::from_f32(a) + F32::from_f32(b)).to_f32(), + a + b + ); + } + fn sub(a: f32, b: f32) { + println!( + "{} - {} = {} {}", + a, + b, + (F32::from_f32(a) - F32::from_f32(b)).to_f32(), + a - b + ); + } + fn mul(a: f32, b: f32) { + println!( + "{} * {} = {} {}", + a, + b, + (F32::from_f32(a) * F32::from_f32(b)).to_f32(), + a * b + ); + } + fn div(a: f32, b: f32) { + println!( + "{} / {} = {} {}", + a, + b, + (F32::from_f32(a) / F32::from_f32(b)).to_f32(), + a / b + ); + } + + fn test(a: f32, b: f32) { + add(a, b); + sub(a, b); + mul(a, b); + div(a, b); + } + + test(1.123, 7.567); + test(1.123, 1.456); + test(7.567, 1.123); + test(1.123, -7.567); + test(1.123, -1.456); + test(7.567, -1.123); + test(-1.123, -7.567); + test(-1.123, -1.456); + test(-7.567, -1.123); + test(1000.123, 0.001); + test(1000.123, 0.0000001); + test(0.0012, 1000.123); + test(0.0000001, 1000.123); + test(0., 0.); + test(0., 1.); + test(1., 0.); + test(1., 1.); + test(2., f32::INFINITY); + test(2., f32::NEG_INFINITY); + test(f32::INFINITY, 2.); + test(f32::NEG_INFINITY, 2.); + test(f32::NEG_INFINITY, 2.); + test(f32::MAX, 2.); + test(f32::MIN, 2.); + test(f32::MIN_POSITIVE, 2.); + test(2., f32::MAX); + test(2., f32::MIN); + test(2., f32::MIN_POSITIVE); + } +} diff --git a/drivers/gpu/drm/asahi/fw/buffer.rs b/drivers/gpu/drm/asahi/fw/buffer.rs new file mode 100644 index 00000000000000..b1f4974fd02902 --- /dev/null +++ b/drivers/gpu/drm/asahi/fw/buffer.rs @@ -0,0 +1,184 @@ +// SPDX-License-Identifier: GPL-2.0-only OR MIT + +//! GPU tiled vertex buffer control firmware structures + +use super::types::*; +use super::workqueue; +use crate::{ + default_zeroed, + no_debug, + trivial_gpustruct, // +}; +use kernel::sync::Arc; + +pub(crate) mod raw { + use super::*; + + #[derive(Debug)] + #[repr(C)] + pub(crate) struct BlockControl { + pub(crate) total: AtomicU32, + pub(crate) wptr: AtomicU32, + pub(crate) unk: AtomicU32, + pub(crate) pad: Pad<0x34>, + } + default_zeroed!(BlockControl); + + #[derive(Debug)] + #[repr(C)] + pub(crate) struct Counter { + pub(crate) count: AtomicU32, + __pad: Pad<0x3c>, + } + default_zeroed!(Counter); + + #[derive(Debug, Default)] + #[repr(C)] + pub(crate) struct Stats { + pub(crate) max_pages: AtomicU32, + pub(crate) max_b: AtomicU32, + pub(crate) overflow_count: AtomicU32, + pub(crate) gpu_c: AtomicU32, + pub(crate) __pad0: Pad<0x10>, + pub(crate) reset: AtomicU32, + pub(crate) __pad1: Pad<0x1c>, + } + + #[versions(AGX)] + #[derive(Debug)] + #[repr(C)] + pub(crate) struct Info<'a> { + pub(crate) gpu_counter: u32, + pub(crate) unk_4: u32, + pub(crate) last_id: i32, + pub(crate) cur_id: i32, + pub(crate) unk_10: u32, + pub(crate) gpu_counter2: u32, + pub(crate) unk_18: u32, + + #[ver(V < V13_0B4 || G >= G14X)] + pub(crate) unk_1c: u32, + + pub(crate) page_list: GpuPointer<'a, &'a [u32]>, + pub(crate) page_list_size: u32, + pub(crate) page_count: AtomicU32, + pub(crate) max_blocks: u32, + pub(crate) block_count: AtomicU32, + pub(crate) unk_38: u32, + pub(crate) block_list: GpuPointer<'a, &'a [u32]>, + pub(crate) block_ctl: GpuPointer<'a, super::BlockControl>, + pub(crate) last_page: AtomicU32, + pub(crate) gpu_page_ptr1: u32, + pub(crate) gpu_page_ptr2: u32, + pub(crate) unk_58: u32, + pub(crate) block_size: u32, + pub(crate) unk_60: U64, + pub(crate) counter: GpuPointer<'a, super::Counter>, + pub(crate) unk_70: u32, + pub(crate) unk_74: u32, + pub(crate) unk_78: u32, + pub(crate) unk_7c: u32, + pub(crate) unk_80: u32, + pub(crate) max_pages: u32, + pub(crate) max_pages_nomemless: u32, + pub(crate) unk_8c: u32, + pub(crate) unk_90: Array<0x30, u8>, + } + + #[versions(AGX)] + #[derive(Debug)] + #[repr(C)] + pub(crate) struct Scene<'a> { + #[ver(G >= G14X)] + pub(crate) control_word: GpuPointer<'a, &'a [u32]>, + #[ver(G >= G14X)] + pub(crate) control_word2: GpuPointer<'a, &'a [u32]>, + pub(crate) pass_page_count: AtomicU32, + pub(crate) unk_4: u32, + pub(crate) unk_8: U64, + pub(crate) unk_10: U64, + pub(crate) user_buffer: GpuPointer<'a, &'a [u8]>, + pub(crate) unk_20: u32, + #[ver(V >= V13_3)] + pub(crate) unk_28: U64, + pub(crate) stats: GpuWeakPointer, + pub(crate) total_page_count: AtomicU32, + #[ver(G < G14X)] + pub(crate) unk_30: U64, // pad + #[ver(G < G14X)] + pub(crate) unk_38: U64, // pad + } + + #[versions(AGX)] + #[derive(Debug)] + #[repr(C)] + pub(crate) struct InitBuffer<'a> { + pub(crate) tag: workqueue::CommandType, + pub(crate) vm_slot: u32, + pub(crate) buffer_slot: u32, + pub(crate) unk_c: u32, + pub(crate) block_count: u32, + pub(crate) buffer: GpuPointer<'a, super::Info::ver>, + pub(crate) stamp_value: EventValue, + } +} + +trivial_gpustruct!(BlockControl); +trivial_gpustruct!(Counter); +trivial_gpustruct!(Stats); + +#[versions(AGX)] +#[derive(Debug)] +pub(crate) struct Info { + pub(crate) block_ctl: GpuObject, + pub(crate) counter: GpuObject, + pub(crate) page_list: GpuArray, + pub(crate) block_list: GpuArray, +} + +#[versions(AGX)] +impl GpuStruct for Info::ver { + type Raw<'a> = raw::Info::ver<'a>; +} + +pub(crate) struct ClusterBuffers { + pub(crate) tilemaps: GpuArray, + pub(crate) meta: GpuArray, +} + +#[versions(AGX)] +pub(crate) struct Scene { + pub(crate) user_buffer: GpuArray, + pub(crate) buffer: crate::buffer::Buffer::ver, + pub(crate) tvb_heapmeta: GpuArray, + pub(crate) tvb_tilemap: GpuArray, + pub(crate) tpc: Arc>, + pub(crate) clustering: Option, + pub(crate) preempt_buf: GpuArray, + #[ver(G >= G14X)] + pub(crate) control_word: GpuArray, +} + +#[versions(AGX)] +no_debug!(Scene::ver); + +#[versions(AGX)] +impl GpuStruct for Scene::ver { + type Raw<'a> = raw::Scene::ver<'a>; +} + +#[versions(AGX)] +pub(crate) struct InitBuffer { + pub(crate) scene: Arc, +} + +#[versions(AGX)] +no_debug!(InitBuffer::ver); + +#[versions(AGX)] +impl workqueue::Command for InitBuffer::ver {} + +#[versions(AGX)] +impl GpuStruct for InitBuffer::ver { + type Raw<'a> = raw::InitBuffer::ver<'a>; +} diff --git a/drivers/gpu/drm/asahi/fw/channels.rs b/drivers/gpu/drm/asahi/fw/channels.rs new file mode 100644 index 00000000000000..c1a7ec82aad1e2 --- /dev/null +++ b/drivers/gpu/drm/asahi/fw/channels.rs @@ -0,0 +1,443 @@ +// SPDX-License-Identifier: GPL-2.0-only OR MIT + +//! GPU communication channel firmware structures (ring buffers) + +use super::types::*; +use crate::default_zeroed; +use core::sync::atomic::Ordering; +use kernel::static_assert; + +pub(crate) mod raw { + use super::*; + + #[derive(Debug)] + #[repr(C)] + pub(crate) struct ChannelState<'a> { + pub(crate) read_ptr: AtomicU32, + __pad0: Pad<0x1c>, + pub(crate) write_ptr: AtomicU32, + __pad1: Pad<0xc>, + _p: PhantomData<&'a ()>, + } + default_zeroed!(<'a>, ChannelState<'a>); + + #[derive(Debug)] + #[repr(C)] + pub(crate) struct FwCtlChannelState<'a> { + pub(crate) read_ptr: AtomicU32, + __pad0: Pad<0xc>, + pub(crate) write_ptr: AtomicU32, + __pad1: Pad<0xc>, + _p: PhantomData<&'a ()>, + } + default_zeroed!(<'a>, FwCtlChannelState<'a>); +} + +pub(crate) trait RxChannelState: GpuStruct + Debug + Default +where + for<'a> ::Raw<'a>: Default + Zeroable, +{ + const SUB_CHANNELS: usize; + + fn wptr(raw: &Self::Raw<'_>, index: usize) -> u32; + fn set_rptr(raw: &Self::Raw<'_>, index: usize, rptr: u32); +} + +#[derive(Debug, Default)] +pub(crate) struct ChannelState {} + +impl GpuStruct for ChannelState { + type Raw<'a> = raw::ChannelState<'a>; +} + +impl RxChannelState for ChannelState { + const SUB_CHANNELS: usize = 1; + + fn wptr(raw: &Self::Raw<'_>, _index: usize) -> u32 { + raw.write_ptr.load(Ordering::Acquire) + } + + fn set_rptr(raw: &Self::Raw<'_>, _index: usize, rptr: u32) { + raw.read_ptr.store(rptr, Ordering::Release); + } +} + +#[derive(Debug, Default)] +pub(crate) struct FwLogChannelState {} + +impl GpuStruct for FwLogChannelState { + type Raw<'a> = Array<6, raw::ChannelState<'a>>; +} + +impl RxChannelState for FwLogChannelState { + const SUB_CHANNELS: usize = 6; + + fn wptr(raw: &Self::Raw<'_>, index: usize) -> u32 { + raw[index].write_ptr.load(Ordering::Acquire) + } + + fn set_rptr(raw: &Self::Raw<'_>, index: usize, rptr: u32) { + raw[index].read_ptr.store(rptr, Ordering::Release); + } +} + +#[derive(Debug, Default)] +pub(crate) struct FwCtlChannelState {} + +impl GpuStruct for FwCtlChannelState { + type Raw<'a> = raw::FwCtlChannelState<'a>; +} + +pub(crate) trait TxChannelState: GpuStruct + Debug + Default { + fn rptr(raw: &Self::Raw<'_>) -> u32; + fn set_wptr(raw: &Self::Raw<'_>, wptr: u32); +} + +impl TxChannelState for ChannelState { + fn rptr(raw: &Self::Raw<'_>) -> u32 { + raw.read_ptr.load(Ordering::Acquire) + } + + fn set_wptr(raw: &Self::Raw<'_>, wptr: u32) { + raw.write_ptr.store(wptr, Ordering::Release); + } +} + +impl TxChannelState for FwCtlChannelState { + fn rptr(raw: &Self::Raw<'_>) -> u32 { + raw.read_ptr.load(Ordering::Acquire) + } + + fn set_wptr(raw: &Self::Raw<'_>, wptr: u32) { + raw.write_ptr.store(wptr, Ordering::Release); + } +} + +#[derive(Debug, Copy, Clone, Default, PartialEq, Eq)] +#[repr(u32)] +pub(crate) enum PipeType { + #[default] + Vertex = 0, + Fragment = 1, + Compute = 2, +} + +#[versions(AGX)] +#[derive(Debug, Copy, Clone, Default)] +#[repr(C)] +pub(crate) struct RunWorkQueueMsg { + pub(crate) pipe_type: PipeType, + pub(crate) work_queue: Option>, + pub(crate) wptr: u32, + pub(crate) event_slot: u32, + pub(crate) is_new: bool, + #[ver(V >= V13_2 && G == G14)] + pub(crate) __pad: Pad<0x2b>, + #[ver(V < V13_2 || G != G14)] + pub(crate) __pad: Pad<0x1b>, +} + +#[versions(AGX)] +pub(crate) type PipeMsg = RunWorkQueueMsg::ver; + +#[versions(AGX)] +pub(crate) const DEVICECONTROL_SZ: usize = { + #[ver(V < V13_2 || G != G14)] + { + 0x2c + } + #[ver(V >= V13_2 && G == G14)] + { + 0x3c + } +}; + +// TODO: clean up when arbitrary_enum_discriminant is stable +// https://github.com/rust-lang/rust/issues/60553 + +#[versions(AGX)] +#[derive(Debug, Copy, Clone)] +#[repr(C, u32)] +#[allow(dead_code)] +pub(crate) enum DeviceControlMsg { + Unk00(Array), + Unk01(Array), + Unk02(Array), + Unk03(Array), + Unk04(Array), + Unk05(Array), + Unk06(Array), + Unk07(Array), + Unk08(Array), + Unk09(Array), + Unk0a(Array), + Unk0b(Array), + Unk0c(Array), + #[ver(V >= V13_3)] + Unk0d(Array), + GrowTVBAck { + unk_4: u32, + buffer_slot: u32, + vm_slot: u32, + counter: u32, + subpipe: u32, + halt_count: U64, + __pad: Pad<{ DEVICECONTROL_SZ::ver - 0x1c }>, + }, + RecoverChannel { + pipe_type: u32, + work_queue: GpuWeakPointer, + event_value: u32, + __pad: Pad<{ DEVICECONTROL_SZ::ver - 0x10 }>, + }, + IdlePowerOff { + val: u32, + __pad: Pad<{ DEVICECONTROL_SZ::ver - 0x4 }>, + }, + Unk10(Array), + Unk11(Array), + Unk12(Array), + Unk13(Array), + Unk14(Array), // Init? + Unk15(Array), // Enable something + Unk16(Array), // Disable something + DestroyContext { + unk_4: u32, + ctx_23: u8, + #[ver(V < V13_3)] + __pad0: Pad<3>, + unk_c: U32, + unk_10: U32, + ctx_0: u8, + ctx_1: u8, + ctx_4: u8, + #[ver(V < V13_3)] + __pad1: Pad<1>, + #[ver(V < V13_3)] + unk_18: u32, + gpu_context: Option>, + #[ver(V < V13_3)] + __pad2: Pad<{ DEVICECONTROL_SZ::ver - 0x20 }>, + #[ver(V >= V13_3)] + __pad2: Pad<{ DEVICECONTROL_SZ::ver - 0x18 }>, + }, + Unk18(Array), + Initialize(Pad), // Update RegionC +} + +#[versions(AGX)] +static_assert!(core::mem::size_of::() == 4 + DEVICECONTROL_SZ::ver); + +#[versions(AGX)] +default_zeroed!(DeviceControlMsg::ver); + +#[derive(Copy, Clone, Default, Debug)] +#[repr(C)] +#[allow(dead_code)] +pub(crate) struct FwCtlMsg { + pub(crate) addr: U64, + pub(crate) unk_8: u32, + pub(crate) slot: u32, + pub(crate) page_count: u16, + pub(crate) unk_12: u16, +} + +pub(crate) const EVENT_SZ: usize = 0x34; + +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +#[repr(C, u32)] +#[allow(dead_code)] +pub(crate) enum ChannelErrorType { + MemoryError, + DMKill, + Aborted, + Unk3, + Unknown(u32), +} + +#[derive(Debug, Copy, Clone)] +#[repr(C, u32)] +#[allow(dead_code)] +pub(crate) enum EventMsg { + Fault, + Flag { + firing: [u32; 4], + unk_14: u16, + }, + Unk2(Array), + Unk3(Array), + Timeout { + counter: u32, + unk_8: u32, + event_slot: i32, + }, + Unk5(Array), + Unk6(Array), + GrowTVB { + vm_slot: u32, + buffer_slot: u32, + counter: u32, + }, + ChannelError { + error_type: u32, + pipe_type: u32, + event_slot: u32, + event_value: u32, + }, + // Max discriminant: 0x8 +} + +static_assert!(core::mem::size_of::() == 4 + EVENT_SZ); + +pub(crate) const EVENT_MAX: u32 = 0x8; + +#[derive(Copy, Clone)] +#[repr(C)] +pub(crate) union RawEventMsg { + pub(crate) raw: (u32, Array), + pub(crate) msg: EventMsg, +} + +default_zeroed!(RawEventMsg); + +#[derive(Debug, Copy, Clone, Default)] +#[repr(C)] +pub(crate) struct RawFwLogMsg { + pub(crate) msg_type: u32, + __pad0: u32, + pub(crate) msg_index: U64, + __pad1: Pad<0x28>, +} + +#[derive(Debug, Copy, Clone, Default)] +#[repr(C)] +pub(crate) struct RawFwLogPayloadMsg { + pub(crate) msg_type: u32, + pub(crate) seq_no: u32, + pub(crate) timestamp: U64, + pub(crate) msg: Array<0xc8, u8>, +} + +#[derive(Debug, Copy, Clone, Default)] +#[repr(C)] +pub(crate) struct RawKTraceMsg { + pub(crate) msg_type: u32, + pub(crate) timestamp: U64, + pub(crate) args: Array<4, U64>, + pub(crate) code: u8, + pub(crate) channel: u8, + __pad: Pad<1>, + pub(crate) thread: u8, + pub(crate) unk_flag: U64, +} + +#[versions(AGX)] +pub(crate) const STATS_SZ: usize = { + #[ver(V < V13_0B4)] + { + 0x2c + } + #[ver(V >= V13_0B4)] + { + 0x3c + } +}; + +#[versions(AGX)] +#[derive(Debug, Copy, Clone)] +#[repr(C, u32)] +#[allow(dead_code)] +pub(crate) enum StatsMsg { + Power { + // 0x00 + __pad: Pad<0x18>, + power: U64, + }, + Unk1(Array<{ STATS_SZ::ver }, u8>), + PowerOn { + // 0x02 + off_time: U64, + }, + PowerOff { + // 0x03 + on_time: U64, + }, + Utilization { + // 0x04 + timestamp: U64, + util1: u32, + util2: u32, + util3: u32, + util4: u32, + }, + Unk5(Array<{ STATS_SZ::ver }, u8>), + Unk6(Array<{ STATS_SZ::ver }, u8>), + Unk7(Array<{ STATS_SZ::ver }, u8>), + Unk8(Array<{ STATS_SZ::ver }, u8>), + AvgPower { + // 0x09 + active_cs: U64, + unk2: u32, + unk3: u32, + unk4: u32, + avg_power: u32, + }, + Temperature { + // 0x0a + __pad: Pad<0x8>, + raw_value: u32, + scale: u32, + tmin: u32, + tmax: u32, + }, + PowerState { + // 0x0b + timestamp: U64, + last_busy_ts: U64, + active: u32, + poweroff: u32, + unk1: u32, + pstate: u32, + unk2: u32, + unk3: u32, + }, + FwBusy { + // 0x0c + timestamp: U64, + busy: u32, + }, + PState { + // 0x0d + __pad: Pad<0x8>, + ps_min: u32, + unk1: u32, + ps_max: u32, + unk2: u32, + }, + TempSensor { + // 0x0e + __pad: Pad<0x4>, + sensor_id: u32, + raw_value: u32, + scale: u32, + tmin: u32, + tmax: u32, + }, // Max discriminant: 0xe +} + +#[versions(AGX)] +static_assert!(core::mem::size_of::() == 4 + STATS_SZ::ver); + +#[versions(AGX)] +pub(crate) const STATS_MAX: u32 = 0xe; + +#[versions(AGX)] +#[derive(Copy, Clone)] +#[repr(C)] +pub(crate) union RawStatsMsg { + pub(crate) raw: (u32, Array<{ STATS_SZ::ver }, u8>), + pub(crate) msg: StatsMsg::ver, +} + +#[versions(AGX)] +default_zeroed!(RawStatsMsg::ver); diff --git a/drivers/gpu/drm/asahi/fw/compute.rs b/drivers/gpu/drm/asahi/fw/compute.rs new file mode 100644 index 00000000000000..f5f6ffa9d8d0d8 --- /dev/null +++ b/drivers/gpu/drm/asahi/fw/compute.rs @@ -0,0 +1,120 @@ +// SPDX-License-Identifier: GPL-2.0-only OR MIT + +//! GPU compute job firmware structures + +use super::types::*; +use super::{ + event, + job, + workqueue, // +}; +use crate::{ + microseq, + mmu, // +}; +use kernel::sync::Arc; + +pub(crate) mod raw { + use super::*; + + #[derive(Debug)] + #[repr(C)] + pub(crate) struct JobParameters1<'a> { + pub(crate) preempt_buf1: GpuPointer<'a, &'a [u8]>, + pub(crate) cdm_ctrl_stream_base: U64, + pub(crate) preempt_buf2: GpuPointer<'a, &'a [u8]>, + pub(crate) preempt_buf3: GpuPointer<'a, &'a [u8]>, + pub(crate) preempt_buf4: GpuPointer<'a, &'a [u8]>, + pub(crate) preempt_buf5: GpuPointer<'a, &'a [u8]>, + pub(crate) usc_exec_base_cp: U64, + pub(crate) unk_38: U64, + pub(crate) helper_program: u32, + pub(crate) unk_44: u32, + pub(crate) helper_arg: U64, + pub(crate) helper_cfg: u32, + pub(crate) unk_54: u32, + pub(crate) unk_58: u32, + pub(crate) unk_5c: u32, + pub(crate) iogpu_unk_40: u32, + pub(crate) __pad: Pad<0xfc>, + } + + #[versions(AGX)] + #[derive(Debug)] + #[repr(C)] + pub(crate) struct JobParameters2<'a> { + #[ver(V >= V13_0B4)] + pub(crate) unk_0_0: u32, + pub(crate) unk_0: Array<0x24, u8>, + pub(crate) preempt_buf1: GpuPointer<'a, &'a [u8]>, + pub(crate) cdm_ctrl_stream_end: U64, + pub(crate) unk_34: Array<0x20, u8>, + pub(crate) unk_g14x: u32, + pub(crate) unk_58: u32, + #[ver(V < V13_0B4)] + pub(crate) unk_5c: u32, + } + + #[versions(AGX)] + #[derive(Debug)] + #[repr(C)] + pub(crate) struct RunCompute<'a> { + pub(crate) tag: workqueue::CommandType, + + #[ver(V >= V13_0B4)] + pub(crate) counter: U64, + + pub(crate) unk_4: u32, + pub(crate) vm_slot: u32, + pub(crate) notifier: GpuPointer<'a, event::Notifier::ver>, + pub(crate) unk_pointee: u32, + #[ver(G < G14X)] + pub(crate) __pad0: Array<0x50, u8>, + #[ver(G < G14X)] + pub(crate) job_params1: JobParameters1<'a>, + #[ver(G >= G14X)] + pub(crate) registers: job::raw::RegisterArray, + pub(crate) __pad1: Array<0x20, u8>, + pub(crate) microsequence: GpuPointer<'a, &'a [u8]>, + pub(crate) microsequence_size: u32, + pub(crate) job_params2: JobParameters2::ver<'a>, + pub(crate) encoder_params: job::raw::EncoderParams, + pub(crate) meta: job::raw::JobMeta, + pub(crate) command_time: U64, + pub(crate) timestamp_pointers: job::raw::TimestampPointers<'a>, + pub(crate) user_timestamp_pointers: job::raw::TimestampPointers<'a>, + pub(crate) client_sequence: u8, + pub(crate) pad_2d1: Array<3, u8>, + pub(crate) unk_2d4: u32, + pub(crate) unk_2d8: u8, + #[ver(V >= V13_0B4)] + pub(crate) context_store_req: U64, + #[ver(V >= V13_0B4)] + pub(crate) context_store_compl: U64, + #[ver(V >= V13_0B4)] + pub(crate) unk_2e9: Array<0x14, u8>, + #[ver(V >= V13_0B4)] + pub(crate) unk_flag: U32, + #[ver(V >= V13_0B4)] + pub(crate) unk_pad: Array<0x10, u8>, + } +} + +#[versions(AGX)] +#[derive(Debug)] +pub(crate) struct RunCompute { + pub(crate) notifier: Arc>, + pub(crate) preempt_buf: GpuArray, + pub(crate) micro_seq: microseq::MicroSequence, + pub(crate) vm_bind: mmu::VmBind, + pub(crate) timestamps: Arc>, + pub(crate) user_timestamps: job::UserTimestamps, +} + +#[versions(AGX)] +impl GpuStruct for RunCompute::ver { + type Raw<'a> = raw::RunCompute::ver<'a>; +} + +#[versions(AGX)] +impl workqueue::Command for RunCompute::ver {} diff --git a/drivers/gpu/drm/asahi/fw/event.rs b/drivers/gpu/drm/asahi/fw/event.rs new file mode 100644 index 00000000000000..52bc456f58707d --- /dev/null +++ b/drivers/gpu/drm/asahi/fw/event.rs @@ -0,0 +1,104 @@ +// SPDX-License-Identifier: GPL-2.0-only OR MIT + +//! GPU events control structures & stamps + +use super::types::*; +use crate::{ + default_zeroed, + trivial_gpustruct, // +}; +use core::sync::atomic::Ordering; + +pub(crate) mod raw { + use super::*; + + #[derive(Debug, Clone, Copy, Default)] + #[repr(C)] + pub(crate) struct LinkedListHead { + pub(crate) prev: Option>, + pub(crate) next: Option>, + } + + #[derive(Debug, Clone, Copy)] + #[repr(C)] + pub(crate) struct NotifierList { + pub(crate) list_head: LinkedListHead, + pub(crate) unkptr_10: U64, + } + default_zeroed!(NotifierList); + + #[versions(AGX)] + #[derive(Debug, Clone, Copy)] + #[repr(C)] + pub(crate) struct NotifierState { + unk_14: u32, + unk_18: U64, + unk_20: u32, + vm_slot: u32, + has_vtx: u32, + pstamp_vtx: Array<4, U64>, + has_frag: u32, + pstamp_frag: Array<4, U64>, + has_comp: u32, + pstamp_comp: Array<4, U64>, + #[ver(G >= G14 && V < V13_0B4)] + unk_98_g14_0: Array<0x14, u8>, + in_list: u32, + list_head: LinkedListHead, + #[ver(G >= G14 && V < V13_0B4)] + unk_a8_g14_0: Pad<4>, + #[ver(V >= V13_0B4)] + pub(crate) unk_buf: Array<0x8, u8>, // Init to all-ff + } + + #[versions(AGX)] + impl Default for NotifierState::ver { + fn default() -> Self { + #[allow(unused_mut)] + // SAFETY: All bit patterns are valid for this type. + let mut s: Self = unsafe { core::mem::zeroed() }; + #[ver(V >= V13_0B4)] + s.unk_buf = Array::new([0xff; 0x8]); + s + } + } + + #[derive(Debug)] + #[repr(transparent)] + pub(crate) struct Threshold(AtomicU64); + default_zeroed!(Threshold); + + impl Threshold { + pub(crate) fn increase(&self, amount: u32) { + // We could use fetch_add, but the non-LSE atomic + // sequence Rust produces confuses the hypervisor. + let v = self.0.load(Ordering::Relaxed); + self.0.store(v + (amount as u64), Ordering::Relaxed); + } + } + + #[versions(AGX)] + #[derive(Debug)] + #[repr(C)] + pub(crate) struct Notifier<'a> { + pub(crate) threshold: GpuPointer<'a, super::Threshold>, + pub(crate) generation: AtomicU32, + pub(crate) cur_count: AtomicU32, + pub(crate) unk_10: AtomicU32, + pub(crate) state: NotifierState::ver, + } +} + +trivial_gpustruct!(Threshold); +trivial_gpustruct!(NotifierList); + +#[versions(AGX)] +#[derive(Debug)] +pub(crate) struct Notifier { + pub(crate) threshold: GpuObject, +} + +#[versions(AGX)] +impl GpuStruct for Notifier::ver { + type Raw<'a> = raw::Notifier::ver<'a>; +} diff --git a/drivers/gpu/drm/asahi/fw/fragment.rs b/drivers/gpu/drm/asahi/fw/fragment.rs new file mode 100644 index 00000000000000..3daad1ae4db671 --- /dev/null +++ b/drivers/gpu/drm/asahi/fw/fragment.rs @@ -0,0 +1,296 @@ +// SPDX-License-Identifier: GPL-2.0-only OR MIT + +//! GPU fragment job firmware structures + +use super::types::*; +use super::{ + event, + job, + workqueue, // +}; +use crate::{ + buffer, + fw, + microseq, + mmu, // +}; +use kernel::sync::Arc; + +pub(crate) mod raw { + use super::*; + + #[derive(Debug, Clone, Copy)] + #[repr(C)] + pub(crate) struct BackgroundProgram { + pub(crate) rsrc_spec: U64, + pub(crate) address: U64, + } + + #[derive(Debug, Clone, Copy, Default)] + #[repr(C)] + pub(crate) struct EotProgram { + pub(crate) unk_0: U64, + pub(crate) unk_8: u32, + pub(crate) rsrc_spec: u32, + pub(crate) unk_10: u32, + pub(crate) address: u32, + pub(crate) unk_18: u32, + pub(crate) unk_1c_padding: u32, + } + + impl EotProgram { + pub(crate) fn new(rsrc_spec: u32, address: u32) -> EotProgram { + EotProgram { + rsrc_spec, + address, + ..Default::default() + } + } + } + + #[derive(Debug)] + #[repr(C)] + pub(crate) struct ArrayAddr { + pub(crate) ptr: U64, + pub(crate) unk_padding: U64, + } + + #[versions(AGX)] + #[derive(Debug, Clone, Copy)] + #[repr(C)] + pub(crate) struct AuxFBInfo { + pub(crate) isp_ctl: u32, + pub(crate) unk2: u32, + pub(crate) width: u32, + pub(crate) height: u32, + + #[ver(V >= V13_0B4)] + pub(crate) unk3: U64, + } + + #[versions(AGX)] + #[derive(Debug)] + #[repr(C)] + pub(crate) struct JobParameters1<'a> { + pub(crate) utile_config: u32, + pub(crate) unk_4: u32, + pub(crate) bg: BackgroundProgram, + pub(crate) ppp_multisamplectl: U64, + pub(crate) isp_scissor_base: U64, + pub(crate) isp_dbias_base: U64, + pub(crate) aux_fb_info: AuxFBInfo::ver, + pub(crate) isp_zls_pixels: U64, + pub(crate) isp_oclqry_base: U64, + pub(crate) zls_ctrl: U64, + + #[ver(G >= G14)] + pub(crate) unk_58_g14_0: U64, + #[ver(G >= G14)] + pub(crate) unk_58_g14_8: U64, + + pub(crate) z_load: U64, + pub(crate) z_store: U64, + pub(crate) s_load: U64, + pub(crate) s_store: U64, + + #[ver(G >= G14)] + pub(crate) unk_68_g14_0: Array<0x20, u8>, + + pub(crate) z_load_stride: U64, + pub(crate) z_store_stride: U64, + pub(crate) s_load_stride: U64, + pub(crate) s_store_stride: U64, + pub(crate) z_load_comp: U64, + pub(crate) z_load_comp_stride: U64, + pub(crate) z_store_comp: U64, + pub(crate) z_store_comp_stride: U64, + pub(crate) s_load_comp: U64, + pub(crate) s_load_comp_stride: U64, + pub(crate) s_store_comp: U64, + pub(crate) s_store_comp_stride: U64, + pub(crate) tvb_tilemap: GpuPointer<'a, &'a [u8]>, + pub(crate) tvb_layermeta: GpuPointer<'a, &'a [u8]>, + pub(crate) mtile_stride_dwords: U64, + pub(crate) tvb_heapmeta: GpuPointer<'a, &'a [u8]>, + pub(crate) tile_config: U64, + pub(crate) aux_fb: GpuPointer<'a, &'a [u8]>, + pub(crate) unk_108: Array<0x6, U64>, + pub(crate) usc_exec_base_isp: U64, + pub(crate) unk_140: U64, + pub(crate) helper_program: u32, + pub(crate) unk_14c: u32, + pub(crate) helper_arg: U64, + pub(crate) unk_158: U64, + pub(crate) unk_160: U64, + + #[ver(G < G14)] + pub(crate) __pad: Pad<0x1d8>, + #[ver(G >= G14)] + pub(crate) __pad: Pad<0x1a8>, + #[ver(V < V13_0B4)] + pub(crate) __pad1: Pad<0x8>, + } + + #[derive(Debug)] + #[repr(C)] + pub(crate) struct JobParameters2 { + pub(crate) eot_rsrc_spec: u32, + pub(crate) eot_usc: u32, + pub(crate) unk_8: u32, + pub(crate) unk_c: u32, + pub(crate) isp_merge_upper_x: F32, + pub(crate) isp_merge_upper_y: F32, + pub(crate) unk_18: U64, + pub(crate) utiles_per_mtile_y: u16, + pub(crate) utiles_per_mtile_x: u16, + pub(crate) unk_24: u32, + pub(crate) tile_counts: u32, + pub(crate) tib_blocks: u32, + pub(crate) isp_bgobjdepth: u32, + pub(crate) isp_bgobjvals: u32, + pub(crate) unk_38: u32, + pub(crate) unk_3c: u32, + pub(crate) helper_cfg: u32, + pub(crate) __pad: Pad<0xac>, + } + + #[versions(AGX)] + #[derive(Debug)] + #[repr(C)] + pub(crate) struct JobParameters3 { + pub(crate) isp_dbias_base: ArrayAddr, + pub(crate) isp_scissor_base: ArrayAddr, + pub(crate) isp_oclqry_base: U64, + pub(crate) unk_118: U64, + pub(crate) unk_120: Array<0x25, U64>, + pub(crate) unk_partial_bg: BackgroundProgram, + pub(crate) unk_258: U64, + pub(crate) unk_260: U64, + pub(crate) unk_268: U64, + pub(crate) unk_270: U64, + pub(crate) partial_bg: BackgroundProgram, + pub(crate) zls_ctrl: U64, + pub(crate) unk_290: U64, + pub(crate) z_load: U64, + pub(crate) z_partial_stride: U64, + pub(crate) z_partial_comp_stride: U64, + pub(crate) z_store: U64, + pub(crate) z_partial: U64, + pub(crate) z_partial_comp: U64, + pub(crate) s_load: U64, + pub(crate) s_partial_stride: U64, + pub(crate) s_partial_comp_stride: U64, + pub(crate) s_store: U64, + pub(crate) s_partial: U64, + pub(crate) s_partial_comp: U64, + pub(crate) unk_2f8: Array<2, U64>, + pub(crate) tib_blocks: u32, + pub(crate) unk_30c: u32, + pub(crate) aux_fb_info: AuxFBInfo::ver, + pub(crate) tile_config: U64, + pub(crate) unk_328_padding: Array<0x8, u8>, + pub(crate) unk_partial_eot: EotProgram, + pub(crate) partial_eot: EotProgram, + pub(crate) isp_bgobjdepth: u32, + pub(crate) isp_bgobjvals: u32, + pub(crate) sample_size: u32, + pub(crate) unk_37c: u32, + pub(crate) unk_380: U64, + pub(crate) unk_388: U64, + + #[ver(V >= V13_0B4)] + pub(crate) unk_390_0: U64, + + pub(crate) isp_zls_pixels: U64, + } + + #[versions(AGX)] + #[derive(Debug)] + #[repr(C)] + pub(crate) struct RunFragment<'a> { + pub(crate) tag: workqueue::CommandType, + + #[ver(V >= V13_0B4)] + pub(crate) counter: U64, + + pub(crate) vm_slot: u32, + pub(crate) unk_8: u32, + pub(crate) microsequence: GpuPointer<'a, &'a [u8]>, + pub(crate) microsequence_size: u32, + pub(crate) notifier: GpuPointer<'a, event::Notifier::ver>, + pub(crate) buffer: GpuPointer<'a, fw::buffer::Info::ver>, + pub(crate) scene: GpuPointer<'a, fw::buffer::Scene::ver>, + pub(crate) unk_buffer_buf: GpuWeakPointer<[u8]>, + pub(crate) tvb_tilemap: GpuPointer<'a, &'a [u8]>, + pub(crate) ppp_multisamplectl: U64, + pub(crate) samples: u32, + pub(crate) tiles_per_mtile_y: u16, + pub(crate) tiles_per_mtile_x: u16, + pub(crate) unk_50: U64, + pub(crate) unk_58: U64, + pub(crate) isp_merge_upper_x: F32, + pub(crate) isp_merge_upper_y: F32, + pub(crate) unk_68: U64, + pub(crate) tile_count: U64, + + #[ver(G < G14X)] + pub(crate) job_params1: JobParameters1::ver<'a>, + #[ver(G < G14X)] + pub(crate) job_params2: JobParameters2, + #[ver(G >= G14X)] + pub(crate) registers: job::raw::RegisterArray, + + pub(crate) job_params3: JobParameters3::ver, + pub(crate) unk_758_flag: u32, + pub(crate) unk_75c_flag: u32, + pub(crate) unk_buf: Array<0x110, u8>, + pub(crate) busy_flag: u32, + pub(crate) tvb_overflow_count: u32, + pub(crate) unk_878: u32, + pub(crate) encoder_params: job::raw::EncoderParams, + pub(crate) process_empty_tiles: u32, + pub(crate) no_clear_pipeline_textures: u32, + pub(crate) msaa_zs: u32, + pub(crate) unk_pointee: u32, + #[ver(V >= V13_3)] + pub(crate) unk_v13_3: u32, + pub(crate) meta: job::raw::JobMeta, + pub(crate) unk_after_meta: u32, + pub(crate) unk_buf_0: U64, + pub(crate) unk_buf_8: U64, + pub(crate) unk_buf_10: U64, + pub(crate) command_time: U64, + pub(crate) timestamp_pointers: job::raw::TimestampPointers<'a>, + pub(crate) user_timestamp_pointers: job::raw::TimestampPointers<'a>, + pub(crate) client_sequence: u8, + pub(crate) pad_925: Array<3, u8>, + pub(crate) unk_928: u32, + pub(crate) unk_92c: u8, + + #[ver(V >= V13_0B4)] + pub(crate) unk_ts: U64, + + #[ver(V >= V13_0B4)] + pub(crate) unk_92d_8: Array<0x1b, u8>, + } +} + +#[versions(AGX)] +#[derive(Debug)] +pub(crate) struct RunFragment { + pub(crate) notifier: Arc>, + pub(crate) scene: Arc, + pub(crate) micro_seq: microseq::MicroSequence, + pub(crate) vm_bind: mmu::VmBind, + pub(crate) aux_fb: GpuArray, + pub(crate) timestamps: Arc>, + pub(crate) user_timestamps: job::UserTimestamps, +} + +#[versions(AGX)] +impl GpuStruct for RunFragment::ver { + type Raw<'a> = raw::RunFragment::ver<'a>; +} + +#[versions(AGX)] +impl workqueue::Command for RunFragment::ver {} diff --git a/drivers/gpu/drm/asahi/fw/initdata.rs b/drivers/gpu/drm/asahi/fw/initdata.rs new file mode 100644 index 00000000000000..c8cb348056961a --- /dev/null +++ b/drivers/gpu/drm/asahi/fw/initdata.rs @@ -0,0 +1,1359 @@ +// SPDX-License-Identifier: GPL-2.0-only OR MIT + +//! GPU initialization / global structures + +use super::channels; +use super::types::*; +use crate::{ + default_zeroed, + gem, + mmu, + no_debug, + trivial_gpustruct, // +}; + +pub(crate) mod raw { + use super::*; + + #[derive(Debug, Default)] + #[repr(C)] + pub(crate) struct ChannelRing { + pub(crate) state: Option>, + pub(crate) ring: Option>, + } + + #[versions(AGX)] + #[derive(Debug)] + #[repr(C)] + pub(crate) struct PipeChannels { + pub(crate) vtx: ChannelRing, + pub(crate) frag: ChannelRing, + pub(crate) comp: ChannelRing, + } + #[versions(AGX)] + default_zeroed!(PipeChannels::ver); + + #[derive(Debug, Default)] + #[repr(C)] + pub(crate) struct FwStatusFlags { + pub(crate) halt_count: AtomicU64, + __pad0: Pad<0x8>, + pub(crate) halted: AtomicU32, + __pad1: Pad<0xc>, + pub(crate) resume: AtomicU32, + __pad2: Pad<0xc>, + pub(crate) unk_40: u32, + __pad3: Pad<0xc>, + pub(crate) unk_ctr: u32, + __pad4: Pad<0xc>, + pub(crate) unk_60: u32, + __pad5: Pad<0xc>, + pub(crate) unk_70: u32, + __pad6: Pad<0xc>, + } + + #[derive(Debug, Default)] + #[repr(C)] + pub(crate) struct FwStatus { + pub(crate) fwctl_channel: ChannelRing, + pub(crate) flags: FwStatusFlags, + } + + #[derive(Debug)] + #[repr(C)] + pub(crate) struct HwDataShared1 { + pub(crate) table: Array<16, i32>, + pub(crate) unk_44: Array<0x60, u8>, + pub(crate) unk_a4: u32, + pub(crate) unk_a8: u32, + } + default_zeroed!(HwDataShared1); + + #[derive(Debug, Default)] + #[repr(C)] + pub(crate) struct HwDataShared2Curve { + pub(crate) unk_0: u32, + pub(crate) unk_4: u32, + pub(crate) t1: Array<16, u16>, + pub(crate) t2: Array<16, i16>, + pub(crate) t3: Array<8, Array<16, i32>>, + } + + #[derive(Debug, Default)] + #[repr(C)] + pub(crate) struct HwDataShared2G14 { + pub(crate) unk_0: Array<5, u32>, + pub(crate) unk_14: u32, + pub(crate) unk_18: Array<8, u32>, + pub(crate) curve1: HwDataShared2Curve, + pub(crate) curve2: HwDataShared2Curve, + } + + #[derive(Debug)] + #[repr(C)] + pub(crate) struct HwDataShared2 { + pub(crate) table: Array<10, i32>, + pub(crate) unk_28: Array<0x10, u8>, + pub(crate) g14: HwDataShared2G14, + pub(crate) unk_500: u32, + pub(crate) unk_504: u32, + pub(crate) unk_508: u32, + pub(crate) unk_50c: u32, + } + default_zeroed!(HwDataShared2); + + #[derive(Debug)] + #[repr(C)] + pub(crate) struct HwDataShared3 { + pub(crate) unk_0: u32, + pub(crate) unk_4: u32, + pub(crate) unk_8: u32, + pub(crate) table: Array<16, u32>, + pub(crate) unk_4c: u32, + } + default_zeroed!(HwDataShared3); + + #[derive(Debug)] + #[repr(C)] + pub(crate) struct HwDataA130Extra { + pub(crate) unk_0: Array<0x38, u8>, + pub(crate) unk_38: u32, + pub(crate) unk_3c: u32, + pub(crate) gpu_se_inactive_threshold: u32, + pub(crate) unk_44: u32, + pub(crate) gpu_se_engagement_criteria: i32, + pub(crate) gpu_se_reset_criteria: u32, + pub(crate) unk_50: u32, + pub(crate) unk_54: u32, + pub(crate) unk_58: u32, + pub(crate) unk_5c: u32, + pub(crate) gpu_se_filter_a_neg: F32, + pub(crate) gpu_se_filter_1_a_neg: F32, + pub(crate) gpu_se_filter_a: F32, + pub(crate) gpu_se_filter_1_a: F32, + pub(crate) gpu_se_ki_dt: F32, + pub(crate) gpu_se_ki_1_dt: F32, + pub(crate) unk_78: F32, + pub(crate) unk_7c: F32, + pub(crate) gpu_se_kp: F32, + pub(crate) gpu_se_kp_1: F32, + pub(crate) unk_88: u32, + pub(crate) unk_8c: u32, + pub(crate) max_pstate_scaled_1: u32, + pub(crate) unk_94: u32, + pub(crate) unk_98: u32, + pub(crate) unk_9c: F32, + pub(crate) unk_a0: u32, + pub(crate) unk_a4: u32, + pub(crate) gpu_se_filter_time_constant_ms: u32, + pub(crate) gpu_se_filter_time_constant_1_ms: u32, + pub(crate) gpu_se_filter_time_constant_clks: U64, + pub(crate) gpu_se_filter_time_constant_1_clks: U64, + pub(crate) unk_c0: u32, + pub(crate) unk_c4: F32, + pub(crate) unk_c8: Array<0x4c, u8>, + pub(crate) unk_114: F32, + pub(crate) unk_118: u32, + pub(crate) unk_11c: u32, + pub(crate) unk_120: u32, + pub(crate) unk_124: u32, + pub(crate) max_pstate_scaled_2: u32, + pub(crate) unk_12c: Array<0x8c, u8>, + } + default_zeroed!(HwDataA130Extra); + + #[repr(C)] + pub(crate) struct T81xxData { + pub(crate) unk_d8c: u32, + pub(crate) unk_d90: u32, + pub(crate) unk_d94: u32, + pub(crate) unk_d98: u32, + pub(crate) unk_d9c: F32, + pub(crate) unk_da0: u32, + pub(crate) unk_da4: F32, + pub(crate) unk_da8: u32, + pub(crate) unk_dac: F32, + pub(crate) unk_db0: u32, + pub(crate) unk_db4: u32, + pub(crate) unk_db8: F32, + pub(crate) unk_dbc: F32, + pub(crate) unk_dc0: u32, + pub(crate) unk_dc4: u32, + pub(crate) unk_dc8: u32, + pub(crate) max_pstate_scaled: u32, + } + default_zeroed!(T81xxData); + + #[versions(AGX)] + #[derive(Default, Copy, Clone)] + #[repr(C)] + pub(crate) struct PowerZone { + pub(crate) val: F32, + pub(crate) target: u32, + pub(crate) target_off: u32, + pub(crate) filter_tc_x4: u32, + pub(crate) filter_tc_xperiod: u32, + #[ver(V >= V13_0B4)] + pub(crate) unk_10: u32, + #[ver(V >= V13_0B4)] + pub(crate) unk_14: u32, + pub(crate) filter_a_neg: F32, + pub(crate) filter_a: F32, + pub(crate) pad: u32, + } + + #[versions(AGX)] + const MAX_CORES_PER_CLUSTER: usize = { + #[ver(G >= G14X)] + { + 16 + } + #[ver(G < G14X)] + { + 8 + } + }; + + #[derive(Debug, Default)] + #[repr(C)] + pub(crate) struct AuxLeakCoef { + pub(crate) afr_1: Array<2, F32>, + pub(crate) cs_1: Array<2, F32>, + pub(crate) afr_2: Array<2, F32>, + pub(crate) cs_2: Array<2, F32>, + } + + #[versions(AGX)] + #[repr(C)] + pub(crate) struct HwDataA { + pub(crate) unk_0: u32, + pub(crate) clocks_per_period: u32, + + #[ver(V >= V13_0B4)] + pub(crate) clocks_per_period_2: u32, + + pub(crate) unk_8: u32, + pub(crate) pwr_status: AtomicU32, + pub(crate) unk_10: F32, + pub(crate) unk_14: u32, + pub(crate) unk_18: u32, + pub(crate) unk_1c: u32, + pub(crate) unk_20: u32, + pub(crate) unk_24: u32, + pub(crate) actual_pstate: u32, + pub(crate) tgt_pstate: u32, + pub(crate) unk_30: u32, + pub(crate) cur_pstate: u32, + pub(crate) unk_38: u32, + + #[ver(V >= V13_0B4)] + pub(crate) unk_3c_0: u32, + + pub(crate) base_pstate_scaled: u32, + pub(crate) unk_40: u32, + pub(crate) max_pstate_scaled: u32, + pub(crate) unk_48: u32, + pub(crate) min_pstate_scaled: u32, + pub(crate) freq_mhz: F32, + pub(crate) unk_54: Array<0x20, u8>, + + #[ver(V >= V13_0B4)] + pub(crate) unk_74_0: u32, + + pub(crate) sram_k: Array<0x10, F32>, + pub(crate) unk_b4: Array<0x100, u8>, + pub(crate) unk_1b4: u32, + pub(crate) temp_c: u32, + pub(crate) avg_power_mw: u32, + pub(crate) update_ts: U64, + pub(crate) unk_1c8: u32, + pub(crate) unk_1cc: Array<0x478, u8>, + pub(crate) pad_644: Pad<0x8>, + pub(crate) unk_64c: u32, + pub(crate) unk_650: u32, + pub(crate) pad_654: u32, + pub(crate) pwr_filter_a_neg: F32, + pub(crate) pad_65c: u32, + pub(crate) pwr_filter_a: F32, + pub(crate) pad_664: u32, + pub(crate) pwr_integral_gain: F32, + pub(crate) pad_66c: u32, + pub(crate) pwr_integral_min_clamp: F32, + pub(crate) max_power_1: F32, + pub(crate) pwr_proportional_gain: F32, + pub(crate) pad_67c: u32, + pub(crate) pwr_pstate_related_k: F32, + pub(crate) pwr_pstate_max_dc_offset: i32, + pub(crate) unk_688: u32, + pub(crate) max_pstate_scaled_2: u32, + pub(crate) pad_690: u32, + pub(crate) unk_694: u32, + pub(crate) max_power_2: u32, + pub(crate) pad_69c: Pad<0x18>, + pub(crate) unk_6b4: u32, + + #[ver(V >= V13_0B4)] + pub(crate) unk_6b8_0: Array<0x10, u8>, + + pub(crate) max_pstate_scaled_3: u32, + pub(crate) unk_6bc: u32, + pub(crate) pad_6c0: Pad<0x14>, + pub(crate) ppm_filter_tc_periods_x4: u32, + pub(crate) unk_6d8: u32, + pub(crate) pad_6dc: u32, + pub(crate) ppm_filter_a_neg: F32, + pub(crate) pad_6e4: u32, + pub(crate) ppm_filter_a: F32, + pub(crate) pad_6ec: u32, + pub(crate) ppm_ki_dt: F32, + pub(crate) pad_6f4: u32, + pub(crate) pwr_integral_min_clamp_2: u32, + pub(crate) unk_6fc: F32, + pub(crate) ppm_kp: F32, + pub(crate) pad_704: u32, + pub(crate) unk_708: u32, + pub(crate) pwr_min_duty_cycle: u32, + pub(crate) max_pstate_scaled_4: u32, + pub(crate) unk_714: u32, + pub(crate) pad_718: u32, + pub(crate) unk_71c: F32, + pub(crate) max_power_3: u32, + pub(crate) cur_power_mw_2: u32, + pub(crate) ppm_filter_tc_ms: u32, + pub(crate) unk_72c: u32, + + #[ver(V >= V13_0B4)] + pub(crate) ppm_filter_tc_clks: u32, + + #[ver(V >= V13_0B4)] + pub(crate) unk_730_4: u32, + + #[ver(V >= V13_0B4)] + pub(crate) unk_730_8: u32, + + #[ver(V >= V13_0B4)] + pub(crate) unk_730_c: u32, + + pub(crate) unk_730: F32, + pub(crate) unk_734: u32, + pub(crate) unk_738: u32, + pub(crate) unk_73c: u32, + pub(crate) unk_740: u32, + pub(crate) unk_744: u32, + pub(crate) unk_748: Array<0x4, F32>, + pub(crate) unk_758: u32, + pub(crate) perf_tgt_utilization: u32, + pub(crate) pad_760: u32, + pub(crate) perf_boost_min_util: u32, + pub(crate) perf_boost_ce_step: u32, + pub(crate) perf_reset_iters: u32, + pub(crate) pad_770: u32, + pub(crate) unk_774: u32, + pub(crate) unk_778: u32, + pub(crate) perf_filter_drop_threshold: u32, + pub(crate) perf_filter_a_neg: F32, + pub(crate) perf_filter_a2_neg: F32, + pub(crate) perf_filter_a: F32, + pub(crate) perf_filter_a2: F32, + pub(crate) perf_ki: F32, + pub(crate) perf_ki2: F32, + pub(crate) perf_integral_min_clamp: F32, + pub(crate) unk_79c: F32, + pub(crate) perf_kp: F32, + pub(crate) perf_kp2: F32, + pub(crate) boost_state_unk_k: F32, + pub(crate) base_pstate_scaled_2: u32, + pub(crate) max_pstate_scaled_5: u32, + pub(crate) base_pstate_scaled_3: u32, + pub(crate) pad_7b8: u32, + pub(crate) perf_cur_utilization: F32, + pub(crate) perf_tgt_utilization_2: u32, + pub(crate) pad_7c4: Pad<0x18>, + pub(crate) unk_7dc: u32, + + #[ver(V >= V13_0B4)] + pub(crate) unk_7e0_0: Array<0x10, u8>, + + pub(crate) base_pstate_scaled_4: u32, + pub(crate) pad_7e4: u32, + pub(crate) unk_7e8: Array<0x14, u8>, + pub(crate) unk_7fc: F32, + pub(crate) pwr_min_duty_cycle_2: F32, + pub(crate) max_pstate_scaled_6: F32, + pub(crate) max_freq_mhz: u32, + pub(crate) pad_80c: u32, + pub(crate) unk_810: u32, + pub(crate) pad_814: u32, + pub(crate) pwr_min_duty_cycle_3: u32, + pub(crate) unk_81c: u32, + pub(crate) pad_820: u32, + pub(crate) min_pstate_scaled_4: F32, + pub(crate) max_pstate_scaled_7: u32, + pub(crate) unk_82c: u32, + pub(crate) unk_alpha_neg: F32, + pub(crate) unk_alpha: F32, + pub(crate) unk_838: u32, + pub(crate) unk_83c: u32, + pub(crate) pad_840: Pad<0x2c>, + pub(crate) unk_86c: u32, + pub(crate) fast_die0_sensor_mask: U64, + #[ver(G >= G14X)] + pub(crate) fast_die1_sensor_mask: U64, + pub(crate) fast_die0_release_temp_cc: u32, + pub(crate) unk_87c: i32, + pub(crate) unk_880: u32, + pub(crate) unk_884: u32, + pub(crate) pad_888: u32, + pub(crate) unk_88c: u32, + pub(crate) pad_890: u32, + pub(crate) unk_894: F32, + pub(crate) pad_898: u32, + pub(crate) fast_die0_ki_dt: F32, + pub(crate) pad_8a0: u32, + pub(crate) unk_8a4: u32, + pub(crate) unk_8a8: F32, + pub(crate) fast_die0_kp: F32, + pub(crate) pad_8b0: u32, + pub(crate) unk_8b4: u32, + pub(crate) pwr_min_duty_cycle_4: u32, + pub(crate) max_pstate_scaled_8: u32, + pub(crate) max_pstate_scaled_9: u32, + pub(crate) fast_die0_prop_tgt_delta: u32, + pub(crate) unk_8c8: u32, + pub(crate) unk_8cc: u32, + pub(crate) pad_8d0: Pad<0x14>, + + #[ver(V >= V13_0B4)] + pub(crate) unk_8e4_0: Array<0x10, u8>, + + pub(crate) unk_8e4: u32, + pub(crate) unk_8e8: u32, + pub(crate) max_pstate_scaled_10: u32, + pub(crate) unk_8f0: u32, + pub(crate) unk_8f4: u32, + pub(crate) pad_8f8: u32, + pub(crate) pad_8fc: u32, + pub(crate) unk_900: Array<0x24, u8>, + + pub(crate) unk_coef_a1: Array<8, Array>, + pub(crate) unk_coef_a2: Array<8, Array>, + + pub(crate) pad_b24: Pad<0x70>, + pub(crate) max_pstate_scaled_11: u32, + pub(crate) freq_with_off: u32, + pub(crate) unk_b9c: u32, + pub(crate) unk_ba0: U64, + pub(crate) unk_ba8: U64, + pub(crate) unk_bb0: u32, + pub(crate) unk_bb4: u32, + + #[ver(V >= V13_3)] + pub(crate) pad_bb8_0: Pad<0x200>, + #[ver(V >= V13_5)] + pub(crate) pad_bb8_200: Pad<0x8>, + + pub(crate) pad_bb8: Pad<0x74>, + pub(crate) unk_c2c: u32, + pub(crate) power_zone_count: u32, + pub(crate) max_power_4: u32, + pub(crate) max_power_5: u32, + pub(crate) max_power_6: u32, + pub(crate) unk_c40: u32, + pub(crate) unk_c44: F32, + pub(crate) avg_power_target_filter_a_neg: F32, + pub(crate) avg_power_target_filter_a: F32, + pub(crate) avg_power_target_filter_tc_x4: u32, + pub(crate) avg_power_target_filter_tc_xperiod: u32, + + #[ver(V >= V13_0B4)] + pub(crate) avg_power_target_filter_tc_clks: u32, + + #[ver(V >= V13_0B4)] + pub(crate) unk_c58_4: u32, + + pub(crate) power_zones: Array<5, PowerZone::ver>, + pub(crate) avg_power_filter_tc_periods_x4: u32, + pub(crate) unk_cfc: u32, + pub(crate) unk_d00: u32, + pub(crate) avg_power_filter_a_neg: F32, + pub(crate) unk_d08: u32, + pub(crate) avg_power_filter_a: F32, + pub(crate) unk_d10: u32, + pub(crate) avg_power_ki_dt: F32, + pub(crate) unk_d18: u32, + pub(crate) unk_d1c: u32, + pub(crate) unk_d20: F32, + pub(crate) avg_power_kp: F32, + pub(crate) unk_d28: u32, + pub(crate) unk_d2c: u32, + pub(crate) avg_power_min_duty_cycle: u32, + pub(crate) max_pstate_scaled_12: u32, + pub(crate) max_pstate_scaled_13: u32, + pub(crate) unk_d3c: u32, + pub(crate) max_power_7: F32, + pub(crate) max_power_8: u32, + pub(crate) unk_d48: u32, + pub(crate) avg_power_filter_tc_ms: u32, + pub(crate) unk_d50: u32, + + #[ver(V >= V13_0B4)] + pub(crate) avg_power_filter_tc_clks: u32, + + #[ver(V >= V13_0B4)] + pub(crate) unk_d54_4: Array<0xc, u8>, + + pub(crate) unk_d54: Array<0x10, u8>, + pub(crate) max_pstate_scaled_14: u32, + pub(crate) unk_d68: Array<0x24, u8>, + + pub(crate) t81xx_data: T81xxData, + + pub(crate) unk_dd0: Array<0x40, u8>, + + #[ver(V >= V13_2)] + pub(crate) unk_e10_pad: Array<0x10, u8>, + + #[ver(V >= V13_0B4)] + pub(crate) unk_e10_0: HwDataA130Extra, + + pub(crate) unk_e10: Array<0xc, u8>, + + pub(crate) fast_die0_sensor_mask_2: U64, + #[ver(G >= G14X)] + pub(crate) fast_die1_sensor_mask_2: U64, + + pub(crate) unk_e24: u32, + pub(crate) unk_e28: u32, + pub(crate) unk_e2c: Pad<0x1c>, + pub(crate) unk_coef_b1: Array<8, Array>, + pub(crate) unk_coef_b2: Array<8, Array>, + + #[ver(G >= G14X)] + pub(crate) pad_1048_0: Pad<0x600>, + + pub(crate) pad_1048: Pad<0x5e4>, + + pub(crate) fast_die0_sensor_mask_alt: U64, + #[ver(G >= G14X)] + pub(crate) fast_die1_sensor_mask_alt: U64, + #[ver(V < V13_0B4)] + pub(crate) fast_die0_sensor_present: U64, + + pub(crate) unk_163c: u32, + + pub(crate) unk_1640: Array<0x2000, u8>, + + #[ver(G >= G14X)] + pub(crate) unk_3640_0: Array<0x2000, u8>, + + pub(crate) unk_3640: u32, + pub(crate) unk_3644: u32, + pub(crate) hws1: HwDataShared1, + + #[ver(V >= V13_0B4)] + pub(crate) unk_hws2: Array<16, u16>, + + pub(crate) hws2: HwDataShared2, + pub(crate) unk_3c00: u32, + pub(crate) unk_3c04: u32, + pub(crate) hws3: HwDataShared3, + pub(crate) unk_3c58: Array<0x3c, u8>, + pub(crate) unk_3c94: u32, + pub(crate) unk_3c98: U64, + pub(crate) unk_3ca0: U64, + pub(crate) unk_3ca8: U64, + pub(crate) unk_3cb0: U64, + pub(crate) ts_last_idle: U64, + pub(crate) ts_last_poweron: U64, + pub(crate) ts_last_poweroff: U64, + pub(crate) unk_3cd0: U64, + pub(crate) unk_3cd8: U64, + + #[ver(V >= V13_0B4)] + pub(crate) unk_3ce0_0: u32, + + pub(crate) unk_3ce0: u32, + pub(crate) unk_3ce4: u32, + pub(crate) unk_3ce8: u32, + pub(crate) unk_3cec: u32, + pub(crate) unk_3cf0: u32, + pub(crate) core_leak_coef: Array<8, F32>, + pub(crate) sram_leak_coef: Array<8, F32>, + + #[ver(V >= V13_0B4)] + pub(crate) aux_leak_coef: AuxLeakCoef, + #[ver(V >= V13_0B4)] + pub(crate) unk_3d34_0: Array<0x18, u8>, + + pub(crate) unk_3d34: Array<0x38, u8>, + } + #[versions(AGX)] + default_zeroed!(HwDataA::ver); + #[versions(AGX)] + no_debug!(HwDataA::ver); + + #[derive(Debug, Default, Clone, Copy)] + #[repr(C)] + pub(crate) struct IOMapping { + pub(crate) phys_addr: U64, + pub(crate) virt_addr: U64, + pub(crate) total_size: u32, + pub(crate) element_size: u32, + pub(crate) readwrite: U64, + } + + #[versions(AGX)] + const IO_MAPPING_COUNT: usize = { + #[ver(V < V13_0B4)] + { + 0x14 + } + #[ver(V >= V13_0B4 && V < V13_3)] + { + 0x17 + } + #[ver(V >= V13_3 && V < V13_5)] + { + 0x18 + } + #[ver(V >= V13_5)] + { + 0x19 + } + }; + + #[derive(Debug)] + #[repr(C)] + pub(crate) struct HwDataBAuxPStates { + pub(crate) cs_max_pstate: u32, + pub(crate) cs_frequencies: Array<0x10, u32>, + pub(crate) cs_voltages: Array<0x10, Array<0x2, u32>>, + pub(crate) cs_voltages_sram: Array<0x10, Array<0x2, u32>>, + pub(crate) cs_unkpad: u32, + pub(crate) afr_max_pstate: u32, + pub(crate) afr_frequencies: Array<0x8, u32>, + pub(crate) afr_voltages: Array<0x8, Array<0x2, u32>>, + pub(crate) afr_voltages_sram: Array<0x8, Array<0x2, u32>>, + pub(crate) afr_unkpad: u32, + } + + #[versions(AGX)] + #[derive(Debug)] + #[repr(C)] + pub(crate) struct HwDataB { + #[ver(V < V13_0B4)] + pub(crate) unk_0: U64, + + pub(crate) unk_8: U64, + + #[ver(V < V13_0B4)] + pub(crate) unk_10: U64, + + pub(crate) unk_18: U64, + pub(crate) unk_20: U64, + pub(crate) unk_28: U64, + pub(crate) unk_30: U64, + pub(crate) timestamp_area_base: U64, + pub(crate) pad_40: Pad<0x20>, + + #[ver(V < V13_0B4)] + pub(crate) yuv_matrices: Array<0xf, Array<3, Array<4, i16>>>, + + #[ver(V >= V13_0B4)] + pub(crate) yuv_matrices: Array<0x3f, Array<3, Array<4, i16>>>, + + pub(crate) pad_1c8: Pad<0x8>, + pub(crate) io_mappings: Array, + + #[ver(V >= V13_0B4)] + pub(crate) sgx_sram_ptr: U64, + + pub(crate) chip_id: u32, + pub(crate) unk_454: u32, + pub(crate) unk_458: u32, + pub(crate) unk_45c: u32, + pub(crate) unk_460: u32, + pub(crate) unk_464: u32, + pub(crate) unk_468: u32, + pub(crate) unk_46c: u32, + pub(crate) unk_470: u32, + pub(crate) unk_474: u32, + pub(crate) unk_478: u32, + pub(crate) unk_47c: u32, + pub(crate) unk_480: u32, + pub(crate) unk_484: u32, + pub(crate) unk_488: u32, + pub(crate) unk_48c: u32, + pub(crate) base_clock_khz: u32, + pub(crate) power_sample_period: u32, + pub(crate) pad_498: Pad<0x4>, + pub(crate) unk_49c: u32, + pub(crate) unk_4a0: u32, + pub(crate) unk_4a4: u32, + pub(crate) pad_4a8: Pad<0x4>, + pub(crate) unk_4ac: u32, + pub(crate) pad_4b0: Pad<0x8>, + pub(crate) unk_4b8: u32, + pub(crate) unk_4bc: Array<0x4, u8>, + pub(crate) unk_4c0: u32, + pub(crate) unk_4c4: u32, + pub(crate) unk_4c8: u32, + pub(crate) unk_4cc: u32, + pub(crate) unk_4d0: u32, + pub(crate) unk_4d4: u32, + pub(crate) unk_4d8: Array<0x4, u8>, + pub(crate) unk_4dc: u32, + pub(crate) unk_4e0: U64, + pub(crate) unk_4e8: u32, + pub(crate) unk_4ec: u32, + pub(crate) unk_4f0: u32, + pub(crate) unk_4f4: u32, + pub(crate) unk_4f8: u32, + pub(crate) unk_4fc: u32, + pub(crate) unk_500: u32, + + #[ver(V >= V13_0B4)] + pub(crate) unk_504_0: u32, + + pub(crate) unk_504: u32, + pub(crate) unk_508: u32, + pub(crate) unk_50c: u32, + pub(crate) unk_510: u32, + pub(crate) unk_514: u32, + pub(crate) unk_518: u32, + pub(crate) unk_51c: u32, + pub(crate) unk_520: u32, + pub(crate) unk_524: u32, + pub(crate) unk_528: u32, + pub(crate) unk_52c: u32, + pub(crate) unk_530: u32, + + #[ver(V >= V13_0B4)] + pub(crate) unk_534_0: u32, + + pub(crate) unk_534: u32, + pub(crate) unk_538: u32, + + pub(crate) num_frags: u32, + pub(crate) unk_540: u32, + pub(crate) unk_544: u32, + pub(crate) unk_548: u32, + pub(crate) unk_54c: u32, + pub(crate) unk_550: u32, + pub(crate) unk_554: u32, + pub(crate) uat_ttb_base: U64, + pub(crate) gpu_core_id: u32, + pub(crate) gpu_rev_id: u32, + pub(crate) num_cores: u32, + pub(crate) max_pstate: u32, + + #[ver(V < V13_0B4)] + pub(crate) num_pstates: u32, + + pub(crate) frequencies: Array<0x10, u32>, + pub(crate) voltages: Array<0x10, [u32; 0x8]>, + pub(crate) voltages_sram: Array<0x10, [u32; 0x8]>, + + #[ver(V >= V13_3)] + pub(crate) unk_9f4_0: Pad<64>, + + pub(crate) sram_k: Array<0x10, F32>, + pub(crate) unk_9f4: Array<0x10, u32>, + pub(crate) rel_max_powers: Array<0x10, u32>, + pub(crate) rel_boost_freqs: Array<0x10, u32>, + + #[ver(V >= V13_3)] + pub(crate) unk_arr_0: Array<32, u32>, + + #[ver(V < V13_0B4)] + pub(crate) min_sram_volt: u32, + + #[ver(V < V13_0B4)] + pub(crate) unk_ab8: u32, + + #[ver(V < V13_0B4)] + pub(crate) unk_abc: u32, + + #[ver(V < V13_0B4)] + pub(crate) unk_ac0: u32, + + #[ver(V >= V13_0B4)] + pub(crate) aux_ps: HwDataBAuxPStates, + + #[ver(V >= V13_3)] + pub(crate) pad_ac4_0: Array<0x44c, u8>, + + pub(crate) pad_ac4: Pad<0x8>, + pub(crate) unk_acc: u32, + pub(crate) unk_ad0: u32, + pub(crate) pad_ad4: Pad<0x10>, + pub(crate) unk_ae4: Array<0x4, u32>, + pub(crate) pad_af4: Pad<0x4>, + pub(crate) unk_af8: u32, + pub(crate) pad_afc: Pad<0x8>, + pub(crate) unk_b04: u32, + pub(crate) unk_b08: u32, + pub(crate) unk_b0c: u32, + + #[ver(G >= G14X)] + pub(crate) pad_b10_0: Array<0x8, u8>, + + pub(crate) unk_b10: u32, + pub(crate) timer_offset: U64, + pub(crate) unk_b1c: u32, + pub(crate) unk_b20: u32, + pub(crate) unk_b24: u32, + pub(crate) unk_b28: u32, + pub(crate) unk_b2c: u32, + pub(crate) unk_b30: u32, + pub(crate) unk_b34: u32, + + #[ver(V >= V13_0B4)] + pub(crate) unk_b38_0: u32, + + #[ver(V >= V13_0B4)] + pub(crate) unk_b38_4: u32, + + #[ver(V >= V13_3)] + pub(crate) unk_b38_8: u32, + + pub(crate) unk_b38: Array<0xc, u32>, + pub(crate) unk_b68: u32, + + #[ver(V >= V13_0B4)] + pub(crate) unk_b6c: Array<0xd0, u8>, + + #[ver(G >= G14X)] + pub(crate) unk_c3c_0: Array<0x8, u8>, + + #[ver(G < G14X && V >= V13_5)] + pub(crate) unk_c3c_8: Array<0x10, u8>, + + #[ver(V >= V13_5)] + pub(crate) unk_c3c_18: Array<0x20, u8>, + + #[ver(V >= V13_0B4)] + pub(crate) unk_c3c: u32, + } + #[versions(AGX)] + default_zeroed!(HwDataB::ver); + + #[derive(Debug)] + #[repr(C, packed)] + pub(crate) struct GpuStatsVtx { + // This changes all the time and we don't use it, let's just make it a big buffer + pub(crate) opaque: Array<0x3000, u8>, + } + default_zeroed!(GpuStatsVtx); + + #[versions(AGX)] + #[derive(Debug)] + #[repr(C)] + pub(crate) struct GpuStatsFrag { + // This changes all the time and we don't use it, let's just make it a big buffer + // except for these two fields which may need init. + #[ver(G >= G14X)] + pub(crate) unk1_0: Array<0x910, u8>, + pub(crate) unk1: Array<0x100, u8>, + pub(crate) cur_stamp_id: i32, + pub(crate) unk2: Array<0x14, u8>, + pub(crate) unk_id: i32, + pub(crate) unk3: Array<0x1000, u8>, + } + + #[versions(AGX)] + impl Default for GpuStatsFrag::ver { + fn default() -> Self { + Self { + #[ver(G >= G14X)] + unk1_0: Default::default(), + unk1: Default::default(), + cur_stamp_id: -1, + unk2: Default::default(), + unk_id: -1, + unk3: Default::default(), + } + } + } + + #[derive(Debug)] + #[repr(C)] + pub(crate) struct GpuGlobalStatsVtx { + pub(crate) total_cmds: u32, + pub(crate) stats: GpuStatsVtx, + } + default_zeroed!(GpuGlobalStatsVtx); + + #[versions(AGX)] + #[derive(Debug, Default)] + #[repr(C)] + pub(crate) struct GpuGlobalStatsFrag { + pub(crate) total_cmds: u32, + pub(crate) unk_4: u32, + pub(crate) stats: GpuStatsFrag::ver, + } + + #[derive(Debug)] + #[repr(C)] + pub(crate) struct GpuStatsComp { + // This changes all the time and we don't use it, let's just make it a big buffer + pub(crate) opaque: Array<0x3000, u8>, + } + default_zeroed!(GpuStatsComp); + + #[versions(AGX)] + #[derive(Debug)] + #[repr(C)] + pub(crate) struct RuntimeScratch { + pub(crate) unk_280: Array<0x6800, u8>, + pub(crate) unk_6a80: u32, + pub(crate) gpu_idle: u32, + pub(crate) unkpad_6a88: Pad<0x14>, + pub(crate) unk_6a9c: u32, + pub(crate) unk_ctr0: u32, + pub(crate) unk_ctr1: u32, + pub(crate) unk_6aa8: u32, + pub(crate) unk_6aac: u32, + pub(crate) unk_ctr2: u32, + pub(crate) unk_6ab4: u32, + pub(crate) unk_6ab8: u32, + pub(crate) unk_6abc: u32, + pub(crate) unk_6ac0: u32, + pub(crate) unk_6ac4: u32, + pub(crate) unk_ctr3: u32, + pub(crate) unk_6acc: u32, + pub(crate) unk_6ad0: u32, + pub(crate) unk_6ad4: u32, + pub(crate) unk_6ad8: u32, + pub(crate) unk_6adc: u32, + pub(crate) unk_6ae0: u32, + pub(crate) unk_6ae4: u32, + pub(crate) unk_6ae8: u32, + pub(crate) unk_6aec: u32, + pub(crate) unk_6af0: u32, + pub(crate) unk_ctr4: u32, + pub(crate) unk_ctr5: u32, + pub(crate) unk_6afc: u32, + pub(crate) pad_6b00: Pad<0x38>, + + #[ver(G >= G14X)] + pub(crate) pad_6b00_extra: Array<0x4800, u8>, + + pub(crate) unk_6b38: u32, + pub(crate) pad_6b3c: Pad<0x84>, + } + #[versions(AGX)] + default_zeroed!(RuntimeScratch::ver); + + #[versions(AGX)] + #[repr(C)] + pub(crate) struct RuntimePointers<'a> { + pub(crate) pipes: Array<4, PipeChannels::ver>, + + pub(crate) device_control: + ChannelRing, + pub(crate) event: ChannelRing, + pub(crate) fw_log: ChannelRing, + pub(crate) ktrace: ChannelRing, + pub(crate) stats: ChannelRing, + + pub(crate) __pad0: Pad<0x50>, + pub(crate) unk_160: U64, + pub(crate) unk_168: U64, + pub(crate) stats_vtx: GpuPointer<'a, super::GpuGlobalStatsVtx>, + pub(crate) stats_frag: GpuPointer<'a, super::GpuGlobalStatsFrag::ver>, + pub(crate) stats_comp: GpuPointer<'a, super::GpuStatsComp>, + pub(crate) hwdata_a: GpuPointer<'a, super::HwDataA::ver>, + pub(crate) unkptr_190: GpuPointer<'a, &'a [u8]>, + pub(crate) unkptr_198: GpuPointer<'a, &'a [u8]>, + pub(crate) hwdata_b: GpuPointer<'a, super::HwDataB::ver>, + pub(crate) hwdata_b_2: GpuPointer<'a, super::HwDataB::ver>, + pub(crate) fwlog_buf: Option>, + pub(crate) unkptr_1b8: GpuPointer<'a, &'a [u8]>, + + #[ver(G < G14X)] + pub(crate) unkptr_1c0: GpuPointer<'a, &'a [u8]>, + #[ver(G < G14X)] + pub(crate) unkptr_1c8: GpuPointer<'a, &'a [u8]>, + + pub(crate) unk_1d0: u32, + pub(crate) unk_1d4: u32, + pub(crate) unk_1d8: Array<0x3c, u8>, + pub(crate) buffer_mgr_ctl_gpu_addr: U64, + pub(crate) buffer_mgr_ctl_fw_addr: U64, + pub(crate) __pad1: Pad<0x5c>, + pub(crate) gpu_scratch: RuntimeScratch::ver, + } + #[versions(AGX)] + no_debug!(RuntimePointers::ver<'_>); + + #[derive(Debug)] + #[repr(C)] + pub(crate) struct PendingStamp { + pub(crate) info: AtomicU32, + pub(crate) wait_value: AtomicU32, + } + default_zeroed!(PendingStamp); + + #[derive(Debug, Clone, Copy)] + #[repr(C, packed)] + pub(crate) struct FaultInfo { + pub(crate) unk_0: u32, + pub(crate) unk_4: u32, + pub(crate) queue_uuid: u32, + pub(crate) unk_c: u32, + pub(crate) unk_10: u32, + pub(crate) unk_14: u32, + } + default_zeroed!(FaultInfo); + + #[versions(AGX)] + #[derive(Debug, Clone, Copy)] + #[repr(C, packed)] + pub(crate) struct GlobalsSub { + pub(crate) unk_54: u16, + pub(crate) unk_56: u16, + pub(crate) unk_58: u16, + pub(crate) unk_5a: U32, + pub(crate) unk_5e: U32, + pub(crate) unk_62: U32, + + #[ver(V >= V13_0B4)] + pub(crate) unk_66_0: Array<0xc, u8>, + + pub(crate) unk_66: U32, + pub(crate) unk_6a: Array<0x16, u8>, + } + #[versions(AGX)] + default_zeroed!(GlobalsSub::ver); + + #[derive(Debug, Clone, Copy)] + #[repr(C)] + pub(crate) struct PowerZoneGlobal { + pub(crate) target: u32, + pub(crate) target_off: u32, + pub(crate) filter_tc: u32, + } + default_zeroed!(PowerZoneGlobal); + + #[versions(AGX)] + #[derive(Debug)] + #[repr(C)] + pub(crate) struct Globals { + pub(crate) ktrace_enable: u32, + pub(crate) unk_4: Array<0x20, u8>, + + #[ver(V >= V13_2)] + pub(crate) unk_24_0: u32, + + pub(crate) unk_24: u32, + + #[ver(V >= V13_0B4)] + pub(crate) debug: u32, + + #[ver(V >= V13_3)] + pub(crate) unk_28_4: u32, + + pub(crate) unk_28: u32, + + #[ver(V >= V13_0B4)] + pub(crate) unk_2c_0: u32, + + pub(crate) unk_2c: u32, + pub(crate) unk_30: u32, + pub(crate) unk_34: u32, + pub(crate) unk_38: Array<0x1c, u8>, + + pub(crate) sub: GlobalsSub::ver, + + pub(crate) unk_80: Array<0xf80, u8>, + pub(crate) unk_1000: Array<0x7000, u8>, + pub(crate) unk_8000: Array<0x900, u8>, + + #[ver(G >= G14X)] + pub(crate) unk_8900_pad: Array<0x484c, u8>, + + #[ver(V >= V13_3)] + pub(crate) unk_8900_pad2: Array<0x54, u8>, + + pub(crate) unk_8900: u32, + pub(crate) pending_submissions: AtomicU32, + pub(crate) max_power: u32, + pub(crate) max_pstate_scaled: u32, + pub(crate) max_pstate_scaled_2: u32, + pub(crate) unk_8914: u32, + pub(crate) unk_8918: u32, + pub(crate) max_pstate_scaled_3: u32, + pub(crate) unk_8920: u32, + pub(crate) power_zone_count: u32, + pub(crate) avg_power_filter_tc_periods: u32, + pub(crate) avg_power_ki_dt: F32, + pub(crate) avg_power_kp: F32, + pub(crate) avg_power_min_duty_cycle: u32, + pub(crate) avg_power_target_filter_tc: u32, + pub(crate) power_zones: Array<5, PowerZoneGlobal>, + pub(crate) unk_8978: Array<0x44, u8>, + + #[ver(V >= V13_0B4)] + pub(crate) unk_89bc_0: Array<0x3c, u8>, + + pub(crate) unk_89bc: u32, + pub(crate) fast_die0_release_temp: u32, + pub(crate) unk_89c4: i32, + pub(crate) fast_die0_prop_tgt_delta: u32, + pub(crate) fast_die0_kp: F32, + pub(crate) fast_die0_ki_dt: F32, + pub(crate) unk_89d4: Array<0xc, u8>, + pub(crate) unk_89e0: u32, + pub(crate) max_power_2: u32, + pub(crate) ppm_kp: F32, + pub(crate) ppm_ki_dt: F32, + pub(crate) unk_89f0: u32, + + #[ver(V >= V13_0B4)] + pub(crate) unk_89f4_0: Array<0x8, u8>, + + #[ver(V >= V13_0B4)] + pub(crate) unk_89f4_8: u32, + + #[ver(V >= V13_0B4)] + pub(crate) unk_89f4_c: Array<0x50, u8>, + + #[ver(V >= V13_3)] + pub(crate) unk_89f4_5c: Array<0xc, u8>, + + pub(crate) unk_89f4: u32, + pub(crate) hws1: HwDataShared1, + pub(crate) hws2: HwDataShared2, + + #[ver(V >= V13_0B4)] + pub(crate) idle_off_standby_timer: u32, + + #[ver(V >= V13_0B4)] + pub(crate) unk_hws2_4: Array<0x8, F32>, + + #[ver(V >= V13_0B4)] + pub(crate) unk_hws2_24: u32, + + pub(crate) unk_hws2_28: u32, + + pub(crate) hws3: HwDataShared3, + pub(crate) unk_9004: Array<8, u8>, + pub(crate) unk_900c: u32, + + #[ver(V >= V13_0B4)] + pub(crate) unk_9010_0: u32, + + #[ver(V >= V13_0B4)] + pub(crate) unk_9010_4: Array<0x14, u8>, + + pub(crate) unk_9010: Array<0x2c, u8>, + pub(crate) unk_903c: u32, + pub(crate) unk_9040: Array<0xc0, u8>, + pub(crate) unk_9100: Array<0x6f00, u8>, + pub(crate) unk_10000: Array<0xe50, u8>, + pub(crate) unk_10e50: u32, + pub(crate) unk_10e54: Array<0x2c, u8>, + + #[ver((G >= G14X && V < V13_3) || (G <= G14 && V >= V13_3))] + pub(crate) unk_x_pad: Array<0x4, u8>, + + // bit 0: sets sgx_reg 0x17620 + // bit 1: sets sgx_reg 0x17630 + pub(crate) fault_control: u32, + pub(crate) do_init: u32, + pub(crate) unk_10e88: Array<0x188, u8>, + pub(crate) idle_ts: U64, + pub(crate) idle_unk: U64, + pub(crate) progress_check_interval_3d: u32, + pub(crate) progress_check_interval_ta: u32, + pub(crate) progress_check_interval_cl: u32, + + #[ver(V >= V13_0B4)] + pub(crate) unk_1102c_0: u32, + + #[ver(V >= V13_0B4)] + pub(crate) unk_1102c_4: u32, + + #[ver(V >= V13_0B4)] + pub(crate) unk_1102c_8: u32, + + #[ver(V >= V13_0B4)] + pub(crate) unk_1102c_c: u32, + + #[ver(V >= V13_0B4)] + pub(crate) unk_1102c_10: u32, + + pub(crate) unk_1102c: u32, + pub(crate) idle_off_delay_ms: AtomicU32, + pub(crate) fender_idle_off_delay_ms: u32, + pub(crate) fw_early_wake_timeout_ms: u32, + #[ver(V == V13_3)] + pub(crate) ps_pad_0: Pad<0x8>, + pub(crate) pending_stamps: Array<0x100, PendingStamp>, + #[ver(V != V13_3)] + pub(crate) ps_pad_0: Pad<0x8>, + pub(crate) unkpad_ps: Pad<0x78>, + pub(crate) unk_117bc: u32, + pub(crate) fault_info: FaultInfo, + pub(crate) counter: u32, + pub(crate) unk_118dc: u32, + + #[ver(V >= V13_0B4)] + pub(crate) unk_118e0_0: Array<0x9c, u8>, + + #[ver(G >= G14X)] + pub(crate) unk_118e0_9c: Array<0x580, u8>, + + #[ver(V >= V13_3)] + pub(crate) unk_118e0_9c_x: Array<0x8, u8>, + + pub(crate) cl_context_switch_timeout_ms: u32, + + #[ver(V >= V13_0B4)] + pub(crate) cl_kill_timeout_ms: u32, + + pub(crate) cdm_context_store_latency_threshold: u32, + pub(crate) unk_118e8: u32, + pub(crate) unk_118ec: Array<0x400, u8>, + pub(crate) unk_11cec: Array<0x54, u8>, + + #[ver(V >= V13_0B4)] + pub(crate) unk_11d40: Array<0x19c, u8>, + + #[ver(V >= V13_0B4)] + pub(crate) unk_11edc: u32, + + #[ver(V >= V13_0B4)] + pub(crate) unk_11ee0: Array<0x1c, u8>, + + #[ver(V >= V13_0B4)] + pub(crate) unk_11efc: u32, + + #[ver(V >= V13_3)] + pub(crate) unk_11f00: Array<0x280, u8>, + } + #[versions(AGX)] + default_zeroed!(Globals::ver); + + #[derive(Debug, Default, Clone, Copy)] + #[repr(C, packed)] + pub(crate) struct UatLevelInfo { + pub(crate) unk_3: u8, + pub(crate) unk_1: u8, + pub(crate) unk_2: u8, + pub(crate) index_shift: u8, + pub(crate) num_entries: u16, + pub(crate) unk_4: u16, + pub(crate) unk_8: U64, + pub(crate) unk_10: U64, + pub(crate) index_mask: U64, + } + + #[versions(AGX)] + #[derive(Debug)] + #[repr(C)] + pub(crate) struct InitData<'a> { + #[ver(V >= V13_0B4)] + pub(crate) ver_info: Array<0x4, u16>, + + pub(crate) unk_buf: GpuPointer<'a, &'a [u8]>, + pub(crate) unk_8: u32, + pub(crate) unk_c: u32, + pub(crate) runtime_pointers: GpuPointer<'a, super::RuntimePointers::ver>, + pub(crate) globals: GpuPointer<'a, super::Globals::ver>, + pub(crate) fw_status: GpuPointer<'a, super::FwStatus>, + pub(crate) uat_page_size: u16, + pub(crate) uat_page_bits: u8, + pub(crate) uat_num_levels: u8, + pub(crate) uat_level_info: Array<0x3, UatLevelInfo>, + pub(crate) __pad0: Pad<0x14>, + pub(crate) host_mapped_fw_allocations: u32, + pub(crate) unk_ac: u32, + pub(crate) unk_b0: u32, + pub(crate) unk_b4: u32, + pub(crate) unk_b8: u32, + } +} + +#[derive(Debug)] +pub(crate) struct ChannelRing +where + for<'a> ::Raw<'a>: Debug, +{ + pub(crate) state: GpuObject, + pub(crate) ring: GpuArray, +} + +impl ChannelRing +where + for<'a> ::Raw<'a>: Debug, +{ + pub(crate) fn to_raw(&self) -> raw::ChannelRing { + raw::ChannelRing { + state: Some(self.state.weak_pointer()), + ring: Some(self.ring.weak_pointer()), + } + } +} + +trivial_gpustruct!(FwStatus); +trivial_gpustruct!(GpuGlobalStatsVtx); +#[versions(AGX)] +trivial_gpustruct!(GpuGlobalStatsFrag::ver); +trivial_gpustruct!(GpuStatsComp); + +#[versions(AGX)] +trivial_gpustruct!(HwDataA::ver); + +#[versions(AGX)] +trivial_gpustruct!(HwDataB::ver); + +#[versions(AGX)] +#[derive(Debug)] +pub(crate) struct Stats { + pub(crate) vtx: GpuObject, + pub(crate) frag: GpuObject, + pub(crate) comp: GpuObject, +} + +#[versions(AGX)] +#[derive(Debug)] +pub(crate) struct RuntimePointers { + pub(crate) stats: Stats::ver, + + pub(crate) hwdata_a: GpuObject, + pub(crate) unkptr_190: GpuArray, + pub(crate) unkptr_198: GpuArray, + pub(crate) hwdata_b: GpuObject, + + pub(crate) unkptr_1b8: GpuArray, + pub(crate) unkptr_1c0: GpuArray, + pub(crate) unkptr_1c8: GpuArray, + + pub(crate) buffer_mgr_ctl: gem::ObjectRef, + pub(crate) buffer_mgr_ctl_low_mapping: Option, + pub(crate) buffer_mgr_ctl_high_mapping: Option, +} + +#[versions(AGX)] +impl GpuStruct for RuntimePointers::ver { + type Raw<'a> = raw::RuntimePointers::ver<'a>; +} + +#[versions(AGX)] +trivial_gpustruct!(Globals::ver); + +#[versions(AGX)] +#[derive(Debug)] +pub(crate) struct InitData { + pub(crate) unk_buf: GpuArray, + pub(crate) runtime_pointers: GpuObject, + pub(crate) globals: GpuObject, + pub(crate) fw_status: GpuObject, +} + +#[versions(AGX)] +impl GpuStruct for InitData::ver { + type Raw<'a> = raw::InitData::ver<'a>; +} diff --git a/drivers/gpu/drm/asahi/fw/job.rs b/drivers/gpu/drm/asahi/fw/job.rs new file mode 100644 index 00000000000000..e4f2f9225ea050 --- /dev/null +++ b/drivers/gpu/drm/asahi/fw/job.rs @@ -0,0 +1,160 @@ +// SPDX-License-Identifier: GPL-2.0-only OR MIT + +//! Common GPU job firmware structures + +use super::types::*; +use crate::{ + default_zeroed, + mmu, + trivial_gpustruct, // +}; +use kernel::prelude::Result; +use kernel::sync::Arc; + +pub(crate) mod raw { + use super::*; + + #[derive(Debug, Clone, Copy)] + #[repr(C)] + pub(crate) struct JobMeta { + pub(crate) unk_0: u16, + pub(crate) unk_2: u8, + pub(crate) no_preemption: u8, + pub(crate) stamp: GpuWeakPointer, + pub(crate) fw_stamp: GpuWeakPointer, + pub(crate) stamp_value: EventValue, + pub(crate) stamp_slot: u32, + pub(crate) evctl_index: u32, + pub(crate) flush_stamps: u32, + pub(crate) uuid: u32, + pub(crate) event_seq: u32, + } + + #[derive(Debug)] + #[repr(C)] + pub(crate) struct EncoderParams { + pub(crate) unk_8: u32, + pub(crate) sync_grow: u32, + pub(crate) unk_10: u32, + pub(crate) encoder_id: u32, + pub(crate) unk_18: u32, + pub(crate) unk_mask: u32, + pub(crate) sampler_array: U64, + pub(crate) sampler_count: u32, + pub(crate) sampler_max: u32, + } + + #[derive(Debug)] + #[repr(C)] + pub(crate) struct JobTimestamps { + pub(crate) start: AtomicU64, + pub(crate) end: AtomicU64, + } + default_zeroed!(JobTimestamps); + + #[derive(Debug)] + #[repr(C)] + pub(crate) struct RenderTimestamps { + pub(crate) vtx: JobTimestamps, + pub(crate) frag: JobTimestamps, + } + default_zeroed!(RenderTimestamps); + + #[derive(Debug)] + #[repr(C)] + pub(crate) struct Register { + pub(crate) number: u32, + pub(crate) value: U64, + } + default_zeroed!(Register); + + impl Register { + fn new(number: u32, value: u64) -> Register { + Register { + number, + value: U64(value), + } + } + } + + #[derive(Debug)] + #[repr(C)] + pub(crate) struct RegisterArray { + pub(crate) registers: Array<128, Register>, + pub(crate) pad: Array<0x100, u8>, + + pub(crate) addr: GpuWeakPointer>, + pub(crate) count: u16, + pub(crate) length: u16, + pub(crate) unk_pad: u32, + } + + impl RegisterArray { + pub(crate) fn new( + self_ptr: GpuWeakPointer>, + cb: impl FnOnce(&mut RegisterArray), + ) -> RegisterArray { + let mut array = RegisterArray { + registers: Default::default(), + pad: Default::default(), + addr: self_ptr, + count: 0, + length: 0, + unk_pad: 0, + }; + + cb(&mut array); + + array + } + + pub(crate) fn add(&mut self, number: u32, value: u64) { + self.registers[self.count as usize] = Register::new(number, value); + self.count += 1; + self.length += core::mem::size_of::() as u16; + } + } + + #[derive(Debug)] + #[repr(C)] + pub(crate) struct TimestampPointers<'a> { + pub(crate) start_addr: Option>, + pub(crate) end_addr: Option>, + } +} + +trivial_gpustruct!(JobTimestamps); +trivial_gpustruct!(RenderTimestamps); + +#[derive(Debug)] +pub(crate) struct UserTimestamp { + pub(crate) mapping: Arc, + pub(crate) offset: usize, +} + +#[derive(Debug, Default)] +pub(crate) struct UserTimestamps { + pub(crate) start: Option, + pub(crate) end: Option, +} + +impl UserTimestamps { + pub(crate) fn any(&self) -> bool { + self.start.is_some() || self.end.is_some() + } + + pub(crate) fn pointers(&self) -> Result> { + Ok(raw::TimestampPointers { + start_addr: self + .start + .as_ref() + .map(|a| GpuPointer::from_mapping(&a.mapping, a.offset)) + .transpose()?, + end_addr: self + .end + .as_ref() + .map(|a| GpuPointer::from_mapping(&a.mapping, a.offset)) + .transpose()?, + }) + } +} diff --git a/drivers/gpu/drm/asahi/fw/microseq.rs b/drivers/gpu/drm/asahi/fw/microseq.rs new file mode 100644 index 00000000000000..ff59deda6be615 --- /dev/null +++ b/drivers/gpu/drm/asahi/fw/microseq.rs @@ -0,0 +1,412 @@ +// SPDX-License-Identifier: GPL-2.0-only OR MIT + +//! GPU firmware microsequence operations + +use super::types::*; +use super::{ + buffer, + compute, + fragment, + initdata, + job, + vertex, + workqueue, // +}; +use crate::default_zeroed; + +pub(crate) trait Operation {} + +#[derive(Debug, Copy, Clone)] +#[repr(u32)] +enum OpCode { + WaitForIdle = 0x01, + WaitForIdle2 = 0x02, + RetireStamp = 0x18, + #[allow(dead_code)] + Timestamp = 0x19, + StartVertex = 0x22, + FinalizeVertex = 0x23, + StartFragment = 0x24, + FinalizeFragment = 0x25, + StartCompute = 0x29, + FinalizeCompute = 0x2a, +} + +#[derive(Debug, Copy, Clone)] +#[repr(u32)] +pub(crate) enum Pipe { + Vertex = 1 << 0, + Fragment = 1 << 8, + Compute = 1 << 15, +} + +pub(crate) const MAX_ATTACHMENTS: usize = 16; + +#[derive(Debug, Clone, Copy)] +#[repr(C)] +pub(crate) struct Attachment { + pub(crate) address: U64, + pub(crate) size: u32, + pub(crate) unk_c: u16, + pub(crate) unk_e: u16, +} +default_zeroed!(Attachment); + +#[derive(Debug, Clone, Copy, Default)] +#[repr(C)] +pub(crate) struct Attachments { + pub(crate) list: Array, + pub(crate) count: u32, +} + +#[derive(Debug, Copy, Clone)] +#[repr(transparent)] +pub(crate) struct OpHeader(u32); + +impl OpHeader { + const fn new(opcode: OpCode) -> OpHeader { + OpHeader(opcode as u32) + } + const fn with_args(opcode: OpCode, args: u32) -> OpHeader { + OpHeader(opcode as u32 | args) + } +} + +macro_rules! simple_op { + ($name:ident) => { + #[allow(dead_code)] + #[derive(Debug, Copy, Clone)] + pub(crate) struct $name(OpHeader); + + impl $name { + pub(crate) const HEADER: $name = $name(OpHeader::new(OpCode::$name)); + } + }; +} + +pub(crate) mod op { + use super::*; + + simple_op!(StartVertex); + simple_op!(FinalizeVertex); + simple_op!(StartFragment); + simple_op!(FinalizeFragment); + simple_op!(StartCompute); + simple_op!(FinalizeCompute); + simple_op!(WaitForIdle2); + + #[allow(dead_code)] + #[derive(Debug, Copy, Clone)] + pub(crate) struct RetireStamp(OpHeader); + impl RetireStamp { + pub(crate) const HEADER: RetireStamp = + RetireStamp(OpHeader::with_args(OpCode::RetireStamp, 0x40000000)); + } + + #[allow(dead_code)] + #[derive(Debug, Copy, Clone)] + pub(crate) struct WaitForIdle(OpHeader); + impl WaitForIdle { + pub(crate) const fn new(pipe: Pipe) -> WaitForIdle { + WaitForIdle(OpHeader::with_args(OpCode::WaitForIdle, (pipe as u32) << 8)) + } + } + + #[allow(dead_code)] + #[derive(Debug, Copy, Clone)] + pub(crate) struct Timestamp(OpHeader); + impl Timestamp { + #[allow(dead_code)] + pub(crate) const fn new(flag: bool) -> Timestamp { + Timestamp(OpHeader::with_args(OpCode::Timestamp, (flag as u32) << 31)) + } + } +} + +#[derive(Debug)] +#[repr(C)] +pub(crate) struct WaitForIdle { + pub(crate) header: op::WaitForIdle, +} + +impl Operation for WaitForIdle {} + +#[derive(Debug)] +#[repr(C)] +pub(crate) struct WaitForIdle2 { + pub(crate) header: op::WaitForIdle2, +} + +impl Operation for WaitForIdle2 {} + +#[derive(Debug)] +#[repr(C)] +pub(crate) struct RetireStamp { + pub(crate) header: op::RetireStamp, +} + +impl Operation for RetireStamp {} + +#[versions(AGX)] +#[derive(Debug)] +#[repr(C)] +pub(crate) struct Timestamp<'a> { + pub(crate) header: op::Timestamp, + pub(crate) command_time: GpuWeakPointer, + pub(crate) ts_pointers: GpuWeakPointer>, + // Unused? + pub(crate) update_ts: GpuWeakPointer>>, + pub(crate) work_queue: GpuWeakPointer, + pub(crate) user_ts_pointers: GpuWeakPointer>, + + #[ver(V >= V13_0B4)] + pub(crate) unk_ts: GpuWeakPointer, + + pub(crate) uuid: u32, + pub(crate) unk_30_padding: u32, +} + +#[versions(AGX)] +impl<'a> Operation for Timestamp::ver<'a> {} + +#[versions(AGX)] +#[derive(Debug)] +#[repr(C)] +pub(crate) struct StartVertex<'a> { + pub(crate) header: op::StartVertex, + pub(crate) tiling_params: Option>, + pub(crate) job_params1: Option>>, + #[ver(G >= G14X)] + pub(crate) registers: GpuWeakPointer, + pub(crate) buffer: GpuWeakPointer, + pub(crate) scene: GpuWeakPointer, + pub(crate) stats: GpuWeakPointer, + pub(crate) work_queue: GpuWeakPointer, + pub(crate) vm_slot: u32, + pub(crate) unk_38: u32, + pub(crate) event_generation: u32, + pub(crate) buffer_slot: u32, + pub(crate) unk_44: u32, + pub(crate) event_seq: U64, + pub(crate) unk_50: u32, + pub(crate) unk_pointer: GpuWeakPointer, + pub(crate) unk_job_buf: GpuWeakPointer, + pub(crate) unk_64: u32, + pub(crate) unk_68: u32, + pub(crate) uuid: u32, + pub(crate) attachments: Attachments, + pub(crate) padding: u32, + + #[ver(V >= V13_0B4)] + pub(crate) counter: U64, + + #[ver(V >= V13_0B4)] + pub(crate) notifier_buf: GpuWeakPointer>, + + pub(crate) unk_178: u32, +} + +#[versions(AGX)] +impl<'a> Operation for StartVertex::ver<'a> {} + +#[versions(AGX)] +#[derive(Debug)] +#[repr(C)] +pub(crate) struct FinalizeVertex { + pub(crate) header: op::FinalizeVertex, + pub(crate) scene: GpuWeakPointer, + pub(crate) buffer: GpuWeakPointer, + pub(crate) stats: GpuWeakPointer, + pub(crate) work_queue: GpuWeakPointer, + pub(crate) vm_slot: u32, + pub(crate) unk_28: u32, + pub(crate) unk_pointer: GpuWeakPointer, + pub(crate) unk_34: u32, + pub(crate) uuid: u32, + pub(crate) fw_stamp: GpuWeakPointer, + pub(crate) stamp_value: EventValue, + pub(crate) unk_48: U64, + pub(crate) unk_50: u32, + pub(crate) unk_54: u32, + pub(crate) unk_58: U64, + pub(crate) unk_60: u32, + pub(crate) unk_64: u32, + pub(crate) unk_68: u32, + + #[ver(G >= G14 && V < V13_0B4)] + pub(crate) unk_68_g14: U64, + + pub(crate) restart_branch_offset: i32, + pub(crate) has_attachments: u32, // Check DCMP errors bits 2,3 1=ktrace 2=log 3=panic + + #[ver(V >= V13_0B4)] + pub(crate) unk_74: Array<0x10, u8>, +} + +#[versions(AGX)] +impl Operation for FinalizeVertex::ver {} + +#[versions(AGX)] +#[derive(Debug)] +#[repr(C)] +pub(crate) struct StartFragment<'a> { + pub(crate) header: op::StartFragment, + pub(crate) job_params2: Option>, + pub(crate) job_params1: Option>>, + #[ver(G >= G14X)] + pub(crate) registers: GpuWeakPointer, + pub(crate) scene: GpuPointer<'a, buffer::Scene::ver>, + pub(crate) stats: GpuWeakPointer, + pub(crate) busy_flag: GpuWeakPointer, + pub(crate) tvb_overflow_count: GpuWeakPointer, + pub(crate) unk_pointer: GpuWeakPointer, + pub(crate) work_queue: GpuWeakPointer, + pub(crate) work_item: GpuWeakPointer, + pub(crate) vm_slot: u32, + pub(crate) unk_50: u32, + pub(crate) event_generation: u32, + pub(crate) buffer_slot: u32, + pub(crate) sync_grow: u32, + pub(crate) event_seq: U64, + pub(crate) unk_68: u32, + pub(crate) unk_758_flag: GpuWeakPointer, + pub(crate) unk_job_buf: GpuWeakPointer, + #[ver(V >= V13_3)] + pub(crate) unk_7c_0: U64, + pub(crate) unk_7c: u32, + pub(crate) unk_80: u32, + pub(crate) unk_84: u32, + pub(crate) uuid: u32, + pub(crate) attachments: Attachments, + pub(crate) padding: u32, + + #[ver(V >= V13_0B4)] + pub(crate) counter: U64, + + #[ver(V >= V13_0B4)] + pub(crate) notifier_buf: GpuWeakPointer>, +} + +#[versions(AGX)] +impl<'a> Operation for StartFragment::ver<'a> {} + +#[versions(AGX)] +#[derive(Debug)] +#[repr(C)] +pub(crate) struct FinalizeFragment { + pub(crate) header: op::FinalizeFragment, + pub(crate) uuid: u32, + pub(crate) unk_8: u32, + pub(crate) fw_stamp: GpuWeakPointer, + pub(crate) stamp_value: EventValue, + pub(crate) unk_18: u32, + pub(crate) scene: GpuWeakPointer, + pub(crate) buffer: GpuWeakPointer, + pub(crate) unk_2c: U64, + pub(crate) stats: GpuWeakPointer, + pub(crate) unk_pointer: GpuWeakPointer, + pub(crate) busy_flag: GpuWeakPointer, + pub(crate) work_queue: GpuWeakPointer, + pub(crate) work_item: GpuWeakPointer, + pub(crate) vm_slot: u32, + pub(crate) unk_60: u32, + pub(crate) unk_758_flag: GpuWeakPointer, + #[ver(V >= V13_3)] + pub(crate) unk_6c_0: U64, + pub(crate) unk_6c: U64, + pub(crate) unk_74: U64, + pub(crate) unk_7c: U64, + pub(crate) unk_84: U64, + pub(crate) unk_8c: U64, + + #[ver(G == G14 && V < V13_0B4)] + pub(crate) unk_8c_g14: U64, + + pub(crate) restart_branch_offset: i32, + pub(crate) has_attachments: u32, // Check DCMP errors bits 2,3 1=ktrace 2=log 3=panic + + #[ver(V >= V13_0B4)] + pub(crate) unk_9c: Array<0x10, u8>, +} + +#[versions(AGX)] +impl Operation for FinalizeFragment::ver {} + +#[versions(AGX)] +#[derive(Debug)] +#[repr(C)] +pub(crate) struct StartCompute<'a> { + pub(crate) header: op::StartCompute, + pub(crate) unk_pointer: GpuWeakPointer, + pub(crate) job_params1: Option>>, + #[ver(G >= G14X)] + pub(crate) registers: GpuWeakPointer, + pub(crate) stats: GpuWeakPointer, + pub(crate) work_queue: GpuWeakPointer, + pub(crate) vm_slot: u32, + pub(crate) unk_28: u32, + pub(crate) event_generation: u32, + pub(crate) event_seq: U64, + pub(crate) unk_38: u32, + pub(crate) job_params2: GpuWeakPointer>, + pub(crate) unk_44: u32, + pub(crate) uuid: u32, + pub(crate) attachments: Attachments, + pub(crate) padding: u32, + + #[ver(V >= V13_0B4)] + pub(crate) unk_flag: GpuWeakPointer, + + #[ver(V >= V13_0B4)] + pub(crate) counter: U64, + + #[ver(V >= V13_0B4)] + pub(crate) notifier_buf: GpuWeakPointer>, +} + +#[versions(AGX)] +impl<'a> Operation for StartCompute::ver<'a> {} + +#[versions(AGX)] +#[derive(Debug)] +#[repr(C)] +pub(crate) struct FinalizeCompute<'a> { + pub(crate) header: op::FinalizeCompute, + pub(crate) stats: GpuWeakPointer, + pub(crate) work_queue: GpuWeakPointer, + pub(crate) vm_slot: u32, + #[ver(V < V13_0B4)] + pub(crate) unk_18: u32, + pub(crate) job_params2: GpuWeakPointer>, + pub(crate) unk_24: u32, + pub(crate) uuid: u32, + pub(crate) fw_stamp: GpuWeakPointer, + pub(crate) stamp_value: EventValue, + pub(crate) unk_38: u32, + pub(crate) unk_3c: u32, + pub(crate) unk_40: u32, + pub(crate) unk_44: u32, + pub(crate) unk_48: u32, + pub(crate) unk_4c: u32, + pub(crate) unk_50: u32, + pub(crate) unk_54: u32, + pub(crate) unk_58: u32, + + #[ver(G == G14 && V < V13_0B4)] + pub(crate) unk_5c_g14: U64, + + pub(crate) restart_branch_offset: i32, + pub(crate) has_attachments: u32, // Check DCMP errors bits 2,3 1=ktrace 2=log 3=panic + + #[ver(V >= V13_0B4)] + pub(crate) unk_64: Array<0xd, u8>, + + #[ver(V >= V13_0B4)] + pub(crate) unk_flag: GpuWeakPointer, + + #[ver(V >= V13_0B4)] + pub(crate) unk_79: Array<0x7, u8>, +} + +#[versions(AGX)] +impl<'a> Operation for FinalizeCompute::ver<'a> {} diff --git a/drivers/gpu/drm/asahi/fw/mod.rs b/drivers/gpu/drm/asahi/fw/mod.rs new file mode 100644 index 00000000000000..a5649aa20d3a8e --- /dev/null +++ b/drivers/gpu/drm/asahi/fw/mod.rs @@ -0,0 +1,15 @@ +// SPDX-License-Identifier: GPL-2.0-only OR MIT + +//! Firmware structures for Apple AGX GPUs + +pub(crate) mod buffer; +pub(crate) mod channels; +pub(crate) mod compute; +pub(crate) mod event; +pub(crate) mod fragment; +pub(crate) mod initdata; +pub(crate) mod job; +pub(crate) mod microseq; +pub(crate) mod types; +pub(crate) mod vertex; +pub(crate) mod workqueue; diff --git a/drivers/gpu/drm/asahi/fw/types.rs b/drivers/gpu/drm/asahi/fw/types.rs new file mode 100644 index 00000000000000..f55d6cec6b8ca3 --- /dev/null +++ b/drivers/gpu/drm/asahi/fw/types.rs @@ -0,0 +1,224 @@ +// SPDX-License-Identifier: GPL-2.0-only OR MIT + +//! Common types for firmware structure definitions + +use crate::{alloc, object}; +use core::fmt; +use core::ops::{Deref, DerefMut, Index, IndexMut}; + +pub(crate) use crate::event::EventValue; +pub(crate) use crate::object::{ + GpuPointer, + GpuStruct, + GpuWeakPointer, // +}; +pub(crate) use crate::{ + f32, + float::F32, // +}; + +pub(crate) use core::fmt::Debug; +pub(crate) use core::marker::PhantomData; +pub(crate) use core::sync::atomic::{ + AtomicI32, + AtomicU32, + AtomicU64, // +}; +pub(crate) use kernel::macros::versions; +pub(crate) use kernel::prelude::Zeroable; + +// Make the trait visible +pub(crate) use crate::alloc::Allocator as _Allocator; + +/// General allocator type used for the driver +pub(crate) type Allocator = alloc::DefaultAllocator; + +/// General GpuObject type used for the driver +pub(crate) type GpuObject = + object::GpuObject>; + +/// General GpuArray type used for the driver +pub(crate) type GpuArray = object::GpuArray>; + +/// General GpuOnlyArray type used for the driver +pub(crate) type GpuOnlyArray = + object::GpuOnlyArray>; + +/// A stamp slot that is shared between firmware and the driver. +#[derive(Debug, Default)] +#[repr(transparent)] +pub(crate) struct Stamp(pub(crate) AtomicU32); + +/// A stamp slot that is for private firmware use. +/// +/// This is a separate type to guard against pointer type confusion. +#[derive(Debug, Default)] +#[repr(transparent)] +pub(crate) struct FwStamp(pub(crate) AtomicU32); + +/// An unaligned u64 type. +/// +/// This is useful to avoid having to pack firmware structures entirely, since that is incompatible +/// with `#[derive(Debug)]` and atomics. +#[derive(Copy, Clone, Default)] +#[repr(C, packed(1))] +pub(crate) struct U64(pub(crate) u64); + +// SAFETY: U64 is zeroable just like u64 +unsafe impl Zeroable for U64 {} + +impl fmt::Debug for U64 { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let v = self.0; + f.write_fmt(format_args!("{:#x}", v)) + } +} + +/// An unaligned u32 type. +/// +/// This is useful to avoid having to pack firmware structures entirely, since that is incompatible +/// with `#[derive(Debug)]` and atomics. +#[derive(Copy, Clone, Default)] +#[repr(C, packed(1))] +pub(crate) struct U32(pub(crate) u32); + +// SAFETY: U32 is zeroable just like u32 +unsafe impl Zeroable for U32 {} + +impl fmt::Debug for U32 { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let v = self.0; + f.write_fmt(format_args!("{:#x}", v)) + } +} + +/// Create a dummy `Debug` implementation, for when we need it but it's too painful to write by +/// hand or not very useful. +#[macro_export] +macro_rules! no_debug { + ($type:ty) => { + impl ::core::fmt::Debug for $type { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + write!(f, "...") + } + } + }; +} + +/// Implement Zeroable for a given type (and Default along with it). +/// +/// # Safety +/// +/// This macro must only be used if a type only contains primitive types which can be +/// zero-initialized, FFI structs intended to be zero-initialized, or other types which +/// impl Zeroable. +#[macro_export] +macro_rules! default_zeroed { + (<$($lt:lifetime),*>, $type:ty) => { + impl<$($lt),*> Default for $type { + fn default() -> $type { + unsafe { core::mem::zeroed() } + } + } + // SAFETY: The user is responsible for ensuring this is safe. + unsafe impl<$($lt),*> ::pin_init::Zeroable for $type {} + }; + ($type:ty) => { + impl Default for $type { + fn default() -> $type { + unsafe { core::mem::zeroed() } + } + } + // SAFETY: The user is responsible for ensuring this is safe. + unsafe impl ::pin_init::Zeroable for $type {} + }; +} + +/// A convenience type for a number of padding bytes. Hidden from Debug formatting. +#[derive(Copy, Clone)] +#[repr(C, packed)] +pub(crate) struct Pad([u8; N]); + +/// SAFETY: Primitive type, safe to zero-init. +unsafe impl Zeroable for Pad {} + +impl Default for Pad { + fn default() -> Self { + unsafe { core::mem::zeroed() } + } +} + +impl fmt::Debug for Pad { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.write_fmt(format_args!("")) + } +} + +/// A convenience type for a fixed-sized array with Default/Zeroable impls. +#[derive(Copy, Clone)] +#[repr(C)] +pub(crate) struct Array([T; N]); + +impl Array { + pub(crate) fn new(data: [T; N]) -> Self { + Self(data) + } +} + +// SAFETY: Arrays of Zeroable values can be safely Zeroable. +unsafe impl Zeroable for Array {} + +impl Default for Array { + fn default() -> Self { + unsafe { core::mem::zeroed() } + } +} + +impl Index for Array { + type Output = T; + + fn index(&self, index: usize) -> &Self::Output { + &self.0[index] + } +} + +impl IndexMut for Array { + fn index_mut(&mut self, index: usize) -> &mut Self::Output { + &mut self.0[index] + } +} + +impl Deref for Array { + type Target = [T; N]; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl DerefMut for Array { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.0 + } +} + +impl fmt::Debug for Array { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + self.0.fmt(f) + } +} + +/// Convenience macro to define an identically-named trivial GpuStruct with no inner fields for a +/// given raw type name. +#[macro_export] +macro_rules! trivial_gpustruct { + ($type:ident) => { + #[derive(Debug)] + pub(crate) struct $type {} + + impl GpuStruct for $type { + type Raw<'a> = raw::$type; + } + $crate::default_zeroed!($type); + }; +} diff --git a/drivers/gpu/drm/asahi/fw/vertex.rs b/drivers/gpu/drm/asahi/fw/vertex.rs new file mode 100644 index 00000000000000..07a0e05c72112c --- /dev/null +++ b/drivers/gpu/drm/asahi/fw/vertex.rs @@ -0,0 +1,193 @@ +// SPDX-License-Identifier: GPL-2.0-only OR MIT + +//! GPU vertex job firmware structures + +use super::types::*; +use super::{ + event, + job, + workqueue, // +}; +use crate::{ + buffer, + fw, + microseq, + mmu, // +}; +use kernel::sync::Arc; + +pub(crate) mod raw { + use super::*; + + #[derive(Debug, Default, Copy, Clone)] + #[repr(C)] + pub(crate) struct TilingParameters { + pub(crate) rgn_size: u32, + pub(crate) unk_4: u32, + pub(crate) ppp_ctrl: u32, + pub(crate) x_max: u16, + pub(crate) y_max: u16, + pub(crate) te_screen: u32, + pub(crate) te_mtile1: u32, + pub(crate) te_mtile2: u32, + pub(crate) tiles_per_mtile: u32, + pub(crate) tpc_stride: u32, + pub(crate) unk_24: u32, + pub(crate) unk_28: u32, + pub(crate) helper_cfg: u32, + pub(crate) __pad: Pad<0x70>, + } + + #[versions(AGX)] + #[derive(Debug)] + #[repr(C)] + pub(crate) struct JobParameters1<'a> { + pub(crate) unk_0: U64, + pub(crate) unk_8: F32, + pub(crate) unk_c: F32, + pub(crate) tvb_tilemap: GpuPointer<'a, &'a [u8]>, + #[ver(G < G14)] + pub(crate) tvb_cluster_tilemaps: Option>, + pub(crate) tpc: GpuPointer<'a, &'a [u8]>, + pub(crate) tvb_heapmeta: GpuPointer<'a, &'a [u8]>, + pub(crate) iogpu_unk_54: U64, + pub(crate) iogpu_unk_56: U64, + #[ver(G < G14)] + pub(crate) tvb_cluster_meta1: Option>, + pub(crate) utile_config: u32, + pub(crate) unk_4c: u32, + pub(crate) ppp_multisamplectl: U64, + pub(crate) tvb_layermeta: GpuPointer<'a, &'a [u8]>, + #[ver(G < G14)] + pub(crate) tvb_cluster_layermeta: Option>, + #[ver(G < G14)] + pub(crate) core_mask: Array<2, u32>, + pub(crate) preempt_buf1: GpuPointer<'a, &'a [u8]>, + pub(crate) preempt_buf2: GpuPointer<'a, &'a [u8]>, + pub(crate) unk_80: U64, + pub(crate) preempt_buf3: GpuPointer<'a, &'a [u8]>, + pub(crate) vdm_ctrl_stream_base: U64, + #[ver(G < G14)] + pub(crate) tvb_cluster_meta2: Option>, + #[ver(G < G14)] + pub(crate) tvb_cluster_meta3: Option>, + #[ver(G < G14)] + pub(crate) tiling_control: u32, + #[ver(G < G14)] + pub(crate) unk_ac: u32, + pub(crate) unk_b0: Array<6, U64>, + pub(crate) usc_exec_base_ta: U64, + #[ver(G < G14)] + pub(crate) tvb_cluster_meta4: Option>, + #[ver(G < G14)] + pub(crate) unk_f0: U64, + pub(crate) unk_f8: U64, + pub(crate) helper_program: u32, + pub(crate) unk_104: u32, + pub(crate) helper_arg: U64, + pub(crate) unk_110: U64, + pub(crate) unk_118: u32, + #[ver(G >= G14)] + pub(crate) __pad: Pad<{ 8 * 9 + 0x268 }>, + #[ver(G < G14)] + pub(crate) __pad: Pad<0x268>, + } + + #[derive(Debug)] + #[repr(C)] + pub(crate) struct JobParameters2<'a> { + pub(crate) unk_480: Array<4, u32>, + pub(crate) unk_498: U64, + pub(crate) unk_4a0: u32, + pub(crate) preempt_buf1: GpuPointer<'a, &'a [u8]>, + pub(crate) unk_4ac: u32, + pub(crate) unk_4b0: U64, + pub(crate) unk_4b8: u32, + pub(crate) unk_4bc: U64, + pub(crate) unk_4c4_padding: Array<0x48, u8>, + pub(crate) unk_50c: u32, + pub(crate) unk_510: U64, + pub(crate) unk_518: U64, + pub(crate) unk_520: U64, + } + + #[versions(AGX)] + #[derive(Debug)] + #[repr(C)] + pub(crate) struct RunVertex<'a> { + pub(crate) tag: workqueue::CommandType, + + #[ver(V >= V13_0B4)] + pub(crate) counter: U64, + + pub(crate) vm_slot: u32, + pub(crate) unk_8: u32, + pub(crate) notifier: GpuPointer<'a, event::Notifier::ver>, + pub(crate) buffer_slot: u32, + pub(crate) unk_1c: u32, + pub(crate) buffer: GpuPointer<'a, fw::buffer::Info::ver>, + pub(crate) scene: GpuPointer<'a, fw::buffer::Scene::ver>, + pub(crate) unk_buffer_buf: GpuWeakPointer<[u8]>, + pub(crate) unk_34: u32, + + #[ver(G < G14X)] + pub(crate) job_params1: JobParameters1::ver<'a>, + #[ver(G < G14X)] + pub(crate) tiling_params: TilingParameters, + #[ver(G >= G14X)] + pub(crate) registers: job::raw::RegisterArray, + + pub(crate) tpc: GpuPointer<'a, &'a [u8]>, + pub(crate) tpc_size: U64, + pub(crate) microsequence: GpuPointer<'a, &'a [u8]>, + pub(crate) microsequence_size: u32, + pub(crate) fragment_stamp_slot: u32, + pub(crate) fragment_stamp_value: EventValue, + pub(crate) unk_pointee: u32, + pub(crate) unk_pad: u32, + pub(crate) job_params2: JobParameters2<'a>, + pub(crate) encoder_params: job::raw::EncoderParams, + pub(crate) unk_55c: u32, + pub(crate) unk_560: u32, + pub(crate) sync_grow: u32, + pub(crate) unk_568: u32, + pub(crate) uses_scratch: u32, + pub(crate) meta: job::raw::JobMeta, + pub(crate) unk_after_meta: u32, + pub(crate) unk_buf_0: U64, + pub(crate) unk_buf_8: U64, + pub(crate) unk_buf_10: U64, + pub(crate) command_time: U64, + pub(crate) timestamp_pointers: job::raw::TimestampPointers<'a>, + pub(crate) user_timestamp_pointers: job::raw::TimestampPointers<'a>, + pub(crate) client_sequence: u8, + pub(crate) pad_5d5: Array<3, u8>, + pub(crate) unk_5d8: u32, + pub(crate) unk_5dc: u8, + + #[ver(V >= V13_0B4)] + pub(crate) unk_ts: U64, + + #[ver(V >= V13_0B4)] + pub(crate) unk_5dd_8: Array<0x1b, u8>, + } +} + +#[versions(AGX)] +#[derive(Debug)] +pub(crate) struct RunVertex { + pub(crate) notifier: Arc>, + pub(crate) scene: Arc, + pub(crate) micro_seq: microseq::MicroSequence, + pub(crate) vm_bind: mmu::VmBind, + pub(crate) timestamps: Arc>, + pub(crate) user_timestamps: job::UserTimestamps, +} + +#[versions(AGX)] +impl GpuStruct for RunVertex::ver { + type Raw<'a> = raw::RunVertex::ver<'a>; +} + +#[versions(AGX)] +impl workqueue::Command for RunVertex::ver {} diff --git a/drivers/gpu/drm/asahi/fw/workqueue.rs b/drivers/gpu/drm/asahi/fw/workqueue.rs new file mode 100644 index 00000000000000..b86bd3ff3757af --- /dev/null +++ b/drivers/gpu/drm/asahi/fw/workqueue.rs @@ -0,0 +1,187 @@ +// SPDX-License-Identifier: GPL-2.0-only OR MIT + +//! GPU work queue firmware structes + +use super::event; +use super::types::*; +use crate::event::EventValue; +use crate::{ + default_zeroed, + trivial_gpustruct, // +}; +use kernel::sync::Arc; + +#[derive(Debug)] +#[repr(u32)] +pub(crate) enum CommandType { + RunVertex = 0, + RunFragment = 1, + #[allow(dead_code)] + RunBlitter = 2, + RunCompute = 3, + Barrier = 4, + InitBuffer = 6, +} + +pub(crate) trait Command: GpuStruct + Send + Sync {} + +pub(crate) mod raw { + use super::*; + + #[derive(Debug)] + #[repr(C)] + pub(crate) struct Barrier { + pub(crate) tag: CommandType, + pub(crate) wait_stamp: GpuWeakPointer, + pub(crate) wait_value: EventValue, + pub(crate) wait_slot: u32, + pub(crate) stamp_self: EventValue, + pub(crate) uuid: u32, + pub(crate) external_barrier: u32, + // G14X addition + pub(crate) internal_barrier_type: u32, + pub(crate) padding: Pad<0x1c>, + } + + #[derive(Debug, Clone, Copy)] + #[repr(C)] + pub(crate) struct GpuContextData { + pub(crate) unk_0: u8, + pub(crate) unk_1: u8, + unk_2: Array<0x2, u8>, + pub(crate) unk_4: u8, + pub(crate) unk_5: u8, + unk_6: Array<0x18, u8>, + pub(crate) unk_1e: u8, + pub(crate) unk_1f: u8, + unk_20: Array<0x3, u8>, + pub(crate) unk_23: u8, + unk_24: Array<0x1c, u8>, + } + + impl Default for GpuContextData { + fn default() -> Self { + Self { + unk_0: 0xff, + unk_1: 0xff, + unk_2: Default::default(), + unk_4: 0, + unk_5: 1, + unk_6: Default::default(), + unk_1e: 0xff, + unk_1f: 0, + unk_20: Default::default(), + unk_23: 2, + unk_24: Default::default(), + } + } + } + + #[derive(Debug)] + #[repr(C)] + pub(crate) struct RingState { + pub(crate) gpu_doneptr: AtomicU32, + __pad0: Pad<0xc>, + pub(crate) unk_10: AtomicU32, + __pad1: Pad<0xc>, + pub(crate) unk_20: AtomicU32, + __pad2: Pad<0xc>, + pub(crate) gpu_rptr: AtomicU32, + __pad3: Pad<0xc>, + pub(crate) cpu_wptr: AtomicU32, + __pad4: Pad<0xc>, + pub(crate) rb_size: u32, + __pad5: Pad<0xc>, + // This isn't part of the structure, but it's here as a + // debugging hack so we can inspect what ring position + // the driver considered complete and freeable. + pub(crate) cpu_freeptr: AtomicU32, + __pad6: Pad<0xc>, + } + default_zeroed!(RingState); + + #[derive(Debug, Clone, Copy)] + #[repr(C)] + pub(crate) struct Priority( + pub(crate) u32, + pub(crate) u32, + pub(crate) U64, + pub(crate) u32, + pub(crate) u32, + pub(crate) u32, + ); + + pub(crate) const PRIORITY: [Priority; 4] = [ + Priority(0, 0, U64(0xffff_ffff_ffff_0000), 1, 0, 1), + Priority(1, 1, U64(0xffff_ffff_0000_0000), 0, 0, 0), + Priority(2, 2, U64(0xffff_0000_0000_0000), 0, 0, 2), + Priority(3, 3, U64(0x0000_0000_0000_0000), 0, 0, 3), + ]; + + impl Default for Priority { + fn default() -> Priority { + PRIORITY[2] + } + } + + #[versions(AGX)] + #[derive(Debug)] + #[repr(C)] + pub(crate) struct QueueInfo<'a> { + pub(crate) state: GpuPointer<'a, super::RingState>, + pub(crate) ring: GpuPointer<'a, &'a [u64]>, + pub(crate) notifier_list: GpuPointer<'a, event::NotifierList>, + pub(crate) gpu_buf: GpuPointer<'a, &'a [u8]>, + pub(crate) gpu_rptr1: AtomicU32, + pub(crate) gpu_rptr2: AtomicU32, + pub(crate) gpu_rptr3: AtomicU32, + pub(crate) event_id: AtomicI32, + pub(crate) priority: Priority, + pub(crate) unk_4c: i32, + pub(crate) uuid: u32, + pub(crate) unk_54: i32, + pub(crate) unk_58: U64, + pub(crate) busy: AtomicU32, + pub(crate) __pad: Pad<0x20>, + #[ver(V >= V13_2 && G < G14X)] + pub(crate) unk_84_0: u32, + pub(crate) unk_84_state: AtomicU32, + pub(crate) error_count: AtomicU32, + pub(crate) unk_8c: u32, + pub(crate) unk_90: u32, + pub(crate) unk_94: u32, + pub(crate) pending: AtomicU32, + pub(crate) unk_9c: u32, + pub(crate) gpu_context: GpuPointer<'a, super::GpuContextData>, + pub(crate) unk_a8: U64, + #[ver(V >= V13_2 && G < G14X)] + pub(crate) unk_b0: u32, + } +} + +trivial_gpustruct!(Barrier); +trivial_gpustruct!(RingState); + +impl Command for Barrier {} + +pub(crate) struct GpuContextData { + pub(crate) _buffer: Arc, +} +impl GpuStruct for GpuContextData { + type Raw<'a> = raw::GpuContextData; +} + +#[versions(AGX)] +#[derive(Debug)] +pub(crate) struct QueueInfo { + pub(crate) state: GpuObject, + pub(crate) ring: GpuArray, + pub(crate) gpu_buf: GpuArray, + pub(crate) notifier_list: Arc>, + pub(crate) gpu_context: Arc, +} + +#[versions(AGX)] +impl GpuStruct for QueueInfo::ver { + type Raw<'a> = raw::QueueInfo::ver<'a>; +} diff --git a/drivers/gpu/drm/asahi/gem.rs b/drivers/gpu/drm/asahi/gem.rs new file mode 100644 index 00000000000000..c2f58aa29ce4b1 --- /dev/null +++ b/drivers/gpu/drm/asahi/gem.rs @@ -0,0 +1,248 @@ +// SPDX-License-Identifier: GPL-2.0-only OR MIT + +//! Asahi driver GEM object implementation +//! +//! Basic wrappers and adaptations between generic GEM shmem objects and this driver's +//! view of what a GPU buffer object is. It is in charge of keeping track of all mappings for +//! each GEM object so we can remove them when a client (File) or a Vm are destroyed, as well as +//! implementing RTKit buffers on top of GEM objects for firmware use. + +use kernel::{ + drm, + drm::gem::{ + shmem, + shmem::VMap, + BaseObject, + DriverObject, // + }, + error::Result, + prelude::*, + types::ARef, + uapi, +}; + +use core::ops::Range; +use core::sync::atomic::{ + AtomicU64, + Ordering, // +}; + +use crate::{ + debug::*, + driver::{ + AsahiDevice, + AsahiDriver, // + }, + file, + mmu, + util::*, // +}; + +const DEBUG_CLASS: DebugFlags = DebugFlags::Gem; + +/// Represents the inner data of a GEM object for this driver. +#[pin_data] +pub(crate) struct AsahiObject { + /// ID for debug + id: u64, + /// Object creation flags. + flags: u32, + /// Whether this object can be exported. + exportable: bool, + /// Whether this is a kernel-created object. + kernel: bool, +} + +/// Type alias for the shmem GEM object type for this driver. +pub(crate) type Object = shmem::Object; + +unsafe impl Send for AsahiObject {} +unsafe impl Sync for AsahiObject {} + +// /// Type alias for the SGTable type for this driver. +// pub(crate) type SGTable = shmem::SGTable; + +/// A shared reference to a GEM object for this driver. +pub(crate) struct ObjectRef { + /// The underlying GEM object reference + pub(crate) gem: ARef, + /// The kernel-side VMap of this object, if needed + vmap: Option>, +} + +crate::no_debug!(ObjectRef); + +static GEM_ID: AtomicU64 = AtomicU64::new(0); + +impl ObjectRef { + /// Create a new wrapper for a raw GEM object reference. + pub(crate) fn new(gem: ARef) -> ObjectRef { + ObjectRef { gem, vmap: None } + } + + /// Return the `VMap` for this object, creating it if necessary. + pub(crate) fn vmap(&mut self) -> Result> { + if self.vmap.is_none() { + self.vmap = Some(self.gem.owned_vmap()?); + } + self.gem.vmap() + } + + /// Returns the size of an object in bytes + pub(crate) fn size(&self) -> usize { + self.gem.size() + } + + /// Maps an object into a given `Vm` at any free address within a given range. + pub(crate) fn map_into_range( + &mut self, + vm: &crate::mmu::Vm, + range: Range, + alignment: u64, + prot: u32, + guard: bool, + ) -> Result { + // Only used for kernel objects now + if !self.gem.kernel { + return Err(EINVAL); + } + vm.map_in_range(&self.gem, 0..self.gem.size(), alignment, range, prot, guard) + } + + /// Maps a range within an object into a given `Vm` at any free address within a given range. + pub(crate) fn map_range_into_range( + &mut self, + vm: &crate::mmu::Vm, + obj_range: Range, + range: Range, + alignment: u64, + prot: u32, + guard: bool, + ) -> Result { + if obj_range.end > self.gem.size() { + return Err(EINVAL); + } + if self.gem.flags & uapi::drm_asahi_gem_flags_DRM_ASAHI_GEM_VM_PRIVATE != 0 + && vm.is_extobj(&*self.gem) + { + return Err(EINVAL); + } + vm.map_in_range(&self.gem, obj_range, alignment, range, prot, guard) + } + + /// Maps an object into a given `Vm` at a specific address. + /// + /// Returns Err(ENOSPC) if the requested address is already busy. + pub(crate) fn map_at( + &mut self, + vm: &crate::mmu::Vm, + addr: u64, + prot: u32, + guard: bool, + ) -> Result { + if self.gem.flags & uapi::drm_asahi_gem_flags_DRM_ASAHI_GEM_VM_PRIVATE != 0 + && vm.is_extobj(&*self.gem) + { + return Err(EINVAL); + } + + vm.map_at(addr, self.gem.size(), self.gem.clone(), prot, guard) + } +} + +pub(crate) struct AsahiObjConfig { + flags: u32, + exportable: bool, + kernel: bool, +} + +/// Create a new kernel-owned GEM object. +pub(crate) fn new_kernel_object(dev: &AsahiDevice, size: usize) -> Result { + let gem = shmem::Object::::new( + dev, + align(size, mmu::UAT_PGSZ), + shmem::ObjectConfig:: { + map_wc: false, + parent_resv_obj: None, + }, + AsahiObjConfig { + flags: 0, + exportable: false, + kernel: true, + }, + )?; + + mod_pr_debug!("AsahiObject new kernel object id={}\n", gem.id); + Ok(ObjectRef::new(gem)) +} + +/// Create a new user-owned GEM object with the given flags. +pub(crate) fn new_object( + dev: &AsahiDevice, + size: usize, + flags: u32, + parent_object: Option<&shmem::Object>, +) -> Result> { + if (flags & uapi::drm_asahi_gem_flags_DRM_ASAHI_GEM_VM_PRIVATE != 0) != parent_object.is_some() + { + return Err(EINVAL); + } + + let gem = shmem::Object::::new( + dev, + align(size, mmu::UAT_PGSZ), + shmem::ObjectConfig:: { + map_wc: flags & uapi::drm_asahi_gem_flags_DRM_ASAHI_GEM_WRITEBACK == 0, + parent_resv_obj: parent_object, + }, + AsahiObjConfig { + flags, + exportable: parent_object.is_none(), + kernel: false, + }, + )?; + + mod_pr_debug!("AsahiObject new user object: id={}\n", gem.id); + Ok(gem) +} + +#[vtable] +impl DriverObject for AsahiObject { + type Driver = AsahiDriver; + type Args = AsahiObjConfig; + + const HAS_EXPORT: bool = true; + + /// Callback to create the inner data of a GEM object + fn new(_dev: &AsahiDevice, _size: usize, args: Self::Args) -> impl PinInit { + let id = GEM_ID.fetch_add(1, Ordering::Relaxed); + mod_pr_debug!("AsahiObject::new id={}\n", id); + try_pin_init!(AsahiObject { + id, + flags: args.flags, + exportable: args.exportable, + kernel: args.kernel, + }) + } + + /// Callback to drop all mappings for a GEM object owned by a given `File` + fn close(obj: &::Object, file: &drm::gem::DriverFile) { + // fn close(obj: &Object, file: &DrmFile) { + mod_pr_debug!("AsahiObject::close id={}\n", obj.id); + if file::File::unbind_gem_object(file, obj).is_err() { + pr_err!("AsahiObject::close: Failed to unbind GEM object\n"); + } + } + + /// Optional handle for exporting a gem object. + fn export( + obj: &::Object, + flags: u32, + ) -> Result> { + if !obj.exportable { + return Err(EINVAL); + } + + obj.prime_export(flags) + } +} diff --git a/drivers/gpu/drm/asahi/gpu.rs b/drivers/gpu/drm/asahi/gpu.rs new file mode 100644 index 00000000000000..87bf9a3277a378 --- /dev/null +++ b/drivers/gpu/drm/asahi/gpu.rs @@ -0,0 +1,1556 @@ +// SPDX-License-Identifier: GPL-2.0-only OR MIT + +//! Top-level GPU manager +//! +//! This module is the root of all GPU firmware management for a given driver instance. It is +//! responsible for initialization, owning the top-level managers (events, UAT, etc.), and +//! communicating with the raw RtKit endpoints to send and receive messages to/from the GPU +//! firmware. +//! +//! It is also the point where diverging driver firmware/GPU variants (using the versions macro) +//! are unified, so that the top level of the driver itself (in `driver`) does not have to concern +//! itself with version dependence. + +use core::any::Any; +use core::ops::Range; +use core::sync::atomic::{ + AtomicBool, + AtomicU64, + Ordering, // +}; + +use kernel::{ + c_str, + drm::gem::shmem, + error::code::*, + macros::versions, + new_mutex, + prelude::*, + soc::apple::rtkit, + sync::{ + lock::{ + mutex::MutexBackend, + Guard, // + }, + Arc, + Mutex, + UniqueArc, // + }, + time::{ + msecs_to_jiffies, + Delta, + Instant, + Monotonic, // + }, + types::ForeignOwnable, // +}; + +use crate::alloc::Allocator; +use crate::debug::*; +use crate::driver::{ + AsahiDevRef, + AsahiDevice, // +}; +use crate::fw::channels::{ + ChannelErrorType, + PipeType, // +}; +use crate::fw::types::{ + U32, + U64, // +}; +use crate::{ + alloc, + buffer, + channel, + event, + fw, + gem, + hw, + initdata, + mem, + mmu, + queue, + regs, + workqueue, // +}; + +const DEBUG_CLASS: DebugFlags = DebugFlags::Gpu; + +/// Firmware endpoint for init & incoming notifications. +const EP_FIRMWARE: u8 = 0x20; + +/// Doorbell endpoint for work/message submissions. +const EP_DOORBELL: u8 = 0x21; + +/// Initialize the GPU firmware. +const MSG_INIT: u64 = 0x81 << 48; +const INIT_DATA_MASK: u64 = (1 << 44) - 1; + +/// TX channel doorbell. +const MSG_TX_DOORBELL: u64 = 0x83 << 48; +/// Firmware control channel doorbell. +const MSG_FWCTL: u64 = 0x84 << 48; +// /// Halt the firmware (?). +// const MSG_HALT: u64 = 0x85 << 48; + +/// Receive channel doorbell notification. +const MSG_RX_DOORBELL: u64 = 0x42 << 48; + +/// Doorbell number for firmware kicks/wakeups. +const DOORBELL_KICKFW: u64 = 0x10; +/// Doorbell number for device control channel kicks. +const DOORBELL_DEVCTRL: u64 = 0x11; + +// Upper kernel half VA address ranges. +/// Private (cached) firmware structure VA range base. +const IOVA_KERN_PRIV_RANGE: Range = 0xffffffa000000000..0xffffffa600000000; +/// Private (cached) GPU-RO firmware structure VA range base. +const IOVA_KERN_GPU_RO_RANGE: Range = 0xffffffa600000000..0xffffffa800000000; +/// Shared (uncached) firmware structure VA range base. +const IOVA_KERN_SHARED_RANGE: Range = 0xffffffa800000000..0xffffffaa00000000; +/// Shared (uncached) read-only firmware structure VA range base. +const IOVA_KERN_SHARED_RO_RANGE: Range = 0xffffffaa00000000..0xffffffac00000000; +/// GPU/FW shared structure VA range base. +const IOVA_KERN_GPU_RANGE: Range = 0xffffffac00000000..0xffffffae00000000; +/// GPU/FW shared structure VA range base. +const IOVA_KERN_RTKIT_RANGE: Range = 0xffffffae00000000..0xffffffae10000000; +/// Shared (uncached) timestamp region. +pub(crate) const IOVA_KERN_TIMESTAMP_RANGE: Range = 0xffffffae10000000..0xffffffae14000000; +/// FW MMIO VA range base. +const IOVA_KERN_MMIO_RANGE: Range = 0xffffffaf00000000..0xffffffb000000000; + +/// GPU/FW buffer manager control address (context 0 low) +pub(crate) const IOVA_KERN_GPU_BUFMGR_LOW: u64 = 0x20_0000_0000; +/// GPU/FW buffer manager control address (context 0 high) +pub(crate) const IOVA_KERN_GPU_BUFMGR_HIGH: u64 = 0xffffffaeffff0000; + +/// Timeout for entering the halt state after a fault or request. +const HALT_ENTER_TIMEOUT: Delta = Delta::from_millis(100); + +/// Maximum amount of firmware-private memory garbage allowed before collection. +/// Collection flushes the FW cache and is expensive, so this needs to be +/// reasonably high. +const MAX_FW_ALLOC_GARBAGE_BYTES: usize = 16 * 1024 * 1024; +/// Maximum count of firmware-private memory garbage objects allowed before collection. +/// This works out to 16K of memory in the garbage list (8 bytes each), which keeps us +/// within the safe range for kmalloc (on 16K page systems). +const MAX_FW_ALLOC_GARBAGE_OBJECTS: usize = 2048; + +/// Global allocators used for kernel-half structures. +pub(crate) struct KernelAllocators { + pub(crate) private: alloc::DefaultAllocator, + pub(crate) shared: alloc::DefaultAllocator, + pub(crate) shared_ro: alloc::DefaultAllocator, + #[allow(dead_code)] + pub(crate) gpu: alloc::DefaultAllocator, + pub(crate) gpu_ro: alloc::DefaultAllocator, +} + +/// Receive (GPU->driver) ring buffer channels. +#[versions(AGX)] +#[pin_data] +struct RxChannels { + event: channel::EventChannel::ver, + fw_log: channel::FwLogChannel, + ktrace: channel::KTraceChannel, + stats: channel::StatsChannel::ver, +} + +/// GPU work submission pipe channels (driver->GPU). +#[versions(AGX)] +struct PipeChannels { + pub(crate) vtx: KVec>>>, + pub(crate) frag: KVec>>>, + pub(crate) comp: KVec>>>, +} + +/// Misc command transmit (driver->GPU) channels. +#[versions(AGX)] +#[pin_data] +struct TxChannels { + pub(crate) device_control: channel::DeviceControlChannel::ver, +} + +/// Number of work submission pipes per type, one for each priority level. +const NUM_PIPES: usize = 4; + +/// A generic monotonically incrementing ID used to uniquely identify object instances within the +/// driver. +pub(crate) struct ID(AtomicU64); + +impl ID { + /// Create a new ID counter with a given value. + fn new(val: u64) -> ID { + ID(AtomicU64::new(val)) + } + + /// Fetch the next unique ID. + pub(crate) fn next(&self) -> u64 { + self.0.fetch_add(1, Ordering::Relaxed) + } +} + +impl Default for ID { + /// IDs default to starting at 2, as 0/1 are considered reserved for the system. + fn default() -> Self { + Self::new(2) + } +} + +/// A guard representing one active submission on the GPU. When dropped, decrements the active +/// submission count. +pub(crate) struct OpGuard(Arc); + +impl Drop for OpGuard { + fn drop(&mut self) { + self.0.end_op(); + } +} + +/// Set of global sequence IDs used in the driver. +#[derive(Default)] +pub(crate) struct SequenceIDs { + /// `File` instance ID. + pub(crate) file: ID, + /// `Vm` instance ID. + pub(crate) vm: ID, + /// Submission instance ID. + pub(crate) submission: ID, + /// `Queue` instance ID. + pub(crate) queue: ID, +} + +/// Top-level GPU manager that owns all the global state relevant to the driver instance. +#[versions(AGX)] +#[pin_data] +pub(crate) struct GpuManager { + dev: AsahiDevRef, + cfg: &'static hw::HwConfig, + dyncfg: hw::DynConfig, + pub(crate) initdata: fw::types::GpuObject, + uat: mmu::Uat, + crashed: AtomicBool, + #[pin] + alloc: Mutex, + io_mappings: KVec, + next_mmio_iova: u64, + #[pin] + rtkit: Mutex>>, + #[pin] + rx_channels: Mutex, + #[pin] + tx_channels: Mutex, + #[pin] + fwctl_channel: Mutex, + pipes: PipeChannels::ver, + event_manager: Arc, + buffer_mgr: buffer::BufferManager::ver, + ids: SequenceIDs, + #[allow(clippy::vec_box)] + #[pin] + garbage_contexts: Mutex>>>, +} + +/// Trait used to abstract the firmware/GPU-dependent variants of the GpuManager. +pub(crate) trait GpuManager: Send + Sync { + /// Cast as an Any type. + fn as_any(&self) -> &dyn Any; + /// Cast Arc as an Any type. + fn arc_as_any(self: Arc) -> Arc; + /// Initialize the GPU. + fn init(&self) -> Result; + /// Update the GPU globals from global info + /// + /// TODO: Unclear what can and cannot be updated like this. + fn update_globals(&self); + /// Get a reference to the KernelAllocators. + fn alloc(&self) -> Guard<'_, KernelAllocators, MutexBackend>; + /// Create a new `Vm` given a unique `File` ID. + fn new_vm(&self, kernel_range: Range) -> Result; + /// Bind a `Vm` to an available slot and return the `VmBind`. + fn bind_vm(&self, vm: &mmu::Vm) -> Result; + /// Create a new user command queue. + fn new_queue( + &self, + vm: mmu::Vm, + ualloc: Arc>, + ualloc_priv: Arc>, + priority: u32, + usc_exec_base: u64, + ) -> Result>; + /// Return a reference to the global `SequenceIDs` instance. + fn ids(&self) -> &SequenceIDs; + /// Kick the firmware (wake it up if asleep). + /// + /// This should be useful to reduce latency on work submission, so we can ask the firmware to + /// wake up while we do some preparatory work for the work submission. + fn kick_firmware(&self) -> Result; + /// Flush the entire firmware cache. + /// + /// TODO: Does this actually work? + fn flush_fw_cache(&self) -> Result; + /// Handle a GPU work timeout event. + fn handle_timeout(&self, counter: u32, event_slot: i32, unk: u32); + /// Handle a GPU fault event. + fn handle_fault(&self); + /// Handle a channel error event. + fn handle_channel_error( + &self, + error_type: ChannelErrorType, + pipe_type: u32, + event_slot: u32, + event_value: u32, + ); + /// Acknowledge a Buffer grow op. + fn ack_grow(&self, buffer_slot: u32, vm_slot: u32, counter: u32); + /// Send a firmware control command (secure cache flush). + fn fwctl(&self, msg: fw::channels::FwCtlMsg) -> Result; + /// Get the static GPU configuration for this SoC. + fn get_cfg(&self) -> &'static hw::HwConfig; + /// Get the dynamic GPU configuration for this SoC. + fn get_dyncfg(&self) -> &hw::DynConfig; + /// Register an unused context as garbage + fn free_context(&self, data: KBox>); + /// Check whether the GPU is crashed + fn is_crashed(&self) -> bool; + /// Map a BO as a timestamp buffer + fn map_timestamp_buffer( + &self, + bo: gem::ObjectRef, + range: Range, + ) -> Result; +} + +/// Private generic trait for functions that don't need to escape this module. +trait GpuManagerPriv { + /// Decrement the pending submission counter. + fn end_op(&self); +} + +pub(crate) struct RtkitObject { + vmap: shmem::VMap, + mapping: mmu::KernelMapping, +} + +impl rtkit::Buffer for RtkitObject { + fn iova(&self) -> Result { + Ok(self.mapping.iova() as usize) + } + fn buf(&mut self) -> Result> { + Ok(self.vmap.get()) + } +} + +#[versions(AGX)] +#[vtable] +impl rtkit::Operations for GpuManager::ver { + type Data = Arc; + type Buffer = RtkitObject; + + fn recv_message(data: ::Borrowed<'_>, ep: u8, msg: u64) { + let dev = &data.dev; + //dev_info!(dev.as_ref(), "RtKit message: {:#x}:{:#x}\n", ep, msg); + + if ep != EP_FIRMWARE || msg != MSG_RX_DOORBELL { + dev_err!(dev.as_ref(), "Unknown message: {:#x}:{:#x}\n", ep, msg); + return; + } + + let mut ch = data.rx_channels.lock(); + + ch.fw_log.poll(); + ch.ktrace.poll(); + ch.stats.poll(); + ch.event.poll(); + } + + fn crashed(data: ::Borrowed<'_>, _crashlog: Option<&[u8]>) { + let dev = &data.dev; + + data.crashed.store(true, Ordering::Relaxed); + + if debug_enabled(DebugFlags::OopsOnGpuCrash) { + panic!("GPU firmware crashed"); + } else { + dev_err!(dev.as_ref(), "GPU firmware crashed, failing all jobs\n"); + data.event_manager.fail_all(workqueue::WorkError::NoDevice); + } + } + + fn shmem_alloc( + data: ::Borrowed<'_>, + size: usize, + ) -> Result { + let dev = &data.dev; + mod_dev_dbg!(dev, "shmem_alloc() {:#x} bytes\n", size); + + let mut obj = gem::new_kernel_object(dev, size)?; + let vmap = obj.gem.owned_vmap()?; + let mapping = obj.map_into_range( + data.uat.kernel_vm(), + IOVA_KERN_RTKIT_RANGE, + mmu::UAT_PGSZ as u64, + mmu::PROT_FW_SHARED_RW, + true, + )?; + mod_dev_dbg!(dev, "shmem_alloc() -> VA {:#x}\n", mapping.iova()); + Ok(RtkitObject { vmap, mapping }) + } +} + +#[versions(AGX)] +impl GpuManager::ver { + /// Create a new GpuManager of this version/GPU combination. + #[inline(never)] + pub(crate) fn new( + dev: &AsahiDevice, + res: ®s::Resources, + cfg: &'static hw::HwConfig, + ) -> Result> { + let uat = Self::make_uat(dev, cfg)?; + let dyncfg = Self::make_dyncfg(dev, res, cfg, &uat)?; + + let mut alloc = KernelAllocators { + private: alloc::DefaultAllocator::new( + dev, + uat.kernel_vm(), + IOVA_KERN_PRIV_RANGE, + 0x80, + mmu::PROT_FW_PRIV_RW, + 1024 * 1024, + true, + fmt!("Kernel Private"), + true, + )?, + shared: alloc::DefaultAllocator::new( + dev, + uat.kernel_vm(), + IOVA_KERN_SHARED_RANGE, + 0x80, + mmu::PROT_FW_SHARED_RW, + 1024 * 1024, + true, + fmt!("Kernel Shared"), + false, + )?, + shared_ro: alloc::DefaultAllocator::new( + dev, + uat.kernel_vm(), + IOVA_KERN_SHARED_RO_RANGE, + 0x80, + mmu::PROT_FW_SHARED_RO, + 64 * 1024, + true, + fmt!("Kernel RO Shared"), + false, + )?, + gpu: alloc::DefaultAllocator::new( + dev, + uat.kernel_vm(), + IOVA_KERN_GPU_RANGE, + 0x80, + mmu::PROT_GPU_FW_SHARED_RW, + 64 * 1024, + true, + fmt!("Kernel GPU Shared"), + false, + )?, + gpu_ro: alloc::DefaultAllocator::new( + dev, + uat.kernel_vm(), + IOVA_KERN_GPU_RO_RANGE, + 0x80, + mmu::PROT_GPU_RO_FW_PRIV_RW, + 1024 * 1024, + true, + fmt!("Kernel GPU RO Shared"), + true, + )?, + }; + + let event_manager = Self::make_event_manager(&mut alloc)?; + let mut initdata = Self::make_initdata(dev, cfg, &dyncfg, &mut alloc)?; + + initdata.runtime_pointers.buffer_mgr_ctl_low_mapping = + Some(initdata.runtime_pointers.buffer_mgr_ctl.map_at( + uat.kernel_lower_vm(), + IOVA_KERN_GPU_BUFMGR_LOW, + mmu::PROT_GPU_SHARED_RW, + false, + )?); + initdata.runtime_pointers.buffer_mgr_ctl_high_mapping = + Some(initdata.runtime_pointers.buffer_mgr_ctl.map_at( + uat.kernel_vm(), + IOVA_KERN_GPU_BUFMGR_HIGH, + mmu::PROT_FW_SHARED_RW, + false, + )?); + + let mut mgr = Self::make_mgr(dev, cfg, dyncfg, uat, alloc, event_manager, initdata)?; + + { + let fwctl = mgr.fwctl_channel.lock(); + let p_fwctl = fwctl.to_raw(); + core::mem::drop(fwctl); + + mgr.as_mut() + .initdata_mut() + .fw_status + .with_mut(|raw, _inner| { + raw.fwctl_channel = p_fwctl; + }); + } + + { + let txc = mgr.tx_channels.lock(); + let p_device_control = txc.device_control.to_raw(); + core::mem::drop(txc); + + let rxc = mgr.rx_channels.lock(); + let p_event = rxc.event.to_raw(); + let p_fw_log = rxc.fw_log.to_raw(); + let p_ktrace = rxc.ktrace.to_raw(); + let p_stats = rxc.stats.to_raw(); + let p_fwlog_buf = rxc.fw_log.get_buf(); + core::mem::drop(rxc); + + mgr.as_mut() + .initdata_mut() + .runtime_pointers + .with_mut(|raw, _inner| { + raw.device_control = p_device_control; + raw.event = p_event; + raw.fw_log = p_fw_log; + raw.ktrace = p_ktrace; + raw.stats = p_stats; + raw.fwlog_buf = Some(p_fwlog_buf); + }); + } + + let mut p_pipes: KVec = KVec::new(); + + for ((v, f), c) in mgr + .pipes + .vtx + .iter() + .zip(&mgr.pipes.frag) + .zip(&mgr.pipes.comp) + { + p_pipes.push( + fw::initdata::raw::PipeChannels::ver { + vtx: v.lock().to_raw(), + frag: f.lock().to_raw(), + comp: c.lock().to_raw(), + }, + GFP_KERNEL, + )?; + } + + mgr.as_mut() + .initdata_mut() + .runtime_pointers + .with_mut(|raw, _inner| { + for (i, p) in p_pipes.into_iter().enumerate() { + raw.pipes[i].vtx = p.vtx; + raw.pipes[i].frag = p.frag; + raw.pipes[i].comp = p.comp; + } + }); + + for (i, map) in cfg.io_mappings.iter().enumerate() { + if let Some(map) = map.as_ref() { + Self::iomap(&mut mgr, cfg, i, map)?; + } + } + + #[ver(V >= V13_0B4)] + if let Some(base) = cfg.sram_base { + let size = cfg.sram_size.unwrap(); + let iova = mgr.as_mut().alloc_mmio_iova(size); + + let mapping = mgr + .uat + .kernel_vm() + .map_io(iova, base, size, mmu::PROT_FW_SHARED_RW)?; + + mgr.as_mut() + .initdata_mut() + .runtime_pointers + .hwdata_b + .with_mut(|raw, _| { + raw.sgx_sram_ptr = U64(mapping.iova()); + }); + + mgr.as_mut().io_mappings_mut().push(mapping, GFP_KERNEL)?; + } + + let mgr = Arc::from(mgr); + + let rtkit = rtkit::RtKit::::new(dev.as_ref(), None, 0, mgr.clone())?; + + *mgr.rtkit.lock() = Some(rtkit); + + { + let mut rxc = mgr.rx_channels.lock(); + rxc.event.set_manager(mgr.clone()); + } + + Ok(mgr) + } + + /// Return a mutable reference to the initdata member + fn initdata_mut( + self: Pin<&mut Self>, + ) -> &mut fw::types::GpuObject { + // SAFETY: initdata does not require structural pinning. + unsafe { &mut self.get_unchecked_mut().initdata } + } + + /// Return a mutable reference to the io_mappings member + fn io_mappings_mut(self: Pin<&mut Self>) -> &mut KVec { + // SAFETY: io_mappings does not require structural pinning. + unsafe { &mut self.get_unchecked_mut().io_mappings } + } + + /// Allocate an MMIO iova range + fn alloc_mmio_iova(self: Pin<&mut Self>, size: usize) -> u64 { + // SAFETY: next_mmio_iova does not require structural pinning. + let next_ref = unsafe { &mut self.get_unchecked_mut().next_mmio_iova }; + + let addr = *next_ref; + let next = addr + (size + mmu::UAT_PGSZ) as u64; + + assert!(next <= IOVA_KERN_MMIO_RANGE.end); + + *next_ref = next; + + addr + } + + /// Build the entire GPU InitData structure tree and return it as a boxed GpuObject. + fn make_initdata( + dev: &AsahiDevice, + cfg: &'static hw::HwConfig, + dyncfg: &hw::DynConfig, + alloc: &mut KernelAllocators, + ) -> Result>> { + let mut builder = initdata::InitDataBuilder::ver::new(dev, alloc, cfg, dyncfg); + builder.build() + } + + /// Create a fresh boxed Uat instance. + /// + /// Force disable inlining to avoid blowing up the stack. + #[inline(never)] + fn make_uat(dev: &AsahiDevice, cfg: &'static hw::HwConfig) -> Result> { + // G14X has a new thing in the Scene structure that unfortunately requires + // write access from user contexts. Hopefully it's not security-sensitive. + #[ver(G >= G14X)] + let map_kernel_to_user = true; + #[ver(G < G14X)] + let map_kernel_to_user = false; + + Ok(KBox::new( + mmu::Uat::new(dev, cfg, map_kernel_to_user)?, + GFP_KERNEL, + )?) + } + + /// Actually create the final GpuManager instance, as a UniqueArc. + /// + /// Force disable inlining to avoid blowing up the stack. + #[inline(never)] + fn make_mgr( + dev: &AsahiDevice, + cfg: &'static hw::HwConfig, + dyncfg: KBox, + uat: KBox, + mut alloc: KernelAllocators, + event_manager: Arc, + initdata: KBox>, + ) -> Result>> { + let mut pipes = PipeChannels::ver { + vtx: KVec::new(), + frag: KVec::new(), + comp: KVec::new(), + }; + + for _i in 0..=NUM_PIPES - 1 { + pipes.vtx.push( + KBox::pin_init( + new_mutex!(channel::PipeChannel::ver::new(dev, &mut alloc)?, "pipe_vtx",), + GFP_KERNEL, + )?, + GFP_KERNEL, + )?; + pipes.frag.push( + KBox::pin_init( + new_mutex!( + channel::PipeChannel::ver::new(dev, &mut alloc)?, + "pipe_frag", + ), + GFP_KERNEL, + )?, + GFP_KERNEL, + )?; + pipes.comp.push( + KBox::pin_init( + new_mutex!( + channel::PipeChannel::ver::new(dev, &mut alloc)?, + "pipe_comp", + ), + GFP_KERNEL, + )?, + GFP_KERNEL, + )?; + } + + let fwctl_channel = channel::FwCtlChannel::new(dev, &mut alloc)?; + + let buffer_mgr = buffer::BufferManager::ver::new()?; + let event_manager_clone = event_manager.clone(); + let buffer_mgr_clone = buffer_mgr.clone(); + let alloc_ref = &mut alloc; + let rx_channels = KBox::init( + try_init!(RxChannels::ver { + event: channel::EventChannel::ver::new( + dev, + alloc_ref, + event_manager_clone, + buffer_mgr_clone, + )?, + fw_log: channel::FwLogChannel::new(dev, alloc_ref)?, + ktrace: channel::KTraceChannel::new(dev, alloc_ref)?, + stats: channel::StatsChannel::ver::new(dev, alloc_ref)?, + }), + GFP_KERNEL, + )?; + + let alloc_ref = &mut alloc; + let tx_channels = KBox::init( + try_init!(TxChannels::ver { + device_control: channel::DeviceControlChannel::ver::new(dev, alloc_ref)?, + }), + GFP_KERNEL, + )?; + + let x = UniqueArc::pin_init( + try_pin_init!(GpuManager::ver { + dev: dev.into(), + cfg, + dyncfg: KBox::::into_inner(dyncfg), + initdata: KBox::>::into_inner(initdata), + uat: KBox::::into_inner(uat), + io_mappings: KVec::new(), + next_mmio_iova: IOVA_KERN_MMIO_RANGE.start, + rtkit <- new_mutex!(None, "rtkit"), + crashed: AtomicBool::new(false), + event_manager, + alloc <- new_mutex!(alloc, "alloc"), + fwctl_channel <- new_mutex!(fwctl_channel, "fwctl_channel"), + rx_channels <- new_mutex!(KBox::::into_inner(rx_channels), "rx_channels"), + tx_channels <- new_mutex!(KBox::::into_inner(tx_channels), "tx_channels"), + pipes, + buffer_mgr, + ids: Default::default(), + garbage_contexts <- new_mutex!(KVec::new(), "garbage_contexts"), + }), + GFP_KERNEL, + )?; + + Ok(x) + } + + /// Fetch and validate the GPU dynamic configuration from the device tree and hardware. + /// + /// Force disable inlining to avoid blowing up the stack. + #[inline(never)] + fn make_dyncfg( + dev: &AsahiDevice, + res: ®s::Resources, + cfg: &'static hw::HwConfig, + uat: &mmu::Uat, + ) -> Result> { + let gpu_id = res.get_gpu_id()?; + + dev_info!(dev.as_ref(), "GPU Information:\n"); + dev_info!( + dev.as_ref(), + " Type: {:?}{:?}\n", + gpu_id.gpu_gen, + gpu_id.gpu_variant + ); + dev_info!(dev.as_ref(), " Clusters: {}\n", gpu_id.num_clusters); + dev_info!( + dev.as_ref(), + " Cores: {} ({})\n", + gpu_id.num_cores, + gpu_id.num_cores * gpu_id.num_clusters + ); + dev_info!( + dev.as_ref(), + " Frags: {} ({})\n", + gpu_id.num_frags, + gpu_id.num_frags * gpu_id.num_clusters + ); + dev_info!( + dev.as_ref(), + " GPs: {} ({})\n", + gpu_id.num_gps, + gpu_id.num_gps * gpu_id.num_clusters + ); + dev_info!(dev.as_ref(), " Core masks: {:#x?}\n", gpu_id.core_masks); + dev_info!( + dev.as_ref(), + " Active cores: {}\n", + gpu_id.total_active_cores + ); + + dev_info!(dev.as_ref(), "Getting configuration from device tree...\n"); + let pwr_cfg = hw::PwrConfig::load(dev, cfg)?; + dev_info!(dev.as_ref(), "Dynamic configuration fetched\n"); + + if gpu_id.gpu_gen != cfg.gpu_gen || gpu_id.gpu_variant != cfg.gpu_variant { + dev_err!( + dev.as_ref(), + "GPU type mismatch (expected {:?}{:?}, found {:?}{:?})\n", + cfg.gpu_gen, + cfg.gpu_variant, + gpu_id.gpu_gen, + gpu_id.gpu_variant + ); + return Err(EIO); + } + if gpu_id.num_clusters > cfg.max_num_clusters { + dev_err!( + dev.as_ref(), + "Too many clusters ({} > {})\n", + gpu_id.num_clusters, + cfg.max_num_clusters + ); + return Err(EIO); + } + if gpu_id.num_cores > cfg.max_num_cores { + dev_err!( + dev.as_ref(), + "Too many cores ({} > {})\n", + gpu_id.num_cores, + cfg.max_num_cores + ); + return Err(EIO); + } + if gpu_id.num_frags > cfg.max_num_frags { + dev_err!( + dev.as_ref(), + "Too many frags ({} > {})\n", + gpu_id.num_frags, + cfg.max_num_frags + ); + return Err(EIO); + } + if gpu_id.num_gps > cfg.max_num_gps { + dev_err!( + dev.as_ref(), + "Too many GPs ({} > {})\n", + gpu_id.num_gps, + cfg.max_num_gps + ); + return Err(EIO); + } + + let fwnode = dev.as_ref().fwnode().ok_or(ENOENT)?; + + Ok(KBox::new( + hw::DynConfig { + pwr: pwr_cfg, + uat_ttb_base: uat.ttb_base(), + id: gpu_id, + firmware_version: fwnode + .property_read_array_vec(c_str!("apple,firmware-version"), 3)? + .or(kernel::kvec![0; 3]?), + }, + GFP_KERNEL, + )?) + } + + /// Create the global GPU event manager, and return an `Arc<>` to it. + fn make_event_manager(alloc: &mut KernelAllocators) -> Result> { + Ok(Arc::new(event::EventManager::new(alloc)?, GFP_KERNEL)?) + } + + /// Create a new MMIO mapping and add it to the mappings list in initdata at the specified + /// index. + fn iomap( + this: &mut Pin>, + cfg: &'static hw::HwConfig, + index: usize, + map: &hw::IOMapping, + ) -> Result { + let dies = if map.per_die { + cfg.num_dies as usize + } else { + 1 + }; + + let off = map.base & mmu::UAT_PGMSK; + let base = map.base - off; + let end = (map.base + map.size + mmu::UAT_PGMSK) & !mmu::UAT_PGMSK; + let map_size = end - base; + + // Array mappings must be aligned + assert!((off == 0 && map_size == map.size) || (map.count == 1 && !map.per_die)); + assert!(map.count > 0); + + let iova = this.as_mut().alloc_mmio_iova(map_size * map.count * dies); + let mut cur_iova = iova; + + for die in 0..dies { + for i in 0..map.count { + let phys_off = die * 0x20_0000_0000 + i * map.stride; + + let mapping = this.uat.kernel_vm().map_io( + cur_iova, + base + phys_off, + map_size, + if map.writable { + mmu::PROT_FW_MMIO_RW + } else { + mmu::PROT_FW_MMIO_RO + }, + )?; + + this.as_mut().io_mappings_mut().push(mapping, GFP_KERNEL)?; + cur_iova += map_size as u64; + } + } + + this.as_mut() + .initdata_mut() + .runtime_pointers + .hwdata_b + .with_mut(|raw, _| { + raw.io_mappings[index] = fw::initdata::raw::IOMapping { + phys_addr: U64(map.base as u64), + virt_addr: U64(iova + off as u64), + total_size: (map.size * map.count * dies) as u32, + element_size: map.size as u32, + readwrite: U64(map.writable as u64), + }; + }); + + Ok(()) + } + + /// Mark work associated with currently in-progress event slots as failed, after a fault or + /// timeout. + fn mark_pending_events(&self, culprit_slot: Option, error: workqueue::WorkError) { + dev_err!(self.dev.as_ref(), " Pending events:\n"); + + self.initdata.globals.with(|raw, _inner| { + for (index, i) in raw.pending_stamps.iter().enumerate() { + let info = i.info.load(Ordering::Relaxed); + let wait_value = i.wait_value.load(Ordering::Relaxed); + + if info & 1 != 0 { + #[ver(V >= V13_5)] + let slot = (info >> 4) & 0x7f; + #[ver(V < V13_5)] + let slot = (info >> 3) & 0x7f; + #[ver(V >= V13_5)] + let flags = info & 0xf; + #[ver(V < V13_5)] + let flags = info & 0x7; + dev_err!( + self.dev.as_ref(), + " [{}:{}] flags={} value={:#x}\n", + index, + slot, + flags, + wait_value + ); + let error = if culprit_slot.is_some() && culprit_slot != Some(slot) { + workqueue::WorkError::Killed + } else { + error + }; + self.event_manager.mark_error(slot, wait_value, error); + i.info.store(0, Ordering::Relaxed); + i.wait_value.store(0, Ordering::Relaxed); + } + } + }); + } + + /// Fetch the GPU MMU fault information from the hardware registers. + fn get_fault_info(&self) -> Option { + let res = &(*self.dev).resources; + + let info = res.get_fault_info(self.cfg); + if info.is_some() { + dev_err!( + self.dev.as_ref(), + " Fault info: {:#x?}\n", + info.as_ref().unwrap() + ); + } + info + } + + /// Resume the GPU firmware after it halts (due to a timeout, fault, or request). + fn recover(&self) { + self.initdata.fw_status.with(|raw, _inner| { + let halt_count = raw.flags.halt_count.load(Ordering::Relaxed); + let mut halted = raw.flags.halted.load(Ordering::Relaxed); + dev_err!(self.dev.as_ref(), " Halt count: {}\n", halt_count); + dev_err!(self.dev.as_ref(), " Halted: {}\n", halted); + + if halted == 0 { + let start = Instant::::now(); + while start.elapsed() < HALT_ENTER_TIMEOUT { + halted = raw.flags.halted.load(Ordering::Relaxed); + if halted != 0 { + break; + } + mem::sync(); + } + halted = raw.flags.halted.load(Ordering::Relaxed); + } + + if debug_enabled(DebugFlags::NoGpuRecovery) { + dev_crit!( + self.dev.as_ref(), + " GPU recovery is disabled, wedging forever!\n" + ); + } else if halted != 0 { + dev_err!(self.dev.as_ref(), " Attempting recovery...\n"); + raw.flags.halted.store(0, Ordering::SeqCst); + raw.flags.resume.store(1, Ordering::SeqCst); + } else { + dev_err!(self.dev.as_ref(), " Cannot recover.\n"); + } + }); + } + + /// Return the packed GPU enabled core masks. + // Only used for some versions + #[allow(dead_code)] + pub(crate) fn core_masks_packed(&self) -> &[u32] { + self.dyncfg.id.core_masks_packed.as_slice() + } + + /// Kick a submission pipe for a submitted job to tell the firmware to start processing it. + pub(crate) fn run_job(&self, job: workqueue::JobSubmission::ver<'_>) -> Result { + mod_dev_dbg!(self.dev, "GPU: run_job\n"); + + let pipe_type = job.pipe_type(); + mod_dev_dbg!(self.dev, "GPU: run_job: pipe_type={:?}\n", pipe_type); + + let pipes = match pipe_type { + PipeType::Vertex => &self.pipes.vtx, + PipeType::Fragment => &self.pipes.frag, + PipeType::Compute => &self.pipes.comp, + }; + + let index: usize = job.priority() as usize; + let mut pipe = pipes.get(index).ok_or(EIO)?.lock(); + + mod_dev_dbg!(self.dev, "GPU: run_job: run()\n"); + job.run(&mut pipe); + mod_dev_dbg!(self.dev, "GPU: run_job: ring doorbell\n"); + + let mut guard = self.rtkit.lock(); + let rtk = guard.as_mut().unwrap(); + rtk.send_message( + EP_DOORBELL, + MSG_TX_DOORBELL | pipe_type as u64 | ((index as u64) << 2), + )?; + mod_dev_dbg!(self.dev, "GPU: run_job: done\n"); + + Ok(()) + } + + pub(crate) fn start_op(self: &Arc) -> Result { + if self.is_crashed() { + return Err(ENODEV); + } + + let val = self + .initdata + .globals + .with(|raw, _inner| raw.pending_submissions.fetch_add(1, Ordering::Acquire)); + + mod_dev_dbg!(self.dev, "OP start (pending: {})\n", val + 1); + self.kick_firmware()?; + Ok(OpGuard(self.clone())) + } + + fn invalidate_context( + &self, + context: &fw::types::GpuObject, + ) -> Result { + mod_dev_dbg!( + self.dev, + "Invalidating GPU context @ {:?}\n", + context.weak_pointer() + ); + + if self.is_crashed() { + return Err(ENODEV); + } + + let mut guard = self.alloc.lock(); + let (garbage_count, _) = guard.private.garbage(); + let (garbage_count_gpuro, _) = guard.gpu_ro.garbage(); + + let dc = context.with( + |raw, _inner| fw::channels::DeviceControlMsg::ver::DestroyContext { + unk_4: 0, + ctx_23: raw.unk_23, + #[ver(V < V13_3)] + __pad0: Default::default(), + unk_c: U32(0), + unk_10: U32(0), + ctx_0: raw.unk_0, + ctx_1: raw.unk_1, + ctx_4: raw.unk_4, + #[ver(V < V13_3)] + __pad1: Default::default(), + #[ver(V < V13_3)] + unk_18: 0, + gpu_context: Some(context.weak_pointer()), + __pad2: Default::default(), + }, + ); + + mod_dev_dbg!(self.dev, "Context invalidation command: {:?}\n", &dc); + + let mut txch = self.tx_channels.lock(); + + let token = txch.device_control.send(&dc); + + { + let mut guard = self.rtkit.lock(); + let rtk = guard.as_mut().unwrap(); + rtk.send_message(EP_DOORBELL, MSG_TX_DOORBELL | DOORBELL_DEVCTRL)?; + } + + txch.device_control.wait_for(token)?; + + mod_dev_dbg!( + self.dev, + "GPU context invalidated: {:?}\n", + context.weak_pointer() + ); + + // The invalidation does a cache flush, so it is okay to collect garbage + guard.private.collect_garbage(garbage_count); + guard.gpu_ro.collect_garbage(garbage_count_gpuro); + + Ok(()) + } +} + +#[versions(AGX)] +impl GpuManager for GpuManager::ver { + fn as_any(&self) -> &dyn Any { + self + } + + fn arc_as_any(self: Arc) -> Arc { + self as Arc + } + + fn init(&self) -> Result { + self.tx_channels.lock().device_control.send( + &fw::channels::DeviceControlMsg::ver::Initialize(Default::default()), + ); + + let initdata = self.initdata.gpu_va().get(); + let mut guard = self.rtkit.lock(); + let rtk = guard.as_mut().unwrap(); + + rtk.boot()?; + rtk.start_endpoint(EP_FIRMWARE)?; + rtk.start_endpoint(EP_DOORBELL)?; + rtk.send_message(EP_FIRMWARE, MSG_INIT | (initdata & INIT_DATA_MASK))?; + rtk.send_message(EP_DOORBELL, MSG_TX_DOORBELL | DOORBELL_DEVCTRL)?; + core::mem::drop(guard); + + self.kick_firmware()?; + Ok(()) + } + + fn update_globals(&self) { + let mut timeout: u32 = 2; + if debug_enabled(DebugFlags::WaitForPowerOff) { + timeout = 0; + } else if debug_enabled(DebugFlags::KeepGpuPowered) { + timeout = 5000; + } + + self.initdata.globals.with(|raw, _inner| { + raw.idle_off_delay_ms.store(timeout, Ordering::Relaxed); + }); + } + + fn alloc(&self) -> Guard<'_, KernelAllocators, MutexBackend> { + /* Clean up idle contexts */ + let mut garbage_ctx = KVec::new(); + core::mem::swap(&mut *self.garbage_contexts.lock(), &mut garbage_ctx); + + for ctx in garbage_ctx { + if self.invalidate_context(&ctx).is_err() { + dev_err!( + self.dev.as_ref(), + "GpuContext: Failed to invalidate GPU context!\n" + ); + if debug_enabled(DebugFlags::OopsOnGpuCrash) { + panic!("GPU firmware timed out"); + } + } + } + + let mut guard = self.alloc.lock(); + let (garbage_count, garbage_bytes) = guard.private.garbage(); + let (ro_garbage_count, ro_garbage_bytes) = guard.gpu_ro.garbage(); + + if garbage_bytes > MAX_FW_ALLOC_GARBAGE_BYTES + || ro_garbage_bytes > MAX_FW_ALLOC_GARBAGE_BYTES + || garbage_count > MAX_FW_ALLOC_GARBAGE_OBJECTS + || ro_garbage_count > MAX_FW_ALLOC_GARBAGE_OBJECTS + { + mod_dev_dbg!( + self.dev, + "Collecting kalloc garbage (private: {} objects, {} bytes, gpuro: {} objects, {} bytes)\n", + garbage_count, + garbage_bytes, + ro_garbage_count, + ro_garbage_bytes + ); + if self.flush_fw_cache().is_err() { + dev_err!(self.dev.as_ref(), "Failed to flush FW cache\n"); + } else { + guard.private.collect_garbage(garbage_count); + guard.gpu_ro.collect_garbage(ro_garbage_count); + } + } + + guard + } + + fn new_vm(&self, kernel_range: Range) -> Result { + self.uat.new_vm(self.ids.vm.next(), kernel_range) + } + + fn bind_vm(&self, vm: &mmu::Vm) -> Result { + self.uat.bind(vm) + } + + fn new_queue( + &self, + vm: mmu::Vm, + ualloc: Arc>, + ualloc_priv: Arc>, + priority: u32, + usc_exec_base: u64, + ) -> Result> { + let mut kalloc = self.alloc(); + let id = self.ids.queue.next(); + Ok(KBox::new( + queue::Queue::ver::new( + &self.dev, + vm, + &mut kalloc, + ualloc, + ualloc_priv, + self.event_manager.clone(), + &self.buffer_mgr, + id, + priority, + usc_exec_base, + )?, + GFP_KERNEL, + )?) + } + + fn kick_firmware(&self) -> Result { + if self.is_crashed() { + return Err(ENODEV); + } + + let mut guard = self.rtkit.lock(); + let rtk = guard.as_mut().unwrap(); + rtk.send_message(EP_DOORBELL, MSG_TX_DOORBELL | DOORBELL_KICKFW)?; + + Ok(()) + } + + fn flush_fw_cache(&self) -> Result { + mod_dev_dbg!(self.dev, "Flushing coprocessor data cache\n"); + + if self.is_crashed() { + return Err(ENODEV); + } + + // ctx_0 == 0xff or ctx_1 == 0xff cause no effect on context, + // but this command does a full cache flush too, so abuse it + // for that. + + let dc = fw::channels::DeviceControlMsg::ver::DestroyContext { + unk_4: 0, + + ctx_23: 0, + #[ver(V < V13_3)] + __pad0: Default::default(), + unk_c: U32(0), + unk_10: U32(0), + ctx_0: 0xff, + ctx_1: 0xff, + ctx_4: 0, + #[ver(V < V13_3)] + __pad1: Default::default(), + #[ver(V < V13_3)] + unk_18: 0, + gpu_context: None, + __pad2: Default::default(), + }; + + let mut txch = self.tx_channels.lock(); + + let token = txch.device_control.send(&dc); + { + let mut guard = self.rtkit.lock(); + let rtk = guard.as_mut().unwrap(); + rtk.send_message(EP_DOORBELL, MSG_TX_DOORBELL | DOORBELL_DEVCTRL)?; + } + + txch.device_control.wait_for(token)?; + Ok(()) + } + + fn ids(&self) -> &SequenceIDs { + &self.ids + } + + fn handle_timeout(&self, counter: u32, event_slot: i32, unk: u32) { + dev_err!(self.dev.as_ref(), " (\\________/) \n"); + dev_err!(self.dev.as_ref(), " | | \n"); + dev_err!(self.dev.as_ref(), "'.| \\ , / |.'\n"); + dev_err!(self.dev.as_ref(), "--| / (( \\ |--\n"); + dev_err!(self.dev.as_ref(), ".'| _-_- |'.\n"); + dev_err!(self.dev.as_ref(), " |________| \n"); + dev_err!(self.dev.as_ref(), "** GPU timeout nya~!!!!! **\n"); + dev_err!(self.dev.as_ref(), " Event slot: {}\n", event_slot); + dev_err!(self.dev.as_ref(), " Timeout count: {}\n", counter); + dev_err!(self.dev.as_ref(), " Unk: {}\n", unk); + + // If we have fault info, consider it a fault. + let error = match self.get_fault_info() { + Some(info) => workqueue::WorkError::Fault(info), + None => workqueue::WorkError::Timeout, + }; + self.mark_pending_events(event_slot.try_into().ok(), error); + self.recover(); + } + + fn handle_fault(&self) { + dev_err!(self.dev.as_ref(), " (\\________/) \n"); + dev_err!(self.dev.as_ref(), " | | \n"); + dev_err!(self.dev.as_ref(), "'.| \\ , / |.'\n"); + dev_err!(self.dev.as_ref(), "--| / (( \\ |--\n"); + dev_err!(self.dev.as_ref(), ".'| _-_- |'.\n"); + dev_err!(self.dev.as_ref(), " |________| \n"); + dev_err!(self.dev.as_ref(), "GPU fault nya~!!!!!\n"); + let error = match self.get_fault_info() { + Some(info) => workqueue::WorkError::Fault(info), + None => workqueue::WorkError::Unknown, + }; + self.mark_pending_events(None, error); + self.recover(); + } + + fn handle_channel_error( + &self, + error_type: ChannelErrorType, + pipe_type: u32, + event_slot: u32, + event_value: u32, + ) { + dev_err!(self.dev.as_ref(), " (\\________/) \n"); + dev_err!(self.dev.as_ref(), " | | \n"); + dev_err!(self.dev.as_ref(), "'.| \\ , / |.'\n"); + dev_err!(self.dev.as_ref(), "--| / (( \\ |--\n"); + dev_err!(self.dev.as_ref(), ".'| _-_- |'.\n"); + dev_err!(self.dev.as_ref(), " |________| \n"); + dev_err!(self.dev.as_ref(), "GPU channel error nya~!!!!!\n"); + dev_err!(self.dev.as_ref(), " Error type: {:?}\n", error_type); + dev_err!(self.dev.as_ref(), " Pipe type: {}\n", pipe_type); + dev_err!(self.dev.as_ref(), " Event slot: {}\n", event_slot); + dev_err!(self.dev.as_ref(), " Event value: {:#x?}\n", event_value); + + self.event_manager.mark_error( + event_slot, + event_value, + workqueue::WorkError::ChannelError(error_type), + ); + + let wq = match self.event_manager.get_owner(event_slot) { + Some(wq) => wq, + None => { + dev_err!( + self.dev.as_ref(), + "Workqueue not found for this event slot!\n" + ); + return; + } + }; + + let wq = match wq.as_any().downcast_ref::() { + Some(wq) => wq, + None => { + dev_crit!(self.dev.as_ref(), "GpuManager mismatched with WorkQueue!\n"); + return; + } + }; + + if debug_enabled(DebugFlags::VerboseFaults) { + wq.dump_info(); + } + + let dc = fw::channels::DeviceControlMsg::ver::RecoverChannel { + pipe_type, + work_queue: wq.info_pointer(), + event_value, + __pad: Default::default(), + }; + + mod_dev_dbg!(self.dev, "Recover Channel command: {:?}\n", &dc); + let mut txch = self.tx_channels.lock(); + + let token = txch.device_control.send(&dc); + { + let mut guard = self.rtkit.lock(); + let rtk = guard.as_mut().unwrap(); + if rtk + .send_message(EP_DOORBELL, MSG_TX_DOORBELL | DOORBELL_DEVCTRL) + .is_err() + { + dev_err!( + self.dev.as_ref(), + "Failed to send Recover Channel command\n" + ); + } + } + + if txch.device_control.wait_for(token).is_err() { + dev_err!( + self.dev.as_ref(), + "Timed out waiting for Recover Channel command\n" + ); + } + + if debug_enabled(DebugFlags::VerboseFaults) { + wq.dump_info(); + } + } + + fn ack_grow(&self, buffer_slot: u32, vm_slot: u32, counter: u32) { + let halt_count = self + .initdata + .fw_status + .with(|raw, _inner| raw.flags.halt_count.load(Ordering::Relaxed)); + + let dc = fw::channels::DeviceControlMsg::ver::GrowTVBAck { + unk_4: 1, + buffer_slot, + vm_slot, + counter, + subpipe: 0, // TODO + halt_count: U64(halt_count), + __pad: Default::default(), + }; + + mod_dev_dbg!(self.dev, "TVB Grow Ack command: {:?}\n", &dc); + + let mut txch = self.tx_channels.lock(); + + txch.device_control.send(&dc); + { + let mut guard = self.rtkit.lock(); + let rtk = guard.as_mut().unwrap(); + if rtk + .send_message(EP_DOORBELL, MSG_TX_DOORBELL | DOORBELL_DEVCTRL) + .is_err() + { + dev_err!(self.dev.as_ref(), "Failed to send TVB Grow Ack command\n"); + } + } + } + + fn fwctl(&self, msg: fw::channels::FwCtlMsg) -> Result { + if self.is_crashed() { + return Err(ENODEV); + } + + let mut fwctl = self.fwctl_channel.lock(); + let token = fwctl.send(&msg); + { + let mut guard = self.rtkit.lock(); + let rtk = guard.as_mut().unwrap(); + rtk.send_message(EP_DOORBELL, MSG_FWCTL)?; + } + fwctl.wait_for(token)?; + Ok(()) + } + + fn get_cfg(&self) -> &'static hw::HwConfig { + self.cfg + } + + fn get_dyncfg(&self) -> &hw::DynConfig { + &self.dyncfg + } + + fn free_context(&self, ctx: KBox>) { + let mut garbage = self.garbage_contexts.lock(); + + if garbage.push(ctx, GFP_KERNEL).is_err() { + dev_err!( + self.dev.as_ref(), + "Failed to reserve space for freed context, deadlock possible.\n" + ); + } + } + + fn is_crashed(&self) -> bool { + self.crashed.load(Ordering::Relaxed) + } + + fn map_timestamp_buffer( + &self, + mut bo: gem::ObjectRef, + range: Range, + ) -> Result { + bo.map_range_into_range( + self.uat.kernel_vm(), + range, + IOVA_KERN_TIMESTAMP_RANGE, + mmu::UAT_PGSZ as u64, + mmu::PROT_FW_SHARED_RW, + false, + ) + } +} + +#[versions(AGX)] +impl GpuManagerPriv for GpuManager::ver { + fn end_op(&self) { + let val = self + .initdata + .globals + .with(|raw, _inner| raw.pending_submissions.fetch_sub(1, Ordering::Release)); + + mod_dev_dbg!(self.dev, "OP end (pending: {})\n", val - 1); + } +} diff --git a/drivers/gpu/drm/asahi/hw/mod.rs b/drivers/gpu/drm/asahi/hw/mod.rs new file mode 100644 index 00000000000000..8841073e1b4c70 --- /dev/null +++ b/drivers/gpu/drm/asahi/hw/mod.rs @@ -0,0 +1,653 @@ +// SPDX-License-Identifier: GPL-2.0-only OR MIT + +//! Per-SoC hardware configuration structures +//! +//! This module contains the definitions used to store per-GPU and per-SoC configuration data. + +use crate::driver::AsahiDevice; +use crate::fw::types::*; +use kernel::c_str; +use kernel::prelude::*; + +const MAX_POWERZONES: usize = 5; + +pub(crate) mod t600x; +pub(crate) mod t602x; +pub(crate) mod t8103; +pub(crate) mod t8112; + +/// GPU generation enumeration. Note: Part of the UABI. +#[derive(Debug, PartialEq, Copy, Clone)] +#[repr(u32)] +pub(crate) enum GpuGen { + G13 = 13, + G14 = 14, +} + +/// GPU variant enumeration. Note: Part of the UABI. +#[derive(Debug, PartialEq, Copy, Clone)] +#[repr(u32)] +pub(crate) enum GpuVariant { + P = 'P' as u32, + G = 'G' as u32, + S = 'S' as u32, + C = 'C' as u32, + D = 'D' as u32, +} + +/// GPU revision enumeration. Note: Part of the UABI. +#[derive(Debug, PartialEq, Copy, Clone)] +#[repr(u32)] +pub(crate) enum GpuRevision { + A0 = 0x00, + A1 = 0x01, + B0 = 0x10, + B1 = 0x11, + C0 = 0x20, + C1 = 0x21, +} + +/// GPU core type enumeration. Note: Part of the firmware ABI. +#[derive(Debug, Copy, Clone)] +#[repr(u32)] +pub(crate) enum GpuCore { + // Unknown = 0, + // G5P = 1, + // G5G = 2, + // G9P = 3, + // G9G = 4, + // G10P = 5, + // G11P = 6, + // G11M = 7, + // G11G = 8, + // G12P = 9, + // G13P = 10, + G13G = 11, + G13S = 12, + G13C = 13, + // G14P = 14, + G14G = 15, + G14S = 16, + G14C = 17, + G14D = 18, // Split out, unlike G13D +} + +/// GPU revision ID. Note: Part of the firmware ABI. +#[derive(Debug, PartialEq, Copy, Clone)] +#[repr(u32)] +pub(crate) enum GpuRevisionID { + // Unknown = 0, + A0 = 1, + A1 = 2, + B0 = 3, + B1 = 4, + C0 = 5, + C1 = 6, +} + +/// A single performance state of the GPU. +#[derive(Debug)] +pub(crate) struct PState { + /// Voltage in millivolts, per GPU cluster. + pub(crate) volt_mv: KVec, + /// Frequency in hertz. + pub(crate) freq_hz: u32, + /// Maximum power consumption of the GPU at this pstate, in milliwatts. + pub(crate) pwr_mw: u32, +} + +impl PState { + pub(crate) fn max_volt_mv(&self) -> u32 { + *self.volt_mv.iter().max().expect("No voltages") + } +} + +/// A power zone definition (we have no idea what this is but Apple puts them in the DT). +#[allow(missing_docs)] +#[derive(Debug, Copy, Clone)] +pub(crate) struct PowerZone { + pub(crate) target: u32, + pub(crate) target_offset: u32, + pub(crate) filter_tc: u32, +} + +/// An MMIO mapping used by the firmware. +#[derive(Debug, Copy, Clone)] +pub(crate) struct IOMapping { + /// Base physical address of the mapping. + pub(crate) base: usize, + /// Whether this mapping should be replicated to all dies + pub(crate) per_die: bool, + /// Number of mappings. + pub(crate) count: usize, + /// Size of one mapping. + pub(crate) size: usize, + /// Stride between mappings. + pub(crate) stride: usize, + /// Whether the mapping should be writable. + pub(crate) writable: bool, +} + +impl IOMapping { + /// Convenience constructor for a new IOMapping. + pub(crate) const fn new( + base: usize, + per_die: bool, + count: usize, + size: usize, + stride: usize, + writable: bool, + ) -> IOMapping { + IOMapping { + base, + per_die, + count, + size, + stride, + writable, + } + } +} + +/// Unknown HwConfigA fields that vary from SoC to SoC. +#[allow(missing_docs)] +#[derive(Debug, Copy, Clone)] +pub(crate) struct HwConfigA { + pub(crate) unk_87c: i32, + pub(crate) unk_8cc: u32, + pub(crate) unk_e24: u32, +} + +/// Unknown HwConfigB fields that vary from SoC to SoC. +#[allow(missing_docs)] +#[derive(Debug, Copy, Clone)] +pub(crate) struct HwConfigB { + pub(crate) unk_454: u32, + pub(crate) unk_4e0: u64, + pub(crate) unk_534: u32, + pub(crate) unk_ab8: u32, + pub(crate) unk_abc: u32, + pub(crate) unk_b30: u32, +} + +/// Render command configs that vary from SoC to SoC. +#[derive(Debug, Copy, Clone)] +pub(crate) struct HwRenderConfig { + /// Vertex/tiling-related configuration register (lsb: disable clustering) + pub(crate) tiling_control: u32, +} + +#[derive(Debug)] +pub(crate) struct HwConfigShared2Curves { + pub(crate) t1_coef: u32, + pub(crate) t2: &'static [i16], + pub(crate) t3_coefs: &'static [u32], + pub(crate) t3_scales: &'static [u32], +} + +/// Static hardware clustering configuration for multi-cluster SoCs. +#[derive(Debug)] +pub(crate) struct HwClusteringConfig { + pub(crate) meta1_blocksize: usize, + pub(crate) meta2_size: usize, + pub(crate) meta3_size: usize, + pub(crate) meta4_size: usize, + pub(crate) max_splits: usize, +} + +/// Static hardware configuration for a given SoC model. +#[derive(Debug)] +pub(crate) struct HwConfig { + /// Chip ID in hex format (e.g. 0x8103 for t8103). + pub(crate) chip_id: u32, + /// GPU generation. + pub(crate) gpu_gen: GpuGen, + /// GPU variant type. + pub(crate) gpu_variant: GpuVariant, + /// GPU core type ID (as known by the firmware). + pub(crate) gpu_core: GpuCore, + + /// Base clock used used for timekeeping. + pub(crate) base_clock_hz: u32, + /// Output address space for the UAT on this SoC. + pub(crate) uat_oas: u32, + /// Number of dies on this SoC. + pub(crate) num_dies: u32, + /// Maximum number of clusters on this SoC. + pub(crate) max_num_clusters: u32, + /// Maximum number of cores per cluster for this GPU. + pub(crate) max_num_cores: u32, + /// Maximum number of frags per cluster for this GPU. + pub(crate) max_num_frags: u32, + /// Maximum number of GPs per cluster for this GPU. + pub(crate) max_num_gps: u32, + + /// Required size of the first preemption buffer. + pub(crate) preempt1_size: usize, + /// Required size of the second preemption buffer. + pub(crate) preempt2_size: usize, + /// Required size of the third preemption buffer. + pub(crate) preempt3_size: usize, + + /// Required size of the compute preemption buffer. + pub(crate) compute_preempt1_size: usize, + + pub(crate) clustering: Option, + + /// Rendering-relevant configuration. + pub(crate) render: HwRenderConfig, + + /// Misc HWDataA field values. + pub(crate) da: HwConfigA, + /// Misc HWDataB field values. + pub(crate) db: HwConfigB, + /// HwDataShared1.table. + pub(crate) shared1_tab: &'static [i32], + /// HwDataShared1.unk_a4. + pub(crate) shared1_a4: u32, + /// HwDataShared2.table. + pub(crate) shared2_tab: &'static [i32], + /// HwDataShared2.unk_508. + pub(crate) shared2_unk_508: u32, + /// HwDataShared2.unk_508. + pub(crate) shared2_curves: Option, + + /// HwDataShared3.unk_8. + pub(crate) shared3_unk: u32, + /// HwDataShared3.table. + pub(crate) shared3_tab: &'static [u32], + + /// Globals.idle_off_standby_timer. + pub(crate) idle_off_standby_timer_default: u32, + /// Globals.unk_hws2_4. + pub(crate) unk_hws2_4: Option<[F32; 8]>, + /// Globals.unk_hws2_24. + pub(crate) unk_hws2_24: u32, + /// Globals.unk_54 + pub(crate) global_unk_54: u16, + + /// Constant related to SRAM voltages. + pub(crate) sram_k: F32, + /// Unknown per-cluster coefficients 1. + pub(crate) unk_coef_a: &'static [&'static [F32]], + /// Unknown per-cluster coefficients 2. + pub(crate) unk_coef_b: &'static [&'static [F32]], + /// Unknown table in Global struct. + pub(crate) global_tab: Option<&'static [u8]>, + /// Whether this GPU has CS/AFR performance states + pub(crate) has_csafr: bool, + + /// Temperature sensor list (8 bits per sensor). + pub(crate) fast_sensor_mask: [u64; 2], + /// Temperature sensor list (alternate). + pub(crate) fast_sensor_mask_alt: [u64; 2], + /// Temperature sensor present bitmask. + pub(crate) fast_die0_sensor_present: u32, + /// Required MMIO mappings for this GPU/firmware. + pub(crate) io_mappings: &'static [Option], + /// SRAM base + pub(crate) sram_base: Option, + /// SRAM size + pub(crate) sram_size: Option, +} + +/// Dynamic (fetched from hardware/DT) configuration. +#[derive(Debug)] +pub(crate) struct DynConfig { + /// Base physical address of the UAT TTB (from DT reserved memory region). + pub(crate) uat_ttb_base: u64, + /// GPU ID configuration read from hardware. + pub(crate) id: GpuIdConfig, + /// Power calibration configuration for this specific chip/device. + pub(crate) pwr: PwrConfig, + /// Firmware version. + #[allow(dead_code)] + pub(crate) firmware_version: KVec, +} + +/// Specific GPU ID configuration fetched from SGX MMIO registers. +#[derive(Debug)] +pub(crate) struct GpuIdConfig { + /// GPU generation (should match static config). + pub(crate) gpu_gen: GpuGen, + /// GPU variant type (should match static config). + pub(crate) gpu_variant: GpuVariant, + /// GPU silicon revision. + pub(crate) gpu_rev: GpuRevision, + /// GPU silicon revision ID (firmware enum). + pub(crate) gpu_rev_id: GpuRevisionID, + /// Total number of GPU clusters. + pub(crate) num_clusters: u32, + /// Maximum number of GPU cores per cluster. + pub(crate) num_cores: u32, + /// Number of frags per cluster. + pub(crate) num_frags: u32, + /// Number of GPs per cluster. + pub(crate) num_gps: u32, + /// Total number of active cores for the whole GPU. + pub(crate) total_active_cores: u32, + /// Mask of active cores per cluster. + pub(crate) core_masks: KVec, + /// Packed mask of all active cores. + pub(crate) core_masks_packed: KVec, +} + +/// Configurable CS/AFR GPU power settings from the device tree. +#[derive(Debug)] +pub(crate) struct CsAfrPwrConfig { + /// GPU CS performance state list. + pub(crate) perf_states_cs: KVec, + /// GPU AFR performance state list. + pub(crate) perf_states_afr: KVec, + + /// CS leakage coefficient per die. + pub(crate) leak_coef_cs: KVec, + /// AFR leakage coefficient per die. + pub(crate) leak_coef_afr: KVec, + + /// Minimum voltage for the CS/AFR SRAM power domain in microvolts. + pub(crate) min_sram_microvolt: u32, +} + +/// Configurable GPU power settings from the device tree. +#[derive(Debug)] +pub(crate) struct PwrConfig { + /// GPU performance state list. + pub(crate) perf_states: KVec, + /// GPU power zone list. + pub(crate) power_zones: KVec, + + /// Core leakage coefficient per cluster. + pub(crate) core_leak_coef: KVec, + /// SRAM leakage coefficient per cluster. + pub(crate) sram_leak_coef: KVec, + + pub(crate) csafr: Option, + + /// Maximum total power of the GPU in milliwatts. + pub(crate) max_power_mw: u32, + /// Maximum frequency of the GPU in megahertz. + pub(crate) max_freq_mhz: u32, + + /// Minimum performance state to start at. + pub(crate) perf_base_pstate: u32, + /// Maximum enabled performance state. + pub(crate) perf_max_pstate: u32, + + /// Minimum voltage for the SRAM power domain in microvolts. + pub(crate) min_sram_microvolt: u32, + + // Most of these fields are just named after Apple ADT property names and we don't fully + // understand them. They configure various power-related PID loops and filters. + /// Average power filter time constant in milliseconds. + pub(crate) avg_power_filter_tc_ms: u32, + /// Average power filter PID integral gain? + pub(crate) avg_power_ki_only: F32, + /// Average power filter PID proportional gain? + pub(crate) avg_power_kp: F32, + pub(crate) avg_power_min_duty_cycle: u32, + /// Average power target filter time constant in periods. + pub(crate) avg_power_target_filter_tc: u32, + /// "Fast die0" (temperature?) PID integral gain. + pub(crate) fast_die0_integral_gain: F32, + /// "Fast die0" (temperature?) PID proportional gain. + pub(crate) fast_die0_proportional_gain: F32, + pub(crate) fast_die0_prop_tgt_delta: u32, + pub(crate) fast_die0_release_temp: u32, + /// Delay from the fender (?) becoming idle to powerdown + pub(crate) fender_idle_off_delay_ms: u32, + /// Timeout from firmware early wake to sleep if no work was submitted (?) + pub(crate) fw_early_wake_timeout_ms: u32, + /// Delay from the GPU becoming idle to powerdown + pub(crate) idle_off_delay_ms: u32, + /// Related to the above? + pub(crate) idle_off_standby_timer: u32, + /// Percent? + pub(crate) perf_boost_ce_step: u32, + /// Minimum utilization before performance state is increased in %. + pub(crate) perf_boost_min_util: u32, + pub(crate) perf_filter_drop_threshold: u32, + /// Performance PID filter time constant? (periods?) + pub(crate) perf_filter_time_constant: u32, + /// Performance PID filter time constant 2? (periods?) + pub(crate) perf_filter_time_constant2: u32, + /// Performance PID integral gain. + pub(crate) perf_integral_gain: F32, + /// Performance PID integral gain 2 (?). + pub(crate) perf_integral_gain2: F32, + pub(crate) perf_integral_min_clamp: u32, + /// Performance PID proportional gain. + pub(crate) perf_proportional_gain: F32, + /// Performance PID proportional gain 2 (?). + pub(crate) perf_proportional_gain2: F32, + pub(crate) perf_reset_iters: u32, + /// Target GPU utilization for the performance controller in %. + pub(crate) perf_tgt_utilization: u32, + /// Power sampling period in milliseconds. + pub(crate) power_sample_period: u32, + /// PPM (?) filter time constant in milliseconds. + pub(crate) ppm_filter_time_constant_ms: u32, + /// PPM (?) filter PID integral gain. + pub(crate) ppm_ki: F32, + /// PPM (?) filter PID proportional gain. + pub(crate) ppm_kp: F32, + /// Power consumption filter time constant (periods?) + pub(crate) pwr_filter_time_constant: u32, + /// Power consumption filter PID integral gain. + pub(crate) pwr_integral_gain: F32, + pub(crate) pwr_integral_min_clamp: u32, + pub(crate) pwr_min_duty_cycle: u32, + pub(crate) pwr_proportional_gain: F32, + /// Power sample period in base clocks, used when not an integer number of ms + pub(crate) pwr_sample_period_aic_clks: u32, + + pub(crate) se_engagement_criteria: i32, + pub(crate) se_filter_time_constant: u32, + pub(crate) se_filter_time_constant_1: u32, + pub(crate) se_inactive_threshold: u32, + pub(crate) se_ki: F32, + pub(crate) se_ki_1: F32, + pub(crate) se_kp: F32, + pub(crate) se_kp_1: F32, + pub(crate) se_reset_criteria: u32, +} + +impl PwrConfig { + fn load_opp( + dev: &AsahiDevice, + name: &CStr, + cfg: &HwConfig, + is_main: bool, + ) -> Result> { + let mut perf_states = KVec::new(); + + let node = dev.as_ref().of_node().ok_or(EIO)?; + let opps = node.parse_phandle(name, 0).ok_or(EIO)?; + + for opp in opps.children() { + let freq_hz: u64 = opp.get_property(c_str!("opp-hz"))?; + let mut volt_uv: KVec = opp.get_property(c_str!("opp-microvolt"))?; + let pwr_uw: u32 = if is_main { + opp.get_property(c_str!("opp-microwatt"))? + } else { + 0 + }; + + let voltage_count = if is_main { + cfg.max_num_clusters + } else { + cfg.num_dies + }; + + if volt_uv.len() != voltage_count as usize { + dev_err!( + dev.as_ref(), + "Invalid opp-microvolt length (expected {}, got {})\n", + voltage_count, + volt_uv.len() + ); + return Err(EINVAL); + } + + volt_uv.iter_mut().for_each(|a| *a /= 1000); + let volt_mv = volt_uv; + + let pwr_mw = pwr_uw / 1000; + + perf_states.push( + PState { + freq_hz: freq_hz.try_into()?, + volt_mv, + pwr_mw, + }, + GFP_KERNEL, + )?; + } + + if perf_states.is_empty() { + Err(EINVAL) + } else { + Ok(perf_states) + } + } + + /// Load the GPU power configuration from the device tree. + pub(crate) fn load(dev: &AsahiDevice, cfg: &HwConfig) -> Result { + let perf_states = Self::load_opp(dev, c_str!("operating-points-v2"), cfg, true)?; + let node = dev.as_ref().of_node().ok_or(EIO)?; + + macro_rules! prop { + ($prop:expr, $default:expr) => {{ + node.get_opt_property(c_str!($prop)) + .map_err(|e| { + dev_err!(dev.as_ref(), "Error reading property {}: {:?}\n", $prop, e); + e + })? + .unwrap_or($default) + }}; + ($prop:expr) => {{ + node.get_property(c_str!($prop)).map_err(|e| { + dev_err!(dev.as_ref(), "Error reading property {}: {:?}\n", $prop, e); + e + })? + }}; + } + + let pz_data = prop!("apple,power-zones", KVec::new()); + + if pz_data.len() > 3 * MAX_POWERZONES || pz_data.len() % 3 != 0 { + dev_err!(dev.as_ref(), "Invalid apple,power-zones value\n"); + return Err(EINVAL); + } + + let pz_count = pz_data.len() / 3; + let mut power_zones = KVec::new(); + for i in (0..pz_count).step_by(3) { + power_zones.push( + PowerZone { + target: pz_data[i], + target_offset: pz_data[i + 1], + filter_tc: pz_data[i + 2], + }, + GFP_KERNEL, + )?; + } + + let core_leak_coef: KVec = prop!("apple,core-leak-coef"); + let sram_leak_coef: KVec = prop!("apple,sram-leak-coef"); + + if core_leak_coef.len() != cfg.max_num_clusters as usize { + dev_err!(dev.as_ref(), "Invalid apple,core-leak-coef\n"); + return Err(EINVAL); + } + if sram_leak_coef.len() != cfg.max_num_clusters as usize { + dev_err!(dev.as_ref(), "Invalid apple,sram_leak_coef\n"); + return Err(EINVAL); + } + + let csafr = if cfg.has_csafr { + Some(CsAfrPwrConfig { + perf_states_cs: Self::load_opp(dev, c_str!("apple,cs-opp"), cfg, false)?, + perf_states_afr: Self::load_opp(dev, c_str!("apple,afr-opp"), cfg, false)?, + leak_coef_cs: prop!("apple,cs-leak-coef"), + leak_coef_afr: prop!("apple,afr-leak-coef"), + min_sram_microvolt: prop!("apple,csafr-min-sram-microvolt"), + }) + } else { + None + }; + + let power_sample_period: u32 = prop!("apple,power-sample-period"); + + Ok(PwrConfig { + core_leak_coef, + sram_leak_coef, + + max_power_mw: perf_states.iter().map(|a| a.pwr_mw).max().unwrap(), + max_freq_mhz: perf_states.iter().map(|a| a.freq_hz).max().unwrap() / 1_000_000, + + perf_base_pstate: prop!("apple,perf-base-pstate", 1), + perf_max_pstate: perf_states.len() as u32 - 1, + min_sram_microvolt: prop!("apple,min-sram-microvolt"), + + avg_power_filter_tc_ms: prop!("apple,avg-power-filter-tc-ms"), + avg_power_ki_only: prop!("apple,avg-power-ki-only"), + avg_power_kp: prop!("apple,avg-power-kp"), + avg_power_min_duty_cycle: prop!("apple,avg-power-min-duty-cycle"), + avg_power_target_filter_tc: prop!("apple,avg-power-target-filter-tc"), + fast_die0_integral_gain: prop!("apple,fast-die0-integral-gain"), + fast_die0_proportional_gain: prop!("apple,fast-die0-proportional-gain"), + fast_die0_prop_tgt_delta: prop!("apple,fast-die0-prop-tgt-delta", 0), + fast_die0_release_temp: prop!("apple,fast-die0-release-temp", 80), + fender_idle_off_delay_ms: prop!("apple,fender-idle-off-delay-ms", 40), + fw_early_wake_timeout_ms: prop!("apple,fw-early-wake-timeout-ms", 5), + idle_off_delay_ms: prop!("apple,idle-off-delay-ms", 2), + idle_off_standby_timer: prop!( + "apple,idleoff-standby-timer", + cfg.idle_off_standby_timer_default + ), + perf_boost_ce_step: prop!("apple,perf-boost-ce-step", 25), + perf_boost_min_util: prop!("apple,perf-boost-min-util", 100), + perf_filter_drop_threshold: prop!("apple,perf-filter-drop-threshold"), + perf_filter_time_constant2: prop!("apple,perf-filter-time-constant2"), + perf_filter_time_constant: prop!("apple,perf-filter-time-constant"), + perf_integral_gain2: prop!("apple,perf-integral-gain2"), + perf_integral_gain: prop!("apple,perf-integral-gain", f32!(7.8956833)), + perf_integral_min_clamp: prop!("apple,perf-integral-min-clamp"), + perf_proportional_gain2: prop!("apple,perf-proportional-gain2"), + perf_proportional_gain: prop!("apple,perf-proportional-gain", f32!(14.707963)), + perf_reset_iters: prop!("apple,perf-reset-iters", 6), + perf_tgt_utilization: prop!("apple,perf-tgt-utilization"), + power_sample_period, + ppm_filter_time_constant_ms: prop!("apple,ppm-filter-time-constant-ms"), + ppm_ki: prop!("apple,ppm-ki"), + ppm_kp: prop!("apple,ppm-kp"), + pwr_filter_time_constant: prop!("apple,pwr-filter-time-constant", 313), + pwr_integral_gain: prop!("apple,pwr-integral-gain", f32!(0.0202129)), + pwr_integral_min_clamp: prop!("apple,pwr-integral-min-clamp", 0), + pwr_min_duty_cycle: prop!("apple,pwr-min-duty-cycle"), + pwr_proportional_gain: prop!("apple,pwr-proportional-gain", f32!(5.2831855)), + pwr_sample_period_aic_clks: prop!( + "apple,pwr-sample-period-aic-clks", + cfg.base_clock_hz / 1000 * power_sample_period + ), + se_engagement_criteria: prop!("apple,se-engagement-criteria", -1), + se_filter_time_constant: prop!("apple,se-filter-time-constant", 9), + se_filter_time_constant_1: prop!("apple,se-filter-time-constant-1", 3), + se_inactive_threshold: prop!("apple,se-inactive-threshold", 2500), + se_ki: prop!("apple,se-ki", f32!(-50.0)), + se_ki_1: prop!("apple,se-ki-1", f32!(-100.0)), + se_kp: prop!("apple,se-kp", f32!(-5.0)), + se_kp_1: prop!("apple,se-kp-1", f32!(-10.0)), + se_reset_criteria: prop!("apple,se-reset-criteria", 50), + + perf_states, + power_zones, + csafr, + }) + } + + pub(crate) fn max_frequency_khz(&self) -> u32 { + self.perf_states[self.perf_max_pstate as usize].freq_hz / 1000 + } +} diff --git a/drivers/gpu/drm/asahi/hw/t600x.rs b/drivers/gpu/drm/asahi/hw/t600x.rs new file mode 100644 index 00000000000000..58665f985ec38e --- /dev/null +++ b/drivers/gpu/drm/asahi/hw/t600x.rs @@ -0,0 +1,161 @@ +// SPDX-License-Identifier: GPL-2.0-only OR MIT + +//! Hardware configuration for t600x (M1 Pro/Max/Ultra) platforms. + +use crate::f32; + +use super::*; + +const fn iomaps(mcc_count: usize, has_die1: bool) -> [Option; 20] { + [ + Some(IOMapping::new(0x404d00000, false, 1, 0x1c000, 0, true)), // Fender + Some(IOMapping::new(0x20e100000, false, 1, 0x4000, 0, false)), // AICTimer + Some(IOMapping::new(0x28e104000, false, 1, 0x4000, 0, true)), // AICSWInt + Some(IOMapping::new(0x404000000, false, 1, 0x20000, 0, true)), // RGX + None, // UVD + None, // unused + None, // DisplayUnderrunWA + Some(IOMapping::new(0x28e494000, true, 1, 0x4000, 0, false)), // AnalogTempSensorControllerRegs + None, // PMPDoorbell + Some(IOMapping::new(0x404d80000, false, 1, 0x8000, 0, true)), // MetrologySensorRegs + Some(IOMapping::new(0x204d61000, false, 1, 0x1000, 0, true)), // GMGIFAFRegs + Some(IOMapping::new( + 0x200000000, + true, + mcc_count, + 0xd8000, + 0x1000000, + true, + )), // MCache registers + None, // AICBankedRegisters + None, // PMGRScratch + Some(IOMapping::new(0x2643c4000, false, 1, 0x1000, 0, true)), // NIA Special agent idle register die 0 + if has_die1 { + // NIA Special agent idle register die 1 + Some(IOMapping::new(0x22643c4000, false, 1, 0x1000, 0, true)) + } else { + None + }, + None, // CRE registers + None, // Streaming codec registers + Some(IOMapping::new(0x28e3d0000, false, 1, 0x1000, 0, true)), // ? + Some(IOMapping::new(0x28e3c0000, false, 1, 0x2000, 0, false)), // ? + ] +} + +pub(crate) const HWCONFIG_T6002: super::HwConfig = HwConfig { + chip_id: 0x6002, + gpu_gen: GpuGen::G13, + gpu_variant: GpuVariant::D, + gpu_core: GpuCore::G13C, + + base_clock_hz: 24_000_000, + uat_oas: 42, + num_dies: 2, + max_num_clusters: 8, + max_num_cores: 8, + max_num_frags: 8, + max_num_gps: 4, + + preempt1_size: 0x540, + preempt2_size: 0x280, + preempt3_size: 0x20, + compute_preempt1_size: 0x3bd00, + clustering: Some(HwClusteringConfig { + meta1_blocksize: 0x44, + meta2_size: 0xc0 * 8, + meta3_size: 0x280 * 8, + meta4_size: 0x30 * 16, + max_splits: 16, + }), + + render: HwRenderConfig { + tiling_control: 0xa540, + }, + + da: HwConfigA { + unk_87c: 900, + unk_8cc: 11000, + unk_e24: 125, + }, + db: HwConfigB { + unk_454: 1, + unk_4e0: 4, + unk_534: 1, + unk_ab8: 0x2084, + unk_abc: 0x80, + unk_b30: 0, + }, + shared1_tab: &[ + 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, + 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, + ], + shared1_a4: 0xffff, + shared2_tab: &[-1, -1, -1, -1, 0x2aa, 0xaaa, -1, -1, 0, 0], + shared2_unk_508: 0xcc00001, + shared2_curves: None, + shared3_unk: 0, + shared3_tab: &[], + idle_off_standby_timer_default: 0, + unk_hws2_4: None, + unk_hws2_24: 0, + global_unk_54: 0xffff, + sram_k: f32!(1.02), + unk_coef_a: &[ + &f32!([9.838]), + &f32!([9.819]), + &f32!([9.826]), + &f32!([9.799]), + &f32!([9.799]), + &f32!([9.826]), + &f32!([9.819]), + &f32!([9.838]), + ], + unk_coef_b: &[ + &f32!([13.0]), + &f32!([13.0]), + &f32!([13.0]), + &f32!([13.0]), + &f32!([13.0]), + &f32!([13.0]), + &f32!([13.0]), + &f32!([13.0]), + ], + global_tab: Some(&[ + 0, 1, 2, 1, 1, 90, 75, 1, 1, 1, 2, 90, 75, 1, 1, 1, 1, 90, 75, 1, 1, + ]), + has_csafr: false, + fast_sensor_mask: [0x8080808080808080, 0], + fast_sensor_mask_alt: [0x9090909090909090, 0], + fast_die0_sensor_present: 0xff, + io_mappings: &iomaps(8, true), + sram_base: None, + sram_size: None, +}; + +pub(crate) const HWCONFIG_T6001: super::HwConfig = HwConfig { + chip_id: 0x6001, + gpu_variant: GpuVariant::C, + gpu_core: GpuCore::G13C, + + num_dies: 1, + max_num_clusters: 4, + fast_sensor_mask: [0x80808080, 0], + fast_sensor_mask_alt: [0x90909090, 0], + fast_die0_sensor_present: 0x0f, + io_mappings: &iomaps(8, false), + ..HWCONFIG_T6002 +}; + +pub(crate) const HWCONFIG_T6000: super::HwConfig = HwConfig { + chip_id: 0x6000, + gpu_variant: GpuVariant::S, + gpu_core: GpuCore::G13S, + + max_num_clusters: 2, + fast_sensor_mask: [0x8080, 0], + fast_sensor_mask_alt: [0x9090, 0], + fast_die0_sensor_present: 0x03, + io_mappings: &iomaps(4, false), + ..HWCONFIG_T6001 +}; diff --git a/drivers/gpu/drm/asahi/hw/t602x.rs b/drivers/gpu/drm/asahi/hw/t602x.rs new file mode 100644 index 00000000000000..98a7ac2b76e571 --- /dev/null +++ b/drivers/gpu/drm/asahi/hw/t602x.rs @@ -0,0 +1,179 @@ +// SPDX-License-Identifier: GPL-2.0-only OR MIT + +//! Hardware configuration for t600x (M1 Pro/Max/Ultra) platforms. + +use crate::f32; + +use super::*; + +const fn iomaps(chip_id: u32, mcc_count: usize) -> [Option; 24] { + [ + Some(IOMapping::new(0x404d00000, false, 1, 0x144000, 0, true)), // Fender + Some(IOMapping::new(0x20e100000, false, 1, 0x4000, 0, false)), // AICTimer + Some(IOMapping::new(0x28e106000, false, 1, 0x4000, 0, true)), // AICSWInt + Some(IOMapping::new(0x404000000, false, 1, 0x20000, 0, true)), // RGX + None, // UVD + None, // unused + None, // DisplayUnderrunWA + Some(match chip_id { + 0x6020 => IOMapping::new(0x28e460000, true, 1, 0x4000, 0, false), + _ => IOMapping::new(0x28e478000, true, 1, 0x4000, 0, false), + }), // AnalogTempSensorControllerRegs + None, // PMPDoorbell + Some(IOMapping::new(0x404e08000, false, 1, 0x8000, 0, true)), // MetrologySensorRegs + None, // GMGIFAFRegs + Some(IOMapping::new( + 0x200000000, + true, + mcc_count, + 0xd8000, + 0x1000000, + true, + )), // MCache registers + Some(IOMapping::new(0x28e118000, false, 1, 0x4000, 0, false)), // AICBankedRegisters + None, // PMGRScratch + None, // NIA Special agent idle register die 0 + None, // NIA Special agent idle register die 1 + None, // CRE registers + None, // Streaming codec registers + Some(IOMapping::new(0x28e3d0000, false, 1, 0x4000, 0, true)), // ? + Some(IOMapping::new(0x28e3c0000, false, 1, 0x4000, 0, false)), // ? + Some(IOMapping::new(0x28e3d8000, false, 1, 0x4000, 0, true)), // ? + Some(IOMapping::new(0x404eac000, true, 1, 0x4000, 0, true)), // ? + None, + None, + ] +} + +// TODO: Tentative +pub(crate) const HWCONFIG_T6022: super::HwConfig = HwConfig { + chip_id: 0x6022, + gpu_gen: GpuGen::G14, + gpu_variant: GpuVariant::D, + gpu_core: GpuCore::G14D, + + base_clock_hz: 24_000_000, + uat_oas: 42, + num_dies: 2, + max_num_clusters: 8, + max_num_cores: 10, + max_num_frags: 10, + max_num_gps: 4, + + preempt1_size: 0x540, + preempt2_size: 0x280, + preempt3_size: 0x40, + compute_preempt1_size: 0x25980 * 2, // Conservative guess + clustering: Some(HwClusteringConfig { + meta1_blocksize: 0x44, + meta2_size: 0xc0 * 16, + meta3_size: 0x280 * 16, + meta4_size: 0x10 * 128, + max_splits: 64, + }), + + render: HwRenderConfig { + tiling_control: 0x180340, + }, + + da: HwConfigA { + unk_87c: 500, + unk_8cc: 11000, + unk_e24: 125, + }, + db: HwConfigB { + unk_454: 1, + unk_4e0: 4, + unk_534: 0, + unk_ab8: 0, // Unused + unk_abc: 0, // Unused + unk_b30: 0, + }, + shared1_tab: &[ + 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, + 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, + ], + shared1_a4: 0, + shared2_tab: &[0x800, 0x1555, -1, -1, -1, -1, -1, -1, 0xaaaaa, 0], + shared2_unk_508: 0xc00007, + shared2_curves: Some(HwConfigShared2Curves { + t1_coef: 11000, + t2: &[ + 0xf07, 0x4c0, 0x680, 0x8c0, 0xa80, 0xc40, 0xd80, 0xec0, 0xf40, + ], + t3_coefs: &[0, 20, 27, 36, 43, 50, 55, 60, 62], + t3_scales: &[9, 3209, 10400], + }), + shared3_unk: 8, + shared3_tab: &[ + 125, 125, 125, 125, 125, 125, 125, 125, 7500, 125, 125, 125, 125, 125, 125, 125, + ], + idle_off_standby_timer_default: 700, + unk_hws2_4: Some(f32!([1.0, 0.8, 0.2, 0.9, 0.1, 0.25, 0.5, 0.9])), + unk_hws2_24: 6, + global_unk_54: 4000, + sram_k: f32!(1.02), + unk_coef_a: &[ + &f32!([0.0, 8.2, 0.0, 6.9, 6.9]), + &f32!([0.0, 0.0, 0.0, 6.9, 6.9]), + &f32!([0.0, 8.2, 0.0, 6.9, 0.0]), + &f32!([0.0, 0.0, 0.0, 6.9, 0.0]), + &f32!([0.0, 0.0, 0.0, 6.9, 0.0]), + &f32!([0.0, 8.2, 0.0, 6.9, 0.0]), + &f32!([0.0, 0.0, 0.0, 6.9, 6.9]), + &f32!([0.0, 8.2, 0.0, 6.9, 6.9]), + ], + unk_coef_b: &[ + &f32!([0.0, 9.0, 0.0, 8.0, 8.0]), + &f32!([0.0, 0.0, 0.0, 8.0, 8.0]), + &f32!([0.0, 9.0, 0.0, 8.0, 0.0]), + &f32!([0.0, 0.0, 0.0, 8.0, 0.0]), + &f32!([0.0, 0.0, 0.0, 8.0, 0.0]), + &f32!([0.0, 9.0, 0.0, 8.0, 0.0]), + &f32!([0.0, 0.0, 0.0, 8.0, 8.0]), + &f32!([0.0, 9.0, 0.0, 8.0, 8.0]), + ], + global_tab: Some(&[ + 0, 2, 2, 1, 1, 90, 75, 1, 1, 1, 2, 90, 75, 1, 1, 1, 2, 90, 75, 1, 1, 1, 1, 90, 75, 1, 1, + ]), + has_csafr: true, + fast_sensor_mask: [0x40005000c000d00, 0xd000c0005000400], + // Apple typo? Should probably be 0x140015001c001d00 + fast_sensor_mask_alt: [0x140015001d001d00, 0x1d001c0015001400], + fast_die0_sensor_present: 0, // Unused + io_mappings: &iomaps(0x6022, 8), + sram_base: Some(0x404d60000), + sram_size: Some(0x20000), +}; + +pub(crate) const HWCONFIG_T6021: super::HwConfig = HwConfig { + chip_id: 0x6021, + gpu_variant: GpuVariant::C, + gpu_core: GpuCore::G14C, + + num_dies: 1, + max_num_clusters: 4, + compute_preempt1_size: 0x25980, + unk_hws2_4: Some(f32!([1.0, 0.8, 0.2, 0.9, 0.1, 0.25, 0.7, 0.9])), + fast_sensor_mask: [0x40005000c000d00, 0], + fast_sensor_mask_alt: [0x140015001d001d00, 0], + io_mappings: &iomaps(0x6021, 8), + ..HWCONFIG_T6022 +}; + +pub(crate) const HWCONFIG_T6020: super::HwConfig = HwConfig { + chip_id: 0x6020, + gpu_variant: GpuVariant::S, + gpu_core: GpuCore::G14S, + + db: HwConfigB { + unk_454: 0, + ..HWCONFIG_T6021.db + }, + + max_num_clusters: 2, + fast_sensor_mask: [0xc000d00, 0], + fast_sensor_mask_alt: [0x1d001d00, 0], + io_mappings: &iomaps(0x6020, 4), + ..HWCONFIG_T6021 +}; diff --git a/drivers/gpu/drm/asahi/hw/t8103.rs b/drivers/gpu/drm/asahi/hw/t8103.rs new file mode 100644 index 00000000000000..484bf6c3414f2f --- /dev/null +++ b/drivers/gpu/drm/asahi/hw/t8103.rs @@ -0,0 +1,92 @@ +// SPDX-License-Identifier: GPL-2.0-only OR MIT + +//! Hardware configuration for t8103 platforms (M1). + +use crate::f32; + +use super::*; + +pub(crate) const HWCONFIG: super::HwConfig = HwConfig { + chip_id: 0x8103, + gpu_gen: GpuGen::G13, + gpu_variant: GpuVariant::G, + gpu_core: GpuCore::G13G, + + base_clock_hz: 24_000_000, + uat_oas: 40, + num_dies: 1, + max_num_clusters: 1, + max_num_cores: 8, + max_num_frags: 8, + max_num_gps: 4, + + preempt1_size: 0x540, + preempt2_size: 0x280, + preempt3_size: 0x20, + compute_preempt1_size: 0x7f80, + clustering: None, + + render: HwRenderConfig { + // bit 0: disable clustering (always) + tiling_control: 0xa041, + }, + + da: HwConfigA { + unk_87c: -220, + unk_8cc: 9880, + unk_e24: 112, + }, + db: HwConfigB { + unk_454: 1, + unk_4e0: 0, + unk_534: 0, + unk_ab8: 0x48, + unk_abc: 0x8, + unk_b30: 0, + }, + shared1_tab: &[ + -1, 0x7282, 0x50ea, 0x370a, 0x25be, 0x1c1f, 0x16fb, -1, -1, -1, -1, -1, -1, -1, -1, -1, + ], + shared1_a4: 0xffff, + shared2_tab: &[0x800, 0x1555, -1, -1, -1, -1, -1, -1, 0, 0], + shared2_unk_508: 0xc00007, + shared2_curves: None, + shared3_unk: 0, + shared3_tab: &[], + idle_off_standby_timer_default: 0, + unk_hws2_4: None, + unk_hws2_24: 0, + global_unk_54: 0xffff, + sram_k: f32!(1.02), + unk_coef_a: &[], + unk_coef_b: &[], + global_tab: None, + has_csafr: false, + fast_sensor_mask: [0x12, 0], + fast_sensor_mask_alt: [0x12, 0], + fast_die0_sensor_present: 0x01, + io_mappings: &[ + Some(IOMapping::new(0x204d00000, false, 1, 0x1c000, 0, true)), // Fender + Some(IOMapping::new(0x20e100000, false, 1, 0x4000, 0, false)), // AICTimer + Some(IOMapping::new(0x23b104000, false, 1, 0x4000, 0, true)), // AICSWInt + Some(IOMapping::new(0x204000000, false, 1, 0x20000, 0, true)), // RGX + None, // UVD + None, // unused + None, // DisplayUnderrunWA + Some(IOMapping::new(0x23b2e8000, false, 1, 0x1000, 0, false)), // AnalogTempSensorControllerRegs + Some(IOMapping::new(0x23bc00000, false, 1, 0x1000, 0, true)), // PMPDoorbell + Some(IOMapping::new(0x204d80000, false, 1, 0x5000, 0, true)), // MetrologySensorRegs + Some(IOMapping::new(0x204d61000, false, 1, 0x1000, 0, true)), // GMGIFAFRegs + Some(IOMapping::new(0x200000000, false, 1, 0xd6400, 0, true)), // MCache registers + None, // AICBankedRegisters + Some(IOMapping::new(0x23b738000, false, 1, 0x1000, 0, true)), // PMGRScratch + None, // NIA Special agent idle register die 0 + None, // NIA Special agent idle register die 1 + None, // CRE registers + None, // Streaming codec registers + None, // + None, // + ], + sram_base: None, + sram_size: None, +}; diff --git a/drivers/gpu/drm/asahi/hw/t8112.rs b/drivers/gpu/drm/asahi/hw/t8112.rs new file mode 100644 index 00000000000000..3eba0457d76ac9 --- /dev/null +++ b/drivers/gpu/drm/asahi/hw/t8112.rs @@ -0,0 +1,105 @@ +// SPDX-License-Identifier: GPL-2.0-only OR MIT + +//! Hardware configuration for t8112 platforms (M2). + +use crate::f32; + +use super::*; + +pub(crate) const HWCONFIG: super::HwConfig = HwConfig { + chip_id: 0x8112, + gpu_gen: GpuGen::G14, + gpu_variant: GpuVariant::G, + gpu_core: GpuCore::G14G, + + base_clock_hz: 24_000_000, + uat_oas: 40, + num_dies: 1, + max_num_clusters: 1, + max_num_cores: 10, + max_num_frags: 10, + max_num_gps: 4, + + preempt1_size: 0x540, + preempt2_size: 0x280, + preempt3_size: 0x20, + compute_preempt1_size: 0x10000, // TODO: Check + clustering: None, + + render: HwRenderConfig { + // TODO: this is unused here, may be present in newer FW + tiling_control: 0xa041, + }, + + da: HwConfigA { + unk_87c: 900, + unk_8cc: 11000, + unk_e24: 125, + }, + db: HwConfigB { + unk_454: 1, + unk_4e0: 4, + unk_534: 0, + unk_ab8: 0x2048, + unk_abc: 0x4000, + unk_b30: 1, + }, + shared1_tab: &[ + 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, + 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, + ], + shared1_a4: 0, + shared2_tab: &[-1, -1, -1, -1, -1, -1, -1, -1, 0xaa5aa, 0], + shared2_unk_508: 0xc00000, + shared2_curves: Some(HwConfigShared2Curves { + t1_coef: 7200, + t2: &[ + 0xf07, 0x4c0, 0x6c0, 0x8c0, 0xac0, 0xc40, 0xdc0, 0xec0, 0xf80, + ], + t3_coefs: &[0, 20, 28, 36, 44, 50, 56, 60, 63], + t3_scales: &[9, 3209, 10400], + }), + shared3_unk: 5, + shared3_tab: &[ + 10700, 10700, 10700, 10700, 10700, 6000, 1000, 1000, 1000, 10700, 10700, 10700, 10700, + 10700, 10700, 10700, + ], + idle_off_standby_timer_default: 0, + unk_hws2_4: None, + unk_hws2_24: 0, + global_unk_54: 0xffff, + + sram_k: f32!(1.02), + // 13.2: last coef changed from 6.6 to 5.3, assuming that was a fix we can backport + unk_coef_a: &[&f32!([0.0, 0.0, 0.0, 0.0, 5.3, 0.0, 5.3, /*6.6*/ 5.3])], + unk_coef_b: &[&f32!([0.0, 0.0, 0.0, 0.0, 5.3, 0.0, 5.3, /*6.6*/ 5.3])], + global_tab: None, + has_csafr: false, + fast_sensor_mask: [0x6800, 0], + fast_sensor_mask_alt: [0x6800, 0], + fast_die0_sensor_present: 0x02, + io_mappings: &[ + Some(IOMapping::new(0x204d00000, false, 1, 0x14000, 0, true)), // Fender + Some(IOMapping::new(0x20e100000, false, 1, 0x4000, 0, false)), // AICTimer + Some(IOMapping::new(0x23b0c4000, false, 1, 0x4000, 0, true)), // AICSWInt + Some(IOMapping::new(0x204000000, false, 1, 0x20000, 0, true)), // RGX + None, // UVD + None, // unused + None, // DisplayUnderrunWA + Some(IOMapping::new(0x23b2c0000, false, 1, 0x1000, 0, false)), // AnalogTempSensorControllerRegs + None, // PMPDoorbell + Some(IOMapping::new(0x204d80000, false, 1, 0x8000, 0, true)), // MetrologySensorRegs + Some(IOMapping::new(0x204d61000, false, 1, 0x1000, 0, true)), // GMGIFAFRegs + Some(IOMapping::new(0x200000000, false, 1, 0xd6400, 0, true)), // MCache registers + None, // AICBankedRegisters + None, // PMGRScratch + None, // NIA Special agent idle register die 0 + None, // NIA Special agent idle register die 1 + Some(IOMapping::new(0x204e00000, false, 1, 0x10000, 0, true)), // CRE registers + Some(IOMapping::new(0x27d050000, false, 1, 0x4000, 0, true)), // Streaming codec registers + Some(IOMapping::new(0x23b3d0000, false, 1, 0x1000, 0, true)), // + Some(IOMapping::new(0x23b3c0000, false, 1, 0x1000, 0, false)), // + ], + sram_base: None, + sram_size: None, +}; diff --git a/drivers/gpu/drm/asahi/initdata.rs b/drivers/gpu/drm/asahi/initdata.rs new file mode 100644 index 00000000000000..4573c3ca29b2fc --- /dev/null +++ b/drivers/gpu/drm/asahi/initdata.rs @@ -0,0 +1,933 @@ +// SPDX-License-Identifier: GPL-2.0-only OR MIT +#![allow(clippy::unusual_byte_groupings)] + +//! GPU initialization data builder. +//! +//! The root of all interaction between the GPU firmware and the host driver is a complex set of +//! nested structures that we call InitData. This includes both GPU hardware/firmware configuration +//! and the pointers to the ring buffers and global data fields that are used for communication at +//! runtime. +//! +//! Many of these structures are poorly understood, so there are lots of hardcoded unknown values +//! derived from observing the InitData structures that macOS generates. + +use crate::f32; +use crate::fw::initdata::*; +use crate::fw::types::*; +use crate::module_parameters; +use crate::{ + driver::AsahiDevice, + gem, + gpu, + hw, + mmu, // +}; +use kernel::error::{ + Error, + Result, // +}; +use kernel::macros::versions; +use kernel::prelude::*; +use kernel::try_init; + +use ::pin_init; +use ::pin_init::Init; + +/// Builder helper for the global GPU InitData. +#[versions(AGX)] +pub(crate) struct InitDataBuilder<'a> { + dev: &'a AsahiDevice, + alloc: &'a mut gpu::KernelAllocators, + cfg: &'static hw::HwConfig, + dyncfg: &'a hw::DynConfig, +} + +#[versions(AGX)] +impl<'a> InitDataBuilder::ver<'a> { + /// Create a new InitData builder + pub(crate) fn new( + dev: &'a AsahiDevice, + alloc: &'a mut gpu::KernelAllocators, + cfg: &'static hw::HwConfig, + dyncfg: &'a hw::DynConfig, + ) -> InitDataBuilder::ver<'a> { + InitDataBuilder::ver { + dev, + alloc, + cfg, + dyncfg, + } + } + + /// Create the HwDataShared1 structure, which is used in two places in InitData. + fn hw_shared1(cfg: &'static hw::HwConfig) -> impl Init { + init!(raw::HwDataShared1 { + unk_a4: cfg.shared1_a4, + ..Zeroable::init_zeroed() + }) + .chain(|ret| { + for (i, val) in cfg.shared1_tab.iter().enumerate() { + ret.table[i] = *val; + } + Ok(()) + }) + } + + fn init_curve( + curve: &mut raw::HwDataShared2Curve, + unk_0: u32, + unk_4: u32, + t1: &[u16], + t2: &[i16], + t3: &[KVec], + ) { + curve.unk_0 = unk_0; + curve.unk_4 = unk_4; + (*curve.t1)[..t1.len()].copy_from_slice(t1); + (*curve.t1)[t1.len()..].fill(t1[0]); + (*curve.t2)[..t2.len()].copy_from_slice(t2); + (*curve.t2)[t2.len()..].fill(t2[0]); + for (i, a) in curve.t3.iter_mut().enumerate() { + a.fill(0x3ffffff); + if i < t3.len() { + let b = &t3[i]; + (**a)[..b.len()].copy_from_slice(b); + } + } + } + + /// Create the HwDataShared2 structure, which is used in two places in InitData. + fn hw_shared2( + cfg: &'static hw::HwConfig, + dyncfg: &'a hw::DynConfig, + ) -> impl Init + 'a { + try_init!(raw::HwDataShared2 { + unk_28: Array::new([0xff; 16]), + g14: Default::default(), + unk_508: cfg.shared2_unk_508, + ..Zeroable::init_zeroed() + }) + .chain(|ret| { + for (i, val) in cfg.shared2_tab.iter().enumerate() { + ret.table[i] = *val; + } + + let curve_cfg = match cfg.shared2_curves.as_ref() { + None => return Ok(()), + Some(a) => a, + }; + + let mut t1 = KVec::new(); + let mut t3 = KVec::new(); + + for _ in 0..curve_cfg.t3_scales.len() { + t3.push(KVec::new(), GFP_KERNEL)?; + } + + for (i, ps) in dyncfg.pwr.perf_states.iter().enumerate() { + let t3_coef = curve_cfg.t3_coefs[i]; + if t3_coef == 0 { + t1.push(0xffff, GFP_KERNEL)?; + for j in t3.iter_mut() { + j.push(0x3ffffff, GFP_KERNEL)?; + } + continue; + } + + let f_khz = (ps.freq_hz / 1000) as u64; + let v_max = ps.max_volt_mv() as u64; + + t1.push( + (1000000000 * (curve_cfg.t1_coef as u64) / (f_khz * v_max)) + .try_into() + .unwrap(), + GFP_KERNEL, + )?; + + for (j, scale) in curve_cfg.t3_scales.iter().enumerate() { + t3[j].push( + (t3_coef as u64 * 1000000100 * *scale as u64 / (f_khz * v_max * 6)) + .try_into() + .unwrap(), + GFP_KERNEL, + )?; + } + } + + ret.g14.unk_14 = 0x6000000; + Self::init_curve( + &mut ret.g14.curve1, + 0, + 0x20000000, + &[0xffff], + &[0x0f07], + &[], + ); + Self::init_curve(&mut ret.g14.curve2, 7, 0x80000000, &t1, curve_cfg.t2, &t3); + + Ok(()) + }) + } + + /// Create the HwDataShared3 structure, which is used in two places in InitData. + fn hw_shared3(cfg: &'static hw::HwConfig) -> impl Init { + pin_init::init_zeroed::().chain(|ret| { + if !cfg.shared3_tab.is_empty() { + ret.unk_0 = 1; + ret.unk_4 = 500; + ret.unk_8 = cfg.shared3_unk; + ret.table.copy_from_slice(cfg.shared3_tab); + ret.unk_4c = 1; + } + Ok(()) + }) + } + + /// Create an unknown T81xx-specific data structure. + fn t81xx_data( + cfg: &'static hw::HwConfig, + dyncfg: &'a hw::DynConfig, + ) -> impl Init { + let _perf_max_pstate = dyncfg.pwr.perf_max_pstate; + + pin_init::init_zeroed::().chain(move |_ret| { + match cfg.chip_id { + 0x8103 | 0x8112 => { + #[ver(V < V13_3)] + { + _ret.unk_d8c = 0x80000000; + _ret.unk_d90 = 4; + _ret.unk_d9c = f32!(0.6); + _ret.unk_da4 = f32!(0.4); + _ret.unk_dac = f32!(0.38552); + _ret.unk_db8 = f32!(65536.0); + _ret.unk_dbc = f32!(13.56); + _ret.max_pstate_scaled = 100 * _perf_max_pstate; + } + } + _ => (), + } + Ok(()) + }) + } + + /// Create the HwDataA structure. This mostly contains power-related configuration. + fn hwdata_a(&mut self) -> Result> { + let pwr = &self.dyncfg.pwr; + let period_ms = pwr.power_sample_period; + let period_s = F32::from(period_ms) / f32!(1000.0); + let ppm_filter_tc_periods = pwr.ppm_filter_time_constant_ms / period_ms; + #[ver(V >= V13_0B4)] + let ppm_filter_tc_ms_rounded = ppm_filter_tc_periods * period_ms; + let ppm_filter_a = f32!(1.0) / ppm_filter_tc_periods.into(); + let perf_filter_a = f32!(1.0) / pwr.perf_filter_time_constant.into(); + let perf_filter_a2 = f32!(1.0) / pwr.perf_filter_time_constant2.into(); + let avg_power_target_filter_a = f32!(1.0) / pwr.avg_power_target_filter_tc.into(); + let avg_power_filter_tc_periods = pwr.avg_power_filter_tc_ms / period_ms; + #[ver(V >= V13_0B4)] + let avg_power_filter_tc_ms_rounded = avg_power_filter_tc_periods * period_ms; + let avg_power_filter_a = f32!(1.0) / avg_power_filter_tc_periods.into(); + let pwr_filter_a = f32!(1.0) / pwr.pwr_filter_time_constant.into(); + + let base_ps = pwr.perf_base_pstate; + let base_ps_scaled = 100 * base_ps; + let max_ps = pwr.perf_max_pstate; + let max_ps_scaled = 100 * max_ps; + let boost_ps_count = max_ps - base_ps; + + #[allow(unused_variables)] + let base_clock_khz = self.cfg.base_clock_hz / 1000; + let clocks_per_period = pwr.pwr_sample_period_aic_clks; + + #[allow(unused_variables)] + let clocks_per_period_coarse = self.cfg.base_clock_hz / 1000 * pwr.power_sample_period; + + self.alloc + .private + .new_init(pin_init::init_zeroed(), |_inner, _ptr| { + let cfg = &self.cfg; + let dyncfg = &self.dyncfg; + try_init!(raw::HwDataA::ver { + clocks_per_period: clocks_per_period, + #[ver(V >= V13_0B4)] + clocks_per_period_2: clocks_per_period, + pwr_status: AtomicU32::new(4), + unk_10: f32!(1.0), + actual_pstate: 1, + tgt_pstate: 1, + base_pstate_scaled: base_ps_scaled, + unk_40: 1, + max_pstate_scaled: max_ps_scaled, + min_pstate_scaled: 100, + unk_64c: 625, + pwr_filter_a_neg: f32!(1.0) - pwr_filter_a, + pwr_filter_a: pwr_filter_a, + pwr_integral_gain: pwr.pwr_integral_gain, + pwr_integral_min_clamp: pwr.pwr_integral_min_clamp.into(), + max_power_1: pwr.max_power_mw.into(), + pwr_proportional_gain: pwr.pwr_proportional_gain, + pwr_pstate_related_k: -F32::from(max_ps_scaled) / pwr.max_power_mw.into(), + pwr_pstate_max_dc_offset: pwr.pwr_min_duty_cycle as i32 - max_ps_scaled as i32, + max_pstate_scaled_2: max_ps_scaled, + max_power_2: pwr.max_power_mw, + max_pstate_scaled_3: max_ps_scaled, + ppm_filter_tc_periods_x4: ppm_filter_tc_periods * 4, + ppm_filter_a_neg: f32!(1.0) - ppm_filter_a, + ppm_filter_a: ppm_filter_a, + ppm_ki_dt: pwr.ppm_ki * period_s, + unk_6fc: f32!(65536.0), + ppm_kp: pwr.ppm_kp, + pwr_min_duty_cycle: pwr.pwr_min_duty_cycle, + max_pstate_scaled_4: max_ps_scaled, + unk_71c: f32!(0.0), + max_power_3: pwr.max_power_mw, + cur_power_mw_2: 0x0, + ppm_filter_tc_ms: pwr.ppm_filter_time_constant_ms, + #[ver(V >= V13_0B4)] + ppm_filter_tc_clks: ppm_filter_tc_ms_rounded * base_clock_khz, + perf_tgt_utilization: pwr.perf_tgt_utilization, + perf_boost_min_util: pwr.perf_boost_min_util, + perf_boost_ce_step: pwr.perf_boost_ce_step, + perf_reset_iters: pwr.perf_reset_iters, + unk_774: 6, + unk_778: 1, + perf_filter_drop_threshold: pwr.perf_filter_drop_threshold, + perf_filter_a_neg: f32!(1.0) - perf_filter_a, + perf_filter_a2_neg: f32!(1.0) - perf_filter_a2, + perf_filter_a: perf_filter_a, + perf_filter_a2: perf_filter_a2, + perf_ki: pwr.perf_integral_gain, + perf_ki2: pwr.perf_integral_gain2, + perf_integral_min_clamp: pwr.perf_integral_min_clamp.into(), + unk_79c: f32!(95.0), + perf_kp: pwr.perf_proportional_gain, + perf_kp2: pwr.perf_proportional_gain2, + boost_state_unk_k: F32::from(boost_ps_count) / f32!(0.95), + base_pstate_scaled_2: base_ps_scaled, + max_pstate_scaled_5: max_ps_scaled, + base_pstate_scaled_3: base_ps_scaled, + perf_tgt_utilization_2: pwr.perf_tgt_utilization, + base_pstate_scaled_4: base_ps_scaled, + unk_7fc: f32!(65536.0), + pwr_min_duty_cycle_2: pwr.pwr_min_duty_cycle.into(), + max_pstate_scaled_6: max_ps_scaled.into(), + max_freq_mhz: pwr.max_freq_mhz, + pwr_min_duty_cycle_3: pwr.pwr_min_duty_cycle, + min_pstate_scaled_4: f32!(100.0), + max_pstate_scaled_7: max_ps_scaled, + unk_alpha_neg: f32!(0.8), + unk_alpha: f32!(0.2), + fast_die0_sensor_mask: U64(cfg.fast_sensor_mask[0]), + #[ver(G >= G14X)] + fast_die1_sensor_mask: U64(cfg.fast_sensor_mask[1]), + fast_die0_release_temp_cc: 100 * pwr.fast_die0_release_temp, + unk_87c: cfg.da.unk_87c, + unk_880: 0x4, + unk_894: f32!(1.0), + + fast_die0_ki_dt: pwr.fast_die0_integral_gain * period_s, + unk_8a8: f32!(65536.0), + fast_die0_kp: pwr.fast_die0_proportional_gain, + pwr_min_duty_cycle_4: pwr.pwr_min_duty_cycle, + max_pstate_scaled_8: max_ps_scaled, + max_pstate_scaled_9: max_ps_scaled, + fast_die0_prop_tgt_delta: 100 * pwr.fast_die0_prop_tgt_delta, + unk_8cc: cfg.da.unk_8cc, + max_pstate_scaled_10: max_ps_scaled, + max_pstate_scaled_11: max_ps_scaled, + unk_c2c: 1, + power_zone_count: pwr.power_zones.len() as u32, + max_power_4: pwr.max_power_mw, + max_power_5: pwr.max_power_mw, + max_power_6: pwr.max_power_mw, + avg_power_target_filter_a_neg: f32!(1.0) - avg_power_target_filter_a, + avg_power_target_filter_a: avg_power_target_filter_a, + avg_power_target_filter_tc_x4: 4 * pwr.avg_power_target_filter_tc, + avg_power_target_filter_tc_xperiod: period_ms * pwr.avg_power_target_filter_tc, + #[ver(V >= V13_0B4)] + avg_power_target_filter_tc_clks: period_ms + * pwr.avg_power_target_filter_tc + * base_clock_khz, + avg_power_filter_tc_periods_x4: 4 * avg_power_filter_tc_periods, + avg_power_filter_a_neg: f32!(1.0) - avg_power_filter_a, + avg_power_filter_a: avg_power_filter_a, + avg_power_ki_dt: pwr.avg_power_ki_only * period_s, + unk_d20: f32!(65536.0), + avg_power_kp: pwr.avg_power_kp, + avg_power_min_duty_cycle: pwr.avg_power_min_duty_cycle, + max_pstate_scaled_12: max_ps_scaled, + max_pstate_scaled_13: max_ps_scaled, + max_power_7: pwr.max_power_mw.into(), + max_power_8: pwr.max_power_mw, + avg_power_filter_tc_ms: pwr.avg_power_filter_tc_ms, + #[ver(V >= V13_0B4)] + avg_power_filter_tc_clks: avg_power_filter_tc_ms_rounded * base_clock_khz, + max_pstate_scaled_14: max_ps_scaled, + t81xx_data <- Self::t81xx_data(cfg, dyncfg), + #[ver(V >= V13_0B4)] + unk_e10_0 <- { + let filter_a = f32!(1.0) / pwr.se_filter_time_constant.into(); + let filter_1_a = f32!(1.0) / pwr.se_filter_time_constant_1.into(); + try_init!(raw::HwDataA130Extra { + unk_38: 4, + unk_3c: 8000, + gpu_se_inactive_threshold: pwr.se_inactive_threshold, + gpu_se_engagement_criteria: pwr.se_engagement_criteria, + gpu_se_reset_criteria: pwr.se_reset_criteria, + unk_54: 50, + unk_58: 0x1, + gpu_se_filter_a_neg: f32!(1.0) - filter_a, + gpu_se_filter_1_a_neg: f32!(1.0) - filter_1_a, + gpu_se_filter_a: filter_a, + gpu_se_filter_1_a: filter_1_a, + gpu_se_ki_dt: pwr.se_ki * period_s, + gpu_se_ki_1_dt: pwr.se_ki_1 * period_s, + unk_7c: f32!(65536.0), + gpu_se_kp: pwr.se_kp, + gpu_se_kp_1: pwr.se_kp_1, + + #[ver(V >= V13_3)] + unk_8c: 100, + #[ver(V < V13_3)] + unk_8c: 40, + + max_pstate_scaled_1: max_ps_scaled, + unk_9c: f32!(8000.0), + unk_a0: 1400, + gpu_se_filter_time_constant_ms: pwr.se_filter_time_constant * period_ms, + gpu_se_filter_time_constant_1_ms: pwr.se_filter_time_constant_1 + * period_ms, + gpu_se_filter_time_constant_clks: U64((pwr.se_filter_time_constant + * clocks_per_period_coarse) + .into()), + gpu_se_filter_time_constant_1_clks: U64((pwr + .se_filter_time_constant_1 + * clocks_per_period_coarse) + .into()), + unk_c4: f32!(65536.0), + unk_114: f32!(65536.0), + unk_124: 40, + max_pstate_scaled_2: max_ps_scaled, + ..Zeroable::init_zeroed() + }) + }, + fast_die0_sensor_mask_2: U64(cfg.fast_sensor_mask[0]), + #[ver(G >= G14X)] + fast_die1_sensor_mask_2: U64(cfg.fast_sensor_mask[1]), + unk_e24: cfg.da.unk_e24, + unk_e28: 1, + fast_die0_sensor_mask_alt: U64(cfg.fast_sensor_mask_alt[0]), + #[ver(G >= G14X)] + fast_die1_sensor_mask_alt: U64(cfg.fast_sensor_mask_alt[1]), + #[ver(V < V13_0B4)] + fast_die0_sensor_present: U64(cfg.fast_die0_sensor_present as u64), + unk_163c: 1, + unk_3644: 0, + hws1 <- Self::hw_shared1(cfg), + hws2 <- Self::hw_shared2(cfg, dyncfg), + hws3 <- Self::hw_shared3(cfg), + unk_3ce8: 1, + ..Zeroable::init_zeroed() + }) + .chain(|raw| { + for i in 0..self.dyncfg.pwr.perf_states.len() { + raw.sram_k[i] = self.cfg.sram_k; + } + + for (i, coef) in pwr.core_leak_coef.iter().enumerate() { + raw.core_leak_coef[i] = *coef; + } + + for (i, coef) in pwr.sram_leak_coef.iter().enumerate() { + raw.sram_leak_coef[i] = *coef; + } + + #[ver(V >= V13_0B4)] + if let Some(csafr) = pwr.csafr.as_ref() { + for (i, coef) in csafr.leak_coef_afr.iter().enumerate() { + raw.aux_leak_coef.cs_1[i] = *coef; + raw.aux_leak_coef.cs_2[i] = *coef; + } + + for (i, coef) in csafr.leak_coef_cs.iter().enumerate() { + raw.aux_leak_coef.afr_1[i] = *coef; + raw.aux_leak_coef.afr_2[i] = *coef; + } + } + + for i in 0..self.dyncfg.id.num_clusters as usize { + if let Some(coef_a) = self.cfg.unk_coef_a.get(i) { + (*raw.unk_coef_a1[i])[..coef_a.len()].copy_from_slice(coef_a); + (*raw.unk_coef_a2[i])[..coef_a.len()].copy_from_slice(coef_a); + } + if let Some(coef_b) = self.cfg.unk_coef_b.get(i) { + (*raw.unk_coef_b1[i])[..coef_b.len()].copy_from_slice(coef_b); + (*raw.unk_coef_b2[i])[..coef_b.len()].copy_from_slice(coef_b); + } + } + + for (i, pz) in pwr.power_zones.iter().enumerate() { + raw.power_zones[i].target = pz.target; + raw.power_zones[i].target_off = pz.target - pz.target_offset; + raw.power_zones[i].filter_tc_x4 = 4 * pz.filter_tc; + raw.power_zones[i].filter_tc_xperiod = period_ms * pz.filter_tc; + let filter_a = f32!(1.0) / pz.filter_tc.into(); + raw.power_zones[i].filter_a = filter_a; + raw.power_zones[i].filter_a_neg = f32!(1.0) - filter_a; + #[ver(V >= V13_0B4)] + raw.power_zones[i].unk_10 = 1320000000; + } + + #[ver(V >= V13_0B4 && G >= G14X)] + for (i, j) in raw.hws2.g14.curve2.t1.iter().enumerate() { + raw.unk_hws2[i] = if *j == 0xffff { 0 } else { j / 2 }; + } + + Ok(()) + }) + }) + } + + /// Create the HwDataB structure. This mostly contains GPU-related configuration. + fn hwdata_b(&mut self) -> Result> { + self.alloc + .private + .new_init(pin_init::init_zeroed(), |_inner, _ptr| { + let cfg = &self.cfg; + let dyncfg = &self.dyncfg; + try_init!(raw::HwDataB::ver { + // Userspace VA map related + #[ver(V < V13_0B4)] + unk_0: U64(0x13_00000000), + unk_8: U64(0x14_00000000), + #[ver(V < V13_0B4)] + unk_10: U64(0x1_00000000), + unk_18: U64(0xffc00000), + // USC start + unk_20: U64(0), // U64(0x11_00000000), + unk_28: U64(0), // U64(0x11_00000000), + // Unknown page + //unk_30: U64(0x6f_ffff8000), + unk_30: U64(mmu::IOVA_UNK_PAGE), + timestamp_area_base: U64(gpu::IOVA_KERN_TIMESTAMP_RANGE.start), + // TODO: yuv matrices + chip_id: cfg.chip_id, + unk_454: cfg.db.unk_454, + unk_458: 0x1, + unk_460: 0x1, + unk_464: 0x1, + unk_468: 0x1, + unk_47c: 0x1, + unk_484: 0x1, + unk_48c: 0x1, + base_clock_khz: cfg.base_clock_hz / 1000, + power_sample_period: dyncfg.pwr.power_sample_period, + unk_49c: 0x1, + unk_4a0: 0x1, + unk_4a4: 0x1, + unk_4c0: 0x1f, + unk_4e0: U64(cfg.db.unk_4e0), + unk_4f0: 0x1, + unk_4f4: 0x1, + unk_504: 0x31, + unk_524: 0x1, // use_secure_cache_flush + unk_534: cfg.db.unk_534, + num_frags: dyncfg.id.num_frags * dyncfg.id.num_clusters, + unk_554: 0x1, + uat_ttb_base: U64(dyncfg.uat_ttb_base), + gpu_core_id: cfg.gpu_core as u32, + gpu_rev_id: dyncfg.id.gpu_rev_id as u32, + num_cores: dyncfg.id.num_cores * dyncfg.id.num_clusters, + max_pstate: dyncfg.pwr.perf_states.len() as u32 - 1, + #[ver(V < V13_0B4)] + num_pstates: dyncfg.pwr.perf_states.len() as u32, + #[ver(V < V13_0B4)] + min_sram_volt: dyncfg.pwr.min_sram_microvolt / 1000, + #[ver(V < V13_0B4)] + unk_ab8: cfg.db.unk_ab8, + #[ver(V < V13_0B4)] + unk_abc: cfg.db.unk_abc, + #[ver(V < V13_0B4)] + unk_ac0: 0x1020, + + #[ver(V >= V13_0B4)] + unk_ae4: Array::new([0x0, 0x3, 0x7, 0x7]), + #[ver(V < V13_0B4)] + unk_ae4: Array::new([0x0, 0xf, 0x3f, 0x3f]), + unk_b10: 0x1, + timer_offset: U64(0), + unk_b24: 0x1, + unk_b28: 0x1, + unk_b2c: 0x1, + unk_b30: cfg.db.unk_b30, + #[ver(V >= V13_0B4)] + unk_b38_0: 1, + #[ver(V >= V13_0B4)] + unk_b38_4: 1, + unk_b38: Array::new([0xffffffff; 12]), + #[ver(V >= V13_0B4 && V < V13_3)] + unk_c3c: 0x19, + #[ver(V >= V13_3)] + unk_c3c: 0x1a, + ..Zeroable::init_zeroed() + }) + .chain(|raw| { + #[ver(V >= V13_3)] + for i in 0..16 { + raw.unk_arr_0[i] = i as u32; + } + + let base_ps = self.dyncfg.pwr.perf_base_pstate as usize; + let max_ps = self.dyncfg.pwr.perf_max_pstate as usize; + let base_freq = self.dyncfg.pwr.perf_states[base_ps].freq_hz; + let max_freq = self.dyncfg.pwr.perf_states[max_ps].freq_hz; + + for (i, ps) in self.dyncfg.pwr.perf_states.iter().enumerate() { + raw.frequencies[i] = ps.freq_hz / 1000000; + for (j, mv) in ps.volt_mv.iter().enumerate() { + let sram_mv = (*mv).max(self.dyncfg.pwr.min_sram_microvolt / 1000); + raw.voltages[i][j] = *mv; + raw.voltages_sram[i][j] = sram_mv; + } + for j in ps.volt_mv.len()..raw.voltages[i].len() { + raw.voltages[i][j] = raw.voltages[i][0]; + raw.voltages_sram[i][j] = raw.voltages_sram[i][0]; + } + raw.sram_k[i] = self.cfg.sram_k; + raw.rel_max_powers[i] = ps.pwr_mw * 100 / self.dyncfg.pwr.max_power_mw; + raw.rel_boost_freqs[i] = if i > base_ps { + (ps.freq_hz - base_freq) / ((max_freq - base_freq) / 100) + } else { + 0 + }; + } + + #[ver(V >= V13_0B4)] + if let Some(csafr) = self.dyncfg.pwr.csafr.as_ref() { + let aux = &mut raw.aux_ps; + aux.cs_max_pstate = (csafr.perf_states_cs.len() - 1).try_into()?; + aux.afr_max_pstate = (csafr.perf_states_afr.len() - 1).try_into()?; + + for (i, ps) in csafr.perf_states_cs.iter().enumerate() { + aux.cs_frequencies[i] = ps.freq_hz / 1000000; + for (j, mv) in ps.volt_mv.iter().enumerate() { + let sram_mv = (*mv).max(csafr.min_sram_microvolt / 1000); + aux.cs_voltages[i][j] = *mv; + aux.cs_voltages_sram[i][j] = sram_mv; + } + } + + for (i, ps) in csafr.perf_states_afr.iter().enumerate() { + aux.afr_frequencies[i] = ps.freq_hz / 1000000; + for (j, mv) in ps.volt_mv.iter().enumerate() { + let sram_mv = (*mv).max(csafr.min_sram_microvolt / 1000); + aux.afr_voltages[i][j] = *mv; + aux.afr_voltages_sram[i][j] = sram_mv; + } + } + } + + // Special case override for T602x + #[ver(G == G14X)] + if dyncfg.id.gpu_rev_id == hw::GpuRevisionID::B1 { + raw.gpu_rev_id = hw::GpuRevisionID::B0 as u32; + } + + Ok(()) + }) + }) + } + + /// Create the Globals structure, which contains global firmware config including more power + /// configuration data and globals used to exchange state between the firmware and driver. + fn globals(&mut self) -> Result> { + self.alloc + .private + .new_init(pin_init::init_zeroed(), |_inner, _ptr| { + let cfg = &self.cfg; + let dyncfg = &self.dyncfg; + let pwr = &dyncfg.pwr; + let period_ms = pwr.power_sample_period; + let period_s = F32::from(period_ms) / f32!(1000.0); + let avg_power_filter_tc_periods = pwr.avg_power_filter_tc_ms / period_ms; + + let max_ps = pwr.perf_max_pstate; + let max_ps_scaled = 100 * max_ps; + + try_init!(raw::Globals::ver { + //ktrace_enable: 0xffffffff, + ktrace_enable: 0, + #[ver(V >= V13_2)] + unk_24_0: 3000, + unk_24: 0, + #[ver(V >= V13_0B4)] + debug: 0, + unk_28: 1, + #[ver(G >= G14X)] + unk_2c_0: 1, + #[ver(V >= V13_0B4 && G < G14X)] + unk_2c_0: 0, + unk_2c: 1, + unk_30: 0, + unk_34: 120, + sub <- try_init!(raw::GlobalsSub::ver { + unk_54: cfg.global_unk_54, + unk_56: 40, + unk_58: 0xffff, + unk_5e: U32(1), + unk_66: U32(1), + ..Zeroable::init_zeroed() + }), + unk_8900: 1, + pending_submissions: AtomicU32::new(0), + max_power: pwr.max_power_mw, + max_pstate_scaled: max_ps_scaled, + max_pstate_scaled_2: max_ps_scaled, + max_pstate_scaled_3: max_ps_scaled, + power_zone_count: pwr.power_zones.len() as u32, + avg_power_filter_tc_periods: avg_power_filter_tc_periods, + avg_power_ki_dt: pwr.avg_power_ki_only * period_s, + avg_power_kp: pwr.avg_power_kp, + avg_power_min_duty_cycle: pwr.avg_power_min_duty_cycle, + avg_power_target_filter_tc: pwr.avg_power_target_filter_tc, + unk_89bc: cfg.da.unk_8cc, + fast_die0_release_temp: 100 * pwr.fast_die0_release_temp, + unk_89c4: cfg.da.unk_87c, + fast_die0_prop_tgt_delta: 100 * pwr.fast_die0_prop_tgt_delta, + fast_die0_kp: pwr.fast_die0_proportional_gain, + fast_die0_ki_dt: pwr.fast_die0_integral_gain * period_s, + unk_89e0: 1, + max_power_2: pwr.max_power_mw, + ppm_kp: pwr.ppm_kp, + ppm_ki_dt: pwr.ppm_ki * period_s, + #[ver(V >= V13_0B4)] + unk_89f4_8: 1, + unk_89f4: 0, + hws1 <- Self::hw_shared1(cfg), + hws2 <- Self::hw_shared2(cfg, dyncfg), + hws3 <- Self::hw_shared3(cfg), + #[ver(V >= V13_0B4)] + idle_off_standby_timer: pwr.idle_off_standby_timer, + #[ver(V >= V13_0B4)] + unk_hws2_4: cfg.unk_hws2_4.map(Array::new).unwrap_or_default(), + #[ver(V >= V13_0B4)] + unk_hws2_24: cfg.unk_hws2_24, + unk_900c: 1, + #[ver(V >= V13_0B4)] + unk_9010_0: 1, + #[ver(V >= V13_0B4)] + unk_903c: 1, + #[ver(V < V13_0B4)] + unk_903c: 0, + fault_control: *module_parameters::fault_control.value(), + do_init: 1, + progress_check_interval_3d: 40, + progress_check_interval_ta: 10, + progress_check_interval_cl: 250, + #[ver(V >= V13_0B4)] + unk_1102c_0: 1, + #[ver(V >= V13_0B4)] + unk_1102c_4: 1, + #[ver(V >= V13_0B4)] + unk_1102c_8: 100, + #[ver(V >= V13_0B4)] + unk_1102c_c: 1, + idle_off_delay_ms: AtomicU32::new(pwr.idle_off_delay_ms), + fender_idle_off_delay_ms: pwr.fender_idle_off_delay_ms, + fw_early_wake_timeout_ms: pwr.fw_early_wake_timeout_ms, + cl_context_switch_timeout_ms: 40, + #[ver(V >= V13_0B4)] + cl_kill_timeout_ms: 50, + #[ver(V >= V13_0B4)] + unk_11edc: 0, + #[ver(V >= V13_0B4)] + unk_11efc: 0, + ..Zeroable::init_zeroed() + }) + .chain(|raw| { + for (i, pz) in self.dyncfg.pwr.power_zones.iter().enumerate() { + raw.power_zones[i].target = pz.target; + raw.power_zones[i].target_off = pz.target - pz.target_offset; + raw.power_zones[i].filter_tc = pz.filter_tc; + } + + if let Some(tab) = self.cfg.global_tab.as_ref() { + for (i, x) in tab.iter().enumerate() { + raw.unk_118ec[i] = *x; + } + raw.unk_118e8 = 1; + } + Ok(()) + }) + }) + } + + /// Create the RuntimePointers structure, which contains pointers to most of the other + /// structures including the ring buffer channels, statistics structures, and HwDataA/HwDataB. + fn runtime_pointers(&mut self) -> Result> { + let hwa = self.hwdata_a()?; + let hwb = self.hwdata_b()?; + + let mut buffer_mgr_ctl = gem::new_kernel_object(self.dev, 0x4000)?; + buffer_mgr_ctl.vmap()?.memset(0); + + GpuObject::new_init_prealloc( + self.alloc.private.alloc_object()?, + |_ptr| { + let alloc = &mut *self.alloc; + try_init!(RuntimePointers::ver { + stats <- { + let alloc = &mut *alloc; + try_init!(Stats::ver { + vtx: alloc.private.new_default::()?, + frag: alloc.private.new_init( + pin_init::init_zeroed::(), + |_inner, _ptr| { + try_init!(raw::GpuGlobalStatsFrag::ver { + total_cmds: 0, + unk_4: 0, + stats: Default::default(), + }) + } + )?, + comp: alloc.private.new_default::()?, + }) + }, + + hwdata_a: hwa, + unkptr_190: alloc.private.array_empty_tagged(0x80, b"I190")?, + unkptr_198: alloc.private.array_empty_tagged(0xc0, b"I198")?, + hwdata_b: hwb, + + unkptr_1b8: alloc.private.array_empty_tagged(0x1000, b"I1B8")?, + unkptr_1c0: alloc.private.array_empty_tagged(0x300, b"I1C0")?, + unkptr_1c8: alloc.private.array_empty_tagged(0x1000, b"I1C8")?, + + buffer_mgr_ctl, + buffer_mgr_ctl_low_mapping: None, + buffer_mgr_ctl_high_mapping: None, + }) + }, + |inner, _ptr| { + try_init!(raw::RuntimePointers::ver { + pipes: Default::default(), + device_control: Default::default(), + event: Default::default(), + fw_log: Default::default(), + ktrace: Default::default(), + stats: Default::default(), + + stats_vtx: inner.stats.vtx.gpu_pointer(), + stats_frag: inner.stats.frag.gpu_pointer(), + stats_comp: inner.stats.comp.gpu_pointer(), + + hwdata_a: inner.hwdata_a.gpu_pointer(), + unkptr_190: inner.unkptr_190.gpu_pointer(), + unkptr_198: inner.unkptr_198.gpu_pointer(), + hwdata_b: inner.hwdata_b.gpu_pointer(), + hwdata_b_2: inner.hwdata_b.gpu_pointer(), + + fwlog_buf: None, + + unkptr_1b8: inner.unkptr_1b8.gpu_pointer(), + + #[ver(G < G14X)] + unkptr_1c0: inner.unkptr_1c0.gpu_pointer(), + #[ver(G < G14X)] + unkptr_1c8: inner.unkptr_1c8.gpu_pointer(), + + buffer_mgr_ctl_gpu_addr: U64(gpu::IOVA_KERN_GPU_BUFMGR_LOW), + buffer_mgr_ctl_fw_addr: U64(gpu::IOVA_KERN_GPU_BUFMGR_HIGH), + + __pad0: Default::default(), + unk_160: U64(0), + unk_168: U64(0), + unk_1d0: 0, + unk_1d4: 0, + unk_1d8: Default::default(), + + __pad1: Default::default(), + gpu_scratch: raw::RuntimeScratch::ver { + unk_6b38: 0xff, + ..Default::default() + }, + }) + }, + ) + } + + /// Create the FwStatus structure, which is used to coordinate the firmware halt state between + /// the firmware and the driver. + fn fw_status(&mut self) -> Result> { + self.alloc + .shared + .new_object(Default::default(), |_inner| Default::default()) + } + + /// Create one UatLevelInfo structure, which describes one level of translation for the UAT MMU. + fn uat_level_info( + cfg: &'static hw::HwConfig, + index_shift: usize, + num_entries: usize, + ) -> raw::UatLevelInfo { + raw::UatLevelInfo { + index_shift: index_shift as _, + unk_1: 14, + unk_2: 14, + unk_3: 8, + unk_4: 0x4000, + num_entries: num_entries as _, + unk_8: U64(1), + unk_10: U64(((1u64 << cfg.uat_oas) - 1) & !(mmu::UAT_PGMSK as u64)), + index_mask: U64(((num_entries - 1) << index_shift) as u64), + } + } + + /// Build the top-level InitData object. + #[inline(never)] + pub(crate) fn build(&mut self) -> Result>> { + let runtime_pointers = self.runtime_pointers()?; + let globals = self.globals()?; + let fw_status = self.fw_status()?; + let shared_ro = &mut self.alloc.shared_ro; + + let obj = self.alloc.private.new_init( + try_init!(InitData::ver { + unk_buf: shared_ro.array_empty_tagged(0x4000, b"IDTA")?, + runtime_pointers, + globals, + fw_status, + }), + |inner, _ptr| { + let cfg = &self.cfg; + try_init!(raw::InitData::ver { + #[ver(V == V13_5 && G != G14X)] + ver_info: Array::new([0x6ba0, 0x1f28, 0x601, 0xb0]), + #[ver(V == V13_5 && G == G14X)] + ver_info: Array::new([0xb390, 0x70f8, 0x601, 0xb0]), + unk_buf: inner.unk_buf.gpu_pointer(), + unk_8: 0, + unk_c: 0, + runtime_pointers: inner.runtime_pointers.gpu_pointer(), + globals: inner.globals.gpu_pointer(), + fw_status: inner.fw_status.gpu_pointer(), + uat_page_size: 0x4000, + uat_page_bits: 14, + uat_num_levels: 3, + uat_level_info: Array::new([ + Self::uat_level_info(cfg, 36, 8), + Self::uat_level_info(cfg, 25, 2048), + Self::uat_level_info(cfg, 14, 2048), + ]), + __pad0: Default::default(), + host_mapped_fw_allocations: 1, + unk_ac: 0, + unk_b0: 0, + unk_b4: 0, + unk_b8: 0, + }) + }, + )?; + Ok(KBox::new(obj, GFP_KERNEL)?) + } +} diff --git a/drivers/gpu/drm/asahi/mem.rs b/drivers/gpu/drm/asahi/mem.rs new file mode 100644 index 00000000000000..60a64e23a161c5 --- /dev/null +++ b/drivers/gpu/drm/asahi/mem.rs @@ -0,0 +1,144 @@ +// SPDX-License-Identifier: GPL-2.0-only OR MIT + +//! ARM64 low level memory operations. +//! +//! This GPU uses CPU-side `tlbi` outer-shareable instructions to manage its TLBs. +//! Yes, really. Even though the VA address spaces are unrelated. +//! +//! Right now we pick our own ASIDs and don't coordinate with the CPU. This might result +//! in needless TLB shootdowns on the CPU side... TODO: fix this. + +use core::arch::asm; +use core::cmp::min; + +use crate::debug::*; +use crate::mmu; + +type Asid = u8; + +/// Invalidate the entire GPU TLB. +#[inline(always)] +pub(crate) fn tlbi_all() { + // SAFETY: tlbi is always safe by definition + unsafe { + asm!(".arch armv8.4-a", "tlbi vmalle1os",); + } +} + +/// Invalidate all TLB entries for a given ASID. +#[inline(always)] +pub(crate) fn tlbi_asid(asid: Asid) { + if debug_enabled(DebugFlags::ConservativeTlbi) { + tlbi_all(); + sync(); + return; + } + + // SAFETY: tlbi is always safe by definition + unsafe { + asm!( + ".arch armv8.4-a", + "tlbi aside1os, {x}", + x = in(reg) ((asid as u64) << 48) + ); + } +} + +/// Invalidate a single page for a given ASID. +#[inline(always)] +pub(crate) fn tlbi_page(asid: Asid, va: usize) { + if debug_enabled(DebugFlags::ConservativeTlbi) { + tlbi_all(); + sync(); + return; + } + + let val: u64 = ((asid as u64) << 48) | ((va as u64 >> 12) & 0xffffffffffc); + // SAFETY: tlbi is always safe by definition + unsafe { + asm!( + ".arch armv8.4-a", + "tlbi vae1os, {x}", + x = in(reg) val + ); + } +} + +/// Invalidate a range of pages for a given ASID. +#[inline(always)] +pub(crate) fn tlbi_range(asid: Asid, va: usize, len: usize) { + if debug_enabled(DebugFlags::ConservativeTlbi) { + tlbi_all(); + sync(); + return; + } + + if len == 0 { + return; + } + + let start_pg = va >> mmu::UAT_PGBIT; + let end_pg = (va + len + mmu::UAT_PGMSK) >> mmu::UAT_PGBIT; + + let mut val: u64 = ((asid as u64) << 48) | (2 << 46) | (start_pg as u64 & 0x1fffffffff); + let pages = end_pg - start_pg; + + // Guess? It's possible that the page count is in terms of 4K pages + // when the CPU is in 4K mode... + #[cfg(CONFIG_ARM64_4K_PAGES)] + let pages = 4 * pages; + + if pages == 1 { + tlbi_page(asid, va); + return; + } + + // Page count is always in units of 2 + let num = ((pages + 1) >> 1) as u64; + // base: 5 bits + // exp: 2 bits + // pages = (base + 1) << (5 * exp + 1) + // 0:00000 -> 2 pages = 2 << 0 + // 0:11111 -> 32 * 2 pages = 2 << 5 + // 1:00000 -> 1 * 32 * 2 pages = 2 << 5 + // 1:11111 -> 32 * 32 * 2 pages = 2 << 10 + // 2:00000 -> 1 * 32 * 32 * 2 pages = 2 << 10 + // 2:11111 -> 32 * 32 * 32 * 2 pages = 2 << 15 + // 3:00000 -> 1 * 32 * 32 * 32 * 2 pages = 2 << 15 + // 3:11111 -> 32 * 32 * 32 * 32 * 2 pages = 2 << 20 + let exp = min(3, (64 - num.leading_zeros()) / 5); + let bits = 5 * exp; + let mut base = (num + (1 << bits) - 1) >> bits; + + val |= (exp as u64) << 44; + + while base > 32 { + // SAFETY: tlbi is always safe by definition + unsafe { + asm!( + ".arch armv8.4-a", + "tlbi rvae1os, {x}", + x = in(reg) val | (31 << 39) + ); + } + base -= 32; + } + + // SAFETY: tlbi is always safe by definition + unsafe { + asm!( + ".arch armv8.4-a", + "tlbi rvae1os, {x}", + x = in(reg) val | ((base - 1) << 39) + ); + } +} + +/// Issue a memory barrier (`dsb sy`). +#[inline(always)] +pub(crate) fn sync() { + // SAFETY: Barriers are always safe + unsafe { + asm!("dsb sy"); + } +} diff --git a/drivers/gpu/drm/asahi/microseq.rs b/drivers/gpu/drm/asahi/microseq.rs new file mode 100644 index 00000000000000..cbdb5de62e9218 --- /dev/null +++ b/drivers/gpu/drm/asahi/microseq.rs @@ -0,0 +1,63 @@ +// SPDX-License-Identifier: GPL-2.0-only OR MIT + +//! GPU Micro operation sequence builder +//! +//! As part of a single job submisssion to the GPU, the GPU firmware interprets a sequence of +//! commands that we call a "microsequence". These are responsible for setting up the job execution, +//! timestamping the process, waiting for completion, tearing up any resources, and signaling +//! completion to the driver via the event stamp mechanism. +//! +//! Although the microsequences used by the macOS driver are usually quite uniform and simple, the +//! firmware actually implements enough operations to make this interpreter Turing-complete (!). +//! Most of those aren't implemented yet, since we don't need them, but they could come in handy in +//! the future to do strange things or work around firmware bugs... +//! +//! This module simply implements a collection of microsequence operations that can be appended to +//! and later concatenated into one buffer, ready for firmware execution. + +use crate::fw::microseq; +pub(crate) use crate::fw::microseq::*; +use crate::fw::types::*; +use kernel::prelude::*; + +/// MicroSequence object type, which is just an opaque byte array. +pub(crate) type MicroSequence = GpuArray; + +/// MicroSequence builder. +pub(crate) struct Builder { + ops: KVec, +} + +impl Builder { + /// Create a new Builder object + pub(crate) fn new() -> Builder { + Builder { ops: KVec::new() } + } + + /// Get the relative offset from the current pointer to a given target offset. + /// + /// Used for relative jumps. + pub(crate) fn offset_to(&self, target: i32) -> i32 { + target - self.ops.len() as i32 + } + + /// Add an operation to the end of the sequence. + pub(crate) fn add(&mut self, op: T) -> Result { + let off = self.ops.len(); + let p: *const T = &op; + let p: *const u8 = p as *const u8; + // SAFETY: Microseq operations always have no padding bytes, so it is safe to + // access them as a byte slice. + let s: &[u8] = unsafe { core::slice::from_raw_parts(p, core::mem::size_of::()) }; + self.ops.extend_from_slice(s, GFP_KERNEL)?; + Ok(off as i32) + } + + /// Collect all submitted operations into a finalized GPU object. + pub(crate) fn build(self, alloc: &mut Allocator) -> Result { + let mut array = alloc.array_empty::(self.ops.len())?; + + array.as_mut_slice().clone_from_slice(self.ops.as_slice()); + Ok(array) + } +} diff --git a/drivers/gpu/drm/asahi/mmu.rs b/drivers/gpu/drm/asahi/mmu.rs new file mode 100644 index 00000000000000..871b20855a65bc --- /dev/null +++ b/drivers/gpu/drm/asahi/mmu.rs @@ -0,0 +1,1667 @@ +// SPDX-License-Identifier: GPL-2.0-only OR MIT + +//! GPU UAT (MMU) management +//! +//! AGX GPUs use an MMU called the UAT, which is largely compatible with the ARM64 page table +//! format. This module manages the global MMU structures, including a shared handoff structure +//! that is used to coordinate VM management operations with the firmware, the TTBAT which points +//! to currently active GPU VM contexts, as well as the individual `Vm` operations to map and +//! unmap buffer objects into a single user or kernel address space. +//! +//! The actual page table management is in the `pt` module. + +use core::fmt::Debug; +use core::mem::size_of; +use core::num::NonZeroUsize; +use core::ops::Range; +use core::sync::atomic::{ + fence, + AtomicU32, + AtomicU64, + AtomicU8, + Ordering, // +}; + +use kernel::{ + c_str, + device, + drm::{ + gem::shmem, + gpuvm, + mm, // + }, + error::Result, + io, + io_pgtable, + io_pgtable::{ + prot, + AppleUAT, + IoPageTable, // + }, + new_mutex, + prelude::*, + static_lock_class, + sync::{ + lock::{ + mutex::MutexBackend, + Guard, // + }, + Arc, Mutex, + }, + time::{ + delay::fsleep, + Delta, + Instant, + Monotonic, // + }, + types::ARef, // +}; + +use crate::debug::*; +use crate::module_parameters; +use crate::no_debug; +use crate::{ + driver, + fw, + gem, + hw, + mem, + slotalloc, + util::RangeExt, // +}; + +use pin_init; + +const DEBUG_CLASS: DebugFlags = DebugFlags::Mmu; + +/// PPL magic number for the handoff region +const PPL_MAGIC: u64 = 0x4b1d000000000002; + +/// Number of supported context entries in the TTBAT +const UAT_NUM_CTX: usize = 64; +/// First context available for users +const UAT_USER_CTX_START: usize = 1; +/// Number of available user contexts +const UAT_USER_CTX: usize = UAT_NUM_CTX - UAT_USER_CTX_START; + +/// Lower/user base VA +pub(crate) const IOVA_USER_BASE: u64 = UAT_PGSZ as u64; +/// Lower/user top VA +pub(crate) const IOVA_USER_TOP: u64 = 1 << (UAT_IAS as u64); +/// Lower/user VA range +pub(crate) const IOVA_USER_RANGE: Range = IOVA_USER_BASE..IOVA_USER_TOP; + +/// Upper/kernel base VA +// const IOVA_TTBR1_BASE: usize = 0xffffff8000000000; +/// Driver-managed kernel base VA +const IOVA_KERN_BASE: u64 = 0xffffffa000000000; +/// Driver-managed kernel top VA +const IOVA_KERN_TOP: u64 = 0xffffffb000000000; +/// Lower/user VA range +const IOVA_KERN_RANGE: Range = IOVA_KERN_BASE..IOVA_KERN_TOP; + +const TTBR_VALID: u64 = 0x1; // BIT(0) +const TTBR_ASID_SHIFT: usize = 48; + +/// Address of a special dummy page? +//const IOVA_UNK_PAGE: u64 = 0x6f_ffff8000; +pub(crate) const IOVA_UNK_PAGE: u64 = IOVA_USER_TOP - 2 * UAT_PGSZ as u64; +/// User VA range excluding the unk page +pub(crate) const IOVA_USER_USABLE_RANGE: Range = IOVA_USER_BASE..IOVA_UNK_PAGE; + +// KernelMapping protection types + +// Note: prot::CACHE means "cache coherency", which for UAT means *uncached*, +// since uncached mappings from the GFX ASC side are cache coherent with the AP cache. +// Not having that flag means *cached noncoherent*. + +/// Firmware MMIO R/W +pub(crate) const PROT_FW_MMIO_RW: u32 = + prot::PRIV | prot::READ | prot::WRITE | prot::CACHE | prot::MMIO; +/// Firmware MMIO R/O +pub(crate) const PROT_FW_MMIO_RO: u32 = prot::PRIV | prot::READ | prot::CACHE | prot::MMIO; +/// Firmware shared (uncached) RW +pub(crate) const PROT_FW_SHARED_RW: u32 = prot::PRIV | prot::READ | prot::WRITE | prot::CACHE; +/// Firmware shared (uncached) RO +pub(crate) const PROT_FW_SHARED_RO: u32 = prot::PRIV | prot::READ | prot::CACHE; +/// Firmware private (cached) RW +pub(crate) const PROT_FW_PRIV_RW: u32 = prot::PRIV | prot::READ | prot::WRITE; +/* +/// Firmware private (cached) RO +pub(crate) const PROT_FW_PRIV_RO: u32 = prot::PRIV | prot::READ; +*/ +/// Firmware/GPU shared (uncached) RW +pub(crate) const PROT_GPU_FW_SHARED_RW: u32 = prot::READ | prot::WRITE | prot::CACHE; +/// Firmware/GPU shared (private) RW +pub(crate) const PROT_GPU_FW_PRIV_RW: u32 = prot::READ | prot::WRITE; +/// Firmware-RW/GPU-RO shared (private) RW +pub(crate) const PROT_GPU_RO_FW_PRIV_RW: u32 = prot::PRIV | prot::WRITE; +/// GPU shared/coherent RW +pub(crate) const PROT_GPU_SHARED_RW: u32 = prot::READ | prot::WRITE | prot::CACHE | prot::NOEXEC; +/// GPU shared/coherent RO +pub(crate) const PROT_GPU_SHARED_RO: u32 = prot::READ | prot::CACHE | prot::NOEXEC; +/// GPU shared/coherent WO +pub(crate) const PROT_GPU_SHARED_WO: u32 = prot::WRITE | prot::CACHE | prot::NOEXEC; +/* +/// GPU private/noncoherent RW +pub(crate) const PROT_GPU_PRIV_RW: u32 = prot::READ | prot::WRITE | prot::NOEXEC; +/// GPU private/noncoherent RO +pub(crate) const PROT_GPU_PRIV_RO: u32 = prot::READ | prot::NOEXEC; +*/ + +type PhysAddr = bindings::phys_addr_t; + +/// A pre-allocated memory region for UAT management +struct UatRegion { + base: PhysAddr, + map: io::mem::Mem, +} + +/// SAFETY: It's safe to share UAT region records across threads. +unsafe impl Send for UatRegion {} +/// SAFETY: It's safe to share UAT region records across threads. +unsafe impl Sync for UatRegion {} + +/// Handoff region flush info structure +#[repr(C)] +struct FlushInfo { + state: AtomicU64, + addr: AtomicU64, + size: AtomicU64, +} + +/// UAT Handoff region layout +#[repr(C)] +struct Handoff { + magic_ap: AtomicU64, + magic_fw: AtomicU64, + + lock_ap: AtomicU8, + lock_fw: AtomicU8, + // Implicit padding: 2 bytes + turn: AtomicU32, + cur_slot: AtomicU32, + // Implicit padding: 4 bytes + flush: [FlushInfo; UAT_NUM_CTX + 1], + + unk2: AtomicU8, + // Implicit padding: 7 bytes + unk3: AtomicU64, +} + +const HANDOFF_SIZE: usize = size_of::(); + +/// One VM slot in the TTBAT +#[repr(C)] +struct SlotTTBS { + ttb0: AtomicU64, + ttb1: AtomicU64, +} + +const SLOTS_SIZE: usize = UAT_NUM_CTX * size_of::(); + +// We need at least page 0 (ttb0) +const PAGETABLES_SIZE: usize = UAT_PGSZ; + +/// Inner data for a Vm instance. This is reference-counted by the outer Vm object. +struct VmInner { + dev: driver::AsahiDevRef, + is_kernel: bool, + va_range: Range, + page_table: AppleUAT, + mm: mm::Allocator<(), KernelMappingInner>, + uat_inner: Arc, + binding: Arc>, + id: u64, +} + +/// Slot binding-related inner data for a Vm instance. +struct VmBinding { + active_users: usize, + binding: Option>, + bind_token: Option, + ttb: u64, +} + +/// Data associated with a VM <=> BO pairing +#[pin_data] +struct VmBo { + #[pin] + sgt: Mutex>>, +} + +impl gpuvm::DriverGpuVmBo for VmBo { + fn new() -> impl PinInit { + pin_init!(VmBo { + sgt <- new_mutex!(None, "VmBinding"), + }) + } +} + +#[derive(Default)] +struct StepContext { + new_va: Option>>>, + prev_va: Option>>>, + next_va: Option>>>, + vm_bo: Option>>, + prot: u32, +} + +impl gpuvm::DriverGpuVm for VmInner { + type Driver = driver::AsahiDriver; + type GpuVmBo = VmBo; + type StepContext = StepContext; + + fn step_map( + self: &mut gpuvm::UpdatingGpuVm<'_, Self>, + op: &mut gpuvm::OpMap, + ctx: &mut Self::StepContext, + ) -> Result { + let mut iova = op.addr(); + let mut left = op.range() as usize; + let mut offset = op.offset() as usize; + + let bo = ctx.vm_bo.as_ref().expect("step_map with no BO"); + + let guard = bo.inner().sgt.lock(); + for range in guard.as_ref().expect("step_map with no SGT").iter() { + // TODO: proper DMA address/length handling + let mut addr = range.dma_address() as usize; + let mut len: usize = range.dma_len() as usize; + + if left == 0 { + break; + } + + if offset > 0 { + let skip = len.min(offset); + addr += skip; + len -= skip; + offset -= skip; + } + + if len == 0 { + continue; + } + + assert!(offset == 0); + + len = len.min(left); + + mod_dev_dbg!( + self.dev, + "MMU: map: {:#x}:{:#x} -> {:#x}\n", + addr, + len, + iova + ); + + self.map_pages(iova, addr, UAT_PGSZ, len >> UAT_PGBIT, ctx.prot)?; + + left -= len; + iova += len as u64; + } + + let gpuva = ctx.new_va.take().expect("Multiple step_map calls"); + + if op + .map_and_link_va( + self, + gpuva, + ctx.vm_bo.as_ref().expect("step_map with no BO"), + ) + .is_err() + { + dev_err!( + self.dev.as_ref(), + "map_and_link_va failed: {:#x} [{:#x}] -> {:#x}\n", + op.offset(), + op.range(), + op.addr() + ); + return Err(EINVAL); + } + Ok(()) + } + fn step_unmap( + self: &mut gpuvm::UpdatingGpuVm<'_, Self>, + op: &mut gpuvm::OpUnMap, + _ctx: &mut Self::StepContext, + ) -> Result { + let va = op.va().expect("step_unmap: missing VA"); + + mod_dev_dbg!(self.dev, "MMU: unmap: {:#x}:{:#x}\n", va.addr(), va.range()); + + self.unmap_pages(va.addr(), UAT_PGSZ, (va.range() >> UAT_PGBIT) as usize)?; + + if let Some(asid) = self.slot() { + fence(Ordering::SeqCst); + mem::tlbi_range(asid as u8, va.addr() as usize, va.range() as usize); + mod_dev_dbg!( + self.dev, + "MMU: flush range: asid={:#x} start={:#x} len={:#x}\n", + asid, + va.addr(), + va.range(), + ); + mem::sync(); + } + + if op.unmap_and_unlink_va().is_none() { + dev_err!(self.dev.as_ref(), "step_unmap: could not unlink gpuva"); + } + Ok(()) + } + fn step_remap( + self: &mut gpuvm::UpdatingGpuVm<'_, Self>, + op: &mut gpuvm::OpReMap, + vm_bo: &gpuvm::GpuVmBo, + ctx: &mut Self::StepContext, + ) -> Result { + let va = op.unmap().va().expect("No previous VA"); + let orig_addr = va.addr(); + let orig_range = va.range(); + + // Only unmap the hole between prev/next, if they exist + let unmap_start = if let Some(op) = op.prev_map() { + op.addr() + op.range() + } else { + orig_addr + }; + + let unmap_end = if let Some(op) = op.next_map() { + op.addr() + } else { + orig_addr + orig_range + }; + + let unmap_range = unmap_end - unmap_start; + + mod_dev_dbg!( + self.dev, + "MMU: unmap for remap: {:#x}:{:#x} (from {:#x}:{:#x})\n", + unmap_start, + unmap_range, + orig_addr, + orig_range + ); + + self.unmap_pages(unmap_start, UAT_PGSZ, (unmap_range >> UAT_PGBIT) as usize)?; + + if let Some(asid) = self.slot() { + fence(Ordering::SeqCst); + mem::tlbi_range(asid as u8, unmap_start as usize, unmap_range as usize); + mod_dev_dbg!( + self.dev, + "MMU: flush range: asid={:#x} start={:#x} len={:#x}\n", + asid, + unmap_start, + unmap_range, + ); + mem::sync(); + } + + if op.unmap().unmap_and_unlink_va().is_none() { + dev_err!(self.dev.as_ref(), "step_unmap: could not unlink gpuva"); + } + + if let Some(prev_op) = op.prev_map() { + let prev_gpuva = ctx + .prev_va + .take() + .expect("Multiple step_remap calls with prev_op"); + if prev_op.map_and_link_va(self, prev_gpuva, vm_bo).is_err() { + dev_err!(self.dev.as_ref(), "step_remap: could not relink prev gpuva"); + return Err(EINVAL); + } + } + + if let Some(next_op) = op.next_map() { + let next_gpuva = ctx + .next_va + .take() + .expect("Multiple step_remap calls with next_op"); + if next_op.map_and_link_va(self, next_gpuva, vm_bo).is_err() { + dev_err!(self.dev.as_ref(), "step_remap: could not relink next gpuva"); + return Err(EINVAL); + } + } + + Ok(()) + } +} + +impl VmInner { + /// Returns the slot index, if this VM is bound. + fn slot(&self) -> Option { + if self.is_kernel { + // The GFX ASC does not care about the ASID. Pick an arbitrary one. + // TODO: This needs to be a persistently reserved ASID once we integrate + // with the ARM64 kernel ASID machinery to avoid overlap. + Some(0) + } else { + // We don't check whether we lost the slot, which could cause unnecessary + // invalidations against another Vm. However, this situation should be very + // rare (e.g. a Vm lost its slot, which means 63 other Vms bound in the + // interim, and then it gets killed / drops its mappings without doing any + // final rendering). Anything doing active maps/unmaps is probably also + // rendering and therefore likely bound. + self.binding + .lock() + .bind_token + .as_ref() + .map(|token| token.last_slot() + UAT_USER_CTX_START as u32) + } + } + + /// Returns the translation table base for this Vm + fn ttb(&self) -> u64 { + self.page_table.cfg().ttbr + } + + /// Map an IOVA to the shifted address the underlying io_pgtable uses. + fn map_iova(&self, iova: u64, size: usize) -> Result { + if !self.va_range.is_superset(iova..(iova + size as u64)) { + Err(EINVAL) + } else if self.is_kernel { + Ok(iova - self.va_range.start) + } else { + Ok(iova) + } + } + + /// Map a contiguous range of virtual->physical pages. + fn map_pages( + &mut self, + mut iova: u64, + mut paddr: usize, + pgsize: usize, + pgcount: usize, + prot: u32, + ) -> Result { + let mut left = pgcount; + while left > 0 { + let mapped_iova = self.map_iova(iova, pgsize * left)?; + let mapped = + self.page_table + .map_pages(mapped_iova as usize, paddr, pgsize, left, prot)?; + assert!(mapped <= left * pgsize); + + left -= mapped / pgsize; + paddr += mapped; + iova += mapped as u64; + } + Ok(pgcount * pgsize) + } + + /// Unmap a contiguous range of pages. + fn unmap_pages(&mut self, mut iova: u64, pgsize: usize, pgcount: usize) -> Result { + let mut left = pgcount; + while left > 0 { + let mapped_iova = self.map_iova(iova, pgsize * left)?; + let mut unmapped = self + .page_table + .unmap_pages(mapped_iova as usize, pgsize, left); + if unmapped == 0 { + dev_err!( + self.dev.as_ref(), + "unmap_pages {:#x}:{:#x} returned 0\n", + mapped_iova, + left + ); + unmapped = pgsize; // Pretend we unmapped one page and try again... + } + assert!(unmapped <= left * pgsize); + + left -= unmapped / pgsize; + iova += unmapped as u64; + } + + Ok(pgcount * pgsize) + } + + /// Map an `mm::Node` representing an mapping in VA space. + fn map_node(&mut self, node: &mm::Node<(), KernelMappingInner>, prot: u32) -> Result { + let mut iova = node.start(); + let guard = node.bo.as_ref().ok_or(EINVAL)?.inner().sgt.lock(); + let sgt = guard.as_ref().ok_or(EINVAL)?; + let mut offset = node.offset; + + for range in unsafe { sgt.iter_raw() } { + // TODO: proper DMA address/length handling + let mut addr = range.dma_address() as usize; + let mut len: usize = range.dma_len() as usize; + + if (offset | addr | len | iova as usize) & UAT_PGMSK != 0 { + dev_err!( + self.dev.as_ref(), + "MMU: KernelMapping {:#x}:{:#x} -> {:#x} is not page-aligned\n", + addr, + len, + iova + ); + return Err(EINVAL); + } + + if offset > 0 { + let skip = len.min(offset); + addr += skip; + len -= skip; + offset -= skip; + } + + if len == 0 { + continue; + } + + mod_dev_dbg!( + self.dev, + "MMU: map: {:#x}:{:#x} -> {:#x}\n", + addr, + len, + iova + ); + + self.map_pages(iova, addr, UAT_PGSZ, len >> UAT_PGBIT, prot)?; + + iova += len as u64; + } + Ok(()) + } +} + +/// Shared reference to a virtual memory address space ([`Vm`]). +#[derive(Clone)] +pub(crate) struct Vm { + id: u64, + inner: ARef>, + dummy_obj: ARef, + binding: Arc>, +} +no_debug!(Vm); + +/// Slot data for a [`Vm`] slot (nothing, we only care about the indices). +pub(crate) struct SlotInner(); + +impl slotalloc::SlotItem for SlotInner { + type Data = (); +} + +/// Represents a single user of a binding of a [`Vm`] to a slot. +/// +/// The number of users is counted, and the slot will be freed when it drops to 0. +#[derive(Debug)] +pub(crate) struct VmBind(Vm, u32); + +impl VmBind { + /// Returns the slot that this `Vm` is bound to. + pub(crate) fn slot(&self) -> u32 { + self.1 + } +} + +impl Drop for VmBind { + fn drop(&mut self) { + let mut binding = self.0.binding.lock(); + + assert_ne!(binding.active_users, 0); + binding.active_users -= 1; + mod_pr_debug!( + "MMU: slot {} active users {}\n", + self.1, + binding.active_users + ); + if binding.active_users == 0 { + binding.binding = None; + } + } +} + +impl Clone for VmBind { + fn clone(&self) -> VmBind { + let mut binding = self.0.binding.lock(); + + binding.active_users += 1; + mod_pr_debug!( + "MMU: slot {} active users {}\n", + self.1, + binding.active_users + ); + VmBind(self.0.clone(), self.1) + } +} + +/// Inner data required for an object mapping into a [`Vm`]. +pub(crate) struct KernelMappingInner { + // Drop order matters: + // - Drop the GpuVmBo first, which resv locks its BO and drops a GpuVm reference + // - Drop the GEM BO next, since BO free can take the resv lock itself + // - Drop the owner GpuVm last, since that again can take resv locks when the refcount drops to 0 + bo: Option>>, + _gem: Option>, + owner: ARef>, + uat_inner: Arc, + prot: u32, + offset: usize, + mapped_size: usize, +} + +/// An object mapping into a [`Vm`], which reserves the address range from use by other mappings. +pub(crate) struct KernelMapping(mm::Node<(), KernelMappingInner>); + +impl KernelMapping { + /// Returns the IOVA base of this mapping + pub(crate) fn iova(&self) -> u64 { + self.0.start() + } + + /// Returns the size of this mapping in bytes + pub(crate) fn size(&self) -> usize { + self.0.mapped_size + } + + /// Remap a cached mapping as uncached, then synchronously flush that range of VAs from the + /// coprocessor cache. This is required to safely unmap cached/private mappings. + fn remap_uncached_and_flush(&mut self) { + let mut owner = self + .0 + .owner + .exec_lock(None) + .expect("Failed to exec_lock in remap_uncached_and_flush"); + + mod_dev_dbg!( + owner.dev, + "MMU: remap as uncached {:#x}:{:#x}\n", + self.iova(), + self.size() + ); + + // The IOMMU API does not allow us to remap things in-place... + // just do an unmap and map again for now. + // Do not try to unmap guard page (-1) + if owner + .unmap_pages(self.iova(), UAT_PGSZ, self.size() >> UAT_PGBIT) + .is_err() + { + dev_err!( + owner.dev.as_ref(), + "MMU: unmap for remap {:#x}:{:#x} failed\n", + self.iova(), + self.size() + ); + } + + let prot = self.0.prot | prot::CACHE; + if owner.map_node(&self.0, prot).is_err() { + dev_err!( + owner.dev.as_ref(), + "MMU: remap {:#x}:{:#x} failed\n", + self.iova(), + self.size() + ); + } + fence(Ordering::SeqCst); + + // If we don't have (and have never had) a VM slot, just return + let slot = match owner.slot() { + None => return, + Some(slot) => slot, + }; + + let flush_slot = if owner.is_kernel { + // If this is a kernel mapping, always flush on index 64 + UAT_NUM_CTX as u32 + } else { + // Otherwise, check if this slot is the active one, otherwise return + // Also check that we actually own this slot + let ttb = owner.ttb() | TTBR_VALID | (slot as u64) << TTBR_ASID_SHIFT; + + let uat_inner = self.0.uat_inner.lock(); + uat_inner.handoff().lock(); + let cur_slot = uat_inner.handoff().current_slot(); + let ttb_cur = uat_inner.ttbs()[slot as usize].ttb0.load(Ordering::Relaxed); + uat_inner.handoff().unlock(); + if cur_slot == Some(slot) && ttb_cur == ttb { + slot + } else { + return; + } + }; + + // FIXME: There is a race here, though it'll probably never happen in practice. + // In theory, it's possible for the ASC to finish using our slot, whatever command + // it was processing to complete, the slot to be lost to another context, and the ASC + // to begin using it again with a different page table, thus faulting when it gets a + // flush request here. In practice, the chance of this happening is probably vanishingly + // small, as all 62 other slots would have to be recycled or in use before that slot can + // be reused, and the ASC using user contexts at all is very rare. + + // Still, the locking around UAT/Handoff/TTBs should probably be redesigned to better + // model the interactions with the firmware and avoid these races. + // Possibly TTB changes should be tied to slot locks: + + // Flush: + // - Can early check handoff here (no need to lock). + // If user slot and it doesn't match the active ASC slot, + // we can elide the flush as the ASC guarantees it flushes + // TLBs/caches when it switches context. We just need a + // barrier to ensure ordering. + // - Lock TTB slot + // - If user ctx: + // - Lock handoff AP-side + // - Lock handoff dekker + // - Check TTB & handoff cur ctx + // - Perform flush if necessary + // - This implies taking the fwring lock + // + // TTB change: + // - lock TTB slot + // - lock handoff AP-side + // - lock handoff dekker + // change TTB + + // Lock this flush slot, and write the range to it + let flush = self.0.uat_inner.lock_flush(flush_slot); + let pages = self.size() >> UAT_PGBIT; + flush.begin_flush(self.iova(), self.size() as u64); + if pages >= 0x10000 { + dev_err!( + owner.dev.as_ref(), + "MMU: Flush too big ({:#x} pages))\n", + pages + ); + } + + let cmd = fw::channels::FwCtlMsg { + addr: fw::types::U64(self.iova()), + unk_8: 0, + slot: flush_slot, + page_count: pages as u16, + unk_12: 2, // ? + }; + + // Tell the firmware to do a cache flush + if let Err(e) = (*owner.dev).gpu.fwctl(cmd) { + dev_err!( + owner.dev.as_ref(), + "MMU: ASC cache flush {:#x}:{:#x} failed (err: {:?})\n", + self.iova(), + self.size(), + e + ); + } + + // Finish the flush + flush.end_flush(); + + // Slot is unlocked here + } +} +no_debug!(KernelMapping); + +impl Drop for KernelMapping { + fn drop(&mut self) { + // This is the main unmap function for UAT mappings. + // The sequence of operations here is finicky, due to the interaction + // between cached GFX ASC mappings and the page tables. These mappings + // always have to be flushed from the cache before being unmapped. + + // For uncached mappings, just unmapping and flushing the TLB is sufficient. + + // For cached mappings, this is the required sequence: + // 1. Remap it as uncached + // 2. Flush the TLB range + // 3. If kernel VA mapping OR user VA mapping and handoff.current_slot() == slot: + // a. Take a lock for this slot + // b. Write the flush range to the right context slot in handoff area + // c. Issue a cache invalidation request via FwCtl queue + // d. Poll for completion via queue + // e. Check for completion flag in the handoff area + // f. Drop the lock + // 4. Unmap + // 5. Flush the TLB range again + + // prot::CACHE means "cache coherent" which means *uncached* here. + if self.0.prot & prot::CACHE == 0 { + self.remap_uncached_and_flush(); + } + + let mut owner = self + .0 + .owner + .exec_lock(None) + .expect("exec_lock failed in KernelMapping::drop"); + mod_dev_dbg!( + owner.dev, + "MMU: unmap {:#x}:{:#x}\n", + self.iova(), + self.size() + ); + + if owner + .unmap_pages(self.iova(), UAT_PGSZ, self.size() >> UAT_PGBIT) + .is_err() + { + dev_err!( + owner.dev.as_ref(), + "MMU: unmap {:#x}:{:#x} failed\n", + self.iova(), + self.size() + ); + } + + if let Some(asid) = owner.slot() { + fence(Ordering::SeqCst); + mem::tlbi_range(asid as u8, self.iova() as usize, self.size()); + mod_dev_dbg!( + owner.dev, + "MMU: flush range: asid={:#x} start={:#x} len={:#x}\n", + asid, + self.iova(), + self.size() + ); + mem::sync(); + } + } +} + +/// Shared UAT global data structures +struct UatShared { + kernel_ttb1: u64, + map_kernel_to_user: bool, + handoff_rgn: UatRegion, + ttbs_rgn: UatRegion, +} + +impl UatShared { + /// Returns the handoff region area + fn handoff(&self) -> &Handoff { + // SAFETY: pointer is non-null per the type invariant + unsafe { (self.handoff_rgn.map.ptr() as *mut Handoff).as_ref() }.unwrap() + } + + /// Returns the TTBAT area + fn ttbs(&self) -> &[SlotTTBS; UAT_NUM_CTX] { + // SAFETY: pointer is non-null per the type invariant + unsafe { (self.ttbs_rgn.map.ptr() as *mut [SlotTTBS; UAT_NUM_CTX]).as_ref() }.unwrap() + } +} + +// SAFETY: Nothing here is unsafe to send across threads. +unsafe impl Send for UatShared {} + +/// Inner data for the top-level UAT instance. +#[pin_data] +struct UatInner { + #[pin] + shared: Mutex, + #[pin] + handoff_flush: [Mutex; UAT_NUM_CTX + 1], +} + +impl UatInner { + /// Take the lock on the shared data and return the guard. + fn lock(&self) -> Guard<'_, UatShared, MutexBackend> { + self.shared.lock() + } + + /// Take a lock on a handoff flush slot and return the guard. + fn lock_flush(&self, slot: u32) -> Guard<'_, HandoffFlush, MutexBackend> { + self.handoff_flush[slot as usize].lock() + } +} + +/// Top-level UAT manager object +pub(crate) struct Uat { + dev: driver::AsahiDevRef, + cfg: &'static hw::HwConfig, + pagetables_rgn: UatRegion, + + inner: Arc, + slots: slotalloc::SlotAllocator, + + kernel_vm: Vm, + kernel_lower_vm: Vm, +} + +impl Handoff { + /// Lock the handoff region from firmware access + fn lock(&self) { + self.lock_ap.store(1, Ordering::Relaxed); + fence(Ordering::SeqCst); + + while self.lock_fw.load(Ordering::Relaxed) != 0 { + if self.turn.load(Ordering::Relaxed) != 0 { + self.lock_ap.store(0, Ordering::Relaxed); + while self.turn.load(Ordering::Relaxed) != 0 {} + self.lock_ap.store(1, Ordering::Relaxed); + fence(Ordering::SeqCst); + } + } + fence(Ordering::Acquire); + } + + /// Unlock the handoff region, allowing firmware access + fn unlock(&self) { + self.turn.store(1, Ordering::Relaxed); + self.lock_ap.store(0, Ordering::Release); + } + + /// Returns the current Vm slot mapped by the firmware for lower/unprivileged access, if any. + fn current_slot(&self) -> Option { + let slot = self.cur_slot.load(Ordering::Relaxed); + if slot == 0 || slot == u32::MAX { + None + } else { + Some(slot) + } + } + + /// Initialize the handoff region + fn init(&self) -> Result { + self.magic_ap.store(PPL_MAGIC, Ordering::Relaxed); + self.cur_slot.store(0, Ordering::Relaxed); + self.unk3.store(0, Ordering::Relaxed); + fence(Ordering::SeqCst); + + let start = Instant::::now(); + const TIMEOUT: Delta = Delta::from_millis(1000); + + self.lock(); + while start.elapsed() < TIMEOUT { + if self.magic_fw.load(Ordering::Relaxed) == PPL_MAGIC { + break; + } else { + self.unlock(); + fsleep(Delta::from_millis(10)); + self.lock(); + } + } + + if self.magic_fw.load(Ordering::Relaxed) != PPL_MAGIC { + self.unlock(); + pr_err!("Handoff: Failed to initialize (firmware not running?)\n"); + return Err(EIO); + } + + self.unlock(); + + for i in 0..=UAT_NUM_CTX { + self.flush[i].state.store(0, Ordering::Relaxed); + self.flush[i].addr.store(0, Ordering::Relaxed); + self.flush[i].size.store(0, Ordering::Relaxed); + } + fence(Ordering::SeqCst); + Ok(()) + } +} + +/// Represents a single flush info slot in the handoff region. +/// +/// # Invariants +/// The pointer is valid and there is no aliasing HandoffFlush instance. +struct HandoffFlush(*const FlushInfo); + +// SAFETY: These pointers are safe to send across threads. +unsafe impl Send for HandoffFlush {} + +impl HandoffFlush { + /// Set up a flush operation for the coprocessor + fn begin_flush(&self, start: u64, size: u64) { + // SAFETY: Per the type invariant, this is safe + let flush = unsafe { self.0.as_ref().unwrap() }; + + let state = flush.state.load(Ordering::Relaxed); + if state != 0 { + pr_err!("Handoff: expected flush state 0, got {}\n", state); + } + flush.addr.store(start, Ordering::Relaxed); + flush.size.store(size, Ordering::Relaxed); + flush.state.store(1, Ordering::Relaxed); + } + + /// Complete a flush operation for the coprocessor + fn end_flush(&self) { + // SAFETY: Per the type invariant, this is safe + let flush = unsafe { self.0.as_ref().unwrap() }; + let state = flush.state.load(Ordering::Relaxed); + if state != 2 { + pr_err!("Handoff: expected flush state 2, got {}\n", state); + } + flush.state.store(0, Ordering::Relaxed); + } +} + +// We do not implement FlushOps, since we flush manually in this module after +// page table operations. Just provide dummy implementations. +impl io_pgtable::FlushOps for Uat { + type Data = (); + + fn tlb_flush_all(_data: ::Borrowed<'_>) {} + fn tlb_flush_walk( + _data: ::Borrowed<'_>, + _iova: usize, + _size: usize, + _granule: usize, + ) { + } + fn tlb_add_page( + _data: ::Borrowed<'_>, + _iova: usize, + _granule: usize, + ) { + } +} + +impl Vm { + /// Create a new virtual memory address space + fn new( + dev: &driver::AsahiDevice, + uat_inner: Arc, + kernel_range: Range, + cfg: &'static hw::HwConfig, + is_kernel: bool, + id: u64, + ) -> Result { + let dummy_obj = gem::new_kernel_object(dev, 0x4000)?; + + let page_table = AppleUAT::new( + dev.as_ref(), + io_pgtable::Config { + pgsize_bitmap: UAT_PGSZ, + ias: if is_kernel { UAT_IAS_KERN } else { UAT_IAS }, + oas: cfg.uat_oas, + coherent_walk: true, + quirks: 0, + }, + (), + )?; + let (va_range, gpuvm_range) = if is_kernel { + (IOVA_KERN_RANGE, kernel_range.clone()) + } else { + (IOVA_USER_RANGE, IOVA_USER_USABLE_RANGE) + }; + + let mm = mm::Allocator::new(va_range.start, va_range.range(), ())?; + + let binding = Arc::pin_init( + new_mutex!( + VmBinding { + binding: None, + bind_token: None, + active_users: 0, + ttb: page_table.cfg().ttbr, + }, + "VmBinding", + ), + GFP_KERNEL, + )?; + + let binding_clone = binding.clone(); + Ok(Vm { + id, + dummy_obj: dummy_obj.gem.clone(), + inner: gpuvm::GpuVm::new( + c_str!("Asahi::GpuVm"), + dev, + dummy_obj.gem.clone(), + gpuvm_range, + kernel_range, + init!(VmInner { + dev: dev.into(), + va_range, + is_kernel, + page_table, + mm, + uat_inner, + binding: binding_clone, + id, + }), + )?, + binding, + }) + } + + /// Get the translation table base for this Vm + fn ttb(&self) -> u64 { + self.binding.lock().ttb + } + + /// Map a GEM object (using its `SGTable`) into this Vm at a free address in a given range. + #[allow(clippy::too_many_arguments)] + pub(crate) fn map_in_range( + &self, + gem: &gem::Object, + object_range: Range, + alignment: u64, + range: Range, + prot: u32, + guard: bool, + ) -> Result { + let size = object_range.range(); + let sgt = gem.owned_sg_table()?; + let mut inner = self.inner.exec_lock(Some(gem))?; + let vm_bo = inner.obtain_bo()?; + + let mut vm_bo_guard = vm_bo.inner().sgt.lock(); + if vm_bo_guard.is_none() { + vm_bo_guard.replace(sgt); + } + core::mem::drop(vm_bo_guard); + + let uat_inner = inner.uat_inner.clone(); + let node = inner.mm.insert_node_in_range( + KernelMappingInner { + owner: self.inner.clone(), + uat_inner, + prot, + bo: Some(vm_bo), + _gem: Some(gem.into()), + offset: object_range.start, + mapped_size: size, + }, + (size + if guard { UAT_PGSZ } else { 0 }) as u64, // Add guard page + alignment, + 0, + range.start, + range.end, + mm::InsertMode::Best, + )?; + + inner.map_node(&node, prot)?; + Ok(KernelMapping(node)) + } + + /// Map a GEM object into this Vm at a specific address. + #[allow(clippy::too_many_arguments)] + pub(crate) fn map_at( + &self, + addr: u64, + size: usize, + gem: ARef, + prot: u32, + guard: bool, + ) -> Result { + let sgt = gem.owned_sg_table()?; + let mut inner = self.inner.exec_lock(Some(&gem))?; + + let vm_bo = inner.obtain_bo()?; + + let mut vm_bo_guard = vm_bo.inner().sgt.lock(); + if vm_bo_guard.is_none() { + vm_bo_guard.replace(sgt); + } + core::mem::drop(vm_bo_guard); + + let uat_inner = inner.uat_inner.clone(); + let node = inner.mm.reserve_node( + KernelMappingInner { + owner: self.inner.clone(), + uat_inner, + prot, + bo: Some(vm_bo), + _gem: Some(gem.clone()), + offset: 0, + mapped_size: size, + }, + addr, + (size + if guard { UAT_PGSZ } else { 0 }) as u64, // Add guard page + 0, + )?; + + inner.map_node(&node, prot)?; + Ok(KernelMapping(node)) + } + + /// Map a range of a GEM object into this Vm using GPUVM. + #[allow(clippy::too_many_arguments)] + pub(crate) fn bind_object( + &self, + gem: &gem::Object, + addr: u64, + size: u64, + offset: u64, + prot: u32, + ) -> Result { + // Mapping needs a complete context + let mut ctx = StepContext { + new_va: Some(gpuvm::GpuVa::::new(pin_init::default())?), + prev_va: Some(gpuvm::GpuVa::::new(pin_init::default())?), + next_va: Some(gpuvm::GpuVa::::new(pin_init::default())?), + prot, + ..Default::default() + }; + + let sgt = gem.owned_sg_table()?; + let mut inner = self.inner.exec_lock(Some(gem))?; + + // Preallocate the page tables, to fail early if we ENOMEM + inner.page_table.alloc_pages(addr..(addr + size))?; + + let vm_bo = inner.obtain_bo()?; + + let mut vm_bo_guard = vm_bo.inner().sgt.lock(); + if vm_bo_guard.is_none() { + vm_bo_guard.replace(sgt); + } + core::mem::drop(vm_bo_guard); + + ctx.vm_bo = Some(vm_bo); + + if (addr | size | offset) & (UAT_PGMSK as u64) != 0 { + dev_err!( + inner.dev.as_ref(), + "MMU: Map step {:#x} [{:#x}] -> {:#x} is not page-aligned\n", + offset, + size, + addr + ); + return Err(EINVAL); + } + + mod_dev_dbg!( + inner.dev, + "MMU: sm_map: {:#x} [{:#x}] -> {:#x}\n", + offset, + size, + addr + ); + inner.sm_map(&mut ctx, addr, size, offset) + } + + /// Add a direct MMIO mapping to this Vm at a free address. + pub(crate) fn map_io( + &self, + iova: u64, + phys: usize, + size: usize, + prot: u32, + ) -> Result { + let mut inner = self.inner.exec_lock(None)?; + + if (iova as usize | phys | size) & UAT_PGMSK != 0 { + dev_err!( + inner.dev.as_ref(), + "MMU: KernelMapping {:#x}:{:#x} -> {:#x} is not page-aligned\n", + phys, + size, + iova + ); + return Err(EINVAL); + } + + dev_info!( + inner.dev.as_ref(), + "MMU: IO map: {:#x}:{:#x} -> {:#x}\n", + phys, + size, + iova + ); + + let uat_inner = inner.uat_inner.clone(); + let node = inner.mm.reserve_node( + KernelMappingInner { + owner: self.inner.clone(), + uat_inner, + prot, + bo: None, + _gem: None, + offset: 0, + mapped_size: size, + }, + iova, + size as u64, + 0, + )?; + + inner.map_pages(iova, phys, UAT_PGSZ, size >> UAT_PGBIT, prot)?; + + Ok(KernelMapping(node)) + } + + /// Unmap everything in an address range. + pub(crate) fn unmap_range(&self, iova: u64, size: u64) -> Result { + // Unmapping a range can only do a single split, so just preallocate + // the prev and next GpuVas + let mut ctx = StepContext { + prev_va: Some(gpuvm::GpuVa::::new(pin_init::default())?), + next_va: Some(gpuvm::GpuVa::::new(pin_init::default())?), + ..Default::default() + }; + + let mut inner = self.inner.exec_lock(None)?; + + mod_dev_dbg!(inner.dev, "MMU: sm_unmap: {:#x}:{:#x}\n", iova, size); + inner.sm_unmap(&mut ctx, iova, size) + } + + /// Drop mappings for a given bo. + pub(crate) fn drop_mappings(&self, gem: &gem::Object) -> Result { + // Removing whole mappings only does unmaps, so no preallocated VAs + let mut ctx = Default::default(); + + let mut inner = self.inner.exec_lock(Some(gem))?; + + if let Some(bo) = inner.find_bo() { + mod_dev_dbg!(inner.dev, "MMU: bo_unmap\n"); + inner.bo_unmap(&mut ctx, &bo)?; + mod_dev_dbg!(inner.dev, "MMU: bo_unmap done\n"); + // We need to drop the exec_lock first, then the GpuVmBo since that will take the lock itself. + core::mem::drop(inner); + core::mem::drop(bo); + } + + Ok(()) + } + + /// Returns the dummy GEM object used to hold the shared DMA reservation locks + pub(crate) fn get_resv_obj(&self) -> ARef { + self.dummy_obj.clone() + } + + /// Check whether an object is external to this GpuVm + pub(crate) fn is_extobj(&self, gem: &gem::Object) -> bool { + self.inner.is_extobj(gem) + } +} + +impl Drop for VmInner { + fn drop(&mut self) { + let mut binding = self.binding.lock(); + assert_eq!(binding.active_users, 0); + + mod_pr_debug!( + "VmInner::Drop [{}]: bind_token={:?}\n", + self.id, + binding.bind_token + ); + + // Make sure this VM is not mapped to a TTB if it was + if let Some(token) = binding.bind_token.take() { + let idx = (token.last_slot() as usize) + UAT_USER_CTX_START; + let ttb = self.ttb() | TTBR_VALID | (idx as u64) << TTBR_ASID_SHIFT; + + let uat_inner = self.uat_inner.lock(); + uat_inner.handoff().lock(); + let handoff_cur = uat_inner.handoff().current_slot(); + let ttb_cur = uat_inner.ttbs()[idx].ttb0.load(Ordering::SeqCst); + let inval = ttb_cur == ttb; + if inval { + if handoff_cur == Some(idx as u32) { + pr_err!( + "VmInner::drop owning slot {}, but it is currently in use by the ASC?\n", + idx + ); + } + uat_inner.ttbs()[idx].ttb0.store(0, Ordering::SeqCst); + uat_inner.ttbs()[idx].ttb1.store(0, Ordering::SeqCst); + } + uat_inner.handoff().unlock(); + core::mem::drop(uat_inner); + + // In principle we dropped all the KernelMappings already, but we might as + // well play it safe and invalidate the whole ASID. + if inval { + mod_pr_debug!( + "VmInner::Drop [{}]: need inval for ASID {:#x}\n", + self.id, + idx + ); + mem::tlbi_asid(idx as u8); + mem::sync(); + } + } + } +} + +impl Uat { + /// Map a bootloader-preallocated memory region + fn map_region( + dev: &device::Device, + name: &CStr, + size: usize, + cached: bool, + ) -> Result { + let of_node = dev.of_node().ok_or(EINVAL)?; + let res = of_node.reserved_mem_region_to_resource_byname(name)?; + let base = res.start(); + let res_size = res.size().try_into()?; + + if size > res_size { + dev_err!( + dev, + "Region {} is too small (expected {}, got {})\n", + name, + size, + res_size + ); + return Err(ENOMEM); + } + + let flags = if cached { + io::mem::MemFlags::WB + } else { + io::mem::MemFlags::WC + }; + + // SAFETY: The safety of this operation hinges on the correctness of + // much of this file and also the `pgtable` module, so it is difficult + // to prove in a single safety comment. Such is life with raw GPU + // page table management... + let map = unsafe { io::mem::Mem::try_new(res, flags) }.inspect_err(|_| { + dev_err!(dev, "Failed to remap {} mem resource\n", name); + })?; + + Ok(UatRegion { base, map }) + } + + /// Returns a view into the root kernel (upper half) page table + fn kpt0(&self) -> &[Pte; UAT_NPTE] { + // SAFETY: pointer is non-null per the type invariant + unsafe { (self.pagetables_rgn.map.as_ptr() as *mut [Pte; UAT_NPTE]).as_ref() }.unwrap() + } + + /// Returns a reference to the global kernel (upper half) `Vm` + pub(crate) fn kernel_vm(&self) -> &Vm { + &self.kernel_vm + } + + /// Returns a reference to the local kernel (lower half) `Vm` + pub(crate) fn kernel_lower_vm(&self) -> &Vm { + &self.kernel_lower_vm + } + + /// Returns the base physical address of the TTBAT region. + pub(crate) fn ttb_base(&self) -> u64 { + let inner = self.inner.lock(); + + inner.ttbs_rgn.base + } + + /// Binds a `Vm` to a slot, preferring the last used one. + pub(crate) fn bind(&self, vm: &Vm) -> Result { + let mut binding = vm.binding.lock(); + + if binding.binding.is_none() { + assert_eq!(binding.active_users, 0); + + let isolation = *module_parameters::robust_isolation.value() != 0; + + self.slots.set_limit(if isolation { + NonZeroUsize::new(1) + } else { + None + }); + + let slot = self.slots.get(binding.bind_token)?; + if slot.changed() { + mod_pr_debug!("Vm Bind [{}]: bind_token={:?}\n", vm.id, slot.token(),); + let idx = (slot.slot() as usize) + UAT_USER_CTX_START; + let ttb = binding.ttb | TTBR_VALID | (idx as u64) << TTBR_ASID_SHIFT; + + let uat_inner = self.inner.lock(); + + let ttb1 = if uat_inner.map_kernel_to_user { + uat_inner.kernel_ttb1 | TTBR_VALID | (idx as u64) << TTBR_ASID_SHIFT + } else { + 0 + }; + + let ttbs = uat_inner.ttbs(); + uat_inner.handoff().lock(); + if uat_inner.handoff().current_slot() == Some(idx as u32) { + pr_err!( + "Vm::bind to slot {}, but it is currently in use by the ASC?\n", + idx + ); + } + ttbs[idx].ttb0.store(ttb, Ordering::Relaxed); + ttbs[idx].ttb1.store(ttb1, Ordering::Relaxed); + uat_inner.handoff().unlock(); + core::mem::drop(uat_inner); + + // Make sure all TLB entries from the previous owner of this ASID are gone + mem::tlbi_asid(idx as u8); + mem::sync(); + } + + binding.bind_token = Some(slot.token()); + binding.binding = Some(slot); + } + + binding.active_users += 1; + + let slot = binding.binding.as_ref().unwrap().slot() + UAT_USER_CTX_START as u32; + mod_pr_debug!("MMU: slot {} active users {}\n", slot, binding.active_users); + Ok(VmBind(vm.clone(), slot)) + } + + /// Creates a new `Vm` linked to this UAT. + pub(crate) fn new_vm(&self, id: u64, kernel_range: Range) -> Result { + Vm::new( + &self.dev, + self.inner.clone(), + kernel_range, + self.cfg, + false, + id, + ) + } + + /// Creates the reference-counted inner data for a new `Uat` instance. + #[inline(never)] + fn make_inner(dev: &driver::AsahiDevice) -> Result> { + let handoff_rgn = Self::map_region(dev.as_ref(), c_str!("handoff"), HANDOFF_SIZE, true)?; + let ttbs_rgn = Self::map_region(dev.as_ref(), c_str!("ttbs"), SLOTS_SIZE, true)?; + + // SAFETY: The Handoff struct layout matches the firmware's view of memory at this address, + // and the region is at least large enough per the size specified above. + let handoff = unsafe { &(handoff_rgn.map.ptr() as *mut Handoff).as_ref().unwrap() }; + + dev_info!(dev.as_ref(), "MMU: Initializing kernel page table\n"); + + Arc::pin_init( + try_pin_init!(UatInner { + handoff_flush <- pin_init::pin_init_array_from_fn(|i| { + new_mutex!(HandoffFlush(&handoff.flush[i]), "handoff_flush") + }), + shared <- new_mutex!( + UatShared { + kernel_ttb1: 0, + map_kernel_to_user: false, + handoff_rgn, + ttbs_rgn, + }, + "uat_shared" + ), + }), + GFP_KERNEL, + ) + } + + /// Creates a new `Uat` instance given the relevant hardware config. + #[inline(never)] + pub(crate) fn new( + dev: &driver::AsahiDevice, + cfg: &'static hw::HwConfig, + map_kernel_to_user: bool, + ) -> Result { + dev_info!(dev.as_ref(), "MMU: Initializing...\n"); + + let inner = Self::make_inner(dev)?; + + let pagetables_rgn = + Self::map_region(dev.as_ref(), c_str!("pagetables"), PAGETABLES_SIZE, true)?; + + dev_info!(dev.as_ref(), "MMU: Creating kernel page tables\n"); + let kernel_lower_vm = Vm::new(dev, inner.clone(), IOVA_USER_RANGE, cfg, false, 1)?; + let kernel_vm = Vm::new(dev, inner.clone(), IOVA_KERN_RANGE, cfg, true, 0)?; + + dev_info!(dev.as_ref(), "MMU: Kernel page tables created\n"); + + let ttb0 = kernel_lower_vm.ttb(); + let ttb1 = kernel_vm.ttb(); + + let uat = Self { + dev: dev.into(), + cfg, + pagetables_rgn, + kernel_vm, + kernel_lower_vm, + inner, + slots: slotalloc::SlotAllocator::new( + UAT_USER_CTX as u32, + (), + |_inner, _slot| Some(SlotInner()), + c_str!("Uat::SlotAllocator"), + static_lock_class!(), + static_lock_class!(), + )?, + }; + + let mut inner = uat.inner.lock(); + + inner.map_kernel_to_user = map_kernel_to_user; + inner.kernel_ttb1 = uat.pagetables_rgn.base; + + inner.handoff().init()?; + + dev_info!(dev.as_ref(), "MMU: Initializing TTBs\n"); + + inner.handoff().lock(); + + let ttbs = inner.ttbs(); + + ttbs[0].ttb0.store(ttb0 | TTBR_VALID, Ordering::Relaxed); + ttbs[0] + .ttb1 + .store(uat.pagetables_rgn.base | TTBR_VALID, Ordering::Relaxed); + + for ctx in &ttbs[1..] { + ctx.ttb0.store(0, Ordering::Relaxed); + ctx.ttb1.store(0, Ordering::Relaxed); + } + + inner.handoff().unlock(); + + core::mem::drop(inner); + + uat.kpt0()[2].store(ttb1 | PTE_TABLE, Ordering::Relaxed); + + dev_info!(dev.as_ref(), "MMU: initialized\n"); + + Ok(uat) + } +} + +impl Drop for Uat { + fn drop(&mut self) { + // Unmap what we mapped + self.kpt0()[2].store(0, Ordering::Relaxed); + + // Make sure we flush the TLBs + fence(Ordering::SeqCst); + mem::tlbi_all(); + mem::sync(); + } +} diff --git a/drivers/gpu/drm/asahi/object.rs b/drivers/gpu/drm/asahi/object.rs new file mode 100644 index 00000000000000..38a2268137effb --- /dev/null +++ b/drivers/gpu/drm/asahi/object.rs @@ -0,0 +1,733 @@ +// SPDX-License-Identifier: GPL-2.0-only OR MIT + +//! Asahi GPU object model +//! +//! The AGX GPU includes a coprocessor that uses a large number of shared memory structures to +//! communicate with the driver. These structures contain GPU VA pointers to each other, which are +//! directly dereferenced by the firmware and are expected to always be valid for the usage +//! lifetime of the containing struct (which is an implicit contract, not explicitly managed). +//! Any faults cause an unrecoverable firmware crash, requiring a full system reboot. +//! +//! In order to manage this complexity safely, we implement a GPU object model using Rust's type +//! system to enforce GPU object lifetime relationships. GPU objects represent an allocated piece +//! of memory of a given type, mapped to the GPU (and usually also the CPU). On the CPU side, +//! these objects are associated with a pure Rust structure that contains the objects it depends +//! on (or references to them). This allows us to map Rust lifetimes into the GPU object model +//! system. Then, GPU VA pointers also inherit those lifetimes, which means the Rust borrow checker +//! can ensure that all pointers are assigned an address that is guaranteed to outlive the GPU +//! object it points to. +//! +//! Since the firmware object model does have self-referencing pointers (and there is of course no +//! underlying revocability mechanism to make it safe), we must have an escape hatch. GPU pointers +//! can be weak pointers, which do not enforce lifetimes. In those cases, it is the user's +//! responsibility to ensure that lifetime requirements are met. +//! +//! In other words, the model is necessarily leaky and there is no way to fully map Rust safety to +//! GPU firmware object safety. The goal of the model is to make it easy to model the lifetimes of +//! GPU objects and have the compiler help in avoiding mistakes, rather than to guarantee safety +//! 100% of the time as would be the case for CPU-side Rust code. + +// TODO: There is a fundamental soundness issue with sharing memory with the GPU (that even affects +// C code too). Since the GPU is free to mutate that memory at any time, normal reference invariants +// cannot be enforced on the CPU side. For example, the compiler could perform an optimization that +// assumes that a given memory location does not change between two reads, and causes UB otherwise, +// and then the GPU could mutate that memory out from under the CPU. +// +// For cases where we *expect* this to happen, we use atomic types, which avoid this issue. However, +// doing so for every single field of every type is a non-starter. Right now, there seems to be no +// good solution for this that does not come with significant performance or ergonomics downsides. +// +// In *practice* we are almost always only writing GPU memory, and only reading from atomics, so the +// chances of this actually triggering UB (e.g. a security issue that can be triggered from the GPU +// side) due to a compiler optimization are very slim. +// +// Further discussion: https://github.com/rust-lang/unsafe-code-guidelines/issues/152 + +use kernel::{ + error::code::*, + prelude::*, + sync::Arc, // +}; + +use core::fmt; +use core::fmt::Debug; +use core::fmt::Formatter; +use core::marker::PhantomData; +use core::mem::MaybeUninit; +use core::num::NonZeroU64; +use core::ops::{ + Deref, + DerefMut, + Index, + IndexMut, // +}; +use core::{mem, ptr, slice}; + +use crate::alloc::Allocation; +use crate::debug::*; +use crate::fw::types::Zeroable; +use crate::mmu; + +const DEBUG_CLASS: DebugFlags = DebugFlags::Object; + +/// A GPU-side strong pointer, which is a 64-bit non-zero VA with an associated lifetime. +/// +/// In rare cases these pointers are not aligned, so this is `packed(1)`. +#[repr(C, packed(1))] +pub(crate) struct GpuPointer<'a, T: ?Sized>(NonZeroU64, PhantomData<&'a T>); + +impl<'a, T: ?Sized> GpuPointer<'a, T> { + /// Logical OR the pointer with an arbitrary `u64`. This is used when GPU struct fields contain + /// misc flag fields in the upper bits. The lifetime is retained. This is GPU-unsafe in + /// principle, but we assert that only non-implemented address bits are touched, which is safe + /// for pointers used by the GPU (not by firmware). + pub(crate) fn or(&self, other: u64) -> GpuPointer<'a, T> { + // This will fail for kernel-half pointers, which should not be ORed. + assert_eq!(self.0.get() & other, 0); + // Assert that we only touch the high bits. + assert_eq!(other & 0xffffffffff, 0); + GpuPointer(self.0 | other, PhantomData) + } + + /// Add an arbitrary offset to the pointer. This is not safe (from the GPU perspective), and + /// should only be used via the `inner_ptr` macro to get pointers to inner fields, hence we mark + /// it `unsafe` to discourage direct use. + /// + /// # Safety + /// Do not use directly, only via `inner_ptr`. + // NOTE: The third argument is a type inference hack. + pub(crate) unsafe fn offset(&self, off: usize, _: *const U) -> GpuPointer<'a, U> { + GpuPointer::<'a, U>( + NonZeroU64::new(self.0.get() + (off as u64)).unwrap(), + PhantomData, + ) + } +} + +impl<'a, T> GpuPointer<'a, T> { + /// Create a GPU pointer from a KernelMapping and an offset. + /// TODO: Change all GPU pointers to point to the raw types so size_of here is GPU-sound. + pub(crate) fn from_mapping( + mapping: &'a Arc, + offset: usize, + ) -> Result> { + let addr = mapping.iova().checked_add(offset as u64).ok_or(EINVAL)?; + let end = offset + .checked_add(core::mem::size_of::()) + .ok_or(EINVAL)?; + if end > mapping.size() { + Err(ERANGE) + } else { + Ok(Self(addr.try_into().unwrap(), PhantomData)) + } + } +} + +impl<'a, T: ?Sized> Debug for GpuPointer<'a, T> { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + let val = self.0; + f.write_fmt(format_args!("{:#x} ({})", val, core::any::type_name::())) + } +} + +impl<'a, T: ?Sized> From> for u64 { + fn from(value: GpuPointer<'a, T>) -> Self { + value.0.get() + } +} + +/// Take a pointer to a sub-field within a structure pointed to by a GpuPointer, keeping the +/// lifetime. +#[macro_export] +macro_rules! inner_ptr { + ($gpuva:expr, $($f:tt)*) => ({ + // This mirrors kernel::offset_of(), except we use type inference to avoid having to know + // the type of the pointer explicitly. + fn uninit_from(_: GpuPointer<'_, T>) -> core::mem::MaybeUninit> { + core::mem::MaybeUninit::uninit() + } + let tmp = uninit_from($gpuva); + let outer = tmp.as_ptr(); + // SAFETY: The pointer is valid and aligned, just not initialised; `addr_of` ensures that + // we don't actually read from `outer` (which would be UB) nor create an intermediate + // reference. + let p: *const _ = unsafe { core::ptr::addr_of!((*outer).$($f)*) }; + let inner = p as *const u8; + // SAFETY: The two pointers are within the same allocation block. + let off = unsafe { inner.offset_from(outer as *const u8) }; + // SAFETY: The resulting pointer is guaranteed to point to valid memory within the outer + // object. + unsafe { $gpuva.offset(off.try_into().unwrap(), p) } + }) +} + +/// A GPU-side weak pointer, which is a 64-bit non-zero VA with no lifetime. +/// +/// In rare cases these pointers are not aligned, so this is `packed(1)`. +#[repr(C, packed(1))] +pub(crate) struct GpuWeakPointer(NonZeroU64, PhantomData<*const T>); + +/// SAFETY: GPU weak pointers are always safe to share between threads. +unsafe impl Send for GpuWeakPointer {} +/// SAFETY: GPU weak pointers are always safe to share between threads. +unsafe impl Sync for GpuWeakPointer {} + +// Weak pointers can be copied/cloned regardless of their target type. +impl Copy for GpuWeakPointer {} + +impl Clone for GpuWeakPointer { + fn clone(&self) -> Self { + *self + } +} + +impl GpuWeakPointer { + /// Add an arbitrary offset to the pointer. This is not safe (from the GPU perspective), and + /// should only be used via the `inner_weak_ptr` macro to get pointers to inner fields, hence we + /// mark it `unsafe` to discourage direct use. + /// + /// # Safety + /// Do not use directly, only via `inner_weak_ptr`. + // NOTE: The third argument is a type inference hack. + pub(crate) unsafe fn offset(&self, off: usize, _: *const U) -> GpuWeakPointer { + GpuWeakPointer::( + NonZeroU64::new(self.0.get() + (off as u64)).unwrap(), + PhantomData, + ) + } + + /// Upgrade a weak pointer into a strong pointer. This is not considered safe from the GPU + /// perspective. + /// + /// # Safety + /// The caller must ensure tht the data pointed to lives in the GPU at least as long as the + /// returned lifetime. + pub(crate) unsafe fn upgrade<'a>(&self) -> GpuPointer<'a, T> { + GpuPointer(self.0, PhantomData) + } +} + +impl From> for u64 { + fn from(value: GpuWeakPointer) -> Self { + value.0.get() + } +} + +impl Debug for GpuWeakPointer { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + let val = self.0; + f.write_fmt(format_args!("{:#x} ({})", val, core::any::type_name::())) + } +} + +/// Take a pointer to a sub-field within a structure pointed to by a GpuWeakPointer. +#[macro_export] +macro_rules! inner_weak_ptr { + ($gpuva:expr, $($f:tt)*) => ({ + // See inner_ptr() + fn uninit_from(_: GpuWeakPointer) -> core::mem::MaybeUninit> { + core::mem::MaybeUninit::uninit() + } + let tmp = uninit_from($gpuva); + let outer = tmp.as_ptr(); + // SAFETY: The pointer is valid and aligned, just not initialised; `addr_of` ensures that + // we don't actually read from `outer` (which would be UB) nor create an intermediate + // reference. + let p: *const _ = unsafe { core::ptr::addr_of!((*outer).$($f)*) }; + let inner = p as *const u8; + // SAFETY: The two pointers are within the same allocation block. + let off = unsafe { inner.offset_from(outer as *const u8) }; + // SAFETY: The resulting pointer is guaranteed to point to valid memory within the outer + // object. + unsafe { $gpuva.offset(off.try_into().unwrap(), p) } + }) +} + +/// Types that implement this trait represent a GPU structure from the CPU side. +/// +/// The `Raw` type represents the actual raw structure definition on the GPU side. +/// +/// Types implementing [`GpuStruct`] must have fields owning any objects (or strong references +/// to them) that GPU pointers in the `Raw` structure point to. This mechanism is used to enforce +/// lifetimes. +pub(crate) trait GpuStruct: 'static { + /// The type of the GPU-side structure definition representing the firmware struct layout. + type Raw<'a>; +} + +/// An instance of a GPU object in memory. +/// +/// # Invariants +/// `raw` must point to a valid mapping of the `T::Raw` type associated with the `alloc` allocation. +/// `gpu_ptr` must be the GPU address of the same object. +pub(crate) struct GpuObject> { + raw: *mut T::Raw<'static>, + alloc: U, + gpu_ptr: GpuWeakPointer, + inner: KBox, +} + +impl> GpuObject { + /// Create a new GpuObject given an allocator and the inner data (a type implementing + /// GpuStruct). + /// + /// The caller passes a closure that constructs the `T::Raw` type given a reference to the + /// `GpuStruct`. This is the mechanism used to enforce lifetimes. + pub(crate) fn new( + alloc: U, + inner: T, + callback: impl for<'a> FnOnce(&'a T) -> T::Raw<'a>, + ) -> Result { + let size = mem::size_of::>(); + if size > 0x1000 { + dev_crit!( + alloc.device().as_ref(), + "Allocating {} of size {:#x}, with new, please use new_boxed!\n", + core::any::type_name::(), + size + ); + } + if alloc.size() < size { + return Err(ENOMEM); + } + let gpu_ptr = + GpuWeakPointer::(NonZeroU64::new(alloc.gpu_ptr()).ok_or(EINVAL)?, PhantomData); + mod_dev_dbg!( + alloc.device(), + "Allocating {} @ {:#x}\n", + core::any::type_name::(), + alloc.gpu_ptr() + ); + let p = alloc.ptr().ok_or(EINVAL)?.as_ptr() as *mut T::Raw<'static>; + let mut raw = callback(&inner); + // SAFETY: `p` is guaranteed to be valid per the Allocation invariant, and the type is + // identical to the type of `raw` other than the lifetime. + unsafe { p.copy_from(&mut raw as *mut _ as *mut u8 as *mut _, 1) }; + mem::forget(raw); + Ok(Self { + raw: p, + gpu_ptr, + alloc, + inner: KBox::new(inner, GFP_KERNEL)?, + }) + } + + /// Create a new GpuObject given an allocator and the boxed inner data (a type implementing + /// GpuStruct). + /// + /// The caller passes a closure that initializes the `T::Raw` type given a reference to the + /// `GpuStruct` and a `MaybeUninit`. This is intended to be used with the place!() + /// macro to avoid constructing the whole `T::Raw` object on the stack. + pub(crate) fn new_boxed( + alloc: U, + inner: KBox, + callback: impl for<'a> FnOnce( + &'a T, + &'a mut MaybeUninit>, + ) -> Result<&'a mut T::Raw<'a>>, + ) -> Result { + if alloc.size() < mem::size_of::>() { + return Err(ENOMEM); + } + let gpu_ptr = + GpuWeakPointer::(NonZeroU64::new(alloc.gpu_ptr()).ok_or(EINVAL)?, PhantomData); + mod_dev_dbg!( + alloc.device(), + "Allocating {} @ {:#x}\n", + core::any::type_name::(), + alloc.gpu_ptr() + ); + let p = alloc.ptr().ok_or(EINVAL)?.as_ptr() as *mut MaybeUninit>; + // SAFETY: `p` is guaranteed to be valid per the Allocation invariant. + let raw = callback(&inner, unsafe { &mut *p })?; + if p as *mut T::Raw<'_> != raw as *mut _ { + dev_err!( + alloc.device().as_ref(), + "Allocation callback returned a mismatched reference ({})\n", + core::any::type_name::(), + ); + return Err(EINVAL); + } + Ok(Self { + raw: p as *mut u8 as *mut T::Raw<'static>, + gpu_ptr, + alloc, + inner, + }) + } + + /// Create a new GpuObject given an allocator and the inner data (a type implementing + /// GpuStruct). + /// + /// The caller passes a closure that initializes the `T::Raw` type given a reference to the + /// `GpuStruct` and a `MaybeUninit`. This is intended to be used with the place!() + /// macro to avoid constructing the whole `T::Raw` object on the stack. + pub(crate) fn new_inplace( + alloc: U, + inner: T, + callback: impl for<'a> FnOnce( + &'a T, + &'a mut MaybeUninit>, + ) -> Result<&'a mut T::Raw<'a>>, + ) -> Result { + GpuObject::::new_boxed(alloc, KBox::new(inner, GFP_KERNEL)?, callback) + } + + /// Create a new GpuObject given an allocator and the boxed inner data (a type implementing + /// GpuStruct). + /// + /// The caller passes a closure that initializes the `T::Raw` type given a reference to the + /// `GpuStruct` and a `MaybeUninit`. This is intended to be used with the place!() + /// macro to avoid constructing the whole `T::Raw` object on the stack. + pub(crate) fn new_init_prealloc<'a, I: Init, R: PinInit, F>, E, F>( + alloc: U, + inner_init: impl FnOnce(GpuWeakPointer) -> I, + raw_init: impl FnOnce(&'a T, GpuWeakPointer) -> R, + ) -> Result + where + kernel::error::Error: core::convert::From, + kernel::error::Error: core::convert::From, + { + if alloc.size() < mem::size_of::>() { + return Err(ENOMEM); + } + let gpu_ptr = + GpuWeakPointer::(NonZeroU64::new(alloc.gpu_ptr()).ok_or(EINVAL)?, PhantomData); + mod_dev_dbg!( + alloc.device(), + "Allocating {} @ {:#x}\n", + core::any::type_name::(), + alloc.gpu_ptr() + ); + let inner = inner_init(gpu_ptr); + let p = alloc.ptr().ok_or(EINVAL)?.as_ptr() as *mut T::Raw<'_>; + let ret = Self { + raw: p as *mut u8 as *mut T::Raw<'static>, + gpu_ptr, + alloc, + inner: KBox::init(inner, GFP_KERNEL)?, + }; + let q = &*ret.inner as *const T; + // SAFETY: `p` is guaranteed to be valid per the Allocation invariant. + unsafe { raw_init(&*q, gpu_ptr).__pinned_init(p) }?; + Ok(ret) + } + + /// Returns the GPU VA of this object (as a raw [`NonZeroU64`]) + pub(crate) fn gpu_va(&self) -> NonZeroU64 { + self.gpu_ptr.0 + } + + /// Returns a strong GPU pointer to this object, with a lifetime. + pub(crate) fn gpu_pointer(&self) -> GpuPointer<'_, T> { + GpuPointer(self.gpu_ptr.0, PhantomData) + } + + /// Returns a weak GPU pointer to this object, with no lifetime. + pub(crate) fn weak_pointer(&self) -> GpuWeakPointer { + GpuWeakPointer(self.gpu_ptr.0, PhantomData) + } + + /// Perform a mutation to the inner `Raw` data given a user-supplied callback. + /// + /// The callback gets a mutable reference to the `GpuStruct` type. + pub(crate) fn with_mut( + &mut self, + callback: impl for<'a> FnOnce(&'a mut ::Raw<'a>, &'a mut T) -> RetVal, + ) -> RetVal { + // SAFETY: `self.raw` is valid per the type invariant, and the second half is just + // converting lifetimes. + unsafe { callback(&mut *self.raw, &mut *(&mut *self.inner as *mut _)) } + } + + /// Access the inner `Raw` data given a user-supplied callback. + /// + /// The callback gets a reference to the `GpuStruct` type. + pub(crate) fn with( + &self, + callback: impl for<'a> FnOnce(&'a ::Raw<'a>, &'a T) -> RetVal, + ) -> RetVal { + // SAFETY: `self.raw` is valid per the type invariant, and the second half is just + // converting lifetimes. + unsafe { callback(&*self.raw, &*(&*self.inner as *const _)) } + } +} + +impl> Deref for GpuObject { + type Target = T; + + fn deref(&self) -> &Self::Target { + &self.inner + } +} + +impl> DerefMut for GpuObject { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.inner + } +} + +impl> Debug for GpuObject +where + ::Raw<'static>: Debug, +{ + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + f.debug_struct(core::any::type_name::()) + // SAFETY: `self.raw` is valid per the type invariant. + .field("raw", &format_args!("{:#X?}", unsafe { &*self.raw })) + .field("inner", &format_args!("{:#X?}", &self.inner)) + .field("alloc", &format_args!("{:?}", &self.alloc)) + .finish() + } +} + +impl> GpuObject +where + for<'a> ::Raw<'a>: Default + Zeroable, +{ + /// Create a new GpuObject with default data. `T` must implement `Default` and `T::Raw` must + /// implement `Zeroable`, since the GPU-side memory is initialized by zeroing. + pub(crate) fn new_default(alloc: U) -> Result { + GpuObject::::new_inplace(alloc, Default::default(), |_inner, raw| { + // SAFETY: `raw` is valid here, and `T::Raw` implements `Zeroable`. + Ok(unsafe { + ptr::write_bytes(raw, 0, 1); + (*raw).assume_init_mut() + }) + }) + } +} + +impl> Drop for GpuObject { + fn drop(&mut self) { + mod_dev_dbg!( + self.alloc.device(), + "Dropping {} @ {:?}\n", + core::any::type_name::(), + self.gpu_pointer() + ); + } +} + +// SAFETY: GpuObjects are Send as long as the GpuStruct itself is Send +unsafe impl> Send for GpuObject {} +// SAFETY: GpuObjects are Send as long as the GpuStruct itself is Send +unsafe impl> Sync for GpuObject {} + +/// Trait used to erase the type of a GpuObject, used when we need to keep a list of heterogenous +/// objects around. +pub(crate) trait OpaqueGpuObject: Send + Sync { + fn gpu_va(&self) -> NonZeroU64; +} + +impl> OpaqueGpuObject for GpuObject { + fn gpu_va(&self) -> NonZeroU64 { + Self::gpu_va(self) + } +} + +/// An array of raw GPU objects that is only accessible to the GPU (no CPU-side mapping required). +/// +/// This must necessarily be uninitialized as far as the GPU is concerned, so it cannot be used +/// when initialization is required. +/// +/// # Invariants +/// +/// `alloc` is valid and at least as large as `len` times the size of one `T`. +/// `gpu_ptr` is valid and points to the allocation start. +pub(crate) struct GpuOnlyArray> { + len: usize, + alloc: U, + gpu_ptr: NonZeroU64, + _p: PhantomData, +} + +impl> GpuOnlyArray { + /// Allocate a new GPU-only array with the given length. + pub(crate) fn new(alloc: U, count: usize) -> Result> { + let bytes = count * mem::size_of::(); + let gpu_ptr = NonZeroU64::new(alloc.gpu_ptr()).ok_or(EINVAL)?; + if alloc.size() < bytes { + return Err(ENOMEM); + } + Ok(Self { + len: count, + alloc, + gpu_ptr, + _p: PhantomData, + }) + } + + /// Returns the GPU VA of this arraw (as a raw [`NonZeroU64`]) + pub(crate) fn gpu_va(&self) -> NonZeroU64 { + self.gpu_ptr + } + + /// Returns a strong GPU pointer to this array, with a lifetime. + pub(crate) fn gpu_pointer(&self) -> GpuPointer<'_, &'_ [T]> { + GpuPointer(self.gpu_ptr, PhantomData) + } + + /// Returns a weak GPU pointer to this array, with no lifetime. + pub(crate) fn weak_pointer(&self) -> GpuWeakPointer<[T]> { + GpuWeakPointer(self.gpu_ptr, PhantomData) + } + + /// Returns a pointer to an offset within the array (as a subslice). + pub(crate) fn gpu_offset_pointer(&self, offset: usize) -> GpuPointer<'_, &'_ [T]> { + if offset > self.len { + panic!("Index {} out of bounds (len: {})", offset, self.len); + } + GpuPointer( + NonZeroU64::new(self.gpu_ptr.get() + (offset * mem::size_of::()) as u64).unwrap(), + PhantomData, + ) + } + + /* Not used yet + /// Returns a weak pointer to an offset within the array (as a subslice). + pub(crate) fn weak_offset_pointer(&self, offset: usize) -> GpuWeakPointer<[T]> { + if offset > self.len { + panic!("Index {} out of bounds (len: {})", offset, self.len); + } + GpuWeakPointer( + NonZeroU64::new(self.gpu_ptr.get() + (offset * mem::size_of::()) as u64).unwrap(), + PhantomData, + ) + } + + /// Returns a pointer to an element within the array. + pub(crate) fn gpu_item_pointer(&self, index: usize) -> GpuPointer<'_, &'_ T> { + if index >= self.len { + panic!("Index {} out of bounds (len: {})", index, self.len); + } + GpuPointer( + NonZeroU64::new(self.gpu_ptr.get() + (index * mem::size_of::()) as u64).unwrap(), + PhantomData, + ) + } + */ + + /// Returns a weak pointer to an element within the array. + pub(crate) fn weak_item_pointer(&self, index: usize) -> GpuWeakPointer { + if index >= self.len { + panic!("Index {} out of bounds (len: {})", index, self.len); + } + GpuWeakPointer( + NonZeroU64::new(self.gpu_ptr.get() + (index * mem::size_of::()) as u64).unwrap(), + PhantomData, + ) + } + + /// Returns the length of the array. + pub(crate) fn len(&self) -> usize { + self.len + } +} + +impl> Debug for GpuOnlyArray { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + f.debug_struct(core::any::type_name::()) + .field("len", &format_args!("{:#X?}", self.len())) + .finish() + } +} + +impl> Drop for GpuOnlyArray { + fn drop(&mut self) { + mod_dev_dbg!( + self.alloc.device(), + "Dropping {} @ {:?}\n", + core::any::type_name::(), + self.gpu_pointer() + ); + } +} + +/// An array of raw GPU objects that is also CPU-accessible. +/// +/// # Invariants +/// +/// `raw` is valid and points to the CPU-side view of the array (which must have one). +pub(crate) struct GpuArray> { + raw: *mut T, + array: GpuOnlyArray, +} + +impl> GpuArray { + /// Allocate a new GPU array, initializing each element to its default. + pub(crate) fn empty(alloc: U, count: usize) -> Result> { + let p = alloc.ptr().ok_or(EINVAL)?.as_ptr(); + let inner = GpuOnlyArray::new(alloc, count)?; + let mut pi = p; + for _i in 0..count { + // SAFETY: `pi` is valid per the Allocation type invariant, and GpuOnlyArray guarantees + // that it can never iterate beyond the buffer length. + unsafe { + pi.write(Default::default()); + pi = pi.add(1); + } + } + Ok(Self { + raw: p, + array: inner, + }) + } +} + +impl> GpuArray { + /// Get a slice view of the array contents. + pub(crate) fn as_slice(&self) -> &[T] { + // SAFETY: self.raw / self.len are valid per the type invariant + unsafe { slice::from_raw_parts(self.raw, self.len) } + } + + /// Get a mutable slice view of the array contents. + pub(crate) fn as_mut_slice(&mut self) -> &mut [T] { + // SAFETY: self.raw / self.len are valid per the type invariant + unsafe { slice::from_raw_parts_mut(self.raw, self.len) } + } +} + +impl> Deref for GpuArray { + type Target = GpuOnlyArray; + + fn deref(&self) -> &GpuOnlyArray { + &self.array + } +} + +impl> Index for GpuArray { + type Output = T; + + fn index(&self, index: usize) -> &T { + if index >= self.len { + panic!("Index {} out of bounds (len: {})", index, self.len); + } + // SAFETY: This is bounds checked above + unsafe { &*(self.raw.add(index)) } + } +} + +impl> IndexMut for GpuArray { + fn index_mut(&mut self, index: usize) -> &mut T { + if index >= self.len { + panic!("Index {} out of bounds (len: {})", index, self.len); + } + // SAFETY: This is bounds checked above + unsafe { &mut *(self.raw.add(index)) } + } +} + +// SAFETY: GpuArray are Send as long as the contained type itself is Send +unsafe impl> Send for GpuArray {} +// SAFETY: GpuArray are Sync as long as the contained type itself is Sync +unsafe impl> Sync for GpuArray {} + +impl> Debug for GpuArray { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + f.debug_struct(core::any::type_name::()) + .field("array", &format_args!("{:#X?}", self.as_slice())) + .finish() + } +} diff --git a/drivers/gpu/drm/asahi/queue/common.rs b/drivers/gpu/drm/asahi/queue/common.rs new file mode 100644 index 00000000000000..a68352828cfbc3 --- /dev/null +++ b/drivers/gpu/drm/asahi/queue/common.rs @@ -0,0 +1,42 @@ +// SPDX-License-Identifier: GPL-2.0-only OR MIT + +//! Common queue functionality. +//! +//! Shared helpers used by the submission logic for multiple command types. + +use crate::file; +use crate::fw::job::UserTimestamp; + +use kernel::prelude::*; +use kernel::uapi; +use kernel::xarray; + +pub(super) fn get_timestamp_object( + objects: Pin<&xarray::XArray>>, + timestamp: uapi::drm_asahi_timestamp, +) -> Result> { + if timestamp.handle == 0 { + return Ok(None); + } + + let guard = objects.lock(); + let object = guard + .get(timestamp.handle.try_into()?) + .ok_or(ENOENT)? + .clone(); + core::mem::drop(guard); + + #[allow(irrefutable_let_patterns)] + if let file::Object::TimestampBuffer(mapping) = object { + let offset = timestamp.offset; + if (offset.checked_add(8).ok_or(EINVAL)?) as usize > mapping.size() { + return Err(ERANGE); + } + Ok(Some(UserTimestamp { + mapping: mapping.clone(), + offset: offset as usize, + })) + } else { + Err(EINVAL) + } +} diff --git a/drivers/gpu/drm/asahi/queue/compute.rs b/drivers/gpu/drm/asahi/queue/compute.rs new file mode 100644 index 00000000000000..62afc561806703 --- /dev/null +++ b/drivers/gpu/drm/asahi/queue/compute.rs @@ -0,0 +1,385 @@ +// SPDX-License-Identifier: GPL-2.0-only OR MIT +#![allow(clippy::unusual_byte_groupings)] + +//! Compute work queue. +//! +//! A compute queue consists of one underlying WorkQueue. +//! This module is in charge of creating all of the firmware structures required to submit compute +//! work to the GPU, based on the userspace command buffer. + +use super::common; +use crate::alloc::Allocator; +use crate::debug::*; +use crate::fw::types::*; +use crate::gpu::GpuManager; +use crate::{ + file, + fw, + gpu, + microseq, // +}; +use crate::{ + inner_ptr, + inner_weak_ptr, // +}; +use core::sync::atomic::Ordering; +use kernel::dma_fence::RawDmaFence; +use kernel::drm::sched::Job; +use kernel::prelude::*; +use kernel::sync::Arc; +use kernel::uapi; +use kernel::xarray; + +const DEBUG_CLASS: DebugFlags = DebugFlags::Compute; + +#[versions(AGX)] +impl super::QueueInner::ver { + /// Submit work to a compute queue. + pub(super) fn submit_compute( + &self, + job: &mut Job, + cmdbuf: &uapi::drm_asahi_cmd_compute, + attachments: µseq::Attachments, + objects: Pin<&xarray::XArray>>, + id: u64, + flush_stamps: bool, + ) -> Result { + let gpu = match (*self.dev) + .gpu + .as_any() + .downcast_ref::() + { + Some(gpu) => gpu, + None => { + dev_crit!(self.dev.as_ref(), "GpuManager mismatched with Queue!\n"); + return Err(EIO); + } + }; + + let mut alloc = gpu.alloc(); + let kalloc = &mut *alloc; + + mod_dev_dbg!(self.dev, "[Submission {}] Compute!\n", id); + + if cmdbuf.flags != 0 { + return Err(EINVAL); + } + + let mut user_timestamps: fw::job::UserTimestamps = Default::default(); + user_timestamps.start = common::get_timestamp_object(objects, cmdbuf.ts.start)?; + user_timestamps.end = common::get_timestamp_object(objects, cmdbuf.ts.end)?; + + // This sequence number increases per new client/VM? assigned to some slot, + // but it's unclear *which* slot... + let slot_client_seq: u8 = (self.id & 0xff) as u8; + + let vm_bind = job.vm_bind.clone(); + + mod_dev_dbg!( + self.dev, + "[Submission {}] VM slot = {}\n", + id, + vm_bind.slot() + ); + + let notifier = self.notifier.clone(); + + let fence = job.fence.clone(); + let comp_job = job.get_comp()?; + let ev_comp = comp_job.event_info(); + + let preempt2_off = gpu.get_cfg().compute_preempt1_size; + let preempt3_off = preempt2_off + 8; + let preempt4_off = preempt3_off + 8; + let preempt5_off = preempt4_off + 8; + let preempt_size = preempt5_off + 8; + + let preempt_buf = self + .ualloc + .lock() + .array_empty_tagged(preempt_size, b"CPMT")?; + + mod_dev_dbg!( + self.dev, + "[Submission {}] Event #{} {:#x?} -> {:#x?}\n", + id, + ev_comp.slot, + ev_comp.value, + ev_comp.value.next(), + ); + + let timestamps = Arc::new( + kalloc.shared.new_default::()?, + GFP_KERNEL, + )?; + + let uuid = 0; + mod_dev_dbg!(self.dev, "[Submission {}] UUID = {:#x?}\n", id, uuid); + + // TODO: check + #[ver(V >= V13_0B4)] + let count = self.counter.fetch_add(1, Ordering::Relaxed); + + let comp = GpuObject::new_init_prealloc( + kalloc.gpu_ro.alloc_object()?, + |ptr: GpuWeakPointer| { + let notifier = notifier.clone(); + let vm_bind = vm_bind.clone(); + try_init!(fw::compute::RunCompute::ver { + preempt_buf: preempt_buf, + micro_seq: { + let mut builder = microseq::Builder::new(); + + let stats = gpu.initdata.runtime_pointers.stats.comp.weak_pointer(); + + let start_comp = builder.add(microseq::StartCompute::ver { + header: microseq::op::StartCompute::HEADER, + unk_pointer: inner_weak_ptr!(ptr, unk_pointee), + #[ver(G < G14X)] + job_params1: Some(inner_weak_ptr!(ptr, job_params1)), + #[ver(G >= G14X)] + job_params1: None, + #[ver(G >= G14X)] + registers: inner_weak_ptr!(ptr, registers), + stats, + work_queue: ev_comp.info_ptr, + vm_slot: vm_bind.slot(), + unk_28: 0x1, + event_generation: self.id as u32, + event_seq: U64(ev_comp.event_seq), + unk_38: 0x0, + job_params2: inner_weak_ptr!(ptr, job_params2), + unk_44: 0x0, + uuid, + attachments: *attachments, + padding: Default::default(), + #[ver(V >= V13_0B4)] + unk_flag: inner_weak_ptr!(ptr, unk_flag), + #[ver(V >= V13_0B4)] + counter: U64(count), + #[ver(V >= V13_0B4)] + notifier_buf: inner_weak_ptr!(notifier.weak_pointer(), state.unk_buf), + })?; + + if user_timestamps.any() { + builder.add(microseq::Timestamp::ver { + header: microseq::op::Timestamp::new(true), + command_time: inner_weak_ptr!(ptr, command_time), + ts_pointers: inner_weak_ptr!(ptr, timestamp_pointers), + update_ts: inner_weak_ptr!(ptr, timestamp_pointers.start_addr), + work_queue: ev_comp.info_ptr, + user_ts_pointers: inner_weak_ptr!(ptr, user_timestamp_pointers), + #[ver(V >= V13_0B4)] + unk_ts: inner_weak_ptr!(ptr, context_store_req), + uuid, + unk_30_padding: 0, + })?; + } + + #[ver(G < G14X)] + builder.add(microseq::WaitForIdle { + header: microseq::op::WaitForIdle::new(microseq::Pipe::Compute), + })?; + #[ver(G >= G14X)] + builder.add(microseq::WaitForIdle2 { + header: microseq::op::WaitForIdle2::HEADER, + })?; + + if user_timestamps.any() { + builder.add(microseq::Timestamp::ver { + header: microseq::op::Timestamp::new(false), + command_time: inner_weak_ptr!(ptr, command_time), + ts_pointers: inner_weak_ptr!(ptr, timestamp_pointers), + update_ts: inner_weak_ptr!(ptr, timestamp_pointers.end_addr), + work_queue: ev_comp.info_ptr, + user_ts_pointers: inner_weak_ptr!(ptr, user_timestamp_pointers), + #[ver(V >= V13_0B4)] + unk_ts: inner_weak_ptr!(ptr, context_store_req), + uuid, + unk_30_padding: 0, + })?; + } + + let off = builder.offset_to(start_comp); + builder.add(microseq::FinalizeCompute::ver { + header: microseq::op::FinalizeCompute::HEADER, + stats, + work_queue: ev_comp.info_ptr, + vm_slot: vm_bind.slot(), + #[ver(V < V13_0B4)] + unk_18: 0, + job_params2: inner_weak_ptr!(ptr, job_params2), + unk_24: 0, + uuid, + fw_stamp: ev_comp.fw_stamp_pointer, + stamp_value: ev_comp.value.next(), + unk_38: 0, + unk_3c: 0, + unk_40: 0, + unk_44: 0, + unk_48: 0, + unk_4c: 0, + unk_50: 0, + unk_54: 0, + unk_58: 0, + #[ver(G == G14 && V < V13_0B4)] + unk_5c_g14: U64(0), + restart_branch_offset: off, + has_attachments: (attachments.count > 0) as u32, + #[ver(V >= V13_0B4)] + unk_64: Default::default(), + #[ver(V >= V13_0B4)] + unk_flag: inner_weak_ptr!(ptr, unk_flag), + #[ver(V >= V13_0B4)] + unk_79: Default::default(), + })?; + + builder.add(microseq::RetireStamp { + header: microseq::op::RetireStamp::HEADER, + })?; + builder.build(&mut kalloc.private)? + }, + notifier, + vm_bind, + timestamps, + user_timestamps, + }) + }, + |inner, _ptr| { + let vm_slot = vm_bind.slot(); + try_init!(fw::compute::raw::RunCompute::ver { + tag: fw::workqueue::CommandType::RunCompute, + #[ver(V >= V13_0B4)] + counter: U64(count), + unk_4: 0, + vm_slot, + notifier: inner.notifier.gpu_pointer(), + unk_pointee: Default::default(), + #[ver(G < G14X)] + __pad0: Default::default(), + #[ver(G < G14X)] + job_params1 <- try_init!(fw::compute::raw::JobParameters1 { + preempt_buf1: inner.preempt_buf.gpu_pointer(), + cdm_ctrl_stream_base: U64(cmdbuf.cdm_ctrl_stream_base), + // buf2-5 Only if internal program is used + preempt_buf2: inner.preempt_buf.gpu_offset_pointer(preempt2_off), + preempt_buf3: inner.preempt_buf.gpu_offset_pointer(preempt3_off), + preempt_buf4: inner.preempt_buf.gpu_offset_pointer(preempt4_off), + preempt_buf5: inner.preempt_buf.gpu_offset_pointer(preempt5_off), + usc_exec_base_cp: U64(self.usc_exec_base), + unk_38: U64(0x8c60), + helper_program: cmdbuf.helper.binary, // Internal program addr | 1 + unk_44: 0, + helper_arg: U64(cmdbuf.helper.data), // Only if internal program used + helper_cfg: cmdbuf.helper.cfg, // 0x40 if internal program used + unk_54: 0, + unk_58: 1, + unk_5c: 0, + iogpu_unk_40: 0, // 0x1c if internal program used + __pad: Default::default(), + }), + #[ver(G >= G14X)] + registers: fw::job::raw::RegisterArray::new( + inner_weak_ptr!(_ptr, registers.registers), + |r| { + r.add(0x1a510, inner.preempt_buf.gpu_pointer().into()); + r.add(0x1a420, cmdbuf.cdm_ctrl_stream_base); + // buf2-5 Only if internal program is used + r.add(0x1a4d0, inner.preempt_buf.gpu_offset_pointer(preempt2_off).into()); + r.add(0x1a4d8, inner.preempt_buf.gpu_offset_pointer(preempt3_off).into()); + r.add(0x1a4e0, inner.preempt_buf.gpu_offset_pointer(preempt4_off).into()); + r.add(0x1a4e8, inner.preempt_buf.gpu_offset_pointer(preempt5_off).into()); + r.add(0x10071, self.usc_exec_base); // USC_EXEC_BASE_CP + r.add(0x11841, cmdbuf.helper.binary.into()); + r.add(0x11849, cmdbuf.helper.data); + r.add(0x11f81, cmdbuf.helper.cfg.into()); + r.add(0x1a440, 0x24201); + r.add(0x12091, 0 /* iogpu_unk_40 */); + /* + r.add(0x10201, 0x100); // Some kind of counter?? Does this matter? + r.add(0x10428, 0x100); // Some kind of counter?? Does this matter? + */ + } + ), + __pad1: Default::default(), + microsequence: inner.micro_seq.gpu_pointer(), + microsequence_size: inner.micro_seq.len() as u32, + job_params2 <- try_init!(fw::compute::raw::JobParameters2::ver { + #[ver(V >= V13_0B4)] + unk_0_0: 0, + unk_0: Default::default(), + preempt_buf1: inner.preempt_buf.gpu_pointer(), + cdm_ctrl_stream_end: U64(cmdbuf.cdm_ctrl_stream_end), + unk_34: Default::default(), + #[ver(G < G14X)] + unk_g14x: 0, + #[ver(G >= G14X)] + unk_g14x: 0x24201, + unk_58: 0, + #[ver(V < V13_0B4)] + unk_5c: 0, + }), + encoder_params <- try_init!(fw::job::raw::EncoderParams { + unk_8: 0x0, // fixed + sync_grow: 0x0, // check! + unk_10: 0x0, // fixed + encoder_id: 0, + unk_18: 0x0, // fixed + unk_mask: 0xffffffff, + sampler_array: U64(cmdbuf.sampler_heap), + sampler_count: cmdbuf.sampler_count as u32, + sampler_max: (cmdbuf.sampler_count as u32) + 1, + }), + meta <- try_init!(fw::job::raw::JobMeta { + unk_0: 0, + unk_2: 0, + no_preemption: 0, + stamp: ev_comp.stamp_pointer, + fw_stamp: ev_comp.fw_stamp_pointer, + stamp_value: ev_comp.value.next(), + stamp_slot: ev_comp.slot, + evctl_index: 0, // fixed + flush_stamps: flush_stamps as u32, + uuid, + event_seq: ev_comp.event_seq as u32, + }), + command_time: U64(0), + timestamp_pointers <- try_init!(fw::job::raw::TimestampPointers { + start_addr: Some(inner_ptr!(inner.timestamps.gpu_pointer(), start)), + end_addr: Some(inner_ptr!(inner.timestamps.gpu_pointer(), end)), + }), + user_timestamp_pointers: inner.user_timestamps.pointers()?, + client_sequence: slot_client_seq, + pad_2d1: Default::default(), + unk_2d4: 0, + unk_2d8: 0, + #[ver(V >= V13_0B4)] + context_store_req: U64(0), + #[ver(V >= V13_0B4)] + context_store_compl: U64(0), + #[ver(V >= V13_0B4)] + unk_2e9: Default::default(), + #[ver(V >= V13_0B4)] + unk_flag: U32(0), + #[ver(V >= V13_0B4)] + unk_pad: Default::default(), + }) + }, + )?; + + core::mem::drop(alloc); + + fence.add_command(); + comp_job.add_cb(comp, vm_bind.slot(), move |error| { + if let Some(err) = error { + fence.set_error(err.into()) + } + + fence.command_complete(); + })?; + + comp_job.next_seq(); + + Ok(()) + } +} diff --git a/drivers/gpu/drm/asahi/queue/mod.rs b/drivers/gpu/drm/asahi/queue/mod.rs new file mode 100644 index 00000000000000..caae3e19f994c6 --- /dev/null +++ b/drivers/gpu/drm/asahi/queue/mod.rs @@ -0,0 +1,937 @@ +// SPDX-License-Identifier: GPL-2.0-only OR MIT + +//! Submission queue management +//! +//! This module implements the userspace view of submission queues and the logic to map userspace +//! submissions to firmware queues. + +use kernel::dma_fence::*; +use kernel::prelude::*; +use kernel::{ + c_str, + dma_fence, + drm::sched, + macros::versions, + sync::{ + Arc, + LockClassKey, + Mutex, // + }, + uapi, + xarray, // +}; + +use crate::alloc::Allocator; +use crate::debug::*; +use crate::driver::{AsahiDevRef, AsahiDevice}; +use crate::file::MAX_COMMANDS_PER_SUBMISSION; +use crate::fw::types::*; +use crate::gpu::GpuManager; +use crate::inner_weak_ptr; +use crate::microseq; +use crate::module_parameters; +use crate::util::{ + AnyBitPattern, + Reader, // +}; +use crate::{ + alloc, + buffer, + channel, + event, + file, + fw, + gpu, + mmu, + workqueue, // +}; + +use core::sync::atomic::{ + AtomicU64, + Ordering, // +}; + +const DEBUG_CLASS: DebugFlags = DebugFlags::Queue; + +const WQ_SIZE: u32 = 0x500; + +mod common; +mod compute; +mod render; + +/// Trait implemented by all versioned queues. +pub(crate) trait Queue: Send + Sync { + fn submit( + &mut self, + id: u64, + syncs: KVec, + in_sync_count: usize, + cmdbuf_raw: &[u8], + objects: Pin<&xarray::XArray>>, + ) -> Result; +} + +#[versions(AGX)] +struct SubQueue { + wq: Arc, +} + +#[versions(AGX)] +impl SubQueue::ver { + fn new_job(&mut self, fence: dma_fence::Fence) -> SubQueueJob::ver { + SubQueueJob::ver { + wq: self.wq.clone(), + fence: Some(fence), + job: None, + } + } +} + +#[versions(AGX)] +struct SubQueueJob { + wq: Arc, + job: Option, + fence: Option, +} + +#[versions(AGX)] +impl SubQueueJob::ver { + fn get(&mut self) -> Result<&mut workqueue::Job::ver> { + if self.job.is_none() { + mod_pr_debug!("SubQueueJob: Creating {:?} job\n", self.wq.pipe_type()); + self.job + .replace(self.wq.new_job(self.fence.take().unwrap())?); + } + Ok(self.job.as_mut().expect("expected a Job")) + } + + fn commit(&mut self) -> Result { + match self.job.as_mut() { + Some(job) => job.commit(), + None => Ok(()), + } + } + + fn can_submit(&self) -> Option { + self.job.as_ref().and_then(|job| job.can_submit()) + } +} + +#[versions(AGX)] +pub(crate) struct Queue { + dev: AsahiDevRef, + _sched: sched::Scheduler, + entity: sched::Entity, + vm: mmu::Vm, + q_vtx: Option, + q_frag: Option, + q_comp: Option, + fence_ctx: FenceContexts, + inner: QueueInner::ver, +} + +#[versions(AGX)] +pub(crate) struct QueueInner { + dev: AsahiDevRef, + ualloc: Arc>, + buffer: buffer::Buffer::ver, + gpu_context: Arc, + notifier_list: Arc>, + notifier: Arc>, + usc_exec_base: u64, + id: u64, + #[ver(V >= V13_0B4)] + counter: AtomicU64, +} + +#[versions(AGX)] +#[derive(Default)] +pub(crate) struct JobFence { + id: u64, + pending: AtomicU64, +} + +#[versions(AGX)] +impl JobFence::ver { + fn add_command(self: &FenceObject) { + self.pending.fetch_add(1, Ordering::Relaxed); + } + + fn command_complete(self: &FenceObject) { + let remain = self.pending.fetch_sub(1, Ordering::Relaxed) - 1; + mod_pr_debug!( + "JobFence[{}]: Command complete (remain: {})\n", + self.id, + remain + ); + if remain == 0 { + mod_pr_debug!("JobFence[{}]: Signaling\n", self.id); + if self.signal().is_err() { + pr_err!("JobFence[{}]: Fence signal failed\n", self.id); + } + } + } +} + +#[versions(AGX)] +#[vtable] +impl dma_fence::FenceOps for JobFence::ver { + fn get_driver_name<'a>(self: &'a FenceObject) -> &'a CStr { + c_str!("asahi") + } + fn get_timeline_name<'a>(self: &'a FenceObject) -> &'a CStr { + c_str!("queue") + } +} + +#[versions(AGX)] +pub(crate) struct QueueJob { + dev: AsahiDevRef, + vm_bind: mmu::VmBind, + op_guard: Option, + sj_vtx: Option, + sj_frag: Option, + sj_comp: Option, + fence: UserFence, + notifier: Arc>, + notification_count: u32, + did_run: bool, + id: u64, +} + +#[versions(AGX)] +impl QueueJob::ver { + fn get_vtx(&mut self) -> Result<&mut workqueue::Job::ver> { + self.sj_vtx + .as_mut() + .ok_or_else(|| { + cls_pr_debug!(Errors, "No vertex queue\n"); + EINVAL + })? + .get() + } + fn get_frag(&mut self) -> Result<&mut workqueue::Job::ver> { + self.sj_frag + .as_mut() + .ok_or_else(|| { + cls_pr_debug!(Errors, "No fragment queue\n"); + EINVAL + })? + .get() + } + fn get_comp(&mut self) -> Result<&mut workqueue::Job::ver> { + self.sj_comp + .as_mut() + .ok_or_else(|| { + cls_pr_debug!(Errors, "No compute queue\n"); + EINVAL + })? + .get() + } + + fn commit(&mut self) -> Result { + mod_dev_dbg!(self.dev, "QueueJob {}: Committing\n", self.id); + + self.sj_vtx.as_mut().map(|a| a.commit()).unwrap_or(Ok(()))?; + self.sj_frag + .as_mut() + .map(|a| a.commit()) + .unwrap_or(Ok(()))?; + self.sj_comp.as_mut().map(|a| a.commit()).unwrap_or(Ok(())) + } +} + +#[versions(AGX)] +impl sched::JobImpl for QueueJob::ver { + fn prepare(job: &mut sched::Job) -> Option { + mod_dev_dbg!(job.dev, "QueueJob {}: Checking runnability\n", job.id); + + if let Some(sj) = job.sj_vtx.as_ref() { + if let Some(fence) = sj.can_submit() { + mod_dev_dbg!( + job.dev, + "QueueJob {}: Blocking due to vertex queue full\n", + job.id + ); + return Some(fence); + } + } + if let Some(sj) = job.sj_frag.as_ref() { + if let Some(fence) = sj.can_submit() { + mod_dev_dbg!( + job.dev, + "QueueJob {}: Blocking due to fragment queue full\n", + job.id + ); + return Some(fence); + } + } + if let Some(sj) = job.sj_comp.as_ref() { + if let Some(fence) = sj.can_submit() { + mod_dev_dbg!( + job.dev, + "QueueJob {}: Blocking due to compute queue full\n", + job.id + ); + return Some(fence); + } + } + None + } + + #[allow(unused_assignments)] + fn run(job: &mut sched::Job) -> Result> { + mod_dev_dbg!(job.dev, "QueueJob {}: Running Job\n", job.id); + + // We can only increase the notifier threshold here, now that we are + // actually running the job. We cannot increase it while queueing the + // job without introducing subtle race conditions. Suppose we did, as + // early versions of drm/asahi did: + // + // 1. When processing the ioctl submit, a job is queued to drm_sched. + // Incorrectly, the notifier threshold is increased, gating firmware + // events. + // 2. When DRM schedules an event, the hardware is kicked. + // 3. When the number of processed jobs equals the threshold, the + // firmware signals the complete event to the kernel + // 4. When the kernel gets a complete event, we signal the out-syncs. + // + // Does that work? There are a few scenarios. + // + // 1. There is nothing else ioctl submitted before the job completes. + // The job is scheduled, completes, and signals immediately. + // Everything works. + // 2. There is nontrivial sync across different queues. Since each queue + // has a separate own notifier threshold, submitting one does not + // block scheduling of the other. Everything works the way you'd + // expect. drm/sched handles the wait/signal ordering. + // 3. Two ioctls are submitted back-to-back. The first signals a fence + // that the second waits on. Due to the notifier threshold increment, + // the first job's completion event is deferred. But in good + // conditions, drm/sched will schedule the second submit anyway + // because it kills the pointless intra-queue sync. Then both + // commands execute and are signalled together. + // 4. Two ioctls are submitted back-to-back as above, but conditions are + // bad. Reporting completion of the first job is still masked by the + // notifier threshold, but the intra-queue fences are not optimized + // out in drm/sched... drm/sched doesn't schedule the second job + // until the first is signalled, but the first isn't signalled until + // the second is completed, but the second can't complete until it's + // scheduled. We hang! + // + // In good conditions, everything works properly and/or we win the race + // to mask the issue. So the issue here is challenging to hit. + // Nevertheless, we do need to get it right. + // + // The intention with drm/sched is that jobs that are not yet scheduled + // are "invisible" to the firmware. Incrementing the notifier threshold + // earlier than this violates that which leads to circles like the + // above. Deferring the increment to submit solves the race. + job.notifier.threshold.with(|raw, _inner| { + raw.increase(job.notification_count); + }); + + let gpu = match (*job.dev) + .gpu + .clone() + .arc_as_any() + .downcast::() + { + Ok(gpu) => gpu, + Err(_) => { + dev_crit!(job.dev.as_ref(), "GpuManager mismatched with QueueJob!\n"); + return Err(EIO); + } + }; + + if job.op_guard.is_none() { + job.op_guard = Some(gpu.start_op()?); + } + + // First submit all the commands for each queue. This can fail. + + let mut frag_job = None; + let mut frag_sub = None; + if let Some(sj) = job.sj_frag.as_mut() { + frag_job = sj.job.take(); + if let Some(wqjob) = frag_job.as_mut() { + mod_dev_dbg!(job.dev, "QueueJob {}: Submit fragment\n", job.id); + frag_sub = Some(wqjob.submit()?); + } + } + + let mut vtx_job = None; + let mut vtx_sub = None; + if let Some(sj) = job.sj_vtx.as_mut() { + vtx_job = sj.job.take(); + if let Some(wqjob) = vtx_job.as_mut() { + mod_dev_dbg!(job.dev, "QueueJob {}: Submit vertex\n", job.id); + vtx_sub = Some(wqjob.submit()?); + } + } + + let mut comp_job = None; + let mut comp_sub = None; + if let Some(sj) = job.sj_comp.as_mut() { + comp_job = sj.job.take(); + if let Some(wqjob) = comp_job.as_mut() { + mod_dev_dbg!(job.dev, "QueueJob {}: Submit compute\n", job.id); + comp_sub = Some(wqjob.submit()?); + } + } + + // Now we fully commit to running the job + mod_dev_dbg!(job.dev, "QueueJob {}: Run fragment\n", job.id); + frag_sub.map(|a| gpu.run_job(a)).transpose()?; + + mod_dev_dbg!(job.dev, "QueueJob {}: Run vertex\n", job.id); + vtx_sub.map(|a| gpu.run_job(a)).transpose()?; + + mod_dev_dbg!(job.dev, "QueueJob {}: Run compute\n", job.id); + comp_sub.map(|a| gpu.run_job(a)).transpose()?; + + mod_dev_dbg!(job.dev, "QueueJob {}: Drop compute job\n", job.id); + core::mem::drop(comp_job); + mod_dev_dbg!(job.dev, "QueueJob {}: Drop vertex job\n", job.id); + core::mem::drop(vtx_job); + mod_dev_dbg!(job.dev, "QueueJob {}: Drop fragment job\n", job.id); + core::mem::drop(frag_job); + + job.did_run = true; + + Ok(Some(Fence::from_fence(&job.fence))) + } + + fn timed_out(job: &mut sched::Job) -> sched::Status { + // FIXME: Handle timeouts properly + dev_err!( + job.dev.as_ref(), + "QueueJob {}: Job timed out on the DRM scheduler, things will probably break (ran: {})\n", + job.id, job.did_run + ); + sched::Status::NoDevice + } + + fn cancel(job: &mut sched::Job) { + dev_info!( + job.dev.as_ref(), + "QueueJob {}: Job canceled on DRM scheduler teardown\n", + job.id + ); + } +} + +#[versions(AGX)] +impl Drop for QueueJob::ver { + fn drop(&mut self) { + mod_dev_dbg!(self.dev, "QueueJob {}: Dropping\n", self.id); + } +} + +static QUEUE_NAME: &CStr = c_str!("asahi_fence"); +static QUEUE_CLASS_KEY: Pin<&LockClassKey> = kernel::static_lock_class!(); + +#[versions(AGX)] +impl Queue::ver { + /// Create a new user queue. + #[allow(clippy::too_many_arguments)] + pub(crate) fn new( + dev: &AsahiDevice, + vm: mmu::Vm, + alloc: &mut gpu::KernelAllocators, + ualloc: Arc>, + ualloc_priv: Arc>, + event_manager: Arc, + mgr: &buffer::BufferManager::ver, + id: u64, + priority: u32, + usc_exec_base: u64, + ) -> Result { + mod_dev_dbg!(dev, "[Queue {}] Creating queue\n", id); + + // Must be shared, no cache management on this one! + let mut notifier_list = alloc.shared.new_default::()?; + + let self_ptr = notifier_list.weak_pointer(); + notifier_list.with_mut(|raw, _inner| { + raw.list_head.next = Some(inner_weak_ptr!(self_ptr, list_head)); + }); + + let threshold = alloc.shared.new_default::()?; + + let notifier: Arc> = Arc::new( + alloc.private.new_init( + /*try_*/ init!(fw::event::Notifier::ver { threshold }), + |inner, _p| { + try_init!(fw::event::raw::Notifier::ver { + threshold: inner.threshold.gpu_pointer(), + generation: AtomicU32::new(id as u32), + cur_count: AtomicU32::new(0), + unk_10: AtomicU32::new(0x50), + state: Default::default() + }) + }, + )?, + GFP_KERNEL, + )?; + + // Priorities are handled by the AGX scheduler, there is no meaning within a + // per-queue scheduler. Use a single run queue wth Kernel priority. + let sched = + sched::Scheduler::new(dev.as_ref(), 1, WQ_SIZE, 0, 100000, c_str!("asahi_sched"))?; + let entity = sched::Entity::new(&sched, sched::Priority::Kernel)?; + + let buffer = + buffer::Buffer::ver::new(&*(*dev).gpu, alloc, ualloc.clone(), ualloc_priv, mgr)?; + + let mut ret = Queue::ver { + dev: dev.into(), + _sched: sched, + entity, + vm, + q_vtx: None, + q_frag: None, + q_comp: None, + fence_ctx: FenceContexts::new(1, QUEUE_NAME, QUEUE_CLASS_KEY)?, + inner: QueueInner::ver { + dev: dev.into(), + ualloc, + gpu_context: Arc::new( + workqueue::GpuContext::new(dev, alloc, buffer.any_ref())?, + GFP_KERNEL, + )?, + + buffer, + notifier_list: Arc::new(notifier_list, GFP_KERNEL)?, + notifier, + usc_exec_base, + id, + #[ver(V >= V13_0B4)] + counter: AtomicU64::new(0), + }, + }; + + // Rendering structures + let tvb_blocks = *module_parameters::initial_tvb_size.value(); + + ret.inner.buffer.ensure_blocks(tvb_blocks)?; + + ret.q_vtx = Some(SubQueue::ver { + wq: workqueue::WorkQueue::ver::new( + dev, + alloc, + event_manager.clone(), + ret.inner.gpu_context.clone(), + ret.inner.notifier_list.clone(), + channel::PipeType::Vertex, + id, + priority, + WQ_SIZE, + )?, + }); + + ret.q_frag = Some(SubQueue::ver { + wq: workqueue::WorkQueue::ver::new( + dev, + alloc, + event_manager.clone(), + ret.inner.gpu_context.clone(), + ret.inner.notifier_list.clone(), + channel::PipeType::Fragment, + id, + priority, + WQ_SIZE, + )?, + }); + + // Compute structures + ret.q_comp = Some(SubQueue::ver { + wq: workqueue::WorkQueue::ver::new( + dev, + alloc, + event_manager, + ret.inner.gpu_context.clone(), + ret.inner.notifier_list.clone(), + channel::PipeType::Compute, + id, + priority, + WQ_SIZE, + )?, + }); + + mod_dev_dbg!(dev, "[Queue {}] Queue created\n", id); + Ok(ret) + } +} + +const SQ_RENDER: usize = 0; +const SQ_COMPUTE: usize = 1; +const SQ_COUNT: usize = 2; + +// SAFETY: All bit patterns are valid by construction. +unsafe impl AnyBitPattern for uapi::drm_asahi_cmd_header {} +unsafe impl AnyBitPattern for uapi::drm_asahi_cmd_render {} +unsafe impl AnyBitPattern for uapi::drm_asahi_cmd_compute {} +unsafe impl AnyBitPattern for uapi::drm_asahi_attachment {} + +fn build_attachments(reader: &mut Reader<'_>, size: usize) -> Result { + const STRIDE: usize = core::mem::size_of::(); + let count = size / STRIDE; + + if count > microseq::MAX_ATTACHMENTS { + return Err(EINVAL); + } + + let mut attachments: microseq::Attachments = Default::default(); + attachments.count = count as u32; + + for i in 0..count { + let att: uapi::drm_asahi_attachment = reader.read()?; + + if att.flags != 0 || att.pad != 0 { + return Err(EINVAL); + } + + // Some kind of power-of-2 exponent related to attachment size, in + // bounds [1, 6]? We don't know what this is exactly yet. + let unk_e = 1; + + let cache_lines = (att.size + 127) >> 7; + attachments.list[i as usize] = microseq::Attachment { + address: U64(att.pointer), + size: cache_lines.try_into()?, + unk_c: 0x17, + unk_e: unk_e as u16, + }; + } + + Ok(attachments) +} + +#[versions(AGX)] +impl Queue for Queue::ver { + fn submit( + &mut self, + id: u64, + mut syncs: KVec, + in_sync_count: usize, + cmdbuf_raw: &[u8], + objects: Pin<&xarray::XArray>>, + ) -> Result { + let gpu = match (*self.dev) + .gpu + .clone() + .arc_as_any() + .downcast::() + { + Ok(gpu) => gpu, + Err(_) => { + dev_crit!(self.dev.as_ref(), "GpuManager mismatched with JobImpl!\n"); + return Err(EIO); + } + }; + + mod_dev_dbg!(self.dev, "[Submission {}] Submit job\n", id); + + if gpu.is_crashed() { + dev_err!( + self.dev.as_ref(), + "[Submission {}] GPU is crashed, cannot submit\n", + id + ); + return Err(ENODEV); + } + + let op_guard = if in_sync_count > 0 { + Some(gpu.start_op()?) + } else { + None + }; + + let mut events: [KVec>; SQ_COUNT] = + Default::default(); + + events[SQ_RENDER].push( + self.q_frag.as_ref().and_then(|a| a.wq.event_info()), + GFP_KERNEL, + )?; + events[SQ_COMPUTE].push( + self.q_comp.as_ref().and_then(|a| a.wq.event_info()), + GFP_KERNEL, + )?; + + let vm_bind = gpu.bind_vm(&self.vm)?; + let vm_slot = vm_bind.slot(); + + mod_dev_dbg!(self.dev, "[Submission {}] Creating job\n", id); + + // FIXME: I think this can violate the fence seqno ordering contract. + // If we have e.g. a render submission with no barriers and then a compute submission + // with no barriers, it's possible for the compute submission to complete first, and + // therefore its fence. Maybe we should have separate fence contexts for render + // and compute, and then do a ? (Vert+frag should be fine since there is no vert + // without frag, and frag always serializes.) + let fence: UserFence = self + .fence_ctx + .new_fence::( + 0, + JobFence::ver { + id, + pending: Default::default(), + }, + )? + .into(); + + let mut cmdbuf = Reader::new(cmdbuf_raw); + + // First, parse the headers to determine the number of compute/render + // commands. This will be used to determine when to flush stamps. + // + // We also use it to determine how many notifications the job will + // generate. We could calculate that in the second pass since we don't + // need until much later, but it's convenient to gather everything at + // the same time. + let mut nr_commands = 0; + let mut last_compute = 0; + let mut last_render = 0; + let mut nr_render = 0; + let mut nr_compute = 0; + + while !cmdbuf.is_empty() { + let header: uapi::drm_asahi_cmd_header = cmdbuf.read()?; + cmdbuf.skip(header.size as usize); + nr_commands += 1; + + match header.cmd_type as u32 { + uapi::drm_asahi_cmd_type_DRM_ASAHI_CMD_RENDER => { + last_compute = nr_commands; + nr_render += 1; + } + uapi::drm_asahi_cmd_type_DRM_ASAHI_CMD_COMPUTE => { + last_render = nr_commands; + nr_compute += 1; + } + _ => {} + } + } + + let mut job = self.entity.new_job( + 1, + QueueJob::ver { + dev: self.dev.clone(), + vm_bind, + op_guard, + sj_vtx: self + .q_vtx + .as_mut() + .map(|a| a.new_job(Fence::from_fence(&fence))), + sj_frag: self + .q_frag + .as_mut() + .map(|a| a.new_job(Fence::from_fence(&fence))), + sj_comp: self + .q_comp + .as_mut() + .map(|a| a.new_job(Fence::from_fence(&fence))), + fence, + notifier: self.inner.notifier.clone(), + + // Each render command generates 2 notifications: 1 for the + // vertex part, 1 for the fragment part. Each compute command + // generates 1 notification. Sum up to calculate the total + // notification count for the job. + notification_count: (2 * nr_render) + nr_compute, + + did_run: false, + id, + }, + )?; + + mod_dev_dbg!( + self.dev, + "[Submission {}] Adding {} in_syncs\n", + id, + in_sync_count + ); + for sync in syncs.drain(0..in_sync_count) { + if let Some(fence) = sync.fence { + job.add_dependency(fence)?; + } + } + + // Validate the number of hardware commands, ignoring software commands + let nr_hw_commands = nr_render + nr_compute; + if nr_hw_commands == 0 || nr_hw_commands > MAX_COMMANDS_PER_SUBMISSION { + cls_pr_debug!( + Errors, + "submit: Command count {} out of valid range [1, {}]\n", + nr_hw_commands, + MAX_COMMANDS_PER_SUBMISSION - 1 + ); + return Err(EINVAL); + } + + cmdbuf.rewind(); + + let mut command_index = 0; + let mut vertex_attachments: microseq::Attachments = Default::default(); + let mut fragment_attachments: microseq::Attachments = Default::default(); + let mut compute_attachments: microseq::Attachments = Default::default(); + + // Parse the full command buffer submitting as we go + while !cmdbuf.is_empty() { + let header: uapi::drm_asahi_cmd_header = cmdbuf.read()?; + let header_size = header.size as usize; + + // Pre-increment command index to match last_compute/last_render + command_index += 1; + + for (queue_idx, index) in [header.vdm_barrier, header.cdm_barrier].iter().enumerate() { + if *index == uapi::DRM_ASAHI_BARRIER_NONE as u16 { + continue; + } + if let Some(event) = events[queue_idx].get(*index as usize).ok_or_else(|| { + cls_pr_debug!(Errors, "Invalid barrier #{}: {}\n", queue_idx, index); + EINVAL + })? { + let mut alloc = gpu.alloc(); + let queue_job = match header.cmd_type as u32 { + uapi::drm_asahi_cmd_type_DRM_ASAHI_CMD_RENDER => job.get_vtx()?, + uapi::drm_asahi_cmd_type_DRM_ASAHI_CMD_COMPUTE => job.get_comp()?, + _ => return Err(EINVAL), + }; + mod_dev_dbg!(self.dev, "[Submission {}] Create Explicit Barrier\n", id); + let barrier = alloc.private.new_init( + pin_init::zeroed::(), + |_inner, _p| { + let queue_job = &queue_job; + try_init!(fw::workqueue::raw::Barrier { + tag: fw::workqueue::CommandType::Barrier, + wait_stamp: event.fw_stamp_pointer, + wait_value: event.value, + wait_slot: event.slot, + stamp_self: queue_job.event_info().value.next(), + uuid: 0xffffbbbb, + external_barrier: 0, + internal_barrier_type: 1, + padding: Default::default(), + }) + }, + )?; + mod_dev_dbg!(self.dev, "[Submission {}] Add Explicit Barrier\n", id); + queue_job.add(barrier, vm_slot)?; + } else { + assert!(*index == 0); + } + } + + match header.cmd_type as u32 { + uapi::drm_asahi_cmd_type_DRM_ASAHI_CMD_RENDER => { + let render: uapi::drm_asahi_cmd_render = cmdbuf.read_up_to(header_size)?; + + self.inner.submit_render( + &mut job, + &render, + &vertex_attachments, + &fragment_attachments, + objects, + id, + command_index == last_render, + )?; + events[SQ_RENDER].push( + Some( + job.sj_frag + .as_ref() + .expect("No frag queue?") + .job + .as_ref() + .expect("No frag job?") + .event_info(), + ), + GFP_KERNEL, + )?; + } + uapi::drm_asahi_cmd_type_DRM_ASAHI_CMD_COMPUTE => { + let compute: uapi::drm_asahi_cmd_compute = cmdbuf.read_up_to(header_size)?; + + self.inner.submit_compute( + &mut job, + &compute, + &compute_attachments, + objects, + id, + command_index == last_compute, + )?; + events[SQ_COMPUTE].push( + Some( + job.sj_comp + .as_ref() + .expect("No comp queue?") + .job + .as_ref() + .expect("No comp job?") + .event_info(), + ), + GFP_KERNEL, + )?; + } + uapi::drm_asahi_cmd_type_DRM_ASAHI_SET_VERTEX_ATTACHMENTS => { + vertex_attachments = build_attachments(&mut cmdbuf, header_size)?; + } + uapi::drm_asahi_cmd_type_DRM_ASAHI_SET_FRAGMENT_ATTACHMENTS => { + fragment_attachments = build_attachments(&mut cmdbuf, header_size)?; + } + uapi::drm_asahi_cmd_type_DRM_ASAHI_SET_COMPUTE_ATTACHMENTS => { + compute_attachments = build_attachments(&mut cmdbuf, header_size)?; + } + _ => { + cls_pr_debug!(Errors, "Unknown command type {}\n", header.cmd_type); + return Err(EINVAL); + } + } + } + + mod_dev_dbg!( + self.dev, + "Queue {}: Committing job {}\n", + self.inner.id, + job.id + ); + job.commit()?; + + mod_dev_dbg!(self.dev, "Queue {}: Arming job {}\n", self.inner.id, job.id); + let mut job = job.arm(); + let out_fence = job.fences().finished(); + mod_dev_dbg!( + self.dev, + "Queue {}: Pushing job {}\n", + self.inner.id, + job.id + ); + job.push(); + + mod_dev_dbg!( + self.dev, + "Queue {}: Adding {} out_syncs\n", + self.inner.id, + syncs.len() + ); + for mut sync in syncs { + if let Some(chain) = sync.chain_fence.take() { + sync.syncobj + .add_point(chain, &out_fence, sync.timeline_value); + } else { + sync.syncobj.replace_fence(Some(&out_fence)); + } + } + + Ok(()) + } +} + +#[versions(AGX)] +impl Drop for Queue::ver { + fn drop(&mut self) { + mod_dev_dbg!(self.dev, "[Queue {}] Dropping queue\n", self.inner.id); + } +} diff --git a/drivers/gpu/drm/asahi/queue/render.rs b/drivers/gpu/drm/asahi/queue/render.rs new file mode 100644 index 00000000000000..32273b2975b505 --- /dev/null +++ b/drivers/gpu/drm/asahi/queue/render.rs @@ -0,0 +1,1400 @@ +// SPDX-License-Identifier: GPL-2.0-only OR MIT +#![allow(clippy::unusual_byte_groupings)] + +//! Render work queue. +//! +//! A render queue consists of two underlying WorkQueues, one for vertex and one for fragment work. +//! This module is in charge of creating all of the firmware structures required to submit 3D +//! rendering work to the GPU, based on the userspace command buffer. + +use super::common; +use crate::alloc::Allocator; +use crate::debug::*; +use crate::fw::types::*; +use crate::gpu::GpuManager; +use crate::util::*; +use crate::{ + buffer, + file, + fw, + gpu, + microseq, // +}; +use crate::{ + inner_ptr, + inner_weak_ptr, // +}; +use core::sync::atomic::Ordering; +use kernel::dma_fence::RawDmaFence; +use kernel::drm::sched::Job; +use kernel::prelude::*; +use kernel::sync::Arc; +use kernel::uapi; +use kernel::xarray; + +const DEBUG_CLASS: DebugFlags = DebugFlags::Render; + +/// Tiling/Vertex control bit to disable using more than one GPU cluster. This results in decreased +/// throughput but also less latency, which is probably desirable for light vertex loads where the +/// overhead of clustering/merging would exceed the time it takes to just run the job on one +/// cluster. +const TILECTL_DISABLE_CLUSTERING: u32 = 1u32 << 0; + +#[versions(AGX)] +impl super::QueueInner::ver { + /// Get the appropriate tiling parameters for a given userspace command buffer. + fn get_tiling_params( + cmdbuf: &uapi::drm_asahi_cmd_render, + num_clusters: u32, + ) -> Result { + let width: u32 = cmdbuf.width_px as u32; + let height: u32 = cmdbuf.height_px as u32; + let layers: u32 = cmdbuf.layers as u32; + + if layers == 0 || layers > 2048 { + cls_pr_debug!(Errors, "Layer count invalid ({})\n", layers); + return Err(EINVAL); + } + + // This is overflow safe: all these calculations are done in u32. + // At 64Kx64K max dimensions above, this is 2**32 pixels max. + // In terms of tiles that are always larger than one pixel, + // this can never overflow. Note that real actual dimensions + // are limited to 16K * 16K below anyway. + // + // Once we multiply by the layer count, then we need to check + // for overflow or use u64. + + let tile_width = 32u32; + let tile_height = 32u32; + + let utile_width = cmdbuf.utile_width_px as u32; + let utile_height = cmdbuf.utile_height_px as u32; + + match (utile_width, utile_height) { + (32, 32) | (32, 16) | (16, 16) => (), + _ => { + cls_pr_debug!( + Errors, + "uTile size invalid ({} x {})\n", + utile_width, + utile_height + ); + return Err(EINVAL); + } + }; + + let utiles_per_tile_x = tile_width / utile_width; + let utiles_per_tile_y = tile_height / utile_height; + + let utiles_per_tile = utiles_per_tile_x * utiles_per_tile_y; + + let tiles_x = width.div_ceil(tile_width); + let tiles_y = height.div_ceil(tile_height); + let tiles = tiles_x * tiles_y; + + let mtiles_x = 4u32; + let mtiles_y = 4u32; + let mtiles = mtiles_x * mtiles_y; + + let tiles_per_mtile_x = align(tiles_x.div_ceil(mtiles_x), 4); + let tiles_per_mtile_y = align(tiles_y.div_ceil(mtiles_y), 4); + let tiles_per_mtile = tiles_per_mtile_x * tiles_per_mtile_y; + + let mtile_x1 = tiles_per_mtile_x; + let mtile_x2 = 2 * tiles_per_mtile_x; + let mtile_x3 = 3 * tiles_per_mtile_x; + + let mtile_y1 = tiles_per_mtile_y; + let mtile_y2 = 2 * tiles_per_mtile_y; + let mtile_y3 = 3 * tiles_per_mtile_y; + + let rgn_entry_size = 5; + // Macrotile stride in 32-bit words + let rgn_size = align(rgn_entry_size * tiles_per_mtile * utiles_per_tile, 4) / 4; + let tilemap_size = (4 * rgn_size * mtiles) as usize * layers as usize; + + let tpc_entry_size = 8; + // TPC stride in 32-bit words + let tpc_mtile_stride = tpc_entry_size * utiles_per_tile * tiles_per_mtile / 4; + let tpc_size = + (4 * tpc_mtile_stride * mtiles) as usize * layers as usize * num_clusters as usize; + + // No idea where this comes from, but it fits what macOS does... + // GUESS: Number of 32K heap blocks to fit a 5-byte region header/pointer per tile? + // That would make a ton of sense... + let meta1_layer_stride = if num_clusters > 1 { + (align(tiles_x, 2) * align(tiles_y, 4) * utiles_per_tile).div_ceil(0x1980) + } else { + 0 + }; + + let mut min_tvb_blocks = align((tiles_x * tiles_y).div_ceil(128), 8); + + if num_clusters > 1 { + min_tvb_blocks = min_tvb_blocks.max(7 + 2 * layers); + } + + Ok(buffer::TileInfo { + tiles_x, + tiles_y, + tiles, + utile_width, + utile_height, + //mtiles_x, + //mtiles_y, + tiles_per_mtile_x, + tiles_per_mtile_y, + //tiles_per_mtile, + utiles_per_mtile_x: tiles_per_mtile_x * utiles_per_tile_x, + utiles_per_mtile_y: tiles_per_mtile_y * utiles_per_tile_y, + //utiles_per_mtile: tiles_per_mtile * utiles_per_tile, + tilemap_size, + tpc_size, + meta1_layer_stride, + #[ver(G < G14X)] + meta1_blocks: meta1_layer_stride * (cmdbuf.layers as u32), + #[ver(G >= G14X)] + meta1_blocks: meta1_layer_stride, + layermeta_size: if layers > 1 { 0x100 } else { 0 }, + min_tvb_blocks: min_tvb_blocks as usize, + params: fw::vertex::raw::TilingParameters { + rgn_size, + unk_4: 0x88, + ppp_ctrl: cmdbuf.ppp_ctrl, + x_max: (width - 1) as u16, + y_max: (height - 1) as u16, + te_screen: ((tiles_y - 1) << 12) | (tiles_x - 1), + te_mtile1: mtile_x3 | (mtile_x2 << 9) | (mtile_x1 << 18), + te_mtile2: mtile_y3 | (mtile_y2 << 9) | (mtile_y1 << 18), + tiles_per_mtile, + tpc_stride: tpc_mtile_stride, + unk_24: 0x100, + unk_28: if layers > 1 { + 0xe000 | (layers - 1) + } else { + 0x8000 + }, + helper_cfg: cmdbuf.vertex_helper.cfg, + __pad: Default::default(), + }, + }) + } + + /// Submit work to a render queue. + pub(super) fn submit_render( + &self, + job: &mut Job, + cmdbuf: &uapi::drm_asahi_cmd_render, + vertex_attachments: µseq::Attachments, + fragment_attachments: µseq::Attachments, + objects: Pin<&xarray::XArray>>, + id: u64, + flush_stamps: bool, + ) -> Result { + mod_dev_dbg!(self.dev, "[Submission {}] Render!\n", id); + + if cmdbuf.flags + & !(uapi::drm_asahi_render_flags_DRM_ASAHI_RENDER_VERTEX_SCRATCH + | uapi::drm_asahi_render_flags_DRM_ASAHI_RENDER_PROCESS_EMPTY_TILES + | uapi::drm_asahi_render_flags_DRM_ASAHI_RENDER_NO_VERTEX_CLUSTERING + | uapi::drm_asahi_render_flags_DRM_ASAHI_RENDER_DBIAS_IS_INT) as u32 + != 0 + { + cls_pr_debug!(Errors, "Invalid flags ({:#x})\n", cmdbuf.flags); + return Err(EINVAL); + } + + if cmdbuf.width_px == 0 + || cmdbuf.height_px == 0 + || cmdbuf.width_px > 16384 + || cmdbuf.height_px > 16384 + { + cls_pr_debug!( + Errors, + "Invalid dimensions ({}x{})\n", + cmdbuf.width_px, + cmdbuf.height_px + ); + return Err(EINVAL); + } + + let mut vtx_user_timestamps: fw::job::UserTimestamps = Default::default(); + let mut frg_user_timestamps: fw::job::UserTimestamps = Default::default(); + + vtx_user_timestamps.start = common::get_timestamp_object(objects, cmdbuf.ts_vtx.start)?; + vtx_user_timestamps.end = common::get_timestamp_object(objects, cmdbuf.ts_vtx.end)?; + frg_user_timestamps.start = common::get_timestamp_object(objects, cmdbuf.ts_frag.start)?; + frg_user_timestamps.end = common::get_timestamp_object(objects, cmdbuf.ts_frag.end)?; + + let gpu = match (*self.dev) + .gpu + .as_any() + .downcast_ref::() + { + Some(gpu) => gpu, + None => { + dev_crit!(self.dev.as_ref(), "GpuManager mismatched with Queue!\n"); + return Err(EIO); + } + }; + + let nclusters = gpu.get_dyncfg().id.num_clusters; + + // Can be set to false to disable clustering (for simpler jobs), but then the + // core masks below should be adjusted to cover a single rolling cluster. + let mut clustering = nclusters > 1; + + if debug_enabled(debug::DebugFlags::DisableClustering) + || cmdbuf.flags + & uapi::drm_asahi_render_flags_DRM_ASAHI_RENDER_NO_VERTEX_CLUSTERING as u32 + != 0 + { + clustering = false; + } + + #[ver(G != G14)] + let tiling_control = { + let render_cfg = gpu.get_cfg().render; + let mut tiling_control = render_cfg.tiling_control; + + if !clustering { + tiling_control |= TILECTL_DISABLE_CLUSTERING; + } + tiling_control + }; + + let mut alloc = gpu.alloc(); + let kalloc = &mut *alloc; + + // This sequence number increases per new client/VM? assigned to some slot, + // but it's unclear *which* slot... + let slot_client_seq: u8 = (self.id & 0xff) as u8; + + let tile_info = Self::get_tiling_params(&cmdbuf, if clustering { nclusters } else { 1 })?; + + let buffer = &self.buffer; + let notifier = self.notifier.clone(); + + let tvb_autogrown = buffer.auto_grow()?; + if tvb_autogrown { + let new_size = buffer.block_count() as usize; + cls_dev_dbg!( + TVBStats, + &self.dev, + "[Submission {}] TVB grew to {} bytes ({} blocks) due to overflows\n", + id, + new_size * buffer::BLOCK_SIZE, + new_size, + ); + } + + let tvb_grown = buffer.ensure_blocks(tile_info.min_tvb_blocks)?; + if tvb_grown { + cls_dev_dbg!( + TVBStats, + &self.dev, + "[Submission {}] TVB grew to {} bytes ({} blocks) due to dimensions ({}x{})\n", + id, + tile_info.min_tvb_blocks * buffer::BLOCK_SIZE, + tile_info.min_tvb_blocks, + cmdbuf.width_px, + cmdbuf.height_px + ); + } + + let scene = Arc::new(buffer.new_scene(kalloc, &tile_info)?, GFP_KERNEL)?; + + let vm_bind = job.vm_bind.clone(); + + mod_dev_dbg!( + self.dev, + "[Submission {}] VM slot = {}\n", + id, + vm_bind.slot() + ); + + let ev_vtx = job.get_vtx()?.event_info(); + let ev_frag = job.get_frag()?.event_info(); + + mod_dev_dbg!( + self.dev, + "[Submission {}] Vert event #{} -> {:#x?}\n", + id, + ev_vtx.slot, + ev_vtx.value.next(), + ); + mod_dev_dbg!( + self.dev, + "[Submission {}] Frag event #{} -> {:#x?}\n", + id, + ev_frag.slot, + ev_frag.value.next(), + ); + + let uuid_3d = 0; + let uuid_ta = 0; + + mod_dev_dbg!( + self.dev, + "[Submission {}] Vert UUID = {:#x?}\n", + id, + uuid_ta + ); + mod_dev_dbg!( + self.dev, + "[Submission {}] Frag UUID = {:#x?}\n", + id, + uuid_3d + ); + + let fence = job.fence.clone(); + let frag_job = job.get_frag()?; + + mod_dev_dbg!(self.dev, "[Submission {}] Create Barrier\n", id); + let barrier = kalloc.private.new_init( + pin_init::zeroed::(), + |_inner, _p| { + try_init!(fw::workqueue::raw::Barrier { + tag: fw::workqueue::CommandType::Barrier, + wait_stamp: ev_vtx.fw_stamp_pointer, + wait_value: ev_vtx.value.next(), + wait_slot: ev_vtx.slot, + stamp_self: ev_frag.value.next(), + uuid: uuid_3d, + external_barrier: 0, + internal_barrier_type: 0, + padding: Default::default(), + }) + }, + )?; + + mod_dev_dbg!(self.dev, "[Submission {}] Add Barrier\n", id); + frag_job.add(barrier, vm_bind.slot())?; + + let timestamps = Arc::new( + kalloc.shared.new_default::()?, + GFP_KERNEL, + )?; + + let unk1 = false; + + let mut tile_config: u64 = 0; + if !unk1 { + tile_config |= 0x280; + } + if cmdbuf.layers > 1 { + tile_config |= 1; + } + if cmdbuf.flags & uapi::drm_asahi_render_flags_DRM_ASAHI_RENDER_PROCESS_EMPTY_TILES as u32 + != 0 + { + tile_config |= 0x10000; + } + + let samples_log2 = match cmdbuf.samples { + 1 => 0, + 2 => 1, + 4 => 2, + _ => { + cls_pr_debug!(Errors, "Invalid sample count {}\n", cmdbuf.samples); + return Err(EINVAL); + } + }; + + let utile_config = ((tile_info.utile_width / 16) << 12) + | ((tile_info.utile_height / 16) << 14) + | samples_log2; + + // Calculate the number of 2KiB blocks to allocate per utile. This is + // just a bit of dimensional analysis. + let pixels_per_utile: u32 = + (cmdbuf.utile_width_px as u32) * (cmdbuf.utile_height_px as u32); + let samples_per_utile: u32 = pixels_per_utile << samples_log2; + let utile_size_bytes: u32 = (cmdbuf.sample_size_B as u32) * samples_per_utile; + let block_size_bytes: u32 = 2048; + let blocks_per_utile: u32 = utile_size_bytes.div_ceil(block_size_bytes); + + #[ver(G >= G14X)] + let frg_tilecfg = 0x0000000_00036011 + | (((tile_info.tiles_x - 1) as u64) << 44) + | (((tile_info.tiles_y - 1) as u64) << 53) + | (if unk1 { 0 } else { 0x20_00000000 }) + | (if cmdbuf.layers > 1 { 0x1_00000000 } else { 0 }) + | ((utile_config as u64 & 0xf000) << 28); + + // TODO: check + #[ver(V >= V13_0B4)] + let count_frag = self.counter.fetch_add(2, Ordering::Relaxed); + #[ver(V >= V13_0B4)] + let count_vtx = count_frag + 1; + + // Unknowns handling + + #[ver(G >= G14)] + let g14_unk = 0x4040404; + #[ver(G < G14)] + let g14_unk = 0; + #[ver(G < G14X)] + let frg_unk_140 = 0x8c60; + let frg_unk_158 = 0x1c; + #[ver(G >= G14)] + let load_bgobjvals = cmdbuf.isp_bgobjvals as u64; + #[ver(G < G14)] + let load_bgobjvals = cmdbuf.isp_bgobjvals as u64 | 0x400; + let reload_zlsctrl = cmdbuf.zls_ctrl; + let iogpu_unk54 = 0x3a0012006b0003; + let iogpu_unk56 = 1; + #[ver(G < G14)] + let tiling_control_2 = 0; + #[ver(G >= G14X)] + let tiling_control_2 = 4; + #[ver(G >= G14X)] + let vtx_unk_f0 = 0x1c; + #[ver(G < G14)] + let vtx_unk_f0 = 0x1c + (align(tile_info.meta1_blocks, 4) as u64); + let vtx_unk_118 = 0x1c; + + // DRM_ASAHI_RENDER_DBIAS_IS_INT chosen to match hardware bit. + let isp_ctl = 0xc000u32 + | (cmdbuf.flags & uapi::drm_asahi_render_flags_DRM_ASAHI_RENDER_DBIAS_IS_INT as u32); + + // Always allow preemption at the UAPI level + let no_preemption = false; + + mod_dev_dbg!(self.dev, "[Submission {}] Create Frag\n", id); + let frag = GpuObject::new_init_prealloc( + kalloc.gpu_ro.alloc_object()?, + |ptr: GpuWeakPointer| { + let scene = scene.clone(); + let notifier = notifier.clone(); + let vm_bind = vm_bind.clone(); + let timestamps = timestamps.clone(); + let private = &mut kalloc.private; + try_init!(fw::fragment::RunFragment::ver { + micro_seq: { + let mut builder = microseq::Builder::new(); + + let stats = inner_weak_ptr!( + gpu.initdata.runtime_pointers.stats.frag.weak_pointer(), + stats + ); + + let start_frag = builder.add(microseq::StartFragment::ver { + header: microseq::op::StartFragment::HEADER, + #[ver(G < G14X)] + job_params2: Some(inner_weak_ptr!(ptr, job_params2)), + #[ver(G < G14X)] + job_params1: Some(inner_weak_ptr!(ptr, job_params1)), + #[ver(G >= G14X)] + job_params1: None, + #[ver(G >= G14X)] + job_params2: None, + #[ver(G >= G14X)] + registers: inner_weak_ptr!(ptr, registers), + scene: scene.gpu_pointer(), + stats, + busy_flag: inner_weak_ptr!(ptr, busy_flag), + tvb_overflow_count: inner_weak_ptr!(ptr, tvb_overflow_count), + unk_pointer: inner_weak_ptr!(ptr, unk_pointee), + work_queue: ev_frag.info_ptr, + work_item: ptr, + vm_slot: vm_bind.slot(), + unk_50: 0x1, // fixed + event_generation: self.id as u32, + buffer_slot: scene.slot(), + sync_grow: 0, + event_seq: U64(ev_frag.event_seq), + unk_68: 0, + unk_758_flag: inner_weak_ptr!(ptr, unk_758_flag), + unk_job_buf: inner_weak_ptr!(ptr, unk_buf_0), + #[ver(V >= V13_3)] + unk_7c_0: U64(0), + unk_7c: 0, + unk_80: 0, + unk_84: unk1.into(), + uuid: uuid_3d, + attachments: *fragment_attachments, + padding: 0, + #[ver(V >= V13_0B4)] + counter: U64(count_frag), + #[ver(V >= V13_0B4)] + notifier_buf: inner_weak_ptr!(notifier.weak_pointer(), state.unk_buf), + })?; + + if frg_user_timestamps.any() { + builder.add(microseq::Timestamp::ver { + header: microseq::op::Timestamp::new(true), + command_time: inner_weak_ptr!(ptr, command_time), + ts_pointers: inner_weak_ptr!(ptr, timestamp_pointers), + update_ts: inner_weak_ptr!(ptr, timestamp_pointers.start_addr), + work_queue: ev_frag.info_ptr, + user_ts_pointers: inner_weak_ptr!(ptr, user_timestamp_pointers), + #[ver(V >= V13_0B4)] + unk_ts: inner_weak_ptr!(ptr, unk_ts), + uuid: uuid_3d, + unk_30_padding: 0, + })?; + } + + #[ver(G < G14X)] + builder.add(microseq::WaitForIdle { + header: microseq::op::WaitForIdle::new(microseq::Pipe::Fragment), + })?; + #[ver(G >= G14X)] + builder.add(microseq::WaitForIdle2 { + header: microseq::op::WaitForIdle2::HEADER, + })?; + + if frg_user_timestamps.any() { + builder.add(microseq::Timestamp::ver { + header: microseq::op::Timestamp::new(false), + command_time: inner_weak_ptr!(ptr, command_time), + ts_pointers: inner_weak_ptr!(ptr, timestamp_pointers), + update_ts: inner_weak_ptr!(ptr, timestamp_pointers.end_addr), + work_queue: ev_frag.info_ptr, + user_ts_pointers: inner_weak_ptr!(ptr, user_timestamp_pointers), + #[ver(V >= V13_0B4)] + unk_ts: inner_weak_ptr!(ptr, unk_ts), + uuid: uuid_3d, + unk_30_padding: 0, + })?; + } + + let off = builder.offset_to(start_frag); + builder.add(microseq::FinalizeFragment::ver { + header: microseq::op::FinalizeFragment::HEADER, + uuid: uuid_3d, + unk_8: 0, + fw_stamp: ev_frag.fw_stamp_pointer, + stamp_value: ev_frag.value.next(), + unk_18: 0, + scene: scene.weak_pointer(), + buffer: scene.weak_buffer_pointer(), + unk_2c: U64(1), + stats, + unk_pointer: inner_weak_ptr!(ptr, unk_pointee), + busy_flag: inner_weak_ptr!(ptr, busy_flag), + work_queue: ev_frag.info_ptr, + work_item: ptr, + vm_slot: vm_bind.slot(), + unk_60: 0, + unk_758_flag: inner_weak_ptr!(ptr, unk_758_flag), + #[ver(V >= V13_3)] + unk_6c_0: U64(0), + unk_6c: U64(0), + unk_74: U64(0), + unk_7c: U64(0), + unk_84: U64(0), + unk_8c: U64(0), + #[ver(G == G14 && V < V13_0B4)] + unk_8c_g14: U64(0), + restart_branch_offset: off, + has_attachments: (fragment_attachments.count > 0) as u32, + #[ver(V >= V13_0B4)] + unk_9c: Default::default(), + })?; + + builder.add(microseq::RetireStamp { + header: microseq::op::RetireStamp::HEADER, + })?; + + builder.build(private)? + }, + notifier, + scene, + vm_bind, + aux_fb: self.ualloc.lock().array_empty_tagged(0x8000, b"AXFB")?, + timestamps, + user_timestamps: frg_user_timestamps, + }) + }, + |inner, _ptr| { + let vm_slot = vm_bind.slot(); + let aux_fb_info = fw::fragment::raw::AuxFBInfo::ver { + isp_ctl: isp_ctl, + unk2: 0, + width: cmdbuf.width_px as u32, + height: cmdbuf.height_px as u32, + #[ver(V >= V13_0B4)] + unk3: U64(0x100000), + }; + + try_init!(fw::fragment::raw::RunFragment::ver { + tag: fw::workqueue::CommandType::RunFragment, + #[ver(V >= V13_0B4)] + counter: U64(count_frag), + vm_slot, + unk_8: 0, + microsequence: inner.micro_seq.gpu_pointer(), + microsequence_size: inner.micro_seq.len() as u32, + notifier: inner.notifier.gpu_pointer(), + buffer: inner.scene.buffer_pointer(), + scene: inner.scene.gpu_pointer(), + unk_buffer_buf: inner.scene.kernel_buffer_pointer(), + tvb_tilemap: inner.scene.tvb_tilemap_pointer(), + ppp_multisamplectl: U64(cmdbuf.ppp_multisamplectl), + samples: cmdbuf.samples as u32, + tiles_per_mtile_y: tile_info.tiles_per_mtile_y as u16, + tiles_per_mtile_x: tile_info.tiles_per_mtile_x as u16, + unk_50: U64(0), + unk_58: U64(0), + isp_merge_upper_x: F32::from_bits(cmdbuf.isp_merge_upper_x), + isp_merge_upper_y: F32::from_bits(cmdbuf.isp_merge_upper_y), + unk_68: U64(0), + tile_count: U64(tile_info.tiles as u64), + #[ver(G < G14X)] + job_params1 <- try_init!(fw::fragment::raw::JobParameters1::ver { + utile_config, + unk_4: 0, + bg: fw::fragment::raw::BackgroundProgram { + rsrc_spec: U64(cmdbuf.bg.rsrc_spec as u64), + address: U64(cmdbuf.bg.usc as u64), + }, + ppp_multisamplectl: U64(cmdbuf.ppp_multisamplectl), + isp_scissor_base: U64(cmdbuf.isp_scissor_base), + isp_dbias_base: U64(cmdbuf.isp_dbias_base), + isp_oclqry_base: U64(cmdbuf.isp_oclqry_base), + aux_fb_info, + isp_zls_pixels: U64(cmdbuf.isp_zls_pixels as u64), + zls_ctrl: U64(cmdbuf.zls_ctrl), + #[ver(G >= G14)] + unk_58_g14_0: U64(g14_unk), + #[ver(G >= G14)] + unk_58_g14_8: U64(0), + z_load: U64(cmdbuf.depth.base), + z_store: U64(cmdbuf.depth.base), + s_load: U64(cmdbuf.stencil.base), + s_store: U64(cmdbuf.stencil.base), + #[ver(G >= G14)] + unk_68_g14_0: Default::default(), + z_load_stride: U64(cmdbuf.depth.stride as u64), + z_store_stride: U64(cmdbuf.depth.stride as u64), + s_load_stride: U64(cmdbuf.stencil.stride as u64), + s_store_stride: U64(cmdbuf.stencil.stride as u64), + z_load_comp: U64(cmdbuf.depth.comp_base), + z_load_comp_stride: U64(cmdbuf.depth.comp_stride as u64), + z_store_comp: U64(cmdbuf.depth.comp_base), + z_store_comp_stride: U64(cmdbuf.depth.comp_stride as u64), + s_load_comp: U64(cmdbuf.stencil.comp_base), + s_load_comp_stride: U64(cmdbuf.stencil.comp_stride as u64), + s_store_comp: U64(cmdbuf.stencil.comp_base), + s_store_comp_stride: U64(cmdbuf.stencil.comp_stride as u64), + tvb_tilemap: inner.scene.tvb_tilemap_pointer(), + tvb_layermeta: inner.scene.tvb_layermeta_pointer(), + mtile_stride_dwords: U64((4 * tile_info.params.rgn_size as u64) << 24), + tvb_heapmeta: inner.scene.tvb_heapmeta_pointer(), + tile_config: U64(tile_config), + aux_fb: inner.aux_fb.gpu_pointer(), + unk_108: Default::default(), + usc_exec_base_isp: U64(self.usc_exec_base), + unk_140: U64(frg_unk_140), + helper_program: cmdbuf.fragment_helper.binary, + unk_14c: 0, + helper_arg: U64(cmdbuf.fragment_helper.data), + unk_158: U64(frg_unk_158), + unk_160: U64(0), + __pad: Default::default(), + #[ver(V < V13_0B4)] + __pad1: Default::default(), + }), + #[ver(G < G14X)] + job_params2 <- try_init!(fw::fragment::raw::JobParameters2 { + eot_rsrc_spec: cmdbuf.eot.rsrc_spec, + eot_usc: cmdbuf.eot.usc, + unk_8: 0x0, + unk_c: 0x0, + isp_merge_upper_x: F32::from_bits(cmdbuf.isp_merge_upper_x), + isp_merge_upper_y: F32::from_bits(cmdbuf.isp_merge_upper_y), + unk_18: U64(0x0), + utiles_per_mtile_y: tile_info.utiles_per_mtile_y as u16, + utiles_per_mtile_x: tile_info.utiles_per_mtile_x as u16, + unk_24: 0x0, + tile_counts: ((tile_info.tiles_y - 1) << 12) | (tile_info.tiles_x - 1), + tib_blocks: blocks_per_utile, + isp_bgobjdepth: cmdbuf.isp_bgobjdepth, + // TODO: does this flag need to be exposed to userspace? + isp_bgobjvals: load_bgobjvals as u32, + unk_38: 0x0, + unk_3c: 0x1, + helper_cfg: cmdbuf.fragment_helper.cfg, + __pad: Default::default(), + }), + #[ver(G >= G14X)] + registers: fw::job::raw::RegisterArray::new( + inner_weak_ptr!(_ptr, registers.registers), + |r| { + r.add(0x1739, 1); + r.add(0x10009, utile_config.into()); + r.add(0x15379, cmdbuf.eot.rsrc_spec.into()); + r.add(0x15381, cmdbuf.eot.usc.into()); + r.add(0x15369, cmdbuf.bg.rsrc_spec.into()); + r.add(0x15371, cmdbuf.bg.usc.into()); + r.add(0x15131, cmdbuf.isp_merge_upper_x.into()); + r.add(0x15139, cmdbuf.isp_merge_upper_y.into()); + r.add(0x100a1, 0); + r.add(0x15069, 0); + r.add(0x15071, 0); // pointer + r.add(0x16058, 0); + r.add(0x10019, cmdbuf.ppp_multisamplectl); + let isp_mtile_size = (tile_info.utiles_per_mtile_y + | (tile_info.utiles_per_mtile_x << 16)) + .into(); + r.add(0x100b1, isp_mtile_size); // ISP_MTILE_SIZE + r.add(0x16030, isp_mtile_size); // ISP_MTILE_SIZE + r.add( + 0x100d9, + (((tile_info.tiles_y - 1) << 12) | (tile_info.tiles_x - 1)).into(), + ); // TE_SCREEN + r.add(0x16098, inner.scene.tvb_heapmeta_pointer().into()); + r.add(0x15109, cmdbuf.isp_scissor_base); // ISP_SCISSOR_BASE + r.add(0x15101, cmdbuf.isp_dbias_base); // ISP_DBIAS_BASE + r.add(0x15021, isp_ctl.into()); // aux_fb_info.unk_1 + r.add( + 0x15211, + ((cmdbuf.height_px as u64) << 32) | cmdbuf.width_px as u64, + ); // aux_fb_info.{width, heigh + r.add(0x15049, 0x100000); // s2.aux_fb_info.unk3 + r.add(0x10051, blocks_per_utile.into()); // s1.unk_2c + r.add(0x15321, cmdbuf.isp_zls_pixels.into()); // ISP_ZLS_PIXELS + r.add(0x15301, cmdbuf.isp_bgobjdepth.into()); // ISP_BGOBJDEPTH + r.add(0x15309, load_bgobjvals); // ISP_BGOBJVALS + r.add(0x15311, cmdbuf.isp_oclqry_base); // ISP_OCLQRY_BASE + r.add(0x15319, cmdbuf.zls_ctrl); // ISP_ZLSCTL + r.add(0x15349, g14_unk); // s2.unk_58_g14_0 + r.add(0x15351, 0); // s2.unk_58_g14_8 + r.add(0x15329, cmdbuf.depth.base); // ISP_ZLOAD_BASE + r.add(0x15331, cmdbuf.depth.base); // ISP_ZSTORE_BASE + r.add(0x15339, cmdbuf.stencil.base); // ISP_STENCIL_LOAD_BASE + r.add(0x15341, cmdbuf.stencil.base); // ISP_STENCIL_STORE_BASE + r.add(0x15231, 0); + r.add(0x15221, 0); + r.add(0x15239, 0); + r.add(0x15229, 0); + r.add(0x15401, cmdbuf.depth.stride as u64); // load + r.add(0x15421, cmdbuf.depth.stride as u64); // store + r.add(0x15409, cmdbuf.stencil.stride as u64); // load + r.add(0x15429, cmdbuf.stencil.stride as u64); + r.add(0x153c1, cmdbuf.depth.comp_base); // load + r.add(0x15411, cmdbuf.depth.comp_stride as u64); // load + r.add(0x153c9, cmdbuf.depth.comp_base); // store + r.add(0x15431, cmdbuf.depth.comp_stride as u64); // store + r.add(0x153d1, cmdbuf.stencil.comp_base); // load + r.add(0x15419, cmdbuf.stencil.comp_stride as u64); // load + r.add(0x153d9, cmdbuf.stencil.comp_base); // store + r.add(0x15439, cmdbuf.stencil.comp_stride as u64); // store + r.add(0x16429, inner.scene.tvb_tilemap_pointer().into()); + r.add(0x16060, inner.scene.tvb_layermeta_pointer().into()); + r.add(0x16431, (4 * tile_info.params.rgn_size as u64) << 24); // ISP_RGN? + r.add(0x10039, tile_config); // tile_config ISP_CTL? + r.add(0x16451, 0x0); // ISP_RENDER_ORIGIN + r.add(0x11821, cmdbuf.fragment_helper.binary.into()); + r.add(0x11829, cmdbuf.fragment_helper.data); + r.add(0x11f79, cmdbuf.fragment_helper.cfg.into()); + r.add(0x15359, 0); + r.add(0x10069, self.usc_exec_base); // frag; USC_EXEC_BASE_ISP + r.add(0x16020, 0); + r.add(0x16461, inner.aux_fb.gpu_pointer().into()); + r.add(0x16090, inner.aux_fb.gpu_pointer().into()); + r.add(0x120a1, frg_unk_158); + r.add(0x160a8, 0); + r.add(0x16068, frg_tilecfg); + r.add(0x160b8, 0x0); + /* + r.add(0x10201, 0x100); // Some kind of counter?? Does this matter? + r.add(0x10428, 0x100); // Some kind of counter?? Does this matter? + r.add(0x1c838, 1); // ? + r.add(0x1ca28, 0x1502960f00); // ?? + r.add(0x1731, 0x1); // ?? + */ + } + ), + job_params3 <- try_init!(fw::fragment::raw::JobParameters3::ver { + isp_dbias_base: fw::fragment::raw::ArrayAddr { + ptr: U64(cmdbuf.isp_dbias_base), + unk_padding: U64(0), + }, + isp_scissor_base: fw::fragment::raw::ArrayAddr { + ptr: U64(cmdbuf.isp_scissor_base), + unk_padding: U64(0), + }, + isp_oclqry_base: U64(cmdbuf.isp_oclqry_base), + unk_118: U64(0x0), + unk_120: Default::default(), + unk_partial_bg: fw::fragment::raw::BackgroundProgram { + rsrc_spec: U64(cmdbuf.partial_bg.rsrc_spec as u64), + address: U64(cmdbuf.partial_bg.usc as u64), + }, + unk_258: U64(0), + unk_260: U64(0), + unk_268: U64(0), + unk_270: U64(0), + partial_bg: fw::fragment::raw::BackgroundProgram { + rsrc_spec: U64(cmdbuf.partial_bg.rsrc_spec as u64), + address: U64(cmdbuf.partial_bg.usc as u64), + }, + zls_ctrl: U64(reload_zlsctrl), + unk_290: U64(g14_unk), + z_load: U64(cmdbuf.depth.base), + z_partial_stride: U64(cmdbuf.depth.stride as u64), + z_partial_comp_stride: U64(cmdbuf.depth.comp_stride as u64), + z_store: U64(cmdbuf.depth.base), + z_partial: U64(cmdbuf.depth.base), + z_partial_comp: U64(cmdbuf.depth.comp_base), + s_load: U64(cmdbuf.stencil.base), + s_partial_stride: U64(cmdbuf.stencil.stride as u64), + s_partial_comp_stride: U64(cmdbuf.stencil.comp_stride as u64), + s_store: U64(cmdbuf.stencil.base), + s_partial: U64(cmdbuf.stencil.base), + s_partial_comp: U64(cmdbuf.stencil.comp_base), + unk_2f8: Default::default(), + tib_blocks: blocks_per_utile, + unk_30c: 0x0, + aux_fb_info, + tile_config: U64(tile_config), + unk_328_padding: Default::default(), + unk_partial_eot: fw::fragment::raw::EotProgram::new( + cmdbuf.partial_eot.rsrc_spec, + cmdbuf.partial_eot.usc + ), + partial_eot: fw::fragment::raw::EotProgram::new( + cmdbuf.partial_eot.rsrc_spec, + cmdbuf.partial_eot.usc + ), + isp_bgobjdepth: cmdbuf.isp_bgobjdepth, + isp_bgobjvals: cmdbuf.isp_bgobjvals, + sample_size: cmdbuf.sample_size_B as u32, + unk_37c: 0x0, + unk_380: U64(0x0), + unk_388: U64(0x0), + #[ver(V >= V13_0B4)] + unk_390_0: U64(0x0), + isp_zls_pixels: U64(cmdbuf.isp_zls_pixels as u64), + }), + unk_758_flag: 0, + unk_75c_flag: 0, + unk_buf: Default::default(), + busy_flag: 0, + tvb_overflow_count: 0, + unk_878: 0, + encoder_params <- try_init!(fw::job::raw::EncoderParams { + // Maybe set when reloading z/s? + unk_8: 0, + sync_grow: 0, + unk_10: 0x0, // fixed + encoder_id: 0, + unk_18: 0x0, // fixed + unk_mask: 0xffffffffu32, + sampler_array: U64(cmdbuf.sampler_heap), + sampler_count: cmdbuf.sampler_count as u32, + sampler_max: (cmdbuf.sampler_count as u32) + 1, + }), + process_empty_tiles: (cmdbuf.flags + & uapi::drm_asahi_render_flags_DRM_ASAHI_RENDER_PROCESS_EMPTY_TILES as u32 + != 0) as u32, + // TODO: needs to be investigated + no_clear_pipeline_textures: 1, + // TODO: needs to be investigated + msaa_zs: 0, + unk_pointee: 0, + #[ver(V >= V13_3)] + unk_v13_3: 0, + meta <- try_init!(fw::job::raw::JobMeta { + unk_0: 0, + unk_2: 0, + no_preemption: no_preemption as u8, + stamp: ev_frag.stamp_pointer, + fw_stamp: ev_frag.fw_stamp_pointer, + stamp_value: ev_frag.value.next(), + stamp_slot: ev_frag.slot, + evctl_index: 0, // fixed + flush_stamps: flush_stamps as u32, + uuid: uuid_3d, + event_seq: ev_frag.event_seq as u32, + }), + unk_after_meta: unk1.into(), + unk_buf_0: U64(0), + unk_buf_8: U64(0), + #[ver(G < G14X)] + unk_buf_10: U64(1), + #[ver(G >= G14X)] + unk_buf_10: U64(0), + command_time: U64(0), + timestamp_pointers <- try_init!(fw::job::raw::TimestampPointers { + start_addr: Some(inner_ptr!(inner.timestamps.gpu_pointer(), frag.start)), + end_addr: Some(inner_ptr!(inner.timestamps.gpu_pointer(), frag.end)), + }), + user_timestamp_pointers: inner.user_timestamps.pointers()?, + client_sequence: slot_client_seq, + pad_925: Default::default(), + unk_928: 0, + unk_92c: 0, + #[ver(V >= V13_0B4)] + unk_ts: U64(0), + #[ver(V >= V13_0B4)] + unk_92d_8: Default::default(), + }) + }, + )?; + + mod_dev_dbg!(self.dev, "[Submission {}] Add Frag\n", id); + fence.add_command(); + + frag_job.add_cb(frag, vm_bind.slot(), move |error| { + if let Some(err) = error { + fence.set_error(err.into()); + } + + fence.command_complete(); + })?; + + let fence = job.fence.clone(); + let vtx_job = job.get_vtx()?; + + if scene.rebind() || tvb_grown || tvb_autogrown { + mod_dev_dbg!(self.dev, "[Submission {}] Create Bind Buffer\n", id); + let bind_buffer = kalloc.private.new_init( + { + let scene = scene.clone(); + try_init!(fw::buffer::InitBuffer::ver { scene }) + }, + |inner, _ptr| { + let vm_slot = vm_bind.slot(); + try_init!(fw::buffer::raw::InitBuffer::ver { + tag: fw::workqueue::CommandType::InitBuffer, + vm_slot, + buffer_slot: inner.scene.slot(), + unk_c: 0, + block_count: buffer.block_count(), + buffer: inner.scene.buffer_pointer(), + stamp_value: ev_vtx.value.next(), + }) + }, + )?; + + mod_dev_dbg!(self.dev, "[Submission {}] Add Bind Buffer\n", id); + vtx_job.add(bind_buffer, vm_bind.slot())?; + } + + mod_dev_dbg!(self.dev, "[Submission {}] Create Vertex\n", id); + let vtx = GpuObject::new_init_prealloc( + kalloc.gpu_ro.alloc_object()?, + |ptr: GpuWeakPointer| { + let scene = scene.clone(); + let vm_bind = vm_bind.clone(); + let timestamps = timestamps.clone(); + let private = &mut kalloc.private; + try_init!(fw::vertex::RunVertex::ver { + micro_seq: { + let mut builder = microseq::Builder::new(); + + let stats = inner_weak_ptr!( + gpu.initdata.runtime_pointers.stats.vtx.weak_pointer(), + stats + ); + + let start_vtx = builder.add(microseq::StartVertex::ver { + header: microseq::op::StartVertex::HEADER, + #[ver(G < G14X)] + tiling_params: Some(inner_weak_ptr!(ptr, tiling_params)), + #[ver(G < G14X)] + job_params1: Some(inner_weak_ptr!(ptr, job_params1)), + #[ver(G >= G14X)] + tiling_params: None, + #[ver(G >= G14X)] + job_params1: None, + #[ver(G >= G14X)] + registers: inner_weak_ptr!(ptr, registers), + buffer: scene.weak_buffer_pointer(), + scene: scene.weak_pointer(), + stats, + work_queue: ev_vtx.info_ptr, + vm_slot: vm_bind.slot(), + unk_38: 1, // fixed + event_generation: self.id as u32, + buffer_slot: scene.slot(), + unk_44: 0, + event_seq: U64(ev_vtx.event_seq), + unk_50: 0, + unk_pointer: inner_weak_ptr!(ptr, unk_pointee), + unk_job_buf: inner_weak_ptr!(ptr, unk_buf_0), + unk_64: 0x0, // fixed + unk_68: unk1.into(), + uuid: uuid_ta, + attachments: *vertex_attachments, + padding: 0, + #[ver(V >= V13_0B4)] + counter: U64(count_vtx), + #[ver(V >= V13_0B4)] + notifier_buf: inner_weak_ptr!(notifier.weak_pointer(), state.unk_buf), + #[ver(V < V13_0B4)] + unk_178: 0x0, // padding? + #[ver(V >= V13_0B4)] + unk_178: (!clustering) as u32, + })?; + + if vtx_user_timestamps.any() { + builder.add(microseq::Timestamp::ver { + header: microseq::op::Timestamp::new(true), + command_time: inner_weak_ptr!(ptr, command_time), + ts_pointers: inner_weak_ptr!(ptr, timestamp_pointers), + update_ts: inner_weak_ptr!(ptr, timestamp_pointers.start_addr), + work_queue: ev_vtx.info_ptr, + user_ts_pointers: inner_weak_ptr!(ptr, user_timestamp_pointers), + #[ver(V >= V13_0B4)] + unk_ts: inner_weak_ptr!(ptr, unk_ts), + uuid: uuid_ta, + unk_30_padding: 0, + })?; + } + + #[ver(G < G14X)] + builder.add(microseq::WaitForIdle { + header: microseq::op::WaitForIdle::new(microseq::Pipe::Vertex), + })?; + #[ver(G >= G14X)] + builder.add(microseq::WaitForIdle2 { + header: microseq::op::WaitForIdle2::HEADER, + })?; + + if vtx_user_timestamps.any() { + builder.add(microseq::Timestamp::ver { + header: microseq::op::Timestamp::new(false), + command_time: inner_weak_ptr!(ptr, command_time), + ts_pointers: inner_weak_ptr!(ptr, timestamp_pointers), + update_ts: inner_weak_ptr!(ptr, timestamp_pointers.end_addr), + work_queue: ev_vtx.info_ptr, + user_ts_pointers: inner_weak_ptr!(ptr, user_timestamp_pointers), + #[ver(V >= V13_0B4)] + unk_ts: inner_weak_ptr!(ptr, unk_ts), + uuid: uuid_ta, + unk_30_padding: 0, + })?; + } + + let off = builder.offset_to(start_vtx); + builder.add(microseq::FinalizeVertex::ver { + header: microseq::op::FinalizeVertex::HEADER, + scene: scene.weak_pointer(), + buffer: scene.weak_buffer_pointer(), + stats, + work_queue: ev_vtx.info_ptr, + vm_slot: vm_bind.slot(), + unk_28: 0x0, // fixed + unk_pointer: inner_weak_ptr!(ptr, unk_pointee), + unk_34: 0x0, // fixed + uuid: uuid_ta, + fw_stamp: ev_vtx.fw_stamp_pointer, + stamp_value: ev_vtx.value.next(), + unk_48: U64(0x0), // fixed + unk_50: 0x0, // fixed + unk_54: 0x0, // fixed + unk_58: U64(0x0), // fixed + unk_60: 0x0, // fixed + unk_64: 0x0, // fixed + unk_68: 0x0, // fixed + #[ver(G >= G14 && V < V13_0B4)] + unk_68_g14: U64(0), + restart_branch_offset: off, + has_attachments: (vertex_attachments.count > 0) as u32, + #[ver(V >= V13_0B4)] + unk_74: Default::default(), // Ventura + })?; + + builder.add(microseq::RetireStamp { + header: microseq::op::RetireStamp::HEADER, + })?; + builder.build(private)? + }, + notifier, + scene, + vm_bind, + timestamps, + user_timestamps: vtx_user_timestamps, + }) + }, + |inner, _ptr| { + let vm_slot = vm_bind.slot(); + #[ver(G < G14)] + let core_masks = gpu.core_masks_packed(); + + try_init!(fw::vertex::raw::RunVertex::ver { + tag: fw::workqueue::CommandType::RunVertex, + #[ver(V >= V13_0B4)] + counter: U64(count_vtx), + vm_slot, + unk_8: 0, + notifier: inner.notifier.gpu_pointer(), + buffer_slot: inner.scene.slot(), + unk_1c: 0, + buffer: inner.scene.buffer_pointer(), + scene: inner.scene.gpu_pointer(), + unk_buffer_buf: inner.scene.kernel_buffer_pointer(), + unk_34: 0, + #[ver(G < G14X)] + job_params1 <- try_init!(fw::vertex::raw::JobParameters1::ver { + unk_0: U64(if unk1 { 0 } else { 0x200 }), // sometimes 0 + unk_8: f32!(1e-20), // fixed + unk_c: f32!(1e-20), // fixed + tvb_tilemap: inner.scene.tvb_tilemap_pointer(), + #[ver(G < G14)] + tvb_cluster_tilemaps: inner.scene.cluster_tilemaps_pointer(), + tpc: inner.scene.tpc_pointer(), + tvb_heapmeta: inner.scene.tvb_heapmeta_pointer().or(0x8000_0000_0000_0000), + iogpu_unk_54: U64(iogpu_unk54), // fixed + iogpu_unk_56: U64(iogpu_unk56), // fixed + #[ver(G < G14)] + tvb_cluster_meta1: inner + .scene + .meta_1_pointer() + .map(|x| x.or((tile_info.meta1_layer_stride as u64) << 50)), + utile_config, + unk_4c: 0, + ppp_multisamplectl: U64(cmdbuf.ppp_multisamplectl), // fixed + tvb_layermeta: inner.scene.tvb_layermeta_pointer(), + #[ver(G < G14)] + tvb_cluster_layermeta: inner.scene.tvb_cluster_layermeta_pointer(), + #[ver(G < G14)] + core_mask: Array::new([ + *core_masks.first().unwrap_or(&0), + *core_masks.get(1).unwrap_or(&0), + ]), + preempt_buf1: inner.scene.preempt_buf_1_pointer(), + preempt_buf2: inner.scene.preempt_buf_2_pointer(), + unk_80: U64(0x1), // fixed + preempt_buf3: inner.scene.preempt_buf_3_pointer().or(0x4_0000_0000_0000), // check + vdm_ctrl_stream_base: U64(cmdbuf.vdm_ctrl_stream_base), + #[ver(G < G14)] + tvb_cluster_meta2: inner.scene.meta_2_pointer(), + #[ver(G < G14)] + tvb_cluster_meta3: inner.scene.meta_3_pointer(), + #[ver(G < G14)] + tiling_control, + #[ver(G < G14)] + unk_ac: tiling_control_2 as u32, // fixed + unk_b0: Default::default(), // fixed + usc_exec_base_ta: U64(self.usc_exec_base), + #[ver(G < G14)] + tvb_cluster_meta4: inner + .scene + .meta_4_pointer() + .map(|x| x.or(0x3000_0000_0000_0000)), + #[ver(G < G14)] + unk_f0: U64(vtx_unk_f0), + unk_f8: U64(0x8c60), // fixed + helper_program: cmdbuf.vertex_helper.binary, + unk_104: 0, + helper_arg: U64(cmdbuf.vertex_helper.data), + unk_110: Default::default(), // fixed + unk_118: vtx_unk_118 as u32, // fixed + __pad: Default::default(), + }), + #[ver(G < G14X)] + tiling_params: tile_info.params, + #[ver(G >= G14X)] + registers: fw::job::raw::RegisterArray::new( + inner_weak_ptr!(_ptr, registers.registers), + |r| { + r.add(0x10141, if unk1 { 0 } else { 0x200 }); // s2.unk_0 + r.add(0x1c039, inner.scene.tvb_tilemap_pointer().into()); + r.add(0x1c9c8, inner.scene.tvb_tilemap_pointer().into()); + + let cl_tilemaps_ptr = inner + .scene + .cluster_tilemaps_pointer() + .map_or(0, |a| a.into()); + r.add(0x1c041, cl_tilemaps_ptr); + r.add(0x1c9d0, cl_tilemaps_ptr); + r.add(0x1c0a1, inner.scene.tpc_pointer().into()); // TE_TPC_ADDR + + let tvb_heapmeta_ptr = inner + .scene + .tvb_heapmeta_pointer() + .or(0x8000_0000_0000_0000) + .into(); + r.add(0x1c031, tvb_heapmeta_ptr); + r.add(0x1c9c0, tvb_heapmeta_ptr); + r.add(0x1c051, iogpu_unk54); // iogpu_unk_54/55 + r.add(0x1c061, iogpu_unk56); // iogpu_unk_56 + r.add(0x10149, utile_config.into()); // s2.unk_48 utile_config + r.add(0x10139, cmdbuf.ppp_multisamplectl); // PPP_MULTISAMPLECTL + r.add(0x10111, inner.scene.preempt_buf_1_pointer().into()); + r.add(0x1c9b0, inner.scene.preempt_buf_1_pointer().into()); + r.add(0x10119, inner.scene.preempt_buf_2_pointer().into()); + r.add(0x1c9b8, inner.scene.preempt_buf_2_pointer().into()); + r.add(0x1c958, 1); // s2.unk_80 + r.add( + 0x1c950, + inner + .scene + .preempt_buf_3_pointer() + .or(0x4_0000_0000_0000) + .into(), + ); + r.add(0x1c930, 0); // VCE related addr, lsb to enable + r.add(0x1c880, cmdbuf.vdm_ctrl_stream_base); // VDM_CTRL_STREAM_BASE + r.add(0x1c898, 0x0); // if lsb set, faults in UL1C0, possibly missing addr. + r.add( + 0x1c948, + inner.scene.meta_2_pointer().map_or(0, |a| a.into()), + ); // tvb_cluster_meta2 + r.add( + 0x1c888, + inner.scene.meta_3_pointer().map_or(0, |a| a.into()), + ); // tvb_cluster_meta3 + r.add(0x1c890, tiling_control.into()); // tvb_tiling_control + r.add(0x1c918, tiling_control_2); + r.add(0x1c079, inner.scene.tvb_layermeta_pointer().into()); + r.add(0x1c9d8, inner.scene.tvb_layermeta_pointer().into()); + let cl_layermeta_pointer = + inner.scene.tvb_cluster_layermeta_pointer().map_or(0, |a| a.into()); + r.add(0x1c089, cl_layermeta_pointer); + r.add(0x1c9e0, cl_layermeta_pointer); + let cl_meta_4_pointer = + inner.scene.meta_4_pointer().map_or(0, |a| a.into()); + r.add(0x16c41, cl_meta_4_pointer); // tvb_cluster_meta4 + r.add(0x1ca40, cl_meta_4_pointer); // tvb_cluster_meta4 + r.add(0x1c9a8, vtx_unk_f0); // + meta1_blocks? min_free_tvb_pages? + r.add( + 0x1c920, + inner.scene.meta_1_pointer().map_or(0, |a| a.into()), + ); // ??? | meta1_blocks? + r.add(0x10151, 0); + r.add(0x1c199, 0); + r.add(0x1c1a1, 0); + r.add(0x1c1a9, 0); // 0x10151 bit 1 enables + r.add(0x1c1b1, 0); + r.add(0x1c1b9, 0); + r.add(0x10061, self.usc_exec_base); // USC_EXEC_BASE_TA + r.add(0x11801, cmdbuf.vertex_helper.binary.into()); + r.add(0x11809, cmdbuf.vertex_helper.data); + r.add(0x11f71, cmdbuf.vertex_helper.cfg.into()); + r.add(0x1c0b1, tile_info.params.rgn_size.into()); // TE_PSG + r.add(0x1c850, tile_info.params.rgn_size.into()); + r.add(0x10131, tile_info.params.unk_4.into()); + r.add(0x10121, tile_info.params.ppp_ctrl.into()); // PPP_CTRL + r.add( + 0x10129, + tile_info.params.x_max as u64 + | ((tile_info.params.y_max as u64) << 16), + ); // PPP_SCREEN + r.add(0x101b9, tile_info.params.te_screen.into()); // TE_SCREEN + r.add(0x1c069, tile_info.params.te_mtile1.into()); // TE_MTILE1 + r.add(0x1c071, tile_info.params.te_mtile2.into()); // TE_MTILE2 + r.add(0x1c081, tile_info.params.tiles_per_mtile.into()); // TE_MTILE + r.add(0x1c0a9, tile_info.params.tpc_stride.into()); // TE_TPC + r.add(0x10171, tile_info.params.unk_24.into()); + r.add(0x10169, tile_info.params.unk_28.into()); // TA_RENDER_TARGET_MAX + r.add(0x12099, vtx_unk_118); + r.add(0x1c9e8, (tile_info.params.unk_28 & 0x4fff).into()); + /* + r.add(0x10209, 0x100); // Some kind of counter?? Does this matter? + r.add(0x1c9f0, 0x100); // Some kind of counter?? Does this matter? + r.add(0x1c830, 1); // ? + r.add(0x1ca30, 0x1502960e60); // ? + r.add(0x16c39, 0x1502960e60); // ? + r.add(0x1c910, 0xa0000b011d); // ? + r.add(0x1c8e0, 0xff); // cluster mask + r.add(0x1c8e8, 0); // ? + */ + } + ), + tpc: inner.scene.tpc_pointer(), + tpc_size: U64(tile_info.tpc_size as u64), + microsequence: inner.micro_seq.gpu_pointer(), + microsequence_size: inner.micro_seq.len() as u32, + fragment_stamp_slot: ev_frag.slot, + fragment_stamp_value: ev_frag.value.next(), + unk_pointee: 0, + unk_pad: 0, + job_params2 <- try_init!(fw::vertex::raw::JobParameters2 { + unk_480: Default::default(), // fixed + unk_498: U64(0x0), // fixed + unk_4a0: 0x0, // fixed + preempt_buf1: inner.scene.preempt_buf_1_pointer(), + unk_4ac: 0x0, // fixed + unk_4b0: U64(0x0), // fixed + unk_4b8: 0x0, // fixed + unk_4bc: U64(0x0), // fixed + unk_4c4_padding: Default::default(), + unk_50c: 0x0, // fixed + unk_510: U64(0x0), // fixed + unk_518: U64(0x0), // fixed + unk_520: U64(0x0), // fixed + }), + encoder_params <- try_init!(fw::job::raw::EncoderParams { + unk_8: 0x0, // fixed + sync_grow: 0x0, // fixed + unk_10: 0x0, // fixed + encoder_id: 0, + unk_18: 0x0, // fixed + unk_mask: 0xffffffffu32, + sampler_array: U64(cmdbuf.sampler_heap), + sampler_count: cmdbuf.sampler_count as u32, + sampler_max: (cmdbuf.sampler_count as u32) + 1, + }), + unk_55c: 0, + unk_560: 0, + sync_grow: 0, + unk_568: 0, + uses_scratch: (cmdbuf.flags + & uapi::drm_asahi_render_flags_DRM_ASAHI_RENDER_VERTEX_SCRATCH as u32 + != 0) as u32, + meta <- try_init!(fw::job::raw::JobMeta { + unk_0: 0, + unk_2: 0, + no_preemption: no_preemption as u8, + stamp: ev_vtx.stamp_pointer, + fw_stamp: ev_vtx.fw_stamp_pointer, + stamp_value: ev_vtx.value.next(), + stamp_slot: ev_vtx.slot, + evctl_index: 0, // fixed + flush_stamps: flush_stamps as u32, + uuid: uuid_ta, + event_seq: ev_vtx.event_seq as u32, + }), + unk_after_meta: unk1.into(), + unk_buf_0: U64(0), + unk_buf_8: U64(0), + unk_buf_10: U64(0), + command_time: U64(0), + timestamp_pointers <- try_init!(fw::job::raw::TimestampPointers { + start_addr: Some(inner_ptr!(inner.timestamps.gpu_pointer(), vtx.start)), + end_addr: Some(inner_ptr!(inner.timestamps.gpu_pointer(), vtx.end)), + }), + user_timestamp_pointers: inner.user_timestamps.pointers()?, + client_sequence: slot_client_seq, + pad_5d5: Default::default(), + unk_5d8: 0, + unk_5dc: 0, + #[ver(V >= V13_0B4)] + unk_ts: U64(0), + #[ver(V >= V13_0B4)] + unk_5dd_8: Default::default(), + }) + }, + )?; + + core::mem::drop(alloc); + + mod_dev_dbg!(self.dev, "[Submission {}] Add Vertex\n", id); + fence.add_command(); + vtx_job.add_cb(vtx, vm_bind.slot(), move |error| { + if let Some(err) = error { + fence.set_error(err.into()) + } + + fence.command_complete(); + })?; + + mod_dev_dbg!(self.dev, "[Submission {}] Increment counters\n", id); + + // TODO: handle rollbacks, move to job submit? + buffer.increment(); + + job.get_vtx()?.next_seq(); + job.get_frag()?.next_seq(); + + Ok(()) + } +} diff --git a/drivers/gpu/drm/asahi/regs.rs b/drivers/gpu/drm/asahi/regs.rs new file mode 100644 index 00000000000000..6ebbaa56f48c81 --- /dev/null +++ b/drivers/gpu/drm/asahi/regs.rs @@ -0,0 +1,491 @@ +// SPDX-License-Identifier: GPL-2.0-only OR MIT + +//! GPU MMIO register abstraction +//! +//! Since the vast majority of the interactions with the GPU are brokered through the firmware, +//! there is very little need to interact directly with GPU MMIO register. This module abstracts +//! the few operations that require that, mainly reading the MMU fault status, reading GPU ID +//! information, and starting the GPU firmware coprocessor. + +use crate::hw; +use kernel::{ + c_str, + device::Core, + devres::Devres, + io::mem::IoMem, + platform, + prelude::*, + types::ARef, // +}; + +/// Size of the ASC control MMIO region. +pub(crate) const ASC_CTL_SIZE: usize = 0x4000; + +/// Size of the SGX MMIO region. +pub(crate) const SGX_SIZE: usize = 0x1000000; + +const CPU_CONTROL: usize = 0x44; +const CPU_RUN: u32 = 0x1 << 4; // BIT(4) + +const FAULT_INFO: usize = 0x17030; + +const ID_VERSION: usize = 0xd04000; +const ID_UNK08: usize = 0xd04008; +const ID_COUNTS_1: usize = 0xd04010; +const ID_COUNTS_2: usize = 0xd04014; +const ID_UNK18: usize = 0xd04018; +const ID_CLUSTERS: usize = 0xd0401c; + +const CORE_MASK_0: usize = 0xd01500; +const CORE_MASK_1: usize = 0xd01514; + +const CORE_MASKS_G14X: usize = 0xe01500; +const FAULT_INFO_G14X: usize = 0xd8c0; +const FAULT_ADDR_G14X: usize = 0xd8c8; + +/// Enum representing the unit that caused an MMU fault. +#[allow(non_camel_case_types)] +#[allow(clippy::upper_case_acronyms)] +#[derive(Copy, Clone, Debug, Eq, PartialEq)] +pub(crate) enum FaultUnit { + /// Decompress / pixel fetch + DCMP(u8), + /// USC L1 Cache (device loads/stores) + UL1C(u8), + /// Compress / pixel store + CMP(u8), + GSL1(u8), + IAP(u8), + VCE(u8), + /// Tiling Engine + TE(u8), + RAS(u8), + /// Vertex Data Master + VDM(u8), + PPP(u8), + /// ISP Parameter Fetch + IPF(u8), + IPF_CPF(u8), + VF(u8), + VF_CPF(u8), + /// Depth/Stencil load/store + ZLS(u8), + + /// Parameter Management + dPM, + /// Compute Data Master + dCDM_KS(u8), + dIPP, + dIPP_CS, + // Vertex Data Master + dVDM_CSD, + dVDM_SSD, + dVDM_ILF, + dVDM_ILD, + dRDE(u8), + FC, + GSL2, + + /// Graphics L2 Cache Control? + GL2CC_META(u8), + GL2CC_MB, + + /// Parameter Management + gPM_SP(u8), + /// Vertex Data Master - CSD + gVDM_CSD_SP(u8), + gVDM_SSD_SP(u8), + gVDM_ILF_SP(u8), + gVDM_TFP_SP(u8), + gVDM_MMB_SP(u8), + /// Compute Data Master + gCDM_CS_KS0_SP(u8), + gCDM_CS_KS1_SP(u8), + gCDM_CS_KS2_SP(u8), + gCDM_KS0_SP(u8), + gCDM_KS1_SP(u8), + gCDM_KS2_SP(u8), + gIPP_SP(u8), + gIPP_CS_SP(u8), + gRDE0_SP(u8), + gRDE1_SP(u8), + + gCDM_CS, + gCDM_ID, + gCDM_CSR, + gCDM_CSW, + gCDM_CTXR, + gCDM_CTXW, + gIPP, + gIPP_CS, + gKSM_RCE, + + Unknown(u8), +} + +/// Reason for an MMU fault. +#[derive(Copy, Clone, Debug, Eq, PartialEq)] +pub(crate) enum FaultReason { + Unmapped, + AfFault, + WriteOnly, + ReadOnly, + NoAccess, + Unknown(u8), +} + +/// Collection of information about an MMU fault. +#[derive(Copy, Clone, Debug, Eq, PartialEq)] +pub(crate) struct FaultInfo { + pub(crate) address: u64, + pub(crate) sideband: u8, + pub(crate) vm_slot: u32, + pub(crate) unit_code: u8, + pub(crate) unit: FaultUnit, + pub(crate) level: u8, + pub(crate) unk_5: u8, + pub(crate) read: bool, + pub(crate) reason: FaultReason, +} + +/// Device resources for this GPU instance. +pub(crate) struct Resources { + dev: ARef, + sgx: Pin>>>, +} + +impl Resources { + /// Map the required resources given our platform device. + pub(crate) fn new(pdev: &platform::Device) -> Result { + let sgx_req = pdev.io_request_by_name(c_str!("sgx")).ok_or(EINVAL)?; + let sgx_iomem = KBox::pin_init(sgx_req.iomap_sized::(), GFP_KERNEL)?; + + Ok(Resources { + // SAFETY: This device does DMA via the UAT IOMMU. + dev: pdev.into(), + sgx: sgx_iomem, + }) + } + + fn sgx_read32(&self) -> u32 { + if let Some(sgx) = self.sgx.try_access() { + sgx.read32_relaxed(OFF) + } else { + 0 + } + } + + /* Not yet used + fn sgx_write32(&self, val: u32) { + if let Some(sgx) = self.sgx.try_access() { + sgx.write32_relaxed(val, OFF) + } + } + */ + + fn sgx_read64(&self) -> u64 { + if let Some(sgx) = self.sgx.try_access() { + sgx.read64_relaxed(OFF) + } else { + 0 + } + } + + /* Not yet used + fn sgx_write64(&self, val: u64) { + if let Some(sgx) = self.sgx.try_access() { + sgx.write64_relaxed(val, OFF) + } + } + */ + + /// Initialize the MMIO registers for the GPU. + pub(crate) fn init_mmio(&self) -> Result { + // Nothing to do for now... + + Ok(()) + } + + /// Start the ASC coprocessor CPU. + pub(crate) fn start_cpu(pdev: &platform::Device) -> Result { + let asc_req = pdev.io_request_by_name(c_str!("asc")).ok_or(EINVAL)?; + let asc_iomem = KBox::pin_init(asc_req.iomap_sized::(), GFP_KERNEL)?; + let res = asc_iomem.access(pdev.as_ref())?; + + let val = res.read32_relaxed(CPU_CONTROL); + res.write32_relaxed(val | CPU_RUN, CPU_CONTROL); + Ok(()) + } + + /// Get the GPU identification info from registers. + /// + /// See [`hw::GpuIdConfig`] for the result. + pub(crate) fn get_gpu_id(&self) -> Result { + let id_version = self.sgx_read32::(); + let id_unk08 = self.sgx_read32::(); + let id_counts_1 = self.sgx_read32::(); + let id_counts_2 = self.sgx_read32::(); + let id_unk18 = self.sgx_read32::(); + let id_clusters = self.sgx_read32::(); + + dev_info!( + self.dev.as_ref(), + "GPU ID registers: {:#x} {:#x} {:#x} {:#x} {:#x} {:#x}\n", + id_version, + id_unk08, + id_counts_1, + id_counts_2, + id_unk18, + id_clusters + ); + + let gpu_gen = (id_version >> 24) & 0xff; + + let mut core_mask_regs = KVec::new(); + + let num_clusters = match gpu_gen { + 4 | 5 => { + // G13 | G14G + core_mask_regs.push(self.sgx_read32::(), GFP_KERNEL)?; + core_mask_regs.push(self.sgx_read32::(), GFP_KERNEL)?; + (id_clusters >> 12) & 0xff + } + 6 => { + // G14X + core_mask_regs.push(self.sgx_read32::(), GFP_KERNEL)?; + core_mask_regs.push(self.sgx_read32::<{ CORE_MASKS_G14X + 4 }>(), GFP_KERNEL)?; + core_mask_regs.push(self.sgx_read32::<{ CORE_MASKS_G14X + 8 }>(), GFP_KERNEL)?; + // Clusters per die * num dies + ((id_counts_1 >> 8) & 0xff) * ((id_counts_1 >> 16) & 0xf) + } + a => { + dev_err!(self.dev.as_ref(), "Unknown GPU generation {}\n", a); + return Err(ENODEV); + } + }; + + let mut core_masks_packed = KVec::new(); + core_masks_packed.extend_from_slice(&core_mask_regs, GFP_KERNEL)?; + + dev_info!(self.dev.as_ref(), "Core masks: {:#x?}\n", core_masks_packed); + + let num_cores = id_counts_1 & 0xff; + + if num_cores > 32 { + dev_err!( + self.dev.as_ref(), + "Too many cores per cluster ({} > 32)\n", + num_cores + ); + return Err(ENODEV); + } + + if num_cores * num_clusters > (core_mask_regs.len() * 32) as u32 { + dev_err!( + self.dev.as_ref(), + "Too many total cores ({} x {} > {})\n", + num_clusters, + num_cores, + core_mask_regs.len() * 32 + ); + return Err(ENODEV); + } + + let mut core_masks = KVec::new(); + let mut total_active_cores: u32 = 0; + + let max_core_mask = ((1u64 << num_cores) - 1) as u32; + for _ in 0..num_clusters { + let mask = core_mask_regs[0] & max_core_mask; + core_masks.push(mask, GFP_KERNEL)?; + for i in 0..core_mask_regs.len() { + core_mask_regs[i] >>= num_cores; + if i < (core_mask_regs.len() - 1) { + core_mask_regs[i] |= core_mask_regs[i + 1] << (32 - num_cores); + } + } + total_active_cores += mask.count_ones(); + } + + if core_mask_regs.iter().any(|a| *a != 0) { + dev_err!( + self.dev.as_ref(), + "Leftover core mask: {:#x?}\n", + core_mask_regs + ); + return Err(EIO); + } + + let (gpu_rev, gpu_rev_id) = match (id_version >> 8) & 0xff { + 0x00 => (hw::GpuRevision::A0, hw::GpuRevisionID::A0), + 0x01 => (hw::GpuRevision::A1, hw::GpuRevisionID::A1), + 0x10 => (hw::GpuRevision::B0, hw::GpuRevisionID::B0), + 0x11 => (hw::GpuRevision::B1, hw::GpuRevisionID::B1), + 0x20 => (hw::GpuRevision::C0, hw::GpuRevisionID::C0), + 0x21 => (hw::GpuRevision::C1, hw::GpuRevisionID::C1), + a => { + dev_err!(self.dev.as_ref(), "Unknown GPU revision {}\n", a); + return Err(ENODEV); + } + }; + + Ok(hw::GpuIdConfig { + gpu_gen: match (id_version >> 24) & 0xff { + 4 => hw::GpuGen::G13, + 5 => hw::GpuGen::G14, + 6 => hw::GpuGen::G14, // G14X has a separate ID + a => { + dev_err!(self.dev.as_ref(), "Unknown GPU generation {}\n", a); + return Err(ENODEV); + } + }, + gpu_variant: match (id_version >> 16) & 0xff { + 1 => hw::GpuVariant::P, // Guess + 2 => hw::GpuVariant::G, + 3 => hw::GpuVariant::S, + 4 => { + if num_clusters > 4 { + hw::GpuVariant::D + } else { + hw::GpuVariant::C + } + } + a => { + dev_err!(self.dev.as_ref(), "Unknown GPU variant {}\n", a); + return Err(ENODEV); + } + }, + gpu_rev, + gpu_rev_id, + num_clusters, + num_cores, + num_frags: num_cores, // Used to be id_counts_1[15:8] but does not work for G14X + num_gps: (id_counts_2 >> 16) & 0xff, + total_active_cores, + core_masks, + core_masks_packed, + }) + } + + /// Get the fault information from the MMU status register, if one occurred. + pub(crate) fn get_fault_info(&self, cfg: &'static hw::HwConfig) -> Option { + let g14x = cfg.gpu_core as u32 >= hw::GpuCore::G14S as u32; + + let fault_info = if g14x { + self.sgx_read64::() + } else { + self.sgx_read64::() + }; + + if fault_info & 1 == 0 { + return None; + } + + let fault_addr = if g14x { + self.sgx_read64::() + } else { + fault_info >> 30 + }; + + let unit_code = ((fault_info >> 9) & 0xff) as u8; + let unit = match unit_code { + 0x00..=0x9f => match unit_code & 0xf { + 0x0 => FaultUnit::DCMP(unit_code >> 4), + 0x1 => FaultUnit::UL1C(unit_code >> 4), + 0x2 => FaultUnit::CMP(unit_code >> 4), + 0x3 => FaultUnit::GSL1(unit_code >> 4), + 0x4 => FaultUnit::IAP(unit_code >> 4), + 0x5 => FaultUnit::VCE(unit_code >> 4), + 0x6 => FaultUnit::TE(unit_code >> 4), + 0x7 => FaultUnit::RAS(unit_code >> 4), + 0x8 => FaultUnit::VDM(unit_code >> 4), + 0x9 => FaultUnit::PPP(unit_code >> 4), + 0xa => FaultUnit::IPF(unit_code >> 4), + 0xb => FaultUnit::IPF_CPF(unit_code >> 4), + 0xc => FaultUnit::VF(unit_code >> 4), + 0xd => FaultUnit::VF_CPF(unit_code >> 4), + 0xe => FaultUnit::ZLS(unit_code >> 4), + _ => FaultUnit::Unknown(unit_code), + }, + 0xa1 => FaultUnit::dPM, + 0xa2 => FaultUnit::dCDM_KS(0), + 0xa3 => FaultUnit::dCDM_KS(1), + 0xa4 => FaultUnit::dCDM_KS(2), + 0xa5 => FaultUnit::dIPP, + 0xa6 => FaultUnit::dIPP_CS, + 0xa7 => FaultUnit::dVDM_CSD, + 0xa8 => FaultUnit::dVDM_SSD, + 0xa9 => FaultUnit::dVDM_ILF, + 0xaa => FaultUnit::dVDM_ILD, + 0xab => FaultUnit::dRDE(0), + 0xac => FaultUnit::dRDE(1), + 0xad => FaultUnit::FC, + 0xae => FaultUnit::GSL2, + 0xb0..=0xb7 => FaultUnit::GL2CC_META(unit_code & 0xf), + 0xb8 => FaultUnit::GL2CC_MB, + 0xd0..=0xdf if g14x => match unit_code & 0xf { + 0x0 => FaultUnit::gCDM_CS, + 0x1 => FaultUnit::gCDM_ID, + 0x2 => FaultUnit::gCDM_CSR, + 0x3 => FaultUnit::gCDM_CSW, + 0x4 => FaultUnit::gCDM_CTXR, + 0x5 => FaultUnit::gCDM_CTXW, + 0x6 => FaultUnit::gIPP, + 0x7 => FaultUnit::gIPP_CS, + 0x8 => FaultUnit::gKSM_RCE, + _ => FaultUnit::Unknown(unit_code), + }, + 0xe0..=0xff if g14x => match unit_code & 0xf { + 0x0 => FaultUnit::gPM_SP((unit_code >> 4) & 1), + 0x1 => FaultUnit::gVDM_CSD_SP((unit_code >> 4) & 1), + 0x2 => FaultUnit::gVDM_SSD_SP((unit_code >> 4) & 1), + 0x3 => FaultUnit::gVDM_ILF_SP((unit_code >> 4) & 1), + 0x4 => FaultUnit::gVDM_TFP_SP((unit_code >> 4) & 1), + 0x5 => FaultUnit::gVDM_MMB_SP((unit_code >> 4) & 1), + 0x6 => FaultUnit::gRDE0_SP((unit_code >> 4) & 1), + _ => FaultUnit::Unknown(unit_code), + }, + 0xe0..=0xff if !g14x => match unit_code & 0xf { + 0x0 => FaultUnit::gPM_SP((unit_code >> 4) & 1), + 0x1 => FaultUnit::gVDM_CSD_SP((unit_code >> 4) & 1), + 0x2 => FaultUnit::gVDM_SSD_SP((unit_code >> 4) & 1), + 0x3 => FaultUnit::gVDM_ILF_SP((unit_code >> 4) & 1), + 0x4 => FaultUnit::gVDM_TFP_SP((unit_code >> 4) & 1), + 0x5 => FaultUnit::gVDM_MMB_SP((unit_code >> 4) & 1), + 0x6 => FaultUnit::gCDM_CS_KS0_SP((unit_code >> 4) & 1), + 0x7 => FaultUnit::gCDM_CS_KS1_SP((unit_code >> 4) & 1), + 0x8 => FaultUnit::gCDM_CS_KS2_SP((unit_code >> 4) & 1), + 0x9 => FaultUnit::gCDM_KS0_SP((unit_code >> 4) & 1), + 0xa => FaultUnit::gCDM_KS1_SP((unit_code >> 4) & 1), + 0xb => FaultUnit::gCDM_KS2_SP((unit_code >> 4) & 1), + 0xc => FaultUnit::gIPP_SP((unit_code >> 4) & 1), + 0xd => FaultUnit::gIPP_CS_SP((unit_code >> 4) & 1), + 0xe => FaultUnit::gRDE0_SP((unit_code >> 4) & 1), + 0xf => FaultUnit::gRDE1_SP((unit_code >> 4) & 1), + _ => FaultUnit::Unknown(unit_code), + }, + _ => FaultUnit::Unknown(unit_code), + }; + + let reason = match (fault_info >> 1) & 0x7 { + 0 => FaultReason::Unmapped, + 1 => FaultReason::AfFault, + 2 => FaultReason::WriteOnly, + 3 => FaultReason::ReadOnly, + 4 => FaultReason::NoAccess, + a => FaultReason::Unknown(a as u8), + }; + + Some(FaultInfo { + address: fault_addr << 6, + sideband: ((fault_info >> 23) & 0x7f) as u8, + vm_slot: ((fault_info >> 17) & 0x3f) as u32, + unit_code, + unit, + level: ((fault_info >> 7) & 3) as u8, + unk_5: ((fault_info >> 5) & 3) as u8, + read: (fault_info & (1 << 4)) != 0, + reason, + }) + } +} diff --git a/drivers/gpu/drm/asahi/slotalloc.rs b/drivers/gpu/drm/asahi/slotalloc.rs new file mode 100644 index 00000000000000..fde7470fe57791 --- /dev/null +++ b/drivers/gpu/drm/asahi/slotalloc.rs @@ -0,0 +1,323 @@ +// SPDX-License-Identifier: GPL-2.0-only OR MIT + +//! Generic slot allocator +//! +//! This is a simple allocator to manage fixed-size pools of GPU resources that are transiently +//! required during command execution. Each item resides in a "slot" at a given index. Users borrow +//! and return free items from the available pool. +//! +//! Allocations are "sticky", and return a token that callers can use to request the same slot +//! again later. This allows slots to be lazily invalidated, so that multiple uses by the same user +//! avoid any actual cleanup work. +//! +//! The allocation policy is currently a simple LRU mechanism, doing a full linear scan over the +//! slots when no token was previously provided. This is probably good enough, since in the absence +//! of serious system contention most allocation requests will be immediately fulfilled from the +//! previous slot without doing an LRU scan. + +use core::num::NonZeroUsize; +use core::ops::{ + Deref, + DerefMut, // +}; +use kernel::{ + error::{ + code::*, + Result, // + }, + prelude::*, + str::CStr, + sync::{ + Arc, + CondVar, + LockClassKey, + Mutex, // + }, +}; + +/// Trait representing a single item within a slot. +pub(crate) trait SlotItem { + /// Arbitrary user data associated with the SlotAllocator. + type Data; + + /// Called eagerly when this item is released back into the available pool. + fn release(&mut self, _data: &mut Self::Data, _slot: u32) {} +} + +/// Trivial implementation for users which do not require any slot data nor any allocator data. +impl SlotItem for () { + type Data = (); +} + +/// Represents a current or previous allocation of an item from a slot. Users keep `SlotToken`s +/// around across allocations to request that, if possible, the same slot be reused. +#[derive(Copy, Clone, Debug)] +pub(crate) struct SlotToken { + time: u64, + slot: u32, +} + +impl SlotToken { + /// Returns the slot index that this token represents a past assignment to. + pub(crate) fn last_slot(&self) -> u32 { + self.slot + } +} + +/// A guard representing active ownership of a slot. +pub(crate) struct Guard { + item: Option, + changed: bool, + token: SlotToken, + alloc: Arc>, +} + +impl Guard { + /// Returns the active slot owned by this `Guard`. + pub(crate) fn slot(&self) -> u32 { + self.token.slot + } + + /// Returns `true` if the slot changed since the last allocation (or no `SlotToken` was + /// provided), or `false` if the previously allocated slot was successfully re-acquired with + /// no other users in the interim. + pub(crate) fn changed(&self) -> bool { + self.changed + } + + /// Returns a `SlotToken` that can be used to re-request the same slot at a later time, after + /// this `Guard` is dropped. + pub(crate) fn token(&self) -> SlotToken { + self.token + } +} + +impl Deref for Guard { + type Target = T; + + fn deref(&self) -> &Self::Target { + self.item.as_ref().expect("SlotItem Guard lost our item!") + } +} + +impl DerefMut for Guard { + fn deref_mut(&mut self) -> &mut Self::Target { + self.item.as_mut().expect("SlotItem Guard lost our item!") + } +} + +/// A slot item that is currently free. +struct Entry { + item: T, + get_time: u64, + drop_time: u64, +} + +/// Inner data for the `SlotAllocator`, protected by a `Mutex`. +struct SlotAllocatorInner { + data: T::Data, + slots: KVec>>, + get_count: u64, + drop_count: u64, + slot_limit: usize, +} + +/// A single slot allocator instance. +#[pin_data] +struct SlotAllocatorOuter { + #[pin] + inner: Mutex>, + #[pin] + cond: CondVar, +} + +/// A shared reference to a slot allocator instance. +pub(crate) struct SlotAllocator(Arc>); + +impl SlotAllocator { + /// Creates a new `SlotAllocator`, with a fixed number of slots and arbitrary associated data. + /// + /// The caller provides a constructor callback which takes a reference to the `T::Data` and + /// creates a single slot. This is called during construction to create all the initial + /// items, which then live the lifetime of the `SlotAllocator`. + pub(crate) fn new( + num_slots: u32, + mut data: T::Data, + mut constructor: impl FnMut(&mut T::Data, u32) -> Option, + name: &'static CStr, + lock_key1: Pin<&'static LockClassKey>, + lock_key2: Pin<&'static LockClassKey>, + ) -> Result> { + let mut slots = KVec::with_capacity(num_slots as usize, GFP_KERNEL)?; + + for i in 0..num_slots { + slots + .push( + constructor(&mut data, i).map(|item| Entry { + item, + get_time: 0, + drop_time: 0, + }), + GFP_KERNEL, + ) + .expect("try_push() failed after reservation"); + } + + let inner = SlotAllocatorInner { + data, + slots, + get_count: 0, + drop_count: 0, + slot_limit: usize::MAX, + }; + + let alloc = Arc::pin_init( + pin_init!(SlotAllocatorOuter { + // SAFETY: `mutex_init!` is called below. + inner <- Mutex::new(inner, name, lock_key1), + // SAFETY: `condvar_init!` is called below. + cond <- CondVar::new(name, lock_key2), + }), + GFP_KERNEL, + )?; + + Ok(SlotAllocator(alloc)) + } + + /// Calls a callback on the inner data associated with this allocator, taking the lock. + pub(crate) fn with_inner(&self, cb: impl FnOnce(&mut T::Data) -> RetVal) -> RetVal { + let mut inner = self.0.inner.lock(); + cb(&mut inner.data) + } + + /// Set the slot limit for this allocator. New bindings will not use slots above + /// this threshold. + pub(crate) fn set_limit(&self, limit: Option) { + let mut inner = self.0.inner.lock(); + inner.slot_limit = limit.unwrap_or(NonZeroUsize::MAX).get(); + } + + /// Gets a fresh slot, optionally reusing a previous allocation if a `SlotToken` is provided. + /// + /// Blocks if no slots are free. + pub(crate) fn get(&self, token: Option) -> Result> { + self.get_inner(token, |_a, _b| Ok(())) + } + + /// Gets a fresh slot, optionally reusing a previous allocation if a `SlotToken` is provided. + /// + /// Blocks if no slots are free. + /// + /// This version allows the caller to pass in a callback that gets a mutable reference to the + /// user data for the allocator and the freshly acquired slot, which is called before the + /// allocator lock is released. This can be used to perform bookkeeping associated with + /// specific slots (such as tracking their current owner). + pub(crate) fn get_inner( + &self, + token: Option, + cb: impl FnOnce(&mut T::Data, &mut Guard) -> Result<()>, + ) -> Result> { + let mut inner = self.0.inner.lock(); + + if let Some(token) = token { + if (token.slot as usize) < inner.slot_limit { + let slot = &mut inner.slots[token.slot as usize]; + if slot.is_some() { + let count = slot.as_ref().unwrap().get_time; + if count == token.time { + let mut guard = Guard { + item: Some(slot.take().unwrap().item), + token, + changed: false, + alloc: self.0.clone(), + }; + cb(&mut inner.data, &mut guard)?; + return Ok(guard); + } + } + } + } + + let mut first = true; + let slot = loop { + let mut oldest_time = u64::MAX; + let mut oldest_slot = 0u32; + + for (i, slot) in inner.slots.iter().enumerate() { + if i >= inner.slot_limit { + break; + } + if let Some(slot) = slot.as_ref() { + if slot.drop_time < oldest_time { + oldest_slot = i as u32; + oldest_time = slot.drop_time; + } + } + } + + if oldest_time == u64::MAX { + if first && inner.slot_limit == usize::MAX { + pr_warn!( + "{}: out of slots, blocking\n", + core::any::type_name::() + ); + } + first = false; + if self.0.cond.wait_interruptible(&mut inner) { + return Err(ERESTARTSYS); + } + } else { + break oldest_slot; + } + }; + + inner.get_count += 1; + + let item = inner.slots[slot as usize] + .take() + .expect("Someone stole our slot?") + .item; + + let mut guard = Guard { + item: Some(item), + changed: true, + token: SlotToken { + time: inner.get_count, + slot, + }, + alloc: self.0.clone(), + }; + + cb(&mut inner.data, &mut guard)?; + Ok(guard) + } +} + +impl Clone for SlotAllocator { + fn clone(&self) -> Self { + SlotAllocator(self.0.clone()) + } +} + +impl Drop for Guard { + fn drop(&mut self) { + let mut inner = self.alloc.inner.lock(); + if inner.slots[self.token.slot as usize].is_some() { + pr_crit!( + "{}: tried to return an item into a full slot ({})\n", + core::any::type_name::(), + self.token.slot + ); + } else { + inner.drop_count += 1; + let mut item = self.item.take().expect("Guard lost its item"); + item.release(&mut inner.data, self.token.slot); + inner.slots[self.token.slot as usize] = Some(Entry { + item, + get_time: self.token.time, + drop_time: inner.drop_count, + }); + self.alloc.cond.notify_one(); + } + } +} diff --git a/drivers/gpu/drm/asahi/util.rs b/drivers/gpu/drm/asahi/util.rs new file mode 100644 index 00000000000000..1a41d8f16d4432 --- /dev/null +++ b/drivers/gpu/drm/asahi/util.rs @@ -0,0 +1,141 @@ +// SPDX-License-Identifier: GPL-2.0-only OR MIT + +//! Miscellaneous utility functions + +use core::ops::{ + Add, + BitAnd, + Div, + Not, + Sub, // +}; +use kernel::prelude::*; + +/// Aligns an integer type to a power of two. +pub(crate) fn align(a: T, b: T) -> T +where + T: Copy + + Default + + BitAnd + + Not + + Add + + Sub + + Div + + core::cmp::PartialEq, +{ + let def: T = Default::default(); + #[allow(clippy::eq_op)] + let one: T = !def / !def; + + assert!((b & (b - one)) == def); + + (a + b - one) & !(b - one) +} + +/// Aligns an integer type down to a power of two. +pub(crate) fn align_down(a: T, b: T) -> T +where + T: Copy + + Default + + BitAnd + + Not + + Sub + + Div + + core::cmp::PartialEq, +{ + let def: T = Default::default(); + #[allow(clippy::eq_op)] + let one: T = !def / !def; + + assert!((b & (b - one)) == def); + + a & !(b - one) +} + +pub(crate) trait RangeExt { + fn overlaps(&self, other: Self) -> bool; + fn is_superset(&self, other: Self) -> bool; + // fn len(&self) -> usize; + fn range(&self) -> T; +} + +impl + Default + Copy + Sub> RangeExt for core::ops::Range +where + usize: core::convert::TryFrom, + >::Error: core::fmt::Debug, +{ + fn overlaps(&self, other: Self) -> bool { + !(self.is_empty() || other.is_empty() || self.end <= other.start || other.end <= self.start) + } + fn is_superset(&self, other: Self) -> bool { + !self.is_empty() + && (other.is_empty() || (other.start >= self.start && other.end <= self.end)) + } + fn range(&self) -> T { + if self.is_empty() { + Default::default() + } else { + self.end - self.start + } + } + // fn len(&self) -> usize { + // self.range().try_into().unwrap() + // } +} + +pub(crate) fn gcd(in_n: u64, in_m: u64) -> u64 { + let mut n = in_n; + let mut m = in_m; + + while n != 0 { + let remainder = m % n; + m = n; + n = remainder; + } + + m +} + +pub(crate) unsafe trait AnyBitPattern: Default + Sized + Copy + 'static {} + +pub(crate) struct Reader<'a> { + buffer: &'a [u8], + offset: usize, +} + +impl<'a> Reader<'a> { + pub(crate) fn new(buffer: &'a [u8]) -> Self { + Reader { buffer, offset: 0 } + } + + pub(crate) fn read_up_to(&mut self, max_size: usize) -> Result { + let mut obj: T = Default::default(); + let size: usize = core::mem::size_of::().min(max_size); + let range = self.offset..self.offset + size; + let src = self.buffer.get(range).ok_or(EINVAL)?; + + // SAFETY: The output pointer is valid, and the size does not exceed + // the type size, and all bit patterns are valid. + let dst = unsafe { core::slice::from_raw_parts_mut(&mut obj as *mut _ as *mut u8, size) }; + + dst.copy_from_slice(src); + self.offset += size; + Ok(obj) + } + + pub(crate) fn read(&mut self) -> Result { + self.read_up_to(!0) + } + + pub(crate) fn is_empty(&self) -> bool { + self.offset >= self.buffer.len() + } + + pub(crate) fn skip(&mut self, size: usize) { + self.offset += size + } + + pub(crate) fn rewind(&mut self) { + self.offset = 0 + } +} diff --git a/drivers/gpu/drm/asahi/workqueue.rs b/drivers/gpu/drm/asahi/workqueue.rs new file mode 100644 index 00000000000000..e3b9009fff0b79 --- /dev/null +++ b/drivers/gpu/drm/asahi/workqueue.rs @@ -0,0 +1,1032 @@ +// SPDX-License-Identifier: GPL-2.0-only OR MIT + +//! GPU command execution queues +//! +//! The AGX GPU firmware schedules GPU work commands out of work queues, which are ring buffers of +//! pointers to work commands. There can be an arbitrary number of work queues. Work queues have an +//! associated type (vertex, fragment, or compute) and may only contain generic commands or commands +//! specific to that type. +//! +//! This module manages queueing work commands into a work queue and submitting them for execution +//! by the firmware. An active work queue needs an event to signal completion of its work, which is +//! owned by what we call a batch. This event then notifies the work queue when work is completed, +//! and that triggers freeing of all resources associated with that work. An idle work queue gives +//! up its associated event. + +use crate::debug::*; +use crate::fw::channels::{ + ChannelErrorType, + PipeType, // +}; +use crate::fw::types::*; +use crate::fw::workqueue::*; +use crate::no_debug; +use crate::object::OpaqueGpuObject; +use crate::{ + channel, + driver, + event, + fw, + gpu, + regs, // +}; +use core::any::Any; +use core::num::NonZeroU64; +use core::sync::atomic::Ordering; +use kernel::{ + dma_fence, + error::code::*, + new_mutex, + prelude::*, + sync::{ + lock::{ + mutex::MutexBackend, + Guard, // + }, + Arc, + Mutex, // + }, + workqueue::{ + self, + impl_has_work, + new_work, + Work, + WorkItem, // + }, // +}; + +pub(crate) trait OpaqueCommandObject: OpaqueGpuObject {} + +impl OpaqueCommandObject for GpuObject where T: Command {} + +const DEBUG_CLASS: DebugFlags = DebugFlags::WorkQueue; + +const MAX_JOB_SLOTS: u32 = 127; + +/// An enum of possible errors that might cause a piece of work to fail execution. +#[derive(Copy, Clone, Debug, PartialEq, Eq)] +pub(crate) enum WorkError { + /// GPU timeout (command execution took too long). + Timeout, + /// GPU MMU fault (invalid access). + Fault(regs::FaultInfo), + /// Work failed due to an error caused by other concurrent GPU work. + Killed, + /// Channel error + ChannelError(ChannelErrorType), + /// The GPU crashed. + NoDevice, + /// Unknown reason. + Unknown, +} + +impl From for kernel::error::Error { + fn from(err: WorkError) -> Self { + match err { + WorkError::Timeout => ETIMEDOUT, + // Not EFAULT because that's for userspace faults + WorkError::Fault(_) => EIO, + WorkError::Unknown => ENODATA, + WorkError::Killed => ECANCELED, + WorkError::NoDevice => ENODEV, + WorkError::ChannelError(_) => EIO, + } + } +} + +/// A GPU context tracking structure, which must be explicitly invalidated when dropped. +pub(crate) struct GpuContext { + dev: driver::AsahiDevRef, + data: Option>>, +} +no_debug!(GpuContext); + +impl GpuContext { + /// Allocate a new GPU context. + pub(crate) fn new( + dev: &driver::AsahiDevice, + alloc: &mut gpu::KernelAllocators, + buffer: Arc, + ) -> Result { + Ok(GpuContext { + dev: dev.into(), + data: Some(KBox::new( + alloc.shared.new_object( + fw::workqueue::GpuContextData { _buffer: buffer }, + |_inner| Default::default(), + )?, + GFP_KERNEL, + )?), + }) + } + + /// Returns the GPU pointer to the inner GPU context data structure. + pub(crate) fn gpu_pointer(&self) -> GpuPointer<'_, fw::workqueue::GpuContextData> { + self.data.as_ref().unwrap().gpu_pointer() + } +} + +impl Drop for GpuContext { + fn drop(&mut self) { + mod_dev_dbg!(self.dev, "GpuContext: Freeing GPU context\n"); + let data = self.data.take().unwrap(); + (*self.dev).gpu.free_context(data); + } +} + +struct SubmittedWork +where + O: OpaqueCommandObject, + C: FnOnce(Option) + Send + Sync + 'static, +{ + object: O, + value: EventValue, + error: Option, + wptr: u32, + vm_slot: u32, + callback: Option, + fence: dma_fence::Fence, +} + +pub(crate) trait GenSubmittedWork: Send + Sync { + fn gpu_va(&self) -> NonZeroU64; + fn value(&self) -> event::EventValue; + fn wptr(&self) -> u32; + fn set_wptr(&mut self, wptr: u32); + fn mark_error(&mut self, error: WorkError); + fn complete(&mut self); + fn get_fence(&self) -> dma_fence::Fence; +} + +#[pin_data] +struct SubmittedWorkContainer { + #[pin] + work: Work, + inner: KBox, +} + +impl_has_work! { + impl HasWork for SubmittedWorkContainer { self.work } +} + +impl WorkItem for SubmittedWorkContainer { + type Pointer = Pin>; + + fn run(this: Pin>) { + mod_pr_debug!("WorkQueue: Freeing command @ {:?}\n", this.inner.gpu_va()); + } +} + +impl SubmittedWorkContainer { + fn inner_mut(self: Pin<&mut Self>) -> &mut KBox { + // SAFETY: inner does not require structural pinning. + unsafe { &mut self.get_unchecked_mut().inner } + } +} + +impl) + Send + Sync> GenSubmittedWork + for SubmittedWork +{ + fn gpu_va(&self) -> NonZeroU64 { + self.object.gpu_va() + } + + fn value(&self) -> event::EventValue { + self.value + } + + fn wptr(&self) -> u32 { + self.wptr + } + + fn set_wptr(&mut self, wptr: u32) { + self.wptr = wptr; + } + + fn complete(&mut self) { + if let Some(cb) = self.callback.take() { + cb(self.error); + } + } + + fn mark_error(&mut self, error: WorkError) { + mod_pr_debug!("WorkQueue: Command at value {:#x?} failed\n", self.value); + self.error = Some(match error { + WorkError::Fault(info) if info.vm_slot != self.vm_slot => WorkError::Killed, + err => err, + }); + } + + fn get_fence(&self) -> dma_fence::Fence { + self.fence.clone() + } +} + +/// Inner data for managing a single work queue. +#[versions(AGX)] +struct WorkQueueInner { + dev: driver::AsahiDevRef, + event_manager: Arc, + info: GpuObject, + new: bool, + pipe_type: PipeType, + size: u32, + wptr: u32, + pending: KVec>>, + last_completed_work: Option>>, + last_token: Option, + pending_jobs: usize, + last_submitted: Option, + last_completed: Option, + event: Option<(event::Event, event::EventValue)>, + priority: u32, + commit_seq: u64, + submit_seq: u64, + event_seq: u64, +} + +/// An instance of a work queue. +#[versions(AGX)] +#[pin_data] +pub(crate) struct WorkQueue { + info_pointer: GpuWeakPointer, + #[pin] + inner: Mutex, +} + +#[versions(AGX)] +impl WorkQueueInner::ver { + /// Return the GPU done pointer, representing how many work items have been completed by the + /// GPU. + fn doneptr(&self) -> u32 { + self.info + .state + .with(|raw, _inner| raw.gpu_doneptr.load(Ordering::Acquire)) + } +} + +#[versions(AGX)] +#[derive(Copy, Clone)] +pub(crate) struct QueueEventInfo { + pub(crate) stamp_pointer: GpuWeakPointer, + pub(crate) fw_stamp_pointer: GpuWeakPointer, + pub(crate) slot: u32, + pub(crate) value: event::EventValue, + pub(crate) cmd_seq: u64, + pub(crate) event_seq: u64, + pub(crate) info_ptr: GpuWeakPointer, +} + +#[versions(AGX)] +pub(crate) struct Job { + wq: Arc, + event_info: QueueEventInfo::ver, + start_value: EventValue, + pending: KVec>>, + committed: bool, + submitted: bool, + event_count: usize, + fence: dma_fence::Fence, +} + +#[versions(AGX)] +pub(crate) struct JobSubmission<'a> { + inner: Option>, + wptr: u32, + event_count: usize, + command_count: usize, +} + +#[versions(AGX)] +impl Job::ver { + pub(crate) fn event_info(&self) -> QueueEventInfo::ver { + let mut info = self.event_info; + info.cmd_seq += self.pending.len() as u64; + info.event_seq += self.event_count as u64; + + info + } + + pub(crate) fn next_seq(&mut self) { + self.event_count += 1; + self.event_info.value.increment(); + } + + pub(crate) fn add( + &mut self, + command: O, + vm_slot: u32, + ) -> Result { + self.add_cb(command, vm_slot, |_| {}) + } + + pub(crate) fn add_cb( + &mut self, + command: O, + vm_slot: u32, + callback: impl FnOnce(Option) + Sync + Send + 'static, + ) -> Result { + if self.committed { + pr_err!("WorkQueue: Tried to mutate committed Job\n"); + return Err(EINVAL); + } + + let fence = self.fence.clone(); + let value = self.event_info.value.next(); + + self.pending.push( + KBox::try_pin_init( + try_pin_init!(SubmittedWorkContainer { + work <- new_work!("SubmittedWorkWrapper::work"), + inner: KBox::new(SubmittedWork::<_, _> { + object: command, + value, + error: None, + callback: Some(callback), + wptr: 0, + vm_slot, + fence, + }, GFP_KERNEL)? + }), + GFP_KERNEL, + )?, + GFP_KERNEL, + )?; + + Ok(()) + } + + pub(crate) fn commit(&mut self) -> Result { + if self.committed { + pr_err!("WorkQueue: Tried to commit committed Job\n"); + return Err(EINVAL); + } + + if self.pending.is_empty() { + pr_err!("WorkQueue: Job::commit() with no commands\n"); + return Err(EINVAL); + } + + let mut inner = self.wq.inner.lock(); + + let ev = inner.event.as_mut().expect("WorkQueue: Job lost its event"); + + if ev.1 != self.start_value { + pr_err!( + "WorkQueue: Job::commit() out of order (event slot {} {:?} != {:?}\n", + ev.0.slot(), + ev.1, + self.start_value + ); + return Err(EINVAL); + } + + ev.1 = self.event_info.value; + inner.commit_seq += self.pending.len() as u64; + inner.event_seq += self.event_count as u64; + self.committed = true; + + Ok(()) + } + + pub(crate) fn can_submit(&self) -> Option { + let inner = self.wq.inner.lock(); + if inner.free_slots() > self.event_count && inner.free_space() > self.pending.len() { + None + } else if let Some(work) = inner.pending.first() { + Some(work.inner.get_fence()) + } else { + pr_err!( + "WorkQueue: Cannot submit, but queue is empty? {} > {}, {} > {} (pend={} ls={:#x?} lc={:#x?}) ev={:#x?} cur={:#x?} slot {:?}\n", + inner.free_slots(), + self.event_count, + inner.free_space(), + self.pending.len(), + inner.pending.len(), + inner.last_submitted, + inner.last_completed, + inner.event.as_ref().map(|a| a.1), + inner.event.as_ref().map(|a| a.0.current()), + inner.event.as_ref().map(|a| a.0.slot()), + ); + None + } + } + + pub(crate) fn submit(&mut self) -> Result> { + if !self.committed { + pr_err!("WorkQueue: Tried to submit uncommitted Job\n"); + return Err(EINVAL); + } + + if self.submitted { + pr_err!("WorkQueue: Tried to submit Job twice\n"); + return Err(EINVAL); + } + + if self.pending.is_empty() { + pr_err!("WorkQueue: Job::submit() with no commands\n"); + return Err(EINVAL); + } + + let mut inner = self.wq.inner.lock(); + + if inner.submit_seq != self.event_info.cmd_seq { + pr_err!( + "WorkQueue: Job::submit() out of order (submit_seq {} != {})\n", + inner.submit_seq, + self.event_info.cmd_seq + ); + return Err(EINVAL); + } + + if inner.commit_seq < (self.event_info.cmd_seq + self.pending.len() as u64) { + pr_err!( + "WorkQueue: Job::submit() out of order (commit_seq {} != {})\n", + inner.commit_seq, + (self.event_info.cmd_seq + self.pending.len() as u64) + ); + return Err(EINVAL); + } + + let mut wptr = inner.wptr; + let command_count = self.pending.len(); + + if inner.free_space() <= command_count { + pr_err!("WorkQueue: Job does not fit in ring buffer\n"); + return Err(EBUSY); + } + + inner.pending.reserve(command_count, GFP_KERNEL)?; + + inner.last_submitted = Some(self.event_info.value); + mod_dev_dbg!( + inner.dev, + "WorkQueue: submitting {} cmds at {:#x?}, lc {:#x?}, cur {:#x?}, pending {}, events {}\n", + self.pending.len(), + inner.last_submitted, + inner.last_completed, + inner.event.as_ref().map(|a| a.0.current()), + inner.pending.len(), + self.event_count, + ); + + for mut command in self.pending.drain(..) { + command.as_mut().inner_mut().set_wptr(wptr); + + let next_wptr = (wptr + 1) % inner.size; + assert!(inner.doneptr() != next_wptr); + inner.info.ring[wptr as usize] = command.inner.gpu_va().get(); + wptr = next_wptr; + + // Cannot fail, since we did a reserve(1) above + inner + .pending + .push(command, GFP_KERNEL) + .expect("push() failed after reserve()"); + } + + self.submitted = true; + + Ok(JobSubmission::ver { + inner: Some(inner), + wptr, + command_count, + event_count: self.event_count, + }) + } +} + +#[versions(AGX)] +impl<'a> JobSubmission::ver<'a> { + pub(crate) fn run(mut self, channel: &mut channel::PipeChannel::ver) { + let command_count = self.command_count; + let mut inner = self.inner.take().expect("No inner?"); + let wptr = self.wptr; + core::mem::forget(self); + + inner + .info + .state + .with(|raw, _inner| raw.cpu_wptr.store(wptr, Ordering::Release)); + + inner.wptr = wptr; + + let event = inner.event.as_mut().expect("JobSubmission lost its event"); + + let event_slot = event.0.slot(); + + let msg = fw::channels::RunWorkQueueMsg::ver { + pipe_type: inner.pipe_type, + work_queue: Some(inner.info.weak_pointer()), + wptr: inner.wptr, + event_slot, + is_new: inner.new, + __pad: Default::default(), + }; + channel.send(&msg); + inner.new = false; + + inner.submit_seq += command_count as u64; + } + + pub(crate) fn pipe_type(&self) -> PipeType { + self.inner.as_ref().expect("No inner?").pipe_type + } + + pub(crate) fn priority(&self) -> u32 { + self.inner.as_ref().expect("No inner?").priority + } +} + +#[versions(AGX)] +impl Drop for Job::ver { + fn drop(&mut self) { + mod_pr_debug!("WorkQueue: Dropping Job\n"); + let mut inner = self.wq.inner.lock(); + + if !self.committed { + pr_info!( + "WorkQueue: Dropping uncommitted job with {} events\n", + self.event_count + ); + } + + if self.committed && !self.submitted { + let pipe_type = inner.pipe_type; + let event = inner.event.as_mut().expect("Job lost its event"); + pr_info!( + "WorkQueue({:?}): Roll back {} events (slot {} val {:#x?}) and {} commands\n", + pipe_type, + self.event_count, + event.0.slot(), + event.1, + self.pending.len() + ); + event.1.sub(self.event_count as u32); + inner.commit_seq -= self.pending.len() as u64; + inner.event_seq -= self.event_count as u64; + } + + inner.pending_jobs -= 1; + + if inner.pending.is_empty() && inner.pending_jobs == 0 { + mod_pr_debug!("WorkQueue({:?}): Dropping event\n", inner.pipe_type); + inner.event = None; + inner.last_submitted = None; + inner.last_completed = None; + } + mod_pr_debug!("WorkQueue({:?}): Dropped Job\n", inner.pipe_type); + } +} + +#[versions(AGX)] +impl<'a> Drop for JobSubmission::ver<'a> { + fn drop(&mut self) { + let inner = self.inner.as_mut().expect("No inner?"); + mod_pr_debug!("WorkQueue({:?}): Dropping JobSubmission\n", inner.pipe_type); + + let new_len = inner.pending.len() - self.command_count; + inner.pending.truncate(new_len); + + let pipe_type = inner.pipe_type; + let event = inner.event.as_mut().expect("JobSubmission lost its event"); + pr_info!( + "WorkQueue({:?}): JobSubmission: Roll back {} events (slot {} val {:#x?}) and {} commands\n", + pipe_type, + self.event_count, + event.0.slot(), + event.1, + self.command_count + ); + event.1.sub(self.event_count as u32); + let val = event.1; + inner.commit_seq -= self.command_count as u64; + inner.event_seq -= self.event_count as u64; + inner.last_submitted = Some(val); + mod_pr_debug!("WorkQueue({:?}): Dropped JobSubmission\n", inner.pipe_type); + } +} + +#[versions(AGX)] +impl WorkQueueInner::ver { + /// Return the number of free entries in the workqueue + pub(crate) fn free_space(&self) -> usize { + self.size as usize - self.pending.len() - 1 + } + + pub(crate) fn free_slots(&self) -> usize { + let busy_slots = if let Some(ls) = self.last_submitted { + let lc = self + .last_completed + .expect("last_submitted but not completed?"); + ls.delta(&lc) + } else { + 0 + }; + + ((MAX_JOB_SLOTS as i32) - busy_slots).max(0) as usize + } +} + +#[versions(AGX)] +impl WorkQueue::ver { + /// Create a new WorkQueue of a given type and priority. + #[allow(clippy::too_many_arguments)] + pub(crate) fn new( + dev: &driver::AsahiDevice, + alloc: &mut gpu::KernelAllocators, + event_manager: Arc, + gpu_context: Arc, + notifier_list: Arc>, + pipe_type: PipeType, + id: u64, + priority: u32, + size: u32, + ) -> Result> { + let gpu_buf = alloc.private.array_empty_tagged(0x2c18, b"GPBF")?; + let mut state = alloc.shared.new_default::()?; + let ring = alloc.shared.array_empty(size as usize)?; + let mut prio = *raw::PRIORITY.get(priority as usize).ok_or(EINVAL)?; + + if pipe_type == PipeType::Compute && !debug_enabled(DebugFlags::Debug0) { + // Hack to disable compute preemption until we fix it + prio.0 = 0; + prio.5 = 1; + } + + let inner = WorkQueueInner::ver { + dev: dev.into(), + event_manager, + // Use shared (coherent) state with verbose faults so we can dump state correctly + info: if debug_enabled(DebugFlags::VerboseFaults) { + &mut alloc.shared + } else { + &mut alloc.private + } + .new_init( + try_init!(QueueInfo::ver { + state: { + state.with_mut(|raw, _inner| { + raw.rb_size = size; + }); + state + }, + ring, + gpu_buf, + notifier_list: notifier_list, + gpu_context: gpu_context, + }), + |inner, _p| { + try_init!(raw::QueueInfo::ver { + state: inner.state.gpu_pointer(), + ring: inner.ring.gpu_pointer(), + notifier_list: inner.notifier_list.gpu_pointer(), + gpu_buf: inner.gpu_buf.gpu_pointer(), + gpu_rptr1: Default::default(), + gpu_rptr2: Default::default(), + gpu_rptr3: Default::default(), + event_id: AtomicI32::new(-1), + priority: prio, + unk_4c: -1, + uuid: id as u32, + unk_54: -1, + unk_58: Default::default(), + busy: Default::default(), + __pad: Default::default(), + #[ver(V >= V13_2 && G < G14X)] + unk_84_0: 0, + unk_84_state: Default::default(), + error_count: Default::default(), + unk_8c: 0, + unk_90: 0, + unk_94: 0, + pending: Default::default(), + unk_9c: 0, + gpu_context: inner.gpu_context.gpu_pointer(), + unk_a8: Default::default(), + #[ver(V >= V13_2 && G < G14X)] + unk_b0: 0, + }) + }, + )?, + new: true, + pipe_type, + size, + wptr: 0, + pending: KVec::new(), + last_completed_work: None, + last_token: None, + event: None, + priority, + pending_jobs: 0, + commit_seq: 0, + submit_seq: 0, + event_seq: 0, + last_completed: None, + last_submitted: None, + }; + + let info_pointer = inner.info.weak_pointer(); + + Arc::pin_init( + pin_init!(Self { + info_pointer, + inner <- match pipe_type { + PipeType::Vertex => new_mutex!(inner, "WorkQueue::inner (Vertex)"), + PipeType::Fragment => new_mutex!(inner, "WorkQueue::inner (Fragment)"), + PipeType::Compute => new_mutex!(inner, "WorkQueue::inner (Compute)"), + }, + }), + GFP_KERNEL, + ) + } + + pub(crate) fn event_info(&self) -> Option { + let inner = self.inner.lock(); + + inner.event.as_ref().map(|ev| QueueEventInfo::ver { + stamp_pointer: ev.0.stamp_pointer(), + fw_stamp_pointer: ev.0.fw_stamp_pointer(), + slot: ev.0.slot(), + value: ev.1, + cmd_seq: inner.commit_seq, + event_seq: inner.event_seq, + info_ptr: self.info_pointer, + }) + } + + pub(crate) fn new_job(self: &Arc, fence: dma_fence::Fence) -> Result { + let mut inner = self.inner.lock(); + + if inner.event.is_none() { + mod_pr_debug!("WorkQueue({:?}): Grabbing event\n", inner.pipe_type); + let event = inner.event_manager.get(inner.last_token, self.clone())?; + let cur = event.current(); + inner.last_token = Some(event.token()); + mod_pr_debug!( + "WorkQueue({:?}): Grabbed event slot {}: {:#x?}\n", + inner.pipe_type, + event.slot(), + cur + ); + inner.event = Some((event, cur)); + inner.last_submitted = Some(cur); + inner.last_completed = Some(cur); + } + + inner.pending_jobs += 1; + + let ev = &inner.event.as_ref().unwrap(); + + mod_pr_debug!( + "WorkQueue({:?}): New job at value {:#x?} slot {}\n", + inner.pipe_type, + ev.1, + ev.0.slot() + ); + Ok(Job::ver { + wq: self.clone(), + event_info: QueueEventInfo::ver { + stamp_pointer: ev.0.stamp_pointer(), + fw_stamp_pointer: ev.0.fw_stamp_pointer(), + slot: ev.0.slot(), + value: ev.1, + cmd_seq: inner.commit_seq, + event_seq: inner.event_seq, + info_ptr: self.info_pointer, + }, + start_value: ev.1, + pending: KVec::new(), + event_count: 0, + committed: false, + submitted: false, + fence, + }) + } + + pub(crate) fn pipe_type(&self) -> PipeType { + self.inner.lock().pipe_type + } + + pub(crate) fn dump_info(&self) { + pr_info!("WorkQueue @ {:?}:", self.info_pointer); + self.inner.lock().info.with(|raw, _inner| { + pr_info!(" GPU rptr1: {:#x}", raw.gpu_rptr1.load(Ordering::Relaxed)); + pr_info!(" GPU rptr1: {:#x}", raw.gpu_rptr2.load(Ordering::Relaxed)); + pr_info!(" GPU rptr1: {:#x}", raw.gpu_rptr3.load(Ordering::Relaxed)); + pr_info!(" Event ID: {:#x}", raw.event_id.load(Ordering::Relaxed)); + pr_info!(" Busy: {:#x}", raw.busy.load(Ordering::Relaxed)); + pr_info!(" Unk 84: {:#x}", raw.unk_84_state.load(Ordering::Relaxed)); + pr_info!( + " Error count: {:#x}", + raw.error_count.load(Ordering::Relaxed) + ); + pr_info!(" Pending: {:#x}", raw.pending.load(Ordering::Relaxed)); + }); + } + + pub(crate) fn info_pointer(&self) -> GpuWeakPointer { + self.info_pointer + } +} + +/// Trait used to erase the version-specific type of WorkQueues, to avoid leaking +/// version-specificity into the event module. +pub(crate) trait WorkQueue { + /// Cast as an Any type. + fn as_any(&self) -> &dyn Any; + + fn signal(&self) -> bool; + fn mark_error(&self, value: event::EventValue, error: WorkError); + fn fail_all(&self, error: WorkError); +} + +#[versions(AGX)] +impl WorkQueue for WorkQueue::ver { + fn as_any(&self) -> &dyn Any { + self + } + + /// Signal a workqueue that some work was completed. + /// + /// This will check the event stamp value to find out exactly how many commands were processed. + fn signal(&self) -> bool { + let mut inner = self.inner.lock(); + let event = inner.event.as_ref(); + let value = match event { + None => { + mod_pr_debug!("WorkQueue: signal() called but no event?\n"); + + if inner.pending_jobs > 0 || !inner.pending.is_empty() { + pr_crit!("WorkQueue: signal() called with no event and pending jobs.\n"); + } + return true; + } + Some(event) => event.0.current(), + }; + + if let Some(lc) = inner.last_completed { + if value < lc { + pr_err!( + "WorkQueue: event rolled back? cur {:#x?}, lc {:#x?}, ls {:#x?}", + value, + inner.last_completed, + inner.last_submitted + ); + } + } else { + pr_crit!("WorkQueue: signal() called with no last_completed.\n"); + } + inner.last_completed = Some(value); + + mod_pr_debug!( + "WorkQueue({:?}): Signaling event {:?} value {:#x?}\n", + inner.pipe_type, + inner.last_token, + value + ); + + let mut completed_commands: usize = 0; + + for cmd in inner.pending.iter() { + if cmd.inner.value() <= value { + mod_pr_debug!( + "WorkQueue({:?}): Command at value {:#x?} complete\n", + inner.pipe_type, + cmd.inner.value() + ); + completed_commands += 1; + } else { + break; + } + } + + if completed_commands == 0 { + return inner.pending.is_empty(); + } + + let last_wptr = inner.pending[completed_commands - 1].inner.wptr(); + let pipe_type = inner.pipe_type; + + let mut last_cmd = inner.last_completed_work.take(); + + for mut cmd in inner.pending.drain(..completed_commands) { + mod_pr_debug!( + "WorkQueue({:?}): Queueing command @ {:?} for cleanup\n", + pipe_type, + cmd.inner.gpu_va() + ); + cmd.as_mut().inner_mut().complete(); + if let Some(last_cmd) = last_cmd.replace(cmd) { + workqueue::system().enqueue(last_cmd); + } + } + + inner.last_completed_work = last_cmd; + + mod_pr_debug!( + "WorkQueue({:?}): Completed {} commands, left pending {}, ls {:#x?}, lc {:#x?}\n", + inner.pipe_type, + completed_commands, + inner.pending.len(), + inner.last_submitted, + inner.last_completed, + ); + + inner + .info + .state + .with(|raw, _inner| raw.cpu_freeptr.store(last_wptr, Ordering::Release)); + + let empty = inner.pending.is_empty(); + if empty && inner.pending_jobs == 0 { + inner.event = None; + inner.last_submitted = None; + inner.last_completed = None; + } + + empty + } + + /// Mark this queue's work up to a certain stamp value as having failed. + fn mark_error(&self, value: event::EventValue, error: WorkError) { + // If anything is marked completed, we can consider it successful + // at this point, even if we didn't get the signal event yet. + self.signal(); + + let mut inner = self.inner.lock(); + + if inner.event.is_none() { + mod_pr_debug!("WorkQueue: signal_fault() called but no event?\n"); + + if inner.pending_jobs > 0 || !inner.pending.is_empty() { + pr_crit!("WorkQueue: signal_fault() called with no event and pending jobs.\n"); + } + return; + } + + mod_pr_debug!( + "WorkQueue({:?}): Signaling fault for event {:?} at value {:#x?}\n", + inner.pipe_type, + inner.last_token, + value + ); + + for cmd in inner.pending.iter_mut() { + if cmd.inner.value() <= value { + cmd.as_mut().inner_mut().mark_error(error); + } else { + break; + } + } + } + + /// Mark all of this queue's work as having failed, and complete it. + fn fail_all(&self, error: WorkError) { + // If anything is marked completed, we can consider it successful + // at this point, even if we didn't get the signal event yet. + self.signal(); + + let mut inner = self.inner.lock(); + + if inner.event.is_none() { + mod_pr_debug!("WorkQueue: fail_all() called but no event?\n"); + + if inner.pending_jobs > 0 || !inner.pending.is_empty() { + pr_crit!("WorkQueue: fail_all() called with no event and pending jobs.\n"); + } + return; + } + + mod_pr_debug!( + "WorkQueue({:?}): Failing all jobs {:?}\n", + inner.pipe_type, + error + ); + + let mut cmds = KVec::new(); + + core::mem::swap(&mut inner.pending, &mut cmds); + + if inner.pending_jobs == 0 { + inner.event = None; + } + + core::mem::drop(inner); + + for mut cmd in cmds { + cmd.as_mut().inner_mut().mark_error(error); + cmd.as_mut().inner_mut().complete(); + } + } +} + +#[versions(AGX)] +impl Drop for WorkQueueInner::ver { + fn drop(&mut self) { + if let Some(last_cmd) = self.last_completed_work.take() { + workqueue::system().enqueue(last_cmd); + } + } +} From f7572f7a7f20259cb1197853434904c3d65962b8 Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Thu, 4 Dec 2025 09:03:25 +0100 Subject: [PATCH 1278/3902] drm/asahi: initdata: Fold GlobalsSub struct into Globals With commit 42415d163e5d ("rust: pin-init: add references to previously initialized fields") "#[repr(C, packed)]" structs can no longer be embedded into (pin-)init structs because they have an lignment of 1. Signed-off-by: Janne Grunau --- drivers/gpu/drm/asahi/fw/initdata.rs | 35 +++++++++++----------------- drivers/gpu/drm/asahi/initdata.rs | 6 ++--- 2 files changed, 17 insertions(+), 24 deletions(-) diff --git a/drivers/gpu/drm/asahi/fw/initdata.rs b/drivers/gpu/drm/asahi/fw/initdata.rs index c8cb348056961a..7c7488e950acef 100644 --- a/drivers/gpu/drm/asahi/fw/initdata.rs +++ b/drivers/gpu/drm/asahi/fw/initdata.rs @@ -1007,26 +1007,6 @@ pub(crate) mod raw { } default_zeroed!(FaultInfo); - #[versions(AGX)] - #[derive(Debug, Clone, Copy)] - #[repr(C, packed)] - pub(crate) struct GlobalsSub { - pub(crate) unk_54: u16, - pub(crate) unk_56: u16, - pub(crate) unk_58: u16, - pub(crate) unk_5a: U32, - pub(crate) unk_5e: U32, - pub(crate) unk_62: U32, - - #[ver(V >= V13_0B4)] - pub(crate) unk_66_0: Array<0xc, u8>, - - pub(crate) unk_66: U32, - pub(crate) unk_6a: Array<0x16, u8>, - } - #[versions(AGX)] - default_zeroed!(GlobalsSub::ver); - #[derive(Debug, Clone, Copy)] #[repr(C)] pub(crate) struct PowerZoneGlobal { @@ -1064,7 +1044,20 @@ pub(crate) mod raw { pub(crate) unk_34: u32, pub(crate) unk_38: Array<0x1c, u8>, - pub(crate) sub: GlobalsSub::ver, + // pub(crate) sub: GlobalsSub::ver, + pub(crate) unk_54: u16, + pub(crate) unk_56: u16, + pub(crate) unk_58: u16, + pub(crate) unk_5a: U32, + pub(crate) unk_5e: U32, + pub(crate) unk_62: U32, + + #[ver(V >= V13_0B4)] + pub(crate) unk_66_0: Array<0xc, u8>, + + pub(crate) unk_66: U32, + pub(crate) unk_6a: Array<0x16, u8>, + // end GlobalsSub::ver pub(crate) unk_80: Array<0xf80, u8>, pub(crate) unk_1000: Array<0x7000, u8>, diff --git a/drivers/gpu/drm/asahi/initdata.rs b/drivers/gpu/drm/asahi/initdata.rs index 4573c3ca29b2fc..24957a0e148515 100644 --- a/drivers/gpu/drm/asahi/initdata.rs +++ b/drivers/gpu/drm/asahi/initdata.rs @@ -670,14 +670,14 @@ impl<'a> InitDataBuilder::ver<'a> { unk_2c: 1, unk_30: 0, unk_34: 120, - sub <- try_init!(raw::GlobalsSub::ver { + // sub <- try_init!(raw::GlobalsSub::ver { unk_54: cfg.global_unk_54, unk_56: 40, unk_58: 0xffff, unk_5e: U32(1), unk_66: U32(1), - ..Zeroable::init_zeroed() - }), + // ..Zeroable::init_zeroed() + // }), unk_8900: 1, pending_submissions: AtomicU32::new(0), max_power: pwr.max_power_mw, From 2698fa4121f2856fa9fd6972c2fbd1d6691e84b1 Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Thu, 4 Dec 2025 21:50:30 +0100 Subject: [PATCH 1279/3902] drm/asahi: Avoid variable/field ref shadowing in pin-init Signed-off-by: Janne Grunau --- drivers/gpu/drm/asahi/initdata.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/drivers/gpu/drm/asahi/initdata.rs b/drivers/gpu/drm/asahi/initdata.rs index 24957a0e148515..3436522b95cb98 100644 --- a/drivers/gpu/drm/asahi/initdata.rs +++ b/drivers/gpu/drm/asahi/initdata.rs @@ -237,7 +237,7 @@ impl<'a> InitDataBuilder::ver<'a> { #[allow(unused_variables)] let base_clock_khz = self.cfg.base_clock_hz / 1000; - let clocks_per_period = pwr.pwr_sample_period_aic_clks; + let v_clocks_per_period = pwr.pwr_sample_period_aic_clks; #[allow(unused_variables)] let clocks_per_period_coarse = self.cfg.base_clock_hz / 1000 * pwr.power_sample_period; @@ -248,9 +248,9 @@ impl<'a> InitDataBuilder::ver<'a> { let cfg = &self.cfg; let dyncfg = &self.dyncfg; try_init!(raw::HwDataA::ver { - clocks_per_period: clocks_per_period, + clocks_per_period: v_clocks_per_period, #[ver(V >= V13_0B4)] - clocks_per_period_2: clocks_per_period, + clocks_per_period_2: v_clocks_per_period, pwr_status: AtomicU32::new(4), unk_10: f32!(1.0), actual_pstate: 1, From ab536b6d5f8450ef85ffece537aea7775bfc1166 Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Wed, 17 Dec 2025 08:56:19 +0100 Subject: [PATCH 1280/3902] drm/asahi: Move unsafe data initialization to driver code Keep the drm_device private data initialization after device creation hacks out of rust/drm/device.rs. This will hopefully soon be solved by device context for drm::device::Device. Signed-off-by: Janne Grunau --- drivers/gpu/drm/asahi/driver.rs | 25 ++++++++++++++++--------- 1 file changed, 16 insertions(+), 9 deletions(-) diff --git a/drivers/gpu/drm/asahi/driver.rs b/drivers/gpu/drm/asahi/driver.rs index 14bfc7cb4253f4..7f407bfad44d24 100644 --- a/drivers/gpu/drm/asahi/driver.rs +++ b/drivers/gpu/drm/asahi/driver.rs @@ -178,25 +178,31 @@ impl platform::Driver for AsahiDriver { .property_read_array_vec(c_str!("apple,firmware-compat"), 3)? .required_by(pdev.as_ref())?; - let raw_drm = unsafe { drm::device::Device::::new_uninit(pdev.as_ref())? }; - - let drm: AsahiDevRef = unsafe { ARef::from_raw(raw_drm) }; + // TODO: This is very temporary + // SAFETY: This should be safe as data is not touched by the driver + // untill it gets fully initialised. + // Additionally drm::device::Device::release() will not drop data and + // leaks instead. + let uninit = unsafe { + pin_init::pin_init_from_closure::(|_slot| Ok(())) + }; + let drm: ARef = drm::device::Device::new(pdev.as_ref(), uninit)?; let gpu = match (cfg.gpu_gen, cfg.gpu_variant, compat.as_slice()) { (hw::GpuGen::G13, _, &[12, 3, 0]) => { - gpu::GpuManagerG13V12_3::new(&drm, &res, cfg)? as Arc + gpu::GpuManagerG13V12_3::new(&drm.clone(), &res, cfg)? as Arc } (hw::GpuGen::G14, hw::GpuVariant::G, &[12, 4, 0]) => { - gpu::GpuManagerG14V12_4::new(&drm, &res, cfg)? as Arc + gpu::GpuManagerG14V12_4::new(&drm.clone(), &res, cfg)? as Arc } (hw::GpuGen::G13, _, &[13, 5, 0]) => { - gpu::GpuManagerG13V13_5::new(&drm, &res, cfg)? as Arc + gpu::GpuManagerG13V13_5::new(&drm.clone(), &res, cfg)? as Arc } (hw::GpuGen::G14, hw::GpuVariant::G, &[13, 5, 0]) => { - gpu::GpuManagerG14V13_5::new(&drm, &res, cfg)? as Arc + gpu::GpuManagerG14V13_5::new(&drm.clone(), &res, cfg)? as Arc } (hw::GpuGen::G14, _, &[13, 5, 0]) => { - gpu::GpuManagerG14XV13_5::new(&drm, &res, cfg)? as Arc + gpu::GpuManagerG14XV13_5::new(&drm.clone(), &res, cfg)? as Arc } _ => { dev_info!( @@ -216,7 +222,8 @@ impl platform::Driver for AsahiDriver { resources: res, }); - let drm = unsafe { AsahiDevice::init_data(raw_drm, data)? }; + let ptr: *const AsahiData = &raw const **drm; + unsafe { data.__pinned_init(ptr as *mut AsahiData)?; } (*drm).gpu.init()?; From 6c4b546ae76cd21de5d394be0611f811c12011ec Mon Sep 17 00:00:00 2001 From: Asahi Lina Date: Tue, 10 Dec 2024 03:29:46 +0900 Subject: [PATCH 1281/3902] rust: page: Convert to Ownable This allows Page references to be returned as borrowed references, without necessarily owning the struct page. Signed-off-by: Asahi Lina --- rust/kernel/page.rs | 27 ++++++++++++++++----------- 1 file changed, 16 insertions(+), 11 deletions(-) diff --git a/rust/kernel/page.rs b/rust/kernel/page.rs index 432fc0297d4a85..73ef4d9b04973d 100644 --- a/rust/kernel/page.rs +++ b/rust/kernel/page.rs @@ -7,6 +7,7 @@ use crate::{ bindings, error::code::*, error::Result, + types::{Opaque, Ownable, Owned}, uaccess::UserSliceReader, }; use core::{ @@ -77,7 +78,7 @@ pub const fn page_align(addr: usize) -> usize { /// /// [`VBox`]: kernel::alloc::VBox /// [`Vmalloc`]: kernel::alloc::allocator::Vmalloc -pub struct BorrowedPage<'a>(ManuallyDrop, PhantomData<&'a Page>); +pub struct BorrowedPage<'a>(ManuallyDrop>, PhantomData<&'a Page>); impl<'a> BorrowedPage<'a> { /// Constructs a [`BorrowedPage`] from a raw pointer to a `struct page`. @@ -87,7 +88,7 @@ impl<'a> BorrowedPage<'a> { /// - `ptr` must point to a valid `bindings::page`. /// - `ptr` must remain valid for the entire lifetime `'a`. pub unsafe fn from_raw(ptr: NonNull) -> Self { - let page = Page { page: ptr }; + let page = unsafe { Page::from_phys(bindings::page_to_phys(ptr.as_ptr())) }; // INVARIANT: The safety requirements guarantee that `ptr` is valid for the entire lifetime // `'a`. @@ -120,8 +121,9 @@ pub trait AsPageIter { /// # Invariants /// /// The pointer is valid, and has ownership over the page. +#[repr(transparent)] pub struct Page { - page: NonNull, + page: Opaque, } // SAFETY: Pages have no logic that relies on them staying on a given thread, so moving them across @@ -155,19 +157,20 @@ impl Page { /// # Ok::<(), kernel::alloc::AllocError>(()) /// ``` #[inline] - pub fn alloc_page(flags: Flags) -> Result { + pub fn alloc_page(flags: Flags) -> Result, AllocError> { // SAFETY: Depending on the value of `gfp_flags`, this call may sleep. Other than that, it // is always safe to call this method. let page = unsafe { bindings::alloc_pages(flags.as_raw(), 0) }; let page = NonNull::new(page).ok_or(AllocError)?; - // INVARIANT: We just successfully allocated a page, so we now have ownership of the newly - // allocated page. We transfer that ownership to the new `Page` object. - Ok(Self { page }) + // SAFETY: We just successfully allocated a page, so we now have ownership of the newly + // allocated page. We transfer that ownership to the new `Owned` object. + // Since `Page` is transparent, we can cast the pointer directly. + Ok(unsafe { Owned::from_raw(page.cast()) }) } /// Returns a raw pointer to the page. pub fn as_ptr(&self) -> *mut bindings::page { - self.page.as_ptr() + Opaque::cast_into(&self.page) } /// Get the node id containing this page. @@ -342,10 +345,12 @@ impl Page { } } -impl Drop for Page { +// SAFETY: See below. +unsafe impl Ownable for Page { #[inline] - fn drop(&mut self) { + unsafe fn release(this: NonNull) { // SAFETY: By the type invariants, we have ownership of the page and can free it. - unsafe { bindings::__free_pages(self.page.as_ptr(), 0) }; + // Since Page is transparent, we can cast the raw pointer directly. + unsafe { bindings::__free_pages(this.cast().as_ptr(), 0) }; } } From e966feb6ccb78a7a05a809168aca30eb55d400ed Mon Sep 17 00:00:00 2001 From: Asahi Lina Date: Tue, 10 Dec 2024 03:30:28 +0900 Subject: [PATCH 1282/3902] rust: page: Make with_page_mapped() and with_pointer_into_page() public Lets users do (unsafe) complex page read/write operations without having to repeatedly call into read_raw()/write_raw() (which may be expensive in some cases). Signed-off-by: Asahi Lina --- rust/kernel/page.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/rust/kernel/page.rs b/rust/kernel/page.rs index 73ef4d9b04973d..d7718fd0f9fe26 100644 --- a/rust/kernel/page.rs +++ b/rust/kernel/page.rs @@ -195,7 +195,7 @@ impl Page { /// different addresses. However, even if the addresses are different, the underlying memory is /// still the same for these purposes (e.g., it's still a data race if they both write to the /// same underlying byte at the same time). - fn with_page_mapped(&self, f: impl FnOnce(*mut u8) -> T) -> T { + pub fn with_page_mapped(&self, f: impl FnOnce(*mut u8) -> T) -> T { // SAFETY: `page` is valid due to the type invariants on `Page`. let mapped_addr = unsafe { bindings::kmap_local_page(self.as_ptr()) }; @@ -236,7 +236,7 @@ impl Page { /// different addresses. However, even if the addresses are different, the underlying memory is /// still the same for these purposes (e.g., it's still a data race if they both write to the /// same underlying byte at the same time). - fn with_pointer_into_page( + pub fn with_pointer_into_page( &self, off: usize, len: usize, From c27b7b3376d388e5d1667c8905f067ae0767879b Mon Sep 17 00:00:00 2001 From: Asahi Lina Date: Tue, 10 Dec 2024 04:03:40 +0900 Subject: [PATCH 1283/3902] rust: addr: Add a module to declare core address types Encapsulates the core physical/DMA address types, so they can be used by Rust abstractions. Signed-off-by: Asahi Lina --- rust/kernel/addr.rs | 15 +++++++++++++++ rust/kernel/lib.rs | 1 + 2 files changed, 16 insertions(+) create mode 100644 rust/kernel/addr.rs diff --git a/rust/kernel/addr.rs b/rust/kernel/addr.rs new file mode 100644 index 00000000000000..06aff10a033235 --- /dev/null +++ b/rust/kernel/addr.rs @@ -0,0 +1,15 @@ +// SPDX-License-Identifier: GPL-2.0 + +//! Kernel core address types. + +use bindings; +use core::ffi; + +/// A physical memory address (which may be wider than the CPU pointer size) +pub type PhysicalAddr = bindings::phys_addr_t; +/// A DMA memory address (which may be narrower than `PhysicalAddr` on some systems) +pub type DmaAddr = bindings::dma_addr_t; +/// A physical resource size, typically the same width as `PhysicalAddr` +pub type ResourceSize = bindings::resource_size_t; +/// A raw page frame number, not to be confused with the C `pfn_t` which also encodes flags. +pub type Pfn = ffi::c_ulong; diff --git a/rust/kernel/lib.rs b/rust/kernel/lib.rs index 1b4c3adda8e5d9..81accf0118bd0d 100644 --- a/rust/kernel/lib.rs +++ b/rust/kernel/lib.rs @@ -77,6 +77,7 @@ extern crate self as kernel; pub use ffi; pub mod acpi; +pub mod addr; pub mod alloc; #[cfg(CONFIG_AUXILIARY_BUS)] pub mod auxiliary; From 39ce531ac9762312f15f62b223120f760d779917 Mon Sep 17 00:00:00 2001 From: Asahi Lina Date: Tue, 10 Dec 2024 04:04:55 +0900 Subject: [PATCH 1284/3902] rust: page: Add physical address conversion functions Add methods to allow code using the Page type to obtain the physical address of a page, convert to and from an (owned) physical address, and borrow a Page from a physical address. Most of these operations are, as you might expect, unsafe. These primitives are useful to implement page table structures in Rust, and to implement arbitrary physical memory access (as needed to walk arbitrary page tables and dereference through them). These mechanisms are, of course, fraught with danger, and are only expected to be used for core memory management code (in e.g. drivers with their own device page table implementations) and for debug features such as crash dumps of device memory. Signed-off-by: Asahi Lina --- rust/helpers/page.c | 26 ++++++++++++++++++ rust/kernel/page.rs | 64 +++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 90 insertions(+) diff --git a/rust/helpers/page.c b/rust/helpers/page.c index 7144de5a61dbdb..1e15bb4d525b59 100644 --- a/rust/helpers/page.c +++ b/rust/helpers/page.c @@ -1,5 +1,6 @@ // SPDX-License-Identifier: GPL-2.0 +#include #include #include #include @@ -25,3 +26,28 @@ int rust_helper_page_to_nid(const struct page *page) return page_to_nid(page); } #endif + +struct page *rust_helper_phys_to_page(phys_addr_t phys) +{ + return phys_to_page(phys); +} + +phys_addr_t rust_helper_page_to_phys(struct page *page) +{ + return page_to_phys(page); +} + +unsigned long rust_helper_phys_to_pfn(phys_addr_t phys) +{ + return __phys_to_pfn(phys); +} + +struct page *rust_helper_pfn_to_page(unsigned long pfn) +{ + return pfn_to_page(pfn); +} + +bool rust_helper_pfn_valid(unsigned long pfn) +{ + return pfn_valid(pfn); +} diff --git a/rust/kernel/page.rs b/rust/kernel/page.rs index d7718fd0f9fe26..c57311c2a64fed 100644 --- a/rust/kernel/page.rs +++ b/rust/kernel/page.rs @@ -3,6 +3,7 @@ //! Kernel page allocation and management. use crate::{ + addr::*, alloc::{AllocError, Flags}, bindings, error::code::*, @@ -343,6 +344,69 @@ impl Page { reader.read_raw(unsafe { core::slice::from_raw_parts_mut(dst.cast(), len) }) }) } + + /// Returns the physical address of this page. + pub fn phys(&self) -> PhysicalAddr { + // SAFETY: `page` is valid due to the type invariants on `Page`. + unsafe { bindings::page_to_phys(self.as_ptr()) } + } + + /// Converts a Rust-owned Page into its physical address. + /// The caller is responsible for calling `from_phys()` to avoid + /// leaking memory. + pub fn into_phys(this: Owned) -> PhysicalAddr { + ManuallyDrop::new(this).phys() + } + + /// Converts a physical address to a Rust-owned Page. + /// + /// SAFETY: + /// The caller must ensure that the physical address was previously returned + /// by a call to `Page::into_phys()`, and that the physical address is no + /// longer used after this call, nor is `from_phys()` called again on it. + pub unsafe fn from_phys(phys: PhysicalAddr) -> Owned { + // SAFETY: By the safety requirements, the physical address must be valid and + // have come from `into_phys()`, so phys_to_page() cannot fail and + // must return the original struct page pointer. + unsafe { Owned::from_raw(NonNull::new_unchecked(bindings::phys_to_page(phys)).cast()) } + } + + /// Borrows a Page from a physical address, without taking over ownership. + /// + /// If the physical address does not have a `struct page` entry or is not + /// part of the System RAM region, returns None. + /// + /// SAFETY: + /// The caller must ensure that the physical address, if it is backed by a + /// `struct page`, remains available for the duration of the borrowed + /// lifetime. + pub unsafe fn borrow_phys(phys: &PhysicalAddr) -> Option<&Self> { + // SAFETY: This is always safe, as it is just arithmetic + let pfn = unsafe { bindings::phys_to_pfn(*phys) }; + // SAFETY: This function is safe to call with any pfn + if !unsafe { bindings::pfn_valid(pfn) && bindings::page_is_ram(pfn) != 0 } { + None + } else { + // SAFETY: We have just checked that the pfn is valid above, so it must + // have a corresponding struct page. By the safety requirements, we can + // return a borrowed reference to it. + Some(unsafe { &*(bindings::pfn_to_page(pfn) as *mut Self as *const Self) }) + } + } + + /// Borrows a Page from a physical address, without taking over ownership + /// nor checking for validity. + /// + /// SAFETY: + /// The caller must ensure that the physical address is backed by a + /// `struct page` and corresponds to System RAM. + pub unsafe fn borrow_phys_unchecked(phys: &PhysicalAddr) -> &Self { + // SAFETY: This is always safe, as it is just arithmetic + let pfn = unsafe { bindings::phys_to_pfn(*phys) }; + // SAFETY: The caller guarantees that the pfn is valid. By the safety + // requirements, we can return a borrowed reference to it. + unsafe { &*(bindings::pfn_to_page(pfn) as *mut Self as *const Self) } + } } // SAFETY: See below. From f19a78b0d815832149cd71f0831f5ebb3b7e615c Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Sat, 20 Dec 2025 23:31:38 +0100 Subject: [PATCH 1285/3902] drm/asahi: RiiR page tables --- drivers/gpu/drm/asahi/alloc.rs | 10 +- drivers/gpu/drm/asahi/asahi.rs | 1 + drivers/gpu/drm/asahi/gem.rs | 6 +- drivers/gpu/drm/asahi/mmu.rs | 337 +++++++-------------- drivers/gpu/drm/asahi/pgtable.rs | 496 +++++++++++++++++++++++++++++++ 5 files changed, 620 insertions(+), 230 deletions(-) create mode 100644 drivers/gpu/drm/asahi/pgtable.rs diff --git a/drivers/gpu/drm/asahi/alloc.rs b/drivers/gpu/drm/asahi/alloc.rs index cf3908960e5f74..2711b784843300 100644 --- a/drivers/gpu/drm/asahi/alloc.rs +++ b/drivers/gpu/drm/asahi/alloc.rs @@ -36,8 +36,8 @@ use crate::object::{ use crate::util::RangeExt; use core::cmp::Ordering; +use core::fmt; use core::fmt::{ - self, Debug, Formatter, // }; @@ -469,7 +469,7 @@ impl RawAllocation for SimpleAllocation { pub(crate) struct SimpleAllocator { dev: AsahiDevRef, range: Range, - prot: u32, + prot: mmu::Prot, vm: mmu::Vm, min_align: usize, cpu_maps: bool, @@ -484,7 +484,7 @@ impl SimpleAllocator { vm: &mmu::Vm, range: Range, min_align: usize, - prot: u32, + prot: mmu::Prot, _block_size: usize, mut cpu_maps: bool, _name: fmt::Arguments<'_>, @@ -678,7 +678,7 @@ pub(crate) struct HeapAllocator { dev: AsahiDevRef, range: Range, top: u64, - prot: u32, + prot: mmu::Prot, vm: mmu::Vm, min_align: usize, block_size: usize, @@ -698,7 +698,7 @@ impl HeapAllocator { vm: &mmu::Vm, range: Range, min_align: usize, - prot: u32, + prot: mmu::Prot, block_size: usize, mut cpu_maps: bool, name: fmt::Arguments<'_>, diff --git a/drivers/gpu/drm/asahi/asahi.rs b/drivers/gpu/drm/asahi/asahi.rs index 85325ccfb6e74b..016b6f5cfdf03e 100644 --- a/drivers/gpu/drm/asahi/asahi.rs +++ b/drivers/gpu/drm/asahi/asahi.rs @@ -20,6 +20,7 @@ mod mem; mod microseq; mod mmu; mod object; +mod pgtable; mod queue; mod regs; mod slotalloc; diff --git a/drivers/gpu/drm/asahi/gem.rs b/drivers/gpu/drm/asahi/gem.rs index c2f58aa29ce4b1..8affba257d956f 100644 --- a/drivers/gpu/drm/asahi/gem.rs +++ b/drivers/gpu/drm/asahi/gem.rs @@ -99,7 +99,7 @@ impl ObjectRef { vm: &crate::mmu::Vm, range: Range, alignment: u64, - prot: u32, + prot: mmu::Prot, guard: bool, ) -> Result { // Only used for kernel objects now @@ -116,7 +116,7 @@ impl ObjectRef { obj_range: Range, range: Range, alignment: u64, - prot: u32, + prot: mmu::Prot, guard: bool, ) -> Result { if obj_range.end > self.gem.size() { @@ -137,7 +137,7 @@ impl ObjectRef { &mut self, vm: &crate::mmu::Vm, addr: u64, - prot: u32, + prot: mmu::Prot, guard: bool, ) -> Result { if self.gem.flags & uapi::drm_asahi_gem_flags_DRM_ASAHI_GEM_VM_PRIVATE != 0 diff --git a/drivers/gpu/drm/asahi/mmu.rs b/drivers/gpu/drm/asahi/mmu.rs index 871b20855a65bc..0bc33060de4ad6 100644 --- a/drivers/gpu/drm/asahi/mmu.rs +++ b/drivers/gpu/drm/asahi/mmu.rs @@ -23,6 +23,7 @@ use core::sync::atomic::{ }; use kernel::{ + addr::PhysicalAddr, c_str, device, drm::{ @@ -32,12 +33,6 @@ use kernel::{ }, error::Result, io, - io_pgtable, - io_pgtable::{ - prot, - AppleUAT, - IoPageTable, // - }, new_mutex, prelude::*, static_lock_class, @@ -66,10 +61,23 @@ use crate::{ gem, hw, mem, + pgtable, slotalloc, util::RangeExt, // }; +// KernelMapping protection types +pub(crate) use crate::pgtable::Prot; +pub(crate) use pgtable::prot::*; +pub(crate) use pgtable::{ + UatPageTable, + UAT_PGBIT, + UAT_PGMSK, + UAT_PGSZ, // +}; + +use pgtable::UAT_IAS; + use pin_init; const DEBUG_CLASS: DebugFlags = DebugFlags::Mmu; @@ -109,51 +117,9 @@ pub(crate) const IOVA_UNK_PAGE: u64 = IOVA_USER_TOP - 2 * UAT_PGSZ as u64; /// User VA range excluding the unk page pub(crate) const IOVA_USER_USABLE_RANGE: Range = IOVA_USER_BASE..IOVA_UNK_PAGE; -// KernelMapping protection types - -// Note: prot::CACHE means "cache coherency", which for UAT means *uncached*, -// since uncached mappings from the GFX ASC side are cache coherent with the AP cache. -// Not having that flag means *cached noncoherent*. - -/// Firmware MMIO R/W -pub(crate) const PROT_FW_MMIO_RW: u32 = - prot::PRIV | prot::READ | prot::WRITE | prot::CACHE | prot::MMIO; -/// Firmware MMIO R/O -pub(crate) const PROT_FW_MMIO_RO: u32 = prot::PRIV | prot::READ | prot::CACHE | prot::MMIO; -/// Firmware shared (uncached) RW -pub(crate) const PROT_FW_SHARED_RW: u32 = prot::PRIV | prot::READ | prot::WRITE | prot::CACHE; -/// Firmware shared (uncached) RO -pub(crate) const PROT_FW_SHARED_RO: u32 = prot::PRIV | prot::READ | prot::CACHE; -/// Firmware private (cached) RW -pub(crate) const PROT_FW_PRIV_RW: u32 = prot::PRIV | prot::READ | prot::WRITE; -/* -/// Firmware private (cached) RO -pub(crate) const PROT_FW_PRIV_RO: u32 = prot::PRIV | prot::READ; -*/ -/// Firmware/GPU shared (uncached) RW -pub(crate) const PROT_GPU_FW_SHARED_RW: u32 = prot::READ | prot::WRITE | prot::CACHE; -/// Firmware/GPU shared (private) RW -pub(crate) const PROT_GPU_FW_PRIV_RW: u32 = prot::READ | prot::WRITE; -/// Firmware-RW/GPU-RO shared (private) RW -pub(crate) const PROT_GPU_RO_FW_PRIV_RW: u32 = prot::PRIV | prot::WRITE; -/// GPU shared/coherent RW -pub(crate) const PROT_GPU_SHARED_RW: u32 = prot::READ | prot::WRITE | prot::CACHE | prot::NOEXEC; -/// GPU shared/coherent RO -pub(crate) const PROT_GPU_SHARED_RO: u32 = prot::READ | prot::CACHE | prot::NOEXEC; -/// GPU shared/coherent WO -pub(crate) const PROT_GPU_SHARED_WO: u32 = prot::WRITE | prot::CACHE | prot::NOEXEC; -/* -/// GPU private/noncoherent RW -pub(crate) const PROT_GPU_PRIV_RW: u32 = prot::READ | prot::WRITE | prot::NOEXEC; -/// GPU private/noncoherent RO -pub(crate) const PROT_GPU_PRIV_RO: u32 = prot::READ | prot::NOEXEC; -*/ - -type PhysAddr = bindings::phys_addr_t; - /// A pre-allocated memory region for UAT management struct UatRegion { - base: PhysAddr, + base: PhysicalAddr, map: io::mem::Mem, } @@ -208,7 +174,7 @@ struct VmInner { dev: driver::AsahiDevRef, is_kernel: bool, va_range: Range, - page_table: AppleUAT, + page_table: UatPageTable, mm: mm::Allocator<(), KernelMappingInner>, uat_inner: Arc, binding: Arc>, @@ -244,7 +210,7 @@ struct StepContext { prev_va: Option>>>, next_va: Option>>>, vm_bo: Option>>, - prot: u32, + prot: Prot, } impl gpuvm::DriverGpuVm for VmInner { @@ -296,7 +262,8 @@ impl gpuvm::DriverGpuVm for VmInner { iova ); - self.map_pages(iova, addr, UAT_PGSZ, len >> UAT_PGBIT, ctx.prot)?; + self.page_table + .map_pages(iova..(iova + len as u64), addr as PhysicalAddr, ctx.prot)?; left -= len; iova += len as u64; @@ -332,7 +299,8 @@ impl gpuvm::DriverGpuVm for VmInner { mod_dev_dbg!(self.dev, "MMU: unmap: {:#x}:{:#x}\n", va.addr(), va.range()); - self.unmap_pages(va.addr(), UAT_PGSZ, (va.range() >> UAT_PGBIT) as usize)?; + self.page_table + .unmap_pages(va.addr()..(va.addr() + va.range()))?; if let Some(asid) = self.slot() { fence(Ordering::SeqCst); @@ -375,18 +343,18 @@ impl gpuvm::DriverGpuVm for VmInner { orig_addr + orig_range }; - let unmap_range = unmap_end - unmap_start; - mod_dev_dbg!( self.dev, - "MMU: unmap for remap: {:#x}:{:#x} (from {:#x}:{:#x})\n", + "MMU: unmap for remap: {:#x}..{:#x} (from {:#x}:{:#x})\n", unmap_start, - unmap_range, + unmap_end, orig_addr, orig_range ); - self.unmap_pages(unmap_start, UAT_PGSZ, (unmap_range >> UAT_PGBIT) as usize)?; + let unmap_range = unmap_end - unmap_start; + + self.page_table.unmap_pages(unmap_start..unmap_end)?; if let Some(asid) = self.slot() { fence(Ordering::SeqCst); @@ -456,78 +424,22 @@ impl VmInner { /// Returns the translation table base for this Vm fn ttb(&self) -> u64 { - self.page_table.cfg().ttbr - } - - /// Map an IOVA to the shifted address the underlying io_pgtable uses. - fn map_iova(&self, iova: u64, size: usize) -> Result { - if !self.va_range.is_superset(iova..(iova + size as u64)) { - Err(EINVAL) - } else if self.is_kernel { - Ok(iova - self.va_range.start) - } else { - Ok(iova) - } - } - - /// Map a contiguous range of virtual->physical pages. - fn map_pages( - &mut self, - mut iova: u64, - mut paddr: usize, - pgsize: usize, - pgcount: usize, - prot: u32, - ) -> Result { - let mut left = pgcount; - while left > 0 { - let mapped_iova = self.map_iova(iova, pgsize * left)?; - let mapped = - self.page_table - .map_pages(mapped_iova as usize, paddr, pgsize, left, prot)?; - assert!(mapped <= left * pgsize); - - left -= mapped / pgsize; - paddr += mapped; - iova += mapped as u64; - } - Ok(pgcount * pgsize) - } - - /// Unmap a contiguous range of pages. - fn unmap_pages(&mut self, mut iova: u64, pgsize: usize, pgcount: usize) -> Result { - let mut left = pgcount; - while left > 0 { - let mapped_iova = self.map_iova(iova, pgsize * left)?; - let mut unmapped = self - .page_table - .unmap_pages(mapped_iova as usize, pgsize, left); - if unmapped == 0 { - dev_err!( - self.dev.as_ref(), - "unmap_pages {:#x}:{:#x} returned 0\n", - mapped_iova, - left - ); - unmapped = pgsize; // Pretend we unmapped one page and try again... - } - assert!(unmapped <= left * pgsize); - - left -= unmapped / pgsize; - iova += unmapped as u64; - } - - Ok(pgcount * pgsize) + self.page_table.ttb() } /// Map an `mm::Node` representing an mapping in VA space. - fn map_node(&mut self, node: &mm::Node<(), KernelMappingInner>, prot: u32) -> Result { + fn map_node(&mut self, node: &mm::Node<(), KernelMappingInner>, prot: Prot) -> Result { let mut iova = node.start(); let guard = node.bo.as_ref().ok_or(EINVAL)?.inner().sgt.lock(); let sgt = guard.as_ref().ok_or(EINVAL)?; let mut offset = node.offset; + let mut left = node.mapped_size; + + for range in sgt.iter() { + if left == 0 { + break; + } - for range in unsafe { sgt.iter_raw() } { // TODO: proper DMA address/length handling let mut addr = range.dma_address() as usize; let mut len: usize = range.dma_len() as usize; @@ -550,6 +462,8 @@ impl VmInner { offset -= skip; } + len = len.min(left); + if len == 0 { continue; } @@ -562,9 +476,11 @@ impl VmInner { iova ); - self.map_pages(iova, addr, UAT_PGSZ, len >> UAT_PGBIT, prot)?; + self.page_table + .map_pages(iova..(iova + len as u64), addr as PhysicalAddr, prot)?; iova += len as u64; + left -= len; } Ok(()) } @@ -641,7 +557,7 @@ pub(crate) struct KernelMappingInner { _gem: Option>, owner: ARef>, uat_inner: Arc, - prot: u32, + prot: Prot, offset: usize, mapped_size: usize, } @@ -660,13 +576,18 @@ impl KernelMapping { self.0.mapped_size } + /// Returns the IOVA base of this mapping + pub(crate) fn iova_range(&self) -> Range { + self.0.start()..(self.0.start() + self.0.mapped_size as u64) + } + /// Remap a cached mapping as uncached, then synchronously flush that range of VAs from the /// coprocessor cache. This is required to safely unmap cached/private mappings. fn remap_uncached_and_flush(&mut self) { let mut owner = self .0 .owner - .exec_lock(None) + .exec_lock(None, false) .expect("Failed to exec_lock in remap_uncached_and_flush"); mod_dev_dbg!( @@ -676,23 +597,14 @@ impl KernelMapping { self.size() ); - // The IOMMU API does not allow us to remap things in-place... - // just do an unmap and map again for now. - // Do not try to unmap guard page (-1) + // Remap in-place as uncached. + // Do not try to unmap the guard page (-1) + let prot = self.0.prot.as_uncached(); if owner - .unmap_pages(self.iova(), UAT_PGSZ, self.size() >> UAT_PGBIT) + .page_table + .reprot_pages(self.iova_range(), prot) .is_err() { - dev_err!( - owner.dev.as_ref(), - "MMU: unmap for remap {:#x}:{:#x} failed\n", - self.iova(), - self.size() - ); - } - - let prot = self.0.prot | prot::CACHE; - if owner.map_node(&self.0, prot).is_err() { dev_err!( owner.dev.as_ref(), "MMU: remap {:#x}:{:#x} failed\n", @@ -821,15 +733,19 @@ impl Drop for KernelMapping { // 4. Unmap // 5. Flush the TLB range again - // prot::CACHE means "cache coherent" which means *uncached* here. - if self.0.prot & prot::CACHE == 0 { + if self.0.prot.is_cached_noncoherent() { + mod_pr_debug!( + "MMU: remap as uncached {:#x}:{:#x}\n", + self.iova(), + self.size() + ); self.remap_uncached_and_flush(); } let mut owner = self .0 .owner - .exec_lock(None) + .exec_lock(None, false) .expect("exec_lock failed in KernelMapping::drop"); mod_dev_dbg!( owner.dev, @@ -838,10 +754,7 @@ impl Drop for KernelMapping { self.size() ); - if owner - .unmap_pages(self.iova(), UAT_PGSZ, self.size() >> UAT_PGBIT) - .is_err() - { + if owner.page_table.unmap_pages(self.iova_range()).is_err() { dev_err!( owner.dev.as_ref(), "MMU: unmap {:#x}:{:#x} failed\n", @@ -915,7 +828,6 @@ impl UatInner { pub(crate) struct Uat { dev: driver::AsahiDevRef, cfg: &'static hw::HwConfig, - pagetables_rgn: UatRegion, inner: Arc, slots: slotalloc::SlotAllocator, @@ -1032,27 +944,6 @@ impl HandoffFlush { } } -// We do not implement FlushOps, since we flush manually in this module after -// page table operations. Just provide dummy implementations. -impl io_pgtable::FlushOps for Uat { - type Data = (); - - fn tlb_flush_all(_data: ::Borrowed<'_>) {} - fn tlb_flush_walk( - _data: ::Borrowed<'_>, - _iova: usize, - _size: usize, - _granule: usize, - ) { - } - fn tlb_add_page( - _data: ::Borrowed<'_>, - _iova: usize, - _granule: usize, - ) { - } -} - impl Vm { /// Create a new virtual memory address space fn new( @@ -1060,22 +951,18 @@ impl Vm { uat_inner: Arc, kernel_range: Range, cfg: &'static hw::HwConfig, - is_kernel: bool, + ttb: Option, id: u64, ) -> Result { - let dummy_obj = gem::new_kernel_object(dev, 0x4000)?; - - let page_table = AppleUAT::new( - dev.as_ref(), - io_pgtable::Config { - pgsize_bitmap: UAT_PGSZ, - ias: if is_kernel { UAT_IAS_KERN } else { UAT_IAS }, - oas: cfg.uat_oas, - coherent_walk: true, - quirks: 0, - }, - (), - )?; + let dummy_obj = gem::new_kernel_object(dev, UAT_PGSZ)?; + let is_kernel = ttb.is_some(); + + let page_table = if let Some(ttb) = ttb { + UatPageTable::new_with_ttb(ttb, IOVA_KERN_RANGE, cfg.uat_oas)? + } else { + UatPageTable::new(cfg.uat_oas)? + }; + let (va_range, gpuvm_range) = if is_kernel { (IOVA_KERN_RANGE, kernel_range.clone()) } else { @@ -1090,7 +977,7 @@ impl Vm { binding: None, bind_token: None, active_users: 0, - ttb: page_table.cfg().ttbr, + ttb: page_table.ttb(), }, "VmBinding", ), @@ -1135,12 +1022,12 @@ impl Vm { object_range: Range, alignment: u64, range: Range, - prot: u32, + prot: Prot, guard: bool, ) -> Result { let size = object_range.range(); let sgt = gem.owned_sg_table()?; - let mut inner = self.inner.exec_lock(Some(gem))?; + let mut inner = self.inner.exec_lock(Some(gem), false)?; let vm_bo = inner.obtain_bo()?; let mut vm_bo_guard = vm_bo.inner().sgt.lock(); @@ -1168,7 +1055,11 @@ impl Vm { mm::InsertMode::Best, )?; - inner.map_node(&node, prot)?; + let ret = inner.map_node(&node, prot); + // Drop the exec_lock first, so that if map_node failed the + // KernelMappingInner destructur does not deadlock. + core::mem::drop(inner); + ret?; Ok(KernelMapping(node)) } @@ -1179,11 +1070,11 @@ impl Vm { addr: u64, size: usize, gem: ARef, - prot: u32, + prot: Prot, guard: bool, ) -> Result { let sgt = gem.owned_sg_table()?; - let mut inner = self.inner.exec_lock(Some(&gem))?; + let mut inner = self.inner.exec_lock(Some(&gem), false)?; let vm_bo = inner.obtain_bo()?; @@ -1209,7 +1100,11 @@ impl Vm { 0, )?; - inner.map_node(&node, prot)?; + let ret = inner.map_node(&node, prot); + // Drop the exec_lock first, so that if map_node failed the + // KernelMappingInner destructur does not deadlock. + core::mem::drop(inner); + ret?; Ok(KernelMapping(node)) } @@ -1221,7 +1116,7 @@ impl Vm { addr: u64, size: u64, offset: u64, - prot: u32, + prot: Prot, ) -> Result { // Mapping needs a complete context let mut ctx = StepContext { @@ -1233,7 +1128,7 @@ impl Vm { }; let sgt = gem.owned_sg_table()?; - let mut inner = self.inner.exec_lock(Some(gem))?; + let mut inner = self.inner.exec_lock(Some(gem), true)?; // Preallocate the page tables, to fail early if we ENOMEM inner.page_table.alloc_pages(addr..(addr + size))?; @@ -1275,9 +1170,9 @@ impl Vm { iova: u64, phys: usize, size: usize, - prot: u32, + prot: Prot, ) -> Result { - let mut inner = self.inner.exec_lock(None)?; + let mut inner = self.inner.exec_lock(None, false)?; if (iova as usize | phys | size) & UAT_PGMSK != 0 { dev_err!( @@ -1314,8 +1209,14 @@ impl Vm { 0, )?; - inner.map_pages(iova, phys, UAT_PGSZ, size >> UAT_PGBIT, prot)?; - + let ret = + inner + .page_table + .map_pages(iova..(iova + size as u64), phys as PhysicalAddr, prot); + // Drop the exec_lock first, so that if map_node failed the + // KernelMappingInner destructur does not deadlock. + core::mem::drop(inner); + ret?; Ok(KernelMapping(node)) } @@ -1329,7 +1230,7 @@ impl Vm { ..Default::default() }; - let mut inner = self.inner.exec_lock(None)?; + let mut inner = self.inner.exec_lock(None, false)?; mod_dev_dbg!(inner.dev, "MMU: sm_unmap: {:#x}:{:#x}\n", iova, size); inner.sm_unmap(&mut ctx, iova, size) @@ -1340,7 +1241,7 @@ impl Vm { // Removing whole mappings only does unmaps, so no preallocated VAs let mut ctx = Default::default(); - let mut inner = self.inner.exec_lock(Some(gem))?; + let mut inner = self.inner.exec_lock(Some(gem), false)?; if let Some(bo) = inner.find_bo() { mod_dev_dbg!(inner.dev, "MMU: bo_unmap\n"); @@ -1455,12 +1356,6 @@ impl Uat { Ok(UatRegion { base, map }) } - /// Returns a view into the root kernel (upper half) page table - fn kpt0(&self) -> &[Pte; UAT_NPTE] { - // SAFETY: pointer is non-null per the type invariant - unsafe { (self.pagetables_rgn.map.as_ptr() as *mut [Pte; UAT_NPTE]).as_ref() }.unwrap() - } - /// Returns a reference to the global kernel (upper half) `Vm` pub(crate) fn kernel_vm(&self) -> &Vm { &self.kernel_vm @@ -1515,8 +1410,8 @@ impl Uat { idx ); } - ttbs[idx].ttb0.store(ttb, Ordering::Relaxed); - ttbs[idx].ttb1.store(ttb1, Ordering::Relaxed); + ttbs[idx].ttb0.store(ttb, Ordering::Release); + ttbs[idx].ttb1.store(ttb1, Ordering::Release); uat_inner.handoff().unlock(); core::mem::drop(uat_inner); @@ -1543,7 +1438,7 @@ impl Uat { self.inner.clone(), kernel_range, self.cfg, - false, + None, id, ) } @@ -1590,22 +1485,27 @@ impl Uat { let inner = Self::make_inner(dev)?; - let pagetables_rgn = - Self::map_region(dev.as_ref(), c_str!("pagetables"), PAGETABLES_SIZE, true)?; + let of_node = dev.as_ref().of_node().ok_or(EINVAL)?; + let res = of_node.reserved_mem_region_to_resource_byname(c_str!("pagetables"))?; + let ttb1 = res.start(); + let ttb1size: usize = res.size().try_into()?; + + if ttb1size < PAGETABLES_SIZE { + dev_err!(dev.as_ref(), "MMU: Pagetables region is too small\n"); + return Err(ENOMEM); + } dev_info!(dev.as_ref(), "MMU: Creating kernel page tables\n"); - let kernel_lower_vm = Vm::new(dev, inner.clone(), IOVA_USER_RANGE, cfg, false, 1)?; - let kernel_vm = Vm::new(dev, inner.clone(), IOVA_KERN_RANGE, cfg, true, 0)?; + let kernel_lower_vm = Vm::new(dev, inner.clone(), IOVA_USER_RANGE, cfg, None, 1)?; + let kernel_vm = Vm::new(dev, inner.clone(), IOVA_KERN_RANGE, cfg, Some(ttb1), 0)?; dev_info!(dev.as_ref(), "MMU: Kernel page tables created\n"); let ttb0 = kernel_lower_vm.ttb(); - let ttb1 = kernel_vm.ttb(); let uat = Self { dev: dev.into(), cfg, - pagetables_rgn, kernel_vm, kernel_lower_vm, inner, @@ -1622,7 +1522,7 @@ impl Uat { let mut inner = uat.inner.lock(); inner.map_kernel_to_user = map_kernel_to_user; - inner.kernel_ttb1 = uat.pagetables_rgn.base; + inner.kernel_ttb1 = ttb1; inner.handoff().init()?; @@ -1632,10 +1532,8 @@ impl Uat { let ttbs = inner.ttbs(); - ttbs[0].ttb0.store(ttb0 | TTBR_VALID, Ordering::Relaxed); - ttbs[0] - .ttb1 - .store(uat.pagetables_rgn.base | TTBR_VALID, Ordering::Relaxed); + ttbs[0].ttb0.store(ttb0 | TTBR_VALID, Ordering::SeqCst); + ttbs[0].ttb1.store(ttb1 | TTBR_VALID, Ordering::SeqCst); for ctx in &ttbs[1..] { ctx.ttb0.store(0, Ordering::Relaxed); @@ -1646,8 +1544,6 @@ impl Uat { core::mem::drop(inner); - uat.kpt0()[2].store(ttb1 | PTE_TABLE, Ordering::Relaxed); - dev_info!(dev.as_ref(), "MMU: initialized\n"); Ok(uat) @@ -1656,9 +1552,6 @@ impl Uat { impl Drop for Uat { fn drop(&mut self) { - // Unmap what we mapped - self.kpt0()[2].store(0, Ordering::Relaxed); - // Make sure we flush the TLBs fence(Ordering::SeqCst); mem::tlbi_all(); diff --git a/drivers/gpu/drm/asahi/pgtable.rs b/drivers/gpu/drm/asahi/pgtable.rs new file mode 100644 index 00000000000000..0340624823c5e1 --- /dev/null +++ b/drivers/gpu/drm/asahi/pgtable.rs @@ -0,0 +1,496 @@ +// SPDX-License-Identifier: GPL-2.0-only OR MIT + +//! UAT Page Table management +//! +//! AGX GPUs use an MMU called the UAT, which is largely compatible with the ARM64 page table +//! format. This module manages the actual page tables by allocating raw memory pages from +//! the kernel page allocator. + +use core::fmt::Debug; +use core::mem::size_of; +use core::ops::Range; +use core::sync::atomic::{ + AtomicU64, + Ordering, // +}; + +use kernel::addr::PhysicalAddr; +use kernel::{ + error::Result, + page::Page, + prelude::*, // +}; + +use crate::debug::*; +use crate::util::align; + +const DEBUG_CLASS: DebugFlags = DebugFlags::PgTable; + +/// Number of bits in a page offset. +pub(crate) const UAT_PGBIT: usize = 14; +/// UAT page size. +pub(crate) const UAT_PGSZ: usize = 1 << UAT_PGBIT; +/// UAT page offset mask. +pub(crate) const UAT_PGMSK: usize = UAT_PGSZ - 1; + +type Pte = AtomicU64; + +const PTE_BIT: usize = 3; // log2(sizeof(Pte)) +const PTE_SIZE: usize = 1 << PTE_BIT; + +/// Number of PTEs per page. +const UAT_NPTE: usize = UAT_PGSZ / size_of::(); + +/// Number of address bits to address a level +const UAT_LVBIT: usize = UAT_PGBIT - PTE_BIT; +/// Number of entries per level +const UAT_LVSZ: usize = UAT_NPTE; +/// Mask of level bits +const UAT_LVMSK: u64 = (UAT_LVSZ - 1) as u64; + +const UAT_LEVELS: usize = 3; + +/// UAT input address space +pub(crate) const UAT_IAS: usize = 39; +const UAT_IASMSK: u64 = (1u64 << UAT_IAS) - 1; + +const PTE_TYPE_BITS: u64 = 3; +const PTE_TYPE_LEAF_TABLE: u64 = 3; + +const UAT_NON_GLOBAL: u64 = 1 << 11; +const UAT_AP_SHIFT: u32 = 6; +const UAT_AP_BITS: u64 = 3 << UAT_AP_SHIFT; +const UAT_HIGH_BITS_SHIFT: u32 = 53; +const UAT_HIGH_BITS: u64 = 7 << UAT_HIGH_BITS_SHIFT; +const UAT_MEMATTR_SHIFT: u32 = 2; +const UAT_MEMATTR_BITS: u64 = 7 << UAT_MEMATTR_SHIFT; + +const UAT_PROT_BITS: u64 = UAT_AP_BITS | UAT_MEMATTR_BITS | UAT_HIGH_BITS; + +const UAT_AF: u64 = 1 << 10; + +const MEMATTR_CACHED: u8 = 0; +const MEMATTR_DEV: u8 = 1; +const MEMATTR_UNCACHED: u8 = 2; + +const AP_FW_GPU: u8 = 0; +const AP_FW: u8 = 1; +const AP_GPU: u8 = 2; + +const HIGH_BITS_PXN: u8 = 1 << 0; +const HIGH_BITS_UXN: u8 = 1 << 1; +const HIGH_BITS_GPU_ACCESS: u8 = 1 << 2; + +#[derive(Debug, Copy, Clone)] +pub(crate) struct Prot { + memattr: u8, + ap: u8, + high_bits: u8, +} + +// Firmware + GPU access +const PROT_FW_GPU_NA: Prot = Prot::from_bits(AP_FW_GPU, 0, 0); +const _PROT_FW_GPU_RO: Prot = Prot::from_bits(AP_FW_GPU, 0, 1); +const _PROT_FW_GPU_WO: Prot = Prot::from_bits(AP_FW_GPU, 1, 0); +const PROT_FW_GPU_RW: Prot = Prot::from_bits(AP_FW_GPU, 1, 1); + +// Firmware only access +const PROT_FW_RO: Prot = Prot::from_bits(AP_FW, 0, 0); +const _PROT_FW_NA: Prot = Prot::from_bits(AP_FW, 0, 1); +const PROT_FW_RW: Prot = Prot::from_bits(AP_FW, 1, 0); +const PROT_FW_RW_GPU_RO: Prot = Prot::from_bits(AP_FW, 1, 1); + +// GPU only access +const PROT_GPU_RO: Prot = Prot::from_bits(AP_GPU, 0, 0); +const PROT_GPU_WO: Prot = Prot::from_bits(AP_GPU, 0, 1); +const PROT_GPU_RW: Prot = Prot::from_bits(AP_GPU, 1, 0); +const _PROT_GPU_NA: Prot = Prot::from_bits(AP_GPU, 1, 1); + +pub(crate) mod prot { + pub(crate) use super::Prot; + use super::*; + + /// Firmware MMIO R/W + pub(crate) const PROT_FW_MMIO_RW: Prot = PROT_FW_RW.memattr(MEMATTR_DEV); + /// Firmware MMIO R/O + pub(crate) const PROT_FW_MMIO_RO: Prot = PROT_FW_RO.memattr(MEMATTR_DEV); + /// Firmware shared (uncached) RW + pub(crate) const PROT_FW_SHARED_RW: Prot = PROT_FW_RW.memattr(MEMATTR_UNCACHED); + /// Firmware shared (uncached) RO + pub(crate) const PROT_FW_SHARED_RO: Prot = PROT_FW_RO.memattr(MEMATTR_UNCACHED); + /// Firmware private (cached) RW + pub(crate) const PROT_FW_PRIV_RW: Prot = PROT_FW_RW.memattr(MEMATTR_CACHED); + /// Firmware/GPU shared (uncached) RW + pub(crate) const PROT_GPU_FW_SHARED_RW: Prot = PROT_FW_GPU_RW.memattr(MEMATTR_UNCACHED); + /// Firmware/GPU shared (private) RW + pub(crate) const PROT_GPU_FW_PRIV_RW: Prot = PROT_FW_GPU_RW.memattr(MEMATTR_CACHED); + /// Firmware-RW/GPU-RO shared (private) RW + pub(crate) const PROT_GPU_RO_FW_PRIV_RW: Prot = PROT_FW_RW_GPU_RO.memattr(MEMATTR_CACHED); + /// GPU shared/coherent RW + pub(crate) const PROT_GPU_SHARED_RW: Prot = PROT_GPU_RW.memattr(MEMATTR_UNCACHED); + /// GPU shared/coherent RO + pub(crate) const PROT_GPU_SHARED_RO: Prot = PROT_GPU_RO.memattr(MEMATTR_UNCACHED); + /// GPU shared/coherent WO + pub(crate) const PROT_GPU_SHARED_WO: Prot = PROT_GPU_WO.memattr(MEMATTR_UNCACHED); +} + +impl Prot { + const fn from_bits(ap: u8, uxn: u8, pxn: u8) -> Self { + assert!(uxn <= 1); + assert!(pxn <= 1); + assert!(ap <= 3); + + Prot { + high_bits: HIGH_BITS_GPU_ACCESS | (pxn * HIGH_BITS_PXN) | (uxn * HIGH_BITS_UXN), + memattr: 0, + ap, + } + } + + const fn memattr(&self, memattr: u8) -> Self { + Self { memattr, ..*self } + } + + const fn as_pte(&self) -> u64 { + (self.ap as u64) << UAT_AP_SHIFT + | (self.high_bits as u64) << UAT_HIGH_BITS_SHIFT + | (self.memattr as u64) << UAT_MEMATTR_SHIFT + | UAT_AF + } + + pub(crate) const fn is_cached_noncoherent(&self) -> bool { + self.ap != AP_GPU && self.memattr == MEMATTR_CACHED + } + + pub(crate) const fn as_uncached(&self) -> Self { + self.memattr(MEMATTR_UNCACHED) + } +} + +impl Default for Prot { + fn default() -> Self { + PROT_FW_GPU_NA + } +} + +pub(crate) struct UatPageTable { + ttb: PhysicalAddr, + ttb_owned: bool, + va_range: Range, + oas_mask: u64, +} + +impl UatPageTable { + pub(crate) fn new(oas: u32) -> Result { + mod_pr_debug!("UATPageTable::new: oas={}\n", oas); + let ttb_page = Page::alloc_page(GFP_KERNEL | __GFP_ZERO)?; + let ttb = Page::into_phys(ttb_page); + Ok(UatPageTable { + ttb, + ttb_owned: true, + va_range: 0..(1u64 << UAT_IAS), + oas_mask: (1u64 << oas) - 1, + }) + } + + pub(crate) fn new_with_ttb(ttb: PhysicalAddr, va_range: Range, oas: u32) -> Result { + mod_pr_debug!( + "UATPageTable::new_with_ttb: ttb={:#x} range={:#x?} oas={}\n", + ttb, + va_range, + oas + ); + if ttb & (UAT_PGMSK as PhysicalAddr) != 0 { + return Err(EINVAL); + } + if (va_range.start | va_range.end) & (UAT_PGMSK as u64) != 0 { + return Err(EINVAL); + } + // SAFETY: The TTB is should remain valid (if properly mapped), as it is bootloader-managed. + if unsafe { Page::borrow_phys(&ttb) }.is_none() { + pr_err!( + "UATPageTable::new_with_ttb: ttb at {:#x} is not mapped (DT using no-map?)\n", + ttb + ); + return Err(EIO); + } + + Ok(UatPageTable { + ttb, + ttb_owned: false, + va_range, + oas_mask: (1u64 << oas) - 1, + }) + } + + pub(crate) fn ttb(&self) -> PhysicalAddr { + self.ttb + } + + fn with_pages(&mut self, iova_range: Range, free: bool, mut cb: F) -> Result + where + F: FnMut(u64, &[Pte]), + { + mod_pr_debug!("UATPageTable::with_pages: {:#x?} {}\n", iova_range, free); + if (iova_range.start | iova_range.end) & (UAT_PGMSK as u64) != 0 { + pr_err!( + "UATPageTable::with_pages: iova range not aligned: {:#x?}\n", + iova_range + ); + return Err(EINVAL); + } + + if iova_range.is_empty() { + return Ok(()); + } + + let mut iova = iova_range.start & UAT_IASMSK; + let mut last_iova = iova; + // Handle the case where iova_range.end is just at the top boundary of the IAS + let end = ((iova_range.end - 1) & UAT_IASMSK) + 1; + + let mut pt_addr: [Option; UAT_LEVELS] = Default::default(); + pt_addr[UAT_LEVELS - 1] = Some(self.ttb); + + 'outer: while iova < end { + mod_pr_debug!("UATPageTable::with_pages: iova={:#x}\n", iova); + let addr_diff = last_iova ^ iova; + for level in (0..UAT_LEVELS - 1).rev() { + // If the iova has changed at this level or above, invalidate the physaddr + if addr_diff & !((1 << (UAT_PGBIT + (level + 1) * UAT_LVBIT)) - 1) != 0 { + if let Some(phys) = pt_addr[level].take() { + if free { + mod_pr_debug!( + "UATPageTable::with_pages: free level {} {:#x?}\n", + level, + phys + ); + // SAFETY: Page tables for our VA ranges always come from Page::into_phys(). + unsafe { Page::from_phys(phys) }; + } + mod_pr_debug!("UATPageTable::with_pages: invalidate level {}\n", level); + } + } + } + last_iova = iova; + for level in (0..UAT_LEVELS - 1).rev() { + // Fetch the page table base address for this level + if pt_addr[level].is_none() { + let phys = pt_addr[level + 1].unwrap(); + mod_pr_debug!( + "UATPageTable::with_pages: need level {}, parent phys {:#x}\n", + level, + phys + ); + let upidx = ((iova >> (UAT_PGBIT + (level + 1) * UAT_LVBIT) as u64) & UAT_LVMSK) + as usize; + // SAFETY: Page table addresses are either allocated by us, or + // firmware-managed and safe to borrow a struct page from. + let upt = unsafe { Page::borrow_phys_unchecked(&phys) }; + mod_pr_debug!("UATPageTable::with_pages: borrowed phys {:#x}\n", phys); + pt_addr[level] = + upt.with_pointer_into_page(upidx * PTE_SIZE, PTE_SIZE, |p| { + let uptep = p as *const _ as *const Pte; + let upte = unsafe { &*uptep }; + let mut upte_val = upte.load(Ordering::Relaxed); + // Allocate if requested + if upte_val == 0 && !free { + let pt_page = Page::alloc_page(GFP_KERNEL | __GFP_ZERO)?; + mod_pr_debug!("UATPageTable::with_pages: alloc PT at {:#x}\n", pt_page.phys()); + let pt_paddr = Page::into_phys(pt_page); + upte_val = pt_paddr | PTE_TYPE_LEAF_TABLE; + upte.store(upte_val, Ordering::Relaxed); + } + if upte_val & PTE_TYPE_BITS == PTE_TYPE_LEAF_TABLE { + Ok(Some(upte_val & self.oas_mask & (!UAT_PGMSK as u64))) + } else if upte_val == 0 { + mod_pr_debug!("UATPageTable::with_pages: no level {}\n", level); + Ok(None) + } else { + pr_err!("UATPageTable::with_pages: Unexpected Table PTE value {:#x} at iova {:#x} index {} phys {:#x}\n", upte_val, + iova, level + 1, phys + ((upidx * PTE_SIZE) as PhysicalAddr)); + Ok(None) + } + })?; + mod_pr_debug!( + "UATPageTable::with_pages: level {} PT {:#x?}\n", + level, + pt_addr[level] + ); + } + // If we don't have a page table, skip this entire level + if pt_addr[level].is_none() { + let block = 1 << (UAT_PGBIT + UAT_LVBIT * (level + 1)); + let old = iova; + iova = align(iova + 1, block); + mod_pr_debug!( + "UATPageTable::with_pages: skip {:#x} {:#x} -> {:#x}\n", + block, + old, + iova + ); + continue 'outer; + } + } + + let idx = ((iova >> UAT_PGBIT as u64) & UAT_LVMSK) as usize; + let max_count = UAT_NPTE - idx; + let count = (((end - iova) >> UAT_PGBIT) as usize).min(max_count); + let phys = pt_addr[0].unwrap(); + // SAFETY: Page table addresses are either allocated by us, or + // firmware-managed and safe to borrow a struct page from. + mod_pr_debug!( + "UATPageTable::with_pages: leaf PT at {:#x} idx {:#x} count {:#x} iova {:#x}\n", + phys, + idx, + count, + iova + ); + // SAFETY: Page table addresses are either allocated by us, or + // firmware-managed and safe to borrow a struct page from. + let pt = unsafe { Page::borrow_phys_unchecked(&phys) }; + pt.with_pointer_into_page(idx * PTE_SIZE, count * PTE_SIZE, |p| { + let ptep = p as *const _ as *const Pte; + // SAFETY: We know this is a valid pointer to PTEs and the range is valid and + // checked by with_pointer_into_page(). + let ptes = unsafe { core::slice::from_raw_parts(ptep, count) }; + cb(iova, ptes); + Ok(()) + })?; + + let block = 1 << (UAT_PGBIT + UAT_LVBIT); + iova = align(iova + 1, block); + } + + if free { + for level in (0..UAT_LEVELS - 1).rev() { + if let Some(phys) = pt_addr[level] { + // SAFETY: Page tables for our VA ranges always come from Page::into_phys(). + mod_pr_debug!( + "UATPageTable::with_pages: free level {} {:#x?}\n", + level, + phys + ); + unsafe { Page::from_phys(phys) }; + } + } + } + + Ok(()) + } + + pub(crate) fn alloc_pages(&mut self, iova_range: Range) -> Result { + mod_pr_debug!("UATPageTable::alloc_pages: {:#x?}\n", iova_range); + self.with_pages(iova_range, false, |_, _| {}) + } + + fn pte_bits(&self) -> u64 { + if self.ttb_owned { + // Owned page tables are userspace, so non-global + PTE_TYPE_LEAF_TABLE | UAT_NON_GLOBAL + } else { + // The sole non-owned page table is kernelspace, so global + PTE_TYPE_LEAF_TABLE + } + } + + pub(crate) fn map_pages( + &mut self, + iova_range: Range, + mut phys: PhysicalAddr, + prot: Prot, + ) -> Result { + mod_pr_debug!( + "UATPageTable::map_pages: {:#x?} {:#x?} {:?}\n", + iova_range, + phys, + prot + ); + if phys & (UAT_PGMSK as PhysicalAddr) != 0 { + pr_err!("UATPageTable::map_pages: phys not aligned: {:#x?}\n", phys); + return Err(EINVAL); + } + + let pte_bits = self.pte_bits(); + + self.with_pages(iova_range, false, |iova, ptes| { + for (idx, pte) in ptes.iter().enumerate() { + let ptev = pte.load(Ordering::Relaxed); + if ptev != 0 { + pr_err!( + "UATPageTable::map_pages: Page at IOVA {:#x} is mapped (PTE: {:#x})\n", + iova + (idx * UAT_PGSZ) as u64, + ptev + ); + } + pte.store(phys | prot.as_pte() | pte_bits, Ordering::Relaxed); + phys += UAT_PGSZ as PhysicalAddr; + } + }) + } + + pub(crate) fn reprot_pages(&mut self, iova_range: Range, prot: Prot) -> Result { + mod_pr_debug!( + "UATPageTable::reprot_pages: {:#x?} {:?}\n", + iova_range, + prot + ); + self.with_pages(iova_range, false, |iova, ptes| { + for (idx, pte) in ptes.iter().enumerate() { + let ptev = pte.load(Ordering::Relaxed); + if ptev & PTE_TYPE_BITS != PTE_TYPE_LEAF_TABLE { + pr_err!( + "UATPageTable::reprot_pages: Page at IOVA {:#x} is unmapped (PTE: {:#x})\n", + iova + (idx * UAT_PGSZ) as u64, + ptev + ); + continue; + } + pte.store((ptev & !UAT_PROT_BITS) | prot.as_pte(), Ordering::Relaxed); + } + }) + } + + pub(crate) fn unmap_pages(&mut self, iova_range: Range) -> Result { + mod_pr_debug!("UATPageTable::unmap_pages: {:#x?}\n", iova_range); + self.with_pages(iova_range, false, |iova, ptes| { + for (idx, pte) in ptes.iter().enumerate() { + if pte.load(Ordering::Relaxed) & PTE_TYPE_LEAF_TABLE == 0 { + pr_err!( + "UATPageTable::unmap_pages: Page at IOVA {:#x} already unmapped\n", + iova + (idx * UAT_PGSZ) as u64 + ); + } + pte.store(0, Ordering::Relaxed); + } + }) + } +} + +impl Drop for UatPageTable { + fn drop(&mut self) { + mod_pr_debug!("UATPageTable::drop range: {:#x?}\n", &self.va_range); + if self + .with_pages(self.va_range.clone(), true, |iova, ptes| { + for (idx, pte) in ptes.iter().enumerate() { + if pte.load(Ordering::Relaxed) != 0 { + pr_err!( + "UATPageTable::drop: Leaked page at IOVA {:#x}\n", + iova + (idx * UAT_PGSZ) as u64 + ); + } + } + }) + .is_err() + { + pr_err!("UATPageTable::drop failed to free page tables\n",); + } + if self.ttb_owned { + mod_pr_debug!("UATPageTable::drop: Free TTB {:#x}\n", self.ttb); + // SAFETY: If we own the ttb, it was allocated with Page::into_phys(). + unsafe { + Page::from_phys(self.ttb); + } + } + } +} From fc316edb82326649c23f9172476d4d28db1c0e0f Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Sun, 21 Dec 2025 01:06:56 +0100 Subject: [PATCH 1286/3902] drm/asahi: Implement ASAHI_BIND_SINGLE_PAGE (mmu/pgtbl) Signed-off-by: Asahi Lina --- drivers/gpu/drm/asahi/mmu.rs | 48 ++++++++++++++++++++++++-------- drivers/gpu/drm/asahi/pgtable.rs | 5 +++- 2 files changed, 40 insertions(+), 13 deletions(-) diff --git a/drivers/gpu/drm/asahi/mmu.rs b/drivers/gpu/drm/asahi/mmu.rs index 0bc33060de4ad6..6520e1dd547d72 100644 --- a/drivers/gpu/drm/asahi/mmu.rs +++ b/drivers/gpu/drm/asahi/mmu.rs @@ -229,6 +229,8 @@ impl gpuvm::DriverGpuVm for VmInner { let bo = ctx.vm_bo.as_ref().expect("step_map with no BO"); + let one_page = op.flags().contains(gpuvm::GpuVaFlags::REPEAT); + let guard = bo.inner().sgt.lock(); for range in guard.as_ref().expect("step_map with no SGT").iter() { // TODO: proper DMA address/length handling @@ -252,18 +254,27 @@ impl gpuvm::DriverGpuVm for VmInner { assert!(offset == 0); - len = len.min(left); + if one_page { + len = left; + } else { + len = len.min(left); + } mod_dev_dbg!( self.dev, - "MMU: map: {:#x}:{:#x} -> {:#x}\n", + "MMU: map: {:#x}:{:#x} -> {:#x} [OP={}]\n", addr, len, - iova + iova, + one_page ); - self.page_table - .map_pages(iova..(iova + len as u64), addr as PhysicalAddr, ctx.prot)?; + self.page_table.map_pages( + iova..(iova + len as u64), + addr as PhysicalAddr, + ctx.prot, + one_page, + )?; left -= len; iova += len as u64; @@ -476,8 +487,12 @@ impl VmInner { iova ); - self.page_table - .map_pages(iova..(iova + len as u64), addr as PhysicalAddr, prot)?; + self.page_table.map_pages( + iova..(iova + len as u64), + addr as PhysicalAddr, + prot, + false, + )?; iova += len as u64; left -= len; @@ -1117,6 +1132,7 @@ impl Vm { size: u64, offset: u64, prot: Prot, + single_page: bool, ) -> Result { // Mapping needs a complete context let mut ctx = StepContext { @@ -1154,6 +1170,12 @@ impl Vm { return Err(EINVAL); } + let (flags, gem_range) = if single_page { + (gpuvm::GpuVaFlags::REPEAT, UAT_PGSZ as u32) + } else { + (gpuvm::GpuVaFlags::NONE, 0u32) + }; + mod_dev_dbg!( inner.dev, "MMU: sm_map: {:#x} [{:#x}] -> {:#x}\n", @@ -1161,7 +1183,7 @@ impl Vm { size, addr ); - inner.sm_map(&mut ctx, addr, size, offset) + inner.sm_map(&mut ctx, addr, size, offset, gem_range, flags) } /// Add a direct MMIO mapping to this Vm at a free address. @@ -1209,10 +1231,12 @@ impl Vm { 0, )?; - let ret = - inner - .page_table - .map_pages(iova..(iova + size as u64), phys as PhysicalAddr, prot); + let ret = inner.page_table.map_pages( + iova..(iova + size as u64), + phys as PhysicalAddr, + prot, + false, + ); // Drop the exec_lock first, so that if map_node failed the // KernelMappingInner destructur does not deadlock. core::mem::drop(inner); diff --git a/drivers/gpu/drm/asahi/pgtable.rs b/drivers/gpu/drm/asahi/pgtable.rs index 0340624823c5e1..4267430b134125 100644 --- a/drivers/gpu/drm/asahi/pgtable.rs +++ b/drivers/gpu/drm/asahi/pgtable.rs @@ -399,6 +399,7 @@ impl UatPageTable { iova_range: Range, mut phys: PhysicalAddr, prot: Prot, + one_page: bool, ) -> Result { mod_pr_debug!( "UATPageTable::map_pages: {:#x?} {:#x?} {:?}\n", @@ -424,7 +425,9 @@ impl UatPageTable { ); } pte.store(phys | prot.as_pte() | pte_bits, Ordering::Relaxed); - phys += UAT_PGSZ as PhysicalAddr; + if !one_page { + phys += UAT_PGSZ as PhysicalAddr; + } } }) } From a94e9ef7b7e5ff7fe64d80b333e060e5828e051f Mon Sep 17 00:00:00 2001 From: Asahi Lina Date: Mon, 27 Jan 2025 23:02:59 +0900 Subject: [PATCH 1287/3902] drm/asahi: pgtable: Add dumper Signed-off-by: Asahi Lina --- drivers/gpu/drm/asahi/pgtable.rs | 154 ++++++++++++++++++++++++++++--- 1 file changed, 139 insertions(+), 15 deletions(-) diff --git a/drivers/gpu/drm/asahi/pgtable.rs b/drivers/gpu/drm/asahi/pgtable.rs index 4267430b134125..65dc20649604f8 100644 --- a/drivers/gpu/drm/asahi/pgtable.rs +++ b/drivers/gpu/drm/asahi/pgtable.rs @@ -14,12 +14,16 @@ use core::sync::atomic::{ Ordering, // }; -use kernel::addr::PhysicalAddr; use kernel::{ + addr::PhysicalAddr, error::Result, page::Page, prelude::*, // }; +#[cfg(CONFIG_DEV_COREDUMP)] +use kernel::{ + types::Owned, +}; use crate::debug::*; use crate::util::align; @@ -173,6 +177,13 @@ impl Default for Prot { } } +#[cfg(CONFIG_DEV_COREDUMP)] +pub(crate) struct DumpedPage { + pub(crate) iova: u64, + pub(crate) pte: u64, + pub(crate) data: Option>, +} + pub(crate) struct UatPageTable { ttb: PhysicalAddr, ttb_owned: bool, @@ -227,11 +238,22 @@ impl UatPageTable { self.ttb } - fn with_pages(&mut self, iova_range: Range, free: bool, mut cb: F) -> Result + fn with_pages( + &mut self, + iova_range: Range, + alloc: bool, + free: bool, + mut cb: F, + ) -> Result where - F: FnMut(u64, &[Pte]), + F: FnMut(u64, &[Pte]) -> Result, { - mod_pr_debug!("UATPageTable::with_pages: {:#x?} {}\n", iova_range, free); + mod_pr_debug!( + "UATPageTable::with_pages: {:#x?} alloc={} free={}\n", + iova_range, + alloc, + free + ); if (iova_range.start | iova_range.end) & (UAT_PGMSK as u64) != 0 { pr_err!( "UATPageTable::with_pages: iova range not aligned: {:#x?}\n", @@ -291,10 +313,12 @@ impl UatPageTable { pt_addr[level] = upt.with_pointer_into_page(upidx * PTE_SIZE, PTE_SIZE, |p| { let uptep = p as *const _ as *const Pte; + // SAFETY: with_pointer_into_page() ensures the pointer is valid, + // and our index is aligned so it is safe to deref as an AtomicU64. let upte = unsafe { &*uptep }; let mut upte_val = upte.load(Ordering::Relaxed); // Allocate if requested - if upte_val == 0 && !free { + if upte_val == 0 && alloc { let pt_page = Page::alloc_page(GFP_KERNEL | __GFP_ZERO)?; mod_pr_debug!("UATPageTable::with_pages: alloc PT at {:#x}\n", pt_page.phys()); let pt_paddr = Page::into_phys(pt_page); @@ -303,7 +327,7 @@ impl UatPageTable { } if upte_val & PTE_TYPE_BITS == PTE_TYPE_LEAF_TABLE { Ok(Some(upte_val & self.oas_mask & (!UAT_PGMSK as u64))) - } else if upte_val == 0 { + } else if upte_val == 0 || (!alloc && !free) { mod_pr_debug!("UATPageTable::with_pages: no level {}\n", level); Ok(None) } else { @@ -337,8 +361,6 @@ impl UatPageTable { let max_count = UAT_NPTE - idx; let count = (((end - iova) >> UAT_PGBIT) as usize).min(max_count); let phys = pt_addr[0].unwrap(); - // SAFETY: Page table addresses are either allocated by us, or - // firmware-managed and safe to borrow a struct page from. mod_pr_debug!( "UATPageTable::with_pages: leaf PT at {:#x} idx {:#x} count {:#x} iova {:#x}\n", phys, @@ -354,7 +376,7 @@ impl UatPageTable { // SAFETY: We know this is a valid pointer to PTEs and the range is valid and // checked by with_pointer_into_page(). let ptes = unsafe { core::slice::from_raw_parts(ptep, count) }; - cb(iova, ptes); + cb(iova, ptes)?; Ok(()) })?; @@ -365,12 +387,12 @@ impl UatPageTable { if free { for level in (0..UAT_LEVELS - 1).rev() { if let Some(phys) = pt_addr[level] { - // SAFETY: Page tables for our VA ranges always come from Page::into_phys(). mod_pr_debug!( "UATPageTable::with_pages: free level {} {:#x?}\n", level, phys ); + // SAFETY: Page tables for our VA ranges always come from Page::into_phys(). unsafe { Page::from_phys(phys) }; } } @@ -381,7 +403,7 @@ impl UatPageTable { pub(crate) fn alloc_pages(&mut self, iova_range: Range) -> Result { mod_pr_debug!("UATPageTable::alloc_pages: {:#x?}\n", iova_range); - self.with_pages(iova_range, false, |_, _| {}) + self.with_pages(iova_range, true, false, |_, _| Ok(())) } fn pte_bits(&self) -> u64 { @@ -414,7 +436,7 @@ impl UatPageTable { let pte_bits = self.pte_bits(); - self.with_pages(iova_range, false, |iova, ptes| { + self.with_pages(iova_range, true, false, |iova, ptes| { for (idx, pte) in ptes.iter().enumerate() { let ptev = pte.load(Ordering::Relaxed); if ptev != 0 { @@ -429,6 +451,7 @@ impl UatPageTable { phys += UAT_PGSZ as PhysicalAddr; } } + Ok(()) }) } @@ -438,7 +461,7 @@ impl UatPageTable { iova_range, prot ); - self.with_pages(iova_range, false, |iova, ptes| { + self.with_pages(iova_range, true, false, |iova, ptes| { for (idx, pte) in ptes.iter().enumerate() { let ptev = pte.load(Ordering::Relaxed); if ptev & PTE_TYPE_BITS != PTE_TYPE_LEAF_TABLE { @@ -451,12 +474,13 @@ impl UatPageTable { } pte.store((ptev & !UAT_PROT_BITS) | prot.as_pte(), Ordering::Relaxed); } + Ok(()) }) } pub(crate) fn unmap_pages(&mut self, iova_range: Range) -> Result { mod_pr_debug!("UATPageTable::unmap_pages: {:#x?}\n", iova_range); - self.with_pages(iova_range, false, |iova, ptes| { + self.with_pages(iova_range, false, false, |iova, ptes| { for (idx, pte) in ptes.iter().enumerate() { if pte.load(Ordering::Relaxed) & PTE_TYPE_LEAF_TABLE == 0 { pr_err!( @@ -466,15 +490,114 @@ impl UatPageTable { } pte.store(0, Ordering::Relaxed); } + Ok(()) }) } + + #[cfg(CONFIG_DEV_COREDUMP)] + pub(crate) fn dump_pages(&mut self, iova_range: Range) -> Result> { + let mut pages = KVVec::new(); + let oas_mask = self.oas_mask; + let iova_base = self.va_range.start & !UAT_IASMSK; + self.with_pages(iova_range, false, false, |iova, ptes| { + let iova = iova | iova_base; + for (idx, ppte) in ptes.iter().enumerate() { + let pte = ppte.load(Ordering::Relaxed); + if (pte & PTE_TYPE_LEAF_TABLE) != PTE_TYPE_LEAF_TABLE { + continue; + } + let memattr = ((pte & UAT_MEMATTR_BITS) >> UAT_MEMATTR_SHIFT) as u8; + + if !(memattr == MEMATTR_CACHED || memattr == MEMATTR_UNCACHED) { + pages.push( + DumpedPage { + iova: iova + (idx * UAT_PGSZ) as u64, + pte, + data: None, + }, + GFP_KERNEL, + )?; + continue; + } + let phys = pte & oas_mask & (!UAT_PGMSK as u64); + // SAFETY: GPU pages are either firmware/preallocated pages + // (which the kernel isn't concerned with and are either in + // the page map or not, and if they aren't, borrow_phys() + // will fail), or GPU page table pages (which we own), + // or GEM buffer pages (which are locked while they are + // mapped in the page table), so they should be safe to + // borrow. + // + // This does trust the firmware not to have any weird + // mappings in its own internal page tables, but since + // those are managed by the uPPL which is privileged anyway, + // this trust does not actually extend any trust boundary. + let src_page = match unsafe { Page::borrow_phys(&phys) } { + Some(page) => page, + None => { + pages.push( + DumpedPage { + iova: iova + (idx * UAT_PGSZ) as u64, + pte, + data: None, + }, + GFP_KERNEL, + )?; + continue; + } + }; + let dst_page = Page::alloc_page(GFP_KERNEL)?; + src_page.with_page_mapped(|psrc| -> Result { + // SAFETY: This could technically still have a data race with the firmware + // or other driver code (or even userspace with timestamp buffers), but while + // the Rust language technically says this is UB, in the real world, using + // atomic reads for this is guaranteed to never cause any harmful effects + // other than possibly reading torn/unreliable data. At least on ARM64 anyway. + // + // (Yes, I checked with Rust people about this. ~~ Lina) + // + let src_items = unsafe { + core::slice::from_raw_parts( + psrc as *const AtomicU64, + UAT_PGSZ / core::mem::size_of::(), + ) + }; + dst_page.with_page_mapped(|pdst| -> Result { + // SAFETY: We own the destination page, so it is safe to view its contents + // as a u64 slice. + let dst_items = unsafe { + core::slice::from_raw_parts_mut( + pdst as *mut u64, + UAT_PGSZ / core::mem::size_of::(), + ) + }; + for (si, di) in src_items.iter().zip(dst_items.iter_mut()) { + *di = si.load(Ordering::Relaxed); + } + Ok(()) + })?; + Ok(()) + })?; + pages.push( + DumpedPage { + iova: iova + (idx * UAT_PGSZ) as u64, + pte, + data: Some(dst_page), + }, + GFP_KERNEL, + )?; + } + Ok(()) + })?; + Ok(pages) + } } impl Drop for UatPageTable { fn drop(&mut self) { mod_pr_debug!("UATPageTable::drop range: {:#x?}\n", &self.va_range); if self - .with_pages(self.va_range.clone(), true, |iova, ptes| { + .with_pages(self.va_range.clone(), false, true, |iova, ptes| { for (idx, pte) in ptes.iter().enumerate() { if pte.load(Ordering::Relaxed) != 0 { pr_err!( @@ -483,6 +606,7 @@ impl Drop for UatPageTable { ); } } + Ok(()) }) .is_err() { From 1c51814951815930d7a72c12679e6b2e3e188b6a Mon Sep 17 00:00:00 2001 From: Asahi Lina Date: Tue, 28 Jan 2025 02:12:01 +0900 Subject: [PATCH 1288/3902] drm/asahi: pgtable: Add helpers for decoding PTE perms Signed-off-by: Asahi Lina --- drivers/gpu/drm/asahi/pgtable.rs | 81 +++++++++++++++++++++++++++++--- 1 file changed, 74 insertions(+), 7 deletions(-) diff --git a/drivers/gpu/drm/asahi/pgtable.rs b/drivers/gpu/drm/asahi/pgtable.rs index 65dc20649604f8..7c9b54c2242515 100644 --- a/drivers/gpu/drm/asahi/pgtable.rs +++ b/drivers/gpu/drm/asahi/pgtable.rs @@ -23,6 +23,11 @@ use kernel::{ #[cfg(CONFIG_DEV_COREDUMP)] use kernel::{ types::Owned, + uapi::{ + PF_R, + PF_W, + PF_X, // + }, }; use crate::debug::*; @@ -64,8 +69,8 @@ const PTE_TYPE_LEAF_TABLE: u64 = 3; const UAT_NON_GLOBAL: u64 = 1 << 11; const UAT_AP_SHIFT: u32 = 6; const UAT_AP_BITS: u64 = 3 << UAT_AP_SHIFT; -const UAT_HIGH_BITS_SHIFT: u32 = 53; -const UAT_HIGH_BITS: u64 = 7 << UAT_HIGH_BITS_SHIFT; +const UAT_HIGH_BITS_SHIFT: u32 = 52; +const UAT_HIGH_BITS: u64 = 0xfff << UAT_HIGH_BITS_SHIFT; const UAT_MEMATTR_SHIFT: u32 = 2; const UAT_MEMATTR_BITS: u64 = 7 << UAT_MEMATTR_SHIFT; @@ -81,15 +86,18 @@ const AP_FW_GPU: u8 = 0; const AP_FW: u8 = 1; const AP_GPU: u8 = 2; -const HIGH_BITS_PXN: u8 = 1 << 0; -const HIGH_BITS_UXN: u8 = 1 << 1; -const HIGH_BITS_GPU_ACCESS: u8 = 1 << 2; +const HIGH_BITS_PXN: u16 = 1 << 1; +const HIGH_BITS_UXN: u16 = 1 << 2; +const HIGH_BITS_GPU_ACCESS: u16 = 1 << 3; + +#[cfg(CONFIG_DEV_COREDUMP)] +pub(crate) const PTE_ADDR_BITS: u64 = (!UAT_PGMSK as u64) & (!UAT_HIGH_BITS); #[derive(Debug, Copy, Clone)] pub(crate) struct Prot { memattr: u8, ap: u8, - high_bits: u8, + high_bits: u16, } // Firmware + GPU access @@ -110,6 +118,27 @@ const PROT_GPU_WO: Prot = Prot::from_bits(AP_GPU, 0, 1); const PROT_GPU_RW: Prot = Prot::from_bits(AP_GPU, 1, 0); const _PROT_GPU_NA: Prot = Prot::from_bits(AP_GPU, 1, 1); +#[cfg(CONFIG_DEV_COREDUMP)] +const PF_RW: u32 = PF_R | PF_W; +#[cfg(CONFIG_DEV_COREDUMP)] +const PF_RX: u32 = PF_R | PF_X; + +// For crash dumps +#[cfg(CONFIG_DEV_COREDUMP)] +const PROT_TO_PERMS_FW: [[u32; 4]; 4] = [ + [0, 0, 0, PF_RW], + [0, PF_RW, 0, PF_RW], + [PF_RX, PF_RX, 0, PF_R], + [PF_RX, PF_RW, 0, PF_R], +]; +#[cfg(CONFIG_DEV_COREDUMP)] +const PROT_TO_PERMS_OS: [[u32; 4]; 4] = [ + [0, PF_R, PF_W, PF_RW], + [PF_R, 0, PF_RW, PF_RW], + [0, 0, 0, 0], + [0, 0, 0, 0], +]; + pub(crate) mod prot { pub(crate) use super::Prot; use super::*; @@ -139,7 +168,7 @@ pub(crate) mod prot { } impl Prot { - const fn from_bits(ap: u8, uxn: u8, pxn: u8) -> Self { + const fn from_bits(ap: u8, uxn: u16, pxn: u16) -> Self { assert!(uxn <= 1); assert!(pxn <= 1); assert!(ap <= 3); @@ -151,6 +180,44 @@ impl Prot { } } + #[cfg(CONFIG_DEV_COREDUMP)] + pub(crate) const fn from_pte(pte: u64) -> Self { + Prot { + high_bits: (pte >> UAT_HIGH_BITS_SHIFT) as u16, + ap: ((pte & UAT_AP_BITS) >> UAT_AP_SHIFT) as u8, + memattr: ((pte & UAT_MEMATTR_BITS) >> UAT_MEMATTR_SHIFT) as u8, + } + } + + #[cfg(CONFIG_DEV_COREDUMP)] + pub(crate) const fn elf_flags(&self) -> u32 { + let ap = (self.ap & 3) as usize; + let uxn = if self.high_bits & HIGH_BITS_UXN != 0 { + 1 + } else { + 0 + }; + let pxn = if self.high_bits & HIGH_BITS_PXN != 0 { + 1 + } else { + 0 + }; + let gpu = self.high_bits & HIGH_BITS_GPU_ACCESS != 0; + + // Format: + // [12 top bits of PTE] [12 bottom bits of PTE] [5 bits pad] [ELF RWX] + let mut perms = if gpu { + PROT_TO_PERMS_OS[ap][(uxn << 1) | pxn] + } else { + PROT_TO_PERMS_FW[ap][(uxn << 1) | pxn] + }; + + perms |= ((self.as_pte() >> 52) << 20) as u32; + perms |= ((self.as_pte() & 0xfff) << 8) as u32; + + perms + } + const fn memattr(&self, memattr: u8) -> Self { Self { memattr, ..*self } } From 0df8326c05f0e5ec7a9a99c4314d1d44eac734f2 Mon Sep 17 00:00:00 2001 From: Asahi Lina Date: Tue, 28 Jan 2025 02:12:36 +0900 Subject: [PATCH 1289/3902] drm/asahi: crashdump: Add crash dumper module Signed-off-by: Asahi Lina --- drivers/gpu/drm/asahi/asahi.rs | 1 + drivers/gpu/drm/asahi/crashdump.rs | 263 +++++++++++++++++++++++++++++ 2 files changed, 264 insertions(+) create mode 100644 drivers/gpu/drm/asahi/crashdump.rs diff --git a/drivers/gpu/drm/asahi/asahi.rs b/drivers/gpu/drm/asahi/asahi.rs index 016b6f5cfdf03e..929672a1e4fe71 100644 --- a/drivers/gpu/drm/asahi/asahi.rs +++ b/drivers/gpu/drm/asahi/asahi.rs @@ -6,6 +6,7 @@ mod alloc; mod buffer; mod channel; +mod crashdump; mod debug; mod driver; mod event; diff --git a/drivers/gpu/drm/asahi/crashdump.rs b/drivers/gpu/drm/asahi/crashdump.rs new file mode 100644 index 00000000000000..062184f0f093e4 --- /dev/null +++ b/drivers/gpu/drm/asahi/crashdump.rs @@ -0,0 +1,263 @@ +// SPDX-License-Identifier: GPL-2.0-only OR MIT + +//! GPU crash dump formatter +//! +//! Takes a raw dump of firmware/kernel mapped pages from `pgtable` and formats it into +//! an ELF core dump suitable for dumping into userspace. + +use core::mem::size_of; + +use kernel::{error::Result, page::Page, prelude::*, types::Owned}; + +use crate::hw; +use crate::pgtable::{self, DumpedPage, Prot, UAT_PGSZ}; +use crate::util::align; +use kernel::uapi; + +pub(crate) struct CrashDump { + headers: KVVec, + pages: KVVec>, +} + +const NOTE_NAME_AGX: &str = &"AGX"; +const NOTE_AGX_DUMP_INFO: u32 = 1; + +const NOTE_NAME_RTKIT: &str = &"RTKIT"; +const NOTE_RTKIT_CRASHLOG: u32 = 1; + +#[repr(C)] +pub(crate) struct AGXDumpInfo { + initdata_address: u64, + chip_id: u32, + gpu_gen: hw::GpuGen, + gpu_variant: hw::GpuVariant, + gpu_rev: hw::GpuRevision, + total_active_cores: u32, + firmware_version: [u32; 6], +} + +struct ELFNote { + name: &'static str, + ty: u32, + data: KVVec, +} + +pub(crate) struct CrashDumpBuilder { + page_dump: KVVec, + notes: KVec, +} + +// Helper to convert ELF headers into byte slices +// TODO: Hook this up into kernel::AsBytes somehow +unsafe trait AsBytes: Sized { + fn as_bytes(&self) -> &[u8] { + // SAFETY: This trait is only implemented for types with no padding bytes + unsafe { core::slice::from_raw_parts(self as *const _ as *const u8, size_of::()) } + } + fn slice_as_bytes(slice: &[Self]) -> &[u8] { + // SAFETY: This trait is only implemented for types with no padding bytes + unsafe { + core::slice::from_raw_parts( + slice.as_ptr() as *const u8, + slice.len() * size_of::(), + ) + } + } +} + +// SAFETY: This type has no padding +unsafe impl AsBytes for uapi::Elf64_Ehdr {} +// SAFETY: This type has no padding +unsafe impl AsBytes for uapi::Elf64_Phdr {} +// SAFETY: This type has no padding +unsafe impl AsBytes for uapi::Elf64_Nhdr {} +// SAFETY: This type has no padding +unsafe impl AsBytes for AGXDumpInfo {} + +const FIRMWARE_ENTRYPOINT: u64 = 0xFFFFFF8000000000u64; + +impl CrashDumpBuilder { + pub(crate) fn new(page_dump: KVVec) -> Result { + Ok(CrashDumpBuilder { + page_dump, + notes: KVec::new(), + }) + } + + pub(crate) fn add_agx_info( + &mut self, + cfg: &hw::HwConfig, + dyncfg: &hw::DynConfig, + initdata_address: u64, + ) -> Result { + let mut info = AGXDumpInfo { + chip_id: cfg.chip_id, + gpu_gen: dyncfg.id.gpu_gen, + gpu_variant: dyncfg.id.gpu_variant, + gpu_rev: dyncfg.id.gpu_rev, + total_active_cores: dyncfg.id.total_active_cores, + firmware_version: [0; 6], + initdata_address, + }; + info.firmware_version[..dyncfg.firmware_version.len().min(6)] + .copy_from_slice(&dyncfg.firmware_version); + + let mut data = KVVec::new(); + data.extend_from_slice(info.as_bytes(), GFP_KERNEL)?; + + self.notes.push( + ELFNote { + name: NOTE_NAME_AGX, + ty: NOTE_AGX_DUMP_INFO, + data, + }, + GFP_KERNEL, + )?; + Ok(()) + } + + pub(crate) fn add_crashlog(&mut self, crashlog: &[u8]) -> Result { + let mut data = KVVec::new(); + data.extend_from_slice(&crashlog, GFP_KERNEL)?; + + self.notes.push( + ELFNote { + name: NOTE_NAME_RTKIT, + ty: NOTE_RTKIT_CRASHLOG, + data, + }, + GFP_KERNEL, + )?; + + Ok(()) + } + + pub(crate) fn finalize(self) -> Result { + let CrashDumpBuilder { page_dump, notes } = self; + + let mut ehdr: uapi::Elf64_Ehdr = Default::default(); + + ehdr.e_ident[uapi::EI_MAG0 as usize..=uapi::EI_MAG3 as usize].copy_from_slice(b"\x7fELF"); + ehdr.e_ident[uapi::EI_CLASS as usize] = uapi::ELFCLASS64 as u8; + ehdr.e_ident[uapi::EI_DATA as usize] = uapi::ELFDATA2LSB as u8; + ehdr.e_ident[uapi::EI_VERSION as usize] = uapi::EV_CURRENT as u8; + ehdr.e_type = uapi::ET_CORE as u16; + ehdr.e_machine = uapi::EM_AARCH64 as u16; + ehdr.e_version = uapi::EV_CURRENT as u32; + ehdr.e_entry = FIRMWARE_ENTRYPOINT; + ehdr.e_ehsize = core::mem::size_of::() as u16; + ehdr.e_phentsize = core::mem::size_of::() as u16; + + let phdr_offset = core::mem::size_of::(); + + // PHDRs come after the ELF header + ehdr.e_phoff = phdr_offset as u64; + + let mut phdrs = KVVec::new(); + + // First PHDR is the NOTE section + phdrs.push( + uapi::Elf64_Phdr { + p_type: uapi::PT_NOTE, + p_flags: uapi::PF_R, + p_align: 1, + ..Default::default() + }, + GFP_KERNEL, + )?; + + // Generate the page phdrs. The offset will be fixed up later. + let mut off: usize = 0; + let mut next = None; + let mut pages: KVVec> = KVVec::new(); + + for mut page in page_dump { + let vaddr = page.iova; + let paddr = page.pte & pgtable::PTE_ADDR_BITS; + let flags = Prot::from_pte(page.pte).elf_flags(); + let valid = page.data.is_some(); + let cur = (vaddr, paddr, flags, valid); + if Some(cur) != next { + phdrs.push( + uapi::Elf64_Phdr { + p_type: uapi::PT_LOAD, + p_offset: if valid { off as u64 } else { 0 }, + p_vaddr: vaddr, + p_paddr: paddr, + p_filesz: if valid { UAT_PGSZ as u64 } else { 0 }, + p_memsz: UAT_PGSZ as u64, + p_flags: flags, + p_align: UAT_PGSZ as u64, + ..Default::default() + }, + GFP_KERNEL, + )?; + if valid { + off += UAT_PGSZ; + } + } else { + let ph = phdrs.last_mut().unwrap(); + ph.p_memsz += UAT_PGSZ as u64; + if valid { + ph.p_filesz += UAT_PGSZ as u64; + off += UAT_PGSZ; + } + } + if let Some(data_page) = page.data.take() { + pages.push(data_page, GFP_KERNEL)?; + } + next = Some(( + vaddr + UAT_PGSZ as u64, + paddr + UAT_PGSZ as u64, + flags, + valid, + )); + } + + ehdr.e_phnum = phdrs.len() as u16; + + let note_offset = phdr_offset + size_of::() * phdrs.len(); + + let mut note_data: KVVec = KVVec::new(); + + for note in notes { + let hdr = uapi::Elf64_Nhdr { + n_namesz: note.name.len() as u32 + 1, + n_descsz: note.data.len() as u32, + n_type: note.ty, + }; + note_data.extend_from_slice(hdr.as_bytes(), GFP_KERNEL)?; + note_data.extend_from_slice(note.name.as_bytes(), GFP_KERNEL)?; + note_data.push(0, GFP_KERNEL)?; + while note_data.len() & 3 != 0 { + note_data.push(0, GFP_KERNEL)?; + } + note_data.extend_from_slice(¬e.data, GFP_KERNEL)?; + while note_data.len() & 3 != 0 { + note_data.push(0, GFP_KERNEL)?; + } + } + + // NOTE section comes after the PHDRs + phdrs[0].p_offset = note_offset as u64; + phdrs[0].p_filesz = note_data.len() as u64; + + // Align data section to the page size + let data_offset = align(note_offset + note_data.len(), UAT_PGSZ); + + // Fix up data PHDR offsets + for phdr in &mut phdrs[1..] { + phdr.p_offset += data_offset as u64; + } + + // Build ELF header buffer + let mut headers: KVVec = KVVec::from_elem(0, data_offset, GFP_KERNEL)?; + + headers[0..size_of::()].copy_from_slice(ehdr.as_bytes()); + headers[phdr_offset..phdr_offset + phdrs.len() * size_of::()] + .copy_from_slice(AsBytes::slice_as_bytes(&phdrs)); + headers[note_offset..note_offset + note_data.len()].copy_from_slice(¬e_data); + + Ok(CrashDump { headers, pages }) + } +} From 851eee4a21e14d00adff5fa3def6f1b06e497858 Mon Sep 17 00:00:00 2001 From: Asahi Lina Date: Tue, 28 Jan 2025 02:28:01 +0900 Subject: [PATCH 1290/3902] drm/asahi: mmu: Wire up kernel AS dumper Signed-off-by: Asahi Lina --- drivers/gpu/drm/asahi/mmu.rs | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/drivers/gpu/drm/asahi/mmu.rs b/drivers/gpu/drm/asahi/mmu.rs index 6520e1dd547d72..de652cf40a793c 100644 --- a/drivers/gpu/drm/asahi/mmu.rs +++ b/drivers/gpu/drm/asahi/mmu.rs @@ -100,13 +100,17 @@ pub(crate) const IOVA_USER_TOP: u64 = 1 << (UAT_IAS as u64); pub(crate) const IOVA_USER_RANGE: Range = IOVA_USER_BASE..IOVA_USER_TOP; /// Upper/kernel base VA -// const IOVA_TTBR1_BASE: usize = 0xffffff8000000000; +#[cfg(CONFIG_DEV_COREDUMP)] +const IOVA_TTBR1_BASE: u64 = 0xffffff8000000000; /// Driver-managed kernel base VA const IOVA_KERN_BASE: u64 = 0xffffffa000000000; /// Driver-managed kernel top VA const IOVA_KERN_TOP: u64 = 0xffffffb000000000; -/// Lower/user VA range +/// Driver-managed kernel VA range const IOVA_KERN_RANGE: Range = IOVA_KERN_BASE..IOVA_KERN_TOP; +/// Full kernel VA range +#[cfg(CONFIG_DEV_COREDUMP)] +const IOVA_KERN_FULL_RANGE: Range = IOVA_TTBR1_BASE..(!UAT_PGMSK as u64); const TTBR_VALID: u64 = 0x1; // BIT(0) const TTBR_ASID_SHIFT: usize = 48; @@ -1390,6 +1394,12 @@ impl Uat { &self.kernel_lower_vm } + #[cfg(CONFIG_DEV_COREDUMP)] + pub(crate) fn dump_kernel_pages(&self) -> Result> { + let mut inner = self.kernel_vm.inner.exec_lock(None, false)?; + inner.page_table.dump_pages(IOVA_KERN_FULL_RANGE) + } + /// Returns the base physical address of the TTBAT region. pub(crate) fn ttb_base(&self) -> u64 { let inner = self.inner.lock(); From b031d33f3f43e1898938a89a9357756d1f1e712f Mon Sep 17 00:00:00 2001 From: Asahi Lina Date: Tue, 28 Jan 2025 02:36:19 +0900 Subject: [PATCH 1291/3902] drm/asahi: gpu: Hook up crashdump generation Signed-off-by: Asahi Lina --- drivers/gpu/drm/asahi/gpu.rs | 25 ++++++++++++++++++++++++- 1 file changed, 24 insertions(+), 1 deletion(-) diff --git a/drivers/gpu/drm/asahi/gpu.rs b/drivers/gpu/drm/asahi/gpu.rs index 87bf9a3277a378..80d5c229322a61 100644 --- a/drivers/gpu/drm/asahi/gpu.rs +++ b/drivers/gpu/drm/asahi/gpu.rs @@ -365,11 +365,17 @@ impl rtkit::Operations for GpuManager::ver { ch.event.poll(); } - fn crashed(data: ::Borrowed<'_>, _crashlog: Option<&[u8]>) { + fn crashed(data: ::Borrowed<'_>, crashlog: Option<&[u8]>) { let dev = &data.dev; data.crashed.store(true, Ordering::Relaxed); + if let Err(e) = data.generate_crashdump(crashlog) { + dev_err!(dev.as_ref(), "Could not dump kernel VM pages: {:?}\n", e); + } + #[cfg(not(CONFIG_DEV_COREDUMP))] + let _ = crashlog; + if debug_enabled(DebugFlags::OopsOnGpuCrash) { panic!("GPU firmware crashed"); } else { @@ -1149,6 +1155,23 @@ impl GpuManager::ver { Ok(()) } + + fn generate_crashdump(&self, crashlog: Option<&[u8]>) -> Result { + // Lock the allocators, to block kernel/FW memory mutations (mostly) + let kalloc = self.alloc(); + let pages = self.uat.dump_kernel_pages()?; + core::mem::drop(kalloc); + + let mut crashdump = crashdump::CrashDumpBuilder::new(pages)?; + let initdata_addr = self.initdata.gpu_va().get(); + crashdump.add_agx_info(self.cfg, &self.dyncfg, initdata_addr)?; + if let Some(crashlog) = crashlog { + crashdump.add_crashlog(crashlog)?; + } + let crashdump = crashdump.finalize(); + + Ok(()) + } } #[versions(AGX)] From e7abe09302691c562baedeeb8aab725e06e24ff2 Mon Sep 17 00:00:00 2001 From: Asahi Lina Date: Tue, 28 Jan 2025 02:06:00 +0900 Subject: [PATCH 1292/3902] rust: uapi: Add ELF headers Useful for drivers which need to parse firmware files or generate device coredumps. Signed-off-by: Asahi Lina --- rust/uapi/uapi_helper.h | 2 ++ 1 file changed, 2 insertions(+) diff --git a/rust/uapi/uapi_helper.h b/rust/uapi/uapi_helper.h index 8d0b4293cd2f19..512ac0aea08fde 100644 --- a/rust/uapi/uapi_helper.h +++ b/rust/uapi/uapi_helper.h @@ -12,6 +12,8 @@ #include #include #include +#include +#include #include #include #include From 7e251b8697385550802e5b997ab33279c09ba374 Mon Sep 17 00:00:00 2001 From: Asahi Lina Date: Tue, 28 Jan 2025 03:53:14 +0900 Subject: [PATCH 1293/3902] rust: devcoredump: Add devcoredump abstraction Signed-off-by: Asahi Lina --- rust/bindings/bindings_helper.h | 1 + rust/kernel/devcoredump.rs | 79 +++++++++++++++++++++++++++++++++ rust/kernel/lib.rs | 2 + 3 files changed, 82 insertions(+) create mode 100644 rust/kernel/devcoredump.rs diff --git a/rust/bindings/bindings_helper.h b/rust/bindings/bindings_helper.h index 71076935d31d93..e43ead6fc4e3ad 100644 --- a/rust/bindings/bindings_helper.h +++ b/rust/bindings/bindings_helper.h @@ -53,6 +53,7 @@ #include #include #include +#include #include #include #include diff --git a/rust/kernel/devcoredump.rs b/rust/kernel/devcoredump.rs new file mode 100644 index 00000000000000..a4a42d862f63b5 --- /dev/null +++ b/rust/kernel/devcoredump.rs @@ -0,0 +1,79 @@ +// SPDX-License-Identifier: GPL-2.0-only OR MIT + +//! Device coredump support. +//! +//! C header: [`include/linux/devcoredump.h`](../../../../include/linux/devcoredump.h) + +use crate::{ + alloc, bindings, device, error::from_result, prelude::Result, time::Jiffies, + types::ForeignOwnable, ThisModule, +}; + +use core::ops::Deref; + +/// The default timeout for device coredumps. +pub const DEFAULT_TIMEOUT: Jiffies = bindings::DEVCD_TIMEOUT as Jiffies; + +/// Trait to implement reading from a device coredump. +/// +/// Users must implement this trait to provide device coredump support. +pub trait DevCoreDump { + /// Returns the IOVA (virtual address) of the buffer from RTKit's point of view, or an error if + /// unavailable. + fn read(&self, buf: &mut [u8], offset: usize) -> Result; +} + +unsafe extern "C" fn read_callback< + 'a, + T: ForeignOwnable: Deref>, + D: DevCoreDump, +>( + buffer: *mut crate::ffi::c_char, + offset: bindings::loff_t, + count: usize, + data: *mut crate::ffi::c_void, + _datalen: usize, +) -> isize { + // SAFETY: This pointer came from into_foreign() below. + let coredump = unsafe { T::borrow(data.cast()) }; + // SAFETY: The caller guarantees `buffer` points to at least `count` bytes. + let buf = unsafe { core::slice::from_raw_parts_mut(buffer, count) }; + + from_result(|| Ok(coredump.read(buf, offset.try_into()?)?.try_into()?)) +} + +unsafe extern "C" fn free_callback< + 'a, + T: ForeignOwnable: Deref>, + D: DevCoreDump, +>( + data: *mut crate::ffi::c_void, +) { + // SAFETY: This pointer came from into_foreign() below. + unsafe { + T::from_foreign(data.cast()); + } +} + +/// Registers a coredump for the given device. +pub fn dev_coredump<'a, T: ForeignOwnable: Deref>, D: DevCoreDump>( + dev: &device::Device, + module: &'static ThisModule, + coredump: T, + gfp: alloc::Flags, + timeout: Jiffies, +) { + // SAFETY: Call upholds dev_coredumpm lifetime requirements. + unsafe { + bindings::dev_coredumpm_timeout( + dev.as_raw(), + module.0, + coredump.into_foreign() as *mut _, + 0, + gfp.as_raw(), + Some(read_callback::<'a, T, D>), + Some(free_callback::<'a, T, D>), + timeout, + ) + } +} diff --git a/rust/kernel/lib.rs b/rust/kernel/lib.rs index 81accf0118bd0d..dcec857743f2f1 100644 --- a/rust/kernel/lib.rs +++ b/rust/kernel/lib.rs @@ -97,6 +97,8 @@ pub mod cpufreq; pub mod cpumask; pub mod cred; pub mod debugfs; +#[cfg(CONFIG_DEV_COREDUMP)] +pub mod devcoredump; pub mod device; pub mod device_id; pub mod devres; From d17aabef2c2a307f37281036b94d590865a71ef1 Mon Sep 17 00:00:00 2001 From: Asahi Lina Date: Tue, 28 Jan 2025 03:54:28 +0900 Subject: [PATCH 1294/3902] drm/asahi: Hook up crashdump to devcoredump Signed-off-by: Asahi Lina --- drivers/gpu/drm/asahi/Kconfig | 1 + drivers/gpu/drm/asahi/asahi.rs | 1 + drivers/gpu/drm/asahi/crashdump.rs | 68 ++++++++++++++++++++++++------ drivers/gpu/drm/asahi/gpu.rs | 22 ++++++++-- 4 files changed, 75 insertions(+), 17 deletions(-) diff --git a/drivers/gpu/drm/asahi/Kconfig b/drivers/gpu/drm/asahi/Kconfig index 254f214a67c3e8..ab98d29b6b4a84 100644 --- a/drivers/gpu/drm/asahi/Kconfig +++ b/drivers/gpu/drm/asahi/Kconfig @@ -24,6 +24,7 @@ config DRM_ASAHI select RUST_DRM_GEM_SHMEM_HELPER select RUST_DRM_GPUVM select RUST_APPLE_RTKIT + select WANT_DEV_COREDUMP help DRM driver for Apple AGX GPUs (G13x, found in the M1 SoC family) diff --git a/drivers/gpu/drm/asahi/asahi.rs b/drivers/gpu/drm/asahi/asahi.rs index 929672a1e4fe71..9164bec7d89d4d 100644 --- a/drivers/gpu/drm/asahi/asahi.rs +++ b/drivers/gpu/drm/asahi/asahi.rs @@ -6,6 +6,7 @@ mod alloc; mod buffer; mod channel; +#[cfg(CONFIG_DEV_COREDUMP)] mod crashdump; mod debug; mod driver; diff --git a/drivers/gpu/drm/asahi/crashdump.rs b/drivers/gpu/drm/asahi/crashdump.rs index 062184f0f093e4..bd9f2f1649584f 100644 --- a/drivers/gpu/drm/asahi/crashdump.rs +++ b/drivers/gpu/drm/asahi/crashdump.rs @@ -7,22 +7,28 @@ use core::mem::size_of; -use kernel::{error::Result, page::Page, prelude::*, types::Owned}; +use kernel::{ + devcoredump::DevCoreDump, + error::Result, + page::{Page, PAGE_MASK, PAGE_SHIFT, PAGE_SIZE}, + prelude::*, + types::Owned, + uapi, +}; use crate::hw; use crate::pgtable::{self, DumpedPage, Prot, UAT_PGSZ}; use crate::util::align; -use kernel::uapi; pub(crate) struct CrashDump { headers: KVVec, pages: KVVec>, } -const NOTE_NAME_AGX: &str = &"AGX"; +const NOTE_NAME_AGX: &str = "AGX"; const NOTE_AGX_DUMP_INFO: u32 = 1; -const NOTE_NAME_RTKIT: &str = &"RTKIT"; +const NOTE_NAME_RTKIT: &str = "RTKIT"; const NOTE_RTKIT_CRASHLOG: u32 = 1; #[repr(C)] @@ -47,8 +53,12 @@ pub(crate) struct CrashDumpBuilder { notes: KVec, } -// Helper to convert ELF headers into byte slices -// TODO: Hook this up into kernel::AsBytes somehow +/// Helper to convert ELF headers into byte slices +/// TODO: Hook this up into kernel::AsBytes somehow +/// +/// # Safety +/// +/// Types implementing this trait must have no padding bytes. unsafe trait AsBytes: Sized { fn as_bytes(&self) -> &[u8] { // SAFETY: This trait is only implemented for types with no padding bytes @@ -57,10 +67,7 @@ unsafe trait AsBytes: Sized { fn slice_as_bytes(slice: &[Self]) -> &[u8] { // SAFETY: This trait is only implemented for types with no padding bytes unsafe { - core::slice::from_raw_parts( - slice.as_ptr() as *const u8, - slice.len() * size_of::(), - ) + core::slice::from_raw_parts(slice.as_ptr() as *const u8, core::mem::size_of_val(slice)) } } } @@ -118,7 +125,7 @@ impl CrashDumpBuilder { pub(crate) fn add_crashlog(&mut self, crashlog: &[u8]) -> Result { let mut data = KVVec::new(); - data.extend_from_slice(&crashlog, GFP_KERNEL)?; + data.extend_from_slice(crashlog, GFP_KERNEL)?; self.notes.push( ELFNote { @@ -143,7 +150,7 @@ impl CrashDumpBuilder { ehdr.e_ident[uapi::EI_VERSION as usize] = uapi::EV_CURRENT as u8; ehdr.e_type = uapi::ET_CORE as u16; ehdr.e_machine = uapi::EM_AARCH64 as u16; - ehdr.e_version = uapi::EV_CURRENT as u32; + ehdr.e_version = uapi::EV_CURRENT; ehdr.e_entry = FIRMWARE_ENTRYPOINT; ehdr.e_ehsize = core::mem::size_of::() as u16; ehdr.e_phentsize = core::mem::size_of::() as u16; @@ -188,7 +195,6 @@ impl CrashDumpBuilder { p_memsz: UAT_PGSZ as u64, p_flags: flags, p_align: UAT_PGSZ as u64, - ..Default::default() }, GFP_KERNEL, )?; @@ -261,3 +267,39 @@ impl CrashDumpBuilder { Ok(CrashDump { headers, pages }) } } + +impl DevCoreDump for CrashDump { + fn read(&self, buf: &mut [u8], mut offset: usize) -> Result { + let mut read = 0; + let mut left = buf.len(); + if offset < self.headers.len() { + let block = left.min(self.headers.len() - offset); + buf[..block].copy_from_slice(&self.headers[offset..offset + block]); + read += block; + offset += block; + left -= block; + } + if left == 0 { + return Ok(read); + } + offset -= self.headers.len(); // Offset from the page area + + while left > 0 { + let page_index = offset >> PAGE_SHIFT; + let page_offset = offset & !PAGE_MASK; + let block = left.min(PAGE_SIZE - page_offset); + let Some(page) = self.pages.get(page_index) else { + break; + }; + let slice = &mut buf[read..read + block]; + // SAFETY: We own the page, and the slice guarantees the + // dst length is sufficient. + unsafe { page.read_raw(slice.as_mut_ptr(), page_offset, slice.len())? }; + read += block; + offset += block; + left -= block; + } + + Ok(read) + } +} diff --git a/drivers/gpu/drm/asahi/gpu.rs b/drivers/gpu/drm/asahi/gpu.rs index 80d5c229322a61..ba0c03bbd2d63d 100644 --- a/drivers/gpu/drm/asahi/gpu.rs +++ b/drivers/gpu/drm/asahi/gpu.rs @@ -37,13 +37,17 @@ use kernel::{ UniqueArc, // }, time::{ - msecs_to_jiffies, Delta, Instant, Monotonic, // }, types::ForeignOwnable, // }; +#[cfg(CONFIG_DEV_COREDUMP)] +use kernel::{ + devcoredump, + time::msecs_to_jiffies, // +}; use crate::alloc::Allocator; use crate::debug::*; @@ -370,8 +374,9 @@ impl rtkit::Operations for GpuManager::ver { data.crashed.store(true, Ordering::Relaxed); + #[cfg(CONFIG_DEV_COREDUMP)] if let Err(e) = data.generate_crashdump(crashlog) { - dev_err!(dev.as_ref(), "Could not dump kernel VM pages: {:?}\n", e); + dev_err!(dev.as_ref(), "Could not generate crashdump: {:?}\n", e); } #[cfg(not(CONFIG_DEV_COREDUMP))] let _ = crashlog; @@ -1156,19 +1161,28 @@ impl GpuManager::ver { Ok(()) } + #[cfg(CONFIG_DEV_COREDUMP)] fn generate_crashdump(&self, crashlog: Option<&[u8]>) -> Result { // Lock the allocators, to block kernel/FW memory mutations (mostly) let kalloc = self.alloc(); let pages = self.uat.dump_kernel_pages()?; core::mem::drop(kalloc); - let mut crashdump = crashdump::CrashDumpBuilder::new(pages)?; + let mut crashdump = crate::crashdump::CrashDumpBuilder::new(pages)?; let initdata_addr = self.initdata.gpu_va().get(); crashdump.add_agx_info(self.cfg, &self.dyncfg, initdata_addr)?; if let Some(crashlog) = crashlog { crashdump.add_crashlog(crashlog)?; } - let crashdump = crashdump.finalize(); + let crashdump = KBox::new(crashdump.finalize()?, GFP_KERNEL)?; + + devcoredump::dev_coredump( + self.dev.as_ref(), + &crate::THIS_MODULE, + crashdump, + GFP_KERNEL, + msecs_to_jiffies(60 * 60 * 1000), + ); Ok(()) } From 4655d12184d897408ddd80d1fa397db537a42874 Mon Sep 17 00:00:00 2001 From: Sasha Finkelstein Date: Sun, 11 May 2025 12:40:04 +0200 Subject: [PATCH 1295/3902] drm/asahi: starlight-debug - pass exact size via "debug,*-size" properties - skip comparison if the starlight data is missing - use `dev_{err,info}!` for logging - explicitly log matching data Signed-off-by: Sasha Finkelstein Co-developed-by: Janne Grunau Signed-off-by: Janne Grunau --- drivers/gpu/drm/asahi/gpu.rs | 44 ++++++++++++++++ drivers/gpu/drm/asahi/hw/mod.rs | 4 ++ drivers/gpu/drm/asahi/initdata.rs | 88 +++++++++++++++++++++++++++++++ 3 files changed, 136 insertions(+) diff --git a/drivers/gpu/drm/asahi/gpu.rs b/drivers/gpu/drm/asahi/gpu.rs index ba0c03bbd2d63d..1d7bf094651d94 100644 --- a/drivers/gpu/drm/asahi/gpu.rs +++ b/drivers/gpu/drm/asahi/gpu.rs @@ -13,6 +13,7 @@ use core::any::Any; use core::ops::Range; +use core::slice; use core::sync::atomic::{ AtomicBool, AtomicU64, @@ -23,6 +24,8 @@ use kernel::{ c_str, drm::gem::shmem, error::code::*, + io::mem::{Mem, MemFlags}, + iosys_map::IoSysMapRef, macros::versions, new_mutex, prelude::*, @@ -773,6 +776,28 @@ impl GpuManager::ver { Ok(x) } + fn load_hwdata_blob(dev: &AsahiDevice, name: &CStr, size_name: &CStr) -> Result> { + let of_node = dev.as_ref().of_node().ok_or(EINVAL)?; + let size: usize = dev + .as_ref() + .fwnode() + .ok_or(ENOENT)? + .property_read::(size_name) + .or(0) + .try_into()?; + let res = of_node.reserved_mem_region_to_resource_byname(name)?; + // SAFETY: No dma here, just loading init data. + let mem = unsafe { Mem::try_new(res, MemFlags::WB)? }; + if size > mem.size() { + return Err(ENOENT); + } + // SAFETY: trusting the bootloader to fill it out correctly + let blob_sl = unsafe { slice::from_raw_parts(mem.ptr(), size) }; + let mut blob = KVVec::new(); + blob.extend_from_slice(blob_sl, GFP_KERNEL)?; + Ok(blob) + } + /// Fetch and validate the GPU dynamic configuration from the device tree and hardware. /// /// Force disable inlining to avoid blowing up the stack. @@ -880,6 +905,25 @@ impl GpuManager::ver { firmware_version: fwnode .property_read_array_vec(c_str!("apple,firmware-version"), 3)? .or(kernel::kvec![0; 3]?), + + hw_data_a: Self::load_hwdata_blob( + dev, + c_str!("hw-cal-a"), + c_str!("debug,hw-cal-a-size"), + ) + .unwrap_or(KVVec::new()), + hw_data_b: Self::load_hwdata_blob( + dev, + c_str!("hw-cal-b"), + c_str!("debug,hw-cal-b-size"), + ) + .unwrap_or(KVVec::new()), + hw_globals: Self::load_hwdata_blob( + dev, + c_str!("globals"), + c_str!("debug,globals-size"), + ) + .unwrap_or(KVVec::new()), }, GFP_KERNEL, )?) diff --git a/drivers/gpu/drm/asahi/hw/mod.rs b/drivers/gpu/drm/asahi/hw/mod.rs index 8841073e1b4c70..611764b5463b59 100644 --- a/drivers/gpu/drm/asahi/hw/mod.rs +++ b/drivers/gpu/drm/asahi/hw/mod.rs @@ -303,6 +303,10 @@ pub(crate) struct DynConfig { /// Firmware version. #[allow(dead_code)] pub(crate) firmware_version: KVec, + + pub(crate) hw_data_a: KVVec, + pub(crate) hw_data_b: KVVec, + pub(crate) hw_globals: KVVec, } /// Specific GPU ID configuration fetched from SGX MMIO registers. diff --git a/drivers/gpu/drm/asahi/initdata.rs b/drivers/gpu/drm/asahi/initdata.rs index 3436522b95cb98..b190a7da6fe85f 100644 --- a/drivers/gpu/drm/asahi/initdata.rs +++ b/drivers/gpu/drm/asahi/initdata.rs @@ -483,6 +483,35 @@ impl<'a> InitDataBuilder::ver<'a> { raw.unk_hws2[i] = if *j == 0xffff { 0 } else { j / 2 }; } + if !dyncfg.hw_data_b.is_empty() { + unsafe { + let mut matches: bool = true; + let sla = core::slice::from_raw_parts( + raw as *const raw::HwDataA::ver as *const u8, + core::mem::size_of::(), + ); + if sla.len() != dyncfg.hw_data_a.len() { + matches = false; + dev_err!( + self.dev.as_ref(), + "!!! Hwdata A size mismatch: {} {}", + sla.len(), + dyncfg.hw_data_a.len(), + ); + } + for i in 0..core::cmp::min(sla.len(), dyncfg.hw_data_a.len()) { + if sla[i] != dyncfg.hw_data_a[i] { + matches = false; + dev_err!(self.dev.as_ref(), "!!! Hwdata A first mismatch: {i}"); + break; + } + } + if matches { + dev_info!(self.dev.as_ref(), "!!! Hwdata A match"); + } + } + } + Ok(()) }) }) @@ -633,6 +662,35 @@ impl<'a> InitDataBuilder::ver<'a> { raw.gpu_rev_id = hw::GpuRevisionID::B0 as u32; } + if !dyncfg.hw_data_b.is_empty() { + unsafe { + let mut matches: bool = true; + let sla = core::slice::from_raw_parts( + raw as *const raw::HwDataB::ver as *const u8, + core::mem::size_of::(), + ); + if sla.len() != dyncfg.hw_data_b.len() { + matches = false; + dev_err!( + self.dev.as_ref(), + "!!! Hwdata B size mismatch: {} {}", + sla.len(), + dyncfg.hw_data_b.len(), + ); + } + for i in 0..core::cmp::min(sla.len(), dyncfg.hw_data_b.len()) { + if sla[i] != dyncfg.hw_data_b[i] { + matches = false; + dev_err!(self.dev.as_ref(), "!!! Hwdata B first mismatch: {i}"); + break; + } + } + if matches { + dev_info!(self.dev.as_ref(), "!!! Hwdata B match"); + } + } + } + Ok(()) }) }) @@ -757,6 +815,36 @@ impl<'a> InitDataBuilder::ver<'a> { } raw.unk_118e8 = 1; } + + if !dyncfg.hw_globals.is_empty() { + unsafe { + let mut matches: bool = true; + let sla = core::slice::from_raw_parts( + raw as *const raw::Globals::ver as *const u8, + core::mem::size_of::(), + ); + if sla.len() != dyncfg.hw_globals.len() { + matches = false; + dev_err!( + self.dev.as_ref(), + "!!! Globals size mismatch: {} {}", + sla.len(), + dyncfg.hw_globals.len(), + ); + } + for i in 0..core::cmp::min(sla.len(), dyncfg.hw_globals.len()) { + if sla[i] != dyncfg.hw_globals[i] { + matches = false; + dev_err!(self.dev.as_ref(), "!!! Globals first mismatch: {i}"); + break; + } + } + if matches { + dev_info!(self.dev.as_ref(), "!!! Globals match"); + } + } + } + Ok(()) }) }) From 8bcfb1abb31d5a0e0b8da1ec50934d8a2642c20e Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Wed, 24 Sep 2025 10:24:55 +0200 Subject: [PATCH 1296/3902] drm/asahi: Copy tyr's mmu/vm/range.rs Signed-off-by: Janne Grunau --- drivers/gpu/drm/asahi/asahi.rs | 2 + drivers/gpu/drm/asahi/vm/mod.rs | 5 ++ drivers/gpu/drm/asahi/vm/range.rs | 125 ++++++++++++++++++++++++++++++ 3 files changed, 132 insertions(+) create mode 100644 drivers/gpu/drm/asahi/vm/mod.rs create mode 100644 drivers/gpu/drm/asahi/vm/range.rs diff --git a/drivers/gpu/drm/asahi/asahi.rs b/drivers/gpu/drm/asahi/asahi.rs index 9164bec7d89d4d..296091dced554d 100644 --- a/drivers/gpu/drm/asahi/asahi.rs +++ b/drivers/gpu/drm/asahi/asahi.rs @@ -27,6 +27,8 @@ mod queue; mod regs; mod slotalloc; mod util; +#[cfg(CONFIG_DRM_ASAHI_MAPLE_TREE)] +mod vm; mod workqueue; kernel::module_platform_driver! { diff --git a/drivers/gpu/drm/asahi/vm/mod.rs b/drivers/gpu/drm/asahi/vm/mod.rs new file mode 100644 index 00000000000000..63cf8a76cd5ce4 --- /dev/null +++ b/drivers/gpu/drm/asahi/vm/mod.rs @@ -0,0 +1,5 @@ +// SPDX-License-Identifier: GPL-2.0-only OR MIT + +//! Virtual address space management + +mod range; diff --git a/drivers/gpu/drm/asahi/vm/range.rs b/drivers/gpu/drm/asahi/vm/range.rs new file mode 100644 index 00000000000000..727d29a5cccf02 --- /dev/null +++ b/drivers/gpu/drm/asahi/vm/range.rs @@ -0,0 +1,125 @@ +// SPDX-License-Identifier: GPL-2.0 or MIT + +// Copied from tyr's mmu/vm/range.rs + +//! Range allocator. +//! +//! This module allows you to search for unused ranges to store GEM objects. + +use kernel::alloc::Flags; +use kernel::maple_tree::MapleTreeAlloc; +use kernel::prelude::*; +use kernel::sync::Arc; + +use core::ops::Range; + +/// The actual storage for the ranges. +/// +/// All ranges must fit within the `range` field. +/// +/// The implementation is different on 32-bit and 64-bit cpus. On 64-bit, the 64-bit addresses are +/// stored directly in the maple tree, but on 32-bit, the maple tree stores the ranges translated +/// in the range zero until `range.end-range.start`. This is done because the maple tree uses +/// unsigned long as its address type, which is too small to store the 64-bit address directly on +/// 32-bit machines. +#[pin_data] +struct RangeAllocInner { + #[pin] + maple: MapleTreeAlloc<()>, + range: Range, +} + +/// This object allows you to allocate ranges on the inner maple tree. +pub(crate) struct RangeAlloc { + inner: Arc, +} + +/// Represents a live range in the maple tree. +/// +/// The destructor removes the range from the maple tree, allowing others to allocate it in the +/// future. +pub(crate) struct LiveRange { + inner: Arc, + offset: u64, + size: usize, +} + +impl RangeAlloc { + pub(crate) fn new(start: u64, end: u64, gfp: Flags) -> Result { + if end < start { + return Err(EINVAL); + } + + let inner = Arc::pin_init( + try_pin_init!(RangeAllocInner { + maple <- MapleTreeAlloc::new(), + range: start..end, + }), + gfp, + )?; + + Ok(RangeAlloc { inner }) + } + + pub(crate) fn allocate(&self, size: usize, gfp: Flags) -> Result { + let maple_start = self.inner.range.start as usize; + let maple_end = self.inner.range.end as usize; + + let offset = self + .inner + .maple + .alloc_range(size, (), maple_start..maple_end, gfp)?; + + Ok(LiveRange { + inner: self.inner.clone(), + offset: offset as u64, + size, + }) + } + + pub(crate) fn insert(&self, start: u64, end: u64, gfp: Flags) -> Result { + if end < start { + return Err(EINVAL); + } + if start < self.inner.range.start { + return Err(EINVAL); + } + if end > self.inner.range.end { + return Err(EINVAL); + } + + self.inner + .maple + .insert_range(start as usize..end as usize, (), gfp)?; + + Ok(LiveRange { + inner: self.inner.clone(), + offset: start, + size: (end - start) as usize, + }) + } +} + +impl LiveRange { + pub(crate) fn size(&self) -> usize { + self.size + } + + pub(crate) fn start(&self) -> u64 { + self.offset + } + + pub(crate) fn end(&self) -> u64 { + self.offset + self.size as u64 + } + + pub(crate) fn range(&self) -> Range { + self.start()..self.end() + } +} + +impl Drop for LiveRange { + fn drop(&mut self) { + self.inner.maple.erase(self.offset as usize); + } +} From 1583d9e53a46858148685639e4b98799ed81be43 Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Sun, 2 Nov 2025 10:23:15 +0100 Subject: [PATCH 1297/3902] drm/asahi: Switch gpuvm to DRM_GPUVM_IMMEDIATE_MODE DRM_GPUVM_IMMEDIATE_MODE supports deferred gpuva unlink and gpuvm bu release. Gpuva unlink of imported DMAbufs might drop the last reference of the gem object resulting in calling drm_prime_gem_destroy(). This calls ma_buf_unmap_attachment_unlocked() which expects to be able to lock dma_resv. This obviously deadlocks if called from a locked gpuvm. Signed-off-by: Janne Grunau --- drivers/gpu/drm/asahi/file.rs | 4 ++++ drivers/gpu/drm/asahi/mmu.rs | 24 ++++++++++++++++-------- 2 files changed, 20 insertions(+), 8 deletions(-) diff --git a/drivers/gpu/drm/asahi/file.rs b/drivers/gpu/drm/asahi/file.rs index 3a5460c091a728..02a4ce874a92c8 100644 --- a/drivers/gpu/drm/asahi/file.rs +++ b/drivers/gpu/drm/asahi/file.rs @@ -674,6 +674,8 @@ impl File { vm.bind_object(&bo, data.addr, data.range, data.offset, prot, single_page)?; + vm.bo_deferred_cleanup(); + Ok(0) } @@ -736,6 +738,8 @@ impl File { vm.unmap_range(range.start, range.range())?; + vm.bo_deferred_cleanup(); + Ok(0) } diff --git a/drivers/gpu/drm/asahi/mmu.rs b/drivers/gpu/drm/asahi/mmu.rs index de652cf40a793c..e8f2bd48f11211 100644 --- a/drivers/gpu/drm/asahi/mmu.rs +++ b/drivers/gpu/drm/asahi/mmu.rs @@ -24,6 +24,7 @@ use core::sync::atomic::{ use kernel::{ addr::PhysicalAddr, + bindings::drm_gpuvm_flags_DRM_GPUVM_IMMEDIATE_MODE, c_str, device, drm::{ @@ -330,7 +331,7 @@ impl gpuvm::DriverGpuVm for VmInner { mem::sync(); } - if op.unmap_and_unlink_va().is_none() { + if op.unmap_and_unlink_va_defer().is_none() { dev_err!(self.dev.as_ref(), "step_unmap: could not unlink gpuva"); } Ok(()) @@ -384,7 +385,7 @@ impl gpuvm::DriverGpuVm for VmInner { mem::sync(); } - if op.unmap().unmap_and_unlink_va().is_none() { + if op.unmap().unmap_and_unlink_va_defer().is_none() { dev_err!(self.dev.as_ref(), "step_unmap: could not unlink gpuva"); } @@ -1009,6 +1010,8 @@ impl Vm { dummy_obj: dummy_obj.gem.clone(), inner: gpuvm::GpuVm::new( c_str!("Asahi::GpuVm"), + // TODO: should we using DRM_GPUVM_RESV_PROTECTED as well? + drm_gpuvm_flags_DRM_GPUVM_IMMEDIATE_MODE, dev, dummy_obj.gem.clone(), gpuvm_range, @@ -1047,7 +1050,7 @@ impl Vm { let size = object_range.range(); let sgt = gem.owned_sg_table()?; let mut inner = self.inner.exec_lock(Some(gem), false)?; - let vm_bo = inner.obtain_bo()?; + let vm_bo = self.inner.obtain_bo(gem)?; let mut vm_bo_guard = vm_bo.inner().sgt.lock(); if vm_bo_guard.is_none() { @@ -1095,7 +1098,7 @@ impl Vm { let sgt = gem.owned_sg_table()?; let mut inner = self.inner.exec_lock(Some(&gem), false)?; - let vm_bo = inner.obtain_bo()?; + let vm_bo = self.inner.obtain_bo(&gem)?; let mut vm_bo_guard = vm_bo.inner().sgt.lock(); if vm_bo_guard.is_none() { @@ -1153,7 +1156,7 @@ impl Vm { // Preallocate the page tables, to fail early if we ENOMEM inner.page_table.alloc_pages(addr..(addr + size))?; - let vm_bo = inner.obtain_bo()?; + let vm_bo = self.inner.obtain_bo(gem)?; let mut vm_bo_guard = vm_bo.inner().sgt.lock(); if vm_bo_guard.is_none() { @@ -1269,11 +1272,11 @@ impl Vm { // Removing whole mappings only does unmaps, so no preallocated VAs let mut ctx = Default::default(); - let mut inner = self.inner.exec_lock(Some(gem), false)?; + let inner = self.inner.exec_lock(Some(gem), false)?; - if let Some(bo) = inner.find_bo() { + if let Some(bo) = self.inner.find_bo(gem) { mod_dev_dbg!(inner.dev, "MMU: bo_unmap\n"); - inner.bo_unmap(&mut ctx, &bo)?; + self.inner.bo_unmap(&mut ctx, &bo)?; mod_dev_dbg!(inner.dev, "MMU: bo_unmap done\n"); // We need to drop the exec_lock first, then the GpuVmBo since that will take the lock itself. core::mem::drop(inner); @@ -1292,6 +1295,11 @@ impl Vm { pub(crate) fn is_extobj(&self, gem: &gem::Object) -> bool { self.inner.is_extobj(gem) } + + /// Check whether an object is external to this GpuVm + pub(crate) fn bo_deferred_cleanup(&self) { + self.inner.bo_deferred_cleanup() + } } impl Drop for VmInner { From a8000096874b8ff2e07c149718e339fe78067f75 Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Sat, 27 Dec 2025 11:42:53 +0100 Subject: [PATCH 1298/3902] fixup! drm: apple: Brightness control via atomic commits Signed-off-by: Janne Grunau --- drivers/gpu/drm/apple/Kconfig | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/gpu/drm/apple/Kconfig b/drivers/gpu/drm/apple/Kconfig index 9828a5fa193284..df247b6ed77deb 100644 --- a/drivers/gpu/drm/apple/Kconfig +++ b/drivers/gpu/drm/apple/Kconfig @@ -10,6 +10,7 @@ config DRM_APPLE select DRM_KMS_DMA_HELPER select DRM_GEM_DMA_HELPER select VIDEOMODE_HELPERS + select BACKLIGHT_CLASS_DEVICE select MULTIPLEXER help Say Y if you have an Apple Silicon chipset. From 488f839fbf54d7107301d2cd0d2868786dd7e7c8 Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Sat, 27 Dec 2025 14:58:47 +0100 Subject: [PATCH 1299/3902] drm: apple: Increase timeout for dcp_set_power_state_req to 5000ms ossibly helps with slow wakeup on dp-altmode. Signed-off-by: Janne Grunau --- drivers/gpu/drm/apple/iomfb_template.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/gpu/drm/apple/iomfb_template.c b/drivers/gpu/drm/apple/iomfb_template.c index 373d58f171240c..b1e0b1a77f23e4 100644 --- a/drivers/gpu/drm/apple/iomfb_template.c +++ b/drivers/gpu/drm/apple/iomfb_template.c @@ -825,7 +825,7 @@ void DCP_FW_NAME(iomfb_poweron)(struct apple_dcp *dcp) dcp_set_display_device(dcp, false, &handle, dcp_on_set_parameter, cookie); } - ret = wait_for_completion_timeout(&cookie->done, msecs_to_jiffies(500)); + ret = wait_for_completion_timeout(&cookie->done, msecs_to_jiffies(5000)); if (ret == 0) dev_warn(dcp->dev, "wait for power timed out\n"); From e20025c2c644ab658c4d143dcb11afa49754f940 Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Sat, 27 Dec 2025 15:00:14 +0100 Subject: [PATCH 1300/3902] drm: apple: Switch link status to BAD if power on fails Signed-off-by: Janne Grunau --- drivers/gpu/drm/apple/iomfb_template.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/drivers/gpu/drm/apple/iomfb_template.c b/drivers/gpu/drm/apple/iomfb_template.c index b1e0b1a77f23e4..8a20b1f9224838 100644 --- a/drivers/gpu/drm/apple/iomfb_template.c +++ b/drivers/gpu/drm/apple/iomfb_template.c @@ -829,6 +829,11 @@ void DCP_FW_NAME(iomfb_poweron)(struct apple_dcp *dcp) if (ret == 0) dev_warn(dcp->dev, "wait for power timed out\n"); + else if (ret > 0) + dev_info(dcp->dev, "dcp_set_power_state_req returned, %d ms remaining\n", jiffies_to_msecs(ret)); + if (ret <= 0) + drm_connector_set_link_status_property(&dcp->connector->base, + DRM_MODE_LINK_STATUS_BAD); kref_put(&cookie->refcount, release_wait_cookie);; From 18315ee30b08339c76059caf60638840c90993c4 Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Wed, 24 Dec 2025 13:12:11 +0100 Subject: [PATCH 1301/3902] drm: apple: Move plane bits out of apple_drv/iomfb_flush Now that dcp may use multiple planes with more complex paramters move this out of iomfb_flush and use the appropriate KMS atomic plane helper functions. Also move most plane handling functions from apple_drv.c to its own file. Signed-off-by: Janne Grunau --- drivers/gpu/drm/apple/Makefile | 1 + drivers/gpu/drm/apple/apple_drv.c | 170 +--------------- drivers/gpu/drm/apple/iomfb.c | 19 -- drivers/gpu/drm/apple/iomfb.h | 20 -- drivers/gpu/drm/apple/iomfb_internal.h | 2 - drivers/gpu/drm/apple/iomfb_plane.h | 64 ++++++ drivers/gpu/drm/apple/iomfb_template.c | 30 +-- drivers/gpu/drm/apple/iomfb_template.h | 29 +-- drivers/gpu/drm/apple/plane.c | 261 +++++++++++++++++++++++++ drivers/gpu/drm/apple/plane.h | 26 +++ 10 files changed, 357 insertions(+), 265 deletions(-) create mode 100644 drivers/gpu/drm/apple/iomfb_plane.h create mode 100644 drivers/gpu/drm/apple/plane.c create mode 100644 drivers/gpu/drm/apple/plane.h diff --git a/drivers/gpu/drm/apple/Makefile b/drivers/gpu/drm/apple/Makefile index 3d5faaeed7dbb6..2bb2fd107daed3 100644 --- a/drivers/gpu/drm/apple/Makefile +++ b/drivers/gpu/drm/apple/Makefile @@ -11,6 +11,7 @@ appledrm-y += ibootep.o appledrm-y += iomfb_v12_3.o appledrm-y += iomfb_v13_3.o appledrm-y += epic/dpavservep.o +appledrm-y += plane.o appledrm-$(CONFIG_TRACING) += trace.o diff --git a/drivers/gpu/drm/apple/apple_drv.c b/drivers/gpu/drm/apple/apple_drv.c index 6bb65833077954..9624d94611abae 100644 --- a/drivers/gpu/drm/apple/apple_drv.c +++ b/drivers/gpu/drm/apple/apple_drv.c @@ -26,7 +26,6 @@ #include #include #include -#include #include #include #include @@ -40,12 +39,11 @@ #include #include "dcp.h" +#include "plane.h" #define DRIVER_NAME "apple" #define DRIVER_DESC "Apple display controller DRM driver" -#define FRAC_16_16(mult, div) (((mult) << 16) / (div)) - #define MAX_COPROCESSORS 3 struct apple_drm_private { @@ -77,172 +75,6 @@ static const struct drm_driver apple_drm_driver = { .fops = &apple_fops, }; -static int apple_plane_atomic_check(struct drm_plane *plane, - struct drm_atomic_state *state) -{ - struct drm_plane_state *new_plane_state; - struct drm_crtc_state *crtc_state; - struct drm_rect *dst; - int ret; - - new_plane_state = drm_atomic_get_new_plane_state(state, plane); - - if (!new_plane_state->crtc) - return 0; - - crtc_state = drm_atomic_get_crtc_state(state, new_plane_state->crtc); - if (IS_ERR(crtc_state)) - return PTR_ERR(crtc_state); - - /* - * DCP limits downscaling to 2x and upscaling to 4x. Attempting to - * scale outside these bounds errors out when swapping. - * - * This function also takes care of clipping the src/dest rectangles, - * which is required for correct operation. Partially off-screen - * surfaces may appear corrupted. - * - * DCP does not distinguish plane types in the hardware, so we set - * can_position. If the primary plane does not fill the screen, the - * hardware will fill in zeroes (black). - */ - ret = drm_atomic_helper_check_plane_state(new_plane_state, crtc_state, - FRAC_16_16(1, 2), - FRAC_16_16(4, 1), - true, true); - if (ret < 0) - return ret; - - if (!new_plane_state->visible) - return 0; - - /* - * DCP does not allow a surface to clip off the screen, and will crash - * if any blended surface is smaller than 32x32. Reject the atomic op - * if the plane will crash DCP. - * - * This is most pertinent to cursors. Userspace should fall back to - * software cursors if the plane check is rejected. - */ - dst = &new_plane_state->dst; - if (drm_rect_width(dst) < 32 || drm_rect_height(dst) < 32) { - dev_err_once(state->dev->dev, - "Plane operation would have crashed DCP! Rejected!\n\ - DCP requires 32x32 of every plane to be within screen space.\n\ - Your compositor asked to overlay [%dx%d, %dx%d] on %dx%d.\n\ - This is not supported, and your compositor should have\n\ - switched to software compositing when this operation failed.\n\ - You should not have noticed this at all. If your screen\n\ - froze/hitched, or your compositor crashed, please report\n\ - this to the your compositor's developers. We will not\n\ - throw this error again until you next reboot.\n", - dst->x1, dst->y1, dst->x2, dst->y2, - crtc_state->mode.hdisplay, crtc_state->mode.vdisplay); - return -EINVAL; - } - - return 0; -} - -static void apple_plane_atomic_update(struct drm_plane *plane, - struct drm_atomic_state *state) -{ - /* Handled in atomic_flush */ -} - -static const struct drm_plane_helper_funcs apple_primary_plane_helper_funcs = { - .atomic_check = apple_plane_atomic_check, - .atomic_update = apple_plane_atomic_update, - .get_scanout_buffer = drm_fb_dma_get_scanout_buffer, -}; - -static const struct drm_plane_helper_funcs apple_plane_helper_funcs = { - .atomic_check = apple_plane_atomic_check, - .atomic_update = apple_plane_atomic_update, -}; - -static void apple_plane_cleanup(struct drm_plane *plane) -{ - drm_plane_cleanup(plane); - kfree(plane); -} - -static const struct drm_plane_funcs apple_plane_funcs = { - .update_plane = drm_atomic_helper_update_plane, - .disable_plane = drm_atomic_helper_disable_plane, - .destroy = apple_plane_cleanup, - .reset = drm_atomic_helper_plane_reset, - .atomic_duplicate_state = drm_atomic_helper_plane_duplicate_state, - .atomic_destroy_state = drm_atomic_helper_plane_destroy_state, -}; - -/* - * Table of supported formats, mapping from DRM fourccs to DCP fourccs. - * - * For future work, DCP supports more formats not listed, including YUV - * formats, an extra RGBA format, and a biplanar RGB10_A8 format (fourcc b3a8) - * used for HDR. - * - * Note: we don't have non-alpha formats but userspace breaks without XRGB. It - * doesn't matter for the primary plane, but cursors/overlays must not - * advertise formats without alpha. - */ -static const u32 dcp_primary_formats[] = { - DRM_FORMAT_XRGB2101010, - DRM_FORMAT_XRGB8888, - DRM_FORMAT_ARGB8888, - DRM_FORMAT_XBGR8888, - DRM_FORMAT_ABGR8888, -}; - -static const u32 dcp_overlay_formats[] = { - DRM_FORMAT_ARGB8888, - DRM_FORMAT_ABGR8888, -}; - -u64 apple_format_modifiers[] = { - DRM_FORMAT_MOD_LINEAR, - DRM_FORMAT_MOD_INVALID -}; - -static struct drm_plane *apple_plane_init(struct drm_device *dev, - unsigned long possible_crtcs, - enum drm_plane_type type) -{ - int ret; - struct drm_plane *plane; - - plane = kzalloc(sizeof(*plane), GFP_KERNEL); - - switch (type) { - case DRM_PLANE_TYPE_PRIMARY: - ret = drm_universal_plane_init(dev, plane, possible_crtcs, - &apple_plane_funcs, - dcp_primary_formats, ARRAY_SIZE(dcp_primary_formats), - apple_format_modifiers, type, NULL); - break; - case DRM_PLANE_TYPE_OVERLAY: - case DRM_PLANE_TYPE_CURSOR: - ret = drm_universal_plane_init(dev, plane, possible_crtcs, - &apple_plane_funcs, - dcp_overlay_formats, ARRAY_SIZE(dcp_overlay_formats), - apple_format_modifiers, type, NULL); - break; - default: - return NULL; - } - - if (ret) - return ERR_PTR(ret); - - if (type == DRM_PLANE_TYPE_PRIMARY) - drm_plane_helper_add(plane, &apple_primary_plane_helper_funcs); - else - drm_plane_helper_add(plane, &apple_plane_helper_funcs); - - return plane; -} - static enum drm_connector_status apple_connector_detect(struct drm_connector *connector, bool force) { diff --git a/drivers/gpu/drm/apple/iomfb.c b/drivers/gpu/drm/apple/iomfb.c index 3f82cdcdb0700e..ad94a953b99dd7 100644 --- a/drivers/gpu/drm/apple/iomfb.c +++ b/drivers/gpu/drm/apple/iomfb.c @@ -356,25 +356,6 @@ struct dcp_rect drm_to_dcp_rect(struct drm_rect *rect) .h = drm_rect_height(rect) }; } -u32 drm_format_to_dcp(u32 drm) -{ - switch (drm) { - case DRM_FORMAT_XRGB8888: - case DRM_FORMAT_ARGB8888: - return fourcc_code('A', 'R', 'G', 'B'); - - case DRM_FORMAT_XBGR8888: - case DRM_FORMAT_ABGR8888: - return fourcc_code('A', 'B', 'G', 'R'); - - case DRM_FORMAT_XRGB2101010: - return fourcc_code('r', '0', '3', 'w'); - } - - pr_warn("DRM format %X not supported in DCP\n", drm); - return 0; -} - int dcp_get_modes(struct drm_connector *connector) { struct apple_connector *apple_connector = to_apple_connector(connector); diff --git a/drivers/gpu/drm/apple/iomfb.h b/drivers/gpu/drm/apple/iomfb.h index c92d4c087168c1..d0874810dbdb95 100644 --- a/drivers/gpu/drm/apple/iomfb.h +++ b/drivers/gpu/drm/apple/iomfb.h @@ -79,7 +79,6 @@ enum iomfb_property_id { #define SWAP_SURFACES 4 /* We have 4 surfaces, but we can only ever blend two */ #define MAX_BLEND_SURFACES 2 -#define MAX_PLANES 3 enum dcp_colorspace { DCP_COLORSPACE_BG_SRGB = 0, @@ -113,25 +112,6 @@ struct dcp_rect { */ #define IOMFB_SET_BACKGROUND BIT(31) -/* Information describing a plane of a planar compressed surface */ -struct dcp_plane_info { - u32 width; - u32 height; - u32 base; - u32 offset; - u32 stride; - u32 size; - u16 tile_size; - u8 tile_w; - u8 tile_h; - u32 unk[13]; -} __packed; - -struct dcp_component_types { - u8 count; - u8 types[7]; -} __packed; - struct dcp_allocate_bandwidth_req { u64 unk1; u64 unk2; diff --git a/drivers/gpu/drm/apple/iomfb_internal.h b/drivers/gpu/drm/apple/iomfb_internal.h index 09f8857d30c341..9bd211f2f44e82 100644 --- a/drivers/gpu/drm/apple/iomfb_internal.h +++ b/drivers/gpu/drm/apple/iomfb_internal.h @@ -116,8 +116,6 @@ void dcp_ack(struct apple_dcp *dcp, enum dcp_context_id context); */ struct dcp_rect drm_to_dcp_rect(struct drm_rect *rect); -u32 drm_format_to_dcp(u32 drm); - /* The user may own drm_display_mode, so we need to search for our copy */ struct dcp_display_mode *lookup_mode(struct apple_dcp *dcp, const struct drm_display_mode *mode); diff --git a/drivers/gpu/drm/apple/iomfb_plane.h b/drivers/gpu/drm/apple/iomfb_plane.h new file mode 100644 index 00000000000000..e23fcffaddbf62 --- /dev/null +++ b/drivers/gpu/drm/apple/iomfb_plane.h @@ -0,0 +1,64 @@ +// SPDX-License-Identifier: GPL-2.0-only OR MIT +/* + * Copyright (C) The Asahi Linux Contributors + */ + + +#ifndef __APPLE_IOMFB_PLANE_H__ +#define __APPLE_IOMFB_PLANE_H__ + +#include + +#define DCP_SURF_MAX_PLANES 3 + +/* Information describing a plane of a planar compressed surface */ +struct dcp_plane_info { + u32 width; + u32 height; + u32 base; + u32 offset; + u32 stride; + u32 size; + u16 tile_size; + u8 tile_w; + u8 tile_h; + u32 unk[13]; +} __packed; + +struct dcp_component_types { + u8 count; + u8 types[7]; +} __packed; + +/* Information describing a surface */ +struct dcp_surface { + u8 is_tiled; + u8 is_tearing_allowed; + u8 is_premultiplied; + u32 plane_cnt; + u32 plane_cnt2; + u32 format; /* DCP fourcc */ + u32 ycbcr_matrix; + u8 xfer_func; + u8 colorspace; + u32 stride; + u16 pix_size; + u8 pel_w; + u8 pel_h; + u32 offset; + u32 width; + u32 height; + u32 buf_size; + u64 protection_opts; + u32 surface_id; + struct dcp_component_types comp_types[DCP_SURF_MAX_PLANES]; + u64 has_comp; + struct dcp_plane_info planes[DCP_SURF_MAX_PLANES]; + u64 has_planes; + u32 compression_info[DCP_SURF_MAX_PLANES][13]; + u64 has_compr_info; + u32 unk_num; + u32 unk_denom; +} __packed; + +#endif /* __APPLE_IOMFB_PLANE_H__ */ diff --git a/drivers/gpu/drm/apple/iomfb_template.c b/drivers/gpu/drm/apple/iomfb_template.c index 8a20b1f9224838..61a08ba9debe98 100644 --- a/drivers/gpu/drm/apple/iomfb_template.c +++ b/drivers/gpu/drm/apple/iomfb_template.c @@ -1305,10 +1305,10 @@ void DCP_FW_NAME(iomfb_flush)(struct apple_dcp *dcp, struct drm_crtc *crtc, stru } for_each_oldnew_plane_in_state(state, plane, old_state, new_state, plane_idx) { + struct apple_plane_state *apple_state = to_apple_plane_state(new_state); struct drm_framebuffer *fb = new_state->fb; struct drm_gem_dma_object *obj; struct drm_rect src_rect; - bool is_premultiplied = false; /* skip planes not for this crtc */ if (old_state->crtc != crtc && new_state->crtc != crtc) @@ -1357,15 +1357,6 @@ void DCP_FW_NAME(iomfb_flush)(struct apple_dcp *dcp, struct drm_crtc *crtc, stru req->surf_null[l] = false; has_surface = 1; - /* - * DCP doesn't support XBGR8 / XRGB8 natively. Blending as - * pre-multiplied alpha with a black background can be used as - * workaround for the bottommost plane. - */ - if (fb->format->format == DRM_FORMAT_XRGB8888 || - fb->format->format == DRM_FORMAT_XBGR8888) - is_premultiplied = true; - drm_rect_fp_to_int(&src_rect, &new_state->src); req->swap.src_rect[l] = drm_to_dcp_rect(&src_rect); @@ -1382,24 +1373,7 @@ void DCP_FW_NAME(iomfb_flush)(struct apple_dcp *dcp, struct drm_crtc *crtc, stru if (obj) req->surf_iova[l] = obj->dma_addr + fb->offsets[0]; - req->surf[l] = (struct DCP_FW_NAME(dcp_surface)){ - .is_premultiplied = is_premultiplied, - .format = drm_format_to_dcp(fb->format->format), - .xfer_func = DCP_XFER_FUNC_SDR, - .colorspace = DCP_COLORSPACE_NATIVE, - .stride = fb->pitches[0], - .width = fb->width, - .height = fb->height, - .buf_size = fb->height * fb->pitches[0], - .surface_id = req->swap.surf_ids[l], - - /* Only used for compressed or multiplanar surfaces */ - .pix_size = 1, - .pel_w = 1, - .pel_h = 1, - .has_comp = 1, - .has_planes = 1, - }; + req->surf[l].base = apple_state->surf; } diff --git a/drivers/gpu/drm/apple/iomfb_template.h b/drivers/gpu/drm/apple/iomfb_template.h index 878ffdf9e644cd..7723a427bb5149 100644 --- a/drivers/gpu/drm/apple/iomfb_template.h +++ b/drivers/gpu/drm/apple/iomfb_template.h @@ -13,6 +13,7 @@ #include #include "iomfb.h" +#include "plane.h" #include "version_utils.h" struct DCP_FW_NAME(dcp_swap) { @@ -61,33 +62,7 @@ struct DCP_FW_NAME(dcp_swap) { /* Information describing a surface */ struct DCP_FW_NAME(dcp_surface) { - u8 is_tiled; - u8 is_tearing_allowed; - u8 is_premultiplied; - u32 plane_cnt; - u32 plane_cnt2; - u32 format; /* DCP fourcc */ - u32 ycbcr_matrix; - u8 xfer_func; - u8 colorspace; - u32 stride; - u16 pix_size; - u8 pel_w; - u8 pel_h; - u32 offset; - u32 width; - u32 height; - u32 buf_size; - u64 protection_opts; - u32 surface_id; - struct dcp_component_types comp_types[MAX_PLANES]; - u64 has_comp; - struct dcp_plane_info planes[MAX_PLANES]; - u64 has_planes; - u32 compression_info[MAX_PLANES][13]; - u64 has_compr_info; - u32 unk_num; - u32 unk_denom; + struct dcp_surface base; #if DCP_FW_VER < DCP_FW_VERSION(13, 2, 0) u8 padding[7]; #else diff --git a/drivers/gpu/drm/apple/plane.c b/drivers/gpu/drm/apple/plane.c new file mode 100644 index 00000000000000..c43c9e594a20d6 --- /dev/null +++ b/drivers/gpu/drm/apple/plane.c @@ -0,0 +1,261 @@ +// SPDX-License-Identifier: GPL-2.0-only OR MIT +/* + * Copyright (C) The Asahi Linux Contributors + */ + +#include "plane.h" + +#include "iomfb_internal.h" + +#include +#include +#include +#include +#include +#include + +#define FRAC_16_16(mult, div) (((mult) << 16) / (div)) + +static int apple_plane_atomic_check(struct drm_plane *plane, + struct drm_atomic_state *state) +{ + struct drm_plane_state *new_plane_state; + struct drm_crtc_state *crtc_state; + struct drm_rect *dst; + int ret; + + new_plane_state = drm_atomic_get_new_plane_state(state, plane); + + if (!new_plane_state->crtc) + return 0; + + crtc_state = drm_atomic_get_crtc_state(state, new_plane_state->crtc); + if (IS_ERR(crtc_state)) + return PTR_ERR(crtc_state); + + /* + * DCP limits downscaling to 2x and upscaling to 4x. Attempting to + * scale outside these bounds errors out when swapping. + * + * This function also takes care of clipping the src/dest rectangles, + * which is required for correct operation. Partially off-screen + * surfaces may appear corrupted. + * + * DCP does not distinguish plane types in the hardware, so we set + * can_position. If the primary plane does not fill the screen, the + * hardware will fill in zeroes (black). + */ + ret = drm_atomic_helper_check_plane_state(new_plane_state, crtc_state, + FRAC_16_16(1, 2), + FRAC_16_16(4, 1), + true, true); + if (ret < 0) + return ret; + + if (!new_plane_state->visible) + return 0; + + /* + * DCP does not allow a surface to clip off the screen, and will crash + * if any blended surface is smaller than 32x32. Reject the atomic op + * if the plane will crash DCP. + * + * This is most pertinent to cursors. Userspace should fall back to + * software cursors if the plane check is rejected. + */ + dst = &new_plane_state->dst; + if (drm_rect_width(dst) < 32 || drm_rect_height(dst) < 32) { + dev_err_once(state->dev->dev, + "Plane operation would have crashed DCP! Rejected!\n\ + DCP requires 32x32 of every plane to be within screen space.\n\ + Your compositor asked to overlay [%dx%d, %dx%d] on %dx%d.\n\ + This is not supported, and your compositor should have\n\ + switched to software compositing when this operation failed.\n\ + You should not have noticed this at all. If your screen\n\ + froze/hitched, or your compositor crashed, please report\n\ + this to the your compositor's developers. We will not\n\ + throw this error again until you next reboot.\n", + dst->x1, dst->y1, dst->x2, dst->y2, + crtc_state->mode.hdisplay, crtc_state->mode.vdisplay); + return -EINVAL; + } + + return 0; +} + +static u32 drm_format_to_dcp(u32 drm) +{ + switch (drm) { + case DRM_FORMAT_XRGB8888: + case DRM_FORMAT_ARGB8888: + return fourcc_code('A', 'R', 'G', 'B'); + + case DRM_FORMAT_XBGR8888: + case DRM_FORMAT_ABGR8888: + return fourcc_code('A', 'B', 'G', 'R'); + + case DRM_FORMAT_XRGB2101010: + return fourcc_code('r', '0', '3', 'w'); + } + + pr_warn("DRM format %X not supported in DCP\n", drm); + return 0; +} + +static void apple_plane_atomic_update(struct drm_plane *plane, + struct drm_atomic_state *state) +{ + struct drm_plane_state *base = drm_atomic_get_new_plane_state(state, plane); + struct apple_plane_state *new_state; + bool is_premultiplied = false; + + if (!base) + return; + + new_state = to_apple_plane_state(base); + + if (!base->fb) { + memset(&new_state->surf, 0, sizeof(new_state->surf)); + return; + } + + struct drm_framebuffer *fb = base->fb; + /* + * DCP doesn't support XBGR8 / XRGB8 natively. Blending as + * pre-multiplied alpha with a black background can be used as + * workaround for the bottommost plane. + */ + if (fb->format->format == DRM_FORMAT_XRGB8888 || + fb->format->format == DRM_FORMAT_XBGR8888) + is_premultiplied = true; + + new_state->surf = (struct dcp_surface){ + .is_premultiplied = is_premultiplied, + .format = drm_format_to_dcp(fb->format->format), + .xfer_func = DCP_XFER_FUNC_SDR, + .colorspace = DCP_COLORSPACE_NATIVE, + .stride = fb->pitches[0], + .width = fb->width, + .height = fb->height, + .buf_size = fb->height * fb->pitches[0], + // .surface_id = req->swap.surf_ids[l], + + /* Only used for compressed or multiplanar surfaces */ + .pix_size = 1, + .pel_w = 1, + .pel_h = 1, + .has_comp = 1, + .has_planes = 1, + }; +} + +static const struct drm_plane_helper_funcs apple_primary_plane_helper_funcs = { + .atomic_check = apple_plane_atomic_check, + .atomic_update = apple_plane_atomic_update, + .get_scanout_buffer = drm_fb_dma_get_scanout_buffer, +}; + +static const struct drm_plane_helper_funcs apple_plane_helper_funcs = { + .atomic_check = apple_plane_atomic_check, + .atomic_update = apple_plane_atomic_update, +}; + +static struct drm_plane_state * +apple_plane_duplicate_state(struct drm_plane *plane) +{ + struct apple_plane_state *apple_plane_state, *old_apple_plane_state; + + old_apple_plane_state = to_apple_plane_state(plane->state); + apple_plane_state = kzalloc(sizeof(*apple_plane_state), GFP_KERNEL); + if (!apple_plane_state) + return NULL; + + __drm_atomic_helper_plane_duplicate_state(plane, &apple_plane_state->base); + + apple_plane_state->surf = old_apple_plane_state->surf; + + return &apple_plane_state->base; +} + +// void apple_plane_destroy_state(struct drm_plane *plane, +// struct drm_plane_state *state) +// { +// drm_atomic_helper_plane_destroy_state(plane, state); +// } + +static const struct drm_plane_funcs apple_plane_funcs = { + .update_plane = drm_atomic_helper_update_plane, + .disable_plane = drm_atomic_helper_disable_plane, + .reset = drm_atomic_helper_plane_reset, + .atomic_duplicate_state = apple_plane_duplicate_state, + // .atomic_destroy_state = apple_plane_destroy_state, + .atomic_destroy_state = drm_atomic_helper_plane_destroy_state, +}; + +/* + * Table of supported formats, mapping from DRM fourccs to DCP fourccs. + * + * For future work, DCP supports more formats not listed, including YUV + * formats, an extra RGBA format, and a biplanar RGB10_A8 format (fourcc b3a8) + * used for HDR. + * + * Note: we don't have non-alpha formats but userspace breaks without XRGB. It + * doesn't matter for the primary plane, but cursors/overlays must not + * advertise formats without alpha. + */ +static const u32 dcp_primary_formats[] = { + DRM_FORMAT_XRGB2101010, + DRM_FORMAT_XRGB8888, + DRM_FORMAT_ARGB8888, + DRM_FORMAT_XBGR8888, + DRM_FORMAT_ABGR8888, +}; + +static const u32 dcp_overlay_formats[] = { + DRM_FORMAT_ARGB8888, + DRM_FORMAT_ABGR8888, +}; + +u64 apple_format_modifiers[] = { + DRM_FORMAT_MOD_LINEAR, + DRM_FORMAT_MOD_INVALID +}; + +struct apple_plane { + struct drm_plane base; +}; + +struct drm_plane *apple_plane_init(struct drm_device *dev, + unsigned long possible_crtcs, + enum drm_plane_type type) +{ + struct apple_plane *plane; + + switch (type) { + case DRM_PLANE_TYPE_PRIMARY: + plane = drmm_universal_plane_alloc(dev, struct apple_plane, base, possible_crtcs, + &apple_plane_funcs, + dcp_primary_formats, ARRAY_SIZE(dcp_primary_formats), + apple_format_modifiers, type, NULL); + break; + case DRM_PLANE_TYPE_OVERLAY: + case DRM_PLANE_TYPE_CURSOR: + plane = drmm_universal_plane_alloc(dev, struct apple_plane, base, possible_crtcs, + &apple_plane_funcs, + dcp_overlay_formats, ARRAY_SIZE(dcp_overlay_formats), + apple_format_modifiers, type, NULL); + break; + default: + return ERR_PTR(-EINVAL); + } + + if (IS_ERR(plane)) + return ERR_PTR(PTR_ERR(plane)); + + if (type == DRM_PLANE_TYPE_PRIMARY) + drm_plane_helper_add(&plane->base, &apple_primary_plane_helper_funcs); + else + drm_plane_helper_add(&plane->base, &apple_plane_helper_funcs); + + return &plane->base; +} diff --git a/drivers/gpu/drm/apple/plane.h b/drivers/gpu/drm/apple/plane.h new file mode 100644 index 00000000000000..e32c1e609e6704 --- /dev/null +++ b/drivers/gpu/drm/apple/plane.h @@ -0,0 +1,26 @@ +// SPDX-License-Identifier: GPL-2.0-only OR MIT +/* + * Copyright (C) The Asahi Linux Contributors + */ + +#ifndef __APPLE_PLANE_H__ +#define __APPLE_PLANE_H__ + +#include + +#include + +#include "iomfb_plane.h" + +struct apple_plane_state { + struct drm_plane_state base; + struct dcp_surface surf; +}; + +#define to_apple_plane_state(x) container_of(x, struct apple_plane_state, base) + +struct drm_plane *apple_plane_init(struct drm_device *dev, + unsigned long possible_crtcs, + enum drm_plane_type type); + +#endif /* __APPLE_PLANE_H__ */ From a474ef8e48a5d1ec994abf5f899d0426f818fb7d Mon Sep 17 00:00:00 2001 From: James Calligeros Date: Fri, 26 Dec 2025 21:54:13 +1000 Subject: [PATCH 1302/3902] drm: apple: move dcp rectangle creation to atomic_plane_update We should not be programming rectangles in atomic_flush. Move this step to atomic_plane_update and store the resultant rectangles with the rest of the surface's state. Signed-off-by: James Calligeros Signed-off-by: Janne Grunau --- drivers/gpu/drm/apple/iomfb.c | 13 ------------- drivers/gpu/drm/apple/iomfb.h | 7 ------- drivers/gpu/drm/apple/iomfb_internal.h | 7 ------- drivers/gpu/drm/apple/iomfb_plane.h | 7 +++++++ drivers/gpu/drm/apple/iomfb_template.c | 7 ++----- drivers/gpu/drm/apple/iomfb_template.h | 1 + drivers/gpu/drm/apple/plane.c | 24 ++++++++++++++++++++++++ drivers/gpu/drm/apple/plane.h | 2 ++ 8 files changed, 36 insertions(+), 32 deletions(-) diff --git a/drivers/gpu/drm/apple/iomfb.c b/drivers/gpu/drm/apple/iomfb.c index ad94a953b99dd7..421ff925e0ecb2 100644 --- a/drivers/gpu/drm/apple/iomfb.c +++ b/drivers/gpu/drm/apple/iomfb.c @@ -343,19 +343,6 @@ static void dcpep_got_msg(struct apple_dcp *dcp, u64 message) dcpep_handle_cb(dcp, ctx_id, data, length, offset); } -/* - * DRM specifies rectangles as start and end coordinates. DCP specifies - * rectangles as a start coordinate and a width/height. Convert a DRM rectangle - * to a DCP rectangle. - */ -struct dcp_rect drm_to_dcp_rect(struct drm_rect *rect) -{ - return (struct dcp_rect){ .x = rect->x1, - .y = rect->y1, - .w = drm_rect_width(rect), - .h = drm_rect_height(rect) }; -} - int dcp_get_modes(struct drm_connector *connector) { struct apple_connector *apple_connector = to_apple_connector(connector); diff --git a/drivers/gpu/drm/apple/iomfb.h b/drivers/gpu/drm/apple/iomfb.h index d0874810dbdb95..cc75129f2a9b42 100644 --- a/drivers/gpu/drm/apple/iomfb.h +++ b/drivers/gpu/drm/apple/iomfb.h @@ -100,13 +100,6 @@ struct dcp_iouserclient { u8 padding[2]; } __packed; -struct dcp_rect { - u32 x; - u32 y; - u32 w; - u32 h; -} __packed; - /* * Update background color to struct dcp_swap.bg_color */ diff --git a/drivers/gpu/drm/apple/iomfb_internal.h b/drivers/gpu/drm/apple/iomfb_internal.h index 9bd211f2f44e82..75e9d7b0e8cc84 100644 --- a/drivers/gpu/drm/apple/iomfb_internal.h +++ b/drivers/gpu/drm/apple/iomfb_internal.h @@ -109,13 +109,6 @@ int dcp_parse_tag(char tag[4]); void dcp_ack(struct apple_dcp *dcp, enum dcp_context_id context); -/* - * DRM specifies rectangles as start and end coordinates. DCP specifies - * rectangles as a start coordinate and a width/height. Convert a DRM rectangle - * to a DCP rectangle. - */ -struct dcp_rect drm_to_dcp_rect(struct drm_rect *rect); - /* The user may own drm_display_mode, so we need to search for our copy */ struct dcp_display_mode *lookup_mode(struct apple_dcp *dcp, const struct drm_display_mode *mode); diff --git a/drivers/gpu/drm/apple/iomfb_plane.h b/drivers/gpu/drm/apple/iomfb_plane.h index e23fcffaddbf62..9de35d64c12ba4 100644 --- a/drivers/gpu/drm/apple/iomfb_plane.h +++ b/drivers/gpu/drm/apple/iomfb_plane.h @@ -11,6 +11,13 @@ #define DCP_SURF_MAX_PLANES 3 +struct dcp_rect { + u32 x; + u32 y; + u32 w; + u32 h; +} __packed; + /* Information describing a plane of a planar compressed surface */ struct dcp_plane_info { u32 width; diff --git a/drivers/gpu/drm/apple/iomfb_template.c b/drivers/gpu/drm/apple/iomfb_template.c index 61a08ba9debe98..0dcefedddcd283 100644 --- a/drivers/gpu/drm/apple/iomfb_template.c +++ b/drivers/gpu/drm/apple/iomfb_template.c @@ -1308,7 +1308,6 @@ void DCP_FW_NAME(iomfb_flush)(struct apple_dcp *dcp, struct drm_crtc *crtc, stru struct apple_plane_state *apple_state = to_apple_plane_state(new_state); struct drm_framebuffer *fb = new_state->fb; struct drm_gem_dma_object *obj; - struct drm_rect src_rect; /* skip planes not for this crtc */ if (old_state->crtc != crtc && new_state->crtc != crtc) @@ -1357,10 +1356,8 @@ void DCP_FW_NAME(iomfb_flush)(struct apple_dcp *dcp, struct drm_crtc *crtc, stru req->surf_null[l] = false; has_surface = 1; - drm_rect_fp_to_int(&src_rect, &new_state->src); - - req->swap.src_rect[l] = drm_to_dcp_rect(&src_rect); - req->swap.dst_rect[l] = drm_to_dcp_rect(&new_state->dst); + req->swap.src_rect[l] = apple_state->src_rect; + req->swap.dst_rect[l] = apple_state->dst_rect; if (dcp->notch_height > 0) req->swap.dst_rect[l].y += dcp->notch_height; diff --git a/drivers/gpu/drm/apple/iomfb_template.h b/drivers/gpu/drm/apple/iomfb_template.h index 7723a427bb5149..c208e1e8e6c1f9 100644 --- a/drivers/gpu/drm/apple/iomfb_template.h +++ b/drivers/gpu/drm/apple/iomfb_template.h @@ -13,6 +13,7 @@ #include #include "iomfb.h" +#include "iomfb_plane.h" #include "plane.h" #include "version_utils.h" diff --git a/drivers/gpu/drm/apple/plane.c b/drivers/gpu/drm/apple/plane.c index c43c9e594a20d6..0f83f7c3fb907b 100644 --- a/drivers/gpu/drm/apple/plane.c +++ b/drivers/gpu/drm/apple/plane.c @@ -83,6 +83,27 @@ static int apple_plane_atomic_check(struct drm_plane *plane, return 0; } +/* + * DRM specifies rectangles as start and end coordinates. DCP specifies + * rectangles as a start coordinate and a width/height. Convert a DRM rectangle + * to a DCP rectangle. + */ +static struct dcp_rect drm_to_dcp_rect(const struct drm_rect *rect) +{ + return (struct dcp_rect){ .x = rect->x1, + .y = rect->y1, + .w = drm_rect_width(rect), + .h = drm_rect_height(rect), + }; +} + +static struct dcp_rect drm_to_dcp_rect_fp(const struct drm_rect *fp_rect) +{ + struct drm_rect rect; + drm_rect_fp_to_int(&rect, fp_rect); + return drm_to_dcp_rect(&rect); +} + static u32 drm_format_to_dcp(u32 drm) { switch (drm) { @@ -129,6 +150,9 @@ static void apple_plane_atomic_update(struct drm_plane *plane, fb->format->format == DRM_FORMAT_XBGR8888) is_premultiplied = true; + new_state->src_rect = drm_to_dcp_rect_fp(&base->src); + new_state->dst_rect = drm_to_dcp_rect(&base->dst); + new_state->surf = (struct dcp_surface){ .is_premultiplied = is_premultiplied, .format = drm_format_to_dcp(fb->format->format), diff --git a/drivers/gpu/drm/apple/plane.h b/drivers/gpu/drm/apple/plane.h index e32c1e609e6704..96461d7da59648 100644 --- a/drivers/gpu/drm/apple/plane.h +++ b/drivers/gpu/drm/apple/plane.h @@ -15,6 +15,8 @@ struct apple_plane_state { struct drm_plane_state base; struct dcp_surface surf; + struct dcp_rect src_rect; + struct dcp_rect dst_rect; }; #define to_apple_plane_state(x) container_of(x, struct apple_plane_state, base) From 0cb70411c2a050dd72ba5eb14df86e1966e82126 Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Thu, 1 Jan 2026 13:53:50 +0100 Subject: [PATCH 1303/3902] drm: apple: Use defines for dcp's fourcc formats Add defines for Apple's full gamut packed 10-bit ARGB format ("l10r") and 2 and 3 plane YCbCr 8-bit formats with 4:2:0, 4:2:2 and 4:4:4 subsampling in limited and full range. Signed-off-by: Janne Grunau --- drivers/gpu/drm/apple/iomfb_plane.h | 10 ++++++++++ drivers/gpu/drm/apple/plane.c | 6 +++--- 2 files changed, 13 insertions(+), 3 deletions(-) diff --git a/drivers/gpu/drm/apple/iomfb_plane.h b/drivers/gpu/drm/apple/iomfb_plane.h index 9de35d64c12ba4..adf6916d375891 100644 --- a/drivers/gpu/drm/apple/iomfb_plane.h +++ b/drivers/gpu/drm/apple/iomfb_plane.h @@ -7,10 +7,20 @@ #ifndef __APPLE_IOMFB_PLANE_H__ #define __APPLE_IOMFB_PLANE_H__ +#include + #include #define DCP_SURF_MAX_PLANES 3 +#define DCP_FORMAT_BGRA fourcc_code('A', 'R', 'G', 'B') +#define DCP_FORMAT_RGBA fourcc_code('A', 'B', 'G', 'R') + +#define DCP_FORMAT_W30R fourcc_code('r', '0', '3', 'w') // wide gamut packed 10-bit RGB without alpha +#define DCP_FORMAT_L10R fourcc_code('r', '0', '1', 'l') // full range packed 10-bit RGB with alpha + + + struct dcp_rect { u32 x; u32 y; diff --git a/drivers/gpu/drm/apple/plane.c b/drivers/gpu/drm/apple/plane.c index 0f83f7c3fb907b..5f4b61dbb20181 100644 --- a/drivers/gpu/drm/apple/plane.c +++ b/drivers/gpu/drm/apple/plane.c @@ -109,14 +109,14 @@ static u32 drm_format_to_dcp(u32 drm) switch (drm) { case DRM_FORMAT_XRGB8888: case DRM_FORMAT_ARGB8888: - return fourcc_code('A', 'R', 'G', 'B'); + return DCP_FORMAT_BGRA; case DRM_FORMAT_XBGR8888: case DRM_FORMAT_ABGR8888: - return fourcc_code('A', 'B', 'G', 'R'); + return DCP_FORMAT_RGBA; case DRM_FORMAT_XRGB2101010: - return fourcc_code('r', '0', '3', 'w'); + return DCP_FORMAT_W30R; } pr_warn("DRM format %X not supported in DCP\n", drm); From 32cc1159e9816953d30e7e86ecd97185ff8d1ab2 Mon Sep 17 00:00:00 2001 From: James Calligeros Date: Fri, 26 Dec 2025 22:00:26 +1000 Subject: [PATCH 1304/3902] drm: apple: get framebuffer iova in atomic_plane_update Signed-off-by: James Calligeros Signed-off-by: Janne Grunau --- drivers/gpu/drm/apple/iomfb_template.c | 14 ++------------ drivers/gpu/drm/apple/plane.c | 10 ++++++++++ drivers/gpu/drm/apple/plane.h | 1 + 3 files changed, 13 insertions(+), 12 deletions(-) diff --git a/drivers/gpu/drm/apple/iomfb_template.c b/drivers/gpu/drm/apple/iomfb_template.c index 0dcefedddcd283..c5f6765733d4bb 100644 --- a/drivers/gpu/drm/apple/iomfb_template.c +++ b/drivers/gpu/drm/apple/iomfb_template.c @@ -20,7 +20,6 @@ #include #include #include -#include #include #include @@ -1306,8 +1305,6 @@ void DCP_FW_NAME(iomfb_flush)(struct apple_dcp *dcp, struct drm_crtc *crtc, stru for_each_oldnew_plane_in_state(state, plane, old_state, new_state, plane_idx) { struct apple_plane_state *apple_state = to_apple_plane_state(new_state); - struct drm_framebuffer *fb = new_state->fb; - struct drm_gem_dma_object *obj; /* skip planes not for this crtc */ if (old_state->crtc != crtc && new_state->crtc != crtc) @@ -1329,7 +1326,7 @@ void DCP_FW_NAME(iomfb_flush)(struct apple_dcp *dcp, struct drm_crtc *crtc, stru req->swap.swap_enabled |= BIT(l); - if (old_state->fb && fb != old_state->fb) { + if (old_state->fb && new_state->fb != old_state->fb) { /* * Race condition between a framebuffer unbind getting * swapped out and GEM unreferencing a framebuffer. If @@ -1362,14 +1359,7 @@ void DCP_FW_NAME(iomfb_flush)(struct apple_dcp *dcp, struct drm_crtc *crtc, stru if (dcp->notch_height > 0) req->swap.dst_rect[l].y += dcp->notch_height; - /* the obvious helper call drm_fb_dma_get_gem_addr() adjusts - * the address for source x/y offsets. Since IOMFB has a direct - * support source position prefer that. - */ - obj = drm_fb_dma_get_gem_obj(fb, 0); - if (obj) - req->surf_iova[l] = obj->dma_addr + fb->offsets[0]; - + req->surf_iova[l] = apple_state->iova; req->surf[l].base = apple_state->surf; } diff --git a/drivers/gpu/drm/apple/plane.c b/drivers/gpu/drm/apple/plane.c index 5f4b61dbb20181..c37e0de550e14e 100644 --- a/drivers/gpu/drm/apple/plane.c +++ b/drivers/gpu/drm/apple/plane.c @@ -12,6 +12,7 @@ #include #include #include +#include #include #define FRAC_16_16(mult, div) (((mult) << 16) / (div)) @@ -128,6 +129,7 @@ static void apple_plane_atomic_update(struct drm_plane *plane, { struct drm_plane_state *base = drm_atomic_get_new_plane_state(state, plane); struct apple_plane_state *new_state; + struct drm_gem_dma_object *obj; bool is_premultiplied = false; if (!base) @@ -171,6 +173,14 @@ static void apple_plane_atomic_update(struct drm_plane *plane, .has_comp = 1, .has_planes = 1, }; + + /* the obvious helper call drm_fb_dma_get_gem_addr() adjusts + * the address for source x/y offsets. Since IOMFB has a direct + * support source position prefer that. + */ + obj = drm_fb_dma_get_gem_obj(base->fb, 0); + if (obj) + new_state->iova = obj->dma_addr + base->fb->offsets[0]; } static const struct drm_plane_helper_funcs apple_primary_plane_helper_funcs = { diff --git a/drivers/gpu/drm/apple/plane.h b/drivers/gpu/drm/apple/plane.h index 96461d7da59648..b03c3fdfed7dec 100644 --- a/drivers/gpu/drm/apple/plane.h +++ b/drivers/gpu/drm/apple/plane.h @@ -17,6 +17,7 @@ struct apple_plane_state { struct dcp_surface surf; struct dcp_rect src_rect; struct dcp_rect dst_rect; + u64 iova; }; #define to_apple_plane_state(x) container_of(x, struct apple_plane_state, base) From 69cabf38f63a38d4a5de1b293983d1549f156bca Mon Sep 17 00:00:00 2001 From: Michael Date: Mon, 29 Dec 2025 22:51:17 +1100 Subject: [PATCH 1305/3902] power: supply: macsmc: Add M3 generation power events The Apple M3 machines, and potentially M1/M2 machines with updated SMC firmware generate a new set of SMC events starting with 0x7113 when cables are plugged or unplugged. Without this patch, the kernel logs "Unknown charger event" errors, and the power status may not update immediately. The event structure is 0x7113[Port][Status]. Observed on M3: - Port 0 (USB-C): 0x711300xx - Port 1 (USB-C): 0x711301xx - Port 2 (MagSafe): 0x711302xx - Disconnect: 0x7113ffxx Status 0x04 indicates a stable connection, while 0x02/0x03 appear during negotiation. This patch handles these events and triggers a power_supply_changed notification. Signed-off-by: Michael Reeves --- drivers/power/supply/macsmc-power.c | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/drivers/power/supply/macsmc-power.c b/drivers/power/supply/macsmc-power.c index 0948ede776cf70..93f4fef365eaef 100644 --- a/drivers/power/supply/macsmc-power.c +++ b/drivers/power/supply/macsmc-power.c @@ -816,6 +816,21 @@ static int macsmc_power_event(struct notifier_block *nb, unsigned long event, vo power_supply_changed(power->batt); power_supply_changed(power->ac); + return NOTIFY_OK; + } else if ((event & 0xffff0000) == 0x71130000) { + u8 port_index = (event >> 8) & 0xff; + u8 status = event & 0xff; + + if (port_index == 0xff) + dev_info(power->dev, "Connector event: Disconnect (status 0x%02x)\n", + status); + else + dev_info(power->dev, "Connector event: Port %d (status 0x%02x)\n", + port_index + 1, status); + + power_supply_changed(power->batt); + power_supply_changed(power->ac); + return NOTIFY_OK; } else if ((event & 0xff000000) == 0x71000000) { dev_info(power->dev, "Unknown charger event 0x%lx\n", event); From 0475333af1c8f67c53bd9151f418fe012c6cc421 Mon Sep 17 00:00:00 2001 From: Sasha Finkelstein Date: Thu, 1 Jan 2026 11:42:32 +0100 Subject: [PATCH 1306/3902] rust: drm: gpuvm: Add a missing lock Lockdep complains otherwise: ------------[ cut here ]------------ WARNING: CPU: 5 PID: 885 at drivers/gpu/drm/drm_gpuvm.c:1620 drm_gpuvm_bo_put+0x1b4/0x254 Modules linked in: brcmfmac_wcc uhid overlay squashfs zlib_inflate brcmfmac hci_bcm4377 brcmutil spi_nor aop_las aop_als industrialio cfg80211 fuse nfn> CPU: 5 UID: 1000 PID: 885 Comm: kwin_wayland Tainted: G S W 6.18.2+ #5 PREEMPTLAZY Tainted: [S]=CPU_OUT_OF_SPEC, [W]=WARN Hardware name: Apple MacBook Pro (14-inch, M1 Pro, 2021) (DT) pstate: 61400009 (nZCv daif +PAN -UAO -TCO +DIT -SSBS BTYPE=--) pc : drm_gpuvm_bo_put+0x1b4/0x254 lr : drm_gpuvm_bo_put+0x14c/0x254 sp : ffff8000893d7c10 x29: ffff8000893d7c10 x28: ffff00001b8ef9c0 x27: 0000000000000000 x26: 0000000000000002 x25: ffff800081451000 x24: dead000000000100 x23: ffff800080ee82d0 x22: ffff0000108f9d50 x21: ffff0000108f9c00 x20: ffff0000492e0700 x19: ffff000048700000 x18: 0000000000000000 x17: 0000000000000000 x16: 0000000000000000 x15: 0000000000000000 x14: 0000000000000008 x13: 0000000000000000 x12: ffff8000815e34d0 x11: 0000000000000001 x10: 00000000ffffffff x9 : 0000000100000000 x8 : 0000000000000000 x7 : 0000000000000000 x6 : ffff8000807d2bcc x5 : 0000000000000000 x4 : 0000000000000000 x3 : 0000000000000000 x2 : 0000000000000000 x1 : ffff000048700258 x0 : 0000000000000000 Call trace: drm_gpuvm_bo_put+0x1b4/0x254 (P) _RINvNtCs1tcwcP3FgYC_4core3ptr13drop_in_placeINtNtNtCsgIauPoi8ikU_6kernel3drm2mm8NodeDatauNtNtCshTJcMxhWd7O_5asahi3mmu18KernelMappingInnerEEB1t_+0xb0/> _RINvNtCs1tcwcP3FgYC_4core3ptr13drop_in_placeNtNtCshTJcMxhWd7O_5asahi4file2VmEBK_+0xcc/0xf0 _RNvMNtNtCsgIauPoi8ikU_6kernel3drm4fileINtB2_4FileNtNtCshTJcMxhWd7O_5asahi4file4FileE18postclose_callbackBP_+0xac/0x2f4 drm_file_free+0x1b8/0x210 drm_release+0xb8/0x140 __fput+0xf8/0x2e4 fput_close_sync+0x44/0x114 __arm64_sys_close+0xb0/0xfc invoke_syscall+0x48/0xc8 do_el0_svc+0x7c/0xa8 el0_svc+0x3c/0xd8 el0t_64_sync_handler+0x68/0xdc el0t_64_sync+0x198/0x19c ---[ end trace 0000000000000000 ]--- Signed-off-by: Sasha Finkelstein --- rust/kernel/drm/gpuvm.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/rust/kernel/drm/gpuvm.rs b/rust/kernel/drm/gpuvm.rs index e7b073cffe0a58..29b0aa42f361e4 100644 --- a/rust/kernel/drm/gpuvm.rs +++ b/rust/kernel/drm/gpuvm.rs @@ -337,7 +337,9 @@ unsafe impl AlwaysRefCounted for GpuVmBo { unsafe { let resv = (*obj.as_mut().bo.obj).resv; bindings::dma_resv_lock(resv, core::ptr::null_mut()); + obj.as_ref().lock_gpuva(); bindings::drm_gpuvm_bo_put(&mut obj.as_mut().bo); + obj.as_ref().unlock_gpuva(); bindings::dma_resv_unlock(resv); } } From 815abd1709666f2807ee2651a814c883120c04ba Mon Sep 17 00:00:00 2001 From: Filipe Manana Date: Wed, 3 Dec 2025 17:02:00 +0000 Subject: [PATCH 1307/3902] btrfs: do not skip logging new dentries when logging a new name [ Upstream commit 5630f7557de61264ccb4f031d4734a1a97eaed16 ] When we are logging a directory and the log context indicates that we are logging a new name for some other file (that is or was inside that directory), we skip logging the inodes for new dentries in the directory. This is ok most of the time, but if after the rename or link operation that triggered the logging of that directory, we have an explicit fsync of that directory without the directory inode being evicted and reloaded, we end up never logging the inodes for the new dentries that we found during the new name logging, as the next directory fsync will only process dentries that were added after the last time we logged the directory (we are doing an incremental directory logging). So make sure we always log new dentries for a directory even if we are in a context of logging a new name. We started skipping logging inodes for new dentries as of commit c48792c6ee7a ("btrfs: do not log new dentries when logging that a new name exists") and it was fine back then, because when logging a directory we always iterated over all the directory entries (for leaves changed in the current transaction) so a subsequent fsync would always log anything that was previously skipped while logging a directory when logging a new name (with btrfs_log_new_name()). But later support for incrementally logging a directory was added in commit dc2872247ec0 ("btrfs: keep track of the last logged keys when logging a directory"), to avoid checking all dir items every time we log a directory, so the check to skip dentry logging added in the first commit should have been removed when the incremental support for logging a directory was added. A test case for fstests will follow soon. Reported-by: Vyacheslav Kovalevsky Link: https://lore.kernel.org/linux-btrfs/84c4e713-85d6-42b9-8dcf-0722ed26cb05@gmail.com/ Fixes: dc2872247ec0 ("btrfs: keep track of the last logged keys when logging a directory") Reviewed-by: Boris Burkov Signed-off-by: Filipe Manana Signed-off-by: David Sterba Signed-off-by: Sasha Levin --- fs/btrfs/tree-log.c | 8 -------- 1 file changed, 8 deletions(-) diff --git a/fs/btrfs/tree-log.c b/fs/btrfs/tree-log.c index 30f3c3b849c143..f55d886debe2fa 100644 --- a/fs/btrfs/tree-log.c +++ b/fs/btrfs/tree-log.c @@ -5872,14 +5872,6 @@ static int log_new_dir_dentries(struct btrfs_trans_handle *trans, struct btrfs_inode *curr_inode = start_inode; int ret = 0; - /* - * If we are logging a new name, as part of a link or rename operation, - * don't bother logging new dentries, as we just want to log the names - * of an inode and that any new parents exist. - */ - if (ctx->logging_new_name) - return 0; - path = btrfs_alloc_path(); if (!path) return -ENOMEM; From f79a80c7fc06df8e4b2b0918608f035136a43fa3 Mon Sep 17 00:00:00 2001 From: Qu Wenruo Date: Tue, 25 Nov 2025 18:49:56 +1030 Subject: [PATCH 1308/3902] btrfs: fix a potential path leak in print_data_reloc_error() [ Upstream commit 313ef70a9f0f637a09d9ef45222f5bdcf30a354b ] Inside print_data_reloc_error(), if extent_from_logical() failed we return immediately. However there are the following cases where extent_from_logical() can return error but still holds a path: - btrfs_search_slot() returned 0 - No backref item found in extent tree - No flags_ret provided This is not possible in this call site though. So for the above two cases, we can return without releasing the path, causing extent buffer leaks. Fixes: b9a9a85059cd ("btrfs: output affected files when relocation fails") Signed-off-by: Qu Wenruo Reviewed-by: David Sterba Signed-off-by: David Sterba Signed-off-by: Sasha Levin --- fs/btrfs/inode.c | 1 + 1 file changed, 1 insertion(+) diff --git a/fs/btrfs/inode.c b/fs/btrfs/inode.c index 6282911e536f08..51401d586a7b65 100644 --- a/fs/btrfs/inode.c +++ b/fs/btrfs/inode.c @@ -256,6 +256,7 @@ static void print_data_reloc_error(const struct btrfs_inode *inode, u64 file_off if (ret < 0) { btrfs_err_rl(fs_info, "failed to lookup extent item for logical %llu: %d", logical, ret); + btrfs_release_path(&path); return; } eb = path.nodes[0]; From 75fffc9fef36e98a667b390ccaad8a774dd3360c Mon Sep 17 00:00:00 2001 From: Ondrej Mosnacek Date: Thu, 4 Dec 2025 13:59:16 +0100 Subject: [PATCH 1309/3902] bpf, arm64: Do not audit capability check in do_jit() [ Upstream commit 189e5deb944a6f9c7992355d60bffd8ec2e54a9c ] Analogically to the x86 commit 881a9c9cb785 ("bpf: Do not audit capability check in do_jit()"), change the capable() call to ns_capable_noaudit() in order to avoid spurious SELinux denials in audit log. The commit log from that commit applies here as well: """ The failure of this check only results in a security mitigation being applied, slightly affecting performance of the compiled BPF program. It doesn't result in a failed syscall, an thus auditing a failed LSM permission check for it is unwanted. For example with SELinux, it causes a denial to be reported for confined processes running as root, which tends to be flagged as a problem to be fixed in the policy. Yet dontauditing or allowing CAP_SYS_ADMIN to the domain may not be desirable, as it would allow/silence also other checks - either going against the principle of least privilege or making debugging potentially harder. Fix it by changing it from capable() to ns_capable_noaudit(), which instructs the LSMs to not audit the resulting denials. """ Fixes: f300769ead03 ("arm64: bpf: Only mitigate cBPF programs loaded by unprivileged users") Signed-off-by: Ondrej Mosnacek Link: https://lore.kernel.org/r/20251204125916.441021-1-omosnace@redhat.com Signed-off-by: Alexei Starovoitov Signed-off-by: Sasha Levin --- arch/arm64/net/bpf_jit_comp.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/arch/arm64/net/bpf_jit_comp.c b/arch/arm64/net/bpf_jit_comp.c index 0c9a50a1e73e7d..0dfefeedfe56ce 100644 --- a/arch/arm64/net/bpf_jit_comp.c +++ b/arch/arm64/net/bpf_jit_comp.c @@ -1004,7 +1004,7 @@ static void __maybe_unused build_bhb_mitigation(struct jit_ctx *ctx) arm64_get_spectre_v2_state() == SPECTRE_VULNERABLE) return; - if (capable(CAP_SYS_ADMIN)) + if (ns_capable_noaudit(&init_user_ns, CAP_SYS_ADMIN)) return; if (supports_clearbhb(SCOPE_SYSTEM)) { From b7a2957ace3de743ff1bcf1295e662576b1a02ec Mon Sep 17 00:00:00 2001 From: "T.J. Mercier" Date: Wed, 3 Dec 2025 16:03:47 -0800 Subject: [PATCH 1310/3902] bpf: Fix truncated dmabuf iterator reads [ Upstream commit 234483565dbb2b264fdd165927c89fbf3ecf4733 ] If there is a large number (hundreds) of dmabufs allocated, the text output generated from dmabuf_iter_seq_show can exceed common user buffer sizes (e.g. PAGE_SIZE) necessitating multiple start/stop cycles to iterate through all dmabufs. However the dmabuf iterator currently returns NULL in dmabuf_iter_seq_start for all non-zero pos values, which results in the truncation of the output before all dmabufs are handled. After dma_buf_iter_begin / dma_buf_iter_next, the refcount of the buffer is elevated so that the BPF iterator program can run without holding any locks. When a stop occurs, instead of immediately dropping the reference on the buffer, stash a pointer to the buffer in seq->priv until either start is called or the iterator is released. This also enables the resumption of iteration without first walking through the list of dmabufs based on the pos value. Fixes: 76ea95534995 ("bpf: Add dmabuf iterator") Signed-off-by: T.J. Mercier Link: https://lore.kernel.org/r/20251204000348.1413593-1-tjmercier@google.com Signed-off-by: Alexei Starovoitov Signed-off-by: Sasha Levin --- kernel/bpf/dmabuf_iter.c | 56 +++++++++++++++++++++++++++++++++++----- 1 file changed, 49 insertions(+), 7 deletions(-) diff --git a/kernel/bpf/dmabuf_iter.c b/kernel/bpf/dmabuf_iter.c index 4dd7ef7c145ca7..cd500248abd959 100644 --- a/kernel/bpf/dmabuf_iter.c +++ b/kernel/bpf/dmabuf_iter.c @@ -6,10 +6,33 @@ #include #include +struct dmabuf_iter_priv { + /* + * If this pointer is non-NULL, the buffer's refcount is elevated to + * prevent destruction between stop/start. If reading is not resumed and + * start is never called again, then dmabuf_iter_seq_fini drops the + * reference when the iterator is released. + */ + struct dma_buf *dmabuf; +}; + static void *dmabuf_iter_seq_start(struct seq_file *seq, loff_t *pos) { - if (*pos) - return NULL; + struct dmabuf_iter_priv *p = seq->private; + + if (*pos) { + struct dma_buf *dmabuf = p->dmabuf; + + if (!dmabuf) + return NULL; + + /* + * Always resume from where we stopped, regardless of the value + * of pos. + */ + p->dmabuf = NULL; + return dmabuf; + } return dma_buf_iter_begin(); } @@ -54,8 +77,11 @@ static void dmabuf_iter_seq_stop(struct seq_file *seq, void *v) { struct dma_buf *dmabuf = v; - if (dmabuf) - dma_buf_put(dmabuf); + if (dmabuf) { + struct dmabuf_iter_priv *p = seq->private; + + p->dmabuf = dmabuf; + } } static const struct seq_operations dmabuf_iter_seq_ops = { @@ -71,11 +97,27 @@ static void bpf_iter_dmabuf_show_fdinfo(const struct bpf_iter_aux_info *aux, seq_puts(seq, "dmabuf iter\n"); } +static int dmabuf_iter_seq_init(void *priv, struct bpf_iter_aux_info *aux) +{ + struct dmabuf_iter_priv *p = (struct dmabuf_iter_priv *)priv; + + p->dmabuf = NULL; + return 0; +} + +static void dmabuf_iter_seq_fini(void *priv) +{ + struct dmabuf_iter_priv *p = (struct dmabuf_iter_priv *)priv; + + if (p->dmabuf) + dma_buf_put(p->dmabuf); +} + static const struct bpf_iter_seq_info dmabuf_iter_seq_info = { .seq_ops = &dmabuf_iter_seq_ops, - .init_seq_private = NULL, - .fini_seq_private = NULL, - .seq_priv_size = 0, + .init_seq_private = dmabuf_iter_seq_init, + .fini_seq_private = dmabuf_iter_seq_fini, + .seq_priv_size = sizeof(struct dmabuf_iter_priv), }; static struct bpf_iter_reg bpf_dmabuf_reg_info = { From d0e177e176e7d1e90c55b17c519c2aa954a4db1e Mon Sep 17 00:00:00 2001 From: Shuran Liu Date: Sat, 6 Dec 2025 22:12:09 +0800 Subject: [PATCH 1311/3902] bpf: Fix verifier assumptions of bpf_d_path's output buffer [ Upstream commit ac44dcc788b950606793e8f9690c30925f59df02 ] Commit 37cce22dbd51 ("bpf: verifier: Refactor helper access type tracking") started distinguishing read vs write accesses performed by helpers. The second argument of bpf_d_path() is a pointer to a buffer that the helper fills with the resulting path. However, its prototype currently uses ARG_PTR_TO_MEM without MEM_WRITE. Before 37cce22dbd51, helper accesses were conservatively treated as potential writes, so this mismatch did not cause issues. Since that commit, the verifier may incorrectly assume that the buffer contents are unchanged across the helper call and base its optimizations on this wrong assumption. This can lead to misbehaviour in BPF programs that read back the buffer, such as prefix comparisons on the returned path. Fix this by marking the second argument of bpf_d_path() as ARG_PTR_TO_MEM | MEM_WRITE so that the verifier correctly models the write to the caller-provided buffer. Fixes: 37cce22dbd51 ("bpf: verifier: Refactor helper access type tracking") Co-developed-by: Zesen Liu Signed-off-by: Zesen Liu Co-developed-by: Peili Gao Signed-off-by: Peili Gao Co-developed-by: Haoran Ni Signed-off-by: Haoran Ni Signed-off-by: Shuran Liu Reviewed-by: Matt Bobrowski Link: https://lore.kernel.org/r/20251206141210.3148-2-electronlsr@gmail.com Signed-off-by: Alexei Starovoitov Signed-off-by: Sasha Levin --- kernel/trace/bpf_trace.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/kernel/trace/bpf_trace.c b/kernel/trace/bpf_trace.c index 4f87c16d915a02..49e0bdaa7a1bf1 100644 --- a/kernel/trace/bpf_trace.c +++ b/kernel/trace/bpf_trace.c @@ -965,7 +965,7 @@ static const struct bpf_func_proto bpf_d_path_proto = { .ret_type = RET_INTEGER, .arg1_type = ARG_PTR_TO_BTF_ID, .arg1_btf_id = &bpf_d_path_btf_ids[0], - .arg2_type = ARG_PTR_TO_MEM, + .arg2_type = ARG_PTR_TO_MEM | MEM_WRITE, .arg3_type = ARG_CONST_SIZE_OR_ZERO, .allowed = bpf_d_path_allowed, }; From 6564f76a9efe7af2244140199faf8f8a18eccfbc Mon Sep 17 00:00:00 2001 From: Deepanshu Kartikey Date: Wed, 10 Dec 2025 18:58:07 +0530 Subject: [PATCH 1312/3902] btrfs: fix memory leak of fs_devices in degraded seed device path [ Upstream commit b57f2ddd28737db6ff0e9da8467f0ab9d707e997 ] In open_seed_devices(), when find_fsid() fails and we're in DEGRADED mode, a new fs_devices is allocated via alloc_fs_devices() but is never added to the seed_list before returning. This contrasts with the normal path where fs_devices is properly added via list_add(). If any error occurs later in read_one_dev() or btrfs_read_chunk_tree(), the cleanup code iterates seed_list to free seed devices, but this orphaned fs_devices is never found and never freed, causing a memory leak. Any devices allocated via add_missing_dev() and attached to this fs_devices are also leaked. Fix this by adding the newly allocated fs_devices to seed_list in the degraded path, consistent with the normal path. Fixes: 5f37583569442 ("Btrfs: move the missing device to its own fs device list") Reported-by: syzbot+eadd98df8bceb15d7fed@syzkaller.appspotmail.com Closes: https://syzkaller.appspot.com/bug?extid=eadd98df8bceb15d7fed Tested-by: syzbot+eadd98df8bceb15d7fed@syzkaller.appspotmail.com Reviewed-by: Qu Wenruo Signed-off-by: Deepanshu Kartikey Reviewed-by: David Sterba Signed-off-by: David Sterba Signed-off-by: Sasha Levin --- fs/btrfs/volumes.c | 1 + 1 file changed, 1 insertion(+) diff --git a/fs/btrfs/volumes.c b/fs/btrfs/volumes.c index 2bec544d8ba300..48e717c105c356 100644 --- a/fs/btrfs/volumes.c +++ b/fs/btrfs/volumes.c @@ -7178,6 +7178,7 @@ static struct btrfs_fs_devices *open_seed_devices(struct btrfs_fs_info *fs_info, fs_devices->seeding = true; fs_devices->opened = 1; + list_add(&fs_devices->seed_list, &fs_info->fs_devices->seed_list); return fs_devices; } From 6fd59082b6266a2392fd005009b97f6bcf9912f5 Mon Sep 17 00:00:00 2001 From: Filipe Manana Date: Thu, 11 Dec 2025 11:51:19 +0000 Subject: [PATCH 1313/3902] btrfs: fix changeset leak on mmap write after failure to reserve metadata [ Upstream commit 37343524f000d2a64359867d7024a73233d3b438 ] If the call to btrfs_delalloc_reserve_metadata() fails we jump to the 'out_noreserve' label and there we never free the extent_changeset allocated by the previous call to btrfs_check_data_free_space() (if qgroups are enabled). Fix this by calling extent_changeset_free() under the 'out_noreserve' label. Fixes: 6599716de2d6 ("btrfs: fix -ENOSPC mmap write failure on NOCOW files/extents") Reported-by: syzbot+2f8aa76e6acc9fce6638@syzkaller.appspotmail.com Link: https://lore.kernel.org/linux-btrfs/693a635a.a70a0220.33cd7b.0029.GAE@google.com/ Reviewed-by: Qu Wenruo Signed-off-by: Filipe Manana Reviewed-by: David Sterba Signed-off-by: David Sterba Signed-off-by: Sasha Levin --- fs/btrfs/file.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/fs/btrfs/file.c b/fs/btrfs/file.c index fa82def46e3952..0fee45d35a5f83 100644 --- a/fs/btrfs/file.c +++ b/fs/btrfs/file.c @@ -2018,13 +2018,14 @@ static vm_fault_t btrfs_page_mkwrite(struct vm_fault *vmf) else btrfs_delalloc_release_space(inode, data_reserved, page_start, reserved_space, true); - extent_changeset_free(data_reserved); out_noreserve: if (only_release_metadata) btrfs_check_nocow_unlock(inode); sb_end_pagefault(inode->vfs_inode.i_sb); + extent_changeset_free(data_reserved); + if (ret < 0) return vmf_error(ret); From 4642686699a46718d7f2fb5acd1e9d866a9d9cca Mon Sep 17 00:00:00 2001 From: Al Viro Date: Sat, 13 Dec 2025 17:50:23 -0500 Subject: [PATCH 1314/3902] shmem: fix recovery on rename failures [ Upstream commit e1b4c6a58304fd490124cc2b454d80edc786665c ] maple_tree insertions can fail if we are seriously short on memory; simple_offset_rename() does not recover well if it runs into that. The same goes for simple_offset_rename_exchange(). Moreover, shmem_whiteout() expects that if it succeeds, the caller will progress to d_move(), i.e. that shmem_rename2() won't fail past the successful call of shmem_whiteout(). Not hard to fix, fortunately - mtree_store() can't fail if the index we are trying to store into is already present in the tree as a singleton. For simple_offset_rename_exchange() that's enough - we just need to be careful about the order of operations. For simple_offset_rename() solution is to preinsert the target into the tree for new_dir; the rest can be done without any potentially failing operations. That preinsertion has to be done in shmem_rename2() rather than in simple_offset_rename() itself - otherwise we'd need to deal with the possibility of failure after successful shmem_whiteout(). Fixes: a2e459555c5f ("shmem: stable directory offsets") Reviewed-by: Christian Brauner Reviewed-by: Chuck Lever Signed-off-by: Al Viro Signed-off-by: Sasha Levin --- fs/libfs.c | 50 +++++++++++++++++++--------------------------- include/linux/fs.h | 2 +- mm/shmem.c | 18 ++++++++++++----- 3 files changed, 35 insertions(+), 35 deletions(-) diff --git a/fs/libfs.c b/fs/libfs.c index ce8c496a6940ac..6be233c787fd8c 100644 --- a/fs/libfs.c +++ b/fs/libfs.c @@ -346,22 +346,22 @@ void simple_offset_remove(struct offset_ctx *octx, struct dentry *dentry) * User space expects the directory offset value of the replaced * (new) directory entry to be unchanged after a rename. * - * Returns zero on success, a negative errno value on failure. + * Caller must have grabbed a slot for new_dentry in the maple_tree + * associated with new_dir, even if dentry is negative. */ -int simple_offset_rename(struct inode *old_dir, struct dentry *old_dentry, - struct inode *new_dir, struct dentry *new_dentry) +void simple_offset_rename(struct inode *old_dir, struct dentry *old_dentry, + struct inode *new_dir, struct dentry *new_dentry) { struct offset_ctx *old_ctx = old_dir->i_op->get_offset_ctx(old_dir); struct offset_ctx *new_ctx = new_dir->i_op->get_offset_ctx(new_dir); long new_offset = dentry2offset(new_dentry); - simple_offset_remove(old_ctx, old_dentry); + if (WARN_ON(!new_offset)) + return; - if (new_offset) { - offset_set(new_dentry, 0); - return simple_offset_replace(new_ctx, old_dentry, new_offset); - } - return simple_offset_add(new_ctx, old_dentry); + simple_offset_remove(old_ctx, old_dentry); + offset_set(new_dentry, 0); + WARN_ON(simple_offset_replace(new_ctx, old_dentry, new_offset)); } /** @@ -388,31 +388,23 @@ int simple_offset_rename_exchange(struct inode *old_dir, long new_index = dentry2offset(new_dentry); int ret; - simple_offset_remove(old_ctx, old_dentry); - simple_offset_remove(new_ctx, new_dentry); + if (WARN_ON(!old_index || !new_index)) + return -EINVAL; - ret = simple_offset_replace(new_ctx, old_dentry, new_index); - if (ret) - goto out_restore; + ret = mtree_store(&new_ctx->mt, new_index, old_dentry, GFP_KERNEL); + if (WARN_ON(ret)) + return ret; - ret = simple_offset_replace(old_ctx, new_dentry, old_index); - if (ret) { - simple_offset_remove(new_ctx, old_dentry); - goto out_restore; + ret = mtree_store(&old_ctx->mt, old_index, new_dentry, GFP_KERNEL); + if (WARN_ON(ret)) { + mtree_store(&new_ctx->mt, new_index, new_dentry, GFP_KERNEL); + return ret; } - ret = simple_rename_exchange(old_dir, old_dentry, new_dir, new_dentry); - if (ret) { - simple_offset_remove(new_ctx, old_dentry); - simple_offset_remove(old_ctx, new_dentry); - goto out_restore; - } + offset_set(old_dentry, new_index); + offset_set(new_dentry, old_index); + simple_rename_exchange(old_dir, old_dentry, new_dir, new_dentry); return 0; - -out_restore: - (void)simple_offset_replace(old_ctx, old_dentry, old_index); - (void)simple_offset_replace(new_ctx, new_dentry, new_index); - return ret; } /** diff --git a/include/linux/fs.h b/include/linux/fs.h index dd3b57cfadeeb4..9b2230fb2332ff 100644 --- a/include/linux/fs.h +++ b/include/linux/fs.h @@ -3676,7 +3676,7 @@ struct offset_ctx { void simple_offset_init(struct offset_ctx *octx); int simple_offset_add(struct offset_ctx *octx, struct dentry *dentry); void simple_offset_remove(struct offset_ctx *octx, struct dentry *dentry); -int simple_offset_rename(struct inode *old_dir, struct dentry *old_dentry, +void simple_offset_rename(struct inode *old_dir, struct dentry *old_dentry, struct inode *new_dir, struct dentry *new_dentry); int simple_offset_rename_exchange(struct inode *old_dir, struct dentry *old_dentry, diff --git a/mm/shmem.c b/mm/shmem.c index 5a3f0f754dc0c6..d09ccb2ba21e93 100644 --- a/mm/shmem.c +++ b/mm/shmem.c @@ -4039,6 +4039,7 @@ static int shmem_rename2(struct mnt_idmap *idmap, { struct inode *inode = d_inode(old_dentry); int they_are_dirs = S_ISDIR(inode->i_mode); + bool had_offset = false; int error; if (flags & ~(RENAME_NOREPLACE | RENAME_EXCHANGE | RENAME_WHITEOUT)) @@ -4051,16 +4052,23 @@ static int shmem_rename2(struct mnt_idmap *idmap, if (!simple_empty(new_dentry)) return -ENOTEMPTY; + error = simple_offset_add(shmem_get_offset_ctx(new_dir), new_dentry); + if (error == -EBUSY) + had_offset = true; + else if (unlikely(error)) + return error; + if (flags & RENAME_WHITEOUT) { error = shmem_whiteout(idmap, old_dir, old_dentry); - if (error) + if (error) { + if (!had_offset) + simple_offset_remove(shmem_get_offset_ctx(new_dir), + new_dentry); return error; + } } - error = simple_offset_rename(old_dir, old_dentry, new_dir, new_dentry); - if (error) - return error; - + simple_offset_rename(old_dir, old_dentry, new_dir, new_dentry); if (d_really_is_positive(new_dentry)) { (void) shmem_unlink(new_dir, new_dentry); if (they_are_dirs) { From 142194fb21afe964d2d194cab1fc357cbf87e899 Mon Sep 17 00:00:00 2001 From: Joanne Koong Date: Mon, 22 Sep 2025 11:00:42 -0700 Subject: [PATCH 1315/3902] iomap: adjust read range correctly for non-block-aligned positions [ Upstream commit 7aa6bc3e8766990824f66ca76c19596ce10daf3e ] iomap_adjust_read_range() assumes that the position and length passed in are block-aligned. This is not always the case however, as shown in the syzbot generated case for erofs. This causes too many bytes to be skipped for uptodate blocks, which results in returning the incorrect position and length to read in. If all the blocks are uptodate, this underflows length and returns a position beyond the folio. Fix the calculation to also take into account the block offset when calculating how many bytes can be skipped for uptodate blocks. Signed-off-by: Joanne Koong Tested-by: syzbot@syzkaller.appspotmail.com Reviewed-by: Brian Foster Reviewed-by: Christoph Hellwig Signed-off-by: Christian Brauner Signed-off-by: Sasha Levin --- fs/iomap/buffered-io.c | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/fs/iomap/buffered-io.c b/fs/iomap/buffered-io.c index 8b847a1e27f13e..1c95a0a7b302d1 100644 --- a/fs/iomap/buffered-io.c +++ b/fs/iomap/buffered-io.c @@ -240,17 +240,24 @@ static void iomap_adjust_read_range(struct inode *inode, struct folio *folio, * to avoid reading in already uptodate ranges. */ if (ifs) { - unsigned int i; + unsigned int i, blocks_skipped; /* move forward for each leading block marked uptodate */ - for (i = first; i <= last; i++) { + for (i = first; i <= last; i++) if (!ifs_block_is_uptodate(ifs, i)) break; - *pos += block_size; - poff += block_size; - plen -= block_size; - first++; + + blocks_skipped = i - first; + if (blocks_skipped) { + unsigned long block_offset = *pos & (block_size - 1); + unsigned bytes_skipped = + (blocks_skipped << block_bits) - block_offset; + + *pos += bytes_skipped; + poff += bytes_skipped; + plen -= bytes_skipped; } + first = i; /* truncate len if we find any trailing uptodate block(s) */ while (++i <= last) { From bfa4924cbc5204cac3e8fa02048d94be1eb50062 Mon Sep 17 00:00:00 2001 From: Joanne Koong Date: Tue, 11 Nov 2025 11:36:51 -0800 Subject: [PATCH 1316/3902] iomap: account for unaligned end offsets when truncating read range [ Upstream commit 9d875e0eef8ec15b6b1da0cb9a0f8ed13efee89e ] The end position to start truncating from may be at an offset into a block, which under the current logic would result in overtruncation. Adjust the calculation to account for unaligned end offsets. Signed-off-by: Joanne Koong Link: https://patch.msgid.link/20251111193658.3495942-3-joannelkoong@gmail.com Reviewed-by: Christoph Hellwig Reviewed-by: Darrick J. Wong Signed-off-by: Christian Brauner Signed-off-by: Sasha Levin --- fs/iomap/buffered-io.c | 22 ++++++++++++++++++++-- 1 file changed, 20 insertions(+), 2 deletions(-) diff --git a/fs/iomap/buffered-io.c b/fs/iomap/buffered-io.c index 1c95a0a7b302d1..c0fa6acf19375b 100644 --- a/fs/iomap/buffered-io.c +++ b/fs/iomap/buffered-io.c @@ -217,6 +217,22 @@ static void ifs_free(struct folio *folio) kfree(ifs); } +/* + * Calculate how many bytes to truncate based off the number of blocks to + * truncate and the end position to start truncating from. + */ +static size_t iomap_bytes_to_truncate(loff_t end_pos, unsigned block_bits, + unsigned blocks_truncated) +{ + unsigned block_size = 1 << block_bits; + unsigned block_offset = end_pos & (block_size - 1); + + if (!block_offset) + return blocks_truncated << block_bits; + + return ((blocks_truncated - 1) << block_bits) + block_offset; +} + /* * Calculate the range inside the folio that we actually need to read. */ @@ -262,7 +278,8 @@ static void iomap_adjust_read_range(struct inode *inode, struct folio *folio, /* truncate len if we find any trailing uptodate block(s) */ while (++i <= last) { if (ifs_block_is_uptodate(ifs, i)) { - plen -= (last - i + 1) * block_size; + plen -= iomap_bytes_to_truncate(*pos + plen, + block_bits, last - i + 1); last = i - 1; break; } @@ -278,7 +295,8 @@ static void iomap_adjust_read_range(struct inode *inode, struct folio *folio, unsigned end = offset_in_folio(folio, isize - 1) >> block_bits; if (first <= end && last > end) - plen -= (last - end) * block_size; + plen -= iomap_bytes_to_truncate(*pos + plen, block_bits, + last - end); } *offp = poff; From ec73412f61d3d36af5ae97df453871fef839628f Mon Sep 17 00:00:00 2001 From: Pankaj Raghav Date: Sun, 21 Sep 2025 12:03:58 +0200 Subject: [PATCH 1317/3902] scripts/faddr2line: Fix "Argument list too long" error [ Upstream commit ff5c0466486ba8d07ab2700380e8fd6d5344b4e9 ] The run_readelf() function reads the entire output of readelf into a single shell variable. For large object files with extensive debug information, the size of this variable can exceed the system's command-line argument length limit. When this variable is subsequently passed to sed via `echo "${out}"`, it triggers an "Argument list too long" error, causing the script to fail. Fix this by redirecting the output of readelf to a temporary file instead of a variable. The sed commands are then modified to read from this file, avoiding the argument length limitation entirely. Signed-off-by: Pankaj Raghav Signed-off-by: Josh Poimboeuf Signed-off-by: Sasha Levin --- scripts/faddr2line | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/scripts/faddr2line b/scripts/faddr2line index 1fa6beef9f978e..477b6d2aa31794 100755 --- a/scripts/faddr2line +++ b/scripts/faddr2line @@ -107,14 +107,19 @@ find_dir_prefix() { run_readelf() { local objfile=$1 - local out=$(${READELF} --file-header --section-headers --symbols --wide $objfile) + local tmpfile + tmpfile=$(mktemp) + + ${READELF} --file-header --section-headers --symbols --wide "$objfile" > "$tmpfile" # This assumes that readelf first prints the file header, then the section headers, then the symbols. # Note: It seems that GNU readelf does not prefix section headers with the "There are X section headers" # line when multiple options are given, so let's also match with the "Section Headers:" line. - ELF_FILEHEADER=$(echo "${out}" | sed -n '/There are [0-9]* section headers, starting at offset\|Section Headers:/q;p') - ELF_SECHEADERS=$(echo "${out}" | sed -n '/There are [0-9]* section headers, starting at offset\|Section Headers:/,$p' | sed -n '/Symbol table .* contains [0-9]* entries:/q;p') - ELF_SYMS=$(echo "${out}" | sed -n '/Symbol table .* contains [0-9]* entries:/,$p') + ELF_FILEHEADER=$(sed -n '/There are [0-9]* section headers, starting at offset\|Section Headers:/q;p' "$tmpfile") + ELF_SECHEADERS=$(sed -n '/There are [0-9]* section headers, starting at offset\|Section Headers:/,$p' "$tmpfile" | sed -n '/Symbol table .* contains [0-9]* entries:/q;p') + ELF_SYMS=$(sed -n '/Symbol table .* contains [0-9]* entries:/,$p' "$tmpfile") + + rm -f -- "$tmpfile" } check_vmlinux() { From 43c2e5c2acaae50e99d1c20a5a46e367c442fb3b Mon Sep 17 00:00:00 2001 From: George Kennedy Date: Tue, 8 Oct 2024 08:00:53 -0500 Subject: [PATCH 1318/3902] perf/x86/amd: Check event before enable to avoid GPF [ Upstream commit 866cf36bfee4fba6a492d2dcc5133f857e3446b0 ] On AMD machines cpuc->events[idx] can become NULL in a subtle race condition with NMI->throttle->x86_pmu_stop(). Check event for NULL in amd_pmu_enable_all() before enable to avoid a GPF. This appears to be an AMD only issue. Syzkaller reported a GPF in amd_pmu_enable_all. INFO: NMI handler (perf_event_nmi_handler) took too long to run: 13.143 msecs Oops: general protection fault, probably for non-canonical address 0xdffffc0000000034: 0000 PREEMPT SMP KASAN NOPTI KASAN: null-ptr-deref in range [0x00000000000001a0-0x00000000000001a7] CPU: 0 UID: 0 PID: 328415 Comm: repro_36674776 Not tainted 6.12.0-rc1-syzk RIP: 0010:x86_pmu_enable_event (arch/x86/events/perf_event.h:1195 arch/x86/events/core.c:1430) RSP: 0018:ffff888118009d60 EFLAGS: 00010012 RAX: dffffc0000000000 RBX: 0000000000000000 RCX: 0000000000000000 RDX: 0000000000000034 RSI: 0000000000000000 RDI: 00000000000001a0 RBP: 0000000000000001 R08: 0000000000000000 R09: 0000000000000000 R10: 0000000000000000 R11: 0000000000000000 R12: 0000000000000002 R13: ffff88811802a440 R14: ffff88811802a240 R15: ffff8881132d8601 FS: 00007f097dfaa700(0000) GS:ffff888118000000(0000) GS:0000000000000000 CS: 0010 DS: 0000 ES: 0000 CR0: 0000000080050033 CR2: 00000000200001c0 CR3: 0000000103d56000 CR4: 00000000000006f0 Call Trace: amd_pmu_enable_all (arch/x86/events/amd/core.c:760 (discriminator 2)) x86_pmu_enable (arch/x86/events/core.c:1360) event_sched_out (kernel/events/core.c:1191 kernel/events/core.c:1186 kernel/events/core.c:2346) __perf_remove_from_context (kernel/events/core.c:2435) event_function (kernel/events/core.c:259) remote_function (kernel/events/core.c:92 (discriminator 1) kernel/events/core.c:72 (discriminator 1)) __flush_smp_call_function_queue (./arch/x86/include/asm/jump_label.h:27 ./include/linux/jump_label.h:207 ./include/trace/events/csd.h:64 kernel/smp.c:135 kernel/smp.c:540) __sysvec_call_function_single (./arch/x86/include/asm/jump_label.h:27 ./include/linux/jump_label.h:207 ./arch/x86/include/asm/trace/irq_vectors.h:99 arch/x86/kernel/smp.c:272) sysvec_call_function_single (arch/x86/kernel/smp.c:266 (discriminator 47) arch/x86/kernel/smp.c:266 (discriminator 47)) Reported-by: syzkaller Signed-off-by: George Kennedy Signed-off-by: Peter Zijlstra (Intel) Signed-off-by: Sasha Levin --- arch/x86/events/amd/core.c | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/arch/x86/events/amd/core.c b/arch/x86/events/amd/core.c index b20661b8621d13..8868f5f5379ba8 100644 --- a/arch/x86/events/amd/core.c +++ b/arch/x86/events/amd/core.c @@ -763,7 +763,12 @@ static void amd_pmu_enable_all(int added) if (!test_bit(idx, cpuc->active_mask)) continue; - amd_pmu_enable_event(cpuc->events[idx]); + /* + * FIXME: cpuc->events[idx] can become NULL in a subtle race + * condition with NMI->throttle->x86_pmu_stop(). + */ + if (cpuc->events[idx]) + amd_pmu_enable_event(cpuc->events[idx]); } } From 3ed049fbfb4d75b4e0b8ab54c934f485129d5dc8 Mon Sep 17 00:00:00 2001 From: Doug Berger Date: Thu, 14 Aug 2025 18:22:36 -0700 Subject: [PATCH 1319/3902] sched/deadline: only set free_cpus for online runqueues [ Upstream commit 382748c05e58a9f1935f5a653c352422375566ea ] Commit 16b269436b72 ("sched/deadline: Modify cpudl::free_cpus to reflect rd->online") introduced the cpudl_set/clear_freecpu functions to allow the cpu_dl::free_cpus mask to be manipulated by the deadline scheduler class rq_on/offline callbacks so the mask would also reflect this state. Commit 9659e1eeee28 ("sched/deadline: Remove cpu_active_mask from cpudl_find()") removed the check of the cpu_active_mask to save some processing on the premise that the cpudl::free_cpus mask already reflected the runqueue online state. Unfortunately, there are cases where it is possible for the cpudl_clear function to set the free_cpus bit for a CPU when the deadline runqueue is offline. When this occurs while a CPU is connected to the default root domain the flag may retain the bad state after the CPU has been unplugged. Later, a different CPU that is transitioning through the default root domain may push a deadline task to the powered down CPU when cpudl_find sees its free_cpus bit is set. If this happens the task will not have the opportunity to run. One example is outlined here: https://lore.kernel.org/lkml/20250110233010.2339521-1-opendmb@gmail.com Another occurs when the last deadline task is migrated from a CPU that has an offlined runqueue. The dequeue_task member of the deadline scheduler class will eventually call cpudl_clear and set the free_cpus bit for the CPU. This commit modifies the cpudl_clear function to be aware of the online state of the deadline runqueue so that the free_cpus mask can be updated appropriately. It is no longer necessary to manage the mask outside of the cpudl_set/clear functions so the cpudl_set/clear_freecpu functions are removed. In addition, since the free_cpus mask is now only updated under the cpudl lock the code was changed to use the non-atomic __cpumask functions. Signed-off-by: Doug Berger Signed-off-by: Peter Zijlstra (Intel) Signed-off-by: Sasha Levin --- kernel/sched/cpudeadline.c | 34 +++++++++------------------------- kernel/sched/cpudeadline.h | 4 +--- kernel/sched/deadline.c | 8 ++++---- 3 files changed, 14 insertions(+), 32 deletions(-) diff --git a/kernel/sched/cpudeadline.c b/kernel/sched/cpudeadline.c index cdd740b3f77436..37b572cc8aca2e 100644 --- a/kernel/sched/cpudeadline.c +++ b/kernel/sched/cpudeadline.c @@ -166,12 +166,13 @@ int cpudl_find(struct cpudl *cp, struct task_struct *p, * cpudl_clear - remove a CPU from the cpudl max-heap * @cp: the cpudl max-heap context * @cpu: the target CPU + * @online: the online state of the deadline runqueue * * Notes: assumes cpu_rq(cpu)->lock is locked * * Returns: (void) */ -void cpudl_clear(struct cpudl *cp, int cpu) +void cpudl_clear(struct cpudl *cp, int cpu, bool online) { int old_idx, new_cpu; unsigned long flags; @@ -184,7 +185,7 @@ void cpudl_clear(struct cpudl *cp, int cpu) if (old_idx == IDX_INVALID) { /* * Nothing to remove if old_idx was invalid. - * This could happen if a rq_offline_dl is + * This could happen if rq_online_dl or rq_offline_dl is * called for a CPU without -dl tasks running. */ } else { @@ -195,9 +196,12 @@ void cpudl_clear(struct cpudl *cp, int cpu) cp->elements[new_cpu].idx = old_idx; cp->elements[cpu].idx = IDX_INVALID; cpudl_heapify(cp, old_idx); - - cpumask_set_cpu(cpu, cp->free_cpus); } + if (likely(online)) + __cpumask_set_cpu(cpu, cp->free_cpus); + else + __cpumask_clear_cpu(cpu, cp->free_cpus); + raw_spin_unlock_irqrestore(&cp->lock, flags); } @@ -228,7 +232,7 @@ void cpudl_set(struct cpudl *cp, int cpu, u64 dl) cp->elements[new_idx].cpu = cpu; cp->elements[cpu].idx = new_idx; cpudl_heapify_up(cp, new_idx); - cpumask_clear_cpu(cpu, cp->free_cpus); + __cpumask_clear_cpu(cpu, cp->free_cpus); } else { cp->elements[old_idx].dl = dl; cpudl_heapify(cp, old_idx); @@ -237,26 +241,6 @@ void cpudl_set(struct cpudl *cp, int cpu, u64 dl) raw_spin_unlock_irqrestore(&cp->lock, flags); } -/* - * cpudl_set_freecpu - Set the cpudl.free_cpus - * @cp: the cpudl max-heap context - * @cpu: rd attached CPU - */ -void cpudl_set_freecpu(struct cpudl *cp, int cpu) -{ - cpumask_set_cpu(cpu, cp->free_cpus); -} - -/* - * cpudl_clear_freecpu - Clear the cpudl.free_cpus - * @cp: the cpudl max-heap context - * @cpu: rd attached CPU - */ -void cpudl_clear_freecpu(struct cpudl *cp, int cpu) -{ - cpumask_clear_cpu(cpu, cp->free_cpus); -} - /* * cpudl_init - initialize the cpudl structure * @cp: the cpudl max-heap context diff --git a/kernel/sched/cpudeadline.h b/kernel/sched/cpudeadline.h index 11c0f1faa7e11d..d7699468eedd57 100644 --- a/kernel/sched/cpudeadline.h +++ b/kernel/sched/cpudeadline.h @@ -19,8 +19,6 @@ struct cpudl { int cpudl_find(struct cpudl *cp, struct task_struct *p, struct cpumask *later_mask); void cpudl_set(struct cpudl *cp, int cpu, u64 dl); -void cpudl_clear(struct cpudl *cp, int cpu); +void cpudl_clear(struct cpudl *cp, int cpu, bool online); int cpudl_init(struct cpudl *cp); -void cpudl_set_freecpu(struct cpudl *cp, int cpu); -void cpudl_clear_freecpu(struct cpudl *cp, int cpu); void cpudl_cleanup(struct cpudl *cp); diff --git a/kernel/sched/deadline.c b/kernel/sched/deadline.c index 7b7671060bf9ed..19b1a8b81c76ce 100644 --- a/kernel/sched/deadline.c +++ b/kernel/sched/deadline.c @@ -1811,7 +1811,7 @@ static void dec_dl_deadline(struct dl_rq *dl_rq, u64 deadline) if (!dl_rq->dl_nr_running) { dl_rq->earliest_dl.curr = 0; dl_rq->earliest_dl.next = 0; - cpudl_clear(&rq->rd->cpudl, rq->cpu); + cpudl_clear(&rq->rd->cpudl, rq->cpu, rq->online); cpupri_set(&rq->rd->cpupri, rq->cpu, rq->rt.highest_prio.curr); } else { struct rb_node *leftmost = rb_first_cached(&dl_rq->root); @@ -2883,9 +2883,10 @@ static void rq_online_dl(struct rq *rq) if (rq->dl.overloaded) dl_set_overload(rq); - cpudl_set_freecpu(&rq->rd->cpudl, rq->cpu); if (rq->dl.dl_nr_running > 0) cpudl_set(&rq->rd->cpudl, rq->cpu, rq->dl.earliest_dl.curr); + else + cpudl_clear(&rq->rd->cpudl, rq->cpu, true); } /* Assumes rq->lock is held */ @@ -2894,8 +2895,7 @@ static void rq_offline_dl(struct rq *rq) if (rq->dl.overloaded) dl_clear_overload(rq); - cpudl_clear(&rq->rd->cpudl, rq->cpu); - cpudl_clear_freecpu(&rq->rd->cpudl, rq->cpu); + cpudl_clear(&rq->rd->cpudl, rq->cpu, false); } void __init init_sched_dl_class(void) From aab3236ff1da534d2886798caeaaec5fa1f7fe96 Mon Sep 17 00:00:00 2001 From: Peter Zijlstra Date: Fri, 7 Nov 2025 17:01:20 +0100 Subject: [PATCH 1320/3902] sched/fair: Revert max_newidle_lb_cost bump [ Upstream commit d206fbad9328ddb68ebabd7cf7413392acd38081 ] Many people reported regressions on their database workloads due to: 155213a2aed4 ("sched/fair: Bump sd->max_newidle_lb_cost when newidle balance fails") For instance Adam Li reported a 6% regression on SpecJBB. Conversely this will regress schbench again; on my machine from 2.22 Mrps/s down to 2.04 Mrps/s. Reported-by: Joseph Salisbury Reported-by: Adam Li Reported-by: Dietmar Eggemann Reported-by: Hazem Mohamed Abuelfotoh Signed-off-by: Peter Zijlstra (Intel) Reviewed-by: Dietmar Eggemann Tested-by: Dietmar Eggemann Tested-by: Chris Mason Link: https://lkml.kernel.org/r/20250626144017.1510594-2-clm@fb.com Link: https://lkml.kernel.org/r/006c9df2-b691-47f1-82e6-e233c3f91faf@oracle.com Link: https://patch.msgid.link/20251107161739.406147760@infradead.org Signed-off-by: Sasha Levin --- kernel/sched/fair.c | 19 +++---------------- 1 file changed, 3 insertions(+), 16 deletions(-) diff --git a/kernel/sched/fair.c b/kernel/sched/fair.c index 71d3f4125b0e73..967ca52fb23118 100644 --- a/kernel/sched/fair.c +++ b/kernel/sched/fair.c @@ -12174,14 +12174,8 @@ static inline bool update_newidle_cost(struct sched_domain *sd, u64 cost) /* * Track max cost of a domain to make sure to not delay the * next wakeup on the CPU. - * - * sched_balance_newidle() bumps the cost whenever newidle - * balance fails, and we don't want things to grow out of - * control. Use the sysctl_sched_migration_cost as the upper - * limit, plus a litle extra to avoid off by ones. */ - sd->max_newidle_lb_cost = - min(cost, sysctl_sched_migration_cost + 200); + sd->max_newidle_lb_cost = cost; sd->last_decay_max_lb_cost = jiffies; } else if (time_after(jiffies, sd->last_decay_max_lb_cost + HZ)) { /* @@ -12873,17 +12867,10 @@ static int sched_balance_newidle(struct rq *this_rq, struct rq_flags *rf) t1 = sched_clock_cpu(this_cpu); domain_cost = t1 - t0; + update_newidle_cost(sd, domain_cost); + curr_cost += domain_cost; t0 = t1; - - /* - * Failing newidle means it is not effective; - * bump the cost so we end up doing less of it. - */ - if (!pulled_task) - domain_cost = (3 * sd->max_newidle_lb_cost) / 2; - - update_newidle_cost(sd, domain_cost); } /* From bb4c2f559e576dbabdb5413e92b692d36d9f5703 Mon Sep 17 00:00:00 2001 From: Peter Zijlstra Date: Fri, 31 Oct 2025 12:04:24 +0100 Subject: [PATCH 1321/3902] x86/ptrace: Always inline trivial accessors [ Upstream commit 1fe4002cf7f23d70c79bda429ca2a9423ebcfdfa ] A KASAN build bloats these single load/store helpers such that it fails to inline them: vmlinux.o: error: objtool: irqentry_exit+0x5e8: call to instruction_pointer_set() with UACCESS enabled Make sure the compiler isn't allowed to do stupid. Signed-off-by: Peter Zijlstra (Intel) Signed-off-by: Ingo Molnar Link: https://patch.msgid.link/20251031105435.GU4068168@noisy.programming.kicks-ass.net Signed-off-by: Sasha Levin --- arch/x86/include/asm/ptrace.h | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/arch/x86/include/asm/ptrace.h b/arch/x86/include/asm/ptrace.h index 50f75467f73d0f..b5dec859bc75aa 100644 --- a/arch/x86/include/asm/ptrace.h +++ b/arch/x86/include/asm/ptrace.h @@ -187,12 +187,12 @@ convert_ip_to_linear(struct task_struct *child, struct pt_regs *regs); extern void send_sigtrap(struct pt_regs *regs, int error_code, int si_code); -static inline unsigned long regs_return_value(struct pt_regs *regs) +static __always_inline unsigned long regs_return_value(struct pt_regs *regs) { return regs->ax; } -static inline void regs_set_return_value(struct pt_regs *regs, unsigned long rc) +static __always_inline void regs_set_return_value(struct pt_regs *regs, unsigned long rc) { regs->ax = rc; } @@ -277,34 +277,34 @@ static __always_inline bool ip_within_syscall_gap(struct pt_regs *regs) } #endif -static inline unsigned long kernel_stack_pointer(struct pt_regs *regs) +static __always_inline unsigned long kernel_stack_pointer(struct pt_regs *regs) { return regs->sp; } -static inline unsigned long instruction_pointer(struct pt_regs *regs) +static __always_inline unsigned long instruction_pointer(struct pt_regs *regs) { return regs->ip; } -static inline void instruction_pointer_set(struct pt_regs *regs, - unsigned long val) +static __always_inline +void instruction_pointer_set(struct pt_regs *regs, unsigned long val) { regs->ip = val; } -static inline unsigned long frame_pointer(struct pt_regs *regs) +static __always_inline unsigned long frame_pointer(struct pt_regs *regs) { return regs->bp; } -static inline unsigned long user_stack_pointer(struct pt_regs *regs) +static __always_inline unsigned long user_stack_pointer(struct pt_regs *regs) { return regs->sp; } -static inline void user_stack_pointer_set(struct pt_regs *regs, - unsigned long val) +static __always_inline +void user_stack_pointer_set(struct pt_regs *regs, unsigned long val) { regs->sp = val; } From f91dad0a3b381244183ffbea4cec5a7a69d6f41e Mon Sep 17 00:00:00 2001 From: Cryolitia PukNgae Date: Tue, 25 Nov 2025 16:14:38 +0800 Subject: [PATCH 1322/3902] ACPICA: Avoid walking the Namespace if start_node is NULL [ Upstream commit 9d6c58dae8f6590c746ac5d0012ffe14a77539f0 ] Although commit 0c9992315e73 ("ACPICA: Avoid walking the ACPI Namespace if it is not there") fixed the situation when both start_node and acpi_gbl_root_node are NULL, the Linux kernel mainline now still crashed on Honor Magicbook 14 Pro [1]. That happens due to the access to the member of parent_node in acpi_ns_get_next_node(). The NULL pointer dereference will always happen, no matter whether or not the start_node is equal to ACPI_ROOT_OBJECT, so move the check of start_node being NULL out of the if block. Unfortunately, all the attempts to contact Honor have failed, they refused to provide any technical support for Linux. The bad DSDT table's dump could be found on GitHub [2]. DMI: HONOR FMB-P/FMB-P-PCB, BIOS 1.13 05/08/2025 Link: https://github.com/acpica/acpica/commit/1c1b57b9eba4554cb132ee658dd942c0210ed20d Link: https://gist.github.com/Cryolitia/a860ffc97437dcd2cd988371d5b73ed7 [1] Link: https://github.com/denis-bb/honor-fmb-p-dsdt [2] Signed-off-by: Cryolitia PukNgae Reviewed-by: WangYuli [ rjw: Subject adjustment, changelog edits ] Link: https://patch.msgid.link/20251125-acpica-v1-1-99e63b1b25f8@linux.dev Signed-off-by: Rafael J. Wysocki Signed-off-by: Sasha Levin --- drivers/acpi/acpica/nswalk.c | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/drivers/acpi/acpica/nswalk.c b/drivers/acpi/acpica/nswalk.c index a2ac06a26e921c..5670ff5a43cd48 100644 --- a/drivers/acpi/acpica/nswalk.c +++ b/drivers/acpi/acpica/nswalk.c @@ -169,9 +169,12 @@ acpi_ns_walk_namespace(acpi_object_type type, if (start_node == ACPI_ROOT_OBJECT) { start_node = acpi_gbl_root_node; - if (!start_node) { - return_ACPI_STATUS(AE_NO_NAMESPACE); - } + } + + /* Avoid walking the namespace if the StartNode is NULL */ + + if (!start_node) { + return_ACPI_STATUS(AE_NO_NAMESPACE); } /* Null child means "get first node" */ From 29014a19d4176067b88aecc25a483fc525e1ef97 Mon Sep 17 00:00:00 2001 From: Sakari Ailus Date: Wed, 1 Oct 2025 13:43:19 +0300 Subject: [PATCH 1323/3902] ACPI: property: Use ACPI functions in acpi_graph_get_next_endpoint() only [ Upstream commit 5d010473cdeaabf6a2d3a9e2aed2186c1b73c213 ] Calling fwnode_get_next_child_node() in ACPI implementation of the fwnode property API is somewhat problematic as the latter is used in the impelementation of the former. Instead of using fwnode_get_next_child_node() in acpi_graph_get_next_endpoint(), call acpi_get_next_subnode() directly instead. Signed-off-by: Sakari Ailus Reviewed-by: Laurent Pinchart Reviewed-by: Jonathan Cameron Link: https://patch.msgid.link/20251001104320.1272752-3-sakari.ailus@linux.intel.com Signed-off-by: Rafael J. Wysocki Signed-off-by: Sasha Levin --- drivers/acpi/property.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/drivers/acpi/property.c b/drivers/acpi/property.c index b12057baaae7b6..19737f9e1e16fa 100644 --- a/drivers/acpi/property.c +++ b/drivers/acpi/property.c @@ -1472,7 +1472,7 @@ static struct fwnode_handle *acpi_graph_get_next_endpoint( if (!prev) { do { - port = fwnode_get_next_child_node(fwnode, port); + port = acpi_get_next_subnode(fwnode, port); /* * The names of the port nodes begin with "port@" * followed by the number of the port node and they also @@ -1490,13 +1490,13 @@ static struct fwnode_handle *acpi_graph_get_next_endpoint( if (!port) return NULL; - endpoint = fwnode_get_next_child_node(port, prev); + endpoint = acpi_get_next_subnode(port, prev); while (!endpoint) { - port = fwnode_get_next_child_node(fwnode, port); + port = acpi_get_next_subnode(fwnode, port); if (!port) break; if (is_acpi_graph_node(port, "port")) - endpoint = fwnode_get_next_child_node(port, NULL); + endpoint = acpi_get_next_subnode(port, NULL); } /* From 3d9ecabe2953d3827d9bf9198b48d2db573a8259 Mon Sep 17 00:00:00 2001 From: Hal Feng Date: Thu, 16 Oct 2025 16:00:48 +0800 Subject: [PATCH 1324/3902] cpufreq: dt-platdev: Add JH7110S SOC to the allowlist [ Upstream commit 6e7970cab51d01b8f7c56f120486c571c22e1b80 ] Add the compatible strings for supporting the generic cpufreq driver on the StarFive JH7110S SoC. Signed-off-by: Hal Feng Reviewed-by: Heinrich Schuchardt Signed-off-by: Viresh Kumar Signed-off-by: Sasha Levin --- drivers/cpufreq/cpufreq-dt-platdev.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/cpufreq/cpufreq-dt-platdev.c b/drivers/cpufreq/cpufreq-dt-platdev.c index cd1816a12bb996..dc11b62399ad5b 100644 --- a/drivers/cpufreq/cpufreq-dt-platdev.c +++ b/drivers/cpufreq/cpufreq-dt-platdev.c @@ -87,6 +87,7 @@ static const struct of_device_id allowlist[] __initconst = { { .compatible = "st-ericsson,u9540", }, { .compatible = "starfive,jh7110", }, + { .compatible = "starfive,jh7110s", }, { .compatible = "ti,omap2", }, { .compatible = "ti,omap4", }, From f916f071e41c637ea73aa0ea6f4551eedbd81ec9 Mon Sep 17 00:00:00 2001 From: Armin Wolf Date: Wed, 8 Oct 2025 01:41:45 +0200 Subject: [PATCH 1325/3902] ACPI: fan: Workaround for 64-bit firmware bug [ Upstream commit 2e00f7a4bb0ac25ec7477b55fe482da39fb4dce8 ] Some firmware implementations use the "Ones" ASL opcode to produce an integer with all bits set in order to indicate missing speed or power readings. This however only works when using 32-bit integers, as the ACPI spec requires a 32-bit integer (0xFFFFFFFF) to be returned for missing speed/power readings. With 64-bit integers the "Ones" opcode produces a 64-bit integer with all bits set, violating the ACPI spec regarding the placeholder value for missing readings. Work around such buggy firmware implementation by also checking for 64-bit integers with all bits set when reading _FST. Signed-off-by: Armin Wolf [ rjw: Typo fix in the changelog ] Link: https://patch.msgid.link/20251007234149.2769-3-W_Armin@gmx.de Signed-off-by: Rafael J. Wysocki Signed-off-by: Sasha Levin --- drivers/acpi/fan.h | 33 +++++++++++++++++++++++++++++++++ drivers/acpi/fan_hwmon.c | 10 +++------- 2 files changed, 36 insertions(+), 7 deletions(-) diff --git a/drivers/acpi/fan.h b/drivers/acpi/fan.h index bedbab0e8e4e90..0d73433c388923 100644 --- a/drivers/acpi/fan.h +++ b/drivers/acpi/fan.h @@ -11,6 +11,7 @@ #define _ACPI_FAN_H_ #include +#include #define ACPI_FAN_DEVICE_IDS \ {"INT3404", }, /* Fan */ \ @@ -60,6 +61,38 @@ struct acpi_fan { struct device_attribute fine_grain_control; }; +/** + * acpi_fan_speed_valid - Check if fan speed value is valid + * @speeed: Speed value returned by the ACPI firmware + * + * Check if the fan speed value returned by the ACPI firmware is valid. This function is + * necessary as ACPI firmware implementations can return 0xFFFFFFFF to signal that the + * ACPI fan does not support speed reporting. Additionally, some buggy ACPI firmware + * implementations return a value larger than the 32-bit integer value defined by + * the ACPI specification when using placeholder values. Such invalid values are also + * detected by this function. + * + * Returns: True if the fan speed value is valid, false otherwise. + */ +static inline bool acpi_fan_speed_valid(u64 speed) +{ + return speed < U32_MAX; +} + +/** + * acpi_fan_power_valid - Check if fan power value is valid + * @power: Power value returned by the ACPI firmware + * + * Check if the fan power value returned by the ACPI firmware is valid. + * See acpi_fan_speed_valid() for details. + * + * Returns: True if the fan power value is valid, false otherwise. + */ +static inline bool acpi_fan_power_valid(u64 power) +{ + return power < U32_MAX; +} + int acpi_fan_get_fst(acpi_handle handle, struct acpi_fan_fst *fst); int acpi_fan_create_attributes(struct acpi_device *device); void acpi_fan_delete_attributes(struct acpi_device *device); diff --git a/drivers/acpi/fan_hwmon.c b/drivers/acpi/fan_hwmon.c index 4b2c2007f2d7fa..47a02ef5a60671 100644 --- a/drivers/acpi/fan_hwmon.c +++ b/drivers/acpi/fan_hwmon.c @@ -15,10 +15,6 @@ #include "fan.h" -/* Returned when the ACPI fan does not support speed reporting */ -#define FAN_SPEED_UNAVAILABLE U32_MAX -#define FAN_POWER_UNAVAILABLE U32_MAX - static struct acpi_fan_fps *acpi_fan_get_current_fps(struct acpi_fan *fan, u64 control) { unsigned int i; @@ -77,7 +73,7 @@ static umode_t acpi_fan_hwmon_is_visible(const void *drvdata, enum hwmon_sensor_ * when the associated attribute should not be created. */ for (i = 0; i < fan->fps_count; i++) { - if (fan->fps[i].power != FAN_POWER_UNAVAILABLE) + if (acpi_fan_power_valid(fan->fps[i].power)) return 0444; } @@ -106,7 +102,7 @@ static int acpi_fan_hwmon_read(struct device *dev, enum hwmon_sensor_types type, case hwmon_fan: switch (attr) { case hwmon_fan_input: - if (fst.speed == FAN_SPEED_UNAVAILABLE) + if (!acpi_fan_speed_valid(fst.speed)) return -ENODEV; if (fst.speed > LONG_MAX) @@ -134,7 +130,7 @@ static int acpi_fan_hwmon_read(struct device *dev, enum hwmon_sensor_types type, if (!fps) return -EIO; - if (fps->power == FAN_POWER_UNAVAILABLE) + if (!acpi_fan_power_valid(fps->power)) return -ENODEV; if (fps->power > LONG_MAX / MICROWATT_PER_MILLIWATT) From e486de4c945d32e6205aa9b0404880956d01bb83 Mon Sep 17 00:00:00 2001 From: Shuhao Fu Date: Mon, 6 Oct 2025 03:31:17 +0800 Subject: [PATCH 1326/3902] cpufreq: s5pv210: fix refcount leak [ Upstream commit 2de5cb96060a1664880d65b120e59485a73588a8 ] In function `s5pv210_cpu_init`, a possible refcount inconsistency has been identified, causing a resource leak. Why it is a bug: 1. For every clk_get, there should be a matching clk_put on every successive error handling path. 2. After calling `clk_get(dmc1_clk)`, variable `dmc1_clk` will not be freed even if any error happens. How it is fixed: For every failed path, an extra goto label is added to ensure `dmc1_clk` will be freed regardlessly. Signed-off-by: Shuhao Fu Signed-off-by: Viresh Kumar Signed-off-by: Sasha Levin --- drivers/cpufreq/s5pv210-cpufreq.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/drivers/cpufreq/s5pv210-cpufreq.c b/drivers/cpufreq/s5pv210-cpufreq.c index 4215621deb3fe8..ba8a1c96427a10 100644 --- a/drivers/cpufreq/s5pv210-cpufreq.c +++ b/drivers/cpufreq/s5pv210-cpufreq.c @@ -518,7 +518,7 @@ static int s5pv210_cpu_init(struct cpufreq_policy *policy) if (policy->cpu != 0) { ret = -EINVAL; - goto out_dmc1; + goto out; } /* @@ -530,7 +530,7 @@ static int s5pv210_cpu_init(struct cpufreq_policy *policy) if ((mem_type != LPDDR) && (mem_type != LPDDR2)) { pr_err("CPUFreq doesn't support this memory type\n"); ret = -EINVAL; - goto out_dmc1; + goto out; } /* Find current refresh counter and frequency each DMC */ @@ -544,6 +544,8 @@ static int s5pv210_cpu_init(struct cpufreq_policy *policy) cpufreq_generic_init(policy, s5pv210_freq_table, 40000); return 0; +out: + clk_put(dmc1_clk); out_dmc1: clk_put(dmc0_clk); out_dmc0: From 63859f6da00d6f6b7ed5b64453d9ff7f9b8d24c7 Mon Sep 17 00:00:00 2001 From: Aboorva Devarajan Date: Mon, 6 Oct 2025 07:09:54 +0530 Subject: [PATCH 1327/3902] cpuidle: menu: Use residency threshold in polling state override decisions [ Upstream commit 07d815701274d156ad8c7c088a52e01642156fb8 ] On virtualized PowerPC (pseries) systems, where only one polling state (Snooze) and one deep state (CEDE) are available, selecting CEDE when the predicted idle duration is less than the target residency of CEDE state can hurt performance. In such cases, the entry/exit overhead of CEDE outweighs the power savings, leading to unnecessary state transitions and higher latency. Menu governor currently contains a special-case rule that prioritizes the first non-polling state over polling, even when its target residency is much longer than the predicted idle duration. On PowerPC/pseries, where the gap between the polling state (Snooze) and the first non-polling state (CEDE) is large, this behavior causes performance regressions. Refine that special case by adding an extra requirement: the first non-polling state can only be chosen if its target residency is below the defined RESIDENCY_THRESHOLD_NS. If this condition is not satisfied, polling is allowed instead, avoiding suboptimal non-polling state entries. This change is limited to the single special-case rule for the first non-polling state. The general non-polling state selection logic in the menu governor remains unchanged. Performance improvement observed with pgbench on PowerPC (pseries) system: +---------------------------+------------+------------+------------+ | Metric | Baseline | Patched | Change (%) | +---------------------------+------------+------------+------------+ | Transactions/sec (TPS) | 495,210 | 536,982 | +8.45% | | Avg latency (ms) | 0.163 | 0.150 | -7.98% | +---------------------------+------------+------------+------------+ CPUIdle state usage: +--------------+--------------+-------------+ | Metric | Baseline | Patched | +--------------+--------------+-------------+ | Total usage | 12,735,820 | 13,918,442 | | Above usage | 11,401,520 | 1,598,210 | | Below usage | 20,145 | 702,395 | +--------------+--------------+-------------+ Above/Total and Below/Total usage percentages: +------------------------+-----------+---------+ | Metric | Baseline | Patched | +------------------------+-----------+---------+ | Above % (Above/Total) | 89.56% | 11.49% | | Below % (Below/Total) | 0.16% | 5.05% | | Total cpuidle miss (%) | 89.72% | 16.54% | +------------------------+-----------+---------+ The results indicate that restricting CEDE selection to cases where its residency matches the predicted idle time reduces mispredictions, lowers unnecessary state transitions, and improves overall throughput. Reviewed-by: Christian Loehle Signed-off-by: Aboorva Devarajan [ rjw: Changelog edits, rebase ] Link: https://patch.msgid.link/20251006013954.17972-1-aboorvad@linux.ibm.com Signed-off-by: Rafael J. Wysocki Signed-off-by: Sasha Levin --- drivers/cpuidle/governors/menu.c | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/drivers/cpuidle/governors/menu.c b/drivers/cpuidle/governors/menu.c index 23239b0c04f95b..64d6f7a1c77663 100644 --- a/drivers/cpuidle/governors/menu.c +++ b/drivers/cpuidle/governors/menu.c @@ -317,12 +317,13 @@ static int menu_select(struct cpuidle_driver *drv, struct cpuidle_device *dev, } /* - * Use a physical idle state, not busy polling, unless a timer - * is going to trigger soon enough or the exit latency of the - * idle state in question is greater than the predicted idle - * duration. + * Use a physical idle state instead of busy polling so long as + * its target residency is below the residency threshold, its + * exit latency is not greater than the predicted idle duration, + * and the next timer doesn't expire soon. */ if ((drv->states[idx].flags & CPUIDLE_FLAG_POLLING) && + s->target_residency_ns < RESIDENCY_THRESHOLD_NS && s->target_residency_ns <= data->next_timer_ns && s->exit_latency_ns <= predicted_ns) { predicted_ns = s->target_residency_ns; From 3ef4a51315a92fb88f7541516af1b19aa0305712 Mon Sep 17 00:00:00 2001 From: Yu Peng Date: Thu, 30 Oct 2025 20:37:57 +0800 Subject: [PATCH 1328/3902] x86/microcode: Mark early_parse_cmdline() as __init [ Upstream commit ca8313fd83399ea1d18e695c2ae9b259985c9e1f ] Fix section mismatch warning reported by modpost: .text:early_parse_cmdline() -> .init.data:boot_command_line The function early_parse_cmdline() is only called during init and accesses init data, so mark it __init to match its usage. [ bp: This happens only when the toolchain fails to inline the function and I haven't been able to reproduce it with any toolchain I'm using. Patch is obviously correct regardless. ] Signed-off-by: Yu Peng Signed-off-by: Borislav Petkov (AMD) Link: https://patch.msgid.link/all/20251030123757.1410904-1-pengyu@kylinos.cn Signed-off-by: Sasha Levin --- arch/x86/kernel/cpu/microcode/core.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/arch/x86/kernel/cpu/microcode/core.c b/arch/x86/kernel/cpu/microcode/core.c index f75c140906d002..539edd6d6dc8c3 100644 --- a/arch/x86/kernel/cpu/microcode/core.c +++ b/arch/x86/kernel/cpu/microcode/core.c @@ -136,7 +136,7 @@ bool __init microcode_loader_disabled(void) return dis_ucode_ldr; } -static void early_parse_cmdline(void) +static void __init early_parse_cmdline(void) { char cmd_buf[64] = {}; char *s, *p = cmd_buf; From ab665eb9997198408b915724df6954156260bfd3 Mon Sep 17 00:00:00 2001 From: Mauro Carvalho Chehab Date: Thu, 18 Sep 2025 13:54:55 +0200 Subject: [PATCH 1329/3902] scripts: kdoc_parser.py: warn about Python version only once [ Upstream commit ade9b9576e2f000fb2ef0ac3bcd26e1167fd813b ] When running kernel-doc over multiple documents, it emits one error message per file with is not what we want: $ python3.6 scripts/kernel-doc.py . --none ... Warning: ./include/trace/events/swiotlb.h:0 Python 3.7 or later is required for correct results Warning: ./include/trace/events/iommu.h:0 Python 3.7 or later is required for correct results Warning: ./include/trace/events/sock.h:0 Python 3.7 or later is required for correct results ... Change the logic to warn it only once at the library: $ python3.6 scripts/kernel-doc.py . --none Warning: Python 3.7 or later is required for correct results Warning: ./include/cxl/features.h:0 Python 3.7 or later is required for correct results When running from command line, it warns twice, but that sounds ok. Signed-off-by: Mauro Carvalho Chehab Message-ID: <68e54cf8b1201d1f683aad9bc710a99421910356.1758196090.git.mchehab+huawei@kernel.org> Signed-off-by: Jonathan Corbet Signed-off-by: Sasha Levin --- scripts/lib/kdoc/kdoc_parser.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/scripts/lib/kdoc/kdoc_parser.py b/scripts/lib/kdoc/kdoc_parser.py index 2376f180b1fa97..89d920e0b65ca0 100644 --- a/scripts/lib/kdoc/kdoc_parser.py +++ b/scripts/lib/kdoc/kdoc_parser.py @@ -350,6 +350,7 @@ def dump_section(self, start_new=True): self.section = SECTION_DEFAULT self._contents = [] +python_warning = False class KernelDoc: """ @@ -383,9 +384,13 @@ def __init__(self, config, fname): # We need Python 3.7 for its "dicts remember the insertion # order" guarantee # - if sys.version_info.major == 3 and sys.version_info.minor < 7: + global python_warning + if (not python_warning and + sys.version_info.major == 3 and sys.version_info.minor < 7): + self.emit_msg(0, 'Python 3.7 or later is required for correct results') + python_warning = True def emit_msg(self, ln, msg, warning=True): """Emit a message""" From c9266cafb2772d7ff3adae699b86b2e12d6d4d5f Mon Sep 17 00:00:00 2001 From: Song Liu Date: Mon, 13 Oct 2025 10:30:19 -0700 Subject: [PATCH 1330/3902] livepatch: Match old_sympos 0 and 1 in klp_find_func() [ Upstream commit 139560e8b973402140cafeb68c656c1374bd4c20 ] When there is only one function of the same name, old_sympos of 0 and 1 are logically identical. Match them in klp_find_func(). This is to avoid a corner case with different toolchain behavior. In this specific issue, two versions of kpatch-build were used to build livepatch for the same kernel. One assigns old_sympos == 0 for unique local functions, the other assigns old_sympos == 1 for unique local functions. Both versions work fine by themselves. (PS: This behavior change was introduced in a downstream version of kpatch-build. This change does not exist in upstream kpatch-build.) However, during livepatch upgrade (with the replace flag set) from a patch built with one version of kpatch-build to the same fix built with the other version of kpatch-build, livepatching fails with errors like: [ 14.218706] sysfs: cannot create duplicate filename 'xxx/somefunc,1' ... [ 14.219466] Call Trace: [ 14.219468] [ 14.219469] dump_stack_lvl+0x47/0x60 [ 14.219474] sysfs_warn_dup.cold+0x17/0x27 [ 14.219476] sysfs_create_dir_ns+0x95/0xb0 [ 14.219479] kobject_add_internal+0x9e/0x260 [ 14.219483] kobject_add+0x68/0x80 [ 14.219485] ? kstrdup+0x3c/0xa0 [ 14.219486] klp_enable_patch+0x320/0x830 [ 14.219488] patch_init+0x443/0x1000 [ccc_0_6] [ 14.219491] ? 0xffffffffa05eb000 [ 14.219492] do_one_initcall+0x2e/0x190 [ 14.219494] do_init_module+0x67/0x270 [ 14.219496] init_module_from_file+0x75/0xa0 [ 14.219499] idempotent_init_module+0x15a/0x240 [ 14.219501] __x64_sys_finit_module+0x61/0xc0 [ 14.219503] do_syscall_64+0x5b/0x160 [ 14.219505] entry_SYSCALL_64_after_hwframe+0x4b/0x53 [ 14.219507] RIP: 0033:0x7f545a4bd96d ... [ 14.219516] kobject: kobject_add_internal failed for somefunc,1 with -EEXIST, don't try to register things with the same name ... This happens because klp_find_func() thinks somefunc with old_sympos==0 is not the same as somefunc with old_sympos==1, and klp_add_object_nops adds another xxx/func,1 to the list of functions to patch. Signed-off-by: Song Liu Acked-by: Josh Poimboeuf [pmladek@suse.com: Fixed some typos.] Reviewed-by: Petr Mladek Tested-by: Petr Mladek Signed-off-by: Petr Mladek Signed-off-by: Sasha Levin --- kernel/livepatch/core.c | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/kernel/livepatch/core.c b/kernel/livepatch/core.c index 0e73fac55f8ebe..4e7a5cbc40a917 100644 --- a/kernel/livepatch/core.c +++ b/kernel/livepatch/core.c @@ -88,8 +88,14 @@ static struct klp_func *klp_find_func(struct klp_object *obj, struct klp_func *func; klp_for_each_func(obj, func) { + /* + * Besides identical old_sympos, also consider old_sympos + * of 0 and 1 are identical. + */ if ((strcmp(old_func->old_name, func->old_name) == 0) && - (old_func->old_sympos == func->old_sympos)) { + ((old_func->old_sympos == func->old_sympos) || + (old_func->old_sympos == 0 && func->old_sympos == 1) || + (old_func->old_sympos == 1 && func->old_sympos == 0))) { return func; } } From 1c553c454f540e1f312a5d47280612fd50d52091 Mon Sep 17 00:00:00 2001 From: "Mario Limonciello (AMD)" Date: Wed, 29 Oct 2025 11:15:01 -0500 Subject: [PATCH 1331/3902] crypto: ccp - Add support for PCI device 0x115A [ Upstream commit 9fc6290117259a8dbf8247cb54559df62fd1550f ] PCI device 0x115A is similar to pspv5, except it doesn't have platform access mailbox support. Signed-off-by: Mario Limonciello (AMD) Acked-by: Tom Lendacky Signed-off-by: Herbert Xu Signed-off-by: Sasha Levin --- drivers/crypto/ccp/sp-pci.c | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/drivers/crypto/ccp/sp-pci.c b/drivers/crypto/ccp/sp-pci.c index e7bb803912a6d3..8891ceee1d7d05 100644 --- a/drivers/crypto/ccp/sp-pci.c +++ b/drivers/crypto/ccp/sp-pci.c @@ -459,6 +459,17 @@ static const struct psp_vdata pspv6 = { .intsts_reg = 0x10514, /* P2CMSG_INTSTS */ }; +static const struct psp_vdata pspv7 = { + .tee = &teev2, + .cmdresp_reg = 0x10944, /* C2PMSG_17 */ + .cmdbuff_addr_lo_reg = 0x10948, /* C2PMSG_18 */ + .cmdbuff_addr_hi_reg = 0x1094c, /* C2PMSG_19 */ + .bootloader_info_reg = 0x109ec, /* C2PMSG_59 */ + .feature_reg = 0x109fc, /* C2PMSG_63 */ + .inten_reg = 0x10510, /* P2CMSG_INTEN */ + .intsts_reg = 0x10514, /* P2CMSG_INTSTS */ +}; + #endif static const struct sp_dev_vdata dev_vdata[] = { @@ -525,6 +536,13 @@ static const struct sp_dev_vdata dev_vdata[] = { .psp_vdata = &pspv6, #endif }, + { /* 9 */ + .bar = 2, +#ifdef CONFIG_CRYPTO_DEV_SP_PSP + .psp_vdata = &pspv7, +#endif + }, + }; static const struct pci_device_id sp_pci_table[] = { { PCI_VDEVICE(AMD, 0x1537), (kernel_ulong_t)&dev_vdata[0] }, @@ -539,6 +557,7 @@ static const struct pci_device_id sp_pci_table[] = { { PCI_VDEVICE(AMD, 0x17E0), (kernel_ulong_t)&dev_vdata[7] }, { PCI_VDEVICE(AMD, 0x156E), (kernel_ulong_t)&dev_vdata[8] }, { PCI_VDEVICE(AMD, 0x17D8), (kernel_ulong_t)&dev_vdata[8] }, + { PCI_VDEVICE(AMD, 0x115A), (kernel_ulong_t)&dev_vdata[9] }, /* Last entry must be zero */ { 0, } }; From 5859a556db4b3ba93bcef21c5d448467a9bc6b53 Mon Sep 17 00:00:00 2001 From: Konstantin Komarov Date: Mon, 1 Sep 2025 11:48:48 +0300 Subject: [PATCH 1332/3902] fs/ntfs3: Support timestamps prior to epoch [ Upstream commit 5180138604323895b5c291eca6aa7c20be494ade ] Before it used an unsigned 64-bit type, which prevented proper handling of timestamps earlier than 1970-01-01. Switch to a signed 64-bit type to support pre-epoch timestamps. The issue was caught by xfstests. Signed-off-by: Konstantin Komarov Signed-off-by: Sasha Levin --- fs/ntfs3/ntfs_fs.h | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/fs/ntfs3/ntfs_fs.h b/fs/ntfs3/ntfs_fs.h index 630128716ea739..2649fbe16669de 100644 --- a/fs/ntfs3/ntfs_fs.h +++ b/fs/ntfs3/ntfs_fs.h @@ -979,11 +979,12 @@ static inline __le64 kernel2nt(const struct timespec64 *ts) */ static inline void nt2kernel(const __le64 tm, struct timespec64 *ts) { - u64 t = le64_to_cpu(tm) - _100ns2seconds * SecondsToStartOf1970; + s32 t32; + /* use signed 64 bit to support timestamps prior to epoch. xfstest 258. */ + s64 t = le64_to_cpu(tm) - _100ns2seconds * SecondsToStartOf1970; - // WARNING: do_div changes its first argument(!) - ts->tv_nsec = do_div(t, _100ns2seconds) * 100; - ts->tv_sec = t; + ts->tv_sec = div_s64_rem(t, _100ns2seconds, &t32); + ts->tv_nsec = t32 * 100; } static inline struct ntfs_sb_info *ntfs_sb(struct super_block *sb) From f44e3d622d74e3e77cd672046e7afae6f337d73c Mon Sep 17 00:00:00 2001 From: Mikhail Malyshev Date: Wed, 15 Oct 2025 16:34:52 +0000 Subject: [PATCH 1333/3902] kbuild: Use objtree for module signing key path [ Upstream commit af61da281f52aba0c5b090bafb3a31c5739850ff ] When building out-of-tree modules with CONFIG_MODULE_SIG_FORCE=y, module signing fails because the private key path uses $(srctree) while the public key path uses $(objtree). Since signing keys are generated in the build directory during kernel compilation, both paths should use $(objtree) for consistency. This causes SSL errors like: SSL error:02001002:system library:fopen:No such file or directory sign-file: /kernel-src/certs/signing_key.pem The issue occurs because: - sig-key uses: $(srctree)/certs/signing_key.pem (source tree) - cmd_sign uses: $(objtree)/certs/signing_key.x509 (build tree) But both keys are generated in $(objtree) during the build. This complements commit 25ff08aa43e37 ("kbuild: Fix signing issue for external modules") which fixed the scripts path and public key path, but missed the private key path inconsistency. Fixes out-of-tree module signing for configurations with separate source and build directories (e.g., O=/kernel-out). Signed-off-by: Mikhail Malyshev Reviewed-by: Nathan Chancellor Tested-by: Nicolas Schier Link: https://patch.msgid.link/20251015163452.3754286-1-mike.malyshev@gmail.com Signed-off-by: Nicolas Schier Signed-off-by: Sasha Levin --- scripts/Makefile.modinst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/Makefile.modinst b/scripts/Makefile.modinst index 1628198f3e8309..9ba45e5b32b183 100644 --- a/scripts/Makefile.modinst +++ b/scripts/Makefile.modinst @@ -100,7 +100,7 @@ endif # Don't stop modules_install even if we can't sign external modules. # ifeq ($(filter pkcs11:%, $(CONFIG_MODULE_SIG_KEY)),) -sig-key := $(if $(wildcard $(CONFIG_MODULE_SIG_KEY)),,$(srctree)/)$(CONFIG_MODULE_SIG_KEY) +sig-key := $(if $(wildcard $(CONFIG_MODULE_SIG_KEY)),,$(objtree)/)$(CONFIG_MODULE_SIG_KEY) else sig-key := $(CONFIG_MODULE_SIG_KEY) endif From b3c151fe8f543f1a0b8b5df16ce5d97afa5ec85a Mon Sep 17 00:00:00 2001 From: Pedro Demarchi Gomes Date: Fri, 3 Oct 2025 12:38:50 -0300 Subject: [PATCH 1334/3902] ntfs: set dummy blocksize to read boot_block when mounting [ Upstream commit d1693a7d5a38acf6424235a6070bcf5b186a360d ] When mounting, sb->s_blocksize is used to read the boot_block without being defined or validated. Set a dummy blocksize before attempting to read the boot_block. The issue can be triggered with the following syz reproducer: mkdirat(0xffffffffffffff9c, &(0x7f0000000080)='./file1\x00', 0x0) r4 = openat$nullb(0xffffffffffffff9c, &(0x7f0000000040), 0x121403, 0x0) ioctl$FS_IOC_SETFLAGS(r4, 0x40081271, &(0x7f0000000980)=0x4000) mount(&(0x7f0000000140)=@nullb, &(0x7f0000000040)='./cgroup\x00', &(0x7f0000000000)='ntfs3\x00', 0x2208004, 0x0) syz_clone(0x88200200, 0x0, 0x0, 0x0, 0x0, 0x0) Here, the ioctl sets the bdev block size to 16384. During mount, get_tree_bdev_flags() calls sb_set_blocksize(sb, block_size(bdev)), but since block_size(bdev) > PAGE_SIZE, sb_set_blocksize() leaves sb->s_blocksize at zero. Later, ntfs_init_from_boot() attempts to read the boot_block while sb->s_blocksize is still zero, which triggers the bug. Reported-by: syzbot+f4f84b57a01d6b8364ad@syzkaller.appspotmail.com Closes: https://syzkaller.appspot.com/bug?extid=f4f84b57a01d6b8364ad Signed-off-by: Pedro Demarchi Gomes [almaz.alexandrovich@paragon-software.com: changed comment style, added return value handling] Signed-off-by: Konstantin Komarov Signed-off-by: Sasha Levin --- fs/ntfs3/super.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/fs/ntfs3/super.c b/fs/ntfs3/super.c index ddff94c091b8c6..e6c0908e27c299 100644 --- a/fs/ntfs3/super.c +++ b/fs/ntfs3/super.c @@ -933,6 +933,11 @@ static int ntfs_init_from_boot(struct super_block *sb, u32 sector_size, sbi->volume.blocks = dev_size >> PAGE_SHIFT; + /* Set dummy blocksize to read boot_block. */ + if (!sb_min_blocksize(sb, PAGE_SIZE)) { + return -EINVAL; + } + read_boot: bh = ntfs_bread(sb, boot_block); if (!bh) From 772e0a8809f17c3fffec98ab6422fe2bb13dace2 Mon Sep 17 00:00:00 2001 From: Viacheslav Dubeyko Date: Fri, 31 Oct 2025 17:12:30 -0700 Subject: [PATCH 1335/3902] hfsplus: fix volume corruption issue for generic/070 [ Upstream commit ed490f36f439b877393c12a2113601e4145a5a56 ] The xfstests' test-case generic/070 leaves HFS+ volume in corrupted state: sudo ./check generic/070 FSTYP -- hfsplus PLATFORM -- Linux/x86_64 hfsplus-testing-0001 6.17.0-rc1+ #4 SMP PREEMPT_DYNAMIC Wed Oct 1 15:02:44 PDT 2025 MKFS_OPTIONS -- /dev/loop51 MOUNT_OPTIONS -- /dev/loop51 /mnt/scratch generic/070 _check_generic_filesystem: filesystem on /dev/loop50 is inconsistent (see xfstests-dev/results//generic/070.full for details) Ran: generic/070 Failures: generic/070 Failed 1 of 1 tests sudo fsck.hfsplus -d /dev/loop50 ** /dev/loop50 Using cacheBlockSize=32K cacheTotalBlock=1024 cacheSize=32768K. Executing fsck_hfs (version 540.1-Linux). ** Checking non-journaled HFS Plus Volume. The volume name is test ** Checking extents overflow file. Unused node is not erased (node = 1) ** Checking catalog file. ** Checking multi-linked files. ** Checking catalog hierarchy. ** Checking extended attributes file. ** Checking volume bitmap. ** Checking volume information. Verify Status: VIStat = 0x0000, ABTStat = 0x0000 EBTStat = 0x0004 CBTStat = 0x0000 CatStat = 0x00000000 ** Repairing volume. ** Rechecking volume. ** Checking non-journaled HFS Plus Volume. The volume name is test ** Checking extents overflow file. ** Checking catalog file. ** Checking multi-linked files. ** Checking catalog hierarchy. ** Checking extended attributes file. ** Checking volume bitmap. ** Checking volume information. ** The volume test was repaired successfully. It is possible to see that fsck.hfsplus detected not erased and unused node for the case of extents overflow file. The HFS+ logic has special method that defines if the node should be erased: bool hfs_bnode_need_zeroout(struct hfs_btree *tree) { struct super_block *sb = tree->inode->i_sb; struct hfsplus_sb_info *sbi = HFSPLUS_SB(sb); const u32 volume_attr = be32_to_cpu(sbi->s_vhdr->attributes); return tree->cnid == HFSPLUS_CAT_CNID && volume_attr & HFSPLUS_VOL_UNUSED_NODE_FIX; } However, it is possible to see that this method works only for the case of catalog file. But debugging of the issue has shown that HFSPLUS_VOL_UNUSED_NODE_FIX attribute has been requested for the extents overflow file too: catalog file kernel: hfsplus: node 4, num_recs 0, flags 0x10 kernel: hfsplus: tree->cnid 4, volume_attr 0x80000800 extents overflow file kernel: hfsplus: node 1, num_recs 0, flags 0x10 kernel: hfsplus: tree->cnid 3, volume_attr 0x80000800 This patch modifies the hfs_bnode_need_zeroout() by checking only volume_attr but not the b-tree ID because node zeroing can be requested for all HFS+ b-tree types. sudo ./check generic/070 FSTYP -- hfsplus PLATFORM -- Linux/x86_64 hfsplus-testing-0001 6.18.0-rc3+ #79 SMP PREEMPT_DYNAMIC Fri Oct 31 16:07:42 PDT 2025 MKFS_OPTIONS -- /dev/loop51 MOUNT_OPTIONS -- /dev/loop51 /mnt/scratch generic/070 33s ... 34s Ran: generic/070 Passed all 1 tests Signed-off-by: Viacheslav Dubeyko cc: John Paul Adrian Glaubitz cc: Yangtao Li cc: linux-fsdevel@vger.kernel.org Link: https://lore.kernel.org/r/20251101001229.247432-1-slava@dubeyko.com Signed-off-by: Viacheslav Dubeyko Signed-off-by: Sasha Levin --- fs/hfsplus/bnode.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/fs/hfsplus/bnode.c b/fs/hfsplus/bnode.c index 63e652ad1e0def..edf7e27e1e3757 100644 --- a/fs/hfsplus/bnode.c +++ b/fs/hfsplus/bnode.c @@ -704,6 +704,5 @@ bool hfs_bnode_need_zeroout(struct hfs_btree *tree) struct hfsplus_sb_info *sbi = HFSPLUS_SB(sb); const u32 volume_attr = be32_to_cpu(sbi->s_vhdr->attributes); - return tree->cnid == HFSPLUS_CAT_CNID && - volume_attr & HFSPLUS_VOL_UNUSED_NODE_FIX; + return volume_attr & HFSPLUS_VOL_UNUSED_NODE_FIX; } From 5882e7c8cdbb5e254a69628b780acff89c78071e Mon Sep 17 00:00:00 2001 From: Yang Chenzhi Date: Fri, 29 Aug 2025 17:39:12 +0800 Subject: [PATCH 1336/3902] hfsplus: fix missing hfs_bnode_get() in __hfs_bnode_create [ Upstream commit 152af114287851583cf7e0abc10129941f19466a ] When sync() and link() are called concurrently, both threads may enter hfs_bnode_find() without finding the node in the hash table and proceed to create it. Thread A: hfsplus_write_inode() -> hfsplus_write_system_inode() -> hfs_btree_write() -> hfs_bnode_find(tree, 0) -> __hfs_bnode_create(tree, 0) Thread B: hfsplus_create_cat() -> hfs_brec_insert() -> hfs_bnode_split() -> hfs_bmap_alloc() -> hfs_bnode_find(tree, 0) -> __hfs_bnode_create(tree, 0) In this case, thread A creates the bnode, sets refcnt=1, and hashes it. Thread B also tries to create the same bnode, notices it has already been inserted, drops its own instance, and uses the hashed one without getting the node. ``` node2 = hfs_bnode_findhash(tree, cnid); if (!node2) { <- Thread A hash = hfs_bnode_hash(cnid); node->next_hash = tree->node_hash[hash]; tree->node_hash[hash] = node; tree->node_hash_cnt++; } else { <- Thread B spin_unlock(&tree->hash_lock); kfree(node); wait_event(node2->lock_wq, !test_bit(HFS_BNODE_NEW, &node2->flags)); return node2; } ``` However, hfs_bnode_find() requires each call to take a reference. Here both threads end up setting refcnt=1. When they later put the node, this triggers: BUG_ON(!atomic_read(&node->refcnt)) In this scenario, Thread B in fact finds the node in the hash table rather than creating a new one, and thus must take a reference. Fix this by calling hfs_bnode_get() when reusing a bnode newly created by another thread to ensure the refcount is updated correctly. A similar bug was fixed in HFS long ago in commit a9dc087fd3c4 ("fix missing hfs_bnode_get() in __hfs_bnode_create") but the same issue remained in HFS+ until now. Reported-by: syzbot+005d2a9ecd9fbf525f6a@syzkaller.appspotmail.com Signed-off-by: Yang Chenzhi Signed-off-by: Viacheslav Dubeyko Link: https://lore.kernel.org/r/20250829093912.611853-1-yang.chenzhi@vivo.com Signed-off-by: Viacheslav Dubeyko Signed-off-by: Sasha Levin --- fs/hfsplus/bnode.c | 1 + 1 file changed, 1 insertion(+) diff --git a/fs/hfsplus/bnode.c b/fs/hfsplus/bnode.c index edf7e27e1e3757..482a6c5faa1975 100644 --- a/fs/hfsplus/bnode.c +++ b/fs/hfsplus/bnode.c @@ -481,6 +481,7 @@ static struct hfs_bnode *__hfs_bnode_create(struct hfs_btree *tree, u32 cnid) tree->node_hash[hash] = node; tree->node_hash_cnt++; } else { + hfs_bnode_get(node2); spin_unlock(&tree->hash_lock); kfree(node); wait_event(node2->lock_wq, From 91f114bffa36ce56d0e1f60a0a44fc09baaefc79 Mon Sep 17 00:00:00 2001 From: Tetsuo Handa Date: Sat, 15 Nov 2025 18:18:54 +0900 Subject: [PATCH 1337/3902] hfsplus: Verify inode mode when loading from disk [ Upstream commit 005d4b0d33f6b4a23d382b7930f7a96b95b01f39 ] syzbot is reporting that S_IFMT bits of inode->i_mode can become bogus when the S_IFMT bits of the 16bits "mode" field loaded from disk are corrupted. According to [1], the permissions field was treated as reserved in Mac OS 8 and 9. According to [2], the reserved field was explicitly initialized with 0, and that field must remain 0 as long as reserved. Therefore, when the "mode" field is not 0 (i.e. no longer reserved), the file must be S_IFDIR if dir == 1, and the file must be one of S_IFREG/S_IFLNK/S_IFCHR/ S_IFBLK/S_IFIFO/S_IFSOCK if dir == 0. Reported-by: syzbot Closes: https://syzkaller.appspot.com/bug?extid=895c23f6917da440ed0d Link: https://developer.apple.com/library/archive/technotes/tn/tn1150.html#HFSPlusPermissions [1] Link: https://developer.apple.com/library/archive/technotes/tn/tn1150.html#ReservedAndPadFields [2] Signed-off-by: Tetsuo Handa Reviewed-by: Viacheslav Dubeyko Signed-off-by: Viacheslav Dubeyko Link: https://lore.kernel.org/r/04ded9f9-73fb-496c-bfa5-89c4f5d1d7bb@I-love.SAKURA.ne.jp Signed-off-by: Viacheslav Dubeyko Signed-off-by: Sasha Levin --- fs/hfsplus/inode.c | 32 ++++++++++++++++++++++++++++---- 1 file changed, 28 insertions(+), 4 deletions(-) diff --git a/fs/hfsplus/inode.c b/fs/hfsplus/inode.c index b51a411ecd2376..e290e417ed3a7c 100644 --- a/fs/hfsplus/inode.c +++ b/fs/hfsplus/inode.c @@ -180,13 +180,29 @@ const struct dentry_operations hfsplus_dentry_operations = { .d_compare = hfsplus_compare_dentry, }; -static void hfsplus_get_perms(struct inode *inode, - struct hfsplus_perm *perms, int dir) +static int hfsplus_get_perms(struct inode *inode, + struct hfsplus_perm *perms, int dir) { struct hfsplus_sb_info *sbi = HFSPLUS_SB(inode->i_sb); u16 mode; mode = be16_to_cpu(perms->mode); + if (dir) { + if (mode && !S_ISDIR(mode)) + goto bad_type; + } else if (mode) { + switch (mode & S_IFMT) { + case S_IFREG: + case S_IFLNK: + case S_IFCHR: + case S_IFBLK: + case S_IFIFO: + case S_IFSOCK: + break; + default: + goto bad_type; + } + } i_uid_write(inode, be32_to_cpu(perms->owner)); if ((test_bit(HFSPLUS_SB_UID, &sbi->flags)) || (!i_uid_read(inode) && !mode)) @@ -212,6 +228,10 @@ static void hfsplus_get_perms(struct inode *inode, inode->i_flags |= S_APPEND; else inode->i_flags &= ~S_APPEND; + return 0; +bad_type: + pr_err("invalid file type 0%04o for inode %lu\n", mode, inode->i_ino); + return -EIO; } static int hfsplus_file_open(struct inode *inode, struct file *file) @@ -516,7 +536,9 @@ int hfsplus_cat_read_inode(struct inode *inode, struct hfs_find_data *fd) } hfs_bnode_read(fd->bnode, &entry, fd->entryoffset, sizeof(struct hfsplus_cat_folder)); - hfsplus_get_perms(inode, &folder->permissions, 1); + res = hfsplus_get_perms(inode, &folder->permissions, 1); + if (res) + goto out; set_nlink(inode, 1); inode->i_size = 2 + be32_to_cpu(folder->valence); inode_set_atime_to_ts(inode, hfsp_mt2ut(folder->access_date)); @@ -545,7 +567,9 @@ int hfsplus_cat_read_inode(struct inode *inode, struct hfs_find_data *fd) hfsplus_inode_read_fork(inode, HFSPLUS_IS_RSRC(inode) ? &file->rsrc_fork : &file->data_fork); - hfsplus_get_perms(inode, &file->permissions, 0); + res = hfsplus_get_perms(inode, &file->permissions, 0); + if (res) + goto out; set_nlink(inode, 1); if (S_ISREG(inode->i_mode)) { if (file->permissions.dev) From 282214ddf84727ca1b8ee44636da8c20114a82c7 Mon Sep 17 00:00:00 2001 From: Viacheslav Dubeyko Date: Wed, 12 Nov 2025 15:25:23 -0800 Subject: [PATCH 1338/3902] hfsplus: fix volume corruption issue for generic/073 [ Upstream commit 24e17a29cf7537f0947f26a50f85319abd723c6c ] The xfstests' test-case generic/073 leaves HFS+ volume in corrupted state: sudo ./check generic/073 FSTYP -- hfsplus PLATFORM -- Linux/x86_64 hfsplus-testing-0001 6.17.0-rc1+ #4 SMP PREEMPT_DYNAMIC Wed Oct 1 15:02:44 PDT 2025 MKFS_OPTIONS -- /dev/loop51 MOUNT_OPTIONS -- /dev/loop51 /mnt/scratch generic/073 _check_generic_filesystem: filesystem on /dev/loop51 is inconsistent (see XFSTESTS-2/xfstests-dev/results//generic/073.full for details) Ran: generic/073 Failures: generic/073 Failed 1 of 1 tests sudo fsck.hfsplus -d /dev/loop51 ** /dev/loop51 Using cacheBlockSize=32K cacheTotalBlock=1024 cacheSize=32768K. Executing fsck_hfs (version 540.1-Linux). ** Checking non-journaled HFS Plus Volume. The volume name is untitled ** Checking extents overflow file. ** Checking catalog file. ** Checking multi-linked files. ** Checking catalog hierarchy. Invalid directory item count (It should be 1 instead of 0) ** Checking extended attributes file. ** Checking volume bitmap. ** Checking volume information. Verify Status: VIStat = 0x0000, ABTStat = 0x0000 EBTStat = 0x0000 CBTStat = 0x0000 CatStat = 0x00004000 ** Repairing volume. ** Rechecking volume. ** Checking non-journaled HFS Plus Volume. The volume name is untitled ** Checking extents overflow file. ** Checking catalog file. ** Checking multi-linked files. ** Checking catalog hierarchy. ** Checking extended attributes file. ** Checking volume bitmap. ** Checking volume information. ** The volume untitled was repaired successfully. The test is doing these steps on final phase: mv $SCRATCH_MNT/testdir_1/bar $SCRATCH_MNT/testdir_2/bar $XFS_IO_PROG -c "fsync" $SCRATCH_MNT/testdir_1 $XFS_IO_PROG -c "fsync" $SCRATCH_MNT/foo So, we move file bar from testdir_1 into testdir_2 folder. It means that HFS+ logic decrements the number of entries in testdir_1 and increments number of entries in testdir_2. Finally, we do fsync only for testdir_1 and foo but not for testdir_2. As a result, this is the reason why fsck.hfsplus detects the volume corruption afterwards. This patch fixes the issue by means of adding the hfsplus_cat_write_inode() call for old_dir and new_dir in hfsplus_rename() after the successful ending of hfsplus_rename_cat(). This method makes modification of in-core inode objects for old_dir and new_dir but it doesn't save these modifications in Catalog File's entries. It was expected that hfsplus_write_inode() will save these modifications afterwards. However, because generic/073 does fsync only for testdir_1 and foo then testdir_2 modification hasn't beed saved into Catalog File's entry and it was flushed without this modification. And it was detected by fsck.hfsplus. Now, hfsplus_rename() stores in Catalog File all modified entries and correct state of Catalog File will be flushed during hfsplus_file_fsync() call. Finally, it makes fsck.hfsplus happy. sudo ./check generic/073 FSTYP -- hfsplus PLATFORM -- Linux/x86_64 hfsplus-testing-0001 6.18.0-rc3+ #93 SMP PREEMPT_DYNAMIC Wed Nov 12 14:37:49 PST 2025 MKFS_OPTIONS -- /dev/loop51 MOUNT_OPTIONS -- /dev/loop51 /mnt/scratch generic/073 32s ... 32s Ran: generic/073 Passed all 1 tests Signed-off-by: Viacheslav Dubeyko cc: John Paul Adrian Glaubitz cc: Yangtao Li cc: linux-fsdevel@vger.kernel.org Link: https://lore.kernel.org/r/20251112232522.814038-1-slava@dubeyko.com Signed-off-by: Viacheslav Dubeyko Signed-off-by: Sasha Levin --- fs/hfsplus/dir.c | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/fs/hfsplus/dir.c b/fs/hfsplus/dir.c index 1b3e27a0d5e038..cadf0b5f93422e 100644 --- a/fs/hfsplus/dir.c +++ b/fs/hfsplus/dir.c @@ -552,8 +552,13 @@ static int hfsplus_rename(struct mnt_idmap *idmap, res = hfsplus_rename_cat((u32)(unsigned long)old_dentry->d_fsdata, old_dir, &old_dentry->d_name, new_dir, &new_dentry->d_name); - if (!res) + if (!res) { new_dentry->d_fsdata = old_dentry->d_fsdata; + + res = hfsplus_cat_write_inode(old_dir); + if (!res) + res = hfsplus_cat_write_inode(new_dir); + } return res; } From d5fb4e2c9bb01f65713dfd5f58be42176411fa56 Mon Sep 17 00:00:00 2001 From: Konstantin Komarov Date: Thu, 6 Nov 2025 16:17:19 +0300 Subject: [PATCH 1339/3902] fs/ntfs3: check for shutdown in fsync [ Upstream commit 1b2ae190ea43bebb8c73d21f076addc8a8c71849 ] Ensure fsync() returns -EIO when the ntfs3 filesystem is in forced shutdown, instead of silently succeeding via generic_file_fsync(). Signed-off-by: Konstantin Komarov Signed-off-by: Sasha Levin --- fs/ntfs3/file.c | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/fs/ntfs3/file.c b/fs/ntfs3/file.c index 4c90ec2fa2eae0..83f0072f0896c7 100644 --- a/fs/ntfs3/file.c +++ b/fs/ntfs3/file.c @@ -1375,6 +1375,18 @@ static ssize_t ntfs_file_splice_write(struct pipe_inode_info *pipe, return iter_file_splice_write(pipe, file, ppos, len, flags); } +/* + * ntfs_file_fsync - file_operations::fsync + */ +static int ntfs_file_fsync(struct file *file, loff_t start, loff_t end, int datasync) +{ + struct inode *inode = file_inode(file); + if (unlikely(ntfs3_forced_shutdown(inode->i_sb))) + return -EIO; + + return generic_file_fsync(file, start, end, datasync); +} + // clang-format off const struct inode_operations ntfs_file_inode_operations = { .getattr = ntfs_getattr, @@ -1397,7 +1409,7 @@ const struct file_operations ntfs_file_operations = { .splice_write = ntfs_file_splice_write, .mmap_prepare = ntfs_file_mmap_prepare, .open = ntfs_file_open, - .fsync = generic_file_fsync, + .fsync = ntfs_file_fsync, .fallocate = ntfs_fallocate, .release = ntfs_file_release, }; From 40257a2ef1bc19805af016e8213fcfb8a085f6cf Mon Sep 17 00:00:00 2001 From: Bitterblue Smith Date: Thu, 20 Nov 2025 16:10:01 +0200 Subject: [PATCH 1340/3902] wifi: rtl8xxxu: Fix HT40 channel config for RTL8192CU, RTL8723AU [ Upstream commit 5511ba3de434892e5ef3594d6eabbd12b1629356 ] Flip the response rate subchannel. It was backwards, causing low speeds when using 40 MHz channel width. "iw dev ... station dump" showed a low RX rate, 11M or less. Also fix the channel width field of RF6052_REG_MODE_AG. Tested only with RTL8192CU, but these settings are identical for RTL8723AU. Signed-off-by: Bitterblue Smith Reviewed-by: Ping-Ke Shih Signed-off-by: Ping-Ke Shih Link: https://patch.msgid.link/1f46571d-855b-43e1-8bfc-abacceb96043@gmail.com Signed-off-by: Sasha Levin --- drivers/net/wireless/realtek/rtl8xxxu/core.c | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/drivers/net/wireless/realtek/rtl8xxxu/core.c b/drivers/net/wireless/realtek/rtl8xxxu/core.c index be39463bd6c464..3e87c571e24195 100644 --- a/drivers/net/wireless/realtek/rtl8xxxu/core.c +++ b/drivers/net/wireless/realtek/rtl8xxxu/core.c @@ -1252,7 +1252,7 @@ void rtl8xxxu_gen1_config_channel(struct ieee80211_hw *hw) opmode &= ~BW_OPMODE_20MHZ; rtl8xxxu_write8(priv, REG_BW_OPMODE, opmode); rsr &= ~RSR_RSC_BANDWIDTH_40M; - if (sec_ch_above) + if (!sec_ch_above) rsr |= RSR_RSC_UPPER_SUB_CHANNEL; else rsr |= RSR_RSC_LOWER_SUB_CHANNEL; @@ -1321,9 +1321,8 @@ void rtl8xxxu_gen1_config_channel(struct ieee80211_hw *hw) for (i = RF_A; i < priv->rf_paths; i++) { val32 = rtl8xxxu_read_rfreg(priv, i, RF6052_REG_MODE_AG); - if (hw->conf.chandef.width == NL80211_CHAN_WIDTH_40) - val32 &= ~MODE_AG_CHANNEL_20MHZ; - else + val32 &= ~MODE_AG_BW_MASK; + if (hw->conf.chandef.width != NL80211_CHAN_WIDTH_40) val32 |= MODE_AG_CHANNEL_20MHZ; rtl8xxxu_write_rfreg(priv, i, RF6052_REG_MODE_AG, val32); } From c3ab9657866fc39a03ac8a797758924cfcd2c98c Mon Sep 17 00:00:00 2001 From: Johannes Berg Date: Fri, 21 Nov 2025 17:40:21 +0100 Subject: [PATCH 1341/3902] wifi: cfg80211: stop radar detection in cfg80211_leave() [ Upstream commit 9f33477b9a31a1edfe2df9f1a0359cccb0e16b4c ] If an interface is set down or, per the previous patch, changes type, radar detection for it should be cancelled. This is done for AP mode in mac80211 (somewhat needlessly, since cfg80211 can do it, but didn't until now), but wasn't handled for mesh, so if radar detection was started and then the interface set down or its type switched (the latter sometimes happning in the hwsim test 'mesh_peer_connected_dfs'), radar detection would be around with the interface unknown to the driver, later leading to some warnings around chanctx usage. Link: https://patch.msgid.link/20251121174021.290120e419e3.I2a5650c9062e29c988992dd8ce0d8eb570d23267@changeid Signed-off-by: Johannes Berg Signed-off-by: Sasha Levin --- net/wireless/core.c | 1 + net/wireless/core.h | 1 + net/wireless/mlme.c | 19 +++++++++++++++++++ 3 files changed, 21 insertions(+) diff --git a/net/wireless/core.c b/net/wireless/core.c index 54a34d8d356e04..5e5c1bc380a897 100644 --- a/net/wireless/core.c +++ b/net/wireless/core.c @@ -1365,6 +1365,7 @@ void cfg80211_leave(struct cfg80211_registered_device *rdev, cfg80211_pmsr_wdev_down(wdev); + cfg80211_stop_radar_detection(wdev); cfg80211_stop_background_radar_detection(wdev); switch (wdev->iftype) { diff --git a/net/wireless/core.h b/net/wireless/core.h index b6bd7f4d6385a1..d5d78752227af9 100644 --- a/net/wireless/core.h +++ b/net/wireless/core.h @@ -489,6 +489,7 @@ cfg80211_start_background_radar_detection(struct cfg80211_registered_device *rde struct wireless_dev *wdev, struct cfg80211_chan_def *chandef); +void cfg80211_stop_radar_detection(struct wireless_dev *wdev); void cfg80211_stop_background_radar_detection(struct wireless_dev *wdev); void cfg80211_background_cac_done_wk(struct work_struct *work); diff --git a/net/wireless/mlme.c b/net/wireless/mlme.c index 46394eb2086f6e..3fc175f9f86861 100644 --- a/net/wireless/mlme.c +++ b/net/wireless/mlme.c @@ -1295,6 +1295,25 @@ cfg80211_start_background_radar_detection(struct cfg80211_registered_device *rde return 0; } +void cfg80211_stop_radar_detection(struct wireless_dev *wdev) +{ + struct wiphy *wiphy = wdev->wiphy; + struct cfg80211_registered_device *rdev = wiphy_to_rdev(wiphy); + int link_id; + + for_each_valid_link(wdev, link_id) { + struct cfg80211_chan_def chandef; + + if (!wdev->links[link_id].cac_started) + continue; + + chandef = *wdev_chandef(wdev, link_id); + rdev_end_cac(rdev, wdev->netdev, link_id); + nl80211_radar_notify(rdev, &chandef, NL80211_RADAR_CAC_ABORTED, + wdev->netdev, GFP_KERNEL); + } +} + void cfg80211_stop_background_radar_detection(struct wireless_dev *wdev) { struct wiphy *wiphy = wdev->wiphy; From 0283fac52b42712196e23509283f634ffa6c3b65 Mon Sep 17 00:00:00 2001 From: Johannes Berg Date: Fri, 21 Nov 2025 17:40:20 +0100 Subject: [PATCH 1342/3902] wifi: cfg80211: use cfg80211_leave() in iftype change [ Upstream commit 7a27b73943a70ee226fa125327101fb18e94701d ] When changing the interface type, all activity on the interface has to be stopped first. This was done independent of existing code in cfg80211_leave(), so didn't handle e.g. background radar detection. Use cfg80211_leave() to handle it the same way. Note that cfg80211_leave() behaves slightly differently for IBSS in wireless extensions, it won't send an event in that case. We could handle that, but since nl80211 was used to change the type, IBSS is rare, and wext is already a corner case, it doesn't seem worth it. Link: https://patch.msgid.link/20251121174021.922ef48ce007.I970c8514252ef8a864a7fbdab9591b71031dee03@changeid Signed-off-by: Johannes Berg Signed-off-by: Sasha Levin --- net/wireless/util.c | 23 +---------------------- 1 file changed, 1 insertion(+), 22 deletions(-) diff --git a/net/wireless/util.c b/net/wireless/util.c index 56724b33af0455..4eb028ad168366 100644 --- a/net/wireless/util.c +++ b/net/wireless/util.c @@ -1203,28 +1203,7 @@ int cfg80211_change_iface(struct cfg80211_registered_device *rdev, dev->ieee80211_ptr->use_4addr = false; rdev_set_qos_map(rdev, dev, NULL); - switch (otype) { - case NL80211_IFTYPE_AP: - case NL80211_IFTYPE_P2P_GO: - cfg80211_stop_ap(rdev, dev, -1, true); - break; - case NL80211_IFTYPE_ADHOC: - cfg80211_leave_ibss(rdev, dev, false); - break; - case NL80211_IFTYPE_STATION: - case NL80211_IFTYPE_P2P_CLIENT: - cfg80211_disconnect(rdev, dev, - WLAN_REASON_DEAUTH_LEAVING, true); - break; - case NL80211_IFTYPE_MESH_POINT: - /* mesh should be handled? */ - break; - case NL80211_IFTYPE_OCB: - cfg80211_leave_ocb(rdev, dev); - break; - default: - break; - } + cfg80211_leave(rdev, dev->ieee80211_ptr); cfg80211_process_rdev_events(rdev); cfg80211_mlme_purge_registrations(dev->ieee80211_ptr); From 9c6f9b2a80ed7a877008491593eeddea17625b9c Mon Sep 17 00:00:00 2001 From: Quan Zhou Date: Tue, 18 Nov 2025 19:54:54 +0800 Subject: [PATCH 1343/3902] wifi: mt76: mt792x: fix wifi init fail by setting MCU_RUNNING after CLC load [ Upstream commit 066f417be5fd8c7fe581c5550206364735dad7a3 ] Set the MT76_STATE_MCU_RUNNING bit only after mt7921_load_clc() has successfully completed. Previously, the MCU_RUNNING state was set before loading CLC, which could cause conflict between chip mcu_init retry and mac_reset flow, result in chip init fail and chip abnormal status. By moving the state set after CLC load, firmware initialization becomes robust and resolves init fail issue. Signed-off-by: Quan Zhou Reviewed-by: druth@chromium.org Link: https://patch.msgid.link/19ec8e4465142e774f17801025accd0ae2214092.1763465933.git.quan.zhou@mediatek.com Signed-off-by: Felix Fietkau Signed-off-by: Sasha Levin --- drivers/net/wireless/mediatek/mt76/mt7921/mcu.c | 2 +- drivers/net/wireless/mediatek/mt76/mt7925/mcu.c | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/net/wireless/mediatek/mt76/mt7921/mcu.c b/drivers/net/wireless/mediatek/mt76/mt7921/mcu.c index 86bd33b916a9de..edc1df3c071e56 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7921/mcu.c +++ b/drivers/net/wireless/mediatek/mt76/mt7921/mcu.c @@ -646,10 +646,10 @@ int mt7921_run_firmware(struct mt792x_dev *dev) if (err) return err; - set_bit(MT76_STATE_MCU_RUNNING, &dev->mphy.state); err = mt7921_load_clc(dev, mt792x_ram_name(dev)); if (err) return err; + set_bit(MT76_STATE_MCU_RUNNING, &dev->mphy.state); return mt7921_mcu_fw_log_2_host(dev, 1); } diff --git a/drivers/net/wireless/mediatek/mt76/mt7925/mcu.c b/drivers/net/wireless/mediatek/mt76/mt7925/mcu.c index 8eda407e4135e0..c12b71b71cfc78 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7925/mcu.c +++ b/drivers/net/wireless/mediatek/mt76/mt7925/mcu.c @@ -1003,10 +1003,10 @@ int mt7925_run_firmware(struct mt792x_dev *dev) if (err) return err; - set_bit(MT76_STATE_MCU_RUNNING, &dev->mphy.state); err = mt7925_load_clc(dev, mt792x_ram_name(dev)); if (err) return err; + set_bit(MT76_STATE_MCU_RUNNING, &dev->mphy.state); return mt7925_mcu_fw_log_2_host(dev, 1); } From f5323b43019d8fb834f3aa1efe752303c09b2971 Mon Sep 17 00:00:00 2001 From: Hans de Goede Date: Mon, 3 Nov 2025 11:03:14 +0100 Subject: [PATCH 1344/3902] wifi: brcmfmac: Add DMI nvram filename quirk for Acer A1 840 tablet [ Upstream commit a8e5a110c0c38e08e5dd66356cd1156e91cf88e1 ] The Acer A1 840 tablet contains quite generic names in the sys_vendor and product_name DMI strings, without this patch brcmfmac will try to load: brcmfmac43340-sdio.Insyde-BayTrail.txt as nvram file which is a bit too generic. Add a DMI quirk so that a unique and clearly identifiable nvram file name is used on the Acer A1 840 tablet. Acked-by: Arend van Spriel Signed-off-by: Hans de Goede Link: https://patch.msgid.link/20251103100314.353826-1-hansg@kernel.org Signed-off-by: Johannes Berg Signed-off-by: Sasha Levin --- .../net/wireless/broadcom/brcm80211/brcmfmac/dmi.c | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/dmi.c b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/dmi.c index c3a602197662b7..abe7f6501e5ed0 100644 --- a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/dmi.c +++ b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/dmi.c @@ -24,6 +24,10 @@ static const struct brcmf_dmi_data acepc_t8_data = { BRCM_CC_4345_CHIP_ID, 6, "acepc-t8" }; +static const struct brcmf_dmi_data acer_a1_840_data = { + BRCM_CC_43340_CHIP_ID, 2, "acer-a1-840" +}; + /* The Chuwi Hi8 Pro uses the same Ampak AP6212 module as the Chuwi Vi8 Plus * and the nvram for the Vi8 Plus is already in linux-firmware, so use that. */ @@ -91,6 +95,16 @@ static const struct dmi_system_id dmi_platform_data[] = { }, .driver_data = (void *)&acepc_t8_data, }, + { + /* Acer Iconia One 8 A1-840 (non FHD version) */ + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "Insyde"), + DMI_MATCH(DMI_PRODUCT_NAME, "BayTrail"), + /* Above strings are too generic also match BIOS date */ + DMI_MATCH(DMI_BIOS_DATE, "04/01/2014"), + }, + .driver_data = (void *)&acer_a1_840_data, + }, { /* Chuwi Hi8 Pro with D2D3_Hi8Pro.233 BIOS */ .matches = { From 306d8449af7c8253664b74aa77ba45543eaaaec1 Mon Sep 17 00:00:00 2001 From: Qu Wenruo Date: Mon, 3 Nov 2025 12:51:09 +1030 Subject: [PATCH 1345/3902] btrfs: scrub: always update btrfs_scrub_progress::last_physical [ Upstream commit 54df8b80cc63aa0f22c4590cad11542731ed43ff ] [BUG] When a scrub failed immediately without any byte scrubbed, the returned btrfs_scrub_progress::last_physical will always be 0, even if there is a non-zero @start passed into btrfs_scrub_dev() for resume cases. This will reset the progress and make later scrub resume start from the beginning. [CAUSE] The function btrfs_scrub_dev() accepts a @progress parameter to copy its updated progress to the caller, there are cases where we either don't touch progress::last_physical at all or copy 0 into last_physical: - last_physical not updated at all If some error happened before scrubbing any super block or chunk, we will not copy the progress, leaving the @last_physical untouched. E.g. failed to allocate @sctx, scrubbing a missing device or even there is already a running scrub and so on. All those cases won't touch @progress at all, resulting the last_physical untouched and will be left as 0 for most cases. - Error out before scrubbing any bytes In those case we allocated @sctx, and sctx->stat.last_physical is all zero (initialized by kvzalloc()). Unfortunately some critical errors happened during scrub_enumerate_chunks() or scrub_supers() before any stripe is really scrubbed. In that case although we will copy sctx->stat back to @progress, since no byte is really scrubbed, last_physical will be overwritten to 0. [FIX] Make sure the parameter @progress always has its @last_physical member updated to @start parameter inside btrfs_scrub_dev(). At the very beginning of the function, set @progress->last_physical to @start, so that even if we error out without doing progress copying, last_physical is still at @start. Then after we got @sctx allocated, set sctx->stat.last_physical to @start, this will make sure even if we didn't get any byte scrubbed, at the progress copying stage the @last_physical is not left as zero. This should resolve the resume progress reset problem. Signed-off-by: Qu Wenruo Reviewed-by: David Sterba Signed-off-by: David Sterba Signed-off-by: Sasha Levin --- fs/btrfs/scrub.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/fs/btrfs/scrub.c b/fs/btrfs/scrub.c index 65361af3023436..b6a7ea105eb134 100644 --- a/fs/btrfs/scrub.c +++ b/fs/btrfs/scrub.c @@ -3039,6 +3039,10 @@ int btrfs_scrub_dev(struct btrfs_fs_info *fs_info, u64 devid, u64 start, unsigned int nofs_flag; bool need_commit = false; + /* Set the basic fallback @last_physical before we got a sctx. */ + if (progress) + progress->last_physical = start; + if (btrfs_fs_closing(fs_info)) return -EAGAIN; @@ -3057,6 +3061,7 @@ int btrfs_scrub_dev(struct btrfs_fs_info *fs_info, u64 devid, u64 start, sctx = scrub_setup_ctx(fs_info, is_dev_replace); if (IS_ERR(sctx)) return PTR_ERR(sctx); + sctx->stat.last_physical = start; ret = scrub_workers_get(fs_info); if (ret) From 66e2f3c1aefea54f80f9010f75815b668bbc50f1 Mon Sep 17 00:00:00 2001 From: Viacheslav Dubeyko Date: Wed, 19 Nov 2025 14:32:20 -0800 Subject: [PATCH 1346/3902] hfsplus: fix volume corruption issue for generic/101 [ Upstream commit 3f04ee216bc1406cb6214ceaa7e544114108e0fa ] The xfstests' test-case generic/101 leaves HFS+ volume in corrupted state: sudo ./check generic/101 FSTYP -- hfsplus PLATFORM -- Linux/x86_64 hfsplus-testing-0001 6.17.0-rc1+ #4 SMP PREEMPT_DYNAMIC Wed Oct 1 15:02:44 PDT 2025 MKFS_OPTIONS -- /dev/loop51 MOUNT_OPTIONS -- /dev/loop51 /mnt/scratch generic/101 _check_generic_filesystem: filesystem on /dev/loop51 is inconsistent (see XFSTESTS-2/xfstests-dev/results//generic/101.full for details) Ran: generic/101 Failures: generic/101 Failed 1 of 1 tests sudo fsck.hfsplus -d /dev/loop51 ** /dev/loop51 Using cacheBlockSize=32K cacheTotalBlock=1024 cacheSize=32768K. Executing fsck_hfs (version 540.1-Linux). ** Checking non-journaled HFS Plus Volume. The volume name is untitled ** Checking extents overflow file. ** Checking catalog file. ** Checking multi-linked files. ** Checking catalog hierarchy. ** Checking extended attributes file. ** Checking volume bitmap. ** Checking volume information. Invalid volume free block count (It should be 2614350 instead of 2614382) Verify Status: VIStat = 0x8000, ABTStat = 0x0000 EBTStat = 0x0000 CBTStat = 0x0000 CatStat = 0x00000000 ** Repairing volume. ** Rechecking volume. ** Checking non-journaled HFS Plus Volume. The volume name is untitled ** Checking extents overflow file. ** Checking catalog file. ** Checking multi-linked files. ** Checking catalog hierarchy. ** Checking extended attributes file. ** Checking volume bitmap. ** Checking volume information. ** The volume untitled was repaired successfully. This test executes such steps: "Test that if we truncate a file to a smaller size, then truncate it to its original size or a larger size, then fsyncing it and a power failure happens, the file will have the range [first_truncate_size, last_size[ with all bytes having a value of 0x00 if we read it the next time the filesystem is mounted.". HFS+ keeps volume's free block count in the superblock. However, hfsplus_file_fsync() doesn't store superblock's content. As a result, superblock contains not correct value of free blocks if a power failure happens. This patch adds functionality of saving superblock's content during hfsplus_file_fsync() call. sudo ./check generic/101 FSTYP -- hfsplus PLATFORM -- Linux/x86_64 hfsplus-testing-0001 6.18.0-rc3+ #96 SMP PREEMPT_DYNAMIC Wed Nov 19 12:47:37 PST 2025 MKFS_OPTIONS -- /dev/loop51 MOUNT_OPTIONS -- /dev/loop51 /mnt/scratch generic/101 32s ... 30s Ran: generic/101 Passed all 1 tests sudo fsck.hfsplus -d /dev/loop51 ** /dev/loop51 Using cacheBlockSize=32K cacheTotalBlock=1024 cacheSize=32768K. Executing fsck_hfs (version 540.1-Linux). ** Checking non-journaled HFS Plus Volume. The volume name is untitled ** Checking extents overflow file. ** Checking catalog file. ** Checking multi-linked files. ** Checking catalog hierarchy. ** Checking extended attributes file. ** Checking volume bitmap. ** Checking volume information. ** The volume untitled appears to be OK. Signed-off-by: Viacheslav Dubeyko cc: John Paul Adrian Glaubitz cc: Yangtao Li cc: linux-fsdevel@vger.kernel.org Link: https://lore.kernel.org/r/20251119223219.1824434-1-slava@dubeyko.com Signed-off-by: Viacheslav Dubeyko Signed-off-by: Sasha Levin --- fs/hfsplus/hfsplus_fs.h | 2 + fs/hfsplus/inode.c | 9 +++++ fs/hfsplus/super.c | 87 +++++++++++++++++++++++++---------------- 3 files changed, 65 insertions(+), 33 deletions(-) diff --git a/fs/hfsplus/hfsplus_fs.h b/fs/hfsplus/hfsplus_fs.h index 89e8b19c127b0a..de801942ae4713 100644 --- a/fs/hfsplus/hfsplus_fs.h +++ b/fs/hfsplus/hfsplus_fs.h @@ -477,6 +477,8 @@ int hfs_part_find(struct super_block *sb, sector_t *part_start, /* super.c */ struct inode *hfsplus_iget(struct super_block *sb, unsigned long ino); void hfsplus_mark_mdb_dirty(struct super_block *sb); +void hfsplus_prepare_volume_header_for_commit(struct hfsplus_vh *vhdr); +int hfsplus_commit_superblock(struct super_block *sb); /* tables.c */ extern u16 hfsplus_case_fold_table[]; diff --git a/fs/hfsplus/inode.c b/fs/hfsplus/inode.c index e290e417ed3a7c..7ae6745ca7ae12 100644 --- a/fs/hfsplus/inode.c +++ b/fs/hfsplus/inode.c @@ -325,6 +325,7 @@ int hfsplus_file_fsync(struct file *file, loff_t start, loff_t end, struct inode *inode = file->f_mapping->host; struct hfsplus_inode_info *hip = HFSPLUS_I(inode); struct hfsplus_sb_info *sbi = HFSPLUS_SB(inode->i_sb); + struct hfsplus_vh *vhdr = sbi->s_vhdr; int error = 0, error2; error = file_write_and_wait_range(file, start, end); @@ -368,6 +369,14 @@ int hfsplus_file_fsync(struct file *file, loff_t start, loff_t end, error = error2; } + mutex_lock(&sbi->vh_mutex); + hfsplus_prepare_volume_header_for_commit(vhdr); + mutex_unlock(&sbi->vh_mutex); + + error2 = hfsplus_commit_superblock(inode->i_sb); + if (!error) + error = error2; + if (!test_bit(HFSPLUS_SB_NOBARRIER, &sbi->flags)) blkdev_issue_flush(inode->i_sb->s_bdev); diff --git a/fs/hfsplus/super.c b/fs/hfsplus/super.c index 16bc4abc67e08f..67a7a2a093476d 100644 --- a/fs/hfsplus/super.c +++ b/fs/hfsplus/super.c @@ -187,40 +187,15 @@ static void hfsplus_evict_inode(struct inode *inode) } } -static int hfsplus_sync_fs(struct super_block *sb, int wait) +int hfsplus_commit_superblock(struct super_block *sb) { struct hfsplus_sb_info *sbi = HFSPLUS_SB(sb); struct hfsplus_vh *vhdr = sbi->s_vhdr; int write_backup = 0; - int error, error2; - - if (!wait) - return 0; + int error = 0, error2; hfs_dbg("starting...\n"); - /* - * Explicitly write out the special metadata inodes. - * - * While these special inodes are marked as hashed and written - * out peridocically by the flusher threads we redirty them - * during writeout of normal inodes, and thus the life lock - * prevents us from getting the latest state to disk. - */ - error = filemap_write_and_wait(sbi->cat_tree->inode->i_mapping); - error2 = filemap_write_and_wait(sbi->ext_tree->inode->i_mapping); - if (!error) - error = error2; - if (sbi->attr_tree) { - error2 = - filemap_write_and_wait(sbi->attr_tree->inode->i_mapping); - if (!error) - error = error2; - } - error2 = filemap_write_and_wait(sbi->alloc_file->i_mapping); - if (!error) - error = error2; - mutex_lock(&sbi->vh_mutex); mutex_lock(&sbi->alloc_mutex); vhdr->free_blocks = cpu_to_be32(sbi->free_blocks); @@ -249,11 +224,52 @@ static int hfsplus_sync_fs(struct super_block *sb, int wait) sbi->part_start + sbi->sect_count - 2, sbi->s_backup_vhdr_buf, NULL, REQ_OP_WRITE); if (!error) - error2 = error; + error = error2; out: mutex_unlock(&sbi->alloc_mutex); mutex_unlock(&sbi->vh_mutex); + hfs_dbg("finished: err %d\n", error); + + return error; +} + +static int hfsplus_sync_fs(struct super_block *sb, int wait) +{ + struct hfsplus_sb_info *sbi = HFSPLUS_SB(sb); + int error, error2; + + if (!wait) + return 0; + + hfs_dbg("starting...\n"); + + /* + * Explicitly write out the special metadata inodes. + * + * While these special inodes are marked as hashed and written + * out peridocically by the flusher threads we redirty them + * during writeout of normal inodes, and thus the life lock + * prevents us from getting the latest state to disk. + */ + error = filemap_write_and_wait(sbi->cat_tree->inode->i_mapping); + error2 = filemap_write_and_wait(sbi->ext_tree->inode->i_mapping); + if (!error) + error = error2; + if (sbi->attr_tree) { + error2 = + filemap_write_and_wait(sbi->attr_tree->inode->i_mapping); + if (!error) + error = error2; + } + error2 = filemap_write_and_wait(sbi->alloc_file->i_mapping); + if (!error) + error = error2; + + error2 = hfsplus_commit_superblock(sb); + if (!error) + error = error2; + if (!test_bit(HFSPLUS_SB_NOBARRIER, &sbi->flags)) blkdev_issue_flush(sb->s_bdev); @@ -395,6 +411,15 @@ static const struct super_operations hfsplus_sops = { .show_options = hfsplus_show_options, }; +void hfsplus_prepare_volume_header_for_commit(struct hfsplus_vh *vhdr) +{ + vhdr->last_mount_vers = cpu_to_be32(HFSP_MOUNT_VERSION); + vhdr->modify_date = hfsp_now2mt(); + be32_add_cpu(&vhdr->write_count, 1); + vhdr->attributes &= cpu_to_be32(~HFSPLUS_VOL_UNMNT); + vhdr->attributes |= cpu_to_be32(HFSPLUS_VOL_INCNSTNT); +} + static int hfsplus_fill_super(struct super_block *sb, struct fs_context *fc) { struct hfsplus_vh *vhdr; @@ -562,11 +587,7 @@ static int hfsplus_fill_super(struct super_block *sb, struct fs_context *fc) * H+LX == hfsplusutils, H+Lx == this driver, H+lx is unused * all three are registered with Apple for our use */ - vhdr->last_mount_vers = cpu_to_be32(HFSP_MOUNT_VERSION); - vhdr->modify_date = hfsp_now2mt(); - be32_add_cpu(&vhdr->write_count, 1); - vhdr->attributes &= cpu_to_be32(~HFSPLUS_VOL_UNMNT); - vhdr->attributes |= cpu_to_be32(HFSPLUS_VOL_INCNSTNT); + hfsplus_prepare_volume_header_for_commit(vhdr); hfsplus_sync_fs(sb, 1); if (!sbi->hidden_dir) { From a2046f5916c25c609db6a2c7e7d796f600a9eafe Mon Sep 17 00:00:00 2001 From: Andreas Gruenbacher Date: Wed, 19 Nov 2025 12:14:24 +0000 Subject: [PATCH 1347/3902] gfs2: fix remote evict for read-only filesystems [ Upstream commit 64c10ed9274bc46416f502afea48b4ae11279669 ] When a node tries to delete an inode, it first requests exclusive access to the iopen glock. This triggers demote requests on all remote nodes currently holding the iopen glock. To satisfy those requests, the remote nodes evict the inode in question, or they poke the corresponding inode glock to signal that the inode is still in active use. This behavior doesn't depend on whether or not a filesystem is read-only, so remove the incorrect read-only check. Signed-off-by: Andreas Gruenbacher Signed-off-by: Sasha Levin --- fs/gfs2/glops.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/fs/gfs2/glops.c b/fs/gfs2/glops.c index 0c0a80b3bacab2..0c68ab4432b08a 100644 --- a/fs/gfs2/glops.c +++ b/fs/gfs2/glops.c @@ -630,8 +630,7 @@ static void iopen_go_callback(struct gfs2_glock *gl, bool remote) struct gfs2_inode *ip = gl->gl_object; struct gfs2_sbd *sdp = gl->gl_name.ln_sbd; - if (!remote || sb_rdonly(sdp->sd_vfs) || - test_bit(SDF_KILL, &sdp->sd_flags)) + if (!remote || test_bit(SDF_KILL, &sdp->sd_flags)) return; if (gl->gl_demote_state == LM_ST_UNLOCKED && From 32c3960b421240534669f7bfb0c4ebb56dd799e7 Mon Sep 17 00:00:00 2001 From: Andreas Gruenbacher Date: Wed, 26 Nov 2025 23:27:14 +0000 Subject: [PATCH 1348/3902] gfs2: Fix "gfs2: Switch to wait_event in gfs2_quotad" [ Upstream commit dff1fb6d8b7abe5b1119fa060f5d6b3370bf10ac ] Commit e4a8b5481c59a ("gfs2: Switch to wait_event in gfs2_quotad") broke cyclic statfs syncing, so the numbers reported by "df" could easily get completely out of sync with reality. Fix this by reverting part of commit e4a8b5481c59a for now. A follow-up commit will clean this code up later. Signed-off-by: Andreas Gruenbacher Signed-off-by: Sasha Levin --- fs/gfs2/quota.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fs/gfs2/quota.c b/fs/gfs2/quota.c index 2298e06797ac38..f2df01f801b811 100644 --- a/fs/gfs2/quota.c +++ b/fs/gfs2/quota.c @@ -1616,7 +1616,7 @@ int gfs2_quotad(void *data) t = min(quotad_timeo, statfs_timeo); - t = wait_event_freezable_timeout(sdp->sd_quota_wait, + t -= wait_event_freezable_timeout(sdp->sd_quota_wait, sdp->sd_statfs_force_sync || gfs2_withdrawing_or_withdrawn(sdp) || kthread_should_stop(), From d8c5009bf41db126e284864748f2d0e947bc436c Mon Sep 17 00:00:00 2001 From: ChenXiaoSong Date: Fri, 17 Oct 2025 18:46:10 +0800 Subject: [PATCH 1349/3902] smb/server: fix return value of smb2_ioctl() [ Upstream commit 269df046c1e15ab34fa26fd90db9381f022a0963 ] __process_request() will not print error messages if smb2_ioctl() always returns 0. Fix this by returning the correct value at the end of function. Signed-off-by: ChenXiaoSong Acked-by: Namjae Jeon Signed-off-by: Steve French Signed-off-by: Sasha Levin --- fs/smb/server/smb2pdu.c | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/fs/smb/server/smb2pdu.c b/fs/smb/server/smb2pdu.c index f901ae18e68adf..a2830ec67e7823 100644 --- a/fs/smb/server/smb2pdu.c +++ b/fs/smb/server/smb2pdu.c @@ -8164,7 +8164,7 @@ int smb2_ioctl(struct ksmbd_work *work) id = req->VolatileFileId; if (req->Flags != cpu_to_le32(SMB2_0_IOCTL_IS_FSCTL)) { - rsp->hdr.Status = STATUS_NOT_SUPPORTED; + ret = -EOPNOTSUPP; goto out; } @@ -8184,8 +8184,9 @@ int smb2_ioctl(struct ksmbd_work *work) case FSCTL_DFS_GET_REFERRALS: case FSCTL_DFS_GET_REFERRALS_EX: /* Not support DFS yet */ + ret = -EOPNOTSUPP; rsp->hdr.Status = STATUS_FS_DRIVER_REQUIRED; - goto out; + goto out2; case FSCTL_CREATE_OR_GET_OBJECT_ID: { struct file_object_buf_type1_ioctl_rsp *obj_buf; @@ -8475,8 +8476,10 @@ int smb2_ioctl(struct ksmbd_work *work) rsp->hdr.Status = STATUS_BUFFER_TOO_SMALL; else if (ret < 0 || rsp->hdr.Status == 0) rsp->hdr.Status = STATUS_INVALID_PARAMETER; + +out2: smb2_set_err_rsp(work); - return 0; + return ret; } /** From 063cbbc6f595ea36ad146e1b7d2af820894beb21 Mon Sep 17 00:00:00 2001 From: Namjae Jeon Date: Tue, 18 Nov 2025 09:05:46 +0900 Subject: [PATCH 1350/3902] ksmbd: fix use-after-free in ksmbd_tree_connect_put under concurrency [ Upstream commit b39a1833cc4a2755b02603eec3a71a85e9dff926 ] Under high concurrency, A tree-connection object (tcon) is freed on a disconnect path while another path still holds a reference and later executes *_put()/write on it. Reported-by: Qianchang Zhao Reported-by: Zhitong Liu Signed-off-by: Namjae Jeon Signed-off-by: Steve French Signed-off-by: Sasha Levin --- fs/smb/server/mgmt/tree_connect.c | 18 ++++-------------- fs/smb/server/mgmt/tree_connect.h | 1 - fs/smb/server/smb2pdu.c | 3 --- 3 files changed, 4 insertions(+), 18 deletions(-) diff --git a/fs/smb/server/mgmt/tree_connect.c b/fs/smb/server/mgmt/tree_connect.c index ecfc5750867124..d3483d9c757c79 100644 --- a/fs/smb/server/mgmt/tree_connect.c +++ b/fs/smb/server/mgmt/tree_connect.c @@ -78,7 +78,6 @@ ksmbd_tree_conn_connect(struct ksmbd_work *work, const char *share_name) tree_conn->t_state = TREE_NEW; status.tree_conn = tree_conn; atomic_set(&tree_conn->refcount, 1); - init_waitqueue_head(&tree_conn->refcount_q); ret = xa_err(xa_store(&sess->tree_conns, tree_conn->id, tree_conn, KSMBD_DEFAULT_GFP)); @@ -100,14 +99,8 @@ ksmbd_tree_conn_connect(struct ksmbd_work *work, const char *share_name) void ksmbd_tree_connect_put(struct ksmbd_tree_connect *tcon) { - /* - * Checking waitqueue to releasing tree connect on - * tree disconnect. waitqueue_active is safe because it - * uses atomic operation for condition. - */ - if (!atomic_dec_return(&tcon->refcount) && - waitqueue_active(&tcon->refcount_q)) - wake_up(&tcon->refcount_q); + if (atomic_dec_and_test(&tcon->refcount)) + kfree(tcon); } int ksmbd_tree_conn_disconnect(struct ksmbd_session *sess, @@ -119,14 +112,11 @@ int ksmbd_tree_conn_disconnect(struct ksmbd_session *sess, xa_erase(&sess->tree_conns, tree_conn->id); write_unlock(&sess->tree_conns_lock); - if (!atomic_dec_and_test(&tree_conn->refcount)) - wait_event(tree_conn->refcount_q, - atomic_read(&tree_conn->refcount) == 0); - ret = ksmbd_ipc_tree_disconnect_request(sess->id, tree_conn->id); ksmbd_release_tree_conn_id(sess, tree_conn->id); ksmbd_share_config_put(tree_conn->share_conf); - kfree(tree_conn); + if (atomic_dec_and_test(&tree_conn->refcount)) + kfree(tree_conn); return ret; } diff --git a/fs/smb/server/mgmt/tree_connect.h b/fs/smb/server/mgmt/tree_connect.h index a42cdd05104114..f0023d86716f25 100644 --- a/fs/smb/server/mgmt/tree_connect.h +++ b/fs/smb/server/mgmt/tree_connect.h @@ -33,7 +33,6 @@ struct ksmbd_tree_connect { int maximal_access; bool posix_extensions; atomic_t refcount; - wait_queue_head_t refcount_q; unsigned int t_state; }; diff --git a/fs/smb/server/smb2pdu.c b/fs/smb/server/smb2pdu.c index a2830ec67e7823..dba29881debdc6 100644 --- a/fs/smb/server/smb2pdu.c +++ b/fs/smb/server/smb2pdu.c @@ -2200,7 +2200,6 @@ int smb2_tree_disconnect(struct ksmbd_work *work) goto err_out; } - WARN_ON_ONCE(atomic_dec_and_test(&tcon->refcount)); tcon->t_state = TREE_DISCONNECTED; write_unlock(&sess->tree_conns_lock); @@ -2210,8 +2209,6 @@ int smb2_tree_disconnect(struct ksmbd_work *work) goto err_out; } - work->tcon = NULL; - rsp->StructureSize = cpu_to_le16(4); err = ksmbd_iov_pin_rsp(work, rsp, sizeof(struct smb2_tree_disconnect_rsp)); From ee63729760f5b61a66f345c54dc4c7514e62383d Mon Sep 17 00:00:00 2001 From: Qianchang Zhao Date: Mon, 24 Nov 2025 16:05:09 +0900 Subject: [PATCH 1351/3902] ksmbd: vfs: fix race on m_flags in vfs_cache [ Upstream commit 991f8a79db99b14c48d20d2052c82d65b9186cad ] ksmbd maintains delete-on-close and pending-delete state in ksmbd_inode->m_flags. In vfs_cache.c this field is accessed under inconsistent locking: some paths read and modify m_flags under ci->m_lock while others do so without taking the lock at all. Examples: - ksmbd_query_inode_status() and __ksmbd_inode_close() use ci->m_lock when checking or updating m_flags. - ksmbd_inode_pending_delete(), ksmbd_set_inode_pending_delete(), ksmbd_clear_inode_pending_delete() and ksmbd_fd_set_delete_on_close() used to read and modify m_flags without ci->m_lock. This creates a potential data race on m_flags when multiple threads open, close and delete the same file concurrently. In the worst case delete-on-close and pending-delete bits can be lost or observed in an inconsistent state, leading to confusing delete semantics (files that stay on disk after delete-on-close, or files that disappear while still in use). Fix it by: - Making ksmbd_query_inode_status() look at m_flags under ci->m_lock after dropping inode_hash_lock. - Adding ci->m_lock protection to all helpers that read or modify m_flags (ksmbd_inode_pending_delete(), ksmbd_set_inode_pending_delete(), ksmbd_clear_inode_pending_delete(), ksmbd_fd_set_delete_on_close()). - Keeping the existing ci->m_lock protection in __ksmbd_inode_close(), and moving the actual unlink/xattr removal outside the lock. This unifies the locking around m_flags and removes the data race while preserving the existing delete-on-close behaviour. Reported-by: Qianchang Zhao Reported-by: Zhitong Liu Signed-off-by: Qianchang Zhao Acked-by: Namjae Jeon Signed-off-by: Steve French Signed-off-by: Sasha Levin --- fs/smb/server/vfs_cache.c | 88 +++++++++++++++++++++++++++------------ 1 file changed, 62 insertions(+), 26 deletions(-) diff --git a/fs/smb/server/vfs_cache.c b/fs/smb/server/vfs_cache.c index dfed6fce890498..6ef116585af645 100644 --- a/fs/smb/server/vfs_cache.c +++ b/fs/smb/server/vfs_cache.c @@ -112,40 +112,62 @@ int ksmbd_query_inode_status(struct dentry *dentry) read_lock(&inode_hash_lock); ci = __ksmbd_inode_lookup(dentry); - if (ci) { - ret = KSMBD_INODE_STATUS_OK; - if (ci->m_flags & (S_DEL_PENDING | S_DEL_ON_CLS)) - ret = KSMBD_INODE_STATUS_PENDING_DELETE; - atomic_dec(&ci->m_count); - } read_unlock(&inode_hash_lock); + if (!ci) + return ret; + + down_read(&ci->m_lock); + if (ci->m_flags & (S_DEL_PENDING | S_DEL_ON_CLS)) + ret = KSMBD_INODE_STATUS_PENDING_DELETE; + else + ret = KSMBD_INODE_STATUS_OK; + up_read(&ci->m_lock); + + atomic_dec(&ci->m_count); return ret; } bool ksmbd_inode_pending_delete(struct ksmbd_file *fp) { - return (fp->f_ci->m_flags & (S_DEL_PENDING | S_DEL_ON_CLS)); + struct ksmbd_inode *ci = fp->f_ci; + int ret; + + down_read(&ci->m_lock); + ret = (ci->m_flags & (S_DEL_PENDING | S_DEL_ON_CLS)); + up_read(&ci->m_lock); + + return ret; } void ksmbd_set_inode_pending_delete(struct ksmbd_file *fp) { - fp->f_ci->m_flags |= S_DEL_PENDING; + struct ksmbd_inode *ci = fp->f_ci; + + down_write(&ci->m_lock); + ci->m_flags |= S_DEL_PENDING; + up_write(&ci->m_lock); } void ksmbd_clear_inode_pending_delete(struct ksmbd_file *fp) { - fp->f_ci->m_flags &= ~S_DEL_PENDING; + struct ksmbd_inode *ci = fp->f_ci; + + down_write(&ci->m_lock); + ci->m_flags &= ~S_DEL_PENDING; + up_write(&ci->m_lock); } void ksmbd_fd_set_delete_on_close(struct ksmbd_file *fp, int file_info) { - if (ksmbd_stream_fd(fp)) { - fp->f_ci->m_flags |= S_DEL_ON_CLS_STREAM; - return; - } + struct ksmbd_inode *ci = fp->f_ci; - fp->f_ci->m_flags |= S_DEL_ON_CLS; + down_write(&ci->m_lock); + if (ksmbd_stream_fd(fp)) + ci->m_flags |= S_DEL_ON_CLS_STREAM; + else + ci->m_flags |= S_DEL_ON_CLS; + up_write(&ci->m_lock); } static void ksmbd_inode_hash(struct ksmbd_inode *ci) @@ -257,27 +279,41 @@ static void __ksmbd_inode_close(struct ksmbd_file *fp) struct file *filp; filp = fp->filp; - if (ksmbd_stream_fd(fp) && (ci->m_flags & S_DEL_ON_CLS_STREAM)) { - ci->m_flags &= ~S_DEL_ON_CLS_STREAM; - err = ksmbd_vfs_remove_xattr(file_mnt_idmap(filp), - &filp->f_path, - fp->stream.name, - true); - if (err) - pr_err("remove xattr failed : %s\n", - fp->stream.name); + + if (ksmbd_stream_fd(fp)) { + bool remove_stream_xattr = false; + + down_write(&ci->m_lock); + if (ci->m_flags & S_DEL_ON_CLS_STREAM) { + ci->m_flags &= ~S_DEL_ON_CLS_STREAM; + remove_stream_xattr = true; + } + up_write(&ci->m_lock); + + if (remove_stream_xattr) { + err = ksmbd_vfs_remove_xattr(file_mnt_idmap(filp), + &filp->f_path, + fp->stream.name, + true); + if (err) + pr_err("remove xattr failed : %s\n", + fp->stream.name); + } } if (atomic_dec_and_test(&ci->m_count)) { + bool do_unlink = false; + down_write(&ci->m_lock); if (ci->m_flags & (S_DEL_ON_CLS | S_DEL_PENDING)) { ci->m_flags &= ~(S_DEL_ON_CLS | S_DEL_PENDING); - up_write(&ci->m_lock); - ksmbd_vfs_unlink(filp); - down_write(&ci->m_lock); + do_unlink = true; } up_write(&ci->m_lock); + if (do_unlink) + ksmbd_vfs_unlink(filp); + ksmbd_inode_free(ci); } } From 112a000505b88e71024943399c518932c9c9768c Mon Sep 17 00:00:00 2001 From: Chingbin Li Date: Mon, 6 Oct 2025 16:46:47 +0800 Subject: [PATCH 1352/3902] Bluetooth: btusb: Add new VID/PID 2b89/6275 for RTL8761BUV [ Upstream commit 8dbbb5423c0802ec21266765de80fd491868fab1 ] Add VID 2b89 & PID 6275 for Realtek RTL8761BUV USB Bluetooth chip. The information in /sys/kernel/debug/usb/devices about the Bluetooth device is listed as the below. T: Bus=01 Lev=01 Prnt=01 Port=02 Cnt=01 Dev#= 6 Spd=12 MxCh= 0 D: Ver= 1.10 Cls=e0(wlcon) Sub=01 Prot=01 MxPS=64 #Cfgs= 1 P: Vendor=2b89 ProdID=6275 Rev= 2.00 S: Manufacturer=Realtek S: Product=Bluetooth Radio S: SerialNumber=00E04C239987 C:* #Ifs= 2 Cfg#= 1 Atr=e0 MxPwr=500mA I:* If#= 0 Alt= 0 #EPs= 3 Cls=e0(wlcon) Sub=01 Prot=01 Driver=btusb E: Ad=81(I) Atr=03(Int.) MxPS= 16 Ivl=1ms E: Ad=02(O) Atr=02(Bulk) MxPS= 64 Ivl=0ms E: Ad=82(I) Atr=02(Bulk) MxPS= 64 Ivl=0ms I:* If#= 1 Alt= 0 #EPs= 2 Cls=e0(wlcon) Sub=01 Prot=01 Driver=btusb E: Ad=03(O) Atr=01(Isoc) MxPS= 0 Ivl=1ms E: Ad=83(I) Atr=01(Isoc) MxPS= 0 Ivl=1ms I: If#= 1 Alt= 1 #EPs= 2 Cls=e0(wlcon) Sub=01 Prot=01 Driver=btusb E: Ad=03(O) Atr=01(Isoc) MxPS= 9 Ivl=1ms E: Ad=83(I) Atr=01(Isoc) MxPS= 9 Ivl=1ms I: If#= 1 Alt= 2 #EPs= 2 Cls=e0(wlcon) Sub=01 Prot=01 Driver=btusb E: Ad=03(O) Atr=01(Isoc) MxPS= 17 Ivl=1ms E: Ad=83(I) Atr=01(Isoc) MxPS= 17 Ivl=1ms I: If#= 1 Alt= 3 #EPs= 2 Cls=e0(wlcon) Sub=01 Prot=01 Driver=btusb E: Ad=03(O) Atr=01(Isoc) MxPS= 25 Ivl=1ms E: Ad=83(I) Atr=01(Isoc) MxPS= 25 Ivl=1ms I: If#= 1 Alt= 4 #EPs= 2 Cls=e0(wlcon) Sub=01 Prot=01 Driver=btusb E: Ad=03(O) Atr=01(Isoc) MxPS= 33 Ivl=1ms E: Ad=83(I) Atr=01(Isoc) MxPS= 33 Ivl=1ms I: If#= 1 Alt= 5 #EPs= 2 Cls=e0(wlcon) Sub=01 Prot=01 Driver=btusb E: Ad=03(O) Atr=01(Isoc) MxPS= 49 Ivl=1ms E: Ad=83(I) Atr=01(Isoc) MxPS= 49 Ivl=1ms Signed-off-by: Chingbin Li Signed-off-by: Luiz Augusto von Dentz Signed-off-by: Sasha Levin --- drivers/bluetooth/btusb.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/drivers/bluetooth/btusb.c b/drivers/bluetooth/btusb.c index fa683bb7f0b494..c70e79e69be8dc 100644 --- a/drivers/bluetooth/btusb.c +++ b/drivers/bluetooth/btusb.c @@ -781,6 +781,8 @@ static const struct usb_device_id quirks_table[] = { BTUSB_WIDEBAND_SPEECH }, { USB_DEVICE(0x2b89, 0x8761), .driver_info = BTUSB_REALTEK | BTUSB_WIDEBAND_SPEECH }, + { USB_DEVICE(0x2b89, 0x6275), .driver_info = BTUSB_REALTEK | + BTUSB_WIDEBAND_SPEECH }, /* Additional Realtek 8821AE Bluetooth devices */ { USB_DEVICE(0x0b05, 0x17dc), .driver_info = BTUSB_REALTEK }, From c5f173e20fdd7d44c16bc412c3d94f8bc0c5f632 Mon Sep 17 00:00:00 2001 From: Chris Lu Date: Wed, 15 Oct 2025 11:31:50 +0800 Subject: [PATCH 1353/3902] Bluetooth: btusb: MT7922: Add VID/PID 0489/e170 [ Upstream commit 5a6700a31c953af9a17a7e2681335f31d922614d ] Add VID 0489 & PID e170 for MediaTek MT7922 USB Bluetooth chip. The information in /sys/kernel/debug/usb/devices about the Bluetooth device is listed as the below. T: Bus=06 Lev=01 Prnt=01 Port=00 Cnt=01 Dev#= 2 Spd=480 MxCh= 0 D: Ver= 2.10 Cls=ef(misc ) Sub=02 Prot=01 MxPS=64 #Cfgs= 1 P: Vendor=0489 ProdID=e170 Rev= 1.00 S: Manufacturer=MediaTek Inc. S: Product=Wireless_Device S: SerialNumber=000000000 C:* #Ifs= 3 Cfg#= 1 Atr=e0 MxPwr=100mA A: FirstIf#= 0 IfCount= 3 Cls=e0(wlcon) Sub=01 Prot=01 I:* If#= 0 Alt= 0 #EPs= 3 Cls=e0(wlcon) Sub=01 Prot=01 Driver=btusb E: Ad=81(I) Atr=03(Int.) MxPS= 16 Ivl=125us E: Ad=82(I) Atr=02(Bulk) MxPS= 512 Ivl=0ms E: Ad=02(O) Atr=02(Bulk) MxPS= 512 Ivl=0ms I:* If#= 1 Alt= 0 #EPs= 2 Cls=e0(wlcon) Sub=01 Prot=01 Driver=btusb E: Ad=83(I) Atr=01(Isoc) MxPS= 0 Ivl=1ms E: Ad=03(O) Atr=01(Isoc) MxPS= 0 Ivl=1ms I: If#= 1 Alt= 1 #EPs= 2 Cls=e0(wlcon) Sub=01 Prot=01 Driver=btusb E: Ad=83(I) Atr=01(Isoc) MxPS= 9 Ivl=1ms E: Ad=03(O) Atr=01(Isoc) MxPS= 9 Ivl=1ms I: If#= 1 Alt= 2 #EPs= 2 Cls=e0(wlcon) Sub=01 Prot=01 Driver=btusb E: Ad=83(I) Atr=01(Isoc) MxPS= 17 Ivl=1ms E: Ad=03(O) Atr=01(Isoc) MxPS= 17 Ivl=1ms I: If#= 1 Alt= 3 #EPs= 2 Cls=e0(wlcon) Sub=01 Prot=01 Driver=btusb E: Ad=83(I) Atr=01(Isoc) MxPS= 25 Ivl=1ms E: Ad=03(O) Atr=01(Isoc) MxPS= 25 Ivl=1ms I: If#= 1 Alt= 4 #EPs= 2 Cls=e0(wlcon) Sub=01 Prot=01 Driver=btusb E: Ad=83(I) Atr=01(Isoc) MxPS= 33 Ivl=1ms E: Ad=03(O) Atr=01(Isoc) MxPS= 33 Ivl=1ms I: If#= 1 Alt= 5 #EPs= 2 Cls=e0(wlcon) Sub=01 Prot=01 Driver=btusb E: Ad=83(I) Atr=01(Isoc) MxPS= 49 Ivl=1ms E: Ad=03(O) Atr=01(Isoc) MxPS= 49 Ivl=1ms I: If#= 1 Alt= 6 #EPs= 2 Cls=e0(wlcon) Sub=01 Prot=01 Driver=btusb E: Ad=83(I) Atr=01(Isoc) MxPS= 63 Ivl=1ms E: Ad=03(O) Atr=01(Isoc) MxPS= 63 Ivl=1ms I:* If#= 2 Alt= 0 #EPs= 2 Cls=e0(wlcon) Sub=01 Prot=01 Driver=(none) E: Ad=8a(I) Atr=03(Int.) MxPS= 64 Ivl=125us E: Ad=0a(O) Atr=03(Int.) MxPS= 64 Ivl=125us I: If#= 2 Alt= 1 #EPs= 2 Cls=e0(wlcon) Sub=01 Prot=01 Driver=(none) E: Ad=8a(I) Atr=03(Int.) MxPS= 512 Ivl=125us E: Ad=0a(O) Atr=03(Int.) MxPS= 512 Ivl=125us Signed-off-by: Chris Lu Reviewed-by: Paul Menzel Signed-off-by: Luiz Augusto von Dentz Signed-off-by: Sasha Levin --- drivers/bluetooth/btusb.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/drivers/bluetooth/btusb.c b/drivers/bluetooth/btusb.c index c70e79e69be8dc..36f18f2657ab89 100644 --- a/drivers/bluetooth/btusb.c +++ b/drivers/bluetooth/btusb.c @@ -685,6 +685,8 @@ static const struct usb_device_id quirks_table[] = { BTUSB_WIDEBAND_SPEECH }, { USB_DEVICE(0x0489, 0xe153), .driver_info = BTUSB_MEDIATEK | BTUSB_WIDEBAND_SPEECH }, + { USB_DEVICE(0x0489, 0xe170), .driver_info = BTUSB_MEDIATEK | + BTUSB_WIDEBAND_SPEECH }, { USB_DEVICE(0x04ca, 0x3804), .driver_info = BTUSB_MEDIATEK | BTUSB_WIDEBAND_SPEECH }, { USB_DEVICE(0x04ca, 0x38e4), .driver_info = BTUSB_MEDIATEK | From a8c7343e2a044bb121ba76c005f6392120d199d0 Mon Sep 17 00:00:00 2001 From: Chris Lu Date: Wed, 15 Oct 2025 11:31:49 +0800 Subject: [PATCH 1354/3902] Bluetooth: btusb: MT7920: Add VID/PID 0489/e135 [ Upstream commit c126f98c011f5796ba118ef2093122d02809d30d ] Add VID 0489 & PID e135 for MediaTek MT7920 USB Bluetooth chip. The information in /sys/kernel/debug/usb/devices about the Bluetooth device is listed as the below. T: Bus=06 Lev=01 Prnt=01 Port=00 Cnt=01 Dev#= 2 Spd=480 MxCh= 0 D: Ver= 2.10 Cls=ef(misc ) Sub=02 Prot=01 MxPS=64 #Cfgs= 1 P: Vendor=0489 ProdID=e135 Rev= 1.00 S: Manufacturer=MediaTek Inc. S: Product=Wireless_Device S: SerialNumber=000000000 C:* #Ifs= 3 Cfg#= 1 Atr=e0 MxPwr=100mA A: FirstIf#= 0 IfCount= 3 Cls=e0(wlcon) Sub=01 Prot=01 I:* If#= 0 Alt= 0 #EPs= 3 Cls=e0(wlcon) Sub=01 Prot=01 Driver=btusb E: Ad=81(I) Atr=03(Int.) MxPS= 16 Ivl=125us E: Ad=82(I) Atr=02(Bulk) MxPS= 512 Ivl=0ms E: Ad=02(O) Atr=02(Bulk) MxPS= 512 Ivl=0ms I:* If#= 1 Alt= 0 #EPs= 2 Cls=e0(wlcon) Sub=01 Prot=01 Driver=btusb E: Ad=83(I) Atr=01(Isoc) MxPS= 0 Ivl=1ms E: Ad=03(O) Atr=01(Isoc) MxPS= 0 Ivl=1ms I: If#= 1 Alt= 1 #EPs= 2 Cls=e0(wlcon) Sub=01 Prot=01 Driver=btusb E: Ad=83(I) Atr=01(Isoc) MxPS= 9 Ivl=1ms E: Ad=03(O) Atr=01(Isoc) MxPS= 9 Ivl=1ms I: If#= 1 Alt= 2 #EPs= 2 Cls=e0(wlcon) Sub=01 Prot=01 Driver=btusb E: Ad=83(I) Atr=01(Isoc) MxPS= 17 Ivl=1ms E: Ad=03(O) Atr=01(Isoc) MxPS= 17 Ivl=1ms I: If#= 1 Alt= 3 #EPs= 2 Cls=e0(wlcon) Sub=01 Prot=01 Driver=btusb E: Ad=83(I) Atr=01(Isoc) MxPS= 25 Ivl=1ms E: Ad=03(O) Atr=01(Isoc) MxPS= 25 Ivl=1ms I: If#= 1 Alt= 4 #EPs= 2 Cls=e0(wlcon) Sub=01 Prot=01 Driver=btusb E: Ad=83(I) Atr=01(Isoc) MxPS= 33 Ivl=1ms E: Ad=03(O) Atr=01(Isoc) MxPS= 33 Ivl=1ms I: If#= 1 Alt= 5 #EPs= 2 Cls=e0(wlcon) Sub=01 Prot=01 Driver=btusb E: Ad=83(I) Atr=01(Isoc) MxPS= 49 Ivl=1ms E: Ad=03(O) Atr=01(Isoc) MxPS= 49 Ivl=1ms I: If#= 1 Alt= 6 #EPs= 2 Cls=e0(wlcon) Sub=01 Prot=01 Driver=btusb E: Ad=83(I) Atr=01(Isoc) MxPS= 63 Ivl=1ms E: Ad=03(O) Atr=01(Isoc) MxPS= 63 Ivl=1ms I:* If#= 2 Alt= 0 #EPs= 2 Cls=e0(wlcon) Sub=01 Prot=01 Driver=(none) E: Ad=8a(I) Atr=03(Int.) MxPS= 64 Ivl=125us E: Ad=0a(O) Atr=03(Int.) MxPS= 64 Ivl=125us I: If#= 2 Alt= 1 #EPs= 2 Cls=e0(wlcon) Sub=01 Prot=01 Driver=(none) E: Ad=8a(I) Atr=03(Int.) MxPS= 64 Ivl=125us E: Ad=0a(O) Atr=03(Int.) MxPS= 64 Ivl=125us Signed-off-by: Chris Lu Reviewed-by: Paul Menzel Signed-off-by: Luiz Augusto von Dentz Signed-off-by: Sasha Levin --- drivers/bluetooth/btusb.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/drivers/bluetooth/btusb.c b/drivers/bluetooth/btusb.c index 36f18f2657ab89..cc03c8c38b16fb 100644 --- a/drivers/bluetooth/btusb.c +++ b/drivers/bluetooth/btusb.c @@ -621,6 +621,8 @@ static const struct usb_device_id quirks_table[] = { /* Additional MediaTek MT7920 Bluetooth devices */ { USB_DEVICE(0x0489, 0xe134), .driver_info = BTUSB_MEDIATEK | BTUSB_WIDEBAND_SPEECH }, + { USB_DEVICE(0x0489, 0xe135), .driver_info = BTUSB_MEDIATEK | + BTUSB_WIDEBAND_SPEECH }, { USB_DEVICE(0x13d3, 0x3620), .driver_info = BTUSB_MEDIATEK | BTUSB_WIDEBAND_SPEECH }, { USB_DEVICE(0x13d3, 0x3621), .driver_info = BTUSB_MEDIATEK | From a35109f796db972cc6c7b087a9319a5918e2a5aa Mon Sep 17 00:00:00 2001 From: Shuai Zhang Date: Sun, 9 Nov 2025 17:24:37 +0800 Subject: [PATCH 1355/3902] Bluetooth: btusb: add new custom firmwares [ Upstream commit a8b38d19857d42a1f2e90c9d9b0f74de2500acd7 ] The new platform uses the QCA2066 chip along with a new board ID, which requires a dedicated firmware file to ensure proper initialization. Without this entry, the driver cannot locate and load the correct firmware, resulting in Bluetooth bring-up failure. This patch adds a new entry to the firmware table for QCA2066 so that the driver can correctly identify the board ID and load the appropriate firmware from 'qca/QCA2066/' in the linux-firmware repository. Signed-off-by: Shuai Zhang Acked-by: Dmitry Baryshkov Signed-off-by: Luiz Augusto von Dentz Signed-off-by: Sasha Levin --- drivers/bluetooth/btusb.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/bluetooth/btusb.c b/drivers/bluetooth/btusb.c index cc03c8c38b16fb..22f1932fe91269 100644 --- a/drivers/bluetooth/btusb.c +++ b/drivers/bluetooth/btusb.c @@ -3267,6 +3267,7 @@ static const struct qca_device_info qca_devices_table[] = { static const struct qca_custom_firmware qca_custom_btfws[] = { { 0x00130201, 0x030A, "QCA2066" }, + { 0x00130201, 0x030B, "QCA2066" }, { }, }; From 81b702a5486c9d4c569b95ded28be9950db08489 Mon Sep 17 00:00:00 2001 From: Gongwei Li Date: Wed, 19 Nov 2025 15:33:38 +0800 Subject: [PATCH 1356/3902] Bluetooth: btusb: Add new VID/PID 13d3/3533 for RTL8821CE [ Upstream commit 525459da4bd62a81142fea3f3d52188ceb4d8907 ] Add VID 13d3 & PID 3533 for Realtek RTL8821CE USB Bluetooth chip. The information in /sys/kernel/debug/usb/devices about the Bluetooth device is listed as the below. T: Bus=01 Lev=01 Prnt=01 Port=00 Cnt=01 Dev#= 2 Spd=12 MxCh= 0 D: Ver= 1.10 Cls=e0(wlcon) Sub=01 Prot=01 MxPS=64 #Cfgs= 1 P: Vendor=13d3 ProdID=3533 Rev= 1.10 S: Manufacturer=Realtek S: Product=Bluetooth Radio S: SerialNumber=00e04c000001 C:* #Ifs= 2 Cfg#= 1 Atr=e0 MxPwr=500mA I:* If#= 0 Alt= 0 #EPs= 3 Cls=e0(wlcon) Sub=01 Prot=01 Driver=btusb E: Ad=81(I) Atr=03(Int.) MxPS= 16 Ivl=1ms E: Ad=02(O) Atr=02(Bulk) MxPS= 64 Ivl=0ms E: Ad=82(I) Atr=02(Bulk) MxPS= 64 Ivl=0ms I:* If#= 1 Alt= 0 #EPs= 2 Cls=e0(wlcon) Sub=01 Prot=01 Driver=btusb E: Ad=03(O) Atr=01(Isoc) MxPS= 0 Ivl=1ms E: Ad=83(I) Atr=01(Isoc) MxPS= 0 Ivl=1ms I: If#= 1 Alt= 1 #EPs= 2 Cls=e0(wlcon) Sub=01 Prot=01 Driver=btusb E: Ad=03(O) Atr=01(Isoc) MxPS= 9 Ivl=1ms E: Ad=83(I) Atr=01(Isoc) MxPS= 9 Ivl=1ms I: If#= 1 Alt= 2 #EPs= 2 Cls=e0(wlcon) Sub=01 Prot=01 Driver=btusb E: Ad=03(O) Atr=01(Isoc) MxPS= 17 Ivl=1ms E: Ad=83(I) Atr=01(Isoc) MxPS= 17 Ivl=1ms I: If#= 1 Alt= 3 #EPs= 2 Cls=e0(wlcon) Sub=01 Prot=01 Driver=btusb E: Ad=03(O) Atr=01(Isoc) MxPS= 25 Ivl=1ms E: Ad=83(I) Atr=01(Isoc) MxPS= 25 Ivl=1ms I: If#= 1 Alt= 4 #EPs= 2 Cls=e0(wlcon) Sub=01 Prot=01 Driver=btusb E: Ad=03(O) Atr=01(Isoc) MxPS= 33 Ivl=1ms E: Ad=83(I) Atr=01(Isoc) MxPS= 33 Ivl=1ms I: If#= 1 Alt= 5 #EPs= 2 Cls=e0(wlcon) Sub=01 Prot=01 Driver=btusb E: Ad=03(O) Atr=01(Isoc) MxPS= 49 Ivl=1ms E: Ad=83(I) Atr=01(Isoc) MxPS= 49 Ivl=1ms Signed-off-by: Gongwei Li Signed-off-by: Luiz Augusto von Dentz Signed-off-by: Sasha Levin --- drivers/bluetooth/btusb.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/drivers/bluetooth/btusb.c b/drivers/bluetooth/btusb.c index 22f1932fe91269..bd26a8db64096e 100644 --- a/drivers/bluetooth/btusb.c +++ b/drivers/bluetooth/btusb.c @@ -504,6 +504,8 @@ static const struct usb_device_id quirks_table[] = { /* Realtek 8821CE Bluetooth devices */ { USB_DEVICE(0x13d3, 0x3529), .driver_info = BTUSB_REALTEK | BTUSB_WIDEBAND_SPEECH }, + { USB_DEVICE(0x13d3, 0x3533), .driver_info = BTUSB_REALTEK | + BTUSB_WIDEBAND_SPEECH }, /* Realtek 8822CE Bluetooth devices */ { USB_DEVICE(0x0bda, 0xb00c), .driver_info = BTUSB_REALTEK | From e84d2d5f3680148882b5b40914dc768819b90e29 Mon Sep 17 00:00:00 2001 From: Max Chou Date: Wed, 5 Nov 2025 13:50:39 +0800 Subject: [PATCH 1357/3902] Bluetooth: btusb: Add new VID/PID 0x0489/0xE12F for RTL8852BE-VT [ Upstream commit 32caa197b9b603e20f49fd3a0dffecd0cd620499 ] Add the support ID(0x0489, 0xE12F) to usb_device_id table for Realtek RTL8852BE-VT. The device info from /sys/kernel/debug/usb/devices as below. T: Bus=04 Lev=02 Prnt=02 Port=05 Cnt=01 Dev#= 86 Spd=12 MxCh= 0 D: Ver= 1.00 Cls=e0(wlcon) Sub=01 Prot=01 MxPS=64 #Cfgs= 1 P: Vendor=0489 ProdID=e12f Rev= 0.00 S: Manufacturer=Realtek S: Product=Bluetooth Radio S: SerialNumber=00e04c000001 C:* #Ifs= 2 Cfg#= 1 Atr=e0 MxPwr=500mA I:* If#= 0 Alt= 0 #EPs= 3 Cls=e0(wlcon) Sub=01 Prot=01 Driver=btusb E: Ad=81(I) Atr=03(Int.) MxPS= 16 Ivl=1ms E: Ad=02(O) Atr=02(Bulk) MxPS= 64 Ivl=0ms E: Ad=82(I) Atr=02(Bulk) MxPS= 64 Ivl=0ms I:* If#= 1 Alt= 0 #EPs= 2 Cls=e0(wlcon) Sub=01 Prot=01 Driver=btusb E: Ad=03(O) Atr=01(Isoc) MxPS= 0 Ivl=1ms E: Ad=83(I) Atr=01(Isoc) MxPS= 0 Ivl=1ms I: If#= 1 Alt= 1 #EPs= 2 Cls=e0(wlcon) Sub=01 Prot=01 Driver=btusb E: Ad=03(O) Atr=01(Isoc) MxPS= 9 Ivl=1ms E: Ad=83(I) Atr=01(Isoc) MxPS= 9 Ivl=1ms I: If#= 1 Alt= 2 #EPs= 2 Cls=e0(wlcon) Sub=01 Prot=01 Driver=btusb E: Ad=03(O) Atr=01(Isoc) MxPS= 17 Ivl=1ms E: Ad=83(I) Atr=01(Isoc) MxPS= 17 Ivl=1ms I: If#= 1 Alt= 3 #EPs= 2 Cls=e0(wlcon) Sub=01 Prot=01 Driver=btusb E: Ad=03(O) Atr=01(Isoc) MxPS= 25 Ivl=1ms E: Ad=83(I) Atr=01(Isoc) MxPS= 25 Ivl=1ms I: If#= 1 Alt= 4 #EPs= 2 Cls=e0(wlcon) Sub=01 Prot=01 Driver=btusb E: Ad=03(O) Atr=01(Isoc) MxPS= 33 Ivl=1ms E: Ad=83(I) Atr=01(Isoc) MxPS= 33 Ivl=1ms I: If#= 1 Alt= 5 #EPs= 2 Cls=e0(wlcon) Sub=01 Prot=01 Driver=btusb E: Ad=03(O) Atr=01(Isoc) MxPS= 49 Ivl=1ms E: Ad=83(I) Atr=01(Isoc) MxPS= 49 Ivl=1ms Signed-off-by: Max Chou Signed-off-by: Luiz Augusto von Dentz Signed-off-by: Sasha Levin --- drivers/bluetooth/btusb.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/drivers/bluetooth/btusb.c b/drivers/bluetooth/btusb.c index bd26a8db64096e..b92bfd131567ea 100644 --- a/drivers/bluetooth/btusb.c +++ b/drivers/bluetooth/btusb.c @@ -587,6 +587,8 @@ static const struct usb_device_id quirks_table[] = { /* Realtek 8852BT/8852BE-VT Bluetooth devices */ { USB_DEVICE(0x0bda, 0x8520), .driver_info = BTUSB_REALTEK | BTUSB_WIDEBAND_SPEECH }, + { USB_DEVICE(0x0489, 0xe12f), .driver_info = BTUSB_REALTEK | + BTUSB_WIDEBAND_SPEECH }, /* Realtek 8922AE Bluetooth devices */ { USB_DEVICE(0x0bda, 0x8922), .driver_info = BTUSB_REALTEK | From 6938e2fb4b4bbeb28112e716f749ef91600e2ff1 Mon Sep 17 00:00:00 2001 From: Andreas Gruenbacher Date: Sun, 30 Nov 2025 21:19:52 +0000 Subject: [PATCH 1358/3902] gfs2: Fix use of bio_chain [ Upstream commit 8a157e0a0aa5143b5d94201508c0ca1bb8cfb941 ] In gfs2_chain_bio(), the call to bio_chain() has its arguments swapped. The result is leaked bios and incorrect synchronization (only the last bio will actually be waited for). This code is only used during mount and filesystem thaw, so the bug normally won't be noticeable. Reported-by: Stephen Zhang Signed-off-by: Andreas Gruenbacher Signed-off-by: Sasha Levin --- fs/gfs2/lops.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fs/gfs2/lops.c b/fs/gfs2/lops.c index 9c8c305a75c468..914d03f6c4e829 100644 --- a/fs/gfs2/lops.c +++ b/fs/gfs2/lops.c @@ -487,7 +487,7 @@ static struct bio *gfs2_chain_bio(struct bio *prev, unsigned int nr_iovecs) new = bio_alloc(prev->bi_bdev, nr_iovecs, prev->bi_opf, GFP_NOIO); bio_clone_blkg_association(new, prev); new->bi_iter.bi_sector = bio_end_sector(prev); - bio_chain(new, prev); + bio_chain(prev, new); submit_bio(prev); return new; } From ef4943d8c55c561a6c5a5370d7a95bf765cb59b2 Mon Sep 17 00:00:00 2001 From: Wei Fang Date: Fri, 28 Nov 2025 10:59:15 +0800 Subject: [PATCH 1359/3902] net: fec: ERR007885 Workaround for XDP TX path [ Upstream commit e8e032cd24dda7cceaa27bc2eb627f82843f0466 ] The ERR007885 will lead to a TDAR race condition for mutliQ when the driver sets TDAR and the UDMA clears TDAR simultaneously or in a small window (2-4 cycles). And it will cause the udma_tx and udma_tx_arbiter state machines to hang. Therefore, the commit 53bb20d1faba ("net: fec: add variable reg_desc_active to speed things up") and the commit a179aad12bad ("net: fec: ERR007885 Workaround for conventional TX") have added the workaround to fix the potential issue for the conventional TX path. Similarly, the XDP TX path should also have the potential hang issue, so add the workaround for XDP TX path. Fixes: 6d6b39f180b8 ("net: fec: add initial XDP support") Signed-off-by: Wei Fang Link: https://patch.msgid.link/20251128025915.2486943-1-wei.fang@nxp.com Signed-off-by: Paolo Abeni Signed-off-by: Sasha Levin --- drivers/net/ethernet/freescale/fec_main.c | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/drivers/net/ethernet/freescale/fec_main.c b/drivers/net/ethernet/freescale/fec_main.c index 3222359ac15b77..e2b75d1970ae6f 100644 --- a/drivers/net/ethernet/freescale/fec_main.c +++ b/drivers/net/ethernet/freescale/fec_main.c @@ -3948,7 +3948,12 @@ static int fec_enet_txq_xmit_frame(struct fec_enet_private *fep, txq->bd.cur = bdp; /* Trigger transmission start */ - writel(0, txq->bd.reg_desc_active); + if (!(fep->quirks & FEC_QUIRK_ERR007885) || + !readl(txq->bd.reg_desc_active) || + !readl(txq->bd.reg_desc_active) || + !readl(txq->bd.reg_desc_active) || + !readl(txq->bd.reg_desc_active)) + writel(0, txq->bd.reg_desc_active); return 0; } From 51f5fbc1681bdcffcc7d18bf3dfdb2b1278d3977 Mon Sep 17 00:00:00 2001 From: Wang Liang Date: Sat, 29 Nov 2025 12:13:15 +0800 Subject: [PATCH 1360/3902] netrom: Fix memory leak in nr_sendmsg() [ Upstream commit 613d12dd794e078be8ff3cf6b62a6b9acf7f4619 ] syzbot reported a memory leak [1]. When function sock_alloc_send_skb() return NULL in nr_output(), the original skb is not freed, which was allocated in nr_sendmsg(). Fix this by freeing it before return. [1] BUG: memory leak unreferenced object 0xffff888129f35500 (size 240): comm "syz.0.17", pid 6119, jiffies 4294944652 hex dump (first 32 bytes): 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 00 00 00 00 00 00 00 00 00 10 52 28 81 88 ff ff ..........R(.... backtrace (crc 1456a3e4): kmemleak_alloc_recursive include/linux/kmemleak.h:44 [inline] slab_post_alloc_hook mm/slub.c:4983 [inline] slab_alloc_node mm/slub.c:5288 [inline] kmem_cache_alloc_node_noprof+0x36f/0x5e0 mm/slub.c:5340 __alloc_skb+0x203/0x240 net/core/skbuff.c:660 alloc_skb include/linux/skbuff.h:1383 [inline] alloc_skb_with_frags+0x69/0x3f0 net/core/skbuff.c:6671 sock_alloc_send_pskb+0x379/0x3e0 net/core/sock.c:2965 sock_alloc_send_skb include/net/sock.h:1859 [inline] nr_sendmsg+0x287/0x450 net/netrom/af_netrom.c:1105 sock_sendmsg_nosec net/socket.c:727 [inline] __sock_sendmsg net/socket.c:742 [inline] sock_write_iter+0x293/0x2a0 net/socket.c:1195 new_sync_write fs/read_write.c:593 [inline] vfs_write+0x45d/0x710 fs/read_write.c:686 ksys_write+0x143/0x170 fs/read_write.c:738 do_syscall_x64 arch/x86/entry/syscall_64.c:63 [inline] do_syscall_64+0xa4/0xfa0 arch/x86/entry/syscall_64.c:94 entry_SYSCALL_64_after_hwframe+0x77/0x7f Reported-by: syzbot+d7abc36bbbb6d7d40b58@syzkaller.appspotmail.com Closes: https://syzkaller.appspot.com/bug?extid=d7abc36bbbb6d7d40b58 Tested-by: syzbot+d7abc36bbbb6d7d40b58@syzkaller.appspotmail.com Fixes: 1da177e4c3f4 ("Linux-2.6.12-rc2") Signed-off-by: Wang Liang Link: https://patch.msgid.link/20251129041315.1550766-1-wangliang74@huawei.com Signed-off-by: Paolo Abeni Signed-off-by: Sasha Levin --- net/netrom/nr_out.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/net/netrom/nr_out.c b/net/netrom/nr_out.c index 5e531394a724b7..2b3cbceb0b52d5 100644 --- a/net/netrom/nr_out.c +++ b/net/netrom/nr_out.c @@ -43,8 +43,10 @@ void nr_output(struct sock *sk, struct sk_buff *skb) frontlen = skb_headroom(skb); while (skb->len > 0) { - if ((skbn = sock_alloc_send_skb(sk, frontlen + NR_MAX_PACKET_SIZE, 0, &err)) == NULL) + if ((skbn = sock_alloc_send_skb(sk, frontlen + NR_MAX_PACKET_SIZE, 0, &err)) == NULL) { + kfree_skb(skb); return; + } skb_reserve(skbn, frontlen); From 45466141da3c98a0c5fa88be0bc14b4b6a4bd75c Mon Sep 17 00:00:00 2001 From: Jamal Hadi Salim Date: Fri, 28 Nov 2025 10:19:19 -0500 Subject: [PATCH 1361/3902] net/sched: ets: Always remove class from active list before deleting in ets_qdisc_change [ Upstream commit ce052b9402e461a9aded599f5b47e76bc727f7de ] zdi-disclosures@trendmicro.com says: The vulnerability is a race condition between `ets_qdisc_dequeue` and `ets_qdisc_change`. It leads to UAF on `struct Qdisc` object. Attacker requires the capability to create new user and network namespace in order to trigger the bug. See my additional commentary at the end of the analysis. Analysis: static int ets_qdisc_change(struct Qdisc *sch, struct nlattr *opt, struct netlink_ext_ack *extack) { ... // (1) this lock is preventing .change handler (`ets_qdisc_change`) //to race with .dequeue handler (`ets_qdisc_dequeue`) sch_tree_lock(sch); for (i = nbands; i < oldbands; i++) { if (i >= q->nstrict && q->classes[i].qdisc->q.qlen) list_del_init(&q->classes[i].alist); qdisc_purge_queue(q->classes[i].qdisc); } WRITE_ONCE(q->nbands, nbands); for (i = nstrict; i < q->nstrict; i++) { if (q->classes[i].qdisc->q.qlen) { // (2) the class is added to the q->active list_add_tail(&q->classes[i].alist, &q->active); q->classes[i].deficit = quanta[i]; } } WRITE_ONCE(q->nstrict, nstrict); memcpy(q->prio2band, priomap, sizeof(priomap)); for (i = 0; i < q->nbands; i++) WRITE_ONCE(q->classes[i].quantum, quanta[i]); for (i = oldbands; i < q->nbands; i++) { q->classes[i].qdisc = queues[i]; if (q->classes[i].qdisc != &noop_qdisc) qdisc_hash_add(q->classes[i].qdisc, true); } // (3) the qdisc is unlocked, now dequeue can be called in parallel // to the rest of .change handler sch_tree_unlock(sch); ets_offload_change(sch); for (i = q->nbands; i < oldbands; i++) { // (4) we're reducing the refcount for our class's qdisc and // freeing it qdisc_put(q->classes[i].qdisc); // (5) If we call .dequeue between (4) and (5), we will have // a strong UAF and we can control RIP q->classes[i].qdisc = NULL; WRITE_ONCE(q->classes[i].quantum, 0); q->classes[i].deficit = 0; gnet_stats_basic_sync_init(&q->classes[i].bstats); memset(&q->classes[i].qstats, 0, sizeof(q->classes[i].qstats)); } return 0; } Comment: This happens because some of the classes have their qdiscs assigned to NULL, but remain in the active list. This commit fixes this issue by always removing the class from the active list before deleting and freeing its associated qdisc Reproducer Steps (trimmed version of what was sent by zdi-disclosures@trendmicro.com) ``` DEV="${DEV:-lo}" ROOT_HANDLE="${ROOT_HANDLE:-1:}" BAND2_HANDLE="${BAND2_HANDLE:-20:}" # child under 1:2 PING_BYTES="${PING_BYTES:-48}" PING_COUNT="${PING_COUNT:-200000}" PING_DST="${PING_DST:-127.0.0.1}" SLOW_TBF_RATE="${SLOW_TBF_RATE:-8bit}" SLOW_TBF_BURST="${SLOW_TBF_BURST:-100b}" SLOW_TBF_LAT="${SLOW_TBF_LAT:-1s}" cleanup() { tc qdisc del dev "$DEV" root 2>/dev/null } trap cleanup EXIT ip link set "$DEV" up tc qdisc del dev "$DEV" root 2>/dev/null || true tc qdisc add dev "$DEV" root handle "$ROOT_HANDLE" ets bands 2 strict 2 tc qdisc add dev "$DEV" parent 1:2 handle "$BAND2_HANDLE" \ tbf rate "$SLOW_TBF_RATE" burst "$SLOW_TBF_BURST" latency "$SLOW_TBF_LAT" tc filter add dev "$DEV" parent 1: protocol all prio 1 u32 match u32 0 0 flowid 1:2 tc -s qdisc ls dev $DEV ping -I "$DEV" -f -c "$PING_COUNT" -s "$PING_BYTES" -W 0.001 "$PING_DST" \ >/dev/null 2>&1 & tc qdisc change dev "$DEV" root handle "$ROOT_HANDLE" ets bands 2 strict 0 tc qdisc change dev "$DEV" root handle "$ROOT_HANDLE" ets bands 2 strict 2 tc -s qdisc ls dev $DEV tc qdisc del dev "$DEV" parent 1:2 || true tc -s qdisc ls dev $DEV tc qdisc change dev "$DEV" root handle "$ROOT_HANDLE" ets bands 1 strict 1 ``` KASAN report ``` ================================================================== BUG: KASAN: slab-use-after-free in ets_qdisc_dequeue+0x1071/0x11b0 kernel/net/sched/sch_ets.c:481 Read of size 8 at addr ffff8880502fc018 by task ping/12308 > CPU: 0 UID: 0 PID: 12308 Comm: ping Not tainted 6.18.0-rc4-dirty #1 PREEMPT(full) Hardware name: QEMU Ubuntu 25.04 PC (i440FX + PIIX, 1996), BIOS 1.16.3-debian-1.16.3-2 04/01/2014 Call Trace: __dump_stack kernel/lib/dump_stack.c:94 dump_stack_lvl+0x100/0x190 kernel/lib/dump_stack.c:120 print_address_description kernel/mm/kasan/report.c:378 print_report+0x156/0x4c9 kernel/mm/kasan/report.c:482 kasan_report+0xdf/0x110 kernel/mm/kasan/report.c:595 ets_qdisc_dequeue+0x1071/0x11b0 kernel/net/sched/sch_ets.c:481 dequeue_skb kernel/net/sched/sch_generic.c:294 qdisc_restart kernel/net/sched/sch_generic.c:399 __qdisc_run+0x1c9/0x1b00 kernel/net/sched/sch_generic.c:417 __dev_xmit_skb kernel/net/core/dev.c:4221 __dev_queue_xmit+0x2848/0x4410 kernel/net/core/dev.c:4729 dev_queue_xmit kernel/./include/linux/netdevice.h:3365 [...] Allocated by task 17115: kasan_save_stack+0x30/0x50 kernel/mm/kasan/common.c:56 kasan_save_track+0x14/0x30 kernel/mm/kasan/common.c:77 poison_kmalloc_redzone kernel/mm/kasan/common.c:400 __kasan_kmalloc+0xaa/0xb0 kernel/mm/kasan/common.c:417 kasan_kmalloc kernel/./include/linux/kasan.h:262 __do_kmalloc_node kernel/mm/slub.c:5642 __kmalloc_node_noprof+0x34e/0x990 kernel/mm/slub.c:5648 kmalloc_node_noprof kernel/./include/linux/slab.h:987 qdisc_alloc+0xb8/0xc30 kernel/net/sched/sch_generic.c:950 qdisc_create_dflt+0x93/0x490 kernel/net/sched/sch_generic.c:1012 ets_class_graft+0x4fd/0x800 kernel/net/sched/sch_ets.c:261 qdisc_graft+0x3e4/0x1780 kernel/net/sched/sch_api.c:1196 [...] Freed by task 9905: kasan_save_stack+0x30/0x50 kernel/mm/kasan/common.c:56 kasan_save_track+0x14/0x30 kernel/mm/kasan/common.c:77 __kasan_save_free_info+0x3b/0x70 kernel/mm/kasan/generic.c:587 kasan_save_free_info kernel/mm/kasan/kasan.h:406 poison_slab_object kernel/mm/kasan/common.c:252 __kasan_slab_free+0x5f/0x80 kernel/mm/kasan/common.c:284 kasan_slab_free kernel/./include/linux/kasan.h:234 slab_free_hook kernel/mm/slub.c:2539 slab_free kernel/mm/slub.c:6630 kfree+0x144/0x700 kernel/mm/slub.c:6837 rcu_do_batch kernel/kernel/rcu/tree.c:2605 rcu_core+0x7c0/0x1500 kernel/kernel/rcu/tree.c:2861 handle_softirqs+0x1ea/0x8a0 kernel/kernel/softirq.c:622 __do_softirq kernel/kernel/softirq.c:656 [...] Commentary: 1. Maher Azzouzi working with Trend Micro Zero Day Initiative was reported as the person who found the issue. I requested to get a proper email to add to the reported-by tag but got no response. For this reason i will credit the person i exchanged emails with i.e zdi-disclosures@trendmicro.com 2. Neither i nor Victor who did a much more thorough testing was able to reproduce a UAF with the PoC or other approaches we tried. We were both able to reproduce a null ptr deref. After exchange with zdi-disclosures@trendmicro.com they sent a small change to be made to the code to add an extra delay which was able to simulate the UAF. i.e, this: qdisc_put(q->classes[i].qdisc); mdelay(90); q->classes[i].qdisc = NULL; I was informed by Thomas Gleixner(tglx@linutronix.de) that adding delays was acceptable approach for demonstrating the bug, quote: "Adding such delays is common exploit validation practice" The equivalent delay could happen "by virt scheduling the vCPU out, SMIs, NMIs, PREEMPT_RT enabled kernel" 3. I asked the OP to test and report back but got no response and after a few days gave up and proceeded to submit this fix. Fixes: de6d25924c2a ("net/sched: sch_ets: don't peek at classes beyond 'nbands'") Reported-by: zdi-disclosures@trendmicro.com Tested-by: Victor Nogueira Signed-off-by: Jamal Hadi Salim Reviewed-by: Davide Caratti Link: https://patch.msgid.link/20251128151919.576920-1-jhs@mojatatu.com Signed-off-by: Paolo Abeni Signed-off-by: Sasha Levin --- net/sched/sch_ets.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/net/sched/sch_ets.c b/net/sched/sch_ets.c index 82635dd2cfa59f..ae46643e596d30 100644 --- a/net/sched/sch_ets.c +++ b/net/sched/sch_ets.c @@ -652,7 +652,7 @@ static int ets_qdisc_change(struct Qdisc *sch, struct nlattr *opt, sch_tree_lock(sch); for (i = nbands; i < oldbands; i++) { - if (i >= q->nstrict && q->classes[i].qdisc->q.qlen) + if (cl_is_active(&q->classes[i])) list_del_init(&q->classes[i].alist); qdisc_purge_queue(q->classes[i].qdisc); } From f4a16bb7f8b5bf0074ad1707ecf9c76acd7e2ff1 Mon Sep 17 00:00:00 2001 From: Moshe Shemesh Date: Mon, 1 Dec 2025 17:13:27 +0200 Subject: [PATCH 1362/3902] net/mlx5: make enable_mpesw idempotent [ Upstream commit cd7671ef4cf2edf73cd2a3dca3a2f522a4525bf5 ] The enable_mpesw() function returns -EINVAL if ldev->mode is not MLX5_LAG_MODE_NONE. This means attempting to enable MPESW mode when it's already enabled will fail. In contrast, disable_mpesw() properly checks if the mode is MLX5_LAG_MODE_MPESW before proceeding, making it naturally idempotent and safe to call multiple times. Fix enable_mpesw() to return success if mpesw is already enabled. Fixes: a32327a3a02c ("net/mlx5: Lag, Control MultiPort E-Switch single FDB mode") Signed-off-by: Moshe Shemesh Reviewed-by: Shay Drori Signed-off-by: Tariq Toukan Reviewed-by: Simon Horman Link: https://patch.msgid.link/1764602008-1334866-2-git-send-email-tariqt@nvidia.com Signed-off-by: Paolo Abeni Signed-off-by: Sasha Levin --- drivers/net/ethernet/mellanox/mlx5/core/lag/mpesw.c | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/drivers/net/ethernet/mellanox/mlx5/core/lag/mpesw.c b/drivers/net/ethernet/mellanox/mlx5/core/lag/mpesw.c index aad52d3a90e68b..2d86af8f0d9b81 100644 --- a/drivers/net/ethernet/mellanox/mlx5/core/lag/mpesw.c +++ b/drivers/net/ethernet/mellanox/mlx5/core/lag/mpesw.c @@ -67,12 +67,19 @@ static int mlx5_mpesw_metadata_set(struct mlx5_lag *ldev) static int enable_mpesw(struct mlx5_lag *ldev) { - int idx = mlx5_lag_get_dev_index_by_seq(ldev, MLX5_LAG_P1); struct mlx5_core_dev *dev0; int err; + int idx; int i; - if (idx < 0 || ldev->mode != MLX5_LAG_MODE_NONE) + if (ldev->mode == MLX5_LAG_MODE_MPESW) + return 0; + + if (ldev->mode != MLX5_LAG_MODE_NONE) + return -EINVAL; + + idx = mlx5_lag_get_dev_index_by_seq(ldev, MLX5_LAG_P1); + if (idx < 0) return -EINVAL; dev0 = ldev->pf[idx].dev; From e12c912f92ccea671b514caf371f28485714bb4b Mon Sep 17 00:00:00 2001 From: Cosmin Ratiu Date: Mon, 1 Dec 2025 17:13:28 +0200 Subject: [PATCH 1363/3902] net/mlx5e: Avoid unregistering PSP twice [ Upstream commit 35e93736f69963337912594eb3951ab320b77521 ] PSP is unregistered twice in: _mlx5e_remove -> mlx5e_psp_unregister mlx5e_nic_cleanup -> mlx5e_psp_unregister This leads to a refcount underflow in some conditions: ------------[ cut here ]------------ refcount_t: underflow; use-after-free. WARNING: CPU: 2 PID: 1694 at lib/refcount.c:28 refcount_warn_saturate+0xd8/0xe0 [...] mlx5e_psp_unregister+0x26/0x50 [mlx5_core] mlx5e_nic_cleanup+0x26/0x90 [mlx5_core] mlx5e_remove+0xe6/0x1f0 [mlx5_core] auxiliary_bus_remove+0x18/0x30 device_release_driver_internal+0x194/0x1f0 bus_remove_device+0xc6/0x130 device_del+0x159/0x3c0 mlx5_rescan_drivers_locked+0xbc/0x2a0 [mlx5_core] [...] Do not directly remove psp from the _mlx5e_remove path, the PSP cleanup happens as part of profile cleanup. Fixes: 89ee2d92f66c ("net/mlx5e: Support PSP offload functionality") Signed-off-by: Cosmin Ratiu Reviewed-by: Dragos Tatulea Signed-off-by: Tariq Toukan Reviewed-by: Simon Horman Link: https://patch.msgid.link/1764602008-1334866-3-git-send-email-tariqt@nvidia.com Signed-off-by: Paolo Abeni Signed-off-by: Sasha Levin --- drivers/net/ethernet/mellanox/mlx5/core/en_main.c | 1 - 1 file changed, 1 deletion(-) diff --git a/drivers/net/ethernet/mellanox/mlx5/core/en_main.c b/drivers/net/ethernet/mellanox/mlx5/core/en_main.c index 5e17eae81f4b3c..1545f9c008f49c 100644 --- a/drivers/net/ethernet/mellanox/mlx5/core/en_main.c +++ b/drivers/net/ethernet/mellanox/mlx5/core/en_main.c @@ -6805,7 +6805,6 @@ static void _mlx5e_remove(struct auxiliary_device *adev) * is already unregistered before changing to NIC profile. */ if (priv->netdev->reg_state == NETREG_REGISTERED) { - mlx5e_psp_unregister(priv); unregister_netdev(priv->netdev); _mlx5e_suspend(adev, false); } else { From 7ec73f0e16000e10c0a4e023ce7f2115d8ef6601 Mon Sep 17 00:00:00 2001 From: Vladimir Oltean Date: Tue, 18 Nov 2025 01:40:29 +0200 Subject: [PATCH 1364/3902] net: phy: realtek: eliminate priv->phycr2 variable [ Upstream commit 27033d06917758d47162581da7e9de8004049dee ] The RTL8211F(D)(I)-VD-CG PHY also has support for disabling the CLKOUT, and we'd like to introduce the "realtek,clkout-disable" property for that. But it isn't done through the PHYCR2 register, and it becomes awkward to have the driver pretend that it is. So just replace the machine-level "u16 phycr2" variable with a logical "bool disable_clk_out", which scales better to the other PHY as well. The change is a complete functional equivalent. Before, if the device tree property was absent, priv->phycr2 would contain the RTL8211F_CLKOUT_EN bit as read from hardware. Now, we don't save priv->phycr2, but we just don't call phy_modify_paged() on it. Also, we can simply call phy_modify_paged() with the "set" argument to 0. Signed-off-by: Vladimir Oltean Link: https://patch.msgid.link/20251117234033.345679-3-vladimir.oltean@nxp.com Signed-off-by: Jakub Kicinski Stable-dep-of: 4f0638b12451 ("net: phy: RTL8211FVD: Restore disabling of PHY-mode EEE") Signed-off-by: Sasha Levin --- drivers/net/phy/realtek/realtek_main.c | 38 ++++++++++++++++---------- 1 file changed, 23 insertions(+), 15 deletions(-) diff --git a/drivers/net/phy/realtek/realtek_main.c b/drivers/net/phy/realtek/realtek_main.c index b26f4d1c6bbfae..1625919a47be8d 100644 --- a/drivers/net/phy/realtek/realtek_main.c +++ b/drivers/net/phy/realtek/realtek_main.c @@ -167,8 +167,8 @@ MODULE_LICENSE("GPL"); struct rtl821x_priv { u16 phycr1; - u16 phycr2; bool has_phycr2; + bool disable_clk_out; struct clk *clk; /* rtl8211f */ u16 iner; @@ -239,15 +239,8 @@ static int rtl821x_probe(struct phy_device *phydev) priv->phycr1 |= RTL8211F_ALDPS_PLL_OFF | RTL8211F_ALDPS_ENABLE | RTL8211F_ALDPS_XTAL_OFF; priv->has_phycr2 = !(phy_id == RTL_8211FVD_PHYID); - if (priv->has_phycr2) { - ret = phy_read_paged(phydev, RTL8211F_PHYCR_PAGE, RTL8211F_PHYCR2); - if (ret < 0) - return ret; - - priv->phycr2 = ret & RTL8211F_CLKOUT_EN; - if (of_property_read_bool(dev->of_node, "realtek,clkout-disable")) - priv->phycr2 &= ~RTL8211F_CLKOUT_EN; - } + priv->disable_clk_out = of_property_read_bool(dev->of_node, + "realtek,clkout-disable"); phydev->priv = priv; @@ -627,6 +620,23 @@ static int rtl8211f_config_rgmii_delay(struct phy_device *phydev) return 0; } +static int rtl8211f_config_clk_out(struct phy_device *phydev) +{ + struct rtl821x_priv *priv = phydev->priv; + int ret; + + /* The value is preserved if the device tree property is absent */ + if (!priv->disable_clk_out) + return 0; + + ret = phy_modify_paged(phydev, RTL8211F_PHYCR_PAGE, + RTL8211F_PHYCR2, RTL8211F_CLKOUT_EN, 0); + if (ret) + return ret; + + return genphy_soft_reset(phydev); +} + static int rtl8211f_config_init(struct phy_device *phydev) { struct rtl821x_priv *priv = phydev->priv; @@ -655,16 +665,14 @@ static int rtl8211f_config_init(struct phy_device *phydev) if (ret) return ret; - ret = phy_modify_paged(phydev, RTL8211F_PHYCR_PAGE, - RTL8211F_PHYCR2, RTL8211F_CLKOUT_EN, - priv->phycr2); - if (ret < 0) { + ret = rtl8211f_config_clk_out(phydev); + if (ret) { dev_err(dev, "clkout configuration failed: %pe\n", ERR_PTR(ret)); return ret; } - return genphy_soft_reset(phydev); + return 0; } static int rtl821x_suspend(struct phy_device *phydev) From d064d95c74d33bc2a2c21b65a45679a71eabab3a Mon Sep 17 00:00:00 2001 From: Vladimir Oltean Date: Tue, 18 Nov 2025 01:40:30 +0200 Subject: [PATCH 1365/3902] net: phy: realtek: eliminate has_phycr2 variable [ Upstream commit 910ac7bfb1af1ae4cd141ef80e03a6729213c189 ] This variable is assigned in rtl821x_probe() and used in rtl8211f_config_init(), which is more complex than it needs to be. Simply testing the same condition from rtl821x_probe() in rtl8211f_config_init() yields the same result (the PHY driver ID is a runtime invariant), but with one temporary variable less. Signed-off-by: Vladimir Oltean Reviewed-by: Andrew Lunn Link: https://patch.msgid.link/20251117234033.345679-4-vladimir.oltean@nxp.com Signed-off-by: Jakub Kicinski Stable-dep-of: 4f0638b12451 ("net: phy: RTL8211FVD: Restore disabling of PHY-mode EEE") Signed-off-by: Sasha Levin --- drivers/net/phy/realtek/realtek_main.c | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/drivers/net/phy/realtek/realtek_main.c b/drivers/net/phy/realtek/realtek_main.c index 1625919a47be8d..daf2457b378bfd 100644 --- a/drivers/net/phy/realtek/realtek_main.c +++ b/drivers/net/phy/realtek/realtek_main.c @@ -167,7 +167,6 @@ MODULE_LICENSE("GPL"); struct rtl821x_priv { u16 phycr1; - bool has_phycr2; bool disable_clk_out; struct clk *clk; /* rtl8211f */ @@ -218,7 +217,6 @@ static int rtl821x_probe(struct phy_device *phydev) { struct device *dev = &phydev->mdio.dev; struct rtl821x_priv *priv; - u32 phy_id = phydev->drv->phy_id; int ret; priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); @@ -238,7 +236,6 @@ static int rtl821x_probe(struct phy_device *phydev) if (of_property_read_bool(dev->of_node, "realtek,aldps-enable")) priv->phycr1 |= RTL8211F_ALDPS_PLL_OFF | RTL8211F_ALDPS_ENABLE | RTL8211F_ALDPS_XTAL_OFF; - priv->has_phycr2 = !(phy_id == RTL_8211FVD_PHYID); priv->disable_clk_out = of_property_read_bool(dev->of_node, "realtek,clkout-disable"); @@ -656,7 +653,8 @@ static int rtl8211f_config_init(struct phy_device *phydev) if (ret) return ret; - if (!priv->has_phycr2) + /* RTL8211FVD has no PHYCR2 register */ + if (phydev->drv->phy_id == RTL_8211FVD_PHYID) return 0; /* Disable PHY-mode EEE so LPI is passed to the MAC */ From ce370d988122db1aab79b99419e7444dfe31996d Mon Sep 17 00:00:00 2001 From: Vladimir Oltean Date: Tue, 18 Nov 2025 01:40:31 +0200 Subject: [PATCH 1366/3902] net: phy: realtek: allow CLKOUT to be disabled on RTL8211F(D)(I)-VD-CG [ Upstream commit e1a31c41bef678afe0d99b7f0dc3711a80c68447 ] Add CLKOUT disable support for RTL8211F(D)(I)-VD-CG. Like with other PHY variants, this feature might be requested by customers when the clock output is not used, in order to reduce electromagnetic interference (EMI). In the common driver, the CLKOUT configuration is done through PHYCR2. The RTL_8211FVD_PHYID is singled out as not having that register, and execution in rtl8211f_config_init() returns early after commit 2c67301584f2 ("net: phy: realtek: Avoid PHYCR2 access if PHYCR2 not present"). But actually CLKOUT is configured through a different register for this PHY. Instead of pretending this is PHYCR2 (which it is not), just add some code for modifying this register inside the rtl8211f_disable_clk_out() function, and move that outside the code portion that runs only if PHYCR2 exists. In practice this reorders the PHYCR2 writes to disable PHY-mode EEE and to disable the CLKOUT for the normal RTL8211F variants, but this should be perfectly fine. It was not noted that RTL8211F(D)(I)-VD-CG would need a genphy_soft_reset() call after disabling the CLKOUT. Despite that, we do it out of caution and for symmetry with the other RTL8211F models. Co-developed-by: Clark Wang Signed-off-by: Clark Wang Signed-off-by: Vladimir Oltean Reviewed-by: Andrew Lunn Link: https://patch.msgid.link/20251117234033.345679-5-vladimir.oltean@nxp.com Signed-off-by: Jakub Kicinski Stable-dep-of: 4f0638b12451 ("net: phy: RTL8211FVD: Restore disabling of PHY-mode EEE") Signed-off-by: Sasha Levin --- drivers/net/phy/realtek/realtek_main.c | 31 ++++++++++++++++++-------- 1 file changed, 22 insertions(+), 9 deletions(-) diff --git a/drivers/net/phy/realtek/realtek_main.c b/drivers/net/phy/realtek/realtek_main.c index daf2457b378bfd..f0348b92beec33 100644 --- a/drivers/net/phy/realtek/realtek_main.c +++ b/drivers/net/phy/realtek/realtek_main.c @@ -89,6 +89,14 @@ #define RTL8211F_LEDCR_MASK GENMASK(4, 0) #define RTL8211F_LEDCR_SHIFT 5 +/* RTL8211F(D)(I)-VD-CG CLKOUT configuration is specified via magic values + * to undocumented register pages. The names here do not reflect the datasheet. + * Unlike other PHY models, CLKOUT configuration does not go through PHYCR2. + */ +#define RTL8211FVD_CLKOUT_PAGE 0xd05 +#define RTL8211FVD_CLKOUT_REG 0x11 +#define RTL8211FVD_CLKOUT_EN BIT(8) + /* RTL8211F RGMII configuration */ #define RTL8211F_RGMII_PAGE 0xd08 @@ -626,8 +634,13 @@ static int rtl8211f_config_clk_out(struct phy_device *phydev) if (!priv->disable_clk_out) return 0; - ret = phy_modify_paged(phydev, RTL8211F_PHYCR_PAGE, - RTL8211F_PHYCR2, RTL8211F_CLKOUT_EN, 0); + if (phydev->drv->phy_id == RTL_8211FVD_PHYID) + ret = phy_modify_paged(phydev, RTL8211FVD_CLKOUT_PAGE, + RTL8211FVD_CLKOUT_REG, + RTL8211FVD_CLKOUT_EN, 0); + else + ret = phy_modify_paged(phydev, RTL8211F_PHYCR_PAGE, + RTL8211F_PHYCR2, RTL8211F_CLKOUT_EN, 0); if (ret) return ret; @@ -653,6 +666,13 @@ static int rtl8211f_config_init(struct phy_device *phydev) if (ret) return ret; + ret = rtl8211f_config_clk_out(phydev); + if (ret) { + dev_err(dev, "clkout configuration failed: %pe\n", + ERR_PTR(ret)); + return ret; + } + /* RTL8211FVD has no PHYCR2 register */ if (phydev->drv->phy_id == RTL_8211FVD_PHYID) return 0; @@ -663,13 +683,6 @@ static int rtl8211f_config_init(struct phy_device *phydev) if (ret) return ret; - ret = rtl8211f_config_clk_out(phydev); - if (ret) { - dev_err(dev, "clkout configuration failed: %pe\n", - ERR_PTR(ret)); - return ret; - } - return 0; } From c6a23498f795fd286acddd912bc1b2de79ab0306 Mon Sep 17 00:00:00 2001 From: Vladimir Oltean Date: Tue, 18 Nov 2025 01:40:32 +0200 Subject: [PATCH 1367/3902] net: phy: realtek: eliminate priv->phycr1 variable [ Upstream commit bb78b71faf60d11a15f07e3390fcfd31e5e523bb ] Previous changes have replaced the machine-level priv->phycr2 with a high-level priv->disable_clk_out. This created a discrepancy with priv->phycr1 which is resolved here, for uniformity. One advantage of this new implementation is that we don't read priv->phycr1 in rtl821x_probe() if we're never going to modify it. We never test the positive return code from phy_modify_mmd_changed(), so we could just as well use phy_modify_mmd(). I took the ALDPS feature description from commit d90db36a9e74 ("net: phy: realtek: add dt property to enable ALDPS mode") and transformed it into a function comment - the feature is sufficiently non-obvious to deserve that. Signed-off-by: Vladimir Oltean Reviewed-by: Andrew Lunn Link: https://patch.msgid.link/20251117234033.345679-6-vladimir.oltean@nxp.com Signed-off-by: Jakub Kicinski Stable-dep-of: 4f0638b12451 ("net: phy: RTL8211FVD: Restore disabling of PHY-mode EEE") Signed-off-by: Sasha Levin --- drivers/net/phy/realtek/realtek_main.c | 44 ++++++++++++++++---------- 1 file changed, 28 insertions(+), 16 deletions(-) diff --git a/drivers/net/phy/realtek/realtek_main.c b/drivers/net/phy/realtek/realtek_main.c index f0348b92beec33..35f40bfdaf113d 100644 --- a/drivers/net/phy/realtek/realtek_main.c +++ b/drivers/net/phy/realtek/realtek_main.c @@ -174,7 +174,7 @@ MODULE_AUTHOR("Johnson Leung"); MODULE_LICENSE("GPL"); struct rtl821x_priv { - u16 phycr1; + bool enable_aldps; bool disable_clk_out; struct clk *clk; /* rtl8211f */ @@ -225,7 +225,6 @@ static int rtl821x_probe(struct phy_device *phydev) { struct device *dev = &phydev->mdio.dev; struct rtl821x_priv *priv; - int ret; priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); if (!priv) @@ -236,14 +235,8 @@ static int rtl821x_probe(struct phy_device *phydev) return dev_err_probe(dev, PTR_ERR(priv->clk), "failed to get phy clock\n"); - ret = phy_read_paged(phydev, RTL8211F_PHYCR_PAGE, RTL8211F_PHYCR1); - if (ret < 0) - return ret; - - priv->phycr1 = ret & (RTL8211F_ALDPS_PLL_OFF | RTL8211F_ALDPS_ENABLE | RTL8211F_ALDPS_XTAL_OFF); - if (of_property_read_bool(dev->of_node, "realtek,aldps-enable")) - priv->phycr1 |= RTL8211F_ALDPS_PLL_OFF | RTL8211F_ALDPS_ENABLE | RTL8211F_ALDPS_XTAL_OFF; - + priv->enable_aldps = of_property_read_bool(dev->of_node, + "realtek,aldps-enable"); priv->disable_clk_out = of_property_read_bool(dev->of_node, "realtek,clkout-disable"); @@ -647,17 +640,36 @@ static int rtl8211f_config_clk_out(struct phy_device *phydev) return genphy_soft_reset(phydev); } -static int rtl8211f_config_init(struct phy_device *phydev) +/* Advance Link Down Power Saving (ALDPS) mode changes crystal/clock behaviour, + * which causes the RXC clock signal to stop for tens to hundreds of + * milliseconds. + * + * Some MACs need the RXC clock to support their internal RX logic, so ALDPS is + * only enabled based on an opt-in device tree property. + */ +static int rtl8211f_config_aldps(struct phy_device *phydev) { struct rtl821x_priv *priv = phydev->priv; + u16 mask = RTL8211F_ALDPS_PLL_OFF | + RTL8211F_ALDPS_ENABLE | + RTL8211F_ALDPS_XTAL_OFF; + + /* The value is preserved if the device tree property is absent */ + if (!priv->enable_aldps) + return 0; + + return phy_modify_paged(phydev, RTL8211F_PHYCR_PAGE, RTL8211F_PHYCR1, + mask, mask); +} + +static int rtl8211f_config_init(struct phy_device *phydev) +{ struct device *dev = &phydev->mdio.dev; int ret; - ret = phy_modify_paged_changed(phydev, RTL8211F_PHYCR_PAGE, RTL8211F_PHYCR1, - RTL8211F_ALDPS_PLL_OFF | RTL8211F_ALDPS_ENABLE | RTL8211F_ALDPS_XTAL_OFF, - priv->phycr1); - if (ret < 0) { - dev_err(dev, "aldps mode configuration failed: %pe\n", + ret = rtl8211f_config_aldps(phydev); + if (ret) { + dev_err(dev, "aldps mode configuration failed: %pe\n", ERR_PTR(ret)); return ret; } From e3859271c72cafc081756bfc1ab9f4e19a5e5215 Mon Sep 17 00:00:00 2001 From: Vladimir Oltean Date: Tue, 18 Nov 2025 01:40:33 +0200 Subject: [PATCH 1368/3902] net: phy: realtek: create rtl8211f_config_phy_eee() helper [ Upstream commit 4465ae435ddc0162d5033a543658449d53d46d08 ] To simplify the rtl8211f_config_init() control flow and get rid of "early" returns for PHYs where the PHYCR2 register is absent, move the entire logic sub-block that deals with disabling PHY-mode EEE to a separate function. There, it is much more obvious what the early "return 0" skips, and it becomes more difficult to accidentally skip unintended stuff. Signed-off-by: Vladimir Oltean Link: https://patch.msgid.link/20251117234033.345679-7-vladimir.oltean@nxp.com Signed-off-by: Jakub Kicinski Stable-dep-of: 4f0638b12451 ("net: phy: RTL8211FVD: Restore disabling of PHY-mode EEE") Signed-off-by: Sasha Levin --- drivers/net/phy/realtek/realtek_main.c | 23 ++++++++++++----------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/drivers/net/phy/realtek/realtek_main.c b/drivers/net/phy/realtek/realtek_main.c index 35f40bfdaf113d..2c661346050f15 100644 --- a/drivers/net/phy/realtek/realtek_main.c +++ b/drivers/net/phy/realtek/realtek_main.c @@ -662,6 +662,17 @@ static int rtl8211f_config_aldps(struct phy_device *phydev) mask, mask); } +static int rtl8211f_config_phy_eee(struct phy_device *phydev) +{ + /* RTL8211FVD has no PHYCR2 register */ + if (phydev->drv->phy_id == RTL_8211FVD_PHYID) + return 0; + + /* Disable PHY-mode EEE so LPI is passed to the MAC */ + return phy_modify_paged(phydev, RTL8211F_PHYCR_PAGE, RTL8211F_PHYCR2, + RTL8211F_PHYCR2_PHY_EEE_ENABLE, 0); +} + static int rtl8211f_config_init(struct phy_device *phydev) { struct device *dev = &phydev->mdio.dev; @@ -685,17 +696,7 @@ static int rtl8211f_config_init(struct phy_device *phydev) return ret; } - /* RTL8211FVD has no PHYCR2 register */ - if (phydev->drv->phy_id == RTL_8211FVD_PHYID) - return 0; - - /* Disable PHY-mode EEE so LPI is passed to the MAC */ - ret = phy_modify_paged(phydev, RTL8211F_PHYCR_PAGE, RTL8211F_PHYCR2, - RTL8211F_PHYCR2_PHY_EEE_ENABLE, 0); - if (ret) - return ret; - - return 0; + return rtl8211f_config_phy_eee(phydev); } static int rtl821x_suspend(struct phy_device *phydev) From 90206ede631836277eee7c1ff03288e5b8420b7a Mon Sep 17 00:00:00 2001 From: Ivan Galkin Date: Tue, 2 Dec 2025 10:07:42 +0100 Subject: [PATCH 1369/3902] net: phy: RTL8211FVD: Restore disabling of PHY-mode EEE [ Upstream commit 4f0638b12451112de4138689fa679315c8d388dc ] When support for RTL8211F(D)(I)-VD-CG was introduced in commit bb726b753f75 ("net: phy: realtek: add support for RTL8211F(D)(I)-VD-CG") the implementation assumed that this PHY model doesn't have the control register PHYCR2 (Page 0xa43 Address 0x19). This assumption was based on the differences in CLKOUT configurations between RTL8211FVD and the remaining RTL8211F PHYs. In the latter commit 2c67301584f2 ("net: phy: realtek: Avoid PHYCR2 access if PHYCR2 not present") this assumption was expanded to the PHY-mode EEE. I performed tests on RTL8211FI-VD-CG and confirmed that disabling PHY-mode EEE works correctly and is uniform with other PHYs supported by the driver. To validate the correctness, I contacted Realtek support. Realtek confirmed that PHY-mode EEE on RTL8211F(D)(I)-VD-CG is configured via Page 0xa43 Address 0x19 bit 5. Moreover, Realtek informed me that the most recent datasheet for RTL8211F(D)(I)-VD-CG v1.1 is incomplete and the naming of control registers is partly inconsistent. The errata I received from Realtek corrects the naming as follows: | Register | Datasheet v1.1 | Errata | |-------------------------|----------------|--------| | Page 0xa44 Address 0x11 | PHYCR2 | PHYCR3 | | Page 0xa43 Address 0x19 | N/A | PHYCR2 | This information confirms that the supposedly missing control register, PHYCR2, exists in the RTL8211F(D)(I)-VD-CG under the same address and the same name. It controls widely the same configs as other PHYs from the RTL8211F series (e.g. PHY-mode EEE). Clock out configuration is an exception. Given all this information, restore disabling of the PHY-mode EEE. Fixes: 2c67301584f2 ("net: phy: realtek: Avoid PHYCR2 access if PHYCR2 not present") Signed-off-by: Ivan Galkin Reviewed-by: Vladimir Oltean Link: https://patch.msgid.link/20251202-phy_eee-v1-1-fe0bf6ab3df0@axis.com Signed-off-by: Paolo Abeni Signed-off-by: Sasha Levin --- drivers/net/phy/realtek/realtek_main.c | 4 ---- 1 file changed, 4 deletions(-) diff --git a/drivers/net/phy/realtek/realtek_main.c b/drivers/net/phy/realtek/realtek_main.c index 2c661346050f15..7c3d277efaf07a 100644 --- a/drivers/net/phy/realtek/realtek_main.c +++ b/drivers/net/phy/realtek/realtek_main.c @@ -664,10 +664,6 @@ static int rtl8211f_config_aldps(struct phy_device *phydev) static int rtl8211f_config_phy_eee(struct phy_device *phydev) { - /* RTL8211FVD has no PHYCR2 register */ - if (phydev->drv->phy_id == RTL_8211FVD_PHYID) - return 0; - /* Disable PHY-mode EEE so LPI is passed to the MAC */ return phy_modify_paged(phydev, RTL8211F_PHYCR_PAGE, RTL8211F_PHYCR2, RTL8211F_PHYCR2_PHY_EEE_ENABLE, 0); From 7aa2085ca72b140f0895b25c766da924656827aa Mon Sep 17 00:00:00 2001 From: Dmitry Skorodumov Date: Tue, 2 Dec 2025 13:39:03 +0300 Subject: [PATCH 1370/3902] ipvlan: Ignore PACKET_LOOPBACK in handle_mode_l2() [ Upstream commit 0c57ff008a11f24f7f05fa760222692a00465fec ] Packets with pkt_type == PACKET_LOOPBACK are captured by handle_frame() function, but they don't have L2 header. We should not process them in handle_mode_l2(). This doesn't affect old L2 functionality, since handling was anyway incorrect. Handle them the same way as in br_handle_frame(): just pass the skb. To observe invalid behaviour, just start "ping -b" on bcast address of port-interface. Fixes: 2ad7bf363841 ("ipvlan: Initial check-in of the IPVLAN driver.") Signed-off-by: Dmitry Skorodumov Link: https://patch.msgid.link/20251202103906.4087675-1-skorodumov.dmitry@huawei.com Signed-off-by: Paolo Abeni Signed-off-by: Sasha Levin --- drivers/net/ipvlan/ipvlan_core.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/drivers/net/ipvlan/ipvlan_core.c b/drivers/net/ipvlan/ipvlan_core.c index d7e3ddbcab6f41..baf2ef3bcd54b6 100644 --- a/drivers/net/ipvlan/ipvlan_core.c +++ b/drivers/net/ipvlan/ipvlan_core.c @@ -737,6 +737,9 @@ static rx_handler_result_t ipvlan_handle_mode_l2(struct sk_buff **pskb, struct ethhdr *eth = eth_hdr(skb); rx_handler_result_t ret = RX_HANDLER_PASS; + if (unlikely(skb->pkt_type == PACKET_LOOPBACK)) + return RX_HANDLER_PASS; + if (is_multicast_ether_addr(eth->h_dest)) { if (ipvlan_external_frame(skb, port)) { struct sk_buff *nskb = skb_clone(skb, GFP_ATOMIC); From d2495f529d60e8e8c43e6ad524089c38b8be7bc4 Mon Sep 17 00:00:00 2001 From: Gerd Bayer Date: Tue, 2 Dec 2025 12:12:57 +0100 Subject: [PATCH 1371/3902] net/mlx5: Fix double unregister of HCA_PORTS component [ Upstream commit 6a107cfe9c99a079e578a4c5eb70038101a3599f ] Clear hca_devcom_comp in device's private data after unregistering it in LAG teardown. Otherwise a slightly lagging second pass through mlx5_unload_one() might try to unregister it again and trip over use-after-free. On s390 almost all PCI level recovery events trigger two passes through mxl5_unload_one() - one through the poll_health() method and one through mlx5_pci_err_detected() as callback from generic PCI error recovery. While testing PCI error recovery paths with more kernel debug features enabled, this issue reproducibly led to kernel panics with the following call chain: Unable to handle kernel pointer dereference in virtual kernel address space Failing address: 6b6b6b6b6b6b6000 TEID: 6b6b6b6b6b6b6803 ESOP-2 FSI Fault in home space mode while using kernel ASCE. AS:00000000705c4007 R3:0000000000000024 Oops: 0038 ilc:3 [#1]SMP CPU: 14 UID: 0 PID: 156 Comm: kmcheck Kdump: loaded Not tainted 6.18.0-20251130.rc7.git0.16131a59cab1.300.fc43.s390x+debug #1 PREEMPT Krnl PSW : 0404e00180000000 0000020fc86aa1dc (__lock_acquire+0x5c/0x15f0) R:0 T:1 IO:0 EX:0 Key:0 M:1 W:0 P:0 AS:3 CC:2 PM:0 RI:0 EA:3 Krnl GPRS: 0000000000000000 0000020f00000001 6b6b6b6b6b6b6c33 0000000000000000 0000000000000000 0000000000000000 0000000000000001 0000000000000000 0000000000000000 0000020fca28b820 0000000000000000 0000010a1ced8100 0000010a1ced8100 0000020fc9775068 0000018fce14f8b8 0000018fce14f7f8 Krnl Code: 0000020fc86aa1cc: e3b003400004 lg %r11,832 0000020fc86aa1d2: a7840211 brc 8,0000020fc86aa5f4 *0000020fc86aa1d6: c09000df0b25 larl %r9,0000020fca28b820 >0000020fc86aa1dc: d50790002000 clc 0(8,%r9),0(%r2) 0000020fc86aa1e2: a7840209 brc 8,0000020fc86aa5f4 0000020fc86aa1e6: c0e001100401 larl %r14,0000020fca8aa9e8 0000020fc86aa1ec: c01000e25a00 larl %r1,0000020fca2f55ec 0000020fc86aa1f2: a7eb00e8 aghi %r14,232 Call Trace: __lock_acquire+0x5c/0x15f0 lock_acquire.part.0+0xf8/0x270 lock_acquire+0xb0/0x1b0 down_write+0x5a/0x250 mlx5_detach_device+0x42/0x110 [mlx5_core] mlx5_unload_one_devl_locked+0x50/0xc0 [mlx5_core] mlx5_unload_one+0x42/0x60 [mlx5_core] mlx5_pci_err_detected+0x94/0x150 [mlx5_core] zpci_event_attempt_error_recovery+0xcc/0x388 Fixes: 5a977b5833b7 ("net/mlx5: Lag, move devcom registration to LAG layer") Signed-off-by: Gerd Bayer Reviewed-by: Moshe Shemesh Acked-by: Tariq Toukan Link: https://patch.msgid.link/20251202-fix_lag-v1-1-59e8177ffce0@linux.ibm.com Signed-off-by: Paolo Abeni Signed-off-by: Sasha Levin --- drivers/net/ethernet/mellanox/mlx5/core/lag/lag.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/net/ethernet/mellanox/mlx5/core/lag/lag.c b/drivers/net/ethernet/mellanox/mlx5/core/lag/lag.c index 3db0387bf6dcb7..8ec04a5f434dd4 100644 --- a/drivers/net/ethernet/mellanox/mlx5/core/lag/lag.c +++ b/drivers/net/ethernet/mellanox/mlx5/core/lag/lag.c @@ -1413,6 +1413,7 @@ static int __mlx5_lag_dev_add_mdev(struct mlx5_core_dev *dev) static void mlx5_lag_unregister_hca_devcom_comp(struct mlx5_core_dev *dev) { mlx5_devcom_unregister_component(dev->priv.hca_devcom_comp); + dev->priv.hca_devcom_comp = NULL; } static int mlx5_lag_register_hca_devcom_comp(struct mlx5_core_dev *dev) From fcb9e2e5cf0497df47f8f9191ab99fdae6e65dbe Mon Sep 17 00:00:00 2001 From: Ido Schimmel Date: Tue, 2 Dec 2025 18:44:11 +0100 Subject: [PATCH 1372/3902] mlxsw: spectrum_router: Fix possible neighbour reference count leak [ Upstream commit b6b638bda240395dff49a87403b2e32493e56d2a ] mlxsw_sp_router_schedule_work() takes a reference on a neighbour, expecting a work item to release it later on. However, we might fail to schedule the work item, in which case the neighbour reference count will be leaked. Fix by taking the reference just before scheduling the work item. Note that mlxsw_sp_router_schedule_work() can receive a NULL neighbour pointer, but neigh_clone() handles that correctly. Spotted during code review, did not actually observe the reference count leak. Fixes: 151b89f6025a ("mlxsw: spectrum_router: Reuse work neighbor initialization in work scheduler") Reviewed-by: Petr Machata Signed-off-by: Ido Schimmel Signed-off-by: Petr Machata Reviewed-by: Simon Horman Link: https://patch.msgid.link/ec2934ae4aca187a8d8c9329a08ce93cca411378.1764695650.git.petrm@nvidia.com Signed-off-by: Jakub Kicinski Signed-off-by: Sasha Levin --- drivers/net/ethernet/mellanox/mlxsw/spectrum_router.c | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/drivers/net/ethernet/mellanox/mlxsw/spectrum_router.c b/drivers/net/ethernet/mellanox/mlxsw/spectrum_router.c index a2033837182e86..f4e9ecaeb104ff 100644 --- a/drivers/net/ethernet/mellanox/mlxsw/spectrum_router.c +++ b/drivers/net/ethernet/mellanox/mlxsw/spectrum_router.c @@ -2858,6 +2858,11 @@ static int mlxsw_sp_router_schedule_work(struct net *net, if (!net_work) return NOTIFY_BAD; + /* Take a reference to ensure the neighbour won't be destructed until + * we drop the reference in the work item. + */ + neigh_clone(n); + INIT_WORK(&net_work->work, cb); net_work->mlxsw_sp = router->mlxsw_sp; net_work->n = n; @@ -2881,11 +2886,6 @@ static int mlxsw_sp_router_schedule_neigh_work(struct mlxsw_sp_router *router, struct net *net; net = neigh_parms_net(n->parms); - - /* Take a reference to ensure the neighbour won't be destructed until we - * drop the reference in delayed work. - */ - neigh_clone(n); return mlxsw_sp_router_schedule_work(net, router, n, mlxsw_sp_router_neigh_event_work); } From 675c5aeadf6472672c472dc0f26401e4fcfbf254 Mon Sep 17 00:00:00 2001 From: Ido Schimmel Date: Tue, 2 Dec 2025 18:44:12 +0100 Subject: [PATCH 1373/3902] mlxsw: spectrum_router: Fix neighbour use-after-free [ Upstream commit 8b0e69763ef948fb872a7767df4be665d18f5fd4 ] We sometimes observe use-after-free when dereferencing a neighbour [1]. The problem seems to be that the driver stores a pointer to the neighbour, but without holding a reference on it. A reference is only taken when the neighbour is used by a nexthop. Fix by simplifying the reference counting scheme. Always take a reference when storing a neighbour pointer in a neighbour entry. Avoid taking a referencing when the neighbour is used by a nexthop as the neighbour entry associated with the nexthop already holds a reference. Tested by running the test that uncovered the problem over 300 times. Without this patch the problem was reproduced after a handful of iterations. [1] BUG: KASAN: slab-use-after-free in mlxsw_sp_neigh_entry_update+0x2d4/0x310 Read of size 8 at addr ffff88817f8e3420 by task ip/3929 CPU: 3 UID: 0 PID: 3929 Comm: ip Not tainted 6.18.0-rc4-virtme-g36b21a067510 #3 PREEMPT(full) Hardware name: Nvidia SN5600/VMOD0013, BIOS 5.13 05/31/2023 Call Trace: dump_stack_lvl+0x6f/0xa0 print_address_description.constprop.0+0x6e/0x300 print_report+0xfc/0x1fb kasan_report+0xe4/0x110 mlxsw_sp_neigh_entry_update+0x2d4/0x310 mlxsw_sp_router_rif_gone_sync+0x35f/0x510 mlxsw_sp_rif_destroy+0x1ea/0x730 mlxsw_sp_inetaddr_port_vlan_event+0xa1/0x1b0 __mlxsw_sp_inetaddr_lag_event+0xcc/0x130 __mlxsw_sp_inetaddr_event+0xf5/0x3c0 mlxsw_sp_router_netdevice_event+0x1015/0x1580 notifier_call_chain+0xcc/0x150 call_netdevice_notifiers_info+0x7e/0x100 __netdev_upper_dev_unlink+0x10b/0x210 netdev_upper_dev_unlink+0x79/0xa0 vrf_del_slave+0x18/0x50 do_set_master+0x146/0x7d0 do_setlink.isra.0+0x9a0/0x2880 rtnl_newlink+0x637/0xb20 rtnetlink_rcv_msg+0x6fe/0xb90 netlink_rcv_skb+0x123/0x380 netlink_unicast+0x4a3/0x770 netlink_sendmsg+0x75b/0xc90 __sock_sendmsg+0xbe/0x160 ____sys_sendmsg+0x5b2/0x7d0 ___sys_sendmsg+0xfd/0x180 __sys_sendmsg+0x124/0x1c0 do_syscall_64+0xbb/0xfd0 entry_SYSCALL_64_after_hwframe+0x4b/0x53 [...] Allocated by task 109: kasan_save_stack+0x30/0x50 kasan_save_track+0x14/0x30 __kasan_kmalloc+0x7b/0x90 __kmalloc_noprof+0x2c1/0x790 neigh_alloc+0x6af/0x8f0 ___neigh_create+0x63/0xe90 mlxsw_sp_nexthop_neigh_init+0x430/0x7e0 mlxsw_sp_nexthop_type_init+0x212/0x960 mlxsw_sp_nexthop6_group_info_init.constprop.0+0x81f/0x1280 mlxsw_sp_nexthop6_group_get+0x392/0x6a0 mlxsw_sp_fib6_entry_create+0x46a/0xfd0 mlxsw_sp_router_fib6_replace+0x1ed/0x5f0 mlxsw_sp_router_fib6_event_work+0x10a/0x2a0 process_one_work+0xd57/0x1390 worker_thread+0x4d6/0xd40 kthread+0x355/0x5b0 ret_from_fork+0x1d4/0x270 ret_from_fork_asm+0x11/0x20 Freed by task 154: kasan_save_stack+0x30/0x50 kasan_save_track+0x14/0x30 __kasan_save_free_info+0x3b/0x60 __kasan_slab_free+0x43/0x70 kmem_cache_free_bulk.part.0+0x1eb/0x5e0 kvfree_rcu_bulk+0x1f2/0x260 kfree_rcu_work+0x130/0x1b0 process_one_work+0xd57/0x1390 worker_thread+0x4d6/0xd40 kthread+0x355/0x5b0 ret_from_fork+0x1d4/0x270 ret_from_fork_asm+0x11/0x20 Last potentially related work creation: kasan_save_stack+0x30/0x50 kasan_record_aux_stack+0x8c/0xa0 kvfree_call_rcu+0x93/0x5b0 mlxsw_sp_router_neigh_event_work+0x67d/0x860 process_one_work+0xd57/0x1390 worker_thread+0x4d6/0xd40 kthread+0x355/0x5b0 ret_from_fork+0x1d4/0x270 ret_from_fork_asm+0x11/0x20 Fixes: 6cf3c971dc84 ("mlxsw: spectrum_router: Add private neigh table") Signed-off-by: Ido Schimmel Reviewed-by: Petr Machata Signed-off-by: Petr Machata Reviewed-by: Simon Horman Link: https://patch.msgid.link/92d75e21d95d163a41b5cea67a15cd33f547cba6.1764695650.git.petrm@nvidia.com Signed-off-by: Jakub Kicinski Signed-off-by: Sasha Levin --- .../ethernet/mellanox/mlxsw/spectrum_router.c | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/drivers/net/ethernet/mellanox/mlxsw/spectrum_router.c b/drivers/net/ethernet/mellanox/mlxsw/spectrum_router.c index f4e9ecaeb104ff..2d0e89bd2fb9ca 100644 --- a/drivers/net/ethernet/mellanox/mlxsw/spectrum_router.c +++ b/drivers/net/ethernet/mellanox/mlxsw/spectrum_router.c @@ -2265,6 +2265,7 @@ mlxsw_sp_neigh_entry_alloc(struct mlxsw_sp *mlxsw_sp, struct neighbour *n, if (!neigh_entry) return NULL; + neigh_hold(n); neigh_entry->key.n = n; neigh_entry->rif = rif; INIT_LIST_HEAD(&neigh_entry->nexthop_list); @@ -2274,6 +2275,7 @@ mlxsw_sp_neigh_entry_alloc(struct mlxsw_sp *mlxsw_sp, struct neighbour *n, static void mlxsw_sp_neigh_entry_free(struct mlxsw_sp_neigh_entry *neigh_entry) { + neigh_release(neigh_entry->key.n); kfree(neigh_entry); } @@ -4320,6 +4322,8 @@ mlxsw_sp_nexthop_dead_neigh_replace(struct mlxsw_sp *mlxsw_sp, if (err) goto err_neigh_entry_insert; + neigh_release(old_n); + read_lock_bh(&n->lock); nud_state = n->nud_state; dead = n->dead; @@ -4328,14 +4332,10 @@ mlxsw_sp_nexthop_dead_neigh_replace(struct mlxsw_sp *mlxsw_sp, list_for_each_entry(nh, &neigh_entry->nexthop_list, neigh_list_node) { - neigh_release(old_n); - neigh_clone(n); __mlxsw_sp_nexthop_neigh_update(nh, !entry_connected); mlxsw_sp_nexthop_group_refresh(mlxsw_sp, nh->nhgi->nh_grp); } - neigh_release(n); - return 0; err_neigh_entry_insert: @@ -4428,6 +4428,11 @@ static int mlxsw_sp_nexthop_neigh_init(struct mlxsw_sp *mlxsw_sp, } } + /* Release the reference taken by neigh_lookup() / neigh_create() since + * neigh_entry already holds one. + */ + neigh_release(n); + /* If that is the first nexthop connected to that neigh, add to * nexthop_neighs_list */ @@ -4454,11 +4459,9 @@ static void mlxsw_sp_nexthop_neigh_fini(struct mlxsw_sp *mlxsw_sp, struct mlxsw_sp_nexthop *nh) { struct mlxsw_sp_neigh_entry *neigh_entry = nh->neigh_entry; - struct neighbour *n; if (!neigh_entry) return; - n = neigh_entry->key.n; __mlxsw_sp_nexthop_neigh_update(nh, true); list_del(&nh->neigh_list_node); @@ -4472,8 +4475,6 @@ static void mlxsw_sp_nexthop_neigh_fini(struct mlxsw_sp *mlxsw_sp, if (!neigh_entry->connected && list_empty(&neigh_entry->nexthop_list)) mlxsw_sp_neigh_entry_destroy(mlxsw_sp, neigh_entry); - - neigh_release(n); } static bool mlxsw_sp_ipip_netdev_ul_up(struct net_device *ol_dev) From 4049a6ace209f4ed150429f86ae796d7d6a4c22b Mon Sep 17 00:00:00 2001 From: Ido Schimmel Date: Tue, 2 Dec 2025 18:44:13 +0100 Subject: [PATCH 1374/3902] mlxsw: spectrum_mr: Fix use-after-free when updating multicast route stats [ Upstream commit 8ac1dacec458f55f871f7153242ed6ab60373b90 ] Cited commit added a dedicated mutex (instead of RTNL) to protect the multicast route list, so that it will not change while the driver periodically traverses it in order to update the kernel about multicast route stats that were queried from the device. One instance of list entry deletion (during route replace) was missed and it can result in a use-after-free [1]. Fix by acquiring the mutex before deleting the entry from the list and releasing it afterwards. [1] BUG: KASAN: slab-use-after-free in mlxsw_sp_mr_stats_update+0x4a5/0x540 drivers/net/ethernet/mellanox/mlxsw/spectrum_mr.c:1006 [mlxsw_spectrum] Read of size 8 at addr ffff8881523c2fa8 by task kworker/2:5/22043 CPU: 2 UID: 0 PID: 22043 Comm: kworker/2:5 Not tainted 6.18.0-rc1-custom-g1a3d6d7cd014 #1 PREEMPT(full) Hardware name: Mellanox Technologies Ltd. MSN2010/SA002610, BIOS 5.6.5 08/24/2017 Workqueue: mlxsw_core mlxsw_sp_mr_stats_update [mlxsw_spectrum] Call Trace: dump_stack_lvl+0xba/0x110 print_report+0x174/0x4f5 kasan_report+0xdf/0x110 mlxsw_sp_mr_stats_update+0x4a5/0x540 drivers/net/ethernet/mellanox/mlxsw/spectrum_mr.c:1006 [mlxsw_spectrum] process_one_work+0x9cc/0x18e0 worker_thread+0x5df/0xe40 kthread+0x3b8/0x730 ret_from_fork+0x3e9/0x560 ret_from_fork_asm+0x1a/0x30 Allocated by task 29933: kasan_save_stack+0x30/0x50 kasan_save_track+0x14/0x30 __kasan_kmalloc+0x8f/0xa0 mlxsw_sp_mr_route_add+0xd8/0x4770 [mlxsw_spectrum] mlxsw_sp_router_fibmr_event_work+0x371/0xad0 drivers/net/ethernet/mellanox/mlxsw/spectrum_router.c:7965 [mlxsw_spectrum] process_one_work+0x9cc/0x18e0 worker_thread+0x5df/0xe40 kthread+0x3b8/0x730 ret_from_fork+0x3e9/0x560 ret_from_fork_asm+0x1a/0x30 Freed by task 29933: kasan_save_stack+0x30/0x50 kasan_save_track+0x14/0x30 __kasan_save_free_info+0x3b/0x70 __kasan_slab_free+0x43/0x70 kfree+0x14e/0x700 mlxsw_sp_mr_route_add+0x2dea/0x4770 drivers/net/ethernet/mellanox/mlxsw/spectrum_mr.c:444 [mlxsw_spectrum] mlxsw_sp_router_fibmr_event_work+0x371/0xad0 drivers/net/ethernet/mellanox/mlxsw/spectrum_router.c:7965 [mlxsw_spectrum] process_one_work+0x9cc/0x18e0 worker_thread+0x5df/0xe40 kthread+0x3b8/0x730 ret_from_fork+0x3e9/0x560 ret_from_fork_asm+0x1a/0x30 Fixes: f38656d06725 ("mlxsw: spectrum_mr: Protect multicast route list with a lock") Signed-off-by: Ido Schimmel Reviewed-by: Petr Machata Signed-off-by: Petr Machata Reviewed-by: Simon Horman Link: https://patch.msgid.link/f996feecfd59fde297964bfc85040b6d83ec6089.1764695650.git.petrm@nvidia.com Signed-off-by: Jakub Kicinski Signed-off-by: Sasha Levin --- drivers/net/ethernet/mellanox/mlxsw/spectrum_mr.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/drivers/net/ethernet/mellanox/mlxsw/spectrum_mr.c b/drivers/net/ethernet/mellanox/mlxsw/spectrum_mr.c index 5afe6b155ef0d5..81935f87bfcd71 100644 --- a/drivers/net/ethernet/mellanox/mlxsw/spectrum_mr.c +++ b/drivers/net/ethernet/mellanox/mlxsw/spectrum_mr.c @@ -440,7 +440,9 @@ int mlxsw_sp_mr_route_add(struct mlxsw_sp_mr_table *mr_table, rhashtable_remove_fast(&mr_table->route_ht, &mr_orig_route->ht_node, mlxsw_sp_mr_route_ht_params); + mutex_lock(&mr_table->route_list_lock); list_del(&mr_orig_route->node); + mutex_unlock(&mr_table->route_list_lock); mlxsw_sp_mr_route_destroy(mr_table, mr_orig_route); } From f17e0c1208485b24d61271bc1ddc8f2087e71561 Mon Sep 17 00:00:00 2001 From: Michael Chan Date: Tue, 2 Dec 2025 16:30:24 -0800 Subject: [PATCH 1375/3902] bnxt_en: Fix XDP_TX path [ Upstream commit 0373d5c387f24de749cc22e694a14b3a7c7eb515 ] For XDP_TX action in bnxt_rx_xdp(), clearing of the event flags is not correct. __bnxt_poll_work() -> bnxt_rx_pkt() -> bnxt_rx_xdp() may be looping within NAPI and some event flags may be set in earlier iterations. In particular, if BNXT_TX_EVENT is set earlier indicating some XDP_TX packets are ready and pending, it will be cleared if it is XDP_TX action again. Normally, we will set BNXT_TX_EVENT again when we successfully call __bnxt_xmit_xdp(). But if the TX ring has no more room, the flag will not be set. This will cause the TX producer to be ahead but the driver will not hit the TX doorbell. For multi-buf XDP_TX, there is no need to clear the event flags and set BNXT_AGG_EVENT. The BNXT_AGG_EVENT flag should have been set earlier in bnxt_rx_pkt(). The visible symptom of this is that the RX ring associated with the TX XDP ring will eventually become empty and all packets will be dropped. Because this condition will cause the driver to not refill the RX ring seeing that the TX ring has forever pending XDP_TX packets. The fix is to only clear BNXT_RX_EVENT when we have successfully called __bnxt_xmit_xdp(). Fixes: 7f0a168b0441 ("bnxt_en: Add completion ring pointer in TX and RX ring structures") Reported-by: Pavel Dubovitsky Reviewed-by: Andy Gospodarek Reviewed-by: Pavan Chebbi Reviewed-by: Kalesh AP Signed-off-by: Michael Chan Reviewed-by: Jacob Keller Link: https://patch.msgid.link/20251203003024.2246699-1-michael.chan@broadcom.com Signed-off-by: Jakub Kicinski Signed-off-by: Sasha Levin --- drivers/net/ethernet/broadcom/bnxt/bnxt_xdp.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/drivers/net/ethernet/broadcom/bnxt/bnxt_xdp.c b/drivers/net/ethernet/broadcom/bnxt/bnxt_xdp.c index 3e77a96e5a3e39..c94a391b1ba5b2 100644 --- a/drivers/net/ethernet/broadcom/bnxt/bnxt_xdp.c +++ b/drivers/net/ethernet/broadcom/bnxt/bnxt_xdp.c @@ -268,13 +268,11 @@ bool bnxt_rx_xdp(struct bnxt *bp, struct bnxt_rx_ring_info *rxr, u16 cons, case XDP_TX: rx_buf = &rxr->rx_buf_ring[cons]; mapping = rx_buf->mapping - bp->rx_dma_offset; - *event &= BNXT_TX_CMP_EVENT; if (unlikely(xdp_buff_has_frags(xdp))) { struct skb_shared_info *sinfo = xdp_get_shared_info_from_buff(xdp); tx_needed += sinfo->nr_frags; - *event = BNXT_AGG_EVENT; } if (tx_avail < tx_needed) { @@ -287,6 +285,7 @@ bool bnxt_rx_xdp(struct bnxt *bp, struct bnxt_rx_ring_info *rxr, u16 cons, dma_sync_single_for_device(&pdev->dev, mapping + offset, *len, bp->rx_dir); + *event &= ~BNXT_RX_EVENT; *event |= BNXT_TX_EVENT; __bnxt_xmit_xdp(bp, txr, mapping + offset, *len, NEXT_RX(rxr->rx_prod), xdp); From c999153bfb2d1d9b295b7010d920f2a7c6d7595f Mon Sep 17 00:00:00 2001 From: Ilya Maximets Date: Thu, 4 Dec 2025 11:53:32 +0100 Subject: [PATCH 1376/3902] net: openvswitch: fix middle attribute validation in push_nsh() action [ Upstream commit 5ace7ef87f059d68b5f50837ef3e8a1a4870c36e ] The push_nsh() action structure looks like this: OVS_ACTION_ATTR_PUSH_NSH(OVS_KEY_ATTR_NSH(OVS_NSH_KEY_ATTR_BASE,...)) The outermost OVS_ACTION_ATTR_PUSH_NSH attribute is OK'ed by the nla_for_each_nested() inside __ovs_nla_copy_actions(). The innermost OVS_NSH_KEY_ATTR_BASE/MD1/MD2 are OK'ed by the nla_for_each_nested() inside nsh_key_put_from_nlattr(). But nothing checks if the attribute in the middle is OK. We don't even check that this attribute is the OVS_KEY_ATTR_NSH. We just do a double unwrap with a pair of nla_data() calls - first time directly while calling validate_push_nsh() and the second time as part of the nla_for_each_nested() macro, which isn't safe, potentially causing invalid memory access if the size of this attribute is incorrect. The failure may not be noticed during validation due to larger netlink buffer, but cause trouble later during action execution where the buffer is allocated exactly to the size: BUG: KASAN: slab-out-of-bounds in nsh_hdr_from_nlattr+0x1dd/0x6a0 [openvswitch] Read of size 184 at addr ffff88816459a634 by task a.out/22624 CPU: 8 UID: 0 PID: 22624 6.18.0-rc7+ #115 PREEMPT(voluntary) Call Trace: dump_stack_lvl+0x51/0x70 print_address_description.constprop.0+0x2c/0x390 kasan_report+0xdd/0x110 kasan_check_range+0x35/0x1b0 __asan_memcpy+0x20/0x60 nsh_hdr_from_nlattr+0x1dd/0x6a0 [openvswitch] push_nsh+0x82/0x120 [openvswitch] do_execute_actions+0x1405/0x2840 [openvswitch] ovs_execute_actions+0xd5/0x3b0 [openvswitch] ovs_packet_cmd_execute+0x949/0xdb0 [openvswitch] genl_family_rcv_msg_doit+0x1d6/0x2b0 genl_family_rcv_msg+0x336/0x580 genl_rcv_msg+0x9f/0x130 netlink_rcv_skb+0x11f/0x370 genl_rcv+0x24/0x40 netlink_unicast+0x73e/0xaa0 netlink_sendmsg+0x744/0xbf0 __sys_sendto+0x3d6/0x450 do_syscall_64+0x79/0x2c0 entry_SYSCALL_64_after_hwframe+0x76/0x7e Let's add some checks that the attribute is properly sized and it's the only one attribute inside the action. Technically, there is no real reason for OVS_KEY_ATTR_NSH to be there, as we know that we're pushing an NSH header already, it just creates extra nesting, but that's how uAPI works today. So, keeping as it is. Fixes: b2d0f5d5dc53 ("openvswitch: enable NSH support") Reported-by: Junvy Yang Signed-off-by: Ilya Maximets Acked-by: Eelco Chaudron echaudro@redhat.com Reviewed-by: Aaron Conole Link: https://patch.msgid.link/20251204105334.900379-1-i.maximets@ovn.org Signed-off-by: Jakub Kicinski Signed-off-by: Sasha Levin --- net/openvswitch/flow_netlink.c | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/net/openvswitch/flow_netlink.c b/net/openvswitch/flow_netlink.c index 1cb4f97335d87b..2d536901309ea9 100644 --- a/net/openvswitch/flow_netlink.c +++ b/net/openvswitch/flow_netlink.c @@ -2802,13 +2802,20 @@ static int validate_and_copy_set_tun(const struct nlattr *attr, return err; } -static bool validate_push_nsh(const struct nlattr *attr, bool log) +static bool validate_push_nsh(const struct nlattr *a, bool log) { + struct nlattr *nsh_key = nla_data(a); struct sw_flow_match match; struct sw_flow_key key; + /* There must be one and only one NSH header. */ + if (!nla_ok(nsh_key, nla_len(a)) || + nla_total_size(nla_len(nsh_key)) != nla_len(a) || + nla_type(nsh_key) != OVS_KEY_ATTR_NSH) + return false; + ovs_match_init(&match, &key, true, NULL); - return !nsh_key_put_from_nlattr(attr, &match, false, true, log); + return !nsh_key_put_from_nlattr(nsh_key, &match, false, true, log); } /* Return false if there are any non-masked bits set. @@ -3389,7 +3396,7 @@ static int __ovs_nla_copy_actions(struct net *net, const struct nlattr *attr, return -EINVAL; } mac_proto = MAC_PROTO_NONE; - if (!validate_push_nsh(nla_data(a), log)) + if (!validate_push_nsh(a, log)) return -EINVAL; break; From 698f389552abc34531c277cd4dde9c846a976f1b Mon Sep 17 00:00:00 2001 From: Arnd Bergmann Date: Thu, 4 Dec 2025 11:01:28 +0100 Subject: [PATCH 1377/3902] net: ti: icssg-prueth: add PTP_1588_CLOCK_OPTIONAL dependency [ Upstream commit 9e7477a427449a8a3cd00c188e20a880e3d94638 ] The new icssg-prueth driver needs the same dependency as the other parts that use the ptp-1588: WARNING: unmet direct dependencies detected for TI_ICSS_IEP Depends on [m]: NETDEVICES [=y] && ETHERNET [=y] && NET_VENDOR_TI [=y] && PTP_1588_CLOCK_OPTIONAL [=m] && TI_PRUSS [=y] Selected by [y]: - TI_PRUETH [=y] && NETDEVICES [=y] && ETHERNET [=y] && NET_VENDOR_TI [=y] && PRU_REMOTEPROC [=y] && NET_SWITCHDEV [=y] Add the correct dependency on the two drivers missing it, and remove the pointless 'imply' in the process. Fixes: e654b85a693e ("net: ti: icssg-prueth: Add ICSSG Ethernet driver for AM65x SR1.0 platforms") Fixes: 511f6c1ae093 ("net: ti: icssm-prueth: Adds ICSSM Ethernet driver") Signed-off-by: Arnd Bergmann Reviewed-by: Vadim Fedorenko Link: https://patch.msgid.link/20251204100138.1034175-1-arnd@kernel.org Signed-off-by: Jakub Kicinski Signed-off-by: Sasha Levin --- drivers/net/ethernet/ti/Kconfig | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/drivers/net/ethernet/ti/Kconfig b/drivers/net/ethernet/ti/Kconfig index a54d71155263c2..fe5b2926d8ab06 100644 --- a/drivers/net/ethernet/ti/Kconfig +++ b/drivers/net/ethernet/ti/Kconfig @@ -209,6 +209,7 @@ config TI_ICSSG_PRUETH_SR1 depends on PRU_REMOTEPROC depends on NET_SWITCHDEV depends on ARCH_K3 && OF && TI_K3_UDMA_GLUE_LAYER + depends on PTP_1588_CLOCK_OPTIONAL help Support dual Gigabit Ethernet ports over the ICSSG PRU Subsystem. This subsystem is available on the AM65 SR1.0 platform. @@ -234,7 +235,7 @@ config TI_PRUETH depends on PRU_REMOTEPROC depends on NET_SWITCHDEV select TI_ICSS_IEP - imply PTP_1588_CLOCK + depends on PTP_1588_CLOCK_OPTIONAL help Some TI SoCs has Programmable Realtime Unit (PRU) cores which can support Single or Dual Ethernet ports with the help of firmware code From 1d18e7155f64d283561fd97d474b966de8444812 Mon Sep 17 00:00:00 2001 From: Alexey Simakov Date: Fri, 5 Dec 2025 18:58:16 +0300 Subject: [PATCH 1378/3902] broadcom: b44: prevent uninitialized value usage [ Upstream commit 50b3db3e11864cb4e18ff099cfb38e11e7f87a68 ] On execution path with raised B44_FLAG_EXTERNAL_PHY, b44_readphy() leaves bmcr value uninitialized and it is used later in the code. Add check of this flag at the beginning of the b44_nway_reset() and exit early of the function with restarting autonegotiation if an external PHY is used. Fixes: 753f492093da ("[B44]: port to native ssb support") Reviewed-by: Jonas Gorski Reviewed-by: Andrew Lunn Signed-off-by: Alexey Simakov Reviewed-by: Michael Chan Link: https://patch.msgid.link/20251205155815.4348-1-bigalex934@gmail.com Signed-off-by: Jakub Kicinski Signed-off-by: Sasha Levin --- drivers/net/ethernet/broadcom/b44.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/drivers/net/ethernet/broadcom/b44.c b/drivers/net/ethernet/broadcom/b44.c index 0353359c3fe96c..073d7d490d4b61 100644 --- a/drivers/net/ethernet/broadcom/b44.c +++ b/drivers/net/ethernet/broadcom/b44.c @@ -1789,6 +1789,9 @@ static int b44_nway_reset(struct net_device *dev) u32 bmcr; int r; + if (bp->flags & B44_FLAG_EXTERNAL_PHY) + return phy_ethtool_nway_reset(dev); + spin_lock_irq(&bp->lock); b44_readphy(bp, MII_BMCR, &bmcr); b44_readphy(bp, MII_BMCR, &bmcr); From 008fd87cd7082ba3bd9db7df275ffdabab647524 Mon Sep 17 00:00:00 2001 From: Guenter Roeck Date: Fri, 5 Dec 2025 09:10:00 -0800 Subject: [PATCH 1379/3902] selftest: af_unix: Support compilers without flex-array-member-not-at-end support MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [ Upstream commit 06f7cae92fe346fa49a8a9b161124b26cc5c3ed1 ] Fix: gcc: error: unrecognized command-line option ‘-Wflex-array-member-not-at-end’ by making the compiler option dependent on its support. Fixes: 1838731f1072c ("selftest: af_unix: Add -Wall and -Wflex-array-member-not-at-end to CFLAGS.") Cc: Kuniyuki Iwashima Signed-off-by: Guenter Roeck Link: https://patch.msgid.link/20251205171010.515236-7-linux@roeck-us.net Signed-off-by: Jakub Kicinski Signed-off-by: Sasha Levin --- tools/testing/selftests/net/af_unix/Makefile | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/tools/testing/selftests/net/af_unix/Makefile b/tools/testing/selftests/net/af_unix/Makefile index 528d14c598bb59..2889403e354680 100644 --- a/tools/testing/selftests/net/af_unix/Makefile +++ b/tools/testing/selftests/net/af_unix/Makefile @@ -1,4 +1,9 @@ -CFLAGS += $(KHDR_INCLUDES) -Wall -Wflex-array-member-not-at-end +top_srcdir := ../../../../.. +include $(top_srcdir)/scripts/Makefile.compiler + +cc-option = $(call __cc-option, $(CC),,$(1),$(2)) + +CFLAGS += $(KHDR_INCLUDES) -Wall $(call cc-option,-Wflex-array-member-not-at-end) TEST_GEN_PROGS := \ diag_uid \ From aa3f345dbac252f54aeebdb384ef3af78dd77219 Mon Sep 17 00:00:00 2001 From: Guenter Roeck Date: Fri, 5 Dec 2025 09:10:04 -0800 Subject: [PATCH 1380/3902] selftests: net: Fix build warnings MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [ Upstream commit 59546e874403c1dd0cbc42df06fdf8c113f72022 ] Fix ksft.h: In function ‘ksft_ready’: ksft.h:27:9: warning: ignoring return value of ‘write’ declared with attribute ‘warn_unused_result’ ksft.h: In function ‘ksft_wait’: ksft.h:51:9: warning: ignoring return value of ‘read’ declared with attribute ‘warn_unused_result’ by checking the return value of the affected functions and displaying an error message if an error is seen. Fixes: 2b6d490b82668 ("selftests: drv-net: Factor out ksft C helpers") Cc: Joe Damato Signed-off-by: Guenter Roeck Link: https://patch.msgid.link/20251205171010.515236-11-linux@roeck-us.net Signed-off-by: Jakub Kicinski Signed-off-by: Sasha Levin --- tools/testing/selftests/net/lib/ksft.h | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/tools/testing/selftests/net/lib/ksft.h b/tools/testing/selftests/net/lib/ksft.h index 17dc34a612c64e..03912902a6d30c 100644 --- a/tools/testing/selftests/net/lib/ksft.h +++ b/tools/testing/selftests/net/lib/ksft.h @@ -24,7 +24,8 @@ static inline void ksft_ready(void) fd = STDOUT_FILENO; } - write(fd, msg, sizeof(msg)); + if (write(fd, msg, sizeof(msg)) < 0) + perror("write()"); if (fd != STDOUT_FILENO) close(fd); } @@ -48,7 +49,8 @@ static inline void ksft_wait(void) fd = STDIN_FILENO; } - read(fd, &byte, sizeof(byte)); + if (read(fd, &byte, sizeof(byte)) < 0) + perror("read()"); if (fd != STDIN_FILENO) close(fd); } From 9ed53331e41895cd603c168ba1c0619a14520921 Mon Sep 17 00:00:00 2001 From: Guenter Roeck Date: Fri, 5 Dec 2025 09:10:07 -0800 Subject: [PATCH 1381/3902] selftests: net: tfo: Fix build warning MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [ Upstream commit 91dc09a609d9443e6b34bdb355a18d579a95e132 ] Fix tfo.c: In function ‘run_server’: tfo.c:84:9: warning: ignoring return value of ‘read’ declared with attribute ‘warn_unused_result’ by evaluating the return value from read() and displaying an error message if it reports an error. Fixes: c65b5bb2329e3 ("selftests: net: add passive TFO test binary") Cc: David Wei Signed-off-by: Guenter Roeck Link: https://patch.msgid.link/20251205171010.515236-14-linux@roeck-us.net Signed-off-by: Jakub Kicinski Signed-off-by: Sasha Levin --- tools/testing/selftests/net/tfo.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tools/testing/selftests/net/tfo.c b/tools/testing/selftests/net/tfo.c index eb3cac5e583c9d..8d82140f0f7679 100644 --- a/tools/testing/selftests/net/tfo.c +++ b/tools/testing/selftests/net/tfo.c @@ -81,7 +81,8 @@ static void run_server(void) if (getsockopt(connfd, SOL_SOCKET, SO_INCOMING_NAPI_ID, &opt, &len) < 0) error(1, errno, "getsockopt(SO_INCOMING_NAPI_ID)"); - read(connfd, buf, 64); + if (read(connfd, buf, 64) < 0) + perror("read()"); fprintf(outfile, "%d\n", opt); fclose(outfile); From 1b5c3b8826d4550513aa2191885a35f98a9f2f09 Mon Sep 17 00:00:00 2001 From: Jakub Kicinski Date: Sat, 6 Dec 2025 17:09:39 -0800 Subject: [PATCH 1382/3902] inet: frags: avoid theoretical race in ip_frag_reinit() [ Upstream commit 8ef522c8a59a048117f7e05eb5213043c02f986f ] In ip_frag_reinit() we want to move the frag timeout timer into the future. If the timer fires in the meantime we inadvertently scheduled it again, and since the timer assumes a ref on frag_queue we need to acquire one to balance things out. This is technically racy, we should have acquired the reference _before_ we touch the timer, it may fire again before we take the ref. Avoid this entire dance by using mod_timer_pending() which only modifies the timer if its pending (and which exists since Linux v2.6.30) Note that this was the only place we ever took a ref on frag_queue since Eric's conversion to RCU. So we could potentially replace the whole refcnt field with an atomic flag and a bit more RCU. Fixes: 1da177e4c3f4 ("Linux-2.6.12-rc2") Reviewed-by: Eric Dumazet Link: https://patch.msgid.link/20251207010942.1672972-2-kuba@kernel.org Signed-off-by: Jakub Kicinski Signed-off-by: Sasha Levin --- net/ipv4/inet_fragment.c | 4 +++- net/ipv4/ip_fragment.c | 4 +--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/net/ipv4/inet_fragment.c b/net/ipv4/inet_fragment.c index 025895eb6ec597..30f4fa50ee2d73 100644 --- a/net/ipv4/inet_fragment.c +++ b/net/ipv4/inet_fragment.c @@ -327,7 +327,9 @@ static struct inet_frag_queue *inet_frag_alloc(struct fqdir *fqdir, timer_setup(&q->timer, f->frag_expire, 0); spin_lock_init(&q->lock); - /* One reference for the timer, one for the hash table. */ + /* One reference for the timer, one for the hash table. + * We never take any extra references, only decrement this field. + */ refcount_set(&q->refcnt, 2); return q; diff --git a/net/ipv4/ip_fragment.c b/net/ipv4/ip_fragment.c index f7012479713ba6..d7bccdc9dc6938 100644 --- a/net/ipv4/ip_fragment.c +++ b/net/ipv4/ip_fragment.c @@ -242,10 +242,8 @@ static int ip_frag_reinit(struct ipq *qp) { unsigned int sum_truesize = 0; - if (!mod_timer(&qp->q.timer, jiffies + qp->q.fqdir->timeout)) { - refcount_inc(&qp->q.refcnt); + if (!mod_timer_pending(&qp->q.timer, jiffies + qp->q.fqdir->timeout)) return -ETIMEDOUT; - } sum_truesize = inet_frag_rbtree_purge(&qp->q.rb_fragments, SKB_DROP_REASON_FRAG_TOO_FAR); From 58e7108971fa61f280590903223a2ced65a1e942 Mon Sep 17 00:00:00 2001 From: Jakub Kicinski Date: Sat, 6 Dec 2025 17:09:40 -0800 Subject: [PATCH 1383/3902] inet: frags: add inet_frag_queue_flush() [ Upstream commit 1231eec6994be29d6bb5c303dfa54731ed9fc0e6 ] Instead of exporting inet_frag_rbtree_purge() which requires that caller takes care of memory accounting, add a new helper. We will need to call it from a few places in the next patch. Reviewed-by: Eric Dumazet Link: https://patch.msgid.link/20251207010942.1672972-3-kuba@kernel.org Signed-off-by: Jakub Kicinski Stable-dep-of: 006a5035b495 ("inet: frags: flush pending skbs in fqdir_pre_exit()") Signed-off-by: Sasha Levin --- include/net/inet_frag.h | 5 ++--- net/ipv4/inet_fragment.c | 15 ++++++++++++--- net/ipv4/ip_fragment.c | 6 +----- 3 files changed, 15 insertions(+), 11 deletions(-) diff --git a/include/net/inet_frag.h b/include/net/inet_frag.h index 0eccd9c3a883fb..3ffaceee7bbc0a 100644 --- a/include/net/inet_frag.h +++ b/include/net/inet_frag.h @@ -141,9 +141,8 @@ void inet_frag_kill(struct inet_frag_queue *q, int *refs); void inet_frag_destroy(struct inet_frag_queue *q); struct inet_frag_queue *inet_frag_find(struct fqdir *fqdir, void *key); -/* Free all skbs in the queue; return the sum of their truesizes. */ -unsigned int inet_frag_rbtree_purge(struct rb_root *root, - enum skb_drop_reason reason); +void inet_frag_queue_flush(struct inet_frag_queue *q, + enum skb_drop_reason reason); static inline void inet_frag_putn(struct inet_frag_queue *q, int refs) { diff --git a/net/ipv4/inet_fragment.c b/net/ipv4/inet_fragment.c index 30f4fa50ee2d73..1bf969b5a1cb55 100644 --- a/net/ipv4/inet_fragment.c +++ b/net/ipv4/inet_fragment.c @@ -263,8 +263,8 @@ static void inet_frag_destroy_rcu(struct rcu_head *head) kmem_cache_free(f->frags_cachep, q); } -unsigned int inet_frag_rbtree_purge(struct rb_root *root, - enum skb_drop_reason reason) +static unsigned int +inet_frag_rbtree_purge(struct rb_root *root, enum skb_drop_reason reason) { struct rb_node *p = rb_first(root); unsigned int sum = 0; @@ -284,7 +284,16 @@ unsigned int inet_frag_rbtree_purge(struct rb_root *root, } return sum; } -EXPORT_SYMBOL(inet_frag_rbtree_purge); + +void inet_frag_queue_flush(struct inet_frag_queue *q, + enum skb_drop_reason reason) +{ + unsigned int sum; + + sum = inet_frag_rbtree_purge(&q->rb_fragments, reason); + sub_frag_mem_limit(q->fqdir, sum); +} +EXPORT_SYMBOL(inet_frag_queue_flush); void inet_frag_destroy(struct inet_frag_queue *q) { diff --git a/net/ipv4/ip_fragment.c b/net/ipv4/ip_fragment.c index d7bccdc9dc6938..32f1c1a46ba72b 100644 --- a/net/ipv4/ip_fragment.c +++ b/net/ipv4/ip_fragment.c @@ -240,14 +240,10 @@ static int ip_frag_too_far(struct ipq *qp) static int ip_frag_reinit(struct ipq *qp) { - unsigned int sum_truesize = 0; - if (!mod_timer_pending(&qp->q.timer, jiffies + qp->q.fqdir->timeout)) return -ETIMEDOUT; - sum_truesize = inet_frag_rbtree_purge(&qp->q.rb_fragments, - SKB_DROP_REASON_FRAG_TOO_FAR); - sub_frag_mem_limit(qp->q.fqdir, sum_truesize); + inet_frag_queue_flush(&qp->q, SKB_DROP_REASON_FRAG_TOO_FAR); qp->q.flags = 0; qp->q.len = 0; From c70df25214ac9b32b53e18e6ae3b8f073ffa6903 Mon Sep 17 00:00:00 2001 From: Jakub Kicinski Date: Sat, 6 Dec 2025 17:09:41 -0800 Subject: [PATCH 1384/3902] inet: frags: flush pending skbs in fqdir_pre_exit() [ Upstream commit 006a5035b495dec008805df249f92c22c89c3d2e ] We have been seeing occasional deadlocks on pernet_ops_rwsem since September in NIPA. The stuck task was usually modprobe (often loading a driver like ipvlan), trying to take the lock as a Writer. lockdep does not track readers for rwsems so the read wasn't obvious from the reports. On closer inspection the Reader holding the lock was conntrack looping forever in nf_conntrack_cleanup_net_list(). Based on past experience with occasional NIPA crashes I looked thru the tests which run before the crash and noticed that the crash follows ip_defrag.sh. An immediate red flag. Scouring thru (de)fragmentation queues reveals skbs sitting around, holding conntrack references. The problem is that since conntrack depends on nf_defrag_ipv6, nf_defrag_ipv6 will load first. Since nf_defrag_ipv6 loads first its netns exit hooks run _after_ conntrack's netns exit hook. Flush all fragment queue SKBs during fqdir_pre_exit() to release conntrack references before conntrack cleanup runs. Also flush the queues in timer expiry handlers when they discover fqdir->dead is set, in case packet sneaks in while we're running the pre_exit flush. The commit under Fixes is not exactly the culprit, but I think previously the timer firing would eventually unblock the spinning conntrack. Fixes: d5dd88794a13 ("inet: fix various use-after-free in defrags units") Reviewed-by: Eric Dumazet Link: https://patch.msgid.link/20251207010942.1672972-4-kuba@kernel.org Signed-off-by: Jakub Kicinski Signed-off-by: Sasha Levin --- include/net/inet_frag.h | 13 +------------ include/net/ipv6_frag.h | 9 ++++++--- net/ipv4/inet_fragment.c | 36 ++++++++++++++++++++++++++++++++++++ net/ipv4/ip_fragment.c | 12 +++++++----- 4 files changed, 50 insertions(+), 20 deletions(-) diff --git a/include/net/inet_frag.h b/include/net/inet_frag.h index 3ffaceee7bbc0a..365925c9d26286 100644 --- a/include/net/inet_frag.h +++ b/include/net/inet_frag.h @@ -123,18 +123,7 @@ void inet_frags_fini(struct inet_frags *); int fqdir_init(struct fqdir **fqdirp, struct inet_frags *f, struct net *net); -static inline void fqdir_pre_exit(struct fqdir *fqdir) -{ - /* Prevent creation of new frags. - * Pairs with READ_ONCE() in inet_frag_find(). - */ - WRITE_ONCE(fqdir->high_thresh, 0); - - /* Pairs with READ_ONCE() in inet_frag_kill(), ip_expire() - * and ip6frag_expire_frag_queue(). - */ - WRITE_ONCE(fqdir->dead, true); -} +void fqdir_pre_exit(struct fqdir *fqdir); void fqdir_exit(struct fqdir *fqdir); void inet_frag_kill(struct inet_frag_queue *q, int *refs); diff --git a/include/net/ipv6_frag.h b/include/net/ipv6_frag.h index 38ef66826939ee..41d9fc6965f9a0 100644 --- a/include/net/ipv6_frag.h +++ b/include/net/ipv6_frag.h @@ -69,9 +69,6 @@ ip6frag_expire_frag_queue(struct net *net, struct frag_queue *fq) int refs = 1; rcu_read_lock(); - /* Paired with the WRITE_ONCE() in fqdir_pre_exit(). */ - if (READ_ONCE(fq->q.fqdir->dead)) - goto out_rcu_unlock; spin_lock(&fq->q.lock); if (fq->q.flags & INET_FRAG_COMPLETE) @@ -80,6 +77,12 @@ ip6frag_expire_frag_queue(struct net *net, struct frag_queue *fq) fq->q.flags |= INET_FRAG_DROP; inet_frag_kill(&fq->q, &refs); + /* Paired with the WRITE_ONCE() in fqdir_pre_exit(). */ + if (READ_ONCE(fq->q.fqdir->dead)) { + inet_frag_queue_flush(&fq->q, 0); + goto out; + } + dev = dev_get_by_index_rcu(net, fq->iif); if (!dev) goto out; diff --git a/net/ipv4/inet_fragment.c b/net/ipv4/inet_fragment.c index 1bf969b5a1cb55..001ee5c4d962e3 100644 --- a/net/ipv4/inet_fragment.c +++ b/net/ipv4/inet_fragment.c @@ -218,6 +218,41 @@ static int __init inet_frag_wq_init(void) pure_initcall(inet_frag_wq_init); +void fqdir_pre_exit(struct fqdir *fqdir) +{ + struct inet_frag_queue *fq; + struct rhashtable_iter hti; + + /* Prevent creation of new frags. + * Pairs with READ_ONCE() in inet_frag_find(). + */ + WRITE_ONCE(fqdir->high_thresh, 0); + + /* Pairs with READ_ONCE() in inet_frag_kill(), ip_expire() + * and ip6frag_expire_frag_queue(). + */ + WRITE_ONCE(fqdir->dead, true); + + rhashtable_walk_enter(&fqdir->rhashtable, &hti); + rhashtable_walk_start(&hti); + + while ((fq = rhashtable_walk_next(&hti))) { + if (IS_ERR(fq)) { + if (PTR_ERR(fq) != -EAGAIN) + break; + continue; + } + spin_lock_bh(&fq->lock); + if (!(fq->flags & INET_FRAG_COMPLETE)) + inet_frag_queue_flush(fq, 0); + spin_unlock_bh(&fq->lock); + } + + rhashtable_walk_stop(&hti); + rhashtable_walk_exit(&hti); +} +EXPORT_SYMBOL(fqdir_pre_exit); + void fqdir_exit(struct fqdir *fqdir) { INIT_WORK(&fqdir->destroy_work, fqdir_work_fn); @@ -290,6 +325,7 @@ void inet_frag_queue_flush(struct inet_frag_queue *q, { unsigned int sum; + reason = reason ?: SKB_DROP_REASON_FRAG_REASM_TIMEOUT; sum = inet_frag_rbtree_purge(&q->rb_fragments, reason); sub_frag_mem_limit(q->fqdir, sum); } diff --git a/net/ipv4/ip_fragment.c b/net/ipv4/ip_fragment.c index 32f1c1a46ba72b..56b0f738d2f27b 100644 --- a/net/ipv4/ip_fragment.c +++ b/net/ipv4/ip_fragment.c @@ -134,11 +134,6 @@ static void ip_expire(struct timer_list *t) net = qp->q.fqdir->net; rcu_read_lock(); - - /* Paired with WRITE_ONCE() in fqdir_pre_exit(). */ - if (READ_ONCE(qp->q.fqdir->dead)) - goto out_rcu_unlock; - spin_lock(&qp->q.lock); if (qp->q.flags & INET_FRAG_COMPLETE) @@ -146,6 +141,13 @@ static void ip_expire(struct timer_list *t) qp->q.flags |= INET_FRAG_DROP; inet_frag_kill(&qp->q, &refs); + + /* Paired with WRITE_ONCE() in fqdir_pre_exit(). */ + if (READ_ONCE(qp->q.fqdir->dead)) { + inet_frag_queue_flush(&qp->q, 0); + goto out; + } + __IP_INC_STATS(net, IPSTATS_MIB_REASMFAILS); __IP_INC_STATS(net, IPSTATS_MIB_REASMTIMEOUT); From 4bd2b89f4028f250dd1c1625eb3da1979b04a5e8 Mon Sep 17 00:00:00 2001 From: Fernando Fernandez Mancera Date: Fri, 5 Dec 2025 12:58:01 +0100 Subject: [PATCH 1385/3902] netfilter: nf_conncount: fix leaked ct in error paths [ Upstream commit 2e2a720766886190a6d35c116794693aabd332b6 ] There are some situations where ct might be leaked as error paths are skipping the refcounted check and return immediately. In order to solve it make sure that the check is always called. Fixes: be102eb6a0e7 ("netfilter: nf_conncount: rework API to use sk_buff directly") Signed-off-by: Fernando Fernandez Mancera Signed-off-by: Florian Westphal Signed-off-by: Sasha Levin --- net/netfilter/nf_conncount.c | 25 ++++++++++++++----------- 1 file changed, 14 insertions(+), 11 deletions(-) diff --git a/net/netfilter/nf_conncount.c b/net/netfilter/nf_conncount.c index b84cfb5616df4d..3c1b155f7a0ea3 100644 --- a/net/netfilter/nf_conncount.c +++ b/net/netfilter/nf_conncount.c @@ -172,14 +172,14 @@ static int __nf_conncount_add(struct net *net, struct nf_conn *found_ct; unsigned int collect = 0; bool refcounted = false; + int err = 0; if (!get_ct_or_tuple_from_skb(net, skb, l3num, &ct, &tuple, &zone, &refcounted)) return -ENOENT; if (ct && nf_ct_is_confirmed(ct)) { - if (refcounted) - nf_ct_put(ct); - return -EEXIST; + err = -EEXIST; + goto out_put; } if ((u32)jiffies == list->last_gc) @@ -231,12 +231,16 @@ static int __nf_conncount_add(struct net *net, } add_new_node: - if (WARN_ON_ONCE(list->count > INT_MAX)) - return -EOVERFLOW; + if (WARN_ON_ONCE(list->count > INT_MAX)) { + err = -EOVERFLOW; + goto out_put; + } conn = kmem_cache_alloc(conncount_conn_cachep, GFP_ATOMIC); - if (conn == NULL) - return -ENOMEM; + if (conn == NULL) { + err = -ENOMEM; + goto out_put; + } conn->tuple = tuple; conn->zone = *zone; @@ -249,7 +253,7 @@ static int __nf_conncount_add(struct net *net, out_put: if (refcounted) nf_ct_put(ct); - return 0; + return err; } int nf_conncount_add_skb(struct net *net, @@ -446,11 +450,10 @@ insert_tree(struct net *net, rb_link_node_rcu(&rbconn->node, parent, rbnode); rb_insert_color(&rbconn->node, root); - - if (refcounted) - nf_ct_put(ct); } out_unlock: + if (refcounted) + nf_ct_put(ct); spin_unlock_bh(&nf_conncount_locks[hash]); return count; } From 689a627d14788ad772e0fa24c2e57a23dbc7ce90 Mon Sep 17 00:00:00 2001 From: Slavin Liu Date: Fri, 21 Nov 2025 16:52:13 +0800 Subject: [PATCH 1386/3902] ipvs: fix ipv4 null-ptr-deref in route error path MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [ Upstream commit ad891bb3d079a46a821bf2b8867854645191bab0 ] The IPv4 code path in __ip_vs_get_out_rt() calls dst_link_failure() without ensuring skb->dev is set, leading to a NULL pointer dereference in fib_compute_spec_dst() when ipv4_link_failure() attempts to send ICMP destination unreachable messages. The issue emerged after commit ed0de45a1008 ("ipv4: recompile ip options in ipv4_link_failure") started calling __ip_options_compile() from ipv4_link_failure(). This code path eventually calls fib_compute_spec_dst() which dereferences skb->dev. An attempt was made to fix the NULL skb->dev dereference in commit 0113d9c9d1cc ("ipv4: fix null-deref in ipv4_link_failure"), but it only addressed the immediate dev_net(skb->dev) dereference by using a fallback device. The fix was incomplete because fib_compute_spec_dst() later in the call chain still accesses skb->dev directly, which remains NULL when IPVS calls dst_link_failure(). The crash occurs when: 1. IPVS processes a packet in NAT mode with a misconfigured destination 2. Route lookup fails in __ip_vs_get_out_rt() before establishing a route 3. The error path calls dst_link_failure(skb) with skb->dev == NULL 4. ipv4_link_failure() → ipv4_send_dest_unreach() → __ip_options_compile() → fib_compute_spec_dst() 5. fib_compute_spec_dst() dereferences NULL skb->dev Apply the same fix used for IPv6 in commit 326bf17ea5d4 ("ipvs: fix ipv6 route unreach panic"): set skb->dev from skb_dst(skb)->dev before calling dst_link_failure(). KASAN: null-ptr-deref in range [0x0000000000000328-0x000000000000032f] CPU: 1 PID: 12732 Comm: syz.1.3469 Not tainted 6.6.114 #2 RIP: 0010:__in_dev_get_rcu include/linux/inetdevice.h:233 RIP: 0010:fib_compute_spec_dst+0x17a/0x9f0 net/ipv4/fib_frontend.c:285 Call Trace: spec_dst_fill net/ipv4/ip_options.c:232 spec_dst_fill net/ipv4/ip_options.c:229 __ip_options_compile+0x13a1/0x17d0 net/ipv4/ip_options.c:330 ipv4_send_dest_unreach net/ipv4/route.c:1252 ipv4_link_failure+0x702/0xb80 net/ipv4/route.c:1265 dst_link_failure include/net/dst.h:437 __ip_vs_get_out_rt+0x15fd/0x19e0 net/netfilter/ipvs/ip_vs_xmit.c:412 ip_vs_nat_xmit+0x1d8/0xc80 net/netfilter/ipvs/ip_vs_xmit.c:764 Fixes: ed0de45a1008 ("ipv4: recompile ip options in ipv4_link_failure") Signed-off-by: Slavin Liu Acked-by: Julian Anastasov Signed-off-by: Florian Westphal Signed-off-by: Sasha Levin --- net/netfilter/ipvs/ip_vs_xmit.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/net/netfilter/ipvs/ip_vs_xmit.c b/net/netfilter/ipvs/ip_vs_xmit.c index 95af252b29397d..618fbe1240b54c 100644 --- a/net/netfilter/ipvs/ip_vs_xmit.c +++ b/net/netfilter/ipvs/ip_vs_xmit.c @@ -409,6 +409,9 @@ __ip_vs_get_out_rt(struct netns_ipvs *ipvs, int skb_af, struct sk_buff *skb, return -1; err_unreach: + if (!skb->dev) + skb->dev = skb_dst(skb)->dev; + dst_link_failure(skb); return -1; } From 82abf247e013330a43580880e47290c8782ac791 Mon Sep 17 00:00:00 2001 From: Florian Westphal Date: Tue, 9 Dec 2025 00:03:36 +0100 Subject: [PATCH 1387/3902] selftests: netfilter: prefer xfail in case race wasn't triggered [ Upstream commit b8a81b0ce539e021ac72825238aea1eb657000f0 ] Jakub says: "We try to reserve SKIP for tests skipped because tool is missing in env, something isn't built into the kernel etc." use xfail, we can't force the race condition to appear at will so its expected that the test 'fails' occasionally. Fixes: 78a588363587 ("selftests: netfilter: add conntrack clash resolution test case") Reported-by: Jakub Kicinski Closes: https://lore.kernel.org/netdev/20251206175647.5c32f419@kernel.org/ Signed-off-by: Florian Westphal Signed-off-by: Sasha Levin --- tools/testing/selftests/net/netfilter/conntrack_clash.sh | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/tools/testing/selftests/net/netfilter/conntrack_clash.sh b/tools/testing/selftests/net/netfilter/conntrack_clash.sh index 7fc6c5dbd5516e..84b8eb12143ae7 100755 --- a/tools/testing/selftests/net/netfilter/conntrack_clash.sh +++ b/tools/testing/selftests/net/netfilter/conntrack_clash.sh @@ -116,7 +116,7 @@ run_one_clash_test() # not a failure: clash resolution logic did not trigger. # With right timing, xmit completed sequentially and # no parallel insertion occurs. - return $ksft_skip + return $ksft_xfail } run_clash_test() @@ -133,12 +133,12 @@ run_clash_test() if [ $rv -eq 0 ];then echo "PASS: clash resolution test for $daddr:$dport on attempt $i" return 0 - elif [ $rv -eq $ksft_skip ]; then + elif [ $rv -eq $ksft_xfail ]; then softerr=1 fi done - [ $softerr -eq 1 ] && echo "SKIP: clash resolution for $daddr:$dport did not trigger" + [ $softerr -eq 1 ] && echo "XFAIL: clash resolution for $daddr:$dport did not trigger" } ip link add veth0 netns "$nsclient1" type veth peer name veth0 netns "$nsrouter" @@ -167,8 +167,7 @@ load_simple_ruleset "$nsclient2" run_clash_test "$nsclient2" "$nsclient2" 127.0.0.1 9001 if [ $clash_resolution_active -eq 0 ];then - [ "$ret" -eq 0 ] && ret=$ksft_skip - echo "SKIP: Clash resolution did not trigger" + [ "$ret" -eq 0 ] && ret=$ksft_xfail fi exit $ret From 21fdcc00656a60af3c7aae2dea8dd96abd35519c Mon Sep 17 00:00:00 2001 From: Junrui Luo Date: Thu, 4 Dec 2025 21:30:47 +0800 Subject: [PATCH 1388/3902] caif: fix integer underflow in cffrml_receive() [ Upstream commit 8a11ff0948b5ad09b71896b7ccc850625f9878d1 ] The cffrml_receive() function extracts a length field from the packet header and, when FCS is disabled, subtracts 2 from this length without validating that len >= 2. If an attacker sends a malicious packet with a length field of 0 or 1 to an interface with FCS disabled, the subtraction causes an integer underflow. This can lead to memory exhaustion and kernel instability, potential information disclosure if padding contains uninitialized kernel memory. Fix this by validating that len >= 2 before performing the subtraction. Reported-by: Yuhao Jiang Reported-by: Junrui Luo Fixes: b482cd2053e3 ("net-caif: add CAIF core protocol stack") Signed-off-by: Junrui Luo Reviewed-by: Simon Horman Link: https://patch.msgid.link/SYBPR01MB7881511122BAFEA8212A1608AFA6A@SYBPR01MB7881.ausprd01.prod.outlook.com Signed-off-by: Jakub Kicinski Signed-off-by: Sasha Levin --- net/caif/cffrml.c | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/net/caif/cffrml.c b/net/caif/cffrml.c index 6651a8dc62e04d..d4d63586053ad4 100644 --- a/net/caif/cffrml.c +++ b/net/caif/cffrml.c @@ -92,8 +92,15 @@ static int cffrml_receive(struct cflayer *layr, struct cfpkt *pkt) len = le16_to_cpu(tmp); /* Subtract for FCS on length if FCS is not used. */ - if (!this->dofcs) + if (!this->dofcs) { + if (len < 2) { + ++cffrml_rcv_error; + pr_err("Invalid frame length (%d)\n", len); + cfpkt_destroy(pkt); + return -EPROTO; + } len -= 2; + } if (cfpkt_setlen(pkt, len) < 0) { ++cffrml_rcv_error; From 43d9a530c8c094d137159784e7c951c65f11ec6c Mon Sep 17 00:00:00 2001 From: Victor Nogueira Date: Mon, 8 Dec 2025 16:01:24 -0300 Subject: [PATCH 1389/3902] net/sched: ets: Remove drr class from the active list if it changes to strict [ Upstream commit b1e125ae425aba9b45252e933ca8df52a843ec70 ] Whenever a user issues an ets qdisc change command, transforming a drr class into a strict one, the ets code isn't checking whether that class was in the active list and removing it. This means that, if a user changes a strict class (which was in the active list) back to a drr one, that class will be added twice to the active list [1]. Doing so with the following commands: tc qdisc add dev lo root handle 1: ets bands 2 strict 1 tc qdisc add dev lo parent 1:2 handle 20: \ tbf rate 8bit burst 100b latency 1s tc filter add dev lo parent 1: basic classid 1:2 ping -c1 -W0.01 -s 56 127.0.0.1 tc qdisc change dev lo root handle 1: ets bands 2 strict 2 tc qdisc change dev lo root handle 1: ets bands 2 strict 1 ping -c1 -W0.01 -s 56 127.0.0.1 Will trigger the following splat with list debug turned on: [ 59.279014][ T365] ------------[ cut here ]------------ [ 59.279452][ T365] list_add double add: new=ffff88801d60e350, prev=ffff88801d60e350, next=ffff88801d60e2c0. [ 59.280153][ T365] WARNING: CPU: 3 PID: 365 at lib/list_debug.c:35 __list_add_valid_or_report+0x17f/0x220 [ 59.280860][ T365] Modules linked in: [ 59.281165][ T365] CPU: 3 UID: 0 PID: 365 Comm: tc Not tainted 6.18.0-rc7-00105-g7e9f13163c13-dirty #239 PREEMPT(voluntary) [ 59.281977][ T365] Hardware name: Bochs Bochs, BIOS Bochs 01/01/2011 [ 59.282391][ T365] RIP: 0010:__list_add_valid_or_report+0x17f/0x220 [ 59.282842][ T365] Code: 89 c6 e8 d4 b7 0d ff 90 0f 0b 90 90 31 c0 e9 31 ff ff ff 90 48 c7 c7 e0 a0 22 9f 48 89 f2 48 89 c1 4c 89 c6 e8 b2 b7 0d ff 90 <0f> 0b 90 90 31 c0 e9 0f ff ff ff 48 89 f7 48 89 44 24 10 4c 89 44 ... [ 59.288812][ T365] Call Trace: [ 59.289056][ T365] [ 59.289224][ T365] ? srso_alias_return_thunk+0x5/0xfbef5 [ 59.289546][ T365] ets_qdisc_change+0xd2b/0x1e80 [ 59.289891][ T365] ? __lock_acquire+0x7e7/0x1be0 [ 59.290223][ T365] ? __pfx_ets_qdisc_change+0x10/0x10 [ 59.290546][ T365] ? srso_alias_return_thunk+0x5/0xfbef5 [ 59.290898][ T365] ? __mutex_trylock_common+0xda/0x240 [ 59.291228][ T365] ? __pfx___mutex_trylock_common+0x10/0x10 [ 59.291655][ T365] ? srso_alias_return_thunk+0x5/0xfbef5 [ 59.291993][ T365] ? srso_alias_return_thunk+0x5/0xfbef5 [ 59.292313][ T365] ? trace_contention_end+0xc8/0x110 [ 59.292656][ T365] ? srso_alias_return_thunk+0x5/0xfbef5 [ 59.293022][ T365] ? srso_alias_return_thunk+0x5/0xfbef5 [ 59.293351][ T365] tc_modify_qdisc+0x63a/0x1cf0 Fix this by always checking and removing an ets class from the active list when changing it to strict. [1] https://git.kernel.org/pub/scm/linux/kernel/git/netdev/net.git/tree/net/sched/sch_ets.c?id=ce052b9402e461a9aded599f5b47e76bc727f7de#n663 Fixes: cd9b50adc6bb9 ("net/sched: ets: fix crash when flipping from 'strict' to 'quantum'") Acked-by: Jamal Hadi Salim Signed-off-by: Victor Nogueira Reviewed-by: Petr Machata Link: https://patch.msgid.link/20251208190125.1868423-1-victor@mojatatu.com Signed-off-by: Jakub Kicinski Signed-off-by: Sasha Levin --- net/sched/sch_ets.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/net/sched/sch_ets.c b/net/sched/sch_ets.c index ae46643e596d30..306e046276d465 100644 --- a/net/sched/sch_ets.c +++ b/net/sched/sch_ets.c @@ -664,6 +664,10 @@ static int ets_qdisc_change(struct Qdisc *sch, struct nlattr *opt, q->classes[i].deficit = quanta[i]; } } + for (i = q->nstrict; i < nstrict; i++) { + if (cl_is_active(&q->classes[i])) + list_del_init(&q->classes[i].alist); + } WRITE_ONCE(q->nstrict, nstrict); memcpy(q->prio2band, priomap, sizeof(priomap)); From c59137e1314dd7a88b4920d5bf8b9bdeb5b74cb8 Mon Sep 17 00:00:00 2001 From: Dan Carpenter Date: Tue, 9 Dec 2025 09:56:39 +0300 Subject: [PATCH 1390/3902] nfc: pn533: Fix error code in pn533_acr122_poweron_rdr() [ Upstream commit 885bebac9909994050bbbeed0829c727e42bd1b7 ] Set the error code if "transferred != sizeof(cmd)" instead of returning success. Fixes: dbafc28955fa ("NFC: pn533: don't send USB data off of the stack") Signed-off-by: Dan Carpenter Link: https://patch.msgid.link/aTfIJ9tZPmeUF4W1@stanley.mountain Signed-off-by: Jakub Kicinski Signed-off-by: Sasha Levin --- drivers/nfc/pn533/usb.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/nfc/pn533/usb.c b/drivers/nfc/pn533/usb.c index ffd7367ce11945..018a80674f06ed 100644 --- a/drivers/nfc/pn533/usb.c +++ b/drivers/nfc/pn533/usb.c @@ -406,7 +406,7 @@ static int pn533_acr122_poweron_rdr(struct pn533_usb_phy *phy) if (rc || (transferred != sizeof(cmd))) { nfc_err(&phy->udev->dev, "Reader power on cmd error %d\n", rc); - return rc; + return rc ?: -EINVAL; } rc = usb_submit_urb(phy->in_urb, GFP_KERNEL); From 79ea9102e66b9d8b8400a6ae5861f5ef06b4112e Mon Sep 17 00:00:00 2001 From: Florian Westphal Date: Mon, 8 Dec 2025 16:00:34 +0100 Subject: [PATCH 1391/3902] netfilter: nf_nat: remove bogus direction check [ Upstream commit 5ec8ca26fe93103577c904644b0957f069d0051a ] Jakub reports spurious failures of the 'conntrack_reverse_clash.sh' selftest. A bogus test makes nat core resort to port rewrite even though there is no need for this. When the test is made, nf_nat_used_tuple() would already have caused us to return if no other CPU had added a colliding entry. Moreover, nf_nat_used_tuple() would have ignored the colliding entry if their origin tuples had been the same. All that is left to check is if the colliding entry in the hash table is subject to NAT, and, if its not, if our entry matches in the reverse direction, e.g. hash table has addr1:1234 -> addr2:80, and we want to commit addr2:80 -> addr1:1234. Because we already checked that neither the new nor the committed entry is subject to NAT we only have to check origin vs. reply tuple: for non-nat entries, the reply tuple is always the inverted original. Just in case there are more problems extend the error reporting in the selftest while at it and dump conntrack table/stats on error. Reported-by: Jakub Kicinski Closes: https://lore.kernel.org/netdev/20251206175135.4a56591b@kernel.org/ Fixes: d8f84a9bc7c4 ("netfilter: nf_nat: don't try nat source port reallocation for reverse dir clash") Signed-off-by: Florian Westphal Signed-off-by: Sasha Levin --- net/netfilter/nf_nat_core.c | 14 +------------- .../net/netfilter/conntrack_reverse_clash.c | 13 +++++++++---- .../net/netfilter/conntrack_reverse_clash.sh | 2 ++ 3 files changed, 12 insertions(+), 17 deletions(-) diff --git a/net/netfilter/nf_nat_core.c b/net/netfilter/nf_nat_core.c index 78a61dac4ade82..e6b24586d2fed5 100644 --- a/net/netfilter/nf_nat_core.c +++ b/net/netfilter/nf_nat_core.c @@ -294,25 +294,13 @@ nf_nat_used_tuple_new(const struct nf_conntrack_tuple *tuple, ct = nf_ct_tuplehash_to_ctrack(thash); - /* NB: IP_CT_DIR_ORIGINAL should be impossible because - * nf_nat_used_tuple() handles origin collisions. - * - * Handle remote chance other CPU confirmed its ct right after. - */ - if (thash->tuple.dst.dir != IP_CT_DIR_REPLY) - goto out; - /* clashing connection subject to NAT? Retry with new tuple. */ if (READ_ONCE(ct->status) & uses_nat) goto out; if (nf_ct_tuple_equal(&ct->tuplehash[IP_CT_DIR_ORIGINAL].tuple, - &ignored_ct->tuplehash[IP_CT_DIR_REPLY].tuple) && - nf_ct_tuple_equal(&ct->tuplehash[IP_CT_DIR_REPLY].tuple, - &ignored_ct->tuplehash[IP_CT_DIR_ORIGINAL].tuple)) { + &ignored_ct->tuplehash[IP_CT_DIR_REPLY].tuple)) taken = false; - goto out; - } out: nf_ct_put(ct); return taken; diff --git a/tools/testing/selftests/net/netfilter/conntrack_reverse_clash.c b/tools/testing/selftests/net/netfilter/conntrack_reverse_clash.c index 507930cee8cb69..462d628cc3bdba 100644 --- a/tools/testing/selftests/net/netfilter/conntrack_reverse_clash.c +++ b/tools/testing/selftests/net/netfilter/conntrack_reverse_clash.c @@ -33,9 +33,14 @@ static void die(const char *e) exit(111); } -static void die_port(uint16_t got, uint16_t want) +static void die_port(const struct sockaddr_in *sin, uint16_t want) { - fprintf(stderr, "Port number changed, wanted %d got %d\n", want, ntohs(got)); + uint16_t got = ntohs(sin->sin_port); + char str[INET_ADDRSTRLEN]; + + inet_ntop(AF_INET, &sin->sin_addr, str, sizeof(str)); + + fprintf(stderr, "Port number changed, wanted %d got %d from %s\n", want, got, str); exit(1); } @@ -100,7 +105,7 @@ int main(int argc, char *argv[]) die("child recvfrom"); if (peer.sin_port != htons(PORT)) - die_port(peer.sin_port, PORT); + die_port(&peer, PORT); } else { if (sendto(s2, buf, LEN, 0, (struct sockaddr *)&sa1, sizeof(sa1)) != LEN) continue; @@ -109,7 +114,7 @@ int main(int argc, char *argv[]) die("parent recvfrom"); if (peer.sin_port != htons((PORT + 1))) - die_port(peer.sin_port, PORT + 1); + die_port(&peer, PORT + 1); } } diff --git a/tools/testing/selftests/net/netfilter/conntrack_reverse_clash.sh b/tools/testing/selftests/net/netfilter/conntrack_reverse_clash.sh index a24c896347a889..dc7e9d6da0624c 100755 --- a/tools/testing/selftests/net/netfilter/conntrack_reverse_clash.sh +++ b/tools/testing/selftests/net/netfilter/conntrack_reverse_clash.sh @@ -45,6 +45,8 @@ if ip netns exec "$ns0" ./conntrack_reverse_clash; then echo "PASS: No SNAT performed for null bindings" else echo "ERROR: SNAT performed without any matching snat rule" + ip netns exec "$ns0" conntrack -L + ip netns exec "$ns0" conntrack -S exit 1 fi From 95b2dac3aaee87140b260b4f8e33389da102f582 Mon Sep 17 00:00:00 2001 From: Pablo Neira Ayuso Date: Wed, 19 Nov 2025 13:42:05 +0100 Subject: [PATCH 1392/3902] netfilter: nf_tables: remove redundant chain validation on register store [ Upstream commit a67fd55f6a09f4119b7232c19e0f348fe31ab0db ] This validation predates the introduction of the state machine that determines when to enter slow path validation for error reporting. Currently, table validation is perform when: - new rule contains expressions that need validation. - new set element with jump/goto verdict. Validation on register store skips most checks with no basechains, still this walks the graph searching for loops and ensuring expressions are called from the right hook. Remove this. Fixes: a654de8fdc18 ("netfilter: nf_tables: fix chain dependency validation") Signed-off-by: Pablo Neira Ayuso Signed-off-by: Florian Westphal Signed-off-by: Sasha Levin --- net/netfilter/nf_tables_api.c | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/net/netfilter/nf_tables_api.c b/net/netfilter/nf_tables_api.c index eed434e0a9702e..1a204f6371ad14 100644 --- a/net/netfilter/nf_tables_api.c +++ b/net/netfilter/nf_tables_api.c @@ -11678,21 +11678,10 @@ static int nft_validate_register_store(const struct nft_ctx *ctx, enum nft_data_types type, unsigned int len) { - int err; - switch (reg) { case NFT_REG_VERDICT: if (type != NFT_DATA_VERDICT) return -EINVAL; - - if (data != NULL && - (data->verdict.code == NFT_GOTO || - data->verdict.code == NFT_JUMP)) { - err = nft_chain_validate(ctx, data->verdict.chain); - if (err < 0) - return err; - } - break; default: if (type != NFT_DATA_VALUE) From 365417a77d385b481958d3ff0143c47ebb0bfffe Mon Sep 17 00:00:00 2001 From: Florian Westphal Date: Thu, 11 Dec 2025 13:16:49 +0100 Subject: [PATCH 1393/3902] selftests: netfilter: packetdrill: avoid failure on HZ=100 kernel [ Upstream commit fec7b0795548b43e2c3c46e3143c34ef6070341c ] packetdrill --ip_version=ipv4 --mtu=1500 --tolerance_usecs=1000000 --non_fatal packet conntrack_syn_challenge_ack.pkt conntrack v1.4.8 (conntrack-tools): 1 flow entries have been shown. conntrack_syn_challenge_ack.pkt:32: error executing `conntrack -f $NFCT_IP_VERSION \ -L -p tcp --dport 8080 | grep UNREPLIED | grep -q SYN_SENT` command: non-zero status 1 Affected kernel had CONFIG_HZ=100; reset packet was still sitting in backlog. Reported-by: Yi Chen Fixes: a8a388c2aae4 ("selftests: netfilter: add packetdrill based conntrack tests") Signed-off-by: Florian Westphal Signed-off-by: Sasha Levin --- .../net/netfilter/packetdrill/conntrack_syn_challenge_ack.pkt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/testing/selftests/net/netfilter/packetdrill/conntrack_syn_challenge_ack.pkt b/tools/testing/selftests/net/netfilter/packetdrill/conntrack_syn_challenge_ack.pkt index 3442cd29bc9320..cdb3910af95b4d 100644 --- a/tools/testing/selftests/net/netfilter/packetdrill/conntrack_syn_challenge_ack.pkt +++ b/tools/testing/selftests/net/netfilter/packetdrill/conntrack_syn_challenge_ack.pkt @@ -26,7 +26,7 @@ +0.01 > R 643160523:643160523(0) win 0 -+0.01 `conntrack -f $NFCT_IP_VERSION -L -p tcp --dport 8080 2>/dev/null | grep UNREPLIED | grep -q SYN_SENT` ++0.1 `conntrack -f $NFCT_IP_VERSION -L -p tcp --dport 8080 2>/dev/null | grep UNREPLIED | grep -q SYN_SENT` // Must go through. +0.01 > S 0:0(0) win 65535 From 9bec0d7064873639818a622992bdfe652738310f Mon Sep 17 00:00:00 2001 From: Jason Gunthorpe Date: Fri, 5 Dec 2025 14:56:12 -0400 Subject: [PATCH 1394/3902] iommufd/selftest: Make it clearer to gcc that the access is not out of bounds [ Upstream commit 5b244b077c0b0e76573fbb9542cf038e42368901 ] GCC gets a bit confused and reports: In function '_test_cmd_get_hw_info', inlined from 'iommufd_ioas_get_hw_info' at iommufd.c:779:3, inlined from 'wrapper_iommufd_ioas_get_hw_info' at iommufd.c:752:1: >> iommufd_utils.h:804:37: warning: array subscript 'struct iommu_test_hw_info[0]' is partly outside array bounds of 'struct iommu_test_hw_info_buffer_smaller[1]' [-Warray-bounds=] 804 | assert(!info->flags); | ~~~~^~~~~~~ iommufd.c: In function 'wrapper_iommufd_ioas_get_hw_info': iommufd.c:761:11: note: object 'buffer_smaller' of size 4 761 | } buffer_smaller; | ^~~~~~~~~~~~~~ While it is true that "struct iommu_test_hw_info[0]" is partly out of bounds of the input pointer, it is not true that info->flags is out of bounds. Unclear why it warns on this. Reuse an existing properly sized stack buffer and pass a truncated length instead to test the same thing. Fixes: af4fde93c319 ("iommufd/selftest: Add coverage for IOMMU_GET_HW_INFO ioctl") Link: https://patch.msgid.link/r/0-v1-63a2cffb09da+4486-iommufd_gcc_bounds_jgg@nvidia.com Reviewed-by: Kevin Tian Reviewed-by: Nicolin Chen Reported-by: kernel test robot Closes: https://lore.kernel.org/oe-kbuild-all/202512032344.kaAcKFIM-lkp@intel.com/ Signed-off-by: Jason Gunthorpe Signed-off-by: Sasha Levin --- tools/testing/selftests/iommu/iommufd.c | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/tools/testing/selftests/iommu/iommufd.c b/tools/testing/selftests/iommu/iommufd.c index bb4d33dde3c899..1f52140d9d9ee3 100644 --- a/tools/testing/selftests/iommu/iommufd.c +++ b/tools/testing/selftests/iommu/iommufd.c @@ -758,9 +758,6 @@ TEST_F(iommufd_ioas, get_hw_info) struct iommu_test_hw_info info; uint64_t trailing_bytes; } buffer_larger; - struct iommu_test_hw_info_buffer_smaller { - __u32 flags; - } buffer_smaller; if (self->device_id) { uint8_t max_pasid = 0; @@ -792,8 +789,9 @@ TEST_F(iommufd_ioas, get_hw_info) * the fields within the size range still gets updated. */ test_cmd_get_hw_info(self->device_id, - IOMMU_HW_INFO_TYPE_DEFAULT, - &buffer_smaller, sizeof(buffer_smaller)); + IOMMU_HW_INFO_TYPE_DEFAULT, &buffer_exact, + offsetofend(struct iommu_test_hw_info, + flags)); test_cmd_get_hw_info_pasid(self->device_id, &max_pasid); ASSERT_EQ(0, max_pasid); if (variant->pasid_capable) { From b166b8e0a381429fefd9180e67fbc834b3cee82f Mon Sep 17 00:00:00 2001 From: Jason Gunthorpe Date: Tue, 16 Dec 2025 11:53:40 -0400 Subject: [PATCH 1395/3902] iommufd/selftest: Check for overflow in IOMMU_TEST_OP_ADD_RESERVED [ Upstream commit e6a973af11135439de32ece3b9cbe3bfc043bea8 ] syzkaller found it could overflow math in the test infrastructure and cause a WARN_ON by corrupting the reserved interval tree. This only effects test kernels with CONFIG_IOMMUFD_TEST. Validate the user input length in the test ioctl. Fixes: f4b20bb34c83 ("iommufd: Add kernel support for testing iommufd") Link: https://patch.msgid.link/r/0-v1-cd99f6049ba5+51-iommufd_syz_add_resv_jgg@nvidia.com Reviewed-by: Samiullah Khawaja Reviewed-by: Kevin Tian Tested-by: Yi Liu Reported-by: syzbot+57fdb0cf6a0c5d1f15a2@syzkaller.appspotmail.com Closes: https://lore.kernel.org/all/69368129.a70a0220.38f243.008f.GAE@google.com Signed-off-by: Jason Gunthorpe Signed-off-by: Sasha Levin --- drivers/iommu/iommufd/selftest.c | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/drivers/iommu/iommufd/selftest.c b/drivers/iommu/iommufd/selftest.c index de178827a078a9..dc0947aaac625a 100644 --- a/drivers/iommu/iommufd/selftest.c +++ b/drivers/iommu/iommufd/selftest.c @@ -1257,14 +1257,20 @@ static int iommufd_test_add_reserved(struct iommufd_ucmd *ucmd, unsigned int mockpt_id, unsigned long start, size_t length) { + unsigned long last; struct iommufd_ioas *ioas; int rc; + if (!length) + return -EINVAL; + if (check_add_overflow(start, length - 1, &last)) + return -EOVERFLOW; + ioas = iommufd_get_ioas(ucmd->ictx, mockpt_id); if (IS_ERR(ioas)) return PTR_ERR(ioas); down_write(&ioas->iopt.iova_rwsem); - rc = iopt_reserve_iova(&ioas->iopt, start, start + length - 1, NULL); + rc = iopt_reserve_iova(&ioas->iopt, start, last, NULL); up_write(&ioas->iopt.iova_rwsem); iommufd_put_object(ucmd->ictx, &ioas->obj); return rc; From 730a125ec4678ddeca965fb8a78767b51518769e Mon Sep 17 00:00:00 2001 From: Tetsuo Handa Date: Tue, 25 Nov 2025 22:43:12 +0900 Subject: [PATCH 1396/3902] can: j1939: make j1939_sk_bind() fail if device is no longer registered [ Upstream commit 46cea215dc9444ec32a76b1b6a9cb809e17b64d5 ] There is a theoretical race window in j1939_sk_netdev_event_unregister() where two j1939_sk_bind() calls jump in between read_unlock_bh() and lock_sock(). The assumption jsk->priv == priv can fail if the first j1939_sk_bind() call once made jsk->priv == NULL due to failed j1939_local_ecu_get() call and the second j1939_sk_bind() call again made jsk->priv != NULL due to successful j1939_local_ecu_get() call. Since the socket lock is held by both j1939_sk_netdev_event_unregister() and j1939_sk_bind(), checking ndev->reg_state with the socket lock held can reliably make the second j1939_sk_bind() call fail (and close this race window). Fixes: 7fcbe5b2c6a4 ("can: j1939: implement NETDEV_UNREGISTER notification handler") Signed-off-by: Tetsuo Handa Acked-by: Oleksij Rempel Link: https://patch.msgid.link/5732921e-247e-4957-a364-da74bd7031d7@I-love.SAKURA.ne.jp Signed-off-by: Marc Kleine-Budde Signed-off-by: Sasha Levin --- net/can/j1939/socket.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/net/can/j1939/socket.c b/net/can/j1939/socket.c index 88e7160d424896..e3ba2e9fc0e9b7 100644 --- a/net/can/j1939/socket.c +++ b/net/can/j1939/socket.c @@ -482,6 +482,12 @@ static int j1939_sk_bind(struct socket *sock, struct sockaddr *uaddr, int len) goto out_release_sock; } + if (ndev->reg_state != NETREG_REGISTERED) { + dev_put(ndev); + ret = -ENODEV; + goto out_release_sock; + } + can_ml = can_get_ml_priv(ndev); if (!can_ml) { dev_put(ndev); From 4066b5b546293f44cd6d0e84ece6e3ee7ff27093 Mon Sep 17 00:00:00 2001 From: Gal Pressman Date: Mon, 8 Dec 2025 14:19:01 +0200 Subject: [PATCH 1397/3902] ethtool: Avoid overflowing userspace buffer on stats query [ Upstream commit 7b07be1ff1cb6c49869910518650e8d0abc7d25f ] The ethtool -S command operates across three ioctl calls: ETHTOOL_GSSET_INFO for the size, ETHTOOL_GSTRINGS for the names, and ETHTOOL_GSTATS for the values. If the number of stats changes between these calls (e.g., due to device reconfiguration), userspace's buffer allocation will be incorrect, potentially leading to buffer overflow. Drivers are generally expected to maintain stable stat counts, but some drivers (e.g., mlx5, bnx2x, bna, ksz884x) use dynamic counters, making this scenario possible. Some drivers try to handle this internally: - bnad_get_ethtool_stats() returns early in case stats.n_stats is not equal to the driver's stats count. - micrel/ksz884x also makes sure not to write anything beyond stats.n_stats and overflow the buffer. However, both use stats.n_stats which is already assigned with the value returned from get_sset_count(), hence won't solve the issue described here. Change ethtool_get_strings(), ethtool_get_stats(), ethtool_get_phy_stats() to not return anything in case of a mismatch between userspace's size and get_sset_size(), to prevent buffer overflow. The returned n_stats value will be equal to zero, to reflect that nothing has been returned. This could result in one of two cases when using upstream ethtool, depending on when the size change is detected: 1. When detected in ethtool_get_strings(): # ethtool -S eth2 no stats available 2. When detected in get stats, all stats will be reported as zero. Both cases are presumably transient, and a subsequent ethtool call should succeed. Other than the overflow avoidance, these two cases are very evident (no output/cleared stats), which is arguably better than presenting incorrect/shifted stats. I also considered returning an error instead of a "silent" response, but that seems more destructive towards userspace apps. Notes: - This patch does not claim to fix the inherent race, it only makes sure that we do not overflow the userspace buffer, and makes for a more predictable behavior. - RTNL lock is held during each ioctl, the race window exists between the separate ioctl calls when the lock is released. - Userspace ethtool always fills stats.n_stats, but it is likely that these stats ioctls are implemented in other userspace applications which might not fill it. The added code checks that it's not zero, to prevent any regressions. Fixes: 1da177e4c3f4 ("Linux-2.6.12-rc2") Reviewed-by: Dragos Tatulea Reviewed-by: Tariq Toukan Signed-off-by: Gal Pressman Link: https://patch.msgid.link/20251208121901.3203692-1-gal@nvidia.com Signed-off-by: Paolo Abeni Signed-off-by: Sasha Levin --- net/ethtool/ioctl.c | 30 ++++++++++++++++++++++++------ 1 file changed, 24 insertions(+), 6 deletions(-) diff --git a/net/ethtool/ioctl.c b/net/ethtool/ioctl.c index fa83ddade4f817..9431e305b2333f 100644 --- a/net/ethtool/ioctl.c +++ b/net/ethtool/ioctl.c @@ -2383,7 +2383,10 @@ static int ethtool_get_strings(struct net_device *dev, void __user *useraddr) return -ENOMEM; WARN_ON_ONCE(!ret); - gstrings.len = ret; + if (gstrings.len && gstrings.len != ret) + gstrings.len = 0; + else + gstrings.len = ret; if (gstrings.len) { data = vzalloc(array_size(gstrings.len, ETH_GSTRING_LEN)); @@ -2509,10 +2512,13 @@ static int ethtool_get_stats(struct net_device *dev, void __user *useraddr) if (copy_from_user(&stats, useraddr, sizeof(stats))) return -EFAULT; - stats.n_stats = n_stats; + if (stats.n_stats && stats.n_stats != n_stats) + stats.n_stats = 0; + else + stats.n_stats = n_stats; - if (n_stats) { - data = vzalloc(array_size(n_stats, sizeof(u64))); + if (stats.n_stats) { + data = vzalloc(array_size(stats.n_stats, sizeof(u64))); if (!data) return -ENOMEM; ops->get_ethtool_stats(dev, &stats, data); @@ -2524,7 +2530,9 @@ static int ethtool_get_stats(struct net_device *dev, void __user *useraddr) if (copy_to_user(useraddr, &stats, sizeof(stats))) goto out; useraddr += sizeof(stats); - if (n_stats && copy_to_user(useraddr, data, array_size(n_stats, sizeof(u64)))) + if (stats.n_stats && + copy_to_user(useraddr, data, + array_size(stats.n_stats, sizeof(u64)))) goto out; ret = 0; @@ -2560,6 +2568,10 @@ static int ethtool_get_phy_stats_phydev(struct phy_device *phydev, return -EOPNOTSUPP; n_stats = phy_ops->get_sset_count(phydev); + if (stats->n_stats && stats->n_stats != n_stats) { + stats->n_stats = 0; + return 0; + } ret = ethtool_vzalloc_stats_array(n_stats, data); if (ret) @@ -2580,6 +2592,10 @@ static int ethtool_get_phy_stats_ethtool(struct net_device *dev, return -EOPNOTSUPP; n_stats = ops->get_sset_count(dev, ETH_SS_PHY_STATS); + if (stats->n_stats && stats->n_stats != n_stats) { + stats->n_stats = 0; + return 0; + } ret = ethtool_vzalloc_stats_array(n_stats, data); if (ret) @@ -2616,7 +2632,9 @@ static int ethtool_get_phy_stats(struct net_device *dev, void __user *useraddr) } useraddr += sizeof(stats); - if (copy_to_user(useraddr, data, array_size(stats.n_stats, sizeof(u64)))) + if (stats.n_stats && + copy_to_user(useraddr, data, + array_size(stats.n_stats, sizeof(u64)))) ret = -EFAULT; out: From 5af49c5c3bdb6bc0bf4c58b2177ef7cee7f2c5e7 Mon Sep 17 00:00:00 2001 From: Moshe Shemesh Date: Tue, 9 Dec 2025 14:56:09 +0200 Subject: [PATCH 1398/3902] net/mlx5: fw reset, clear reset requested on drain_fw_reset [ Upstream commit 89a898d63f6f588acf5c104c65c94a38b68c69a6 ] drain_fw_reset() waits for ongoing firmware reset events and blocks new event handling, but does not clear the reset requested flag, and may keep sync reset polling. To fix it, call mlx5_sync_reset_clear_reset_requested() to clear the flag, stop sync reset polling, and resume health polling, ensuring health issues are still detected after the firmware reset drain. Fixes: 16d42d313350 ("net/mlx5: Drain fw_reset when removing device") Signed-off-by: Moshe Shemesh Reviewed-by: Shay Drori Signed-off-by: Tariq Toukan Link: https://patch.msgid.link/1765284977-1363052-2-git-send-email-tariqt@nvidia.com Signed-off-by: Paolo Abeni Signed-off-by: Sasha Levin --- drivers/net/ethernet/mellanox/mlx5/core/fw_reset.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/drivers/net/ethernet/mellanox/mlx5/core/fw_reset.c b/drivers/net/ethernet/mellanox/mlx5/core/fw_reset.c index 89e399606877ba..33df0418e57542 100644 --- a/drivers/net/ethernet/mellanox/mlx5/core/fw_reset.c +++ b/drivers/net/ethernet/mellanox/mlx5/core/fw_reset.c @@ -843,7 +843,8 @@ void mlx5_drain_fw_reset(struct mlx5_core_dev *dev) cancel_work_sync(&fw_reset->reset_reload_work); cancel_work_sync(&fw_reset->reset_now_work); cancel_work_sync(&fw_reset->reset_abort_work); - cancel_delayed_work(&fw_reset->reset_timeout_work); + if (test_bit(MLX5_FW_RESET_FLAGS_RESET_REQUESTED, &fw_reset->reset_flags)) + mlx5_sync_reset_clear_reset_requested(dev, true); } static const struct devlink_param mlx5_fw_reset_devlink_params[] = { From 2d2d97d8a644d3c29cfc3e0783eebea57441cebd Mon Sep 17 00:00:00 2001 From: Moshe Shemesh Date: Tue, 9 Dec 2025 14:56:10 +0200 Subject: [PATCH 1399/3902] net/mlx5: Drain firmware reset in shutdown callback [ Upstream commit 5846a365fc6476b02d6766963cf0985520f0385f ] Invoke drain_fw_reset() in the shutdown callback to ensure all firmware reset handling is completed before shutdown proceeds. Fixes: 16d42d313350 ("net/mlx5: Drain fw_reset when removing device") Signed-off-by: Moshe Shemesh Reviewed-by: Shay Drori Signed-off-by: Tariq Toukan Link: https://patch.msgid.link/1765284977-1363052-3-git-send-email-tariqt@nvidia.com Signed-off-by: Paolo Abeni Signed-off-by: Sasha Levin --- drivers/net/ethernet/mellanox/mlx5/core/main.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/net/ethernet/mellanox/mlx5/core/main.c b/drivers/net/ethernet/mellanox/mlx5/core/main.c index 70c156591b0ba9..9e0c9e6266a47d 100644 --- a/drivers/net/ethernet/mellanox/mlx5/core/main.c +++ b/drivers/net/ethernet/mellanox/mlx5/core/main.c @@ -2189,6 +2189,7 @@ static void shutdown(struct pci_dev *pdev) mlx5_core_info(dev, "Shutdown was called\n"); set_bit(MLX5_BREAK_FW_WAIT, &dev->intf_state); + mlx5_drain_fw_reset(dev); mlx5_drain_health_wq(dev); err = mlx5_try_fast_unload(dev); if (err) From 8c35c2448086870509ede43947845be0833251f0 Mon Sep 17 00:00:00 2001 From: Shay Drory Date: Tue, 9 Dec 2025 14:56:11 +0200 Subject: [PATCH 1400/3902] net/mlx5: fw_tracer, Validate format string parameters [ Upstream commit b35966042d20b14e2d83330049f77deec5229749 ] Add validation for format string parameters in the firmware tracer to prevent potential security vulnerabilities and crashes from malformed format strings received from firmware. The firmware tracer receives format strings from the device firmware and uses them to format trace messages. Without proper validation, bad firmware could provide format strings with invalid format specifiers (e.g., %s, %p, %n) that could lead to crashes, or other undefined behavior. Add mlx5_tracer_validate_params() to validate that all format specifiers in trace strings are limited to safe integer/hex formats (%x, %d, %i, %u, %llx, %lx, etc.). Reject strings containing other format types that could be used to access arbitrary memory or cause crashes. Invalid format strings are added to the trace output for visibility with "BAD_FORMAT: " prefix. Fixes: 70dd6fdb8987 ("net/mlx5: FW tracer, parse traces and kernel tracing support") Signed-off-by: Shay Drory Reviewed-by: Moshe Shemesh Reported-by: Breno Leitao Closes: https://lore.kernel.org/netdev/hanz6rzrb2bqbplryjrakvkbmv4y5jlmtthnvi3thg5slqvelp@t3s3erottr6s/ Signed-off-by: Tariq Toukan Link: https://patch.msgid.link/1765284977-1363052-4-git-send-email-tariqt@nvidia.com Signed-off-by: Paolo Abeni Signed-off-by: Sasha Levin --- .../mellanox/mlx5/core/diag/fw_tracer.c | 83 ++++++++++++++++--- .../mellanox/mlx5/core/diag/fw_tracer.h | 1 + 2 files changed, 74 insertions(+), 10 deletions(-) diff --git a/drivers/net/ethernet/mellanox/mlx5/core/diag/fw_tracer.c b/drivers/net/ethernet/mellanox/mlx5/core/diag/fw_tracer.c index 080e7eab52c7e9..9c86c8c72d049c 100644 --- a/drivers/net/ethernet/mellanox/mlx5/core/diag/fw_tracer.c +++ b/drivers/net/ethernet/mellanox/mlx5/core/diag/fw_tracer.c @@ -33,6 +33,7 @@ #include "lib/eq.h" #include "fw_tracer.h" #include "fw_tracer_tracepoint.h" +#include static int mlx5_query_mtrc_caps(struct mlx5_fw_tracer *tracer) { @@ -358,6 +359,43 @@ static const char *VAL_PARM = "%llx"; static const char *REPLACE_64_VAL_PARM = "%x%x"; static const char *PARAM_CHAR = "%"; +static bool mlx5_is_valid_spec(const char *str) +{ + /* Parse format specifiers to find the actual type. + * Structure: %[flags][width][.precision][length]type + * Skip flags, width, precision & length. + */ + while (isdigit(*str) || *str == '#' || *str == '.' || *str == 'l') + str++; + + /* Check if it's a valid integer/hex specifier: + * Valid formats: %x, %d, %i, %u, etc. + */ + if (*str != 'x' && *str != 'X' && *str != 'd' && *str != 'i' && + *str != 'u' && *str != 'c') + return false; + + return true; +} + +static bool mlx5_tracer_validate_params(const char *str) +{ + const char *substr = str; + + if (!str) + return false; + + substr = strstr(substr, PARAM_CHAR); + while (substr) { + if (!mlx5_is_valid_spec(substr + 1)) + return false; + + substr = strstr(substr + 1, PARAM_CHAR); + } + + return true; +} + static int mlx5_tracer_message_hash(u32 message_id) { return jhash_1word(message_id, 0) & (MESSAGE_HASH_SIZE - 1); @@ -419,6 +457,10 @@ static int mlx5_tracer_get_num_of_params(char *str) char *substr, *pstr = str; int num_of_params = 0; + /* Validate that all parameters are valid before processing */ + if (!mlx5_tracer_validate_params(str)) + return -EINVAL; + /* replace %llx with %x%x */ substr = strstr(pstr, VAL_PARM); while (substr) { @@ -570,14 +612,17 @@ void mlx5_tracer_print_trace(struct tracer_string_format *str_frmt, { char tmp[512]; - snprintf(tmp, sizeof(tmp), str_frmt->string, - str_frmt->params[0], - str_frmt->params[1], - str_frmt->params[2], - str_frmt->params[3], - str_frmt->params[4], - str_frmt->params[5], - str_frmt->params[6]); + if (str_frmt->invalid_string) + snprintf(tmp, sizeof(tmp), "BAD_FORMAT: %s", str_frmt->string); + else + snprintf(tmp, sizeof(tmp), str_frmt->string, + str_frmt->params[0], + str_frmt->params[1], + str_frmt->params[2], + str_frmt->params[3], + str_frmt->params[4], + str_frmt->params[5], + str_frmt->params[6]); trace_mlx5_fw(dev->tracer, trace_timestamp, str_frmt->lost, str_frmt->event_id, tmp); @@ -609,6 +654,13 @@ static int mlx5_tracer_handle_raw_string(struct mlx5_fw_tracer *tracer, return 0; } +static void mlx5_tracer_handle_bad_format_string(struct mlx5_fw_tracer *tracer, + struct tracer_string_format *cur_string) +{ + cur_string->invalid_string = true; + list_add_tail(&cur_string->list, &tracer->ready_strings_list); +} + static int mlx5_tracer_handle_string_trace(struct mlx5_fw_tracer *tracer, struct tracer_event *tracer_event) { @@ -619,12 +671,18 @@ static int mlx5_tracer_handle_string_trace(struct mlx5_fw_tracer *tracer, if (!cur_string) return mlx5_tracer_handle_raw_string(tracer, tracer_event); - cur_string->num_of_params = mlx5_tracer_get_num_of_params(cur_string->string); - cur_string->last_param_num = 0; cur_string->event_id = tracer_event->event_id; cur_string->tmsn = tracer_event->string_event.tmsn; cur_string->timestamp = tracer_event->string_event.timestamp; cur_string->lost = tracer_event->lost_event; + cur_string->last_param_num = 0; + cur_string->num_of_params = mlx5_tracer_get_num_of_params(cur_string->string); + if (cur_string->num_of_params < 0) { + pr_debug("%s Invalid format string parameters\n", + __func__); + mlx5_tracer_handle_bad_format_string(tracer, cur_string); + return 0; + } if (cur_string->num_of_params == 0) /* trace with no params */ list_add_tail(&cur_string->list, &tracer->ready_strings_list); } else { @@ -634,6 +692,11 @@ static int mlx5_tracer_handle_string_trace(struct mlx5_fw_tracer *tracer, __func__, tracer_event->string_event.tmsn); return mlx5_tracer_handle_raw_string(tracer, tracer_event); } + if (cur_string->num_of_params < 0) { + pr_debug("%s string parameter of invalid string, dumping\n", + __func__); + return 0; + } cur_string->last_param_num += 1; if (cur_string->last_param_num > TRACER_MAX_PARAMS) { pr_debug("%s Number of params exceeds the max (%d)\n", diff --git a/drivers/net/ethernet/mellanox/mlx5/core/diag/fw_tracer.h b/drivers/net/ethernet/mellanox/mlx5/core/diag/fw_tracer.h index 5c548bb74f07b6..30d0bcba884791 100644 --- a/drivers/net/ethernet/mellanox/mlx5/core/diag/fw_tracer.h +++ b/drivers/net/ethernet/mellanox/mlx5/core/diag/fw_tracer.h @@ -125,6 +125,7 @@ struct tracer_string_format { struct list_head list; u32 timestamp; bool lost; + bool invalid_string; }; enum mlx5_fw_tracer_ownership_state { From d173741ab4cb7c58c8c9e5cea88b2a0f366e668e Mon Sep 17 00:00:00 2001 From: Shay Drory Date: Tue, 9 Dec 2025 14:56:12 +0200 Subject: [PATCH 1401/3902] net/mlx5: fw_tracer, Handle escaped percent properly [ Upstream commit c0289f67f7d6a0dfba0e92cfe661a5c70c8c6e92 ] The firmware tracer's format string validation and parameter counting did not properly handle escaped percent signs (%%). This caused fw_tracer to count more parameters when trace format strings contained literal percent characters. To fix it, allow %% to pass string validation and skip %% sequences when counting parameters since they represent literal percent signs rather than format specifiers. Fixes: 70dd6fdb8987 ("net/mlx5: FW tracer, parse traces and kernel tracing support") Signed-off-by: Shay Drory Reported-by: Breno Leitao Reviewed-by: Moshe Shemesh Closes: https://lore.kernel.org/netdev/hanz6rzrb2bqbplryjrakvkbmv4y5jlmtthnvi3thg5slqvelp@t3s3erottr6s/ Signed-off-by: Tariq Toukan Link: https://patch.msgid.link/1765284977-1363052-5-git-send-email-tariqt@nvidia.com Signed-off-by: Paolo Abeni Signed-off-by: Sasha Levin --- .../mellanox/mlx5/core/diag/fw_tracer.c | 20 +++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/drivers/net/ethernet/mellanox/mlx5/core/diag/fw_tracer.c b/drivers/net/ethernet/mellanox/mlx5/core/diag/fw_tracer.c index 9c86c8c72d049c..0b82a6a133d6cd 100644 --- a/drivers/net/ethernet/mellanox/mlx5/core/diag/fw_tracer.c +++ b/drivers/net/ethernet/mellanox/mlx5/core/diag/fw_tracer.c @@ -368,11 +368,11 @@ static bool mlx5_is_valid_spec(const char *str) while (isdigit(*str) || *str == '#' || *str == '.' || *str == 'l') str++; - /* Check if it's a valid integer/hex specifier: + /* Check if it's a valid integer/hex specifier or %%: * Valid formats: %x, %d, %i, %u, etc. */ if (*str != 'x' && *str != 'X' && *str != 'd' && *str != 'i' && - *str != 'u' && *str != 'c') + *str != 'u' && *str != 'c' && *str != '%') return false; return true; @@ -390,7 +390,11 @@ static bool mlx5_tracer_validate_params(const char *str) if (!mlx5_is_valid_spec(substr + 1)) return false; - substr = strstr(substr + 1, PARAM_CHAR); + if (*(substr + 1) == '%') + substr = strstr(substr + 2, PARAM_CHAR); + else + substr = strstr(substr + 1, PARAM_CHAR); + } return true; @@ -469,11 +473,15 @@ static int mlx5_tracer_get_num_of_params(char *str) substr = strstr(pstr, VAL_PARM); } - /* count all the % characters */ + /* count all the % characters, but skip %% (escaped percent) */ substr = strstr(str, PARAM_CHAR); while (substr) { - num_of_params += 1; - str = substr + 1; + if (*(substr + 1) != '%') { + num_of_params += 1; + str = substr + 1; + } else { + str = substr + 2; + } substr = strstr(str, PARAM_CHAR); } From a935b5993b9ed52e348d013cd00b8486d9be72d0 Mon Sep 17 00:00:00 2001 From: Shay Drory Date: Tue, 9 Dec 2025 14:56:13 +0200 Subject: [PATCH 1402/3902] net/mlx5: Serialize firmware reset with devlink [ Upstream commit 367e501f8b095eca08d2eb0ba4ccea5b5e82c169 ] The firmware reset mechanism can be triggered by asynchronous events, which may race with other devlink operations like devlink reload or devlink dev eswitch set, potentially leading to inconsistent states. This patch addresses the race by using the devl_lock to serialize the firmware reset against other devlink operations. When a reset is requested, the driver attempts to acquire the lock. If successful, it sets a flag to block devlink reload or eswitch changes, ACKs the reset to firmware and then releases the lock. If the lock is already held by another operation, the driver NACKs the firmware reset request, indicating that the reset cannot proceed. Firmware reset does not keep the devl_lock and instead uses an internal firmware reset bit. This is because firmware resets can be triggered by asynchronous events, and processed in different threads. It is illegal and unsafe to acquire a lock in one thread and attempt to release it in another, as lock ownership is intrinsically thread-specific. This change ensures that firmware resets and other devlink operations are mutually exclusive during the critical reset request phase, preventing race conditions. Fixes: 38b9f903f22b ("net/mlx5: Handle sync reset request event") Signed-off-by: Shay Drory Reviewed-by: Mateusz Berezecki Reviewed-by: Moshe Shemesh Signed-off-by: Tariq Toukan Link: https://patch.msgid.link/1765284977-1363052-6-git-send-email-tariqt@nvidia.com Signed-off-by: Paolo Abeni Signed-off-by: Sasha Levin --- .../net/ethernet/mellanox/mlx5/core/devlink.c | 5 +++ .../mellanox/mlx5/core/eswitch_offloads.c | 6 +++ .../ethernet/mellanox/mlx5/core/fw_reset.c | 45 +++++++++++++++++-- .../ethernet/mellanox/mlx5/core/fw_reset.h | 1 + 4 files changed, 53 insertions(+), 4 deletions(-) diff --git a/drivers/net/ethernet/mellanox/mlx5/core/devlink.c b/drivers/net/ethernet/mellanox/mlx5/core/devlink.c index 887adf4807d164..ea77fbd98396a2 100644 --- a/drivers/net/ethernet/mellanox/mlx5/core/devlink.c +++ b/drivers/net/ethernet/mellanox/mlx5/core/devlink.c @@ -197,6 +197,11 @@ static int mlx5_devlink_reload_down(struct devlink *devlink, bool netns_change, struct pci_dev *pdev = dev->pdev; int ret = 0; + if (mlx5_fw_reset_in_progress(dev)) { + NL_SET_ERR_MSG_MOD(extack, "Can't reload during firmware reset"); + return -EBUSY; + } + if (mlx5_dev_is_lightweight(dev)) { if (action != DEVLINK_RELOAD_ACTION_DRIVER_REINIT) return -EOPNOTSUPP; diff --git a/drivers/net/ethernet/mellanox/mlx5/core/eswitch_offloads.c b/drivers/net/ethernet/mellanox/mlx5/core/eswitch_offloads.c index 44a142a041b2fe..784130cdf6c07f 100644 --- a/drivers/net/ethernet/mellanox/mlx5/core/eswitch_offloads.c +++ b/drivers/net/ethernet/mellanox/mlx5/core/eswitch_offloads.c @@ -52,6 +52,7 @@ #include "devlink.h" #include "lag/lag.h" #include "en/tc/post_meter.h" +#include "fw_reset.h" /* There are two match-all miss flows, one for unicast dst mac and * one for multicast. @@ -3807,6 +3808,11 @@ int mlx5_devlink_eswitch_mode_set(struct devlink *devlink, u16 mode, if (IS_ERR(esw)) return PTR_ERR(esw); + if (mlx5_fw_reset_in_progress(esw->dev)) { + NL_SET_ERR_MSG_MOD(extack, "Can't change eswitch mode during firmware reset"); + return -EBUSY; + } + if (esw_mode_from_devlink(mode, &mlx5_mode)) return -EINVAL; diff --git a/drivers/net/ethernet/mellanox/mlx5/core/fw_reset.c b/drivers/net/ethernet/mellanox/mlx5/core/fw_reset.c index 33df0418e57542..4544f1968f73fe 100644 --- a/drivers/net/ethernet/mellanox/mlx5/core/fw_reset.c +++ b/drivers/net/ethernet/mellanox/mlx5/core/fw_reset.c @@ -15,6 +15,7 @@ enum { MLX5_FW_RESET_FLAGS_DROP_NEW_REQUESTS, MLX5_FW_RESET_FLAGS_RELOAD_REQUIRED, MLX5_FW_RESET_FLAGS_UNLOAD_EVENT, + MLX5_FW_RESET_FLAGS_RESET_IN_PROGRESS, }; struct mlx5_fw_reset { @@ -127,6 +128,16 @@ int mlx5_fw_reset_query(struct mlx5_core_dev *dev, u8 *reset_level, u8 *reset_ty return mlx5_reg_mfrl_query(dev, reset_level, reset_type, NULL, NULL); } +bool mlx5_fw_reset_in_progress(struct mlx5_core_dev *dev) +{ + struct mlx5_fw_reset *fw_reset = dev->priv.fw_reset; + + if (!fw_reset) + return false; + + return test_bit(MLX5_FW_RESET_FLAGS_RESET_IN_PROGRESS, &fw_reset->reset_flags); +} + static int mlx5_fw_reset_get_reset_method(struct mlx5_core_dev *dev, u8 *reset_method) { @@ -242,6 +253,8 @@ static void mlx5_fw_reset_complete_reload(struct mlx5_core_dev *dev) BIT(DEVLINK_RELOAD_ACTION_FW_ACTIVATE)); devl_unlock(devlink); } + + clear_bit(MLX5_FW_RESET_FLAGS_RESET_IN_PROGRESS, &fw_reset->reset_flags); } static void mlx5_stop_sync_reset_poll(struct mlx5_core_dev *dev) @@ -461,27 +474,48 @@ static void mlx5_sync_reset_request_event(struct work_struct *work) struct mlx5_fw_reset *fw_reset = container_of(work, struct mlx5_fw_reset, reset_request_work); struct mlx5_core_dev *dev = fw_reset->dev; + bool nack_request = false; + struct devlink *devlink; int err; err = mlx5_fw_reset_get_reset_method(dev, &fw_reset->reset_method); - if (err) + if (err) { + nack_request = true; mlx5_core_warn(dev, "Failed reading MFRL, err %d\n", err); + } else if (!mlx5_is_reset_now_capable(dev, fw_reset->reset_method) || + test_bit(MLX5_FW_RESET_FLAGS_NACK_RESET_REQUEST, + &fw_reset->reset_flags)) { + nack_request = true; + } - if (err || test_bit(MLX5_FW_RESET_FLAGS_NACK_RESET_REQUEST, &fw_reset->reset_flags) || - !mlx5_is_reset_now_capable(dev, fw_reset->reset_method)) { + devlink = priv_to_devlink(dev); + /* For external resets, try to acquire devl_lock. Skip if devlink reset is + * pending (lock already held) + */ + if (nack_request || + (!test_bit(MLX5_FW_RESET_FLAGS_PENDING_COMP, + &fw_reset->reset_flags) && + !devl_trylock(devlink))) { err = mlx5_fw_reset_set_reset_sync_nack(dev); mlx5_core_warn(dev, "PCI Sync FW Update Reset Nack %s", err ? "Failed" : "Sent"); return; } + if (mlx5_sync_reset_set_reset_requested(dev)) - return; + goto unlock; + + set_bit(MLX5_FW_RESET_FLAGS_RESET_IN_PROGRESS, &fw_reset->reset_flags); err = mlx5_fw_reset_set_reset_sync_ack(dev); if (err) mlx5_core_warn(dev, "PCI Sync FW Update Reset Ack Failed. Error code: %d\n", err); else mlx5_core_warn(dev, "PCI Sync FW Update Reset Ack. Device reset is expected.\n"); + +unlock: + if (!test_bit(MLX5_FW_RESET_FLAGS_PENDING_COMP, &fw_reset->reset_flags)) + devl_unlock(devlink); } static int mlx5_pci_link_toggle(struct mlx5_core_dev *dev, u16 dev_id) @@ -721,6 +755,8 @@ static void mlx5_sync_reset_abort_event(struct work_struct *work) if (mlx5_sync_reset_clear_reset_requested(dev, true)) return; + + clear_bit(MLX5_FW_RESET_FLAGS_RESET_IN_PROGRESS, &fw_reset->reset_flags); mlx5_core_warn(dev, "PCI Sync FW Update Reset Aborted.\n"); } @@ -757,6 +793,7 @@ static void mlx5_sync_reset_timeout_work(struct work_struct *work) if (mlx5_sync_reset_clear_reset_requested(dev, true)) return; + clear_bit(MLX5_FW_RESET_FLAGS_RESET_IN_PROGRESS, &fw_reset->reset_flags); mlx5_core_warn(dev, "PCI Sync FW Update Reset Timeout.\n"); } diff --git a/drivers/net/ethernet/mellanox/mlx5/core/fw_reset.h b/drivers/net/ethernet/mellanox/mlx5/core/fw_reset.h index d5b28525c960dc..2d96b2adc1cdf1 100644 --- a/drivers/net/ethernet/mellanox/mlx5/core/fw_reset.h +++ b/drivers/net/ethernet/mellanox/mlx5/core/fw_reset.h @@ -10,6 +10,7 @@ int mlx5_fw_reset_query(struct mlx5_core_dev *dev, u8 *reset_level, u8 *reset_ty int mlx5_fw_reset_set_reset_sync(struct mlx5_core_dev *dev, u8 reset_type_sel, struct netlink_ext_ack *extack); int mlx5_fw_reset_set_live_patch(struct mlx5_core_dev *dev); +bool mlx5_fw_reset_in_progress(struct mlx5_core_dev *dev); int mlx5_fw_reset_wait_reset_done(struct mlx5_core_dev *dev); void mlx5_sync_reset_unload_flow(struct mlx5_core_dev *dev, bool locked); From 2d2e05d3c2afc485a1f5d808b0b1b6bed4364e4e Mon Sep 17 00:00:00 2001 From: Jianbo Liu Date: Tue, 9 Dec 2025 14:56:14 +0200 Subject: [PATCH 1403/3902] net/mlx5e: Use ip6_dst_lookup instead of ipv6_dst_lookup_flow for MAC init [ Upstream commit e35d7da8dd9e55b37c3e8ab548f6793af0c2ab49 ] Replace ipv6_stub->ipv6_dst_lookup_flow() with ip6_dst_lookup() in mlx5e_ipsec_init_macs() since IPsec transformations are not needed during Security Association setup - only basic routing information is required for nexthop MAC address resolution. This resolves an issue where XfrmOutNoStates error counter would be incremented when xfrm policy is configured before xfrm state, as the IPsec-aware routing function would attempt policy checks during SA initialization. Fixes: 71670f766b8f ("net/mlx5e: Support routed networks during IPsec MACs initialization") Signed-off-by: Jianbo Liu Reviewed-by: Leon Romanovsky Signed-off-by: Tariq Toukan Link: https://patch.msgid.link/1765284977-1363052-7-git-send-email-tariqt@nvidia.com Signed-off-by: Paolo Abeni Signed-off-by: Sasha Levin --- drivers/net/ethernet/mellanox/mlx5/core/en_accel/ipsec.c | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/drivers/net/ethernet/mellanox/mlx5/core/en_accel/ipsec.c b/drivers/net/ethernet/mellanox/mlx5/core/en_accel/ipsec.c index 35d9530037a655..6c79b9cea2efbc 100644 --- a/drivers/net/ethernet/mellanox/mlx5/core/en_accel/ipsec.c +++ b/drivers/net/ethernet/mellanox/mlx5/core/en_accel/ipsec.c @@ -342,9 +342,8 @@ static void mlx5e_ipsec_init_macs(struct mlx5e_ipsec_sa_entry *sa_entry, rt_dst_entry = &rt->dst; break; case AF_INET6: - rt_dst_entry = ipv6_stub->ipv6_dst_lookup_flow( - dev_net(netdev), NULL, &fl6, NULL); - if (IS_ERR(rt_dst_entry)) + if (!IS_ENABLED(CONFIG_IPV6) || + ip6_dst_lookup(dev_net(netdev), NULL, &rt_dst_entry, &fl6)) goto neigh; break; default: From 52f7785dad93275cc4e6948c10e15baed7f8de93 Mon Sep 17 00:00:00 2001 From: Jianbo Liu Date: Tue, 9 Dec 2025 14:56:15 +0200 Subject: [PATCH 1404/3902] net/mlx5e: Trigger neighbor resolution for unresolved destinations [ Upstream commit 9ab89bde13e5251e1d0507e1cc426edcdfe19142 ] When initializing the MAC addresses for an outbound IPsec packet offload rule in mlx5e_ipsec_init_macs, the call to dst_neigh_lookup is used to find the next-hop neighbor (typically the gateway in tunnel mode). This call might create a new neighbor entry if one doesn't already exist. This newly created entry starts in the INCOMPLETE state, as the kernel hasn't yet sent an ARP or NDISC probe to resolve the MAC address. In this case, neigh_ha_snapshot will correctly return an all-zero MAC address. IPsec packet offload requires the actual next-hop MAC address to program the rule correctly. If the neighbor state is INCOMPLETE when the rule is created, the hardware rule is programmed with an all-zero destination MAC address. Packets sent using this rule will be subsequently dropped by the receiving network infrastructure or host. This patch adds a check specifically for the outbound offload path. If neigh_ha_snapshot returns an all-zero MAC address, it proactively calls neigh_event_send(n, NULL). This ensures the kernel immediately sends the initial ARP or NDISC probe if one isn't already pending, accelerating the resolution process. This helps prevent the hardware rule from being programmed with an invalid MAC address and avoids packet drops due to unresolved neighbors. Fixes: 71670f766b8f ("net/mlx5e: Support routed networks during IPsec MACs initialization") Signed-off-by: Jianbo Liu Reviewed-by: Leon Romanovsky Signed-off-by: Tariq Toukan Link: https://patch.msgid.link/1765284977-1363052-8-git-send-email-tariqt@nvidia.com Signed-off-by: Paolo Abeni Signed-off-by: Sasha Levin --- drivers/net/ethernet/mellanox/mlx5/core/en_accel/ipsec.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/drivers/net/ethernet/mellanox/mlx5/core/en_accel/ipsec.c b/drivers/net/ethernet/mellanox/mlx5/core/en_accel/ipsec.c index 6c79b9cea2efbc..a8fb4bec369cf4 100644 --- a/drivers/net/ethernet/mellanox/mlx5/core/en_accel/ipsec.c +++ b/drivers/net/ethernet/mellanox/mlx5/core/en_accel/ipsec.c @@ -358,6 +358,9 @@ static void mlx5e_ipsec_init_macs(struct mlx5e_ipsec_sa_entry *sa_entry, neigh_ha_snapshot(addr, n, netdev); ether_addr_copy(dst, addr); + if (attrs->dir == XFRM_DEV_OFFLOAD_OUT && + is_zero_ether_addr(addr)) + neigh_event_send(n, NULL); dst_release(rt_dst_entry); neigh_release(n); return; From 4af9c2cd9a841592d30af3f976f874490affdcad Mon Sep 17 00:00:00 2001 From: Cosmin Ratiu Date: Tue, 9 Dec 2025 14:56:17 +0200 Subject: [PATCH 1405/3902] net/mlx5e: Don't include PSP in the hard MTU calculations [ Upstream commit 4198a14c8c6252fd1191afaa742dd515dcaf3487 ] Commit [1] added the 40 bytes required by the PSP header+trailer and the UDP header to MLX5E_ETH_HARD_MTU, which limits the device-wide max software MTU that could be set. This is not okay, because most packets are not PSP packets and it doesn't make sense to always reserve space for headers which won't get added in most cases. As it turns out, for TCP connections, PSP overhead is already taken into account in the TCP MSS calculations via inet_csk(sk)->icsk_ext_hdr_len. This was added in commit [2]. This means that the extra space reserved in the hard MTU for mlx5 ends up unused and wasted. Remove the unnecessary 40 byte reservation from hard MTU. [1] commit e5a1861a298e ("net/mlx5e: Implement PSP Tx data path") [2] commit e97269257fe4 ("net: psp: update the TCP MSS to reflect PSP packet overhead") Fixes: e5a1861a298e ("net/mlx5e: Implement PSP Tx data path") Signed-off-by: Cosmin Ratiu Reviewed-by: Shahar Shitrit Signed-off-by: Tariq Toukan Link: https://patch.msgid.link/1765284977-1363052-10-git-send-email-tariqt@nvidia.com Signed-off-by: Paolo Abeni Signed-off-by: Sasha Levin --- drivers/net/ethernet/mellanox/mlx5/core/en.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/net/ethernet/mellanox/mlx5/core/en.h b/drivers/net/ethernet/mellanox/mlx5/core/en.h index a163f81f07c13d..a6479e4d8d8c60 100644 --- a/drivers/net/ethernet/mellanox/mlx5/core/en.h +++ b/drivers/net/ethernet/mellanox/mlx5/core/en.h @@ -69,7 +69,7 @@ struct page_pool; #define MLX5E_METADATA_ETHER_TYPE (0x8CE4) #define MLX5E_METADATA_ETHER_LEN 8 -#define MLX5E_ETH_HARD_MTU (ETH_HLEN + PSP_ENCAP_HLEN + PSP_TRL_SIZE + VLAN_HLEN + ETH_FCS_LEN) +#define MLX5E_ETH_HARD_MTU (ETH_HLEN + VLAN_HLEN + ETH_FCS_LEN) #define MLX5E_HW2SW_MTU(params, hwmtu) ((hwmtu) - ((params)->hard_mtu)) #define MLX5E_SW2HW_MTU(params, swmtu) ((swmtu) + ((params)->hard_mtu)) From 3c330f1dee3cd92b57e19b9d21dc8ce5970b09be Mon Sep 17 00:00:00 2001 From: Scott Mayhew Date: Tue, 9 Dec 2025 14:30:15 -0500 Subject: [PATCH 1406/3902] net/handshake: duplicate handshake cancellations leak socket [ Upstream commit 15564bd67e2975002f2a8e9defee33e321d3183f ] When a handshake request is cancelled it is removed from the handshake_net->hn_requests list, but it is still present in the handshake_rhashtbl until it is destroyed. If a second cancellation request arrives for the same handshake request, then remove_pending() will return false... and assuming HANDSHAKE_F_REQ_COMPLETED isn't set in req->hr_flags, we'll continue processing through the out_true label, where we put another reference on the sock and a refcount underflow occurs. This can happen for example if a handshake times out - particularly if the SUNRPC client sends the AUTH_TLS probe to the server but doesn't follow it up with the ClientHello due to a problem with tlshd. When the timeout is hit on the server, the server will send a FIN, which triggers a cancellation request via xs_reset_transport(). When the timeout is hit on the client, another cancellation request happens via xs_tls_handshake_sync(). Add a test_and_set_bit(HANDSHAKE_F_REQ_COMPLETED) in the pending cancel path so duplicate cancels can be detected. Fixes: 3b3009ea8abb ("net/handshake: Create a NETLINK service for handling handshake requests") Suggested-by: Chuck Lever Signed-off-by: Scott Mayhew Reviewed-by: Chuck Lever Link: https://patch.msgid.link/20251209193015.3032058-1-smayhew@redhat.com Signed-off-by: Paolo Abeni Signed-off-by: Sasha Levin --- net/handshake/request.c | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/net/handshake/request.c b/net/handshake/request.c index 274d2c89b6b207..f78091680bca52 100644 --- a/net/handshake/request.c +++ b/net/handshake/request.c @@ -324,7 +324,11 @@ bool handshake_req_cancel(struct sock *sk) hn = handshake_pernet(net); if (hn && remove_pending(hn, req)) { - /* Request hadn't been accepted */ + /* Request hadn't been accepted - mark cancelled */ + if (test_and_set_bit(HANDSHAKE_F_REQ_COMPLETED, &req->hr_flags)) { + trace_handshake_cancel_busy(net, req, sk); + return false; + } goto out_true; } if (test_and_set_bit(HANDSHAKE_F_REQ_COMPLETED, &req->hr_flags)) { From 918aafade36d2f2f73e638bd5d6f1f488aeb91ef Mon Sep 17 00:00:00 2001 From: Wei Fang Date: Thu, 11 Dec 2025 10:09:19 +0800 Subject: [PATCH 1407/3902] net: enetc: do not transmit redirected XDP frames when the link is down [ Upstream commit 2939203ffee818f1e5ebd60bbb85a174d63aab9c ] In the current implementation, the enetc_xdp_xmit() always transmits redirected XDP frames even if the link is down, but the frames cannot be transmitted from TX BD rings when the link is down, so the frames are still kept in the TX BD rings. If the XDP program is uninstalled, users will see the following warning logs. fsl_enetc 0000:00:00.0 eno0: timeout for tx ring #6 clear More worse, the TX BD ring cannot work properly anymore, because the HW PIR and CIR are not equal after the re-initialization of the TX BD ring. At this point, the BDs between CIR and PIR are invalid, which will cause a hardware malfunction. Another reason is that there is internal context in the ring prefetch logic that will retain the state from the first incarnation of the ring and continue prefetching from the stale location when we re-initialize the ring. The internal context is only reset by an FLR. That is to say, for LS1028A ENETC, software cannot set the HW CIR and PIR when initializing the TX BD ring. It does not make sense to transmit redirected XDP frames when the link is down. Add a link status check to prevent transmission in this condition. This fixes part of the issue, but more complex cases remain. For example, the TX BD ring may still contain unsent frames when the link goes down. Those situations require additional patches, which will build on this one. Fixes: 9d2b68cc108d ("net: enetc: add support for XDP_REDIRECT") Signed-off-by: Wei Fang Reviewed-by: Frank Li Reviewed-by: Hariprasad Kelam Reviewed-by: Vladimir Oltean Link: https://patch.msgid.link/20251211020919.121113-1-wei.fang@nxp.com Signed-off-by: Paolo Abeni Signed-off-by: Sasha Levin --- drivers/net/ethernet/freescale/enetc/enetc.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/drivers/net/ethernet/freescale/enetc/enetc.c b/drivers/net/ethernet/freescale/enetc/enetc.c index 0535e92404e3cb..f410c245ea918b 100644 --- a/drivers/net/ethernet/freescale/enetc/enetc.c +++ b/drivers/net/ethernet/freescale/enetc/enetc.c @@ -1778,7 +1778,8 @@ int enetc_xdp_xmit(struct net_device *ndev, int num_frames, int xdp_tx_bd_cnt, i, k; int xdp_tx_frm_cnt = 0; - if (unlikely(test_bit(ENETC_TX_DOWN, &priv->flags))) + if (unlikely(test_bit(ENETC_TX_DOWN, &priv->flags) || + !netif_carrier_ok(ndev))) return -ENETDOWN; enetc_lock_mdio(); From 62f28d79a6186a602a9d926a2dbb5b12b6867df7 Mon Sep 17 00:00:00 2001 From: Jian Shen Date: Thu, 11 Dec 2025 10:37:35 +0800 Subject: [PATCH 1408/3902] net: hns3: using the num_tqps in the vf driver to apply for resources [ Upstream commit c2a16269742e176fccdd0ef9c016a233491a49ad ] Currently, hdev->htqp is allocated using hdev->num_tqps, and kinfo->tqp is allocated using kinfo->num_tqps. However, kinfo->num_tqps is set to min(new_tqps, hdev->num_tqps); Therefore, kinfo->num_tqps may be smaller than hdev->num_tqps, which causes some hdev->htqp[i] to remain uninitialized in hclgevf_knic_setup(). Thus, this patch allocates hdev->htqp and kinfo->tqp using hdev->num_tqps, ensuring that the lengths of hdev->htqp and kinfo->tqp are consistent and that all elements are properly initialized. Fixes: e2cb1dec9779 ("net: hns3: Add HNS3 VF HCL(Hardware Compatibility Layer) Support") Signed-off-by: Jian Shen Signed-off-by: Jijie Shao Reviewed-by: Simon Horman Link: https://patch.msgid.link/20251211023737.2327018-2-shaojijie@huawei.com Signed-off-by: Paolo Abeni Signed-off-by: Sasha Levin --- drivers/net/ethernet/hisilicon/hns3/hns3vf/hclgevf_main.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/net/ethernet/hisilicon/hns3/hns3vf/hclgevf_main.c b/drivers/net/ethernet/hisilicon/hns3/hns3vf/hclgevf_main.c index 8fcf220a120d21..70327a73dee326 100644 --- a/drivers/net/ethernet/hisilicon/hns3/hns3vf/hclgevf_main.c +++ b/drivers/net/ethernet/hisilicon/hns3/hns3vf/hclgevf_main.c @@ -368,12 +368,12 @@ static int hclgevf_knic_setup(struct hclgevf_dev *hdev) new_tqps = kinfo->rss_size * num_tc; kinfo->num_tqps = min(new_tqps, hdev->num_tqps); - kinfo->tqp = devm_kcalloc(&hdev->pdev->dev, kinfo->num_tqps, + kinfo->tqp = devm_kcalloc(&hdev->pdev->dev, hdev->num_tqps, sizeof(struct hnae3_queue *), GFP_KERNEL); if (!kinfo->tqp) return -ENOMEM; - for (i = 0; i < kinfo->num_tqps; i++) { + for (i = 0; i < hdev->num_tqps; i++) { hdev->htqp[i].q.handle = &hdev->nic; hdev->htqp[i].q.tqp_index = i; kinfo->tqp[i] = &hdev->htqp[i].q; From f02f283ebfaf2b28b28615c29084b8bfb14f93d5 Mon Sep 17 00:00:00 2001 From: Jian Shen Date: Thu, 11 Dec 2025 10:37:36 +0800 Subject: [PATCH 1409/3902] net: hns3: using the num_tqps to check whether tqp_index is out of range when vf get ring info from mbx [ Upstream commit d180c11aa8a6fa735f9ac2c72c61364a9afc2ba7 ] Currently, rss_size = num_tqps / tc_num. If tc_num is 1, then num_tqps equals rss_size. However, if the tc_num is greater than 1, then rss_size will be less than num_tqps, causing the tqp_index check for subsequent TCs using rss_size to always fail. This patch uses the num_tqps to check whether tqp_index is out of range, instead of rss_size. Fixes: 326334aad024 ("net: hns3: add a check for tqp_index in hclge_get_ring_chain_from_mbx()") Signed-off-by: Jian Shen Signed-off-by: Jijie Shao Reviewed-by: Simon Horman Link: https://patch.msgid.link/20251211023737.2327018-3-shaojijie@huawei.com Signed-off-by: Paolo Abeni Signed-off-by: Sasha Levin --- drivers/net/ethernet/hisilicon/hns3/hns3pf/hclge_mbx.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/net/ethernet/hisilicon/hns3/hns3pf/hclge_mbx.c b/drivers/net/ethernet/hisilicon/hns3/hns3pf/hclge_mbx.c index c7ff12a6c0764d..b7d4e06a55d40e 100644 --- a/drivers/net/ethernet/hisilicon/hns3/hns3pf/hclge_mbx.c +++ b/drivers/net/ethernet/hisilicon/hns3/hns3pf/hclge_mbx.c @@ -193,10 +193,10 @@ static int hclge_get_ring_chain_from_mbx( return -EINVAL; for (i = 0; i < ring_num; i++) { - if (req->msg.param[i].tqp_index >= vport->nic.kinfo.rss_size) { + if (req->msg.param[i].tqp_index >= vport->nic.kinfo.num_tqps) { dev_err(&hdev->pdev->dev, "tqp index(%u) is out of range(0-%u)\n", req->msg.param[i].tqp_index, - vport->nic.kinfo.rss_size - 1U); + vport->nic.kinfo.num_tqps - 1U); return -EINVAL; } } From 91a51d01be5c9f82c12c2921ca5cceaa31b67128 Mon Sep 17 00:00:00 2001 From: Jian Shen Date: Thu, 11 Dec 2025 10:37:37 +0800 Subject: [PATCH 1410/3902] net: hns3: add VLAN id validation before using [ Upstream commit 6ef935e65902bfed53980ad2754b06a284ea8ac1 ] Currently, the VLAN id may be used without validation when receive a VLAN configuration mailbox from VF. The length of vlan_del_fail_bmap is BITS_TO_LONGS(VLAN_N_VID). It may cause out-of-bounds memory access once the VLAN id is bigger than or equal to VLAN_N_VID. Therefore, VLAN id needs to be checked to ensure it is within the range of VLAN_N_VID. Fixes: fe4144d47eef ("net: hns3: sync VLAN filter entries when kill VLAN ID failed") Signed-off-by: Jian Shen Signed-off-by: Jijie Shao Reviewed-by: Simon Horman Link: https://patch.msgid.link/20251211023737.2327018-4-shaojijie@huawei.com Signed-off-by: Paolo Abeni Signed-off-by: Sasha Levin --- drivers/net/ethernet/hisilicon/hns3/hns3pf/hclge_main.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/drivers/net/ethernet/hisilicon/hns3/hns3pf/hclge_main.c b/drivers/net/ethernet/hisilicon/hns3/hns3pf/hclge_main.c index 782bb48c9f3d77..1b103d1154da96 100644 --- a/drivers/net/ethernet/hisilicon/hns3/hns3pf/hclge_main.c +++ b/drivers/net/ethernet/hisilicon/hns3/hns3pf/hclge_main.c @@ -10562,6 +10562,9 @@ int hclge_set_vlan_filter(struct hnae3_handle *handle, __be16 proto, bool writen_to_tbl = false; int ret = 0; + if (vlan_id >= VLAN_N_VID) + return -EINVAL; + /* When device is resetting or reset failed, firmware is unable to * handle mailbox. Just record the vlan id, and remove it after * reset finished. From c35d4fba2e0b5f1bbcaa2ab7e085267af318ddca Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Exp=C3=B3sito?= Date: Tue, 4 Nov 2025 11:22:35 +0100 Subject: [PATCH 1411/3902] drm/tests: hdmi: Handle drm_kunit_helper_enable_crtc_connector() returning EDEADLK MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [ Upstream commit fe27e709d91fb645182751b602cb88966b4a1bb6 ] Fedora/CentOS/RHEL CI is reporting intermittent failures while running the KUnit tests present in drm_hdmi_state_helper_test.c [1]. While the specific test causing the failure change between runs, all of them are caused by drm_kunit_helper_enable_crtc_connector() returning -EDEADLK. The error trace always follow this structure: # : ASSERTION FAILED at # drivers/gpu/drm/tests/drm_hdmi_state_helper_test.c: Expected ret == 0, but ret == -35 (0xffffffffffffffdd) As documented, if the drm_kunit_helper_enable_crtc_connector() function returns -EDEADLK (-35), the entire atomic sequence must be restarted. Handle this error code for all function calls. Closes: https://datawarehouse.cki-project.org/issue/4039 [1] Fixes: 6a5c0ad7e08e ("drm/tests: hdmi_state_helpers: Switch to new helper") Reviewed-by: Maxime Ripard Signed-off-by: José Expósito Link: https://patch.msgid.link/20251104102258.10026-1-jose.exposito89@gmail.com Signed-off-by: Sasha Levin --- .../drm/tests/drm_hdmi_state_helper_test.c | 143 ++++++++++++++++++ 1 file changed, 143 insertions(+) diff --git a/drivers/gpu/drm/tests/drm_hdmi_state_helper_test.c b/drivers/gpu/drm/tests/drm_hdmi_state_helper_test.c index 8bd412735000cb..70f9aa70214303 100644 --- a/drivers/gpu/drm/tests/drm_hdmi_state_helper_test.c +++ b/drivers/gpu/drm/tests/drm_hdmi_state_helper_test.c @@ -257,10 +257,16 @@ static void drm_test_check_broadcast_rgb_crtc_mode_changed(struct kunit *test) drm_modeset_acquire_init(&ctx, 0); +retry_conn_enable: ret = drm_kunit_helper_enable_crtc_connector(test, drm, crtc, conn, preferred, &ctx); + if (ret == -EDEADLK) { + ret = drm_modeset_backoff(&ctx); + if (!ret) + goto retry_conn_enable; + } KUNIT_ASSERT_EQ(test, ret, 0); state = drm_kunit_helper_atomic_state_alloc(test, drm, &ctx); @@ -326,10 +332,16 @@ static void drm_test_check_broadcast_rgb_crtc_mode_not_changed(struct kunit *tes drm_modeset_acquire_init(&ctx, 0); +retry_conn_enable: ret = drm_kunit_helper_enable_crtc_connector(test, drm, crtc, conn, preferred, &ctx); + if (ret == -EDEADLK) { + ret = drm_modeset_backoff(&ctx); + if (!ret) + goto retry_conn_enable; + } KUNIT_ASSERT_EQ(test, ret, 0); state = drm_kunit_helper_atomic_state_alloc(test, drm, &ctx); @@ -397,10 +409,16 @@ static void drm_test_check_broadcast_rgb_auto_cea_mode(struct kunit *test) drm_modeset_acquire_init(&ctx, 0); +retry_conn_enable: ret = drm_kunit_helper_enable_crtc_connector(test, drm, crtc, conn, preferred, &ctx); + if (ret == -EDEADLK) { + ret = drm_modeset_backoff(&ctx); + if (!ret) + goto retry_conn_enable; + } KUNIT_ASSERT_EQ(test, ret, 0); state = drm_kunit_helper_atomic_state_alloc(test, drm, &ctx); @@ -457,10 +475,17 @@ static void drm_test_check_broadcast_rgb_auto_cea_mode_vic_1(struct kunit *test) KUNIT_ASSERT_NOT_NULL(test, mode); crtc = priv->crtc; + +retry_conn_enable: ret = drm_kunit_helper_enable_crtc_connector(test, drm, crtc, conn, mode, &ctx); + if (ret == -EDEADLK) { + ret = drm_modeset_backoff(&ctx); + if (!ret) + goto retry_conn_enable; + } KUNIT_ASSERT_EQ(test, ret, 0); state = drm_kunit_helper_atomic_state_alloc(test, drm, &ctx); @@ -518,10 +543,16 @@ static void drm_test_check_broadcast_rgb_full_cea_mode(struct kunit *test) drm_modeset_acquire_init(&ctx, 0); +retry_conn_enable: ret = drm_kunit_helper_enable_crtc_connector(test, drm, crtc, conn, preferred, &ctx); + if (ret == -EDEADLK) { + ret = drm_modeset_backoff(&ctx); + if (!ret) + goto retry_conn_enable; + } KUNIT_ASSERT_EQ(test, ret, 0); state = drm_kunit_helper_atomic_state_alloc(test, drm, &ctx); @@ -580,10 +611,17 @@ static void drm_test_check_broadcast_rgb_full_cea_mode_vic_1(struct kunit *test) KUNIT_ASSERT_NOT_NULL(test, mode); crtc = priv->crtc; + +retry_conn_enable: ret = drm_kunit_helper_enable_crtc_connector(test, drm, crtc, conn, mode, &ctx); + if (ret == -EDEADLK) { + ret = drm_modeset_backoff(&ctx); + if (!ret) + goto retry_conn_enable; + } KUNIT_ASSERT_EQ(test, ret, 0); state = drm_kunit_helper_atomic_state_alloc(test, drm, &ctx); @@ -643,10 +681,16 @@ static void drm_test_check_broadcast_rgb_limited_cea_mode(struct kunit *test) drm_modeset_acquire_init(&ctx, 0); +retry_conn_enable: ret = drm_kunit_helper_enable_crtc_connector(test, drm, crtc, conn, preferred, &ctx); + if (ret == -EDEADLK) { + ret = drm_modeset_backoff(&ctx); + if (!ret) + goto retry_conn_enable; + } KUNIT_ASSERT_EQ(test, ret, 0); state = drm_kunit_helper_atomic_state_alloc(test, drm, &ctx); @@ -705,10 +749,17 @@ static void drm_test_check_broadcast_rgb_limited_cea_mode_vic_1(struct kunit *te KUNIT_ASSERT_NOT_NULL(test, mode); crtc = priv->crtc; + +retry_conn_enable: ret = drm_kunit_helper_enable_crtc_connector(test, drm, crtc, conn, mode, &ctx); + if (ret == -EDEADLK) { + ret = drm_modeset_backoff(&ctx); + if (!ret) + goto retry_conn_enable; + } KUNIT_ASSERT_EQ(test, ret, 0); state = drm_kunit_helper_atomic_state_alloc(test, drm, &ctx); @@ -870,10 +921,16 @@ static void drm_test_check_output_bpc_crtc_mode_changed(struct kunit *test) drm_modeset_acquire_init(&ctx, 0); +retry_conn_enable: ret = drm_kunit_helper_enable_crtc_connector(test, drm, crtc, conn, preferred, &ctx); + if (ret == -EDEADLK) { + ret = drm_modeset_backoff(&ctx); + if (!ret) + goto retry_conn_enable; + } KUNIT_ASSERT_EQ(test, ret, 0); state = drm_kunit_helper_atomic_state_alloc(test, drm, &ctx); @@ -946,10 +1003,16 @@ static void drm_test_check_output_bpc_crtc_mode_not_changed(struct kunit *test) drm_modeset_acquire_init(&ctx, 0); +retry_conn_enable: ret = drm_kunit_helper_enable_crtc_connector(test, drm, crtc, conn, preferred, &ctx); + if (ret == -EDEADLK) { + ret = drm_modeset_backoff(&ctx); + if (!ret) + goto retry_conn_enable; + } KUNIT_ASSERT_EQ(test, ret, 0); state = drm_kunit_helper_atomic_state_alloc(test, drm, &ctx); @@ -1022,10 +1085,16 @@ static void drm_test_check_output_bpc_dvi(struct kunit *test) drm_modeset_acquire_init(&ctx, 0); +retry_conn_enable: ret = drm_kunit_helper_enable_crtc_connector(test, drm, crtc, conn, preferred, &ctx); + if (ret == -EDEADLK) { + ret = drm_modeset_backoff(&ctx); + if (!ret) + goto retry_conn_enable; + } KUNIT_ASSERT_EQ(test, ret, 0); conn_state = conn->state; @@ -1069,10 +1138,16 @@ static void drm_test_check_tmds_char_rate_rgb_8bpc(struct kunit *test) drm_modeset_acquire_init(&ctx, 0); +retry_conn_enable: ret = drm_kunit_helper_enable_crtc_connector(test, drm, crtc, conn, preferred, &ctx); + if (ret == -EDEADLK) { + ret = drm_modeset_backoff(&ctx); + if (!ret) + goto retry_conn_enable; + } KUNIT_ASSERT_EQ(test, ret, 0); conn_state = conn->state; @@ -1118,10 +1193,16 @@ static void drm_test_check_tmds_char_rate_rgb_10bpc(struct kunit *test) drm_modeset_acquire_init(&ctx, 0); +retry_conn_enable: ret = drm_kunit_helper_enable_crtc_connector(test, drm, crtc, conn, preferred, &ctx); + if (ret == -EDEADLK) { + ret = drm_modeset_backoff(&ctx); + if (!ret) + goto retry_conn_enable; + } KUNIT_ASSERT_EQ(test, ret, 0); conn_state = conn->state; @@ -1167,10 +1248,16 @@ static void drm_test_check_tmds_char_rate_rgb_12bpc(struct kunit *test) drm_modeset_acquire_init(&ctx, 0); +retry_conn_enable: ret = drm_kunit_helper_enable_crtc_connector(test, drm, crtc, conn, preferred, &ctx); + if (ret == -EDEADLK) { + ret = drm_modeset_backoff(&ctx); + if (!ret) + goto retry_conn_enable; + } KUNIT_ASSERT_EQ(test, ret, 0); conn_state = conn->state; @@ -1218,10 +1305,16 @@ static void drm_test_check_hdmi_funcs_reject_rate(struct kunit *test) drm_modeset_acquire_init(&ctx, 0); +retry_conn_enable: ret = drm_kunit_helper_enable_crtc_connector(test, drm, crtc, conn, preferred, &ctx); + if (ret == -EDEADLK) { + ret = drm_modeset_backoff(&ctx); + if (!ret) + goto retry_conn_enable; + } KUNIT_ASSERT_EQ(test, ret, 0); /* You shouldn't be doing that at home. */ @@ -1292,10 +1385,16 @@ static void drm_test_check_max_tmds_rate_bpc_fallback_rgb(struct kunit *test) drm_modeset_acquire_init(&ctx, 0); +retry_conn_enable: ret = drm_kunit_helper_enable_crtc_connector(test, drm, crtc, conn, preferred, &ctx); + if (ret == -EDEADLK) { + ret = drm_modeset_backoff(&ctx); + if (!ret) + goto retry_conn_enable; + } KUNIT_EXPECT_EQ(test, ret, 0); conn_state = conn->state; @@ -1440,10 +1539,16 @@ static void drm_test_check_max_tmds_rate_bpc_fallback_ignore_yuv422(struct kunit drm_modeset_acquire_init(&ctx, 0); +retry_conn_enable: ret = drm_kunit_helper_enable_crtc_connector(test, drm, crtc, conn, preferred, &ctx); + if (ret == -EDEADLK) { + ret = drm_modeset_backoff(&ctx); + if (!ret) + goto retry_conn_enable; + } KUNIT_EXPECT_EQ(test, ret, 0); conn_state = conn->state; @@ -1669,10 +1774,17 @@ static void drm_test_check_output_bpc_format_vic_1(struct kunit *test) drm_modeset_acquire_init(&ctx, 0); crtc = priv->crtc; + +retry_conn_enable: ret = drm_kunit_helper_enable_crtc_connector(test, drm, crtc, conn, mode, &ctx); + if (ret == -EDEADLK) { + ret = drm_modeset_backoff(&ctx); + if (!ret) + goto retry_conn_enable; + } KUNIT_EXPECT_EQ(test, ret, 0); conn_state = conn->state; @@ -1736,10 +1848,16 @@ static void drm_test_check_output_bpc_format_driver_rgb_only(struct kunit *test) drm_modeset_acquire_init(&ctx, 0); +retry_conn_enable: ret = drm_kunit_helper_enable_crtc_connector(test, drm, crtc, conn, preferred, &ctx); + if (ret == -EDEADLK) { + ret = drm_modeset_backoff(&ctx); + if (!ret) + goto retry_conn_enable; + } KUNIT_EXPECT_EQ(test, ret, 0); conn_state = conn->state; @@ -1805,10 +1923,16 @@ static void drm_test_check_output_bpc_format_display_rgb_only(struct kunit *test drm_modeset_acquire_init(&ctx, 0); +retry_conn_enable: ret = drm_kunit_helper_enable_crtc_connector(test, drm, crtc, conn, preferred, &ctx); + if (ret == -EDEADLK) { + ret = drm_modeset_backoff(&ctx); + if (!ret) + goto retry_conn_enable; + } KUNIT_EXPECT_EQ(test, ret, 0); conn_state = conn->state; @@ -1865,10 +1989,16 @@ static void drm_test_check_output_bpc_format_driver_8bpc_only(struct kunit *test drm_modeset_acquire_init(&ctx, 0); +retry_conn_enable: ret = drm_kunit_helper_enable_crtc_connector(test, drm, crtc, conn, preferred, &ctx); + if (ret == -EDEADLK) { + ret = drm_modeset_backoff(&ctx); + if (!ret) + goto retry_conn_enable; + } KUNIT_EXPECT_EQ(test, ret, 0); conn_state = conn->state; @@ -1927,10 +2057,16 @@ static void drm_test_check_output_bpc_format_display_8bpc_only(struct kunit *tes drm_modeset_acquire_init(&ctx, 0); +retry_conn_enable: ret = drm_kunit_helper_enable_crtc_connector(test, drm, crtc, conn, preferred, &ctx); + if (ret == -EDEADLK) { + ret = drm_modeset_backoff(&ctx); + if (!ret) + goto retry_conn_enable; + } KUNIT_EXPECT_EQ(test, ret, 0); conn_state = conn->state; @@ -1970,10 +2106,17 @@ static void drm_test_check_disable_connector(struct kunit *test) drm = &priv->drm; crtc = priv->crtc; + +retry_conn_enable: ret = drm_kunit_helper_enable_crtc_connector(test, drm, crtc, conn, preferred, &ctx); + if (ret == -EDEADLK) { + ret = drm_modeset_backoff(&ctx); + if (!ret) + goto retry_conn_enable; + } KUNIT_ASSERT_EQ(test, ret, 0); state = drm_kunit_helper_atomic_state_alloc(test, drm, &ctx); From 5ffc53a58a755b4b9f907afcbcec771777e9f120 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Exp=C3=B3sito?= Date: Tue, 4 Nov 2025 11:25:21 +0100 Subject: [PATCH 1412/3902] drm/tests: Handle EDEADLK in drm_test_check_valid_clones() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [ Upstream commit 141d95e42884628314f5ad9394657b0b35424300 ] Fedora/CentOS/RHEL CI is reporting intermittent failures while running the drm_test_check_valid_clones() KUnit test. The error log can be either [1]: # drm_test_check_valid_clones: ASSERTION FAILED at # drivers/gpu/drm/tests/drm_atomic_state_test.c:295 Expected ret == param->expected_result, but ret == -35 (0xffffffffffffffdd) param->expected_result == 0 (0x0) Or [2] depending on the test case: # drm_test_check_valid_clones: ASSERTION FAILED at # drivers/gpu/drm/tests/drm_atomic_state_test.c:295 Expected ret == param->expected_result, but ret == -35 (0xffffffffffffffdd) param->expected_result == -22 (0xffffffffffffffea) Restart the atomic sequence when EDEADLK is returned. [1] https://s3.amazonaws.com/arr-cki-prod-trusted-artifacts/trusted-artifacts/2113057246/test_x86_64/11802139999/artifacts/jobwatch/logs/recipes/19824965/tasks/204347800/results/946112713/logs/dmesg.log [2] https://s3.amazonaws.com/arr-cki-prod-trusted-artifacts/trusted-artifacts/2106744297/test_aarch64/11762450907/artifacts/jobwatch/logs/recipes/19797942/tasks/204139727/results/945094561/logs/dmesg.log Fixes: 88849f24e2ab ("drm/tests: Add test for drm_atomic_helper_check_modeset()") Closes: https://datawarehouse.cki-project.org/issue/4004 Reviewed-by: Maxime Ripard Signed-off-by: José Expósito Link: https://patch.msgid.link/20251104102535.12212-1-jose.exposito89@gmail.com Signed-off-by: Sasha Levin --- drivers/gpu/drm/tests/drm_atomic_state_test.c | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/drivers/gpu/drm/tests/drm_atomic_state_test.c b/drivers/gpu/drm/tests/drm_atomic_state_test.c index 2f6ac7a09f4458..1e857d86574cc5 100644 --- a/drivers/gpu/drm/tests/drm_atomic_state_test.c +++ b/drivers/gpu/drm/tests/drm_atomic_state_test.c @@ -283,7 +283,14 @@ static void drm_test_check_valid_clones(struct kunit *test) state = drm_kunit_helper_atomic_state_alloc(test, drm, &ctx); KUNIT_ASSERT_NOT_ERR_OR_NULL(test, state); +retry: crtc_state = drm_atomic_get_crtc_state(state, priv->crtc); + if (PTR_ERR(crtc_state) == -EDEADLK) { + drm_atomic_state_clear(state); + ret = drm_modeset_backoff(&ctx); + if (!ret) + goto retry; + } KUNIT_ASSERT_NOT_ERR_OR_NULL(test, crtc_state); crtc_state->encoder_mask = param->encoder_mask; @@ -292,6 +299,12 @@ static void drm_test_check_valid_clones(struct kunit *test) crtc_state->mode_changed = true; ret = drm_atomic_helper_check_modeset(drm, state); + if (ret == -EDEADLK) { + drm_atomic_state_clear(state); + ret = drm_modeset_backoff(&ctx); + if (!ret) + goto retry; + } KUNIT_ASSERT_EQ(test, ret, param->expected_result); drm_modeset_drop_locks(&ctx); From 9c0dc1d40b693a4b2a608fc3008fb2daa3970363 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Exp=C3=B3sito?= Date: Tue, 4 Nov 2025 11:25:22 +0100 Subject: [PATCH 1413/3902] drm/tests: Handle EDEADLK in set_up_atomic_state() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [ Upstream commit 526aafabd756cc56401b383d6ae554af3e21dcdd ] Fedora/CentOS/RHEL CI is reporting intermittent failures while running the drm_validate_modeset test [1]: # drm_test_check_connector_changed_modeset: EXPECTATION FAILED at # drivers/gpu/drm/tests/drm_atomic_state_test.c:162 Expected ret == 0, but ret == -35 (0xffffffffffffffdd) Change the set_up_atomic_state() helper function to return on error and restart the atomic sequence when the returned error is EDEADLK. [1] https://s3.amazonaws.com/arr-cki-prod-trusted-artifacts/trusted-artifacts/2106744096/test_x86_64/11762450343/artifacts/jobwatch/logs/recipes/19797909/tasks/204139142/results/945095586/logs/dmesg.log Fixes: 73d934d7b6e3 ("drm/tests: Add test for drm_atomic_helper_commit_modeset_disables()") Closes: https://datawarehouse.cki-project.org/issue/4004 Reviewed-by: Maxime Ripard Signed-off-by: José Expósito Link: https://patch.msgid.link/20251104102535.12212-2-jose.exposito89@gmail.com Signed-off-by: Sasha Levin --- drivers/gpu/drm/tests/drm_atomic_state_test.c | 27 +++++++++++++++---- 1 file changed, 22 insertions(+), 5 deletions(-) diff --git a/drivers/gpu/drm/tests/drm_atomic_state_test.c b/drivers/gpu/drm/tests/drm_atomic_state_test.c index 1e857d86574cc5..bc27f65b282339 100644 --- a/drivers/gpu/drm/tests/drm_atomic_state_test.c +++ b/drivers/gpu/drm/tests/drm_atomic_state_test.c @@ -156,24 +156,29 @@ static int set_up_atomic_state(struct kunit *test, if (connector) { conn_state = drm_atomic_get_connector_state(state, connector); - KUNIT_ASSERT_NOT_ERR_OR_NULL(test, conn_state); + if (IS_ERR(conn_state)) + return PTR_ERR(conn_state); ret = drm_atomic_set_crtc_for_connector(conn_state, crtc); - KUNIT_EXPECT_EQ(test, ret, 0); + if (ret) + return ret; } crtc_state = drm_atomic_get_crtc_state(state, crtc); - KUNIT_ASSERT_NOT_ERR_OR_NULL(test, crtc_state); + if (IS_ERR(crtc_state)) + return PTR_ERR(crtc_state); ret = drm_atomic_set_mode_for_crtc(crtc_state, &drm_atomic_test_mode); - KUNIT_EXPECT_EQ(test, ret, 0); + if (ret) + return ret; crtc_state->enable = true; crtc_state->active = true; if (connector) { ret = drm_atomic_commit(state); - KUNIT_ASSERT_EQ(test, ret, 0); + if (ret) + return ret; } else { // dummy connector mask crtc_state->connector_mask = DRM_TEST_CONN_0; @@ -206,7 +211,13 @@ static void drm_test_check_connector_changed_modeset(struct kunit *test) drm_modeset_acquire_init(&ctx, 0); // first modeset to enable +retry_set_up: ret = set_up_atomic_state(test, priv, old_conn, &ctx); + if (ret == -EDEADLK) { + ret = drm_modeset_backoff(&ctx); + if (!ret) + goto retry_set_up; + } KUNIT_ASSERT_EQ(test, ret, 0); state = drm_kunit_helper_atomic_state_alloc(test, drm, &ctx); @@ -277,7 +288,13 @@ static void drm_test_check_valid_clones(struct kunit *test) drm_modeset_acquire_init(&ctx, 0); +retry_set_up: ret = set_up_atomic_state(test, priv, NULL, &ctx); + if (ret == -EDEADLK) { + ret = drm_modeset_backoff(&ctx); + if (!ret) + goto retry_set_up; + } KUNIT_ASSERT_EQ(test, ret, 0); state = drm_kunit_helper_atomic_state_alloc(test, drm, &ctx); From 322d0132d8b55a24825e6ef683cdf88a5a6265a6 Mon Sep 17 00:00:00 2001 From: Ming Lei Date: Fri, 12 Dec 2025 10:16:59 -0700 Subject: [PATCH 1414/3902] selftests: ublk: fix overflow in ublk_queue_auto_zc_fallback() [ Upstream commit 9637fc3bdd10c8e073f71897bd35babbd21e9b29 ] The functions ublk_queue_use_zc(), ublk_queue_use_auto_zc(), and ublk_queue_auto_zc_fallback() were returning int, but performing bitwise AND on q->flags which is __u64. When a flag bit is set in the upper 32 bits (beyond INT_MAX), the result of the bitwise AND operation could overflow when cast to int, leading to incorrect boolean evaluation. For example, if UBLKS_Q_AUTO_BUF_REG_FALLBACK is 0x8000000000000000: - (u64)flags & 0x8000000000000000 = 0x8000000000000000 - Cast to int: undefined behavior / incorrect value - Used in if(): may evaluate incorrectly Fix by: 1. Changing return type from int to bool for semantic correctness 2. Using !! to explicitly convert to boolean (0 or 1) This ensures the functions return proper boolean values regardless of which bit position the flags occupy in the 64-bit field. Fixes: c3a6d48f86da ("selftests: ublk: remove ublk queue self-defined flags") Signed-off-by: Ming Lei Signed-off-by: Jens Axboe Signed-off-by: Sasha Levin --- tools/testing/selftests/ublk/kublk.h | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/tools/testing/selftests/ublk/kublk.h b/tools/testing/selftests/ublk/kublk.h index 5e55484fb0aa26..1b8833a4006436 100644 --- a/tools/testing/selftests/ublk/kublk.h +++ b/tools/testing/selftests/ublk/kublk.h @@ -393,19 +393,19 @@ static inline int ublk_completed_tgt_io(struct ublk_thread *t, return --io->tgt_ios == 0; } -static inline int ublk_queue_use_zc(const struct ublk_queue *q) +static inline bool ublk_queue_use_zc(const struct ublk_queue *q) { - return q->flags & UBLK_F_SUPPORT_ZERO_COPY; + return !!(q->flags & UBLK_F_SUPPORT_ZERO_COPY); } -static inline int ublk_queue_use_auto_zc(const struct ublk_queue *q) +static inline bool ublk_queue_use_auto_zc(const struct ublk_queue *q) { - return q->flags & UBLK_F_AUTO_BUF_REG; + return !!(q->flags & UBLK_F_AUTO_BUF_REG); } -static inline int ublk_queue_auto_zc_fallback(const struct ublk_queue *q) +static inline bool ublk_queue_auto_zc_fallback(const struct ublk_queue *q) { - return q->flags & UBLKS_Q_AUTO_BUF_REG_FALLBACK; + return !!(q->flags & UBLKS_Q_AUTO_BUF_REG_FALLBACK); } static inline int ublk_queue_no_buf(const struct ublk_queue *q) From 22f98de8fa78f5e245046e2714244479e0ffd371 Mon Sep 17 00:00:00 2001 From: Nilay Shroff Date: Thu, 13 Nov 2025 14:28:18 +0530 Subject: [PATCH 1415/3902] block: unify elevator tags and type xarrays into struct elv_change_ctx MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [ Upstream commit 232143b605387b372dee0ec7830f93b93df5f67d ] Currently, the nr_hw_queues update path manages two disjoint xarrays — one for elevator tags and another for elevator type — both used during elevator switching. Maintaining these two parallel structures for the same purpose adds unnecessary complexity and potential for mismatched state. This patch unifies both xarrays into a single structure, struct elv_change_ctx, which holds all per-queue elevator change context. A single xarray, named elv_tbl, now maps each queue (q->id) in a tagset to its corresponding elv_change_ctx entry, encapsulating the elevator tags, type and name references. This unification simplifies the code, improves maintainability, and clarifies ownership of per-queue elevator state. Reviewed-by: Ming Lei Reviewed-by: Yu Kuai Signed-off-by: Nilay Shroff Signed-off-by: Jens Axboe Stable-dep-of: 9869d3a6fed3 ("block: fix race between wbt_enable_default and IO submission") Signed-off-by: Sasha Levin --- block/blk-mq-sched.c | 76 +++++++++++++++++++++++++++++++++----------- block/blk-mq-sched.h | 3 ++ block/blk-mq.c | 50 +++++++++++++++++------------ block/blk.h | 7 ++-- block/elevator.c | 31 ++++-------------- block/elevator.h | 15 +++++++++ 6 files changed, 115 insertions(+), 67 deletions(-) diff --git a/block/blk-mq-sched.c b/block/blk-mq-sched.c index e0bed16485c346..3d9386555a50a7 100644 --- a/block/blk-mq-sched.c +++ b/block/blk-mq-sched.c @@ -427,11 +427,11 @@ void blk_mq_free_sched_tags(struct elevator_tags *et, kfree(et); } -void blk_mq_free_sched_tags_batch(struct xarray *et_table, +void blk_mq_free_sched_tags_batch(struct xarray *elv_tbl, struct blk_mq_tag_set *set) { struct request_queue *q; - struct elevator_tags *et; + struct elv_change_ctx *ctx; lockdep_assert_held_write(&set->update_nr_hwq_lock); @@ -444,13 +444,47 @@ void blk_mq_free_sched_tags_batch(struct xarray *et_table, * concurrently. */ if (q->elevator) { - et = xa_load(et_table, q->id); - if (unlikely(!et)) + ctx = xa_load(elv_tbl, q->id); + if (!ctx || !ctx->et) { WARN_ON_ONCE(1); - else - blk_mq_free_sched_tags(et, set); + continue; + } + blk_mq_free_sched_tags(ctx->et, set); + ctx->et = NULL; + } + } +} + +void blk_mq_free_sched_ctx_batch(struct xarray *elv_tbl) +{ + unsigned long i; + struct elv_change_ctx *ctx; + + xa_for_each(elv_tbl, i, ctx) { + xa_erase(elv_tbl, i); + kfree(ctx); + } +} + +int blk_mq_alloc_sched_ctx_batch(struct xarray *elv_tbl, + struct blk_mq_tag_set *set) +{ + struct request_queue *q; + struct elv_change_ctx *ctx; + + lockdep_assert_held_write(&set->update_nr_hwq_lock); + + list_for_each_entry(q, &set->tag_list, tag_set_list) { + ctx = kzalloc(sizeof(struct elv_change_ctx), GFP_KERNEL); + if (!ctx) + return -ENOMEM; + + if (xa_insert(elv_tbl, q->id, ctx, GFP_KERNEL)) { + kfree(ctx); + return -ENOMEM; } } + return 0; } struct elevator_tags *blk_mq_alloc_sched_tags(struct blk_mq_tag_set *set, @@ -498,12 +532,13 @@ struct elevator_tags *blk_mq_alloc_sched_tags(struct blk_mq_tag_set *set, return NULL; } -int blk_mq_alloc_sched_tags_batch(struct xarray *et_table, +int blk_mq_alloc_sched_tags_batch(struct xarray *elv_tbl, struct blk_mq_tag_set *set, unsigned int nr_hw_queues) { + struct elv_change_ctx *ctx; struct request_queue *q; struct elevator_tags *et; - gfp_t gfp = GFP_NOIO | __GFP_ZERO | __GFP_NOWARN | __GFP_NORETRY; + int ret = -ENOMEM; lockdep_assert_held_write(&set->update_nr_hwq_lock); @@ -516,26 +551,31 @@ int blk_mq_alloc_sched_tags_batch(struct xarray *et_table, * concurrently. */ if (q->elevator) { - et = blk_mq_alloc_sched_tags(set, nr_hw_queues, + ctx = xa_load(elv_tbl, q->id); + if (WARN_ON_ONCE(!ctx)) { + ret = -ENOENT; + goto out_unwind; + } + + ctx->et = blk_mq_alloc_sched_tags(set, nr_hw_queues, blk_mq_default_nr_requests(set)); - if (!et) + if (!ctx->et) goto out_unwind; - if (xa_insert(et_table, q->id, et, gfp)) - goto out_free_tags; + } } return 0; -out_free_tags: - blk_mq_free_sched_tags(et, set); out_unwind: list_for_each_entry_continue_reverse(q, &set->tag_list, tag_set_list) { if (q->elevator) { - et = xa_load(et_table, q->id); - if (et) - blk_mq_free_sched_tags(et, set); + ctx = xa_load(elv_tbl, q->id); + if (ctx && ctx->et) { + blk_mq_free_sched_tags(ctx->et, set); + ctx->et = NULL; + } } } - return -ENOMEM; + return ret; } /* caller must have a reference to @e, will grab another one if successful */ diff --git a/block/blk-mq-sched.h b/block/blk-mq-sched.h index 8e21a6b1415d9d..2fddbc91a23595 100644 --- a/block/blk-mq-sched.h +++ b/block/blk-mq-sched.h @@ -27,6 +27,9 @@ struct elevator_tags *blk_mq_alloc_sched_tags(struct blk_mq_tag_set *set, unsigned int nr_hw_queues, unsigned int nr_requests); int blk_mq_alloc_sched_tags_batch(struct xarray *et_table, struct blk_mq_tag_set *set, unsigned int nr_hw_queues); +int blk_mq_alloc_sched_ctx_batch(struct xarray *elv_tbl, + struct blk_mq_tag_set *set); +void blk_mq_free_sched_ctx_batch(struct xarray *elv_tbl); void blk_mq_free_sched_tags(struct elevator_tags *et, struct blk_mq_tag_set *set); void blk_mq_free_sched_tags_batch(struct xarray *et_table, diff --git a/block/blk-mq.c b/block/blk-mq.c index f901aeba855229..180d45db562466 100644 --- a/block/blk-mq.c +++ b/block/blk-mq.c @@ -4996,27 +4996,28 @@ struct elevator_tags *blk_mq_update_nr_requests(struct request_queue *q, * Switch back to the elevator type stored in the xarray. */ static void blk_mq_elv_switch_back(struct request_queue *q, - struct xarray *elv_tbl, struct xarray *et_tbl) + struct xarray *elv_tbl) { - struct elevator_type *e = xa_load(elv_tbl, q->id); - struct elevator_tags *t = xa_load(et_tbl, q->id); + struct elv_change_ctx *ctx = xa_load(elv_tbl, q->id); + + if (WARN_ON_ONCE(!ctx)) + return; /* The elv_update_nr_hw_queues unfreezes the queue. */ - elv_update_nr_hw_queues(q, e, t); + elv_update_nr_hw_queues(q, ctx); /* Drop the reference acquired in blk_mq_elv_switch_none. */ - if (e) - elevator_put(e); + if (ctx->type) + elevator_put(ctx->type); } /* - * Stores elevator type in xarray and set current elevator to none. It uses - * q->id as an index to store the elevator type into the xarray. + * Stores elevator name and type in ctx and set current elevator to none. */ static int blk_mq_elv_switch_none(struct request_queue *q, struct xarray *elv_tbl) { - int ret = 0; + struct elv_change_ctx *ctx; lockdep_assert_held_write(&q->tag_set->update_nr_hwq_lock); @@ -5028,10 +5029,11 @@ static int blk_mq_elv_switch_none(struct request_queue *q, * can't run concurrently. */ if (q->elevator) { + ctx = xa_load(elv_tbl, q->id); + if (WARN_ON_ONCE(!ctx)) + return -ENOENT; - ret = xa_insert(elv_tbl, q->id, q->elevator->type, GFP_KERNEL); - if (WARN_ON_ONCE(ret)) - return ret; + ctx->name = q->elevator->type->elevator_name; /* * Before we switch elevator to 'none', take a reference to @@ -5042,9 +5044,14 @@ static int blk_mq_elv_switch_none(struct request_queue *q, */ __elevator_get(q->elevator->type); + /* + * Store elevator type so that we can release the reference + * taken above later. + */ + ctx->type = q->elevator->type; elevator_set_none(q); } - return ret; + return 0; } static void __blk_mq_update_nr_hw_queues(struct blk_mq_tag_set *set, @@ -5054,7 +5061,7 @@ static void __blk_mq_update_nr_hw_queues(struct blk_mq_tag_set *set, int prev_nr_hw_queues = set->nr_hw_queues; unsigned int memflags; int i; - struct xarray elv_tbl, et_tbl; + struct xarray elv_tbl; bool queues_frozen = false; lockdep_assert_held(&set->tag_list_lock); @@ -5068,11 +5075,12 @@ static void __blk_mq_update_nr_hw_queues(struct blk_mq_tag_set *set, memflags = memalloc_noio_save(); - xa_init(&et_tbl); - if (blk_mq_alloc_sched_tags_batch(&et_tbl, set, nr_hw_queues) < 0) - goto out_memalloc_restore; - xa_init(&elv_tbl); + if (blk_mq_alloc_sched_ctx_batch(&elv_tbl, set) < 0) + goto out_free_ctx; + + if (blk_mq_alloc_sched_tags_batch(&elv_tbl, set, nr_hw_queues) < 0) + goto out_free_ctx; list_for_each_entry(q, &set->tag_list, tag_set_list) { blk_mq_debugfs_unregister_hctxs(q); @@ -5118,7 +5126,7 @@ static void __blk_mq_update_nr_hw_queues(struct blk_mq_tag_set *set, /* switch_back expects queue to be frozen */ if (!queues_frozen) blk_mq_freeze_queue_nomemsave(q); - blk_mq_elv_switch_back(q, &elv_tbl, &et_tbl); + blk_mq_elv_switch_back(q, &elv_tbl); } list_for_each_entry(q, &set->tag_list, tag_set_list) { @@ -5129,9 +5137,9 @@ static void __blk_mq_update_nr_hw_queues(struct blk_mq_tag_set *set, blk_mq_add_hw_queues_cpuhp(q); } +out_free_ctx: + blk_mq_free_sched_ctx_batch(&elv_tbl); xa_destroy(&elv_tbl); - xa_destroy(&et_tbl); -out_memalloc_restore: memalloc_noio_restore(memflags); /* Free the excess tags when nr_hw_queues shrink. */ diff --git a/block/blk.h b/block/blk.h index 170794632135da..a7992680f9e144 100644 --- a/block/blk.h +++ b/block/blk.h @@ -11,8 +11,7 @@ #include #include "blk-crypto-internal.h" -struct elevator_type; -struct elevator_tags; +struct elv_change_ctx; /* * Default upper limit for the software max_sectors limit used for regular I/Os. @@ -333,8 +332,8 @@ bool blk_bio_list_merge(struct request_queue *q, struct list_head *list, bool blk_insert_flush(struct request *rq); -void elv_update_nr_hw_queues(struct request_queue *q, struct elevator_type *e, - struct elevator_tags *t); +void elv_update_nr_hw_queues(struct request_queue *q, + struct elv_change_ctx *ctx); void elevator_set_default(struct request_queue *q); void elevator_set_none(struct request_queue *q); diff --git a/block/elevator.c b/block/elevator.c index e2ebfbf107b3af..cd7bdff205c8c2 100644 --- a/block/elevator.c +++ b/block/elevator.c @@ -45,19 +45,6 @@ #include "blk-wbt.h" #include "blk-cgroup.h" -/* Holding context data for changing elevator */ -struct elv_change_ctx { - const char *name; - bool no_uevent; - - /* for unregistering old elevator */ - struct elevator_queue *old; - /* for registering new elevator */ - struct elevator_queue *new; - /* holds sched tags data */ - struct elevator_tags *et; -}; - static DEFINE_SPINLOCK(elv_list_lock); static LIST_HEAD(elv_list); @@ -706,32 +693,28 @@ static int elevator_change(struct request_queue *q, struct elv_change_ctx *ctx) * The I/O scheduler depends on the number of hardware queues, this forces a * reattachment when nr_hw_queues changes. */ -void elv_update_nr_hw_queues(struct request_queue *q, struct elevator_type *e, - struct elevator_tags *t) +void elv_update_nr_hw_queues(struct request_queue *q, + struct elv_change_ctx *ctx) { struct blk_mq_tag_set *set = q->tag_set; - struct elv_change_ctx ctx = {}; int ret = -ENODEV; WARN_ON_ONCE(q->mq_freeze_depth == 0); - if (e && !blk_queue_dying(q) && blk_queue_registered(q)) { - ctx.name = e->elevator_name; - ctx.et = t; - + if (ctx->type && !blk_queue_dying(q) && blk_queue_registered(q)) { mutex_lock(&q->elevator_lock); /* force to reattach elevator after nr_hw_queue is updated */ - ret = elevator_switch(q, &ctx); + ret = elevator_switch(q, ctx); mutex_unlock(&q->elevator_lock); } blk_mq_unfreeze_queue_nomemrestore(q); if (!ret) - WARN_ON_ONCE(elevator_change_done(q, &ctx)); + WARN_ON_ONCE(elevator_change_done(q, ctx)); /* * Free sched tags if it's allocated but we couldn't switch elevator. */ - if (t && !ctx.new) - blk_mq_free_sched_tags(t, set); + if (ctx->et && !ctx->new) + blk_mq_free_sched_tags(ctx->et, set); } /* diff --git a/block/elevator.h b/block/elevator.h index c4d20155065e80..bad43182361e5c 100644 --- a/block/elevator.h +++ b/block/elevator.h @@ -32,6 +32,21 @@ struct elevator_tags { struct blk_mq_tags *tags[]; }; +/* Holding context data for changing elevator */ +struct elv_change_ctx { + const char *name; + bool no_uevent; + + /* for unregistering old elevator */ + struct elevator_queue *old; + /* for registering new elevator */ + struct elevator_queue *new; + /* store elevator type */ + struct elevator_type *type; + /* holds sched tags data */ + struct elevator_tags *et; +}; + struct elevator_mq_ops { int (*init_sched)(struct request_queue *, struct elevator_queue *); void (*exit_sched)(struct elevator_queue *); From aa81ed74af25ac10fcf69515eae18e14a0709f2b Mon Sep 17 00:00:00 2001 From: Nilay Shroff Date: Thu, 13 Nov 2025 14:28:19 +0530 Subject: [PATCH 1416/3902] block: move elevator tags into struct elevator_resources [ Upstream commit 04728ce90966c54417fd8120a3820104d18ba68d ] This patch introduces a new structure, struct elevator_resources, to group together all elevator-related resources that share the same lifetime. As a first step, this change moves the elevator tag pointer from struct elv_change_ctx into the new struct elevator_resources. Additionally, rename blk_mq_alloc_sched_tags_batch() and blk_mq_free_sched_tags_batch() to blk_mq_alloc_sched_res_batch() and blk_mq_free_sched_res_batch(), respectively. Introduce two new wrapper helpers, blk_mq_alloc_sched_res() and blk_mq_free_sched_res(), around blk_mq_alloc_sched_tags() and blk_mq_free_sched_tags(). These changes pave the way for consolidating the allocation and freeing of elevator-specific resources into common helper functions. This refactoring improves encapsulation and prepares the code for future extensions, allowing additional elevator-specific data to be added to struct elevator_resources without cluttering struct elv_change_ctx. Subsequent patches will extend struct elevator_resources to include other elevator-related data. Reviewed-by: Ming Lei Reviewed-by: Yu Kuai Signed-off-by: Nilay Shroff Signed-off-by: Jens Axboe Stable-dep-of: 9869d3a6fed3 ("block: fix race between wbt_enable_default and IO submission") Signed-off-by: Sasha Levin --- block/blk-mq-sched.c | 48 ++++++++++++++++++++++++++++++-------------- block/blk-mq-sched.h | 10 ++++++--- block/blk-mq.c | 2 +- block/elevator.c | 31 ++++++++++++++-------------- block/elevator.h | 9 +++++++-- 5 files changed, 64 insertions(+), 36 deletions(-) diff --git a/block/blk-mq-sched.c b/block/blk-mq-sched.c index 3d9386555a50a7..03ff16c49976a2 100644 --- a/block/blk-mq-sched.c +++ b/block/blk-mq-sched.c @@ -427,7 +427,16 @@ void blk_mq_free_sched_tags(struct elevator_tags *et, kfree(et); } -void blk_mq_free_sched_tags_batch(struct xarray *elv_tbl, +void blk_mq_free_sched_res(struct elevator_resources *res, + struct blk_mq_tag_set *set) +{ + if (res->et) { + blk_mq_free_sched_tags(res->et, set); + res->et = NULL; + } +} + +void blk_mq_free_sched_res_batch(struct xarray *elv_tbl, struct blk_mq_tag_set *set) { struct request_queue *q; @@ -445,12 +454,11 @@ void blk_mq_free_sched_tags_batch(struct xarray *elv_tbl, */ if (q->elevator) { ctx = xa_load(elv_tbl, q->id); - if (!ctx || !ctx->et) { + if (!ctx) { WARN_ON_ONCE(1); continue; } - blk_mq_free_sched_tags(ctx->et, set); - ctx->et = NULL; + blk_mq_free_sched_res(&ctx->res, set); } } } @@ -532,12 +540,24 @@ struct elevator_tags *blk_mq_alloc_sched_tags(struct blk_mq_tag_set *set, return NULL; } -int blk_mq_alloc_sched_tags_batch(struct xarray *elv_tbl, +int blk_mq_alloc_sched_res(struct request_queue *q, + struct elevator_resources *res, unsigned int nr_hw_queues) +{ + struct blk_mq_tag_set *set = q->tag_set; + + res->et = blk_mq_alloc_sched_tags(set, nr_hw_queues, + blk_mq_default_nr_requests(set)); + if (!res->et) + return -ENOMEM; + + return 0; +} + +int blk_mq_alloc_sched_res_batch(struct xarray *elv_tbl, struct blk_mq_tag_set *set, unsigned int nr_hw_queues) { struct elv_change_ctx *ctx; struct request_queue *q; - struct elevator_tags *et; int ret = -ENOMEM; lockdep_assert_held_write(&set->update_nr_hwq_lock); @@ -557,11 +577,10 @@ int blk_mq_alloc_sched_tags_batch(struct xarray *elv_tbl, goto out_unwind; } - ctx->et = blk_mq_alloc_sched_tags(set, nr_hw_queues, - blk_mq_default_nr_requests(set)); - if (!ctx->et) + ret = blk_mq_alloc_sched_res(q, &ctx->res, + nr_hw_queues); + if (ret) goto out_unwind; - } } return 0; @@ -569,10 +588,8 @@ int blk_mq_alloc_sched_tags_batch(struct xarray *elv_tbl, list_for_each_entry_continue_reverse(q, &set->tag_list, tag_set_list) { if (q->elevator) { ctx = xa_load(elv_tbl, q->id); - if (ctx && ctx->et) { - blk_mq_free_sched_tags(ctx->et, set); - ctx->et = NULL; - } + if (ctx) + blk_mq_free_sched_res(&ctx->res, set); } } return ret; @@ -580,9 +597,10 @@ int blk_mq_alloc_sched_tags_batch(struct xarray *elv_tbl, /* caller must have a reference to @e, will grab another one if successful */ int blk_mq_init_sched(struct request_queue *q, struct elevator_type *e, - struct elevator_tags *et) + struct elevator_resources *res) { unsigned int flags = q->tag_set->flags; + struct elevator_tags *et = res->et; struct blk_mq_hw_ctx *hctx; struct elevator_queue *eq; unsigned long i; diff --git a/block/blk-mq-sched.h b/block/blk-mq-sched.h index 2fddbc91a23595..1f8e58dd4b497a 100644 --- a/block/blk-mq-sched.h +++ b/block/blk-mq-sched.h @@ -19,20 +19,24 @@ void __blk_mq_sched_restart(struct blk_mq_hw_ctx *hctx); void blk_mq_sched_dispatch_requests(struct blk_mq_hw_ctx *hctx); int blk_mq_init_sched(struct request_queue *q, struct elevator_type *e, - struct elevator_tags *et); + struct elevator_resources *res); void blk_mq_exit_sched(struct request_queue *q, struct elevator_queue *e); void blk_mq_sched_free_rqs(struct request_queue *q); struct elevator_tags *blk_mq_alloc_sched_tags(struct blk_mq_tag_set *set, unsigned int nr_hw_queues, unsigned int nr_requests); -int blk_mq_alloc_sched_tags_batch(struct xarray *et_table, +int blk_mq_alloc_sched_res(struct request_queue *q, + struct elevator_resources *res, unsigned int nr_hw_queues); +int blk_mq_alloc_sched_res_batch(struct xarray *elv_tbl, struct blk_mq_tag_set *set, unsigned int nr_hw_queues); int blk_mq_alloc_sched_ctx_batch(struct xarray *elv_tbl, struct blk_mq_tag_set *set); void blk_mq_free_sched_ctx_batch(struct xarray *elv_tbl); void blk_mq_free_sched_tags(struct elevator_tags *et, struct blk_mq_tag_set *set); -void blk_mq_free_sched_tags_batch(struct xarray *et_table, +void blk_mq_free_sched_res(struct elevator_resources *res, + struct blk_mq_tag_set *set); +void blk_mq_free_sched_res_batch(struct xarray *et_table, struct blk_mq_tag_set *set); static inline void blk_mq_sched_restart(struct blk_mq_hw_ctx *hctx) diff --git a/block/blk-mq.c b/block/blk-mq.c index 180d45db562466..ea5f948af7a42d 100644 --- a/block/blk-mq.c +++ b/block/blk-mq.c @@ -5079,7 +5079,7 @@ static void __blk_mq_update_nr_hw_queues(struct blk_mq_tag_set *set, if (blk_mq_alloc_sched_ctx_batch(&elv_tbl, set) < 0) goto out_free_ctx; - if (blk_mq_alloc_sched_tags_batch(&elv_tbl, set, nr_hw_queues) < 0) + if (blk_mq_alloc_sched_res_batch(&elv_tbl, set, nr_hw_queues) < 0) goto out_free_ctx; list_for_each_entry(q, &set->tag_list, tag_set_list) { diff --git a/block/elevator.c b/block/elevator.c index cd7bdff205c8c2..cbec292a4af5b1 100644 --- a/block/elevator.c +++ b/block/elevator.c @@ -580,7 +580,7 @@ static int elevator_switch(struct request_queue *q, struct elv_change_ctx *ctx) } if (new_e) { - ret = blk_mq_init_sched(q, new_e, ctx->et); + ret = blk_mq_init_sched(q, new_e, &ctx->res); if (ret) goto out_unfreeze; ctx->new = q->elevator; @@ -604,7 +604,8 @@ static int elevator_switch(struct request_queue *q, struct elv_change_ctx *ctx) return ret; } -static void elv_exit_and_release(struct request_queue *q) +static void elv_exit_and_release(struct elv_change_ctx *ctx, + struct request_queue *q) { struct elevator_queue *e; unsigned memflags; @@ -616,7 +617,7 @@ static void elv_exit_and_release(struct request_queue *q) mutex_unlock(&q->elevator_lock); blk_mq_unfreeze_queue(q, memflags); if (e) { - blk_mq_free_sched_tags(e->et, q->tag_set); + blk_mq_free_sched_res(&ctx->res, q->tag_set); kobject_put(&e->kobj); } } @@ -627,11 +628,12 @@ static int elevator_change_done(struct request_queue *q, int ret = 0; if (ctx->old) { + struct elevator_resources res = {.et = ctx->old->et}; bool enable_wbt = test_bit(ELEVATOR_FLAG_ENABLE_WBT_ON_EXIT, &ctx->old->flags); elv_unregister_queue(q, ctx->old); - blk_mq_free_sched_tags(ctx->old->et, q->tag_set); + blk_mq_free_sched_res(&res, q->tag_set); kobject_put(&ctx->old->kobj); if (enable_wbt) wbt_enable_default(q->disk); @@ -639,7 +641,7 @@ static int elevator_change_done(struct request_queue *q, if (ctx->new) { ret = elv_register_queue(q, ctx->new, !ctx->no_uevent); if (ret) - elv_exit_and_release(q); + elv_exit_and_release(ctx, q); } return ret; } @@ -656,10 +658,9 @@ static int elevator_change(struct request_queue *q, struct elv_change_ctx *ctx) lockdep_assert_held(&set->update_nr_hwq_lock); if (strncmp(ctx->name, "none", 4)) { - ctx->et = blk_mq_alloc_sched_tags(set, set->nr_hw_queues, - blk_mq_default_nr_requests(set)); - if (!ctx->et) - return -ENOMEM; + ret = blk_mq_alloc_sched_res(q, &ctx->res, set->nr_hw_queues); + if (ret) + return ret; } memflags = blk_mq_freeze_queue(q); @@ -681,10 +682,10 @@ static int elevator_change(struct request_queue *q, struct elv_change_ctx *ctx) if (!ret) ret = elevator_change_done(q, ctx); /* - * Free sched tags if it's allocated but we couldn't switch elevator. + * Free sched resource if it's allocated but we couldn't switch elevator. */ - if (ctx->et && !ctx->new) - blk_mq_free_sched_tags(ctx->et, set); + if (!ctx->new) + blk_mq_free_sched_res(&ctx->res, set); return ret; } @@ -711,10 +712,10 @@ void elv_update_nr_hw_queues(struct request_queue *q, if (!ret) WARN_ON_ONCE(elevator_change_done(q, ctx)); /* - * Free sched tags if it's allocated but we couldn't switch elevator. + * Free sched resource if it's allocated but we couldn't switch elevator. */ - if (ctx->et && !ctx->new) - blk_mq_free_sched_tags(ctx->et, set); + if (!ctx->new) + blk_mq_free_sched_res(&ctx->res, set); } /* diff --git a/block/elevator.h b/block/elevator.h index bad43182361e5c..621a63597249e7 100644 --- a/block/elevator.h +++ b/block/elevator.h @@ -32,6 +32,11 @@ struct elevator_tags { struct blk_mq_tags *tags[]; }; +struct elevator_resources { + /* holds elevator tags */ + struct elevator_tags *et; +}; + /* Holding context data for changing elevator */ struct elv_change_ctx { const char *name; @@ -43,8 +48,8 @@ struct elv_change_ctx { struct elevator_queue *new; /* store elevator type */ struct elevator_type *type; - /* holds sched tags data */ - struct elevator_tags *et; + /* store elevator resources */ + struct elevator_resources res; }; struct elevator_mq_ops { From 93869ded0cccf27fbb82d5356afd66d711f88230 Mon Sep 17 00:00:00 2001 From: Nilay Shroff Date: Thu, 13 Nov 2025 14:28:20 +0530 Subject: [PATCH 1417/3902] block: introduce alloc_sched_data and free_sched_data elevator methods [ Upstream commit 61019afdf6ac17c8e8f9c42665aa1fa82f04a3e2 ] The recent lockdep splat [1] highlights a potential deadlock risk involving ->elevator_lock and ->freeze_lock dependencies on -pcpu_alloc_ mutex. The trace shows that the issue occurs when the Kyber scheduler allocates dynamic memory for its elevator data during initialization. To address this, introduce two new elevator operation callbacks: ->alloc_sched_data and ->free_sched_data. The subsequent patch would build upon these newly introduced methods to suppress lockdep splat[1]. [1] https://lore.kernel.org/all/CAGVVp+VNW4M-5DZMNoADp6o2VKFhi7KxWpTDkcnVyjO0=-D5+A@mail.gmail.com/ Signed-off-by: Nilay Shroff Reviewed-by: Ming Lei Signed-off-by: Jens Axboe Stable-dep-of: 9869d3a6fed3 ("block: fix race between wbt_enable_default and IO submission") Signed-off-by: Sasha Levin --- block/blk-mq-sched.h | 24 ++++++++++++++++++++++++ block/elevator.h | 2 ++ 2 files changed, 26 insertions(+) diff --git a/block/blk-mq-sched.h b/block/blk-mq-sched.h index 1f8e58dd4b497a..4e1b86e85a8a97 100644 --- a/block/blk-mq-sched.h +++ b/block/blk-mq-sched.h @@ -38,6 +38,30 @@ void blk_mq_free_sched_res(struct elevator_resources *res, struct blk_mq_tag_set *set); void blk_mq_free_sched_res_batch(struct xarray *et_table, struct blk_mq_tag_set *set); +/* + * blk_mq_alloc_sched_data() - Allocates scheduler specific data + * Returns: + * - Pointer to allocated data on success + * - NULL if no allocation needed + * - ERR_PTR(-ENOMEM) in case of failure + */ +static inline void *blk_mq_alloc_sched_data(struct request_queue *q, + struct elevator_type *e) +{ + void *sched_data; + + if (!e || !e->ops.alloc_sched_data) + return NULL; + + sched_data = e->ops.alloc_sched_data(q); + return (sched_data) ?: ERR_PTR(-ENOMEM); +} + +static inline void blk_mq_free_sched_data(struct elevator_type *e, void *data) +{ + if (e && e->ops.free_sched_data) + e->ops.free_sched_data(data); +} static inline void blk_mq_sched_restart(struct blk_mq_hw_ctx *hctx) { diff --git a/block/elevator.h b/block/elevator.h index 621a63597249e7..e34043f6da26e3 100644 --- a/block/elevator.h +++ b/block/elevator.h @@ -58,6 +58,8 @@ struct elevator_mq_ops { int (*init_hctx)(struct blk_mq_hw_ctx *, unsigned int); void (*exit_hctx)(struct blk_mq_hw_ctx *, unsigned int); void (*depth_updated)(struct request_queue *); + void *(*alloc_sched_data)(struct request_queue *); + void (*free_sched_data)(void *); bool (*allow_merge)(struct request_queue *, struct request *, struct bio *); bool (*bio_merge)(struct request_queue *, struct bio *, unsigned int); From ef8178f8100249e066bf70fe8305481eb3e149a1 Mon Sep 17 00:00:00 2001 From: Nilay Shroff Date: Thu, 13 Nov 2025 14:28:21 +0530 Subject: [PATCH 1418/3902] block: use {alloc|free}_sched data methods [ Upstream commit 0315476e78c050048e80f66334a310e5581b46bb ] The previous patch introduced ->alloc_sched_data and ->free_sched_data methods. This patch builds upon that by now using these methods during elevator switch and nr_hw_queue update. It's also ensured that scheduler-specific data is allocated and freed through the new callbacks outside of the ->freeze_lock and ->elevator_lock locking contexts, thereby preventing any dependency on pcpu_alloc_mutex. Reviewed-by: Ming Lei Reviewed-by: Yu Kuai Signed-off-by: Nilay Shroff Signed-off-by: Jens Axboe Stable-dep-of: 9869d3a6fed3 ("block: fix race between wbt_enable_default and IO submission") Signed-off-by: Sasha Levin --- block/blk-mq-sched.c | 27 +++++++++++++++++++++------ block/blk-mq-sched.h | 5 ++++- block/elevator.c | 34 ++++++++++++++++++++++------------ block/elevator.h | 4 +++- 4 files changed, 50 insertions(+), 20 deletions(-) diff --git a/block/blk-mq-sched.c b/block/blk-mq-sched.c index 03ff16c49976a2..128f2be9d42073 100644 --- a/block/blk-mq-sched.c +++ b/block/blk-mq-sched.c @@ -428,12 +428,17 @@ void blk_mq_free_sched_tags(struct elevator_tags *et, } void blk_mq_free_sched_res(struct elevator_resources *res, + struct elevator_type *type, struct blk_mq_tag_set *set) { if (res->et) { blk_mq_free_sched_tags(res->et, set); res->et = NULL; } + if (res->data) { + blk_mq_free_sched_data(type, res->data); + res->data = NULL; + } } void blk_mq_free_sched_res_batch(struct xarray *elv_tbl, @@ -458,7 +463,7 @@ void blk_mq_free_sched_res_batch(struct xarray *elv_tbl, WARN_ON_ONCE(1); continue; } - blk_mq_free_sched_res(&ctx->res, set); + blk_mq_free_sched_res(&ctx->res, ctx->type, set); } } } @@ -541,7 +546,9 @@ struct elevator_tags *blk_mq_alloc_sched_tags(struct blk_mq_tag_set *set, } int blk_mq_alloc_sched_res(struct request_queue *q, - struct elevator_resources *res, unsigned int nr_hw_queues) + struct elevator_type *type, + struct elevator_resources *res, + unsigned int nr_hw_queues) { struct blk_mq_tag_set *set = q->tag_set; @@ -550,6 +557,12 @@ int blk_mq_alloc_sched_res(struct request_queue *q, if (!res->et) return -ENOMEM; + res->data = blk_mq_alloc_sched_data(q, type); + if (IS_ERR(res->data)) { + blk_mq_free_sched_tags(res->et, set); + return -ENOMEM; + } + return 0; } @@ -577,19 +590,21 @@ int blk_mq_alloc_sched_res_batch(struct xarray *elv_tbl, goto out_unwind; } - ret = blk_mq_alloc_sched_res(q, &ctx->res, - nr_hw_queues); + ret = blk_mq_alloc_sched_res(q, q->elevator->type, + &ctx->res, nr_hw_queues); if (ret) goto out_unwind; } } return 0; + out_unwind: list_for_each_entry_continue_reverse(q, &set->tag_list, tag_set_list) { if (q->elevator) { ctx = xa_load(elv_tbl, q->id); if (ctx) - blk_mq_free_sched_res(&ctx->res, set); + blk_mq_free_sched_res(&ctx->res, + ctx->type, set); } } return ret; @@ -606,7 +621,7 @@ int blk_mq_init_sched(struct request_queue *q, struct elevator_type *e, unsigned long i; int ret; - eq = elevator_alloc(q, e, et); + eq = elevator_alloc(q, e, res); if (!eq) return -ENOMEM; diff --git a/block/blk-mq-sched.h b/block/blk-mq-sched.h index 4e1b86e85a8a97..02c40a72e9598c 100644 --- a/block/blk-mq-sched.h +++ b/block/blk-mq-sched.h @@ -26,7 +26,9 @@ void blk_mq_sched_free_rqs(struct request_queue *q); struct elevator_tags *blk_mq_alloc_sched_tags(struct blk_mq_tag_set *set, unsigned int nr_hw_queues, unsigned int nr_requests); int blk_mq_alloc_sched_res(struct request_queue *q, - struct elevator_resources *res, unsigned int nr_hw_queues); + struct elevator_type *type, + struct elevator_resources *res, + unsigned int nr_hw_queues); int blk_mq_alloc_sched_res_batch(struct xarray *elv_tbl, struct blk_mq_tag_set *set, unsigned int nr_hw_queues); int blk_mq_alloc_sched_ctx_batch(struct xarray *elv_tbl, @@ -35,6 +37,7 @@ void blk_mq_free_sched_ctx_batch(struct xarray *elv_tbl); void blk_mq_free_sched_tags(struct elevator_tags *et, struct blk_mq_tag_set *set); void blk_mq_free_sched_res(struct elevator_resources *res, + struct elevator_type *type, struct blk_mq_tag_set *set); void blk_mq_free_sched_res_batch(struct xarray *et_table, struct blk_mq_tag_set *set); diff --git a/block/elevator.c b/block/elevator.c index cbec292a4af5b1..5b37ef44f52d7f 100644 --- a/block/elevator.c +++ b/block/elevator.c @@ -121,7 +121,7 @@ static struct elevator_type *elevator_find_get(const char *name) static const struct kobj_type elv_ktype; struct elevator_queue *elevator_alloc(struct request_queue *q, - struct elevator_type *e, struct elevator_tags *et) + struct elevator_type *e, struct elevator_resources *res) { struct elevator_queue *eq; @@ -134,7 +134,8 @@ struct elevator_queue *elevator_alloc(struct request_queue *q, kobject_init(&eq->kobj, &elv_ktype); mutex_init(&eq->sysfs_lock); hash_init(eq->hash); - eq->et = et; + eq->et = res->et; + eq->elevator_data = res->data; return eq; } @@ -617,7 +618,7 @@ static void elv_exit_and_release(struct elv_change_ctx *ctx, mutex_unlock(&q->elevator_lock); blk_mq_unfreeze_queue(q, memflags); if (e) { - blk_mq_free_sched_res(&ctx->res, q->tag_set); + blk_mq_free_sched_res(&ctx->res, ctx->type, q->tag_set); kobject_put(&e->kobj); } } @@ -628,12 +629,15 @@ static int elevator_change_done(struct request_queue *q, int ret = 0; if (ctx->old) { - struct elevator_resources res = {.et = ctx->old->et}; + struct elevator_resources res = { + .et = ctx->old->et, + .data = ctx->old->elevator_data + }; bool enable_wbt = test_bit(ELEVATOR_FLAG_ENABLE_WBT_ON_EXIT, &ctx->old->flags); elv_unregister_queue(q, ctx->old); - blk_mq_free_sched_res(&res, q->tag_set); + blk_mq_free_sched_res(&res, ctx->old->type, q->tag_set); kobject_put(&ctx->old->kobj); if (enable_wbt) wbt_enable_default(q->disk); @@ -658,7 +662,8 @@ static int elevator_change(struct request_queue *q, struct elv_change_ctx *ctx) lockdep_assert_held(&set->update_nr_hwq_lock); if (strncmp(ctx->name, "none", 4)) { - ret = blk_mq_alloc_sched_res(q, &ctx->res, set->nr_hw_queues); + ret = blk_mq_alloc_sched_res(q, ctx->type, &ctx->res, + set->nr_hw_queues); if (ret) return ret; } @@ -681,11 +686,12 @@ static int elevator_change(struct request_queue *q, struct elv_change_ctx *ctx) blk_mq_unfreeze_queue(q, memflags); if (!ret) ret = elevator_change_done(q, ctx); + /* * Free sched resource if it's allocated but we couldn't switch elevator. */ if (!ctx->new) - blk_mq_free_sched_res(&ctx->res, set); + blk_mq_free_sched_res(&ctx->res, ctx->type, set); return ret; } @@ -711,11 +717,12 @@ void elv_update_nr_hw_queues(struct request_queue *q, blk_mq_unfreeze_queue_nomemrestore(q); if (!ret) WARN_ON_ONCE(elevator_change_done(q, ctx)); + /* * Free sched resource if it's allocated but we couldn't switch elevator. */ if (!ctx->new) - blk_mq_free_sched_res(&ctx->res, set); + blk_mq_free_sched_res(&ctx->res, ctx->type, set); } /* @@ -729,7 +736,6 @@ void elevator_set_default(struct request_queue *q) .no_uevent = true, }; int err; - struct elevator_type *e; /* now we allow to switch elevator */ blk_queue_flag_clear(QUEUE_FLAG_NO_ELV_SWITCH, q); @@ -742,8 +748,8 @@ void elevator_set_default(struct request_queue *q) * have multiple queues or mq-deadline is not available, default * to "none". */ - e = elevator_find_get(ctx.name); - if (!e) + ctx.type = elevator_find_get(ctx.name); + if (!ctx.type) return; if ((q->nr_hw_queues == 1 || @@ -753,7 +759,7 @@ void elevator_set_default(struct request_queue *q) pr_warn("\"%s\" elevator initialization, failed %d, falling back to \"none\"\n", ctx.name, err); } - elevator_put(e); + elevator_put(ctx.type); } void elevator_set_none(struct request_queue *q) @@ -802,6 +808,7 @@ ssize_t elv_iosched_store(struct gendisk *disk, const char *buf, ctx.name = strstrip(elevator_name); elv_iosched_load_module(ctx.name); + ctx.type = elevator_find_get(ctx.name); down_read(&set->update_nr_hwq_lock); if (!blk_queue_no_elv_switch(q)) { @@ -812,6 +819,9 @@ ssize_t elv_iosched_store(struct gendisk *disk, const char *buf, ret = -ENOENT; } up_read(&set->update_nr_hwq_lock); + + if (ctx.type) + elevator_put(ctx.type); return ret; } diff --git a/block/elevator.h b/block/elevator.h index e34043f6da26e3..3ee1d494f48afc 100644 --- a/block/elevator.h +++ b/block/elevator.h @@ -33,6 +33,8 @@ struct elevator_tags { }; struct elevator_resources { + /* holds elevator data */ + void *data; /* holds elevator tags */ struct elevator_tags *et; }; @@ -185,7 +187,7 @@ ssize_t elv_iosched_store(struct gendisk *disk, const char *page, size_t count); extern bool elv_bio_merge_ok(struct request *, struct bio *); struct elevator_queue *elevator_alloc(struct request_queue *, - struct elevator_type *, struct elevator_tags *); + struct elevator_type *, struct elevator_resources *); /* * Helper functions. From f55201fb3becff6a903fd29f4d1147cc7e91eb0c Mon Sep 17 00:00:00 2001 From: Ming Lei Date: Fri, 12 Dec 2025 22:35:00 +0800 Subject: [PATCH 1419/3902] block: fix race between wbt_enable_default and IO submission [ Upstream commit 9869d3a6fed381f3b98404e26e1afc75d680cbf9 ] When wbt_enable_default() is moved out of queue freezing in elevator_change(), it can cause the wbt inflight counter to become negative (-1), leading to hung tasks in the writeback path. Tasks get stuck in wbt_wait() because the counter is in an inconsistent state. The issue occurs because wbt_enable_default() could race with IO submission, allowing the counter to be decremented before proper initialization. This manifests as: rq_wait[0]: inflight: -1 has_waiters: True rwb_enabled() checks the state, which can be updated exactly between wbt_wait() (rq_qos_throttle()) and wbt_track()(rq_qos_track()), then the inflight counter will become negative. And results in hung task warnings like: task:kworker/u24:39 state:D stack:0 pid:14767 Call Trace: rq_qos_wait+0xb4/0x150 wbt_wait+0xa9/0x100 __rq_qos_throttle+0x24/0x40 blk_mq_submit_bio+0x672/0x7b0 ... Fix this by: 1. Splitting wbt_enable_default() into: - __wbt_enable_default(): Returns true if wbt_init() should be called - wbt_enable_default(): Wrapper for existing callers (no init) - wbt_init_enable_default(): New function that checks and inits WBT 2. Using wbt_init_enable_default() in blk_register_queue() to ensure proper initialization during queue registration 3. Move wbt_init() out of wbt_enable_default() which is only for enabling disabled wbt from bfq and iocost, and wbt_init() isn't needed. Then the original lock warning can be avoided. 4. Removing the ELEVATOR_FLAG_ENABLE_WBT_ON_EXIT flag and its handling code since it's no longer needed This ensures WBT is properly initialized before any IO can be submitted, preventing the counter from going negative. Cc: Nilay Shroff Cc: Yu Kuai Cc: Guangwu Zhang Fixes: 78c271344b6f ("block: move wbt_enable_default() out of queue freezing from sched ->exit()") Signed-off-by: Ming Lei Reviewed-by: Nilay Shroff Signed-off-by: Jens Axboe Signed-off-by: Sasha Levin --- block/bfq-iosched.c | 2 +- block/blk-sysfs.c | 2 +- block/blk-wbt.c | 20 ++++++++++++++++---- block/blk-wbt.h | 5 +++++ block/elevator.c | 4 ---- block/elevator.h | 1 - 6 files changed, 23 insertions(+), 11 deletions(-) diff --git a/block/bfq-iosched.c b/block/bfq-iosched.c index 4a8d3d96bfe492..6e54b1d3d8bc2a 100644 --- a/block/bfq-iosched.c +++ b/block/bfq-iosched.c @@ -7181,7 +7181,7 @@ static void bfq_exit_queue(struct elevator_queue *e) blk_stat_disable_accounting(bfqd->queue); blk_queue_flag_clear(QUEUE_FLAG_DISABLE_WBT_DEF, bfqd->queue); - set_bit(ELEVATOR_FLAG_ENABLE_WBT_ON_EXIT, &e->flags); + wbt_enable_default(bfqd->queue->disk); kfree(bfqd); } diff --git a/block/blk-sysfs.c b/block/blk-sysfs.c index 76c47fe9b8d68c..c0e4daaf96100a 100644 --- a/block/blk-sysfs.c +++ b/block/blk-sysfs.c @@ -942,7 +942,7 @@ int blk_register_queue(struct gendisk *disk) elevator_set_default(q); blk_queue_flag_set(QUEUE_FLAG_REGISTERED, q); - wbt_enable_default(disk); + wbt_init_enable_default(disk); /* Now everything is ready and send out KOBJ_ADD uevent */ kobject_uevent(&disk->queue_kobj, KOBJ_ADD); diff --git a/block/blk-wbt.c b/block/blk-wbt.c index eb8037bae0bda6..0974875f77bda2 100644 --- a/block/blk-wbt.c +++ b/block/blk-wbt.c @@ -699,7 +699,7 @@ static void wbt_requeue(struct rq_qos *rqos, struct request *rq) /* * Enable wbt if defaults are configured that way */ -void wbt_enable_default(struct gendisk *disk) +static bool __wbt_enable_default(struct gendisk *disk) { struct request_queue *q = disk->queue; struct rq_qos *rqos; @@ -716,19 +716,31 @@ void wbt_enable_default(struct gendisk *disk) if (enable && RQWB(rqos)->enable_state == WBT_STATE_OFF_DEFAULT) RQWB(rqos)->enable_state = WBT_STATE_ON_DEFAULT; mutex_unlock(&disk->rqos_state_mutex); - return; + return false; } mutex_unlock(&disk->rqos_state_mutex); /* Queue not registered? Maybe shutting down... */ if (!blk_queue_registered(q)) - return; + return false; if (queue_is_mq(q) && enable) - wbt_init(disk); + return true; + return false; +} + +void wbt_enable_default(struct gendisk *disk) +{ + __wbt_enable_default(disk); } EXPORT_SYMBOL_GPL(wbt_enable_default); +void wbt_init_enable_default(struct gendisk *disk) +{ + if (__wbt_enable_default(disk)) + WARN_ON_ONCE(wbt_init(disk)); +} + u64 wbt_default_latency_nsec(struct request_queue *q) { /* diff --git a/block/blk-wbt.h b/block/blk-wbt.h index e5fc653b9b76f6..925f2247573834 100644 --- a/block/blk-wbt.h +++ b/block/blk-wbt.h @@ -5,6 +5,7 @@ #ifdef CONFIG_BLK_WBT int wbt_init(struct gendisk *disk); +void wbt_init_enable_default(struct gendisk *disk); void wbt_disable_default(struct gendisk *disk); void wbt_enable_default(struct gendisk *disk); @@ -16,6 +17,10 @@ u64 wbt_default_latency_nsec(struct request_queue *); #else +static inline void wbt_init_enable_default(struct gendisk *disk) +{ +} + static inline void wbt_disable_default(struct gendisk *disk) { } diff --git a/block/elevator.c b/block/elevator.c index 5b37ef44f52d7f..a2f8b2251dc6e6 100644 --- a/block/elevator.c +++ b/block/elevator.c @@ -633,14 +633,10 @@ static int elevator_change_done(struct request_queue *q, .et = ctx->old->et, .data = ctx->old->elevator_data }; - bool enable_wbt = test_bit(ELEVATOR_FLAG_ENABLE_WBT_ON_EXIT, - &ctx->old->flags); elv_unregister_queue(q, ctx->old); blk_mq_free_sched_res(&res, ctx->old->type, q->tag_set); kobject_put(&ctx->old->kobj); - if (enable_wbt) - wbt_enable_default(q->disk); } if (ctx->new) { ret = elv_register_queue(q, ctx->new, !ctx->no_uevent); diff --git a/block/elevator.h b/block/elevator.h index 3ee1d494f48afc..02172637604234 100644 --- a/block/elevator.h +++ b/block/elevator.h @@ -156,7 +156,6 @@ struct elevator_queue #define ELEVATOR_FLAG_REGISTERED 0 #define ELEVATOR_FLAG_DYING 1 -#define ELEVATOR_FLAG_ENABLE_WBT_ON_EXIT 2 /* * block elevator interface From c062e2785bbea7aebc1f8dbe82d9b63afdb7872a Mon Sep 17 00:00:00 2001 From: Prajna Rajendra Kumar Date: Fri, 14 Nov 2025 10:45:43 +0000 Subject: [PATCH 1420/3902] spi: microchip: rename driver file and internal identifiers [ Upstream commit 71c814e98696f2cd53e9e6cef7501c2d667d4c5a ] The spi-microchip-core.c driver provides support for the Microchip PolarFire SoC (MPFS) "hard" SPI controller. It was originally named "core" with the expectation that it might also cover Microchip's CoreSPI "soft" IP, but that never materialized. The CoreSPI IP cannot be supported by this driver because its register layout differs substantially from the MPFS SPI controller. In practice most of the code would need to be replaced to handle those differences so keeping the drivers separate is the simpler approach. The file and internal symbols are renamed to reflect MPFS support and to free up "spi-microchip-core.c" for CoreSPI driver. Fixes: 9ac8d17694b6 ("spi: add support for microchip fpga spi controllers") Signed-off-by: Prajna Rajendra Kumar Acked-by: Conor Dooley Link: https://patch.msgid.link/20251114104545.284765-2-prajna.rajendrakumar@microchip.com Signed-off-by: Mark Brown Stable-dep-of: a8a313612af7 ("spi: mpfs: Fix an error handling path in mpfs_spi_probe()") Signed-off-by: Sasha Levin --- drivers/spi/Kconfig | 19 +- drivers/spi/Makefile | 2 +- .../spi/{spi-microchip-core.c => spi-mpfs.c} | 207 +++++++++--------- 3 files changed, 115 insertions(+), 113 deletions(-) rename drivers/spi/{spi-microchip-core.c => spi-mpfs.c} (68%) diff --git a/drivers/spi/Kconfig b/drivers/spi/Kconfig index 55675750182e3d..1872f9d54a5cc9 100644 --- a/drivers/spi/Kconfig +++ b/drivers/spi/Kconfig @@ -706,15 +706,6 @@ config SPI_MESON_SPIFC This enables master mode support for the SPIFC (SPI flash controller) available in Amlogic Meson SoCs. -config SPI_MICROCHIP_CORE - tristate "Microchip FPGA SPI controllers" - depends on SPI_MASTER - help - This enables the SPI driver for Microchip FPGA SPI controllers. - Say Y or M here if you want to use the "hard" controllers on - PolarFire SoC. - If built as a module, it will be called spi-microchip-core. - config SPI_MICROCHIP_CORE_QSPI tristate "Microchip FPGA QSPI controllers" depends on SPI_MASTER @@ -871,6 +862,16 @@ config SPI_PL022 controller. If you have an embedded system with an AMBA(R) bus and a PL022 controller, say Y or M here. +config SPI_POLARFIRE_SOC + + tristate "Microchip FPGA SPI controllers" + depends on SPI_MASTER && ARCH_MICROCHIP + help + This enables the SPI driver for Microchip FPGA SPI controllers. + Say Y or M here if you want to use the "hard" controllers on + PolarFire SoC. + If built as a module, it will be called spi-mpfs. + config SPI_PPC4xx tristate "PPC4xx SPI Controller" depends on PPC32 && 4xx diff --git a/drivers/spi/Makefile b/drivers/spi/Makefile index 8ff74a13faaa88..1f7c06a3091d92 100644 --- a/drivers/spi/Makefile +++ b/drivers/spi/Makefile @@ -86,7 +86,6 @@ obj-$(CONFIG_SPI_LOONGSON_PLATFORM) += spi-loongson-plat.o obj-$(CONFIG_SPI_LP8841_RTC) += spi-lp8841-rtc.o obj-$(CONFIG_SPI_MESON_SPICC) += spi-meson-spicc.o obj-$(CONFIG_SPI_MESON_SPIFC) += spi-meson-spifc.o -obj-$(CONFIG_SPI_MICROCHIP_CORE) += spi-microchip-core.o obj-$(CONFIG_SPI_MICROCHIP_CORE_QSPI) += spi-microchip-core-qspi.o obj-$(CONFIG_SPI_MPC512x_PSC) += spi-mpc512x-psc.o obj-$(CONFIG_SPI_MPC52xx_PSC) += spi-mpc52xx-psc.o @@ -97,6 +96,7 @@ obj-$(CONFIG_SPI_MTK_NOR) += spi-mtk-nor.o obj-$(CONFIG_SPI_MTK_SNFI) += spi-mtk-snfi.o obj-$(CONFIG_SPI_MXIC) += spi-mxic.o obj-$(CONFIG_SPI_MXS) += spi-mxs.o +obj-$(CONFIG_SPI_POLARFIRE_SOC) += spi-mpfs.o obj-$(CONFIG_SPI_WPCM_FIU) += spi-wpcm-fiu.o obj-$(CONFIG_SPI_NPCM_FIU) += spi-npcm-fiu.o obj-$(CONFIG_SPI_NPCM_PSPI) += spi-npcm-pspi.o diff --git a/drivers/spi/spi-microchip-core.c b/drivers/spi/spi-mpfs.c similarity index 68% rename from drivers/spi/spi-microchip-core.c rename to drivers/spi/spi-mpfs.c index 9128b86c536603..9a14d1732a1598 100644 --- a/drivers/spi/spi-microchip-core.c +++ b/drivers/spi/spi-mpfs.c @@ -99,7 +99,7 @@ #define REG_CTRL2 (0x48) #define REG_FRAMESUP (0x50) -struct mchp_corespi { +struct mpfs_spi { void __iomem *regs; struct clk *clk; const u8 *tx_buf; @@ -113,34 +113,34 @@ struct mchp_corespi { int n_bytes; }; -static inline u32 mchp_corespi_read(struct mchp_corespi *spi, unsigned int reg) +static inline u32 mpfs_spi_read(struct mpfs_spi *spi, unsigned int reg) { return readl(spi->regs + reg); } -static inline void mchp_corespi_write(struct mchp_corespi *spi, unsigned int reg, u32 val) +static inline void mpfs_spi_write(struct mpfs_spi *spi, unsigned int reg, u32 val) { writel(val, spi->regs + reg); } -static inline void mchp_corespi_disable(struct mchp_corespi *spi) +static inline void mpfs_spi_disable(struct mpfs_spi *spi) { - u32 control = mchp_corespi_read(spi, REG_CONTROL); + u32 control = mpfs_spi_read(spi, REG_CONTROL); control &= ~CONTROL_ENABLE; - mchp_corespi_write(spi, REG_CONTROL, control); + mpfs_spi_write(spi, REG_CONTROL, control); } -static inline void mchp_corespi_read_fifo(struct mchp_corespi *spi, int fifo_max) +static inline void mpfs_spi_read_fifo(struct mpfs_spi *spi, int fifo_max) { for (int i = 0; i < fifo_max; i++) { u32 data; - while (mchp_corespi_read(spi, REG_STATUS) & STATUS_RXFIFO_EMPTY) + while (mpfs_spi_read(spi, REG_STATUS) & STATUS_RXFIFO_EMPTY) ; - data = mchp_corespi_read(spi, REG_RX_DATA); + data = mpfs_spi_read(spi, REG_RX_DATA); spi->rx_len -= spi->n_bytes; @@ -158,34 +158,34 @@ static inline void mchp_corespi_read_fifo(struct mchp_corespi *spi, int fifo_max } } -static void mchp_corespi_enable_ints(struct mchp_corespi *spi) +static void mpfs_spi_enable_ints(struct mpfs_spi *spi) { - u32 control = mchp_corespi_read(spi, REG_CONTROL); + u32 control = mpfs_spi_read(spi, REG_CONTROL); control |= INT_ENABLE_MASK; - mchp_corespi_write(spi, REG_CONTROL, control); + mpfs_spi_write(spi, REG_CONTROL, control); } -static void mchp_corespi_disable_ints(struct mchp_corespi *spi) +static void mpfs_spi_disable_ints(struct mpfs_spi *spi) { - u32 control = mchp_corespi_read(spi, REG_CONTROL); + u32 control = mpfs_spi_read(spi, REG_CONTROL); control &= ~INT_ENABLE_MASK; - mchp_corespi_write(spi, REG_CONTROL, control); + mpfs_spi_write(spi, REG_CONTROL, control); } -static inline void mchp_corespi_set_xfer_size(struct mchp_corespi *spi, int len) +static inline void mpfs_spi_set_xfer_size(struct mpfs_spi *spi, int len) { u32 control; u32 lenpart; - u32 frames = mchp_corespi_read(spi, REG_FRAMESUP); + u32 frames = mpfs_spi_read(spi, REG_FRAMESUP); /* * Writing to FRAMECNT in REG_CONTROL will reset the frame count, taking * a shortcut requires an explicit clear. */ if (frames == len) { - mchp_corespi_write(spi, REG_COMMAND, COMMAND_CLRFRAMECNT); + mpfs_spi_write(spi, REG_COMMAND, COMMAND_CLRFRAMECNT); return; } @@ -208,20 +208,20 @@ static inline void mchp_corespi_set_xfer_size(struct mchp_corespi *spi, int len) * that matches the documentation. */ lenpart = len & 0xffff; - control = mchp_corespi_read(spi, REG_CONTROL); + control = mpfs_spi_read(spi, REG_CONTROL); control &= ~CONTROL_FRAMECNT_MASK; control |= lenpart << CONTROL_FRAMECNT_SHIFT; - mchp_corespi_write(spi, REG_CONTROL, control); - mchp_corespi_write(spi, REG_FRAMESUP, len); + mpfs_spi_write(spi, REG_CONTROL, control); + mpfs_spi_write(spi, REG_FRAMESUP, len); } -static inline void mchp_corespi_write_fifo(struct mchp_corespi *spi, int fifo_max) +static inline void mpfs_spi_write_fifo(struct mpfs_spi *spi, int fifo_max) { int i = 0; - mchp_corespi_set_xfer_size(spi, fifo_max); + mpfs_spi_set_xfer_size(spi, fifo_max); - while ((i < fifo_max) && !(mchp_corespi_read(spi, REG_STATUS) & STATUS_TXFIFO_FULL)) { + while ((i < fifo_max) && !(mpfs_spi_read(spi, REG_STATUS) & STATUS_TXFIFO_FULL)) { u32 word; if (spi->n_bytes == 4) @@ -231,7 +231,7 @@ static inline void mchp_corespi_write_fifo(struct mchp_corespi *spi, int fifo_ma else word = spi->tx_buf ? *spi->tx_buf : 0xaa; - mchp_corespi_write(spi, REG_TX_DATA, word); + mpfs_spi_write(spi, REG_TX_DATA, word); if (spi->tx_buf) spi->tx_buf += spi->n_bytes; i++; @@ -240,9 +240,9 @@ static inline void mchp_corespi_write_fifo(struct mchp_corespi *spi, int fifo_ma spi->tx_len -= i * spi->n_bytes; } -static inline void mchp_corespi_set_framesize(struct mchp_corespi *spi, int bt) +static inline void mpfs_spi_set_framesize(struct mpfs_spi *spi, int bt) { - u32 frame_size = mchp_corespi_read(spi, REG_FRAME_SIZE); + u32 frame_size = mpfs_spi_read(spi, REG_FRAME_SIZE); u32 control; if ((frame_size & FRAME_SIZE_MASK) == bt) @@ -252,25 +252,25 @@ static inline void mchp_corespi_set_framesize(struct mchp_corespi *spi, int bt) * Disable the SPI controller. Writes to the frame size have * no effect when the controller is enabled. */ - control = mchp_corespi_read(spi, REG_CONTROL); + control = mpfs_spi_read(spi, REG_CONTROL); control &= ~CONTROL_ENABLE; - mchp_corespi_write(spi, REG_CONTROL, control); + mpfs_spi_write(spi, REG_CONTROL, control); - mchp_corespi_write(spi, REG_FRAME_SIZE, bt); + mpfs_spi_write(spi, REG_FRAME_SIZE, bt); control |= CONTROL_ENABLE; - mchp_corespi_write(spi, REG_CONTROL, control); + mpfs_spi_write(spi, REG_CONTROL, control); } -static void mchp_corespi_set_cs(struct spi_device *spi, bool disable) +static void mpfs_spi_set_cs(struct spi_device *spi, bool disable) { u32 reg; - struct mchp_corespi *corespi = spi_controller_get_devdata(spi->controller); + struct mpfs_spi *mspi = spi_controller_get_devdata(spi->controller); - reg = mchp_corespi_read(corespi, REG_SLAVE_SELECT); + reg = mpfs_spi_read(mspi, REG_SLAVE_SELECT); reg &= ~BIT(spi_get_chipselect(spi, 0)); reg |= !disable << spi_get_chipselect(spi, 0); - corespi->pending_slave_select = reg; + mspi->pending_slave_select = reg; /* * Only deassert chip select immediately. Writing to some registers @@ -281,12 +281,12 @@ static void mchp_corespi_set_cs(struct spi_device *spi, bool disable) * doesn't see any spurious clock transitions whilst CS is enabled. */ if (((spi->mode & SPI_CS_HIGH) == 0) == disable) - mchp_corespi_write(corespi, REG_SLAVE_SELECT, reg); + mpfs_spi_write(mspi, REG_SLAVE_SELECT, reg); } -static int mchp_corespi_setup(struct spi_device *spi) +static int mpfs_spi_setup(struct spi_device *spi) { - struct mchp_corespi *corespi = spi_controller_get_devdata(spi->controller); + struct mpfs_spi *mspi = spi_controller_get_devdata(spi->controller); u32 reg; if (spi_is_csgpiod(spi)) @@ -298,21 +298,21 @@ static int mchp_corespi_setup(struct spi_device *spi) * driving their select line low. */ if (spi->mode & SPI_CS_HIGH) { - reg = mchp_corespi_read(corespi, REG_SLAVE_SELECT); + reg = mpfs_spi_read(mspi, REG_SLAVE_SELECT); reg |= BIT(spi_get_chipselect(spi, 0)); - corespi->pending_slave_select = reg; - mchp_corespi_write(corespi, REG_SLAVE_SELECT, reg); + mspi->pending_slave_select = reg; + mpfs_spi_write(mspi, REG_SLAVE_SELECT, reg); } return 0; } -static void mchp_corespi_init(struct spi_controller *host, struct mchp_corespi *spi) +static void mpfs_spi_init(struct spi_controller *host, struct mpfs_spi *spi) { unsigned long clk_hz; - u32 control = mchp_corespi_read(spi, REG_CONTROL); + u32 control = mpfs_spi_read(spi, REG_CONTROL); control &= ~CONTROL_ENABLE; - mchp_corespi_write(spi, REG_CONTROL, control); + mpfs_spi_write(spi, REG_CONTROL, control); control |= CONTROL_MASTER; control &= ~CONTROL_MODE_MASK; @@ -328,15 +328,15 @@ static void mchp_corespi_init(struct spi_controller *host, struct mchp_corespi * */ control |= CONTROL_SPS | CONTROL_BIGFIFO; - mchp_corespi_write(spi, REG_CONTROL, control); + mpfs_spi_write(spi, REG_CONTROL, control); - mchp_corespi_set_framesize(spi, DEFAULT_FRAMESIZE); + mpfs_spi_set_framesize(spi, DEFAULT_FRAMESIZE); /* max. possible spi clock rate is the apb clock rate */ clk_hz = clk_get_rate(spi->clk); host->max_speed_hz = clk_hz; - mchp_corespi_enable_ints(spi); + mpfs_spi_enable_ints(spi); /* * It is required to enable direct mode, otherwise control over the chip @@ -344,34 +344,34 @@ static void mchp_corespi_init(struct spi_controller *host, struct mchp_corespi * * can deal with active high targets. */ spi->pending_slave_select = SSELOUT | SSEL_DIRECT; - mchp_corespi_write(spi, REG_SLAVE_SELECT, spi->pending_slave_select); + mpfs_spi_write(spi, REG_SLAVE_SELECT, spi->pending_slave_select); - control = mchp_corespi_read(spi, REG_CONTROL); + control = mpfs_spi_read(spi, REG_CONTROL); control &= ~CONTROL_RESET; control |= CONTROL_ENABLE; - mchp_corespi_write(spi, REG_CONTROL, control); + mpfs_spi_write(spi, REG_CONTROL, control); } -static inline void mchp_corespi_set_clk_gen(struct mchp_corespi *spi) +static inline void mpfs_spi_set_clk_gen(struct mpfs_spi *spi) { u32 control; - control = mchp_corespi_read(spi, REG_CONTROL); + control = mpfs_spi_read(spi, REG_CONTROL); if (spi->clk_mode) control |= CONTROL_CLKMODE; else control &= ~CONTROL_CLKMODE; - mchp_corespi_write(spi, REG_CLK_GEN, spi->clk_gen); - mchp_corespi_write(spi, REG_CONTROL, control); + mpfs_spi_write(spi, REG_CLK_GEN, spi->clk_gen); + mpfs_spi_write(spi, REG_CONTROL, control); } -static inline void mchp_corespi_set_mode(struct mchp_corespi *spi, unsigned int mode) +static inline void mpfs_spi_set_mode(struct mpfs_spi *spi, unsigned int mode) { u32 mode_val; - u32 control = mchp_corespi_read(spi, REG_CONTROL); + u32 control = mpfs_spi_read(spi, REG_CONTROL); switch (mode & SPI_MODE_X_MASK) { case SPI_MODE_0: @@ -394,22 +394,22 @@ static inline void mchp_corespi_set_mode(struct mchp_corespi *spi, unsigned int */ control &= ~CONTROL_ENABLE; - mchp_corespi_write(spi, REG_CONTROL, control); + mpfs_spi_write(spi, REG_CONTROL, control); control &= ~(SPI_MODE_X_MASK << MODE_X_MASK_SHIFT); control |= mode_val; - mchp_corespi_write(spi, REG_CONTROL, control); + mpfs_spi_write(spi, REG_CONTROL, control); control |= CONTROL_ENABLE; - mchp_corespi_write(spi, REG_CONTROL, control); + mpfs_spi_write(spi, REG_CONTROL, control); } -static irqreturn_t mchp_corespi_interrupt(int irq, void *dev_id) +static irqreturn_t mpfs_spi_interrupt(int irq, void *dev_id) { struct spi_controller *host = dev_id; - struct mchp_corespi *spi = spi_controller_get_devdata(host); - u32 intfield = mchp_corespi_read(spi, REG_MIS) & 0xf; + struct mpfs_spi *spi = spi_controller_get_devdata(host); + u32 intfield = mpfs_spi_read(spi, REG_MIS) & 0xf; bool finalise = false; /* Interrupt line may be shared and not for us at all */ @@ -417,7 +417,7 @@ static irqreturn_t mchp_corespi_interrupt(int irq, void *dev_id) return IRQ_NONE; if (intfield & INT_RX_CHANNEL_OVERFLOW) { - mchp_corespi_write(spi, REG_INT_CLEAR, INT_RX_CHANNEL_OVERFLOW); + mpfs_spi_write(spi, REG_INT_CLEAR, INT_RX_CHANNEL_OVERFLOW); finalise = true; dev_err(&host->dev, "%s: RX OVERFLOW: rxlen: %d, txlen: %d\n", __func__, @@ -425,7 +425,7 @@ static irqreturn_t mchp_corespi_interrupt(int irq, void *dev_id) } if (intfield & INT_TX_CHANNEL_UNDERRUN) { - mchp_corespi_write(spi, REG_INT_CLEAR, INT_TX_CHANNEL_UNDERRUN); + mpfs_spi_write(spi, REG_INT_CLEAR, INT_TX_CHANNEL_UNDERRUN); finalise = true; dev_err(&host->dev, "%s: TX UNDERFLOW: rxlen: %d, txlen: %d\n", __func__, @@ -438,8 +438,8 @@ static irqreturn_t mchp_corespi_interrupt(int irq, void *dev_id) return IRQ_HANDLED; } -static int mchp_corespi_calculate_clkgen(struct mchp_corespi *spi, - unsigned long target_hz) +static int mpfs_spi_calculate_clkgen(struct mpfs_spi *spi, + unsigned long target_hz) { unsigned long clk_hz, spi_hz, clk_gen; @@ -475,20 +475,20 @@ static int mchp_corespi_calculate_clkgen(struct mchp_corespi *spi, return 0; } -static int mchp_corespi_transfer_one(struct spi_controller *host, - struct spi_device *spi_dev, - struct spi_transfer *xfer) +static int mpfs_spi_transfer_one(struct spi_controller *host, + struct spi_device *spi_dev, + struct spi_transfer *xfer) { - struct mchp_corespi *spi = spi_controller_get_devdata(host); + struct mpfs_spi *spi = spi_controller_get_devdata(host); int ret; - ret = mchp_corespi_calculate_clkgen(spi, (unsigned long)xfer->speed_hz); + ret = mpfs_spi_calculate_clkgen(spi, (unsigned long)xfer->speed_hz); if (ret) { dev_err(&host->dev, "failed to set clk_gen for target %u Hz\n", xfer->speed_hz); return ret; } - mchp_corespi_set_clk_gen(spi); + mpfs_spi_set_clk_gen(spi); spi->tx_buf = xfer->tx_buf; spi->rx_buf = xfer->rx_buf; @@ -496,45 +496,46 @@ static int mchp_corespi_transfer_one(struct spi_controller *host, spi->rx_len = xfer->len; spi->n_bytes = roundup_pow_of_two(DIV_ROUND_UP(xfer->bits_per_word, BITS_PER_BYTE)); - mchp_corespi_set_framesize(spi, xfer->bits_per_word); + mpfs_spi_set_framesize(spi, xfer->bits_per_word); - mchp_corespi_write(spi, REG_COMMAND, COMMAND_RXFIFORST | COMMAND_TXFIFORST); + mpfs_spi_write(spi, REG_COMMAND, COMMAND_RXFIFORST | COMMAND_TXFIFORST); - mchp_corespi_write(spi, REG_SLAVE_SELECT, spi->pending_slave_select); + mpfs_spi_write(spi, REG_SLAVE_SELECT, spi->pending_slave_select); while (spi->tx_len) { int fifo_max = DIV_ROUND_UP(min(spi->tx_len, FIFO_DEPTH), spi->n_bytes); - mchp_corespi_write_fifo(spi, fifo_max); - mchp_corespi_read_fifo(spi, fifo_max); + mpfs_spi_write_fifo(spi, fifo_max); + mpfs_spi_read_fifo(spi, fifo_max); } spi_finalize_current_transfer(host); return 1; } -static int mchp_corespi_prepare_message(struct spi_controller *host, - struct spi_message *msg) +static int mpfs_spi_prepare_message(struct spi_controller *host, + struct spi_message *msg) { struct spi_device *spi_dev = msg->spi; - struct mchp_corespi *spi = spi_controller_get_devdata(host); + struct mpfs_spi *spi = spi_controller_get_devdata(host); - mchp_corespi_set_mode(spi, spi_dev->mode); + mpfs_spi_set_mode(spi, spi_dev->mode); return 0; } -static int mchp_corespi_probe(struct platform_device *pdev) +static int mpfs_spi_probe(struct platform_device *pdev) { struct spi_controller *host; - struct mchp_corespi *spi; + struct mpfs_spi *spi; struct resource *res; u32 num_cs; int ret = 0; host = devm_spi_alloc_host(&pdev->dev, sizeof(*spi)); if (!host) - return -ENOMEM; + return dev_err_probe(&pdev->dev, -ENOMEM, + "unable to allocate host for SPI controller\n"); platform_set_drvdata(pdev, host); @@ -544,11 +545,11 @@ static int mchp_corespi_probe(struct platform_device *pdev) host->num_chipselect = num_cs; host->mode_bits = SPI_CPOL | SPI_CPHA | SPI_CS_HIGH; host->use_gpio_descriptors = true; - host->setup = mchp_corespi_setup; + host->setup = mpfs_spi_setup; host->bits_per_word_mask = SPI_BPW_RANGE_MASK(1, 32); - host->transfer_one = mchp_corespi_transfer_one; - host->prepare_message = mchp_corespi_prepare_message; - host->set_cs = mchp_corespi_set_cs; + host->transfer_one = mpfs_spi_transfer_one; + host->prepare_message = mpfs_spi_prepare_message; + host->set_cs = mpfs_spi_set_cs; host->dev.of_node = pdev->dev.of_node; spi = spi_controller_get_devdata(host); @@ -561,7 +562,7 @@ static int mchp_corespi_probe(struct platform_device *pdev) if (spi->irq < 0) return spi->irq; - ret = devm_request_irq(&pdev->dev, spi->irq, mchp_corespi_interrupt, + ret = devm_request_irq(&pdev->dev, spi->irq, mpfs_spi_interrupt, IRQF_SHARED, dev_name(&pdev->dev), host); if (ret) return dev_err_probe(&pdev->dev, ret, @@ -572,11 +573,11 @@ static int mchp_corespi_probe(struct platform_device *pdev) return dev_err_probe(&pdev->dev, PTR_ERR(spi->clk), "could not get clk\n"); - mchp_corespi_init(host, spi); + mpfs_spi_init(host, spi); ret = devm_spi_register_controller(&pdev->dev, host); if (ret) { - mchp_corespi_disable(spi); + mpfs_spi_disable(spi); return dev_err_probe(&pdev->dev, ret, "unable to register host for SPI controller\n"); } @@ -586,13 +587,13 @@ static int mchp_corespi_probe(struct platform_device *pdev) return 0; } -static void mchp_corespi_remove(struct platform_device *pdev) +static void mpfs_spi_remove(struct platform_device *pdev) { struct spi_controller *host = platform_get_drvdata(pdev); - struct mchp_corespi *spi = spi_controller_get_devdata(host); + struct mpfs_spi *spi = spi_controller_get_devdata(host); - mchp_corespi_disable_ints(spi); - mchp_corespi_disable(spi); + mpfs_spi_disable_ints(spi); + mpfs_spi_disable(spi); } #define MICROCHIP_SPI_PM_OPS (NULL) @@ -602,23 +603,23 @@ static void mchp_corespi_remove(struct platform_device *pdev) */ #if defined(CONFIG_OF) -static const struct of_device_id mchp_corespi_dt_ids[] = { +static const struct of_device_id mpfs_spi_dt_ids[] = { { .compatible = "microchip,mpfs-spi" }, { /* sentinel */ } }; -MODULE_DEVICE_TABLE(of, mchp_corespi_dt_ids); +MODULE_DEVICE_TABLE(of, mpfs_spi_dt_ids); #endif -static struct platform_driver mchp_corespi_driver = { - .probe = mchp_corespi_probe, +static struct platform_driver mpfs_spi_driver = { + .probe = mpfs_spi_probe, .driver = { - .name = "microchip-corespi", + .name = "microchip-spi", .pm = MICROCHIP_SPI_PM_OPS, - .of_match_table = of_match_ptr(mchp_corespi_dt_ids), + .of_match_table = of_match_ptr(mpfs_spi_dt_ids), }, - .remove = mchp_corespi_remove, + .remove = mpfs_spi_remove, }; -module_platform_driver(mchp_corespi_driver); +module_platform_driver(mpfs_spi_driver); MODULE_DESCRIPTION("Microchip coreSPI SPI controller driver"); MODULE_AUTHOR("Daire McNamara "); MODULE_AUTHOR("Conor Dooley "); From 5e4481ef397ea22b9d4fedb2fdef7865ece9fabe Mon Sep 17 00:00:00 2001 From: Christophe JAILLET Date: Sat, 13 Dec 2025 08:48:51 +0100 Subject: [PATCH 1421/3902] spi: mpfs: Fix an error handling path in mpfs_spi_probe() [ Upstream commit a8a313612af7a55083ba5720f14f1835319debee ] mpfs_spi_init() calls mpfs_spi_enable_ints(), so mpfs_spi_disable_ints() should be called if an error occurs after calling mpfs_spi_init(), as already done in the remove function. Fixes: 9ac8d17694b6 ("spi: add support for microchip fpga spi controllers") Signed-off-by: Christophe JAILLET Link: https://patch.msgid.link/eb35f168517cc402ef7e78f26da02863e2f45c03.1765612110.git.christophe.jaillet@wanadoo.fr Signed-off-by: Mark Brown Signed-off-by: Sasha Levin --- drivers/spi/spi-mpfs.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/spi/spi-mpfs.c b/drivers/spi/spi-mpfs.c index 9a14d1732a1598..7e9e64d8e6c813 100644 --- a/drivers/spi/spi-mpfs.c +++ b/drivers/spi/spi-mpfs.c @@ -577,6 +577,7 @@ static int mpfs_spi_probe(struct platform_device *pdev) ret = devm_spi_register_controller(&pdev->dev, host); if (ret) { + mpfs_spi_disable_ints(spi); mpfs_spi_disable(spi); return dev_err_probe(&pdev->dev, ret, "unable to register host for SPI controller\n"); From e52b375b7dae7999107e54ea92c70dc3cc12533f Mon Sep 17 00:00:00 2001 From: Denis Sergeev Date: Tue, 9 Dec 2025 09:37:06 +0300 Subject: [PATCH 1422/3902] hwmon: (dell-smm) Limit fan multiplier to avoid overflow [ Upstream commit 46c28bbbb150b80827e4bcbea231560af9d16854 ] The fan nominal speed returned by SMM is limited to 16 bits, but the driver allows the fan multiplier to be set via a module parameter. Clamp the computed fan multiplier so that fan_nominal_speed * i8k_fan_mult always fits into a signed 32-bit integer and refuse to initialize the driver if the value is too large. Found by Linux Verification Center (linuxtesting.org) with SVACE. Fixes: 20bdeebc88269 ("hwmon: (dell-smm) Introduce helper function for data init") Signed-off-by: Denis Sergeev Link: https://lore.kernel.org/r/20251209063706.49008-1-denserg.edu@gmail.com Signed-off-by: Guenter Roeck Signed-off-by: Sasha Levin --- drivers/hwmon/dell-smm-hwmon.c | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/drivers/hwmon/dell-smm-hwmon.c b/drivers/hwmon/dell-smm-hwmon.c index cbe1a74a3deea3..f0e8a9bc0d0e0a 100644 --- a/drivers/hwmon/dell-smm-hwmon.c +++ b/drivers/hwmon/dell-smm-hwmon.c @@ -76,6 +76,9 @@ #define DELL_SMM_NO_TEMP 10 #define DELL_SMM_NO_FANS 4 +/* limit fan multiplier to avoid overflow */ +#define DELL_SMM_MAX_FAN_MULT (INT_MAX / U16_MAX) + struct smm_regs { unsigned int eax; unsigned int ebx; @@ -1253,6 +1256,12 @@ static int dell_smm_init_data(struct device *dev, const struct dell_smm_ops *ops data->ops = ops; /* All options must not be 0 */ data->i8k_fan_mult = fan_mult ? : I8K_FAN_MULT; + if (data->i8k_fan_mult > DELL_SMM_MAX_FAN_MULT) { + dev_err(dev, + "fan multiplier %u is too large (max %u)\n", + data->i8k_fan_mult, DELL_SMM_MAX_FAN_MULT); + return -EINVAL; + } data->i8k_fan_max = fan_max ? : I8K_FAN_HIGH; data->i8k_pwm_mult = DIV_ROUND_UP(255, data->i8k_fan_max); From 5aa2139201667c1f644601e4529c4acd6bf8db5a Mon Sep 17 00:00:00 2001 From: Junrui Luo Date: Wed, 10 Dec 2025 17:48:08 +0800 Subject: [PATCH 1423/3902] hwmon: (ibmpex) fix use-after-free in high/low store [ Upstream commit 6946c726c3f4c36f0f049e6f97e88c510b15f65d ] The ibmpex_high_low_store() function retrieves driver data using dev_get_drvdata() and uses it without validation. This creates a race condition where the sysfs callback can be invoked after the data structure is freed, leading to use-after-free. Fix by adding a NULL check after dev_get_drvdata(), and reordering operations in the deletion path to prevent TOCTOU. Reported-by: Yuhao Jiang Reported-by: Junrui Luo Fixes: 57c7c3a0fdea ("hwmon: IBM power meter driver") Signed-off-by: Junrui Luo Link: https://lore.kernel.org/r/MEYPR01MB7886BE2F51BFE41875B74B60AFA0A@MEYPR01MB7886.ausprd01.prod.outlook.com Signed-off-by: Guenter Roeck Signed-off-by: Sasha Levin --- drivers/hwmon/ibmpex.c | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/drivers/hwmon/ibmpex.c b/drivers/hwmon/ibmpex.c index 228c5f6c6f3836..129f3a9e8fe965 100644 --- a/drivers/hwmon/ibmpex.c +++ b/drivers/hwmon/ibmpex.c @@ -277,6 +277,9 @@ static ssize_t ibmpex_high_low_store(struct device *dev, { struct ibmpex_bmc_data *data = dev_get_drvdata(dev); + if (!data) + return -ENODEV; + ibmpex_reset_high_low_data(data); return count; @@ -508,6 +511,9 @@ static void ibmpex_bmc_delete(struct ibmpex_bmc_data *data) { int i, j; + hwmon_device_unregister(data->hwmon_dev); + dev_set_drvdata(data->bmc_device, NULL); + device_remove_file(data->bmc_device, &sensor_dev_attr_reset_high_low.dev_attr); device_remove_file(data->bmc_device, &dev_attr_name.attr); @@ -521,8 +527,7 @@ static void ibmpex_bmc_delete(struct ibmpex_bmc_data *data) } list_del(&data->list); - dev_set_drvdata(data->bmc_device, NULL); - hwmon_device_unregister(data->hwmon_dev); + ipmi_destroy_user(data->user); kfree(data->sensors); kfree(data); From ea62d22f577248f2145b006f73445b26143618f1 Mon Sep 17 00:00:00 2001 From: Alexey Simakov Date: Thu, 11 Dec 2025 19:43:43 +0300 Subject: [PATCH 1424/3902] hwmon: (tmp401) fix overflow caused by default conversion rate value [ Upstream commit 82f2aab35a1ab2e1460de06ef04c726460aed51c ] The driver computes conversion intervals using the formula: interval = (1 << (7 - rate)) * 125ms where 'rate' is the sensor's conversion rate register value. According to the datasheet, the power-on reset value of this register is 0x8, which could be assigned to the register, after handling i2c general call. Using this default value causes a result greater than the bit width of left operand and an undefined behaviour in the calculation above, since shifting by values larger than the bit width is undefined behaviour as per C language standard. Limit the maximum usable 'rate' value to 7 to prevent undefined behaviour in calculations. Found by Linux Verification Center (linuxtesting.org) with Svace. Note (groeck): This does not matter in practice unless someone overwrites the chip configuration from outside the driver while the driver is loaded. The conversion time register is initialized with a value of 5 (500ms) when the driver is loaded, and the driver never writes a bad value. Fixes: ca53e7640de7 ("hwmon: (tmp401) Convert to _info API") Signed-off-by: Alexey Simakov Link: https://lore.kernel.org/r/20251211164342.6291-1-bigalex934@gmail.com Signed-off-by: Guenter Roeck Signed-off-by: Sasha Levin --- drivers/hwmon/tmp401.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/hwmon/tmp401.c b/drivers/hwmon/tmp401.c index 02c5a3bb1071cb..84aaf817144cfa 100644 --- a/drivers/hwmon/tmp401.c +++ b/drivers/hwmon/tmp401.c @@ -401,7 +401,7 @@ static int tmp401_chip_read(struct device *dev, u32 attr, int channel, long *val ret = regmap_read(data->regmap, TMP401_CONVERSION_RATE, ®val); if (ret < 0) return ret; - *val = (1 << (7 - regval)) * 125; + *val = (1 << (7 - min(regval, 7))) * 125; break; case hwmon_chip_temp_reset_history: *val = 0; From f6ec2d5c21b14966c396cc2d55a5d883e84fd6b5 Mon Sep 17 00:00:00 2001 From: Shuicheng Lin Date: Fri, 14 Nov 2025 20:56:39 +0000 Subject: [PATCH 1425/3902] drm/xe: Fix freq kobject leak on sysfs_create_files failure MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [ Upstream commit b32045d73bb4333a2cebc5d3c005807adb03ab58 ] Ensure gt->freq is released when sysfs_create_files() fails in xe_gt_freq_init(). Without this, the kobject would leak. Add kobject_put() before returning the error. Fixes: fdc81c43f0c1 ("drm/xe: use devm_add_action_or_reset() helper") Signed-off-by: Shuicheng Lin Reviewed-by: Alex Zuo Reviewed-by: Xin Wang Link: https://patch.msgid.link/20251114205638.2184529-2-shuicheng.lin@intel.com Signed-off-by: Matt Roper (cherry picked from commit 251be5fb4982ebb0f5a81b62d975bd770f3ad5c2) Signed-off-by: Thomas Hellström Signed-off-by: Sasha Levin --- drivers/gpu/drm/xe/xe_gt_freq.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/drivers/gpu/drm/xe/xe_gt_freq.c b/drivers/gpu/drm/xe/xe_gt_freq.c index 4ff1b6b58d6be4..e8e70fd2e8c42d 100644 --- a/drivers/gpu/drm/xe/xe_gt_freq.c +++ b/drivers/gpu/drm/xe/xe_gt_freq.c @@ -296,8 +296,10 @@ int xe_gt_freq_init(struct xe_gt *gt) return -ENOMEM; err = sysfs_create_files(gt->freq, freq_attrs); - if (err) + if (err) { + kobject_put(gt->freq); return err; + } err = devm_add_action_or_reset(xe->drm.dev, freq_fini, gt->freq); if (err) From 3a3612dc705513498471187787314db57b2ab1df Mon Sep 17 00:00:00 2001 From: Vinay Belgaumkar Date: Fri, 28 Nov 2025 21:25:48 -0800 Subject: [PATCH 1426/3902] drm/xe: Apply Wa_14020316580 in xe_gt_idle_enable_pg() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [ Upstream commit c88a0731ed95f9705deb127a7f1927fa59aa742b ] Wa_14020316580 was getting clobbered by power gating init code later in the driver load sequence. Move the Wa so that it applies correctly. Fixes: 7cd05ef89c9d ("drm/xe/xe2hpm: Add initial set of workarounds") Suggested-by: Matt Roper Signed-off-by: Vinay Belgaumkar Reviewed-by: Riana Tauro Reviewed-by: Matt Roper Link: https://patch.msgid.link/20251129052548.70766-1-vinay.belgaumkar@intel.com Signed-off-by: Matt Roper (cherry picked from commit 8b5502145351bde87f522df082b9e41356898ba3) Signed-off-by: Thomas Hellström Signed-off-by: Sasha Levin --- drivers/gpu/drm/xe/xe_gt_idle.c | 8 ++++++++ drivers/gpu/drm/xe/xe_wa.c | 8 -------- drivers/gpu/drm/xe/xe_wa_oob.rules | 1 + 3 files changed, 9 insertions(+), 8 deletions(-) diff --git a/drivers/gpu/drm/xe/xe_gt_idle.c b/drivers/gpu/drm/xe/xe_gt_idle.c index bdc9d9877ec490..3e3d1d52f63026 100644 --- a/drivers/gpu/drm/xe/xe_gt_idle.c +++ b/drivers/gpu/drm/xe/xe_gt_idle.c @@ -5,6 +5,7 @@ #include +#include #include "xe_force_wake.h" #include "xe_device.h" #include "xe_gt.h" @@ -16,6 +17,7 @@ #include "xe_mmio.h" #include "xe_pm.h" #include "xe_sriov.h" +#include "xe_wa.h" /** * DOC: Xe GT Idle @@ -145,6 +147,12 @@ void xe_gt_idle_enable_pg(struct xe_gt *gt) xe_mmio_write32(mmio, RENDER_POWERGATE_IDLE_HYSTERESIS, 25); } + if (XE_GT_WA(gt, 14020316580)) + gtidle->powergate_enable &= ~(VDN_HCP_POWERGATE_ENABLE(0) | + VDN_MFXVDENC_POWERGATE_ENABLE(0) | + VDN_HCP_POWERGATE_ENABLE(2) | + VDN_MFXVDENC_POWERGATE_ENABLE(2)); + xe_mmio_write32(mmio, POWERGATE_ENABLE, gtidle->powergate_enable); xe_force_wake_put(gt_to_fw(gt), fw_ref); } diff --git a/drivers/gpu/drm/xe/xe_wa.c b/drivers/gpu/drm/xe/xe_wa.c index 3cf30718b20039..d209434fd7fc21 100644 --- a/drivers/gpu/drm/xe/xe_wa.c +++ b/drivers/gpu/drm/xe/xe_wa.c @@ -270,14 +270,6 @@ static const struct xe_rtp_entry_sr gt_was[] = { XE_RTP_ACTIONS(SET(VDBOX_CGCTL3F1C(0), MFXPIPE_CLKGATE_DIS)), XE_RTP_ENTRY_FLAG(FOREACH_ENGINE), }, - { XE_RTP_NAME("14020316580"), - XE_RTP_RULES(MEDIA_VERSION(1301)), - XE_RTP_ACTIONS(CLR(POWERGATE_ENABLE, - VDN_HCP_POWERGATE_ENABLE(0) | - VDN_MFXVDENC_POWERGATE_ENABLE(0) | - VDN_HCP_POWERGATE_ENABLE(2) | - VDN_MFXVDENC_POWERGATE_ENABLE(2))), - }, { XE_RTP_NAME("14019449301"), XE_RTP_RULES(MEDIA_VERSION(1301), ENGINE_CLASS(VIDEO_DECODE)), XE_RTP_ACTIONS(SET(VDBOX_CGCTL3F08(0), CG3DDISHRS_CLKGATE_DIS)), diff --git a/drivers/gpu/drm/xe/xe_wa_oob.rules b/drivers/gpu/drm/xe/xe_wa_oob.rules index f3a6d5d239cecb..1d32fa1c3b2d75 100644 --- a/drivers/gpu/drm/xe/xe_wa_oob.rules +++ b/drivers/gpu/drm/xe/xe_wa_oob.rules @@ -81,3 +81,4 @@ 15015404425_disable PLATFORM(PANTHERLAKE), MEDIA_STEP(B0, FOREVER) 16026007364 MEDIA_VERSION(3000) +14020316580 MEDIA_VERSION(1301) From 754b10c067f0faf0302ef30b176bcb4b479dd361 Mon Sep 17 00:00:00 2001 From: Arnd Bergmann Date: Thu, 4 Dec 2025 10:46:58 +0100 Subject: [PATCH 1427/3902] drm/xe: fix drm_gpusvm_init() arguments MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [ Upstream commit 9acc3295813b9b846791fd3eab0a78a3144af560 ] The Xe driver fails to build when CONFIG_DRM_XE_GPUSVM is disabled but CONFIG_DRM_GPUSVM is turned on, due to the clash of two commits: In file included from drivers/gpu/drm/xe/xe_vm_madvise.c:8: drivers/gpu/drm/xe/xe_svm.h: In function 'xe_svm_init': include/linux/stddef.h:8:14: error: passing argument 5 of 'drm_gpusvm_init' makes integer from pointer without a cast [-Wint-conversion] drivers/gpu/drm/xe/xe_svm.h:217:38: note: in expansion of macro 'NULL' 217 | NULL, NULL, 0, 0, 0, NULL, NULL, 0); | ^~~~ In file included from drivers/gpu/drm/xe/xe_bo_types.h:11, from drivers/gpu/drm/xe/xe_bo.h:11, from drivers/gpu/drm/xe/xe_vm_madvise.c:11: include/drm/drm_gpusvm.h:254:35: note: expected 'long unsigned int' but argument is of type 'void *' 254 | unsigned long mm_start, unsigned long mm_range, | ~~~~~~~~~~~~~~^~~~~~~~ In file included from drivers/gpu/drm/xe/xe_vm_madvise.c:14: drivers/gpu/drm/xe/xe_svm.h:216:16: error: too many arguments to function 'drm_gpusvm_init'; expected 10, have 11 216 | return drm_gpusvm_init(&vm->svm.gpusvm, "Xe SVM (simple)", &vm->xe->drm, | ^~~~~~~~~~~~~~~ 217 | NULL, NULL, 0, 0, 0, NULL, NULL, 0); | ~ include/drm/drm_gpusvm.h:251:5: note: declared here Adapt the caller to the new argument list by removing the extraneous NULL argument. Fixes: 9e9787414882 ("drm/xe/userptr: replace xe_hmm with gpusvm") Fixes: 10aa5c806030 ("drm/gpusvm, drm/xe: Fix userptr to not allow device private pages") Signed-off-by: Arnd Bergmann Reviewed-by: Thomas Hellström Signed-off-by: Thomas Hellström Link: https://patch.msgid.link/20251204094704.1030933-1-arnd@kernel.org (cherry picked from commit 29bce9c8b41d5c378263a927acb9a9074d0e7a0e) Signed-off-by: Thomas Hellström Signed-off-by: Sasha Levin --- drivers/gpu/drm/xe/xe_svm.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/gpu/drm/xe/xe_svm.h b/drivers/gpu/drm/xe/xe_svm.h index 0955d2ac8d7448..fa757dd07954d9 100644 --- a/drivers/gpu/drm/xe/xe_svm.h +++ b/drivers/gpu/drm/xe/xe_svm.h @@ -214,7 +214,7 @@ int xe_svm_init(struct xe_vm *vm) { #if IS_ENABLED(CONFIG_DRM_GPUSVM) return drm_gpusvm_init(&vm->svm.gpusvm, "Xe SVM (simple)", &vm->xe->drm, - NULL, NULL, 0, 0, 0, NULL, NULL, 0); + NULL, 0, 0, 0, NULL, NULL, 0); #else return 0; #endif From f72c9ecd4b258d0fdd8205eba294afdf9f8ec668 Mon Sep 17 00:00:00 2001 From: Junxiao Chang Date: Fri, 7 Nov 2025 11:31:52 +0800 Subject: [PATCH 1428/3902] drm/me/gsc: mei interrupt top half should be in irq disabled context MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [ Upstream commit 17445af7dcc7d645b6fb8951fd10c8b72cc7f23f ] MEI GSC interrupt comes from i915 or xe driver. It has top half and bottom half. Top half is called from i915/xe interrupt handler. It should be in irq disabled context. With RT kernel(PREEMPT_RT enabled), by default IRQ handler is in threaded IRQ. MEI GSC top half might be in threaded IRQ context. generic_handle_irq_safe API could be called from either IRQ or process context, it disables local IRQ then calls MEI GSC interrupt top half. This change fixes B580 GPU boot issue with RT enabled. Fixes: e02cea83d32d ("drm/xe/gsc: add Battlemage support") Tested-by: Baoli Zhang Signed-off-by: Junxiao Chang Reviewed-by: Sebastian Andrzej Siewior Reviewed-by: Matthew Brost Link: https://patch.msgid.link/20251107033152.834960-1-junxiao.chang@intel.com Signed-off-by: Maarten Lankhorst (cherry picked from commit 3efadf028783a49ab2941294187c8b6dd86bf7da) Signed-off-by: Thomas Hellström Signed-off-by: Sasha Levin --- drivers/gpu/drm/xe/xe_heci_gsc.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/gpu/drm/xe/xe_heci_gsc.c b/drivers/gpu/drm/xe/xe_heci_gsc.c index a415ca48879140..32d509b1139153 100644 --- a/drivers/gpu/drm/xe/xe_heci_gsc.c +++ b/drivers/gpu/drm/xe/xe_heci_gsc.c @@ -221,7 +221,7 @@ void xe_heci_gsc_irq_handler(struct xe_device *xe, u32 iir) if (xe->heci_gsc.irq < 0) return; - ret = generic_handle_irq(xe->heci_gsc.irq); + ret = generic_handle_irq_safe(xe->heci_gsc.irq); if (ret) drm_err_ratelimited(&xe->drm, "error handling GSC irq: %d\n", ret); } @@ -241,7 +241,7 @@ void xe_heci_csc_irq_handler(struct xe_device *xe, u32 iir) if (xe->heci_gsc.irq < 0) return; - ret = generic_handle_irq(xe->heci_gsc.irq); + ret = generic_handle_irq_safe(xe->heci_gsc.irq); if (ret) drm_err_ratelimited(&xe->drm, "error handling GSC irq: %d\n", ret); } From 4c8a881044e1a5b501a5cf47af768992e065a4c2 Mon Sep 17 00:00:00 2001 From: Jagmeet Randhawa Date: Fri, 12 Dec 2025 05:21:46 +0800 Subject: [PATCH 1429/3902] drm/xe: Increase TDF timeout MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [ Upstream commit eafb6f62093f756535a7be1fc4559374a511e460 ] There are some corner cases where flushing transient data may take slightly longer than the 150us timeout we currently allow. Update the driver to use a 300us timeout instead based on the latest guidance from the hardware team. An update to the bspec to formally document this is expected to arrive soon. Fixes: c01c6066e6fa ("drm/xe/device: implement transient flush") Signed-off-by: Jagmeet Randhawa Reviewed-by: Jonathan Cavitt Reviewed-by: Matt Roper Link: https://patch.msgid.link/0201b1d6ec64d3651fcbff1ea21026efa915126a.1765487866.git.jagmeet.randhawa@intel.com Signed-off-by: Matt Roper (cherry picked from commit d69d3636f5f7a84bae7cd43473b3701ad9b7d544) Signed-off-by: Thomas Hellström Signed-off-by: Sasha Levin --- drivers/gpu/drm/xe/xe_device.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/gpu/drm/xe/xe_device.c b/drivers/gpu/drm/xe/xe_device.c index 456899238377ed..5f757790d6f53f 100644 --- a/drivers/gpu/drm/xe/xe_device.c +++ b/drivers/gpu/drm/xe/xe_device.c @@ -1046,7 +1046,7 @@ static void tdf_request_sync(struct xe_device *xe) * transient and need to be flushed.. */ if (xe_mmio_wait32(>->mmio, XE2_TDF_CTRL, TRANSIENT_FLUSH_REQUEST, 0, - 150, NULL, false)) + 300, NULL, false)) xe_gt_err_once(gt, "TD flush timeout\n"); xe_force_wake_put(gt_to_fw(gt), fw_ref); From 63cc97bd85e38257e58e9601d727a7d4632b7a3b Mon Sep 17 00:00:00 2001 From: Jan Maslak Date: Wed, 10 Dec 2025 15:56:18 +0100 Subject: [PATCH 1430/3902] drm/xe: Restore engine registers before restarting schedulers after GT reset MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [ Upstream commit eed5b815fa49c17d513202f54e980eb91955d3ed ] During GT reset recovery in do_gt_restart(), xe_uc_start() was called before xe_reg_sr_apply_mmio() restored engine-specific registers. This created a race window where the scheduler could run jobs before hardware state was fully restored. This caused failures in eudebug tests (xe_exec_sip_eudebug@breakpoint- waitsip-*) where TD_CTL register (containing TD_CTL_GLOBAL_DEBUG_ENABLE) wasn't restored before jobs started executing. Breakpoints would fail to trigger SIP entry because the debug enable bit wasn't set yet. Fix by moving xe_uc_start() after all MMIO register restoration, including engine registers and CCS mode configuration, ensuring all hardware state is fully restored before any jobs can be scheduled. Fixes: dd08ebf6c352 ("drm/xe: Introduce a new DRM driver for Intel GPUs") Signed-off-by: Jan Maslak Reviewed-by: Jonathan Cavitt Reviewed-by: Matthew Brost Signed-off-by: Matthew Brost Link: https://patch.msgid.link/20251210145618.169625-2-jan.maslak@intel.com (cherry picked from commit 825aed0328588b2837636c1c5a0c48795d724617) Signed-off-by: Thomas Hellström Signed-off-by: Sasha Levin --- drivers/gpu/drm/xe/xe_gt.c | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/drivers/gpu/drm/xe/xe_gt.c b/drivers/gpu/drm/xe/xe_gt.c index 6d3db5e55d98ae..61bed3b04dedd2 100644 --- a/drivers/gpu/drm/xe/xe_gt.c +++ b/drivers/gpu/drm/xe/xe_gt.c @@ -784,9 +784,6 @@ static int do_gt_restart(struct xe_gt *gt) xe_gt_sriov_pf_init_hw(gt); xe_mocs_init(gt); - err = xe_uc_start(>->uc); - if (err) - return err; for_each_hw_engine(hwe, gt, id) xe_reg_sr_apply_mmio(&hwe->reg_sr, gt); @@ -794,6 +791,10 @@ static int do_gt_restart(struct xe_gt *gt) /* Get CCS mode in sync between sw/hw */ xe_gt_apply_ccs_mode(gt); + err = xe_uc_start(>->uc); + if (err) + return err; + /* Restore GT freq to expected values */ xe_gt_sanitize_freq(gt); From 548e158dbf5786635357429556d01e7938712f71 Mon Sep 17 00:00:00 2001 From: Haoxiang Li Date: Thu, 4 Dec 2025 18:36:18 +0800 Subject: [PATCH 1431/3902] MIPS: Fix a reference leak bug in ip22_check_gio() [ Upstream commit 680ad315caaa2860df411cb378bf3614d96c7648 ] If gio_device_register fails, gio_dev_put() is required to drop the gio_dev device reference. Fixes: e84de0c61905 ("MIPS: GIO bus support for SGI IP22/28") Signed-off-by: Haoxiang Li Signed-off-by: Thomas Bogendoerfer Signed-off-by: Sasha Levin --- arch/mips/sgi-ip22/ip22-gio.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/arch/mips/sgi-ip22/ip22-gio.c b/arch/mips/sgi-ip22/ip22-gio.c index 5893ea4e382cae..19b70928d6dc38 100644 --- a/arch/mips/sgi-ip22/ip22-gio.c +++ b/arch/mips/sgi-ip22/ip22-gio.c @@ -372,7 +372,8 @@ static void ip22_check_gio(int slotno, unsigned long addr, int irq) gio_dev->resource.flags = IORESOURCE_MEM; gio_dev->irq = irq; dev_set_name(&gio_dev->dev, "%d", slotno); - gio_device_register(gio_dev); + if (gio_device_register(gio_dev)) + gio_dev_put(gio_dev); } else printk(KERN_INFO "GIO: slot %d : Empty\n", slotno); } From 09fe52c728e098e47eecd683e770c5e5deb71388 Mon Sep 17 00:00:00 2001 From: Marijn Suijten Date: Sun, 30 Nov 2025 23:40:05 +0100 Subject: [PATCH 1432/3902] drm/panel: sony-td4353-jdi: Enable prepare_prev_first [ Upstream commit 2b973ca48ff3ef1952091c8f988d7796781836c8 ] The DSI host must be enabled before our prepare function can run, which has to send its init sequence over DSI. Without enabling the host first the panel will not probe. Fixes: 9e15123eca79 ("drm/msm/dsi: Stop unconditionally powering up DSI hosts at modeset") Signed-off-by: Marijn Suijten Reviewed-by: Douglas Anderson Reviewed-by: AngeloGioacchino Del Regno Reviewed-by: Martin Botka Signed-off-by: Douglas Anderson Link: https://patch.msgid.link/20251130-sony-akari-fix-panel-v1-1-1d27c60a55f5@somainline.org Signed-off-by: Sasha Levin --- drivers/gpu/drm/panel/panel-sony-td4353-jdi.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/drivers/gpu/drm/panel/panel-sony-td4353-jdi.c b/drivers/gpu/drm/panel/panel-sony-td4353-jdi.c index 7c989b70ab5130..a14c86c60d19d7 100644 --- a/drivers/gpu/drm/panel/panel-sony-td4353-jdi.c +++ b/drivers/gpu/drm/panel/panel-sony-td4353-jdi.c @@ -212,6 +212,8 @@ static int sony_td4353_jdi_probe(struct mipi_dsi_device *dsi) if (ret) return dev_err_probe(dev, ret, "Failed to get backlight\n"); + ctx->panel.prepare_prev_first = true; + drm_panel_add(&ctx->panel); ret = mipi_dsi_attach(dsi); From 0901f5fe2a01ed93be105bea58bcb5d4ab5ecee6 Mon Sep 17 00:00:00 2001 From: Juergen Gross Date: Mon, 15 Dec 2025 12:51:12 +0100 Subject: [PATCH 1433/3902] x86/xen: Fix sparse warning in enlighten_pv.c [ Upstream commit e5aff444e3a7bdeef5ea796a2099fc3c60a070fa ] The sparse tool issues a warning for arch/x76/xen/enlighten_pv.c: arch/x86/xen/enlighten_pv.c:120:9: sparse: sparse: incorrect type in initializer (different address spaces) expected void const [noderef] __percpu *__vpp_verify got bool * This is due to the percpu variable xen_in_preemptible_hcall being exported via EXPORT_SYMBOL_GPL() instead of EXPORT_PER_CPU_SYMBOL_GPL(). Reported-by: kernel test robot Closes: https://lore.kernel.org/oe-kbuild-all/202512140856.Ic6FetG6-lkp@intel.com/ Fixes: fdfd811ddde3 ("x86/xen: allow privcmd hypercalls to be preempted") Reviewed-by: Boris Ostrovsky Signed-off-by: Juergen Gross Message-ID: <20251215115112.15072-1-jgross@suse.com> Signed-off-by: Sasha Levin --- arch/x86/xen/enlighten_pv.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/arch/x86/xen/enlighten_pv.c b/arch/x86/xen/enlighten_pv.c index 4806cc28d7ca77..b74ff8bc7f2a8d 100644 --- a/arch/x86/xen/enlighten_pv.c +++ b/arch/x86/xen/enlighten_pv.c @@ -108,7 +108,7 @@ static int xen_cpu_dead_pv(unsigned int cpu); * calls. */ DEFINE_PER_CPU(bool, xen_in_preemptible_hcall); -EXPORT_SYMBOL_GPL(xen_in_preemptible_hcall); +EXPORT_PER_CPU_SYMBOL_GPL(xen_in_preemptible_hcall); /* * In case of scheduling the flag must be cleared and restored after From f51cc7aeb3d0ac3ea4fd08c83e0a9d807507c1b1 Mon Sep 17 00:00:00 2001 From: Jianpeng Chang Date: Fri, 5 Dec 2025 09:59:34 +0800 Subject: [PATCH 1434/3902] arm64: kdump: Fix elfcorehdr overlap caused by reserved memory processing reorder [ Upstream commit 3e8ade58b71b48913d21b647b2089e03e81f117e ] Commit 8a6e02d0c00e ("of: reserved_mem: Restructure how the reserved memory regions are processed") changed the processing order of reserved memory regions, causing elfcorehdr to overlap with dynamically allocated reserved memory regions during kdump kernel boot. The issue occurs because: 1. kexec-tools allocates elfcorehdr in the last crashkernel reserved memory region and passes it to the second kernel 2. The problematic commit moved dynamic reserved memory allocation (like bman-fbpr) to occur during fdt_scan_reserved_mem(), before elfcorehdr reservation in fdt_reserve_elfcorehdr() 3. bman-fbpr with 16MB alignment requirement can get allocated at addresses that overlap with the elfcorehdr location 4. When fdt_reserve_elfcorehdr() tries to reserve elfcorehdr memory, overlap detection identifies the conflict and skips reservation 5. kdump kernel fails with "Unable to handle kernel paging request" because elfcorehdr memory is not properly reserved The boot log: Before 8a6e02d0c00e: OF: fdt: Reserving 1 KiB of memory at 0xf4fff000 for elfcorehdr OF: reserved mem: 0xf3000000..0xf3ffffff bman-fbpr After 8a6e02d0c00e: OF: reserved mem: 0xf4000000..0xf4ffffff bman-fbpr OF: fdt: elfcorehdr is overlapped Fix this by ensuring elfcorehdr reservation occurs before dynamic reserved memory allocation. Fixes: 8a6e02d0c00e ("of: reserved_mem: Restructure how the reserved memory regions are processed") Signed-off-by: Jianpeng Chang Link: https://patch.msgid.link/20251205015934.700016-1-jianpeng.chang.cn@windriver.com Signed-off-by: Rob Herring (Arm) Signed-off-by: Sasha Levin --- drivers/of/fdt.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/of/fdt.c b/drivers/of/fdt.c index fdaee4906836f9..3851ce24458586 100644 --- a/drivers/of/fdt.c +++ b/drivers/of/fdt.c @@ -503,8 +503,8 @@ void __init early_init_fdt_scan_reserved_mem(void) if (!initial_boot_params) return; - fdt_scan_reserved_mem(); fdt_reserve_elfcorehdr(); + fdt_scan_reserved_mem(); /* Process header /memreserve/ fields */ for (n = 0; ; n++) { From dac58c012c47cadf337a35eb05d44498c43e5cd0 Mon Sep 17 00:00:00 2001 From: Alex Deucher Date: Wed, 10 Dec 2025 11:02:30 -0500 Subject: [PATCH 1435/3902] drm/amdgpu: fix a job->pasid access race in gpu recovery MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [ Upstream commit 77f73253015cbc7893fca1821ac3eae9eb4bc943 ] Avoid a possible UAF in GPU recovery due to a race between the sched timeout callback and the tdr work queue. The gpu recovery function calls drm_sched_stop() and later drm_sched_start(). drm_sched_start() restarts the tdr queue which will eventually free the job. If the tdr queue frees the job before time out callback completes, the job will be freed and we'll get a UAF when accessing the pasid. Cache it early to avoid the UAF. Example KASAN trace: [ 493.058141] BUG: KASAN: slab-use-after-free in amdgpu_device_gpu_recover+0x968/0x990 [amdgpu] [ 493.067530] Read of size 4 at addr ffff88b0ce3f794c by task kworker/u128:1/323 [ 493.074892] [ 493.076485] CPU: 9 UID: 0 PID: 323 Comm: kworker/u128:1 Tainted: G E 6.16.0-1289896.2.zuul.bf4f11df81c1410bbe901c4373305a31 #1 PREEMPT(voluntary) [ 493.076493] Tainted: [E]=UNSIGNED_MODULE [ 493.076495] Hardware name: TYAN B8021G88V2HR-2T/S8021GM2NR-2T, BIOS V1.03.B10 04/01/2019 [ 493.076500] Workqueue: amdgpu-reset-dev drm_sched_job_timedout [gpu_sched] [ 493.076512] Call Trace: [ 493.076515] [ 493.076518] dump_stack_lvl+0x64/0x80 [ 493.076529] print_report+0xce/0x630 [ 493.076536] ? _raw_spin_lock_irqsave+0x86/0xd0 [ 493.076541] ? __pfx__raw_spin_lock_irqsave+0x10/0x10 [ 493.076545] ? amdgpu_device_gpu_recover+0x968/0x990 [amdgpu] [ 493.077253] kasan_report+0xb8/0xf0 [ 493.077258] ? amdgpu_device_gpu_recover+0x968/0x990 [amdgpu] [ 493.077965] amdgpu_device_gpu_recover+0x968/0x990 [amdgpu] [ 493.078672] ? __pfx_amdgpu_device_gpu_recover+0x10/0x10 [amdgpu] [ 493.079378] ? amdgpu_coredump+0x1fd/0x4c0 [amdgpu] [ 493.080111] amdgpu_job_timedout+0x642/0x1400 [amdgpu] [ 493.080903] ? pick_task_fair+0x24e/0x330 [ 493.080910] ? __pfx_amdgpu_job_timedout+0x10/0x10 [amdgpu] [ 493.081702] ? _raw_spin_lock+0x75/0xc0 [ 493.081708] ? __pfx__raw_spin_lock+0x10/0x10 [ 493.081712] drm_sched_job_timedout+0x1b0/0x4b0 [gpu_sched] [ 493.081721] ? __pfx__raw_spin_lock_irq+0x10/0x10 [ 493.081725] process_one_work+0x679/0xff0 [ 493.081732] worker_thread+0x6ce/0xfd0 [ 493.081736] ? __pfx_worker_thread+0x10/0x10 [ 493.081739] kthread+0x376/0x730 [ 493.081744] ? __pfx_kthread+0x10/0x10 [ 493.081748] ? __pfx__raw_spin_lock_irq+0x10/0x10 [ 493.081751] ? __pfx_kthread+0x10/0x10 [ 493.081755] ret_from_fork+0x247/0x330 [ 493.081761] ? __pfx_kthread+0x10/0x10 [ 493.081764] ret_from_fork_asm+0x1a/0x30 [ 493.081771] Fixes: a72002cb181f ("drm/amdgpu: Make use of drm_wedge_task_info") Link: https://github.com/HansKristian-Work/vkd3d-proton/pull/2670 Cc: SRINIVASAN.SHANMUGAM@amd.com Cc: vitaly.prosyak@amd.com Cc: christian.koenig@amd.com Suggested-by: Matthew Brost Reviewed-by: Srinivasan Shanmugam Reviewed-by: Lijo Lazar Reviewed-by: Christian König Signed-off-by: Alex Deucher (cherry picked from commit 20880a3fd5dd7bca1a079534cf6596bda92e107d) Signed-off-by: Sasha Levin --- drivers/gpu/drm/amd/amdgpu/amdgpu_device.c | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_device.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_device.c index 96b6738e625265..843770e61e4211 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_device.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_device.c @@ -6476,6 +6476,8 @@ int amdgpu_device_gpu_recover(struct amdgpu_device *adev, struct amdgpu_hive_info *hive = NULL; int r = 0; bool need_emergency_restart = false; + /* save the pasid here as the job may be freed before the end of the reset */ + int pasid = job ? job->pasid : -EINVAL; /* * If it reaches here because of hang/timeout and a RAS error is @@ -6572,8 +6574,12 @@ int amdgpu_device_gpu_recover(struct amdgpu_device *adev, if (!r) { struct amdgpu_task_info *ti = NULL; - if (job) - ti = amdgpu_vm_get_task_info_pasid(adev, job->pasid); + /* + * The job may already be freed at this point via the sched tdr workqueue so + * use the cached pasid. + */ + if (pasid >= 0) + ti = amdgpu_vm_get_task_info_pasid(adev, pasid); drm_dev_wedged_event(adev_to_drm(adev), DRM_WEDGE_RECOVERY_NONE, ti ? &ti->task : NULL); From 0ab22a1796e04de6a30bde28727ab885d313a16c Mon Sep 17 00:00:00 2001 From: Anurag Dutta Date: Fri, 12 Dec 2025 12:53:12 +0530 Subject: [PATCH 1436/3902] spi: cadence-quadspi: Fix clock disable on probe failure path [ Upstream commit 1889dd2081975ce1f6275b06cdebaa8d154847a9 ] When cqspi_request_mmap_dma() returns -EPROBE_DEFER after runtime PM is enabled, the error path calls clk_disable_unprepare() on an already disabled clock, causing an imbalance. Use pm_runtime_get_sync() to increment the usage counter and resume the device. This prevents runtime_suspend() from being invoked and causing a double clock disable. Fixes: 140623410536 ("mtd: spi-nor: Add driver for Cadence Quad SPI Flash Controller") Signed-off-by: Anurag Dutta Tested-by: Nishanth Menon Link: https://patch.msgid.link/20251212072312.2711806-3-a-dutta@ti.com Signed-off-by: Mark Brown Signed-off-by: Sasha Levin --- drivers/spi/spi-cadence-quadspi.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/drivers/spi/spi-cadence-quadspi.c b/drivers/spi/spi-cadence-quadspi.c index af6d050da1c8ac..3231bdaf9bd06e 100644 --- a/drivers/spi/spi-cadence-quadspi.c +++ b/drivers/spi/spi-cadence-quadspi.c @@ -2024,7 +2024,9 @@ static int cqspi_probe(struct platform_device *pdev) probe_reset_failed: if (cqspi->is_jh7110) cqspi_jh7110_disable_clk(pdev, cqspi); - clk_disable_unprepare(cqspi->clk); + + if (pm_runtime_get_sync(&pdev->dev) >= 0) + clk_disable_unprepare(cqspi->clk); probe_clk_failed: return ret; } From a4315e409ab0ad8eabca1fc338b9c157c09245d3 Mon Sep 17 00:00:00 2001 From: huang-jl Date: Wed, 17 Dec 2025 14:26:32 +0800 Subject: [PATCH 1437/3902] io_uring: fix nr_segs calculation in io_import_kbuf [ Upstream commit 114ea9bbaf7681c4d363e13b7916e6fef6a4963a ] io_import_kbuf() calculates nr_segs incorrectly when iov_offset is non-zero after iov_iter_advance(). It doesn't account for the partial consumption of the first bvec. The problem comes when meet the following conditions: 1. Use UBLK_F_AUTO_BUF_REG feature of ublk. 2. The kernel will help to register the buffer, into the io uring. 3. Later, the ublk server try to send IO request using the registered buffer in the io uring, to read/write to fuse-based filesystem, with O_DIRECT. >From a userspace perspective, the ublk server thread is blocked in the kernel, and will see "soft lockup" in the kernel dmesg. When ublk registers a buffer with mixed-size bvecs like [4K]*6 + [12K] and a request partially consumes a bvec, the next request's nr_segs calculation uses bvec->bv_len instead of (bv_len - iov_offset). This causes fuse_get_user_pages() to loop forever because nr_segs indicates fewer pages than actually needed. Specifically, the infinite loop happens at: fuse_get_user_pages() -> iov_iter_extract_pages() -> iov_iter_extract_bvec_pages() Since the nr_segs is miscalculated, the iov_iter_extract_bvec_pages returns when finding that i->nr_segs is zero. Then iov_iter_extract_pages returns zero. However, fuse_get_user_pages does still not get enough data/pages, causing infinite loop. Example: - Bvecs: [4K, 4K, 4K, 4K, 4K, 4K, 12K, ...] - Request 1: 32K at offset 0, uses 6*4K + 8K of the 12K bvec - Request 2: 32K at offset 32K - iov_offset = 8K (8K already consumed from 12K bvec) - Bug: calculates using 12K, not (12K - 8K) = 4K - Result: nr_segs too small, infinite loop in fuse_get_user_pages. Fix by accounting for iov_offset when calculating the first segment's available length. Fixes: b419bed4f0a6 ("io_uring/rsrc: ensure segments counts are correct on kbuf buffers") Signed-off-by: huang-jl Reviewed-by: Ming Lei Signed-off-by: Jens Axboe Signed-off-by: Sasha Levin --- io_uring/rsrc.c | 1 + 1 file changed, 1 insertion(+) diff --git a/io_uring/rsrc.c b/io_uring/rsrc.c index 0010c499249060..5b6b73c6a62bdc 100644 --- a/io_uring/rsrc.c +++ b/io_uring/rsrc.c @@ -1057,6 +1057,7 @@ static int io_import_kbuf(int ddir, struct iov_iter *iter, if (count < imu->len) { const struct bio_vec *bvec = iter->bvec; + len += iter->iov_offset; while (len > bvec->bv_len) { len -= bvec->bv_len; bvec++; From 0f6f3cf11bfecb0c5dbf849b12aabff85e525ca8 Mon Sep 17 00:00:00 2001 From: Ming Lei Date: Fri, 21 Nov 2025 09:58:24 +0800 Subject: [PATCH 1438/3902] ublk: add parameter `struct io_uring_cmd *` to ublk_prep_auto_buf_reg() [ Upstream commit 3035b9b46b0611898babc0b96ede65790d3566f7 ] Add parameter `struct io_uring_cmd *` to ublk_prep_auto_buf_reg() and prepare for reusing this helper for the coming UBLK_BATCH_IO feature, which can fetch & commit one batch of io commands via single uring_cmd. Reviewed-by: Caleb Sander Mateos Signed-off-by: Ming Lei Signed-off-by: Jens Axboe Stable-dep-of: c258f5c4502c ("ublk: fix deadlock when reading partition table") Signed-off-by: Sasha Levin --- drivers/block/ublk_drv.c | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/drivers/block/ublk_drv.c b/drivers/block/ublk_drv.c index 359564c40cb50f..599571634b7ac8 100644 --- a/drivers/block/ublk_drv.c +++ b/drivers/block/ublk_drv.c @@ -1242,11 +1242,12 @@ ublk_auto_buf_reg_fallback(const struct ublk_queue *ubq, struct ublk_io *io) } static bool ublk_auto_buf_reg(const struct ublk_queue *ubq, struct request *req, - struct ublk_io *io, unsigned int issue_flags) + struct ublk_io *io, struct io_uring_cmd *cmd, + unsigned int issue_flags) { int ret; - ret = io_buffer_register_bvec(io->cmd, req, ublk_io_release, + ret = io_buffer_register_bvec(cmd, req, ublk_io_release, io->buf.index, issue_flags); if (ret) { if (io->buf.flags & UBLK_AUTO_BUF_REG_FALLBACK) { @@ -1258,18 +1259,19 @@ static bool ublk_auto_buf_reg(const struct ublk_queue *ubq, struct request *req, } io->task_registered_buffers = 1; - io->buf_ctx_handle = io_uring_cmd_ctx_handle(io->cmd); + io->buf_ctx_handle = io_uring_cmd_ctx_handle(cmd); io->flags |= UBLK_IO_FLAG_AUTO_BUF_REG; return true; } static bool ublk_prep_auto_buf_reg(struct ublk_queue *ubq, struct request *req, struct ublk_io *io, + struct io_uring_cmd *cmd, unsigned int issue_flags) { ublk_init_req_ref(ubq, io); if (ublk_support_auto_buf_reg(ubq) && ublk_rq_has_data(req)) - return ublk_auto_buf_reg(ubq, req, io, issue_flags); + return ublk_auto_buf_reg(ubq, req, io, cmd, issue_flags); return true; } @@ -1344,7 +1346,7 @@ static void ublk_dispatch_req(struct ublk_queue *ubq, if (!ublk_start_io(ubq, req, io)) return; - if (ublk_prep_auto_buf_reg(ubq, req, io, issue_flags)) + if (ublk_prep_auto_buf_reg(ubq, req, io, io->cmd, issue_flags)) ublk_complete_io_cmd(io, req, UBLK_IO_RES_OK, issue_flags); } From 5907d423400e5fe9774dc52f77fa41073131b093 Mon Sep 17 00:00:00 2001 From: Ming Lei Date: Fri, 21 Nov 2025 09:58:25 +0800 Subject: [PATCH 1439/3902] ublk: add `union ublk_io_buf` with improved naming [ Upstream commit 8d61ece156bd4f2b9e7d3b2a374a26d42c7a4a06 ] Add `union ublk_io_buf` for naming the anonymous union of struct ublk_io's addr and buf fields, meantime apply it to `struct ublk_io` for storing either ublk auto buffer register data or ublk server io buffer address. The union uses clear field names: - `addr`: for regular ublk server io buffer addresses - `auto_reg`: for ublk auto buffer registration data This eliminates confusing access patterns and improves code readability. Reviewed-by: Caleb Sander Mateos Signed-off-by: Ming Lei Signed-off-by: Jens Axboe Stable-dep-of: c258f5c4502c ("ublk: fix deadlock when reading partition table") Signed-off-by: Sasha Levin --- drivers/block/ublk_drv.c | 40 ++++++++++++++++++++++------------------ 1 file changed, 22 insertions(+), 18 deletions(-) diff --git a/drivers/block/ublk_drv.c b/drivers/block/ublk_drv.c index 599571634b7ac8..c9d258b990907b 100644 --- a/drivers/block/ublk_drv.c +++ b/drivers/block/ublk_drv.c @@ -155,12 +155,13 @@ struct ublk_uring_cmd_pdu { */ #define UBLK_REFCOUNT_INIT (REFCOUNT_MAX / 2) +union ublk_io_buf { + __u64 addr; + struct ublk_auto_buf_reg auto_reg; +}; + struct ublk_io { - /* userspace buffer address from io cmd */ - union { - __u64 addr; - struct ublk_auto_buf_reg buf; - }; + union ublk_io_buf buf; unsigned int flags; int res; @@ -499,7 +500,7 @@ static blk_status_t ublk_setup_iod_zoned(struct ublk_queue *ubq, iod->op_flags = ublk_op | ublk_req_build_flags(req); iod->nr_sectors = blk_rq_sectors(req); iod->start_sector = blk_rq_pos(req); - iod->addr = io->addr; + iod->addr = io->buf.addr; return BLK_STS_OK; } @@ -1047,7 +1048,7 @@ static int ublk_map_io(const struct ublk_queue *ubq, const struct request *req, struct iov_iter iter; const int dir = ITER_DEST; - import_ubuf(dir, u64_to_user_ptr(io->addr), rq_bytes, &iter); + import_ubuf(dir, u64_to_user_ptr(io->buf.addr), rq_bytes, &iter); return ublk_copy_user_pages(req, 0, &iter, dir); } return rq_bytes; @@ -1068,7 +1069,7 @@ static int ublk_unmap_io(bool need_map, WARN_ON_ONCE(io->res > rq_bytes); - import_ubuf(dir, u64_to_user_ptr(io->addr), io->res, &iter); + import_ubuf(dir, u64_to_user_ptr(io->buf.addr), io->res, &iter); return ublk_copy_user_pages(req, 0, &iter, dir); } return rq_bytes; @@ -1134,7 +1135,7 @@ static blk_status_t ublk_setup_iod(struct ublk_queue *ubq, struct request *req) iod->op_flags = ublk_op | ublk_req_build_flags(req); iod->nr_sectors = blk_rq_sectors(req); iod->start_sector = blk_rq_pos(req); - iod->addr = io->addr; + iod->addr = io->buf.addr; return BLK_STS_OK; } @@ -1248,9 +1249,9 @@ static bool ublk_auto_buf_reg(const struct ublk_queue *ubq, struct request *req, int ret; ret = io_buffer_register_bvec(cmd, req, ublk_io_release, - io->buf.index, issue_flags); + io->buf.auto_reg.index, issue_flags); if (ret) { - if (io->buf.flags & UBLK_AUTO_BUF_REG_FALLBACK) { + if (io->buf.auto_reg.flags & UBLK_AUTO_BUF_REG_FALLBACK) { ublk_auto_buf_reg_fallback(ubq, io); return true; } @@ -1539,7 +1540,7 @@ static void ublk_queue_reinit(struct ublk_device *ub, struct ublk_queue *ubq) */ io->flags &= UBLK_IO_FLAG_CANCELED; io->cmd = NULL; - io->addr = 0; + io->buf.addr = 0; /* * old task is PF_EXITING, put it now @@ -2100,13 +2101,16 @@ static inline int ublk_check_cmd_op(u32 cmd_op) static inline int ublk_set_auto_buf_reg(struct ublk_io *io, struct io_uring_cmd *cmd) { - io->buf = ublk_sqe_addr_to_auto_buf_reg(READ_ONCE(cmd->sqe->addr)); + struct ublk_auto_buf_reg buf; + + buf = ublk_sqe_addr_to_auto_buf_reg(READ_ONCE(cmd->sqe->addr)); - if (io->buf.reserved0 || io->buf.reserved1) + if (buf.reserved0 || buf.reserved1) return -EINVAL; - if (io->buf.flags & ~UBLK_AUTO_BUF_REG_F_MASK) + if (buf.flags & ~UBLK_AUTO_BUF_REG_F_MASK) return -EINVAL; + io->buf.auto_reg = buf; return 0; } @@ -2128,7 +2132,7 @@ static int ublk_handle_auto_buf_reg(struct ublk_io *io, * this ublk request gets stuck. */ if (io->buf_ctx_handle == io_uring_cmd_ctx_handle(cmd)) - *buf_idx = io->buf.index; + *buf_idx = io->buf.auto_reg.index; } return ublk_set_auto_buf_reg(io, cmd); @@ -2156,7 +2160,7 @@ ublk_config_io_buf(const struct ublk_device *ub, struct ublk_io *io, if (ublk_dev_support_auto_buf_reg(ub)) return ublk_handle_auto_buf_reg(io, cmd, buf_idx); - io->addr = buf_addr; + io->buf.addr = buf_addr; return 0; } @@ -2353,7 +2357,7 @@ static bool ublk_get_data(const struct ublk_queue *ubq, struct ublk_io *io, */ io->flags &= ~UBLK_IO_FLAG_NEED_GET_DATA; /* update iod->addr because ublksrv may have passed a new io buffer */ - ublk_get_iod(ubq, req->tag)->addr = io->addr; + ublk_get_iod(ubq, req->tag)->addr = io->buf.addr; pr_devel("%s: update iod->addr: qid %d tag %d io_flags %x addr %llx\n", __func__, ubq->q_id, req->tag, io->flags, ublk_get_iod(ubq, req->tag)->addr); From 474cf21e0c0ed159839153903455c1aa314c1d07 Mon Sep 17 00:00:00 2001 From: Ming Lei Date: Fri, 21 Nov 2025 09:58:26 +0800 Subject: [PATCH 1440/3902] ublk: refactor auto buffer register in ublk_dispatch_req() [ Upstream commit 0a9beafa7c633e6ff66b05b81eea78231b7e6520 ] Refactor auto buffer register code and prepare for supporting batch IO feature, and the main motivation is to put 'ublk_io' operation code together, so that per-io lock can be applied for the code block. The key changes are: - Rename ublk_auto_buf_reg() as ublk_do_auto_buf_reg() - Introduce an enum `auto_buf_reg_res` to represent the result of the buffer registration attempt (FAIL, FALLBACK, OK). - Split the existing `ublk_do_auto_buf_reg` function into two: - `__ublk_do_auto_buf_reg`: Performs the actual buffer registration and returns the `auto_buf_reg_res` status. - `ublk_do_auto_buf_reg`: A wrapper that calls the internal function and handles the I/O preparation based on the result. - Introduce `ublk_prep_auto_buf_reg_io` to encapsulate the logic for preparing the I/O for completion after buffer registration. - Pass the `tag` directly to `ublk_auto_buf_reg_fallback` to avoid recalculating it. This refactoring makes the control flow clearer and isolates the different stages of the auto buffer registration process. Reviewed-by: Caleb Sander Mateos Signed-off-by: Ming Lei Signed-off-by: Jens Axboe Stable-dep-of: c258f5c4502c ("ublk: fix deadlock when reading partition table") Signed-off-by: Sasha Levin --- drivers/block/ublk_drv.c | 64 +++++++++++++++++++++++++++------------- 1 file changed, 43 insertions(+), 21 deletions(-) diff --git a/drivers/block/ublk_drv.c b/drivers/block/ublk_drv.c index c9d258b990907b..bdb897d440897d 100644 --- a/drivers/block/ublk_drv.c +++ b/drivers/block/ublk_drv.c @@ -1234,17 +1234,37 @@ static inline void __ublk_abort_rq(struct ublk_queue *ubq, } static void -ublk_auto_buf_reg_fallback(const struct ublk_queue *ubq, struct ublk_io *io) +ublk_auto_buf_reg_fallback(const struct ublk_queue *ubq, unsigned tag) { - unsigned tag = io - ubq->ios; struct ublksrv_io_desc *iod = ublk_get_iod(ubq, tag); iod->op_flags |= UBLK_IO_F_NEED_REG_BUF; } -static bool ublk_auto_buf_reg(const struct ublk_queue *ubq, struct request *req, - struct ublk_io *io, struct io_uring_cmd *cmd, - unsigned int issue_flags) +enum auto_buf_reg_res { + AUTO_BUF_REG_FAIL, + AUTO_BUF_REG_FALLBACK, + AUTO_BUF_REG_OK, +}; + +static void ublk_prep_auto_buf_reg_io(const struct ublk_queue *ubq, + struct request *req, struct ublk_io *io, + struct io_uring_cmd *cmd, + enum auto_buf_reg_res res) +{ + if (res == AUTO_BUF_REG_OK) { + io->task_registered_buffers = 1; + io->buf_ctx_handle = io_uring_cmd_ctx_handle(cmd); + io->flags |= UBLK_IO_FLAG_AUTO_BUF_REG; + } + ublk_init_req_ref(ubq, io); + __ublk_prep_compl_io_cmd(io, req); +} + +static enum auto_buf_reg_res +__ublk_do_auto_buf_reg(const struct ublk_queue *ubq, struct request *req, + struct ublk_io *io, struct io_uring_cmd *cmd, + unsigned int issue_flags) { int ret; @@ -1252,29 +1272,27 @@ static bool ublk_auto_buf_reg(const struct ublk_queue *ubq, struct request *req, io->buf.auto_reg.index, issue_flags); if (ret) { if (io->buf.auto_reg.flags & UBLK_AUTO_BUF_REG_FALLBACK) { - ublk_auto_buf_reg_fallback(ubq, io); - return true; + ublk_auto_buf_reg_fallback(ubq, req->tag); + return AUTO_BUF_REG_FALLBACK; } blk_mq_end_request(req, BLK_STS_IOERR); - return false; + return AUTO_BUF_REG_FAIL; } - io->task_registered_buffers = 1; - io->buf_ctx_handle = io_uring_cmd_ctx_handle(cmd); - io->flags |= UBLK_IO_FLAG_AUTO_BUF_REG; - return true; + return AUTO_BUF_REG_OK; } -static bool ublk_prep_auto_buf_reg(struct ublk_queue *ubq, - struct request *req, struct ublk_io *io, - struct io_uring_cmd *cmd, - unsigned int issue_flags) +static void ublk_do_auto_buf_reg(const struct ublk_queue *ubq, struct request *req, + struct ublk_io *io, struct io_uring_cmd *cmd, + unsigned int issue_flags) { - ublk_init_req_ref(ubq, io); - if (ublk_support_auto_buf_reg(ubq) && ublk_rq_has_data(req)) - return ublk_auto_buf_reg(ubq, req, io, cmd, issue_flags); + enum auto_buf_reg_res res = __ublk_do_auto_buf_reg(ubq, req, io, cmd, + issue_flags); - return true; + if (res != AUTO_BUF_REG_FAIL) { + ublk_prep_auto_buf_reg_io(ubq, req, io, cmd, res); + io_uring_cmd_done(cmd, UBLK_IO_RES_OK, issue_flags); + } } static bool ublk_start_io(const struct ublk_queue *ubq, struct request *req, @@ -1347,8 +1365,12 @@ static void ublk_dispatch_req(struct ublk_queue *ubq, if (!ublk_start_io(ubq, req, io)) return; - if (ublk_prep_auto_buf_reg(ubq, req, io, io->cmd, issue_flags)) + if (ublk_support_auto_buf_reg(ubq) && ublk_rq_has_data(req)) { + ublk_do_auto_buf_reg(ubq, req, io, io->cmd, issue_flags); + } else { + ublk_init_req_ref(ubq, io); ublk_complete_io_cmd(io, req, UBLK_IO_RES_OK, issue_flags); + } } static void ublk_cmd_tw_cb(struct io_uring_cmd *cmd, From 0460e09a614291f06c008443f47393c37b7358e7 Mon Sep 17 00:00:00 2001 From: Ming Lei Date: Fri, 12 Dec 2025 22:34:15 +0800 Subject: [PATCH 1441/3902] ublk: fix deadlock when reading partition table [ Upstream commit c258f5c4502c9667bccf5d76fa731ab9c96687c1 ] When one process(such as udev) opens ublk block device (e.g., to read the partition table via bdev_open()), a deadlock[1] can occur: 1. bdev_open() grabs disk->open_mutex 2. The process issues read I/O to ublk backend to read partition table 3. In __ublk_complete_rq(), blk_update_request() or blk_mq_end_request() runs bio->bi_end_io() callbacks 4. If this triggers fput() on file descriptor of ublk block device, the work may be deferred to current task's task work (see fput() implementation) 5. This eventually calls blkdev_release() from the same context 6. blkdev_release() tries to grab disk->open_mutex again 7. Deadlock: same task waiting for a mutex it already holds The fix is to run blk_update_request() and blk_mq_end_request() with bottom halves disabled. This forces blkdev_release() to run in kernel work-queue context instead of current task work context, and allows ublk server to make forward progress, and avoids the deadlock. Fixes: 71f28f3136af ("ublk_drv: add io_uring based userspace block driver") Link: https://github.com/ublk-org/ublksrv/issues/170 [1] Signed-off-by: Ming Lei Reviewed-by: Caleb Sander Mateos [axboe: rewrite comment in ublk] Signed-off-by: Jens Axboe Signed-off-by: Sasha Levin --- drivers/block/ublk_drv.c | 32 ++++++++++++++++++++++++++++---- 1 file changed, 28 insertions(+), 4 deletions(-) diff --git a/drivers/block/ublk_drv.c b/drivers/block/ublk_drv.c index bdb897d440897d..fa7b0481ea0463 100644 --- a/drivers/block/ublk_drv.c +++ b/drivers/block/ublk_drv.c @@ -1146,12 +1146,20 @@ static inline struct ublk_uring_cmd_pdu *ublk_get_uring_cmd_pdu( return io_uring_cmd_to_pdu(ioucmd, struct ublk_uring_cmd_pdu); } +static void ublk_end_request(struct request *req, blk_status_t error) +{ + local_bh_disable(); + blk_mq_end_request(req, error); + local_bh_enable(); +} + /* todo: handle partial completion */ static inline void __ublk_complete_rq(struct request *req, struct ublk_io *io, bool need_map) { unsigned int unmapped_bytes; blk_status_t res = BLK_STS_OK; + bool requeue; /* failed read IO if nothing is read */ if (!io->res && req_op(req) == REQ_OP_READ) @@ -1183,14 +1191,30 @@ static inline void __ublk_complete_rq(struct request *req, struct ublk_io *io, if (unlikely(unmapped_bytes < io->res)) io->res = unmapped_bytes; - if (blk_update_request(req, BLK_STS_OK, io->res)) + /* + * Run bio->bi_end_io() with softirqs disabled. If the final fput + * happens off this path, then that will prevent ublk's blkdev_release() + * from being called on current's task work, see fput() implementation. + * + * Otherwise, ublk server may not provide forward progress in case of + * reading the partition table from bdev_open() with disk->open_mutex + * held, and causes dead lock as we could already be holding + * disk->open_mutex here. + * + * Preferably we would not be doing IO with a mutex held that is also + * used for release, but this work-around will suffice for now. + */ + local_bh_disable(); + requeue = blk_update_request(req, BLK_STS_OK, io->res); + local_bh_enable(); + if (requeue) blk_mq_requeue_request(req, true); else if (likely(!blk_should_fake_timeout(req->q))) __blk_mq_end_request(req, BLK_STS_OK); return; exit: - blk_mq_end_request(req, res); + ublk_end_request(req, res); } static struct io_uring_cmd *__ublk_prep_compl_io_cmd(struct ublk_io *io, @@ -1230,7 +1254,7 @@ static inline void __ublk_abort_rq(struct ublk_queue *ubq, if (ublk_nosrv_dev_should_queue_io(ubq->dev)) blk_mq_requeue_request(rq, false); else - blk_mq_end_request(rq, BLK_STS_IOERR); + ublk_end_request(rq, BLK_STS_IOERR); } static void @@ -1275,7 +1299,7 @@ __ublk_do_auto_buf_reg(const struct ublk_queue *ubq, struct request *req, ublk_auto_buf_reg_fallback(ubq, req->tag); return AUTO_BUF_REG_FALLBACK; } - blk_mq_end_request(req, BLK_STS_IOERR); + ublk_end_request(req, BLK_STS_IOERR); return AUTO_BUF_REG_FAIL; } From cfc473f29eb5f44516993c14c237b39836bbf80b Mon Sep 17 00:00:00 2001 From: Thomas Fourier Date: Wed, 17 Dec 2025 10:36:48 +0100 Subject: [PATCH 1442/3902] block: rnbd-clt: Fix leaked ID in init_dev() [ Upstream commit c9b5645fd8ca10f310e41b07540f98e6a9720f40 ] If kstrdup() fails in init_dev(), then the newly allocated ID is lost. Fixes: 64e8a6ece1a5 ("block/rnbd-clt: Dynamically alloc buffer for pathname & blk_symlink_name") Signed-off-by: Thomas Fourier Acked-by: Jack Wang Signed-off-by: Jens Axboe Signed-off-by: Sasha Levin --- drivers/block/rnbd/rnbd-clt.c | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/drivers/block/rnbd/rnbd-clt.c b/drivers/block/rnbd/rnbd-clt.c index f1409e54010a67..d1c354636315d2 100644 --- a/drivers/block/rnbd/rnbd-clt.c +++ b/drivers/block/rnbd/rnbd-clt.c @@ -1423,9 +1423,11 @@ static struct rnbd_clt_dev *init_dev(struct rnbd_clt_session *sess, goto out_alloc; } - ret = ida_alloc_max(&index_ida, (1 << (MINORBITS - RNBD_PART_BITS)) - 1, - GFP_KERNEL); - if (ret < 0) { + dev->clt_device_id = ida_alloc_max(&index_ida, + (1 << (MINORBITS - RNBD_PART_BITS)) - 1, + GFP_KERNEL); + if (dev->clt_device_id < 0) { + ret = dev->clt_device_id; pr_err("Failed to initialize device '%s' from session %s, allocating idr failed, err: %d\n", pathname, sess->sessname, ret); goto out_queues; @@ -1434,10 +1436,9 @@ static struct rnbd_clt_dev *init_dev(struct rnbd_clt_session *sess, dev->pathname = kstrdup(pathname, GFP_KERNEL); if (!dev->pathname) { ret = -ENOMEM; - goto out_queues; + goto out_ida; } - dev->clt_device_id = ret; dev->sess = sess; dev->access_mode = access_mode; dev->nr_poll_queues = nr_poll_queues; @@ -1453,6 +1454,8 @@ static struct rnbd_clt_dev *init_dev(struct rnbd_clt_session *sess, return dev; +out_ida: + ida_free(&index_ida, dev->clt_device_id); out_queues: kfree(dev->hw_queues); out_alloc: From 1d200017f55f829b9e376093bd31dfbec92081de Mon Sep 17 00:00:00 2001 From: Shuicheng Lin Date: Fri, 5 Dec 2025 23:47:17 +0000 Subject: [PATCH 1443/3902] drm/xe: Limit num_syncs to prevent oversized allocations MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [ Upstream commit 8e461304009135270e9ccf2d7e2dfe29daec9b60 ] The exec and vm_bind ioctl allow userspace to specify an arbitrary num_syncs value. Without bounds checking, a very large num_syncs can force an excessively large allocation, leading to kernel warnings from the page allocator as below. Introduce DRM_XE_MAX_SYNCS (set to 1024) and reject any request exceeding this limit. " ------------[ cut here ]------------ WARNING: CPU: 0 PID: 1217 at mm/page_alloc.c:5124 __alloc_frozen_pages_noprof+0x2f8/0x2180 mm/page_alloc.c:5124 ... Call Trace: alloc_pages_mpol+0xe4/0x330 mm/mempolicy.c:2416 ___kmalloc_large_node+0xd8/0x110 mm/slub.c:4317 __kmalloc_large_node_noprof+0x18/0xe0 mm/slub.c:4348 __do_kmalloc_node mm/slub.c:4364 [inline] __kmalloc_noprof+0x3d4/0x4b0 mm/slub.c:4388 kmalloc_noprof include/linux/slab.h:909 [inline] kmalloc_array_noprof include/linux/slab.h:948 [inline] xe_exec_ioctl+0xa47/0x1e70 drivers/gpu/drm/xe/xe_exec.c:158 drm_ioctl_kernel+0x1f1/0x3e0 drivers/gpu/drm/drm_ioctl.c:797 drm_ioctl+0x5e7/0xc50 drivers/gpu/drm/drm_ioctl.c:894 xe_drm_ioctl+0x10b/0x170 drivers/gpu/drm/xe/xe_device.c:224 vfs_ioctl fs/ioctl.c:51 [inline] __do_sys_ioctl fs/ioctl.c:598 [inline] __se_sys_ioctl fs/ioctl.c:584 [inline] __x64_sys_ioctl+0x18b/0x210 fs/ioctl.c:584 do_syscall_x64 arch/x86/entry/syscall_64.c:63 [inline] do_syscall_64+0xbb/0x380 arch/x86/entry/syscall_64.c:94 entry_SYSCALL_64_after_hwframe+0x77/0x7f ... " v2: Add "Reported-by" and Cc stable kernels. v3: Change XE_MAX_SYNCS from 64 to 1024. (Matt & Ashutosh) v4: s/XE_MAX_SYNCS/DRM_XE_MAX_SYNCS/ (Matt) v5: Do the check at the top of the exec func. (Matt) Fixes: dd08ebf6c352 ("drm/xe: Introduce a new DRM driver for Intel GPUs") Reported-by: Koen Koning Reported-by: Peter Senna Tschudin Closes: https://gitlab.freedesktop.org/drm/xe/kernel/-/issues/6450 Cc: # v6.12+ Cc: Matthew Brost Cc: Michal Mrozek Cc: Carl Zhang Cc: José Roberto de Souza Cc: Lionel Landwerlin Cc: Ivan Briano Cc: Thomas Hellström Cc: Ashutosh Dixit Signed-off-by: Shuicheng Lin Reviewed-by: Matthew Brost Signed-off-by: Matthew Brost Link: https://patch.msgid.link/20251205234715.2476561-5-shuicheng.lin@intel.com (cherry picked from commit b07bac9bd708ec468cd1b8a5fe70ae2ac9b0a11c) Signed-off-by: Thomas Hellström Stable-dep-of: f8dd66bfb4e1 ("drm/xe/oa: Limit num_syncs to prevent oversized allocations") Signed-off-by: Sasha Levin --- drivers/gpu/drm/xe/xe_exec.c | 3 ++- drivers/gpu/drm/xe/xe_vm.c | 3 +++ include/uapi/drm/xe_drm.h | 1 + 3 files changed, 6 insertions(+), 1 deletion(-) diff --git a/drivers/gpu/drm/xe/xe_exec.c b/drivers/gpu/drm/xe/xe_exec.c index a8ab363a804658..ca85f7c15fabe8 100644 --- a/drivers/gpu/drm/xe/xe_exec.c +++ b/drivers/gpu/drm/xe/xe_exec.c @@ -130,7 +130,8 @@ int xe_exec_ioctl(struct drm_device *dev, void *data, struct drm_file *file) if (XE_IOCTL_DBG(xe, args->extensions) || XE_IOCTL_DBG(xe, args->pad[0] || args->pad[1] || args->pad[2]) || - XE_IOCTL_DBG(xe, args->reserved[0] || args->reserved[1])) + XE_IOCTL_DBG(xe, args->reserved[0] || args->reserved[1]) || + XE_IOCTL_DBG(xe, args->num_syncs > DRM_XE_MAX_SYNCS)) return -EINVAL; q = xe_exec_queue_lookup(xef, args->exec_queue_id); diff --git a/drivers/gpu/drm/xe/xe_vm.c b/drivers/gpu/drm/xe/xe_vm.c index cdd1dc540a59e6..f0f699baa9f6e8 100644 --- a/drivers/gpu/drm/xe/xe_vm.c +++ b/drivers/gpu/drm/xe/xe_vm.c @@ -3282,6 +3282,9 @@ static int vm_bind_ioctl_check_args(struct xe_device *xe, struct xe_vm *vm, if (XE_IOCTL_DBG(xe, args->extensions)) return -EINVAL; + if (XE_IOCTL_DBG(xe, args->num_syncs > DRM_XE_MAX_SYNCS)) + return -EINVAL; + if (args->num_binds > 1) { u64 __user *bind_user = u64_to_user_ptr(args->vector_of_binds); diff --git a/include/uapi/drm/xe_drm.h b/include/uapi/drm/xe_drm.h index 517489a7ec60aa..400555a8af187f 100644 --- a/include/uapi/drm/xe_drm.h +++ b/include/uapi/drm/xe_drm.h @@ -1459,6 +1459,7 @@ struct drm_xe_exec { /** @exec_queue_id: Exec queue ID for the batch buffer */ __u32 exec_queue_id; +#define DRM_XE_MAX_SYNCS 1024 /** @num_syncs: Amount of struct drm_xe_sync in array. */ __u32 num_syncs; From 338849090ee610ff6d11e5e90857d2c27a4121ab Mon Sep 17 00:00:00 2001 From: Shuicheng Lin Date: Fri, 5 Dec 2025 23:47:18 +0000 Subject: [PATCH 1444/3902] drm/xe/oa: Limit num_syncs to prevent oversized allocations MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [ Upstream commit f8dd66bfb4e184c71bd26418a00546ebe7f5c17a ] The OA open parameters did not validate num_syncs, allowing userspace to pass arbitrarily large values, potentially leading to excessive allocations. Add check to ensure that num_syncs does not exceed DRM_XE_MAX_SYNCS, returning -EINVAL when the limit is violated. v2: use XE_IOCTL_DBG() and drop duplicated check. (Ashutosh) Fixes: c8507a25cebd ("drm/xe/oa/uapi: Define and parse OA sync properties") Cc: Matthew Brost Cc: Ashutosh Dixit Signed-off-by: Shuicheng Lin Reviewed-by: Ashutosh Dixit Signed-off-by: Matthew Brost Link: https://patch.msgid.link/20251205234715.2476561-6-shuicheng.lin@intel.com (cherry picked from commit e057b2d2b8d815df3858a87dffafa2af37e5945b) Signed-off-by: Thomas Hellström Signed-off-by: Sasha Levin --- drivers/gpu/drm/xe/xe_oa.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/drivers/gpu/drm/xe/xe_oa.c b/drivers/gpu/drm/xe/xe_oa.c index 125698a9ecf1c4..10047373e1842d 100644 --- a/drivers/gpu/drm/xe/xe_oa.c +++ b/drivers/gpu/drm/xe/xe_oa.c @@ -1253,6 +1253,9 @@ static int xe_oa_set_no_preempt(struct xe_oa *oa, u64 value, static int xe_oa_set_prop_num_syncs(struct xe_oa *oa, u64 value, struct xe_oa_open_param *param) { + if (XE_IOCTL_DBG(oa->xe, value > DRM_XE_MAX_SYNCS)) + return -EINVAL; + param->num_syncs = value; return 0; } From 1e5b2d2138f6b747e716a2e8c7ce22a9c2b7852d Mon Sep 17 00:00:00 2001 From: Ashutosh Dixit Date: Fri, 5 Dec 2025 13:26:13 -0800 Subject: [PATCH 1445/3902] drm/xe/oa: Always set OAG_OAGLBCTXCTRL_COUNTER_RESUME MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [ Upstream commit 256edb267a9d0b5aef70e408e9fba4f930f9926e ] Reports can be written out to the OA buffer using ways other than periodic sampling. These include mmio trigger and context switches. To support these use cases, when periodic sampling is not enabled, OAG_OAGLBCTXCTRL_COUNTER_RESUME must be set. Fixes: 1db9a9dc90ae ("drm/xe/oa: OA stream initialization (OAG)") Signed-off-by: Ashutosh Dixit Reviewed-by: Umesh Nerlige Ramappa Link: https://patch.msgid.link/20251205212613.826224-4-ashutosh.dixit@intel.com (cherry picked from commit 88d98e74adf3e20f678bb89581a5c3149fdbdeaa) Signed-off-by: Thomas Hellström Signed-off-by: Sasha Levin --- drivers/gpu/drm/xe/xe_oa.c | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/drivers/gpu/drm/xe/xe_oa.c b/drivers/gpu/drm/xe/xe_oa.c index 10047373e1842d..d0ceb67af83ed5 100644 --- a/drivers/gpu/drm/xe/xe_oa.c +++ b/drivers/gpu/drm/xe/xe_oa.c @@ -1104,11 +1104,12 @@ static int xe_oa_enable_metric_set(struct xe_oa_stream *stream) oag_buf_size_select(stream) | oag_configure_mmio_trigger(stream, true)); - xe_mmio_write32(mmio, __oa_regs(stream)->oa_ctx_ctrl, stream->periodic ? - (OAG_OAGLBCTXCTRL_COUNTER_RESUME | + xe_mmio_write32(mmio, __oa_regs(stream)->oa_ctx_ctrl, + OAG_OAGLBCTXCTRL_COUNTER_RESUME | + (stream->periodic ? OAG_OAGLBCTXCTRL_TIMER_ENABLE | REG_FIELD_PREP(OAG_OAGLBCTXCTRL_TIMER_PERIOD_MASK, - stream->period_exponent)) : 0); + stream->period_exponent) : 0)); /* * Initialize Super Queue Internal Cnt Register From 66dce411c7e901631915edd336bf37b03c7a7c4b Mon Sep 17 00:00:00 2001 From: Sairaj Kodilkar Date: Fri, 21 Nov 2025 14:41:15 +0530 Subject: [PATCH 1446/3902] amd/iommu: Preserve domain ids inside the kdump kernel [ Upstream commit c2e8dc1222c2136e714d5d972dce7e64924e4ed8 ] Currently AMD IOMMU driver does not reserve domain ids programmed in the DTE while reusing the device table inside kdump kernel. This can cause reallocation of these domain ids for newer domains that are created by the kdump kernel, which can lead to potential IO_PAGE_FAULTs Hence reserve these ids inside pdom_ids. Fixes: 38e5f33ee359 ("iommu/amd: Reuse device table for kdump") Signed-off-by: Sairaj Kodilkar Reported-by: Jason Gunthorpe Reviewed-by: Vasant Hegde Reviewed-by: Jason Gunthorpe Signed-off-by: Joerg Roedel Signed-off-by: Sasha Levin --- drivers/iommu/amd/init.c | 23 ++++++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/drivers/iommu/amd/init.c b/drivers/iommu/amd/init.c index f2991c11867cba..14eb9de33ccb01 100644 --- a/drivers/iommu/amd/init.c +++ b/drivers/iommu/amd/init.c @@ -1136,9 +1136,13 @@ static void set_dte_bit(struct dev_table_entry *dte, u8 bit) static bool __reuse_device_table(struct amd_iommu *iommu) { struct amd_iommu_pci_seg *pci_seg = iommu->pci_seg; - u32 lo, hi, old_devtb_size; + struct dev_table_entry *old_dev_tbl_entry; + u32 lo, hi, old_devtb_size, devid; phys_addr_t old_devtb_phys; + u16 dom_id; + bool dte_v; u64 entry; + int ret; /* Each IOMMU use separate device table with the same size */ lo = readl(iommu->mmio_base + MMIO_DEV_TABLE_OFFSET); @@ -1173,6 +1177,23 @@ static bool __reuse_device_table(struct amd_iommu *iommu) return false; } + for (devid = 0; devid <= pci_seg->last_bdf; devid++) { + old_dev_tbl_entry = &pci_seg->old_dev_tbl_cpy[devid]; + dte_v = FIELD_GET(DTE_FLAG_V, old_dev_tbl_entry->data[0]); + dom_id = FIELD_GET(DEV_DOMID_MASK, old_dev_tbl_entry->data[1]); + + if (!dte_v || !dom_id) + continue; + /* + * ID reservation can fail with -ENOSPC when there + * are multiple devices present in the same domain, + * hence check only for -ENOMEM. + */ + ret = ida_alloc_range(&pdom_ids, dom_id, dom_id, GFP_KERNEL); + if (ret == -ENOMEM) + return false; + } + return true; } From 0821f3ad2d6e2fd9219ed859ad0f7e5b815766dc Mon Sep 17 00:00:00 2001 From: "Rob Herring (Arm)" Date: Fri, 5 Dec 2025 22:59:38 +0100 Subject: [PATCH 1447/3902] arm64: dts: mediatek: Apply mt8395-radxa DT overlay at build time [ Upstream commit ce7b1d58609abc2941a1f38094147f439fb74233 ] It's a requirement that DT overlays be applied at build time in order to validate them as overlays are not validated on their own. Add missing target for mt8395-radxa hd panel overlay. Fixes: 4c8ff61199a7 ("arm64: dts: mediatek: mt8395-radxa-nio-12l: Add Radxa 8 HD panel") Signed-off-by: Frank Wunderlich Acked-by: AngeloGioacchino Del Regno Link: https://patch.msgid.link/20251205215940.19287-1-linux@fw-web.de Signed-off-by: Rob Herring (Arm) Signed-off-by: Sasha Levin --- arch/arm64/boot/dts/mediatek/Makefile | 2 ++ 1 file changed, 2 insertions(+) diff --git a/arch/arm64/boot/dts/mediatek/Makefile b/arch/arm64/boot/dts/mediatek/Makefile index a4df4c21399eef..b50799b2a65fbd 100644 --- a/arch/arm64/boot/dts/mediatek/Makefile +++ b/arch/arm64/boot/dts/mediatek/Makefile @@ -104,6 +104,8 @@ dtb-$(CONFIG_ARCH_MEDIATEK) += mt8390-genio-700-evk.dtb dtb-$(CONFIG_ARCH_MEDIATEK) += mt8395-kontron-3-5-sbc-i1200.dtb dtb-$(CONFIG_ARCH_MEDIATEK) += mt8395-radxa-nio-12l.dtb dtb-$(CONFIG_ARCH_MEDIATEK) += mt8395-radxa-nio-12l-8-hd-panel.dtbo +mt8395-radxa-nio-12l-8-hd-panel-dtbs := mt8395-radxa-nio-12l.dtb mt8395-radxa-nio-12l-8-hd-panel.dtbo +dtb-$(CONFIG_ARCH_MEDIATEK) += mt8395-radxa-nio-12l-8-hd-panel.dtb dtb-$(CONFIG_ARCH_MEDIATEK) += mt8516-pumpkin.dtb # Device tree overlays support From 3041d93301ca6f79db063e868b82d3e7f256dcb7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nuno=20S=C3=A1?= Date: Fri, 19 Dec 2025 16:11:05 +0000 Subject: [PATCH 1448/3902] hwmon: (ltc4282): Fix reset_history file permissions MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [ Upstream commit b3db91c3bfea69a6c6258fea508f25a59c0feb1a ] The reset_history attributes are write only. Hence don't report them as readable just to return -EOPNOTSUPP later on. Fixes: cbc29538dbf7 ("hwmon: Add driver for LTC4282") Signed-off-by: Nuno Sá Link: https://lore.kernel.org/r/20251219-ltc4282-fix-reset-history-v1-1-8eab974c124b@analog.com Signed-off-by: Guenter Roeck Signed-off-by: Sasha Levin --- drivers/hwmon/ltc4282.c | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/drivers/hwmon/ltc4282.c b/drivers/hwmon/ltc4282.c index 1d664a2d7b3cb9..58c2d3a6243293 100644 --- a/drivers/hwmon/ltc4282.c +++ b/drivers/hwmon/ltc4282.c @@ -1018,8 +1018,9 @@ static umode_t ltc4282_in_is_visible(const struct ltc4282_state *st, u32 attr) case hwmon_in_max: case hwmon_in_min: case hwmon_in_enable: - case hwmon_in_reset_history: return 0644; + case hwmon_in_reset_history: + return 0200; default: return 0; } @@ -1038,8 +1039,9 @@ static umode_t ltc4282_curr_is_visible(u32 attr) return 0444; case hwmon_curr_max: case hwmon_curr_min: - case hwmon_curr_reset_history: return 0644; + case hwmon_curr_reset_history: + return 0200; default: return 0; } @@ -1057,8 +1059,9 @@ static umode_t ltc4282_power_is_visible(u32 attr) return 0444; case hwmon_power_max: case hwmon_power_min: - case hwmon_power_reset_history: return 0644; + case hwmon_power_reset_history: + return 0200; default: return 0; } From 571204e4758a528fbd67330bd4b0dfbdafb33dd8 Mon Sep 17 00:00:00 2001 From: Qianchang Zhao Date: Sun, 9 Nov 2025 10:00:55 +0900 Subject: [PATCH 1449/3902] ksmbd: skip lock-range check on equal size to avoid size==0 underflow commit 5d510ac31626ed157d2182149559430350cf2104 upstream. When size equals the current i_size (including 0), the code used to call check_lock_range(filp, i_size, size - 1, WRITE), which computes `size - 1` and can underflow for size==0. Skip the equal case. Cc: stable@vger.kernel.org Reported-by: Qianchang Zhao Reported-by: Zhitong Liu Signed-off-by: Qianchang Zhao Acked-by: Namjae Jeon Signed-off-by: Steve French Signed-off-by: Greg Kroah-Hartman --- fs/smb/server/vfs.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/fs/smb/server/vfs.c b/fs/smb/server/vfs.c index 891ed2dc2b7351..90e693e4e41497 100644 --- a/fs/smb/server/vfs.c +++ b/fs/smb/server/vfs.c @@ -324,6 +324,9 @@ static int check_lock_range(struct file *filp, loff_t start, loff_t end, struct file_lock_context *ctx = locks_inode_context(file_inode(filp)); int error = 0; + if (start == end) + return 0; + if (!ctx || list_empty_careful(&ctx->flc_posix)) return 0; @@ -828,7 +831,7 @@ int ksmbd_vfs_truncate(struct ksmbd_work *work, if (size < inode->i_size) { err = check_lock_range(filp, size, inode->i_size - 1, WRITE); - } else { + } else if (size > inode->i_size) { err = check_lock_range(filp, inode->i_size, size - 1, WRITE); } From 8cabcb4dd3dc85dd83a37d26efcc59a66a4074d7 Mon Sep 17 00:00:00 2001 From: Namjae Jeon Date: Sun, 14 Dec 2025 15:05:56 +0900 Subject: [PATCH 1450/3902] ksmbd: Fix refcount leak when invalid session is found on session lookup commit cafb57f7bdd57abba87725eb4e82bbdca4959644 upstream. When a session is found but its state is not SMB2_SESSION_VALID, It indicates that no valid session was found, but it is missing to decrement the reference count acquired by the session lookup, which results in a reference count leak. This patch fixes the issue by explicitly calling ksmbd_user_session_put to release the reference to the session. Cc: stable@vger.kernel.org Reported-by: Alexandre Reported-by: Stanislas Polu Signed-off-by: Namjae Jeon Signed-off-by: Steve French Signed-off-by: Greg Kroah-Hartman --- fs/smb/server/mgmt/user_session.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/fs/smb/server/mgmt/user_session.c b/fs/smb/server/mgmt/user_session.c index 1c181ef9992957..7d880ff34402e0 100644 --- a/fs/smb/server/mgmt/user_session.c +++ b/fs/smb/server/mgmt/user_session.c @@ -325,8 +325,10 @@ struct ksmbd_session *ksmbd_session_lookup_all(struct ksmbd_conn *conn, sess = ksmbd_session_lookup(conn, id); if (!sess && conn->binding) sess = ksmbd_session_lookup_slowpath(id); - if (sess && sess->state != SMB2_SESSION_VALID) + if (sess && sess->state != SMB2_SESSION_VALID) { + ksmbd_user_session_put(sess); sess = NULL; + } return sess; } From 6dc8cf6e7998ef7aeb9383a4c2904ea5d22fa2e4 Mon Sep 17 00:00:00 2001 From: Namjae Jeon Date: Sun, 14 Dec 2025 15:06:34 +0900 Subject: [PATCH 1451/3902] ksmbd: fix buffer validation by including null terminator size in EA length commit 95d7a890e4b03e198836d49d699408fd1867cb55 upstream. The smb2_set_ea function, which handles Extended Attributes (EA), was performing buffer validation checks that incorrectly omitted the size of the null terminating character (+1 byte) for EA Name. This patch fixes the issue by explicitly adding '+ 1' to EaNameLength where the null terminator is expected to be present in the buffer, ensuring the validation accurately reflects the total required buffer size. Cc: stable@vger.kernel.org Reported-by: Roger Reported-by: Stanislas Polu Signed-off-by: Namjae Jeon Signed-off-by: Steve French Signed-off-by: Greg Kroah-Hartman --- fs/smb/server/smb2pdu.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/fs/smb/server/smb2pdu.c b/fs/smb/server/smb2pdu.c index dba29881debdc6..6a94cda0927d7d 100644 --- a/fs/smb/server/smb2pdu.c +++ b/fs/smb/server/smb2pdu.c @@ -2373,7 +2373,7 @@ static int smb2_set_ea(struct smb2_ea_info *eabuf, unsigned int buf_len, int rc = 0; unsigned int next = 0; - if (buf_len < sizeof(struct smb2_ea_info) + eabuf->EaNameLength + + if (buf_len < sizeof(struct smb2_ea_info) + eabuf->EaNameLength + 1 + le16_to_cpu(eabuf->EaValueLength)) return -EINVAL; @@ -2450,7 +2450,7 @@ static int smb2_set_ea(struct smb2_ea_info *eabuf, unsigned int buf_len, break; } - if (buf_len < sizeof(struct smb2_ea_info) + eabuf->EaNameLength + + if (buf_len < sizeof(struct smb2_ea_info) + eabuf->EaNameLength + 1 + le16_to_cpu(eabuf->EaValueLength)) { rc = -EINVAL; break; From 14b1a6009e488db77c454f1286292643532b6585 Mon Sep 17 00:00:00 2001 From: Ping Cheng Date: Mon, 27 Oct 2025 13:37:42 -0700 Subject: [PATCH 1452/3902] HID: input: map HID_GD_Z to ABS_DISTANCE for stylus/pen commit 7953794f741e94d30df9dafaaa4c031c85b891d6 upstream. HID_GD_Z is mapped to ABS_Z for stylus and pen in hid-input.c. But HID_GD_Z should be used to report ABS_DISTANCE for stylus and pen as described at: Documentation/input/event-codes.rst#n226 * ABS_DISTANCE: - Used to describe the distance of a tool from an interaction surface. This event should only be emitted while the tool is hovering, meaning in close proximity of the device and while the value of the BTN_TOUCH code is 0. If the input device may be used freely in three dimensions, consider ABS_Z instead. - BTN_TOOL_ should be set to 1 when the tool comes into detectable proximity and set to 0 when the tool leaves detectable proximity. BTN_TOOL_ signals the type of tool that is currently detected by the hardware and is otherwise independent of ABS_DISTANCE and/or BTN_TOUCH. This patch makes the correct mapping. The ABS_DISTANCE is currently not mapped by any HID usage in hid-generic driver. Signed-off-by: Ping Cheng Cc: stable@kernel.org Signed-off-by: Jiri Kosina Signed-off-by: Greg Kroah-Hartman --- drivers/hid/hid-input.c | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/drivers/hid/hid-input.c b/drivers/hid/hid-input.c index 2bbb645c2ff418..32a96ae2946f1d 100644 --- a/drivers/hid/hid-input.c +++ b/drivers/hid/hid-input.c @@ -878,7 +878,7 @@ static void hidinput_configure_usage(struct hid_input *hidinput, struct hid_fiel switch (usage->hid) { /* These usage IDs map directly to the usage codes. */ - case HID_GD_X: case HID_GD_Y: case HID_GD_Z: + case HID_GD_X: case HID_GD_Y: case HID_GD_RX: case HID_GD_RY: case HID_GD_RZ: if (field->flags & HID_MAIN_ITEM_RELATIVE) map_rel(usage->hid & 0xf); @@ -886,6 +886,22 @@ static void hidinput_configure_usage(struct hid_input *hidinput, struct hid_fiel map_abs_clear(usage->hid & 0xf); break; + case HID_GD_Z: + /* HID_GD_Z is mapped to ABS_DISTANCE for stylus/pen */ + if (field->flags & HID_MAIN_ITEM_RELATIVE) { + map_rel(usage->hid & 0xf); + } else { + if (field->application == HID_DG_PEN || + field->physical == HID_DG_PEN || + field->logical == HID_DG_STYLUS || + field->physical == HID_DG_STYLUS || + field->application == HID_DG_DIGITIZER) + map_abs_clear(ABS_DISTANCE); + else + map_abs_clear(usage->hid & 0xf); + } + break; + case HID_GD_WHEEL: if (field->flags & HID_MAIN_ITEM_RELATIVE) { set_bit(REL_WHEEL, input->relbit); From cae029e0575dfd2d581bfd82f3031268e445368e Mon Sep 17 00:00:00 2001 From: Sasha Finkelstein Date: Thu, 18 Dec 2025 10:15:23 -0800 Subject: [PATCH 1453/3902] Input: apple_z2 - fix reading incorrect reports after exiting sleep commit d579478cee228bdc0029a0c12a1f6a63ea9d1c77 upstream. Under certain conditions (more prevalent after a suspend/resume cycle), the touchscreen controller can send the "boot complete" interrupt before it actually finished booting. In those cases, attempting to read touch data resuls in a stream of "not ready" messages being read and interpreted as a touch report. Check that the response is in fact a touch report and discard it otherwise. Reported-by: pitust Closes: https://oftc.catirclogs.org/asahi/2025-12-17#34878715; Fixes: 471a92f8a21a ("Input: apple_z2 - add a driver for Apple Z2 touchscreens") Signed-off-by: Sasha Finkelstein Link: https://patch.msgid.link/20251218-z2-init-fix-v1-1-48e3aa239caf@gmail.com Cc: stable@vger.kernel.org Signed-off-by: Dmitry Torokhov Signed-off-by: Greg Kroah-Hartman --- drivers/input/touchscreen/apple_z2.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/drivers/input/touchscreen/apple_z2.c b/drivers/input/touchscreen/apple_z2.c index 0de161eae59a05..271ababf0ad559 100644 --- a/drivers/input/touchscreen/apple_z2.c +++ b/drivers/input/touchscreen/apple_z2.c @@ -21,6 +21,7 @@ #define APPLE_Z2_TOUCH_STARTED 3 #define APPLE_Z2_TOUCH_MOVED 4 #define APPLE_Z2_CMD_READ_INTERRUPT_DATA 0xEB +#define APPLE_Z2_REPLY_INTERRUPT_DATA 0xE1 #define APPLE_Z2_HBPP_CMD_BLOB 0x3001 #define APPLE_Z2_FW_MAGIC 0x5746325A #define LOAD_COMMAND_INIT_PAYLOAD 0 @@ -142,6 +143,9 @@ static int apple_z2_read_packet(struct apple_z2 *z2) if (error) return error; + if (z2->rx_buf[0] != APPLE_Z2_REPLY_INTERRUPT_DATA) + return 0; + pkt_len = (get_unaligned_le16(z2->rx_buf + 1) + 8) & 0xfffffffc; error = spi_read(z2->spidev, z2->rx_buf, pkt_len); From 9d9eab0eb2bf6befde94113fbcd8204a6ff15e0f Mon Sep 17 00:00:00 2001 From: Sanjay Govind Date: Sat, 29 Nov 2025 20:37:11 +1300 Subject: [PATCH 1454/3902] Input: xpad - add support for CRKD Guitars commit 806ec7b797adc1cc9b11535307638a55ddfb873c upstream. Add support for various CRKD Guitar Controllers. Signed-off-by: Sanjay Govind Link: https://patch.msgid.link/20251129073720.2750-2-sanjay.govind9@gmail.com Cc: stable@vger.kernel.org Signed-off-by: Dmitry Torokhov Signed-off-by: Greg Kroah-Hartman --- drivers/input/joystick/xpad.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/drivers/input/joystick/xpad.c b/drivers/input/joystick/xpad.c index d72e89c25e5031..363d509493866a 100644 --- a/drivers/input/joystick/xpad.c +++ b/drivers/input/joystick/xpad.c @@ -133,6 +133,8 @@ static const struct xpad_device { } xpad_device[] = { /* Please keep this list sorted by vendor and product ID. */ { 0x0079, 0x18d4, "GPD Win 2 X-Box Controller", 0, XTYPE_XBOX360 }, + { 0x0351, 0x1000, "CRKD LP Blueberry Burst Pro Edition (Xbox)", 0, XTYPE_XBOX360 }, + { 0x0351, 0x2000, "CRKD LP Black Tribal Edition (Xbox) ", 0, XTYPE_XBOX360 }, { 0x03eb, 0xff01, "Wooting One (Legacy)", 0, XTYPE_XBOX360 }, { 0x03eb, 0xff02, "Wooting Two (Legacy)", 0, XTYPE_XBOX360 }, { 0x03f0, 0x038D, "HyperX Clutch", 0, XTYPE_XBOX360 }, /* wired */ @@ -420,6 +422,7 @@ static const struct xpad_device { { 0x3285, 0x0663, "Nacon Evol-X", 0, XTYPE_XBOXONE }, { 0x3537, 0x1004, "GameSir T4 Kaleid", 0, XTYPE_XBOX360 }, { 0x3537, 0x1010, "GameSir G7 SE", 0, XTYPE_XBOXONE }, + { 0x3651, 0x1000, "CRKD SG", 0, XTYPE_XBOX360 }, { 0x366c, 0x0005, "ByoWave Proteus Controller", MAP_SHARE_BUTTON, XTYPE_XBOXONE, FLAG_DELAY_INIT }, { 0x3767, 0x0101, "Fanatec Speedster 3 Forceshock Wheel", 0, XTYPE_XBOX }, { 0x37d7, 0x2501, "Flydigi Apex 5", 0, XTYPE_XBOX360 }, @@ -518,6 +521,7 @@ static const struct usb_device_id xpad_table[] = { */ { USB_INTERFACE_INFO('X', 'B', 0) }, /* Xbox USB-IF not-approved class */ XPAD_XBOX360_VENDOR(0x0079), /* GPD Win 2 controller */ + XPAD_XBOX360_VENDOR(0x0351), /* CRKD Controllers */ XPAD_XBOX360_VENDOR(0x03eb), /* Wooting Keyboards (Legacy) */ XPAD_XBOX360_VENDOR(0x03f0), /* HP HyperX Xbox 360 controllers */ XPAD_XBOXONE_VENDOR(0x03f0), /* HP HyperX Xbox One controllers */ @@ -578,6 +582,7 @@ static const struct usb_device_id xpad_table[] = { XPAD_XBOXONE_VENDOR(0x3285), /* Nacon Evol-X */ XPAD_XBOX360_VENDOR(0x3537), /* GameSir Controllers */ XPAD_XBOXONE_VENDOR(0x3537), /* GameSir Controllers */ + XPAD_XBOX360_VENDOR(0x3651), /* CRKD Controllers */ XPAD_XBOXONE_VENDOR(0x366c), /* ByoWave controllers */ XPAD_XBOX360_VENDOR(0x37d7), /* Flydigi Controllers */ XPAD_XBOX360_VENDOR(0x413d), /* Black Shark Green Ghost Controller */ From 40e3042de43ffa0017a8460ff9b4cad7b8c7cb96 Mon Sep 17 00:00:00 2001 From: Junjie Cao Date: Thu, 18 Dec 2025 21:56:59 -0800 Subject: [PATCH 1455/3902] Input: ti_am335x_tsc - fix off-by-one error in wire_order validation commit 248d3a73a0167dce15ba100477c3e778c4787178 upstream. The current validation 'wire_order[i] > ARRAY_SIZE(config_pins)' allows wire_order[i] to equal ARRAY_SIZE(config_pins), which causes out-of-bounds access when used as index in 'config_pins[wire_order[i]]'. Since config_pins has 4 elements (indices 0-3), the valid range for wire_order should be 0-3. Fix the off-by-one error by using >= instead of > in the validation check. Signed-off-by: Junjie Cao Link: https://patch.msgid.link/20251114062817.852698-1-junjie.cao@intel.com Fixes: bb76dc09ddfc ("input: ti_am33x_tsc: Order of TSC wires, made configurable") Cc: stable@vger.kernel.org Signed-off-by: Dmitry Torokhov Signed-off-by: Greg Kroah-Hartman --- drivers/input/touchscreen/ti_am335x_tsc.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/input/touchscreen/ti_am335x_tsc.c b/drivers/input/touchscreen/ti_am335x_tsc.c index 93d659ff90aa94..73980142f492b8 100644 --- a/drivers/input/touchscreen/ti_am335x_tsc.c +++ b/drivers/input/touchscreen/ti_am335x_tsc.c @@ -85,7 +85,7 @@ static int titsc_config_wires(struct titsc *ts_dev) wire_order[i] = ts_dev->config_inp[i] & 0x0F; if (WARN_ON(analog_line[i] > 7)) return -EINVAL; - if (WARN_ON(wire_order[i] > ARRAY_SIZE(config_pins))) + if (WARN_ON(wire_order[i] >= ARRAY_SIZE(config_pins))) return -EINVAL; } From cffc4e29b1e2d44ab094cf142d7c461ff09b9104 Mon Sep 17 00:00:00 2001 From: Minseong Kim Date: Fri, 12 Dec 2025 00:29:23 -0800 Subject: [PATCH 1456/3902] Input: lkkbd - disable pending work before freeing device commit e58c88f0cb2d8ed89de78f6f17409d29cfab6c5c upstream. lkkbd_interrupt() schedules lk->tq via schedule_work(), and the work handler lkkbd_reinit() dereferences the lkkbd structure and its serio/input_dev fields. lkkbd_disconnect() and error paths in lkkbd_connect() free the lkkbd structure without preventing the reinit work from being queued again until serio_close() returns. This can allow the work handler to run after the structure has been freed, leading to a potential use-after-free. Use disable_work_sync() instead of cancel_work_sync() to ensure the reinit work cannot be re-queued, and call it both in lkkbd_disconnect() and in lkkbd_connect() error paths after serio_open(). Signed-off-by: Minseong Kim Cc: stable@vger.kernel.org Link: https://patch.msgid.link/20251212052314.16139-1-ii4gsp@gmail.com Signed-off-by: Dmitry Torokhov Signed-off-by: Greg Kroah-Hartman --- drivers/input/keyboard/lkkbd.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/drivers/input/keyboard/lkkbd.c b/drivers/input/keyboard/lkkbd.c index c035216dd27c12..2f130f819363c6 100644 --- a/drivers/input/keyboard/lkkbd.c +++ b/drivers/input/keyboard/lkkbd.c @@ -670,7 +670,8 @@ static int lkkbd_connect(struct serio *serio, struct serio_driver *drv) return 0; - fail3: serio_close(serio); + fail3: disable_work_sync(&lk->tq); + serio_close(serio); fail2: serio_set_drvdata(serio, NULL); fail1: input_free_device(input_dev); kfree(lk); @@ -684,6 +685,8 @@ static void lkkbd_disconnect(struct serio *serio) { struct lkkbd *lk = serio_get_drvdata(serio); + disable_work_sync(&lk->tq); + input_get_device(lk->dev); input_unregister_device(lk->dev); serio_close(serio); From a9c115e017b2c633d25bdfe6709dda6fc36f08c2 Mon Sep 17 00:00:00 2001 From: Duoming Zhou Date: Wed, 17 Dec 2025 11:00:17 +0800 Subject: [PATCH 1457/3902] Input: alps - fix use-after-free bugs caused by dev3_register_work commit bf40644ef8c8a288742fa45580897ed0e0289474 upstream. The dev3_register_work delayed work item is initialized within alps_reconnect() and scheduled upon receipt of the first bare PS/2 packet from an external PS/2 device connected to the ALPS touchpad. During device detachment, the original implementation calls flush_workqueue() in psmouse_disconnect() to ensure completion of dev3_register_work. However, the flush_workqueue() in psmouse_disconnect() only blocks and waits for work items that were already queued to the workqueue prior to its invocation. Any work items submitted after flush_workqueue() is called are not included in the set of tasks that the flush operation awaits. This means that after flush_workqueue() has finished executing, the dev3_register_work could still be scheduled. Although the psmouse state is set to PSMOUSE_CMD_MODE in psmouse_disconnect(), the scheduling of dev3_register_work remains unaffected. The race condition can occur as follows: CPU 0 (cleanup path) | CPU 1 (delayed work) psmouse_disconnect() | psmouse_set_state() | flush_workqueue() | alps_report_bare_ps2_packet() alps_disconnect() | psmouse_queue_work() kfree(priv); // FREE | alps_register_bare_ps2_mouse() | priv = container_of(work...); // USE | priv->dev3 // USE Add disable_delayed_work_sync() in alps_disconnect() to ensure that dev3_register_work is properly canceled and prevented from executing after the alps_data structure has been deallocated. This bug is identified by static analysis. Fixes: 04aae283ba6a ("Input: ALPS - do not mix trackstick and external PS/2 mouse data") Cc: stable@kernel.org Signed-off-by: Duoming Zhou Link: https://patch.msgid.link/b57b0a9ccca51a3f06be141bfc02b9ffe69d1845.1765939397.git.duoming@zju.edu.cn Signed-off-by: Dmitry Torokhov Signed-off-by: Greg Kroah-Hartman --- drivers/input/mouse/alps.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/input/mouse/alps.c b/drivers/input/mouse/alps.c index d0cb9fb9482185..df8953a5196e1b 100644 --- a/drivers/input/mouse/alps.c +++ b/drivers/input/mouse/alps.c @@ -2975,6 +2975,7 @@ static void alps_disconnect(struct psmouse *psmouse) psmouse_reset(psmouse); timer_shutdown_sync(&priv->timer); + disable_delayed_work_sync(&priv->dev3_register_work); if (priv->dev2) input_unregister_device(priv->dev2); if (!IS_ERR_OR_NULL(priv->dev3)) From 8722085fd23b72cce302fe072648260ca0bb0e20 Mon Sep 17 00:00:00 2001 From: Christoffer Sandberg Date: Mon, 24 Nov 2025 21:31:34 +0100 Subject: [PATCH 1458/3902] Input: i8042 - add TUXEDO InfinityBook Max Gen10 AMD to i8042 quirk table commit aed3716db7fff74919cc5775ca3a80c8bb246489 upstream. The device occasionally wakes up from suspend with missing input on the internal keyboard and the following suspend attempt results in an instant wake-up. The quirks fix both issues for this device. Signed-off-by: Christoffer Sandberg Signed-off-by: Werner Sembach Cc: stable@vger.kernel.org Link: https://patch.msgid.link/20251124203336.64072-1-wse@tuxedocomputers.com Signed-off-by: Dmitry Torokhov Signed-off-by: Greg Kroah-Hartman --- drivers/input/serio/i8042-acpipnpio.h | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/drivers/input/serio/i8042-acpipnpio.h b/drivers/input/serio/i8042-acpipnpio.h index 1caa6c4ca435c7..654771275ce878 100644 --- a/drivers/input/serio/i8042-acpipnpio.h +++ b/drivers/input/serio/i8042-acpipnpio.h @@ -1169,6 +1169,13 @@ static const struct dmi_system_id i8042_dmi_quirk_table[] __initconst = { .driver_data = (void *)(SERIO_QUIRK_NOMUX | SERIO_QUIRK_RESET_ALWAYS | SERIO_QUIRK_NOLOOP | SERIO_QUIRK_NOPNP) }, + { + .matches = { + DMI_MATCH(DMI_BOARD_NAME, "X5KK45xS_X5SP45xS"), + }, + .driver_data = (void *)(SERIO_QUIRK_NOMUX | SERIO_QUIRK_RESET_ALWAYS | + SERIO_QUIRK_NOLOOP | SERIO_QUIRK_NOPNP) + }, /* * A lot of modern Clevo barebones have touchpad and/or keyboard issues * after suspend fixable with the forcenorestore quirk. From da852f46c76e4929c8b6559aa2489284291d309b Mon Sep 17 00:00:00 2001 From: Christoph Hellwig Date: Mon, 10 Nov 2025 14:22:53 +0100 Subject: [PATCH 1459/3902] xfs: don't leak a locked dquot when xfs_dquot_attach_buf fails commit 204c8f77e8d4a3006f8abe40331f221a597ce608 upstream. xfs_qm_quotacheck_dqadjust acquired the dquot through xfs_qm_dqget, which means it owns a reference and holds q_qlock. Both need to be dropped on an error exit. Cc: # v6.13 Fixes: ca378189fdfa ("xfs: convert quotacheck to attach dquot buffers") Reported-by: kernel test robot Reported-by: Dan Carpenter Signed-off-by: Christoph Hellwig Reviewed-by: Darrick J. Wong Signed-off-by: Carlos Maiolino Signed-off-by: Greg Kroah-Hartman --- fs/xfs/xfs_qm.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/fs/xfs/xfs_qm.c b/fs/xfs/xfs_qm.c index 23ba84ec919a4d..18a19947bbdbac 100644 --- a/fs/xfs/xfs_qm.c +++ b/fs/xfs/xfs_qm.c @@ -1318,7 +1318,7 @@ xfs_qm_quotacheck_dqadjust( error = xfs_dquot_attach_buf(NULL, dqp); if (error) - return error; + goto out_unlock; trace_xfs_dqadjust(dqp); @@ -1348,8 +1348,9 @@ xfs_qm_quotacheck_dqadjust( } dqp->q_flags |= XFS_DQFLAG_DIRTY; +out_unlock: xfs_qm_dqput(dqp); - return 0; + return error; } /* From bb958a1320b533077c46aa041a6326b93fc5cb82 Mon Sep 17 00:00:00 2001 From: Marc Kleine-Budde Date: Mon, 1 Dec 2025 19:26:38 +0100 Subject: [PATCH 1460/3902] can: gs_usb: gs_can_open(): fix error handling commit 3e54d3b4a8437b6783d4145c86962a2aa51022f3 upstream. Commit 2603be9e8167 ("can: gs_usb: gs_can_open(): improve error handling") added missing error handling to the gs_can_open() function. The driver uses 2 USB anchors to track the allocated URBs: the TX URBs in struct gs_can::tx_submitted for each netdev and the RX URBs in struct gs_usb::rx_submitted for the USB device. gs_can_open() allocates the RX URBs, while TX URBs are allocated during gs_can_start_xmit(). The cleanup in gs_can_open() kills all anchored dev->tx_submitted URBs (which is not necessary since the netdev is not yet registered), but misses the parent->rx_submitted URBs. Fix the problem by killing the rx_submitted instead of the tx_submitted. Fixes: 2603be9e8167 ("can: gs_usb: gs_can_open(): improve error handling") Cc: stable@vger.kernel.org Link: https://patch.msgid.link/20251210-gs_usb-fix-error-handling-v1-1-d6a5a03f10bb@pengutronix.de Signed-off-by: Marc Kleine-Budde Signed-off-by: Greg Kroah-Hartman --- drivers/net/can/usb/gs_usb.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/net/can/usb/gs_usb.c b/drivers/net/can/usb/gs_usb.c index 8d8a610f914411..db6885bcba28dd 100644 --- a/drivers/net/can/usb/gs_usb.c +++ b/drivers/net/can/usb/gs_usb.c @@ -1074,7 +1074,7 @@ static int gs_can_open(struct net_device *netdev) usb_free_urb(urb); out_usb_kill_anchored_urbs: if (!parent->active_channels) { - usb_kill_anchored_urbs(&dev->tx_submitted); + usb_kill_anchored_urbs(&parent->rx_submitted); if (dev->feature & GS_CAN_FEATURE_HW_TIMESTAMP) gs_usb_timestamp_stop(parent); From 4a4193d9c6dde6e8c69b0d37828493ea449b8988 Mon Sep 17 00:00:00 2001 From: Kartik Rajput Date: Wed, 8 Oct 2025 16:46:18 +0530 Subject: [PATCH 1461/3902] soc/tegra: fuse: Do not register SoC device on ACPI boot commit c87f820bc4748fdd4d50969e8930cd88d1b61582 upstream. On Tegra platforms using ACPI, the SMCCC driver already registers the SoC device. This makes the registration performed by the Tegra fuse driver redundant. When booted via ACPI, skip registering the SoC device and suppress printing SKU information from the Tegra fuse driver, as this information is already provided by the SMCCC driver. Fixes: 972167c69080 ("soc/tegra: fuse: Add ACPI support for Tegra194 and Tegra234") Cc: stable@vger.kernel.org Signed-off-by: Kartik Rajput Signed-off-by: Thierry Reding Signed-off-by: Greg Kroah-Hartman --- drivers/soc/tegra/fuse/fuse-tegra.c | 2 -- 1 file changed, 2 deletions(-) diff --git a/drivers/soc/tegra/fuse/fuse-tegra.c b/drivers/soc/tegra/fuse/fuse-tegra.c index d276672838465c..74d2fedea71ca6 100644 --- a/drivers/soc/tegra/fuse/fuse-tegra.c +++ b/drivers/soc/tegra/fuse/fuse-tegra.c @@ -182,8 +182,6 @@ static int tegra_fuse_probe(struct platform_device *pdev) } fuse->soc->init(fuse); - tegra_fuse_print_sku_info(&tegra_sku_info); - tegra_soc_device_register(); err = tegra_fuse_add_lookups(fuse); if (err) From 76033cf8c2e488c191a64eab11c4858b6d83685d Mon Sep 17 00:00:00 2001 From: Yongxin Liu Date: Fri, 28 Nov 2025 18:24:38 +0800 Subject: [PATCH 1462/3902] platform/x86: intel_pmc_ipc: fix ACPI buffer memory leak MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit commit 611cf41ef6ac8301d23daadd8e78b013db0c5071 upstream. The intel_pmc_ipc() function uses ACPI_ALLOCATE_BUFFER to allocate memory for the ACPI evaluation result but never frees it, causing a 192-byte memory leak on each call. This leak is triggered during network interface initialization when the stmmac driver calls intel_mac_finish() -> intel_pmc_ipc(). unreferenced object 0xffff96a848d6ea80 (size 192): comm "dhcpcd", pid 541, jiffies 4294684345 hex dump (first 32 bytes): 04 00 00 00 05 00 00 00 98 ea d6 48 a8 96 ff ff ...........H.... 00 00 00 00 00 00 00 00 01 00 00 00 00 00 00 00 ................ backtrace (crc b1564374): kmemleak_alloc+0x2d/0x40 __kmalloc_noprof+0x2fa/0x730 acpi_ut_initialize_buffer+0x83/0xc0 acpi_evaluate_object+0x29a/0x2f0 intel_pmc_ipc+0xfd/0x170 intel_mac_finish+0x168/0x230 stmmac_mac_finish+0x3d/0x50 phylink_major_config+0x22b/0x5b0 phylink_mac_initial_config.constprop.0+0xf1/0x1b0 phylink_start+0x8e/0x210 __stmmac_open+0x12c/0x2b0 stmmac_open+0x23c/0x380 __dev_open+0x11d/0x2c0 __dev_change_flags+0x1d2/0x250 netif_change_flags+0x2b/0x70 dev_change_flags+0x40/0xb0 Add __free(kfree) for ACPI object to properly release the allocated buffer. Cc: stable@vger.kernel.org Fixes: 7e2f7e25f6ff ("arch: x86: add IPC mailbox accessor function and add SoC register access") Signed-off-by: Yongxin Liu Link: https://patch.msgid.link/20251128102437.3412891-2-yongxin.liu@windriver.com Reviewed-by: Ilpo Järvinen Signed-off-by: Ilpo Järvinen Signed-off-by: Greg Kroah-Hartman --- include/linux/platform_data/x86/intel_pmc_ipc.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/include/linux/platform_data/x86/intel_pmc_ipc.h b/include/linux/platform_data/x86/intel_pmc_ipc.h index 1d34435b70016a..85ea381e4a2752 100644 --- a/include/linux/platform_data/x86/intel_pmc_ipc.h +++ b/include/linux/platform_data/x86/intel_pmc_ipc.h @@ -9,6 +9,7 @@ #ifndef INTEL_PMC_IPC_H #define INTEL_PMC_IPC_H #include +#include #define IPC_SOC_REGISTER_ACCESS 0xAA #define IPC_SOC_SUB_CMD_READ 0x00 @@ -48,7 +49,6 @@ static inline int intel_pmc_ipc(struct pmc_ipc_cmd *ipc_cmd, struct pmc_ipc_rbuf {.type = ACPI_TYPE_INTEGER,}, }; struct acpi_object_list arg_list = { PMC_IPCS_PARAM_COUNT, params }; - union acpi_object *obj; int status; if (!ipc_cmd || !rbuf) @@ -72,7 +72,7 @@ static inline int intel_pmc_ipc(struct pmc_ipc_cmd *ipc_cmd, struct pmc_ipc_rbuf if (ACPI_FAILURE(status)) return -ENODEV; - obj = buffer.pointer; + union acpi_object *obj __free(kfree) = buffer.pointer; if (obj && obj->type == ACPI_TYPE_PACKAGE && obj->package.count == VALID_IPC_RESPONSE) { From 4cb75cd4eeb4bac9d833272832592dac6bdc8626 Mon Sep 17 00:00:00 2001 From: Pengjie Zhang Date: Wed, 10 Dec 2025 21:26:34 +0800 Subject: [PATCH 1463/3902] ACPI: PCC: Fix race condition by removing static qualifier commit f103fa127c93016bcd89b05d8e11dc1a84f6990d upstream. Local variable 'ret' in acpi_pcc_address_space_setup() is currently declared as 'static'. This can lead to race conditions in a multithreaded environment. Remove the 'static' qualifier to ensure that 'ret' will be allocated directly on the stack as a local variable. Fixes: a10b1c99e2dc ("ACPI: PCC: Setup PCC Opregion handler only if platform interrupt is available") Signed-off-by: Pengjie Zhang Reviewed-by: Sudeep Holla Acked-by: lihuisong@huawei.com Cc: 6.2+ # 6.2+ [ rjw: Changelog edits ] Link: https://patch.msgid.link/20251210132634.2050033-1-zhangpengjie2@huawei.com Signed-off-by: Rafael J. Wysocki Signed-off-by: Greg Kroah-Hartman --- drivers/acpi/acpi_pcc.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/acpi/acpi_pcc.c b/drivers/acpi/acpi_pcc.c index 97064e943768ad..e3f302b9dee5f5 100644 --- a/drivers/acpi/acpi_pcc.c +++ b/drivers/acpi/acpi_pcc.c @@ -52,7 +52,7 @@ acpi_pcc_address_space_setup(acpi_handle region_handle, u32 function, struct pcc_data *data; struct acpi_pcc_info *ctx = handler_context; struct pcc_mbox_chan *pcc_chan; - static acpi_status ret; + acpi_status ret; data = kzalloc(sizeof(*data), GFP_KERNEL); if (!data) From 7e086c97dd2b746cbbfb813d73ba91d6c270bdb4 Mon Sep 17 00:00:00 2001 From: Pengjie Zhang Date: Wed, 10 Dec 2025 21:22:27 +0800 Subject: [PATCH 1464/3902] ACPI: CPPC: Fix missing PCC check for guaranteed_perf commit 6ea3a44cef28add2d93b1ef119d84886cb1e3c9b upstream. The current implementation overlooks the 'guaranteed_perf' register in this check. If the Guaranteed Performance register is located in the PCC subspace, the function currently attempts to read it without acquiring the lock and without sending the CMD_READ doorbell to the firmware. This can result in reading stale data. Fixes: 29523f095397 ("ACPI / CPPC: Add support for guaranteed performance") Signed-off-by: Pengjie Zhang Cc: 4.20+ # 4.20+ [ rjw: Subject and changelog edits ] Link: https://patch.msgid.link/20251210132227.1988380-1-zhangpengjie2@huawei.com Signed-off-by: Rafael J. Wysocki Signed-off-by: Greg Kroah-Hartman --- drivers/acpi/cppc_acpi.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/drivers/acpi/cppc_acpi.c b/drivers/acpi/cppc_acpi.c index 3bdeeee3414e68..e66e20d1f31b76 100644 --- a/drivers/acpi/cppc_acpi.c +++ b/drivers/acpi/cppc_acpi.c @@ -1366,7 +1366,8 @@ int cppc_get_perf_caps(int cpunum, struct cppc_perf_caps *perf_caps) /* Are any of the regs PCC ?*/ if (CPC_IN_PCC(highest_reg) || CPC_IN_PCC(lowest_reg) || CPC_IN_PCC(lowest_non_linear_reg) || CPC_IN_PCC(nominal_reg) || - CPC_IN_PCC(low_freq_reg) || CPC_IN_PCC(nom_freq_reg)) { + CPC_IN_PCC(low_freq_reg) || CPC_IN_PCC(nom_freq_reg) || + CPC_IN_PCC(guaranteed_reg)) { if (pcc_ss_id < 0) { pr_debug("Invalid pcc_ss_id\n"); return -ENODEV; From be0b613198e6bfa104ad520397cab82ad3ec1771 Mon Sep 17 00:00:00 2001 From: Christophe Leroy Date: Thu, 20 Nov 2025 09:34:49 +0100 Subject: [PATCH 1465/3902] spi: fsl-cpm: Check length parity before switching to 16 bit mode commit 1417927df8049a0194933861e9b098669a95c762 upstream. Commit fc96ec826bce ("spi: fsl-cpm: Use 16 bit mode for large transfers with even size") failed to make sure that the size is really even before switching to 16 bit mode. Until recently the problem went unnoticed because kernfs uses a pre-allocated bounce buffer of size PAGE_SIZE for reading EEPROM. But commit 8ad6249c51d0 ("eeprom: at25: convert to spi-mem API") introduced an additional dynamically allocated bounce buffer whose size is exactly the size of the transfer, leading to a buffer overrun in the fsl-cpm driver when that size is odd. Add the missing length parity verification and remain in 8 bit mode when the length is not even. Fixes: fc96ec826bce ("spi: fsl-cpm: Use 16 bit mode for large transfers with even size") Cc: stable@vger.kernel.org Closes: https://lore.kernel.org/all/638496dd-ec60-4e53-bad7-eb657f67d580@csgroup.eu/ Signed-off-by: Christophe Leroy Reviewed-by: Sverdlin Alexander Link: https://patch.msgid.link/3c4d81c3923c93f95ec56702a454744a4bad3cfc.1763627618.git.christophe.leroy@csgroup.eu Signed-off-by: Mark Brown Signed-off-by: Greg Kroah-Hartman --- drivers/spi/spi-fsl-spi.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/spi/spi-fsl-spi.c b/drivers/spi/spi-fsl-spi.c index 2f2082652a1a28..481a7b28aacd3d 100644 --- a/drivers/spi/spi-fsl-spi.c +++ b/drivers/spi/spi-fsl-spi.c @@ -335,7 +335,7 @@ static int fsl_spi_prepare_message(struct spi_controller *ctlr, if (t->bits_per_word == 16 || t->bits_per_word == 32) t->bits_per_word = 8; /* pretend its 8 bits */ if (t->bits_per_word == 8 && t->len >= 256 && - (mpc8xxx_spi->flags & SPI_CPM1)) + !(t->len & 1) && (mpc8xxx_spi->flags & SPI_CPM1)) t->bits_per_word = 16; } } From 87e74671ddbcf023345d9978d8c67bbfe48e2956 Mon Sep 17 00:00:00 2001 From: Jared Kangas Date: Fri, 12 Dec 2025 07:03:17 -0800 Subject: [PATCH 1466/3902] mmc: sdhci-esdhc-imx: add alternate ARCH_S32 dependency to Kconfig commit d3ecb12e2e04ce53c95f933c462f2d8b150b965b upstream. MMC_SDHCI_ESDHC_IMX requires ARCH_MXC despite also being used on ARCH_S32, which results in unmet dependencies when compiling strictly for ARCH_S32. Resolve this by adding ARCH_S32 as an alternative to ARCH_MXC in the driver's dependencies. Fixes: 5c4f00627c9a ("mmc: sdhci-esdhc-imx: add NXP S32G2 support") Cc: stable@bvger.kernel.org Signed-off-by: Jared Kangas Reviewed-by: Haibo Chen Signed-off-by: Ulf Hansson Signed-off-by: Greg Kroah-Hartman --- drivers/mmc/host/Kconfig | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/mmc/host/Kconfig b/drivers/mmc/host/Kconfig index 10d0ef58ef4931..c94ae4794545de 100644 --- a/drivers/mmc/host/Kconfig +++ b/drivers/mmc/host/Kconfig @@ -315,14 +315,14 @@ config MMC_SDHCI_ESDHC_MCF config MMC_SDHCI_ESDHC_IMX tristate "SDHCI support for the Freescale eSDHC/uSDHC i.MX controller" - depends on ARCH_MXC || COMPILE_TEST + depends on ARCH_MXC || ARCH_S32 || COMPILE_TEST depends on MMC_SDHCI_PLTFM depends on OF select MMC_SDHCI_IO_ACCESSORS select MMC_CQHCI help This selects the Freescale eSDHC/uSDHC controller support - found on i.MX25, i.MX35 i.MX5x and i.MX6x. + found on i.MX25, i.MX35, i.MX5x, i.MX6x, and S32G. If you have a controller with this interface, say Y or M here. From 2ebf2e631d53b42c53bea61569938e4665da1710 Mon Sep 17 00:00:00 2001 From: Sai Krishna Potthuri Date: Fri, 12 Dec 2025 12:05:09 +0530 Subject: [PATCH 1467/3902] mmc: sdhci-of-arasan: Increase CD stable timeout to 2 seconds commit a9c4c9085ec8ce3ce01be21b75184789e74f5f19 upstream. On Xilinx/AMD platforms, the CD stable bit take slightly longer than one second(about an additional 100ms) to assert after a host controller reset. Although no functional failure observed with the existing one second delay but to ensure reliable initialization, increase the CD stable timeout to 2 seconds. Fixes: e251709aaddb ("mmc: sdhci-of-arasan: Ensure CD logic stabilization before power-up") Cc: stable@vger.kernel.org Signed-off-by: Sai Krishna Potthuri Acked-by: Adrian Hunter Signed-off-by: Ulf Hansson Signed-off-by: Greg Kroah-Hartman --- drivers/mmc/host/sdhci-of-arasan.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/mmc/host/sdhci-of-arasan.c b/drivers/mmc/host/sdhci-of-arasan.c index c6f09b53325d2d..0a4b4696bc01cd 100644 --- a/drivers/mmc/host/sdhci-of-arasan.c +++ b/drivers/mmc/host/sdhci-of-arasan.c @@ -99,7 +99,7 @@ #define HIWORD_UPDATE(val, mask, shift) \ ((val) << (shift) | (mask) << ((shift) + 16)) -#define CD_STABLE_TIMEOUT_US 1000000 +#define CD_STABLE_TIMEOUT_US 2000000 #define CD_STABLE_MAX_SLEEP_US 10 /** From e1be561f6b14cfd8dc4f17bfb9797377526e18ae Mon Sep 17 00:00:00 2001 From: Andrew Jeffery Date: Thu, 11 Dec 2025 17:45:48 +0900 Subject: [PATCH 1468/3902] dt-bindings: mmc: sdhci-of-aspeed: Switch ref to sdhci-common.yaml commit ed724ea1b82a800af4704311cb89e5ef1b4ea7ac upstream. Enable use of common SDHCI-related properties such as sdhci-caps-mask as found in the AST2600 EVB DTS. Cc: stable@vger.kernel.org # v6.2+ Signed-off-by: Andrew Jeffery Signed-off-by: Ulf Hansson Signed-off-by: Greg Kroah-Hartman --- Documentation/devicetree/bindings/mmc/aspeed,sdhci.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Documentation/devicetree/bindings/mmc/aspeed,sdhci.yaml b/Documentation/devicetree/bindings/mmc/aspeed,sdhci.yaml index 9fce8cd7b0b62b..d24950ccea9522 100644 --- a/Documentation/devicetree/bindings/mmc/aspeed,sdhci.yaml +++ b/Documentation/devicetree/bindings/mmc/aspeed,sdhci.yaml @@ -41,7 +41,7 @@ properties: patternProperties: "^sdhci@[0-9a-f]+$": type: object - $ref: mmc-controller.yaml + $ref: sdhci-common.yaml unevaluatedProperties: false properties: From 6220d38a08f8837575cd8f830928b49a3a5a5095 Mon Sep 17 00:00:00 2001 From: Shaurya Rane Date: Sat, 29 Nov 2025 15:07:18 +0530 Subject: [PATCH 1469/3902] net/hsr: fix NULL pointer dereference in prp_get_untagged_frame() commit 188e0fa5a679570ea35474575e724d8211423d17 upstream. prp_get_untagged_frame() calls __pskb_copy() to create frame->skb_std but doesn't check if the allocation failed. If __pskb_copy() returns NULL, skb_clone() is called with a NULL pointer, causing a crash: Oops: general protection fault, probably for non-canonical address 0xdffffc000000000f: 0000 [#1] SMP KASAN NOPTI KASAN: null-ptr-deref in range [0x0000000000000078-0x000000000000007f] CPU: 0 UID: 0 PID: 5625 Comm: syz.1.18 Not tainted syzkaller #0 PREEMPT(full) Hardware name: QEMU Standard PC (Q35 + ICH9, 2009), BIOS 1.16.3-debian-1.16.3-2~bpo12+1 04/01/2014 RIP: 0010:skb_clone+0xd7/0x3a0 net/core/skbuff.c:2041 Code: 03 42 80 3c 20 00 74 08 4c 89 f7 e8 23 29 05 f9 49 83 3e 00 0f 85 a0 01 00 00 e8 94 dd 9d f8 48 8d 6b 7e 49 89 ee 49 c1 ee 03 <43> 0f b6 04 26 84 c0 0f 85 d1 01 00 00 44 0f b6 7d 00 41 83 e7 0c RSP: 0018:ffffc9000d00f200 EFLAGS: 00010207 RAX: ffffffff892235a1 RBX: 0000000000000000 RCX: ffff88803372a480 RDX: 0000000000000000 RSI: 0000000000000820 RDI: 0000000000000000 RBP: 000000000000007e R08: ffffffff8f7d0f77 R09: 1ffffffff1efa1ee R10: dffffc0000000000 R11: fffffbfff1efa1ef R12: dffffc0000000000 R13: 0000000000000820 R14: 000000000000000f R15: ffff88805144cc00 FS: 0000555557f6d500(0000) GS:ffff88808d72f000(0000) knlGS:0000000000000000 CS: 0010 DS: 0000 ES: 0000 CR0: 0000000080050033 CR2: 0000555581d35808 CR3: 000000005040e000 CR4: 0000000000352ef0 Call Trace: hsr_forward_do net/hsr/hsr_forward.c:-1 [inline] hsr_forward_skb+0x1013/0x2860 net/hsr/hsr_forward.c:741 hsr_handle_frame+0x6ce/0xa70 net/hsr/hsr_slave.c:84 __netif_receive_skb_core+0x10b9/0x4380 net/core/dev.c:5966 __netif_receive_skb_one_core net/core/dev.c:6077 [inline] __netif_receive_skb+0x72/0x380 net/core/dev.c:6192 netif_receive_skb_internal net/core/dev.c:6278 [inline] netif_receive_skb+0x1cb/0x790 net/core/dev.c:6337 tun_rx_batched+0x1b9/0x730 drivers/net/tun.c:1485 tun_get_user+0x2b65/0x3e90 drivers/net/tun.c:1953 tun_chr_write_iter+0x113/0x200 drivers/net/tun.c:1999 new_sync_write fs/read_write.c:593 [inline] vfs_write+0x5c9/0xb30 fs/read_write.c:686 ksys_write+0x145/0x250 fs/read_write.c:738 do_syscall_x64 arch/x86/entry/syscall_64.c:63 [inline] do_syscall_64+0xfa/0xfa0 arch/x86/entry/syscall_64.c:94 entry_SYSCALL_64_after_hwframe+0x77/0x7f RIP: 0033:0x7f0449f8e1ff Code: 89 54 24 18 48 89 74 24 10 89 7c 24 08 e8 f9 92 02 00 48 8b 54 24 18 48 8b 74 24 10 41 89 c0 8b 7c 24 08 b8 01 00 00 00 0f 05 <48> 3d 00 f0 ff ff 77 31 44 89 c7 48 89 44 24 08 e8 4c 93 02 00 48 RSP: 002b:00007ffd7ad94c90 EFLAGS: 00000293 ORIG_RAX: 0000000000000001 RAX: ffffffffffffffda RBX: 00007f044a1e5fa0 RCX: 00007f0449f8e1ff RDX: 000000000000003e RSI: 0000200000000500 RDI: 00000000000000c8 RBP: 00007ffd7ad94d20 R08: 0000000000000000 R09: 0000000000000000 R10: 000000000000003e R11: 0000000000000293 R12: 0000000000000001 R13: 00007f044a1e5fa0 R14: 00007f044a1e5fa0 R15: 0000000000000003 Add a NULL check immediately after __pskb_copy() to handle allocation failures gracefully. Reported-by: syzbot+2fa344348a579b779e05@syzkaller.appspotmail.com Closes: https://syzkaller.appspot.com/bug?extid=2fa344348a579b779e05 Fixes: f266a683a480 ("net/hsr: Better frame dispatch") Cc: stable@vger.kernel.org Signed-off-by: Shaurya Rane Reviewed-by: Felix Maurer Tested-by: Felix Maurer Link: https://patch.msgid.link/20251129093718.25320-1-ssrane_b23@ee.vjti.ac.in Signed-off-by: Paolo Abeni Signed-off-by: Greg Kroah-Hartman --- net/hsr/hsr_forward.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/net/hsr/hsr_forward.c b/net/hsr/hsr_forward.c index 339f0d22021294..aefc9b6936ba0c 100644 --- a/net/hsr/hsr_forward.c +++ b/net/hsr/hsr_forward.c @@ -205,6 +205,8 @@ struct sk_buff *prp_get_untagged_frame(struct hsr_frame_info *frame, __pskb_copy(frame->skb_prp, skb_headroom(frame->skb_prp), GFP_ATOMIC); + if (!frame->skb_std) + return NULL; } else { /* Unexpected */ WARN_ONCE(1, "%s:%d: Unexpected frame received (port_src %s)\n", From 34c0e95524d61b8c0a10c828470d0810b81bb7b6 Mon Sep 17 00:00:00 2001 From: Peter Zijlstra Date: Thu, 18 Dec 2025 11:47:38 +0100 Subject: [PATCH 1470/3902] x86/bug: Fix old GCC compile fails commit c56a12c71ad38f381105f6e5036dede64ad2dfee upstream. For some mysterious reasons the GCC 8 and 9 preprocessor manages to sporadically fumble _ASM_BYTES(0x0f, 0x0b): $ grep ".byte[ ]*0x0f" defconfig-build/drivers/net/wireless/realtek/rtlwifi/base.s 1: .byte0x0f,0x0b ; 1: .byte 0x0f,0x0b ; which makes the assembler upset and all that. While there are more _ASM_BYTES() users (notably the NOP instructions), those don't seem affected. Therefore replace the offending ASM_UD2 with one using the ud2 mnemonic. Reported-by: Jean Delvare Suggested-by: Uros Bizjak Fixes: 85a2d4a890dc ("x86,ibt: Use UDB instead of 0xEA") Cc: stable@kernel.org Signed-off-by: Peter Zijlstra (Intel) Link: https://patch.msgid.link/20251218104659.GT3911114@noisy.programming.kicks-ass.net Signed-off-by: Greg Kroah-Hartman --- arch/x86/include/asm/bug.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/arch/x86/include/asm/bug.h b/arch/x86/include/asm/bug.h index 880ca15073ed12..99017bb1031e91 100644 --- a/arch/x86/include/asm/bug.h +++ b/arch/x86/include/asm/bug.h @@ -10,7 +10,7 @@ /* * Despite that some emulators terminate on UD2, we use it for WARN(). */ -#define ASM_UD2 _ASM_BYTES(0x0f, 0x0b) +#define ASM_UD2 __ASM_FORM(ud2) #define INSN_UD2 0x0b0f #define LEN_UD2 2 From f01e9f891c3791960dedd417a0b6571d13b1a28e Mon Sep 17 00:00:00 2001 From: Thomas Gleixner Date: Tue, 25 Nov 2025 22:50:45 +0100 Subject: [PATCH 1471/3902] x86/msi: Make irq_retrigger() functional for posted MSI commit 0edc78b82bea85e1b2165d8e870a5c3535919695 upstream. Luigi reported that retriggering a posted MSI interrupt does not work correctly. The reason is that the retrigger happens at the vector domain by sending an IPI to the actual vector on the target CPU. That works correctly exactly once because the posted MSI interrupt chip does not issue an EOI as that's only required for the posted MSI notification vector itself. As a consequence the vector becomes stale in the ISR, which not only affects this vector but also any lower priority vector in the affected APIC because the ISR bit is not cleared. Luigi proposed to set the vector in the remap PIR bitmap and raise the posted MSI notification vector. That works, but that still does not cure a related problem: If there is ever a stray interrupt on such a vector, then the related APIC ISR bit becomes stale due to the lack of EOI as described above. Unlikely to happen, but if it happens it's not debuggable at all. So instead of playing games with the PIR, this can be actually solved for both cases by: 1) Keeping track of the posted interrupt vector handler state 2) Implementing a posted MSI specific irq_ack() callback which checks that state. If the posted vector handler is inactive it issues an EOI, otherwise it delegates that to the posted handler. This is correct versus affinity changes and concurrent events on the posted vector as the actual handler invocation is serialized through the interrupt descriptor lock. Fixes: ed1e48ea4370 ("iommu/vt-d: Enable posted mode for device MSIs") Reported-by: Luigi Rizzo Signed-off-by: Thomas Gleixner Tested-by: Luigi Rizzo Cc: stable@vger.kernel.org Link: https://patch.msgid.link/20251125214631.044440658@linutronix.de Closes: https://lore.kernel.org/lkml/20251124104836.3685533-1-lrizzo@google.com Signed-off-by: Greg Kroah-Hartman --- arch/x86/include/asm/irq_remapping.h | 7 +++++++ arch/x86/kernel/irq.c | 23 +++++++++++++++++++++++ drivers/iommu/intel/irq_remapping.c | 8 ++++---- 3 files changed, 34 insertions(+), 4 deletions(-) diff --git a/arch/x86/include/asm/irq_remapping.h b/arch/x86/include/asm/irq_remapping.h index 5a0d42464d4424..4e55d17558465d 100644 --- a/arch/x86/include/asm/irq_remapping.h +++ b/arch/x86/include/asm/irq_remapping.h @@ -87,4 +87,11 @@ static inline void panic_if_irq_remap(const char *msg) } #endif /* CONFIG_IRQ_REMAP */ + +#ifdef CONFIG_X86_POSTED_MSI +void intel_ack_posted_msi_irq(struct irq_data *irqd); +#else +#define intel_ack_posted_msi_irq NULL +#endif + #endif /* __X86_IRQ_REMAPPING_H */ diff --git a/arch/x86/kernel/irq.c b/arch/x86/kernel/irq.c index 10721a12522694..59d38411a1d99f 100644 --- a/arch/x86/kernel/irq.c +++ b/arch/x86/kernel/irq.c @@ -396,6 +396,7 @@ DEFINE_IDTENTRY_SYSVEC_SIMPLE(sysvec_kvm_posted_intr_nested_ipi) /* Posted Interrupt Descriptors for coalesced MSIs to be posted */ DEFINE_PER_CPU_ALIGNED(struct pi_desc, posted_msi_pi_desc); +static DEFINE_PER_CPU_CACHE_HOT(bool, posted_msi_handler_active); void intel_posted_msi_init(void) { @@ -413,6 +414,25 @@ void intel_posted_msi_init(void) this_cpu_write(posted_msi_pi_desc.ndst, destination); } +void intel_ack_posted_msi_irq(struct irq_data *irqd) +{ + irq_move_irq(irqd); + + /* + * Handle the rare case that irq_retrigger() raised the actual + * assigned vector on the target CPU, which means that it was not + * invoked via the posted MSI handler below. In that case APIC EOI + * is required as otherwise the ISR entry becomes stale and lower + * priority interrupts are never going to be delivered after that. + * + * If the posted handler invoked the device interrupt handler then + * the EOI would be premature because it would acknowledge the + * posted vector. + */ + if (unlikely(!__this_cpu_read(posted_msi_handler_active))) + apic_eoi(); +} + static __always_inline bool handle_pending_pir(unsigned long *pir, struct pt_regs *regs) { unsigned long pir_copy[NR_PIR_WORDS]; @@ -445,6 +465,8 @@ DEFINE_IDTENTRY_SYSVEC(sysvec_posted_msi_notification) pid = this_cpu_ptr(&posted_msi_pi_desc); + /* Mark the handler active for intel_ack_posted_msi_irq() */ + __this_cpu_write(posted_msi_handler_active, true); inc_irq_stat(posted_msi_notification_count); irq_enter(); @@ -473,6 +495,7 @@ DEFINE_IDTENTRY_SYSVEC(sysvec_posted_msi_notification) apic_eoi(); irq_exit(); + __this_cpu_write(posted_msi_handler_active, false); set_irq_regs(old_regs); } #endif /* X86_POSTED_MSI */ diff --git a/drivers/iommu/intel/irq_remapping.c b/drivers/iommu/intel/irq_remapping.c index 4f9b01dc91e86f..8bcbfe3d9c7221 100644 --- a/drivers/iommu/intel/irq_remapping.c +++ b/drivers/iommu/intel/irq_remapping.c @@ -1303,17 +1303,17 @@ static struct irq_chip intel_ir_chip = { * irq_enter(); * handle_edge_irq() * irq_chip_ack_parent() - * irq_move_irq(); // No EOI + * intel_ack_posted_msi_irq(); // No EOI * handle_irq_event() * driver_handler() * handle_edge_irq() * irq_chip_ack_parent() - * irq_move_irq(); // No EOI + * intel_ack_posted_msi_irq(); // No EOI * handle_irq_event() * driver_handler() * handle_edge_irq() * irq_chip_ack_parent() - * irq_move_irq(); // No EOI + * intel_ack_posted_msi_irq(); // No EOI * handle_irq_event() * driver_handler() * apic_eoi() @@ -1322,7 +1322,7 @@ static struct irq_chip intel_ir_chip = { */ static struct irq_chip intel_ir_chip_post_msi = { .name = "INTEL-IR-POST", - .irq_ack = irq_move_irq, + .irq_ack = intel_ack_posted_msi_irq, .irq_set_affinity = intel_ir_set_affinity, .irq_compose_msi_msg = intel_ir_compose_msi_msg, .irq_set_vcpu_affinity = intel_ir_set_vcpu_affinity, From 82b6dfc0aa60de28a7a7631adb8eaaddde638dd6 Mon Sep 17 00:00:00 2001 From: Yongxin Liu Date: Wed, 10 Dec 2025 08:02:20 +0800 Subject: [PATCH 1472/3902] x86/fpu: Fix FPU state core dump truncation on CPUs with no extended xfeatures [ Upstream commit c8161e5304abb26e6c0bec6efc947992500fa6c5 ] Zero can be a valid value of num_records. For example, on Intel Atom x6425RE, only x87 and SSE are supported (features 0, 1), and fpu_user_cfg.max_features is 3. The for_each_extended_xfeature() loop only iterates feature 2, which is not enabled, so num_records = 0. This is valid and should not cause core dump failure. The issue is that dump_xsave_layout_desc() returns 0 for both genuine errors (dump_emit() failure) and valid cases (no extended features). Use negative return values for errors and only abort on genuine failures. Fixes: ba386777a30b ("x86/elf: Add a new FPU buffer layout info to x86 core files") Signed-off-by: Yongxin Liu Signed-off-by: Ingo Molnar Link: https://patch.msgid.link/20251210000219.4094353-2-yongxin.liu@windriver.com Signed-off-by: Sasha Levin --- arch/x86/kernel/fpu/xstate.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/arch/x86/kernel/fpu/xstate.c b/arch/x86/kernel/fpu/xstate.c index 28e4fd65c9da71..5f54a207ace46c 100644 --- a/arch/x86/kernel/fpu/xstate.c +++ b/arch/x86/kernel/fpu/xstate.c @@ -1945,7 +1945,7 @@ static int dump_xsave_layout_desc(struct coredump_params *cprm) }; if (!dump_emit(cprm, &xc, sizeof(xc))) - return 0; + return -1; num_records++; } @@ -1983,7 +1983,7 @@ int elf_coredump_extra_notes_write(struct coredump_params *cprm) return 1; num_records = dump_xsave_layout_desc(cprm); - if (!num_records) + if (num_records < 0) return 1; /* Total size should be equal to the number of records */ From 425672fdb7b6f9897be6a7b1a077da1f9f2ecbe9 Mon Sep 17 00:00:00 2001 From: Tal Zussman Date: Fri, 12 Dec 2025 04:08:07 -0500 Subject: [PATCH 1473/3902] x86/mm/tlb/trace: Export the TLB_REMOTE_WRONG_CPU enum in [ Upstream commit 8b62e64e6d30fa047b3aefb1a36e1f80c8acb3d2 ] When the TLB_REMOTE_WRONG_CPU enum was introduced for the tlb_flush tracepoint, the enum was not exported to user-space. Add it to the appropriate macro definition to enable parsing by userspace tools, as per: Link: https://lore.kernel.org/all/20150403013802.220157513@goodmis.org [ mingo: Capitalize IPI, etc. ] Fixes: 2815a56e4b72 ("x86/mm/tlb: Add tracepoint for TLB flush IPI to stale CPU") Signed-off-by: Tal Zussman Signed-off-by: Ingo Molnar Reviewed-by: Steven Rostedt (Google) Reviewed-by: David Hildenbrand Reviewed-by: Rik van Riel Link: https://patch.msgid.link/20251212-tlb-trace-fix-v2-1-d322e0ad9b69@columbia.edu Signed-off-by: Sasha Levin --- include/trace/events/tlb.h | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/include/trace/events/tlb.h b/include/trace/events/tlb.h index b4d8e7dc38f880..fb836951168564 100644 --- a/include/trace/events/tlb.h +++ b/include/trace/events/tlb.h @@ -12,8 +12,9 @@ EM( TLB_FLUSH_ON_TASK_SWITCH, "flush on task switch" ) \ EM( TLB_REMOTE_SHOOTDOWN, "remote shootdown" ) \ EM( TLB_LOCAL_SHOOTDOWN, "local shootdown" ) \ - EM( TLB_LOCAL_MM_SHOOTDOWN, "local mm shootdown" ) \ - EMe( TLB_REMOTE_SEND_IPI, "remote ipi send" ) + EM( TLB_LOCAL_MM_SHOOTDOWN, "local MM shootdown" ) \ + EM( TLB_REMOTE_SEND_IPI, "remote IPI send" ) \ + EMe( TLB_REMOTE_WRONG_CPU, "remote wrong CPU" ) /* * First define the enums in TLB_FLUSH_REASON to be exported to userspace From 2068cf6f827650071f32c411045efd76bd40d73b Mon Sep 17 00:00:00 2001 From: Chancel Liu Date: Wed, 10 Dec 2025 15:21:09 +0900 Subject: [PATCH 1474/3902] ASoC: fsl_sai: Constrain sample rates from audio PLLs only in master mode [ Upstream commit 9f4d0899efd9892fc7514c9488270e1bb7dedd2b ] If SAI works in master mode it will generate clocks for external codec from audio PLLs. Thus sample rates should be constrained according to audio PLL clocks. While SAI works in slave mode which means clocks are generated externally then constraints are independent of audio PLLs. Fixes: 4edc98598be4 ("ASoC: fsl_sai: Add sample rate constraint") Signed-off-by: Chancel Liu Link: https://patch.msgid.link/20251210062109.2577735-1-chancel.liu@nxp.com Signed-off-by: Mark Brown Signed-off-by: Sasha Levin --- sound/soc/fsl/fsl_sai.c | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/sound/soc/fsl/fsl_sai.c b/sound/soc/fsl/fsl_sai.c index 72bfc91e21b9b4..86730c2149146e 100644 --- a/sound/soc/fsl/fsl_sai.c +++ b/sound/soc/fsl/fsl_sai.c @@ -917,8 +917,14 @@ static int fsl_sai_startup(struct snd_pcm_substream *substream, tx ? sai->dma_params_tx.maxburst : sai->dma_params_rx.maxburst); - ret = snd_pcm_hw_constraint_list(substream->runtime, 0, - SNDRV_PCM_HW_PARAM_RATE, &sai->constraint_rates); + if (sai->is_consumer_mode[tx]) + ret = snd_pcm_hw_constraint_list(substream->runtime, 0, + SNDRV_PCM_HW_PARAM_RATE, + &fsl_sai_rate_constraints); + else + ret = snd_pcm_hw_constraint_list(substream->runtime, 0, + SNDRV_PCM_HW_PARAM_RATE, + &sai->constraint_rates); return ret; } From 042e9e86d8e170bffd0f8aff7717cee55e33f797 Mon Sep 17 00:00:00 2001 From: Haotian Zhang Date: Mon, 15 Dec 2025 12:26:52 +0800 Subject: [PATCH 1475/3902] ALSA: vxpocket: Fix resource leak in vxpocket_probe error path [ Upstream commit 2a03b40deacbd293ac9aed0f9b11197dad54fe5f ] When vxpocket_config() fails, vxpocket_probe() returns the error code directly without freeing the sound card resources allocated by snd_card_new(), which leads to a memory leak. Add proper error handling to free the sound card and clear the allocation bit when vxpocket_config() fails. Fixes: 15b99ac17295 ("[PATCH] pcmcia: add return value to _config() functions") Signed-off-by: Haotian Zhang Link: https://patch.msgid.link/20251215042652.695-1-vulab@iscas.ac.cn Signed-off-by: Takashi Iwai Signed-off-by: Sasha Levin --- sound/pcmcia/vx/vxpocket.c | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/sound/pcmcia/vx/vxpocket.c b/sound/pcmcia/vx/vxpocket.c index 2e09f2a513a6a0..9a5c9aa8eec4a0 100644 --- a/sound/pcmcia/vx/vxpocket.c +++ b/sound/pcmcia/vx/vxpocket.c @@ -284,7 +284,13 @@ static int vxpocket_probe(struct pcmcia_device *p_dev) vxp->p_dev = p_dev; - return vxpocket_config(p_dev); + err = vxpocket_config(p_dev); + if (err < 0) { + card_alloc &= ~(1 << i); + snd_card_free(card); + return err; + } + return 0; } static void vxpocket_detach(struct pcmcia_device *link) From 499057a7cc6b33fd34a4a0339d48e3b09863ca6c Mon Sep 17 00:00:00 2001 From: Haotian Zhang Date: Mon, 15 Dec 2025 17:04:33 +0800 Subject: [PATCH 1476/3902] ALSA: pcmcia: Fix resource leak in snd_pdacf_probe error path [ Upstream commit 5032347c04ba7ff9ba878f262e075d745c06a2a8 ] When pdacf_config() fails, snd_pdacf_probe() returns the error code directly without freeing the sound card resources allocated by snd_card_new(), which leads to a memory leak. Add proper error handling to free the sound card and clear the card list entry when pdacf_config() fails. Fixes: 15b99ac17295 ("[PATCH] pcmcia: add return value to _config() functions") Suggested-by: Takashi Iwai Signed-off-by: Haotian Zhang Link: https://patch.msgid.link/20251215090433.211-1-vulab@iscas.ac.cn Signed-off-by: Takashi Iwai Signed-off-by: Sasha Levin --- sound/pcmcia/pdaudiocf/pdaudiocf.c | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/sound/pcmcia/pdaudiocf/pdaudiocf.c b/sound/pcmcia/pdaudiocf/pdaudiocf.c index 13419837dfb7ce..a3291e626440e0 100644 --- a/sound/pcmcia/pdaudiocf/pdaudiocf.c +++ b/sound/pcmcia/pdaudiocf/pdaudiocf.c @@ -131,7 +131,13 @@ static int snd_pdacf_probe(struct pcmcia_device *link) link->config_index = 1; link->config_regs = PRESENT_OPTION; - return pdacf_config(link); + err = pdacf_config(link); + if (err < 0) { + card_list[i] = NULL; + snd_card_free(card); + return err; + } + return 0; } From eaa95228b8a56c4880a182c0350d67922b22408f Mon Sep 17 00:00:00 2001 From: Shipei Qu Date: Wed, 17 Dec 2025 10:46:30 +0800 Subject: [PATCH 1477/3902] ALSA: usb-mixer: us16x08: validate meter packet indices [ Upstream commit 5526c1c6ba1d0913c7dfcbbd6fe1744ea7c55f1e ] get_meter_levels_from_urb() parses the 64-byte meter packets sent by the device and fills the per-channel arrays meter_level[], comp_level[] and master_level[] in struct snd_us16x08_meter_store. Currently the function derives the channel index directly from the meter packet (MUB2(meter_urb, s) - 1) and uses it to index those arrays without validating the range. If the packet contains a negative or out-of-range channel number, the driver may write past the end of these arrays. Introduce a local channel variable and validate it before updating the arrays. We reject negative indices, limit meter_level[] and comp_level[] to SND_US16X08_MAX_CHANNELS, and guard master_level[] updates with ARRAY_SIZE(master_level). Fixes: d2bb390a2081 ("ALSA: usb-audio: Tascam US-16x08 DSP mixer quirk") Reported-by: DARKNAVY (@DarkNavyOrg) Closes: https://lore.kernel.org/tencent_21C112743C44C1A2517FF219@qq.com Signed-off-by: Shipei Qu Link: https://patch.msgid.link/20251217024630.59576-1-qu@darknavy.com Signed-off-by: Takashi Iwai Signed-off-by: Sasha Levin --- sound/usb/mixer_us16x08.c | 20 ++++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/sound/usb/mixer_us16x08.c b/sound/usb/mixer_us16x08.c index 1c5712c31f5e20..f9df40730effd1 100644 --- a/sound/usb/mixer_us16x08.c +++ b/sound/usb/mixer_us16x08.c @@ -655,17 +655,25 @@ static void get_meter_levels_from_urb(int s, u8 *meter_urb) { int val = MUC2(meter_urb, s) + (MUC3(meter_urb, s) << 8); + int ch = MUB2(meter_urb, s) - 1; + + if (ch < 0) + return; if (MUA0(meter_urb, s) == 0x61 && MUA1(meter_urb, s) == 0x02 && MUA2(meter_urb, s) == 0x04 && MUB0(meter_urb, s) == 0x62) { - if (MUC0(meter_urb, s) == 0x72) - store->meter_level[MUB2(meter_urb, s) - 1] = val; - if (MUC0(meter_urb, s) == 0xb2) - store->comp_level[MUB2(meter_urb, s) - 1] = val; + if (ch < SND_US16X08_MAX_CHANNELS) { + if (MUC0(meter_urb, s) == 0x72) + store->meter_level[ch] = val; + if (MUC0(meter_urb, s) == 0xb2) + store->comp_level[ch] = val; + } } if (MUA0(meter_urb, s) == 0x61 && MUA1(meter_urb, s) == 0x02 && - MUA2(meter_urb, s) == 0x02 && MUB0(meter_urb, s) == 0x62) - store->master_level[MUB2(meter_urb, s) - 1] = val; + MUA2(meter_urb, s) == 0x02 && MUB0(meter_urb, s) == 0x62) { + if (ch < ARRAY_SIZE(store->master_level)) + store->master_level[ch] = val; + } } /* Function to retrieve current meter values from the device. From 74fd0ae1a7de0a0da236877acd23e22f54d7d084 Mon Sep 17 00:00:00 2001 From: Shengjiu Wang Date: Tue, 16 Dec 2025 15:02:01 +0800 Subject: [PATCH 1478/3902] ASoC: ak4458: remove the reset operation in probe and remove [ Upstream commit 00b960a83c764208b0623089eb70af3685e3906f ] The reset_control handler has the reference count for usage, as there is reset operation in runtime suspend and resume, then reset operation in probe() would cause the reference count of reset not balanced. Previously add reset operation in probe and remove is to fix the compile issue with !CONFIG_PM, as the driver has been update to use RUNTIME_PM_OPS(), so that change can be reverted. Fixes: 1e0dff741b0a ("ASoC: ak4458: remove "reset-gpios" property handler") Signed-off-by: Shengjiu Wang Link: https://patch.msgid.link/20251216070201.358477-1-shengjiu.wang@nxp.com Signed-off-by: Mark Brown Signed-off-by: Sasha Levin --- sound/soc/codecs/ak4458.c | 4 ---- 1 file changed, 4 deletions(-) diff --git a/sound/soc/codecs/ak4458.c b/sound/soc/codecs/ak4458.c index a6c04dd3de3ed7..abe742edb10fe5 100644 --- a/sound/soc/codecs/ak4458.c +++ b/sound/soc/codecs/ak4458.c @@ -783,16 +783,12 @@ static int ak4458_i2c_probe(struct i2c_client *i2c) pm_runtime_enable(&i2c->dev); regcache_cache_only(ak4458->regmap, true); - ak4458_reset(ak4458, false); return 0; } static void ak4458_i2c_remove(struct i2c_client *i2c) { - struct ak4458_priv *ak4458 = i2c_get_clientdata(i2c); - - ak4458_reset(ak4458, true); pm_runtime_disable(&i2c->dev); } From 523c21a04c1764ac231c29ddb911335a8430849e Mon Sep 17 00:00:00 2001 From: Shardul Bankar Date: Mon, 17 Nov 2025 17:41:21 +0530 Subject: [PATCH 1479/3902] nfsd: fix memory leak in nfsd_create_serv error paths [ Upstream commit df8d829bba3adcf3cc744c01d933b6fd7cf06e91 ] When nfsd_create_serv() calls percpu_ref_init() to initialize nn->nfsd_net_ref, it allocates both a percpu reference counter and a percpu_ref_data structure (64 bytes). However, if the function fails later due to svc_create_pooled() returning NULL or svc_bind() returning an error, these allocations are not cleaned up, resulting in a memory leak. The leak manifests as: - Unreferenced percpu allocation (8 bytes per CPU) - Unreferenced percpu_ref_data structure (64 bytes) Fix this by adding percpu_ref_exit() calls in both error paths to properly clean up the percpu_ref_init() allocations. This patch fixes the percpu_ref leak in nfsd_create_serv() seen as an auxiliary leak in syzbot report 099461f8558eb0a1f4f3; the prepare_creds() and vsock-related leaks in the same report remain to be addressed separately. Reported-by: syzbot+099461f8558eb0a1f4f3@syzkaller.appspotmail.com Link: https://syzkaller.appspot.com/bug?extid=099461f8558eb0a1f4f3 Fixes: 47e988147f40 ("nfsd: add nfsd_serv_try_get and nfsd_serv_put") Signed-off-by: Shardul Bankar Reviewed-by: Jeff Layton Signed-off-by: Chuck Lever Signed-off-by: Sasha Levin --- fs/nfsd/nfssvc.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/fs/nfsd/nfssvc.c b/fs/nfsd/nfssvc.c index 7057ddd7a0a873..32cc03a7e7bec5 100644 --- a/fs/nfsd/nfssvc.c +++ b/fs/nfsd/nfssvc.c @@ -633,12 +633,15 @@ int nfsd_create_serv(struct net *net) serv = svc_create_pooled(nfsd_programs, ARRAY_SIZE(nfsd_programs), &nn->nfsd_svcstats, nfsd_max_blksize, nfsd); - if (serv == NULL) + if (serv == NULL) { + percpu_ref_exit(&nn->nfsd_net_ref); return -ENOMEM; + } error = svc_bind(serv, net); if (error < 0) { svc_destroy(&serv); + percpu_ref_exit(&nn->nfsd_net_ref); return error; } spin_lock(&nfsd_notifier_lock); From aa0449550e1ea938d64057d64e00c29cf4a76c57 Mon Sep 17 00:00:00 2001 From: Shuming Fan Date: Thu, 6 Nov 2025 17:33:35 +0800 Subject: [PATCH 1480/3902] ASoC: SDCA: support Q7.8 volume format [ Upstream commit 1b0f3f9ee41ee2bdd206667f85ea2aa36dfe6e69 ] The SDCA specification uses Q7.8 volume format. This patch adds a field to indicate whether it is SDCA volume control and supports the volume settings. Signed-off-by: Shuming Fan Reviewed-by: Charles Keepax Link: https://patch.msgid.link/20251106093335.1363237-1-shumingf@realtek.com Signed-off-by: Mark Brown Stable-dep-of: 095d62114182 ("ASoC: ops: fix snd_soc_get_volsw for sx controls") Signed-off-by: Sasha Levin --- include/sound/soc.h | 1 + sound/soc/sdca/sdca_asoc.c | 34 ++++++--------------- sound/soc/soc-ops.c | 62 +++++++++++++++++++++++++++++++------- 3 files changed, 61 insertions(+), 36 deletions(-) diff --git a/include/sound/soc.h b/include/sound/soc.h index ddc508ff7b9be1..a9c058b06ab428 100644 --- a/include/sound/soc.h +++ b/include/sound/soc.h @@ -1225,6 +1225,7 @@ struct soc_mixer_control { unsigned int sign_bit; unsigned int invert:1; unsigned int autodisable:1; + unsigned int sdca_q78:1; #ifdef CONFIG_SND_SOC_TOPOLOGY struct snd_soc_dobj dobj; #endif diff --git a/sound/soc/sdca/sdca_asoc.c b/sound/soc/sdca/sdca_asoc.c index c493ec530cc5c8..892b7c028faea8 100644 --- a/sound/soc/sdca/sdca_asoc.c +++ b/sound/soc/sdca/sdca_asoc.c @@ -795,7 +795,6 @@ static int control_limit_kctl(struct device *dev, struct sdca_control_range *range; int min, max, step; unsigned int *tlv; - int shift; if (control->type != SDCA_CTL_DATATYPE_Q7P8DB) return 0; @@ -814,37 +813,22 @@ static int control_limit_kctl(struct device *dev, min = sign_extend32(min, control->nbits - 1); max = sign_extend32(max, control->nbits - 1); - /* - * FIXME: Only support power of 2 step sizes as this can be supported - * by a simple shift. - */ - if (hweight32(step) != 1) { - dev_err(dev, "%s: %s: currently unsupported step size\n", - entity->label, control->label); - return -EINVAL; - } - - /* - * The SDCA volumes are in steps of 1/256th of a dB, a step down of - * 64 (shift of 6) gives 1/4dB. 1/4dB is the smallest unit that is also - * representable in the ALSA TLVs which are in 1/100ths of a dB. - */ - shift = max(ffs(step) - 1, 6); - tlv = devm_kcalloc(dev, 4, sizeof(*tlv), GFP_KERNEL); if (!tlv) return -ENOMEM; - tlv[0] = SNDRV_CTL_TLVT_DB_SCALE; + tlv[0] = SNDRV_CTL_TLVT_DB_MINMAX; tlv[1] = 2 * sizeof(*tlv); tlv[2] = (min * 100) >> 8; - tlv[3] = ((1 << shift) * 100) >> 8; + tlv[3] = (max * 100) >> 8; + + step = (step * 100) >> 8; - mc->min = min >> shift; - mc->max = max >> shift; - mc->shift = shift; - mc->rshift = shift; - mc->sign_bit = 15 - shift; + mc->min = ((int)tlv[2] / step); + mc->max = ((int)tlv[3] / step); + mc->shift = step; + mc->sign_bit = 15; + mc->sdca_q78 = 1; kctl->tlv.p = tlv; kctl->access |= SNDRV_CTL_ELEM_ACCESS_TLV_READ; diff --git a/sound/soc/soc-ops.c b/sound/soc/soc-ops.c index d2b6fb8e0b6c69..ce86978c158d69 100644 --- a/sound/soc/soc-ops.c +++ b/sound/soc/soc-ops.c @@ -110,6 +110,36 @@ int snd_soc_put_enum_double(struct snd_kcontrol *kcontrol, } EXPORT_SYMBOL_GPL(snd_soc_put_enum_double); +static int sdca_soc_q78_reg_to_ctl(struct soc_mixer_control *mc, unsigned int reg_val, + unsigned int mask, unsigned int shift, int max) +{ + int val = reg_val; + + if (WARN_ON(!mc->shift)) + return -EINVAL; + + val = sign_extend32(val, mc->sign_bit); + val = (((val * 100) >> 8) / (int)mc->shift); + val -= mc->min; + + return val & mask; +} + +static unsigned int sdca_soc_q78_ctl_to_reg(struct soc_mixer_control *mc, int val, + unsigned int mask, unsigned int shift, int max) +{ + unsigned int ret_val; + int reg_val; + + if (WARN_ON(!mc->shift)) + return -EINVAL; + + reg_val = val + mc->min; + ret_val = (int)((reg_val * mc->shift) << 8) / 100; + + return ret_val & mask; +} + static int soc_mixer_reg_to_ctl(struct soc_mixer_control *mc, unsigned int reg_val, unsigned int mask, unsigned int shift, int max) { @@ -197,19 +227,27 @@ static int soc_put_volsw(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol, struct soc_mixer_control *mc, int mask, int max) { + unsigned int (*ctl_to_reg)(struct soc_mixer_control *, int, unsigned int, unsigned int, int); struct snd_soc_component *component = snd_kcontrol_chip(kcontrol); unsigned int val1, val_mask; unsigned int val2 = 0; bool double_r = false; int ret; + if (mc->sdca_q78) { + ctl_to_reg = sdca_soc_q78_ctl_to_reg; + val_mask = mask; + } else { + ctl_to_reg = soc_mixer_ctl_to_reg; + val_mask = mask << mc->shift; + } + ret = soc_mixer_valid_ctl(mc, ucontrol->value.integer.value[0], max); if (ret) return ret; - val1 = soc_mixer_ctl_to_reg(mc, ucontrol->value.integer.value[0], + val1 = ctl_to_reg(mc, ucontrol->value.integer.value[0], mask, mc->shift, max); - val_mask = mask << mc->shift; if (snd_soc_volsw_is_stereo(mc)) { ret = soc_mixer_valid_ctl(mc, ucontrol->value.integer.value[1], max); @@ -217,14 +255,10 @@ static int soc_put_volsw(struct snd_kcontrol *kcontrol, return ret; if (mc->reg == mc->rreg) { - val1 |= soc_mixer_ctl_to_reg(mc, - ucontrol->value.integer.value[1], - mask, mc->rshift, max); + val1 |= ctl_to_reg(mc, ucontrol->value.integer.value[1], mask, mc->rshift, max); val_mask |= mask << mc->rshift; } else { - val2 = soc_mixer_ctl_to_reg(mc, - ucontrol->value.integer.value[1], - mask, mc->shift, max); + val2 = ctl_to_reg(mc, ucontrol->value.integer.value[1], mask, mc->shift, max); double_r = true; } } @@ -248,21 +282,27 @@ static int soc_get_volsw(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol, struct soc_mixer_control *mc, int mask, int max) { + int (*reg_to_ctl)(struct soc_mixer_control *, unsigned int, unsigned int, unsigned int, int); struct snd_soc_component *component = snd_kcontrol_chip(kcontrol); unsigned int reg_val; int val; + if (mc->sdca_q78) + reg_to_ctl = sdca_soc_q78_reg_to_ctl; + else + reg_to_ctl = soc_mixer_reg_to_ctl; + reg_val = snd_soc_component_read(component, mc->reg); - val = soc_mixer_reg_to_ctl(mc, reg_val, mask, mc->shift, max); + val = reg_to_ctl(mc, reg_val, mask, mc->shift, max); ucontrol->value.integer.value[0] = val; if (snd_soc_volsw_is_stereo(mc)) { if (mc->reg == mc->rreg) { - val = soc_mixer_reg_to_ctl(mc, reg_val, mask, mc->rshift, max); + val = reg_to_ctl(mc, reg_val, mask, mc->rshift, max); } else { reg_val = snd_soc_component_read(component, mc->rreg); - val = soc_mixer_reg_to_ctl(mc, reg_val, mask, mc->shift, max); + val = reg_to_ctl(mc, reg_val, mask, mc->shift, max); } ucontrol->value.integer.value[1] = val; From 8a9c52cbaf96417ecc48023318ffec0acef2f656 Mon Sep 17 00:00:00 2001 From: Stefan Binding Date: Tue, 16 Dec 2025 13:49:20 +0000 Subject: [PATCH 1481/3902] ASoC: ops: fix snd_soc_get_volsw for sx controls [ Upstream commit 095d621141826a2841dae85b52c784c147ea99d3 ] SX controls are currently broken, since the clamp introduced in commit a0ce874cfaaa ("ASoC: ops: improve snd_soc_get_volsw") does not handle SX controls, for example where the min value in the clamp is greater than the max value in the clamp. Add clamp parameter to prevent clamping in SX controls. The nature of SX controls mean that it wraps around 0, with a variable number of bits, therefore clamping the value becomes complicated and prone to error. Fixes 35 kunit tests for soc_ops_test_access. Fixes: a0ce874cfaaa ("ASoC: ops: improve snd_soc_get_volsw") Co-developed-by: Charles Keepax Signed-off-by: Stefan Binding Tested-by: Peter Ujfalusi Link: https://patch.msgid.link/20251216134938.788625-1-sbinding@opensource.cirrus.com Signed-off-by: Mark Brown Signed-off-by: Sasha Levin --- sound/soc/soc-ops.c | 32 ++++++++++++++++++++------------ 1 file changed, 20 insertions(+), 12 deletions(-) diff --git a/sound/soc/soc-ops.c b/sound/soc/soc-ops.c index ce86978c158d69..624e9269fc25b4 100644 --- a/sound/soc/soc-ops.c +++ b/sound/soc/soc-ops.c @@ -111,7 +111,8 @@ int snd_soc_put_enum_double(struct snd_kcontrol *kcontrol, EXPORT_SYMBOL_GPL(snd_soc_put_enum_double); static int sdca_soc_q78_reg_to_ctl(struct soc_mixer_control *mc, unsigned int reg_val, - unsigned int mask, unsigned int shift, int max) + unsigned int mask, unsigned int shift, int max, + bool sx) { int val = reg_val; @@ -141,20 +142,26 @@ static unsigned int sdca_soc_q78_ctl_to_reg(struct soc_mixer_control *mc, int va } static int soc_mixer_reg_to_ctl(struct soc_mixer_control *mc, unsigned int reg_val, - unsigned int mask, unsigned int shift, int max) + unsigned int mask, unsigned int shift, int max, + bool sx) { int val = (reg_val >> shift) & mask; if (mc->sign_bit) val = sign_extend32(val, mc->sign_bit); - val = clamp(val, mc->min, mc->max); - val -= mc->min; + if (sx) { + val -= mc->min; // SX controls intentionally can overflow here + val = min_t(unsigned int, val & mask, max); + } else { + val = clamp(val, mc->min, mc->max); + val -= mc->min; + } if (mc->invert) val = max - val; - return val & mask; + return val; } static unsigned int soc_mixer_ctl_to_reg(struct soc_mixer_control *mc, int val, @@ -280,9 +287,10 @@ static int soc_put_volsw(struct snd_kcontrol *kcontrol, static int soc_get_volsw(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol, - struct soc_mixer_control *mc, int mask, int max) + struct soc_mixer_control *mc, int mask, int max, bool sx) { - int (*reg_to_ctl)(struct soc_mixer_control *, unsigned int, unsigned int, unsigned int, int); + int (*reg_to_ctl)(struct soc_mixer_control *, unsigned int, unsigned int, + unsigned int, int, bool); struct snd_soc_component *component = snd_kcontrol_chip(kcontrol); unsigned int reg_val; int val; @@ -293,16 +301,16 @@ static int soc_get_volsw(struct snd_kcontrol *kcontrol, reg_to_ctl = soc_mixer_reg_to_ctl; reg_val = snd_soc_component_read(component, mc->reg); - val = reg_to_ctl(mc, reg_val, mask, mc->shift, max); + val = reg_to_ctl(mc, reg_val, mask, mc->shift, max, sx); ucontrol->value.integer.value[0] = val; if (snd_soc_volsw_is_stereo(mc)) { if (mc->reg == mc->rreg) { - val = reg_to_ctl(mc, reg_val, mask, mc->rshift, max); + val = reg_to_ctl(mc, reg_val, mask, mc->rshift, max, sx); } else { reg_val = snd_soc_component_read(component, mc->rreg); - val = reg_to_ctl(mc, reg_val, mask, mc->shift, max); + val = reg_to_ctl(mc, reg_val, mask, mc->shift, max, sx); } ucontrol->value.integer.value[1] = val; @@ -371,7 +379,7 @@ int snd_soc_get_volsw(struct snd_kcontrol *kcontrol, (struct soc_mixer_control *)kcontrol->private_value; unsigned int mask = soc_mixer_mask(mc); - return soc_get_volsw(kcontrol, ucontrol, mc, mask, mc->max - mc->min); + return soc_get_volsw(kcontrol, ucontrol, mc, mask, mc->max - mc->min, false); } EXPORT_SYMBOL_GPL(snd_soc_get_volsw); @@ -413,7 +421,7 @@ int snd_soc_get_volsw_sx(struct snd_kcontrol *kcontrol, (struct soc_mixer_control *)kcontrol->private_value; unsigned int mask = soc_mixer_sx_mask(mc); - return soc_get_volsw(kcontrol, ucontrol, mc, mask, mc->max); + return soc_get_volsw(kcontrol, ucontrol, mc, mask, mc->max, true); } EXPORT_SYMBOL_GPL(snd_soc_get_volsw_sx); From 235ae3fc9e9c9fbcd4245ec6f6b5423ed47fcda9 Mon Sep 17 00:00:00 2001 From: Jinhui Guo Date: Tue, 30 Sep 2025 15:42:37 +0800 Subject: [PATCH 1482/3902] ipmi: Fix the race between __scan_channels() and deliver_response() [ Upstream commit 936750fdba4c45e13bbd17f261bb140dd55f5e93 ] The race window between __scan_channels() and deliver_response() causes the parameters of some channels to be set to 0. 1.[CPUA] __scan_channels() issues an IPMI request and waits with wait_event() until all channels have been scanned. wait_event() internally calls might_sleep(), which might yield the CPU. (Moreover, an interrupt can preempt wait_event() and force the task to yield the CPU.) 2.[CPUB] deliver_response() is invoked when the CPU receives the IPMI response. After processing a IPMI response, deliver_response() directly assigns intf->wchannels to intf->channel_list and sets intf->channels_ready to true. However, not all channels are actually ready for use. 3.[CPUA] Since intf->channels_ready is already true, wait_event() never enters __wait_event(). __scan_channels() immediately clears intf->null_user_handler and exits. 4.[CPUB] Once intf->null_user_handler is set to NULL, deliver_response() ignores further IPMI responses, leaving the remaining channels zero-initialized and unusable. CPUA CPUB ------------------------------- ----------------------------- __scan_channels() intf->null_user_handler = channel_handler; send_channel_info_cmd(intf, 0); wait_event(intf->waitq, intf->channels_ready); do { might_sleep(); deliver_response() channel_handler() intf->channel_list = intf->wchannels + set; intf->channels_ready = true; send_channel_info_cmd(intf, intf->curr_channel); if (condition) break; __wait_event(wq_head, condition); } while(0) intf->null_user_handler = NULL; deliver_response() if (!msg->user) if (intf->null_user_handler) rv = -EINVAL; return rv; ------------------------------- ----------------------------- Fix the race between __scan_channels() and deliver_response() by deferring both the assignment intf->channel_list = intf->wchannels and the flag intf->channels_ready = true until all channels have been successfully scanned or until the IPMI request has failed. Signed-off-by: Jinhui Guo Message-ID: <20250930074239.2353-2-guojinhui.liam@bytedance.com> Signed-off-by: Corey Minyard Signed-off-by: Sasha Levin --- drivers/char/ipmi/ipmi_msghandler.c | 2 -- 1 file changed, 2 deletions(-) diff --git a/drivers/char/ipmi/ipmi_msghandler.c b/drivers/char/ipmi/ipmi_msghandler.c index 3700ab4eba3e7e..d3f84deee4513d 100644 --- a/drivers/char/ipmi/ipmi_msghandler.c +++ b/drivers/char/ipmi/ipmi_msghandler.c @@ -3417,8 +3417,6 @@ channel_handler(struct ipmi_smi *intf, struct ipmi_recv_msg *msg) intf->channels_ready = true; wake_up(&intf->waitq); } else { - intf->channel_list = intf->wchannels + set; - intf->channels_ready = true; rv = send_channel_info_cmd(intf, intf->curr_channel); } From eb8d9fb6280dac6df702e0899b590b4daadf2e8a Mon Sep 17 00:00:00 2001 From: Jinhui Guo Date: Tue, 30 Sep 2025 15:42:38 +0800 Subject: [PATCH 1483/3902] ipmi: Fix __scan_channels() failing to rescan channels [ Upstream commit 6bd30d8fc523fb880b4be548e8501bc0fe8f42d4 ] channel_handler() sets intf->channels_ready to true but never clears it, so __scan_channels() skips any rescan. When the BMC firmware changes a rescan is required. Allow it by clearing the flag before starting a new scan. Signed-off-by: Jinhui Guo Message-ID: <20250930074239.2353-3-guojinhui.liam@bytedance.com> Signed-off-by: Corey Minyard Signed-off-by: Sasha Levin --- drivers/char/ipmi/ipmi_msghandler.c | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/drivers/char/ipmi/ipmi_msghandler.c b/drivers/char/ipmi/ipmi_msghandler.c index d3f84deee4513d..0a886399f9dafb 100644 --- a/drivers/char/ipmi/ipmi_msghandler.c +++ b/drivers/char/ipmi/ipmi_msghandler.c @@ -599,7 +599,8 @@ static void __ipmi_bmc_unregister(struct ipmi_smi *intf); static int __ipmi_bmc_register(struct ipmi_smi *intf, struct ipmi_device_id *id, bool guid_set, guid_t *guid, int intf_num); -static int __scan_channels(struct ipmi_smi *intf, struct ipmi_device_id *id); +static int __scan_channels(struct ipmi_smi *intf, + struct ipmi_device_id *id, bool rescan); static void free_ipmi_user(struct kref *ref) { @@ -2668,7 +2669,7 @@ static int __bmc_get_device_id(struct ipmi_smi *intf, struct bmc_device *bmc, if (__ipmi_bmc_register(intf, &id, guid_set, &guid, intf_num)) need_waiter(intf); /* Retry later on an error. */ else - __scan_channels(intf, &id); + __scan_channels(intf, &id, false); if (!intf_set) { @@ -2688,7 +2689,7 @@ static int __bmc_get_device_id(struct ipmi_smi *intf, struct bmc_device *bmc, goto out_noprocessing; } else if (memcmp(&bmc->fetch_id, &bmc->id, sizeof(bmc->id))) /* Version info changes, scan the channels again. */ - __scan_channels(intf, &bmc->fetch_id); + __scan_channels(intf, &bmc->fetch_id, true); bmc->dyn_id_expiry = jiffies + IPMI_DYN_DEV_ID_EXPIRY; @@ -3438,10 +3439,17 @@ channel_handler(struct ipmi_smi *intf, struct ipmi_recv_msg *msg) /* * Must be holding intf->bmc_reg_mutex to call this. */ -static int __scan_channels(struct ipmi_smi *intf, struct ipmi_device_id *id) +static int __scan_channels(struct ipmi_smi *intf, + struct ipmi_device_id *id, + bool rescan) { int rv; + if (rescan) { + /* Clear channels_ready to force channels rescan. */ + intf->channels_ready = false; + } + if (ipmi_version_major(id) > 1 || (ipmi_version_major(id) == 1 && ipmi_version_minor(id) >= 5)) { @@ -3656,7 +3664,7 @@ int ipmi_add_smi(struct module *owner, } mutex_lock(&intf->bmc_reg_mutex); - rv = __scan_channels(intf, &id); + rv = __scan_channels(intf, &id, false); mutex_unlock(&intf->bmc_reg_mutex); if (rv) goto out_err_bmc_reg; From 9f2ff08737c4bf99b784adc921de9a64a03984c3 Mon Sep 17 00:00:00 2001 From: Peter Wang Date: Wed, 24 Sep 2025 17:43:27 +0800 Subject: [PATCH 1484/3902] scsi: ufs: host: mediatek: Fix shutdown/suspend race condition [ Upstream commit 014de20bb36ba03e0e0b0a7e0a1406ab900c9fda ] Address a race condition between shutdown and suspend operations in the UFS Mediatek driver. Before entering suspend, check if a shutdown is in progress to prevent conflicts and ensure system stability. Signed-off-by: Peter Wang Acked-by: Chun-Hung Wu Link: https://patch.msgid.link/20250924094527.2992256-6-peter.wang@mediatek.com Signed-off-by: Martin K. Petersen Signed-off-by: Sasha Levin --- drivers/ufs/host/ufs-mediatek.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/drivers/ufs/host/ufs-mediatek.c b/drivers/ufs/host/ufs-mediatek.c index 758a393a9de1a2..d0cbd96ad29dc5 100644 --- a/drivers/ufs/host/ufs-mediatek.c +++ b/drivers/ufs/host/ufs-mediatek.c @@ -2373,6 +2373,11 @@ static int ufs_mtk_system_suspend(struct device *dev) struct arm_smccc_res res; int ret; + if (hba->shutting_down) { + ret = -EBUSY; + goto out; + } + ret = ufshcd_system_suspend(dev); if (ret) goto out; From 64b989a051b00d7babecb7f8574c6034e15bdc80 Mon Sep 17 00:00:00 2001 From: Peng Fan Date: Fri, 17 Oct 2025 09:56:26 +0800 Subject: [PATCH 1485/3902] firmware: imx: scu-irq: Init workqueue before request mbox channel [ Upstream commit 81fb53feb66a3aefbf6fcab73bb8d06f5b0c54ad ] With mailbox channel requested, there is possibility that interrupts may come in, so need to make sure the workqueue is initialized before the queue is scheduled by mailbox rx callback. Reviewed-by: Frank Li Signed-off-by: Peng Fan Signed-off-by: Shawn Guo Signed-off-by: Sasha Levin --- drivers/firmware/imx/imx-scu-irq.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/firmware/imx/imx-scu-irq.c b/drivers/firmware/imx/imx-scu-irq.c index f2b902e95b738f..b9f6128d56f708 100644 --- a/drivers/firmware/imx/imx-scu-irq.c +++ b/drivers/firmware/imx/imx-scu-irq.c @@ -214,6 +214,8 @@ int imx_scu_enable_general_irq_channel(struct device *dev) cl->dev = dev; cl->rx_callback = imx_scu_irq_callback; + INIT_WORK(&imx_sc_irq_work, imx_scu_irq_work_handler); + /* SCU general IRQ uses general interrupt channel 3 */ ch = mbox_request_channel_byname(cl, "gip3"); if (IS_ERR(ch)) { @@ -223,8 +225,6 @@ int imx_scu_enable_general_irq_channel(struct device *dev) return ret; } - INIT_WORK(&imx_sc_irq_work, imx_scu_irq_work_handler); - if (!of_parse_phandle_with_args(dev->of_node, "mboxes", "#mbox-cells", 0, &spec)) { i = of_alias_get_id(spec.np, "mu"); From dbbf6d47130674640cd12a0781a0fb2a575d0e44 Mon Sep 17 00:00:00 2001 From: Johannes Berg Date: Wed, 24 Sep 2025 11:32:13 +0200 Subject: [PATCH 1486/3902] um: init cpu_tasks[] earlier [ Upstream commit 7b5d4416964c07c902163822a30a622111172b01 ] This is currently done in uml_finishsetup(), but e.g. with KCOV enabled we'll crash because some init code can call into e.g. memparse(), which has coverage annotations, and then the checks in check_kcov_mode() crash because current is NULL. Simply initialize the cpu_tasks[] array statically, which fixes the crash. For the later SMP work, it seems to have not really caused any problems yet, but initialize all of the entries anyway. Link: https://patch.msgid.link/20250924113214.c76cd74d0583.I974f691ebb1a2b47915bd2b04cc38e5263b9447f@changeid Signed-off-by: Johannes Berg Signed-off-by: Sasha Levin --- arch/um/kernel/process.c | 4 +++- arch/um/kernel/um_arch.c | 2 -- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/arch/um/kernel/process.c b/arch/um/kernel/process.c index 9c9c66dc45f054..13d461712c9972 100644 --- a/arch/um/kernel/process.c +++ b/arch/um/kernel/process.c @@ -43,7 +43,9 @@ * cares about its entry, so it's OK if another processor is modifying its * entry. */ -struct task_struct *cpu_tasks[NR_CPUS]; +struct task_struct *cpu_tasks[NR_CPUS] = { + [0 ... NR_CPUS - 1] = &init_task, +}; EXPORT_SYMBOL(cpu_tasks); void free_stack(unsigned long stack, int order) diff --git a/arch/um/kernel/um_arch.c b/arch/um/kernel/um_arch.c index cfbbbf8500c345..ed2f67848a50e7 100644 --- a/arch/um/kernel/um_arch.c +++ b/arch/um/kernel/um_arch.c @@ -239,8 +239,6 @@ static struct notifier_block panic_exit_notifier = { void uml_finishsetup(void) { - cpu_tasks[0] = &init_task; - atomic_notifier_chain_register(&panic_notifier_list, &panic_exit_notifier); From 15108657bce0ac6111c0beb17246c563648f2d0d Mon Sep 17 00:00:00 2001 From: Matthias Schiffer Date: Mon, 25 Aug 2025 15:11:13 +0200 Subject: [PATCH 1487/3902] ti-sysc: allow OMAP2 and OMAP4 timers to be reserved on AM33xx [ Upstream commit 3f61783920504b2cf99330b372d82914bb004d8e ] am33xx.dtsi has the same clock setup as am35xx.dtsi, setting ti,no-reset-on-init and ti,no-idle on timer1_target and timer2_target, so AM33 needs the same workaround as AM35 to avoid ti-sysc probe failing on certain target modules. Signed-off-by: Matthias Schiffer Signed-off-by: Alexander Stein Link: https://lore.kernel.org/r/20250825131114.2206804-1-alexander.stein@ew.tq-group.com Signed-off-by: Kevin Hilman Signed-off-by: Sasha Levin --- drivers/bus/ti-sysc.c | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/drivers/bus/ti-sysc.c b/drivers/bus/ti-sysc.c index 5566ad11399e7e..610354ce7f8f0d 100644 --- a/drivers/bus/ti-sysc.c +++ b/drivers/bus/ti-sysc.c @@ -48,6 +48,7 @@ enum sysc_soc { SOC_UNKNOWN, SOC_2420, SOC_2430, + SOC_AM33, SOC_3430, SOC_AM35, SOC_3630, @@ -2912,6 +2913,7 @@ static void ti_sysc_idle(struct work_struct *work) static const struct soc_device_attribute sysc_soc_match[] = { SOC_FLAG("OMAP242*", SOC_2420), SOC_FLAG("OMAP243*", SOC_2430), + SOC_FLAG("AM33*", SOC_AM33), SOC_FLAG("AM35*", SOC_AM35), SOC_FLAG("OMAP3[45]*", SOC_3430), SOC_FLAG("OMAP3[67]*", SOC_3630), @@ -3117,10 +3119,15 @@ static int sysc_check_active_timer(struct sysc *ddata) * can be dropped if we stop supporting old beagleboard revisions * A to B4 at some point. */ - if (sysc_soc->soc == SOC_3430 || sysc_soc->soc == SOC_AM35) + switch (sysc_soc->soc) { + case SOC_AM33: + case SOC_3430: + case SOC_AM35: error = -ENXIO; - else + break; + default: error = -EBUSY; + } if ((ddata->cfg.quirks & SYSC_QUIRK_NO_RESET_ON_INIT) && (ddata->cfg.quirks & SYSC_QUIRK_NO_IDLE)) From 4ca4c0b6c34ffc5cfd99312d751c95fc693c92e8 Mon Sep 17 00:00:00 2001 From: David Strahan Date: Thu, 6 Nov 2025 10:38:21 -0600 Subject: [PATCH 1488/3902] scsi: smartpqi: Add support for Hurray Data new controller PCI device [ Upstream commit 48e6b7e708029cea451e53a8c16fc8c16039ecdc ] Add support for new Hurray Data controller. All entries are in HEX. Add PCI IDs for Hurray Data controllers: VID / DID / SVID / SDID ---- ---- ---- ---- 9005 028f 207d 4840 Reviewed-by: Scott Benesh Reviewed-by: Scott Teel Reviewed-by: Mike McGowen Signed-off-by: David Strahan Signed-off-by: Don Brace Link: https://patch.msgid.link/20251106163823.786828-4-don.brace@microchip.com Signed-off-by: Martin K. Petersen Signed-off-by: Sasha Levin --- drivers/scsi/smartpqi/smartpqi_init.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/drivers/scsi/smartpqi/smartpqi_init.c b/drivers/scsi/smartpqi/smartpqi_init.c index fdc856845a05d9..98e93900254cb2 100644 --- a/drivers/scsi/smartpqi/smartpqi_init.c +++ b/drivers/scsi/smartpqi/smartpqi_init.c @@ -10127,6 +10127,10 @@ static const struct pci_device_id pqi_pci_id_table[] = { PCI_DEVICE_SUB(PCI_VENDOR_ID_ADAPTEC2, 0x028f, 0x207d, 0x4240) }, + { + PCI_DEVICE_SUB(PCI_VENDOR_ID_ADAPTEC2, 0x028f, + 0x207d, 0x4840) + }, { PCI_DEVICE_SUB(PCI_VENDOR_ID_ADAPTEC2, 0x028f, PCI_VENDOR_ID_ADVANTECH, 0x8312) From 5f9a7a00a2be5734ef0934566feabe17eb094786 Mon Sep 17 00:00:00 2001 From: Justin Tee Date: Thu, 6 Nov 2025 14:46:36 -0800 Subject: [PATCH 1489/3902] scsi: lpfc: Fix reusing an ndlp that is marked NLP_DROPPED during FLOGI [ Upstream commit 07caedc6a3887938813727beafea40f07c497705 ] It's possible for an unstable link to repeatedly bounce allowing a FLOGI retry, but then bounce again forcing an abort of the FLOGI. Ensure that the initial reference count on the FLOGI ndlp is restored in this faulty link scenario. Signed-off-by: Justin Tee Link: https://patch.msgid.link/20251106224639.139176-8-justintee8345@gmail.com Signed-off-by: Martin K. Petersen Signed-off-by: Sasha Levin --- drivers/scsi/lpfc/lpfc_els.c | 36 +++++++++++++++++++++++++------- drivers/scsi/lpfc/lpfc_hbadisc.c | 4 +++- 2 files changed, 32 insertions(+), 8 deletions(-) diff --git a/drivers/scsi/lpfc/lpfc_els.c b/drivers/scsi/lpfc/lpfc_els.c index b71db7d7d747d2..c08237f04bce27 100644 --- a/drivers/scsi/lpfc/lpfc_els.c +++ b/drivers/scsi/lpfc/lpfc_els.c @@ -934,10 +934,15 @@ lpfc_cmpl_els_flogi(struct lpfc_hba *phba, struct lpfc_iocbq *cmdiocb, /* Check to see if link went down during discovery */ if (lpfc_els_chk_latt(vport)) { /* One additional decrement on node reference count to - * trigger the release of the node + * trigger the release of the node. Make sure the ndlp + * is marked NLP_DROPPED. */ - if (!(ndlp->fc4_xpt_flags & SCSI_XPT_REGD)) + if (!test_bit(NLP_IN_DEV_LOSS, &ndlp->nlp_flag) && + !test_bit(NLP_DROPPED, &ndlp->nlp_flag) && + !(ndlp->fc4_xpt_flags & SCSI_XPT_REGD)) { + set_bit(NLP_DROPPED, &ndlp->nlp_flag); lpfc_nlp_put(ndlp); + } goto out; } @@ -995,9 +1000,10 @@ lpfc_cmpl_els_flogi(struct lpfc_hba *phba, struct lpfc_iocbq *cmdiocb, IOERR_LOOP_OPEN_FAILURE))) lpfc_vlog_msg(vport, KERN_WARNING, LOG_ELS, "2858 FLOGI Status:x%x/x%x TMO" - ":x%x Data x%lx x%x\n", + ":x%x Data x%lx x%x x%lx x%x\n", ulp_status, ulp_word4, tmo, - phba->hba_flag, phba->fcf.fcf_flag); + phba->hba_flag, phba->fcf.fcf_flag, + ndlp->nlp_flag, ndlp->fc4_xpt_flags); /* Check for retry */ if (lpfc_els_retry(phba, cmdiocb, rspiocb)) { @@ -1015,14 +1021,17 @@ lpfc_cmpl_els_flogi(struct lpfc_hba *phba, struct lpfc_iocbq *cmdiocb, * reference to trigger node release. */ if (!test_bit(NLP_IN_DEV_LOSS, &ndlp->nlp_flag) && - !(ndlp->fc4_xpt_flags & SCSI_XPT_REGD)) + !test_bit(NLP_DROPPED, &ndlp->nlp_flag) && + !(ndlp->fc4_xpt_flags & SCSI_XPT_REGD)) { + set_bit(NLP_DROPPED, &ndlp->nlp_flag); lpfc_nlp_put(ndlp); + } lpfc_printf_vlog(vport, KERN_WARNING, LOG_ELS, "0150 FLOGI Status:x%x/x%x " - "xri x%x TMO:x%x refcnt %d\n", + "xri x%x iotag x%x TMO:x%x refcnt %d\n", ulp_status, ulp_word4, cmdiocb->sli4_xritag, - tmo, kref_read(&ndlp->kref)); + cmdiocb->iotag, tmo, kref_read(&ndlp->kref)); /* If this is not a loop open failure, bail out */ if (!(ulp_status == IOSTAT_LOCAL_REJECT && @@ -1279,6 +1288,19 @@ lpfc_issue_els_flogi(struct lpfc_vport *vport, struct lpfc_nodelist *ndlp, uint32_t tmo, did; int rc; + /* It's possible for lpfc to reissue a FLOGI on an ndlp that is marked + * NLP_DROPPED. This happens when the FLOGI completed with the XB bit + * set causing lpfc to reference the ndlp until the XRI_ABORTED CQE is + * issued. The time window for the XRI_ABORTED CQE can be as much as + * 2*2*RA_TOV allowing for ndlp reuse of this type when the link is + * cycling quickly. When true, restore the initial reference and remove + * the NLP_DROPPED flag as lpfc is retrying. + */ + if (test_and_clear_bit(NLP_DROPPED, &ndlp->nlp_flag)) { + if (!lpfc_nlp_get(ndlp)) + return 1; + } + cmdsize = (sizeof(uint32_t) + sizeof(struct serv_parm)); elsiocb = lpfc_prep_els_iocb(vport, 1, cmdsize, retry, ndlp, ndlp->nlp_DID, ELS_CMD_FLOGI); diff --git a/drivers/scsi/lpfc/lpfc_hbadisc.c b/drivers/scsi/lpfc/lpfc_hbadisc.c index 43d246c5c049cb..717ae56c8e4bd9 100644 --- a/drivers/scsi/lpfc/lpfc_hbadisc.c +++ b/drivers/scsi/lpfc/lpfc_hbadisc.c @@ -424,6 +424,7 @@ lpfc_check_nlp_post_devloss(struct lpfc_vport *vport, struct lpfc_nodelist *ndlp) { if (test_and_clear_bit(NLP_IN_RECOV_POST_DEV_LOSS, &ndlp->save_flags)) { + clear_bit(NLP_DROPPED, &ndlp->nlp_flag); lpfc_nlp_get(ndlp); lpfc_printf_vlog(vport, KERN_INFO, LOG_DISCOVERY | LOG_NODE, "8438 Devloss timeout reversed on DID x%x " @@ -566,7 +567,8 @@ lpfc_dev_loss_tmo_handler(struct lpfc_nodelist *ndlp) return fcf_inuse; } - lpfc_nlp_put(ndlp); + if (!test_and_set_bit(NLP_DROPPED, &ndlp->nlp_flag)) + lpfc_nlp_put(ndlp); return fcf_inuse; } From d45fcd38c692b13784a61e77d2e5ea5f4dacc79b Mon Sep 17 00:00:00 2001 From: Josua Mayer Date: Thu, 30 Oct 2025 16:16:26 +0100 Subject: [PATCH 1490/3902] clk: mvebu: cp110 add CLK_IGNORE_UNUSED to pcie_x10, pcie_x11 & pcie_x4 [ Upstream commit f0e6bc0c3ef4b4afb299bd6912586cafd5d864e9 ] CP110 based platforms rely on the bootloader for pci port initialization. TF-A actively prevents non-uboot re-configuration of pci lanes, and many boards do not have software control over the pci card reset. If a pci port had link at boot-time and the clock is stopped at a later point, the link fails and can not be recovered. PCI controller driver probe - and by extension ownership of a driver for the pci clocks - may be delayed especially on large modular kernels, causing the clock core to start disabling unused clocks. Add the CLK_IGNORE_UNUSED flag to the three pci port's clocks to ensure they are not stopped before the pci controller driver has taken ownership and tested for an existing link. This fixes failed pci link detection when controller driver probes late, e.g. with arm64 defconfig and CONFIG_PHY_MVEBU_CP110_COMPHY=m. Closes: https://lore.kernel.org/r/b71596c7-461b-44b6-89ab-3cfbd492639f@solid-run.com Signed-off-by: Josua Mayer Reviewed-by: Andrew Lunn Signed-off-by: Gregory CLEMENT Signed-off-by: Sasha Levin --- drivers/clk/mvebu/cp110-system-controller.c | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/drivers/clk/mvebu/cp110-system-controller.c b/drivers/clk/mvebu/cp110-system-controller.c index 03c59bf2210602..b47c8690604667 100644 --- a/drivers/clk/mvebu/cp110-system-controller.c +++ b/drivers/clk/mvebu/cp110-system-controller.c @@ -110,6 +110,25 @@ static const char * const gate_base_names[] = { [CP110_GATE_EIP197] = "eip197" }; +static unsigned long gate_flags(const u8 bit_idx) +{ + switch (bit_idx) { + case CP110_GATE_PCIE_X1_0: + case CP110_GATE_PCIE_X1_1: + case CP110_GATE_PCIE_X4: + /* + * If a port had an active link at boot time, stopping + * the clock creates a failed state from which controller + * driver can not recover. + * Prevent stopping this clock till after a driver has taken + * ownership. + */ + return CLK_IGNORE_UNUSED; + default: + return 0; + } +}; + struct cp110_gate_clk { struct clk_hw hw; struct regmap *regmap; @@ -171,6 +190,7 @@ static struct clk_hw *cp110_register_gate(const char *name, init.ops = &cp110_gate_ops; init.parent_names = &parent_name; init.num_parents = 1; + init.flags = gate_flags(bit_idx); gate->regmap = regmap; gate->bit_idx = bit_idx; From 095704d7f334d2efcc0a225ce086a6d423523935 Mon Sep 17 00:00:00 2001 From: Ben Collins Date: Mon, 21 Apr 2025 22:31:13 -0400 Subject: [PATCH 1491/3902] powerpc/addnote: Fix overflow on 32-bit builds [ Upstream commit 825ce89a3ef17f84cf2c0eacfa6b8dc9fd11d13f ] The PUT_64[LB]E() macros need to cast the value to unsigned long long like the GET_64[LB]E() macros. Caused lots of warnings when compiled on 32-bit, and clobbered addresses (36-bit P4080). Signed-off-by: Ben Collins Reviewed-by: Christophe Leroy Signed-off-by: Madhavan Srinivasan Link: https://patch.msgid.link/2025042122-mustard-wrasse-694572@boujee-and-buff Signed-off-by: Sasha Levin --- arch/powerpc/boot/addnote.c | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/arch/powerpc/boot/addnote.c b/arch/powerpc/boot/addnote.c index 53b3b2621457d4..78704927453aa6 100644 --- a/arch/powerpc/boot/addnote.c +++ b/arch/powerpc/boot/addnote.c @@ -68,8 +68,8 @@ static int e_class = ELFCLASS32; #define PUT_16BE(off, v)(buf[off] = ((v) >> 8) & 0xff, \ buf[(off) + 1] = (v) & 0xff) #define PUT_32BE(off, v)(PUT_16BE((off), (v) >> 16L), PUT_16BE((off) + 2, (v))) -#define PUT_64BE(off, v)((PUT_32BE((off), (v) >> 32L), \ - PUT_32BE((off) + 4, (v)))) +#define PUT_64BE(off, v)((PUT_32BE((off), (unsigned long long)(v) >> 32L), \ + PUT_32BE((off) + 4, (unsigned long long)(v)))) #define GET_16LE(off) ((buf[off]) + (buf[(off)+1] << 8)) #define GET_32LE(off) (GET_16LE(off) + (GET_16LE((off)+2U) << 16U)) @@ -78,7 +78,8 @@ static int e_class = ELFCLASS32; #define PUT_16LE(off, v) (buf[off] = (v) & 0xff, \ buf[(off) + 1] = ((v) >> 8) & 0xff) #define PUT_32LE(off, v) (PUT_16LE((off), (v)), PUT_16LE((off) + 2, (v) >> 16L)) -#define PUT_64LE(off, v) (PUT_32LE((off), (v)), PUT_32LE((off) + 4, (v) >> 32L)) +#define PUT_64LE(off, v) (PUT_32LE((off), (unsigned long long)(v)), \ + PUT_32LE((off) + 4, (unsigned long long)(v) >> 32L)) #define GET_16(off) (e_data == ELFDATA2MSB ? GET_16BE(off) : GET_16LE(off)) #define GET_32(off) (e_data == ELFDATA2MSB ? GET_32BE(off) : GET_32LE(off)) From 45185214175c72da7b9d37f8374a6e1294f33302 Mon Sep 17 00:00:00 2001 From: Tony Battersby Date: Mon, 10 Nov 2025 10:50:05 -0500 Subject: [PATCH 1492/3902] scsi: qla2xxx: Fix lost interrupts with qlini_mode=disabled [ Upstream commit 4f6aaade2a22ac428fa99ed716cf2b87e79c9837 ] When qla2xxx is loaded with qlini_mode=disabled, ha->flags.disable_msix_handshake is used before it is set, resulting in the wrong interrupt handler being used on certain HBAs (qla2xxx_msix_rsp_q_hs() is used when qla2xxx_msix_rsp_q() should be used). The only difference between these two interrupt handlers is that the _hs() version writes to a register to clear the "RISC" interrupt, whereas the other version does not. So this bug results in the RISC interrupt being cleared when it should not be. This occasionally causes a different interrupt handler qla24xx_msix_default() for a different vector to see ((stat & HSRX_RISC_INT) == 0) and ignore its interrupt, which then causes problems like: qla2xxx [0000:02:00.0]-d04c:6: MBX Command timeout for cmd 20, iocontrol=8 jiffies=1090c0300 mb[0-3]=[0x4000 0x0 0x40 0xda] mb7 0x500 host_status 0x40000010 hccr 0x3f00 qla2xxx [0000:02:00.0]-101e:6: Mailbox cmd timeout occurred, cmd=0x20, mb[0]=0x20. Scheduling ISP abort (the cmd varies; sometimes it is 0x20, 0x22, 0x54, 0x5a, 0x5d, or 0x6a) This problem can be reproduced with a 16 or 32 Gbps HBA by loading qla2xxx with qlini_mode=disabled and running a high IOPS test while triggering frequent RSCN database change events. While analyzing the problem I discovered that even with disable_msix_handshake forced to 0, it is not necessary to clear the RISC interrupt from qla2xxx_msix_rsp_q_hs() (more below). So just completely remove qla2xxx_msix_rsp_q_hs() and the logic for selecting it, which also fixes the bug with qlini_mode=disabled. The test below describes the justification for not needing qla2xxx_msix_rsp_q_hs(): Force disable_msix_handshake to 0: qla24xx_config_rings(): if (0 && (ha->fw_attributes & BIT_6) && (IS_MSIX_NACK_CAPABLE(ha)) && (ha->flags.msix_enabled)) { In qla24xx_msix_rsp_q() and qla2xxx_msix_rsp_q_hs(), check: (rd_reg_dword(®->host_status) & HSRX_RISC_INT) Count the number of calls to each function with HSRX_RISC_INT set and the number with HSRX_RISC_INT not set while performing some I/O. If qla2xxx_msix_rsp_q_hs() clears the RISC interrupt (original code): qla24xx_msix_rsp_q: 50% of calls have HSRX_RISC_INT set qla2xxx_msix_rsp_q_hs: 5% of calls have HSRX_RISC_INT set (# of qla2xxx_msix_rsp_q_hs interrupts) = (# of qla24xx_msix_rsp_q interrupts) * 3 If qla2xxx_msix_rsp_q_hs() does not clear the RISC interrupt (patched code): qla24xx_msix_rsp_q: 100% of calls have HSRX_RISC_INT set qla2xxx_msix_rsp_q_hs: 9% of calls have HSRX_RISC_INT set (# of qla2xxx_msix_rsp_q_hs interrupts) = (# of qla24xx_msix_rsp_q interrupts) * 3 In the case of the original code, qla24xx_msix_rsp_q() was seeing HSRX_RISC_INT set only 50% of the time because qla2xxx_msix_rsp_q_hs() was clearing it when it shouldn't have been. In the patched code, qla24xx_msix_rsp_q() sees HSRX_RISC_INT set 100% of the time, which makes sense if that interrupt handler needs to clear the RISC interrupt (which it does). qla2xxx_msix_rsp_q_hs() sees HSRX_RISC_INT only 9% of the time, which is just overlap from the other interrupt during the high IOPS test. Tested with SCST on: QLE2742 FW:v9.08.02 (32 Gbps 2-port) QLE2694L FW:v9.10.11 (16 Gbps 4-port) QLE2694L FW:v9.08.02 (16 Gbps 4-port) QLE2672 FW:v8.07.12 (16 Gbps 2-port) both initiator and target mode Signed-off-by: Tony Battersby Link: https://patch.msgid.link/56d378eb-14ad-49c7-bae9-c649b6c7691e@cybernetics.com Signed-off-by: Martin K. Petersen Signed-off-by: Sasha Levin --- drivers/scsi/qla2xxx/qla_def.h | 1 - drivers/scsi/qla2xxx/qla_gbl.h | 2 +- drivers/scsi/qla2xxx/qla_isr.c | 32 +++----------------------------- drivers/scsi/qla2xxx/qla_mid.c | 4 +--- 4 files changed, 5 insertions(+), 34 deletions(-) diff --git a/drivers/scsi/qla2xxx/qla_def.h b/drivers/scsi/qla2xxx/qla_def.h index cb95b7b12051da..b3265952c4bed9 100644 --- a/drivers/scsi/qla2xxx/qla_def.h +++ b/drivers/scsi/qla2xxx/qla_def.h @@ -3503,7 +3503,6 @@ struct isp_operations { #define QLA_MSIX_RSP_Q 0x01 #define QLA_ATIO_VECTOR 0x02 #define QLA_MSIX_QPAIR_MULTIQ_RSP_Q 0x03 -#define QLA_MSIX_QPAIR_MULTIQ_RSP_Q_HS 0x04 #define QLA_MIDX_DEFAULT 0 #define QLA_MIDX_RSP_Q 1 diff --git a/drivers/scsi/qla2xxx/qla_gbl.h b/drivers/scsi/qla2xxx/qla_gbl.h index 145defc420f27a..55d531c19e6b22 100644 --- a/drivers/scsi/qla2xxx/qla_gbl.h +++ b/drivers/scsi/qla2xxx/qla_gbl.h @@ -766,7 +766,7 @@ extern int qla2x00_dfs_remove(scsi_qla_host_t *); /* Globa function prototypes for multi-q */ extern int qla25xx_request_irq(struct qla_hw_data *, struct qla_qpair *, - struct qla_msix_entry *, int); + struct qla_msix_entry *); extern int qla25xx_init_req_que(struct scsi_qla_host *, struct req_que *); extern int qla25xx_init_rsp_que(struct scsi_qla_host *, struct rsp_que *); extern int qla25xx_create_req_que(struct qla_hw_data *, uint16_t, uint8_t, diff --git a/drivers/scsi/qla2xxx/qla_isr.c b/drivers/scsi/qla2xxx/qla_isr.c index c4c6b5c6658c07..a3971afc2dd1e9 100644 --- a/drivers/scsi/qla2xxx/qla_isr.c +++ b/drivers/scsi/qla2xxx/qla_isr.c @@ -4467,32 +4467,6 @@ qla2xxx_msix_rsp_q(int irq, void *dev_id) return IRQ_HANDLED; } -irqreturn_t -qla2xxx_msix_rsp_q_hs(int irq, void *dev_id) -{ - struct qla_hw_data *ha; - struct qla_qpair *qpair; - struct device_reg_24xx __iomem *reg; - unsigned long flags; - - qpair = dev_id; - if (!qpair) { - ql_log(ql_log_info, NULL, 0x505b, - "%s: NULL response queue pointer.\n", __func__); - return IRQ_NONE; - } - ha = qpair->hw; - - reg = &ha->iobase->isp24; - spin_lock_irqsave(&ha->hardware_lock, flags); - wrt_reg_dword(®->hccr, HCCRX_CLR_RISC_INT); - spin_unlock_irqrestore(&ha->hardware_lock, flags); - - queue_work(ha->wq, &qpair->q_work); - - return IRQ_HANDLED; -} - /* Interrupt handling helpers. */ struct qla_init_msix_entry { @@ -4505,7 +4479,6 @@ static const struct qla_init_msix_entry msix_entries[] = { { "rsp_q", qla24xx_msix_rsp_q }, { "atio_q", qla83xx_msix_atio_q }, { "qpair_multiq", qla2xxx_msix_rsp_q }, - { "qpair_multiq_hs", qla2xxx_msix_rsp_q_hs }, }; static const struct qla_init_msix_entry qla82xx_msix_entries[] = { @@ -4792,9 +4765,10 @@ qla2x00_free_irqs(scsi_qla_host_t *vha) } int qla25xx_request_irq(struct qla_hw_data *ha, struct qla_qpair *qpair, - struct qla_msix_entry *msix, int vector_type) + struct qla_msix_entry *msix) { - const struct qla_init_msix_entry *intr = &msix_entries[vector_type]; + const struct qla_init_msix_entry *intr = + &msix_entries[QLA_MSIX_QPAIR_MULTIQ_RSP_Q]; scsi_qla_host_t *vha = pci_get_drvdata(ha->pdev); int ret; diff --git a/drivers/scsi/qla2xxx/qla_mid.c b/drivers/scsi/qla2xxx/qla_mid.c index 8b71ac0b1d9995..0abc47e72e0bf1 100644 --- a/drivers/scsi/qla2xxx/qla_mid.c +++ b/drivers/scsi/qla2xxx/qla_mid.c @@ -899,9 +899,7 @@ qla25xx_create_rsp_que(struct qla_hw_data *ha, uint16_t options, rsp->options, rsp->id, rsp->rsp_q_in, rsp->rsp_q_out); - ret = qla25xx_request_irq(ha, qpair, qpair->msix, - ha->flags.disable_msix_handshake ? - QLA_MSIX_QPAIR_MULTIQ_RSP_Q : QLA_MSIX_QPAIR_MULTIQ_RSP_Q_HS); + ret = qla25xx_request_irq(ha, qpair, qpair->msix); if (ret) goto que_failed; From 6407b63db802cf68ee40cd1677a01215216cd62d Mon Sep 17 00:00:00 2001 From: Tony Battersby Date: Mon, 10 Nov 2025 10:48:45 -0500 Subject: [PATCH 1493/3902] scsi: qla2xxx: Fix initiator mode with qlini_mode=exclusive [ Upstream commit 8f58fc64d559b5fda1b0a5e2a71422be61e79ab9 ] When given the module parameter qlini_mode=exclusive, qla2xxx in initiator mode is initially unable to successfully send SCSI commands to devices it finds while scanning, resulting in an escalating series of resets until an adapter reset clears the issue. Fix by checking the active mode instead of the module parameter. Signed-off-by: Tony Battersby Link: https://patch.msgid.link/1715ec14-ba9a-45dc-9cf2-d41aa6b81b5e@cybernetics.com Signed-off-by: Martin K. Petersen Signed-off-by: Sasha Levin --- drivers/scsi/qla2xxx/qla_os.c | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/drivers/scsi/qla2xxx/qla_os.c b/drivers/scsi/qla2xxx/qla_os.c index 70a579cf9c3fd5..6b0f616b1ca0ad 100644 --- a/drivers/scsi/qla2xxx/qla_os.c +++ b/drivers/scsi/qla2xxx/qla_os.c @@ -3460,13 +3460,7 @@ qla2x00_probe_one(struct pci_dev *pdev, const struct pci_device_id *id) ha->mqenable = 0; if (ha->mqenable) { - bool startit = false; - - if (QLA_TGT_MODE_ENABLED()) - startit = false; - - if (ql2x_ini_mode == QLA2XXX_INI_MODE_ENABLED) - startit = true; + bool startit = !!(host->active_mode & MODE_INITIATOR); /* Create start of day qpairs for Block MQ */ for (i = 0; i < ha->max_qpairs; i++) From 05f9aa19fdc169196c22b62ba3d81ba94d8575ea Mon Sep 17 00:00:00 2001 From: Tony Battersby Date: Mon, 10 Nov 2025 10:51:28 -0500 Subject: [PATCH 1494/3902] scsi: qla2xxx: Use reinit_completion on mbx_intr_comp [ Upstream commit 957aa5974989fba4ae4f807ebcb27f12796edd4d ] If a mailbox command completes immediately after wait_for_completion_timeout() times out, ha->mbx_intr_comp could be left in an inconsistent state, causing the next mailbox command not to wait for the hardware. Fix by reinitializing the completion before use. Signed-off-by: Tony Battersby Link: https://patch.msgid.link/11b6485e-0bfd-4784-8f99-c06a196dad94@cybernetics.com Signed-off-by: Martin K. Petersen Signed-off-by: Sasha Levin --- drivers/scsi/qla2xxx/qla_mbx.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/drivers/scsi/qla2xxx/qla_mbx.c b/drivers/scsi/qla2xxx/qla_mbx.c index 32eb0ce8b170d9..1f01576f044b8e 100644 --- a/drivers/scsi/qla2xxx/qla_mbx.c +++ b/drivers/scsi/qla2xxx/qla_mbx.c @@ -253,6 +253,7 @@ qla2x00_mailbox_command(scsi_qla_host_t *vha, mbx_cmd_t *mcp) /* Issue set host interrupt command to send cmd out. */ ha->flags.mbox_int = 0; clear_bit(MBX_INTERRUPT, &ha->mbx_cmd_flags); + reinit_completion(&ha->mbx_intr_comp); /* Unlock mbx registers and wait for interrupt */ ql_dbg(ql_dbg_mbx, vha, 0x100f, @@ -279,6 +280,7 @@ qla2x00_mailbox_command(scsi_qla_host_t *vha, mbx_cmd_t *mcp) "cmd=%x Timeout.\n", command); spin_lock_irqsave(&ha->hardware_lock, flags); clear_bit(MBX_INTR_WAIT, &ha->mbx_cmd_flags); + reinit_completion(&ha->mbx_intr_comp); spin_unlock_irqrestore(&ha->hardware_lock, flags); if (chip_reset != ha->chip_reset) { From b88dd8517ec8de43e03395609236fe3f94680099 Mon Sep 17 00:00:00 2001 From: Bernd Schubert Date: Thu, 23 Oct 2025 00:21:18 +0200 Subject: [PATCH 1495/3902] fuse: Always flush the page cache before FOPEN_DIRECT_IO write [ Upstream commit 1ce120dcefc056ce8af2486cebbb77a458aad4c3 ] This was done as condition on direct_io_allow_mmap, but I believe this is not right, as a file might be open two times - once with write-back enabled another time with FOPEN_DIRECT_IO. Signed-off-by: Bernd Schubert Signed-off-by: Miklos Szeredi Signed-off-by: Sasha Levin --- fs/fuse/file.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fs/fuse/file.c b/fs/fuse/file.c index f1ef77a0be05bb..c5c82b3807911a 100644 --- a/fs/fuse/file.c +++ b/fs/fuse/file.c @@ -1607,7 +1607,7 @@ ssize_t fuse_direct_io(struct fuse_io_priv *io, struct iov_iter *iter, if (!ia) return -ENOMEM; - if (fopen_direct_io && fc->direct_io_allow_mmap) { + if (fopen_direct_io) { res = filemap_write_and_wait_range(mapping, pos, pos + count - 1); if (res) { fuse_io_free(ia); From ea0c31c521439d8c16cdebe305fa7c9d90811566 Mon Sep 17 00:00:00 2001 From: Bernd Schubert Date: Thu, 23 Oct 2025 00:21:17 +0200 Subject: [PATCH 1496/3902] fuse: Invalidate the page cache after FOPEN_DIRECT_IO write [ Upstream commit b359af8275a982a458e8df6c6beab1415be1f795 ] generic_file_direct_write() also does this and has a large comment about. Reproducer here is xfstest's generic/209, which is exactly to have competing DIO write and cached IO read. Signed-off-by: Bernd Schubert Signed-off-by: Miklos Szeredi Signed-off-by: Sasha Levin --- fs/fuse/file.c | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/fs/fuse/file.c b/fs/fuse/file.c index c5c82b3807911a..bb4ecfd469a5e8 100644 --- a/fs/fuse/file.c +++ b/fs/fuse/file.c @@ -1681,6 +1681,15 @@ ssize_t fuse_direct_io(struct fuse_io_priv *io, struct iov_iter *iter, if (res > 0) *ppos = pos; + if (res > 0 && write && fopen_direct_io) { + /* + * As in generic_file_direct_write(), invalidate after the + * write, to invalidate read-ahead cache that may have competed + * with the write. + */ + invalidate_inode_pages2_range(mapping, idx_from, idx_to); + } + return res > 0 ? res : err; } EXPORT_SYMBOL_GPL(fuse_direct_io); From c6a2dd4f2e4e6cbdfe7a1618160281af897b75db Mon Sep 17 00:00:00 2001 From: Li Qiang Date: Sun, 28 Sep 2025 16:33:32 +0800 Subject: [PATCH 1497/3902] via_wdt: fix critical boot hang due to unnamed resource allocation [ Upstream commit 7aa31ee9ec92915926e74731378c009c9cc04928 ] The VIA watchdog driver uses allocate_resource() to reserve a MMIO region for the watchdog control register. However, the allocated resource was not given a name, which causes the kernel resource tree to contain an entry marked as "" under /proc/iomem on x86 platforms. During boot, this unnamed resource can lead to a critical hang because subsequent resource lookups and conflict checks fail to handle the invalid entry properly. Signed-off-by: Li Qiang Reviewed-by: Guenter Roeck Signed-off-by: Guenter Roeck Signed-off-by: Wim Van Sebroeck Signed-off-by: Sasha Levin --- drivers/watchdog/via_wdt.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/watchdog/via_wdt.c b/drivers/watchdog/via_wdt.c index d647923d68fede..f5557639265184 100644 --- a/drivers/watchdog/via_wdt.c +++ b/drivers/watchdog/via_wdt.c @@ -165,6 +165,7 @@ static int wdt_probe(struct pci_dev *pdev, dev_err(&pdev->dev, "cannot enable PCI device\n"); return -ENODEV; } + wdt_res.name = "via_wdt"; /* * Allocate a MMIO region which contains watchdog control register From b49c766856fb5901490de577e046149ebf15e39d Mon Sep 17 00:00:00 2001 From: Al Viro Date: Fri, 14 Nov 2025 02:18:22 -0500 Subject: [PATCH 1498/3902] functionfs: fix the open/removal races [ Upstream commit e5bf5ee266633cb18fff6f98f0b7d59a62819eee ] ffs_epfile_open() can race with removal, ending up with file->private_data pointing to freed object. There is a total count of opened files on functionfs (both ep0 and dynamic ones) and when it hits zero, dynamic files get removed. Unfortunately, that removal can happen while another thread is in ffs_epfile_open(), but has not incremented the count yet. In that case open will succeed, leaving us with UAF on any subsequent read() or write(). The root cause is that ffs->opened is misused; atomic_dec_and_test() vs. atomic_add_return() is not a good idea, when object remains visible all along. To untangle that * serialize openers on ffs->mutex (both for ep0 and for dynamic files) * have dynamic ones use atomic_inc_not_zero() and fail if we had zero ->opened; in that case the file we are opening is doomed. * have the inodes of dynamic files marked on removal (from the callback of simple_recursive_removal()) - clear ->i_private there. * have open of dynamic ones verify they hadn't been already removed, along with checking that state is FFS_ACTIVE. Reviewed-by: Greg Kroah-Hartman Signed-off-by: Al Viro Signed-off-by: Sasha Levin --- drivers/usb/gadget/function/f_fs.c | 53 ++++++++++++++++++++++++------ 1 file changed, 43 insertions(+), 10 deletions(-) diff --git a/drivers/usb/gadget/function/f_fs.c b/drivers/usb/gadget/function/f_fs.c index 47cfbe41fdff8d..69f6e3c0f7e00d 100644 --- a/drivers/usb/gadget/function/f_fs.c +++ b/drivers/usb/gadget/function/f_fs.c @@ -640,13 +640,22 @@ static ssize_t ffs_ep0_read(struct file *file, char __user *buf, static int ffs_ep0_open(struct inode *inode, struct file *file) { - struct ffs_data *ffs = inode->i_private; + struct ffs_data *ffs = inode->i_sb->s_fs_info; + int ret; - if (ffs->state == FFS_CLOSING) - return -EBUSY; + /* Acquire mutex */ + ret = ffs_mutex_lock(&ffs->mutex, file->f_flags & O_NONBLOCK); + if (ret < 0) + return ret; - file->private_data = ffs; ffs_data_opened(ffs); + if (ffs->state == FFS_CLOSING) { + ffs_data_closed(ffs); + mutex_unlock(&ffs->mutex); + return -EBUSY; + } + mutex_unlock(&ffs->mutex); + file->private_data = ffs; return stream_open(inode, file); } @@ -1193,14 +1202,33 @@ static ssize_t ffs_epfile_io(struct file *file, struct ffs_io_data *io_data) static int ffs_epfile_open(struct inode *inode, struct file *file) { - struct ffs_epfile *epfile = inode->i_private; + struct ffs_data *ffs = inode->i_sb->s_fs_info; + struct ffs_epfile *epfile; + int ret; - if (WARN_ON(epfile->ffs->state != FFS_ACTIVE)) + /* Acquire mutex */ + ret = ffs_mutex_lock(&ffs->mutex, file->f_flags & O_NONBLOCK); + if (ret < 0) + return ret; + + if (!atomic_inc_not_zero(&ffs->opened)) { + mutex_unlock(&ffs->mutex); + return -ENODEV; + } + /* + * we want the state to be FFS_ACTIVE; FFS_ACTIVE alone is + * not enough, though - we might have been through FFS_CLOSING + * and back to FFS_ACTIVE, with our file already removed. + */ + epfile = smp_load_acquire(&inode->i_private); + if (unlikely(ffs->state != FFS_ACTIVE || !epfile)) { + mutex_unlock(&ffs->mutex); + ffs_data_closed(ffs); return -ENODEV; + } + mutex_unlock(&ffs->mutex); file->private_data = epfile; - ffs_data_opened(epfile->ffs); - return stream_open(inode, file); } @@ -1332,7 +1360,7 @@ static void ffs_dmabuf_put(struct dma_buf_attachment *attach) static int ffs_epfile_release(struct inode *inode, struct file *file) { - struct ffs_epfile *epfile = inode->i_private; + struct ffs_epfile *epfile = file->private_data; struct ffs_dmabuf_priv *priv, *tmp; struct ffs_data *ffs = epfile->ffs; @@ -2352,6 +2380,11 @@ static int ffs_epfiles_create(struct ffs_data *ffs) return 0; } +static void clear_one(struct dentry *dentry) +{ + smp_store_release(&dentry->d_inode->i_private, NULL); +} + static void ffs_epfiles_destroy(struct ffs_epfile *epfiles, unsigned count) { struct ffs_epfile *epfile = epfiles; @@ -2359,7 +2392,7 @@ static void ffs_epfiles_destroy(struct ffs_epfile *epfiles, unsigned count) for (; count; --count, ++epfile) { BUG_ON(mutex_is_locked(&epfile->mutex)); if (epfile->dentry) { - simple_recursive_removal(epfile->dentry, NULL); + simple_recursive_removal(epfile->dentry, clear_one); epfile->dentry = NULL; } } From f04d53f6e3ff2d682cbc9d6c05e7daf935232856 Mon Sep 17 00:00:00 2001 From: Encrow Thorne Date: Mon, 10 Nov 2025 14:10:37 +0800 Subject: [PATCH 1499/3902] reset: fix BIT macro reference [ Upstream commit f3d8b64ee46c9b4b0b82b1a4642027728bac95b8 ] RESET_CONTROL_FLAGS_BIT_* macros use BIT(), but reset.h does not include bits.h. This causes compilation errors when including reset.h standalone. Include bits.h to make reset.h self-contained. Suggested-by: Troy Mitchell Reviewed-by: Troy Mitchell Reviewed-by: Philipp Zabel Signed-off-by: Encrow Thorne Signed-off-by: Philipp Zabel Signed-off-by: Sasha Levin --- include/linux/reset.h | 1 + 1 file changed, 1 insertion(+) diff --git a/include/linux/reset.h b/include/linux/reset.h index 840d75d172f623..44f9e3415f92c9 100644 --- a/include/linux/reset.h +++ b/include/linux/reset.h @@ -2,6 +2,7 @@ #ifndef _LINUX_RESET_H_ #define _LINUX_RESET_H_ +#include #include #include #include From ff3e06e80c9e4c81e0038155c5a185e9a0079dbb Mon Sep 17 00:00:00 2001 From: Yuezhang Mo Date: Fri, 28 Nov 2025 17:51:10 +0800 Subject: [PATCH 1500/3902] exfat: fix remount failure in different process environments [ Upstream commit 51fc7b4ce10ccab8ea5e4876bcdc42cf5202a0ef ] The kernel test robot reported that the exFAT remount operation failed. The reason for the failure was that the process's umask is different between mount and remount, causing fs_fmask and fs_dmask are changed. Potentially, both gid and uid may also be changed. Therefore, when initializing fs_context for remount, inherit these mount options from the options used during mount. Reported-by: kernel test robot Closes: https://lore.kernel.org/oe-lkp/202511251637.81670f5c-lkp@intel.com Signed-off-by: Yuezhang Mo Signed-off-by: Namjae Jeon Signed-off-by: Sasha Levin --- fs/exfat/super.c | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/fs/exfat/super.c b/fs/exfat/super.c index 74d451f732c732..581754001128b7 100644 --- a/fs/exfat/super.c +++ b/fs/exfat/super.c @@ -813,10 +813,21 @@ static int exfat_init_fs_context(struct fs_context *fc) ratelimit_state_init(&sbi->ratelimit, DEFAULT_RATELIMIT_INTERVAL, DEFAULT_RATELIMIT_BURST); - sbi->options.fs_uid = current_uid(); - sbi->options.fs_gid = current_gid(); - sbi->options.fs_fmask = current->fs->umask; - sbi->options.fs_dmask = current->fs->umask; + if (fc->purpose == FS_CONTEXT_FOR_RECONFIGURE && fc->root) { + struct super_block *sb = fc->root->d_sb; + struct exfat_mount_options *cur_opts = &EXFAT_SB(sb)->options; + + sbi->options.fs_uid = cur_opts->fs_uid; + sbi->options.fs_gid = cur_opts->fs_gid; + sbi->options.fs_fmask = cur_opts->fs_fmask; + sbi->options.fs_dmask = cur_opts->fs_dmask; + } else { + sbi->options.fs_uid = current_uid(); + sbi->options.fs_gid = current_gid(); + sbi->options.fs_fmask = current->fs->umask; + sbi->options.fs_dmask = current->fs->umask; + } + sbi->options.allow_utime = -1; sbi->options.errors = EXFAT_ERRORS_RO; exfat_set_iocharset(&sbi->options, exfat_default_iocharset); From 967ac825b4f61ee6a268faedb6cd6393ad903210 Mon Sep 17 00:00:00 2001 From: Yuezhang Mo Date: Mon, 27 Oct 2025 17:03:41 +0800 Subject: [PATCH 1501/3902] exfat: zero out post-EOF page cache on file extension [ Upstream commit 4e163c39dd4e70fcdce948b8774d96e0482b4a11 ] xfstests generic/363 was failing due to unzeroed post-EOF page cache that allowed mmap writes beyond EOF to become visible after file extension. For example, in following xfs_io sequence, 0x22 should not be written to the file but would become visible after the extension: xfs_io -f -t -c "pwrite -S 0x11 0 8" \ -c "mmap 0 4096" \ -c "mwrite -S 0x22 32 32" \ -c "munmap" \ -c "pwrite -S 0x33 512 32" \ $testfile This violates the expected behavior where writes beyond EOF via mmap should not persist after the file is extended. Instead, the extended region should contain zeros. Fix this by using truncate_pagecache() to truncate the page cache after the current EOF when extending the file. Signed-off-by: Yuezhang Mo Signed-off-by: Namjae Jeon Signed-off-by: Sasha Levin --- fs/exfat/file.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/fs/exfat/file.c b/fs/exfat/file.c index adc37b4d7fc2d0..536c8078f0c192 100644 --- a/fs/exfat/file.c +++ b/fs/exfat/file.c @@ -25,6 +25,8 @@ static int exfat_cont_expand(struct inode *inode, loff_t size) struct exfat_sb_info *sbi = EXFAT_SB(sb); struct exfat_chain clu; + truncate_pagecache(inode, i_size_read(inode)); + ret = inode_newsize_ok(inode, size); if (ret) return ret; @@ -639,6 +641,9 @@ static ssize_t exfat_file_write_iter(struct kiocb *iocb, struct iov_iter *iter) inode_lock(inode); + if (pos > i_size_read(inode)) + truncate_pagecache(inode, i_size_read(inode)); + valid_size = ei->valid_size; ret = generic_write_checks(iocb, iter); From 1681cf6da695b9cc1c4f3411b230e1af98d6e1d3 Mon Sep 17 00:00:00 2001 From: Lizhi Xu Date: Tue, 16 Sep 2025 09:41:43 +0800 Subject: [PATCH 1502/3902] usbip: Fix locking bug in RT-enabled kernels [ Upstream commit 09bf21bf5249880f62fe759b53b14b4b52900c6c ] Interrupts are disabled before entering usb_hcd_giveback_urb(). A spinlock_t becomes a sleeping lock on PREEMPT_RT, so it cannot be acquired with disabled interrupts. Save the interrupt status and restore it after usb_hcd_giveback_urb(). syz reported: BUG: sleeping function called from invalid context at kernel/locking/spinlock_rt.c:48 Call Trace: dump_stack_lvl+0x189/0x250 lib/dump_stack.c:120 rt_spin_lock+0xc7/0x2c0 kernel/locking/spinlock_rt.c:57 spin_lock include/linux/spinlock_rt.h:44 [inline] mon_bus_complete drivers/usb/mon/mon_main.c:134 [inline] mon_complete+0x5c/0x200 drivers/usb/mon/mon_main.c:147 usbmon_urb_complete include/linux/usb/hcd.h:738 [inline] __usb_hcd_giveback_urb+0x254/0x5e0 drivers/usb/core/hcd.c:1647 vhci_urb_enqueue+0xb4f/0xe70 drivers/usb/usbip/vhci_hcd.c:818 Reported-by: syzbot+205ef33a3b636b4181fb@syzkaller.appspotmail.com Closes: https://syzkaller.appspot.com/bug?extid=205ef33a3b636b4181fb Signed-off-by: Lizhi Xu Acked-by: Shuah Khan Link: https://lore.kernel.org/r/20250916014143.1439759-1-lizhi.xu@windriver.com Signed-off-by: Greg Kroah-Hartman Signed-off-by: Sasha Levin --- drivers/usb/usbip/vhci_hcd.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/drivers/usb/usbip/vhci_hcd.c b/drivers/usb/usbip/vhci_hcd.c index 0d6c10a8490c0b..f7e405abe60849 100644 --- a/drivers/usb/usbip/vhci_hcd.c +++ b/drivers/usb/usbip/vhci_hcd.c @@ -831,15 +831,15 @@ static int vhci_urb_enqueue(struct usb_hcd *hcd, struct urb *urb, gfp_t mem_flag no_need_xmit: usb_hcd_unlink_urb_from_ep(hcd, urb); no_need_unlink: - spin_unlock_irqrestore(&vhci->lock, flags); if (!ret) { /* usb_hcd_giveback_urb() should be called with * irqs disabled */ - local_irq_disable(); + spin_unlock(&vhci->lock); usb_hcd_giveback_urb(hcd, urb, urb->status); - local_irq_enable(); + spin_lock(&vhci->lock); } + spin_unlock_irqrestore(&vhci->lock, flags); return ret; } From 132fe187e0d940f388f839fe2cde9b84106ad20d Mon Sep 17 00:00:00 2001 From: Mark Pearson Date: Thu, 21 Aug 2025 14:53:07 -0400 Subject: [PATCH 1503/3902] usb: typec: ucsi: Handle incorrect num_connectors capability [ Upstream commit 30cd2cb1abf4c4acdb1ddb468c946f68939819fb ] The UCSI spec states that the num_connectors field is 7 bits, and the 8th bit is reserved and should be set to zero. Some buggy FW has been known to set this bit, and it can lead to a system not booting. Flag that the FW is not behaving correctly, and auto-fix the value so that the system boots correctly. Found on Lenovo P1 G8 during Linux enablement program. The FW will be fixed, but seemed worth addressing in case it hit platforms that aren't officially Linux supported. Signed-off-by: Mark Pearson Reviewed-by: Heikki Krogerus Link: https://lore.kernel.org/r/20250821185319.2585023-1-mpearson-lenovo@squebb.ca Signed-off-by: Greg Kroah-Hartman Signed-off-by: Sasha Levin --- drivers/usb/typec/ucsi/ucsi.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/drivers/usb/typec/ucsi/ucsi.c b/drivers/usb/typec/ucsi/ucsi.c index 3f568f790f39b0..3995483a0aa097 100644 --- a/drivers/usb/typec/ucsi/ucsi.c +++ b/drivers/usb/typec/ucsi/ucsi.c @@ -1807,6 +1807,12 @@ static int ucsi_init(struct ucsi *ucsi) ret = -ENODEV; goto err_reset; } + /* Check if reserved bit set. This is out of spec but happens in buggy FW */ + if (ucsi->cap.num_connectors & 0x80) { + dev_warn(ucsi->dev, "UCSI: Invalid num_connectors %d. Likely buggy FW\n", + ucsi->cap.num_connectors); + ucsi->cap.num_connectors &= 0x7f; // clear bit and carry on + } /* Allocate the connectors. Released in ucsi_unregister() */ connector = kcalloc(ucsi->cap.num_connectors + 1, sizeof(*connector), GFP_KERNEL); From 41374236a556d25266f4f3d70d857ab658e5b063 Mon Sep 17 00:00:00 2001 From: Pei Xiao Date: Tue, 14 Oct 2025 17:12:50 +0800 Subject: [PATCH 1504/3902] iio: adc: ti_am335x_adc: Limit step_avg to valid range for gcc complains [ Upstream commit c9fb952360d0c78bbe98239bd6b702f05c2dbb31 ] FIELD_PREP() checks that a value fits into the available bitfield, add a check for step_avg to fix gcc complains. which gcc complains about: drivers/iio/adc/ti_am335x_adc.c: In function 'tiadc_step_config': include/linux/compiler_types.h:572:38: error: call to '__compiletime_assert_491' declared with attribute error: FIELD_PREP: value too large for the field include/linux/mfd/ti_am335x_tscadc.h:58:29: note: in expansion of macro 'FIELD_PREP' #define STEPCONFIG_AVG(val) FIELD_PREP(GENMASK(4, 2), (val)) ^~~~~~~~~~ drivers/iio/adc/ti_am335x_adc.c:127:17: note: in expansion of macro 'STEPCONFIG_AVG' stepconfig = STEPCONFIG_AVG(ffs(adc_dev->step_avg[i]) - 1) Reported-by: kernel test robot Closes: https://lore.kernel.org/oe-kbuild-all/202510102117.Jqxrw1vF-lkp@intel.com/ Signed-off-by: Pei Xiao Signed-off-by: Jonathan Cameron Signed-off-by: Sasha Levin --- drivers/iio/adc/ti_am335x_adc.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/iio/adc/ti_am335x_adc.c b/drivers/iio/adc/ti_am335x_adc.c index 99f274adc870dc..a1a28584de9303 100644 --- a/drivers/iio/adc/ti_am335x_adc.c +++ b/drivers/iio/adc/ti_am335x_adc.c @@ -123,7 +123,7 @@ static void tiadc_step_config(struct iio_dev *indio_dev) chan = adc_dev->channel_line[i]; - if (adc_dev->step_avg[i]) + if (adc_dev->step_avg[i] && adc_dev->step_avg[i] <= STEPCONFIG_AVG_16) stepconfig = STEPCONFIG_AVG(ffs(adc_dev->step_avg[i]) - 1) | STEPCONFIG_FIFO1; else From 6dc09b9b211b35e8c9372a777499f0612a4f0cd5 Mon Sep 17 00:00:00 2001 From: Hongyu Xie Date: Wed, 19 Nov 2025 16:23:55 +0200 Subject: [PATCH 1505/3902] usb: xhci: limit run_graceperiod for only usb 3.0 devices [ Upstream commit 8d34983720155b8f05de765f0183d9b0e1345cc0 ] run_graceperiod blocks usb 2.0 devices from auto suspending after xhci_start for 500ms. Log shows: [ 13.387170] xhci_hub_control:1271: xhci-hcd PNP0D10:03: Get port status 7-1 read: 0x2a0, return 0x100 [ 13.387177] hub_event:5779: hub 7-0:1.0: state 7 ports 1 chg 0000 evt 0000 [ 13.387182] hub_suspend:3903: hub 7-0:1.0: hub_suspend [ 13.387188] hcd_bus_suspend:2250: usb usb7: bus auto-suspend, wakeup 1 [ 13.387191] hcd_bus_suspend:2279: usb usb7: suspend raced with wakeup event [ 13.387193] hcd_bus_resume:2303: usb usb7: usb auto-resume [ 13.387296] hub_event:5779: hub 3-0:1.0: state 7 ports 1 chg 0000 evt 0000 [ 13.393343] handle_port_status:2034: xhci-hcd PNP0D10:02: handle_port_status: starting usb5 port polling. [ 13.393353] xhci_hub_control:1271: xhci-hcd PNP0D10:02: Get port status 5-1 read: 0x206e1, return 0x10101 [ 13.400047] hub_suspend:3903: hub 3-0:1.0: hub_suspend [ 13.403077] hub_resume:3948: hub 7-0:1.0: hub_resume [ 13.403080] xhci_hub_control:1271: xhci-hcd PNP0D10:03: Get port status 7-1 read: 0x2a0, return 0x100 [ 13.403085] hub_event:5779: hub 7-0:1.0: state 7 ports 1 chg 0000 evt 0000 [ 13.403087] hub_suspend:3903: hub 7-0:1.0: hub_suspend [ 13.403090] hcd_bus_suspend:2250: usb usb7: bus auto-suspend, wakeup 1 [ 13.403093] hcd_bus_suspend:2279: usb usb7: suspend raced with wakeup event [ 13.403095] hcd_bus_resume:2303: usb usb7: usb auto-resume [ 13.405002] handle_port_status:1913: xhci-hcd PNP0D10:04: Port change event, 9-1, id 1, portsc: 0x6e1 [ 13.405016] hub_activate:1169: usb usb5-port1: status 0101 change 0001 [ 13.405026] xhci_clear_port_change_bit:658: xhci-hcd PNP0D10:02: clear port1 connect change, portsc: 0x6e1 [ 13.413275] hcd_bus_suspend:2250: usb usb3: bus auto-suspend, wakeup 1 [ 13.419081] hub_resume:3948: hub 7-0:1.0: hub_resume [ 13.419086] xhci_hub_control:1271: xhci-hcd PNP0D10:03: Get port status 7-1 read: 0x2a0, return 0x100 [ 13.419095] hub_event:5779: hub 7-0:1.0: state 7 ports 1 chg 0000 evt 0000 [ 13.419100] hub_suspend:3903: hub 7-0:1.0: hub_suspend [ 13.419106] hcd_bus_suspend:2250: usb usb7: bus auto-suspend, wakeup 1 [ 13.419110] hcd_bus_suspend:2279: usb usb7: suspend raced with wakeup event [ 13.419112] hcd_bus_resume:2303: usb usb7: usb auto-resume [ 13.420455] handle_port_status:2034: xhci-hcd PNP0D10:04: handle_port_status: starting usb9 port polling. [ 13.420493] handle_port_status:1913: xhci-hcd PNP0D10:05: Port change event, 10-1, id 1, portsc: 0x6e1 [ 13.425332] hcd_bus_suspend:2279: usb usb3: suspend raced with wakeup event [ 13.431931] handle_port_status:2034: xhci-hcd PNP0D10:05: handle_port_status: starting usb10 port polling. [ 13.435080] hub_resume:3948: hub 7-0:1.0: hub_resume [ 13.435084] xhci_hub_control:1271: xhci-hcd PNP0D10:03: Get port status 7-1 read: 0x2a0, return 0x100 [ 13.435092] hub_event:5779: hub 7-0:1.0: state 7 ports 1 chg 0000 evt 0000 [ 13.435096] hub_suspend:3903: hub 7-0:1.0: hub_suspend [ 13.435102] hcd_bus_suspend:2250: usb usb7: bus auto-suspend, wakeup 1 [ 13.435106] hcd_bus_suspend:2279: usb usb7: suspend raced with wakeup event usb7 and other usb 2.0 root hub were rapidly toggling between suspend and resume states. More, "suspend raced with wakeup event" confuses people. So, limit run_graceperiod for only usb 3.0 devices Signed-off-by: Hongyu Xie Signed-off-by: Mathias Nyman Link: https://patch.msgid.link/20251119142417.2820519-2-mathias.nyman@linux.intel.com Signed-off-by: Greg Kroah-Hartman Signed-off-by: Sasha Levin --- drivers/usb/host/xhci-hub.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/usb/host/xhci-hub.c b/drivers/usb/host/xhci-hub.c index b3a59ce1b3f41f..5e1442e91743da 100644 --- a/drivers/usb/host/xhci-hub.c +++ b/drivers/usb/host/xhci-hub.c @@ -1671,7 +1671,7 @@ int xhci_hub_status_data(struct usb_hcd *hcd, char *buf) * SS devices are only visible to roothub after link training completes. * Keep polling roothubs for a grace period after xHC start */ - if (xhci->run_graceperiod) { + if (hcd->speed >= HCD_USB3 && xhci->run_graceperiod) { if (time_before(jiffies, xhci->run_graceperiod)) status = 1; else From e375aa7f5a1eea7f5a5887da0bd156939b00cc17 Mon Sep 17 00:00:00 2001 From: Chen Changcheng Date: Fri, 21 Nov 2025 14:40:20 +0800 Subject: [PATCH 1506/3902] usb: usb-storage: No additional quirks need to be added to the EL-R12 optical drive. [ Upstream commit 955a48a5353f4fe009704a9a4272a3adf627cd35 ] The optical drive of EL-R12 has the same vid and pid as INIC-3069, as follows: T: Bus=02 Lev=02 Prnt=02 Port=01 Cnt=01 Dev#= 3 Spd=5000 MxCh= 0 D: Ver= 3.00 Cls=00(>ifc ) Sub=00 Prot=00 MxPS= 9 #Cfgs= 1 P: Vendor=13fd ProdID=3940 Rev= 3.10 S: Manufacturer=HL-DT-ST S: Product= DVD+-RW GT80N S: SerialNumber=423349524E4E38303338323439202020 C:* #Ifs= 1 Cfg#= 1 Atr=80 MxPwr=144mA I:* If#= 0 Alt= 0 #EPs= 2 Cls=08(stor.) Sub=02 Prot=50 Driver=usb-storage E: Ad=83(I) Atr=02(Bulk) MxPS=1024 Ivl=0ms E: Ad=0a(O) Atr=02(Bulk) MxPS=1024 Ivl=0ms This will result in the optical drive device also adding the quirks of US_FL_NO_ATA_1X. When performing an erase operation, it will fail, and the reason for the failure is as follows: [ 388.967742] sr 5:0:0:0: [sr0] tag#0 Send: scmd 0x00000000d20c33a7 [ 388.967742] sr 5:0:0:0: [sr0] tag#0 CDB: ATA command pass through(12)/Blank a1 11 00 00 00 00 00 00 00 00 00 00 [ 388.967773] sr 5:0:0:0: [sr0] tag#0 Done: SUCCESS Result: hostbyte=DID_TARGET_FAILURE driverbyte=DRIVER_OK cmd_age=0s [ 388.967773] sr 5:0:0:0: [sr0] tag#0 CDB: ATA command pass through(12)/Blank a1 11 00 00 00 00 00 00 00 00 00 00 [ 388.967803] sr 5:0:0:0: [sr0] tag#0 Sense Key : Illegal Request [current] [ 388.967803] sr 5:0:0:0: [sr0] tag#0 Add. Sense: Invalid field in cdb [ 388.967803] sr 5:0:0:0: [sr0] tag#0 scsi host busy 1 failed 0 [ 388.967803] sr 5:0:0:0: Notifying upper driver of completion (result 8100002) [ 388.967834] sr 5:0:0:0: [sr0] tag#0 0 sectors total, 0 bytes done. For the EL-R12 standard optical drive, all operational commands and usage scenarios were tested without adding the IGNORE_RESIDUE quirks, and no issues were encountered. It can be reasonably concluded that removing the IGNORE_RESIDUE quirks has no impact. Signed-off-by: Chen Changcheng Link: https://patch.msgid.link/20251121064020.29332-1-chenchangcheng@kylinos.cn Signed-off-by: Greg Kroah-Hartman Signed-off-by: Sasha Levin --- drivers/usb/storage/unusual_uas.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/usb/storage/unusual_uas.h b/drivers/usb/storage/unusual_uas.h index 1477e31d776327..b695f5ba9a4097 100644 --- a/drivers/usb/storage/unusual_uas.h +++ b/drivers/usb/storage/unusual_uas.h @@ -98,7 +98,7 @@ UNUSUAL_DEV(0x125f, 0xa94a, 0x0160, 0x0160, US_FL_NO_ATA_1X), /* Reported-by: Benjamin Tissoires */ -UNUSUAL_DEV(0x13fd, 0x3940, 0x0000, 0x9999, +UNUSUAL_DEV(0x13fd, 0x3940, 0x0309, 0x0309, "Initio Corporation", "INIC-3069", USB_SC_DEVICE, USB_PR_DEVICE, NULL, From e63d2173887c1c7c66898fecd35d4a9eceffd063 Mon Sep 17 00:00:00 2001 From: Michal Pecio Date: Wed, 19 Nov 2025 16:24:04 +0200 Subject: [PATCH 1507/3902] usb: xhci: Don't unchain link TRBs on quirky HCs [ Upstream commit e6aec6d9f5794e85d2312497a5d81296d885090e ] Some old HCs ignore transfer ring link TRBs whose chain bit is unset. This breaks endpoint operation and sometimes makes it execute other ring's TDs, which may corrupt their buffers or cause unwanted device action. We avoid this by chaining all link TRBs on affected rings. Fix an omission which allows them to be unchained by cancelling TDs. The patch was tested by reproducing this condition on an isochronous endpoint (non-power-of-two TDs are sometimes split not to cross 64K) and printing link TRBs in trb_to_noop() on good and buggy HCs. Actual hardware malfunction is rare since it requires Missed Service Error shortly before the unchained link TRB, at least on NEC and AMD. I have never seen it after commit bb0ba4cb1065 ("usb: xhci: Apply the link chain quirk on NEC isoc endpoints"), but it's Russian roulette and I can't test all affected hosts and workloads. Fairly often MSEs happen after cancellation because the endpoint was stopped. Signed-off-by: Michal Pecio Signed-off-by: Mathias Nyman Link: https://patch.msgid.link/20251119142417.2820519-11-mathias.nyman@linux.intel.com Signed-off-by: Greg Kroah-Hartman Signed-off-by: Sasha Levin --- drivers/usb/host/xhci-ring.c | 27 ++++++++++++++++----------- 1 file changed, 16 insertions(+), 11 deletions(-) diff --git a/drivers/usb/host/xhci-ring.c b/drivers/usb/host/xhci-ring.c index 5bdcf9ab2b99d7..25185552287c05 100644 --- a/drivers/usb/host/xhci-ring.c +++ b/drivers/usb/host/xhci-ring.c @@ -128,11 +128,11 @@ static void inc_td_cnt(struct urb *urb) urb_priv->num_tds_done++; } -static void trb_to_noop(union xhci_trb *trb, u32 noop_type) +static void trb_to_noop(union xhci_trb *trb, u32 noop_type, bool unchain_links) { if (trb_is_link(trb)) { - /* unchain chained link TRBs */ - trb->link.control &= cpu_to_le32(~TRB_CHAIN); + if (unchain_links) + trb->link.control &= cpu_to_le32(~TRB_CHAIN); } else { trb->generic.field[0] = 0; trb->generic.field[1] = 0; @@ -465,7 +465,7 @@ static void xhci_handle_stopped_cmd_ring(struct xhci_hcd *xhci, xhci_dbg(xhci, "Turn aborted command %p to no-op\n", i_cmd->command_trb); - trb_to_noop(i_cmd->command_trb, TRB_CMD_NOOP); + trb_to_noop(i_cmd->command_trb, TRB_CMD_NOOP, false); /* * caller waiting for completion is called when command @@ -797,13 +797,18 @@ static int xhci_move_dequeue_past_td(struct xhci_hcd *xhci, * (The last TRB actually points to the ring enqueue pointer, which is not part * of this TD.) This is used to remove partially enqueued isoc TDs from a ring. */ -static void td_to_noop(struct xhci_td *td, bool flip_cycle) +static void td_to_noop(struct xhci_hcd *xhci, struct xhci_virt_ep *ep, + struct xhci_td *td, bool flip_cycle) { + bool unchain_links; struct xhci_segment *seg = td->start_seg; union xhci_trb *trb = td->start_trb; + /* link TRBs should now be unchained, but some old HCs expect otherwise */ + unchain_links = !xhci_link_chain_quirk(xhci, ep->ring ? ep->ring->type : TYPE_STREAM); + while (1) { - trb_to_noop(trb, TRB_TR_NOOP); + trb_to_noop(trb, TRB_TR_NOOP, unchain_links); /* flip cycle if asked to */ if (flip_cycle && trb != td->start_trb && trb != td->end_trb) @@ -1091,16 +1096,16 @@ static int xhci_invalidate_cancelled_tds(struct xhci_virt_ep *ep) "Found multiple active URBs %p and %p in stream %u?\n", td->urb, cached_td->urb, td->urb->stream_id); - td_to_noop(cached_td, false); + td_to_noop(xhci, ep, cached_td, false); cached_td->cancel_status = TD_CLEARED; } - td_to_noop(td, false); + td_to_noop(xhci, ep, td, false); td->cancel_status = TD_CLEARING_CACHE; cached_td = td; break; } } else { - td_to_noop(td, false); + td_to_noop(xhci, ep, td, false); td->cancel_status = TD_CLEARED; } } @@ -1125,7 +1130,7 @@ static int xhci_invalidate_cancelled_tds(struct xhci_virt_ep *ep) continue; xhci_warn(xhci, "Failed to clear cancelled cached URB %p, mark clear anyway\n", td->urb); - td_to_noop(td, false); + td_to_noop(xhci, ep, td, false); td->cancel_status = TD_CLEARED; } } @@ -4273,7 +4278,7 @@ static int xhci_queue_isoc_tx(struct xhci_hcd *xhci, gfp_t mem_flags, */ urb_priv->td[0].end_trb = ep_ring->enqueue; /* Every TRB except the first & last will have its cycle bit flipped. */ - td_to_noop(&urb_priv->td[0], true); + td_to_noop(xhci, xep, &urb_priv->td[0], true); /* Reset the ring enqueue back to the first TRB and its cycle bit. */ ep_ring->enqueue = urb_priv->td[0].start_trb; From ad9f0f01373c985b61e0e6be5e85685ab2a11635 Mon Sep 17 00:00:00 2001 From: Wenhua Lin Date: Wed, 22 Oct 2025 11:08:40 +0800 Subject: [PATCH 1508/3902] serial: sprd: Return -EPROBE_DEFER when uart clock is not ready [ Upstream commit 29e8a0c587e328ed458380a45d6028adf64d7487 ] In sprd_clk_init(), when devm_clk_get() returns -EPROBE_DEFER for either uart or source clock, we should propagate the error instead of just warning and continuing with NULL clocks. Currently the driver only emits a warning when clock acquisition fails and proceeds with NULL clock pointers. This can lead to issues later when the clocks are actually needed. More importantly, when the clock provider is not ready yet and returns -EPROBE_DEFER, we should return this error to allow deferred probing. This change adds explicit checks for -EPROBE_DEFER after both: 1. devm_clk_get(uport->dev, uart) 2. devm_clk_get(uport->dev, source) When -EPROBE_DEFER is encountered, the function now returns -EPROBE_DEFER to let the driver framework retry probing later when the clock dependencies are resolved. Signed-off-by: Wenhua Lin Link: https://patch.msgid.link/20251022030840.956589-1-Wenhua.Lin@unisoc.com Reviewed-by: Cixi Geng Signed-off-by: Greg Kroah-Hartman Signed-off-by: Sasha Levin --- drivers/tty/serial/sprd_serial.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/drivers/tty/serial/sprd_serial.c b/drivers/tty/serial/sprd_serial.c index 8c9366321f8e73..092755f356836a 100644 --- a/drivers/tty/serial/sprd_serial.c +++ b/drivers/tty/serial/sprd_serial.c @@ -1133,6 +1133,9 @@ static int sprd_clk_init(struct uart_port *uport) clk_uart = devm_clk_get(uport->dev, "uart"); if (IS_ERR(clk_uart)) { + if (PTR_ERR(clk_uart) == -EPROBE_DEFER) + return -EPROBE_DEFER; + dev_warn(uport->dev, "uart%d can't get uart clock\n", uport->line); clk_uart = NULL; @@ -1140,6 +1143,9 @@ static int sprd_clk_init(struct uart_port *uport) clk_parent = devm_clk_get(uport->dev, "source"); if (IS_ERR(clk_parent)) { + if (PTR_ERR(clk_parent) == -EPROBE_DEFER) + return -EPROBE_DEFER; + dev_warn(uport->dev, "uart%d can't get source clock\n", uport->line); clk_parent = NULL; From 9a9a9b88a8d8120ac139fc18057cdf088cb3d9b0 Mon Sep 17 00:00:00 2001 From: Ian Rogers Date: Wed, 3 Dec 2025 13:47:01 -0800 Subject: [PATCH 1509/3902] libperf cpumap: Fix perf_cpu_map__max for an empty/NULL map [ Upstream commit a0a4173631bfcfd3520192c0a61cf911d6a52c3a ] Passing an empty map to perf_cpu_map__max triggered a SEGV. Explicitly test for the empty map. Reported-by: Ingo Molnar Closes: https://lore.kernel.org/linux-perf-users/aSwt7yzFjVJCEmVp@gmail.com/ Tested-by: Ingo Molnar Signed-off-by: Ian Rogers Tested-by: Thomas Richter Signed-off-by: Namhyung Kim Signed-off-by: Sasha Levin --- tools/lib/perf/cpumap.c | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/tools/lib/perf/cpumap.c b/tools/lib/perf/cpumap.c index b20a5280f2b333..2bbbe1c782b8a4 100644 --- a/tools/lib/perf/cpumap.c +++ b/tools/lib/perf/cpumap.c @@ -368,10 +368,12 @@ struct perf_cpu perf_cpu_map__max(const struct perf_cpu_map *map) .cpu = -1 }; - // cpu_map__trim_new() qsort()s it, cpu_map__default_new() sorts it as well. - return __perf_cpu_map__nr(map) > 0 - ? __perf_cpu_map__cpu(map, __perf_cpu_map__nr(map) - 1) - : result; + if (!map) + return result; + + // The CPUs are always sorted and nr is always > 0 as 0 length map is + // encoded as NULL. + return __perf_cpu_map__cpu(map, __perf_cpu_map__nr(map) - 1); } /** Is 'b' a subset of 'a'. */ From 7b85814424575ef499fd99071a6444615b52f6fe Mon Sep 17 00:00:00 2001 From: Jens Reidel Date: Fri, 19 Sep 2025 14:34:32 +0200 Subject: [PATCH 1510/3902] clk: qcom: dispcc-sm7150: Fix dispcc_mdss_pclk0_clk_src [ Upstream commit e3c13e0caa8ceb7dec1a7c4fcfd9dbef56a69fbe ] Set CLK_OPS_PARENT_ENABLE to ensure the parent gets prepared and enabled when switching to it, fixing an "rcg didn't update its configuration" warning. Signed-off-by: Jens Reidel Reviewed-by: Dmitry Baryshkov Link: https://lore.kernel.org/r/20250919-sm7150-dispcc-fixes-v1-3-308ad47c5fce@mainlining.org Signed-off-by: Bjorn Andersson Signed-off-by: Sasha Levin --- drivers/clk/qcom/dispcc-sm7150.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/clk/qcom/dispcc-sm7150.c b/drivers/clk/qcom/dispcc-sm7150.c index bdfff246ed3fe0..ddc7230b8aea77 100644 --- a/drivers/clk/qcom/dispcc-sm7150.c +++ b/drivers/clk/qcom/dispcc-sm7150.c @@ -356,7 +356,7 @@ static struct clk_rcg2 dispcc_mdss_pclk0_clk_src = { .name = "dispcc_mdss_pclk0_clk_src", .parent_data = dispcc_parent_data_4, .num_parents = ARRAY_SIZE(dispcc_parent_data_4), - .flags = CLK_SET_RATE_PARENT, + .flags = CLK_SET_RATE_PARENT | CLK_OPS_PARENT_ENABLE, .ops = &clk_pixel_ops, }, }; From ef7653481a059e12c42e01893fd15497330b2e4d Mon Sep 17 00:00:00 2001 From: Jinhui Guo Date: Tue, 21 Oct 2025 15:57:14 +0800 Subject: [PATCH 1511/3902] i2c: designware: Disable SMBus interrupts to prevent storms from mis-configured firmware [ Upstream commit d3429178ee51dd7155445d15a5ab87a45fae3c73 ] When probing the I2C master, disable SMBus interrupts to prevent storms caused by broken firmware mis-configuring IC_SMBUS=1; the handler never services them and a mis-configured SMBUS Master extend-clock timeout or SMBUS Slave extend-clock timeout can flood the CPU. Signed-off-by: Jinhui Guo Reviewed-by: Andy Shevchenko Acked-by: Mika Westerberg Signed-off-by: Andi Shyti Link: https://lore.kernel.org/r/20251021075714.3712-2-guojinhui.liam@bytedance.com Signed-off-by: Sasha Levin --- drivers/i2c/busses/i2c-designware-core.h | 1 + drivers/i2c/busses/i2c-designware-master.c | 7 +++++++ 2 files changed, 8 insertions(+) diff --git a/drivers/i2c/busses/i2c-designware-core.h b/drivers/i2c/busses/i2c-designware-core.h index 347843b4f5dd7a..436555543c79d0 100644 --- a/drivers/i2c/busses/i2c-designware-core.h +++ b/drivers/i2c/busses/i2c-designware-core.h @@ -78,6 +78,7 @@ #define DW_IC_TX_ABRT_SOURCE 0x80 #define DW_IC_ENABLE_STATUS 0x9c #define DW_IC_CLR_RESTART_DET 0xa8 +#define DW_IC_SMBUS_INTR_MASK 0xcc #define DW_IC_COMP_PARAM_1 0xf4 #define DW_IC_COMP_VERSION 0xf8 #define DW_IC_SDA_HOLD_MIN_VERS 0x3131312A /* "111*" == v1.11* */ diff --git a/drivers/i2c/busses/i2c-designware-master.c b/drivers/i2c/busses/i2c-designware-master.c index 41e9b5ecad2013..45bfca05bb300f 100644 --- a/drivers/i2c/busses/i2c-designware-master.c +++ b/drivers/i2c/busses/i2c-designware-master.c @@ -220,6 +220,13 @@ static int i2c_dw_init_master(struct dw_i2c_dev *dev) /* Disable the adapter */ __i2c_dw_disable(dev); + /* + * Mask SMBus interrupts to block storms from broken + * firmware that leaves IC_SMBUS=1; the handler never + * services them. + */ + regmap_write(dev->map, DW_IC_SMBUS_INTR_MASK, 0); + /* Write standard speed timing parameters */ regmap_write(dev->map, DW_IC_SS_SCL_HCNT, dev->ss_hcnt); regmap_write(dev->map, DW_IC_SS_SCL_LCNT, dev->ss_lcnt); From 3b96dc29e385edd25b3f2ec9f3c705702a7dc982 Mon Sep 17 00:00:00 2001 From: "Derek J. Clark" Date: Thu, 27 Nov 2025 07:16:05 -0800 Subject: [PATCH 1512/3902] platform/x86: wmi-gamezone: Add Legion Go 2 Quirks MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [ Upstream commit 55715d7ad5e772d621c3201da3895f250591bce8 ] Add Legion Go 2 SKU's to the Extreme Mode quirks table. Signed-off-by: Derek J. Clark Reviewed-by: Armin Wolf Reviewed-by: Mark Pearson Link: https://patch.msgid.link/20251127151605.1018026-4-derekjohn.clark@gmail.com Reviewed-by: Ilpo Järvinen Signed-off-by: Ilpo Järvinen Signed-off-by: Sasha Levin --- drivers/platform/x86/lenovo/wmi-gamezone.c | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/drivers/platform/x86/lenovo/wmi-gamezone.c b/drivers/platform/x86/lenovo/wmi-gamezone.c index 0eb7fe8222f4ae..b26806b37d960f 100644 --- a/drivers/platform/x86/lenovo/wmi-gamezone.c +++ b/drivers/platform/x86/lenovo/wmi-gamezone.c @@ -274,8 +274,23 @@ static const struct dmi_system_id fwbug_list[] = { }, .driver_data = &quirk_no_extreme_bug, }, + { + .ident = "Legion Go 8ASP2", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"), + DMI_MATCH(DMI_PRODUCT_VERSION, "Legion Go 8ASP2"), + }, + .driver_data = &quirk_no_extreme_bug, + }, + { + .ident = "Legion Go 8AHP2", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"), + DMI_MATCH(DMI_PRODUCT_VERSION, "Legion Go 8AHP2"), + }, + .driver_data = &quirk_no_extreme_bug, + }, {}, - }; /** From 4f7d90c7184a14dda6c1d38b5017ed0184357e9a Mon Sep 17 00:00:00 2001 From: Daniel Wagner Date: Thu, 30 Oct 2025 11:05:45 +0100 Subject: [PATCH 1513/3902] nvme-fc: don't hold rport lock when putting ctrl [ Upstream commit b71cbcf7d170e51148d5467820ae8a72febcb651 ] nvme_fc_ctrl_put can acquire the rport lock when freeing the ctrl object: nvme_fc_ctrl_put nvme_fc_ctrl_free spin_lock_irqsave(rport->lock) Thus we can't hold the rport lock when calling nvme_fc_ctrl_put. Justin suggested use the safe list iterator variant because nvme_fc_ctrl_put will also modify the rport->list. Cc: Justin Tee Reviewed-by: Christoph Hellwig Signed-off-by: Daniel Wagner Signed-off-by: Keith Busch Signed-off-by: Sasha Levin --- drivers/nvme/host/fc.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/drivers/nvme/host/fc.c b/drivers/nvme/host/fc.c index 2c903729b0b909..8324230c53719e 100644 --- a/drivers/nvme/host/fc.c +++ b/drivers/nvme/host/fc.c @@ -1468,14 +1468,14 @@ nvme_fc_match_disconn_ls(struct nvme_fc_rport *rport, { struct fcnvme_ls_disconnect_assoc_rqst *rqst = &lsop->rqstbuf->rq_dis_assoc; - struct nvme_fc_ctrl *ctrl, *ret = NULL; + struct nvme_fc_ctrl *ctrl, *tmp, *ret = NULL; struct nvmefc_ls_rcv_op *oldls = NULL; u64 association_id = be64_to_cpu(rqst->associd.association_id); unsigned long flags; spin_lock_irqsave(&rport->lock, flags); - list_for_each_entry(ctrl, &rport->ctrl_list, ctrl_list) { + list_for_each_entry_safe(ctrl, tmp, &rport->ctrl_list, ctrl_list) { if (!nvme_fc_ctrl_get(ctrl)) continue; spin_lock(&ctrl->lock); @@ -1488,7 +1488,9 @@ nvme_fc_match_disconn_ls(struct nvme_fc_rport *rport, if (ret) /* leave the ctrl get reference */ break; + spin_unlock_irqrestore(&rport->lock, flags); nvme_fc_ctrl_put(ctrl); + spin_lock_irqsave(&rport->lock, flags); } spin_unlock_irqrestore(&rport->lock, flags); From 76bced5efbf429c1f70475b4e29840b08ad816f2 Mon Sep 17 00:00:00 2001 From: Pei Xiao Date: Fri, 5 Dec 2025 11:15:13 +0800 Subject: [PATCH 1514/3902] hwmon: (emc2305) fix device node refcount leak in error path [ Upstream commit 4910da6b36b122db50a27fabf6ab7f8611b60bf8 ] The for_each_child_of_node() macro automatically manages device node reference counts during normal iteration. However, when breaking out of the loop early with return, the current iteration's node is not automatically released, leading to a reference count leak. Fix this by adding of_node_put(child) before returning from the loop when emc2305_set_single_tz() fails. This issue could lead to memory leaks over multiple probe cycles. Signed-off-by: Pei Xiao Link: https://lore.kernel.org/r/tencent_5CDC08544C901D5ECA270573D5AEE3117108@qq.com Signed-off-by: Guenter Roeck Signed-off-by: Sasha Levin --- drivers/hwmon/emc2305.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/drivers/hwmon/emc2305.c b/drivers/hwmon/emc2305.c index 60809289f8169d..84cb9b72cb6c2b 100644 --- a/drivers/hwmon/emc2305.c +++ b/drivers/hwmon/emc2305.c @@ -685,8 +685,10 @@ static int emc2305_probe(struct i2c_client *client) i = 0; for_each_child_of_node(dev->of_node, child) { ret = emc2305_set_single_tz(dev, child, i); - if (ret != 0) + if (ret != 0) { + of_node_put(child); return ret; + } i++; } } else { From 6c95c2e00996ce1d1aedf437311d1d3526c05d04 Mon Sep 17 00:00:00 2001 From: Justin Tee Date: Mon, 17 Nov 2025 10:43:43 -0800 Subject: [PATCH 1515/3902] nvme-fabrics: add ENOKEY to no retry criteria for authentication failures [ Upstream commit 13989207ee29c40501e719512e8dc90768325895 ] With authentication, in addition to EKEYREJECTED there is also no point in retrying reconnects when status is ENOKEY. Thus, add -ENOKEY as another criteria to determine when to stop retries. Cc: Daniel Wagner Cc: Hannes Reinecke Closes: https://lore.kernel.org/linux-nvme/20250829-nvme-fc-sync-v3-0-d69c87e63aee@kernel.org/ Signed-off-by: Justin Tee Tested-by: Daniel Wagner Reviewed-by: Daniel Wagner Reviewed-by: Hannes Reinecke Signed-off-by: Keith Busch Signed-off-by: Sasha Levin --- drivers/nvme/host/fabrics.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/nvme/host/fabrics.c b/drivers/nvme/host/fabrics.c index 2e58a7ce109052..55a8afd2efd503 100644 --- a/drivers/nvme/host/fabrics.c +++ b/drivers/nvme/host/fabrics.c @@ -592,7 +592,7 @@ bool nvmf_should_reconnect(struct nvme_ctrl *ctrl, int status) if (status > 0 && (status & NVME_STATUS_DNR)) return false; - if (status == -EKEYREJECTED) + if (status == -EKEYREJECTED || status == -ENOKEY) return false; if (ctrl->opts->max_reconnects == -1 || From c3cd1d514eb03c55c986a20bee8ea86c96682aff Mon Sep 17 00:00:00 2001 From: Pei Xiao Date: Fri, 5 Dec 2025 10:02:41 +0800 Subject: [PATCH 1516/3902] hwmon: (emc2305) fix double put in emc2305_probe_childs_from_dt [ Upstream commit 541dfb49dcb80c2509e030842de77adfb77820f5 ] ./drivers/hwmon/emc2305.c:597:4-15: ERROR: probable double put Device node iterators put the previous value of the index variable, so an explicit put causes a double put. Signed-off-by: Pei Xiao Link: https://lore.kernel.org/r/tencent_CD373F952BE48697C949E39CB5EB77841D06@qq.com Signed-off-by: Guenter Roeck Signed-off-by: Sasha Levin --- drivers/hwmon/emc2305.c | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/drivers/hwmon/emc2305.c b/drivers/hwmon/emc2305.c index 84cb9b72cb6c2b..ceae96c07ac45b 100644 --- a/drivers/hwmon/emc2305.c +++ b/drivers/hwmon/emc2305.c @@ -593,10 +593,8 @@ static int emc2305_probe_childs_from_dt(struct device *dev) for_each_child_of_node(dev->of_node, child) { if (of_property_present(child, "reg")) { ret = emc2305_of_parse_pwm_child(dev, child, data); - if (ret) { - of_node_put(child); + if (ret) continue; - } count++; } } From 6455d25857f21841daa33bf8d732ebc134280a02 Mon Sep 17 00:00:00 2001 From: "Chia-Lin Kao (AceLan)" Date: Thu, 27 Nov 2025 15:04:07 +0800 Subject: [PATCH 1517/3902] platform/x86/intel/hid: Add Dell Pro Rugged 10/12 tablet to VGBS DMI quirks MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [ Upstream commit b169e1733cadb614e87f69d7a5ae1b186c50d313 ] Dell Pro Rugged 10/12 tablets has a reliable VGBS method. If VGBS is not called on boot, the on-screen keyboard won't appear if the device is booted without a keyboard. Call VGBS on boot on thess devices to get the initial state of SW_TABLET_MODE in a reliable way. Signed-off-by: Chia-Lin Kao (AceLan) Reviewed-by: Hans de Goede Link: https://patch.msgid.link/20251127070407.656463-1-acelan.kao@canonical.com Reviewed-by: Ilpo Järvinen Signed-off-by: Ilpo Järvinen Signed-off-by: Sasha Levin --- drivers/platform/x86/intel/hid.c | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/drivers/platform/x86/intel/hid.c b/drivers/platform/x86/intel/hid.c index 9c07a7faf18fee..560cc063198e10 100644 --- a/drivers/platform/x86/intel/hid.c +++ b/drivers/platform/x86/intel/hid.c @@ -177,6 +177,18 @@ static const struct dmi_system_id dmi_vgbs_allow_list[] = { DMI_MATCH(DMI_PRODUCT_NAME, "HP Elite Dragonfly G2 Notebook PC"), }, }, + { + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."), + DMI_MATCH(DMI_PRODUCT_NAME, "Dell Pro Rugged 10 Tablet RA00260"), + }, + }, + { + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."), + DMI_MATCH(DMI_PRODUCT_NAME, "Dell Pro Rugged 12 Tablet RA02260"), + }, + }, { } }; From 7f39b9d0e86ed6236b9a5fb67616ab1f76c4f150 Mon Sep 17 00:00:00 2001 From: Gregory CLEMENT Date: Fri, 28 Nov 2025 09:30:06 +0100 Subject: [PATCH 1518/3902] MIPS: ftrace: Fix memory corruption when kernel is located beyond 32 bits [ Upstream commit 36dac9a3dda1f2bae343191bc16b910c603cac25 ] Since commit e424054000878 ("MIPS: Tracing: Reduce the overhead of dynamic Function Tracer"), the macro UASM_i_LA_mostly has been used, and this macro can generate more than 2 instructions. At the same time, the code in ftrace assumes that no more than 2 instructions can be generated, which is why it stores them in an int[2] array. However, as previously noted, the macro UASM_i_LA_mostly (and now UASM_i_LA) causes a buffer overflow when _mcount is beyond 32 bits. This leads to corruption of the variables located in the __read_mostly section. This corruption was observed because the variable __cpu_primary_thread_mask was corrupted, causing a hang very early during boot. This fix prevents the corruption by avoiding the generation of instructions if they could exceed 2 instructions in length. Fortunately, insn_la_mcount is only used if the instrumented code is located outside the kernel code section, so dynamic ftrace can still be used, albeit in a more limited scope. This is still preferable to corrupting memory and/or crashing the kernel. Signed-off-by: Gregory CLEMENT Signed-off-by: Thomas Bogendoerfer Signed-off-by: Sasha Levin --- arch/mips/kernel/ftrace.c | 25 +++++++++++++++++++++---- 1 file changed, 21 insertions(+), 4 deletions(-) diff --git a/arch/mips/kernel/ftrace.c b/arch/mips/kernel/ftrace.c index f39e85fd58fa99..b15615b2856907 100644 --- a/arch/mips/kernel/ftrace.c +++ b/arch/mips/kernel/ftrace.c @@ -54,10 +54,20 @@ static inline void ftrace_dyn_arch_init_insns(void) u32 *buf; unsigned int v1; - /* la v1, _mcount */ - v1 = 3; - buf = (u32 *)&insn_la_mcount[0]; - UASM_i_LA(&buf, v1, MCOUNT_ADDR); + /* If we are not in compat space, the number of generated + * instructions will exceed the maximum expected limit of 2. + * To prevent buffer overflow, we avoid generating them. + * insn_la_mcount will not be used later in ftrace_make_call. + */ + if (uasm_in_compat_space_p(MCOUNT_ADDR)) { + /* la v1, _mcount */ + v1 = 3; + buf = (u32 *)&insn_la_mcount[0]; + UASM_i_LA(&buf, v1, MCOUNT_ADDR); + } else { + pr_warn("ftrace: mcount address beyond 32 bits is not supported (%lX)\n", + MCOUNT_ADDR); + } /* jal (ftrace_caller + 8), jump over the first two instruction */ buf = (u32 *)&insn_jal_ftrace_caller; @@ -189,6 +199,13 @@ int ftrace_make_call(struct dyn_ftrace *rec, unsigned long addr) unsigned int new; unsigned long ip = rec->ip; + /* When the code to patch does not belong to the kernel code + * space, we must use insn_la_mcount. However, if MCOUNT_ADDR + * is not in compat space, insn_la_mcount is not usable. + */ + if (!core_kernel_text(ip) && !uasm_in_compat_space_p(MCOUNT_ADDR)) + return -EFAULT; + new = core_kernel_text(ip) ? insn_jal_ftrace_caller : insn_la_mcount[0]; #ifdef CONFIG_64BIT From e64d5d45ffa80ef61f762dfa357aea5d6dc0e245 Mon Sep 17 00:00:00 2001 From: John Garry Date: Thu, 11 Dec 2025 10:06:51 +0000 Subject: [PATCH 1519/3902] scsi: scsi_debug: Fix atomic write enable module param description [ Upstream commit 1f7d6e2efeedd8f545d3e0e9bf338023bf4ea584 ] The atomic write enable module param is "atomic_wr", and not "atomic_write", so fix the module param description. Fixes: 84f3a3c01d70 ("scsi: scsi_debug: Atomic write support") Signed-off-by: John Garry Reviewed-by: Bart Van Assche Link: https://patch.msgid.link/20251211100651.9056-1-john.g.garry@oracle.com Signed-off-by: Martin K. Petersen Signed-off-by: Sasha Levin --- drivers/scsi/scsi_debug.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/scsi/scsi_debug.c b/drivers/scsi/scsi_debug.c index b2ab97be5db3d4..047d56d23beab4 100644 --- a/drivers/scsi/scsi_debug.c +++ b/drivers/scsi/scsi_debug.c @@ -7410,7 +7410,7 @@ MODULE_PARM_DESC(lbprz, MODULE_PARM_DESC(lbpu, "enable LBP, support UNMAP command (def=0)"); MODULE_PARM_DESC(lbpws, "enable LBP, support WRITE SAME(16) with UNMAP bit (def=0)"); MODULE_PARM_DESC(lbpws10, "enable LBP, support WRITE SAME(10) with UNMAP bit (def=0)"); -MODULE_PARM_DESC(atomic_write, "enable ATOMIC WRITE support, support WRITE ATOMIC(16) (def=0)"); +MODULE_PARM_DESC(atomic_wr, "enable ATOMIC WRITE support, support WRITE ATOMIC(16) (def=0)"); MODULE_PARM_DESC(lowest_aligned, "lowest aligned lba (def=0)"); MODULE_PARM_DESC(lun_format, "LUN format: 0->peripheral (def); 1 --> flat address method"); MODULE_PARM_DESC(max_luns, "number of LUNs per target to simulate(def=1)"); From 19648135e904bce447d368ecb6136e5da809639c Mon Sep 17 00:00:00 2001 From: Neil Armstrong Date: Mon, 17 Nov 2025 15:51:35 +0100 Subject: [PATCH 1520/3902] drm/msm: adreno: fix deferencing ifpc_reglist when not declared [ Upstream commit 129049d4fe22c998ae9fd1ec479fbb4ed5338c15 ] On plaforms with an a7xx GPU not supporting IFPC, the ifpc_reglist if still deferenced in a7xx_patch_pwrup_reglist() which causes a kernel crash: Unable to handle kernel NULL pointer dereference at virtual address 0000000000000008 ... pc : a6xx_hw_init+0x155c/0x1e4c [msm] lr : a6xx_hw_init+0x9a8/0x1e4c [msm] ... Call trace: a6xx_hw_init+0x155c/0x1e4c [msm] (P) msm_gpu_hw_init+0x58/0x88 [msm] adreno_load_gpu+0x94/0x1fc [msm] msm_open+0xe4/0xf4 [msm] drm_file_alloc+0x1a0/0x2e4 [drm] drm_client_init+0x7c/0x104 [drm] drm_fbdev_client_setup+0x94/0xcf0 [drm_client_lib] drm_client_setup+0xb4/0xd8 [drm_client_lib] msm_drm_kms_post_init+0x2c/0x3c [msm] msm_drm_init+0x1a4/0x228 [msm] msm_drm_bind+0x30/0x3c [msm] ... Check the validity of ifpc_reglist before deferencing the table to setup the register values. Fixes: a6a0157cc68e ("drm/msm/a6xx: Enable IFPC on Adreno X1-85") Signed-off-by: Neil Armstrong Reviewed-by: Akhil P Oommen Patchwork: https://patchwork.freedesktop.org/patch/688944/ Message-ID: <20251117-topic-sm8x50-fix-a6xx-non-ifpc-v1-1-e4473cbf5903@linaro.org> Signed-off-by: Rob Clark Signed-off-by: Sasha Levin --- drivers/gpu/drm/msm/adreno/a6xx_gpu.c | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/drivers/gpu/drm/msm/adreno/a6xx_gpu.c b/drivers/gpu/drm/msm/adreno/a6xx_gpu.c index 6f7ed07670b12c..d1eaa849b19783 100644 --- a/drivers/gpu/drm/msm/adreno/a6xx_gpu.c +++ b/drivers/gpu/drm/msm/adreno/a6xx_gpu.c @@ -837,15 +837,17 @@ static void a7xx_patch_pwrup_reglist(struct msm_gpu *gpu) lock->gpu_req = lock->cpu_req = lock->turn = 0; reglist = adreno_gpu->info->a6xx->ifpc_reglist; - lock->ifpc_list_len = reglist->count; + if (reglist) { + lock->ifpc_list_len = reglist->count; - /* - * For each entry in each of the lists, write the offset and the current - * register value into the GPU buffer - */ - for (i = 0; i < reglist->count; i++) { - *dest++ = reglist->regs[i]; - *dest++ = gpu_read(gpu, reglist->regs[i]); + /* + * For each entry in each of the lists, write the offset and the current + * register value into the GPU buffer + */ + for (i = 0; i < reglist->count; i++) { + *dest++ = reglist->regs[i]; + *dest++ = gpu_read(gpu, reglist->regs[i]); + } } reglist = adreno_gpu->info->a6xx->pwrup_reglist; From 2c46497eb148ec61909f4101b8443f3c4c2daaec Mon Sep 17 00:00:00 2001 From: Alok Tiwari Date: Thu, 13 Nov 2025 00:28:31 -0800 Subject: [PATCH 1521/3902] drm/msm/a6xx: move preempt_prepare_postamble after error check [ Upstream commit ef3b04091fd8bc737dc45312375df8625b8318e2 ] Move the call to preempt_prepare_postamble() after verifying that preempt_postamble_ptr is valid. If preempt_postamble_ptr is NULL, dereferencing it in preempt_prepare_postamble() would lead to a crash. This change avoids calling the preparation function when the postamble allocation has failed, preventing potential NULL pointer dereference and ensuring proper error handling. Fixes: 50117cad0c50 ("drm/msm/a6xx: Use posamble to reset counters on preemption") Signed-off-by: Alok Tiwari Patchwork: https://patchwork.freedesktop.org/patch/687659/ Message-ID: <20251113082839.3821867-1-alok.a.tiwari@oracle.com> Signed-off-by: Rob Clark Signed-off-by: Sasha Levin --- drivers/gpu/drm/msm/adreno/a6xx_preempt.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/gpu/drm/msm/adreno/a6xx_preempt.c b/drivers/gpu/drm/msm/adreno/a6xx_preempt.c index afc5f4aa3b1733..747a22afad9f6f 100644 --- a/drivers/gpu/drm/msm/adreno/a6xx_preempt.c +++ b/drivers/gpu/drm/msm/adreno/a6xx_preempt.c @@ -454,11 +454,11 @@ void a6xx_preempt_init(struct msm_gpu *gpu) gpu->vm, &a6xx_gpu->preempt_postamble_bo, &a6xx_gpu->preempt_postamble_iova); - preempt_prepare_postamble(a6xx_gpu); - if (IS_ERR(a6xx_gpu->preempt_postamble_ptr)) goto fail; + preempt_prepare_postamble(a6xx_gpu); + timer_setup(&a6xx_gpu->preempt_timer, a6xx_preempt_timer, 0); return; From 13456b4f1033d911f8bf3a0a1195656f293ba0f6 Mon Sep 17 00:00:00 2001 From: Caleb Sander Mateos Date: Fri, 12 Dec 2025 17:19:49 -0700 Subject: [PATCH 1522/3902] ublk: clean up user copy references on ublk server exit [ Upstream commit daa24603d9f0808929514ee62ced30052ca7221c ] If a ublk server process releases a ublk char device file, any requests dispatched to the ublk server but not yet completed will retain a ref value of UBLK_REFCOUNT_INIT. Before commit e63d2228ef83 ("ublk: simplify aborting ublk request"), __ublk_fail_req() would decrement the reference count before completing the failed request. However, that commit optimized __ublk_fail_req() to call __ublk_complete_rq() directly without decrementing the request reference count. The leaked reference count incorrectly allows user copy and zero copy operations on the completed ublk request. It also triggers the WARN_ON_ONCE(refcount_read(&io->ref)) warnings in ublk_queue_reinit() and ublk_deinit_queue(). Commit c5c5eb24ed61 ("ublk: avoid ublk_io_release() called after ublk char dev is closed") already fixed the issue for ublk devices using UBLK_F_SUPPORT_ZERO_COPY or UBLK_F_AUTO_BUF_REG. However, the reference count leak also affects UBLK_F_USER_COPY, the other reference-counted data copy mode. Fix the condition in ublk_check_and_reset_active_ref() to include all reference-counted data copy modes. This ensures that any ublk requests still owned by the ublk server when it exits have their reference counts reset to 0. Signed-off-by: Caleb Sander Mateos Fixes: e63d2228ef83 ("ublk: simplify aborting ublk request") Reviewed-by: Ming Lei Signed-off-by: Jens Axboe Signed-off-by: Sasha Levin --- drivers/block/ublk_drv.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/drivers/block/ublk_drv.c b/drivers/block/ublk_drv.c index fa7b0481ea0463..d8079ea8f8ca12 100644 --- a/drivers/block/ublk_drv.c +++ b/drivers/block/ublk_drv.c @@ -1674,8 +1674,7 @@ static bool ublk_check_and_reset_active_ref(struct ublk_device *ub) { int i, j; - if (!(ub->dev_info.flags & (UBLK_F_SUPPORT_ZERO_COPY | - UBLK_F_AUTO_BUF_REG))) + if (!ublk_dev_need_req_ref(ub)) return false; for (i = 0; i < ub->dev_info.nr_hw_queues; i++) { From 74dd8b66f9d242e9a79a54d67605e7aa97def214 Mon Sep 17 00:00:00 2001 From: Dan Carpenter Date: Sat, 20 Dec 2025 11:46:10 +0300 Subject: [PATCH 1523/3902] block: rnbd-clt: Fix signedness bug in init_dev() [ Upstream commit 1ddb815fdfd45613c32e9bd1f7137428f298e541 ] The "dev->clt_device_id" variable is set using ida_alloc_max() which returns an int and in particular it returns negative error codes. Change the type from u32 to int to fix the error checking. Fixes: c9b5645fd8ca ("block: rnbd-clt: Fix leaked ID in init_dev()") Signed-off-by: Dan Carpenter Signed-off-by: Jens Axboe Signed-off-by: Sasha Levin --- drivers/block/rnbd/rnbd-clt.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/block/rnbd/rnbd-clt.h b/drivers/block/rnbd/rnbd-clt.h index a48e040abe639d..fbc1ed766025c1 100644 --- a/drivers/block/rnbd/rnbd-clt.h +++ b/drivers/block/rnbd/rnbd-clt.h @@ -112,7 +112,7 @@ struct rnbd_clt_dev { struct rnbd_queue *hw_queues; u32 device_id; /* local Idr index - used to track minor number allocations. */ - u32 clt_device_id; + int clt_device_id; struct mutex lock; enum rnbd_clt_dev_state dev_state; refcount_t refcount; From f8b19571b1db094525a7d4db9e16df2a1b4a6cfd Mon Sep 17 00:00:00 2001 From: Stefano Garzarella Date: Wed, 26 Nov 2025 14:38:26 +0100 Subject: [PATCH 1524/3902] vhost/vsock: improve RCU read sections around vhost_vsock_get() [ Upstream commit d8ee3cfdc89b75dc059dc21c27bef2c1440f67eb ] vhost_vsock_get() uses hash_for_each_possible_rcu() to find the `vhost_vsock` associated with the `guest_cid`. hash_for_each_possible_rcu() should only be called within an RCU read section, as mentioned in the following comment in include/linux/rculist.h: /** * hlist_for_each_entry_rcu - iterate over rcu list of given type * @pos: the type * to use as a loop cursor. * @head: the head for your list. * @member: the name of the hlist_node within the struct. * @cond: optional lockdep expression if called from non-RCU protection. * * This list-traversal primitive may safely run concurrently with * the _rcu list-mutation primitives such as hlist_add_head_rcu() * as long as the traversal is guarded by rcu_read_lock(). */ Currently, all calls to vhost_vsock_get() are between rcu_read_lock() and rcu_read_unlock() except for calls in vhost_vsock_set_cid() and vhost_vsock_reset_orphans(). In both cases, the current code is safe, but we can make improvements to make it more robust. About vhost_vsock_set_cid(), when building the kernel with CONFIG_PROVE_RCU_LIST enabled, we get the following RCU warning when the user space issues `ioctl(dev, VHOST_VSOCK_SET_GUEST_CID, ...)` : WARNING: suspicious RCU usage 6.18.0-rc7 #62 Not tainted ----------------------------- drivers/vhost/vsock.c:74 RCU-list traversed in non-reader section!! other info that might help us debug this: rcu_scheduler_active = 2, debug_locks = 1 1 lock held by rpc-libvirtd/3443: #0: ffffffffc05032a8 (vhost_vsock_mutex){+.+.}-{4:4}, at: vhost_vsock_dev_ioctl+0x2ff/0x530 [vhost_vsock] stack backtrace: CPU: 2 UID: 0 PID: 3443 Comm: rpc-libvirtd Not tainted 6.18.0-rc7 #62 PREEMPT(none) Hardware name: QEMU Standard PC (i440FX + PIIX, 1996), BIOS 1.17.0-7.fc42 06/10/2025 Call Trace: dump_stack_lvl+0x75/0xb0 dump_stack+0x14/0x1a lockdep_rcu_suspicious.cold+0x4e/0x97 vhost_vsock_get+0x8f/0xa0 [vhost_vsock] vhost_vsock_dev_ioctl+0x307/0x530 [vhost_vsock] __x64_sys_ioctl+0x4f2/0xa00 x64_sys_call+0xed0/0x1da0 do_syscall_64+0x73/0xfa0 entry_SYSCALL_64_after_hwframe+0x76/0x7e ... This is not a real problem, because the vhost_vsock_get() caller, i.e. vhost_vsock_set_cid(), holds the `vhost_vsock_mutex` used by the hash table writers. Anyway, to prevent that warning, add lockdep_is_held() condition to hash_for_each_possible_rcu() to verify that either the caller is in an RCU read section or `vhost_vsock_mutex` is held when CONFIG_PROVE_RCU_LIST is enabled; and also clarify the comment for vhost_vsock_get() to better describe the locking requirements and the scope of the returned pointer validity. About vhost_vsock_reset_orphans(), currently this function is only called via vsock_for_each_connected_socket(), which holds the `vsock_table_lock` spinlock (which is also an RCU read-side critical section). However, add an explicit RCU read lock there to make the code more robust and explicit about the RCU requirements, and to prevent issues if the calling context changes in the future or if vhost_vsock_reset_orphans() is called from other contexts. Fixes: 834e772c8db0 ("vhost/vsock: fix use-after-free in network stack callers") Cc: stefanha@redhat.com Signed-off-by: Stefano Garzarella Reviewed-by: Stefan Hajnoczi Message-Id: <20251126133826.142496-1-sgarzare@redhat.com> Message-ID: <20251126210313.GA499503@fedora> Acked-by: Jason Wang Signed-off-by: Michael S. Tsirkin Signed-off-by: Sasha Levin --- drivers/vhost/vsock.c | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/drivers/vhost/vsock.c b/drivers/vhost/vsock.c index ae01457ea2cdbb..78cc66fbb3dd66 100644 --- a/drivers/vhost/vsock.c +++ b/drivers/vhost/vsock.c @@ -64,14 +64,15 @@ static u32 vhost_transport_get_local_cid(void) return VHOST_VSOCK_DEFAULT_HOST_CID; } -/* Callers that dereference the return value must hold vhost_vsock_mutex or the - * RCU read lock. +/* Callers must be in an RCU read section or hold the vhost_vsock_mutex. + * The return value can only be dereferenced while within the section. */ static struct vhost_vsock *vhost_vsock_get(u32 guest_cid) { struct vhost_vsock *vsock; - hash_for_each_possible_rcu(vhost_vsock_hash, vsock, hash, guest_cid) { + hash_for_each_possible_rcu(vhost_vsock_hash, vsock, hash, guest_cid, + lockdep_is_held(&vhost_vsock_mutex)) { u32 other_cid = vsock->guest_cid; /* Skip instances that have no CID yet */ @@ -707,9 +708,15 @@ static void vhost_vsock_reset_orphans(struct sock *sk) * executing. */ + rcu_read_lock(); + /* If the peer is still valid, no need to reset connection */ - if (vhost_vsock_get(vsk->remote_addr.svm_cid)) + if (vhost_vsock_get(vsk->remote_addr.svm_cid)) { + rcu_read_unlock(); return; + } + + rcu_read_unlock(); /* If the close timeout is pending, let it expire. This avoids races * with the timeout callback. From bb82aaee16907dc4d0b9b0ca7953ceb3edc328c6 Mon Sep 17 00:00:00 2001 From: Zilin Guan Date: Wed, 24 Dec 2025 15:21:42 +0000 Subject: [PATCH 1525/3902] cifs: Fix memory and information leak in smb3_reconfigure() [ Upstream commit cb6d5aa9c0f10074f1ad056c3e2278ad2cc7ec8d ] In smb3_reconfigure(), if smb3_sync_session_ctx_passwords() fails, the function returns immediately without freeing and erasing the newly allocated new_password and new_password2. This causes both a memory leak and a potential information leak. Fix this by calling kfree_sensitive() on both password buffers before returning in this error case. Fixes: 0f0e357902957 ("cifs: during remount, make sure passwords are in sync") Signed-off-by: Zilin Guan Reviewed-by: ChenXiaoSong Signed-off-by: Steve French Signed-off-by: Sasha Levin --- fs/smb/client/fs_context.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/fs/smb/client/fs_context.c b/fs/smb/client/fs_context.c index 2a0d8b87bd8ea8..d8bd3cdc535df7 100644 --- a/fs/smb/client/fs_context.c +++ b/fs/smb/client/fs_context.c @@ -1080,6 +1080,8 @@ static int smb3_reconfigure(struct fs_context *fc) rc = smb3_sync_session_ctx_passwords(cifs_sb, ses); if (rc) { mutex_unlock(&ses->session_mutex); + kfree_sensitive(new_password); + kfree_sensitive(new_password2); return rc; } From 505e8c7f6ca1c3e8f4caa2bc598f47fa3ac664e3 Mon Sep 17 00:00:00 2001 From: Lyude Paul Date: Fri, 7 Nov 2025 15:25:56 -0500 Subject: [PATCH 1526/3902] rust/drm/gem: Fix missing header in `Object` rustdoc commit e54ad0cd3673c93cdafda58505eaa81610fe3aef upstream. Invariants should be prefixed with a # to turn it into a header. There are no functional changes in this patch. Cc: stable@vger.kernel.org Fixes: c284d3e42338 ("rust: drm: gem: Add GEM object abstraction") Signed-off-by: Lyude Paul Link: https://patch.msgid.link/20251107202603.465932-1-lyude@redhat.com Signed-off-by: Alice Ryhl Signed-off-by: Greg Kroah-Hartman --- rust/kernel/drm/gem/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rust/kernel/drm/gem/mod.rs b/rust/kernel/drm/gem/mod.rs index 30c853988b9420..73b5f0bf3f85d9 100644 --- a/rust/kernel/drm/gem/mod.rs +++ b/rust/kernel/drm/gem/mod.rs @@ -184,7 +184,7 @@ impl BaseObject for T {} /// A base GEM object. /// -/// Invariants +/// # Invariants /// /// - `self.obj` is a valid instance of a `struct drm_gem_object`. /// - `self.dev` is always a valid pointer to a `struct drm_device`. From bee1f36801dba1d079c9eaf9a7d1263ea90cbaff Mon Sep 17 00:00:00 2001 From: Alice Ryhl Date: Tue, 11 Nov 2025 14:23:33 +0000 Subject: [PATCH 1527/3902] rust_binder: avoid mem::take on delivered_deaths commit 6c37bebd8c926ad01ef157c0d123633a203e5c0d upstream. Similar to the previous commit, List::remove is used on delivered_deaths, so do not use mem::take on it as that may result in violations of the List::remove safety requirements. I don't think this particular case can be triggered because it requires fd close to run in parallel with an ioctl on the same fd. But let's not tempt fate. Cc: stable@vger.kernel.org Fixes: eafedbc7c050 ("rust_binder: add Rust Binder driver") Signed-off-by: Alice Ryhl Acked-by: Miguel Ojeda Link: https://patch.msgid.link/20251111-binder-fix-list-remove-v1-2-8ed14a0da63d@google.com Signed-off-by: Greg Kroah-Hartman Signed-off-by: Greg Kroah-Hartman --- drivers/android/binder/process.rs | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/drivers/android/binder/process.rs b/drivers/android/binder/process.rs index 7607353a5e9249..ef4dbb2b571c77 100644 --- a/drivers/android/binder/process.rs +++ b/drivers/android/binder/process.rs @@ -1362,8 +1362,12 @@ impl Process { work.into_arc().cancel(); } - let delivered_deaths = take(&mut self.inner.lock().delivered_deaths); - drop(delivered_deaths); + // Clear delivered_deaths list. + // + // Scope ensures that MutexGuard is dropped while executing the body. + while let Some(delivered_death) = { self.inner.lock().delivered_deaths.pop_front() } { + drop(delivered_death); + } // Free any resources kept alive by allocated buffers. let omapping = self.inner.lock().mapping.take(); From 48298e1ba378c04876dc8feb8dfc412706403762 Mon Sep 17 00:00:00 2001 From: FUJITA Tomonori Date: Fri, 5 Dec 2025 01:06:39 +0900 Subject: [PATCH 1528/3902] rust: dma: add helpers for architectures without CONFIG_HAS_DMA commit d8932355f8c5673106eca49abd142f8fe0c1fe8b upstream. Add dma_set_mask(), dma_set_coherent_mask(), dma_map_sgtable(), and dma_max_mapping_size() helpers to fix a build error when CONFIG_HAS_DMA is not enabled. Note that when CONFIG_HAS_DMA is enabled, they are included in both bindings_generated.rs and bindings_helpers_generated.rs. The former takes precedence so behavior remains unchanged in that case. This fixes the following build error on UML: error[E0425]: cannot find function `dma_set_mask` in crate `bindings` --> rust/kernel/dma.rs:46:38 | 46 | to_result(unsafe { bindings::dma_set_mask(self.as_ref().as_raw(), mask.value()) }) | ^^^^^^^^^^^^ help: a function with a similar name exists: `xa_set_mark` | ::: rust/bindings/bindings_generated.rs:24690:5 | 24690 | pub fn xa_set_mark(arg1: *mut xarray, index: ffi::c_ulong, arg2: xa_mark_t); | ---------------------------------------------------------------------------- similarly named function `xa_set_mark` defined here error[E0425]: cannot find function `dma_set_coherent_mask` in crate `bindings` --> rust/kernel/dma.rs:63:38 | 63 | to_result(unsafe { bindings::dma_set_coherent_mask(self.as_ref().as_raw(), mask.value()) }) | ^^^^^^^^^^^^^^^^^^^^^ help: a function with a similar name exists: `dma_coherent_ok` | ::: rust/bindings/bindings_generated.rs:52745:5 | 52745 | pub fn dma_coherent_ok(dev: *mut device, phys: phys_addr_t, size: usize) -> bool_; | ---------------------------------------------------------------------------------- similarly named function `dma_coherent_ok` defined here error[E0425]: cannot find function `dma_map_sgtable` in crate `bindings` --> rust/kernel/scatterlist.rs:212:23 | 212 | bindings::dma_map_sgtable(dev.as_raw(), sgt.as_ptr(), dir.into(), 0) | ^^^^^^^^^^^^^^^ help: a function with a similar name exists: `dma_unmap_sgtable` | ::: rust/bindings/bindings_helpers_generated.rs:1351:5 | 1351 | / pub fn dma_unmap_sgtable( 1352 | | dev: *mut device, 1353 | | sgt: *mut sg_table, 1354 | | dir: dma_data_direction, 1355 | | attrs: ffi::c_ulong, 1356 | | ); | |______- similarly named function `dma_unmap_sgtable` defined here error[E0425]: cannot find function `dma_max_mapping_size` in crate `bindings` --> rust/kernel/scatterlist.rs:356:52 | 356 | let max_segment = match unsafe { bindings::dma_max_mapping_size(dev.as_raw()) } { | ^^^^^^^^^^^^^^^^^^^^ not found in `bindings` error: aborting due to 4 previous errors Cc: stable@vger.kernel.org # v6.17+ Fixes: 101d66828a4ee ("rust: dma: add DMA addressing capabilities") Signed-off-by: FUJITA Tomonori Reviewed-by: David Gow Reviewed-by: Alice Ryhl Link: https://patch.msgid.link/20251204160639.364936-1-fujita.tomonori@gmail.com [ Use relative paths in the error splat; add 'dma' prefix. - Danilo ] Signed-off-by: Danilo Krummrich Signed-off-by: Greg Kroah-Hartman --- rust/helpers/dma.c | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/rust/helpers/dma.c b/rust/helpers/dma.c index 6e741c19724257..2afa32c21c946e 100644 --- a/rust/helpers/dma.c +++ b/rust/helpers/dma.c @@ -19,3 +19,24 @@ int rust_helper_dma_set_mask_and_coherent(struct device *dev, u64 mask) { return dma_set_mask_and_coherent(dev, mask); } + +int rust_helper_dma_set_mask(struct device *dev, u64 mask) +{ + return dma_set_mask(dev, mask); +} + +int rust_helper_dma_set_coherent_mask(struct device *dev, u64 mask) +{ + return dma_set_coherent_mask(dev, mask); +} + +int rust_helper_dma_map_sgtable(struct device *dev, struct sg_table *sgt, + enum dma_data_direction dir, unsigned long attrs) +{ + return dma_map_sgtable(dev, sgt, dir, attrs); +} + +size_t rust_helper_dma_max_mapping_size(struct device *dev) +{ + return dma_max_mapping_size(dev); +} From 93b3274587e8d747d99c2cb77164a9b2304c0935 Mon Sep 17 00:00:00 2001 From: Marko Turk Date: Wed, 10 Dec 2025 12:25:51 +0100 Subject: [PATCH 1529/3902] samples: rust: fix endianness issue in rust_driver_pci commit e2f1081ca8f18c146e8f928486deac61eca2b517 upstream. MMIO backend of PCI Bar always assumes little-endian devices and will convert to CPU endianness automatically. Remove the u32::from_le conversion which would cause a bug on big-endian machines. Cc: stable@vger.kernel.org Reviewed-by: Dirk Behme Signed-off-by: Marko Turk Fixes: 685376d18e9a ("samples: rust: add Rust PCI sample driver") Link: https://patch.msgid.link/20251210112503.62925-2-mt@markoturk.info Signed-off-by: Danilo Krummrich Signed-off-by: Greg Kroah-Hartman --- samples/rust/rust_driver_pci.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/samples/rust/rust_driver_pci.rs b/samples/rust/rust_driver_pci.rs index 55a683c39ed910..ea3063e558b1a3 100644 --- a/samples/rust/rust_driver_pci.rs +++ b/samples/rust/rust_driver_pci.rs @@ -48,7 +48,7 @@ impl SampleDriver { // Select the test. bar.write8(index.0, Regs::TEST); - let offset = u32::from_le(bar.read32(Regs::OFFSET)) as usize; + let offset = bar.read32(Regs::OFFSET) as usize; let data = bar.read8(Regs::DATA); // Write `data` to `offset` to increase `count` by one. From 0bf59179bdef493a39061cfd3263bc0578f852b0 Mon Sep 17 00:00:00 2001 From: Alice Ryhl Date: Wed, 12 Nov 2025 09:48:32 +0000 Subject: [PATCH 1530/3902] rust: io: define ResourceSize as resource_size_t commit 919b72922717e396be9435c83916b9969505bd23 upstream. These typedefs are always equivalent so this should not change anything, but the code makes a lot more sense like this. Cc: stable@vger.kernel.org Signed-off-by: Alice Ryhl Fixes: 493fc33ec252 ("rust: io: add resource abstraction") Link: https://patch.msgid.link/20251112-resource-phys-typedefs-v2-1-538307384f82@google.com Signed-off-by: Danilo Krummrich Signed-off-by: Greg Kroah-Hartman --- rust/kernel/io/resource.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rust/kernel/io/resource.rs b/rust/kernel/io/resource.rs index bea3ee0ed87b51..11b6bb9678b4e3 100644 --- a/rust/kernel/io/resource.rs +++ b/rust/kernel/io/resource.rs @@ -16,7 +16,7 @@ use crate::types::Opaque; /// /// This is a type alias to either `u32` or `u64` depending on the config option /// `CONFIG_PHYS_ADDR_T_64BIT`, and it can be a u64 even on 32-bit architectures. -pub type ResourceSize = bindings::phys_addr_t; +pub type ResourceSize = bindings::resource_size_t; /// A region allocated from a parent [`Resource`]. /// From 52df55b21ede5fc0418972754f516a200a460709 Mon Sep 17 00:00:00 2001 From: Alice Ryhl Date: Wed, 12 Nov 2025 09:48:33 +0000 Subject: [PATCH 1531/3902] rust: io: move ResourceSize to top-level io module commit dfd67993044f507ba8fd6ee9956f923ba4b7e851 upstream. Resource sizes are a general concept for dealing with physical addresses, and not specific to the Resource type, which is just one way to access physical addresses. Thus, move the typedef to the io module. Still keep a re-export under resource. This avoids this commit from being a flag-day, but I also think it's a useful re-export in general so that you can import use kernel::io::resource::{Resource, ResourceSize}; instead of having to write use kernel::io::{ resource::Resource, ResourceSize, }; in the specific cases where you need ResourceSize because you are using the Resource type. Therefore I think it makes sense to keep this re-export indefinitely and it is *not* intended as a temporary re-export for migration purposes. Cc: stable@vger.kernel.org # for v6.18 [1] Signed-off-by: Alice Ryhl Link: https://patch.msgid.link/20251112-resource-phys-typedefs-v2-2-538307384f82@google.com Link: https://lore.kernel.org/all/20251112-resource-phys-typedefs-v2-0-538307384f82@google.com/ [1] Signed-off-by: Danilo Krummrich Signed-off-by: Greg Kroah-Hartman --- rust/kernel/io.rs | 6 ++++++ rust/kernel/io/resource.rs | 6 +----- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/rust/kernel/io.rs b/rust/kernel/io.rs index ee182b0b5452df..6465ea94e85d68 100644 --- a/rust/kernel/io.rs +++ b/rust/kernel/io.rs @@ -13,6 +13,12 @@ pub mod resource; pub use resource::Resource; +/// Resource Size type. +/// +/// This is a type alias to either `u32` or `u64` depending on the config option +/// `CONFIG_PHYS_ADDR_T_64BIT`, and it can be a u64 even on 32-bit architectures. +pub type ResourceSize = bindings::resource_size_t; + /// Raw representation of an MMIO region. /// /// By itself, the existence of an instance of this structure does not provide any guarantees that diff --git a/rust/kernel/io/resource.rs b/rust/kernel/io/resource.rs index 11b6bb9678b4e3..7fed41fc20307f 100644 --- a/rust/kernel/io/resource.rs +++ b/rust/kernel/io/resource.rs @@ -12,11 +12,7 @@ use crate::prelude::*; use crate::str::{CStr, CString}; use crate::types::Opaque; -/// Resource Size type. -/// -/// This is a type alias to either `u32` or `u64` depending on the config option -/// `CONFIG_PHYS_ADDR_T_64BIT`, and it can be a u64 even on 32-bit architectures. -pub type ResourceSize = bindings::resource_size_t; +pub use super::ResourceSize; /// A region allocated from a parent [`Resource`]. /// From c30ed32690b23e5994ec8de4338896b687d46287 Mon Sep 17 00:00:00 2001 From: Alice Ryhl Date: Wed, 12 Nov 2025 09:48:35 +0000 Subject: [PATCH 1532/3902] rust: io: add typedef for phys_addr_t commit dd6ff5cf56fb183fce605ca6a5bfce228cd8888b upstream. The C typedef phys_addr_t is missing an analogue in Rust, meaning that we end up using bindings::phys_addr_t or ResourceSize as a replacement in various places throughout the kernel. Fix that by introducing a new typedef on the Rust side. Place it next to the existing ResourceSize typedef since they're quite related to each other. Cc: stable@vger.kernel.org # for v6.18 [1] Signed-off-by: Alice Ryhl Link: https://patch.msgid.link/20251112-resource-phys-typedefs-v2-4-538307384f82@google.com Link: https://lore.kernel.org/all/20251112-resource-phys-typedefs-v2-0-538307384f82@google.com/ [1] Signed-off-by: Danilo Krummrich Signed-off-by: Greg Kroah-Hartman --- rust/kernel/devres.rs | 18 +++++++++++++++--- rust/kernel/io.rs | 20 +++++++++++++++++--- rust/kernel/io/resource.rs | 9 ++++++--- 3 files changed, 38 insertions(+), 9 deletions(-) diff --git a/rust/kernel/devres.rs b/rust/kernel/devres.rs index 2392c281459eff..835d9c11948e7d 100644 --- a/rust/kernel/devres.rs +++ b/rust/kernel/devres.rs @@ -52,8 +52,20 @@ struct Inner { /// # Examples /// /// ```no_run -/// # use kernel::{bindings, device::{Bound, Device}, devres::Devres, io::{Io, IoRaw}}; -/// # use core::ops::Deref; +/// use kernel::{ +/// bindings, +/// device::{ +/// Bound, +/// Device, +/// }, +/// devres::Devres, +/// io::{ +/// Io, +/// IoRaw, +/// PhysAddr, +/// }, +/// }; +/// use core::ops::Deref; /// /// // See also [`pci::Bar`] for a real example. /// struct IoMem(IoRaw); @@ -66,7 +78,7 @@ struct Inner { /// unsafe fn new(paddr: usize) -> Result{ /// // SAFETY: By the safety requirements of this function [`paddr`, `paddr` + `SIZE`) is /// // valid for `ioremap`. -/// let addr = unsafe { bindings::ioremap(paddr as bindings::phys_addr_t, SIZE) }; +/// let addr = unsafe { bindings::ioremap(paddr as PhysAddr, SIZE) }; /// if addr.is_null() { /// return Err(ENOMEM); /// } diff --git a/rust/kernel/io.rs b/rust/kernel/io.rs index 6465ea94e85d68..56a435eb14e3a1 100644 --- a/rust/kernel/io.rs +++ b/rust/kernel/io.rs @@ -13,6 +13,12 @@ pub mod resource; pub use resource::Resource; +/// Physical address type. +/// +/// This is a type alias to either `u32` or `u64` depending on the config option +/// `CONFIG_PHYS_ADDR_T_64BIT`, and it can be a u64 even on 32-bit architectures. +pub type PhysAddr = bindings::phys_addr_t; + /// Resource Size type. /// /// This is a type alias to either `u32` or `u64` depending on the config option @@ -68,8 +74,16 @@ impl IoRaw { /// # Examples /// /// ```no_run -/// # use kernel::{bindings, ffi::c_void, io::{Io, IoRaw}}; -/// # use core::ops::Deref; +/// use kernel::{ +/// bindings, +/// ffi::c_void, +/// io::{ +/// Io, +/// IoRaw, +/// PhysAddr, +/// }, +/// }; +/// use core::ops::Deref; /// /// // See also [`pci::Bar`] for a real example. /// struct IoMem(IoRaw); @@ -82,7 +96,7 @@ impl IoRaw { /// unsafe fn new(paddr: usize) -> Result{ /// // SAFETY: By the safety requirements of this function [`paddr`, `paddr` + `SIZE`) is /// // valid for `ioremap`. -/// let addr = unsafe { bindings::ioremap(paddr as bindings::phys_addr_t, SIZE) }; +/// let addr = unsafe { bindings::ioremap(paddr as PhysAddr, SIZE) }; /// if addr.is_null() { /// return Err(ENOMEM); /// } diff --git a/rust/kernel/io/resource.rs b/rust/kernel/io/resource.rs index 7fed41fc20307f..0e86ee9c98d840 100644 --- a/rust/kernel/io/resource.rs +++ b/rust/kernel/io/resource.rs @@ -12,7 +12,10 @@ use crate::prelude::*; use crate::str::{CStr, CString}; use crate::types::Opaque; -pub use super::ResourceSize; +pub use super::{ + PhysAddr, + ResourceSize, // +}; /// A region allocated from a parent [`Resource`]. /// @@ -93,7 +96,7 @@ impl Resource { /// the region, or a part of it, is already in use. pub fn request_region( &self, - start: ResourceSize, + start: PhysAddr, size: ResourceSize, name: CString, flags: Flags, @@ -127,7 +130,7 @@ impl Resource { } /// Returns the start address of the resource. - pub fn start(&self) -> ResourceSize { + pub fn start(&self) -> PhysAddr { let inner = self.0.get(); // SAFETY: Safe as per the invariants of `Resource`. unsafe { (*inner).start } From 9e7c63c69f57b1db1a8a1542359a6167ff8fcef1 Mon Sep 17 00:00:00 2001 From: Jarkko Sakkinen Date: Sat, 18 Oct 2025 13:30:36 +0300 Subject: [PATCH 1533/3902] KEYS: trusted: Fix a memory leak in tpm2_load_cmd commit 62cd5d480b9762ce70d720a81fa5b373052ae05f upstream. 'tpm2_load_cmd' allocates a tempoary blob indirectly via 'tpm2_key_decode' but it is not freed in the failure paths. Address this by wrapping the blob into with a cleanup helper. Cc: stable@vger.kernel.org # v5.13+ Fixes: f2219745250f ("security: keys: trusted: use ASN.1 TPM2 key format for the blobs") Signed-off-by: Jarkko Sakkinen Signed-off-by: Greg Kroah-Hartman --- security/keys/trusted-keys/trusted_tpm2.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/security/keys/trusted-keys/trusted_tpm2.c b/security/keys/trusted-keys/trusted_tpm2.c index 024be262702fed..76a86f8a8d6c84 100644 --- a/security/keys/trusted-keys/trusted_tpm2.c +++ b/security/keys/trusted-keys/trusted_tpm2.c @@ -387,6 +387,7 @@ static int tpm2_load_cmd(struct tpm_chip *chip, struct trusted_key_options *options, u32 *blob_handle) { + u8 *blob_ref __free(kfree) = NULL; struct tpm_buf buf; unsigned int private_len; unsigned int public_len; @@ -400,6 +401,9 @@ static int tpm2_load_cmd(struct tpm_chip *chip, /* old form */ blob = payload->blob; payload->old_format = 1; + } else { + /* Bind for cleanup: */ + blob_ref = blob; } /* new format carries keyhandle but old format doesn't */ @@ -464,8 +468,6 @@ static int tpm2_load_cmd(struct tpm_chip *chip, (__be32 *) &buf.data[TPM_HEADER_SIZE]); out: - if (blob != payload->blob) - kfree(blob); tpm_buf_destroy(&buf); if (rc > 0) From 3dfe2e1531c837f18c541e1db90f6ef7890a497c Mon Sep 17 00:00:00 2001 From: Johan Hovold Date: Thu, 27 Nov 2025 14:42:43 +0100 Subject: [PATCH 1534/3902] clk: keystone: syscon-clk: fix regmap leak on probe failure commit 9c75986a298f121ed2c6599b05e51d9a34e77068 upstream. The mmio regmap allocated during probe is never freed. Switch to using the device managed allocator so that the regmap is released on probe failures (e.g. probe deferral) and on driver unbind. Fixes: a250cd4c1901 ("clk: keystone: syscon-clk: Do not use syscon helper to build regmap") Cc: stable@vger.kernel.org # 6.15 Cc: Andrew Davis Signed-off-by: Johan Hovold Signed-off-by: Stephen Boyd Signed-off-by: Greg Kroah-Hartman --- drivers/clk/keystone/syscon-clk.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/clk/keystone/syscon-clk.c b/drivers/clk/keystone/syscon-clk.c index c509929da85452..ecf180a7949c93 100644 --- a/drivers/clk/keystone/syscon-clk.c +++ b/drivers/clk/keystone/syscon-clk.c @@ -129,7 +129,7 @@ static int ti_syscon_gate_clk_probe(struct platform_device *pdev) if (IS_ERR(base)) return PTR_ERR(base); - regmap = regmap_init_mmio(dev, base, &ti_syscon_regmap_cfg); + regmap = devm_regmap_init_mmio(dev, base, &ti_syscon_regmap_cfg); if (IS_ERR(regmap)) return dev_err_probe(dev, PTR_ERR(regmap), "failed to get regmap\n"); From 13a8f7b88c2d40c6b33f6216190478dda95d385f Mon Sep 17 00:00:00 2001 From: Jens Axboe Date: Mon, 1 Dec 2025 13:25:22 -0700 Subject: [PATCH 1535/3902] io_uring/poll: correctly handle io_poll_add() return value on update commit 84230ad2d2afbf0c44c32967e525c0ad92e26b4e upstream. When the core of io_uring was updated to handle completions consistently and with fixed return codes, the POLL_REMOVE opcode with updates got slightly broken. If a POLL_ADD is pending and then POLL_REMOVE is used to update the events of that request, if that update causes the POLL_ADD to now trigger, then that completion is lost and a CQE is never posted. Additionally, ensure that if an update does cause an existing POLL_ADD to complete, that the completion value isn't always overwritten with -ECANCELED. For that case, whatever io_poll_add() set the value to should just be retained. Cc: stable@vger.kernel.org Fixes: 97b388d70b53 ("io_uring: handle completions in the core") Reported-by: syzbot+641eec6b7af1f62f2b99@syzkaller.appspotmail.com Tested-by: syzbot+641eec6b7af1f62f2b99@syzkaller.appspotmail.com Signed-off-by: Jens Axboe Signed-off-by: Greg Kroah-Hartman --- io_uring/poll.c | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/io_uring/poll.c b/io_uring/poll.c index b9681d0f9f1314..0d5bb90d47433b 100644 --- a/io_uring/poll.c +++ b/io_uring/poll.c @@ -936,12 +936,17 @@ int io_poll_remove(struct io_kiocb *req, unsigned int issue_flags) ret2 = io_poll_add(preq, issue_flags & ~IO_URING_F_UNLOCKED); /* successfully updated, don't complete poll request */ - if (!ret2 || ret2 == -EIOCBQUEUED) + if (ret2 == IOU_ISSUE_SKIP_COMPLETE) goto out; + /* request completed as part of the update, complete it */ + else if (ret2 == IOU_COMPLETE) + goto complete; } - req_set_fail(preq); io_req_set_res(preq, -ECANCELED, 0); +complete: + if (preq->cqe.res < 0) + req_set_fail(preq); preq->io_task_work.func = io_req_task_complete; io_req_task_work_add(preq); out: From dcb0d8bf1771fd7b6b92fda8bc2da47a7dacff39 Mon Sep 17 00:00:00 2001 From: Jens Axboe Date: Tue, 9 Dec 2025 13:25:23 -0700 Subject: [PATCH 1536/3902] io_uring: fix min_wait wakeups for SQPOLL commit e15cb2200b934e507273510ba6bc747d5cde24a3 upstream. Using min_wait, two timeouts are given: 1) The min_wait timeout, within which up to 'wait_nr' events are waited for. 2) The overall long timeout, which is entered if no events are generated in the min_wait window. If the min_wait has expired, any event being posted must wake the task. For SQPOLL, that isn't the case, as it won't trigger the io_has_work() condition, as it will have already processed the task_work that happened when an event was posted. This causes any event to trigger post the min_wait to not always cause the waiting application to wakeup, and instead it will wait until the overall timeout has expired. This can be shown in a test case that has a 1 second min_wait, with a 5 second overall wait, even if an event triggers after 1.5 seconds: axboe@m2max-kvm /d/iouring-mre (master)> zig-out/bin/iouring info: MIN_TIMEOUT supported: true, features: 0x3ffff info: Testing: min_wait=1000ms, timeout=5s, wait_nr=4 info: 1 cqes in 5000.2ms where the expected result should be: axboe@m2max-kvm /d/iouring-mre (master)> zig-out/bin/iouring info: MIN_TIMEOUT supported: true, features: 0x3ffff info: Testing: min_wait=1000ms, timeout=5s, wait_nr=4 info: 1 cqes in 1500.3ms When the min_wait timeout triggers, reset the number of completions needed to wake the task. This should ensure that any future events will wake the task, regardless of how many events it originally wanted to wait for. Reported-by: Tip ten Brink Cc: stable@vger.kernel.org Fixes: 1100c4a2656d ("io_uring: add support for batch wait timeout") Link: https://github.com/axboe/liburing/issues/1477 Signed-off-by: Jens Axboe Signed-off-by: Greg Kroah-Hartman --- io_uring/io_uring.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/io_uring/io_uring.c b/io_uring/io_uring.c index 765abec2571f03..60adab71ad2d26 100644 --- a/io_uring/io_uring.c +++ b/io_uring/io_uring.c @@ -2551,6 +2551,9 @@ static enum hrtimer_restart io_cqring_min_timer_wakeup(struct hrtimer *timer) goto out_wake; } + /* any generated CQE posted past this time should wake us up */ + iowq->cq_tail = iowq->cq_min_tail; + hrtimer_update_function(&iowq->t, io_cqring_timer_wakeup); hrtimer_set_expires(timer, iowq->timeout); return HRTIMER_RESTART; From 7fbfb85b05bc960cc50e09d03e5e562131e48d45 Mon Sep 17 00:00:00 2001 From: Prithvi Tambewagh Date: Thu, 25 Dec 2025 12:58:29 +0530 Subject: [PATCH 1537/3902] io_uring: fix filename leak in __io_openat_prep() commit b14fad555302a2104948feaff70503b64c80ac01 upstream. __io_openat_prep() allocates a struct filename using getname(). However, for the condition of the file being installed in the fixed file table as well as having O_CLOEXEC flag set, the function returns early. At that point, the request doesn't have REQ_F_NEED_CLEANUP flag set. Due to this, the memory for the newly allocated struct filename is not cleaned up, causing a memory leak. Fix this by setting the REQ_F_NEED_CLEANUP for the request just after the successful getname() call, so that when the request is torn down, the filename will be cleaned up, along with other resources needing cleanup. Reported-by: syzbot+00e61c43eb5e4740438f@syzkaller.appspotmail.com Closes: https://syzkaller.appspot.com/bug?extid=00e61c43eb5e4740438f Tested-by: syzbot+00e61c43eb5e4740438f@syzkaller.appspotmail.com Cc: stable@vger.kernel.org Signed-off-by: Prithvi Tambewagh Fixes: b9445598d8c6 ("io_uring: openat directly into fixed fd table") Signed-off-by: Jens Axboe Signed-off-by: Greg Kroah-Hartman --- io_uring/openclose.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/io_uring/openclose.c b/io_uring/openclose.c index bfeb91b31bba51..15dde9bd6ff670 100644 --- a/io_uring/openclose.c +++ b/io_uring/openclose.c @@ -73,13 +73,13 @@ static int __io_openat_prep(struct io_kiocb *req, const struct io_uring_sqe *sqe open->filename = NULL; return ret; } + req->flags |= REQ_F_NEED_CLEANUP; open->file_slot = READ_ONCE(sqe->file_index); if (open->file_slot && (open->how.flags & O_CLOEXEC)) return -EINVAL; open->nofile = rlimit(RLIMIT_NOFILE); - req->flags |= REQ_F_NEED_CLEANUP; if (io_openat_force_async(open)) req->flags |= REQ_F_FORCE_ASYNC; return 0; From 3192204aa77fb6b3b95bb88795b9978fe467f811 Mon Sep 17 00:00:00 2001 From: John Ogness Date: Thu, 13 Nov 2025 17:09:48 +0106 Subject: [PATCH 1538/3902] printk: Avoid scheduling irq_work on suspend commit 26873e3e7f0cb26c45e6ad63656f9fe36b2aa31b upstream. Allowing irq_work to be scheduled while trying to suspend has shown to cause problems as some architectures interpret the pending interrupts as a reason to not suspend. This became a problem for printk() with the introduction of NBCON consoles. With every printk() call, NBCON console printing kthreads are woken by queueing irq_work. This means that irq_work continues to be queued due to printk() calls late in the suspend procedure. Avoid this problem by preventing printk() from queueing irq_work once console suspending has begun. This applies to triggering NBCON and legacy deferred printing as well as klogd waiters. Since triggering of NBCON threaded printing relies on irq_work, the pr_flush() within console_suspend_all() is used to perform the final flushing before suspending consoles and blocking irq_work queueing. NBCON consoles that are not suspended (due to the usage of the "no_console_suspend" boot argument) transition to atomic flushing. Introduce a new global variable @console_irqwork_blocked to flag when irq_work queueing is to be avoided. The flag is used by printk_get_console_flush_type() to avoid allowing deferred printing and switch NBCON consoles to atomic flushing. It is also used by vprintk_emit() to avoid klogd waking. Add WARN_ON_ONCE(console_irqwork_blocked) to the irq_work queuing functions to catch any code that attempts to queue printk irq_work during the suspending/resuming procedure. Cc: stable@vger.kernel.org # 6.13.x because no drivers in 6.12.x Fixes: 6b93bb41f6ea ("printk: Add non-BKL (nbcon) console basic infrastructure") Closes: https://lore.kernel.org/lkml/DB9PR04MB8429E7DDF2D93C2695DE401D92C4A@DB9PR04MB8429.eurprd04.prod.outlook.com Signed-off-by: John Ogness Reviewed-by: Petr Mladek Tested-by: Sherry Sun Link: https://patch.msgid.link/20251113160351.113031-3-john.ogness@linutronix.de Signed-off-by: Petr Mladek Signed-off-by: Greg Kroah-Hartman --- kernel/printk/internal.h | 8 +++--- kernel/printk/nbcon.c | 7 +++++ kernel/printk/printk.c | 58 +++++++++++++++++++++++++++++----------- 3 files changed, 55 insertions(+), 18 deletions(-) diff --git a/kernel/printk/internal.h b/kernel/printk/internal.h index f72bbfa266d6c9..b20929b7d71f59 100644 --- a/kernel/printk/internal.h +++ b/kernel/printk/internal.h @@ -230,6 +230,8 @@ struct console_flush_type { bool legacy_offload; }; +extern bool console_irqwork_blocked; + /* * Identify which console flushing methods should be used in the context of * the caller. @@ -241,7 +243,7 @@ static inline void printk_get_console_flush_type(struct console_flush_type *ft) switch (nbcon_get_default_prio()) { case NBCON_PRIO_NORMAL: if (have_nbcon_console && !have_boot_console) { - if (printk_kthreads_running) + if (printk_kthreads_running && !console_irqwork_blocked) ft->nbcon_offload = true; else ft->nbcon_atomic = true; @@ -251,7 +253,7 @@ static inline void printk_get_console_flush_type(struct console_flush_type *ft) if (have_legacy_console || have_boot_console) { if (!is_printk_legacy_deferred()) ft->legacy_direct = true; - else + else if (!console_irqwork_blocked) ft->legacy_offload = true; } break; @@ -264,7 +266,7 @@ static inline void printk_get_console_flush_type(struct console_flush_type *ft) if (have_legacy_console || have_boot_console) { if (!is_printk_legacy_deferred()) ft->legacy_direct = true; - else + else if (!console_irqwork_blocked) ft->legacy_offload = true; } break; diff --git a/kernel/printk/nbcon.c b/kernel/printk/nbcon.c index 558ef317797603..da07c59f7aa2eb 100644 --- a/kernel/printk/nbcon.c +++ b/kernel/printk/nbcon.c @@ -1276,6 +1276,13 @@ void nbcon_kthreads_wake(void) if (!printk_kthreads_running) return; + /* + * It is not allowed to call this function when console irq_work + * is blocked. + */ + if (WARN_ON_ONCE(console_irqwork_blocked)) + return; + cookie = console_srcu_read_lock(); for_each_console_srcu(con) { if (!(console_srcu_read_flags(con) & CON_NBCON)) diff --git a/kernel/printk/printk.c b/kernel/printk/printk.c index 5aee9ffb16b9a5..e850c237b4ba99 100644 --- a/kernel/printk/printk.c +++ b/kernel/printk/printk.c @@ -462,6 +462,9 @@ bool have_boot_console; /* See printk_legacy_allow_panic_sync() for details. */ bool legacy_allow_panic_sync; +/* Avoid using irq_work when suspending. */ +bool console_irqwork_blocked; + #ifdef CONFIG_PRINTK DECLARE_WAIT_QUEUE_HEAD(log_wait); static DECLARE_WAIT_QUEUE_HEAD(legacy_wait); @@ -2426,7 +2429,7 @@ asmlinkage int vprintk_emit(int facility, int level, if (ft.legacy_offload) defer_console_output(); - else + else if (!console_irqwork_blocked) wake_up_klogd(); return printed_len; @@ -2730,10 +2733,20 @@ void console_suspend_all(void) { struct console *con; + if (console_suspend_enabled) + pr_info("Suspending console(s) (use no_console_suspend to debug)\n"); + + /* + * Flush any console backlog and then avoid queueing irq_work until + * console_resume_all(). Until then deferred printing is no longer + * triggered, NBCON consoles transition to atomic flushing, and + * any klogd waiters are not triggered. + */ + pr_flush(1000, true); + console_irqwork_blocked = true; + if (!console_suspend_enabled) return; - pr_info("Suspending console(s) (use no_console_suspend to debug)\n"); - pr_flush(1000, true); console_list_lock(); for_each_console(con) @@ -2754,26 +2767,34 @@ void console_resume_all(void) struct console_flush_type ft; struct console *con; - if (!console_suspend_enabled) - return; - - console_list_lock(); - for_each_console(con) - console_srcu_write_flags(con, con->flags & ~CON_SUSPENDED); - console_list_unlock(); - /* - * Ensure that all SRCU list walks have completed. All printing - * contexts must be able to see they are no longer suspended so - * that they are guaranteed to wake up and resume printing. + * Allow queueing irq_work. After restoring console state, deferred + * printing and any klogd waiters need to be triggered in case there + * is now a console backlog. */ - synchronize_srcu(&console_srcu); + console_irqwork_blocked = false; + + if (console_suspend_enabled) { + console_list_lock(); + for_each_console(con) + console_srcu_write_flags(con, con->flags & ~CON_SUSPENDED); + console_list_unlock(); + + /* + * Ensure that all SRCU list walks have completed. All printing + * contexts must be able to see they are no longer suspended so + * that they are guaranteed to wake up and resume printing. + */ + synchronize_srcu(&console_srcu); + } printk_get_console_flush_type(&ft); if (ft.nbcon_offload) nbcon_kthreads_wake(); if (ft.legacy_offload) defer_console_output(); + else + wake_up_klogd(); pr_flush(1000, true); } @@ -4511,6 +4532,13 @@ static void __wake_up_klogd(int val) if (!printk_percpu_data_ready()) return; + /* + * It is not allowed to call this function when console irq_work + * is blocked. + */ + if (WARN_ON_ONCE(console_irqwork_blocked)) + return; + preempt_disable(); /* * Guarantee any new records can be seen by tasks preparing to wait From 514d605ab6ce88f45f551ebaade2f5e52c1bb6ed Mon Sep 17 00:00:00 2001 From: Tejun Heo Date: Thu, 11 Dec 2025 15:45:03 -1000 Subject: [PATCH 1539/3902] sched_ext: Factor out local_dsq_post_enq() from dispatch_enqueue() commit 530b6637c79e728d58f1d9b66bd4acf4b735b86d upstream. Factor out local_dsq_post_enq() which performs post-enqueue handling for local DSQs - triggering resched_curr() if SCX_ENQ_PREEMPT is specified or if the current CPU is idle. No functional change. This will be used by the next patch to fix move_local_task_to_local_dsq(). Cc: stable@vger.kernel.org # v6.12+ Reviewed-by: Andrea Righi Reviewed-by: Emil Tsalapatis Signed-off-by: Tejun Heo Signed-off-by: Greg Kroah-Hartman --- kernel/sched/ext.c | 34 +++++++++++++++++++--------------- 1 file changed, 19 insertions(+), 15 deletions(-) diff --git a/kernel/sched/ext.c b/kernel/sched/ext.c index 979484dab2d3d1..34d0d569548cc1 100644 --- a/kernel/sched/ext.c +++ b/kernel/sched/ext.c @@ -906,6 +906,22 @@ static void refill_task_slice_dfl(struct scx_sched *sch, struct task_struct *p) __scx_add_event(sch, SCX_EV_REFILL_SLICE_DFL, 1); } +static void local_dsq_post_enq(struct scx_dispatch_q *dsq, struct task_struct *p, + u64 enq_flags) +{ + struct rq *rq = container_of(dsq, struct rq, scx.local_dsq); + bool preempt = false; + + if ((enq_flags & SCX_ENQ_PREEMPT) && p != rq->curr && + rq->curr->sched_class == &ext_sched_class) { + rq->curr->scx.slice = 0; + preempt = true; + } + + if (preempt || sched_class_above(&ext_sched_class, rq->curr->sched_class)) + resched_curr(rq); +} + static void dispatch_enqueue(struct scx_sched *sch, struct scx_dispatch_q *dsq, struct task_struct *p, u64 enq_flags) { @@ -1003,22 +1019,10 @@ static void dispatch_enqueue(struct scx_sched *sch, struct scx_dispatch_q *dsq, if (enq_flags & SCX_ENQ_CLEAR_OPSS) atomic_long_set_release(&p->scx.ops_state, SCX_OPSS_NONE); - if (is_local) { - struct rq *rq = container_of(dsq, struct rq, scx.local_dsq); - bool preempt = false; - - if ((enq_flags & SCX_ENQ_PREEMPT) && p != rq->curr && - rq->curr->sched_class == &ext_sched_class) { - rq->curr->scx.slice = 0; - preempt = true; - } - - if (preempt || sched_class_above(&ext_sched_class, - rq->curr->sched_class)) - resched_curr(rq); - } else { + if (is_local) + local_dsq_post_enq(dsq, p, enq_flags); + else raw_spin_unlock(&dsq->lock); - } } static void task_unlink_from_dsq(struct task_struct *p, From ff67270a177f4fea5a78f6d8ac799e0ee1b05b5e Mon Sep 17 00:00:00 2001 From: Zqiang Date: Mon, 8 Dec 2025 19:23:19 +0800 Subject: [PATCH 1540/3902] sched_ext: Fix the memleak for sch->helper objects commit 517a44d18537ef8ab888f71197c80116c14cee0a upstream. This commit use kthread_destroy_worker() to release sch->helper objects to fix the following kmemleak: unreferenced object 0xffff888121ec7b00 (size 128): comm "scx_simple", pid 1197, jiffies 4295884415 hex dump (first 32 bytes): 00 00 00 00 00 00 00 00 00 00 00 00 ad 4e ad de .............N.. ff ff ff ff 00 00 00 00 ff ff ff ff ff ff ff ff ................ backtrace (crc 587b3352): kmemleak_alloc+0x62/0xa0 __kmalloc_cache_noprof+0x28d/0x3e0 kthread_create_worker_on_node+0xd5/0x1f0 scx_enable.isra.210+0x6c2/0x25b0 bpf_scx_reg+0x12/0x20 bpf_struct_ops_link_create+0x2c3/0x3b0 __sys_bpf+0x3102/0x4b00 __x64_sys_bpf+0x79/0xc0 x64_sys_call+0x15d9/0x1dd0 do_syscall_64+0xf0/0x470 entry_SYSCALL_64_after_hwframe+0x77/0x7f Fixes: bff3b5aec1b7 ("sched_ext: Move disable machinery into scx_sched") Cc: stable@vger.kernel.org # v6.16+ Signed-off-by: Zqiang Signed-off-by: Tejun Heo Signed-off-by: Greg Kroah-Hartman --- kernel/sched/ext.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/kernel/sched/ext.c b/kernel/sched/ext.c index 34d0d569548cc1..836a33e01cc860 100644 --- a/kernel/sched/ext.c +++ b/kernel/sched/ext.c @@ -3512,7 +3512,7 @@ static void scx_sched_free_rcu_work(struct work_struct *work) int node; irq_work_sync(&sch->error_irq_work); - kthread_stop(sch->helper->task); + kthread_destroy_worker(sch->helper); free_percpu(sch->pcpu); @@ -4504,7 +4504,7 @@ static struct scx_sched *scx_alloc_and_add_sched(struct sched_ext_ops *ops) return sch; err_stop_helper: - kthread_stop(sch->helper->task); + kthread_destroy_worker(sch->helper); err_free_pcpu: free_percpu(sch->pcpu); err_free_gdsqs: From 5ddd444484bea3e70521f6136cda9caf3d77adcc Mon Sep 17 00:00:00 2001 From: Tejun Heo Date: Tue, 9 Dec 2025 11:04:33 -1000 Subject: [PATCH 1541/3902] sched_ext: Fix bypass depth leak on scx_enable() failure commit 9f769637a93fac81689b80df6855f545839cf999 upstream. scx_enable() calls scx_bypass(true) to initialize in bypass mode and then scx_bypass(false) on success to exit. If scx_enable() fails during task initialization - e.g. scx_cgroup_init() or scx_init_task() returns an error - it jumps to err_disable while bypass is still active. scx_disable_workfn() then calls scx_bypass(true/false) for its own bypass, leaving the bypass depth at 1 instead of 0. This causes the system to remain permanently in bypass mode after a failed scx_enable(). Failures after task initialization is complete - e.g. scx_tryset_enable_state() at the end - already call scx_bypass(false) before reaching the error path and are not affected. This only affects a subset of failure modes. Fix it by tracking whether scx_enable() called scx_bypass(true) in a bool and having scx_disable_workfn() call an extra scx_bypass(false) to clear it. This is a temporary measure as the bypass depth will be moved into the sched instance, which will make this tracking unnecessary. Fixes: 8c2090c504e9 ("sched_ext: Initialize in bypass mode") Cc: stable@vger.kernel.org # v6.12+ Reported-by: Chris Mason Reviewed-by: Emil Tsalapatis Link: https://lore.kernel.org/stable/286e6f7787a81239e1ce2989b52391ce%40kernel.org Signed-off-by: Tejun Heo Signed-off-by: Greg Kroah-Hartman --- kernel/sched/ext.c | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/kernel/sched/ext.c b/kernel/sched/ext.c index 836a33e01cc860..29901d11b26049 100644 --- a/kernel/sched/ext.c +++ b/kernel/sched/ext.c @@ -40,6 +40,13 @@ static bool scx_init_task_enabled; static bool scx_switching_all; DEFINE_STATIC_KEY_FALSE(__scx_switched_all); +/* + * Tracks whether scx_enable() called scx_bypass(true). Used to balance bypass + * depth on enable failure. Will be removed when bypass depth is moved into the + * sched instance. + */ +static bool scx_bypassed_for_enable; + static atomic_long_t scx_nr_rejected = ATOMIC_LONG_INIT(0); static atomic_long_t scx_hotplug_seq = ATOMIC_LONG_INIT(0); @@ -4051,6 +4058,11 @@ static void scx_disable_workfn(struct kthread_work *work) scx_dsp_max_batch = 0; free_kick_pseqs(); + if (scx_bypassed_for_enable) { + scx_bypassed_for_enable = false; + scx_bypass(false); + } + mutex_unlock(&scx_enable_mutex); WARN_ON_ONCE(scx_set_enable_state(SCX_DISABLED) != SCX_DISABLING); @@ -4676,6 +4688,7 @@ static int scx_enable(struct sched_ext_ops *ops, struct bpf_link *link) * Init in bypass mode to guarantee forward progress. */ scx_bypass(true); + scx_bypassed_for_enable = true; for (i = SCX_OPI_NORMAL_BEGIN; i < SCX_OPI_NORMAL_END; i++) if (((void (**)(void))ops)[i]) @@ -4780,6 +4793,7 @@ static int scx_enable(struct sched_ext_ops *ops, struct bpf_link *link) scx_task_iter_stop(&sti); percpu_up_write(&scx_fork_rwsem); + scx_bypassed_for_enable = false; scx_bypass(false); if (!scx_tryset_enable_state(SCX_ENABLED, SCX_ENABLING)) { From 2ad1e2faebbdba686fcdf4d38a530763cf729244 Mon Sep 17 00:00:00 2001 From: Tejun Heo Date: Thu, 11 Dec 2025 15:45:04 -1000 Subject: [PATCH 1542/3902] sched_ext: Fix missing post-enqueue handling in move_local_task_to_local_dsq() commit f5e1e5ec204da11fa87fdf006d451d80ce06e118 upstream. move_local_task_to_local_dsq() is used when moving a task from a non-local DSQ to a local DSQ on the same CPU. It directly manipulates the local DSQ without going through dispatch_enqueue() and was missing the post-enqueue handling that triggers preemption when SCX_ENQ_PREEMPT is set or the idle task is running. The function is used by move_task_between_dsqs() which backs scx_bpf_dsq_move() and may be called while the CPU is busy. Add local_dsq_post_enq() call to move_local_task_to_local_dsq(). As the dispatch path doesn't need post-enqueue handling, add SCX_RQ_IN_BALANCE early exit to keep consume_dispatch_q() behavior unchanged and avoid triggering unnecessary resched when scx_bpf_dsq_move() is used from the dispatch path. Fixes: 4c30f5ce4f7a ("sched_ext: Implement scx_bpf_dispatch[_vtime]_from_dsq()") Cc: stable@vger.kernel.org # v6.12+ Reviewed-by: Andrea Righi Reviewed-by: Emil Tsalapatis Signed-off-by: Tejun Heo Signed-off-by: Greg Kroah-Hartman --- kernel/sched/ext.c | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/kernel/sched/ext.c b/kernel/sched/ext.c index 29901d11b26049..b959a70471c104 100644 --- a/kernel/sched/ext.c +++ b/kernel/sched/ext.c @@ -919,6 +919,14 @@ static void local_dsq_post_enq(struct scx_dispatch_q *dsq, struct task_struct *p struct rq *rq = container_of(dsq, struct rq, scx.local_dsq); bool preempt = false; + /* + * If @rq is in balance, the CPU is already vacant and looking for the + * next task to run. No need to preempt or trigger resched after moving + * @p into its local DSQ. + */ + if (rq->scx.flags & SCX_RQ_IN_BALANCE) + return; + if ((enq_flags & SCX_ENQ_PREEMPT) && p != rq->curr && rq->curr->sched_class == &ext_sched_class) { rq->curr->scx.slice = 0; @@ -1524,6 +1532,8 @@ static void move_local_task_to_local_dsq(struct task_struct *p, u64 enq_flags, dsq_mod_nr(dst_dsq, 1); p->scx.dsq = dst_dsq; + + local_dsq_post_enq(dst_dsq, p, enq_flags); } /** From 4bb9164e8f21ecb3372dfa051450bdcc1490dbaa Mon Sep 17 00:00:00 2001 From: Avadhut Naik Date: Fri, 21 Nov 2025 19:04:04 +0000 Subject: [PATCH 1543/3902] x86/mce: Do not clear bank's poll bit in mce_poll_banks on AMD SMCA systems commit d7ac083f095d894a0b8ac0573516bfd035e6b25a upstream. Currently, when a CMCI storm detected on a Machine Check bank, subsides, the bank's corresponding bit in the mce_poll_banks per-CPU variable is cleared unconditionally by cmci_storm_end(). On AMD SMCA systems, this essentially disables polling on that particular bank on that CPU. Consequently, any subsequent correctable errors or storms will not be logged. Since AMD SMCA systems allow banks to be managed by both polling and interrupts, the polling banks bitmap for a CPU, i.e., mce_poll_banks, should not be modified when a storm subsides. Fixes: 7eae17c4add5 ("x86/mce: Add per-bank CMCI storm mitigation") Signed-off-by: Avadhut Naik Signed-off-by: Borislav Petkov (AMD) Cc: stable@vger.kernel.org Link: https://patch.msgid.link/20251121190542.2447913-2-avadhut.naik@amd.com Signed-off-by: Greg Kroah-Hartman --- arch/x86/kernel/cpu/mce/threshold.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/arch/x86/kernel/cpu/mce/threshold.c b/arch/x86/kernel/cpu/mce/threshold.c index f4a007616468ec..61eaa17749311c 100644 --- a/arch/x86/kernel/cpu/mce/threshold.c +++ b/arch/x86/kernel/cpu/mce/threshold.c @@ -85,7 +85,8 @@ void cmci_storm_end(unsigned int bank) { struct mca_storm_desc *storm = this_cpu_ptr(&storm_desc); - __clear_bit(bank, this_cpu_ptr(mce_poll_banks)); + if (!mce_flags.amd_threshold) + __clear_bit(bank, this_cpu_ptr(mce_poll_banks)); storm->banks[bank].history = 0; storm->banks[bank].in_storm_mode = false; From 9285d04830639e4bfe05a94fef50296014f9bffe Mon Sep 17 00:00:00 2001 From: Sarthak Garg Date: Fri, 14 Nov 2025 13:58:24 +0530 Subject: [PATCH 1544/3902] mmc: sdhci-msm: Avoid early clock doubling during HS400 transition commit b1f856b1727c2eaa4be2c6d7cd7a8ed052bbeb87 upstream. According to the hardware programming guide, the clock frequency must remain below 52MHz during the transition to HS400 mode. However,in the current implementation, the timing is set to HS400 (a DDR mode) before adjusting the clock. This causes the clock to double prematurely to 104MHz during the transition phase, violating the specification and potentially resulting in CRC errors or CMD timeouts. This change ensures that clock doubling is avoided during intermediate transitions and is applied only when the card requires a 200MHz clock for HS400 operation. Signed-off-by: Sarthak Garg Reviewed-by: Bjorn Andersson Acked-by: Adrian Hunter Cc: stable@vger.kernel.org Signed-off-by: Ulf Hansson Signed-off-by: Greg Kroah-Hartman --- drivers/mmc/host/sdhci-msm.c | 27 +++++++++++++++------------ 1 file changed, 15 insertions(+), 12 deletions(-) diff --git a/drivers/mmc/host/sdhci-msm.c b/drivers/mmc/host/sdhci-msm.c index 4e5edbf2fc9b6f..3b85233131b386 100644 --- a/drivers/mmc/host/sdhci-msm.c +++ b/drivers/mmc/host/sdhci-msm.c @@ -344,41 +344,43 @@ static void sdhci_msm_v5_variant_writel_relaxed(u32 val, writel_relaxed(val, host->ioaddr + offset); } -static unsigned int msm_get_clock_mult_for_bus_mode(struct sdhci_host *host) +static unsigned int msm_get_clock_mult_for_bus_mode(struct sdhci_host *host, + unsigned int clock, + unsigned int timing) { - struct mmc_ios ios = host->mmc->ios; /* * The SDHC requires internal clock frequency to be double the * actual clock that will be set for DDR mode. The controller * uses the faster clock(100/400MHz) for some of its parts and * send the actual required clock (50/200MHz) to the card. */ - if (ios.timing == MMC_TIMING_UHS_DDR50 || - ios.timing == MMC_TIMING_MMC_DDR52 || - ios.timing == MMC_TIMING_MMC_HS400 || + if (timing == MMC_TIMING_UHS_DDR50 || + timing == MMC_TIMING_MMC_DDR52 || + (timing == MMC_TIMING_MMC_HS400 && + clock == MMC_HS200_MAX_DTR) || host->flags & SDHCI_HS400_TUNING) return 2; return 1; } static void msm_set_clock_rate_for_bus_mode(struct sdhci_host *host, - unsigned int clock) + unsigned int clock, + unsigned int timing) { struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host); struct sdhci_msm_host *msm_host = sdhci_pltfm_priv(pltfm_host); - struct mmc_ios curr_ios = host->mmc->ios; struct clk *core_clk = msm_host->bulk_clks[0].clk; unsigned long achieved_rate; unsigned int desired_rate; unsigned int mult; int rc; - mult = msm_get_clock_mult_for_bus_mode(host); + mult = msm_get_clock_mult_for_bus_mode(host, clock, timing); desired_rate = clock * mult; rc = dev_pm_opp_set_rate(mmc_dev(host->mmc), desired_rate); if (rc) { pr_err("%s: Failed to set clock at rate %u at timing %d\n", - mmc_hostname(host->mmc), desired_rate, curr_ios.timing); + mmc_hostname(host->mmc), desired_rate, timing); return; } @@ -397,7 +399,7 @@ static void msm_set_clock_rate_for_bus_mode(struct sdhci_host *host, msm_host->clk_rate = desired_rate; pr_debug("%s: Setting clock at rate %lu at timing %d\n", - mmc_hostname(host->mmc), achieved_rate, curr_ios.timing); + mmc_hostname(host->mmc), achieved_rate, timing); } /* Platform specific tuning */ @@ -1239,7 +1241,7 @@ static int sdhci_msm_execute_tuning(struct mmc_host *mmc, u32 opcode) */ if (host->flags & SDHCI_HS400_TUNING) { sdhci_msm_hc_select_mode(host); - msm_set_clock_rate_for_bus_mode(host, ios.clock); + msm_set_clock_rate_for_bus_mode(host, ios.clock, ios.timing); host->flags &= ~SDHCI_HS400_TUNING; } @@ -1864,6 +1866,7 @@ static void sdhci_msm_set_clock(struct sdhci_host *host, unsigned int clock) { struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host); struct sdhci_msm_host *msm_host = sdhci_pltfm_priv(pltfm_host); + struct mmc_ios ios = host->mmc->ios; if (!clock) { host->mmc->actual_clock = msm_host->clk_rate = 0; @@ -1872,7 +1875,7 @@ static void sdhci_msm_set_clock(struct sdhci_host *host, unsigned int clock) sdhci_msm_hc_select_mode(host); - msm_set_clock_rate_for_bus_mode(host, clock); + msm_set_clock_rate_for_bus_mode(host, ios.clock, ios.timing); out: __sdhci_msm_set_clock(host, clock); } From 344057b2605d53b325ce029d0c07ad5892ce6974 Mon Sep 17 00:00:00 2001 From: Alexey Minnekhanov Date: Sun, 16 Nov 2025 04:12:33 +0300 Subject: [PATCH 1545/3902] dt-bindings: clock: mmcc-sdm660: Add missing MDSS reset commit c57210bc15371caa06a5d4040e7d8aaeed4cb661 upstream. Add definition for display subsystem reset control, so display driver can reset display controller properly, clearing any configuration left there by bootloader. Since 6.17 after PM domains rework it became necessary for display to function. Fixes: 0e789b491ba0 ("pmdomain: core: Leave powered-on genpds on until sync_state") Cc: stable@vger.kernel.org # 6.17 Signed-off-by: Alexey Minnekhanov Acked-by: Krzysztof Kozlowski Link: https://lore.kernel.org/r/20251116-sdm660-mdss-reset-v2-1-6219bec0a97f@postmarketos.org Signed-off-by: Bjorn Andersson Signed-off-by: Greg Kroah-Hartman --- include/dt-bindings/clock/qcom,mmcc-sdm660.h | 1 + 1 file changed, 1 insertion(+) diff --git a/include/dt-bindings/clock/qcom,mmcc-sdm660.h b/include/dt-bindings/clock/qcom,mmcc-sdm660.h index f9dbc21cb5c7cf..ee2a89dae72dc5 100644 --- a/include/dt-bindings/clock/qcom,mmcc-sdm660.h +++ b/include/dt-bindings/clock/qcom,mmcc-sdm660.h @@ -157,6 +157,7 @@ #define BIMC_SMMU_GDSC 7 #define CAMSS_MICRO_BCR 0 +#define MDSS_BCR 1 #endif From 265fad7d9c42cb7b29c2de06a645dce33e7b9f42 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Draszik?= Date: Mon, 6 Oct 2025 09:07:12 +0100 Subject: [PATCH 1546/3902] phy: exynos5-usbdrd: fix clock prepare imbalance MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit commit 5e428e45bf17a8f3784099ca5ded16e3b5d59766 upstream. Commit f4fb9c4d7f94 ("phy: exynos5-usbdrd: allow DWC3 runtime suspend with UDC bound (E850+)") incorrectly added clk_bulk_disable() as the inverse of clk_bulk_prepare_enable() while it should have of course used clk_bulk_disable_unprepare(). This means incorrect reference counts to the CMU driver remain. Update the code accordingly. Fixes: f4fb9c4d7f94 ("phy: exynos5-usbdrd: allow DWC3 runtime suspend with UDC bound (E850+)") CC: stable@vger.kernel.org Signed-off-by: André Draszik Reviewed-by: Sam Protsenko Reviewed-by: Peter Griffin Link: https://patch.msgid.link/20251006-gs101-usb-phy-clk-imbalance-v1-1-205b206126cf@linaro.org Signed-off-by: Vinod Koul Signed-off-by: Greg Kroah-Hartman --- drivers/phy/samsung/phy-exynos5-usbdrd.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/phy/samsung/phy-exynos5-usbdrd.c b/drivers/phy/samsung/phy-exynos5-usbdrd.c index a88ba95bdc8f53..1c8bf80119f11e 100644 --- a/drivers/phy/samsung/phy-exynos5-usbdrd.c +++ b/drivers/phy/samsung/phy-exynos5-usbdrd.c @@ -1823,7 +1823,7 @@ static int exynos5_usbdrd_orien_sw_set(struct typec_switch_dev *sw, phy_drd->orientation = orientation; } - clk_bulk_disable(phy_drd->drv_data->n_clks, phy_drd->clks); + clk_bulk_disable_unprepare(phy_drd->drv_data->n_clks, phy_drd->clks); return 0; } From bacecf370e0cca03bd45a4f4bfc500be3a37bd35 Mon Sep 17 00:00:00 2001 From: Ard Biesheuvel Date: Wed, 15 Oct 2025 22:56:36 +0200 Subject: [PATCH 1547/3902] efi: Add missing static initializer for efi_mm::cpus_allowed_lock commit 40374d308e4e456048d83991e937f13fc8bda8bf upstream. Initialize the cpus_allowed_lock struct member of efi_mm. Cc: stable@vger.kernel.org Signed-off-by: Ard Biesheuvel Acked-by: Catalin Marinas Signed-off-by: Catalin Marinas Signed-off-by: Greg Kroah-Hartman --- drivers/firmware/efi/efi.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/drivers/firmware/efi/efi.c b/drivers/firmware/efi/efi.c index 1ce428e2ac8a0e..fc407d891348f0 100644 --- a/drivers/firmware/efi/efi.c +++ b/drivers/firmware/efi/efi.c @@ -74,6 +74,9 @@ struct mm_struct efi_mm = { .page_table_lock = __SPIN_LOCK_UNLOCKED(efi_mm.page_table_lock), .mmlist = LIST_HEAD_INIT(efi_mm.mmlist), .cpu_bitmap = { [BITS_TO_LONGS(NR_CPUS)] = 0}, +#ifdef CONFIG_SCHED_MM_CID + .cpus_allowed_lock = __RAW_SPIN_LOCK_UNLOCKED(efi_mm.cpus_allowed_lock), +#endif }; struct workqueue_struct *efi_rts_wq; From 0eeaa2ba3b43cc7b0608b6e467ed9ff0ae527847 Mon Sep 17 00:00:00 2001 From: Ma Ke Date: Wed, 22 Oct 2025 19:53:25 +0800 Subject: [PATCH 1548/3902] perf: arm_cspmu: fix error handling in arm_cspmu_impl_unregister() commit 970e1e41805f0bd49dc234330a9390f4708d097d upstream. driver_find_device() calls get_device() to increment the reference count once a matching device is found. device_release_driver() releases the driver, but it does not decrease the reference count that was incremented by driver_find_device(). At the end of the loop, there is no put_device() to balance the reference count. To avoid reference count leakage, add put_device() to decrease the reference count. Found by code review. Cc: stable@vger.kernel.org Fixes: bfc653aa89cb ("perf: arm_cspmu: Separate Arm and vendor module") Signed-off-by: Ma Ke Signed-off-by: Will Deacon Signed-off-by: Greg Kroah-Hartman --- drivers/perf/arm_cspmu/arm_cspmu.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/drivers/perf/arm_cspmu/arm_cspmu.c b/drivers/perf/arm_cspmu/arm_cspmu.c index efa9b229e7012e..e0d4293f06f93f 100644 --- a/drivers/perf/arm_cspmu/arm_cspmu.c +++ b/drivers/perf/arm_cspmu/arm_cspmu.c @@ -1365,8 +1365,10 @@ void arm_cspmu_impl_unregister(const struct arm_cspmu_impl_match *impl_match) /* Unbind the driver from all matching backend devices. */ while ((dev = driver_find_device(&arm_cspmu_driver.driver, NULL, - match, arm_cspmu_match_device))) + match, arm_cspmu_match_device))) { device_release_driver(dev); + put_device(dev); + } mutex_lock(&arm_cspmu_lock); From 3b95fdb75d74c9bc4aee5868f7b3499af00c1632 Mon Sep 17 00:00:00 2001 From: Eric Biggers Date: Sun, 2 Nov 2025 15:42:04 -0800 Subject: [PATCH 1549/3902] lib/crypto: x86/blake2s: Fix 32-bit arg treated as 64-bit commit 2f22115709fc7ebcfa40af3367a508fbbd2f71e9 upstream. In the C code, the 'inc' argument to the assembly functions blake2s_compress_ssse3() and blake2s_compress_avx512() is declared with type u32, matching blake2s_compress(). The assembly code then reads it from the 64-bit %rcx. However, the ABI doesn't guarantee zero-extension to 64 bits, nor do gcc or clang guarantee it. Therefore, fix these functions to read this argument from the 32-bit %ecx. In theory, this bug could have caused the wrong 'inc' value to be used, causing incorrect BLAKE2s hashes. In practice, probably not: I've fixed essentially this same bug in many other assembly files too, but there's never been a real report of it having caused a problem. In x86_64, all writes to 32-bit registers are zero-extended to 64 bits. That results in zero-extension in nearly all situations. I've only been able to demonstrate a lack of zero-extension with a somewhat contrived example involving truncation, e.g. when the C code has a u64 variable holding 0x1234567800000040 and passes it as a u32 expecting it to be truncated to 0x40 (64). But that's not what the real code does, of course. Fixes: ed0356eda153 ("crypto: blake2s - x86_64 SIMD implementation") Cc: stable@vger.kernel.org Reviewed-by: Ard Biesheuvel Link: https://lore.kernel.org/r/20251102234209.62133-2-ebiggers@kernel.org Signed-off-by: Eric Biggers Signed-off-by: Greg Kroah-Hartman --- lib/crypto/x86/blake2s-core.S | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/crypto/x86/blake2s-core.S b/lib/crypto/x86/blake2s-core.S index ef8e9f427aab34..093e7814f38796 100644 --- a/lib/crypto/x86/blake2s-core.S +++ b/lib/crypto/x86/blake2s-core.S @@ -52,7 +52,7 @@ SYM_FUNC_START(blake2s_compress_ssse3) movdqa ROT16(%rip),%xmm12 movdqa ROR328(%rip),%xmm13 movdqu 0x20(%rdi),%xmm14 - movq %rcx,%xmm15 + movd %ecx,%xmm15 leaq SIGMA+0xa0(%rip),%r8 jmp .Lbeginofloop .align 32 @@ -176,7 +176,7 @@ SYM_FUNC_START(blake2s_compress_avx512) vmovdqu (%rdi),%xmm0 vmovdqu 0x10(%rdi),%xmm1 vmovdqu 0x20(%rdi),%xmm4 - vmovq %rcx,%xmm5 + vmovd %ecx,%xmm5 vmovdqa IV(%rip),%xmm14 vmovdqa IV+16(%rip),%xmm15 jmp .Lblake2s_compress_avx512_mainloop From aceaa18f791add733b969f8b70fe614a3e43f183 Mon Sep 17 00:00:00 2001 From: Stefan Haberland Date: Wed, 26 Nov 2025 17:06:31 +0100 Subject: [PATCH 1550/3902] s390/dasd: Fix gendisk parent after copy pair swap commit c943bfc6afb8d0e781b9b7406f36caa8bbf95cb9 upstream. After a copy pair swap the block device's "device" symlink points to the secondary CCW device, but the gendisk's parent remained the primary, leaving /sys/block/ under the wrong parent. Move the gendisk to the secondary's device with device_move(), keeping the sysfs topology consistent after the swap. Fixes: 413862caad6f ("s390/dasd: add copy pair swap capability") Cc: stable@vger.kernel.org #6.1 Reviewed-by: Jan Hoeppner Signed-off-by: Stefan Haberland Signed-off-by: Jens Axboe Signed-off-by: Greg Kroah-Hartman --- drivers/s390/block/dasd_eckd.c | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/drivers/s390/block/dasd_eckd.c b/drivers/s390/block/dasd_eckd.c index 88fa17aea2ec84..ec0c62e5ef73b6 100644 --- a/drivers/s390/block/dasd_eckd.c +++ b/drivers/s390/block/dasd_eckd.c @@ -6150,6 +6150,7 @@ static int dasd_eckd_copy_pair_swap(struct dasd_device *device, char *prim_busid struct dasd_copy_relation *copy; struct dasd_block *block; struct gendisk *gdp; + int rc; copy = device->copy; if (!copy) @@ -6184,6 +6185,13 @@ static int dasd_eckd_copy_pair_swap(struct dasd_device *device, char *prim_busid /* swap blocklayer device link */ gdp = block->gdp; dasd_add_link_to_gendisk(gdp, secondary); + rc = device_move(disk_to_dev(gdp), &secondary->cdev->dev, DPM_ORDER_NONE); + if (rc) { + dev_err(&primary->cdev->dev, + "copy_pair_swap: moving blockdevice parent %s->%s failed (%d)\n", + dev_name(&primary->cdev->dev), + dev_name(&secondary->cdev->dev), rc); + } /* re-enable device */ dasd_device_remove_stop_bits(primary, DASD_STOPPED_PPRC); From 498e286941b961903be570d52d44e57f150d55ea Mon Sep 17 00:00:00 2001 From: "Sven Eckelmann (Plasma Cloud)" Date: Fri, 26 Sep 2025 11:32:54 +0200 Subject: [PATCH 1551/3902] wifi: mt76: Fix DTS power-limits on little endian systems commit 38b845e1f9e810869b0a0b69f202b877b7b7fb12 upstream. The power-limits for ru and mcs and stored in the devicetree as bytewise array (often with sizes which are not a multiple of 4). These arrays have a prefix which defines for how many modes a line is applied. This prefix is also only a byte - but the code still tried to fix the endianness of this byte with a be32 operation. As result, loading was mostly failing or was sending completely unexpected values to the firmware. Since the other rates are also stored in the devicetree as bytewise arrays, just drop the u32 access + be32_to_cpu conversion and directly access them as bytes arrays. Cc: stable@vger.kernel.org Fixes: 22b980badc0f ("mt76: add functions for parsing rate power limits from DT") Fixes: a9627d992b5e ("mt76: extend DT rate power limits to support 11ax devices") Signed-off-by: Sven Eckelmann (Plasma Cloud) Signed-off-by: Felix Fietkau Signed-off-by: Greg Kroah-Hartman --- drivers/net/wireless/mediatek/mt76/eeprom.c | 37 +++++++++++++-------- 1 file changed, 24 insertions(+), 13 deletions(-) diff --git a/drivers/net/wireless/mediatek/mt76/eeprom.c b/drivers/net/wireless/mediatek/mt76/eeprom.c index a987c5e4eff6c6..6ce8e4af18fe53 100644 --- a/drivers/net/wireless/mediatek/mt76/eeprom.c +++ b/drivers/net/wireless/mediatek/mt76/eeprom.c @@ -253,6 +253,19 @@ mt76_get_of_array(struct device_node *np, char *name, size_t *len, int min) return prop->value; } +static const s8 * +mt76_get_of_array_s8(struct device_node *np, char *name, size_t *len, int min) +{ + struct property *prop = of_find_property(np, name, NULL); + + if (!prop || !prop->value || prop->length < min) + return NULL; + + *len = prop->length; + + return prop->value; +} + struct device_node * mt76_find_channel_node(struct device_node *np, struct ieee80211_channel *chan) { @@ -294,7 +307,7 @@ mt76_get_txs_delta(struct device_node *np, u8 nss) } static void -mt76_apply_array_limit(s8 *pwr, size_t pwr_len, const __be32 *data, +mt76_apply_array_limit(s8 *pwr, size_t pwr_len, const s8 *data, s8 target_power, s8 nss_delta, s8 *max_power) { int i; @@ -303,15 +316,14 @@ mt76_apply_array_limit(s8 *pwr, size_t pwr_len, const __be32 *data, return; for (i = 0; i < pwr_len; i++) { - pwr[i] = min_t(s8, target_power, - be32_to_cpu(data[i]) + nss_delta); + pwr[i] = min_t(s8, target_power, data[i] + nss_delta); *max_power = max(*max_power, pwr[i]); } } static void mt76_apply_multi_array_limit(s8 *pwr, size_t pwr_len, s8 pwr_num, - const __be32 *data, size_t len, s8 target_power, + const s8 *data, size_t len, s8 target_power, s8 nss_delta, s8 *max_power) { int i, cur; @@ -319,8 +331,7 @@ mt76_apply_multi_array_limit(s8 *pwr, size_t pwr_len, s8 pwr_num, if (!data) return; - len /= 4; - cur = be32_to_cpu(data[0]); + cur = data[0]; for (i = 0; i < pwr_num; i++) { if (len < pwr_len + 1) break; @@ -335,7 +346,7 @@ mt76_apply_multi_array_limit(s8 *pwr, size_t pwr_len, s8 pwr_num, if (!len) break; - cur = be32_to_cpu(data[0]); + cur = data[0]; } } @@ -346,7 +357,7 @@ s8 mt76_get_rate_power_limits(struct mt76_phy *phy, { struct mt76_dev *dev = phy->dev; struct device_node *np; - const __be32 *val; + const s8 *val; char name[16]; u32 mcs_rates = dev->drv->mcs_rates; u32 ru_rates = ARRAY_SIZE(dest->ru[0]); @@ -392,21 +403,21 @@ s8 mt76_get_rate_power_limits(struct mt76_phy *phy, txs_delta = mt76_get_txs_delta(np, hweight16(phy->chainmask)); - val = mt76_get_of_array(np, "rates-cck", &len, ARRAY_SIZE(dest->cck)); + val = mt76_get_of_array_s8(np, "rates-cck", &len, ARRAY_SIZE(dest->cck)); mt76_apply_array_limit(dest->cck, ARRAY_SIZE(dest->cck), val, target_power, txs_delta, &max_power); - val = mt76_get_of_array(np, "rates-ofdm", - &len, ARRAY_SIZE(dest->ofdm)); + val = mt76_get_of_array_s8(np, "rates-ofdm", + &len, ARRAY_SIZE(dest->ofdm)); mt76_apply_array_limit(dest->ofdm, ARRAY_SIZE(dest->ofdm), val, target_power, txs_delta, &max_power); - val = mt76_get_of_array(np, "rates-mcs", &len, mcs_rates + 1); + val = mt76_get_of_array_s8(np, "rates-mcs", &len, mcs_rates + 1); mt76_apply_multi_array_limit(dest->mcs[0], ARRAY_SIZE(dest->mcs[0]), ARRAY_SIZE(dest->mcs), val, len, target_power, txs_delta, &max_power); - val = mt76_get_of_array(np, "rates-ru", &len, ru_rates + 1); + val = mt76_get_of_array_s8(np, "rates-ru", &len, ru_rates + 1); mt76_apply_multi_array_limit(dest->ru[0], ARRAY_SIZE(dest->ru[0]), ARRAY_SIZE(dest->ru), val, len, target_power, txs_delta, &max_power); From 98d65b62ddf84db83aafe4a9ef914428c835cd07 Mon Sep 17 00:00:00 2001 From: Josef Bacik Date: Tue, 18 Nov 2025 17:08:41 +0100 Subject: [PATCH 1552/3902] btrfs: don't rewrite ret from inode_permission commit 0185c2292c600993199bc6b1f342ad47a9e8c678 upstream. In our user safe ino resolve ioctl we'll just turn any ret into -EACCES from inode_permission(). This is redundant, and could potentially be wrong if we had an ENOMEM in the security layer or some such other error, so simply return the actual return value. Note: The patch was taken from v5 of fscrypt patchset (https://lore.kernel.org/linux-btrfs/cover.1706116485.git.josef@toxicpanda.com/) which was handled over time by various people: Omar Sandoval, Sweet Tea Dorminy, Josef Bacik. Fixes: 23d0b79dfaed ("btrfs: Add unprivileged version of ino_lookup ioctl") CC: stable@vger.kernel.org # 5.4+ Reviewed-by: Johannes Thumshirn Signed-off-by: Josef Bacik Signed-off-by: Daniel Vacek Reviewed-by: David Sterba [ add note ] Signed-off-by: David Sterba Signed-off-by: Greg Kroah-Hartman --- fs/btrfs/ioctl.c | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/fs/btrfs/ioctl.c b/fs/btrfs/ioctl.c index 8cb7d5a462ef79..9a34d6530658e5 100644 --- a/fs/btrfs/ioctl.c +++ b/fs/btrfs/ioctl.c @@ -1913,10 +1913,8 @@ static int btrfs_search_path_in_tree_user(struct mnt_idmap *idmap, ret = inode_permission(idmap, &temp_inode->vfs_inode, MAY_READ | MAY_EXEC); iput(&temp_inode->vfs_inode); - if (ret) { - ret = -EACCES; + if (ret) goto out_put; - } if (key.offset == upper_limit) break; From 3715bb43ccd3ce2a04fa50e8007c1c81e29360ed Mon Sep 17 00:00:00 2001 From: Alexey Velichayshiy Date: Mon, 17 Nov 2025 12:05:18 +0300 Subject: [PATCH 1553/3902] gfs2: fix freeze error handling commit 4cfc7d5a4a01d2133b278cdbb1371fba1b419174 upstream. After commit b77b4a4815a9 ("gfs2: Rework freeze / thaw logic"), the freeze error handling is broken because gfs2_do_thaw() overwrites the 'error' variable, causing incorrect processing of the original freeze error. Fix this by calling gfs2_do_thaw() when gfs2_lock_fs_check_clean() fails but ignoring its return value to preserve the original freeze error for proper reporting. Found by Linux Verification Center (linuxtesting.org) with SVACE. Fixes: b77b4a4815a9 ("gfs2: Rework freeze / thaw logic") Cc: stable@vger.kernel.org # v6.5+ Signed-off-by: Alexey Velichayshiy Signed-off-by: Andreas Gruenbacher Signed-off-by: Greg Kroah-Hartman --- fs/gfs2/super.c | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/fs/gfs2/super.c b/fs/gfs2/super.c index 644b2d1e72769e..54c6f2098f01ef 100644 --- a/fs/gfs2/super.c +++ b/fs/gfs2/super.c @@ -749,9 +749,7 @@ static int gfs2_freeze_super(struct super_block *sb, enum freeze_holder who, break; } - error = gfs2_do_thaw(sdp, who, freeze_owner); - if (error) - goto out; + (void)gfs2_do_thaw(sdp, who, freeze_owner); if (error == -EBUSY) fs_err(sdp, "waiting for recovery before freeze\n"); From 8dd553771afe454ca2c4a3ad66d7d51b50f436df Mon Sep 17 00:00:00 2001 From: Li Chen Date: Mon, 17 Nov 2025 13:34:07 +0800 Subject: [PATCH 1554/3902] block: rate-limit capacity change info log commit 3179a5f7f86bcc3acd5d6fb2a29f891ef5615852 upstream. loop devices under heavy stress-ng loop streessor can trigger many capacity change events in a short time. Each event prints an info message from set_capacity_and_notify(), flooding the console and contributing to soft lockups on slow consoles. Switch the printk in set_capacity_and_notify() to pr_info_ratelimited() so frequent capacity changes do not spam the log while still reporting occasional changes. Cc: stable@vger.kernel.org Signed-off-by: Li Chen Reviewed-by: Chaitanya Kulkarni Reviewed-by: Bart Van Assche Signed-off-by: Jens Axboe Signed-off-by: Greg Kroah-Hartman --- block/genhd.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/block/genhd.c b/block/genhd.c index 9bbc38d1279266..bd3a6841e5b59a 100644 --- a/block/genhd.c +++ b/block/genhd.c @@ -90,7 +90,7 @@ bool set_capacity_and_notify(struct gendisk *disk, sector_t size) (disk->flags & GENHD_FL_HIDDEN)) return false; - pr_info("%s: detected capacity change from %lld to %lld\n", + pr_info_ratelimited("%s: detected capacity change from %lld to %lld\n", disk->disk_name, capacity, size); /* From cbaae7bb64d40e49928e3428faa7a5c3d0217ee7 Mon Sep 17 00:00:00 2001 From: Ye Bin Date: Mon, 3 Nov 2025 09:01:23 +0800 Subject: [PATCH 1555/3902] jbd2: fix the inconsistency between checksum and data in memory for journal sb commit 6abfe107894af7e8ce3a2e120c619d81ee764ad5 upstream. Copying the file system while it is mounted as read-only results in a mount failure: [~]# mkfs.ext4 -F /dev/sdc [~]# mount /dev/sdc -o ro /mnt/test [~]# dd if=/dev/sdc of=/dev/sda bs=1M [~]# mount /dev/sda /mnt/test1 [ 1094.849826] JBD2: journal checksum error [ 1094.850927] EXT4-fs (sda): Could not load journal inode mount: mount /dev/sda on /mnt/test1 failed: Bad message The process described above is just an abstracted way I came up with to reproduce the issue. In the actual scenario, the file system was mounted read-only and then copied while it was still mounted. It was found that the mount operation failed. The user intended to verify the data or use it as a backup, and this action was performed during a version upgrade. Above issue may happen as follows: ext4_fill_super set_journal_csum_feature_set(sb) if (ext4_has_metadata_csum(sb)) incompat = JBD2_FEATURE_INCOMPAT_CSUM_V3; if (test_opt(sb, JOURNAL_CHECKSUM) jbd2_journal_set_features(sbi->s_journal, compat, 0, incompat); lock_buffer(journal->j_sb_buffer); sb->s_feature_incompat |= cpu_to_be32(incompat); //The data in the journal sb was modified, but the checksum was not updated, so the data remaining in memory has a mismatch between the data and the checksum. unlock_buffer(journal->j_sb_buffer); In this case, the journal sb copied over is in a state where the checksum and data are inconsistent, so mounting fails. To solve the above issue, update the checksum in memory after modifying the journal sb. Fixes: 4fd5ea43bc11 ("jbd2: checksum journal superblock") Signed-off-by: Ye Bin Reviewed-by: Baokun Li Reviewed-by: Darrick J. Wong Reviewed-by: Jan Kara Message-ID: <20251103010123.3753631-1-yebin@huaweicloud.com> Signed-off-by: Theodore Ts'o Cc: stable@kernel.org Signed-off-by: Greg Kroah-Hartman --- fs/jbd2/journal.c | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/fs/jbd2/journal.c b/fs/jbd2/journal.c index d480b94117cd77..bf255f8b5eebf7 100644 --- a/fs/jbd2/journal.c +++ b/fs/jbd2/journal.c @@ -2349,6 +2349,12 @@ int jbd2_journal_set_features(journal_t *journal, unsigned long compat, sb->s_feature_compat |= cpu_to_be32(compat); sb->s_feature_ro_compat |= cpu_to_be32(ro); sb->s_feature_incompat |= cpu_to_be32(incompat); + /* + * Update the checksum now so that it is valid even for read-only + * filesystems where jbd2_write_superblock() doesn't get called. + */ + if (jbd2_journal_has_csum_v2or3(journal)) + sb->s_checksum = jbd2_superblock_csum(sb); unlock_buffer(journal->j_sb_buffer); jbd2_journal_init_transaction_limits(journal); @@ -2378,9 +2384,17 @@ void jbd2_journal_clear_features(journal_t *journal, unsigned long compat, sb = journal->j_superblock; + lock_buffer(journal->j_sb_buffer); sb->s_feature_compat &= ~cpu_to_be32(compat); sb->s_feature_ro_compat &= ~cpu_to_be32(ro); sb->s_feature_incompat &= ~cpu_to_be32(incompat); + /* + * Update the checksum now so that it is valid even for read-only + * filesystems where jbd2_write_superblock() doesn't get called. + */ + if (jbd2_journal_has_csum_v2or3(journal)) + sb->s_checksum = jbd2_superblock_csum(sb); + unlock_buffer(journal->j_sb_buffer); jbd2_journal_init_transaction_limits(journal); } EXPORT_SYMBOL(jbd2_journal_clear_features); From 3312f03e9948ce538800cd5ca5d4bf3370e9da8f Mon Sep 17 00:00:00 2001 From: Rene Rebe Date: Fri, 14 Nov 2025 14:41:27 +0100 Subject: [PATCH 1556/3902] floppy: fix for PAGE_SIZE != 4KB MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit commit 82d20481024cbae2ea87fe8b86d12961bfda7169 upstream. For years I wondered why the floppy driver does not just work on sparc64, e.g: root@SUNW_375_0066:# disktype /dev/fd0 disktype: Can't open /dev/fd0: No such device or address [ 525.341906] disktype: attempt to access beyond end of device fd0: rw=0, sector=0, nr_sectors = 16 limit=8 [ 525.341991] floppy: error 10 while reading block 0 Turns out floppy.c __floppy_read_block_0 tries to read one page for the first test read to determine the disk size and thus fails if that is greater than 4k. Adjust minimum MAX_DISK_SIZE to PAGE_SIZE to fix floppy on sparc64 and likely all other PAGE_SIZE != 4KB configs. Cc: stable@vger.kernel.org Signed-off-by: René Rebe Signed-off-by: Jens Axboe Signed-off-by: Greg Kroah-Hartman --- drivers/block/floppy.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/block/floppy.c b/drivers/block/floppy.c index 5336c3c5ca362d..c28786e0fe1c85 100644 --- a/drivers/block/floppy.c +++ b/drivers/block/floppy.c @@ -329,7 +329,7 @@ static bool initialized; * This default is used whenever the current disk size is unknown. * [Now it is rather a minimum] */ -#define MAX_DISK_SIZE 4 /* 3984 */ +#define MAX_DISK_SIZE (PAGE_SIZE / 1024) /* * globals used by 'result()' From 17900c56e263732687989b606ba5a6d1c44a0fa1 Mon Sep 17 00:00:00 2001 From: Eric Biggers Date: Sat, 15 Nov 2025 15:08:16 -0800 Subject: [PATCH 1557/3902] crypto: scatterwalk - Fix memcpy_sglist() to always succeed commit 4dffc9bbffb9ccfcda730d899c97c553599e7ca8 upstream. The original implementation of memcpy_sglist() was broken because it didn't handle scatterlists that describe exactly the same memory, which is a case that many callers rely on. The current implementation is broken too because it calls the skcipher_walk functions which can fail. It ignores any errors from those functions. Fix it by replacing it with a new implementation written from scratch. It always succeeds. It's also a bit faster, since it avoids the overhead of skcipher_walk. skcipher_walk includes a lot of functionality (such as alignmask handling) that's irrelevant here. Reported-by: Colin Ian King Closes: https://lore.kernel.org/r/20251114122620.111623-1-coking@nvidia.com Fixes: 131bdceca1f0 ("crypto: scatterwalk - Add memcpy_sglist") Fixes: 0f8d42bf128d ("crypto: scatterwalk - Move skcipher walk and use it for memcpy_sglist") Cc: stable@vger.kernel.org Signed-off-by: Eric Biggers Signed-off-by: Herbert Xu Signed-off-by: Greg Kroah-Hartman --- crypto/scatterwalk.c | 97 +++++++++++++++++++++++++++++++----- include/crypto/scatterwalk.h | 52 +++++++++++-------- 2 files changed, 115 insertions(+), 34 deletions(-) diff --git a/crypto/scatterwalk.c b/crypto/scatterwalk.c index 1d010e2a1b1a2e..b95e5974e327bf 100644 --- a/crypto/scatterwalk.c +++ b/crypto/scatterwalk.c @@ -101,26 +101,97 @@ void memcpy_to_sglist(struct scatterlist *sg, unsigned int start, } EXPORT_SYMBOL_GPL(memcpy_to_sglist); +/** + * memcpy_sglist() - Copy data from one scatterlist to another + * @dst: The destination scatterlist. Can be NULL if @nbytes == 0. + * @src: The source scatterlist. Can be NULL if @nbytes == 0. + * @nbytes: Number of bytes to copy + * + * The scatterlists can describe exactly the same memory, in which case this + * function is a no-op. No other overlaps are supported. + * + * Context: Any context + */ void memcpy_sglist(struct scatterlist *dst, struct scatterlist *src, unsigned int nbytes) { - struct skcipher_walk walk = {}; + unsigned int src_offset, dst_offset; - if (unlikely(nbytes == 0)) /* in case sg == NULL */ + if (unlikely(nbytes == 0)) /* in case src and/or dst is NULL */ return; - walk.total = nbytes; - - scatterwalk_start(&walk.in, src); - scatterwalk_start(&walk.out, dst); + src_offset = src->offset; + dst_offset = dst->offset; + for (;;) { + /* Compute the length to copy this step. */ + unsigned int len = min3(src->offset + src->length - src_offset, + dst->offset + dst->length - dst_offset, + nbytes); + struct page *src_page = sg_page(src); + struct page *dst_page = sg_page(dst); + const void *src_virt; + void *dst_virt; + + if (IS_ENABLED(CONFIG_HIGHMEM)) { + /* HIGHMEM: we may have to actually map the pages. */ + const unsigned int src_oip = offset_in_page(src_offset); + const unsigned int dst_oip = offset_in_page(dst_offset); + const unsigned int limit = PAGE_SIZE; + + /* Further limit len to not cross a page boundary. */ + len = min3(len, limit - src_oip, limit - dst_oip); + + /* Compute the source and destination pages. */ + src_page += src_offset / PAGE_SIZE; + dst_page += dst_offset / PAGE_SIZE; + + if (src_page != dst_page) { + /* Copy between different pages. */ + memcpy_page(dst_page, dst_oip, + src_page, src_oip, len); + flush_dcache_page(dst_page); + } else if (src_oip != dst_oip) { + /* Copy between different parts of same page. */ + dst_virt = kmap_local_page(dst_page); + memcpy(dst_virt + dst_oip, dst_virt + src_oip, + len); + kunmap_local(dst_virt); + flush_dcache_page(dst_page); + } /* Else, it's the same memory. No action needed. */ + } else { + /* + * !HIGHMEM: no mapping needed. Just work in the linear + * buffer of each sg entry. Note that we can cross page + * boundaries, as they are not significant in this case. + */ + src_virt = page_address(src_page) + src_offset; + dst_virt = page_address(dst_page) + dst_offset; + if (src_virt != dst_virt) { + memcpy(dst_virt, src_virt, len); + if (ARCH_IMPLEMENTS_FLUSH_DCACHE_PAGE) + __scatterwalk_flush_dcache_pages( + dst_page, dst_offset, len); + } /* Else, it's the same memory. No action needed. */ + } + nbytes -= len; + if (nbytes == 0) /* No more to copy? */ + break; - skcipher_walk_first(&walk, true); - do { - if (walk.src.virt.addr != walk.dst.virt.addr) - memcpy(walk.dst.virt.addr, walk.src.virt.addr, - walk.nbytes); - skcipher_walk_done(&walk, 0); - } while (walk.nbytes); + /* + * There's more to copy. Advance the offsets by the length + * copied this step, and advance the sg entries as needed. + */ + src_offset += len; + if (src_offset >= src->offset + src->length) { + src = sg_next(src); + src_offset = src->offset; + } + dst_offset += len; + if (dst_offset >= dst->offset + dst->length) { + dst = sg_next(dst); + dst_offset = dst->offset; + } + } } EXPORT_SYMBOL_GPL(memcpy_sglist); diff --git a/include/crypto/scatterwalk.h b/include/crypto/scatterwalk.h index 83d14376ff2bc8..f485454e3955c3 100644 --- a/include/crypto/scatterwalk.h +++ b/include/crypto/scatterwalk.h @@ -227,6 +227,34 @@ static inline void scatterwalk_done_src(struct scatter_walk *walk, scatterwalk_advance(walk, nbytes); } +/* + * Flush the dcache of any pages that overlap the region + * [offset, offset + nbytes) relative to base_page. + * + * This should be called only when ARCH_IMPLEMENTS_FLUSH_DCACHE_PAGE, to ensure + * that all relevant code (including the call to sg_page() in the caller, if + * applicable) gets fully optimized out when !ARCH_IMPLEMENTS_FLUSH_DCACHE_PAGE. + */ +static inline void __scatterwalk_flush_dcache_pages(struct page *base_page, + unsigned int offset, + unsigned int nbytes) +{ + unsigned int num_pages; + + base_page += offset / PAGE_SIZE; + offset %= PAGE_SIZE; + + /* + * This is an overflow-safe version of + * num_pages = DIV_ROUND_UP(offset + nbytes, PAGE_SIZE). + */ + num_pages = nbytes / PAGE_SIZE; + num_pages += DIV_ROUND_UP(offset + (nbytes % PAGE_SIZE), PAGE_SIZE); + + for (unsigned int i = 0; i < num_pages; i++) + flush_dcache_page(base_page + i); +} + /** * scatterwalk_done_dst() - Finish one step of a walk of destination scatterlist * @walk: the scatter_walk @@ -240,27 +268,9 @@ static inline void scatterwalk_done_dst(struct scatter_walk *walk, unsigned int nbytes) { scatterwalk_unmap(walk); - /* - * Explicitly check ARCH_IMPLEMENTS_FLUSH_DCACHE_PAGE instead of just - * relying on flush_dcache_page() being a no-op when not implemented, - * since otherwise the BUG_ON in sg_page() does not get optimized out. - * This also avoids having to consider whether the loop would get - * reliably optimized out or not. - */ - if (ARCH_IMPLEMENTS_FLUSH_DCACHE_PAGE) { - struct page *base_page; - unsigned int offset; - int start, end, i; - - base_page = sg_page(walk->sg); - offset = walk->offset; - start = offset >> PAGE_SHIFT; - end = start + (nbytes >> PAGE_SHIFT); - end += (offset_in_page(offset) + offset_in_page(nbytes) + - PAGE_SIZE - 1) >> PAGE_SHIFT; - for (i = start; i < end; i++) - flush_dcache_page(base_page + i); - } + if (ARCH_IMPLEMENTS_FLUSH_DCACHE_PAGE) + __scatterwalk_flush_dcache_pages(sg_page(walk->sg), + walk->offset, nbytes); scatterwalk_advance(walk, nbytes); } From 419b1ea754c9d09eec3ee06bbedc1b749f06b732 Mon Sep 17 00:00:00 2001 From: Zheng Yejian Date: Fri, 11 Oct 2024 22:38:53 +0800 Subject: [PATCH 1558/3902] kallsyms: Fix wrong "big" kernel symbol type read from procfs commit f3f9f42232dee596d15491ca3f611d02174db49c upstream. Currently when the length of a symbol is longer than 0x7f characters, its type shown in /proc/kallsyms can be incorrect. I found this issue when reading the code, but it can be reproduced by following steps: 1. Define a function which symbol length is 130 characters: #define X13(x) x##x##x##x##x##x##x##x##x##x##x##x##x static noinline void X13(x123456789)(void) { printk("hello world\n"); } 2. The type in vmlinux is 't': $ nm vmlinux | grep x123456 ffffffff816290f0 t x123456789x123456789x123456789x12[...] 3. Then boot the kernel, the type shown in /proc/kallsyms becomes 'g' instead of the expected 't': # cat /proc/kallsyms | grep x123456 ffffffff816290f0 g x123456789x123456789x123456789x12[...] The root cause is that, after commit 73bbb94466fd ("kallsyms: support "big" kernel symbols"), ULEB128 was used to encode symbol name length. That is, for "big" kernel symbols of which name length is longer than 0x7f characters, the length info is encoded into 2 bytes. kallsyms_get_symbol_type() expects to read the first char of the symbol name which indicates the symbol type. However, due to the "big" symbol case not being handled, the symbol type read from /proc/kallsyms may be wrong, so handle it properly. Cc: stable@vger.kernel.org Fixes: 73bbb94466fd ("kallsyms: support "big" kernel symbols") Signed-off-by: Zheng Yejian Acked-by: Gary Guo Link: https://patch.msgid.link/20241011143853.3022643-1-zhengyejian@huaweicloud.com Signed-off-by: Miguel Ojeda Signed-off-by: Greg Kroah-Hartman --- kernel/kallsyms.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/kernel/kallsyms.c b/kernel/kallsyms.c index 1e76358641247f..049e296f586ccd 100644 --- a/kernel/kallsyms.c +++ b/kernel/kallsyms.c @@ -103,8 +103,11 @@ static char kallsyms_get_symbol_type(unsigned int off) { /* * Get just the first code, look it up in the token table, - * and return the first char from this token. + * and return the first char from this token. If MSB of length + * is 1, it is a "big" symbol, so needs an additional byte. */ + if (kallsyms_names[off] & 0x80) + off++; return kallsyms_token_table[kallsyms_token_index[kallsyms_names[off + 1]]]; } From b3b036bac7c91629a25250e9f40ab32f868bd4ba Mon Sep 17 00:00:00 2001 From: Konstantin Komarov Date: Thu, 18 Sep 2025 13:35:24 +0300 Subject: [PATCH 1559/3902] fs/ntfs3: fix mount failure for sparse runs in run_unpack() commit 801f614ba263cb37624982b27b4c82f3c3c597a9 upstream. Some NTFS volumes failed to mount because sparse data runs were not handled correctly during runlist unpacking. The code performed arithmetic on the special SPARSE_LCN64 marker, leading to invalid LCN values and mount errors. Add an explicit check for the case described above, marking the run as sparse without applying arithmetic. Fixes: 736fc7bf5f68 ("fs: ntfs3: Fix integer overflow in run_unpack()") Cc: stable@vger.kernel.org Signed-off-by: Konstantin Komarov Signed-off-by: Greg Kroah-Hartman --- fs/ntfs3/run.c | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/fs/ntfs3/run.c b/fs/ntfs3/run.c index 88550085f74575..5df55e4adbb114 100644 --- a/fs/ntfs3/run.c +++ b/fs/ntfs3/run.c @@ -984,8 +984,12 @@ int run_unpack(struct runs_tree *run, struct ntfs_sb_info *sbi, CLST ino, if (!dlcn) return -EINVAL; - if (check_add_overflow(prev_lcn, dlcn, &lcn)) + /* Check special combination: 0 + SPARSE_LCN64. */ + if (!prev_lcn && dlcn == SPARSE_LCN64) { + lcn = SPARSE_LCN64; + } else if (check_add_overflow(prev_lcn, dlcn, &lcn)) { return -EINVAL; + } prev_lcn = lcn; } else { /* The size of 'dlcn' can't be > 8. */ From 2b78da4cc75d26069017e949af0d7f44447a4161 Mon Sep 17 00:00:00 2001 From: Steven Rostedt Date: Wed, 3 Dec 2025 18:09:24 -0500 Subject: [PATCH 1560/3902] ktest.pl: Fix uninitialized var in config-bisect.pl commit d3042cbe84a060b4df764eb6c5300bbe20d125ca upstream. The error path of copying the old config used the wrong variable in the error message: $ mkdir /tmp/build $ ./tools/testing/ktest/config-bisect.pl -b /tmp/build config-good /tmp/config-bad $ chmod 0 /tmp/build $ ./tools/testing/ktest/config-bisect.pl -b /tmp/build config-good /tmp/config-bad good cp /tmp/build//.config config-good.tmp ... [0 seconds] FAILED! Use of uninitialized value $config in concatenation (.) or string at ./tools/testing/ktest/config-bisect.pl line 744. failed to copy to config-good.tmp When it should have shown: failed to copy /tmp/build//.config to config-good.tmp Cc: stable@vger.kernel.org Cc: John 'Warthog9' Hawley Fixes: 0f0db065999cf ("ktest: Add standalone config-bisect.pl program") Link: https://patch.msgid.link/20251203180924.6862bd26@gandalf.local.home Reported-by: "John W. Krahn" Signed-off-by: Steven Rostedt Signed-off-by: Greg Kroah-Hartman --- tools/testing/ktest/config-bisect.pl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tools/testing/ktest/config-bisect.pl b/tools/testing/ktest/config-bisect.pl index 6fd86493531959..bee7cb34a289c0 100755 --- a/tools/testing/ktest/config-bisect.pl +++ b/tools/testing/ktest/config-bisect.pl @@ -741,9 +741,9 @@ sub config_bisect { die "Can not find file $bad\n"; } if ($val eq "good") { - run_command "cp $output_config $good" or die "failed to copy $config to $good\n"; + run_command "cp $output_config $good" or die "failed to copy $output_config to $good\n"; } elsif ($val eq "bad") { - run_command "cp $output_config $bad" or die "failed to copy $config to $bad\n"; + run_command "cp $output_config $bad" or die "failed to copy $output_config to $bad\n"; } } From 858344bc9210bea9ab2bdc7e9e331ba84c164e50 Mon Sep 17 00:00:00 2001 From: Jarkko Sakkinen Date: Tue, 30 Sep 2025 15:58:02 +0300 Subject: [PATCH 1561/3902] tpm: Cap the number of PCR banks commit faf07e611dfa464b201223a7253e9dc5ee0f3c9e upstream. tpm2_get_pcr_allocation() does not cap any upper limit for the number of banks. Cap the limit to eight banks so that out of bounds values coming from external I/O cause on only limited harm. Cc: stable@vger.kernel.org # v5.10+ Fixes: bcfff8384f6c ("tpm: dynamically allocate the allocated_banks array") Tested-by: Lai Yi Reviewed-by: Jonathan McDowell Reviewed-by: Roberto Sassu Signed-off-by: Jarkko Sakkinen Signed-off-by: Greg Kroah-Hartman --- drivers/char/tpm/tpm-chip.c | 1 - drivers/char/tpm/tpm1-cmd.c | 5 ----- drivers/char/tpm/tpm2-cmd.c | 8 +++----- include/linux/tpm.h | 8 +++++--- 4 files changed, 8 insertions(+), 14 deletions(-) diff --git a/drivers/char/tpm/tpm-chip.c b/drivers/char/tpm/tpm-chip.c index e25daf2396d37b..dfeb28866a3273 100644 --- a/drivers/char/tpm/tpm-chip.c +++ b/drivers/char/tpm/tpm-chip.c @@ -282,7 +282,6 @@ static void tpm_dev_release(struct device *dev) kfree(chip->work_space.context_buf); kfree(chip->work_space.session_buf); - kfree(chip->allocated_banks); #ifdef CONFIG_TCG_TPM2_HMAC kfree(chip->auth); #endif diff --git a/drivers/char/tpm/tpm1-cmd.c b/drivers/char/tpm/tpm1-cmd.c index cf64c738510529..b49a790f1bd53f 100644 --- a/drivers/char/tpm/tpm1-cmd.c +++ b/drivers/char/tpm/tpm1-cmd.c @@ -799,11 +799,6 @@ int tpm1_pm_suspend(struct tpm_chip *chip, u32 tpm_suspend_pcr) */ int tpm1_get_pcr_allocation(struct tpm_chip *chip) { - chip->allocated_banks = kcalloc(1, sizeof(*chip->allocated_banks), - GFP_KERNEL); - if (!chip->allocated_banks) - return -ENOMEM; - chip->allocated_banks[0].alg_id = TPM_ALG_SHA1; chip->allocated_banks[0].digest_size = hash_digest_size[HASH_ALGO_SHA1]; chip->allocated_banks[0].crypto_id = HASH_ALGO_SHA1; diff --git a/drivers/char/tpm/tpm2-cmd.c b/drivers/char/tpm/tpm2-cmd.c index 7d77f6fbc1520c..5b6ccf901623c2 100644 --- a/drivers/char/tpm/tpm2-cmd.c +++ b/drivers/char/tpm/tpm2-cmd.c @@ -538,11 +538,9 @@ ssize_t tpm2_get_pcr_allocation(struct tpm_chip *chip) nr_possible_banks = be32_to_cpup( (__be32 *)&buf.data[TPM_HEADER_SIZE + 5]); - - chip->allocated_banks = kcalloc(nr_possible_banks, - sizeof(*chip->allocated_banks), - GFP_KERNEL); - if (!chip->allocated_banks) { + if (nr_possible_banks > TPM2_MAX_PCR_BANKS) { + pr_err("tpm: out of bank capacity: %u > %u\n", + nr_possible_banks, TPM2_MAX_PCR_BANKS); rc = -ENOMEM; goto out; } diff --git a/include/linux/tpm.h b/include/linux/tpm.h index dc0338a783f370..eb0ff071bcaed1 100644 --- a/include/linux/tpm.h +++ b/include/linux/tpm.h @@ -26,7 +26,9 @@ #include #define TPM_DIGEST_SIZE 20 /* Max TPM v1.2 PCR size */ -#define TPM_MAX_DIGEST_SIZE SHA512_DIGEST_SIZE + +#define TPM2_MAX_DIGEST_SIZE SHA512_DIGEST_SIZE +#define TPM2_MAX_PCR_BANKS 8 struct tpm_chip; struct trusted_key_payload; @@ -68,7 +70,7 @@ enum tpm2_curves { struct tpm_digest { u16 alg_id; - u8 digest[TPM_MAX_DIGEST_SIZE]; + u8 digest[TPM2_MAX_DIGEST_SIZE]; } __packed; struct tpm_bank_info { @@ -189,7 +191,7 @@ struct tpm_chip { unsigned int groups_cnt; u32 nr_allocated_banks; - struct tpm_bank_info *allocated_banks; + struct tpm_bank_info allocated_banks[TPM2_MAX_PCR_BANKS]; #ifdef CONFIG_ACPI acpi_handle acpi_dev_handle; char ppi_version[TPM_PPI_VERSION_LEN + 1]; From b107196729ff6b9d6cde0a71f49c1243def43328 Mon Sep 17 00:00:00 2001 From: "Rafael J. Wysocki" Date: Tue, 2 Dec 2025 19:27:29 +0100 Subject: [PATCH 1562/3902] fs: PM: Fix reverse check in filesystems_freeze_callback() commit 222047f68e8565c558728f792f6fef152a1d4d51 upstream. The freeze_all_ptr check in filesystems_freeze_callback() introduced by commit a3f8f8662771 ("power: always freeze efivarfs") is reverse which quite confusingly causes all file systems to be frozen when filesystem_freeze_enabled is false. On my systems it causes the WARN_ON_ONCE() in __set_task_frozen() to trigger, most likely due to an attempt to freeze a file system that is not ready for that. Add a logical negation to the check in question to reverse it as appropriate. Fixes: a3f8f8662771 ("power: always freeze efivarfs") Cc: 6.18+ # 6.18+ Signed-off-by: Rafael J. Wysocki Link: https://patch.msgid.link/12788397.O9o76ZdvQC@rafael.j.wysocki Signed-off-by: Christian Brauner Signed-off-by: Greg Kroah-Hartman --- fs/super.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fs/super.c b/fs/super.c index 277b84e5c27913..4c79f170ac0d27 100644 --- a/fs/super.c +++ b/fs/super.c @@ -1188,7 +1188,7 @@ static void filesystems_freeze_callback(struct super_block *sb, void *freeze_all if (!sb->s_op->freeze_fs && !sb->s_op->freeze_super) return; - if (freeze_all_ptr && !(sb->s_type->fs_flags & FS_POWER_FREEZE)) + if (!freeze_all_ptr && !(sb->s_type->fs_flags & FS_POWER_FREEZE)) return; if (!get_active_super(sb)) From be8ba672cc5ae52e8a10ac8eddb89d88f66b3cab Mon Sep 17 00:00:00 2001 From: John Ogness Date: Thu, 13 Nov 2025 17:09:47 +0106 Subject: [PATCH 1563/3902] printk: Allow printk_trigger_flush() to flush all types commit d01ff281bd9b1bfeac9ab98ec8a9ee41da900d5e upstream. Currently printk_trigger_flush() only triggers legacy offloaded flushing, even if that may not be the appropriate method to flush for currently registered consoles. (The function predates the NBCON consoles.) Since commit 6690d6b52726 ("printk: Add helper for flush type logic") there is printk_get_console_flush_type(), which also considers NBCON consoles and reports all the methods of flushing appropriate based on the system state and consoles available. Update printk_trigger_flush() to use printk_get_console_flush_type() to appropriately flush registered consoles. Suggested-by: Petr Mladek Signed-off-by: John Ogness Reviewed-by: Petr Mladek Link: https://lore.kernel.org/stable/20251113160351.113031-2-john.ogness%40linutronix.de Tested-by: Sherry Sun Link: https://patch.msgid.link/20251113160351.113031-2-john.ogness@linutronix.de Signed-off-by: Petr Mladek Signed-off-by: Greg Kroah-Hartman --- kernel/printk/nbcon.c | 2 +- kernel/printk/printk.c | 23 ++++++++++++++++++++++- 2 files changed, 23 insertions(+), 2 deletions(-) diff --git a/kernel/printk/nbcon.c b/kernel/printk/nbcon.c index da07c59f7aa2eb..730d14f6cbc582 100644 --- a/kernel/printk/nbcon.c +++ b/kernel/printk/nbcon.c @@ -1856,7 +1856,7 @@ void nbcon_device_release(struct console *con) if (console_trylock()) console_unlock(); } else if (ft.legacy_offload) { - printk_trigger_flush(); + defer_console_output(); } } console_srcu_read_unlock(cookie); diff --git a/kernel/printk/printk.c b/kernel/printk/printk.c index e850c237b4ba99..b1c0d35cf3caa3 100644 --- a/kernel/printk/printk.c +++ b/kernel/printk/printk.c @@ -4595,9 +4595,30 @@ void defer_console_output(void) __wake_up_klogd(PRINTK_PENDING_WAKEUP | PRINTK_PENDING_OUTPUT); } +/** + * printk_trigger_flush - Attempt to flush printk buffer to consoles. + * + * If possible, flush the printk buffer to all consoles in the caller's + * context. If offloading is available, trigger deferred printing. + * + * This is best effort. Depending on the system state, console states, + * and caller context, no actual flushing may result from this call. + */ void printk_trigger_flush(void) { - defer_console_output(); + struct console_flush_type ft; + + printk_get_console_flush_type(&ft); + if (ft.nbcon_atomic) + nbcon_atomic_flush_pending(); + if (ft.nbcon_offload) + nbcon_kthreads_wake(); + if (ft.legacy_direct) { + if (console_trylock()) + console_unlock(); + } + if (ft.legacy_offload) + defer_console_output(); } int vprintk_deferred(const char *fmt, va_list args) From 8626a78ce7ddaa807324a8e0056817ac8d1b0267 Mon Sep 17 00:00:00 2001 From: John Ogness Date: Fri, 21 Nov 2025 11:26:00 +0106 Subject: [PATCH 1564/3902] printk: Avoid irq_work for printk_deferred() on suspend commit 66e7c1e0ee08cfb6db64f8f3f6e5a3cc930145c8 upstream. With commit ("printk: Avoid scheduling irq_work on suspend") the implementation of printk_get_console_flush_type() was modified to avoid offloading when irq_work should be blocked during suspend. Since printk uses the returned flush type to determine what flushing methods are used, this was thought to be sufficient for avoiding irq_work usage during the suspend phase. However, vprintk_emit() implements a hack to support printk_deferred(). In this hack, the returned flush type is adjusted to make sure no legacy direct printing occurs when printk_deferred() was used. Because of this hack, the legacy offloading flushing method can still be used, causing irq_work to be queued when it should not be. Adjust the vprintk_emit() hack to also consider @console_irqwork_blocked so that legacy offloading will not be chosen when irq_work should be blocked. Link: https://lore.kernel.org/lkml/87fra90xv4.fsf@jogness.linutronix.de Signed-off-by: John Ogness Fixes: 26873e3e7f0c ("printk: Avoid scheduling irq_work on suspend") Reviewed-by: Petr Mladek Signed-off-by: Petr Mladek Signed-off-by: Greg Kroah-Hartman --- kernel/printk/printk.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/kernel/printk/printk.c b/kernel/printk/printk.c index b1c0d35cf3caa3..c27fc7fc64eb51 100644 --- a/kernel/printk/printk.c +++ b/kernel/printk/printk.c @@ -2393,7 +2393,7 @@ asmlinkage int vprintk_emit(int facility, int level, /* If called from the scheduler, we can not call up(). */ if (level == LOGLEVEL_SCHED) { level = LOGLEVEL_DEFAULT; - ft.legacy_offload |= ft.legacy_direct; + ft.legacy_offload |= ft.legacy_direct && !console_irqwork_blocked; ft.legacy_direct = false; } From 5bbacbbf1ca4419861dca3c6b82707c10e9c021c Mon Sep 17 00:00:00 2001 From: Fedor Pchelkin Date: Sat, 1 Nov 2025 19:04:28 +0300 Subject: [PATCH 1565/3902] ext4: fix string copying in parse_apply_sb_mount_options() commit ee5a977b4e771cc181f39d504426dbd31ed701cc upstream. strscpy_pad() can't be used to copy a non-NUL-term string into a NUL-term string of possibly bigger size. Commit 0efc5990bca5 ("string.h: Introduce memtostr() and memtostr_pad()") provides additional information in that regard. So if this happens, the following warning is observed: strnlen: detected buffer overflow: 65 byte read of buffer size 64 WARNING: CPU: 0 PID: 28655 at lib/string_helpers.c:1032 __fortify_report+0x96/0xc0 lib/string_helpers.c:1032 Modules linked in: CPU: 0 UID: 0 PID: 28655 Comm: syz-executor.3 Not tainted 6.12.54-syzkaller-00144-g5f0270f1ba00 #0 Hardware name: QEMU Standard PC (i440FX + PIIX, 1996), BIOS 1.16.3-debian-1.16.3-2 04/01/2014 RIP: 0010:__fortify_report+0x96/0xc0 lib/string_helpers.c:1032 Call Trace: __fortify_panic+0x1f/0x30 lib/string_helpers.c:1039 strnlen include/linux/fortify-string.h:235 [inline] sized_strscpy include/linux/fortify-string.h:309 [inline] parse_apply_sb_mount_options fs/ext4/super.c:2504 [inline] __ext4_fill_super fs/ext4/super.c:5261 [inline] ext4_fill_super+0x3c35/0xad00 fs/ext4/super.c:5706 get_tree_bdev_flags+0x387/0x620 fs/super.c:1636 vfs_get_tree+0x93/0x380 fs/super.c:1814 do_new_mount fs/namespace.c:3553 [inline] path_mount+0x6ae/0x1f70 fs/namespace.c:3880 do_mount fs/namespace.c:3893 [inline] __do_sys_mount fs/namespace.c:4103 [inline] __se_sys_mount fs/namespace.c:4080 [inline] __x64_sys_mount+0x280/0x300 fs/namespace.c:4080 do_syscall_x64 arch/x86/entry/common.c:52 [inline] do_syscall_64+0x64/0x140 arch/x86/entry/common.c:83 entry_SYSCALL_64_after_hwframe+0x76/0x7e Since userspace is expected to provide s_mount_opts field to be at most 63 characters long with the ending byte being NUL-term, use a 64-byte buffer which matches the size of s_mount_opts, so that strscpy_pad() does its job properly. Return with error if the user still managed to provide a non-NUL-term string here. Found by Linux Verification Center (linuxtesting.org) with Syzkaller. Fixes: 8ecb790ea8c3 ("ext4: avoid potential buffer over-read in parse_apply_sb_mount_options()") Cc: stable@vger.kernel.org Signed-off-by: Fedor Pchelkin Reviewed-by: Baokun Li Reviewed-by: Jan Kara Message-ID: <20251101160430.222297-1-pchelkin@ispras.ru> Signed-off-by: Theodore Ts'o Signed-off-by: Greg Kroah-Hartman --- fs/ext4/super.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/fs/ext4/super.c b/fs/ext4/super.c index 33e7c08c9529c3..15bef41f08bd1a 100644 --- a/fs/ext4/super.c +++ b/fs/ext4/super.c @@ -2475,7 +2475,7 @@ static int parse_apply_sb_mount_options(struct super_block *sb, struct ext4_fs_context *m_ctx) { struct ext4_sb_info *sbi = EXT4_SB(sb); - char s_mount_opts[65]; + char s_mount_opts[64]; struct ext4_fs_context *s_ctx = NULL; struct fs_context *fc = NULL; int ret = -ENOMEM; @@ -2483,7 +2483,8 @@ static int parse_apply_sb_mount_options(struct super_block *sb, if (!sbi->s_es->s_mount_opts[0]) return 0; - strscpy_pad(s_mount_opts, sbi->s_es->s_mount_opts); + if (strscpy_pad(s_mount_opts, sbi->s_es->s_mount_opts) < 0) + return -E2BIG; fc = kzalloc(sizeof(struct fs_context), GFP_KERNEL); if (!fc) From ecb7e5603b57e4e0182cd648937b7dbd89122285 Mon Sep 17 00:00:00 2001 From: Fedor Pchelkin Date: Sat, 1 Nov 2025 19:04:29 +0300 Subject: [PATCH 1566/3902] ext4: check if mount_opts is NUL-terminated in ext4_ioctl_set_tune_sb() commit 3db63d2c2d1d1e78615dd742568c5a2d55291ad1 upstream. params.mount_opts may come as potentially non-NUL-term string. Userspace is expected to pass a NUL-term string. Add an extra check to ensure this holds true. Note that further code utilizes strscpy_pad() so this is just for proper informing the user of incorrect data being provided. Found by Linux Verification Center (linuxtesting.org). Signed-off-by: Fedor Pchelkin Reviewed-by: Baokun Li Reviewed-by: Jan Kara Message-ID: <20251101160430.222297-2-pchelkin@ispras.ru> Signed-off-by: Theodore Ts'o Cc: stable@kernel.org Signed-off-by: Greg Kroah-Hartman --- fs/ext4/ioctl.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/fs/ext4/ioctl.c b/fs/ext4/ioctl.c index a93a7baae990cc..3dec26c939fde7 100644 --- a/fs/ext4/ioctl.c +++ b/fs/ext4/ioctl.c @@ -1394,6 +1394,10 @@ static int ext4_ioctl_set_tune_sb(struct file *filp, if (copy_from_user(¶ms, in, sizeof(params))) return -EFAULT; + if (strnlen(params.mount_opts, sizeof(params.mount_opts)) == + sizeof(params.mount_opts)) + return -E2BIG; + if ((params.set_flags & ~TUNE_OPS_SUPPORTED) != 0) return -EOPNOTSUPP; From ce5f54c065a4a7cbb92787f4f140917112350142 Mon Sep 17 00:00:00 2001 From: Karina Yankevich Date: Wed, 22 Oct 2025 12:32:53 +0300 Subject: [PATCH 1567/3902] ext4: xattr: fix null pointer deref in ext4_raw_inode() commit b97cb7d6a051aa6ebd57906df0e26e9e36c26d14 upstream. If ext4_get_inode_loc() fails (e.g. if it returns -EFSCORRUPTED), iloc.bh will remain set to NULL. Since ext4_xattr_inode_dec_ref_all() lacks error checking, this will lead to a null pointer dereference in ext4_raw_inode(), called right after ext4_get_inode_loc(). Found by Linux Verification Center (linuxtesting.org) with SVACE. Fixes: c8e008b60492 ("ext4: ignore xattrs past end") Cc: stable@kernel.org Signed-off-by: Karina Yankevich Reviewed-by: Sergey Shtylyov Reviewed-by: Baokun Li Message-ID: <20251022093253.3546296-1-k.yankevich@omp.ru> Signed-off-by: Theodore Ts'o Signed-off-by: Greg Kroah-Hartman --- fs/ext4/xattr.c | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/fs/ext4/xattr.c b/fs/ext4/xattr.c index ce7253b3f54996..2e02efbddaacca 100644 --- a/fs/ext4/xattr.c +++ b/fs/ext4/xattr.c @@ -1174,7 +1174,11 @@ ext4_xattr_inode_dec_ref_all(handle_t *handle, struct inode *parent, if (block_csum) end = (void *)bh->b_data + bh->b_size; else { - ext4_get_inode_loc(parent, &iloc); + err = ext4_get_inode_loc(parent, &iloc); + if (err) { + EXT4_ERROR_INODE(parent, "parent inode loc (error %d)", err); + return; + } end = (void *)ext4_raw_inode(&iloc) + EXT4_SB(parent->i_sb)->s_inode_size; } From 8cde2072825bd0e47478e8dbf0353f6a0b7107d5 Mon Sep 17 00:00:00 2001 From: Haibo Chen Date: Tue, 4 Nov 2025 16:12:24 +0800 Subject: [PATCH 1568/3902] ext4: clear i_state_flags when alloc inode commit 4091c8206cfd2e3bb529ef260887296b90d9b6a2 upstream. i_state_flags used on 32-bit archs, need to clear this flag when alloc inode. Find this issue when umount ext4, sometimes track the inode as orphan accidently, cause ext4 mesg dump. Fixes: acf943e9768e ("ext4: fix checks for orphan inodes") Signed-off-by: Haibo Chen Reviewed-by: Baokun Li Reviewed-by: Zhang Yi Reviewed-by: Jan Kara Message-ID: <20251104-ext4-v1-1-73691a0800f9@nxp.com> Signed-off-by: Theodore Ts'o Cc: stable@kernel.org Signed-off-by: Greg Kroah-Hartman --- fs/ext4/ialloc.c | 1 - fs/ext4/inode.c | 1 - fs/ext4/super.c | 1 + 3 files changed, 1 insertion(+), 2 deletions(-) diff --git a/fs/ext4/ialloc.c b/fs/ext4/ialloc.c index ba4fd9aba1c14d..b20a1bf866abed 100644 --- a/fs/ext4/ialloc.c +++ b/fs/ext4/ialloc.c @@ -1293,7 +1293,6 @@ struct inode *__ext4_new_inode(struct mnt_idmap *idmap, ei->i_csum_seed = ext4_chksum(csum, (__u8 *)&gen, sizeof(gen)); } - ext4_clear_state_flags(ei); /* Only relevant on 32-bit archs */ ext4_set_inode_state(inode, EXT4_STATE_NEW); ei->i_extra_isize = sbi->s_want_extra_isize; diff --git a/fs/ext4/inode.c b/fs/ext4/inode.c index e99306a8f47ce7..5fbe1a2ab81c39 100644 --- a/fs/ext4/inode.c +++ b/fs/ext4/inode.c @@ -5288,7 +5288,6 @@ struct inode *__ext4_iget(struct super_block *sb, unsigned long ino, ei->i_projid = make_kprojid(&init_user_ns, i_projid); set_nlink(inode, le16_to_cpu(raw_inode->i_links_count)); - ext4_clear_state_flags(ei); /* Only relevant on 32-bit archs */ ei->i_inline_off = 0; ei->i_dir_start_lookup = 0; ei->i_dtime = le32_to_cpu(raw_inode->i_dtime); diff --git a/fs/ext4/super.c b/fs/ext4/super.c index 15bef41f08bd1a..4ef5590c73fdad 100644 --- a/fs/ext4/super.c +++ b/fs/ext4/super.c @@ -1396,6 +1396,7 @@ static struct inode *ext4_alloc_inode(struct super_block *sb) inode_set_iversion(&ei->vfs_inode, 1); ei->i_flags = 0; + ext4_clear_state_flags(ei); /* Only relevant on 32-bit archs */ spin_lock_init(&ei->i_raw_lock); ei->i_prealloc_node = RB_ROOT; atomic_set(&ei->i_prealloc_active, 0); From e70d561d9d8393acbf5db1fc2e0f4d2e488a12b6 Mon Sep 17 00:00:00 2001 From: Yongjian Sun Date: Thu, 6 Nov 2025 14:06:13 +0800 Subject: [PATCH 1569/3902] ext4: fix incorrect group number assertion in mb_check_buddy commit 3f7a79d05c692c7cfec70bf104b1b3c3d0ce6247 upstream. When the MB_CHECK_ASSERT macro is enabled, an assertion failure can occur in __mb_check_buddy when checking preallocated blocks (pa) in a block group: Assertion failure in mb_free_blocks() : "groupnr == e4b->bd_group" This happens when a pa at the very end of a block group (e.g., pa_pstart=32765, pa_len=3 in a group of 32768 blocks) becomes exhausted - its pa_pstart is advanced by pa_len to 32768, which lies in the next block group. If this exhausted pa (with pa_len == 0) is still in the bb_prealloc_list during the buddy check, the assertion incorrectly flags it as belonging to the wrong group. A possible sequence is as follows: ext4_mb_new_blocks ext4_mb_release_context pa->pa_pstart += EXT4_C2B(sbi, ac->ac_b_ex.fe_len) pa->pa_len -= ac->ac_b_ex.fe_len __mb_check_buddy for each pa in group ext4_get_group_no_and_offset MB_CHECK_ASSERT(groupnr == e4b->bd_group) To fix this, we modify the check to skip block group validation for exhausted preallocations (where pa_len == 0). Such entries are in a transitional state and will be removed from the list soon, so they should not trigger an assertion. This change prevents the false positive while maintaining the integrity of the checks for active allocations. Fixes: c9de560ded61f ("ext4: Add multi block allocator for ext4") Signed-off-by: Yongjian Sun Reviewed-by: Baokun Li Reviewed-by: Jan Kara Message-ID: <20251106060614.631382-2-sunyongjian@huaweicloud.com> Signed-off-by: Theodore Ts'o Cc: stable@kernel.org Signed-off-by: Greg Kroah-Hartman --- fs/ext4/mballoc.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/fs/ext4/mballoc.c b/fs/ext4/mballoc.c index c56b51e1162ebd..65335248825ce4 100644 --- a/fs/ext4/mballoc.c +++ b/fs/ext4/mballoc.c @@ -783,6 +783,8 @@ static void __mb_check_buddy(struct ext4_buddy *e4b, char *file, ext4_group_t groupnr; struct ext4_prealloc_space *pa; pa = list_entry(cur, struct ext4_prealloc_space, pa_group_list); + if (!pa->pa_len) + continue; ext4_get_group_no_and_offset(sb, pa->pa_pstart, &groupnr, &k); MB_CHECK_ASSERT(groupnr == e4b->bd_group); for (i = 0; i < pa->pa_len; i++) From b59fa60114806fa57961f23fa2c626ba3211f28b Mon Sep 17 00:00:00 2001 From: Baokun Li Date: Thu, 20 Nov 2025 21:42:33 +0800 Subject: [PATCH 1570/3902] ext4: align max orphan file size with e2fsprogs limit MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit commit 7c11c56eb32eae96893eebafdbe3decadefe88ad upstream. Kernel commit 0a6ce20c1564 ("ext4: verify orphan file size is not too big") limits the maximum supported orphan file size to 8 << 20. However, in e2fsprogs, the orphan file size is set to 32–512 filesystem blocks when creating a filesystem. With 64k block size, formatting an ext4 fs >32G gives an orphan file bigger than the kernel allows, so mount prints an error and fails: EXT4-fs (vdb): orphan file too big: 8650752 EXT4-fs (vdb): mount failed To prevent this issue and allow previously created 64KB filesystems to mount, we updates the maximum allowed orphan file size in the kernel to 512 filesystem blocks. Fixes: 0a6ce20c1564 ("ext4: verify orphan file size is not too big") Signed-off-by: Baokun Li Reviewed-by: Jan Kara Message-ID: <20251120134233.2994147-1-libaokun@huaweicloud.com> Signed-off-by: Theodore Ts'o Cc: stable@kernel.org Signed-off-by: Greg Kroah-Hartman --- fs/ext4/orphan.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/fs/ext4/orphan.c b/fs/ext4/orphan.c index 82d5e750145559..fb57bba0d19d1e 100644 --- a/fs/ext4/orphan.c +++ b/fs/ext4/orphan.c @@ -8,6 +8,8 @@ #include "ext4.h" #include "ext4_jbd2.h" +#define EXT4_MAX_ORPHAN_FILE_BLOCKS 512 + static int ext4_orphan_file_add(handle_t *handle, struct inode *inode) { int i, j, start; @@ -588,7 +590,7 @@ int ext4_init_orphan_info(struct super_block *sb) * consuming absurd amounts of memory when pinning blocks of orphan * file in memory. */ - if (inode->i_size > 8 << 20) { + if (inode->i_size > (EXT4_MAX_ORPHAN_FILE_BLOCKS << inode->i_blkbits)) { ext4_msg(sb, KERN_ERR, "orphan file too big: %llu", (unsigned long long)inode->i_size); ret = -EFSCORRUPTED; From 4a9e50d881f5aac9ee01edc41abce9588982c0fc Mon Sep 17 00:00:00 2001 From: Tetsuo Handa Date: Wed, 22 Oct 2025 20:11:37 +0900 Subject: [PATCH 1571/3902] jbd2: use a per-journal lock_class_key for jbd2_trans_commit_key commit 524c3853831cf4f7e1db579e487c757c3065165c upstream. syzbot is reporting possibility of deadlock due to sharing lock_class_key for jbd2_handle across ext4 and ocfs2. But this is a false positive, for one disk partition can't have two filesystems at the same time. Reported-by: syzbot+6e493c165d26d6fcbf72@syzkaller.appspotmail.com Closes: https://syzkaller.appspot.com/bug?extid=6e493c165d26d6fcbf72 Signed-off-by: Tetsuo Handa Tested-by: syzbot+6e493c165d26d6fcbf72@syzkaller.appspotmail.com Reviewed-by: Jan Kara Message-ID: <987110fc-5470-457a-a218-d286a09dd82f@I-love.SAKURA.ne.jp> Signed-off-by: Theodore Ts'o Cc: stable@kernel.org Signed-off-by: Greg Kroah-Hartman --- fs/jbd2/journal.c | 6 ++++-- include/linux/jbd2.h | 6 ++++++ 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/fs/jbd2/journal.c b/fs/jbd2/journal.c index bf255f8b5eebf7..d02fc04e60d08e 100644 --- a/fs/jbd2/journal.c +++ b/fs/jbd2/journal.c @@ -1521,7 +1521,6 @@ static journal_t *journal_init_common(struct block_device *bdev, struct block_device *fs_dev, unsigned long long start, int len, int blocksize) { - static struct lock_class_key jbd2_trans_commit_key; journal_t *journal; int err; int n; @@ -1530,6 +1529,7 @@ static journal_t *journal_init_common(struct block_device *bdev, if (!journal) return ERR_PTR(-ENOMEM); + lockdep_register_key(&journal->jbd2_trans_commit_key); journal->j_blocksize = blocksize; journal->j_dev = bdev; journal->j_fs_dev = fs_dev; @@ -1560,7 +1560,7 @@ static journal_t *journal_init_common(struct block_device *bdev, journal->j_max_batch_time = 15000; /* 15ms */ atomic_set(&journal->j_reserved_credits, 0); lockdep_init_map(&journal->j_trans_commit_map, "jbd2_handle", - &jbd2_trans_commit_key, 0); + &journal->jbd2_trans_commit_key, 0); /* The journal is marked for error until we succeed with recovery! */ journal->j_flags = JBD2_ABORT; @@ -1611,6 +1611,7 @@ static journal_t *journal_init_common(struct block_device *bdev, kfree(journal->j_wbuf); jbd2_journal_destroy_revoke(journal); journal_fail_superblock(journal); + lockdep_unregister_key(&journal->jbd2_trans_commit_key); kfree(journal); return ERR_PTR(err); } @@ -2187,6 +2188,7 @@ int jbd2_journal_destroy(journal_t *journal) jbd2_journal_destroy_revoke(journal); kfree(journal->j_fc_wbuf); kfree(journal->j_wbuf); + lockdep_unregister_key(&journal->jbd2_trans_commit_key); kfree(journal); return err; diff --git a/include/linux/jbd2.h b/include/linux/jbd2.h index 43b9297fe8a75e..f5eaf76198f377 100644 --- a/include/linux/jbd2.h +++ b/include/linux/jbd2.h @@ -1253,6 +1253,12 @@ struct journal_s */ struct lockdep_map j_trans_commit_map; #endif + /** + * @jbd2_trans_commit_key: + * + * "struct lock_class_key" for @j_trans_commit_map + */ + struct lock_class_key jbd2_trans_commit_key; /** * @j_fc_cleanup_callback: From f0dba761b2824a82d13bf2fc605a86b157c825f7 Mon Sep 17 00:00:00 2001 From: Byungchul Park Date: Fri, 24 Oct 2025 16:39:40 +0900 Subject: [PATCH 1572/3902] jbd2: use a weaker annotation in journal handling commit 40a71b53d5a6d4ea17e4d54b99b2ac03a7f5e783 upstream. jbd2 journal handling code doesn't want jbd2_might_wait_for_commit() to be placed between start_this_handle() and stop_this_handle(). So it marks the region with rwsem_acquire_read() and rwsem_release(). However, the annotation is too strong for that purpose. We don't have to use more than try lock annotation for that. rwsem_acquire_read() implies: 1. might be a waiter on contention of the lock. 2. enter to the critical section of the lock. All we need in here is to act 2, not 1. So trylock version of annotation is sufficient for that purpose. Now that dept partially relies on lockdep annotaions, dept interpets rwsem_acquire_read() as a potential wait and might report a deadlock by the wait. Replace it with trylock version of annotation. Signed-off-by: Byungchul Park Reviewed-by: Jan Kara Cc: stable@kernel.org Message-ID: <20251024073940.1063-1-byungchul@sk.com> Signed-off-by: Theodore Ts'o Signed-off-by: Greg Kroah-Hartman --- fs/jbd2/transaction.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fs/jbd2/transaction.c b/fs/jbd2/transaction.c index 9ce626ac947e30..653ee540c4a1b7 100644 --- a/fs/jbd2/transaction.c +++ b/fs/jbd2/transaction.c @@ -441,7 +441,7 @@ static int start_this_handle(journal_t *journal, handle_t *handle, read_unlock(&journal->j_state_lock); current->journal_info = handle; - rwsem_acquire_read(&journal->j_trans_commit_map, 0, 0, _THIS_IP_); + rwsem_acquire_read(&journal->j_trans_commit_map, 0, 1, _THIS_IP_); jbd2_journal_free_transaction(new_transaction); /* * Ensure that no allocations done while the transaction is open are From 3997b3147c7b68b0308378fa95a766015f8ceb1c Mon Sep 17 00:00:00 2001 From: Bart Van Assche Date: Fri, 14 Nov 2025 13:04:07 -0800 Subject: [PATCH 1573/3902] block: Remove queue freezing from several sysfs store callbacks commit 935a20d1bebf6236076785fac3ff81e3931834e9 upstream. Freezing the request queue from inside sysfs store callbacks may cause a deadlock in combination with the dm-multipath driver and the queue_if_no_path option. Additionally, freezing the request queue slows down system boot on systems where sysfs attributes are set synchronously. Fix this by removing the blk_mq_freeze_queue() / blk_mq_unfreeze_queue() calls from the store callbacks that do not strictly need these callbacks. Add the __data_racy annotation to request_queue.rq_timeout to suppress KCSAN data race reports about the rq_timeout reads. This patch may cause a small delay in applying the new settings. For all the attributes affected by this patch, I/O will complete correctly whether the old or the new value of the attribute is used. This patch affects the following sysfs attributes: * io_poll_delay * io_timeout * nomerges * read_ahead_kb * rq_affinity Here is an example of a deadlock triggered by running test srp/002 if this patch is not applied: task:multipathd Call Trace: __schedule+0x8c1/0x1bf0 schedule+0xdd/0x270 schedule_preempt_disabled+0x1c/0x30 __mutex_lock+0xb89/0x1650 mutex_lock_nested+0x1f/0x30 dm_table_set_restrictions+0x823/0xdf0 __bind+0x166/0x590 dm_swap_table+0x2a7/0x490 do_resume+0x1b1/0x610 dev_suspend+0x55/0x1a0 ctl_ioctl+0x3a5/0x7e0 dm_ctl_ioctl+0x12/0x20 __x64_sys_ioctl+0x127/0x1a0 x64_sys_call+0xe2b/0x17d0 do_syscall_64+0x96/0x3a0 entry_SYSCALL_64_after_hwframe+0x4b/0x53 task:(udev-worker) Call Trace: __schedule+0x8c1/0x1bf0 schedule+0xdd/0x270 blk_mq_freeze_queue_wait+0xf2/0x140 blk_mq_freeze_queue_nomemsave+0x23/0x30 queue_ra_store+0x14e/0x290 queue_attr_store+0x23e/0x2c0 sysfs_kf_write+0xde/0x140 kernfs_fop_write_iter+0x3b2/0x630 vfs_write+0x4fd/0x1390 ksys_write+0xfd/0x230 __x64_sys_write+0x76/0xc0 x64_sys_call+0x276/0x17d0 do_syscall_64+0x96/0x3a0 entry_SYSCALL_64_after_hwframe+0x4b/0x53 Cc: Christoph Hellwig Cc: Ming Lei Cc: Nilay Shroff Cc: Martin Wilck Cc: Benjamin Marzinski Cc: stable@vger.kernel.org Fixes: af2814149883 ("block: freeze the queue in queue_attr_store") Signed-off-by: Bart Van Assche Reviewed-by: Nilay Shroff Signed-off-by: Jens Axboe Signed-off-by: Greg Kroah-Hartman --- block/blk-sysfs.c | 26 ++++++++------------------ include/linux/blkdev.h | 2 +- 2 files changed, 9 insertions(+), 19 deletions(-) diff --git a/block/blk-sysfs.c b/block/blk-sysfs.c index c0e4daaf96100a..e0a70d26972b3b 100644 --- a/block/blk-sysfs.c +++ b/block/blk-sysfs.c @@ -143,21 +143,22 @@ queue_ra_store(struct gendisk *disk, const char *page, size_t count) { unsigned long ra_kb; ssize_t ret; - unsigned int memflags; struct request_queue *q = disk->queue; ret = queue_var_store(&ra_kb, page, count); if (ret < 0) return ret; /* - * ->ra_pages is protected by ->limits_lock because it is usually - * calculated from the queue limits by queue_limits_commit_update. + * The ->ra_pages change below is protected by ->limits_lock because it + * is usually calculated from the queue limits by + * queue_limits_commit_update(). + * + * bdi->ra_pages reads are not serialized against bdi->ra_pages writes. + * Use WRITE_ONCE() to write bdi->ra_pages once. */ mutex_lock(&q->limits_lock); - memflags = blk_mq_freeze_queue(q); - disk->bdi->ra_pages = ra_kb >> (PAGE_SHIFT - 10); + WRITE_ONCE(disk->bdi->ra_pages, ra_kb >> (PAGE_SHIFT - 10)); mutex_unlock(&q->limits_lock); - blk_mq_unfreeze_queue(q, memflags); return ret; } @@ -375,21 +376,18 @@ static ssize_t queue_nomerges_store(struct gendisk *disk, const char *page, size_t count) { unsigned long nm; - unsigned int memflags; struct request_queue *q = disk->queue; ssize_t ret = queue_var_store(&nm, page, count); if (ret < 0) return ret; - memflags = blk_mq_freeze_queue(q); blk_queue_flag_clear(QUEUE_FLAG_NOMERGES, q); blk_queue_flag_clear(QUEUE_FLAG_NOXMERGES, q); if (nm == 2) blk_queue_flag_set(QUEUE_FLAG_NOMERGES, q); else if (nm) blk_queue_flag_set(QUEUE_FLAG_NOXMERGES, q); - blk_mq_unfreeze_queue(q, memflags); return ret; } @@ -409,7 +407,6 @@ queue_rq_affinity_store(struct gendisk *disk, const char *page, size_t count) #ifdef CONFIG_SMP struct request_queue *q = disk->queue; unsigned long val; - unsigned int memflags; ret = queue_var_store(&val, page, count); if (ret < 0) @@ -421,7 +418,6 @@ queue_rq_affinity_store(struct gendisk *disk, const char *page, size_t count) * are accessed individually using atomic test_bit operation. So we * don't grab any lock while updating these flags. */ - memflags = blk_mq_freeze_queue(q); if (val == 2) { blk_queue_flag_set(QUEUE_FLAG_SAME_COMP, q); blk_queue_flag_set(QUEUE_FLAG_SAME_FORCE, q); @@ -432,7 +428,6 @@ queue_rq_affinity_store(struct gendisk *disk, const char *page, size_t count) blk_queue_flag_clear(QUEUE_FLAG_SAME_COMP, q); blk_queue_flag_clear(QUEUE_FLAG_SAME_FORCE, q); } - blk_mq_unfreeze_queue(q, memflags); #endif return ret; } @@ -446,11 +441,9 @@ static ssize_t queue_poll_delay_store(struct gendisk *disk, const char *page, static ssize_t queue_poll_store(struct gendisk *disk, const char *page, size_t count) { - unsigned int memflags; ssize_t ret = count; struct request_queue *q = disk->queue; - memflags = blk_mq_freeze_queue(q); if (!(q->limits.features & BLK_FEAT_POLL)) { ret = -EINVAL; goto out; @@ -459,7 +452,6 @@ static ssize_t queue_poll_store(struct gendisk *disk, const char *page, pr_info_ratelimited("writes to the poll attribute are ignored.\n"); pr_info_ratelimited("please use driver specific parameters instead.\n"); out: - blk_mq_unfreeze_queue(q, memflags); return ret; } @@ -472,7 +464,7 @@ static ssize_t queue_io_timeout_show(struct gendisk *disk, char *page) static ssize_t queue_io_timeout_store(struct gendisk *disk, const char *page, size_t count) { - unsigned int val, memflags; + unsigned int val; int err; struct request_queue *q = disk->queue; @@ -480,9 +472,7 @@ static ssize_t queue_io_timeout_store(struct gendisk *disk, const char *page, if (err || val == 0) return -EINVAL; - memflags = blk_mq_freeze_queue(q); blk_queue_rq_timeout(q, msecs_to_jiffies(val)); - blk_mq_unfreeze_queue(q, memflags); return count; } diff --git a/include/linux/blkdev.h b/include/linux/blkdev.h index 70b671a9a7f775..59e54550a053f9 100644 --- a/include/linux/blkdev.h +++ b/include/linux/blkdev.h @@ -485,7 +485,7 @@ struct request_queue { */ unsigned long queue_flags; - unsigned int rq_timeout; + unsigned int __data_racy rq_timeout; unsigned int queue_depth; From 2c636fb02af2e80f36686e297c0f295c019937be Mon Sep 17 00:00:00 2001 From: xu xin Date: Tue, 7 Oct 2025 18:28:21 +0800 Subject: [PATCH 1574/3902] mm/ksm: fix exec/fork inheritance support for prctl commit 590c03ca6a3fbb114396673314e2aa483839608b upstream. Patch series "ksm: fix exec/fork inheritance", v2. This series fixes exec/fork inheritance. See the detailed description of the issue below. This patch (of 2): Background ========== commit d7597f59d1d33 ("mm: add new api to enable ksm per process") introduced MMF_VM_MERGE_ANY for mm->flags, and allowed user to set it by prctl() so that the process's VMAs are forcibly scanned by ksmd. Subsequently, the 3c6f33b7273a ("mm/ksm: support fork/exec for prctl") supported inheriting the MMF_VM_MERGE_ANY flag when a task calls execve(). Finally, commit 3a9e567ca45fb ("mm/ksm: fix ksm exec support for prctl") fixed the issue that ksmd doesn't scan the mm_struct with MMF_VM_MERGE_ANY by adding the mm_slot to ksm_mm_head in __bprm_mm_init(). Problem ======= In some extreme scenarios, however, this inheritance of MMF_VM_MERGE_ANY during exec/fork can fail. For example, when the scanning frequency of ksmd is tuned extremely high, a process carrying MMF_VM_MERGE_ANY may still fail to pass it to the newly exec'd process. This happens because ksm_execve() is executed too early in the do_execve flow (prematurely adding the new mm_struct to the ksm_mm_slot list). As a result, before do_execve completes, ksmd may have already performed a scan and found that this new mm_struct has no VM_MERGEABLE VMAs, thus clearing its MMF_VM_MERGE_ANY flag. Consequently, when the new program executes, the flag MMF_VM_MERGE_ANY inheritance missed. Root reason =========== commit d7597f59d1d33 ("mm: add new api to enable ksm per process") clear the flag MMF_VM_MERGE_ANY when ksmd found no VM_MERGEABLE VMAs. Solution ======== Firstly, Don't clear MMF_VM_MERGE_ANY when ksmd found no VM_MERGEABLE VMAs, because perhaps their mm_struct has just been added to ksm_mm_slot list, and its process has not yet officially started running or has not yet performed mmap/brk to allocate anonymous VMAS. Secondly, recheck MMF_VM_MERGEABLE again if a process takes MMF_VM_MERGE_ANY, and create a mm_slot and join it into ksm_scan_list again. Link: https://lkml.kernel.org/r/20251007182504440BJgK8VXRHh8TD7IGSUIY4@zte.com.cn Link: https://lkml.kernel.org/r/20251007182821572h_SoFqYZXEP1mvWI4n9VL@zte.com.cn Fixes: 3c6f33b7273a ("mm/ksm: support fork/exec for prctl") Fixes: d7597f59d1d3 ("mm: add new api to enable ksm per process") Signed-off-by: xu xin Cc: Stefan Roesch Cc: David Hildenbrand Cc: Jinjiang Tu Cc: Wang Yaxin Cc: Yang Yang Cc: Signed-off-by: Andrew Morton Signed-off-by: Greg Kroah-Hartman --- include/linux/ksm.h | 4 ++-- mm/ksm.c | 20 +++++++++++++++++--- 2 files changed, 19 insertions(+), 5 deletions(-) diff --git a/include/linux/ksm.h b/include/linux/ksm.h index 067538fc4d5855..c982694c987b96 100644 --- a/include/linux/ksm.h +++ b/include/linux/ksm.h @@ -17,7 +17,7 @@ #ifdef CONFIG_KSM int ksm_madvise(struct vm_area_struct *vma, unsigned long start, unsigned long end, int advice, vm_flags_t *vm_flags); -vm_flags_t ksm_vma_flags(const struct mm_struct *mm, const struct file *file, +vm_flags_t ksm_vma_flags(struct mm_struct *mm, const struct file *file, vm_flags_t vm_flags); int ksm_enable_merge_any(struct mm_struct *mm); int ksm_disable_merge_any(struct mm_struct *mm); @@ -103,7 +103,7 @@ bool ksm_process_mergeable(struct mm_struct *mm); #else /* !CONFIG_KSM */ -static inline vm_flags_t ksm_vma_flags(const struct mm_struct *mm, +static inline vm_flags_t ksm_vma_flags(struct mm_struct *mm, const struct file *file, vm_flags_t vm_flags) { return vm_flags; diff --git a/mm/ksm.c b/mm/ksm.c index c4e73040994999..ba97828f32903a 100644 --- a/mm/ksm.c +++ b/mm/ksm.c @@ -2712,8 +2712,14 @@ static struct ksm_rmap_item *scan_get_next_rmap_item(struct page **page) spin_unlock(&ksm_mmlist_lock); mm_slot_free(mm_slot_cache, mm_slot); + /* + * Only clear MMF_VM_MERGEABLE. We must not clear + * MMF_VM_MERGE_ANY, because for those MMF_VM_MERGE_ANY process, + * perhaps their mm_struct has just been added to ksm_mm_slot + * list, and its process has not yet officially started running + * or has not yet performed mmap/brk to allocate anonymous VMAS. + */ mm_flags_clear(MMF_VM_MERGEABLE, mm); - mm_flags_clear(MMF_VM_MERGE_ANY, mm); mmap_read_unlock(mm); mmdrop(mm); } else { @@ -2831,12 +2837,20 @@ static int __ksm_del_vma(struct vm_area_struct *vma) * * Returns: @vm_flags possibly updated to mark mergeable. */ -vm_flags_t ksm_vma_flags(const struct mm_struct *mm, const struct file *file, +vm_flags_t ksm_vma_flags(struct mm_struct *mm, const struct file *file, vm_flags_t vm_flags) { if (mm_flags_test(MMF_VM_MERGE_ANY, mm) && - __ksm_should_add_vma(file, vm_flags)) + __ksm_should_add_vma(file, vm_flags)) { vm_flags |= VM_MERGEABLE; + /* + * Generally, the flags here always include MMF_VM_MERGEABLE. + * However, in rare cases, this flag may be cleared by ksmd who + * scans a cycle without finding any mergeable vma. + */ + if (unlikely(!mm_flags_test(MMF_VM_MERGEABLE, mm))) + __ksm_enter(mm); + } return vm_flags; } From d710b4a2c7130627f6ddfefab0ebd3740d96d1e4 Mon Sep 17 00:00:00 2001 From: Laurent Pinchart Date: Wed, 8 Oct 2025 12:55:18 +0300 Subject: [PATCH 1575/3902] media: v4l2-mem2mem: Fix outdated documentation commit 082b86919b7a94de01d849021b4da820a6cb89dc upstream. Commit cbd9463da1b1 ("media: v4l2-mem2mem: Avoid calling .device_run in v4l2_m2m_job_finish") deferred calls to .device_run() to a work queue to avoid recursive calls when a job is finished right away from .device_run(). It failed to update the v4l2_m2m_job_finish() documentation that still states the function must not be called from .device_run(). Fix it. Fixes: cbd9463da1b1 ("media: v4l2-mem2mem: Avoid calling .device_run in v4l2_m2m_job_finish") Cc: stable@vger.kernel.org Signed-off-by: Laurent Pinchart Signed-off-by: Hans Verkuil Signed-off-by: Greg Kroah-Hartman --- include/media/v4l2-mem2mem.h | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/include/media/v4l2-mem2mem.h b/include/media/v4l2-mem2mem.h index 09c6164577ccf4..500f81f399dfaa 100644 --- a/include/media/v4l2-mem2mem.h +++ b/include/media/v4l2-mem2mem.h @@ -192,8 +192,7 @@ void v4l2_m2m_try_schedule(struct v4l2_m2m_ctx *m2m_ctx); * other instances to take control of the device. * * This function has to be called only after &v4l2_m2m_ops->device_run - * callback has been called on the driver. To prevent recursion, it should - * not be called directly from the &v4l2_m2m_ops->device_run callback though. + * callback has been called on the driver. */ void v4l2_m2m_job_finish(struct v4l2_m2m_dev *m2m_dev, struct v4l2_m2m_ctx *m2m_ctx); From cebbf5d16bb2bd86f90f32e6a912d7730ca0699a Mon Sep 17 00:00:00 2001 From: Wei Yang Date: Wed, 8 Oct 2025 09:54:52 +0000 Subject: [PATCH 1576/3902] mm/huge_memory: add pmd folio to ds_queue in do_huge_zero_wp_pmd() commit 2a1351cd4176ee1809b0900d386919d03b7652f8 upstream. We add pmd folio into ds_queue on the first page fault in __do_huge_pmd_anonymous_page(), so that we can split it in case of memory pressure. This should be the same for a pmd folio during wp page fault. Commit 1ced09e0331f ("mm: allocate THP on hugezeropage wp-fault") miss to add it to ds_queue, which means system may not reclaim enough memory in case of memory pressure even the pmd folio is under used. Move deferred_split_folio() into map_anon_folio_pmd() to make the pmd folio installation consistent. Link: https://lkml.kernel.org/r/20251008095453.18772-2-richard.weiyang@gmail.com Fixes: 1ced09e0331f ("mm: allocate THP on hugezeropage wp-fault") Signed-off-by: Wei Yang Acked-by: David Hildenbrand Reviewed-by: Lance Yang Reviewed-by: Dev Jain Acked-by: Usama Arif Reviewed-by: Zi Yan Reviewed-by: Baolin Wang Cc: Matthew Wilcox Cc: Signed-off-by: Andrew Morton Signed-off-by: Greg Kroah-Hartman --- mm/huge_memory.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mm/huge_memory.c b/mm/huge_memory.c index 6cba1cb14b23ac..f66a4b15cb3363 100644 --- a/mm/huge_memory.c +++ b/mm/huge_memory.c @@ -1233,6 +1233,7 @@ static void map_anon_folio_pmd(struct folio *folio, pmd_t *pmd, count_vm_event(THP_FAULT_ALLOC); count_mthp_stat(HPAGE_PMD_ORDER, MTHP_STAT_ANON_FAULT_ALLOC); count_memcg_event_mm(vma->vm_mm, THP_FAULT_ALLOC); + deferred_split_folio(folio, false); } static vm_fault_t __do_huge_pmd_anonymous_page(struct vm_fault *vmf) @@ -1273,7 +1274,6 @@ static vm_fault_t __do_huge_pmd_anonymous_page(struct vm_fault *vmf) pgtable_trans_huge_deposit(vma->vm_mm, vmf->pmd, pgtable); map_anon_folio_pmd(folio, vmf->pmd, vma, haddr); mm_inc_nr_ptes(vma->vm_mm); - deferred_split_folio(folio, false); spin_unlock(vmf->ptl); } From 04a3aa6e8c5f878cc51a8a1c90b6d3c54079bc43 Mon Sep 17 00:00:00 2001 From: Jarkko Sakkinen Date: Sun, 30 Nov 2025 21:07:12 +0200 Subject: [PATCH 1577/3902] tpm2-sessions: Fix out of range indexing in name_size commit 6e9722e9a7bfe1bbad649937c811076acf86e1fd upstream. 'name_size' does not have any range checks, and it just directly indexes with TPM_ALG_ID, which could lead into memory corruption at worst. Address the issue by only processing known values and returning -EINVAL for unrecognized values. Make also 'tpm_buf_append_name' and 'tpm_buf_fill_hmac_session' fallible so that errors are detected before causing any spurious TPM traffic. End also the authorization session on failure in both of the functions, as the session state would be then by definition corrupted. Cc: stable@vger.kernel.org # v6.10+ Fixes: 1085b8276bb4 ("tpm: Add the rest of the session HMAC API") Reviewed-by: Jonathan McDowell Signed-off-by: Jarkko Sakkinen Signed-off-by: Greg Kroah-Hartman --- drivers/char/tpm/tpm2-cmd.c | 23 +++- drivers/char/tpm/tpm2-sessions.c | 132 +++++++++++++++------- include/linux/tpm.h | 13 ++- security/keys/trusted-keys/trusted_tpm2.c | 29 ++++- 4 files changed, 142 insertions(+), 55 deletions(-) diff --git a/drivers/char/tpm/tpm2-cmd.c b/drivers/char/tpm/tpm2-cmd.c index 5b6ccf901623c2..4473b81122e872 100644 --- a/drivers/char/tpm/tpm2-cmd.c +++ b/drivers/char/tpm/tpm2-cmd.c @@ -187,7 +187,11 @@ int tpm2_pcr_extend(struct tpm_chip *chip, u32 pcr_idx, } if (!disable_pcr_integrity) { - tpm_buf_append_name(chip, &buf, pcr_idx, NULL); + rc = tpm_buf_append_name(chip, &buf, pcr_idx, NULL); + if (rc) { + tpm_buf_destroy(&buf); + return rc; + } tpm_buf_append_hmac_session(chip, &buf, 0, NULL, 0); } else { tpm_buf_append_handle(chip, &buf, pcr_idx); @@ -202,8 +206,14 @@ int tpm2_pcr_extend(struct tpm_chip *chip, u32 pcr_idx, chip->allocated_banks[i].digest_size); } - if (!disable_pcr_integrity) - tpm_buf_fill_hmac_session(chip, &buf); + if (!disable_pcr_integrity) { + rc = tpm_buf_fill_hmac_session(chip, &buf); + if (rc) { + tpm_buf_destroy(&buf); + return rc; + } + } + rc = tpm_transmit_cmd(chip, &buf, 0, "attempting extend a PCR value"); if (!disable_pcr_integrity) rc = tpm_buf_check_hmac_response(chip, &buf, rc); @@ -261,7 +271,12 @@ int tpm2_get_random(struct tpm_chip *chip, u8 *dest, size_t max) | TPM2_SA_CONTINUE_SESSION, NULL, 0); tpm_buf_append_u16(&buf, num_bytes); - tpm_buf_fill_hmac_session(chip, &buf); + err = tpm_buf_fill_hmac_session(chip, &buf); + if (err) { + tpm_buf_destroy(&buf); + return err; + } + err = tpm_transmit_cmd(chip, &buf, offsetof(struct tpm2_get_random_out, buffer), diff --git a/drivers/char/tpm/tpm2-sessions.c b/drivers/char/tpm/tpm2-sessions.c index 6d03c224e6b210..385014dbca39d9 100644 --- a/drivers/char/tpm/tpm2-sessions.c +++ b/drivers/char/tpm/tpm2-sessions.c @@ -144,16 +144,23 @@ struct tpm2_auth { /* * Name Size based on TPM algorithm (assumes no hash bigger than 255) */ -static u8 name_size(const u8 *name) +static int name_size(const u8 *name) { - static u8 size_map[] = { - [TPM_ALG_SHA1] = SHA1_DIGEST_SIZE, - [TPM_ALG_SHA256] = SHA256_DIGEST_SIZE, - [TPM_ALG_SHA384] = SHA384_DIGEST_SIZE, - [TPM_ALG_SHA512] = SHA512_DIGEST_SIZE, - }; - u16 alg = get_unaligned_be16(name); - return size_map[alg] + 2; + u16 hash_alg = get_unaligned_be16(name); + + switch (hash_alg) { + case TPM_ALG_SHA1: + return SHA1_DIGEST_SIZE + 2; + case TPM_ALG_SHA256: + return SHA256_DIGEST_SIZE + 2; + case TPM_ALG_SHA384: + return SHA384_DIGEST_SIZE + 2; + case TPM_ALG_SHA512: + return SHA512_DIGEST_SIZE + 2; + default: + pr_warn("tpm: unsupported name algorithm: 0x%04x\n", hash_alg); + return -EINVAL; + } } static int tpm2_parse_read_public(char *name, struct tpm_buf *buf) @@ -161,6 +168,7 @@ static int tpm2_parse_read_public(char *name, struct tpm_buf *buf) struct tpm_header *head = (struct tpm_header *)buf->data; off_t offset = TPM_HEADER_SIZE; u32 tot_len = be32_to_cpu(head->length); + int ret; u32 val; /* we're starting after the header so adjust the length */ @@ -173,8 +181,13 @@ static int tpm2_parse_read_public(char *name, struct tpm_buf *buf) offset += val; /* name */ val = tpm_buf_read_u16(buf, &offset); - if (val != name_size(&buf->data[offset])) + ret = name_size(&buf->data[offset]); + if (ret < 0) + return ret; + + if (val != ret) return -EINVAL; + memcpy(name, &buf->data[offset], val); /* forget the rest */ return 0; @@ -221,46 +234,72 @@ static int tpm2_read_public(struct tpm_chip *chip, u32 handle, char *name) * As with most tpm_buf operations, success is assumed because failure * will be caused by an incorrect programming model and indicated by a * kernel message. + * + * Ends the authorization session on failure. */ -void tpm_buf_append_name(struct tpm_chip *chip, struct tpm_buf *buf, - u32 handle, u8 *name) +int tpm_buf_append_name(struct tpm_chip *chip, struct tpm_buf *buf, + u32 handle, u8 *name) { #ifdef CONFIG_TCG_TPM2_HMAC enum tpm2_mso_type mso = tpm2_handle_mso(handle); struct tpm2_auth *auth; int slot; + int ret; #endif if (!tpm2_chip_auth(chip)) { tpm_buf_append_handle(chip, buf, handle); - return; + return 0; } #ifdef CONFIG_TCG_TPM2_HMAC slot = (tpm_buf_length(buf) - TPM_HEADER_SIZE) / 4; if (slot >= AUTH_MAX_NAMES) { - dev_err(&chip->dev, "TPM: too many handles\n"); - return; + dev_err(&chip->dev, "too many handles\n"); + ret = -EIO; + goto err; } auth = chip->auth; - WARN(auth->session != tpm_buf_length(buf), - "name added in wrong place\n"); + if (auth->session != tpm_buf_length(buf)) { + dev_err(&chip->dev, "session state malformed"); + ret = -EIO; + goto err; + } tpm_buf_append_u32(buf, handle); auth->session += 4; if (mso == TPM2_MSO_PERSISTENT || mso == TPM2_MSO_VOLATILE || mso == TPM2_MSO_NVRAM) { - if (!name) - tpm2_read_public(chip, handle, auth->name[slot]); + if (!name) { + ret = tpm2_read_public(chip, handle, auth->name[slot]); + if (ret) + goto err; + } } else { - if (name) - dev_err(&chip->dev, "TPM: Handle does not require name but one is specified\n"); + if (name) { + dev_err(&chip->dev, "handle 0x%08x does not use a name\n", + handle); + ret = -EIO; + goto err; + } } auth->name_h[slot] = handle; - if (name) - memcpy(auth->name[slot], name, name_size(name)); + if (name) { + ret = name_size(name); + if (ret < 0) + goto err; + + memcpy(auth->name[slot], name, ret); + } +#endif + return 0; + +#ifdef CONFIG_TCG_TPM2_HMAC +err: + tpm2_end_auth_session(chip); + return tpm_ret_to_err(ret); #endif } EXPORT_SYMBOL_GPL(tpm_buf_append_name); @@ -533,11 +572,9 @@ static void tpm_buf_append_salt(struct tpm_buf *buf, struct tpm_chip *chip, * encryption key and encrypts the first parameter of the command * buffer with it. * - * As with most tpm_buf operations, success is assumed because failure - * will be caused by an incorrect programming model and indicated by a - * kernel message. + * Ends the authorization session on failure. */ -void tpm_buf_fill_hmac_session(struct tpm_chip *chip, struct tpm_buf *buf) +int tpm_buf_fill_hmac_session(struct tpm_chip *chip, struct tpm_buf *buf) { u32 cc, handles, val; struct tpm2_auth *auth = chip->auth; @@ -549,9 +586,12 @@ void tpm_buf_fill_hmac_session(struct tpm_chip *chip, struct tpm_buf *buf) u8 cphash[SHA256_DIGEST_SIZE]; struct sha256_ctx sctx; struct hmac_sha256_ctx hctx; + int ret; - if (!auth) - return; + if (!auth) { + ret = -EIO; + goto err; + } /* save the command code in BE format */ auth->ordinal = head->ordinal; @@ -560,9 +600,11 @@ void tpm_buf_fill_hmac_session(struct tpm_chip *chip, struct tpm_buf *buf) i = tpm2_find_cc(chip, cc); if (i < 0) { - dev_err(&chip->dev, "Command 0x%x not found in TPM\n", cc); - return; + dev_err(&chip->dev, "command 0x%08x not found\n", cc); + ret = -EIO; + goto err; } + attrs = chip->cc_attrs_tbl[i]; handles = (attrs >> TPM2_CC_ATTR_CHANDLES) & GENMASK(2, 0); @@ -576,9 +618,9 @@ void tpm_buf_fill_hmac_session(struct tpm_chip *chip, struct tpm_buf *buf) u32 handle = tpm_buf_read_u32(buf, &offset_s); if (auth->name_h[i] != handle) { - dev_err(&chip->dev, "TPM: handle %d wrong for name\n", - i); - return; + dev_err(&chip->dev, "invalid handle 0x%08x\n", handle); + ret = -EIO; + goto err; } } /* point offset_s to the start of the sessions */ @@ -609,12 +651,14 @@ void tpm_buf_fill_hmac_session(struct tpm_chip *chip, struct tpm_buf *buf) offset_s += len; } if (offset_s != offset_p) { - dev_err(&chip->dev, "TPM session length is incorrect\n"); - return; + dev_err(&chip->dev, "session length is incorrect\n"); + ret = -EIO; + goto err; } if (!hmac) { - dev_err(&chip->dev, "TPM could not find HMAC session\n"); - return; + dev_err(&chip->dev, "could not find HMAC session\n"); + ret = -EIO; + goto err; } /* encrypt before HMAC */ @@ -646,8 +690,11 @@ void tpm_buf_fill_hmac_session(struct tpm_chip *chip, struct tpm_buf *buf) if (mso == TPM2_MSO_PERSISTENT || mso == TPM2_MSO_VOLATILE || mso == TPM2_MSO_NVRAM) { - sha256_update(&sctx, auth->name[i], - name_size(auth->name[i])); + ret = name_size(auth->name[i]); + if (ret < 0) + goto err; + + sha256_update(&sctx, auth->name[i], ret); } else { __be32 h = cpu_to_be32(auth->name_h[i]); @@ -668,6 +715,11 @@ void tpm_buf_fill_hmac_session(struct tpm_chip *chip, struct tpm_buf *buf) hmac_sha256_update(&hctx, auth->tpm_nonce, sizeof(auth->tpm_nonce)); hmac_sha256_update(&hctx, &auth->attrs, 1); hmac_sha256_final(&hctx, hmac); + return 0; + +err: + tpm2_end_auth_session(chip); + return ret; } EXPORT_SYMBOL(tpm_buf_fill_hmac_session); diff --git a/include/linux/tpm.h b/include/linux/tpm.h index eb0ff071bcaed1..5d5c926a71119d 100644 --- a/include/linux/tpm.h +++ b/include/linux/tpm.h @@ -526,8 +526,8 @@ static inline struct tpm2_auth *tpm2_chip_auth(struct tpm_chip *chip) #endif } -void tpm_buf_append_name(struct tpm_chip *chip, struct tpm_buf *buf, - u32 handle, u8 *name); +int tpm_buf_append_name(struct tpm_chip *chip, struct tpm_buf *buf, + u32 handle, u8 *name); void tpm_buf_append_hmac_session(struct tpm_chip *chip, struct tpm_buf *buf, u8 attributes, u8 *passphrase, int passphraselen); @@ -560,7 +560,7 @@ static inline void tpm_buf_append_hmac_session_opt(struct tpm_chip *chip, #ifdef CONFIG_TCG_TPM2_HMAC int tpm2_start_auth_session(struct tpm_chip *chip); -void tpm_buf_fill_hmac_session(struct tpm_chip *chip, struct tpm_buf *buf); +int tpm_buf_fill_hmac_session(struct tpm_chip *chip, struct tpm_buf *buf); int tpm_buf_check_hmac_response(struct tpm_chip *chip, struct tpm_buf *buf, int rc); void tpm2_end_auth_session(struct tpm_chip *chip); @@ -574,10 +574,13 @@ static inline int tpm2_start_auth_session(struct tpm_chip *chip) static inline void tpm2_end_auth_session(struct tpm_chip *chip) { } -static inline void tpm_buf_fill_hmac_session(struct tpm_chip *chip, - struct tpm_buf *buf) + +static inline int tpm_buf_fill_hmac_session(struct tpm_chip *chip, + struct tpm_buf *buf) { + return 0; } + static inline int tpm_buf_check_hmac_response(struct tpm_chip *chip, struct tpm_buf *buf, int rc) diff --git a/security/keys/trusted-keys/trusted_tpm2.c b/security/keys/trusted-keys/trusted_tpm2.c index 76a86f8a8d6c84..7187768716b78e 100644 --- a/security/keys/trusted-keys/trusted_tpm2.c +++ b/security/keys/trusted-keys/trusted_tpm2.c @@ -283,7 +283,10 @@ int tpm2_seal_trusted(struct tpm_chip *chip, goto out_put; } - tpm_buf_append_name(chip, &buf, options->keyhandle, NULL); + rc = tpm_buf_append_name(chip, &buf, options->keyhandle, NULL); + if (rc) + goto out; + tpm_buf_append_hmac_session(chip, &buf, TPM2_SA_DECRYPT, options->keyauth, TPM_DIGEST_SIZE); @@ -331,7 +334,10 @@ int tpm2_seal_trusted(struct tpm_chip *chip, goto out; } - tpm_buf_fill_hmac_session(chip, &buf); + rc = tpm_buf_fill_hmac_session(chip, &buf); + if (rc) + goto out; + rc = tpm_transmit_cmd(chip, &buf, 4, "sealing data"); rc = tpm_buf_check_hmac_response(chip, &buf, rc); if (rc) @@ -448,7 +454,10 @@ static int tpm2_load_cmd(struct tpm_chip *chip, return rc; } - tpm_buf_append_name(chip, &buf, options->keyhandle, NULL); + rc = tpm_buf_append_name(chip, &buf, options->keyhandle, NULL); + if (rc) + goto out; + tpm_buf_append_hmac_session(chip, &buf, 0, options->keyauth, TPM_DIGEST_SIZE); @@ -460,7 +469,10 @@ static int tpm2_load_cmd(struct tpm_chip *chip, goto out; } - tpm_buf_fill_hmac_session(chip, &buf); + rc = tpm_buf_fill_hmac_session(chip, &buf); + if (rc) + goto out; + rc = tpm_transmit_cmd(chip, &buf, 4, "loading blob"); rc = tpm_buf_check_hmac_response(chip, &buf, rc); if (!rc) @@ -508,7 +520,9 @@ static int tpm2_unseal_cmd(struct tpm_chip *chip, return rc; } - tpm_buf_append_name(chip, &buf, blob_handle, NULL); + rc = tpm_buf_append_name(chip, &buf, options->keyhandle, NULL); + if (rc) + goto out; if (!options->policyhandle) { tpm_buf_append_hmac_session(chip, &buf, TPM2_SA_ENCRYPT, @@ -533,7 +547,10 @@ static int tpm2_unseal_cmd(struct tpm_chip *chip, NULL, 0); } - tpm_buf_fill_hmac_session(chip, &buf); + rc = tpm_buf_fill_hmac_session(chip, &buf); + if (rc) + goto out; + rc = tpm_transmit_cmd(chip, &buf, 6, "unsealing"); rc = tpm_buf_check_hmac_response(chip, &buf, rc); if (rc > 0) From 20eda7c74b69fe9e1caf9b930a5c016bf8d755fa Mon Sep 17 00:00:00 2001 From: Jarkko Sakkinen Date: Mon, 1 Dec 2025 15:38:02 +0200 Subject: [PATCH 1578/3902] tpm2-sessions: Fix tpm2_read_public range checks commit bda1cbf73c6e241267c286427f2ed52b5735d872 upstream. tpm2_read_public() has some rudimentary range checks but the function does not ensure that the response buffer has enough bytes for the full TPMT_HA payload. Re-implement the function with necessary checks and validation, and return name and name size for all handle types back to the caller. Cc: stable@vger.kernel.org # v6.10+ Fixes: d0a25bb961e6 ("tpm: Add HMAC session name/handle append") Signed-off-by: Jarkko Sakkinen Reviewed-by: Jonathan McDowell Signed-off-by: Greg Kroah-Hartman --- drivers/char/tpm/tpm2-cmd.c | 3 + drivers/char/tpm/tpm2-sessions.c | 94 +++++++++++++++++--------------- 2 files changed, 53 insertions(+), 44 deletions(-) diff --git a/drivers/char/tpm/tpm2-cmd.c b/drivers/char/tpm/tpm2-cmd.c index 4473b81122e872..58a8477cda8514 100644 --- a/drivers/char/tpm/tpm2-cmd.c +++ b/drivers/char/tpm/tpm2-cmd.c @@ -11,8 +11,11 @@ * used by the kernel internally. */ +#include "linux/dev_printk.h" +#include "linux/tpm.h" #include "tpm.h" #include +#include static bool disable_pcr_integrity; module_param(disable_pcr_integrity, bool, 0444); diff --git a/drivers/char/tpm/tpm2-sessions.c b/drivers/char/tpm/tpm2-sessions.c index 385014dbca39d9..3f389e2f6f580b 100644 --- a/drivers/char/tpm/tpm2-sessions.c +++ b/drivers/char/tpm/tpm2-sessions.c @@ -163,53 +163,61 @@ static int name_size(const u8 *name) } } -static int tpm2_parse_read_public(char *name, struct tpm_buf *buf) +static int tpm2_read_public(struct tpm_chip *chip, u32 handle, void *name) { - struct tpm_header *head = (struct tpm_header *)buf->data; + u32 mso = tpm2_handle_mso(handle); off_t offset = TPM_HEADER_SIZE; - u32 tot_len = be32_to_cpu(head->length); - int ret; - u32 val; - - /* we're starting after the header so adjust the length */ - tot_len -= TPM_HEADER_SIZE; - - /* skip public */ - val = tpm_buf_read_u16(buf, &offset); - if (val > tot_len) - return -EINVAL; - offset += val; - /* name */ - val = tpm_buf_read_u16(buf, &offset); - ret = name_size(&buf->data[offset]); - if (ret < 0) - return ret; - - if (val != ret) - return -EINVAL; - - memcpy(name, &buf->data[offset], val); - /* forget the rest */ - return 0; -} - -static int tpm2_read_public(struct tpm_chip *chip, u32 handle, char *name) -{ + int rc, name_size_alg; struct tpm_buf buf; - int rc; + + if (mso != TPM2_MSO_PERSISTENT && mso != TPM2_MSO_VOLATILE && + mso != TPM2_MSO_NVRAM) { + memcpy(name, &handle, sizeof(u32)); + return sizeof(u32); + } rc = tpm_buf_init(&buf, TPM2_ST_NO_SESSIONS, TPM2_CC_READ_PUBLIC); if (rc) return rc; tpm_buf_append_u32(&buf, handle); - rc = tpm_transmit_cmd(chip, &buf, 0, "read public"); - if (rc == TPM2_RC_SUCCESS) - rc = tpm2_parse_read_public(name, &buf); - tpm_buf_destroy(&buf); + rc = tpm_transmit_cmd(chip, &buf, 0, "TPM2_ReadPublic"); + if (rc) { + tpm_buf_destroy(&buf); + return tpm_ret_to_err(rc); + } - return rc; + /* Skip TPMT_PUBLIC: */ + offset += tpm_buf_read_u16(&buf, &offset); + + /* + * Ensure space for the length field of TPM2B_NAME and hashAlg field of + * TPMT_HA (the extra four bytes). + */ + if (offset + 4 > tpm_buf_length(&buf)) { + tpm_buf_destroy(&buf); + return -EIO; + } + + rc = tpm_buf_read_u16(&buf, &offset); + name_size_alg = name_size(&buf.data[offset]); + + if (name_size_alg < 0) + return name_size_alg; + + if (rc != name_size_alg) { + tpm_buf_destroy(&buf); + return -EIO; + } + + if (offset + rc > tpm_buf_length(&buf)) { + tpm_buf_destroy(&buf); + return -EIO; + } + + memcpy(name, &buf.data[offset], rc); + return name_size_alg; } #endif /* CONFIG_TCG_TPM2_HMAC */ @@ -243,6 +251,7 @@ int tpm_buf_append_name(struct tpm_chip *chip, struct tpm_buf *buf, #ifdef CONFIG_TCG_TPM2_HMAC enum tpm2_mso_type mso = tpm2_handle_mso(handle); struct tpm2_auth *auth; + u16 name_size_alg; int slot; int ret; #endif @@ -273,8 +282,10 @@ int tpm_buf_append_name(struct tpm_chip *chip, struct tpm_buf *buf, mso == TPM2_MSO_NVRAM) { if (!name) { ret = tpm2_read_public(chip, handle, auth->name[slot]); - if (ret) + if (ret < 0) goto err; + + name_size_alg = ret; } } else { if (name) { @@ -286,13 +297,8 @@ int tpm_buf_append_name(struct tpm_chip *chip, struct tpm_buf *buf, } auth->name_h[slot] = handle; - if (name) { - ret = name_size(name); - if (ret < 0) - goto err; - - memcpy(auth->name[slot], name, ret); - } + if (name) + memcpy(auth->name[slot], name, name_size_alg); #endif return 0; From d519d23a6b1c2306571f19f7d8e39bceee681e3c Mon Sep 17 00:00:00 2001 From: Sourabh Jain Date: Thu, 16 Oct 2025 19:58:31 +0530 Subject: [PATCH 1579/3902] crash: let architecture decide crash memory export to iomem_resource commit adc15829fb73e402903b7030729263b6ee4a7232 upstream. With the generic crashkernel reservation, the kernel emits the following warning on powerpc: WARNING: CPU: 0 PID: 1 at arch/powerpc/mm/mem.c:341 add_system_ram_resources+0xfc/0x180 Modules linked in: CPU: 0 UID: 0 PID: 1 Comm: swapper/0 Not tainted 6.17.0-auto-12607-g5472d60c129f #1 VOLUNTARY Hardware name: IBM,9080-HEX Power11 (architected) 0x820200 0xf000007 of:IBM,FW1110.01 (NH1110_069) hv:phyp pSeries NIP: c00000000201de3c LR: c00000000201de34 CTR: 0000000000000000 REGS: c000000127cef8a0 TRAP: 0700 Not tainted (6.17.0-auto-12607-g5472d60c129f) MSR: 8000000002029033 CR: 84000840 XER: 20040010 CFAR: c00000000017eed0 IRQMASK: 0 GPR00: c00000000201de34 c000000127cefb40 c0000000016a8100 0000000000000001 GPR04: c00000012005aa00 0000000020000000 c000000002b705c8 0000000000000000 GPR08: 000000007fffffff fffffffffffffff0 c000000002db8100 000000011fffffff GPR12: c00000000201dd40 c000000002ff0000 c0000000000112bc 0000000000000000 GPR16: 0000000000000000 0000000000000000 0000000000000000 0000000000000000 GPR20: 0000000000000000 0000000000000000 0000000000000000 c0000000015a3808 GPR24: c00000000200468c c000000001699888 0000000000000106 c0000000020d1950 GPR28: c0000000014683f8 0000000081000200 c0000000015c1868 c000000002b9f710 NIP [c00000000201de3c] add_system_ram_resources+0xfc/0x180 LR [c00000000201de34] add_system_ram_resources+0xf4/0x180 Call Trace: add_system_ram_resources+0xf4/0x180 (unreliable) do_one_initcall+0x60/0x36c do_initcalls+0x120/0x220 kernel_init_freeable+0x23c/0x390 kernel_init+0x34/0x26c ret_from_kernel_user_thread+0x14/0x1c This warning occurs due to a conflict between crashkernel and System RAM iomem resources. The generic crashkernel reservation adds the crashkernel memory range to /proc/iomem during early initialization. Later, all memblock ranges are added to /proc/iomem as System RAM. If the crashkernel region overlaps with any memblock range, it causes a conflict while adding those memblock regions as iomem resources, triggering the above warning. The conflicting memblock regions are then omitted from /proc/iomem. For example, if the following crashkernel region is added to /proc/iomem: 20000000-11fffffff : Crash kernel then the following memblock regions System RAM regions fail to be inserted: 00000000-7fffffff : System RAM 80000000-257fffffff : System RAM Fix this by not adding the crashkernel memory to /proc/iomem on powerpc. Introduce an architecture hook to let each architecture decide whether to export the crashkernel region to /proc/iomem. For more info checkout commit c40dd2f766440 ("powerpc: Add System RAM to /proc/iomem") and commit bce074bdbc36 ("powerpc: insert System RAM resource to prevent crashkernel conflict") Note: Before switching to the generic crashkernel reservation, powerpc never exported the crashkernel region to /proc/iomem. Link: https://lkml.kernel.org/r/20251016142831.144515-1-sourabhjain@linux.ibm.com Fixes: e3185ee438c2 ("powerpc/crash: use generic crashkernel reservation"). Signed-off-by: Sourabh Jain Reported-by: Venkat Rao Bagalkote Closes: https://lore.kernel.org/all/90937fe0-2e76-4c82-b27e-7b8a7fe3ac69@linux.ibm.com/ Tested-by: Venkat Rao Bagalkote Cc: Baoquan he Cc: Hari Bathini Cc: Madhavan Srinivasan Cc: Mahesh Salgaonkar Cc: Michael Ellerman Cc: Ritesh Harjani (IBM) Cc: Vivek Goyal Cc: Dave Young Cc: Mike Rapoport Cc: Signed-off-by: Andrew Morton Signed-off-by: Greg Kroah-Hartman --- arch/powerpc/include/asm/crash_reserve.h | 8 ++++++++ include/linux/crash_reserve.h | 6 ++++++ kernel/crash_reserve.c | 3 +++ 3 files changed, 17 insertions(+) diff --git a/arch/powerpc/include/asm/crash_reserve.h b/arch/powerpc/include/asm/crash_reserve.h index 6467ce29b1fabf..d1b570ddbf98bf 100644 --- a/arch/powerpc/include/asm/crash_reserve.h +++ b/arch/powerpc/include/asm/crash_reserve.h @@ -5,4 +5,12 @@ /* crash kernel regions are Page size agliged */ #define CRASH_ALIGN PAGE_SIZE +#ifdef CONFIG_ARCH_HAS_GENERIC_CRASHKERNEL_RESERVATION +static inline bool arch_add_crash_res_to_iomem(void) +{ + return false; +} +#define arch_add_crash_res_to_iomem arch_add_crash_res_to_iomem +#endif + #endif /* _ASM_POWERPC_CRASH_RESERVE_H */ diff --git a/include/linux/crash_reserve.h b/include/linux/crash_reserve.h index 7b44b41d0a20d4..f0dc03d94ca2cd 100644 --- a/include/linux/crash_reserve.h +++ b/include/linux/crash_reserve.h @@ -32,6 +32,12 @@ int __init parse_crashkernel(char *cmdline, unsigned long long system_ram, void __init reserve_crashkernel_cma(unsigned long long cma_size); #ifdef CONFIG_ARCH_HAS_GENERIC_CRASHKERNEL_RESERVATION +#ifndef arch_add_crash_res_to_iomem +static inline bool arch_add_crash_res_to_iomem(void) +{ + return true; +} +#endif #ifndef DEFAULT_CRASH_KERNEL_LOW_SIZE #define DEFAULT_CRASH_KERNEL_LOW_SIZE (128UL << 20) #endif diff --git a/kernel/crash_reserve.c b/kernel/crash_reserve.c index 87bf4d41eabba9..62e60e0223cff2 100644 --- a/kernel/crash_reserve.c +++ b/kernel/crash_reserve.c @@ -524,6 +524,9 @@ void __init reserve_crashkernel_cma(unsigned long long cma_size) #ifndef HAVE_ARCH_ADD_CRASH_RES_TO_IOMEM_EARLY static __init int insert_crashkernel_resources(void) { + if (!arch_add_crash_res_to_iomem()) + return 0; + if (crashk_res.start < crashk_res.end) insert_resource(&iomem_resource, &crashk_res); From 2766fae7c4c6741e2e4bb01ed485347d84df7f6d Mon Sep 17 00:00:00 2001 From: Hans de Goede Date: Sun, 7 Dec 2025 19:47:56 +0100 Subject: [PATCH 1580/3902] dma-mapping: Fix DMA_BIT_MASK() macro being broken commit 31b931bebd11a0f00967114f62c8c38952f483e5 upstream. After commit a50f7456f853 ("dma-mapping: Allow use of DMA_BIT_MASK(64) in global scope"), the DMA_BIT_MASK() macro is broken when passed non trivial statements for the value of 'n'. This is caused by the new version missing parenthesis around 'n' when evaluating 'n'. One example of this breakage is the IPU6 driver now crashing due to it getting DMA-addresses with address bit 32 set even though it has tried to set a 32 bit DMA mask. The IPU6 CSI2 engine has a DMA mask of either 31 or 32 bits depending on if it is in secure mode or not and it sets this masks like this: mmu_info->aperture_end = (dma_addr_t)DMA_BIT_MASK(isp->secure_mode ? IPU6_MMU_ADDR_BITS : IPU6_MMU_ADDR_BITS_NON_SECURE); So the 'n' argument here is "isp->secure_mode ? IPU6_MMU_ADDR_BITS : IPU6_MMU_ADDR_BITS_NON_SECURE" which gets expanded into: isp->secure_mode ? IPU6_MMU_ADDR_BITS : IPU6_MMU_ADDR_BITS_NON_SECURE - 1 With the -1 only being applied in the non secure case, causing the secure mode mask to be one 1 bit too large. Fixes: a50f7456f853 ("dma-mapping: Allow use of DMA_BIT_MASK(64) in global scope") Cc: Sakari Ailus Cc: James Clark Cc: Nathan Chancellor Cc: stable@vger.kernel.org Signed-off-by: Hans de Goede Reviewed-by: Nathan Chancellor Signed-off-by: Marek Szyprowski Link: https://lore.kernel.org/r/20251207184756.97904-1-johannes.goede@oss.qualcomm.com Signed-off-by: Greg Kroah-Hartman --- include/linux/dma-mapping.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/linux/dma-mapping.h b/include/linux/dma-mapping.h index 2ceda49c609fb7..aa36a0d1d9df6a 100644 --- a/include/linux/dma-mapping.h +++ b/include/linux/dma-mapping.h @@ -90,7 +90,7 @@ */ #define DMA_MAPPING_ERROR (~(dma_addr_t)0) -#define DMA_BIT_MASK(n) GENMASK_ULL(n - 1, 0) +#define DMA_BIT_MASK(n) GENMASK_ULL((n) - 1, 0) struct dma_iova_state { dma_addr_t addr; From 344789964c6ca16a28b6ff493152c94eb01c0601 Mon Sep 17 00:00:00 2001 From: Harry Yoo Date: Mon, 8 Dec 2025 00:41:47 +0900 Subject: [PATCH 1581/3902] mm/slab: introduce kvfree_rcu_barrier_on_cache() for cache destruction commit 0f35040de59371ad542b915d7b91176c9910dadc upstream. Currently, kvfree_rcu_barrier() flushes RCU sheaves across all slab caches when a cache is destroyed. This is unnecessary; only the RCU sheaves belonging to the cache being destroyed need to be flushed. As suggested by Vlastimil Babka, introduce a weaker form of kvfree_rcu_barrier() that operates on a specific slab cache. Factor out flush_rcu_sheaves_on_cache() from flush_all_rcu_sheaves() and call it from flush_all_rcu_sheaves() and kvfree_rcu_barrier_on_cache(). Call kvfree_rcu_barrier_on_cache() instead of kvfree_rcu_barrier() on cache destruction. The performance benefit is evaluated on a 12 core 24 threads AMD Ryzen 5900X machine (1 socket), by loading slub_kunit module. Before: Total calls: 19 Average latency (us): 18127 Total time (us): 344414 After: Total calls: 19 Average latency (us): 10066 Total time (us): 191264 Two performance regression have been reported: - stress module loader test's runtime increases by 50-60% (Daniel) - internal graphics test's runtime on Tegra234 increases by 35% (Jon) They are fixed by this change. Suggested-by: Vlastimil Babka Fixes: ec66e0d59952 ("slab: add sheaf support for batching kfree_rcu() operations") Cc: stable@vger.kernel.org Link: https://lore.kernel.org/linux-mm/1bda09da-93be-4737-aef0-d47f8c5c9301@suse.cz Reported-and-tested-by: Daniel Gomez Closes: https://lore.kernel.org/linux-mm/0406562e-2066-4cf8-9902-b2b0616dd742@kernel.org Reported-and-tested-by: Jon Hunter Closes: https://lore.kernel.org/linux-mm/e988eff6-1287-425e-a06c-805af5bbf262@nvidia.com Signed-off-by: Harry Yoo Link: https://patch.msgid.link/20251207154148.117723-1-harry.yoo@oracle.com Signed-off-by: Vlastimil Babka Signed-off-by: Greg Kroah-Hartman --- include/linux/slab.h | 7 ++++++ mm/slab.h | 1 + mm/slab_common.c | 52 +++++++++++++++++++++++++++++------------ mm/slub.c | 55 ++++++++++++++++++++++++-------------------- 4 files changed, 75 insertions(+), 40 deletions(-) diff --git a/include/linux/slab.h b/include/linux/slab.h index cf443f064a667e..2482992248dc9c 100644 --- a/include/linux/slab.h +++ b/include/linux/slab.h @@ -1150,10 +1150,17 @@ static inline void kvfree_rcu_barrier(void) rcu_barrier(); } +static inline void kvfree_rcu_barrier_on_cache(struct kmem_cache *s) +{ + rcu_barrier(); +} + static inline void kfree_rcu_scheduler_running(void) { } #else void kvfree_rcu_barrier(void); +void kvfree_rcu_barrier_on_cache(struct kmem_cache *s); + void kfree_rcu_scheduler_running(void); #endif diff --git a/mm/slab.h b/mm/slab.h index 078daecc7cf50a..bf9d8940b8f218 100644 --- a/mm/slab.h +++ b/mm/slab.h @@ -442,6 +442,7 @@ static inline bool is_kmalloc_normal(struct kmem_cache *s) bool __kfree_rcu_sheaf(struct kmem_cache *s, void *obj); void flush_all_rcu_sheaves(void); +void flush_rcu_sheaves_on_cache(struct kmem_cache *s); #define SLAB_CORE_FLAGS (SLAB_HWCACHE_ALIGN | SLAB_CACHE_DMA | \ SLAB_CACHE_DMA32 | SLAB_PANIC | \ diff --git a/mm/slab_common.c b/mm/slab_common.c index 932d13ada36c0d..29be54153fa915 100644 --- a/mm/slab_common.c +++ b/mm/slab_common.c @@ -492,7 +492,7 @@ void kmem_cache_destroy(struct kmem_cache *s) return; /* in-flight kfree_rcu()'s may include objects from our cache */ - kvfree_rcu_barrier(); + kvfree_rcu_barrier_on_cache(s); if (IS_ENABLED(CONFIG_SLUB_RCU_DEBUG) && (s->flags & SLAB_TYPESAFE_BY_RCU)) { @@ -2039,25 +2039,13 @@ void kvfree_call_rcu(struct rcu_head *head, void *ptr) } EXPORT_SYMBOL_GPL(kvfree_call_rcu); -/** - * kvfree_rcu_barrier - Wait until all in-flight kvfree_rcu() complete. - * - * Note that a single argument of kvfree_rcu() call has a slow path that - * triggers synchronize_rcu() following by freeing a pointer. It is done - * before the return from the function. Therefore for any single-argument - * call that will result in a kfree() to a cache that is to be destroyed - * during module exit, it is developer's responsibility to ensure that all - * such calls have returned before the call to kmem_cache_destroy(). - */ -void kvfree_rcu_barrier(void) +static inline void __kvfree_rcu_barrier(void) { struct kfree_rcu_cpu_work *krwp; struct kfree_rcu_cpu *krcp; bool queued; int i, cpu; - flush_all_rcu_sheaves(); - /* * Firstly we detach objects and queue them over an RCU-batch * for all CPUs. Finally queued works are flushed for each CPU. @@ -2119,8 +2107,43 @@ void kvfree_rcu_barrier(void) } } } + +/** + * kvfree_rcu_barrier - Wait until all in-flight kvfree_rcu() complete. + * + * Note that a single argument of kvfree_rcu() call has a slow path that + * triggers synchronize_rcu() following by freeing a pointer. It is done + * before the return from the function. Therefore for any single-argument + * call that will result in a kfree() to a cache that is to be destroyed + * during module exit, it is developer's responsibility to ensure that all + * such calls have returned before the call to kmem_cache_destroy(). + */ +void kvfree_rcu_barrier(void) +{ + flush_all_rcu_sheaves(); + __kvfree_rcu_barrier(); +} EXPORT_SYMBOL_GPL(kvfree_rcu_barrier); +/** + * kvfree_rcu_barrier_on_cache - Wait for in-flight kvfree_rcu() calls on a + * specific slab cache. + * @s: slab cache to wait for + * + * See the description of kvfree_rcu_barrier() for details. + */ +void kvfree_rcu_barrier_on_cache(struct kmem_cache *s) +{ + if (s->cpu_sheaves) + flush_rcu_sheaves_on_cache(s); + /* + * TODO: Introduce a version of __kvfree_rcu_barrier() that works + * on a specific slab cache. + */ + __kvfree_rcu_barrier(); +} +EXPORT_SYMBOL_GPL(kvfree_rcu_barrier_on_cache); + static unsigned long kfree_rcu_shrink_count(struct shrinker *shrink, struct shrink_control *sc) { @@ -2216,4 +2239,3 @@ void __init kvfree_rcu_init(void) } #endif /* CONFIG_KVFREE_RCU_BATCHED */ - diff --git a/mm/slub.c b/mm/slub.c index a0b905c2a557ef..e4aaaf0a5e040b 100644 --- a/mm/slub.c +++ b/mm/slub.c @@ -4118,42 +4118,47 @@ static void flush_rcu_sheaf(struct work_struct *w) /* needed for kvfree_rcu_barrier() */ -void flush_all_rcu_sheaves(void) +void flush_rcu_sheaves_on_cache(struct kmem_cache *s) { struct slub_flush_work *sfw; - struct kmem_cache *s; unsigned int cpu; - cpus_read_lock(); - mutex_lock(&slab_mutex); + mutex_lock(&flush_lock); - list_for_each_entry(s, &slab_caches, list) { - if (!s->cpu_sheaves) - continue; + for_each_online_cpu(cpu) { + sfw = &per_cpu(slub_flush, cpu); - mutex_lock(&flush_lock); + /* + * we don't check if rcu_free sheaf exists - racing + * __kfree_rcu_sheaf() might have just removed it. + * by executing flush_rcu_sheaf() on the cpu we make + * sure the __kfree_rcu_sheaf() finished its call_rcu() + */ - for_each_online_cpu(cpu) { - sfw = &per_cpu(slub_flush, cpu); + INIT_WORK(&sfw->work, flush_rcu_sheaf); + sfw->s = s; + queue_work_on(cpu, flushwq, &sfw->work); + } - /* - * we don't check if rcu_free sheaf exists - racing - * __kfree_rcu_sheaf() might have just removed it. - * by executing flush_rcu_sheaf() on the cpu we make - * sure the __kfree_rcu_sheaf() finished its call_rcu() - */ + for_each_online_cpu(cpu) { + sfw = &per_cpu(slub_flush, cpu); + flush_work(&sfw->work); + } - INIT_WORK(&sfw->work, flush_rcu_sheaf); - sfw->s = s; - queue_work_on(cpu, flushwq, &sfw->work); - } + mutex_unlock(&flush_lock); +} - for_each_online_cpu(cpu) { - sfw = &per_cpu(slub_flush, cpu); - flush_work(&sfw->work); - } +void flush_all_rcu_sheaves(void) +{ + struct kmem_cache *s; + + cpus_read_lock(); + mutex_lock(&slab_mutex); - mutex_unlock(&flush_lock); + list_for_each_entry(s, &slab_caches, list) { + if (!s->cpu_sheaves) + continue; + flush_rcu_sheaves_on_cache(s); } mutex_unlock(&slab_mutex); From ec4358c724915df67cac2eb2f01b79270f91bc9a Mon Sep 17 00:00:00 2001 From: "Matthieu Baerts (NGI0)" Date: Fri, 5 Dec 2025 19:55:14 +0100 Subject: [PATCH 1582/3902] mptcp: pm: ignore unknown endpoint flags commit 0ace3297a7301911e52d8195cb1006414897c859 upstream. Before this patch, the kernel was saving any flags set by the userspace, even unknown ones. This doesn't cause critical issues because the kernel is only looking at specific ones. But on the other hand, endpoints dumps could tell the userspace some recent flags seem to be supported on older kernel versions. Instead, ignore all unknown flags when parsing them. By doing that, the userspace can continue to set unsupported flags, but it has a way to verify what is supported by the kernel. Note that it sounds better to continue accepting unsupported flags not to change the behaviour, but also that eases things on the userspace side by adding "optional" endpoint types only supported by newer kernel versions without having to deal with the different kernel versions. A note for the backports: there will be conflicts in mptcp.h on older versions not having the mentioned flags, the new line should still be added last, and the '5' needs to be adapted to have the same value as the last entry. Fixes: 01cacb00b35c ("mptcp: add netlink-based PM") Cc: stable@vger.kernel.org Reviewed-by: Mat Martineau Signed-off-by: Matthieu Baerts (NGI0) Link: https://patch.msgid.link/20251205-net-mptcp-misc-fixes-6-19-rc1-v1-1-9e4781a6c1b8@kernel.org Signed-off-by: Jakub Kicinski Signed-off-by: Greg Kroah-Hartman --- include/uapi/linux/mptcp.h | 1 + net/mptcp/pm_netlink.c | 3 ++- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/include/uapi/linux/mptcp.h b/include/uapi/linux/mptcp.h index 87cfab874e2415..49e9b4e98c8b4b 100644 --- a/include/uapi/linux/mptcp.h +++ b/include/uapi/linux/mptcp.h @@ -40,6 +40,7 @@ #define MPTCP_PM_ADDR_FLAG_FULLMESH _BITUL(3) #define MPTCP_PM_ADDR_FLAG_IMPLICIT _BITUL(4) #define MPTCP_PM_ADDR_FLAG_LAMINAR _BITUL(5) +#define MPTCP_PM_ADDR_FLAGS_MASK GENMASK(5, 0) struct mptcp_info { __u8 mptcpi_subflows; diff --git a/net/mptcp/pm_netlink.c b/net/mptcp/pm_netlink.c index d5b383870f7995..7aa42de9c47b55 100644 --- a/net/mptcp/pm_netlink.c +++ b/net/mptcp/pm_netlink.c @@ -119,7 +119,8 @@ int mptcp_pm_parse_entry(struct nlattr *attr, struct genl_info *info, } if (tb[MPTCP_PM_ADDR_ATTR_FLAGS]) - entry->flags = nla_get_u32(tb[MPTCP_PM_ADDR_ATTR_FLAGS]); + entry->flags = nla_get_u32(tb[MPTCP_PM_ADDR_ATTR_FLAGS]) & + MPTCP_PM_ADDR_FLAGS_MASK; if (tb[MPTCP_PM_ADDR_ATTR_PORT]) entry->addr.port = htons(nla_get_u16(tb[MPTCP_PM_ADDR_ATTR_PORT])); From ea0129061d61665f4b8e9ff09bafe0554824207f Mon Sep 17 00:00:00 2001 From: "Matthieu Baerts (NGI0)" Date: Fri, 5 Dec 2025 19:55:15 +0100 Subject: [PATCH 1583/3902] selftests: mptcp: pm: ensure unknown flags are ignored commit 29f4801e9c8dfd12bdcb33b61a6ac479c7162bd7 upstream. This validates the previous commit: the userspace can set unknown flags -- the 7th bit is currently unused -- without errors, but only the supported ones are printed in the endpoints dumps. The 'Fixes' tag here below is the same as the one from the previous commit: this patch here is not fixing anything wrong in the selftests, but it validates the previous fix for an issue introduced by this commit ID. Fixes: 01cacb00b35c ("mptcp: add netlink-based PM") Cc: stable@vger.kernel.org Reviewed-by: Mat Martineau Signed-off-by: Matthieu Baerts (NGI0) Link: https://patch.msgid.link/20251205-net-mptcp-misc-fixes-6-19-rc1-v1-2-9e4781a6c1b8@kernel.org Signed-off-by: Jakub Kicinski Signed-off-by: Greg Kroah-Hartman --- tools/testing/selftests/net/mptcp/pm_netlink.sh | 4 ++++ tools/testing/selftests/net/mptcp/pm_nl_ctl.c | 11 +++++++++++ 2 files changed, 15 insertions(+) diff --git a/tools/testing/selftests/net/mptcp/pm_netlink.sh b/tools/testing/selftests/net/mptcp/pm_netlink.sh index ec6a8758819194..123d9d7a0278cd 100755 --- a/tools/testing/selftests/net/mptcp/pm_netlink.sh +++ b/tools/testing/selftests/net/mptcp/pm_netlink.sh @@ -192,6 +192,10 @@ check "show_endpoints" \ flush_endpoint check "show_endpoints" "" "flush addrs" +add_endpoint 10.0.1.1 flags unknown +check "show_endpoints" "$(format_endpoints "1,10.0.1.1")" "ignore unknown flags" +flush_endpoint + set_limits 9 1 2>/dev/null check "get_limits" "${default_limits}" "rcv addrs above hard limit" diff --git a/tools/testing/selftests/net/mptcp/pm_nl_ctl.c b/tools/testing/selftests/net/mptcp/pm_nl_ctl.c index 65b374232ff5ac..99eecccbf0c876 100644 --- a/tools/testing/selftests/net/mptcp/pm_nl_ctl.c +++ b/tools/testing/selftests/net/mptcp/pm_nl_ctl.c @@ -24,6 +24,8 @@ #define IPPROTO_MPTCP 262 #endif +#define MPTCP_PM_ADDR_FLAG_UNKNOWN _BITUL(7) + static void syntax(char *argv[]) { fprintf(stderr, "%s add|ann|rem|csf|dsf|get|set|del|flush|dump|events|listen|accept []\n", argv[0]); @@ -836,6 +838,8 @@ int add_addr(int fd, int pm_family, int argc, char *argv[]) flags |= MPTCP_PM_ADDR_FLAG_BACKUP; else if (!strcmp(tok, "fullmesh")) flags |= MPTCP_PM_ADDR_FLAG_FULLMESH; + else if (!strcmp(tok, "unknown")) + flags |= MPTCP_PM_ADDR_FLAG_UNKNOWN; else error(1, errno, "unknown flag %s", argv[arg]); @@ -1048,6 +1052,13 @@ static void print_addr(struct rtattr *attrs, int len) printf(","); } + if (flags & MPTCP_PM_ADDR_FLAG_UNKNOWN) { + printf("unknown"); + flags &= ~MPTCP_PM_ADDR_FLAG_UNKNOWN; + if (flags) + printf(","); + } + /* bump unknown flags, if any */ if (flags) printf("0x%x", flags); From 77ea2657b4704f978d3504883bb57a7ea08ecf47 Mon Sep 17 00:00:00 2001 From: Paolo Abeni Date: Fri, 5 Dec 2025 19:55:16 +0100 Subject: [PATCH 1584/3902] mptcp: schedule rtx timer only after pushing data commit 2ea6190f42d0416a4310e60a7fcb0b49fcbbd4fb upstream. The MPTCP protocol usually schedule the retransmission timer only when there is some chances for such retransmissions to happen. With a notable exception: __mptcp_push_pending() currently schedule such timer unconditionally, potentially leading to unnecessary rtx timer expiration. The issue is present since the blamed commit below but become easily reproducible after commit 27b0e701d387 ("mptcp: drop bogus optimization in __mptcp_check_push()") Fixes: 33d41c9cd74c ("mptcp: more accurate timeout") Cc: stable@vger.kernel.org Signed-off-by: Paolo Abeni Reviewed-by: Matthieu Baerts (NGI0) Signed-off-by: Matthieu Baerts (NGI0) Link: https://patch.msgid.link/20251205-net-mptcp-misc-fixes-6-19-rc1-v1-3-9e4781a6c1b8@kernel.org Signed-off-by: Jakub Kicinski Signed-off-by: Greg Kroah-Hartman --- net/mptcp/protocol.c | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/net/mptcp/protocol.c b/net/mptcp/protocol.c index 1e413426deeecf..cd5edc19cb3456 100644 --- a/net/mptcp/protocol.c +++ b/net/mptcp/protocol.c @@ -1597,7 +1597,7 @@ void __mptcp_push_pending(struct sock *sk, unsigned int flags) struct mptcp_sendmsg_info info = { .flags = flags, }; - bool do_check_data_fin = false; + bool copied = false; int push_count = 1; while (mptcp_send_head(sk) && (push_count > 0)) { @@ -1639,7 +1639,7 @@ void __mptcp_push_pending(struct sock *sk, unsigned int flags) push_count--; continue; } - do_check_data_fin = true; + copied = true; } } } @@ -1648,11 +1648,14 @@ void __mptcp_push_pending(struct sock *sk, unsigned int flags) if (ssk) mptcp_push_release(ssk, &info); - /* ensure the rtx timer is running */ - if (!mptcp_rtx_timer_pending(sk)) - mptcp_reset_rtx_timer(sk); - if (do_check_data_fin) + /* Avoid scheduling the rtx timer if no data has been pushed; the timer + * will be updated on positive acks by __mptcp_cleanup_una(). + */ + if (copied) { + if (!mptcp_rtx_timer_pending(sk)) + mptcp_reset_rtx_timer(sk); mptcp_check_send_data_fin(sk); + } } static void __mptcp_subflow_push_pending(struct sock *sk, struct sock *ssk, bool first) From 50f47c02be419bf0a3ae94c118addf67beef359f Mon Sep 17 00:00:00 2001 From: Paolo Abeni Date: Fri, 5 Dec 2025 19:55:17 +0100 Subject: [PATCH 1585/3902] mptcp: avoid deadlock on fallback while reinjecting commit ffb8c27b0539dd90262d1021488e7817fae57c42 upstream. Jakub reported an MPTCP deadlock at fallback time: WARNING: possible recursive locking detected 6.18.0-rc7-virtme #1 Not tainted -------------------------------------------- mptcp_connect/20858 is trying to acquire lock: ff1100001da18b60 (&msk->fallback_lock){+.-.}-{3:3}, at: __mptcp_try_fallback+0xd8/0x280 but task is already holding lock: ff1100001da18b60 (&msk->fallback_lock){+.-.}-{3:3}, at: __mptcp_retrans+0x352/0xaa0 other info that might help us debug this: Possible unsafe locking scenario: CPU0 ---- lock(&msk->fallback_lock); lock(&msk->fallback_lock); *** DEADLOCK *** May be due to missing lock nesting notation 3 locks held by mptcp_connect/20858: #0: ff1100001da18290 (sk_lock-AF_INET){+.+.}-{0:0}, at: mptcp_sendmsg+0x114/0x1bc0 #1: ff1100001db40fd0 (k-sk_lock-AF_INET#2){+.+.}-{0:0}, at: __mptcp_retrans+0x2cb/0xaa0 #2: ff1100001da18b60 (&msk->fallback_lock){+.-.}-{3:3}, at: __mptcp_retrans+0x352/0xaa0 stack backtrace: CPU: 0 UID: 0 PID: 20858 Comm: mptcp_connect Not tainted 6.18.0-rc7-virtme #1 PREEMPT(full) Hardware name: Bochs, BIOS Bochs 01/01/2011 Call Trace: dump_stack_lvl+0x6f/0xa0 print_deadlock_bug.cold+0xc0/0xcd validate_chain+0x2ff/0x5f0 __lock_acquire+0x34c/0x740 lock_acquire.part.0+0xbc/0x260 _raw_spin_lock_bh+0x38/0x50 __mptcp_try_fallback+0xd8/0x280 mptcp_sendmsg_frag+0x16c2/0x3050 __mptcp_retrans+0x421/0xaa0 mptcp_release_cb+0x5aa/0xa70 release_sock+0xab/0x1d0 mptcp_sendmsg+0xd5b/0x1bc0 sock_write_iter+0x281/0x4d0 new_sync_write+0x3c5/0x6f0 vfs_write+0x65e/0xbb0 ksys_write+0x17e/0x200 do_syscall_64+0xbb/0xfd0 entry_SYSCALL_64_after_hwframe+0x4b/0x53 RIP: 0033:0x7fa5627cbc5e Code: 4d 89 d8 e8 14 bd 00 00 4c 8b 5d f8 41 8b 93 08 03 00 00 59 5e 48 83 f8 fc 74 11 c9 c3 0f 1f 80 00 00 00 00 48 8b 45 10 0f 05 c3 83 e2 39 83 fa 08 75 e7 e8 13 ff ff ff 0f 1f 00 f3 0f 1e fa RSP: 002b:00007fff1fe14700 EFLAGS: 00000202 ORIG_RAX: 0000000000000001 RAX: ffffffffffffffda RBX: 0000000000000005 RCX: 00007fa5627cbc5e RDX: 0000000000001f9c RSI: 00007fff1fe16984 RDI: 0000000000000005 RBP: 00007fff1fe14710 R08: 0000000000000000 R09: 0000000000000000 R10: 0000000000000000 R11: 0000000000000202 R12: 00007fff1fe16920 R13: 0000000000002000 R14: 0000000000001f9c R15: 0000000000001f9c The packet scheduler could attempt a reinjection after receiving an MP_FAIL and before the infinite map has been transmitted, causing a deadlock since MPTCP needs to do the reinjection atomically from WRT fallback. Address the issue explicitly avoiding the reinjection in the critical scenario. Note that this is the only fallback critical section that could potentially send packets and hit the double-lock. Reported-by: Jakub Kicinski Closes: https://netdev-ctrl.bots.linux.dev/logs/vmksft/mptcp-dbg/results/412720/1-mptcp-join-sh/stderr Fixes: f8a1d9b18c5e ("mptcp: make fallback action and fallback decision atomic") Cc: stable@vger.kernel.org Signed-off-by: Paolo Abeni Reviewed-by: Matthieu Baerts (NGI0) Signed-off-by: Matthieu Baerts (NGI0) Link: https://patch.msgid.link/20251205-net-mptcp-misc-fixes-6-19-rc1-v1-4-9e4781a6c1b8@kernel.org Signed-off-by: Jakub Kicinski Signed-off-by: Greg Kroah-Hartman --- net/mptcp/protocol.c | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/net/mptcp/protocol.c b/net/mptcp/protocol.c index cd5edc19cb3456..221a5ea019e697 100644 --- a/net/mptcp/protocol.c +++ b/net/mptcp/protocol.c @@ -2693,10 +2693,13 @@ static void __mptcp_retrans(struct sock *sk) /* * make the whole retrans decision, xmit, disallow - * fallback atomic + * fallback atomic, note that we can't retrans even + * when an infinite fallback is in progress, i.e. new + * subflows are disallowed. */ spin_lock_bh(&msk->fallback_lock); - if (__mptcp_check_fallback(msk)) { + if (__mptcp_check_fallback(msk) || + !msk->allow_subflows) { spin_unlock_bh(&msk->fallback_lock); release_sock(ssk); goto clear_scheduled; From 207d2d90ad871cfdd9915d48816285214699b785 Mon Sep 17 00:00:00 2001 From: Chen Changcheng Date: Thu, 18 Dec 2025 09:23:18 +0800 Subject: [PATCH 1586/3902] usb: usb-storage: Maintain minimal modifications to the bcdDevice range. commit 0831269b5f71594882accfceb02638124f88955d upstream. We cannot determine which models require the NO_ATA_1X and IGNORE_RESIDUE quirks aside from the EL-R12 optical drive device. Fixes: 955a48a5353f ("usb: usb-storage: No additional quirks need to be added to the EL-R12 optical drive.") Signed-off-by: Chen Changcheng Link: https://patch.msgid.link/20251218012318.15978-1-chenchangcheng@kylinos.cn Signed-off-by: Greg Kroah-Hartman --- drivers/usb/storage/unusual_uas.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/usb/storage/unusual_uas.h b/drivers/usb/storage/unusual_uas.h index b695f5ba9a4097..939a98c2d3f747 100644 --- a/drivers/usb/storage/unusual_uas.h +++ b/drivers/usb/storage/unusual_uas.h @@ -98,7 +98,7 @@ UNUSUAL_DEV(0x125f, 0xa94a, 0x0160, 0x0160, US_FL_NO_ATA_1X), /* Reported-by: Benjamin Tissoires */ -UNUSUAL_DEV(0x13fd, 0x3940, 0x0309, 0x0309, +UNUSUAL_DEV(0x13fd, 0x3940, 0x0000, 0x0309, "Initio Corporation", "INIC-3069", USB_SC_DEVICE, USB_PR_DEVICE, NULL, From ac92151ff2494130d9fc686055d6bbb9743a673e Mon Sep 17 00:00:00 2001 From: Jeongjun Park Date: Mon, 21 Apr 2025 21:52:44 +0900 Subject: [PATCH 1587/3902] media: dvb-usb: dtv5100: fix out-of-bounds in dtv5100_i2c_msg() commit b91e6aafe8d356086cc621bc03e35ba2299e4788 upstream. rlen value is a user-controlled value, but dtv5100_i2c_msg() does not check the size of the rlen value. Therefore, if it is set to a value larger than sizeof(st->data), an out-of-bounds vuln occurs for st->data. Therefore, we need to add proper range checking to prevent this vuln. Fixes: 60688d5e6e6e ("V4L/DVB (8735): dtv5100: replace dummy frontend by zl10353") Cc: stable@vger.kernel.org Signed-off-by: Jeongjun Park Signed-off-by: Hans Verkuil Signed-off-by: Greg Kroah-Hartman --- drivers/media/usb/dvb-usb/dtv5100.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/drivers/media/usb/dvb-usb/dtv5100.c b/drivers/media/usb/dvb-usb/dtv5100.c index 3d85c6f7f6ecf4..c448e2ebda1ac0 100644 --- a/drivers/media/usb/dvb-usb/dtv5100.c +++ b/drivers/media/usb/dvb-usb/dtv5100.c @@ -55,6 +55,11 @@ static int dtv5100_i2c_msg(struct dvb_usb_device *d, u8 addr, } index = (addr << 8) + wbuf[0]; + if (rlen > sizeof(st->data)) { + warn("rlen = %x is too big!\n", rlen); + return -EINVAL; + } + memcpy(st->data, rbuf, rlen); msleep(1); /* avoid I2C errors */ return usb_control_msg(d->udev, pipe, request, From b07bfaf62d07844c48270c4d0f390722d3953e0e Mon Sep 17 00:00:00 2001 From: Colin Ian King Date: Wed, 3 Sep 2025 09:44:16 +0100 Subject: [PATCH 1588/3902] media: pvrusb2: Fix incorrect variable used in trace message commit be440980eace19c035a0745fd6b6e42707bc4f49 upstream. The pvr2_trace message is reporting an error about control read transfers, however it is using the incorrect variable write_len instead of read_lean. Fix this by using the correct variable read_len. Fixes: d855497edbfb ("V4L/DVB (4228a): pvrusb2 to kernel 2.6.18") Cc: stable@vger.kernel.org Signed-off-by: Colin Ian King Signed-off-by: Hans Verkuil Signed-off-by: Greg Kroah-Hartman --- drivers/media/usb/pvrusb2/pvrusb2-hdw.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/media/usb/pvrusb2/pvrusb2-hdw.c b/drivers/media/usb/pvrusb2/pvrusb2-hdw.c index f21c2806eb9fe3..b32bb906a9de29 100644 --- a/drivers/media/usb/pvrusb2/pvrusb2-hdw.c +++ b/drivers/media/usb/pvrusb2/pvrusb2-hdw.c @@ -3622,7 +3622,7 @@ static int pvr2_send_request_ex(struct pvr2_hdw *hdw, pvr2_trace( PVR2_TRACE_ERROR_LEGS, "Attempted to execute %d byte control-read transfer (limit=%d)", - write_len,PVR2_CTL_BUFFSIZE); + read_len, PVR2_CTL_BUFFSIZE); return -EINVAL; } if ((!write_len) && (!read_len)) { From 987c0ddfe8bb23f5700c2512efd4ba273967ed6d Mon Sep 17 00:00:00 2001 From: Johan Hovold Date: Fri, 17 Oct 2025 07:45:37 +0200 Subject: [PATCH 1589/3902] phy: broadcom: bcm63xx-usbh: fix section mismatches MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit commit 356d1924b9a6bc2164ce2bf1fad147b0c37ae085 upstream. Platform drivers can be probed after their init sections have been discarded (e.g. on probe deferral or manual rebind through sysfs) so the probe function and match table must not live in init. Fixes: 783f6d3dcf35 ("phy: bcm63xx-usbh: Add BCM63xx USBH driver") Cc: stable@vger.kernel.org # 5.9 Cc: Álvaro Fernández Rojas Signed-off-by: Johan Hovold Reviewed-by: Neil Armstrong Link: https://patch.msgid.link/20251017054537.6884-1-johan@kernel.org Signed-off-by: Vinod Koul Signed-off-by: Greg Kroah-Hartman --- drivers/phy/broadcom/phy-bcm63xx-usbh.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/drivers/phy/broadcom/phy-bcm63xx-usbh.c b/drivers/phy/broadcom/phy-bcm63xx-usbh.c index 647644de041bba..29fd6791bae642 100644 --- a/drivers/phy/broadcom/phy-bcm63xx-usbh.c +++ b/drivers/phy/broadcom/phy-bcm63xx-usbh.c @@ -375,7 +375,7 @@ static struct phy *bcm63xx_usbh_phy_xlate(struct device *dev, return of_phy_simple_xlate(dev, args); } -static int __init bcm63xx_usbh_phy_probe(struct platform_device *pdev) +static int bcm63xx_usbh_phy_probe(struct platform_device *pdev) { struct device *dev = &pdev->dev; struct bcm63xx_usbh_phy *usbh; @@ -432,7 +432,7 @@ static int __init bcm63xx_usbh_phy_probe(struct platform_device *pdev) return 0; } -static const struct of_device_id bcm63xx_usbh_phy_ids[] __initconst = { +static const struct of_device_id bcm63xx_usbh_phy_ids[] = { { .compatible = "brcm,bcm6318-usbh-phy", .data = &usbh_bcm6318 }, { .compatible = "brcm,bcm6328-usbh-phy", .data = &usbh_bcm6328 }, { .compatible = "brcm,bcm6358-usbh-phy", .data = &usbh_bcm6358 }, @@ -443,7 +443,7 @@ static const struct of_device_id bcm63xx_usbh_phy_ids[] __initconst = { }; MODULE_DEVICE_TABLE(of, bcm63xx_usbh_phy_ids); -static struct platform_driver bcm63xx_usbh_phy_driver __refdata = { +static struct platform_driver bcm63xx_usbh_phy_driver = { .driver = { .name = "bcm63xx-usbh-phy", .of_match_table = bcm63xx_usbh_phy_ids, From a9cd9a4846ef4898c012932c6ca33a814b9ab6c4 Mon Sep 17 00:00:00 2001 From: Johan Hovold Date: Thu, 18 Dec 2025 16:35:17 +0100 Subject: [PATCH 1590/3902] usb: ohci-nxp: fix device leak on probe failure commit b4c61e542faf8c9131d69ecfc3ad6de96d1b2ab8 upstream. Make sure to drop the reference taken when looking up the PHY I2C device during probe on probe failure (e.g. probe deferral) and on driver unbind. Fixes: 73108aa90cbf ("USB: ohci-nxp: Use isp1301 driver") Cc: stable@vger.kernel.org # 3.5 Reported-by: Ma Ke Link: https://lore.kernel.org/lkml/20251117013428.21840-1-make24@iscas.ac.cn/ Signed-off-by: Johan Hovold Acked-by: Alan Stern Reviewed-by: Vladimir Zapolskiy Link: https://patch.msgid.link/20251218153519.19453-4-johan@kernel.org Signed-off-by: Greg Kroah-Hartman --- drivers/usb/host/ohci-nxp.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/drivers/usb/host/ohci-nxp.c b/drivers/usb/host/ohci-nxp.c index 24d5a1dc505604..509ca7d8d51380 100644 --- a/drivers/usb/host/ohci-nxp.c +++ b/drivers/usb/host/ohci-nxp.c @@ -223,6 +223,7 @@ static int ohci_hcd_nxp_probe(struct platform_device *pdev) fail_resource: usb_put_hcd(hcd); fail_disable: + put_device(&isp1301_i2c_client->dev); isp1301_i2c_client = NULL; return ret; } @@ -234,6 +235,7 @@ static void ohci_hcd_nxp_remove(struct platform_device *pdev) usb_remove_hcd(hcd); ohci_nxp_stop_hc(); usb_put_hcd(hcd); + put_device(&isp1301_i2c_client->dev); isp1301_i2c_client = NULL; } From 8422cdf91db1dc73f371adbc8af6d24d1908d1d3 Mon Sep 17 00:00:00 2001 From: Arnd Bergmann Date: Thu, 4 Dec 2025 11:11:07 +0100 Subject: [PATCH 1591/3902] usb: typec: ucsi: huawei-gaokin: add DRM dependency commit d14cd998e67ba8f1cca52a260a1ce1a60954fd8b upstream. Selecting DRM_AUX_HPD_BRIDGE is not possible from a built-in driver when CONFIG_DRM=m: WARNING: unmet direct dependencies detected for DRM_AUX_HPD_BRIDGE Depends on [m]: HAS_IOMEM [=y] && DRM [=m] && DRM_BRIDGE [=y] && OF [=y] Selected by [y]: - UCSI_HUAWEI_GAOKUN [=y] && USB_SUPPORT [=y] && TYPEC [=y] && TYPEC_UCSI [=y] && EC_HUAWEI_GAOKUN [=y] && DRM_BRIDGE [=y] && OF [=y] Add the same dependency we have in similar drivers to work around this. Fixes: 00327d7f2c8c ("usb: typec: ucsi: add Huawei Matebook E Go ucsi driver") Cc: stable Signed-off-by: Arnd Bergmann Reviewed-by: Heikki Krogerus Link: https://patch.msgid.link/20251204101111.1035975-1-arnd@kernel.org Signed-off-by: Greg Kroah-Hartman --- drivers/usb/typec/ucsi/Kconfig | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/usb/typec/ucsi/Kconfig b/drivers/usb/typec/ucsi/Kconfig index 7fcb1e1de5d6d1..b812be4d0e674e 100644 --- a/drivers/usb/typec/ucsi/Kconfig +++ b/drivers/usb/typec/ucsi/Kconfig @@ -96,6 +96,7 @@ config UCSI_LENOVO_YOGA_C630 config UCSI_HUAWEI_GAOKUN tristate "UCSI Interface Driver for Huawei Matebook E Go" depends on EC_HUAWEI_GAOKUN + depends on DRM || !DRM select DRM_AUX_HPD_BRIDGE if DRM_BRIDGE && OF help This driver enables UCSI support on the Huawei Matebook E Go tablet, From d377fe40ff5a4d7ccb0e3efa9381adfd4238f3b2 Mon Sep 17 00:00:00 2001 From: Haoxiang Li Date: Sat, 6 Dec 2025 15:04:45 +0800 Subject: [PATCH 1592/3902] usb: typec: altmodes/displayport: Drop the device reference in dp_altmode_probe() commit 128bb7fab342546352603bde8b49ff54e3af0529 upstream. In error paths, call typec_altmode_put_plug() to drop the device reference obtained by typec_altmode_get_plug(). Fixes: 71ba4fe56656 ("usb: typec: altmodes/displayport: add SOP' support") Cc: stable Signed-off-by: Haoxiang Li Reviewed-by: Heikki Krogerus Link: https://patch.msgid.link/20251206070445.190770-1-lihaoxiang@isrc.iscas.ac.cn Signed-off-by: Greg Kroah-Hartman --- drivers/usb/typec/altmodes/displayport.c | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/drivers/usb/typec/altmodes/displayport.c b/drivers/usb/typec/altmodes/displayport.c index 1dcb77faf85d45..64627056045107 100644 --- a/drivers/usb/typec/altmodes/displayport.c +++ b/drivers/usb/typec/altmodes/displayport.c @@ -764,12 +764,16 @@ int dp_altmode_probe(struct typec_altmode *alt) if (!(DP_CAP_PIN_ASSIGN_DFP_D(port->vdo) & DP_CAP_PIN_ASSIGN_UFP_D(alt->vdo)) && !(DP_CAP_PIN_ASSIGN_UFP_D(port->vdo) & - DP_CAP_PIN_ASSIGN_DFP_D(alt->vdo))) + DP_CAP_PIN_ASSIGN_DFP_D(alt->vdo))) { + typec_altmode_put_plug(plug); return -ENODEV; + } dp = devm_kzalloc(&alt->dev, sizeof(*dp), GFP_KERNEL); - if (!dp) + if (!dp) { + typec_altmode_put_plug(plug); return -ENOMEM; + } INIT_WORK(&dp->work, dp_altmode_work); mutex_init(&dp->lock); From cefaad839a384a72331aedad927b1944fb6943dc Mon Sep 17 00:00:00 2001 From: Ma Ke Date: Mon, 15 Dec 2025 10:09:31 +0800 Subject: [PATCH 1593/3902] USB: lpc32xx_udc: Fix error handling in probe commit c84117912bddd9e5d87e68daf182410c98181407 upstream. lpc32xx_udc_probe() acquires an i2c_client reference through isp1301_get_client() but fails to release it in both error handling paths and the normal removal path. This could result in a reference count leak for the I2C device, preventing proper cleanup and potentially leading to resource exhaustion. Add put_device() to release the reference in the probe failure path and in the remove function. Calling path: isp1301_get_client() -> of_find_i2c_device_by_node() -> i2c_find_device_by_fwnode(). As comments of i2c_find_device_by_fwnode() says, 'The user must call put_device(&client->dev) once done with the i2c client.' Found by code review. Cc: stable Fixes: 24a28e428351 ("USB: gadget driver for LPC32xx") Signed-off-by: Ma Ke Link: https://patch.msgid.link/20251215020931.15324-1-make24@iscas.ac.cn Signed-off-by: Greg Kroah-Hartman --- drivers/usb/gadget/udc/lpc32xx_udc.c | 21 +++++++++++++++------ 1 file changed, 15 insertions(+), 6 deletions(-) diff --git a/drivers/usb/gadget/udc/lpc32xx_udc.c b/drivers/usb/gadget/udc/lpc32xx_udc.c index 1a7d3c4f652fef..73c0f28a85852f 100644 --- a/drivers/usb/gadget/udc/lpc32xx_udc.c +++ b/drivers/usb/gadget/udc/lpc32xx_udc.c @@ -3020,7 +3020,7 @@ static int lpc32xx_udc_probe(struct platform_device *pdev) pdev->dev.dma_mask = &lpc32xx_usbd_dmamask; retval = dma_set_coherent_mask(&pdev->dev, DMA_BIT_MASK(32)); if (retval) - return retval; + goto i2c_fail; udc->board = &lpc32xx_usbddata; @@ -3038,28 +3038,32 @@ static int lpc32xx_udc_probe(struct platform_device *pdev) /* Get IRQs */ for (i = 0; i < 4; i++) { udc->udp_irq[i] = platform_get_irq(pdev, i); - if (udc->udp_irq[i] < 0) - return udc->udp_irq[i]; + if (udc->udp_irq[i] < 0) { + retval = udc->udp_irq[i]; + goto i2c_fail; + } } udc->udp_baseaddr = devm_platform_ioremap_resource(pdev, 0); if (IS_ERR(udc->udp_baseaddr)) { dev_err(udc->dev, "IO map failure\n"); - return PTR_ERR(udc->udp_baseaddr); + retval = PTR_ERR(udc->udp_baseaddr); + goto i2c_fail; } /* Get USB device clock */ udc->usb_slv_clk = devm_clk_get(&pdev->dev, NULL); if (IS_ERR(udc->usb_slv_clk)) { dev_err(udc->dev, "failed to acquire USB device clock\n"); - return PTR_ERR(udc->usb_slv_clk); + retval = PTR_ERR(udc->usb_slv_clk); + goto i2c_fail; } /* Enable USB device clock */ retval = clk_prepare_enable(udc->usb_slv_clk); if (retval < 0) { dev_err(udc->dev, "failed to start USB device clock\n"); - return retval; + goto i2c_fail; } /* Setup deferred workqueue data */ @@ -3161,6 +3165,8 @@ static int lpc32xx_udc_probe(struct platform_device *pdev) dma_free_coherent(&pdev->dev, UDCA_BUFF_SIZE, udc->udca_v_base, udc->udca_p_base); i2c_fail: + if (udc->isp1301_i2c_client) + put_device(&udc->isp1301_i2c_client->dev); clk_disable_unprepare(udc->usb_slv_clk); dev_err(udc->dev, "%s probe failed, %d\n", driver_name, retval); @@ -3189,6 +3195,9 @@ static void lpc32xx_udc_remove(struct platform_device *pdev) dma_free_coherent(&pdev->dev, UDCA_BUFF_SIZE, udc->udca_v_base, udc->udca_p_base); + if (udc->isp1301_i2c_client) + put_device(&udc->isp1301_i2c_client->dev); + clk_disable_unprepare(udc->usb_slv_clk); } From 2e7c47e2eb3cfeadf78a1ccbac8492c60d508f23 Mon Sep 17 00:00:00 2001 From: Duoming Zhou Date: Fri, 5 Dec 2025 11:48:31 +0800 Subject: [PATCH 1594/3902] usb: phy: fsl-usb: Fix use-after-free in delayed work during device removal commit 41ca62e3e21e48c2903b3b45e232cf4f2ff7434f upstream. The delayed work item otg_event is initialized in fsl_otg_conf() and scheduled under two conditions: 1. When a host controller binds to the OTG controller. 2. When the USB ID pin state changes (cable insertion/removal). A race condition occurs when the device is removed via fsl_otg_remove(): the fsl_otg instance may be freed while the delayed work is still pending or executing. This leads to use-after-free when the work function fsl_otg_event() accesses the already freed memory. The problematic scenario: (detach thread) | (delayed work) fsl_otg_remove() | kfree(fsl_otg_dev) //FREE| fsl_otg_event() | og = container_of(...) //USE | og-> //USE Fix this by calling disable_delayed_work_sync() in fsl_otg_remove() before deallocating the fsl_otg structure. This ensures the delayed work is properly canceled and completes execution prior to memory deallocation. This bug was identified through static analysis. Fixes: 0807c500a1a6 ("USB: add Freescale USB OTG Transceiver driver") Cc: stable Signed-off-by: Duoming Zhou Link: https://patch.msgid.link/20251205034831.12846-1-duoming@zju.edu.cn Signed-off-by: Greg Kroah-Hartman --- drivers/usb/phy/phy-fsl-usb.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/usb/phy/phy-fsl-usb.c b/drivers/usb/phy/phy-fsl-usb.c index 40ac68e52cee73..e266a47c4d4833 100644 --- a/drivers/usb/phy/phy-fsl-usb.c +++ b/drivers/usb/phy/phy-fsl-usb.c @@ -988,6 +988,7 @@ static void fsl_otg_remove(struct platform_device *pdev) { struct fsl_usb2_platform_data *pdata = dev_get_platdata(&pdev->dev); + disable_delayed_work_sync(&fsl_otg_dev->otg_event); usb_remove_phy(&fsl_otg_dev->phy); free_irq(fsl_otg_dev->irq, fsl_otg_dev); From 7501ecfe3e5202490c2d13dc7e181203601fcd69 Mon Sep 17 00:00:00 2001 From: Johan Hovold Date: Thu, 18 Dec 2025 16:35:16 +0100 Subject: [PATCH 1595/3902] usb: phy: isp1301: fix non-OF device reference imbalance commit b4b64fda4d30a83a7f00e92a0c8a1d47699609f3 upstream. A recent change fixing a device reference leak in a UDC driver introduced a potential use-after-free in the non-OF case as the isp1301_get_client() helper only increases the reference count for the returned I2C device in the OF case. Increment the reference count also for non-OF so that the caller can decrement it unconditionally. Note that this is inherently racy just as using the returned I2C device is since nothing is preventing the PHY driver from being unbound while in use. Fixes: c84117912bdd ("USB: lpc32xx_udc: Fix error handling in probe") Cc: stable@vger.kernel.org Cc: Ma Ke Signed-off-by: Johan Hovold Reviewed-by: Vladimir Zapolskiy Link: https://patch.msgid.link/20251218153519.19453-3-johan@kernel.org Signed-off-by: Greg Kroah-Hartman --- drivers/usb/phy/phy-isp1301.c | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/drivers/usb/phy/phy-isp1301.c b/drivers/usb/phy/phy-isp1301.c index f9b5c411aee4e5..2940f0c84e1b77 100644 --- a/drivers/usb/phy/phy-isp1301.c +++ b/drivers/usb/phy/phy-isp1301.c @@ -149,7 +149,12 @@ struct i2c_client *isp1301_get_client(struct device_node *node) return client; /* non-DT: only one ISP1301 chip supported */ - return isp1301_i2c_client; + if (isp1301_i2c_client) { + get_device(&isp1301_i2c_client->dev); + return isp1301_i2c_client; + } + + return NULL; } EXPORT_SYMBOL_GPL(isp1301_get_client); From 9f4bc5c304028795ac525b31192534b4bd7ca272 Mon Sep 17 00:00:00 2001 From: Johan Hovold Date: Thu, 18 Dec 2025 16:35:15 +0100 Subject: [PATCH 1596/3902] usb: gadget: lpc32xx_udc: fix clock imbalance in error path commit 782be79e4551550d7a82b1957fc0f7347e6d461f upstream. A recent change fixing a device reference leak introduced a clock imbalance by reusing an error path so that the clock may be disabled before having been enabled. Note that the clock framework allows for passing in NULL clocks so there is no risk for a NULL pointer dereference. Also drop the bogus I2C client NULL check added by the offending commit as the pointer has already been verified to be non-NULL. Fixes: c84117912bdd ("USB: lpc32xx_udc: Fix error handling in probe") Cc: stable@vger.kernel.org Cc: Ma Ke Signed-off-by: Johan Hovold Reviewed-by: Vladimir Zapolskiy Link: https://patch.msgid.link/20251218153519.19453-2-johan@kernel.org Signed-off-by: Greg Kroah-Hartman --- drivers/usb/gadget/udc/lpc32xx_udc.c | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/drivers/usb/gadget/udc/lpc32xx_udc.c b/drivers/usb/gadget/udc/lpc32xx_udc.c index 73c0f28a85852f..a962d4294fbec6 100644 --- a/drivers/usb/gadget/udc/lpc32xx_udc.c +++ b/drivers/usb/gadget/udc/lpc32xx_udc.c @@ -3020,7 +3020,7 @@ static int lpc32xx_udc_probe(struct platform_device *pdev) pdev->dev.dma_mask = &lpc32xx_usbd_dmamask; retval = dma_set_coherent_mask(&pdev->dev, DMA_BIT_MASK(32)); if (retval) - goto i2c_fail; + goto err_put_client; udc->board = &lpc32xx_usbddata; @@ -3040,7 +3040,7 @@ static int lpc32xx_udc_probe(struct platform_device *pdev) udc->udp_irq[i] = platform_get_irq(pdev, i); if (udc->udp_irq[i] < 0) { retval = udc->udp_irq[i]; - goto i2c_fail; + goto err_put_client; } } @@ -3048,7 +3048,7 @@ static int lpc32xx_udc_probe(struct platform_device *pdev) if (IS_ERR(udc->udp_baseaddr)) { dev_err(udc->dev, "IO map failure\n"); retval = PTR_ERR(udc->udp_baseaddr); - goto i2c_fail; + goto err_put_client; } /* Get USB device clock */ @@ -3056,14 +3056,14 @@ static int lpc32xx_udc_probe(struct platform_device *pdev) if (IS_ERR(udc->usb_slv_clk)) { dev_err(udc->dev, "failed to acquire USB device clock\n"); retval = PTR_ERR(udc->usb_slv_clk); - goto i2c_fail; + goto err_put_client; } /* Enable USB device clock */ retval = clk_prepare_enable(udc->usb_slv_clk); if (retval < 0) { dev_err(udc->dev, "failed to start USB device clock\n"); - goto i2c_fail; + goto err_put_client; } /* Setup deferred workqueue data */ @@ -3165,9 +3165,10 @@ static int lpc32xx_udc_probe(struct platform_device *pdev) dma_free_coherent(&pdev->dev, UDCA_BUFF_SIZE, udc->udca_v_base, udc->udca_p_base); i2c_fail: - if (udc->isp1301_i2c_client) - put_device(&udc->isp1301_i2c_client->dev); clk_disable_unprepare(udc->usb_slv_clk); +err_put_client: + put_device(&udc->isp1301_i2c_client->dev); + dev_err(udc->dev, "%s probe failed, %d\n", driver_name, retval); return retval; @@ -3195,10 +3196,9 @@ static void lpc32xx_udc_remove(struct platform_device *pdev) dma_free_coherent(&pdev->dev, UDCA_BUFF_SIZE, udc->udca_v_base, udc->udca_p_base); - if (udc->isp1301_i2c_client) - put_device(&udc->isp1301_i2c_client->dev); - clk_disable_unprepare(udc->usb_slv_clk); + + put_device(&udc->isp1301_i2c_client->dev); } #ifdef CONFIG_PM From 5a45d75da3b79abe0edf3608ecd58f751ffe5bbf Mon Sep 17 00:00:00 2001 From: Miaoqian Lin Date: Thu, 11 Dec 2025 10:49:36 +0400 Subject: [PATCH 1597/3902] usb: dwc3: of-simple: fix clock resource leak in dwc3_of_simple_probe commit 3b4961313d31e200c9e974bb1536cdea217f78b5 upstream. When clk_bulk_prepare_enable() fails, the error path jumps to err_resetc_assert, skipping clk_bulk_put_all() and leaking the clock references acquired by clk_bulk_get_all(). Add err_clk_put_all label to properly release clock resources in all error paths. Found via static analysis and code review. Fixes: c0c61471ef86 ("usb: dwc3: of-simple: Convert to bulk clk API") Cc: stable Signed-off-by: Miaoqian Lin Acked-by: Thinh Nguyen Link: https://patch.msgid.link/20251211064937.2360510-1-linmq006@gmail.com Signed-off-by: Greg Kroah-Hartman Signed-off-by: Greg Kroah-Hartman --- drivers/usb/dwc3/dwc3-of-simple.c | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/drivers/usb/dwc3/dwc3-of-simple.c b/drivers/usb/dwc3/dwc3-of-simple.c index a4954a21be9301..c116143335d9f9 100644 --- a/drivers/usb/dwc3/dwc3-of-simple.c +++ b/drivers/usb/dwc3/dwc3-of-simple.c @@ -70,11 +70,11 @@ static int dwc3_of_simple_probe(struct platform_device *pdev) simple->num_clocks = ret; ret = clk_bulk_prepare_enable(simple->num_clocks, simple->clks); if (ret) - goto err_resetc_assert; + goto err_clk_put_all; ret = of_platform_populate(np, NULL, NULL, dev); if (ret) - goto err_clk_put; + goto err_clk_disable; pm_runtime_set_active(dev); pm_runtime_enable(dev); @@ -82,8 +82,9 @@ static int dwc3_of_simple_probe(struct platform_device *pdev) return 0; -err_clk_put: +err_clk_disable: clk_bulk_disable_unprepare(simple->num_clocks, simple->clks); +err_clk_put_all: clk_bulk_put_all(simple->num_clocks, simple->clks); err_resetc_assert: From 1f069425f4f089d3c92064f276e90cf8170df182 Mon Sep 17 00:00:00 2001 From: Udipto Goswami Date: Wed, 26 Nov 2025 11:12:21 +0530 Subject: [PATCH 1598/3902] usb: dwc3: keep susphy enabled during exit to avoid controller faults commit e1003aa7ec9eccdde4c926bd64ef42816ad55f25 upstream. On some platforms, switching USB roles from host to device can trigger controller faults due to premature PHY power-down. This occurs when the PHY is disabled too early during teardown, causing synchronization issues between the PHY and controller. Keep susphy enabled during dwc3_host_exit() and dwc3_gadget_exit() ensures the PHY remains in a low-power state capable of handling required commands during role switch. Cc: stable Fixes: 6d735722063a ("usb: dwc3: core: Prevent phy suspend during init") Suggested-by: Thinh Nguyen Signed-off-by: Udipto Goswami Acked-by: Thinh Nguyen Link: https://patch.msgid.link/20251126054221.120638-1-udipto.goswami@oss.qualcomm.com Signed-off-by: Greg Kroah-Hartman Signed-off-by: Greg Kroah-Hartman --- drivers/usb/dwc3/gadget.c | 2 +- drivers/usb/dwc3/host.c | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/usb/dwc3/gadget.c b/drivers/usb/dwc3/gadget.c index 5e4997f974ddde..17eebb60900bf2 100644 --- a/drivers/usb/dwc3/gadget.c +++ b/drivers/usb/dwc3/gadget.c @@ -4825,7 +4825,7 @@ void dwc3_gadget_exit(struct dwc3 *dwc) if (!dwc->gadget) return; - dwc3_enable_susphy(dwc, false); + dwc3_enable_susphy(dwc, true); usb_del_gadget(dwc->gadget); dwc3_gadget_free_endpoints(dwc); usb_put_gadget(dwc->gadget); diff --git a/drivers/usb/dwc3/host.c b/drivers/usb/dwc3/host.c index e77fd86d09cf0a..3f075c57605cb5 100644 --- a/drivers/usb/dwc3/host.c +++ b/drivers/usb/dwc3/host.c @@ -226,7 +226,7 @@ void dwc3_host_exit(struct dwc3 *dwc) if (dwc->sys_wakeup) device_init_wakeup(&dwc->xhci->dev, false); - dwc3_enable_susphy(dwc, false); + dwc3_enable_susphy(dwc, true); platform_device_unregister(dwc->xhci); dwc->xhci = NULL; } From fd00f88ef36bbd850cbd409b210fe86aa53a2746 Mon Sep 17 00:00:00 2001 From: Haoxiang Li Date: Thu, 4 Dec 2025 21:21:29 +0800 Subject: [PATCH 1599/3902] usb: renesas_usbhs: Fix a resource leak in usbhs_pipe_malloc() commit 36cc7e09df9e43db21b46519b740145410dd9f4a upstream. usbhsp_get_pipe() set pipe's flags to IS_USED. In error paths, usbhsp_put_pipe() is required to clear pipe's flags to prevent pipe exhaustion. Fixes: f1407d5c6624 ("usb: renesas_usbhs: Add Renesas USBHS common code") Cc: stable Signed-off-by: Haoxiang Li Link: https://patch.msgid.link/20251204132129.109234-1-haoxiang_li2024@163.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/renesas_usbhs/pipe.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/drivers/usb/renesas_usbhs/pipe.c b/drivers/usb/renesas_usbhs/pipe.c index 75fff2e4cbc65f..56fc3ff5016fc7 100644 --- a/drivers/usb/renesas_usbhs/pipe.c +++ b/drivers/usb/renesas_usbhs/pipe.c @@ -713,11 +713,13 @@ struct usbhs_pipe *usbhs_pipe_malloc(struct usbhs_priv *priv, /* make sure pipe is not busy */ ret = usbhsp_pipe_barrier(pipe); if (ret < 0) { + usbhsp_put_pipe(pipe); dev_err(dev, "pipe setup failed %d\n", usbhs_pipe_number(pipe)); return NULL; } if (usbhsp_setup_pipecfg(pipe, is_host, dir_in, &pipecfg)) { + usbhsp_put_pipe(pipe); dev_err(dev, "can't setup pipe\n"); return NULL; } From 687ccc341f63cf21a351faa2196c82f0c90aff6d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Bartosik?= Date: Thu, 27 Nov 2025 11:16:44 +0000 Subject: [PATCH 1600/3902] xhci: dbgtty: fix device unregister: fixup MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit commit 74098cc06e753d3ffd8398b040a3a1dfb65260c0 upstream. This fixup replaces tty_vhangup() call with call to tty_port_tty_vhangup(). Both calls hangup tty device synchronously however tty_port_tty_vhangup() increases reference count during the hangup operation using scoped_guard(tty_port_tty). Cc: stable Fixes: 1f73b8b56cf3 ("xhci: dbgtty: fix device unregister") Signed-off-by: Łukasz Bartosik Link: https://patch.msgid.link/20251127111644.3161386-1-ukaszb@google.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/host/xhci-dbgtty.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/usb/host/xhci-dbgtty.c b/drivers/usb/host/xhci-dbgtty.c index 57cdda4e09c8ea..90282e51e23ec0 100644 --- a/drivers/usb/host/xhci-dbgtty.c +++ b/drivers/usb/host/xhci-dbgtty.c @@ -554,7 +554,7 @@ static void xhci_dbc_tty_unregister_device(struct xhci_dbc *dbc) * Hang up the TTY. This wakes up any blocked * writers and causes subsequent writes to fail. */ - tty_vhangup(port->port.tty); + tty_port_tty_vhangup(&port->port); tty_unregister_device(dbc_tty_driver, port->minor); xhci_dbc_tty_exit_port(port); From f83e3e9f89181b42f6076a115d767a7552c4a39e Mon Sep 17 00:00:00 2001 From: Tianchu Chen Date: Fri, 28 Nov 2025 15:53:23 +0800 Subject: [PATCH 1601/3902] char: applicom: fix NULL pointer dereference in ac_ioctl commit 82d12088c297fa1cef670e1718b3d24f414c23f7 upstream. Discovered by Atuin - Automated Vulnerability Discovery Engine. In ac_ioctl, the validation of IndexCard and the check for a valid RamIO pointer are skipped when cmd is 6. However, the function unconditionally executes readb(apbs[IndexCard].RamIO + VERS) at the end. If cmd is 6, IndexCard may reference a board that does not exist (where RamIO is NULL), leading to a NULL pointer dereference. Fix this by skipping the readb access when cmd is 6, as this command is a global information query and does not target a specific board context. Signed-off-by: Tianchu Chen Acked-by: Arnd Bergmann Cc: stable Link: https://patch.msgid.link/20251128155323.a786fde92ebb926cbe96fcb1@linux.dev Signed-off-by: Greg Kroah-Hartman --- drivers/char/applicom.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/drivers/char/applicom.c b/drivers/char/applicom.c index 9fed9706d9cd26..c138c468f3a441 100644 --- a/drivers/char/applicom.c +++ b/drivers/char/applicom.c @@ -835,7 +835,10 @@ static long ac_ioctl(struct file *file, unsigned int cmd, unsigned long arg) ret = -ENOTTY; break; } - Dummy = readb(apbs[IndexCard].RamIO + VERS); + + if (cmd != 6) + Dummy = readb(apbs[IndexCard].RamIO + VERS); + kfree(adgl); mutex_unlock(&ac_mutex); return ret; From 1f53652922cd39d4547268f2962c24e1b7dfc8b0 Mon Sep 17 00:00:00 2001 From: Srinivas Kandagatla Date: Fri, 14 Nov 2025 11:05:05 +0000 Subject: [PATCH 1602/3902] dt-bindings: slimbus: fix warning from example commit 4d4e746aa9f0f07261dcb41e4f51edb98723dcaa upstream. Fix below warnings generated dt_bindings_check for examples in the bindings. Documentation/devicetree/bindings/slimbus/slimbus.example.dtb: slim@28080000 (qcom,slim-ngd-v1.5.0): 'audio-codec@1,0' does not match any of the regexes: '^pinctrl-[0-9]+$', '^slim@[0-9a-f]+$' from schema $id: http://devicetree.org/schemas/slimbus/qcom,slim-ngd.yaml# Documentation/devicetree/bindings/slimbus/slimbus.example.dtb: slim@28080000 (qcom,slim-ngd-v1.5.0): #address-cells: 1 was expected from schema $id: http://devicetree.org/schemas/slimbus/qcom,slim-ngd.yaml# Documentation/devicetree/bindings/slimbus/slimbus.example.dtb: slim@28080000 (qcom,slim-ngd-v1.5.0): 'dmas' is a required property from schema $id: http://devicetree.org/schemas/slimbus/qcom,slim-ngd.yaml# Documentation/devicetree/bindings/slimbus/slimbus.example.dtb: slim@28080000 (qcom,slim-ngd-v1.5.0): 'dma-names' is a required property from schema $id: http://devicetree.org/schemas/slimbus/qcom,slim-ngd.yaml# Fixes: 7cbba32a2d62 ("slimbus: qcom: remove unused qcom controller driver") Cc: stable Reported-by: Rob Herring Signed-off-by: Srinivas Kandagatla Acked-by: Rob Herring (Arm) Signed-off-by: Srinivas Kandagatla Link: https://patch.msgid.link/20251114110505.143105-1-srini@kernel.org Signed-off-by: Greg Kroah-Hartman --- .../devicetree/bindings/slimbus/slimbus.yaml | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/Documentation/devicetree/bindings/slimbus/slimbus.yaml b/Documentation/devicetree/bindings/slimbus/slimbus.yaml index 89017d9cda1093..5a941610ce4edd 100644 --- a/Documentation/devicetree/bindings/slimbus/slimbus.yaml +++ b/Documentation/devicetree/bindings/slimbus/slimbus.yaml @@ -75,16 +75,22 @@ examples: #size-cells = <1>; ranges; - slim@28080000 { + controller@28080000 { compatible = "qcom,slim-ngd-v1.5.0"; reg = <0x091c0000 0x2c000>; interrupts = ; - #address-cells = <2>; + dmas = <&slimbam 3>, <&slimbam 4>; + dma-names = "rx", "tx"; + #address-cells = <1>; #size-cells = <0>; - - audio-codec@1,0 { + slim@1 { + reg = <1>; + #address-cells = <2>; + #size-cells = <0>; + codec@1,0 { compatible = "slim217,1a0"; reg = <1 0>; + }; }; + }; }; - }; From 4157f7bba6299d4288f95e935a01beba82ebffb5 Mon Sep 17 00:00:00 2001 From: Ma Ke Date: Wed, 12 Nov 2025 17:17:23 +0800 Subject: [PATCH 1603/3902] intel_th: Fix error handling in intel_th_output_open commit 6d5925b667e4ed9e77c8278cc215191d29454a3f upstream. intel_th_output_open() calls bus_find_device_by_devt() which internally increments the device reference count via get_device(), but this reference is not properly released in several error paths. When device driver is unavailable, file operations cannot be obtained, or the driver's open method fails, the function returns without calling put_device(), leading to a permanent device reference count leak. This prevents the device from being properly released and could cause resource exhaustion over time. Found by code review. Cc: stable Fixes: 39f4034693b7 ("intel_th: Add driver infrastructure for Intel(R) Trace Hub devices") Signed-off-by: Ma Ke Link: https://patch.msgid.link/20251112091723.35963-1-make24@iscas.ac.cn Signed-off-by: Greg Kroah-Hartman --- drivers/hwtracing/intel_th/core.c | 20 +++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/drivers/hwtracing/intel_th/core.c b/drivers/hwtracing/intel_th/core.c index 47d9e6c3bac041..fdb9d022d8753f 100644 --- a/drivers/hwtracing/intel_th/core.c +++ b/drivers/hwtracing/intel_th/core.c @@ -810,13 +810,17 @@ static int intel_th_output_open(struct inode *inode, struct file *file) int err; dev = bus_find_device_by_devt(&intel_th_bus, inode->i_rdev); - if (!dev || !dev->driver) - return -ENODEV; + if (!dev || !dev->driver) { + err = -ENODEV; + goto out_no_device; + } thdrv = to_intel_th_driver(dev->driver); fops = fops_get(thdrv->fops); - if (!fops) - return -ENODEV; + if (!fops) { + err = -ENODEV; + goto out_put_device; + } replace_fops(file, fops); @@ -824,10 +828,16 @@ static int intel_th_output_open(struct inode *inode, struct file *file) if (file->f_op->open) { err = file->f_op->open(inode, file); - return err; + if (err) + goto out_put_device; } return 0; + +out_put_device: + put_device(dev); +out_no_device: + return err; } static const struct file_operations intel_th_output_fops = { From 670c2d09ac5708f6cfd85f9ecd77e9276c8ac948 Mon Sep 17 00:00:00 2001 From: Ma Ke Date: Tue, 4 Nov 2025 10:01:33 +0800 Subject: [PATCH 1604/3902] mei: Fix error handling in mei_register commit a6dab2f61d23c1eb32f1d08fa7b4919a2478950b upstream. mei_register() fails to release the device reference in error paths after device_initialize(). During normal device registration, the reference is properly handled through mei_deregister() which calls device_destroy(). However, in error handling paths (such as cdev_alloc failure, cdev_add failure, etc.), missing put_device() calls cause reference count leaks, preventing the device's release function (mei_device_release) from being called and resulting in memory leaks of mei_device. Found by code review. Cc: stable Fixes: 7704e6be4ed2 ("mei: hook mei_device on class device") Signed-off-by: Ma Ke Acked-by: Alexander Usyskin Link: https://patch.msgid.link/20251104020133.5017-1-make24@iscas.ac.cn Signed-off-by: Greg Kroah-Hartman --- drivers/misc/mei/main.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/misc/mei/main.c b/drivers/misc/mei/main.c index 86a73684a37325..6f26d516078835 100644 --- a/drivers/misc/mei/main.c +++ b/drivers/misc/mei/main.c @@ -1307,6 +1307,7 @@ int mei_register(struct mei_device *dev, struct device *parent) err_del_cdev: cdev_del(dev->cdev); err: + put_device(&dev->dev); mei_minor_free(minor); return ret; } From 864deb6cf1c071cf10ef138e184c4666631dbcf8 Mon Sep 17 00:00:00 2001 From: Junxiao Chang Date: Sun, 9 Nov 2025 17:35:33 +0200 Subject: [PATCH 1605/3902] mei: gsc: add dependency on Xe driver commit 5d92c3b41f0bddfa416130c6e1b424414f3d2acf upstream. INTEL_MEI_GSC depends on either i915 or Xe and can be present when either of above is present. Cc: stable Fixes: 87a4c85d3a3e ("drm/xe/gsc: add gsc device support") Tested-by: Baoli Zhang Signed-off-by: Junxiao Chang Signed-off-by: Alexander Usyskin Link: https://patch.msgid.link/20251109153533.3179787-1-alexander.usyskin@intel.com Signed-off-by: Greg Kroah-Hartman --- drivers/misc/mei/Kconfig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/misc/mei/Kconfig b/drivers/misc/mei/Kconfig index f8b04e49e4baf1..f4eb307cd35ed0 100644 --- a/drivers/misc/mei/Kconfig +++ b/drivers/misc/mei/Kconfig @@ -49,7 +49,7 @@ config INTEL_MEI_TXE config INTEL_MEI_GSC tristate "Intel MEI GSC embedded device" depends on INTEL_MEI_ME - depends on DRM_I915 + depends on DRM_I915 || DRM_XE help Intel auxiliary driver for GSC devices embedded in Intel graphics devices. From c255a5164c58ae6c0f993e431843a965088e6ed8 Mon Sep 17 00:00:00 2001 From: Andy Shevchenko Date: Thu, 27 Nov 2025 17:36:50 +0100 Subject: [PATCH 1606/3902] serial: core: Restore sysfs fwnode information commit 24ec03cc55126b7b3adf102f4b3d9f716532b329 upstream. The change that restores sysfs fwnode information does it only for OF cases. Update the fix to cover all possible types of fwnodes. Fixes: d36f0e9a0002 ("serial: core: restore of_node information in sysfs") Cc: stable Signed-off-by: Andy Shevchenko Link: https://patch.msgid.link/20251127163650.2942075-1-andriy.shevchenko@linux.intel.com Signed-off-by: Greg Kroah-Hartman --- drivers/tty/serial/serial_base_bus.c | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/drivers/tty/serial/serial_base_bus.c b/drivers/tty/serial/serial_base_bus.c index 22749ab0428a7c..8e891984cdc0df 100644 --- a/drivers/tty/serial/serial_base_bus.c +++ b/drivers/tty/serial/serial_base_bus.c @@ -13,7 +13,7 @@ #include #include #include -#include +#include #include #include #include @@ -60,6 +60,7 @@ void serial_base_driver_unregister(struct device_driver *driver) driver_unregister(driver); } +/* On failure the caller must put device @dev with put_device() */ static int serial_base_device_init(struct uart_port *port, struct device *dev, struct device *parent_dev, @@ -73,7 +74,8 @@ static int serial_base_device_init(struct uart_port *port, dev->parent = parent_dev; dev->bus = &serial_base_bus_type; dev->release = release; - device_set_of_node_from_dev(dev, parent_dev); + + device_set_node(dev, fwnode_handle_get(dev_fwnode(parent_dev))); if (!serial_base_initialized) { dev_dbg(port->dev, "uart_add_one_port() called before arch_initcall()?\n"); @@ -94,7 +96,7 @@ static void serial_base_ctrl_release(struct device *dev) { struct serial_ctrl_device *ctrl_dev = to_serial_base_ctrl_device(dev); - of_node_put(dev->of_node); + fwnode_handle_put(dev_fwnode(dev)); kfree(ctrl_dev); } @@ -142,7 +144,7 @@ static void serial_base_port_release(struct device *dev) { struct serial_port_device *port_dev = to_serial_base_port_device(dev); - of_node_put(dev->of_node); + fwnode_handle_put(dev_fwnode(dev)); kfree(port_dev); } From 320578703ebeca2d96f818d547bacc8ea9afac3c Mon Sep 17 00:00:00 2001 From: Alexander Stein Date: Fri, 19 Dec 2025 16:28:12 +0100 Subject: [PATCH 1607/3902] serial: core: Fix serial device initialization commit f54151148b969fb4b62bec8093d255306d20df30 upstream. During restoring sysfs fwnode information the information of_node_reused was dropped. This was previously set by device_set_of_node_from_dev(). Add it back manually Fixes: 24ec03cc5512 ("serial: core: Restore sysfs fwnode information") Cc: stable Suggested-by: Cosmin Tanislav Signed-off-by: Alexander Stein Tested-by: Michael Walle Tested-by: Marek Szyprowski Tested-by: Cosmin Tanislav Link: https://patch.msgid.link/20251219152813.1893982-1-alexander.stein@ew.tq-group.com Signed-off-by: Greg Kroah-Hartman --- drivers/tty/serial/serial_base_bus.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/tty/serial/serial_base_bus.c b/drivers/tty/serial/serial_base_bus.c index 8e891984cdc0df..1e1ad28d83fcf7 100644 --- a/drivers/tty/serial/serial_base_bus.c +++ b/drivers/tty/serial/serial_base_bus.c @@ -74,6 +74,7 @@ static int serial_base_device_init(struct uart_port *port, dev->parent = parent_dev; dev->bus = &serial_base_bus_type; dev->release = release; + dev->of_node_reused = true; device_set_node(dev, fwnode_handle_get(dev_fwnode(parent_dev))); From 190dc4b74800746146db12ed669b26ac773003eb Mon Sep 17 00:00:00 2001 From: "j.turek" Date: Sun, 21 Dec 2025 11:32:21 +0100 Subject: [PATCH 1608/3902] serial: xilinx_uartps: fix rs485 delay_rts_after_send commit 267ee93c417e685d9f8e079e41c70ba6ee4df5a5 upstream. RTS line control with delay should be triggered when there is no more bytes in kfifo and hardware buffer is empty. Without this patch RTS control is scheduled right after feeding hardware buffer and this is too early. RTS line may change state before hardware buffer is empty. With this patch delayed RTS state change is triggered when function cdns_uart_handle_tx is called from cdns_uart_isr on CDNS_UART_IXR_TXEMPTY exactly when hardware completed transmission Fixes: fccc9d9233f9 ("tty: serial: uartps: Add rs485 support to uartps driver") Cc: stable Link: https://patch.msgid.link/20251221103221.1971125-1-jakub.turek@elsta.tech Signed-off-by: Jakub Turek Signed-off-by: Greg Kroah-Hartman --- drivers/tty/serial/xilinx_uartps.c | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/drivers/tty/serial/xilinx_uartps.c b/drivers/tty/serial/xilinx_uartps.c index a66b44d21fba25..a354326a4242a6 100644 --- a/drivers/tty/serial/xilinx_uartps.c +++ b/drivers/tty/serial/xilinx_uartps.c @@ -430,10 +430,17 @@ static void cdns_uart_handle_tx(void *dev_id) struct tty_port *tport = &port->state->port; unsigned int numbytes; unsigned char ch; + ktime_t rts_delay; if (kfifo_is_empty(&tport->xmit_fifo) || uart_tx_stopped(port)) { /* Disable the TX Empty interrupt */ writel(CDNS_UART_IXR_TXEMPTY, port->membase + CDNS_UART_IDR); + /* Set RTS line after delay */ + if (cdns_uart->port->rs485.flags & SER_RS485_ENABLED) { + cdns_uart->tx_timer.function = &cdns_rs485_rx_callback; + rts_delay = ns_to_ktime(cdns_calc_after_tx_delay(cdns_uart)); + hrtimer_start(&cdns_uart->tx_timer, rts_delay, HRTIMER_MODE_REL); + } return; } @@ -450,13 +457,6 @@ static void cdns_uart_handle_tx(void *dev_id) /* Enable the TX Empty interrupt */ writel(CDNS_UART_IXR_TXEMPTY, cdns_uart->port->membase + CDNS_UART_IER); - - if (cdns_uart->port->rs485.flags & SER_RS485_ENABLED && - (kfifo_is_empty(&tport->xmit_fifo) || uart_tx_stopped(port))) { - hrtimer_update_function(&cdns_uart->tx_timer, cdns_rs485_rx_callback); - hrtimer_start(&cdns_uart->tx_timer, - ns_to_ktime(cdns_calc_after_tx_delay(cdns_uart)), HRTIMER_MODE_REL); - } } /** From 17157612e40d90c13e5b571c91b0cf2b5b825770 Mon Sep 17 00:00:00 2001 From: Claudiu Beznea Date: Wed, 17 Dec 2025 15:57:59 +0200 Subject: [PATCH 1609/3902] serial: sh-sci: Check that the DMA cookie is valid commit c3ca8a0aac832fe8047608bb2ae2cca314c6d717 upstream. The driver updates struct sci_port::tx_cookie to zero right before the TX work is scheduled, or to -EINVAL when DMA is disabled. dma_async_is_complete(), called through dma_cookie_status() (and possibly through dmaengine_tx_status()), considers cookies valid only if they have values greater than or equal to 1. Passing zero or -EINVAL to dmaengine_tx_status() before any TX DMA transfer has started leads to an incorrect TX status being reported, as the cookie is invalid for the DMA subsystem. This may cause long wait times when the serial device is opened for configuration before any TX activity has occurred. Check that the TX cookie is valid before passing it to dmaengine_tx_status(). Fixes: 7cc0e0a43a91 ("serial: sh-sci: Check if TX data was written to device in .tx_empty()") Cc: stable Signed-off-by: Claudiu Beznea Link: https://patch.msgid.link/20251217135759.402015-1-claudiu.beznea.uj@bp.renesas.com Signed-off-by: Greg Kroah-Hartman --- drivers/tty/serial/sh-sci.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/tty/serial/sh-sci.c b/drivers/tty/serial/sh-sci.c index 1db4af6fe59096..d91b1fb69bebdf 100644 --- a/drivers/tty/serial/sh-sci.c +++ b/drivers/tty/serial/sh-sci.c @@ -1740,7 +1740,7 @@ static void sci_dma_check_tx_occurred(struct sci_port *s) struct dma_tx_state state; enum dma_status status; - if (!s->chan_tx) + if (!s->chan_tx || s->cookie_tx <= 0) return; status = dmaengine_tx_status(s->chan_tx, s->cookie_tx, &state); From 87f11fea5acb300dfe491715868367a1d4bdd9a4 Mon Sep 17 00:00:00 2001 From: "Rafael J. Wysocki" Date: Thu, 13 Nov 2025 14:24:31 +0100 Subject: [PATCH 1610/3902] cpuidle: governors: teo: Drop misguided target residency check commit a03b2011808ab02ccb7ab6b573b013b77fbb5921 upstream. When the target residency of the current candidate idle state is greater than the expected time till the closest timer (the sleep length), it does not matter whether or not the tick has already been stopped or if it is going to be stopped. The closest timer will trigger anyway at its due time, so if an idle state with target residency above the sleep length is selected, energy will be wasted and there may be excess latency. Of course, if the closest timer were canceled before it could trigger, a deeper idle state would be more suitable, but this is not expected to happen (generally speaking, hrtimers are not expected to be canceled as a rule). Accordingly, the teo_state_ok() check done in that case causes energy to be wasted more often than it allows any energy to be saved (if it allows any energy to be saved at all), so drop it and let the governor use the teo_find_shallower_state() return value as the new candidate idle state index. Fixes: 21d28cd2fa5f ("cpuidle: teo: Do not call tick_nohz_get_sleep_length() upfront") Cc: All applicable Signed-off-by: Rafael J. Wysocki Reviewed-by: Christian Loehle Tested-by: Christian Loehle Link: https://patch.msgid.link/5955081.DvuYhMxLoT@rafael.j.wysocki Signed-off-by: Greg Kroah-Hartman --- drivers/cpuidle/governors/teo.c | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/drivers/cpuidle/governors/teo.c b/drivers/cpuidle/governors/teo.c index bfa55c1eab5bcd..8078eca72ca536 100644 --- a/drivers/cpuidle/governors/teo.c +++ b/drivers/cpuidle/governors/teo.c @@ -458,11 +458,8 @@ static int teo_select(struct cpuidle_driver *drv, struct cpuidle_device *dev, * If the closest expected timer is before the target residency of the * candidate state, a shallower one needs to be found. */ - if (drv->states[idx].target_residency_ns > duration_ns) { - i = teo_find_shallower_state(drv, dev, idx, duration_ns, false); - if (teo_state_ok(i, drv)) - idx = i; - } + if (drv->states[idx].target_residency_ns > duration_ns) + idx = teo_find_shallower_state(drv, dev, idx, duration_ns, false); /* * If the selected state's target residency is below the tick length From 8f92817b6f100397d24a59a153f96a67fee2a7bc Mon Sep 17 00:00:00 2001 From: Miaoqian Lin Date: Mon, 27 Oct 2025 23:04:45 +0800 Subject: [PATCH 1611/3902] cpufreq: nforce2: fix reference count leak in nforce2 commit 9600156bb99852c216a2128cdf9f114eb67c350f upstream. There are two reference count leaks in this driver: 1. In nforce2_fsb_read(): pci_get_subsys() increases the reference count of the PCI device, but pci_dev_put() is never called to release it, thus leaking the reference. 2. In nforce2_detect_chipset(): pci_get_subsys() gets a reference to the nforce2_dev which is stored in a global variable, but the reference is never released when the module is unloaded. Fix both by: - Adding pci_dev_put(nforce2_sub5) in nforce2_fsb_read() after reading the configuration. - Adding pci_dev_put(nforce2_dev) in nforce2_exit() to release the global device reference. Found via static analysis. Fixes: 1da177e4c3f4 ("Linux-2.6.12-rc2") Cc: stable@vger.kernel.org Signed-off-by: Miaoqian Lin Signed-off-by: Viresh Kumar Signed-off-by: Greg Kroah-Hartman --- drivers/cpufreq/cpufreq-nforce2.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/drivers/cpufreq/cpufreq-nforce2.c b/drivers/cpufreq/cpufreq-nforce2.c index fedad1081973fd..fbbbe501cf2dc1 100644 --- a/drivers/cpufreq/cpufreq-nforce2.c +++ b/drivers/cpufreq/cpufreq-nforce2.c @@ -145,6 +145,8 @@ static unsigned int nforce2_fsb_read(int bootfsb) pci_read_config_dword(nforce2_sub5, NFORCE2_BOOTFSB, &fsb); fsb /= 1000000; + pci_dev_put(nforce2_sub5); + /* Check if PLL register is already set */ pci_read_config_byte(nforce2_dev, NFORCE2_PLLENABLE, (u8 *)&temp); @@ -426,6 +428,7 @@ static int __init nforce2_init(void) static void __exit nforce2_exit(void) { cpufreq_unregister_driver(&nforce2_driver); + pci_dev_put(nforce2_dev); } module_init(nforce2_init); From 1c728951bc769b795d377852eae1abddad88635d Mon Sep 17 00:00:00 2001 From: Tony Battersby Date: Mon, 10 Nov 2025 10:47:35 -0500 Subject: [PATCH 1612/3902] scsi: Revert "scsi: qla2xxx: Perform lockless command completion in abort path" commit b57fbc88715b6d18f379463f48a15b560b087ffe upstream. This reverts commit 0367076b0817d5c75dfb83001ce7ce5c64d803a9. The commit being reverted added code to __qla2x00_abort_all_cmds() to call sp->done() without holding a spinlock. But unlike the older code below it, this new code failed to check sp->cmd_type and just assumed TYPE_SRB, which results in a jump to an invalid pointer in target-mode with TYPE_TGT_CMD: qla2xxx [0000:65:00.0]-d034:8: qla24xx_do_nack_work create sess success 0000000009f7a79b qla2xxx [0000:65:00.0]-5003:8: ISP System Error - mbx1=1ff5h mbx2=10h mbx3=0h mbx4=0h mbx5=191h mbx6=0h mbx7=0h. qla2xxx [0000:65:00.0]-d01e:8: -> fwdump no buffer qla2xxx [0000:65:00.0]-f03a:8: qla_target(0): System error async event 0x8002 occurred qla2xxx [0000:65:00.0]-00af:8: Performing ISP error recovery - ha=0000000058183fda. BUG: kernel NULL pointer dereference, address: 0000000000000000 PF: supervisor instruction fetch in kernel mode PF: error_code(0x0010) - not-present page PGD 0 P4D 0 Oops: 0010 [#1] SMP CPU: 2 PID: 9446 Comm: qla2xxx_8_dpc Tainted: G O 6.1.133 #1 Hardware name: Supermicro Super Server/X11SPL-F, BIOS 4.2 12/15/2023 RIP: 0010:0x0 Code: Unable to access opcode bytes at 0xffffffffffffffd6. RSP: 0018:ffffc90001f93dc8 EFLAGS: 00010206 RAX: 0000000000000282 RBX: 0000000000000355 RCX: ffff88810d16a000 RDX: ffff88810dbadaa8 RSI: 0000000000080000 RDI: ffff888169dc38c0 RBP: ffff888169dc38c0 R08: 0000000000000001 R09: 0000000000000045 R10: ffffffffa034bdf0 R11: 0000000000000000 R12: ffff88810800bb40 R13: 0000000000001aa8 R14: ffff888100136610 R15: ffff8881070f7400 FS: 0000000000000000(0000) GS:ffff88bf80080000(0000) knlGS:0000000000000000 CS: 0010 DS: 0000 ES: 0000 CR0: 0000000080050033 CR2: ffffffffffffffd6 CR3: 000000010c8ff006 CR4: 00000000003706e0 DR0: 0000000000000000 DR1: 0000000000000000 DR2: 0000000000000000 DR3: 0000000000000000 DR6: 00000000fffe0ff0 DR7: 0000000000000400 Call Trace: ? __die+0x4d/0x8b ? page_fault_oops+0x91/0x180 ? trace_buffer_unlock_commit_regs+0x38/0x1a0 ? exc_page_fault+0x391/0x5e0 ? asm_exc_page_fault+0x22/0x30 __qla2x00_abort_all_cmds+0xcb/0x3e0 [qla2xxx_scst] qla2x00_abort_all_cmds+0x50/0x70 [qla2xxx_scst] qla2x00_abort_isp_cleanup+0x3b7/0x4b0 [qla2xxx_scst] qla2x00_abort_isp+0xfd/0x860 [qla2xxx_scst] qla2x00_do_dpc+0x581/0xa40 [qla2xxx_scst] kthread+0xa8/0xd0 Then commit 4475afa2646d ("scsi: qla2xxx: Complete command early within lock") added the spinlock back, because not having the lock caused a race and a crash. But qla2x00_abort_srb() in the switch below already checks for qla2x00_chip_is_down() and handles it the same way, so the code above the switch is now redundant and still buggy in target-mode. Remove it. Cc: stable@vger.kernel.org Signed-off-by: Tony Battersby Link: https://patch.msgid.link/3a8022dc-bcfd-4b01-9f9b-7a9ec61fa2a3@cybernetics.com Signed-off-by: Martin K. Petersen Signed-off-by: Greg Kroah-Hartman --- drivers/scsi/qla2xxx/qla_os.c | 6 ------ 1 file changed, 6 deletions(-) diff --git a/drivers/scsi/qla2xxx/qla_os.c b/drivers/scsi/qla2xxx/qla_os.c index 6b0f616b1ca0ad..fd32b30a5b38b2 100644 --- a/drivers/scsi/qla2xxx/qla_os.c +++ b/drivers/scsi/qla2xxx/qla_os.c @@ -1862,12 +1862,6 @@ __qla2x00_abort_all_cmds(struct qla_qpair *qp, int res) for (cnt = 1; cnt < req->num_outstanding_cmds; cnt++) { sp = req->outstanding_cmds[cnt]; if (sp) { - if (qla2x00_chip_is_down(vha)) { - req->outstanding_cmds[cnt] = NULL; - sp->done(sp, res); - continue; - } - switch (sp->cmd_type) { case TYPE_SRB: qla2x00_abort_srb(qp, sp, res, &flags); From 751c19635c2bfaaf2836a533caa3663633066dcf Mon Sep 17 00:00:00 2001 From: Junrui Luo Date: Wed, 29 Oct 2025 00:29:04 +0800 Subject: [PATCH 1613/3902] scsi: aic94xx: fix use-after-free in device removal path commit f6ab594672d4cba08540919a4e6be2e202b60007 upstream. The asd_pci_remove() function fails to synchronize with pending tasklets before freeing the asd_ha structure, leading to a potential use-after-free vulnerability. When a device removal is triggered (via hot-unplug or module unload), race condition can occur. The fix adds tasklet_kill() before freeing the asd_ha structure, ensuring all scheduled tasklets complete before cleanup proceeds. Reported-by: Yuhao Jiang Reported-by: Junrui Luo Fixes: 2908d778ab3e ("[SCSI] aic94xx: new driver") Cc: stable@vger.kernel.org Signed-off-by: Junrui Luo Link: https://patch.msgid.link/ME2PR01MB3156AB7DCACA206C845FC7E8AFFDA@ME2PR01MB3156.ausprd01.prod.outlook.com Signed-off-by: Martin K. Petersen Signed-off-by: Greg Kroah-Hartman --- drivers/scsi/aic94xx/aic94xx_init.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/drivers/scsi/aic94xx/aic94xx_init.c b/drivers/scsi/aic94xx/aic94xx_init.c index adf3d91456064e..95f3620059f7dc 100644 --- a/drivers/scsi/aic94xx/aic94xx_init.c +++ b/drivers/scsi/aic94xx/aic94xx_init.c @@ -882,6 +882,9 @@ static void asd_pci_remove(struct pci_dev *dev) asd_disable_ints(asd_ha); + /* Ensure all scheduled tasklets complete before freeing resources */ + tasklet_kill(&asd_ha->seq.dl_tasklet); + asd_remove_dev_attrs(asd_ha); /* XXX more here as needed */ From 8c5ce5fafe9c78426ab66416b3373796b9b1db45 Mon Sep 17 00:00:00 2001 From: Dai Ngo Date: Wed, 5 Nov 2025 12:45:54 -0800 Subject: [PATCH 1614/3902] NFSD: use correct reservation type in nfsd4_scsi_fence_client commit 6f52063db9aabdaabea929b1e998af98c2e8d917 upstream. The reservation type argument for the pr_preempt call should match the one used in nfsd4_block_get_device_info_scsi. Fixes: f99d4fbdae67 ("nfsd: add SCSI layout support") Cc: stable@vger.kernel.org Signed-off-by: Dai Ngo Reviewed-by: Christoph Hellwig Signed-off-by: Chuck Lever Signed-off-by: Greg Kroah-Hartman --- fs/nfsd/blocklayout.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/fs/nfsd/blocklayout.c b/fs/nfsd/blocklayout.c index 425648565ab2d4..e9512c34c9dcf7 100644 --- a/fs/nfsd/blocklayout.c +++ b/fs/nfsd/blocklayout.c @@ -344,7 +344,8 @@ nfsd4_scsi_fence_client(struct nfs4_layout_stateid *ls, struct nfsd_file *file) struct block_device *bdev = file->nf_file->f_path.mnt->mnt_sb->s_bdev; bdev->bd_disk->fops->pr_ops->pr_preempt(bdev, NFSD_MDS_PR_KEY, - nfsd4_scsi_pr_key(clp), 0, true); + nfsd4_scsi_pr_key(clp), + PR_EXCLUSIVE_ACCESS_REG_ONLY, true); } const struct nfsd4_layout_ops scsi_layout_ops = { From 8edbb9e371af186b4cf40819dab65fafe109df4d Mon Sep 17 00:00:00 2001 From: Andrey Vatoropin Date: Tue, 18 Nov 2025 08:42:31 +0000 Subject: [PATCH 1615/3902] scsi: target: Reset t_task_cdb pointer in error case commit 5053eab38a4c4543522d0c320c639c56a8b59908 upstream. If allocation of cmd->t_task_cdb fails, it remains NULL but is later dereferenced in the 'err' path. In case of error, reset NULL t_task_cdb value to point at the default fixed-size buffer. Found by Linux Verification Center (linuxtesting.org) with SVACE. Fixes: 9e95fb805dc0 ("scsi: target: Fix NULL pointer dereference") Cc: stable@vger.kernel.org Signed-off-by: Andrey Vatoropin Reviewed-by: Mike Christie Link: https://patch.msgid.link/20251118084014.324940-1-a.vatoropin@crpt.ru Signed-off-by: Martin K. Petersen Signed-off-by: Greg Kroah-Hartman --- drivers/target/target_core_transport.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/target/target_core_transport.c b/drivers/target/target_core_transport.c index 0a76bdfe552820..88544c911949c8 100644 --- a/drivers/target/target_core_transport.c +++ b/drivers/target/target_core_transport.c @@ -1524,6 +1524,7 @@ target_cmd_init_cdb(struct se_cmd *cmd, unsigned char *cdb, gfp_t gfp) if (scsi_command_size(cdb) > sizeof(cmd->__t_task_cdb)) { cmd->t_task_cdb = kzalloc(scsi_command_size(cdb), gfp); if (!cmd->t_task_cdb) { + cmd->t_task_cdb = &cmd->__t_task_cdb[0]; pr_err("Unable to allocate cmd->t_task_cdb" " %u > sizeof(cmd->__t_task_cdb): %lu ops\n", scsi_command_size(cdb), From a0d4e9e24474fa739cea696b5757071e80cb6858 Mon Sep 17 00:00:00 2001 From: Chandrakanth Patil Date: Thu, 11 Dec 2025 05:59:29 +0530 Subject: [PATCH 1616/3902] scsi: mpi3mr: Read missing IOCFacts flag for reply queue full overflow commit d373163194982f43b92c552c138c29d9f0b79553 upstream. The driver was not reading the MAX_REQ_PER_REPLY_QUEUE_LIMIT IOCFacts flag, so the reply-queue-full handling was never enabled, even on firmware that supports it. Reading this flag enables the feature and prevents reply queue overflow. Fixes: f08b24d82749 ("scsi: mpi3mr: Avoid reply queue full condition") Cc: stable@vger.kernel.org Signed-off-by: Chandrakanth Patil Link: https://patch.msgid.link/20251211002929.22071-1-chandrakanth.patil@broadcom.com Signed-off-by: Martin K. Petersen Signed-off-by: Greg Kroah-Hartman --- drivers/scsi/mpi3mr/mpi/mpi30_ioc.h | 1 + drivers/scsi/mpi3mr/mpi3mr_fw.c | 2 ++ 2 files changed, 3 insertions(+) diff --git a/drivers/scsi/mpi3mr/mpi/mpi30_ioc.h b/drivers/scsi/mpi3mr/mpi/mpi30_ioc.h index b42933fcd42336..6561f98c3cb2da 100644 --- a/drivers/scsi/mpi3mr/mpi/mpi30_ioc.h +++ b/drivers/scsi/mpi3mr/mpi/mpi30_ioc.h @@ -166,6 +166,7 @@ struct mpi3_ioc_facts_data { #define MPI3_IOCFACTS_FLAGS_SIGNED_NVDATA_REQUIRED (0x00010000) #define MPI3_IOCFACTS_FLAGS_DMA_ADDRESS_WIDTH_MASK (0x0000ff00) #define MPI3_IOCFACTS_FLAGS_DMA_ADDRESS_WIDTH_SHIFT (8) +#define MPI3_IOCFACTS_FLAGS_MAX_REQ_PER_REPLY_QUEUE_LIMIT (0x00000040) #define MPI3_IOCFACTS_FLAGS_INITIAL_PORT_ENABLE_MASK (0x00000030) #define MPI3_IOCFACTS_FLAGS_INITIAL_PORT_ENABLE_SHIFT (4) #define MPI3_IOCFACTS_FLAGS_INITIAL_PORT_ENABLE_NOT_STARTED (0x00000000) diff --git a/drivers/scsi/mpi3mr/mpi3mr_fw.c b/drivers/scsi/mpi3mr/mpi3mr_fw.c index 8fe6e0bf342e25..8c4bb7169a87c3 100644 --- a/drivers/scsi/mpi3mr/mpi3mr_fw.c +++ b/drivers/scsi/mpi3mr/mpi3mr_fw.c @@ -3158,6 +3158,8 @@ static void mpi3mr_process_factsdata(struct mpi3mr_ioc *mrioc, mrioc->facts.dma_mask = (facts_flags & MPI3_IOCFACTS_FLAGS_DMA_ADDRESS_WIDTH_MASK) >> MPI3_IOCFACTS_FLAGS_DMA_ADDRESS_WIDTH_SHIFT; + mrioc->facts.max_req_limit = (facts_flags & + MPI3_IOCFACTS_FLAGS_MAX_REQ_PER_REPLY_QUEUE_LIMIT); mrioc->facts.protocol_flags = facts_data->protocol_flags; mrioc->facts.mpi_version = le32_to_cpu(facts_data->mpi_version.word); mrioc->facts.max_reqs = le16_to_cpu(facts_data->max_outstanding_requests); From 3fb2db6f50eab78f81455d137ed8987541d16e9f Mon Sep 17 00:00:00 2001 From: Seunghwan Baek Date: Wed, 10 Dec 2025 15:38:54 +0900 Subject: [PATCH 1617/3902] scsi: ufs: core: Add ufshcd_update_evt_hist() for UFS suspend error commit c9f36f04a8a2725172cdf2b5e32363e4addcb14c upstream. If UFS resume fails, the event history is updated in ufshcd_resume(), but there is no code anywhere to record UFS suspend. Therefore, add code to record UFS suspend error event history. Fixes: dd11376b9f1b ("scsi: ufs: Split the drivers/scsi/ufs directory") Cc: stable@vger.kernel.org Signed-off-by: Seunghwan Baek Reviewed-by: Peter Wang Link: https://patch.msgid.link/20251210063854.1483899-2-sh8267.baek@samsung.com Signed-off-by: Martin K. Petersen Signed-off-by: Greg Kroah-Hartman --- drivers/ufs/core/ufshcd.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/drivers/ufs/core/ufshcd.c b/drivers/ufs/core/ufshcd.c index a921a9098a291e..ba1bb3953cf69a 100644 --- a/drivers/ufs/core/ufshcd.c +++ b/drivers/ufs/core/ufshcd.c @@ -10223,7 +10223,7 @@ static int ufshcd_suspend(struct ufs_hba *hba) ret = ufshcd_setup_clocks(hba, false); if (ret) { ufshcd_enable_irq(hba); - return ret; + goto out; } if (ufshcd_is_clkgating_allowed(hba)) { hba->clk_gating.state = CLKS_OFF; @@ -10235,6 +10235,9 @@ static int ufshcd_suspend(struct ufs_hba *hba) /* Put the host controller in low power mode if possible */ ufshcd_hba_vreg_set_lpm(hba); ufshcd_pm_qos_update(hba, false); +out: + if (ret) + ufshcd_update_evt_hist(hba, UFS_EVT_SUSPEND_ERR, (u32)ret); return ret; } From 0b36fae23621a09e772c8adf918b9011158f8511 Mon Sep 17 00:00:00 2001 From: Jan Prusakowski Date: Mon, 6 Oct 2025 10:46:15 +0200 Subject: [PATCH 1618/3902] f2fs: ensure node page reads complete before f2fs_put_super() finishes commit 297baa4aa263ff8f5b3d246ee16a660d76aa82c4 upstream. Xfstests generic/335, generic/336 sometimes crash with the following message: F2FS-fs (dm-0): detect filesystem reference count leak during umount, type: 9, count: 1 ------------[ cut here ]------------ kernel BUG at fs/f2fs/super.c:1939! Oops: invalid opcode: 0000 [#1] SMP NOPTI CPU: 1 UID: 0 PID: 609351 Comm: umount Tainted: G W 6.17.0-rc5-xfstests-g9dd1835ecda5 #1 PREEMPT(none) Tainted: [W]=WARN Hardware name: QEMU Standard PC (Q35 + ICH9, 2009), BIOS 1.16.3-debian-1.16.3-2 04/01/2014 RIP: 0010:f2fs_put_super+0x3b3/0x3c0 Call Trace: generic_shutdown_super+0x7e/0x190 kill_block_super+0x1a/0x40 kill_f2fs_super+0x9d/0x190 deactivate_locked_super+0x30/0xb0 cleanup_mnt+0xba/0x150 task_work_run+0x5c/0xa0 exit_to_user_mode_loop+0xb7/0xc0 do_syscall_64+0x1ae/0x1c0 entry_SYSCALL_64_after_hwframe+0x76/0x7e ---[ end trace 0000000000000000 ]--- It appears that sometimes it is possible that f2fs_put_super() is called before all node page reads are completed. Adding a call to f2fs_wait_on_all_pages() for F2FS_RD_NODE fixes the problem. Cc: stable@kernel.org Fixes: 20872584b8c0b ("f2fs: fix to drop all dirty meta/node pages during umount()") Signed-off-by: Jan Prusakowski Reviewed-by: Chao Yu Signed-off-by: Jaegeuk Kim Signed-off-by: Greg Kroah-Hartman --- fs/f2fs/super.c | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/fs/f2fs/super.c b/fs/f2fs/super.c index 9085b4a511a480..256cb567b07244 100644 --- a/fs/f2fs/super.c +++ b/fs/f2fs/super.c @@ -1988,14 +1988,6 @@ static void f2fs_put_super(struct super_block *sb) truncate_inode_pages_final(META_MAPPING(sbi)); } - for (i = 0; i < NR_COUNT_TYPE; i++) { - if (!get_pages(sbi, i)) - continue; - f2fs_err(sbi, "detect filesystem reference count leak during " - "umount, type: %d, count: %lld", i, get_pages(sbi, i)); - f2fs_bug_on(sbi, 1); - } - f2fs_bug_on(sbi, sbi->fsync_node_num); f2fs_destroy_compress_inode(sbi); @@ -2006,6 +1998,15 @@ static void f2fs_put_super(struct super_block *sb) iput(sbi->meta_inode); sbi->meta_inode = NULL; + /* Should check the page counts after dropping all node/meta pages */ + for (i = 0; i < NR_COUNT_TYPE; i++) { + if (!get_pages(sbi, i)) + continue; + f2fs_err(sbi, "detect filesystem reference count leak during " + "umount, type: %d, count: %lld", i, get_pages(sbi, i)); + f2fs_bug_on(sbi, 1); + } + /* * iput() can update stat information, if f2fs_write_checkpoint() * above failed with error. From 0df713a9c082a474c8b0bcf670edc8e98461d5a0 Mon Sep 17 00:00:00 2001 From: Chao Yu Date: Wed, 22 Oct 2025 11:06:36 +0800 Subject: [PATCH 1619/3902] f2fs: fix to avoid updating compression context during writeback commit 10b591e7fb7cdc8c1e53e9c000dc0ef7069aaa76 upstream. Bai, Shuangpeng reported a bug as below: Oops: divide error: 0000 [#1] SMP KASAN PTI CPU: 0 UID: 0 PID: 11441 Comm: syz.0.46 Not tainted 6.17.0 #1 PREEMPT(full) Hardware name: QEMU Standard PC (i440FX + PIIX, 1996), BIOS 1.15.0-1 04/01/2014 RIP: 0010:f2fs_all_cluster_page_ready+0x106/0x550 fs/f2fs/compress.c:857 Call Trace: f2fs_write_cache_pages fs/f2fs/data.c:3078 [inline] __f2fs_write_data_pages fs/f2fs/data.c:3290 [inline] f2fs_write_data_pages+0x1c19/0x3600 fs/f2fs/data.c:3317 do_writepages+0x38e/0x640 mm/page-writeback.c:2634 filemap_fdatawrite_wbc mm/filemap.c:386 [inline] __filemap_fdatawrite_range mm/filemap.c:419 [inline] file_write_and_wait_range+0x2ba/0x3e0 mm/filemap.c:794 f2fs_do_sync_file+0x6e6/0x1b00 fs/f2fs/file.c:294 generic_write_sync include/linux/fs.h:3043 [inline] f2fs_file_write_iter+0x76e/0x2700 fs/f2fs/file.c:5259 new_sync_write fs/read_write.c:593 [inline] vfs_write+0x7e9/0xe00 fs/read_write.c:686 ksys_write+0x19d/0x2d0 fs/read_write.c:738 do_syscall_x64 arch/x86/entry/syscall_64.c:63 [inline] do_syscall_64+0xf7/0x470 arch/x86/entry/syscall_64.c:94 entry_SYSCALL_64_after_hwframe+0x77/0x7f The bug was triggered w/ below race condition: fsync setattr ioctl - f2fs_do_sync_file - file_write_and_wait_range - f2fs_write_cache_pages : inode is non-compressed : cc.cluster_size = F2FS_I(inode)->i_cluster_size = 0 - tag_pages_for_writeback - f2fs_setattr - truncate_setsize - f2fs_truncate - f2fs_fileattr_set - f2fs_setflags_common - set_compress_context : F2FS_I(inode)->i_cluster_size = 4 : set_inode_flag(inode, FI_COMPRESSED_FILE) - f2fs_compressed_file : return true - f2fs_all_cluster_page_ready : "pgidx % cc->cluster_size" trigger dividing 0 issue Let's change as below to fix this issue: - introduce a new atomic type variable .writeback in structure f2fs_inode_info to track the number of threads which calling f2fs_write_cache_pages(). - use .i_sem lock to protect .writeback update. - check .writeback before update compression context in f2fs_setflags_common() to avoid race w/ ->writepages. Fixes: 4c8ff7095bef ("f2fs: support data compression") Cc: stable@kernel.org Reported-by: Bai, Shuangpeng Tested-by: Bai, Shuangpeng Closes: https://lore.kernel.org/lkml/44D8F7B3-68AD-425F-9915-65D27591F93F@psu.edu Signed-off-by: Chao Yu Signed-off-by: Jaegeuk Kim Signed-off-by: Greg Kroah-Hartman --- fs/f2fs/data.c | 17 +++++++++++++++++ fs/f2fs/f2fs.h | 3 ++- fs/f2fs/file.c | 5 +++-- fs/f2fs/super.c | 1 + 4 files changed, 23 insertions(+), 3 deletions(-) diff --git a/fs/f2fs/data.c b/fs/f2fs/data.c index 775aa4f63aa303..0f9446143c8e17 100644 --- a/fs/f2fs/data.c +++ b/fs/f2fs/data.c @@ -3224,6 +3224,19 @@ static inline bool __should_serialize_io(struct inode *inode, return false; } +static inline void account_writeback(struct inode *inode, bool inc) +{ + if (!f2fs_sb_has_compression(F2FS_I_SB(inode))) + return; + + f2fs_down_read(&F2FS_I(inode)->i_sem); + if (inc) + atomic_inc(&F2FS_I(inode)->writeback); + else + atomic_dec(&F2FS_I(inode)->writeback); + f2fs_up_read(&F2FS_I(inode)->i_sem); +} + static int __f2fs_write_data_pages(struct address_space *mapping, struct writeback_control *wbc, enum iostat_type io_type) @@ -3269,10 +3282,14 @@ static int __f2fs_write_data_pages(struct address_space *mapping, locked = true; } + account_writeback(inode, true); + blk_start_plug(&plug); ret = f2fs_write_cache_pages(mapping, wbc, io_type); blk_finish_plug(&plug); + account_writeback(inode, false); + if (locked) mutex_unlock(&sbi->writepages); diff --git a/fs/f2fs/f2fs.h b/fs/f2fs/f2fs.h index 5149f351f03d27..2ff617820f13a1 100644 --- a/fs/f2fs/f2fs.h +++ b/fs/f2fs/f2fs.h @@ -948,6 +948,7 @@ struct f2fs_inode_info { unsigned char i_compress_level; /* compress level (lz4hc,zstd) */ unsigned char i_compress_flag; /* compress flag */ unsigned int i_cluster_size; /* cluster size */ + atomic_t writeback; /* count # of writeback thread */ unsigned int atomic_write_cnt; loff_t original_i_size; /* original i_size before atomic write */ @@ -4675,7 +4676,7 @@ static inline bool f2fs_disable_compressed_file(struct inode *inode) f2fs_up_write(&fi->i_sem); return true; } - if (f2fs_is_mmap_file(inode) || + if (f2fs_is_mmap_file(inode) || atomic_read(&fi->writeback) || (S_ISREG(inode->i_mode) && F2FS_HAS_BLOCKS(inode))) { f2fs_up_write(&fi->i_sem); return false; diff --git a/fs/f2fs/file.c b/fs/f2fs/file.c index ffa045b39c01de..c641a0209956e4 100644 --- a/fs/f2fs/file.c +++ b/fs/f2fs/file.c @@ -2125,8 +2125,9 @@ static int f2fs_setflags_common(struct inode *inode, u32 iflags, u32 mask) f2fs_down_write(&fi->i_sem); if (!f2fs_may_compress(inode) || - (S_ISREG(inode->i_mode) && - F2FS_HAS_BLOCKS(inode))) { + atomic_read(&fi->writeback) || + (S_ISREG(inode->i_mode) && + F2FS_HAS_BLOCKS(inode))) { f2fs_up_write(&fi->i_sem); return -EINVAL; } diff --git a/fs/f2fs/super.c b/fs/f2fs/super.c index 256cb567b07244..10f09c85b46281 100644 --- a/fs/f2fs/super.c +++ b/fs/f2fs/super.c @@ -1759,6 +1759,7 @@ static struct inode *f2fs_alloc_inode(struct super_block *sb) atomic_set(&fi->dirty_pages, 0); atomic_set(&fi->i_compr_blocks, 0); atomic_set(&fi->open_count, 0); + atomic_set(&fi->writeback, 0); init_f2fs_rwsem(&fi->i_sem); spin_lock_init(&fi->i_size_lock); INIT_LIST_HEAD(&fi->dirty_list); From 86a85a7b622e6e8dba69810257733ce5eab5ed55 Mon Sep 17 00:00:00 2001 From: Chao Yu Date: Tue, 14 Oct 2025 19:47:35 +0800 Subject: [PATCH 1620/3902] f2fs: fix to avoid potential deadlock commit ca8b201f28547e28343a6f00a6e91fa8c09572fe upstream. As Jiaming Zhang and syzbot reported, there is potential deadlock in f2fs as below: Chain exists of: &sbi->cp_rwsem --> fs_reclaim --> sb_internal#2 Possible unsafe locking scenario: CPU0 CPU1 ---- ---- rlock(sb_internal#2); lock(fs_reclaim); lock(sb_internal#2); rlock(&sbi->cp_rwsem); *** DEADLOCK *** 3 locks held by kswapd0/73: #0: ffffffff8e247a40 (fs_reclaim){+.+.}-{0:0}, at: balance_pgdat mm/vmscan.c:7015 [inline] #0: ffffffff8e247a40 (fs_reclaim){+.+.}-{0:0}, at: kswapd+0x951/0x2800 mm/vmscan.c:7389 #1: ffff8880118400e0 (&type->s_umount_key#50){.+.+}-{4:4}, at: super_trylock_shared fs/super.c:562 [inline] #1: ffff8880118400e0 (&type->s_umount_key#50){.+.+}-{4:4}, at: super_cache_scan+0x91/0x4b0 fs/super.c:197 #2: ffff888011840610 (sb_internal#2){.+.+}-{0:0}, at: f2fs_evict_inode+0x8d9/0x1b60 fs/f2fs/inode.c:890 stack backtrace: CPU: 0 UID: 0 PID: 73 Comm: kswapd0 Not tainted syzkaller #0 PREEMPT(full) Hardware name: QEMU Standard PC (Q35 + ICH9, 2009), BIOS 1.16.3-debian-1.16.3-2~bpo12+1 04/01/2014 Call Trace: dump_stack_lvl+0x189/0x250 lib/dump_stack.c:120 print_circular_bug+0x2ee/0x310 kernel/locking/lockdep.c:2043 check_noncircular+0x134/0x160 kernel/locking/lockdep.c:2175 check_prev_add kernel/locking/lockdep.c:3165 [inline] check_prevs_add kernel/locking/lockdep.c:3284 [inline] validate_chain+0xb9b/0x2140 kernel/locking/lockdep.c:3908 __lock_acquire+0xab9/0xd20 kernel/locking/lockdep.c:5237 lock_acquire+0x120/0x360 kernel/locking/lockdep.c:5868 down_read+0x46/0x2e0 kernel/locking/rwsem.c:1537 f2fs_down_read fs/f2fs/f2fs.h:2278 [inline] f2fs_lock_op fs/f2fs/f2fs.h:2357 [inline] f2fs_do_truncate_blocks+0x21c/0x10c0 fs/f2fs/file.c:791 f2fs_truncate_blocks+0x10a/0x300 fs/f2fs/file.c:867 f2fs_truncate+0x489/0x7c0 fs/f2fs/file.c:925 f2fs_evict_inode+0x9f2/0x1b60 fs/f2fs/inode.c:897 evict+0x504/0x9c0 fs/inode.c:810 f2fs_evict_inode+0x1dc/0x1b60 fs/f2fs/inode.c:853 evict+0x504/0x9c0 fs/inode.c:810 dispose_list fs/inode.c:852 [inline] prune_icache_sb+0x21b/0x2c0 fs/inode.c:1000 super_cache_scan+0x39b/0x4b0 fs/super.c:224 do_shrink_slab+0x6ef/0x1110 mm/shrinker.c:437 shrink_slab_memcg mm/shrinker.c:550 [inline] shrink_slab+0x7ef/0x10d0 mm/shrinker.c:628 shrink_one+0x28a/0x7c0 mm/vmscan.c:4955 shrink_many mm/vmscan.c:5016 [inline] lru_gen_shrink_node mm/vmscan.c:5094 [inline] shrink_node+0x315d/0x3780 mm/vmscan.c:6081 kswapd_shrink_node mm/vmscan.c:6941 [inline] balance_pgdat mm/vmscan.c:7124 [inline] kswapd+0x147c/0x2800 mm/vmscan.c:7389 kthread+0x70e/0x8a0 kernel/kthread.c:463 ret_from_fork+0x4bc/0x870 arch/x86/kernel/process.c:158 ret_from_fork_asm+0x1a/0x30 arch/x86/entry/entry_64.S:245 The root cause is deadlock among four locks as below: kswapd - fs_reclaim --- Lock A - shrink_one - evict - f2fs_evict_inode - sb_start_intwrite --- Lock B - iput - evict - f2fs_evict_inode - sb_start_intwrite --- Lock B - f2fs_truncate - f2fs_truncate_blocks - f2fs_do_truncate_blocks - f2fs_lock_op --- Lock C ioctl - f2fs_ioc_commit_atomic_write - f2fs_lock_op --- Lock C - __f2fs_commit_atomic_write - __replace_atomic_write_block - f2fs_get_dnode_of_data - __get_node_folio - f2fs_check_nid_range - f2fs_handle_error - f2fs_record_errors - f2fs_down_write --- Lock D open - do_open - do_truncate - security_inode_need_killpriv - f2fs_getxattr - lookup_all_xattrs - f2fs_handle_error - f2fs_record_errors - f2fs_down_write --- Lock D - f2fs_commit_super - read_mapping_folio - filemap_alloc_folio_noprof - prepare_alloc_pages - fs_reclaim_acquire --- Lock A In order to avoid such deadlock, we need to avoid grabbing sb_lock in f2fs_handle_error(), so, let's use asynchronous method instead: - remove f2fs_handle_error() implementation - rename f2fs_handle_error_async() to f2fs_handle_error() - spread f2fs_handle_error() Fixes: 95fa90c9e5a7 ("f2fs: support recording errors into superblock") Cc: stable@kernel.org Reported-by: syzbot+14b90e1156b9f6fc1266@syzkaller.appspotmail.com Closes: https://lore.kernel.org/linux-f2fs-devel/68eae49b.050a0220.ac43.0001.GAE@google.com Reported-by: Jiaming Zhang Closes: https://lore.kernel.org/lkml/CANypQFa-Gy9sD-N35o3PC+FystOWkNuN8pv6S75HLT0ga-Tzgw@mail.gmail.com Signed-off-by: Chao Yu Signed-off-by: Jaegeuk Kim Signed-off-by: Greg Kroah-Hartman --- fs/f2fs/compress.c | 5 +---- fs/f2fs/f2fs.h | 1 - fs/f2fs/super.c | 41 ----------------------------------------- 3 files changed, 1 insertion(+), 46 deletions(-) diff --git a/fs/f2fs/compress.c b/fs/f2fs/compress.c index 6ad8d3bc6df7a8..811bfe38e5c09a 100644 --- a/fs/f2fs/compress.c +++ b/fs/f2fs/compress.c @@ -759,10 +759,7 @@ void f2fs_decompress_cluster(struct decompress_io_ctx *dic, bool in_task) ret = -EFSCORRUPTED; /* Avoid f2fs_commit_super in irq context */ - if (!in_task) - f2fs_handle_error_async(sbi, ERROR_FAIL_DECOMPRESSION); - else - f2fs_handle_error(sbi, ERROR_FAIL_DECOMPRESSION); + f2fs_handle_error(sbi, ERROR_FAIL_DECOMPRESSION); goto out_release; } diff --git a/fs/f2fs/f2fs.h b/fs/f2fs/f2fs.h index 2ff617820f13a1..a10c62f031626d 100644 --- a/fs/f2fs/f2fs.h +++ b/fs/f2fs/f2fs.h @@ -3812,7 +3812,6 @@ void f2fs_quota_off_umount(struct super_block *sb); void f2fs_save_errors(struct f2fs_sb_info *sbi, unsigned char flag); void f2fs_handle_critical_error(struct f2fs_sb_info *sbi, unsigned char reason); void f2fs_handle_error(struct f2fs_sb_info *sbi, unsigned char error); -void f2fs_handle_error_async(struct f2fs_sb_info *sbi, unsigned char error); int f2fs_commit_super(struct f2fs_sb_info *sbi, bool recover); int f2fs_sync_fs(struct super_block *sb, int sync); int f2fs_sanity_check_ckpt(struct f2fs_sb_info *sbi); diff --git a/fs/f2fs/super.c b/fs/f2fs/super.c index 10f09c85b46281..679dc4aef7466b 100644 --- a/fs/f2fs/super.c +++ b/fs/f2fs/super.c @@ -4560,48 +4560,7 @@ void f2fs_save_errors(struct f2fs_sb_info *sbi, unsigned char flag) spin_unlock_irqrestore(&sbi->error_lock, flags); } -static bool f2fs_update_errors(struct f2fs_sb_info *sbi) -{ - unsigned long flags; - bool need_update = false; - - spin_lock_irqsave(&sbi->error_lock, flags); - if (sbi->error_dirty) { - memcpy(F2FS_RAW_SUPER(sbi)->s_errors, sbi->errors, - MAX_F2FS_ERRORS); - sbi->error_dirty = false; - need_update = true; - } - spin_unlock_irqrestore(&sbi->error_lock, flags); - - return need_update; -} - -static void f2fs_record_errors(struct f2fs_sb_info *sbi, unsigned char error) -{ - int err; - - f2fs_down_write(&sbi->sb_lock); - - if (!f2fs_update_errors(sbi)) - goto out_unlock; - - err = f2fs_commit_super(sbi, false); - if (err) - f2fs_err_ratelimited(sbi, - "f2fs_commit_super fails to record errors:%u, err:%d", - error, err); -out_unlock: - f2fs_up_write(&sbi->sb_lock); -} - void f2fs_handle_error(struct f2fs_sb_info *sbi, unsigned char error) -{ - f2fs_save_errors(sbi, error); - f2fs_record_errors(sbi, error); -} - -void f2fs_handle_error_async(struct f2fs_sb_info *sbi, unsigned char error) { f2fs_save_errors(sbi, error); From 40409e089b41b5dc3d96f2916a9af291f577f228 Mon Sep 17 00:00:00 2001 From: Chao Yu Date: Mon, 27 Oct 2025 14:35:33 +0800 Subject: [PATCH 1621/3902] f2fs: fix to propagate error from f2fs_enable_checkpoint() commit be112e7449a6e1b54aa9feac618825d154b3a5c7 upstream. In order to let userspace detect such error rather than suffering silent failure. Fixes: 4354994f097d ("f2fs: checkpoint disabling") Cc: stable@kernel.org Signed-off-by: Chao Yu Signed-off-by: Jaegeuk Kim Signed-off-by: Greg Kroah-Hartman --- fs/f2fs/super.c | 26 ++++++++++++++++---------- 1 file changed, 16 insertions(+), 10 deletions(-) diff --git a/fs/f2fs/super.c b/fs/f2fs/super.c index 679dc4aef7466b..d11ea4fd438398 100644 --- a/fs/f2fs/super.c +++ b/fs/f2fs/super.c @@ -2634,10 +2634,11 @@ static int f2fs_disable_checkpoint(struct f2fs_sb_info *sbi) return err; } -static void f2fs_enable_checkpoint(struct f2fs_sb_info *sbi) +static int f2fs_enable_checkpoint(struct f2fs_sb_info *sbi) { unsigned int nr_pages = get_pages(sbi, F2FS_DIRTY_DATA) / 16; long long start, writeback, end; + int ret; f2fs_info(sbi, "f2fs_enable_checkpoint() starts, meta: %lld, node: %lld, data: %lld", get_pages(sbi, F2FS_DIRTY_META), @@ -2671,7 +2672,9 @@ static void f2fs_enable_checkpoint(struct f2fs_sb_info *sbi) set_sbi_flag(sbi, SBI_IS_DIRTY); f2fs_up_write(&sbi->gc_lock); - f2fs_sync_fs(sbi->sb, 1); + ret = f2fs_sync_fs(sbi->sb, 1); + if (ret) + f2fs_err(sbi, "%s sync_fs failed, ret: %d", __func__, ret); /* Let's ensure there's no pending checkpoint anymore */ f2fs_flush_ckpt_thread(sbi); @@ -2681,6 +2684,7 @@ static void f2fs_enable_checkpoint(struct f2fs_sb_info *sbi) f2fs_info(sbi, "f2fs_enable_checkpoint() finishes, writeback:%llu, sync:%llu", ktime_ms_delta(writeback, start), ktime_ms_delta(end, writeback)); + return ret; } static int __f2fs_remount(struct fs_context *fc, struct super_block *sb) @@ -2894,7 +2898,9 @@ static int __f2fs_remount(struct fs_context *fc, struct super_block *sb) goto restore_discard; need_enable_checkpoint = true; } else { - f2fs_enable_checkpoint(sbi); + err = f2fs_enable_checkpoint(sbi); + if (err) + goto restore_discard; need_disable_checkpoint = true; } } @@ -2937,7 +2943,8 @@ static int __f2fs_remount(struct fs_context *fc, struct super_block *sb) return 0; restore_checkpoint: if (need_enable_checkpoint) { - f2fs_enable_checkpoint(sbi); + if (f2fs_enable_checkpoint(sbi)) + f2fs_warn(sbi, "checkpoint has not been enabled"); } else if (need_disable_checkpoint) { if (f2fs_disable_checkpoint(sbi)) f2fs_warn(sbi, "checkpoint has not been disabled"); @@ -5232,13 +5239,12 @@ static int f2fs_fill_super(struct super_block *sb, struct fs_context *fc) if (err) goto sync_free_meta; - if (test_opt(sbi, DISABLE_CHECKPOINT)) { + if (test_opt(sbi, DISABLE_CHECKPOINT)) err = f2fs_disable_checkpoint(sbi); - if (err) - goto sync_free_meta; - } else if (is_set_ckpt_flags(sbi, CP_DISABLED_FLAG)) { - f2fs_enable_checkpoint(sbi); - } + else if (is_set_ckpt_flags(sbi, CP_DISABLED_FLAG)) + err = f2fs_enable_checkpoint(sbi); + if (err) + goto sync_free_meta; /* * If filesystem is not mounted as read-only then From bac23833220a1f8fe8dfab7e16efa20ff64d7589 Mon Sep 17 00:00:00 2001 From: Chao Yu Date: Mon, 20 Oct 2025 10:42:12 +0800 Subject: [PATCH 1622/3902] f2fs: fix to avoid updating zero-sized extent in extent cache commit 7c37c79510329cd951a4dedf3f7bf7e2b18dccec upstream. As syzbot reported: F2FS-fs (loop0): __update_extent_tree_range: extent len is zero, type: 0, extent [0, 0, 0], age [0, 0] ------------[ cut here ]------------ kernel BUG at fs/f2fs/extent_cache.c:678! Oops: invalid opcode: 0000 [#1] SMP KASAN NOPTI CPU: 0 UID: 0 PID: 5336 Comm: syz.0.0 Not tainted syzkaller #0 PREEMPT(full) Hardware name: QEMU Standard PC (Q35 + ICH9, 2009), BIOS 1.16.3-debian-1.16.3-2~bpo12+1 04/01/2014 RIP: 0010:__update_extent_tree_range+0x13bc/0x1500 fs/f2fs/extent_cache.c:678 Call Trace: f2fs_update_read_extent_cache_range+0x192/0x3e0 fs/f2fs/extent_cache.c:1085 f2fs_do_zero_range fs/f2fs/file.c:1657 [inline] f2fs_zero_range+0x10c1/0x1580 fs/f2fs/file.c:1737 f2fs_fallocate+0x583/0x990 fs/f2fs/file.c:2030 vfs_fallocate+0x669/0x7e0 fs/open.c:342 ioctl_preallocate fs/ioctl.c:289 [inline] file_ioctl+0x611/0x780 fs/ioctl.c:-1 do_vfs_ioctl+0xb33/0x1430 fs/ioctl.c:576 __do_sys_ioctl fs/ioctl.c:595 [inline] __se_sys_ioctl+0x82/0x170 fs/ioctl.c:583 do_syscall_x64 arch/x86/entry/syscall_64.c:63 [inline] do_syscall_64+0xfa/0x3b0 arch/x86/entry/syscall_64.c:94 entry_SYSCALL_64_after_hwframe+0x77/0x7f RIP: 0033:0x7f07bc58eec9 In error path of f2fs_zero_range(), it may add a zero-sized extent into extent cache, it should be avoided. Fixes: 6e9619499f53 ("f2fs: support in batch fzero in dnode page") Cc: stable@kernel.org Reported-by: syzbot+24124df3170c3638b35f@syzkaller.appspotmail.com Closes: https://lore.kernel.org/linux-f2fs-devel/68e5d698.050a0220.256323.0032.GAE@google.com Signed-off-by: Chao Yu Signed-off-by: Jaegeuk Kim Signed-off-by: Greg Kroah-Hartman --- fs/f2fs/file.c | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/fs/f2fs/file.c b/fs/f2fs/file.c index c641a0209956e4..6d42e2d28861ce 100644 --- a/fs/f2fs/file.c +++ b/fs/f2fs/file.c @@ -1654,8 +1654,11 @@ static int f2fs_do_zero_range(struct dnode_of_data *dn, pgoff_t start, f2fs_set_data_blkaddr(dn, NEW_ADDR); } - f2fs_update_read_extent_cache_range(dn, start, 0, index - start); - f2fs_update_age_extent_cache_range(dn, start, index - start); + if (index > start) { + f2fs_update_read_extent_cache_range(dn, start, 0, + index - start); + f2fs_update_age_extent_cache_range(dn, start, index - start); + } return ret; } From 0dde30753c1e8648665dbe069d814e540ce2fd37 Mon Sep 17 00:00:00 2001 From: Deepanshu Kartikey Date: Mon, 27 Oct 2025 18:36:34 +0530 Subject: [PATCH 1623/3902] f2fs: invalidate dentry cache on failed whiteout creation MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit commit d33f89b34aa313f50f9a512d58dd288999f246b0 upstream. F2FS can mount filesystems with corrupted directory depth values that get runtime-clamped to MAX_DIR_HASH_DEPTH. When RENAME_WHITEOUT operations are performed on such directories, f2fs_rename performs directory modifications (updating target entry and deleting source entry) before attempting to add the whiteout entry via f2fs_add_link. If f2fs_add_link fails due to the corrupted directory structure, the function returns an error to VFS, but the partial directory modifications have already been committed to disk. VFS assumes the entire rename operation failed and does not update the dentry cache, leaving stale mappings. In the error path, VFS does not call d_move() to update the dentry cache. This results in new_dentry still pointing to the old inode (new_inode) which has already had its i_nlink decremented to zero. The stale cache causes subsequent operations to incorrectly reference the freed inode. This causes subsequent operations to use cached dentry information that no longer matches the on-disk state. When a second rename targets the same entry, VFS attempts to decrement i_nlink on the stale inode, which may already have i_nlink=0, triggering a WARNING in drop_nlink(). Example sequence: 1. First rename (RENAME_WHITEOUT): file2 → file1 - f2fs updates file1 entry on disk (points to inode 8) - f2fs deletes file2 entry on disk - f2fs_add_link(whiteout) fails (corrupted directory) - Returns error to VFS - VFS does not call d_move() due to error - VFS cache still has: file1 → inode 7 (stale!) - inode 7 has i_nlink=0 (already decremented) 2. Second rename: file3 → file1 - VFS uses stale cache: file1 → inode 7 - Tries to drop_nlink on inode 7 (i_nlink already 0) - WARNING in drop_nlink() Fix this by explicitly invalidating old_dentry and new_dentry when f2fs_add_link fails during whiteout creation. This forces VFS to refresh from disk on subsequent operations, ensuring cache consistency even when the rename partially succeeds. Reproducer: 1. Mount F2FS image with corrupted i_current_depth 2. renameat2(file2, file1, RENAME_WHITEOUT) 3. renameat2(file3, file1, 0) 4. System triggers WARNING in drop_nlink() Fixes: 7e01e7ad746b ("f2fs: support RENAME_WHITEOUT") Reported-by: syzbot+632cf32276a9a564188d@syzkaller.appspotmail.com Closes: https://syzkaller.appspot.com/bug?extid=632cf32276a9a564188d Suggested-by: Chao Yu Link: https://lore.kernel.org/all/20251022233349.102728-1-kartikey406@gmail.com/ [v1] Cc: stable@vger.kernel.org Signed-off-by: Deepanshu Kartikey Reviewed-by: Chao Yu Signed-off-by: Jaegeuk Kim Signed-off-by: Greg Kroah-Hartman --- fs/f2fs/namei.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/fs/f2fs/namei.c b/fs/f2fs/namei.c index b882771e469971..712479b7b93d78 100644 --- a/fs/f2fs/namei.c +++ b/fs/f2fs/namei.c @@ -1053,9 +1053,11 @@ static int f2fs_rename(struct mnt_idmap *idmap, struct inode *old_dir, if (whiteout) { set_inode_flag(whiteout, FI_INC_LINK); err = f2fs_add_link(old_dentry, whiteout); - if (err) + if (err) { + d_invalidate(old_dentry); + d_invalidate(new_dentry); goto put_out_dir; - + } spin_lock(&whiteout->i_lock); whiteout->i_state &= ~I_LINKABLE; spin_unlock(&whiteout->i_lock); From e6d828eae00ec192e18c2ddaa2fd32050a96048a Mon Sep 17 00:00:00 2001 From: Chao Yu Date: Tue, 21 Oct 2025 11:48:56 +0800 Subject: [PATCH 1624/3902] f2fs: use global inline_xattr_slab instead of per-sb slab cache MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit commit 1f27ef42bb0b7c0740c5616ec577ec188b8a1d05 upstream. As Hong Yun reported in mailing list: loop7: detected capacity change from 0 to 131072 ------------[ cut here ]------------ kmem_cache of name 'f2fs_xattr_entry-7:7' already exists WARNING: CPU: 0 PID: 24426 at mm/slab_common.c:110 kmem_cache_sanity_check mm/slab_common.c:109 [inline] WARNING: CPU: 0 PID: 24426 at mm/slab_common.c:110 __kmem_cache_create_args+0xa6/0x320 mm/slab_common.c:307 CPU: 0 UID: 0 PID: 24426 Comm: syz.7.1370 Not tainted 6.17.0-rc4 #1 PREEMPT(full) Hardware name: QEMU Standard PC (i440FX + PIIX, 1996), BIOS 1.13.0-1ubuntu1.1 04/01/2014 RIP: 0010:kmem_cache_sanity_check mm/slab_common.c:109 [inline] RIP: 0010:__kmem_cache_create_args+0xa6/0x320 mm/slab_common.c:307 Call Trace:  __kmem_cache_create include/linux/slab.h:353 [inline]  f2fs_kmem_cache_create fs/f2fs/f2fs.h:2943 [inline]  f2fs_init_xattr_caches+0xa5/0xe0 fs/f2fs/xattr.c:843  f2fs_fill_super+0x1645/0x2620 fs/f2fs/super.c:4918  get_tree_bdev_flags+0x1fb/0x260 fs/super.c:1692  vfs_get_tree+0x43/0x140 fs/super.c:1815  do_new_mount+0x201/0x550 fs/namespace.c:3808  do_mount fs/namespace.c:4136 [inline]  __do_sys_mount fs/namespace.c:4347 [inline]  __se_sys_mount+0x298/0x2f0 fs/namespace.c:4324  do_syscall_x64 arch/x86/entry/syscall_64.c:63 [inline]  do_syscall_64+0x8e/0x3a0 arch/x86/entry/syscall_64.c:94  entry_SYSCALL_64_after_hwframe+0x76/0x7e The bug can be reproduced w/ below scripts: - mount /dev/vdb /mnt1 - mount /dev/vdc /mnt2 - umount /mnt1 - mounnt /dev/vdb /mnt1 The reason is if we created two slab caches, named f2fs_xattr_entry-7:3 and f2fs_xattr_entry-7:7, and they have the same slab size. Actually, slab system will only create one slab cache core structure which has slab name of "f2fs_xattr_entry-7:3", and two slab caches share the same structure and cache address. So, if we destroy f2fs_xattr_entry-7:3 cache w/ cache address, it will decrease reference count of slab cache, rather than release slab cache entirely, since there is one more user has referenced the cache. Then, if we try to create slab cache w/ name "f2fs_xattr_entry-7:3" again, slab system will find that there is existed cache which has the same name and trigger the warning. Let's changes to use global inline_xattr_slab instead of per-sb slab cache for fixing. Fixes: a999150f4fe3 ("f2fs: use kmem_cache pool during inline xattr lookups") Cc: stable@kernel.org Reported-by: Hong Yun Tested-by: Hong Yun Signed-off-by: Chao Yu Signed-off-by: Jaegeuk Kim Signed-off-by: Greg Kroah-Hartman --- fs/f2fs/f2fs.h | 3 --- fs/f2fs/super.c | 17 ++++++++--------- fs/f2fs/xattr.c | 32 +++++++++++--------------------- fs/f2fs/xattr.h | 10 ++++++---- 4 files changed, 25 insertions(+), 37 deletions(-) diff --git a/fs/f2fs/f2fs.h b/fs/f2fs/f2fs.h index a10c62f031626d..6c9c0e3857789e 100644 --- a/fs/f2fs/f2fs.h +++ b/fs/f2fs/f2fs.h @@ -1886,9 +1886,6 @@ struct f2fs_sb_info { spinlock_t error_lock; /* protect errors/stop_reason array */ bool error_dirty; /* errors of sb is dirty */ - struct kmem_cache *inline_xattr_slab; /* inline xattr entry */ - unsigned int inline_xattr_slab_size; /* default inline xattr slab size */ - /* For reclaimed segs statistics per each GC mode */ unsigned int gc_segment_mode; /* GC state for reclaimed segments */ unsigned int gc_reclaimed_segs[MAX_GC_MODE]; /* Reclaimed segs for each mode */ diff --git a/fs/f2fs/super.c b/fs/f2fs/super.c index d11ea4fd438398..41b38e557046e0 100644 --- a/fs/f2fs/super.c +++ b/fs/f2fs/super.c @@ -2028,7 +2028,6 @@ static void f2fs_put_super(struct super_block *sb) kfree(sbi->raw_super); f2fs_destroy_page_array_cache(sbi); - f2fs_destroy_xattr_caches(sbi); #ifdef CONFIG_QUOTA for (i = 0; i < MAXQUOTAS; i++) kfree(F2FS_OPTION(sbi).s_qf_names[i]); @@ -4997,13 +4996,9 @@ static int f2fs_fill_super(struct super_block *sb, struct fs_context *fc) if (err) goto free_iostat; - /* init per sbi slab cache */ - err = f2fs_init_xattr_caches(sbi); - if (err) - goto free_percpu; err = f2fs_init_page_array_cache(sbi); if (err) - goto free_xattr_cache; + goto free_percpu; /* get an inode for meta space */ sbi->meta_inode = f2fs_iget(sb, F2FS_META_INO(sbi)); @@ -5331,8 +5326,6 @@ static int f2fs_fill_super(struct super_block *sb, struct fs_context *fc) sbi->meta_inode = NULL; free_page_array_cache: f2fs_destroy_page_array_cache(sbi); -free_xattr_cache: - f2fs_destroy_xattr_caches(sbi); free_percpu: destroy_percpu_info(sbi); free_iostat: @@ -5535,10 +5528,15 @@ static int __init init_f2fs_fs(void) err = f2fs_create_casefold_cache(); if (err) goto free_compress_cache; - err = register_filesystem(&f2fs_fs_type); + err = f2fs_init_xattr_cache(); if (err) goto free_casefold_cache; + err = register_filesystem(&f2fs_fs_type); + if (err) + goto free_xattr_cache; return 0; +free_xattr_cache: + f2fs_destroy_xattr_cache(); free_casefold_cache: f2fs_destroy_casefold_cache(); free_compress_cache: @@ -5579,6 +5577,7 @@ static int __init init_f2fs_fs(void) static void __exit exit_f2fs_fs(void) { unregister_filesystem(&f2fs_fs_type); + f2fs_destroy_xattr_cache(); f2fs_destroy_casefold_cache(); f2fs_destroy_compress_cache(); f2fs_destroy_compress_mempool(); diff --git a/fs/f2fs/xattr.c b/fs/f2fs/xattr.c index 58632a2b66136c..b4e5c406632f81 100644 --- a/fs/f2fs/xattr.c +++ b/fs/f2fs/xattr.c @@ -23,11 +23,12 @@ #include "xattr.h" #include "segment.h" +static struct kmem_cache *inline_xattr_slab; static void *xattr_alloc(struct f2fs_sb_info *sbi, int size, bool *is_inline) { - if (likely(size == sbi->inline_xattr_slab_size)) { + if (likely(size == DEFAULT_XATTR_SLAB_SIZE)) { *is_inline = true; - return f2fs_kmem_cache_alloc(sbi->inline_xattr_slab, + return f2fs_kmem_cache_alloc(inline_xattr_slab, GFP_F2FS_ZERO, false, sbi); } *is_inline = false; @@ -38,7 +39,7 @@ static void xattr_free(struct f2fs_sb_info *sbi, void *xattr_addr, bool is_inline) { if (is_inline) - kmem_cache_free(sbi->inline_xattr_slab, xattr_addr); + kmem_cache_free(inline_xattr_slab, xattr_addr); else kfree(xattr_addr); } @@ -830,25 +831,14 @@ int f2fs_setxattr(struct inode *inode, int index, const char *name, return err; } -int f2fs_init_xattr_caches(struct f2fs_sb_info *sbi) +int __init f2fs_init_xattr_cache(void) { - dev_t dev = sbi->sb->s_bdev->bd_dev; - char slab_name[32]; - - sprintf(slab_name, "f2fs_xattr_entry-%u:%u", MAJOR(dev), MINOR(dev)); - - sbi->inline_xattr_slab_size = F2FS_OPTION(sbi).inline_xattr_size * - sizeof(__le32) + XATTR_PADDING_SIZE; - - sbi->inline_xattr_slab = f2fs_kmem_cache_create(slab_name, - sbi->inline_xattr_slab_size); - if (!sbi->inline_xattr_slab) - return -ENOMEM; - - return 0; + inline_xattr_slab = f2fs_kmem_cache_create("f2fs_xattr_entry", + DEFAULT_XATTR_SLAB_SIZE); + return inline_xattr_slab ? 0 : -ENOMEM; } -void f2fs_destroy_xattr_caches(struct f2fs_sb_info *sbi) +void f2fs_destroy_xattr_cache(void) { - kmem_cache_destroy(sbi->inline_xattr_slab); -} + kmem_cache_destroy(inline_xattr_slab); +} \ No newline at end of file diff --git a/fs/f2fs/xattr.h b/fs/f2fs/xattr.h index 4fc0b2305fbd80..bce3d93e4755b5 100644 --- a/fs/f2fs/xattr.h +++ b/fs/f2fs/xattr.h @@ -89,6 +89,8 @@ struct f2fs_xattr_entry { F2FS_TOTAL_EXTRA_ATTR_SIZE / sizeof(__le32) - \ DEF_INLINE_RESERVED_SIZE - \ MIN_INLINE_DENTRY_SIZE / sizeof(__le32)) +#define DEFAULT_XATTR_SLAB_SIZE (DEFAULT_INLINE_XATTR_ADDRS * \ + sizeof(__le32) + XATTR_PADDING_SIZE) /* * On-disk structure of f2fs_xattr @@ -132,8 +134,8 @@ int f2fs_setxattr(struct inode *, int, const char *, const void *, int f2fs_getxattr(struct inode *, int, const char *, void *, size_t, struct folio *); ssize_t f2fs_listxattr(struct dentry *, char *, size_t); -int f2fs_init_xattr_caches(struct f2fs_sb_info *); -void f2fs_destroy_xattr_caches(struct f2fs_sb_info *); +int __init f2fs_init_xattr_cache(void); +void f2fs_destroy_xattr_cache(void); #else #define f2fs_xattr_handlers NULL @@ -150,8 +152,8 @@ static inline int f2fs_getxattr(struct inode *inode, int index, { return -EOPNOTSUPP; } -static inline int f2fs_init_xattr_caches(struct f2fs_sb_info *sbi) { return 0; } -static inline void f2fs_destroy_xattr_caches(struct f2fs_sb_info *sbi) { } +static inline int __init f2fs_init_xattr_cache(void) { return 0; } +static inline void f2fs_destroy_xattr_cache(void) { } #endif #ifdef CONFIG_F2FS_FS_SECURITY From b355d300f51b0f67552cec6ea2709ba54738d285 Mon Sep 17 00:00:00 2001 From: Xiaole He Date: Mon, 27 Oct 2025 17:23:41 +0800 Subject: [PATCH 1625/3902] f2fs: fix age extent cache insertion skip on counter overflow commit 27bf6a637b7613fc85fa6af468b7d612d78cd5c0 upstream. The age extent cache uses last_blocks (derived from allocated_data_blocks) to determine data age. However, there's a conflict between the deletion marker (last_blocks=0) and legitimate last_blocks=0 cases when allocated_data_blocks overflows to 0 after reaching ULLONG_MAX. In this case, valid extents are incorrectly skipped due to the "if (!tei->last_blocks)" check in __update_extent_tree_range(). This patch fixes the issue by: 1. Reserving ULLONG_MAX as an invalid/deletion marker 2. Limiting allocated_data_blocks to range [0, ULLONG_MAX-1] 3. Using F2FS_EXTENT_AGE_INVALID for deletion scenarios 4. Adjusting overflow age calculation from ULLONG_MAX to (ULLONG_MAX-1) Reproducer (using a patched kernel with allocated_data_blocks initialized to ULLONG_MAX - 3 for quick testing): Step 1: Mount and check initial state # dd if=/dev/zero of=/tmp/test.img bs=1M count=100 # mkfs.f2fs -f /tmp/test.img # mkdir -p /mnt/f2fs_test # mount -t f2fs -o loop,age_extent_cache /tmp/test.img /mnt/f2fs_test # cat /sys/kernel/debug/f2fs/status | grep -A 4 "Block Age" Allocated Data Blocks: 18446744073709551612 # ULLONG_MAX - 3 Inner Struct Count: tree: 1(0), node: 0 Step 2: Create files and write data to trigger overflow # touch /mnt/f2fs_test/{1,2,3,4}.txt; sync # cat /sys/kernel/debug/f2fs/status | grep -A 4 "Block Age" Allocated Data Blocks: 18446744073709551613 # ULLONG_MAX - 2 Inner Struct Count: tree: 5(0), node: 1 # dd if=/dev/urandom of=/mnt/f2fs_test/1.txt bs=4K count=1; sync # cat /sys/kernel/debug/f2fs/status | grep -A 4 "Block Age" Allocated Data Blocks: 18446744073709551614 # ULLONG_MAX - 1 Inner Struct Count: tree: 5(0), node: 2 # dd if=/dev/urandom of=/mnt/f2fs_test/2.txt bs=4K count=1; sync # cat /sys/kernel/debug/f2fs/status | grep -A 4 "Block Age" Allocated Data Blocks: 18446744073709551615 # ULLONG_MAX Inner Struct Count: tree: 5(0), node: 3 # dd if=/dev/urandom of=/mnt/f2fs_test/3.txt bs=4K count=1; sync # cat /sys/kernel/debug/f2fs/status | grep -A 4 "Block Age" Allocated Data Blocks: 0 # Counter overflowed! Inner Struct Count: tree: 5(0), node: 4 Step 3: Trigger the bug - next write should create node but gets skipped # dd if=/dev/urandom of=/mnt/f2fs_test/4.txt bs=4K count=1; sync # cat /sys/kernel/debug/f2fs/status | grep -A 4 "Block Age" Allocated Data Blocks: 1 Inner Struct Count: tree: 5(0), node: 4 Expected: node: 5 (new extent node for 4.txt) Actual: node: 4 (extent insertion was incorrectly skipped due to last_blocks = allocated_data_blocks = 0 in __get_new_block_age) After this fix, the extent node is correctly inserted and node count becomes 5 as expected. Fixes: 71644dff4811 ("f2fs: add block_age-based extent cache") Cc: stable@kernel.org Signed-off-by: Xiaole He Reviewed-by: Chao Yu Signed-off-by: Jaegeuk Kim Signed-off-by: Greg Kroah-Hartman --- fs/f2fs/extent_cache.c | 5 +++-- fs/f2fs/f2fs.h | 6 ++++++ fs/f2fs/segment.c | 9 +++++++-- 3 files changed, 16 insertions(+), 4 deletions(-) diff --git a/fs/f2fs/extent_cache.c b/fs/f2fs/extent_cache.c index 33e09c453c7033..0ed84cc065a7ed 100644 --- a/fs/f2fs/extent_cache.c +++ b/fs/f2fs/extent_cache.c @@ -808,7 +808,7 @@ static void __update_extent_tree_range(struct inode *inode, } goto out_read_extent_cache; update_age_extent_cache: - if (!tei->last_blocks) + if (tei->last_blocks == F2FS_EXTENT_AGE_INVALID) goto out_read_extent_cache; __set_extent_info(&ei, fofs, len, 0, false, @@ -912,7 +912,7 @@ static int __get_new_block_age(struct inode *inode, struct extent_info *ei, cur_age = cur_blocks - tei.last_blocks; else /* allocated_data_blocks overflow */ - cur_age = ULLONG_MAX - tei.last_blocks + cur_blocks; + cur_age = (ULLONG_MAX - 1) - tei.last_blocks + cur_blocks; if (tei.age) ei->age = __calculate_block_age(sbi, cur_age, tei.age); @@ -1114,6 +1114,7 @@ void f2fs_update_age_extent_cache_range(struct dnode_of_data *dn, struct extent_info ei = { .fofs = fofs, .len = len, + .last_blocks = F2FS_EXTENT_AGE_INVALID, }; if (!__may_extent_tree(dn->inode, EX_BLOCK_AGE)) diff --git a/fs/f2fs/f2fs.h b/fs/f2fs/f2fs.h index 6c9c0e3857789e..37ad0c27c5b480 100644 --- a/fs/f2fs/f2fs.h +++ b/fs/f2fs/f2fs.h @@ -708,6 +708,12 @@ enum extent_type { NR_EXTENT_CACHES, }; +/* + * Reserved value to mark invalid age extents, hence valid block range + * from 0 to ULLONG_MAX-1 + */ +#define F2FS_EXTENT_AGE_INVALID ULLONG_MAX + struct extent_info { unsigned int fofs; /* start offset in a file */ unsigned int len; /* length of the extent */ diff --git a/fs/f2fs/segment.c b/fs/f2fs/segment.c index ac84559dc26931..10d873d1b328cd 100644 --- a/fs/f2fs/segment.c +++ b/fs/f2fs/segment.c @@ -3881,8 +3881,13 @@ int f2fs_allocate_data_block(struct f2fs_sb_info *sbi, struct folio *folio, locate_dirty_segment(sbi, GET_SEGNO(sbi, old_blkaddr)); locate_dirty_segment(sbi, GET_SEGNO(sbi, *new_blkaddr)); - if (IS_DATASEG(curseg->seg_type)) - atomic64_inc(&sbi->allocated_data_blocks); + if (IS_DATASEG(curseg->seg_type)) { + unsigned long long new_val; + + new_val = atomic64_inc_return(&sbi->allocated_data_blocks); + if (unlikely(new_val == ULLONG_MAX)) + atomic64_set(&sbi->allocated_data_blocks, 0); + } up_write(&sit_i->sentry_lock); From 57e36f84f2b5bf1535452d277f825784960a649e Mon Sep 17 00:00:00 2001 From: Xiaole He Date: Wed, 29 Oct 2025 13:18:07 +0800 Subject: [PATCH 1626/3902] f2fs: fix uninitialized one_time_gc in victim_sel_policy commit 392711ef18bff524a873b9c239a73148c5432262 upstream. The one_time_gc field in struct victim_sel_policy is conditionally initialized but unconditionally read, leading to undefined behavior that triggers UBSAN warnings. In f2fs_get_victim() at fs/f2fs/gc.c:774, the victim_sel_policy structure is declared without initialization: struct victim_sel_policy p; The field p.one_time_gc is only assigned when the 'one_time' parameter is true (line 789): if (one_time) { p.one_time_gc = one_time; ... } However, this field is unconditionally read in subsequent get_gc_cost() at line 395: if (p->one_time_gc && (valid_thresh_ratio < 100) && ...) When one_time is false, p.one_time_gc contains uninitialized stack memory. Hence p.one_time_gc is an invalid bool value. UBSAN detects this invalid bool value: UBSAN: invalid-load in fs/f2fs/gc.c:395:7 load of value 77 is not a valid value for type '_Bool' CPU: 3 UID: 0 PID: 1297 Comm: f2fs_gc-252:16 Not tainted 6.18.0-rc3 #5 PREEMPT(voluntary) Hardware name: OpenStack Foundation OpenStack Nova, BIOS 1.13.0-1ubuntu1.1 04/01/2014 Call Trace: dump_stack_lvl+0x70/0x90 dump_stack+0x14/0x20 __ubsan_handle_load_invalid_value+0xb3/0xf0 ? dl_server_update+0x2e/0x40 ? update_curr+0x147/0x170 f2fs_get_victim.cold+0x66/0x134 [f2fs] ? sched_balance_newidle+0x2ca/0x470 ? finish_task_switch.isra.0+0x8d/0x2a0 f2fs_gc+0x2ba/0x8e0 [f2fs] ? _raw_spin_unlock_irqrestore+0x12/0x40 ? __timer_delete_sync+0x80/0xe0 ? timer_delete_sync+0x14/0x20 ? schedule_timeout+0x82/0x100 gc_thread_func+0x38b/0x860 [f2fs] ? gc_thread_func+0x38b/0x860 [f2fs] ? __pfx_autoremove_wake_function+0x10/0x10 kthread+0x10b/0x220 ? __pfx_gc_thread_func+0x10/0x10 [f2fs] ? _raw_spin_unlock_irq+0x12/0x40 ? __pfx_kthread+0x10/0x10 ret_from_fork+0x11a/0x160 ? __pfx_kthread+0x10/0x10 ret_from_fork_asm+0x1a/0x30 This issue is reliably reproducible with the following steps on a 100GB SSD /dev/vdb: mkfs.f2fs -f /dev/vdb mount /dev/vdb /mnt/f2fs_test fio --name=gc --directory=/mnt/f2fs_test --rw=randwrite \ --bs=4k --size=8G --numjobs=12 --fsync=4 --runtime=10 \ --time_based echo 1 > /sys/fs/f2fs/vdb/gc_urgent The uninitialized value causes incorrect GC victim selection, leading to unpredictable garbage collection behavior. Fix by zero-initializing the entire victim_sel_policy structure to ensure all fields have defined values. Fixes: e791d00bd06c ("f2fs: add valid block ratio not to do excessive GC for one time GC") Cc: stable@kernel.org Signed-off-by: Xiaole He Reviewed-by: Chao Yu Signed-off-by: Jaegeuk Kim Signed-off-by: Greg Kroah-Hartman --- fs/f2fs/gc.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fs/f2fs/gc.c b/fs/f2fs/gc.c index af2f4d28462c0b..5f90cca64c7aa2 100644 --- a/fs/f2fs/gc.c +++ b/fs/f2fs/gc.c @@ -774,7 +774,7 @@ int f2fs_get_victim(struct f2fs_sb_info *sbi, unsigned int *result, { struct dirty_seglist_info *dirty_i = DIRTY_I(sbi); struct sit_info *sm = SIT_I(sbi); - struct victim_sel_policy p; + struct victim_sel_policy p = {0}; unsigned int secno, last_victim; unsigned int last_segment; unsigned int nsearched; From 2efb6b9c5bd27ff11abfc80e3b4ec59f3335c9d4 Mon Sep 17 00:00:00 2001 From: Chao Yu Date: Wed, 5 Nov 2025 14:50:23 +0800 Subject: [PATCH 1627/3902] f2fs: fix to detect recoverable inode during dryrun of find_fsync_dnodes() commit 68d05693f8c031257a0822464366e1c2a239a512 upstream. mkfs.f2fs -f /dev/vdd mount /dev/vdd /mnt/f2fs touch /mnt/f2fs/foo sync # avoid CP_UMOUNT_FLAG in last f2fs_checkpoint.ckpt_flags touch /mnt/f2fs/bar f2fs_io fsync /mnt/f2fs/bar f2fs_io shutdown 2 /mnt/f2fs umount /mnt/f2fs blockdev --setro /dev/vdd mount /dev/vdd /mnt/f2fs mount: /mnt/f2fs: WARNING: source write-protected, mounted read-only. For the case if we create and fsync a new inode before sudden power-cut, without norecovery or disable_roll_forward mount option, the following mount will succeed w/o recovering last fsynced inode. The problem here is that we only check inode_list list after find_fsync_dnodes() in f2fs_recover_fsync_data() to find out whether there is recoverable data in the iamge, but there is a missed case, if last fsynced inode is not existing in last checkpoint, then, we will fail to get its inode due to nat of inode node is not existing in last checkpoint, so the inode won't be linked in inode_list. Let's detect such case in dyrun mode to fix this issue. After this change, mount will fail as expected below: mount: /mnt/f2fs: cannot mount /dev/vdd read-only. dmesg(1) may have more information after failed mount system call. demsg: F2FS-fs (vdd): Need to recover fsync data, but write access unavailable, please try mount w/ disable_roll_forward or norecovery Cc: stable@kernel.org Fixes: 6781eabba1bd ("f2fs: give -EINVAL for norecovery and rw mount") Signed-off-by: Chao Yu Signed-off-by: Jaegeuk Kim Signed-off-by: Greg Kroah-Hartman --- fs/f2fs/recovery.c | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/fs/f2fs/recovery.c b/fs/f2fs/recovery.c index af72309b9bfc6c..62a0c71b5b75d6 100644 --- a/fs/f2fs/recovery.c +++ b/fs/f2fs/recovery.c @@ -399,7 +399,7 @@ static int sanity_check_node_chain(struct f2fs_sb_info *sbi, block_t blkaddr, } static int find_fsync_dnodes(struct f2fs_sb_info *sbi, struct list_head *head, - bool check_only) + bool check_only, bool *new_inode) { struct curseg_info *curseg; block_t blkaddr, blkaddr_fast; @@ -447,16 +447,19 @@ static int find_fsync_dnodes(struct f2fs_sb_info *sbi, struct list_head *head, quota_inode = true; } - /* - * CP | dnode(F) | inode(DF) - * For this case, we should not give up now. - */ entry = add_fsync_inode(sbi, head, ino_of_node(folio), quota_inode); if (IS_ERR(entry)) { err = PTR_ERR(entry); - if (err == -ENOENT) + /* + * CP | dnode(F) | inode(DF) + * For this case, we should not give up now. + */ + if (err == -ENOENT) { + if (check_only) + *new_inode = true; goto next; + } f2fs_folio_put(folio, true); break; } @@ -875,6 +878,7 @@ int f2fs_recover_fsync_data(struct f2fs_sb_info *sbi, bool check_only) int ret = 0; unsigned long s_flags = sbi->sb->s_flags; bool need_writecp = false; + bool new_inode = false; f2fs_notice(sbi, "f2fs_recover_fsync_data: recovery fsync data, " "check_only: %d", check_only); @@ -890,8 +894,8 @@ int f2fs_recover_fsync_data(struct f2fs_sb_info *sbi, bool check_only) f2fs_down_write(&sbi->cp_global_sem); /* step #1: find fsynced inode numbers */ - err = find_fsync_dnodes(sbi, &inode_list, check_only); - if (err || list_empty(&inode_list)) + err = find_fsync_dnodes(sbi, &inode_list, check_only, &new_inode); + if (err < 0 || (list_empty(&inode_list) && (!check_only || !new_inode))) goto skip; if (check_only) { From 51659b91898f2bc4441d597042223e1b644237fc Mon Sep 17 00:00:00 2001 From: Chao Yu Date: Fri, 28 Nov 2025 17:25:07 +0800 Subject: [PATCH 1628/3902] f2fs: fix to not account invalid blocks in get_left_section_blocks() commit 37345eae9deaa2e4f372eeb98f6594cd0ee0916e upstream. w/ LFS mode, in get_left_section_blocks(), we should not account the blocks which were used before and now are invalided, otherwise those blocks will be counted as freed one in has_curseg_enough_space(), result in missing to trigger GC in time. Cc: stable@kernel.org Fixes: 249ad438e1d9 ("f2fs: add a method for calculating the remaining blocks in the current segment in LFS mode.") Fixes: bf34c93d2645 ("f2fs: check curseg space before foreground GC") Signed-off-by: Chao Yu Signed-off-by: Jaegeuk Kim Signed-off-by: Greg Kroah-Hartman --- fs/f2fs/segment.h | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/fs/f2fs/segment.h b/fs/f2fs/segment.h index e883f14c228f26..f3e2fff45cf532 100644 --- a/fs/f2fs/segment.h +++ b/fs/f2fs/segment.h @@ -607,10 +607,12 @@ static inline int reserved_sections(struct f2fs_sb_info *sbi) static inline unsigned int get_left_section_blocks(struct f2fs_sb_info *sbi, enum log_type type, unsigned int segno) { - if (f2fs_lfs_mode(sbi) && __is_large_section(sbi)) - return CAP_BLKS_PER_SEC(sbi) - SEGS_TO_BLKS(sbi, - (segno - GET_START_SEG_FROM_SEC(sbi, segno))) - + if (f2fs_lfs_mode(sbi)) { + unsigned int used_blocks = __is_large_section(sbi) ? SEGS_TO_BLKS(sbi, + (segno - GET_START_SEG_FROM_SEC(sbi, segno))) : 0; + return CAP_BLKS_PER_SEC(sbi) - used_blocks - CURSEG_I(sbi, type)->next_blkoff; + } return CAP_BLKS_PER_SEC(sbi) - get_ckpt_valid_blocks(sbi, segno, true); } From 4560db9678a2c5952b6205fbca468c6805c2ba2a Mon Sep 17 00:00:00 2001 From: Chao Yu Date: Wed, 5 Nov 2025 14:50:22 +0800 Subject: [PATCH 1629/3902] f2fs: fix return value of f2fs_recover_fsync_data() commit 01fba45deaddcce0d0b01c411435d1acf6feab7b upstream. With below scripts, it will trigger panic in f2fs: mkfs.f2fs -f /dev/vdd mount /dev/vdd /mnt/f2fs touch /mnt/f2fs/foo sync echo 111 >> /mnt/f2fs/foo f2fs_io fsync /mnt/f2fs/foo f2fs_io shutdown 2 /mnt/f2fs umount /mnt/f2fs mount -o ro,norecovery /dev/vdd /mnt/f2fs or mount -o ro,disable_roll_forward /dev/vdd /mnt/f2fs F2FS-fs (vdd): f2fs_recover_fsync_data: recovery fsync data, check_only: 0 F2FS-fs (vdd): Mounted with checkpoint version = 7f5c361f F2FS-fs (vdd): Stopped filesystem due to reason: 0 F2FS-fs (vdd): f2fs_recover_fsync_data: recovery fsync data, check_only: 1 Filesystem f2fs get_tree() didn't set fc->root, returned 1 ------------[ cut here ]------------ kernel BUG at fs/super.c:1761! Oops: invalid opcode: 0000 [#1] SMP PTI CPU: 3 UID: 0 PID: 722 Comm: mount Not tainted 6.18.0-rc2+ #721 PREEMPT(voluntary) Hardware name: QEMU Standard PC (i440FX + PIIX, 1996), BIOS 1.16.3-debian-1.16.3-2 04/01/2014 RIP: 0010:vfs_get_tree.cold+0x18/0x1a Call Trace: fc_mount+0x13/0xa0 path_mount+0x34e/0xc50 __x64_sys_mount+0x121/0x150 do_syscall_64+0x84/0x800 entry_SYSCALL_64_after_hwframe+0x76/0x7e RIP: 0033:0x7fa6cc126cfe The root cause is we missed to handle error number returned from f2fs_recover_fsync_data() when mounting image w/ ro,norecovery or ro,disable_roll_forward mount option, result in returning a positive error number to vfs_get_tree(), fix it. Cc: stable@kernel.org Fixes: 6781eabba1bd ("f2fs: give -EINVAL for norecovery and rw mount") Signed-off-by: Chao Yu Signed-off-by: Jaegeuk Kim Signed-off-by: Greg Kroah-Hartman --- fs/f2fs/super.c | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/fs/f2fs/super.c b/fs/f2fs/super.c index 41b38e557046e0..eb466a11d9d7f6 100644 --- a/fs/f2fs/super.c +++ b/fs/f2fs/super.c @@ -5203,11 +5203,15 @@ static int f2fs_fill_super(struct super_block *sb, struct fs_context *fc) } } else { err = f2fs_recover_fsync_data(sbi, true); - - if (!f2fs_readonly(sb) && err > 0) { - err = -EINVAL; - f2fs_err(sbi, "Need to recover fsync data"); - goto free_meta; + if (err > 0) { + if (!f2fs_readonly(sb)) { + f2fs_err(sbi, "Need to recover fsync data"); + err = -EINVAL; + goto free_meta; + } else { + f2fs_info(sbi, "drop all fsynced data"); + err = 0; + } } } From a1223a72c419452ce7f36584e9610f2408277c40 Mon Sep 17 00:00:00 2001 From: Alison Schofield Date: Fri, 31 Oct 2025 16:42:20 -0700 Subject: [PATCH 1630/3902] tools/testing/nvdimm: Use per-DIMM device handle commit f59b701b4674f7955170b54c4167c5590f4714eb upstream. KASAN reports a global-out-of-bounds access when running these nfit tests: clear.sh, pmem-errors.sh, pfn-meta-errors.sh, btt-errors.sh, daxdev-errors.sh, and inject-error.sh. [] BUG: KASAN: global-out-of-bounds in nfit_test_ctl+0x769f/0x7840 [nfit_test] [] Read of size 4 at addr ffffffffc03ea01c by task ndctl/1215 [] The buggy address belongs to the variable: [] handle+0x1c/0x1df4 [nfit_test] nfit_test_search_spa() uses handle[nvdimm->id] to retrieve a device handle and triggers a KASAN error when it reads past the end of the handle array. It should not be indexing the handle array at all. The correct device handle is stored in per-DIMM test data. Each DIMM has a struct nfit_mem that embeds a struct acpi_nfit_memdev that describes the NFIT device handle. Use that device handle here. Fixes: 10246dc84dfc ("acpi nfit: nfit_test supports translate SPA") Cc: stable@vger.kernel.org Signed-off-by: Alison Schofield Reviewed-by: Dave Jiang > --- Link: https://patch.msgid.link/20251031234227.1303113-1-alison.schofield@intel.com Signed-off-by: Ira Weiny Signed-off-by: Greg Kroah-Hartman --- tools/testing/nvdimm/test/nfit.c | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/tools/testing/nvdimm/test/nfit.c b/tools/testing/nvdimm/test/nfit.c index cfd4378e2129b2..f87e9f251d1310 100644 --- a/tools/testing/nvdimm/test/nfit.c +++ b/tools/testing/nvdimm/test/nfit.c @@ -670,6 +670,7 @@ static int nfit_test_search_spa(struct nvdimm_bus *bus, .addr = spa->spa, .region = NULL, }; + struct nfit_mem *nfit_mem; u64 dpa; ret = device_for_each_child(&bus->dev, &ctx, @@ -687,8 +688,12 @@ static int nfit_test_search_spa(struct nvdimm_bus *bus, */ nd_mapping = &nd_region->mapping[nd_region->ndr_mappings - 1]; nvdimm = nd_mapping->nvdimm; + nfit_mem = nvdimm_provider_data(nvdimm); + if (!nfit_mem) + return -EINVAL; - spa->devices[0].nfit_device_handle = handle[nvdimm->id]; + spa->devices[0].nfit_device_handle = + __to_nfit_memdev(nfit_mem)->device_handle; spa->num_nvdimms = 1; spa->devices[0].dpa = dpa; From dd6e519ba91e41cc336bdbbe6e9c7b247aead280 Mon Sep 17 00:00:00 2001 From: Claudiu Beznea Date: Fri, 12 Sep 2025 12:53:08 +0300 Subject: [PATCH 1631/3902] pinctrl: renesas: rzg2l: Fix ISEL restore on resume commit 44bf66122c12ef6d3382a9b84b9be1802e5f0e95 upstream. Commit 1d2da79708cb ("pinctrl: renesas: rzg2l: Avoid configuring ISEL in gpio_irq_{en,dis}able*()") dropped the configuration of ISEL from struct irq_chip::{irq_enable, irq_disable} APIs and moved it to struct gpio_chip::irq::{child_to_parent_hwirq, child_irq_domain_ops::free} APIs to fix spurious IRQs. After commit 1d2da79708cb ("pinctrl: renesas: rzg2l: Avoid configuring ISEL in gpio_irq_{en,dis}able*()"), ISEL was no longer configured properly on resume. This is because the pinctrl resume code used struct irq_chip::irq_enable (called from rzg2l_gpio_irq_restore()) to reconfigure the wakeup interrupts. Some drivers (e.g. Ethernet) may also reconfigure non-wakeup interrupts on resume through their own code, eventually calling struct irq_chip::irq_enable. Fix this by adding ISEL configuration back into the struct irq_chip::irq_enable API and on resume path for wakeup interrupts. As struct irq_chip::irq_enable needs now to lock to update the ISEL, convert the struct rzg2l_pinctrl::lock to a raw spinlock and replace the locking API calls with the raw variants. Otherwise the lockdep reports invalid wait context when probing the adv7511 module on RZ/G2L: [ BUG: Invalid wait context ] 6.17.0-rc5-next-20250911-00001-gfcfac22533c9 #18 Not tainted ----------------------------- (udev-worker)/165 is trying to lock: ffff00000e3664a8 (&pctrl->lock){....}-{3:3}, at: rzg2l_gpio_irq_enable+0x38/0x78 other info that might help us debug this: context-{5:5} 3 locks held by (udev-worker)/165: #0: ffff00000e890108 (&dev->mutex){....}-{4:4}, at: __driver_attach+0x90/0x1ac #1: ffff000011c07240 (request_class){+.+.}-{4:4}, at: __setup_irq+0xb4/0x6dc #2: ffff000011c070c8 (lock_class){....}-{2:2}, at: __setup_irq+0xdc/0x6dc stack backtrace: CPU: 1 UID: 0 PID: 165 Comm: (udev-worker) Not tainted 6.17.0-rc5-next-20250911-00001-gfcfac22533c9 #18 PREEMPT Hardware name: Renesas SMARC EVK based on r9a07g044l2 (DT) Call trace: show_stack+0x18/0x24 (C) dump_stack_lvl+0x90/0xd0 dump_stack+0x18/0x24 __lock_acquire+0xa14/0x20b4 lock_acquire+0x1c8/0x354 _raw_spin_lock_irqsave+0x60/0x88 rzg2l_gpio_irq_enable+0x38/0x78 irq_enable+0x40/0x8c __irq_startup+0x78/0xa4 irq_startup+0x108/0x16c __setup_irq+0x3c0/0x6dc request_threaded_irq+0xec/0x1ac devm_request_threaded_irq+0x80/0x134 adv7511_probe+0x928/0x9a4 [adv7511] i2c_device_probe+0x22c/0x3dc really_probe+0xbc/0x2a0 __driver_probe_device+0x78/0x12c driver_probe_device+0x40/0x164 __driver_attach+0x9c/0x1ac bus_for_each_dev+0x74/0xd0 driver_attach+0x24/0x30 bus_add_driver+0xe4/0x208 driver_register+0x60/0x128 i2c_register_driver+0x48/0xd0 adv7511_init+0x5c/0x1000 [adv7511] do_one_initcall+0x64/0x30c do_init_module+0x58/0x23c load_module+0x1bcc/0x1d40 init_module_from_file+0x88/0xc4 idempotent_init_module+0x188/0x27c __arm64_sys_finit_module+0x68/0xac invoke_syscall+0x48/0x110 el0_svc_common.constprop.0+0xc0/0xe0 do_el0_svc+0x1c/0x28 el0_svc+0x4c/0x160 el0t_64_sync_handler+0xa0/0xe4 el0t_64_sync+0x198/0x19c Having ISEL configuration back into the struct irq_chip::irq_enable API should be safe with respect to spurious IRQs, as in the probe case IRQs are enabled anyway in struct gpio_chip::irq::child_to_parent_hwirq. No spurious IRQs were detected on suspend/resume, boot, ethernet link insert/remove tests (executed on RZ/G3S). Boot, ethernet link insert/remove tests were also executed successfully on RZ/G2L. Fixes: 1d2da79708cb ("pinctrl: renesas: rzg2l: Avoid configuring ISEL in gpio_irq_{en,dis}able*(") Cc: stable@vger.kernel.org Signed-off-by: Claudiu Beznea Reviewed-by: Geert Uytterhoeven Link: https://patch.msgid.link/20250912095308.3603704-1-claudiu.beznea.uj@bp.renesas.com Signed-off-by: Geert Uytterhoeven Signed-off-by: Greg Kroah-Hartman --- drivers/pinctrl/renesas/pinctrl-rzg2l.c | 71 +++++++++++++++---------- 1 file changed, 44 insertions(+), 27 deletions(-) diff --git a/drivers/pinctrl/renesas/pinctrl-rzg2l.c b/drivers/pinctrl/renesas/pinctrl-rzg2l.c index 94cb77949f5953..c2711b2de374dd 100644 --- a/drivers/pinctrl/renesas/pinctrl-rzg2l.c +++ b/drivers/pinctrl/renesas/pinctrl-rzg2l.c @@ -359,7 +359,7 @@ struct rzg2l_pinctrl { spinlock_t bitmap_lock; /* protect tint_slot bitmap */ unsigned int hwirq[RZG2L_TINT_MAX_INTERRUPT]; - spinlock_t lock; /* lock read/write registers */ + raw_spinlock_t lock; /* lock read/write registers */ struct mutex mutex; /* serialize adding groups and functions */ struct rzg2l_pinctrl_pin_settings *settings; @@ -543,7 +543,7 @@ static void rzg2l_pinctrl_set_pfc_mode(struct rzg2l_pinctrl *pctrl, unsigned long flags; u32 reg; - spin_lock_irqsave(&pctrl->lock, flags); + raw_spin_lock_irqsave(&pctrl->lock, flags); /* Set pin to 'Non-use (Hi-Z input protection)' */ reg = readw(pctrl->base + PM(off)); @@ -567,7 +567,7 @@ static void rzg2l_pinctrl_set_pfc_mode(struct rzg2l_pinctrl *pctrl, pctrl->data->pwpr_pfc_lock_unlock(pctrl, true); - spin_unlock_irqrestore(&pctrl->lock, flags); + raw_spin_unlock_irqrestore(&pctrl->lock, flags); }; static int rzg2l_pinctrl_set_mux(struct pinctrl_dev *pctldev, @@ -882,10 +882,10 @@ static void rzg2l_rmw_pin_config(struct rzg2l_pinctrl *pctrl, u32 offset, addr += 4; } - spin_lock_irqsave(&pctrl->lock, flags); + raw_spin_lock_irqsave(&pctrl->lock, flags); reg = readl(addr) & ~(mask << (bit * 8)); writel(reg | (val << (bit * 8)), addr); - spin_unlock_irqrestore(&pctrl->lock, flags); + raw_spin_unlock_irqrestore(&pctrl->lock, flags); } static int rzg2l_caps_to_pwr_reg(const struct rzg2l_register_offsets *regs, u32 caps) @@ -1121,7 +1121,7 @@ static int rzg2l_write_oen(struct rzg2l_pinctrl *pctrl, unsigned int _pin, u8 oe if (bit < 0) return -EINVAL; - spin_lock_irqsave(&pctrl->lock, flags); + raw_spin_lock_irqsave(&pctrl->lock, flags); val = readb(pctrl->base + oen_offset); if (oen) val &= ~BIT(bit); @@ -1134,7 +1134,7 @@ static int rzg2l_write_oen(struct rzg2l_pinctrl *pctrl, unsigned int _pin, u8 oe writeb(val, pctrl->base + oen_offset); if (pctrl->data->hwcfg->oen_pwpr_lock) writeb(pwpr & ~PWPR_REGWE_B, pctrl->base + regs->pwpr); - spin_unlock_irqrestore(&pctrl->lock, flags); + raw_spin_unlock_irqrestore(&pctrl->lock, flags); return 0; } @@ -1687,14 +1687,14 @@ static int rzg2l_gpio_request(struct gpio_chip *chip, unsigned int offset) if (ret) return ret; - spin_lock_irqsave(&pctrl->lock, flags); + raw_spin_lock_irqsave(&pctrl->lock, flags); /* Select GPIO mode in PMC Register */ reg8 = readb(pctrl->base + PMC(off)); reg8 &= ~BIT(bit); pctrl->data->pmc_writeb(pctrl, reg8, PMC(off)); - spin_unlock_irqrestore(&pctrl->lock, flags); + raw_spin_unlock_irqrestore(&pctrl->lock, flags); return 0; } @@ -1709,7 +1709,7 @@ static void rzg2l_gpio_set_direction(struct rzg2l_pinctrl *pctrl, u32 offset, unsigned long flags; u16 reg16; - spin_lock_irqsave(&pctrl->lock, flags); + raw_spin_lock_irqsave(&pctrl->lock, flags); reg16 = readw(pctrl->base + PM(off)); reg16 &= ~(PM_MASK << (bit * 2)); @@ -1717,7 +1717,7 @@ static void rzg2l_gpio_set_direction(struct rzg2l_pinctrl *pctrl, u32 offset, reg16 |= (output ? PM_OUTPUT : PM_INPUT) << (bit * 2); writew(reg16, pctrl->base + PM(off)); - spin_unlock_irqrestore(&pctrl->lock, flags); + raw_spin_unlock_irqrestore(&pctrl->lock, flags); } static int rzg2l_gpio_get_direction(struct gpio_chip *chip, unsigned int offset) @@ -1761,7 +1761,7 @@ static int rzg2l_gpio_set(struct gpio_chip *chip, unsigned int offset, unsigned long flags; u8 reg8; - spin_lock_irqsave(&pctrl->lock, flags); + raw_spin_lock_irqsave(&pctrl->lock, flags); reg8 = readb(pctrl->base + P(off)); @@ -1770,7 +1770,7 @@ static int rzg2l_gpio_set(struct gpio_chip *chip, unsigned int offset, else writeb(reg8 & ~BIT(bit), pctrl->base + P(off)); - spin_unlock_irqrestore(&pctrl->lock, flags); + raw_spin_unlock_irqrestore(&pctrl->lock, flags); return 0; } @@ -2429,14 +2429,13 @@ static int rzg2l_gpio_get_gpioint(unsigned int virq, struct rzg2l_pinctrl *pctrl return gpioint; } -static void rzg2l_gpio_irq_endisable(struct rzg2l_pinctrl *pctrl, - unsigned int hwirq, bool enable) +static void __rzg2l_gpio_irq_endisable(struct rzg2l_pinctrl *pctrl, + unsigned int hwirq, bool enable) { const struct pinctrl_pin_desc *pin_desc = &pctrl->desc.pins[hwirq]; u64 *pin_data = pin_desc->drv_data; u32 off = RZG2L_PIN_CFG_TO_PORT_OFFSET(*pin_data); u8 bit = RZG2L_PIN_ID_TO_PIN(hwirq); - unsigned long flags; void __iomem *addr; addr = pctrl->base + ISEL(off); @@ -2445,12 +2444,20 @@ static void rzg2l_gpio_irq_endisable(struct rzg2l_pinctrl *pctrl, addr += 4; } - spin_lock_irqsave(&pctrl->lock, flags); if (enable) writel(readl(addr) | BIT(bit * 8), addr); else writel(readl(addr) & ~BIT(bit * 8), addr); - spin_unlock_irqrestore(&pctrl->lock, flags); +} + +static void rzg2l_gpio_irq_endisable(struct rzg2l_pinctrl *pctrl, + unsigned int hwirq, bool enable) +{ + unsigned long flags; + + raw_spin_lock_irqsave(&pctrl->lock, flags); + __rzg2l_gpio_irq_endisable(pctrl, hwirq, enable); + raw_spin_unlock_irqrestore(&pctrl->lock, flags); } static void rzg2l_gpio_irq_disable(struct irq_data *d) @@ -2462,15 +2469,25 @@ static void rzg2l_gpio_irq_disable(struct irq_data *d) gpiochip_disable_irq(gc, hwirq); } -static void rzg2l_gpio_irq_enable(struct irq_data *d) +static void __rzg2l_gpio_irq_enable(struct irq_data *d, bool lock) { struct gpio_chip *gc = irq_data_get_irq_chip_data(d); + struct rzg2l_pinctrl *pctrl = container_of(gc, struct rzg2l_pinctrl, gpio_chip); unsigned int hwirq = irqd_to_hwirq(d); gpiochip_enable_irq(gc, hwirq); + if (lock) + rzg2l_gpio_irq_endisable(pctrl, hwirq, true); + else + __rzg2l_gpio_irq_endisable(pctrl, hwirq, true); irq_chip_enable_parent(d); } +static void rzg2l_gpio_irq_enable(struct irq_data *d) +{ + __rzg2l_gpio_irq_enable(d, true); +} + static int rzg2l_gpio_irq_set_type(struct irq_data *d, unsigned int type) { return irq_chip_set_type_parent(d, type); @@ -2616,11 +2633,11 @@ static void rzg2l_gpio_irq_restore(struct rzg2l_pinctrl *pctrl) * This has to be atomically executed to protect against a concurrent * interrupt. */ - spin_lock_irqsave(&pctrl->lock, flags); + raw_spin_lock_irqsave(&pctrl->lock, flags); ret = rzg2l_gpio_irq_set_type(data, irqd_get_trigger_type(data)); if (!ret && !irqd_irq_disabled(data)) - rzg2l_gpio_irq_enable(data); - spin_unlock_irqrestore(&pctrl->lock, flags); + __rzg2l_gpio_irq_enable(data, false); + raw_spin_unlock_irqrestore(&pctrl->lock, flags); if (ret) dev_crit(pctrl->dev, "Failed to set IRQ type for virq=%u\n", virq); @@ -2950,7 +2967,7 @@ static int rzg2l_pinctrl_probe(struct platform_device *pdev) "failed to enable GPIO clk\n"); } - spin_lock_init(&pctrl->lock); + raw_spin_lock_init(&pctrl->lock); spin_lock_init(&pctrl->bitmap_lock); mutex_init(&pctrl->mutex); atomic_set(&pctrl->wakeup_path, 0); @@ -3097,7 +3114,7 @@ static void rzg2l_pinctrl_pm_setup_pfc(struct rzg2l_pinctrl *pctrl) u32 nports = pctrl->data->n_port_pins / RZG2L_PINS_PER_PORT; unsigned long flags; - spin_lock_irqsave(&pctrl->lock, flags); + raw_spin_lock_irqsave(&pctrl->lock, flags); pctrl->data->pwpr_pfc_lock_unlock(pctrl, false); /* Restore port registers. */ @@ -3142,7 +3159,7 @@ static void rzg2l_pinctrl_pm_setup_pfc(struct rzg2l_pinctrl *pctrl) } pctrl->data->pwpr_pfc_lock_unlock(pctrl, true); - spin_unlock_irqrestore(&pctrl->lock, flags); + raw_spin_unlock_irqrestore(&pctrl->lock, flags); } static int rzg2l_pinctrl_suspend_noirq(struct device *dev) @@ -3191,14 +3208,14 @@ static int rzg2l_pinctrl_resume_noirq(struct device *dev) writeb(cache->qspi, pctrl->base + QSPI); if (pctrl->data->hwcfg->oen_pwpr_lock) { - spin_lock_irqsave(&pctrl->lock, flags); + raw_spin_lock_irqsave(&pctrl->lock, flags); pwpr = readb(pctrl->base + regs->pwpr); writeb(pwpr | PWPR_REGWE_B, pctrl->base + regs->pwpr); } writeb(cache->oen, pctrl->base + pctrl->data->hwcfg->regs.oen); if (pctrl->data->hwcfg->oen_pwpr_lock) { writeb(pwpr & ~PWPR_REGWE_B, pctrl->base + regs->pwpr); - spin_unlock_irqrestore(&pctrl->lock, flags); + raw_spin_unlock_irqrestore(&pctrl->lock, flags); } for (u8 i = 0; i < 2; i++) { if (regs->sd_ch) From cb51bef465d8ec60a968507330e01020e35dc127 Mon Sep 17 00:00:00 2001 From: Sean Christopherson Date: Mon, 1 Dec 2025 18:03:33 -0800 Subject: [PATCH 1632/3902] KVM: Disallow toggling KVM_MEM_GUEST_MEMFD on an existing memslot commit 9935df5333aa503a18de5071f53762b65c783c4c upstream. Reject attempts to disable KVM_MEM_GUEST_MEMFD on a memslot that was initially created with a guest_memfd binding, as KVM doesn't support toggling KVM_MEM_GUEST_MEMFD on existing memslots. KVM prevents enabling KVM_MEM_GUEST_MEMFD, but doesn't prevent clearing the flag. Failure to reject the new memslot results in a use-after-free due to KVM not unbinding from the guest_memfd instance. Unbinding on a FLAGS_ONLY change is easy enough, and can/will be done as a hardening measure (in anticipation of KVM supporting dirty logging on guest_memfd at some point), but fixing the use-after-free would only address the immediate symptom. ================================================================== BUG: KASAN: slab-use-after-free in kvm_gmem_release+0x362/0x400 [kvm] Write of size 8 at addr ffff8881111ae908 by task repro/745 CPU: 7 UID: 1000 PID: 745 Comm: repro Not tainted 6.18.0-rc6-115d5de2eef3-next-kasan #3 NONE Hardware name: QEMU Standard PC (Q35 + ICH9, 2009), BIOS 0.0.0 02/06/2015 Call Trace: dump_stack_lvl+0x51/0x60 print_report+0xcb/0x5c0 kasan_report+0xb4/0xe0 kvm_gmem_release+0x362/0x400 [kvm] __fput+0x2fa/0x9d0 task_work_run+0x12c/0x200 do_exit+0x6ae/0x2100 do_group_exit+0xa8/0x230 __x64_sys_exit_group+0x3a/0x50 x64_sys_call+0x737/0x740 do_syscall_64+0x5b/0x900 entry_SYSCALL_64_after_hwframe+0x4b/0x53 RIP: 0033:0x7f581f2eac31 Allocated by task 745 on cpu 6 at 9.746971s: kasan_save_stack+0x20/0x40 kasan_save_track+0x13/0x50 __kasan_kmalloc+0x77/0x90 kvm_set_memory_region.part.0+0x652/0x1110 [kvm] kvm_vm_ioctl+0x14b0/0x3290 [kvm] __x64_sys_ioctl+0x129/0x1a0 do_syscall_64+0x5b/0x900 entry_SYSCALL_64_after_hwframe+0x4b/0x53 Freed by task 745 on cpu 6 at 9.747467s: kasan_save_stack+0x20/0x40 kasan_save_track+0x13/0x50 __kasan_save_free_info+0x37/0x50 __kasan_slab_free+0x3b/0x60 kfree+0xf5/0x440 kvm_set_memslot+0x3c2/0x1160 [kvm] kvm_set_memory_region.part.0+0x86a/0x1110 [kvm] kvm_vm_ioctl+0x14b0/0x3290 [kvm] __x64_sys_ioctl+0x129/0x1a0 do_syscall_64+0x5b/0x900 entry_SYSCALL_64_after_hwframe+0x4b/0x53 Reported-by: Alexander Potapenko Fixes: a7800aa80ea4 ("KVM: Add KVM_CREATE_GUEST_MEMFD ioctl() for guest-specific backing memory") Cc: stable@vger.kernel.org Link: https://patch.msgid.link/20251202020334.1171351-2-seanjc@google.com Signed-off-by: Sean Christopherson Signed-off-by: Greg Kroah-Hartman --- virt/kvm/kvm_main.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/virt/kvm/kvm_main.c b/virt/kvm/kvm_main.c index b7a0ae2a7b205c..01ea4f8351a617 100644 --- a/virt/kvm/kvm_main.c +++ b/virt/kvm/kvm_main.c @@ -2085,7 +2085,7 @@ static int kvm_set_memory_region(struct kvm *kvm, return -EINVAL; if ((mem->userspace_addr != old->userspace_addr) || (npages != old->npages) || - ((mem->flags ^ old->flags) & KVM_MEM_READONLY)) + ((mem->flags ^ old->flags) & (KVM_MEM_READONLY | KVM_MEM_GUEST_MEMFD))) return -EINVAL; if (base_gfn != old->base_gfn) From 65d4e5af2a2e82f4fc50d8259aee208fbc6b2c1d Mon Sep 17 00:00:00 2001 From: Deepanshu Kartikey Date: Wed, 10 Dec 2025 07:50:24 +0530 Subject: [PATCH 1633/3902] mm/slub: reset KASAN tag in defer_free() before accessing freed memory commit 53ca00a19d345197a37a1bf552e8d1e7b091666c upstream. When CONFIG_SLUB_TINY is enabled, kfree_nolock() calls kasan_slab_free() before defer_free(). On ARM64 with MTE (Memory Tagging Extension), kasan_slab_free() poisons the memory and changes the tag from the original (e.g., 0xf3) to a poison tag (0xfe). When defer_free() then tries to write to the freed object to build the deferred free list via llist_add(), the pointer still has the old tag, causing a tag mismatch and triggering a KASAN use-after-free report: BUG: KASAN: slab-use-after-free in defer_free+0x3c/0xbc mm/slub.c:6537 Write at addr f3f000000854f020 by task kworker/u8:6/983 Pointer tag: [f3], memory tag: [fe] Fix this by calling kasan_reset_tag() before accessing the freed memory. This is safe because defer_free() is part of the allocator itself and is expected to manipulate freed memory for bookkeeping purposes. Fixes: af92793e52c3 ("slab: Introduce kmalloc_nolock() and kfree_nolock().") Cc: stable@vger.kernel.org Reported-by: syzbot+7a25305a76d872abcfa1@syzkaller.appspotmail.com Closes: https://syzkaller.appspot.com/bug?extid=7a25305a76d872abcfa1 Tested-by: syzbot+7a25305a76d872abcfa1@syzkaller.appspotmail.com Signed-off-by: Deepanshu Kartikey Acked-by: Alexei Starovoitov Link: https://patch.msgid.link/20251210022024.3255826-1-kartikey406@gmail.com Signed-off-by: Vlastimil Babka Signed-off-by: Greg Kroah-Hartman --- mm/slub.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/mm/slub.c b/mm/slub.c index e4aaaf0a5e040b..507f346102256f 100644 --- a/mm/slub.c +++ b/mm/slub.c @@ -6509,6 +6509,8 @@ static void defer_free(struct kmem_cache *s, void *head) guard(preempt)(); + head = kasan_reset_tag(head); + df = this_cpu_ptr(&defer_free_objects); if (llist_add(head + s->offset, &df->objects)) irq_work_queue(&df->work); From 30f4d4e5224a9e44e9ceb3956489462319d804ce Mon Sep 17 00:00:00 2001 From: Jeongjun Park Date: Fri, 5 Sep 2025 14:18:16 +0900 Subject: [PATCH 1634/3902] media: vidtv: initialize local pointers upon transfer of memory ownership commit 98aabfe2d79f74613abc2b0b1cef08f97eaf5322 upstream. vidtv_channel_si_init() creates a temporary list (program, service, event) and ownership of the memory itself is transferred to the PAT/SDT/EIT tables through vidtv_psi_pat_program_assign(), vidtv_psi_sdt_service_assign(), vidtv_psi_eit_event_assign(). The problem here is that the local pointer where the memory ownership transfer was completed is not initialized to NULL. This causes the vidtv_psi_pmt_create_sec_for_each_pat_entry() function to fail, and in the flow that jumps to free_eit, the memory that was freed by vidtv_psi_*_table_destroy() can be accessed again by vidtv_psi_*_event_destroy() due to the uninitialized local pointer, so it is freed once again. Therefore, to prevent use-after-free and double-free vulnerability, local pointers must be initialized to NULL when transferring memory ownership. Cc: Reported-by: syzbot+1d9c0edea5907af239e0@syzkaller.appspotmail.com Closes: https://syzkaller.appspot.com/bug?extid=1d9c0edea5907af239e0 Fixes: 3be8037960bc ("media: vidtv: add error checks") Signed-off-by: Jeongjun Park Reviewed-by: Daniel Almeida Signed-off-by: Hans Verkuil Signed-off-by: Greg Kroah-Hartman --- drivers/media/test-drivers/vidtv/vidtv_channel.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/drivers/media/test-drivers/vidtv/vidtv_channel.c b/drivers/media/test-drivers/vidtv/vidtv_channel.c index f3023e91b3ebc8..3541155c6fc635 100644 --- a/drivers/media/test-drivers/vidtv/vidtv_channel.c +++ b/drivers/media/test-drivers/vidtv/vidtv_channel.c @@ -461,12 +461,15 @@ int vidtv_channel_si_init(struct vidtv_mux *m) /* assemble all programs and assign to PAT */ vidtv_psi_pat_program_assign(m->si.pat, programs); + programs = NULL; /* assemble all services and assign to SDT */ vidtv_psi_sdt_service_assign(m->si.sdt, services); + services = NULL; /* assemble all events and assign to EIT */ vidtv_psi_eit_event_assign(m->si.eit, events); + events = NULL; m->si.pmt_secs = vidtv_psi_pmt_create_sec_for_each_pat_entry(m->si.pat, m->pcr_pid); From 7acc0390e0dd7474c4451d05465a677d55ad4268 Mon Sep 17 00:00:00 2001 From: Prithvi Tambewagh Date: Mon, 1 Dec 2025 18:37:11 +0530 Subject: [PATCH 1635/3902] ocfs2: fix kernel BUG in ocfs2_find_victim_chain commit 039bef30e320827bac8990c9f29d2a68cd8adb5f upstream. syzbot reported a kernel BUG in ocfs2_find_victim_chain() because the `cl_next_free_rec` field of the allocation chain list (next free slot in the chain list) is 0, triggring the BUG_ON(!cl->cl_next_free_rec) condition in ocfs2_find_victim_chain() and panicking the kernel. To fix this, an if condition is introduced in ocfs2_claim_suballoc_bits(), just before calling ocfs2_find_victim_chain(), the code block in it being executed when either of the following conditions is true: 1. `cl_next_free_rec` is equal to 0, indicating that there are no free chains in the allocation chain list 2. `cl_next_free_rec` is greater than `cl_count` (the total number of chains in the allocation chain list) Either of them being true is indicative of the fact that there are no chains left for usage. This is addressed using ocfs2_error(), which prints the error log for debugging purposes, rather than panicking the kernel. Link: https://lkml.kernel.org/r/20251201130711.143900-1-activprithvi@gmail.com Signed-off-by: Prithvi Tambewagh Reported-by: syzbot+96d38c6e1655c1420a72@syzkaller.appspotmail.com Closes: https://syzkaller.appspot.com/bug?extid=96d38c6e1655c1420a72 Tested-by: syzbot+96d38c6e1655c1420a72@syzkaller.appspotmail.com Reviewed-by: Joseph Qi Cc: Mark Fasheh Cc: Joel Becker Cc: Junxiao Bi Cc: Changwei Ge Cc: Jun Piao Cc: Heming Zhao Cc: Signed-off-by: Andrew Morton Signed-off-by: Greg Kroah-Hartman --- fs/ocfs2/suballoc.c | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/fs/ocfs2/suballoc.c b/fs/ocfs2/suballoc.c index 6ac4dcd54588cf..e93fc842bb2038 100644 --- a/fs/ocfs2/suballoc.c +++ b/fs/ocfs2/suballoc.c @@ -1992,6 +1992,16 @@ static int ocfs2_claim_suballoc_bits(struct ocfs2_alloc_context *ac, } cl = (struct ocfs2_chain_list *) &fe->id2.i_chain; + if (!le16_to_cpu(cl->cl_next_free_rec) || + le16_to_cpu(cl->cl_next_free_rec) > le16_to_cpu(cl->cl_count)) { + status = ocfs2_error(ac->ac_inode->i_sb, + "Chain allocator dinode %llu has invalid next " + "free chain record %u, but only %u total\n", + (unsigned long long)le64_to_cpu(fe->i_blkno), + le16_to_cpu(cl->cl_next_free_rec), + le16_to_cpu(cl->cl_count)); + goto bail; + } victim = ocfs2_find_victim_chain(cl); ac->ac_chain = victim; From f73b74dafa3c888c62618c3b8c39fba3b058cfc0 Mon Sep 17 00:00:00 2001 From: Maxim Levitsky Date: Tue, 14 Oct 2025 23:32:58 -0400 Subject: [PATCH 1636/3902] KVM: x86: Don't clear async #PF queue when CR0.PG is disabled (e.g. on #SMI) commit ab4e41eb9fabd4607304fa7cfe8ec9c0bd8e1552 upstream. Fix an interaction between SMM and PV asynchronous #PFs where an #SMI can cause KVM to drop an async #PF ready event, and thus result in guest tasks becoming permanently stuck due to the task that encountered the #PF never being resumed. Specifically, don't clear the completion queue when paging is disabled, and re-check for completed async #PFs if/when paging is enabled. Prior to commit 2635b5c4a0e4 ("KVM: x86: interrupt based APF 'page ready' event delivery"), flushing the APF queue without notifying the guest of completed APF requests when paging is disabled was "necessary", in that delivering a #PF to the guest when paging is disabled would likely confuse and/or crash the guest. And presumably the original async #PF development assumed that a guest would only disable paging when there was no intent to ever re-enable paging. That assumption fails in several scenarios, most visibly on an emulated SMI, as entering SMM always disables CR0.PG (i.e. initially runs with paging disabled). When the SMM handler eventually executes RSM, the interrupted paging-enabled is restored, and the async #PF event is lost. Similarly, invoking firmware, e.g. via EFI runtime calls, might require a transition through paging modes and thus also disable paging with valid entries in the competion queue. To avoid dropping completion events, drop the "clear" entirely, and handle paging-enable transitions in the same way KVM already handles APIC enable/disable events: if a vCPU's APIC is disabled, APF completion events are not kept pending and not injected while APIC is disabled. Once a vCPU's APIC is re-enabled, KVM raises KVM_REQ_APF_READY so that the vCPU recognizes any pending pending #APF ready events. Signed-off-by: Maxim Levitsky Cc: stable@vger.kernel.org Link: https://patch.msgid.link/20251015033258.50974-4-mlevitsk@redhat.com [sean: rework changelog to call out #PF injection, drop "real mode" references, expand the code comment] Signed-off-by: Sean Christopherson Signed-off-by: Greg Kroah-Hartman --- arch/x86/kvm/x86.c | 25 +++++++++++++++---------- 1 file changed, 15 insertions(+), 10 deletions(-) diff --git a/arch/x86/kvm/x86.c b/arch/x86/kvm/x86.c index e6f2e34ec97d10..5d3297cef8a996 100644 --- a/arch/x86/kvm/x86.c +++ b/arch/x86/kvm/x86.c @@ -1045,6 +1045,13 @@ bool kvm_require_dr(struct kvm_vcpu *vcpu, int dr) } EXPORT_SYMBOL_FOR_KVM_INTERNAL(kvm_require_dr); +static bool kvm_pv_async_pf_enabled(struct kvm_vcpu *vcpu) +{ + u64 mask = KVM_ASYNC_PF_ENABLED | KVM_ASYNC_PF_DELIVERY_AS_INT; + + return (vcpu->arch.apf.msr_en_val & mask) == mask; +} + static inline u64 pdptr_rsvd_bits(struct kvm_vcpu *vcpu) { return vcpu->arch.reserved_gpa_bits | rsvd_bits(5, 8) | rsvd_bits(1, 2); @@ -1137,15 +1144,20 @@ void kvm_post_set_cr0(struct kvm_vcpu *vcpu, unsigned long old_cr0, unsigned lon } if ((cr0 ^ old_cr0) & X86_CR0_PG) { - kvm_clear_async_pf_completion_queue(vcpu); - kvm_async_pf_hash_reset(vcpu); - /* * Clearing CR0.PG is defined to flush the TLB from the guest's * perspective. */ if (!(cr0 & X86_CR0_PG)) kvm_make_request(KVM_REQ_TLB_FLUSH_GUEST, vcpu); + /* + * Check for async #PF completion events when enabling paging, + * as the vCPU may have previously encountered async #PFs (it's + * entirely legal for the guest to toggle paging on/off without + * waiting for the async #PF queue to drain). + */ + else if (kvm_pv_async_pf_enabled(vcpu)) + kvm_make_request(KVM_REQ_APF_READY, vcpu); } if ((cr0 ^ old_cr0) & KVM_MMU_CR0_ROLE_BITS) @@ -3650,13 +3662,6 @@ static int set_msr_mce(struct kvm_vcpu *vcpu, struct msr_data *msr_info) return 0; } -static inline bool kvm_pv_async_pf_enabled(struct kvm_vcpu *vcpu) -{ - u64 mask = KVM_ASYNC_PF_ENABLED | KVM_ASYNC_PF_DELIVERY_AS_INT; - - return (vcpu->arch.apf.msr_en_val & mask) == mask; -} - static int kvm_pv_enable_async_pf(struct kvm_vcpu *vcpu, u64 data) { gpa_t gpa = data & ~0x3f; From 24a2062257bbdfc831de5ed21c27b04b5bdf2437 Mon Sep 17 00:00:00 2001 From: Tzung-Bi Shih Date: Fri, 31 Oct 2025 03:39:00 +0000 Subject: [PATCH 1637/3902] platform/chrome: cros_ec_ishtp: Fix UAF after unbinding driver commit 944edca81e7aea15f83cf9a13a6ab67f711e8abd upstream. After unbinding the driver, another kthread `cros_ec_console_log_work` is still accessing the device, resulting an UAF and crash. The driver doesn't unregister the EC device in .remove() which should shutdown sub-devices synchronously. Fix it. Fixes: 26a14267aff2 ("platform/chrome: Add ChromeOS EC ISHTP driver") Cc: stable@vger.kernel.org Link: https://lore.kernel.org/r/20251031033900.3577394-1-tzungbi@kernel.org Signed-off-by: Tzung-Bi Shih Signed-off-by: Greg Kroah-Hartman --- drivers/platform/chrome/cros_ec_ishtp.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/platform/chrome/cros_ec_ishtp.c b/drivers/platform/chrome/cros_ec_ishtp.c index 4e74e702c5a24d..3766cef81fe82e 100644 --- a/drivers/platform/chrome/cros_ec_ishtp.c +++ b/drivers/platform/chrome/cros_ec_ishtp.c @@ -667,6 +667,7 @@ static void cros_ec_ishtp_remove(struct ishtp_cl_device *cl_device) cancel_work_sync(&client_data->work_ishtp_reset); cancel_work_sync(&client_data->work_ec_evt); + cros_ec_unregister(client_data->ec_dev); cros_ish_deinit(cros_ish_cl); ishtp_put_device(cl_device); } From f8b136296722e258ec43237a35f72c92a6d4501a Mon Sep 17 00:00:00 2001 From: Wangao Wang Date: Mon, 27 Oct 2025 17:35:59 +0800 Subject: [PATCH 1638/3902] media: iris: Add sanity check for stop streaming commit ad699fa78b59241c9d71a8cafb51525f3dab04d4 upstream. Add sanity check in iris_vb2_stop_streaming. If inst->state is already IRIS_INST_ERROR, we should skip the stream_off operation because it would still send packets to the firmware. In iris_kill_session, inst->state is set to IRIS_INST_ERROR and session_close is executed, which will kfree(inst_hfi_gen2->packet). If stop_streaming is called afterward, it will cause a crash. Fixes: 11712ce70f8e5 ("media: iris: implement vb2 streaming ops") Cc: stable@vger.kernel.org Reviewed-by: Bryan O'Donoghue Reviewed-by: Dikshita Agarwal Signed-off-by: Wangao Wang Reviewed-by: Vikash Garodia [bod: remove qcom from patch title] Signed-off-by: Bryan O'Donoghue Signed-off-by: Hans Verkuil Signed-off-by: Greg Kroah-Hartman --- drivers/media/platform/qcom/iris/iris_vb2.c | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/drivers/media/platform/qcom/iris/iris_vb2.c b/drivers/media/platform/qcom/iris/iris_vb2.c index 139b821f7952fe..db8768d8a8f61c 100644 --- a/drivers/media/platform/qcom/iris/iris_vb2.c +++ b/drivers/media/platform/qcom/iris/iris_vb2.c @@ -231,6 +231,8 @@ void iris_vb2_stop_streaming(struct vb2_queue *q) return; mutex_lock(&inst->lock); + if (inst->state == IRIS_INST_ERROR) + goto exit; if (!V4L2_TYPE_IS_OUTPUT(q->type) && !V4L2_TYPE_IS_CAPTURE(q->type)) @@ -241,10 +243,10 @@ void iris_vb2_stop_streaming(struct vb2_queue *q) goto exit; exit: - iris_helper_buffers_done(inst, q->type, VB2_BUF_STATE_ERROR); - if (ret) + if (ret) { + iris_helper_buffers_done(inst, q->type, VB2_BUF_STATE_ERROR); iris_inst_change_state(inst, IRIS_INST_ERROR); - + } mutex_unlock(&inst->lock); } From a19fb3611e4c06624fc0f83ef19f4fb8d57d4751 Mon Sep 17 00:00:00 2001 From: Zhichi Lin Date: Sat, 11 Oct 2025 16:22:22 +0800 Subject: [PATCH 1639/3902] scs: fix a wrong parameter in __scs_magic commit 08bd4c46d5e63b78e77f2605283874bbe868ab19 upstream. __scs_magic() needs a 'void *' variable, but a 'struct task_struct *' is given. 'task_scs(tsk)' is the starting address of the task's shadow call stack, and '__scs_magic(task_scs(tsk))' is the end address of the task's shadow call stack. Here should be '__scs_magic(task_scs(tsk))'. The user-visible effect of this bug is that when CONFIG_DEBUG_STACK_USAGE is enabled, the shadow call stack usage checking function (scs_check_usage) would scan an incorrect memory range. This could lead to: 1. **Inaccurate stack usage reporting**: The function would calculate wrong usage statistics for the shadow call stack, potentially showing incorrect value in kmsg. 2. **Potential kernel crash**: If the value of __scs_magic(tsk)is greater than that of __scs_magic(task_scs(tsk)), the for loop may access unmapped memory, potentially causing a kernel panic. However, this scenario is unlikely because task_struct is allocated via the slab allocator (which typically returns lower addresses), while the shadow call stack returned by task_scs(tsk) is allocated via vmalloc(which typically returns higher addresses). However, since this is purely a debugging feature (CONFIG_DEBUG_STACK_USAGE), normal production systems should be not unaffected. The bug only impacts developers and testers who are actively debugging stack usage with this configuration enabled. Link: https://lkml.kernel.org/r/20251011082222.12965-1-zhichi.lin@vivo.com Fixes: 5bbaf9d1fcb9 ("scs: Add support for stack usage debugging") Signed-off-by: Jiyuan Xie Signed-off-by: Zhichi Lin Reviewed-by: Sami Tolvanen Acked-by: Will Deacon Cc: Andrey Konovalov Cc: Kees Cook Cc: Marco Elver Cc: Will Deacon Cc: Yee Lee Cc: Signed-off-by: Andrew Morton Signed-off-by: Greg Kroah-Hartman --- kernel/scs.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/kernel/scs.c b/kernel/scs.c index d7809affe74047..772488afd5b972 100644 --- a/kernel/scs.c +++ b/kernel/scs.c @@ -135,7 +135,7 @@ static void scs_check_usage(struct task_struct *tsk) if (!IS_ENABLED(CONFIG_DEBUG_STACK_USAGE)) return; - for (p = task_scs(tsk); p < __scs_magic(tsk); ++p) { + for (p = task_scs(tsk); p < __scs_magic(task_scs(tsk)); ++p) { if (!READ_ONCE_NOCHECK(*p)) break; used += sizeof(*p); From c8f810e20f4bbe50b49f73429d9fa6efad00623e Mon Sep 17 00:00:00 2001 From: Helge Deller Date: Tue, 25 Nov 2025 15:23:02 +0100 Subject: [PATCH 1640/3902] parisc: Do not reprogram affinitiy on ASP chip commit dca7da244349eef4d78527cafc0bf80816b261f5 upstream. The ASP chip is a very old variant of the GSP chip and is used e.g. in HP 730 workstations. When trying to reprogram the affinity it will crash with a HPMC as the relevant registers don't seem to be at the usual location. Let's avoid the crash by checking the sversion. Also note, that reprogramming isn't necessary either, as the HP730 is a just a single-CPU machine. Signed-off-by: Helge Deller Cc: stable@vger.kernel.org Signed-off-by: Greg Kroah-Hartman --- drivers/parisc/gsc.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/drivers/parisc/gsc.c b/drivers/parisc/gsc.c index a0daaa548bc392..8ba778170447eb 100644 --- a/drivers/parisc/gsc.c +++ b/drivers/parisc/gsc.c @@ -154,7 +154,9 @@ static int gsc_set_affinity_irq(struct irq_data *d, const struct cpumask *dest, gsc_dev->eim = ((u32) gsc_dev->gsc_irq.txn_addr) | gsc_dev->gsc_irq.txn_data; /* switch IRQ's for devices below LASI/WAX to other CPU */ - gsc_writel(gsc_dev->eim, gsc_dev->hpa + OFFSET_IAR); + /* ASP chip (svers 0x70) does not support reprogramming */ + if (gsc_dev->gsc->id.sversion != 0x70) + gsc_writel(gsc_dev->eim, gsc_dev->hpa + OFFSET_IAR); irq_data_update_effective_affinity(d, &tmask); From 3383f16f7870e4cd81e326c9a32f3416305b24b5 Mon Sep 17 00:00:00 2001 From: Dongsheng Yang Date: Fri, 5 Dec 2025 05:46:18 +0000 Subject: [PATCH 1641/3902] dm-pcache: advance slot index before writing slot MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit commit ebbb90344a7da2421e4b54668b94e81828b8b308 upstream. In dm-pcache, in order to ensure crash-consistency, a dual-copy scheme is used to alternately update metadata, and there is a slot index that records the current slot. However, in the write path the current implementation writes directly to the current slot indexed by slot index, and then advances the slot — which ends up overwriting the existing slot, violating the crash-consistency guarantee. This patch fixes that behavior, preventing metadata from being overwritten incorrectly. In addition, this patch add a missing pmem_wmb() after memcpy_flushcache(). Signed-off-by: Dongsheng Yang Signed-off-by: Mikulas Patocka Reviewed-by: Zheng Gu Cc: stable@vger.kernel.org # 6.18 Signed-off-by: Greg Kroah-Hartman --- drivers/md/dm-pcache/cache.c | 8 ++++---- drivers/md/dm-pcache/cache_segment.c | 8 ++++---- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/drivers/md/dm-pcache/cache.c b/drivers/md/dm-pcache/cache.c index 698697a7a73c66..d4385d76ac36d6 100644 --- a/drivers/md/dm-pcache/cache.c +++ b/drivers/md/dm-pcache/cache.c @@ -21,10 +21,10 @@ static void cache_info_write(struct pcache_cache *cache) cache_info->header.crc = pcache_meta_crc(&cache_info->header, sizeof(struct pcache_cache_info)); + cache->info_index = (cache->info_index + 1) % PCACHE_META_INDEX_MAX; memcpy_flushcache(get_cache_info_addr(cache), cache_info, sizeof(struct pcache_cache_info)); - - cache->info_index = (cache->info_index + 1) % PCACHE_META_INDEX_MAX; + pmem_wmb(); } static void cache_info_init_default(struct pcache_cache *cache); @@ -93,10 +93,10 @@ void cache_pos_encode(struct pcache_cache *cache, pos_onmedia.header.seq = seq; pos_onmedia.header.crc = cache_pos_onmedia_crc(&pos_onmedia); + *index = (*index + 1) % PCACHE_META_INDEX_MAX; + memcpy_flushcache(pos_onmedia_addr, &pos_onmedia, sizeof(struct pcache_cache_pos_onmedia)); pmem_wmb(); - - *index = (*index + 1) % PCACHE_META_INDEX_MAX; } int cache_pos_decode(struct pcache_cache *cache, diff --git a/drivers/md/dm-pcache/cache_segment.c b/drivers/md/dm-pcache/cache_segment.c index f0b58980806e07..ae57cc261422d8 100644 --- a/drivers/md/dm-pcache/cache_segment.c +++ b/drivers/md/dm-pcache/cache_segment.c @@ -26,11 +26,11 @@ static void cache_seg_info_write(struct pcache_cache_segment *cache_seg) seg_info->header.seq++; seg_info->header.crc = pcache_meta_crc(&seg_info->header, sizeof(struct pcache_segment_info)); + cache_seg->info_index = (cache_seg->info_index + 1) % PCACHE_META_INDEX_MAX; + seg_info_addr = get_seg_info_addr(cache_seg); memcpy_flushcache(seg_info_addr, seg_info, sizeof(struct pcache_segment_info)); pmem_wmb(); - - cache_seg->info_index = (cache_seg->info_index + 1) % PCACHE_META_INDEX_MAX; mutex_unlock(&cache_seg->info_lock); } @@ -129,10 +129,10 @@ static void cache_seg_ctrl_write(struct pcache_cache_segment *cache_seg) cache_seg_gen.header.crc = pcache_meta_crc(&cache_seg_gen.header, sizeof(struct pcache_cache_seg_gen)); + cache_seg->gen_index = (cache_seg->gen_index + 1) % PCACHE_META_INDEX_MAX; + memcpy_flushcache(get_cache_seg_gen_addr(cache_seg), &cache_seg_gen, sizeof(struct pcache_cache_seg_gen)); pmem_wmb(); - - cache_seg->gen_index = (cache_seg->gen_index + 1) % PCACHE_META_INDEX_MAX; } static void cache_seg_ctrl_init(struct pcache_cache_segment *cache_seg) From 37a691ed7942feec5a91d602290818f128a635f8 Mon Sep 17 00:00:00 2001 From: Vivian Wang Date: Tue, 2 Dec 2025 13:25:07 +0800 Subject: [PATCH 1642/3902] lib/crypto: riscv/chacha: Avoid s0/fp register commit 43169328c7b4623b54b7713ec68479cebda5465f upstream. In chacha_zvkb, avoid using the s0 register, which is the frame pointer, by reallocating KEY0 to t5. This makes stack traces available if e.g. a crash happens in chacha_zvkb. No frame pointer maintenance is otherwise required since this is a leaf function. Signed-off-by: Vivian Wang Fixes: bb54668837a0 ("crypto: riscv - add vector crypto accelerated ChaCha20") Cc: stable@vger.kernel.org Link: https://lore.kernel.org/r/20251202-riscv-chacha_zvkb-fp-v2-1-7bd00098c9dc@iscas.ac.cn Signed-off-by: Eric Biggers Signed-off-by: Greg Kroah-Hartman --- lib/crypto/riscv/chacha-riscv64-zvkb.S | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/lib/crypto/riscv/chacha-riscv64-zvkb.S b/lib/crypto/riscv/chacha-riscv64-zvkb.S index b777d0b4e37969..3d183ec818f52a 100644 --- a/lib/crypto/riscv/chacha-riscv64-zvkb.S +++ b/lib/crypto/riscv/chacha-riscv64-zvkb.S @@ -60,7 +60,8 @@ #define VL t2 #define STRIDE t3 #define ROUND_CTR t4 -#define KEY0 s0 +#define KEY0 t5 +// Avoid s0/fp to allow for unwinding #define KEY1 s1 #define KEY2 s2 #define KEY3 s3 @@ -143,7 +144,6 @@ // The updated 32-bit counter is written back to state->x[12] before returning. SYM_FUNC_START(chacha_zvkb) addi sp, sp, -96 - sd s0, 0(sp) sd s1, 8(sp) sd s2, 16(sp) sd s3, 24(sp) @@ -280,7 +280,6 @@ SYM_FUNC_START(chacha_zvkb) bnez NBLOCKS, .Lblock_loop sw COUNTER, 48(STATEP) - ld s0, 0(sp) ld s1, 8(sp) ld s2, 16(sp) ld s3, 24(sp) From 2acb8517429ab42146c6c0ac1daed1f03d2fd125 Mon Sep 17 00:00:00 2001 From: Ilya Dryomov Date: Tue, 2 Dec 2025 10:32:31 +0100 Subject: [PATCH 1643/3902] libceph: make decode_pool() more resilient against corrupted osdmaps commit 8c738512714e8c0aa18f8a10c072d5b01c83db39 upstream. If the osdmap is (maliciously) corrupted such that the encoded length of ceph_pg_pool envelope is less than what is expected for a particular encoding version, out-of-bounds reads may ensue because the only bounds check that is there is based on that length value. This patch adds explicit bounds checks for each field that is decoded or skipped. Cc: stable@vger.kernel.org Reported-by: ziming zhang Signed-off-by: Ilya Dryomov Reviewed-by: Xiubo Li Tested-by: ziming zhang Signed-off-by: Greg Kroah-Hartman --- net/ceph/osdmap.c | 116 +++++++++++++++++++++------------------------- 1 file changed, 52 insertions(+), 64 deletions(-) diff --git a/net/ceph/osdmap.c b/net/ceph/osdmap.c index d245fa508e1cc9..f5f60deb680ae8 100644 --- a/net/ceph/osdmap.c +++ b/net/ceph/osdmap.c @@ -806,51 +806,49 @@ static int decode_pool(void **p, void *end, struct ceph_pg_pool_info *pi) ceph_decode_need(p, end, len, bad); pool_end = *p + len; + ceph_decode_need(p, end, 4 + 4 + 4, bad); pi->type = ceph_decode_8(p); pi->size = ceph_decode_8(p); pi->crush_ruleset = ceph_decode_8(p); pi->object_hash = ceph_decode_8(p); - pi->pg_num = ceph_decode_32(p); pi->pgp_num = ceph_decode_32(p); - *p += 4 + 4; /* skip lpg* */ - *p += 4; /* skip last_change */ - *p += 8 + 4; /* skip snap_seq, snap_epoch */ + /* lpg*, last_change, snap_seq, snap_epoch */ + ceph_decode_skip_n(p, end, 8 + 4 + 8 + 4, bad); /* skip snaps */ - num = ceph_decode_32(p); + ceph_decode_32_safe(p, end, num, bad); while (num--) { - *p += 8; /* snapid key */ - *p += 1 + 1; /* versions */ - len = ceph_decode_32(p); - *p += len; + /* snapid key, pool snap (with versions) */ + ceph_decode_skip_n(p, end, 8 + 2, bad); + ceph_decode_skip_string(p, end, bad); } - /* skip removed_snaps */ - num = ceph_decode_32(p); - *p += num * (8 + 8); + /* removed_snaps */ + ceph_decode_skip_map(p, end, 64, 64, bad); + ceph_decode_need(p, end, 8 + 8 + 4, bad); *p += 8; /* skip auid */ pi->flags = ceph_decode_64(p); *p += 4; /* skip crash_replay_interval */ if (ev >= 7) - pi->min_size = ceph_decode_8(p); + ceph_decode_8_safe(p, end, pi->min_size, bad); else pi->min_size = pi->size - pi->size / 2; if (ev >= 8) - *p += 8 + 8; /* skip quota_max_* */ + /* quota_max_* */ + ceph_decode_skip_n(p, end, 8 + 8, bad); if (ev >= 9) { - /* skip tiers */ - num = ceph_decode_32(p); - *p += num * 8; + /* tiers */ + ceph_decode_skip_set(p, end, 64, bad); + ceph_decode_need(p, end, 8 + 1 + 8 + 8, bad); *p += 8; /* skip tier_of */ *p += 1; /* skip cache_mode */ - pi->read_tier = ceph_decode_64(p); pi->write_tier = ceph_decode_64(p); } else { @@ -858,86 +856,76 @@ static int decode_pool(void **p, void *end, struct ceph_pg_pool_info *pi) pi->write_tier = -1; } - if (ev >= 10) { - /* skip properties */ - num = ceph_decode_32(p); - while (num--) { - len = ceph_decode_32(p); - *p += len; /* key */ - len = ceph_decode_32(p); - *p += len; /* val */ - } - } + if (ev >= 10) + /* properties */ + ceph_decode_skip_map(p, end, string, string, bad); if (ev >= 11) { - /* skip hit_set_params */ - *p += 1 + 1; /* versions */ - len = ceph_decode_32(p); - *p += len; + /* hit_set_params (with versions) */ + ceph_decode_skip_n(p, end, 2, bad); + ceph_decode_skip_string(p, end, bad); - *p += 4; /* skip hit_set_period */ - *p += 4; /* skip hit_set_count */ + /* hit_set_period, hit_set_count */ + ceph_decode_skip_n(p, end, 4 + 4, bad); } if (ev >= 12) - *p += 4; /* skip stripe_width */ + /* stripe_width */ + ceph_decode_skip_32(p, end, bad); - if (ev >= 13) { - *p += 8; /* skip target_max_bytes */ - *p += 8; /* skip target_max_objects */ - *p += 4; /* skip cache_target_dirty_ratio_micro */ - *p += 4; /* skip cache_target_full_ratio_micro */ - *p += 4; /* skip cache_min_flush_age */ - *p += 4; /* skip cache_min_evict_age */ - } + if (ev >= 13) + /* target_max_*, cache_target_*, cache_min_* */ + ceph_decode_skip_n(p, end, 16 + 8 + 8, bad); - if (ev >= 14) { - /* skip erasure_code_profile */ - len = ceph_decode_32(p); - *p += len; - } + if (ev >= 14) + /* erasure_code_profile */ + ceph_decode_skip_string(p, end, bad); /* * last_force_op_resend_preluminous, will be overridden if the * map was encoded with RESEND_ON_SPLIT */ if (ev >= 15) - pi->last_force_request_resend = ceph_decode_32(p); + ceph_decode_32_safe(p, end, pi->last_force_request_resend, bad); else pi->last_force_request_resend = 0; if (ev >= 16) - *p += 4; /* skip min_read_recency_for_promote */ + /* min_read_recency_for_promote */ + ceph_decode_skip_32(p, end, bad); if (ev >= 17) - *p += 8; /* skip expected_num_objects */ + /* expected_num_objects */ + ceph_decode_skip_64(p, end, bad); if (ev >= 19) - *p += 4; /* skip cache_target_dirty_high_ratio_micro */ + /* cache_target_dirty_high_ratio_micro */ + ceph_decode_skip_32(p, end, bad); if (ev >= 20) - *p += 4; /* skip min_write_recency_for_promote */ + /* min_write_recency_for_promote */ + ceph_decode_skip_32(p, end, bad); if (ev >= 21) - *p += 1; /* skip use_gmt_hitset */ + /* use_gmt_hitset */ + ceph_decode_skip_8(p, end, bad); if (ev >= 22) - *p += 1; /* skip fast_read */ + /* fast_read */ + ceph_decode_skip_8(p, end, bad); - if (ev >= 23) { - *p += 4; /* skip hit_set_grade_decay_rate */ - *p += 4; /* skip hit_set_search_last_n */ - } + if (ev >= 23) + /* hit_set_grade_decay_rate, hit_set_search_last_n */ + ceph_decode_skip_n(p, end, 4 + 4, bad); if (ev >= 24) { - /* skip opts */ - *p += 1 + 1; /* versions */ - len = ceph_decode_32(p); - *p += len; + /* opts (with versions) */ + ceph_decode_skip_n(p, end, 2, bad); + ceph_decode_skip_string(p, end, bad); } if (ev >= 25) - pi->last_force_request_resend = ceph_decode_32(p); + ceph_decode_32_safe(p, end, pi->last_force_request_resend, bad); /* ignore the rest */ From b3b320a900a54c48ff134131dfff962f48e7264f Mon Sep 17 00:00:00 2001 From: Finn Thain Date: Mon, 10 Nov 2025 10:30:22 +1100 Subject: [PATCH 1644/3902] powerpc: Add reloc_offset() to font bitmap pointer used for bootx_printf() commit b94b73567561642323617155bf4ee24ef0d258fe upstream. Since Linux v6.7, booting using BootX on an Old World PowerMac produces an early crash. Stan Johnson writes, "the symptoms are that the screen goes blank and the backlight stays on, and the system freezes (Linux doesn't boot)." Further testing revealed that the failure can be avoided by disabling CONFIG_BOOTX_TEXT. Bisection revealed that the regression was caused by a change to the font bitmap pointer that's used when btext_init() begins painting characters on the display, early in the boot process. Christophe Leroy explains, "before kernel text is relocated to its final location ... data is addressed with an offset which is added to the Global Offset Table (GOT) entries at the start of bootx_init() by function reloc_got2(). But the pointers that are located inside a structure are not referenced in the GOT and are therefore not updated by reloc_got2(). It is therefore needed to apply the offset manually by using PTRRELOC() macro." Cc: stable@vger.kernel.org Link: https://lists.debian.org/debian-powerpc/2025/10/msg00111.html Link: https://lore.kernel.org/linuxppc-dev/d81ddca8-c5ee-d583-d579-02b19ed95301@yahoo.com/ Reported-by: Cedar Maxwell Closes: https://lists.debian.org/debian-powerpc/2025/09/msg00031.html Bisected-by: Stan Johnson Tested-by: Stan Johnson Fixes: 0ebc7feae79a ("powerpc: Use shared font data") Suggested-by: Christophe Leroy Signed-off-by: Finn Thain Reviewed-by: Christophe Leroy Signed-off-by: Madhavan Srinivasan Link: https://patch.msgid.link/22b3b247425a052b079ab84da926706b3702c2c7.1762731022.git.fthain@linux-m68k.org Signed-off-by: Greg Kroah-Hartman --- arch/powerpc/kernel/btext.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/arch/powerpc/kernel/btext.c b/arch/powerpc/kernel/btext.c index 7f63f1cdc6c39e..ca00c4824e313f 100644 --- a/arch/powerpc/kernel/btext.c +++ b/arch/powerpc/kernel/btext.c @@ -20,6 +20,7 @@ #include #include #include +#include #define NO_SCROLL @@ -463,7 +464,7 @@ static noinline void draw_byte(unsigned char c, long locX, long locY) { unsigned char *base = calc_base(locX << 3, locY << 4); unsigned int font_index = c * 16; - const unsigned char *font = font_sun_8x16.data + font_index; + const unsigned char *font = PTRRELOC(font_sun_8x16.data) + font_index; int rb = dispDeviceRowBytes; rmci_maybe_on(); From 16fa73b11a9472444f855b386e478d6ebfb2348a Mon Sep 17 00:00:00 2001 From: Sean Christopherson Date: Thu, 13 Nov 2025 12:51:11 -0800 Subject: [PATCH 1645/3902] KVM: x86: WARN if hrtimer callback for periodic APIC timer fires with period=0 commit 0ea9494be9c931ddbc084ad5e11fda91b554cf47 upstream. WARN and don't restart the hrtimer if KVM's callback runs with the guest's APIC timer in periodic mode but with a period of '0', as not advancing the hrtimer's deadline would put the CPU into an infinite loop of hrtimer events. Observing a period of '0' should be impossible, even when the hrtimer is running on a different CPU than the vCPU, as KVM is supposed to cancel the hrtimer before changing (or zeroing) the period, e.g. when switching from periodic to one-shot. Cc: stable@vger.kernel.org Link: https://patch.msgid.link/20251113205114.1647493-2-seanjc@google.com Signed-off-by: Sean Christopherson Signed-off-by: Greg Kroah-Hartman --- arch/x86/kvm/lapic.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/arch/x86/kvm/lapic.c b/arch/x86/kvm/lapic.c index 0ae7f913d7826a..78b74ba17592be 100644 --- a/arch/x86/kvm/lapic.c +++ b/arch/x86/kvm/lapic.c @@ -2970,7 +2970,7 @@ static enum hrtimer_restart apic_timer_fn(struct hrtimer *data) apic_timer_expired(apic, true); - if (lapic_is_periodic(apic)) { + if (lapic_is_periodic(apic) && !WARN_ON_ONCE(!apic->lapic_timer.period)) { advance_periodic_target_expiration(apic); hrtimer_add_expires_ns(&ktimer->timer, ktimer->period); return HRTIMER_RESTART; From a16ccbffb144f2b91084375bd77a1b1aab01d28e Mon Sep 17 00:00:00 2001 From: fuqiang wang Date: Thu, 13 Nov 2025 12:51:12 -0800 Subject: [PATCH 1646/3902] KVM: x86: Explicitly set new periodic hrtimer expiration in apic_timer_fn() commit 9633f180ce994ab293ce4924a9b7aaf4673aa114 upstream. When restarting an hrtimer to emulate a the guest's APIC timer in periodic mode, explicitly set the expiration using the target expiration computed by advance_periodic_target_expiration() instead of adding the period to the existing timer. This will allow making adjustments to the expiration, e.g. to deal with expirations far in the past, without having to implement the same logic in both advance_periodic_target_expiration() and apic_timer_fn(). Cc: stable@vger.kernel.org Signed-off-by: fuqiang wang [sean: split to separate patch, write changelog] Link: https://patch.msgid.link/20251113205114.1647493-3-seanjc@google.com Signed-off-by: Sean Christopherson Signed-off-by: Greg Kroah-Hartman --- arch/x86/kvm/lapic.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/arch/x86/kvm/lapic.c b/arch/x86/kvm/lapic.c index 78b74ba17592be..a5c927e7bae686 100644 --- a/arch/x86/kvm/lapic.c +++ b/arch/x86/kvm/lapic.c @@ -2972,7 +2972,7 @@ static enum hrtimer_restart apic_timer_fn(struct hrtimer *data) if (lapic_is_periodic(apic) && !WARN_ON_ONCE(!apic->lapic_timer.period)) { advance_periodic_target_expiration(apic); - hrtimer_add_expires_ns(&ktimer->timer, ktimer->period); + hrtimer_set_expires(&ktimer->timer, ktimer->target_expiration); return HRTIMER_RESTART; } else return HRTIMER_NORESTART; From e23f46f1a971c73dad2fd63e1408696114ddebe2 Mon Sep 17 00:00:00 2001 From: fuqiang wang Date: Thu, 13 Nov 2025 12:51:13 -0800 Subject: [PATCH 1647/3902] KVM: x86: Fix VM hard lockup after prolonged inactivity with periodic HV timer commit 18ab3fc8e880791aa9f7c000261320fc812b5465 upstream. When advancing the target expiration for the guest's APIC timer in periodic mode, set the expiration to "now" if the target expiration is in the past (similar to what is done in update_target_expiration()). Blindly adding the period to the previous target expiration can result in KVM generating a practically unbounded number of hrtimer IRQs due to programming an expired timer over and over. In extreme scenarios, e.g. if userspace pauses/suspends a VM for an extended duration, this can even cause hard lockups in the host. Currently, the bug only affects Intel CPUs when using the hypervisor timer (HV timer), a.k.a. the VMX preemption timer. Unlike the software timer, a.k.a. hrtimer, which KVM keeps running even on exits to userspace, the HV timer only runs while the guest is active. As a result, if the vCPU does not run for an extended duration, there will be a huge gap between the target expiration and the current time the vCPU resumes running. Because the target expiration is incremented by only one period on each timer expiration, this leads to a series of timer expirations occurring rapidly after the vCPU/VM resumes. More critically, when the vCPU first triggers a periodic HV timer expiration after resuming, advancing the expiration by only one period will result in a target expiration in the past. As a result, the delta may be calculated as a negative value. When the delta is converted into an absolute value (tscdeadline is an unsigned u64), the resulting value can overflow what the HV timer is capable of programming. I.e. the large value will exceed the VMX Preemption Timer's maximum bit width of cpu_preemption_timer_multi + 32, and thus cause KVM to switch from the HV timer to the software timer (hrtimers). After switching to the software timer, periodic timer expiration callbacks may be executed consecutively within a single clock interrupt handler, because hrtimers honors KVM's request for an expiration in the past and immediately re-invokes KVM's callback after reprogramming. And because the interrupt handler runs with IRQs disabled, restarting KVM's hrtimer over and over until the target expiration is advanced to "now" can result in a hard lockup. E.g. the following hard lockup was triggered in the host when running a Windows VM (only relevant because it used the APIC timer in periodic mode) after resuming the VM from a long suspend (in the host). NMI watchdog: Watchdog detected hard LOCKUP on cpu 45 ... RIP: 0010:advance_periodic_target_expiration+0x4d/0x80 [kvm] ... RSP: 0018:ff4f88f5d98d8ef0 EFLAGS: 00000046 RAX: fff0103f91be678e RBX: fff0103f91be678e RCX: 00843a7d9e127bcc RDX: 0000000000000002 RSI: 0052ca4003697505 RDI: ff440d5bfbdbd500 RBP: ff440d5956f99200 R08: ff2ff2a42deb6a84 R09: 000000000002a6c0 R10: 0122d794016332b3 R11: 0000000000000000 R12: ff440db1af39cfc0 R13: ff440db1af39cfc0 R14: ffffffffc0d4a560 R15: ff440db1af39d0f8 FS: 00007f04a6ffd700(0000) GS:ff440db1af380000(0000) knlGS:000000e38a3b8000 CS: 0010 DS: 0000 ES: 0000 CR0: 0000000080050033 CR2: 000000d5651feff8 CR3: 000000684e038002 CR4: 0000000000773ee0 PKRU: 55555554 Call Trace: apic_timer_fn+0x31/0x50 [kvm] __hrtimer_run_queues+0x100/0x280 hrtimer_interrupt+0x100/0x210 ? ttwu_do_wakeup+0x19/0x160 smp_apic_timer_interrupt+0x6a/0x130 apic_timer_interrupt+0xf/0x20 Moreover, if the suspend duration of the virtual machine is not long enough to trigger a hard lockup in this scenario, since commit 98c25ead5eda ("KVM: VMX: Move preemption timer <=> hrtimer dance to common x86"), KVM will continue using the software timer until the guest reprograms the APIC timer in some way. Since the periodic timer does not require frequent APIC timer register programming, the guest may continue to use the software timer in perpetuity. Fixes: d8f2f498d9ed ("x86/kvm: fix LAPIC timer drift when guest uses periodic mode") Cc: stable@vger.kernel.org Signed-off-by: fuqiang wang [sean: massage comments and changelog] Link: https://patch.msgid.link/20251113205114.1647493-4-seanjc@google.com Signed-off-by: Sean Christopherson Signed-off-by: Greg Kroah-Hartman --- arch/x86/kvm/lapic.c | 28 +++++++++++++++++++++++----- 1 file changed, 23 insertions(+), 5 deletions(-) diff --git a/arch/x86/kvm/lapic.c b/arch/x86/kvm/lapic.c index a5c927e7bae686..8b6ec3304100ff 100644 --- a/arch/x86/kvm/lapic.c +++ b/arch/x86/kvm/lapic.c @@ -2131,15 +2131,33 @@ static void advance_periodic_target_expiration(struct kvm_lapic *apic) ktime_t delta; /* - * Synchronize both deadlines to the same time source or - * differences in the periods (caused by differences in the - * underlying clocks or numerical approximation errors) will - * cause the two to drift apart over time as the errors - * accumulate. + * Use kernel time as the time source for both the hrtimer deadline and + * TSC-based deadline so that they stay synchronized. Computing each + * deadline independently will cause the two deadlines to drift apart + * over time as differences in the periods accumulate, e.g. due to + * differences in the underlying clocks or numerical approximation errors. */ apic->lapic_timer.target_expiration = ktime_add_ns(apic->lapic_timer.target_expiration, apic->lapic_timer.period); + + /* + * If the new expiration is in the past, e.g. because userspace stopped + * running the VM for an extended duration, then force the expiration + * to "now" and don't try to play catch-up with the missed events. KVM + * will only deliver a single interrupt regardless of how many events + * are pending, i.e. restarting the timer with an expiration in the + * past will do nothing more than waste host cycles, and can even lead + * to a hard lockup in extreme cases. + */ + if (ktime_before(apic->lapic_timer.target_expiration, now)) + apic->lapic_timer.target_expiration = now; + + /* + * Note, ensuring the expiration isn't in the past also prevents delta + * from going negative, which could cause the TSC deadline to become + * excessively large due to it an unsigned value. + */ delta = ktime_sub(apic->lapic_timer.target_expiration, now); apic->lapic_timer.tscdeadline = kvm_read_l1_tsc(apic->vcpu, tscl) + nsec_to_cycles(apic->vcpu, delta); From c1f8d4d41ed3c0f8b429cfbe2c6d4ca5477ad418 Mon Sep 17 00:00:00 2001 From: Yosry Ahmed Date: Fri, 24 Oct 2025 19:29:18 +0000 Subject: [PATCH 1648/3902] KVM: nSVM: Avoid incorrect injection of SVM_EXIT_CR0_SEL_WRITE MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit commit 3d80f4c93d3d26d0f9a0dd2844961a632eeea634 upstream. When emulating L2 instructions, svm_check_intercept() checks whether a write to CR0 should trigger a synthesized #VMEXIT with SVM_EXIT_CR0_SEL_WRITE. However, it does not check whether L1 enabled the intercept for SVM_EXIT_WRITE_CR0, which has higher priority according to the APM (24593—Rev. 3.42—March 2024, Table 15-7): When both selective and non-selective CR0-write intercepts are active at the same time, the non-selective intercept takes priority. With respect to exceptions, the priority of this intercept is the same as the generic CR0-write intercept. Make sure L1 does NOT intercept SVM_EXIT_WRITE_CR0 before checking if SVM_EXIT_CR0_SEL_WRITE needs to be injected. Opportunistically tweak the "not CR0" logic to explicitly bail early so that it's more obvious that only CR0 has a selective intercept, and that modifying icpt_info.exit_code is functionally necessary so that the call to nested_svm_exit_handled() checks the correct exit code. Fixes: cfec82cb7d31 ("KVM: SVM: Add intercept check for emulated cr accesses") Cc: stable@vger.kernel.org Signed-off-by: Yosry Ahmed Link: https://patch.msgid.link/20251024192918.3191141-4-yosry.ahmed@linux.dev [sean: isolate non-CR0 write logic, tweak comments accordingly] Signed-off-by: Sean Christopherson Signed-off-by: Greg Kroah-Hartman --- arch/x86/kvm/svm/svm.c | 24 +++++++++++++++++++----- 1 file changed, 19 insertions(+), 5 deletions(-) diff --git a/arch/x86/kvm/svm/svm.c b/arch/x86/kvm/svm/svm.c index f2fa69dd5cc72c..4154b220fec95d 100644 --- a/arch/x86/kvm/svm/svm.c +++ b/arch/x86/kvm/svm/svm.c @@ -4528,15 +4528,29 @@ static int svm_check_intercept(struct kvm_vcpu *vcpu, case SVM_EXIT_WRITE_CR0: { unsigned long cr0, val; - if (info->intercept == x86_intercept_cr_write) + /* + * Adjust the exit code accordingly if a CR other than CR0 is + * being written, and skip straight to the common handling as + * only CR0 has an additional selective intercept. + */ + if (info->intercept == x86_intercept_cr_write && info->modrm_reg) { icpt_info.exit_code += info->modrm_reg; + break; + } - if (icpt_info.exit_code != SVM_EXIT_WRITE_CR0 || - info->intercept == x86_intercept_clts) + /* + * Convert the exit_code to SVM_EXIT_CR0_SEL_WRITE if a + * selective CR0 intercept is triggered (the common logic will + * treat the selective intercept as being enabled). Note, the + * unconditional intercept has higher priority, i.e. this is + * only relevant if *only* the selective intercept is enabled. + */ + if (vmcb12_is_intercept(&svm->nested.ctl, INTERCEPT_CR0_WRITE) || + !(vmcb12_is_intercept(&svm->nested.ctl, INTERCEPT_SELECTIVE_CR0))) break; - if (!(vmcb12_is_intercept(&svm->nested.ctl, - INTERCEPT_SELECTIVE_CR0))) + /* CLTS never triggers INTERCEPT_SELECTIVE_CR0 */ + if (info->intercept == x86_intercept_clts) break; cr0 = vcpu->arch.cr0 & ~SVM_CR0_SELECTIVE_MASK; From 201b4a7ebbe0bcc67010ee364a4f93b0e7e18984 Mon Sep 17 00:00:00 2001 From: Jim Mattson Date: Mon, 22 Sep 2025 09:29:23 -0700 Subject: [PATCH 1649/3902] KVM: SVM: Mark VMCB_NPT as dirty on nested VMRUN commit 7c8b465a1c91f674655ea9cec5083744ec5f796a upstream. Mark the VMCB_NPT bit as dirty in nested_vmcb02_prepare_save() on every nested VMRUN. If L1 changes the PAT MSR between two VMRUN instructions on the same L1 vCPU, the g_pat field in the associated vmcb02 will change, and the VMCB_NPT clean bit should be cleared. Fixes: 4bb170a5430b ("KVM: nSVM: do not mark all VMCB02 fields dirty on nested vmexit") Cc: stable@vger.kernel.org Signed-off-by: Jim Mattson Link: https://lore.kernel.org/r/20250922162935.621409-3-jmattson@google.com Signed-off-by: Sean Christopherson Signed-off-by: Greg Kroah-Hartman --- arch/x86/kvm/svm/nested.c | 1 + 1 file changed, 1 insertion(+) diff --git a/arch/x86/kvm/svm/nested.c b/arch/x86/kvm/svm/nested.c index da6e80b3ac353c..6ae021b3630bdf 100644 --- a/arch/x86/kvm/svm/nested.c +++ b/arch/x86/kvm/svm/nested.c @@ -613,6 +613,7 @@ static void nested_vmcb02_prepare_save(struct vcpu_svm *svm, struct vmcb *vmcb12 struct kvm_vcpu *vcpu = &svm->vcpu; nested_vmcb02_compute_g_pat(svm); + vmcb_mark_dirty(vmcb02, VMCB_NPT); /* Load the nested guest state */ if (svm->nested.vmcb12_gpa != svm->nested.last_vmcb12_gpa) { From 2853896ef465fca266f0fa27abd3eddaecdace8f Mon Sep 17 00:00:00 2001 From: Sean Christopherson Date: Tue, 7 Oct 2025 15:30:57 -0700 Subject: [PATCH 1650/3902] KVM: selftests: Forcefully override ARCH from x86_64 to x86 commit 17e5a9b77716564540d81f0c1e6082d28cf305c9 upstream. Forcefully override ARCH from x86_64 to x86 to handle the scenario where the user specifies ARCH=x86_64 on the command line. Fixes: 9af04539d474 ("KVM: selftests: Override ARCH for x86_64 instead of using ARCH_DIR") Cc: stable@vger.kernel.org Reported-by: David Matlack Closes: https://lore.kernel.org/all/20250724213130.3374922-1-dmatlack@google.com Link: https://lore.kernel.org/r/20251007223057.368082-1-seanjc@google.com Signed-off-by: Sean Christopherson Signed-off-by: Greg Kroah-Hartman --- tools/testing/selftests/kvm/Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/testing/selftests/kvm/Makefile b/tools/testing/selftests/kvm/Makefile index d9fffe06d3eae4..f2b223072b626a 100644 --- a/tools/testing/selftests/kvm/Makefile +++ b/tools/testing/selftests/kvm/Makefile @@ -6,7 +6,7 @@ ARCH ?= $(SUBARCH) ifeq ($(ARCH),$(filter $(ARCH),arm64 s390 riscv x86 x86_64 loongarch)) # Top-level selftests allows ARCH=x86_64 :-( ifeq ($(ARCH),x86_64) - ARCH := x86 + override ARCH := x86 endif include Makefile.kvm else From 691ff7e13f0d825b91bc50cb4ceb00af2178e9ec Mon Sep 17 00:00:00 2001 From: Yosry Ahmed Date: Fri, 24 Oct 2025 19:29:17 +0000 Subject: [PATCH 1651/3902] KVM: nSVM: Propagate SVM_EXIT_CR0_SEL_WRITE correctly for LMSW emulation MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit commit 5674a76db0213f9db1e4d08e847ff649b46889c0 upstream. When emulating L2 instructions, svm_check_intercept() checks whether a write to CR0 should trigger a synthesized #VMEXIT with SVM_EXIT_CR0_SEL_WRITE. For MOV-to-CR0, SVM_EXIT_CR0_SEL_WRITE is only triggered if any bit other than CR0.MP and CR0.TS is updated. However, according to the APM (24593—Rev. 3.42—March 2024, Table 15-7): The LMSW instruction treats the selective CR0-write intercept as a non-selective intercept (i.e., it intercepts regardless of the value being written). Skip checking the changed bits for x86_intercept_lmsw and always inject SVM_EXIT_CR0_SEL_WRITE. Fixes: cfec82cb7d31 ("KVM: SVM: Add intercept check for emulated cr accesses") Cc: stable@vger.kernel.org Reported-by: Matteo Rizzo Signed-off-by: Yosry Ahmed Link: https://patch.msgid.link/20251024192918.3191141-3-yosry.ahmed@linux.dev Signed-off-by: Sean Christopherson Signed-off-by: Greg Kroah-Hartman --- arch/x86/kvm/svm/svm.c | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/arch/x86/kvm/svm/svm.c b/arch/x86/kvm/svm/svm.c index 4154b220fec95d..a856f063b82502 100644 --- a/arch/x86/kvm/svm/svm.c +++ b/arch/x86/kvm/svm/svm.c @@ -4553,20 +4553,20 @@ static int svm_check_intercept(struct kvm_vcpu *vcpu, if (info->intercept == x86_intercept_clts) break; - cr0 = vcpu->arch.cr0 & ~SVM_CR0_SELECTIVE_MASK; - val = info->src_val & ~SVM_CR0_SELECTIVE_MASK; - + /* LMSW always triggers INTERCEPT_SELECTIVE_CR0 */ if (info->intercept == x86_intercept_lmsw) { - cr0 &= 0xfUL; - val &= 0xfUL; - /* lmsw can't clear PE - catch this here */ - if (cr0 & X86_CR0_PE) - val |= X86_CR0_PE; + icpt_info.exit_code = SVM_EXIT_CR0_SEL_WRITE; + break; } + /* + * MOV-to-CR0 only triggers INTERCEPT_SELECTIVE_CR0 if any bit + * other than SVM_CR0_SELECTIVE_MASK is changed. + */ + cr0 = vcpu->arch.cr0 & ~SVM_CR0_SELECTIVE_MASK; + val = info->src_val & ~SVM_CR0_SELECTIVE_MASK; if (cr0 ^ val) icpt_info.exit_code = SVM_EXIT_CR0_SEL_WRITE; - break; } case SVM_EXIT_READ_DR0: From 2c3719a04b04a251782e77618d4255907ea4bbc8 Mon Sep 17 00:00:00 2001 From: Wanpeng Li Date: Mon, 10 Nov 2025 11:32:27 +0800 Subject: [PATCH 1652/3902] KVM: Fix last_boosted_vcpu index assignment bug commit 32bd348be3fa07b26c5ea6b818a161c142dcc2f2 upstream. In kvm_vcpu_on_spin(), the loop counter 'i' is incorrectly written to last_boosted_vcpu instead of the actual vCPU index 'idx'. This causes last_boosted_vcpu to store the loop iteration count rather than the vCPU index, leading to incorrect round-robin behavior in subsequent directed yield operations. Fix this by using 'idx' instead of 'i' in the assignment. Signed-off-by: Wanpeng Li Reviewed-by: Sean Christopherson Message-ID: <20251110033232.12538-7-kernellwp@gmail.com> Cc: stable@vger.kernel.org Signed-off-by: Paolo Bonzini Signed-off-by: Greg Kroah-Hartman --- virt/kvm/kvm_main.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/virt/kvm/kvm_main.c b/virt/kvm/kvm_main.c index 01ea4f8351a617..7fea6ba91c1ef3 100644 --- a/virt/kvm/kvm_main.c +++ b/virt/kvm/kvm_main.c @@ -4026,7 +4026,7 @@ void kvm_vcpu_on_spin(struct kvm_vcpu *me, bool yield_to_kernel_mode) yielded = kvm_vcpu_yield_to(vcpu); if (yielded > 0) { - WRITE_ONCE(kvm->last_boosted_vcpu, i); + WRITE_ONCE(kvm->last_boosted_vcpu, idx); break; } else if (yielded < 0 && !--try) { break; From da28922a8c600574fac7366c0f2e5005f41b1326 Mon Sep 17 00:00:00 2001 From: Jim Mattson Date: Mon, 22 Sep 2025 09:29:22 -0700 Subject: [PATCH 1653/3902] KVM: SVM: Mark VMCB_PERM_MAP as dirty on nested VMRUN commit 93c9e107386dbe1243287a5b14ceca894de372b9 upstream. Mark the VMCB_PERM_MAP bit as dirty in nested_vmcb02_prepare_control() on every nested VMRUN. If L1 changes MSR interception (INTERCEPT_MSR_PROT) between two VMRUN instructions on the same L1 vCPU, the msrpm_base_pa in the associated vmcb02 will change, and the VMCB_PERM_MAP clean bit should be cleared. Fixes: 4bb170a5430b ("KVM: nSVM: do not mark all VMCB02 fields dirty on nested vmexit") Reported-by: Matteo Rizzo Cc: stable@vger.kernel.org Signed-off-by: Jim Mattson Link: https://lore.kernel.org/r/20250922162935.621409-2-jmattson@google.com Signed-off-by: Sean Christopherson Signed-off-by: Greg Kroah-Hartman --- arch/x86/kvm/svm/nested.c | 1 + 1 file changed, 1 insertion(+) diff --git a/arch/x86/kvm/svm/nested.c b/arch/x86/kvm/svm/nested.c index 6ae021b3630bdf..2df6ec74d6ee60 100644 --- a/arch/x86/kvm/svm/nested.c +++ b/arch/x86/kvm/svm/nested.c @@ -752,6 +752,7 @@ static void nested_vmcb02_prepare_control(struct vcpu_svm *svm, vmcb02->control.nested_ctl = vmcb01->control.nested_ctl; vmcb02->control.iopm_base_pa = vmcb01->control.iopm_base_pa; vmcb02->control.msrpm_base_pa = vmcb01->control.msrpm_base_pa; + vmcb_mark_dirty(vmcb02, VMCB_PERM_MAP); /* * Stash vmcb02's counter if the guest hasn't moved past the guilty From 3fe48378e53fe38948ab06cfde0a4cea5f53258a Mon Sep 17 00:00:00 2001 From: Sean Christopherson Date: Thu, 30 Oct 2025 12:15:25 -0700 Subject: [PATCH 1654/3902] KVM: TDX: Explicitly set user-return MSRs that *may* be clobbered by the TDX-Module commit c0711f8c610e1634ed54fb04da1e82252730306a upstream. Set all user-return MSRs to their post-TD-exit value when preparing to run a TDX vCPU to ensure the value that KVM expects to be loaded after running the vCPU is indeed the value that's loaded in hardware. If the TDX-Module doesn't actually enter the guest, i.e. doesn't do VM-Enter, then it won't "restore" VMM state, i.e. won't clobber user-return MSRs to their expected post-run values, in which case simply updating KVM's "cached" value will effectively corrupt the cache due to hardware still holding the original value. In theory, KVM could conditionally update the current user-return value if and only if tdh_vp_enter() succeeds, but in practice "success" doesn't guarantee the TDX-Module actually entered the guest, e.g. if the TDX-Module synthesizes an EPT Violation because it suspects a zero-step attack. Force-load the expected values instead of trying to decipher whether or not the TDX-Module restored/clobbered MSRs, as the risk doesn't justify the benefits. Effectively avoiding four WRMSRs once per run loop (even if the vCPU is scheduled out, user-return MSRs only need to be reloaded if the CPU exits to userspace or runs a non-TDX vCPU) is likely in the noise when amortized over all entries, given the cost of running a TDX vCPU. E.g. the cost of the WRMSRs is somewhere between ~300 and ~500 cycles, whereas the cost of a _single_ roundtrip to/from a TDX guest is thousands of cycles. Fixes: e0b4f31a3c65 ("KVM: TDX: restore user ret MSRs") Cc: stable@vger.kernel.org Cc: Yan Zhao Cc: Xiaoyao Li Cc: Rick Edgecombe Reviewed-by: Xiaoyao Li Link: https://patch.msgid.link/20251030191528.3380553-2-seanjc@google.com Signed-off-by: Sean Christopherson Signed-off-by: Greg Kroah-Hartman --- arch/x86/include/asm/kvm_host.h | 1 - arch/x86/kvm/vmx/tdx.c | 56 ++++++++++++++------------------- arch/x86/kvm/vmx/tdx.h | 1 - arch/x86/kvm/x86.c | 9 ------ 4 files changed, 23 insertions(+), 44 deletions(-) diff --git a/arch/x86/include/asm/kvm_host.h b/arch/x86/include/asm/kvm_host.h index 974d64bf0a4d2c..b74ae7183f3ae8 100644 --- a/arch/x86/include/asm/kvm_host.h +++ b/arch/x86/include/asm/kvm_host.h @@ -2387,7 +2387,6 @@ int kvm_pv_send_ipi(struct kvm *kvm, unsigned long ipi_bitmap_low, int kvm_add_user_return_msr(u32 msr); int kvm_find_user_return_msr(u32 msr); int kvm_set_user_return_msr(unsigned index, u64 val, u64 mask); -void kvm_user_return_msr_update_cache(unsigned int index, u64 val); u64 kvm_get_user_return_msr(unsigned int slot); static inline bool kvm_is_supported_user_return_msr(u32 msr) diff --git a/arch/x86/kvm/vmx/tdx.c b/arch/x86/kvm/vmx/tdx.c index 0a49c863c811bf..d9ffc16dfefe61 100644 --- a/arch/x86/kvm/vmx/tdx.c +++ b/arch/x86/kvm/vmx/tdx.c @@ -763,25 +763,6 @@ static bool tdx_protected_apic_has_interrupt(struct kvm_vcpu *vcpu) return tdx_vcpu_state_details_intr_pending(vcpu_state_details); } -/* - * Compared to vmx_prepare_switch_to_guest(), there is not much to do - * as SEAMCALL/SEAMRET calls take care of most of save and restore. - */ -void tdx_prepare_switch_to_guest(struct kvm_vcpu *vcpu) -{ - struct vcpu_vt *vt = to_vt(vcpu); - - if (vt->guest_state_loaded) - return; - - if (likely(is_64bit_mm(current->mm))) - vt->msr_host_kernel_gs_base = current->thread.gsbase; - else - vt->msr_host_kernel_gs_base = read_msr(MSR_KERNEL_GS_BASE); - - vt->guest_state_loaded = true; -} - struct tdx_uret_msr { u32 msr; unsigned int slot; @@ -795,19 +776,38 @@ static struct tdx_uret_msr tdx_uret_msrs[] = { {.msr = MSR_TSC_AUX,}, }; -static void tdx_user_return_msr_update_cache(void) +void tdx_prepare_switch_to_guest(struct kvm_vcpu *vcpu) { + struct vcpu_vt *vt = to_vt(vcpu); int i; + if (vt->guest_state_loaded) + return; + + if (likely(is_64bit_mm(current->mm))) + vt->msr_host_kernel_gs_base = current->thread.gsbase; + else + vt->msr_host_kernel_gs_base = read_msr(MSR_KERNEL_GS_BASE); + + vt->guest_state_loaded = true; + + /* + * Explicitly set user-return MSRs that are clobbered by the TDX-Module + * if VP.ENTER succeeds, i.e. on TD-Exit, with the values that would be + * written by the TDX-Module. Don't rely on the TDX-Module to actually + * clobber the MSRs, as the contract is poorly defined and not upheld. + * E.g. the TDX-Module will synthesize an EPT Violation without doing + * VM-Enter if it suspects a zero-step attack, and never "restore" VMM + * state. + */ for (i = 0; i < ARRAY_SIZE(tdx_uret_msrs); i++) - kvm_user_return_msr_update_cache(tdx_uret_msrs[i].slot, - tdx_uret_msrs[i].defval); + kvm_set_user_return_msr(tdx_uret_msrs[i].slot, + tdx_uret_msrs[i].defval, -1ull); } static void tdx_prepare_switch_to_host(struct kvm_vcpu *vcpu) { struct vcpu_vt *vt = to_vt(vcpu); - struct vcpu_tdx *tdx = to_tdx(vcpu); if (!vt->guest_state_loaded) return; @@ -815,11 +815,6 @@ static void tdx_prepare_switch_to_host(struct kvm_vcpu *vcpu) ++vcpu->stat.host_state_reload; wrmsrl(MSR_KERNEL_GS_BASE, vt->msr_host_kernel_gs_base); - if (tdx->guest_entered) { - tdx_user_return_msr_update_cache(); - tdx->guest_entered = false; - } - vt->guest_state_loaded = false; } @@ -1059,7 +1054,6 @@ fastpath_t tdx_vcpu_run(struct kvm_vcpu *vcpu, u64 run_flags) update_debugctlmsr(vcpu->arch.host_debugctl); tdx_load_host_xsave_state(vcpu); - tdx->guest_entered = true; vcpu->arch.regs_avail &= TDX_REGS_AVAIL_SET; @@ -3447,10 +3441,6 @@ static int __init __tdx_bringup(void) /* * Check if MSRs (tdx_uret_msrs) can be saved/restored * before returning to user space. - * - * this_cpu_ptr(user_return_msrs)->registered isn't checked - * because the registration is done at vcpu runtime by - * tdx_user_return_msr_update_cache(). */ tdx_uret_msrs[i].slot = kvm_find_user_return_msr(tdx_uret_msrs[i].msr); if (tdx_uret_msrs[i].slot == -1) { diff --git a/arch/x86/kvm/vmx/tdx.h b/arch/x86/kvm/vmx/tdx.h index ca39a9391db1e3..7f258870dc4109 100644 --- a/arch/x86/kvm/vmx/tdx.h +++ b/arch/x86/kvm/vmx/tdx.h @@ -67,7 +67,6 @@ struct vcpu_tdx { u64 vp_enter_ret; enum vcpu_tdx_state state; - bool guest_entered; u64 map_gpa_next; u64 map_gpa_end; diff --git a/arch/x86/kvm/x86.c b/arch/x86/kvm/x86.c index 5d3297cef8a996..19d2d6d9e64ab6 100644 --- a/arch/x86/kvm/x86.c +++ b/arch/x86/kvm/x86.c @@ -681,15 +681,6 @@ int kvm_set_user_return_msr(unsigned slot, u64 value, u64 mask) } EXPORT_SYMBOL_FOR_KVM_INTERNAL(kvm_set_user_return_msr); -void kvm_user_return_msr_update_cache(unsigned int slot, u64 value) -{ - struct kvm_user_return_msrs *msrs = this_cpu_ptr(user_return_msrs); - - msrs->values[slot].curr = value; - kvm_user_return_register_notifier(msrs); -} -EXPORT_SYMBOL_FOR_KVM_INTERNAL(kvm_user_return_msr_update_cache); - u64 kvm_get_user_return_msr(unsigned int slot) { return this_cpu_ptr(user_return_msrs)->values[slot].curr; From 6617e02cf8dfb71b406f94337df8083bb9d5ac02 Mon Sep 17 00:00:00 2001 From: Dongli Zhang Date: Fri, 5 Dec 2025 15:19:05 -0800 Subject: [PATCH 1655/3902] KVM: nVMX: Immediately refresh APICv controls as needed on nested VM-Exit commit 29763138830916f46daaa50e83e7f4f907a3236b upstream. If an APICv status updated was pended while L2 was active, immediately refresh vmcs01's controls instead of pending KVM_REQ_APICV_UPDATE as kvm_vcpu_update_apicv() only calls into vendor code if a change is necessary. E.g. if APICv is inhibited, and then activated while L2 is running: kvm_vcpu_update_apicv() | -> __kvm_vcpu_update_apicv() | -> apic->apicv_active = true | -> vmx_refresh_apicv_exec_ctrl() | -> vmx->nested.update_vmcs01_apicv_status = true | -> return Then L2 exits to L1: __nested_vmx_vmexit() | -> kvm_make_request(KVM_REQ_APICV_UPDATE) vcpu_enter_guest(): KVM_REQ_APICV_UPDATE -> kvm_vcpu_update_apicv() | -> __kvm_vcpu_update_apicv() | -> return // because if (apic->apicv_active == activate) Reported-by: Chao Gao Closes: https://lore.kernel.org/all/aQ2jmnN8wUYVEawF@intel.com Fixes: 7c69661e225c ("KVM: nVMX: Defer APICv updates while L2 is active until L1 is active") Cc: stable@vger.kernel.org Signed-off-by: Dongli Zhang [sean: write changelog] Link: https://patch.msgid.link/20251205231913.441872-3-seanjc@google.com Signed-off-by: Sean Christopherson Signed-off-by: Greg Kroah-Hartman --- arch/x86/kvm/vmx/nested.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/arch/x86/kvm/vmx/nested.c b/arch/x86/kvm/vmx/nested.c index bcea087b642fd0..1725c6a94f99bc 100644 --- a/arch/x86/kvm/vmx/nested.c +++ b/arch/x86/kvm/vmx/nested.c @@ -19,6 +19,7 @@ #include "trace.h" #include "vmx.h" #include "smm.h" +#include "x86_ops.h" static bool __read_mostly enable_shadow_vmcs = 1; module_param_named(enable_shadow_vmcs, enable_shadow_vmcs, bool, S_IRUGO); @@ -5216,7 +5217,7 @@ void __nested_vmx_vmexit(struct kvm_vcpu *vcpu, u32 vm_exit_reason, if (vmx->nested.update_vmcs01_apicv_status) { vmx->nested.update_vmcs01_apicv_status = false; - kvm_make_request(KVM_REQ_APICV_UPDATE, vcpu); + vmx_refresh_apicv_exec_ctrl(vcpu); } if (vmx->nested.update_vmcs01_hwapic_isr) { From acffd1cee6887e83f2efe90a481c71a3804257c9 Mon Sep 17 00:00:00 2001 From: Sean Christopherson Date: Thu, 13 Nov 2025 14:56:14 -0800 Subject: [PATCH 1656/3902] KVM: nSVM: Set exit_code_hi to -1 when synthesizing SVM_EXIT_ERR (failed VMRUN) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit commit f402ecd7a8b6446547076f4bd24bd5d4dcc94481 upstream. Set exit_code_hi to -1u as a temporary band-aid to fix a long-standing (effectively since KVM's inception) bug where KVM treats the exit code as a 32-bit value, when in reality it's a 64-bit value. Per the APM, offset 0x70 is a single 64-bit value: 070h 63:0 EXITCODE And a sane reading of the error values defined in "Table C-1. SVM Intercept Codes" is that negative values use the full 64 bits: –1 VMEXIT_INVALID Invalid guest state in VMCB. –2 VMEXIT_BUSYBUSY bit was set in the VMSA –3 VMEXIT_IDLE_REQUIREDThe sibling thread is not in an idle state -4 VMEXIT_INVALID_PMC Invalid PMC state And that interpretation is confirmed by testing on Milan and Turin (by setting bits in CR0[63:32] to generate VMEXIT_INVALID on VMRUN). Furthermore, Xen has treated exitcode as a 64-bit value since HVM support was adding in 2006 (see Xen commit d1bd157fbc ("Big merge the HVM full-virtualisation abstractions.")). Cc: Jim Mattson Cc: Yosry Ahmed Cc: stable@vger.kernel.org Reviewed-by: Yosry Ahmed Link: https://patch.msgid.link/20251113225621.1688428-3-seanjc@google.com Signed-off-by: Sean Christopherson Signed-off-by: Greg Kroah-Hartman --- arch/x86/kvm/svm/nested.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/arch/x86/kvm/svm/nested.c b/arch/x86/kvm/svm/nested.c index 2df6ec74d6ee60..e56c9b37e2b5b9 100644 --- a/arch/x86/kvm/svm/nested.c +++ b/arch/x86/kvm/svm/nested.c @@ -985,7 +985,7 @@ int nested_svm_vmrun(struct kvm_vcpu *vcpu) if (!nested_vmcb_check_save(vcpu) || !nested_vmcb_check_controls(vcpu)) { vmcb12->control.exit_code = SVM_EXIT_ERR; - vmcb12->control.exit_code_hi = 0; + vmcb12->control.exit_code_hi = -1u; vmcb12->control.exit_info_1 = 0; vmcb12->control.exit_info_2 = 0; goto out; @@ -1018,7 +1018,7 @@ int nested_svm_vmrun(struct kvm_vcpu *vcpu) svm->soft_int_injected = false; svm->vmcb->control.exit_code = SVM_EXIT_ERR; - svm->vmcb->control.exit_code_hi = 0; + svm->vmcb->control.exit_code_hi = -1u; svm->vmcb->control.exit_info_1 = 0; svm->vmcb->control.exit_info_2 = 0; From aedf238216636cd10de024f79b6b151a3148bfe9 Mon Sep 17 00:00:00 2001 From: Sean Christopherson Date: Thu, 13 Nov 2025 14:56:13 -0800 Subject: [PATCH 1657/3902] KVM: nSVM: Clear exit_code_hi in VMCB when synthesizing nested VM-Exits commit da01f64e7470988f8607776aa7afa924208863fb upstream. Explicitly clear exit_code_hi in the VMCB when synthesizing "normal" nested VM-Exits, as the full exit code is a 64-bit value (spoiler alert), and all exit codes for non-failing VMRUN use only bits 31:0. Cc: Jim Mattson Cc: Yosry Ahmed Cc: stable@vger.kernel.org Reviewed-by: Yosry Ahmed Link: https://patch.msgid.link/20251113225621.1688428-2-seanjc@google.com Signed-off-by: Sean Christopherson Signed-off-by: Greg Kroah-Hartman --- arch/x86/kvm/svm/svm.c | 2 ++ arch/x86/kvm/svm/svm.h | 7 ++++--- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/arch/x86/kvm/svm/svm.c b/arch/x86/kvm/svm/svm.c index a856f063b82502..3b215c5b5b01d6 100644 --- a/arch/x86/kvm/svm/svm.c +++ b/arch/x86/kvm/svm/svm.c @@ -2438,6 +2438,7 @@ static bool check_selective_cr0_intercepted(struct kvm_vcpu *vcpu, if (cr0 ^ val) { svm->vmcb->control.exit_code = SVM_EXIT_CR0_SEL_WRITE; + svm->vmcb->control.exit_code_hi = 0; ret = (nested_svm_exit_handled(svm) == NESTED_EXIT_DONE); } @@ -4627,6 +4628,7 @@ static int svm_check_intercept(struct kvm_vcpu *vcpu, if (static_cpu_has(X86_FEATURE_NRIPS)) vmcb->control.next_rip = info->next_rip; vmcb->control.exit_code = icpt_info.exit_code; + vmcb->control.exit_code_hi = 0; vmexit = nested_svm_exit_handled(svm); ret = (vmexit == NESTED_EXIT_DONE) ? X86EMUL_INTERCEPTED diff --git a/arch/x86/kvm/svm/svm.h b/arch/x86/kvm/svm/svm.h index dd78e64023450b..e66a16e59b1a5f 100644 --- a/arch/x86/kvm/svm/svm.h +++ b/arch/x86/kvm/svm/svm.h @@ -764,9 +764,10 @@ int nested_svm_vmexit(struct vcpu_svm *svm); static inline int nested_svm_simple_vmexit(struct vcpu_svm *svm, u32 exit_code) { - svm->vmcb->control.exit_code = exit_code; - svm->vmcb->control.exit_info_1 = 0; - svm->vmcb->control.exit_info_2 = 0; + svm->vmcb->control.exit_code = exit_code; + svm->vmcb->control.exit_code_hi = 0; + svm->vmcb->control.exit_info_1 = 0; + svm->vmcb->control.exit_info_2 = 0; return nested_svm_vmexit(svm); } From caf68b6da38957e78c8d83df4553542102366449 Mon Sep 17 00:00:00 2001 From: Sean Christopherson Date: Mon, 1 Dec 2025 17:50:48 -0800 Subject: [PATCH 1658/3902] KVM: x86: Apply runtime updates to current CPUID during KVM_SET_CPUID{,2} commit e2b43fb25243d502ad36b07bab9de09f4b76fff9 upstream. When handling KVM_SET_CPUID{,2}, do runtime CPUID updates on the vCPU's current CPUID (and caps) prior to swapping in the incoming CPUID state so that KVM doesn't lose pending updates if the incoming CPUID is rejected, and to prevent a false failure on the equality check. Note, runtime updates are unconditionally performed on the incoming/new CPUID (and associated caps), i.e. clearing the dirty flag won't negatively affect the new CPUID. Fixes: 93da6af3ae56 ("KVM: x86: Defer runtime updates of dynamic CPUID bits until CPUID emulation") Reported-by: Igor Mammedov Closes: https://lore.kernel.org/all/20251128123202.68424a95@imammedo Cc: stable@vger.kernel.org Acked-by: Igor Mammedov Tested-by: Igor Mammedov Link: https://patch.msgid.link/20251202015049.1167490-2-seanjc@google.com Signed-off-by: Sean Christopherson Signed-off-by: Greg Kroah-Hartman --- arch/x86/kvm/cpuid.c | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/arch/x86/kvm/cpuid.c b/arch/x86/kvm/cpuid.c index 52524e0ca97f78..913ffb995279af 100644 --- a/arch/x86/kvm/cpuid.c +++ b/arch/x86/kvm/cpuid.c @@ -509,11 +509,18 @@ static int kvm_set_cpuid(struct kvm_vcpu *vcpu, struct kvm_cpuid_entry2 *e2, u32 vcpu_caps[NR_KVM_CPU_CAPS]; int r; + /* + * Apply pending runtime CPUID updates to the current CPUID entries to + * avoid false positives due to mismatches on KVM-owned feature flags. + */ + if (vcpu->arch.cpuid_dynamic_bits_dirty) + kvm_update_cpuid_runtime(vcpu); + /* * Swap the existing (old) entries with the incoming (new) entries in * order to massage the new entries, e.g. to account for dynamic bits - * that KVM controls, without clobbering the current guest CPUID, which - * KVM needs to preserve in order to unwind on failure. + * that KVM controls, without losing the current guest CPUID, which KVM + * needs to preserve in order to unwind on failure. * * Similarly, save the vCPU's current cpu_caps so that the capabilities * can be updated alongside the CPUID entries when performing runtime From 0c01fa83c09beb5095aa16c16b5cc3f8b7220499 Mon Sep 17 00:00:00 2001 From: Gavin Shan Date: Mon, 24 Nov 2025 15:04:27 +1000 Subject: [PATCH 1659/3902] KVM: selftests: Add missing "break" in rseq_test's param parsing commit 1b9439c933b500cb24710bbd81fe56e9b0025b6f upstream. In commit 0297cdc12a87 ("KVM: selftests: Add option to rseq test to override /dev/cpu_dma_latency"), a 'break' is missed before the option 'l' in the argument parsing loop, which leads to an unexpected core dump in atoi_paranoid(). It tries to get the latency from non-existent argument. host$ ./rseq_test -u Random seed: 0x6b8b4567 Segmentation fault (core dumped) Add a 'break' before the option 'l' in the argument parsing loop to avoid the unexpected core dump. Fixes: 0297cdc12a87 ("KVM: selftests: Add option to rseq test to override /dev/cpu_dma_latency") Cc: stable@vger.kernel.org # v6.15+ Signed-off-by: Gavin Shan Link: https://patch.msgid.link/20251124050427.1924591-1-gshan@redhat.com [sean: describe code change in shortlog] Signed-off-by: Sean Christopherson Signed-off-by: Greg Kroah-Hartman --- tools/testing/selftests/kvm/rseq_test.c | 1 + 1 file changed, 1 insertion(+) diff --git a/tools/testing/selftests/kvm/rseq_test.c b/tools/testing/selftests/kvm/rseq_test.c index 1375fca80bcdbe..f80ad6b47d16b0 100644 --- a/tools/testing/selftests/kvm/rseq_test.c +++ b/tools/testing/selftests/kvm/rseq_test.c @@ -215,6 +215,7 @@ int main(int argc, char *argv[]) switch (opt) { case 'u': skip_sanity_check = true; + break; case 'l': latency = atoi_paranoid(optarg); break; From 55c28a875425c9975a9414a247e8e6709d3bf528 Mon Sep 17 00:00:00 2001 From: Haoxiang Li Date: Wed, 10 Dec 2025 17:06:01 +0800 Subject: [PATCH 1660/3902] xfs: fix a memory leak in xfs_buf_item_init() commit fc40459de82543b565ebc839dca8f7987f16f62e upstream. xfs_buf_item_get_format() may allocate memory for bip->bli_formats, free the memory in the error path. Fixes: c3d5f0c2fb85 ("xfs: complain if anyone tries to create a too-large buffer log item") Cc: stable@vger.kernel.org Signed-off-by: Haoxiang Li Reviewed-by: Christoph Hellwig Reviewed-by: Carlos Maiolino Signed-off-by: Carlos Maiolino Signed-off-by: Greg Kroah-Hartman --- fs/xfs/xfs_buf_item.c | 1 + 1 file changed, 1 insertion(+) diff --git a/fs/xfs/xfs_buf_item.c b/fs/xfs/xfs_buf_item.c index 8d85b5eee44441..f4c5be67826e22 100644 --- a/fs/xfs/xfs_buf_item.c +++ b/fs/xfs/xfs_buf_item.c @@ -896,6 +896,7 @@ xfs_buf_item_init( map_size = DIV_ROUND_UP(chunks, NBWORD); if (map_size > XFS_BLF_DATAMAP_SIZE) { + xfs_buf_item_free_format(bip); kmem_cache_free(xfs_buf_item_cache, bip); xfs_err(mp, "buffer item dirty bitmap (%u uints) too small to reflect %u bytes!", From 0f3b87c66d892c4b23233585612affb74bd18441 Mon Sep 17 00:00:00 2001 From: "Darrick J. Wong" Date: Thu, 4 Dec 2025 13:44:15 -0800 Subject: [PATCH 1661/3902] xfs: fix stupid compiler warning MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit commit f06725052098d7b1133ac3846d693c383dc427a2 upstream. gcc 14.2 warns about: xfs_attr_item.c: In function ‘xfs_attr_recover_work’: xfs_attr_item.c:785:9: warning: ‘ip’ may be used uninitialized [-Wmaybe-uninitialized] 785 | xfs_trans_ijoin(tp, ip, 0); | ^~~~~~~~~~~~~~~~~~~~~~~~~~ xfs_attr_item.c:740:42: note: ‘ip’ was declared here 740 | struct xfs_inode *ip; | ^~ I think this is bogus since xfs_attri_recover_work either returns a real pointer having initialized ip or an ERR_PTR having not touched it, but the tools are smarter than me so let's just null-init the variable anyway. Cc: stable@vger.kernel.org # v6.8 Fixes: e70fb328d52772 ("xfs: recreate work items when recovering intent items") Signed-off-by: Darrick J. Wong Reviewed-by: Carlos Maiolino Reviewed-by: Christoph Hellwig Signed-off-by: Carlos Maiolino Signed-off-by: Greg Kroah-Hartman --- fs/xfs/xfs_attr_item.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fs/xfs/xfs_attr_item.c b/fs/xfs/xfs_attr_item.c index c3a593319bee71..e8fa326ac995bc 100644 --- a/fs/xfs/xfs_attr_item.c +++ b/fs/xfs/xfs_attr_item.c @@ -737,7 +737,7 @@ xfs_attr_recover_work( struct xfs_attri_log_item *attrip = ATTRI_ITEM(lip); struct xfs_attr_intent *attr; struct xfs_mount *mp = lip->li_log->l_mp; - struct xfs_inode *ip; + struct xfs_inode *ip = NULL; struct xfs_da_args *args; struct xfs_trans *tp; struct xfs_trans_res resv; From 92f6a7b6cfda18b3da0862e402d849efd79c80a3 Mon Sep 17 00:00:00 2001 From: Christoph Hellwig Date: Tue, 16 Dec 2025 18:30:09 +0100 Subject: [PATCH 1662/3902] xfs: fix the zoned RT growfs check for zone alignment commit dc68c0f601691010dd5ae53442f8523f41a53131 upstream. The grofs code for zoned RT subvolums already tries to check for zone alignment, but gets it wrong by using the old instead of the new mount structure. Fixes: 01b71e64bb87 ("xfs: support growfs on zoned file systems") Signed-off-by: Christoph Hellwig Reviewed-by: Darrick J. Wong Cc: stable@vger.kernel.org # v6.15 Signed-off-by: Carlos Maiolino Signed-off-by: Greg Kroah-Hartman --- fs/xfs/xfs_rtalloc.c | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/fs/xfs/xfs_rtalloc.c b/fs/xfs/xfs_rtalloc.c index 6907e871fa1511..e063f4f2f2e617 100644 --- a/fs/xfs/xfs_rtalloc.c +++ b/fs/xfs/xfs_rtalloc.c @@ -1255,12 +1255,10 @@ xfs_growfs_check_rtgeom( min_logfsbs = min_t(xfs_extlen_t, xfs_log_calc_minimum_size(nmp), nmp->m_rsumblocks * 2); - kfree(nmp); - trace_xfs_growfs_check_rtgeom(mp, min_logfsbs); if (min_logfsbs > mp->m_sb.sb_logblocks) - return -EINVAL; + goto out_inval; if (xfs_has_zoned(mp)) { uint32_t gblocks = mp->m_groups[XG_TYPE_RTG].blocks; @@ -1268,16 +1266,20 @@ xfs_growfs_check_rtgeom( if (rextsize != 1) return -EINVAL; - div_u64_rem(mp->m_sb.sb_rblocks, gblocks, &rem); + div_u64_rem(nmp->m_sb.sb_rblocks, gblocks, &rem); if (rem) { xfs_warn(mp, "new RT volume size (%lld) not aligned to RT group size (%d)", - mp->m_sb.sb_rblocks, gblocks); - return -EINVAL; + nmp->m_sb.sb_rblocks, gblocks); + goto out_inval; } } + kfree(nmp); return 0; +out_inval: + kfree(nmp); + return -EINVAL; } /* From d29ed9ff972afe17c215cab171761d7a15d7063f Mon Sep 17 00:00:00 2001 From: "Darrick J. Wong" Date: Thu, 4 Dec 2025 13:43:50 -0800 Subject: [PATCH 1663/3902] xfs: fix a UAF problem in xattr repair commit 5990fd756943836978ad184aac980e2b36ab7e01 upstream. The xchk_setup_xattr_buf function can allocate a new value buffer, which means that any reference to ab->value before the call could become a dangling pointer. Fix this by moving an assignment to after the buffer setup. Cc: stable@vger.kernel.org # v6.10 Fixes: e47dcf113ae348 ("xfs: repair extended attributes") Signed-off-by: Darrick J. Wong Reviewed-by: Christoph Hellwig Signed-off-by: Carlos Maiolino Signed-off-by: Greg Kroah-Hartman --- fs/xfs/scrub/attr_repair.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fs/xfs/scrub/attr_repair.c b/fs/xfs/scrub/attr_repair.c index c7eb94069cafcd..09d63aa10314b0 100644 --- a/fs/xfs/scrub/attr_repair.c +++ b/fs/xfs/scrub/attr_repair.c @@ -333,7 +333,6 @@ xrep_xattr_salvage_remote_attr( .attr_filter = ent->flags & XFS_ATTR_NSP_ONDISK_MASK, .namelen = rentry->namelen, .name = rentry->name, - .value = ab->value, .valuelen = be32_to_cpu(rentry->valuelen), }; unsigned int namesize; @@ -363,6 +362,7 @@ xrep_xattr_salvage_remote_attr( error = -EDEADLOCK; if (error) return error; + args.value = ab->value; /* Look up the remote value and stash it for reconstruction. */ error = xfs_attr3_leaf_getvalue(leaf_bp, &args); From cec87632c34afc4589a315448690e1812c635c78 Mon Sep 17 00:00:00 2001 From: Christoph Hellwig Date: Tue, 16 Dec 2025 18:30:08 +0100 Subject: [PATCH 1664/3902] xfs: validate that zoned RT devices are zone aligned commit 982d2616a2906113e433fdc0cfcc122f8d1bb60a upstream. Garbage collection assumes all zones contain the full amount of blocks. Mkfs already ensures this happens, but make the kernel check it as well to avoid getting into trouble due to fuzzers or mkfs bugs. Fixes: 2167eaabe2fa ("xfs: define the zoned on-disk format") Signed-off-by: Christoph Hellwig Reviewed-by: Darrick J. Wong Cc: stable@vger.kernel.org # v6.15 Signed-off-by: Carlos Maiolino Signed-off-by: Greg Kroah-Hartman --- fs/xfs/libxfs/xfs_sb.c | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/fs/xfs/libxfs/xfs_sb.c b/fs/xfs/libxfs/xfs_sb.c index cdd16dd805d77c..94c272a2ae2622 100644 --- a/fs/xfs/libxfs/xfs_sb.c +++ b/fs/xfs/libxfs/xfs_sb.c @@ -301,6 +301,21 @@ xfs_validate_rt_geometry( sbp->sb_rbmblocks != xfs_expected_rbmblocks(sbp)) return false; + if (xfs_sb_is_v5(sbp) && + (sbp->sb_features_incompat & XFS_SB_FEAT_INCOMPAT_ZONED)) { + uint32_t mod; + + /* + * Zoned RT devices must be aligned to the RT group size, + * because garbage collection assumes that all zones have the + * same size to avoid insane complexity if that weren't the + * case. + */ + div_u64_rem(sbp->sb_rextents, sbp->sb_rgextents, &mod); + if (mod) + return false; + } + return true; } From 6df47e5bb9b62d72f186f826ab643ea1856877c7 Mon Sep 17 00:00:00 2001 From: Steven Rostedt Date: Tue, 16 Dec 2025 18:24:40 -0500 Subject: [PATCH 1665/3902] tracing: Do not register unsupported perf events commit ef7f38df890f5dcd2ae62f8dbde191d72f3bebae upstream. Synthetic events currently do not have a function to register perf events. This leads to calling the tracepoint register functions with a NULL function pointer which triggers: ------------[ cut here ]------------ WARNING: kernel/tracepoint.c:175 at tracepoint_add_func+0x357/0x370, CPU#2: perf/2272 Modules linked in: kvm_intel kvm irqbypass CPU: 2 UID: 0 PID: 2272 Comm: perf Not tainted 6.18.0-ftest-11964-ge022764176fc-dirty #323 PREEMPTLAZY Hardware name: QEMU Standard PC (Q35 + ICH9, 2009), BIOS 1.17.0-debian-1.17.0-1 04/01/2014 RIP: 0010:tracepoint_add_func+0x357/0x370 Code: 28 9c e8 4c 0b f5 ff eb 0f 4c 89 f7 48 c7 c6 80 4d 28 9c e8 ab 89 f4 ff 31 c0 5b 41 5c 41 5d 41 5e 41 5f 5d c3 cc cc cc cc cc <0f> 0b 49 c7 c6 ea ff ff ff e9 ee fe ff ff 0f 0b e9 f9 fe ff ff 0f RSP: 0018:ffffabc0c44d3c40 EFLAGS: 00010246 RAX: 0000000000000001 RBX: ffff9380aa9e4060 RCX: 0000000000000000 RDX: 000000000000000a RSI: ffffffff9e1d4a98 RDI: ffff937fcf5fd6c8 RBP: 0000000000000001 R08: 0000000000000007 R09: ffff937fcf5fc780 R10: 0000000000000003 R11: ffffffff9c193910 R12: 000000000000000a R13: ffffffff9e1e5888 R14: 0000000000000000 R15: ffffabc0c44d3c78 FS: 00007f6202f5f340(0000) GS:ffff93819f00f000(0000) knlGS:0000000000000000 CS: 0010 DS: 0000 ES: 0000 CR0: 0000000080050033 CR2: 000055d3162281a8 CR3: 0000000106a56003 CR4: 0000000000172ef0 Call Trace: tracepoint_probe_register+0x5d/0x90 synth_event_reg+0x3c/0x60 perf_trace_event_init+0x204/0x340 perf_trace_init+0x85/0xd0 perf_tp_event_init+0x2e/0x50 perf_try_init_event+0x6f/0x230 ? perf_event_alloc+0x4bb/0xdc0 perf_event_alloc+0x65a/0xdc0 __se_sys_perf_event_open+0x290/0x9f0 do_syscall_64+0x93/0x7b0 ? entry_SYSCALL_64_after_hwframe+0x76/0x7e ? trace_hardirqs_off+0x53/0xc0 entry_SYSCALL_64_after_hwframe+0x76/0x7e Instead, have the code return -ENODEV, which doesn't warn and has perf error out with: # perf record -e synthetic:futex_wait Error: The sys_perf_event_open() syscall returned with 19 (No such device) for event (synthetic:futex_wait). "dmesg | grep -i perf" may provide additional information. Ideally perf should support synthetic events, but for now just fix the warning. The support can come later. Cc: stable@vger.kernel.org Cc: Masami Hiramatsu Cc: Mathieu Desnoyers Cc: Arnaldo Carvalho de Melo Cc: Jiri Olsa Cc: Namhyung Kim Link: https://patch.msgid.link/20251216182440.147e4453@gandalf.local.home Fixes: 4b147936fa509 ("tracing: Add support for 'synthetic' events") Reported-by: Ian Rogers Signed-off-by: Steven Rostedt (Google) Signed-off-by: Greg Kroah-Hartman --- kernel/trace/trace_events.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/kernel/trace/trace_events.c b/kernel/trace/trace_events.c index e00da4182deb78..099f0813290213 100644 --- a/kernel/trace/trace_events.c +++ b/kernel/trace/trace_events.c @@ -700,6 +700,8 @@ int trace_event_reg(struct trace_event_call *call, #ifdef CONFIG_PERF_EVENTS case TRACE_REG_PERF_REGISTER: + if (!call->class->perf_probe) + return -ENODEV; return tracepoint_probe_register(call->tp, call->class->perf_probe, call); From 7992a8c946890a1a6d870bfac4e737a71a8a1676 Mon Sep 17 00:00:00 2001 From: "Rafael J. Wysocki" Date: Mon, 15 Dec 2025 15:21:34 +0100 Subject: [PATCH 1666/3902] PM: runtime: Do not clear needs_force_resume with enabled runtime PM commit 359afc8eb02a518fbdd0cbd462c8c2827c6cbec2 upstream. Commit 89d9cec3b1e9 ("PM: runtime: Clear power.needs_force_resume in pm_runtime_reinit()") added provisional clearing of power.needs_force_resume to pm_runtime_reinit(), but it is done unconditionally which is a mistake because pm_runtime_reinit() may race with driver probing and removal [1]. To address this, notice that power.needs_force_resume should never be set when runtime PM is enabled and so it only needs to be cleared when runtime PM is disabled, and update pm_runtime_init() to only clear that flag when runtime PM is disabled. Fixes: 89d9cec3b1e9 ("PM: runtime: Clear power.needs_force_resume in pm_runtime_reinit()") Reported-by: Ed Tsai Closes: https://lore.kernel.org/linux-pm/20251215122154.3180001-1-ed.tsai@mediatek.com/ [1] Signed-off-by: Rafael J. Wysocki Cc: 6.17+ # 6.17+ Reviewed-by: Ulf Hansson Link: https://patch.msgid.link/12807571.O9o76ZdvQC@rafael.j.wysocki Signed-off-by: Greg Kroah-Hartman --- drivers/base/power/runtime.c | 22 ++++++++++++---------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/drivers/base/power/runtime.c b/drivers/base/power/runtime.c index 1b11a3cd4acc27..e882b5269ebec4 100644 --- a/drivers/base/power/runtime.c +++ b/drivers/base/power/runtime.c @@ -1869,16 +1869,18 @@ void pm_runtime_init(struct device *dev) */ void pm_runtime_reinit(struct device *dev) { - if (!pm_runtime_enabled(dev)) { - if (dev->power.runtime_status == RPM_ACTIVE) - pm_runtime_set_suspended(dev); - if (dev->power.irq_safe) { - spin_lock_irq(&dev->power.lock); - dev->power.irq_safe = 0; - spin_unlock_irq(&dev->power.lock); - if (dev->parent) - pm_runtime_put(dev->parent); - } + if (pm_runtime_enabled(dev)) + return; + + if (dev->power.runtime_status == RPM_ACTIVE) + pm_runtime_set_suspended(dev); + + if (dev->power.irq_safe) { + spin_lock_irq(&dev->power.lock); + dev->power.irq_safe = 0; + spin_unlock_irq(&dev->power.lock); + if (dev->parent) + pm_runtime_put(dev->parent); } /* * Clear power.needs_force_resume in case it has been set by From 5bb88024409ef7bb53e5d42d35097a7a3157f764 Mon Sep 17 00:00:00 2001 From: Mark Brown Date: Sat, 29 Nov 2025 00:48:45 +0000 Subject: [PATCH 1667/3902] arm64/gcs: Flush the GCS locking state on exec commit 98a97bf41528ef738b06eb07ec2b2eb1cfde6ce6 upstream. When we exec a new task we forget to flush the set of locked GCS mode bits. Since we do flush the rest of the state this means that if GCS is locked the new task will be unable to enable GCS, it will be locked as being disabled. Add the expected flush. Fixes: fc84bc5378a8 ("arm64/gcs: Context switch GCS state for EL0") Cc: # 6.13.x Reported-by: Yury Khrustalev Signed-off-by: Mark Brown Tested-by: Yury Khrustalev Signed-off-by: Catalin Marinas Signed-off-by: Greg Kroah-Hartman --- arch/arm64/kernel/process.c | 1 + 1 file changed, 1 insertion(+) diff --git a/arch/arm64/kernel/process.c b/arch/arm64/kernel/process.c index fba7ca102a8c42..489554931231e6 100644 --- a/arch/arm64/kernel/process.c +++ b/arch/arm64/kernel/process.c @@ -292,6 +292,7 @@ static void flush_gcs(void) current->thread.gcs_base = 0; current->thread.gcs_size = 0; current->thread.gcs_el0_mode = 0; + current->thread.gcs_el0_locked = 0; write_sysreg_s(GCSCRE0_EL1_nTR, SYS_GCSCRE0_EL1); write_sysreg_s(0, SYS_GCSPR_EL0); } From 681fd588733cd9e3ca033474f6b46ed147331af7 Mon Sep 17 00:00:00 2001 From: Charles Mirabile Date: Fri, 12 Dec 2025 13:47:17 -0500 Subject: [PATCH 1668/3902] lib/crypto: riscv: Add poly1305-core.S to .gitignore commit 5a0b1882506858b12cc77f0e2439a5f3c5052761 upstream. poly1305-core.S is an auto-generated file, so it should be ignored. Fixes: bef9c7559869 ("lib/crypto: riscv/poly1305: Import OpenSSL/CRYPTOGAMS implementation") Cc: stable@vger.kernel.org Signed-off-by: Charles Mirabile Link: https://lore.kernel.org/r/20251212184717.133701-1-cmirabil@redhat.com Signed-off-by: Eric Biggers Signed-off-by: Greg Kroah-Hartman --- lib/crypto/riscv/.gitignore | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 lib/crypto/riscv/.gitignore diff --git a/lib/crypto/riscv/.gitignore b/lib/crypto/riscv/.gitignore new file mode 100644 index 00000000000000..0d47d4f21c6de9 --- /dev/null +++ b/lib/crypto/riscv/.gitignore @@ -0,0 +1,2 @@ +# SPDX-License-Identifier: GPL-2.0-only +poly1305-core.S From 779fa2a063d4603da4b9b5c89c326da2d0f5e253 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20Rebe?= Date: Tue, 2 Dec 2025 19:41:37 +0100 Subject: [PATCH 1669/3902] r8169: fix RTL8117 Wake-on-Lan in DASH mode MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit commit dd75c723ef566f7f009c047f47e0eee95fe348ab upstream. Wake-on-Lan does currently not work for r8169 in DASH mode, e.g. the ASUS Pro WS X570-ACE with RTL8168fp/RTL8117. Fix by not returning early in rtl_prepare_power_down when dash_enabled. While this fixes WoL, it still kills the OOB RTL8117 remote management BMC connection. Fix by not calling rtl8168_driver_stop if WoL is enabled. Fixes: 065c27c184d6 ("r8169: phy power ops") Signed-off-by: René Rebe Cc: stable@vger.kernel.org Reviewed-by: Heiner Kallweit Link: https://patch.msgid.link/20251202.194137.1647877804487085954.rene@exactco.de Signed-off-by: Jakub Kicinski Signed-off-by: Greg Kroah-Hartman --- drivers/net/ethernet/realtek/r8169_main.c | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/drivers/net/ethernet/realtek/r8169_main.c b/drivers/net/ethernet/realtek/r8169_main.c index 853aabedb128da..d407b031cc2ae4 100644 --- a/drivers/net/ethernet/realtek/r8169_main.c +++ b/drivers/net/ethernet/realtek/r8169_main.c @@ -2669,9 +2669,6 @@ static void rtl_wol_enable_rx(struct rtl8169_private *tp) static void rtl_prepare_power_down(struct rtl8169_private *tp) { - if (tp->dash_enabled) - return; - if (tp->mac_version == RTL_GIGA_MAC_VER_32 || tp->mac_version == RTL_GIGA_MAC_VER_33) rtl_ephy_write(tp, 0x19, 0xff64); @@ -4807,7 +4804,7 @@ static void rtl8169_down(struct rtl8169_private *tp) rtl_disable_exit_l1(tp); rtl_prepare_power_down(tp); - if (tp->dash_type != RTL_DASH_NONE) + if (tp->dash_type != RTL_DASH_NONE && !tp->saved_wolopts) rtl8168_driver_stop(tp); } From 7b9ea7aa24bd1cafa12aab519c8023048033fafc Mon Sep 17 00:00:00 2001 From: Thorsten Blum Date: Tue, 2 Dec 2025 18:27:44 +0100 Subject: [PATCH 1670/3902] net: phy: marvell-88q2xxx: Fix clamped value in mv88q2xxx_hwmon_write commit c4cdf7376271bce5714c06d79ec67759b18910eb upstream. The local variable 'val' was never clamped to -75000 or 180000 because the return value of clamp_val() was not used. Fix this by assigning the clamped value back to 'val', and use clamp() instead of clamp_val(). Cc: stable@vger.kernel.org Fixes: a557a92e6881 ("net: phy: marvell-88q2xxx: add support for temperature sensor") Signed-off-by: Thorsten Blum Reviewed-by: Dimitri Fedrau Reviewed-by: Andrew Lunn Link: https://patch.msgid.link/20251202172743.453055-3-thorsten.blum@linux.dev Signed-off-by: Jakub Kicinski Signed-off-by: Greg Kroah-Hartman --- drivers/net/phy/marvell-88q2xxx.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/net/phy/marvell-88q2xxx.c b/drivers/net/phy/marvell-88q2xxx.c index f3d83b04c9535c..201dee1a16985e 100644 --- a/drivers/net/phy/marvell-88q2xxx.c +++ b/drivers/net/phy/marvell-88q2xxx.c @@ -698,7 +698,7 @@ static int mv88q2xxx_hwmon_write(struct device *dev, switch (attr) { case hwmon_temp_max: - clamp_val(val, -75000, 180000); + val = clamp(val, -75000, 180000); val = (val / 1000) + 75; val = FIELD_PREP(MDIO_MMD_PCS_MV_TEMP_SENSOR3_INT_THRESH_MASK, val); From 7a93edb23bcf07a3aaf8b598edfc2faa8fbcc0b6 Mon Sep 17 00:00:00 2001 From: Amir Goldstein Date: Sun, 7 Dec 2025 11:44:55 +0100 Subject: [PATCH 1671/3902] fsnotify: do not generate ACCESS/MODIFY events on child for special files commit 635bc4def026a24e071436f4f356ea08c0eed6ff upstream. inotify/fanotify do not allow users with no read access to a file to subscribe to events (e.g. IN_ACCESS/IN_MODIFY), but they do allow the same user to subscribe for watching events on children when the user has access to the parent directory (e.g. /dev). Users with no read access to a file but with read access to its parent directory can still stat the file and see if it was accessed/modified via atime/mtime change. The same is not true for special files (e.g. /dev/null). Users will not generally observe atime/mtime changes when other users read/write to special files, only when someone sets atime/mtime via utimensat(). Align fsnotify events with this stat behavior and do not generate ACCESS/MODIFY events to parent watchers on read/write of special files. The events are still generated to parent watchers on utimensat(). This closes some side-channels that could be possibly used for information exfiltration [1]. [1] https://snee.la/pdf/pubs/file-notification-attacks.pdf Reported-by: Sudheendra Raghav Neela CC: stable@vger.kernel.org Signed-off-by: Amir Goldstein Signed-off-by: Jan Kara Signed-off-by: Greg Kroah-Hartman --- fs/notify/fsnotify.c | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/fs/notify/fsnotify.c b/fs/notify/fsnotify.c index 46bfc543f9467c..63dd44931989d4 100644 --- a/fs/notify/fsnotify.c +++ b/fs/notify/fsnotify.c @@ -270,8 +270,15 @@ int __fsnotify_parent(struct dentry *dentry, __u32 mask, const void *data, /* * Include parent/name in notification either if some notification * groups require parent info or the parent is interested in this event. + * The parent interest in ACCESS/MODIFY events does not apply to special + * files, where read/write are not on the filesystem of the parent and + * events can provide an undesirable side-channel for information + * exfiltration. */ - parent_interested = mask & p_mask & ALL_FSNOTIFY_EVENTS; + parent_interested = mask & p_mask & ALL_FSNOTIFY_EVENTS && + !(data_type == FSNOTIFY_EVENT_PATH && + d_is_special(dentry) && + (mask & (FS_ACCESS | FS_MODIFY))); if (parent_needed || parent_interested) { /* When notifying parent, child should be passed as data */ WARN_ON_ONCE(inode != fsnotify_data_inode(data, data_type)); From b225325be7b247c7268e65eea6090db1fc786d1f Mon Sep 17 00:00:00 2001 From: caoping Date: Thu, 4 Dec 2025 01:10:58 -0800 Subject: [PATCH 1672/3902] net/handshake: restore destructor on submit failure commit 6af2a01d65f89e73c1cbb9267f8880d83a88cee4 upstream. handshake_req_submit() replaces sk->sk_destruct but never restores it when submission fails before the request is hashed. handshake_sk_destruct() then returns early and the original destructor never runs, leaking the socket. Restore sk_destruct on the error path. Fixes: 3b3009ea8abb ("net/handshake: Create a NETLINK service for handling handshake requests") Reviewed-by: Chuck Lever Cc: stable@vger.kernel.org Signed-off-by: caoping Link: https://patch.msgid.link/20251204091058.1545151-1-caoping@cmss.chinamobile.com Signed-off-by: Jakub Kicinski Signed-off-by: Greg Kroah-Hartman --- net/handshake/request.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/net/handshake/request.c b/net/handshake/request.c index f78091680bca52..6b7e3e0bf3996e 100644 --- a/net/handshake/request.c +++ b/net/handshake/request.c @@ -276,6 +276,8 @@ int handshake_req_submit(struct socket *sock, struct handshake_req *req, out_unlock: spin_unlock(&hn->hn_lock); out_err: + /* Restore original destructor so socket teardown still runs on failure */ + req->hr_sk->sk_destruct = req->hr_odestruct; trace_handshake_submit_err(net, req, req->hr_sk, ret); handshake_req_destroy(req); return ret; From b8db16f97d086fb6e14d6a5c0b1f5b862c230443 Mon Sep 17 00:00:00 2001 From: Chuck Lever Date: Mon, 17 Nov 2025 11:00:49 -0500 Subject: [PATCH 1673/3902] NFSD: Clear SECLABEL in the suppattr_exclcreat bitmap commit 27d17641cacfedd816789b75d342430f6b912bd2 upstream. >From RFC 8881: 5.8.1.14. Attribute 75: suppattr_exclcreat > The bit vector that would set all REQUIRED and RECOMMENDED > attributes that are supported by the EXCLUSIVE4_1 method of file > creation via the OPEN operation. The scope of this attribute > applies to all objects with a matching fsid. There's nothing in RFC 8881 that states that suppattr_exclcreat is or is not allowed to contain bits for attributes that are clear in the reported supported_attrs bitmask. But it doesn't make sense for an NFS server to indicate that it /doesn't/ implement an attribute, but then also indicate that clients /are/ allowed to set that attribute using OPEN(create) with EXCLUSIVE4_1. Ensure that the SECURITY_LABEL and ACL bits are not set in the suppattr_exclcreat bitmask when they are also not set in the supported_attrs bitmask. Fixes: 8c18f2052e75 ("nfsd41: SUPPATTR_EXCLCREAT attribute") Cc: stable@vger.kernel.org Reviewed-by: Jeff Layton Signed-off-by: Chuck Lever Signed-off-by: Greg Kroah-Hartman --- fs/nfsd/nfs4xdr.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/fs/nfsd/nfs4xdr.c b/fs/nfsd/nfs4xdr.c index 67bb9c0b9fcb18..4a403ce4fd468c 100644 --- a/fs/nfsd/nfs4xdr.c +++ b/fs/nfsd/nfs4xdr.c @@ -3375,6 +3375,11 @@ static __be32 nfsd4_encode_fattr4_suppattr_exclcreat(struct xdr_stream *xdr, u32 supp[3]; memcpy(supp, nfsd_suppattrs[resp->cstate.minorversion], sizeof(supp)); + if (!IS_POSIXACL(d_inode(args->dentry))) + supp[0] &= ~FATTR4_WORD0_ACL; + if (!args->contextsupport) + supp[2] &= ~FATTR4_WORD2_SECURITY_LABEL; + supp[0] &= NFSD_SUPPATTR_EXCLCREAT_WORD0; supp[1] &= NFSD_SUPPATTR_EXCLCREAT_WORD1; supp[2] &= NFSD_SUPPATTR_EXCLCREAT_WORD2; From 214b396480061cbc8b16f2c518b2add7fbfa5192 Mon Sep 17 00:00:00 2001 From: Chuck Lever Date: Tue, 18 Nov 2025 19:51:19 -0500 Subject: [PATCH 1674/3902] NFSD: NFSv4 file creation neglects setting ACL MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit commit 913f7cf77bf14c13cfea70e89bcb6d0b22239562 upstream. An NFSv4 client that sets an ACL with a named principal during file creation retrieves the ACL afterwards, and finds that it is only a default ACL (based on the mode bits) and not the ACL that was requested during file creation. This violates RFC 8881 section 6.4.1.3: "the ACL attribute is set as given". The issue occurs in nfsd_create_setattr(), which calls nfsd_attrs_valid() to determine whether to call nfsd_setattr(). However, nfsd_attrs_valid() checks only for iattr changes and security labels, but not POSIX ACLs. When only an ACL is present, the function returns false, nfsd_setattr() is skipped, and the POSIX ACL is never applied to the inode. Subsequently, when the client retrieves the ACL, the server finds no POSIX ACL on the inode and returns one generated from the file's mode bits rather than returning the originally-specified ACL. Reported-by: Aurélien Couderc Fixes: c0cbe70742f4 ("NFSD: add posix ACLs to struct nfsd_attrs") Cc: Roland Mainz Cc: stable@vger.kernel.org Signed-off-by: Chuck Lever Signed-off-by: Greg Kroah-Hartman --- fs/nfsd/vfs.h | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/fs/nfsd/vfs.h b/fs/nfsd/vfs.h index 0c0292611c6de3..9652c1de53ec13 100644 --- a/fs/nfsd/vfs.h +++ b/fs/nfsd/vfs.h @@ -67,7 +67,8 @@ static inline bool nfsd_attrs_valid(struct nfsd_attrs *attrs) struct iattr *iap = attrs->na_iattr; return (iap->ia_valid || (attrs->na_seclabel && - attrs->na_seclabel->len)); + attrs->na_seclabel->len) || + attrs->na_pacl || attrs->na_dpacl); } __be32 nfserrno (int errno); From 134436e66019d1176d0ebd74b9aedff6deb79534 Mon Sep 17 00:00:00 2001 From: Peter Ujfalusi Date: Mon, 15 Dec 2025 14:06:47 +0200 Subject: [PATCH 1675/3902] ASoC: SOF: ipc4-topology: Prefer 32-bit DMIC blobs for 8-bit formats as well commit 26e455064983e00013c0a63ffe0eed9e9ec2fa89 upstream. With the introduction of 8-bit formats the DMIC blob lookup also needs to be modified to prefer the 32-bit blob when 8-bit format is used on FE. At the same time we also need to make sure that in case 8-bit format is used, but only 16-bit blob is available for DMIC then we will not try to look for 8-bit blob (which is invalid) as fallback, but for a 16-bit one. Fixes: c04c2e829649 ("ASoC: SOF: ipc4-topology: Add support for 8-bit formats") Cc: stable@vger.kernel.org Signed-off-by: Peter Ujfalusi Reviewed-by: Bard Liao Reviewed-by: Seppo Ingalsuo Reviewed-by: Kai Vehmanen Reviewed-by: Ranjani Sridharan Link: https://patch.msgid.link/20251215120648.4827-2-peter.ujfalusi@linux.intel.com Signed-off-by: Mark Brown Signed-off-by: Greg Kroah-Hartman --- sound/soc/sof/ipc4-topology.c | 22 ++++++++++++++-------- 1 file changed, 14 insertions(+), 8 deletions(-) diff --git a/sound/soc/sof/ipc4-topology.c b/sound/soc/sof/ipc4-topology.c index 221e9d4052b8f0..47959f182f4be9 100644 --- a/sound/soc/sof/ipc4-topology.c +++ b/sound/soc/sof/ipc4-topology.c @@ -1752,11 +1752,9 @@ snd_sof_get_nhlt_endpoint_data(struct snd_sof_dev *sdev, struct snd_sof_dai *dai channel_count = params_channels(params); sample_rate = params_rate(params); bit_depth = params_width(params); - /* - * Look for 32-bit blob first instead of 16-bit if copier - * supports multiple formats - */ - if (bit_depth == 16 && !single_bitdepth) { + + /* Prefer 32-bit blob if copier supports multiple formats */ + if (bit_depth <= 16 && !single_bitdepth) { dev_dbg(sdev->dev, "Looking for 32-bit blob first for DMIC\n"); format_change = true; bit_depth = 32; @@ -1799,10 +1797,18 @@ snd_sof_get_nhlt_endpoint_data(struct snd_sof_dev *sdev, struct snd_sof_dai *dai if (format_change) { /* * The 32-bit blob was not found in NHLT table, try to - * look for one based on the params + * look for 16-bit for DMIC or based on the params for + * SSP */ - bit_depth = params_width(params); - format_change = false; + if (linktype == SOF_DAI_INTEL_DMIC) { + bit_depth = 16; + if (params_width(params) == 16) + format_change = false; + } else { + bit_depth = params_width(params); + format_change = false; + } + get_new_blob = true; } else if (linktype == SOF_DAI_INTEL_DMIC && !single_bitdepth) { /* From 5060592025103e78cc90854a9c8d64591fbeccf1 Mon Sep 17 00:00:00 2001 From: Antheas Kapenekakis Date: Tue, 16 Dec 2025 22:17:14 +0100 Subject: [PATCH 1676/3902] ALSA: hda/realtek: Add Asus quirk for TAS amplifiers commit f7cede182c963720edd1e5fb50ea4f1c7eafa30e upstream. By default, these devices use the quirk ALC294_FIXUP_ASUS_SPK. Not using it causes the headphone jack to stop working. Therefore, introduce a new quirk ALC287_FIXUP_TXNW2781_I2C_ASUS that binds to the TAS amplifier while using that quirk. Cc: stable@kernel.org Fixes: 18a4895370a7 ("ALSA: hda/realtek: Add match for ASUS Xbox Ally projects") Signed-off-by: Antheas Kapenekakis Link: https://patch.msgid.link/20251216211714.1116898-1-lkml@antheas.dev Signed-off-by: Takashi Iwai Signed-off-by: Greg Kroah-Hartman --- sound/hda/codecs/realtek/alc269.c | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/sound/hda/codecs/realtek/alc269.c b/sound/hda/codecs/realtek/alc269.c index a6d494dab5b6d7..b3582e390dfa35 100644 --- a/sound/hda/codecs/realtek/alc269.c +++ b/sound/hda/codecs/realtek/alc269.c @@ -3703,6 +3703,7 @@ enum { ALC295_FIXUP_DELL_TAS2781_I2C, ALC245_FIXUP_TAS2781_SPI_2, ALC287_FIXUP_TXNW2781_I2C, + ALC287_FIXUP_TXNW2781_I2C_ASUS, ALC287_FIXUP_YOGA7_14ARB7_I2C, ALC245_FIXUP_HP_MUTE_LED_COEFBIT, ALC245_FIXUP_HP_MUTE_LED_V1_COEFBIT, @@ -5993,6 +5994,12 @@ static const struct hda_fixup alc269_fixups[] = { .chained = true, .chain_id = ALC285_FIXUP_THINKPAD_HEADSET_JACK, }, + [ALC287_FIXUP_TXNW2781_I2C_ASUS] = { + .type = HDA_FIXUP_FUNC, + .v.func = tas2781_fixup_txnw_i2c, + .chained = true, + .chain_id = ALC294_FIXUP_ASUS_SPK, + }, [ALC287_FIXUP_YOGA7_14ARB7_I2C] = { .type = HDA_FIXUP_FUNC, .v.func = yoga7_14arb7_fixup_i2c, @@ -6736,8 +6743,8 @@ static const struct hda_quirk alc269_fixup_tbl[] = { SND_PCI_QUIRK(0x1043, 0x12f0, "ASUS X541UV", ALC256_FIXUP_ASUS_MIC_NO_PRESENCE), SND_PCI_QUIRK(0x1043, 0x1313, "Asus K42JZ", ALC269VB_FIXUP_ASUS_MIC_NO_PRESENCE), SND_PCI_QUIRK(0x1043, 0x1314, "ASUS GA605K", ALC285_FIXUP_ASUS_GA605K_HEADSET_MIC), - SND_PCI_QUIRK(0x1043, 0x1384, "ASUS RC73XA", ALC287_FIXUP_TXNW2781_I2C), - SND_PCI_QUIRK(0x1043, 0x1394, "ASUS RC73YA", ALC287_FIXUP_TXNW2781_I2C), + SND_PCI_QUIRK(0x1043, 0x1384, "ASUS RC73XA", ALC287_FIXUP_TXNW2781_I2C_ASUS), + SND_PCI_QUIRK(0x1043, 0x1394, "ASUS RC73YA", ALC287_FIXUP_TXNW2781_I2C_ASUS), SND_PCI_QUIRK(0x1043, 0x13b0, "ASUS Z550SA", ALC256_FIXUP_ASUS_MIC_NO_PRESENCE), SND_PCI_QUIRK(0x1043, 0x1427, "Asus Zenbook UX31E", ALC269VB_FIXUP_ASUS_ZENBOOK), SND_PCI_QUIRK(0x1043, 0x1433, "ASUS GX650PY/PZ/PV/PU/PYV/PZV/PIV/PVV", ALC285_FIXUP_ASUS_I2C_HEADSET_MIC), From 42ed4c781019e4c19af74b9edd1f7e31b439c2c9 Mon Sep 17 00:00:00 2001 From: Chuck Lever Date: Mon, 17 Nov 2025 11:00:50 -0500 Subject: [PATCH 1677/3902] NFSD: Clear TIME_DELEG in the suppattr_exclcreat bitmap commit ad3cbbb0c1892c48919727fcb8dec5965da8bacb upstream. >From RFC 8881: 5.8.1.14. Attribute 75: suppattr_exclcreat > The bit vector that would set all REQUIRED and RECOMMENDED > attributes that are supported by the EXCLUSIVE4_1 method of file > creation via the OPEN operation. The scope of this attribute > applies to all objects with a matching fsid. There's nothing in RFC 8881 that states that suppattr_exclcreat is or is not allowed to contain bits for attributes that are clear in the reported supported_attrs bitmask. But it doesn't make sense for an NFS server to indicate that it /doesn't/ implement an attribute, but then also indicate that clients /are/ allowed to set that attribute using OPEN(create) with EXCLUSIVE4_1. The FATTR4_WORD2_TIME_DELEG attributes are also not to be allowed for OPEN(create) with EXCLUSIVE4_1. It doesn't make sense to set a delegated timestamp on a new file. Fixes: 7e13f4f8d27d ("nfsd: handle delegated timestamps in SETATTR") Cc: stable@vger.kernel.org Reviewed-by: Jeff Layton Signed-off-by: Chuck Lever Signed-off-by: Greg Kroah-Hartman --- fs/nfsd/nfsd.h | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/fs/nfsd/nfsd.h b/fs/nfsd/nfsd.h index b752433c3c2c43..191cbf9320c0fb 100644 --- a/fs/nfsd/nfsd.h +++ b/fs/nfsd/nfsd.h @@ -547,8 +547,14 @@ static inline bool nfsd_attrs_supported(u32 minorversion, const u32 *bmval) #define NFSD_SUPPATTR_EXCLCREAT_WORD1 \ (NFSD_WRITEABLE_ATTRS_WORD1 & \ ~(FATTR4_WORD1_TIME_ACCESS_SET | FATTR4_WORD1_TIME_MODIFY_SET)) +/* + * The FATTR4_WORD2_TIME_DELEG attributes are not to be allowed for + * OPEN(create) with EXCLUSIVE4_1. It doesn't make sense to set a + * delegated timestamp on a new file. + */ #define NFSD_SUPPATTR_EXCLCREAT_WORD2 \ - NFSD_WRITEABLE_ATTRS_WORD2 + (NFSD_WRITEABLE_ATTRS_WORD2 & \ + ~(FATTR4_WORD2_TIME_DELEG_ACCESS | FATTR4_WORD2_TIME_DELEG_MODIFY)) extern int nfsd4_is_junction(struct dentry *dentry); extern int register_cld_notifier(void); From 7d17693eb79d3fe6c81066b6ca566546fd38ba1f Mon Sep 17 00:00:00 2001 From: Peter Ujfalusi Date: Mon, 15 Dec 2025 14:06:48 +0200 Subject: [PATCH 1678/3902] ASoC: SOF: ipc4-topology: Convert FLOAT to S32 during blob selection commit 816f291fc23f325d31509d0e97873249ad75ae9a upstream. SSP/DMIC blobs have no support for FLOAT type, they are using S32 on data bus. Convert the format from FLOAT_LE to S32_LE to make sure that the correct format is used within the path. FLOAT conversion will be done on the host side (or within the path). Fixes: f7c41911ad74 ("ASoC: SOF: ipc4-topology: Add support for float sample type") Cc: stable@vger.kernel.org Signed-off-by: Peter Ujfalusi Reviewed-by: Bard Liao Reviewed-by: Seppo Ingalsuo Reviewed-by: Kai Vehmanen Reviewed-by: Ranjani Sridharan Link: https://patch.msgid.link/20251215120648.4827-3-peter.ujfalusi@linux.intel.com Signed-off-by: Mark Brown Signed-off-by: Greg Kroah-Hartman --- sound/soc/sof/ipc4-topology.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sound/soc/sof/ipc4-topology.c b/sound/soc/sof/ipc4-topology.c index 47959f182f4be9..32b628e2fe29b5 100644 --- a/sound/soc/sof/ipc4-topology.c +++ b/sound/soc/sof/ipc4-topology.c @@ -1843,7 +1843,7 @@ snd_sof_get_nhlt_endpoint_data(struct snd_sof_dev *sdev, struct snd_sof_dai *dai *len = cfg->size >> 2; *dst = (u32 *)cfg->caps; - if (format_change) { + if (format_change || params_format(params) == SNDRV_PCM_FORMAT_FLOAT_LE) { /* * Update the params to reflect that different blob was loaded * instead of the requested bit depth (16 -> 32 or 32 -> 16). From da1ccfc4c452541584a4eae89e337cfa21be6d5a Mon Sep 17 00:00:00 2001 From: Joshua Rogers Date: Fri, 7 Nov 2025 10:09:49 -0500 Subject: [PATCH 1679/3902] svcrdma: bound check rq_pages index in inline path commit d1bea0ce35b6095544ee82bb54156fc62c067e58 upstream. svc_rdma_copy_inline_range indexed rqstp->rq_pages[rc_curpage] without verifying rc_curpage stays within the allocated page array. Add guards before the first use and after advancing to a new page. Fixes: d7cc73972661 ("svcrdma: support multiple Read chunks per RPC") Cc: stable@vger.kernel.org Signed-off-by: Joshua Rogers Signed-off-by: Chuck Lever Signed-off-by: Greg Kroah-Hartman --- net/sunrpc/xprtrdma/svc_rdma_rw.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/net/sunrpc/xprtrdma/svc_rdma_rw.c b/net/sunrpc/xprtrdma/svc_rdma_rw.c index 661b3fe2779f0c..b5cd37884f53d5 100644 --- a/net/sunrpc/xprtrdma/svc_rdma_rw.c +++ b/net/sunrpc/xprtrdma/svc_rdma_rw.c @@ -841,6 +841,9 @@ static int svc_rdma_copy_inline_range(struct svc_rqst *rqstp, for (page_no = 0; page_no < numpages; page_no++) { unsigned int page_len; + if (head->rc_curpage >= rqstp->rq_maxpages) + return -EINVAL; + page_len = min_t(unsigned int, remaining, PAGE_SIZE - head->rc_pageoff); From 433bb1344ef66cc071d7e1977744ef9b06a15c2b Mon Sep 17 00:00:00 2001 From: Andy Shevchenko Date: Thu, 13 Nov 2025 09:31:31 +0100 Subject: [PATCH 1680/3902] nfsd: Mark variable __maybe_unused to avoid W=1 build break commit ebae102897e760e9e6bc625f701dd666b2163bd1 upstream. Clang is not happy about set but (in some cases) unused variable: fs/nfsd/export.c:1027:17: error: variable 'inode' set but not used [-Werror,-Wunused-but-set-variable] since it's used as a parameter to dprintk() which might be configured a no-op. To avoid uglifying code with the specific ifdeffery just mark the variable __maybe_unused. The commit [1], which introduced this behaviour, is quite old and hence the Fixes tag points to the first of the Git era. Link: https://git.kernel.org/pub/scm/linux/kernel/git/history/history.git/commit/?id=0431923fb7a1 [1] Fixes: 1da177e4c3f4 ("Linux-2.6.12-rc2") Cc: stable@vger.kernel.org Signed-off-by: Andy Shevchenko Signed-off-by: Chuck Lever Signed-off-by: Greg Kroah-Hartman --- fs/nfsd/export.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fs/nfsd/export.c b/fs/nfsd/export.c index 9d55512d0cc97f..2a1499f2ad196a 100644 --- a/fs/nfsd/export.c +++ b/fs/nfsd/export.c @@ -1024,7 +1024,7 @@ exp_rootfh(struct net *net, struct auth_domain *clp, char *name, { struct svc_export *exp; struct path path; - struct inode *inode; + struct inode *inode __maybe_unused; struct svc_fh fh; int err; struct nfsd_net *nn = net_generic(net, nfsd_net_id); From 4846a4c6acb98efdefc1274c735746c3c31090e4 Mon Sep 17 00:00:00 2001 From: Joshua Rogers Date: Fri, 7 Nov 2025 10:09:48 -0500 Subject: [PATCH 1681/3902] svcrdma: return 0 on success from svc_rdma_copy_inline_range commit 94972027ab55b200e031059fd6c7a649f8248020 upstream. The function comment specifies 0 on success and -EINVAL on invalid parameters. Make the tail return 0 after a successful copy loop. Fixes: d7cc73972661 ("svcrdma: support multiple Read chunks per RPC") Cc: stable@vger.kernel.org Signed-off-by: Joshua Rogers Signed-off-by: Chuck Lever Signed-off-by: Greg Kroah-Hartman --- net/sunrpc/xprtrdma/svc_rdma_rw.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/net/sunrpc/xprtrdma/svc_rdma_rw.c b/net/sunrpc/xprtrdma/svc_rdma_rw.c index b5cd37884f53d5..5bc950d29366bd 100644 --- a/net/sunrpc/xprtrdma/svc_rdma_rw.c +++ b/net/sunrpc/xprtrdma/svc_rdma_rw.c @@ -863,7 +863,7 @@ static int svc_rdma_copy_inline_range(struct svc_rqst *rqstp, offset += page_len; } - return -EINVAL; + return 0; } /** From 2a77c8dd49bccf0ca232be7c836cec1209abb8da Mon Sep 17 00:00:00 2001 From: Joshua Rogers Date: Fri, 7 Nov 2025 10:09:47 -0500 Subject: [PATCH 1682/3902] svcrdma: use rc_pageoff for memcpy byte offset commit a8ee9099f30654917aa68f55d707b5627e1dbf77 upstream. svc_rdma_copy_inline_range added rc_curpage (page index) to the page base instead of the byte offset rc_pageoff. Use rc_pageoff so copies land within the current page. Found by ZeroPath (https://zeropath.com) Fixes: 8e122582680c ("svcrdma: Move svc_rdma_read_info::ri_pageno to struct svc_rdma_recv_ctxt") Cc: stable@vger.kernel.org Signed-off-by: Joshua Rogers Signed-off-by: Chuck Lever Signed-off-by: Greg Kroah-Hartman --- net/sunrpc/xprtrdma/svc_rdma_rw.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/net/sunrpc/xprtrdma/svc_rdma_rw.c b/net/sunrpc/xprtrdma/svc_rdma_rw.c index 5bc950d29366bd..310de7a80be52a 100644 --- a/net/sunrpc/xprtrdma/svc_rdma_rw.c +++ b/net/sunrpc/xprtrdma/svc_rdma_rw.c @@ -851,7 +851,7 @@ static int svc_rdma_copy_inline_range(struct svc_rqst *rqstp, head->rc_page_count++; dst = page_address(rqstp->rq_pages[head->rc_curpage]); - memcpy(dst + head->rc_curpage, src + offset, page_len); + memcpy((unsigned char *)dst + head->rc_pageoff, src + offset, page_len); head->rc_readbytes += page_len; head->rc_pageoff += page_len; From 1c8bb965e9b0559ff0f5690615a527c30f651dd8 Mon Sep 17 00:00:00 2001 From: Joshua Rogers Date: Fri, 7 Nov 2025 10:05:33 -0500 Subject: [PATCH 1683/3902] SUNRPC: svcauth_gss: avoid NULL deref on zero length gss_token in gss_read_proxy_verf commit d4b69a6186b215d2dc1ebcab965ed88e8d41768d upstream. A zero length gss_token results in pages == 0 and in_token->pages[0] is NULL. The code unconditionally evaluates page_address(in_token->pages[0]) for the initial memcpy, which can dereference NULL even when the copy length is 0. Guard the first memcpy so it only runs when length > 0. Fixes: 5866efa8cbfb ("SUNRPC: Fix svcauth_gss_proxy_init()") Cc: stable@vger.kernel.org Signed-off-by: Joshua Rogers Signed-off-by: Chuck Lever Signed-off-by: Greg Kroah-Hartman --- net/sunrpc/auth_gss/svcauth_gss.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/net/sunrpc/auth_gss/svcauth_gss.c b/net/sunrpc/auth_gss/svcauth_gss.c index a8ec30759a184e..e2f0df8cdaa6a0 100644 --- a/net/sunrpc/auth_gss/svcauth_gss.c +++ b/net/sunrpc/auth_gss/svcauth_gss.c @@ -1083,7 +1083,8 @@ static int gss_read_proxy_verf(struct svc_rqst *rqstp, } length = min_t(unsigned int, inlen, (char *)xdr->end - (char *)xdr->p); - memcpy(page_address(in_token->pages[0]), xdr->p, length); + if (length) + memcpy(page_address(in_token->pages[0]), xdr->p, length); inlen -= length; to_offs = length; From 0d5c9e901ad40bd39b38e119c0454b52d7663930 Mon Sep 17 00:00:00 2001 From: "Nysal Jan K.A." Date: Tue, 28 Oct 2025 16:25:12 +0530 Subject: [PATCH 1684/3902] powerpc/kexec: Enable SMT before waking offline CPUs commit c2296a1e42418556efbeb5636c4fa6aa6106713a upstream. If SMT is disabled or a partial SMT state is enabled, when a new kernel image is loaded for kexec, on reboot the following warning is observed: kexec: Waking offline cpu 228. WARNING: CPU: 0 PID: 9062 at arch/powerpc/kexec/core_64.c:223 kexec_prepare_cpus+0x1b0/0x1bc [snip] NIP kexec_prepare_cpus+0x1b0/0x1bc LR kexec_prepare_cpus+0x1a0/0x1bc Call Trace: kexec_prepare_cpus+0x1a0/0x1bc (unreliable) default_machine_kexec+0x160/0x19c machine_kexec+0x80/0x88 kernel_kexec+0xd0/0x118 __do_sys_reboot+0x210/0x2c4 system_call_exception+0x124/0x320 system_call_vectored_common+0x15c/0x2ec This occurs as add_cpu() fails due to cpu_bootable() returning false for CPUs that fail the cpu_smt_thread_allowed() check or non primary threads if SMT is disabled. Fix the issue by enabling SMT and resetting the number of SMT threads to the number of threads per core, before attempting to wake up all present CPUs. Fixes: 38253464bc82 ("cpu/SMT: Create topology_smt_thread_allowed()") Reported-by: Sachin P Bappalige Cc: stable@vger.kernel.org # v6.6+ Reviewed-by: Srikar Dronamraju Signed-off-by: Nysal Jan K.A. Tested-by: Samir M Reviewed-by: Sourabh Jain Signed-off-by: Madhavan Srinivasan Link: https://patch.msgid.link/20251028105516.26258-1-nysal@linux.ibm.com Signed-off-by: Greg Kroah-Hartman --- arch/powerpc/kexec/core_64.c | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/arch/powerpc/kexec/core_64.c b/arch/powerpc/kexec/core_64.c index 222aa326daceef..825ab8a88f18e6 100644 --- a/arch/powerpc/kexec/core_64.c +++ b/arch/powerpc/kexec/core_64.c @@ -202,6 +202,23 @@ static void kexec_prepare_cpus_wait(int wait_state) mb(); } + +/* + * The add_cpu() call in wake_offline_cpus() can fail as cpu_bootable() + * returns false for CPUs that fail the cpu_smt_thread_allowed() check + * or non primary threads if SMT is disabled. Re-enable SMT and set the + * number of SMT threads to threads per core. + */ +static void kexec_smt_reenable(void) +{ +#if defined(CONFIG_SMP) && defined(CONFIG_HOTPLUG_SMT) + lock_device_hotplug(); + cpu_smt_num_threads = threads_per_core; + cpu_smt_control = CPU_SMT_ENABLED; + unlock_device_hotplug(); +#endif +} + /* * We need to make sure each present CPU is online. The next kernel will scan * the device tree and assume primary threads are online and query secondary @@ -216,6 +233,8 @@ static void wake_offline_cpus(void) { int cpu = 0; + kexec_smt_reenable(); + for_each_present_cpu(cpu) { if (!cpu_online(cpu)) { printk(KERN_INFO "kexec: Waking offline cpu %d.\n", From a35788ddf8df65837897ecbb0ddb2896b863159e Mon Sep 17 00:00:00 2001 From: Filipe Manana Date: Thu, 27 Nov 2025 16:35:59 +0000 Subject: [PATCH 1685/3902] btrfs: don't log conflicting inode if it's a dir moved in the current transaction commit 266273eaf4d99475f1ae57f687b3e42bc71ec6f0 upstream. We can't log a conflicting inode if it's a directory and it was moved from one parent directory to another parent directory in the current transaction, as this can result an attempt to have a directory with two hard links during log replay, one for the old parent directory and another for the new parent directory. The following scenario triggers that issue: 1) We have directories "dir1" and "dir2" created in a past transaction. Directory "dir1" has inode A as its parent directory; 2) We move "dir1" to some other directory; 3) We create a file with the name "dir1" in directory inode A; 4) We fsync the new file. This results in logging the inode of the new file and the inode for the directory "dir1" that was previously moved in the current transaction. So the log tree has the INODE_REF item for the new location of "dir1"; 5) We move the new file to some other directory. This results in updating the log tree to included the new INODE_REF for the new location of the file and removes the INODE_REF for the old location. This happens during the rename when we call btrfs_log_new_name(); 6) We fsync the file, and that persists the log tree changes done in the previous step (btrfs_log_new_name() only updates the log tree in memory); 7) We have a power failure; 8) Next time the fs is mounted, log replay happens and when processing the inode for directory "dir1" we find a new INODE_REF and add that link, but we don't remove the old link of the inode since we have not logged the old parent directory of the directory inode "dir1". As a result after log replay finishes when we trigger writeback of the subvolume tree's extent buffers, the tree check will detect that we have a directory a hard link count of 2 and we get a mount failure. The errors and stack traces reported in dmesg/syslog are like this: [ 3845.729764] BTRFS info (device dm-0): start tree-log replay [ 3845.730304] page: refcount:3 mapcount:0 mapping:000000005c8a3027 index:0x1d00 pfn:0x11510c [ 3845.731236] memcg:ffff9264c02f4e00 [ 3845.731751] aops:btree_aops [btrfs] ino:1 [ 3845.732300] flags: 0x17fffc00000400a(uptodate|private|writeback|node=0|zone=2|lastcpupid=0x1ffff) [ 3845.733346] raw: 017fffc00000400a 0000000000000000 dead000000000122 ffff9264d978aea8 [ 3845.734265] raw: 0000000000001d00 ffff92650e6d4738 00000003ffffffff ffff9264c02f4e00 [ 3845.735305] page dumped because: eb page dump [ 3845.735981] BTRFS critical (device dm-0): corrupt leaf: root=5 block=30408704 slot=6 ino=257, invalid nlink: has 2 expect no more than 1 for dir [ 3845.737786] BTRFS info (device dm-0): leaf 30408704 gen 10 total ptrs 17 free space 14881 owner 5 [ 3845.737789] BTRFS info (device dm-0): refs 4 lock_owner 0 current 30701 [ 3845.737792] item 0 key (256 INODE_ITEM 0) itemoff 16123 itemsize 160 [ 3845.737794] inode generation 3 transid 9 size 16 nbytes 16384 [ 3845.737795] block group 0 mode 40755 links 1 uid 0 gid 0 [ 3845.737797] rdev 0 sequence 2 flags 0x0 [ 3845.737798] atime 1764259517.0 [ 3845.737800] ctime 1764259517.572889464 [ 3845.737801] mtime 1764259517.572889464 [ 3845.737802] otime 1764259517.0 [ 3845.737803] item 1 key (256 INODE_REF 256) itemoff 16111 itemsize 12 [ 3845.737805] index 0 name_len 2 [ 3845.737807] item 2 key (256 DIR_ITEM 2363071922) itemoff 16077 itemsize 34 [ 3845.737808] location key (257 1 0) type 2 [ 3845.737810] transid 9 data_len 0 name_len 4 [ 3845.737811] item 3 key (256 DIR_ITEM 2676584006) itemoff 16043 itemsize 34 [ 3845.737813] location key (258 1 0) type 2 [ 3845.737814] transid 9 data_len 0 name_len 4 [ 3845.737815] item 4 key (256 DIR_INDEX 2) itemoff 16009 itemsize 34 [ 3845.737816] location key (257 1 0) type 2 [ 3845.737818] transid 9 data_len 0 name_len 4 [ 3845.737819] item 5 key (256 DIR_INDEX 3) itemoff 15975 itemsize 34 [ 3845.737820] location key (258 1 0) type 2 [ 3845.737821] transid 9 data_len 0 name_len 4 [ 3845.737822] item 6 key (257 INODE_ITEM 0) itemoff 15815 itemsize 160 [ 3845.737824] inode generation 9 transid 10 size 6 nbytes 0 [ 3845.737825] block group 0 mode 40755 links 2 uid 0 gid 0 [ 3845.737826] rdev 0 sequence 1 flags 0x0 [ 3845.737827] atime 1764259517.572889464 [ 3845.737828] ctime 1764259517.572889464 [ 3845.737830] mtime 1764259517.572889464 [ 3845.737831] otime 1764259517.572889464 [ 3845.737832] item 7 key (257 INODE_REF 256) itemoff 15801 itemsize 14 [ 3845.737833] index 2 name_len 4 [ 3845.737834] item 8 key (257 INODE_REF 258) itemoff 15787 itemsize 14 [ 3845.737836] index 2 name_len 4 [ 3845.737837] item 9 key (257 DIR_ITEM 2507850652) itemoff 15754 itemsize 33 [ 3845.737838] location key (259 1 0) type 1 [ 3845.737839] transid 10 data_len 0 name_len 3 [ 3845.737840] item 10 key (257 DIR_INDEX 2) itemoff 15721 itemsize 33 [ 3845.737842] location key (259 1 0) type 1 [ 3845.737843] transid 10 data_len 0 name_len 3 [ 3845.737844] item 11 key (258 INODE_ITEM 0) itemoff 15561 itemsize 160 [ 3845.737846] inode generation 9 transid 10 size 8 nbytes 0 [ 3845.737847] block group 0 mode 40755 links 1 uid 0 gid 0 [ 3845.737848] rdev 0 sequence 1 flags 0x0 [ 3845.737849] atime 1764259517.572889464 [ 3845.737850] ctime 1764259517.572889464 [ 3845.737851] mtime 1764259517.572889464 [ 3845.737852] otime 1764259517.572889464 [ 3845.737853] item 12 key (258 INODE_REF 256) itemoff 15547 itemsize 14 [ 3845.737855] index 3 name_len 4 [ 3845.737856] item 13 key (258 DIR_ITEM 1843588421) itemoff 15513 itemsize 34 [ 3845.737857] location key (257 1 0) type 2 [ 3845.737858] transid 10 data_len 0 name_len 4 [ 3845.737860] item 14 key (258 DIR_INDEX 2) itemoff 15479 itemsize 34 [ 3845.737861] location key (257 1 0) type 2 [ 3845.737862] transid 10 data_len 0 name_len 4 [ 3845.737863] item 15 key (259 INODE_ITEM 0) itemoff 15319 itemsize 160 [ 3845.737865] inode generation 10 transid 10 size 0 nbytes 0 [ 3845.737866] block group 0 mode 100600 links 1 uid 0 gid 0 [ 3845.737867] rdev 0 sequence 2 flags 0x0 [ 3845.737868] atime 1764259517.580874966 [ 3845.737869] ctime 1764259517.586121869 [ 3845.737870] mtime 1764259517.580874966 [ 3845.737872] otime 1764259517.580874966 [ 3845.737873] item 16 key (259 INODE_REF 257) itemoff 15306 itemsize 13 [ 3845.737874] index 2 name_len 3 [ 3845.737875] BTRFS error (device dm-0): block=30408704 write time tree block corruption detected [ 3845.739448] ------------[ cut here ]------------ [ 3845.740092] WARNING: CPU: 5 PID: 30701 at fs/btrfs/disk-io.c:335 btree_csum_one_bio+0x25a/0x270 [btrfs] [ 3845.741439] Modules linked in: btrfs dm_flakey crc32c_cryptoapi (...) [ 3845.750626] CPU: 5 UID: 0 PID: 30701 Comm: mount Tainted: G W 6.18.0-rc6-btrfs-next-218+ #1 PREEMPT(full) [ 3845.752414] Tainted: [W]=WARN [ 3845.752828] Hardware name: QEMU Standard PC (i440FX + PIIX, 1996), BIOS rel-1.16.2-0-gea1b7a073390-prebuilt.qemu.org 04/01/2014 [ 3845.754499] RIP: 0010:btree_csum_one_bio+0x25a/0x270 [btrfs] [ 3845.755460] Code: 31 f6 48 89 (...) [ 3845.758685] RSP: 0018:ffffa8d9c5677678 EFLAGS: 00010246 [ 3845.759450] RAX: 0000000000000000 RBX: ffff92650e6d4738 RCX: 0000000000000000 [ 3845.760309] RDX: 0000000000000000 RSI: ffffffff9aab45b9 RDI: ffff9264c4748000 [ 3845.761239] RBP: ffff9264d4324000 R08: 0000000000000000 R09: ffffa8d9c5677468 [ 3845.762607] R10: ffff926bdc1fffa8 R11: 0000000000000003 R12: ffffa8d9c5677680 [ 3845.764099] R13: 0000000000004000 R14: ffff9264dd624000 R15: ffff9264d978aba8 [ 3845.765094] FS: 00007f751fa5a840(0000) GS:ffff926c42a82000(0000) knlGS:0000000000000000 [ 3845.766226] CS: 0010 DS: 0000 ES: 0000 CR0: 0000000080050033 [ 3845.766970] CR2: 0000558df1815380 CR3: 000000010ed88003 CR4: 0000000000370ef0 [ 3845.768009] Call Trace: [ 3845.768392] [ 3845.768714] btrfs_submit_bbio+0x6ee/0x7f0 [btrfs] [ 3845.769640] ? write_one_eb+0x28e/0x340 [btrfs] [ 3845.770588] btree_write_cache_pages+0x2f0/0x550 [btrfs] [ 3845.771286] ? alloc_extent_state+0x19/0x100 [btrfs] [ 3845.771967] ? merge_next_state+0x1a/0x90 [btrfs] [ 3845.772586] ? set_extent_bit+0x233/0x8b0 [btrfs] [ 3845.773198] ? xas_load+0x9/0xc0 [ 3845.773589] ? xas_find+0x14d/0x1a0 [ 3845.773969] do_writepages+0xc6/0x160 [ 3845.774367] filemap_fdatawrite_wbc+0x48/0x60 [ 3845.775003] __filemap_fdatawrite_range+0x5b/0x80 [ 3845.775902] btrfs_write_marked_extents+0x61/0x170 [btrfs] [ 3845.776707] btrfs_write_and_wait_transaction+0x4e/0xc0 [btrfs] [ 3845.777379] ? _raw_spin_unlock_irqrestore+0x23/0x40 [ 3845.777923] btrfs_commit_transaction+0x5ea/0xd20 [btrfs] [ 3845.778551] ? _raw_spin_unlock+0x15/0x30 [ 3845.778986] ? release_extent_buffer+0x34/0x160 [btrfs] [ 3845.779659] btrfs_recover_log_trees+0x7a3/0x7c0 [btrfs] [ 3845.780416] ? __pfx_replay_one_buffer+0x10/0x10 [btrfs] [ 3845.781499] open_ctree+0x10bb/0x15f0 [btrfs] [ 3845.782194] btrfs_get_tree.cold+0xb/0x16c [btrfs] [ 3845.782764] ? fscontext_read+0x15c/0x180 [ 3845.783202] ? rw_verify_area+0x50/0x180 [ 3845.783667] vfs_get_tree+0x25/0xd0 [ 3845.784047] vfs_cmd_create+0x59/0xe0 [ 3845.784458] __do_sys_fsconfig+0x4f6/0x6b0 [ 3845.784914] do_syscall_64+0x50/0x1220 [ 3845.785340] entry_SYSCALL_64_after_hwframe+0x76/0x7e [ 3845.785980] RIP: 0033:0x7f751fc7f4aa [ 3845.786759] Code: 73 01 c3 48 (...) [ 3845.789951] RSP: 002b:00007ffcdba45dc8 EFLAGS: 00000246 ORIG_RAX: 00000000000001af [ 3845.791402] RAX: ffffffffffffffda RBX: 000055ccc8291c20 RCX: 00007f751fc7f4aa [ 3845.792688] RDX: 0000000000000000 RSI: 0000000000000006 RDI: 0000000000000003 [ 3845.794308] RBP: 000055ccc8292120 R08: 0000000000000000 R09: 0000000000000000 [ 3845.795829] R10: 0000000000000000 R11: 0000000000000246 R12: 0000000000000000 [ 3845.797183] R13: 00007f751fe11580 R14: 00007f751fe1326c R15: 00007f751fdf8a23 [ 3845.798633] [ 3845.799067] ---[ end trace 0000000000000000 ]--- [ 3845.800215] BTRFS: error (device dm-0) in btrfs_commit_transaction:2553: errno=-5 IO failure (Error while writing out transaction) [ 3845.801860] BTRFS warning (device dm-0 state E): Skipping commit of aborted transaction. [ 3845.802815] BTRFS error (device dm-0 state EA): Transaction aborted (error -5) [ 3845.803728] BTRFS: error (device dm-0 state EA) in cleanup_transaction:2036: errno=-5 IO failure [ 3845.805374] BTRFS: error (device dm-0 state EA) in btrfs_replay_log:2083: errno=-5 IO failure (Failed to recover log tree) [ 3845.807919] BTRFS error (device dm-0 state EA): open_ctree failed: -5 Fix this by never logging a conflicting inode that is a directory and was moved in the current transaction (its last_unlink_trans equals the current transaction) and instead fallback to a transaction commit. A test case for fstests will follow soon. Reported-by: Vyacheslav Kovalevsky Link: https://lore.kernel.org/linux-btrfs/7bbc9419-5c56-450a-b5a0-efeae7457113@gmail.com/ CC: stable@vger.kernel.org # 6.1+ Signed-off-by: Filipe Manana Signed-off-by: David Sterba Signed-off-by: Greg Kroah-Hartman --- fs/btrfs/tree-log.c | 38 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 38 insertions(+) diff --git a/fs/btrfs/tree-log.c b/fs/btrfs/tree-log.c index f55d886debe2fa..e0b0750696a15b 100644 --- a/fs/btrfs/tree-log.c +++ b/fs/btrfs/tree-log.c @@ -6050,6 +6050,33 @@ static int conflicting_inode_is_dir(struct btrfs_root *root, u64 ino, return ret; } +static bool can_log_conflicting_inode(const struct btrfs_trans_handle *trans, + const struct btrfs_inode *inode) +{ + if (!S_ISDIR(inode->vfs_inode.i_mode)) + return true; + + if (inode->last_unlink_trans < trans->transid) + return true; + + /* + * If this is a directory and its unlink_trans is not from a past + * transaction then we must fallback to a transaction commit in order + * to avoid getting a directory with 2 hard links after log replay. + * + * This happens if a directory A is renamed, moved from one parent + * directory to another one, a new file is created in the old parent + * directory with the old name of our directory A, the new file is + * fsynced, then we moved the new file to some other parent directory + * and fsync again the new file. This results in a log tree where we + * logged that directory A existed, with the INODE_REF item for the + * new location but without having logged its old parent inode, so + * that on log replay we add a new link for the new location but the + * old link remains, resulting in a link count of 2. + */ + return false; +} + static int add_conflicting_inode(struct btrfs_trans_handle *trans, struct btrfs_root *root, struct btrfs_path *path, @@ -6153,6 +6180,11 @@ static int add_conflicting_inode(struct btrfs_trans_handle *trans, return 0; } + if (!can_log_conflicting_inode(trans, inode)) { + btrfs_add_delayed_iput(inode); + return BTRFS_LOG_FORCE_COMMIT; + } + btrfs_add_delayed_iput(inode); ino_elem = kmalloc(sizeof(*ino_elem), GFP_NOFS); @@ -6217,6 +6249,12 @@ static int log_conflicting_inodes(struct btrfs_trans_handle *trans, break; } + if (!can_log_conflicting_inode(trans, inode)) { + btrfs_add_delayed_iput(inode); + ret = BTRFS_LOG_FORCE_COMMIT; + break; + } + /* * Always log the directory, we cannot make this * conditional on need_log_inode() because the directory From 5fcdaa29e5992c2bb66cc93aa78337d0bc5982dc Mon Sep 17 00:00:00 2001 From: Shakeel Butt Date: Fri, 5 Dec 2025 12:01:06 -0800 Subject: [PATCH 1686/3902] cgroup: rstat: use LOCK CMPXCHG in css_rstat_updated commit 3309b63a2281efb72df7621d60cc1246b6286ad3 upstream. On x86-64, this_cpu_cmpxchg() uses CMPXCHG without LOCK prefix which means it is only safe for the local CPU and not for multiple CPUs. Recently the commit 36df6e3dbd7e ("cgroup: make css_rstat_updated nmi safe") make css_rstat_updated lockless and uses lockless list to allow reentrancy. Since css_rstat_updated can invoked from process context, IRQ and NMI, it uses this_cpu_cmpxchg() to select the winner which will inset the lockless lnode into the global per-cpu lockless list. However the commit missed one case where lockless node of a cgroup can be accessed and modified by another CPU doing the flushing. Basically llist_del_first_init() in css_process_update_tree(). On a cursory look, it can be questioned how css_process_update_tree() can see a lockless node in global lockless list where the updater is at this_cpu_cmpxchg() and before llist_add() call in css_rstat_updated(). This can indeed happen in the presence of IRQs/NMI. Consider this scenario: Updater for cgroup stat C on CPU A in process context is after llist_on_list() check and before this_cpu_cmpxchg() in css_rstat_updated() where it get interrupted by IRQ/NMI. In the IRQ/NMI context, a new updater calls css_rstat_updated() for same cgroup C and successfully inserts rstatc_pcpu->lnode. Now concurrently CPU B is running the flusher and it calls llist_del_first_init() for CPU A and got rstatc_pcpu->lnode of cgroup C which was added by the IRQ/NMI updater. Now imagine CPU B calling init_llist_node() on cgroup C's rstatc_pcpu->lnode of CPU A and on CPU A, the process context updater calling this_cpu_cmpxchg(rstatc_pcpu->lnode) concurrently. The CMPXCNG without LOCK on CPU A is not safe and thus we need LOCK prefix. In Meta's fleet running the kernel with the commit 36df6e3dbd7e, we are observing on some machines the memcg stats are getting skewed by more than the actual memory on the system. On close inspection, we noticed that lockless node for a workload for specific CPU was in the bad state and thus all the updates on that CPU for that cgroup was being lost. To confirm if this skew was indeed due to this CMPXCHG without LOCK in css_rstat_updated(), we created a repro (using AI) at [1] which shows that CMPXCHG without LOCK creates almost the same lnode corruption as seem in Meta's fleet and with LOCK CMPXCHG the issue does not reproduces. Link: http://lore.kernel.org/efiagdwmzfwpdzps74fvcwq3n4cs36q33ij7eebcpssactv3zu@se4hqiwxcfxq [1] Signed-off-by: Shakeel Butt Cc: stable@vger.kernel.org # v6.17+ Fixes: 36df6e3dbd7e ("cgroup: make css_rstat_updated nmi safe") Signed-off-by: Tejun Heo Signed-off-by: Greg Kroah-Hartman --- kernel/cgroup/rstat.c | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/kernel/cgroup/rstat.c b/kernel/cgroup/rstat.c index a198e40c799b48..150e5871e66f25 100644 --- a/kernel/cgroup/rstat.c +++ b/kernel/cgroup/rstat.c @@ -71,7 +71,6 @@ __bpf_kfunc void css_rstat_updated(struct cgroup_subsys_state *css, int cpu) { struct llist_head *lhead; struct css_rstat_cpu *rstatc; - struct css_rstat_cpu __percpu *rstatc_pcpu; struct llist_node *self; /* @@ -104,18 +103,22 @@ __bpf_kfunc void css_rstat_updated(struct cgroup_subsys_state *css, int cpu) /* * This function can be renentered by irqs and nmis for the same cgroup * and may try to insert the same per-cpu lnode into the llist. Note - * that llist_add() does not protect against such scenarios. + * that llist_add() does not protect against such scenarios. In addition + * this same per-cpu lnode can be modified through init_llist_node() + * from css_rstat_flush() running on a different CPU. * * To protect against such stacked contexts of irqs/nmis, we use the * fact that lnode points to itself when not on a list and then use - * this_cpu_cmpxchg() to atomically set to NULL to select the winner + * try_cmpxchg() to atomically set to NULL to select the winner * which will call llist_add(). The losers can assume the insertion is * successful and the winner will eventually add the per-cpu lnode to * the llist. + * + * Please note that we can not use this_cpu_cmpxchg() here as on some + * archs it is not safe against modifications from multiple CPUs. */ self = &rstatc->lnode; - rstatc_pcpu = css->rstat_cpu; - if (this_cpu_cmpxchg(rstatc_pcpu->lnode.next, self, NULL) != self) + if (!try_cmpxchg(&rstatc->lnode.next, &self, NULL)) return; lhead = ss_lhead_cpu(css->ss, cpu); From 0f5940176ad6f43b7a182612460b4df84368a09a Mon Sep 17 00:00:00 2001 From: Sven Schnelle Date: Fri, 5 Dec 2025 10:58:57 +0100 Subject: [PATCH 1687/3902] s390/ipl: Clear SBP flag when bootprog is set commit b1aa01d31249bd116b18c7f512d3e46b4b4ad83b upstream. With z16 a new flag 'search boot program' was introduced for list-directed IPL (SCSI, NVMe, ECKD DASD). If this flag is set, e.g. via selecting the "Automatic" value for the "Boot program selector" control on an HMC load panel, it is copied to the reipl structure from the initial ipl structure. When a user now sets a boot prog via sysfs, the flag is not cleared and the bootloader will again automatically select the boot program, ignoring user configuration. To avoid that, clear the SBP flag when a bootprog sysfs file is written. Cc: stable@vger.kernel.org Reviewed-by: Peter Oberparleiter Reviewed-by: Heiko Carstens Signed-off-by: Sven Schnelle Signed-off-by: Heiko Carstens Signed-off-by: Greg Kroah-Hartman --- arch/s390/include/uapi/asm/ipl.h | 1 + arch/s390/kernel/ipl.c | 48 ++++++++++++++++++++++++-------- 2 files changed, 37 insertions(+), 12 deletions(-) diff --git a/arch/s390/include/uapi/asm/ipl.h b/arch/s390/include/uapi/asm/ipl.h index 2cd28af50dd439..3d64a22516994b 100644 --- a/arch/s390/include/uapi/asm/ipl.h +++ b/arch/s390/include/uapi/asm/ipl.h @@ -15,6 +15,7 @@ struct ipl_pl_hdr { #define IPL_PL_FLAG_IPLPS 0x80 #define IPL_PL_FLAG_SIPL 0x40 #define IPL_PL_FLAG_IPLSR 0x20 +#define IPL_PL_FLAG_SBP 0x10 /* IPL Parameter Block header */ struct ipl_pb_hdr { diff --git a/arch/s390/kernel/ipl.c b/arch/s390/kernel/ipl.c index 961a3d60a4ddda..dcdc7e27484867 100644 --- a/arch/s390/kernel/ipl.c +++ b/arch/s390/kernel/ipl.c @@ -262,6 +262,24 @@ static struct kobj_attribute sys_##_prefix##_##_name##_attr = \ sys_##_prefix##_##_name##_show, \ sys_##_prefix##_##_name##_store) +#define DEFINE_IPL_ATTR_BOOTPROG_RW(_prefix, _name, _fmt_out, _fmt_in, _hdr, _value) \ + IPL_ATTR_SHOW_FN(_prefix, _name, _fmt_out, (unsigned long long) _value) \ +static ssize_t sys_##_prefix##_##_name##_store(struct kobject *kobj, \ + struct kobj_attribute *attr, \ + const char *buf, size_t len) \ +{ \ + unsigned long long value; \ + if (sscanf(buf, _fmt_in, &value) != 1) \ + return -EINVAL; \ + (_value) = value; \ + (_hdr).flags &= ~IPL_PL_FLAG_SBP; \ + return len; \ +} \ +static struct kobj_attribute sys_##_prefix##_##_name##_attr = \ + __ATTR(_name, 0644, \ + sys_##_prefix##_##_name##_show, \ + sys_##_prefix##_##_name##_store) + #define DEFINE_IPL_ATTR_STR_RW(_prefix, _name, _fmt_out, _fmt_in, _value)\ IPL_ATTR_SHOW_FN(_prefix, _name, _fmt_out, _value) \ static ssize_t sys_##_prefix##_##_name##_store(struct kobject *kobj, \ @@ -818,12 +836,13 @@ DEFINE_IPL_ATTR_RW(reipl_fcp, wwpn, "0x%016llx\n", "%llx\n", reipl_block_fcp->fcp.wwpn); DEFINE_IPL_ATTR_RW(reipl_fcp, lun, "0x%016llx\n", "%llx\n", reipl_block_fcp->fcp.lun); -DEFINE_IPL_ATTR_RW(reipl_fcp, bootprog, "%lld\n", "%lld\n", - reipl_block_fcp->fcp.bootprog); DEFINE_IPL_ATTR_RW(reipl_fcp, br_lba, "%lld\n", "%lld\n", reipl_block_fcp->fcp.br_lba); DEFINE_IPL_ATTR_RW(reipl_fcp, device, "0.0.%04llx\n", "0.0.%llx\n", reipl_block_fcp->fcp.devno); +DEFINE_IPL_ATTR_BOOTPROG_RW(reipl_fcp, bootprog, "%lld\n", "%lld\n", + reipl_block_fcp->hdr, + reipl_block_fcp->fcp.bootprog); static void reipl_get_ascii_loadparm(char *loadparm, struct ipl_parameter_block *ibp) @@ -942,10 +961,11 @@ DEFINE_IPL_ATTR_RW(reipl_nvme, fid, "0x%08llx\n", "%llx\n", reipl_block_nvme->nvme.fid); DEFINE_IPL_ATTR_RW(reipl_nvme, nsid, "0x%08llx\n", "%llx\n", reipl_block_nvme->nvme.nsid); -DEFINE_IPL_ATTR_RW(reipl_nvme, bootprog, "%lld\n", "%lld\n", - reipl_block_nvme->nvme.bootprog); DEFINE_IPL_ATTR_RW(reipl_nvme, br_lba, "%lld\n", "%lld\n", reipl_block_nvme->nvme.br_lba); +DEFINE_IPL_ATTR_BOOTPROG_RW(reipl_nvme, bootprog, "%lld\n", "%lld\n", + reipl_block_nvme->hdr, + reipl_block_nvme->nvme.bootprog); static struct attribute *reipl_nvme_attrs[] = { &sys_reipl_nvme_fid_attr.attr, @@ -1038,8 +1058,9 @@ static const struct bin_attribute *const reipl_eckd_bin_attrs[] = { }; DEFINE_IPL_CCW_ATTR_RW(reipl_eckd, device, reipl_block_eckd->eckd); -DEFINE_IPL_ATTR_RW(reipl_eckd, bootprog, "%lld\n", "%lld\n", - reipl_block_eckd->eckd.bootprog); +DEFINE_IPL_ATTR_BOOTPROG_RW(reipl_eckd, bootprog, "%lld\n", "%lld\n", + reipl_block_eckd->hdr, + reipl_block_eckd->eckd.bootprog); static struct attribute *reipl_eckd_attrs[] = { &sys_reipl_eckd_device_attr.attr, @@ -1567,12 +1588,13 @@ DEFINE_IPL_ATTR_RW(dump_fcp, wwpn, "0x%016llx\n", "%llx\n", dump_block_fcp->fcp.wwpn); DEFINE_IPL_ATTR_RW(dump_fcp, lun, "0x%016llx\n", "%llx\n", dump_block_fcp->fcp.lun); -DEFINE_IPL_ATTR_RW(dump_fcp, bootprog, "%lld\n", "%lld\n", - dump_block_fcp->fcp.bootprog); DEFINE_IPL_ATTR_RW(dump_fcp, br_lba, "%lld\n", "%lld\n", dump_block_fcp->fcp.br_lba); DEFINE_IPL_ATTR_RW(dump_fcp, device, "0.0.%04llx\n", "0.0.%llx\n", dump_block_fcp->fcp.devno); +DEFINE_IPL_ATTR_BOOTPROG_RW(dump_fcp, bootprog, "%lld\n", "%lld\n", + dump_block_fcp->hdr, + dump_block_fcp->fcp.bootprog); DEFINE_IPL_ATTR_SCP_DATA_RW(dump_fcp, dump_block_fcp->hdr, dump_block_fcp->fcp, @@ -1604,10 +1626,11 @@ DEFINE_IPL_ATTR_RW(dump_nvme, fid, "0x%08llx\n", "%llx\n", dump_block_nvme->nvme.fid); DEFINE_IPL_ATTR_RW(dump_nvme, nsid, "0x%08llx\n", "%llx\n", dump_block_nvme->nvme.nsid); -DEFINE_IPL_ATTR_RW(dump_nvme, bootprog, "%lld\n", "%llx\n", - dump_block_nvme->nvme.bootprog); DEFINE_IPL_ATTR_RW(dump_nvme, br_lba, "%lld\n", "%llx\n", dump_block_nvme->nvme.br_lba); +DEFINE_IPL_ATTR_BOOTPROG_RW(dump_nvme, bootprog, "%lld\n", "%llx\n", + dump_block_nvme->hdr, + dump_block_nvme->nvme.bootprog); DEFINE_IPL_ATTR_SCP_DATA_RW(dump_nvme, dump_block_nvme->hdr, dump_block_nvme->nvme, @@ -1635,8 +1658,9 @@ static const struct attribute_group dump_nvme_attr_group = { /* ECKD dump device attributes */ DEFINE_IPL_CCW_ATTR_RW(dump_eckd, device, dump_block_eckd->eckd); -DEFINE_IPL_ATTR_RW(dump_eckd, bootprog, "%lld\n", "%llx\n", - dump_block_eckd->eckd.bootprog); +DEFINE_IPL_ATTR_BOOTPROG_RW(dump_eckd, bootprog, "%lld\n", "%llx\n", + dump_block_eckd->hdr, + dump_block_eckd->eckd.bootprog); IPL_ATTR_BR_CHR_SHOW_FN(dump, dump_block_eckd->eckd); IPL_ATTR_BR_CHR_STORE_FN(dump, dump_block_eckd->eckd); From efdcbc1b10354661178305a07c6a9f8872ff31a9 Mon Sep 17 00:00:00 2001 From: Wentao Guan Date: Thu, 4 Dec 2025 18:13:04 +0800 Subject: [PATCH 1688/3902] gpio: regmap: Fix memleak in error path in gpio_regmap_register() commit 52721cfc78c76b09c66e092b52617006390ae96a upstream. Call gpiochip_remove() to free the resources allocated by gpiochip_add_data() in error path. Fixes: 553b75d4bfe9 ("gpio: regmap: Allow to allocate regmap-irq device") Fixes: ae495810cffe ("gpio: regmap: add the .fixed_direction_output configuration parameter") CC: stable@vger.kernel.org Co-developed-by: WangYuli Signed-off-by: WangYuli Signed-off-by: Wentao Guan Reviewed-by: Andy Shevchenko Link: https://lore.kernel.org/r/20251204101303.30353-1-guanwentao@uniontech.com [Bartosz: reworked the commit message] Signed-off-by: Bartosz Golaszewski Signed-off-by: Greg Kroah-Hartman --- drivers/gpio/gpio-regmap.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/gpio/gpio-regmap.c b/drivers/gpio/gpio-regmap.c index f4267af00027e2..c64805dcb9f885 100644 --- a/drivers/gpio/gpio-regmap.c +++ b/drivers/gpio/gpio-regmap.c @@ -328,7 +328,7 @@ struct gpio_regmap *gpio_regmap_register(const struct gpio_regmap_config *config config->regmap_irq_line, config->regmap_irq_flags, 0, config->regmap_irq_chip, &gpio->irq_chip_data); if (ret) - goto err_free_bitmap; + goto err_remove_gpiochip; irq_domain = regmap_irq_get_domain(gpio->irq_chip_data); } else From 5f8dc9f0327e8cc3ca508bf99223b88f4773b669 Mon Sep 17 00:00:00 2001 From: Askar Safin Date: Sat, 6 Dec 2025 18:04:13 +0000 Subject: [PATCH 1689/3902] gpiolib: acpi: Add quirk for Dell Precision 7780 commit 2d967310c49ed93ac11cef408a55ddf15c3dd52e upstream. Dell Precision 7780 often wakes up on its own from suspend. Sometimes wake up happens immediately (i. e. within 7 seconds), sometimes it happens after, say, 30 minutes. Fixes: 1796f808e4bb ("HID: i2c-hid: acpi: Stop setting wakeup_capable") Link: https://lore.kernel.org/linux-i2c/197ae95ffd8.dc819e60457077.7692120488609091556@zohomail.com/ Cc: stable@vger.kernel.org Reviewed-by: Andy Shevchenko Signed-off-by: Askar Safin Link: https://lore.kernel.org/r/20251206180414.3183334-2-safinaskar@gmail.com Signed-off-by: Bartosz Golaszewski Signed-off-by: Greg Kroah-Hartman --- drivers/gpio/gpiolib-acpi-quirks.c | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/drivers/gpio/gpiolib-acpi-quirks.c b/drivers/gpio/gpiolib-acpi-quirks.c index 7b95d1b0336149..a0116f004975ae 100644 --- a/drivers/gpio/gpiolib-acpi-quirks.c +++ b/drivers/gpio/gpiolib-acpi-quirks.c @@ -370,6 +370,28 @@ static const struct dmi_system_id gpiolib_acpi_quirks[] __initconst = { .ignore_wake = "ASCP1A00:00@8", }, }, + { + /* + * Spurious wakeups, likely from touchpad controller + * Dell Precision 7780 + * Found in BIOS 1.24.1 + * + * Found in touchpad firmware, installed by Dell Touchpad Firmware Update Utility version 1160.4196.9, A01 + * ( Dell-Touchpad-Firmware-Update-Utility_VYGNN_WIN64_1160.4196.9_A00.EXE ), + * released on 11 Jul 2024 + * + * https://lore.kernel.org/linux-i2c/197ae95ffd8.dc819e60457077.7692120488609091556@zohomail.com/ + */ + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."), + DMI_MATCH(DMI_PRODUCT_FAMILY, "Precision"), + DMI_MATCH(DMI_PRODUCT_NAME, "Precision 7780"), + DMI_MATCH(DMI_BOARD_NAME, "0C6JVW"), + }, + .driver_data = &(struct acpi_gpiolib_dmi_quirk) { + .ignore_wake = "VEN_0488:00@355", + }, + }, {} /* Terminating entry */ }; From acb3a68f2170e8c7b5c1461d999a333c21a5dba9 Mon Sep 17 00:00:00 2001 From: Xi Ruoyao Date: Fri, 28 Nov 2025 15:50:32 +0800 Subject: [PATCH 1690/3902] gpio: loongson: Switch 2K2000/3000 GPIO to BYTE_CTRL_MODE commit dae9750105cf93ac1e156ef91f4beeb53bd64777 upstream. The manuals of 2K2000 says both BIT_CTRL_MODE and BYTE_CTRL_MODE are supported but the latter is recommended. Also on 2K3000, per the ACPI DSDT the GPIO controller is compatible with 2K2000, but it fails to operate GPIOs 62 and 63 (and maybe others) using BIT_CTRL_MODE. Using BYTE_CTRL_MODE also makes those 2K3000 GPIOs work. Fixes: 3feb70a61740 ("gpio: loongson: add more gpio chip support") Cc: stable@vger.kernel.org Signed-off-by: Xi Ruoyao Reviewed-by: Huacai Chen Link: https://lore.kernel.org/r/20251128075033.255821-1-xry111@xry111.site Signed-off-by: Bartosz Golaszewski Signed-off-by: Greg Kroah-Hartman --- drivers/gpio/gpio-loongson-64bit.c | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/drivers/gpio/gpio-loongson-64bit.c b/drivers/gpio/gpio-loongson-64bit.c index 02f181cb219e99..82d4c3aa4d2fc7 100644 --- a/drivers/gpio/gpio-loongson-64bit.c +++ b/drivers/gpio/gpio-loongson-64bit.c @@ -407,11 +407,11 @@ static const struct loongson_gpio_chip_data loongson_gpio_ls2k2000_data0 = { static const struct loongson_gpio_chip_data loongson_gpio_ls2k2000_data1 = { .label = "ls2k2000_gpio", - .mode = BIT_CTRL_MODE, - .conf_offset = 0x0, - .in_offset = 0x20, - .out_offset = 0x10, - .inten_offset = 0x30, + .mode = BYTE_CTRL_MODE, + .conf_offset = 0x800, + .in_offset = 0xa00, + .out_offset = 0x900, + .inten_offset = 0xb00, }; static const struct loongson_gpio_chip_data loongson_gpio_ls2k2000_data2 = { From 9837f8d57a54984e7516c7faeac4049f599f3942 Mon Sep 17 00:00:00 2001 From: Mario Limonciello Date: Tue, 9 Dec 2025 11:14:47 -0600 Subject: [PATCH 1691/3902] Revert "drm/amd/display: Fix pbn to kbps Conversion" commit 72e24456a54fe04710d89626cc5a88703e2f6202 upstream. Deeply daisy chained DP/MST displays are no longer able to light up. This reverts commit e0dec00f3d05 ("drm/amd/display: Fix pbn to kbps Conversion") Cc: Jerry Zuo Reported-by: nat@nullable.se Closes: https://gitlab.freedesktop.org/drm/amd/-/issues/4756 Signed-off-by: Mario Limonciello Acked-by: Alex Deucher Signed-off-by: Alex Deucher (cherry picked from commit e1c94109c76e8a77a21531bd53f6c63356c81158) Cc: stable@vger.kernel.org # 6.17+ Signed-off-by: Greg Kroah-Hartman --- .../display/amdgpu_dm/amdgpu_dm_mst_types.c | 59 +++++++++++-------- 1 file changed, 36 insertions(+), 23 deletions(-) diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_mst_types.c b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_mst_types.c index dbd1da4d85d323..5e92eaa67aa33d 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_mst_types.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_mst_types.c @@ -884,28 +884,26 @@ struct dsc_mst_fairness_params { }; #if defined(CONFIG_DRM_AMD_DC_FP) -static uint64_t kbps_to_pbn(int kbps, bool is_peak_pbn) +static uint16_t get_fec_overhead_multiplier(struct dc_link *dc_link) { - uint64_t effective_kbps = (uint64_t)kbps; + u8 link_coding_cap; + uint16_t fec_overhead_multiplier_x1000 = PBN_FEC_OVERHEAD_MULTIPLIER_8B_10B; - if (is_peak_pbn) { // add 0.6% (1006/1000) overhead into effective kbps - effective_kbps *= 1006; - effective_kbps = div_u64(effective_kbps, 1000); - } + link_coding_cap = dc_link_dp_mst_decide_link_encoding_format(dc_link); + if (link_coding_cap == DP_128b_132b_ENCODING) + fec_overhead_multiplier_x1000 = PBN_FEC_OVERHEAD_MULTIPLIER_128B_132B; - return (uint64_t) DIV64_U64_ROUND_UP(effective_kbps * 64, (54 * 8 * 1000)); + return fec_overhead_multiplier_x1000; } -static uint32_t pbn_to_kbps(unsigned int pbn, bool with_margin) +static int kbps_to_peak_pbn(int kbps, uint16_t fec_overhead_multiplier_x1000) { - uint64_t pbn_effective = (uint64_t)pbn; - - if (with_margin) // deduct 0.6% (994/1000) overhead from effective pbn - pbn_effective *= (1000000 / PEAK_FACTOR_X1000); - else - pbn_effective *= 1000; + u64 peak_kbps = kbps; - return DIV_U64_ROUND_UP(pbn_effective * 8 * 54, 64); + peak_kbps *= 1006; + peak_kbps *= fec_overhead_multiplier_x1000; + peak_kbps = div_u64(peak_kbps, 1000 * 1000); + return (int) DIV64_U64_ROUND_UP(peak_kbps * 64, (54 * 8 * 1000)); } static void set_dsc_configs_from_fairness_vars(struct dsc_mst_fairness_params *params, @@ -976,7 +974,7 @@ static int bpp_x16_from_pbn(struct dsc_mst_fairness_params param, int pbn) dc_dsc_get_default_config_option(param.sink->ctx->dc, &dsc_options); dsc_options.max_target_bpp_limit_override_x16 = drm_connector->display_info.max_dsc_bpp * 16; - kbps = pbn_to_kbps(pbn, false); + kbps = div_u64((u64)pbn * 994 * 8 * 54, 64); dc_dsc_compute_config( param.sink->ctx->dc->res_pool->dscs[0], ¶m.sink->dsc_caps.dsc_dec_caps, @@ -1005,11 +1003,12 @@ static int increase_dsc_bpp(struct drm_atomic_state *state, int link_timeslots_used; int fair_pbn_alloc; int ret = 0; + uint16_t fec_overhead_multiplier_x1000 = get_fec_overhead_multiplier(dc_link); for (i = 0; i < count; i++) { if (vars[i + k].dsc_enabled) { initial_slack[i] = - kbps_to_pbn(params[i].bw_range.max_kbps, false) - vars[i + k].pbn; + kbps_to_peak_pbn(params[i].bw_range.max_kbps, fec_overhead_multiplier_x1000) - vars[i + k].pbn; bpp_increased[i] = false; remaining_to_increase += 1; } else { @@ -1105,6 +1104,7 @@ static int try_disable_dsc(struct drm_atomic_state *state, int next_index; int remaining_to_try = 0; int ret; + uint16_t fec_overhead_multiplier_x1000 = get_fec_overhead_multiplier(dc_link); int var_pbn; for (i = 0; i < count; i++) { @@ -1137,7 +1137,7 @@ static int try_disable_dsc(struct drm_atomic_state *state, DRM_DEBUG_DRIVER("MST_DSC index #%d, try no compression\n", next_index); var_pbn = vars[next_index].pbn; - vars[next_index].pbn = kbps_to_pbn(params[next_index].bw_range.stream_kbps, true); + vars[next_index].pbn = kbps_to_peak_pbn(params[next_index].bw_range.stream_kbps, fec_overhead_multiplier_x1000); ret = drm_dp_atomic_find_time_slots(state, params[next_index].port->mgr, params[next_index].port, @@ -1197,6 +1197,7 @@ static int compute_mst_dsc_configs_for_link(struct drm_atomic_state *state, int count = 0; int i, k, ret; bool debugfs_overwrite = false; + uint16_t fec_overhead_multiplier_x1000 = get_fec_overhead_multiplier(dc_link); struct drm_connector_state *new_conn_state; memset(params, 0, sizeof(params)); @@ -1277,7 +1278,7 @@ static int compute_mst_dsc_configs_for_link(struct drm_atomic_state *state, DRM_DEBUG_DRIVER("MST_DSC Try no compression\n"); for (i = 0; i < count; i++) { vars[i + k].aconnector = params[i].aconnector; - vars[i + k].pbn = kbps_to_pbn(params[i].bw_range.stream_kbps, false); + vars[i + k].pbn = kbps_to_peak_pbn(params[i].bw_range.stream_kbps, fec_overhead_multiplier_x1000); vars[i + k].dsc_enabled = false; vars[i + k].bpp_x16 = 0; ret = drm_dp_atomic_find_time_slots(state, params[i].port->mgr, params[i].port, @@ -1299,7 +1300,7 @@ static int compute_mst_dsc_configs_for_link(struct drm_atomic_state *state, DRM_DEBUG_DRIVER("MST_DSC Try max compression\n"); for (i = 0; i < count; i++) { if (params[i].compression_possible && params[i].clock_force_enable != DSC_CLK_FORCE_DISABLE) { - vars[i + k].pbn = kbps_to_pbn(params[i].bw_range.min_kbps, false); + vars[i + k].pbn = kbps_to_peak_pbn(params[i].bw_range.min_kbps, fec_overhead_multiplier_x1000); vars[i + k].dsc_enabled = true; vars[i + k].bpp_x16 = params[i].bw_range.min_target_bpp_x16; ret = drm_dp_atomic_find_time_slots(state, params[i].port->mgr, @@ -1307,7 +1308,7 @@ static int compute_mst_dsc_configs_for_link(struct drm_atomic_state *state, if (ret < 0) return ret; } else { - vars[i + k].pbn = kbps_to_pbn(params[i].bw_range.stream_kbps, false); + vars[i + k].pbn = kbps_to_peak_pbn(params[i].bw_range.stream_kbps, fec_overhead_multiplier_x1000); vars[i + k].dsc_enabled = false; vars[i + k].bpp_x16 = 0; ret = drm_dp_atomic_find_time_slots(state, params[i].port->mgr, @@ -1762,6 +1763,18 @@ int pre_validate_dsc(struct drm_atomic_state *state, return ret; } +static uint32_t kbps_from_pbn(unsigned int pbn) +{ + uint64_t kbps = (uint64_t)pbn; + + kbps *= (1000000 / PEAK_FACTOR_X1000); + kbps *= 8; + kbps *= 54; + kbps /= 64; + + return (uint32_t)kbps; +} + static bool is_dsc_common_config_possible(struct dc_stream_state *stream, struct dc_dsc_bw_range *bw_range) { @@ -1860,7 +1873,7 @@ enum dc_status dm_dp_mst_is_port_support_mode( dc_link_get_highest_encoding_format(stream->link)); cur_link_settings = stream->link->verified_link_cap; root_link_bw_in_kbps = dc_link_bandwidth_kbps(aconnector->dc_link, &cur_link_settings); - virtual_channel_bw_in_kbps = pbn_to_kbps(aconnector->mst_output_port->full_pbn, true); + virtual_channel_bw_in_kbps = kbps_from_pbn(aconnector->mst_output_port->full_pbn); /* pick the end to end bw bottleneck */ end_to_end_bw_in_kbps = min(root_link_bw_in_kbps, virtual_channel_bw_in_kbps); @@ -1913,7 +1926,7 @@ enum dc_status dm_dp_mst_is_port_support_mode( immediate_upstream_port = aconnector->mst_output_port->parent->port_parent; if (immediate_upstream_port) { - virtual_channel_bw_in_kbps = pbn_to_kbps(immediate_upstream_port->full_pbn, true); + virtual_channel_bw_in_kbps = kbps_from_pbn(immediate_upstream_port->full_pbn); virtual_channel_bw_in_kbps = min(root_link_bw_in_kbps, virtual_channel_bw_in_kbps); } else { /* For topology LCT 1 case - only one mstb*/ From ede98c3943cd6141b6657e23b03a5ca838d3e8e4 Mon Sep 17 00:00:00 2001 From: Alex Deucher Date: Tue, 11 Nov 2025 11:17:22 -0500 Subject: [PATCH 1692/3902] drm/amd/display: Use GFP_ATOMIC in dc_create_plane_state() commit 3c41114dcdabb7b25f5bc33273c6db9c7af7f4a7 upstream. This can get called from an atomic context. Closes: https://gitlab.freedesktop.org/drm/amd/-/issues/4470 Reviewed-by: Harry Wentland Signed-off-by: Alex Deucher (cherry picked from commit 8acdad9344cc7b4e7bc01f0dfea80093eb3768db) Cc: stable@vger.kernel.org Signed-off-by: Greg Kroah-Hartman --- drivers/gpu/drm/amd/display/dc/core/dc_surface.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/gpu/drm/amd/display/dc/core/dc_surface.c b/drivers/gpu/drm/amd/display/dc/core/dc_surface.c index 922f23557f5d9e..0971dfa258454e 100644 --- a/drivers/gpu/drm/amd/display/dc/core/dc_surface.c +++ b/drivers/gpu/drm/amd/display/dc/core/dc_surface.c @@ -86,7 +86,7 @@ uint8_t dc_plane_get_pipe_mask(struct dc_state *dc_state, const struct dc_plane struct dc_plane_state *dc_create_plane_state(const struct dc *dc) { struct dc_plane_state *plane_state = kvzalloc(sizeof(*plane_state), - GFP_KERNEL); + GFP_ATOMIC); if (NULL == plane_state) return NULL; From 7d5df9dd6343964e831847a0c18ea55a393bb2e3 Mon Sep 17 00:00:00 2001 From: Ray Wu Date: Fri, 28 Nov 2025 08:58:13 +0800 Subject: [PATCH 1693/3902] drm/amd/display: Fix scratch registers offsets for DCN35 commit 69741d9ccc7222e6b6f138db67b012ecc0d72542 upstream. [Why] Different platforms use differnet NBIO header files, causing display code to use differnt offset and read wrong accelerated status. [How] - Unified NBIO offset header file across platform. - Correct scratch registers offsets to proper locations. Closes: https://gitlab.freedesktop.org/drm/amd/-/issues/4667 Cc: Mario Limonciello Cc: Alex Deucher Reviewed-by: Mario Limonciello Signed-off-by: Ray Wu Signed-off-by: Chenyu Chen Tested-by: Daniel Wheeler Signed-off-by: Alex Deucher (cherry picked from commit 49a63bc8eda0304ba307f5ba68305f936174f72d) Cc: stable@vger.kernel.org Signed-off-by: Greg Kroah-Hartman --- .../drm/amd/display/dc/resource/dcn35/dcn35_resource.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/drivers/gpu/drm/amd/display/dc/resource/dcn35/dcn35_resource.c b/drivers/gpu/drm/amd/display/dc/resource/dcn35/dcn35_resource.c index fff57f23f4f7ab..06bec7dcc7556b 100644 --- a/drivers/gpu/drm/amd/display/dc/resource/dcn35/dcn35_resource.c +++ b/drivers/gpu/drm/amd/display/dc/resource/dcn35/dcn35_resource.c @@ -203,12 +203,12 @@ enum dcn35_clk_src_array_id { NBIO_BASE_INNER(seg) #define NBIO_SR(reg_name)\ - REG_STRUCT.reg_name = NBIO_BASE(regBIF_BX2_ ## reg_name ## _BASE_IDX) + \ - regBIF_BX2_ ## reg_name + REG_STRUCT.reg_name = NBIO_BASE(regBIF_BX1_ ## reg_name ## _BASE_IDX) + \ + regBIF_BX1_ ## reg_name #define NBIO_SR_ARR(reg_name, id)\ - REG_STRUCT[id].reg_name = NBIO_BASE(regBIF_BX2_ ## reg_name ## _BASE_IDX) + \ - regBIF_BX2_ ## reg_name + REG_STRUCT[id].reg_name = NBIO_BASE(regBIF_BX1_ ## reg_name ## _BASE_IDX) + \ + regBIF_BX1_ ## reg_name #define bios_regs_init() \ ( \ From f9e805fa1fe8443af0d64a31e039554f3f0cbe5c Mon Sep 17 00:00:00 2001 From: Ray Wu Date: Fri, 28 Nov 2025 09:14:09 +0800 Subject: [PATCH 1694/3902] drm/amd/display: Fix scratch registers offsets for DCN351 commit fd62aa13d3ee0f21c756a40a7c2f900f98992d6a upstream. [Why] Different platforms use different NBIO header files, causing display code to use differnt offset and read wrong accelerated status. [How] - Unified NBIO offset header file across platform. - Correct scratch registers offsets to proper locations. Closes: https://gitlab.freedesktop.org/drm/amd/-/issues/4667 Cc: Mario Limonciello Cc: Alex Deucher Reviewed-by: Mario Limonciello Signed-off-by: Ray Wu Signed-off-by: Chenyu Chen Tested-by: Daniel Wheeler Signed-off-by: Alex Deucher (cherry picked from commit 576e032e909c8a6bb3d907b4ef5f6abe0f644199) Cc: stable@vger.kernel.org Signed-off-by: Greg Kroah-Hartman --- .../drm/amd/display/dc/resource/dcn351/dcn351_resource.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/drivers/gpu/drm/amd/display/dc/resource/dcn351/dcn351_resource.c b/drivers/gpu/drm/amd/display/dc/resource/dcn351/dcn351_resource.c index 0abd163b425e53..7974e306126e06 100644 --- a/drivers/gpu/drm/amd/display/dc/resource/dcn351/dcn351_resource.c +++ b/drivers/gpu/drm/amd/display/dc/resource/dcn351/dcn351_resource.c @@ -183,12 +183,12 @@ enum dcn351_clk_src_array_id { NBIO_BASE_INNER(seg) #define NBIO_SR(reg_name)\ - REG_STRUCT.reg_name = NBIO_BASE(regBIF_BX2_ ## reg_name ## _BASE_IDX) + \ - regBIF_BX2_ ## reg_name + REG_STRUCT.reg_name = NBIO_BASE(regBIF_BX1_ ## reg_name ## _BASE_IDX) + \ + regBIF_BX1_ ## reg_name #define NBIO_SR_ARR(reg_name, id)\ - REG_STRUCT[id].reg_name = NBIO_BASE(regBIF_BX2_ ## reg_name ## _BASE_IDX) + \ - regBIF_BX2_ ## reg_name + REG_STRUCT[id].reg_name = NBIO_BASE(regBIF_BX1_ ## reg_name ## _BASE_IDX) + \ + regBIF_BX1_ ## reg_name #define bios_regs_init() \ ( \ From 149b97dc329533b20172cb7bc3f40c073d414801 Mon Sep 17 00:00:00 2001 From: Jani Nikula Date: Tue, 28 Oct 2025 22:07:25 +0200 Subject: [PATCH 1695/3902] drm/displayid: pass iter to drm_find_displayid_extension() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit commit 520f37c30992fd0c212a34fbe99c062b7a3dc52e upstream. It's more convenient to pass iter than a handful of its members to drm_find_displayid_extension(), especially as we're about to add another member. Rename the function find_next_displayid_extension() while at it, to be more descriptive. Cc: Tiago Martins Araújo Acked-by: Alex Deucher Tested-by: Tiago Martins Araújo Cc: stable@vger.kernel.org Link: https://patch.msgid.link/3837ae7f095e77a082ac2422ce2fac96c4f9373d.1761681968.git.jani.nikula@intel.com Signed-off-by: Jani Nikula Signed-off-by: Greg Kroah-Hartman --- drivers/gpu/drm/drm_displayid.c | 19 +++++++------------ 1 file changed, 7 insertions(+), 12 deletions(-) diff --git a/drivers/gpu/drm/drm_displayid.c b/drivers/gpu/drm/drm_displayid.c index b4fd43783c5096..20b453d2b85478 100644 --- a/drivers/gpu/drm/drm_displayid.c +++ b/drivers/gpu/drm/drm_displayid.c @@ -48,26 +48,24 @@ validate_displayid(const u8 *displayid, int length, int idx) return base; } -static const u8 *drm_find_displayid_extension(const struct drm_edid *drm_edid, - int *length, int *idx, - int *ext_index) +static const u8 *find_next_displayid_extension(struct displayid_iter *iter) { const struct displayid_header *base; const u8 *displayid; - displayid = drm_edid_find_extension(drm_edid, DISPLAYID_EXT, ext_index); + displayid = drm_edid_find_extension(iter->drm_edid, DISPLAYID_EXT, &iter->ext_index); if (!displayid) return NULL; /* EDID extensions block checksum isn't for us */ - *length = EDID_LENGTH - 1; - *idx = 1; + iter->length = EDID_LENGTH - 1; + iter->idx = 1; - base = validate_displayid(displayid, *length, *idx); + base = validate_displayid(displayid, iter->length, iter->idx); if (IS_ERR(base)) return NULL; - *length = *idx + sizeof(*base) + base->bytes; + iter->length = iter->idx + sizeof(*base) + base->bytes; return displayid; } @@ -126,10 +124,7 @@ __displayid_iter_next(struct displayid_iter *iter) /* The first section we encounter is the base section */ bool base_section = !iter->section; - iter->section = drm_find_displayid_extension(iter->drm_edid, - &iter->length, - &iter->idx, - &iter->ext_index); + iter->section = find_next_displayid_extension(iter); if (!iter->section) { iter->drm_edid = NULL; return NULL; From 37f52c4554f3e97fdc096b21c5a3cd67fe6575ee Mon Sep 17 00:00:00 2001 From: Krzysztof Kozlowski Date: Thu, 30 Oct 2025 09:50:45 +0100 Subject: [PATCH 1696/3902] dt-bindings: PCI: qcom,pcie-sc7280: Add missing required power-domains and resets commit ef99c2efeacac7758cc8c2d00e3200100a4da16c upstream. Commit 756485bfbb85 ("dt-bindings: PCI: qcom,pcie-sc7280: Move SC7280 to dedicated schema") move the device schema to separate file, but it missed a "if:not:...then:" clause in the original binding which was requiring power-domains and resets for this particular chip. Fixes: 756485bfbb85 ("dt-bindings: PCI: qcom,pcie-sc7280: Move SC7280 to dedicated schema") Signed-off-by: Krzysztof Kozlowski Signed-off-by: Manivannan Sadhasivam Reviewed-by: Rob Herring (Arm) Cc: stable@vger.kernel.org Link: https://patch.msgid.link/20251030-dt-bindings-pci-qcom-fixes-power-domains-v2-2-28c1f11599fe@linaro.org Signed-off-by: Greg Kroah-Hartman --- Documentation/devicetree/bindings/pci/qcom,pcie-sc7280.yaml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/Documentation/devicetree/bindings/pci/qcom,pcie-sc7280.yaml b/Documentation/devicetree/bindings/pci/qcom,pcie-sc7280.yaml index 4d0a915566030f..f760807b5feb6f 100644 --- a/Documentation/devicetree/bindings/pci/qcom,pcie-sc7280.yaml +++ b/Documentation/devicetree/bindings/pci/qcom,pcie-sc7280.yaml @@ -76,6 +76,11 @@ properties: items: - const: pci +required: + - power-domains + - resets + - reset-names + allOf: - $ref: qcom,pcie-common.yaml# From c3823d1243d3c6c5e4cf5813622d553a9aa60cb7 Mon Sep 17 00:00:00 2001 From: Krzysztof Kozlowski Date: Thu, 30 Oct 2025 09:50:46 +0100 Subject: [PATCH 1697/3902] dt-bindings: PCI: qcom,pcie-sc8280xp: Add missing required power-domains and resets commit ea551601404d286813aef6819ddf0bf1d7d69a24 upstream. Commit c007a5505504 ("dt-bindings: PCI: qcom,pcie-sc8280xp: Move SC8280XP to dedicated schema") move the device schema to separate file, but it missed a "if:not:...then:" clause in the original binding which was requiring power-domains and resets for this particular chip. Fixes: c007a5505504 ("dt-bindings: PCI: qcom,pcie-sc8280xp: Move SC8280XP to dedicated schema") Signed-off-by: Krzysztof Kozlowski Signed-off-by: Manivannan Sadhasivam Reviewed-by: Rob Herring (Arm) Cc: stable@vger.kernel.org Link: https://patch.msgid.link/20251030-dt-bindings-pci-qcom-fixes-power-domains-v2-3-28c1f11599fe@linaro.org Signed-off-by: Greg Kroah-Hartman --- Documentation/devicetree/bindings/pci/qcom,pcie-sc8280xp.yaml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Documentation/devicetree/bindings/pci/qcom,pcie-sc8280xp.yaml b/Documentation/devicetree/bindings/pci/qcom,pcie-sc8280xp.yaml index 15ba2385eb73c4..b6fb0e37cec59b 100644 --- a/Documentation/devicetree/bindings/pci/qcom,pcie-sc8280xp.yaml +++ b/Documentation/devicetree/bindings/pci/qcom,pcie-sc8280xp.yaml @@ -61,6 +61,9 @@ properties: required: - interconnects - interconnect-names + - power-domains + - resets + - reset-names allOf: - $ref: qcom,pcie-common.yaml# From f3510ba5e2652d98209ce00233df1d14e7e7e3b5 Mon Sep 17 00:00:00 2001 From: Krzysztof Kozlowski Date: Thu, 30 Oct 2025 09:50:47 +0100 Subject: [PATCH 1698/3902] dt-bindings: PCI: qcom,pcie-sm8150: Add missing required power-domains and resets commit 31cb432b62fb796e0c1084542ba39311d2f716d5 upstream. Commit 51bc04d5b49d ("dt-bindings: PCI: qcom,pcie-sm8150: Move SM8150 to dedicated schema") move the device schema to separate file, but it missed a "if:not:...then:" clause in the original binding which was requiring power-domains and resets for this particular chip. Fixes: 51bc04d5b49d ("dt-bindings: PCI: qcom,pcie-sm8150: Move SM8150 to dedicated schema") Signed-off-by: Krzysztof Kozlowski Signed-off-by: Manivannan Sadhasivam Reviewed-by: Rob Herring (Arm) Cc: stable@vger.kernel.org Link: https://patch.msgid.link/20251030-dt-bindings-pci-qcom-fixes-power-domains-v2-4-28c1f11599fe@linaro.org Signed-off-by: Greg Kroah-Hartman --- Documentation/devicetree/bindings/pci/qcom,pcie-sm8150.yaml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/Documentation/devicetree/bindings/pci/qcom,pcie-sm8150.yaml b/Documentation/devicetree/bindings/pci/qcom,pcie-sm8150.yaml index 26b247a41785fa..17c0d58af37c6c 100644 --- a/Documentation/devicetree/bindings/pci/qcom,pcie-sm8150.yaml +++ b/Documentation/devicetree/bindings/pci/qcom,pcie-sm8150.yaml @@ -74,6 +74,11 @@ properties: items: - const: pci +required: + - power-domains + - resets + - reset-names + allOf: - $ref: qcom,pcie-common.yaml# From 4eee447b5bd43871097cc8c2727dd0896459da75 Mon Sep 17 00:00:00 2001 From: Krzysztof Kozlowski Date: Thu, 30 Oct 2025 09:50:48 +0100 Subject: [PATCH 1699/3902] dt-bindings: PCI: qcom,pcie-sm8250: Add missing required power-domains and resets commit 2620c6bcd8c141b79ff2afe95dc814dfab644f63 upstream. Commit 4891b66185c1 ("dt-bindings: PCI: qcom,pcie-sm8250: Move SM8250 to dedicated schema") move the device schema to separate file, but it missed a "if:not:...then:" clause in the original binding which was requiring power-domains and resets for this particular chip. Fixes: 4891b66185c1 ("dt-bindings: PCI: qcom,pcie-sm8250: Move SM8250 to dedicated schema") Signed-off-by: Krzysztof Kozlowski Signed-off-by: Manivannan Sadhasivam Reviewed-by: Rob Herring (Arm) Cc: stable@vger.kernel.org Link: https://patch.msgid.link/20251030-dt-bindings-pci-qcom-fixes-power-domains-v2-5-28c1f11599fe@linaro.org Signed-off-by: Greg Kroah-Hartman --- Documentation/devicetree/bindings/pci/qcom,pcie-sm8250.yaml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/Documentation/devicetree/bindings/pci/qcom,pcie-sm8250.yaml b/Documentation/devicetree/bindings/pci/qcom,pcie-sm8250.yaml index af4dae68d50873..591f1a1eddde1c 100644 --- a/Documentation/devicetree/bindings/pci/qcom,pcie-sm8250.yaml +++ b/Documentation/devicetree/bindings/pci/qcom,pcie-sm8250.yaml @@ -83,6 +83,11 @@ properties: items: - const: pci +required: + - power-domains + - resets + - reset-names + allOf: - $ref: qcom,pcie-common.yaml# From d06674a1ca79cc5e8d273bf9f772252f9046e598 Mon Sep 17 00:00:00 2001 From: Krzysztof Kozlowski Date: Thu, 30 Oct 2025 09:50:49 +0100 Subject: [PATCH 1700/3902] dt-bindings: PCI: qcom,pcie-sm8350: Add missing required power-domains and resets commit 012ba0d5f02e1f192eda263b5f9f826e47d607bb upstream. Commit 2278b8b54773 ("dt-bindings: PCI: qcom,pcie-sm8350: Move SM8350 to dedicated schema") move the device schema to separate file, but it missed a "if:not:...then:" clause in the original binding which was requiring power-domains and resets for this particular chip. Fixes: 2278b8b54773 ("dt-bindings: PCI: qcom,pcie-sm8350: Move SM8350 to dedicated schema") Signed-off-by: Krzysztof Kozlowski Signed-off-by: Manivannan Sadhasivam Reviewed-by: Rob Herring (Arm) Cc: stable@vger.kernel.org Link: https://patch.msgid.link/20251030-dt-bindings-pci-qcom-fixes-power-domains-v2-6-28c1f11599fe@linaro.org Signed-off-by: Greg Kroah-Hartman --- Documentation/devicetree/bindings/pci/qcom,pcie-sm8350.yaml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/Documentation/devicetree/bindings/pci/qcom,pcie-sm8350.yaml b/Documentation/devicetree/bindings/pci/qcom,pcie-sm8350.yaml index dde3079adbb331..299473af51d19a 100644 --- a/Documentation/devicetree/bindings/pci/qcom,pcie-sm8350.yaml +++ b/Documentation/devicetree/bindings/pci/qcom,pcie-sm8350.yaml @@ -73,6 +73,11 @@ properties: items: - const: pci +required: + - power-domains + - resets + - reset-names + allOf: - $ref: qcom,pcie-common.yaml# From 1bff3e35ed44d36462dc5fab595a85391e6513d0 Mon Sep 17 00:00:00 2001 From: Krzysztof Kozlowski Date: Thu, 30 Oct 2025 09:50:50 +0100 Subject: [PATCH 1701/3902] dt-bindings: PCI: qcom,pcie-sm8450: Add missing required power-domains and resets commit 667facc4000c49a7c280097ef6638f133bcb1e59 upstream. Commit 88c9b3af4e31 ("dt-bindings: PCI: qcom,pcie-sm8450: Move SM8450 to dedicated schema") move the device schema to separate file, but it missed a "if:not:...then:" clause in the original binding which was requiring power-domains and resets for this particular chip. Fixes: 88c9b3af4e31 ("dt-bindings: PCI: qcom,pcie-sm8450: Move SM8450 to dedicated schema") Signed-off-by: Krzysztof Kozlowski Signed-off-by: Manivannan Sadhasivam Reviewed-by: Rob Herring (Arm) Cc: stable@vger.kernel.org Link: https://patch.msgid.link/20251030-dt-bindings-pci-qcom-fixes-power-domains-v2-7-28c1f11599fe@linaro.org Signed-off-by: Greg Kroah-Hartman --- Documentation/devicetree/bindings/pci/qcom,pcie-sm8450.yaml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/Documentation/devicetree/bindings/pci/qcom,pcie-sm8450.yaml b/Documentation/devicetree/bindings/pci/qcom,pcie-sm8450.yaml index 6e0a6d8f0ed070..6a17d753122c64 100644 --- a/Documentation/devicetree/bindings/pci/qcom,pcie-sm8450.yaml +++ b/Documentation/devicetree/bindings/pci/qcom,pcie-sm8450.yaml @@ -77,6 +77,11 @@ properties: items: - const: pci +required: + - power-domains + - resets + - reset-names + allOf: - $ref: qcom,pcie-common.yaml# From c549b9e0b87730ea2ed2cc7cfc74f786adb8ae03 Mon Sep 17 00:00:00 2001 From: Krzysztof Kozlowski Date: Thu, 30 Oct 2025 09:50:51 +0100 Subject: [PATCH 1702/3902] dt-bindings: PCI: qcom,pcie-sm8550: Add missing required power-domains and resets commit e60c6f34b9f3a83f96006243c0ef96c134520257 upstream. Commit b8d3404058a6 ("dt-bindings: PCI: qcom,pcie-sm8550: Move SM8550 to dedicated schema") move the device schema to separate file, but it missed a "if:not:...then:" clause in the original binding which was requiring power-domains and resets for this particular chip. Fixes: b8d3404058a6 ("dt-bindings: PCI: qcom,pcie-sm8550: Move SM8550 to dedicated schema") Signed-off-by: Krzysztof Kozlowski Signed-off-by: Manivannan Sadhasivam Reviewed-by: Rob Herring (Arm) Cc: stable@vger.kernel.org Link: https://patch.msgid.link/20251030-dt-bindings-pci-qcom-fixes-power-domains-v2-8-28c1f11599fe@linaro.org Signed-off-by: Greg Kroah-Hartman --- Documentation/devicetree/bindings/pci/qcom,pcie-sm8550.yaml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/Documentation/devicetree/bindings/pci/qcom,pcie-sm8550.yaml b/Documentation/devicetree/bindings/pci/qcom,pcie-sm8550.yaml index 38b561e23c1fda..29d7df7aca60b0 100644 --- a/Documentation/devicetree/bindings/pci/qcom,pcie-sm8550.yaml +++ b/Documentation/devicetree/bindings/pci/qcom,pcie-sm8550.yaml @@ -83,6 +83,11 @@ properties: - const: pci # PCIe core reset - const: link_down # PCIe link down reset +required: + - power-domains + - resets + - reset-names + allOf: - $ref: qcom,pcie-common.yaml# From 51a5ab36084f3251ef87eda3e6a6236f6488925e Mon Sep 17 00:00:00 2001 From: Shivani Agarwal Date: Tue, 23 Sep 2025 23:01:48 -0700 Subject: [PATCH 1703/3902] crypto: af_alg - zero initialize memory allocated via sock_kmalloc commit 6f6e309328d53a10c0fe1f77dec2db73373179b6 upstream. Several crypto user API contexts and requests allocated with sock_kmalloc() were left uninitialized, relying on callers to set fields explicitly. This resulted in the use of uninitialized data in certain error paths or when new fields are added in the future. The ACVP patches also contain two user-space interface files: algif_kpp.c and algif_akcipher.c. These too rely on proper initialization of their context structures. A particular issue has been observed with the newly added 'inflight' variable introduced in af_alg_ctx by commit: 67b164a871af ("crypto: af_alg - Disallow multiple in-flight AIO requests") Because the context is not memset to zero after allocation, the inflight variable has contained garbage values. As a result, af_alg_alloc_areq() has incorrectly returned -EBUSY randomly when the garbage value was interpreted as true: https://github.com/gregkh/linux/blame/master/crypto/af_alg.c#L1209 The check directly tests ctx->inflight without explicitly comparing against true/false. Since inflight is only ever set to true or false later, an uninitialized value has triggered -EBUSY failures. Zero-initializing memory allocated with sock_kmalloc() ensures inflight and other fields start in a known state, removing random issues caused by uninitialized data. Fixes: fe869cdb89c9 ("crypto: algif_hash - User-space interface for hash operations") Fixes: 5afdfd22e6ba ("crypto: algif_rng - add random number generator support") Fixes: 2d97591ef43d ("crypto: af_alg - consolidation of duplicate code") Fixes: 67b164a871af ("crypto: af_alg - Disallow multiple in-flight AIO requests") Cc: stable@vger.kernel.org Signed-off-by: Shivani Agarwal Signed-off-by: Herbert Xu Signed-off-by: Greg Kroah-Hartman --- crypto/af_alg.c | 5 ++--- crypto/algif_hash.c | 3 +-- crypto/algif_rng.c | 3 +-- 3 files changed, 4 insertions(+), 7 deletions(-) diff --git a/crypto/af_alg.c b/crypto/af_alg.c index ca6fdcc6c54aca..6c271e55f44d94 100644 --- a/crypto/af_alg.c +++ b/crypto/af_alg.c @@ -1212,15 +1212,14 @@ struct af_alg_async_req *af_alg_alloc_areq(struct sock *sk, if (unlikely(!areq)) return ERR_PTR(-ENOMEM); + memset(areq, 0, areqlen); + ctx->inflight = true; areq->areqlen = areqlen; areq->sk = sk; areq->first_rsgl.sgl.sgt.sgl = areq->first_rsgl.sgl.sgl; - areq->last_rsgl = NULL; INIT_LIST_HEAD(&areq->rsgl_list); - areq->tsgl = NULL; - areq->tsgl_entries = 0; return areq; } diff --git a/crypto/algif_hash.c b/crypto/algif_hash.c index e3f1a4852737b0..4d3dfc60a16a6d 100644 --- a/crypto/algif_hash.c +++ b/crypto/algif_hash.c @@ -416,9 +416,8 @@ static int hash_accept_parent_nokey(void *private, struct sock *sk) if (!ctx) return -ENOMEM; - ctx->result = NULL; + memset(ctx, 0, len); ctx->len = len; - ctx->more = false; crypto_init_wait(&ctx->wait); ask->private = ctx; diff --git a/crypto/algif_rng.c b/crypto/algif_rng.c index 10c41adac3b1f3..1a86e40c8372e5 100644 --- a/crypto/algif_rng.c +++ b/crypto/algif_rng.c @@ -248,9 +248,8 @@ static int rng_accept_parent(void *private, struct sock *sk) if (!ctx) return -ENOMEM; + memset(ctx, 0, len); ctx->len = len; - ctx->addtl = NULL; - ctx->addtl_len = 0; /* * No seeding done at that point -- if multiple accepts are From b674e3522b6748aa3b73156a51ac304a421af074 Mon Sep 17 00:00:00 2001 From: Guangshuo Li Date: Tue, 23 Sep 2025 20:44:18 +0800 Subject: [PATCH 1704/3902] crypto: caam - Add check for kcalloc() in test_len() commit 7cf6e0b69b0d90ab042163e5bbddda0dfcf8b6a7 upstream. As kcalloc() may fail, check its return value to avoid a NULL pointer dereference when passing the buffer to rng->read(). On allocation failure, log the error and return since test_len() returns void. Fixes: 2be0d806e25e ("crypto: caam - add a test for the RNG") Cc: stable@vger.kernel.org Signed-off-by: Guangshuo Li Signed-off-by: Herbert Xu Signed-off-by: Greg Kroah-Hartman --- drivers/crypto/caam/caamrng.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/drivers/crypto/caam/caamrng.c b/drivers/crypto/caam/caamrng.c index b3d14a7f4dd147..0eb43c8625164f 100644 --- a/drivers/crypto/caam/caamrng.c +++ b/drivers/crypto/caam/caamrng.c @@ -181,7 +181,9 @@ static inline void test_len(struct hwrng *rng, size_t len, bool wait) struct device *dev = ctx->ctrldev; buf = kcalloc(CAAM_RNG_MAX_FIFO_STORE_SIZE, sizeof(u8), GFP_KERNEL); - + if (!buf) { + return; + } while (len > 0) { read_len = rng->read(rng, buf, len, wait); From 70cf3d6fe71a6b72548a3f4b7ca25933dd3196f0 Mon Sep 17 00:00:00 2001 From: Eric Biggers Date: Tue, 9 Dec 2025 14:34:17 -0800 Subject: [PATCH 1705/3902] crypto: arm64/ghash - Fix incorrect output from ghash-neon commit f6a458746f905adb7d70e50e8b9383dc9e3fd75f upstream. Commit 9a7c987fb92b ("crypto: arm64/ghash - Use API partial block handling") made ghash_finup() pass the wrong buffer to ghash_do_simd_update(). As a result, ghash-neon now produces incorrect outputs when the message length isn't divisible by 16 bytes. Fix this. (I didn't notice this earlier because this code is reached only on CPUs that support NEON but not PMULL. I haven't yet found a way to get qemu-system-aarch64 to emulate that configuration.) Fixes: 9a7c987fb92b ("crypto: arm64/ghash - Use API partial block handling") Cc: stable@vger.kernel.org Reported-by: Diederik de Haas Closes: https://lore.kernel.org/linux-crypto/DETXT7QI62KE.F3CGH2VWX1SC@cknow-tech.com/ Tested-by: Diederik de Haas Acked-by: Herbert Xu Link: https://lore.kernel.org/r/20251209223417.112294-1-ebiggers@kernel.org Signed-off-by: Eric Biggers Signed-off-by: Greg Kroah-Hartman --- arch/arm64/crypto/ghash-ce-glue.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/arch/arm64/crypto/ghash-ce-glue.c b/arch/arm64/crypto/ghash-ce-glue.c index 4995b6e2233501..9625570dc35359 100644 --- a/arch/arm64/crypto/ghash-ce-glue.c +++ b/arch/arm64/crypto/ghash-ce-glue.c @@ -133,7 +133,7 @@ static int ghash_finup(struct shash_desc *desc, const u8 *src, u8 buf[GHASH_BLOCK_SIZE] = {}; memcpy(buf, src, len); - ghash_do_simd_update(1, ctx->digest, src, key, NULL, + ghash_do_simd_update(1, ctx->digest, buf, key, NULL, pmull_ghash_update_p8); memzero_explicit(buf, sizeof(buf)); } From 1d18fb5c71f63c761e426ce8e73e750c6a891ac8 Mon Sep 17 00:00:00 2001 From: Johan Hovold Date: Thu, 25 Sep 2025 17:00:07 +0200 Subject: [PATCH 1706/3902] amba: tegra-ahb: Fix device leak on SMMU enable commit 500e1368e46928f4b2259612dcabb6999afae2a6 upstream. Make sure to drop the reference taken to the AHB platform device when looking up its driver data while enabling the SMMU. Note that holding a reference to a device does not prevent its driver data from going away. Fixes: 89c788bab1f0 ("ARM: tegra: Add SMMU enabler in AHB") Cc: stable@vger.kernel.org # 3.5 Signed-off-by: Johan Hovold Signed-off-by: Thierry Reding Signed-off-by: Greg Kroah-Hartman --- drivers/amba/tegra-ahb.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/amba/tegra-ahb.c b/drivers/amba/tegra-ahb.c index c0e8b765522dc7..f23c3ed018101c 100644 --- a/drivers/amba/tegra-ahb.c +++ b/drivers/amba/tegra-ahb.c @@ -144,6 +144,7 @@ int tegra_ahb_enable_smmu(struct device_node *dn) if (!dev) return -EPROBE_DEFER; ahb = dev_get_drvdata(dev); + put_device(dev); val = gizmo_readl(ahb, AHB_ARBITRATION_XBAR_CTRL); val |= AHB_ARBITRATION_XBAR_CTRL_SMMU_INIT_DONE; gizmo_writel(ahb, val, AHB_ARBITRATION_XBAR_CTRL); From 35eb1ec73f5727e4b4244ee9c181919e9c69a889 Mon Sep 17 00:00:00 2001 From: Damien Le Moal Date: Sat, 15 Nov 2025 21:15:52 +0900 Subject: [PATCH 1707/3902] zloop: fail zone append operations that are targeting full zones commit cf28f6f923cb1dd2765b5c3d7697bb4dcf2096a0 upstream. zloop_rw() will fail any regular write operation that targets a full sequential zone. The check for this is indirect and achieved by checking the write pointer alignment of the write operation. But this check is ineffective for zone append operations since these are alwasy automatically directed at a zone write pointer. Prevent zone append operations from being executed in a full zone with an explicit check of the zone condition. Fixes: eb0570c7df23 ("block: new zoned loop block device driver") Cc: stable@vger.kernel.org Signed-off-by: Damien Le Moal Signed-off-by: Jens Axboe Signed-off-by: Greg Kroah-Hartman --- drivers/block/zloop.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/drivers/block/zloop.c b/drivers/block/zloop.c index a423228e201ba3..e0123c16040d7b 100644 --- a/drivers/block/zloop.c +++ b/drivers/block/zloop.c @@ -407,6 +407,10 @@ static void zloop_rw(struct zloop_cmd *cmd) mutex_lock(&zone->lock); if (is_append) { + if (zone->cond == BLK_ZONE_COND_FULL) { + ret = -EIO; + goto unlock; + } sector = zone->wp; cmd->sector = sector; } From 3846f8f781cc2758cbb40f99b9765954b1a5b568 Mon Sep 17 00:00:00 2001 From: Damien Le Moal Date: Sat, 15 Nov 2025 21:15:51 +0900 Subject: [PATCH 1708/3902] zloop: make the write pointer of full zones invalid commit 866d65745b635927c3d1343ab67e6fd4a99d116d upstream. The write pointer of zones that are in the full condition is always invalid. Reflect that fact by setting the write pointer of full zones to ULLONG_MAX. Fixes: eb0570c7df23 ("block: new zoned loop block device driver") Cc: stable@vger.kernel.org Signed-off-by: Damien Le Moal Signed-off-by: Jens Axboe Signed-off-by: Greg Kroah-Hartman --- drivers/block/zloop.c | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/drivers/block/zloop.c b/drivers/block/zloop.c index e0123c16040d7b..39a425db670c8b 100644 --- a/drivers/block/zloop.c +++ b/drivers/block/zloop.c @@ -177,7 +177,7 @@ static int zloop_update_seq_zone(struct zloop_device *zlo, unsigned int zone_no) zone->wp = zone->start; } else if (file_sectors == zlo->zone_capacity) { zone->cond = BLK_ZONE_COND_FULL; - zone->wp = zone->start + zlo->zone_size; + zone->wp = ULLONG_MAX; } else { zone->cond = BLK_ZONE_COND_CLOSED; zone->wp = zone->start + file_sectors; @@ -326,7 +326,7 @@ static int zloop_finish_zone(struct zloop_device *zlo, unsigned int zone_no) } zone->cond = BLK_ZONE_COND_FULL; - zone->wp = zone->start + zlo->zone_size; + zone->wp = ULLONG_MAX; clear_bit(ZLOOP_ZONE_SEQ_ERROR, &zone->flags); unlock: @@ -437,8 +437,10 @@ static void zloop_rw(struct zloop_cmd *cmd) * copmpletes. */ zone->wp += nr_sectors; - if (zone->wp == zone_end) + if (zone->wp == zone_end) { zone->cond = BLK_ZONE_COND_FULL; + zone->wp = ULLONG_MAX; + } } rq_for_each_bvec(tmp, rq, rq_iter) From 8716a841d1da4113e757b537b7d249c29d82e6db Mon Sep 17 00:00:00 2001 From: Miaoqian Lin Date: Mon, 27 Oct 2025 14:07:35 +0800 Subject: [PATCH 1709/3902] virtio: vdpa: Fix reference count leak in octep_sriov_enable() commit b41ca62c0019de1321d75f2b2f274a28784a41ed upstream. pci_get_device() will increase the reference count for the returned pci_dev, and also decrease the reference count for the input parameter from if it is not NULL. If we break the loop in with 'vf_pdev' not NULL. We need to call pci_dev_put() to decrease the reference count. Found via static anlaysis and this is similar to commit c508eb042d97 ("perf/x86/intel/uncore: Fix reference count leak in sad_cfg_iio_topology()") Fixes: 8b6c724cdab8 ("virtio: vdpa: vDPA driver for Marvell OCTEON DPU devices") Cc: stable@vger.kernel.org Signed-off-by: Miaoqian Lin Signed-off-by: Michael S. Tsirkin Message-Id: <20251027060737.33815-1-linmq006@gmail.com> Signed-off-by: Greg Kroah-Hartman --- drivers/vdpa/octeon_ep/octep_vdpa_main.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/vdpa/octeon_ep/octep_vdpa_main.c b/drivers/vdpa/octeon_ep/octep_vdpa_main.c index 9e8d07078606f3..31a02e7fd7f279 100644 --- a/drivers/vdpa/octeon_ep/octep_vdpa_main.c +++ b/drivers/vdpa/octeon_ep/octep_vdpa_main.c @@ -736,6 +736,7 @@ static int octep_sriov_enable(struct pci_dev *pdev, int num_vfs) octep_vdpa_assign_barspace(vf_pdev, pdev, index); if (++index == num_vfs) { done = true; + pci_dev_put(vf_pdev); break; } } From 479af0d9c6ba2fc238ceb2e1b704d3335f0428d8 Mon Sep 17 00:00:00 2001 From: Raghavendra Rao Ananta Date: Fri, 31 Oct 2025 17:06:02 +0000 Subject: [PATCH 1710/3902] vfio: Fix ksize arg while copying user struct in vfio_df_ioctl_bind_iommufd() commit 2f03f21fe7516902283b135de272d3c7b10672de upstream. For the cases where user includes a non-zero value in 'token_uuid_ptr' field of 'struct vfio_device_bind_iommufd', the copy_struct_from_user() in vfio_df_ioctl_bind_iommufd() fails with -E2BIG. For the 'minsz' passed, copy_struct_from_user() expects the newly introduced field to be zero-ed, which would be incorrect in this case. Fix this by passing the actual size of the kernel struct. If working with a newer userspace, copy_struct_from_user() would copy the 'token_uuid_ptr' field, and if working with an old userspace, it would zero out this field, thus still retaining backward compatibility. Fixes: 86624ba3b522 ("vfio/pci: Do vf_token checks for VFIO_DEVICE_BIND_IOMMUFD") Cc: stable@vger.kernel.org Signed-off-by: Raghavendra Rao Ananta Reviewed-by: Jason Gunthorpe Link: https://lore.kernel.org/r/20251031170603.2260022-2-rananta@google.com Signed-off-by: Alex Williamson Signed-off-by: Greg Kroah-Hartman --- drivers/vfio/device_cdev.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/vfio/device_cdev.c b/drivers/vfio/device_cdev.c index 480cac3a0c274f..8ceca24ac136c6 100644 --- a/drivers/vfio/device_cdev.c +++ b/drivers/vfio/device_cdev.c @@ -99,7 +99,7 @@ long vfio_df_ioctl_bind_iommufd(struct vfio_device_file *df, return ret; if (user_size < minsz) return -EINVAL; - ret = copy_struct_from_user(&bind, minsz, arg, user_size); + ret = copy_struct_from_user(&bind, sizeof(bind), arg, user_size); if (ret) return ret; From 3ce2b4aec8cd1460629802d9a6c1873abee6a04c Mon Sep 17 00:00:00 2001 From: Steven Rostedt Date: Thu, 4 Dec 2025 15:19:35 -0500 Subject: [PATCH 1711/3902] tracing: Fix fixed array of synthetic event commit 47ef834209e5981f443240d8a8b45bf680df22aa upstream. The commit 4d38328eb442d ("tracing: Fix synth event printk format for str fields") replaced "%.*s" with "%s" but missed removing the number size of the dynamic and static strings. The commit e1a453a57bc7 ("tracing: Do not add length to print format in synthetic events") fixed the dynamic part but did not fix the static part. That is, with the commands: # echo 's:wake_lat char[] wakee; u64 delta;' >> /sys/kernel/tracing/dynamic_events # echo 'hist:keys=pid:ts=common_timestamp.usecs if !(common_flags & 0x18)' > /sys/kernel/tracing/events/sched/sched_waking/trigger # echo 'hist:keys=next_pid:delta=common_timestamp.usecs-$ts:onmatch(sched.sched_waking).trace(wake_lat,next_comm,$delta)' > /sys/kernel/tracing/events/sched/sched_switch/trigger That caused the output of: -0 [001] d..5. 193.428167: wake_lat: wakee=(efault)sshd-sessiondelta=155 sshd-session-879 [001] d..5. 193.811080: wake_lat: wakee=(efault)kworker/u34:5delta=58 -0 [002] d..5. 193.811198: wake_lat: wakee=(efault)bashdelta=91 The commit e1a453a57bc7 fixed the part where the synthetic event had "char[] wakee". But if one were to replace that with a static size string: # echo 's:wake_lat char[16] wakee; u64 delta;' >> /sys/kernel/tracing/dynamic_events Where "wakee" is defined as "char[16]" and not "char[]" making it a static size, the code triggered the "(efaul)" again. Remove the added STR_VAR_LEN_MAX size as the string is still going to be nul terminated. Cc: stable@vger.kernel.org Cc: Masami Hiramatsu Cc: Mathieu Desnoyers Cc: Douglas Raillard Link: https://patch.msgid.link/20251204151935.5fa30355@gandalf.local.home Fixes: e1a453a57bc7 ("tracing: Do not add length to print format in synthetic events") Signed-off-by: Steven Rostedt (Google) Signed-off-by: Greg Kroah-Hartman --- kernel/trace/trace_events_synth.c | 1 - 1 file changed, 1 deletion(-) diff --git a/kernel/trace/trace_events_synth.c b/kernel/trace/trace_events_synth.c index f24ee61f8884fc..8e1524b8667a73 100644 --- a/kernel/trace/trace_events_synth.c +++ b/kernel/trace/trace_events_synth.c @@ -375,7 +375,6 @@ static enum print_line_t print_synth_event(struct trace_iterator *iter, n_u64++; } else { trace_seq_printf(s, print_fmt, se->fields[i]->name, - STR_VAR_LEN_MAX, (char *)&entry->fields[n_u64].as_u64, i == se->n_fields - 1 ? "" : " "); n_u64 += STR_VAR_LEN_MAX / sizeof(u64); From b2d6246ef36f4d3e254e02ca36cdfff5fa6d17fa Mon Sep 17 00:00:00 2001 From: Johan Hovold Date: Fri, 21 Nov 2025 13:18:52 +0100 Subject: [PATCH 1712/3902] soc: samsung: exynos-pmu: fix device leak on regmap lookup commit 990eb9a8eb4540ab90c7b34bb07b87ff13881cad upstream. Make sure to drop the reference taken when looking up the PMU device and its regmap. Note that holding a reference to a device does not prevent its regmap from going away so there is no point in keeping the reference. Fixes: 0b7c6075022c ("soc: samsung: exynos-pmu: Add regmap support for SoCs that protect PMU regs") Cc: stable@vger.kernel.org # 6.9 Cc: Peter Griffin Signed-off-by: Johan Hovold Link: https://patch.msgid.link/20251121121852.16825-1-johan@kernel.org Signed-off-by: Krzysztof Kozlowski Signed-off-by: Greg Kroah-Hartman --- drivers/soc/samsung/exynos-pmu.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/drivers/soc/samsung/exynos-pmu.c b/drivers/soc/samsung/exynos-pmu.c index df5b1f299a2604..f8fe1a5965ab47 100644 --- a/drivers/soc/samsung/exynos-pmu.c +++ b/drivers/soc/samsung/exynos-pmu.c @@ -346,6 +346,8 @@ struct regmap *exynos_get_pmu_regmap_by_phandle(struct device_node *np, if (!dev) return ERR_PTR(-EPROBE_DEFER); + put_device(dev); + return syscon_node_to_regmap(pmu_np); } EXPORT_SYMBOL_GPL(exynos_get_pmu_regmap_by_phandle); From 951515391b8f68ed6230d0a0c59409ec2579dbb8 Mon Sep 17 00:00:00 2001 From: Johan Hovold Date: Fri, 26 Sep 2025 16:35:11 +0200 Subject: [PATCH 1713/3902] soc: qcom: pbs: fix device leak on lookup commit 94124bf253d24b13e89c45618a168d5a1d8a61e7 upstream. Make sure to drop the reference taken to the pbs platform device when looking up its driver data. Note that holding a reference to a device does not prevent its driver data from going away so there is no point in keeping the reference. Fixes: 5b2dd77be1d8 ("soc: qcom: add QCOM PBS driver") Cc: stable@vger.kernel.org # 6.9 Cc: Anjelique Melendez Signed-off-by: Johan Hovold Link: https://lore.kernel.org/r/20250926143511.6715-3-johan@kernel.org Signed-off-by: Bjorn Andersson Signed-off-by: Greg Kroah-Hartman --- drivers/soc/qcom/qcom-pbs.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/drivers/soc/qcom/qcom-pbs.c b/drivers/soc/qcom/qcom-pbs.c index 1cc5d045f9ddfe..06b4a596e27578 100644 --- a/drivers/soc/qcom/qcom-pbs.c +++ b/drivers/soc/qcom/qcom-pbs.c @@ -173,6 +173,8 @@ struct pbs_dev *get_pbs_client_device(struct device *dev) return ERR_PTR(-EINVAL); } + platform_device_put(pdev); + return pbs; } EXPORT_SYMBOL_GPL(get_pbs_client_device); From b28fd17adfb2131a7194a917453ed347702251b1 Mon Sep 17 00:00:00 2001 From: Johan Hovold Date: Fri, 26 Sep 2025 16:35:10 +0200 Subject: [PATCH 1714/3902] soc: qcom: ocmem: fix device leak on lookup commit b5c16ea57b030b8e9428ec726e26219dfe05c3d9 upstream. Make sure to drop the reference taken to the ocmem platform device when looking up its driver data. Note that holding a reference to a device does not prevent its driver data from going away so there is no point in keeping the reference. Also note that commit 0ff027027e05 ("soc: qcom: ocmem: Fix missing put_device() call in of_get_ocmem") fixed the leak in a lookup error path, but the reference is still leaking on success. Fixes: 88c1e9404f1d ("soc: qcom: add OCMEM driver") Cc: stable@vger.kernel.org # 5.5: 0ff027027e05 Cc: Brian Masney Cc: Miaoqian Lin Signed-off-by: Johan Hovold Reviewed-by: Brian Masney Link: https://lore.kernel.org/r/20250926143511.6715-2-johan@kernel.org Signed-off-by: Bjorn Andersson Signed-off-by: Greg Kroah-Hartman --- drivers/soc/qcom/ocmem.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/soc/qcom/ocmem.c b/drivers/soc/qcom/ocmem.c index 9c3bd37b6579d3..71130a2f62e9e6 100644 --- a/drivers/soc/qcom/ocmem.c +++ b/drivers/soc/qcom/ocmem.c @@ -202,9 +202,9 @@ struct ocmem *of_get_ocmem(struct device *dev) } ocmem = platform_get_drvdata(pdev); + put_device(&pdev->dev); if (!ocmem) { dev_err(dev, "Cannot get ocmem\n"); - put_device(&pdev->dev); return ERR_PTR(-ENODEV); } return ocmem; From 25073a05068e681fb61004fab88979d8865ef36d Mon Sep 17 00:00:00 2001 From: Johan Hovold Date: Fri, 26 Sep 2025 16:31:31 +0200 Subject: [PATCH 1715/3902] soc: apple: mailbox: fix device leak on lookup commit f401671e90ccc26b3022f177c4156a429c024f6c upstream. Make sure to drop the reference taken to the mbox platform device when looking up its driver data. Note that holding a reference to a device does not prevent its driver data from going away so there is no point in keeping the reference. Fixes: 6e1457fcad3f ("soc: apple: mailbox: Add ASC/M3 mailbox driver") Cc: stable@vger.kernel.org # 6.8 Signed-off-by: Johan Hovold Reviewed-by: Neal Gompa Signed-off-by: Sven Peter Signed-off-by: Greg Kroah-Hartman --- drivers/soc/apple/mailbox.c | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/drivers/soc/apple/mailbox.c b/drivers/soc/apple/mailbox.c index 8f29108dc69ac9..5c48455185c9ba 100644 --- a/drivers/soc/apple/mailbox.c +++ b/drivers/soc/apple/mailbox.c @@ -302,11 +302,18 @@ struct apple_mbox *apple_mbox_get(struct device *dev, int index) return ERR_PTR(-EPROBE_DEFER); mbox = platform_get_drvdata(pdev); - if (!mbox) - return ERR_PTR(-EPROBE_DEFER); + if (!mbox) { + mbox = ERR_PTR(-EPROBE_DEFER); + goto out_put_pdev; + } + + if (!device_link_add(dev, &pdev->dev, DL_FLAG_AUTOREMOVE_CONSUMER)) { + mbox = ERR_PTR(-ENODEV); + goto out_put_pdev; + } - if (!device_link_add(dev, &pdev->dev, DL_FLAG_AUTOREMOVE_CONSUMER)) - return ERR_PTR(-ENODEV); +out_put_pdev: + put_device(&pdev->dev); return mbox; } From 81bf2780ba433689f80c43a9ac46c03e916a32a9 Mon Sep 17 00:00:00 2001 From: Johan Hovold Date: Fri, 26 Sep 2025 16:24:53 +0200 Subject: [PATCH 1716/3902] soc: amlogic: canvas: fix device leak on lookup commit 32200f4828de9d7e6db379909898e718747f4e18 upstream. Make sure to drop the reference taken to the canvas platform device when looking up its driver data. Note that holding a reference to a device does not prevent its driver data from going away so there is no point in keeping the reference. Also note that commit 28f851e6afa8 ("soc: amlogic: canvas: add missing put_device() call in meson_canvas_get()") fixed the leak in a lookup error path, but the reference is still leaking on success. Fixes: d4983983d987 ("soc: amlogic: add meson-canvas driver") Cc: stable@vger.kernel.org # 4.20: 28f851e6afa8 Cc: Yu Kuai Signed-off-by: Johan Hovold Reviewed-by: Martin Blumenstingl Link: https://patch.msgid.link/20250926142454.5929-2-johan@kernel.org Signed-off-by: Neil Armstrong Signed-off-by: Greg Kroah-Hartman --- drivers/soc/amlogic/meson-canvas.c | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/drivers/soc/amlogic/meson-canvas.c b/drivers/soc/amlogic/meson-canvas.c index b6e06c4d2117f6..0711088da5dcd1 100644 --- a/drivers/soc/amlogic/meson-canvas.c +++ b/drivers/soc/amlogic/meson-canvas.c @@ -73,10 +73,9 @@ struct meson_canvas *meson_canvas_get(struct device *dev) * current state, this driver probe cannot return -EPROBE_DEFER */ canvas = dev_get_drvdata(&canvas_pdev->dev); - if (!canvas) { - put_device(&canvas_pdev->dev); + put_device(&canvas_pdev->dev); + if (!canvas) return ERR_PTR(-EINVAL); - } return canvas; } From 88b71cd0aa39e7c97990a9e9213a360076927610 Mon Sep 17 00:00:00 2001 From: Tomas Glozar Date: Mon, 6 Oct 2025 16:31:00 +0200 Subject: [PATCH 1717/3902] rtla/timerlat_bpf: Stop tracing on user latency commit e4240db9336c25826a2d6634adcca86d5ee01bde upstream. rtla-timerlat allows a *thread* latency threshold to be set via the -T/--thread option. However, the timerlat tracer calls this *total* latency (stop_tracing_total_us), and stops tracing also when the return-to-user latency is over the threshold. Change the behavior of the timerlat BPF program to reflect what the timerlat tracer is doing, to avoid discrepancy between stopping collecting data in the BPF program and stopping tracing in the timerlat tracer. Cc: stable@vger.kernel.org Fixes: e34293ddcebd ("rtla/timerlat: Add BPF skeleton to collect samples") Reviewed-by: Wander Lairson Costa Link: https://lore.kernel.org/r/20251006143100.137255-1-tglozar@redhat.com Signed-off-by: Tomas Glozar Signed-off-by: Greg Kroah-Hartman --- tools/tracing/rtla/src/timerlat.bpf.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/tools/tracing/rtla/src/timerlat.bpf.c b/tools/tracing/rtla/src/timerlat.bpf.c index 084cd10c21fc49..e2265b5d649100 100644 --- a/tools/tracing/rtla/src/timerlat.bpf.c +++ b/tools/tracing/rtla/src/timerlat.bpf.c @@ -148,6 +148,9 @@ int handle_timerlat_sample(struct trace_event_raw_timerlat_sample *tp_args) } else { update_main_hist(&hist_user, bucket); update_summary(&summary_user, latency, bucket); + + if (thread_threshold != 0 && latency_us >= thread_threshold) + set_stop_tracing(); } return 0; From f80e4e91b010ee7d6c52f24d069975ac955ac6b2 Mon Sep 17 00:00:00 2001 From: Srinivas Kandagatla Date: Fri, 22 Aug 2025 11:00:42 +0100 Subject: [PATCH 1718/3902] rpmsg: glink: fix rpmsg device leak commit a53e356df548f6b0e82529ef3cc6070f42622189 upstream. While testing rpmsg-char interface it was noticed that duplicate sysfs entries are getting created and below warning is noticed. Reason for this is that we are leaking rpmsg device pointer, setting it null without actually unregistering device. Any further attempts to unregister fail because rpdev is NULL, resulting in a leak. Fix this by unregistering rpmsg device before removing its reference from rpmsg channel. sysfs: cannot create duplicate filename '/devices/platform/soc@0/3700000.remot eproc/remoteproc/remoteproc1/3700000.remoteproc:glink-edge/3700000.remoteproc: glink-edge.adsp_apps.-1.-1' [ 114.115347] CPU: 0 UID: 0 PID: 9 Comm: kworker/0:0 Not tainted 6.16.0-rc4 #7 PREEMPT [ 114.115355] Hardware name: Qualcomm Technologies, Inc. Robotics RB3gen2 (DT) [ 114.115358] Workqueue: events qcom_glink_work [ 114.115371] Call trace:8 [ 114.115374] show_stack+0x18/0x24 (C) [ 114.115382] dump_stack_lvl+0x60/0x80 [ 114.115388] dump_stack+0x18/0x24 [ 114.115393] sysfs_warn_dup+0x64/0x80 [ 114.115402] sysfs_create_dir_ns+0xf4/0x120 [ 114.115409] kobject_add_internal+0x98/0x260 [ 114.115416] kobject_add+0x9c/0x108 [ 114.115421] device_add+0xc4/0x7a0 [ 114.115429] rpmsg_register_device+0x5c/0xb0 [ 114.115434] qcom_glink_work+0x4bc/0x820 [ 114.115438] process_one_work+0x148/0x284 [ 114.115446] worker_thread+0x2c4/0x3e0 [ 114.115452] kthread+0x12c/0x204 [ 114.115457] ret_from_fork+0x10/0x20 [ 114.115464] kobject: kobject_add_internal failed for 3700000.remoteproc: glink-edge.adsp_apps.-1.-1 with -EEXIST, don't try to register things with the same name in the same directory. [ 114.250045] rpmsg 3700000.remoteproc:glink-edge.adsp_apps.-1.-1: device_add failed: -17 Fixes: 835764ddd9af ("rpmsg: glink: Move the common glink protocol implementation to glink_native.c") Cc: Stable@vger.kernel.org Signed-off-by: Srinivas Kandagatla Reviewed-by: Dmitry Baryshkov Link: https://lore.kernel.org/r/20250822100043.2604794-2-srinivas.kandagatla@oss.qualcomm.com Signed-off-by: Bjorn Andersson Signed-off-by: Greg Kroah-Hartman --- drivers/rpmsg/qcom_glink_native.c | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/drivers/rpmsg/qcom_glink_native.c b/drivers/rpmsg/qcom_glink_native.c index 820a6ca5b1d7a6..3a15d9d108089f 100644 --- a/drivers/rpmsg/qcom_glink_native.c +++ b/drivers/rpmsg/qcom_glink_native.c @@ -1399,6 +1399,7 @@ static void qcom_glink_destroy_ept(struct rpmsg_endpoint *ept) { struct glink_channel *channel = to_glink_channel(ept); struct qcom_glink *glink = channel->glink; + struct rpmsg_channel_info chinfo; unsigned long flags; spin_lock_irqsave(&channel->recv_lock, flags); @@ -1406,6 +1407,13 @@ static void qcom_glink_destroy_ept(struct rpmsg_endpoint *ept) spin_unlock_irqrestore(&channel->recv_lock, flags); /* Decouple the potential rpdev from the channel */ + if (channel->rpdev) { + strscpy_pad(chinfo.name, channel->name, sizeof(chinfo.name)); + chinfo.src = RPMSG_ADDR_ANY; + chinfo.dst = RPMSG_ADDR_ANY; + + rpmsg_unregister_device(glink->dev, &chinfo); + } channel->rpdev = NULL; qcom_glink_send_close_req(glink, channel); From 17be376bb250718e466eb33d3538b478781823af Mon Sep 17 00:00:00 2001 From: Biju Das Date: Wed, 26 Nov 2025 10:42:48 +0000 Subject: [PATCH 1719/3902] pwm: rzg2l-gpt: Allow checking period_tick cache value only if sibling channel is enabled MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit commit fae00ea9f00367771003ace78f29549dead58fc7 upstream. The rzg2l_gpt_config() tests the rzg2l_gpt->period_tick variable when both channels of a hardware channel are in use. This check is not valid if rzg2l_gpt_config() is called after disabling all the channels, as it tests against the cached value. Hence, allow checking and setting the cached value only if the sibling channel is enabled. While at it, drop else after return statement to fix the check patch warning. Cc: stable@kernel.org Fixes: 061f087f5d0b ("pwm: Add support for RZ/G2L GPT") Signed-off-by: Biju Das Link: https://patch.msgid.link/20251126104308.142302-1-biju.das.jz@bp.renesas.com Signed-off-by: Uwe Kleine-König Signed-off-by: Greg Kroah-Hartman --- drivers/pwm/pwm-rzg2l-gpt.c | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/drivers/pwm/pwm-rzg2l-gpt.c b/drivers/pwm/pwm-rzg2l-gpt.c index 360c8bf3b19037..4856af080e8e9f 100644 --- a/drivers/pwm/pwm-rzg2l-gpt.c +++ b/drivers/pwm/pwm-rzg2l-gpt.c @@ -96,6 +96,11 @@ static inline unsigned int rzg2l_gpt_subchannel(unsigned int hwpwm) return hwpwm & 0x1; } +static inline unsigned int rzg2l_gpt_sibling(unsigned int hwpwm) +{ + return hwpwm ^ 0x1; +} + static void rzg2l_gpt_write(struct rzg2l_gpt_chip *rzg2l_gpt, u32 reg, u32 data) { writel(data, rzg2l_gpt->mmio + reg); @@ -271,10 +276,14 @@ static int rzg2l_gpt_config(struct pwm_chip *chip, struct pwm_device *pwm, * in use with different settings. */ if (rzg2l_gpt->channel_request_count[ch] > 1) { - if (period_ticks < rzg2l_gpt->period_ticks[ch]) - return -EBUSY; - else + u8 sibling_ch = rzg2l_gpt_sibling(pwm->hwpwm); + + if (rzg2l_gpt_is_ch_enabled(rzg2l_gpt, sibling_ch)) { + if (period_ticks < rzg2l_gpt->period_ticks[ch]) + return -EBUSY; + period_ticks = rzg2l_gpt->period_ticks[ch]; + } } prescale = rzg2l_gpt_calculate_prescale(rzg2l_gpt, period_ticks); From 19d61f032c7e052c3afc9a7e8f0aaf136b5732bf Mon Sep 17 00:00:00 2001 From: Bartosz Golaszewski Date: Fri, 21 Nov 2025 11:04:50 +0100 Subject: [PATCH 1720/3902] platform/x86: intel: chtwc_int33fe: don't dereference swnode args MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit commit 527250cd9092461f1beac3e4180a4481bffa01b5 upstream. Members of struct software_node_ref_args should not be dereferenced directly but set using the provided macros. Commit d7cdbbc93c56 ("software node: allow referencing firmware nodes") changed the name of the software node member and caused a build failure. Remove all direct dereferences of the ref struct as a fix. However, this driver also seems to abuse the software node interface by waiting for a node with an arbitrary name "intel-xhci-usb-sw" to appear in the system before setting up the reference for the I2C device, while the actual software node already exists in the intel-xhci-usb-role-switch module and should be used to set up a static reference. Add a FIXME for a future improvement. Fixes: d7cdbbc93c56 ("software node: allow referencing firmware nodes") Fixes: 53c24c2932e5 ("platform/x86: intel_cht_int33fe: use inline reference properties") Cc: stable@vger.kernel.org Reported-by: Stephen Rothwell Closes: https://lore.kernel.org/all/20251121111534.7cdbfe5c@canb.auug.org.au/ Signed-off-by: Bartosz Golaszewski Reviewed-by: Hans de Goede Acked-by: Ilpo Järvinen Signed-off-by: Philipp Zabel Signed-off-by: Greg Kroah-Hartman --- drivers/platform/x86/intel/chtwc_int33fe.c | 29 +++++++++++++++------- 1 file changed, 20 insertions(+), 9 deletions(-) diff --git a/drivers/platform/x86/intel/chtwc_int33fe.c b/drivers/platform/x86/intel/chtwc_int33fe.c index 29e8b5432f4c9e..d183aa53c318ba 100644 --- a/drivers/platform/x86/intel/chtwc_int33fe.c +++ b/drivers/platform/x86/intel/chtwc_int33fe.c @@ -77,7 +77,7 @@ static const struct software_node max17047_node = { * software node. */ static struct software_node_ref_args fusb302_mux_refs[] = { - { .node = NULL }, + SOFTWARE_NODE_REFERENCE(NULL), }; static const struct property_entry fusb302_properties[] = { @@ -190,11 +190,6 @@ static void cht_int33fe_remove_nodes(struct cht_int33fe_data *data) { software_node_unregister_node_group(node_group); - if (fusb302_mux_refs[0].node) { - fwnode_handle_put(software_node_fwnode(fusb302_mux_refs[0].node)); - fusb302_mux_refs[0].node = NULL; - } - if (data->dp) { data->dp->secondary = NULL; fwnode_handle_put(data->dp); @@ -202,7 +197,15 @@ static void cht_int33fe_remove_nodes(struct cht_int33fe_data *data) } } -static int cht_int33fe_add_nodes(struct cht_int33fe_data *data) +static void cht_int33fe_put_swnode(void *data) +{ + struct fwnode_handle *fwnode = data; + + fwnode_handle_put(fwnode); + fusb302_mux_refs[0] = SOFTWARE_NODE_REFERENCE(NULL); +} + +static int cht_int33fe_add_nodes(struct device *dev, struct cht_int33fe_data *data) { const struct software_node *mux_ref_node; int ret; @@ -212,17 +215,25 @@ static int cht_int33fe_add_nodes(struct cht_int33fe_data *data) * until the mux driver has created software node for the mux device. * It means we depend on the mux driver. This function will return * -EPROBE_DEFER until the mux device is registered. + * + * FIXME: the relevant software node exists in intel-xhci-usb-role-switch + * and - if exported - could be used to set up a static reference. */ mux_ref_node = software_node_find_by_name(NULL, "intel-xhci-usb-sw"); if (!mux_ref_node) return -EPROBE_DEFER; + ret = devm_add_action_or_reset(dev, cht_int33fe_put_swnode, + software_node_fwnode(mux_ref_node)); + if (ret) + return ret; + /* * Update node used in "usb-role-switch" property. Note that we * rely on software_node_register_node_group() to use the original * instance of properties instead of copying them. */ - fusb302_mux_refs[0].node = mux_ref_node; + fusb302_mux_refs[0] = SOFTWARE_NODE_REFERENCE(mux_ref_node); ret = software_node_register_node_group(node_group); if (ret) @@ -345,7 +356,7 @@ static int cht_int33fe_typec_probe(struct platform_device *pdev) return fusb302_irq; } - ret = cht_int33fe_add_nodes(data); + ret = cht_int33fe_add_nodes(dev, data); if (ret) return ret; From fb5b47ad5387692b39b1f885d81d72501840d86f Mon Sep 17 00:00:00 2001 From: Eric Biggers Date: Sat, 6 Dec 2025 13:37:50 -0800 Subject: [PATCH 1721/3902] lib/crypto: riscv: Depend on RISCV_EFFICIENT_VECTOR_UNALIGNED_ACCESS commit 1cd5bb6e9e027bab33aafd58fe8340124869ba62 upstream. Replace the RISCV_ISA_V dependency of the RISC-V crypto code with RISCV_EFFICIENT_VECTOR_UNALIGNED_ACCESS, which implies RISCV_ISA_V as well as vector unaligned accesses being efficient. This is necessary because this code assumes that vector unaligned accesses are supported and are efficient. (It does so to avoid having to use lots of extra vsetvli instructions to switch the element width back and forth between 8 and either 32 or 64.) This was omitted from the code originally just because the RISC-V kernel support for detecting this feature didn't exist yet. Support has now been added, but it's fragmented into per-CPU runtime detection, a command-line parameter, and a kconfig option. The kconfig option is the only reasonable way to do it, though, so let's just rely on that. Fixes: eb24af5d7a05 ("crypto: riscv - add vector crypto accelerated AES-{ECB,CBC,CTR,XTS}") Fixes: bb54668837a0 ("crypto: riscv - add vector crypto accelerated ChaCha20") Fixes: 600a3853dfa0 ("crypto: riscv - add vector crypto accelerated GHASH") Fixes: 8c8e40470ffe ("crypto: riscv - add vector crypto accelerated SHA-{256,224}") Fixes: b3415925a08b ("crypto: riscv - add vector crypto accelerated SHA-{512,384}") Fixes: 563a5255afa2 ("crypto: riscv - add vector crypto accelerated SM3") Fixes: b8d06352bbf3 ("crypto: riscv - add vector crypto accelerated SM4") Cc: stable@vger.kernel.org Reported-by: Vivian Wang Closes: https://lore.kernel.org/r/b3cfcdac-0337-4db0-a611-258f2868855f@iscas.ac.cn/ Reviewed-by: Jerry Shih Link: https://lore.kernel.org/r/20251206213750.81474-1-ebiggers@kernel.org Signed-off-by: Eric Biggers Signed-off-by: Greg Kroah-Hartman --- arch/riscv/crypto/Kconfig | 12 ++++++++---- lib/crypto/Kconfig | 9 ++++++--- 2 files changed, 14 insertions(+), 7 deletions(-) diff --git a/arch/riscv/crypto/Kconfig b/arch/riscv/crypto/Kconfig index a75d6325607b4c..14c5acb935e941 100644 --- a/arch/riscv/crypto/Kconfig +++ b/arch/riscv/crypto/Kconfig @@ -4,7 +4,8 @@ menu "Accelerated Cryptographic Algorithms for CPU (riscv)" config CRYPTO_AES_RISCV64 tristate "Ciphers: AES, modes: ECB, CBC, CTS, CTR, XTS" - depends on 64BIT && RISCV_ISA_V && TOOLCHAIN_HAS_VECTOR_CRYPTO + depends on 64BIT && TOOLCHAIN_HAS_VECTOR_CRYPTO && \ + RISCV_EFFICIENT_VECTOR_UNALIGNED_ACCESS select CRYPTO_ALGAPI select CRYPTO_LIB_AES select CRYPTO_SKCIPHER @@ -20,7 +21,8 @@ config CRYPTO_AES_RISCV64 config CRYPTO_GHASH_RISCV64 tristate "Hash functions: GHASH" - depends on 64BIT && RISCV_ISA_V && TOOLCHAIN_HAS_VECTOR_CRYPTO + depends on 64BIT && TOOLCHAIN_HAS_VECTOR_CRYPTO && \ + RISCV_EFFICIENT_VECTOR_UNALIGNED_ACCESS select CRYPTO_GCM help GCM GHASH function (NIST SP 800-38D) @@ -30,7 +32,8 @@ config CRYPTO_GHASH_RISCV64 config CRYPTO_SM3_RISCV64 tristate "Hash functions: SM3 (ShangMi 3)" - depends on 64BIT && RISCV_ISA_V && TOOLCHAIN_HAS_VECTOR_CRYPTO + depends on 64BIT && TOOLCHAIN_HAS_VECTOR_CRYPTO && \ + RISCV_EFFICIENT_VECTOR_UNALIGNED_ACCESS select CRYPTO_HASH select CRYPTO_LIB_SM3 help @@ -42,7 +45,8 @@ config CRYPTO_SM3_RISCV64 config CRYPTO_SM4_RISCV64 tristate "Ciphers: SM4 (ShangMi 4)" - depends on 64BIT && RISCV_ISA_V && TOOLCHAIN_HAS_VECTOR_CRYPTO + depends on 64BIT && TOOLCHAIN_HAS_VECTOR_CRYPTO && \ + RISCV_EFFICIENT_VECTOR_UNALIGNED_ACCESS select CRYPTO_ALGAPI select CRYPTO_SM4 help diff --git a/lib/crypto/Kconfig b/lib/crypto/Kconfig index 16859c6226dd4b..fa882108ba62ca 100644 --- a/lib/crypto/Kconfig +++ b/lib/crypto/Kconfig @@ -50,7 +50,8 @@ config CRYPTO_LIB_CHACHA_ARCH default y if ARM64 && KERNEL_MODE_NEON default y if MIPS && CPU_MIPS32_R2 default y if PPC64 && CPU_LITTLE_ENDIAN && VSX - default y if RISCV && 64BIT && RISCV_ISA_V && TOOLCHAIN_HAS_VECTOR_CRYPTO + default y if RISCV && 64BIT && TOOLCHAIN_HAS_VECTOR_CRYPTO && \ + RISCV_EFFICIENT_VECTOR_UNALIGNED_ACCESS default y if S390 default y if X86_64 @@ -161,7 +162,8 @@ config CRYPTO_LIB_SHA256_ARCH default y if ARM64 default y if MIPS && CPU_CAVIUM_OCTEON default y if PPC && SPE - default y if RISCV && 64BIT && RISCV_ISA_V && TOOLCHAIN_HAS_VECTOR_CRYPTO + default y if RISCV && 64BIT && TOOLCHAIN_HAS_VECTOR_CRYPTO && \ + RISCV_EFFICIENT_VECTOR_UNALIGNED_ACCESS default y if S390 default y if SPARC64 default y if X86_64 @@ -179,7 +181,8 @@ config CRYPTO_LIB_SHA512_ARCH default y if ARM && !CPU_V7M default y if ARM64 default y if MIPS && CPU_CAVIUM_OCTEON - default y if RISCV && 64BIT && RISCV_ISA_V && TOOLCHAIN_HAS_VECTOR_CRYPTO + default y if RISCV && 64BIT && TOOLCHAIN_HAS_VECTOR_CRYPTO && \ + RISCV_EFFICIENT_VECTOR_UNALIGNED_ACCESS default y if S390 default y if SPARC64 default y if X86_64 From 5fa6c144d3fb8506a5aa925205df8da4ffe0a274 Mon Sep 17 00:00:00 2001 From: Ma Ke Date: Wed, 22 Oct 2025 17:54:02 +0800 Subject: [PATCH 1722/3902] i2c: amd-mp2: fix reference leak in MP2 PCI device commit a6ee6aac66fb394b7f6e6187c73bdcd873f2d139 upstream. In i2c_amd_probe(), amd_mp2_find_device() utilizes driver_find_next_device() which internally calls driver_find_device() to locate the matching device. driver_find_device() increments the reference count of the found device by calling get_device(), but amd_mp2_find_device() fails to call put_device() to decrement the reference count before returning. This results in a reference count leak of the PCI device each time i2c_amd_probe() is executed, which may prevent the device from being properly released and cause a memory leak. Found by code review. Cc: stable@vger.kernel.org Fixes: 529766e0a011 ("i2c: Add drivers for the AMD PCIe MP2 I2C controller") Signed-off-by: Ma Ke Signed-off-by: Andi Shyti Link: https://lore.kernel.org/r/20251022095402.8846-1-make24@iscas.ac.cn Signed-off-by: Greg Kroah-Hartman --- drivers/i2c/busses/i2c-amd-mp2-pci.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/drivers/i2c/busses/i2c-amd-mp2-pci.c b/drivers/i2c/busses/i2c-amd-mp2-pci.c index ef7370d3dbea31..60edbabc2986be 100644 --- a/drivers/i2c/busses/i2c-amd-mp2-pci.c +++ b/drivers/i2c/busses/i2c-amd-mp2-pci.c @@ -458,13 +458,16 @@ struct amd_mp2_dev *amd_mp2_find_device(void) { struct device *dev; struct pci_dev *pci_dev; + struct amd_mp2_dev *mp2_dev; dev = driver_find_next_device(&amd_mp2_pci_driver.driver, NULL); if (!dev) return NULL; pci_dev = to_pci_dev(dev); - return (struct amd_mp2_dev *)pci_get_drvdata(pci_dev); + mp2_dev = (struct amd_mp2_dev *)pci_get_drvdata(pci_dev); + put_device(dev); + return mp2_dev; } EXPORT_SYMBOL_GPL(amd_mp2_find_device); From c95e4c4caf9a652d882431f1254629b2181f33ea Mon Sep 17 00:00:00 2001 From: Raviteja Laggyshetty Date: Fri, 26 Sep 2025 12:12:09 +0530 Subject: [PATCH 1723/3902] interconnect: qcom: sdx75: Drop QPIC interconnect and BCM nodes commit 295f58fdccd05b2d6da1f4a4f81952ccb565c4dc upstream. As like other SDX SoCs, SDX75 SoC's QPIC BCM resource was modeled as a RPMh clock in clk-rpmh driver. However, for SDX75, this resource was also described as an interconnect and BCM node mistakenly. It is incorrect to describe the same resource in two different providers, as it will lead to votes from clients overriding each other. Hence, drop the QPIC interconnect and BCM nodes and let the clients use clk-rpmh driver to vote for this resource. Without this change, the NAND driver fails to probe on SDX75, as the interconnect sync state disables the QPIC nodes as there were no clients voting for this ICC resource. However, the NAND driver had already voted for this BCM resource through the clk-rpmh driver. Since both votes come from Linux, RPMh was unable to distinguish between these two and ends up disabling the QPIC resource during sync state. Cc: stable@vger.kernel.org Fixes: 3642b4e5cbfe ("interconnect: qcom: Add SDX75 interconnect provider driver") Signed-off-by: Raviteja Laggyshetty [mani: dropped the reference to bcm_qp0, reworded description] Signed-off-by: Manivannan Sadhasivam Reviewed-by: Konrad Dybcio Tested-by: Lakshmi Sowjanya D # on SDX75 Link: https://lore.kernel.org/r/20250926-sdx75-icc-v2-1-20d6820e455c@oss.qualcomm.com Signed-off-by: Georgi Djakov Signed-off-by: Greg Kroah-Hartman --- drivers/interconnect/qcom/sdx75.c | 26 -------------------------- drivers/interconnect/qcom/sdx75.h | 2 -- 2 files changed, 28 deletions(-) diff --git a/drivers/interconnect/qcom/sdx75.c b/drivers/interconnect/qcom/sdx75.c index 7ef1f17f3292e1..2def75f67eb8e0 100644 --- a/drivers/interconnect/qcom/sdx75.c +++ b/drivers/interconnect/qcom/sdx75.c @@ -16,15 +16,6 @@ #include "icc-rpmh.h" #include "sdx75.h" -static struct qcom_icc_node qpic_core_master = { - .name = "qpic_core_master", - .id = SDX75_MASTER_QPIC_CORE, - .channels = 1, - .buswidth = 4, - .num_links = 1, - .links = { SDX75_SLAVE_QPIC_CORE }, -}; - static struct qcom_icc_node qup0_core_master = { .name = "qup0_core_master", .id = SDX75_MASTER_QUP_CORE_0, @@ -375,14 +366,6 @@ static struct qcom_icc_node xm_usb3 = { .links = { SDX75_SLAVE_A1NOC_CFG }, }; -static struct qcom_icc_node qpic_core_slave = { - .name = "qpic_core_slave", - .id = SDX75_SLAVE_QPIC_CORE, - .channels = 1, - .buswidth = 4, - .num_links = 0, -}; - static struct qcom_icc_node qup0_core_slave = { .name = "qup0_core_slave", .id = SDX75_SLAVE_QUP_CORE_0, @@ -831,12 +814,6 @@ static struct qcom_icc_bcm bcm_mc0 = { .nodes = { &ebi }, }; -static struct qcom_icc_bcm bcm_qp0 = { - .name = "QP0", - .num_nodes = 1, - .nodes = { &qpic_core_slave }, -}; - static struct qcom_icc_bcm bcm_qup0 = { .name = "QUP0", .keepalive = true, @@ -898,14 +875,11 @@ static struct qcom_icc_bcm bcm_sn4 = { }; static struct qcom_icc_bcm * const clk_virt_bcms[] = { - &bcm_qp0, &bcm_qup0, }; static struct qcom_icc_node * const clk_virt_nodes[] = { - [MASTER_QPIC_CORE] = &qpic_core_master, [MASTER_QUP_CORE_0] = &qup0_core_master, - [SLAVE_QPIC_CORE] = &qpic_core_slave, [SLAVE_QUP_CORE_0] = &qup0_core_slave, }; diff --git a/drivers/interconnect/qcom/sdx75.h b/drivers/interconnect/qcom/sdx75.h index 24e88715992010..34f51add59dc00 100644 --- a/drivers/interconnect/qcom/sdx75.h +++ b/drivers/interconnect/qcom/sdx75.h @@ -33,7 +33,6 @@ #define SDX75_MASTER_QDSS_ETR 24 #define SDX75_MASTER_QDSS_ETR_1 25 #define SDX75_MASTER_QPIC 26 -#define SDX75_MASTER_QPIC_CORE 27 #define SDX75_MASTER_QUP_0 28 #define SDX75_MASTER_QUP_CORE_0 29 #define SDX75_MASTER_SDCC_1 30 @@ -76,7 +75,6 @@ #define SDX75_SLAVE_QDSS_CFG 67 #define SDX75_SLAVE_QDSS_STM 68 #define SDX75_SLAVE_QPIC 69 -#define SDX75_SLAVE_QPIC_CORE 70 #define SDX75_SLAVE_QUP_0 71 #define SDX75_SLAVE_QUP_CORE_0 72 #define SDX75_SLAVE_SDCC_1 73 From 5b804b8f1e0d66413774d43f7a4b78bba0ca6272 Mon Sep 17 00:00:00 2001 From: Joanne Koong Date: Thu, 4 Dec 2025 13:51:16 -0800 Subject: [PATCH 1724/3902] io_uring/rsrc: fix lost entries after cloned range commit 525916ce496615f531091855604eab9ca573b195 upstream. When cloning with node replacements (IORING_REGISTER_DST_REPLACE), destination entries after the cloned range are not copied over. Add logic to copy them over to the new destination table. Fixes: c1329532d5aa ("io_uring/rsrc: allow cloning with node replacements") Cc: stable@vger.kernel.org Signed-off-by: Joanne Koong Signed-off-by: Jens Axboe Signed-off-by: Greg Kroah-Hartman --- io_uring/rsrc.c | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/io_uring/rsrc.c b/io_uring/rsrc.c index 5b6b73c6a62bdc..160b4de2d00d3c 100644 --- a/io_uring/rsrc.c +++ b/io_uring/rsrc.c @@ -1200,7 +1200,7 @@ static int io_clone_buffers(struct io_ring_ctx *ctx, struct io_ring_ctx *src_ctx if (ret) return ret; - /* Fill entries in data from dst that won't overlap with src */ + /* Copy original dst nodes from before the cloned range */ for (i = 0; i < min(arg->dst_off, ctx->buf_table.nr); i++) { struct io_rsrc_node *src_node = ctx->buf_table.nodes[i]; @@ -1248,6 +1248,16 @@ static int io_clone_buffers(struct io_ring_ctx *ctx, struct io_ring_ctx *src_ctx i++; } + /* Copy original dst nodes from after the cloned range */ + for (i = nbufs; i < ctx->buf_table.nr; i++) { + struct io_rsrc_node *node = ctx->buf_table.nodes[i]; + + if (node) { + data.nodes[i] = node; + node->refs++; + } + } + /* * If asked for replace, put the old table. data->nodes[] holds both * old and new nodes at this point. From ef901d211dcce1bcab6a7e7ee196504c5ae1db10 Mon Sep 17 00:00:00 2001 From: Gui-Dong Han Date: Fri, 28 Nov 2025 20:47:09 +0800 Subject: [PATCH 1725/3902] hwmon: (max16065) Use local variable to avoid TOCTOU commit b8d5acdcf525f44e521ca4ef51dce4dac403dab4 upstream. In max16065_current_show, data->curr_sense is read twice: once for the error check and again for the calculation. Since i2c_smbus_read_byte_data returns negative error codes on failure, if the data changes to an error code between the check and the use, ADC_TO_CURR results in an incorrect calculation. Read data->curr_sense into a local variable to ensure consistency. Note that data->curr_gain is constant and safe to access directly. This aligns max16065_current_show with max16065_input_show, which already uses a local variable for the same reason. Link: https://lore.kernel.org/all/CALbr=LYJ_ehtp53HXEVkSpYoub+XYSTU8Rg=o1xxMJ8=5z8B-g@mail.gmail.com/ Fixes: f5bae2642e3d ("hwmon: Driver for MAX16065 System Manager and compatibles") Cc: stable@vger.kernel.org Signed-off-by: Gui-Dong Han Link: https://lore.kernel.org/r/20251128124709.3876-1-hanguidong02@gmail.com Signed-off-by: Guenter Roeck Signed-off-by: Greg Kroah-Hartman --- drivers/hwmon/max16065.c | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/drivers/hwmon/max16065.c b/drivers/hwmon/max16065.c index 0ccb5eb596fc40..4c9e7892a73c1b 100644 --- a/drivers/hwmon/max16065.c +++ b/drivers/hwmon/max16065.c @@ -216,12 +216,13 @@ static ssize_t max16065_current_show(struct device *dev, struct device_attribute *da, char *buf) { struct max16065_data *data = max16065_update_device(dev); + int curr_sense = data->curr_sense; - if (unlikely(data->curr_sense < 0)) - return data->curr_sense; + if (unlikely(curr_sense < 0)) + return curr_sense; return sysfs_emit(buf, "%d\n", - ADC_TO_CURR(data->curr_sense, data->curr_gain)); + ADC_TO_CURR(curr_sense, data->curr_gain)); } static ssize_t max16065_limit_store(struct device *dev, From ffd46e7f2b87a873bd18db946d61e3466f3ca82f Mon Sep 17 00:00:00 2001 From: Johan Hovold Date: Thu, 27 Nov 2025 14:43:51 +0100 Subject: [PATCH 1726/3902] hwmon: (max6697) fix regmap leak on probe failure commit 02f0ad8e8de8cf5344f8f0fa26d9529b8339da47 upstream. The i2c regmap allocated during probe is never freed. Switch to using the device managed allocator so that the regmap is released on probe failures (e.g. probe deferral) and on driver unbind. Fixes: 3a2a8cc3fe24 ("hwmon: (max6697) Convert to use regmap") Cc: stable@vger.kernel.org # 6.12 Cc: Guenter Roeck Signed-off-by: Johan Hovold Link: https://lore.kernel.org/r/20251127134351.1585-1-johan@kernel.org Signed-off-by: Guenter Roeck Signed-off-by: Greg Kroah-Hartman --- drivers/hwmon/max6697.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/hwmon/max6697.c b/drivers/hwmon/max6697.c index 0735a1d2c20fdc..6926d787b5ad12 100644 --- a/drivers/hwmon/max6697.c +++ b/drivers/hwmon/max6697.c @@ -548,7 +548,7 @@ static int max6697_probe(struct i2c_client *client) struct regmap *regmap; int err; - regmap = regmap_init_i2c(client, &max6697_regmap_config); + regmap = devm_regmap_init_i2c(client, &max6697_regmap_config); if (IS_ERR(regmap)) return PTR_ERR(regmap); From c8cf0c2bdcccc6634b6915ff793b844e12436680 Mon Sep 17 00:00:00 2001 From: Gui-Dong Han Date: Wed, 3 Dec 2025 02:01:05 +0800 Subject: [PATCH 1727/3902] hwmon: (w83791d) Convert macros to functions to avoid TOCTOU commit 670d7ef945d3a84683594429aea6ab2cdfa5ceb4 upstream. The macro FAN_FROM_REG evaluates its arguments multiple times. When used in lockless contexts involving shared driver data, this leads to Time-of-Check to Time-of-Use (TOCTOU) race conditions, potentially causing divide-by-zero errors. Convert the macro to a static function. This guarantees that arguments are evaluated only once (pass-by-value), preventing the race conditions. Additionally, in store_fan_div, move the calculation of the minimum limit inside the update lock. This ensures that the read-modify-write sequence operates on consistent data. Adhere to the principle of minimal changes by only converting macros that evaluate arguments multiple times and are used in lockless contexts. Link: https://lore.kernel.org/all/CALbr=LYJ_ehtp53HXEVkSpYoub+XYSTU8Rg=o1xxMJ8=5z8B-g@mail.gmail.com/ Fixes: 9873964d6eb2 ("[PATCH] HWMON: w83791d: New hardware monitoring driver for the Winbond W83791D") Cc: stable@vger.kernel.org Signed-off-by: Gui-Dong Han Link: https://lore.kernel.org/r/20251202180105.12842-1-hanguidong02@gmail.com Signed-off-by: Guenter Roeck Signed-off-by: Greg Kroah-Hartman --- drivers/hwmon/w83791d.c | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/drivers/hwmon/w83791d.c b/drivers/hwmon/w83791d.c index ace854b370a054..996e36951f9dad 100644 --- a/drivers/hwmon/w83791d.c +++ b/drivers/hwmon/w83791d.c @@ -218,9 +218,14 @@ static u8 fan_to_reg(long rpm, int div) return clamp_val((1350000 + rpm * div / 2) / (rpm * div), 1, 254); } -#define FAN_FROM_REG(val, div) ((val) == 0 ? -1 : \ - ((val) == 255 ? 0 : \ - 1350000 / ((val) * (div)))) +static int fan_from_reg(int val, int div) +{ + if (val == 0) + return -1; + if (val == 255) + return 0; + return 1350000 / (val * div); +} /* for temp1 which is 8-bit resolution, LSB = 1 degree Celsius */ #define TEMP1_FROM_REG(val) ((val) * 1000) @@ -521,7 +526,7 @@ static ssize_t show_##reg(struct device *dev, struct device_attribute *attr, \ struct w83791d_data *data = w83791d_update_device(dev); \ int nr = sensor_attr->index; \ return sprintf(buf, "%d\n", \ - FAN_FROM_REG(data->reg[nr], DIV_FROM_REG(data->fan_div[nr]))); \ + fan_from_reg(data->reg[nr], DIV_FROM_REG(data->fan_div[nr]))); \ } show_fan_reg(fan); @@ -585,10 +590,10 @@ static ssize_t store_fan_div(struct device *dev, struct device_attribute *attr, if (err) return err; + mutex_lock(&data->update_lock); /* Save fan_min */ - min = FAN_FROM_REG(data->fan_min[nr], DIV_FROM_REG(data->fan_div[nr])); + min = fan_from_reg(data->fan_min[nr], DIV_FROM_REG(data->fan_div[nr])); - mutex_lock(&data->update_lock); data->fan_div[nr] = div_to_reg(nr, val); switch (nr) { From 4fc2f8fbfdd43257c9a90b6730e80c0dc952575c Mon Sep 17 00:00:00 2001 From: Gui-Dong Han Date: Fri, 28 Nov 2025 20:38:16 +0800 Subject: [PATCH 1728/3902] hwmon: (w83l786ng) Convert macros to functions to avoid TOCTOU commit 07272e883fc61574b8367d44de48917f622cdd83 upstream. The macros FAN_FROM_REG and TEMP_FROM_REG evaluate their arguments multiple times. When used in lockless contexts involving shared driver data, this causes Time-of-Check to Time-of-Use (TOCTOU) race conditions. Convert the macros to static functions. This guarantees that arguments are evaluated only once (pass-by-value), preventing the race conditions. Adhere to the principle of minimal changes by only converting macros that evaluate arguments multiple times and are used in lockless contexts. Link: https://lore.kernel.org/all/CALbr=LYJ_ehtp53HXEVkSpYoub+XYSTU8Rg=o1xxMJ8=5z8B-g@mail.gmail.com/ Fixes: 85f03bccd6e0 ("hwmon: Add support for Winbond W83L786NG/NR") Cc: stable@vger.kernel.org Signed-off-by: Gui-Dong Han Link: https://lore.kernel.org/r/20251128123816.3670-1-hanguidong02@gmail.com Signed-off-by: Guenter Roeck Signed-off-by: Greg Kroah-Hartman --- drivers/hwmon/w83l786ng.c | 26 ++++++++++++++++++-------- 1 file changed, 18 insertions(+), 8 deletions(-) diff --git a/drivers/hwmon/w83l786ng.c b/drivers/hwmon/w83l786ng.c index 9b81bd406e0596..1d9109ca1585e7 100644 --- a/drivers/hwmon/w83l786ng.c +++ b/drivers/hwmon/w83l786ng.c @@ -76,15 +76,25 @@ FAN_TO_REG(long rpm, int div) return clamp_val((1350000 + rpm * div / 2) / (rpm * div), 1, 254); } -#define FAN_FROM_REG(val, div) ((val) == 0 ? -1 : \ - ((val) == 255 ? 0 : \ - 1350000 / ((val) * (div)))) +static int fan_from_reg(int val, int div) +{ + if (val == 0) + return -1; + if (val == 255) + return 0; + return 1350000 / (val * div); +} /* for temp */ #define TEMP_TO_REG(val) (clamp_val(((val) < 0 ? (val) + 0x100 * 1000 \ : (val)) / 1000, 0, 0xff)) -#define TEMP_FROM_REG(val) (((val) & 0x80 ? \ - (val) - 0x100 : (val)) * 1000) + +static int temp_from_reg(int val) +{ + if (val & 0x80) + return (val - 0x100) * 1000; + return val * 1000; +} /* * The analog voltage inputs have 8mV LSB. Since the sysfs output is @@ -280,7 +290,7 @@ static ssize_t show_##reg(struct device *dev, struct device_attribute *attr, \ int nr = to_sensor_dev_attr(attr)->index; \ struct w83l786ng_data *data = w83l786ng_update_device(dev); \ return sprintf(buf, "%d\n", \ - FAN_FROM_REG(data->reg[nr], DIV_FROM_REG(data->fan_div[nr]))); \ + fan_from_reg(data->reg[nr], DIV_FROM_REG(data->fan_div[nr]))); \ } show_fan_reg(fan); @@ -347,7 +357,7 @@ store_fan_div(struct device *dev, struct device_attribute *attr, /* Save fan_min */ mutex_lock(&data->update_lock); - min = FAN_FROM_REG(data->fan_min[nr], DIV_FROM_REG(data->fan_div[nr])); + min = fan_from_reg(data->fan_min[nr], DIV_FROM_REG(data->fan_div[nr])); data->fan_div[nr] = DIV_TO_REG(val); @@ -409,7 +419,7 @@ show_temp(struct device *dev, struct device_attribute *attr, char *buf) int nr = sensor_attr->nr; int index = sensor_attr->index; struct w83l786ng_data *data = w83l786ng_update_device(dev); - return sprintf(buf, "%d\n", TEMP_FROM_REG(data->temp[nr][index])); + return sprintf(buf, "%d\n", temp_from_reg(data->temp[nr][index])); } static ssize_t From c2423add62422d8b74b7d7d6e40cd0b1dba188cd Mon Sep 17 00:00:00 2001 From: Nicolas Ferre Date: Fri, 14 Nov 2025 15:02:25 +0100 Subject: [PATCH 1729/3902] ARM: dts: microchip: sama5d2: fix spi flexcom fifo size to 32 commit 7d5864dc5d5ea6a35983dd05295fb17f2f2f44ce upstream. Unlike standalone spi peripherals, on sama5d2, the flexcom spi have fifo size of 32 data. Fix flexcom/spi nodes where this property is wrong. Fixes: 6b9a3584c7ed ("ARM: dts: at91: sama5d2: Add missing flexcom definitions") Cc: stable@vger.kernel.org # 5.8+ Signed-off-by: Nicolas Ferre Link: https://lore.kernel.org/r/20251114140225.30372-1-nicolas.ferre@microchip.com Signed-off-by: Claudiu Beznea Signed-off-by: Greg Kroah-Hartman --- arch/arm/boot/dts/microchip/sama5d2.dtsi | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/arch/arm/boot/dts/microchip/sama5d2.dtsi b/arch/arm/boot/dts/microchip/sama5d2.dtsi index 17430d7f20555f..fde890f18d201b 100644 --- a/arch/arm/boot/dts/microchip/sama5d2.dtsi +++ b/arch/arm/boot/dts/microchip/sama5d2.dtsi @@ -571,7 +571,7 @@ AT91_XDMAC_DT_PER_IF(1) | AT91_XDMAC_DT_PERID(12))>; dma-names = "tx", "rx"; - atmel,fifo-size = <16>; + atmel,fifo-size = <32>; status = "disabled"; }; @@ -642,7 +642,7 @@ AT91_XDMAC_DT_PER_IF(1) | AT91_XDMAC_DT_PERID(14))>; dma-names = "tx", "rx"; - atmel,fifo-size = <16>; + atmel,fifo-size = <32>; status = "disabled"; }; @@ -854,7 +854,7 @@ AT91_XDMAC_DT_PER_IF(1) | AT91_XDMAC_DT_PERID(16))>; dma-names = "tx", "rx"; - atmel,fifo-size = <16>; + atmel,fifo-size = <32>; status = "disabled"; }; @@ -925,7 +925,7 @@ AT91_XDMAC_DT_PER_IF(1) | AT91_XDMAC_DT_PERID(18))>; dma-names = "tx", "rx"; - atmel,fifo-size = <16>; + atmel,fifo-size = <32>; status = "disabled"; }; @@ -997,7 +997,7 @@ AT91_XDMAC_DT_PER_IF(1) | AT91_XDMAC_DT_PERID(20))>; dma-names = "tx", "rx"; - atmel,fifo-size = <16>; + atmel,fifo-size = <32>; status = "disabled"; }; From 699d66165f6c86ae9127b626045372b7abfb2918 Mon Sep 17 00:00:00 2001 From: Nicolas Ferre Date: Fri, 14 Nov 2025 11:33:12 +0100 Subject: [PATCH 1730/3902] ARM: dts: microchip: sama7d65: fix uart fifo size to 32 commit 1f591be0a02c697f65a21be35f1d74117bbf4be2 upstream. On some flexcom nodes related to uart, the fifo sizes were wrong: fix them to 32 data. Note that product datasheet is being reviewed to fix inconsistency, but this value is validated by product's designers. Fixes: 261dcfad1b59 ("ARM: dts: microchip: add sama7d65 SoC DT") Fixes: b51e4aea3ecf ("ARM: dts: microchip: sama7d65: Add FLEXCOMs to sama7d65 SoC") Cc: stable@vger.kernel.org # 6.16+ Signed-off-by: Nicolas Ferre Link: https://lore.kernel.org/r/20251114103313.20220-1-nicolas.ferre@microchip.com Signed-off-by: Claudiu Beznea Signed-off-by: Greg Kroah-Hartman --- arch/arm/boot/dts/microchip/sama7d65.dtsi | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/arch/arm/boot/dts/microchip/sama7d65.dtsi b/arch/arm/boot/dts/microchip/sama7d65.dtsi index e53e2dd6d530c0..cd2cf9a6f40b86 100644 --- a/arch/arm/boot/dts/microchip/sama7d65.dtsi +++ b/arch/arm/boot/dts/microchip/sama7d65.dtsi @@ -557,7 +557,7 @@ dma-names = "tx", "rx"; atmel,use-dma-rx; atmel,use-dma-tx; - atmel,fifo-size = <16>; + atmel,fifo-size = <32>; atmel,usart-mode = ; status = "disabled"; }; @@ -618,7 +618,7 @@ clocks = <&pmc PMC_TYPE_PERIPHERAL 40>; clock-names = "usart"; atmel,usart-mode = ; - atmel,fifo-size = <16>; + atmel,fifo-size = <32>; status = "disabled"; }; }; @@ -643,7 +643,7 @@ dma-names = "tx", "rx"; atmel,use-dma-rx; atmel,use-dma-tx; - atmel,fifo-size = <16>; + atmel,fifo-size = <32>; atmel,usart-mode = ; status = "disabled"; }; From ce909de4416eb544a9138b3c9be14c986b8d88ae Mon Sep 17 00:00:00 2001 From: Nicolas Ferre Date: Fri, 14 Nov 2025 11:33:13 +0100 Subject: [PATCH 1731/3902] ARM: dts: microchip: sama7g5: fix uart fifo size to 32 commit 5654889a94b0de5ad6ceae3793e7f5e0b61b50b6 upstream. On some flexcom nodes related to uart, the fifo sizes were wrong: fix them to 32 data. Fixes: 7540629e2fc7 ("ARM: dts: at91: add sama7g5 SoC DT and sama7g5-ek") Cc: stable@vger.kernel.org # 5.15+ Signed-off-by: Nicolas Ferre Link: https://lore.kernel.org/r/20251114103313.20220-2-nicolas.ferre@microchip.com Signed-off-by: Claudiu Beznea Signed-off-by: Greg Kroah-Hartman --- arch/arm/boot/dts/microchip/sama7g5.dtsi | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/arch/arm/boot/dts/microchip/sama7g5.dtsi b/arch/arm/boot/dts/microchip/sama7g5.dtsi index 381cbcfcb34a14..03ef3d9aaeec62 100644 --- a/arch/arm/boot/dts/microchip/sama7g5.dtsi +++ b/arch/arm/boot/dts/microchip/sama7g5.dtsi @@ -824,7 +824,7 @@ dma-names = "tx", "rx"; atmel,use-dma-rx; atmel,use-dma-tx; - atmel,fifo-size = <16>; + atmel,fifo-size = <32>; status = "disabled"; }; }; @@ -850,7 +850,7 @@ dma-names = "tx", "rx"; atmel,use-dma-rx; atmel,use-dma-tx; - atmel,fifo-size = <16>; + atmel,fifo-size = <32>; status = "disabled"; }; }; From bf58ef1c4cd3cd9cfd205f0d3e3bf70085a52e31 Mon Sep 17 00:00:00 2001 From: Damien Le Moal Date: Wed, 5 Nov 2025 06:22:36 +0900 Subject: [PATCH 1732/3902] block: freeze queue when updating zone resources commit bba4322e3f303b2d656e748be758320b567f046f upstream. Modify disk_update_zone_resources() to freeze the device queue before updating the number of zones, zone capacity and other zone related resources. The locking order resulting from the call to queue_limits_commit_update_frozen() is preserved, that is, the queue limits lock is first taken by calling queue_limits_start_update() before freezing the queue, and the queue is unfrozen after executing queue_limits_commit_update(), which replaces the call to queue_limits_commit_update_frozen(). This change ensures that there are no in-flights I/Os when the zone resources are updated due to a zone revalidation. In case of error when the limits are applied, directly call disk_free_zone_resources() from disk_update_zone_resources() while the disk queue is still frozen to avoid needing to freeze & unfreeze the queue again in blk_revalidate_disk_zones(), thus simplifying that function code a little. Fixes: 0b83c86b444a ("block: Prevent potential deadlock in blk_revalidate_disk_zones()") Cc: stable@vger.kernel.org Signed-off-by: Damien Le Moal Reviewed-by: Christoph Hellwig Reviewed-by: Johannes Thumshirn Reviewed-by: Chaitanya Kulkarni Reviewed-by: Hannes Reinecke Reviewed-by: Martin K. Petersen Signed-off-by: Jens Axboe Signed-off-by: Greg Kroah-Hartman --- block/blk-zoned.c | 42 ++++++++++++++++++++++++------------------ 1 file changed, 24 insertions(+), 18 deletions(-) diff --git a/block/blk-zoned.c b/block/blk-zoned.c index 5e2a5788dc3b29..f60af668ab81e8 100644 --- a/block/blk-zoned.c +++ b/block/blk-zoned.c @@ -1516,8 +1516,13 @@ static int disk_update_zone_resources(struct gendisk *disk, { struct request_queue *q = disk->queue; unsigned int nr_seq_zones, nr_conv_zones; - unsigned int pool_size; + unsigned int pool_size, memflags; struct queue_limits lim; + int ret = 0; + + lim = queue_limits_start_update(q); + + memflags = blk_mq_freeze_queue(q); disk->nr_zones = args->nr_zones; disk->zone_capacity = args->zone_capacity; @@ -1527,11 +1532,10 @@ static int disk_update_zone_resources(struct gendisk *disk, if (nr_conv_zones >= disk->nr_zones) { pr_warn("%s: Invalid number of conventional zones %u / %u\n", disk->disk_name, nr_conv_zones, disk->nr_zones); - return -ENODEV; + ret = -ENODEV; + goto unfreeze; } - lim = queue_limits_start_update(q); - /* * Some devices can advertize zone resource limits that are larger than * the number of sequential zones of the zoned block device, e.g. a @@ -1568,7 +1572,15 @@ static int disk_update_zone_resources(struct gendisk *disk, } commit: - return queue_limits_commit_update_frozen(q, &lim); + ret = queue_limits_commit_update(q, &lim); + +unfreeze: + if (ret) + disk_free_zone_resources(disk); + + blk_mq_unfreeze_queue(q, memflags); + + return ret; } static int blk_revalidate_conv_zone(struct blk_zone *zone, unsigned int idx, @@ -1733,7 +1745,7 @@ int blk_revalidate_disk_zones(struct gendisk *disk) sector_t zone_sectors = q->limits.chunk_sectors; sector_t capacity = get_capacity(disk); struct blk_revalidate_zone_args args = { }; - unsigned int noio_flag; + unsigned int memflags, noio_flag; int ret = -ENOMEM; if (WARN_ON_ONCE(!blk_queue_is_zoned(q))) @@ -1783,20 +1795,14 @@ int blk_revalidate_disk_zones(struct gendisk *disk) ret = -ENODEV; } - /* - * Set the new disk zone parameters only once the queue is frozen and - * all I/Os are completed. - */ if (ret > 0) - ret = disk_update_zone_resources(disk, &args); - else - pr_warn("%s: failed to revalidate zones\n", disk->disk_name); - if (ret) { - unsigned int memflags = blk_mq_freeze_queue(q); + return disk_update_zone_resources(disk, &args); - disk_free_zone_resources(disk); - blk_mq_unfreeze_queue(q, memflags); - } + pr_warn("%s: failed to revalidate zones\n", disk->disk_name); + + memflags = blk_mq_freeze_queue(q); + disk_free_zone_resources(disk); + blk_mq_unfreeze_queue(q, memflags); return ret; } From f6c08d3aa441bbc1956e9d65f1cbb89113a5aa8a Mon Sep 17 00:00:00 2001 From: Johan Hovold Date: Mon, 20 Oct 2025 06:53:10 +0200 Subject: [PATCH 1733/3902] iommu/mediatek: fix use-after-free on probe deferral commit de83d4617f9fe059623e97acf7e1e10d209625b5 upstream. The driver is dropping the references taken to the larb devices during probe after successful lookup as well as on errors. This can potentially lead to a use-after-free in case a larb device has not yet been bound to its driver so that the iommu driver probe defers. Fix this by keeping the references as expected while the iommu driver is bound. Fixes: 26593928564c ("iommu/mediatek: Add error path for loop of mm_dts_parse") Cc: stable@vger.kernel.org Cc: Yong Wu Acked-by: Robin Murphy Signed-off-by: Johan Hovold Reviewed-by: Yong Wu Reviewed-by: AngeloGioacchino Del Regno Signed-off-by: Joerg Roedel Signed-off-by: Greg Kroah-Hartman --- drivers/iommu/mtk_iommu.c | 25 ++++++++++++++++++------- 1 file changed, 18 insertions(+), 7 deletions(-) diff --git a/drivers/iommu/mtk_iommu.c b/drivers/iommu/mtk_iommu.c index 0e0285348d2b8e..319974557f3258 100644 --- a/drivers/iommu/mtk_iommu.c +++ b/drivers/iommu/mtk_iommu.c @@ -1211,16 +1211,19 @@ static int mtk_iommu_mm_dts_parse(struct device *dev, struct component_match **m } component_match_add(dev, match, component_compare_dev, &plarbdev->dev); - platform_device_put(plarbdev); } - if (!frst_avail_smicomm_node) - return -EINVAL; + if (!frst_avail_smicomm_node) { + ret = -EINVAL; + goto err_larbdev_put; + } pcommdev = of_find_device_by_node(frst_avail_smicomm_node); of_node_put(frst_avail_smicomm_node); - if (!pcommdev) - return -ENODEV; + if (!pcommdev) { + ret = -ENODEV; + goto err_larbdev_put; + } data->smicomm_dev = &pcommdev->dev; link = device_link_add(data->smicomm_dev, dev, @@ -1228,7 +1231,8 @@ static int mtk_iommu_mm_dts_parse(struct device *dev, struct component_match **m platform_device_put(pcommdev); if (!link) { dev_err(dev, "Unable to link %s.\n", dev_name(data->smicomm_dev)); - return -EINVAL; + ret = -EINVAL; + goto err_larbdev_put; } return 0; @@ -1400,8 +1404,12 @@ static int mtk_iommu_probe(struct platform_device *pdev) iommu_device_sysfs_remove(&data->iommu); out_list_del: list_del(&data->list); - if (MTK_IOMMU_IS_TYPE(data->plat_data, MTK_IOMMU_TYPE_MM)) + if (MTK_IOMMU_IS_TYPE(data->plat_data, MTK_IOMMU_TYPE_MM)) { device_link_remove(data->smicomm_dev, dev); + + for (i = 0; i < MTK_LARB_NR_MAX; i++) + put_device(data->larb_imu[i].dev); + } out_runtime_disable: pm_runtime_disable(dev); return ret; @@ -1421,6 +1429,9 @@ static void mtk_iommu_remove(struct platform_device *pdev) if (MTK_IOMMU_IS_TYPE(data->plat_data, MTK_IOMMU_TYPE_MM)) { device_link_remove(data->smicomm_dev, &pdev->dev); component_master_del(&pdev->dev, &mtk_iommu_com_ops); + + for (i = 0; i < MTK_LARB_NR_MAX; i++) + put_device(data->larb_imu[i].dev); } pm_runtime_disable(&pdev->dev); for (i = 0; i < data->plat_data->banks_num; i++) { From a6d1f1ace16d0e777a85f84267160052d3499b6e Mon Sep 17 00:00:00 2001 From: Joanne Koong Date: Tue, 25 Nov 2025 10:13:47 -0800 Subject: [PATCH 1734/3902] fuse: fix io-uring list corruption for terminated non-committed requests commit 95c39eef7c2b666026c69ab5b30471da94ea2874 upstream. When a request is terminated before it has been committed, the request is not removed from the queue's list. This leaves a dangling list entry that leads to list corruption and use-after-free issues. Remove the request from the queue's list for terminated non-committed requests. Signed-off-by: Joanne Koong Fixes: c090c8abae4b ("fuse: Add io-uring sqe commit and fetch support") Cc: stable@vger.kernel.org Reviewed-by: Bernd Schubert Signed-off-by: Miklos Szeredi Signed-off-by: Greg Kroah-Hartman --- fs/fuse/dev_uring.c | 1 + 1 file changed, 1 insertion(+) diff --git a/fs/fuse/dev_uring.c b/fs/fuse/dev_uring.c index f6b12aebb8bbe7..d2bc05a8b3d109 100644 --- a/fs/fuse/dev_uring.c +++ b/fs/fuse/dev_uring.c @@ -86,6 +86,7 @@ static void fuse_uring_req_end(struct fuse_ring_ent *ent, struct fuse_req *req, lockdep_assert_not_held(&queue->lock); spin_lock(&queue->lock); ent->fuse_req = NULL; + list_del_init(&req->list); if (test_bit(FR_BACKGROUND, &req->flags)) { queue->active_background--; spin_lock(&fc->bg_lock); From e0d6de83a4cc22bbac72713f3a58121af36cc411 Mon Sep 17 00:00:00 2001 From: Joanne Koong Date: Fri, 10 Oct 2025 15:07:38 -0700 Subject: [PATCH 1735/3902] fuse: fix readahead reclaim deadlock commit bd5603eaae0aabf527bfb3ce1bb07e979ce5bd50 upstream. Commit e26ee4efbc79 ("fuse: allocate ff->release_args only if release is needed") skips allocating ff->release_args if the server does not implement open. However in doing so, fuse_prepare_release() now skips grabbing the reference on the inode, which makes it possible for an inode to be evicted from the dcache while there are inflight readahead requests. This causes a deadlock if the server triggers reclaim while servicing the readahead request and reclaim attempts to evict the inode of the file being read ahead. Since the folio is locked during readahead, when reclaim evicts the fuse inode and fuse_evict_inode() attempts to remove all folios associated with the inode from the page cache (truncate_inode_pages_range()), reclaim will block forever waiting for the lock since readahead cannot relinquish the lock because it is itself blocked in reclaim: >>> stack_trace(1504735) folio_wait_bit_common (mm/filemap.c:1308:4) folio_lock (./include/linux/pagemap.h:1052:3) truncate_inode_pages_range (mm/truncate.c:336:10) fuse_evict_inode (fs/fuse/inode.c:161:2) evict (fs/inode.c:704:3) dentry_unlink_inode (fs/dcache.c:412:3) __dentry_kill (fs/dcache.c:615:3) shrink_kill (fs/dcache.c:1060:12) shrink_dentry_list (fs/dcache.c:1087:3) prune_dcache_sb (fs/dcache.c:1168:2) super_cache_scan (fs/super.c:221:10) do_shrink_slab (mm/shrinker.c:435:9) shrink_slab (mm/shrinker.c:626:10) shrink_node (mm/vmscan.c:5951:2) shrink_zones (mm/vmscan.c:6195:3) do_try_to_free_pages (mm/vmscan.c:6257:3) do_swap_page (mm/memory.c:4136:11) handle_pte_fault (mm/memory.c:5562:10) handle_mm_fault (mm/memory.c:5870:9) do_user_addr_fault (arch/x86/mm/fault.c:1338:10) handle_page_fault (arch/x86/mm/fault.c:1481:3) exc_page_fault (arch/x86/mm/fault.c:1539:2) asm_exc_page_fault+0x22/0x27 Fix this deadlock by allocating ff->release_args and grabbing the reference on the inode when preparing the file for release even if the server does not implement open. The inode reference will be dropped when the last reference on the fuse file is dropped (see fuse_file_put() -> fuse_release_end()). Fixes: e26ee4efbc79 ("fuse: allocate ff->release_args only if release is needed") Cc: stable@vger.kernel.org Signed-off-by: Joanne Koong Reported-by: Omar Sandoval Signed-off-by: Miklos Szeredi Signed-off-by: Greg Kroah-Hartman --- fs/fuse/file.c | 26 +++++++++++++++++++------- 1 file changed, 19 insertions(+), 7 deletions(-) diff --git a/fs/fuse/file.c b/fs/fuse/file.c index bb4ecfd469a5e8..6014d588845cd6 100644 --- a/fs/fuse/file.c +++ b/fs/fuse/file.c @@ -110,7 +110,9 @@ static void fuse_file_put(struct fuse_file *ff, bool sync) fuse_file_io_release(ff, ra->inode); if (!args) { - /* Do nothing when server does not implement 'open' */ + /* Do nothing when server does not implement 'opendir' */ + } else if (args->opcode == FUSE_RELEASE && ff->fm->fc->no_open) { + fuse_release_end(ff->fm, args, 0); } else if (sync) { fuse_simple_request(ff->fm, args); fuse_release_end(ff->fm, args, 0); @@ -131,8 +133,17 @@ struct fuse_file *fuse_file_open(struct fuse_mount *fm, u64 nodeid, struct fuse_file *ff; int opcode = isdir ? FUSE_OPENDIR : FUSE_OPEN; bool open = isdir ? !fc->no_opendir : !fc->no_open; + bool release = !isdir || open; - ff = fuse_file_alloc(fm, open); + /* + * ff->args->release_args still needs to be allocated (so we can hold an + * inode reference while there are pending inflight file operations when + * ->release() is called, see fuse_prepare_release()) even if + * fc->no_open is set else it becomes possible for reclaim to deadlock + * if while servicing the readahead request the server triggers reclaim + * and reclaim evicts the inode of the file being read ahead. + */ + ff = fuse_file_alloc(fm, release); if (!ff) return ERR_PTR(-ENOMEM); @@ -152,13 +163,14 @@ struct fuse_file *fuse_file_open(struct fuse_mount *fm, u64 nodeid, fuse_file_free(ff); return ERR_PTR(err); } else { - /* No release needed */ - kfree(ff->args); - ff->args = NULL; - if (isdir) + if (isdir) { + /* No release needed */ + kfree(ff->args); + ff->args = NULL; fc->no_opendir = 1; - else + } else { fc->no_open = 1; + } } } From b79938863f436960eff209130f025c4bd3026bf8 Mon Sep 17 00:00:00 2001 From: Cheng Ding Date: Tue, 21 Oct 2025 22:46:42 +0200 Subject: [PATCH 1736/3902] fuse: missing copy_finish in fuse-over-io-uring argument copies commit 6e0d7f7f4a43ac8868e98c87ecf48805aa8c24dd upstream. Fix a possible reference count leak of payload pages during fuse argument copies. [Joanne: simplified error cleanup] Fixes: c090c8abae4b ("fuse: Add io-uring sqe commit and fetch support") Cc: stable@vger.kernel.org # v6.14 Signed-off-by: Cheng Ding Signed-off-by: Bernd Schubert Reviewed-by: Joanne Koong Signed-off-by: Miklos Szeredi Signed-off-by: Greg Kroah-Hartman --- fs/fuse/dev.c | 2 +- fs/fuse/dev_uring.c | 5 ++++- fs/fuse/fuse_dev_i.h | 1 + 3 files changed, 6 insertions(+), 2 deletions(-) diff --git a/fs/fuse/dev.c b/fs/fuse/dev.c index 132f38619d7072..49b18d7accb399 100644 --- a/fs/fuse/dev.c +++ b/fs/fuse/dev.c @@ -846,7 +846,7 @@ void fuse_copy_init(struct fuse_copy_state *cs, bool write, } /* Unmap and put previous page of userspace buffer */ -static void fuse_copy_finish(struct fuse_copy_state *cs) +void fuse_copy_finish(struct fuse_copy_state *cs) { if (cs->currbuf) { struct pipe_buffer *buf = cs->currbuf; diff --git a/fs/fuse/dev_uring.c b/fs/fuse/dev_uring.c index d2bc05a8b3d109..f0846694822ddf 100644 --- a/fs/fuse/dev_uring.c +++ b/fs/fuse/dev_uring.c @@ -599,7 +599,9 @@ static int fuse_uring_copy_from_ring(struct fuse_ring *ring, cs.is_uring = true; cs.req = req; - return fuse_copy_out_args(&cs, args, ring_in_out.payload_sz); + err = fuse_copy_out_args(&cs, args, ring_in_out.payload_sz); + fuse_copy_finish(&cs); + return err; } /* @@ -650,6 +652,7 @@ static int fuse_uring_args_to_ring(struct fuse_ring *ring, struct fuse_req *req, /* copy the payload */ err = fuse_copy_args(&cs, num_args, args->in_pages, (struct fuse_arg *)in_args, 0); + fuse_copy_finish(&cs); if (err) { pr_info_ratelimited("%s fuse_copy_args failed\n", __func__); return err; diff --git a/fs/fuse/fuse_dev_i.h b/fs/fuse/fuse_dev_i.h index 6e8373f970409e..134bf44aff0d39 100644 --- a/fs/fuse/fuse_dev_i.h +++ b/fs/fuse/fuse_dev_i.h @@ -62,6 +62,7 @@ void fuse_dev_end_requests(struct list_head *head); void fuse_copy_init(struct fuse_copy_state *cs, bool write, struct iov_iter *iter); +void fuse_copy_finish(struct fuse_copy_state *cs); int fuse_copy_args(struct fuse_copy_state *cs, unsigned int numargs, unsigned int argpages, struct fuse_arg *args, int zeroing); From a607c8f744340ad2c2486d46e96b66df47caffba Mon Sep 17 00:00:00 2001 From: Greg Kroah-Hartman Date: Fri, 2 Jan 2026 12:57:31 +0100 Subject: [PATCH 1737/3902] Linux 6.18.3 Link: https://lore.kernel.org/r/20251229160724.139406961@linuxfoundation.org Tested-by: Brett A C Sheffield Tested-by: Salvatore Bonaccorso Tested-by: Ronald Warsow Tested-by: Takeshi Ogasawara Tested-by: Ron Economos Tested-by: Peter Schneider Tested-by: Justin M. Forbes Tested-by: Linux Kernel Functional Testing Tested-by: Jeffrin Jose T Tested-by: Dileep Malepu dileep.debian@gmail.com Tested-by: Miguel Ojeda Tested-by: Florian Fainelli Tested-by: Jon Hunter Signed-off-by: Greg Kroah-Hartman --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index c2b5cfd2fce01b..17415fdcd358dc 100644 --- a/Makefile +++ b/Makefile @@ -1,7 +1,7 @@ # SPDX-License-Identifier: GPL-2.0 VERSION = 6 PATCHLEVEL = 18 -SUBLEVEL = 2 +SUBLEVEL = 3 EXTRAVERSION = NAME = Baby Opossum Posse From b6a8d6ba54f3bae1d61572988819d2953e63096b Mon Sep 17 00:00:00 2001 From: James Calligeros Date: Sat, 27 Dec 2025 16:22:14 +1000 Subject: [PATCH 1738/3902] drm: apple: Advertise ARGB2101010 support The full range packed 10-bit dcp format ("l10r") supports alpha so use that instead of the already slightly misused wide gamut format "w30r" to support DRM_FORMAT_ARGB2101010. Signed-off-by: James Calligeros Signed-off-by: Janne Grunau --- drivers/gpu/drm/apple/plane.c | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/drivers/gpu/drm/apple/plane.c b/drivers/gpu/drm/apple/plane.c index c37e0de550e14e..d3533ac577c30e 100644 --- a/drivers/gpu/drm/apple/plane.c +++ b/drivers/gpu/drm/apple/plane.c @@ -117,7 +117,8 @@ static u32 drm_format_to_dcp(u32 drm) return DCP_FORMAT_RGBA; case DRM_FORMAT_XRGB2101010: - return DCP_FORMAT_W30R; + case DRM_FORMAT_ARGB2101010: + return DCP_FORMAT_L10R; } pr_warn("DRM format %X not supported in DCP\n", drm); @@ -143,13 +144,15 @@ static void apple_plane_atomic_update(struct drm_plane *plane, } struct drm_framebuffer *fb = base->fb; + const struct drm_format_info *fmt = fb->format; /* - * DCP doesn't support XBGR8 / XRGB8 natively. Blending as + * DCP doesn't support XBGR8 / XRGB8 / XBGR2101010 natively. Blending as * pre-multiplied alpha with a black background can be used as * workaround for the bottommost plane. */ - if (fb->format->format == DRM_FORMAT_XRGB8888 || - fb->format->format == DRM_FORMAT_XBGR8888) + if (fmt->format == DRM_FORMAT_XRGB8888 || + fmt->format == DRM_FORMAT_XBGR8888 || + fmt->format == DRM_FORMAT_XBGR2101010) is_premultiplied = true; new_state->src_rect = drm_to_dcp_rect_fp(&base->src); @@ -239,6 +242,7 @@ static const struct drm_plane_funcs apple_plane_funcs = { */ static const u32 dcp_primary_formats[] = { DRM_FORMAT_XRGB2101010, + DRM_FORMAT_ARGB2101010, DRM_FORMAT_XRGB8888, DRM_FORMAT_ARGB8888, DRM_FORMAT_XBGR8888, @@ -246,6 +250,7 @@ static const u32 dcp_primary_formats[] = { }; static const u32 dcp_overlay_formats[] = { + DRM_FORMAT_ARGB2101010, DRM_FORMAT_ARGB8888, DRM_FORMAT_ABGR8888, }; From b31ecd3b75b35fbfbcea3c64fffb522bb7e25bf8 Mon Sep 17 00:00:00 2001 From: James Calligeros Date: Mon, 22 Dec 2025 19:30:47 +1000 Subject: [PATCH 1739/3902] drm: apple: Support YCbCr formats Support 8-bit YCbCr planar and semi-planar formats in 4:2:0, 4:2:2 and 4:4:4 sub-sampling with limited and full range. Use the signalled color space and transfer functions for YCbCr formats. DCP allows a unique colour space to be specified for each surface. The firmware then tonemaps this to the connected display's native colour space. KMS sets color_encoding and color_range only for YCbCr formats. Signed-off-by: James Calligeros Co-developed-by: Janne Grunau Signed-off-by: Janne Grunau --- drivers/gpu/drm/apple/iomfb.h | 11 --- drivers/gpu/drm/apple/iomfb_plane.h | 28 ++++++ drivers/gpu/drm/apple/plane.c | 136 +++++++++++++++++++++++++++- 3 files changed, 160 insertions(+), 15 deletions(-) diff --git a/drivers/gpu/drm/apple/iomfb.h b/drivers/gpu/drm/apple/iomfb.h index cc75129f2a9b42..d40870c8103a25 100644 --- a/drivers/gpu/drm/apple/iomfb.h +++ b/drivers/gpu/drm/apple/iomfb.h @@ -80,17 +80,6 @@ enum iomfb_property_id { /* We have 4 surfaces, but we can only ever blend two */ #define MAX_BLEND_SURFACES 2 -enum dcp_colorspace { - DCP_COLORSPACE_BG_SRGB = 0, - DCP_COLORSPACE_BG_BT2020 = 9, - DCP_COLORSPACE_NATIVE = 12, -}; - -enum dcp_xfer_func { - DCP_XFER_FUNC_SDR = 13, - DCP_XFER_FUNC_HDR = 16, -}; - struct dcp_iouserclient { /* Handle for the IOUserClient. macOS sets this to a kernel VA. */ u64 handle; diff --git a/drivers/gpu/drm/apple/iomfb_plane.h b/drivers/gpu/drm/apple/iomfb_plane.h index adf6916d375891..0701978200311a 100644 --- a/drivers/gpu/drm/apple/iomfb_plane.h +++ b/drivers/gpu/drm/apple/iomfb_plane.h @@ -19,7 +19,35 @@ #define DCP_FORMAT_W30R fourcc_code('r', '0', '3', 'w') // wide gamut packed 10-bit RGB without alpha #define DCP_FORMAT_L10R fourcc_code('r', '0', '1', 'l') // full range packed 10-bit RGB with alpha +#define DCP_FORMAT_420V fourcc_code('v', '0', '2', '4') // NV12 video range 2 plane 8-bit YCbCr +#define DCP_FORMAT_420F fourcc_code('f', '0', '2', '4') // NV12 full range 2 plane 8-bit YCbCr +#define DCP_FORMAT_422V fourcc_code('v', '2', '2', '4') // NV16 video range 2 plane 8-bit YCbCr +#define DCP_FORMAT_422F fourcc_code('f', '2', '2', '4') // NV16 full range 2 plane 8-bit YCbCr +#define DCP_FORMAT_444V fourcc_code('v', '4', '4', '4') // NV24 video range 2 plane 8-bit YCbCr +#define DCP_FORMAT_444F fourcc_code('f', '4', '4', '4') // NV24 full range 2 plane 8-bit YCbCr +#define DCP_FORMAT_X420 fourcc_code('0', '2', '4', 'x') // P010 video range 2 plane 10-bit YCbCR +#define DCP_FORMAT_X422 fourcc_code('2', '2', '4', 'x') // P210 video range 2 plane 10-bit YCbCR +#define DCP_FORMAT_X444 fourcc_code('4', '4', '4', 'x') // P410 video range 2 plane 10-bit YCbCR + +#define DCP_FORMAT_XF20 fourcc_code('0', '2', 'f', 'x') // P010 full range 2 plane 10-bit YCbCR +#define DCP_FORMAT_XF22 fourcc_code('2', '2', 'f', 'x') // P210 full range 2 plane 10-bit YCbCR +#define DCP_FORMAT_XF44 fourcc_code('4', '4', 'f', 'x') // P410 full range 2 plane 10-bit YCbCR + +enum dcp_colorspace { + DCP_COLORSPACE_BG_SRGB = 0, + DCP_COLORSPACE_BT601 = 1, + DCP_COLORSPACE_BT709 = 2, + DCP_COLORSPACE_BG_BT2020 = 9, + DCP_COLORSPACE_NATIVE = 12, +}; + +enum dcp_xfer_func { + DCP_XFER_FUNC_BT601 = 1, + DCP_XFER_FUNC_BT1886 = 2, + DCP_XFER_FUNC_SDR = 13, + DCP_XFER_FUNC_HDR = 16, +}; struct dcp_rect { u32 x; diff --git a/drivers/gpu/drm/apple/plane.c b/drivers/gpu/drm/apple/plane.c index d3533ac577c30e..df29b709600b9a 100644 --- a/drivers/gpu/drm/apple/plane.c +++ b/drivers/gpu/drm/apple/plane.c @@ -12,6 +12,7 @@ #include #include #include +#include #include #include @@ -81,6 +82,29 @@ static int apple_plane_atomic_check(struct drm_plane *plane, return -EINVAL; } + /* + * Pitches have to be 64-byte aligned. + */ + for (u32 i = 0; i < new_plane_state->fb->format->num_planes; i++) + if (new_plane_state->fb->pitches[i] & 63) + return -EINVAL; + + /* + * FIXME: dcp can currently only use multi-planar buffers using the same + * object for all planes. It has a mandatory iommu so it should + * be no problem to map multiple objects "linearly" into DCP + * virtual address space and calculate the offsets accordingly. + * Or maybe it can accept multiple BOs via the per plane field + * `base`. + */ + if (new_plane_state->fb->format->num_planes > 1) { + const struct drm_gem_object *first = new_plane_state->fb->obj[0]; + for (u32 i = 1; i < new_plane_state->fb->format->num_planes; i++) + if (new_plane_state->fb->obj[i] != NULL && + new_plane_state->fb->obj[i] != first) + return -EINVAL; + } + return 0; } @@ -105,8 +129,9 @@ static struct dcp_rect drm_to_dcp_rect_fp(const struct drm_rect *fp_rect) return drm_to_dcp_rect(&rect); } -static u32 drm_format_to_dcp(u32 drm) +static u32 drm_format_to_dcp(u32 drm, enum drm_color_range range) { + bool fr = range == DRM_COLOR_YCBCR_FULL_RANGE; switch (drm) { case DRM_FORMAT_XRGB8888: case DRM_FORMAT_ARGB8888: @@ -119,12 +144,67 @@ static u32 drm_format_to_dcp(u32 drm) case DRM_FORMAT_XRGB2101010: case DRM_FORMAT_ARGB2101010: return DCP_FORMAT_L10R; + + /* semi planar YCbCr formats, limited and full range */ + case DRM_FORMAT_NV12: + return fr ? DCP_FORMAT_420F : DCP_FORMAT_420V; + case DRM_FORMAT_NV16: + return fr ? DCP_FORMAT_422F : DCP_FORMAT_422V; + case DRM_FORMAT_NV24: + return fr ? DCP_FORMAT_444F : DCP_FORMAT_444V; + + /* semi planar 10-bit YCbCr formats, limited and full range */ + case DRM_FORMAT_P010: + return fr ? DCP_FORMAT_XF20 : DCP_FORMAT_X420; + case DRM_FORMAT_P210: + return fr ? DCP_FORMAT_XF22 : DCP_FORMAT_X422; + /* + * TODO: missing DRM fourcc for P410 + */ +#if defined(DRM_FORMAT_P410) + case DRM_FORMAT_P410: + return fr ? DCP_FORMAT_XF44 : DCP_FORMAT_X444; +#endif } pr_warn("DRM format %X not supported in DCP\n", drm); return 0; } +static enum dcp_xfer_func get_xfer_func(bool is_yuv, enum drm_color_encoding enc) +{ + if (!is_yuv) + return DCP_XFER_FUNC_SDR; + + switch (enc) { + case DRM_COLOR_YCBCR_BT601: + return DCP_XFER_FUNC_BT601; + case DRM_COLOR_YCBCR_BT709: + case DRM_COLOR_YCBCR_BT2020: + return DCP_XFER_FUNC_BT1886; + default: + return DCP_XFER_FUNC_SDR; + } +} + +static enum dcp_colorspace get_colorspace(bool is_yuv, + enum drm_color_encoding enc) +{ + if (!is_yuv) + return DCP_COLORSPACE_NATIVE; + + switch (enc) { + case DRM_COLOR_YCBCR_BT601: + return DCP_COLORSPACE_BT601; + case DRM_COLOR_YCBCR_BT709: + return DCP_COLORSPACE_BT709; + case DRM_COLOR_YCBCR_BT2020: + return DCP_COLORSPACE_BG_BT2020; + default: + return DCP_COLORSPACE_NATIVE; + } +} + static void apple_plane_atomic_update(struct drm_plane *plane, struct drm_atomic_state *state) { @@ -160,9 +240,11 @@ static void apple_plane_atomic_update(struct drm_plane *plane, new_state->surf = (struct dcp_surface){ .is_premultiplied = is_premultiplied, - .format = drm_format_to_dcp(fb->format->format), - .xfer_func = DCP_XFER_FUNC_SDR, - .colorspace = DCP_COLORSPACE_NATIVE, + .plane_cnt = fb->format->num_planes, + .plane_cnt2 = fb->format->num_planes, + .format = drm_format_to_dcp(fmt->format, base->color_range), + .xfer_func = get_xfer_func(fmt->is_yuv, base->color_encoding), + .colorspace = get_colorspace(fmt->is_yuv, base->color_encoding), .stride = fb->pitches[0], .width = fb->width, .height = fb->height, @@ -177,6 +259,30 @@ static void apple_plane_atomic_update(struct drm_plane *plane, .has_planes = 1, }; + /* Populate plane information for planar formats */ + struct dcp_surface *surf = &new_state->surf; + for (int i = 0; fb->format->num_planes && i < fb->format->num_planes; i++) { + u32 width = drm_format_info_plane_width(fb->format, fb->width, i); + u32 height = drm_format_info_plane_height(fb->format, fb->height, i); + u32 bh = drm_format_info_block_height(fb->format, i); + u32 bw = drm_format_info_block_width(fb->format, i); + + surf->planes[i] = (struct dcp_plane_info){ + .width = width, + .height = height, + .base = fb->offsets[i] - fb->offsets[0], + .offset = fb->offsets[i] - fb->offsets[0], + .stride = fb->pitches[i], + .size = height * fb->pitches[i], + .tile_size = bw * bh, + .tile_w = bw, + .tile_h = bh, + }; + + if (i > 0) + surf->buf_size += surf->planes[i].size; + } + /* the obvious helper call drm_fb_dma_get_gem_addr() adjusts * the address for source x/y offsets. Since IOMFB has a direct * support source position prefer that. @@ -247,12 +353,28 @@ static const u32 dcp_primary_formats[] = { DRM_FORMAT_ARGB8888, DRM_FORMAT_XBGR8888, DRM_FORMAT_ABGR8888, + DRM_FORMAT_NV12, + DRM_FORMAT_NV16, + DRM_FORMAT_NV24, + DRM_FORMAT_P010, + DRM_FORMAT_P210, +#if defined(DRM_FORMAT_P410) + DRM_FORMAT_P410, +#endif }; static const u32 dcp_overlay_formats[] = { DRM_FORMAT_ARGB2101010, DRM_FORMAT_ARGB8888, DRM_FORMAT_ABGR8888, + DRM_FORMAT_NV12, + DRM_FORMAT_NV16, + DRM_FORMAT_NV24, + DRM_FORMAT_P010, + DRM_FORMAT_P210, +#if defined(DRM_FORMAT_P410) + DRM_FORMAT_P410, +#endif }; u64 apple_format_modifiers[] = { @@ -291,6 +413,12 @@ struct drm_plane *apple_plane_init(struct drm_device *dev, if (IS_ERR(plane)) return ERR_PTR(PTR_ERR(plane)); + drm_plane_create_color_properties(&plane->base, + (1 << DRM_COLOR_ENCODING_MAX) - 1, + (1 << DRM_COLOR_RANGE_MAX) - 1, + DRM_COLOR_YCBCR_BT709, + DRM_COLOR_YCBCR_LIMITED_RANGE); + if (type == DRM_PLANE_TYPE_PRIMARY) drm_plane_helper_add(&plane->base, &apple_primary_plane_helper_funcs); else From 667ca85b1804c13aba63f09ea0f19252f6ed49c6 Mon Sep 17 00:00:00 2001 From: James Calligeros Date: Thu, 1 Jan 2026 16:18:52 +1000 Subject: [PATCH 1740/3902] drm: apple: Assume all RGB planes are sRGB DCP enables us to specify a colourspace and transfer function for each plane, and will automatically tonemap them to the connected display's native colourspace. It also has a fallback "NATIVE" colourspace that tells it to assume the input framebuffer has already been transformed correctly. We were previously using this fallback for all RGB framebuffers, however this is incorrect. By convention, userspace treats the default colourspace as sRGB. This is fine when a display is in sRGB mode, however modern displays almost always cover a wider colour gamut out of the box, This is true of the MacBook builtin displays, which have full DCI-P3 coverage. The result of passing through sRGB framebuffers as "native" is oversaturated colours and bloomy highlights. It is exceedingly rare for userspace to ever output RGB framebuffers in non-sRGB colourspaces unless HDR is enabled, which we currently do not support. Let's just tell DCP that all RGB framebuffers are sRGB until the per-plane colour management patches are merged, at which point we can revisit this logic to make it more accurate. Signed-off-by: James Calligeros --- drivers/gpu/drm/apple/plane.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/gpu/drm/apple/plane.c b/drivers/gpu/drm/apple/plane.c index df29b709600b9a..fac214f63090c8 100644 --- a/drivers/gpu/drm/apple/plane.c +++ b/drivers/gpu/drm/apple/plane.c @@ -191,7 +191,7 @@ static enum dcp_colorspace get_colorspace(bool is_yuv, enum drm_color_encoding enc) { if (!is_yuv) - return DCP_COLORSPACE_NATIVE; + return DCP_COLORSPACE_BG_SRGB; switch (enc) { case DRM_COLOR_YCBCR_BT601: From aaba47a32efde5f67b226f9e8f35e0154d16dc74 Mon Sep 17 00:00:00 2001 From: Sasha Finkelstein Date: Sat, 12 Jul 2025 23:18:42 +0200 Subject: [PATCH 1741/3902] rust: property: HACK? make as_raw() public Signed-off-by: Sasha Finkelstein --- rust/kernel/device/property.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rust/kernel/device/property.rs b/rust/kernel/device/property.rs index 3a332a8c53a9eb..71bbce339949ee 100644 --- a/rust/kernel/device/property.rs +++ b/rust/kernel/device/property.rs @@ -58,7 +58,7 @@ impl FwNode { } /// Obtain the raw `struct fwnode_handle *`. - pub(crate) fn as_raw(&self) -> *mut bindings::fwnode_handle { + pub fn as_raw(&self) -> *mut bindings::fwnode_handle { self.0.get() } From 7f807da1bb52defdd125fdef899d0d5823dd136b Mon Sep 17 00:00:00 2001 From: Sasha Finkelstein Date: Sat, 9 Nov 2024 18:27:59 +0100 Subject: [PATCH 1742/3902] rust: device: WIP(?): Make as_raw() public for AOP series Signed-off-by: Sasha Finkelstein Signed-off-by: Janne Grunau --- rust/kernel/device.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rust/kernel/device.rs b/rust/kernel/device.rs index 56aff98c3f274e..2dfcacd9a5a4a2 100644 --- a/rust/kernel/device.rs +++ b/rust/kernel/device.rs @@ -247,7 +247,7 @@ impl Device { impl Device { /// Obtain the raw `struct device *`. - pub(crate) fn as_raw(&self) -> *mut bindings::device { + pub fn as_raw(&self) -> *mut bindings::device { self.0.get() } From 926c0c050e7a4b407809ef042397eff750320890 Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Sun, 22 Jun 2025 16:47:44 +0200 Subject: [PATCH 1743/3902] rust: device: HACK? make parent() public Signed-off-by: Janne Grunau --- rust/kernel/device.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rust/kernel/device.rs b/rust/kernel/device.rs index 2dfcacd9a5a4a2..5f3e70d7b99194 100644 --- a/rust/kernel/device.rs +++ b/rust/kernel/device.rs @@ -253,7 +253,7 @@ impl Device { /// Returns a reference to the parent device, if any. #[cfg_attr(not(CONFIG_AUXILIARY_BUS), expect(dead_code))] - pub(crate) fn parent(&self) -> Option<&Device> { + pub fn parent(&self) -> Option<&Device> { // SAFETY: // - By the type invariant `self.as_raw()` is always valid. // - The parent device is only ever set at device creation. From c37e7577a130090adfdb3b9bf1adac705538e03d Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Thu, 28 Nov 2024 08:18:45 +0100 Subject: [PATCH 1744/3902] rust: bindgen: Make snd_dec_flac opaque At least with certain some rust / bindgen combinations compilation fails with: error[E0587]: type has conflicting packed and align representation hints --> /Transit/build/linux/rust/bindings/bindings_generated.rs:102244:1 | 102244 | pub struct snd_dec_flac { | ^^^^^^^^^^^^^^^^^^^^^^^ Signed-off-by: Janne Grunau --- rust/bindgen_parameters | 3 +++ 1 file changed, 3 insertions(+) diff --git a/rust/bindgen_parameters b/rust/bindgen_parameters index e13c6f9dd17b22..90b53f4f87c033 100644 --- a/rust/bindgen_parameters +++ b/rust/bindgen_parameters @@ -15,6 +15,9 @@ --opaque-type x86_msi_data --opaque-type x86_msi_addr_lo +# Packed types cannot have larger alignment than the maximal natural aligment of menbers +--opaque-type snd_dec_flac + # `try` is a reserved keyword since Rust 2018; solved in `bindgen` v0.59.2, # commit 2aed6b021680 ("context: Escape the try keyword properly"). --opaque-type kunit_try_catch From 0b1bdf39d3d0eb2dd04d67c56cdcb5ee85221047 Mon Sep 17 00:00:00 2001 From: Sasha Finkelstein Date: Sat, 9 Nov 2024 17:50:03 +0100 Subject: [PATCH 1745/3902] soc: apple: rtkit: Add apple_rtkit_has_endpoint() To be used by RTKit consumers to check if an endpoint is present and should be enabled. Signed-off-by: Sasha Finkelstein --- drivers/soc/apple/rtkit.c | 6 ++++++ include/linux/soc/apple/rtkit.h | 8 ++++++++ 2 files changed, 14 insertions(+) diff --git a/drivers/soc/apple/rtkit.c b/drivers/soc/apple/rtkit.c index b8d4da147d23f7..88ddf3c36dc059 100644 --- a/drivers/soc/apple/rtkit.c +++ b/drivers/soc/apple/rtkit.c @@ -639,6 +639,12 @@ int apple_rtkit_poll(struct apple_rtkit *rtk) } EXPORT_SYMBOL_GPL(apple_rtkit_poll); +bool apple_rtkit_has_endpoint(struct apple_rtkit *rtk, u8 ep) +{ + return test_bit(ep, rtk->endpoints); +} +EXPORT_SYMBOL_GPL(apple_rtkit_has_endpoint); + int apple_rtkit_start_ep(struct apple_rtkit *rtk, u8 endpoint) { u64 msg; diff --git a/include/linux/soc/apple/rtkit.h b/include/linux/soc/apple/rtkit.h index 736f530180179b..9f3d0985150326 100644 --- a/include/linux/soc/apple/rtkit.h +++ b/include/linux/soc/apple/rtkit.h @@ -172,4 +172,12 @@ int apple_rtkit_send_message(struct apple_rtkit *rtk, u8 ep, u64 message, */ int apple_rtkit_poll(struct apple_rtkit *rtk); +/* + * Checks if an endpoint with a given index exists + * + * @rtk: RTKit reference + * @ep: endpoint to check for + */ +bool apple_rtkit_has_endpoint(struct apple_rtkit *rtk, u8 ep); + #endif /* _LINUX_APPLE_RTKIT_H_ */ From 63f4c0da0b343e7658151bcd4c030f1a7195a09d Mon Sep 17 00:00:00 2001 From: Sasha Finkelstein Date: Sat, 9 Nov 2024 18:01:54 +0100 Subject: [PATCH 1746/3902] rust: soc: apple: rtkit: Add apple_rtkit_has_endpoint Signed-off-by: Janne Grunau --- rust/kernel/soc/apple/rtkit.rs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/rust/kernel/soc/apple/rtkit.rs b/rust/kernel/soc/apple/rtkit.rs index a5c2a949803e12..c5d6e3d4b6c4d9 100644 --- a/rust/kernel/soc/apple/rtkit.rs +++ b/rust/kernel/soc/apple/rtkit.rs @@ -259,6 +259,11 @@ impl RtKit { bindings::apple_rtkit_send_message(self.rtk, endpoint, message, ptr::null_mut(), false) }) } + + /// Checks if an endpoint is present + pub fn has_endpoint(&self, endpoint: u8) -> bool { + unsafe { bindings::apple_rtkit_has_endpoint(self.rtk, endpoint) } + } } // SAFETY: `RtKit` operations require a mutable reference From b0db169253c2ff042e8ca4a484decb3ff739fc5c Mon Sep 17 00:00:00 2001 From: Sasha Finkelstein Date: Sat, 9 Nov 2024 18:27:59 +0100 Subject: [PATCH 1747/3902] rust: bindings: WIP(?): Add sound bits for AOP series Signed-off-by: Sasha Finkelstein Signed-off-by: Janne Grunau --- rust/bindgen_parameters | 3 +++ rust/bindings/bindings_helper.h | 5 +++++ 2 files changed, 8 insertions(+) diff --git a/rust/bindgen_parameters b/rust/bindgen_parameters index 90b53f4f87c033..8d84acb7a15be7 100644 --- a/rust/bindgen_parameters +++ b/rust/bindgen_parameters @@ -12,6 +12,9 @@ # Packed type cannot transitively contain a `#[repr(align)]` type. --opaque-type alt_instr +--opaque-type snd_codec_options +--opaque-type snd_codec +--opaque-type snd_compr_params --opaque-type x86_msi_data --opaque-type x86_msi_addr_lo diff --git a/rust/bindings/bindings_helper.h b/rust/bindings/bindings_helper.h index b97b86ef6d098c..de205a612ba1be 100644 --- a/rust/bindings/bindings_helper.h +++ b/rust/bindings/bindings_helper.h @@ -87,6 +87,9 @@ #include #include #include +#include +#include +#include #include #if defined(CONFIG_DRM_PANIC_SCREEN_QR_CODE) @@ -108,6 +111,8 @@ const gfp_t RUST_CONST_HELPER___GFP_NOWARN = ___GFP_NOWARN; const blk_features_t RUST_CONST_HELPER_BLK_FEAT_ROTATIONAL = BLK_FEAT_ROTATIONAL; const fop_flags_t RUST_CONST_HELPER_FOP_UNSIGNED_OFFSET = FOP_UNSIGNED_OFFSET; +const u64 BINDINGS_SNDRV_PCM_FMTBIT_FLOAT_LE = SNDRV_PCM_FMTBIT_FLOAT_LE; + const xa_mark_t RUST_CONST_HELPER_XA_PRESENT = XA_PRESENT; const gfp_t RUST_CONST_HELPER_XA_FLAGS_ALLOC = XA_FLAGS_ALLOC; From 402026657e88dfec46827d73c578e6c52a9e5911 Mon Sep 17 00:00:00 2001 From: Sasha Finkelstein Date: Sat, 9 Nov 2024 18:27:59 +0100 Subject: [PATCH 1748/3902] rust: bindings: WIP(?): Add IIO bits for AOP series Signed-off-by: Sasha Finkelstein Signed-off-by: Janne Grunau --- rust/bindings/bindings_helper.h | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/rust/bindings/bindings_helper.h b/rust/bindings/bindings_helper.h index de205a612ba1be..3a99dd2962ef8e 100644 --- a/rust/bindings/bindings_helper.h +++ b/rust/bindings/bindings_helper.h @@ -58,6 +58,8 @@ #include #include #include +#include +#include #include #include #include @@ -113,6 +115,11 @@ const fop_flags_t RUST_CONST_HELPER_FOP_UNSIGNED_OFFSET = FOP_UNSIGNED_OFFSET; const u64 BINDINGS_SNDRV_PCM_FMTBIT_FLOAT_LE = SNDRV_PCM_FMTBIT_FLOAT_LE; +const u32 BINDINGS_IIO_CHAN_INFO_RAW = IIO_CHAN_INFO_RAW; +const u32 BINDINGS_IIO_CHAN_INFO_PROCESSED = IIO_CHAN_INFO_PROCESSED; +const u32 BINDINGS_IIO_ANGL = IIO_ANGL; +const u32 BINDINGS_IIO_LIGHT = IIO_LIGHT; + const xa_mark_t RUST_CONST_HELPER_XA_PRESENT = XA_PRESENT; const gfp_t RUST_CONST_HELPER_XA_FLAGS_ALLOC = XA_FLAGS_ALLOC; From 4ac3849966fb1a5633f9f11e0acaaf2368e4f8b4 Mon Sep 17 00:00:00 2001 From: Sasha Finkelstein Date: Sat, 9 Nov 2024 18:27:59 +0100 Subject: [PATCH 1749/3902] rust: device: WIP(?): Add get_drvdata for AOP series Signed-off-by: Sasha Finkelstein Signed-off-by: Janne Grunau --- rust/kernel/device.rs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/rust/kernel/device.rs b/rust/kernel/device.rs index 5f3e70d7b99194..b6635a0d617ed7 100644 --- a/rust/kernel/device.rs +++ b/rust/kernel/device.rs @@ -270,6 +270,13 @@ impl Device { } } + /// Returns the driver_data pointer. + pub fn get_drvdata(&self) -> *mut T { + // SAFETY: dev_get_drvdata returns a field of the device, + // pointer to which is valid by type invariant + unsafe { bindings::dev_get_drvdata(self.as_raw()) as *mut T } + } + /// Convert a raw C `struct device` pointer to a `&'a Device`. /// /// # Safety From 233c1d25ff5924b8a5da0722acb5515c9df769c7 Mon Sep 17 00:00:00 2001 From: Sasha Finkelstein Date: Sat, 9 Nov 2024 18:27:59 +0100 Subject: [PATCH 1750/3902] rust: alloc: kvec: WIP(?): Add swap_remove() for AOP series Signed-off-by: Sasha Finkelstein Signed-off-by: Janne Grunau --- rust/kernel/alloc/kvec.rs | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/rust/kernel/alloc/kvec.rs b/rust/kernel/alloc/kvec.rs index 3802874ce153fb..6cc48330515dd1 100644 --- a/rust/kernel/alloc/kvec.rs +++ b/rust/kernel/alloc/kvec.rs @@ -799,6 +799,26 @@ where } } } + /// Removes an element from the vector and returns it. + /// + /// The removed element is replaced by the last element of the vector. + /// + /// This does not preserve ordering of the remaining elements, but is *O*(1). + /// If you need to preserve the element order, use [`remove`] instead. + pub fn swap_remove(&mut self, index: usize) -> T { + if index > self.len() { + panic!("Index out of range"); + } + // SAFETY: index is in range + // self.len() - 1 is in range since at last 1 element exists + unsafe { + let old = ptr::read(self.as_ptr().add(index)); + let last = ptr::read(self.as_ptr().add(self.len() - 1)); + ptr::write(self.as_mut_ptr().add(index), last); + self.dec_len(1); + old + } + } } impl Vec { From 6c417d1da85d18d4bc0ed19ebb0131f746e770dc Mon Sep 17 00:00:00 2001 From: Sasha Finkelstein Date: Sat, 9 Nov 2024 18:31:23 +0100 Subject: [PATCH 1751/3902] soc: apple: Add support for the AOP co-processor This is the base device for a multi-function co-processor present on certain Apple SoCs. On M-series Macs it is in charge of internal microphones, and various environmental sensors. Signed-off-by: Sasha Finkelstein Signed-off-by: Janne Grunau --- drivers/soc/apple/Kconfig | 12 + drivers/soc/apple/Makefile | 2 + drivers/soc/apple/aop.rs | 943 +++++++++++++++++++++++++++++++++++ rust/kernel/soc/apple/aop.rs | 42 ++ rust/kernel/soc/apple/mod.rs | 3 + 5 files changed, 1002 insertions(+) create mode 100644 drivers/soc/apple/aop.rs create mode 100644 rust/kernel/soc/apple/aop.rs diff --git a/drivers/soc/apple/Kconfig b/drivers/soc/apple/Kconfig index 662da18b4e3595..fac8fa57335e12 100644 --- a/drivers/soc/apple/Kconfig +++ b/drivers/soc/apple/Kconfig @@ -44,6 +44,18 @@ config RUST_APPLE_RTKIT depends on RUST select APPLE_RTKIT +config APPLE_AOP + tristate "Apple \"Always-on\" Processor" + depends on ARCH_APPLE || COMPILE_TEST + depends on PM + depends on RUST + select RUST_APPLE_RTKIT + help + A co-processor persent on certain Apple SoCs controlling accelerometers, + gyros, ambient light sensors and microphones. Is not actually always on. + + Say 'y' here if you have an Apple laptop. + endmenu endif diff --git a/drivers/soc/apple/Makefile b/drivers/soc/apple/Makefile index 4d9ab8f3037b71..17af8e2b82d298 100644 --- a/drivers/soc/apple/Makefile +++ b/drivers/soc/apple/Makefile @@ -8,3 +8,5 @@ apple-rtkit-y = rtkit.o rtkit-crashlog.o obj-$(CONFIG_APPLE_SART) += apple-sart.o apple-sart-y = sart.o + +obj-$(CONFIG_APPLE_AOP) += aop.o diff --git a/drivers/soc/apple/aop.rs b/drivers/soc/apple/aop.rs new file mode 100644 index 00000000000000..2e698d85373a76 --- /dev/null +++ b/drivers/soc/apple/aop.rs @@ -0,0 +1,943 @@ +// SPDX-License-Identifier: GPL-2.0-only OR MIT +#![recursion_limit = "2048"] + +//! Apple AOP driver +//! +//! Copyright (C) The Asahi Linux Contributors + +use core::{arch::asm, mem, ptr, slice}; + +use kernel::{ + bindings, c_str, device, + device::Core, + dma::{CoherentAllocation, Device, DmaMask}, + error::from_err_ptr, + io::mem::IoMem, + module_platform_driver, new_condvar, new_mutex, of, platform, + prelude::*, + soc::apple::aop::{from_fourcc, EPICService, FakehidListener, AOP}, + soc::apple::rtkit, + sync::{Arc, ArcBorrow, CondVar, Mutex}, + types::{ARef, ForeignOwnable}, + workqueue::{self, impl_has_work, new_work, Work, WorkItem}, +}; + +const AOP_MMIO_SIZE: usize = 0x1e0000; +const ASC_MMIO_SIZE: usize = 0x4000; +const BOOTARGS_OFFSET: usize = 0x22c; +const BOOTARGS_SIZE: usize = 0x230; +const CPU_CONTROL: usize = 0x44; +const CPU_RUN: u32 = 0x1 << 4; +const AFK_ENDPOINT_START: u8 = 0x20; +const AFK_ENDPOINT_COUNT: u8 = 0xf; +const AFK_OPC_GET_BUF: u64 = 0x89; +const AFK_OPC_INIT: u64 = 0x80; +const AFK_OPC_INIT_RX: u64 = 0x8b; +const AFK_OPC_INIT_TX: u64 = 0x8a; +const AFK_OPC_INIT_UNK: u64 = 0x8c; +const AFK_OPC_SEND: u64 = 0xa2; +const AFK_OPC_START_ACK: u64 = 0x86; +const AFK_OPC_SHUTDOWN_ACK: u64 = 0xc1; +const AFK_OPC_RECV: u64 = 0x85; +const AFK_MSG_GET_BUF_ACK: u64 = 0xa1 << 48; +const AFK_MSG_INIT: u64 = AFK_OPC_INIT << 48; +const AFK_MSG_INIT_ACK: u64 = 0xa0 << 48; +const AFK_MSG_START: u64 = 0xa3 << 48; +const AFK_MSG_SHUTDOWN: u64 = 0xc0 << 48; +const AFK_RB_BLOCK_STEP: usize = 0x40; +const EPIC_TYPE_NOTIFY: u32 = 0; +const EPIC_CATEGORY_REPORT: u8 = 0x00; +const EPIC_CATEGORY_NOTIFY: u8 = 0x10; +const EPIC_CATEGORY_REPLY: u8 = 0x20; +const EPIC_SUBTYPE_STD_SERVICE: u16 = 0xc0; +const EPIC_SUBTYPE_FAKEHID_REPORT: u16 = 0xc4; +const EPIC_SUBTYPE_RETCODE: u16 = 0x84; +const EPIC_SUBTYPE_RETCODE_PAYLOAD: u16 = 0xa0; +const QE_MAGIC1: u32 = from_fourcc(b" POI"); +const QE_MAGIC2: u32 = from_fourcc(b" POA"); + +fn align_up(v: usize, a: usize) -> usize { + (v + a - 1) & !(a - 1) +} + +#[inline(always)] +fn mem_sync() { + unsafe { + asm!("dsb sy"); + } +} + +#[repr(C, packed)] +#[derive(Clone, Copy, Default)] +struct QEHeader { + magic: u32, + size: u32, + channel: u32, + ty: u32, +} + +#[repr(C, packed)] +#[derive(Clone, Copy, Default)] +struct EPICHeader { + version: u8, + seq: u16, + _pad0: u8, + _unk0: u32, + timestamp: u64, + // Subheader + length: u32, + sub_version: u8, + category: u8, + subtype: u16, + tag: u16, + _unk1: u16, + _pad1: u64, + inline_len: u32, +} + +#[repr(C, packed)] +struct EPICServiceAnnounce { + name: [u8; 20], + _unk0: u32, + retcode: u32, + _unk1: u32, + channel: u32, + _unk2: u32, + _unk3: u32, +} + +#[pin_data] +struct FutureValue { + #[pin] + val: Mutex>, + #[pin] + completion: CondVar, +} + +impl FutureValue { + fn pin_init() -> impl PinInit> { + pin_init!( + FutureValue { + val <- new_mutex!(None), + completion <- new_condvar!() + } + ) + } + fn complete(&self, val: T) { + *self.val.lock() = Some(val); + self.completion.notify_all(); + } + fn wait(&self) -> T { + let mut ret_guard = self.val.lock(); + while ret_guard.is_none() { + self.completion.wait(&mut ret_guard); + } + ret_guard.as_ref().unwrap().clone() + } + fn reset(&self) { + *self.val.lock() = None; + } +} + +struct AFKRingBuffer { + offset: usize, + block_size: usize, + buf_size: usize, +} + +struct AFKEndpoint { + index: u8, + iomem: Option>, + txbuf: Option, + rxbuf: Option, + seq: u16, + calls: [Option>>; 8], +} + +unsafe impl Send for AFKEndpoint {} + +impl AFKEndpoint { + fn new(index: u8) -> AFKEndpoint { + AFKEndpoint { + index, + iomem: None, + txbuf: None, + rxbuf: None, + seq: 0, + calls: [const { None }; 8], + } + } + + fn start(&self, rtkit: &mut rtkit::RtKit) -> Result<()> { + rtkit.send_message(self.index, AFK_MSG_INIT) + } + + fn stop(&self, rtkit: &mut rtkit::RtKit) -> Result<()> { + rtkit.send_message(self.index, AFK_MSG_SHUTDOWN) + } + + fn recv_message( + &mut self, + client: ArcBorrow<'_, AopData>, + rtkit: &mut rtkit::RtKit, + msg: u64, + ) -> Result<()> { + let opc = msg >> 48; + match opc { + AFK_OPC_INIT => { + rtkit.send_message(self.index, AFK_MSG_INIT_ACK)?; + } + AFK_OPC_GET_BUF => { + self.recv_get_buf(client.dev.clone(), rtkit, msg)?; + } + AFK_OPC_INIT_UNK => {} // no-op + AFK_OPC_START_ACK => {} + AFK_OPC_INIT_RX => { + if self.rxbuf.is_some() { + dev_err!( + client.dev, + "Got InitRX message with existing rxbuf at endpoint {}", + self.index + ); + return Err(EIO); + } + self.rxbuf = Some(self.parse_ring_buf(msg)?); + if self.txbuf.is_some() { + rtkit.send_message(self.index, AFK_MSG_START)?; + } + } + AFK_OPC_INIT_TX => { + if self.txbuf.is_some() { + dev_err!( + client.dev, + "Got InitTX message with existing txbuf at endpoint {}", + self.index + ); + return Err(EIO); + } + self.txbuf = Some(self.parse_ring_buf(msg)?); + if self.rxbuf.is_some() { + rtkit.send_message(self.index, AFK_MSG_START)?; + } + } + AFK_OPC_RECV => { + self.recv_rb(client)?; + } + AFK_OPC_SHUTDOWN_ACK => { + client.shutdown_complete(); + } + _ => dev_err!( + client.dev, + "AFK endpoint {} got unknown message {}", + self.index, + msg + ), + } + Ok(()) + } + + fn parse_ring_buf(&self, msg: u64) -> Result { + let msg = msg as usize; + let size = ((msg >> 16) & 0xFFFF) * AFK_RB_BLOCK_STEP; + let offset = ((msg >> 32) & 0xFFFF) * AFK_RB_BLOCK_STEP; + let buf_size = self.iomem_read32(offset)? as usize; + let block_size = (size - buf_size) / 3; + Ok(AFKRingBuffer { + offset, + block_size, + buf_size, + }) + } + fn iomem_write32(&mut self, off: usize, data: u32) -> Result<()> { + let size = core::mem::size_of::(); + let data = data.to_le_bytes(); + let buf = unsafe { self.iomem.as_mut().ok_or(ENXIO)?.as_slice_mut(off, size)? }; + buf.copy_from_slice(&data); + Ok(()) + } + + fn iomem_read32(&self, off: usize) -> Result { + let size = core::mem::size_of::(); + let buf = unsafe { self.iomem.as_ref().ok_or(ENXIO)?.as_slice(off, size)? }; + Ok(u32::from_le_bytes(buf.try_into().unwrap())) + } + + fn memcpy_from_iomem(&self, off: usize, target: &mut [u8]) -> Result<()> { + // SAFETY: + // as_slice() checks that off and target.len() are whithin iomem's limits. + unsafe { + let src = self + .iomem + .as_ref() + .ok_or(ENXIO)? + .as_slice(off, target.len())?; + target.copy_from_slice(src); + } + Ok(()) + } + + fn memcpy_to_iomem(&mut self, off: usize, src: &[u8]) -> Result<()> { + // SAFETY: + // as_slice_mut() checks that off and src.len() are whithin iomem's limits. + unsafe { + let target = self + .iomem + .as_mut() + .ok_or(ENXIO)? + .as_slice_mut(off, src.len())?; + target.copy_from_slice(src); + } + Ok(()) + } + + fn recv_get_buf( + &mut self, + dev: ARef, + rtkit: &mut rtkit::RtKit, + msg: u64, + ) -> Result<()> { + let size = ((msg & 0xFFFF0000) >> 16) as usize * AFK_RB_BLOCK_STEP; + if self.iomem.is_some() { + dev_err!( + dev, + "Got GetBuf message with existing buffer on endpoint {}", + self.index + ); + return Err(EIO); + } + let iomem = dev.while_bound_with(|bound_dev| { + CoherentAllocation::::alloc_coherent(bound_dev, size, GFP_KERNEL) + })?; + rtkit.send_message(self.index, AFK_MSG_GET_BUF_ACK | iomem.dma_handle())?; + self.iomem = Some(iomem); + Ok(()) + } + + fn recv_rb(&mut self, client: ArcBorrow<'_, AopData>) -> Result<()> { + let (buf_offset, block_size, buf_size) = match self.rxbuf.as_ref() { + Some(b) => (b.offset, b.block_size, b.buf_size), + None => { + dev_err!( + client.dev, + "Got Recv message with no rxbuf at endpoint {}", + self.index + ); + return Err(EIO); + } + }; + let mut rptr = self.iomem_read32(buf_offset + block_size)? as usize; + let mut wptr = self.iomem_read32(buf_offset + block_size * 2)?; + mem_sync(); + let base = buf_offset + block_size * 3; + let mut msg_buf = KVec::new(); + const QEH_SIZE: usize = mem::size_of::(); + while wptr as usize != rptr { + let mut qeh_bytes = [0; QEH_SIZE]; + self.memcpy_from_iomem(base + rptr, &mut qeh_bytes)?; + let mut qeh = unsafe { &*(qeh_bytes.as_ptr() as *const QEHeader) }; + if qeh.magic != QE_MAGIC1 && qeh.magic != QE_MAGIC2 { + let magic = qeh.magic; + dev_err!( + client.dev, + "Invalid magic on ep {}, got {:x}", + self.index, + magic + ); + return Err(EIO); + } + if qeh.size as usize > (buf_size - rptr - QEH_SIZE) { + rptr = 0; + self.memcpy_from_iomem(base + rptr, &mut qeh_bytes)?; + qeh = unsafe { &*(qeh_bytes.as_ptr() as *const QEHeader) }; + + if qeh.magic != QE_MAGIC1 && qeh.magic != QE_MAGIC2 { + let magic = qeh.magic; + dev_err!( + client.dev, + "Invalid magic on ep {}, got {:x}", + self.index, + magic + ); + return Err(EIO); + } + } + msg_buf.resize(qeh.size as usize, 0, GFP_KERNEL)?; + self.memcpy_from_iomem(base + rptr + QEH_SIZE, &mut msg_buf)?; + let (hdr_bytes, msg) = msg_buf.split_at(mem::size_of::()); + let header = unsafe { &*(hdr_bytes.as_ptr() as *const EPICHeader) }; + self.handle_ipc(client, qeh, header, msg)?; + rptr = align_up(rptr + QEH_SIZE + qeh.size as usize, block_size) % buf_size; + mem_sync(); + self.iomem_write32(buf_offset + block_size, rptr as u32)?; + wptr = self.iomem_read32(buf_offset + block_size * 2)?; + mem_sync(); + } + Ok(()) + } + fn handle_ipc( + &mut self, + client: ArcBorrow<'_, AopData>, + qhdr: &QEHeader, + ehdr: &EPICHeader, + data: &[u8], + ) -> Result<()> { + let subtype = ehdr.subtype; + if ehdr.category == EPIC_CATEGORY_REPORT { + if subtype == EPIC_SUBTYPE_STD_SERVICE { + let announce = unsafe { &*(data.as_ptr() as *const EPICServiceAnnounce) }; + let chan = announce.channel; + let name_len = announce + .name + .iter() + .position(|x| *x == 0) + .unwrap_or(announce.name.len()); + return Into::>::into(client).register_service( + self, + chan, + &announce.name[..name_len], + ); + } else if subtype == EPIC_SUBTYPE_FAKEHID_REPORT { + return client.process_fakehid_report(self, qhdr.channel, data); + } else { + dev_err!( + client.dev, + "Unexpected EPIC report subtype {:x} on endpoint {}", + subtype, + self.index + ); + return Err(EIO); + } + } else if ehdr.category == EPIC_CATEGORY_REPLY { + if subtype == EPIC_SUBTYPE_RETCODE_PAYLOAD || subtype == EPIC_SUBTYPE_RETCODE { + if data.len() < mem::size_of::() { + dev_err!( + client.dev, + "Retcode data too short on endpoint {}", + self.index + ); + return Err(EIO); + } + let retcode = u32::from_ne_bytes(data[..4].try_into().unwrap()); + let tag = ehdr.tag as usize; + if tag == 0 || tag - 1 > self.calls.len() || self.calls[tag - 1].is_none() { + dev_err!( + client.dev, + "Got a retcode with invalid tag {:?} on endpoint {}", + tag, + self.index + ); + return Err(EIO); + } + self.calls[tag - 1].take().unwrap().complete(retcode); + return Ok(()); + } else { + dev_err!( + client.dev, + "Unexpected EPIC reply subtype {:x} on endpoint {}", + subtype, + self.index + ); + return Err(EIO); + } + } + dev_err!( + client.dev, + "Unexpected EPIC category {:x} on endpoint {}", + ehdr.category, + self.index + ); + Err(EIO) + } + fn send_rb( + &mut self, + client: &AopData, + rtkit: &mut rtkit::RtKit, + channel: u32, + ty: u32, + header: &[u8], + data: &[u8], + ) -> Result<()> { + let (buf_offset, block_size, buf_size) = match self.txbuf.as_ref() { + Some(b) => (b.offset, b.block_size, b.buf_size), + None => { + dev_err!( + client.dev, + "Attempting to send message with no txbuf at endpoint {}", + self.index + ); + return Err(EIO); + } + }; + let base = buf_offset + block_size * 3; + mem_sync(); + let rptr = self.iomem_read32(buf_offset + block_size)? as usize; + let mut wptr = self.iomem_read32(buf_offset + block_size * 2)? as usize; + const QEH_SIZE: usize = mem::size_of::(); + if wptr < rptr && wptr + QEH_SIZE >= rptr { + dev_err!(client.dev, "Tx buffer full at endpoint {}", self.index); + return Err(EIO); + } + let payload_len = header.len() + data.len(); + let qeh = QEHeader { + magic: QE_MAGIC1, + size: payload_len as u32, + channel, + ty, + }; + let qeh_bytes = unsafe { + slice::from_raw_parts( + &qeh as *const QEHeader as *const u8, + mem::size_of::(), + ) + }; + self.memcpy_to_iomem(base + wptr, qeh_bytes)?; + if payload_len > buf_size - wptr - QEH_SIZE { + wptr = 0; + self.memcpy_to_iomem(base + wptr, qeh_bytes)?; + } + self.memcpy_to_iomem(base + wptr + QEH_SIZE, header)?; + self.memcpy_to_iomem(base + wptr + QEH_SIZE + header.len(), data)?; + wptr = align_up(wptr + QEH_SIZE + payload_len, block_size) % buf_size; + self.iomem_write32(buf_offset + block_size * 2, wptr as u32)?; + let msg = wptr as u64 | (AFK_OPC_SEND << 48); + rtkit.send_message(self.index, msg) + } + fn epic_notify( + &mut self, + client: &AopData, + rtkit: &mut rtkit::RtKit, + channel: u32, + subtype: u16, + data: &[u8], + ) -> Result>> { + let mut tag = 0; + for i in 0..self.calls.len() { + if self.calls[i].is_none() { + tag = i + 1; + break; + } + } + if tag == 0 { + dev_err!( + client.dev, + "Too many inflight calls on endpoint {}", + self.index + ); + return Err(EIO); + } + let call = Arc::pin_init(FutureValue::pin_init(), GFP_KERNEL)?; + let hdr = EPICHeader { + version: 2, + seq: self.seq, + length: data.len() as u32, + sub_version: 2, + category: EPIC_CATEGORY_NOTIFY, + subtype, + tag: tag as u16, + ..EPICHeader::default() + }; + self.send_rb( + client, + rtkit, + channel, + EPIC_TYPE_NOTIFY, + unsafe { + slice::from_raw_parts( + &hdr as *const EPICHeader as *const u8, + mem::size_of::(), + ) + }, + data, + )?; + self.seq = self.seq.wrapping_add(1); + self.calls[tag - 1] = Some(call.clone()); + Ok(call) + } +} + +struct ListenerEntry { + svc: EPICService, + listener: Arc, +} + +unsafe impl Send for ListenerEntry {} + +#[pin_data] +struct AopData { + dev: ARef, + #[pin] + rtkit: Mutex>>, + #[pin] + endpoints: [Mutex; AFK_ENDPOINT_COUNT as usize], + #[pin] + ep_shutdown: FutureValue<()>, + #[pin] + hid_listeners: Mutex>, + #[pin] + subdevices: Mutex>, +} + +unsafe impl Send for AopData {} +unsafe impl Sync for AopData {} + +#[pin_data] +struct AopServiceRegisterWork { + name: &'static CStr, + data: Arc, + service: EPICService, + #[pin] + work: Work, +} + +impl_has_work! { + impl HasWork for AopServiceRegisterWork { self.work } +} + +impl AopServiceRegisterWork { + fn new( + name: &'static CStr, + data: Arc, + service: EPICService, + ) -> Result>> { + KBox::pin_init( + pin_init!(AopServiceRegisterWork { + name, data, service, + work <- new_work!("AopServiceRegisterWork::work"), + }), + GFP_KERNEL, + ) + } +} + +impl WorkItem for AopServiceRegisterWork { + type Pointer = Pin>; + + fn run(this: Pin>) { + let fwnode = this + .data + .dev + .fwnode() + .and_then(|x| x.get_child_by_name(this.name)); + let info = bindings::platform_device_info { + parent: this.data.dev.as_raw(), + name: this.name.as_ptr() as *const _, + id: bindings::PLATFORM_DEVID_AUTO, + res: ptr::null_mut(), + num_res: 0, + data: &this.service as *const EPICService as *const _, + size_data: mem::size_of::(), + dma_mask: 0, + fwnode: fwnode.map(|x| x.as_raw()).unwrap_or(ptr::null_mut()), + properties: ptr::null_mut(), + of_node_reused: false, + }; + let pdev = unsafe { from_err_ptr(bindings::platform_device_register_full(&info)) }; + match pdev { + Err(e) => { + dev_err!( + this.data.dev, + "Failed to create device for service {:?}: {:?}", + this.name, + e + ); + } + Ok(pdev) => { + let res = this.data.subdevices.lock().push(pdev, GFP_KERNEL); + if res.is_err() { + dev_err!(this.data.dev, "Failed to store subdevice"); + } + } + } + } +} + +impl AopData { + fn new(dev: &platform::Device) -> Result> { + Arc::pin_init( + pin_init!( + AopData { + dev: dev.as_ref().into(), + rtkit <- new_mutex!(None), + endpoints <- pin_init::pin_init_array_from_fn(|i| { + new_mutex!(AFKEndpoint::new(AFK_ENDPOINT_START + i as u8)) + }), + ep_shutdown <- FutureValue::pin_init(), + hid_listeners <- new_mutex!(KVec::new()), + subdevices <- new_mutex!(KVec::new()), + } + ), + GFP_KERNEL, + ) + } + fn start(&self) -> Result<()> { + { + let mut guard = self.rtkit.lock(); + let rtk = guard.as_mut().unwrap(); + rtk.wake()?; + } + for ep in 0..AFK_ENDPOINT_COUNT { + let rtk_ep_num = AFK_ENDPOINT_START + ep; + let mut guard = self.rtkit.lock(); + let rtk = guard.as_mut().unwrap(); + if !rtk.has_endpoint(rtk_ep_num) { + continue; + } + rtk.start_endpoint(rtk_ep_num)?; + let ep_guard = self.endpoints[ep as usize].lock(); + ep_guard.start(rtk)?; + } + Ok(()) + } + fn register_service( + self: Arc, + ep: &mut AFKEndpoint, + channel: u32, + name: &[u8], + ) -> Result<()> { + let svc = EPICService { + channel, + endpoint: ep.index, + }; + let dev_name = match name { + b"aop-audio" => c_str!("audio"), + b"las" => c_str!("las"), + b"als" => c_str!("als"), + _ => { + return Ok(()); + } + }; + // probe can call back into us, run it with locks dropped. + let work = AopServiceRegisterWork::new(dev_name, self, svc)?; + workqueue::system().enqueue(work); + Ok(()) + } + + fn process_fakehid_report(&self, ep: &AFKEndpoint, ch: u32, data: &[u8]) -> Result<()> { + let guard = self.hid_listeners.lock(); + for entry in &*guard { + if entry.svc.endpoint == ep.index && entry.svc.channel == ch { + return entry.listener.process_fakehid_report(data); + } + } + Ok(()) + } + + fn shutdown_complete(&self) { + self.ep_shutdown.complete(()); + } + + fn stop(&self) -> Result<()> { + for ep in 0..AFK_ENDPOINT_COUNT { + { + let rtk_ep_num = AFK_ENDPOINT_START + ep; + let mut guard = self.rtkit.lock(); + let rtk = guard.as_mut().unwrap(); + if !rtk.has_endpoint(rtk_ep_num) { + continue; + } + let ep_guard = self.endpoints[ep as usize].lock(); + ep_guard.stop(rtk)?; + } + self.ep_shutdown.wait(); + self.ep_shutdown.reset(); + } + Ok(()) + } + + fn patch_bootargs( + &self, + aop_mmio: &IoMem, + patches: &[(u32, u64)], + ) -> Result<()> { + let offset = aop_mmio.read32_relaxed(BOOTARGS_OFFSET) as usize; + let size = aop_mmio.read32_relaxed(BOOTARGS_SIZE) as usize; + let mut arg_bytes = KVec::::from_elem(0, size, GFP_KERNEL)?; + aop_mmio.try_memcpy_fromio(&mut arg_bytes, offset)?; + let mut idx = 0; + while idx < size { + let key = u32::from_le_bytes(arg_bytes[idx..idx + 4].try_into().unwrap()); + let size = u32::from_le_bytes(arg_bytes[idx + 4..idx + 8].try_into().unwrap()) as usize; + idx += 8; + for (k, v) in patches.iter() { + if *k != key { + continue; + } + arg_bytes[idx..idx + size].copy_from_slice(&(*v as u64).to_le_bytes()[..size]); + break; + } + idx += size; + } + aop_mmio.try_memcpy_toio(offset, &arg_bytes) + } + + fn start_cpu(&self, asc_mmio: &IoMem) -> Result<()> { + let val = asc_mmio.read32_relaxed(CPU_CONTROL); + asc_mmio.write32_relaxed(val | CPU_RUN, CPU_CONTROL); + Ok(()) + } +} + +impl AOP for AopData { + fn epic_call(&self, svc: &EPICService, subtype: u16, msg_bytes: &[u8]) -> Result { + let ep_idx = svc.endpoint - AFK_ENDPOINT_START; + let call = { + let mut rtk_guard = self.rtkit.lock(); + let rtk = rtk_guard.as_mut().unwrap(); + let mut ep_guard = self.endpoints[ep_idx as usize].lock(); + ep_guard.epic_notify(self, rtk, svc.channel, subtype, msg_bytes)? + }; + Ok(call.wait()) + } + fn add_fakehid_listener( + &self, + svc: EPICService, + listener: Arc, + ) -> Result<()> { + let mut guard = self.hid_listeners.lock(); + Ok(guard.push(ListenerEntry { svc, listener }, GFP_KERNEL)?) + } + fn remove_fakehid_listener(&self, svc: &EPICService) -> bool { + let mut guard = self.hid_listeners.lock(); + for i in 0..guard.len() { + if guard[i].svc == *svc { + guard.swap_remove(i); + return true; + } + } + false + } + fn remove(&self) { + if let Err(e) = self.stop() { + dev_err!(self.dev, "Failed to stop AOP {:?}", e); + } + *self.rtkit.lock() = None; + let guard = self.subdevices.lock(); + for pdev in &*guard { + unsafe { + bindings::platform_device_unregister(*pdev); + } + } + } +} + +struct NoBuffer; +impl rtkit::Buffer for NoBuffer { + fn iova(&self) -> Result { + unreachable!() + } + fn buf(&mut self) -> Result<&mut [u8]> { + unreachable!() + } +} + +#[vtable] +impl rtkit::Operations for AopData { + type Data = Arc; + type Buffer = NoBuffer; + + fn recv_message(data: ::Borrowed<'_>, ep: u8, msg: u64) { + let mut rtk = data.rtkit.lock(); + let mut ep_guard = data.endpoints[(ep - AFK_ENDPOINT_START) as usize].lock(); + let ret = ep_guard.recv_message(data, rtk.as_mut().unwrap(), msg); + if let Err(e) = ret { + dev_err!(data.dev, "Failed to handle rtkit message, error: {:?}", e); + } + } + + fn crashed(data: ::Borrowed<'_>, _crashlog: Option<&[u8]>) { + dev_err!(data.dev, "AOP firmware crashed"); + } +} + +#[repr(transparent)] +struct AopDriver(Arc); + +struct AopHwConfig { + ec0p: u64, + alig: u64, + aopt: u64, +} + +const HW_CFG_T8103: AopHwConfig = AopHwConfig { + ec0p: 0x020000, + aopt: 1, + alig: 128, +}; +const HW_CFG_T8112: AopHwConfig = AopHwConfig { + ec0p: 0x020000, + aopt: 0, + alig: 128, +}; +const HW_CFG_T6000: AopHwConfig = AopHwConfig { + ec0p: 0x020000, + aopt: 0, + alig: 64, +}; +const HW_CFG_T6020: AopHwConfig = AopHwConfig { + ec0p: 0x0100_00000000, + aopt: 0, + alig: 64, +}; + +kernel::of_device_table!( + OF_TABLE, + MODULE_OF_TABLE, + ::IdInfo, + [ + (of::DeviceId::new(c_str!("apple,t8103-aop")), &HW_CFG_T8103), + (of::DeviceId::new(c_str!("apple,t8112-aop")), &HW_CFG_T8112), + (of::DeviceId::new(c_str!("apple,t6000-aop")), &HW_CFG_T6000), + (of::DeviceId::new(c_str!("apple,t6020-aop")), &HW_CFG_T6020), + ] +); + +impl platform::Driver for AopDriver { + type IdInfo = &'static AopHwConfig; + + const OF_ID_TABLE: Option> = Some(&OF_TABLE); + + fn probe( + pdev: &platform::Device, + info: Option<&Self::IdInfo>, + ) -> Result>> { + let cfg = info.ok_or(ENODEV)?; + unsafe { pdev.dma_set_mask_and_coherent(DmaMask::new::<42>())? }; + let aop_req = pdev.io_request_by_index(0).ok_or(EINVAL)?; + let aop_mmio = KBox::pin_init(aop_req.iomap_sized::(), GFP_KERNEL)?; + let asc_req = pdev.io_request_by_index(1).ok_or(EINVAL)?; + let asc_mmio = KBox::pin_init(asc_req.iomap_sized::(), GFP_KERNEL)?; + let data = AopData::new(pdev)?; + let aop_mmio = aop_mmio.access(pdev.as_ref())?; + data.patch_bootargs( + aop_mmio, + &[ + (from_fourcc(b"EC0p"), cfg.ec0p), + (from_fourcc(b"nCal"), 0x0), + (from_fourcc(b"alig"), cfg.alig), + (from_fourcc(b"AOPt"), cfg.aopt), + ], + )?; + let rtkit = rtkit::RtKit::::new(pdev.as_ref(), None, 0, data.clone())?; + *data.rtkit.lock() = Some(rtkit); + let asc_mmio = asc_mmio.access(pdev.as_ref())?; + let _ = data.start_cpu(asc_mmio); + data.start()?; + let data = data as Arc; + Ok(KBox::pin(AopDriver(data), GFP_KERNEL)?) + } +} + +impl Drop for AopDriver { + fn drop(&mut self) { + self.0.remove(); + } +} + +unsafe impl Send for AopDriver {} + +module_platform_driver! { + type: AopDriver, + name: "apple_aop", + description: "AOP driver", + license: "Dual MIT/GPL", +} diff --git a/rust/kernel/soc/apple/aop.rs b/rust/kernel/soc/apple/aop.rs new file mode 100644 index 00000000000000..37aae200b81eb4 --- /dev/null +++ b/rust/kernel/soc/apple/aop.rs @@ -0,0 +1,42 @@ +// SPDX-License-Identifier: GPL-2.0-only OR MIT + +//! Common code for AOP endpoint drivers + +use kernel::{prelude::*, sync::Arc}; + +/// Representation of an "EPIC" service. +#[derive(Clone, Copy, PartialEq, Eq)] +#[repr(C)] +pub struct EPICService { + /// Channel id + pub channel: u32, + /// RTKit endpoint + pub endpoint: u8, +} + +/// Listener for the "HID" events sent by aop +pub trait FakehidListener { + /// Process the event. + fn process_fakehid_report(&self, data: &[u8]) -> Result<()>; +} + +/// AOP communications manager. +pub trait AOP: Send + Sync { + /// Calls a method on a specified service + fn epic_call(&self, svc: &EPICService, subtype: u16, msg_bytes: &[u8]) -> Result; + /// Adds the listener for the specified service + fn add_fakehid_listener( + &self, + svc: EPICService, + listener: Arc, + ) -> Result<()>; + /// Remove the listener for the specified service + fn remove_fakehid_listener(&self, svc: &EPICService) -> bool; + /// Internal method to detach the device. + fn remove(&self); +} + +/// Converts a text representation of a FourCC to u32 +pub const fn from_fourcc(b: &[u8]) -> u32 { + b[3] as u32 | (b[2] as u32) << 8 | (b[1] as u32) << 16 | (b[0] as u32) << 24 +} diff --git a/rust/kernel/soc/apple/mod.rs b/rust/kernel/soc/apple/mod.rs index 964a5267bafb92..d55080b80e2201 100644 --- a/rust/kernel/soc/apple/mod.rs +++ b/rust/kernel/soc/apple/mod.rs @@ -4,3 +4,6 @@ #[cfg(CONFIG_RUST_APPLE_RTKIT = "y")] pub mod rtkit; + +#[cfg(any(CONFIG_APPLE_AOP = "y", CONFIG_APPLE_AOP = "m"))] +pub mod aop; From db8950768ec33330cba1a67c87bb470a77ee90f7 Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Sat, 6 Dec 2025 10:26:13 +0100 Subject: [PATCH 1752/3902] squash! soc: apple: Add support for the AOP co-processor Adapt to rtkit bindings change. --- drivers/soc/apple/aop.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/drivers/soc/apple/aop.rs b/drivers/soc/apple/aop.rs index 2e698d85373a76..8a788cfd253817 100644 --- a/drivers/soc/apple/aop.rs +++ b/drivers/soc/apple/aop.rs @@ -13,6 +13,7 @@ use kernel::{ dma::{CoherentAllocation, Device, DmaMask}, error::from_err_ptr, io::mem::IoMem, + iosys_map::IoSysMapRef, module_platform_driver, new_condvar, new_mutex, of, platform, prelude::*, soc::apple::aop::{from_fourcc, EPICService, FakehidListener, AOP}, @@ -825,7 +826,7 @@ impl rtkit::Buffer for NoBuffer { fn iova(&self) -> Result { unreachable!() } - fn buf(&mut self) -> Result<&mut [u8]> { + fn buf(&mut self) -> Result> { unreachable!() } } From feb37788a0b21c3eff3fe52373a6c36fe46f4b7b Mon Sep 17 00:00:00 2001 From: Sasha Finkelstein Date: Thu, 1 Jan 2026 12:12:46 +0100 Subject: [PATCH 1753/3902] fixup! soc: apple: Add support for the AOP co-processor --- drivers/soc/apple/aop.rs | 66 ++++++++++++++++++++++++++++++------ rust/kernel/soc/apple/aop.rs | 9 +++++ 2 files changed, 65 insertions(+), 10 deletions(-) diff --git a/drivers/soc/apple/aop.rs b/drivers/soc/apple/aop.rs index 8a788cfd253817..b73d05227f2ff3 100644 --- a/drivers/soc/apple/aop.rs +++ b/drivers/soc/apple/aop.rs @@ -5,7 +5,7 @@ //! //! Copyright (C) The Asahi Linux Contributors -use core::{arch::asm, mem, ptr, slice}; +use core::{arch::asm, cmp, mem, ptr, slice}; use kernel::{ bindings, c_str, device, @@ -23,6 +23,7 @@ use kernel::{ workqueue::{self, impl_has_work, new_work, Work, WorkItem}, }; +const AOP_MAX_CALLS: usize = 8; const AOP_MMIO_SIZE: usize = 0x1e0000; const ASC_MMIO_SIZE: usize = 0x4000; const BOOTARGS_OFFSET: usize = 0x22c; @@ -54,6 +55,7 @@ const EPIC_SUBTYPE_STD_SERVICE: u16 = 0xc0; const EPIC_SUBTYPE_FAKEHID_REPORT: u16 = 0xc4; const EPIC_SUBTYPE_RETCODE: u16 = 0x84; const EPIC_SUBTYPE_RETCODE_PAYLOAD: u16 = 0xa0; +const EPIC_SUBTYPE_STRING: u16 = 0x8a; const QE_MAGIC1: u32 = from_fourcc(b" POI"); const QE_MAGIC2: u32 = from_fourcc(b" POA"); @@ -115,7 +117,7 @@ struct FutureValue { completion: CondVar, } -impl FutureValue { +impl FutureValue { fn pin_init() -> impl PinInit> { pin_init!( FutureValue { @@ -133,7 +135,7 @@ impl FutureValue { while ret_guard.is_none() { self.completion.wait(&mut ret_guard); } - ret_guard.as_ref().unwrap().clone() + ret_guard.take().unwrap() } fn reset(&self) { *self.val.lock() = None; @@ -146,13 +148,19 @@ struct AFKRingBuffer { buf_size: usize, } +struct CallResult { + retcode: u32, + extra_data: Option>, +} + struct AFKEndpoint { index: u8, iomem: Option>, txbuf: Option, rxbuf: Option, seq: u16, - calls: [Option>>; 8], + calls: [Option>>; AOP_MAX_CALLS], + call_returns: [Option>; AOP_MAX_CALLS], } unsafe impl Send for AFKEndpoint {} @@ -165,7 +173,8 @@ impl AFKEndpoint { txbuf: None, rxbuf: None, seq: 0, - calls: [const { None }; 8], + calls: [const { None }; AOP_MAX_CALLS], + call_returns: [const { None }; AOP_MAX_CALLS], } } @@ -409,7 +418,10 @@ impl AFKEndpoint { return Err(EIO); } } else if ehdr.category == EPIC_CATEGORY_REPLY { - if subtype == EPIC_SUBTYPE_RETCODE_PAYLOAD || subtype == EPIC_SUBTYPE_RETCODE { + if subtype == EPIC_SUBTYPE_RETCODE_PAYLOAD + || subtype == EPIC_SUBTYPE_RETCODE + || subtype == EPIC_SUBTYPE_STRING + { if data.len() < mem::size_of::() { dev_err!( client.dev, @@ -429,7 +441,20 @@ impl AFKEndpoint { ); return Err(EIO); } - self.calls[tag - 1].take().unwrap().complete(retcode); + let future = self.calls[tag - 1].take().unwrap(); + let extra_data = if let Some(mut ret) = self.call_returns[tag - 1].take() { + let len = cmp::min(data.len() - 4, ret.len()); + ret[..len].copy_from_slice(&data[4..(len + 4)]); + ret.truncate(len); + Some(ret) + } else { + None + }; + future.complete(CallResult { + retcode, + extra_data, + }); + return Ok(()); } else { dev_err!( @@ -510,7 +535,8 @@ impl AFKEndpoint { channel: u32, subtype: u16, data: &[u8], - ) -> Result>> { + ret: Option>, + ) -> Result>> { let mut tag = 0; for i in 0..self.calls.len() { if self.calls[i].is_none() { @@ -537,6 +563,7 @@ impl AFKEndpoint { tag: tag as u16, ..EPICHeader::default() }; + self.call_returns[tag - 1] = ret; self.send_rb( client, rtkit, @@ -785,9 +812,28 @@ impl AOP for AopData { let mut rtk_guard = self.rtkit.lock(); let rtk = rtk_guard.as_mut().unwrap(); let mut ep_guard = self.endpoints[ep_idx as usize].lock(); - ep_guard.epic_notify(self, rtk, svc.channel, subtype, msg_bytes)? + ep_guard.epic_notify(self, rtk, svc.channel, subtype, msg_bytes, None)? + }; + Ok(call.wait().retcode) + } + fn epic_call_ret( + &self, + svc: &EPICService, + subtype: u16, + msg_bytes: &[u8], + ret_len: usize, + ) -> Result<(u32, KVec)> { + let ep_idx = svc.endpoint - AFK_ENDPOINT_START; + let call = { + let mut rtk_guard = self.rtkit.lock(); + let rtk = rtk_guard.as_mut().unwrap(); + let mut ep_guard = self.endpoints[ep_idx as usize].lock(); + let mut ret_buf = KVec::new(); + ret_buf.resize(ret_len, 0, GFP_KERNEL)?; + ep_guard.epic_notify(self, rtk, svc.channel, subtype, msg_bytes, Some(ret_buf))? }; - Ok(call.wait()) + let res = call.wait(); + Ok((res.retcode, res.extra_data.unwrap())) } fn add_fakehid_listener( &self, diff --git a/rust/kernel/soc/apple/aop.rs b/rust/kernel/soc/apple/aop.rs index 37aae200b81eb4..da46ce0bcb027d 100644 --- a/rust/kernel/soc/apple/aop.rs +++ b/rust/kernel/soc/apple/aop.rs @@ -24,6 +24,15 @@ pub trait FakehidListener { pub trait AOP: Send + Sync { /// Calls a method on a specified service fn epic_call(&self, svc: &EPICService, subtype: u16, msg_bytes: &[u8]) -> Result; + /// Just like epic_call, but also returns a value + fn epic_call_ret( + &self, + svc: &EPICService, + subtype: u16, + msg_bytes: &[u8], + ret_len: usize, + ) -> Result<(u32, KVec)>; + /// Adds the listener for the specified service fn add_fakehid_listener( &self, From 740aaaf754a34d13563d1383ab9f5c2a9971dfb6 Mon Sep 17 00:00:00 2001 From: Sasha Finkelstein Date: Mon, 18 Nov 2024 00:03:20 +0100 Subject: [PATCH 1754/3902] ASoC: apple: Add aop_audio driver Apple SoCs have their microphones connected to the AOP co-processor, in order to among other things implement the "voicetrigger" functionality. Add a driver for the "High power audio input" AOP endpoint. Signed-off-by: Sasha Finkelstein --- sound/soc/apple/Kconfig | 11 + sound/soc/apple/Makefile | 3 + sound/soc/apple/aop_audio.rs | 701 +++++++++++++++++++++++++++++++++++ 3 files changed, 715 insertions(+) create mode 100644 sound/soc/apple/aop_audio.rs diff --git a/sound/soc/apple/Kconfig b/sound/soc/apple/Kconfig index d8dc2f1ccc83e0..eebb84dfbdfe5c 100644 --- a/sound/soc/apple/Kconfig +++ b/sound/soc/apple/Kconfig @@ -1,5 +1,16 @@ menu "Apple" +config SND_SOC_APPLE_AOP_AUDIO + tristate "AOP audio driver" + depends on ARCH_APPLE || COMPILE_TEST + depends on RUST + select APPLE_AOP + select SND_DMAENGINE_PCM + help + This option enables an ASoC driver for sound devices connected to the AOP + co-processor on ARM Macs. This includes the built-in microphone on those + machines. + config SND_SOC_APPLE_MCA tristate "Apple Silicon MCA driver" depends on ARCH_APPLE || COMPILE_TEST diff --git a/sound/soc/apple/Makefile b/sound/soc/apple/Makefile index 1eb8fbef60c617..040b002e728198 100644 --- a/sound/soc/apple/Makefile +++ b/sound/soc/apple/Makefile @@ -1,3 +1,6 @@ +snd-soc-aop-y := aop_audio.o +obj-$(CONFIG_SND_SOC_APPLE_AOP_AUDIO) += snd-soc-aop.o + snd-soc-apple-mca-y := mca.o obj-$(CONFIG_SND_SOC_APPLE_MCA) += snd-soc-apple-mca.o diff --git a/sound/soc/apple/aop_audio.rs b/sound/soc/apple/aop_audio.rs new file mode 100644 index 00000000000000..fcbfd31f8e9b4c --- /dev/null +++ b/sound/soc/apple/aop_audio.rs @@ -0,0 +1,701 @@ +// SPDX-License-Identifier: GPL-2.0-only OR MIT +#![recursion_limit = "2048"] + +//! Apple AOP audio driver +//! +//! Copyright (C) The Asahi Linux Contributors + +use core::sync::atomic::{AtomicU32, Ordering}; +use core::{mem, ptr, slice}; + +use kernel::{ + bindings, c_str, device, + device::property::FwNode, + device::Core, + error::from_err_ptr, + module_platform_driver, of, platform, + prelude::*, + soc::apple::aop::{from_fourcc, EPICService, AOP}, + str::CString, + sync::Arc, + types::{ARef, ForeignOwnable}, +}; + +use pin_init::Zeroable; + +const EPIC_SUBTYPE_WRAPPED_CALL: u16 = 0x20; +const CALLTYPE_AUDIO_ATTACH_DEVICE: u32 = 0xc3000002; +const CALLTYPE_AUDIO_SET_PROP: u32 = 0xc3000005; +const PDM_NUM_COEFFS: usize = 120; +const DECIMATION_RATIOS: [u8; 3] = [0xf, 5, 2]; +const COEFFICIENTS: [u8; PDM_NUM_COEFFS * mem::size_of::()] = [ + 0x88, 0x03, 0x00, 0x00, 0x82, 0x08, 0x00, 0x00, 0x51, 0x12, 0x00, 0x00, 0x0a, 0x23, 0x00, 0x00, + 0xce, 0x3d, 0x00, 0x00, 0x97, 0x66, 0x00, 0x00, 0x43, 0xa2, 0x00, 0x00, 0x9c, 0xf6, 0x00, 0x00, + 0x53, 0x6a, 0x01, 0x00, 0xe6, 0x04, 0x02, 0x00, 0x7e, 0xce, 0x02, 0x00, 0xae, 0xcf, 0x03, 0x00, + 0x2e, 0x11, 0x05, 0x00, 0x7d, 0x9b, 0x06, 0x00, 0x75, 0x76, 0x08, 0x00, 0xd8, 0xa8, 0x0a, 0x00, + 0xd2, 0x37, 0x0d, 0x00, 0x82, 0x26, 0x10, 0x00, 0x86, 0x75, 0x13, 0x00, 0x97, 0x22, 0x17, 0x00, + 0x39, 0x28, 0x1b, 0x00, 0x89, 0x7d, 0x1f, 0x00, 0x2e, 0x16, 0x24, 0x00, 0x69, 0xe2, 0x28, 0x00, + 0x56, 0xcf, 0x2d, 0x00, 0x51, 0xc7, 0x32, 0x00, 0x80, 0xb2, 0x37, 0x00, 0x87, 0x77, 0x3c, 0x00, + 0x4c, 0xfc, 0x40, 0x00, 0xd9, 0x26, 0x45, 0x00, 0x47, 0xde, 0x48, 0x00, 0xa0, 0x0b, 0x4c, 0x00, + 0xc1, 0x9a, 0x4e, 0x00, 0x1f, 0x7b, 0x50, 0x00, 0x68, 0xa0, 0x51, 0x00, 0x06, 0x03, 0x52, 0x00, + 0x4a, 0x25, 0x00, 0x00, 0x4c, 0xaf, 0x00, 0x00, 0xc0, 0x07, 0x02, 0x00, 0x45, 0x99, 0x04, 0x00, + 0x9a, 0x84, 0x08, 0x00, 0x7d, 0x38, 0x0d, 0x00, 0x5f, 0x1a, 0x11, 0x00, 0xd9, 0x81, 0x11, 0x00, + 0x80, 0x44, 0x0b, 0x00, 0x8e, 0xe5, 0xfb, 0xff, 0xca, 0x32, 0xe3, 0xff, 0x52, 0xc7, 0xc4, 0xff, + 0xa6, 0xbc, 0xa8, 0xff, 0x83, 0xe6, 0x9a, 0xff, 0xb8, 0x5b, 0xa8, 0xff, 0x6b, 0xae, 0xdb, 0xff, + 0xe7, 0xd8, 0x38, 0x00, 0x24, 0x42, 0xba, 0x00, 0x33, 0x20, 0x50, 0x01, 0x6e, 0xdc, 0xe2, 0x01, + 0x42, 0x23, 0x58, 0x02, 0x2c, 0x50, 0x99, 0x02, 0xcf, 0xfa, 0xff, 0xff, 0x53, 0x0a, 0xff, 0xff, + 0x66, 0x23, 0xfb, 0xff, 0xa0, 0x3e, 0xf4, 0xff, 0xe6, 0x68, 0xf0, 0xff, 0xb8, 0x35, 0xf7, 0xff, + 0x56, 0xec, 0x04, 0x00, 0x37, 0xa3, 0x09, 0x00, 0x00, 0xd4, 0xfe, 0xff, 0x78, 0xa3, 0xf5, 0xff, + 0x03, 0xbf, 0xfe, 0xff, 0x84, 0xd5, 0x0b, 0x00, 0xbe, 0x0b, 0x04, 0x00, 0x52, 0x54, 0xf2, 0xff, + 0x6d, 0x3f, 0xf8, 0xff, 0xc5, 0x7f, 0x0f, 0x00, 0xe6, 0x9e, 0x0c, 0x00, 0x79, 0x03, 0xef, 0xff, + 0xd5, 0x33, 0xed, 0xff, 0xec, 0xd1, 0x11, 0x00, 0x7d, 0x69, 0x1a, 0x00, 0xd6, 0x55, 0xee, 0xff, + 0x88, 0x66, 0xdc, 0xff, 0x57, 0x26, 0x10, 0x00, 0xc7, 0x8d, 0x2e, 0x00, 0x82, 0x2e, 0xf3, 0xff, + 0x63, 0x69, 0xc4, 0xff, 0xcd, 0x08, 0x07, 0x00, 0x35, 0x34, 0x4b, 0x00, 0xaf, 0x21, 0x02, 0x00, + 0x83, 0xb6, 0xa1, 0xff, 0xe2, 0xd5, 0xef, 0xff, 0x94, 0x9b, 0x76, 0x00, 0xf3, 0xd7, 0x25, 0x00, + 0xff, 0xfc, 0x67, 0xff, 0xe3, 0xac, 0xb6, 0xff, 0x52, 0x1b, 0xcc, 0x00, 0x3c, 0x8a, 0x8b, 0x00, + 0x9f, 0x0c, 0xcd, 0xfe, 0x5c, 0x68, 0xcc, 0xfe, 0x4d, 0xc5, 0x98, 0x02, 0x82, 0xcf, 0xfb, 0x06, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +]; +const FILTER_LENGTHS: u32 = 0x542c47; +const AUDIO_DEV_PDM0: u32 = from_fourcc(b"pdm0"); +const AUDIO_DEV_LPAI: u32 = from_fourcc(b"lpai"); +const AUDIO_DEV_HPAI: u32 = from_fourcc(b"hpai"); +const POWER_STATE_OFF: u32 = from_fourcc(b"idle"); +const POWER_STATE_IDLE: u32 = from_fourcc(b"pw1 "); +const POWER_STATE_ON: u32 = from_fourcc(b"pwrd"); + +#[repr(C, packed)] +#[derive(Clone, Copy, Default)] +struct AudioAttachDevice { + _zero0: u32, + unk0: u32, + calltype: u32, + _zero1: u64, + _zero2: u64, + _pad0: u32, + len: u64, + dev_id: u32, + _pad1: u32, +} + +impl AudioAttachDevice { + fn new(dev_id: u32) -> AudioAttachDevice { + AudioAttachDevice { + unk0: 0xFFFFFFFF, + calltype: CALLTYPE_AUDIO_ATTACH_DEVICE, + dev_id, + len: 0x2c, + ..AudioAttachDevice::default() + } + } +} + +#[repr(C, packed)] +#[derive(Clone, Copy, Default)] +struct LpaiChannelConfig { + unk1: u32, + unk2: u32, + unk3: u32, + unk4: u32, +} + +#[repr(C, packed)] +#[derive(Debug, Copy, Clone)] +struct PDMConfig { + bytes_per_sample: u32, + clock_source: u32, + pdm_frequency: u32, + pdmc_frequency: u32, + slow_clock_speed: u32, + fast_clock_speed: u32, + channel_polarity_select: u32, + channel_phase_select: u32, + unk1: u32, + unk2: u16, + ratio1: u8, + ratio2: u8, + ratio3: u8, + _pad0: u8, + filter_lengths: u32, + coeff_bulk: u32, + coeffs: [u8; PDM_NUM_COEFFS * mem::size_of::()], + unk3: u32, + mic_turn_on_time_ms: u32, + _zero0: u64, + _zero1: u64, + unk4: u32, + mic_settle_time_ms: u32, + _zero2: [u8; 69], // ????? +} + +unsafe impl Zeroable for PDMConfig {} + +#[repr(C, packed)] +#[derive(Debug, Copy, Clone)] +struct DecimatorConfig { + latency: u32, + ratio1: u8, + ratio2: u8, + ratio3: u8, + _pad0: u8, + filter_lengths: u32, + coeff_bulk: u32, + coeffs: [u8; PDM_NUM_COEFFS * mem::size_of::()], +} + +unsafe impl Zeroable for DecimatorConfig {} + +#[repr(C, packed)] +#[derive(Clone, Copy, Default, Debug)] +struct PowerSetting { + dev_id: u32, + cookie: u32, + _unk0: u32, + _zero0: u64, + target_pstate: u32, + unk1: u32, + _zero1: [u8; 20], +} + +impl PowerSetting { + fn new(dev_id: u32, cookie: u32, target_pstate: u32, unk1: u32) -> PowerSetting { + PowerSetting { + dev_id, + cookie, + target_pstate, + unk1, + ..PowerSetting::default() + } + } +} + +#[repr(C, packed)] +#[derive(Clone, Copy, Default, Debug)] +struct AudioSetDeviceProp { + _zero0: u32, + unk0: u32, + calltype: u32, + _zero1: u64, + _zero2: u64, + _pad0: u32, + len: u64, + dev_id: u32, + modifier: u32, + len2: u32, + data: T, +} + +impl AudioSetDeviceProp { + fn new(dev_id: u32, modifier: u32, data: T) -> AudioSetDeviceProp { + AudioSetDeviceProp { + unk0: 0xFFFFFFFF, + calltype: CALLTYPE_AUDIO_SET_PROP, + dev_id, + modifier, + len: mem::size_of::() as u64 + 0x30, + len2: mem::size_of::() as u32, + data, + ..AudioSetDeviceProp::default() + } + } +} + +unsafe impl Zeroable for AudioSetDeviceProp {} + +impl AudioSetDeviceProp { + fn try_init( + dev_id: u32, + modifier: u32, + data: impl Init, + ) -> impl Init, Error> + where + Error: From, + { + try_init!( + AudioSetDeviceProp { + unk0: 0xFFFFFFFF, + calltype: CALLTYPE_AUDIO_SET_PROP, + dev_id, + modifier, + len: mem::size_of::() as u64 + 0x30, + len2: mem::size_of::() as u32, + data <- data, + ..Zeroable::init_zeroed() + } + ) + } +} + +struct SndSocAopData { + dev: ARef, + adata: Arc, + service: EPICService, + pstate_cookie: AtomicU32, + fwnode: ARef, +} + +impl SndSocAopData { + fn new( + dev: ARef, + adata: Arc, + service: EPICService, + fwnode: ARef, + ) -> Result> { + Ok(Arc::new( + SndSocAopData { + dev, + adata, + service, + fwnode, + pstate_cookie: AtomicU32::new(1), + }, + GFP_KERNEL, + )?) + } + fn set_pdm_config(&self) -> Result<()> { + let pdm_cfg = init!(PDMConfig { + bytes_per_sample: 2, + clock_source: 0x706c6c20, // 'pll ' + pdm_frequency: 2400000, + pdmc_frequency: 24000000, + slow_clock_speed: 24000000, + fast_clock_speed: 24000000, + channel_polarity_select: 256, + channel_phase_select: 0, + unk1: 0xf7600, + unk2: 0, + ratio1: DECIMATION_RATIOS[0], + ratio2: DECIMATION_RATIOS[1], + ratio3: DECIMATION_RATIOS[2], + filter_lengths: FILTER_LENGTHS, + coeff_bulk: PDM_NUM_COEFFS as u32, + coeffs: COEFFICIENTS, + unk3: 1, + mic_turn_on_time_ms: 20, + unk4: 1, + mic_settle_time_ms: 50, + ..Zeroable::init_zeroed() + }); + let set_prop = AudioSetDeviceProp::::try_init(AUDIO_DEV_PDM0, 200, pdm_cfg); + let msg = KBox::try_init(set_prop, GFP_KERNEL)?; + let ret = self.epic_wrapped_call(msg.as_ref())?; + if ret != 0 { + dev_err!(self.dev, "Unable to set pdm config, return code {}", ret); + return Err(EIO); + } else { + Ok(()) + } + } + fn set_decimator_config(&self) -> Result<()> { + let pdm_cfg = init!(DecimatorConfig { + latency: 15, + ratio1: DECIMATION_RATIOS[0], + ratio2: DECIMATION_RATIOS[1], + ratio3: DECIMATION_RATIOS[2], + filter_lengths: FILTER_LENGTHS, + coeff_bulk: PDM_NUM_COEFFS as u32, + coeffs: COEFFICIENTS, + ..Zeroable::init_zeroed() + }); + let set_prop = + AudioSetDeviceProp::::try_init(AUDIO_DEV_PDM0, 210, pdm_cfg); + let msg = KBox::try_init(set_prop, GFP_KERNEL)?; + let ret = self.epic_wrapped_call(msg.as_ref())?; + if ret != 0 { + dev_err!( + self.dev, + "Unable to set decimator config, return code {}", + ret + ); + return Err(EIO); + } else { + Ok(()) + } + } + fn set_lpai_channel_cfg(&self) -> Result<()> { + let cfg = LpaiChannelConfig { + unk1: 7, + unk2: 7, + unk3: 1, + unk4: 7, + }; + let msg = AudioSetDeviceProp::new(AUDIO_DEV_LPAI, 301, cfg); + let ret = self.epic_wrapped_call(&msg)?; + if ret != 0 { + dev_err!( + self.dev, + "Unable to set lpai channel config, return code {}", + ret + ); + return Err(EIO); + } else { + Ok(()) + } + } + fn audio_attach_device(&self, dev_id: u32) -> Result<()> { + let msg = AudioAttachDevice::new(dev_id); + let ret = self.epic_wrapped_call(&msg)?; + if ret != 0 { + dev_err!( + self.dev, + "Unable to attach device {:?}, return code {}", + dev_id, + ret + ); + return Err(EIO); + } else { + Ok(()) + } + } + fn set_audio_power(&self, pstate: u32, unk1: u32) -> Result<()> { + let set_pstate = PowerSetting::new( + AUDIO_DEV_HPAI, + self.pstate_cookie.fetch_add(1, Ordering::Relaxed), + pstate, + unk1, + ); + let msg = AudioSetDeviceProp::new(AUDIO_DEV_HPAI, 202, set_pstate); + let ret = self.epic_wrapped_call(&msg)?; + if ret != 0 { + dev_err!( + self.dev, + "Unable to set power state {:?}, return code {}", + pstate, + ret + ); + return Err(EIO); + } else { + Ok(()) + } + } + fn epic_wrapped_call(&self, data: &T) -> Result { + let msg_bytes = + unsafe { slice::from_raw_parts(data as *const T as *const u8, mem::size_of::()) }; + self.adata + .epic_call(&self.service, EPIC_SUBTYPE_WRAPPED_CALL, msg_bytes) + } + fn request_dma_channel(&self) -> Result<*mut bindings::dma_chan> { + let res = unsafe { + from_err_ptr(bindings::dma_request_chan( + self.dev.as_raw(), + c_str!("dma").as_ptr() as _, + )) + }; + if res.is_err() { + dev_err!(self.dev, "Unable to get dma channel"); + } + res + } +} + +#[repr(transparent)] +struct SndSocAopDriver(*mut bindings::snd_card); + +fn copy_str(target: &mut [u8], source: &[u8]) { + for i in 0..source.len() { + target[i] = source[i]; + } +} + +unsafe fn dmaengine_slave_config( + chan: *mut bindings::dma_chan, + config: *mut bindings::dma_slave_config, +) -> i32 { + unsafe { + match (*(*chan).device).device_config { + Some(dc) => dc(chan, config), + None => ENOSYS.to_errno(), + } + } +} + +unsafe extern "C" fn aop_hw_params( + substream: *mut bindings::snd_pcm_substream, + params: *mut bindings::snd_pcm_hw_params, +) -> i32 { + let chan = unsafe { bindings::snd_dmaengine_pcm_get_chan(substream) }; + let mut slave_config = bindings::dma_slave_config::default(); + let ret = + unsafe { bindings::snd_hwparams_to_dma_slave_config(substream, params, &mut slave_config) }; + if ret < 0 { + return ret; + } + slave_config.src_port_window_size = 4; + unsafe { dmaengine_slave_config(chan, &mut slave_config) } +} + +unsafe extern "C" fn aop_pcm_open(substream: *mut bindings::snd_pcm_substream) -> i32 { + let data = unsafe { Arc::::borrow((*substream).private_data.cast()) }; + if let Err(e) = data.set_audio_power(POWER_STATE_IDLE, 0) { + dev_err!(data.dev, "Unable to enter 'pw1 ' state"); + return e.to_errno(); + } + let mut hwparams = bindings::snd_pcm_hardware { + info: bindings::SNDRV_PCM_INFO_MMAP + | bindings::SNDRV_PCM_INFO_MMAP_VALID + | bindings::SNDRV_PCM_INFO_INTERLEAVED, + formats: bindings::BINDINGS_SNDRV_PCM_FMTBIT_FLOAT_LE, + subformats: 0, + rates: bindings::SNDRV_PCM_RATE_48000, + rate_min: 48000, + rate_max: 48000, + channels_min: 3, + channels_max: 3, + periods_min: 2, + buffer_bytes_max: usize::MAX, + period_bytes_max: 0x4000, + periods_max: u32::MAX, + period_bytes_min: 256, + fifo_size: 16, + }; + let dma_chan = match data.request_dma_channel() { + Ok(dc) => dc, + Err(e) => return e.to_errno(), + }; + + if unsafe { (*substream).dma_buffer.dev.type_ == bindings::SNDRV_DMA_TYPE_UNKNOWN as _ } { + let ret = unsafe { + bindings::snd_pcm_set_managed_buffer( + substream, + bindings::SNDRV_DMA_TYPE_DEV_IRAM as i32, + (*(*dma_chan).device).dev, + 0, + 0, + ) + }; + if ret < 0 { + dev_err!(data.dev, "Unable to allocate dma buffers"); + unsafe { + bindings::dma_release_channel(dma_chan); + } + return ret; + } + } + + let ret = unsafe { + let mut dai_data = bindings::snd_dmaengine_dai_dma_data::default(); + bindings::snd_dmaengine_pcm_refine_runtime_hwparams( + substream, + &mut dai_data, + &mut hwparams, + dma_chan, + ) + }; + if ret != 0 { + dev_err!(data.dev, "Unable to refine hwparams"); + return ret; + } + if let Err(e) = data.set_audio_power(POWER_STATE_ON, 1) { + dev_err!(data.dev, "Unable to power mic on"); + return e.to_errno(); + } + unsafe { + (*(*substream).runtime).hw = hwparams; + bindings::snd_dmaengine_pcm_open(substream, dma_chan) + } +} + +unsafe extern "C" fn aop_pcm_prepare(_: *mut bindings::snd_pcm_substream) -> i32 { + 0 +} + +unsafe extern "C" fn aop_pcm_close(substream: *mut bindings::snd_pcm_substream) -> i32 { + let data = unsafe { Arc::::borrow((*substream).private_data.cast()) }; + if let Err(e) = data.set_audio_power(POWER_STATE_IDLE, 1) { + dev_err!(data.dev, "Unable to power mic off"); + return e.to_errno(); + } + let ret = unsafe { bindings::snd_dmaengine_pcm_close_release_chan(substream) }; + if ret != 0 { + dev_err!(data.dev, "Unable to close channel"); + return ret; + } + if let Err(e) = data.set_audio_power(POWER_STATE_OFF, 0) { + dev_err!(data.dev, "Unable to enter 'idle' power state"); + return e.to_errno(); + } + 0 +} + +unsafe extern "C" fn aop_pcm_free_private(pcm: *mut bindings::snd_pcm) { + unsafe { + Arc::::from_foreign((*pcm).private_data.cast()); + } +} + +impl SndSocAopDriver { + const VTABLE: bindings::snd_pcm_ops = bindings::snd_pcm_ops { + open: Some(aop_pcm_open), + close: Some(aop_pcm_close), + prepare: Some(aop_pcm_prepare), + trigger: Some(bindings::snd_dmaengine_pcm_trigger), + pointer: Some(bindings::snd_dmaengine_pcm_pointer), + ioctl: None, + hw_params: Some(aop_hw_params), + hw_free: None, + sync_stop: None, + get_time_info: None, + fill_silence: None, + copy: None, + page: None, + mmap: None, + ack: None, + }; + fn new(data: Arc) -> Result { + let mut this = SndSocAopDriver(ptr::null_mut()); + let ret = unsafe { + bindings::snd_card_new( + data.dev.as_raw(), + -1, + ptr::null(), + THIS_MODULE.as_ptr(), + 0, + &mut this.0, + ) + }; + if ret < 0 { + dev_err!(data.dev, "Unable to allocate sound card"); + return Err(Error::from_errno(ret)); + } + let chassis = data + .fwnode + .property_read::(c_str!("apple,chassis-name")) + .required_by(&data.dev)?; + let machine_kind = data + .fwnode + .property_read::(c_str!("apple,machine-kind")) + .required_by(&data.dev)?; + unsafe { + let name = b"aop_audio\0"; + let target = (*this.0).driver.as_mut(); + copy_str(target, name.as_ref()); + } + unsafe { + let prefix = b"Apple"; + let target = (*this.0).id.as_mut(); + copy_str(target, prefix.as_ref()); + let mut ptr = prefix.len(); + copy_str(&mut target[ptr..], chassis.as_bytes_with_nul()); + ptr += chassis.len(); + let suffix = b"HPAI\0"; + copy_str(&mut target[ptr..], suffix); + } + let longname_suffix = b"High-Power Audio Interface\0"; + let mut machine_name = KVec::with_capacity( + chassis.len() + 2 + machine_kind.len() + longname_suffix.len(), + GFP_KERNEL, + )?; + machine_name.extend_from_slice(machine_kind.as_bytes_with_nul(), GFP_KERNEL)?; + let last_item = machine_name.len() - 1; + machine_name[last_item] = b' '; + machine_name.extend_from_slice(chassis.as_bytes_with_nul(), GFP_KERNEL)?; + let last_item = machine_name.len() - 1; + machine_name[last_item] = b' '; + unsafe { + let target = (*this.0).shortname.as_mut(); + copy_str(target, machine_name.as_ref()); + let ptr = machine_name.len(); + let suffix = b"HPAI\0"; + copy_str(&mut target[ptr..], suffix); + } + machine_name.extend_from_slice(longname_suffix, GFP_KERNEL)?; + unsafe { + let target = (*this.0).longname.as_mut(); + copy_str(target, machine_name.as_ref()); + } + + let mut pcm = ptr::null_mut(); + let ret = + unsafe { bindings::snd_pcm_new(this.0, machine_name.as_ptr() as _, 0, 0, 1, &mut pcm) }; + if ret < 0 { + dev_err!(data.dev, "Unable to allocate PCM device"); + return Err(Error::from_errno(ret)); + } + + unsafe { + bindings::snd_pcm_set_ops( + pcm, + bindings::SNDRV_PCM_STREAM_CAPTURE as i32, + &Self::VTABLE, + ); + } + + unsafe { + (*pcm).private_data = data.clone().into_foreign() as _; + (*pcm).private_free = Some(aop_pcm_free_private); + (*pcm).info_flags = 0; + let name = c_str!("aop_audio"); + copy_str((*pcm).name.as_mut(), name.as_ref()); + } + + let ret = unsafe { bindings::snd_card_register(this.0) }; + if ret < 0 { + dev_err!(data.dev, "Unable to register sound card"); + return Err(Error::from_errno(ret)); + } + Ok(this) + } +} + +impl Drop for SndSocAopDriver { + fn drop(&mut self) { + if self.0 != ptr::null_mut() { + unsafe { + bindings::snd_card_free(self.0); + } + } + } +} + +unsafe impl Send for SndSocAopDriver {} +unsafe impl Sync for SndSocAopDriver {} + +kernel::of_device_table!( + OF_TABLE, + MODULE_OF_TABLE, + (), + [(of::DeviceId::new(c_str!("apple,aop-audio")), ())] +); + +impl platform::Driver for SndSocAopDriver { + type IdInfo = (); + + const OF_ID_TABLE: Option> = Some(&OF_TABLE); + + fn probe( + pdev: &platform::Device, + _info: Option<&()>, + ) -> Result>> { + let dev = ARef::::from(pdev.as_ref()); + let parent = pdev.as_ref().parent().unwrap(); + // SAFETY: our parent is AOP, and AopDriver is repr(transparent) for Arc + let adata_ptr = unsafe { Pin::>>::borrow(parent.get_drvdata()) }; + let adata = (&*adata_ptr).clone(); + // SAFETY: AOP sets the platform data correctly + let svc = unsafe { *((*dev.as_raw()).platform_data as *const EPICService) }; + let parent_fwnode = parent.fwnode().ok_or(ENOENT)?; + let fwnode = parent_fwnode + .get_child_by_name(c_str!("audio")) + .ok_or(EIO)?; + let data = SndSocAopData::new(dev, adata, svc, fwnode)?; + for dev in [AUDIO_DEV_PDM0, AUDIO_DEV_HPAI, AUDIO_DEV_LPAI] { + data.audio_attach_device(dev)?; + } + data.set_lpai_channel_cfg()?; + data.set_pdm_config()?; + data.set_decimator_config()?; + Ok(Box::pin(SndSocAopDriver::new(data)?, GFP_KERNEL)?) + } +} + +module_platform_driver! { + type: SndSocAopDriver, + name: "snd_soc_apple_aop", + description: "AOP microphone capture driver", + license: "Dual MIT/GPL", + alias: ["platform:snd_soc_apple_aop"], +} From 45d943f8db6237b3d9d4259a1e651a10f4fb8132 Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Sat, 15 Feb 2025 11:57:07 +0100 Subject: [PATCH 1755/3902] ASoC: apple: aop: Add module parameter to check mics without beamforming Keep this parameter only until all devices have user-space bits in place. Enable mics despite of this via `snd_soc_aop.mic_check_123=1` at module load time, for example bey specifying it in the kernel command line. Signed-off-by: Janne Grunau --- sound/soc/apple/aop_audio.rs | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/sound/soc/apple/aop_audio.rs b/sound/soc/apple/aop_audio.rs index fcbfd31f8e9b4c..8fff165fa9d5b9 100644 --- a/sound/soc/apple/aop_audio.rs +++ b/sound/soc/apple/aop_audio.rs @@ -681,6 +681,10 @@ impl platform::Driver for SndSocAopDriver { let fwnode = parent_fwnode .get_child_by_name(c_str!("audio")) .ok_or(EIO)?; + let audio = *module_parameters::mic_check_123.value() != 0; + if !audio && parent_fwnode.property_present(c_str!("apple,no-beamforming")) { + return Err(ENODEV); + } let data = SndSocAopData::new(dev, adata, svc, fwnode)?; for dev in [AUDIO_DEV_PDM0, AUDIO_DEV_HPAI, AUDIO_DEV_LPAI] { data.audio_attach_device(dev)?; @@ -698,4 +702,10 @@ module_platform_driver! { description: "AOP microphone capture driver", license: "Dual MIT/GPL", alias: ["platform:snd_soc_apple_aop"], + params: { + mic_check_123: u8 { + default: 0, + description: "Enable mics without user space handling", + }, + }, } From 7f12361cb795430df760c39b9f9fd3587d041809 Mon Sep 17 00:00:00 2001 From: Sasha Finkelstein Date: Sat, 9 Nov 2024 18:40:28 +0100 Subject: [PATCH 1756/3902] iio: common: Add AOP sensor drivers The AOP co-processor present on certain Apple SoCs exposes various environmental sensors as "HID" (really not) devices. Add drivers for the ambient light and lid angle sensors exposed that way. Signed-off-by: Sasha Finkelstein --- drivers/iio/common/Kconfig | 1 + drivers/iio/common/Makefile | 1 + drivers/iio/common/aop_sensors/Kconfig | 21 +++ drivers/iio/common/aop_sensors/Makefile | 4 + drivers/iio/common/aop_sensors/aop_als.rs | 151 +++++++++++++++++++ drivers/iio/common/aop_sensors/aop_las.rs | 76 ++++++++++ rust/kernel/iio/common/aop_sensors.rs | 167 ++++++++++++++++++++++ rust/kernel/iio/common/mod.rs | 11 ++ rust/kernel/iio/mod.rs | 5 + rust/kernel/lib.rs | 2 + 10 files changed, 439 insertions(+) create mode 100644 drivers/iio/common/aop_sensors/Kconfig create mode 100644 drivers/iio/common/aop_sensors/Makefile create mode 100644 drivers/iio/common/aop_sensors/aop_als.rs create mode 100644 drivers/iio/common/aop_sensors/aop_las.rs create mode 100644 rust/kernel/iio/common/aop_sensors.rs create mode 100644 rust/kernel/iio/common/mod.rs create mode 100644 rust/kernel/iio/mod.rs diff --git a/drivers/iio/common/Kconfig b/drivers/iio/common/Kconfig index 1ccb5ccf370660..e3818ef567822b 100644 --- a/drivers/iio/common/Kconfig +++ b/drivers/iio/common/Kconfig @@ -3,6 +3,7 @@ # IIO common modules # +source "drivers/iio/common/aop_sensors/Kconfig" source "drivers/iio/common/cros_ec_sensors/Kconfig" source "drivers/iio/common/hid-sensors/Kconfig" source "drivers/iio/common/inv_sensors/Kconfig" diff --git a/drivers/iio/common/Makefile b/drivers/iio/common/Makefile index d3e952239a6219..5f99a429725d66 100644 --- a/drivers/iio/common/Makefile +++ b/drivers/iio/common/Makefile @@ -8,6 +8,7 @@ # # When adding new entries keep the list in alphabetical order +obj-y += aop_sensors/ obj-y += cros_ec_sensors/ obj-y += hid-sensors/ obj-y += inv_sensors/ diff --git a/drivers/iio/common/aop_sensors/Kconfig b/drivers/iio/common/aop_sensors/Kconfig new file mode 100644 index 00000000000000..2c092e63a95884 --- /dev/null +++ b/drivers/iio/common/aop_sensors/Kconfig @@ -0,0 +1,21 @@ +# SPDX-License-Identifier: GPL-2.0-only OR MIT + +config IIO_AOP_SENSOR_LAS + tristate "AOP Lid angle sensor" + depends on ARCH_APPLE || COMPILE_TEST + depends on RUST + depends on SYSFS + select APPLE_AOP + help + Module to handle the lid angle sensor attached to the AOP + coprocessor on Apple laptops. + +config IIO_AOP_SENSOR_ALS + tristate "AOP Ambient light sensor" + depends on ARCH_APPLE || COMPILE_TEST + depends on RUST + depends on SYSFS + select APPLE_AOP + help + Module to handle the ambient light sensor attached to the AOP + coprocessor on Apple laptops. diff --git a/drivers/iio/common/aop_sensors/Makefile b/drivers/iio/common/aop_sensors/Makefile new file mode 100644 index 00000000000000..8da5a19efe0f0c --- /dev/null +++ b/drivers/iio/common/aop_sensors/Makefile @@ -0,0 +1,4 @@ +# SPDX-License-Identifier: GPL-2.0-only OR MIT + +obj-$(CONFIG_IIO_AOP_SENSOR_LAS) += aop_las.o +obj-$(CONFIG_IIO_AOP_SENSOR_ALS) += aop_als.o diff --git a/drivers/iio/common/aop_sensors/aop_als.rs b/drivers/iio/common/aop_sensors/aop_als.rs new file mode 100644 index 00000000000000..324316c4be3465 --- /dev/null +++ b/drivers/iio/common/aop_sensors/aop_als.rs @@ -0,0 +1,151 @@ +// SPDX-License-Identifier: GPL-2.0-only OR MIT + +//! Apple AOP ambient light sensor driver +//! +//! Copyright (C) The Asahi Linux Contributors + +use kernel::{ + bindings, c_str, + device::Core, + firmware::Firmware, + iio::common::aop_sensors::{AopSensorData, IIORegistration, MessageProcessor}, + module_platform_driver, of, platform, + prelude::*, + soc::apple::aop::{EPICService, AOP}, + sync::Arc, + types::ForeignOwnable, +}; + +const EPIC_SUBTYPE_GET_AOP_PROPERTY: u16 = 0xa; +const EPIC_SUBTYPE_SET_ALS_PROPERTY: u16 = 0x4; +const LUX_OFFSET_CT720: usize = 0x1d; +const LUX_OFFSET_VD6286: usize = 0x28; + +fn get_lux_offset(aop: &dyn AOP, dev: &platform::Device, svc: &EPICService) -> Result { + let name = get_aop_property(aop, svc, 0xf, 16)?.1; + match name.as_slice() { + b"Redbird\0" => Ok(LUX_OFFSET_VD6286), + b"FireFish2\0" => Ok(LUX_OFFSET_CT720), + _ => { + dev_warn!( + dev.as_ref(), + "Unknown sensor type {:?}", + str::from_utf8(&name) + ); + Err(EIO) + } + } +} + +fn enable_als(aop: &dyn AOP, dev: &platform::Device, svc: &EPICService) -> Result<()> { + let fw = Firmware::request(c_str!("apple/aop-als-cal.bin"), dev.as_ref())?; + set_als_property(aop, svc, 0xb, fw.data())?; + set_als_property(aop, svc, 0, &200000u32.to_le_bytes())?; + + Ok(()) +} + +fn get_aop_property( + aop: &dyn AOP, + svc: &EPICService, + tag: u32, + data_len: usize, +) -> Result<(u32, KVec)> { + let mut buf = KVec::new(); + buf.resize(8, 0, GFP_KERNEL)?; + buf[4..8].copy_from_slice(&tag.to_le_bytes()); + aop.epic_call_ret(svc, EPIC_SUBTYPE_GET_AOP_PROPERTY, &buf, data_len) +} + +fn set_als_property(aop: &dyn AOP, svc: &EPICService, tag: u32, data: &[u8]) -> Result { + let mut buf = KVec::new(); + buf.resize(data.len() + 8, 0, GFP_KERNEL)?; + buf[8..].copy_from_slice(data); + buf[4..8].copy_from_slice(&tag.to_le_bytes()); + aop.epic_call(svc, EPIC_SUBTYPE_SET_ALS_PROPERTY, &buf) +} + +fn f32_to_u32(f: u32) -> u32 { + if f & 0x80000000 != 0 { + return 0; + } + let exp = ((f & 0x7f800000) >> 23) as i32 - 127; + if exp < 0 { + return 0; + } + if exp == 128 && f & 0x7fffff != 0 { + return 0; + } + let mant = f & 0x7fffff | 0x800000; + if exp <= 23 { + return mant >> (23 - exp); + } + if exp >= 32 { + return u32::MAX; + } + mant << (exp - 23) +} + +struct MsgProc(usize); + +impl MessageProcessor for MsgProc { + fn process(&self, message: &[u8]) -> u32 { + let offset = self.0; + let raw = u32::from_le_bytes(message[offset..offset + 4].try_into().unwrap()); + f32_to_u32(raw) + } +} + +#[repr(transparent)] +struct IIOAopAlsDriver(IIORegistration); + +kernel::of_device_table!( + OF_TABLE, + MODULE_OF_TABLE, + (), + [(of::DeviceId::new(c_str!("apple,aop-als")), ())] +); + +impl platform::Driver for IIOAopAlsDriver { + type IdInfo = (); + + const OF_ID_TABLE: Option> = Some(&OF_TABLE); + + fn probe( + pdev: &platform::Device, + _info: Option<&()>, + ) -> Result>> { + let dev = pdev.as_ref(); + let parent = dev.parent().unwrap(); + // SAFETY: our parent is AOP, and AopDriver is repr(transparent) for Arc + let adata_ptr = unsafe { Pin::>>::borrow(parent.get_drvdata()) }; + let adata = (&*adata_ptr).clone(); + // SAFETY: AOP sets the platform data correctly + let service = unsafe { *((*dev.as_raw()).platform_data as *const EPICService) }; + let ty = bindings::BINDINGS_IIO_LIGHT; + let offset = get_lux_offset(adata.as_ref(), pdev, &service)?; + let data = AopSensorData::new(dev.into(), ty, MsgProc(offset))?; + adata.add_fakehid_listener(service, data.clone())?; + enable_als(adata.as_ref(), pdev, &service)?; + let info_mask = 1 << bindings::BINDINGS_IIO_CHAN_INFO_PROCESSED; + Ok(KBox::pin( + IIOAopAlsDriver(IIORegistration::::new( + data, + c_str!("aop-sensors-als"), + ty, + info_mask, + &THIS_MODULE, + )?), + GFP_KERNEL, + )?) + } +} + +module_platform_driver! { + type: IIOAopAlsDriver, + name: "iio_aop_als", + description: "AOP ambient light sensor driver", + license: "Dual MIT/GPL", + alias: ["platform:iio_aop_als"], + firmware: ["apple/aop-als-cal.bin"], +} diff --git a/drivers/iio/common/aop_sensors/aop_las.rs b/drivers/iio/common/aop_sensors/aop_las.rs new file mode 100644 index 00000000000000..35bb8ec35e28f4 --- /dev/null +++ b/drivers/iio/common/aop_sensors/aop_las.rs @@ -0,0 +1,76 @@ +// SPDX-License-Identifier: GPL-2.0-only OR MIT + +//! Apple AOP lid angle sensor driver +//! +//! Copyright (C) The Asahi Linux Contributors + +use kernel::{ + bindings, c_str, + device::Core, + iio::common::aop_sensors::{AopSensorData, IIORegistration, MessageProcessor}, + module_platform_driver, of, platform, + prelude::*, + soc::apple::aop::{EPICService, AOP}, + sync::Arc, + types::ForeignOwnable, +}; + +struct MsgProc; + +impl MessageProcessor for MsgProc { + fn process(&self, message: &[u8]) -> u32 { + message[1] as u32 + } +} + +#[repr(transparent)] +struct IIOAopLasDriver(IIORegistration); + +kernel::of_device_table!( + OF_TABLE, + MODULE_OF_TABLE, + (), + [(of::DeviceId::new(c_str!("apple,aop-las")), ())] +); + +impl platform::Driver for IIOAopLasDriver { + type IdInfo = (); + + const OF_ID_TABLE: Option> = Some(&OF_TABLE); + + fn probe( + pdev: &platform::Device, + _info: Option<&()>, + ) -> Result>> { + let dev = pdev.as_ref(); + let parent = dev.parent().unwrap(); + // SAFETY: our parent is AOP, and AopDriver is repr(transparent) for Arc + let adata_ptr = unsafe { Pin::>>::borrow(parent.get_drvdata()) }; + let adata = (&*adata_ptr).clone(); + // SAFETY: AOP sets the platform data correctly + let service = unsafe { *((*dev.as_raw()).platform_data as *const EPICService) }; + + let ty = bindings::BINDINGS_IIO_ANGL; + let data = AopSensorData::new(dev.into(), ty, MsgProc)?; + adata.add_fakehid_listener(service, data.clone())?; + let info_mask = 1 << bindings::BINDINGS_IIO_CHAN_INFO_RAW; + Ok(KBox::pin( + IIOAopLasDriver(IIORegistration::::new( + data, + c_str!("aop-sensors-las"), + ty, + info_mask, + &THIS_MODULE, + )?), + GFP_KERNEL, + )?) + } +} + +module_platform_driver! { + type: IIOAopLasDriver, + name: "iio_aop_las", + description: "AOP lid angle sensor driver", + license: "Dual MIT/GPL", + alias: ["platform:iio_aop_las"], +} diff --git a/rust/kernel/iio/common/aop_sensors.rs b/rust/kernel/iio/common/aop_sensors.rs new file mode 100644 index 00000000000000..de0835784b66a8 --- /dev/null +++ b/rust/kernel/iio/common/aop_sensors.rs @@ -0,0 +1,167 @@ +// SPDX-License-Identifier: GPL-2.0-only OR MIT + +//! Apple AOP sensors common code +//! +//! Copyright (C) The Asahi Linux Contributors + +use core::marker::{PhantomData, PhantomPinned}; +use core::ptr; +use core::sync::atomic::{AtomicU32, Ordering}; + +use kernel::{ + bindings, device, + prelude::*, + soc::apple::aop::FakehidListener, + sync::Arc, + types::{ARef, ForeignOwnable}, + ThisModule, +}; + +/// TODO: add documentation +pub trait MessageProcessor { + /// TODO: add documentation + fn process(&self, message: &[u8]) -> u32; +} + +/// TODO: add documentation +pub struct AopSensorData { + dev: ARef, + ty: u32, + value: AtomicU32, + msg_proc: T, +} + +impl AopSensorData { + /// TODO: add documentation + pub fn new(dev: ARef, ty: u32, msg_proc: T) -> Result>> { + Ok(Arc::new( + AopSensorData { + dev, + ty, + value: AtomicU32::new(0), + msg_proc, + }, + GFP_KERNEL, + )?) + } +} + +impl FakehidListener for AopSensorData { + fn process_fakehid_report(&self, data: &[u8]) -> Result<()> { + self.value + .store(self.msg_proc.process(data), Ordering::Relaxed); + Ok(()) + } +} + +unsafe extern "C" fn aop_read_raw( + dev: *mut bindings::iio_dev, + chan: *const bindings::iio_chan_spec, + val: *mut i32, + _: *mut i32, + mask: isize, +) -> i32 { + let data = unsafe { Arc::>::borrow((*dev).priv_.cast()) }; + let ty = unsafe { (*chan).type_ }; + if mask != bindings::BINDINGS_IIO_CHAN_INFO_PROCESSED as isize + && mask != bindings::BINDINGS_IIO_CHAN_INFO_RAW as isize + { + return EINVAL.to_errno(); + } + if data.ty != ty { + return EINVAL.to_errno(); + } + let value = data.value.load(Ordering::Relaxed); + unsafe { + *val = value as i32; + } + bindings::IIO_VAL_INT as i32 +} + +struct IIOSpec { + spec: [bindings::iio_chan_spec; 1], + vtable: bindings::iio_info, + _p: PhantomPinned, +} + +/// TODO: add documentation +pub struct IIORegistration { + dev: *mut bindings::iio_dev, + spec: Pin>, + registered: bool, + _p: PhantomData>, +} + +impl IIORegistration { + /// TODO: add documentation + pub fn new( + data: Arc>, + name: &'static CStr, + ty: u32, + info_mask: usize, + module: &ThisModule, + ) -> Result { + let spec = KBox::pin( + IIOSpec { + spec: [bindings::iio_chan_spec { + type_: ty, + __bindgen_anon_1: bindings::iio_chan_spec__bindgen_ty_1 { + scan_type: bindings::iio_scan_type { + sign: b'u' as _, + realbits: 32, + storagebits: 32, + ..Default::default() + }, + }, + info_mask_separate: info_mask, + ..Default::default() + }], + vtable: bindings::iio_info { + read_raw: Some(aop_read_raw::), + ..Default::default() + }, + _p: PhantomPinned, + }, + GFP_KERNEL, + )?; + let mut this = IIORegistration { + dev: ptr::null_mut(), + spec, + registered: false, + _p: PhantomData, + }; + this.dev = unsafe { bindings::iio_device_alloc(data.dev.as_raw(), 0) }; + unsafe { + (*this.dev).priv_ = data.clone().into_foreign().cast(); + (*this.dev).name = name.as_ptr() as _; + // spec is now pinned + (*this.dev).channels = this.spec.spec.as_ptr(); + (*this.dev).num_channels = this.spec.spec.len() as i32; + (*this.dev).info = &this.spec.vtable; + } + let ret = unsafe { bindings::__iio_device_register(this.dev, module.as_ptr()) }; + if ret < 0 { + dev_err!(data.dev, "Unable to register iio sensor"); + return Err(Error::from_errno(ret)); + } + this.registered = true; + Ok(this) + } +} + +impl Drop for IIORegistration { + fn drop(&mut self) { + if self.dev != ptr::null_mut() { + unsafe { + if self.registered { + bindings::iio_device_unregister(self.dev); + } + Arc::>::from_foreign((*self.dev).priv_.cast()); + bindings::iio_device_free(self.dev); + } + } + } +} + +unsafe impl Send for IIORegistration {} +unsafe impl Sync for IIORegistration {} diff --git a/rust/kernel/iio/common/mod.rs b/rust/kernel/iio/common/mod.rs new file mode 100644 index 00000000000000..570644ce0938a7 --- /dev/null +++ b/rust/kernel/iio/common/mod.rs @@ -0,0 +1,11 @@ +// SPDX-License-Identifier: GPL-2.0 OR MIT + +//! IIO common modules + +#[cfg(any( + CONFIG_IIO_AOP_SENSOR_LAS = "y", + CONFIG_IIO_AOP_SENSOR_ALS = "m", + CONFIG_IIO_AOP_SENSOR_LAS = "y", + CONFIG_IIO_AOP_SENSOR_ALS = "m", +))] +pub mod aop_sensors; diff --git a/rust/kernel/iio/mod.rs b/rust/kernel/iio/mod.rs new file mode 100644 index 00000000000000..b0cb308f0b454c --- /dev/null +++ b/rust/kernel/iio/mod.rs @@ -0,0 +1,5 @@ +// SPDX-License-Identifier: GPL-2.0 or MIT + +//! Industrial IO drivers + +pub mod common; diff --git a/rust/kernel/lib.rs b/rust/kernel/lib.rs index 0a1bc71776b3b7..0ca5328c68c48f 100644 --- a/rust/kernel/lib.rs +++ b/rust/kernel/lib.rs @@ -110,6 +110,8 @@ pub mod firmware; pub mod fmt; pub mod fs; pub mod id_pool; +#[cfg(CONFIG_IIO)] +pub mod iio; pub mod init; pub mod io; pub mod ioctl; From 020498deeed161ac66be55495c8cdb0eea47903d Mon Sep 17 00:00:00 2001 From: Sasha Finkelstein Date: Sun, 10 Nov 2024 23:22:21 +0100 Subject: [PATCH 1757/3902] rust: soc: apple: Add Apple mailbox abstractions Signed-off-by: Sasha Finkelstein --- drivers/soc/apple/Kconfig | 6 + drivers/soc/apple/mailbox.c | 2 +- drivers/soc/apple/rtkit-internal.h | 2 +- .../linux}/soc/apple/mailbox.h | 0 rust/bindings/bindings_helper.h | 1 + rust/kernel/soc/apple/mailbox.rs | 103 ++++++++++++++++++ rust/kernel/soc/apple/mod.rs | 3 + 7 files changed, 115 insertions(+), 2 deletions(-) rename {drivers => include/linux}/soc/apple/mailbox.h (100%) create mode 100644 rust/kernel/soc/apple/mailbox.rs diff --git a/drivers/soc/apple/Kconfig b/drivers/soc/apple/Kconfig index fac8fa57335e12..076b456e39e44f 100644 --- a/drivers/soc/apple/Kconfig +++ b/drivers/soc/apple/Kconfig @@ -38,6 +38,12 @@ config APPLE_SART Say 'y' here if you have an Apple SoC. +config RUST_APPLE_MAILBOX + bool + depends on PM + depends on RUST + select APPLE_MAILBOX + config RUST_APPLE_RTKIT bool depends on PM diff --git a/drivers/soc/apple/mailbox.c b/drivers/soc/apple/mailbox.c index 8f29108dc69ac9..186ed4f80a2711 100644 --- a/drivers/soc/apple/mailbox.c +++ b/drivers/soc/apple/mailbox.c @@ -28,9 +28,9 @@ #include #include #include +#include #include #include -#include "mailbox.h" #define APPLE_ASC_MBOX_CONTROL_FULL BIT(16) #define APPLE_ASC_MBOX_CONTROL_EMPTY BIT(17) diff --git a/drivers/soc/apple/rtkit-internal.h b/drivers/soc/apple/rtkit-internal.h index b8d5244678f010..c82065a8bf7b03 100644 --- a/drivers/soc/apple/rtkit-internal.h +++ b/drivers/soc/apple/rtkit-internal.h @@ -15,9 +15,9 @@ #include #include #include +#include #include #include -#include "mailbox.h" #define APPLE_RTKIT_APP_ENDPOINT_START 0x20 #define APPLE_RTKIT_MAX_ENDPOINTS 0x100 diff --git a/drivers/soc/apple/mailbox.h b/include/linux/soc/apple/mailbox.h similarity index 100% rename from drivers/soc/apple/mailbox.h rename to include/linux/soc/apple/mailbox.h diff --git a/rust/bindings/bindings_helper.h b/rust/bindings/bindings_helper.h index 3a99dd2962ef8e..30c96d07be2125 100644 --- a/rust/bindings/bindings_helper.h +++ b/rust/bindings/bindings_helper.h @@ -83,6 +83,7 @@ #include #include #include +#include #include #include #include diff --git a/rust/kernel/soc/apple/mailbox.rs b/rust/kernel/soc/apple/mailbox.rs new file mode 100644 index 00000000000000..19cc924e9396f1 --- /dev/null +++ b/rust/kernel/soc/apple/mailbox.rs @@ -0,0 +1,103 @@ +// SPDX-License-Identifier: GPL-2.0-only OR MIT + +//! Support for Apple ASC Mailbox. +//! +//! C header: [`include/linux/soc/apple/mailbox.h`](../../../../include/linux/gpio/driver.h) + +use crate::{ + bindings, device, + error::{from_err_ptr, to_result, Result}, + str::CStr, + types::{ForeignOwnable, ScopeGuard}, +}; + +use core::marker::PhantomData; + +/// 96-bit message. What it means is up to the upper layer +pub type Message = bindings::apple_mbox_msg; + +/// Mailbox receive callback +pub trait MailCallback { + /// Callback context + type Data: ForeignOwnable + Send + Sync; + + /// The actual callback. Called in an interrupt context. + fn recv_message(data: ::Borrowed<'_>, msg: Message); +} + +/// Wrapper over `struct apple_mbox *` +#[repr(transparent)] +pub struct Mailbox { + mbox: *mut bindings::apple_mbox, + _p: PhantomData, +} + +extern "C" fn mailbox_rx_callback( + _mbox: *mut bindings::apple_mbox, + msg: Message, + cookie: *mut crate::ffi::c_void, +) { + // SAFETY: cookie came from a call to `into_foreign` + T::recv_message(unsafe { T::Data::borrow(cookie.cast()) }, msg); +} + +impl Mailbox { + /// Creates a mailbox for the specified name. + pub fn new_byname( + dev: &device::Device, + mbox_name: &'static CStr, + data: T::Data, + ) -> Result> { + let ptr: *mut crate::ffi::c_void = data.into_foreign().cast(); + let guard = ScopeGuard::new(|| { + // SAFETY: `ptr` came from a previous call to `into_foreign`. + unsafe { T::Data::from_foreign(ptr.cast()) }; + }); + // SAFETY: Just calling the c function, all values are valid. + let mbox = unsafe { + from_err_ptr(bindings::apple_mbox_get_byname( + dev.as_raw(), + mbox_name.as_char_ptr(), + ))? + }; + // SAFETY: mbox is a valid pointer + unsafe { + (*mbox).cookie = ptr; + (*mbox).rx = Some(mailbox_rx_callback::); + to_result(bindings::apple_mbox_start(mbox))?; + } + guard.dismiss(); + Ok(Mailbox { + mbox, + _p: PhantomData, + }) + } + /// Sends the specified message + pub fn send(&self, msg: Message, atomic: bool) -> Result<()> { + // SAFETY: Calling the c function, `mbox` is a valid pointer + to_result(unsafe { bindings::apple_mbox_send(self.mbox, msg, atomic) }) + } +} + +impl Drop for Mailbox { + fn drop(&mut self) { + // SAFETY: mbox is a valid pointer + unsafe { bindings::apple_mbox_stop(self.mbox) }; + // SAFETY: `cookie` came from `into_foreign` + unsafe { T::Data::from_foreign((*self.mbox).cookie.cast()) }; + } +} + +unsafe impl Sync for Mailbox +where + T: MailCallback, + T::Data: Sync, +{ +} + +unsafe impl Send for Mailbox +where + T: MailCallback, + T::Data: Send, +{ +} diff --git a/rust/kernel/soc/apple/mod.rs b/rust/kernel/soc/apple/mod.rs index d55080b80e2201..e77eba782a5867 100644 --- a/rust/kernel/soc/apple/mod.rs +++ b/rust/kernel/soc/apple/mod.rs @@ -7,3 +7,6 @@ pub mod rtkit; #[cfg(any(CONFIG_APPLE_AOP = "y", CONFIG_APPLE_AOP = "m"))] pub mod aop; + +#[cfg(CONFIG_RUST_APPLE_MAILBOX = "y")] +pub mod mailbox; From cfe7ddbb4294b6f8c3bac57c448d0dfe9b92db30 Mon Sep 17 00:00:00 2001 From: Sasha Finkelstein Date: Sun, 10 Nov 2024 23:24:07 +0100 Subject: [PATCH 1758/3902] soc: apple: Add SEP driver. This is a co-processor in charge of various security-related features on Apple SoCs. This driver only boots the firmware, which is needed to unlock the mic secure disable on certain laptop models. Signed-off-by: Sasha Finkelstein --- drivers/soc/apple/Kconfig | 13 ++ drivers/soc/apple/Makefile | 2 + drivers/soc/apple/sep.rs | 353 +++++++++++++++++++++++++++++++++++++ 3 files changed, 368 insertions(+) create mode 100644 drivers/soc/apple/sep.rs diff --git a/drivers/soc/apple/Kconfig b/drivers/soc/apple/Kconfig index 076b456e39e44f..5babdf2454ad98 100644 --- a/drivers/soc/apple/Kconfig +++ b/drivers/soc/apple/Kconfig @@ -62,6 +62,19 @@ config APPLE_AOP Say 'y' here if you have an Apple laptop. +config APPLE_SEP + tristate "Apple Secure Element Processor" + depends on ARCH_APPLE || COMPILE_TEST + depends on PM + depends on RUST + select RUST_APPLE_RTKIT + select RUST_APPLE_MAILBOX + help + A security co-processor persent on Apple SoCs, controlling transparent + disk encryption, secure boot, HDCP, biometric auth and probably more. + + Say 'y' here if you have an Apple SoC. + endmenu endif diff --git a/drivers/soc/apple/Makefile b/drivers/soc/apple/Makefile index 17af8e2b82d298..eeeaa50eaaefb3 100644 --- a/drivers/soc/apple/Makefile +++ b/drivers/soc/apple/Makefile @@ -10,3 +10,5 @@ obj-$(CONFIG_APPLE_SART) += apple-sart.o apple-sart-y = sart.o obj-$(CONFIG_APPLE_AOP) += aop.o + +obj-$(CONFIG_APPLE_SEP) += sep.o diff --git a/drivers/soc/apple/sep.rs b/drivers/soc/apple/sep.rs new file mode 100644 index 00000000000000..228310cc2ae822 --- /dev/null +++ b/drivers/soc/apple/sep.rs @@ -0,0 +1,353 @@ +// SPDX-License-Identifier: GPL-2.0-only OR MIT +#![recursion_limit = "2048"] + +//! Apple SEP driver +//! +//! Copyright (C) The Asahi Linux Contributors + +use core::sync::atomic::{AtomicBool, Ordering}; + +use kernel::{ + bindings, c_str, device, dma, module_platform_driver, new_mutex, of, platform, + prelude::*, + soc::apple::mailbox::{MailCallback, Mailbox, Message}, + sync::{Arc, Mutex}, + types::{ARef, ForeignOwnable}, + workqueue::{self, impl_has_work, new_work, Work, WorkItem}, +}; + +const SHMEM_SIZE: usize = 0x30000; +const MSG_BOOT_TZ0: u64 = 0x5; +const MSG_BOOT_IMG4: u64 = 0x6; +const MSG_SET_SHMEM: u64 = 0x18; +const MSG_BOOT_TZ0_ACK1: u64 = 0x69; +const MSG_BOOT_TZ0_ACK2: u64 = 0xD2; +const MSG_BOOT_IMG4_ACK: u64 = 0x6A; +const MSG_ADVERTISE_EP: u64 = 0; +const EP_DISCOVER: u64 = 0xFD; +const EP_SHMEM: u64 = 0xFE; +const EP_BOOT: u64 = 0xFF; + +const MSG_TYPE_SHIFT: u32 = 16; +const MSG_TYPE_MASK: u64 = 0xFF; +//const MSG_PARAM_SHIFT: u32 = 24; +//const MSG_PARAM_MASK: u64 = 0xFF; + +const MSG_EP_MASK: u64 = 0xFF; +const MSG_DATA_SHIFT: u32 = 32; + +const IOVA_SHIFT: u32 = 0xC; + +type ShMem = dma::CoherentAllocation; + +fn align_up(v: usize, a: usize) -> usize { + (v + a - 1) & !(a - 1) +} + +fn memcpy_to_iomem(iomem: &mut ShMem, off: usize, src: &[u8]) -> Result<()> { + // SAFETY: + // as_slice_mut() checks that off and src.len() are whithin iomem's limits. + // memcpy_to_iomem is only called from within probe() ansuring there are no + // concurrent read and write accesses to the same region while the slice is + // alive per as_slice_mut()'s requiremnts. + unsafe { + let target = iomem.as_slice_mut(off, src.len())?; + target.copy_from_slice(src); + } + Ok(()) +} + +fn build_shmem(dev: &platform::Device) -> Result { + let fwnode = dev.as_ref().fwnode().ok_or(EIO)?; + let mut iomem = + dma::CoherentAllocation::::alloc_coherent(dev.as_ref(), SHMEM_SIZE, GFP_KERNEL)?; + + let panic_offset = 0x4000; + let panic_size = 0x8000; + memcpy_to_iomem(&mut iomem, panic_offset, &1u32.to_le_bytes())?; + + let lpol_offset = panic_offset + panic_size; + let lpol_prop_name = c_str!("local-policy-manifest"); + let lpol_prop_size = fwnode.property_count_elem::(lpol_prop_name)?; + let lpol = fwnode + .property_read_array_vec(lpol_prop_name, lpol_prop_size)? + .required_by(dev.as_ref())?; + memcpy_to_iomem( + &mut iomem, + lpol_offset, + &(lpol_prop_size as u32).to_le_bytes(), + )?; + memcpy_to_iomem(&mut iomem, lpol_offset + 4, &lpol)?; + let lpol_size = align_up(lpol_prop_size + 4, 0x4000); + + let ibot_offset = lpol_offset + lpol_size; + let ibot_prop_name = c_str!("iboot-manifest"); + let ibot_prop_size = fwnode.property_count_elem::(ibot_prop_name)?; + let ibot = fwnode + .property_read_array_vec(ibot_prop_name, ibot_prop_size)? + .required_by(dev.as_ref())?; + memcpy_to_iomem( + &mut iomem, + ibot_offset, + &(ibot_prop_size as u32).to_le_bytes(), + )?; + memcpy_to_iomem(&mut iomem, ibot_offset + 4, &ibot)?; + let ibot_size = align_up(ibot_prop_size + 4, 0x4000); + + memcpy_to_iomem(&mut iomem, 0, b"CNIP")?; + memcpy_to_iomem(&mut iomem, 4, &(panic_size as u32).to_le_bytes())?; + memcpy_to_iomem(&mut iomem, 8, &(panic_offset as u32).to_le_bytes())?; + + memcpy_to_iomem(&mut iomem, 16, b"OPLA")?; + memcpy_to_iomem(&mut iomem, 16 + 4, &(lpol_size as u32).to_le_bytes())?; + memcpy_to_iomem(&mut iomem, 16 + 8, &(lpol_offset as u32).to_le_bytes())?; + + memcpy_to_iomem(&mut iomem, 32, b"IPIS")?; + memcpy_to_iomem(&mut iomem, 32 + 4, &(ibot_size as u32).to_le_bytes())?; + memcpy_to_iomem(&mut iomem, 32 + 8, &(ibot_offset as u32).to_le_bytes())?; + + memcpy_to_iomem(&mut iomem, 48, b"llun")?; + Ok(iomem) +} + +#[pin_data] +struct SepReceiveWork { + data: Arc, + msg: Message, + #[pin] + work: Work, +} + +impl_has_work! { + impl HasWork for SepReceiveWork { self.work } +} + +impl SepReceiveWork { + fn new(data: Arc, msg: Message) -> Result> { + Arc::pin_init( + pin_init!(SepReceiveWork { + data, + msg, + work <- new_work!("SepReceiveWork::work"), + }), + GFP_ATOMIC, + ) + } +} + +impl WorkItem for SepReceiveWork { + type Pointer = Arc; + + fn run(this: Arc) { + this.data.process_message(this.msg); + } +} + +struct FwRegionParams { + addr: u64, + size: usize, +} + +#[pin_data] +struct SepData { + dev: ARef, + #[pin] + mbox: Mutex>>, + shmem: ShMem, + region_params: FwRegionParams, + fw_mapped: AtomicBool, +} + +impl SepData { + fn new( + dev: &platform::Device, + region_params: FwRegionParams, + ) -> Result> { + Arc::pin_init( + try_pin_init!(SepData { + shmem: build_shmem(dev)?, + dev: ARef::::from(dev.as_ref()), + mbox <- new_mutex!(None), + region_params, + fw_mapped: AtomicBool::new(false), + }), + GFP_KERNEL, + ) + } + fn start(&self) -> Result<()> { + self.mbox.lock().as_ref().unwrap().send( + Message { + msg0: EP_BOOT | (MSG_BOOT_TZ0 << MSG_TYPE_SHIFT), + msg1: 0, + }, + false, + ) + } + fn load_fw_and_shmem(&self) -> Result<()> { + let fw_addr = unsafe { + let res = bindings::dma_map_resource( + self.dev.as_raw(), + self.region_params.addr, + self.region_params.size, + bindings::dma_data_direction_DMA_TO_DEVICE, + 0, + ); + if bindings::dma_mapping_error(self.dev.as_raw(), res) != 0 { + dev_err!(self.dev, "Failed to map firmware"); + return Err(ENOMEM); + } + self.fw_mapped.store(true, Ordering::Relaxed); + res >> IOVA_SHIFT + }; + let guard = self.mbox.lock(); + let mbox = guard.as_ref().unwrap(); + mbox.send( + Message { + msg0: EP_BOOT | (MSG_BOOT_IMG4 << MSG_TYPE_SHIFT) | (fw_addr << MSG_DATA_SHIFT), + msg1: 0, + }, + false, + )?; + let shm_addr = self.shmem.dma_handle() >> IOVA_SHIFT; + mbox.send( + Message { + msg0: EP_SHMEM | (MSG_SET_SHMEM << MSG_TYPE_SHIFT) | (shm_addr << MSG_DATA_SHIFT), + msg1: 0, + }, + false, + )?; + Ok(()) + } + fn process_boot_msg(&self, msg: Message) { + let ty = (msg.msg0 >> MSG_TYPE_SHIFT) & MSG_TYPE_MASK; + match ty { + MSG_BOOT_TZ0_ACK1 => {} + MSG_BOOT_TZ0_ACK2 => { + let res = self.load_fw_and_shmem(); + if let Err(e) = res { + dev_err!(self.dev, "Unable to load firmware: {:?}", e); + } + } + MSG_BOOT_IMG4_ACK => {} + _ => { + dev_err!(self.dev, "Unknown boot message type: {}", ty); + } + } + } + fn process_discover_msg(&self, msg: Message) { + let ty = (msg.msg0 >> MSG_TYPE_SHIFT) & MSG_TYPE_MASK; + //let data = (msg.msg0 >> MSG_DATA_SHIFT) as u32; + //let param = (msg.msg0 >> MSG_PARAM_SHIFT) & MSG_PARAM_MASK; + match ty { + MSG_ADVERTISE_EP => { + /*dev_info!( + self.dev, + "Got endpoint {:?} at {}", + core::str::from_utf8(&data.to_be_bytes()), + param + );*/ + } + _ => { + //dev_warn!(self.dev, "Unknown discovery message type: {}", ty); + } + } + } + fn process_message(&self, msg: Message) { + let ep = msg.msg0 & MSG_EP_MASK; + match ep { + EP_BOOT => self.process_boot_msg(msg), + EP_DISCOVER => self.process_discover_msg(msg), + _ => {} // dev_warn!(self.dev, "Message from unknown endpoint: {}", ep), + } + } + fn remove(&self) { + *self.mbox.lock() = None; + if self.fw_mapped.load(Ordering::Relaxed) { + unsafe { + bindings::dma_unmap_resource( + self.dev.as_raw(), + self.region_params.addr, + self.region_params.size, + bindings::dma_data_direction_DMA_TO_DEVICE, + 0, + ); + } + } + } +} + +impl MailCallback for SepData { + type Data = Arc; + fn recv_message(data: ::Borrowed<'_>, msg: Message) { + let work = SepReceiveWork::new(data.into(), msg); + if let Ok(work) = work { + let res = workqueue::system().enqueue(work); + if res.is_err() { + dev_err!( + data.dev, + "Unable to schedule work item for message {}", + msg.msg0 + ); + } + } else { + dev_err!( + data.dev, + "Unable to allocate work item for message {}", + msg.msg0 + ); + } + } +} + +unsafe impl Send for SepData {} +unsafe impl Sync for SepData {} + +struct SepDriver(Arc); + +kernel::of_device_table!( + OF_TABLE, + MODULE_OF_TABLE, + (), + [(of::DeviceId::new(c_str!("apple,sep")), ())] +); + +impl platform::Driver for SepDriver { + type IdInfo = (); + + const OF_ID_TABLE: Option> = Some(&OF_TABLE); + + fn probe( + pdev: &platform::Device, + _info: Option<&()>, + ) -> Result>> { + let of = pdev.as_ref().of_node().ok_or(EIO)?; + let res = of.reserved_mem_region_to_resource_byname(c_str!("sepfw"))?; + let data = SepData::new( + pdev, + FwRegionParams { + addr: res.start(), + size: res.size().try_into()?, + }, + )?; + *data.mbox.lock() = Some(Mailbox::new_byname( + pdev.as_ref(), + c_str!("mbox"), + data.clone(), + )?); + data.start()?; + Ok(KBox::pin(SepDriver(data), GFP_KERNEL)?) + } +} + +impl Drop for SepDriver { + fn drop(&mut self) { + self.0.remove(); + } +} + +module_platform_driver! { + type: SepDriver, + name: "apple_sep", + description: "Secure enclave processor stub driver", + license: "Dual MIT/GPL", +} From 0522222f2ac243ca69e245ab856ce6e047d09781 Mon Sep 17 00:00:00 2001 From: Fernand Sieber Date: Thu, 6 Nov 2025 12:40:10 +0200 Subject: [PATCH 1759/3902] sched/proxy: Yield the donor task MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit commit 127b90315ca07ccad2618db7ba950a63e3b32d22 upstream. When executing a task in proxy context, handle yields as if they were requested by the donor task. This matches the traditional PI semantics of yield() as well. This avoids scenario like proxy task yielding, pick next task selecting the same previous blocked donor, running the proxy task again, etc. Reported-by: kernel test robot Closes: https://lore.kernel.org/oe-lkp/202510211205.1e0f5223-lkp@intel.com Suggested-by: Peter Zijlstra Signed-off-by: Fernand Sieber Signed-off-by: Peter Zijlstra (Intel) Link: https://patch.msgid.link/20251106104022.195157-1-sieberf@amazon.com Cc: Holger Hoffstätte Signed-off-by: Greg Kroah-Hartman --- kernel/sched/deadline.c | 2 +- kernel/sched/ext.c | 4 ++-- kernel/sched/fair.c | 2 +- kernel/sched/rt.c | 2 +- kernel/sched/syscalls.c | 5 +++-- 5 files changed, 8 insertions(+), 7 deletions(-) diff --git a/kernel/sched/deadline.c b/kernel/sched/deadline.c index 19b1a8b81c76ce..d3be71d5a9ccc9 100644 --- a/kernel/sched/deadline.c +++ b/kernel/sched/deadline.c @@ -2143,7 +2143,7 @@ static void yield_task_dl(struct rq *rq) * it and the bandwidth timer will wake it up and will give it * new scheduling parameters (thanks to dl_yielded=1). */ - rq->curr->dl.dl_yielded = 1; + rq->donor->dl.dl_yielded = 1; update_rq_clock(rq); update_curr_dl(rq); diff --git a/kernel/sched/ext.c b/kernel/sched/ext.c index b959a70471c104..907eea83294ce2 100644 --- a/kernel/sched/ext.c +++ b/kernel/sched/ext.c @@ -1493,7 +1493,7 @@ static bool dequeue_task_scx(struct rq *rq, struct task_struct *p, int deq_flags static void yield_task_scx(struct rq *rq) { struct scx_sched *sch = scx_root; - struct task_struct *p = rq->curr; + struct task_struct *p = rq->donor; if (SCX_HAS_OP(sch, yield)) SCX_CALL_OP_2TASKS_RET(sch, SCX_KF_REST, yield, rq, p, NULL); @@ -1504,7 +1504,7 @@ static void yield_task_scx(struct rq *rq) static bool yield_to_task_scx(struct rq *rq, struct task_struct *to) { struct scx_sched *sch = scx_root; - struct task_struct *from = rq->curr; + struct task_struct *from = rq->donor; if (SCX_HAS_OP(sch, yield)) return SCX_CALL_OP_2TASKS_RET(sch, SCX_KF_REST, yield, rq, diff --git a/kernel/sched/fair.c b/kernel/sched/fair.c index 967ca52fb23118..5c965666d16636 100644 --- a/kernel/sched/fair.c +++ b/kernel/sched/fair.c @@ -8993,7 +8993,7 @@ static void put_prev_task_fair(struct rq *rq, struct task_struct *prev, struct t */ static void yield_task_fair(struct rq *rq) { - struct task_struct *curr = rq->curr; + struct task_struct *curr = rq->donor; struct cfs_rq *cfs_rq = task_cfs_rq(curr); struct sched_entity *se = &curr->se; diff --git a/kernel/sched/rt.c b/kernel/sched/rt.c index 7936d43337313c..fb07dcfc60a244 100644 --- a/kernel/sched/rt.c +++ b/kernel/sched/rt.c @@ -1490,7 +1490,7 @@ static void requeue_task_rt(struct rq *rq, struct task_struct *p, int head) static void yield_task_rt(struct rq *rq) { - requeue_task_rt(rq, rq->curr, 0); + requeue_task_rt(rq, rq->donor, 0); } static int find_lowest_rq(struct task_struct *task); diff --git a/kernel/sched/syscalls.c b/kernel/sched/syscalls.c index 77ae87f36e8412..bf360a6fbb800e 100644 --- a/kernel/sched/syscalls.c +++ b/kernel/sched/syscalls.c @@ -1351,7 +1351,7 @@ static void do_sched_yield(void) rq = this_rq_lock_irq(&rf); schedstat_inc(rq->yld_count); - current->sched_class->yield_task(rq); + rq->donor->sched_class->yield_task(rq); preempt_disable(); rq_unlock_irq(rq, &rf); @@ -1420,12 +1420,13 @@ EXPORT_SYMBOL(yield); */ int __sched yield_to(struct task_struct *p, bool preempt) { - struct task_struct *curr = current; + struct task_struct *curr; struct rq *rq, *p_rq; int yielded = 0; scoped_guard (raw_spinlock_irqsave, &p->pi_lock) { rq = this_rq(); + curr = rq->donor; again: p_rq = task_rq(p); From 5a7ba9b599fc7b1843adc012bc84d2c2c64afed9 Mon Sep 17 00:00:00 2001 From: Danilo Krummrich Date: Tue, 28 Oct 2025 12:00:52 +0100 Subject: [PATCH 1760/3902] drm: nova: depend on CONFIG_64BIT commit ba1b40ed0e34bab597fd90d4c4e9f7397f878c8f upstream. nova-core already depends on CONFIG_64BIT, hence also depend on CONFIG_64BIT for nova-drm. Reviewed-by: Alexandre Courbot Reviewed-by: John Hubbard Link: https://patch.msgid.link/20251028110058.340320-1-dakr@kernel.org Signed-off-by: Danilo Krummrich Cc: Miguel Ojeda Signed-off-by: Greg Kroah-Hartman --- drivers/gpu/drm/nova/Kconfig | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/gpu/drm/nova/Kconfig b/drivers/gpu/drm/nova/Kconfig index bd1df08791911f..3e637ad7b5bad9 100644 --- a/drivers/gpu/drm/nova/Kconfig +++ b/drivers/gpu/drm/nova/Kconfig @@ -1,5 +1,6 @@ config DRM_NOVA tristate "Nova DRM driver" + depends on 64BIT depends on DRM=y depends on PCI depends on RUST From d75aa97c90da26ee4f29c768762061cf45c3106e Mon Sep 17 00:00:00 2001 From: "Borislav Petkov (AMD)" Date: Thu, 25 Sep 2025 13:46:00 +0200 Subject: [PATCH 1761/3902] x86/microcode/AMD: Select which microcode patch to load commit 8d171045069c804e5ffaa18be590c42c6af0cf3f upstream. All microcode patches up to the proper BIOS Entrysign fix are loaded only after the sha256 signature carried in the driver has been verified. Microcode patches after the Entrysign fix has been applied, do not need that signature verification anymore. In order to not abandon machines which haven't received the BIOS update yet, add the capability to select which microcode patch to load. The corresponding microcode container supplied through firmware-linux has been modified to carry two patches per CPU type (family/model/stepping) so that the proper one gets selected. Signed-off-by: Borislav Petkov (AMD) Tested-by: Waiman Long Link: https://patch.msgid.link/20251027133818.4363-1-bp@kernel.org Signed-off-by: Greg Kroah-Hartman --- arch/x86/kernel/cpu/microcode/amd.c | 113 ++++++++++++++++++---------- 1 file changed, 72 insertions(+), 41 deletions(-) diff --git a/arch/x86/kernel/cpu/microcode/amd.c b/arch/x86/kernel/cpu/microcode/amd.c index a881bf4c201143..3821a985f4ffe2 100644 --- a/arch/x86/kernel/cpu/microcode/amd.c +++ b/arch/x86/kernel/cpu/microcode/amd.c @@ -186,50 +186,61 @@ static u32 cpuid_to_ucode_rev(unsigned int val) return p.ucode_rev; } +static u32 get_cutoff_revision(u32 rev) +{ + switch (rev >> 8) { + case 0x80012: return 0x8001277; break; + case 0x80082: return 0x800820f; break; + case 0x83010: return 0x830107c; break; + case 0x86001: return 0x860010e; break; + case 0x86081: return 0x8608108; break; + case 0x87010: return 0x8701034; break; + case 0x8a000: return 0x8a0000a; break; + case 0xa0010: return 0xa00107a; break; + case 0xa0011: return 0xa0011da; break; + case 0xa0012: return 0xa001243; break; + case 0xa0082: return 0xa00820e; break; + case 0xa1011: return 0xa101153; break; + case 0xa1012: return 0xa10124e; break; + case 0xa1081: return 0xa108109; break; + case 0xa2010: return 0xa20102f; break; + case 0xa2012: return 0xa201212; break; + case 0xa4041: return 0xa404109; break; + case 0xa5000: return 0xa500013; break; + case 0xa6012: return 0xa60120a; break; + case 0xa7041: return 0xa704109; break; + case 0xa7052: return 0xa705208; break; + case 0xa7080: return 0xa708009; break; + case 0xa70c0: return 0xa70C009; break; + case 0xaa001: return 0xaa00116; break; + case 0xaa002: return 0xaa00218; break; + case 0xb0021: return 0xb002146; break; + case 0xb0081: return 0xb008111; break; + case 0xb1010: return 0xb101046; break; + case 0xb2040: return 0xb204031; break; + case 0xb4040: return 0xb404031; break; + case 0xb4041: return 0xb404101; break; + case 0xb6000: return 0xb600031; break; + case 0xb6080: return 0xb608031; break; + case 0xb7000: return 0xb700031; break; + default: break; + + } + return 0; +} + static bool need_sha_check(u32 cur_rev) { + u32 cutoff; + if (!cur_rev) { cur_rev = cpuid_to_ucode_rev(bsp_cpuid_1_eax); pr_info_once("No current revision, generating the lowest one: 0x%x\n", cur_rev); } - switch (cur_rev >> 8) { - case 0x80012: return cur_rev <= 0x8001277; break; - case 0x80082: return cur_rev <= 0x800820f; break; - case 0x83010: return cur_rev <= 0x830107c; break; - case 0x86001: return cur_rev <= 0x860010e; break; - case 0x86081: return cur_rev <= 0x8608108; break; - case 0x87010: return cur_rev <= 0x8701034; break; - case 0x8a000: return cur_rev <= 0x8a0000a; break; - case 0xa0010: return cur_rev <= 0xa00107a; break; - case 0xa0011: return cur_rev <= 0xa0011da; break; - case 0xa0012: return cur_rev <= 0xa001243; break; - case 0xa0082: return cur_rev <= 0xa00820e; break; - case 0xa1011: return cur_rev <= 0xa101153; break; - case 0xa1012: return cur_rev <= 0xa10124e; break; - case 0xa1081: return cur_rev <= 0xa108109; break; - case 0xa2010: return cur_rev <= 0xa20102f; break; - case 0xa2012: return cur_rev <= 0xa201212; break; - case 0xa4041: return cur_rev <= 0xa404109; break; - case 0xa5000: return cur_rev <= 0xa500013; break; - case 0xa6012: return cur_rev <= 0xa60120a; break; - case 0xa7041: return cur_rev <= 0xa704109; break; - case 0xa7052: return cur_rev <= 0xa705208; break; - case 0xa7080: return cur_rev <= 0xa708009; break; - case 0xa70c0: return cur_rev <= 0xa70C009; break; - case 0xaa001: return cur_rev <= 0xaa00116; break; - case 0xaa002: return cur_rev <= 0xaa00218; break; - case 0xb0021: return cur_rev <= 0xb002146; break; - case 0xb0081: return cur_rev <= 0xb008111; break; - case 0xb1010: return cur_rev <= 0xb101046; break; - case 0xb2040: return cur_rev <= 0xb204031; break; - case 0xb4040: return cur_rev <= 0xb404031; break; - case 0xb4041: return cur_rev <= 0xb404101; break; - case 0xb6000: return cur_rev <= 0xb600031; break; - case 0xb6080: return cur_rev <= 0xb608031; break; - case 0xb7000: return cur_rev <= 0xb700031; break; - default: break; - } + cutoff = get_cutoff_revision(cur_rev); + if (cutoff) + return cur_rev <= cutoff; pr_info("You should not be seeing this. Please send the following couple of lines to x86--kernel.org\n"); pr_info("CPUID(1).EAX: 0x%x, current revision: 0x%x\n", bsp_cpuid_1_eax, cur_rev); @@ -494,6 +505,7 @@ static int verify_patch(const u8 *buf, size_t buf_size, u32 *patch_size) { u8 family = x86_family(bsp_cpuid_1_eax); struct microcode_header_amd *mc_hdr; + u32 cur_rev, cutoff, patch_rev; u32 sh_psize; u16 proc_id; u8 patch_fam; @@ -533,11 +545,32 @@ static int verify_patch(const u8 *buf, size_t buf_size, u32 *patch_size) proc_id = mc_hdr->processor_rev_id; patch_fam = 0xf + (proc_id >> 12); - ucode_dbg("Patch-ID 0x%08x: family: 0x%x\n", mc_hdr->patch_id, patch_fam); - if (patch_fam != family) return 1; + cur_rev = get_patch_level(); + + /* No cutoff revision means old/unaffected by signing algorithm weakness => matches */ + cutoff = get_cutoff_revision(cur_rev); + if (!cutoff) + goto ok; + + patch_rev = mc_hdr->patch_id; + + ucode_dbg("cur_rev: 0x%x, cutoff: 0x%x, patch_rev: 0x%x\n", + cur_rev, cutoff, patch_rev); + + if (cur_rev <= cutoff && patch_rev <= cutoff) + goto ok; + + if (cur_rev > cutoff && patch_rev > cutoff) + goto ok; + + return 1; + +ok: + ucode_dbg("Patch-ID 0x%08x: family: 0x%x\n", mc_hdr->patch_id, patch_fam); + return 0; } @@ -606,8 +639,6 @@ static size_t parse_container(u8 *ucode, size_t size, struct cont_desc *desc) mc = (struct microcode_amd *)(buf + SECTION_HDR_SIZE); - ucode_dbg("patch_id: 0x%x\n", mc->hdr.patch_id); - if (mc_patch_matches(mc, eq_id)) { desc->psize = patch_size; desc->mc = mc; From fc83284e75274dbf99c716a5a9f370fe041abe3d Mon Sep 17 00:00:00 2001 From: Peter Zijlstra Date: Mon, 29 Dec 2025 14:35:37 -0500 Subject: [PATCH 1762/3902] sched/core: Add comment explaining force-idle vruntime snapshots [ Upstream commit 9359d9785d85bb53f1ff1738a59aeeec4b878906 ] I always end up having to re-read these emails every time I look at this code. And a future patch is going to change this story a little. This means it is past time to stick them in a comment so it can be modified and stay current. Signed-off-by: Peter Zijlstra (Intel) Link: https://lkml.kernel.org/r/20200506143506.GH5298@hirez.programming.kicks-ass.net Link: https://lkml.kernel.org/r/20200515103844.GG2978@hirez.programming.kicks-ass.net Link: https://patch.msgid.link/20251106111603.GB4068168@noisy.programming.kicks-ass.net Stable-dep-of: 79f3f9bedd14 ("sched/eevdf: Fix min_vruntime vs avg_vruntime") Signed-off-by: Sasha Levin Signed-off-by: Greg Kroah-Hartman --- kernel/sched/fair.c | 181 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 181 insertions(+) diff --git a/kernel/sched/fair.c b/kernel/sched/fair.c index 5c965666d16636..94a45a2d434222 100644 --- a/kernel/sched/fair.c +++ b/kernel/sched/fair.c @@ -13013,6 +13013,187 @@ static inline void task_tick_core(struct rq *rq, struct task_struct *curr) resched_curr(rq); } +/* + * Consider any infeasible weight scenario. Take for instance two tasks, + * each bound to their respective sibling, one with weight 1 and one with + * weight 2. Then the lower weight task will run ahead of the higher weight + * task without bound. + * + * This utterly destroys the concept of a shared time base. + * + * Remember; all this is about a proportionally fair scheduling, where each + * tasks receives: + * + * w_i + * dt_i = ---------- dt (1) + * \Sum_j w_j + * + * which we do by tracking a virtual time, s_i: + * + * 1 + * s_i = --- d[t]_i (2) + * w_i + * + * Where d[t] is a delta of discrete time, while dt is an infinitesimal. + * The immediate corollary is that the ideal schedule S, where (2) to use + * an infinitesimal delta, is: + * + * 1 + * S = ---------- dt (3) + * \Sum_i w_i + * + * From which we can define the lag, or deviation from the ideal, as: + * + * lag(i) = S - s_i (4) + * + * And since the one and only purpose is to approximate S, we get that: + * + * \Sum_i w_i lag(i) := 0 (5) + * + * If this were not so, we no longer converge to S, and we can no longer + * claim our scheduler has any of the properties we derive from S. This is + * exactly what you did above, you broke it! + * + * + * Let's continue for a while though; to see if there is anything useful to + * be learned. We can combine (1)-(3) or (4)-(5) and express S in s_i: + * + * \Sum_i w_i s_i + * S = -------------- (6) + * \Sum_i w_i + * + * Which gives us a way to compute S, given our s_i. Now, if you've read + * our code, you know that we do not in fact do this, the reason for this + * is two-fold. Firstly, computing S in that way requires a 64bit division + * for every time we'd use it (see 12), and secondly, this only describes + * the steady-state, it doesn't handle dynamics. + * + * Anyway, in (6): s_i -> x + (s_i - x), to get: + * + * \Sum_i w_i (s_i - x) + * S - x = -------------------- (7) + * \Sum_i w_i + * + * Which shows that S and s_i transform alike (which makes perfect sense + * given that S is basically the (weighted) average of s_i). + * + * Then: + * + * x -> s_min := min{s_i} (8) + * + * to obtain: + * + * \Sum_i w_i (s_i - s_min) + * S = s_min + ------------------------ (9) + * \Sum_i w_i + * + * Which already looks familiar, and is the basis for our current + * approximation: + * + * S ~= s_min (10) + * + * Now, obviously, (10) is absolute crap :-), but it sorta works. + * + * So the thing to remember is that the above is strictly UP. It is + * possible to generalize to multiple runqueues -- however it gets really + * yuck when you have to add affinity support, as illustrated by our very + * first counter-example. + * + * Luckily I think we can avoid needing a full multi-queue variant for + * core-scheduling (or load-balancing). The crucial observation is that we + * only actually need this comparison in the presence of forced-idle; only + * then do we need to tell if the stalled rq has higher priority over the + * other. + * + * [XXX assumes SMT2; better consider the more general case, I suspect + * it'll work out because our comparison is always between 2 rqs and the + * answer is only interesting if one of them is forced-idle] + * + * And (under assumption of SMT2) when there is forced-idle, there is only + * a single queue, so everything works like normal. + * + * Let, for our runqueue 'k': + * + * T_k = \Sum_i w_i s_i + * W_k = \Sum_i w_i ; for all i of k (11) + * + * Then we can write (6) like: + * + * T_k + * S_k = --- (12) + * W_k + * + * From which immediately follows that: + * + * T_k + T_l + * S_k+l = --------- (13) + * W_k + W_l + * + * On which we can define a combined lag: + * + * lag_k+l(i) := S_k+l - s_i (14) + * + * And that gives us the tools to compare tasks across a combined runqueue. + * + * + * Combined this gives the following: + * + * a) when a runqueue enters force-idle, sync it against it's sibling rq(s) + * using (7); this only requires storing single 'time'-stamps. + * + * b) when comparing tasks between 2 runqueues of which one is forced-idle, + * compare the combined lag, per (14). + * + * Now, of course cgroups (I so hate them) make this more interesting in + * that a) seems to suggest we need to iterate all cgroup on a CPU at such + * boundaries, but I think we can avoid that. The force-idle is for the + * whole CPU, all it's rqs. So we can mark it in the root and lazily + * propagate downward on demand. + */ + +/* + * So this sync is basically a relative reset of S to 0. + * + * So with 2 queues, when one goes idle, we drop them both to 0 and one + * then increases due to not being idle, and the idle one builds up lag to + * get re-elected. So far so simple, right? + * + * When there's 3, we can have the situation where 2 run and one is idle, + * we sync to 0 and let the idle one build up lag to get re-election. Now + * suppose another one also drops idle. At this point dropping all to 0 + * again would destroy the built-up lag from the queue that was already + * idle, not good. + * + * So instead of syncing everything, we can: + * + * less := !((s64)(s_a - s_b) <= 0) + * + * (v_a - S_a) - (v_b - S_b) == v_a - v_b - S_a + S_b + * == v_a - (v_b - S_a + S_b) + * + * IOW, we can recast the (lag) comparison to a one-sided difference. + * So if then, instead of syncing the whole queue, sync the idle queue + * against the active queue with S_a + S_b at the point where we sync. + * + * (XXX consider the implication of living in a cyclic group: N / 2^n N) + * + * This gives us means of syncing single queues against the active queue, + * and for already idle queues to preserve their build-up lag. + * + * Of course, then we get the situation where there's 2 active and one + * going idle, who do we pick to sync against? Theory would have us sync + * against the combined S, but as we've already demonstrated, there is no + * such thing in infeasible weight scenarios. + * + * One thing I've considered; and this is where that core_active rudiment + * came from, is having active queues sync up between themselves after + * every tick. This limits the observed divergence due to the work + * conservancy. + * + * On top of that, we can improve upon things by moving away from our + * horrible (10) hack and moving to (9) and employing (13) here. + */ + /* * se_fi_update - Update the cfs_rq->min_vruntime_fi in a CFS hierarchy if needed. */ From b29d5e3a5625e1172518ae306ffd731a166e58c5 Mon Sep 17 00:00:00 2001 From: Peter Zijlstra Date: Mon, 29 Dec 2025 14:35:38 -0500 Subject: [PATCH 1763/3902] sched/eevdf: Fix min_vruntime vs avg_vruntime [ Upstream commit 79f3f9bedd149ea438aaeb0fb6a083637affe205 ] Basically, from the constraint that the sum of lag is zero, you can infer that the 0-lag point is the weighted average of the individual vruntime, which is what we're trying to compute: \Sum w_i * v_i avg = -------------- \Sum w_i Now, since vruntime takes the whole u64 (worse, it wraps), this multiplication term in the numerator is not something we can compute; instead we do the min_vruntime (v0 henceforth) thing like: v_i = (v_i - v0) + v0 This does two things: - it keeps the key: (v_i - v0) 'small'; - it creates a relative 0-point in the modular space. If you do that subtitution and work it all out, you end up with: \Sum w_i * (v_i - v0) avg = --------------------- + v0 \Sum w_i Since you cannot very well track a ratio like that (and not suffer terrible numerical problems) we simpy track the numerator and denominator individually and only perform the division when strictly needed. Notably, the numerator lives in cfs_rq->avg_vruntime and the denominator lives in cfs_rq->avg_load. The one extra 'funny' is that these numbers track the entities in the tree, and current is typically outside of the tree, so avg_vruntime() adds current when needed before doing the division. (vruntime_eligible() elides the division by cross-wise multiplication) Anyway, as mentioned above, we currently use the CFS era min_vruntime for this purpose. However, this thing can only move forward, while the above avg can in fact move backward (when a non-eligible task leaves, the average becomes smaller), this can cause trouble when through happenstance (or construction) these values drift far enough apart to wreck the game. Replace cfs_rq::min_vruntime with cfs_rq::zero_vruntime which is kept near/at avg_vruntime, following its motion. The down-side is that this requires computing the avg more often. Fixes: 147f3efaa241 ("sched/fair: Implement an EEVDF-like scheduling policy") Reported-by: Zicheng Qu Signed-off-by: Peter Zijlstra (Intel) Link: https://patch.msgid.link/20251106111741.GC4068168@noisy.programming.kicks-ass.net Cc: stable@vger.kernel.org Signed-off-by: Sasha Levin Signed-off-by: Greg Kroah-Hartman --- kernel/sched/debug.c | 8 +-- kernel/sched/fair.c | 114 ++++++++++--------------------------------- kernel/sched/sched.h | 4 +- 3 files changed, 31 insertions(+), 95 deletions(-) diff --git a/kernel/sched/debug.c b/kernel/sched/debug.c index 02e16b70a7901e..41caa22e0680a6 100644 --- a/kernel/sched/debug.c +++ b/kernel/sched/debug.c @@ -796,7 +796,7 @@ static void print_rq(struct seq_file *m, struct rq *rq, int rq_cpu) void print_cfs_rq(struct seq_file *m, int cpu, struct cfs_rq *cfs_rq) { - s64 left_vruntime = -1, min_vruntime, right_vruntime = -1, left_deadline = -1, spread; + s64 left_vruntime = -1, zero_vruntime, right_vruntime = -1, left_deadline = -1, spread; struct sched_entity *last, *first, *root; struct rq *rq = cpu_rq(cpu); unsigned long flags; @@ -819,15 +819,15 @@ void print_cfs_rq(struct seq_file *m, int cpu, struct cfs_rq *cfs_rq) last = __pick_last_entity(cfs_rq); if (last) right_vruntime = last->vruntime; - min_vruntime = cfs_rq->min_vruntime; + zero_vruntime = cfs_rq->zero_vruntime; raw_spin_rq_unlock_irqrestore(rq, flags); SEQ_printf(m, " .%-30s: %Ld.%06ld\n", "left_deadline", SPLIT_NS(left_deadline)); SEQ_printf(m, " .%-30s: %Ld.%06ld\n", "left_vruntime", SPLIT_NS(left_vruntime)); - SEQ_printf(m, " .%-30s: %Ld.%06ld\n", "min_vruntime", - SPLIT_NS(min_vruntime)); + SEQ_printf(m, " .%-30s: %Ld.%06ld\n", "zero_vruntime", + SPLIT_NS(zero_vruntime)); SEQ_printf(m, " .%-30s: %Ld.%06ld\n", "avg_vruntime", SPLIT_NS(avg_vruntime(cfs_rq))); SEQ_printf(m, " .%-30s: %Ld.%06ld\n", "right_vruntime", diff --git a/kernel/sched/fair.c b/kernel/sched/fair.c index 94a45a2d434222..bfce451f1210ce 100644 --- a/kernel/sched/fair.c +++ b/kernel/sched/fair.c @@ -554,7 +554,7 @@ static inline bool entity_before(const struct sched_entity *a, static inline s64 entity_key(struct cfs_rq *cfs_rq, struct sched_entity *se) { - return (s64)(se->vruntime - cfs_rq->min_vruntime); + return (s64)(se->vruntime - cfs_rq->zero_vruntime); } #define __node_2_se(node) \ @@ -606,13 +606,13 @@ static inline s64 entity_key(struct cfs_rq *cfs_rq, struct sched_entity *se) * * Which we track using: * - * v0 := cfs_rq->min_vruntime + * v0 := cfs_rq->zero_vruntime * \Sum (v_i - v0) * w_i := cfs_rq->avg_vruntime * \Sum w_i := cfs_rq->avg_load * - * Since min_vruntime is a monotonic increasing variable that closely tracks - * the per-task service, these deltas: (v_i - v), will be in the order of the - * maximal (virtual) lag induced in the system due to quantisation. + * Since zero_vruntime closely tracks the per-task service, these + * deltas: (v_i - v), will be in the order of the maximal (virtual) lag + * induced in the system due to quantisation. * * Also, we use scale_load_down() to reduce the size. * @@ -671,7 +671,7 @@ u64 avg_vruntime(struct cfs_rq *cfs_rq) avg = div_s64(avg, load); } - return cfs_rq->min_vruntime + avg; + return cfs_rq->zero_vruntime + avg; } /* @@ -732,7 +732,7 @@ static int vruntime_eligible(struct cfs_rq *cfs_rq, u64 vruntime) load += weight; } - return avg >= (s64)(vruntime - cfs_rq->min_vruntime) * load; + return avg >= (s64)(vruntime - cfs_rq->zero_vruntime) * load; } int entity_eligible(struct cfs_rq *cfs_rq, struct sched_entity *se) @@ -740,42 +740,14 @@ int entity_eligible(struct cfs_rq *cfs_rq, struct sched_entity *se) return vruntime_eligible(cfs_rq, se->vruntime); } -static u64 __update_min_vruntime(struct cfs_rq *cfs_rq, u64 vruntime) +static void update_zero_vruntime(struct cfs_rq *cfs_rq) { - u64 min_vruntime = cfs_rq->min_vruntime; - /* - * open coded max_vruntime() to allow updating avg_vruntime - */ - s64 delta = (s64)(vruntime - min_vruntime); - if (delta > 0) { - avg_vruntime_update(cfs_rq, delta); - min_vruntime = vruntime; - } - return min_vruntime; -} + u64 vruntime = avg_vruntime(cfs_rq); + s64 delta = (s64)(vruntime - cfs_rq->zero_vruntime); -static void update_min_vruntime(struct cfs_rq *cfs_rq) -{ - struct sched_entity *se = __pick_root_entity(cfs_rq); - struct sched_entity *curr = cfs_rq->curr; - u64 vruntime = cfs_rq->min_vruntime; - - if (curr) { - if (curr->on_rq) - vruntime = curr->vruntime; - else - curr = NULL; - } + avg_vruntime_update(cfs_rq, delta); - if (se) { - if (!curr) - vruntime = se->min_vruntime; - else - vruntime = min_vruntime(vruntime, se->min_vruntime); - } - - /* ensure we never gain time by being placed backwards. */ - cfs_rq->min_vruntime = __update_min_vruntime(cfs_rq, vruntime); + cfs_rq->zero_vruntime = vruntime; } static inline u64 cfs_rq_min_slice(struct cfs_rq *cfs_rq) @@ -848,6 +820,7 @@ RB_DECLARE_CALLBACKS(static, min_vruntime_cb, struct sched_entity, static void __enqueue_entity(struct cfs_rq *cfs_rq, struct sched_entity *se) { avg_vruntime_add(cfs_rq, se); + update_zero_vruntime(cfs_rq); se->min_vruntime = se->vruntime; se->min_slice = se->slice; rb_add_augmented_cached(&se->run_node, &cfs_rq->tasks_timeline, @@ -859,6 +832,7 @@ static void __dequeue_entity(struct cfs_rq *cfs_rq, struct sched_entity *se) rb_erase_augmented_cached(&se->run_node, &cfs_rq->tasks_timeline, &min_vruntime_cb); avg_vruntime_sub(cfs_rq, se); + update_zero_vruntime(cfs_rq); } struct sched_entity *__pick_root_entity(struct cfs_rq *cfs_rq) @@ -1226,7 +1200,6 @@ static void update_curr(struct cfs_rq *cfs_rq) curr->vruntime += calc_delta_fair(delta_exec, curr); resched = update_deadline(cfs_rq, curr); - update_min_vruntime(cfs_rq); if (entity_is_task(curr)) { /* @@ -3808,15 +3781,6 @@ static void reweight_entity(struct cfs_rq *cfs_rq, struct sched_entity *se, if (!curr) __enqueue_entity(cfs_rq, se); cfs_rq->nr_queued++; - - /* - * The entity's vruntime has been adjusted, so let's check - * whether the rq-wide min_vruntime needs updated too. Since - * the calculations above require stable min_vruntime rather - * than up-to-date one, we do the update at the end of the - * reweight process. - */ - update_min_vruntime(cfs_rq); } } @@ -5432,15 +5396,6 @@ dequeue_entity(struct cfs_rq *cfs_rq, struct sched_entity *se, int flags) update_cfs_group(se); - /* - * Now advance min_vruntime if @se was the entity holding it back, - * except when: DEQUEUE_SAVE && !DEQUEUE_MOVE, in this case we'll be - * put back on, and if we advance min_vruntime, we'll be placed back - * further than we started -- i.e. we'll be penalized. - */ - if ((flags & (DEQUEUE_SAVE | DEQUEUE_MOVE)) != DEQUEUE_SAVE) - update_min_vruntime(cfs_rq); - if (flags & DEQUEUE_DELAYED) finish_delayed_dequeue_entity(se); @@ -9028,7 +8983,6 @@ static void yield_task_fair(struct rq *rq) if (entity_eligible(cfs_rq, se)) { se->vruntime = se->deadline; se->deadline += calc_delta_fair(se->slice, se); - update_min_vruntime(cfs_rq); } } @@ -13077,23 +13031,6 @@ static inline void task_tick_core(struct rq *rq, struct task_struct *curr) * Which shows that S and s_i transform alike (which makes perfect sense * given that S is basically the (weighted) average of s_i). * - * Then: - * - * x -> s_min := min{s_i} (8) - * - * to obtain: - * - * \Sum_i w_i (s_i - s_min) - * S = s_min + ------------------------ (9) - * \Sum_i w_i - * - * Which already looks familiar, and is the basis for our current - * approximation: - * - * S ~= s_min (10) - * - * Now, obviously, (10) is absolute crap :-), but it sorta works. - * * So the thing to remember is that the above is strictly UP. It is * possible to generalize to multiple runqueues -- however it gets really * yuck when you have to add affinity support, as illustrated by our very @@ -13115,23 +13052,23 @@ static inline void task_tick_core(struct rq *rq, struct task_struct *curr) * Let, for our runqueue 'k': * * T_k = \Sum_i w_i s_i - * W_k = \Sum_i w_i ; for all i of k (11) + * W_k = \Sum_i w_i ; for all i of k (8) * * Then we can write (6) like: * * T_k - * S_k = --- (12) + * S_k = --- (9) * W_k * * From which immediately follows that: * * T_k + T_l - * S_k+l = --------- (13) + * S_k+l = --------- (10) * W_k + W_l * * On which we can define a combined lag: * - * lag_k+l(i) := S_k+l - s_i (14) + * lag_k+l(i) := S_k+l - s_i (11) * * And that gives us the tools to compare tasks across a combined runqueue. * @@ -13142,7 +13079,7 @@ static inline void task_tick_core(struct rq *rq, struct task_struct *curr) * using (7); this only requires storing single 'time'-stamps. * * b) when comparing tasks between 2 runqueues of which one is forced-idle, - * compare the combined lag, per (14). + * compare the combined lag, per (11). * * Now, of course cgroups (I so hate them) make this more interesting in * that a) seems to suggest we need to iterate all cgroup on a CPU at such @@ -13190,12 +13127,11 @@ static inline void task_tick_core(struct rq *rq, struct task_struct *curr) * every tick. This limits the observed divergence due to the work * conservancy. * - * On top of that, we can improve upon things by moving away from our - * horrible (10) hack and moving to (9) and employing (13) here. + * On top of that, we can improve upon things by employing (10) here. */ /* - * se_fi_update - Update the cfs_rq->min_vruntime_fi in a CFS hierarchy if needed. + * se_fi_update - Update the cfs_rq->zero_vruntime_fi in a CFS hierarchy if needed. */ static void se_fi_update(const struct sched_entity *se, unsigned int fi_seq, bool forceidle) @@ -13209,7 +13145,7 @@ static void se_fi_update(const struct sched_entity *se, unsigned int fi_seq, cfs_rq->forceidle_seq = fi_seq; } - cfs_rq->min_vruntime_fi = cfs_rq->min_vruntime; + cfs_rq->zero_vruntime_fi = cfs_rq->zero_vruntime; } } @@ -13262,11 +13198,11 @@ bool cfs_prio_less(const struct task_struct *a, const struct task_struct *b, /* * Find delta after normalizing se's vruntime with its cfs_rq's - * min_vruntime_fi, which would have been updated in prior calls + * zero_vruntime_fi, which would have been updated in prior calls * to se_fi_update(). */ delta = (s64)(sea->vruntime - seb->vruntime) + - (s64)(cfs_rqb->min_vruntime_fi - cfs_rqa->min_vruntime_fi); + (s64)(cfs_rqb->zero_vruntime_fi - cfs_rqa->zero_vruntime_fi); return delta > 0; } @@ -13502,7 +13438,7 @@ static void set_next_task_fair(struct rq *rq, struct task_struct *p, bool first) void init_cfs_rq(struct cfs_rq *cfs_rq) { cfs_rq->tasks_timeline = RB_ROOT_CACHED; - cfs_rq->min_vruntime = (u64)(-(1LL << 20)); + cfs_rq->zero_vruntime = (u64)(-(1LL << 20)); raw_spin_lock_init(&cfs_rq->removed.lock); } diff --git a/kernel/sched/sched.h b/kernel/sched/sched.h index adfb6e3409d729..92ec751799f555 100644 --- a/kernel/sched/sched.h +++ b/kernel/sched/sched.h @@ -682,10 +682,10 @@ struct cfs_rq { s64 avg_vruntime; u64 avg_load; - u64 min_vruntime; + u64 zero_vruntime; #ifdef CONFIG_SCHED_CORE unsigned int forceidle_seq; - u64 min_vruntime_fi; + u64 zero_vruntime_fi; #endif struct rb_root_cached tasks_timeline; From aeabe44c5019327f171973e357d634e42bf038cf Mon Sep 17 00:00:00 2001 From: Zqiang Date: Mon, 29 Dec 2025 14:36:40 -0500 Subject: [PATCH 1764/3902] sched_ext: Fix incorrect sched_class settings for per-cpu migration tasks [ Upstream commit 1dd6c84f1c544e552848a8968599220bd464e338 ] When loading the ebpf scheduler, the tasks in the scx_tasks list will be traversed and invoke __setscheduler_class() to get new sched_class. however, this would also incorrectly set the per-cpu migration task's->sched_class to rt_sched_class, even after unload, the per-cpu migration task's->sched_class remains sched_rt_class. The log for this issue is as follows: ./scx_rustland --stats 1 [ 199.245639][ T630] sched_ext: "rustland" does not implement cgroup cpu.weight [ 199.269213][ T630] sched_ext: BPF scheduler "rustland" enabled 04:25:09 [INFO] RustLand scheduler attached bpftrace -e 'iter:task /strcontains(ctx->task->comm, "migration")/ { printf("%s:%d->%pS\n", ctx->task->comm, ctx->task->pid, ctx->task->sched_class); }' Attaching 1 probe... migration/0:24->rt_sched_class+0x0/0xe0 migration/1:27->rt_sched_class+0x0/0xe0 migration/2:33->rt_sched_class+0x0/0xe0 migration/3:39->rt_sched_class+0x0/0xe0 migration/4:45->rt_sched_class+0x0/0xe0 migration/5:52->rt_sched_class+0x0/0xe0 migration/6:58->rt_sched_class+0x0/0xe0 migration/7:64->rt_sched_class+0x0/0xe0 sched_ext: BPF scheduler "rustland" disabled (unregistered from user space) EXIT: unregistered from user space 04:25:21 [INFO] Unregister RustLand scheduler bpftrace -e 'iter:task /strcontains(ctx->task->comm, "migration")/ { printf("%s:%d->%pS\n", ctx->task->comm, ctx->task->pid, ctx->task->sched_class); }' Attaching 1 probe... migration/0:24->rt_sched_class+0x0/0xe0 migration/1:27->rt_sched_class+0x0/0xe0 migration/2:33->rt_sched_class+0x0/0xe0 migration/3:39->rt_sched_class+0x0/0xe0 migration/4:45->rt_sched_class+0x0/0xe0 migration/5:52->rt_sched_class+0x0/0xe0 migration/6:58->rt_sched_class+0x0/0xe0 migration/7:64->rt_sched_class+0x0/0xe0 This commit therefore generate a new scx_setscheduler_class() and add check for stop_sched_class to replace __setscheduler_class(). Fixes: f0e1a0643a59 ("sched_ext: Implement BPF extensible scheduler class") Cc: stable@vger.kernel.org # v6.12+ Signed-off-by: Zqiang Reviewed-by: Andrea Righi Signed-off-by: Tejun Heo [ Adjust context ] Signed-off-by: Sasha Levin Signed-off-by: Greg Kroah-Hartman --- kernel/sched/ext.c | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/kernel/sched/ext.c b/kernel/sched/ext.c index 907eea83294ce2..6139263afd5983 100644 --- a/kernel/sched/ext.c +++ b/kernel/sched/ext.c @@ -210,6 +210,14 @@ static struct scx_dispatch_q *find_user_dsq(struct scx_sched *sch, u64 dsq_id) return rhashtable_lookup_fast(&sch->dsq_hash, &dsq_id, dsq_hash_params); } +static const struct sched_class *scx_setscheduler_class(struct task_struct *p) +{ + if (p->sched_class == &stop_sched_class) + return &stop_sched_class; + + return __setscheduler_class(p->policy, p->prio); +} + /* * scx_kf_mask enforcement. Some kfuncs can only be called from specific SCX * ops. When invoking SCX ops, SCX_CALL_OP[_RET]() should be used to indicate @@ -3994,8 +4002,7 @@ static void scx_disable_workfn(struct kthread_work *work) scx_task_iter_start(&sti); while ((p = scx_task_iter_next_locked(&sti))) { const struct sched_class *old_class = p->sched_class; - const struct sched_class *new_class = - __setscheduler_class(p->policy, p->prio); + const struct sched_class *new_class = scx_setscheduler_class(p); struct sched_enq_and_set_ctx ctx; if (old_class != new_class && p->se.sched_delayed) @@ -4779,8 +4786,7 @@ static int scx_enable(struct sched_ext_ops *ops, struct bpf_link *link) scx_task_iter_start(&sti); while ((p = scx_task_iter_next_locked(&sti))) { const struct sched_class *old_class = p->sched_class; - const struct sched_class *new_class = - __setscheduler_class(p->policy, p->prio); + const struct sched_class *new_class = scx_setscheduler_class(p); struct sched_enq_and_set_ctx ctx; if (!tryget_task_struct(p)) From 2a30b3c9eae1ca45cd0c7232a3d78b62d5dcd7c3 Mon Sep 17 00:00:00 2001 From: Wei Yang Date: Mon, 29 Dec 2025 21:48:31 -0500 Subject: [PATCH 1765/3902] mm/huge_memory: merge uniform_split_supported() and non_uniform_split_supported() [ Upstream commit 8a0e4bdddd1c998b894d879a1d22f1e745606215 ] uniform_split_supported() and non_uniform_split_supported() share significantly similar logic. The only functional difference is that uniform_split_supported() includes an additional check on the requested @new_order. The reason for this check comes from the following two aspects: * some file system or swap cache just supports order-0 folio * the behavioral difference between uniform/non-uniform split The behavioral difference between uniform split and non-uniform: * uniform split splits folio directly to @new_order * non-uniform split creates after-split folios with orders from folio_order(folio) - 1 to new_order. This means for non-uniform split or !new_order split we should check the file system and swap cache respectively. This commit unifies the logic and merge the two functions into a single combined helper, removing redundant code and simplifying the split support checking mechanism. Link: https://lkml.kernel.org/r/20251106034155.21398-3-richard.weiyang@gmail.com Fixes: c010d47f107f ("mm: thp: split huge page to any lower order pages") Signed-off-by: Wei Yang Reviewed-by: Zi Yan Cc: Zi Yan Cc: "David Hildenbrand (Red Hat)" Cc: Baolin Wang Cc: Barry Song Cc: Dev Jain Cc: Lance Yang Cc: Liam Howlett Cc: Lorenzo Stoakes Cc: Nico Pache Cc: Ryan Roberts Cc: Signed-off-by: Andrew Morton [ split_type => uniform_split and replaced SPLIT_TYPE_NON_UNIFORM checks ] Signed-off-by: Sasha Levin Signed-off-by: Greg Kroah-Hartman --- include/linux/huge_mm.h | 8 ++--- mm/huge_memory.c | 71 +++++++++++++++++------------------------ 2 files changed, 33 insertions(+), 46 deletions(-) diff --git a/include/linux/huge_mm.h b/include/linux/huge_mm.h index 71ac78b9f834f6..240cbc67648090 100644 --- a/include/linux/huge_mm.h +++ b/include/linux/huge_mm.h @@ -369,10 +369,8 @@ int split_huge_page_to_list_to_order(struct page *page, struct list_head *list, unsigned int new_order); int min_order_for_split(struct folio *folio); int split_folio_to_list(struct folio *folio, struct list_head *list); -bool uniform_split_supported(struct folio *folio, unsigned int new_order, - bool warns); -bool non_uniform_split_supported(struct folio *folio, unsigned int new_order, - bool warns); +bool folio_split_supported(struct folio *folio, unsigned int new_order, + bool uniform_split, bool warns); int folio_split(struct folio *folio, unsigned int new_order, struct page *page, struct list_head *list); /* @@ -392,7 +390,7 @@ int folio_split(struct folio *folio, unsigned int new_order, struct page *page, static inline int try_folio_split_to_order(struct folio *folio, struct page *page, unsigned int new_order) { - if (!non_uniform_split_supported(folio, new_order, /* warns= */ false)) + if (!folio_split_supported(folio, new_order, false, /* warns= */ false)) return split_huge_page_to_list_to_order(&folio->page, NULL, new_order); return folio_split(folio, new_order, page, NULL); diff --git a/mm/huge_memory.c b/mm/huge_memory.c index f66a4b15cb3363..8ad170b9855a5a 100644 --- a/mm/huge_memory.c +++ b/mm/huge_memory.c @@ -3515,8 +3515,8 @@ static int __split_unmapped_folio(struct folio *folio, int new_order, return ret; } -bool non_uniform_split_supported(struct folio *folio, unsigned int new_order, - bool warns) +bool folio_split_supported(struct folio *folio, unsigned int new_order, + bool uniform_split, bool warns) { if (folio_test_anon(folio)) { /* order-1 is not supported for anonymous THP. */ @@ -3524,48 +3524,41 @@ bool non_uniform_split_supported(struct folio *folio, unsigned int new_order, "Cannot split to order-1 folio"); if (new_order == 1) return false; - } else if (IS_ENABLED(CONFIG_READ_ONLY_THP_FOR_FS) && - !mapping_large_folio_support(folio->mapping)) { - /* - * No split if the file system does not support large folio. - * Note that we might still have THPs in such mappings due to - * CONFIG_READ_ONLY_THP_FOR_FS. But in that case, the mapping - * does not actually support large folios properly. - */ - VM_WARN_ONCE(warns, - "Cannot split file folio to non-0 order"); - return false; - } - - /* Only swapping a whole PMD-mapped folio is supported */ - if (folio_test_swapcache(folio)) { - VM_WARN_ONCE(warns, - "Cannot split swapcache folio to non-0 order"); - return false; - } - - return true; -} - -/* See comments in non_uniform_split_supported() */ -bool uniform_split_supported(struct folio *folio, unsigned int new_order, - bool warns) -{ - if (folio_test_anon(folio)) { - VM_WARN_ONCE(warns && new_order == 1, - "Cannot split to order-1 folio"); - if (new_order == 1) - return false; - } else if (new_order) { + } else if (!uniform_split || new_order) { if (IS_ENABLED(CONFIG_READ_ONLY_THP_FOR_FS) && !mapping_large_folio_support(folio->mapping)) { + /* + * We can always split a folio down to a single page + * (new_order == 0) uniformly. + * + * For any other scenario + * a) uniform split targeting a large folio + * (new_order > 0) + * b) any non-uniform split + * we must confirm that the file system supports large + * folios. + * + * Note that we might still have THPs in such + * mappings, which is created from khugepaged when + * CONFIG_READ_ONLY_THP_FOR_FS is enabled. But in that + * case, the mapping does not actually support large + * folios properly. + */ VM_WARN_ONCE(warns, "Cannot split file folio to non-0 order"); return false; } } - if (new_order && folio_test_swapcache(folio)) { + /* + * swapcache folio could only be split to order 0 + * + * non-uniform split creates after-split folios with orders from + * folio_order(folio) - 1 to new_order, making it not suitable for any + * swapcache folio split. Only uniform split to order-0 can be used + * here. + */ + if ((!uniform_split || new_order) && folio_test_swapcache(folio)) { VM_WARN_ONCE(warns, "Cannot split swapcache folio to non-0 order"); return false; @@ -3632,11 +3625,7 @@ static int __folio_split(struct folio *folio, unsigned int new_order, if (new_order >= folio_order(folio)) return -EINVAL; - if (uniform_split && !uniform_split_supported(folio, new_order, true)) - return -EINVAL; - - if (!uniform_split && - !non_uniform_split_supported(folio, new_order, true)) + if (!folio_split_supported(folio, new_order, uniform_split, /* warn = */ true)) return -EINVAL; is_hzp = is_huge_zero_folio(folio); From 2af2abbcbf8573100288e8f8aea2dab8a2a0ceb7 Mon Sep 17 00:00:00 2001 From: Claudio Imbrenda Date: Tue, 30 Dec 2025 22:16:26 -0500 Subject: [PATCH 1766/3902] KVM: s390: Fix gmap_helper_zap_one_page() again [ Upstream commit 2f393c228cc519ddf19b8c6c05bf15723241aa96 ] A few checks were missing in gmap_helper_zap_one_page(), which can lead to memory corruption in the guest under specific circumstances. Add the missing checks. Fixes: 5deafa27d9ae ("KVM: s390: Fix to clear PTE when discarding a swapped page") Cc: stable@vger.kernel.org Reported-by: Marc Hartmayer Tested-by: Marc Hartmayer Acked-by: Christian Borntraeger Signed-off-by: Claudio Imbrenda Signed-off-by: Heiko Carstens [ adapted ptep_zap_softleaf_entry() and softleaf_from_pte() calls to ptep_zap_swap_entry() and pte_to_swp_entry() ] Signed-off-by: Sasha Levin Signed-off-by: Greg Kroah-Hartman --- arch/s390/mm/gmap_helpers.c | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/arch/s390/mm/gmap_helpers.c b/arch/s390/mm/gmap_helpers.c index d4c3c36855e26c..38a2d82cd88a7a 100644 --- a/arch/s390/mm/gmap_helpers.c +++ b/arch/s390/mm/gmap_helpers.c @@ -47,6 +47,7 @@ static void ptep_zap_swap_entry(struct mm_struct *mm, swp_entry_t entry) void gmap_helper_zap_one_page(struct mm_struct *mm, unsigned long vmaddr) { struct vm_area_struct *vma; + unsigned long pgstev; spinlock_t *ptl; pgste_t pgste; pte_t *ptep; @@ -65,9 +66,13 @@ void gmap_helper_zap_one_page(struct mm_struct *mm, unsigned long vmaddr) if (pte_swap(*ptep)) { preempt_disable(); pgste = pgste_get_lock(ptep); + pgstev = pgste_val(pgste); - ptep_zap_swap_entry(mm, pte_to_swp_entry(*ptep)); - pte_clear(mm, vmaddr, ptep); + if ((pgstev & _PGSTE_GPS_USAGE_MASK) == _PGSTE_GPS_USAGE_UNUSED || + (pgstev & _PGSTE_GPS_ZERO)) { + ptep_zap_swap_entry(mm, pte_to_swp_entry(*ptep)); + pte_clear(mm, vmaddr, ptep); + } pgste_set_unlock(ptep, pgste); preempt_enable(); From 61c0901cbd7220434c0e019db888b45e064420ec Mon Sep 17 00:00:00 2001 From: Jani Nikula Date: Wed, 31 Dec 2025 11:18:55 -0500 Subject: [PATCH 1767/3902] drm/edid: add DRM_EDID_IDENT_INIT() to initialize struct drm_edid_ident MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [ Upstream commit 8b61583f993589a64c061aa91b44f5bd350d90a5 ] Add a convenience helper for initializing struct drm_edid_ident. Cc: Tiago Martins Araújo Acked-by: Alex Deucher Tested-by: Tiago Martins Araújo Cc: stable@vger.kernel.org Link: https://patch.msgid.link/710b2ac6a211606ec1f90afa57b79e8c7375a27e.1761681968.git.jani.nikula@intel.com Signed-off-by: Jani Nikula Stable-dep-of: 83cbb4d33dc2 ("drm/displayid: add quirk to ignore DisplayID checksum errors") Signed-off-by: Sasha Levin Signed-off-by: Greg Kroah-Hartman --- include/drm/drm_edid.h | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/include/drm/drm_edid.h b/include/drm/drm_edid.h index 3d1aecfec9b2a4..04f7a7f1f10826 100644 --- a/include/drm/drm_edid.h +++ b/include/drm/drm_edid.h @@ -340,6 +340,12 @@ struct drm_edid_ident { const char *name; }; +#define DRM_EDID_IDENT_INIT(_vend_chr_0, _vend_chr_1, _vend_chr_2, _product_id, _name) \ +{ \ + .panel_id = drm_edid_encode_panel_id(_vend_chr_0, _vend_chr_1, _vend_chr_2, _product_id), \ + .name = _name, \ +} + #define EDID_PRODUCT_ID(e) ((e)->prod_code[0] | ((e)->prod_code[1] << 8)) /* Short Audio Descriptor */ From 99204fdc989ca6abb74526200020c2069d139814 Mon Sep 17 00:00:00 2001 From: Jani Nikula Date: Wed, 31 Dec 2025 11:18:56 -0500 Subject: [PATCH 1768/3902] drm/displayid: add quirk to ignore DisplayID checksum errors MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [ Upstream commit 83cbb4d33dc22b0ca1a4e85c6e892c9b729e28d4 ] Add a mechanism for DisplayID specific quirks, and add the first quirk to ignore DisplayID section checksum errors. It would be quite inconvenient to pass existing EDID quirks from drm_edid.c for DisplayID parsing. Not all places doing DisplayID iteration have the quirks readily available, and would have to pass it in all places. Simply add a separate array of DisplayID specific EDID quirks. We do end up checking it every time we iterate DisplayID blocks, but hopefully the number of quirks remains small. There are a few laptop models with DisplayID checksum failures, leading to higher refresh rates only present in the DisplayID blocks being ignored. Add a quirk for the panel in the machines. Reported-by: Tiago Martins Araújo Closes: https://lore.kernel.org/r/CACRbrPGvLP5LANXuFi6z0S7XMbAG4X5y2YOLBDxfOVtfGGqiKQ@mail.gmail.com Closes: https://gitlab.freedesktop.org/drm/i915/kernel/-/issues/14703 Acked-by: Alex Deucher Tested-by: Tiago Martins Araújo Cc: stable@vger.kernel.org Link: https://patch.msgid.link/c04d81ae648c5f21b3f5b7953f924718051f2798.1761681968.git.jani.nikula@intel.com Signed-off-by: Jani Nikula Signed-off-by: Sasha Levin Signed-off-by: Greg Kroah-Hartman --- drivers/gpu/drm/drm_displayid.c | 41 +++++++++++++++++++++--- drivers/gpu/drm/drm_displayid_internal.h | 2 ++ 2 files changed, 39 insertions(+), 4 deletions(-) diff --git a/drivers/gpu/drm/drm_displayid.c b/drivers/gpu/drm/drm_displayid.c index 20b453d2b85478..58d0bb6d267689 100644 --- a/drivers/gpu/drm/drm_displayid.c +++ b/drivers/gpu/drm/drm_displayid.c @@ -9,6 +9,34 @@ #include "drm_crtc_internal.h" #include "drm_displayid_internal.h" +enum { + QUIRK_IGNORE_CHECKSUM, +}; + +struct displayid_quirk { + const struct drm_edid_ident ident; + u8 quirks; +}; + +static const struct displayid_quirk quirks[] = { + { + .ident = DRM_EDID_IDENT_INIT('C', 'S', 'O', 5142, "MNE007ZA1-5"), + .quirks = BIT(QUIRK_IGNORE_CHECKSUM), + }, +}; + +static u8 get_quirks(const struct drm_edid *drm_edid) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(quirks); i++) { + if (drm_edid_match(drm_edid, &quirks[i].ident)) + return quirks[i].quirks; + } + + return 0; +} + static const struct displayid_header * displayid_get_header(const u8 *displayid, int length, int index) { @@ -23,7 +51,7 @@ displayid_get_header(const u8 *displayid, int length, int index) } static const struct displayid_header * -validate_displayid(const u8 *displayid, int length, int idx) +validate_displayid(const u8 *displayid, int length, int idx, bool ignore_checksum) { int i, dispid_length; u8 csum = 0; @@ -41,8 +69,11 @@ validate_displayid(const u8 *displayid, int length, int idx) for (i = 0; i < dispid_length; i++) csum += displayid[idx + i]; if (csum) { - DRM_NOTE("DisplayID checksum invalid, remainder is %d\n", csum); - return ERR_PTR(-EINVAL); + DRM_NOTE("DisplayID checksum invalid, remainder is %d%s\n", csum, + ignore_checksum ? " (ignoring)" : ""); + + if (!ignore_checksum) + return ERR_PTR(-EINVAL); } return base; @@ -52,6 +83,7 @@ static const u8 *find_next_displayid_extension(struct displayid_iter *iter) { const struct displayid_header *base; const u8 *displayid; + bool ignore_checksum = iter->quirks & BIT(QUIRK_IGNORE_CHECKSUM); displayid = drm_edid_find_extension(iter->drm_edid, DISPLAYID_EXT, &iter->ext_index); if (!displayid) @@ -61,7 +93,7 @@ static const u8 *find_next_displayid_extension(struct displayid_iter *iter) iter->length = EDID_LENGTH - 1; iter->idx = 1; - base = validate_displayid(displayid, iter->length, iter->idx); + base = validate_displayid(displayid, iter->length, iter->idx, ignore_checksum); if (IS_ERR(base)) return NULL; @@ -76,6 +108,7 @@ void displayid_iter_edid_begin(const struct drm_edid *drm_edid, memset(iter, 0, sizeof(*iter)); iter->drm_edid = drm_edid; + iter->quirks = get_quirks(drm_edid); } static const struct displayid_block * diff --git a/drivers/gpu/drm/drm_displayid_internal.h b/drivers/gpu/drm/drm_displayid_internal.h index 957dd0619f5c5c..5b1b32f7351662 100644 --- a/drivers/gpu/drm/drm_displayid_internal.h +++ b/drivers/gpu/drm/drm_displayid_internal.h @@ -167,6 +167,8 @@ struct displayid_iter { u8 version; u8 primary_use; + + u8 quirks; }; void displayid_iter_edid_begin(const struct drm_edid *drm_edid, From 7f1f50c2055b0d19534e4615b653af936a967d79 Mon Sep 17 00:00:00 2001 From: Alex Deucher Date: Tue, 2 Dec 2025 14:24:03 -0500 Subject: [PATCH 1769/3902] drm/amdgpu: don't attach the tlb fence for SI MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit commit eb296c09805ee37dd4ea520a7fb3ec157c31090f upstream. SI hardware doesn't support pasids, user mode queues, or KIQ/MES so there is no need for this. Doing so results in a segfault as these callbacks are non-existent for SI. Closes: https://gitlab.freedesktop.org/drm/amd/-/issues/4744 Fixes: f3854e04b708 ("drm/amdgpu: attach tlb fence to the PTs update") Reviewed-by: Timur Kristóf Signed-off-by: Alex Deucher (cherry picked from commit 820b3d376e8a102c6aeab737ec6edebbbb710e04) Signed-off-by: Greg Kroah-Hartman --- drivers/gpu/drm/amd/amdgpu/amdgpu_vm.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_vm.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_vm.c index 676e24fb886474..cdcafde3c71a13 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_vm.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_vm.c @@ -1066,7 +1066,9 @@ amdgpu_vm_tlb_flush(struct amdgpu_vm_update_params *params, } /* Prepare a TLB flush fence to be attached to PTs */ - if (!params->unlocked) { + if (!params->unlocked && + /* SI doesn't support pasid or KIQ/MES */ + params->adev->family > AMDGPU_FAMILY_SI) { amdgpu_vm_tlb_fence_create(params->adev, vm, fence); /* Makes sure no PD/PT is freed before the flush */ From e5c129a0553101250ef53c274d2ef7a8c39d76b1 Mon Sep 17 00:00:00 2001 From: Ping-Ke Shih Date: Tue, 25 Nov 2025 09:38:49 +0800 Subject: [PATCH 1770/3902] wifi: rtw88: limit indirect IO under powered off for RTL8822CS [ Upstream commit f3ccdfda345ca9a624ea425840a926b8338c1e25 ] The indirect IO is necessary for RTL8822CS, but not necessary for other chips. Otherwiese, it throws errors and becomes unusable. rtw88_8723cs mmc1:0001:1: WOW Firmware version 11.0.0, H2C version 0 rtw88_8723cs mmc1:0001:1: Firmware version 11.0.0, H2C version 0 rtw88_8723cs mmc1:0001:1: sdio read32 failed (0xf0): -110 rtw88_8723cs mmc1:0001:1: sdio write8 failed (0x1c): -110 rtw88_8723cs mmc1:0001:1: sdio read32 failed (0xf0): -110 By vendor driver, only RTL8822CS and RTL8822ES need indirect IO, but RTL8822ES isn't supported yet. Therefore, limit it to RTL8822CS only. Reported-by: Andrey Skvortsov Closes: https://lore.kernel.org/linux-wireless/07a32e2d6c764eb1bd9415b5a921a652@realtek.com/T/#m997b4522f7209ba629561c776bfd1d13ab24c1d4 Fixes: 58de1f91e033 ("wifi: rtw88: sdio: use indirect IO for device registers before power-on") Signed-off-by: Ping-Ke Shih Tested-by: Andrey Skvortsov Link: https://patch.msgid.link/1764034729-1251-1-git-send-email-pkshih@realtek.com Signed-off-by: Sasha Levin --- drivers/net/wireless/realtek/rtw88/sdio.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/drivers/net/wireless/realtek/rtw88/sdio.c b/drivers/net/wireless/realtek/rtw88/sdio.c index 99d7c629eac6fb..e35de52d8eb430 100644 --- a/drivers/net/wireless/realtek/rtw88/sdio.c +++ b/drivers/net/wireless/realtek/rtw88/sdio.c @@ -144,8 +144,10 @@ static u32 rtw_sdio_to_io_address(struct rtw_dev *rtwdev, u32 addr, static bool rtw_sdio_use_direct_io(struct rtw_dev *rtwdev, u32 addr) { + bool might_indirect_under_power_off = rtwdev->chip->id == RTW_CHIP_TYPE_8822C; + if (!test_bit(RTW_FLAG_POWERON, rtwdev->flags) && - !rtw_sdio_is_bus_addr(addr)) + !rtw_sdio_is_bus_addr(addr) && might_indirect_under_power_off) return false; return !rtw_sdio_is_sdio30_supported(rtwdev) || From 90a15ff324645aa806d81fa349497cd964861b66 Mon Sep 17 00:00:00 2001 From: Morning Star Date: Thu, 27 Nov 2025 16:37:08 +0800 Subject: [PATCH 1771/3902] wifi: rtlwifi: 8192cu: fix tid out of range in rtl92cu_tx_fill_desc() [ Upstream commit dd39edb445f07400e748da967a07d5dca5c5f96e ] TID getting from ieee80211_get_tid() might be out of range of array size of sta_entry->tids[], so check TID is less than MAX_TID_COUNT. Othwerwise, UBSAN warn: UBSAN: array-index-out-of-bounds in drivers/net/wireless/realtek/rtlwifi/rtl8192cu/trx.c:514:30 index 10 is out of range for type 'rtl_tid_data [9]' Fixes: 8ca4cdef9329 ("wifi: rtlwifi: rtl8192cu: Fix TX aggregation") Signed-off-by: Morning Star Signed-off-by: Ping-Ke Shih Link: https://patch.msgid.link/1764232628-13625-1-git-send-email-pkshih@realtek.com Signed-off-by: Sasha Levin --- drivers/net/wireless/realtek/rtlwifi/rtl8192cu/trx.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/drivers/net/wireless/realtek/rtlwifi/rtl8192cu/trx.c b/drivers/net/wireless/realtek/rtlwifi/rtl8192cu/trx.c index aa702ba7c9f540..d6c35e8d02a58c 100644 --- a/drivers/net/wireless/realtek/rtlwifi/rtl8192cu/trx.c +++ b/drivers/net/wireless/realtek/rtlwifi/rtl8192cu/trx.c @@ -511,7 +511,8 @@ void rtl92cu_tx_fill_desc(struct ieee80211_hw *hw, if (sta) { sta_entry = (struct rtl_sta_info *)sta->drv_priv; tid = ieee80211_get_tid(hdr); - agg_state = sta_entry->tids[tid].agg.agg_state; + if (tid < MAX_TID_COUNT) + agg_state = sta_entry->tids[tid].agg.agg_state; ampdu_density = sta->deflink.ht_cap.ampdu_density; } From 7ea38152a2895fd7bf0f28802bab858451ff94fb Mon Sep 17 00:00:00 2001 From: Dan Carpenter Date: Wed, 3 Dec 2025 14:14:47 +0300 Subject: [PATCH 1772/3902] wifi: cfg80211: sme: store capped length in __cfg80211_connect_result() [ Upstream commit 2b77b9551d1184cb5af8271ff350e6e2c1b3db0d ] The QGenie AI code review tool says we should store the capped length to wdev->u.client.ssid_len. The AI is correct. Fixes: 62b635dcd69c ("wifi: cfg80211: sme: cap SSID length in __cfg80211_connect_result()") Signed-off-by: Dan Carpenter Link: https://patch.msgid.link/aTAbp5RleyH_lnZE@stanley.mountain Signed-off-by: Johannes Berg Signed-off-by: Sasha Levin --- net/wireless/sme.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/net/wireless/sme.c b/net/wireless/sme.c index 3a028ff287fbbe..4e629ca305bcc3 100644 --- a/net/wireless/sme.c +++ b/net/wireless/sme.c @@ -910,7 +910,7 @@ void __cfg80211_connect_result(struct net_device *dev, ssid_len = min(ssid->datalen, IEEE80211_MAX_SSID_LEN); memcpy(wdev->u.client.ssid, ssid->data, ssid_len); - wdev->u.client.ssid_len = ssid->datalen; + wdev->u.client.ssid_len = ssid_len; break; } rcu_read_unlock(); From 182a2786d248e5051c55e1710643e7af32a98453 Mon Sep 17 00:00:00 2001 From: Aloka Dixit Date: Mon, 15 Dec 2025 09:46:56 -0800 Subject: [PATCH 1773/3902] wifi: mac80211: do not use old MBSSID elements [ Upstream commit a519be2f5d958c5804f2cfd68f1f384291271fab ] When userspace brings down and deletes a non-transmitted profile, it is expected to send a new updated Beacon template for the transmitted profile of that multiple BSSID (MBSSID) group which does not include the removed profile in MBSSID element. This update comes via NL80211_CMD_SET_BEACON. Such updates work well as long as the group continues to have at least one non-transmitted profile as NL80211_ATTR_MBSSID_ELEMS is included in the new Beacon template. But when the last non-trasmitted profile is removed, it still gets included in Beacon templates sent to driver. This happens because when no MBSSID elements are sent by the userspace, ieee80211_assign_beacon() ends up using the element stored from earlier Beacon template. Do not copy old MBSSID elements, instead userspace should always include these when applicable. Fixes: 2b3171c6fe0a ("mac80211: MBSSID beacon handling in AP mode") Signed-off-by: Aloka Dixit Link: https://patch.msgid.link/20251215174656.2866319-2-aloka.dixit@oss.qualcomm.com Signed-off-by: Johannes Berg Signed-off-by: Sasha Levin --- net/mac80211/cfg.c | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/net/mac80211/cfg.c b/net/mac80211/cfg.c index c52b0456039ddc..e18df59951a82d 100644 --- a/net/mac80211/cfg.c +++ b/net/mac80211/cfg.c @@ -1328,7 +1328,6 @@ ieee80211_assign_beacon(struct ieee80211_sub_if_data *sdata, size = sizeof(*new) + new_head_len + new_tail_len; - /* new or old multiple BSSID elements? */ if (params->mbssid_ies) { mbssid = params->mbssid_ies; size += struct_size(new->mbssid_ies, elem, mbssid->cnt); @@ -1338,15 +1337,6 @@ ieee80211_assign_beacon(struct ieee80211_sub_if_data *sdata, } size += ieee80211_get_mbssid_beacon_len(mbssid, rnr, mbssid->cnt); - } else if (old && old->mbssid_ies) { - mbssid = old->mbssid_ies; - size += struct_size(new->mbssid_ies, elem, mbssid->cnt); - if (old && old->rnr_ies) { - rnr = old->rnr_ies; - size += struct_size(new->rnr_ies, elem, rnr->cnt); - } - size += ieee80211_get_mbssid_beacon_len(mbssid, rnr, - mbssid->cnt); } new = kzalloc(size, GFP_KERNEL); From f9cb8a49f7a5db234b171d78ee25b6c6c9aa6fdb Mon Sep 17 00:00:00 2001 From: Liang Jie Date: Tue, 16 Dec 2025 17:39:55 +0800 Subject: [PATCH 1774/3902] sched_ext: fix uninitialized ret on alloc_percpu() failure [ Upstream commit b0101ccb5b4641885f30fecc352ef891ed06e083 ] Smatch reported: kernel/sched/ext.c:5332 scx_alloc_and_add_sched() warn: passing zero to 'ERR_PTR' In scx_alloc_and_add_sched(), the alloc_percpu() failure path jumps to err_free_gdsqs without initializing @ret. That can lead to returning ERR_PTR(0), which violates the ERR_PTR() convention and confuses callers. Set @ret to -ENOMEM before jumping to the error path when alloc_percpu() fails. Reported-by: kernel test robot Closes: https://lore.kernel.org/r/202512141601.yAXDAeA9-lkp@intel.com/ Reported-by: Dan Carpenter Fixes: c201ea1578d3 ("sched_ext: Move event_stats_cpu into scx_sched") Signed-off-by: Liang Jie Reviewed-by: Emil Tsalapatis Reviewed-by: Andrea Righi Signed-off-by: Tejun Heo Signed-off-by: Sasha Levin --- kernel/sched/ext.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/kernel/sched/ext.c b/kernel/sched/ext.c index 6139263afd5983..31eda2a56920dc 100644 --- a/kernel/sched/ext.c +++ b/kernel/sched/ext.c @@ -4508,8 +4508,10 @@ static struct scx_sched *scx_alloc_and_add_sched(struct sched_ext_ops *ops) } sch->pcpu = alloc_percpu(struct scx_sched_pcpu); - if (!sch->pcpu) + if (!sch->pcpu) { + ret = -ENOMEM; goto err_free_gdsqs; + } sch->helper = kthread_run_worker(0, "sched_ext_helper"); if (IS_ERR(sch->helper)) { From db54feefa859bd5d9d3d2d67ba39c65392fadb77 Mon Sep 17 00:00:00 2001 From: Przemyslaw Korba Date: Thu, 20 Nov 2025 13:07:28 +0100 Subject: [PATCH 1775/3902] i40e: fix scheduling in set_rx_mode [ Upstream commit be43abc5514167cc129a8d8e9727b89b8e1d9719 ] Add service task schedule to set_rx_mode. In some cases there are error messages printed out in PTP application (ptp4l): ptp4l[13848.762]: port 1 (ens2f3np3): received SYNC without timestamp ptp4l[13848.825]: port 1 (ens2f3np3): received SYNC without timestamp ptp4l[13848.887]: port 1 (ens2f3np3): received SYNC without timestamp This happens when service task would not run immediately after set_rx_mode, and we need it for setup tasks. This service task checks, if PTP RX packets are hung in firmware, and propagate correct settings such as multicast address for IEEE 1588 Precision Time Protocol. RX timestamping depends on some of these filters set. Bug happens only with high PTP packets frequency incoming, and not every run since sometimes service task is being ran from a different place immediately after starting ptp4l. Fixes: 0e4425ed641f ("i40e: fix: do not sleep in netdev_ops") Reviewed-by: Grzegorz Nitka Reviewed-by: Jacob Keller Reviewed-by: Aleksandr Loktionov Signed-off-by: Przemyslaw Korba Tested-by: Rinitha S (A Contingent worker at Intel) Signed-off-by: Tony Nguyen Signed-off-by: Sasha Levin --- drivers/net/ethernet/intel/i40e/i40e_main.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/net/ethernet/intel/i40e/i40e_main.c b/drivers/net/ethernet/intel/i40e/i40e_main.c index 50be0a60ae13b0..07d32f2586c80f 100644 --- a/drivers/net/ethernet/intel/i40e/i40e_main.c +++ b/drivers/net/ethernet/intel/i40e/i40e_main.c @@ -2234,6 +2234,7 @@ static void i40e_set_rx_mode(struct net_device *netdev) vsi->flags |= I40E_VSI_FLAG_FILTER_CHANGED; set_bit(__I40E_MACVLAN_SYNC_PENDING, vsi->back->state); } + i40e_service_event_schedule(vsi->back); } /** From 5e703706b6eaf6f6dfc7b3c02244d590d1904155 Mon Sep 17 00:00:00 2001 From: Gregory Herrero Date: Fri, 12 Dec 2025 22:06:43 +0100 Subject: [PATCH 1776/3902] i40e: validate ring_len parameter against hardware-specific values [ Upstream commit 69942834215323cd9131db557091b4dec43f19c5 ] The maximum number of descriptors supported by the hardware is hardware-dependent and can be retrieved using i40e_get_max_num_descriptors(). Move this function to a shared header and use it when checking for valid ring_len parameter rather than using hardcoded value. By fixing an over-acceptance issue, behavior change could be seen where ring_len could now be rejected while configuring rx and tx queues if its size is larger than the hardware-dependent maximum number of descriptors. Fixes: 55d225670def ("i40e: add validation for ring_len param") Signed-off-by: Gregory Herrero Tested-by: Rafal Romanowski Reviewed-by: Aleksandr Loktionov Signed-off-by: Tony Nguyen Signed-off-by: Sasha Levin --- drivers/net/ethernet/intel/i40e/i40e.h | 11 +++++++++++ drivers/net/ethernet/intel/i40e/i40e_ethtool.c | 12 ------------ drivers/net/ethernet/intel/i40e/i40e_virtchnl_pf.c | 4 ++-- 3 files changed, 13 insertions(+), 14 deletions(-) diff --git a/drivers/net/ethernet/intel/i40e/i40e.h b/drivers/net/ethernet/intel/i40e/i40e.h index 801a57a925dadc..feec9e1e13b36e 100644 --- a/drivers/net/ethernet/intel/i40e/i40e.h +++ b/drivers/net/ethernet/intel/i40e/i40e.h @@ -1418,4 +1418,15 @@ static inline struct i40e_veb *i40e_pf_get_main_veb(struct i40e_pf *pf) return (pf->lan_veb != I40E_NO_VEB) ? pf->veb[pf->lan_veb] : NULL; } +static inline u32 i40e_get_max_num_descriptors(const struct i40e_pf *pf) +{ + const struct i40e_hw *hw = &pf->hw; + + switch (hw->mac.type) { + case I40E_MAC_XL710: + return I40E_MAX_NUM_DESCRIPTORS_XL710; + default: + return I40E_MAX_NUM_DESCRIPTORS; + } +} #endif /* _I40E_H_ */ diff --git a/drivers/net/ethernet/intel/i40e/i40e_ethtool.c b/drivers/net/ethernet/intel/i40e/i40e_ethtool.c index 86c72596617a39..61c39e881b008e 100644 --- a/drivers/net/ethernet/intel/i40e/i40e_ethtool.c +++ b/drivers/net/ethernet/intel/i40e/i40e_ethtool.c @@ -2013,18 +2013,6 @@ static void i40e_get_drvinfo(struct net_device *netdev, drvinfo->n_priv_flags += I40E_GL_PRIV_FLAGS_STR_LEN; } -static u32 i40e_get_max_num_descriptors(struct i40e_pf *pf) -{ - struct i40e_hw *hw = &pf->hw; - - switch (hw->mac.type) { - case I40E_MAC_XL710: - return I40E_MAX_NUM_DESCRIPTORS_XL710; - default: - return I40E_MAX_NUM_DESCRIPTORS; - } -} - static void i40e_get_ringparam(struct net_device *netdev, struct ethtool_ringparam *ring, struct kernel_ethtool_ringparam *kernel_ring, diff --git a/drivers/net/ethernet/intel/i40e/i40e_virtchnl_pf.c b/drivers/net/ethernet/intel/i40e/i40e_virtchnl_pf.c index 081a4526a2f000..cf831c649c9c57 100644 --- a/drivers/net/ethernet/intel/i40e/i40e_virtchnl_pf.c +++ b/drivers/net/ethernet/intel/i40e/i40e_virtchnl_pf.c @@ -656,7 +656,7 @@ static int i40e_config_vsi_tx_queue(struct i40e_vf *vf, u16 vsi_id, /* ring_len has to be multiple of 8 */ if (!IS_ALIGNED(info->ring_len, 8) || - info->ring_len > I40E_MAX_NUM_DESCRIPTORS_XL710) { + info->ring_len > i40e_get_max_num_descriptors(pf)) { ret = -EINVAL; goto error_context; } @@ -726,7 +726,7 @@ static int i40e_config_vsi_rx_queue(struct i40e_vf *vf, u16 vsi_id, /* ring_len has to be multiple of 32 */ if (!IS_ALIGNED(info->ring_len, 32) || - info->ring_len > I40E_MAX_NUM_DESCRIPTORS_XL710) { + info->ring_len > i40e_get_max_num_descriptors(pf)) { ret = -EINVAL; goto error_param; } From 3095228e1320371e143835d0cebeef1a8a754c66 Mon Sep 17 00:00:00 2001 From: Kohei Enju Date: Sun, 26 Oct 2025 01:58:50 +0900 Subject: [PATCH 1777/3902] iavf: fix off-by-one issues in iavf_config_rss_reg() [ Upstream commit 6daa2893f323981c7894c68440823326e93a7d61 ] There are off-by-one bugs when configuring RSS hash key and lookup table, causing out-of-bounds reads to memory [1] and out-of-bounds writes to device registers. Before commit 43a3d9ba34c9 ("i40evf: Allow PF driver to configure RSS"), the loop upper bounds were: i <= I40E_VFQF_{HKEY,HLUT}_MAX_INDEX which is safe since the value is the last valid index. That commit changed the bounds to: i <= adapter->rss_{key,lut}_size / 4 where `rss_{key,lut}_size / 4` is the number of dwords, so the last valid index is `(rss_{key,lut}_size / 4) - 1`. Therefore, using `<=` accesses one element past the end. Fix the issues by using `<` instead of `<=`, ensuring we do not exceed the bounds. [1] KASAN splat about rss_key_size off-by-one BUG: KASAN: slab-out-of-bounds in iavf_config_rss+0x619/0x800 Read of size 4 at addr ffff888102c50134 by task kworker/u8:6/63 CPU: 0 UID: 0 PID: 63 Comm: kworker/u8:6 Not tainted 6.18.0-rc2-enjuk-tnguy-00378-g3005f5b77652-dirty #156 PREEMPT(voluntary) Hardware name: QEMU Standard PC (Q35 + ICH9, 2009), BIOS 1.16.3-debian-1.16.3-2 04/01/2014 Workqueue: iavf iavf_watchdog_task Call Trace: dump_stack_lvl+0x6f/0xb0 print_report+0x170/0x4f3 kasan_report+0xe1/0x1a0 iavf_config_rss+0x619/0x800 iavf_watchdog_task+0x2be7/0x3230 process_one_work+0x7fd/0x1420 worker_thread+0x4d1/0xd40 kthread+0x344/0x660 ret_from_fork+0x249/0x320 ret_from_fork_asm+0x1a/0x30 Allocated by task 63: kasan_save_stack+0x30/0x50 kasan_save_track+0x14/0x30 __kasan_kmalloc+0x7f/0x90 __kmalloc_noprof+0x246/0x6f0 iavf_watchdog_task+0x28fc/0x3230 process_one_work+0x7fd/0x1420 worker_thread+0x4d1/0xd40 kthread+0x344/0x660 ret_from_fork+0x249/0x320 ret_from_fork_asm+0x1a/0x30 The buggy address belongs to the object at ffff888102c50100 which belongs to the cache kmalloc-64 of size 64 The buggy address is located 0 bytes to the right of allocated 52-byte region [ffff888102c50100, ffff888102c50134) The buggy address belongs to the physical page: page: refcount:0 mapcount:0 mapping:0000000000000000 index:0x0 pfn:0x102c50 flags: 0x200000000000000(node=0|zone=2) page_type: f5(slab) raw: 0200000000000000 ffff8881000418c0 dead000000000122 0000000000000000 raw: 0000000000000000 0000000080200020 00000000f5000000 0000000000000000 page dumped because: kasan: bad access detected Memory state around the buggy address: ffff888102c50000: 00 00 00 00 00 00 00 fc fc fc fc fc fc fc fc fc ffff888102c50080: 00 00 00 00 00 00 00 fc fc fc fc fc fc fc fc fc >ffff888102c50100: 00 00 00 00 00 00 04 fc fc fc fc fc fc fc fc fc ^ ffff888102c50180: 00 00 00 00 00 00 00 00 fc fc fc fc fc fc fc fc ffff888102c50200: fc fc fc fc fc fc fc fc fc fc fc fc fc fc fc fc Fixes: 43a3d9ba34c9 ("i40evf: Allow PF driver to configure RSS") Signed-off-by: Kohei Enju Reviewed-by: Aleksandr Loktionov Reviewed-by: Przemek Kitszel Tested-by: Rafal Romanowski Signed-off-by: Tony Nguyen Signed-off-by: Sasha Levin --- drivers/net/ethernet/intel/iavf/iavf_main.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/net/ethernet/intel/iavf/iavf_main.c b/drivers/net/ethernet/intel/iavf/iavf_main.c index c2fbe443ef853a..4b0fc8f354bc90 100644 --- a/drivers/net/ethernet/intel/iavf/iavf_main.c +++ b/drivers/net/ethernet/intel/iavf/iavf_main.c @@ -1726,11 +1726,11 @@ static int iavf_config_rss_reg(struct iavf_adapter *adapter) u16 i; dw = (u32 *)adapter->rss_key; - for (i = 0; i <= adapter->rss_key_size / 4; i++) + for (i = 0; i < adapter->rss_key_size / 4; i++) wr32(hw, IAVF_VFQF_HKEY(i), dw[i]); dw = (u32 *)adapter->rss_lut; - for (i = 0; i <= adapter->rss_lut_size / 4; i++) + for (i = 0; i < adapter->rss_lut_size / 4; i++) wr32(hw, IAVF_VFQF_HLUT(i), dw[i]); iavf_flush(hw); From 85230d7ab57edeb0413bdfe307894549106209bc Mon Sep 17 00:00:00 2001 From: Larysa Zaremba Date: Tue, 7 Oct 2025 13:46:22 +0200 Subject: [PATCH 1778/3902] idpf: fix LAN memory regions command on some NVMs [ Upstream commit 4af1f9a47291f7d446398065e0d6eb4943f7e184 ] IPU SDK versions 1.9 through 2.0.5 require send buffer to contain a single empty memory region. Set number of regions to 1 and use appropriate send buffer size to satisfy this requirement. Fixes: 6aa53e861c1a ("idpf: implement get LAN MMIO memory regions") Suggested-by: Michal Swiatkowski Reviewed-by: Aleksandr Loktionov Signed-off-by: Larysa Zaremba Tested-by: Krishneil Singh Signed-off-by: Tony Nguyen Signed-off-by: Sasha Levin --- drivers/net/ethernet/intel/idpf/idpf_virtchnl.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/drivers/net/ethernet/intel/idpf/idpf_virtchnl.c b/drivers/net/ethernet/intel/idpf/idpf_virtchnl.c index cbb5fa30f5a0ec..fc03d55bc9b908 100644 --- a/drivers/net/ethernet/intel/idpf/idpf_virtchnl.c +++ b/drivers/net/ethernet/intel/idpf/idpf_virtchnl.c @@ -1016,6 +1016,9 @@ static int idpf_send_get_lan_memory_regions(struct idpf_adapter *adapter) struct idpf_vc_xn_params xn_params = { .vc_op = VIRTCHNL2_OP_GET_LAN_MEMORY_REGIONS, .recv_buf.iov_len = IDPF_CTLQ_MAX_BUF_LEN, + .send_buf.iov_len = + sizeof(struct virtchnl2_get_lan_memory_regions) + + sizeof(struct virtchnl2_mem_region), .timeout_ms = IDPF_VC_XN_DEFAULT_TIMEOUT_MSEC, }; int num_regions, size; @@ -1028,6 +1031,8 @@ static int idpf_send_get_lan_memory_regions(struct idpf_adapter *adapter) return -ENOMEM; xn_params.recv_buf.iov_base = rcvd_regions; + rcvd_regions->num_memory_regions = cpu_to_le16(1); + xn_params.send_buf.iov_base = rcvd_regions; reply_sz = idpf_vc_xn_exec(adapter, &xn_params); if (reply_sz < 0) return reply_sz; From 5a7ba7e66b54adea1896917d5f5d4781e69f2a59 Mon Sep 17 00:00:00 2001 From: Brian Vazquez Date: Mon, 10 Nov 2025 20:58:37 +0000 Subject: [PATCH 1779/3902] idpf: reduce mbx_task schedule delay to 300us [ Upstream commit b3d6bbae1d6d5638a4ab702ab195476787cde857 ] During the IDPF init phase, the mailbox runs in poll mode until it is configured to properly handle interrupts. The previous delay of 300ms is excessively long for the mailbox polling mechanism, which causes a slow initialization of ~2s: echo 0000:06:12.4 > /sys/bus/pci/drivers/idpf/bind [ 52.444239] idpf 0000:06:12.4: enabling device (0000 -> 0002) [ 52.485005] idpf 0000:06:12.4: Device HW Reset initiated [ 54.177181] idpf 0000:06:12.4: PTP init failed, err=-EOPNOTSUPP [ 54.206177] idpf 0000:06:12.4: Minimum RX descriptor support not provided, using the default [ 54.206182] idpf 0000:06:12.4: Minimum TX descriptor support not provided, using the default Changing the delay to 300us avoids the delays during the initial mailbox transactions, making the init phase much faster: [ 83.342590] idpf 0000:06:12.4: enabling device (0000 -> 0002) [ 83.384402] idpf 0000:06:12.4: Device HW Reset initiated [ 83.518323] idpf 0000:06:12.4: PTP init failed, err=-EOPNOTSUPP [ 83.547430] idpf 0000:06:12.4: Minimum RX descriptor support not provided, using the default [ 83.547435] idpf 0000:06:12.4: Minimum TX descriptor support not provided, using the default Fixes: 4930fbf419a7 ("idpf: add core init and interrupt request") Signed-off-by: Brian Vazquez Reviewed-by: Aleksandr Loktionov Tested-by: Samuel Salin Signed-off-by: Tony Nguyen Signed-off-by: Sasha Levin --- drivers/net/ethernet/intel/idpf/idpf_lib.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/net/ethernet/intel/idpf/idpf_lib.c b/drivers/net/ethernet/intel/idpf/idpf_lib.c index 8a941f0fb048b4..aaafe40f5eaf74 100644 --- a/drivers/net/ethernet/intel/idpf/idpf_lib.c +++ b/drivers/net/ethernet/intel/idpf/idpf_lib.c @@ -1271,7 +1271,7 @@ void idpf_mbx_task(struct work_struct *work) idpf_mb_irq_enable(adapter); else queue_delayed_work(adapter->mbx_wq, &adapter->mbx_task, - msecs_to_jiffies(300)); + usecs_to_jiffies(300)); idpf_recv_mb_msg(adapter); } From 5d8b9d38a7676be7bb5e7d57f92156a98dab39fb Mon Sep 17 00:00:00 2001 From: Chen Ridong Date: Thu, 18 Dec 2025 01:59:50 +0000 Subject: [PATCH 1780/3902] cpuset: fix warning when disabling remote partition [ Upstream commit aa7d3a56a20f07978d9f401e13637a6479b13bd0 ] A warning was triggered as follows: WARNING: kernel/cgroup/cpuset.c:1651 at remote_partition_disable+0xf7/0x110 RIP: 0010:remote_partition_disable+0xf7/0x110 RSP: 0018:ffffc90001947d88 EFLAGS: 00000206 RAX: 0000000000007fff RBX: ffff888103b6e000 RCX: 0000000000006f40 RDX: 0000000000006f00 RSI: ffffc90001947da8 RDI: ffff888103b6e000 RBP: ffff888103b6e000 R08: 0000000000000000 R09: 0000000000000000 R10: 0000000000000001 R11: ffff88810b2e2728 R12: ffffc90001947da8 R13: 0000000000000000 R14: ffffc90001947da8 R15: ffff8881081f1c00 CS: 0010 DS: 0000 ES: 0000 CR0: 0000000080050033 CR2: 00007f55c8bbe0b2 CR3: 000000010b14c000 CR4: 00000000000006f0 Call Trace: update_prstate+0x2d3/0x580 cpuset_partition_write+0x94/0xf0 kernfs_fop_write_iter+0x147/0x200 vfs_write+0x35d/0x500 ksys_write+0x66/0xe0 do_syscall_64+0x6b/0x390 entry_SYSCALL_64_after_hwframe+0x4b/0x53 RIP: 0033:0x7f55c8cd4887 Reproduction steps (on a 16-CPU machine): # cd /sys/fs/cgroup/ # mkdir A1 # echo +cpuset > A1/cgroup.subtree_control # echo "0-14" > A1/cpuset.cpus.exclusive # mkdir A1/A2 # echo "0-14" > A1/A2/cpuset.cpus.exclusive # echo "root" > A1/A2/cpuset.cpus.partition # echo 0 > /sys/devices/system/cpu/cpu15/online # echo member > A1/A2/cpuset.cpus.partition When CPU 15 is offlined, subpartitions_cpus gets cleared because no CPUs remain available for the top_cpuset, forcing partitions to share CPUs with the top_cpuset. In this scenario, disabling the remote partition triggers a warning stating that effective_xcpus is not a subset of subpartitions_cpus. Partitions should be invalidated in this case to inform users that the partition is now invalid(cpus are shared with top_cpuset). To fix this issue: 1. Only emit the warning only if subpartitions_cpus is not empty and the effective_xcpus is not a subset of subpartitions_cpus. 2. During the CPU hotplug process, invalidate partitions if subpartitions_cpus is empty. Fixes: f62a5d39368e ("cgroup/cpuset: Remove remote_partition_check() & make update_cpumasks_hier() handle remote partition") Signed-off-by: Chen Ridong Reviewed-by: Waiman Long Signed-off-by: Tejun Heo Signed-off-by: Sasha Levin --- kernel/cgroup/cpuset.c | 21 ++++++++++++++++----- 1 file changed, 16 insertions(+), 5 deletions(-) diff --git a/kernel/cgroup/cpuset.c b/kernel/cgroup/cpuset.c index 4dcd633fd6df5f..61b56b6ca66a76 100644 --- a/kernel/cgroup/cpuset.c +++ b/kernel/cgroup/cpuset.c @@ -1600,7 +1600,14 @@ static void remote_partition_disable(struct cpuset *cs, struct tmpmasks *tmp) bool isolcpus_updated; WARN_ON_ONCE(!is_remote_partition(cs)); - WARN_ON_ONCE(!cpumask_subset(cs->effective_xcpus, subpartitions_cpus)); + /* + * When a CPU is offlined, top_cpuset may end up with no available CPUs, + * which should clear subpartitions_cpus. We should not emit a warning for this + * scenario: the hierarchy is updated from top to bottom, so subpartitions_cpus + * may already be cleared when disabling the partition. + */ + WARN_ON_ONCE(!cpumask_subset(cs->effective_xcpus, subpartitions_cpus) && + !cpumask_empty(subpartitions_cpus)); spin_lock_irq(&callback_lock); list_del_init(&cs->remote_sibling); @@ -3927,8 +3934,9 @@ static void cpuset_hotplug_update_tasks(struct cpuset *cs, struct tmpmasks *tmp) if (remote || (is_partition_valid(cs) && is_partition_valid(parent))) compute_partition_effective_cpumask(cs, &new_cpus); - if (remote && cpumask_empty(&new_cpus) && - partition_is_populated(cs, NULL)) { + if (remote && (cpumask_empty(subpartitions_cpus) || + (cpumask_empty(&new_cpus) && + partition_is_populated(cs, NULL)))) { cs->prs_err = PERR_HOTPLUG; remote_partition_disable(cs, tmp); compute_effective_cpumask(&new_cpus, cs, parent); @@ -3941,9 +3949,12 @@ static void cpuset_hotplug_update_tasks(struct cpuset *cs, struct tmpmasks *tmp) * 1) empty effective cpus but not valid empty partition. * 2) parent is invalid or doesn't grant any cpus to child * partitions. + * 3) subpartitions_cpus is empty. */ - if (is_local_partition(cs) && (!is_partition_valid(parent) || - tasks_nocpu_error(parent, cs, &new_cpus))) + if (is_local_partition(cs) && + (!is_partition_valid(parent) || + tasks_nocpu_error(parent, cs, &new_cpus) || + cpumask_empty(subpartitions_cpus))) partcmd = partcmd_invalidate; /* * On the other hand, an invalid partition root may be transitioned From 5476f7f8a311236604b78fcc5b2a63b3a61b0169 Mon Sep 17 00:00:00 2001 From: Herbert Xu Date: Wed, 17 Dec 2025 14:15:41 +0800 Subject: [PATCH 1781/3902] crypto: seqiv - Do not use req->iv after crypto_aead_encrypt [ Upstream commit 50fdb78b7c0bcc550910ef69c0984e751cac72fa ] As soon as crypto_aead_encrypt is called, the underlying request may be freed by an asynchronous completion. Thus dereferencing req->iv after it returns is invalid. Instead of checking req->iv against info, create a new variable unaligned_info and use it for that purpose instead. Fixes: 0a270321dbf9 ("[CRYPTO] seqiv: Add Sequence Number IV Generator") Reported-by: Xiumei Mu Reported-by: Xin Long Signed-off-by: Herbert Xu Signed-off-by: Sasha Levin --- crypto/seqiv.c | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/crypto/seqiv.c b/crypto/seqiv.c index 2bae99e3352683..678bb4145d783a 100644 --- a/crypto/seqiv.c +++ b/crypto/seqiv.c @@ -50,6 +50,7 @@ static int seqiv_aead_encrypt(struct aead_request *req) struct aead_geniv_ctx *ctx = crypto_aead_ctx(geniv); struct aead_request *subreq = aead_request_ctx(req); crypto_completion_t compl; + bool unaligned_info; void *data; u8 *info; unsigned int ivsize = 8; @@ -68,8 +69,9 @@ static int seqiv_aead_encrypt(struct aead_request *req) memcpy_sglist(req->dst, req->src, req->assoclen + req->cryptlen); - if (unlikely(!IS_ALIGNED((unsigned long)info, - crypto_aead_alignmask(geniv) + 1))) { + unaligned_info = !IS_ALIGNED((unsigned long)info, + crypto_aead_alignmask(geniv) + 1); + if (unlikely(unaligned_info)) { info = kmemdup(req->iv, ivsize, req->base.flags & CRYPTO_TFM_REQ_MAY_SLEEP ? GFP_KERNEL : GFP_ATOMIC); @@ -89,7 +91,7 @@ static int seqiv_aead_encrypt(struct aead_request *req) scatterwalk_map_and_copy(info, req->dst, req->assoclen, ivsize, 1); err = crypto_aead_encrypt(subreq); - if (unlikely(info != req->iv)) + if (unlikely(unaligned_info)) seqiv_aead_encrypt_complete2(req, err); return err; } From d084061f3360fe8d2b855276ee6e436b177bea65 Mon Sep 17 00:00:00 2001 From: Pauli Virtanen Date: Thu, 4 Dec 2025 22:40:20 +0200 Subject: [PATCH 1782/3902] Bluetooth: MGMT: report BIS capability flags in supported settings [ Upstream commit 348240e5fa901d3d4ba8dffa0e2ba9fc7aba93ab ] MGMT_SETTING_ISO_BROADCASTER and MGMT_SETTING_ISO_RECEIVER flags are missing from supported_settings although they are in current_settings. Report them also in supported_settings to be consistent. Fixes: ae7533613133 ("Bluetooth: Check for ISO support in controller") Signed-off-by: Pauli Virtanen Signed-off-by: Luiz Augusto von Dentz Signed-off-by: Sasha Levin --- net/bluetooth/mgmt.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/net/bluetooth/mgmt.c b/net/bluetooth/mgmt.c index 262bf984d2aafa..211951eb832af7 100644 --- a/net/bluetooth/mgmt.c +++ b/net/bluetooth/mgmt.c @@ -849,6 +849,12 @@ static u32 get_supported_settings(struct hci_dev *hdev) if (cis_peripheral_capable(hdev)) settings |= MGMT_SETTING_CIS_PERIPHERAL; + if (bis_capable(hdev)) + settings |= MGMT_SETTING_ISO_BROADCASTER; + + if (sync_recv_capable(hdev)) + settings |= MGMT_SETTING_ISO_SYNC_RECEIVER; + if (ll_privacy_capable(hdev)) settings |= MGMT_SETTING_LL_PRIVACY; From fdf7c640fb8a44a59b0671143d8c2f738bc48003 Mon Sep 17 00:00:00 2001 From: Raphael Pinsonneault-Thibeault Date: Wed, 10 Dec 2025 11:02:28 -0500 Subject: [PATCH 1783/3902] Bluetooth: btusb: revert use of devm_kzalloc in btusb [ Upstream commit 252714f1e8bdd542025b16321c790458014d6880 ] This reverts commit 98921dbd00c4e ("Bluetooth: Use devm_kzalloc in btusb.c file"). In btusb_probe(), we use devm_kzalloc() to allocate the btusb data. This ties the lifetime of all the btusb data to the binding of a driver to one interface, INTF. In a driver that binds to other interfaces, ISOC and DIAG, this is an accident waiting to happen. The issue is revealed in btusb_disconnect(), where calling usb_driver_release_interface(&btusb_driver, data->intf) will have devm free the data that is also being used by the other interfaces of the driver that may not be released yet. To fix this, revert the use of devm and go back to freeing memory explicitly. Fixes: 98921dbd00c4e ("Bluetooth: Use devm_kzalloc in btusb.c file") Signed-off-by: Raphael Pinsonneault-Thibeault Signed-off-by: Luiz Augusto von Dentz Signed-off-by: Sasha Levin --- drivers/bluetooth/btusb.c | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/drivers/bluetooth/btusb.c b/drivers/bluetooth/btusb.c index b92bfd131567ea..3420f711f0f089 100644 --- a/drivers/bluetooth/btusb.c +++ b/drivers/bluetooth/btusb.c @@ -4017,7 +4017,7 @@ static int btusb_probe(struct usb_interface *intf, return -ENODEV; } - data = devm_kzalloc(&intf->dev, sizeof(*data), GFP_KERNEL); + data = kzalloc(sizeof(*data), GFP_KERNEL); if (!data) return -ENOMEM; @@ -4040,8 +4040,10 @@ static int btusb_probe(struct usb_interface *intf, } } - if (!data->intr_ep || !data->bulk_tx_ep || !data->bulk_rx_ep) + if (!data->intr_ep || !data->bulk_tx_ep || !data->bulk_rx_ep) { + kfree(data); return -ENODEV; + } if (id->driver_info & BTUSB_AMP) { data->cmdreq_type = USB_TYPE_CLASS | 0x01; @@ -4096,8 +4098,10 @@ static int btusb_probe(struct usb_interface *intf, data->recv_acl = hci_recv_frame; hdev = hci_alloc_dev_priv(priv_size); - if (!hdev) + if (!hdev) { + kfree(data); return -ENOMEM; + } hdev->bus = HCI_USB; hci_set_drvdata(hdev, data); @@ -4370,6 +4374,7 @@ static int btusb_probe(struct usb_interface *intf, if (data->reset_gpio) gpiod_put(data->reset_gpio); hci_free_dev(hdev); + kfree(data); return err; } @@ -4418,6 +4423,7 @@ static void btusb_disconnect(struct usb_interface *intf) } hci_free_dev(hdev); + kfree(data); } #ifdef CONFIG_PM From 05359659c110482b5a667c79d77eeff484e11388 Mon Sep 17 00:00:00 2001 From: Jacky Chou Date: Thu, 11 Dec 2025 14:24:58 +0800 Subject: [PATCH 1784/3902] net: mdio: aspeed: add dummy read to avoid read-after-write issue [ Upstream commit d1a1a4bade4b20c0858d0b2f81d2611de055f675 ] The Aspeed MDIO controller may return incorrect data when a read operation follows immediately after a write. Due to a controller bug, the subsequent read can latch stale data, causing the polling logic to terminate earlier than expected. To work around this hardware issue, insert a dummy read after each write operation. This ensures that the next actual read returns the correct data and prevents premature polling exit. This workaround has been verified to stabilize MDIO transactions on affected Aspeed platforms. Fixes: f160e99462c6 ("net: phy: Add mdio-aspeed") Signed-off-by: Jacky Chou Reviewed-by: Andrew Lunn Link: https://patch.msgid.link/20251211-aspeed_mdio_add_dummy_read-v3-1-382868869004@aspeedtech.com Signed-off-by: Paolo Abeni Signed-off-by: Sasha Levin --- drivers/net/mdio/mdio-aspeed.c | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/drivers/net/mdio/mdio-aspeed.c b/drivers/net/mdio/mdio-aspeed.c index e55be6dc9ae70f..d6b9004c61dc16 100644 --- a/drivers/net/mdio/mdio-aspeed.c +++ b/drivers/net/mdio/mdio-aspeed.c @@ -63,6 +63,13 @@ static int aspeed_mdio_op(struct mii_bus *bus, u8 st, u8 op, u8 phyad, u8 regad, iowrite32(ctrl, ctx->base + ASPEED_MDIO_CTRL); + /* Workaround for read-after-write issue. + * The controller may return stale data if a read follows immediately + * after a write. A dummy read forces the hardware to update its + * internal state, ensuring that the next real read returns correct data. + */ + ioread32(ctx->base + ASPEED_MDIO_CTRL); + return readl_poll_timeout(ctx->base + ASPEED_MDIO_CTRL, ctrl, !(ctrl & ASPEED_MDIO_CTRL_FIRE), ASPEED_MDIO_INTERVAL_US, From f31557fb1b35332cca9994aa196cef284bcf3807 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Toke=20H=C3=B8iland-J=C3=B8rgensen?= Date: Thu, 11 Dec 2025 12:50:05 +0100 Subject: [PATCH 1785/3902] net: openvswitch: Avoid needlessly taking the RTNL on vport destroy MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [ Upstream commit 5498227676303e3ffa9a3a46214af96bc3e81314 ] The openvswitch teardown code will immediately call ovs_netdev_detach_dev() in response to a NETDEV_UNREGISTER notification. It will then start the dp_notify_work workqueue, which will later end up calling the vport destroy() callback. This callback takes the RTNL to do another ovs_netdev_detach_port(), which in this case is unnecessary. This causes extra pressure on the RTNL, in some cases leading to "unregister_netdevice: waiting for XX to become free" warnings on teardown. We can straight-forwardly avoid the extra RTNL lock acquisition by checking the device flags before taking the lock, and skip the locking altogether if the IFF_OVS_DATAPATH flag has already been unset. Fixes: b07c26511e94 ("openvswitch: fix vport-netdev unregister") Tested-by: Adrian Moreno Signed-off-by: Toke Høiland-Jørgensen Acked-by: Eelco Chaudron Acked-by: Aaron Conole Link: https://patch.msgid.link/20251211115006.228876-1-toke@redhat.com Signed-off-by: Paolo Abeni Signed-off-by: Sasha Levin --- net/openvswitch/vport-netdev.c | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/net/openvswitch/vport-netdev.c b/net/openvswitch/vport-netdev.c index 91a11067e4588c..6574f9bcdc0268 100644 --- a/net/openvswitch/vport-netdev.c +++ b/net/openvswitch/vport-netdev.c @@ -160,10 +160,19 @@ void ovs_netdev_detach_dev(struct vport *vport) static void netdev_destroy(struct vport *vport) { - rtnl_lock(); - if (netif_is_ovs_port(vport->dev)) - ovs_netdev_detach_dev(vport); - rtnl_unlock(); + /* When called from ovs_db_notify_wq() after a dp_device_event(), the + * port has already been detached, so we can avoid taking the RTNL by + * checking this first. + */ + if (netif_is_ovs_port(vport->dev)) { + rtnl_lock(); + /* Check again while holding the lock to ensure we don't race + * with the netdev notifier and detach twice. + */ + if (netif_is_ovs_port(vport->dev)) + ovs_netdev_detach_dev(vport); + rtnl_unlock(); + } call_rcu(&vport->rcu, vport_netdev_free); } From 91a2b25be07ce1a7549ceebbe82017551d2eec92 Mon Sep 17 00:00:00 2001 From: Eric Dumazet Date: Thu, 11 Dec 2025 17:35:50 +0000 Subject: [PATCH 1786/3902] ip6_gre: make ip6gre_header() robust [ Upstream commit db5b4e39c4e63700c68a7e65fc4e1f1375273476 ] Over the years, syzbot found many ways to crash the kernel in ip6gre_header() [1]. This involves team or bonding drivers ability to dynamically change their dev->needed_headroom and/or dev->hard_header_len In this particular crash mld_newpack() allocated an skb with a too small reserve/headroom, and by the time mld_sendpack() was called, syzbot managed to attach an ip6gre device. [1] skbuff: skb_under_panic: text:ffffffff8a1d69a8 len:136 put:40 head:ffff888059bc7000 data:ffff888059bc6fe8 tail:0x70 end:0x6c0 dev:team0 ------------[ cut here ]------------ kernel BUG at net/core/skbuff.c:213 ! skb_under_panic net/core/skbuff.c:223 [inline] skb_push+0xc3/0xe0 net/core/skbuff.c:2641 ip6gre_header+0xc8/0x790 net/ipv6/ip6_gre.c:1371 dev_hard_header include/linux/netdevice.h:3436 [inline] neigh_connected_output+0x286/0x460 net/core/neighbour.c:1618 neigh_output include/net/neighbour.h:556 [inline] ip6_finish_output2+0xfb3/0x1480 net/ipv6/ip6_output.c:136 __ip6_finish_output net/ipv6/ip6_output.c:-1 [inline] ip6_finish_output+0x234/0x7d0 net/ipv6/ip6_output.c:220 NF_HOOK_COND include/linux/netfilter.h:307 [inline] ip6_output+0x340/0x550 net/ipv6/ip6_output.c:247 NF_HOOK+0x9e/0x380 include/linux/netfilter.h:318 mld_sendpack+0x8d4/0xe60 net/ipv6/mcast.c:1855 mld_send_cr net/ipv6/mcast.c:2154 [inline] mld_ifc_work+0x83e/0xd60 net/ipv6/mcast.c:2693 Fixes: c12b395a4664 ("gre: Support GRE over IPv6") Reported-by: syzbot+43a2ebcf2a64b1102d64@syzkaller.appspotmail.com Closes: https://lore.kernel.org/netdev/693b002c.a70a0220.33cd7b.0033.GAE@google.com/T/#u Signed-off-by: Eric Dumazet Link: https://patch.msgid.link/20251211173550.2032674-1-edumazet@google.com Signed-off-by: Paolo Abeni Signed-off-by: Sasha Levin --- net/ipv6/ip6_gre.c | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/net/ipv6/ip6_gre.c b/net/ipv6/ip6_gre.c index c82a75510c0e2f..8bc3f05f594ed6 100644 --- a/net/ipv6/ip6_gre.c +++ b/net/ipv6/ip6_gre.c @@ -1366,9 +1366,16 @@ static int ip6gre_header(struct sk_buff *skb, struct net_device *dev, { struct ip6_tnl *t = netdev_priv(dev); struct ipv6hdr *ipv6h; + int needed; __be16 *p; - ipv6h = skb_push(skb, t->hlen + sizeof(*ipv6h)); + needed = t->hlen + sizeof(*ipv6h); + if (skb_headroom(skb) < needed && + pskb_expand_head(skb, HH_DATA_ALIGN(needed - skb_headroom(skb)), + 0, GFP_ATOMIC)) + return -needed; + + ipv6h = skb_push(skb, needed); ip6_flow_hdr(ipv6h, 0, ip6_make_flowlabel(dev_net(dev), skb, t->fl.u.ip6.flowlabel, true, &t->fl.u.ip6)); From 26cca984de64f7a89787eed936503964c8d17587 Mon Sep 17 00:00:00 2001 From: Jan Stancek Date: Tue, 23 Sep 2025 17:32:16 +0200 Subject: [PATCH 1787/3902] powerpc/tools: drop `-o pipefail` in gcc check scripts [ Upstream commit f1164534ad62f0cc247d99650b07bd59ad2a49fd ] Fixes: 0f71dcfb4aef ("powerpc/ftrace: Add support for -fpatchable-function-entry") Fixes: b71c9ffb1405 ("powerpc: Add arch/powerpc/tools directory") Reported-by: Joe Lawrence Acked-by: Joe Lawrence Signed-off-by: Jan Stancek Fixes: 8c50b72a3b4f ("powerpc/ftrace: Add Kconfig & Make glue for mprofile-kernel") Fixes: abba759796f9 ("powerpc/kbuild: move -mprofile-kernel check to Kconfig") Tested-by: Justin M. Forbes Reviewed-by: Naveen N Rao (AMD) Reviewed-by: Josh Poimboeuf Signed-off-by: Madhavan Srinivasan Link: https://patch.msgid.link/cc6cdd116c3ad9d990df21f13c6d8e8a83815bbd.1758641374.git.jstancek@redhat.com Signed-off-by: Sasha Levin --- arch/powerpc/tools/gcc-check-fpatchable-function-entry.sh | 1 - arch/powerpc/tools/gcc-check-mprofile-kernel.sh | 1 - 2 files changed, 2 deletions(-) diff --git a/arch/powerpc/tools/gcc-check-fpatchable-function-entry.sh b/arch/powerpc/tools/gcc-check-fpatchable-function-entry.sh index 06706903503b6c..baed467a016b3d 100755 --- a/arch/powerpc/tools/gcc-check-fpatchable-function-entry.sh +++ b/arch/powerpc/tools/gcc-check-fpatchable-function-entry.sh @@ -2,7 +2,6 @@ # SPDX-License-Identifier: GPL-2.0 set -e -set -o pipefail # To debug, uncomment the following line # set -x diff --git a/arch/powerpc/tools/gcc-check-mprofile-kernel.sh b/arch/powerpc/tools/gcc-check-mprofile-kernel.sh index 73e331e7660ef1..6193b0ed0c7751 100755 --- a/arch/powerpc/tools/gcc-check-mprofile-kernel.sh +++ b/arch/powerpc/tools/gcc-check-mprofile-kernel.sh @@ -2,7 +2,6 @@ # SPDX-License-Identifier: GPL-2.0 set -e -set -o pipefail # To debug, uncomment the following line # set -x From f1c7923932bbcbc4faa0daa961075edd4f3d327a Mon Sep 17 00:00:00 2001 From: Shravan Kumar Ramani Date: Thu, 18 Dec 2025 12:18:13 +0000 Subject: [PATCH 1788/3902] platform/mellanox: mlxbf-pmc: Remove trailing whitespaces from event names MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [ Upstream commit f13bce715d1600698310a4a7832f6a52499d5395 ] Some event names have trailing whitespaces at the end which causes programming of counters using the name for these specific events to fail and hence need to be removed. Fixes: 423c3361855c ("platform/mellanox: mlxbf-pmc: Add support for BlueField-3") Signed-off-by: Shravan Kumar Ramani Reviewed-by: David Thompson Link: https://patch.msgid.link/065cbae0717dcc1169681c4dbb1a6e050b8574b3.1766059953.git.shravankr@nvidia.com Reviewed-by: Ilpo Järvinen Signed-off-by: Ilpo Järvinen Signed-off-by: Sasha Levin --- drivers/platform/mellanox/mlxbf-pmc.c | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/drivers/platform/mellanox/mlxbf-pmc.c b/drivers/platform/mellanox/mlxbf-pmc.c index 16a2fd9fdd9b81..5ec1ad4716967d 100644 --- a/drivers/platform/mellanox/mlxbf-pmc.c +++ b/drivers/platform/mellanox/mlxbf-pmc.c @@ -801,18 +801,18 @@ static const struct mlxbf_pmc_events mlxbf_pmc_llt_miss_events[] = { {11, "GDC_MISS_MACHINE_CHI_TXDAT"}, {12, "GDC_MISS_MACHINE_CHI_RXDAT"}, {13, "GDC_MISS_MACHINE_G_FIFO_FF_EXEC0_0"}, - {14, "GDC_MISS_MACHINE_G_FIFO_FF_EXEC0_1 "}, + {14, "GDC_MISS_MACHINE_G_FIFO_FF_EXEC0_1"}, {15, "GDC_MISS_MACHINE_G_FIFO_FF_EXEC0_2"}, - {16, "GDC_MISS_MACHINE_G_FIFO_FF_EXEC0_3 "}, - {17, "GDC_MISS_MACHINE_G_FIFO_FF_EXEC1_0 "}, - {18, "GDC_MISS_MACHINE_G_FIFO_FF_EXEC1_1 "}, - {19, "GDC_MISS_MACHINE_G_FIFO_FF_EXEC1_2 "}, - {20, "GDC_MISS_MACHINE_G_FIFO_FF_EXEC1_3 "}, + {16, "GDC_MISS_MACHINE_G_FIFO_FF_EXEC0_3"}, + {17, "GDC_MISS_MACHINE_G_FIFO_FF_EXEC1_0"}, + {18, "GDC_MISS_MACHINE_G_FIFO_FF_EXEC1_1"}, + {19, "GDC_MISS_MACHINE_G_FIFO_FF_EXEC1_2"}, + {20, "GDC_MISS_MACHINE_G_FIFO_FF_EXEC1_3"}, {21, "GDC_MISS_MACHINE_G_FIFO_FF_EXEC_DONE0_0"}, {22, "GDC_MISS_MACHINE_G_FIFO_FF_EXEC_DONE0_1"}, {23, "GDC_MISS_MACHINE_G_FIFO_FF_EXEC_DONE0_2"}, {24, "GDC_MISS_MACHINE_G_FIFO_FF_EXEC_DONE0_3"}, - {25, "GDC_MISS_MACHINE_G_FIFO_FF_EXEC_DONE1_0 "}, + {25, "GDC_MISS_MACHINE_G_FIFO_FF_EXEC_DONE1_0"}, {26, "GDC_MISS_MACHINE_G_FIFO_FF_EXEC_DONE1_1"}, {27, "GDC_MISS_MACHINE_G_FIFO_FF_EXEC_DONE1_2"}, {28, "GDC_MISS_MACHINE_G_FIFO_FF_EXEC_DONE1_3"}, From 8f2ba8ac9ca5027f06c0e6d8074640d2e639c0d6 Mon Sep 17 00:00:00 2001 From: Thomas Fourier Date: Wed, 17 Dec 2025 11:36:13 +0100 Subject: [PATCH 1789/3902] platform/x86: msi-laptop: add missing sysfs_remove_group() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [ Upstream commit 1461209cf813b6ee6d40f29b96b544587df6d2b1 ] A sysfs group is created in msi_init() when old_ec_model is enabled, but never removed. Remove the msipf_old_attribute_group in that case. Fixes: 03696e51d75a ("msi-laptop: Disable brightness control for new EC") Signed-off-by: Thomas Fourier Link: https://patch.msgid.link/20251217103617.27668-2-fourier.thomas@gmail.com Reviewed-by: Ilpo Järvinen Signed-off-by: Ilpo Järvinen Signed-off-by: Sasha Levin --- drivers/platform/x86/msi-laptop.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/drivers/platform/x86/msi-laptop.c b/drivers/platform/x86/msi-laptop.c index c4b150fa093fef..ddef6b78d2fa9f 100644 --- a/drivers/platform/x86/msi-laptop.c +++ b/drivers/platform/x86/msi-laptop.c @@ -1130,6 +1130,9 @@ static void __exit msi_cleanup(void) sysfs_remove_group(&msipf_device->dev.kobj, &msipf_attribute_group); if (!quirks->old_ec_model && threeg_exists) device_remove_file(&msipf_device->dev, &dev_attr_threeg); + if (quirks->old_ec_model) + sysfs_remove_group(&msipf_device->dev.kobj, + &msipf_old_attribute_group); platform_device_unregister(msipf_device); platform_driver_unregister(&msipf_driver); backlight_device_unregister(msibl_device); From 4defca287bae36a08159952b378ae30255d6d3f1 Mon Sep 17 00:00:00 2001 From: Junrui Luo Date: Fri, 19 Dec 2025 16:30:29 +0800 Subject: [PATCH 1790/3902] platform/x86: ibm_rtl: fix EBDA signature search pointer arithmetic MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [ Upstream commit 15dd100349b8526cbdf2de0ce3e72e700eb6c208 ] The ibm_rtl_init() function searches for the signature but has a pointer arithmetic error. The loop counter suggests searching at 4-byte intervals but the implementation only advances by 1 byte per iteration. Fix by properly advancing the pointer by sizeof(unsigned int) bytes each iteration. Reported-by: Yuhao Jiang Reported-by: Junrui Luo Fixes: 35f0ce032b0f ("IBM Real-Time "SMI Free" mode driver -v7") Signed-off-by: Junrui Luo Link: https://patch.msgid.link/SYBPR01MB78812D887A92DE3802D0D06EAFA9A@SYBPR01MB7881.ausprd01.prod.outlook.com Reviewed-by: Ilpo Järvinen Signed-off-by: Ilpo Järvinen Signed-off-by: Sasha Levin --- drivers/platform/x86/ibm_rtl.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/platform/x86/ibm_rtl.c b/drivers/platform/x86/ibm_rtl.c index 231b3790980170..139956168cf947 100644 --- a/drivers/platform/x86/ibm_rtl.c +++ b/drivers/platform/x86/ibm_rtl.c @@ -273,7 +273,7 @@ static int __init ibm_rtl_init(void) { /* search for the _RTL_ signature at the start of the table */ for (i = 0 ; i < ebda_size/sizeof(unsigned int); i++) { struct ibm_rtl_table __iomem * tmp; - tmp = (struct ibm_rtl_table __iomem *) (ebda_map+i); + tmp = (struct ibm_rtl_table __iomem *) (ebda_map + i*sizeof(unsigned int)); if ((readq(&tmp->signature) & RTL_MASK) == RTL_SIGNATURE) { phys_addr_t addr; unsigned int plen; From b71187648ef2349254673d0523fdf96d1fe3d758 Mon Sep 17 00:00:00 2001 From: Jiri Pirko Date: Fri, 12 Dec 2025 11:29:53 +0100 Subject: [PATCH 1791/3902] team: fix check for port enabled in team_queue_override_port_prio_changed() [ Upstream commit 932ac51d9953eaf77a1252f79b656d4ca86163c6 ] There has been a syzkaller bug reported recently with the following trace: list_del corruption, ffff888058bea080->prev is LIST_POISON2 (dead000000000122) ------------[ cut here ]------------ kernel BUG at lib/list_debug.c:59! Oops: invalid opcode: 0000 [#1] SMP KASAN NOPTI CPU: 3 UID: 0 PID: 21246 Comm: syz.0.2928 Not tainted syzkaller #0 PREEMPT(full) Hardware name: QEMU Standard PC (Q35 + ICH9, 2009), BIOS 1.16.3-debian-1.16.3-2~bpo12+1 04/01/2014 RIP: 0010:__list_del_entry_valid_or_report+0x13e/0x200 lib/list_debug.c:59 Code: 48 c7 c7 e0 71 f0 8b e8 30 08 ef fc 90 0f 0b 48 89 ef e8 a5 02 55 fd 48 89 ea 48 89 de 48 c7 c7 40 72 f0 8b e8 13 08 ef fc 90 <0f> 0b 48 89 ef e8 88 02 55 fd 48 89 ea 48 b8 00 00 00 00 00 fc ff RSP: 0018:ffffc9000d49f370 EFLAGS: 00010286 RAX: 000000000000004e RBX: ffff888058bea080 RCX: ffffc9002817d000 RDX: 0000000000000000 RSI: ffffffff819becc6 RDI: 0000000000000005 RBP: dead000000000122 R08: 0000000000000005 R09: 0000000000000000 R10: 0000000080000000 R11: 0000000000000001 R12: ffff888039e9c230 R13: ffff888058bea088 R14: ffff888058bea080 R15: ffff888055461480 FS: 00007fbbcfe6f6c0(0000) GS:ffff8880d6d0a000(0000) knlGS:0000000000000000 CS: 0010 DS: 0000 ES: 0000 CR0: 0000000080050033 CR2: 000000110c3afcb0 CR3: 00000000382c7000 CR4: 0000000000352ef0 Call Trace: __list_del_entry_valid include/linux/list.h:132 [inline] __list_del_entry include/linux/list.h:223 [inline] list_del_rcu include/linux/rculist.h:178 [inline] __team_queue_override_port_del drivers/net/team/team_core.c:826 [inline] __team_queue_override_port_del drivers/net/team/team_core.c:821 [inline] team_queue_override_port_prio_changed drivers/net/team/team_core.c:883 [inline] team_priority_option_set+0x171/0x2f0 drivers/net/team/team_core.c:1534 team_option_set drivers/net/team/team_core.c:376 [inline] team_nl_options_set_doit+0x8ae/0xe60 drivers/net/team/team_core.c:2653 genl_family_rcv_msg_doit+0x209/0x2f0 net/netlink/genetlink.c:1115 genl_family_rcv_msg net/netlink/genetlink.c:1195 [inline] genl_rcv_msg+0x55c/0x800 net/netlink/genetlink.c:1210 netlink_rcv_skb+0x158/0x420 net/netlink/af_netlink.c:2552 genl_rcv+0x28/0x40 net/netlink/genetlink.c:1219 netlink_unicast_kernel net/netlink/af_netlink.c:1320 [inline] netlink_unicast+0x5aa/0x870 net/netlink/af_netlink.c:1346 netlink_sendmsg+0x8c8/0xdd0 net/netlink/af_netlink.c:1896 sock_sendmsg_nosec net/socket.c:727 [inline] __sock_sendmsg net/socket.c:742 [inline] ____sys_sendmsg+0xa98/0xc70 net/socket.c:2630 ___sys_sendmsg+0x134/0x1d0 net/socket.c:2684 __sys_sendmsg+0x16d/0x220 net/socket.c:2716 do_syscall_x64 arch/x86/entry/syscall_64.c:63 [inline] do_syscall_64+0xcd/0xfa0 arch/x86/entry/syscall_64.c:94 entry_SYSCALL_64_after_hwframe+0x77/0x7f The problem is in this flow: 1) Port is enabled, queue_id != 0, in qom_list 2) Port gets disabled -> team_port_disable() -> team_queue_override_port_del() -> del (removed from list) 3) Port is disabled, queue_id != 0, not in any list 4) Priority changes -> team_queue_override_port_prio_changed() -> checks: port disabled && queue_id != 0 -> calls del - hits the BUG as it is removed already To fix this, change the check in team_queue_override_port_prio_changed() so it returns early if port is not enabled. Reported-by: syzbot+422806e5f4cce722a71f@syzkaller.appspotmail.com Closes: https://syzkaller.appspot.com/bug?extid=422806e5f4cce722a71f Fixes: 6c31ff366c11 ("team: remove synchronize_rcu() called during queue override change") Signed-off-by: Jiri Pirko Reviewed-by: Simon Horman Link: https://patch.msgid.link/20251212102953.167287-1-jiri@resnulli.us Signed-off-by: Paolo Abeni Signed-off-by: Sasha Levin --- drivers/net/team/team_core.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/net/team/team_core.c b/drivers/net/team/team_core.c index 25562b17debe1b..2fd3469d10464e 100644 --- a/drivers/net/team/team_core.c +++ b/drivers/net/team/team_core.c @@ -878,7 +878,7 @@ static void __team_queue_override_enabled_check(struct team *team) static void team_queue_override_port_prio_changed(struct team *team, struct team_port *port) { - if (!port->queue_id || team_port_enabled(port)) + if (!port->queue_id || !team_port_enabled(port)) return; __team_queue_override_port_del(team, port); __team_queue_override_port_add(team, port); From 0b4fa7ac13061158030744f6991b0aa696e0f173 Mon Sep 17 00:00:00 2001 From: Lorenzo Bianconi Date: Sun, 14 Dec 2025 10:30:07 +0100 Subject: [PATCH 1792/3902] net: airoha: Move net_devs registration in a dedicated routine [ Upstream commit 5e7365b5a1ac8f517a7a84442289d7de242deb76 ] Since airoha_probe() is not executed under rtnl lock, there is small race where a given device is configured by user-space while the remaining ones are not completely loaded from the dts yet. This condition will allow a hw device misconfiguration since there are some conditions (e.g. GDM2 check in airoha_dev_init()) that require all device are properly loaded from the device tree. Fix the issue moving net_devices registration at the end of the airoha_probe routine. Fixes: 9cd451d414f6e ("net: airoha: Add loopback support for GDM2") Signed-off-by: Lorenzo Bianconi Reviewed-by: Simon Horman Link: https://patch.msgid.link/20251214-airoha-fix-dev-registration-v1-1-860e027ad4c6@kernel.org Signed-off-by: Paolo Abeni Signed-off-by: Sasha Levin --- drivers/net/ethernet/airoha/airoha_eth.c | 39 ++++++++++++++++-------- 1 file changed, 26 insertions(+), 13 deletions(-) diff --git a/drivers/net/ethernet/airoha/airoha_eth.c b/drivers/net/ethernet/airoha/airoha_eth.c index 433a646e983177..0394ba6a90a9b2 100644 --- a/drivers/net/ethernet/airoha/airoha_eth.c +++ b/drivers/net/ethernet/airoha/airoha_eth.c @@ -2900,19 +2900,26 @@ static int airoha_alloc_gdm_port(struct airoha_eth *eth, port->id = id; eth->ports[p] = port; - err = airoha_metadata_dst_alloc(port); - if (err) - return err; + return airoha_metadata_dst_alloc(port); +} - err = register_netdev(dev); - if (err) - goto free_metadata_dst; +static int airoha_register_gdm_devices(struct airoha_eth *eth) +{ + int i; - return 0; + for (i = 0; i < ARRAY_SIZE(eth->ports); i++) { + struct airoha_gdm_port *port = eth->ports[i]; + int err; -free_metadata_dst: - airoha_metadata_dst_free(port); - return err; + if (!port) + continue; + + err = register_netdev(port->dev); + if (err) + return err; + } + + return 0; } static int airoha_probe(struct platform_device *pdev) @@ -2993,6 +3000,10 @@ static int airoha_probe(struct platform_device *pdev) } } + err = airoha_register_gdm_devices(eth); + if (err) + goto error_napi_stop; + return 0; error_napi_stop: @@ -3006,10 +3017,12 @@ static int airoha_probe(struct platform_device *pdev) for (i = 0; i < ARRAY_SIZE(eth->ports); i++) { struct airoha_gdm_port *port = eth->ports[i]; - if (port && port->dev->reg_state == NETREG_REGISTERED) { + if (!port) + continue; + + if (port->dev->reg_state == NETREG_REGISTERED) unregister_netdev(port->dev); - airoha_metadata_dst_free(port); - } + airoha_metadata_dst_free(port); } free_netdev(eth->napi_dev); platform_set_drvdata(pdev, NULL); From 0e766b77ba5093583dfe609fae0aa1545c46dbbd Mon Sep 17 00:00:00 2001 From: Vladimir Oltean Date: Mon, 15 Dec 2025 17:02:35 +0200 Subject: [PATCH 1793/3902] net: dsa: properly keep track of conduit reference [ Upstream commit 06e219f6a706c367c93051f408ac61417643d2f9 ] Problem description ------------------- DSA has a mumbo-jumbo of reference handling of the conduit net device and its kobject which, sadly, is just wrong and doesn't make sense. There are two distinct problems. 1. The OF path, which uses of_find_net_device_by_node(), never releases the elevated refcount on the conduit's kobject. Nominally, the OF and non-OF paths should result in objects having identical reference counts taken, and it is already suspicious that dsa_dev_to_net_device() has a put_device() call which is missing in dsa_port_parse_of(), but we can actually even verify that an issue exists. With CONFIG_DEBUG_KOBJECT_RELEASE=y, if we run this command "before" and "after" applying this patch: (unbind the conduit driver for net device eno2) echo 0000:00:00.2 > /sys/bus/pci/drivers/fsl_enetc/unbind we see these lines in the output diff which appear only with the patch applied: kobject: 'eno2' (ffff002009a3a6b8): kobject_release, parent 0000000000000000 (delayed 1000) kobject: '109' (ffff0020099d59a0): kobject_release, parent 0000000000000000 (delayed 1000) 2. After we find the conduit interface one way (OF) or another (non-OF), it can get unregistered at any time, and DSA remains with a long-lived, but in this case stale, cpu_dp->conduit pointer. Holding the net device's underlying kobject isn't actually of much help, it just prevents it from being freed (but we never need that kobject directly). What helps us to prevent the net device from being unregistered is the parallel netdev reference mechanism (dev_hold() and dev_put()). Actually we actually use that netdev tracker mechanism implicitly on user ports since commit 2f1e8ea726e9 ("net: dsa: link interfaces with the DSA master to get rid of lockdep warnings"), via netdev_upper_dev_link(). But time still passes at DSA switch probe time between the initial of_find_net_device_by_node() code and the user port creation time, time during which the conduit could unregister itself and DSA wouldn't know about it. So we have to run of_find_net_device_by_node() under rtnl_lock() to prevent that from happening, and release the lock only with the netdev tracker having acquired the reference. Do we need to keep the reference until dsa_unregister_switch() / dsa_switch_shutdown()? 1: Maybe yes. A switch device will still be registered even if all user ports failed to probe, see commit 86f8b1c01a0a ("net: dsa: Do not make user port errors fatal"), and the cpu_dp->conduit pointers remain valid. I haven't audited all call paths to see whether they will actually use the conduit in lack of any user port, but if they do, it seems safer to not rely on user ports for that reference. 2. Definitely yes. We support changing the conduit which a user port is associated to, and we can get into a situation where we've moved all user ports away from a conduit, thus no longer hold any reference to it via the net device tracker. But we shouldn't let it go nonetheless - see the next change in relation to dsa_tree_find_first_conduit() and LAG conduits which disappear. We have to be prepared to return to the physical conduit, so the CPU port must explicitly keep another reference to it. This is also to say: the user ports and their CPU ports may not always keep a reference to the same conduit net device, and both are needed. As for the conduit's kobject for the /sys/class/net/ entry, we don't care about it, we can release it as soon as we hold the net device object itself. History and blame attribution ----------------------------- The code has been refactored so many times, it is very difficult to follow and properly attribute a blame, but I'll try to make a short history which I hope to be correct. We have two distinct probing paths: - one for OF, introduced in 2016 in commit 83c0afaec7b7 ("net: dsa: Add new binding implementation") - one for non-OF, introduced in 2017 in commit 71e0bbde0d88 ("net: dsa: Add support for platform data") These are both complete rewrites of the original probing paths (which used struct dsa_switch_driver and other weird stuff, instead of regular devices on their respective buses for register access, like MDIO, SPI, I2C etc): - one for OF, introduced in 2013 in commit 5e95329b701c ("dsa: add device tree bindings to register DSA switches") - one for non-OF, introduced in 2008 in commit 91da11f870f0 ("net: Distributed Switch Architecture protocol support") except for tiny bits and pieces like dsa_dev_to_net_device() which were seemingly carried over since the original commit, and used to this day. The point is that the original probing paths received a fix in 2015 in the form of commit 679fb46c5785 ("net: dsa: Add missing master netdev dev_put() calls"), but the fix never made it into the "new" (dsa2) probing paths that can still be traced to today, and the fixed probing path was later deleted in 2019 in commit 93e86b3bc842 ("net: dsa: Remove legacy probing support"). That is to say, the new probing paths were never quite correct in this area. The existence of the legacy probing support which was deleted in 2019 explains why dsa_dev_to_net_device() returns a conduit with elevated refcount (because it was supposed to be released during dsa_remove_dst()). After the removal of the legacy code, the only user of dsa_dev_to_net_device() calls dev_put(conduit) immediately after this function returns. This pattern makes no sense today, and can only be interpreted historically to understand why dev_hold() was there in the first place. Change details -------------- Today we have a better netdev tracking infrastructure which we should use. Logically netdev_hold() belongs in common code (dsa_port_parse_cpu(), where dp->conduit is assigned), but there is a tradeoff to be made with the rtnl_lock() section which would become a bit too long if we did that - dsa_port_parse_cpu() also calls request_module(). So we duplicate a bit of logic in order for the callers of dsa_port_parse_cpu() to be the ones responsible of holding the conduit reference and releasing it on error. This shortens the rtnl_lock() section significantly. In the dsa_switch_probe() error path, dsa_switch_release_ports() will be called in a number of situations, one being where dsa_port_parse_cpu() maybe didn't get the chance to run at all (a different port failed earlier, etc). So we have to test for the conduit being NULL prior to calling netdev_put(). There have still been so many transformations to the code since the blamed commits (rename master -> conduit, commit 0650bf52b31f ("net: dsa: be compatible with masters which unregister on shutdown")), that it only makes sense to fix the code using the best methods available today and see how it can be backported to stable later. I suspect the fix cannot even be backported to kernels which lack dsa_switch_shutdown(), and I suspect this is also maybe why the long-lived conduit reference didn't make it into the new DSA probing paths at the time (problems during shutdown). Because dsa_dev_to_net_device() has a single call site and has to be changed anyway, the logic was just absorbed into the non-OF dsa_port_parse(). Tested on the ocelot/felix switch and on dsa_loop, both on the NXP LS1028A with CONFIG_DEBUG_KOBJECT_RELEASE=y. Reported-by: Ma Ke Closes: https://lore.kernel.org/netdev/20251214131204.4684-1-make24@iscas.ac.cn/ Fixes: 83c0afaec7b7 ("net: dsa: Add new binding implementation") Fixes: 71e0bbde0d88 ("net: dsa: Add support for platform data") Reviewed-by: Jonas Gorski Signed-off-by: Vladimir Oltean Link: https://patch.msgid.link/20251215150236.3931670-1-vladimir.oltean@nxp.com Signed-off-by: Paolo Abeni Signed-off-by: Sasha Levin --- include/net/dsa.h | 1 + net/dsa/dsa.c | 59 +++++++++++++++++++++++++++-------------------- 2 files changed, 35 insertions(+), 25 deletions(-) diff --git a/include/net/dsa.h b/include/net/dsa.h index d73ea08800660e..5cb456bf46394b 100644 --- a/include/net/dsa.h +++ b/include/net/dsa.h @@ -298,6 +298,7 @@ struct dsa_port { struct devlink_port devlink_port; struct phylink *pl; struct phylink_config pl_config; + netdevice_tracker conduit_tracker; struct dsa_lag *lag; struct net_device *hsr_dev; diff --git a/net/dsa/dsa.c b/net/dsa/dsa.c index 5b01a0e43ebe8a..d466d023408720 100644 --- a/net/dsa/dsa.c +++ b/net/dsa/dsa.c @@ -1252,14 +1252,25 @@ static int dsa_port_parse_of(struct dsa_port *dp, struct device_node *dn) if (ethernet) { struct net_device *conduit; const char *user_protocol; + int err; + rtnl_lock(); conduit = of_find_net_device_by_node(ethernet); of_node_put(ethernet); - if (!conduit) + if (!conduit) { + rtnl_unlock(); return -EPROBE_DEFER; + } + + netdev_hold(conduit, &dp->conduit_tracker, GFP_KERNEL); + put_device(&conduit->dev); + rtnl_unlock(); user_protocol = of_get_property(dn, "dsa-tag-protocol", NULL); - return dsa_port_parse_cpu(dp, conduit, user_protocol); + err = dsa_port_parse_cpu(dp, conduit, user_protocol); + if (err) + netdev_put(conduit, &dp->conduit_tracker); + return err; } if (link) @@ -1392,37 +1403,30 @@ static struct device *dev_find_class(struct device *parent, char *class) return device_find_child(parent, class, dev_is_class); } -static struct net_device *dsa_dev_to_net_device(struct device *dev) -{ - struct device *d; - - d = dev_find_class(dev, "net"); - if (d != NULL) { - struct net_device *nd; - - nd = to_net_dev(d); - dev_hold(nd); - put_device(d); - - return nd; - } - - return NULL; -} - static int dsa_port_parse(struct dsa_port *dp, const char *name, struct device *dev) { if (!strcmp(name, "cpu")) { struct net_device *conduit; + struct device *d; + int err; - conduit = dsa_dev_to_net_device(dev); - if (!conduit) + rtnl_lock(); + d = dev_find_class(dev, "net"); + if (!d) { + rtnl_unlock(); return -EPROBE_DEFER; + } - dev_put(conduit); + conduit = to_net_dev(d); + netdev_hold(conduit, &dp->conduit_tracker, GFP_KERNEL); + put_device(d); + rtnl_unlock(); - return dsa_port_parse_cpu(dp, conduit, NULL); + err = dsa_port_parse_cpu(dp, conduit, NULL); + if (err) + netdev_put(conduit, &dp->conduit_tracker); + return err; } if (!strcmp(name, "dsa")) @@ -1490,6 +1494,9 @@ static void dsa_switch_release_ports(struct dsa_switch *ds) struct dsa_vlan *v, *n; dsa_switch_for_each_port_safe(dp, next, ds) { + if (dsa_port_is_cpu(dp) && dp->conduit) + netdev_put(dp->conduit, &dp->conduit_tracker); + /* These are either entries that upper layers lost track of * (probably due to bugs), or installed through interfaces * where one does not necessarily have to remove them, like @@ -1634,8 +1641,10 @@ void dsa_switch_shutdown(struct dsa_switch *ds) /* Disconnect from further netdevice notifiers on the conduit, * since netdev_uses_dsa() will now return false. */ - dsa_switch_for_each_cpu_port(dp, ds) + dsa_switch_for_each_cpu_port(dp, ds) { dp->conduit->dsa_ptr = NULL; + netdev_put(dp->conduit, &dp->conduit_tracker); + } rtnl_unlock(); out: From ddbb72c338d9655cdb2f95d56223e909f51597bd Mon Sep 17 00:00:00 2001 From: Vladimir Oltean Date: Mon, 15 Dec 2025 17:02:36 +0200 Subject: [PATCH 1794/3902] net: dsa: fix missing put_device() in dsa_tree_find_first_conduit() [ Upstream commit a9f96dc59b4a50ffbf86158f315e115969172d48 ] of_find_net_device_by_node() searches net devices by their /sys/class/net/, entry. It is documented in its kernel-doc that: * If successful, returns a pointer to the net_device with the embedded * struct device refcount incremented by one, or NULL on failure. The * refcount must be dropped when done with the net_device. We are missing a put_device(&conduit->dev) which we could place at the end of dsa_tree_find_first_conduit(). But to explain why calling put_device() right away is safe is the same as to explain why the chosen solution is different. The code is very poorly split: dsa_tree_find_first_conduit() was first introduced in commit 95f510d0b792 ("net: dsa: allow the DSA master to be seen and changed through rtnetlink") but was first used several commits later, in commit acc43b7bf52a ("net: dsa: allow masters to join a LAG"). Assume there is a switch with 2 CPU ports and 2 conduits, eno2 and eno3. When we create a LAG (bonding or team device) and place eno2 and eno3 beneath it, we create a 3rd conduit (the LAG device itself), but this is slightly different than the first two. Namely, the cpu_dp->conduit pointer of the CPU ports does not change, and remains pointing towards the physical Ethernet controllers which are now LAG ports. Only 2 things change: - the LAG device has a dev->dsa_ptr which marks it as a DSA conduit - dsa_port_to_conduit(user port) finds the LAG and not the physical conduit, because of the dp->cpu_port_in_lag bit being set. When the LAG device is destroyed, dsa_tree_migrate_ports_from_lag_conduit() is called and this is where dsa_tree_find_first_conduit() kicks in. This is the logical mistake and the reason why introducing code in one patch and using it from another is bad practice. I didn't realize that I don't have to call of_find_net_device_by_node() again; the cpu_dp->conduit association was never undone, and is still available for direct (re)use. There's only one concern - maybe the conduit disappeared in the meantime, but the netdev_hold() call we made during dsa_port_parse_cpu() (see previous change) ensures that this was not the case. Therefore, fixing the code means reimplementing it in the simplest way. I am blaming the time of use, since this is what "git blame" would show if we were to monitor for the conduit's kobject's refcount remaining elevated instead of being freed. Tested on the NXP LS1028A, using the steps from Documentation/networking/dsa/configuration.rst section "Affinity of user ports to CPU ports", followed by (extra prints added by me): $ ip link del bond0 mscc_felix 0000:00:00.5 swp3: Link is Down bond0 (unregistering): (slave eno2): Releasing backup interface fsl_enetc 0000:00:00.2 eno2: Link is Down mscc_felix 0000:00:00.5 swp0: bond0 disappeared, migrating to eno2 mscc_felix 0000:00:00.5 swp1: bond0 disappeared, migrating to eno2 mscc_felix 0000:00:00.5 swp2: bond0 disappeared, migrating to eno2 mscc_felix 0000:00:00.5 swp3: bond0 disappeared, migrating to eno2 Fixes: acc43b7bf52a ("net: dsa: allow masters to join a LAG") Signed-off-by: Vladimir Oltean Link: https://patch.msgid.link/20251215150236.3931670-2-vladimir.oltean@nxp.com Signed-off-by: Paolo Abeni Signed-off-by: Sasha Levin --- net/dsa/dsa.c | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/net/dsa/dsa.c b/net/dsa/dsa.c index d466d023408720..ded9a291e6204e 100644 --- a/net/dsa/dsa.c +++ b/net/dsa/dsa.c @@ -366,16 +366,10 @@ static struct dsa_port *dsa_tree_find_first_cpu(struct dsa_switch_tree *dst) struct net_device *dsa_tree_find_first_conduit(struct dsa_switch_tree *dst) { - struct device_node *ethernet; - struct net_device *conduit; struct dsa_port *cpu_dp; cpu_dp = dsa_tree_find_first_cpu(dst); - ethernet = of_parse_phandle(cpu_dp->dn, "ethernet", 0); - conduit = of_find_net_device_by_node(ethernet); - of_node_put(ethernet); - - return conduit; + return cpu_dp->conduit; } /* Assign the default CPU port (the first one in the tree) to all ports of the From 6602403b2fa19d9df89d63323bce5ffe335ae0bf Mon Sep 17 00:00:00 2001 From: Raju Rangoju Date: Mon, 15 Dec 2025 20:47:28 +0530 Subject: [PATCH 1795/3902] amd-xgbe: reset retries and mode on RX adapt failures [ Upstream commit df60c332caf95d70f967aeace826e7e2f0847361 ] During the stress tests, early RX adaptation handshakes can fail, such as missing the RX_ADAPT ACK or not receiving a coefficient update before block lock is established. Continuing to retry RX adaptation in this state is often ineffective if the current mode selection is not viable. Resetting the RX adaptation retry counter when an RX_ADAPT request fails to receive ACK or a coefficient update prior to block lock, and clearing mode_set so the next bring-up performs a fresh mode selection rather than looping on a likely invalid configuration. Fixes: 4f3b20bfbb75 ("amd-xgbe: add support for rx-adaptation") Signed-off-by: Raju Rangoju Reviewed-by: Simon Horman Reviewed-by: Shyam Sundar S K Link: https://patch.msgid.link/20251215151728.311713-1-Raju.Rangoju@amd.com Signed-off-by: Paolo Abeni Signed-off-by: Sasha Levin --- drivers/net/ethernet/amd/xgbe/xgbe-phy-v2.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/drivers/net/ethernet/amd/xgbe/xgbe-phy-v2.c b/drivers/net/ethernet/amd/xgbe/xgbe-phy-v2.c index a56efc1bee3399..450a573960e7aa 100644 --- a/drivers/net/ethernet/amd/xgbe/xgbe-phy-v2.c +++ b/drivers/net/ethernet/amd/xgbe/xgbe-phy-v2.c @@ -1927,6 +1927,7 @@ static void xgbe_set_rx_adap_mode(struct xgbe_prv_data *pdata, { if (pdata->rx_adapt_retries++ >= MAX_RX_ADAPT_RETRIES) { pdata->rx_adapt_retries = 0; + pdata->mode_set = false; return; } @@ -1973,6 +1974,7 @@ static void xgbe_rx_adaptation(struct xgbe_prv_data *pdata) */ netif_dbg(pdata, link, pdata->netdev, "Block_lock done"); pdata->rx_adapt_done = true; + pdata->rx_adapt_retries = 0; pdata->mode_set = false; return; } From 018071d06c3b1dd09adf8f5d6dffbc7b275b10d3 Mon Sep 17 00:00:00 2001 From: Daniel Zahka Date: Tue, 16 Dec 2025 06:21:35 -0800 Subject: [PATCH 1796/3902] selftests: drv-net: psp: fix templated test names in psp_ip_ver_test_builder() [ Upstream commit d52668cac3f98f86aa1fb238dec1320c80fbefea ] test_case will only take on its formatted name after it is called by the test runner. Move the assignment to test_case.__name__ to when the test_case is constructed, not called. Fixes: 8f90dc6e417a ("selftests: drv-net: psp: add basic data transfer and key rotation tests") Signed-off-by: Daniel Zahka Link: https://patch.msgid.link/20251216-psp-test-fix-v1-1-3b5a6dde186f@gmail.com Signed-off-by: Paolo Abeni Signed-off-by: Sasha Levin --- tools/testing/selftests/drivers/net/psp.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tools/testing/selftests/drivers/net/psp.py b/tools/testing/selftests/drivers/net/psp.py index 4ae7a785ff10a3..827e04cc8423f1 100755 --- a/tools/testing/selftests/drivers/net/psp.py +++ b/tools/testing/selftests/drivers/net/psp.py @@ -560,8 +560,9 @@ def psp_ip_ver_test_builder(name, test_func, psp_ver, ipver): """Build test cases for each combo of PSP version and IP version""" def test_case(cfg): cfg.require_ipver(ipver) - test_case.__name__ = f"{name}_v{psp_ver}_ip{ipver}" test_func(cfg, psp_ver, ipver) + + test_case.__name__ = f"{name}_v{psp_ver}_ip{ipver}" return test_case From e4e5c21a9cd5ae00efcb33752ea44d8aef115f6e Mon Sep 17 00:00:00 2001 From: Daniel Zahka Date: Tue, 16 Dec 2025 06:21:36 -0800 Subject: [PATCH 1797/3902] selftests: drv-net: psp: fix test names in ipver_test_builder() [ Upstream commit f0e5126f5e55d4939784ff61b0b7e9f9636d787d ] test_case will only take on the formatted name after being called. This does not work with the way ksft_run() currently works. Assign the name after the test_case is created. Fixes: 81236c74dba6 ("selftests: drv-net: psp: add test for auto-adjusting TCP MSS") Signed-off-by: Daniel Zahka Link: https://patch.msgid.link/20251216-psp-test-fix-v1-2-3b5a6dde186f@gmail.com Signed-off-by: Paolo Abeni Signed-off-by: Sasha Levin --- tools/testing/selftests/drivers/net/psp.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tools/testing/selftests/drivers/net/psp.py b/tools/testing/selftests/drivers/net/psp.py index 827e04cc8423f1..473573e216e34e 100755 --- a/tools/testing/selftests/drivers/net/psp.py +++ b/tools/testing/selftests/drivers/net/psp.py @@ -570,8 +570,9 @@ def ipver_test_builder(name, test_func, ipver): """Build test cases for each IP version""" def test_case(cfg): cfg.require_ipver(ipver) - test_case.__name__ = f"{name}_ip{ipver}" test_func(cfg, ipver) + + test_case.__name__ = f"{name}_ip{ipver}" return test_case From 151403e903840c9cf06754097b6732c14f26c532 Mon Sep 17 00:00:00 2001 From: Deepakkumar Karn Date: Tue, 16 Dec 2025 20:43:05 +0530 Subject: [PATCH 1798/3902] net: usb: rtl8150: fix memory leak on usb_submit_urb() failure [ Upstream commit 12cab1191d9890097171156d06bfa8d31f1e39c8 ] In async_set_registers(), when usb_submit_urb() fails, the allocated async_req structure and URB are not freed, causing a memory leak. The completion callback async_set_reg_cb() is responsible for freeing these allocations, but it is only called after the URB is successfully submitted and completes (successfully or with error). If submission fails, the callback never runs and the memory is leaked. Fix this by freeing both the URB and the request structure in the error path when usb_submit_urb() fails. Reported-by: syzbot+8dd915c7cb0490fc8c52@syzkaller.appspotmail.com Closes: https://syzkaller.appspot.com/bug?extid=8dd915c7cb0490fc8c52 Fixes: 4d12997a9bb3 ("drivers: net: usb: rtl8150: concurrent URB bugfix") Signed-off-by: Deepakkumar Karn Link: https://patch.msgid.link/20251216151304.59865-2-dkarn@redhat.com Signed-off-by: Paolo Abeni Signed-off-by: Sasha Levin --- drivers/net/usb/rtl8150.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/drivers/net/usb/rtl8150.c b/drivers/net/usb/rtl8150.c index 278e6cb6f4d99a..e40b0669d9f4ba 100644 --- a/drivers/net/usb/rtl8150.c +++ b/drivers/net/usb/rtl8150.c @@ -211,6 +211,8 @@ static int async_set_registers(rtl8150_t *dev, u16 indx, u16 size, u16 reg) if (res == -ENODEV) netif_device_detach(dev->netdev); dev_err(&dev->udev->dev, "%s failed with %d\n", __func__, res); + kfree(req); + usb_free_urb(async_urb); } return res; } From 9b91553a30a632edde7497e50fd8aa512229efc2 Mon Sep 17 00:00:00 2001 From: "Alice C. Munduruca" Date: Tue, 16 Dec 2025 12:06:41 -0500 Subject: [PATCH 1799/3902] selftests: net: fix "buffer overflow detected" for tap.c [ Upstream commit 472c5dd6b95c02b3e5d7395acf542150e91165e7 ] When the selftest 'tap.c' is compiled with '-D_FORTIFY_SOURCE=3', the strcpy() in rtattr_add_strsz() is replaced with a checked version which causes the test to consistently fail when compiled with toolchains for which this option is enabled by default. TAP version 13 1..3 # Starting 3 tests from 1 test cases. # RUN tap.test_packet_valid_udp_gso ... *** buffer overflow detected ***: terminated # test_packet_valid_udp_gso: Test terminated by assertion # FAIL tap.test_packet_valid_udp_gso not ok 1 tap.test_packet_valid_udp_gso # RUN tap.test_packet_valid_udp_csum ... *** buffer overflow detected ***: terminated # test_packet_valid_udp_csum: Test terminated by assertion # FAIL tap.test_packet_valid_udp_csum not ok 2 tap.test_packet_valid_udp_csum # RUN tap.test_packet_crash_tap_invalid_eth_proto ... *** buffer overflow detected ***: terminated # test_packet_crash_tap_invalid_eth_proto: Test terminated by assertion # FAIL tap.test_packet_crash_tap_invalid_eth_proto not ok 3 tap.test_packet_crash_tap_invalid_eth_proto # FAILED: 0 / 3 tests passed. # Totals: pass:0 fail:3 xfail:0 xpass:0 skip:0 error:0 A buffer overflow is detected by the fortified glibc __strcpy_chk() since the __builtin_object_size() of `RTA_DATA(rta)` is incorrectly reported as 1, even though there is ample space in its bounding buffer `req`. Additionally, given that IFLA_IFNAME also expects a null-terminated string, callers of rtaddr_add_str{,sz}() could simply use the rtaddr_add_strsz() variant. (which has been renamed to remove the trailing `sz`) memset() has been used for this function since it is unchecked and thus circumvents the issue discussed in the previous paragraph. Fixes: 2e64fe4624d1 ("selftests: add few test cases for tap driver") Signed-off-by: Alice C. Munduruca Reviewed-by: Cengiz Can Reviewed-by: Willem de Bruijn Link: https://patch.msgid.link/20251216170641.250494-1-alice.munduruca@canonical.com Signed-off-by: Paolo Abeni Signed-off-by: Sasha Levin --- tools/testing/selftests/net/tap.c | 16 +++++----------- 1 file changed, 5 insertions(+), 11 deletions(-) diff --git a/tools/testing/selftests/net/tap.c b/tools/testing/selftests/net/tap.c index 247c3b3ac1c97c..51a209014f1cf8 100644 --- a/tools/testing/selftests/net/tap.c +++ b/tools/testing/selftests/net/tap.c @@ -56,18 +56,12 @@ static void rtattr_end(struct nlmsghdr *nh, struct rtattr *attr) static struct rtattr *rtattr_add_str(struct nlmsghdr *nh, unsigned short type, const char *s) { - struct rtattr *rta = rtattr_add(nh, type, strlen(s)); + unsigned int strsz = strlen(s) + 1; + struct rtattr *rta; - memcpy(RTA_DATA(rta), s, strlen(s)); - return rta; -} - -static struct rtattr *rtattr_add_strsz(struct nlmsghdr *nh, unsigned short type, - const char *s) -{ - struct rtattr *rta = rtattr_add(nh, type, strlen(s) + 1); + rta = rtattr_add(nh, type, strsz); - strcpy(RTA_DATA(rta), s); + memcpy(RTA_DATA(rta), s, strsz); return rta; } @@ -119,7 +113,7 @@ static int dev_create(const char *dev, const char *link_type, link_info = rtattr_begin(&req.nh, IFLA_LINKINFO); - rtattr_add_strsz(&req.nh, IFLA_INFO_KIND, link_type); + rtattr_add_str(&req.nh, IFLA_INFO_KIND, link_type); if (fill_info_data) { info_data = rtattr_begin(&req.nh, IFLA_INFO_DATA); From 66299520bb6e88caf4638104b65a8b4248ca64a8 Mon Sep 17 00:00:00 2001 From: Arnd Bergmann Date: Tue, 16 Dec 2025 22:35:42 +0100 Subject: [PATCH 1800/3902] net: wangxun: move PHYLINK dependency [ Upstream commit b94f11af9d9201426f4d6c8a753493fd58d6ac16 ] The LIBWX library code is what calls into phylink, so any user of it has to select CONFIG_PHYLINK at the moment, with NGBEVF missing this: x86_64-linux-ld: drivers/net/ethernet/wangxun/libwx/wx_ethtool.o: in function `wx_nway_reset': wx_ethtool.c:(.text+0x613): undefined reference to `phylink_ethtool_nway_reset' x86_64-linux-ld: drivers/net/ethernet/wangxun/libwx/wx_ethtool.o: in function `wx_get_link_ksettings': wx_ethtool.c:(.text+0x62b): undefined reference to `phylink_ethtool_ksettings_get' x86_64-linux-ld: drivers/net/ethernet/wangxun/libwx/wx_ethtool.o: in function `wx_set_link_ksettings': wx_ethtool.c:(.text+0x643): undefined reference to `phylink_ethtool_ksettings_set' x86_64-linux-ld: drivers/net/ethernet/wangxun/libwx/wx_ethtool.o: in function `wx_get_pauseparam': wx_ethtool.c:(.text+0x65b): undefined reference to `phylink_ethtool_get_pauseparam' x86_64-linux-ld: drivers/net/ethernet/wangxun/libwx/wx_ethtool.o: in function `wx_set_pauseparam': wx_ethtool.c:(.text+0x677): undefined reference to `phylink_ethtool_set_pauseparam' Add the 'select PHYLINK' line in the libwx option directly so this will always be enabled for all current and future wangxun drivers, and remove the now duplicate lines. Fixes: a0008a3658a3 ("net: wangxun: add ngbevf build") Signed-off-by: Arnd Bergmann Reviewed-by: Vadim Fedorenko Link: https://patch.msgid.link/20251216213547.115026-1-arnd@kernel.org Signed-off-by: Paolo Abeni Signed-off-by: Sasha Levin --- drivers/net/ethernet/wangxun/Kconfig | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/drivers/net/ethernet/wangxun/Kconfig b/drivers/net/ethernet/wangxun/Kconfig index d138dea7d208d7..ec278f99d2955c 100644 --- a/drivers/net/ethernet/wangxun/Kconfig +++ b/drivers/net/ethernet/wangxun/Kconfig @@ -21,6 +21,7 @@ config LIBWX depends on PTP_1588_CLOCK_OPTIONAL select PAGE_POOL select DIMLIB + select PHYLINK help Common library for Wangxun(R) Ethernet drivers. @@ -29,7 +30,6 @@ config NGBE depends on PCI depends on PTP_1588_CLOCK_OPTIONAL select LIBWX - select PHYLINK help This driver supports Wangxun(R) GbE PCI Express family of adapters. @@ -48,7 +48,6 @@ config TXGBE depends on PTP_1588_CLOCK_OPTIONAL select MARVELL_10G_PHY select REGMAP - select PHYLINK select HWMON if TXGBE=y select SFP select GPIOLIB @@ -71,7 +70,6 @@ config TXGBEVF depends on PCI_MSI depends on PTP_1588_CLOCK_OPTIONAL select LIBWX - select PHYLINK help This driver supports virtual functions for SP1000A, WX1820AL, WX5XXX, WX5XXXAL. From 277b256f579ae8966bc3eeb27bbfc6de7ad17010 Mon Sep 17 00:00:00 2001 From: Kaushlendra Kumar Date: Tue, 23 Dec 2025 14:10:41 +0530 Subject: [PATCH 1801/3902] platform/x86/intel/pmt: Fix kobject memory leak on init failure MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [ Upstream commit 00c22b1e84288bf0e17ab1e7e59d75237cf0d0dc ] When kobject_init_and_add() fails in pmt_features_discovery(), the function returns without calling kobject_put(). This violates the kobject API contract where kobject_put() must be called even on initialization failure to properly release allocated resources. Fixes: d9a078809356 ("platform/x86/intel/pmt: Add PMT Discovery driver") Signed-off-by: Kaushlendra Kumar Link: https://patch.msgid.link/20251223084041.3832933-1-kaushlendra.kumar@intel.com Reviewed-by: Ilpo Järvinen Signed-off-by: Ilpo Järvinen Signed-off-by: Sasha Levin --- drivers/platform/x86/intel/pmt/discovery.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/drivers/platform/x86/intel/pmt/discovery.c b/drivers/platform/x86/intel/pmt/discovery.c index 32713a194a550d..9c5b4d0e1fae68 100644 --- a/drivers/platform/x86/intel/pmt/discovery.c +++ b/drivers/platform/x86/intel/pmt/discovery.c @@ -503,8 +503,10 @@ static int pmt_features_discovery(struct pmt_features_priv *priv, ret = kobject_init_and_add(&feature->kobj, ktype, &priv->dev->kobj, "%s", pmt_feature_names[feature->id]); - if (ret) + if (ret) { + kobject_put(&feature->kobj); return ret; + } kobject_uevent(&feature->kobj, KOBJ_ADD); pmt_features_add_feat(feature); From b6018d5c1a8f09d5efe4d6961d7ee45fdf3a7ce3 Mon Sep 17 00:00:00 2001 From: Yeoreum Yun Date: Wed, 17 Dec 2025 08:51:15 +0000 Subject: [PATCH 1802/3902] smc91x: fix broken irq-context in PREEMPT_RT [ Upstream commit 6402078bd9d1ed46e79465e1faaa42e3458f8a33 ] When smc91x.c is built with PREEMPT_RT, the following splat occurs in FVP_RevC: [ 13.055000] smc91x LNRO0003:00 eth0: link up, 10Mbps, half-duplex, lpa 0x0000 [ 13.062137] BUG: workqueue leaked atomic, lock or RCU: kworker/2:1[106] [ 13.062137] preempt=0x00000000 lock=0->0 RCU=0->1 workfn=mld_ifc_work [ 13.062266] C ** replaying previous printk message ** [ 13.062266] CPU: 2 UID: 0 PID: 106 Comm: kworker/2:1 Not tainted 6.18.0-dirty #179 PREEMPT_{RT,(full)} [ 13.062353] Hardware name: , BIOS [ 13.062382] Workqueue: mld mld_ifc_work [ 13.062469] Call trace: [ 13.062494] show_stack+0x24/0x40 (C) [ 13.062602] __dump_stack+0x28/0x48 [ 13.062710] dump_stack_lvl+0x7c/0xb0 [ 13.062818] dump_stack+0x18/0x34 [ 13.062926] process_scheduled_works+0x294/0x450 [ 13.063043] worker_thread+0x260/0x3d8 [ 13.063124] kthread+0x1c4/0x228 [ 13.063235] ret_from_fork+0x10/0x20 This happens because smc_special_trylock() disables IRQs even on PREEMPT_RT, but smc_special_unlock() does not restore IRQs on PREEMPT_RT. The reason is that smc_special_unlock() calls spin_unlock_irqrestore(), and rcu_read_unlock_bh() in __dev_queue_xmit() cannot invoke rcu_read_unlock() through __local_bh_enable_ip() when current->softirq_disable_cnt becomes zero. To address this issue, replace smc_special_trylock() with spin_trylock_irqsave(). Fixes: 342a93247e08 ("locking/spinlock: Provide RT variant header: ") Signed-off-by: Yeoreum Yun Reviewed-by: Simon Horman Link: https://patch.msgid.link/20251217085115.1730036-1-yeoreum.yun@arm.com Signed-off-by: Paolo Abeni Signed-off-by: Sasha Levin --- drivers/net/ethernet/smsc/smc91x.c | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/drivers/net/ethernet/smsc/smc91x.c b/drivers/net/ethernet/smsc/smc91x.c index 9d1a83a5fa7e5d..d16c178d103440 100644 --- a/drivers/net/ethernet/smsc/smc91x.c +++ b/drivers/net/ethernet/smsc/smc91x.c @@ -516,15 +516,7 @@ static inline void smc_rcv(struct net_device *dev) * any other concurrent access and C would always interrupt B. But life * isn't that easy in a SMP world... */ -#define smc_special_trylock(lock, flags) \ -({ \ - int __ret; \ - local_irq_save(flags); \ - __ret = spin_trylock(lock); \ - if (!__ret) \ - local_irq_restore(flags); \ - __ret; \ -}) +#define smc_special_trylock(lock, flags) spin_trylock_irqsave(lock, flags) #define smc_special_lock(lock, flags) spin_lock_irqsave(lock, flags) #define smc_special_unlock(lock, flags) spin_unlock_irqrestore(lock, flags) #else From 3411103f6b3e0ce1725a5fe161f290dd4dd7c5eb Mon Sep 17 00:00:00 2001 From: Andrew Morton Date: Thu, 27 Nov 2025 10:39:24 -0800 Subject: [PATCH 1803/3902] genalloc.h: fix htmldocs warning [ Upstream commit 5393802c94e0ab1295c04c94c57bcb00222d4674 ] WARNING: include/linux/genalloc.h:52 function parameter 'start_addr' not described in 'genpool_algo_t' Fixes: 52fbf1134d47 ("lib/genalloc.c: fix allocation of aligned buffer from non-aligned chunk") Reported-by: Stephen Rothwell Closes: https://lkml.kernel.org/r/20251127130624.563597e3@canb.auug.org.au Acked-by: Randy Dunlap Tested-by: Randy Dunlap Cc: Alexey Skidanov Signed-off-by: Andrew Morton Signed-off-by: Sasha Levin --- include/linux/genalloc.h | 1 + 1 file changed, 1 insertion(+) diff --git a/include/linux/genalloc.h b/include/linux/genalloc.h index 0bd581003cd5df..60de63e46b33dd 100644 --- a/include/linux/genalloc.h +++ b/include/linux/genalloc.h @@ -44,6 +44,7 @@ struct gen_pool; * @nr: The number of zeroed bits we're looking for * @data: optional additional data used by the callback * @pool: the pool being allocated from + * @start_addr: start address of memory chunk */ typedef unsigned long (*genpool_algo_t)(unsigned long *map, unsigned long size, From f7d95f9ee070486db6f919023c0796c33344805c Mon Sep 17 00:00:00 2001 From: Thomas Fourier Date: Tue, 16 Dec 2025 17:54:18 +0100 Subject: [PATCH 1804/3902] firewire: nosy: Fix dma_free_coherent() size [ Upstream commit c48c0fd0e19684b6ecdb4108a429e3a4e73f5e21 ] It looks like the buffer allocated and mapped in add_card() is done with size RCV_BUFFER_SIZE which is 16 KB and 4KB. Fixes: 286468210d83 ("firewire: new driver: nosy - IEEE 1394 traffic sniffer") Co-developed-by: Thomas Fourier Signed-off-by: Thomas Fourier Co-developed-by: Christophe JAILLET Signed-off-by: Christophe JAILLET Link: https://lore.kernel.org/r/20251216165420.38355-2-fourier.thomas@gmail.com Signed-off-by: Takashi Sakamoto Signed-off-by: Sasha Levin --- drivers/firewire/nosy.c | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/drivers/firewire/nosy.c b/drivers/firewire/nosy.c index ea31ac7ac1ca93..e59053738a432a 100644 --- a/drivers/firewire/nosy.c +++ b/drivers/firewire/nosy.c @@ -36,6 +36,8 @@ static char driver_name[] = KBUILD_MODNAME; +#define RCV_BUFFER_SIZE (16 * 1024) + /* this is the physical layout of a PCL, its size is 128 bytes */ struct pcl { __le32 next; @@ -517,16 +519,14 @@ remove_card(struct pci_dev *dev) lynx->rcv_start_pcl, lynx->rcv_start_pcl_bus); dma_free_coherent(&lynx->pci_device->dev, sizeof(struct pcl), lynx->rcv_pcl, lynx->rcv_pcl_bus); - dma_free_coherent(&lynx->pci_device->dev, PAGE_SIZE, lynx->rcv_buffer, - lynx->rcv_buffer_bus); + dma_free_coherent(&lynx->pci_device->dev, RCV_BUFFER_SIZE, + lynx->rcv_buffer, lynx->rcv_buffer_bus); iounmap(lynx->registers); pci_disable_device(dev); lynx_put(lynx); } -#define RCV_BUFFER_SIZE (16 * 1024) - static int add_card(struct pci_dev *dev, const struct pci_device_id *unused) { @@ -680,7 +680,7 @@ add_card(struct pci_dev *dev, const struct pci_device_id *unused) dma_free_coherent(&lynx->pci_device->dev, sizeof(struct pcl), lynx->rcv_pcl, lynx->rcv_pcl_bus); if (lynx->rcv_buffer) - dma_free_coherent(&lynx->pci_device->dev, PAGE_SIZE, + dma_free_coherent(&lynx->pci_device->dev, RCV_BUFFER_SIZE, lynx->rcv_buffer, lynx->rcv_buffer_bus); iounmap(lynx->registers); From 3b3ddded9c8624d2dbfc77a5d1fbd8211825142b Mon Sep 17 00:00:00 2001 From: Rajashekar Hudumula Date: Wed, 17 Dec 2025 02:47:48 -0800 Subject: [PATCH 1805/3902] bng_en: update module description [ Upstream commit d5dc28305143f126dc3d8da21e1ad75865b194e2 ] The Broadcom BCM57708/800G NIC family is branded as ThorUltra. Update the driver description accordingly. Fixes: 74715c4ab0fa0 ("bng_en: Add PCI interface") Signed-off-by: Rajashekar Hudumula Reviewed-by: Vikas Gupta Reviewed-by: Bhargava Chenna Marreddy Link: https://patch.msgid.link/20251217104748.3004706-1-rajashekar.hudumula@broadcom.com Signed-off-by: Paolo Abeni Signed-off-by: Sasha Levin --- drivers/net/ethernet/broadcom/Kconfig | 8 ++++---- drivers/net/ethernet/broadcom/bnge/bnge.h | 2 +- drivers/net/ethernet/broadcom/bnge/bnge_core.c | 2 +- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/drivers/net/ethernet/broadcom/Kconfig b/drivers/net/ethernet/broadcom/Kconfig index 9fdef874f5ca33..fe15d684990fc7 100644 --- a/drivers/net/ethernet/broadcom/Kconfig +++ b/drivers/net/ethernet/broadcom/Kconfig @@ -254,14 +254,14 @@ config BNXT_HWMON devices, via the hwmon sysfs interface. config BNGE - tristate "Broadcom Ethernet device support" + tristate "Broadcom ThorUltra Ethernet device support" depends on PCI select NET_DEVLINK select PAGE_POOL help - This driver supports Broadcom 50/100/200/400/800 gigabit Ethernet cards. - The module will be called bng_en. To compile this driver as a module, - choose M here. + This driver supports Broadcom ThorUltra 50/100/200/400/800 gigabit + Ethernet cards. The module will be called bng_en. To compile this + driver as a module, choose M here. config BCMASP tristate "Broadcom ASP 2.0 Ethernet support" diff --git a/drivers/net/ethernet/broadcom/bnge/bnge.h b/drivers/net/ethernet/broadcom/bnge/bnge.h index 7aed5f81cd51e5..0c154995d9ab5f 100644 --- a/drivers/net/ethernet/broadcom/bnge/bnge.h +++ b/drivers/net/ethernet/broadcom/bnge/bnge.h @@ -5,7 +5,7 @@ #define _BNGE_H_ #define DRV_NAME "bng_en" -#define DRV_SUMMARY "Broadcom 800G Ethernet Linux Driver" +#define DRV_SUMMARY "Broadcom ThorUltra NIC Ethernet Driver" #include #include diff --git a/drivers/net/ethernet/broadcom/bnge/bnge_core.c b/drivers/net/ethernet/broadcom/bnge/bnge_core.c index 2c72dd34d50d00..312a9db4d75d1d 100644 --- a/drivers/net/ethernet/broadcom/bnge/bnge_core.c +++ b/drivers/net/ethernet/broadcom/bnge/bnge_core.c @@ -19,7 +19,7 @@ char bnge_driver_name[] = DRV_NAME; static const struct { char *name; } board_info[] = { - [BCM57708] = { "Broadcom BCM57708 50Gb/100Gb/200Gb/400Gb/800Gb Ethernet" }, + [BCM57708] = { "Broadcom BCM57708 ThorUltra 50Gb/100Gb/200Gb/400Gb/800Gb Ethernet" }, }; static const struct pci_device_id bnge_pci_tbl[] = { From 6ddf3ae19574095d46c5a65f421f104d5aaac7c6 Mon Sep 17 00:00:00 2001 From: Jonas Gorski Date: Wed, 17 Dec 2025 21:57:56 +0100 Subject: [PATCH 1806/3902] net: dsa: b53: skip multicast entries for fdb_dump() [ Upstream commit d42bce414d1c5c0b536758466a1f63ac358e613c ] port_fdb_dump() is supposed to only add fdb entries, but we iterate over the full ARL table, which also includes multicast entries. So check if the entry is a multicast entry before passing it on to the callback(). Additionally, the port of those entries is a bitmask, not a port number, so any included entries would have even be for the wrong port. Fixes: 1da6df85c6fb ("net: dsa: b53: Implement ARL add/del/dump operations") Signed-off-by: Jonas Gorski Reviewed-by: Florian Fainelli Link: https://patch.msgid.link/20251217205756.172123-1-jonas.gorski@gmail.com Signed-off-by: Paolo Abeni Signed-off-by: Sasha Levin --- drivers/net/dsa/b53/b53_common.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/drivers/net/dsa/b53/b53_common.c b/drivers/net/dsa/b53/b53_common.c index 62cafced758e77..7d6ec2eb7c75ea 100644 --- a/drivers/net/dsa/b53/b53_common.c +++ b/drivers/net/dsa/b53/b53_common.c @@ -2155,6 +2155,9 @@ static int b53_fdb_copy(int port, const struct b53_arl_entry *ent, if (!ent->is_valid) return 0; + if (is_multicast_ether_addr(ent->mac)) + return 0; + if (port != ent->port) return 0; From 0c1df928f0b87cc672ab46bdd51412d5d776c327 Mon Sep 17 00:00:00 2001 From: Thomas De Schampheleire Date: Wed, 26 Nov 2025 11:00:16 +0100 Subject: [PATCH 1807/3902] kbuild: fix compilation of dtb specified on command-line without make rule [ Upstream commit b08fc4d0ec2466558f6d5511434efdfabbddf2a6 ] Since commit e7e2941300d2 ("kbuild: split device tree build rules into scripts/Makefile.dtbs"), it is no longer possible to compile a device tree blob that is not specified in a make rule like: dtb-$(CONFIG_FOO) += foo.dtb Before the mentioned commit, one could copy a dts file to e.g. arch/arm64/boot/dts/ (or a new subdirectory) and then convert it to a dtb file using: make ARCH=arm64 foo.dtb In this scenario, both 'dtb-y' and 'dtb-' are empty, and the inclusion of scripts/Makefile.dtbs relies on 'targets' to contain the MAKECMDGOALS. The value of 'targets', however, is only final later in the code. Move the conditional include of scripts/Makefile.dtbs down to where the value of 'targets' is final. Since Makefile.dtbs updates 'always-y' which is used as a prerequisite in the build rule, the build rule also needs to move down. Fixes: e7e2941300d2 ("kbuild: split device tree build rules into scripts/Makefile.dtbs") Signed-off-by: Thomas De Schampheleire Reviewed-by: Nathan Chancellor Tested-by: Nathan Chancellor Acked-by: Rob Herring (Arm) Link: https://patch.msgid.link/20251126100017.1162330-1-thomas.de_schampheleire@nokia.com Signed-off-by: Nicolas Schier Signed-off-by: Sasha Levin --- scripts/Makefile.build | 26 ++++++++++++++------------ 1 file changed, 14 insertions(+), 12 deletions(-) diff --git a/scripts/Makefile.build b/scripts/Makefile.build index 52c08c4eb0b9af..5037f4715d7491 100644 --- a/scripts/Makefile.build +++ b/scripts/Makefile.build @@ -527,18 +527,6 @@ ifneq ($(userprogs),) include $(srctree)/scripts/Makefile.userprogs endif -ifneq ($(need-dtbslist)$(dtb-y)$(dtb-)$(filter %.dtb %.dtb.o %.dtbo.o,$(targets)),) -include $(srctree)/scripts/Makefile.dtbs -endif - -# Build -# --------------------------------------------------------------------------- - -$(obj)/: $(if $(KBUILD_BUILTIN), $(targets-for-builtin)) \ - $(if $(KBUILD_MODULES), $(targets-for-modules)) \ - $(subdir-ym) $(always-y) - @: - # Single targets # --------------------------------------------------------------------------- @@ -568,6 +556,20 @@ FORCE: targets += $(filter-out $(single-subdir-goals), $(MAKECMDGOALS)) targets := $(filter-out $(PHONY), $(targets)) +# Now that targets is fully known, include dtb rules if needed +ifneq ($(need-dtbslist)$(dtb-y)$(dtb-)$(filter %.dtb %.dtb.o %.dtbo.o,$(targets)),) +include $(srctree)/scripts/Makefile.dtbs +endif + +# Build +# Needs to be after the include of Makefile.dtbs, which updates always-y +# --------------------------------------------------------------------------- + +$(obj)/: $(if $(KBUILD_BUILTIN), $(targets-for-builtin)) \ + $(if $(KBUILD_MODULES), $(targets-for-modules)) \ + $(subdir-ym) $(always-y) + @: + # Read all saved command lines and dependencies for the $(targets) we # may be building above, using $(if_changed{,_dep}). As an # optimization, we don't need to read them if the target does not From 937c7172d1e36d23a1e7c36220f636c2d9483f58 Mon Sep 17 00:00:00 2001 From: Jose Javier Rodriguez Barbarin Date: Tue, 2 Dec 2025 09:42:00 +0100 Subject: [PATCH 1808/3902] mcb: Add missing modpost build support [ Upstream commit 1f4ea4838b13c3b2278436a8dcb148e3c23f4b64 ] mcb bus is not prepared to autoload client drivers with the data defined on the drivers' MODULE_DEVICE_TABLE. modpost cannot access to mcb_table_id inside MODULE_DEVICE_TABLE so the data declared inside is ignored. Add modpost build support for accessing to the mcb_table_id coded on device drivers' MODULE_DEVICE_TABLE. Fixes: 3764e82e5150 ("drivers: Introduce MEN Chameleon Bus") Reviewed-by: Jorge Sanjuan Garcia Signed-off-by: Jose Javier Rodriguez Barbarin Acked-by: Nathan Chancellor Reviewed-by: Andy Shevchenko Link: https://patch.msgid.link/20251202084200.10410-1-dev-josejavier.rodriguez@duagon.com Signed-off-by: Nicolas Schier Signed-off-by: Sasha Levin --- scripts/mod/devicetable-offsets.c | 3 +++ scripts/mod/file2alias.c | 9 +++++++++ 2 files changed, 12 insertions(+) diff --git a/scripts/mod/devicetable-offsets.c b/scripts/mod/devicetable-offsets.c index d3d00e85edf735..0470ba7c796d4a 100644 --- a/scripts/mod/devicetable-offsets.c +++ b/scripts/mod/devicetable-offsets.c @@ -198,6 +198,9 @@ int main(void) DEVID(cpu_feature); DEVID_FIELD(cpu_feature, feature); + DEVID(mcb_device_id); + DEVID_FIELD(mcb_device_id, device); + DEVID(mei_cl_device_id); DEVID_FIELD(mei_cl_device_id, name); DEVID_FIELD(mei_cl_device_id, uuid); diff --git a/scripts/mod/file2alias.c b/scripts/mod/file2alias.c index b3333560b95ee9..4e99393a35f152 100644 --- a/scripts/mod/file2alias.c +++ b/scripts/mod/file2alias.c @@ -1110,6 +1110,14 @@ static void do_cpu_entry(struct module *mod, void *symval) module_alias_printf(mod, false, "cpu:type:*:feature:*%04X*", feature); } +/* Looks like: mcb:16zN */ +static void do_mcb_entry(struct module *mod, void *symval) +{ + DEF_FIELD(symval, mcb_device_id, device); + + module_alias_printf(mod, false, "mcb:16z%03d", device); +} + /* Looks like: mei:S:uuid:N:* */ static void do_mei_entry(struct module *mod, void *symval) { @@ -1444,6 +1452,7 @@ static const struct devtable devtable[] = { {"mipscdmm", SIZE_mips_cdmm_device_id, do_mips_cdmm_entry}, {"x86cpu", SIZE_x86_cpu_id, do_x86cpu_entry}, {"cpu", SIZE_cpu_feature, do_cpu_entry}, + {"mcb", SIZE_mcb_device_id, do_mcb_entry}, {"mei", SIZE_mei_cl_device_id, do_mei_entry}, {"rapidio", SIZE_rio_device_id, do_rio_entry}, {"ulpi", SIZE_ulpi_device_id, do_ulpi_entry}, From 93a880f73b8867d2bb30ae91539a3b0e915a23ac Mon Sep 17 00:00:00 2001 From: Rosen Penev Date: Wed, 17 Dec 2025 13:01:53 -0800 Subject: [PATCH 1809/3902] net: mdio: rtl9300: use scoped for loops [ Upstream commit a4f800c4487dc5d6fcc28da89c7cc3c187ccc731 ] Currently in the return path, fwnode_handle_put calls are missing. Just use _scoped to avoid the issue. Fixes: 24e31e474769 ("net: mdio: Add RTL9300 MDIO driver") Signed-off-by: Rosen Penev Link: https://patch.msgid.link/20251217210153.14641-1-rosenp@gmail.com Signed-off-by: Paolo Abeni Signed-off-by: Sasha Levin --- drivers/net/mdio/mdio-realtek-rtl9300.c | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/drivers/net/mdio/mdio-realtek-rtl9300.c b/drivers/net/mdio/mdio-realtek-rtl9300.c index 33694c3ff9a719..405a07075dd115 100644 --- a/drivers/net/mdio/mdio-realtek-rtl9300.c +++ b/drivers/net/mdio/mdio-realtek-rtl9300.c @@ -354,7 +354,6 @@ static int rtl9300_mdiobus_probe_one(struct device *dev, struct rtl9300_mdio_pri struct fwnode_handle *node) { struct rtl9300_mdio_chan *chan; - struct fwnode_handle *child; struct mii_bus *bus; u32 mdio_bus; int err; @@ -371,7 +370,7 @@ static int rtl9300_mdiobus_probe_one(struct device *dev, struct rtl9300_mdio_pri * compatible = "ethernet-phy-ieee802.3-c45". This does mean we can't * support both c45 and c22 on the same MDIO bus. */ - fwnode_for_each_child_node(node, child) + fwnode_for_each_child_node_scoped(node, child) if (fwnode_device_is_compatible(child, "ethernet-phy-ieee802.3-c45")) priv->smi_bus_is_c45[mdio_bus] = true; @@ -409,7 +408,6 @@ static int rtl9300_mdiobus_map_ports(struct device *dev) { struct rtl9300_mdio_priv *priv = dev_get_drvdata(dev); struct device *parent = dev->parent; - struct fwnode_handle *port; int err; struct fwnode_handle *ports __free(fwnode_handle) = @@ -418,7 +416,7 @@ static int rtl9300_mdiobus_map_ports(struct device *dev) return dev_err_probe(dev, -EINVAL, "%pfwP missing ethernet-ports\n", dev_fwnode(parent)); - fwnode_for_each_child_node(ports, port) { + fwnode_for_each_child_node_scoped(ports, port) { struct device_node *mdio_dn; u32 addr; u32 bus; From bf8a0f3b787ca7c5889bfca12c60c483041fbee3 Mon Sep 17 00:00:00 2001 From: Deepanshu Kartikey Date: Thu, 18 Dec 2025 06:41:56 +0530 Subject: [PATCH 1810/3902] net: usb: asix: validate PHY address before use [ Upstream commit a1e077a3f76eea0dc671ed6792e7d543946227e8 ] The ASIX driver reads the PHY address from the USB device via asix_read_phy_addr(). A malicious or faulty device can return an invalid address (>= PHY_MAX_ADDR), which causes a warning in mdiobus_get_phy(): addr 207 out of range WARNING: drivers/net/phy/mdio_bus.c:76 Validate the PHY address in asix_read_phy_addr() and remove the now-redundant check in ax88172a.c. Reported-by: syzbot+3d43c9066a5b54902232@syzkaller.appspotmail.com Closes: https://syzkaller.appspot.com/bug?extid=3d43c9066a5b54902232 Tested-by: syzbot+3d43c9066a5b54902232@syzkaller.appspotmail.com Fixes: 7e88b11a862a ("net: usb: asix: refactor asix_read_phy_addr() and handle errors on return") Link: https://lore.kernel.org/all/20251217085057.270704-1-kartikey406@gmail.com/T/ [v1] Signed-off-by: Deepanshu Kartikey Reviewed-by: Andrew Lunn Link: https://patch.msgid.link/20251218011156.276824-1-kartikey406@gmail.com Signed-off-by: Paolo Abeni Signed-off-by: Sasha Levin --- drivers/net/usb/asix_common.c | 5 +++++ drivers/net/usb/ax88172a.c | 6 +----- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/drivers/net/usb/asix_common.c b/drivers/net/usb/asix_common.c index 7fd763917ae2cf..6ab3486072cb0f 100644 --- a/drivers/net/usb/asix_common.c +++ b/drivers/net/usb/asix_common.c @@ -335,6 +335,11 @@ int asix_read_phy_addr(struct usbnet *dev, bool internal) offset = (internal ? 1 : 0); ret = buf[offset]; + if (ret >= PHY_MAX_ADDR) { + netdev_err(dev->net, "invalid PHY address: %d\n", ret); + return -ENODEV; + } + netdev_dbg(dev->net, "%s PHY address 0x%x\n", internal ? "internal" : "external", ret); diff --git a/drivers/net/usb/ax88172a.c b/drivers/net/usb/ax88172a.c index f613e4bc68c851..758a423a459b80 100644 --- a/drivers/net/usb/ax88172a.c +++ b/drivers/net/usb/ax88172a.c @@ -210,11 +210,7 @@ static int ax88172a_bind(struct usbnet *dev, struct usb_interface *intf) ret = asix_read_phy_addr(dev, priv->use_embdphy); if (ret < 0) goto free; - if (ret >= PHY_MAX_ADDR) { - netdev_err(dev->net, "Invalid PHY address %#x\n", ret); - ret = -ENODEV; - goto free; - } + priv->phy_addr = ret; ax88172a_reset_phy(dev, priv->use_embdphy); From 6081ef09c54e0a943dfed11bb8ea149865bc3177 Mon Sep 17 00:00:00 2001 From: Bagas Sanjaya Date: Thu, 18 Dec 2025 11:29:37 +0700 Subject: [PATCH 1811/3902] net: bridge: Describe @tunnel_hash member in net_bridge_vlan_group struct [ Upstream commit f79f9b7ace1713e4b83888c385f5f55519dfb687 ] Sphinx reports kernel-doc warning: WARNING: ./net/bridge/br_private.h:267 struct member 'tunnel_hash' not described in 'net_bridge_vlan_group' Fix it by describing @tunnel_hash member. Fixes: efa5356b0d9753 ("bridge: per vlan dst_metadata netlink support") Signed-off-by: Bagas Sanjaya Acked-by: Nikolay Aleksandrov Reviewed-by: Ido Schimmel Link: https://patch.msgid.link/20251218042936.24175-2-bagasdotme@gmail.com Signed-off-by: Paolo Abeni Signed-off-by: Sasha Levin --- net/bridge/br_private.h | 1 + 1 file changed, 1 insertion(+) diff --git a/net/bridge/br_private.h b/net/bridge/br_private.h index 7280c4e9305f36..b9b2981c484149 100644 --- a/net/bridge/br_private.h +++ b/net/bridge/br_private.h @@ -247,6 +247,7 @@ struct net_bridge_vlan { * struct net_bridge_vlan_group * * @vlan_hash: VLAN entry rhashtable + * @tunnel_hash: Hash table to map from tunnel key ID (e.g. VXLAN VNI) to VLAN * @vlan_list: sorted VLAN entry list * @num_vlans: number of total VLAN entries * @pvid: PVID VLAN id From 4904161220d4869298ca2754d2c82ff6677825ed Mon Sep 17 00:00:00 2001 From: Kohei Enju Date: Fri, 26 Dec 2025 17:46:49 +0900 Subject: [PATCH 1812/3902] tools/sched_ext: fix scx_show_state.py for scx_root change [ Upstream commit f92ff79ba2640fc482bf2bfb5b42e33957f90caf ] Commit 48e126777386 ("sched_ext: Introduce scx_sched") introduced scx_root and removed scx_ops, causing scx_show_state.py to fail when searching for the 'scx_ops' object. [1] Fix by using 'scx_root' instead, with NULL pointer handling. [1] # drgn -s vmlinux ./tools/sched_ext/scx_show_state.py Traceback (most recent call last): File "/root/.venv/bin/drgn", line 8, in sys.exit(_main()) ~~~~~^^ File "/root/.venv/lib64/python3.14/site-packages/drgn/cli.py", line 625, in _main runpy.run_path( ~~~~~~~~~~~~~~^ script_path, init_globals={"prog": prog}, run_name="__main__" ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ) ^ File "", line 287, in run_path File "", line 98, in _run_module_code File "", line 88, in _run_code File "./tools/sched_ext/scx_show_state.py", line 30, in ops = prog['scx_ops'] ~~~~^^^^^^^^^^^ _drgn.ObjectNotFoundError: could not find 'scx_ops' Fixes: 48e126777386 ("sched_ext: Introduce scx_sched") Signed-off-by: Kohei Enju Reviewed-by: Emil Tsalapatis Signed-off-by: Tejun Heo Signed-off-by: Sasha Levin --- tools/sched_ext/scx_show_state.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/tools/sched_ext/scx_show_state.py b/tools/sched_ext/scx_show_state.py index 7cdcc6729ea4ee..aec4a4498140fe 100644 --- a/tools/sched_ext/scx_show_state.py +++ b/tools/sched_ext/scx_show_state.py @@ -27,10 +27,13 @@ def read_static_key(name): def state_str(state): return prog['scx_enable_state_str'][state].string_().decode() -ops = prog['scx_ops'] +root = prog['scx_root'] enable_state = read_atomic("scx_enable_state_var") -print(f'ops : {ops.name.string_().decode()}') +if root: + print(f'ops : {root.ops.name.string_().decode()}') +else: + print('ops : ') print(f'enabled : {read_static_key("__scx_enabled")}') print(f'switching_all : {read_int("scx_switching_all")}') print(f'switched_all : {read_static_key("__scx_switched_all")}') From fe9339b33e2b28d88946bacae7baaf24d467783a Mon Sep 17 00:00:00 2001 From: Zilin Guan Date: Thu, 25 Dec 2025 14:31:50 +0000 Subject: [PATCH 1813/3902] vfio/pds: Fix memory leak in pds_vfio_dirty_enable() [ Upstream commit 665077d78dc7941ce6a330c02023a2b469cc8cc7 ] pds_vfio_dirty_enable() allocates memory for region_info. If interval_tree_iter_first() returns NULL, the function returns -EINVAL immediately without freeing the allocated memory, causing a memory leak. Fix this by jumping to the out_free_region_info label to ensure region_info is freed. Fixes: 2e7c6feb4ef52 ("vfio/pds: Add multi-region support") Signed-off-by: Zilin Guan Link: https://lore.kernel.org/r/20251225143150.1117366-1-zilin@seu.edu.cn Signed-off-by: Alex Williamson Signed-off-by: Sasha Levin --- drivers/vfio/pci/pds/dirty.c | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/drivers/vfio/pci/pds/dirty.c b/drivers/vfio/pci/pds/dirty.c index 481992142f7901..4915a7c1c4916d 100644 --- a/drivers/vfio/pci/pds/dirty.c +++ b/drivers/vfio/pci/pds/dirty.c @@ -292,8 +292,11 @@ static int pds_vfio_dirty_enable(struct pds_vfio_pci_device *pds_vfio, len = num_ranges * sizeof(*region_info); node = interval_tree_iter_first(ranges, 0, ULONG_MAX); - if (!node) - return -EINVAL; + if (!node) { + err = -EINVAL; + goto out_free_region_info; + } + for (int i = 0; i < num_ranges; i++) { struct pds_lm_dirty_region_info *ri = ®ion_info[i]; u64 region_size = node->last - node->start + 1; From 79cab730dbaaac03b946c7f5681bd08c986e2abd Mon Sep 17 00:00:00 2001 From: Junrui Luo Date: Fri, 26 Dec 2025 19:42:05 +0800 Subject: [PATCH 1814/3902] platform/x86: hp-bioscfg: Fix out-of-bounds array access in ACPI package parsing MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [ Upstream commit e44c42c830b7ab36e3a3a86321c619f24def5206 ] The hp_populate_*_elements_from_package() functions in the hp-bioscfg driver contain out-of-bounds array access vulnerabilities. These functions parse ACPI packages into internal data structures using a for loop with index variable 'elem' that iterates through enum_obj/integer_obj/order_obj/password_obj/string_obj arrays. When processing multi-element fields like PREREQUISITES and ENUM_POSSIBLE_VALUES, these functions read multiple consecutive array elements using expressions like 'enum_obj[elem + reqs]' and 'enum_obj[elem + pos_values]' within nested loops. The bug is that the bounds check only validated elem, but did not consider the additional offset when accessing elem + reqs or elem + pos_values. The fix changes the bounds check to validate the actual accessed index. Reported-by: Yuhao Jiang Reported-by: Junrui Luo Fixes: e6c7b3e15559 ("platform/x86: hp-bioscfg: string-attributes") Signed-off-by: Junrui Luo Link: https://patch.msgid.link/SYBPR01MB788173D7DD4EA2CB6383683DAFB0A@SYBPR01MB7881.ausprd01.prod.outlook.com Reviewed-by: Ilpo Järvinen Signed-off-by: Ilpo Järvinen Signed-off-by: Sasha Levin --- drivers/platform/x86/hp/hp-bioscfg/enum-attributes.c | 4 ++-- drivers/platform/x86/hp/hp-bioscfg/int-attributes.c | 2 +- drivers/platform/x86/hp/hp-bioscfg/order-list-attributes.c | 5 +++++ drivers/platform/x86/hp/hp-bioscfg/passwdobj-attributes.c | 5 +++++ drivers/platform/x86/hp/hp-bioscfg/string-attributes.c | 2 +- 5 files changed, 14 insertions(+), 4 deletions(-) diff --git a/drivers/platform/x86/hp/hp-bioscfg/enum-attributes.c b/drivers/platform/x86/hp/hp-bioscfg/enum-attributes.c index c50ad58805038e..f346aad8e9d895 100644 --- a/drivers/platform/x86/hp/hp-bioscfg/enum-attributes.c +++ b/drivers/platform/x86/hp/hp-bioscfg/enum-attributes.c @@ -207,7 +207,7 @@ static int hp_populate_enumeration_elements_from_package(union acpi_object *enum case PREREQUISITES: size = min_t(u32, enum_data->common.prerequisites_size, MAX_PREREQUISITES_SIZE); for (reqs = 0; reqs < size; reqs++) { - if (elem >= enum_obj_count) { + if (elem + reqs >= enum_obj_count) { pr_err("Error enum-objects package is too small\n"); return -EINVAL; } @@ -255,7 +255,7 @@ static int hp_populate_enumeration_elements_from_package(union acpi_object *enum for (pos_values = 0; pos_values < size && pos_values < MAX_VALUES_SIZE; pos_values++) { - if (elem >= enum_obj_count) { + if (elem + pos_values >= enum_obj_count) { pr_err("Error enum-objects package is too small\n"); return -EINVAL; } diff --git a/drivers/platform/x86/hp/hp-bioscfg/int-attributes.c b/drivers/platform/x86/hp/hp-bioscfg/int-attributes.c index 6c7f4d5fa9cb9c..63b1fda2be4e20 100644 --- a/drivers/platform/x86/hp/hp-bioscfg/int-attributes.c +++ b/drivers/platform/x86/hp/hp-bioscfg/int-attributes.c @@ -227,7 +227,7 @@ static int hp_populate_integer_elements_from_package(union acpi_object *integer_ size = min_t(u32, integer_data->common.prerequisites_size, MAX_PREREQUISITES_SIZE); for (reqs = 0; reqs < size; reqs++) { - if (elem >= integer_obj_count) { + if (elem + reqs >= integer_obj_count) { pr_err("Error elem-objects package is too small\n"); return -EINVAL; } diff --git a/drivers/platform/x86/hp/hp-bioscfg/order-list-attributes.c b/drivers/platform/x86/hp/hp-bioscfg/order-list-attributes.c index c6e57bb9d8b74d..6a31f47ce3f5b7 100644 --- a/drivers/platform/x86/hp/hp-bioscfg/order-list-attributes.c +++ b/drivers/platform/x86/hp/hp-bioscfg/order-list-attributes.c @@ -216,6 +216,11 @@ static int hp_populate_ordered_list_elements_from_package(union acpi_object *ord size = min_t(u32, ordered_list_data->common.prerequisites_size, MAX_PREREQUISITES_SIZE); for (reqs = 0; reqs < size; reqs++) { + if (elem + reqs >= order_obj_count) { + pr_err("Error elem-objects package is too small\n"); + return -EINVAL; + } + ret = hp_convert_hexstr_to_str(order_obj[elem + reqs].string.pointer, order_obj[elem + reqs].string.length, &str_value, &value_len); diff --git a/drivers/platform/x86/hp/hp-bioscfg/passwdobj-attributes.c b/drivers/platform/x86/hp/hp-bioscfg/passwdobj-attributes.c index 187b372123ed39..ec79d9d50377af 100644 --- a/drivers/platform/x86/hp/hp-bioscfg/passwdobj-attributes.c +++ b/drivers/platform/x86/hp/hp-bioscfg/passwdobj-attributes.c @@ -303,6 +303,11 @@ static int hp_populate_password_elements_from_package(union acpi_object *passwor MAX_PREREQUISITES_SIZE); for (reqs = 0; reqs < size; reqs++) { + if (elem + reqs >= password_obj_count) { + pr_err("Error elem-objects package is too small\n"); + return -EINVAL; + } + ret = hp_convert_hexstr_to_str(password_obj[elem + reqs].string.pointer, password_obj[elem + reqs].string.length, &str_value, &value_len); diff --git a/drivers/platform/x86/hp/hp-bioscfg/string-attributes.c b/drivers/platform/x86/hp/hp-bioscfg/string-attributes.c index 27758b779b2d3c..7b885d25650c52 100644 --- a/drivers/platform/x86/hp/hp-bioscfg/string-attributes.c +++ b/drivers/platform/x86/hp/hp-bioscfg/string-attributes.c @@ -217,7 +217,7 @@ static int hp_populate_string_elements_from_package(union acpi_object *string_ob MAX_PREREQUISITES_SIZE); for (reqs = 0; reqs < size; reqs++) { - if (elem >= string_obj_count) { + if (elem + reqs >= string_obj_count) { pr_err("Error elem-objects package is too small\n"); return -EINVAL; } From 7f18ebf8c455f9b51a2e58c62f98310357069ada Mon Sep 17 00:00:00 2001 From: Alok Tiwari Date: Wed, 24 Dec 2025 01:51:09 -0800 Subject: [PATCH 1815/3902] platform/x86/intel/pmt/discovery: use valid device pointer in dev_err_probe MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [ Upstream commit 66e245db16f0175af656cd812b6dc1a5e1f7b80a ] The PMT feature probe creates a child device with device_create(). If device creation fail, the code pass priv->dev (which is an ERR_PTR) to dev_err_probe(), which is not a valid device pointer. This patch change the dev_err_probe() call to use the parent auxiliary device (&auxdev->dev) and update the error message to reference the parent device name. It ensure correct error reporting and avoid passing an invalid device pointer. Fixes: d9a078809356 ("platform/x86/intel/pmt: Add PMT Discovery driver") Signed-off-by: Alok Tiwari Link: https://patch.msgid.link/20251224095133.115678-1-alok.a.tiwari@oracle.com Reviewed-by: Ilpo Järvinen Signed-off-by: Ilpo Järvinen Signed-off-by: Sasha Levin --- drivers/platform/x86/intel/pmt/discovery.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/platform/x86/intel/pmt/discovery.c b/drivers/platform/x86/intel/pmt/discovery.c index 9c5b4d0e1fae68..e500aa327d2374 100644 --- a/drivers/platform/x86/intel/pmt/discovery.c +++ b/drivers/platform/x86/intel/pmt/discovery.c @@ -548,9 +548,9 @@ static int pmt_features_probe(struct auxiliary_device *auxdev, const struct auxi priv->dev = device_create(&intel_pmt_class, &auxdev->dev, MKDEV(0, 0), priv, "%s-%s", "features", dev_name(priv->parent)); if (IS_ERR(priv->dev)) - return dev_err_probe(priv->dev, PTR_ERR(priv->dev), + return dev_err_probe(&auxdev->dev, PTR_ERR(priv->dev), "Could not create %s-%s device node\n", - "features", dev_name(priv->dev)); + "features", dev_name(priv->parent)); /* Initialize each feature */ for (i = 0; i < ivdev->num_resources; i++) { From 442848e457f5a9f71a4e7e14d24d73dae278ebe3 Mon Sep 17 00:00:00 2001 From: Anshumali Gaur Date: Fri, 19 Dec 2025 11:52:26 +0530 Subject: [PATCH 1816/3902] octeontx2-pf: fix "UBSAN: shift-out-of-bounds error" [ Upstream commit 85f4b0c650d9f9db10bda8d3acfa1af83bf78cf7 ] This patch ensures that the RX ring size (rx_pending) is not set below the permitted length. This avoids UBSAN shift-out-of-bounds errors when users passes small or zero ring sizes via ethtool -G. Fixes: d45d8979840d ("octeontx2-pf: Add basic ethtool support") Signed-off-by: Anshumali Gaur Link: https://patch.msgid.link/20251219062226.524844-1-agaur@marvell.com Signed-off-by: Paolo Abeni Signed-off-by: Sasha Levin --- drivers/net/ethernet/marvell/octeontx2/nic/otx2_ethtool.c | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/drivers/net/ethernet/marvell/octeontx2/nic/otx2_ethtool.c b/drivers/net/ethernet/marvell/octeontx2/nic/otx2_ethtool.c index b90e23dc49de98..b6449f0a9e7dd5 100644 --- a/drivers/net/ethernet/marvell/octeontx2/nic/otx2_ethtool.c +++ b/drivers/net/ethernet/marvell/octeontx2/nic/otx2_ethtool.c @@ -418,6 +418,14 @@ static int otx2_set_ringparam(struct net_device *netdev, */ if (rx_count < pfvf->hw.rq_skid) rx_count = pfvf->hw.rq_skid; + + if (ring->rx_pending < 16) { + netdev_err(netdev, + "rx ring size %u invalid, min is 16\n", + ring->rx_pending); + return -EINVAL; + } + rx_count = Q_COUNT(Q_SIZE(rx_count, 3)); /* Due pipelining impact minimum 2000 unused SQ CQE's From 5e5988736a95b1de7f91b10ac2575454b70e4897 Mon Sep 17 00:00:00 2001 From: Wei Fang Date: Thu, 4 Dec 2025 15:13:32 +0800 Subject: [PATCH 1817/3902] net: stmmac: fix the crash issue for zero copy XDP_TX action [ Upstream commit a48e232210009be50591fdea8ba7c07b0f566a13 ] There is a crash issue when running zero copy XDP_TX action, the crash log is shown below. [ 216.122464] Unable to handle kernel paging request at virtual address fffeffff80000000 [ 216.187524] Internal error: Oops: 0000000096000144 [#1] SMP [ 216.301694] Call trace: [ 216.304130] dcache_clean_poc+0x20/0x38 (P) [ 216.308308] __dma_sync_single_for_device+0x1bc/0x1e0 [ 216.313351] stmmac_xdp_xmit_xdpf+0x354/0x400 [ 216.317701] __stmmac_xdp_run_prog+0x164/0x368 [ 216.322139] stmmac_napi_poll_rxtx+0xba8/0xf00 [ 216.326576] __napi_poll+0x40/0x218 [ 216.408054] Kernel panic - not syncing: Oops: Fatal exception in interrupt For XDP_TX action, the xdp_buff is converted to xdp_frame by xdp_convert_buff_to_frame(). The memory type of the resulting xdp_frame depends on the memory type of the xdp_buff. For page pool based xdp_buff it produces xdp_frame with memory type MEM_TYPE_PAGE_POOL. For zero copy XSK pool based xdp_buff it produces xdp_frame with memory type MEM_TYPE_PAGE_ORDER0. However, stmmac_xdp_xmit_back() does not check the memory type and always uses the page pool type, this leads to invalid mappings and causes the crash. Therefore, check the xdp_buff memory type in stmmac_xdp_xmit_back() to fix this issue. Fixes: bba2556efad6 ("net: stmmac: Enable RX via AF_XDP zero-copy") Signed-off-by: Wei Fang Reviewed-by: Hariprasad Kelam Link: https://patch.msgid.link/20251204071332.1907111-1-wei.fang@nxp.com Signed-off-by: Paolo Abeni Signed-off-by: Sasha Levin --- .../net/ethernet/stmicro/stmmac/stmmac_main.c | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c b/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c index 86e912471dead2..0dd17179c85d24 100644 --- a/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c +++ b/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c @@ -88,6 +88,7 @@ MODULE_PARM_DESC(phyaddr, "Physical device address"); #define STMMAC_XDP_CONSUMED BIT(0) #define STMMAC_XDP_TX BIT(1) #define STMMAC_XDP_REDIRECT BIT(2) +#define STMMAC_XSK_CONSUMED BIT(3) static int flow_ctrl = 0xdead; module_param(flow_ctrl, int, 0644); @@ -4988,6 +4989,7 @@ static int stmmac_xdp_get_tx_queue(struct stmmac_priv *priv, static int stmmac_xdp_xmit_back(struct stmmac_priv *priv, struct xdp_buff *xdp) { + bool zc = !!(xdp->rxq->mem.type == MEM_TYPE_XSK_BUFF_POOL); struct xdp_frame *xdpf = xdp_convert_buff_to_frame(xdp); int cpu = smp_processor_id(); struct netdev_queue *nq; @@ -5004,9 +5006,18 @@ static int stmmac_xdp_xmit_back(struct stmmac_priv *priv, /* Avoids TX time-out as we are sharing with slow path */ txq_trans_cond_update(nq); - res = stmmac_xdp_xmit_xdpf(priv, queue, xdpf, false); - if (res == STMMAC_XDP_TX) + /* For zero copy XDP_TX action, dma_map is true */ + res = stmmac_xdp_xmit_xdpf(priv, queue, xdpf, zc); + if (res == STMMAC_XDP_TX) { stmmac_flush_tx_descriptors(priv, queue); + } else if (res == STMMAC_XDP_CONSUMED && zc) { + /* xdp has been freed by xdp_convert_buff_to_frame(), + * no need to call xsk_buff_free() again, so return + * STMMAC_XSK_CONSUMED. + */ + res = STMMAC_XSK_CONSUMED; + xdp_return_frame(xdpf); + } __netif_tx_unlock(nq); @@ -5356,6 +5367,8 @@ static int stmmac_rx_zc(struct stmmac_priv *priv, int limit, u32 queue) break; case STMMAC_XDP_CONSUMED: xsk_buff_free(buf->xdp); + fallthrough; + case STMMAC_XSK_CONSUMED: rx_dropped++; break; case STMMAC_XDP_TX: From 73744ad5696dce0e0f43872aba8de6a83d6ad570 Mon Sep 17 00:00:00 2001 From: Will Rosenberg Date: Fri, 19 Dec 2025 10:36:37 -0700 Subject: [PATCH 1818/3902] ipv6: BUG() in pskb_expand_head() as part of calipso_skbuff_setattr() [ Upstream commit 58fc7342b529803d3c221101102fe913df7adb83 ] There exists a kernel oops caused by a BUG_ON(nhead < 0) at net/core/skbuff.c:2232 in pskb_expand_head(). This bug is triggered as part of the calipso_skbuff_setattr() routine when skb_cow() is passed headroom > INT_MAX (i.e. (int)(skb_headroom(skb) + len_delta) < 0). The root cause of the bug is due to an implicit integer cast in __skb_cow(). The check (headroom > skb_headroom(skb)) is meant to ensure that delta = headroom - skb_headroom(skb) is never negative, otherwise we will trigger a BUG_ON in pskb_expand_head(). However, if headroom > INT_MAX and delta <= -NET_SKB_PAD, the check passes, delta becomes negative, and pskb_expand_head() is passed a negative value for nhead. Fix the trigger condition in calipso_skbuff_setattr(). Avoid passing "negative" headroom sizes to skb_cow() within calipso_skbuff_setattr() by only using skb_cow() to grow headroom. PoC: Using `netlabelctl` tool: netlabelctl map del default netlabelctl calipso add pass doi:7 netlabelctl map add default address:0::1/128 protocol:calipso,7 Then run the following PoC: int fd = socket(AF_INET6, SOCK_DGRAM, IPPROTO_UDP); // setup msghdr int cmsg_size = 2; int cmsg_len = 0x60; struct msghdr msg; struct sockaddr_in6 dest_addr; struct cmsghdr * cmsg = (struct cmsghdr *) calloc(1, sizeof(struct cmsghdr) + cmsg_len); msg.msg_name = &dest_addr; msg.msg_namelen = sizeof(dest_addr); msg.msg_iov = NULL; msg.msg_iovlen = 0; msg.msg_control = cmsg; msg.msg_controllen = cmsg_len; msg.msg_flags = 0; // setup sockaddr dest_addr.sin6_family = AF_INET6; dest_addr.sin6_port = htons(31337); dest_addr.sin6_flowinfo = htonl(31337); dest_addr.sin6_addr = in6addr_loopback; dest_addr.sin6_scope_id = 31337; // setup cmsghdr cmsg->cmsg_len = cmsg_len; cmsg->cmsg_level = IPPROTO_IPV6; cmsg->cmsg_type = IPV6_HOPOPTS; char * hop_hdr = (char *)cmsg + sizeof(struct cmsghdr); hop_hdr[1] = 0x9; //set hop size - (0x9 + 1) * 8 = 80 sendmsg(fd, &msg, 0); Fixes: 2917f57b6bc1 ("calipso: Allow the lsm to label the skbuff directly.") Suggested-by: Paul Moore Signed-off-by: Will Rosenberg Acked-by: Paul Moore Link: https://patch.msgid.link/20251219173637.797418-1-whrosenb@asu.edu Signed-off-by: Paolo Abeni Signed-off-by: Sasha Levin --- net/ipv6/calipso.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/net/ipv6/calipso.c b/net/ipv6/calipso.c index df1986973430c0..21f6ed126253a2 100644 --- a/net/ipv6/calipso.c +++ b/net/ipv6/calipso.c @@ -1342,7 +1342,8 @@ static int calipso_skbuff_setattr(struct sk_buff *skb, /* At this point new_end aligns to 4n, so (new_end & 4) pads to 8n */ pad = ((new_end & 4) + (end & 7)) & 7; len_delta = new_end - (int)end + pad; - ret_val = skb_cow(skb, skb_headroom(skb) + len_delta); + ret_val = skb_cow(skb, + skb_headroom(skb) + (len_delta > 0 ? len_delta : 0)); if (ret_val < 0) return ret_val; From e3fc381320d04e4a74311e576a86cac49a16fc43 Mon Sep 17 00:00:00 2001 From: Ido Schimmel Date: Sun, 21 Dec 2025 16:48:28 +0200 Subject: [PATCH 1819/3902] ipv4: Fix reference count leak when using error routes with nexthop objects [ Upstream commit ac782f4e3bfcde145b8a7f8af31d9422d94d172a ] When a nexthop object is deleted, it is marked as dead and then fib_table_flush() is called to flush all the routes that are using the dead nexthop. The current logic in fib_table_flush() is to only flush error routes (e.g., blackhole) when it is called as part of network namespace dismantle (i.e., with flush_all=true). Therefore, error routes are not flushed when their nexthop object is deleted: # ip link add name dummy1 up type dummy # ip nexthop add id 1 dev dummy1 # ip route add 198.51.100.1/32 nhid 1 # ip route add blackhole 198.51.100.2/32 nhid 1 # ip nexthop del id 1 # ip route show blackhole 198.51.100.2 nhid 1 dev dummy1 As such, they keep holding a reference on the nexthop object which in turn holds a reference on the nexthop device, resulting in a reference count leak: # ip link del dev dummy1 [ 70.516258] unregister_netdevice: waiting for dummy1 to become free. Usage count = 2 Fix by flushing error routes when their nexthop is marked as dead. IPv6 does not suffer from this problem. Fixes: 493ced1ac47c ("ipv4: Allow routes to use nexthop objects") Reported-by: Tetsuo Handa Closes: https://lore.kernel.org/netdev/d943f806-4da6-4970-ac28-b9373b0e63ac@I-love.SAKURA.ne.jp/ Reported-by: syzbot+881d65229ca4f9ae8c84@syzkaller.appspotmail.com Signed-off-by: Ido Schimmel Reviewed-by: David Ahern Link: https://patch.msgid.link/20251221144829.197694-1-idosch@nvidia.com Signed-off-by: Paolo Abeni Signed-off-by: Sasha Levin --- net/ipv4/fib_trie.c | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/net/ipv4/fib_trie.c b/net/ipv4/fib_trie.c index 59a6f0a9638f99..7e2c17fec3fc46 100644 --- a/net/ipv4/fib_trie.c +++ b/net/ipv4/fib_trie.c @@ -2053,10 +2053,11 @@ int fib_table_flush(struct net *net, struct fib_table *tb, bool flush_all) continue; } - /* Do not flush error routes if network namespace is - * not being dismantled + /* When not flushing the entire table, skip error + * routes that are not marked for deletion. */ - if (!flush_all && fib_props[fa->fa_type].error) { + if (!flush_all && fib_props[fa->fa_type].error && + !(fi->fib_flags & RTNH_F_DEAD)) { slen = fa->fa_slen; continue; } From ffd335167d7edc171380546a0f204d4c680d690d Mon Sep 17 00:00:00 2001 From: Vadim Fedorenko Date: Sun, 21 Dec 2025 19:26:38 +0000 Subject: [PATCH 1820/3902] net: fib: restore ECMP balance from loopback [ Upstream commit 6e17474aa9fe15015c9921a5081c7ca71783aac6 ] Preference of nexthop with source address broke ECMP for packets with source addresses which are not in the broadcast domain, but rather added to loopback/dummy interfaces. Original behaviour was to balance over nexthops while now it uses the latest nexthop from the group. To fix the issue introduce next hop scoring system where next hops with source address equal to requested will always have higher priority. For the case with 198.51.100.1/32 assigned to dummy0 and routed using 192.0.2.0/24 and 203.0.113.0/24 networks: 2: dummy0: mtu 1500 qdisc noqueue state UNKNOWN group default qlen 1000 link/ether d6:54:8a:ff:78:f5 brd ff:ff:ff:ff:ff:ff inet 198.51.100.1/32 scope global dummy0 valid_lft forever preferred_lft forever 7: veth1@if6: mtu 1500 qdisc noqueue state UP group default qlen 1000 link/ether 06:ed:98:87:6d:8a brd ff:ff:ff:ff:ff:ff link-netnsid 0 inet 192.0.2.2/24 scope global veth1 valid_lft forever preferred_lft forever inet6 fe80::4ed:98ff:fe87:6d8a/64 scope link proto kernel_ll valid_lft forever preferred_lft forever 9: veth3@if8: mtu 1500 qdisc noqueue state UP group default qlen 1000 link/ether ae:75:23:38:a0:d2 brd ff:ff:ff:ff:ff:ff link-netnsid 0 inet 203.0.113.2/24 scope global veth3 valid_lft forever preferred_lft forever inet6 fe80::ac75:23ff:fe38:a0d2/64 scope link proto kernel_ll valid_lft forever preferred_lft forever ~ ip ro list: default nexthop via 192.0.2.1 dev veth1 weight 1 nexthop via 203.0.113.1 dev veth3 weight 1 192.0.2.0/24 dev veth1 proto kernel scope link src 192.0.2.2 203.0.113.0/24 dev veth3 proto kernel scope link src 203.0.113.2 before: for i in {1..255} ; do ip ro get 10.0.0.$i; done | grep veth | awk ' {print $(NF-2)}' | sort | uniq -c: 255 veth3 after: for i in {1..255} ; do ip ro get 10.0.0.$i; done | grep veth | awk ' {print $(NF-2)}' | sort | uniq -c: 122 veth1 133 veth3 Fixes: 32607a332cfe ("ipv4: prefer multipath nexthop that matches source address") Signed-off-by: Vadim Fedorenko Reviewed-by: Ido Schimmel Reviewed-by: Willem de Bruijn Link: https://patch.msgid.link/20251221192639.3911901-1-vadim.fedorenko@linux.dev Signed-off-by: Paolo Abeni Signed-off-by: Sasha Levin --- net/ipv4/fib_semantics.c | 26 ++++++++++---------------- 1 file changed, 10 insertions(+), 16 deletions(-) diff --git a/net/ipv4/fib_semantics.c b/net/ipv4/fib_semantics.c index a5f3c8459758f1..0caf38e44c738c 100644 --- a/net/ipv4/fib_semantics.c +++ b/net/ipv4/fib_semantics.c @@ -2167,8 +2167,8 @@ void fib_select_multipath(struct fib_result *res, int hash, { struct fib_info *fi = res->fi; struct net *net = fi->fib_net; - bool found = false; bool use_neigh; + int score = -1; __be32 saddr; if (unlikely(res->fi->nh)) { @@ -2180,7 +2180,7 @@ void fib_select_multipath(struct fib_result *res, int hash, saddr = fl4 ? fl4->saddr : 0; change_nexthops(fi) { - int nh_upper_bound; + int nh_upper_bound, nh_score = 0; /* Nexthops without a carrier are assigned an upper bound of * minus one when "ignore_routes_with_linkdown" is set. @@ -2190,24 +2190,18 @@ void fib_select_multipath(struct fib_result *res, int hash, (use_neigh && !fib_good_nh(nexthop_nh))) continue; - if (!found) { + if (saddr && nexthop_nh->nh_saddr == saddr) + nh_score += 2; + if (hash <= nh_upper_bound) + nh_score++; + if (score < nh_score) { res->nh_sel = nhsel; res->nhc = &nexthop_nh->nh_common; - found = !saddr || nexthop_nh->nh_saddr == saddr; + if (nh_score == 3 || (!saddr && nh_score == 1)) + return; + score = nh_score; } - if (hash > nh_upper_bound) - continue; - - if (!saddr || nexthop_nh->nh_saddr == saddr) { - res->nh_sel = nhsel; - res->nhc = &nexthop_nh->nh_common; - return; - } - - if (found) - return; - } endfor_nexthops(fi); } #endif From 92d900aac3a5721fb54f3328f1e089b44a861c38 Mon Sep 17 00:00:00 2001 From: Pwnverse Date: Mon, 22 Dec 2025 21:22:27 +0000 Subject: [PATCH 1821/3902] net: rose: fix invalid array index in rose_kill_by_device() [ Upstream commit 6595beb40fb0ec47223d3f6058ee40354694c8e4 ] rose_kill_by_device() collects sockets into a local array[] and then iterates over them to disconnect sockets bound to a device being brought down. The loop mistakenly indexes array[cnt] instead of array[i]. For cnt < ARRAY_SIZE(array), this reads an uninitialized entry; for cnt == ARRAY_SIZE(array), it is an out-of-bounds read. Either case can lead to an invalid socket pointer dereference and also leaks references taken via sock_hold(). Fix the index to use i. Fixes: 64b8bc7d5f143 ("net/rose: fix races in rose_kill_by_device()") Co-developed-by: Fatma Alwasmi Signed-off-by: Fatma Alwasmi Signed-off-by: Pwnverse Link: https://patch.msgid.link/20251222212227.4116041-1-ritviktanksalkar@gmail.com Signed-off-by: Paolo Abeni Signed-off-by: Sasha Levin --- net/rose/af_rose.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/net/rose/af_rose.c b/net/rose/af_rose.c index 543f9e8ebb6937..fad6518e6e39be 100644 --- a/net/rose/af_rose.c +++ b/net/rose/af_rose.c @@ -205,7 +205,7 @@ static void rose_kill_by_device(struct net_device *dev) spin_unlock_bh(&rose_list_lock); for (i = 0; i < cnt; i++) { - sk = array[cnt]; + sk = array[i]; rose = rose_sk(sk); lock_sock(sk); spin_lock_bh(&rose_list_lock); From 787515ccb2292f82eb0876993129154629a49651 Mon Sep 17 00:00:00 2001 From: Jiayuan Chen Date: Tue, 23 Dec 2025 13:14:12 +0800 Subject: [PATCH 1822/3902] ipv6: fix a BUG in rt6_get_pcpu_route() under PREEMPT_RT [ Upstream commit 1adaea51c61b52e24e7ab38f7d3eba023b2d050d ] On PREEMPT_RT kernels, after rt6_get_pcpu_route() returns NULL, the current task can be preempted. Another task running on the same CPU may then execute rt6_make_pcpu_route() and successfully install a pcpu_rt entry. When the first task resumes execution, its cmpxchg() in rt6_make_pcpu_route() will fail because rt6i_pcpu is no longer NULL, triggering the BUG_ON(prev). It's easy to reproduce it by adding mdelay() after rt6_get_pcpu_route(). Using preempt_disable/enable is not appropriate here because ip6_rt_pcpu_alloc() may sleep. Fix this by handling the cmpxchg() failure gracefully on PREEMPT_RT: free our allocation and return the existing pcpu_rt installed by another task. The BUG_ON is replaced by WARN_ON_ONCE for non-PREEMPT_RT kernels where such races should not occur. Link: https://syzkaller.appspot.com/bug?extid=9b35e9bc0951140d13e6 Fixes: d2d6422f8bd1 ("x86: Allow to enable PREEMPT_RT.") Reported-by: syzbot+9b35e9bc0951140d13e6@syzkaller.appspotmail.com Closes: https://lore.kernel.org/all/6918cd88.050a0220.1c914e.0045.GAE@google.com/T/ Signed-off-by: Jiayuan Chen Link: https://patch.msgid.link/20251223051413.124687-1-jiayuan.chen@linux.dev Signed-off-by: Paolo Abeni Signed-off-by: Sasha Levin --- net/ipv6/route.c | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/net/ipv6/route.c b/net/ipv6/route.c index aee6a10b112aac..a3e051dc66ee07 100644 --- a/net/ipv6/route.c +++ b/net/ipv6/route.c @@ -1470,7 +1470,18 @@ static struct rt6_info *rt6_make_pcpu_route(struct net *net, p = this_cpu_ptr(res->nh->rt6i_pcpu); prev = cmpxchg(p, NULL, pcpu_rt); - BUG_ON(prev); + if (unlikely(prev)) { + /* + * Another task on this CPU already installed a pcpu_rt. + * This can happen on PREEMPT_RT where preemption is possible. + * Free our allocation and return the existing one. + */ + WARN_ON_ONCE(!IS_ENABLED(CONFIG_PREEMPT_RT)); + + dst_dev_put(&pcpu_rt->dst); + dst_release(&pcpu_rt->dst); + return prev; + } if (res->f6i->fib6_destroying) { struct fib6_info *from; From 263255a62cea53699aa52cd033fcf765513f815f Mon Sep 17 00:00:00 2001 From: Arnd Bergmann Date: Mon, 8 Dec 2025 14:33:05 +0100 Subject: [PATCH 1823/3902] RDMA/ucma: Fix rdma_ucm_query_ib_service_resp struct padding [ Upstream commit 2dc675f614850b80deab7cf6d12902636ed8a7f4 ] On a few 32-bit architectures, the newly added ib_user_service_rec structure is not 64-bit aligned the way it is on most regular ones. Add explicit padding into the rdma_ucm_query_ib_service_resp and rdma_ucm_resolve_ib_service structures that embed it, so that the layout is compatible across all of them. This is an ABI change on i386, aligning it with x86_64 and the other 64-bit architectures to avoid having to use a compat ioctl handler. Fixes: 810f874eda8e ("RDMA/ucma: Support query resolved service records") Link: https://patch.msgid.link/r/20251208133311.313977-1-arnd@kernel.org Signed-off-by: Arnd Bergmann Signed-off-by: Jason Gunthorpe Signed-off-by: Sasha Levin --- include/uapi/rdma/rdma_user_cm.h | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/include/uapi/rdma/rdma_user_cm.h b/include/uapi/rdma/rdma_user_cm.h index 5ded174687ee04..838f8d46025603 100644 --- a/include/uapi/rdma/rdma_user_cm.h +++ b/include/uapi/rdma/rdma_user_cm.h @@ -192,6 +192,7 @@ struct rdma_ucm_query_path_resp { struct rdma_ucm_query_ib_service_resp { __u32 num_service_recs; + __u32 reserved; struct ib_user_service_rec recs[]; }; @@ -354,7 +355,7 @@ enum { #define RDMA_USER_CM_IB_SERVICE_NAME_SIZE 64 struct rdma_ucm_ib_service { - __u64 service_id; + __aligned_u64 service_id; __u8 service_name[RDMA_USER_CM_IB_SERVICE_NAME_SIZE]; __u32 flags; __u32 reserved; @@ -362,6 +363,7 @@ struct rdma_ucm_ib_service { struct rdma_ucm_resolve_ib_service { __u32 id; + __u32 reserved; struct rdma_ucm_ib_service ibs; }; From 20710399c9b5ef80e21d13fa0138b1370e358c8f Mon Sep 17 00:00:00 2001 From: Arnd Bergmann Date: Mon, 8 Dec 2025 14:38:44 +0100 Subject: [PATCH 1824/3902] RDMA/irdma: Fix irdma_alloc_ucontext_resp padding [ Upstream commit d95e99a74eaf35c070f5939295331e5d7857c723 ] A recent commit modified struct irdma_alloc_ucontext_resp by adding a member with implicit padding in front of it, though this does not change the offset of the data members other than m68k. Reported by scripts/check-uapi.sh: ==== ABI differences detected in include/rdma/irdma-abi.h from 1dd7bde2e91c -> HEAD ==== [C] 'struct irdma_alloc_ucontext_resp' changed: type size changed from 704 to 640 (in bits) 1 data member deletion: '__u8 rsvd3[2]', at offset 640 (in bits) at irdma-abi.h:61:1 1 data member insertion: '__u8 revd3[2]', at offset 592 (in bits) at irdma-abi.h:60:1 Change the size back to the previous version, and remove the implicit padding by making it explicit and matching what x86-64 would do by placing max_hw_srq_quanta member into a naturally aligned location. Fixes: 563e1feb5f6e ("RDMA/irdma: Add SRQ support") Link: https://patch.msgid.link/r/20251208133849.315451-1-arnd@kernel.org Signed-off-by: Arnd Bergmann Reviewed-by: Geert Uytterhoeven Tested-by: Jacob Moroni Signed-off-by: Jason Gunthorpe Signed-off-by: Sasha Levin --- include/uapi/rdma/irdma-abi.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/uapi/rdma/irdma-abi.h b/include/uapi/rdma/irdma-abi.h index f7788d33376b83..36f20802bcc849 100644 --- a/include/uapi/rdma/irdma-abi.h +++ b/include/uapi/rdma/irdma-abi.h @@ -57,8 +57,8 @@ struct irdma_alloc_ucontext_resp { __u8 rsvd2; __aligned_u64 comp_mask; __u16 min_hw_wq_size; + __u8 revd3[2]; __u32 max_hw_srq_quanta; - __u8 rsvd3[2]; }; struct irdma_alloc_pd_resp { From 94dda131004e7218b465ac95c52dd6eeee5cd6be Mon Sep 17 00:00:00 2001 From: Konstantin Taranov Date: Thu, 23 Oct 2025 03:03:00 -0700 Subject: [PATCH 1825/3902] RDMA/mana_ib: check cqe length for kernel CQs [ Upstream commit 887bfe5986396aca908b7afd2d214471ba7d5544 ] Check queue size during kernel CQ creation to prevent overflow of u32. Fixes: bec127e45d9f ("RDMA/mana_ib: create kernel-level CQs") Link: https://patch.msgid.link/r/1761213780-5457-1-git-send-email-kotaranov@linux.microsoft.com Signed-off-by: Konstantin Taranov Reviewed-by: Long Li Signed-off-by: Jason Gunthorpe Signed-off-by: Sasha Levin --- drivers/infiniband/hw/mana/cq.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/drivers/infiniband/hw/mana/cq.c b/drivers/infiniband/hw/mana/cq.c index 1becc877912358..7600412b0739ff 100644 --- a/drivers/infiniband/hw/mana/cq.c +++ b/drivers/infiniband/hw/mana/cq.c @@ -56,6 +56,10 @@ int mana_ib_create_cq(struct ib_cq *ibcq, const struct ib_cq_init_attr *attr, doorbell = mana_ucontext->doorbell; } else { is_rnic_cq = true; + if (attr->cqe > U32_MAX / COMP_ENTRY_SIZE / 2 + 1) { + ibdev_dbg(ibdev, "CQE %d exceeding limit\n", attr->cqe); + return -EINVAL; + } buf_size = MANA_PAGE_ALIGN(roundup_pow_of_two(attr->cqe * COMP_ENTRY_SIZE)); cq->cqe = buf_size / COMP_ENTRY_SIZE; err = mana_ib_create_kernel_queue(mdev, buf_size, GDMA_CQ, &cq->queue); From d9b9affd103f51b42322da4ed5ac025b560bc354 Mon Sep 17 00:00:00 2001 From: Michal Schmidt Date: Thu, 27 Nov 2025 15:31:50 +0100 Subject: [PATCH 1826/3902] RDMA/irdma: avoid invalid read in irdma_net_event [ Upstream commit 6f05611728e9d0ab024832a4f1abb74a5f5d0bb0 ] irdma_net_event() should not dereference anything from "neigh" (alias "ptr") until it has checked that the event is NETEVENT_NEIGH_UPDATE. Other events come with different structures pointed to by "ptr" and they may be smaller than struct neighbour. Move the read of neigh->dev under the NETEVENT_NEIGH_UPDATE case. The bug is mostly harmless, but it triggers KASAN on debug kernels: BUG: KASAN: stack-out-of-bounds in irdma_net_event+0x32e/0x3b0 [irdma] Read of size 8 at addr ffffc900075e07f0 by task kworker/27:2/542554 CPU: 27 PID: 542554 Comm: kworker/27:2 Kdump: loaded Not tainted 5.14.0-630.el9.x86_64+debug #1 Hardware name: [...] Workqueue: events rt6_probe_deferred Call Trace: dump_stack_lvl+0x60/0xb0 print_address_description.constprop.0+0x2c/0x3f0 print_report+0xb4/0x270 kasan_report+0x92/0xc0 irdma_net_event+0x32e/0x3b0 [irdma] notifier_call_chain+0x9e/0x180 atomic_notifier_call_chain+0x5c/0x110 rt6_do_redirect+0xb91/0x1080 tcp_v6_err+0xe9b/0x13e0 icmpv6_notify+0x2b2/0x630 ndisc_redirect_rcv+0x328/0x530 icmpv6_rcv+0xc16/0x1360 ip6_protocol_deliver_rcu+0xb84/0x12e0 ip6_input_finish+0x117/0x240 ip6_input+0xc4/0x370 ipv6_rcv+0x420/0x7d0 __netif_receive_skb_one_core+0x118/0x1b0 process_backlog+0xd1/0x5d0 __napi_poll.constprop.0+0xa3/0x440 net_rx_action+0x78a/0xba0 handle_softirqs+0x2d4/0x9c0 do_softirq+0xad/0xe0 Fixes: 915cc7ac0f8e ("RDMA/irdma: Add miscellaneous utility definitions") Link: https://patch.msgid.link/r/20251127143150.121099-1-mschmidt@redhat.com Signed-off-by: Michal Schmidt Signed-off-by: Jason Gunthorpe Signed-off-by: Sasha Levin --- drivers/infiniband/hw/irdma/utils.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/drivers/infiniband/hw/irdma/utils.c b/drivers/infiniband/hw/irdma/utils.c index 8b94d87b01923e..b6c4ccf38eb78c 100644 --- a/drivers/infiniband/hw/irdma/utils.c +++ b/drivers/infiniband/hw/irdma/utils.c @@ -251,7 +251,7 @@ int irdma_net_event(struct notifier_block *notifier, unsigned long event, void *ptr) { struct neighbour *neigh = ptr; - struct net_device *real_dev, *netdev = (struct net_device *)neigh->dev; + struct net_device *real_dev, *netdev; struct irdma_device *iwdev; struct ib_device *ibdev; __be32 *p; @@ -260,6 +260,7 @@ int irdma_net_event(struct notifier_block *notifier, unsigned long event, switch (event) { case NETEVENT_NEIGH_UPDATE: + netdev = neigh->dev; real_dev = rdma_vlan_dev_real_dev(netdev); if (!real_dev) real_dev = netdev; From 277f1f4a80b40e5c12489f59b77ea4e2402cd765 Mon Sep 17 00:00:00 2001 From: Michael Margolin Date: Wed, 10 Dec 2025 17:36:56 +0000 Subject: [PATCH 1827/3902] RDMA/efa: Remove possible negative shift [ Upstream commit 85463eb6a46caf2f1e0e1a6d0731f2f3bab17780 ] The page size used for device might in some cases be smaller than PAGE_SIZE what results in a negative shift when calculating the number of host pages in PAGE_SIZE for a debug log. Remove the debug line together with the calculation. Fixes: 40909f664d27 ("RDMA/efa: Add EFA verbs implementation") Link: https://patch.msgid.link/r/20251210173656.8180-1-mrgolin@amazon.com Reviewed-by: Tom Sela Reviewed-by: Yonatan Nachum Signed-off-by: Michael Margolin Reviewed-by: Gal Pressman Signed-off-by: Jason Gunthorpe Signed-off-by: Sasha Levin --- drivers/infiniband/hw/efa/efa_verbs.c | 4 ---- 1 file changed, 4 deletions(-) diff --git a/drivers/infiniband/hw/efa/efa_verbs.c b/drivers/infiniband/hw/efa/efa_verbs.c index 22d3e25c3b9d11..755bba8d58bbc9 100644 --- a/drivers/infiniband/hw/efa/efa_verbs.c +++ b/drivers/infiniband/hw/efa/efa_verbs.c @@ -1320,13 +1320,9 @@ static int umem_to_page_list(struct efa_dev *dev, u32 hp_cnt, u8 hp_shift) { - u32 pages_in_hp = BIT(hp_shift - PAGE_SHIFT); struct ib_block_iter biter; unsigned int hp_idx = 0; - ibdev_dbg(&dev->ibdev, "hp_cnt[%u], pages_in_hp[%u]\n", - hp_cnt, pages_in_hp); - rdma_umem_for_each_dma_block(umem, &biter, BIT(hp_shift)) page_list[hp_idx++] = rdma_block_iter_dma_address(&biter); From 868197d9f85ba7a68eacd8bacf28292b1b7345b9 Mon Sep 17 00:00:00 2001 From: Jang Ingyu Date: Fri, 19 Dec 2025 13:15:08 +0900 Subject: [PATCH 1828/3902] RDMA/core: Fix logic error in ib_get_gids_from_rdma_hdr() [ Upstream commit 8aaa848eaddd9ef8680fc6aafbd3a0646da5df40 ] Fix missing comparison operator for RDMA_NETWORK_ROCE_V1 in the conditional statement. The constant was used directly instead of being compared with net_type, causing the condition to always evaluate to true. Fixes: 1c15b4f2a42f ("RDMA/core: Modify enum ib_gid_type and enum rdma_network_type") Signed-off-by: Jang Ingyu Link: https://patch.msgid.link/20251219041508.1725947-1-ingyujang25@korea.ac.kr Signed-off-by: Leon Romanovsky Signed-off-by: Sasha Levin --- drivers/infiniband/core/verbs.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/infiniband/core/verbs.c b/drivers/infiniband/core/verbs.c index 3a5f81402d2f8b..d279e301f5a1a8 100644 --- a/drivers/infiniband/core/verbs.c +++ b/drivers/infiniband/core/verbs.c @@ -735,7 +735,7 @@ int ib_get_gids_from_rdma_hdr(const union rdma_network_hdr *hdr, (struct in6_addr *)dgid); return 0; } else if (net_type == RDMA_NETWORK_IPV6 || - net_type == RDMA_NETWORK_IB || RDMA_NETWORK_ROCE_V1) { + net_type == RDMA_NETWORK_IB || net_type == RDMA_NETWORK_ROCE_V1) { *dgid = hdr->ibgrh.dgid; *sgid = hdr->ibgrh.sgid; return 0; From b20d6455f5372171cc3f2cc40763393f384433ea Mon Sep 17 00:00:00 2001 From: Alok Tiwari Date: Wed, 17 Dec 2025 02:01:41 -0800 Subject: [PATCH 1829/3902] RDMA/bnxt_re: Fix incorrect BAR check in bnxt_qplib_map_creq_db() [ Upstream commit 145a417a39d7efbc881f52e829817376972b278c ] RCFW_COMM_CONS_PCI_BAR_REGION is defined as BAR 2, so checking !creq_db->reg.bar_id is incorrect and always false. pci_resource_start() returns the BAR base address, and a value of 0 indicates that the BAR is unassigned. Update the condition to test bar_base == 0 instead. This ensures the driver detects and logs an error for an unassigned RCFW communication BAR. Fixes: cee0c7bba486 ("RDMA/bnxt_re: Refactor command queue management code") Signed-off-by: Alok Tiwari Link: https://patch.msgid.link/20251217100158.752504-1-alok.a.tiwari@oracle.com Reviewed-by: Kalesh AP Signed-off-by: Leon Romanovsky Signed-off-by: Sasha Levin --- drivers/infiniband/hw/bnxt_re/qplib_rcfw.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/infiniband/hw/bnxt_re/qplib_rcfw.c b/drivers/infiniband/hw/bnxt_re/qplib_rcfw.c index 295a9610f3e677..4dad0cfcfa98d1 100644 --- a/drivers/infiniband/hw/bnxt_re/qplib_rcfw.c +++ b/drivers/infiniband/hw/bnxt_re/qplib_rcfw.c @@ -1112,7 +1112,7 @@ static int bnxt_qplib_map_creq_db(struct bnxt_qplib_rcfw *rcfw, u32 reg_offt) creq_db->dbinfo.flags = 0; creq_db->reg.bar_id = RCFW_COMM_CONS_PCI_BAR_REGION; creq_db->reg.bar_base = pci_resource_start(pdev, creq_db->reg.bar_id); - if (!creq_db->reg.bar_id) + if (!creq_db->reg.bar_base) dev_err(&pdev->dev, "QPLIB: CREQ BAR region %d resc start is 0!", creq_db->reg.bar_id); From fe8d456080423b9ed410469fbd1e2098d3acce2b Mon Sep 17 00:00:00 2001 From: Tetsuo Handa Date: Sat, 20 Dec 2025 11:11:33 +0900 Subject: [PATCH 1830/3902] RDMA/core: always drop device refcount in ib_del_sub_device_and_put() [ Upstream commit fa3c411d21ebc26ffd175c7256c37cefa35020aa ] Since nldev_deldev() (introduced by commit 060c642b2ab8 ("RDMA/nldev: Add support to add/delete a sub IB device through netlink") grabs a reference using ib_device_get_by_index() before calling ib_del_sub_device_and_put(), we need to drop that reference before returning -EOPNOTSUPP error. Reported-by: syzbot+881d65229ca4f9ae8c84@syzkaller.appspotmail.com Closes: https://syzkaller.appspot.com/bug?extid=881d65229ca4f9ae8c84 Fixes: bca51197620a ("RDMA/core: Support IB sub device with type "SMI"") Signed-off-by: Tetsuo Handa Link: https://patch.msgid.link/80749a85-cbe2-460c-8451-42516013f9fa@I-love.SAKURA.ne.jp Reviewed-by: Parav Pandit Signed-off-by: Leon Romanovsky Signed-off-by: Sasha Levin --- drivers/infiniband/core/device.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/drivers/infiniband/core/device.c b/drivers/infiniband/core/device.c index b4f3c835844a92..e3ba236d7c09fb 100644 --- a/drivers/infiniband/core/device.c +++ b/drivers/infiniband/core/device.c @@ -2881,8 +2881,10 @@ int ib_del_sub_device_and_put(struct ib_device *sub) { struct ib_device *parent = sub->parent; - if (!parent) + if (!parent) { + ib_device_put(sub); return -EOPNOTSUPP; + } mutex_lock(&parent->subdev_lock); list_del(&sub->subdev_list); From 08e98ad766b1d59a4569aedffb09a60fc545c758 Mon Sep 17 00:00:00 2001 From: Thomas Zimmermann Date: Tue, 9 Dec 2025 14:41:59 +0100 Subject: [PATCH 1831/3902] drm/gem-shmem: Fix the MODULE_LICENSE() string [ Upstream commit 3fbd97618f49e07e05aad96510e5f2ed22d68809 ] Replace the bogus "GPL v2" with "GPL" as MODULE_LICNSE() string. The value does not declare the module's exact license, but only lets the module loader test whether the module is Free Software or not. See commit bf7fbeeae6db ("module: Cure the MODULE_LICENSE "GPL" vs. "GPL v2" bogosity") in the details of the issue. The fix is to use "GPL" for all modules under any variant of the GPL. Signed-off-by: Thomas Zimmermann Reviewed-by: Boris Brezillon Fixes: 4b2b5e142ff4 ("drm: Move GEM memory managers into modules") Link: https://patch.msgid.link/20251209140141.94407-3-tzimmermann@suse.de Signed-off-by: Sasha Levin --- drivers/gpu/drm/drm_gem_shmem_helper.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/gpu/drm/drm_gem_shmem_helper.c b/drivers/gpu/drm/drm_gem_shmem_helper.c index 5d1349c34afd3d..365b5737ca2c61 100644 --- a/drivers/gpu/drm/drm_gem_shmem_helper.c +++ b/drivers/gpu/drm/drm_gem_shmem_helper.c @@ -863,4 +863,4 @@ EXPORT_SYMBOL_GPL(drm_gem_shmem_prime_import_no_map); MODULE_DESCRIPTION("DRM SHMEM memory-management helpers"); MODULE_IMPORT_NS("DMA_BUF"); -MODULE_LICENSE("GPL v2"); +MODULE_LICENSE("GPL"); From ac4567773ce505763b0d2fb9c0b876db419676c6 Mon Sep 17 00:00:00 2001 From: Alok Tiwari Date: Fri, 19 Dec 2025 01:32:57 -0800 Subject: [PATCH 1832/3902] RDMA/bnxt_re: Fix IB_SEND_IP_CSUM handling in post_send [ Upstream commit f01765a2361323e78e3d91b1cb1d5527a83c5cf7 ] The bnxt_re SEND path checks wr->send_flags to enable features such as IP checksum offload. However, send_flags is a bitmask and may contain multiple flags (e.g. IB_SEND_SIGNALED | IB_SEND_IP_CSUM), while the existing code uses a switch() statement that only matches when send_flags is exactly IB_SEND_IP_CSUM. As a result, checksum offload is not enabled when additional SEND flags are present. Replace the switch() with a bitmask test: if (wr->send_flags & IB_SEND_IP_CSUM) This ensures IP checksum offload is enabled correctly when multiple SEND flags are used. Fixes: 1ac5a4047975 ("RDMA/bnxt_re: Add bnxt_re RoCE driver") Signed-off-by: Alok Tiwari Link: https://patch.msgid.link/20251219093308.2415620-1-alok.a.tiwari@oracle.com Reviewed-by: Kalesh AP Signed-off-by: Leon Romanovsky Signed-off-by: Sasha Levin --- drivers/infiniband/hw/bnxt_re/ib_verbs.c | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/drivers/infiniband/hw/bnxt_re/ib_verbs.c b/drivers/infiniband/hw/bnxt_re/ib_verbs.c index f19b55c13d5809..ff91511bd3389e 100644 --- a/drivers/infiniband/hw/bnxt_re/ib_verbs.c +++ b/drivers/infiniband/hw/bnxt_re/ib_verbs.c @@ -2919,14 +2919,9 @@ int bnxt_re_post_send(struct ib_qp *ib_qp, const struct ib_send_wr *wr, wqe.rawqp1.lflags |= SQ_SEND_RAWETH_QP1_LFLAGS_ROCE_CRC; } - switch (wr->send_flags) { - case IB_SEND_IP_CSUM: + if (wr->send_flags & IB_SEND_IP_CSUM) wqe.rawqp1.lflags |= SQ_SEND_RAWETH_QP1_LFLAGS_IP_CHKSUM; - break; - default: - break; - } fallthrough; case IB_WR_SEND_WITH_INV: rc = bnxt_re_build_send_wqe(qp, wr, &wqe); From 369a161c48723f60f06f3510b82ea7d96d0499ab Mon Sep 17 00:00:00 2001 From: Ding Hui Date: Mon, 8 Dec 2025 15:21:10 +0800 Subject: [PATCH 1833/3902] RDMA/bnxt_re: Fix OOB write in bnxt_re_copy_err_stats() [ Upstream commit 9b68a1cc966bc947d00e4c0df7722d118125aa37 ] Commit ef56081d1864 ("RDMA/bnxt_re: RoCE related hardware counters update") added three new counters and placed them after BNXT_RE_OUT_OF_SEQ_ERR. BNXT_RE_OUT_OF_SEQ_ERR acts as a boundary marker for allocating hardware statistics with different num_counters values on chip_gen_p5_p7 devices. As a result, BNXT_RE_NUM_STD_COUNTERS are used when allocating hw_stats, which leads to an out-of-bounds write in bnxt_re_copy_err_stats(). The counters BNXT_RE_REQ_CQE_ERROR, BNXT_RE_RESP_CQE_ERROR, and BNXT_RE_RESP_REMOTE_ACCESS_ERRS are applicable to generic hardware, not only p5/p7 devices. Fix this by moving these counters before BNXT_RE_OUT_OF_SEQ_ERR so they are included in the generic counter set. Fixes: ef56081d1864 ("RDMA/bnxt_re: RoCE related hardware counters update") Reported-by: Yingying Zheng Signed-off-by: Ding Hui Link: https://patch.msgid.link/20251208072110.28874-1-dinghui@sangfor.com.cn Reviewed-by: Kalesh AP Tested-by: Kalesh AP Signed-off-by: Leon Romanovsky Signed-off-by: Sasha Levin --- drivers/infiniband/hw/bnxt_re/hw_counters.h | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/drivers/infiniband/hw/bnxt_re/hw_counters.h b/drivers/infiniband/hw/bnxt_re/hw_counters.h index 09d371d442aa78..cebec033f4a014 100644 --- a/drivers/infiniband/hw/bnxt_re/hw_counters.h +++ b/drivers/infiniband/hw/bnxt_re/hw_counters.h @@ -89,6 +89,9 @@ enum bnxt_re_hw_stats { BNXT_RE_RES_SRQ_LOAD_ERR, BNXT_RE_RES_TX_PCI_ERR, BNXT_RE_RES_RX_PCI_ERR, + BNXT_RE_REQ_CQE_ERROR, + BNXT_RE_RESP_CQE_ERROR, + BNXT_RE_RESP_REMOTE_ACCESS_ERRS, BNXT_RE_OUT_OF_SEQ_ERR, BNXT_RE_TX_ATOMIC_REQ, BNXT_RE_TX_READ_REQ, @@ -110,9 +113,6 @@ enum bnxt_re_hw_stats { BNXT_RE_TX_CNP, BNXT_RE_RX_CNP, BNXT_RE_RX_ECN, - BNXT_RE_REQ_CQE_ERROR, - BNXT_RE_RESP_CQE_ERROR, - BNXT_RE_RESP_REMOTE_ACCESS_ERRS, BNXT_RE_NUM_EXT_COUNTERS }; From e6d8d31d6e8721e3d94d09af0cbafa0eb77eddbd Mon Sep 17 00:00:00 2001 From: David Gow Date: Fri, 19 Dec 2025 16:52:58 +0800 Subject: [PATCH 1834/3902] kunit: Enforce task execution in {soft,hard}irq contexts [ Upstream commit c31f4aa8fed048fa70e742c4bb49bb48dc489ab3 ] The kunit_run_irq_test() helper allows a function to be run in hardirq and softirq contexts (in addition to the task context). It does this by running the user-provided function concurrently in the three contexts, until either a timeout has expired or a number of iterations have completed in the normal task context. However, on setups where the initialisation of the hardirq and softirq contexts (or, indeed, the scheduling of those tasks) is significantly slower than the function execution, it's possible for that number of iterations to be exceeded before any runs in irq contexts actually occur. This occurs with the polyval.test_polyval_preparekey_in_irqs test, which runs 20000 iterations of the relatively fast preparekey function, and therefore fails often under many UML, 32-bit arm, m68k and other environments. Instead, ensure that the max_iterations limit counts executions in all three contexts, and requires at least one of each. This will cause the test to continue iterating until at least the irq contexts have been tested, or the 1s wall-clock limit has been exceeded. This causes the test to pass in all of my environments. In so doing, we also update the task counters to atomic ints, to better match both the 'int' max_iterations input, and to ensure they are correctly updated across contexts. Finally, we also fix a few potential assertion messages to be less-specific to the original crypto usecases. Fixes: 950a81224e8b ("lib/crypto: tests: Add hash-test-template.h and gen-hash-testvecs.py") Signed-off-by: David Gow Link: https://lore.kernel.org/r/20251219085259.1163048-1-davidgow@google.com Signed-off-by: Eric Biggers Signed-off-by: Sasha Levin --- include/kunit/run-in-irq-context.h | 53 +++++++++++++++++++----------- 1 file changed, 33 insertions(+), 20 deletions(-) diff --git a/include/kunit/run-in-irq-context.h b/include/kunit/run-in-irq-context.h index 108e96433ea45b..c89b1b1b12dd58 100644 --- a/include/kunit/run-in-irq-context.h +++ b/include/kunit/run-in-irq-context.h @@ -20,8 +20,8 @@ struct kunit_irq_test_state { bool task_func_reported_failure; bool hardirq_func_reported_failure; bool softirq_func_reported_failure; - unsigned long hardirq_func_calls; - unsigned long softirq_func_calls; + atomic_t hardirq_func_calls; + atomic_t softirq_func_calls; struct hrtimer timer; struct work_struct bh_work; }; @@ -32,7 +32,7 @@ static enum hrtimer_restart kunit_irq_test_timer_func(struct hrtimer *timer) container_of(timer, typeof(*state), timer); WARN_ON_ONCE(!in_hardirq()); - state->hardirq_func_calls++; + atomic_inc(&state->hardirq_func_calls); if (!state->func(state->test_specific_state)) state->hardirq_func_reported_failure = true; @@ -48,7 +48,7 @@ static void kunit_irq_test_bh_work_func(struct work_struct *work) container_of(work, typeof(*state), bh_work); WARN_ON_ONCE(!in_serving_softirq()); - state->softirq_func_calls++; + atomic_inc(&state->softirq_func_calls); if (!state->func(state->test_specific_state)) state->softirq_func_reported_failure = true; @@ -59,7 +59,10 @@ static void kunit_irq_test_bh_work_func(struct work_struct *work) * hardirq context concurrently, and reports a failure to KUnit if any * invocation of @func in any context returns false. @func is passed * @test_specific_state as its argument. At most 3 invocations of @func will - * run concurrently: one in each of task, softirq, and hardirq context. + * run concurrently: one in each of task, softirq, and hardirq context. @func + * will continue running until either @max_iterations calls have been made (so + * long as at least one each runs in task, softirq, and hardirq contexts), or + * one second has passed. * * The main purpose of this interrupt context testing is to validate fallback * code paths that run in contexts where the normal code path cannot be used, @@ -85,6 +88,8 @@ static inline void kunit_run_irq_test(struct kunit *test, bool (*func)(void *), .test_specific_state = test_specific_state, }; unsigned long end_jiffies; + int hardirq_calls, softirq_calls; + bool allctx = false; /* * Set up a hrtimer (the way we access hardirq context) and a work @@ -94,14 +99,25 @@ static inline void kunit_run_irq_test(struct kunit *test, bool (*func)(void *), CLOCK_MONOTONIC, HRTIMER_MODE_REL_HARD); INIT_WORK_ONSTACK(&state.bh_work, kunit_irq_test_bh_work_func); - /* Run for up to max_iterations or 1 second, whichever comes first. */ + /* + * Run for up to max_iterations (including at least one task, softirq, + * and hardirq), or 1 second, whichever comes first. + */ end_jiffies = jiffies + HZ; hrtimer_start(&state.timer, KUNIT_IRQ_TEST_HRTIMER_INTERVAL, HRTIMER_MODE_REL_HARD); - for (int i = 0; i < max_iterations && !time_after(jiffies, end_jiffies); - i++) { + for (int task_calls = 0, calls = 0; + ((calls < max_iterations) || !allctx) && + !time_after(jiffies, end_jiffies); + task_calls++) { if (!func(test_specific_state)) state.task_func_reported_failure = true; + + hardirq_calls = atomic_read(&state.hardirq_func_calls); + softirq_calls = atomic_read(&state.softirq_func_calls); + calls = task_calls + hardirq_calls + softirq_calls; + allctx = (task_calls > 0) && (hardirq_calls > 0) && + (softirq_calls > 0); } /* Cancel the timer and work. */ @@ -109,21 +125,18 @@ static inline void kunit_run_irq_test(struct kunit *test, bool (*func)(void *), flush_work(&state.bh_work); /* Sanity check: the timer and BH functions should have been run. */ - KUNIT_EXPECT_GT_MSG(test, state.hardirq_func_calls, 0, + KUNIT_EXPECT_GT_MSG(test, atomic_read(&state.hardirq_func_calls), 0, "Timer function was not called"); - KUNIT_EXPECT_GT_MSG(test, state.softirq_func_calls, 0, + KUNIT_EXPECT_GT_MSG(test, atomic_read(&state.softirq_func_calls), 0, "BH work function was not called"); - /* Check for incorrect hash values reported from any context. */ - KUNIT_EXPECT_FALSE_MSG( - test, state.task_func_reported_failure, - "Incorrect hash values reported from task context"); - KUNIT_EXPECT_FALSE_MSG( - test, state.hardirq_func_reported_failure, - "Incorrect hash values reported from hardirq context"); - KUNIT_EXPECT_FALSE_MSG( - test, state.softirq_func_reported_failure, - "Incorrect hash values reported from softirq context"); + /* Check for failure reported from any context. */ + KUNIT_EXPECT_FALSE_MSG(test, state.task_func_reported_failure, + "Failure reported from task context"); + KUNIT_EXPECT_FALSE_MSG(test, state.hardirq_func_reported_failure, + "Failure reported from hardirq context"); + KUNIT_EXPECT_FALSE_MSG(test, state.softirq_func_reported_failure, + "Failure reported from softirq context"); } #endif /* _KUNIT_RUN_IN_IRQ_CONTEXT_H */ From e0321917c03ba1dcc6c755ee1067570d9f63e754 Mon Sep 17 00:00:00 2001 From: Kalesh AP Date: Tue, 23 Dec 2025 18:48:55 +0530 Subject: [PATCH 1835/3902] RDMA/bnxt_re: Fix to use correct page size for PDE table [ Upstream commit 3d70e0fb0f289b0c778041c5bb04d099e1aa7c1c ] In bnxt_qplib_alloc_init_hwq(), while allocating memory for PDE table driver incorrectly is using the "pg_size" value passed to the function. Fixed to use the right value 4K. Also, fixed the allocation size for PBL table. Fixes: 0c4dcd602817 ("RDMA/bnxt_re: Refactor hardware queue memory allocation") Signed-off-by: Damodharam Ammepalli Signed-off-by: Kalesh AP Link: https://patch.msgid.link/20251223131855.145955-1-kalesh-anakkur.purayil@broadcom.com Reviewed-by: Selvin Xavier Signed-off-by: Leon Romanovsky Signed-off-by: Sasha Levin --- drivers/infiniband/hw/bnxt_re/qplib_res.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/infiniband/hw/bnxt_re/qplib_res.c b/drivers/infiniband/hw/bnxt_re/qplib_res.c index 875d7b52c06ab9..d5c12a51aa4387 100644 --- a/drivers/infiniband/hw/bnxt_re/qplib_res.c +++ b/drivers/infiniband/hw/bnxt_re/qplib_res.c @@ -237,7 +237,7 @@ int bnxt_qplib_alloc_init_hwq(struct bnxt_qplib_hwq *hwq, if (npbl % BIT(MAX_PDL_LVL_SHIFT)) npde++; /* Alloc PDE pages */ - sginfo.pgsize = npde * pg_size; + sginfo.pgsize = npde * ROCE_PG_SIZE_4K; sginfo.npages = 1; rc = __alloc_pbl(res, &hwq->pbl[PBL_LVL_0], &sginfo); if (rc) @@ -245,7 +245,7 @@ int bnxt_qplib_alloc_init_hwq(struct bnxt_qplib_hwq *hwq, /* Alloc PBL pages */ sginfo.npages = npbl; - sginfo.pgsize = PAGE_SIZE; + sginfo.pgsize = ROCE_PG_SIZE_4K; rc = __alloc_pbl(res, &hwq->pbl[PBL_LVL_1], &sginfo); if (rc) goto fail; From b53635e0e42b08e8b6a8d3479449b8b9fc2cb618 Mon Sep 17 00:00:00 2001 From: Li Nan Date: Mon, 15 Dec 2025 20:44:12 +0800 Subject: [PATCH 1836/3902] md: Fix static checker warning in analyze_sbs [ Upstream commit 00f6c1b4d15d35fadb7f34768a1831c81aaa8936 ] The following warn is reported: drivers/md/md.c:3912 analyze_sbs() warn: iterator 'i' not incremented Fixes: d8730f0cf4ef ("md: Remove deprecated CONFIG_MD_MULTIPATH") Reported-by: Dan Carpenter Closes: https://lore.kernel.org/linux-raid/7e2e95ce-3740-09d8-a561-af6bfb767f18@huaweicloud.com/T/#t Signed-off-by: Li Nan Link: https://lore.kernel.org/linux-raid/20251215124412.4015572-1-linan666@huaweicloud.com Signed-off-by: Yu Kuai Signed-off-by: Sasha Levin --- drivers/md/md.c | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/drivers/md/md.c b/drivers/md/md.c index cef5b2954ac5aa..7b1365143f58d3 100644 --- a/drivers/md/md.c +++ b/drivers/md/md.c @@ -3874,7 +3874,6 @@ static struct md_rdev *md_import_device(dev_t newdev, int super_format, int supe static int analyze_sbs(struct mddev *mddev) { - int i; struct md_rdev *rdev, *freshest, *tmp; freshest = NULL; @@ -3901,11 +3900,9 @@ static int analyze_sbs(struct mddev *mddev) super_types[mddev->major_version]. validate_super(mddev, NULL/*freshest*/, freshest); - i = 0; rdev_for_each_safe(rdev, tmp, mddev) { if (mddev->max_disks && - (rdev->desc_nr >= mddev->max_disks || - i > mddev->max_disks)) { + rdev->desc_nr >= mddev->max_disks) { pr_warn("md: %s: %pg: only %d devices permitted\n", mdname(mddev), rdev->bdev, mddev->max_disks); From e5abb6af905de6b2fead8a0b3f32ab0b81468a01 Mon Sep 17 00:00:00 2001 From: Tuo Li Date: Thu, 25 Dec 2025 21:03:26 +0800 Subject: [PATCH 1837/3902] md/raid5: fix possible null-pointer dereferences in raid5_store_group_thread_cnt() [ Upstream commit 7ad6ef91d8745d04aff9cce7bdbc6320d8e05fe9 ] The variable mddev->private is first assigned to conf and then checked: conf = mddev->private; if (!conf) ... If conf is NULL, then mddev->private is also NULL. In this case, null-pointer dereferences can occur when calling raid5_quiesce(): raid5_quiesce(mddev, true); raid5_quiesce(mddev, false); since mddev->private is assigned to conf again in raid5_quiesce(), and conf is dereferenced in several places, for example: conf->quiesce = 0; wake_up(&conf->wait_for_quiescent); To fix this issue, the function should unlock mddev and return before invoking raid5_quiesce() when conf is NULL, following the existing pattern in raid5_change_consistency_policy(). Fixes: fa1944bbe622 ("md/raid5: Wait sync io to finish before changing group cnt") Signed-off-by: Tuo Li Reviewed-by: Xiao Ni Reviewed-by: Paul Menzel Link: https://lore.kernel.org/linux-raid/20251225130326.67780-1-islituo@gmail.com Signed-off-by: Yu Kuai Signed-off-by: Sasha Levin --- drivers/md/raid5.c | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/drivers/md/raid5.c b/drivers/md/raid5.c index 8b5f8a12d4179e..41de29206402ad 100644 --- a/drivers/md/raid5.c +++ b/drivers/md/raid5.c @@ -7187,12 +7187,14 @@ raid5_store_group_thread_cnt(struct mddev *mddev, const char *page, size_t len) err = mddev_suspend_and_lock(mddev); if (err) return err; + conf = mddev->private; + if (!conf) { + mddev_unlock_and_resume(mddev); + return -ENODEV; + } raid5_quiesce(mddev, true); - conf = mddev->private; - if (!conf) - err = -ENODEV; - else if (new != conf->worker_cnt_per_group) { + if (new != conf->worker_cnt_per_group) { old_groups = conf->worker_groups; if (old_groups) flush_workqueue(raid5_wq); From 92f024b8d47ac06621d4903e7ad29a62c5c962ca Mon Sep 17 00:00:00 2001 From: Ming Lei Date: Sat, 1 Nov 2025 21:31:17 +0800 Subject: [PATCH 1838/3902] ublk: implement NUMA-aware memory allocation [ Upstream commit 529d4d6327880e5c60f4e0def39b3faaa7954e54 ] Implement NUMA-friendly memory allocation for ublk driver to improve performance on multi-socket systems. This commit includes the following changes: 1. Rename __queues to queues, dropping the __ prefix since the field is now accessed directly throughout the codebase rather than only through the ublk_get_queue() helper. 2. Remove the queue_size field from struct ublk_device as it is no longer needed. 3. Move queue allocation and deallocation into ublk_init_queue() and ublk_deinit_queue() respectively, improving encapsulation. This simplifies ublk_init_queues() and ublk_deinit_queues() to just iterate and call the per-queue functions. 4. Add ublk_get_queue_numa_node() helper function to determine the appropriate NUMA node for a queue by finding the first CPU mapped to that queue via tag_set.map[HCTX_TYPE_DEFAULT].mq_map[] and converting it to a NUMA node using cpu_to_node(). This function is called internally by ublk_init_queue() to determine the allocation node. 5. Allocate each queue structure on its local NUMA node using kvzalloc_node() in ublk_init_queue(). 6. Allocate the I/O command buffer on the same NUMA node using alloc_pages_node(). This reduces memory access latency on multi-socket NUMA systems by ensuring each queue's data structures are local to the CPUs that access them. Reviewed-by: Caleb Sander Mateos Signed-off-by: Ming Lei Signed-off-by: Jens Axboe Stable-dep-of: 7fc4da6a304b ("ublk: scan partition in async way") Signed-off-by: Sasha Levin --- drivers/block/ublk_drv.c | 84 +++++++++++++++++++++++++--------------- 1 file changed, 53 insertions(+), 31 deletions(-) diff --git a/drivers/block/ublk_drv.c b/drivers/block/ublk_drv.c index d8079ea8f8ca12..796035891888c2 100644 --- a/drivers/block/ublk_drv.c +++ b/drivers/block/ublk_drv.c @@ -210,9 +210,6 @@ struct ublk_queue { struct ublk_device { struct gendisk *ub_disk; - char *__queues; - - unsigned int queue_size; struct ublksrv_ctrl_dev_info dev_info; struct blk_mq_tag_set tag_set; @@ -240,6 +237,8 @@ struct ublk_device { bool canceling; pid_t ublksrv_tgid; struct delayed_work exit_work; + + struct ublk_queue *queues[]; }; /* header of ublk_params */ @@ -782,7 +781,7 @@ static noinline void ublk_put_device(struct ublk_device *ub) static inline struct ublk_queue *ublk_get_queue(struct ublk_device *dev, int qid) { - return (struct ublk_queue *)&(dev->__queues[qid * dev->queue_size]); + return dev->queues[qid]; } static inline bool ublk_rq_has_data(const struct request *rq) @@ -2713,9 +2712,13 @@ static const struct file_operations ublk_ch_fops = { static void ublk_deinit_queue(struct ublk_device *ub, int q_id) { - int size = ublk_queue_cmd_buf_size(ub); - struct ublk_queue *ubq = ublk_get_queue(ub, q_id); - int i; + struct ublk_queue *ubq = ub->queues[q_id]; + int size, i; + + if (!ubq) + return; + + size = ublk_queue_cmd_buf_size(ub); for (i = 0; i < ubq->q_depth; i++) { struct ublk_io *io = &ubq->ios[i]; @@ -2727,57 +2730,76 @@ static void ublk_deinit_queue(struct ublk_device *ub, int q_id) if (ubq->io_cmd_buf) free_pages((unsigned long)ubq->io_cmd_buf, get_order(size)); + + kvfree(ubq); + ub->queues[q_id] = NULL; +} + +static int ublk_get_queue_numa_node(struct ublk_device *ub, int q_id) +{ + unsigned int cpu; + + /* Find first CPU mapped to this queue */ + for_each_possible_cpu(cpu) { + if (ub->tag_set.map[HCTX_TYPE_DEFAULT].mq_map[cpu] == q_id) + return cpu_to_node(cpu); + } + + return NUMA_NO_NODE; } static int ublk_init_queue(struct ublk_device *ub, int q_id) { - struct ublk_queue *ubq = ublk_get_queue(ub, q_id); + int depth = ub->dev_info.queue_depth; + int ubq_size = sizeof(struct ublk_queue) + depth * sizeof(struct ublk_io); gfp_t gfp_flags = GFP_KERNEL | __GFP_ZERO; - void *ptr; + struct ublk_queue *ubq; + struct page *page; + int numa_node; int size; + /* Determine NUMA node based on queue's CPU affinity */ + numa_node = ublk_get_queue_numa_node(ub, q_id); + + /* Allocate queue structure on local NUMA node */ + ubq = kvzalloc_node(ubq_size, GFP_KERNEL, numa_node); + if (!ubq) + return -ENOMEM; + spin_lock_init(&ubq->cancel_lock); ubq->flags = ub->dev_info.flags; ubq->q_id = q_id; - ubq->q_depth = ub->dev_info.queue_depth; + ubq->q_depth = depth; size = ublk_queue_cmd_buf_size(ub); - ptr = (void *) __get_free_pages(gfp_flags, get_order(size)); - if (!ptr) + /* Allocate I/O command buffer on local NUMA node */ + page = alloc_pages_node(numa_node, gfp_flags, get_order(size)); + if (!page) { + kvfree(ubq); return -ENOMEM; + } + ubq->io_cmd_buf = page_address(page); - ubq->io_cmd_buf = ptr; + ub->queues[q_id] = ubq; ubq->dev = ub; return 0; } static void ublk_deinit_queues(struct ublk_device *ub) { - int nr_queues = ub->dev_info.nr_hw_queues; int i; - if (!ub->__queues) - return; - - for (i = 0; i < nr_queues; i++) + for (i = 0; i < ub->dev_info.nr_hw_queues; i++) ublk_deinit_queue(ub, i); - kvfree(ub->__queues); } static int ublk_init_queues(struct ublk_device *ub) { - int nr_queues = ub->dev_info.nr_hw_queues; - int depth = ub->dev_info.queue_depth; - int ubq_size = sizeof(struct ublk_queue) + depth * sizeof(struct ublk_io); - int i, ret = -ENOMEM; + int i, ret; - ub->queue_size = ubq_size; - ub->__queues = kvcalloc(nr_queues, ubq_size, GFP_KERNEL); - if (!ub->__queues) - return ret; - - for (i = 0; i < nr_queues; i++) { - if (ublk_init_queue(ub, i)) + for (i = 0; i < ub->dev_info.nr_hw_queues; i++) { + ret = ublk_init_queue(ub, i); + if (ret) goto fail; } @@ -3179,7 +3201,7 @@ static int ublk_ctrl_add_dev(const struct ublksrv_ctrl_cmd *header) goto out_unlock; ret = -ENOMEM; - ub = kzalloc(sizeof(*ub), GFP_KERNEL); + ub = kzalloc(struct_size(ub, queues, info.nr_hw_queues), GFP_KERNEL); if (!ub) goto out_unlock; mutex_init(&ub->mutex); From 63dfbcd59b4b823eac4441efff10b1c303c8f49f Mon Sep 17 00:00:00 2001 From: Ming Lei Date: Tue, 23 Dec 2025 11:27:40 +0800 Subject: [PATCH 1839/3902] ublk: scan partition in async way [ Upstream commit 7fc4da6a304bdcd3de14fc946dc2c19437a9cc5a ] Implement async partition scan to avoid IO hang when reading partition tables. Similar to nvme_partition_scan_work(), partition scanning is deferred to a work queue to prevent deadlocks. When partition scan happens synchronously during add_disk(), IO errors can cause the partition scan to wait while holding ub->mutex, which can deadlock with other operations that need the mutex. Changes: - Add partition_scan_work to ublk_device structure - Implement ublk_partition_scan_work() to perform async scan - Always suppress sync partition scan during add_disk() - Schedule async work after add_disk() for trusted daemons - Add flush_work() in ublk_stop_dev() before grabbing ub->mutex Reviewed-by: Caleb Sander Mateos Reported-by: Yoav Cohen Closes: https://lore.kernel.org/linux-block/DM4PR12MB63280C5637917C071C2F0D65A9A8A@DM4PR12MB6328.namprd12.prod.outlook.com/ Fixes: 71f28f3136af ("ublk_drv: add io_uring based userspace block driver") Signed-off-by: Ming Lei Signed-off-by: Jens Axboe Signed-off-by: Sasha Levin --- drivers/block/ublk_drv.c | 35 ++++++++++++++++++++++++++++++++--- 1 file changed, 32 insertions(+), 3 deletions(-) diff --git a/drivers/block/ublk_drv.c b/drivers/block/ublk_drv.c index 796035891888c2..23aba73d24dc2d 100644 --- a/drivers/block/ublk_drv.c +++ b/drivers/block/ublk_drv.c @@ -237,6 +237,7 @@ struct ublk_device { bool canceling; pid_t ublksrv_tgid; struct delayed_work exit_work; + struct work_struct partition_scan_work; struct ublk_queue *queues[]; }; @@ -254,6 +255,20 @@ static inline struct request *__ublk_check_and_get_req(struct ublk_device *ub, u16 q_id, u16 tag, struct ublk_io *io, size_t offset); static inline unsigned int ublk_req_build_flags(struct request *req); +static void ublk_partition_scan_work(struct work_struct *work) +{ + struct ublk_device *ub = + container_of(work, struct ublk_device, partition_scan_work); + + if (WARN_ON_ONCE(!test_and_clear_bit(GD_SUPPRESS_PART_SCAN, + &ub->ub_disk->state))) + return; + + mutex_lock(&ub->ub_disk->open_mutex); + bdev_disk_changed(ub->ub_disk, false); + mutex_unlock(&ub->ub_disk->open_mutex); +} + static inline struct ublksrv_io_desc * ublk_get_iod(const struct ublk_queue *ubq, unsigned tag) { @@ -2092,6 +2107,7 @@ static void ublk_stop_dev(struct ublk_device *ub) mutex_lock(&ub->mutex); ublk_stop_dev_unlocked(ub); mutex_unlock(&ub->mutex); + flush_work(&ub->partition_scan_work); ublk_cancel_dev(ub); } @@ -3023,9 +3039,17 @@ static int ublk_ctrl_start_dev(struct ublk_device *ub, ublk_apply_params(ub); - /* don't probe partitions if any daemon task is un-trusted */ - if (ub->unprivileged_daemons) - set_bit(GD_SUPPRESS_PART_SCAN, &disk->state); + /* + * Suppress partition scan to avoid potential IO hang. + * + * If ublk server error occurs during partition scan, the IO may + * wait while holding ub->mutex, which can deadlock with other + * operations that need the mutex. Defer partition scan to async + * work. + * For unprivileged daemons, keep GD_SUPPRESS_PART_SCAN set + * permanently. + */ + set_bit(GD_SUPPRESS_PART_SCAN, &disk->state); ublk_get_device(ub); ub->dev_info.state = UBLK_S_DEV_LIVE; @@ -3042,6 +3066,10 @@ static int ublk_ctrl_start_dev(struct ublk_device *ub, set_bit(UB_STATE_USED, &ub->state); + /* Schedule async partition scan for trusted daemons */ + if (!ub->unprivileged_daemons) + schedule_work(&ub->partition_scan_work); + out_put_cdev: if (ret) { ublk_detach_disk(ub); @@ -3207,6 +3235,7 @@ static int ublk_ctrl_add_dev(const struct ublksrv_ctrl_cmd *header) mutex_init(&ub->mutex); spin_lock_init(&ub->lock); mutex_init(&ub->cancel_mutex); + INIT_WORK(&ub->partition_scan_work, ublk_partition_scan_work); ret = ublk_alloc_dev_number(ub, header->dev_id); if (ret < 0) From d260dff568ad6d0f0f2aef4a85058e0832d772c4 Mon Sep 17 00:00:00 2001 From: Jonathan Cavitt Date: Mon, 22 Dec 2025 20:19:59 +0000 Subject: [PATCH 1840/3902] drm/xe/guc: READ/WRITE_ONCE g2h_fence->done MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [ Upstream commit bed2a6bd20681aacfb063015c1edfab6f58a333e ] Use READ_ONCE and WRITE_ONCE when operating on g2h_fence->done to prevent the compiler from ignoring important modifications to its value. Fixes: dd08ebf6c352 ("drm/xe: Introduce a new DRM driver for Intel GPUs") Suggested-by: Matthew Brost Signed-off-by: Jonathan Cavitt Cc: Rodrigo Vivi Reviewed-by: Matthew Brost Link: https://patch.msgid.link/20251222201957.63245-5-jonathan.cavitt@intel.com Signed-off-by: Rodrigo Vivi (cherry picked from commit b5179dbd1c14743ae80f0aaa28eaaf35c361608f) Signed-off-by: Thomas Hellström Signed-off-by: Sasha Levin --- drivers/gpu/drm/xe/xe_guc_ct.c | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/drivers/gpu/drm/xe/xe_guc_ct.c b/drivers/gpu/drm/xe/xe_guc_ct.c index b7afe8e983cb79..3aac1a7aa2e7b2 100644 --- a/drivers/gpu/drm/xe/xe_guc_ct.c +++ b/drivers/gpu/drm/xe/xe_guc_ct.c @@ -106,7 +106,9 @@ static void g2h_fence_cancel(struct g2h_fence *g2h_fence) { g2h_fence->cancel = true; g2h_fence->fail = true; - g2h_fence->done = true; + + /* WRITE_ONCE pairs with READ_ONCEs in guc_ct_send_recv. */ + WRITE_ONCE(g2h_fence->done, true); } static bool g2h_fence_needs_alloc(struct g2h_fence *g2h_fence) @@ -1128,10 +1130,13 @@ static int guc_ct_send_recv(struct xe_guc_ct *ct, const u32 *action, u32 len, return ret; } - ret = wait_event_timeout(ct->g2h_fence_wq, g2h_fence.done, HZ); + /* READ_ONCEs pairs with WRITE_ONCEs in parse_g2h_response + * and g2h_fence_cancel. + */ + ret = wait_event_timeout(ct->g2h_fence_wq, READ_ONCE(g2h_fence.done), HZ); if (!ret) { LNL_FLUSH_WORK(&ct->g2h_worker); - if (g2h_fence.done) { + if (READ_ONCE(g2h_fence.done)) { xe_gt_warn(gt, "G2H fence %u, action %04x, done\n", g2h_fence.seqno, action[0]); ret = 1; @@ -1375,7 +1380,8 @@ static int parse_g2h_response(struct xe_guc_ct *ct, u32 *msg, u32 len) g2h_release_space(ct, GUC_CTB_HXG_MSG_MAX_LEN); - g2h_fence->done = true; + /* WRITE_ONCE pairs with READ_ONCEs in guc_ct_send_recv. */ + WRITE_ONCE(g2h_fence->done, true); smp_mb(); wake_up_all(&ct->g2h_fence_wq); From d026f47db68638521df8543535ef863814fb01b1 Mon Sep 17 00:00:00 2001 From: Zilin Guan Date: Wed, 24 Dec 2025 14:20:16 +0000 Subject: [PATCH 1841/3902] ksmbd: Fix memory leak in get_file_all_info() [ Upstream commit 0c56693b06a68476ba113db6347e7897475f9e4c ] In get_file_all_info(), if vfs_getattr() fails, the function returns immediately without freeing the allocated filename, leading to a memory leak. Fix this by freeing the filename before returning in this error case. Fixes: 5614c8c487f6a ("ksmbd: replace generic_fillattr with vfs_getattr") Signed-off-by: Zilin Guan Acked-by: Namjae Jeon Signed-off-by: Steve French Signed-off-by: Sasha Levin --- fs/smb/server/smb2pdu.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/fs/smb/server/smb2pdu.c b/fs/smb/server/smb2pdu.c index 6a94cda0927d7d..2b59c282cda595 100644 --- a/fs/smb/server/smb2pdu.c +++ b/fs/smb/server/smb2pdu.c @@ -4933,8 +4933,10 @@ static int get_file_all_info(struct ksmbd_work *work, ret = vfs_getattr(&fp->filp->f_path, &stat, STATX_BASIC_STATS, AT_STATX_SYNC_AS_STAT); - if (ret) + if (ret) { + kfree(filename); return ret; + } ksmbd_debug(SMB, "filename = %s\n", filename); delete_pending = ksmbd_inode_pending_delete(fp); From cf49ffdf57b6902edc14277659debc5fe28a2db1 Mon Sep 17 00:00:00 2001 From: Li Zhijian Date: Fri, 26 Dec 2025 17:41:12 +0800 Subject: [PATCH 1842/3902] IB/rxe: Fix missing umem_odp->umem_mutex unlock on error path [ Upstream commit 3c68cf68233e556e0102f45b69f7448908dc1f44 ] rxe_odp_map_range_and_lock() must release umem_odp->umem_mutex when an error occurs, including cases where rxe_check_pagefault() fails. Fixes: 2fae67ab63db ("RDMA/rxe: Add support for Send/Recv/Write/Read with ODP") Signed-off-by: Li Zhijian Link: https://patch.msgid.link/20251226094112.3042583-1-lizhijian@fujitsu.com Reviewed-by: Zhu Yanjun Signed-off-by: Leon Romanovsky Signed-off-by: Sasha Levin --- drivers/infiniband/sw/rxe/rxe_odp.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/drivers/infiniband/sw/rxe/rxe_odp.c b/drivers/infiniband/sw/rxe/rxe_odp.c index f58e3ec6252f52..4d4e3b324dd294 100644 --- a/drivers/infiniband/sw/rxe/rxe_odp.c +++ b/drivers/infiniband/sw/rxe/rxe_odp.c @@ -179,8 +179,10 @@ static int rxe_odp_map_range_and_lock(struct rxe_mr *mr, u64 iova, int length, u return err; need_fault = rxe_check_pagefault(umem_odp, iova, length); - if (need_fault) + if (need_fault) { + mutex_unlock(&umem_odp->umem_mutex); return -EFAULT; + } } return 0; From e4ee11c00d2396991958dbffb430833bfaa451c8 Mon Sep 17 00:00:00 2001 From: Honggang LI Date: Mon, 29 Dec 2025 10:56:17 +0800 Subject: [PATCH 1843/3902] RDMA/rtrs: Fix clt_path::max_pages_per_mr calculation [ Upstream commit 43bd09d5b750f700499ae8ec45fd41a4c48673e6 ] If device max_mr_size bits in the range [mr_page_shift+31:mr_page_shift] are zero, the `min3` function will set clt_path::max_pages_per_mr to zero. `alloc_path_reqs` will pass zero, which is invalid, as the third parameter to `ib_alloc_mr`. Fixes: 6a98d71daea1 ("RDMA/rtrs: client: main functionality") Signed-off-by: Honggang LI Link: https://patch.msgid.link/20251229025617.13241-1-honggangli@163.com Signed-off-by: Leon Romanovsky Signed-off-by: Sasha Levin --- drivers/infiniband/ulp/rtrs/rtrs-clt.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/infiniband/ulp/rtrs/rtrs-clt.c b/drivers/infiniband/ulp/rtrs/rtrs-clt.c index 71387811b28158..2b397a544cb936 100644 --- a/drivers/infiniband/ulp/rtrs/rtrs-clt.c +++ b/drivers/infiniband/ulp/rtrs/rtrs-clt.c @@ -1464,6 +1464,7 @@ static void query_fast_reg_mode(struct rtrs_clt_path *clt_path) mr_page_shift = max(12, ffs(ib_dev->attrs.page_size_cap) - 1); max_pages_per_mr = ib_dev->attrs.max_mr_size; do_div(max_pages_per_mr, (1ull << mr_page_shift)); + max_pages_per_mr = min_not_zero((u32)max_pages_per_mr, U32_MAX); clt_path->max_pages_per_mr = min3(clt_path->max_pages_per_mr, (u32)max_pages_per_mr, ib_dev->attrs.max_fast_reg_page_list_len); From 4f6e92b7c2a7952eb23a119104a42964f4c6562b Mon Sep 17 00:00:00 2001 From: Thomas Fourier Date: Tue, 30 Dec 2025 09:51:21 +0100 Subject: [PATCH 1844/3902] RDMA/bnxt_re: fix dma_free_coherent() pointer [ Upstream commit fcd431a9627f272b4c0bec445eba365fe2232a94 ] The dma_alloc_coherent() allocates a dma-mapped buffer, pbl->pg_arr[i]. The dma_free_coherent() should pass the same buffer to dma_free_coherent() and not page-aligned. Fixes: 1ac5a4047975 ("RDMA/bnxt_re: Add bnxt_re RoCE driver") Signed-off-by: Thomas Fourier Link: https://patch.msgid.link/20251230085121.8023-2-fourier.thomas@gmail.com Signed-off-by: Leon Romanovsky Signed-off-by: Sasha Levin --- drivers/infiniband/hw/bnxt_re/qplib_res.c | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/drivers/infiniband/hw/bnxt_re/qplib_res.c b/drivers/infiniband/hw/bnxt_re/qplib_res.c index d5c12a51aa4387..4d674a3aee1aa3 100644 --- a/drivers/infiniband/hw/bnxt_re/qplib_res.c +++ b/drivers/infiniband/hw/bnxt_re/qplib_res.c @@ -64,9 +64,7 @@ static void __free_pbl(struct bnxt_qplib_res *res, struct bnxt_qplib_pbl *pbl, for (i = 0; i < pbl->pg_count; i++) { if (pbl->pg_arr[i]) dma_free_coherent(&pdev->dev, pbl->pg_size, - (void *)((unsigned long) - pbl->pg_arr[i] & - PAGE_MASK), + pbl->pg_arr[i], pbl->pg_map_arr[i]); else dev_warn(&pdev->dev, From 7d8a5b44b9f2403f874a158498022f936fe7765f Mon Sep 17 00:00:00 2001 From: Cong Zhang Date: Tue, 30 Dec 2025 17:17:05 +0800 Subject: [PATCH 1845/3902] blk-mq: skip CPU offline notify on unmapped hctx [ Upstream commit 10845a105bbcb030647a729f1716c2309da71d33 ] If an hctx has no software ctx mapped, blk_mq_map_swqueue() never allocates tags and leaves hctx->tags NULL. The CPU hotplug offline notifier can still run for that hctx, return early since hctx cannot hold any requests. Signed-off-by: Cong Zhang Fixes: bf0beec0607d ("blk-mq: drain I/O when all CPUs in a hctx are offline") Reviewed-by: Ming Lei Signed-off-by: Jens Axboe Signed-off-by: Sasha Levin --- block/blk-mq.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/block/blk-mq.c b/block/blk-mq.c index ea5f948af7a42d..a03f52ab87d64d 100644 --- a/block/blk-mq.c +++ b/block/blk-mq.c @@ -3710,7 +3710,7 @@ static int blk_mq_hctx_notify_offline(unsigned int cpu, struct hlist_node *node) struct blk_mq_hw_ctx, cpuhp_online); int ret = 0; - if (blk_mq_hctx_has_online_cpu(hctx, cpu)) + if (!hctx->nr_ctx || blk_mq_hctx_has_online_cpu(hctx, cpu)) return 0; /* From caf7a6e95854af9bec0b87f5927f0bbb019c8298 Mon Sep 17 00:00:00 2001 From: Yipeng Zou Date: Fri, 18 Aug 2023 09:32:26 +0800 Subject: [PATCH 1846/3902] selftests/ftrace: traceonoff_triggers: strip off names [ Upstream commit b889b4fb4cbea3ca7eb9814075d6a51936394bd9 ] The func_traceonoff_triggers.tc sometimes goes to fail on my board, Kunpeng-920. [root@localhost]# ./ftracetest ./test.d/ftrace/func_traceonoff_triggers.tc -l fail.log === Ftrace unit tests === [1] ftrace - test for function traceon/off triggers [FAIL] [2] (instance) ftrace - test for function traceon/off triggers [UNSUPPORTED] I look up the log, and it shows that the md5sum is different between csum1 and csum2. ++ cnt=611 ++ sleep .1 +++ cnt_trace +++ grep -v '^#' trace +++ wc -l ++ cnt2=611 ++ '[' 611 -ne 611 ']' +++ cat tracing_on ++ on=0 ++ '[' 0 '!=' 0 ']' +++ md5sum trace ++ csum1='76896aa74362fff66a6a5f3cf8a8a500 trace' ++ sleep .1 +++ md5sum trace ++ csum2='ee8625a21c058818fc26e45c1ed3f6de trace' ++ '[' '76896aa74362fff66a6a5f3cf8a8a500 trace' '!=' 'ee8625a21c058818fc26e45c1ed3f6de trace' ']' ++ fail 'Tracing file is still changing' ++ echo Tracing file is still changing Tracing file is still changing ++ exit_fail ++ exit 1 So I directly dump the trace file before md5sum, the diff shows that: [root@localhost]# diff trace_1.log trace_2.log -y --suppress-common-lines dockerd-12285 [036] d.... 18385.510290: sched_stat | <...>-12285 [036] d.... 18385.510290: sched_stat dockerd-12285 [036] d.... 18385.510291: sched_swit | <...>-12285 [036] d.... 18385.510291: sched_swit <...>-740 [044] d.... 18385.602859: sched_stat | kworker/44:1-740 [044] d.... 18385.602859: sched_stat <...>-740 [044] d.... 18385.602860: sched_swit | kworker/44:1-740 [044] d.... 18385.602860: sched_swit And we can see that <...> filed be filled with names. We can strip off the names there to fix that. After strip off the names: kworker/u257:0-12 [019] d..2. 2528.758910: sched_stat | -12 [019] d..2. 2528.758910: sched_stat_runtime: comm=k kworker/u257:0-12 [019] d..2. 2528.758912: sched_swit | -12 [019] d..2. 2528.758912: sched_switch: prev_comm=kw -0 [000] d.s5. 2528.762318: sched_waki | -0 [000] d.s5. 2528.762318: sched_waking: comm=sshd pi -0 [037] dNh2. 2528.762326: sched_wake | -0 [037] dNh2. 2528.762326: sched_wakeup: comm=sshd pi -0 [037] d..2. 2528.762334: sched_swit | -0 [037] d..2. 2528.762334: sched_switch: prev_comm=sw Link: https://lore.kernel.org/r/20230818013226.2182299-1-zouyipeng@huawei.com Fixes: d87b29179aa0 ("selftests: ftrace: Use md5sum to take less time of checking logs") Suggested-by: Steven Rostedt (Google) Signed-off-by: Yipeng Zou Acked-by: Masami Hiramatsu (Google) Reviewed-by: Steven Rostedt (Google) Signed-off-by: Shuah Khan Signed-off-by: Sasha Levin --- .../ftrace/test.d/ftrace/func_traceonoff_triggers.tc | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/tools/testing/selftests/ftrace/test.d/ftrace/func_traceonoff_triggers.tc b/tools/testing/selftests/ftrace/test.d/ftrace/func_traceonoff_triggers.tc index aee22289536b1b..1b57771dbfdf07 100644 --- a/tools/testing/selftests/ftrace/test.d/ftrace/func_traceonoff_triggers.tc +++ b/tools/testing/selftests/ftrace/test.d/ftrace/func_traceonoff_triggers.tc @@ -90,9 +90,10 @@ if [ $on != "0" ]; then fail "Tracing is not off" fi -csum1=`md5sum trace` +# Cannot rely on names being around as they are only cached, strip them +csum1=`cat trace | sed -e 's/^ *[^ ]*\(-[0-9][0-9]*\)/\1/' | md5sum` sleep $SLEEP_TIME -csum2=`md5sum trace` +csum2=`cat trace | sed -e 's/^ *[^ ]*\(-[0-9][0-9]*\)/\1/' | md5sum` if [ "$csum1" != "$csum2" ]; then fail "Tracing file is still changing" From 7c87afd13484f1399cd7a7ac4d9c1e611d0f222f Mon Sep 17 00:00:00 2001 From: Damien Le Moal Date: Wed, 5 Nov 2025 06:22:35 +0900 Subject: [PATCH 1847/3902] block: handle zone management operations completions commit efae226c2ef19528ffd81d29ba0eecf1b0896ca2 upstream. The functions blk_zone_wplug_handle_reset_or_finish() and blk_zone_wplug_handle_reset_all() both modify the zone write pointer offset of zone write plugs that are the target of a reset, reset all or finish zone management operation. However, these functions do this modification before the BIO is executed. So if the zone operation fails, the modified zone write pointer offsets become invalid. Avoid this by modifying the zone write pointer offset of a zone write plug that is the target of a zone management operation when the operation completes. To do so, modify blk_zone_bio_endio() to call the new function blk_zone_mgmt_bio_endio() which in turn calls the functions blk_zone_reset_all_bio_endio(), blk_zone_reset_bio_endio() or blk_zone_finish_bio_endio() depending on the operation of the completed BIO, to modify a zone write plug write pointer offset accordingly. These functions are called only if the BIO execution was successful. Fixes: dd291d77cc90 ("block: Introduce zone write plugging") Cc: stable@vger.kernel.org Signed-off-by: Damien Le Moal Reviewed-by: Christoph Hellwig Reviewed-by: Johannes Thumshirn Reviewed-by: Chaitanya Kulkarni Reviewed-by: Hannes Reinecke Reviewed-by: Martin K. Petersen Signed-off-by: Jens Axboe Signed-off-by: Greg Kroah-Hartman --- block/blk-zoned.c | 139 ++++++++++++++++++++++++++++++---------------- block/blk.h | 14 +++++ 2 files changed, 104 insertions(+), 49 deletions(-) diff --git a/block/blk-zoned.c b/block/blk-zoned.c index f60af668ab81e8..39381f2b2e9471 100644 --- a/block/blk-zoned.c +++ b/block/blk-zoned.c @@ -71,6 +71,11 @@ struct blk_zone_wplug { struct gendisk *disk; }; +static inline unsigned int disk_zone_wplugs_hash_size(struct gendisk *disk) +{ + return 1U << disk->zone_wplugs_hash_bits; +} + /* * Zone write plug flags bits: * - BLK_ZONE_WPLUG_PLUGGED: Indicates that the zone write plug is plugged, @@ -698,71 +703,91 @@ static int disk_zone_sync_wp_offset(struct gendisk *disk, sector_t sector) disk_report_zones_cb, &args); } -static bool blk_zone_wplug_handle_reset_or_finish(struct bio *bio, - unsigned int wp_offset) +static void blk_zone_reset_bio_endio(struct bio *bio) { struct gendisk *disk = bio->bi_bdev->bd_disk; - sector_t sector = bio->bi_iter.bi_sector; struct blk_zone_wplug *zwplug; - unsigned long flags; - - /* Conventional zones cannot be reset nor finished. */ - if (!bdev_zone_is_seq(bio->bi_bdev, sector)) { - bio_io_error(bio); - return true; - } - - /* - * No-wait reset or finish BIOs do not make much sense as the callers - * issue these as blocking operations in most cases. To avoid issues - * the BIO execution potentially failing with BLK_STS_AGAIN, warn about - * REQ_NOWAIT being set and ignore that flag. - */ - if (WARN_ON_ONCE(bio->bi_opf & REQ_NOWAIT)) - bio->bi_opf &= ~REQ_NOWAIT; /* - * If we have a zone write plug, set its write pointer offset to 0 - * (reset case) or to the zone size (finish case). This will abort all - * BIOs plugged for the target zone. It is fine as resetting or - * finishing zones while writes are still in-flight will result in the + * If we have a zone write plug, set its write pointer offset to 0. + * This will abort all BIOs plugged for the target zone. It is fine as + * resetting zones while writes are still in-flight will result in the * writes failing anyway. */ - zwplug = disk_get_zone_wplug(disk, sector); + zwplug = disk_get_zone_wplug(disk, bio->bi_iter.bi_sector); if (zwplug) { + unsigned long flags; + spin_lock_irqsave(&zwplug->lock, flags); - disk_zone_wplug_set_wp_offset(disk, zwplug, wp_offset); + disk_zone_wplug_set_wp_offset(disk, zwplug, 0); spin_unlock_irqrestore(&zwplug->lock, flags); disk_put_zone_wplug(zwplug); } - - return false; } -static bool blk_zone_wplug_handle_reset_all(struct bio *bio) +static void blk_zone_reset_all_bio_endio(struct bio *bio) { struct gendisk *disk = bio->bi_bdev->bd_disk; struct blk_zone_wplug *zwplug; unsigned long flags; - sector_t sector; + unsigned int i; - /* - * Set the write pointer offset of all zone write plugs to 0. This will - * abort all plugged BIOs. It is fine as resetting zones while writes - * are still in-flight will result in the writes failing anyway. - */ - for (sector = 0; sector < get_capacity(disk); - sector += disk->queue->limits.chunk_sectors) { - zwplug = disk_get_zone_wplug(disk, sector); - if (zwplug) { + /* Update the condition of all zone write plugs. */ + rcu_read_lock(); + for (i = 0; i < disk_zone_wplugs_hash_size(disk); i++) { + hlist_for_each_entry_rcu(zwplug, &disk->zone_wplugs_hash[i], + node) { spin_lock_irqsave(&zwplug->lock, flags); disk_zone_wplug_set_wp_offset(disk, zwplug, 0); spin_unlock_irqrestore(&zwplug->lock, flags); - disk_put_zone_wplug(zwplug); } } + rcu_read_unlock(); +} - return false; +static void blk_zone_finish_bio_endio(struct bio *bio) +{ + struct block_device *bdev = bio->bi_bdev; + struct gendisk *disk = bdev->bd_disk; + struct blk_zone_wplug *zwplug; + + /* + * If we have a zone write plug, set its write pointer offset to the + * zone size. This will abort all BIOs plugged for the target zone. It + * is fine as resetting zones while writes are still in-flight will + * result in the writes failing anyway. + */ + zwplug = disk_get_zone_wplug(disk, bio->bi_iter.bi_sector); + if (zwplug) { + unsigned long flags; + + spin_lock_irqsave(&zwplug->lock, flags); + disk_zone_wplug_set_wp_offset(disk, zwplug, + bdev_zone_sectors(bdev)); + spin_unlock_irqrestore(&zwplug->lock, flags); + disk_put_zone_wplug(zwplug); + } +} + +void blk_zone_mgmt_bio_endio(struct bio *bio) +{ + /* If the BIO failed, we have nothing to do. */ + if (bio->bi_status != BLK_STS_OK) + return; + + switch (bio_op(bio)) { + case REQ_OP_ZONE_RESET: + blk_zone_reset_bio_endio(bio); + return; + case REQ_OP_ZONE_RESET_ALL: + blk_zone_reset_all_bio_endio(bio); + return; + case REQ_OP_ZONE_FINISH: + blk_zone_finish_bio_endio(bio); + return; + default: + return; + } } static void disk_zone_wplug_schedule_bio_work(struct gendisk *disk, @@ -1106,6 +1131,30 @@ static void blk_zone_wplug_handle_native_zone_append(struct bio *bio) disk_put_zone_wplug(zwplug); } +static bool blk_zone_wplug_handle_zone_mgmt(struct bio *bio) +{ + if (bio_op(bio) != REQ_OP_ZONE_RESET_ALL && + !bdev_zone_is_seq(bio->bi_bdev, bio->bi_iter.bi_sector)) { + /* + * Zone reset and zone finish operations do not apply to + * conventional zones. + */ + bio_io_error(bio); + return true; + } + + /* + * No-wait zone management BIOs do not make much sense as the callers + * issue these as blocking operations in most cases. To avoid issues + * with the BIO execution potentially failing with BLK_STS_AGAIN, warn + * about REQ_NOWAIT being set and ignore that flag. + */ + if (WARN_ON_ONCE(bio->bi_opf & REQ_NOWAIT)) + bio->bi_opf &= ~REQ_NOWAIT; + + return false; +} + /** * blk_zone_plug_bio - Handle a zone write BIO with zone write plugging * @bio: The BIO being submitted @@ -1153,12 +1202,9 @@ bool blk_zone_plug_bio(struct bio *bio, unsigned int nr_segs) case REQ_OP_WRITE_ZEROES: return blk_zone_wplug_handle_write(bio, nr_segs); case REQ_OP_ZONE_RESET: - return blk_zone_wplug_handle_reset_or_finish(bio, 0); case REQ_OP_ZONE_FINISH: - return blk_zone_wplug_handle_reset_or_finish(bio, - bdev_zone_sectors(bdev)); case REQ_OP_ZONE_RESET_ALL: - return blk_zone_wplug_handle_reset_all(bio); + return blk_zone_wplug_handle_zone_mgmt(bio); default: return false; } @@ -1332,11 +1378,6 @@ static void blk_zone_wplug_bio_work(struct work_struct *work) disk_put_zone_wplug(zwplug); } -static inline unsigned int disk_zone_wplugs_hash_size(struct gendisk *disk) -{ - return 1U << disk->zone_wplugs_hash_bits; -} - void disk_init_zone_resources(struct gendisk *disk) { spin_lock_init(&disk->zone_wplugs_lock); diff --git a/block/blk.h b/block/blk.h index a7992680f9e144..37b9b6a95c11c3 100644 --- a/block/blk.h +++ b/block/blk.h @@ -488,9 +488,23 @@ static inline bool blk_req_bio_is_zone_append(struct request *rq, void blk_zone_write_plug_bio_merged(struct bio *bio); void blk_zone_write_plug_init_request(struct request *rq); void blk_zone_append_update_request_bio(struct request *rq, struct bio *bio); +void blk_zone_mgmt_bio_endio(struct bio *bio); void blk_zone_write_plug_bio_endio(struct bio *bio); static inline void blk_zone_bio_endio(struct bio *bio) { + /* + * Zone management BIOs may impact zone write plugs (e.g. a zone reset + * changes a zone write plug zone write pointer offset), but these + * operation do not go through zone write plugging as they may operate + * on zones that do not have a zone write + * plug. blk_zone_mgmt_bio_endio() handles the potential changes to zone + * write plugs that are present. + */ + if (op_is_zone_mgmt(bio_op(bio))) { + blk_zone_mgmt_bio_endio(bio); + return; + } + /* * For write BIOs to zoned devices, signal the completion of the BIO so * that the next write BIO can be submitted by zone write plugging. From 791bc3890d6fa56c9de803d601402919d189687f Mon Sep 17 00:00:00 2001 From: "Matthew Wilcox (Oracle)" Date: Fri, 18 Jul 2025 20:53:58 +0100 Subject: [PATCH 1848/3902] ntfs: Do not overwrite uptodate pages commit 68f6bd128e75a032432eda9d16676ed2969a1096 upstream. When reading a compressed file, we may read several pages in addition to the one requested. The current code will overwrite pages in the page cache with the data from disc which can definitely result in changes that have been made being lost. For example if we have four consecutie pages ABCD in the file compressed into a single extent, on first access, we'll bring in ABCD. Then we write to page B. Memory pressure results in the eviction of ACD. When we attempt to write to page C, we will overwrite the data in page B with the data currently on disk. I haven't investigated the decompression code to check whether it's OK to overwrite a clean page or whether it might be possible to see corrupt data. Out of an abundance of caution, decline to overwrite uptodate pages, not just dirty pages. Fixes: 4342306f0f0d (fs/ntfs3: Add file operations and implementation) Signed-off-by: Matthew Wilcox (Oracle) Cc: stable@vger.kernel.org Signed-off-by: Konstantin Komarov Signed-off-by: Greg Kroah-Hartman --- fs/ntfs3/frecord.c | 35 +++++++++++++++++++++++++++++------ 1 file changed, 29 insertions(+), 6 deletions(-) diff --git a/fs/ntfs3/frecord.c b/fs/ntfs3/frecord.c index e5a005d216f316..295f6936a3e588 100644 --- a/fs/ntfs3/frecord.c +++ b/fs/ntfs3/frecord.c @@ -2022,6 +2022,29 @@ int ni_fiemap(struct ntfs_inode *ni, struct fiemap_extent_info *fieinfo, return err; } +static struct page *ntfs_lock_new_page(struct address_space *mapping, + pgoff_t index, gfp_t gfp) +{ + struct folio *folio = __filemap_get_folio(mapping, index, + FGP_LOCK | FGP_ACCESSED | FGP_CREAT, gfp); + struct page *page; + + if (IS_ERR(folio)) + return ERR_CAST(folio); + + if (!folio_test_uptodate(folio)) + return folio_file_page(folio, index); + + /* Use a temporary page to avoid data corruption */ + folio_unlock(folio); + folio_put(folio); + page = alloc_page(gfp); + if (!page) + return ERR_PTR(-ENOMEM); + __SetPageLocked(page); + return page; +} + /* * ni_readpage_cmpr * @@ -2076,9 +2099,9 @@ int ni_readpage_cmpr(struct ntfs_inode *ni, struct folio *folio) if (i == idx) continue; - pg = find_or_create_page(mapping, index, gfp_mask); - if (!pg) { - err = -ENOMEM; + pg = ntfs_lock_new_page(mapping, index, gfp_mask); + if (IS_ERR(pg)) { + err = PTR_ERR(pg); goto out1; } pages[i] = pg; @@ -2177,13 +2200,13 @@ int ni_decompress_file(struct ntfs_inode *ni) for (i = 0; i < pages_per_frame; i++, index++) { struct page *pg; - pg = find_or_create_page(mapping, index, gfp_mask); - if (!pg) { + pg = ntfs_lock_new_page(mapping, index, gfp_mask); + if (IS_ERR(pg)) { while (i--) { unlock_page(pages[i]); put_page(pages[i]); } - err = -ENOMEM; + err = PTR_ERR(pg); goto out; } pages[i] = pg; From 042169940f6adf0ca966ba3bef3ad2ebe361f72b Mon Sep 17 00:00:00 2001 From: Johan Hovold Date: Thu, 27 Nov 2025 14:50:57 +0100 Subject: [PATCH 1849/3902] ASoC: codecs: wcd939x: fix regmap leak on probe failure commit 86dc090f737953f16f8dc60c546ae7854690d4f6 upstream. The soundwire regmap that may be allocated during probe is not freed on late probe failures. Add the missing error handling. Fixes: be2af391cea0 ("ASoC: codecs: Add WCD939x Soundwire devices driver") Cc: stable@vger.kernel.org # 6.9 Cc: Neil Armstrong Signed-off-by: Johan Hovold Link: https://patch.msgid.link/20251127135057.2216-1-johan@kernel.org Signed-off-by: Mark Brown Signed-off-by: Greg Kroah-Hartman --- sound/soc/codecs/wcd939x-sdw.c | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/sound/soc/codecs/wcd939x-sdw.c b/sound/soc/codecs/wcd939x-sdw.c index d369100a245742..da342a0c95a52b 100644 --- a/sound/soc/codecs/wcd939x-sdw.c +++ b/sound/soc/codecs/wcd939x-sdw.c @@ -1400,12 +1400,18 @@ static int wcd9390_probe(struct sdw_slave *pdev, const struct sdw_device_id *id) ret = component_add(dev, &wcd_sdw_component_ops); if (ret) - return ret; + goto err_free_regmap; /* Set suspended until aggregate device is bind */ pm_runtime_set_suspended(dev); return 0; + +err_free_regmap: + if (wcd->regmap) + regmap_exit(wcd->regmap); + + return ret; } static int wcd9390_remove(struct sdw_slave *pdev) From 88baed15284e4dd177373494777aac709b23b95d Mon Sep 17 00:00:00 2001 From: Johan Hovold Date: Mon, 24 Nov 2025 11:49:05 +0100 Subject: [PATCH 1850/3902] ASoC: stm32: sai: fix device leak on probe commit e26ff429eaf10c4ef1bc3dabd9bf27eb54b7e1f4 upstream. Make sure to drop the reference taken when looking up the sync provider device and its driver data during DAI probe on probe failures and on unbind. Note that holding a reference to a device does not prevent its driver data from going away so there is no point in keeping the reference. Fixes: 7dd0d835582f ("ASoC: stm32: sai: simplify sync modes management") Fixes: 1c3816a19487 ("ASoC: stm32: sai: add missing put_device()") Cc: stable@vger.kernel.org # 4.16: 1c3816a19487 Cc: olivier moysan Cc: Wen Yang Signed-off-by: Johan Hovold Reviewed-by: olivier moysan Link: https://patch.msgid.link/20251124104908.15754-2-johan@kernel.org Signed-off-by: Mark Brown Signed-off-by: Greg Kroah-Hartman --- sound/soc/stm/stm32_sai.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sound/soc/stm/stm32_sai.c b/sound/soc/stm/stm32_sai.c index fa821e3fb427e3..7065aeb0e524a1 100644 --- a/sound/soc/stm/stm32_sai.c +++ b/sound/soc/stm/stm32_sai.c @@ -143,6 +143,7 @@ static int stm32_sai_set_sync(struct stm32_sai_data *sai_client, } sai_provider = platform_get_drvdata(pdev); + put_device(&pdev->dev); if (!sai_provider) { dev_err(&sai_client->pdev->dev, "SAI sync provider data not found\n"); @@ -159,7 +160,6 @@ static int stm32_sai_set_sync(struct stm32_sai_data *sai_client, ret = stm32_sai_sync_conf_provider(sai_provider, synco); error: - put_device(&pdev->dev); of_node_put(np_provider); return ret; } From 88636450c4add16f3a63149606aa75603ea5a981 Mon Sep 17 00:00:00 2001 From: Johan Hovold Date: Mon, 24 Nov 2025 11:49:06 +0100 Subject: [PATCH 1851/3902] ASoC: stm32: sai: fix clk prepare imbalance on probe failure commit 312ec2f0d9d1a5656f76d770bbf1d967e9289aa7 upstream. Make sure to unprepare the parent clock also on probe failures (e.g. probe deferral). Fixes: a14bf98c045b ("ASoC: stm32: sai: fix possible circular locking") Cc: stable@vger.kernel.org # 5.5 Cc: Olivier Moysan Signed-off-by: Johan Hovold Reviewed-by: olivier moysan Link: https://patch.msgid.link/20251124104908.15754-3-johan@kernel.org Signed-off-by: Mark Brown Signed-off-by: Greg Kroah-Hartman --- sound/soc/stm/stm32_sai_sub.c | 28 +++++++++++++++++++++------- 1 file changed, 21 insertions(+), 7 deletions(-) diff --git a/sound/soc/stm/stm32_sai_sub.c b/sound/soc/stm/stm32_sai_sub.c index 0ae1eae2a59e2f..7a005b4ad30486 100644 --- a/sound/soc/stm/stm32_sai_sub.c +++ b/sound/soc/stm/stm32_sai_sub.c @@ -1634,14 +1634,21 @@ static int stm32_sai_sub_parse_of(struct platform_device *pdev, if (of_property_present(np, "#clock-cells")) { ret = stm32_sai_add_mclk_provider(sai); if (ret < 0) - return ret; + goto err_unprepare_pclk; } else { sai->sai_mclk = devm_clk_get_optional(&pdev->dev, "MCLK"); - if (IS_ERR(sai->sai_mclk)) - return PTR_ERR(sai->sai_mclk); + if (IS_ERR(sai->sai_mclk)) { + ret = PTR_ERR(sai->sai_mclk); + goto err_unprepare_pclk; + } } return 0; + +err_unprepare_pclk: + clk_unprepare(sai->pdata->pclk); + + return ret; } static int stm32_sai_sub_probe(struct platform_device *pdev) @@ -1688,26 +1695,33 @@ static int stm32_sai_sub_probe(struct platform_device *pdev) IRQF_SHARED, dev_name(&pdev->dev), sai); if (ret) { dev_err(&pdev->dev, "IRQ request returned %d\n", ret); - return ret; + goto err_unprepare_pclk; } if (STM_SAI_PROTOCOL_IS_SPDIF(sai)) conf = &stm32_sai_pcm_config_spdif; ret = snd_dmaengine_pcm_register(&pdev->dev, conf, 0); - if (ret) - return dev_err_probe(&pdev->dev, ret, "Could not register pcm dma\n"); + if (ret) { + ret = dev_err_probe(&pdev->dev, ret, "Could not register pcm dma\n"); + goto err_unprepare_pclk; + } ret = snd_soc_register_component(&pdev->dev, &stm32_component, &sai->cpu_dai_drv, 1); if (ret) { snd_dmaengine_pcm_unregister(&pdev->dev); - return ret; + goto err_unprepare_pclk; } pm_runtime_enable(&pdev->dev); return 0; + +err_unprepare_pclk: + clk_unprepare(sai->pdata->pclk); + + return ret; } static void stm32_sai_sub_remove(struct platform_device *pdev) From 3752afcc6d80d5525e236e329895ba2cb93bcb26 Mon Sep 17 00:00:00 2001 From: Johan Hovold Date: Mon, 24 Nov 2025 11:49:07 +0100 Subject: [PATCH 1852/3902] ASoC: stm32: sai: fix OF node leak on probe commit 23261f0de09427367e99f39f588e31e2856a690e upstream. The reference taken to the sync provider OF node when probing the platform device is currently only dropped if the set_sync() callback fails during DAI probe. Make sure to drop the reference on platform probe failures (e.g. probe deferral) and on driver unbind. This also avoids a potential use-after-free in case the DAI is ever reprobed without first rebinding the platform driver. Fixes: 5914d285f6b7 ("ASoC: stm32: sai: Add synchronization support") Fixes: d4180b4c02e7 ("ASoC: stm32: sai: fix set_sync service") Cc: Olivier Moysan Cc: stable@vger.kernel.org # 4.16: d4180b4c02e7 Signed-off-by: Johan Hovold Reviewed-by: olivier moysan Link: https://patch.msgid.link/20251124104908.15754-4-johan@kernel.org Signed-off-by: Mark Brown Signed-off-by: Greg Kroah-Hartman --- sound/soc/stm/stm32_sai.c | 12 +++--------- sound/soc/stm/stm32_sai_sub.c | 23 ++++++++++++++++------- 2 files changed, 19 insertions(+), 16 deletions(-) diff --git a/sound/soc/stm/stm32_sai.c b/sound/soc/stm/stm32_sai.c index 7065aeb0e524a1..00cf24ceca2d4a 100644 --- a/sound/soc/stm/stm32_sai.c +++ b/sound/soc/stm/stm32_sai.c @@ -138,7 +138,6 @@ static int stm32_sai_set_sync(struct stm32_sai_data *sai_client, if (!pdev) { dev_err(&sai_client->pdev->dev, "Device not found for node %pOFn\n", np_provider); - of_node_put(np_provider); return -ENODEV; } @@ -147,21 +146,16 @@ static int stm32_sai_set_sync(struct stm32_sai_data *sai_client, if (!sai_provider) { dev_err(&sai_client->pdev->dev, "SAI sync provider data not found\n"); - ret = -EINVAL; - goto error; + return -EINVAL; } /* Configure sync client */ ret = stm32_sai_sync_conf_client(sai_client, synci); if (ret < 0) - goto error; + return ret; /* Configure sync provider */ - ret = stm32_sai_sync_conf_provider(sai_provider, synco); - -error: - of_node_put(np_provider); - return ret; + return stm32_sai_sync_conf_provider(sai_provider, synco); } static int stm32_sai_get_parent_clk(struct stm32_sai_data *sai) diff --git a/sound/soc/stm/stm32_sai_sub.c b/sound/soc/stm/stm32_sai_sub.c index 7a005b4ad30486..5ae4d2577f28b7 100644 --- a/sound/soc/stm/stm32_sai_sub.c +++ b/sound/soc/stm/stm32_sai_sub.c @@ -1586,7 +1586,8 @@ static int stm32_sai_sub_parse_of(struct platform_device *pdev, dev_err(&pdev->dev, "External synchro not supported\n"); of_node_put(args.np); - return -EINVAL; + ret = -EINVAL; + goto err_put_sync_provider; } sai->sync = SAI_SYNC_EXTERNAL; @@ -1595,7 +1596,8 @@ static int stm32_sai_sub_parse_of(struct platform_device *pdev, (sai->synci > (SAI_GCR_SYNCIN_MAX + 1))) { dev_err(&pdev->dev, "Wrong SAI index\n"); of_node_put(args.np); - return -EINVAL; + ret = -EINVAL; + goto err_put_sync_provider; } if (of_property_match_string(args.np, "compatible", @@ -1609,7 +1611,8 @@ static int stm32_sai_sub_parse_of(struct platform_device *pdev, if (!sai->synco) { dev_err(&pdev->dev, "Unknown SAI sub-block\n"); of_node_put(args.np); - return -EINVAL; + ret = -EINVAL; + goto err_put_sync_provider; } } @@ -1619,13 +1622,15 @@ static int stm32_sai_sub_parse_of(struct platform_device *pdev, of_node_put(args.np); sai->sai_ck = devm_clk_get(&pdev->dev, "sai_ck"); - if (IS_ERR(sai->sai_ck)) - return dev_err_probe(&pdev->dev, PTR_ERR(sai->sai_ck), - "Missing kernel clock sai_ck\n"); + if (IS_ERR(sai->sai_ck)) { + ret = dev_err_probe(&pdev->dev, PTR_ERR(sai->sai_ck), + "Missing kernel clock sai_ck\n"); + goto err_put_sync_provider; + } ret = clk_prepare(sai->pdata->pclk); if (ret < 0) - return ret; + goto err_put_sync_provider; if (STM_SAI_IS_F4(sai->pdata)) return 0; @@ -1647,6 +1652,8 @@ static int stm32_sai_sub_parse_of(struct platform_device *pdev, err_unprepare_pclk: clk_unprepare(sai->pdata->pclk); +err_put_sync_provider: + of_node_put(sai->np_sync_provider); return ret; } @@ -1720,6 +1727,7 @@ static int stm32_sai_sub_probe(struct platform_device *pdev) err_unprepare_pclk: clk_unprepare(sai->pdata->pclk); + of_node_put(sai->np_sync_provider); return ret; } @@ -1732,6 +1740,7 @@ static void stm32_sai_sub_remove(struct platform_device *pdev) snd_dmaengine_pcm_unregister(&pdev->dev); snd_soc_unregister_component(&pdev->dev); pm_runtime_disable(&pdev->dev); + of_node_put(sai->np_sync_provider); } static int stm32_sai_sub_suspend(struct device *dev) From 4d2ae0495c75a97fa6e09cb6d61a162f7b5345ba Mon Sep 17 00:00:00 2001 From: Biju Das Date: Fri, 14 Nov 2025 07:37:05 +0000 Subject: [PATCH 1853/3902] ASoC: renesas: rz-ssi: Fix channel swap issue in full duplex mode commit 52a525011cb8e293799a085436f026f2958403f9 upstream. The full duplex audio starts with half duplex mode and then switch to full duplex mode (another FIFO reset) when both playback/capture streams available leading to random audio left/right channel swap issue. Fix this channel swap issue by detecting the full duplex condition by populating struct dup variable in startup() callback and synchronize starting both the play and capture at the same time in rz_ssi_start(). Cc: stable@kernel.org Fixes: 4f8cd05a4305 ("ASoC: sh: rz-ssi: Add full duplex support") Co-developed-by: Tony Tang Signed-off-by: Tony Tang Reviewed-by: Kuninori Morimoto Signed-off-by: Biju Das Link: https://patch.msgid.link/20251114073709.4376-2-biju.das.jz@bp.renesas.com Signed-off-by: Mark Brown Signed-off-by: Greg Kroah-Hartman --- sound/soc/renesas/rz-ssi.c | 51 ++++++++++++++++++++++++++++++++------ 1 file changed, 43 insertions(+), 8 deletions(-) diff --git a/sound/soc/renesas/rz-ssi.c b/sound/soc/renesas/rz-ssi.c index 81b883e8ac92aa..62d3222c510f82 100644 --- a/sound/soc/renesas/rz-ssi.c +++ b/sound/soc/renesas/rz-ssi.c @@ -133,6 +133,12 @@ struct rz_ssi_priv { bool bckp_rise; /* Bit clock polarity (SSICR.BCKP) */ bool dma_rt; + struct { + bool tx_active; + bool rx_active; + bool one_stream_triggered; + } dup; + /* Full duplex communication support */ struct { unsigned int rate; @@ -332,13 +338,12 @@ static int rz_ssi_start(struct rz_ssi_priv *ssi, struct rz_ssi_stream *strm) bool is_full_duplex; u32 ssicr, ssifcr; - is_full_duplex = rz_ssi_is_stream_running(&ssi->playback) || - rz_ssi_is_stream_running(&ssi->capture); + is_full_duplex = ssi->dup.tx_active && ssi->dup.rx_active; ssicr = rz_ssi_reg_readl(ssi, SSICR); ssifcr = rz_ssi_reg_readl(ssi, SSIFCR); if (!is_full_duplex) { ssifcr &= ~0xF; - } else { + } else if (ssi->dup.one_stream_triggered) { rz_ssi_reg_mask_setl(ssi, SSICR, SSICR_TEN | SSICR_REN, 0); rz_ssi_set_idle(ssi); ssifcr &= ~SSIFCR_FIFO_RST; @@ -374,12 +379,16 @@ static int rz_ssi_start(struct rz_ssi_priv *ssi, struct rz_ssi_stream *strm) SSISR_RUIRQ), 0); strm->running = 1; - if (is_full_duplex) - ssicr |= SSICR_TEN | SSICR_REN; - else + if (!is_full_duplex) { ssicr |= is_play ? SSICR_TEN : SSICR_REN; - - rz_ssi_reg_writel(ssi, SSICR, ssicr); + rz_ssi_reg_writel(ssi, SSICR, ssicr); + } else if (ssi->dup.one_stream_triggered) { + ssicr |= SSICR_TEN | SSICR_REN; + rz_ssi_reg_writel(ssi, SSICR, ssicr); + ssi->dup.one_stream_triggered = false; + } else { + ssi->dup.one_stream_triggered = true; + } return 0; } @@ -915,6 +924,30 @@ static int rz_ssi_dai_set_fmt(struct snd_soc_dai *dai, unsigned int fmt) return 0; } +static int rz_ssi_startup(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct rz_ssi_priv *ssi = snd_soc_dai_get_drvdata(dai); + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + ssi->dup.tx_active = true; + else + ssi->dup.rx_active = true; + + return 0; +} + +static void rz_ssi_shutdown(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct rz_ssi_priv *ssi = snd_soc_dai_get_drvdata(dai); + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + ssi->dup.tx_active = false; + else + ssi->dup.rx_active = false; +} + static bool rz_ssi_is_valid_hw_params(struct rz_ssi_priv *ssi, unsigned int rate, unsigned int channels, unsigned int sample_width, @@ -985,6 +1018,8 @@ static int rz_ssi_dai_hw_params(struct snd_pcm_substream *substream, } static const struct snd_soc_dai_ops rz_ssi_dai_ops = { + .startup = rz_ssi_startup, + .shutdown = rz_ssi_shutdown, .trigger = rz_ssi_dai_trigger, .set_fmt = rz_ssi_dai_set_fmt, .hw_params = rz_ssi_dai_hw_params, From 21d87fde367a50464fa312e197c74f0e3387cae6 Mon Sep 17 00:00:00 2001 From: Biju Das Date: Fri, 14 Nov 2025 07:37:06 +0000 Subject: [PATCH 1854/3902] ASoC: renesas: rz-ssi: Fix rz_ssi_priv::hw_params_cache::sample_width commit 2bae7beda19f3b2dc6ab2062c94df19c27923712 upstream. The strm->sample_width is not filled during rz_ssi_dai_hw_params(). This wrong value is used for caching sample_width in struct hw_params_cache. Fix this issue by replacing 'strm->sample_width'->'params_width(params)' in rz_ssi_dai_hw_params(). After this drop the variable sample_width from struct rz_ssi_stream as it is unused. Cc: stable@kernel.org Fixes: 4f8cd05a4305 ("ASoC: sh: rz-ssi: Add full duplex support") Reviewed-by: Kuninori Morimoto Signed-off-by: Biju Das Link: https://patch.msgid.link/20251114073709.4376-3-biju.das.jz@bp.renesas.com Signed-off-by: Mark Brown Signed-off-by: Greg Kroah-Hartman --- sound/soc/renesas/rz-ssi.c | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/sound/soc/renesas/rz-ssi.c b/sound/soc/renesas/rz-ssi.c index 62d3222c510f82..f4dc2f68deadac 100644 --- a/sound/soc/renesas/rz-ssi.c +++ b/sound/soc/renesas/rz-ssi.c @@ -13,6 +13,7 @@ #include #include #include +#include #include /* REGISTER OFFSET */ @@ -87,7 +88,6 @@ struct rz_ssi_stream { int dma_buffer_pos; /* The address for the next DMA descriptor */ int completed_dma_buf_pos; /* The address of the last completed DMA descriptor. */ int period_counter; /* for keeping track of periods transferred */ - int sample_width; int buffer_pos; /* current frame position in the buffer */ int running; /* 0=stopped, 1=running */ @@ -217,10 +217,7 @@ static inline bool rz_ssi_is_stream_running(struct rz_ssi_stream *strm) static void rz_ssi_stream_init(struct rz_ssi_stream *strm, struct snd_pcm_substream *substream) { - struct snd_pcm_runtime *runtime = substream->runtime; - rz_ssi_set_substream(strm, substream); - strm->sample_width = samples_to_bytes(runtime, 1); strm->dma_buffer_pos = 0; strm->completed_dma_buf_pos = 0; strm->period_counter = 0; @@ -978,9 +975,9 @@ static int rz_ssi_dai_hw_params(struct snd_pcm_substream *substream, struct snd_soc_dai *dai) { struct rz_ssi_priv *ssi = snd_soc_dai_get_drvdata(dai); - struct rz_ssi_stream *strm = rz_ssi_stream_get(ssi, substream); unsigned int sample_bits = hw_param_interval(params, SNDRV_PCM_HW_PARAM_SAMPLE_BITS)->min; + unsigned int sample_width = params_width(params); unsigned int channels = params_channels(params); unsigned int rate = params_rate(params); int ret; @@ -999,16 +996,14 @@ static int rz_ssi_dai_hw_params(struct snd_pcm_substream *substream, if (rz_ssi_is_stream_running(&ssi->playback) || rz_ssi_is_stream_running(&ssi->capture)) { - if (rz_ssi_is_valid_hw_params(ssi, rate, channels, - strm->sample_width, sample_bits)) + if (rz_ssi_is_valid_hw_params(ssi, rate, channels, sample_width, sample_bits)) return 0; dev_err(ssi->dev, "Full duplex needs same HW params\n"); return -EINVAL; } - rz_ssi_cache_hw_params(ssi, rate, channels, strm->sample_width, - sample_bits); + rz_ssi_cache_hw_params(ssi, rate, channels, sample_width, sample_bits); ret = rz_ssi_swreset(ssi); if (ret) From 15424b48e226e4d9876d39433670c729338eef2b Mon Sep 17 00:00:00 2001 From: Ma Ke Date: Sun, 16 Nov 2025 14:16:23 +0800 Subject: [PATCH 1855/3902] ASoC: codecs: wcd937x: Fix error handling in wcd937x codec driver commit 578ccfe344c5f421c2c6343b872995b397ffd3ff upstream. In wcd937x_bind(), the driver calls of_sdw_find_device_by_node() to obtain references to RX and TX SoundWire devices, which increment the device reference counts. However, the corresponding put_device() are missing in both the error paths and the normal unbind path in wcd937x_unbind(). Add proper error handling with put_device() calls in all error paths of wcd937x_bind() and ensure devices are released in wcd937x_unbind(). Found by code review. Cc: stable@vger.kernel.org Fixes: 772ed12bd04e ("ASoC: codecs: wcdxxxx: use of_sdw_find_device_by_node helper") Signed-off-by: Ma Ke Reviewed-by: David Heidelberg Link: https://patch.msgid.link/20251116061623.11830-1-make24@iscas.ac.cn Signed-off-by: Mark Brown Signed-off-by: Greg Kroah-Hartman --- sound/soc/codecs/wcd937x.c | 43 ++++++++++++++++++++++++++++++-------- 1 file changed, 34 insertions(+), 9 deletions(-) diff --git a/sound/soc/codecs/wcd937x.c b/sound/soc/codecs/wcd937x.c index 421ec7a2d6bdc8..ed0ff45a89649b 100644 --- a/sound/soc/codecs/wcd937x.c +++ b/sound/soc/codecs/wcd937x.c @@ -2748,7 +2748,8 @@ static int wcd937x_bind(struct device *dev) wcd937x->rxdev = of_sdw_find_device_by_node(wcd937x->rxnode); if (!wcd937x->rxdev) { dev_err(dev, "could not find slave with matching of node\n"); - return -EINVAL; + ret = -EINVAL; + goto err_component_unbind; } wcd937x->sdw_priv[AIF1_PB] = dev_get_drvdata(wcd937x->rxdev); @@ -2757,7 +2758,8 @@ static int wcd937x_bind(struct device *dev) wcd937x->txdev = of_sdw_find_device_by_node(wcd937x->txnode); if (!wcd937x->txdev) { dev_err(dev, "could not find txslave with matching of node\n"); - return -EINVAL; + ret = -EINVAL; + goto err_put_rxdev; } wcd937x->sdw_priv[AIF1_CAP] = dev_get_drvdata(wcd937x->txdev); @@ -2765,7 +2767,8 @@ static int wcd937x_bind(struct device *dev) wcd937x->tx_sdw_dev = dev_to_sdw_dev(wcd937x->txdev); if (!wcd937x->tx_sdw_dev) { dev_err(dev, "could not get txslave with matching of dev\n"); - return -EINVAL; + ret = -EINVAL; + goto err_put_txdev; } /* @@ -2775,31 +2778,35 @@ static int wcd937x_bind(struct device *dev) if (!device_link_add(wcd937x->rxdev, wcd937x->txdev, DL_FLAG_STATELESS | DL_FLAG_PM_RUNTIME)) { dev_err(dev, "Could not devlink TX and RX\n"); - return -EINVAL; + ret = -EINVAL; + goto err_put_txdev; } if (!device_link_add(dev, wcd937x->txdev, DL_FLAG_STATELESS | DL_FLAG_PM_RUNTIME)) { dev_err(dev, "Could not devlink WCD and TX\n"); - return -EINVAL; + ret = -EINVAL; + goto err_remove_link1; } if (!device_link_add(dev, wcd937x->rxdev, DL_FLAG_STATELESS | DL_FLAG_PM_RUNTIME)) { dev_err(dev, "Could not devlink WCD and RX\n"); - return -EINVAL; + ret = -EINVAL; + goto err_remove_link2; } wcd937x->regmap = wcd937x->sdw_priv[AIF1_CAP]->regmap; if (!wcd937x->regmap) { dev_err(dev, "could not get TX device regmap\n"); - return -EINVAL; + ret = -EINVAL; + goto err_remove_link3; } ret = wcd937x_irq_init(wcd937x, dev); if (ret) { dev_err(dev, "IRQ init failed: %d\n", ret); - return ret; + goto err_remove_link3; } wcd937x->sdw_priv[AIF1_PB]->slave_irq = wcd937x->virq; @@ -2809,10 +2816,26 @@ static int wcd937x_bind(struct device *dev) ret = snd_soc_register_component(dev, &soc_codec_dev_wcd937x, wcd937x_dais, ARRAY_SIZE(wcd937x_dais)); - if (ret) + if (ret) { dev_err(dev, "Codec registration failed\n"); + goto err_remove_link3; + } return ret; + +err_remove_link3: + device_link_remove(dev, wcd937x->rxdev); +err_remove_link2: + device_link_remove(dev, wcd937x->txdev); +err_remove_link1: + device_link_remove(wcd937x->rxdev, wcd937x->txdev); +err_put_txdev: + put_device(wcd937x->txdev); +err_put_rxdev: + put_device(wcd937x->rxdev); +err_component_unbind: + component_unbind_all(dev, wcd937x); + return ret; } static void wcd937x_unbind(struct device *dev) @@ -2825,6 +2848,8 @@ static void wcd937x_unbind(struct device *dev) device_link_remove(wcd937x->rxdev, wcd937x->txdev); component_unbind_all(dev, wcd937x); mutex_destroy(&wcd937x->micb_lock); + put_device(wcd937x->txdev); + put_device(wcd937x->rxdev); } static const struct component_master_ops wcd937x_comp_ops = { From 28bcaff809cb8d836e77929bb9d7ab41d80f7271 Mon Sep 17 00:00:00 2001 From: Krzysztof Kozlowski Date: Thu, 23 Oct 2025 11:02:50 +0200 Subject: [PATCH 1856/3902] ASoC: codecs: pm4125: Fix potential conflict when probing two devices commit fd94857a934cbe613353810a024c84d54826ead3 upstream. Qualcomm PM4125 codec is always a single device on the board, however nothing stops board designers to have two of them, thus same device driver could probe twice. Device driver is not ready for that case, because it allocates statically 'struct regmap_irq_chip' as non-const and stores during component bind in 'irq_drv_data' member a pointer to per-probe state container ('struct pm4125_priv'). Second component bind would overwrite the 'irq_drv_data' from previous device probe, so interrupts would be executed in wrong context. The fix makes use of currently unused 'struct pm4125_priv' member 'pm4125_regmap_irq_chip', but renames it to a shorter name. Fixes: 8ad529484937 ("ASoC: codecs: add new pm4125 audio codec driver") Cc: stable@vger.kernel.org Signed-off-by: Krzysztof Kozlowski Link: https://patch.msgid.link/20251023-asoc-regmap-irq-chip-v1-1-17ad32680913@linaro.org Signed-off-by: Mark Brown Signed-off-by: Greg Kroah-Hartman --- sound/soc/codecs/pm4125.c | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/sound/soc/codecs/pm4125.c b/sound/soc/codecs/pm4125.c index 706fc668ffe2a3..1d3a019b1b4f22 100644 --- a/sound/soc/codecs/pm4125.c +++ b/sound/soc/codecs/pm4125.c @@ -70,7 +70,7 @@ struct pm4125_priv { struct wcd_mbhc_config mbhc_cfg; struct wcd_mbhc_intr intr_ids; struct irq_domain *virq; - const struct regmap_irq_chip *pm4125_regmap_irq_chip; + const struct regmap_irq_chip *chip_desc; struct regmap_irq_chip_data *irq_chip; struct snd_soc_jack *jack; unsigned long status_mask; @@ -179,7 +179,7 @@ static const u32 pm4125_config_regs[] = { PM4125_DIG_SWR_INTR_LEVEL_0, }; -static struct regmap_irq_chip pm4125_regmap_irq_chip = { +static const struct regmap_irq_chip pm4125_regmap_irq_chip = { .name = "pm4125", .irqs = pm4125_irqs, .num_irqs = ARRAY_SIZE(pm4125_irqs), @@ -1320,10 +1320,8 @@ static int pm4125_irq_init(struct pm4125_priv *pm4125, struct device *dev) return -EINVAL; } - pm4125_regmap_irq_chip.irq_drv_data = pm4125; - return devm_regmap_add_irq_chip(dev, pm4125->regmap, irq_create_mapping(pm4125->virq, 0), - IRQF_ONESHOT, 0, &pm4125_regmap_irq_chip, + IRQF_ONESHOT, 0, pm4125->chip_desc, &pm4125->irq_chip); } @@ -1695,6 +1693,7 @@ static int pm4125_probe(struct platform_device *pdev) { struct component_match *match = NULL; struct device *dev = &pdev->dev; + struct regmap_irq_chip *chip_desc; struct pm4125_priv *pm4125; struct wcd_mbhc_config *cfg; int ret; @@ -1705,6 +1704,14 @@ static int pm4125_probe(struct platform_device *pdev) dev_set_drvdata(dev, pm4125); + chip_desc = devm_kmemdup(dev, &pm4125_regmap_irq_chip, + sizeof(pm4125_regmap_irq_chip), + GFP_KERNEL); + if (!chip_desc) + return -ENOMEM; + chip_desc->irq_drv_data = pm4125; + pm4125->chip_desc = chip_desc; + ret = devm_regulator_bulk_get_enable(dev, ARRAY_SIZE(pm4125_power_supplies), pm4125_power_supplies); if (ret) From 341902036870705629d9c9d7784ca4079c8c4a85 Mon Sep 17 00:00:00 2001 From: Krzysztof Kozlowski Date: Thu, 23 Oct 2025 11:02:51 +0200 Subject: [PATCH 1857/3902] ASoC: codecs: pm4125: Remove irq_chip on component unbind commit e65b871c9b5af9265aefc5b8cd34993586d93aab upstream. Component bind uses devm_regmap_add_irq_chip() to add IRQ chip, so it will be removed only during driver unbind, not component unbind. A component unbind-bind cycle for the same Linux device lifetime would result in two chips added. Fix this by manually removing the IRQ chip during component unbind. Fixes: 8ad529484937 ("ASoC: codecs: add new pm4125 audio codec driver") Cc: stable@vger.kernel.org Signed-off-by: Krzysztof Kozlowski Link: https://patch.msgid.link/20251023-asoc-regmap-irq-chip-v1-2-17ad32680913@linaro.org Signed-off-by: Mark Brown Signed-off-by: Greg Kroah-Hartman --- sound/soc/codecs/pm4125.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/sound/soc/codecs/pm4125.c b/sound/soc/codecs/pm4125.c index 1d3a019b1b4f22..3657a4a759855d 100644 --- a/sound/soc/codecs/pm4125.c +++ b/sound/soc/codecs/pm4125.c @@ -1658,6 +1658,8 @@ static void pm4125_unbind(struct device *dev) struct pm4125_priv *pm4125 = dev_get_drvdata(dev); snd_soc_unregister_component(dev); + devm_regmap_del_irq_chip(dev, irq_find_mapping(pm4125->virq, 0), + pm4125->irq_chip); device_link_remove(dev, pm4125->txdev); device_link_remove(dev, pm4125->rxdev); device_link_remove(pm4125->rxdev, pm4125->txdev); From a619ebcf3e977630f3831b83e98c6945eade8348 Mon Sep 17 00:00:00 2001 From: Srinivas Kandagatla Date: Fri, 31 Oct 2025 12:06:58 +0000 Subject: [PATCH 1858/3902] ASoC: codecs: lpass-tx-macro: fix SM6115 support commit 7c63b5a8ed972a2c8c03d984f6a43349007cea93 upstream. SM6115 does have soundwire controller in tx. For some reason we ended up with this incorrect patch. Fix this by adding the flag to reflect this in SoC data. Fixes: 510c46884299 ("ASoC: codecs: lpass-tx-macro: Add SM6115 support") Cc: Stable@vger.kernel.org Signed-off-by: Srinivas Kandagatla Link: https://patch.msgid.link/20251031120703.590201-2-srinivas.kandagatla@oss.qualcomm.com Signed-off-by: Mark Brown Signed-off-by: Greg Kroah-Hartman --- sound/soc/codecs/lpass-tx-macro.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/sound/soc/codecs/lpass-tx-macro.c b/sound/soc/codecs/lpass-tx-macro.c index 1da34cb3505f35..c7d4dc553e6aad 100644 --- a/sound/soc/codecs/lpass-tx-macro.c +++ b/sound/soc/codecs/lpass-tx-macro.c @@ -2473,7 +2473,8 @@ static const struct tx_macro_data lpass_ver_9_2 = { }; static const struct tx_macro_data lpass_ver_10_sm6115 = { - .flags = LPASS_MACRO_FLAG_HAS_NPL_CLOCK, + .flags = LPASS_MACRO_FLAG_HAS_NPL_CLOCK | + LPASS_MACRO_FLAG_RESET_SWR, .ver = LPASS_VER_10_0_0, .extra_widgets = tx_macro_dapm_widgets_v9_2, .extra_widgets_num = ARRAY_SIZE(tx_macro_dapm_widgets_v9_2), From b79b8613c0c4606edc08b93ae7ad1a82df131f8f Mon Sep 17 00:00:00 2001 From: Srinivas Kandagatla Date: Wed, 22 Oct 2025 15:33:46 +0100 Subject: [PATCH 1859/3902] ASoC: qcom: sdw: fix memory leak for sdw_stream_runtime commit bcba17279327c6e85dee6a97014dc642e2dc93cc upstream. For some reason we endedup allocating sdw_stream_runtime for every cpu dai, this has two issues. 1. we never set snd_soc_dai_set_stream for non soundwire dai, which means there is no way that we can free this, resulting in memory leak 2. startup and shutdown callbacks can be called without hw_params callback called. This combination results in memory leak because machine driver sruntime array pointer is only set in hw_params callback. Fix this by 1. adding a helper function to get sdw_runtime for substream which can be used by shutdown callback to get hold of sruntime to free. 2. only allocate sdw_runtime for soundwire dais. Fixes: d32bac9cb09c ("ASoC: qcom: Add helper for allocating Soundwire stream runtime") Cc: Krzysztof Kozlowski Cc: Stable@vger.kernel.org Signed-off-by: Srinivas Kandagatla Tested-by: Steev Klimaszewski # Thinkpad X13s Link: https://patch.msgid.link/20251022143349.1081513-2-srinivas.kandagatla@oss.qualcomm.com Signed-off-by: Mark Brown Signed-off-by: Greg Kroah-Hartman --- sound/soc/qcom/sc7280.c | 2 +- sound/soc/qcom/sc8280xp.c | 2 +- sound/soc/qcom/sdw.c | 105 +++++++++++++++++++++----------------- sound/soc/qcom/sdw.h | 1 + sound/soc/qcom/sm8250.c | 2 +- sound/soc/qcom/x1e80100.c | 2 +- 6 files changed, 64 insertions(+), 50 deletions(-) diff --git a/sound/soc/qcom/sc7280.c b/sound/soc/qcom/sc7280.c index af412bd0c89ff5..c444dae563c75d 100644 --- a/sound/soc/qcom/sc7280.c +++ b/sound/soc/qcom/sc7280.c @@ -317,7 +317,7 @@ static void sc7280_snd_shutdown(struct snd_pcm_substream *substream) struct snd_soc_card *card = rtd->card; struct sc7280_snd_data *data = snd_soc_card_get_drvdata(card); struct snd_soc_dai *cpu_dai = snd_soc_rtd_to_cpu(rtd, 0); - struct sdw_stream_runtime *sruntime = data->sruntime[cpu_dai->id]; + struct sdw_stream_runtime *sruntime = qcom_snd_sdw_get_stream(substream); switch (cpu_dai->id) { case MI2S_PRIMARY: diff --git a/sound/soc/qcom/sc8280xp.c b/sound/soc/qcom/sc8280xp.c index 187f37ffe32837..ed8b04c6022e9f 100644 --- a/sound/soc/qcom/sc8280xp.c +++ b/sound/soc/qcom/sc8280xp.c @@ -73,7 +73,7 @@ static void sc8280xp_snd_shutdown(struct snd_pcm_substream *substream) struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream); struct snd_soc_dai *cpu_dai = snd_soc_rtd_to_cpu(rtd, 0); struct sc8280xp_snd_data *pdata = snd_soc_card_get_drvdata(rtd->card); - struct sdw_stream_runtime *sruntime = pdata->sruntime[cpu_dai->id]; + struct sdw_stream_runtime *sruntime = qcom_snd_sdw_get_stream(substream); pdata->sruntime[cpu_dai->id] = NULL; sdw_release_stream(sruntime); diff --git a/sound/soc/qcom/sdw.c b/sound/soc/qcom/sdw.c index 7d7981d4295b48..7b2cae92c81296 100644 --- a/sound/soc/qcom/sdw.c +++ b/sound/soc/qcom/sdw.c @@ -7,6 +7,37 @@ #include #include "sdw.h" +static bool qcom_snd_is_sdw_dai(int id) +{ + switch (id) { + case WSA_CODEC_DMA_RX_0: + case WSA_CODEC_DMA_TX_0: + case WSA_CODEC_DMA_RX_1: + case WSA_CODEC_DMA_TX_1: + case WSA_CODEC_DMA_TX_2: + case RX_CODEC_DMA_RX_0: + case TX_CODEC_DMA_TX_0: + case RX_CODEC_DMA_RX_1: + case TX_CODEC_DMA_TX_1: + case RX_CODEC_DMA_RX_2: + case TX_CODEC_DMA_TX_2: + case RX_CODEC_DMA_RX_3: + case TX_CODEC_DMA_TX_3: + case RX_CODEC_DMA_RX_4: + case TX_CODEC_DMA_TX_4: + case RX_CODEC_DMA_RX_5: + case TX_CODEC_DMA_TX_5: + case RX_CODEC_DMA_RX_6: + case RX_CODEC_DMA_RX_7: + case SLIMBUS_0_RX...SLIMBUS_6_TX: + return true; + default: + break; + } + + return false; +} + /** * qcom_snd_sdw_startup() - Helper to start Soundwire stream for SoC audio card * @substream: The PCM substream from audio, as passed to snd_soc_ops->startup() @@ -29,6 +60,9 @@ int qcom_snd_sdw_startup(struct snd_pcm_substream *substream) u32 rx_ch_cnt = 0, tx_ch_cnt = 0; int ret, i, j; + if (!qcom_snd_is_sdw_dai(cpu_dai->id)) + return 0; + sruntime = sdw_alloc_stream(cpu_dai->name, SDW_STREAM_PCM); if (!sruntime) return -ENOMEM; @@ -89,19 +123,8 @@ int qcom_snd_sdw_prepare(struct snd_pcm_substream *substream, if (!sruntime) return 0; - switch (cpu_dai->id) { - case WSA_CODEC_DMA_RX_0: - case WSA_CODEC_DMA_RX_1: - case RX_CODEC_DMA_RX_0: - case RX_CODEC_DMA_RX_1: - case TX_CODEC_DMA_TX_0: - case TX_CODEC_DMA_TX_1: - case TX_CODEC_DMA_TX_2: - case TX_CODEC_DMA_TX_3: - break; - default: + if (!qcom_snd_is_sdw_dai(cpu_dai->id)) return 0; - } if (*stream_prepared) return 0; @@ -129,9 +152,7 @@ int qcom_snd_sdw_prepare(struct snd_pcm_substream *substream, } EXPORT_SYMBOL_GPL(qcom_snd_sdw_prepare); -int qcom_snd_sdw_hw_params(struct snd_pcm_substream *substream, - struct snd_pcm_hw_params *params, - struct sdw_stream_runtime **psruntime) +struct sdw_stream_runtime *qcom_snd_sdw_get_stream(struct snd_pcm_substream *substream) { struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream); struct snd_soc_dai *codec_dai; @@ -139,21 +160,23 @@ int qcom_snd_sdw_hw_params(struct snd_pcm_substream *substream, struct sdw_stream_runtime *sruntime; int i; - switch (cpu_dai->id) { - case WSA_CODEC_DMA_RX_0: - case RX_CODEC_DMA_RX_0: - case RX_CODEC_DMA_RX_1: - case TX_CODEC_DMA_TX_0: - case TX_CODEC_DMA_TX_1: - case TX_CODEC_DMA_TX_2: - case TX_CODEC_DMA_TX_3: - for_each_rtd_codec_dais(rtd, i, codec_dai) { - sruntime = snd_soc_dai_get_stream(codec_dai, substream->stream); - if (sruntime != ERR_PTR(-ENOTSUPP)) - *psruntime = sruntime; - } - break; + if (!qcom_snd_is_sdw_dai(cpu_dai->id)) + return NULL; + + for_each_rtd_codec_dais(rtd, i, codec_dai) { + sruntime = snd_soc_dai_get_stream(codec_dai, substream->stream); + if (sruntime != ERR_PTR(-ENOTSUPP)) + return sruntime; } + return NULL; +} +EXPORT_SYMBOL_GPL(qcom_snd_sdw_get_stream); + +int qcom_snd_sdw_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct sdw_stream_runtime **psruntime) +{ + *psruntime = qcom_snd_sdw_get_stream(substream); return 0; @@ -166,23 +189,13 @@ int qcom_snd_sdw_hw_free(struct snd_pcm_substream *substream, struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream); struct snd_soc_dai *cpu_dai = snd_soc_rtd_to_cpu(rtd, 0); - switch (cpu_dai->id) { - case WSA_CODEC_DMA_RX_0: - case WSA_CODEC_DMA_RX_1: - case RX_CODEC_DMA_RX_0: - case RX_CODEC_DMA_RX_1: - case TX_CODEC_DMA_TX_0: - case TX_CODEC_DMA_TX_1: - case TX_CODEC_DMA_TX_2: - case TX_CODEC_DMA_TX_3: - if (sruntime && *stream_prepared) { - sdw_disable_stream(sruntime); - sdw_deprepare_stream(sruntime); - *stream_prepared = false; - } - break; - default: - break; + if (!qcom_snd_is_sdw_dai(cpu_dai->id)) + return 0; + + if (sruntime && *stream_prepared) { + sdw_disable_stream(sruntime); + sdw_deprepare_stream(sruntime); + *stream_prepared = false; } return 0; diff --git a/sound/soc/qcom/sdw.h b/sound/soc/qcom/sdw.h index 392e3455f1b198..b8bc5beb052230 100644 --- a/sound/soc/qcom/sdw.h +++ b/sound/soc/qcom/sdw.h @@ -10,6 +10,7 @@ int qcom_snd_sdw_startup(struct snd_pcm_substream *substream); int qcom_snd_sdw_prepare(struct snd_pcm_substream *substream, struct sdw_stream_runtime *runtime, bool *stream_prepared); +struct sdw_stream_runtime *qcom_snd_sdw_get_stream(struct snd_pcm_substream *stream); int qcom_snd_sdw_hw_params(struct snd_pcm_substream *substream, struct snd_pcm_hw_params *params, struct sdw_stream_runtime **psruntime); diff --git a/sound/soc/qcom/sm8250.c b/sound/soc/qcom/sm8250.c index f5b75a06e5bd20..ce5b0059207f1c 100644 --- a/sound/soc/qcom/sm8250.c +++ b/sound/soc/qcom/sm8250.c @@ -117,7 +117,7 @@ static void sm8250_snd_shutdown(struct snd_pcm_substream *substream) struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream); struct snd_soc_dai *cpu_dai = snd_soc_rtd_to_cpu(rtd, 0); struct sm8250_snd_data *data = snd_soc_card_get_drvdata(rtd->card); - struct sdw_stream_runtime *sruntime = data->sruntime[cpu_dai->id]; + struct sdw_stream_runtime *sruntime = qcom_snd_sdw_get_stream(substream); data->sruntime[cpu_dai->id] = NULL; sdw_release_stream(sruntime); diff --git a/sound/soc/qcom/x1e80100.c b/sound/soc/qcom/x1e80100.c index 444f2162889f7d..2e3599516aa2ae 100644 --- a/sound/soc/qcom/x1e80100.c +++ b/sound/soc/qcom/x1e80100.c @@ -55,7 +55,7 @@ static void x1e80100_snd_shutdown(struct snd_pcm_substream *substream) struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream); struct snd_soc_dai *cpu_dai = snd_soc_rtd_to_cpu(rtd, 0); struct x1e80100_snd_data *data = snd_soc_card_get_drvdata(rtd->card); - struct sdw_stream_runtime *sruntime = data->sruntime[cpu_dai->id]; + struct sdw_stream_runtime *sruntime = qcom_snd_sdw_get_stream(substream); data->sruntime[cpu_dai->id] = NULL; sdw_release_stream(sruntime); From fb7d608aa7c4f5dbbdccf6bf035f714c0197f4b6 Mon Sep 17 00:00:00 2001 From: Eric Naim Date: Sun, 7 Dec 2025 03:38:12 +0800 Subject: [PATCH 1860/3902] ASoC: cs35l41: Always return 0 when a subsystem ID is found commit b0ff70e9d4fe46cece25eb97b9b9b0166624af95 upstream. When trying to get the system name in the _HID path, after successfully retrieving the subsystem ID the return value isn't set to 0 but instead still kept at -ENODATA, leading to a false negative: [ 12.382507] cs35l41 spi-VLV1776:00: Subsystem ID: VLV1776 [ 12.382521] cs35l41 spi-VLV1776:00: probe with driver cs35l41 failed with error -61 Always return 0 when a subsystem ID is found to mitigate these false negatives. Link: https://github.com/CachyOS/CachyOS-Handheld/issues/83 Fixes: 46c8b4d2a693 ("ASoC: cs35l41: Fallback to reading Subsystem ID property if not ACPI") Cc: stable@vger.kernel.org # 6.18 Signed-off-by: Eric Naim Reviewed-by: Richard Fitzgerald Link: https://patch.msgid.link/20251206193813.56955-1-dnaim@cachyos.org Signed-off-by: Mark Brown Signed-off-by: Greg Kroah-Hartman --- sound/soc/codecs/cs35l41.c | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/sound/soc/codecs/cs35l41.c b/sound/soc/codecs/cs35l41.c index 173d7c59b7254d..5001a546a3e750 100644 --- a/sound/soc/codecs/cs35l41.c +++ b/sound/soc/codecs/cs35l41.c @@ -1188,13 +1188,14 @@ static int cs35l41_get_system_name(struct cs35l41_private *cs35l41) } } -err: if (sub) { cs35l41->dsp.system_name = sub; dev_info(cs35l41->dev, "Subsystem ID: %s\n", cs35l41->dsp.system_name); - } else - dev_warn(cs35l41->dev, "Subsystem ID not found\n"); + return 0; + } +err: + dev_warn(cs35l41->dev, "Subsystem ID not found\n"); return ret; } From 150352153ac9aa3fc995b94946dc7d897f0be354 Mon Sep 17 00:00:00 2001 From: Ma Ke Date: Sun, 16 Nov 2025 11:37:16 +0800 Subject: [PATCH 1861/3902] ASoC: codecs: Fix error handling in pm4125 audio codec driver commit 2196e8172bee2002e9baaa0d02b2f9f2dd213949 upstream. pm4125_bind() acquires references through pm4125_sdw_device_get() but fails to release them in error paths and during normal unbind operations. This could result in reference count leaks, preventing proper cleanup and potentially causing resource exhaustion over multiple bind/unbind cycles. Calling path: pm4125_sdw_device_get() -> bus_find_device_by_of_node() -> bus_find_device() -> get_device. Found by code review. Cc: stable@vger.kernel.org Fixes: 8ad529484937 ("ASoC: codecs: add new pm4125 audio codec driver") Signed-off-by: Ma Ke Reviewed-by: Dmitry Baryshkov Link: https://patch.msgid.link/20251116033716.29369-1-make24@iscas.ac.cn Signed-off-by: Mark Brown Signed-off-by: Greg Kroah-Hartman --- sound/soc/codecs/pm4125.c | 21 ++++++++++++++++++--- 1 file changed, 18 insertions(+), 3 deletions(-) diff --git a/sound/soc/codecs/pm4125.c b/sound/soc/codecs/pm4125.c index 3657a4a759855d..3d7b07624ee614 100644 --- a/sound/soc/codecs/pm4125.c +++ b/sound/soc/codecs/pm4125.c @@ -1549,6 +1549,10 @@ static int pm4125_bind(struct device *dev) struct device_link *devlink; int ret; + /* Initialize device pointers to NULL for safe cleanup */ + pm4125->rxdev = NULL; + pm4125->txdev = NULL; + /* Give the soundwire subdevices some more time to settle */ usleep_range(15000, 15010); @@ -1572,7 +1576,7 @@ static int pm4125_bind(struct device *dev) if (!pm4125->txdev) { dev_err(dev, "could not find txslave with matching of node\n"); ret = -EINVAL; - goto error_unbind_all; + goto error_put_rx; } pm4125->sdw_priv[AIF1_CAP] = dev_get_drvdata(pm4125->txdev); @@ -1582,7 +1586,7 @@ static int pm4125_bind(struct device *dev) if (!pm4125->tx_sdw_dev) { dev_err(dev, "could not get txslave with matching of dev\n"); ret = -EINVAL; - goto error_unbind_all; + goto error_put_tx; } /* @@ -1594,7 +1598,7 @@ static int pm4125_bind(struct device *dev) if (!devlink) { dev_err(dev, "Could not devlink TX and RX\n"); ret = -EINVAL; - goto error_unbind_all; + goto error_put_tx; } devlink = device_link_add(dev, pm4125->txdev, @@ -1648,6 +1652,10 @@ static int pm4125_bind(struct device *dev) device_link_remove(dev, pm4125->txdev); link_remove_rx_tx: device_link_remove(pm4125->rxdev, pm4125->txdev); +error_put_tx: + put_device(pm4125->txdev); +error_put_rx: + put_device(pm4125->rxdev); error_unbind_all: component_unbind_all(dev, pm4125); return ret; @@ -1663,6 +1671,13 @@ static void pm4125_unbind(struct device *dev) device_link_remove(dev, pm4125->txdev); device_link_remove(dev, pm4125->rxdev); device_link_remove(pm4125->rxdev, pm4125->txdev); + + /* Release device references acquired in bind */ + if (pm4125->txdev) + put_device(pm4125->txdev); + if (pm4125->rxdev) + put_device(pm4125->rxdev); + component_unbind_all(dev, pm4125); } From c5c85ef27db6ae6e949baa73b7768857431c2863 Mon Sep 17 00:00:00 2001 From: Srinivas Kandagatla Date: Thu, 23 Oct 2025 11:24:25 +0100 Subject: [PATCH 1862/3902] ASoC: qcom: q6apm-dai: set flags to reflect correct operation of appl_ptr commit 950a4e5788fc7dc6e8e93614a7d4d0449c39fb8d upstream. Driver does not expect the appl_ptr to move backward and requires explict sync. Make sure that the userspace does not do appl_ptr rewinds by specifying the correct flags in pcm_info. Without this patch, the result could be a forever loop as current logic assumes that appl_ptr can only move forward. Fixes: 3d4a4411aa8b ("ASoC: q6apm-dai: schedule all available frames to avoid dsp under-runs") Cc: Stable@vger.kernel.org Signed-off-by: Srinivas Kandagatla Tested-by: Alexey Klimov # RB5, RB3 Link: https://patch.msgid.link/20251023102444.88158-2-srinivas.kandagatla@oss.qualcomm.com Signed-off-by: Mark Brown Signed-off-by: Greg Kroah-Hartman --- sound/soc/qcom/qdsp6/q6apm-dai.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/sound/soc/qcom/qdsp6/q6apm-dai.c b/sound/soc/qcom/qdsp6/q6apm-dai.c index 4ecaff45c51860..786ab3222515c3 100644 --- a/sound/soc/qcom/qdsp6/q6apm-dai.c +++ b/sound/soc/qcom/qdsp6/q6apm-dai.c @@ -86,6 +86,7 @@ static const struct snd_pcm_hardware q6apm_dai_hardware_capture = { .info = (SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_BLOCK_TRANSFER | SNDRV_PCM_INFO_MMAP_VALID | SNDRV_PCM_INFO_INTERLEAVED | SNDRV_PCM_INFO_PAUSE | SNDRV_PCM_INFO_RESUME | + SNDRV_PCM_INFO_NO_REWINDS | SNDRV_PCM_INFO_SYNC_APPLPTR | SNDRV_PCM_INFO_BATCH), .formats = (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S24_LE), .rates = SNDRV_PCM_RATE_8000_48000, @@ -105,6 +106,7 @@ static const struct snd_pcm_hardware q6apm_dai_hardware_playback = { .info = (SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_BLOCK_TRANSFER | SNDRV_PCM_INFO_MMAP_VALID | SNDRV_PCM_INFO_INTERLEAVED | SNDRV_PCM_INFO_PAUSE | SNDRV_PCM_INFO_RESUME | + SNDRV_PCM_INFO_NO_REWINDS | SNDRV_PCM_INFO_SYNC_APPLPTR | SNDRV_PCM_INFO_BATCH), .formats = (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S24_LE), .rates = SNDRV_PCM_RATE_8000_192000, From cd5691807ba602fd3ec40625d2c68614eaa505dc Mon Sep 17 00:00:00 2001 From: Srinivas Kandagatla Date: Thu, 23 Oct 2025 11:24:28 +0100 Subject: [PATCH 1863/3902] ASoC: qcom: q6asm-dai: perform correct state check before closing commit bfbb12dfa144d45575bcfe139a71360b3ce80237 upstream. Do not stop a q6asm stream if its not started, this can result in unnecessary dsp command which will timeout anyway something like below: q6asm-dai ab00000.remoteproc:glink-edge:apr:service@7:dais: CMD 10bcd timeout Fix this by correctly checking the state. Fixes: 2a9e92d371db ("ASoC: qdsp6: q6asm: Add q6asm dai driver") Cc: Stable@vger.kernel.org Signed-off-by: Srinivas Kandagatla Tested-by: Alexey Klimov # RB5, RB3 Link: https://patch.msgid.link/20251023102444.88158-5-srinivas.kandagatla@oss.qualcomm.com Signed-off-by: Mark Brown Signed-off-by: Greg Kroah-Hartman --- sound/soc/qcom/qdsp6/q6asm-dai.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/sound/soc/qcom/qdsp6/q6asm-dai.c b/sound/soc/qcom/qdsp6/q6asm-dai.c index b616ce316d2f13..8e0b2db8a2db0e 100644 --- a/sound/soc/qcom/qdsp6/q6asm-dai.c +++ b/sound/soc/qcom/qdsp6/q6asm-dai.c @@ -233,13 +233,14 @@ static int q6asm_dai_prepare(struct snd_soc_component *component, prtd->pcm_count = snd_pcm_lib_period_bytes(substream); prtd->pcm_irq_pos = 0; /* rate and channels are sent to audio driver */ - if (prtd->state) { + if (prtd->state == Q6ASM_STREAM_RUNNING) { /* clear the previous setup if any */ q6asm_cmd(prtd->audio_client, prtd->stream_id, CMD_CLOSE); q6asm_unmap_memory_regions(substream->stream, prtd->audio_client); q6routing_stream_close(soc_prtd->dai_link->id, substream->stream); + prtd->state = Q6ASM_STREAM_STOPPED; } ret = q6asm_map_memory_regions(substream->stream, prtd->audio_client, From 372796af87d3d0ddccae4f19d930efc367913e5c Mon Sep 17 00:00:00 2001 From: Srinivas Kandagatla Date: Thu, 23 Oct 2025 11:24:26 +0100 Subject: [PATCH 1864/3902] ASoC: qcom: q6adm: the the copp device only during last instance commit 74cc4f3ea4e99262ba0d619c6a4ee33e2cd47f65 upstream. A matching Common object post processing instance is normally resused across multiple streams. However currently we close this on DSP even though there is a refcount on this copp object, this can result in below error. q6routing ab00000.remoteproc:glink-edge:apr:service@8:routing: Found Matching Copp 0x0 qcom-q6adm aprsvc:service:4:8: cmd = 0x10325 return error = 0x2 q6routing ab00000.remoteproc:glink-edge:apr:service@8:routing: DSP returned error[2] q6routing ab00000.remoteproc:glink-edge:apr:service@8:routing: Found Matching Copp 0x0 qcom-q6adm aprsvc:service:4:8: cmd = 0x10325 return error = 0x2 q6routing ab00000.remoteproc:glink-edge:apr:service@8:routing: DSP returned error[2] qcom-q6adm aprsvc:service:4:8: cmd = 0x10327 return error = 0x2 qcom-q6adm aprsvc:service:4:8: DSP returned error[2] qcom-q6adm aprsvc:service:4:8: Failed to close copp -22 qcom-q6adm aprsvc:service:4:8: cmd = 0x10327 return error = 0x2 qcom-q6adm aprsvc:service:4:8: DSP returned error[2] qcom-q6adm aprsvc:service:4:8: Failed to close copp -22 Fix this by addressing moving the adm_close to copp_kref destructor callback. Fixes: 7b20b2be51e1 ("ASoC: qdsp6: q6adm: Add q6adm driver") Cc: Stable@vger.kernel.org Reported-by: Martino Facchin Signed-off-by: Srinivas Kandagatla Tested-by: Alexey Klimov # RB5, RB3 Link: https://patch.msgid.link/20251023102444.88158-3-srinivas.kandagatla@oss.qualcomm.com Signed-off-by: Mark Brown Signed-off-by: Greg Kroah-Hartman --- sound/soc/qcom/qdsp6/q6adm.c | 146 +++++++++++++++++------------------ 1 file changed, 71 insertions(+), 75 deletions(-) diff --git a/sound/soc/qcom/qdsp6/q6adm.c b/sound/soc/qcom/qdsp6/q6adm.c index 1530e98df16564..75a029a696ac92 100644 --- a/sound/soc/qcom/qdsp6/q6adm.c +++ b/sound/soc/qcom/qdsp6/q6adm.c @@ -109,11 +109,75 @@ static struct q6copp *q6adm_find_copp(struct q6adm *adm, int port_idx, } +static int q6adm_apr_send_copp_pkt(struct q6adm *adm, struct q6copp *copp, + struct apr_pkt *pkt, uint32_t rsp_opcode) +{ + struct device *dev = adm->dev; + uint32_t opcode = pkt->hdr.opcode; + int ret; + + mutex_lock(&adm->lock); + copp->result.opcode = 0; + copp->result.status = 0; + ret = apr_send_pkt(adm->apr, pkt); + if (ret < 0) { + dev_err(dev, "Failed to send APR packet\n"); + ret = -EINVAL; + goto err; + } + + /* Wait for the callback with copp id */ + if (rsp_opcode) + ret = wait_event_timeout(copp->wait, + (copp->result.opcode == opcode) || + (copp->result.opcode == rsp_opcode), + msecs_to_jiffies(TIMEOUT_MS)); + else + ret = wait_event_timeout(copp->wait, + (copp->result.opcode == opcode), + msecs_to_jiffies(TIMEOUT_MS)); + + if (!ret) { + dev_err(dev, "ADM copp cmd timedout\n"); + ret = -ETIMEDOUT; + } else if (copp->result.status > 0) { + dev_err(dev, "DSP returned error[%d]\n", + copp->result.status); + ret = -EINVAL; + } + +err: + mutex_unlock(&adm->lock); + return ret; +} + +static int q6adm_device_close(struct q6adm *adm, struct q6copp *copp, + int port_id, int copp_idx) +{ + struct apr_pkt close; + + close.hdr.hdr_field = APR_HDR_FIELD(APR_MSG_TYPE_SEQ_CMD, + APR_HDR_LEN(APR_HDR_SIZE), + APR_PKT_VER); + close.hdr.pkt_size = sizeof(close); + close.hdr.src_port = port_id; + close.hdr.dest_port = copp->id; + close.hdr.token = port_id << 16 | copp_idx; + close.hdr.opcode = ADM_CMD_DEVICE_CLOSE_V5; + + return q6adm_apr_send_copp_pkt(adm, copp, &close, 0); +} + static void q6adm_free_copp(struct kref *ref) { struct q6copp *c = container_of(ref, struct q6copp, refcount); struct q6adm *adm = c->adm; unsigned long flags; + int ret; + + ret = q6adm_device_close(adm, c, c->afe_port, c->copp_idx); + if (ret < 0) + dev_err(adm->dev, "Failed to close copp %d\n", ret); spin_lock_irqsave(&adm->copps_list_lock, flags); clear_bit(c->copp_idx, &adm->copp_bitmap[c->afe_port]); @@ -155,13 +219,13 @@ static int q6adm_callback(struct apr_device *adev, struct apr_resp_pkt *data) switch (result->opcode) { case ADM_CMD_DEVICE_OPEN_V5: case ADM_CMD_DEVICE_CLOSE_V5: - copp = q6adm_find_copp(adm, port_idx, copp_idx); - if (!copp) - return 0; - - copp->result = *result; - wake_up(&copp->wait); - kref_put(&copp->refcount, q6adm_free_copp); + list_for_each_entry(copp, &adm->copps_list, node) { + if ((port_idx == copp->afe_port) && (copp_idx == copp->copp_idx)) { + copp->result = *result; + wake_up(&copp->wait); + break; + } + } break; case ADM_CMD_MATRIX_MAP_ROUTINGS_V5: adm->result = *result; @@ -234,65 +298,6 @@ static struct q6copp *q6adm_alloc_copp(struct q6adm *adm, int port_idx) return c; } -static int q6adm_apr_send_copp_pkt(struct q6adm *adm, struct q6copp *copp, - struct apr_pkt *pkt, uint32_t rsp_opcode) -{ - struct device *dev = adm->dev; - uint32_t opcode = pkt->hdr.opcode; - int ret; - - mutex_lock(&adm->lock); - copp->result.opcode = 0; - copp->result.status = 0; - ret = apr_send_pkt(adm->apr, pkt); - if (ret < 0) { - dev_err(dev, "Failed to send APR packet\n"); - ret = -EINVAL; - goto err; - } - - /* Wait for the callback with copp id */ - if (rsp_opcode) - ret = wait_event_timeout(copp->wait, - (copp->result.opcode == opcode) || - (copp->result.opcode == rsp_opcode), - msecs_to_jiffies(TIMEOUT_MS)); - else - ret = wait_event_timeout(copp->wait, - (copp->result.opcode == opcode), - msecs_to_jiffies(TIMEOUT_MS)); - - if (!ret) { - dev_err(dev, "ADM copp cmd timedout\n"); - ret = -ETIMEDOUT; - } else if (copp->result.status > 0) { - dev_err(dev, "DSP returned error[%d]\n", - copp->result.status); - ret = -EINVAL; - } - -err: - mutex_unlock(&adm->lock); - return ret; -} - -static int q6adm_device_close(struct q6adm *adm, struct q6copp *copp, - int port_id, int copp_idx) -{ - struct apr_pkt close; - - close.hdr.hdr_field = APR_HDR_FIELD(APR_MSG_TYPE_SEQ_CMD, - APR_HDR_LEN(APR_HDR_SIZE), - APR_PKT_VER); - close.hdr.pkt_size = sizeof(close); - close.hdr.src_port = port_id; - close.hdr.dest_port = copp->id; - close.hdr.token = port_id << 16 | copp_idx; - close.hdr.opcode = ADM_CMD_DEVICE_CLOSE_V5; - - return q6adm_apr_send_copp_pkt(adm, copp, &close, 0); -} - static struct q6copp *q6adm_find_matching_copp(struct q6adm *adm, int port_id, int topology, int mode, int rate, @@ -567,15 +572,6 @@ EXPORT_SYMBOL_GPL(q6adm_matrix_map); */ int q6adm_close(struct device *dev, struct q6copp *copp) { - struct q6adm *adm = dev_get_drvdata(dev->parent); - int ret = 0; - - ret = q6adm_device_close(adm, copp, copp->afe_port, copp->copp_idx); - if (ret < 0) { - dev_err(adm->dev, "Failed to close copp %d\n", ret); - return ret; - } - kref_put(&copp->refcount, q6adm_free_copp); return 0; From 00ed0cc57232a453f825bcbe0bf7367c9aeceab3 Mon Sep 17 00:00:00 2001 From: Srinivas Kandagatla Date: Thu, 23 Oct 2025 11:24:27 +0100 Subject: [PATCH 1865/3902] ASoC: qcom: qdsp6: q6asm-dai: set 10 ms period and buffer alignment. commit 81c53b52de21b8d5a3de55ebd06b6bf188bf7efd upstream. DSP expects the periods to be aligned to fragment sizes, currently setting up to hw constriants on periods bytes is not going to work correctly as we can endup with periods sizes aligned to 32 bytes however not aligned to fragment size. Update the constriants to use fragment size, and also set at step of 10ms for period size to accommodate DSP requirements of 10ms latency. Fixes: 2a9e92d371db ("ASoC: qdsp6: q6asm: Add q6asm dai driver") Cc: Stable@vger.kernel.org Signed-off-by: Srinivas Kandagatla Tested-by: Alexey Klimov # RB5, RB3 Link: https://patch.msgid.link/20251023102444.88158-4-srinivas.kandagatla@oss.qualcomm.com Signed-off-by: Mark Brown Signed-off-by: Greg Kroah-Hartman --- sound/soc/qcom/qdsp6/q6asm-dai.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/sound/soc/qcom/qdsp6/q6asm-dai.c b/sound/soc/qcom/qdsp6/q6asm-dai.c index 8e0b2db8a2db0e..0eae8c6e42b8aa 100644 --- a/sound/soc/qcom/qdsp6/q6asm-dai.c +++ b/sound/soc/qcom/qdsp6/q6asm-dai.c @@ -404,13 +404,13 @@ static int q6asm_dai_open(struct snd_soc_component *component, } ret = snd_pcm_hw_constraint_step(runtime, 0, - SNDRV_PCM_HW_PARAM_PERIOD_BYTES, 32); + SNDRV_PCM_HW_PARAM_PERIOD_SIZE, 480); if (ret < 0) { dev_err(dev, "constraint for period bytes step ret = %d\n", ret); } ret = snd_pcm_hw_constraint_step(runtime, 0, - SNDRV_PCM_HW_PARAM_BUFFER_BYTES, 32); + SNDRV_PCM_HW_PARAM_BUFFER_SIZE, 480); if (ret < 0) { dev_err(dev, "constraint for buffer bytes step ret = %d\n", ret); From 47bed96dc7d5151dadd66f60509a75eb33f7d6ef Mon Sep 17 00:00:00 2001 From: Jinhui Guo Date: Tue, 28 Oct 2025 00:50:17 +0800 Subject: [PATCH 1866/3902] iommu/amd: Fix pci_segment memleak in alloc_pci_segment() commit 75ba146c2674ba49ed8a222c67f9abfb4a4f2a4f upstream. Fix a memory leak of struct amd_iommu_pci_segment in alloc_pci_segment() when system memory (or contiguous memory) is insufficient. Fixes: 04230c119930 ("iommu/amd: Introduce per PCI segment device table") Fixes: eda797a27795 ("iommu/amd: Introduce per PCI segment rlookup table") Fixes: 99fc4ac3d297 ("iommu/amd: Introduce per PCI segment alias_table") Cc: stable@vger.kernel.org Signed-off-by: Jinhui Guo Signed-off-by: Joerg Roedel Signed-off-by: Greg Kroah-Hartman --- drivers/iommu/amd/init.c | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/drivers/iommu/amd/init.c b/drivers/iommu/amd/init.c index 14eb9de33ccb01..034edce816d01b 100644 --- a/drivers/iommu/amd/init.c +++ b/drivers/iommu/amd/init.c @@ -1731,13 +1731,22 @@ static struct amd_iommu_pci_seg *__init alloc_pci_segment(u16 id, list_add_tail(&pci_seg->list, &amd_iommu_pci_seg_list); if (alloc_dev_table(pci_seg)) - return NULL; + goto err_free_pci_seg; if (alloc_alias_table(pci_seg)) - return NULL; + goto err_free_dev_table; if (alloc_rlookup_table(pci_seg)) - return NULL; + goto err_free_alias_table; return pci_seg; + +err_free_alias_table: + free_alias_table(pci_seg); +err_free_dev_table: + free_dev_table(pci_seg); +err_free_pci_seg: + list_del(&pci_seg->list); + kfree(pci_seg); + return NULL; } static struct amd_iommu_pci_seg *__init get_pci_segment(u16 id, From 67c5f84f9b1cd76664528cb39697fa57b8945724 Mon Sep 17 00:00:00 2001 From: Jinhui Guo Date: Thu, 20 Nov 2025 23:47:25 +0800 Subject: [PATCH 1867/3902] iommu/amd: Propagate the error code returned by __modify_irte_ga() in modify_irte_ga() commit 2381a1b40be4b286062fb3cf67dd7f005692aa2a upstream. The return type of __modify_irte_ga() is int, but modify_irte_ga() treats it as a bool. Casting the int to bool discards the error code. To fix the issue, change the type of ret to int in modify_irte_ga(). Fixes: 57cdb720eaa5 ("iommu/amd: Do not flush IRTE when only updating isRun and destination fields") Cc: stable@vger.kernel.org Signed-off-by: Jinhui Guo Reviewed-by: Vasant Hegde Signed-off-by: Joerg Roedel Signed-off-by: Greg Kroah-Hartman --- drivers/iommu/amd/iommu.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/iommu/amd/iommu.c b/drivers/iommu/amd/iommu.c index 2e1865daa1cee8..a38304f1a8df5f 100644 --- a/drivers/iommu/amd/iommu.c +++ b/drivers/iommu/amd/iommu.c @@ -3354,7 +3354,7 @@ static int __modify_irte_ga(struct amd_iommu *iommu, u16 devid, int index, static int modify_irte_ga(struct amd_iommu *iommu, u16 devid, int index, struct irte_ga *irte) { - bool ret; + int ret; ret = __modify_irte_ga(iommu, devid, index, irte); if (ret) From 9bb0b50a759ee1b37abfc945152d64414fe92c7b Mon Sep 17 00:00:00 2001 From: Johan Hovold Date: Mon, 20 Oct 2025 06:53:05 +0200 Subject: [PATCH 1868/3902] iommu/apple-dart: fix device leak on of_xlate() commit a6eaa872c52a181ae9a290fd4e40c9df91166d7a upstream. Make sure to drop the reference taken to the iommu platform device when looking up its driver data during of_xlate(). Fixes: 46d1fb072e76 ("iommu/dart: Add DART iommu driver") Cc: stable@vger.kernel.org # 5.15 Cc: Sven Peter Acked-by: Robin Murphy Signed-off-by: Johan Hovold Signed-off-by: Joerg Roedel Signed-off-by: Greg Kroah-Hartman --- drivers/iommu/apple-dart.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/drivers/iommu/apple-dart.c b/drivers/iommu/apple-dart.c index 95a4e62b8f63c3..9804022c7f5901 100644 --- a/drivers/iommu/apple-dart.c +++ b/drivers/iommu/apple-dart.c @@ -802,6 +802,8 @@ static int apple_dart_of_xlate(struct device *dev, struct apple_dart *cfg_dart; int i, sid; + put_device(&iommu_pdev->dev); + if (args->args_count != 1) return -EINVAL; sid = args->args[0]; From b35eeae8a566fe7f32b2a80b7ef2c752d473226a Mon Sep 17 00:00:00 2001 From: Johan Hovold Date: Mon, 20 Oct 2025 06:53:07 +0200 Subject: [PATCH 1869/3902] iommu/exynos: fix device leak on of_xlate() commit 05913cc43cb122f9afecdbe775115c058b906e1b upstream. Make sure to drop the reference taken to the iommu platform device when looking up its driver data during of_xlate(). Note that commit 1a26044954a6 ("iommu/exynos: add missing put_device() call in exynos_iommu_of_xlate()") fixed the leak in a couple of error paths, but the reference is still leaking on success. Fixes: aa759fd376fb ("iommu/exynos: Add callback for initializing devices from device tree") Cc: stable@vger.kernel.org # 4.2: 1a26044954a6 Cc: Yu Kuai Acked-by: Robin Murphy Acked-by: Marek Szyprowski Signed-off-by: Johan Hovold Signed-off-by: Joerg Roedel Signed-off-by: Greg Kroah-Hartman --- drivers/iommu/exynos-iommu.c | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/drivers/iommu/exynos-iommu.c b/drivers/iommu/exynos-iommu.c index b6edd178fe25e7..ce9e935cb84c3d 100644 --- a/drivers/iommu/exynos-iommu.c +++ b/drivers/iommu/exynos-iommu.c @@ -1446,17 +1446,14 @@ static int exynos_iommu_of_xlate(struct device *dev, return -ENODEV; data = platform_get_drvdata(sysmmu); - if (!data) { - put_device(&sysmmu->dev); + put_device(&sysmmu->dev); + if (!data) return -ENODEV; - } if (!owner) { owner = kzalloc(sizeof(*owner), GFP_KERNEL); - if (!owner) { - put_device(&sysmmu->dev); + if (!owner) return -ENOMEM; - } INIT_LIST_HEAD(&owner->controllers); mutex_init(&owner->rpm_lock); From ba202227f98d1ba4505134e8c88135c1a6b6993b Mon Sep 17 00:00:00 2001 From: Johan Hovold Date: Mon, 20 Oct 2025 06:53:08 +0200 Subject: [PATCH 1870/3902] iommu/ipmmu-vmsa: fix device leak on of_xlate() commit 80aa518452c4aceb9459f9a8e3184db657d1b441 upstream. Make sure to drop the reference taken to the iommu platform device when looking up its driver data during of_xlate(). Fixes: 7b2d59611fef ("iommu/ipmmu-vmsa: Replace local utlb code with fwspec ids") Cc: stable@vger.kernel.org # 4.14 Cc: Magnus Damm Acked-by: Robin Murphy Signed-off-by: Johan Hovold Signed-off-by: Joerg Roedel Signed-off-by: Greg Kroah-Hartman --- drivers/iommu/ipmmu-vmsa.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/drivers/iommu/ipmmu-vmsa.c b/drivers/iommu/ipmmu-vmsa.c index ffa892f6571406..02a2a55ffa0a08 100644 --- a/drivers/iommu/ipmmu-vmsa.c +++ b/drivers/iommu/ipmmu-vmsa.c @@ -720,6 +720,8 @@ static int ipmmu_init_platform_device(struct device *dev, dev_iommu_priv_set(dev, platform_get_drvdata(ipmmu_pdev)); + put_device(&ipmmu_pdev->dev); + return 0; } From 574635a02526b37626d60187b4036d19c1fdb7e1 Mon Sep 17 00:00:00 2001 From: Johan Hovold Date: Mon, 20 Oct 2025 06:53:12 +0200 Subject: [PATCH 1871/3902] iommu/mediatek-v1: fix device leak on probe_device() commit c77ad28bfee0df9cbc719eb5adc9864462cfb65b upstream. Make sure to drop the reference taken to the iommu platform device when looking up its driver data during probe_device(). Fixes: b17336c55d89 ("iommu/mediatek: add support for mtk iommu generation one HW") Cc: stable@vger.kernel.org # 4.8 Cc: Honghui Zhang Acked-by: Robin Murphy Reviewed-by: Yong Wu Signed-off-by: Johan Hovold Reviewed-by: AngeloGioacchino Del Regno Signed-off-by: Joerg Roedel Signed-off-by: Greg Kroah-Hartman --- drivers/iommu/mtk_iommu_v1.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/drivers/iommu/mtk_iommu_v1.c b/drivers/iommu/mtk_iommu_v1.c index 10cc0b1197e801..de9153c0a82f7b 100644 --- a/drivers/iommu/mtk_iommu_v1.c +++ b/drivers/iommu/mtk_iommu_v1.c @@ -435,6 +435,8 @@ static int mtk_iommu_v1_create_mapping(struct device *dev, return -EINVAL; dev_iommu_priv_set(dev, platform_get_drvdata(m4updev)); + + put_device(&m4updev->dev); } ret = iommu_fwspec_add_ids(dev, args->args, 1); From fa3cb4012fb22ad7e64a47e55c0598577be492c6 Mon Sep 17 00:00:00 2001 From: Johan Hovold Date: Mon, 20 Oct 2025 06:53:13 +0200 Subject: [PATCH 1872/3902] iommu/mediatek-v1: fix device leaks on probe() commit 46207625c9f33da0e43bb4ae1e91f0791b6ed633 upstream. Make sure to drop the references taken to the larb devices during probe on probe failure (e.g. probe deferral) and on driver unbind. Fixes: b17336c55d89 ("iommu/mediatek: add support for mtk iommu generation one HW") Cc: stable@vger.kernel.org # 4.8 Cc: Honghui Zhang Acked-by: Robin Murphy Signed-off-by: Johan Hovold Reviewed-by: AngeloGioacchino Del Regno Signed-off-by: Joerg Roedel Signed-off-by: Greg Kroah-Hartman --- drivers/iommu/mtk_iommu_v1.c | 23 ++++++++++++++++++----- 1 file changed, 18 insertions(+), 5 deletions(-) diff --git a/drivers/iommu/mtk_iommu_v1.c b/drivers/iommu/mtk_iommu_v1.c index de9153c0a82f7b..44b965a2db923b 100644 --- a/drivers/iommu/mtk_iommu_v1.c +++ b/drivers/iommu/mtk_iommu_v1.c @@ -648,8 +648,10 @@ static int mtk_iommu_v1_probe(struct platform_device *pdev) struct platform_device *plarbdev; larbnode = of_parse_phandle(dev->of_node, "mediatek,larbs", i); - if (!larbnode) - return -EINVAL; + if (!larbnode) { + ret = -EINVAL; + goto out_put_larbs; + } if (!of_device_is_available(larbnode)) { of_node_put(larbnode); @@ -659,11 +661,14 @@ static int mtk_iommu_v1_probe(struct platform_device *pdev) plarbdev = of_find_device_by_node(larbnode); if (!plarbdev) { of_node_put(larbnode); - return -ENODEV; + ret = -ENODEV; + goto out_put_larbs; } if (!plarbdev->dev.driver) { of_node_put(larbnode); - return -EPROBE_DEFER; + put_device(&plarbdev->dev); + ret = -EPROBE_DEFER; + goto out_put_larbs; } data->larb_imu[i].dev = &plarbdev->dev; @@ -675,7 +680,7 @@ static int mtk_iommu_v1_probe(struct platform_device *pdev) ret = mtk_iommu_v1_hw_init(data); if (ret) - return ret; + goto out_put_larbs; ret = iommu_device_sysfs_add(&data->iommu, &pdev->dev, NULL, dev_name(&pdev->dev)); @@ -697,12 +702,17 @@ static int mtk_iommu_v1_probe(struct platform_device *pdev) iommu_device_sysfs_remove(&data->iommu); out_clk_unprepare: clk_disable_unprepare(data->bclk); +out_put_larbs: + for (i = 0; i < MTK_LARB_NR_MAX; i++) + put_device(data->larb_imu[i].dev); + return ret; } static void mtk_iommu_v1_remove(struct platform_device *pdev) { struct mtk_iommu_v1_data *data = platform_get_drvdata(pdev); + int i; iommu_device_sysfs_remove(&data->iommu); iommu_device_unregister(&data->iommu); @@ -710,6 +720,9 @@ static void mtk_iommu_v1_remove(struct platform_device *pdev) clk_disable_unprepare(data->bclk); devm_free_irq(&pdev->dev, data->irq, data); component_master_del(&pdev->dev, &mtk_iommu_v1_com_ops); + + for (i = 0; i < MTK_LARB_NR_MAX; i++) + put_device(data->larb_imu[i].dev); } static int __maybe_unused mtk_iommu_v1_suspend(struct device *dev) From c2a13167991fa306ebf657a81d13d3f3070eb9d9 Mon Sep 17 00:00:00 2001 From: Johan Hovold Date: Mon, 20 Oct 2025 06:53:09 +0200 Subject: [PATCH 1873/3902] iommu/mediatek: fix device leak on of_xlate() commit b3f1ee18280363ef17f82b564fc379ceba9ec86f upstream. Make sure to drop the reference taken to the iommu platform device when looking up its driver data during of_xlate(). Fixes: 0df4fabe208d ("iommu/mediatek: Add mt8173 IOMMU driver") Cc: stable@vger.kernel.org # 4.6 Acked-by: Robin Murphy Reviewed-by: Yong Wu Signed-off-by: Johan Hovold Reviewed-by: AngeloGioacchino Del Regno Signed-off-by: Joerg Roedel Signed-off-by: Greg Kroah-Hartman --- drivers/iommu/mtk_iommu.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/drivers/iommu/mtk_iommu.c b/drivers/iommu/mtk_iommu.c index 319974557f3258..64ce041238fd35 100644 --- a/drivers/iommu/mtk_iommu.c +++ b/drivers/iommu/mtk_iommu.c @@ -974,6 +974,8 @@ static int mtk_iommu_of_xlate(struct device *dev, return -EINVAL; dev_iommu_priv_set(dev, platform_get_drvdata(m4updev)); + + put_device(&m4updev->dev); } return iommu_fwspec_add_ids(dev, args->args, 1); From d496519ccd7ebd0eb038cd4ef41b70422100d0c5 Mon Sep 17 00:00:00 2001 From: Johan Hovold Date: Mon, 20 Oct 2025 06:53:15 +0200 Subject: [PATCH 1874/3902] iommu/omap: fix device leaks on probe_device() commit b5870691065e6bbe6ba0650c0412636c6a239c5a upstream. Make sure to drop the references taken to the iommu platform devices when looking up their driver data during probe_device(). Note that the arch data device pointer added by commit 604629bcb505 ("iommu/omap: add support for late attachment of iommu devices") has never been used. Remove it to underline that the references are not needed. Fixes: 9d5018deec86 ("iommu/omap: Add support to program multiple iommus") Fixes: 7d6827748d54 ("iommu/omap: Fix iommu archdata name for DT-based devices") Cc: stable@vger.kernel.org # 3.18 Cc: Suman Anna Acked-by: Robin Murphy Signed-off-by: Johan Hovold Signed-off-by: Joerg Roedel Signed-off-by: Greg Kroah-Hartman --- drivers/iommu/omap-iommu.c | 2 +- drivers/iommu/omap-iommu.h | 2 -- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/drivers/iommu/omap-iommu.c b/drivers/iommu/omap-iommu.c index 5c6f5943f44b1f..c0315c86cd187a 100644 --- a/drivers/iommu/omap-iommu.c +++ b/drivers/iommu/omap-iommu.c @@ -1675,6 +1675,7 @@ static struct iommu_device *omap_iommu_probe_device(struct device *dev) } oiommu = platform_get_drvdata(pdev); + put_device(&pdev->dev); if (!oiommu) { of_node_put(np); kfree(arch_data); @@ -1682,7 +1683,6 @@ static struct iommu_device *omap_iommu_probe_device(struct device *dev) } tmp->iommu_dev = oiommu; - tmp->dev = &pdev->dev; of_node_put(np); } diff --git a/drivers/iommu/omap-iommu.h b/drivers/iommu/omap-iommu.h index 27697109ec79a5..50b39be61abc7d 100644 --- a/drivers/iommu/omap-iommu.h +++ b/drivers/iommu/omap-iommu.h @@ -88,7 +88,6 @@ struct omap_iommu { /** * struct omap_iommu_arch_data - omap iommu private data * @iommu_dev: handle of the OMAP iommu device - * @dev: handle of the iommu device * * This is an omap iommu private data object, which binds an iommu user * to its iommu device. This object should be placed at the iommu user's @@ -97,7 +96,6 @@ struct omap_iommu { */ struct omap_iommu_arch_data { struct omap_iommu *iommu_dev; - struct device *dev; }; struct cr_regs { From 6b8390fcef610432686b464079955381dcce2c78 Mon Sep 17 00:00:00 2001 From: Johan Hovold Date: Mon, 20 Oct 2025 06:53:06 +0200 Subject: [PATCH 1875/3902] iommu/qcom: fix device leak on of_xlate() commit 6a3908ce56e6879920b44ef136252b2f0c954194 upstream. Make sure to drop the reference taken to the iommu platform device when looking up its driver data during of_xlate(). Note that commit e2eae09939a8 ("iommu/qcom: add missing put_device() call in qcom_iommu_of_xlate()") fixed the leak in a couple of error paths, but the reference is still leaking on success and late failures. Fixes: 0ae349a0f33f ("iommu/qcom: Add qcom_iommu") Cc: stable@vger.kernel.org # 4.14: e2eae09939a8 Cc: Rob Clark Cc: Yu Kuai Acked-by: Robin Murphy Signed-off-by: Johan Hovold Signed-off-by: Joerg Roedel Signed-off-by: Greg Kroah-Hartman --- drivers/iommu/arm/arm-smmu/qcom_iommu.c | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/drivers/iommu/arm/arm-smmu/qcom_iommu.c b/drivers/iommu/arm/arm-smmu/qcom_iommu.c index c5be95e560317e..9c1166a3af6c98 100644 --- a/drivers/iommu/arm/arm-smmu/qcom_iommu.c +++ b/drivers/iommu/arm/arm-smmu/qcom_iommu.c @@ -565,14 +565,14 @@ static int qcom_iommu_of_xlate(struct device *dev, qcom_iommu = platform_get_drvdata(iommu_pdev); + put_device(&iommu_pdev->dev); + /* make sure the asid specified in dt is valid, so we don't have * to sanity check this elsewhere: */ if (WARN_ON(asid > qcom_iommu->max_asid) || - WARN_ON(qcom_iommu->ctxs[asid] == NULL)) { - put_device(&iommu_pdev->dev); + WARN_ON(qcom_iommu->ctxs[asid] == NULL)) return -EINVAL; - } if (!dev_iommu_priv_get(dev)) { dev_iommu_priv_set(dev, qcom_iommu); @@ -581,10 +581,8 @@ static int qcom_iommu_of_xlate(struct device *dev, * multiple different iommu devices. Multiple context * banks are ok, but multiple devices are not: */ - if (WARN_ON(qcom_iommu != dev_iommu_priv_get(dev))) { - put_device(&iommu_pdev->dev); + if (WARN_ON(qcom_iommu != dev_iommu_priv_get(dev))) return -EINVAL; - } } return iommu_fwspec_add_ids(dev, &asid, 1); From bd2f551f575c72e326a4eafa23dd7e47cfafc754 Mon Sep 17 00:00:00 2001 From: Johan Hovold Date: Mon, 20 Oct 2025 06:53:17 +0200 Subject: [PATCH 1876/3902] iommu/sun50i: fix device leak on of_xlate() commit f916109bf53864605d10bf6f4215afa023a80406 upstream. Make sure to drop the reference taken to the iommu platform device when looking up its driver data during of_xlate(). Fixes: 4100b8c229b3 ("iommu: Add Allwinner H6 IOMMU driver") Cc: stable@vger.kernel.org # 5.8 Cc: Maxime Ripard Acked-by: Robin Murphy Signed-off-by: Johan Hovold Signed-off-by: Joerg Roedel Signed-off-by: Greg Kroah-Hartman --- drivers/iommu/sun50i-iommu.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/drivers/iommu/sun50i-iommu.c b/drivers/iommu/sun50i-iommu.c index de10b569d9a940..6306570d57db4f 100644 --- a/drivers/iommu/sun50i-iommu.c +++ b/drivers/iommu/sun50i-iommu.c @@ -839,6 +839,8 @@ static int sun50i_iommu_of_xlate(struct device *dev, dev_iommu_priv_set(dev, platform_get_drvdata(iommu_pdev)); + put_device(&iommu_pdev->dev); + return iommu_fwspec_add_ids(dev, &id, 1); } From b16243e7164c0584049a640e3efa2d3e750f7d33 Mon Sep 17 00:00:00 2001 From: Johan Hovold Date: Mon, 20 Oct 2025 06:53:18 +0200 Subject: [PATCH 1877/3902] iommu/tegra: fix device leak on probe_device() commit c08934a61201db8f1d1c66fcc63fb2eb526b656d upstream. Make sure to drop the reference taken to the iommu platform device when looking up its driver data during probe_device(). Note that commit 9826e393e4a8 ("iommu/tegra-smmu: Fix missing put_device() call in tegra_smmu_find") fixed the leak in an error path, but the reference is still leaking on success. Fixes: 891846516317 ("memory: Add NVIDIA Tegra memory controller support") Cc: stable@vger.kernel.org # 3.19: 9826e393e4a8 Cc: Miaoqian Lin Acked-by: Robin Murphy Acked-by: Thierry Reding Signed-off-by: Johan Hovold Signed-off-by: Joerg Roedel Signed-off-by: Greg Kroah-Hartman --- drivers/iommu/tegra-smmu.c | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/drivers/iommu/tegra-smmu.c b/drivers/iommu/tegra-smmu.c index 36cdd5fbab0779..f6f26a07282083 100644 --- a/drivers/iommu/tegra-smmu.c +++ b/drivers/iommu/tegra-smmu.c @@ -830,10 +830,9 @@ static struct tegra_smmu *tegra_smmu_find(struct device_node *np) return NULL; mc = platform_get_drvdata(pdev); - if (!mc) { - put_device(&pdev->dev); + put_device(&pdev->dev); + if (!mc) return NULL; - } return mc->smmu; } From c341dee80b5df49a936182341b36395c831c2661 Mon Sep 17 00:00:00 2001 From: Lu Baolu Date: Wed, 22 Oct 2025 16:26:27 +0800 Subject: [PATCH 1878/3902] iommu: disable SVA when CONFIG_X86 is set commit 72f98ef9a4be30d2a60136dd6faee376f780d06c upstream. Patch series "Fix stale IOTLB entries for kernel address space", v7. This proposes a fix for a security vulnerability related to IOMMU Shared Virtual Addressing (SVA). In an SVA context, an IOMMU can cache kernel page table entries. When a kernel page table page is freed and reallocated for another purpose, the IOMMU might still hold stale, incorrect entries. This can be exploited to cause a use-after-free or write-after-free condition, potentially leading to privilege escalation or data corruption. This solution introduces a deferred freeing mechanism for kernel page table pages, which provides a safe window to notify the IOMMU to invalidate its caches before the page is reused. This patch (of 8): In the IOMMU Shared Virtual Addressing (SVA) context, the IOMMU hardware shares and walks the CPU's page tables. The x86 architecture maps the kernel's virtual address space into the upper portion of every process's page table. Consequently, in an SVA context, the IOMMU hardware can walk and cache kernel page table entries. The Linux kernel currently lacks a notification mechanism for kernel page table changes, specifically when page table pages are freed and reused. The IOMMU driver is only notified of changes to user virtual address mappings. This can cause the IOMMU's internal caches to retain stale entries for kernel VA. Use-After-Free (UAF) and Write-After-Free (WAF) conditions arise when kernel page table pages are freed and later reallocated. The IOMMU could misinterpret the new data as valid page table entries. The IOMMU might then walk into attacker-controlled memory, leading to arbitrary physical memory DMA access or privilege escalation. This is also a Write-After-Free issue, as the IOMMU will potentially continue to write Accessed and Dirty bits to the freed memory while attempting to walk the stale page tables. Currently, SVA contexts are unprivileged and cannot access kernel mappings. However, the IOMMU will still walk kernel-only page tables all the way down to the leaf entries, where it realizes the mapping is for the kernel and errors out. This means the IOMMU still caches these intermediate page table entries, making the described vulnerability a real concern. Disable SVA on x86 architecture until the IOMMU can receive notification to flush the paging cache before freeing the CPU kernel page table pages. Link: https://lkml.kernel.org/r/20251022082635.2462433-1-baolu.lu@linux.intel.com Link: https://lkml.kernel.org/r/20251022082635.2462433-2-baolu.lu@linux.intel.com Fixes: 26b25a2b98e4 ("iommu: Bind process address spaces to devices") Signed-off-by: Lu Baolu Suggested-by: Jason Gunthorpe Reviewed-by: Jason Gunthorpe Cc: Alistair Popple Cc: Andy Lutomirski Cc: Borislav Betkov Cc: Dave Hansen Cc: David Hildenbrand Cc: Ingo Molnar Cc: Jann Horn Cc: Jean-Philippe Brucker Cc: Joerg Roedel Cc: Kevin Tian Cc: Liam Howlett Cc: Lorenzo Stoakes Cc: Matthew Wilcox (Oracle) Cc: Michal Hocko Cc: Mike Rapoport Cc: Peter Zijlstra Cc: Robin Murohy Cc: Thomas Gleinxer Cc: "Uladzislau Rezki (Sony)" Cc: Vasant Hegde Cc: Vinicius Costa Gomes Cc: Vlastimil Babka Cc: Will Deacon Cc: Yi Lai Cc: Signed-off-by: Andrew Morton Signed-off-by: Greg Kroah-Hartman --- drivers/iommu/iommu-sva.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/drivers/iommu/iommu-sva.c b/drivers/iommu/iommu-sva.c index 1a51cfd82808df..a0442faad952ca 100644 --- a/drivers/iommu/iommu-sva.c +++ b/drivers/iommu/iommu-sva.c @@ -77,6 +77,9 @@ struct iommu_sva *iommu_sva_bind_device(struct device *dev, struct mm_struct *mm if (!group) return ERR_PTR(-ENODEV); + if (IS_ENABLED(CONFIG_X86)) + return ERR_PTR(-EOPNOTSUPP); + mutex_lock(&iommu_sva_lock); /* Allocate mm->pasid if necessary. */ From 1e9cf600da98affcc652857a35ba637d13126bfc Mon Sep 17 00:00:00 2001 From: Armin Wolf Date: Wed, 3 Dec 2025 21:21:09 +0100 Subject: [PATCH 1879/3902] hwmon: (dell-smm) Fix off-by-one error in dell_smm_is_visible() commit fae00a7186cecf90a57757a63b97a0cbcf384fe9 upstream. The documentation states that on machines supporting only global fan mode control, the pwmX_enable attributes should only be created for the first fan channel (pwm1_enable, aka channel 0). Fix the off-by-one error caused by the fact that fan channels have a zero-based index. Cc: stable@vger.kernel.org Fixes: 1c1658058c99 ("hwmon: (dell-smm) Add support for automatic fan mode") Signed-off-by: Armin Wolf Link: https://lore.kernel.org/r/20251203202109.331528-1-W_Armin@gmx.de Signed-off-by: Guenter Roeck Signed-off-by: Greg Kroah-Hartman --- drivers/hwmon/dell-smm-hwmon.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/hwmon/dell-smm-hwmon.c b/drivers/hwmon/dell-smm-hwmon.c index f0e8a9bc0d0e0a..8cf12b9bae2a74 100644 --- a/drivers/hwmon/dell-smm-hwmon.c +++ b/drivers/hwmon/dell-smm-hwmon.c @@ -864,9 +864,9 @@ static umode_t dell_smm_is_visible(const void *drvdata, enum hwmon_sensor_types if (auto_fan) { /* * The setting affects all fans, so only create a - * single attribute. + * single attribute for the first fan channel. */ - if (channel != 1) + if (channel != 0) return 0; /* From 36b5f6b52ca7ba98249591cf5f62f6e1fbe23749 Mon Sep 17 00:00:00 2001 From: Hans de Goede Date: Sat, 8 Nov 2025 22:03:18 +0100 Subject: [PATCH 1880/3902] HID: logitech-dj: Remove duplicate error logging commit ca389a55d8b2d86a817433bf82e0602b68c4d541 upstream. logi_dj_recv_query_paired_devices() and logi_dj_recv_switch_to_dj_mode() both have 2 callers which all log an error if the function fails. Move the error logging to inside these 2 functions to remove the duplicated error logging in the callers. While at it also move the logi_dj_recv_send_report() call error handling in logi_dj_recv_switch_to_dj_mode() to directly after the call. That call only fails if the report cannot be found and in that case it does nothing, so the msleep() is not necessary on failures. Fixes: 6f20d3261265 ("HID: logitech-dj: Fix error handling in logi_dj_recv_switch_to_dj_mode()") Cc: stable@vger.kernel.org Signed-off-by: Hans de Goede Signed-off-by: Jiri Kosina Signed-off-by: Greg Kroah-Hartman --- drivers/hid/hid-logitech-dj.c | 56 ++++++++++++++--------------------- 1 file changed, 23 insertions(+), 33 deletions(-) diff --git a/drivers/hid/hid-logitech-dj.c b/drivers/hid/hid-logitech-dj.c index cce54dd9884a3e..3b5412541c925b 100644 --- a/drivers/hid/hid-logitech-dj.c +++ b/drivers/hid/hid-logitech-dj.c @@ -805,7 +805,6 @@ static void delayedwork_callback(struct work_struct *work) struct dj_workitem workitem; unsigned long flags; int count; - int retval; dbg_hid("%s\n", __func__); @@ -842,11 +841,7 @@ static void delayedwork_callback(struct work_struct *work) logi_dj_recv_destroy_djhid_device(djrcv_dev, &workitem); break; case WORKITEM_TYPE_UNKNOWN: - retval = logi_dj_recv_query_paired_devices(djrcv_dev); - if (retval) { - hid_err(djrcv_dev->hidpp, "%s: logi_dj_recv_query_paired_devices error: %d\n", - __func__, retval); - } + logi_dj_recv_query_paired_devices(djrcv_dev); break; case WORKITEM_TYPE_EMPTY: dbg_hid("%s: device list is empty\n", __func__); @@ -1239,8 +1234,10 @@ static int logi_dj_recv_query_paired_devices(struct dj_receiver_dev *djrcv_dev) djrcv_dev->last_query = jiffies; - if (djrcv_dev->type != recvr_type_dj) - return logi_dj_recv_query_hidpp_devices(djrcv_dev); + if (djrcv_dev->type != recvr_type_dj) { + retval = logi_dj_recv_query_hidpp_devices(djrcv_dev); + goto out; + } dj_report = kzalloc(sizeof(struct dj_report), GFP_KERNEL); if (!dj_report) @@ -1250,6 +1247,10 @@ static int logi_dj_recv_query_paired_devices(struct dj_receiver_dev *djrcv_dev) dj_report->report_type = REPORT_TYPE_CMD_GET_PAIRED_DEVICES; retval = logi_dj_recv_send_report(djrcv_dev, dj_report); kfree(dj_report); +out: + if (retval < 0) + hid_err(djrcv_dev->hidpp, "%s error:%d\n", __func__, retval); + return retval; } @@ -1275,6 +1276,8 @@ static int logi_dj_recv_switch_to_dj_mode(struct dj_receiver_dev *djrcv_dev, (u8)timeout; retval = logi_dj_recv_send_report(djrcv_dev, dj_report); + if (retval) + goto out; /* * Ugly sleep to work around a USB 3.0 bug when the receiver is @@ -1283,11 +1286,6 @@ static int logi_dj_recv_switch_to_dj_mode(struct dj_receiver_dev *djrcv_dev, * 50 msec should gives enough time to the receiver to be ready. */ msleep(50); - - if (retval) { - kfree(dj_report); - return retval; - } } /* @@ -1313,7 +1311,12 @@ static int logi_dj_recv_switch_to_dj_mode(struct dj_receiver_dev *djrcv_dev, HIDPP_REPORT_SHORT_LENGTH, HID_OUTPUT_REPORT, HID_REQ_SET_REPORT); +out: kfree(dj_report); + + if (retval < 0) + hid_err(hdev, "%s error:%d\n", __func__, retval); + return retval; } @@ -1835,11 +1838,8 @@ static int logi_dj_probe(struct hid_device *hdev, if (has_hidpp) { retval = logi_dj_recv_switch_to_dj_mode(djrcv_dev, 0); - if (retval < 0) { - hid_err(hdev, "%s: logi_dj_recv_switch_to_dj_mode returned error:%d\n", - __func__, retval); + if (retval < 0) goto switch_to_dj_mode_fail; - } } /* This is enabling the polling urb on the IN endpoint */ @@ -1857,15 +1857,11 @@ static int logi_dj_probe(struct hid_device *hdev, spin_lock_irqsave(&djrcv_dev->lock, flags); djrcv_dev->ready = true; spin_unlock_irqrestore(&djrcv_dev->lock, flags); - retval = logi_dj_recv_query_paired_devices(djrcv_dev); - if (retval < 0) { - hid_err(hdev, "%s: logi_dj_recv_query_paired_devices error:%d\n", - __func__, retval); - /* - * This can happen with a KVM, let the probe succeed, - * logi_dj_recv_queue_unknown_work will retry later. - */ - } + /* + * This can fail with a KVM. Ignore errors to let the probe + * succeed, logi_dj_recv_queue_unknown_work will retry later. + */ + logi_dj_recv_query_paired_devices(djrcv_dev); } return 0; @@ -1882,18 +1878,12 @@ static int logi_dj_probe(struct hid_device *hdev, #ifdef CONFIG_PM static int logi_dj_reset_resume(struct hid_device *hdev) { - int retval; struct dj_receiver_dev *djrcv_dev = hid_get_drvdata(hdev); if (!djrcv_dev || djrcv_dev->hidpp != hdev) return 0; - retval = logi_dj_recv_switch_to_dj_mode(djrcv_dev, 0); - if (retval < 0) { - hid_err(hdev, "%s: logi_dj_recv_switch_to_dj_mode returned error:%d\n", - __func__, retval); - } - + logi_dj_recv_switch_to_dj_mode(djrcv_dev, 0); return 0; } #endif From 7c8b636d3ce7cca14ca29fbbe0621a59f73ac67e Mon Sep 17 00:00:00 2001 From: Raghavendra Rao Ananta Date: Fri, 31 Oct 2025 17:06:03 +0000 Subject: [PATCH 1881/3902] hisi_acc_vfio_pci: Add .match_token_uuid callback in hisi_acc_vfio_pci_migrn_ops commit 0ed3a30fd996cb0cac872432cf25185fda7e5316 upstream. The commit, <86624ba3b522> ("vfio/pci: Do vf_token checks for VFIO_DEVICE_BIND_IOMMUFD") accidentally ignored including the .match_token_uuid callback in the hisi_acc_vfio_pci_migrn_ops struct. Introduce the missed callback here. Fixes: 86624ba3b522 ("vfio/pci: Do vf_token checks for VFIO_DEVICE_BIND_IOMMUFD") Cc: stable@vger.kernel.org Suggested-by: Longfang Liu Signed-off-by: Raghavendra Rao Ananta Reviewed-by: Longfang Liu Reviewed-by: Jason Gunthorpe Link: https://lore.kernel.org/r/20251031170603.2260022-3-rananta@google.com Signed-off-by: Alex Williamson Signed-off-by: Greg Kroah-Hartman --- drivers/vfio/pci/hisilicon/hisi_acc_vfio_pci.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/vfio/pci/hisilicon/hisi_acc_vfio_pci.c b/drivers/vfio/pci/hisilicon/hisi_acc_vfio_pci.c index fde33f54e99ec5..d07093d7cc3f5c 100644 --- a/drivers/vfio/pci/hisilicon/hisi_acc_vfio_pci.c +++ b/drivers/vfio/pci/hisilicon/hisi_acc_vfio_pci.c @@ -1564,6 +1564,7 @@ static const struct vfio_device_ops hisi_acc_vfio_pci_migrn_ops = { .mmap = hisi_acc_vfio_pci_mmap, .request = vfio_pci_core_request, .match = vfio_pci_core_match, + .match_token_uuid = vfio_pci_core_match_token_uuid, .bind_iommufd = vfio_iommufd_physical_bind, .unbind_iommufd = vfio_iommufd_physical_unbind, .attach_ioas = vfio_iommufd_physical_attach_ioas, From c0a565f133f385f15e69db46e4a9f8d35fe2afca Mon Sep 17 00:00:00 2001 From: Shengming Hu Date: Wed, 26 Nov 2025 17:29:26 +0800 Subject: [PATCH 1882/3902] fgraph: Initialize ftrace_ops->private for function graph ops commit b5d6d3f73d0bac4a7e3a061372f6da166fc6ee5c upstream. The ftrace_pids_enabled(op) check relies on op->private being properly initialized, but fgraph_ops's underlying ftrace_ops->private was left uninitialized. This caused ftrace_pids_enabled() to always return false, effectively disabling PID filtering for function graph tracing. Fix this by copying src_ops->private to dst_ops->private in fgraph_init_ops(), ensuring PID filter state is correctly propagated. Cc: stable@vger.kernel.org Cc: Cc: Cc: Cc: Cc: Cc: Fixes: c132be2c4fcc1 ("function_graph: Have the instances use their own ftrace_ops for filtering") Link: https://patch.msgid.link/20251126172926004y3hC8QyU4WFOjBkU_UxLC@zte.com.cn Signed-off-by: Shengming Hu Signed-off-by: Steven Rostedt (Google) Signed-off-by: Greg Kroah-Hartman --- kernel/trace/fgraph.c | 1 + 1 file changed, 1 insertion(+) diff --git a/kernel/trace/fgraph.c b/kernel/trace/fgraph.c index 484ad7a18463a9..d6222bb99d1d75 100644 --- a/kernel/trace/fgraph.c +++ b/kernel/trace/fgraph.c @@ -1019,6 +1019,7 @@ void fgraph_init_ops(struct ftrace_ops *dst_ops, mutex_init(&dst_ops->local_hash.regex_lock); INIT_LIST_HEAD(&dst_ops->subop_list); dst_ops->flags |= FTRACE_OPS_FL_INITIALIZED; + dst_ops->private = src_ops->private; } #endif } From 1a793f06aa69c59024019dadf09dd736954f2a9b Mon Sep 17 00:00:00 2001 From: Shengming Hu Date: Wed, 26 Nov 2025 17:33:31 +0800 Subject: [PATCH 1883/3902] fgraph: Check ftrace_pids_enabled on registration for early filtering commit 1650a1b6cb1ae6cb99bb4fce21b30ebdf9fc238e upstream. When registering ftrace_graph, check if ftrace_pids_enabled is active. If enabled, assign entryfunc to fgraph_pid_func to ensure filtering is performed before executing the saved original entry function. Cc: stable@vger.kernel.org Cc: Cc: Cc: Cc: Cc: Cc: Link: https://patch.msgid.link/20251126173331679XGVF98NLhyLJRdtNkVZ6w@zte.com.cn Fixes: df3ec5da6a1e7 ("function_graph: Add pid tracing back to function graph tracer") Signed-off-by: Shengming Hu Signed-off-by: Steven Rostedt (Google) Signed-off-by: Greg Kroah-Hartman --- kernel/trace/fgraph.c | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/kernel/trace/fgraph.c b/kernel/trace/fgraph.c index d6222bb99d1d75..599f2939cd940c 100644 --- a/kernel/trace/fgraph.c +++ b/kernel/trace/fgraph.c @@ -1377,6 +1377,13 @@ int register_ftrace_graph(struct fgraph_ops *gops) ftrace_graph_active++; + /* Always save the function, and reset at unregistering */ + gops->saved_func = gops->entryfunc; +#ifdef CONFIG_DYNAMIC_FTRACE + if (ftrace_pids_enabled(&gops->ops)) + gops->entryfunc = fgraph_pid_func; +#endif + if (ftrace_graph_active == 2) ftrace_graph_disable_direct(true); @@ -1396,8 +1403,6 @@ int register_ftrace_graph(struct fgraph_ops *gops) } else { init_task_vars(gops->idx); } - /* Always save the function, and reset at unregistering */ - gops->saved_func = gops->entryfunc; gops->ops.flags |= FTRACE_OPS_FL_GRAPH; From 382895a288515f6f5b151dfb13e7722c332113ed Mon Sep 17 00:00:00 2001 From: Lukas Wunner Date: Wed, 19 Nov 2025 09:50:01 +0100 Subject: [PATCH 1884/3902] PCI/PM: Reinstate clearing state_saved in legacy and !PM codepaths commit 894f475f88e06c0f352c829849560790dbdedbe5 upstream. When a PCI device is suspended, it is normally the PCI core's job to save Config Space and put the device into a low power state. However drivers are allowed to assume these responsibilities. When they do, the PCI core can tell by looking at the state_saved flag in struct pci_dev: The flag is cleared before commencing the suspend sequence and it is set when pci_save_state() is called. If the PCI core finds the flag set late in the suspend sequence, it refrains from calling pci_save_state() itself. But there are two corner cases where the PCI core neglects to clear the flag before commencing the suspend sequence: * If a driver has legacy PCI PM callbacks, pci_legacy_suspend() neglects to clear the flag. The (stale) flag is subsequently queried by pci_legacy_suspend() itself and pci_legacy_suspend_late(). * If a device has no driver or its driver has no PCI PM callbacks, pci_pm_freeze() neglects to clear the flag. The (stale) flag is subsequently queried by pci_pm_freeze_noirq(). The flag may be set prior to suspend if the device went through error recovery: Drivers commonly invoke pci_restore_state() + pci_save_state() to restore Config Space after reset. The flag may also be set if drivers call pci_save_state() on probe to allow for recovery from subsequent errors. The result is that pci_legacy_suspend_late() and pci_pm_freeze_noirq() don't call pci_save_state() and so the state that will be restored on resume is the one recorded on last error recovery or on probe, not the one that the device had on suspend. If the two states happen to be identical, there's no problem. Reinstate clearing the flag in pci_legacy_suspend() and pci_pm_freeze(). The two functions used to do that until commit 4b77b0a2ba27 ("PCI: Clear saved_state after the state has been restored") deemed it unnecessary because it assumed that it's sufficient to clear the flag on resume in pci_restore_state(). The commit seemingly did not take into account that pci_save_state() and pci_restore_state() are not only used by power management code, but also for error recovery. Devices without driver or whose driver has no PCI PM callbacks may be in runtime suspend when pci_pm_freeze() is called. Their state has already been saved, so don't clear the flag to skip a pointless pci_save_state() in pci_pm_freeze_noirq(). None of the drivers with legacy PCI PM callbacks seem to use runtime PM, so clear the flag unconditionally in their case. Fixes: 4b77b0a2ba27 ("PCI: Clear saved_state after the state has been restored") Signed-off-by: Lukas Wunner Signed-off-by: Bjorn Helgaas Reviewed-by: Rafael J. Wysocki (Intel) Cc: stable@vger.kernel.org # v2.6.32+ Link: https://patch.msgid.link/094f2aad64418710daf0940112abe5a0afdc6bce.1763483367.git.lukas@wunner.de Signed-off-by: Greg Kroah-Hartman --- drivers/pci/pci-driver.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/drivers/pci/pci-driver.c b/drivers/pci/pci-driver.c index 302d61783f6c04..327b21c48614db 100644 --- a/drivers/pci/pci-driver.c +++ b/drivers/pci/pci-driver.c @@ -629,6 +629,8 @@ static int pci_legacy_suspend(struct device *dev, pm_message_t state) struct pci_dev *pci_dev = to_pci_dev(dev); struct pci_driver *drv = pci_dev->driver; + pci_dev->state_saved = false; + if (drv && drv->suspend) { pci_power_t prev = pci_dev->current_state; int error; @@ -1036,6 +1038,8 @@ static int pci_pm_freeze(struct device *dev) if (!pm) { pci_pm_default_suspend(pci_dev); + if (!pm_runtime_suspended(dev)) + pci_dev->state_saved = false; return 0; } From f1765578621f4c7b32627059b966ccc3930872a5 Mon Sep 17 00:00:00 2001 From: Siddharth Vadapalli Date: Wed, 19 Nov 2025 21:31:05 +0530 Subject: [PATCH 1885/3902] arm64: dts: ti: k3-j721e-sk: Fix pinmux for pin Y1 used by power regulator commit 51f89c488f2ecc020f82bfedd77482584ce8027a upstream. The SoC pin Y1 is incorrectly defined in the WKUP Pinmux device-tree node (pinctrl@4301c000) leading to the following silent failure: pinctrl-single 4301c000.pinctrl: mux offset out of range: 0x1dc (0x178) According to the datasheet for the J721E SoC [0], the pin Y1 belongs to the MAIN Pinmux device-tree node (pinctrl@11c000). This is confirmed by the address of the pinmux register for it on page 142 of the datasheet which is 0x00011C1DC. Hence fix it. [0]: https://www.ti.com/lit/ds/symlink/tda4vm.pdf Fixes: 97b67cc102dc ("arm64: dts: ti: k3-j721e-sk: Add DT nodes for power regulators") Cc: stable@vger.kernel.org Signed-off-by: Siddharth Vadapalli Reviewed-by: Yemike Abhilash Chandra Link: https://patch.msgid.link/20251119160148.2752616-1-s-vadapalli@ti.com Signed-off-by: Vignesh Raghavendra Signed-off-by: Greg Kroah-Hartman --- arch/arm64/boot/dts/ti/k3-j721e-sk.dts | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/arch/arm64/boot/dts/ti/k3-j721e-sk.dts b/arch/arm64/boot/dts/ti/k3-j721e-sk.dts index 5e5784ef6f8585..77dcc160eda3f7 100644 --- a/arch/arm64/boot/dts/ti/k3-j721e-sk.dts +++ b/arch/arm64/boot/dts/ti/k3-j721e-sk.dts @@ -474,6 +474,12 @@ J721E_IOPAD(0x234, PIN_INPUT, 7) /* (U3) EXT_REFCLK1.GPIO1_12 */ >; }; + + vdd_sd_dv_pins_default: vdd-sd-dv-default-pins { + pinctrl-single,pins = < + J721E_IOPAD(0x1dc, PIN_OUTPUT, 7) /* (Y1) SPI1_CLK.GPIO0_118 */ + >; + }; }; &wkup_pmx0 { @@ -536,12 +542,6 @@ >; }; - vdd_sd_dv_pins_default: vdd-sd-dv-default-pins { - pinctrl-single,pins = < - J721E_IOPAD(0x1dc, PIN_OUTPUT, 7) /* (Y1) SPI1_CLK.GPIO0_118 */ - >; - }; - wkup_uart0_pins_default: wkup-uart0-default-pins { pinctrl-single,pins = < J721E_WKUP_IOPAD(0xa0, PIN_INPUT, 0) /* (J29) WKUP_UART0_RXD */ From 96bdd7ba179fca2946a914df1df47836795f2206 Mon Sep 17 00:00:00 2001 From: Dave Vasilevsky Date: Sun, 16 Nov 2025 01:40:46 -0500 Subject: [PATCH 1886/3902] powerpc, mm: Fix mprotect on book3s 32-bit commit 78fc63ffa7813e33681839bb33826c24195f0eb7 upstream. On 32-bit book3s with hash-MMUs, tlb_flush() was a no-op. This was unnoticed because all uses until recently were for unmaps, and thus handled by __tlb_remove_tlb_entry(). After commit 4a18419f71cd ("mm/mprotect: use mmu_gather") in kernel 5.19, tlb_gather_mmu() started being used for mprotect as well. This caused mprotect to simply not work on these machines: int *ptr = mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0); *ptr = 1; // force HPTE to be created mprotect(ptr, 4096, PROT_READ); *ptr = 2; // should segfault, but succeeds Fixed by making tlb_flush() actually flush TLB pages. This finally agrees with the behaviour of boot3s64's tlb_flush(). Fixes: 4a18419f71cd ("mm/mprotect: use mmu_gather") Cc: stable@vger.kernel.org Reviewed-by: Christophe Leroy Reviewed-by: Ritesh Harjani (IBM) Signed-off-by: Dave Vasilevsky Signed-off-by: Madhavan Srinivasan Link: https://patch.msgid.link/20251116-vasi-mprotect-g3-v3-1-59a9bd33ba00@vasilevsky.ca Signed-off-by: Greg Kroah-Hartman --- arch/powerpc/include/asm/book3s/32/tlbflush.h | 5 ++++- arch/powerpc/mm/book3s32/tlb.c | 9 +++++++++ 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/arch/powerpc/include/asm/book3s/32/tlbflush.h b/arch/powerpc/include/asm/book3s/32/tlbflush.h index e43534da5207aa..4be2200a3c7e1e 100644 --- a/arch/powerpc/include/asm/book3s/32/tlbflush.h +++ b/arch/powerpc/include/asm/book3s/32/tlbflush.h @@ -11,6 +11,7 @@ void hash__flush_tlb_mm(struct mm_struct *mm); void hash__flush_tlb_page(struct vm_area_struct *vma, unsigned long vmaddr); void hash__flush_range(struct mm_struct *mm, unsigned long start, unsigned long end); +void hash__flush_gather(struct mmu_gather *tlb); #ifdef CONFIG_SMP void _tlbie(unsigned long address); @@ -29,7 +30,9 @@ void _tlbia(void); static inline void tlb_flush(struct mmu_gather *tlb) { /* 603 needs to flush the whole TLB here since it doesn't use a hash table. */ - if (!mmu_has_feature(MMU_FTR_HPTE_TABLE)) + if (mmu_has_feature(MMU_FTR_HPTE_TABLE)) + hash__flush_gather(tlb); + else _tlbia(); } diff --git a/arch/powerpc/mm/book3s32/tlb.c b/arch/powerpc/mm/book3s32/tlb.c index 9ad6b56bfec96e..e54a7b0112322e 100644 --- a/arch/powerpc/mm/book3s32/tlb.c +++ b/arch/powerpc/mm/book3s32/tlb.c @@ -105,3 +105,12 @@ void hash__flush_tlb_page(struct vm_area_struct *vma, unsigned long vmaddr) flush_hash_pages(mm->context.id, vmaddr, pmd_val(*pmd), 1); } EXPORT_SYMBOL(hash__flush_tlb_page); + +void hash__flush_gather(struct mmu_gather *tlb) +{ + if (tlb->fullmm || tlb->need_flush_all) + hash__flush_tlb_mm(tlb->mm); + else + hash__flush_range(tlb->mm, tlb->start, tlb->end); +} +EXPORT_SYMBOL(hash__flush_gather); From 4ae1e46d8a290319f33f71a2710a1382ba5431e8 Mon Sep 17 00:00:00 2001 From: Donet Tom Date: Thu, 30 Oct 2025 20:27:26 +0530 Subject: [PATCH 1887/3902] powerpc/64s/slb: Fix SLB multihit issue during SLB preload MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit commit 00312419f0863964625d6dcda8183f96849412c6 upstream. On systems using the hash MMU, there is a software SLB preload cache that mirrors the entries loaded into the hardware SLB buffer. This preload cache is subject to periodic eviction — typically after every 256 context switches — to remove old entry. To optimize performance, the kernel skips switch_mmu_context() in switch_mm_irqs_off() when the prev and next mm_struct are the same. However, on hash MMU systems, this can lead to inconsistencies between the hardware SLB and the software preload cache. If an SLB entry for a process is evicted from the software cache on one CPU, and the same process later runs on another CPU without executing switch_mmu_context(), the hardware SLB may retain stale entries. If the kernel then attempts to reload that entry, it can trigger an SLB multi-hit error. The following timeline shows how stale SLB entries are created and can cause a multi-hit error when a process moves between CPUs without a MMU context switch. CPU 0 CPU 1 ----- ----- Process P exec swapper/1 load_elf_binary begin_new_exc activate_mm switch_mm_irqs_off switch_mmu_context switch_slb /* * This invalidates all * the entries in the HW * and setup the new HW * SLB entries as per the * preload cache. */ context_switch sched_migrate_task migrates process P to cpu-1 Process swapper/0 context switch (to process P) (uses mm_struct of Process P) switch_mm_irqs_off() switch_slb load_slb++ /* * load_slb becomes 0 here * and we evict an entry from * the preload cache with * preload_age(). We still * keep HW SLB and preload * cache in sync, that is * because all HW SLB entries * anyways gets evicted in * switch_slb during SLBIA. * We then only add those * entries back in HW SLB, * which are currently * present in preload_cache * (after eviction). */ load_elf_binary continues... setup_new_exec() slb_setup_new_exec() sched_switch event sched_migrate_task migrates process P to cpu-0 context_switch from swapper/0 to Process P switch_mm_irqs_off() /* * Since both prev and next mm struct are same we don't call * switch_mmu_context(). This will cause the HW SLB and SW preload * cache to go out of sync in preload_new_slb_context. Because there * was an SLB entry which was evicted from both HW and preload cache * on cpu-1. Now later in preload_new_slb_context(), when we will try * to add the same preload entry again, we will add this to the SW * preload cache and then will add it to the HW SLB. Since on cpu-0 * this entry was never invalidated, hence adding this entry to the HW * SLB will cause a SLB multi-hit error. */ load_elf_binary continues... START_THREAD start_thread preload_new_slb_context /* * This tries to add a new EA to preload cache which was earlier * evicted from both cpu-1 HW SLB and preload cache. This caused the * HW SLB of cpu-0 to go out of sync with the SW preload cache. The * reason for this was, that when we context switched back on CPU-0, * we should have ideally called switch_mmu_context() which will * bring the HW SLB entries on CPU-0 in sync with SW preload cache * entries by setting up the mmu context properly. But we didn't do * that since the prev mm_struct running on cpu-0 was same as the * next mm_struct (which is true for swapper / kernel threads). So * now when we try to add this new entry into the HW SLB of cpu-0, * we hit a SLB multi-hit error. */ WARNING: CPU: 0 PID: 1810970 at arch/powerpc/mm/book3s64/slb.c:62 assert_slb_presence+0x2c/0x50(48 results) 02:47:29 [20157/42149] Modules linked in: CPU: 0 UID: 0 PID: 1810970 Comm: dd Not tainted 6.16.0-rc3-dirty #12 VOLUNTARY Hardware name: IBM pSeries (emulated by qemu) POWER8 (architected) 0x4d0200 0xf000004 of:SLOF,HEAD hv:linux,kvm pSeries NIP: c00000000015426c LR: c0000000001543b4 CTR: 0000000000000000 REGS: c0000000497c77e0 TRAP: 0700 Not tainted (6.16.0-rc3-dirty) MSR: 8000000002823033 CR: 28888482 XER: 00000000 CFAR: c0000000001543b0 IRQMASK: 3 <...> NIP [c00000000015426c] assert_slb_presence+0x2c/0x50 LR [c0000000001543b4] slb_insert_entry+0x124/0x390 Call Trace: 0x7fffceb5ffff (unreliable) preload_new_slb_context+0x100/0x1a0 start_thread+0x26c/0x420 load_elf_binary+0x1b04/0x1c40 bprm_execve+0x358/0x680 do_execveat_common+0x1f8/0x240 sys_execve+0x58/0x70 system_call_exception+0x114/0x300 system_call_common+0x160/0x2c4 >From the above analysis, during early exec the hardware SLB is cleared, and entries from the software preload cache are reloaded into hardware by switch_slb. However, preload_new_slb_context and slb_setup_new_exec also attempt to load some of the same entries, which can trigger a multi-hit. In most cases, these additional preloads simply hit existing entries and add nothing new. Removing these functions avoids redundant preloads and eliminates the multi-hit issue. This patch removes these two functions. We tested process switching performance using the context_switch benchmark on POWER9/hash, and observed no regression. Without this patch: 129041 ops/sec With this patch: 129341 ops/sec We also measured SLB faults during boot, and the counts are essentially the same with and without this patch. SLB faults without this patch: 19727 SLB faults with this patch: 19786 Fixes: 5434ae74629a ("powerpc/64s/hash: Add a SLB preload cache") cc: stable@vger.kernel.org Suggested-by: Nicholas Piggin Signed-off-by: Donet Tom Signed-off-by: Ritesh Harjani (IBM) Signed-off-by: Madhavan Srinivasan Link: https://patch.msgid.link/0ac694ae683494fe8cadbd911a1a5018d5d3c541.1761834163.git.ritesh.list@gmail.com Signed-off-by: Greg Kroah-Hartman --- arch/powerpc/include/asm/book3s/64/mmu-hash.h | 1 - arch/powerpc/kernel/process.c | 5 -- arch/powerpc/mm/book3s64/internal.h | 2 - arch/powerpc/mm/book3s64/mmu_context.c | 2 - arch/powerpc/mm/book3s64/slb.c | 88 ------------------- 5 files changed, 98 deletions(-) diff --git a/arch/powerpc/include/asm/book3s/64/mmu-hash.h b/arch/powerpc/include/asm/book3s/64/mmu-hash.h index 3463514232071e..af12e2ba8eb856 100644 --- a/arch/powerpc/include/asm/book3s/64/mmu-hash.h +++ b/arch/powerpc/include/asm/book3s/64/mmu-hash.h @@ -524,7 +524,6 @@ void slb_save_contents(struct slb_entry *slb_ptr); void slb_dump_contents(struct slb_entry *slb_ptr); extern void slb_vmalloc_update(void); -void preload_new_slb_context(unsigned long start, unsigned long sp); #ifdef CONFIG_PPC_64S_HASH_MMU void slb_set_size(u16 size); diff --git a/arch/powerpc/kernel/process.c b/arch/powerpc/kernel/process.c index eb23966ac0a9f0..a45fe147868bc4 100644 --- a/arch/powerpc/kernel/process.c +++ b/arch/powerpc/kernel/process.c @@ -1897,8 +1897,6 @@ int copy_thread(struct task_struct *p, const struct kernel_clone_args *args) return 0; } -void preload_new_slb_context(unsigned long start, unsigned long sp); - /* * Set up a thread for executing a new program */ @@ -1906,9 +1904,6 @@ void start_thread(struct pt_regs *regs, unsigned long start, unsigned long sp) { #ifdef CONFIG_PPC64 unsigned long load_addr = regs->gpr[2]; /* saved by ELF_PLAT_INIT */ - - if (IS_ENABLED(CONFIG_PPC_BOOK3S_64) && !radix_enabled()) - preload_new_slb_context(start, sp); #endif #ifdef CONFIG_PPC_TRANSACTIONAL_MEM diff --git a/arch/powerpc/mm/book3s64/internal.h b/arch/powerpc/mm/book3s64/internal.h index a57a25f06a215b..c26a6f0c90fcd2 100644 --- a/arch/powerpc/mm/book3s64/internal.h +++ b/arch/powerpc/mm/book3s64/internal.h @@ -24,8 +24,6 @@ static inline bool stress_hpt(void) void hpt_do_stress(unsigned long ea, unsigned long hpte_group); -void slb_setup_new_exec(void); - void exit_lazy_flush_tlb(struct mm_struct *mm, bool always_flush); #endif /* ARCH_POWERPC_MM_BOOK3S64_INTERNAL_H */ diff --git a/arch/powerpc/mm/book3s64/mmu_context.c b/arch/powerpc/mm/book3s64/mmu_context.c index 4e1e45420bd496..fb9dcf9ca59988 100644 --- a/arch/powerpc/mm/book3s64/mmu_context.c +++ b/arch/powerpc/mm/book3s64/mmu_context.c @@ -150,8 +150,6 @@ static int hash__init_new_context(struct mm_struct *mm) void hash__setup_new_exec(void) { slice_setup_new_exec(); - - slb_setup_new_exec(); } #else static inline int hash__init_new_context(struct mm_struct *mm) diff --git a/arch/powerpc/mm/book3s64/slb.c b/arch/powerpc/mm/book3s64/slb.c index 6b783552403c60..7e053c561a09df 100644 --- a/arch/powerpc/mm/book3s64/slb.c +++ b/arch/powerpc/mm/book3s64/slb.c @@ -328,94 +328,6 @@ static void preload_age(struct thread_info *ti) ti->slb_preload_tail = (ti->slb_preload_tail + 1) % SLB_PRELOAD_NR; } -void slb_setup_new_exec(void) -{ - struct thread_info *ti = current_thread_info(); - struct mm_struct *mm = current->mm; - unsigned long exec = 0x10000000; - - WARN_ON(irqs_disabled()); - - /* - * preload cache can only be used to determine whether a SLB - * entry exists if it does not start to overflow. - */ - if (ti->slb_preload_nr + 2 > SLB_PRELOAD_NR) - return; - - hard_irq_disable(); - - /* - * We have no good place to clear the slb preload cache on exec, - * flush_thread is about the earliest arch hook but that happens - * after we switch to the mm and have already preloaded the SLBEs. - * - * For the most part that's probably okay to use entries from the - * previous exec, they will age out if unused. It may turn out to - * be an advantage to clear the cache before switching to it, - * however. - */ - - /* - * preload some userspace segments into the SLB. - * Almost all 32 and 64bit PowerPC executables are linked at - * 0x10000000 so it makes sense to preload this segment. - */ - if (!is_kernel_addr(exec)) { - if (preload_add(ti, exec)) - slb_allocate_user(mm, exec); - } - - /* Libraries and mmaps. */ - if (!is_kernel_addr(mm->mmap_base)) { - if (preload_add(ti, mm->mmap_base)) - slb_allocate_user(mm, mm->mmap_base); - } - - /* see switch_slb */ - asm volatile("isync" : : : "memory"); - - local_irq_enable(); -} - -void preload_new_slb_context(unsigned long start, unsigned long sp) -{ - struct thread_info *ti = current_thread_info(); - struct mm_struct *mm = current->mm; - unsigned long heap = mm->start_brk; - - WARN_ON(irqs_disabled()); - - /* see above */ - if (ti->slb_preload_nr + 3 > SLB_PRELOAD_NR) - return; - - hard_irq_disable(); - - /* Userspace entry address. */ - if (!is_kernel_addr(start)) { - if (preload_add(ti, start)) - slb_allocate_user(mm, start); - } - - /* Top of stack, grows down. */ - if (!is_kernel_addr(sp)) { - if (preload_add(ti, sp)) - slb_allocate_user(mm, sp); - } - - /* Bottom of heap, grows up. */ - if (heap && !is_kernel_addr(heap)) { - if (preload_add(ti, heap)) - slb_allocate_user(mm, heap); - } - - /* see switch_slb */ - asm volatile("isync" : : : "memory"); - - local_irq_enable(); -} - static void slb_cache_slbie_kernel(unsigned int index) { unsigned long slbie_data = get_paca()->slb_cache[index]; From 2c3edc2b7aa39ea9898adfe92d5ee1ea89abfcea Mon Sep 17 00:00:00 2001 From: Kairui Song Date: Fri, 24 Oct 2025 02:34:11 +0800 Subject: [PATCH 1888/3902] mm, swap: do not perform synchronous discard during allocation commit 9fb749cd15078c7bdc46e5d45c37493f83323e33 upstream. Patch series "mm, swap: misc cleanup and bugfix", v2. A few cleanups and a bugfix that are either suitable after the swap table phase I or found during code review. Patch 1 is a bugfix and needs to be included in the stable branch, the rest have no behavioral change. This patch (of 5): Since commit 1b7e90020eb77 ("mm, swap: use percpu cluster as allocation fast path"), swap allocation is protected by a local lock, which means we can't do any sleeping calls during allocation. However, the discard routine is not taken well care of. When the swap allocator failed to find any usable cluster, it would look at the pending discard cluster and try to issue some blocking discards. It may not necessarily sleep, but the cond_resched at the bio layer indicates this is wrong when combined with a local lock. And the bio GFP flag used for discard bio is also wrong (not atomic). It's arguable whether this synchronous discard is helpful at all. In most cases, the async discard is good enough. And the swap allocator is doing very differently at organizing the clusters since the recent change, so it is very rare to see discard clusters piling up. So far, no issues have been observed or reported with typical SSD setups under months of high pressure. This issue was found during my code review. But by hacking the kernel a bit: adding a mdelay(500) in the async discard path, this issue will be observable with WARNING triggered by the wrong GFP and cond_resched in the bio layer for debug builds. So now let's apply a hotfix for this issue: remove the synchronous discard in the swap allocation path. And when order 0 is failing with all cluster list drained on all swap devices, try to do a discard following the swap device priority list. If any discards released some cluster, try the allocation again. This way, we can still avoid OOM due to swap failure if the hardware is very slow and memory pressure is extremely high. This may cause more fragmentation issues if the discarding hardware is really slow. Ideally, we want to discard pending clusters before continuing to iterate the fragment cluster lists. This can be implemented in a cleaner way if we clean up the device list iteration part first. Link: https://lkml.kernel.org/r/20251024-swap-clean-after-swap-table-p1-v2-0-a709469052e7@tencent.com Link: https://lkml.kernel.org/r/20251024-swap-clean-after-swap-table-p1-v2-1-c5b0e1092927@tencent.com Fixes: 1b7e90020eb7 ("mm, swap: use percpu cluster as allocation fast path") Signed-off-by: Kairui Song Acked-by: Nhat Pham Acked-by: Chris Li Cc: Baolin Wang Cc: Baoquan He Cc: Barry Song Cc: David Hildenbrand Cc: "Huang, Ying" Cc: Kemeng Shi Cc: Matthew Wilcox (Oracle) Cc: Signed-off-by: Andrew Morton Signed-off-by: Greg Kroah-Hartman --- mm/swapfile.c | 40 +++++++++++++++++++++++++++++++++------- 1 file changed, 33 insertions(+), 7 deletions(-) diff --git a/mm/swapfile.c b/mm/swapfile.c index a1b4b9d80e3b59..82524f8595eda8 100644 --- a/mm/swapfile.c +++ b/mm/swapfile.c @@ -1101,13 +1101,6 @@ static unsigned long cluster_alloc_swap_entry(struct swap_info_struct *si, int o goto done; } - /* - * We don't have free cluster but have some clusters in discarding, - * do discard now and reclaim them. - */ - if ((si->flags & SWP_PAGE_DISCARD) && swap_do_scheduled_discard(si)) - goto new_cluster; - if (order) goto done; @@ -1394,6 +1387,33 @@ static bool swap_alloc_slow(swp_entry_t *entry, return false; } +/* + * Discard pending clusters in a synchronized way when under high pressure. + * Return: true if any cluster is discarded. + */ +static bool swap_sync_discard(void) +{ + bool ret = false; + int nid = numa_node_id(); + struct swap_info_struct *si, *next; + + spin_lock(&swap_avail_lock); + plist_for_each_entry_safe(si, next, &swap_avail_heads[nid], avail_lists[nid]) { + spin_unlock(&swap_avail_lock); + if (get_swap_device_info(si)) { + if (si->flags & SWP_PAGE_DISCARD) + ret = swap_do_scheduled_discard(si); + put_swap_device(si); + } + if (ret) + return true; + spin_lock(&swap_avail_lock); + } + spin_unlock(&swap_avail_lock); + + return false; +} + /** * folio_alloc_swap - allocate swap space for a folio * @folio: folio we want to move to swap @@ -1432,11 +1452,17 @@ int folio_alloc_swap(struct folio *folio, gfp_t gfp) } } +again: local_lock(&percpu_swap_cluster.lock); if (!swap_alloc_fast(&entry, order)) swap_alloc_slow(&entry, order); local_unlock(&percpu_swap_cluster.lock); + if (unlikely(!order && !entry.val)) { + if (swap_sync_discard()) + goto again; + } + /* Need to call this even if allocation failed, for MEMCG_SWAP_FAIL. */ if (mem_cgroup_try_charge_swap(folio, entry)) goto out_free; From 67bfbc5a558ce07cbdb99503cb4f02a343eabd2e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20Wei=C3=9Fschuh?= Date: Tue, 28 Oct 2025 16:31:03 +0100 Subject: [PATCH 1889/3902] leds: leds-cros_ec: Skip LEDs without color components MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit commit 4dbf066d965cd3299fb396f1375d10423c9c625c upstream. A user reports that on their Lenovo Corsola Magneton with EC firmware steelix-15194.270.0 the driver probe fails with EINVAL. It turns out that the power LED does not contain any color components as indicated by the following "ectool led power query" output: Brightness range for LED 1: red : 0x0 green : 0x0 blue : 0x0 yellow : 0x0 white : 0x0 amber : 0x0 The LED also does not react to commands sent manually through ectool and is generally non-functional. Instead of failing the probe for all LEDs managed by the EC when one without color components is encountered, silently skip those. Cc: stable@vger.kernel.org Fixes: 8d6ce6f3ec9d ("leds: Add ChromeOS EC driver") Signed-off-by: Thomas Weißschuh Link: https://patch.msgid.link/20251028-cros_ec-leds-no-colors-v1-1-ebe13a02022a@weissschuh.net Signed-off-by: Lee Jones Signed-off-by: Greg Kroah-Hartman --- drivers/leds/leds-cros_ec.c | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/drivers/leds/leds-cros_ec.c b/drivers/leds/leds-cros_ec.c index 377cf04e202a1e..bea3cc3fbfd2d4 100644 --- a/drivers/leds/leds-cros_ec.c +++ b/drivers/leds/leds-cros_ec.c @@ -142,9 +142,6 @@ static int cros_ec_led_count_subleds(struct device *dev, } } - if (!num_subleds) - return -EINVAL; - *max_brightness = common_range; return num_subleds; } @@ -189,6 +186,8 @@ static int cros_ec_led_probe_one(struct device *dev, struct cros_ec_device *cros &priv->led_mc_cdev.led_cdev.max_brightness); if (num_subleds < 0) return num_subleds; + if (num_subleds == 0) + return 0; /* LED without any colors, skip */ priv->cros_ec = cros_ec; priv->led_id = id; From dcd4efe4817867758905d42762a058d7ca8094c1 Mon Sep 17 00:00:00 2001 From: Christian Hitz Date: Wed, 8 Oct 2025 14:32:21 +0200 Subject: [PATCH 1890/3902] leds: leds-lp50xx: Allow LED 0 to be added to module bank commit 26fe74d598c32e7bc6f150edfc4aa43e1bee55db upstream. led_banks contains LED module number(s) that should be grouped into the module bank. led_banks is 0-initialized. By checking the led_banks entries for 0, un-set entries are detected. But a 0-entry also indicates that LED module 0 should be grouped into the module bank. By only iterating over the available entries no check for unused entries is required and LED module 0 can be added to bank. Cc: stable@vger.kernel.org Fixes: 242b81170fb8 ("leds: lp50xx: Add the LP50XX family of the RGB LED driver") Signed-off-by: Christian Hitz Reviewed-by: Jacek Anaszewski Link: https://patch.msgid.link/20251008123222.1117331-1-christian@klarinett.li Signed-off-by: Lee Jones Signed-off-by: Greg Kroah-Hartman --- drivers/leds/leds-lp50xx.c | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/drivers/leds/leds-lp50xx.c b/drivers/leds/leds-lp50xx.c index 94f8ef6b482c91..d50c7f3e8f9995 100644 --- a/drivers/leds/leds-lp50xx.c +++ b/drivers/leds/leds-lp50xx.c @@ -341,17 +341,15 @@ static int lp50xx_brightness_set(struct led_classdev *cdev, return ret; } -static int lp50xx_set_banks(struct lp50xx *priv, u32 led_banks[]) +static int lp50xx_set_banks(struct lp50xx *priv, u32 led_banks[], int num_leds) { u8 led_config_lo, led_config_hi; u32 bank_enable_mask = 0; int ret; int i; - for (i = 0; i < priv->chip_info->max_modules; i++) { - if (led_banks[i]) - bank_enable_mask |= (1 << led_banks[i]); - } + for (i = 0; i < num_leds; i++) + bank_enable_mask |= (1 << led_banks[i]); led_config_lo = bank_enable_mask; led_config_hi = bank_enable_mask >> 8; @@ -405,7 +403,7 @@ static int lp50xx_probe_leds(struct fwnode_handle *child, struct lp50xx *priv, return ret; } - ret = lp50xx_set_banks(priv, led_banks); + ret = lp50xx_set_banks(priv, led_banks, num_leds); if (ret) { dev_err(priv->dev, "Cannot setup banked LEDs\n"); return ret; From c99352597531733705351eb35524f1a19f60fdc7 Mon Sep 17 00:00:00 2001 From: Christian Hitz Date: Wed, 22 Oct 2025 08:33:04 +0200 Subject: [PATCH 1891/3902] leds: leds-lp50xx: LP5009 supports 3 modules for a total of 9 LEDs commit 5246e3673eeeccb4f5bf4f42375dd495d465ac15 upstream. LP5009 supports 9 LED outputs that are grouped into 3 modules. Cc: stable@vger.kernel.org Fixes: 242b81170fb8 ("leds: lp50xx: Add the LP50XX family of the RGB LED driver") Signed-off-by: Christian Hitz Link: https://patch.msgid.link/20251022063305.972190-1-christian@klarinett.li Signed-off-by: Lee Jones Signed-off-by: Greg Kroah-Hartman --- drivers/leds/leds-lp50xx.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/leds/leds-lp50xx.c b/drivers/leds/leds-lp50xx.c index d50c7f3e8f9995..3a0316be96edb7 100644 --- a/drivers/leds/leds-lp50xx.c +++ b/drivers/leds/leds-lp50xx.c @@ -54,7 +54,7 @@ /* There are 3 LED outputs per bank */ #define LP50XX_LEDS_PER_MODULE 3 -#define LP5009_MAX_LED_MODULES 2 +#define LP5009_MAX_LED_MODULES 3 #define LP5012_MAX_LED_MODULES 4 #define LP5018_MAX_LED_MODULES 6 #define LP5024_MAX_LED_MODULES 8 From 4d29edea49218a51304facea51357a4ab9a284f5 Mon Sep 17 00:00:00 2001 From: Christian Hitz Date: Tue, 28 Oct 2025 16:51:40 +0100 Subject: [PATCH 1892/3902] leds: leds-lp50xx: Enable chip before any communication MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit commit 434959618c47efe9e5f2e20f4a850caac4f6b823 upstream. If a GPIO is used to control the chip's enable pin, it needs to be pulled high before any i2c communication is attempted. Currently, the enable GPIO handling is not correct. Assume the enable GPIO is low when the probe function is entered. In this case the device is in SHUTDOWN mode and does not react to i2c commands. During probe the following sequence happens: 1. The call to lp50xx_reset() on line 548 has no effect as i2c is not possible yet. 2. Then - on line 552 - lp50xx_enable_disable() is called. As "priv->enable_gpio“ has not yet been initialized, setting the GPIO has no effect. Also the i2c enable command is not executed as the device is still in SHUTDOWN. 3. On line 556 the call to lp50xx_probe_dt() finally parses the rest of the DT and the configured priv->enable_gpio is set up. As a result the device is still in SHUTDOWN mode and not ready for operation. Split lp50xx_enable_disable() into distinct enable and disable functions to enforce correct ordering between enable_gpio manipulations and i2c commands. Read enable_gpio configuration from DT before attempting to manipulate enable_gpio. Add delays to observe correct wait timing after manipulating enable_gpio and before any i2c communication. Cc: stable@vger.kernel.org Fixes: 242b81170fb8 ("leds: lp50xx: Add the LP50XX family of the RGB LED driver") Signed-off-by: Christian Hitz Link: https://patch.msgid.link/20251028155141.1603193-1-christian@klarinett.li Signed-off-by: Lee Jones Signed-off-by: Greg Kroah-Hartman --- drivers/leds/leds-lp50xx.c | 55 +++++++++++++++++++++++++++----------- 1 file changed, 40 insertions(+), 15 deletions(-) diff --git a/drivers/leds/leds-lp50xx.c b/drivers/leds/leds-lp50xx.c index 3a0316be96edb7..e2a9c8592953ca 100644 --- a/drivers/leds/leds-lp50xx.c +++ b/drivers/leds/leds-lp50xx.c @@ -50,6 +50,12 @@ #define LP50XX_SW_RESET 0xff #define LP50XX_CHIP_EN BIT(6) +#define LP50XX_CHIP_DISABLE 0x00 +#define LP50XX_START_TIME_US 500 +#define LP50XX_RESET_TIME_US 3 + +#define LP50XX_EN_GPIO_LOW 0 +#define LP50XX_EN_GPIO_HIGH 1 /* There are 3 LED outputs per bank */ #define LP50XX_LEDS_PER_MODULE 3 @@ -369,19 +375,42 @@ static int lp50xx_reset(struct lp50xx *priv) return regmap_write(priv->regmap, priv->chip_info->reset_reg, LP50XX_SW_RESET); } -static int lp50xx_enable_disable(struct lp50xx *priv, int enable_disable) +static int lp50xx_enable(struct lp50xx *priv) { int ret; - ret = gpiod_direction_output(priv->enable_gpio, enable_disable); + if (priv->enable_gpio) { + ret = gpiod_direction_output(priv->enable_gpio, LP50XX_EN_GPIO_HIGH); + if (ret) + return ret; + + udelay(LP50XX_START_TIME_US); + } + + ret = lp50xx_reset(priv); if (ret) return ret; - if (enable_disable) - return regmap_write(priv->regmap, LP50XX_DEV_CFG0, LP50XX_CHIP_EN); - else - return regmap_write(priv->regmap, LP50XX_DEV_CFG0, 0); + return regmap_write(priv->regmap, LP50XX_DEV_CFG0, LP50XX_CHIP_EN); +} +static int lp50xx_disable(struct lp50xx *priv) +{ + int ret; + + ret = regmap_write(priv->regmap, LP50XX_DEV_CFG0, LP50XX_CHIP_DISABLE); + if (ret) + return ret; + + if (priv->enable_gpio) { + ret = gpiod_direction_output(priv->enable_gpio, LP50XX_EN_GPIO_LOW); + if (ret) + return ret; + + udelay(LP50XX_RESET_TIME_US); + } + + return 0; } static int lp50xx_probe_leds(struct fwnode_handle *child, struct lp50xx *priv, @@ -445,6 +474,10 @@ static int lp50xx_probe_dt(struct lp50xx *priv) return dev_err_probe(priv->dev, PTR_ERR(priv->enable_gpio), "Failed to get enable GPIO\n"); + ret = lp50xx_enable(priv); + if (ret) + return ret; + priv->regulator = devm_regulator_get(priv->dev, "vled"); if (IS_ERR(priv->regulator)) priv->regulator = NULL; @@ -545,14 +578,6 @@ static int lp50xx_probe(struct i2c_client *client) return ret; } - ret = lp50xx_reset(led); - if (ret) - return ret; - - ret = lp50xx_enable_disable(led, 1); - if (ret) - return ret; - return lp50xx_probe_dt(led); } @@ -561,7 +586,7 @@ static void lp50xx_remove(struct i2c_client *client) struct lp50xx *led = i2c_get_clientdata(client); int ret; - ret = lp50xx_enable_disable(led, 0); + ret = lp50xx_disable(led); if (ret) dev_err(led->dev, "Failed to disable chip\n"); From 1c678ce2f56be2b66c2b17b4d7311de6fd6e283d Mon Sep 17 00:00:00 2001 From: Damien Le Moal Date: Thu, 4 Dec 2025 19:59:52 +0900 Subject: [PATCH 1893/3902] block: Clear BLK_ZONE_WPLUG_PLUGGED when aborting plugged BIOs commit 552c1149af7ac0cffab6fccd13feeaf816dd1f53 upstream. Commit fe0418eb9bd6 ("block: Prevent potential deadlocks in zone write plug error recovery") added a WARN check in disk_put_zone_wplug() to verify that when the last reference to a zone write plug is dropped, this zone write plug does not have the BLK_ZONE_WPLUG_PLUGGED flag set, that is, that it is not plugged. However, the function disk_zone_wplug_abort(), which is called for zone reset and zone finish operations, does not clear this flag after emptying a zone write plug BIO list. This can result in the disk_put_zone_wplug() warning to trigger if the user (erroneously as that is bad pratcice) issues zone reset or zone finish operations while the target zone still has plugged BIOs. Modify disk_put_zone_wplug() to clear the BLK_ZONE_WPLUG_PLUGGED flag. And while at it, also add a lockdep annotation to ensure that this function is called with the zone write plug spinlock held. Fixes: fe0418eb9bd6 ("block: Prevent potential deadlocks in zone write plug error recovery") Cc: stable@vger.kernel.org Signed-off-by: Damien Le Moal Reviewed-by: Niklas Cassel Reviewed-by: Johannes Thumshirn Signed-off-by: Jens Axboe Signed-off-by: Greg Kroah-Hartman --- block/blk-zoned.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/block/blk-zoned.c b/block/blk-zoned.c index 39381f2b2e9471..3a3cbee60591b5 100644 --- a/block/blk-zoned.c +++ b/block/blk-zoned.c @@ -616,6 +616,8 @@ static void disk_zone_wplug_abort(struct blk_zone_wplug *zwplug) { struct bio *bio; + lockdep_assert_held(&zwplug->lock); + if (bio_list_empty(&zwplug->bio_list)) return; @@ -623,6 +625,8 @@ static void disk_zone_wplug_abort(struct blk_zone_wplug *zwplug) zwplug->disk->disk_name, zwplug->zone_no); while ((bio = bio_list_pop(&zwplug->bio_list))) blk_zone_wplug_bio_io_error(zwplug, bio); + + zwplug->flags &= ~BLK_ZONE_WPLUG_PLUGGED; } /* From a317f63255ebc3dac378c79c5bff4f8d0561c290 Mon Sep 17 00:00:00 2001 From: Nathan Chancellor Date: Mon, 24 Nov 2025 12:11:06 -0700 Subject: [PATCH 1894/3902] clk: samsung: exynos-clkout: Assign .num before accessing .hws commit cf33f0b7df13685234ccea7be7bfe316b60db4db upstream. Commit f316cdff8d67 ("clk: Annotate struct clk_hw_onecell_data with __counted_by") annotated the hws member of 'struct clk_hw_onecell_data' with __counted_by, which informs the bounds sanitizer (UBSAN_BOUNDS) about the number of elements in .hws[], so that it can warn when .hws[] is accessed out of bounds. As noted in that change, the __counted_by member must be initialized with the number of elements before the first array access happens, otherwise there will be a warning from each access prior to the initialization because the number of elements is zero. This occurs in exynos_clkout_probe() due to .num being assigned after .hws[] has been accessed: UBSAN: array-index-out-of-bounds in drivers/clk/samsung/clk-exynos-clkout.c:178:18 index 0 is out of range for type 'clk_hw *[*]' Move the .num initialization to before the first access of .hws[], clearing up the warning. Cc: stable@vger.kernel.org Fixes: f316cdff8d67 ("clk: Annotate struct clk_hw_onecell_data with __counted_by") Reported-by: Jochen Sprickerhof Closes: https://lore.kernel.org/aSIYDN5eyKFKoXKL@eldamar.lan/ Tested-by: Jochen Sprickerhof Signed-off-by: Nathan Chancellor Reviewed-by: Kees Cook Reviewed-by: Sam Protsenko Reviewed-by: Krzysztof Kozlowski Signed-off-by: Stephen Boyd Signed-off-by: Greg Kroah-Hartman --- drivers/clk/samsung/clk-exynos-clkout.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/clk/samsung/clk-exynos-clkout.c b/drivers/clk/samsung/clk-exynos-clkout.c index 5f1a4f5e2e594f..5b21025338bd53 100644 --- a/drivers/clk/samsung/clk-exynos-clkout.c +++ b/drivers/clk/samsung/clk-exynos-clkout.c @@ -175,6 +175,7 @@ static int exynos_clkout_probe(struct platform_device *pdev) clkout->mux.shift = EXYNOS_CLKOUT_MUX_SHIFT; clkout->mux.lock = &clkout->slock; + clkout->data.num = EXYNOS_CLKOUT_NR_CLKS; clkout->data.hws[0] = clk_hw_register_composite(NULL, "clkout", parent_names, parent_count, &clkout->mux.hw, &clk_mux_ops, NULL, NULL, &clkout->gate.hw, @@ -185,7 +186,6 @@ static int exynos_clkout_probe(struct platform_device *pdev) goto err_unmap; } - clkout->data.num = EXYNOS_CLKOUT_NR_CLKS; ret = of_clk_add_hw_provider(clkout->np, of_clk_hw_onecell_get, &clkout->data); if (ret) goto err_clk_unreg; From 34322b533390533feb49b2c29961159328bf04db Mon Sep 17 00:00:00 2001 From: Alexey Minnekhanov Date: Sun, 16 Nov 2025 04:12:34 +0300 Subject: [PATCH 1895/3902] clk: qcom: mmcc-sdm660: Add missing MDSS reset commit 0a0ea5541d30c0fbb3dac975bd1983f299cd6948 upstream. Add offset for display subsystem reset in multimedia clock controller block, which is necessary to reset display when there is some configuration in display controller left by previous stock (Android) bootloader to provide continuous splash functionaluty. Before 6.17 power domains were turned off for long enough to clear registers, now this is not the case and a proper reset is needed to have functioning display. Fixes: 0e789b491ba0 ("pmdomain: core: Leave powered-on genpds on until sync_state") Cc: stable@vger.kernel.org # 6.17 Signed-off-by: Alexey Minnekhanov Reviewed-by: Taniya Das Reviewed-by: Dmitry Baryshkov Link: https://lore.kernel.org/r/20251116-sdm660-mdss-reset-v2-2-6219bec0a97f@postmarketos.org Signed-off-by: Bjorn Andersson Signed-off-by: Greg Kroah-Hartman --- drivers/clk/qcom/mmcc-sdm660.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/clk/qcom/mmcc-sdm660.c b/drivers/clk/qcom/mmcc-sdm660.c index b723c536dfb6ce..dbd3f561dc6d0d 100644 --- a/drivers/clk/qcom/mmcc-sdm660.c +++ b/drivers/clk/qcom/mmcc-sdm660.c @@ -2781,6 +2781,7 @@ static struct gdsc *mmcc_sdm660_gdscs[] = { }; static const struct qcom_reset_map mmcc_660_resets[] = { + [MDSS_BCR] = { 0x2300 }, [CAMSS_MICRO_BCR] = { 0x3490 }, }; From 8f41129c4b5ba5bb2444a6157630550fa0cc7110 Mon Sep 17 00:00:00 2001 From: Nathan Chancellor Date: Tue, 30 Sep 2025 11:56:08 -0700 Subject: [PATCH 1896/3902] clk: qcom: Fix SM_VIDEOCC_6350 dependencies commit f0691a3f7558d33b5b4a900e8312613fbe4afb9d upstream. It is possible to select CONFIG_SM_GCC_6350 when targeting ARCH=arm, causing a Kconfig warning when selecting CONFIG_SM_GCC_6350 without its dependencies, CONFIG_ARM64 or CONFIG_COMPILE_TEST. WARNING: unmet direct dependencies detected for SM_GCC_6350 Depends on [n]: COMMON_CLK [=y] && COMMON_CLK_QCOM [=m] && (ARM64 || COMPILE_TEST [=n]) Selected by [m]: - SM_VIDEOCC_6350 [=m] && COMMON_CLK [=y] && COMMON_CLK_QCOM [=m] Add the same dependency to clear up the warning. Cc: stable@vger.kernel.org Fixes: 720b1e8f2004 ("clk: qcom: Add video clock controller driver for SM6350") Signed-off-by: Nathan Chancellor Reviewed-by: Imran Shaik Reviewed-by: Dmitry Baryshkov Link: https://lore.kernel.org/r/20250930-clk-qcom-kconfig-fixes-arm-v1-1-15ae1ae9ec9f@kernel.org Signed-off-by: Bjorn Andersson Signed-off-by: Greg Kroah-Hartman --- drivers/clk/qcom/Kconfig | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/clk/qcom/Kconfig b/drivers/clk/qcom/Kconfig index 78a3038426136e..ec7d1a9b578e6e 100644 --- a/drivers/clk/qcom/Kconfig +++ b/drivers/clk/qcom/Kconfig @@ -1448,6 +1448,7 @@ config SA_VIDEOCC_8775P config SM_VIDEOCC_6350 tristate "SM6350 Video Clock Controller" + depends on ARM64 || COMPILE_TEST select SM_GCC_6350 select QCOM_GDSC help From deb4bc9fc8d0ba2c7b4a224c6f72cacaafa65d61 Mon Sep 17 00:00:00 2001 From: Nathan Chancellor Date: Tue, 30 Sep 2025 11:56:09 -0700 Subject: [PATCH 1897/3902] clk: qcom: Fix dependencies of QCS_{DISP,GPU,VIDEO}CC_615 commit 7ec1ba01ae37897f0ecf6ab0c980378cb8a2f388 upstream. It is possible to select CONFIG_QCS_{DISP,GPU,VIDEO}CC_615 when targeting ARCH=arm, causing a Kconfig warning when selecting CONFIG_QCS_GCC_615 without its dependencies, CONFIG_ARM64 or CONFIG_COMPILE_TEST. WARNING: unmet direct dependencies detected for QCS_GCC_615 Depends on [n]: COMMON_CLK [=y] && COMMON_CLK_QCOM [=m] && (ARM64 || COMPILE_TEST [=n]) Selected by [m]: - QCS_DISPCC_615 [=m] && COMMON_CLK [=y] && COMMON_CLK_QCOM [=m] - QCS_GPUCC_615 [=m] && COMMON_CLK [=y] && COMMON_CLK_QCOM [=m] - QCS_VIDEOCC_615 [=m] && COMMON_CLK [=y] && COMMON_CLK_QCOM [=m] Add the same dependency to these configurations to clear up the warnings. Cc: stable@vger.kernel.org Fixes: 9b47105f5434 ("clk: qcom: dispcc-qcs615: Add QCS615 display clock controller driver") Fixes: f4b5b40805ab ("clk: qcom: gpucc-qcs615: Add QCS615 graphics clock controller driver") Fixes: f6a8abe0cc16 ("clk: qcom: videocc-qcs615: Add QCS615 video clock controller driver") Signed-off-by: Nathan Chancellor Reviewed-by: Imran Shaik Reviewed-by: Dmitry Baryshkov Link: https://lore.kernel.org/r/20250930-clk-qcom-kconfig-fixes-arm-v1-2-15ae1ae9ec9f@kernel.org Signed-off-by: Bjorn Andersson Signed-off-by: Greg Kroah-Hartman --- drivers/clk/qcom/Kconfig | 3 +++ 1 file changed, 3 insertions(+) diff --git a/drivers/clk/qcom/Kconfig b/drivers/clk/qcom/Kconfig index ec7d1a9b578e6e..6fef0bfc17736b 100644 --- a/drivers/clk/qcom/Kconfig +++ b/drivers/clk/qcom/Kconfig @@ -531,6 +531,7 @@ config QCM_DISPCC_2290 config QCS_DISPCC_615 tristate "QCS615 Display Clock Controller" + depends on ARM64 || COMPILE_TEST select QCS_GCC_615 help Support for the display clock controller on Qualcomm Technologies, Inc @@ -586,6 +587,7 @@ config QCS_GCC_615 config QCS_GPUCC_615 tristate "QCS615 Graphics clock controller" + depends on ARM64 || COMPILE_TEST select QCS_GCC_615 help Support for the graphics clock controller on QCS615 devices. @@ -594,6 +596,7 @@ config QCS_GPUCC_615 config QCS_VIDEOCC_615 tristate "QCS615 Video Clock Controller" + depends on ARM64 || COMPILE_TEST select QCS_GCC_615 help Support for the video clock controller on QCS615 devices. From e4077fcb1479405bbe902eac600092583e066359 Mon Sep 17 00:00:00 2001 From: Johan Hovold Date: Thu, 25 Sep 2025 17:02:19 +0200 Subject: [PATCH 1898/3902] mfd: altera-sysmgr: Fix device leak on sysmgr regmap lookup commit ccb7cd3218e48665f3c7e19eede0da5f069c323d upstream. Make sure to drop the reference taken to the sysmgr platform device when retrieving its driver data. Note that holding a reference to a device does not prevent its driver data from going away. Fixes: f36e789a1f8d ("mfd: altera-sysmgr: Add SOCFPGA System Manager") Cc: stable@vger.kernel.org # 5.2 Signed-off-by: Johan Hovold Signed-off-by: Lee Jones Signed-off-by: Greg Kroah-Hartman --- drivers/mfd/altera-sysmgr.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/drivers/mfd/altera-sysmgr.c b/drivers/mfd/altera-sysmgr.c index fb5f988e61f373..90c6902d537da3 100644 --- a/drivers/mfd/altera-sysmgr.c +++ b/drivers/mfd/altera-sysmgr.c @@ -117,6 +117,8 @@ struct regmap *altr_sysmgr_regmap_lookup_by_phandle(struct device_node *np, sysmgr = dev_get_drvdata(dev); + put_device(dev); + return sysmgr->regmap; } EXPORT_SYMBOL_GPL(altr_sysmgr_regmap_lookup_by_phandle); From 10eecb592afc854683366a8936accf43a197052c Mon Sep 17 00:00:00 2001 From: Krzysztof Kozlowski Date: Thu, 23 Oct 2025 12:19:40 +0200 Subject: [PATCH 1899/3902] mfd: max77620: Fix potential IRQ chip conflict when probing two devices commit 2bac49bad1f3553cc3b3bfb22cc194e9bd9e8427 upstream. MAX77620 is most likely always a single device on the board, however nothing stops board designers to have two of them, thus same device driver could probe twice. Or user could manually try to probing second time. Device driver is not ready for that case, because it allocates statically 'struct regmap_irq_chip' as non-const and stores during probe in 'irq_drv_data' member a pointer to per-probe state container ('struct max77620_chip'). devm_regmap_add_irq_chip() does not make a copy of 'struct regmap_irq_chip' but store the pointer. Second probe - either successful or failure - would overwrite the 'irq_drv_data' from previous device probe, so interrupts would be executed in a wrong context. Cc: stable@vger.kernel.org Fixes: 3df140d11c6d ("mfd: max77620: Mask/unmask interrupt before/after servicing it") Signed-off-by: Krzysztof Kozlowski Link: https://patch.msgid.link/20251023101939.67991-2-krzysztof.kozlowski@linaro.org Signed-off-by: Lee Jones Signed-off-by: Greg Kroah-Hartman --- drivers/mfd/max77620.c | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/drivers/mfd/max77620.c b/drivers/mfd/max77620.c index 21d2ab3db2542e..3af2974b302327 100644 --- a/drivers/mfd/max77620.c +++ b/drivers/mfd/max77620.c @@ -254,7 +254,7 @@ static int max77620_irq_global_unmask(void *irq_drv_data) return ret; } -static struct regmap_irq_chip max77620_top_irq_chip = { +static const struct regmap_irq_chip max77620_top_irq_chip = { .name = "max77620-top", .irqs = max77620_top_irqs, .num_irqs = ARRAY_SIZE(max77620_top_irqs), @@ -498,6 +498,7 @@ static int max77620_probe(struct i2c_client *client) const struct i2c_device_id *id = i2c_client_get_device_id(client); const struct regmap_config *rmap_config; struct max77620_chip *chip; + struct regmap_irq_chip *chip_desc; const struct mfd_cell *mfd_cells; int n_mfd_cells; bool pm_off; @@ -508,6 +509,14 @@ static int max77620_probe(struct i2c_client *client) return -ENOMEM; i2c_set_clientdata(client, chip); + + chip_desc = devm_kmemdup(&client->dev, &max77620_top_irq_chip, + sizeof(max77620_top_irq_chip), + GFP_KERNEL); + if (!chip_desc) + return -ENOMEM; + chip_desc->irq_drv_data = chip; + chip->dev = &client->dev; chip->chip_irq = client->irq; chip->chip_id = (enum max77620_chip_id)id->driver_data; @@ -544,11 +553,9 @@ static int max77620_probe(struct i2c_client *client) if (ret < 0) return ret; - max77620_top_irq_chip.irq_drv_data = chip; ret = devm_regmap_add_irq_chip(chip->dev, chip->rmap, client->irq, IRQF_ONESHOT | IRQF_SHARED, 0, - &max77620_top_irq_chip, - &chip->top_irq_data); + chip_desc, &chip->top_irq_data); if (ret < 0) { dev_err(chip->dev, "Failed to add regmap irq: %d\n", ret); return ret; From e9918c6a0044b9e224bc8b1ceb6d7d956b3f4cab Mon Sep 17 00:00:00 2001 From: Haotian Zhang Date: Fri, 31 Oct 2025 14:03:32 +0800 Subject: [PATCH 1900/3902] media: rc: st_rc: Fix reset control resource leak commit 1240abf4b71f632f0117b056e22488e4d9808938 upstream. The driver calls reset_control_get_optional_exclusive() but never calls reset_control_put() in error paths or in the remove function. This causes a resource leak when probe fails after successfully acquiring the reset control, or when the driver is unloaded. Switch to devm_reset_control_get_optional_exclusive() to automatically manage the reset control resource. Fixes: a4b80242d046 ("media: st-rc: explicitly request exclusive reset control") Cc: stable@vger.kernel.org Signed-off-by: Haotian Zhang Reviewed-by: Patrice Chotard Signed-off-by: Sean Young Signed-off-by: Hans Verkuil Signed-off-by: Greg Kroah-Hartman --- drivers/media/rc/st_rc.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/media/rc/st_rc.c b/drivers/media/rc/st_rc.c index 6539fa0a6e7905..6b70bac5f45d6c 100644 --- a/drivers/media/rc/st_rc.c +++ b/drivers/media/rc/st_rc.c @@ -284,7 +284,7 @@ static int st_rc_probe(struct platform_device *pdev) else rc_dev->rx_base = rc_dev->base; - rc_dev->rstc = reset_control_get_optional_exclusive(dev, NULL); + rc_dev->rstc = devm_reset_control_get_optional_exclusive(dev, NULL); if (IS_ERR(rc_dev->rstc)) { ret = PTR_ERR(rc_dev->rstc); goto err; From e768a9d22d38f5eaee245692455bc7764b251408 Mon Sep 17 00:00:00 2001 From: Nicolas Dufresne Date: Mon, 22 Sep 2025 14:43:38 -0400 Subject: [PATCH 1901/3902] media: verisilicon: Fix CPU stalls on G2 bus error commit 19c286b755072a22a063052f530a6b1fac8a1f63 upstream. In some seek stress tests, we are getting IRQ from the G2 decoder where the dec_bus_int and the dec_e bits are high, meaning the decoder is still running despite the error. Fix this by reworking the IRQ handler to only finish the job once we have reached completion and move the software reset to when our software watchdog triggers. This way, we let the hardware continue on errors when it did not self reset and in worse case scenario the hardware timeout will automatically stop it. The actual error will be fixed in a follow up patch. Fixes: 3385c514ecc5a ("media: hantro: Convert imx8m_vpu_g2_irq to helper") Cc: stable@vger.kernel.org Reviewed-by: Benjamin Gaignard Signed-off-by: Nicolas Dufresne Signed-off-by: Hans Verkuil Signed-off-by: Greg Kroah-Hartman --- .../media/platform/verisilicon/hantro_g2.c | 88 +++++++++++++++---- .../platform/verisilicon/hantro_g2_hevc_dec.c | 2 - .../platform/verisilicon/hantro_g2_regs.h | 13 +++ .../platform/verisilicon/hantro_g2_vp9_dec.c | 2 - .../media/platform/verisilicon/hantro_hw.h | 1 + .../media/platform/verisilicon/imx8m_vpu_hw.c | 2 + 6 files changed, 85 insertions(+), 23 deletions(-) diff --git a/drivers/media/platform/verisilicon/hantro_g2.c b/drivers/media/platform/verisilicon/hantro_g2.c index aae0b562fabb17..318673b66da884 100644 --- a/drivers/media/platform/verisilicon/hantro_g2.c +++ b/drivers/media/platform/verisilicon/hantro_g2.c @@ -5,43 +5,93 @@ * Copyright (C) 2021 Collabora Ltd, Andrzej Pietrasiewicz */ +#include #include "hantro_hw.h" #include "hantro_g2_regs.h" #define G2_ALIGN 16 -void hantro_g2_check_idle(struct hantro_dev *vpu) +static bool hantro_g2_active(struct hantro_ctx *ctx) { - int i; - - for (i = 0; i < 3; i++) { - u32 status; - - /* Make sure the VPU is idle */ - status = vdpu_read(vpu, G2_REG_INTERRUPT); - if (status & G2_REG_INTERRUPT_DEC_E) { - dev_warn(vpu->dev, "device still running, aborting"); - status |= G2_REG_INTERRUPT_DEC_ABORT_E | G2_REG_INTERRUPT_DEC_IRQ_DIS; - vdpu_write(vpu, status, G2_REG_INTERRUPT); - } + struct hantro_dev *vpu = ctx->dev; + u32 status; + + status = vdpu_read(vpu, G2_REG_INTERRUPT); + + return (status & G2_REG_INTERRUPT_DEC_E); +} + +/** + * hantro_g2_reset: + * @ctx: the hantro context + * + * Emulates a reset using Hantro abort function. Failing this procedure would + * results in programming a running IP which leads to CPU hang. + * + * Using a hard reset procedure instead is prefferred. + */ +void hantro_g2_reset(struct hantro_ctx *ctx) +{ + struct hantro_dev *vpu = ctx->dev; + u32 status; + + status = vdpu_read(vpu, G2_REG_INTERRUPT); + if (status & G2_REG_INTERRUPT_DEC_E) { + dev_warn_ratelimited(vpu->dev, "device still running, aborting"); + status |= G2_REG_INTERRUPT_DEC_ABORT_E | G2_REG_INTERRUPT_DEC_IRQ_DIS; + vdpu_write(vpu, status, G2_REG_INTERRUPT); + + do { + mdelay(1); + } while (hantro_g2_active(ctx)); } } irqreturn_t hantro_g2_irq(int irq, void *dev_id) { struct hantro_dev *vpu = dev_id; - enum vb2_buffer_state state; u32 status; status = vdpu_read(vpu, G2_REG_INTERRUPT); - state = (status & G2_REG_INTERRUPT_DEC_RDY_INT) ? - VB2_BUF_STATE_DONE : VB2_BUF_STATE_ERROR; - vdpu_write(vpu, 0, G2_REG_INTERRUPT); - vdpu_write(vpu, G2_REG_CONFIG_DEC_CLK_GATE_E, G2_REG_CONFIG); + if (!(status & G2_REG_INTERRUPT_DEC_IRQ)) + return IRQ_NONE; + + hantro_reg_write(vpu, &g2_dec_irq, 0); + hantro_reg_write(vpu, &g2_dec_int_stat, 0); + hantro_reg_write(vpu, &g2_clk_gate_e, 1); + + if (status & G2_REG_INTERRUPT_DEC_RDY_INT) { + hantro_irq_done(vpu, VB2_BUF_STATE_DONE); + return IRQ_HANDLED; + } + + if (status & G2_REG_INTERRUPT_DEC_ABORT_INT) { + /* disabled on abort, though lets be safe and handle it */ + dev_warn_ratelimited(vpu->dev, "decode operation aborted."); + return IRQ_HANDLED; + } + + if (status & G2_REG_INTERRUPT_DEC_LAST_SLICE_INT) + dev_warn_ratelimited(vpu->dev, "not all macroblocks were decoded."); + + if (status & G2_REG_INTERRUPT_DEC_BUS_INT) + dev_warn_ratelimited(vpu->dev, "bus error detected."); + + if (status & G2_REG_INTERRUPT_DEC_ERROR_INT) + dev_warn_ratelimited(vpu->dev, "decode error detected."); + + if (status & G2_REG_INTERRUPT_DEC_TIMEOUT) + dev_warn_ratelimited(vpu->dev, "frame decode timed out."); - hantro_irq_done(vpu, state); + /** + * If the decoding haven't stopped, let it continue. The hardware timeout + * will trigger if it is trully stuck. + */ + if (status & G2_REG_INTERRUPT_DEC_E) + return IRQ_HANDLED; + hantro_irq_done(vpu, VB2_BUF_STATE_ERROR); return IRQ_HANDLED; } diff --git a/drivers/media/platform/verisilicon/hantro_g2_hevc_dec.c b/drivers/media/platform/verisilicon/hantro_g2_hevc_dec.c index 0e212198dd65b1..f066636e56f985 100644 --- a/drivers/media/platform/verisilicon/hantro_g2_hevc_dec.c +++ b/drivers/media/platform/verisilicon/hantro_g2_hevc_dec.c @@ -582,8 +582,6 @@ int hantro_g2_hevc_dec_run(struct hantro_ctx *ctx) struct hantro_dev *vpu = ctx->dev; int ret; - hantro_g2_check_idle(vpu); - /* Prepare HEVC decoder context. */ ret = hantro_hevc_dec_prepare_run(ctx); if (ret) diff --git a/drivers/media/platform/verisilicon/hantro_g2_regs.h b/drivers/media/platform/verisilicon/hantro_g2_regs.h index b943b1816db7fd..c614951121c79c 100644 --- a/drivers/media/platform/verisilicon/hantro_g2_regs.h +++ b/drivers/media/platform/verisilicon/hantro_g2_regs.h @@ -22,7 +22,14 @@ #define G2_REG_VERSION G2_SWREG(0) #define G2_REG_INTERRUPT G2_SWREG(1) +#define G2_REG_INTERRUPT_DEC_LAST_SLICE_INT BIT(19) +#define G2_REG_INTERRUPT_DEC_TIMEOUT BIT(18) +#define G2_REG_INTERRUPT_DEC_ERROR_INT BIT(16) +#define G2_REG_INTERRUPT_DEC_BUF_INT BIT(14) +#define G2_REG_INTERRUPT_DEC_BUS_INT BIT(13) #define G2_REG_INTERRUPT_DEC_RDY_INT BIT(12) +#define G2_REG_INTERRUPT_DEC_ABORT_INT BIT(11) +#define G2_REG_INTERRUPT_DEC_IRQ BIT(8) #define G2_REG_INTERRUPT_DEC_ABORT_E BIT(5) #define G2_REG_INTERRUPT_DEC_IRQ_DIS BIT(4) #define G2_REG_INTERRUPT_DEC_E BIT(0) @@ -35,6 +42,9 @@ #define BUS_WIDTH_128 2 #define BUS_WIDTH_256 3 +#define g2_dec_int_stat G2_DEC_REG(1, 11, 0xf) +#define g2_dec_irq G2_DEC_REG(1, 8, 0x1) + #define g2_strm_swap G2_DEC_REG(2, 28, 0xf) #define g2_strm_swap_old G2_DEC_REG(2, 27, 0x1f) #define g2_pic_swap G2_DEC_REG(2, 22, 0x1f) @@ -225,6 +235,9 @@ #define vp9_filt_level_seg5 G2_DEC_REG(19, 8, 0x3f) #define vp9_quant_seg5 G2_DEC_REG(19, 0, 0xff) +#define g2_timemout_override_e G2_DEC_REG(45, 31, 0x1) +#define g2_timemout_cycles G2_DEC_REG(45, 0, 0x7fffffff) + #define hevc_cur_poc_00 G2_DEC_REG(46, 24, 0xff) #define hevc_cur_poc_01 G2_DEC_REG(46, 16, 0xff) #define hevc_cur_poc_02 G2_DEC_REG(46, 8, 0xff) diff --git a/drivers/media/platform/verisilicon/hantro_g2_vp9_dec.c b/drivers/media/platform/verisilicon/hantro_g2_vp9_dec.c index 82a478ac645e1d..56c79e339030e8 100644 --- a/drivers/media/platform/verisilicon/hantro_g2_vp9_dec.c +++ b/drivers/media/platform/verisilicon/hantro_g2_vp9_dec.c @@ -893,8 +893,6 @@ int hantro_g2_vp9_dec_run(struct hantro_ctx *ctx) struct vb2_v4l2_buffer *dst; int ret; - hantro_g2_check_idle(ctx->dev); - ret = start_prepare_run(ctx, &decode_params); if (ret) { hantro_end_prepare_run(ctx); diff --git a/drivers/media/platform/verisilicon/hantro_hw.h b/drivers/media/platform/verisilicon/hantro_hw.h index c9b6556f8b2b78..5f2011529f02dc 100644 --- a/drivers/media/platform/verisilicon/hantro_hw.h +++ b/drivers/media/platform/verisilicon/hantro_hw.h @@ -583,6 +583,7 @@ void hantro_g2_vp9_dec_done(struct hantro_ctx *ctx); int hantro_vp9_dec_init(struct hantro_ctx *ctx); void hantro_vp9_dec_exit(struct hantro_ctx *ctx); void hantro_g2_check_idle(struct hantro_dev *vpu); +void hantro_g2_reset(struct hantro_ctx *ctx); irqreturn_t hantro_g2_irq(int irq, void *dev_id); #endif /* HANTRO_HW_H_ */ diff --git a/drivers/media/platform/verisilicon/imx8m_vpu_hw.c b/drivers/media/platform/verisilicon/imx8m_vpu_hw.c index f9f276385c1178..5be0e2e76882f1 100644 --- a/drivers/media/platform/verisilicon/imx8m_vpu_hw.c +++ b/drivers/media/platform/verisilicon/imx8m_vpu_hw.c @@ -294,11 +294,13 @@ static const struct hantro_codec_ops imx8mq_vpu_g1_codec_ops[] = { static const struct hantro_codec_ops imx8mq_vpu_g2_codec_ops[] = { [HANTRO_MODE_HEVC_DEC] = { .run = hantro_g2_hevc_dec_run, + .reset = hantro_g2_reset, .init = hantro_hevc_dec_init, .exit = hantro_hevc_dec_exit, }, [HANTRO_MODE_VP9_DEC] = { .run = hantro_g2_vp9_dec_run, + .reset = hantro_g2_reset, .done = hantro_g2_vp9_dec_done, .init = hantro_vp9_dec_init, .exit = hantro_vp9_dec_exit, From 51615b85109b6d92a3df78b251c3539af00c2eff Mon Sep 17 00:00:00 2001 From: Paresh Bhagat Date: Wed, 29 Oct 2025 02:31:53 +0530 Subject: [PATCH 1902/3902] arm64: dts: ti: k3-am62d2-evm: Fix regulator properties commit 0103435072bf5c54bb43d1a9376d08396c825827 upstream. Fix missing supply for regulators TLV7103318QDSERQ1 and TPS22918DBVR. Correct padconfig and gpio for TLV7103318QDSERQ1. Reference Docs Datasheet - https://www.ti.com/lit/ug/sprujd4/sprujd4.pdf Schematics - https://www.ti.com/lit/zip/sprcal5 Fixes: 1544bca2f188e ("arm64: dts: ti: Add support for AM62D2-EVM") Cc: stable@vger.kernel.org Signed-off-by: Paresh Bhagat Reviewed-by: Shree Ramamoorthy Link: https://patch.msgid.link/20251028210153.420473-1-p-bhagat@ti.com Signed-off-by: Vignesh Raghavendra Signed-off-by: Greg Kroah-Hartman --- arch/arm64/boot/dts/ti/k3-am62d2-evm.dts | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/arch/arm64/boot/dts/ti/k3-am62d2-evm.dts b/arch/arm64/boot/dts/ti/k3-am62d2-evm.dts index 83af889e790aeb..d202484eec3fb6 100644 --- a/arch/arm64/boot/dts/ti/k3-am62d2-evm.dts +++ b/arch/arm64/boot/dts/ti/k3-am62d2-evm.dts @@ -146,6 +146,7 @@ regulator-name = "vdd_mmc1"; regulator-min-microvolt = <3300000>; regulator-max-microvolt = <3300000>; + vin-supply = <&vcc_3v3_sys>; regulator-boot-on; enable-active-high; gpio = <&exp1 3 GPIO_ACTIVE_HIGH>; @@ -165,14 +166,16 @@ }; vddshv_sdio: regulator-6 { + /* output of TLV7103318QDSERQ1 */ compatible = "regulator-gpio"; regulator-name = "vddshv_sdio"; pinctrl-names = "default"; pinctrl-0 = <&vddshv_sdio_pins_default>; regulator-min-microvolt = <1800000>; regulator-max-microvolt = <3300000>; + vin-supply = <&vcc_5v0>; regulator-boot-on; - gpios = <&main_gpio1 31 GPIO_ACTIVE_HIGH>; + gpios = <&main_gpio0 59 GPIO_ACTIVE_HIGH>; states = <1800000 0x0>, <3300000 0x1>; bootph-all; @@ -334,7 +337,7 @@ vddshv_sdio_pins_default: vddshv-sdio-default-pins { pinctrl-single,pins = < - AM62DX_IOPAD(0x1f4, PIN_OUTPUT, 7) /* (M19) GPMC0_CLK.GPIO1_31 */ + AM62DX_IOPAD(0x00f0, PIN_INPUT, 7) /* (Y21) GPIO0_59 */ >; bootph-all; }; From 0b963a6b61373c7f113ce98f6408dfb422caa79f Mon Sep 17 00:00:00 2001 From: Paresh Bhagat Date: Wed, 29 Oct 2025 03:06:44 +0530 Subject: [PATCH 1903/3902] arm64: dts: ti: k3-am62d2-evm: Fix PMIC padconfig commit 394b02210a81c06c4cb879d65ba83d0f1c468c84 upstream. Fix the PMIC padconfig for AM62D. PMIC's INT pin is connected to the SoC's EXTINTn input. Reference Docs Datasheet - https://www.ti.com/lit/ug/sprujd4/sprujd4.pdf Schematics - https://www.ti.com/lit/zip/sprcal5 Fixes: 1544bca2f188e ("arm64: dts: ti: Add support for AM62D2-EVM") Cc: stable@vger.kernel.org Signed-off-by: Paresh Bhagat Link: https://patch.msgid.link/20251028213645.437957-2-p-bhagat@ti.com Signed-off-by: Vignesh Raghavendra Signed-off-by: Greg Kroah-Hartman --- arch/arm64/boot/dts/ti/k3-am62d2-evm.dts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/arch/arm64/boot/dts/ti/k3-am62d2-evm.dts b/arch/arm64/boot/dts/ti/k3-am62d2-evm.dts index d202484eec3fb6..9a74df221f2ad3 100644 --- a/arch/arm64/boot/dts/ti/k3-am62d2-evm.dts +++ b/arch/arm64/boot/dts/ti/k3-am62d2-evm.dts @@ -201,7 +201,7 @@ pmic_irq_pins_default: pmic-irq-default-pins { pinctrl-single,pins = < - AM62DX_MCU_IOPAD(0x000, PIN_INPUT, 7) /* (E11) MCU_GPIO0_0 */ + AM62DX_IOPAD(0x01f4, PIN_INPUT, 7) /* (F17) EXTINTn.GPIO1_31 */ >; }; From 29dc88cc131de7eda2587ba9c3057cfb847ea93f Mon Sep 17 00:00:00 2001 From: Patrice Chotard Date: Fri, 31 Oct 2025 15:07:03 +0100 Subject: [PATCH 1904/3902] arm64: dts: st: Add memory-region-names property for stm32mp257f-ev1 commit 22f0ae971cf5536349521853737d3e06203286d8 upstream. In order to set the AMCR register, which configures the memory-region split between ospi1 and ospi2, we need to identify the ospi instance. By using memory-region-names, it allows to identify the ospi instance this memory-region belongs to. Fixes: cad2492de91c ("arm64: dts: st: Add SPI NOR flash support on stm32mp257f-ev1 board") Signed-off-by: Patrice Chotard Cc: stable@vger.kernel.org Link: https://lore.kernel.org/r/20251031-upstream_fix_dts_omm-v4-1-e4a059a50074@foss.st.com Signed-off-by: Alexandre Torgue Signed-off-by: Greg Kroah-Hartman --- arch/arm64/boot/dts/st/stm32mp257f-ev1.dts | 1 + 1 file changed, 1 insertion(+) diff --git a/arch/arm64/boot/dts/st/stm32mp257f-ev1.dts b/arch/arm64/boot/dts/st/stm32mp257f-ev1.dts index 6e165073f7329d..bb6d6393d2e46d 100644 --- a/arch/arm64/boot/dts/st/stm32mp257f-ev1.dts +++ b/arch/arm64/boot/dts/st/stm32mp257f-ev1.dts @@ -266,6 +266,7 @@ &ommanager { memory-region = <&mm_ospi1>; + memory-region-names = "ospi1"; pinctrl-0 = <&ospi_port1_clk_pins_a &ospi_port1_io03_pins_a &ospi_port1_cs0_pins_a>; From 65b96ed954b4eacf64e07e5574757009d0b5dff0 Mon Sep 17 00:00:00 2001 From: Luca Weiss Date: Thu, 23 Oct 2025 13:39:26 +0200 Subject: [PATCH 1905/3902] arm64: dts: qcom: sm6350: Fix wrong order of freq-table-hz for UFS commit ec9d588391761a08aab5eb4523a48ef3df2c910f upstream. During upstreaming the order of clocks was adjusted to match the upstream sort order, but mistakently freq-table-hz wasn't re-ordered with the new order. Fix that by moving the entry for the ICE clk to the last place. Fixes: 5a814af5fc22 ("arm64: dts: qcom: sm6350: Add UFS nodes") Cc: stable@vger.kernel.org Reviewed-by: Dmitry Baryshkov Reviewed-by: Konrad Dybcio Signed-off-by: Luca Weiss Link: https://lore.kernel.org/r/20251023-sm6350-ufs-things-v3-1-b68b74e29d35@fairphone.com Signed-off-by: Bjorn Andersson Signed-off-by: Greg Kroah-Hartman --- arch/arm64/boot/dts/qcom/sm6350.dtsi | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/arch/arm64/boot/dts/qcom/sm6350.dtsi b/arch/arm64/boot/dts/qcom/sm6350.dtsi index 8459b27cacc72a..19a7b9f9ea8bcf 100644 --- a/arch/arm64/boot/dts/qcom/sm6350.dtsi +++ b/arch/arm64/boot/dts/qcom/sm6350.dtsi @@ -1180,11 +1180,11 @@ <0 0>, <0 0>, <37500000 150000000>, - <75000000 300000000>, <0 0>, <0 0>, <0 0>, - <0 0>; + <0 0>, + <75000000 300000000>; status = "disabled"; }; From 69a585efbf9c145d6b6d39d9c9dc7a0d6eef8473 Mon Sep 17 00:00:00 2001 From: Christian Marangi Date: Sun, 9 Nov 2025 12:52:44 +0100 Subject: [PATCH 1906/3902] mtd: mtdpart: ignore error -ENOENT from parsers on subpartitions commit 64ef5f454e167bb66cf70104f033c3d71e6ef9c0 upstream. Commit 5c2f7727d437 ("mtd: mtdpart: check for subpartitions parsing result") introduced some kind of regression with parser on subpartitions where if a parser emits an error then the entire parsing process from the upper parser fails and partitions are deleted. Not checking for error in subpartitions was originally intended as special parser can emit error also in the case of the partition not correctly init (for example a wiped partition) or special case where the partition should be skipped due to some ENV variables externally provided (from bootloader for example) One example case is the TRX partition where, in the context of a wiped partition, returns a -ENOENT as the trx_magic is not found in the expected TRX header (as the partition is wiped) To better handle this and still keep some kind of error tracking (for example to catch -ENOMEM errors or -EINVAL errors), permit parser on subpartition to emit -ENOENT error, print a debug log and skip them accordingly. This results in giving better tracking of the status of the parser (instead of returning just 0, dropping any kind of signal that there is something wrong with the parser) and to some degree restore the original logic of the subpartitions parse. (worth to notice that some special partition might have all the special header present for the parser and declare 0 partition in it, this is why it would be wrong to simply return 0 in the case of a special partition that is NOT init for the scanning parser) Cc: stable@vger.kernel.org Fixes: 5c2f7727d437 ("mtd: mtdpart: check for subpartitions parsing result") Signed-off-by: Christian Marangi Signed-off-by: Miquel Raynal Signed-off-by: Greg Kroah-Hartman --- drivers/mtd/mtdpart.c | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/drivers/mtd/mtdpart.c b/drivers/mtd/mtdpart.c index 994e8c51e67438..2876501a781457 100644 --- a/drivers/mtd/mtdpart.c +++ b/drivers/mtd/mtdpart.c @@ -425,9 +425,12 @@ int add_mtd_partitions(struct mtd_info *parent, mtd_add_partition_attrs(child); - /* Look for subpartitions */ + /* Look for subpartitions (skip if no maching parser found) */ ret = parse_mtd_partitions(child, parts[i].types, NULL); - if (ret < 0) { + if (ret < 0 && ret == -ENOENT) { + pr_debug("Skip parsing subpartitions: %d\n", ret); + continue; + } else if (ret < 0) { pr_err("Failed to parse subpartitions: %d\n", ret); goto err_del_partitions; } From 0066afc04fd92d62c2ab3990a3241dadf90c2342 Mon Sep 17 00:00:00 2001 From: Miquel Raynal Date: Wed, 5 Nov 2025 18:27:00 +0100 Subject: [PATCH 1907/3902] mtd: spi-nor: winbond: Add support for W25Q01NWxxIQ chips commit aee8c4d9d48d661624d72de670ebe5c6b5687842 upstream. This chip must be described as none of the block protection information are discoverable. This chip supports 4 bits plus the top/bottom addressing capability to identify the protected blocks. Cc: stable@vger.kernel.org Signed-off-by: Miquel Raynal Reviewed-by: Michael Walle Signed-off-by: Pratyush Yadav Signed-off-by: Greg Kroah-Hartman --- drivers/mtd/spi-nor/winbond.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/drivers/mtd/spi-nor/winbond.c b/drivers/mtd/spi-nor/winbond.c index 63a93c9eb9174b..a13a1201eae922 100644 --- a/drivers/mtd/spi-nor/winbond.c +++ b/drivers/mtd/spi-nor/winbond.c @@ -343,6 +343,10 @@ static const struct flash_info winbond_nor_parts[] = { .id = SNOR_ID(0xef, 0x80, 0x20), .name = "w25q512nwm", .otp = SNOR_OTP(256, 3, 0x1000, 0x1000), + }, { + /* W25Q01NWxxIQ */ + .id = SNOR_ID(0xef, 0x60, 0x21), + .flags = SPI_NOR_HAS_LOCK | SPI_NOR_HAS_TB | SPI_NOR_TB_SR_BIT6 | SPI_NOR_4BIT_BP, }, }; From 609b73efab92e3d3240e64008aa2373da58e1752 Mon Sep 17 00:00:00 2001 From: Miquel Raynal Date: Wed, 5 Nov 2025 18:27:01 +0100 Subject: [PATCH 1908/3902] mtd: spi-nor: winbond: Add support for W25Q01NWxxIM chips commit a607e676c8b9258eabc3fc88f45bcd70ea178b41 upstream. These chips must be described as none of the block protection information are discoverable. This chip supports 4 bits plus the top/bottom addressing capability to identify the protected blocks. Cc: stable@vger.kernel.org Signed-off-by: Miquel Raynal Reviewed-by: Michael Walle Signed-off-by: Pratyush Yadav Signed-off-by: Greg Kroah-Hartman --- drivers/mtd/spi-nor/winbond.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/drivers/mtd/spi-nor/winbond.c b/drivers/mtd/spi-nor/winbond.c index a13a1201eae922..580c9cb3795813 100644 --- a/drivers/mtd/spi-nor/winbond.c +++ b/drivers/mtd/spi-nor/winbond.c @@ -347,6 +347,10 @@ static const struct flash_info winbond_nor_parts[] = { /* W25Q01NWxxIQ */ .id = SNOR_ID(0xef, 0x60, 0x21), .flags = SPI_NOR_HAS_LOCK | SPI_NOR_HAS_TB | SPI_NOR_TB_SR_BIT6 | SPI_NOR_4BIT_BP, + }, { + /* W25Q01NWxxIM */ + .id = SNOR_ID(0xef, 0x80, 0x21), + .flags = SPI_NOR_HAS_LOCK | SPI_NOR_HAS_TB | SPI_NOR_TB_SR_BIT6 | SPI_NOR_4BIT_BP, }, }; From ac1a8b2244dd112ed1f2b1c242ea553af217c73e Mon Sep 17 00:00:00 2001 From: Miquel Raynal Date: Wed, 5 Nov 2025 18:27:02 +0100 Subject: [PATCH 1909/3902] mtd: spi-nor: winbond: Add support for W25Q02NWxxIM chips commit 71c239348d9fbdb1f0d6f36013f1697cc06c3e9c upstream. These chips must be described as none of the block protection information are discoverable. This chip supports 4 bits plus the top/bottom addressing capability to identify the protected blocks. Cc: stable@vger.kernel.org Signed-off-by: Miquel Raynal Reviewed-by: Michael Walle Signed-off-by: Pratyush Yadav Signed-off-by: Greg Kroah-Hartman --- drivers/mtd/spi-nor/winbond.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/drivers/mtd/spi-nor/winbond.c b/drivers/mtd/spi-nor/winbond.c index 580c9cb3795813..a65cbbccbbac64 100644 --- a/drivers/mtd/spi-nor/winbond.c +++ b/drivers/mtd/spi-nor/winbond.c @@ -351,6 +351,10 @@ static const struct flash_info winbond_nor_parts[] = { /* W25Q01NWxxIM */ .id = SNOR_ID(0xef, 0x80, 0x21), .flags = SPI_NOR_HAS_LOCK | SPI_NOR_HAS_TB | SPI_NOR_TB_SR_BIT6 | SPI_NOR_4BIT_BP, + }, { + /* W25Q02NWxxIM */ + .id = SNOR_ID(0xef, 0x80, 0x22), + .flags = SPI_NOR_HAS_LOCK | SPI_NOR_HAS_TB | SPI_NOR_TB_SR_BIT6 | SPI_NOR_4BIT_BP, }, }; From c54fec6b0cb954ebf7a7354d3a017c3a29244d91 Mon Sep 17 00:00:00 2001 From: Miquel Raynal Date: Wed, 5 Nov 2025 18:27:03 +0100 Subject: [PATCH 1910/3902] mtd: spi-nor: winbond: Add support for W25H512NWxxAM chips commit f21d2c7d37553b24825918f2f61df123e182b712 upstream. These chips must be described as none of the block protection information are discoverable. This chip supports 4 bits plus the top/bottom addressing capability to identify the protected blocks. Cc: stable@vger.kernel.org Signed-off-by: Miquel Raynal Reviewed-by: Michael Walle Signed-off-by: Pratyush Yadav Signed-off-by: Greg Kroah-Hartman --- drivers/mtd/spi-nor/winbond.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/drivers/mtd/spi-nor/winbond.c b/drivers/mtd/spi-nor/winbond.c index a65cbbccbbac64..781ca0abfcdca2 100644 --- a/drivers/mtd/spi-nor/winbond.c +++ b/drivers/mtd/spi-nor/winbond.c @@ -355,6 +355,10 @@ static const struct flash_info winbond_nor_parts[] = { /* W25Q02NWxxIM */ .id = SNOR_ID(0xef, 0x80, 0x22), .flags = SPI_NOR_HAS_LOCK | SPI_NOR_HAS_TB | SPI_NOR_TB_SR_BIT6 | SPI_NOR_4BIT_BP, + }, { + /* W25H512NWxxAM */ + .id = SNOR_ID(0xef, 0xa0, 0x20), + .flags = SPI_NOR_HAS_LOCK | SPI_NOR_HAS_TB | SPI_NOR_TB_SR_BIT6 | SPI_NOR_4BIT_BP, }, }; From 7708a5031a404a5a24d324dd461b0fa00cf6d67e Mon Sep 17 00:00:00 2001 From: Miquel Raynal Date: Wed, 5 Nov 2025 18:27:04 +0100 Subject: [PATCH 1911/3902] mtd: spi-nor: winbond: Add support for W25H01NWxxAM chips commit 1df1fdbc7e63350b2962dc7d87ded124ee26f3ad upstream. These chips must be described as none of the block protection information are discoverable. This chip supports 4 bits plus the top/bottom addressing capability to identify the protected blocks. Cc: stable@vger.kernel.org Signed-off-by: Miquel Raynal Reviewed-by: Michael Walle Signed-off-by: Pratyush Yadav Signed-off-by: Greg Kroah-Hartman --- drivers/mtd/spi-nor/winbond.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/drivers/mtd/spi-nor/winbond.c b/drivers/mtd/spi-nor/winbond.c index 781ca0abfcdca2..338e44db506a81 100644 --- a/drivers/mtd/spi-nor/winbond.c +++ b/drivers/mtd/spi-nor/winbond.c @@ -359,6 +359,10 @@ static const struct flash_info winbond_nor_parts[] = { /* W25H512NWxxAM */ .id = SNOR_ID(0xef, 0xa0, 0x20), .flags = SPI_NOR_HAS_LOCK | SPI_NOR_HAS_TB | SPI_NOR_TB_SR_BIT6 | SPI_NOR_4BIT_BP, + }, { + /* W25H01NWxxAM */ + .id = SNOR_ID(0xef, 0xa0, 0x21), + .flags = SPI_NOR_HAS_LOCK | SPI_NOR_HAS_TB | SPI_NOR_TB_SR_BIT6 | SPI_NOR_4BIT_BP, }, }; From 71f630e367541808ff7e2166f57af6c075b132b9 Mon Sep 17 00:00:00 2001 From: Miquel Raynal Date: Wed, 5 Nov 2025 18:27:05 +0100 Subject: [PATCH 1912/3902] mtd: spi-nor: winbond: Add support for W25H02NWxxAM chips commit 604cf6a40157abba4677dea9834de8df9047d798 upstream. These chips must be described as none of the block protection information are discoverable. This chip supports 4 bits plus the top/bottom addressing capability to identify the protected blocks. Cc: stable@vger.kernel.org Signed-off-by: Miquel Raynal Reviewed-by: Michael Walle Signed-off-by: Pratyush Yadav Signed-off-by: Greg Kroah-Hartman --- drivers/mtd/spi-nor/winbond.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/drivers/mtd/spi-nor/winbond.c b/drivers/mtd/spi-nor/winbond.c index 338e44db506a81..fb855fe44733db 100644 --- a/drivers/mtd/spi-nor/winbond.c +++ b/drivers/mtd/spi-nor/winbond.c @@ -363,6 +363,10 @@ static const struct flash_info winbond_nor_parts[] = { /* W25H01NWxxAM */ .id = SNOR_ID(0xef, 0xa0, 0x21), .flags = SPI_NOR_HAS_LOCK | SPI_NOR_HAS_TB | SPI_NOR_TB_SR_BIT6 | SPI_NOR_4BIT_BP, + }, { + /* W25H02NWxxAM */ + .id = SNOR_ID(0xef, 0xa0, 0x22), + .flags = SPI_NOR_HAS_LOCK | SPI_NOR_HAS_TB | SPI_NOR_TB_SR_BIT6 | SPI_NOR_4BIT_BP, }, }; From 7d94efe685186816e7e33f9fbe95e13747a1036d Mon Sep 17 00:00:00 2001 From: Chuck Lever Date: Tue, 11 Nov 2025 09:59:30 -0500 Subject: [PATCH 1913/3902] NFSD: Make FILE_SYNC WRITEs comply with spec commit e3e8e176ca4876e6212582022ad80835dddc9de4 upstream. Mike noted that when NFSD responds to an NFS_FILE_SYNC WRITE, it does not also persist file time stamps. To wit, Section 18.32.3 of RFC 8881 mandates: > The client specifies with the stable parameter the method of how > the data is to be processed by the server. If stable is > FILE_SYNC4, the server MUST commit the data written plus all file > system metadata to stable storage before returning results. This > corresponds to the NFSv2 protocol semantics. Any other behavior > constitutes a protocol violation. If stable is DATA_SYNC4, then > the server MUST commit all of the data to stable storage and > enough of the metadata to retrieve the data before returning. Commit 3f3503adb332 ("NFSD: Use vfs_iocb_iter_write()") replaced: - flags |= RWF_SYNC; with: + kiocb.ki_flags |= IOCB_DSYNC; which appears to be correct given: if (flags & RWF_SYNC) kiocb_flags |= IOCB_DSYNC; in kiocb_set_rw_flags(). However the author of that commit did not appreciate that the previous line in kiocb_set_rw_flags() results in IOCB_SYNC also being set: kiocb_flags |= (__force int) (flags & RWF_SUPPORTED); RWF_SUPPORTED contains RWF_SYNC, and RWF_SYNC is the same bit as IOCB_SYNC. Reviewers at the time did not catch the omission. Reported-by: Mike Snitzer Closes: https://lore.kernel.org/linux-nfs/20251018005431.3403-1-cel@kernel.org/T/#t Fixes: 3f3503adb332 ("NFSD: Use vfs_iocb_iter_write()") Cc: stable@vger.kernel.org Reviewed-by: Jeff Layton Reviewed-by: NeilBrown Reviewed-by: Christoph Hellwig Signed-off-by: Chuck Lever Signed-off-by: Greg Kroah-Hartman --- fs/nfsd/vfs.c | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/fs/nfsd/vfs.c b/fs/nfsd/vfs.c index 9cb20d4aeab159..9f0b8bbc41442a 100644 --- a/fs/nfsd/vfs.c +++ b/fs/nfsd/vfs.c @@ -1228,8 +1228,18 @@ nfsd_vfs_write(struct svc_rqst *rqstp, struct svc_fh *fhp, stable = NFS_UNSTABLE; init_sync_kiocb(&kiocb, file); kiocb.ki_pos = offset; - if (stable && !fhp->fh_use_wgather) - kiocb.ki_flags |= IOCB_DSYNC; + if (likely(!fhp->fh_use_wgather)) { + switch (stable) { + case NFS_FILE_SYNC: + /* persist data and timestamps */ + kiocb.ki_flags |= IOCB_DSYNC | IOCB_SYNC; + break; + case NFS_DATA_SYNC: + /* persist data only */ + kiocb.ki_flags |= IOCB_DSYNC; + break; + } + } nvecs = xdr_buf_to_bvec(rqstp->rq_bvec, rqstp->rq_maxpages, payload); iov_iter_bvec(&iter, ITER_SOURCE, rqstp->rq_bvec, nvecs, *cnt); From 116f511839e1a851b3d8be8e63b9c46d3e145774 Mon Sep 17 00:00:00 2001 From: Shin'ichiro Kawasaki Date: Tue, 9 Sep 2025 13:21:22 +0200 Subject: [PATCH 1914/3902] nvmet: pci-epf: move DMA initialization to EPC init callback commit 511b3b644e28d9b66e32515a74c57ff599e89035 upstream. For DMA initialization to work across all EPC drivers, the DMA initialization has to be done in the .init() callback. This is because not all EPC drivers will have a refclock (which is often needed to access registers of a DMA controller embedded in a PCIe controller) at the time the .bind() callback is called. However, all EPC drivers are guaranteed to have a refclock by the time the .init() callback is called. Thus, move the DMA initialization to the .init() callback. This change was already done for other EPF drivers in commit 60bd3e039aa2 ("PCI: endpoint: pci-epf-{mhi/test}: Move DMA initialization to EPC init callback"). Cc: stable@vger.kernel.org Fixes: 0faa0fe6f90e ("nvmet: New NVMe PCI endpoint function target driver") Signed-off-by: Shin'ichiro Kawasaki Signed-off-by: Niklas Cassel Reviewed-by: Damien Le Moal Signed-off-by: Keith Busch Signed-off-by: Greg Kroah-Hartman --- drivers/nvme/target/pci-epf.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/nvme/target/pci-epf.c b/drivers/nvme/target/pci-epf.c index 2e78397a7373a7..9c5b0f78ce8dfc 100644 --- a/drivers/nvme/target/pci-epf.c +++ b/drivers/nvme/target/pci-epf.c @@ -2325,6 +2325,8 @@ static int nvmet_pci_epf_epc_init(struct pci_epf *epf) return ret; } + nvmet_pci_epf_init_dma(nvme_epf); + /* Set device ID, class, etc. */ epf->header->vendorid = ctrl->tctrl->subsys->vendor_id; epf->header->subsys_vendor_id = ctrl->tctrl->subsys->subsys_vendor_id; @@ -2422,8 +2424,6 @@ static int nvmet_pci_epf_bind(struct pci_epf *epf) if (ret) return ret; - nvmet_pci_epf_init_dma(nvme_epf); - return 0; } From 1d0cebc41d83a93655e0d507a8ee358d4c75d836 Mon Sep 17 00:00:00 2001 From: Sven Schnelle Date: Thu, 30 Oct 2025 08:56:05 +0100 Subject: [PATCH 1915/3902] parisc: entry.S: fix space adjustment on interruption for 64-bit userspace commit 1aa4524c0c1b54842c4c0a370171d11b12d0709b upstream. In wide mode, the IASQ contain the upper part of the GVA during interruption. This needs to be reversed before the space is used - otherwise it contains parts of IAOQ. See Page 2-13 "Processing Resources / Interruption Instruction Address Queues" in the Parisc 2.0 Architecture Manual page 2-13 for an explanation. The IAOQ/IASQ space_adjust was skipped for other interruptions than itlb misses. However, the code in handle_interruption() checks whether iasq[0] contains a valid space. Due to the not masked out bits this match failed and the process was killed. Also add space_adjust for IAOQ1/IASQ1 so ptregs contains sane values. Signed-off-by: Sven Schnelle Cc: stable@vger.kernel.org # v6.0+ Signed-off-by: Helge Deller Signed-off-by: Greg Kroah-Hartman --- arch/parisc/kernel/entry.S | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/arch/parisc/kernel/entry.S b/arch/parisc/kernel/entry.S index f4bf61a34701e5..bb383487e302d1 100644 --- a/arch/parisc/kernel/entry.S +++ b/arch/parisc/kernel/entry.S @@ -1059,8 +1059,6 @@ ENTRY_CFI(intr_save) /* for os_hpmc */ STREG %r17, PT_IOR(%r29) #if defined(CONFIG_64BIT) - b,n intr_save2 - skip_save_ior: /* We have a itlb miss, and when executing code above 4 Gb on ILP64, we * need to adjust iasq/iaoq here in the same way we adjusted isr/ior @@ -1069,10 +1067,17 @@ skip_save_ior: bb,COND(>=),n %r8,PSW_W_BIT,intr_save2 LDREG PT_IASQ0(%r29), %r16 LDREG PT_IAOQ0(%r29), %r17 - /* adjust iasq/iaoq */ + /* adjust iasq0/iaoq0 */ space_adjust %r16,%r17,%r1 STREG %r16, PT_IASQ0(%r29) STREG %r17, PT_IAOQ0(%r29) + + LDREG PT_IASQ1(%r29), %r16 + LDREG PT_IAOQ1(%r29), %r17 + /* adjust iasq1/iaoq1 */ + space_adjust %r16,%r17,%r1 + STREG %r16, PT_IASQ1(%r29) + STREG %r17, PT_IAOQ1(%r29) #else skip_save_ior: #endif From a66cd0e5e034e323d04083c1902cc471806b5a9a Mon Sep 17 00:00:00 2001 From: Sven Schnelle Date: Wed, 15 Oct 2025 23:21:41 +0200 Subject: [PATCH 1916/3902] parisc: entry: set W bit for !compat tasks in syscall_restore_rfi() commit 5fb1d3ce3e74a4530042795e1e065422295f1371 upstream. When the kernel leaves to userspace via syscall_restore_rfi(), the W bit is not set in the new PSW. This doesn't cause any problems because there's no 64 bit userspace for parisc. Simple static binaries are usually loaded at addresses way below the 32 bit limit so the W bit doesn't matter. Fix this by setting the W bit when TIF_32BIT is not set. Signed-off-by: Sven Schnelle Cc: stable@vger.kernel.org Signed-off-by: Helge Deller Signed-off-by: Greg Kroah-Hartman --- arch/parisc/kernel/asm-offsets.c | 2 ++ arch/parisc/kernel/entry.S | 5 ++++- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/arch/parisc/kernel/asm-offsets.c b/arch/parisc/kernel/asm-offsets.c index 9abfe65492c65e..3de4b5933b1079 100644 --- a/arch/parisc/kernel/asm-offsets.c +++ b/arch/parisc/kernel/asm-offsets.c @@ -258,6 +258,8 @@ int main(void) BLANK(); DEFINE(TIF_BLOCKSTEP_PA_BIT, 31-TIF_BLOCKSTEP); DEFINE(TIF_SINGLESTEP_PA_BIT, 31-TIF_SINGLESTEP); + DEFINE(TIF_32BIT_PA_BIT, 31-TIF_32BIT); + BLANK(); DEFINE(ASM_PMD_SHIFT, PMD_SHIFT); DEFINE(ASM_PGDIR_SHIFT, PGDIR_SHIFT); diff --git a/arch/parisc/kernel/entry.S b/arch/parisc/kernel/entry.S index bb383487e302d1..e04c5d806c1051 100644 --- a/arch/parisc/kernel/entry.S +++ b/arch/parisc/kernel/entry.S @@ -1846,6 +1846,10 @@ syscall_restore_rfi: extru,= %r19,TIF_BLOCKSTEP_PA_BIT,1,%r0 depi -1,7,1,%r20 /* T bit */ +#ifdef CONFIG_64BIT + extru,<> %r19,TIF_32BIT_PA_BIT,1,%r0 + depi -1,4,1,%r20 /* W bit */ +#endif STREG %r20,TASK_PT_PSW(%r1) /* Always store space registers, since sr3 can be changed (e.g. fork) */ @@ -1859,7 +1863,6 @@ syscall_restore_rfi: STREG %r25,TASK_PT_IASQ0(%r1) STREG %r25,TASK_PT_IASQ1(%r1) - /* XXX W bit??? */ /* Now if old D bit is clear, it means we didn't save all registers * on syscall entry, so do that now. This only happens on TRACEME * calls, or if someone attached to us while we were on a syscall. From 96ce6629d470c055f10034de7356ff36d120fa3d Mon Sep 17 00:00:00 2001 From: Jim Quinlan Date: Fri, 3 Oct 2025 13:04:36 -0400 Subject: [PATCH 1917/3902] PCI: brcmstb: Fix disabling L0s capability commit 9583f9d22991d2cfb5cc59a2552040c4ae98d998 upstream. caab002d5069 ("PCI: brcmstb: Disable L0s component of ASPM if requested") set PCI_EXP_LNKCAP_ASPM_L1 and (optionally) PCI_EXP_LNKCAP_ASPM_L0S in PCI_EXP_LNKCAP (aka PCIE_RC_CFG_PRIV1_LINK_CAPABILITY in brcmstb). But instead of using PCI_EXP_LNKCAP_ASPM_L1 and PCI_EXP_LNKCAP_ASPM_L0S directly, it used PCIE_LINK_STATE_L1 and PCIE_LINK_STATE_L0S, which are Linux-created values that only coincidentally matched the PCIe spec. b478e162f227 ("PCI/ASPM: Consolidate link state defines") later changed them so they no longer matched the PCIe spec, so the bits ended up in the wrong place in PCI_EXP_LNKCAP. Use PCI_EXP_LNKCAP_ASPM_L0S to clear L0s support when there's an 'aspm-no-l0s' property. Rely on brcmstb hardware to advertise L0s and/or L1 support otherwise. Fixes: caab002d5069 ("PCI: brcmstb: Disable L0s component of ASPM if requested") Reported-by: Bjorn Helgaas Closes: https://lore.kernel.org/linux-pci/20250925194424.GA2197200@bhelgaas Signed-off-by: Jim Quinlan [mani: reworded subject and description, added closes tag and CCed stable] Signed-off-by: Manivannan Sadhasivam [bhelgaas: commit log] Signed-off-by: Bjorn Helgaas Reviewed-by: Florian Fainelli Cc: stable@vger.kernel.org Link: https://patch.msgid.link/20251003170436.1446030-1-james.quinlan@broadcom.com Signed-off-by: Greg Kroah-Hartman --- drivers/pci/controller/pcie-brcmstb.c | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/drivers/pci/controller/pcie-brcmstb.c b/drivers/pci/controller/pcie-brcmstb.c index 9afbd02ded35ee..7e9b2f6a604a5a 100644 --- a/drivers/pci/controller/pcie-brcmstb.c +++ b/drivers/pci/controller/pcie-brcmstb.c @@ -48,7 +48,6 @@ #define PCIE_RC_CFG_PRIV1_LINK_CAPABILITY 0x04dc #define PCIE_RC_CFG_PRIV1_LINK_CAPABILITY_MAX_LINK_WIDTH_MASK 0x1f0 -#define PCIE_RC_CFG_PRIV1_LINK_CAPABILITY_ASPM_SUPPORT_MASK 0xc00 #define PCIE_RC_CFG_PRIV1_ROOT_CAP 0x4f8 #define PCIE_RC_CFG_PRIV1_ROOT_CAP_L1SS_MODE_MASK 0xf8 @@ -1075,7 +1074,7 @@ static int brcm_pcie_setup(struct brcm_pcie *pcie) void __iomem *base = pcie->base; struct pci_host_bridge *bridge; struct resource_entry *entry; - u32 tmp, burst, aspm_support, num_lanes, num_lanes_cap; + u32 tmp, burst, num_lanes, num_lanes_cap; u8 num_out_wins = 0; int num_inbound_wins = 0; int memc, ret; @@ -1175,12 +1174,9 @@ static int brcm_pcie_setup(struct brcm_pcie *pcie) /* Don't advertise L0s capability if 'aspm-no-l0s' */ - aspm_support = PCIE_LINK_STATE_L1; - if (!of_property_read_bool(pcie->np, "aspm-no-l0s")) - aspm_support |= PCIE_LINK_STATE_L0S; tmp = readl(base + PCIE_RC_CFG_PRIV1_LINK_CAPABILITY); - u32p_replace_bits(&tmp, aspm_support, - PCIE_RC_CFG_PRIV1_LINK_CAPABILITY_ASPM_SUPPORT_MASK); + if (of_property_read_bool(pcie->np, "aspm-no-l0s")) + tmp &= ~PCI_EXP_LNKCAP_ASPM_L0S; writel(tmp, base + PCIE_RC_CFG_PRIV1_LINK_CAPABILITY); /* 'tmp' still holds the contents of PRIV1_LINK_CAPABILITY */ From b635895918d856527593f8bc5c4974a53b3eadfe Mon Sep 17 00:00:00 2001 From: Manivannan Sadhasivam Date: Sat, 1 Nov 2025 09:59:42 +0530 Subject: [PATCH 1918/3902] PCI: meson: Fix parsing the DBI register region commit eff0306b109f2d611e44f0155b0324f6cfec3ef4 upstream. First of all, the driver was parsing the 'dbi' register region as 'elbi'. This was due to DT mistakenly passing 'dbi' as 'elbi'. Since the DT is now fixed to supply 'dbi' region, this driver can rely on the DWC core driver to parse and map it. However, to support the old DTs, if the 'elbi' region is found in DT, parse and map the region as both 'dw_pcie::elbi_base' as 'dw_pcie::dbi_base'. This will allow the driver to work with both broken and fixed DTs. Also, skip parsing the 'elbi' region in DWC core if 'pci->elbi_base' was already populated. Fixes: 9c0ef6d34fdb ("PCI: amlogic: Add the Amlogic Meson PCIe controller driver") Fixes: c96992a24bec ("PCI: dwc: Add support for ELBI resource mapping") Reported-by: Linnaea Lavia Closes: https://lore.kernel.org/linux-pci/DM4PR05MB102707B8CDF84D776C39F22F2C7F0A@DM4PR05MB10270.namprd05.prod.outlook.com/ Signed-off-by: Manivannan Sadhasivam Signed-off-by: Manivannan Sadhasivam Tested-by: Neil Armstrong # on Bananapi-M2S Reviewed-by: Neil Armstrong Cc: stable@vger.kernel.org # 6.2 Link: https://patch.msgid.link/20251101-pci-meson-fix-v1-3-c50dcc56ed6a@oss.qualcomm.com Signed-off-by: Greg Kroah-Hartman --- drivers/pci/controller/dwc/pci-meson.c | 18 +++++++++++++++--- drivers/pci/controller/dwc/pcie-designware.c | 12 +++++++----- 2 files changed, 22 insertions(+), 8 deletions(-) diff --git a/drivers/pci/controller/dwc/pci-meson.c b/drivers/pci/controller/dwc/pci-meson.c index 787469d1b396d4..54b6a4196f1767 100644 --- a/drivers/pci/controller/dwc/pci-meson.c +++ b/drivers/pci/controller/dwc/pci-meson.c @@ -108,10 +108,22 @@ static int meson_pcie_get_mems(struct platform_device *pdev, struct meson_pcie *mp) { struct dw_pcie *pci = &mp->pci; + struct resource *res; - pci->dbi_base = devm_platform_ioremap_resource_byname(pdev, "elbi"); - if (IS_ERR(pci->dbi_base)) - return PTR_ERR(pci->dbi_base); + /* + * For the broken DTs that supply 'dbi' as 'elbi', parse the 'elbi' + * region and assign it to both 'pci->elbi_base' and 'pci->dbi_space' so + * that the DWC core can skip parsing both regions. + */ + res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "elbi"); + if (res) { + pci->elbi_base = devm_pci_remap_cfg_resource(pci->dev, res); + if (IS_ERR(pci->elbi_base)) + return PTR_ERR(pci->elbi_base); + + pci->dbi_base = pci->elbi_base; + pci->dbi_phys_addr = res->start; + } mp->cfg_base = devm_platform_ioremap_resource_byname(pdev, "cfg"); if (IS_ERR(mp->cfg_base)) diff --git a/drivers/pci/controller/dwc/pcie-designware.c b/drivers/pci/controller/dwc/pcie-designware.c index c644216995f69c..06eca858eb1b3c 100644 --- a/drivers/pci/controller/dwc/pcie-designware.c +++ b/drivers/pci/controller/dwc/pcie-designware.c @@ -168,11 +168,13 @@ int dw_pcie_get_resources(struct dw_pcie *pci) } /* ELBI is an optional resource */ - res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "elbi"); - if (res) { - pci->elbi_base = devm_ioremap_resource(pci->dev, res); - if (IS_ERR(pci->elbi_base)) - return PTR_ERR(pci->elbi_base); + if (!pci->elbi_base) { + res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "elbi"); + if (res) { + pci->elbi_base = devm_ioremap_resource(pci->dev, res); + if (IS_ERR(pci->elbi_base)) + return PTR_ERR(pci->elbi_base); + } } /* LLDD is supposed to manually switch the clocks and resets state */ From c540db17e4afeaf1f4467b76c62b72ac4e2b001b Mon Sep 17 00:00:00 2001 From: Sandipan Das Date: Tue, 9 Dec 2025 13:56:38 +0530 Subject: [PATCH 1919/3902] perf/x86/amd/uncore: Fix the return value of amd_uncore_df_event_init() on error commit 01439286514ce9d13b8123f8ec3717d7135ff1d6 upstream. If amd_uncore_event_init() fails, return an error irrespective of the pmu_version. Setting hwc->config should be safe even if there is an error so use this opportunity to simplify the code. Closes: https://lore.kernel.org/all/aTaI0ci3vZ44lmBn@stanley.mountain/ Fixes: d6389d3ccc13 ("perf/x86/amd/uncore: Refactor uncore management") Reported-by: Dan Carpenter Signed-off-by: Sandipan Das Signed-off-by: Ingo Molnar Cc: Peter Zijlstra Cc: stable@vger.kernel.org Link: https://patch.msgid.link/076935e23a70335d33bd6e23308b75ae0ad35ba2.1765268667.git.sandipan.das@amd.com Signed-off-by: Greg Kroah-Hartman --- arch/x86/events/amd/uncore.c | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/arch/x86/events/amd/uncore.c b/arch/x86/events/amd/uncore.c index e8b6af199c738e..9293ce50574dad 100644 --- a/arch/x86/events/amd/uncore.c +++ b/arch/x86/events/amd/uncore.c @@ -656,14 +656,11 @@ static int amd_uncore_df_event_init(struct perf_event *event) struct hw_perf_event *hwc = &event->hw; int ret = amd_uncore_event_init(event); - if (ret || pmu_version < 2) - return ret; - hwc->config = event->attr.config & (pmu_version >= 2 ? AMD64_PERFMON_V2_RAW_EVENT_MASK_NB : AMD64_RAW_EVENT_MASK_NB); - return 0; + return ret; } static int amd_uncore_df_add(struct perf_event *event, int flags) From 8a241df8cf6d05607ec1b754bd52e7440097a56a Mon Sep 17 00:00:00 2001 From: Krzysztof Kozlowski Date: Thu, 23 Oct 2025 12:29:06 +0200 Subject: [PATCH 1920/3902] power: supply: max77705: Fix potential IRQ chip conflict when probing two devices commit 1cb053ea2e1dedd8f2d9653b7c3ca5b93c8c9275 upstream. MAX77705 charger is most likely always a single device on the board, however nothing stops board designers to have two of them, thus same device driver could probe twice. Or user could manually try to probing second time. Device driver is not ready for that case, because it allocates statically 'struct regmap_irq_chip' as non-const and stores during probe in 'irq_drv_data' member a pointer to per-probe state container ('struct max77705_charger_data'). devm_regmap_add_irq_chip() does not make a copy of 'struct regmap_irq_chip' but stores the pointer. Second probe - either successful or failure - would overwrite the 'irq_drv_data' from previous device probe, so interrupts would be executed in a wrong context. Fixes: a6a494c8e3ce ("power: supply: max77705: Add charger driver for Maxim 77705") Cc: stable@vger.kernel.org Signed-off-by: Krzysztof Kozlowski Link: https://patch.msgid.link/20251023102905.71535-2-krzysztof.kozlowski@linaro.org Signed-off-by: Sebastian Reichel Signed-off-by: Greg Kroah-Hartman --- drivers/power/supply/max77705_charger.c | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/drivers/power/supply/max77705_charger.c b/drivers/power/supply/max77705_charger.c index b1a227bf72e26f..1044bf58aeaca8 100644 --- a/drivers/power/supply/max77705_charger.c +++ b/drivers/power/supply/max77705_charger.c @@ -60,7 +60,7 @@ static const struct regmap_irq max77705_charger_irqs[] = { REGMAP_IRQ_REG_LINE(MAX77705_AICL_I, BITS_PER_BYTE), }; -static struct regmap_irq_chip max77705_charger_irq_chip = { +static const struct regmap_irq_chip max77705_charger_irq_chip = { .name = "max77705-charger", .status_base = MAX77705_CHG_REG_INT, .mask_base = MAX77705_CHG_REG_INT_MASK, @@ -567,6 +567,7 @@ static int max77705_charger_probe(struct i2c_client *i2c) { struct power_supply_config pscfg = {}; struct max77705_charger_data *chg; + struct regmap_irq_chip *chip_desc; struct device *dev; struct regmap_irq_chip_data *irq_data; int ret; @@ -580,6 +581,13 @@ static int max77705_charger_probe(struct i2c_client *i2c) chg->dev = dev; i2c_set_clientdata(i2c, chg); + chip_desc = devm_kmemdup(dev, &max77705_charger_irq_chip, + sizeof(max77705_charger_irq_chip), + GFP_KERNEL); + if (!chip_desc) + return -ENOMEM; + chip_desc->irq_drv_data = chg; + chg->regmap = devm_regmap_init_i2c(i2c, &max77705_chg_regmap_config); if (IS_ERR(chg->regmap)) return PTR_ERR(chg->regmap); @@ -599,11 +607,9 @@ static int max77705_charger_probe(struct i2c_client *i2c) if (IS_ERR(chg->psy_chg)) return PTR_ERR(chg->psy_chg); - max77705_charger_irq_chip.irq_drv_data = chg; ret = devm_regmap_add_irq_chip(chg->dev, chg->regmap, i2c->irq, IRQF_ONESHOT, 0, - &max77705_charger_irq_chip, - &irq_data); + chip_desc, &irq_data); if (ret) return dev_err_probe(dev, ret, "failed to add irq chip\n"); From 40a2a25aff5c1d896d426f3184d726fbaf7bf321 Mon Sep 17 00:00:00 2001 From: David Hildenbrand Date: Tue, 21 Oct 2025 12:06:06 +0200 Subject: [PATCH 1921/3902] powerpc/pseries/cmm: adjust BALLOON_MIGRATE when migrating pages commit 0da2ba35c0d532ca0fe7af698b17d74c4d084b9a upstream. Let's properly adjust BALLOON_MIGRATE like the other drivers. Note that the INFLATE/DEFLATE events are triggered from the core when enqueueing/dequeueing pages. This was found by code inspection. Link: https://lkml.kernel.org/r/20251021100606.148294-3-david@redhat.com Fixes: fe030c9b85e6 ("powerpc/pseries/cmm: Implement balloon compaction") Signed-off-by: David Hildenbrand Reviewed-by: Ritesh Harjani (IBM) Cc: Christophe Leroy Cc: Madhavan Srinivasan Cc: Michael Ellerman Cc: Nicholas Piggin Cc: Signed-off-by: Andrew Morton Signed-off-by: Greg Kroah-Hartman --- arch/powerpc/platforms/pseries/cmm.c | 1 + 1 file changed, 1 insertion(+) diff --git a/arch/powerpc/platforms/pseries/cmm.c b/arch/powerpc/platforms/pseries/cmm.c index 0823fa2da15162..fd958b444adcdc 100644 --- a/arch/powerpc/platforms/pseries/cmm.c +++ b/arch/powerpc/platforms/pseries/cmm.c @@ -532,6 +532,7 @@ static int cmm_migratepage(struct balloon_dev_info *b_dev_info, spin_lock_irqsave(&b_dev_info->pages_lock, flags); balloon_page_insert(b_dev_info, newpage); + __count_vm_event(BALLOON_MIGRATE); b_dev_info->isolated_pages--; spin_unlock_irqrestore(&b_dev_info->pages_lock, flags); From 8fb48de871a5feee1b68130eedcd304be44b103e Mon Sep 17 00:00:00 2001 From: David Hildenbrand Date: Tue, 21 Oct 2025 12:06:05 +0200 Subject: [PATCH 1922/3902] powerpc/pseries/cmm: call balloon_devinfo_init() also without CONFIG_BALLOON_COMPACTION commit fc6bcf9ac4de76f5e7bcd020b3c0a86faff3f2d5 upstream. Patch series "powerpc/pseries/cmm: two smaller fixes". Two smaller fixes identified while doing a bigger rework. This patch (of 2): We always have to initialize the balloon_dev_info, even when compaction is not configured in: otherwise the containing list and the lock are left uninitialized. Likely not many such configs exist in practice, but let's CC stable to be sure. This was found by code inspection. Link: https://lkml.kernel.org/r/20251021100606.148294-1-david@redhat.com Link: https://lkml.kernel.org/r/20251021100606.148294-2-david@redhat.com Fixes: fe030c9b85e6 ("powerpc/pseries/cmm: Implement balloon compaction") Signed-off-by: David Hildenbrand Reviewed-by: Ritesh Harjani (IBM) Cc: Christophe Leroy Cc: Madhavan Srinivasan Cc: Michael Ellerman Cc: Nicholas Piggin Cc: Signed-off-by: Andrew Morton Signed-off-by: Greg Kroah-Hartman --- arch/powerpc/platforms/pseries/cmm.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/arch/powerpc/platforms/pseries/cmm.c b/arch/powerpc/platforms/pseries/cmm.c index fd958b444adcdc..310dab4bc86790 100644 --- a/arch/powerpc/platforms/pseries/cmm.c +++ b/arch/powerpc/platforms/pseries/cmm.c @@ -551,7 +551,6 @@ static int cmm_migratepage(struct balloon_dev_info *b_dev_info, static void cmm_balloon_compaction_init(void) { - balloon_devinfo_init(&b_dev_info); b_dev_info.migratepage = cmm_migratepage; } #else /* CONFIG_BALLOON_COMPACTION */ @@ -573,6 +572,7 @@ static int cmm_init(void) if (!firmware_has_feature(FW_FEATURE_CMO) && !simulate) return -EOPNOTSUPP; + balloon_devinfo_init(&b_dev_info); cmm_balloon_compaction_init(); rc = register_oom_notifier(&cmm_oom_nb); From b693d48a6ed0cd09171103ad418e4a693203d6e4 Mon Sep 17 00:00:00 2001 From: Ivan Abramov Date: Wed, 3 Sep 2025 02:23:31 +0300 Subject: [PATCH 1923/3902] media: adv7842: Avoid possible out-of-bounds array accesses in adv7842_cp_log_status() commit 8163419e3e05d71dcfa8fb49c8fdf8d76908fe51 upstream. It's possible for cp_read() and hdmi_read() to return -EIO. Those values are further used as indexes for accessing arrays. Fix that by checking return values where it's needed. Found by Linux Verification Center (linuxtesting.org) with SVACE. Fixes: a89bcd4c6c20 ("[media] adv7842: add new video decoder driver") Cc: stable@vger.kernel.org Signed-off-by: Ivan Abramov Signed-off-by: Hans Verkuil Signed-off-by: Greg Kroah-Hartman --- drivers/media/i2c/adv7842.c | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/drivers/media/i2c/adv7842.c b/drivers/media/i2c/adv7842.c index 9780082db8415a..111186529ed403 100644 --- a/drivers/media/i2c/adv7842.c +++ b/drivers/media/i2c/adv7842.c @@ -2699,6 +2699,7 @@ static int adv7842_cp_log_status(struct v4l2_subdev *sd) /* CP block */ struct adv7842_state *state = to_state(sd); struct v4l2_dv_timings timings; + int temp; u8 reg_io_0x02 = io_read(sd, 0x02); u8 reg_io_0x21 = io_read(sd, 0x21); u8 reg_rep_0x77 = rep_read(sd, 0x77); @@ -2821,8 +2822,9 @@ static int adv7842_cp_log_status(struct v4l2_subdev *sd) (((reg_io_0x02 >> 2) & 0x01) ^ (reg_io_0x02 & 0x01)) ? "(16-235)" : "(0-255)", (reg_io_0x02 & 0x08) ? "enabled" : "disabled"); + temp = cp_read(sd, 0xf4) >> 4; v4l2_info(sd, "Color space conversion: %s\n", - csc_coeff_sel_rb[cp_read(sd, 0xf4) >> 4]); + temp < 0 ? "" : csc_coeff_sel_rb[temp]); if (!is_digital_input(sd)) return 0; @@ -2852,8 +2854,9 @@ static int adv7842_cp_log_status(struct v4l2_subdev *sd) hdmi_read(sd, 0x5f)); v4l2_info(sd, "AV Mute: %s\n", (hdmi_read(sd, 0x04) & 0x40) ? "on" : "off"); + temp = hdmi_read(sd, 0x0b) >> 6; v4l2_info(sd, "Deep color mode: %s\n", - deep_color_mode_txt[hdmi_read(sd, 0x0b) >> 6]); + temp < 0 ? "" : deep_color_mode_txt[temp]); adv7842_log_infoframes(sd); From 69a5f0fa6e559356b2f0933649e43d2bea928e56 Mon Sep 17 00:00:00 2001 From: Mahesh Rao Date: Mon, 27 Oct 2025 22:54:40 +0800 Subject: [PATCH 1924/3902] firmware: stratix10-svc: Add mutex in stratix10 memory management commit 85f96cbbbc67b59652b2c1ec394b8ddc0ddf1b0b upstream. Add mutex lock to stratix10_svc_allocate_memory and stratix10_svc_free_memory for thread safety. This prevents race conditions and ensures proper synchronization during memory operations. This is required for parallel communication with the Stratix10 service channel. Fixes: 7ca5ce896524f ("firmware: add Intel Stratix10 service layer driver") Cc: stable@vger.kernel.org Signed-off-by: Mahesh Rao Reviewed-by: Matthew Gerlach Signed-off-by: Dinh Nguyen Signed-off-by: Greg Kroah-Hartman --- drivers/firmware/stratix10-svc.c | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/drivers/firmware/stratix10-svc.c b/drivers/firmware/stratix10-svc.c index deee0e7be34bd1..1d24e82f3d09d8 100644 --- a/drivers/firmware/stratix10-svc.c +++ b/drivers/firmware/stratix10-svc.c @@ -1,6 +1,7 @@ // SPDX-License-Identifier: GPL-2.0 /* * Copyright (C) 2017-2018, Intel Corporation + * Copyright (C) 2025, Altera Corporation */ #include @@ -175,6 +176,12 @@ struct stratix10_svc_chan { static LIST_HEAD(svc_ctrl); static LIST_HEAD(svc_data_mem); +/** + * svc_mem_lock protects access to the svc_data_mem list for + * concurrent multi-client operations + */ +static DEFINE_MUTEX(svc_mem_lock); + /** * svc_pa_to_va() - translate physical address to virtual address * @addr: to be translated physical address @@ -187,6 +194,7 @@ static void *svc_pa_to_va(unsigned long addr) struct stratix10_svc_data_mem *pmem; pr_debug("claim back P-addr=0x%016x\n", (unsigned int)addr); + guard(mutex)(&svc_mem_lock); list_for_each_entry(pmem, &svc_data_mem, node) if (pmem->paddr == addr) return pmem->vaddr; @@ -993,6 +1001,7 @@ int stratix10_svc_send(struct stratix10_svc_chan *chan, void *msg) p_data->flag = ct->flags; } } else { + guard(mutex)(&svc_mem_lock); list_for_each_entry(p_mem, &svc_data_mem, node) if (p_mem->vaddr == p_msg->payload) { p_data->paddr = p_mem->paddr; @@ -1075,6 +1084,7 @@ void *stratix10_svc_allocate_memory(struct stratix10_svc_chan *chan, if (!pmem) return ERR_PTR(-ENOMEM); + guard(mutex)(&svc_mem_lock); va = gen_pool_alloc(genpool, s); if (!va) return ERR_PTR(-ENOMEM); @@ -1103,6 +1113,7 @@ EXPORT_SYMBOL_GPL(stratix10_svc_allocate_memory); void stratix10_svc_free_memory(struct stratix10_svc_chan *chan, void *kaddr) { struct stratix10_svc_data_mem *pmem; + guard(mutex)(&svc_mem_lock); list_for_each_entry(pmem, &svc_data_mem, node) if (pmem->vaddr == kaddr) { From e36fd1a4c2ec8485b3ad48833c1fed4bb02348e6 Mon Sep 17 00:00:00 2001 From: "Uladzislau Rezki (Sony)" Date: Mon, 17 Nov 2025 11:59:45 +0100 Subject: [PATCH 1925/3902] dm-ebs: Mark full buffer dirty even on partial write commit 7fa3e7d114abc9cc71cc35d768e116641074ddb4 upstream. When performing a read-modify-write(RMW) operation, any modification to a buffered block must cause the entire buffer to be marked dirty. Marking only a subrange as dirty is incorrect because the underlying device block size(ubs) defines the minimum read/write granularity. A lower device can perform I/O only on regions which are fully aligned and sized to ubs. This change ensures that write-back operations always occur in full ubs-sized chunks, matching the intended emulation semantics of the EBS target. As for user space visible impact, submitting sub-ubs and misaligned I/O for devices which are tuned to ubs sizes only, will reject such requests, therefore it can lead to losing data. Example: 1) Create a 8K nvme device in qemu by adding -device nvme,drive=drv0,serial=foo,logical_block_size=8192,physical_block_size=8192 2) Setup dm-ebs to emulate 512B to 8K mapping urezki@pc638:~/bin$ cat dmsetup.sh lower=/dev/nvme0n1 len=$(blockdev --getsz "$lower") echo "0 $len ebs $lower 0 1 16" | dmsetup create nvme-8k urezki@pc638:~/bin$ offset 0, ebs=1 and ubs=16(in sectors). 3) Create an ext4 filesystem(default 4K block size) urezki@pc638:~/bin$ sudo mkfs.ext4 -F /dev/dm-0 mke2fs 1.47.0 (5-Feb-2023) Discarding device blocks: done Creating filesystem with 2072576 4k blocks and 518144 inodes Filesystem UUID: bd0b6ca6-0506-4e31-86da-8d22c9d50b63 Superblock backups stored on blocks: 32768, 98304, 163840, 229376, 294912, 819200, 884736, 1605632 Allocating group tables: done Writing inode tables: done Creating journal (16384 blocks): done Writing superblocks and filesystem accounting information: mkfs.ext4: Input/output error while writing out and closing file system urezki@pc638:~/bin$ dmesg [ 1618.875449] buffer_io_error: 1028 callbacks suppressed [ 1618.875456] Buffer I/O error on dev dm-0, logical block 0, lost async page write [ 1618.875527] Buffer I/O error on dev dm-0, logical block 1, lost async page write [ 1618.875602] Buffer I/O error on dev dm-0, logical block 2, lost async page write [ 1618.875620] Buffer I/O error on dev dm-0, logical block 3, lost async page write [ 1618.875639] Buffer I/O error on dev dm-0, logical block 4, lost async page write [ 1618.894316] Buffer I/O error on dev dm-0, logical block 5, lost async page write [ 1618.894358] Buffer I/O error on dev dm-0, logical block 6, lost async page write [ 1618.894380] Buffer I/O error on dev dm-0, logical block 7, lost async page write [ 1618.894405] Buffer I/O error on dev dm-0, logical block 8, lost async page write [ 1618.894427] Buffer I/O error on dev dm-0, logical block 9, lost async page write Many I/O errors because the lower 8K device rejects sub-ubs/misaligned requests. with a patch: urezki@pc638:~/bin$ sudo mkfs.ext4 -F /dev/dm-0 mke2fs 1.47.0 (5-Feb-2023) Discarding device blocks: done Creating filesystem with 2072576 4k blocks and 518144 inodes Filesystem UUID: 9b54f44f-ef55-4bd4-9e40-c8b775a616ac Superblock backups stored on blocks: 32768, 98304, 163840, 229376, 294912, 819200, 884736, 1605632 Allocating group tables: done Writing inode tables: done Creating journal (16384 blocks): done Writing superblocks and filesystem accounting information: done urezki@pc638:~/bin$ sudo mount /dev/dm-0 /mnt/ urezki@pc638:~/bin$ ls -al /mnt/ total 24 drwxr-xr-x 3 root root 4096 Oct 17 15:13 . drwxr-xr-x 19 root root 4096 Jul 10 19:42 .. drwx------ 2 root root 16384 Oct 17 15:13 lost+found urezki@pc638:~/bin$ After this change: mkfs completes; mount succeeds. Signed-off-by: Uladzislau Rezki (Sony) Signed-off-by: Mikulas Patocka Cc: stable@vger.kernel.org Signed-off-by: Greg Kroah-Hartman --- drivers/md/dm-ebs-target.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/md/dm-ebs-target.c b/drivers/md/dm-ebs-target.c index 6abb31ca966231..b354e74a670e0f 100644 --- a/drivers/md/dm-ebs-target.c +++ b/drivers/md/dm-ebs-target.c @@ -103,7 +103,7 @@ static int __ebs_rw_bvec(struct ebs_c *ec, enum req_op op, struct bio_vec *bv, } else { flush_dcache_page(bv->bv_page); memcpy(ba, pa, cur_len); - dm_bufio_mark_partial_buffer_dirty(b, buf_off, buf_off + cur_len); + dm_bufio_mark_buffer_dirty(b); } dm_bufio_release(b); From c7d8c1018aa6b898b03ec15ae4a66b602a868af3 Mon Sep 17 00:00:00 2001 From: Mikulas Patocka Date: Mon, 20 Oct 2025 14:48:13 +0200 Subject: [PATCH 1926/3902] dm-bufio: align write boundary on physical block size commit d0ac06ae53be0cdb61f5fe6b62d25d3317c51657 upstream. There may be devices with physical block size larger than 4k. If dm-bufio sends I/O that is not aligned on physical block size, performance is degraded. The 4k minimum alignment limit is there because some SSDs report logical and physical block size 512 despite having 4k internally - so dm-bufio shouldn't send I/Os not aligned on 4k boundary, because they perform badly (the SSD does read-modify-write for them). Signed-off-by: Mikulas Patocka Reported-by: Uladzislau Rezki (Sony) Cc: stable@vger.kernel.org Signed-off-by: Greg Kroah-Hartman --- drivers/md/dm-bufio.c | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/drivers/md/dm-bufio.c b/drivers/md/dm-bufio.c index e6d28be11c5c6c..5235f3e4924b7a 100644 --- a/drivers/md/dm-bufio.c +++ b/drivers/md/dm-bufio.c @@ -1374,7 +1374,7 @@ static void submit_io(struct dm_buffer *b, enum req_op op, unsigned short ioprio { unsigned int n_sectors; sector_t sector; - unsigned int offset, end; + unsigned int offset, end, align; b->end_io = end_io; @@ -1388,9 +1388,11 @@ static void submit_io(struct dm_buffer *b, enum req_op op, unsigned short ioprio b->c->write_callback(b); offset = b->write_start; end = b->write_end; - offset &= -DM_BUFIO_WRITE_ALIGN; - end += DM_BUFIO_WRITE_ALIGN - 1; - end &= -DM_BUFIO_WRITE_ALIGN; + align = max(DM_BUFIO_WRITE_ALIGN, + bdev_physical_block_size(b->c->bdev)); + offset &= -align; + end += align - 1; + end &= -align; if (unlikely(end > b->c->block_size)) end = b->c->block_size; From 87f7a7e2ed5715a120d4c601c2b67ae84525d2a2 Mon Sep 17 00:00:00 2001 From: Li Chen Date: Fri, 5 Dec 2025 05:46:19 +0000 Subject: [PATCH 1927/3902] dm pcache: fix cache info indexing commit ee7633178321f5d983db3adfdea9322456cfdaaa upstream. The on-media cache_info index used sizeof(struct) instead of the 4K metadata stride, so gc_percent updates from dmsetup message were written between slots and lost after reboot. Use PCACHE_CACHE_INFO_SIZE in get_cache_info_addr() and align info_index with the slot returned by pcache_meta_find_latest(). Signed-off-by: Li Chen Signed-off-by: Dongsheng Yang Signed-off-by: Mikulas Patocka Reviewed-by: Zheng Gu Cc: stable@vger.kernel.org # 6.18 Signed-off-by: Greg Kroah-Hartman --- drivers/md/dm-pcache/cache.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/drivers/md/dm-pcache/cache.c b/drivers/md/dm-pcache/cache.c index d4385d76ac36d6..534bf07b794f14 100644 --- a/drivers/md/dm-pcache/cache.c +++ b/drivers/md/dm-pcache/cache.c @@ -10,7 +10,8 @@ struct kmem_cache *key_cache; static inline struct pcache_cache_info *get_cache_info_addr(struct pcache_cache *cache) { - return cache->cache_info_addr + cache->info_index; + return (struct pcache_cache_info *)((char *)cache->cache_info_addr + + (size_t)cache->info_index * PCACHE_CACHE_INFO_SIZE); } static void cache_info_write(struct pcache_cache *cache) @@ -49,6 +50,8 @@ static int cache_info_init(struct pcache_cache *cache, struct pcache_cache_optio return -EINVAL; } + cache->info_index = ((char *)cache_info_addr - (char *)cache->cache_info_addr) / PCACHE_CACHE_INFO_SIZE; + return 0; } From 527f5ea4ced1b3bc99f2cba619c4886ef6abe80d Mon Sep 17 00:00:00 2001 From: Li Chen Date: Fri, 5 Dec 2025 05:46:20 +0000 Subject: [PATCH 1928/3902] dm pcache: fix segment info indexing commit 13ea55ea20176736516b20b9ea2d8cf97dbe74f5 upstream. Segment info indexing also used sizeof(struct) instead of the 4K metadata stride, so info_index could point between slots and subsequent writes would advance incorrectly. Derive info_index from the pointer returned by the segment meta search using PCACHE_SEG_INFO_SIZE and advance to the next slot for future updates. Signed-off-by: Li Chen Signed-off-by: Dongsheng Yang Signed-off-by: Mikulas Patocka Reviewed-by: Zheng Gu Cc: stable@vger.kernel.org # 6.18 Signed-off-by: Greg Kroah-Hartman --- drivers/md/dm-pcache/cache_segment.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/drivers/md/dm-pcache/cache_segment.c b/drivers/md/dm-pcache/cache_segment.c index ae57cc261422d8..9d92e2b067ed04 100644 --- a/drivers/md/dm-pcache/cache_segment.c +++ b/drivers/md/dm-pcache/cache_segment.c @@ -56,7 +56,10 @@ static int cache_seg_info_load(struct pcache_cache_segment *cache_seg) ret = -EIO; goto out; } - cache_seg->info_index = cache_seg_info_addr - cache_seg_info_addr_base; + + cache_seg->info_index = + ((char *)cache_seg_info_addr - (char *)cache_seg_info_addr_base) / + PCACHE_SEG_INFO_SIZE; out: mutex_unlock(&cache_seg->info_lock); From f21b17cb5ed5966c5a1c94afd329b9fb570affe1 Mon Sep 17 00:00:00 2001 From: Rene Rebe Date: Fri, 14 Nov 2025 16:00:42 +0100 Subject: [PATCH 1929/3902] fbdev: gbefb: fix to use physical address instead of dma address MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit commit e3f44742bbb10537fe53d83d20dea2a7c167674d upstream. While debuggigng why X would not start on mips64 Sgi/O2 I found the phys adress being off. Turns out the gbefb passed the internal dma_addr as phys. May be broken pre git history. Fix by converting dma_to_phys. Signed-off-by: René Rebe Cc: # v4.0+ Signed-off-by: Helge Deller Signed-off-by: Greg Kroah-Hartman --- drivers/video/fbdev/gbefb.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/drivers/video/fbdev/gbefb.c b/drivers/video/fbdev/gbefb.c index 4c36a3e409bea9..cb6ff15a21dbff 100644 --- a/drivers/video/fbdev/gbefb.c +++ b/drivers/video/fbdev/gbefb.c @@ -12,6 +12,7 @@ #include #include #include +#include #include #include #include @@ -65,7 +66,7 @@ struct gbefb_par { static unsigned int gbe_mem_size = CONFIG_FB_GBE_MEM * 1024*1024; static void *gbe_mem; static dma_addr_t gbe_dma_addr; -static unsigned long gbe_mem_phys; +static phys_addr_t gbe_mem_phys; static struct { uint16_t *cpu; @@ -1183,7 +1184,7 @@ static int gbefb_probe(struct platform_device *p_dev) goto out_release_mem_region; } - gbe_mem_phys = (unsigned long) gbe_dma_addr; + gbe_mem_phys = dma_to_phys(&p_dev->dev, gbe_dma_addr); } par = info->par; From 1470cccf067e7c1be523b9920a36ed1a3883cdd2 Mon Sep 17 00:00:00 2001 From: Thorsten Blum Date: Tue, 2 Dec 2025 19:15:32 +0100 Subject: [PATCH 1930/3902] fbdev: pxafb: Fix multiple clamped values in pxafb_adjust_timing commit 0155e868cbc111846cc2809c1546ea53810a56ae upstream. The variables were never clamped because the return value of clamp_val() was not used. Fix this by assigning the clamped values, and use clamp() instead of clamp_val(). Cc: stable@vger.kernel.org Fixes: 3f16ff608a75 ("[ARM] pxafb: cleanup of the timing checking code") Signed-off-by: Thorsten Blum Signed-off-by: Helge Deller Signed-off-by: Greg Kroah-Hartman --- drivers/video/fbdev/pxafb.c | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/drivers/video/fbdev/pxafb.c b/drivers/video/fbdev/pxafb.c index b96a8a96bce8d1..e418eee825fb24 100644 --- a/drivers/video/fbdev/pxafb.c +++ b/drivers/video/fbdev/pxafb.c @@ -419,12 +419,12 @@ static int pxafb_adjust_timing(struct pxafb_info *fbi, var->yres = max_t(int, var->yres, MIN_YRES); if (!(fbi->lccr0 & LCCR0_LCDT)) { - clamp_val(var->hsync_len, 1, 64); - clamp_val(var->vsync_len, 1, 64); - clamp_val(var->left_margin, 1, 255); - clamp_val(var->right_margin, 1, 255); - clamp_val(var->upper_margin, 1, 255); - clamp_val(var->lower_margin, 1, 255); + var->hsync_len = clamp(var->hsync_len, 1, 64); + var->vsync_len = clamp(var->vsync_len, 1, 64); + var->left_margin = clamp(var->left_margin, 1, 255); + var->right_margin = clamp(var->right_margin, 1, 255); + var->upper_margin = clamp(var->upper_margin, 1, 255); + var->lower_margin = clamp(var->lower_margin, 1, 255); } /* make sure each line is aligned on word boundary */ From 4357fa7bc03c312a666742c4958ad8d183b2fd37 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20Rebe?= Date: Thu, 20 Nov 2025 14:24:00 +0100 Subject: [PATCH 1931/3902] fbdev: tcx.c fix mem_map to correct smem_start offset MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit commit 35fa2b4bf96415b88d7edaa5cf8af5185d9ce76e upstream. 403ae52ac047 ("sparc: fix drivers/video/tcx.c warning") changed the physbase initializing breaking the user-space mmap, e.g. for Xorg entirely. Fix fbdev mmap table so the sbus mmap helper work correctly, and not try to map vastly (physbase) offset memory. Fixes: 403ae52ac047 ("sparc: fix drivers/video/tcx.c warning") Cc: Signed-off-by: René Rebe Signed-off-by: Helge Deller Signed-off-by: Greg Kroah-Hartman --- drivers/video/fbdev/tcx.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/video/fbdev/tcx.c b/drivers/video/fbdev/tcx.c index f9a0085ad72bf7..ca9e84e8d86052 100644 --- a/drivers/video/fbdev/tcx.c +++ b/drivers/video/fbdev/tcx.c @@ -428,7 +428,7 @@ static int tcx_probe(struct platform_device *op) j = i; break; } - par->mmap_map[i].poff = op->resource[j].start; + par->mmap_map[i].poff = op->resource[j].start - info->fix.smem_start; } info->fbops = &tcx_ops; From d786859abab36070bb24d8ed655f377680a9b198 Mon Sep 17 00:00:00 2001 From: Haotian Zhang Date: Mon, 29 Sep 2025 19:12:29 +0800 Subject: [PATCH 1932/3902] media: cec: Fix debugfs leak on bus_register() failure commit c43bcd2b2aa3c2ca9d2433c3990ecbc2c47d10eb upstream. In cec_devnode_init(), the debugfs directory created with debugfs_create_dir() is not removed if bus_register() fails. This leaves a stale "cec" entry in debugfs and prevents proper module reloading. Fix this by removing the debugfs directory in the error path. Fixes: a56960e8b406 ("[media] cec: add HDMI CEC framework (core)") Cc: stable@vger.kernel.org Signed-off-by: Haotian Zhang Signed-off-by: Hans Verkuil Signed-off-by: Greg Kroah-Hartman --- drivers/media/cec/core/cec-core.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/media/cec/core/cec-core.c b/drivers/media/cec/core/cec-core.c index d7259599029f8b..dd6e24a0899bda 100644 --- a/drivers/media/cec/core/cec-core.c +++ b/drivers/media/cec/core/cec-core.c @@ -421,6 +421,7 @@ static int __init cec_devnode_init(void) ret = bus_register(&cec_bus_type); if (ret < 0) { + debugfs_remove_recursive(top_cec_dir); unregister_chrdev_region(cec_dev_t, CEC_NUM_DEVICES); pr_warn("cec: bus_register failed\n"); return -EIO; From 7cd1d942b87e2a4a652281f04d5c8cd20854a555 Mon Sep 17 00:00:00 2001 From: Dikshita Agarwal Date: Wed, 5 Nov 2025 11:17:37 +0530 Subject: [PATCH 1933/3902] media: iris: Refine internal buffer reconfiguration logic for resolution change commit aec75e355c633e4b0967c99580bd8ef93e0cdc98 upstream. Improve the condition used to determine when input internal buffers need to be reconfigured during streamon on the capture port. Previously, the check relied on the INPUT_PAUSE sub-state, which was also being set during seek operations. This led to input buffers being queued multiple times to the firmware, causing session errors due to duplicate buffer submissions. This change introduces a more accurate check using the FIRST_IPSC and DRC sub-states to ensure that input buffer reconfiguration is triggered only during resolution change scenarios, such as streamoff/on on the capture port. This avoids duplicate buffer queuing during seek operations. Fixes: c1f8b2cc72ec ("media: iris: handle streamoff/on from client in dynamic resolution change") Cc: stable@vger.kernel.org Reported-by: Val Packett Closes: https://gitlab.freedesktop.org/gstreamer/gstreamer/-/issues/4700 Signed-off-by: Dikshita Agarwal Reviewed-by: Vikash Garodia Signed-off-by: Bryan O'Donoghue Signed-off-by: Hans Verkuil Signed-off-by: Greg Kroah-Hartman --- drivers/media/platform/qcom/iris/iris_common.c | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/drivers/media/platform/qcom/iris/iris_common.c b/drivers/media/platform/qcom/iris/iris_common.c index 9fc663bdaf3fc9..7f1c7fe144f707 100644 --- a/drivers/media/platform/qcom/iris/iris_common.c +++ b/drivers/media/platform/qcom/iris/iris_common.c @@ -91,12 +91,14 @@ int iris_process_streamon_input(struct iris_inst *inst) int iris_process_streamon_output(struct iris_inst *inst) { const struct iris_hfi_command_ops *hfi_ops = inst->core->hfi_ops; - bool drain_active = false, drc_active = false; enum iris_inst_sub_state clear_sub_state = 0; + bool drain_active, drc_active, first_ipsc; int ret = 0; iris_scale_power(inst); + first_ipsc = inst->sub_state & IRIS_INST_SUB_FIRST_IPSC; + drain_active = inst->sub_state & IRIS_INST_SUB_DRAIN && inst->sub_state & IRIS_INST_SUB_DRAIN_LAST; @@ -108,7 +110,8 @@ int iris_process_streamon_output(struct iris_inst *inst) else if (drain_active) clear_sub_state = IRIS_INST_SUB_DRAIN | IRIS_INST_SUB_DRAIN_LAST; - if (inst->domain == DECODER && inst->sub_state & IRIS_INST_SUB_INPUT_PAUSE) { + /* Input internal buffer reconfiguration required in case of resolution change */ + if (first_ipsc || drc_active) { ret = iris_alloc_and_queue_input_int_bufs(inst); if (ret) return ret; From a003505a5c8c758ccd1beb48e8238ace7e14707b Mon Sep 17 00:00:00 2001 From: Ivan Abramov Date: Wed, 3 Sep 2025 02:28:14 +0300 Subject: [PATCH 1934/3902] media: msp3400: Avoid possible out-of-bounds array accesses in msp3400c_thread() commit d2bceb2e20e783d57e739c71e4e50b4b9f4a3953 upstream. It's possible for max1 to remain -1 if msp_read() always fail. This variable is further used as index for accessing arrays. Fix that by checking max1 prior to array accesses. It seems that restart is the preferable action in case of out-of-bounds value. Found by Linux Verification Center (linuxtesting.org) with SVACE. Fixes: 8a4b275f9c19 ("V4L/DVB (3427): audmode and rxsubchans fixes (VIDIOC_G/S_TUNER)") Cc: stable@vger.kernel.org Signed-off-by: Ivan Abramov Signed-off-by: Hans Verkuil Signed-off-by: Greg Kroah-Hartman --- drivers/media/i2c/msp3400-kthreads.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/drivers/media/i2c/msp3400-kthreads.c b/drivers/media/i2c/msp3400-kthreads.c index ecabc0e1d32e6e..1d9f41dd7c212c 100644 --- a/drivers/media/i2c/msp3400-kthreads.c +++ b/drivers/media/i2c/msp3400-kthreads.c @@ -596,6 +596,8 @@ int msp3400c_thread(void *data) "carrier2 val: %5d / %s\n", val, cd[i].name); } + if (max1 < 0 || max1 > 3) + goto restart; /* program the msp3400 according to the results */ state->main = msp3400c_carrier_detect_main[max1].cdo; switch (max1) { From 31ba1a4d7a5d9466f6ef4910a372b8200f8aad19 Mon Sep 17 00:00:00 2001 From: Johan Hovold Date: Wed, 24 Sep 2025 16:39:19 +0200 Subject: [PATCH 1935/3902] media: platform: mtk-mdp3: fix device leaks at probe commit 8f6f3aa21517ef34d50808af0c572e69580dca20 upstream. Make sure to drop the references taken when looking up the subsys devices during probe on probe failure (e.g. probe deferral) and on driver unbind. Similarly, drop the SCP device reference after retrieving its platform data during probe to avoid leaking it. Note that holding a reference to a device does not prevent its driver data from going away. Fixes: 61890ccaefaf ("media: platform: mtk-mdp3: add MediaTek MDP3 driver") Cc: stable@vger.kernel.org # 6.1 Cc: Moudy Ho Signed-off-by: Johan Hovold Reviewed-by: AngeloGioacchino Del Regno Signed-off-by: Nicolas Dufresne Signed-off-by: Hans Verkuil Signed-off-by: Greg Kroah-Hartman --- .../media/platform/mediatek/mdp3/mtk-mdp3-core.c | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/drivers/media/platform/mediatek/mdp3/mtk-mdp3-core.c b/drivers/media/platform/mediatek/mdp3/mtk-mdp3-core.c index 6559d72d5d4278..6d26d4aa1eef2a 100644 --- a/drivers/media/platform/mediatek/mdp3/mtk-mdp3-core.c +++ b/drivers/media/platform/mediatek/mdp3/mtk-mdp3-core.c @@ -157,10 +157,18 @@ void mdp_video_device_release(struct video_device *vdev) kfree(mdp); } +static void mdp_put_device(void *_dev) +{ + struct device *dev = _dev; + + put_device(dev); +} + static int mdp_mm_subsys_deploy(struct mdp_dev *mdp, enum mdp_infra_id id) { struct platform_device *mm_pdev = NULL; struct device **dev; + int ret; int i; if (!mdp) @@ -194,6 +202,11 @@ static int mdp_mm_subsys_deploy(struct mdp_dev *mdp, enum mdp_infra_id id) if (WARN_ON(!mm_pdev)) return -ENODEV; + ret = devm_add_action_or_reset(&mdp->pdev->dev, mdp_put_device, + &mm_pdev->dev); + if (ret) + return ret; + *dev = &mm_pdev->dev; } @@ -279,6 +292,7 @@ static int mdp_probe(struct platform_device *pdev) goto err_destroy_clock_wq; } mdp->scp = platform_get_drvdata(mm_pdev); + put_device(&mm_pdev->dev); } mdp->rproc_handle = scp_get_rproc(mdp->scp); From ca31135ad666c0650bf2805fcbe71903fad5f671 Mon Sep 17 00:00:00 2001 From: Miaoqian Lin Date: Wed, 3 Sep 2025 21:37:29 +0800 Subject: [PATCH 1936/3902] media: renesas: rcar_drif: fix device node reference leak in rcar_drif_bond_enabled commit 445e1658894fd74eab7e53071fa16233887574ed upstream. The function calls of_parse_phandle() which returns a device node with an incremented reference count. When the bonded device is not available, the function returns NULL without releasing the reference, causing a reference leak. Add of_node_put(np) to release the device node reference. The of_node_put function handles NULL pointers. Found through static analysis by reviewing the doc of of_parse_phandle() and cross-checking its usage patterns across the codebase. Fixes: 7625ee981af1 ("[media] media: platform: rcar_drif: Add DRIF support") Cc: stable@vger.kernel.org Signed-off-by: Miaoqian Lin Reviewed-by: Geert Uytterhoeven Reviewed-by: Fabrizio Castro Signed-off-by: Hans Verkuil Signed-off-by: Greg Kroah-Hartman --- drivers/media/platform/renesas/rcar_drif.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/media/platform/renesas/rcar_drif.c b/drivers/media/platform/renesas/rcar_drif.c index 11bf47fb8266dc..0844934f7aa68b 100644 --- a/drivers/media/platform/renesas/rcar_drif.c +++ b/drivers/media/platform/renesas/rcar_drif.c @@ -1246,6 +1246,7 @@ static struct device_node *rcar_drif_bond_enabled(struct platform_device *p) if (np && of_device_is_available(np)) return np; + of_node_put(np); return NULL; } From f65ef4b8415e2c8f6b8efdae2a7916efe1f63f1d Mon Sep 17 00:00:00 2001 From: Marek Szyprowski Date: Tue, 14 Oct 2025 12:46:43 +0200 Subject: [PATCH 1937/3902] media: samsung: exynos4-is: fix potential ABBA deadlock on init commit 17dc8ccd6dd5ffe30aa9b0d36e2af1389344ce2b upstream. v4l2_device_register_subdev_nodes() must called without taking media_dev->graph_mutex to avoid potential AB-BA deadlock on further subdevice driver initialization. Fixes: fa91f1056f17 ("[media] exynos4-is: Add support for asynchronous subdevices registration") Cc: stable@vger.kernel.org Signed-off-by: Marek Szyprowski Acked-by: Sylwester Nawrocki Signed-off-by: Hans Verkuil Signed-off-by: Greg Kroah-Hartman --- drivers/media/platform/samsung/exynos4-is/media-dev.c | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/drivers/media/platform/samsung/exynos4-is/media-dev.c b/drivers/media/platform/samsung/exynos4-is/media-dev.c index c781853586fd21..e9d7875cf01ec9 100644 --- a/drivers/media/platform/samsung/exynos4-is/media-dev.c +++ b/drivers/media/platform/samsung/exynos4-is/media-dev.c @@ -1399,12 +1399,14 @@ static int subdev_notifier_complete(struct v4l2_async_notifier *notifier) mutex_lock(&fmd->media_dev.graph_mutex); ret = fimc_md_create_links(fmd); - if (ret < 0) - goto unlock; + if (ret < 0) { + mutex_unlock(&fmd->media_dev.graph_mutex); + return ret; + } - ret = v4l2_device_register_subdev_nodes(&fmd->v4l2_dev); -unlock: mutex_unlock(&fmd->media_dev.graph_mutex); + + ret = v4l2_device_register_subdev_nodes(&fmd->v4l2_dev); if (ret < 0) return ret; From 49446f8e73e27c154f4423c768f6e230f00ab77e Mon Sep 17 00:00:00 2001 From: Duoming Zhou Date: Mon, 1 Sep 2025 21:26:17 +0800 Subject: [PATCH 1938/3902] media: TDA1997x: Remove redundant cancel_delayed_work in probe commit 29de195ca39fc2ac0af6fd45522994df9f431f80 upstream. The delayed_work delayed_work_enable_hpd is initialized with INIT_DELAYED_WORK(), but it is never scheduled in tda1997x_probe(). Calling cancel_delayed_work() on a work that has never been scheduled is redundant and unnecessary, as there is no pending work to cancel. Remove the redundant cancel_delayed_work() from error handling path in tda1997x_probe() to avoid potential confusion. Fixes: 9ac0038db9a7 ("media: i2c: Add TDA1997x HDMI receiver driver") Cc: stable@vger.kernel.org Signed-off-by: Duoming Zhou Signed-off-by: Hans Verkuil Signed-off-by: Greg Kroah-Hartman --- drivers/media/i2c/tda1997x.c | 1 - 1 file changed, 1 deletion(-) diff --git a/drivers/media/i2c/tda1997x.c b/drivers/media/i2c/tda1997x.c index 1087d2bddaf29f..3532766cd795d3 100644 --- a/drivers/media/i2c/tda1997x.c +++ b/drivers/media/i2c/tda1997x.c @@ -2797,7 +2797,6 @@ static int tda1997x_probe(struct i2c_client *client) err_free_handler: v4l2_ctrl_handler_free(&state->hdl); err_free_mutex: - cancel_delayed_work(&state->delayed_work_enable_hpd); mutex_destroy(&state->page_lock); mutex_destroy(&state->lock); tda1997x_set_power(state, 0); From 3cbf9ef5ce849f5c99a936e8482176b70d223011 Mon Sep 17 00:00:00 2001 From: Nicolas Dufresne Date: Mon, 22 Sep 2025 14:43:39 -0400 Subject: [PATCH 1939/3902] media: verisilicon: Protect G2 HEVC decoder against invalid DPB index commit 47825b1646a6a9eca0f90baa3d4f98947c2add96 upstream. Fix the Hantro G2 HEVC decoder so that we use DPB index 0 whenever a ninvalid index is received from user space. This protects the hardware from doing faulty memory access which then leads to bus errors. To be noted that when a reference is missing, userspace such as GStreamer passes an invalid DPB index of 255. This issue was found by seeking to a CRA picture using GStreamer. The framework is currently missing the code to skip over RASL pictures placed after the CRA. This situation can also occur while doing live streaming over lossy transport. Fixes: cb5dd5a0fa518 ("media: hantro: Introduce G2/HEVC decoder") Cc: stable@vger.kernel.org Reviewed-by: Benjamin Gaignard Signed-off-by: Nicolas Dufresne Signed-off-by: Hans Verkuil Signed-off-by: Greg Kroah-Hartman --- .../platform/verisilicon/hantro_g2_hevc_dec.c | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/drivers/media/platform/verisilicon/hantro_g2_hevc_dec.c b/drivers/media/platform/verisilicon/hantro_g2_hevc_dec.c index f066636e56f985..e8c2e83379def5 100644 --- a/drivers/media/platform/verisilicon/hantro_g2_hevc_dec.c +++ b/drivers/media/platform/verisilicon/hantro_g2_hevc_dec.c @@ -283,6 +283,15 @@ static void set_params(struct hantro_ctx *ctx) hantro_reg_write(vpu, &g2_apf_threshold, 8); } +static u32 get_dpb_index(const struct v4l2_ctrl_hevc_decode_params *decode_params, + const u32 index) +{ + if (index > decode_params->num_active_dpb_entries) + return 0; + + return index; +} + static void set_ref_pic_list(struct hantro_ctx *ctx) { const struct hantro_hevc_dec_ctrls *ctrls = &ctx->hevc_dec.ctrls; @@ -355,8 +364,10 @@ static void set_ref_pic_list(struct hantro_ctx *ctx) list1[j++] = list1[i++]; for (i = 0; i < V4L2_HEVC_DPB_ENTRIES_NUM_MAX; i++) { - hantro_reg_write(vpu, &ref_pic_regs0[i], list0[i]); - hantro_reg_write(vpu, &ref_pic_regs1[i], list1[i]); + hantro_reg_write(vpu, &ref_pic_regs0[i], + get_dpb_index(decode_params, list0[i])); + hantro_reg_write(vpu, &ref_pic_regs1[i], + get_dpb_index(decode_params, list1[i])); } } From 19f7dfa0c1d2f0b0208ecb5d76c74b7eec1035d1 Mon Sep 17 00:00:00 2001 From: Haotian Zhang Date: Tue, 28 Oct 2025 14:44:43 +0800 Subject: [PATCH 1940/3902] media: videobuf2: Fix device reference leak in vb2_dc_alloc error path commit 94de23a9aa487d7c1372efb161721d7949a177ae upstream. In vb2_dc_alloc(), get_device() is called to increment the device reference count. However, if subsequent DMA allocation fails (vb2_dc_alloc_coherent or vb2_dc_alloc_non_coherent returns error), the function returns without calling put_device(), causing a device reference leak. Add put_device() call in the error path before kfree() to properly release the device reference acquired earlier. Fixes: de27891f675e ("media: videobuf2: handle non-contiguous DMA allocations") Cc: stable@vger.kernel.org Signed-off-by: Haotian Zhang Reviewed-by: Marek Szyprowski Signed-off-by: Hans Verkuil Signed-off-by: Greg Kroah-Hartman --- drivers/media/common/videobuf2/videobuf2-dma-contig.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/media/common/videobuf2/videobuf2-dma-contig.c b/drivers/media/common/videobuf2/videobuf2-dma-contig.c index a13ec569c82f6d..7123c5fae92cee 100644 --- a/drivers/media/common/videobuf2/videobuf2-dma-contig.c +++ b/drivers/media/common/videobuf2/videobuf2-dma-contig.c @@ -258,6 +258,7 @@ static void *vb2_dc_alloc(struct vb2_buffer *vb, if (ret) { dev_err(dev, "dma alloc of size %lu failed\n", size); + put_device(buf->dev); kfree(buf); return ERR_PTR(-ENOMEM); } From 5d25f923bdb4e37680b01037ec197593a3a5b78a Mon Sep 17 00:00:00 2001 From: Johan Hovold Date: Fri, 17 Oct 2025 07:33:20 +0200 Subject: [PATCH 1941/3902] media: vpif_capture: fix section mismatch commit 0ef841113724166c3c484d0e9ae6db1eb5634fde upstream. Platform drivers can be probed after their init sections have been discarded (e.g. on probe deferral or manual rebind through sysfs) so the probe function must not live in init. Note that commit ffa1b391c61b ("V4L/DVB: vpif_cap/disp: Removed section mismatch warning") incorrectly suppressed the modpost warning. Fixes: ffa1b391c61b ("V4L/DVB: vpif_cap/disp: Removed section mismatch warning") Fixes: 6ffefff5a9e7 ("V4L/DVB (12906c): V4L : vpif capture driver for DM6467") Cc: stable@vger.kernel.org # 2.6.32 Signed-off-by: Johan Hovold Signed-off-by: Hans Verkuil Signed-off-by: Greg Kroah-Hartman --- drivers/media/platform/ti/davinci/vpif_capture.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/media/platform/ti/davinci/vpif_capture.c b/drivers/media/platform/ti/davinci/vpif_capture.c index d053972888d1b4..243c6196b0247c 100644 --- a/drivers/media/platform/ti/davinci/vpif_capture.c +++ b/drivers/media/platform/ti/davinci/vpif_capture.c @@ -1600,7 +1600,7 @@ vpif_capture_get_pdata(struct platform_device *pdev, * This creates device entries by register itself to the V4L2 driver and * initializes fields of each channel objects */ -static __init int vpif_probe(struct platform_device *pdev) +static int vpif_probe(struct platform_device *pdev) { struct vpif_subdev_info *subdevdata; struct i2c_adapter *i2c_adap; @@ -1807,7 +1807,7 @@ static int vpif_resume(struct device *dev) static SIMPLE_DEV_PM_OPS(vpif_pm_ops, vpif_suspend, vpif_resume); -static __refdata struct platform_driver vpif_driver = { +static struct platform_driver vpif_driver = { .driver = { .name = VPIF_DRIVER_NAME, .pm = &vpif_pm_ops, From 6132c3e5592683e58ad9ebd97a117855084dd457 Mon Sep 17 00:00:00 2001 From: Johan Hovold Date: Fri, 17 Oct 2025 07:33:21 +0200 Subject: [PATCH 1942/3902] media: vpif_display: fix section mismatch commit 59ca64bf98e4209df8ace8057d31ae3c80f948cd upstream. Platform drivers can be probed after their init sections have been discarded (e.g. on probe deferral or manual rebind through sysfs) so the probe function must not live in init. Note that commit ffa1b391c61b ("V4L/DVB: vpif_cap/disp: Removed section mismatch warning") incorrectly suppressed the modpost warning. Fixes: ffa1b391c61b ("V4L/DVB: vpif_cap/disp: Removed section mismatch warning") Fixes: e7332e3a552f ("V4L/DVB (12176): davinci/vpif_display: Add VPIF display driver") Cc: stable@vger.kernel.org # 2.6.32 Signed-off-by: Johan Hovold Signed-off-by: Hans Verkuil Signed-off-by: Greg Kroah-Hartman --- drivers/media/platform/ti/davinci/vpif_display.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/media/platform/ti/davinci/vpif_display.c b/drivers/media/platform/ti/davinci/vpif_display.c index 70c89549f4b61d..1e7815e9f8e075 100644 --- a/drivers/media/platform/ti/davinci/vpif_display.c +++ b/drivers/media/platform/ti/davinci/vpif_display.c @@ -1214,7 +1214,7 @@ static int vpif_probe_complete(void) * vpif_probe: This function creates device entries by register itself to the * V4L2 driver and initializes fields of each channel objects */ -static __init int vpif_probe(struct platform_device *pdev) +static int vpif_probe(struct platform_device *pdev) { struct vpif_subdev_info *subdevdata; struct i2c_adapter *i2c_adap; @@ -1390,7 +1390,7 @@ static int vpif_resume(struct device *dev) static SIMPLE_DEV_PM_OPS(vpif_pm_ops, vpif_suspend, vpif_resume); -static __refdata struct platform_driver vpif_driver = { +static struct platform_driver vpif_driver = { .driver = { .name = VPIF_DRIVER_NAME, .pm = &vpif_pm_ops, From b9b29dae5962c4f1561f6ca8d20b6a9907a1be79 Mon Sep 17 00:00:00 2001 From: Ming Qian Date: Tue, 16 Sep 2025 14:08:53 +0800 Subject: [PATCH 1943/3902] media: amphion: Remove vpu_vb_is_codecconfig commit 634c2cd17bd021487c57b95973bddb14be8002ff upstream. Currently the function vpu_vb_is_codecconfig() always returns 0. Delete it and its related code. Fixes: 3cd084519c6f ("media: amphion: add vpu v4l2 m2m support") Cc: stable@vger.kernel.org Signed-off-by: Ming Qian Signed-off-by: Nicolas Dufresne Signed-off-by: Hans Verkuil Signed-off-by: Greg Kroah-Hartman --- drivers/media/platform/amphion/vpu_malone.c | 23 +++------------------ drivers/media/platform/amphion/vpu_v4l2.c | 10 --------- drivers/media/platform/amphion/vpu_v4l2.h | 10 --------- 3 files changed, 3 insertions(+), 40 deletions(-) diff --git a/drivers/media/platform/amphion/vpu_malone.c b/drivers/media/platform/amphion/vpu_malone.c index ba688566dffd04..80802975c4f162 100644 --- a/drivers/media/platform/amphion/vpu_malone.c +++ b/drivers/media/platform/amphion/vpu_malone.c @@ -1337,22 +1337,18 @@ static int vpu_malone_insert_scode_vc1_g_seq(struct malone_scode_t *scode) { if (!scode->inst->total_input_count) return 0; - if (vpu_vb_is_codecconfig(to_vb2_v4l2_buffer(scode->vb))) - scode->need_data = 0; return 0; } static int vpu_malone_insert_scode_vc1_g_pic(struct malone_scode_t *scode) { - struct vb2_v4l2_buffer *vbuf; u8 nal_hdr[MALONE_VC1_NAL_HEADER_LEN]; u32 *data = NULL; int ret; - vbuf = to_vb2_v4l2_buffer(scode->vb); data = vb2_plane_vaddr(scode->vb, 0); - if (scode->inst->total_input_count == 0 || vpu_vb_is_codecconfig(vbuf)) + if (scode->inst->total_input_count == 0) return 0; if (MALONE_VC1_CONTAIN_NAL(*data)) return 0; @@ -1373,8 +1369,6 @@ static int vpu_malone_insert_scode_vc1_l_seq(struct malone_scode_t *scode) int size = 0; u8 rcv_seqhdr[MALONE_VC1_RCV_SEQ_HEADER_LEN]; - if (vpu_vb_is_codecconfig(to_vb2_v4l2_buffer(scode->vb))) - scode->need_data = 0; if (scode->inst->total_input_count) return 0; scode->need_data = 0; @@ -1560,7 +1554,7 @@ static int vpu_malone_input_frame_data(struct vpu_malone_str_buffer __iomem *str scode.vb = vb; scode.wptr = wptr; scode.need_data = 1; - if (vbuf->sequence == 0 || vpu_vb_is_codecconfig(vbuf)) + if (vbuf->sequence == 0) ret = vpu_malone_insert_scode(&scode, SCODE_SEQUENCE); if (ret < 0) @@ -1596,7 +1590,7 @@ static int vpu_malone_input_frame_data(struct vpu_malone_str_buffer __iomem *str * This module is currently only supported for the H264 and HEVC formats, * for other formats, vpu_malone_add_scode() will return 0. */ - if ((disp_imm || low_latency) && !vpu_vb_is_codecconfig(vbuf)) { + if (disp_imm || low_latency) { ret = vpu_malone_add_scode(inst->core->iface, inst->id, &inst->stream_buffer, @@ -1643,7 +1637,6 @@ int vpu_malone_input_frame(struct vpu_shared_addr *shared, struct vpu_inst *inst, struct vb2_buffer *vb) { struct vpu_dec_ctrl *hc = shared->priv; - struct vb2_v4l2_buffer *vbuf; struct vpu_malone_str_buffer __iomem *str_buf = hc->str_buf[inst->id]; u32 disp_imm = hc->codec_param[inst->id].disp_imm; u32 size; @@ -1657,16 +1650,6 @@ int vpu_malone_input_frame(struct vpu_shared_addr *shared, return ret; size = ret; - /* - * if buffer only contain codec data, and the timestamp is invalid, - * don't put the invalid timestamp to resync - * merge the data to next frame - */ - vbuf = to_vb2_v4l2_buffer(vb); - if (vpu_vb_is_codecconfig(vbuf)) { - inst->extra_size += size; - return 0; - } if (inst->extra_size) { size += inst->extra_size; inst->extra_size = 0; diff --git a/drivers/media/platform/amphion/vpu_v4l2.c b/drivers/media/platform/amphion/vpu_v4l2.c index fcb2eff813ac45..511881a131b708 100644 --- a/drivers/media/platform/amphion/vpu_v4l2.c +++ b/drivers/media/platform/amphion/vpu_v4l2.c @@ -349,16 +349,6 @@ struct vb2_v4l2_buffer *vpu_next_src_buf(struct vpu_inst *inst) if (!src_buf || vpu_get_buffer_state(src_buf) == VPU_BUF_STATE_IDLE) return NULL; - while (vpu_vb_is_codecconfig(src_buf)) { - v4l2_m2m_src_buf_remove(inst->fh.m2m_ctx); - vpu_set_buffer_state(src_buf, VPU_BUF_STATE_IDLE); - v4l2_m2m_buf_done(src_buf, VB2_BUF_STATE_DONE); - - src_buf = v4l2_m2m_next_src_buf(inst->fh.m2m_ctx); - if (!src_buf || vpu_get_buffer_state(src_buf) == VPU_BUF_STATE_IDLE) - return NULL; - } - return src_buf; } diff --git a/drivers/media/platform/amphion/vpu_v4l2.h b/drivers/media/platform/amphion/vpu_v4l2.h index 4a87b06ae52030..da9945f25e327f 100644 --- a/drivers/media/platform/amphion/vpu_v4l2.h +++ b/drivers/media/platform/amphion/vpu_v4l2.h @@ -39,14 +39,4 @@ static inline struct vpu_format *vpu_get_format(struct vpu_inst *inst, u32 type) else return &inst->cap_format; } - -static inline int vpu_vb_is_codecconfig(struct vb2_v4l2_buffer *vbuf) -{ -#ifdef V4L2_BUF_FLAG_CODECCONFIG - return (vbuf->flags & V4L2_BUF_FLAG_CODECCONFIG) ? 1 : 0; -#else - return 0; -#endif -} - #endif From 4d08b38a9828481cb0d33686fdce6f9d90a0350f Mon Sep 17 00:00:00 2001 From: Ming Qian Date: Tue, 16 Sep 2025 14:10:07 +0800 Subject: [PATCH 1944/3902] media: amphion: Cancel message work before releasing the VPU core commit ae246b0032146e352c4c06a7bf03cd3d5bcb2ecd upstream. To avoid accessing the VPU register after release of the VPU core, cancel the message work and destroy the workqueue that handles the VPU message before release of the VPU core. Fixes: 3cd084519c6f ("media: amphion: add vpu v4l2 m2m support") Cc: stable@vger.kernel.org Signed-off-by: Ming Qian Signed-off-by: Nicolas Dufresne Signed-off-by: Hans Verkuil Signed-off-by: Greg Kroah-Hartman --- drivers/media/platform/amphion/vpu_v4l2.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/drivers/media/platform/amphion/vpu_v4l2.c b/drivers/media/platform/amphion/vpu_v4l2.c index 511881a131b708..47dff9a35bb46d 100644 --- a/drivers/media/platform/amphion/vpu_v4l2.c +++ b/drivers/media/platform/amphion/vpu_v4l2.c @@ -703,15 +703,15 @@ static int vpu_v4l2_release(struct vpu_inst *inst) { vpu_trace(inst->vpu->dev, "%p\n", inst); - vpu_release_core(inst->core); - put_device(inst->dev); - if (inst->workqueue) { cancel_work_sync(&inst->msg_work); destroy_workqueue(inst->workqueue); inst->workqueue = NULL; } + vpu_release_core(inst->core); + put_device(inst->dev); + v4l2_ctrl_handler_free(&inst->ctrl_handler); mutex_destroy(&inst->lock); From 4d3c49fea27956dd82aadcb20949f1cf9222e8c1 Mon Sep 17 00:00:00 2001 From: Duoming Zhou Date: Tue, 2 Sep 2025 09:53:37 +0800 Subject: [PATCH 1945/3902] media: i2c: ADV7604: Remove redundant cancel_delayed_work in probe commit 8f34f24355a607b98ecd9924837aab13c676eeca upstream. The delayed_work delayed_work_enable_hotplug is initialized with INIT_DELAYED_WORK() in adv76xx_probe(), but it is never scheduled anywhere in the probe function. Calling cancel_delayed_work() on a work that has never been scheduled is redundant and unnecessary, as there is no pending work to cancel. Remove the redundant cancel_delayed_work() from error handling path and adjust the goto label accordingly to simplify the code and avoid potential confusion. Fixes: 54450f591c99 ("[media] adv7604: driver for the Analog Devices ADV7604 video decoder") Cc: stable@vger.kernel.org Signed-off-by: Duoming Zhou Signed-off-by: Hans Verkuil Signed-off-by: Greg Kroah-Hartman --- drivers/media/i2c/adv7604.c | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/drivers/media/i2c/adv7604.c b/drivers/media/i2c/adv7604.c index 8fe7c2f728839c..516553fb17e983 100644 --- a/drivers/media/i2c/adv7604.c +++ b/drivers/media/i2c/adv7604.c @@ -3670,7 +3670,7 @@ static int adv76xx_probe(struct i2c_client *client) err = media_entity_pads_init(&sd->entity, state->source_pad + 1, state->pads); if (err) - goto err_work_queues; + goto err_i2c; /* Configure regmaps */ err = configure_regmaps(state); @@ -3711,8 +3711,6 @@ static int adv76xx_probe(struct i2c_client *client) err_entity: media_entity_cleanup(&sd->entity); -err_work_queues: - cancel_delayed_work(&state->delayed_work_enable_hotplug); err_i2c: adv76xx_unregister_clients(state); err_hdl: From 2e9b9a658d42cd13dec9e900c261e7ead0d589db Mon Sep 17 00:00:00 2001 From: Duoming Zhou Date: Tue, 2 Sep 2025 09:10:31 +0800 Subject: [PATCH 1946/3902] media: i2c: adv7842: Remove redundant cancel_delayed_work in probe commit e66a5cc606c58e72f18f9cdd868a3672e918f9f8 upstream. The delayed_work delayed_work_enable_hotplug is initialized with INIT_DELAYED_WORK() in adv7842_probe(), but it is never scheduled anywhere in the probe function. Calling cancel_delayed_work() on a work that has never been scheduled is redundant and unnecessary, as there is no pending work to cancel. Remove the redundant cancel_delayed_work() from error handling path and adjust the goto label accordingly to simplify the code and avoid potential confusion. Fixes: a89bcd4c6c20 ("[media] adv7842: add new video decoder driver") Cc: stable@vger.kernel.org Signed-off-by: Duoming Zhou Signed-off-by: Hans Verkuil Signed-off-by: Greg Kroah-Hartman --- drivers/media/i2c/adv7842.c | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/drivers/media/i2c/adv7842.c b/drivers/media/i2c/adv7842.c index 111186529ed403..21c3d3682e0b45 100644 --- a/drivers/media/i2c/adv7842.c +++ b/drivers/media/i2c/adv7842.c @@ -3629,7 +3629,7 @@ static int adv7842_probe(struct i2c_client *client) err = media_entity_pads_init(&sd->entity, ADV7842_PAD_SOURCE + 1, state->pads); if (err) - goto err_work_queues; + goto err_i2c; err = adv7842_core_init(sd); if (err) @@ -3650,8 +3650,6 @@ static int adv7842_probe(struct i2c_client *client) err_entity: media_entity_cleanup(&sd->entity); -err_work_queues: - cancel_delayed_work(&state->delayed_work_enable_hotplug); err_i2c: adv7842_unregister_clients(sd); err_hdl: From 378deae752a229b018ab546b551897d4ab705c2d Mon Sep 17 00:00:00 2001 From: Dave Stevenson Date: Fri, 17 Oct 2025 13:43:49 +0530 Subject: [PATCH 1947/3902] media: i2c: imx219: Fix 1920x1080 mode to use 1:1 pixel aspect ratio commit 9ef6e4db152c34580cc52792f32485c193945395 upstream. Commit 0af46fbc333d ("media: i2c: imx219: Calculate crop rectangle dynamically") meant that the 1920x1080 mode switched from using no binning to using vertical binning but no horizontal binning, which resulted in stretched pixels. Until proper controls are available to independently select horizontal and vertical binning, restore the original 1:1 pixel aspect ratio by forcing binning to be uniform in both directions. Cc: stable@vger.kernel.org Fixes: 0af46fbc333d ("media: i2c: imx219: Calculate crop rectangle dynamically") Signed-off-by: Dave Stevenson [Add comment & reword commit message] Signed-off-by: Jai Luthra Reviewed-by: Jacopo Mondi Signed-off-by: Sakari Ailus Signed-off-by: Hans Verkuil Signed-off-by: Greg Kroah-Hartman --- drivers/media/i2c/imx219.c | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/drivers/media/i2c/imx219.c b/drivers/media/i2c/imx219.c index c680aa6c3a55a9..300935b1ef2497 100644 --- a/drivers/media/i2c/imx219.c +++ b/drivers/media/i2c/imx219.c @@ -856,7 +856,7 @@ static int imx219_set_pad_format(struct v4l2_subdev *sd, const struct imx219_mode *mode; struct v4l2_mbus_framefmt *format; struct v4l2_rect *crop; - u8 bin_h, bin_v; + u8 bin_h, bin_v, binning; u32 prev_line_len; format = v4l2_subdev_state_get_format(state, 0); @@ -877,9 +877,12 @@ static int imx219_set_pad_format(struct v4l2_subdev *sd, bin_h = min(IMX219_PIXEL_ARRAY_WIDTH / format->width, 2U); bin_v = min(IMX219_PIXEL_ARRAY_HEIGHT / format->height, 2U); + /* Ensure bin_h and bin_v are same to avoid 1:2 or 2:1 stretching */ + binning = min(bin_h, bin_v); + crop = v4l2_subdev_state_get_crop(state, 0); - crop->width = format->width * bin_h; - crop->height = format->height * bin_v; + crop->width = format->width * binning; + crop->height = format->height * binning; crop->left = (IMX219_NATIVE_WIDTH - crop->width) / 2; crop->top = (IMX219_NATIVE_HEIGHT - crop->height) / 2; From 3e858938b0e659f6ec9ddcf853a87f1c5c3f44e1 Mon Sep 17 00:00:00 2001 From: Chen-Yu Tsai Date: Wed, 20 Aug 2025 15:54:05 +0800 Subject: [PATCH 1948/3902] media: mediatek: vcodec: Use spinlock for context list protection lock commit a5844227e0f030d2af2d85d4aed10c5eca6ca176 upstream. Previously a mutex was added to protect the encoder and decoder context lists from unexpected changes originating from the SCP IP block, causing the context pointer to go invalid, resulting in a NULL pointer dereference in the IPI handler. Turns out on the MT8173, the VPU IPI handler is called from hard IRQ context. This causes a big warning from the scheduler. This was first reported downstream on the ChromeOS kernels, but is also reproducible on mainline using Fluster with the FFmpeg v4l2m2m decoders. Even though the actual capture format is not supported, the affected code paths are triggered. Since this lock just protects the context list and operations on it are very fast, it should be OK to switch to a spinlock. Fixes: 6467cda18c9f ("media: mediatek: vcodec: adding lock to protect decoder context list") Fixes: afaaf3a0f647 ("media: mediatek: vcodec: adding lock to protect encoder context list") Cc: Yunfei Dong Cc: stable@vger.kernel.org Signed-off-by: Chen-Yu Tsai Reviewed-by: Fei Shao Reviewed-by: Tomasz Figa Signed-off-by: Nicolas Dufresne Signed-off-by: Hans Verkuil Signed-off-by: Greg Kroah-Hartman --- .../mediatek/vcodec/common/mtk_vcodec_fw_vpu.c | 10 ++++++---- .../mediatek/vcodec/decoder/mtk_vcodec_dec_drv.c | 12 +++++++----- .../mediatek/vcodec/decoder/mtk_vcodec_dec_drv.h | 2 +- .../platform/mediatek/vcodec/decoder/vdec_vpu_if.c | 5 +++-- .../mediatek/vcodec/encoder/mtk_vcodec_enc_drv.c | 12 +++++++----- .../mediatek/vcodec/encoder/mtk_vcodec_enc_drv.h | 2 +- .../platform/mediatek/vcodec/encoder/venc_vpu_if.c | 5 +++-- 7 files changed, 28 insertions(+), 20 deletions(-) diff --git a/drivers/media/platform/mediatek/vcodec/common/mtk_vcodec_fw_vpu.c b/drivers/media/platform/mediatek/vcodec/common/mtk_vcodec_fw_vpu.c index d7027d600208fc..223fb22948944c 100644 --- a/drivers/media/platform/mediatek/vcodec/common/mtk_vcodec_fw_vpu.c +++ b/drivers/media/platform/mediatek/vcodec/common/mtk_vcodec_fw_vpu.c @@ -47,30 +47,32 @@ static void mtk_vcodec_vpu_reset_dec_handler(void *priv) { struct mtk_vcodec_dec_dev *dev = priv; struct mtk_vcodec_dec_ctx *ctx; + unsigned long flags; dev_err(&dev->plat_dev->dev, "Watchdog timeout!!"); - mutex_lock(&dev->dev_ctx_lock); + spin_lock_irqsave(&dev->dev_ctx_lock, flags); list_for_each_entry(ctx, &dev->ctx_list, list) { ctx->state = MTK_STATE_ABORT; mtk_v4l2_vdec_dbg(0, ctx, "[%d] Change to state MTK_STATE_ABORT", ctx->id); } - mutex_unlock(&dev->dev_ctx_lock); + spin_unlock_irqrestore(&dev->dev_ctx_lock, flags); } static void mtk_vcodec_vpu_reset_enc_handler(void *priv) { struct mtk_vcodec_enc_dev *dev = priv; struct mtk_vcodec_enc_ctx *ctx; + unsigned long flags; dev_err(&dev->plat_dev->dev, "Watchdog timeout!!"); - mutex_lock(&dev->dev_ctx_lock); + spin_lock_irqsave(&dev->dev_ctx_lock, flags); list_for_each_entry(ctx, &dev->ctx_list, list) { ctx->state = MTK_STATE_ABORT; mtk_v4l2_vdec_dbg(0, ctx, "[%d] Change to state MTK_STATE_ABORT", ctx->id); } - mutex_unlock(&dev->dev_ctx_lock); + spin_unlock_irqrestore(&dev->dev_ctx_lock, flags); } static const struct mtk_vcodec_fw_ops mtk_vcodec_vpu_msg = { diff --git a/drivers/media/platform/mediatek/vcodec/decoder/mtk_vcodec_dec_drv.c b/drivers/media/platform/mediatek/vcodec/decoder/mtk_vcodec_dec_drv.c index 46d176e6de63e3..3b81fae9f9135c 100644 --- a/drivers/media/platform/mediatek/vcodec/decoder/mtk_vcodec_dec_drv.c +++ b/drivers/media/platform/mediatek/vcodec/decoder/mtk_vcodec_dec_drv.c @@ -198,6 +198,7 @@ static int fops_vcodec_open(struct file *file) struct mtk_vcodec_dec_ctx *ctx = NULL; int ret = 0, i, hw_count; struct vb2_queue *src_vq; + unsigned long flags; ctx = kzalloc(sizeof(*ctx), GFP_KERNEL); if (!ctx) @@ -267,9 +268,9 @@ static int fops_vcodec_open(struct file *file) ctx->dev->vdec_pdata->init_vdec_params(ctx); - mutex_lock(&dev->dev_ctx_lock); + spin_lock_irqsave(&dev->dev_ctx_lock, flags); list_add(&ctx->list, &dev->ctx_list); - mutex_unlock(&dev->dev_ctx_lock); + spin_unlock_irqrestore(&dev->dev_ctx_lock, flags); mtk_vcodec_dbgfs_create(ctx); mutex_unlock(&dev->dev_mutex); @@ -294,6 +295,7 @@ static int fops_vcodec_release(struct file *file) { struct mtk_vcodec_dec_dev *dev = video_drvdata(file); struct mtk_vcodec_dec_ctx *ctx = file_to_dec_ctx(file); + unsigned long flags; mtk_v4l2_vdec_dbg(0, ctx, "[%d] decoder", ctx->id); mutex_lock(&dev->dev_mutex); @@ -312,9 +314,9 @@ static int fops_vcodec_release(struct file *file) v4l2_ctrl_handler_free(&ctx->ctrl_hdl); mtk_vcodec_dbgfs_remove(dev, ctx->id); - mutex_lock(&dev->dev_ctx_lock); + spin_lock_irqsave(&dev->dev_ctx_lock, flags); list_del_init(&ctx->list); - mutex_unlock(&dev->dev_ctx_lock); + spin_unlock_irqrestore(&dev->dev_ctx_lock, flags); kfree(ctx); mutex_unlock(&dev->dev_mutex); return 0; @@ -407,7 +409,7 @@ static int mtk_vcodec_probe(struct platform_device *pdev) for (i = 0; i < MTK_VDEC_HW_MAX; i++) mutex_init(&dev->dec_mutex[i]); mutex_init(&dev->dev_mutex); - mutex_init(&dev->dev_ctx_lock); + spin_lock_init(&dev->dev_ctx_lock); spin_lock_init(&dev->irqlock); snprintf(dev->v4l2_dev.name, sizeof(dev->v4l2_dev.name), "%s", diff --git a/drivers/media/platform/mediatek/vcodec/decoder/mtk_vcodec_dec_drv.h b/drivers/media/platform/mediatek/vcodec/decoder/mtk_vcodec_dec_drv.h index d047d7c580fb69..9d68808e8f9c3c 100644 --- a/drivers/media/platform/mediatek/vcodec/decoder/mtk_vcodec_dec_drv.h +++ b/drivers/media/platform/mediatek/vcodec/decoder/mtk_vcodec_dec_drv.h @@ -285,7 +285,7 @@ struct mtk_vcodec_dec_dev { /* decoder hardware mutex lock */ struct mutex dec_mutex[MTK_VDEC_HW_MAX]; struct mutex dev_mutex; - struct mutex dev_ctx_lock; + spinlock_t dev_ctx_lock; struct workqueue_struct *decode_workqueue; spinlock_t irqlock; diff --git a/drivers/media/platform/mediatek/vcodec/decoder/vdec_vpu_if.c b/drivers/media/platform/mediatek/vcodec/decoder/vdec_vpu_if.c index 145958206e38a4..40b97f114cf6f0 100644 --- a/drivers/media/platform/mediatek/vcodec/decoder/vdec_vpu_if.c +++ b/drivers/media/platform/mediatek/vcodec/decoder/vdec_vpu_if.c @@ -75,16 +75,17 @@ static void handle_get_param_msg_ack(const struct vdec_vpu_ipi_get_param_ack *ms static bool vpu_dec_check_ap_inst(struct mtk_vcodec_dec_dev *dec_dev, struct vdec_vpu_inst *vpu) { struct mtk_vcodec_dec_ctx *ctx; + unsigned long flags; int ret = false; - mutex_lock(&dec_dev->dev_ctx_lock); + spin_lock_irqsave(&dec_dev->dev_ctx_lock, flags); list_for_each_entry(ctx, &dec_dev->ctx_list, list) { if (!IS_ERR_OR_NULL(ctx) && ctx->vpu_inst == vpu) { ret = true; break; } } - mutex_unlock(&dec_dev->dev_ctx_lock); + spin_unlock_irqrestore(&dec_dev->dev_ctx_lock, flags); return ret; } diff --git a/drivers/media/platform/mediatek/vcodec/encoder/mtk_vcodec_enc_drv.c b/drivers/media/platform/mediatek/vcodec/encoder/mtk_vcodec_enc_drv.c index fb1c3bdc2daeb4..82b8ff38e8f1a0 100644 --- a/drivers/media/platform/mediatek/vcodec/encoder/mtk_vcodec_enc_drv.c +++ b/drivers/media/platform/mediatek/vcodec/encoder/mtk_vcodec_enc_drv.c @@ -117,6 +117,7 @@ static int fops_vcodec_open(struct file *file) struct mtk_vcodec_enc_ctx *ctx = NULL; int ret = 0; struct vb2_queue *src_vq; + unsigned long flags; ctx = kzalloc(sizeof(*ctx), GFP_KERNEL); if (!ctx) @@ -176,9 +177,9 @@ static int fops_vcodec_open(struct file *file) mtk_v4l2_venc_dbg(2, ctx, "Create instance [%d]@%p m2m_ctx=%p ", ctx->id, ctx, ctx->m2m_ctx); - mutex_lock(&dev->dev_ctx_lock); + spin_lock_irqsave(&dev->dev_ctx_lock, flags); list_add(&ctx->list, &dev->ctx_list); - mutex_unlock(&dev->dev_ctx_lock); + spin_unlock_irqrestore(&dev->dev_ctx_lock, flags); mutex_unlock(&dev->dev_mutex); mtk_v4l2_venc_dbg(0, ctx, "%s encoder [%d]", dev_name(&dev->plat_dev->dev), @@ -203,6 +204,7 @@ static int fops_vcodec_release(struct file *file) { struct mtk_vcodec_enc_dev *dev = video_drvdata(file); struct mtk_vcodec_enc_ctx *ctx = file_to_enc_ctx(file); + unsigned long flags; mtk_v4l2_venc_dbg(1, ctx, "[%d] encoder", ctx->id); mutex_lock(&dev->dev_mutex); @@ -213,9 +215,9 @@ static int fops_vcodec_release(struct file *file) v4l2_fh_exit(&ctx->fh); v4l2_ctrl_handler_free(&ctx->ctrl_hdl); - mutex_lock(&dev->dev_ctx_lock); + spin_lock_irqsave(&dev->dev_ctx_lock, flags); list_del_init(&ctx->list); - mutex_unlock(&dev->dev_ctx_lock); + spin_unlock_irqrestore(&dev->dev_ctx_lock, flags); kfree(ctx); mutex_unlock(&dev->dev_mutex); return 0; @@ -297,7 +299,7 @@ static int mtk_vcodec_probe(struct platform_device *pdev) mutex_init(&dev->enc_mutex); mutex_init(&dev->dev_mutex); - mutex_init(&dev->dev_ctx_lock); + spin_lock_init(&dev->dev_ctx_lock); spin_lock_init(&dev->irqlock); snprintf(dev->v4l2_dev.name, sizeof(dev->v4l2_dev.name), "%s", diff --git a/drivers/media/platform/mediatek/vcodec/encoder/mtk_vcodec_enc_drv.h b/drivers/media/platform/mediatek/vcodec/encoder/mtk_vcodec_enc_drv.h index 5b304a5512366e..0cddfa13594f55 100644 --- a/drivers/media/platform/mediatek/vcodec/encoder/mtk_vcodec_enc_drv.h +++ b/drivers/media/platform/mediatek/vcodec/encoder/mtk_vcodec_enc_drv.h @@ -206,7 +206,7 @@ struct mtk_vcodec_enc_dev { /* encoder hardware mutex lock */ struct mutex enc_mutex; struct mutex dev_mutex; - struct mutex dev_ctx_lock; + spinlock_t dev_ctx_lock; struct workqueue_struct *encode_workqueue; int enc_irq; diff --git a/drivers/media/platform/mediatek/vcodec/encoder/venc_vpu_if.c b/drivers/media/platform/mediatek/vcodec/encoder/venc_vpu_if.c index 51bb7ee141b9e5..3c229b1f6b21f4 100644 --- a/drivers/media/platform/mediatek/vcodec/encoder/venc_vpu_if.c +++ b/drivers/media/platform/mediatek/vcodec/encoder/venc_vpu_if.c @@ -45,16 +45,17 @@ static void handle_enc_encode_msg(struct venc_vpu_inst *vpu, const void *data) static bool vpu_enc_check_ap_inst(struct mtk_vcodec_enc_dev *enc_dev, struct venc_vpu_inst *vpu) { struct mtk_vcodec_enc_ctx *ctx; + unsigned long flags; int ret = false; - mutex_lock(&enc_dev->dev_ctx_lock); + spin_lock_irqsave(&enc_dev->dev_ctx_lock, flags); list_for_each_entry(ctx, &enc_dev->ctx_list, list) { if (!IS_ERR_OR_NULL(ctx) && ctx->vpu_inst == vpu) { ret = true; break; } } - mutex_unlock(&enc_dev->dev_ctx_lock); + spin_unlock_irqrestore(&enc_dev->dev_ctx_lock, flags); return ret; } From f736a3f4787b0a74df7f410e87f33cc550d85890 Mon Sep 17 00:00:00 2001 From: Haoxiang Li Date: Mon, 15 Sep 2025 20:09:38 +0800 Subject: [PATCH 1949/3902] media: mediatek: vcodec: Fix a reference leak in mtk_vcodec_fw_vpu_init() commit cdd0f118ef87db8a664fb5ea366fd1766d2df1cd upstream. vpu_get_plat_device() increases the reference count of the returned platform device. However, when devm_kzalloc() fails, the reference is not released, causing a reference leak. Fix this by calling put_device() on fw_pdev->dev before returning on the error path. Fixes: e25a89f743b1 ("media: mtk-vcodec: potential dereference of null pointer") Cc: stable@vger.kernel.org Signed-off-by: Haoxiang Li Reviewed-by: AngeloGioacchino Del Regno Reviewed-by: Tzung-Bi Shih Signed-off-by: Nicolas Dufresne Signed-off-by: Hans Verkuil Signed-off-by: Greg Kroah-Hartman --- .../media/platform/mediatek/vcodec/common/mtk_vcodec_fw_vpu.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/drivers/media/platform/mediatek/vcodec/common/mtk_vcodec_fw_vpu.c b/drivers/media/platform/mediatek/vcodec/common/mtk_vcodec_fw_vpu.c index 223fb22948944c..3632037f78f505 100644 --- a/drivers/media/platform/mediatek/vcodec/common/mtk_vcodec_fw_vpu.c +++ b/drivers/media/platform/mediatek/vcodec/common/mtk_vcodec_fw_vpu.c @@ -119,8 +119,10 @@ struct mtk_vcodec_fw *mtk_vcodec_fw_vpu_init(void *priv, enum mtk_vcodec_fw_use vpu_wdt_reg_handler(fw_pdev, mtk_vcodec_vpu_reset_enc_handler, priv, rst_id); fw = devm_kzalloc(&plat_dev->dev, sizeof(*fw), GFP_KERNEL); - if (!fw) + if (!fw) { + put_device(&fw_pdev->dev); return ERR_PTR(-ENOMEM); + } fw->type = VPU; fw->ops = &mtk_vcodec_vpu_msg; fw->pdev = fw_pdev; From eea343099facd13d27df4629198e9af98d126afe Mon Sep 17 00:00:00 2001 From: Huacai Chen Date: Sat, 6 Dec 2025 10:39:49 +0800 Subject: [PATCH 1950/3902] LoongArch: Add new PCI ID for pci_fixup_vgadev() commit bf3fa8f232a1eec8d7b88dcd9e925e60f04f018d upstream. Loongson-2K3000 has a new PCI ID (0x7a46) for its display controller, Add it for pci_fixup_vgadev() since we prefer a discrete graphics card as default boot device if present. Cc: stable@vger.kernel.org Signed-off-by: Tianrui Zhao Signed-off-by: Huacai Chen Signed-off-by: Greg Kroah-Hartman --- arch/loongarch/pci/pci.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/arch/loongarch/pci/pci.c b/arch/loongarch/pci/pci.c index d9fc5d520b3778..d923295ab8c665 100644 --- a/arch/loongarch/pci/pci.c +++ b/arch/loongarch/pci/pci.c @@ -14,6 +14,7 @@ #define PCI_DEVICE_ID_LOONGSON_HOST 0x7a00 #define PCI_DEVICE_ID_LOONGSON_DC1 0x7a06 #define PCI_DEVICE_ID_LOONGSON_DC2 0x7a36 +#define PCI_DEVICE_ID_LOONGSON_DC3 0x7a46 int raw_pci_read(unsigned int domain, unsigned int bus, unsigned int devfn, int reg, int len, u32 *val) @@ -97,3 +98,4 @@ static void pci_fixup_vgadev(struct pci_dev *pdev) } DECLARE_PCI_FIXUP_FINAL(PCI_VENDOR_ID_LOONGSON, PCI_DEVICE_ID_LOONGSON_DC1, pci_fixup_vgadev); DECLARE_PCI_FIXUP_FINAL(PCI_VENDOR_ID_LOONGSON, PCI_DEVICE_ID_LOONGSON_DC2, pci_fixup_vgadev); +DECLARE_PCI_FIXUP_FINAL(PCI_VENDOR_ID_LOONGSON, PCI_DEVICE_ID_LOONGSON_DC3, pci_fixup_vgadev); From a6021c133ed8dad7db7eabd6c507264d9aea4375 Mon Sep 17 00:00:00 2001 From: Qiang Ma Date: Sat, 6 Dec 2025 10:39:49 +0800 Subject: [PATCH 1951/3902] LoongArch: Correct the calculation logic of thread_count commit 1de0ae21f136efa6c5d8a4d3e07b7d1ca39c750f upstream. For thread_count, the current calculation method has a maximum of 255, which may not be sufficient in the future. Therefore, we are correcting it now. Reference: SMBIOS Specification, 7.5 Processor Information (Type 4)[1] [1]: https://www.dmtf.org/sites/default/files/standards/documents/DSP0134_3.9.0.pdf Cc: stable@vger.kernel.org Signed-off-by: Qiang Ma Signed-off-by: Huacai Chen Signed-off-by: Greg Kroah-Hartman --- arch/loongarch/kernel/setup.c | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/arch/loongarch/kernel/setup.c b/arch/loongarch/kernel/setup.c index 25a87378e48e43..20cb6f30645683 100644 --- a/arch/loongarch/kernel/setup.c +++ b/arch/loongarch/kernel/setup.c @@ -56,6 +56,7 @@ #define SMBIOS_FREQLOW_MASK 0xFF #define SMBIOS_CORE_PACKAGE_OFFSET 0x23 #define SMBIOS_THREAD_PACKAGE_OFFSET 0x25 +#define SMBIOS_THREAD_PACKAGE_2_OFFSET 0x2E #define LOONGSON_EFI_ENABLE (1 << 3) unsigned long fw_arg0, fw_arg1, fw_arg2; @@ -126,7 +127,12 @@ static void __init parse_cpu_table(const struct dmi_header *dm) cpu_clock_freq = freq_temp * 1000000; loongson_sysconf.cpuname = (void *)dmi_string_parse(dm, dmi_data[16]); - loongson_sysconf.cores_per_package = *(dmi_data + SMBIOS_THREAD_PACKAGE_OFFSET); + loongson_sysconf.cores_per_package = *(u8 *)(dmi_data + SMBIOS_THREAD_PACKAGE_OFFSET); + if (dm->length >= 0x30 && loongson_sysconf.cores_per_package == 0xff) { + /* SMBIOS 3.0+ has ThreadCount2 for more than 255 threads */ + loongson_sysconf.cores_per_package = + *(u16 *)(dmi_data + SMBIOS_THREAD_PACKAGE_2_OFFSET); + } pr_info("CpuClock = %llu\n", cpu_clock_freq); } From 1f8330dcdc6084b10768f159cbb93269b392cc71 Mon Sep 17 00:00:00 2001 From: Huacai Chen Date: Sat, 6 Dec 2025 10:39:48 +0800 Subject: [PATCH 1952/3902] LoongArch: Fix arch_dup_task_struct() for CONFIG_RANDSTRUCT commit a91b446e359aa96cc2655318789fd37441337415 upstream. Now the optimized version of arch_dup_task_struct() for LoongArch assumes 'thread' is the last member of 'task_struct'. But this is not true if CONFIG_RANDSTRUCT is enabled after Linux-6.16. So fix the arch_dup_task_struct() function for CONFIG_RANDSTRUCT by copying the whole 'task_struct'. Cc: stable@vger.kernel.org # 6.16+ Signed-off-by: Huacai Chen Signed-off-by: Greg Kroah-Hartman --- arch/loongarch/kernel/process.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/arch/loongarch/kernel/process.c b/arch/loongarch/kernel/process.c index efd9edf65603cc..d1e04f9e0f7951 100644 --- a/arch/loongarch/kernel/process.c +++ b/arch/loongarch/kernel/process.c @@ -130,6 +130,11 @@ int arch_dup_task_struct(struct task_struct *dst, struct task_struct *src) preempt_enable(); + if (IS_ENABLED(CONFIG_RANDSTRUCT)) { + memcpy(dst, src, sizeof(struct task_struct)); + return 0; + } + if (!used_math()) memcpy(dst, src, offsetof(struct task_struct, thread.fpu.fpr)); else From 73a0059dcf26eeb07bebbcd1d95bde84fdaae7cd Mon Sep 17 00:00:00 2001 From: Huacai Chen Date: Sat, 6 Dec 2025 10:39:40 +0800 Subject: [PATCH 1953/3902] LoongArch: Fix build errors for CONFIG_RANDSTRUCT commit 3c250aecef62da81deb38ac6738ac0a88d91f1fc upstream. When CONFIG_RANDSTRUCT enabled, members of task_struct are randomized. There is a chance that TASK_STACK_CANARY be out of 12bit immediate's range and causes build errors. TASK_STACK_CANARY is naturally aligned, so fix it by replacing ld.d/st.d with ldptr.d/stptr.d which have 14bit immediates. Cc: stable@vger.kernel.org Reported-by: kernel test robot Closes: https://lore.kernel.org/oe-kbuild-all/202511240656.0NaPcJs1-lkp@intel.com/ Suggested-by: Rui Wang Signed-off-by: Huacai Chen Signed-off-by: Greg Kroah-Hartman --- arch/loongarch/kernel/switch.S | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/arch/loongarch/kernel/switch.S b/arch/loongarch/kernel/switch.S index 9c23cb7e432f1f..3007e909e0d800 100644 --- a/arch/loongarch/kernel/switch.S +++ b/arch/loongarch/kernel/switch.S @@ -25,8 +25,8 @@ SYM_FUNC_START(__switch_to) stptr.d a4, a0, THREAD_SCHED_CFA #if defined(CONFIG_STACKPROTECTOR) && !defined(CONFIG_SMP) la t7, __stack_chk_guard - LONG_L t8, a1, TASK_STACK_CANARY - LONG_S t8, t7, 0 + ldptr.d t8, a1, TASK_STACK_CANARY + stptr.d t8, t7, 0 #endif move tp, a2 cpu_restore_nonscratch a1 From f92ac4ad6c8634d82f79a76fa995d7b1b7b35584 Mon Sep 17 00:00:00 2001 From: WangYuli Date: Sat, 6 Dec 2025 10:39:48 +0800 Subject: [PATCH 1954/3902] LoongArch: Use __pmd()/__pte() for swap entry conversions MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit commit 4a71df151e703b5e7e85b33369cee59ef2665e61 upstream. The __pmd() and __pte() helper macros provide the correct initialization syntax and abstraction for the pmd_t and pte_t types. Use __pmd() to fix follow warning about __swp_entry_to_pmd() with gcc-15 under specific configs [1] : In file included from ./include/linux/pgtable.h:6, from ./include/linux/mm.h:31, from ./include/linux/pagemap.h:8, from arch/loongarch/mm/init.c:14: ./include/linux/swapops.h: In function ‘swp_entry_to_pmd’: ./arch/loongarch/include/asm/pgtable.h:302:34: error: missing braces around initializer [-Werror=missing-braces] 302 | #define __swp_entry_to_pmd(x) ((pmd_t) { (x).val | _PAGE_HUGE }) | ^ ./include/linux/swapops.h:559:16: note: in expansion of macro ‘__swp_entry_to_pmd’ 559 | return __swp_entry_to_pmd(arch_entry); | ^~~~~~~~~~~~~~~~~~ cc1: all warnings being treated as errors Also update __swp_entry_to_pte() to use __pte() for consistency. [1]. https://download.01.org/0day-ci/archive/20251119/202511190316.luI90kAo-lkp@intel.com/config Cc: stable@vger.kernel.org Signed-off-by: Yuli Wang Signed-off-by: Huacai Chen Signed-off-by: Greg Kroah-Hartman --- arch/loongarch/include/asm/pgtable.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/arch/loongarch/include/asm/pgtable.h b/arch/loongarch/include/asm/pgtable.h index 03fb60432fde79..9a7029285fd510 100644 --- a/arch/loongarch/include/asm/pgtable.h +++ b/arch/loongarch/include/asm/pgtable.h @@ -297,9 +297,9 @@ static inline pte_t mk_swap_pte(unsigned long type, unsigned long offset) #define __swp_offset(x) ((x).val >> 24) #define __swp_entry(type, offset) ((swp_entry_t) { pte_val(mk_swap_pte((type), (offset))) }) #define __pte_to_swp_entry(pte) ((swp_entry_t) { pte_val(pte) }) -#define __swp_entry_to_pte(x) ((pte_t) { (x).val }) +#define __swp_entry_to_pte(x) __pte((x).val) #define __pmd_to_swp_entry(pmd) ((swp_entry_t) { pmd_val(pmd) }) -#define __swp_entry_to_pmd(x) ((pmd_t) { (x).val | _PAGE_HUGE }) +#define __swp_entry_to_pmd(x) __pmd((x).val | _PAGE_HUGE) static inline bool pte_swp_exclusive(pte_t pte) { From ef17c7e2acda23ba6e60eaac197305cc6a6511a6 Mon Sep 17 00:00:00 2001 From: Tiezhu Yang Date: Sat, 6 Dec 2025 10:39:48 +0800 Subject: [PATCH 1955/3902] LoongArch: Use unsigned long for _end and _text commit a258a3cb1895e3acf5f2fe245d17426e894bc935 upstream. It is better to use unsigned long rather than long for _end and _text to calculate the kernel length. Cc: stable@vger.kernel.org # v6.3+ Fixes: e5f02b51fa0c ("LoongArch: Add support for kernel address space layout randomization (KASLR)") Signed-off-by: Tiezhu Yang Signed-off-by: Huacai Chen Signed-off-by: Greg Kroah-Hartman --- arch/loongarch/kernel/relocate.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/arch/loongarch/kernel/relocate.c b/arch/loongarch/kernel/relocate.c index b5e2312a2fca51..76abbb8d293160 100644 --- a/arch/loongarch/kernel/relocate.c +++ b/arch/loongarch/kernel/relocate.c @@ -183,7 +183,7 @@ static inline void __init *determine_relocation_address(void) if (kaslr_disabled()) return destination; - kernel_length = (long)_end - (long)_text; + kernel_length = (unsigned long)_end - (unsigned long)_text; random_offset = get_random_boot() << 16; random_offset &= (CONFIG_RANDOMIZE_BASE_MAX_OFFSET - 1); @@ -232,7 +232,7 @@ unsigned long __init relocate_kernel(void) early_memunmap(cmdline, COMMAND_LINE_SIZE); if (random_offset) { - kernel_length = (long)(_end) - (long)(_text); + kernel_length = (unsigned long)(_end) - (unsigned long)(_text); /* Copy the kernel to it's new location */ memcpy(location_new, _text, kernel_length); From 33cf4171196debf6009e33d96dccdd7929071db5 Mon Sep 17 00:00:00 2001 From: SeongJae Park Date: Sat, 1 Nov 2025 11:20:14 -0700 Subject: [PATCH 1956/3902] mm/damon/tests/sysfs-kunit: handle alloc failures on damon_sysfs_test_add_targets() commit 7d808bf13943f4c6a6142400bffe14267f6dc997 upstream. damon_sysfs_test_add_targets() is assuming all dynamic memory allocation in it will succeed. Those are indeed likely in the real use cases since those allocations are too small to fail, but theoretically those could fail. In the case, inappropriate memory access can happen. Fix it by appropriately cleanup pre-allocated memory and skip the execution of the remaining tests in the failure cases. Link: https://lkml.kernel.org/r/20251101182021.74868-21-sj@kernel.org Fixes: b8ee5575f763 ("mm/damon/sysfs-test: add a unit test for damon_sysfs_set_targets()") Signed-off-by: SeongJae Park Cc: Brendan Higgins Cc: David Gow Cc: Kefeng Wang Cc: [6.7+] Signed-off-by: Andrew Morton Signed-off-by: Greg Kroah-Hartman --- mm/damon/tests/sysfs-kunit.h | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/mm/damon/tests/sysfs-kunit.h b/mm/damon/tests/sysfs-kunit.h index 7b5c7b307da99c..ce7218469f20b4 100644 --- a/mm/damon/tests/sysfs-kunit.h +++ b/mm/damon/tests/sysfs-kunit.h @@ -45,16 +45,41 @@ static void damon_sysfs_test_add_targets(struct kunit *test) struct damon_ctx *ctx; sysfs_targets = damon_sysfs_targets_alloc(); + if (!sysfs_targets) + kunit_skip(test, "sysfs_targets alloc fail"); sysfs_targets->nr = 1; sysfs_targets->targets_arr = kmalloc_array(1, sizeof(*sysfs_targets->targets_arr), GFP_KERNEL); + if (!sysfs_targets->targets_arr) { + kfree(sysfs_targets); + kunit_skip(test, "targets_arr alloc fail"); + } sysfs_target = damon_sysfs_target_alloc(); + if (!sysfs_target) { + kfree(sysfs_targets->targets_arr); + kfree(sysfs_targets); + kunit_skip(test, "sysfs_target alloc fail"); + } sysfs_target->pid = __damon_sysfs_test_get_any_pid(12, 100); sysfs_target->regions = damon_sysfs_regions_alloc(); + if (!sysfs_target->regions) { + kfree(sysfs_targets->targets_arr); + kfree(sysfs_targets); + kfree(sysfs_target); + kunit_skip(test, "sysfs_regions alloc fail"); + } + sysfs_targets->targets_arr[0] = sysfs_target; ctx = damon_new_ctx(); + if (!ctx) { + kfree(sysfs_targets->targets_arr); + kfree(sysfs_targets); + kfree(sysfs_target); + kfree(sysfs_target->regions); + kunit_skip(test, "ctx alloc fail"); + } damon_sysfs_add_targets(ctx, sysfs_targets); KUNIT_EXPECT_EQ(test, 1u, nr_damon_targets(ctx)); From 6fd615cb0440da435b9fdc1fbc5fadec9038db66 Mon Sep 17 00:00:00 2001 From: SeongJae Park Date: Sat, 1 Nov 2025 11:20:11 -0700 Subject: [PATCH 1957/3902] mm/damon/tests/vaddr-kunit: handle alloc failures on damon_do_test_apply_three_regions() commit 2b22d0fcc6320ba29b2122434c1d2f0785fb0a25 upstream. damon_do_test_apply_three_regions() is assuming all dynamic memory allocation in it will succeed. Those are indeed likely in the real use cases since those allocations are too small to fail, but theoretically those could fail. In the case, inappropriate memory access can happen. Fix it by appropriately cleanup pre-allocated memory and skip the execution of the remaining tests in the failure cases. Link: https://lkml.kernel.org/r/20251101182021.74868-18-sj@kernel.org Fixes: 17ccae8bb5c9 ("mm/damon: add kunit tests") Signed-off-by: SeongJae Park Cc: Brendan Higgins Cc: David Gow Cc: Kefeng Wang Cc: [5.15+] Signed-off-by: Andrew Morton Signed-off-by: Greg Kroah-Hartman --- mm/damon/tests/vaddr-kunit.h | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/mm/damon/tests/vaddr-kunit.h b/mm/damon/tests/vaddr-kunit.h index fce38dd53cf8f8..484223f195459d 100644 --- a/mm/damon/tests/vaddr-kunit.h +++ b/mm/damon/tests/vaddr-kunit.h @@ -136,8 +136,14 @@ static void damon_do_test_apply_three_regions(struct kunit *test, int i; t = damon_new_target(); + if (!t) + kunit_skip(test, "target alloc fail"); for (i = 0; i < nr_regions / 2; i++) { r = damon_new_region(regions[i * 2], regions[i * 2 + 1]); + if (!r) { + damon_destroy_target(t, NULL); + kunit_skip(test, "region alloc fail"); + } damon_add_region(r, t); } From 89532a71288cdcbfb6a6dfda331a939c87b08cec Mon Sep 17 00:00:00 2001 From: SeongJae Park Date: Sat, 1 Nov 2025 11:20:12 -0700 Subject: [PATCH 1958/3902] mm/damon/tests/vaddr-kunit: handle alloc failures in damon_test_split_evenly_fail() commit 7890e5b5bb6e386155c6e755fe70e0cdcc77f18e upstream. damon_test_split_evenly_fail() is assuming all dynamic memory allocation in it will succeed. Those are indeed likely in the real use cases since those allocations are too small to fail, but theoretically those could fail. In the case, inappropriate memory access can happen. Fix it by appropriately cleanup pre-allocated memory and skip the execution of the remaining tests in the failure cases. Link: https://lkml.kernel.org/r/20251101182021.74868-19-sj@kernel.org Fixes: 17ccae8bb5c9 ("mm/damon: add kunit tests") Signed-off-by: SeongJae Park Cc: Brendan Higgins Cc: David Gow Cc: Kefeng Wang Cc: [5.15+] Signed-off-by: Andrew Morton Signed-off-by: Greg Kroah-Hartman --- mm/damon/tests/vaddr-kunit.h | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/mm/damon/tests/vaddr-kunit.h b/mm/damon/tests/vaddr-kunit.h index 484223f195459d..1b0f21c2e37630 100644 --- a/mm/damon/tests/vaddr-kunit.h +++ b/mm/damon/tests/vaddr-kunit.h @@ -256,7 +256,16 @@ static void damon_test_split_evenly_fail(struct kunit *test, unsigned long start, unsigned long end, unsigned int nr_pieces) { struct damon_target *t = damon_new_target(); - struct damon_region *r = damon_new_region(start, end); + struct damon_region *r; + + if (!t) + kunit_skip(test, "target alloc fail"); + + r = damon_new_region(start, end); + if (!r) { + damon_free_target(t); + kunit_skip(test, "region alloc fail"); + } damon_add_region(r, t); KUNIT_EXPECT_EQ(test, From 6e0090114d663b55a95dfa45bd1a5ed4483399b7 Mon Sep 17 00:00:00 2001 From: SeongJae Park Date: Sat, 1 Nov 2025 11:20:13 -0700 Subject: [PATCH 1959/3902] mm/damon/tests/vaddr-kunit: handle alloc failures on damon_test_split_evenly_succ() commit 0a63a0e7570b9b2631dfb8d836dc572709dce39e upstream. damon_test_split_evenly_succ() is assuming all dynamic memory allocation in it will succeed. Those are indeed likely in the real use cases since those allocations are too small to fail, but theoretically those could fail. In the case, inappropriate memory access can happen. Fix it by appropriately cleanup pre-allocated memory and skip the execution of the remaining tests in the failure cases. Link: https://lkml.kernel.org/r/20251101182021.74868-20-sj@kernel.org Fixes: 17ccae8bb5c9 ("mm/damon: add kunit tests") Signed-off-by: SeongJae Park Cc: Brendan Higgins Cc: David Gow Cc: Kefeng Wang Cc: [5.15+] Signed-off-by: Andrew Morton Signed-off-by: Greg Kroah-Hartman --- mm/damon/tests/vaddr-kunit.h | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/mm/damon/tests/vaddr-kunit.h b/mm/damon/tests/vaddr-kunit.h index 1b0f21c2e37630..30dc5459f1d2c0 100644 --- a/mm/damon/tests/vaddr-kunit.h +++ b/mm/damon/tests/vaddr-kunit.h @@ -284,10 +284,17 @@ static void damon_test_split_evenly_succ(struct kunit *test, unsigned long start, unsigned long end, unsigned int nr_pieces) { struct damon_target *t = damon_new_target(); - struct damon_region *r = damon_new_region(start, end); + struct damon_region *r; unsigned long expected_width = (end - start) / nr_pieces; unsigned long i = 0; + if (!t) + kunit_skip(test, "target alloc fail"); + r = damon_new_region(start, end); + if (!r) { + damon_free_target(t); + kunit_skip(test, "region alloc fail"); + } damon_add_region(r, t); KUNIT_EXPECT_EQ(test, damon_va_evenly_split_region(t, r, nr_pieces), 0); From b55a42c605afa9450dc3637e55c30326cde41347 Mon Sep 17 00:00:00 2001 From: SeongJae Park Date: Sat, 1 Nov 2025 11:19:55 -0700 Subject: [PATCH 1960/3902] mm/damon/tests/core-kunit: fix memory leak in damon_test_set_filters_default_reject() commit b5ab490d85b772bc99d2648182a282f39f08feb6 upstream. Patch series "mm/damon/tests: fix memory bugs in kunit tests". DAMON kunit tests were initially written assuming those will be run on environments that are well controlled and therefore tolerant to transient test failures and bugs in the test code itself. The user-mode linux based manual run of the tests is one example of such an environment. And the test code was written for adding more test coverage as fast as possible, over making those safe and reliable. As a result, the tests resulted in having a number of bugs including real memory leaks, theoretical unhandled memory allocation failures, and unused memory allocations. The allocation failures that are not handled well are unlikely in the real world, since those allocations are too small to fail. But in theory, it can happen and cause inappropriate memory access. It is arguable if bugs in test code can really harm users. But, anyway bugs are bugs that need to be fixed. Fix the bugs one by one. Also Cc stable@ for the fixes of memory leak and unhandled memory allocation failures. The unused memory allocations are only a matter of memory efficiency, so not Cc-ing stable@. The first patch fixes memory leaks in the test code for the DAMON core layer. Following fifteen, three, and one patches respectively fix unhandled memory allocation failures in the test code for DAMON core layer, virtual address space DAMON operation set, and DAMON sysfs interface, one by one per test function. Final two patches remove memory allocations that are correctly deallocated at the end, but not really being used by any code. This patch (of 22): Kunit test function for damos_set_filters_default_reject() allocates two 'struct damos_filter' objects and not deallocates those, so that the memory for the two objects are leaked for every time the test runs. Fix this by deallocating those objects at the end of the test code. Link: https://lkml.kernel.org/r/20251101182021.74868-1-sj@kernel.org Link: https://lkml.kernel.org/r/20251101182021.74868-2-sj@kernel.org Fixes: 094fb14913c7 ("mm/damon/tests/core-kunit: add a test for damos_set_filters_default_reject()") Signed-off-by: SeongJae Park Cc: Brendan Higgins Cc: David Gow Cc: Kefeng Wang Cc: [6.16+] Signed-off-by: Andrew Morton Signed-off-by: Greg Kroah-Hartman --- mm/damon/tests/core-kunit.h | 3 +++ 1 file changed, 3 insertions(+) diff --git a/mm/damon/tests/core-kunit.h b/mm/damon/tests/core-kunit.h index 51369e35298b1c..69ca44f9270bbe 100644 --- a/mm/damon/tests/core-kunit.h +++ b/mm/damon/tests/core-kunit.h @@ -598,6 +598,9 @@ static void damon_test_set_filters_default_reject(struct kunit *test) */ KUNIT_EXPECT_EQ(test, scheme.core_filters_default_reject, false); KUNIT_EXPECT_EQ(test, scheme.ops_filters_default_reject, true); + + damos_free_filter(anon_filter); + damos_free_filter(target_filter); } static struct kunit_case damon_test_cases[] = { From 9c46f119b70d38443711f27b438cfe303aaaab50 Mon Sep 17 00:00:00 2001 From: SeongJae Park Date: Sat, 1 Nov 2025 11:20:07 -0700 Subject: [PATCH 1961/3902] mm/damon/tests/core-kunit: handle alloc failres in damon_test_new_filter() commit 28ab2265e9422ccd81e4beafc0ace90f78de04c4 upstream. damon_test_new_filter() is assuming all dynamic memory allocation in it will succeed. Those are indeed likely in the real use cases since those allocations are too small to fail, but theoretically those could fail. In the case, inappropriate memory access can happen. Fix it by appropriately cleanup pre-allocated memory and skip the execution of the remaining tests in the failure cases. Link: https://lkml.kernel.org/r/20251101182021.74868-14-sj@kernel.org Fixes: 2a158e956b98 ("mm/damon/core-test: add a test for damos_new_filter()") Signed-off-by: SeongJae Park Cc: Brendan Higgins Cc: David Gow Cc: Kefeng Wang Cc: [6.6+] Signed-off-by: Andrew Morton Signed-off-by: Greg Kroah-Hartman --- mm/damon/tests/core-kunit.h | 2 ++ 1 file changed, 2 insertions(+) diff --git a/mm/damon/tests/core-kunit.h b/mm/damon/tests/core-kunit.h index 69ca44f9270bbe..3d10a5fad5e0ca 100644 --- a/mm/damon/tests/core-kunit.h +++ b/mm/damon/tests/core-kunit.h @@ -412,6 +412,8 @@ static void damos_test_new_filter(struct kunit *test) struct damos_filter *filter; filter = damos_new_filter(DAMOS_FILTER_TYPE_ANON, true, false); + if (!filter) + kunit_skip(test, "filter alloc fail"); KUNIT_EXPECT_EQ(test, filter->type, DAMOS_FILTER_TYPE_ANON); KUNIT_EXPECT_EQ(test, filter->matching, true); KUNIT_EXPECT_PTR_EQ(test, filter->list.prev, &filter->list); From ead9dd2174e9deb2b29a235632cc5cba23811586 Mon Sep 17 00:00:00 2001 From: SeongJae Park Date: Sat, 1 Nov 2025 11:19:59 -0700 Subject: [PATCH 1962/3902] mm/damon/tests/core-kunit: handle alloc failures on damon_test_split_at() commit 5e80d73f22043c59c8ad36452a3253937ed77955 upstream. damon_test_split_at() is assuming all dynamic memory allocation in it will succeed. Those are indeed likely in the real use cases since those allocations are too small to fail, but theoretically those could fail. In the case, inappropriate memory access can happen. Fix it by appropriately cleanup pre-allocated memory and skip the execution of the remaining tests in the failure cases. Link: https://lkml.kernel.org/r/20251101182021.74868-6-sj@kernel.org Fixes: 17ccae8bb5c9 ("mm/damon: add kunit tests") Signed-off-by: SeongJae Park Cc: Brendan Higgins Cc: David Gow Cc: Kefeng Wang Cc: [5.15+] Signed-off-by: Andrew Morton Signed-off-by: Greg Kroah-Hartman --- mm/damon/tests/core-kunit.h | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/mm/damon/tests/core-kunit.h b/mm/damon/tests/core-kunit.h index 3d10a5fad5e0ca..efe4789ab9627a 100644 --- a/mm/damon/tests/core-kunit.h +++ b/mm/damon/tests/core-kunit.h @@ -124,8 +124,19 @@ static void damon_test_split_at(struct kunit *test) struct damon_target *t; struct damon_region *r, *r_new; + if (!c) + kunit_skip(test, "ctx alloc fail"); t = damon_new_target(); + if (!t) { + damon_destroy_ctx(c); + kunit_skip(test, "target alloc fail"); + } r = damon_new_region(0, 100); + if (!r) { + damon_destroy_ctx(c); + damon_free_target(t); + kunit_skip(test, "region alloc fail"); + } r->nr_accesses_bp = 420000; r->nr_accesses = 42; r->last_nr_accesses = 15; From d0870d0fe61338f0c7bbda07e22682e570823d03 Mon Sep 17 00:00:00 2001 From: SeongJae Park Date: Sat, 1 Nov 2025 11:19:56 -0700 Subject: [PATCH 1963/3902] mm/damon/tests/core-kunit: handle allocation failures in damon_test_regions() commit e16fdd4f754048d6e23c56bd8d920b71e41e3777 upstream. damon_test_regions() is assuming all dynamic memory allocation in it will succeed. Those are indeed likely in the real use cases since those allocations are too small to fail, but theoretically those could fail. In the case, inappropriate memory access can happen. Fix it by appropriately cleanup pre-allocated memory and skip the execution of the remaining tests in the failure cases. Link: https://lkml.kernel.org/r/20251101182021.74868-3-sj@kernel.org Fixes: 17ccae8bb5c9 ("mm/damon: add kunit tests") Signed-off-by: SeongJae Park Cc: Brendan Higgins Cc: David Gow Cc: Kefeng Wang Cc: [5.15+] Signed-off-by: Andrew Morton Signed-off-by: Greg Kroah-Hartman --- mm/damon/tests/core-kunit.h | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/mm/damon/tests/core-kunit.h b/mm/damon/tests/core-kunit.h index efe4789ab9627a..22d5ff51fa0b91 100644 --- a/mm/damon/tests/core-kunit.h +++ b/mm/damon/tests/core-kunit.h @@ -20,11 +20,17 @@ static void damon_test_regions(struct kunit *test) struct damon_target *t; r = damon_new_region(1, 2); + if (!r) + kunit_skip(test, "region alloc fail"); KUNIT_EXPECT_EQ(test, 1ul, r->ar.start); KUNIT_EXPECT_EQ(test, 2ul, r->ar.end); KUNIT_EXPECT_EQ(test, 0u, r->nr_accesses); t = damon_new_target(); + if (!t) { + damon_free_region(r); + kunit_skip(test, "target alloc fail"); + } KUNIT_EXPECT_EQ(test, 0u, damon_nr_regions(t)); damon_add_region(r, t); From 63669f6b729f49e397aa1e26a3647ac73fc54fc5 Mon Sep 17 00:00:00 2001 From: SeongJae Park Date: Sat, 1 Nov 2025 11:19:57 -0700 Subject: [PATCH 1964/3902] mm/damon/tests/core-kunit: handle memory failure from damon_test_target() commit fafe953de2c661907c94055a2497c6b8dbfd26f3 upstream. damon_test_target() is assuming all dynamic memory allocation in it will succeed. Those are indeed likely in the real use cases since those allocations are too small to fail, but theoretically those could fail. In the case, inappropriate memory access can happen. Fix it by appropriately cleanup pre-allocated memory and skip the execution of the remaining tests in the failure cases. Link: https://lkml.kernel.org/r/20251101182021.74868-4-sj@kernel.org Fixes: 17ccae8bb5c9 ("mm/damon: add kunit tests") Signed-off-by: SeongJae Park Cc: Brendan Higgins Cc: David Gow Cc: Kefeng Wang Cc: [5.15+] Signed-off-by: Andrew Morton Signed-off-by: Greg Kroah-Hartman --- mm/damon/tests/core-kunit.h | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/mm/damon/tests/core-kunit.h b/mm/damon/tests/core-kunit.h index 22d5ff51fa0b91..fb42763e4cf185 100644 --- a/mm/damon/tests/core-kunit.h +++ b/mm/damon/tests/core-kunit.h @@ -58,7 +58,14 @@ static void damon_test_target(struct kunit *test) struct damon_ctx *c = damon_new_ctx(); struct damon_target *t; + if (!c) + kunit_skip(test, "ctx alloc fail"); + t = damon_new_target(); + if (!t) { + damon_destroy_ctx(c); + kunit_skip(test, "target alloc fail"); + } KUNIT_EXPECT_EQ(test, 0u, nr_damon_targets(c)); damon_add_target(c, t); From 8ac54bafd439bd670945497ac6b9d3b05d645fe2 Mon Sep 17 00:00:00 2001 From: SeongJae Park Date: Sat, 1 Nov 2025 11:19:58 -0700 Subject: [PATCH 1965/3902] mm/damon/tests/core-kunit: handle memory alloc failure from damon_test_aggregate() commit f79f2fc44ebd0ed655239046be3e80e8804b5545 upstream. damon_test_aggregate() is assuming all dynamic memory allocation in it will succeed. Those are indeed likely in the real use cases since those allocations are too small to fail, but theoretically those could fail. In the case, inappropriate memory access can happen. Fix it by appropriately cleanup pre-allocated memory and skip the execution of the remaining tests in the failure cases. Link: https://lkml.kernel.org/r/20251101182021.74868-5-sj@kernel.org Fixes: 17ccae8bb5c9 ("mm/damon: add kunit tests") Signed-off-by: SeongJae Park Cc: Brendan Higgins Cc: David Gow Cc: Kefeng Wang Cc: [5.15+] Signed-off-by: Andrew Morton Signed-off-by: Greg Kroah-Hartman --- mm/damon/tests/core-kunit.h | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/mm/damon/tests/core-kunit.h b/mm/damon/tests/core-kunit.h index fb42763e4cf185..948b1b4ab1685d 100644 --- a/mm/damon/tests/core-kunit.h +++ b/mm/damon/tests/core-kunit.h @@ -97,8 +97,15 @@ static void damon_test_aggregate(struct kunit *test) struct damon_region *r; int it, ir; + if (!ctx) + kunit_skip(test, "ctx alloc fail"); + for (it = 0; it < 3; it++) { t = damon_new_target(); + if (!t) { + damon_destroy_ctx(ctx); + kunit_skip(test, "target alloc fail"); + } damon_add_target(ctx, t); } @@ -106,6 +113,10 @@ static void damon_test_aggregate(struct kunit *test) damon_for_each_target(t, ctx) { for (ir = 0; ir < 3; ir++) { r = damon_new_region(saddr[it][ir], eaddr[it][ir]); + if (!r) { + damon_destroy_ctx(ctx); + kunit_skip(test, "region alloc fail"); + } r->nr_accesses = accesses[it][ir]; r->nr_accesses_bp = accesses[it][ir] * 10000; damon_add_region(r, t); From 753c12ff6d25d6c40f7df347760a52a42f0805b3 Mon Sep 17 00:00:00 2001 From: SeongJae Park Date: Sat, 1 Nov 2025 11:20:01 -0700 Subject: [PATCH 1966/3902] mm/damon/tests/core-kunit: handle alloc failures on dasmon_test_merge_regions_of() commit 0998d2757218771c59d5ca59ccf13d1542a38f17 upstream. damon_test_merge_regions_of() is assuming all dynamic memory allocation in it will succeed. Those are indeed likely in the real use cases since those allocations are too small to fail, but theoretically those could fail. In the case, inappropriate memory access can happen. Fix it by appropriately cleanup pre-allocated memory and skip the execution of the remaining tests in the failure cases. Link: https://lkml.kernel.org/r/20251101182021.74868-8-sj@kernel.org Fixes: 17ccae8bb5c9 ("mm/damon: add kunit tests") Signed-off-by: SeongJae Park Cc: Brendan Higgins Cc: David Gow Cc: Kefeng Wang Cc: [5.15+] Signed-off-by: Andrew Morton Signed-off-by: Greg Kroah-Hartman --- mm/damon/tests/core-kunit.h | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/mm/damon/tests/core-kunit.h b/mm/damon/tests/core-kunit.h index 948b1b4ab1685d..f00edabc5ddbb3 100644 --- a/mm/damon/tests/core-kunit.h +++ b/mm/damon/tests/core-kunit.h @@ -238,8 +238,14 @@ static void damon_test_merge_regions_of(struct kunit *test) int i; t = damon_new_target(); + if (!t) + kunit_skip(test, "target alloc fail"); for (i = 0; i < ARRAY_SIZE(sa); i++) { r = damon_new_region(sa[i], ea[i]); + if (!r) { + damon_free_target(t); + kunit_skip(test, "region alloc fail"); + } r->nr_accesses = nrs[i]; r->nr_accesses_bp = nrs[i] * 10000; damon_add_region(r, t); From 65807e05960b07d6f9b0840bc2c0bb23b50af54a Mon Sep 17 00:00:00 2001 From: SeongJae Park Date: Sat, 1 Nov 2025 11:20:00 -0700 Subject: [PATCH 1967/3902] mm/damon/tests/core-kunit: handle alloc failures on damon_test_merge_two() commit 3d443dd29a1db7efa587a4bb0c06a497e13ca9e4 upstream. damon_test_merge_two() is assuming all dynamic memory allocation in it will succeed. Those are indeed likely in the real use cases since those allocations are too small to fail, but theoretically those could fail. In the case, inappropriate memory access can happen. Fix it by appropriately cleanup pre-allocated memory and skip the execution of the remaining tests in the failure cases. Link: https://lkml.kernel.org/r/20251101182021.74868-7-sj@kernel.org Fixes: 17ccae8bb5c9 ("mm/damon: add kunit tests") Signed-off-by: SeongJae Park Cc: Brendan Higgins Cc: David Gow Cc: Kefeng Wang Cc: [5.15+] Signed-off-by: Andrew Morton Signed-off-by: Greg Kroah-Hartman --- mm/damon/tests/core-kunit.h | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/mm/damon/tests/core-kunit.h b/mm/damon/tests/core-kunit.h index f00edabc5ddbb3..359dce4e7353a8 100644 --- a/mm/damon/tests/core-kunit.h +++ b/mm/damon/tests/core-kunit.h @@ -188,11 +188,21 @@ static void damon_test_merge_two(struct kunit *test) int i; t = damon_new_target(); + if (!t) + kunit_skip(test, "target alloc fail"); r = damon_new_region(0, 100); + if (!r) { + damon_free_target(t); + kunit_skip(test, "region alloc fail"); + } r->nr_accesses = 10; r->nr_accesses_bp = 100000; damon_add_region(r, t); r2 = damon_new_region(100, 300); + if (!r2) { + damon_free_target(t); + kunit_skip(test, "second region alloc fail"); + } r2->nr_accesses = 20; r2->nr_accesses_bp = 200000; damon_add_region(r2, t); From 8ce6a884fd4b282121b72476fcceb2710c4f0e5e Mon Sep 17 00:00:00 2001 From: SeongJae Park Date: Sat, 1 Nov 2025 11:20:04 -0700 Subject: [PATCH 1968/3902] mm/damon/tests/core-kunit: handle alloc failures in damon_test_set_regions() commit 74d5969995d129fd59dd93b9c7daa6669cb6810f upstream. damon_test_set_regions() is assuming all dynamic memory allocation in it will succeed. Those are indeed likely in the real use cases since those allocations are too small to fail, but theoretically those could fail. In the case, inappropriate memory access can happen. Fix it by appropriately cleanup pre-allocated memory and skip the execution of the remaining tests in the failure cases. Link: https://lkml.kernel.org/r/20251101182021.74868-11-sj@kernel.org Fixes: 62f409560eb2 ("mm/damon/core-test: test damon_set_regions") Signed-off-by: SeongJae Park Cc: Brendan Higgins Cc: David Gow Cc: Kefeng Wang Cc: [6.1+] Signed-off-by: Andrew Morton Signed-off-by: Greg Kroah-Hartman --- mm/damon/tests/core-kunit.h | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/mm/damon/tests/core-kunit.h b/mm/damon/tests/core-kunit.h index 359dce4e7353a8..2173686f6776f5 100644 --- a/mm/damon/tests/core-kunit.h +++ b/mm/damon/tests/core-kunit.h @@ -345,13 +345,26 @@ static void damon_test_ops_registration(struct kunit *test) static void damon_test_set_regions(struct kunit *test) { struct damon_target *t = damon_new_target(); - struct damon_region *r1 = damon_new_region(4, 16); - struct damon_region *r2 = damon_new_region(24, 32); + struct damon_region *r1, *r2; struct damon_addr_range range = {.start = 8, .end = 28}; unsigned long expects[] = {8, 16, 16, 24, 24, 28}; int expect_idx = 0; struct damon_region *r; + if (!t) + kunit_skip(test, "target alloc fail"); + r1 = damon_new_region(4, 16); + if (!r1) { + damon_free_target(t); + kunit_skip(test, "region alloc fail"); + } + r2 = damon_new_region(24, 32); + if (!r2) { + damon_free_target(t); + damon_free_region(r1); + kunit_skip(test, "second region alloc fail"); + } + damon_add_region(r1, t); damon_add_region(r2, t); damon_set_regions(t, &range, 1, DAMON_MIN_REGION); From 1e88ea25235544a51faa889846f13de88b6e9a2e Mon Sep 17 00:00:00 2001 From: SeongJae Park Date: Sat, 1 Nov 2025 11:20:05 -0700 Subject: [PATCH 1969/3902] mm/damon/tests/core-kunit: handle alloc failures in damon_test_update_monitoring_result() commit 8cf298c01b7fdb08eef5b6b26d0fe98d48134d72 upstream. damon_test_update_monitoring_result() is assuming all dynamic memory allocation in it will succeed. Those are indeed likely in the real use cases since those allocations are too small to fail, but theoretically those could fail. In the case, inappropriate memory access can happen. Fix it by appropriately cleanup pre-allocated memory and skip the execution of the remaining tests in the failure cases. Link: https://lkml.kernel.org/r/20251101182021.74868-12-sj@kernel.org Fixes: f4c978b6594b ("mm/damon/core-test: add a test for damon_update_monitoring_results()") Signed-off-by: SeongJae Park Cc: Brendan Higgins Cc: David Gow Cc: Kefeng Wang Cc: [6.3+] Signed-off-by: Andrew Morton Signed-off-by: Greg Kroah-Hartman --- mm/damon/tests/core-kunit.h | 3 +++ 1 file changed, 3 insertions(+) diff --git a/mm/damon/tests/core-kunit.h b/mm/damon/tests/core-kunit.h index 2173686f6776f5..51051834e17720 100644 --- a/mm/damon/tests/core-kunit.h +++ b/mm/damon/tests/core-kunit.h @@ -406,6 +406,9 @@ static void damon_test_update_monitoring_result(struct kunit *test) struct damon_attrs new_attrs; struct damon_region *r = damon_new_region(3, 7); + if (!r) + kunit_skip(test, "region alloc fail"); + r->nr_accesses = 15; r->nr_accesses_bp = 150000; r->age = 20; From 7d0299ff888319a03c4a929ea2230cac11816cb8 Mon Sep 17 00:00:00 2001 From: SeongJae Park Date: Sat, 1 Nov 2025 11:20:10 -0700 Subject: [PATCH 1970/3902] mm/damon/tests/core-kunit: handle alloc failures on damon_test_set_filters_default_reject() commit 84be856cc87317bc60ff54bd7c8f8a5aa8f0e2c8 upstream. damon_test_set_filters_default_reject() is assuming all dynamic memory allocation in it will succeed. Those are indeed likely in the real use cases since those allocations are too small to fail, but theoretically those could fail. In the case, inappropriate memory access can happen. Fix it by appropriately cleanup pre-allocated memory and skip the execution of the remaining tests in the failure cases. Link: https://lkml.kernel.org/r/20251101182021.74868-17-sj@kernel.org Fixes: 094fb14913c7 ("mm/damon/tests/core-kunit: add a test for damos_set_filters_default_reject()") Signed-off-by: SeongJae Park Cc: Brendan Higgins Cc: David Gow Cc: Kefeng Wang Cc: [6.16+] Signed-off-by: Andrew Morton Signed-off-by: Greg Kroah-Hartman --- mm/damon/tests/core-kunit.h | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/mm/damon/tests/core-kunit.h b/mm/damon/tests/core-kunit.h index 51051834e17720..62e5bb465a9226 100644 --- a/mm/damon/tests/core-kunit.h +++ b/mm/damon/tests/core-kunit.h @@ -617,6 +617,8 @@ static void damon_test_set_filters_default_reject(struct kunit *test) KUNIT_EXPECT_EQ(test, scheme.ops_filters_default_reject, false); target_filter = damos_new_filter(DAMOS_FILTER_TYPE_TARGET, true, true); + if (!target_filter) + kunit_skip(test, "filter alloc fail"); damos_add_filter(&scheme, target_filter); damos_set_filters_default_reject(&scheme); /* @@ -642,6 +644,10 @@ static void damon_test_set_filters_default_reject(struct kunit *test) KUNIT_EXPECT_EQ(test, scheme.ops_filters_default_reject, false); anon_filter = damos_new_filter(DAMOS_FILTER_TYPE_ANON, true, true); + if (!anon_filter) { + damos_free_filter(target_filter); + kunit_skip(test, "anon_filter alloc fail"); + } damos_add_filter(&scheme, anon_filter); damos_set_filters_default_reject(&scheme); From 3987bd5706c91325ad8d26e5052c60dbd1cc7ed2 Mon Sep 17 00:00:00 2001 From: SeongJae Park Date: Sat, 1 Nov 2025 11:20:09 -0700 Subject: [PATCH 1971/3902] mm/damon/tests/core-kunit: handle alloc failures on damos_test_filter_out() commit d14d5671e7c9cc788c5a1edfa94e6f9064275905 upstream. damon_test_filter_out() is assuming all dynamic memory allocation in it will succeed. Those are indeed likely in the real use cases since those allocations are too small to fail, but theoretically those could fail. In the case, inappropriate memory access can happen. Fix it by appropriately cleanup pre-allocated memory and skip the execution of the remaining tests in the failure cases. Link: https://lkml.kernel.org/r/20251101182021.74868-16-sj@kernel.org Fixes: 26713c890875 ("mm/damon/core-test: add a unit test for __damos_filter_out()") Signed-off-by: SeongJae Park Cc: Brendan Higgins Cc: David Gow Cc: Kefeng Wang Cc: [6.6+] Signed-off-by: Andrew Morton Signed-off-by: Greg Kroah-Hartman --- mm/damon/tests/core-kunit.h | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/mm/damon/tests/core-kunit.h b/mm/damon/tests/core-kunit.h index 62e5bb465a9226..f37344ee809c35 100644 --- a/mm/damon/tests/core-kunit.h +++ b/mm/damon/tests/core-kunit.h @@ -511,11 +511,22 @@ static void damos_test_filter_out(struct kunit *test) struct damos_filter *f; f = damos_new_filter(DAMOS_FILTER_TYPE_ADDR, true, false); + if (!f) + kunit_skip(test, "filter alloc fail"); f->addr_range = (struct damon_addr_range){ .start = DAMON_MIN_REGION * 2, .end = DAMON_MIN_REGION * 6}; t = damon_new_target(); + if (!t) { + damos_destroy_filter(f); + kunit_skip(test, "target alloc fail"); + } r = damon_new_region(DAMON_MIN_REGION * 3, DAMON_MIN_REGION * 5); + if (!r) { + damos_destroy_filter(f); + damon_free_target(t); + kunit_skip(test, "region alloc fail"); + } damon_add_region(r, t); /* region in the range */ From 4b03893690658ff245f9ef1a5905df4f797b5664 Mon Sep 17 00:00:00 2001 From: SeongJae Park Date: Sat, 1 Nov 2025 11:20:03 -0700 Subject: [PATCH 1972/3902] mm/damon/tests/core-kunit: handle alloc failures in damon_test_ops_registration() commit 4f835f4e8c863985f15abd69db033c2f66546094 upstream. damon_test_ops_registration() is assuming all dynamic memory allocation in it will succeed. Those are indeed likely in the real use cases since those allocations are too small to fail, but theoretically those could fail. In the case, inappropriate memory access can happen. Fix it by appropriately cleanup pre-allocated memory and skip the execution of the remaining tests in the failure cases. Link: https://lkml.kernel.org/r/20251101182021.74868-10-sj@kernel.org Fixes: 4f540f5ab4f2 ("mm/damon/core-test: add a kunit test case for ops registration") Signed-off-by: SeongJae Park Cc: Brendan Higgins Cc: David Gow Cc: Kefeng Wang Cc: [5.19+] Signed-off-by: Andrew Morton Signed-off-by: Greg Kroah-Hartman --- mm/damon/tests/core-kunit.h | 3 +++ 1 file changed, 3 insertions(+) diff --git a/mm/damon/tests/core-kunit.h b/mm/damon/tests/core-kunit.h index f37344ee809c35..63474ada21d569 100644 --- a/mm/damon/tests/core-kunit.h +++ b/mm/damon/tests/core-kunit.h @@ -300,6 +300,9 @@ static void damon_test_ops_registration(struct kunit *test) struct damon_operations ops = {.id = DAMON_OPS_VADDR}, bak; bool need_cleanup = false; + if (!c) + kunit_skip(test, "ctx alloc fail"); + /* DAMON_OPS_VADDR is registered only if CONFIG_DAMON_VADDR is set */ if (!damon_is_registered_ops(DAMON_OPS_VADDR)) { bak.id = DAMON_OPS_VADDR; From 792515de4e9d0ac923c0194063f10d7f9e228244 Mon Sep 17 00:00:00 2001 From: SeongJae Park Date: Sat, 1 Nov 2025 11:20:06 -0700 Subject: [PATCH 1973/3902] mm/damon/tests/core-kunit: handle alloc failure on damon_test_set_attrs() commit 915a2453d824a9b6bf724e3f970d86ae1d092a61 upstream. damon_test_set_attrs() is assuming all dynamic memory allocation in it will succeed. Those are indeed likely in the real use cases since those allocations are too small to fail, but theoretically those could fail. In the case, inappropriate memory access can happen. Fix it by appropriately cleanup pre-allocated memory and skip the execution of the remaining tests in the failure cases. Link: https://lkml.kernel.org/r/20251101182021.74868-13-sj@kernel.org Fixes: aa13779be6b7 ("mm/damon/core-test: add a test for damon_set_attrs()") Signed-off-by: SeongJae Park Cc: Brendan Higgins Cc: David Gow Cc: Kefeng Wang Cc: [6.5+] Signed-off-by: Andrew Morton Signed-off-by: Greg Kroah-Hartman --- mm/damon/tests/core-kunit.h | 3 +++ 1 file changed, 3 insertions(+) diff --git a/mm/damon/tests/core-kunit.h b/mm/damon/tests/core-kunit.h index 63474ada21d569..3ad9b851a19e66 100644 --- a/mm/damon/tests/core-kunit.h +++ b/mm/damon/tests/core-kunit.h @@ -445,6 +445,9 @@ static void damon_test_set_attrs(struct kunit *test) .sample_interval = 5000, .aggr_interval = 100000,}; struct damon_attrs invalid_attrs; + if (!c) + kunit_skip(test, "ctx alloc fail"); + KUNIT_EXPECT_EQ(test, damon_set_attrs(c, &valid_attrs), 0); invalid_attrs = valid_attrs; From 11ccb3476ead90e7da669a0d0a5b74ac8819208a Mon Sep 17 00:00:00 2001 From: SeongJae Park Date: Sat, 1 Nov 2025 11:20:08 -0700 Subject: [PATCH 1974/3902] mm/damon/tests/core-kunit: handle alloc failure on damos_test_commit_filter() commit 3e5c4a1a1737bd79abaaa184233d0f815e62273b upstream. damon_test_commit_filter() is assuming all dynamic memory allocation in it will succeed. Those are indeed likely in the real use cases since those allocations are too small to fail, but theoretically those could fail. In the case, inappropriate memory access can happen. Fix it by appropriately cleanup pre-allocated memory and skip the execution of the remaining tests in the failure cases. Link: https://lkml.kernel.org/r/20251101182021.74868-15-sj@kernel.org Fixes: f6a4a150f1ec ("mm/damon/tests/core-kunit: add damos_commit_filter test") Signed-off-by: SeongJae Park Cc: Brendan Higgins Cc: David Gow Cc: Kefeng Wang Cc: [6.18+] Signed-off-by: Andrew Morton Signed-off-by: Greg Kroah-Hartman --- mm/damon/tests/core-kunit.h | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/mm/damon/tests/core-kunit.h b/mm/damon/tests/core-kunit.h index 3ad9b851a19e66..63d10ca934f611 100644 --- a/mm/damon/tests/core-kunit.h +++ b/mm/damon/tests/core-kunit.h @@ -496,11 +496,16 @@ static void damos_test_new_filter(struct kunit *test) static void damos_test_commit_filter(struct kunit *test) { - struct damos_filter *src_filter = damos_new_filter( - DAMOS_FILTER_TYPE_ANON, true, true); - struct damos_filter *dst_filter = damos_new_filter( - DAMOS_FILTER_TYPE_ACTIVE, false, false); - + struct damos_filter *src_filter, *dst_filter; + + src_filter = damos_new_filter(DAMOS_FILTER_TYPE_ANON, true, true); + if (!src_filter) + kunit_skip(test, "src filter alloc fail"); + dst_filter = damos_new_filter(DAMOS_FILTER_TYPE_ACTIVE, false, false); + if (!dst_filter) { + damos_destroy_filter(src_filter); + kunit_skip(test, "dst filter alloc fail"); + } damos_commit_filter(dst_filter, src_filter); KUNIT_EXPECT_EQ(test, dst_filter->type, src_filter->type); KUNIT_EXPECT_EQ(test, dst_filter->matching, src_filter->matching); From 744e1bdd1471b7c914bf9fc06666f8960f149aad Mon Sep 17 00:00:00 2001 From: Macpaul Lin Date: Fri, 28 Nov 2025 12:17:22 +0800 Subject: [PATCH 1975/3902] pmdomain: mtk-pm-domains: Fix spinlock recursion fix in probe commit 305f254727bd379bbed0385afa0162f5bde1f51c upstream. Remove scpsys_get_legacy_regmap(), replacing its usage with of_find_node_with_property(). Explicitly call of_node_get(np) before each of_find_node_with_property() to maintain correct node reference counting. The of_find_node_with_property() function "consumes" its input by calling of_node_put() internally, whether or not it finds a match. Currently, dev->of_node (np) is passed multiple times in sequence without incrementing its reference count, causing it to be decremented multiple times and risking early memory release. Adding of_node_get(np) before each call balances the reference count, preventing premature node release. Fixes: c1bac49fe91f ("pmdomains: mtk-pm-domains: Fix spinlock recursion in probe") Cc: stable@vger.kernel.org Signed-off-by: Macpaul Lin Tested-by: Louis-Alexis Eyraud Signed-off-by: Ulf Hansson Signed-off-by: Greg Kroah-Hartman --- drivers/pmdomain/mediatek/mtk-pm-domains.c | 21 ++++++--------------- 1 file changed, 6 insertions(+), 15 deletions(-) diff --git a/drivers/pmdomain/mediatek/mtk-pm-domains.c b/drivers/pmdomain/mediatek/mtk-pm-domains.c index 9c9323c8c93a70..269634bcd9a407 100644 --- a/drivers/pmdomain/mediatek/mtk-pm-domains.c +++ b/drivers/pmdomain/mediatek/mtk-pm-domains.c @@ -748,18 +748,6 @@ static void scpsys_domain_cleanup(struct scpsys *scpsys) } } -static struct device_node *scpsys_get_legacy_regmap(struct device_node *np, const char *pn) -{ - struct device_node *local_node; - - for_each_child_of_node(np, local_node) { - if (of_property_present(local_node, pn)) - return local_node; - } - - return NULL; -} - static int scpsys_get_bus_protection_legacy(struct device *dev, struct scpsys *scpsys) { const u8 bp_blocks[3] = { @@ -781,7 +769,8 @@ static int scpsys_get_bus_protection_legacy(struct device *dev, struct scpsys *s * this makes it then possible to allocate the array of bus_prot * regmaps and convert all to the new style handling. */ - node = scpsys_get_legacy_regmap(np, "mediatek,infracfg"); + of_node_get(np); + node = of_find_node_with_property(np, "mediatek,infracfg"); if (node) { regmap[0] = syscon_regmap_lookup_by_phandle(node, "mediatek,infracfg"); of_node_put(node); @@ -794,7 +783,8 @@ static int scpsys_get_bus_protection_legacy(struct device *dev, struct scpsys *s regmap[0] = NULL; } - node = scpsys_get_legacy_regmap(np, "mediatek,smi"); + of_node_get(np); + node = of_find_node_with_property(np, "mediatek,smi"); if (node) { smi_np = of_parse_phandle(node, "mediatek,smi", 0); of_node_put(node); @@ -812,7 +802,8 @@ static int scpsys_get_bus_protection_legacy(struct device *dev, struct scpsys *s regmap[1] = NULL; } - node = scpsys_get_legacy_regmap(np, "mediatek,infracfg-nao"); + of_node_get(np); + node = of_find_node_with_property(np, "mediatek,infracfg-nao"); if (node) { regmap[2] = syscon_regmap_lookup_by_phandle(node, "mediatek,infracfg-nao"); num_regmaps++; From 87f09d9245f1e537a579a54d22a4661bebe72e5a Mon Sep 17 00:00:00 2001 From: Wentao Liang Date: Thu, 11 Dec 2025 04:02:52 +0000 Subject: [PATCH 1976/3902] pmdomain: imx: Fix reference count leak in imx_gpc_probe() commit 73cb5f6eafb0ac7aea8cdeb8ff12981aa741d8fb upstream. of_get_child_by_name() returns a node pointer with refcount incremented. Use the __free() attribute to manage the pgc_node reference, ensuring automatic of_node_put() cleanup when pgc_node goes out of scope. This eliminates the need for explicit error handling paths and avoids reference count leaks. Fixes: 721cabf6c660 ("soc: imx: move PGC handling to a new GPC driver") Cc: stable@vger.kernel.org Signed-off-by: Wentao Liang Reviewed-by: Frank Li Signed-off-by: Ulf Hansson Signed-off-by: Greg Kroah-Hartman --- drivers/pmdomain/imx/gpc.c | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/drivers/pmdomain/imx/gpc.c b/drivers/pmdomain/imx/gpc.c index a34b260274f7bd..de695f1944ab31 100644 --- a/drivers/pmdomain/imx/gpc.c +++ b/drivers/pmdomain/imx/gpc.c @@ -402,13 +402,12 @@ static int imx_gpc_old_dt_init(struct device *dev, struct regmap *regmap, static int imx_gpc_probe(struct platform_device *pdev) { const struct imx_gpc_dt_data *of_id_data = device_get_match_data(&pdev->dev); - struct device_node *pgc_node; + struct device_node *pgc_node __free(device_node) + = of_get_child_by_name(pdev->dev.of_node, "pgc"); struct regmap *regmap; void __iomem *base; int ret; - pgc_node = of_get_child_by_name(pdev->dev.of_node, "pgc"); - /* bail out if DT too old and doesn't provide the necessary info */ if (!of_property_present(pdev->dev.of_node, "#power-domain-cells") && !pgc_node) From 089e50f29eeec8eef6ae1450fc88138d719291cb Mon Sep 17 00:00:00 2001 From: Jens Axboe Date: Thu, 18 Dec 2025 15:21:28 -0700 Subject: [PATCH 1977/3902] af_unix: don't post cmsg for SO_INQ unless explicitly asked for commit 4d1442979e4a53b9457ce1e373e187e1511ff688 upstream. A previous commit added SO_INQ support for AF_UNIX (SOCK_STREAM), but it posts a SCM_INQ cmsg even if just msg->msg_get_inq is set. This is incorrect, as ->msg_get_inq is just the caller asking for the remainder to be passed back in msg->msg_inq, it has nothing to do with cmsg. The original commit states that this is done to make sockets io_uring-friendly", but it's actually incorrect as io_uring doesn't use cmsg headers internally at all, and it's actively wrong as this means that cmsg's are always posted if someone does recvmsg via io_uring. Fix that up by only posting a cmsg if u->recvmsg_inq is set. Additionally, mirror how TCP handles inquiry handling in that it should only be done for a successful return. This makes the logic for the two identical. Cc: stable@vger.kernel.org Fixes: df30285b3670 ("af_unix: Introduce SO_INQ.") Reported-by: Julian Orth Link: https://github.com/axboe/liburing/issues/1509 Signed-off-by: Jens Axboe Reviewed-by: Willem de Bruijn Link: https://patch.msgid.link/07adc0c2-2c3b-4d08-8af1-1c466a40b6a8@kernel.dk Signed-off-by: Paolo Abeni Signed-off-by: Greg Kroah-Hartman --- net/unix/af_unix.c | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/net/unix/af_unix.c b/net/unix/af_unix.c index 833c3616d2a2b4..f6f01f514933b4 100644 --- a/net/unix/af_unix.c +++ b/net/unix/af_unix.c @@ -2929,6 +2929,7 @@ static int unix_stream_read_generic(struct unix_stream_read_state *state, unsigned int last_len; struct unix_sock *u; int copied = 0; + bool do_cmsg; int err = 0; long timeo; int target; @@ -2954,6 +2955,9 @@ static int unix_stream_read_generic(struct unix_stream_read_state *state, u = unix_sk(sk); + do_cmsg = READ_ONCE(u->recvmsg_inq); + if (do_cmsg) + msg->msg_get_inq = 1; redo: /* Lock the socket to prevent queue disordering * while sleeps in memcpy_tomsg @@ -3113,10 +3117,11 @@ static int unix_stream_read_generic(struct unix_stream_read_state *state, if (msg) { scm_recv_unix(sock, msg, &scm, flags); - if (READ_ONCE(u->recvmsg_inq) || msg->msg_get_inq) { + if (msg->msg_get_inq && (copied ?: err) >= 0) { msg->msg_inq = READ_ONCE(u->inq_len); - put_cmsg(msg, SOL_SOCKET, SCM_INQ, - sizeof(msg->msg_inq), &msg->msg_inq); + if (do_cmsg) + put_cmsg(msg, SOL_SOCKET, SCM_INQ, + sizeof(msg->msg_inq), &msg->msg_inq); } } else { scm_destroy(&scm); From 6cce897a37dc9813c7b70a7ebe4f5f14aa604c8d Mon Sep 17 00:00:00 2001 From: "H. Peter Anvin" Date: Fri, 18 Jul 2025 11:35:00 -0700 Subject: [PATCH 1978/3902] compiler_types.h: add "auto" as a macro for "__auto_type" commit 2fb6915fa22dc5524d704afba58a13305dd9f533 upstream. "auto" was defined as a keyword back in the K&R days, but as a storage type specifier. No one ever used it, since it was and is the default storage type for local variables. C++11 recycled the keyword to allow a type to be declared based on the type of an initializer. This was finally adopted into standard C in C23. gcc and clang provide the "__auto_type" alias keyword as an extension for pre-C23, however, there is no reason to pollute the bulk of the source base with this temporary keyword; instead define "auto" as a macro unless the compiler is running in C23+ mode. This macro is added in because that header is included in some of the tools headers, wheres is not as it has a bunch of very kernel-specific things in it. [ Cc: stable to reduce potential backporting burden. ] Signed-off-by: H. Peter Anvin (Intel) Acked-by: Miguel Ojeda Cc: Signed-off-by: Greg Kroah-Hartman --- include/linux/compiler_types.h | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/include/linux/compiler_types.h b/include/linux/compiler_types.h index 0a1b9598940d05..8128a445f04808 100644 --- a/include/linux/compiler_types.h +++ b/include/linux/compiler_types.h @@ -13,6 +13,19 @@ #ifndef __ASSEMBLY__ +/* + * C23 introduces "auto" as a standard way to define type-inferred + * variables, but "auto" has been a (useless) keyword even since K&R C, + * so it has always been "namespace reserved." + * + * Until at some future time we require C23 support, we need the gcc + * extension __auto_type, but there is no reason to put that elsewhere + * in the source code. + */ +#if __STDC_VERSION__ < 202311L +# define auto __auto_type +#endif + /* * Skipped when running bindgen due to a libclang issue; * see https://github.com/rust-lang/rust-bindgen/issues/2244. From 25f1ae942c097b7ae4ce5c2b9c6fefb8e3672b86 Mon Sep 17 00:00:00 2001 From: Paolo Abeni Date: Fri, 12 Dec 2025 13:54:03 +0100 Subject: [PATCH 1979/3902] mptcp: fallback earlier on simult connection commit 71154bbe49423128c1c8577b6576de1ed6836830 upstream. Syzkaller reports a simult-connect race leading to inconsistent fallback status: WARNING: CPU: 3 PID: 33 at net/mptcp/subflow.c:1515 subflow_data_ready+0x40b/0x7c0 net/mptcp/subflow.c:1515 Modules linked in: CPU: 3 UID: 0 PID: 33 Comm: ksoftirqd/3 Not tainted syzkaller #0 PREEMPT(full) Hardware name: QEMU Standard PC (Q35 + ICH9, 2009), BIOS 1.16.3-debian-1.16.3-2~bpo12+1 04/01/2014 RIP: 0010:subflow_data_ready+0x40b/0x7c0 net/mptcp/subflow.c:1515 Code: 89 ee e8 78 61 3c f6 40 84 ed 75 21 e8 8e 66 3c f6 44 89 fe bf 07 00 00 00 e8 c1 61 3c f6 41 83 ff 07 74 09 e8 76 66 3c f6 90 <0f> 0b 90 e8 6d 66 3c f6 48 89 df e8 e5 ad ff ff 31 ff 89 c5 89 c6 RSP: 0018:ffffc900006cf338 EFLAGS: 00010246 RAX: 0000000000000000 RBX: ffff888031acd100 RCX: ffffffff8b7f2abf RDX: ffff88801e6ea440 RSI: ffffffff8b7f2aca RDI: 0000000000000005 RBP: 0000000000000000 R08: 0000000000000005 R09: 0000000000000007 R10: 0000000000000004 R11: 0000000000002c10 R12: ffff88802ba69900 R13: 1ffff920000d9e67 R14: ffff888046f81800 R15: 0000000000000004 FS: 0000000000000000(0000) GS:ffff8880d69bc000(0000) knlGS:0000000000000000 CS: 0010 DS: 0000 ES: 0000 CR0: 0000000080050033 CR2: 0000560fc0ca1670 CR3: 0000000032c3a000 CR4: 0000000000352ef0 Call Trace: tcp_data_queue+0x13b0/0x4f90 net/ipv4/tcp_input.c:5197 tcp_rcv_state_process+0xfdf/0x4ec0 net/ipv4/tcp_input.c:6922 tcp_v6_do_rcv+0x492/0x1740 net/ipv6/tcp_ipv6.c:1672 tcp_v6_rcv+0x2976/0x41e0 net/ipv6/tcp_ipv6.c:1918 ip6_protocol_deliver_rcu+0x188/0x1520 net/ipv6/ip6_input.c:438 ip6_input_finish+0x1e4/0x4b0 net/ipv6/ip6_input.c:489 NF_HOOK include/linux/netfilter.h:318 [inline] NF_HOOK include/linux/netfilter.h:312 [inline] ip6_input+0x105/0x2f0 net/ipv6/ip6_input.c:500 dst_input include/net/dst.h:471 [inline] ip6_rcv_finish net/ipv6/ip6_input.c:79 [inline] NF_HOOK include/linux/netfilter.h:318 [inline] NF_HOOK include/linux/netfilter.h:312 [inline] ipv6_rcv+0x264/0x650 net/ipv6/ip6_input.c:311 __netif_receive_skb_one_core+0x12d/0x1e0 net/core/dev.c:5979 __netif_receive_skb+0x1d/0x160 net/core/dev.c:6092 process_backlog+0x442/0x15e0 net/core/dev.c:6444 __napi_poll.constprop.0+0xba/0x550 net/core/dev.c:7494 napi_poll net/core/dev.c:7557 [inline] net_rx_action+0xa9f/0xfe0 net/core/dev.c:7684 handle_softirqs+0x216/0x8e0 kernel/softirq.c:579 run_ksoftirqd kernel/softirq.c:968 [inline] run_ksoftirqd+0x3a/0x60 kernel/softirq.c:960 smpboot_thread_fn+0x3f7/0xae0 kernel/smpboot.c:160 kthread+0x3c2/0x780 kernel/kthread.c:463 ret_from_fork+0x5d7/0x6f0 arch/x86/kernel/process.c:148 ret_from_fork_asm+0x1a/0x30 arch/x86/entry/entry_64.S:245 The TCP subflow can process the simult-connect syn-ack packet after transitioning to TCP_FIN1 state, bypassing the MPTCP fallback check, as the sk_state_change() callback is not invoked for * -> FIN_WAIT1 transitions. That will move the msk socket to an inconsistent status and the next incoming data will hit the reported splat. Close the race moving the simult-fallback check at the earliest possible stage - that is at syn-ack generation time. About the fixes tags: [2] was supposed to also fix this issue introduced by [3]. [1] is required as a dependence: it was not explicitly marked as a fix, but it is one and it has already been backported before [3]. In other words, this commit should be backported up to [3], including [2] and [1] if that's not already there. Fixes: 23e89e8ee7be ("tcp: Don't drop SYN+ACK for simultaneous connect().") [1] Fixes: 4fd19a307016 ("mptcp: fix inconsistent state on fastopen race") [2] Fixes: 1e777f39b4d7 ("mptcp: add MSG_FASTOPEN sendmsg flag support") [3] Cc: stable@vger.kernel.org Reported-by: syzbot+0ff6b771b4f7a5bce83b@syzkaller.appspotmail.com Closes: https://github.com/multipath-tcp/mptcp_net-next/issues/586 Signed-off-by: Paolo Abeni Reviewed-by: Matthieu Baerts (NGI0) Signed-off-by: Matthieu Baerts (NGI0) Link: https://patch.msgid.link/20251212-net-mptcp-subflow_data_ready-warn-v1-1-d1f9fd1c36c8@kernel.org Signed-off-by: Paolo Abeni Signed-off-by: Greg Kroah-Hartman --- net/mptcp/options.c | 10 ++++++++++ net/mptcp/protocol.h | 6 ++---- net/mptcp/subflow.c | 6 ------ 3 files changed, 12 insertions(+), 10 deletions(-) diff --git a/net/mptcp/options.c b/net/mptcp/options.c index f24ae7d40e883a..43df4293f58bfb 100644 --- a/net/mptcp/options.c +++ b/net/mptcp/options.c @@ -408,6 +408,16 @@ bool mptcp_syn_options(struct sock *sk, const struct sk_buff *skb, */ subflow->snd_isn = TCP_SKB_CB(skb)->end_seq; if (subflow->request_mptcp) { + if (unlikely(subflow_simultaneous_connect(sk))) { + WARN_ON_ONCE(!mptcp_try_fallback(sk, MPTCP_MIB_SIMULTCONNFALLBACK)); + + /* Ensure mptcp_finish_connect() will not process the + * MPC handshake. + */ + subflow->request_mptcp = 0; + return false; + } + opts->suboptions = OPTION_MPTCP_MPC_SYN; opts->csum_reqd = mptcp_is_checksum_enabled(sock_net(sk)); opts->allow_join_id0 = mptcp_allow_join_id0(sock_net(sk)); diff --git a/net/mptcp/protocol.h b/net/mptcp/protocol.h index 6ca97096607c39..0e8b0a650108cf 100644 --- a/net/mptcp/protocol.h +++ b/net/mptcp/protocol.h @@ -1293,10 +1293,8 @@ static inline bool subflow_simultaneous_connect(struct sock *sk) { struct mptcp_subflow_context *subflow = mptcp_subflow_ctx(sk); - return (1 << sk->sk_state) & - (TCPF_ESTABLISHED | TCPF_FIN_WAIT1 | TCPF_FIN_WAIT2 | TCPF_CLOSING) && - is_active_ssk(subflow) && - !subflow->conn_finished; + /* Note that the sk state implies !subflow->conn_finished. */ + return sk->sk_state == TCP_SYN_RECV && is_active_ssk(subflow); } #ifdef CONFIG_SYN_COOKIES diff --git a/net/mptcp/subflow.c b/net/mptcp/subflow.c index af707ce0f62445..a8c281bc53778c 100644 --- a/net/mptcp/subflow.c +++ b/net/mptcp/subflow.c @@ -1856,12 +1856,6 @@ static void subflow_state_change(struct sock *sk) __subflow_state_change(sk); - if (subflow_simultaneous_connect(sk)) { - WARN_ON_ONCE(!mptcp_try_fallback(sk, MPTCP_MIB_SIMULTCONNFALLBACK)); - subflow->conn_finished = 1; - mptcp_propagate_state(parent, sk, subflow, NULL); - } - /* as recvmsg() does not acquire the subflow socket for ssk selection * a fin packet carrying a DSS can be unnoticed if we don't trigger * the data available machinery here. From 234ede2960d56a9f772b2612ec1414176b592b6b Mon Sep 17 00:00:00 2001 From: Jiayuan Chen Date: Thu, 4 Dec 2025 18:59:55 +0000 Subject: [PATCH 1980/3902] mm/kasan: fix incorrect unpoisoning in vrealloc for KASAN commit 007f5da43b3d0ecff972e2616062b8da1f862f5e upstream. Patch series "kasan: vmalloc: Fixes for the percpu allocator and vrealloc", v3. Patches fix two issues related to KASAN and vmalloc. The first one, a KASAN tag mismatch, possibly resulting in a kernel panic, can be observed on systems with a tag-based KASAN enabled and with multiple NUMA nodes. Initially it was only noticed on x86 [1] but later a similar issue was also reported on arm64 [2]. Specifically the problem is related to how vm_structs interact with pcpu_chunks - both when they are allocated, assigned and when pcpu_chunk addresses are derived. When vm_structs are allocated they are unpoisoned, each with a different random tag, if vmalloc support is enabled along the KASAN mode. Later when first pcpu chunk is allocated it gets its 'base_addr' field set to the first allocated vm_struct. With that it inherits that vm_struct's tag. When pcpu_chunk addresses are later derived (by pcpu_chunk_addr(), for example in pcpu_alloc_noprof()) the base_addr field is used and offsets are added to it. If the initial conditions are satisfied then some of the offsets will point into memory allocated with a different vm_struct. So while the lower bits will get accurately derived the tag bits in the top of the pointer won't match the shadow memory contents. The solution (proposed at v2 of the x86 KASAN series [3]) is to unpoison the vm_structs with the same tag when allocating them for the per cpu allocator (in pcpu_get_vm_areas()). The second one reported by syzkaller [4] is related to vrealloc and happens because of random tag generation when unpoisoning memory without allocating new pages. This breaks shadow memory tracking and needs to reuse the existing tag instead of generating a new one. At the same time an inconsistency in used flags is corrected. This patch (of 3): Syzkaller reported a memory out-of-bounds bug [4]. This patch fixes two issues: 1. In vrealloc the KASAN_VMALLOC_VM_ALLOC flag is missing when unpoisoning the extended region. This flag is required to correctly associate the allocation with KASAN's vmalloc tracking. Note: In contrast, vzalloc (via __vmalloc_node_range_noprof) explicitly sets KASAN_VMALLOC_VM_ALLOC and calls kasan_unpoison_vmalloc() with it. vrealloc must behave consistently -- especially when reusing existing vmalloc regions -- to ensure KASAN can track allocations correctly. 2. When vrealloc reuses an existing vmalloc region (without allocating new pages) KASAN generates a new tag, which breaks tag-based memory access tracking. Introduce KASAN_VMALLOC_KEEP_TAG, a new KASAN flag that allows reusing the tag already attached to the pointer, ensuring consistent tag behavior during reallocation. Pass KASAN_VMALLOC_KEEP_TAG and KASAN_VMALLOC_VM_ALLOC to the kasan_unpoison_vmalloc inside vrealloc_node_align_noprof(). Link: https://lkml.kernel.org/r/cover.1765978969.git.m.wieczorretman@pm.me Link: https://lkml.kernel.org/r/38dece0a4074c43e48150d1e242f8242c73bf1a5.1764874575.git.m.wieczorretman@pm.me Link: https://lore.kernel.org/all/e7e04692866d02e6d3b32bb43b998e5d17092ba4.1738686764.git.maciej.wieczor-retman@intel.com/ [1] Link: https://lore.kernel.org/all/aMUrW1Znp1GEj7St@MiWiFi-R3L-srv/ [2] Link: https://lore.kernel.org/all/CAPAsAGxDRv_uFeMYu9TwhBVWHCCtkSxoWY4xmFB_vowMbi8raw@mail.gmail.com/ [3] Link: https://syzkaller.appspot.com/bug?extid=997752115a851cb0cf36 [4] Fixes: a0309faf1cb0 ("mm: vmalloc: support more granular vrealloc() sizing") Signed-off-by: Jiayuan Chen Co-developed-by: Maciej Wieczor-Retman Signed-off-by: Maciej Wieczor-Retman Reported-by: syzbot+997752115a851cb0cf36@syzkaller.appspotmail.com Closes: https://lore.kernel.org/all/68e243a2.050a0220.1696c6.007d.GAE@google.com/T/ Reviewed-by: Andrey Konovalov Cc: Alexander Potapenko Cc: Andrey Ryabinin Cc: Danilo Krummrich Cc: Dmitriy Vyukov Cc: Kees Cook Cc: Marco Elver Cc: "Uladzislau Rezki (Sony)" Cc: Vincenzo Frascino Cc: Signed-off-by: Andrew Morton Signed-off-by: Greg Kroah-Hartman --- include/linux/kasan.h | 1 + mm/kasan/hw_tags.c | 2 +- mm/kasan/shadow.c | 4 +++- mm/vmalloc.c | 4 +++- 4 files changed, 8 insertions(+), 3 deletions(-) diff --git a/include/linux/kasan.h b/include/linux/kasan.h index d12e1a5f5a9af8..6d7972bb390cac 100644 --- a/include/linux/kasan.h +++ b/include/linux/kasan.h @@ -28,6 +28,7 @@ typedef unsigned int __bitwise kasan_vmalloc_flags_t; #define KASAN_VMALLOC_INIT ((__force kasan_vmalloc_flags_t)0x01u) #define KASAN_VMALLOC_VM_ALLOC ((__force kasan_vmalloc_flags_t)0x02u) #define KASAN_VMALLOC_PROT_NORMAL ((__force kasan_vmalloc_flags_t)0x04u) +#define KASAN_VMALLOC_KEEP_TAG ((__force kasan_vmalloc_flags_t)0x08u) #define KASAN_VMALLOC_PAGE_RANGE 0x1 /* Apply exsiting page range */ #define KASAN_VMALLOC_TLB_FLUSH 0x2 /* TLB flush */ diff --git a/mm/kasan/hw_tags.c b/mm/kasan/hw_tags.c index 1c373cc4b3fa5a..cbef5e450954e3 100644 --- a/mm/kasan/hw_tags.c +++ b/mm/kasan/hw_tags.c @@ -361,7 +361,7 @@ void *__kasan_unpoison_vmalloc(const void *start, unsigned long size, return (void *)start; } - tag = kasan_random_tag(); + tag = (flags & KASAN_VMALLOC_KEEP_TAG) ? get_tag(start) : kasan_random_tag(); start = set_tag(start, tag); /* Unpoison and initialize memory up to size. */ diff --git a/mm/kasan/shadow.c b/mm/kasan/shadow.c index 5d2a876035d656..5e47ae7fdd590e 100644 --- a/mm/kasan/shadow.c +++ b/mm/kasan/shadow.c @@ -648,7 +648,9 @@ void *__kasan_unpoison_vmalloc(const void *start, unsigned long size, !(flags & KASAN_VMALLOC_PROT_NORMAL)) return (void *)start; - start = set_tag(start, kasan_random_tag()); + if (unlikely(!(flags & KASAN_VMALLOC_KEEP_TAG))) + start = set_tag(start, kasan_random_tag()); + kasan_unpoison(start, size, false); return (void *)start; } diff --git a/mm/vmalloc.c b/mm/vmalloc.c index 798b2ed21e4605..22a73a087135b2 100644 --- a/mm/vmalloc.c +++ b/mm/vmalloc.c @@ -4176,7 +4176,9 @@ void *vrealloc_node_align_noprof(const void *p, size_t size, unsigned long align */ if (size <= alloced_size) { kasan_unpoison_vmalloc(p + old_size, size - old_size, - KASAN_VMALLOC_PROT_NORMAL); + KASAN_VMALLOC_PROT_NORMAL | + KASAN_VMALLOC_VM_ALLOC | + KASAN_VMALLOC_KEEP_TAG); /* * No need to zero memory here, as unused memory will have * already been zeroed at initial allocation time or during From 9548f154f79cefdf3fba9601c92e2237d4f88091 Mon Sep 17 00:00:00 2001 From: Maciej Wieczor-Retman Date: Thu, 4 Dec 2025 19:00:04 +0000 Subject: [PATCH 1981/3902] kasan: refactor pcpu kasan vmalloc unpoison commit 6f13db031e27e88213381039032a9cc061578ea6 upstream. A KASAN tag mismatch, possibly causing a kernel panic, can be observed on systems with a tag-based KASAN enabled and with multiple NUMA nodes. It was reported on arm64 and reproduced on x86. It can be explained in the following points: 1. There can be more than one virtual memory chunk. 2. Chunk's base address has a tag. 3. The base address points at the first chunk and thus inherits the tag of the first chunk. 4. The subsequent chunks will be accessed with the tag from the first chunk. 5. Thus, the subsequent chunks need to have their tag set to match that of the first chunk. Refactor code by reusing __kasan_unpoison_vmalloc in a new helper in preparation for the actual fix. Link: https://lkml.kernel.org/r/eb61d93b907e262eefcaa130261a08bcb6c5ce51.1764874575.git.m.wieczorretman@pm.me Fixes: 1d96320f8d53 ("kasan, vmalloc: add vmalloc tagging for SW_TAGS") Signed-off-by: Maciej Wieczor-Retman Reviewed-by: Andrey Konovalov Cc: Alexander Potapenko Cc: Andrey Ryabinin Cc: Danilo Krummrich Cc: Dmitriy Vyukov Cc: Jiayuan Chen Cc: Kees Cook Cc: Marco Elver Cc: "Uladzislau Rezki (Sony)" Cc: Vincenzo Frascino Cc: [6.1+] Signed-off-by: Andrew Morton Signed-off-by: Greg Kroah-Hartman --- include/linux/kasan.h | 15 +++++++++++++++ mm/kasan/common.c | 17 +++++++++++++++++ mm/vmalloc.c | 4 +--- 3 files changed, 33 insertions(+), 3 deletions(-) diff --git a/include/linux/kasan.h b/include/linux/kasan.h index 6d7972bb390cac..cde493cb7702d2 100644 --- a/include/linux/kasan.h +++ b/include/linux/kasan.h @@ -615,6 +615,16 @@ static __always_inline void kasan_poison_vmalloc(const void *start, __kasan_poison_vmalloc(start, size); } +void __kasan_unpoison_vmap_areas(struct vm_struct **vms, int nr_vms, + kasan_vmalloc_flags_t flags); +static __always_inline void +kasan_unpoison_vmap_areas(struct vm_struct **vms, int nr_vms, + kasan_vmalloc_flags_t flags) +{ + if (kasan_enabled()) + __kasan_unpoison_vmap_areas(vms, nr_vms, flags); +} + #else /* CONFIG_KASAN_VMALLOC */ static inline void kasan_populate_early_vm_area_shadow(void *start, @@ -639,6 +649,11 @@ static inline void *kasan_unpoison_vmalloc(const void *start, static inline void kasan_poison_vmalloc(const void *start, unsigned long size) { } +static __always_inline void +kasan_unpoison_vmap_areas(struct vm_struct **vms, int nr_vms, + kasan_vmalloc_flags_t flags) +{ } + #endif /* CONFIG_KASAN_VMALLOC */ #if (defined(CONFIG_KASAN_GENERIC) || defined(CONFIG_KASAN_SW_TAGS)) && \ diff --git a/mm/kasan/common.c b/mm/kasan/common.c index d4c14359feaf9d..1ed6289d471aa0 100644 --- a/mm/kasan/common.c +++ b/mm/kasan/common.c @@ -28,6 +28,7 @@ #include #include #include +#include #include "kasan.h" #include "../slab.h" @@ -582,3 +583,19 @@ bool __kasan_check_byte(const void *address, unsigned long ip) } return true; } + +#ifdef CONFIG_KASAN_VMALLOC +void __kasan_unpoison_vmap_areas(struct vm_struct **vms, int nr_vms, + kasan_vmalloc_flags_t flags) +{ + unsigned long size; + void *addr; + int area; + + for (area = 0 ; area < nr_vms ; area++) { + size = vms[area]->size; + addr = vms[area]->addr; + vms[area]->addr = __kasan_unpoison_vmalloc(addr, size, flags); + } +} +#endif diff --git a/mm/vmalloc.c b/mm/vmalloc.c index 22a73a087135b2..33e705ccafbaa4 100644 --- a/mm/vmalloc.c +++ b/mm/vmalloc.c @@ -4872,9 +4872,7 @@ struct vm_struct **pcpu_get_vm_areas(const unsigned long *offsets, * With hardware tag-based KASAN, marking is skipped for * non-VM_ALLOC mappings, see __kasan_unpoison_vmalloc(). */ - for (area = 0; area < nr_vms; area++) - vms[area]->addr = kasan_unpoison_vmalloc(vms[area]->addr, - vms[area]->size, KASAN_VMALLOC_PROT_NORMAL); + kasan_unpoison_vmap_areas(vms, nr_vms, KASAN_VMALLOC_PROT_NORMAL); kfree(vas); return vms; From d16a38f3d908b75c5c20c93b8ae9d831cc73a4c9 Mon Sep 17 00:00:00 2001 From: Maciej Wieczor-Retman Date: Thu, 4 Dec 2025 19:00:11 +0000 Subject: [PATCH 1982/3902] kasan: unpoison vms[area] addresses with a common tag commit 6a0e5b333842cf65d6f4e4f0a2a4386504802515 upstream. A KASAN tag mismatch, possibly causing a kernel panic, can be observed on systems with a tag-based KASAN enabled and with multiple NUMA nodes. It was reported on arm64 and reproduced on x86. It can be explained in the following points: 1. There can be more than one virtual memory chunk. 2. Chunk's base address has a tag. 3. The base address points at the first chunk and thus inherits the tag of the first chunk. 4. The subsequent chunks will be accessed with the tag from the first chunk. 5. Thus, the subsequent chunks need to have their tag set to match that of the first chunk. Use the new vmalloc flag that disables random tag assignment in __kasan_unpoison_vmalloc() - pass the same random tag to all the vm_structs by tagging the pointers before they go inside __kasan_unpoison_vmalloc(). Assigning a common tag resolves the pcpu chunk address mismatch. [akpm@linux-foundation.org: use WARN_ON_ONCE(), per Andrey] Link: https://lkml.kernel.org/r/CA+fCnZeuGdKSEm11oGT6FS71_vGq1vjq-xY36kxVdFvwmag2ZQ@mail.gmail.com [maciej.wieczor-retman@intel.com: remove unneeded pr_warn()] Link: https://lkml.kernel.org/r/919897daaaa3c982a27762a2ee038769ad033991.1764945396.git.m.wieczorretman@pm.me Link: https://lkml.kernel.org/r/873821114a9f722ffb5d6702b94782e902883fdf.1764874575.git.m.wieczorretman@pm.me Fixes: 1d96320f8d53 ("kasan, vmalloc: add vmalloc tagging for SW_TAGS") Signed-off-by: Maciej Wieczor-Retman Reviewed-by: Andrey Konovalov Cc: Alexander Potapenko Cc: Andrey Ryabinin Cc: Danilo Krummrich Cc: Dmitriy Vyukov Cc: Jiayuan Chen Cc: Kees Cook Cc: Marco Elver Cc: "Uladzislau Rezki (Sony)" Cc: Vincenzo Frascino Cc: [6.1+] Signed-off-by: Andrew Morton Signed-off-by: Greg Kroah-Hartman --- mm/kasan/common.c | 21 ++++++++++++++++++--- 1 file changed, 18 insertions(+), 3 deletions(-) diff --git a/mm/kasan/common.c b/mm/kasan/common.c index 1ed6289d471aa0..589be3d8673574 100644 --- a/mm/kasan/common.c +++ b/mm/kasan/common.c @@ -591,11 +591,26 @@ void __kasan_unpoison_vmap_areas(struct vm_struct **vms, int nr_vms, unsigned long size; void *addr; int area; + u8 tag; + + /* + * If KASAN_VMALLOC_KEEP_TAG was set at this point, all vms[] pointers + * would be unpoisoned with the KASAN_TAG_KERNEL which would disable + * KASAN checks down the line. + */ + if (WARN_ON_ONCE(flags & KASAN_VMALLOC_KEEP_TAG)) + return; + + size = vms[0]->size; + addr = vms[0]->addr; + vms[0]->addr = __kasan_unpoison_vmalloc(addr, size, flags); + tag = get_tag(vms[0]->addr); - for (area = 0 ; area < nr_vms ; area++) { + for (area = 1 ; area < nr_vms ; area++) { size = vms[area]->size; - addr = vms[area]->addr; - vms[area]->addr = __kasan_unpoison_vmalloc(addr, size, flags); + addr = set_tag(vms[area]->addr, tag); + vms[area]->addr = + __kasan_unpoison_vmalloc(addr, size, flags | KASAN_VMALLOC_KEEP_TAG); } } #endif From 282ac3cf9bc79c75fd3196be2caba06e033e403d Mon Sep 17 00:00:00 2001 From: Pingfan Liu Date: Tue, 16 Dec 2025 09:48:51 +0800 Subject: [PATCH 1983/3902] kernel/kexec: change the prototype of kimage_map_segment() commit fe55ea85939efcbf0e6baa234f0d70acb79e7b58 upstream. The kexec segment index will be required to extract the corresponding information for that segment in kimage_map_segment(). Additionally, kexec_segment already holds the kexec relocation destination address and size. Therefore, the prototype of kimage_map_segment() can be changed. Link: https://lkml.kernel.org/r/20251216014852.8737-1-piliu@redhat.com Fixes: 07d24902977e ("kexec: enable CMA based contiguous allocation") Signed-off-by: Pingfan Liu Acked-by: Baoquan He Cc: Mimi Zohar Cc: Roberto Sassu Cc: Alexander Graf Cc: Steven Chen Cc: Signed-off-by: Andrew Morton Signed-off-by: Greg Kroah-Hartman --- include/linux/kexec.h | 4 ++-- kernel/kexec_core.c | 9 ++++++--- security/integrity/ima/ima_kexec.c | 4 +--- 3 files changed, 9 insertions(+), 8 deletions(-) diff --git a/include/linux/kexec.h b/include/linux/kexec.h index ff7e231b0485a9..8a22bc9b8c6c85 100644 --- a/include/linux/kexec.h +++ b/include/linux/kexec.h @@ -530,7 +530,7 @@ extern bool kexec_file_dbg_print; #define kexec_dprintk(fmt, arg...) \ do { if (kexec_file_dbg_print) pr_info(fmt, ##arg); } while (0) -extern void *kimage_map_segment(struct kimage *image, unsigned long addr, unsigned long size); +extern void *kimage_map_segment(struct kimage *image, int idx); extern void kimage_unmap_segment(void *buffer); #else /* !CONFIG_KEXEC_CORE */ struct pt_regs; @@ -540,7 +540,7 @@ static inline void __crash_kexec(struct pt_regs *regs) { } static inline void crash_kexec(struct pt_regs *regs) { } static inline int kexec_should_crash(struct task_struct *p) { return 0; } static inline int kexec_crash_loaded(void) { return 0; } -static inline void *kimage_map_segment(struct kimage *image, unsigned long addr, unsigned long size) +static inline void *kimage_map_segment(struct kimage *image, int idx) { return NULL; } static inline void kimage_unmap_segment(void *buffer) { } #define kexec_in_progress false diff --git a/kernel/kexec_core.c b/kernel/kexec_core.c index fa00b239c5d9ad..9a1966207041c6 100644 --- a/kernel/kexec_core.c +++ b/kernel/kexec_core.c @@ -960,17 +960,20 @@ int kimage_load_segment(struct kimage *image, int idx) return result; } -void *kimage_map_segment(struct kimage *image, - unsigned long addr, unsigned long size) +void *kimage_map_segment(struct kimage *image, int idx) { + unsigned long addr, size, eaddr; unsigned long src_page_addr, dest_page_addr = 0; - unsigned long eaddr = addr + size; kimage_entry_t *ptr, entry; struct page **src_pages; unsigned int npages; void *vaddr = NULL; int i; + addr = image->segment[idx].mem; + size = image->segment[idx].memsz; + eaddr = addr + size; + /* * Collect the source pages and map them in a contiguous VA range. */ diff --git a/security/integrity/ima/ima_kexec.c b/security/integrity/ima/ima_kexec.c index 7362f68f2d8b17..5beb69edd12fd2 100644 --- a/security/integrity/ima/ima_kexec.c +++ b/security/integrity/ima/ima_kexec.c @@ -250,9 +250,7 @@ void ima_kexec_post_load(struct kimage *image) if (!image->ima_buffer_addr) return; - ima_kexec_buffer = kimage_map_segment(image, - image->ima_buffer_addr, - image->ima_buffer_size); + ima_kexec_buffer = kimage_map_segment(image, image->ima_segment_index); if (!ima_kexec_buffer) { pr_err("Could not map measurements buffer.\n"); return; From a843e4155c83211c55b1b6cc17eab27a6a2c5b6f Mon Sep 17 00:00:00 2001 From: Pingfan Liu Date: Tue, 16 Dec 2025 09:48:52 +0800 Subject: [PATCH 1984/3902] kernel/kexec: fix IMA when allocation happens in CMA area commit a3785ae5d334bb71d47a593d54c686a03fb9d136 upstream. *** Bug description *** When I tested kexec with the latest kernel, I ran into the following warning: [ 40.712410] ------------[ cut here ]------------ [ 40.712576] WARNING: CPU: 2 PID: 1562 at kernel/kexec_core.c:1001 kimage_map_segment+0x144/0x198 [...] [ 40.816047] Call trace: [ 40.818498] kimage_map_segment+0x144/0x198 (P) [ 40.823221] ima_kexec_post_load+0x58/0xc0 [ 40.827246] __do_sys_kexec_file_load+0x29c/0x368 [...] [ 40.855423] ---[ end trace 0000000000000000 ]--- *** How to reproduce *** This bug is only triggered when the kexec target address is allocated in the CMA area. If no CMA area is reserved in the kernel, use the "cma=" option in the kernel command line to reserve one. *** Root cause *** The commit 07d24902977e ("kexec: enable CMA based contiguous allocation") allocates the kexec target address directly on the CMA area to avoid copying during the jump. In this case, there is no IND_SOURCE for the kexec segment. But the current implementation of kimage_map_segment() assumes that IND_SOURCE pages exist and map them into a contiguous virtual address by vmap(). *** Solution *** If IMA segment is allocated in the CMA area, use its page_address() directly. Link: https://lkml.kernel.org/r/20251216014852.8737-2-piliu@redhat.com Fixes: 07d24902977e ("kexec: enable CMA based contiguous allocation") Signed-off-by: Pingfan Liu Acked-by: Baoquan He Cc: Alexander Graf Cc: Steven Chen Cc: Mimi Zohar Cc: Roberto Sassu Cc: Signed-off-by: Andrew Morton Signed-off-by: Greg Kroah-Hartman --- kernel/kexec_core.c | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/kernel/kexec_core.c b/kernel/kexec_core.c index 9a1966207041c6..08ae3c64caca50 100644 --- a/kernel/kexec_core.c +++ b/kernel/kexec_core.c @@ -967,13 +967,17 @@ void *kimage_map_segment(struct kimage *image, int idx) kimage_entry_t *ptr, entry; struct page **src_pages; unsigned int npages; + struct page *cma; void *vaddr = NULL; int i; + cma = image->segment_cma[idx]; + if (cma) + return page_address(cma); + addr = image->segment[idx].mem; size = image->segment[idx].memsz; eaddr = addr + size; - /* * Collect the source pages and map them in a contiguous VA range. */ @@ -1014,7 +1018,8 @@ void *kimage_map_segment(struct kimage *image, int idx) void kimage_unmap_segment(void *segment_buffer) { - vunmap(segment_buffer); + if (is_vmalloc_addr(segment_buffer)) + vunmap(segment_buffer); } struct kexec_load_limit { From 7da6f40d2662a222dac6bf3ff457d7a4b9d487a3 Mon Sep 17 00:00:00 2001 From: NeilBrown Date: Sat, 22 Nov 2025 12:00:36 +1100 Subject: [PATCH 1985/3902] lockd: fix vfs_test_lock() calls commit a49a2a1baa0c553c3548a1c414b6a3c005a8deba upstream. Usage of vfs_test_lock() is somewhat confused. Documentation suggests it is given a "lock" but this is not the case. It is given a struct file_lock which contains some details of the sort of lock it should be looking for. In particular passing a "file_lock" containing fl_lmops or fl_ops is meaningless and possibly confusing. This is particularly problematic in lockd. nlmsvc_testlock() receives an initialised "file_lock" from xdr-decode, including manager ops and an owner. It then mistakenly passes this to vfs_test_lock() which might replace the owner and the ops. This can lead to confusion when freeing the lock. The primary role of the 'struct file_lock' passed to vfs_test_lock() is to report a conflicting lock that was found, so it makes more sense for nlmsvc_testlock() to pass "conflock", which it uses for returning the conflicting lock. With this change, freeing of the lock is not confused and code in __nlm4svc_proc_test() and __nlmsvc_proc_test() can be simplified. Documentation for vfs_test_lock() is improved to reflect its real purpose, and a WARN_ON_ONCE() is added to avoid a similar problem in the future. Reported-by: Olga Kornievskaia Closes: https://lore.kernel.org/all/20251021130506.45065-1-okorniev@redhat.com Signed-off-by: NeilBrown Fixes: 20fa19027286 ("nfs: add export operations") Cc: stable@vger.kernel.org Reviewed-by: Jeff Layton Signed-off-by: Chuck Lever Signed-off-by: Greg Kroah-Hartman --- fs/lockd/svc4proc.c | 4 +--- fs/lockd/svclock.c | 21 ++++++++++++--------- fs/lockd/svcproc.c | 5 +---- fs/locks.c | 12 ++++++++++-- 4 files changed, 24 insertions(+), 18 deletions(-) diff --git a/fs/lockd/svc4proc.c b/fs/lockd/svc4proc.c index 109e5caae8c704..4b6f18d977343d 100644 --- a/fs/lockd/svc4proc.c +++ b/fs/lockd/svc4proc.c @@ -97,7 +97,6 @@ __nlm4svc_proc_test(struct svc_rqst *rqstp, struct nlm_res *resp) struct nlm_args *argp = rqstp->rq_argp; struct nlm_host *host; struct nlm_file *file; - struct nlm_lockowner *test_owner; __be32 rc = rpc_success; dprintk("lockd: TEST4 called\n"); @@ -107,7 +106,6 @@ __nlm4svc_proc_test(struct svc_rqst *rqstp, struct nlm_res *resp) if ((resp->status = nlm4svc_retrieve_args(rqstp, argp, &host, &file))) return resp->status == nlm_drop_reply ? rpc_drop_reply :rpc_success; - test_owner = argp->lock.fl.c.flc_owner; /* Now check for conflicting locks */ resp->status = nlmsvc_testlock(rqstp, file, host, &argp->lock, &resp->lock); @@ -116,7 +114,7 @@ __nlm4svc_proc_test(struct svc_rqst *rqstp, struct nlm_res *resp) else dprintk("lockd: TEST4 status %d\n", ntohl(resp->status)); - nlmsvc_put_lockowner(test_owner); + nlmsvc_release_lockowner(&argp->lock); nlmsvc_release_host(host); nlm_release_file(file); return rc; diff --git a/fs/lockd/svclock.c b/fs/lockd/svclock.c index a31dc9588eb807..d66e8285159996 100644 --- a/fs/lockd/svclock.c +++ b/fs/lockd/svclock.c @@ -627,7 +627,13 @@ nlmsvc_testlock(struct svc_rqst *rqstp, struct nlm_file *file, } mode = lock_to_openmode(&lock->fl); - error = vfs_test_lock(file->f_file[mode], &lock->fl); + locks_init_lock(&conflock->fl); + /* vfs_test_lock only uses start, end, and owner, but tests flc_file */ + conflock->fl.c.flc_file = lock->fl.c.flc_file; + conflock->fl.fl_start = lock->fl.fl_start; + conflock->fl.fl_end = lock->fl.fl_end; + conflock->fl.c.flc_owner = lock->fl.c.flc_owner; + error = vfs_test_lock(file->f_file[mode], &conflock->fl); if (error) { /* We can't currently deal with deferred test requests */ if (error == FILE_LOCK_DEFERRED) @@ -637,22 +643,19 @@ nlmsvc_testlock(struct svc_rqst *rqstp, struct nlm_file *file, goto out; } - if (lock->fl.c.flc_type == F_UNLCK) { + if (conflock->fl.c.flc_type == F_UNLCK) { ret = nlm_granted; goto out; } dprintk("lockd: conflicting lock(ty=%d, %Ld-%Ld)\n", - lock->fl.c.flc_type, (long long)lock->fl.fl_start, - (long long)lock->fl.fl_end); + conflock->fl.c.flc_type, (long long)conflock->fl.fl_start, + (long long)conflock->fl.fl_end); conflock->caller = "somehost"; /* FIXME */ conflock->len = strlen(conflock->caller); conflock->oh.len = 0; /* don't return OH info */ - conflock->svid = lock->fl.c.flc_pid; - conflock->fl.c.flc_type = lock->fl.c.flc_type; - conflock->fl.fl_start = lock->fl.fl_start; - conflock->fl.fl_end = lock->fl.fl_end; - locks_release_private(&lock->fl); + conflock->svid = conflock->fl.c.flc_pid; + locks_release_private(&conflock->fl); ret = nlm_lck_denied; out: diff --git a/fs/lockd/svcproc.c b/fs/lockd/svcproc.c index f53d5177f26732..5817ef272332d9 100644 --- a/fs/lockd/svcproc.c +++ b/fs/lockd/svcproc.c @@ -117,7 +117,6 @@ __nlmsvc_proc_test(struct svc_rqst *rqstp, struct nlm_res *resp) struct nlm_args *argp = rqstp->rq_argp; struct nlm_host *host; struct nlm_file *file; - struct nlm_lockowner *test_owner; __be32 rc = rpc_success; dprintk("lockd: TEST called\n"); @@ -127,8 +126,6 @@ __nlmsvc_proc_test(struct svc_rqst *rqstp, struct nlm_res *resp) if ((resp->status = nlmsvc_retrieve_args(rqstp, argp, &host, &file))) return resp->status == nlm_drop_reply ? rpc_drop_reply :rpc_success; - test_owner = argp->lock.fl.c.flc_owner; - /* Now check for conflicting locks */ resp->status = cast_status(nlmsvc_testlock(rqstp, file, host, &argp->lock, &resp->lock)); @@ -138,7 +135,7 @@ __nlmsvc_proc_test(struct svc_rqst *rqstp, struct nlm_res *resp) dprintk("lockd: TEST status %d vers %d\n", ntohl(resp->status), rqstp->rq_vers); - nlmsvc_put_lockowner(test_owner); + nlmsvc_release_lockowner(&argp->lock); nlmsvc_release_host(host); nlm_release_file(file); return rc; diff --git a/fs/locks.c b/fs/locks.c index 04a3f0e2072461..bf5e0d05a02691 100644 --- a/fs/locks.c +++ b/fs/locks.c @@ -2185,13 +2185,21 @@ SYSCALL_DEFINE2(flock, unsigned int, fd, unsigned int, cmd) /** * vfs_test_lock - test file byte range lock * @filp: The file to test lock for - * @fl: The lock to test; also used to hold result + * @fl: The byte-range in the file to test; also used to hold result * + * On entry, @fl does not contain a lock, but identifies a range (fl_start, fl_end) + * in the file (c.flc_file), and an owner (c.flc_owner) for whom existing locks + * should be ignored. c.flc_type and c.flc_flags are ignored. + * Both fl_lmops and fl_ops in @fl must be NULL. * Returns -ERRNO on failure. Indicates presence of conflicting lock by - * setting conf->fl_type to something other than F_UNLCK. + * setting fl->fl_type to something other than F_UNLCK. + * + * If vfs_test_lock() does find a lock and return it, the caller must + * use locks_free_lock() or locks_release_private() on the returned lock. */ int vfs_test_lock(struct file *filp, struct file_lock *fl) { + WARN_ON_ONCE(fl->fl_ops || fl->fl_lmops); WARN_ON_ONCE(filp != fl->c.flc_file); if (filp->f_op->lock) return filp->f_op->lock(filp, F_GETLK, fl); From fcbe159c90603b0ffb9c214b002edc3f198354c1 Mon Sep 17 00:00:00 2001 From: "Matthew Wilcox (Oracle)" Date: Fri, 28 Nov 2025 16:18:32 +0000 Subject: [PATCH 1986/3902] idr: fix idr_alloc() returning an ID out of range MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit commit c6e8e595a0798ad67da0f7bebaf69c31ef70dfff upstream. If you use an IDR with a non-zero base, and specify a range that lies entirely below the base, 'max - base' becomes very large and idr_get_free() can return an ID that lies outside of the requested range. Link: https://lkml.kernel.org/r/20251128161853.3200058-1-willy@infradead.org Fixes: 6ce711f27500 ("idr: Make 1-based IDRs more efficient") Signed-off-by: Matthew Wilcox (Oracle) Reported-by: Jan Sokolowski Reported-by: Koen Koning Reported-by: Peter Senna Tschudin Closes: https://gitlab.freedesktop.org/drm/xe/kernel/-/issues/6449 Reviewed-by: Christian König Cc: Signed-off-by: Andrew Morton Signed-off-by: Greg Kroah-Hartman --- lib/idr.c | 2 ++ tools/testing/radix-tree/idr-test.c | 21 +++++++++++++++++++++ 2 files changed, 23 insertions(+) diff --git a/lib/idr.c b/lib/idr.c index e2adc457abb4be..457430cff8c5e1 100644 --- a/lib/idr.c +++ b/lib/idr.c @@ -40,6 +40,8 @@ int idr_alloc_u32(struct idr *idr, void *ptr, u32 *nextid, if (WARN_ON_ONCE(!(idr->idr_rt.xa_flags & ROOT_IS_IDR))) idr->idr_rt.xa_flags |= IDR_RT_MARKER; + if (max < base) + return -ENOSPC; id = (id < base) ? 0 : id - base; radix_tree_iter_init(&iter, id); diff --git a/tools/testing/radix-tree/idr-test.c b/tools/testing/radix-tree/idr-test.c index 2f830ff8396cc4..945144e9850724 100644 --- a/tools/testing/radix-tree/idr-test.c +++ b/tools/testing/radix-tree/idr-test.c @@ -57,6 +57,26 @@ void idr_alloc_test(void) idr_destroy(&idr); } +void idr_alloc2_test(void) +{ + int id; + struct idr idr = IDR_INIT_BASE(idr, 1); + + id = idr_alloc(&idr, idr_alloc2_test, 0, 1, GFP_KERNEL); + assert(id == -ENOSPC); + + id = idr_alloc(&idr, idr_alloc2_test, 1, 2, GFP_KERNEL); + assert(id == 1); + + id = idr_alloc(&idr, idr_alloc2_test, 0, 1, GFP_KERNEL); + assert(id == -ENOSPC); + + id = idr_alloc(&idr, idr_alloc2_test, 0, 2, GFP_KERNEL); + assert(id == -ENOSPC); + + idr_destroy(&idr); +} + void idr_replace_test(void) { DEFINE_IDR(idr); @@ -409,6 +429,7 @@ void idr_checks(void) idr_replace_test(); idr_alloc_test(); + idr_alloc2_test(); idr_null_test(); idr_nowait_test(); idr_get_next_test(0); From a794d65b132107a085d165caba33aae1101316a5 Mon Sep 17 00:00:00 2001 From: Alexander Gordeev Date: Fri, 12 Dec 2025 16:14:57 +0100 Subject: [PATCH 1987/3902] mm/page_alloc: change all pageblocks migrate type on coalescing commit 7838a4eb8a1d23160bd3f588ea7f2b8f7c00c55b upstream. When a page is freed it coalesces with a buddy into a higher order page while possible. When the buddy page migrate type differs, it is expected to be updated to match the one of the page being freed. However, only the first pageblock of the buddy page is updated, while the rest of the pageblocks are left unchanged. That causes warnings in later expand() and other code paths (like below), since an inconsistency between migration type of the list containing the page and the page-owned pageblocks migration types is introduced. [ 308.986589] ------------[ cut here ]------------ [ 308.987227] page type is 0, passed migratetype is 1 (nr=256) [ 308.987275] WARNING: CPU: 1 PID: 5224 at mm/page_alloc.c:812 expand+0x23c/0x270 [ 308.987293] Modules linked in: algif_hash(E) af_alg(E) nft_fib_inet(E) nft_fib_ipv4(E) nft_fib_ipv6(E) nft_fib(E) nft_reject_inet(E) nf_reject_ipv4(E) nf_reject_ipv6(E) nft_reject(E) nft_ct(E) nft_chain_nat(E) nf_nat(E) nf_conntrack(E) nf_defrag_ipv6(E) nf_defrag_ipv4(E) nf_tables(E) s390_trng(E) vfio_ccw(E) mdev(E) vfio_iommu_type1(E) vfio(E) sch_fq_codel(E) drm(E) i2c_core(E) drm_panel_orientation_quirks(E) loop(E) nfnetlink(E) vsock_loopback(E) vmw_vsock_virtio_transport_common(E) vsock(E) ctcm(E) fsm(E) diag288_wdt(E) watchdog(E) zfcp(E) scsi_transport_fc(E) ghash_s390(E) prng(E) aes_s390(E) des_generic(E) des_s390(E) libdes(E) sha3_512_s390(E) sha3_256_s390(E) sha_common(E) paes_s390(E) crypto_engine(E) pkey_cca(E) pkey_ep11(E) zcrypt(E) rng_core(E) pkey_pckmo(E) pkey(E) autofs4(E) [ 308.987439] Unloaded tainted modules: hmac_s390(E):2 [ 308.987650] CPU: 1 UID: 0 PID: 5224 Comm: mempig_verify Kdump: loaded Tainted: G E 6.18.0-gcc-bpf-debug #431 PREEMPT [ 308.987657] Tainted: [E]=UNSIGNED_MODULE [ 308.987661] Hardware name: IBM 3906 M04 704 (z/VM 7.3.0) [ 308.987666] Krnl PSW : 0404f00180000000 00000349976fa600 (expand+0x240/0x270) [ 308.987676] R:0 T:1 IO:0 EX:0 Key:0 M:1 W:0 P:0 AS:3 CC:3 PM:0 RI:0 EA:3 [ 308.987682] Krnl GPRS: 0000034980000004 0000000000000005 0000000000000030 000003499a0e6d88 [ 308.987688] 0000000000000005 0000034980000005 000002be803ac000 0000023efe6c8300 [ 308.987692] 0000000000000008 0000034998d57290 000002be00000100 0000023e00000008 [ 308.987696] 0000000000000000 0000000000000000 00000349976fa5fc 000002c99b1eb6f0 [ 308.987708] Krnl Code: 00000349976fa5f0: c020008a02f2 larl %r2,000003499883abd4 00000349976fa5f6: c0e5ffe3f4b5 brasl %r14,0000034997378f60 #00000349976fa5fc: af000000 mc 0,0 >00000349976fa600: a7f4ff4c brc 15,00000349976fa498 00000349976fa604: b9040026 lgr %r2,%r6 00000349976fa608: c0300088317f larl %r3,0000034998800906 00000349976fa60e: c0e5fffdb6e1 brasl %r14,00000349976b13d0 00000349976fa614: af000000 mc 0,0 [ 308.987734] Call Trace: [ 308.987738] [<00000349976fa600>] expand+0x240/0x270 [ 308.987744] ([<00000349976fa5fc>] expand+0x23c/0x270) [ 308.987749] [<00000349976ff95e>] rmqueue_bulk+0x71e/0x940 [ 308.987754] [<00000349976ffd7e>] __rmqueue_pcplist+0x1fe/0x2a0 [ 308.987759] [<0000034997700966>] rmqueue.isra.0+0xb46/0xf40 [ 308.987763] [<0000034997703ec8>] get_page_from_freelist+0x198/0x8d0 [ 308.987768] [<0000034997706fa8>] __alloc_frozen_pages_noprof+0x198/0x400 [ 308.987774] [<00000349977536f8>] alloc_pages_mpol+0xb8/0x220 [ 308.987781] [<0000034997753bf6>] folio_alloc_mpol_noprof+0x26/0xc0 [ 308.987786] [<0000034997753e4c>] vma_alloc_folio_noprof+0x6c/0xa0 [ 308.987791] [<0000034997775b22>] vma_alloc_anon_folio_pmd+0x42/0x240 [ 308.987799] [<000003499777bfea>] __do_huge_pmd_anonymous_page+0x3a/0x210 [ 308.987804] [<00000349976cb08e>] __handle_mm_fault+0x4de/0x500 [ 308.987809] [<00000349976cb14c>] handle_mm_fault+0x9c/0x3a0 [ 308.987813] [<000003499734d70e>] do_exception+0x1de/0x540 [ 308.987822] [<0000034998387390>] __do_pgm_check+0x130/0x220 [ 308.987830] [<000003499839a934>] pgm_check_handler+0x114/0x160 [ 308.987838] 3 locks held by mempig_verify/5224: [ 308.987842] #0: 0000023ea44c1e08 (vm_lock){++++}-{0:0}, at: lock_vma_under_rcu+0xb2/0x2a0 [ 308.987859] #1: 0000023ee4d41b18 (&pcp->lock){+.+.}-{2:2}, at: rmqueue.isra.0+0xad6/0xf40 [ 308.987871] #2: 0000023efe6c8998 (&zone->lock){..-.}-{2:2}, at: rmqueue_bulk+0x5a/0x940 [ 308.987886] Last Breaking-Event-Address: [ 308.987890] [<0000034997379096>] __warn_printk+0x136/0x140 [ 308.987897] irq event stamp: 52330356 [ 308.987901] hardirqs last enabled at (52330355): [<000003499838742e>] __do_pgm_check+0x1ce/0x220 [ 308.987907] hardirqs last disabled at (52330356): [<000003499839932e>] _raw_spin_lock_irqsave+0x9e/0xe0 [ 308.987913] softirqs last enabled at (52329882): [<0000034997383786>] handle_softirqs+0x2c6/0x530 [ 308.987922] softirqs last disabled at (52329859): [<0000034997382f86>] __irq_exit_rcu+0x126/0x140 [ 308.987929] ---[ end trace 0000000000000000 ]--- [ 308.987936] ------------[ cut here ]------------ [ 308.987940] page type is 0, passed migratetype is 1 (nr=256) [ 308.987951] WARNING: CPU: 1 PID: 5224 at mm/page_alloc.c:860 __del_page_from_free_list+0x1be/0x1e0 [ 308.987960] Modules linked in: algif_hash(E) af_alg(E) nft_fib_inet(E) nft_fib_ipv4(E) nft_fib_ipv6(E) nft_fib(E) nft_reject_inet(E) nf_reject_ipv4(E) nf_reject_ipv6(E) nft_reject(E) nft_ct(E) nft_chain_nat(E) nf_nat(E) nf_conntrack(E) nf_defrag_ipv6(E) nf_defrag_ipv4(E) nf_tables(E) s390_trng(E) vfio_ccw(E) mdev(E) vfio_iommu_type1(E) vfio(E) sch_fq_codel(E) drm(E) i2c_core(E) drm_panel_orientation_quirks(E) loop(E) nfnetlink(E) vsock_loopback(E) vmw_vsock_virtio_transport_common(E) vsock(E) ctcm(E) fsm(E) diag288_wdt(E) watchdog(E) zfcp(E) scsi_transport_fc(E) ghash_s390(E) prng(E) aes_s390(E) des_generic(E) des_s390(E) libdes(E) sha3_512_s390(E) sha3_256_s390(E) sha_common(E) paes_s390(E) crypto_engine(E) pkey_cca(E) pkey_ep11(E) zcrypt(E) rng_core(E) pkey_pckmo(E) pkey(E) autofs4(E) [ 308.988070] Unloaded tainted modules: hmac_s390(E):2 [ 308.988087] CPU: 1 UID: 0 PID: 5224 Comm: mempig_verify Kdump: loaded Tainted: G W E 6.18.0-gcc-bpf-debug #431 PREEMPT [ 308.988095] Tainted: [W]=WARN, [E]=UNSIGNED_MODULE [ 308.988100] Hardware name: IBM 3906 M04 704 (z/VM 7.3.0) [ 308.988105] Krnl PSW : 0404f00180000000 00000349976f9e32 (__del_page_from_free_list+0x1c2/0x1e0) [ 308.988118] R:0 T:1 IO:0 EX:0 Key:0 M:1 W:0 P:0 AS:3 CC:3 PM:0 RI:0 EA:3 [ 308.988127] Krnl GPRS: 0000034980000004 0000000000000005 0000000000000030 000003499a0e6d88 [ 308.988133] 0000000000000005 0000034980000005 0000034998d57290 0000023efe6c8300 [ 308.988139] 0000000000000001 0000000000000008 000002be00000100 000002be803ac000 [ 308.988144] 0000000000000000 0000000000000001 00000349976f9e2e 000002c99b1eb728 [ 308.988153] Krnl Code: 00000349976f9e22: c020008a06d9 larl %r2,000003499883abd4 00000349976f9e28: c0e5ffe3f89c brasl %r14,0000034997378f60 #00000349976f9e2e: af000000 mc 0,0 >00000349976f9e32: a7f4ff4e brc 15,00000349976f9cce 00000349976f9e36: b904002b lgr %r2,%r11 00000349976f9e3a: c030008a06e7 larl %r3,000003499883ac08 00000349976f9e40: c0e5fffdbac8 brasl %r14,00000349976b13d0 00000349976f9e46: af000000 mc 0,0 [ 308.988184] Call Trace: [ 308.988188] [<00000349976f9e32>] __del_page_from_free_list+0x1c2/0x1e0 [ 308.988195] ([<00000349976f9e2e>] __del_page_from_free_list+0x1be/0x1e0) [ 308.988202] [<00000349976ff946>] rmqueue_bulk+0x706/0x940 [ 308.988208] [<00000349976ffd7e>] __rmqueue_pcplist+0x1fe/0x2a0 [ 308.988214] [<0000034997700966>] rmqueue.isra.0+0xb46/0xf40 [ 308.988221] [<0000034997703ec8>] get_page_from_freelist+0x198/0x8d0 [ 308.988227] [<0000034997706fa8>] __alloc_frozen_pages_noprof+0x198/0x400 [ 308.988233] [<00000349977536f8>] alloc_pages_mpol+0xb8/0x220 [ 308.988240] [<0000034997753bf6>] folio_alloc_mpol_noprof+0x26/0xc0 [ 308.988247] [<0000034997753e4c>] vma_alloc_folio_noprof+0x6c/0xa0 [ 308.988253] [<0000034997775b22>] vma_alloc_anon_folio_pmd+0x42/0x240 [ 308.988260] [<000003499777bfea>] __do_huge_pmd_anonymous_page+0x3a/0x210 [ 308.988267] [<00000349976cb08e>] __handle_mm_fault+0x4de/0x500 [ 308.988273] [<00000349976cb14c>] handle_mm_fault+0x9c/0x3a0 [ 308.988279] [<000003499734d70e>] do_exception+0x1de/0x540 [ 308.988286] [<0000034998387390>] __do_pgm_check+0x130/0x220 [ 308.988293] [<000003499839a934>] pgm_check_handler+0x114/0x160 [ 308.988300] 3 locks held by mempig_verify/5224: [ 308.988305] #0: 0000023ea44c1e08 (vm_lock){++++}-{0:0}, at: lock_vma_under_rcu+0xb2/0x2a0 [ 308.988322] #1: 0000023ee4d41b18 (&pcp->lock){+.+.}-{2:2}, at: rmqueue.isra.0+0xad6/0xf40 [ 308.988334] #2: 0000023efe6c8998 (&zone->lock){..-.}-{2:2}, at: rmqueue_bulk+0x5a/0x940 [ 308.988346] Last Breaking-Event-Address: [ 308.988350] [<0000034997379096>] __warn_printk+0x136/0x140 [ 308.988356] irq event stamp: 52330356 [ 308.988360] hardirqs last enabled at (52330355): [<000003499838742e>] __do_pgm_check+0x1ce/0x220 [ 308.988366] hardirqs last disabled at (52330356): [<000003499839932e>] _raw_spin_lock_irqsave+0x9e/0xe0 [ 308.988373] softirqs last enabled at (52329882): [<0000034997383786>] handle_softirqs+0x2c6/0x530 [ 308.988380] softirqs last disabled at (52329859): [<0000034997382f86>] __irq_exit_rcu+0x126/0x140 [ 308.988388] ---[ end trace 0000000000000000 ]--- Link: https://lkml.kernel.org/r/20251215081002.3353900A9c-agordeev@linux.ibm.com Link: https://lkml.kernel.org/r/20251212151457.3898073Add-agordeev@linux.ibm.com Fixes: e6cf9e1c4cde ("mm: page_alloc: fix up block types when merging compatible blocks") Signed-off-by: Alexander Gordeev Reported-by: Marc Hartmayer Closes: https://lore.kernel.org/linux-mm/87wmalyktd.fsf@linux.ibm.com/ Acked-by: Vlastimil Babka Acked-by: Johannes Weiner Reviewed-by: Wei Yang Cc: Marc Hartmayer Cc: Signed-off-by: Andrew Morton Signed-off-by: Greg Kroah-Hartman --- mm/page_alloc.c | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/mm/page_alloc.c b/mm/page_alloc.c index ed82ee55e66aff..6e644f2744c2d6 100644 --- a/mm/page_alloc.c +++ b/mm/page_alloc.c @@ -913,6 +913,17 @@ buddy_merge_likely(unsigned long pfn, unsigned long buddy_pfn, NULL) != NULL; } +static void change_pageblock_range(struct page *pageblock_page, + int start_order, int migratetype) +{ + int nr_pageblocks = 1 << (start_order - pageblock_order); + + while (nr_pageblocks--) { + set_pageblock_migratetype(pageblock_page, migratetype); + pageblock_page += pageblock_nr_pages; + } +} + /* * Freeing function for a buddy system allocator. * @@ -999,7 +1010,7 @@ static inline void __free_one_page(struct page *page, * expand() down the line puts the sub-blocks * on the right freelists. */ - set_pageblock_migratetype(buddy, migratetype); + change_pageblock_range(buddy, order, migratetype); } combined_pfn = buddy_pfn & pfn; @@ -2146,17 +2157,6 @@ bool pageblock_unisolate_and_move_free_pages(struct zone *zone, struct page *pag #endif /* CONFIG_MEMORY_ISOLATION */ -static void change_pageblock_range(struct page *pageblock_page, - int start_order, int migratetype) -{ - int nr_pageblocks = 1 << (start_order - pageblock_order); - - while (nr_pageblocks--) { - set_pageblock_migratetype(pageblock_page, migratetype); - pageblock_page += pageblock_nr_pages; - } -} - static inline bool boost_watermark(struct zone *zone) { unsigned long max_boost; From 05a8edf607be826738dfc620a17d9d98e8976762 Mon Sep 17 00:00:00 2001 From: Ran Xiaokai Date: Fri, 19 Dec 2025 07:42:32 +0000 Subject: [PATCH 1988/3902] mm/page_owner: fix memory leak in page_owner_stack_fops->release() commit a76a5ae2c6c645005672c2caf2d49361c6f2500f upstream. The page_owner_stack_fops->open() callback invokes seq_open_private(), therefore its corresponding ->release() callback must call seq_release_private(). Otherwise it will cause a memory leak of struct stack_print_ctx. Link: https://lkml.kernel.org/r/20251219074232.136482-1-ranxiaokai627@163.com Fixes: 765973a09803 ("mm,page_owner: display all stacks and their count") Signed-off-by: Ran Xiaokai Acked-by: Michal Hocko Acked-by: Vlastimil Babka Cc: Andrey Konovalov Cc: Brendan Jackman Cc: Johannes Weiner Cc: Marco Elver Cc: Suren Baghdasaryan Cc: Zi Yan Cc: Signed-off-by: Andrew Morton Signed-off-by: Greg Kroah-Hartman --- mm/page_owner.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mm/page_owner.c b/mm/page_owner.c index 589ec37c94aab9..bc26764142ba5f 100644 --- a/mm/page_owner.c +++ b/mm/page_owner.c @@ -936,7 +936,7 @@ static const struct file_operations page_owner_stack_operations = { .open = page_owner_stack_open, .read = seq_read, .llseek = seq_lseek, - .release = seq_release, + .release = seq_release_private, }; static int page_owner_threshold_get(void *data, u64 *val) From 4bae7111bf299097ef7ba715f9e5be09fe2b1346 Mon Sep 17 00:00:00 2001 From: Bijan Tabatabai Date: Tue, 16 Dec 2025 14:07:27 -0600 Subject: [PATCH 1989/3902] mm: consider non-anon swap cache folios in folio_expected_ref_count() commit f183663901f21fe0fba8bd31ae894bc529709ee0 upstream. Currently, folio_expected_ref_count() only adds references for the swap cache if the folio is anonymous. However, according to the comment above the definition of PG_swapcache in enum pageflags, shmem folios can also have PG_swapcache set. This patch makes sure references for the swap cache are added if folio_test_swapcache(folio) is true. This issue was found when trying to hot-unplug memory in a QEMU/KVM virtual machine. When initiating hot-unplug when most of the guest memory is allocated, hot-unplug hangs partway through removal due to migration failures. The following message would be printed several times, and would be printed again about every five seconds: [ 49.641309] migrating pfn b12f25 failed ret:7 [ 49.641310] page: refcount:2 mapcount:0 mapping:0000000033bd8fe2 index:0x7f404d925 pfn:0xb12f25 [ 49.641311] aops:swap_aops [ 49.641313] flags: 0x300000000030508(uptodate|active|owner_priv_1|reclaim|swapbacked|node=0|zone=3) [ 49.641314] raw: 0300000000030508 ffffed312c4bc908 ffffed312c4bc9c8 0000000000000000 [ 49.641315] raw: 00000007f404d925 00000000000c823b 00000002ffffffff 0000000000000000 [ 49.641315] page dumped because: migration failure When debugging this, I found that these migration failures were due to __migrate_folio() returning -EAGAIN for a small set of folios because the expected reference count it calculates via folio_expected_ref_count() is one less than the actual reference count of the folios. Furthermore, all of the affected folios were not anonymous, but had the PG_swapcache flag set, inspiring this patch. After applying this patch, the memory hot-unplug behaves as expected. I tested this on a machine running Ubuntu 24.04 with kernel version 6.8.0-90-generic and 64GB of memory. The guest VM is managed by libvirt and runs Ubuntu 24.04 with kernel version 6.18 (though the head of the mm-unstable branch as a Dec 16, 2025 was also tested and behaves the same) and 48GB of memory. The libvirt XML definition for the VM can be found at [1]. CONFIG_MHP_DEFAULT_ONLINE_TYPE_ONLINE_MOVABLE is set in the guest kernel so the hot-pluggable memory is automatically onlined. Below are the steps to reproduce this behavior: 1) Define and start and virtual machine host$ virsh -c qemu:///system define ./test_vm.xml # test_vm.xml from [1] host$ virsh -c qemu:///system start test_vm 2) Setup swap in the guest guest$ sudo fallocate -l 32G /swapfile guest$ sudo chmod 0600 /swapfile guest$ sudo mkswap /swapfile guest$ sudo swapon /swapfile 3) Use alloc_data [2] to allocate most of the remaining guest memory guest$ ./alloc_data 45 4) In a separate guest terminal, monitor the amount of used memory guest$ watch -n1 free -h 5) When alloc_data has finished allocating, initiate the memory hot-unplug using the provided xml file [3] host$ virsh -c qemu:///system detach-device test_vm ./remove.xml --live After initiating the memory hot-unplug, you should see the amount of available memory in the guest decrease, and the amount of used swap data increase. If everything works as expected, when all of the memory is unplugged, there should be around 8.5-9GB of data in swap. If the unplugging is unsuccessful, the amount of used swap data will settle below that. If that happens, you should be able to see log messages in dmesg similar to the one posted above. Link: https://lkml.kernel.org/r/20251216200727.2360228-1-bijan311@gmail.com Link: https://github.com/BijanT/linux_patch_files/blob/main/test_vm.xml [1] Link: https://github.com/BijanT/linux_patch_files/blob/main/alloc_data.c [2] Link: https://github.com/BijanT/linux_patch_files/blob/main/remove.xml [3] Fixes: 86ebd50224c0 ("mm: add folio_expected_ref_count() for reference count calculation") Signed-off-by: Bijan Tabatabai Acked-by: David Hildenbrand (Red Hat) Acked-by: Zi Yan Reviewed-by: Baolin Wang Cc: Liam Howlett Cc: Lorenzo Stoakes Cc: Michal Hocko Cc: Mike Rapoport Cc: Shivank Garg Cc: Suren Baghdasaryan Cc: Vlastimil Babka Cc: Kairui Song Cc: Signed-off-by: Andrew Morton Signed-off-by: Greg Kroah-Hartman --- include/linux/mm.h | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/include/linux/mm.h b/include/linux/mm.h index 7c79b3369b82c9..8631c9424987da 100644 --- a/include/linux/mm.h +++ b/include/linux/mm.h @@ -2272,10 +2272,10 @@ static inline int folio_expected_ref_count(const struct folio *folio) if (WARN_ON_ONCE(page_has_type(&folio->page) && !folio_test_hugetlb(folio))) return 0; - if (folio_test_anon(folio)) { - /* One reference per page from the swapcache. */ - ref_count += folio_test_swapcache(folio) << order; - } else { + /* One reference per page from the swapcache. */ + ref_count += folio_test_swapcache(folio) << order; + + if (!folio_test_anon(folio)) { /* One reference per page from the pagecache. */ ref_count += !!folio->mapping << order; /* One reference from PG_private. */ From 54e9bd5025a071058965e12d34caea1a1229e143 Mon Sep 17 00:00:00 2001 From: Rong Zhang Date: Tue, 30 Dec 2025 02:22:21 +0800 Subject: [PATCH 1990/3902] x86/microcode/AMD: Fix Entrysign revision check for Zen5/Strix Halo commit 150b1b97e27513535dcd3795d5ecd28e61b6cb8c upstream. Zen5 also contains family 1Ah, models 70h-7Fh, which are mistakenly missing from cpu_has_entrysign(). Add the missing range. Fixes: 8a9fb5129e8e ("x86/microcode/AMD: Limit Entrysign signature checking to known generations") Signed-off-by: Rong Zhang Signed-off-by: Borislav Petkov (AMD) Cc: stable@kernel.org Link: https://patch.msgid.link/20251229182245.152747-1-i@rong.moe Signed-off-by: Greg Kroah-Hartman --- arch/x86/kernel/cpu/microcode/amd.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/arch/x86/kernel/cpu/microcode/amd.c b/arch/x86/kernel/cpu/microcode/amd.c index 3821a985f4ffe2..46673530bc6f0e 100644 --- a/arch/x86/kernel/cpu/microcode/amd.c +++ b/arch/x86/kernel/cpu/microcode/amd.c @@ -258,7 +258,7 @@ static bool cpu_has_entrysign(void) if (fam == 0x1a) { if (model <= 0x2f || (0x40 <= model && model <= 0x4f) || - (0x60 <= model && model <= 0x6f)) + (0x60 <= model && model <= 0x7f)) return true; } From 4df537e2478d2a694a6936ef48d38f90e798282a Mon Sep 17 00:00:00 2001 From: Kaushlendra Kumar Date: Tue, 9 Dec 2025 10:15:52 +0530 Subject: [PATCH 1991/3902] tools/mm/page_owner_sort: fix timestamp comparison for stable sorting commit 7013803444dd3bbbe28fd3360c084cec3057c554 upstream. The ternary operator in compare_ts() returns 1 when timestamps are equal, causing unstable sorting behavior. Replace with explicit three-way comparison that returns 0 for equal timestamps, ensuring stable qsort ordering and consistent output. Link: https://lkml.kernel.org/r/20251209044552.3396468-1-kaushlendra.kumar@intel.com Fixes: 8f9c447e2e2b ("tools/vm/page_owner_sort.c: support sorting pid and time") Signed-off-by: Kaushlendra Kumar Cc: Chongxi Zhao Cc: Signed-off-by: Andrew Morton Signed-off-by: Greg Kroah-Hartman --- tools/mm/page_owner_sort.c | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/tools/mm/page_owner_sort.c b/tools/mm/page_owner_sort.c index 880e36df0c1189..82d6f6b31348cf 100644 --- a/tools/mm/page_owner_sort.c +++ b/tools/mm/page_owner_sort.c @@ -183,7 +183,11 @@ static int compare_ts(const void *p1, const void *p2) { const struct block_list *l1 = p1, *l2 = p2; - return l1->ts_nsec < l2->ts_nsec ? -1 : 1; + if (l1->ts_nsec < l2->ts_nsec) + return -1; + if (l1->ts_nsec > l2->ts_nsec) + return 1; + return 0; } static int compare_cull_condition(const void *p1, const void *p2) From 6f9af862a8595baa13c65e51aa721ca8bbdd608e Mon Sep 17 00:00:00 2001 From: Wake Liu Date: Wed, 10 Dec 2025 17:14:08 +0800 Subject: [PATCH 1992/3902] selftests/mm: fix thread state check in uffd-unit-tests commit 632b874d59a36caf829ab5790dafb90f9b350fd6 upstream. In the thread_state_get() function, the logic to find the thread's state character was using `sizeof(header) - 1` to calculate the offset from the "State:\t" string. The `header` variable is a `const char *` pointer. `sizeof()` on a pointer returns the size of the pointer itself, not the length of the string literal it points to. This makes the code's behavior dependent on the architecture's pointer size. This bug was identified on a 32-bit ARM build (`gsi_tv_arm`) for Android, running on an ARMv8-based device, compiled with Clang 19.0.1. On this 32-bit architecture, `sizeof(char *)` is 4. The expression `sizeof(header) - 1` resulted in an incorrect offset of 3, causing the test to read the wrong character from `/proc/[tid]/status` and fail. On 64-bit architectures, `sizeof(char *)` is 8, so the expression coincidentally evaluates to 7, which matches the length of "State:\t". This is why the bug likely remained hidden on 64-bit builds. To fix this and make the code portable and correct across all architectures, this patch replaces `sizeof(header) - 1` with `strlen(header)`. The `strlen()` function correctly calculates the string's length, ensuring the correct offset is always used. Link: https://lkml.kernel.org/r/20251210091408.3781445-1-wakel@google.com Fixes: f60b6634cd88 ("mm/selftests: add a test to verify mmap_changing race with -EAGAIN") Signed-off-by: Wake Liu Acked-by: Peter Xu Reviewed-by: Mike Rapoport (Microsoft) Cc: Bill Wendling Cc: Justin Stitt Cc: Liam Howlett Cc: Lorenzo Stoakes Cc: Michal Hocko Cc: Nathan Chancellor Cc: Shuah Khan Cc: Suren Baghdasaryan Cc: Vlastimil Babka Cc: Signed-off-by: Andrew Morton Signed-off-by: Greg Kroah-Hartman --- tools/testing/selftests/mm/uffd-unit-tests.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/testing/selftests/mm/uffd-unit-tests.c b/tools/testing/selftests/mm/uffd-unit-tests.c index f917b4c4c94369..44627d4010f249 100644 --- a/tools/testing/selftests/mm/uffd-unit-tests.c +++ b/tools/testing/selftests/mm/uffd-unit-tests.c @@ -1317,7 +1317,7 @@ static thread_state thread_state_get(pid_t tid) p = strstr(tmp, header); if (p) { /* For example, "State:\tD (disk sleep)" */ - c = *(p + sizeof(header) - 1); + c = *(p + strlen(header)); return c == 'D' ? THR_STATE_UNINTERRUPTIBLE : THR_STATE_UNKNOWN; } From 7d7010f5f0ee21dffeaad375949b2c7d431a2496 Mon Sep 17 00:00:00 2001 From: Chenghao Duan Date: Wed, 31 Dec 2025 15:19:25 +0800 Subject: [PATCH 1993/3902] samples/ftrace: Adjust LoongArch register restore order in direct calls commit bb85d206be208bbf834883e948125a35ac59993a upstream. Ensure that in the ftrace direct call logic, the CPU register state (with ra = parent return address) is restored to the correct state after the execution of the custom trampoline function and before returning to the traced function. Additionally, guarantee the correctness of the jump logic for jr t0 (traced function address). Cc: stable@vger.kernel.org Fixes: 9cdc3b6a299c ("LoongArch: ftrace: Add direct call support") Reported-by: Youling Tang Acked-by: Steven Rostedt (Google) Signed-off-by: Chenghao Duan Signed-off-by: Huacai Chen Signed-off-by: Greg Kroah-Hartman --- samples/ftrace/ftrace-direct-modify.c | 8 ++++---- samples/ftrace/ftrace-direct-multi-modify.c | 8 ++++---- samples/ftrace/ftrace-direct-multi.c | 4 ++-- samples/ftrace/ftrace-direct-too.c | 4 ++-- samples/ftrace/ftrace-direct.c | 4 ++-- 5 files changed, 14 insertions(+), 14 deletions(-) diff --git a/samples/ftrace/ftrace-direct-modify.c b/samples/ftrace/ftrace-direct-modify.c index da3a9f2091f55b..1ba1927b548ee9 100644 --- a/samples/ftrace/ftrace-direct-modify.c +++ b/samples/ftrace/ftrace-direct-modify.c @@ -176,8 +176,8 @@ asm ( " st.d $t0, $sp, 0\n" " st.d $ra, $sp, 8\n" " bl my_direct_func1\n" -" ld.d $t0, $sp, 0\n" -" ld.d $ra, $sp, 8\n" +" ld.d $ra, $sp, 0\n" +" ld.d $t0, $sp, 8\n" " addi.d $sp, $sp, 16\n" " jr $t0\n" " .size my_tramp1, .-my_tramp1\n" @@ -189,8 +189,8 @@ asm ( " st.d $t0, $sp, 0\n" " st.d $ra, $sp, 8\n" " bl my_direct_func2\n" -" ld.d $t0, $sp, 0\n" -" ld.d $ra, $sp, 8\n" +" ld.d $ra, $sp, 0\n" +" ld.d $t0, $sp, 8\n" " addi.d $sp, $sp, 16\n" " jr $t0\n" " .size my_tramp2, .-my_tramp2\n" diff --git a/samples/ftrace/ftrace-direct-multi-modify.c b/samples/ftrace/ftrace-direct-multi-modify.c index 8f7986d698d874..7a7822dfeb50ab 100644 --- a/samples/ftrace/ftrace-direct-multi-modify.c +++ b/samples/ftrace/ftrace-direct-multi-modify.c @@ -199,8 +199,8 @@ asm ( " move $a0, $t0\n" " bl my_direct_func1\n" " ld.d $a0, $sp, 0\n" -" ld.d $t0, $sp, 8\n" -" ld.d $ra, $sp, 16\n" +" ld.d $ra, $sp, 8\n" +" ld.d $t0, $sp, 16\n" " addi.d $sp, $sp, 32\n" " jr $t0\n" " .size my_tramp1, .-my_tramp1\n" @@ -215,8 +215,8 @@ asm ( " move $a0, $t0\n" " bl my_direct_func2\n" " ld.d $a0, $sp, 0\n" -" ld.d $t0, $sp, 8\n" -" ld.d $ra, $sp, 16\n" +" ld.d $ra, $sp, 8\n" +" ld.d $t0, $sp, 16\n" " addi.d $sp, $sp, 32\n" " jr $t0\n" " .size my_tramp2, .-my_tramp2\n" diff --git a/samples/ftrace/ftrace-direct-multi.c b/samples/ftrace/ftrace-direct-multi.c index db326c81a27ddd..3fe6ddaf0b69f7 100644 --- a/samples/ftrace/ftrace-direct-multi.c +++ b/samples/ftrace/ftrace-direct-multi.c @@ -131,8 +131,8 @@ asm ( " move $a0, $t0\n" " bl my_direct_func\n" " ld.d $a0, $sp, 0\n" -" ld.d $t0, $sp, 8\n" -" ld.d $ra, $sp, 16\n" +" ld.d $ra, $sp, 8\n" +" ld.d $t0, $sp, 16\n" " addi.d $sp, $sp, 32\n" " jr $t0\n" " .size my_tramp, .-my_tramp\n" diff --git a/samples/ftrace/ftrace-direct-too.c b/samples/ftrace/ftrace-direct-too.c index 3d0fa260332d47..bf2411aa6fd7ad 100644 --- a/samples/ftrace/ftrace-direct-too.c +++ b/samples/ftrace/ftrace-direct-too.c @@ -143,8 +143,8 @@ asm ( " ld.d $a0, $sp, 0\n" " ld.d $a1, $sp, 8\n" " ld.d $a2, $sp, 16\n" -" ld.d $t0, $sp, 24\n" -" ld.d $ra, $sp, 32\n" +" ld.d $ra, $sp, 24\n" +" ld.d $t0, $sp, 32\n" " addi.d $sp, $sp, 48\n" " jr $t0\n" " .size my_tramp, .-my_tramp\n" diff --git a/samples/ftrace/ftrace-direct.c b/samples/ftrace/ftrace-direct.c index 956834b0d19ac6..5368c8c39cbb44 100644 --- a/samples/ftrace/ftrace-direct.c +++ b/samples/ftrace/ftrace-direct.c @@ -124,8 +124,8 @@ asm ( " st.d $ra, $sp, 16\n" " bl my_direct_func\n" " ld.d $a0, $sp, 0\n" -" ld.d $t0, $sp, 8\n" -" ld.d $ra, $sp, 16\n" +" ld.d $ra, $sp, 8\n" +" ld.d $t0, $sp, 16\n" " addi.d $sp, $sp, 32\n" " jr $t0\n" " .size my_tramp, .-my_tramp\n" From 3b8caba9326746c9ebc2cc66f63cc0e509752a55 Mon Sep 17 00:00:00 2001 From: Alice Ryhl Date: Wed, 17 Dec 2025 13:10:37 +0000 Subject: [PATCH 1994/3902] rust: maple_tree: rcu_read_lock() in destructor to silence lockdep MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit commit 6558749ef3405c143711cbdc67ec88cbc1582d91 upstream. When running the Rust maple tree kunit tests with lockdep, you may trigger a warning that looks like this: lib/maple_tree.c:780 suspicious rcu_dereference_check() usage! other info that might help us debug this: rcu_scheduler_active = 2, debug_locks = 1 no locks held by kunit_try_catch/344. stack backtrace: CPU: 3 UID: 0 PID: 344 Comm: kunit_try_catch Tainted: G N 6.19.0-rc1+ #2 NONE Tainted: [N]=TEST Hardware name: QEMU Standard PC (Q35 + ICH9, 2009), BIOS rel-1.17.0-0-gb52ca86e094d-prebuilt.qemu.org 04/01/2014 Call Trace: dump_stack_lvl+0x71/0x90 lockdep_rcu_suspicious+0x150/0x190 mas_start+0x104/0x150 mas_find+0x179/0x240 _RINvNtCs5QSdWC790r4_4core3ptr13drop_in_placeINtNtCs1cdwasc6FUb_6kernel10maple_tree9MapleTreeINtNtNtBL_5alloc4kbox3BoxlNtNtB1x_9allocator7KmallocEEECsgxAQYCfdR72_25doctests_kernel_generated+0xaf/0x130 rust_doctest_kernel_maple_tree_rs_0+0x600/0x6b0 ? lock_release+0xeb/0x2a0 ? kunit_try_catch_run+0x210/0x210 kunit_try_run_case+0x74/0x160 ? kunit_try_catch_run+0x210/0x210 kunit_generic_run_threadfn_adapter+0x12/0x30 kthread+0x21c/0x230 ? __do_trace_sched_kthread_stop_ret+0x40/0x40 ret_from_fork+0x16c/0x270 ? __do_trace_sched_kthread_stop_ret+0x40/0x40 ret_from_fork_asm+0x11/0x20 This is because the destructor of maple tree calls mas_find() without taking rcu_read_lock() or the spinlock. Doing that is actually ok in this case since the destructor has exclusive access to the entire maple tree, but it triggers a lockdep warning. To fix that, take the rcu read lock. In the future, it's possible that memory reclaim could gain a feature where it reallocates entries in maple trees even if no user-code is touching it. If that feature is added, then this use of rcu read lock would become load-bearing, so I did not make it conditional on lockdep. We have to repeatedly take and release rcu because the destructor of T might perform operations that sleep. Link: https://lkml.kernel.org/r/20251217-maple-drop-rcu-v1-1-702af063573f@google.com Fixes: da939ef4c494 ("rust: maple_tree: add MapleTree") Signed-off-by: Alice Ryhl Reported-by: Andreas Hindborg Closes: https://rust-for-linux.zulipchat.com/#narrow/channel/x/topic/x/near/564215108 Reviewed-by: Gary Guo Reviewed-by: Daniel Almeida Cc: Andrew Ballance Cc: Björn Roy Baron Cc: Boqun Feng Cc: Danilo Krummrich Cc: Liam Howlett Cc: Matthew Wilcox (Oracle) Cc: Miguel Ojeda Cc: Trevor Gross Cc: Signed-off-by: Andrew Morton Signed-off-by: Greg Kroah-Hartman --- rust/kernel/maple_tree.rs | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/rust/kernel/maple_tree.rs b/rust/kernel/maple_tree.rs index e72eec56bf5772..265d6396a78a17 100644 --- a/rust/kernel/maple_tree.rs +++ b/rust/kernel/maple_tree.rs @@ -265,7 +265,16 @@ impl MapleTree { loop { // This uses the raw accessor because we're destroying pointers without removing them // from the maple tree, which is only valid because this is the destructor. - let ptr = ma_state.mas_find_raw(usize::MAX); + // + // Take the rcu lock because mas_find_raw() requires that you hold either the spinlock + // or the rcu read lock. This is only really required if memory reclaim might + // reallocate entries in the tree, as we otherwise have exclusive access. That feature + // doesn't exist yet, so for now, taking the rcu lock only serves the purpose of + // silencing lockdep. + let ptr = { + let _rcu = kernel::sync::rcu::Guard::new(); + ma_state.mas_find_raw(usize::MAX) + }; if ptr.is_null() { break; } From 0b948afc1ded88b3562c893114387f34389eeb94 Mon Sep 17 00:00:00 2001 From: Jason Gunthorpe Date: Fri, 28 Nov 2025 13:37:28 -0400 Subject: [PATCH 1995/3902] RDMA/core: Check for the presence of LS_NLA_TYPE_DGID correctly commit a7b8e876e0ef0232b8076972c57ce9a7286b47ca upstream. The netlink response for RDMA_NL_LS_OP_IP_RESOLVE should always have a LS_NLA_TYPE_DGID attribute, it is invalid if it does not. Use the nl parsing logic properly and call nla_parse_deprecated() to fill the nlattrs array and then directly index that array to get the data for the DGID. Just fail if it is NULL. Remove the for loop searching for the nla, and squash the validation and parsing into one function. Fixes an uninitialized read from the stack triggered by userspace if it does not provide the DGID to a kernel initiated RDMA_NL_LS_OP_IP_RESOLVE query. BUG: KMSAN: uninit-value in hex_byte_pack include/linux/hex.h:13 [inline] BUG: KMSAN: uninit-value in ip6_string+0xef4/0x13a0 lib/vsprintf.c:1490 hex_byte_pack include/linux/hex.h:13 [inline] ip6_string+0xef4/0x13a0 lib/vsprintf.c:1490 ip6_addr_string+0x18a/0x3e0 lib/vsprintf.c:1509 ip_addr_string+0x245/0xee0 lib/vsprintf.c:1633 pointer+0xc09/0x1bd0 lib/vsprintf.c:2542 vsnprintf+0xf8a/0x1bd0 lib/vsprintf.c:2930 vprintk_store+0x3ae/0x1530 kernel/printk/printk.c:2279 vprintk_emit+0x307/0xcd0 kernel/printk/printk.c:2426 vprintk_default+0x3f/0x50 kernel/printk/printk.c:2465 vprintk+0x36/0x50 kernel/printk/printk_safe.c:82 _printk+0x17e/0x1b0 kernel/printk/printk.c:2475 ib_nl_process_good_ip_rsep drivers/infiniband/core/addr.c:128 [inline] ib_nl_handle_ip_res_resp+0x963/0x9d0 drivers/infiniband/core/addr.c:141 rdma_nl_rcv_msg drivers/infiniband/core/netlink.c:-1 [inline] rdma_nl_rcv_skb drivers/infiniband/core/netlink.c:239 [inline] rdma_nl_rcv+0xefa/0x11c0 drivers/infiniband/core/netlink.c:259 netlink_unicast_kernel net/netlink/af_netlink.c:1320 [inline] netlink_unicast+0xf04/0x12b0 net/netlink/af_netlink.c:1346 netlink_sendmsg+0x10b3/0x1250 net/netlink/af_netlink.c:1896 sock_sendmsg_nosec net/socket.c:714 [inline] __sock_sendmsg+0x333/0x3d0 net/socket.c:729 ____sys_sendmsg+0x7e0/0xd80 net/socket.c:2617 ___sys_sendmsg+0x271/0x3b0 net/socket.c:2671 __sys_sendmsg+0x1aa/0x300 net/socket.c:2703 __compat_sys_sendmsg net/compat.c:346 [inline] __do_compat_sys_sendmsg net/compat.c:353 [inline] __se_compat_sys_sendmsg net/compat.c:350 [inline] __ia32_compat_sys_sendmsg+0xa4/0x100 net/compat.c:350 ia32_sys_call+0x3f6c/0x4310 arch/x86/include/generated/asm/syscalls_32.h:371 do_syscall_32_irqs_on arch/x86/entry/syscall_32.c:83 [inline] __do_fast_syscall_32+0xb0/0x150 arch/x86/entry/syscall_32.c:306 do_fast_syscall_32+0x38/0x80 arch/x86/entry/syscall_32.c:331 do_SYSENTER_32+0x1f/0x30 arch/x86/entry/syscall_32.c:3 Link: https://patch.msgid.link/r/0-v1-3fbaef094271+2cf-rdma_op_ip_rslv_syz_jgg@nvidia.com Cc: stable@vger.kernel.org Fixes: ae43f8286730 ("IB/core: Add IP to GID netlink offload") Reported-by: syzbot+938fcd548c303fe33c1a@syzkaller.appspotmail.com Closes: https://lore.kernel.org/r/68dc3dac.a00a0220.102ee.004f.GAE@google.com Signed-off-by: Jason Gunthorpe Signed-off-by: Greg Kroah-Hartman --- drivers/infiniband/core/addr.c | 33 ++++++++++----------------------- 1 file changed, 10 insertions(+), 23 deletions(-) diff --git a/drivers/infiniband/core/addr.c b/drivers/infiniband/core/addr.c index 61596cda2b65f3..35ba852a172aad 100644 --- a/drivers/infiniband/core/addr.c +++ b/drivers/infiniband/core/addr.c @@ -80,37 +80,25 @@ static const struct nla_policy ib_nl_addr_policy[LS_NLA_TYPE_MAX] = { .min = sizeof(struct rdma_nla_ls_gid)}, }; -static inline bool ib_nl_is_good_ip_resp(const struct nlmsghdr *nlh) +static void ib_nl_process_ip_rsep(const struct nlmsghdr *nlh) { struct nlattr *tb[LS_NLA_TYPE_MAX] = {}; + union ib_gid gid; + struct addr_req *req; + int found = 0; int ret; if (nlh->nlmsg_flags & RDMA_NL_LS_F_ERR) - return false; + return; ret = nla_parse_deprecated(tb, LS_NLA_TYPE_MAX - 1, nlmsg_data(nlh), nlmsg_len(nlh), ib_nl_addr_policy, NULL); if (ret) - return false; - - return true; -} - -static void ib_nl_process_good_ip_rsep(const struct nlmsghdr *nlh) -{ - const struct nlattr *head, *curr; - union ib_gid gid; - struct addr_req *req; - int len, rem; - int found = 0; - - head = (const struct nlattr *)nlmsg_data(nlh); - len = nlmsg_len(nlh); + return; - nla_for_each_attr(curr, head, len, rem) { - if (curr->nla_type == LS_NLA_TYPE_DGID) - memcpy(&gid, nla_data(curr), nla_len(curr)); - } + if (!tb[LS_NLA_TYPE_DGID]) + return; + memcpy(&gid, nla_data(tb[LS_NLA_TYPE_DGID]), sizeof(gid)); spin_lock_bh(&lock); list_for_each_entry(req, &req_list, list) { @@ -137,8 +125,7 @@ int ib_nl_handle_ip_res_resp(struct sk_buff *skb, !(NETLINK_CB(skb).sk)) return -EPERM; - if (ib_nl_is_good_ip_resp(nlh)) - ib_nl_process_good_ip_rsep(nlh); + ib_nl_process_ip_rsep(nlh); return 0; } From 3ba6d01c4b3c584264dc733c6a2ecc5bbc8e0bb5 Mon Sep 17 00:00:00 2001 From: Jason Gunthorpe Date: Fri, 28 Nov 2025 20:53:21 -0400 Subject: [PATCH 1996/3902] RDMA/cm: Fix leaking the multicast GID table reference commit 57f3cb6c84159d12ba343574df2115fb18dd83ca upstream. If the CM ID is destroyed while the CM event for multicast creating is still queued the cancel_work_sync() will prevent the work from running which also prevents destroying the ah_attr. This leaks a refcount and triggers a WARN: GID entry ref leak for dev syz1 index 2 ref=573 WARNING: CPU: 1 PID: 655 at drivers/infiniband/core/cache.c:809 release_gid_table drivers/infiniband/core/cache.c:806 [inline] WARNING: CPU: 1 PID: 655 at drivers/infiniband/core/cache.c:809 gid_table_release_one+0x284/0x3cc drivers/infiniband/core/cache.c:886 Destroy the ah_attr after canceling the work, it is safe to call this twice. Link: https://patch.msgid.link/r/0-v1-4285d070a6b2+20a-rdma_mc_gid_leak_syz_jgg@nvidia.com Cc: stable@vger.kernel.org Fixes: fe454dc31e84 ("RDMA/ucma: Fix use-after-free bug in ucma_create_uevent") Reported-by: syzbot+b0da83a6c0e2e2bddbd4@syzkaller.appspotmail.com Closes: https://lore.kernel.org/all/68232e7b.050a0220.f2294.09f6.GAE@google.com Signed-off-by: Jason Gunthorpe Signed-off-by: Greg Kroah-Hartman --- drivers/infiniband/core/cma.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/drivers/infiniband/core/cma.c b/drivers/infiniband/core/cma.c index 5b2d3ae3f9fce8..ce511800b056cf 100644 --- a/drivers/infiniband/core/cma.c +++ b/drivers/infiniband/core/cma.c @@ -2009,6 +2009,7 @@ static void destroy_mc(struct rdma_id_private *id_priv, ib_sa_free_multicast(mc->sa_mc); if (rdma_protocol_roce(id_priv->id.device, id_priv->id.port_num)) { + struct rdma_cm_event *event = &mc->iboe_join.event; struct rdma_dev_addr *dev_addr = &id_priv->id.route.addr.dev_addr; struct net_device *ndev = NULL; @@ -2031,6 +2032,8 @@ static void destroy_mc(struct rdma_id_private *id_priv, dev_put(ndev); cancel_work_sync(&mc->iboe_join.work); + if (event->event == RDMA_CM_EVENT_MULTICAST_JOIN) + rdma_destroy_ah_attr(&event->param.ud.ah_attr); } kfree(mc); } From de5eb6b65e9f51773627bf1ea13d63a67a85d090 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ville=20Syrj=C3=A4l=C3=A4?= Date: Fri, 14 Nov 2025 00:28:52 +0200 Subject: [PATCH 1997/3902] wifi: iwlwifi: Fix firmware version handling MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit commit ca5898222914f399797cea1aeb0ce77109ca2e62 upstream. On my system the arithmetic done on the firmware numbers results in a negative number, but since the types are unsigned it gets interpreted as a large positive number. The end result is that the firmware gets rejected and wifi is defunct. Switch to signed types to handle this case correctly. iwlwifi 0000:0c:00.0: Driver unable to support your firmware API. Driver supports FW core 4294967294..2, firmware is 2. iwlwifi 0000:0c:00.0: Direct firmware load for iwlwifi-5000-4.ucode failed with error -2 iwlwifi 0000:0c:00.0: Direct firmware load for iwlwifi-5000-3.ucode failed with error -2 iwlwifi 0000:0c:00.0: Direct firmware load for iwlwifi-5000-2.ucode failed with error -2 iwlwifi 0000:0c:00.0: Direct firmware load for iwlwifi-5000-1.ucode failed with error -2 iwlwifi 0000:0c:00.0: no suitable firmware found! iwlwifi 0000:0c:00.0: minimum version required: iwlwifi-5000-1 iwlwifi 0000:0c:00.0: maximum version supported: iwlwifi-5000-5 iwlwifi 0000:0c:00.0: check git://git.kernel.org/pub/scm/linux/kernel/git/firmware/linux-firmware.git Cc: stable@vger.kernel.org Fixes: 5f708cccde9d ("wifi: iwlwifi: add a new FW file numbering scheme") Signed-off-by: Ville Syrjälä Closes: https://bugzilla.kernel.org/show_bug.cgi?id=220805 Link: https://patch.msgid.link/20251113222852.15896-1-ville.syrjala@linux.intel.com Signed-off-by: Johannes Berg Signed-off-by: Greg Kroah-Hartman --- drivers/net/wireless/intel/iwlwifi/iwl-drv.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/net/wireless/intel/iwlwifi/iwl-drv.c b/drivers/net/wireless/intel/iwlwifi/iwl-drv.c index 607fcea6f4efc4..0f002ef261fccf 100644 --- a/drivers/net/wireless/intel/iwlwifi/iwl-drv.c +++ b/drivers/net/wireless/intel/iwlwifi/iwl-drv.c @@ -1599,7 +1599,7 @@ static void _iwl_op_mode_stop(struct iwl_drv *drv) */ static void iwl_req_fw_callback(const struct firmware *ucode_raw, void *context) { - unsigned int min_core, max_core, loaded_core; + int min_core, max_core, loaded_core; struct iwl_drv *drv = context; struct iwl_fw *fw = &drv->fw; const struct iwl_ucode_header *ucode; @@ -1678,7 +1678,7 @@ static void iwl_req_fw_callback(const struct firmware *ucode_raw, void *context) if (loaded_core < min_core || loaded_core > max_core) { IWL_ERR(drv, "Driver unable to support your firmware API. " - "Driver supports FW core %u..%u, firmware is %u.\n", + "Driver supports FW core %d..%d, firmware is %d.\n", min_core, max_core, loaded_core); goto try_again; } From a21704df4024708be698fb3fd5830d5b113b70e0 Mon Sep 17 00:00:00 2001 From: Jouni Malinen Date: Mon, 15 Dec 2025 17:11:34 +0200 Subject: [PATCH 1998/3902] wifi: mac80211: Discard Beacon frames to non-broadcast address commit 193d18f60588e95d62e0f82b6a53893e5f2f19f8 upstream. Beacon frames are required to be sent to the broadcast address, see IEEE Std 802.11-2020, 11.1.3.1 ("The Address 1 field of the Beacon .. frame shall be set to the broadcast address"). A unicast Beacon frame might be used as a targeted attack to get one of the associated STAs to do something (e.g., using CSA to move it to another channel). As such, it is better have strict filtering for this on the received side and discard all Beacon frames that are sent to an unexpected address. This is even more important for cases where beacon protection is used. The current implementation in mac80211 is correctly discarding unicast Beacon frames if the Protected Frame bit in the Frame Control field is set to 0. However, if that bit is set to 1, the logic used for checking for configured BIGTK(s) does not actually work. If the driver does not have logic for dropping unicast Beacon frames with Protected Frame bit 1, these frames would be accepted in mac80211 processing as valid Beacon frames even though they are not protected. This would allow beacon protection to be bypassed. While the logic for checking beacon protection could be extended to cover this corner case, a more generic check for discard all Beacon frames based on A1=unicast address covers this without needing additional changes. Address all these issues by dropping received Beacon frames if they are sent to a non-broadcast address. Cc: stable@vger.kernel.org Fixes: af2d14b01c32 ("mac80211: Beacon protection using the new BIGTK (STA)") Signed-off-by: Jouni Malinen Link: https://patch.msgid.link/20251215151134.104501-1-jouni.malinen@oss.qualcomm.com Signed-off-by: Johannes Berg Signed-off-by: Greg Kroah-Hartman --- net/mac80211/rx.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/net/mac80211/rx.c b/net/mac80211/rx.c index 5b4c3fe9970ad3..5091a3c15b0b49 100644 --- a/net/mac80211/rx.c +++ b/net/mac80211/rx.c @@ -3428,6 +3428,11 @@ ieee80211_rx_h_mgmt_check(struct ieee80211_rx_data *rx) rx->skb->len < IEEE80211_MIN_ACTION_SIZE) return RX_DROP_U_RUNT_ACTION; + /* Drop non-broadcast Beacon frames */ + if (ieee80211_is_beacon(mgmt->frame_control) && + !is_broadcast_ether_addr(mgmt->da)) + return RX_DROP; + if (rx->sdata->vif.type == NL80211_IFTYPE_AP && ieee80211_is_beacon(mgmt->frame_control) && !(rx->flags & IEEE80211_RX_BEACON_REPORTED)) { From ee7c125fb3e8b04dd46510130b9fc92380e5d578 Mon Sep 17 00:00:00 2001 From: Guangshuo Li Date: Mon, 1 Dec 2025 11:40:58 +0800 Subject: [PATCH 1999/3902] e1000: fix OOB in e1000_tbi_should_accept() commit 9c72a5182ed92904d01057f208c390a303f00a0f upstream. In e1000_tbi_should_accept() we read the last byte of the frame via 'data[length - 1]' to evaluate the TBI workaround. If the descriptor- reported length is zero or larger than the actual RX buffer size, this read goes out of bounds and can hit unrelated slab objects. The issue is observed from the NAPI receive path (e1000_clean_rx_irq): ================================================================== BUG: KASAN: slab-out-of-bounds in e1000_tbi_should_accept+0x610/0x790 Read of size 1 at addr ffff888014114e54 by task sshd/363 CPU: 0 PID: 363 Comm: sshd Not tainted 5.18.0-rc1 #1 Hardware name: QEMU Standard PC (i440FX + PIIX, 1996), BIOS rel-1.12.0-59-gc9ba5276e321-prebuilt.qemu.org 04/01/2014 Call Trace: dump_stack_lvl+0x5a/0x74 print_address_description+0x7b/0x440 print_report+0x101/0x200 kasan_report+0xc1/0xf0 e1000_tbi_should_accept+0x610/0x790 e1000_clean_rx_irq+0xa8c/0x1110 e1000_clean+0xde2/0x3c10 __napi_poll+0x98/0x380 net_rx_action+0x491/0xa20 __do_softirq+0x2c9/0x61d do_softirq+0xd1/0x120 __local_bh_enable_ip+0xfe/0x130 ip_finish_output2+0x7d5/0xb00 __ip_queue_xmit+0xe24/0x1ab0 __tcp_transmit_skb+0x1bcb/0x3340 tcp_write_xmit+0x175d/0x6bd0 __tcp_push_pending_frames+0x7b/0x280 tcp_sendmsg_locked+0x2e4f/0x32d0 tcp_sendmsg+0x24/0x40 sock_write_iter+0x322/0x430 vfs_write+0x56c/0xa60 ksys_write+0xd1/0x190 do_syscall_64+0x43/0x90 entry_SYSCALL_64_after_hwframe+0x44/0xae RIP: 0033:0x7f511b476b10 Code: 73 01 c3 48 8b 0d 88 d3 2b 00 f7 d8 64 89 01 48 83 c8 ff c3 66 0f 1f 44 00 00 83 3d f9 2b 2c 00 00 75 10 b8 01 00 00 00 0f 05 <48> 3d 01 f0 ff ff 73 31 c3 48 83 ec 08 e8 8e 9b 01 00 48 89 04 24 RSP: 002b:00007ffc9211d4e8 EFLAGS: 00000246 ORIG_RAX: 0000000000000001 RAX: ffffffffffffffda RBX: 0000000000004024 RCX: 00007f511b476b10 RDX: 0000000000004024 RSI: 0000559a9385962c RDI: 0000000000000003 RBP: 0000559a9383a400 R08: fffffffffffffff0 R09: 0000000000004f00 R10: 0000000000000070 R11: 0000000000000246 R12: 0000000000000000 R13: 00007ffc9211d57f R14: 0000559a9347bde7 R15: 0000000000000003 Allocated by task 1: __kasan_krealloc+0x131/0x1c0 krealloc+0x90/0xc0 add_sysfs_param+0xcb/0x8a0 kernel_add_sysfs_param+0x81/0xd4 param_sysfs_builtin+0x138/0x1a6 param_sysfs_init+0x57/0x5b do_one_initcall+0x104/0x250 do_initcall_level+0x102/0x132 do_initcalls+0x46/0x74 kernel_init_freeable+0x28f/0x393 kernel_init+0x14/0x1a0 ret_from_fork+0x22/0x30 The buggy address belongs to the object at ffff888014114000 which belongs to the cache kmalloc-2k of size 2048 The buggy address is located 1620 bytes to the right of 2048-byte region [ffff888014114000, ffff888014114800] The buggy address belongs to the physical page: page:ffffea0000504400 refcount:1 mapcount:0 mapping:0000000000000000 index:0x0 pfn:0x14110 head:ffffea0000504400 order:3 compound_mapcount:0 compound_pincount:0 flags: 0x100000000010200(slab|head|node=0|zone=1) raw: 0100000000010200 0000000000000000 dead000000000001 ffff888013442000 raw: 0000000000000000 0000000000080008 00000001ffffffff 0000000000000000 page dumped because: kasan: bad access detected ================================================================== This happens because the TBI check unconditionally dereferences the last byte without validating the reported length first: u8 last_byte = *(data + length - 1); Fix by rejecting the frame early if the length is zero, or if it exceeds adapter->rx_buffer_len. This preserves the TBI workaround semantics for valid frames and prevents touching memory beyond the RX buffer. Fixes: 2037110c96d5 ("e1000: move tbi workaround code into helper function") Cc: stable@vger.kernel.org Signed-off-by: Guangshuo Li Reviewed-by: Simon Horman Reviewed-by: Aleksandr Loktionov Signed-off-by: Tony Nguyen Signed-off-by: Greg Kroah-Hartman --- drivers/net/ethernet/intel/e1000/e1000_main.c | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/drivers/net/ethernet/intel/e1000/e1000_main.c b/drivers/net/ethernet/intel/e1000/e1000_main.c index 292389aceb2d4a..7f078ec9c14c5f 100644 --- a/drivers/net/ethernet/intel/e1000/e1000_main.c +++ b/drivers/net/ethernet/intel/e1000/e1000_main.c @@ -4094,7 +4094,15 @@ static bool e1000_tbi_should_accept(struct e1000_adapter *adapter, u32 length, const u8 *data) { struct e1000_hw *hw = &adapter->hw; - u8 last_byte = *(data + length - 1); + u8 last_byte; + + /* Guard against OOB on data[length - 1] */ + if (unlikely(!length)) + return false; + /* Upper bound: length must not exceed rx_buffer_len */ + if (unlikely(length > adapter->rx_buffer_len)) + return false; + last_byte = *(data + length - 1); if (TBI_ACCEPT(hw, status, errors, length, last_byte)) { unsigned long irq_flags; From b282b2a9eed848587c1348abdd5d83fa346a2743 Mon Sep 17 00:00:00 2001 From: Frode Nordahl Date: Sat, 13 Dec 2025 10:13:36 +0000 Subject: [PATCH 2000/3902] erspan: Initialize options_len before referencing options. commit 35ddf66c65eff93fff91406756ba273600bf61a3 upstream. The struct ip_tunnel_info has a flexible array member named options that is protected by a counted_by(options_len) attribute. The compiler will use this information to enforce runtime bounds checking deployed by FORTIFY_SOURCE string helpers. As laid out in the GCC documentation, the counter must be initialized before the first reference to the flexible array member. After scanning through the files that use struct ip_tunnel_info and also refer to options or options_len, it appears the normal case is to use the ip_tunnel_info_opts_set() helper. Said helper would initialize options_len properly before copying data into options, however in the GRE ERSPAN code a partial update is done, preventing the use of the helper function. Before this change the handling of ERSPAN traffic in GRE tunnels would cause a kernel panic when the kernel is compiled with GCC 15+ and having FORTIFY_SOURCE configured: memcpy: detected buffer overflow: 4 byte write of buffer size 0 Call Trace: __fortify_panic+0xd/0xf erspan_rcv.cold+0x68/0x83 ? ip_route_input_slow+0x816/0x9d0 gre_rcv+0x1b2/0x1c0 gre_rcv+0x8e/0x100 ? raw_v4_input+0x2a0/0x2b0 ip_protocol_deliver_rcu+0x1ea/0x210 ip_local_deliver_finish+0x86/0x110 ip_local_deliver+0x65/0x110 ? ip_rcv_finish_core+0xd6/0x360 ip_rcv+0x186/0x1a0 Cc: stable@vger.kernel.org Link: https://gcc.gnu.org/onlinedocs/gcc/Common-Variable-Attributes.html#index-counted_005fby-variable-attribute Reported-at: https://launchpad.net/bugs/2129580 Fixes: bb5e62f2d547 ("net: Add options as a flexible array to struct ip_tunnel_info") Signed-off-by: Frode Nordahl Reviewed-by: Simon Horman Link: https://patch.msgid.link/20251213101338.4693-1-fnordahl@ubuntu.com Signed-off-by: Paolo Abeni Signed-off-by: Greg Kroah-Hartman --- net/ipv4/ip_gre.c | 6 ++++-- net/ipv6/ip6_gre.c | 6 ++++-- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/net/ipv4/ip_gre.c b/net/ipv4/ip_gre.c index 761a53c6a89a64..8178c44a3cdd48 100644 --- a/net/ipv4/ip_gre.c +++ b/net/ipv4/ip_gre.c @@ -330,6 +330,10 @@ static int erspan_rcv(struct sk_buff *skb, struct tnl_ptk_info *tpi, if (!tun_dst) return PACKET_REJECT; + /* MUST set options_len before referencing options */ + info = &tun_dst->u.tun_info; + info->options_len = sizeof(*md); + /* skb can be uncloned in __iptunnel_pull_header, so * old pkt_md is no longer valid and we need to reset * it @@ -344,10 +348,8 @@ static int erspan_rcv(struct sk_buff *skb, struct tnl_ptk_info *tpi, memcpy(md2, pkt_md, ver == 1 ? ERSPAN_V1_MDSIZE : ERSPAN_V2_MDSIZE); - info = &tun_dst->u.tun_info; __set_bit(IP_TUNNEL_ERSPAN_OPT_BIT, info->key.tun_flags); - info->options_len = sizeof(*md); } skb_reset_mac_header(skb); diff --git a/net/ipv6/ip6_gre.c b/net/ipv6/ip6_gre.c index 8bc3f05f594ed6..d19d86ed43766b 100644 --- a/net/ipv6/ip6_gre.c +++ b/net/ipv6/ip6_gre.c @@ -535,6 +535,10 @@ static int ip6erspan_rcv(struct sk_buff *skb, if (!tun_dst) return PACKET_REJECT; + /* MUST set options_len before referencing options */ + info = &tun_dst->u.tun_info; + info->options_len = sizeof(*md); + /* skb can be uncloned in __iptunnel_pull_header, so * old pkt_md is no longer valid and we need to reset * it @@ -543,7 +547,6 @@ static int ip6erspan_rcv(struct sk_buff *skb, skb_network_header_len(skb); pkt_md = (struct erspan_metadata *)(gh + gre_hdr_len + sizeof(*ershdr)); - info = &tun_dst->u.tun_info; md = ip_tunnel_info_opts(info); md->version = ver; md2 = &md->u.md2; @@ -551,7 +554,6 @@ static int ip6erspan_rcv(struct sk_buff *skb, ERSPAN_V2_MDSIZE); __set_bit(IP_TUNNEL_ERSPAN_OPT_BIT, info->key.tun_flags); - info->options_len = sizeof(*md); ip6_tnl_rcv(tunnel, skb, tpi, tun_dst, log_ecn_error); From 726fca79cb551fda8ad110b256cfa24b1a5f5ec7 Mon Sep 17 00:00:00 2001 From: Haoxiang Li Date: Thu, 11 Dec 2025 15:37:56 +0800 Subject: [PATCH 2001/3902] fjes: Add missing iounmap in fjes_hw_init() commit 15ef641a0c6728d25a400df73922e80ab2cf029c upstream. In error paths, add fjes_hw_iounmap() to release the resource acquired by fjes_hw_iomap(). Add a goto label to do so. Fixes: 8cdc3f6c5d22 ("fjes: Hardware initialization routine") Cc: stable@vger.kernel.org Signed-off-by: Haoxiang Li Signed-off-by: Simon Horman Reviewed-by: Simon Horman Link: https://patch.msgid.link/20251211073756.101824-1-lihaoxiang@isrc.iscas.ac.cn Signed-off-by: Paolo Abeni Signed-off-by: Greg Kroah-Hartman --- drivers/net/fjes/fjes_hw.c | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/drivers/net/fjes/fjes_hw.c b/drivers/net/fjes/fjes_hw.c index b9b5554ea8620e..5ad2673f213d65 100644 --- a/drivers/net/fjes/fjes_hw.c +++ b/drivers/net/fjes/fjes_hw.c @@ -334,7 +334,7 @@ int fjes_hw_init(struct fjes_hw *hw) ret = fjes_hw_reset(hw); if (ret) - return ret; + goto err_iounmap; fjes_hw_set_irqmask(hw, REG_ICTL_MASK_ALL, true); @@ -347,8 +347,10 @@ int fjes_hw_init(struct fjes_hw *hw) hw->max_epid = fjes_hw_get_max_epid(hw); hw->my_epid = fjes_hw_get_my_epid(hw); - if ((hw->max_epid == 0) || (hw->my_epid >= hw->max_epid)) - return -ENXIO; + if ((hw->max_epid == 0) || (hw->my_epid >= hw->max_epid)) { + ret = -ENXIO; + goto err_iounmap; + } ret = fjes_hw_setup(hw); @@ -356,6 +358,10 @@ int fjes_hw_init(struct fjes_hw *hw) hw->hw_info.trace_size = FJES_DEBUG_BUFFER_SIZE; return ret; + +err_iounmap: + fjes_hw_iounmap(hw); + return ret; } void fjes_hw_exit(struct fjes_hw *hw) From 48f9277680925e1a8623d6b2c50aadb7af824ace Mon Sep 17 00:00:00 2001 From: Ankit Garg Date: Fri, 19 Dec 2025 10:29:45 +0000 Subject: [PATCH 2002/3902] gve: defer interrupt enabling until NAPI registration commit 3d970eda003441f66551a91fda16478ac0711617 upstream. Currently, interrupts are automatically enabled immediately upon request. This allows interrupt to fire before the associated NAPI context is fully initialized and cause failures like below: [ 0.946369] Call Trace: [ 0.946369] [ 0.946369] __napi_poll+0x2a/0x1e0 [ 0.946369] net_rx_action+0x2f9/0x3f0 [ 0.946369] handle_softirqs+0xd6/0x2c0 [ 0.946369] ? handle_edge_irq+0xc1/0x1b0 [ 0.946369] __irq_exit_rcu+0xc3/0xe0 [ 0.946369] common_interrupt+0x81/0xa0 [ 0.946369] [ 0.946369] [ 0.946369] asm_common_interrupt+0x22/0x40 [ 0.946369] RIP: 0010:pv_native_safe_halt+0xb/0x10 Use the `IRQF_NO_AUTOEN` flag when requesting interrupts to prevent auto enablement and explicitly enable the interrupt in NAPI initialization path (and disable it during NAPI teardown). This ensures that interrupt lifecycle is strictly coupled with readiness of NAPI context. Cc: stable@vger.kernel.org Fixes: 1dfc2e46117e ("gve: Refactor napi add and remove functions") Signed-off-by: Ankit Garg Reviewed-by: Jordan Rhee Reviewed-by: Joshua Washington Signed-off-by: Harshitha Ramamurthy Link: https://patch.msgid.link/20251219102945.2193617-1-hramamurthy@google.com Signed-off-by: Paolo Abeni Signed-off-by: Greg Kroah-Hartman --- drivers/net/ethernet/google/gve/gve_main.c | 2 +- drivers/net/ethernet/google/gve/gve_utils.c | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/drivers/net/ethernet/google/gve/gve_main.c b/drivers/net/ethernet/google/gve/gve_main.c index 1be1b1ef31ee30..e240b7d22a3527 100644 --- a/drivers/net/ethernet/google/gve/gve_main.c +++ b/drivers/net/ethernet/google/gve/gve_main.c @@ -558,7 +558,7 @@ static int gve_alloc_notify_blocks(struct gve_priv *priv) block->priv = priv; err = request_irq(priv->msix_vectors[msix_idx].vector, gve_is_gqi(priv) ? gve_intr : gve_intr_dqo, - 0, block->name, block); + IRQF_NO_AUTOEN, block->name, block); if (err) { dev_err(&priv->pdev->dev, "Failed to receive msix vector %d\n", i); diff --git a/drivers/net/ethernet/google/gve/gve_utils.c b/drivers/net/ethernet/google/gve/gve_utils.c index ace9b8698021f0..b53b7fcdcdaf16 100644 --- a/drivers/net/ethernet/google/gve/gve_utils.c +++ b/drivers/net/ethernet/google/gve/gve_utils.c @@ -112,11 +112,13 @@ void gve_add_napi(struct gve_priv *priv, int ntfy_idx, netif_napi_add_locked(priv->dev, &block->napi, gve_poll); netif_napi_set_irq_locked(&block->napi, block->irq); + enable_irq(block->irq); } void gve_remove_napi(struct gve_priv *priv, int ntfy_idx) { struct gve_notify_block *block = &priv->ntfy_blocks[ntfy_idx]; + disable_irq(block->irq); netif_napi_del_locked(&block->napi); } From 35bb95c80ddd1a19f02185b6458442f6244e0529 Mon Sep 17 00:00:00 2001 From: Chenghao Duan Date: Wed, 31 Dec 2025 15:19:20 +0800 Subject: [PATCH 2003/3902] LoongArch: Refactor register restoration in ftrace_common_return commit 45cb47c628dfbd1994c619f3eac271a780602826 upstream. Refactor the register restoration sequence in the ftrace_common_return function to clearly distinguish between the logic of normal returns and direct call returns in function tracing scenarios. The logic is as follows: 1. In the case of a normal return, the execution flow returns to the traced function, and ftrace must ensure that the register data is consistent with the state when the function was entered. ra = parent return address; t0 = traced function return address. 2. In the case of a direct call return, the execution flow jumps to the custom trampoline function, and ftrace must ensure that the register data is consistent with the state when ftrace was entered. ra = traced function return address; t0 = parent return address. Cc: stable@vger.kernel.org Fixes: 9cdc3b6a299c ("LoongArch: ftrace: Add direct call support") Signed-off-by: Chenghao Duan Signed-off-by: Huacai Chen Signed-off-by: Greg Kroah-Hartman --- arch/loongarch/kernel/mcount_dyn.S | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/arch/loongarch/kernel/mcount_dyn.S b/arch/loongarch/kernel/mcount_dyn.S index d6b474ad1d5e55..5729c20e5b8b0e 100644 --- a/arch/loongarch/kernel/mcount_dyn.S +++ b/arch/loongarch/kernel/mcount_dyn.S @@ -94,7 +94,6 @@ SYM_INNER_LABEL(ftrace_graph_call, SYM_L_GLOBAL) * at the callsite, so there is no need to restore the T series regs. */ ftrace_common_return: - PTR_L ra, sp, PT_R1 PTR_L a0, sp, PT_R4 PTR_L a1, sp, PT_R5 PTR_L a2, sp, PT_R6 @@ -104,12 +103,17 @@ ftrace_common_return: PTR_L a6, sp, PT_R10 PTR_L a7, sp, PT_R11 PTR_L fp, sp, PT_R22 - PTR_L t0, sp, PT_ERA PTR_L t1, sp, PT_R13 - PTR_ADDI sp, sp, PT_SIZE bnez t1, .Ldirect + + PTR_L ra, sp, PT_R1 + PTR_L t0, sp, PT_ERA + PTR_ADDI sp, sp, PT_SIZE jr t0 .Ldirect: + PTR_L t0, sp, PT_R1 + PTR_L ra, sp, PT_ERA + PTR_ADDI sp, sp, PT_SIZE jr t1 SYM_CODE_END(ftrace_common) @@ -161,6 +165,8 @@ SYM_CODE_END(return_to_handler) #ifdef CONFIG_DYNAMIC_FTRACE_WITH_DIRECT_CALLS SYM_CODE_START(ftrace_stub_direct_tramp) UNWIND_HINT_UNDEFINED - jr t0 + move t1, ra + move ra, t0 + jr t1 SYM_CODE_END(ftrace_stub_direct_tramp) #endif /* CONFIG_DYNAMIC_FTRACE_WITH_DIRECT_CALLS */ From 2617bbdead6359de809022fea66b28e48784f040 Mon Sep 17 00:00:00 2001 From: Hengqi Chen Date: Wed, 31 Dec 2025 15:19:20 +0800 Subject: [PATCH 2004/3902] LoongArch: BPF: Zero-extend bpf_tail_call() index commit eb71f5c433e1c6dff089b315881dec40a88a7baf upstream. The bpf_tail_call() index should be treated as a u32 value. Let's zero-extend it to avoid calling wrong BPF progs. See similar fixes for x86 [1]) and arm64 ([2]) for more details. [1]: https://github.com/torvalds/linux/commit/90caccdd8cc0215705f18b92771b449b01e2474a [2]: https://github.com/torvalds/linux/commit/16338a9b3ac30740d49f5dfed81bac0ffa53b9c7 Cc: stable@vger.kernel.org Fixes: 5dc615520c4d ("LoongArch: Add BPF JIT support") Signed-off-by: Hengqi Chen Signed-off-by: Huacai Chen Signed-off-by: Greg Kroah-Hartman --- arch/loongarch/net/bpf_jit.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/arch/loongarch/net/bpf_jit.c b/arch/loongarch/net/bpf_jit.c index f97dc993640174..9d9948e549de47 100644 --- a/arch/loongarch/net/bpf_jit.c +++ b/arch/loongarch/net/bpf_jit.c @@ -280,6 +280,8 @@ static int emit_bpf_tail_call(struct jit_ctx *ctx, int insn) * goto out; */ tc_ninsn = insn ? ctx->offset[insn+1] - ctx->offset[insn] : ctx->offset[0]; + emit_zext_32(ctx, a2, true); + off = offsetof(struct bpf_array, map.max_entries); emit_insn(ctx, ldwu, t1, a1, off); /* bgeu $a2, $t1, jmp_offset */ From 321993a874f571a94b5a596f1132f798c663b56e Mon Sep 17 00:00:00 2001 From: Hengqi Chen Date: Wed, 31 Dec 2025 15:19:20 +0800 Subject: [PATCH 2005/3902] LoongArch: BPF: Sign extend kfunc call arguments commit 3f5a238f24d7b75f9efe324d3539ad388f58536e upstream. The kfunc calls are native calls so they should follow LoongArch calling conventions. Sign extend its arguments properly to avoid kernel panic. This is done by adding a new emit_abi_ext() helper. The emit_abi_ext() helper performs extension in place meaning a value already store in the target register (Note: this is different from the existing sign_extend() helper and thus we can't reuse it). Cc: stable@vger.kernel.org Fixes: 5dc615520c4d ("LoongArch: Add BPF JIT support") Signed-off-by: Hengqi Chen Signed-off-by: Huacai Chen Signed-off-by: Greg Kroah-Hartman --- arch/loongarch/net/bpf_jit.c | 16 ++++++++++++++++ arch/loongarch/net/bpf_jit.h | 26 ++++++++++++++++++++++++++ 2 files changed, 42 insertions(+) diff --git a/arch/loongarch/net/bpf_jit.c b/arch/loongarch/net/bpf_jit.c index 9d9948e549de47..4be8515197ce04 100644 --- a/arch/loongarch/net/bpf_jit.c +++ b/arch/loongarch/net/bpf_jit.c @@ -952,6 +952,22 @@ static int build_insn(const struct bpf_insn *insn, struct jit_ctx *ctx, bool ext emit_insn(ctx, ldd, REG_TCC, LOONGARCH_GPR_SP, tcc_ptr_off); } + if (insn->src_reg == BPF_PSEUDO_KFUNC_CALL) { + const struct btf_func_model *m; + int i; + + m = bpf_jit_find_kfunc_model(ctx->prog, insn); + if (!m) + return -EINVAL; + + for (i = 0; i < m->nr_args; i++) { + u8 reg = regmap[BPF_REG_1 + i]; + bool sign = m->arg_flags[i] & BTF_FMODEL_SIGNED_ARG; + + emit_abi_ext(ctx, reg, m->arg_size[i], sign); + } + } + move_addr(ctx, t1, func_addr); emit_insn(ctx, jirl, LOONGARCH_GPR_RA, t1, 0); diff --git a/arch/loongarch/net/bpf_jit.h b/arch/loongarch/net/bpf_jit.h index 5697158fd1645f..75b6330030a9d1 100644 --- a/arch/loongarch/net/bpf_jit.h +++ b/arch/loongarch/net/bpf_jit.h @@ -88,6 +88,32 @@ static inline void emit_sext_32(struct jit_ctx *ctx, enum loongarch_gpr reg, boo emit_insn(ctx, addiw, reg, reg, 0); } +/* Emit proper extension according to ABI requirements. + * Note that it requires a value of size `size` already resides in register `reg`. + */ +static inline void emit_abi_ext(struct jit_ctx *ctx, int reg, u8 size, bool sign) +{ + /* ABI requires unsigned char/short to be zero-extended */ + if (!sign && (size == 1 || size == 2)) + return; + + switch (size) { + case 1: + emit_insn(ctx, extwb, reg, reg); + break; + case 2: + emit_insn(ctx, extwh, reg, reg); + break; + case 4: + emit_insn(ctx, addiw, reg, reg, 0); + break; + case 8: + break; + default: + pr_warn("bpf_jit: invalid size %d for extension\n", size); + } +} + static inline void move_addr(struct jit_ctx *ctx, enum loongarch_gpr rd, u64 addr) { u64 imm_11_0, imm_31_12, imm_51_32, imm_63_52; From 8b3c00c060187dccd9961877ba328cd969522dba Mon Sep 17 00:00:00 2001 From: Chenghao Duan Date: Wed, 31 Dec 2025 15:19:20 +0800 Subject: [PATCH 2006/3902] LoongArch: BPF: Save return address register ra to t0 before trampoline commit d314e1f48260cef3f869e3edc02a02c8a48b08e1 upstream. Modify the build_prologue() function to ensure the return address register ra is saved to t0 before entering trampoline operations. This change ensures the accurate return address handling when a BPF program calls another BPF program, preventing errors in the BPF-to-BPF call chain. Cc: stable@vger.kernel.org Fixes: 677e6123e3d2 ("LoongArch: BPF: Disable trampoline for kernel module function trace") Signed-off-by: Chenghao Duan Signed-off-by: Huacai Chen Signed-off-by: Greg Kroah-Hartman --- arch/loongarch/net/bpf_jit.c | 1 + 1 file changed, 1 insertion(+) diff --git a/arch/loongarch/net/bpf_jit.c b/arch/loongarch/net/bpf_jit.c index 4be8515197ce04..1d09c0d8082e16 100644 --- a/arch/loongarch/net/bpf_jit.c +++ b/arch/loongarch/net/bpf_jit.c @@ -139,6 +139,7 @@ static void build_prologue(struct jit_ctx *ctx) stack_adjust = round_up(stack_adjust, 16); stack_adjust += bpf_stack_adjust; + move_reg(ctx, LOONGARCH_GPR_T0, LOONGARCH_GPR_RA); /* Reserve space for the move_imm + jirl instruction */ for (i = 0; i < LOONGARCH_LONG_JUMP_NINSNS; i++) emit_insn(ctx, nop); From 85fdef3dcc1007ba8619589fa38014628f88854f Mon Sep 17 00:00:00 2001 From: Chenghao Duan Date: Wed, 31 Dec 2025 15:19:21 +0800 Subject: [PATCH 2007/3902] LoongArch: BPF: Enable trampoline-based tracing for module functions commit 26138762d9a27a7f1c33f467c4123c600f64a36e upstream. Remove the previous restrictions that blocked the tracing of kernel module functions. Fix the issue that previously caused kernel lockups when attempting to trace module functions. Before entering the trampoline code, the return address register ra shall store the address of the next assembly instruction after the 'bl trampoline' instruction, which is the traced function address, and the register t0 shall store the parent function return address. Refine the trampoline return logic to ensure that register data remains correct when returning to both the traced function and the parent function. Before this patch was applied, the module_attach test in selftests/bpf encountered a deadlock issue. This was caused by an incorrect jump address after the trampoline execution, which resulted in an infinite loop within the module function. Cc: stable@vger.kernel.org Fixes: 677e6123e3d2 ("LoongArch: BPF: Disable trampoline for kernel module function trace") Signed-off-by: Chenghao Duan Signed-off-by: Huacai Chen Signed-off-by: Greg Kroah-Hartman --- arch/loongarch/net/bpf_jit.c | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/arch/loongarch/net/bpf_jit.c b/arch/loongarch/net/bpf_jit.c index 1d09c0d8082e16..39753ce2d017e9 100644 --- a/arch/loongarch/net/bpf_jit.c +++ b/arch/loongarch/net/bpf_jit.c @@ -1284,7 +1284,7 @@ static int emit_jump_or_nops(void *target, void *ip, u32 *insns, bool is_call) return 0; } - return emit_jump_and_link(&ctx, is_call ? LOONGARCH_GPR_T0 : LOONGARCH_GPR_ZERO, (u64)target); + return emit_jump_and_link(&ctx, is_call ? LOONGARCH_GPR_RA : LOONGARCH_GPR_ZERO, (u64)target); } static int emit_call(struct jit_ctx *ctx, u64 addr) @@ -1638,14 +1638,12 @@ static int __arch_prepare_bpf_trampoline(struct jit_ctx *ctx, struct bpf_tramp_i /* To traced function */ /* Ftrace jump skips 2 NOP instructions */ - if (is_kernel_text((unsigned long)orig_call)) + if (is_kernel_text((unsigned long)orig_call) || + is_module_text_address((unsigned long)orig_call)) orig_call += LOONGARCH_FENTRY_NBYTES; /* Direct jump skips 5 NOP instructions */ else if (is_bpf_text_address((unsigned long)orig_call)) orig_call += LOONGARCH_BPF_FENTRY_NBYTES; - /* Module tracing not supported - cause kernel lockups */ - else if (is_module_text_address((unsigned long)orig_call)) - return -ENOTSUPP; if (flags & BPF_TRAMP_F_CALL_ORIG) { move_addr(ctx, LOONGARCH_GPR_A0, (const u64)im); @@ -1738,12 +1736,16 @@ static int __arch_prepare_bpf_trampoline(struct jit_ctx *ctx, struct bpf_tramp_i emit_insn(ctx, ldd, LOONGARCH_GPR_FP, LOONGARCH_GPR_SP, 0); emit_insn(ctx, addid, LOONGARCH_GPR_SP, LOONGARCH_GPR_SP, 16); - if (flags & BPF_TRAMP_F_SKIP_FRAME) + if (flags & BPF_TRAMP_F_SKIP_FRAME) { /* return to parent function */ - emit_insn(ctx, jirl, LOONGARCH_GPR_ZERO, LOONGARCH_GPR_RA, 0); - else - /* return to traced function */ + move_reg(ctx, LOONGARCH_GPR_RA, LOONGARCH_GPR_T0); emit_insn(ctx, jirl, LOONGARCH_GPR_ZERO, LOONGARCH_GPR_T0, 0); + } else { + /* return to traced function */ + move_reg(ctx, LOONGARCH_GPR_T1, LOONGARCH_GPR_RA); + move_reg(ctx, LOONGARCH_GPR_RA, LOONGARCH_GPR_T0); + emit_insn(ctx, jirl, LOONGARCH_GPR_ZERO, LOONGARCH_GPR_T1, 0); + } } ret = ctx->idx; From a6493ebd6dd5160764fb8d784a0e28b14b8a4d7b Mon Sep 17 00:00:00 2001 From: Chenghao Duan Date: Wed, 31 Dec 2025 15:19:21 +0800 Subject: [PATCH 2008/3902] LoongArch: BPF: Adjust the jump offset of tail calls commit 61319d15a56093358c6822d30659fe2941f589f1 upstream. Call the next bpf prog and skip the first instruction of TCC initialization. A total of 7 instructions are skipped: 'move t0, ra' 1 inst 'move_imm + jirl' 5 inst 'addid REG_TCC, zero, 0' 1 inst Relevant test cases: the tailcalls test item in selftests/bpf. Cc: stable@vger.kernel.org Fixes: 677e6123e3d2 ("LoongArch: BPF: Disable trampoline for kernel module function trace") Signed-off-by: Chenghao Duan Signed-off-by: Huacai Chen Signed-off-by: Greg Kroah-Hartman --- arch/loongarch/net/bpf_jit.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/arch/loongarch/net/bpf_jit.c b/arch/loongarch/net/bpf_jit.c index 39753ce2d017e9..95c214e2cf096a 100644 --- a/arch/loongarch/net/bpf_jit.c +++ b/arch/loongarch/net/bpf_jit.c @@ -239,7 +239,7 @@ static void __build_epilogue(struct jit_ctx *ctx, bool is_tail_call) * Call the next bpf prog and skip the first instruction * of TCC initialization. */ - emit_insn(ctx, jirl, LOONGARCH_GPR_ZERO, LOONGARCH_GPR_T3, 6); + emit_insn(ctx, jirl, LOONGARCH_GPR_ZERO, LOONGARCH_GPR_T3, 7); } } From c07dc84ed67c5a182273171639bacbbb87c12175 Mon Sep 17 00:00:00 2001 From: Chuck Lever Date: Mon, 1 Dec 2025 17:09:55 -0500 Subject: [PATCH 2009/3902] nfsd: fix nfsd_file reference leak in nfsd4_add_rdaccess_to_wrdeleg() commit 8072e34e1387d03102b788677d491e2bcceef6f5 upstream. nfsd4_add_rdaccess_to_wrdeleg() unconditionally overwrites fp->fi_fds[O_RDONLY] with a newly acquired nfsd_file. However, if the client already has a SHARE_ACCESS_READ open from a previous OPEN operation, this action overwrites the existing pointer without releasing its reference, orphaning the previous reference. Additionally, the function originally stored the same nfsd_file pointer in both fp->fi_fds[O_RDONLY] and fp->fi_rdeleg_file with only a single reference. When put_deleg_file() runs, it clears fi_rdeleg_file and calls nfs4_file_put_access() to release the file. However, nfs4_file_put_access() only releases fi_fds[O_RDONLY] when the fi_access[O_RDONLY] counter drops to zero. If another READ open exists on the file, the counter remains elevated and the nfsd_file reference from the delegation is never released. This potentially causes open conflicts on that file. Then, on server shutdown, these leaks cause __nfsd_file_cache_purge() to encounter files with an elevated reference count that cannot be cleaned up, ultimately triggering a BUG() in kmem_cache_destroy() because there are still nfsd_file objects allocated in that cache. Fixes: e7a8ebc305f2 ("NFSD: Offer write delegation for OPEN with OPEN4_SHARE_ACCESS_WRITE") Cc: stable@vger.kernel.org Reviewed-by: Jeff Layton Signed-off-by: Chuck Lever Signed-off-by: Greg Kroah-Hartman --- fs/nfsd/nfs4state.c | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/fs/nfsd/nfs4state.c b/fs/nfsd/nfs4state.c index 8a696050021741..302974c922234a 100644 --- a/fs/nfsd/nfs4state.c +++ b/fs/nfsd/nfs4state.c @@ -1218,8 +1218,10 @@ static void put_deleg_file(struct nfs4_file *fp) if (nf) nfsd_file_put(nf); - if (rnf) + if (rnf) { + nfsd_file_put(rnf); nfs4_file_put_access(fp, NFS4_SHARE_ACCESS_READ); + } } static void nfsd4_finalize_deleg_timestamps(struct nfs4_delegation *dp, struct file *f) @@ -6253,10 +6255,14 @@ nfsd4_add_rdaccess_to_wrdeleg(struct svc_rqst *rqstp, struct nfsd4_open *open, fp = stp->st_stid.sc_file; spin_lock(&fp->fi_lock); __nfs4_file_get_access(fp, NFS4_SHARE_ACCESS_READ); - fp = stp->st_stid.sc_file; - fp->fi_fds[O_RDONLY] = nf; - fp->fi_rdeleg_file = nf; + if (!fp->fi_fds[O_RDONLY]) { + fp->fi_fds[O_RDONLY] = nf; + nf = NULL; + } + fp->fi_rdeleg_file = nfsd_file_get(fp->fi_fds[O_RDONLY]); spin_unlock(&fp->fi_lock); + if (nf) + nfsd_file_put(nf); } return true; } From 1285073c878aac838dfadb6e19acdd65c30c2a76 Mon Sep 17 00:00:00 2001 From: Jeff Layton Date: Wed, 3 Dec 2025 10:52:15 -0500 Subject: [PATCH 2010/3902] nfsd: use ATTR_DELEG in nfsd4_finalize_deleg_timestamps() commit 8f9e967830ff32ab7756f530a36adf74a9f12b76 upstream. When finalizing timestamps that have never been updated and preparing to release the delegation lease, the notify_change() call can trigger a delegation break, and fail to update the timestamps. When this happens, there will be messages like this in dmesg: [ 2709.375785] Unable to update timestamps on inode 00:39:263: -11 Since this code is going to release the lease just after updating the timestamps, breaking the delegation is undesirable. Fix this by setting ATTR_DELEG in ia_valid, in order to avoid the delegation break. Fixes: e5e9b24ab8fa ("nfsd: freeze c/mtime updates with outstanding WRITE_ATTRS delegation") Cc: stable@vger.kernel.org Signed-off-by: Jeff Layton Signed-off-by: Chuck Lever Signed-off-by: Greg Kroah-Hartman --- fs/nfsd/nfs4state.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fs/nfsd/nfs4state.c b/fs/nfsd/nfs4state.c index 302974c922234a..e3f33d3e291cff 100644 --- a/fs/nfsd/nfs4state.c +++ b/fs/nfsd/nfs4state.c @@ -1226,7 +1226,7 @@ static void put_deleg_file(struct nfs4_file *fp) static void nfsd4_finalize_deleg_timestamps(struct nfs4_delegation *dp, struct file *f) { - struct iattr ia = { .ia_valid = ATTR_ATIME | ATTR_CTIME | ATTR_MTIME }; + struct iattr ia = { .ia_valid = ATTR_ATIME | ATTR_CTIME | ATTR_MTIME | ATTR_DELEG }; struct inode *inode = file_inode(f); int ret; From a78ee50545ee6dbf587764c72581c341dddef2cd Mon Sep 17 00:00:00 2001 From: Haoxiang Li Date: Sat, 6 Dec 2025 15:38:42 +0800 Subject: [PATCH 2011/3902] nfsd: Drop the client reference in client_states_open() commit 1f941b2c23fd34c6f3b76d36f9d0a2528fa92b8f upstream. In error path, call drop_client() to drop the reference obtained by get_nfsdfs_clp(). Fixes: 78599c42ae3c ("nfsd4: add file to display list of client's opens") Cc: stable@vger.kernel.org Reviewed-by: Jeff Layton Signed-off-by: Haoxiang Li Signed-off-by: Chuck Lever Signed-off-by: Greg Kroah-Hartman --- fs/nfsd/nfs4state.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/fs/nfsd/nfs4state.c b/fs/nfsd/nfs4state.c index e3f33d3e291cff..bdd20fddbb9860 100644 --- a/fs/nfsd/nfs4state.c +++ b/fs/nfsd/nfs4state.c @@ -3099,8 +3099,10 @@ static int client_states_open(struct inode *inode, struct file *file) return -ENXIO; ret = seq_open(file, &states_seq_ops); - if (ret) + if (ret) { + drop_client(clp); return ret; + } s = file->private_data; s->private = clp; return 0; From c9a8cd3ac85cdf5de80c8a45536974f4f6a64bbe Mon Sep 17 00:00:00 2001 From: Ethan Nelson-Moore Date: Sun, 21 Dec 2025 00:24:00 -0800 Subject: [PATCH 2012/3902] net: usb: sr9700: fix incorrect command used to write single register commit fa0b198be1c6775bc7804731a43be5d899d19e7a upstream. This fixes the device failing to initialize with "error reading MAC address" for me, probably because the incorrect write of NCR_RST to SR_NCR is not actually resetting the device. Fixes: c9b37458e95629b1d1171457afdcc1bf1eb7881d ("USB2NET : SR9700 : One chip USB 1.1 USB2NET SR9700Device Driver Support") Cc: stable@vger.kernel.org Signed-off-by: Ethan Nelson-Moore Link: https://patch.msgid.link/20251221082400.50688-1-enelsonmoore@gmail.com Signed-off-by: Paolo Abeni Signed-off-by: Greg Kroah-Hartman --- drivers/net/usb/sr9700.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/net/usb/sr9700.c b/drivers/net/usb/sr9700.c index 091bc2aca7e8e7..5d97e95a17b0df 100644 --- a/drivers/net/usb/sr9700.c +++ b/drivers/net/usb/sr9700.c @@ -52,7 +52,7 @@ static int sr_read_reg(struct usbnet *dev, u8 reg, u8 *value) static int sr_write_reg(struct usbnet *dev, u8 reg, u8 value) { - return usbnet_write_cmd(dev, SR_WR_REGS, SR_REQ_WR_REG, + return usbnet_write_cmd(dev, SR_WR_REG, SR_REQ_WR_REG, value, reg, NULL, 0); } @@ -65,7 +65,7 @@ static void sr_write_async(struct usbnet *dev, u8 reg, u16 length, static void sr_write_reg_async(struct usbnet *dev, u8 reg, u8 value) { - usbnet_write_cmd_async(dev, SR_WR_REGS, SR_REQ_WR_REG, + usbnet_write_cmd_async(dev, SR_WR_REG, SR_REQ_WR_REG, value, reg, NULL, 0); } From 2431c1e4765a93f9358da530d54a5c20d3f5ed5a Mon Sep 17 00:00:00 2001 From: Miaoqian Lin Date: Thu, 11 Dec 2025 12:13:13 +0400 Subject: [PATCH 2013/3902] net: phy: mediatek: fix nvmem cell reference leak in mt798x_phy_calibration commit 1e5a541420b8c6d87d88eb50b6b978cdeafee1c9 upstream. When nvmem_cell_read() fails in mt798x_phy_calibration(), the function returns without calling nvmem_cell_put(), leaking the cell reference. Move nvmem_cell_put() right after nvmem_cell_read() to ensure the cell reference is always released regardless of the read result. Found via static analysis and code review. Fixes: 98c485eaf509 ("net: phy: add driver for MediaTek SoC built-in GE PHYs") Cc: stable@vger.kernel.org Signed-off-by: Miaoqian Lin Reviewed-by: Daniel Golle Reviewed-by: Andrew Lunn Link: https://patch.msgid.link/20251211081313.2368460-1-linmq006@gmail.com Signed-off-by: Paolo Abeni Signed-off-by: Greg Kroah-Hartman --- drivers/net/phy/mediatek/mtk-ge-soc.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/net/phy/mediatek/mtk-ge-soc.c b/drivers/net/phy/mediatek/mtk-ge-soc.c index cd09fbf92ef23a..2c4bbc236202b7 100644 --- a/drivers/net/phy/mediatek/mtk-ge-soc.c +++ b/drivers/net/phy/mediatek/mtk-ge-soc.c @@ -1167,9 +1167,9 @@ static int mt798x_phy_calibration(struct phy_device *phydev) } buf = (u32 *)nvmem_cell_read(cell, &len); + nvmem_cell_put(cell); if (IS_ERR(buf)) return PTR_ERR(buf); - nvmem_cell_put(cell); if (!buf[0] || !buf[1] || !buf[2] || !buf[3] || len < 4 * sizeof(u32)) { phydev_err(phydev, "invalid efuse data\n"); From f3a8a7c1aa278f2378b2f3a10500c6674dffdfda Mon Sep 17 00:00:00 2001 From: Deepanshu Kartikey Date: Thu, 18 Dec 2025 06:53:54 +0530 Subject: [PATCH 2014/3902] net: nfc: fix deadlock between nfc_unregister_device and rfkill_fop_write commit 1ab526d97a57e44d26fadcc0e9adeb9c0c0182f5 upstream. A deadlock can occur between nfc_unregister_device() and rfkill_fop_write() due to lock ordering inversion between device_lock and rfkill_global_mutex. The problematic lock order is: Thread A (rfkill_fop_write): rfkill_fop_write() mutex_lock(&rfkill_global_mutex) rfkill_set_block() nfc_rfkill_set_block() nfc_dev_down() device_lock(&dev->dev) <- waits for device_lock Thread B (nfc_unregister_device): nfc_unregister_device() device_lock(&dev->dev) rfkill_unregister() mutex_lock(&rfkill_global_mutex) <- waits for rfkill_global_mutex This creates a classic ABBA deadlock scenario. Fix this by moving rfkill_unregister() and rfkill_destroy() outside the device_lock critical section. Store the rfkill pointer in a local variable before releasing the lock, then call rfkill_unregister() after releasing device_lock. This change is safe because rfkill_fop_write() holds rfkill_global_mutex while calling the rfkill callbacks, and rfkill_unregister() also acquires rfkill_global_mutex before cleanup. Therefore, rfkill_unregister() will wait for any ongoing callback to complete before proceeding, and device_del() is only called after rfkill_unregister() returns, preventing any use-after-free. The similar lock ordering in nfc_register_device() (device_lock -> rfkill_global_mutex via rfkill_register) is safe because during registration the device is not yet in rfkill_list, so no concurrent rfkill operations can occur on this device. Fixes: 3e3b5dfcd16a ("NFC: reorder the logic in nfc_{un,}register_device") Cc: stable@vger.kernel.org Reported-by: syzbot+4ef89409a235d804c6c2@syzkaller.appspotmail.com Closes: https://syzkaller.appspot.com/bug?extid=4ef89409a235d804c6c2 Link: https://lore.kernel.org/all/20251217054908.178907-1-kartikey406@gmail.com/T/ [v1] Signed-off-by: Deepanshu Kartikey Reviewed-by: Krzysztof Kozlowski Link: https://patch.msgid.link/20251218012355.279940-1-kartikey406@gmail.com Signed-off-by: Paolo Abeni Signed-off-by: Greg Kroah-Hartman --- net/nfc/core.c | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/net/nfc/core.c b/net/nfc/core.c index ae1c842f9c6427..82f023f377541b 100644 --- a/net/nfc/core.c +++ b/net/nfc/core.c @@ -1154,6 +1154,7 @@ EXPORT_SYMBOL(nfc_register_device); void nfc_unregister_device(struct nfc_dev *dev) { int rc; + struct rfkill *rfk = NULL; pr_debug("dev_name=%s\n", dev_name(&dev->dev)); @@ -1164,13 +1165,17 @@ void nfc_unregister_device(struct nfc_dev *dev) device_lock(&dev->dev); if (dev->rfkill) { - rfkill_unregister(dev->rfkill); - rfkill_destroy(dev->rfkill); + rfk = dev->rfkill; dev->rfkill = NULL; } dev->shutting_down = true; device_unlock(&dev->dev); + if (rfk) { + rfkill_unregister(rfk); + rfkill_destroy(rfk); + } + if (dev->ops->check_presence) { timer_delete_sync(&dev->check_pres_timer); cancel_work_sync(&dev->check_pres_work); From 64b6cd5f7b5abcd4f4552b43664d33abafd4c67f Mon Sep 17 00:00:00 2001 From: Xiaolei Wang Date: Mon, 22 Dec 2025 09:56:24 +0800 Subject: [PATCH 2015/3902] net: macb: Relocate mog_init_rings() callback from macb_mac_link_up() to macb_open() commit 99537d5c476cada9cf75aef9fa75579a31faadb9 upstream. In the non-RT kernel, local_bh_disable() merely disables preemption, whereas it maps to an actual spin lock in the RT kernel. Consequently, when attempting to refill RX buffers via netdev_alloc_skb() in macb_mac_link_up(), a deadlock scenario arises as follows: WARNING: possible circular locking dependency detected 6.18.0-08691-g2061f18ad76e #39 Not tainted ------------------------------------------------------ kworker/0:0/8 is trying to acquire lock: ffff00080369bbe0 (&bp->lock){+.+.}-{3:3}, at: macb_start_xmit+0x808/0xb7c but task is already holding lock: ffff000803698e58 (&queue->tx_ptr_lock){+...}-{3:3}, at: macb_start_xmit +0x148/0xb7c which lock already depends on the new lock. the existing dependency chain (in reverse order) is: -> #3 (&queue->tx_ptr_lock){+...}-{3:3}: rt_spin_lock+0x50/0x1f0 macb_start_xmit+0x148/0xb7c dev_hard_start_xmit+0x94/0x284 sch_direct_xmit+0x8c/0x37c __dev_queue_xmit+0x708/0x1120 neigh_resolve_output+0x148/0x28c ip6_finish_output2+0x2c0/0xb2c __ip6_finish_output+0x114/0x308 ip6_output+0xc4/0x4a4 mld_sendpack+0x220/0x68c mld_ifc_work+0x2a8/0x4f4 process_one_work+0x20c/0x5f8 worker_thread+0x1b0/0x35c kthread+0x144/0x200 ret_from_fork+0x10/0x20 -> #2 (_xmit_ETHER#2){+...}-{3:3}: rt_spin_lock+0x50/0x1f0 sch_direct_xmit+0x11c/0x37c __dev_queue_xmit+0x708/0x1120 neigh_resolve_output+0x148/0x28c ip6_finish_output2+0x2c0/0xb2c __ip6_finish_output+0x114/0x308 ip6_output+0xc4/0x4a4 mld_sendpack+0x220/0x68c mld_ifc_work+0x2a8/0x4f4 process_one_work+0x20c/0x5f8 worker_thread+0x1b0/0x35c kthread+0x144/0x200 ret_from_fork+0x10/0x20 -> #1 ((softirq_ctrl.lock)){+.+.}-{3:3}: lock_release+0x250/0x348 __local_bh_enable_ip+0x7c/0x240 __netdev_alloc_skb+0x1b4/0x1d8 gem_rx_refill+0xdc/0x240 gem_init_rings+0xb4/0x108 macb_mac_link_up+0x9c/0x2b4 phylink_resolve+0x170/0x614 process_one_work+0x20c/0x5f8 worker_thread+0x1b0/0x35c kthread+0x144/0x200 ret_from_fork+0x10/0x20 -> #0 (&bp->lock){+.+.}-{3:3}: __lock_acquire+0x15a8/0x2084 lock_acquire+0x1cc/0x350 rt_spin_lock+0x50/0x1f0 macb_start_xmit+0x808/0xb7c dev_hard_start_xmit+0x94/0x284 sch_direct_xmit+0x8c/0x37c __dev_queue_xmit+0x708/0x1120 neigh_resolve_output+0x148/0x28c ip6_finish_output2+0x2c0/0xb2c __ip6_finish_output+0x114/0x308 ip6_output+0xc4/0x4a4 mld_sendpack+0x220/0x68c mld_ifc_work+0x2a8/0x4f4 process_one_work+0x20c/0x5f8 worker_thread+0x1b0/0x35c kthread+0x144/0x200 ret_from_fork+0x10/0x20 other info that might help us debug this: Chain exists of: &bp->lock --> _xmit_ETHER#2 --> &queue->tx_ptr_lock Possible unsafe locking scenario: CPU0 CPU1 ---- ---- lock(&queue->tx_ptr_lock); lock(_xmit_ETHER#2); lock(&queue->tx_ptr_lock); lock(&bp->lock); *** DEADLOCK *** Call trace: show_stack+0x18/0x24 (C) dump_stack_lvl+0xa0/0xf0 dump_stack+0x18/0x24 print_circular_bug+0x28c/0x370 check_noncircular+0x198/0x1ac __lock_acquire+0x15a8/0x2084 lock_acquire+0x1cc/0x350 rt_spin_lock+0x50/0x1f0 macb_start_xmit+0x808/0xb7c dev_hard_start_xmit+0x94/0x284 sch_direct_xmit+0x8c/0x37c __dev_queue_xmit+0x708/0x1120 neigh_resolve_output+0x148/0x28c ip6_finish_output2+0x2c0/0xb2c __ip6_finish_output+0x114/0x308 ip6_output+0xc4/0x4a4 mld_sendpack+0x220/0x68c mld_ifc_work+0x2a8/0x4f4 process_one_work+0x20c/0x5f8 worker_thread+0x1b0/0x35c kthread+0x144/0x200 ret_from_fork+0x10/0x20 Notably, invoking the mog_init_rings() callback upon link establishment is unnecessary. Instead, we can exclusively call mog_init_rings() within the ndo_open() callback. This adjustment resolves the deadlock issue. Furthermore, since MACB_CAPS_MACB_IS_EMAC cases do not use mog_init_rings() when opening the network interface via at91ether_open(), moving mog_init_rings() to macb_open() also eliminates the MACB_CAPS_MACB_IS_EMAC check. Fixes: 633e98a711ac ("net: macb: use resolved link config in mac_link_up()") Cc: stable@vger.kernel.org Suggested-by: Kevin Hao Signed-off-by: Xiaolei Wang Link: https://patch.msgid.link/20251222015624.1994551-1-xiaolei.wang@windriver.com Signed-off-by: Paolo Abeni Signed-off-by: Greg Kroah-Hartman --- drivers/net/ethernet/cadence/macb_main.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/drivers/net/ethernet/cadence/macb_main.c b/drivers/net/ethernet/cadence/macb_main.c index ca2386b8347371..064fccdcf69945 100644 --- a/drivers/net/ethernet/cadence/macb_main.c +++ b/drivers/net/ethernet/cadence/macb_main.c @@ -744,7 +744,6 @@ static void macb_mac_link_up(struct phylink_config *config, /* Initialize rings & buffers as clearing MACB_BIT(TE) in link down * cleared the pipeline and control registers. */ - bp->macbgem_ops.mog_init_rings(bp); macb_init_buffers(bp); for (q = 0, queue = bp->queues; q < bp->num_queues; ++q, ++queue) @@ -2991,6 +2990,8 @@ static int macb_open(struct net_device *dev) goto pm_exit; } + bp->macbgem_ops.mog_init_rings(bp); + for (q = 0, queue = bp->queues; q < bp->num_queues; ++q, ++queue) { napi_enable(&queue->napi_rx); napi_enable(&queue->napi_tx); From ace965a386253c9a4eba1296d54b79a90f342a1c Mon Sep 17 00:00:00 2001 From: Armin Wolf Date: Sun, 28 Dec 2025 22:41:31 +0100 Subject: [PATCH 2016/3902] platform/x86: samsung-galaxybook: Fix problematic pointer cast MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit commit d37cd54ebeac37a763fbf303ed25f8a6e98328ff upstream. A user reported that reading the charge threshold on his device results in very strange values (like 78497792) being returned. The reason for this seems to be the fact that the driver casts the int pointer to an u8 pointer, leaving the last 3 bytes of the destination uninitialized. Fix this by using a temporary variable instead. Cc: stable@vger.kernel.org Fixes: 56f529ce4370 ("platform/x86: samsung-galaxybook: Add samsung-galaxybook driver") Reported-by: Gianni Ceccarelli Closes: https://lore.kernel.org/platform-driver-x86/20251228115556.14362d66@thenautilus.net/ Tested-by: Gianni Ceccarelli Signed-off-by: Armin Wolf Link: https://patch.msgid.link/20251228214217.35972-1-W_Armin@gmx.de Reviewed-by: Ilpo Järvinen Signed-off-by: Ilpo Järvinen Signed-off-by: Greg Kroah-Hartman --- drivers/platform/x86/samsung-galaxybook.c | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/drivers/platform/x86/samsung-galaxybook.c b/drivers/platform/x86/samsung-galaxybook.c index 3c13e13d488584..755cb82bdb606b 100644 --- a/drivers/platform/x86/samsung-galaxybook.c +++ b/drivers/platform/x86/samsung-galaxybook.c @@ -442,12 +442,13 @@ static int galaxybook_battery_ext_property_get(struct power_supply *psy, union power_supply_propval *val) { struct samsung_galaxybook *galaxybook = ext_data; + u8 value; int err; if (psp != POWER_SUPPLY_PROP_CHARGE_CONTROL_END_THRESHOLD) return -EINVAL; - err = charge_control_end_threshold_acpi_get(galaxybook, (u8 *)&val->intval); + err = charge_control_end_threshold_acpi_get(galaxybook, &value); if (err) return err; @@ -455,8 +456,10 @@ static int galaxybook_battery_ext_property_get(struct power_supply *psy, * device stores "no end threshold" as 0 instead of 100; * if device has 0, report 100 */ - if (val->intval == 0) - val->intval = 100; + if (value == 0) + value = 100; + + val->intval = value; return 0; } From d1cb71222a27a31f29ed777edb46069ec808ded4 Mon Sep 17 00:00:00 2001 From: Kurt Borja Date: Fri, 5 Dec 2025 13:50:10 -0500 Subject: [PATCH 2017/3902] platform/x86: alienware-wmi-wmax: Add support for new Area-51 laptops MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit commit 433f7744cb302ac22800dc0cd50494319ce64ba0 upstream. Add AWCC support for new Alienware Area-51 laptops. Cc: stable@vger.kernel.org Signed-off-by: Kurt Borja Reviewed-by: Ilpo Järvinen Link: https://patch.msgid.link/20251205-area-51-v1-1-d2cb13530851@gmail.com Signed-off-by: Ilpo Järvinen Signed-off-by: Greg Kroah-Hartman --- drivers/platform/x86/dell/alienware-wmi-wmax.c | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/drivers/platform/x86/dell/alienware-wmi-wmax.c b/drivers/platform/x86/dell/alienware-wmi-wmax.c index fadf7aac6779f7..b7b684fda22e0c 100644 --- a/drivers/platform/x86/dell/alienware-wmi-wmax.c +++ b/drivers/platform/x86/dell/alienware-wmi-wmax.c @@ -89,6 +89,22 @@ static struct awcc_quirks generic_quirks = { static struct awcc_quirks empty_quirks; static const struct dmi_system_id awcc_dmi_table[] __initconst = { + { + .ident = "Alienware 16 Area-51", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "Alienware"), + DMI_MATCH(DMI_PRODUCT_NAME, "Alienware 16 Area-51"), + }, + .driver_data = &g_series_quirks, + }, + { + .ident = "Alienware 18 Area-51", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "Alienware"), + DMI_MATCH(DMI_PRODUCT_NAME, "Alienware 18 Area-51"), + }, + .driver_data = &g_series_quirks, + }, { .ident = "Alienware 16 Aurora", .matches = { From 7ba7c591ce05f446f68343933978a6d07618f380 Mon Sep 17 00:00:00 2001 From: Kurt Borja Date: Fri, 5 Dec 2025 13:50:11 -0500 Subject: [PATCH 2018/3902] platform/x86: alienware-wmi-wmax: Add AWCC support for Alienware x16 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit commit a584644a490d276907e56817694859eaac2a4199 upstream. Add AWCC support for Alienware x16 laptops. Cc: stable@vger.kernel.org Signed-off-by: Kurt Borja Reviewed-by: Ilpo Järvinen Link: https://patch.msgid.link/20251205-area-51-v1-2-d2cb13530851@gmail.com Signed-off-by: Ilpo Järvinen Signed-off-by: Greg Kroah-Hartman --- drivers/platform/x86/dell/alienware-wmi-wmax.c | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/drivers/platform/x86/dell/alienware-wmi-wmax.c b/drivers/platform/x86/dell/alienware-wmi-wmax.c index b7b684fda22e0c..baea397e0530b7 100644 --- a/drivers/platform/x86/dell/alienware-wmi-wmax.c +++ b/drivers/platform/x86/dell/alienware-wmi-wmax.c @@ -177,6 +177,14 @@ static const struct dmi_system_id awcc_dmi_table[] __initconst = { }, .driver_data = &generic_quirks, }, + { + .ident = "Alienware x16", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "Alienware"), + DMI_MATCH(DMI_PRODUCT_NAME, "Alienware x16"), + }, + .driver_data = &g_series_quirks, + }, { .ident = "Alienware x17", .matches = { From 3dad9330f71f7f0d2d63cf4ddc28d9a1323ac5e7 Mon Sep 17 00:00:00 2001 From: Kurt Borja Date: Fri, 5 Dec 2025 13:50:12 -0500 Subject: [PATCH 2019/3902] platform/x86: alienware-wmi-wmax: Add support for Alienware 16X Aurora MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit commit 7f3c2499da24551968640528fee9aed3bb4f0c3f upstream. Add AWCC support for Alienware 16X Aurora laptops. Cc: stable@vger.kernel.org Signed-off-by: Kurt Borja Reviewed-by: Ilpo Järvinen Link: https://patch.msgid.link/20251205-area-51-v1-3-d2cb13530851@gmail.com Signed-off-by: Ilpo Järvinen Signed-off-by: Greg Kroah-Hartman --- drivers/platform/x86/dell/alienware-wmi-wmax.c | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/drivers/platform/x86/dell/alienware-wmi-wmax.c b/drivers/platform/x86/dell/alienware-wmi-wmax.c index baea397e0530b7..01af6dde9057f6 100644 --- a/drivers/platform/x86/dell/alienware-wmi-wmax.c +++ b/drivers/platform/x86/dell/alienware-wmi-wmax.c @@ -97,6 +97,14 @@ static const struct dmi_system_id awcc_dmi_table[] __initconst = { }, .driver_data = &g_series_quirks, }, + { + .ident = "Alienware 16X Aurora", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "Alienware"), + DMI_MATCH(DMI_PRODUCT_NAME, "Alienware 16X Aurora"), + }, + .driver_data = &g_series_quirks, + }, { .ident = "Alienware 18 Area-51", .matches = { From 60da8c352cdc37d6587977da7c9ac186e14dc0ff Mon Sep 17 00:00:00 2001 From: "Mario Limonciello (AMD)" Date: Sat, 29 Nov 2025 19:46:31 -0600 Subject: [PATCH 2020/3902] Revert "drm/amd: Skip power ungate during suspend for VPE" commit 3925683515e93844be204381d2d5a1df5de34f31 upstream. Skipping power ungate exposed some scenarios that will fail like below: ``` amdgpu: Register(0) [regVPEC_QUEUE_RESET_REQ] failed to reach value 0x00000000 != 0x00000001n amdgpu 0000:c1:00.0: amdgpu: VPE queue reset failed ... amdgpu: [drm] *ERROR* wait_for_completion_timeout timeout! ``` The underlying s2idle issue that prompted this commit is going to be fixed in BIOS. This reverts commit 2a6c826cfeedd7714611ac115371a959ead55bda. Fixes: 2a6c826cfeed ("drm/amd: Skip power ungate during suspend for VPE") Cc: stable@vger.kernel.org Signed-off-by: Mario Limonciello (AMD) Acked-by: Alex Deucher Reported-by: Konstantin Closes: https://bugzilla.kernel.org/show_bug.cgi?id=220812 Reported-by: Matthew Schwartz Signed-off-by: Alex Deucher Signed-off-by: Greg Kroah-Hartman --- drivers/gpu/drm/amd/amdgpu/amdgpu_device.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_device.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_device.c index 843770e61e4211..0cba31ec025c07 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_device.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_device.c @@ -3416,11 +3416,10 @@ int amdgpu_device_set_pg_state(struct amdgpu_device *adev, (adev->ip_blocks[i].version->type == AMD_IP_BLOCK_TYPE_GFX || adev->ip_blocks[i].version->type == AMD_IP_BLOCK_TYPE_SDMA)) continue; - /* skip CG for VCE/UVD/VPE, it's handled specially */ + /* skip CG for VCE/UVD, it's handled specially */ if (adev->ip_blocks[i].version->type != AMD_IP_BLOCK_TYPE_UVD && adev->ip_blocks[i].version->type != AMD_IP_BLOCK_TYPE_VCE && adev->ip_blocks[i].version->type != AMD_IP_BLOCK_TYPE_VCN && - adev->ip_blocks[i].version->type != AMD_IP_BLOCK_TYPE_VPE && adev->ip_blocks[i].version->type != AMD_IP_BLOCK_TYPE_JPEG && adev->ip_blocks[i].version->funcs->set_powergating_state) { /* enable powergating to save power */ From 8b74d0d7f6a512ed50571015d649cf9861008309 Mon Sep 17 00:00:00 2001 From: Alex Deucher Date: Thu, 13 Nov 2025 15:57:43 -0500 Subject: [PATCH 2021/3902] drm/amdgpu/gmc12: add amdgpu_vm_handle_fault() handling MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit commit ff28ff98db6a8eeb469e02fb8bd1647b353232a9 upstream. We need to call amdgpu_vm_handle_fault() on page fault on all gfx9 and newer parts to properly update the page tables, not just for recoverable page faults. Cc: stable@vger.kernel.org Reviewed-by: Timur Kristóf Signed-off-by: Alex Deucher Signed-off-by: Greg Kroah-Hartman --- drivers/gpu/drm/amd/amdgpu/gmc_v12_0.c | 27 ++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/drivers/gpu/drm/amd/amdgpu/gmc_v12_0.c b/drivers/gpu/drm/amd/amdgpu/gmc_v12_0.c index f4a19357ccbc65..3746d39182707f 100644 --- a/drivers/gpu/drm/amd/amdgpu/gmc_v12_0.c +++ b/drivers/gpu/drm/amd/amdgpu/gmc_v12_0.c @@ -91,6 +91,8 @@ static int gmc_v12_0_process_interrupt(struct amdgpu_device *adev, struct amdgpu_iv_entry *entry) { struct amdgpu_vmhub *hub; + bool retry_fault = !!(entry->src_data[1] & 0x80); + bool write_fault = !!(entry->src_data[1] & 0x20); uint32_t status = 0; u64 addr; @@ -102,6 +104,31 @@ static int gmc_v12_0_process_interrupt(struct amdgpu_device *adev, else hub = &adev->vmhub[AMDGPU_GFXHUB(0)]; + if (retry_fault) { + /* Returning 1 here also prevents sending the IV to the KFD */ + + /* Process it only if it's the first fault for this address */ + if (entry->ih != &adev->irq.ih_soft && + amdgpu_gmc_filter_faults(adev, entry->ih, addr, entry->pasid, + entry->timestamp)) + return 1; + + /* Delegate it to a different ring if the hardware hasn't + * already done it. + */ + if (entry->ih == &adev->irq.ih) { + amdgpu_irq_delegate(adev, entry, 8); + return 1; + } + + /* Try to handle the recoverable page faults by filling page + * tables + */ + if (amdgpu_vm_handle_fault(adev, entry->pasid, 0, 0, addr, + entry->timestamp, write_fault)) + return 1; + } + if (!amdgpu_sriov_vf(adev)) { /* * Issue a dummy read to wait for the status register to From 0b48f98f092bd246964465f16f2e321ee619988c Mon Sep 17 00:00:00 2001 From: Natalie Vock Date: Mon, 1 Dec 2025 12:52:38 -0500 Subject: [PATCH 2022/3902] drm/amdgpu: Forward VMID reservation errors MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit commit 8defb4f081a5feccc3ea8372d0c7af3522124e1f upstream. Otherwise userspace may be fooled into believing it has a reserved VMID when in reality it doesn't, ultimately leading to GPU hangs when SPM is used. Fixes: 80e709ee6ecc ("drm/amdgpu: add option params to enforce process isolation between graphics and compute") Cc: stable@vger.kernel.org Reviewed-by: Christian König Signed-off-by: Natalie Vock Signed-off-by: Alex Deucher Signed-off-by: Greg Kroah-Hartman --- drivers/gpu/drm/amd/amdgpu/amdgpu_vm.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_vm.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_vm.c index cdcafde3c71a13..3d2f9d0e2d2397 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_vm.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_vm.c @@ -2910,8 +2910,7 @@ int amdgpu_vm_ioctl(struct drm_device *dev, void *data, struct drm_file *filp) switch (args->in.op) { case AMDGPU_VM_OP_RESERVE_VMID: /* We only have requirement to reserve vmid from gfxhub */ - amdgpu_vmid_alloc_reserved(adev, vm, AMDGPU_GFXHUB(0)); - break; + return amdgpu_vmid_alloc_reserved(adev, vm, AMDGPU_GFXHUB(0)); case AMDGPU_VM_OP_UNRESERVE_VMID: amdgpu_vmid_free_reserved(adev, vm, AMDGPU_GFXHUB(0)); break; From 431b9cd093635b8a650df3fcf351180260d8c04b Mon Sep 17 00:00:00 2001 From: Pierre-Eric Pelloux-Prayer Date: Tue, 25 Nov 2025 10:48:39 +0100 Subject: [PATCH 2023/3902] drm/amdgpu: add missing lock to amdgpu_ttm_access_memory_sdma MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit commit 4fa944255be521b1bbd9780383f77206303a3a5c upstream. Users of ttm entities need to hold the gtt_window_lock before using them to guarantee proper ordering of jobs. Cc: stable@vger.kernel.org Fixes: cb5cc4f573e1 ("drm/amdgpu: improve debug VRAM access performance using sdma") Signed-off-by: Pierre-Eric Pelloux-Prayer Reviewed-by: Christian König Signed-off-by: Alex Deucher Signed-off-by: Greg Kroah-Hartman --- drivers/gpu/drm/amd/amdgpu/amdgpu_ttm.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_ttm.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_ttm.c index 9d568c16beb1e2..4183e5301cffc0 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_ttm.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_ttm.c @@ -1529,6 +1529,7 @@ static int amdgpu_ttm_access_memory_sdma(struct ttm_buffer_object *bo, if (r) goto out; + mutex_lock(&adev->mman.gtt_window_lock); amdgpu_res_first(abo->tbo.resource, offset, len, &src_mm); src_addr = amdgpu_ttm_domain_start(adev, bo->resource->mem_type) + src_mm.start; @@ -1543,6 +1544,7 @@ static int amdgpu_ttm_access_memory_sdma(struct ttm_buffer_object *bo, WARN_ON(job->ibs[0].length_dw > num_dw); fence = amdgpu_job_submit(job); + mutex_unlock(&adev->mman.gtt_window_lock); if (!dma_fence_wait_timeout(fence, false, adev->sdma_timeout)) r = -ETIMEDOUT; From 16a3106cff4aeac48f17dda67f3be8548300a563 Mon Sep 17 00:00:00 2001 From: Srinivasan Shanmugam Date: Tue, 25 Nov 2025 21:20:45 +0530 Subject: [PATCH 2024/3902] drm/amdgpu/sdma6: Update SDMA 6.0.3 FW version to include UMQ protected-fence fix MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit commit c8e7e3c2215e286ebfe66fe828ed426546c519e6 upstream. On GFX11.0.3, earlier SDMA firmware versions issue the PROTECTED_FENCE write from the user VMID (e.g. VMID 8) instead of VMID 0. This causes a GPU VM protection fault when SDMA tries to write the secure fence location, as seen in the UMQ SDMA test (cs-sdma-with-IP-DMA-UMQ) Fixes the below GPU page fault: [ 514.037189] amdgpu 0000:0b:00.0: amdgpu: [gfxhub] page fault (src_id:0 ring:40 vmid:8 pasid:32770) [ 514.037199] amdgpu 0000:0b:00.0: amdgpu: Process pid 0 thread pid 0 [ 514.037205] amdgpu 0000:0b:00.0: amdgpu: in page starting at address 0x00007fff00409000 from client 10 [ 514.037212] amdgpu 0000:0b:00.0: amdgpu: GCVM_L2_PROTECTION_FAULT_STATUS:0x00841A51 [ 514.037217] amdgpu 0000:0b:00.0: amdgpu: Faulty UTCL2 client ID: SDMA0 (0xd) [ 514.037223] amdgpu 0000:0b:00.0: amdgpu: MORE_FAULTS: 0x1 [ 514.037227] amdgpu 0000:0b:00.0: amdgpu: WALKER_ERROR: 0x0 [ 514.037232] amdgpu 0000:0b:00.0: amdgpu: PERMISSION_FAULTS: 0x5 [ 514.037236] amdgpu 0000:0b:00.0: amdgpu: MAPPING_ERROR: 0x0 [ 514.037241] amdgpu 0000:0b:00.0: amdgpu: RW: 0x1 v2: Updated commit message v3: s/gfx11.0.3/sdma 6.0.3/ in patch title (Alex) Cc: Alex Deucher Cc: Christian König Cc: stable@vger.kernel.org Signed-off-by: Srinivasan Shanmugam Reviewed-by: Alex Deucher Signed-off-by: Alex Deucher Signed-off-by: Greg Kroah-Hartman --- drivers/gpu/drm/amd/amdgpu/sdma_v6_0.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/gpu/drm/amd/amdgpu/sdma_v6_0.c b/drivers/gpu/drm/amd/amdgpu/sdma_v6_0.c index db6e41967f126b..3c6568d5019947 100644 --- a/drivers/gpu/drm/amd/amdgpu/sdma_v6_0.c +++ b/drivers/gpu/drm/amd/amdgpu/sdma_v6_0.c @@ -1389,7 +1389,7 @@ static int sdma_v6_0_sw_init(struct amdgpu_ip_block *ip_block) adev->userq_funcs[AMDGPU_HW_IP_DMA] = &userq_mes_funcs; break; case IP_VERSION(6, 0, 3): - if ((adev->sdma.instance[0].fw_version >= 27) && !adev->sdma.disable_uq) + if (adev->sdma.instance[0].fw_version >= 29 && !adev->sdma.disable_uq) adev->userq_funcs[AMDGPU_HW_IP_DMA] = &userq_mes_funcs; break; case IP_VERSION(6, 1, 0): From b89cd47c4c92b5864488ea80556191a7a1b7074e Mon Sep 17 00:00:00 2001 From: Alex Deucher Date: Thu, 13 Nov 2025 15:55:19 -0500 Subject: [PATCH 2025/3902] drm/amdgpu/gmc11: add amdgpu_vm_handle_fault() handling MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit commit 3f2289b56cd98f5741056bdb6e521324eff07ce5 upstream. We need to call amdgpu_vm_handle_fault() on page fault on all gfx9 and newer parts to properly update the page tables, not just for recoverable page faults. Cc: stable@vger.kernel.org Reviewed-by: Timur Kristóf Signed-off-by: Alex Deucher Signed-off-by: Greg Kroah-Hartman --- drivers/gpu/drm/amd/amdgpu/gmc_v11_0.c | 27 ++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/drivers/gpu/drm/amd/amdgpu/gmc_v11_0.c b/drivers/gpu/drm/amd/amdgpu/gmc_v11_0.c index 7bc389d9f5c487..8acc912e7e4cb3 100644 --- a/drivers/gpu/drm/amd/amdgpu/gmc_v11_0.c +++ b/drivers/gpu/drm/amd/amdgpu/gmc_v11_0.c @@ -103,12 +103,39 @@ static int gmc_v11_0_process_interrupt(struct amdgpu_device *adev, uint32_t vmhub_index = entry->client_id == SOC21_IH_CLIENTID_VMC ? AMDGPU_MMHUB0(0) : AMDGPU_GFXHUB(0); struct amdgpu_vmhub *hub = &adev->vmhub[vmhub_index]; + bool retry_fault = !!(entry->src_data[1] & 0x80); + bool write_fault = !!(entry->src_data[1] & 0x20); uint32_t status = 0; u64 addr; addr = (u64)entry->src_data[0] << 12; addr |= ((u64)entry->src_data[1] & 0xf) << 44; + if (retry_fault) { + /* Returning 1 here also prevents sending the IV to the KFD */ + + /* Process it only if it's the first fault for this address */ + if (entry->ih != &adev->irq.ih_soft && + amdgpu_gmc_filter_faults(adev, entry->ih, addr, entry->pasid, + entry->timestamp)) + return 1; + + /* Delegate it to a different ring if the hardware hasn't + * already done it. + */ + if (entry->ih == &adev->irq.ih) { + amdgpu_irq_delegate(adev, entry, 8); + return 1; + } + + /* Try to handle the recoverable page faults by filling page + * tables + */ + if (amdgpu_vm_handle_fault(adev, entry->pasid, 0, 0, addr, + entry->timestamp, write_fault)) + return 1; + } + if (!amdgpu_sriov_vf(adev)) { /* * Issue a dummy read to wait for the status register to From 2c9ba2fbcd97c02c1cb23ec80290494a808f1690 Mon Sep 17 00:00:00 2001 From: Akhil P Oommen Date: Tue, 18 Nov 2025 14:20:28 +0530 Subject: [PATCH 2026/3902] drm/msm/a6xx: Fix out of bound IO access in a6xx_get_gmu_registers commit 779b68a5bf2764c8ed3aa800e41ba0d5d007e1e7 upstream. REG_A6XX_GMU_AO_AHB_FENCE_CTRL register falls under GMU's register range. So, use gmu_write() routines to write to this register. Fixes: 1707add81551 ("drm/msm/a6xx: Add a6xx gpu state") Cc: stable@vger.kernel.org Signed-off-by: Akhil P Oommen Reviewed-by: Konrad Dybcio Patchwork: https://patchwork.freedesktop.org/patch/688993/ Message-ID: <20251118-kaana-gpu-support-v4-1-86eeb8e93fb6@oss.qualcomm.com> Signed-off-by: Rob Clark Signed-off-by: Greg Kroah-Hartman --- drivers/gpu/drm/msm/adreno/a6xx_gpu_state.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/gpu/drm/msm/adreno/a6xx_gpu_state.c b/drivers/gpu/drm/msm/adreno/a6xx_gpu_state.c index 4c7f3c642f6ac0..9cec333e23e15b 100644 --- a/drivers/gpu/drm/msm/adreno/a6xx_gpu_state.c +++ b/drivers/gpu/drm/msm/adreno/a6xx_gpu_state.c @@ -1255,7 +1255,7 @@ static void a6xx_get_gmu_registers(struct msm_gpu *gpu, return; /* Set the fence to ALLOW mode so we can access the registers */ - gpu_write(gpu, REG_A6XX_GMU_AO_AHB_FENCE_CTRL, 0); + gmu_write(&a6xx_gpu->gmu, REG_A6XX_GMU_AO_AHB_FENCE_CTRL, 0); _a6xx_get_gmu_registers(gpu, a6xx_state, &a6xx_gmu_reglist[2], &a6xx_state->gmu_registers[3], false); From e983164f179e22f095db0842bb2a6d6495ea44df Mon Sep 17 00:00:00 2001 From: Arunpravin Paneer Selvam Date: Mon, 6 Oct 2025 15:21:22 +0530 Subject: [PATCH 2027/3902] drm/buddy: Optimize free block management with RB tree commit c178e534fff1d5a74da80ea03b20e2b948a00113 upstream. Replace the freelist (O(n)) used for free block management with a red-black tree, providing more efficient O(log n) search, insert, and delete operations. This improves scalability and performance when managing large numbers of free blocks per order (e.g., hundreds or thousands). In the VK-CTS memory stress subtest, the buddy manager merges fragmented memory and inserts freed blocks into the freelist. Since freelist insertion is O(n), this becomes a bottleneck as fragmentation increases. Benchmarking shows list_insert_sorted() consumes ~52.69% CPU with the freelist, compared to just 0.03% with the RB tree (rbtree_insert.isra.0), despite performing the same sorted insert. This also improves performance in heavily fragmented workloads, such as games or graphics tests that stress memory. As the buddy allocator evolves with new features such as clear-page tracking, the resulting fragmentation and complexity have grown. These RB-tree based design changes are introduced to address that growth and ensure the allocator continues to perform efficiently under fragmented conditions. The RB tree implementation with separate clear/dirty trees provides: - O(n log n) aggregate complexity for all operations instead of O(n^2) - Elimination of soft lockups and system instability - Improved code maintainability and clarity - Better scalability for large memory systems - Predictable performance under fragmentation v3(Matthew): - Remove RB_EMPTY_NODE check in force_merge function. - Rename rb for loop macros to have less generic names and move to .c file. - Make the rb node rb and link field as union. v4(Jani Nikula): - The kernel-doc comment should be "/**" - Move all the rbtree macros to rbtree.h and add parens to ensure correct precedence. v5: - Remove the inline in a .c file (Jani Nikula). v6(Peter Zijlstra): - Add rb_add() function replacing the existing rbtree_insert() code. v7: - A full walk iteration in rbtree is slower than the list (Peter Zijlstra). - The existing rbtree_postorder_for_each_entry_safe macro should be used in scenarios where traversal order is not a critical factor (Christian). v8(Matthew): - Remove the rbtree_is_empty() check in this patch as well. Cc: stable@vger.kernel.org Fixes: a68c7eaa7a8f ("drm/amdgpu: Enable clear page functionality") Signed-off-by: Arunpravin Paneer Selvam Reviewed-by: Matthew Auld Link: https://lore.kernel.org/r/20251006095124.1663-1-Arunpravin.PaneerSelvam@amd.com Signed-off-by: Greg Kroah-Hartman --- drivers/gpu/drm/drm_buddy.c | 195 ++++++++++++++++++++++-------------- include/drm/drm_buddy.h | 11 +- 2 files changed, 126 insertions(+), 80 deletions(-) diff --git a/drivers/gpu/drm/drm_buddy.c b/drivers/gpu/drm/drm_buddy.c index a94061f373de54..c87210a06c3184 100644 --- a/drivers/gpu/drm/drm_buddy.c +++ b/drivers/gpu/drm/drm_buddy.c @@ -14,6 +14,8 @@ static struct kmem_cache *slab_blocks; +#define rbtree_get_free_block(node) rb_entry((node), struct drm_buddy_block, rb) + static struct drm_buddy_block *drm_block_alloc(struct drm_buddy *mm, struct drm_buddy_block *parent, unsigned int order, @@ -31,6 +33,8 @@ static struct drm_buddy_block *drm_block_alloc(struct drm_buddy *mm, block->header |= order; block->parent = parent; + RB_CLEAR_NODE(&block->rb); + BUG_ON(block->header & DRM_BUDDY_HEADER_UNUSED); return block; } @@ -41,23 +45,49 @@ static void drm_block_free(struct drm_buddy *mm, kmem_cache_free(slab_blocks, block); } -static void list_insert_sorted(struct drm_buddy *mm, - struct drm_buddy_block *block) +static bool drm_buddy_block_offset_less(const struct drm_buddy_block *block, + const struct drm_buddy_block *node) { - struct drm_buddy_block *node; - struct list_head *head; + return drm_buddy_block_offset(block) < drm_buddy_block_offset(node); +} - head = &mm->free_list[drm_buddy_block_order(block)]; - if (list_empty(head)) { - list_add(&block->link, head); - return; - } +static bool rbtree_block_offset_less(struct rb_node *block, + const struct rb_node *node) +{ + return drm_buddy_block_offset_less(rbtree_get_free_block(block), + rbtree_get_free_block(node)); +} - list_for_each_entry(node, head, link) - if (drm_buddy_block_offset(block) < drm_buddy_block_offset(node)) - break; +static void rbtree_insert(struct drm_buddy *mm, + struct drm_buddy_block *block) +{ + rb_add(&block->rb, + &mm->free_tree[drm_buddy_block_order(block)], + rbtree_block_offset_less); +} + +static void rbtree_remove(struct drm_buddy *mm, + struct drm_buddy_block *block) +{ + struct rb_root *root; + + root = &mm->free_tree[drm_buddy_block_order(block)]; + rb_erase(&block->rb, root); + + RB_CLEAR_NODE(&block->rb); +} + +static struct drm_buddy_block * +rbtree_last_entry(struct drm_buddy *mm, unsigned int order) +{ + struct rb_node *node = rb_last(&mm->free_tree[order]); + + return node ? rb_entry(node, struct drm_buddy_block, rb) : NULL; +} - __list_add(&block->link, node->link.prev, &node->link); +static bool rbtree_is_empty(struct drm_buddy *mm, unsigned int order) +{ + return RB_EMPTY_ROOT(&mm->free_tree[order]); } static void clear_reset(struct drm_buddy_block *block) @@ -70,12 +100,13 @@ static void mark_cleared(struct drm_buddy_block *block) block->header |= DRM_BUDDY_HEADER_CLEAR; } -static void mark_allocated(struct drm_buddy_block *block) +static void mark_allocated(struct drm_buddy *mm, + struct drm_buddy_block *block) { block->header &= ~DRM_BUDDY_HEADER_STATE; block->header |= DRM_BUDDY_ALLOCATED; - list_del(&block->link); + rbtree_remove(mm, block); } static void mark_free(struct drm_buddy *mm, @@ -84,15 +115,16 @@ static void mark_free(struct drm_buddy *mm, block->header &= ~DRM_BUDDY_HEADER_STATE; block->header |= DRM_BUDDY_FREE; - list_insert_sorted(mm, block); + rbtree_insert(mm, block); } -static void mark_split(struct drm_buddy_block *block) +static void mark_split(struct drm_buddy *mm, + struct drm_buddy_block *block) { block->header &= ~DRM_BUDDY_HEADER_STATE; block->header |= DRM_BUDDY_SPLIT; - list_del(&block->link); + rbtree_remove(mm, block); } static inline bool overlaps(u64 s1, u64 e1, u64 s2, u64 e2) @@ -148,7 +180,7 @@ static unsigned int __drm_buddy_free(struct drm_buddy *mm, mark_cleared(parent); } - list_del(&buddy->link); + rbtree_remove(mm, buddy); if (force_merge && drm_buddy_block_is_clear(buddy)) mm->clear_avail -= drm_buddy_block_size(mm, buddy); @@ -179,13 +211,19 @@ static int __force_merge(struct drm_buddy *mm, return -EINVAL; for (i = min_order - 1; i >= 0; i--) { - struct drm_buddy_block *block, *prev; + struct rb_root *root = &mm->free_tree[i]; + struct rb_node *iter; + + iter = rb_last(root); - list_for_each_entry_safe_reverse(block, prev, &mm->free_list[i], link) { - struct drm_buddy_block *buddy; + while (iter) { + struct drm_buddy_block *block, *buddy; u64 block_start, block_end; - if (!block->parent) + block = rbtree_get_free_block(iter); + iter = rb_prev(iter); + + if (!block || !block->parent) continue; block_start = drm_buddy_block_offset(block); @@ -201,15 +239,10 @@ static int __force_merge(struct drm_buddy *mm, WARN_ON(drm_buddy_block_is_clear(block) == drm_buddy_block_is_clear(buddy)); - /* - * If the prev block is same as buddy, don't access the - * block in the next iteration as we would free the - * buddy block as part of the free function. - */ - if (prev == buddy) - prev = list_prev_entry(prev, link); + if (iter == &buddy->rb) + iter = rb_prev(iter); - list_del(&block->link); + rbtree_remove(mm, block); if (drm_buddy_block_is_clear(block)) mm->clear_avail -= drm_buddy_block_size(mm, block); @@ -237,7 +270,7 @@ static int __force_merge(struct drm_buddy *mm, int drm_buddy_init(struct drm_buddy *mm, u64 size, u64 chunk_size) { unsigned int i; - u64 offset; + u64 offset = 0; if (size < chunk_size) return -EINVAL; @@ -258,14 +291,14 @@ int drm_buddy_init(struct drm_buddy *mm, u64 size, u64 chunk_size) BUG_ON(mm->max_order > DRM_BUDDY_MAX_ORDER); - mm->free_list = kmalloc_array(mm->max_order + 1, - sizeof(struct list_head), + mm->free_tree = kmalloc_array(mm->max_order + 1, + sizeof(struct rb_root), GFP_KERNEL); - if (!mm->free_list) + if (!mm->free_tree) return -ENOMEM; for (i = 0; i <= mm->max_order; ++i) - INIT_LIST_HEAD(&mm->free_list[i]); + mm->free_tree[i] = RB_ROOT; mm->n_roots = hweight64(size); @@ -273,9 +306,8 @@ int drm_buddy_init(struct drm_buddy *mm, u64 size, u64 chunk_size) sizeof(struct drm_buddy_block *), GFP_KERNEL); if (!mm->roots) - goto out_free_list; + goto out_free_tree; - offset = 0; i = 0; /* @@ -312,8 +344,8 @@ int drm_buddy_init(struct drm_buddy *mm, u64 size, u64 chunk_size) while (i--) drm_block_free(mm, mm->roots[i]); kfree(mm->roots); -out_free_list: - kfree(mm->free_list); +out_free_tree: + kfree(mm->free_tree); return -ENOMEM; } EXPORT_SYMBOL(drm_buddy_init); @@ -323,7 +355,7 @@ EXPORT_SYMBOL(drm_buddy_init); * * @mm: DRM buddy manager to free * - * Cleanup memory manager resources and the freelist + * Cleanup memory manager resources and the freetree */ void drm_buddy_fini(struct drm_buddy *mm) { @@ -350,7 +382,7 @@ void drm_buddy_fini(struct drm_buddy *mm) WARN_ON(mm->avail != mm->size); kfree(mm->roots); - kfree(mm->free_list); + kfree(mm->free_tree); } EXPORT_SYMBOL(drm_buddy_fini); @@ -383,7 +415,7 @@ static int split_block(struct drm_buddy *mm, clear_reset(block); } - mark_split(block); + mark_split(mm, block); return 0; } @@ -412,7 +444,7 @@ EXPORT_SYMBOL(drm_get_buddy); * @is_clear: blocks clear state * * Reset the clear state based on @is_clear value for each block - * in the freelist. + * in the freetree. */ void drm_buddy_reset_clear(struct drm_buddy *mm, bool is_clear) { @@ -431,9 +463,9 @@ void drm_buddy_reset_clear(struct drm_buddy *mm, bool is_clear) } for (i = 0; i <= mm->max_order; ++i) { - struct drm_buddy_block *block; + struct drm_buddy_block *block, *tmp; - list_for_each_entry_reverse(block, &mm->free_list[i], link) { + rbtree_postorder_for_each_entry_safe(block, tmp, &mm->free_tree[i], rb) { if (is_clear != drm_buddy_block_is_clear(block)) { if (is_clear) { mark_cleared(block); @@ -639,14 +671,18 @@ get_maxblock(struct drm_buddy *mm, unsigned int order, unsigned int i; for (i = order; i <= mm->max_order; ++i) { + struct rb_node *iter = rb_last(&mm->free_tree[i]); struct drm_buddy_block *tmp_block; - list_for_each_entry_reverse(tmp_block, &mm->free_list[i], link) { - if (block_incompatible(tmp_block, flags)) - continue; + while (iter) { + tmp_block = rbtree_get_free_block(iter); - block = tmp_block; - break; + if (!block_incompatible(tmp_block, flags)) { + block = tmp_block; + break; + } + + iter = rb_prev(iter); } if (!block) @@ -667,7 +703,7 @@ get_maxblock(struct drm_buddy *mm, unsigned int order, } static struct drm_buddy_block * -alloc_from_freelist(struct drm_buddy *mm, +alloc_from_freetree(struct drm_buddy *mm, unsigned int order, unsigned long flags) { @@ -682,14 +718,18 @@ alloc_from_freelist(struct drm_buddy *mm, tmp = drm_buddy_block_order(block); } else { for (tmp = order; tmp <= mm->max_order; ++tmp) { + struct rb_node *iter = rb_last(&mm->free_tree[tmp]); struct drm_buddy_block *tmp_block; - list_for_each_entry_reverse(tmp_block, &mm->free_list[tmp], link) { - if (block_incompatible(tmp_block, flags)) - continue; + while (iter) { + tmp_block = rbtree_get_free_block(iter); - block = tmp_block; - break; + if (!block_incompatible(tmp_block, flags)) { + block = tmp_block; + break; + } + + iter = rb_prev(iter); } if (block) @@ -700,13 +740,9 @@ alloc_from_freelist(struct drm_buddy *mm, if (!block) { /* Fallback method */ for (tmp = order; tmp <= mm->max_order; ++tmp) { - if (!list_empty(&mm->free_list[tmp])) { - block = list_last_entry(&mm->free_list[tmp], - struct drm_buddy_block, - link); - if (block) - break; - } + block = rbtree_last_entry(mm, tmp); + if (block) + break; } if (!block) @@ -771,7 +807,7 @@ static int __alloc_range(struct drm_buddy *mm, if (contains(start, end, block_start, block_end)) { if (drm_buddy_block_is_free(block)) { - mark_allocated(block); + mark_allocated(mm, block); total_allocated += drm_buddy_block_size(mm, block); mm->avail -= drm_buddy_block_size(mm, block); if (drm_buddy_block_is_clear(block)) @@ -849,8 +885,8 @@ static int __alloc_contig_try_harder(struct drm_buddy *mm, { u64 rhs_offset, lhs_offset, lhs_size, filled; struct drm_buddy_block *block; - struct list_head *list; LIST_HEAD(blocks_lhs); + struct rb_node *iter; unsigned long pages; unsigned int order; u64 modify_size; @@ -862,11 +898,14 @@ static int __alloc_contig_try_harder(struct drm_buddy *mm, if (order == 0) return -ENOSPC; - list = &mm->free_list[order]; - if (list_empty(list)) + if (rbtree_is_empty(mm, order)) return -ENOSPC; - list_for_each_entry_reverse(block, list, link) { + iter = rb_last(&mm->free_tree[order]); + + while (iter) { + block = rbtree_get_free_block(iter); + /* Allocate blocks traversing RHS */ rhs_offset = drm_buddy_block_offset(block); err = __drm_buddy_alloc_range(mm, rhs_offset, size, @@ -891,6 +930,8 @@ static int __alloc_contig_try_harder(struct drm_buddy *mm, } /* Free blocks for the next iteration */ drm_buddy_free_list_internal(mm, blocks); + + iter = rb_prev(iter); } return -ENOSPC; @@ -976,7 +1017,7 @@ int drm_buddy_block_trim(struct drm_buddy *mm, list_add(&block->tmp_link, &dfs); err = __alloc_range(mm, &dfs, new_start, new_size, blocks, NULL); if (err) { - mark_allocated(block); + mark_allocated(mm, block); mm->avail -= drm_buddy_block_size(mm, block); if (drm_buddy_block_is_clear(block)) mm->clear_avail -= drm_buddy_block_size(mm, block); @@ -999,8 +1040,8 @@ __drm_buddy_alloc_blocks(struct drm_buddy *mm, return __drm_buddy_alloc_range_bias(mm, start, end, order, flags); else - /* Allocate from freelist */ - return alloc_from_freelist(mm, order, flags); + /* Allocate from freetree */ + return alloc_from_freetree(mm, order, flags); } /** @@ -1017,8 +1058,8 @@ __drm_buddy_alloc_blocks(struct drm_buddy *mm, * alloc_range_bias() called on range limitations, which traverses * the tree and returns the desired block. * - * alloc_from_freelist() called when *no* range restrictions - * are enforced, which picks the block from the freelist. + * alloc_from_freetree() called when *no* range restrictions + * are enforced, which picks the block from the freetree. * * Returns: * 0 on success, error code on failure. @@ -1120,7 +1161,7 @@ int drm_buddy_alloc_blocks(struct drm_buddy *mm, } } while (1); - mark_allocated(block); + mark_allocated(mm, block); mm->avail -= drm_buddy_block_size(mm, block); if (drm_buddy_block_is_clear(block)) mm->clear_avail -= drm_buddy_block_size(mm, block); @@ -1201,10 +1242,10 @@ void drm_buddy_print(struct drm_buddy *mm, struct drm_printer *p) mm->chunk_size >> 10, mm->size >> 20, mm->avail >> 20, mm->clear_avail >> 20); for (order = mm->max_order; order >= 0; order--) { - struct drm_buddy_block *block; + struct drm_buddy_block *block, *tmp; u64 count = 0, free; - list_for_each_entry(block, &mm->free_list[order], link) { + rbtree_postorder_for_each_entry_safe(block, tmp, &mm->free_tree[order], rb) { BUG_ON(!drm_buddy_block_is_free(block)); count++; } diff --git a/include/drm/drm_buddy.h b/include/drm/drm_buddy.h index 04afd7c21a82e9..f22d292443dbff 100644 --- a/include/drm/drm_buddy.h +++ b/include/drm/drm_buddy.h @@ -10,6 +10,7 @@ #include #include #include +#include #include @@ -44,7 +45,11 @@ struct drm_buddy_block { * a list, if so desired. As soon as the block is freed with * drm_buddy_free* ownership is given back to the mm. */ - struct list_head link; + union { + struct rb_node rb; + struct list_head link; + }; + struct list_head tmp_link; }; @@ -59,7 +64,7 @@ struct drm_buddy_block { */ struct drm_buddy { /* Maintain a free list for each order. */ - struct list_head *free_list; + struct rb_root *free_tree; /* * Maintain explicit binary tree(s) to track the allocation of the @@ -85,7 +90,7 @@ struct drm_buddy { }; static inline u64 -drm_buddy_block_offset(struct drm_buddy_block *block) +drm_buddy_block_offset(const struct drm_buddy_block *block) { return block->header & DRM_BUDDY_HEADER_OFFSET; } From 391f53db144511cd7f5655eac2b4f8efe0a7b0d4 Mon Sep 17 00:00:00 2001 From: Arunpravin Paneer Selvam Date: Mon, 6 Oct 2025 15:21:23 +0530 Subject: [PATCH 2028/3902] drm/buddy: Separate clear and dirty free block trees commit d4cd665c98c144dd6ad5d66d30396e13d23118c9 upstream. Maintain two separate RB trees per order - one for clear (zeroed) blocks and another for dirty (uncleared) blocks. This separation improves code clarity and makes it more obvious which tree is being searched during allocation. It also improves scalability and efficiency when searching for a specific type of block, avoiding unnecessary checks and making the allocator more predictable under fragmentation. The changes have been validated using the existing drm_buddy_test KUnit test cases, along with selected graphics workloads, to ensure correctness and avoid regressions. v2: Missed adding the suggested-by tag. Added it in v2. v3(Matthew): - Remove the double underscores from the internal functions. - Rename the internal functions to have less generic names. - Fix the error handling code. - Pass tree argument for the tree macro. - Use the existing dirty/free bit instead of new tree field. - Make free_trees[] instead of clear_tree and dirty_tree for more cleaner approach. v4: - A bug was reported by Intel CI and it is fixed by Matthew Auld. - Replace the get_root function with &mm->free_trees[tree][order] (Matthew) - Remove the unnecessary rbtree_is_empty() check (Matthew) - Remove the unnecessary get_tree_for_flags() function. - Rename get_tree_for_block() name with get_block_tree() for more clarity. v5(Jani Nikula): - Don't use static inline in .c files. - enum free_tree and enumerator names are quite generic for a header and usage and the whole enum should be an implementation detail. v6: - Rewrite the __force_merge() function using the rb_last() and rb_prev(). v7(Matthew): - Replace the open-coded tree iteration for loops with the for_each_free_tree() macro throughout the code. - Fixed out_free_roots to prevent double decrement of i, addressing potential crash. - Replaced enum drm_buddy_free_tree with unsigned int in for_each_free_tree loops. Cc: stable@vger.kernel.org Fixes: a68c7eaa7a8f ("drm/amdgpu: Enable clear page functionality") Signed-off-by: Arunpravin Paneer Selvam Suggested-by: Matthew Auld Reviewed-by: Matthew Auld Closes: https://gitlab.freedesktop.org/drm/amd/-/issues/4260 Link: https://lore.kernel.org/r/20251006095124.1663-2-Arunpravin.PaneerSelvam@amd.com Signed-off-by: Greg Kroah-Hartman --- drivers/gpu/drm/drm_buddy.c | 333 ++++++++++++++++++++---------------- include/drm/drm_buddy.h | 2 +- 2 files changed, 188 insertions(+), 147 deletions(-) diff --git a/drivers/gpu/drm/drm_buddy.c b/drivers/gpu/drm/drm_buddy.c index c87210a06c3184..f2c92902e4a302 100644 --- a/drivers/gpu/drm/drm_buddy.c +++ b/drivers/gpu/drm/drm_buddy.c @@ -12,9 +12,16 @@ #include +enum drm_buddy_free_tree { + DRM_BUDDY_CLEAR_TREE = 0, + DRM_BUDDY_DIRTY_TREE, + DRM_BUDDY_MAX_FREE_TREES, +}; + static struct kmem_cache *slab_blocks; -#define rbtree_get_free_block(node) rb_entry((node), struct drm_buddy_block, rb) +#define for_each_free_tree(tree) \ + for ((tree) = 0; (tree) < DRM_BUDDY_MAX_FREE_TREES; (tree)++) static struct drm_buddy_block *drm_block_alloc(struct drm_buddy *mm, struct drm_buddy_block *parent, @@ -45,6 +52,30 @@ static void drm_block_free(struct drm_buddy *mm, kmem_cache_free(slab_blocks, block); } +static enum drm_buddy_free_tree +get_block_tree(struct drm_buddy_block *block) +{ + return drm_buddy_block_is_clear(block) ? + DRM_BUDDY_CLEAR_TREE : DRM_BUDDY_DIRTY_TREE; +} + +static struct drm_buddy_block * +rbtree_get_free_block(const struct rb_node *node) +{ + return node ? rb_entry(node, struct drm_buddy_block, rb) : NULL; +} + +static struct drm_buddy_block * +rbtree_last_free_block(struct rb_root *root) +{ + return rbtree_get_free_block(rb_last(root)); +} + +static bool rbtree_is_empty(struct rb_root *root) +{ + return RB_EMPTY_ROOT(root); +} + static bool drm_buddy_block_offset_less(const struct drm_buddy_block *block, const struct drm_buddy_block *node) { @@ -59,37 +90,28 @@ static bool rbtree_block_offset_less(struct rb_node *block, } static void rbtree_insert(struct drm_buddy *mm, - struct drm_buddy_block *block) + struct drm_buddy_block *block, + enum drm_buddy_free_tree tree) { rb_add(&block->rb, - &mm->free_tree[drm_buddy_block_order(block)], + &mm->free_trees[tree][drm_buddy_block_order(block)], rbtree_block_offset_less); } static void rbtree_remove(struct drm_buddy *mm, struct drm_buddy_block *block) { + unsigned int order = drm_buddy_block_order(block); + enum drm_buddy_free_tree tree; struct rb_root *root; - root = &mm->free_tree[drm_buddy_block_order(block)]; - rb_erase(&block->rb, root); + tree = get_block_tree(block); + root = &mm->free_trees[tree][order]; + rb_erase(&block->rb, root); RB_CLEAR_NODE(&block->rb); } -static struct drm_buddy_block * -rbtree_last_entry(struct drm_buddy *mm, unsigned int order) -{ - struct rb_node *node = rb_last(&mm->free_tree[order]); - - return node ? rb_entry(node, struct drm_buddy_block, rb) : NULL; -} - -static bool rbtree_is_empty(struct drm_buddy *mm, unsigned int order) -{ - return RB_EMPTY_ROOT(&mm->free_tree[order]); -} - static void clear_reset(struct drm_buddy_block *block) { block->header &= ~DRM_BUDDY_HEADER_CLEAR; @@ -112,10 +134,13 @@ static void mark_allocated(struct drm_buddy *mm, static void mark_free(struct drm_buddy *mm, struct drm_buddy_block *block) { + enum drm_buddy_free_tree tree; + block->header &= ~DRM_BUDDY_HEADER_STATE; block->header |= DRM_BUDDY_FREE; - rbtree_insert(mm, block); + tree = get_block_tree(block); + rbtree_insert(mm, block, tree); } static void mark_split(struct drm_buddy *mm, @@ -201,7 +226,7 @@ static int __force_merge(struct drm_buddy *mm, u64 end, unsigned int min_order) { - unsigned int order; + unsigned int tree, order; int i; if (!min_order) @@ -210,45 +235,48 @@ static int __force_merge(struct drm_buddy *mm, if (min_order > mm->max_order) return -EINVAL; - for (i = min_order - 1; i >= 0; i--) { - struct rb_root *root = &mm->free_tree[i]; - struct rb_node *iter; + for_each_free_tree(tree) { + for (i = min_order - 1; i >= 0; i--) { + struct rb_node *iter = rb_last(&mm->free_trees[tree][i]); - iter = rb_last(root); - - while (iter) { - struct drm_buddy_block *block, *buddy; - u64 block_start, block_end; + while (iter) { + struct drm_buddy_block *block, *buddy; + u64 block_start, block_end; - block = rbtree_get_free_block(iter); - iter = rb_prev(iter); + block = rbtree_get_free_block(iter); + iter = rb_prev(iter); - if (!block || !block->parent) - continue; + if (!block || !block->parent) + continue; - block_start = drm_buddy_block_offset(block); - block_end = block_start + drm_buddy_block_size(mm, block) - 1; + block_start = drm_buddy_block_offset(block); + block_end = block_start + drm_buddy_block_size(mm, block) - 1; - if (!contains(start, end, block_start, block_end)) - continue; + if (!contains(start, end, block_start, block_end)) + continue; - buddy = __get_buddy(block); - if (!drm_buddy_block_is_free(buddy)) - continue; + buddy = __get_buddy(block); + if (!drm_buddy_block_is_free(buddy)) + continue; - WARN_ON(drm_buddy_block_is_clear(block) == - drm_buddy_block_is_clear(buddy)); + WARN_ON(drm_buddy_block_is_clear(block) == + drm_buddy_block_is_clear(buddy)); - if (iter == &buddy->rb) - iter = rb_prev(iter); + /* + * Advance to the next node when the current node is the buddy, + * as freeing the block will also remove its buddy from the tree. + */ + if (iter == &buddy->rb) + iter = rb_prev(iter); - rbtree_remove(mm, block); - if (drm_buddy_block_is_clear(block)) - mm->clear_avail -= drm_buddy_block_size(mm, block); + rbtree_remove(mm, block); + if (drm_buddy_block_is_clear(block)) + mm->clear_avail -= drm_buddy_block_size(mm, block); - order = __drm_buddy_free(mm, block, true); - if (order >= min_order) - return 0; + order = __drm_buddy_free(mm, block, true); + if (order >= min_order) + return 0; + } } } @@ -269,7 +297,7 @@ static int __force_merge(struct drm_buddy *mm, */ int drm_buddy_init(struct drm_buddy *mm, u64 size, u64 chunk_size) { - unsigned int i; + unsigned int i, j, root_count = 0; u64 offset = 0; if (size < chunk_size) @@ -291,14 +319,22 @@ int drm_buddy_init(struct drm_buddy *mm, u64 size, u64 chunk_size) BUG_ON(mm->max_order > DRM_BUDDY_MAX_ORDER); - mm->free_tree = kmalloc_array(mm->max_order + 1, - sizeof(struct rb_root), - GFP_KERNEL); - if (!mm->free_tree) + mm->free_trees = kmalloc_array(DRM_BUDDY_MAX_FREE_TREES, + sizeof(*mm->free_trees), + GFP_KERNEL); + if (!mm->free_trees) return -ENOMEM; - for (i = 0; i <= mm->max_order; ++i) - mm->free_tree[i] = RB_ROOT; + for_each_free_tree(i) { + mm->free_trees[i] = kmalloc_array(mm->max_order + 1, + sizeof(struct rb_root), + GFP_KERNEL); + if (!mm->free_trees[i]) + goto out_free_tree; + + for (j = 0; j <= mm->max_order; ++j) + mm->free_trees[i][j] = RB_ROOT; + } mm->n_roots = hweight64(size); @@ -308,8 +344,6 @@ int drm_buddy_init(struct drm_buddy *mm, u64 size, u64 chunk_size) if (!mm->roots) goto out_free_tree; - i = 0; - /* * Split into power-of-two blocks, in case we are given a size that is * not itself a power-of-two. @@ -328,24 +362,26 @@ int drm_buddy_init(struct drm_buddy *mm, u64 size, u64 chunk_size) mark_free(mm, root); - BUG_ON(i > mm->max_order); + BUG_ON(root_count > mm->max_order); BUG_ON(drm_buddy_block_size(mm, root) < chunk_size); - mm->roots[i] = root; + mm->roots[root_count] = root; offset += root_size; size -= root_size; - i++; + root_count++; } while (size); return 0; out_free_roots: - while (i--) - drm_block_free(mm, mm->roots[i]); + while (root_count--) + drm_block_free(mm, mm->roots[root_count]); kfree(mm->roots); out_free_tree: - kfree(mm->free_tree); + while (i--) + kfree(mm->free_trees[i]); + kfree(mm->free_trees); return -ENOMEM; } EXPORT_SYMBOL(drm_buddy_init); @@ -381,8 +417,9 @@ void drm_buddy_fini(struct drm_buddy *mm) WARN_ON(mm->avail != mm->size); + for_each_free_tree(i) + kfree(mm->free_trees[i]); kfree(mm->roots); - kfree(mm->free_tree); } EXPORT_SYMBOL(drm_buddy_fini); @@ -406,8 +443,7 @@ static int split_block(struct drm_buddy *mm, return -ENOMEM; } - mark_free(mm, block->left); - mark_free(mm, block->right); + mark_split(mm, block); if (drm_buddy_block_is_clear(block)) { mark_cleared(block->left); @@ -415,7 +451,8 @@ static int split_block(struct drm_buddy *mm, clear_reset(block); } - mark_split(mm, block); + mark_free(mm, block->left); + mark_free(mm, block->right); return 0; } @@ -448,6 +485,7 @@ EXPORT_SYMBOL(drm_get_buddy); */ void drm_buddy_reset_clear(struct drm_buddy *mm, bool is_clear) { + enum drm_buddy_free_tree src_tree, dst_tree; u64 root_size, size, start; unsigned int order; int i; @@ -462,19 +500,24 @@ void drm_buddy_reset_clear(struct drm_buddy *mm, bool is_clear) size -= root_size; } + src_tree = is_clear ? DRM_BUDDY_DIRTY_TREE : DRM_BUDDY_CLEAR_TREE; + dst_tree = is_clear ? DRM_BUDDY_CLEAR_TREE : DRM_BUDDY_DIRTY_TREE; + for (i = 0; i <= mm->max_order; ++i) { + struct rb_root *root = &mm->free_trees[src_tree][i]; struct drm_buddy_block *block, *tmp; - rbtree_postorder_for_each_entry_safe(block, tmp, &mm->free_tree[i], rb) { - if (is_clear != drm_buddy_block_is_clear(block)) { - if (is_clear) { - mark_cleared(block); - mm->clear_avail += drm_buddy_block_size(mm, block); - } else { - clear_reset(block); - mm->clear_avail -= drm_buddy_block_size(mm, block); - } + rbtree_postorder_for_each_entry_safe(block, tmp, root, rb) { + rbtree_remove(mm, block); + if (is_clear) { + mark_cleared(block); + mm->clear_avail += drm_buddy_block_size(mm, block); + } else { + clear_reset(block); + mm->clear_avail -= drm_buddy_block_size(mm, block); } + + rbtree_insert(mm, block, dst_tree); } } } @@ -664,27 +707,17 @@ __drm_buddy_alloc_range_bias(struct drm_buddy *mm, } static struct drm_buddy_block * -get_maxblock(struct drm_buddy *mm, unsigned int order, - unsigned long flags) +get_maxblock(struct drm_buddy *mm, + unsigned int order, + enum drm_buddy_free_tree tree) { struct drm_buddy_block *max_block = NULL, *block = NULL; + struct rb_root *root; unsigned int i; for (i = order; i <= mm->max_order; ++i) { - struct rb_node *iter = rb_last(&mm->free_tree[i]); - struct drm_buddy_block *tmp_block; - - while (iter) { - tmp_block = rbtree_get_free_block(iter); - - if (!block_incompatible(tmp_block, flags)) { - block = tmp_block; - break; - } - - iter = rb_prev(iter); - } - + root = &mm->free_trees[tree][i]; + block = rbtree_last_free_block(root); if (!block) continue; @@ -708,39 +741,37 @@ alloc_from_freetree(struct drm_buddy *mm, unsigned long flags) { struct drm_buddy_block *block = NULL; + struct rb_root *root; + enum drm_buddy_free_tree tree; unsigned int tmp; int err; + tree = (flags & DRM_BUDDY_CLEAR_ALLOCATION) ? + DRM_BUDDY_CLEAR_TREE : DRM_BUDDY_DIRTY_TREE; + if (flags & DRM_BUDDY_TOPDOWN_ALLOCATION) { - block = get_maxblock(mm, order, flags); + block = get_maxblock(mm, order, tree); if (block) /* Store the obtained block order */ tmp = drm_buddy_block_order(block); } else { for (tmp = order; tmp <= mm->max_order; ++tmp) { - struct rb_node *iter = rb_last(&mm->free_tree[tmp]); - struct drm_buddy_block *tmp_block; - - while (iter) { - tmp_block = rbtree_get_free_block(iter); - - if (!block_incompatible(tmp_block, flags)) { - block = tmp_block; - break; - } - - iter = rb_prev(iter); - } - + /* Get RB tree root for this order and tree */ + root = &mm->free_trees[tree][tmp]; + block = rbtree_last_free_block(root); if (block) break; } } if (!block) { - /* Fallback method */ + /* Try allocating from the other tree */ + tree = (tree == DRM_BUDDY_CLEAR_TREE) ? + DRM_BUDDY_DIRTY_TREE : DRM_BUDDY_CLEAR_TREE; + for (tmp = order; tmp <= mm->max_order; ++tmp) { - block = rbtree_last_entry(mm, tmp); + root = &mm->free_trees[tree][tmp]; + block = rbtree_last_free_block(root); if (block) break; } @@ -885,10 +916,9 @@ static int __alloc_contig_try_harder(struct drm_buddy *mm, { u64 rhs_offset, lhs_offset, lhs_size, filled; struct drm_buddy_block *block; + unsigned int tree, order; LIST_HEAD(blocks_lhs); - struct rb_node *iter; unsigned long pages; - unsigned int order; u64 modify_size; int err; @@ -898,40 +928,45 @@ static int __alloc_contig_try_harder(struct drm_buddy *mm, if (order == 0) return -ENOSPC; - if (rbtree_is_empty(mm, order)) - return -ENOSPC; + for_each_free_tree(tree) { + struct rb_root *root; + struct rb_node *iter; - iter = rb_last(&mm->free_tree[order]); - - while (iter) { - block = rbtree_get_free_block(iter); - - /* Allocate blocks traversing RHS */ - rhs_offset = drm_buddy_block_offset(block); - err = __drm_buddy_alloc_range(mm, rhs_offset, size, - &filled, blocks); - if (!err || err != -ENOSPC) - return err; - - lhs_size = max((size - filled), min_block_size); - if (!IS_ALIGNED(lhs_size, min_block_size)) - lhs_size = round_up(lhs_size, min_block_size); - - /* Allocate blocks traversing LHS */ - lhs_offset = drm_buddy_block_offset(block) - lhs_size; - err = __drm_buddy_alloc_range(mm, lhs_offset, lhs_size, - NULL, &blocks_lhs); - if (!err) { - list_splice(&blocks_lhs, blocks); - return 0; - } else if (err != -ENOSPC) { + root = &mm->free_trees[tree][order]; + if (rbtree_is_empty(root)) + continue; + + iter = rb_last(root); + while (iter) { + block = rbtree_get_free_block(iter); + + /* Allocate blocks traversing RHS */ + rhs_offset = drm_buddy_block_offset(block); + err = __drm_buddy_alloc_range(mm, rhs_offset, size, + &filled, blocks); + if (!err || err != -ENOSPC) + return err; + + lhs_size = max((size - filled), min_block_size); + if (!IS_ALIGNED(lhs_size, min_block_size)) + lhs_size = round_up(lhs_size, min_block_size); + + /* Allocate blocks traversing LHS */ + lhs_offset = drm_buddy_block_offset(block) - lhs_size; + err = __drm_buddy_alloc_range(mm, lhs_offset, lhs_size, + NULL, &blocks_lhs); + if (!err) { + list_splice(&blocks_lhs, blocks); + return 0; + } else if (err != -ENOSPC) { + drm_buddy_free_list_internal(mm, blocks); + return err; + } + /* Free blocks for the next iteration */ drm_buddy_free_list_internal(mm, blocks); - return err; - } - /* Free blocks for the next iteration */ - drm_buddy_free_list_internal(mm, blocks); - iter = rb_prev(iter); + iter = rb_prev(iter); + } } return -ENOSPC; @@ -1243,11 +1278,17 @@ void drm_buddy_print(struct drm_buddy *mm, struct drm_printer *p) for (order = mm->max_order; order >= 0; order--) { struct drm_buddy_block *block, *tmp; + struct rb_root *root; u64 count = 0, free; + unsigned int tree; + + for_each_free_tree(tree) { + root = &mm->free_trees[tree][order]; - rbtree_postorder_for_each_entry_safe(block, tmp, &mm->free_tree[order], rb) { - BUG_ON(!drm_buddy_block_is_free(block)); - count++; + rbtree_postorder_for_each_entry_safe(block, tmp, root, rb) { + BUG_ON(!drm_buddy_block_is_free(block)); + count++; + } } drm_printf(p, "order-%2d ", order); diff --git a/include/drm/drm_buddy.h b/include/drm/drm_buddy.h index f22d292443dbff..c2e05a28125224 100644 --- a/include/drm/drm_buddy.h +++ b/include/drm/drm_buddy.h @@ -64,7 +64,7 @@ struct drm_buddy_block { */ struct drm_buddy { /* Maintain a free list for each order. */ - struct rb_root *free_tree; + struct rb_root **free_trees; /* * Maintain explicit binary tree(s) to track the allocation of the From e2e980f091332789fc64da6a278fe35e129e4644 Mon Sep 17 00:00:00 2001 From: Thomas Zimmermann Date: Mon, 29 Sep 2025 10:23:23 +0200 Subject: [PATCH 2029/3902] drm/gma500: Remove unused helper psb_fbdev_fb_setcolreg() commit be729f9de6c64240645dc80a24162ac4d3fe00a8 upstream. Remove psb_fbdev_fb_setcolreg(), which hasn't been called in almost a decade. Gma500 commit 4d8d096e9ae8 ("gma500: introduce the framebuffer support code") added the helper psb_fbdev_fb_setcolreg() for setting the fbdev palette via fbdev's fb_setcolreg callback. Later commit 3da6c2f3b730 ("drm/gma500: use DRM_FB_HELPER_DEFAULT_OPS for fb_ops") set several default helpers for fbdev emulation, including fb_setcmap. The fbdev subsystem always prefers fb_setcmap over fb_setcolreg. [1] Hence, the gma500 code is no longer in use and gma500 has been using drm_fb_helper_setcmap() for several years without issues. Fixes: 3da6c2f3b730 ("drm/gma500: use DRM_FB_HELPER_DEFAULT_OPS for fb_ops") Cc: Patrik Jakobsson Cc: Stefan Christ Cc: Daniel Vetter Cc: dri-devel@lists.freedesktop.org Cc: # v4.10+ Link: https://elixir.bootlin.com/linux/v6.16.9/source/drivers/video/fbdev/core/fbcmap.c#L246 # [1] Signed-off-by: Thomas Zimmermann Acked-by: Patrik Jakobsson Link: https://lore.kernel.org/r/20250929082338.18845-1-tzimmermann@suse.de Signed-off-by: Greg Kroah-Hartman --- drivers/gpu/drm/gma500/fbdev.c | 43 ---------------------------------- 1 file changed, 43 deletions(-) diff --git a/drivers/gpu/drm/gma500/fbdev.c b/drivers/gpu/drm/gma500/fbdev.c index 32d31e5f5f1a78..a6af21514cff73 100644 --- a/drivers/gpu/drm/gma500/fbdev.c +++ b/drivers/gpu/drm/gma500/fbdev.c @@ -50,48 +50,6 @@ static const struct vm_operations_struct psb_fbdev_vm_ops = { * struct fb_ops */ -#define CMAP_TOHW(_val, _width) ((((_val) << (_width)) + 0x7FFF - (_val)) >> 16) - -static int psb_fbdev_fb_setcolreg(unsigned int regno, - unsigned int red, unsigned int green, - unsigned int blue, unsigned int transp, - struct fb_info *info) -{ - struct drm_fb_helper *fb_helper = info->par; - struct drm_framebuffer *fb = fb_helper->fb; - uint32_t v; - - if (!fb) - return -ENOMEM; - - if (regno > 255) - return 1; - - red = CMAP_TOHW(red, info->var.red.length); - blue = CMAP_TOHW(blue, info->var.blue.length); - green = CMAP_TOHW(green, info->var.green.length); - transp = CMAP_TOHW(transp, info->var.transp.length); - - v = (red << info->var.red.offset) | - (green << info->var.green.offset) | - (blue << info->var.blue.offset) | - (transp << info->var.transp.offset); - - if (regno < 16) { - switch (fb->format->cpp[0] * 8) { - case 16: - ((uint32_t *) info->pseudo_palette)[regno] = v; - break; - case 24: - case 32: - ((uint32_t *) info->pseudo_palette)[regno] = v; - break; - } - } - - return 0; -} - static int psb_fbdev_fb_mmap(struct fb_info *info, struct vm_area_struct *vma) { if (vma->vm_pgoff != 0) @@ -135,7 +93,6 @@ static const struct fb_ops psb_fbdev_fb_ops = { .owner = THIS_MODULE, __FB_DEFAULT_IOMEM_OPS_RDWR, DRM_FB_HELPER_DEFAULT_OPS, - .fb_setcolreg = psb_fbdev_fb_setcolreg, __FB_DEFAULT_IOMEM_OPS_DRAW, .fb_mmap = psb_fbdev_fb_mmap, .fb_destroy = psb_fbdev_fb_destroy, From 7cdb9a9da935c687563cc682155461fef5f9b48d Mon Sep 17 00:00:00 2001 From: Sanjay Yadav Date: Tue, 18 Nov 2025 17:19:00 +0530 Subject: [PATCH 2030/3902] drm/xe/oa: Fix potential UAF in xe_oa_add_config_ioctl() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit commit dcb171931954c51a1a7250d558f02b8f36570783 upstream. In xe_oa_add_config_ioctl(), we accessed oa_config->id after dropping metrics_lock. Since this lock protects the lifetime of oa_config, an attacker could guess the id and call xe_oa_remove_config_ioctl() with perfect timing, freeing oa_config before we dereference it, leading to a potential use-after-free. Fix this by caching the id in a local variable while holding the lock. v2: (Matt A) - Dropped mutex_unlock(&oa->metrics_lock) ordering change from xe_oa_remove_config_ioctl() Closes: https://gitlab.freedesktop.org/drm/xe/kernel/-/issues/6614 Fixes: cdf02fe1a94a7 ("drm/xe/oa/uapi: Add/remove OA config perf ops") Cc: # v6.11+ Suggested-by: Matthew Auld Signed-off-by: Sanjay Yadav Reviewed-by: Matthew Auld Signed-off-by: Matthew Auld Link: https://patch.msgid.link/20251118114859.3379952-2-sanjay.kumar.yadav@intel.com (cherry picked from commit 28aeaed130e8e587fd1b73b6d66ca41ccc5a1a31) Signed-off-by: Thomas Hellström Signed-off-by: Greg Kroah-Hartman --- drivers/gpu/drm/xe/xe_oa.c | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/drivers/gpu/drm/xe/xe_oa.c b/drivers/gpu/drm/xe/xe_oa.c index d0ceb67af83ed5..1cb7e4791c6db4 100644 --- a/drivers/gpu/drm/xe/xe_oa.c +++ b/drivers/gpu/drm/xe/xe_oa.c @@ -2407,11 +2407,13 @@ int xe_oa_add_config_ioctl(struct drm_device *dev, u64 data, struct drm_file *fi goto sysfs_err; } - mutex_unlock(&oa->metrics_lock); + id = oa_config->id; + + drm_dbg(&oa->xe->drm, "Added config %s id=%i\n", oa_config->uuid, id); - drm_dbg(&oa->xe->drm, "Added config %s id=%i\n", oa_config->uuid, oa_config->id); + mutex_unlock(&oa->metrics_lock); - return oa_config->id; + return id; sysfs_err: mutex_unlock(&oa->metrics_lock); From 78bc88fda96c1759ced089a65747be37daa06928 Mon Sep 17 00:00:00 2001 From: Dmitry Osipenko Date: Wed, 22 Oct 2025 19:19:48 +0300 Subject: [PATCH 2031/3902] drm/rockchip: Set VOP for the DRM DMA device commit 7d7bb790aced3b1b8550b74e02fdfc001d044bee upstream. Use VOP for DMA operations performed by DRM core. Rockchip DRM driver is backed by a virtual device that isn't IOMMU-capable, while VOP is the actual display controller device backed by IOMMU. Fixes "swiotlb buffer is full" warning messages originated from GEM prime code paths. Note, that backporting is non-trivial as this depends on commit 143ec8d3f9396 ("drm/prime: Support dedicated DMA device for dma-buf imports"), which landed in v6.16 and commit 421be3ee36a4 ("drm/rockchip: Refactor IOMMU initialisation"), which landed in v5.19. Reported-by: Daniel Stone Fixes: 2048e3286f34 ("drm: rockchip: Add basic drm driver") Cc: stable@vger.kernel.org # v6.16+ Reviewed-by: Sebastian Reichel Signed-off-by: Dmitry Osipenko Tested-by: Cristian Ciocaltea Signed-off-by: Heiko Stuebner Link: https://lore.kernel.org/r/20251022161948.199731-1-dmitry.osipenko@collabora.com Signed-off-by: Greg Kroah-Hartman --- drivers/gpu/drm/rockchip/rockchip_drm_drv.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/drivers/gpu/drm/rockchip/rockchip_drm_drv.c b/drivers/gpu/drm/rockchip/rockchip_drm_drv.c index eb77bde9f6283c..e693160e9b7f56 100644 --- a/drivers/gpu/drm/rockchip/rockchip_drm_drv.c +++ b/drivers/gpu/drm/rockchip/rockchip_drm_drv.c @@ -96,6 +96,9 @@ void rockchip_drm_dma_init_device(struct drm_device *drm_dev, private->iommu_dev = ERR_PTR(-ENODEV); else if (!private->iommu_dev) private->iommu_dev = dev; + + if (!IS_ERR(private->iommu_dev)) + drm_dev_set_dma_dev(drm_dev, private->iommu_dev); } static int rockchip_drm_init_iommu(struct drm_device *drm_dev) From 4fcb7f89479b2427f216ad62d8b1519d213bd38a Mon Sep 17 00:00:00 2001 From: Miaoqian Lin Date: Wed, 29 Oct 2025 15:23:06 +0800 Subject: [PATCH 2032/3902] drm/mediatek: Fix device node reference leak in mtk_dp_dt_parse() commit a846505a193d7492ad3531e33cacfca31e4bcdd1 upstream. The function mtk_dp_dt_parse() calls of_graph_get_endpoint_by_regs() to get the endpoint device node, but fails to call of_node_put() to release the reference when the function returns. This results in a device node reference leak. Fix this by adding the missing of_node_put() call before returning from the function. Found via static analysis and code review. Fixes: f70ac097a2cf ("drm/mediatek: Add MT8195 Embedded DisplayPort driver") Cc: stable@vger.kernel.org Signed-off-by: Miaoqian Lin Reviewed-by: Markus Schneider-Pargmann Reviewed-by: CK Hu Link: https://patchwork.kernel.org/project/dri-devel/patch/20251029072307.10955-1-linmq006@gmail.com/ Signed-off-by: Chun-Kuang Hu Signed-off-by: Greg Kroah-Hartman --- drivers/gpu/drm/mediatek/mtk_dp.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/gpu/drm/mediatek/mtk_dp.c b/drivers/gpu/drm/mediatek/mtk_dp.c index bef6eeb30d3ecc..b0b1e158600f34 100644 --- a/drivers/gpu/drm/mediatek/mtk_dp.c +++ b/drivers/gpu/drm/mediatek/mtk_dp.c @@ -2087,6 +2087,7 @@ static int mtk_dp_dt_parse(struct mtk_dp *mtk_dp, endpoint = of_graph_get_endpoint_by_regs(pdev->dev.of_node, 1, -1); len = of_property_count_elems_of_size(endpoint, "data-lanes", sizeof(u32)); + of_node_put(endpoint); if (len < 0 || len > 4 || len == 3) { dev_err(dev, "invalid data lane size: %d\n", len); return -EINVAL; From 54291161eeaaea7b3e1d059a9ec9295337af636c Mon Sep 17 00:00:00 2001 From: Johan Hovold Date: Tue, 23 Sep 2025 17:23:36 +0200 Subject: [PATCH 2033/3902] drm/mediatek: Fix probe resource leaks commit 07c7c640a8eb9e196f357d15d88a59602a947197 upstream. Make sure to unmap and release the component iomap and clock on probe failure (e.g. probe deferral) and on driver unbind. Note that unlike of_iomap(), devm_of_iomap() also checks whether the region is already mapped. Fixes: 119f5173628a ("drm/mediatek: Add DRM Driver for Mediatek SoC MT8173.") Cc: stable@vger.kernel.org # 4.7 Cc: CK Hu Signed-off-by: Johan Hovold Reviewed-by: AngeloGioacchino Del Regno Link: https://patchwork.kernel.org/project/dri-devel/patch/20250923152340.18234-2-johan@kernel.org/ Signed-off-by: Chun-Kuang Hu Signed-off-by: Greg Kroah-Hartman --- drivers/gpu/drm/mediatek/mtk_ddp_comp.c | 20 ++++++++++++++++---- drivers/gpu/drm/mediatek/mtk_ddp_comp.h | 2 +- drivers/gpu/drm/mediatek/mtk_drm_drv.c | 4 ++-- 3 files changed, 19 insertions(+), 7 deletions(-) diff --git a/drivers/gpu/drm/mediatek/mtk_ddp_comp.c b/drivers/gpu/drm/mediatek/mtk_ddp_comp.c index ac6620e10262e3..0264017806adc7 100644 --- a/drivers/gpu/drm/mediatek/mtk_ddp_comp.c +++ b/drivers/gpu/drm/mediatek/mtk_ddp_comp.c @@ -621,15 +621,20 @@ int mtk_find_possible_crtcs(struct drm_device *drm, struct device *dev) return ret; } -int mtk_ddp_comp_init(struct device_node *node, struct mtk_ddp_comp *comp, +static void mtk_ddp_comp_clk_put(void *_clk) +{ + struct clk *clk = _clk; + + clk_put(clk); +} + +int mtk_ddp_comp_init(struct device *dev, struct device_node *node, struct mtk_ddp_comp *comp, unsigned int comp_id) { struct platform_device *comp_pdev; enum mtk_ddp_comp_type type; struct mtk_ddp_comp_dev *priv; -#if IS_REACHABLE(CONFIG_MTK_CMDQ) int ret; -#endif if (comp_id >= DDP_COMPONENT_DRM_ID_MAX) return -EINVAL; @@ -670,11 +675,18 @@ int mtk_ddp_comp_init(struct device_node *node, struct mtk_ddp_comp *comp, if (!priv) return -ENOMEM; - priv->regs = of_iomap(node, 0); + priv->regs = devm_of_iomap(dev, node, 0, NULL); + if (IS_ERR(priv->regs)) + return PTR_ERR(priv->regs); + priv->clk = of_clk_get(node, 0); if (IS_ERR(priv->clk)) return PTR_ERR(priv->clk); + ret = devm_add_action_or_reset(dev, mtk_ddp_comp_clk_put, priv->clk); + if (ret) + return ret; + #if IS_REACHABLE(CONFIG_MTK_CMDQ) ret = cmdq_dev_get_client_reg(comp->dev, &priv->cmdq_reg, 0); if (ret) diff --git a/drivers/gpu/drm/mediatek/mtk_ddp_comp.h b/drivers/gpu/drm/mediatek/mtk_ddp_comp.h index 7289b3dcf22f22..3f3d43f4330da4 100644 --- a/drivers/gpu/drm/mediatek/mtk_ddp_comp.h +++ b/drivers/gpu/drm/mediatek/mtk_ddp_comp.h @@ -350,7 +350,7 @@ static inline void mtk_ddp_comp_encoder_index_set(struct mtk_ddp_comp *comp) int mtk_ddp_comp_get_id(struct device_node *node, enum mtk_ddp_comp_type comp_type); int mtk_find_possible_crtcs(struct drm_device *drm, struct device *dev); -int mtk_ddp_comp_init(struct device_node *comp_node, struct mtk_ddp_comp *comp, +int mtk_ddp_comp_init(struct device *dev, struct device_node *comp_node, struct mtk_ddp_comp *comp, unsigned int comp_id); enum mtk_ddp_comp_type mtk_ddp_comp_get_type(unsigned int comp_id); void mtk_ddp_write(struct cmdq_pkt *cmdq_pkt, unsigned int value, diff --git a/drivers/gpu/drm/mediatek/mtk_drm_drv.c b/drivers/gpu/drm/mediatek/mtk_drm_drv.c index 31ff2922758a44..a94c51a8326162 100644 --- a/drivers/gpu/drm/mediatek/mtk_drm_drv.c +++ b/drivers/gpu/drm/mediatek/mtk_drm_drv.c @@ -1123,7 +1123,7 @@ static int mtk_drm_probe(struct platform_device *pdev) (void *)private->mmsys_dev, sizeof(*private->mmsys_dev)); private->ddp_comp[DDP_COMPONENT_DRM_OVL_ADAPTOR].dev = &ovl_adaptor->dev; - mtk_ddp_comp_init(NULL, &private->ddp_comp[DDP_COMPONENT_DRM_OVL_ADAPTOR], + mtk_ddp_comp_init(dev, NULL, &private->ddp_comp[DDP_COMPONENT_DRM_OVL_ADAPTOR], DDP_COMPONENT_DRM_OVL_ADAPTOR); component_match_add(dev, &match, compare_dev, &ovl_adaptor->dev); } @@ -1189,7 +1189,7 @@ static int mtk_drm_probe(struct platform_device *pdev) node); } - ret = mtk_ddp_comp_init(node, &private->ddp_comp[comp_id], comp_id); + ret = mtk_ddp_comp_init(dev, node, &private->ddp_comp[comp_id], comp_id); if (ret) { of_node_put(node); goto err_node; From e048e15c97e827ef7f6500292dc3d528f556742f Mon Sep 17 00:00:00 2001 From: Johan Hovold Date: Tue, 23 Sep 2025 17:23:37 +0200 Subject: [PATCH 2034/3902] drm/mediatek: Fix probe memory leak commit 5e49200593f331cd0629b5376fab9192f698e8ef upstream. The Mediatek DRM driver allocates private data for components without a platform driver but as the lifetime is tied to each component device, the memory is never freed. Tie the allocation lifetime to the DRM platform device so that the memory is released on probe failure (e.g. probe deferral) and when the driver is unbound. Fixes: c0d36de868a6 ("drm/mediatek: Move clk info from struct mtk_ddp_comp to sub driver private data") Cc: stable@vger.kernel.org # 5.12 Cc: CK Hu Signed-off-by: Johan Hovold Reviewed-by: AngeloGioacchino Del Regno Link: https://patchwork.kernel.org/project/dri-devel/patch/20250923152340.18234-3-johan@kernel.org/ Signed-off-by: Chun-Kuang Hu Signed-off-by: Greg Kroah-Hartman --- drivers/gpu/drm/mediatek/mtk_ddp_comp.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/gpu/drm/mediatek/mtk_ddp_comp.c b/drivers/gpu/drm/mediatek/mtk_ddp_comp.c index 0264017806adc7..31d67a131c5031 100644 --- a/drivers/gpu/drm/mediatek/mtk_ddp_comp.c +++ b/drivers/gpu/drm/mediatek/mtk_ddp_comp.c @@ -671,7 +671,7 @@ int mtk_ddp_comp_init(struct device *dev, struct device_node *node, struct mtk_d type == MTK_DSI) return 0; - priv = devm_kzalloc(comp->dev, sizeof(*priv), GFP_KERNEL); + priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); if (!priv) return -ENOMEM; From 8f059592969d0b21b873be5db26df262d1dc1fa1 Mon Sep 17 00:00:00 2001 From: Johan Hovold Date: Tue, 23 Sep 2025 17:23:38 +0200 Subject: [PATCH 2035/3902] drm/mediatek: Fix probe device leaks commit 2a2a04be8e869a19c9f950b89b1e05832a0f7ec7 upstream. Make sure to drop the reference taken to each component device during probe on probe failure (e.g. probe deferral) and on driver unbind. Fixes: 6ea6f8276725 ("drm/mediatek: Use correct device pointer to get CMDQ client register") Cc: stable@vger.kernel.org # 5.12 Cc: Chun-Kuang Hu Signed-off-by: Johan Hovold Reviewed-by: AngeloGioacchino Del Regno Link: https://patchwork.kernel.org/project/dri-devel/patch/20250923152340.18234-4-johan@kernel.org/ Signed-off-by: Chun-Kuang Hu Signed-off-by: Greg Kroah-Hartman --- drivers/gpu/drm/mediatek/mtk_ddp_comp.c | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/drivers/gpu/drm/mediatek/mtk_ddp_comp.c b/drivers/gpu/drm/mediatek/mtk_ddp_comp.c index 31d67a131c5031..9672ea1f91a2b5 100644 --- a/drivers/gpu/drm/mediatek/mtk_ddp_comp.c +++ b/drivers/gpu/drm/mediatek/mtk_ddp_comp.c @@ -621,6 +621,13 @@ int mtk_find_possible_crtcs(struct drm_device *drm, struct device *dev) return ret; } +static void mtk_ddp_comp_put_device(void *_dev) +{ + struct device *dev = _dev; + + put_device(dev); +} + static void mtk_ddp_comp_clk_put(void *_clk) { struct clk *clk = _clk; @@ -656,6 +663,10 @@ int mtk_ddp_comp_init(struct device *dev, struct device_node *node, struct mtk_d } comp->dev = &comp_pdev->dev; + ret = devm_add_action_or_reset(dev, mtk_ddp_comp_put_device, comp->dev); + if (ret) + return ret; + if (type == MTK_DISP_AAL || type == MTK_DISP_BLS || type == MTK_DISP_CCORR || From b3e92227862263570172887af33d034f2c777793 Mon Sep 17 00:00:00 2001 From: Johan Hovold Date: Tue, 23 Sep 2025 17:23:39 +0200 Subject: [PATCH 2036/3902] drm/mediatek: mtk_hdmi: Fix probe device leaks commit 9545bae5c8acd5a47af7add606718d94578bd838 upstream. Make sure to drop the references to the DDC adapter and CEC device taken during probe on probe failure (e.g. probe deferral) and on driver unbind. Fixes: 8f83f26891e1 ("drm/mediatek: Add HDMI support") Cc: stable@vger.kernel.org # 4.8 Cc: Jie Qiu Signed-off-by: Johan Hovold Reviewed-by: AngeloGioacchino Del Regno Link: https://patchwork.kernel.org/project/dri-devel/patch/20250923152340.18234-5-johan@kernel.org/ Signed-off-by: Chun-Kuang Hu Signed-off-by: Greg Kroah-Hartman --- drivers/gpu/drm/mediatek/mtk_hdmi.c | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/drivers/gpu/drm/mediatek/mtk_hdmi.c b/drivers/gpu/drm/mediatek/mtk_hdmi.c index b766dd5e6c8de6..306e2c90731144 100644 --- a/drivers/gpu/drm/mediatek/mtk_hdmi.c +++ b/drivers/gpu/drm/mediatek/mtk_hdmi.c @@ -1345,6 +1345,13 @@ static const struct drm_bridge_funcs mtk_hdmi_bridge_funcs = { .edid_read = mtk_hdmi_bridge_edid_read, }; +static void mtk_hdmi_put_device(void *_dev) +{ + struct device *dev = _dev; + + put_device(dev); +} + static int mtk_hdmi_get_cec_dev(struct mtk_hdmi *hdmi, struct device *dev, struct device_node *np) { struct platform_device *cec_pdev; @@ -1369,6 +1376,10 @@ static int mtk_hdmi_get_cec_dev(struct mtk_hdmi *hdmi, struct device *dev, struc } of_node_put(cec_np); + ret = devm_add_action_or_reset(dev, mtk_hdmi_put_device, &cec_pdev->dev); + if (ret) + return ret; + /* * The mediatek,syscon-hdmi property contains a phandle link to the * MMSYS_CONFIG device and the register offset of the HDMI_SYS_CFG @@ -1423,6 +1434,10 @@ static int mtk_hdmi_dt_parse_pdata(struct mtk_hdmi *hdmi, if (!hdmi->ddc_adpt) return dev_err_probe(dev, -EINVAL, "Failed to get ddc i2c adapter by node\n"); + ret = devm_add_action_or_reset(dev, mtk_hdmi_put_device, &hdmi->ddc_adpt->dev); + if (ret) + return ret; + ret = mtk_hdmi_get_cec_dev(hdmi, dev, np); if (ret) return ret; From fa6cd92304447571813df840323855a7e0f95953 Mon Sep 17 00:00:00 2001 From: Johan Hovold Date: Tue, 23 Sep 2025 17:23:40 +0200 Subject: [PATCH 2037/3902] drm/mediatek: ovl_adaptor: Fix probe device leaks commit e0f44f74ed6313e50b38eb39a2c7f210ae208db2 upstream. Make sure to drop the references taken to the component devices by of_find_device_by_node() during probe on probe failure (e.g. probe deferral) and on driver unbind. Fixes: 453c3364632a ("drm/mediatek: Add ovl_adaptor support for MT8195") Cc: stable@vger.kernel.org # 6.4 Cc: Nancy.Lin Signed-off-by: Johan Hovold Reviewed-by: AngeloGioacchino Del Regno Link: https://patchwork.kernel.org/project/dri-devel/patch/20250923152340.18234-6-johan@kernel.org/ Signed-off-by: Chun-Kuang Hu Signed-off-by: Greg Kroah-Hartman --- drivers/gpu/drm/mediatek/mtk_disp_ovl_adaptor.c | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/drivers/gpu/drm/mediatek/mtk_disp_ovl_adaptor.c b/drivers/gpu/drm/mediatek/mtk_disp_ovl_adaptor.c index fe97bb97e004ad..c0af3e3b51d554 100644 --- a/drivers/gpu/drm/mediatek/mtk_disp_ovl_adaptor.c +++ b/drivers/gpu/drm/mediatek/mtk_disp_ovl_adaptor.c @@ -527,6 +527,13 @@ bool mtk_ovl_adaptor_is_comp_present(struct device_node *node) type == OVL_ADAPTOR_TYPE_PADDING; } +static void ovl_adaptor_put_device(void *_dev) +{ + struct device *dev = _dev; + + put_device(dev); +} + static int ovl_adaptor_comp_init(struct device *dev, struct component_match **match) { struct mtk_disp_ovl_adaptor *priv = dev_get_drvdata(dev); @@ -560,6 +567,11 @@ static int ovl_adaptor_comp_init(struct device *dev, struct component_match **ma if (!comp_pdev) return -EPROBE_DEFER; + ret = devm_add_action_or_reset(dev, ovl_adaptor_put_device, + &comp_pdev->dev); + if (ret) + return ret; + priv->ovl_adaptor_comp[id] = &comp_pdev->dev; drm_of_component_match_add(dev, match, component_compare_of, node); From 355d4d4ffaf853200149faf6d1da9d9b2ddfbe92 Mon Sep 17 00:00:00 2001 From: "Mario Limonciello (AMD)" Date: Mon, 8 Dec 2025 22:46:46 -0600 Subject: [PATCH 2038/3902] drm/amd: Fix unbind/rebind for VCN 4.0.5 commit 93a01629c8bfd30906c76921ec986802d76920c6 upstream. Unbinding amdgpu has no problems, but binding it again leads to an error of sysfs file already existing. This is because it wasn't actually cleaned up on unbind. Add the missing cleanup step. Fixes: 547aad32edac ("drm/amdgpu: add VCN4 ip block support") Signed-off-by: Mario Limonciello (AMD) Reviewed-by: Alex Deucher Signed-off-by: Alex Deucher (cherry picked from commit d717e62e9b6ccff0e3cec78a58dfbd00858448b3) Cc: stable@vger.kernel.org Signed-off-by: Greg Kroah-Hartman --- drivers/gpu/drm/amd/amdgpu/vcn_v4_0_5.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/drivers/gpu/drm/amd/amdgpu/vcn_v4_0_5.c b/drivers/gpu/drm/amd/amdgpu/vcn_v4_0_5.c index b107ee80e4728a..1f6a22983c0dd5 100644 --- a/drivers/gpu/drm/amd/amdgpu/vcn_v4_0_5.c +++ b/drivers/gpu/drm/amd/amdgpu/vcn_v4_0_5.c @@ -265,6 +265,8 @@ static int vcn_v4_0_5_sw_fini(struct amdgpu_ip_block *ip_block) if (amdgpu_sriov_vf(adev)) amdgpu_virt_free_mm_table(adev); + amdgpu_vcn_sysfs_reset_mask_fini(adev); + for (i = 0; i < adev->vcn.num_vcn_inst; i++) { r = amdgpu_vcn_suspend(adev, i); if (r) From ec648d8c2db78a6c31e43c3896378eb3dbbe2fd7 Mon Sep 17 00:00:00 2001 From: Andy Yan Date: Wed, 12 Nov 2025 16:50:23 +0800 Subject: [PATCH 2039/3902] drm/rockchip: vop2: Use OVL_LAYER_SEL configuration instead of use win_mask calculate used layers commit d3fe9aa495854f8d88c69c41a4b31e69424656ad upstream. When there are multiple Video Ports, and only one of them is working (for example, VP1 is working while VP0 is not), in this case, the win_mask of VP0 is 0. However, we have already set the port mux for VP0 according to vp0->nlayers, and at the same time, in the OVL_LAYER_SEL register, there are windows will also be assigned to layers which will map to the inactive VPs. In this situation, vp0->win_mask is zero as it now working, it is more reliable to calculate the used layers based on the configuration of the OVL_LAYER_SEL register. Note: as the configuration of OVL_LAYER_SEL is take effect when the vsync is come, so we use the value backup in vop2->old_layer_sel instead of read OVL_LAYER_SEL directly. Fixes: 3e89a8c68354 ("drm/rockchip: vop2: Fix the update of LAYER/PORT select registers when there are multi display output on rk3588/rk3568") Cc: stable@vger.kernel.org Reported-by: Diederik de Haas Closes: https://bugs.kde.org/show_bug.cgi?id=511274 Signed-off-by: Andy Yan Tested-by: Dang Huynh Tested-by: Diederik de Haas Signed-off-by: Heiko Stuebner Link: https://lore.kernel.org/r/20251112085024.2480111-1-andyshrk@163.com Signed-off-by: Greg Kroah-Hartman --- drivers/gpu/drm/rockchip/rockchip_vop2_reg.c | 49 +++++++++++++++++--- 1 file changed, 42 insertions(+), 7 deletions(-) diff --git a/drivers/gpu/drm/rockchip/rockchip_vop2_reg.c b/drivers/gpu/drm/rockchip/rockchip_vop2_reg.c index 38c49030c7ab64..cd8380f0eddc84 100644 --- a/drivers/gpu/drm/rockchip/rockchip_vop2_reg.c +++ b/drivers/gpu/drm/rockchip/rockchip_vop2_reg.c @@ -1369,6 +1369,25 @@ static const struct vop2_regs_dump rk3588_regs_dump[] = { }, }; +/* + * phys_id is used to identify a main window(Cluster Win/Smart Win, not + * include the sub win of a cluster or the multi area) that can do overlay + * in main overlay stage. + */ +static struct vop2_win *vop2_find_win_by_phys_id(struct vop2 *vop2, uint8_t phys_id) +{ + struct vop2_win *win; + int i; + + for (i = 0; i < vop2->data->win_size; i++) { + win = &vop2->win[i]; + if (win->data->phys_id == phys_id) + return win; + } + + return NULL; +} + static unsigned long rk3568_set_intf_mux(struct vop2_video_port *vp, int id, u32 polflags) { struct vop2 *vop2 = vp->vop2; @@ -1842,15 +1861,31 @@ static void vop2_parse_alpha(struct vop2_alpha_config *alpha_config, alpha->dst_alpha_ctrl.bits.factor_mode = ALPHA_SRC_INVERSE; } -static int vop2_find_start_mixer_id_for_vp(struct vop2 *vop2, u8 port_id) +static int vop2_find_start_mixer_id_for_vp(struct vop2_video_port *vp) { - struct vop2_video_port *vp; - int used_layer = 0; + struct vop2 *vop2 = vp->vop2; + struct vop2_win *win; + u32 layer_sel = vop2->old_layer_sel; + u32 used_layer = 0; + unsigned long win_mask = vp->win_mask; + unsigned long phys_id; + bool match; int i; - for (i = 0; i < port_id; i++) { - vp = &vop2->vps[i]; - used_layer += hweight32(vp->win_mask); + for (i = 0; i < 31; i += 4) { + match = false; + for_each_set_bit(phys_id, &win_mask, ROCKCHIP_VOP2_ESMART3) { + win = vop2_find_win_by_phys_id(vop2, phys_id); + if (win->data->layer_sel_id[vp->id] == ((layer_sel >> i) & 0xf)) { + match = true; + break; + } + } + + if (!match) + used_layer += 1; + else + break; } return used_layer; @@ -1935,7 +1970,7 @@ static void vop2_setup_alpha(struct vop2_video_port *vp) u32 dst_global_alpha = DRM_BLEND_ALPHA_OPAQUE; if (vop2->version <= VOP_VERSION_RK3588) - mixer_id = vop2_find_start_mixer_id_for_vp(vop2, vp->id); + mixer_id = vop2_find_start_mixer_id_for_vp(vp); else mixer_id = 0; From 07bcf0498047e468c4d5f78c075fbf371d8fd6e7 Mon Sep 17 00:00:00 2001 From: Luca Ceresoli Date: Thu, 27 Nov 2025 09:42:40 +0100 Subject: [PATCH 2040/3902] drm/bridge: ti-sn65dsi83: ignore PLL_UNLOCK errors MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit commit 35e282c1868de3c9d15f9a8812cbb2e7da06b0c1 upstream. On hardware based on Toradex Verdin AM62 the recovery mechanism added by commit ad5c6ecef27e ("drm: bridge: ti-sn65dsi83: Add error recovery mechanism") has been reported [0] to make the display turn on and off and and the kernel logging "Unexpected link status 0x01". According to the report, the error recovery mechanism is triggered by the PLL_UNLOCK error going active. Analysis suggested the board is unable to provide the correct DSI clock neede by the SN65DSI84, to which the TI SN65DSI84 reacts by raising the PLL_UNLOCK, while the display still works apparently without issues. On other hardware, where all the clocks are within the components specifications, the PLL_UNLOCK bit does not trigger while the display is in normal use. It can trigger for e.g. electromagnetic interference, which is a transient event and exactly the reason why the error recovery mechanism has been implemented. Idelly the PLL_UNLOCK bit could be ignored when working out of specification, but this requires to detect in software whether it triggers because the device is working out of specification but visually correctly for the user or for good reasons (e.g. EMI, or even because working out of specifications but compromising the visual output). The ongoing analysis as of this writing [1][2] has not yet found a way for the driver to discriminate among the two cases. So as a temporary measure mask the PLL_UNLOCK error bit unconditionally. [0] https://lore.kernel.org/r/bhkn6hley4xrol5o3ytn343h4unkwsr26p6s6ltcwexnrsjsdx@mgkdf6ztow42 [1] https://lore.kernel.org/all/b71e941c-fc8a-4ac1-9407-0fe7df73b412@gmail.com/ [2] https://lore.kernel.org/all/20251125103900.31750-1-francesco@dolcini.it/ Fixes: ad5c6ecef27e ("drm: bridge: ti-sn65dsi83: Add error recovery mechanism") Closes: https://lore.kernel.org/r/bhkn6hley4xrol5o3ytn343h4unkwsr26p6s6ltcwexnrsjsdx@mgkdf6ztow42 Cc: stable@vger.kernel.org # 6.15+ Reported-by: João Paulo Gonçalves Tested-by: Emanuele Ghidoli Co-developed-by: Hervé Codina Signed-off-by: Hervé Codina Signed-off-by: Luca Ceresoli Link: https://patch.msgid.link/20251127-drm-ti-sn65dsi83-ignore-pll-unlock-v1-1-8a03fdf562e9@bootlin.com Signed-off-by: Maxime Ripard Signed-off-by: Greg Kroah-Hartman --- drivers/gpu/drm/bridge/ti-sn65dsi83.c | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/drivers/gpu/drm/bridge/ti-sn65dsi83.c b/drivers/gpu/drm/bridge/ti-sn65dsi83.c index 033c44326552ab..fffb47b62f437b 100644 --- a/drivers/gpu/drm/bridge/ti-sn65dsi83.c +++ b/drivers/gpu/drm/bridge/ti-sn65dsi83.c @@ -429,7 +429,14 @@ static void sn65dsi83_handle_errors(struct sn65dsi83 *ctx) */ ret = regmap_read(ctx->regmap, REG_IRQ_STAT, &irq_stat); - if (ret || irq_stat) { + + /* + * Some hardware (Toradex Verdin AM62) is known to report the + * PLL_UNLOCK error interrupt while working without visible + * problems. In lack of a reliable way to discriminate such cases + * from user-visible PLL_UNLOCK cases, ignore that bit entirely. + */ + if (ret || irq_stat & ~REG_IRQ_STAT_CHA_PLL_UNLOCK) { /* * IRQ acknowledged is not always possible (the bridge can be in * a state where it doesn't answer anymore). To prevent an @@ -654,7 +661,7 @@ static void sn65dsi83_atomic_enable(struct drm_bridge *bridge, if (ctx->irq) { /* Enable irq to detect errors */ regmap_write(ctx->regmap, REG_IRQ_GLOBAL, REG_IRQ_GLOBAL_IRQ_EN); - regmap_write(ctx->regmap, REG_IRQ_EN, 0xff); + regmap_write(ctx->regmap, REG_IRQ_EN, 0xff & ~REG_IRQ_EN_CHA_PLL_UNLOCK_EN); } else { /* Use the polling task */ sn65dsi83_monitor_start(ctx); From 7e56e90b988ccb5ceb77bfa4e5ebc0c6b8265bad Mon Sep 17 00:00:00 2001 From: Lyude Paul Date: Tue, 2 Dec 2025 12:59:12 -0500 Subject: [PATCH 2041/3902] drm/nouveau/gsp: Allocate fwsec-sb at boot commit da67179e5538b473a47c87e87cb35b1a7551ad9b upstream. At the moment - the memory allocation for fwsec-sb is created as-needed and is released after being used. Typically this is at some point well after driver load, which can cause runtime suspend/resume to initially work on driver load but then later fail on a machine that has been running for long enough with sufficiently high enough memory pressure: kworker/7:1: page allocation failure: order:5, mode:0xcc0(GFP_KERNEL), nodemask=(null),cpuset=/,mems_allowed=0 CPU: 7 UID: 0 PID: 875159 Comm: kworker/7:1 Not tainted 6.17.8-300.fc43.x86_64 #1 PREEMPT(lazy) Hardware name: SLIMBOOK Executive/Executive, BIOS N.1.10GRU06 02/02/2024 Workqueue: pm pm_runtime_work Call Trace: dump_stack_lvl+0x5d/0x80 warn_alloc+0x163/0x190 ? __alloc_pages_direct_compact+0x1b3/0x220 __alloc_pages_slowpath.constprop.0+0x57a/0xb10 __alloc_frozen_pages_noprof+0x334/0x350 __alloc_pages_noprof+0xe/0x20 __dma_direct_alloc_pages.isra.0+0x1eb/0x330 dma_direct_alloc_pages+0x3c/0x190 dma_alloc_pages+0x29/0x130 nvkm_firmware_ctor+0x1ae/0x280 [nouveau] nvkm_falcon_fw_ctor+0x3e/0x60 [nouveau] nvkm_gsp_fwsec+0x10e/0x2c0 [nouveau] ? sysvec_apic_timer_interrupt+0xe/0x90 nvkm_gsp_fwsec_sb+0x27/0x70 [nouveau] tu102_gsp_fini+0x65/0x110 [nouveau] ? ktime_get+0x3c/0xf0 nvkm_subdev_fini+0x67/0xc0 [nouveau] nvkm_device_fini+0x94/0x140 [nouveau] nvkm_udevice_fini+0x50/0x70 [nouveau] nvkm_object_fini+0xb1/0x140 [nouveau] nvkm_object_fini+0x70/0x140 [nouveau] ? __pfx_pci_pm_runtime_suspend+0x10/0x10 nouveau_do_suspend+0xe4/0x170 [nouveau] nouveau_pmops_runtime_suspend+0x3e/0xb0 [nouveau] pci_pm_runtime_suspend+0x67/0x1a0 ? __pfx_pci_pm_runtime_suspend+0x10/0x10 __rpm_callback+0x45/0x1f0 ? __pfx_pci_pm_runtime_suspend+0x10/0x10 rpm_callback+0x6d/0x80 rpm_suspend+0xe5/0x5e0 ? finish_task_switch.isra.0+0x99/0x2c0 pm_runtime_work+0x98/0xb0 process_one_work+0x18f/0x350 worker_thread+0x25a/0x3a0 ? __pfx_worker_thread+0x10/0x10 kthread+0xf9/0x240 ? __pfx_kthread+0x10/0x10 ? __pfx_kthread+0x10/0x10 ret_from_fork+0xf1/0x110 ? __pfx_kthread+0x10/0x10 ret_from_fork_asm+0x1a/0x30 The reason this happens is because the fwsec-sb firmware image only supports being booted from a contiguous coherent sysmem allocation. If a system runs into enough memory fragmentation from memory pressure, such as what can happen on systems with low amounts of memory, this can lead to a situation where it later becomes impossible to find space for a large enough contiguous allocation to hold fwsec-sb. This causes us to fail to boot the firmware image, causing the GPU to fail booting and causing the driver to fail. Since this firmware can't use non-contiguous allocations, the best solution to avoid this issue is to simply allocate the memory for fwsec-sb during initial driver-load, and reuse the memory allocation when fwsec-sb needs to be used. We then release the memory allocations on driver unload. Signed-off-by: Lyude Paul Fixes: 594766ca3e53 ("drm/nouveau/gsp: move booter handling to GPU-specific code") Cc: # v6.16+ Reviewed-by: Timur Tabi Link: https://patch.msgid.link/20251202175918.63533-1-lyude@redhat.com Signed-off-by: Greg Kroah-Hartman --- .../gpu/drm/nouveau/include/nvkm/subdev/gsp.h | 4 ++ .../gpu/drm/nouveau/nvkm/subdev/gsp/fwsec.c | 61 +++++++++++++------ .../gpu/drm/nouveau/nvkm/subdev/gsp/priv.h | 3 + .../drm/nouveau/nvkm/subdev/gsp/rm/r535/gsp.c | 10 ++- 4 files changed, 58 insertions(+), 20 deletions(-) diff --git a/drivers/gpu/drm/nouveau/include/nvkm/subdev/gsp.h b/drivers/gpu/drm/nouveau/include/nvkm/subdev/gsp.h index 226c7ec56b8ed9..b8b97e10ae83ed 100644 --- a/drivers/gpu/drm/nouveau/include/nvkm/subdev/gsp.h +++ b/drivers/gpu/drm/nouveau/include/nvkm/subdev/gsp.h @@ -73,6 +73,10 @@ struct nvkm_gsp { const struct firmware *bl; const struct firmware *rm; + + struct { + struct nvkm_falcon_fw sb; + } falcon; } fws; struct nvkm_firmware fw; diff --git a/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/fwsec.c b/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/fwsec.c index 5b721bd9d79949..50376024666042 100644 --- a/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/fwsec.c +++ b/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/fwsec.c @@ -259,18 +259,16 @@ nvkm_gsp_fwsec_v3(struct nvkm_gsp *gsp, const char *name, } static int -nvkm_gsp_fwsec(struct nvkm_gsp *gsp, const char *name, u32 init_cmd) +nvkm_gsp_fwsec_init(struct nvkm_gsp *gsp, struct nvkm_falcon_fw *fw, const char *name, u32 init_cmd) { struct nvkm_subdev *subdev = &gsp->subdev; struct nvkm_device *device = subdev->device; struct nvkm_bios *bios = device->bios; const union nvfw_falcon_ucode_desc *desc; struct nvbios_pmuE flcn_ucode; - u8 idx, ver, hdr; u32 data; u16 size, vers; - struct nvkm_falcon_fw fw = {}; - u32 mbox0 = 0; + u8 idx, ver, hdr; int ret; /* Lookup in VBIOS. */ @@ -291,8 +289,8 @@ nvkm_gsp_fwsec(struct nvkm_gsp *gsp, const char *name, u32 init_cmd) vers = (desc->v2.Hdr & 0x0000ff00) >> 8; switch (vers) { - case 2: ret = nvkm_gsp_fwsec_v2(gsp, name, &desc->v2, size, init_cmd, &fw); break; - case 3: ret = nvkm_gsp_fwsec_v3(gsp, name, &desc->v3, size, init_cmd, &fw); break; + case 2: ret = nvkm_gsp_fwsec_v2(gsp, name, &desc->v2, size, init_cmd, fw); break; + case 3: ret = nvkm_gsp_fwsec_v3(gsp, name, &desc->v3, size, init_cmd, fw); break; default: nvkm_error(subdev, "%s(v%d): version unknown\n", name, vers); return -EINVAL; @@ -303,15 +301,19 @@ nvkm_gsp_fwsec(struct nvkm_gsp *gsp, const char *name, u32 init_cmd) return ret; } - /* Boot. */ - ret = nvkm_falcon_fw_boot(&fw, subdev, true, &mbox0, NULL, 0, 0); - nvkm_falcon_fw_dtor(&fw); - if (ret) - return ret; - return 0; } +static int +nvkm_gsp_fwsec_boot(struct nvkm_gsp *gsp, struct nvkm_falcon_fw *fw) +{ + struct nvkm_subdev *subdev = &gsp->subdev; + u32 mbox0 = 0; + + /* Boot */ + return nvkm_falcon_fw_boot(fw, subdev, true, &mbox0, NULL, 0, 0); +} + int nvkm_gsp_fwsec_sb(struct nvkm_gsp *gsp) { @@ -320,7 +322,7 @@ nvkm_gsp_fwsec_sb(struct nvkm_gsp *gsp) int ret; u32 err; - ret = nvkm_gsp_fwsec(gsp, "fwsec-sb", NVFW_FALCON_APPIF_DMEMMAPPER_CMD_SB); + ret = nvkm_gsp_fwsec_boot(gsp, &gsp->fws.falcon.sb); if (ret) return ret; @@ -334,27 +336,48 @@ nvkm_gsp_fwsec_sb(struct nvkm_gsp *gsp) return 0; } +int +nvkm_gsp_fwsec_sb_ctor(struct nvkm_gsp *gsp) +{ + return nvkm_gsp_fwsec_init(gsp, &gsp->fws.falcon.sb, "fwsec-sb", + NVFW_FALCON_APPIF_DMEMMAPPER_CMD_SB); +} + +void +nvkm_gsp_fwsec_sb_dtor(struct nvkm_gsp *gsp) +{ + nvkm_falcon_fw_dtor(&gsp->fws.falcon.sb); +} + int nvkm_gsp_fwsec_frts(struct nvkm_gsp *gsp) { struct nvkm_subdev *subdev = &gsp->subdev; struct nvkm_device *device = subdev->device; + struct nvkm_falcon_fw fw = {}; int ret; u32 err, wpr2_lo, wpr2_hi; - ret = nvkm_gsp_fwsec(gsp, "fwsec-frts", NVFW_FALCON_APPIF_DMEMMAPPER_CMD_FRTS); + ret = nvkm_gsp_fwsec_init(gsp, &fw, "fwsec-frts", NVFW_FALCON_APPIF_DMEMMAPPER_CMD_FRTS); if (ret) return ret; + ret = nvkm_gsp_fwsec_boot(gsp, &fw); + if (ret) + goto fwsec_dtor; + /* Verify. */ err = nvkm_rd32(device, 0x001400 + (0xe * 4)) >> 16; if (err) { nvkm_error(subdev, "fwsec-frts: 0x%04x\n", err); - return -EIO; + ret = -EIO; + } else { + wpr2_lo = nvkm_rd32(device, 0x1fa824); + wpr2_hi = nvkm_rd32(device, 0x1fa828); + nvkm_debug(subdev, "fwsec-frts: WPR2 @ %08x - %08x\n", wpr2_lo, wpr2_hi); } - wpr2_lo = nvkm_rd32(device, 0x1fa824); - wpr2_hi = nvkm_rd32(device, 0x1fa828); - nvkm_debug(subdev, "fwsec-frts: WPR2 @ %08x - %08x\n", wpr2_lo, wpr2_hi); - return 0; +fwsec_dtor: + nvkm_falcon_fw_dtor(&fw); + return ret; } diff --git a/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/priv.h b/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/priv.h index c3494b7ac572bc..86bdd203bc107c 100644 --- a/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/priv.h +++ b/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/priv.h @@ -6,7 +6,10 @@ enum nvkm_acr_lsf_id; int nvkm_gsp_fwsec_frts(struct nvkm_gsp *); + +int nvkm_gsp_fwsec_sb_ctor(struct nvkm_gsp *); int nvkm_gsp_fwsec_sb(struct nvkm_gsp *); +void nvkm_gsp_fwsec_sb_dtor(struct nvkm_gsp *); struct nvkm_gsp_fwif { int version; diff --git a/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/rm/r535/gsp.c b/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/rm/r535/gsp.c index 32e6a065d6d7a5..2a7e80c6d70f39 100644 --- a/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/rm/r535/gsp.c +++ b/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/rm/r535/gsp.c @@ -1817,12 +1817,16 @@ r535_gsp_rm_boot_ctor(struct nvkm_gsp *gsp) RM_RISCV_UCODE_DESC *desc; int ret; + ret = nvkm_gsp_fwsec_sb_ctor(gsp); + if (ret) + return ret; + hdr = nvfw_bin_hdr(&gsp->subdev, fw->data); desc = (void *)fw->data + hdr->header_offset; ret = nvkm_gsp_mem_ctor(gsp, hdr->data_size, &gsp->boot.fw); if (ret) - return ret; + goto dtor_fwsec; memcpy(gsp->boot.fw.data, fw->data + hdr->data_offset, hdr->data_size); @@ -1831,6 +1835,9 @@ r535_gsp_rm_boot_ctor(struct nvkm_gsp *gsp) gsp->boot.manifest_offset = desc->manifestOffset; gsp->boot.app_version = desc->appVersion; return 0; +dtor_fwsec: + nvkm_gsp_fwsec_sb_dtor(gsp); + return ret; } static const struct nvkm_firmware_func @@ -2101,6 +2108,7 @@ r535_gsp_dtor(struct nvkm_gsp *gsp) mutex_destroy(&gsp->cmdq.mutex); nvkm_gsp_dtor_fws(gsp); + nvkm_gsp_fwsec_sb_dtor(gsp); nvkm_gsp_mem_dtor(&gsp->rmargs); nvkm_gsp_mem_dtor(&gsp->wpr_meta); From 7445db6a7d5a0242d8214582b480600b266cba9e Mon Sep 17 00:00:00 2001 From: Mario Limonciello Date: Fri, 5 Dec 2025 12:41:58 -0600 Subject: [PATCH 2042/3902] drm/amdkfd: Export the cwsr_size and ctl_stack_size to userspace commit 8fc2796dea6f1210e1a01573961d5836a7ce531e upstream. This is important for userspace to avoid hardcoding VGPR size. Reviewed-by: Kent Russell Signed-off-by: Mario Limonciello Signed-off-by: Alex Deucher (cherry picked from commit 71776e0965f9f730af19c5f548827f2a7c91f5a8) Cc: stable@vger.kernel.org Signed-off-by: Greg Kroah-Hartman --- drivers/gpu/drm/amd/amdkfd/kfd_topology.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/drivers/gpu/drm/amd/amdkfd/kfd_topology.c b/drivers/gpu/drm/amd/amdkfd/kfd_topology.c index 811636af14eaac..3eb32d58a12007 100644 --- a/drivers/gpu/drm/amd/amdkfd/kfd_topology.c +++ b/drivers/gpu/drm/amd/amdkfd/kfd_topology.c @@ -491,6 +491,10 @@ static ssize_t node_show(struct kobject *kobj, struct attribute *attr, dev->node_props.num_sdma_queues_per_engine); sysfs_show_32bit_prop(buffer, offs, "num_cp_queues", dev->node_props.num_cp_queues); + sysfs_show_32bit_prop(buffer, offs, "cwsr_size", + dev->node_props.cwsr_size); + sysfs_show_32bit_prop(buffer, offs, "ctl_stack_size", + dev->node_props.ctl_stack_size); if (dev->gpu) { log_max_watch_addr = From 7f26af7bf9b76c2c2a1a761aab5803e52be21eea Mon Sep 17 00:00:00 2001 From: Jonathan Kim Date: Fri, 5 Dec 2025 14:41:08 -0500 Subject: [PATCH 2043/3902] drm/amdkfd: bump minimum vgpr size for gfx1151 commit cf326449637a566ba98fb82c47d46cd479608c88 upstream. GFX1151 has 1.5x the number of available physical VGPRs per SIMD. Bump total memory availability for acquire checks on queue creation. Signed-off-by: Jonathan Kim Reviewed-by: Mario Limonciello Signed-off-by: Alex Deucher (cherry picked from commit b42f3bf9536c9b710fd1d4deb7d1b0dc819dc72d) Cc: stable@vger.kernel.org Signed-off-by: Greg Kroah-Hartman --- drivers/gpu/drm/amd/amdkfd/kfd_queue.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/gpu/drm/amd/amdkfd/kfd_queue.c b/drivers/gpu/drm/amd/amdkfd/kfd_queue.c index f1e7583650c416..80c4fa2b0975dc 100644 --- a/drivers/gpu/drm/amd/amdkfd/kfd_queue.c +++ b/drivers/gpu/drm/amd/amdkfd/kfd_queue.c @@ -409,6 +409,7 @@ static u32 kfd_get_vgpr_size_per_cu(u32 gfxv) vgpr_size = 0x80000; else if (gfxv == 110000 || /* GFX_VERSION_PLUM_BONITO */ gfxv == 110001 || /* GFX_VERSION_WHEAT_NAS */ + gfxv == 110501 || /* GFX_VERSION_GFX1151 */ gfxv == 120000 || /* GFX_VERSION_GFX1200 */ gfxv == 120001) /* GFX_VERSION_GFX1201 */ vgpr_size = 0x60000; From eb1494204d717eede1f0316c349a047dc61b5669 Mon Sep 17 00:00:00 2001 From: Jay Cornwall Date: Fri, 14 Nov 2025 14:32:42 -0600 Subject: [PATCH 2044/3902] drm/amdkfd: Trap handler support for expert scheduling mode commit b7851f8c66191cd23a0a08bd484465ad74bbbb7d upstream. The trap may be entered with dependency checking disabled. Wait for dependency counters and save/restore scheduling mode. v2: Use ttmp1 instead of ttmp11. ttmp11 is not zero-initialized. While the trap handler does zero this field before use, a user-mode second-level trap handler could not rely on this being zero when using an older kernel mode driver. v3: Use ttmp11 primarily but copy to ttmp1 before jumping to the second level trap handler. ttmp1 is inspectable by a debugger. Unexpected bits in the unused space may regress existing software. Signed-off-by: Jay Cornwall Reviewed-by: Lancelot Six Signed-off-by: Alex Deucher (cherry picked from commit 423888879412e94725ca2bdccd89414887d98e31) Cc: stable@vger.kernel.org Signed-off-by: Greg Kroah-Hartman --- .../gpu/drm/amd/amdkfd/cwsr_trap_handler.h | 62 +++++++++++-------- .../amd/amdkfd/cwsr_trap_handler_gfx12.asm | 37 +++++++++++ 2 files changed, 73 insertions(+), 26 deletions(-) diff --git a/drivers/gpu/drm/amd/amdkfd/cwsr_trap_handler.h b/drivers/gpu/drm/amd/amdkfd/cwsr_trap_handler.h index 0320163b6e740f..f98c735b2905f3 100644 --- a/drivers/gpu/drm/amd/amdkfd/cwsr_trap_handler.h +++ b/drivers/gpu/drm/amd/amdkfd/cwsr_trap_handler.h @@ -3644,14 +3644,18 @@ static const uint32_t cwsr_trap_gfx9_4_3_hex[] = { }; static const uint32_t cwsr_trap_gfx12_hex[] = { - 0xbfa00001, 0xbfa002a2, - 0xb0804009, 0xb8f8f804, + 0xbfa00001, 0xbfa002b2, + 0xb0804009, 0xb8eef81a, + 0xbf880000, 0xb980081a, + 0x00000000, 0xb8f8f804, + 0x9177ff77, 0x0c000000, + 0x846e9a6e, 0x8c776e77, 0x9178ff78, 0x00008c00, 0xb8fbf811, 0x8b6eff78, 0x00004000, 0xbfa10008, 0x8b6eff7b, 0x00000080, 0xbfa20018, 0x8b6ea07b, - 0xbfa20042, 0xbf830010, + 0xbfa2004a, 0xbf830010, 0xb8fbf811, 0xbfa0fffb, 0x8b6eff7b, 0x00000bd0, 0xbfa20010, 0xb8eef812, @@ -3662,28 +3666,32 @@ static const uint32_t cwsr_trap_gfx12_hex[] = { 0xf0000000, 0xbfa20005, 0x8b6fff6f, 0x00000200, 0xbfa20002, 0x8b6ea07b, - 0xbfa2002c, 0xbefa4d82, + 0xbfa20034, 0xbefa4d82, 0xbf8a0000, 0x84fa887a, 0xbf0d8f7b, 0xbfa10002, 0x8c7bff7b, 0xffff0000, - 0xf4601bbd, 0xf8000010, - 0xbf8a0000, 0x846e976e, - 0x9177ff77, 0x00800000, - 0x8c776e77, 0xf4603bbd, - 0xf8000000, 0xbf8a0000, - 0xf4603ebd, 0xf8000008, - 0xbf8a0000, 0x8bee6e6e, - 0xbfa10001, 0xbe80486e, - 0x8b6eff6d, 0xf0000000, - 0xbfa20009, 0xb8eef811, - 0x8b6eff6e, 0x00000080, - 0xbfa20007, 0x8c78ff78, - 0x00004000, 0x80ec886c, - 0x82ed806d, 0xbfa00002, - 0x806c846c, 0x826d806d, - 0x8b6dff6d, 0x0000ffff, - 0x8bfe7e7e, 0x8bea6a6a, - 0x85788978, 0xb9783244, + 0x8b6eff77, 0x0c000000, + 0x916dff6d, 0x0c000000, + 0x8c6d6e6d, 0xf4601bbd, + 0xf8000010, 0xbf8a0000, + 0x846e976e, 0x9177ff77, + 0x00800000, 0x8c776e77, + 0xf4603bbd, 0xf8000000, + 0xbf8a0000, 0xf4603ebd, + 0xf8000008, 0xbf8a0000, + 0x8bee6e6e, 0xbfa10001, + 0xbe80486e, 0x8b6eff6d, + 0xf0000000, 0xbfa20009, + 0xb8eef811, 0x8b6eff6e, + 0x00000080, 0xbfa20007, + 0x8c78ff78, 0x00004000, + 0x80ec886c, 0x82ed806d, + 0xbfa00002, 0x806c846c, + 0x826d806d, 0x8b6dff6d, + 0x0000ffff, 0x8bfe7e7e, + 0x8bea6a6a, 0x85788978, + 0x936eff77, 0x0002001a, + 0xb96ef81a, 0xb9783244, 0xbe804a6c, 0xb8faf802, 0xbf0d987a, 0xbfa10001, 0xbfb00000, 0x8b6dff6d, @@ -3981,7 +3989,7 @@ static const uint32_t cwsr_trap_gfx12_hex[] = { 0x008ce800, 0x00000000, 0x807d817d, 0x8070ff70, 0x00000080, 0xbf0a7b7d, - 0xbfa2fff7, 0xbfa0016e, + 0xbfa2fff7, 0xbfa00171, 0xbef4007e, 0x8b75ff7f, 0x0000ffff, 0x8c75ff75, 0x00040000, 0xbef60080, @@ -4163,12 +4171,14 @@ static const uint32_t cwsr_trap_gfx12_hex[] = { 0xf8000074, 0xbf8a0000, 0x8b6dff6d, 0x0000ffff, 0x8bfe7e7e, 0x8bea6a6a, - 0xb97af804, 0xbe804ec2, - 0xbf94fffe, 0xbe804a6c, + 0x936eff77, 0x0002001a, + 0xb96ef81a, 0xb97af804, 0xbe804ec2, 0xbf94fffe, - 0xbfb10000, 0xbf9f0000, + 0xbe804a6c, 0xbe804ec2, + 0xbf94fffe, 0xbfb10000, 0xbf9f0000, 0xbf9f0000, 0xbf9f0000, 0xbf9f0000, + 0xbf9f0000, 0x00000000, }; static const uint32_t cwsr_trap_gfx9_5_0_hex[] = { diff --git a/drivers/gpu/drm/amd/amdkfd/cwsr_trap_handler_gfx12.asm b/drivers/gpu/drm/amd/amdkfd/cwsr_trap_handler_gfx12.asm index 5a1a1b1f897fe3..07999b4649ded0 100644 --- a/drivers/gpu/drm/amd/amdkfd/cwsr_trap_handler_gfx12.asm +++ b/drivers/gpu/drm/amd/amdkfd/cwsr_trap_handler_gfx12.asm @@ -78,9 +78,16 @@ var SQ_WAVE_EXCP_FLAG_PRIV_RESTORE_PART_2_SHIFT = SQ_WAVE_EXCP_FLAG_PRIV_ILLEGAL var SQ_WAVE_EXCP_FLAG_PRIV_RESTORE_PART_2_SIZE = SQ_WAVE_EXCP_FLAG_PRIV_HOST_TRAP_SHIFT - SQ_WAVE_EXCP_FLAG_PRIV_ILLEGAL_INST_SHIFT var SQ_WAVE_EXCP_FLAG_PRIV_RESTORE_PART_3_SHIFT = SQ_WAVE_EXCP_FLAG_PRIV_WAVE_START_SHIFT var SQ_WAVE_EXCP_FLAG_PRIV_RESTORE_PART_3_SIZE = 32 - SQ_WAVE_EXCP_FLAG_PRIV_RESTORE_PART_3_SHIFT + +var SQ_WAVE_SCHED_MODE_DEP_MODE_SHIFT = 0 +var SQ_WAVE_SCHED_MODE_DEP_MODE_SIZE = 2 + var BARRIER_STATE_SIGNAL_OFFSET = 16 var BARRIER_STATE_VALID_OFFSET = 0 +var TTMP11_SCHED_MODE_SHIFT = 26 +var TTMP11_SCHED_MODE_SIZE = 2 +var TTMP11_SCHED_MODE_MASK = 0xC000000 var TTMP11_DEBUG_TRAP_ENABLED_SHIFT = 23 var TTMP11_DEBUG_TRAP_ENABLED_MASK = 0x800000 @@ -160,8 +167,19 @@ L_JUMP_TO_RESTORE: s_branch L_RESTORE L_SKIP_RESTORE: + // Assume most relaxed scheduling mode is set. Save and revert to normal mode. + s_getreg_b32 ttmp2, hwreg(HW_REG_WAVE_SCHED_MODE) + s_wait_alu 0 + s_setreg_imm32_b32 hwreg(HW_REG_WAVE_SCHED_MODE, \ + SQ_WAVE_SCHED_MODE_DEP_MODE_SHIFT, SQ_WAVE_SCHED_MODE_DEP_MODE_SIZE), 0 + s_getreg_b32 s_save_state_priv, hwreg(HW_REG_WAVE_STATE_PRIV) //save STATUS since we will change SCC + // Save SCHED_MODE[1:0] into ttmp11[27:26]. + s_andn2_b32 ttmp11, ttmp11, TTMP11_SCHED_MODE_MASK + s_lshl_b32 ttmp2, ttmp2, TTMP11_SCHED_MODE_SHIFT + s_or_b32 ttmp11, ttmp11, ttmp2 + // Clear SPI_PRIO: do not save with elevated priority. // Clear ECC_ERR: prevents SQC store and triggers FATAL_HALT if setreg'd. s_andn2_b32 s_save_state_priv, s_save_state_priv, SQ_WAVE_STATE_PRIV_ALWAYS_CLEAR_MASK @@ -238,6 +256,13 @@ L_FETCH_2ND_TRAP: s_cbranch_scc0 L_NO_SIGN_EXTEND_TMA s_or_b32 ttmp15, ttmp15, 0xFFFF0000 L_NO_SIGN_EXTEND_TMA: +#if ASIC_FAMILY == CHIP_GFX12 + // Move SCHED_MODE[1:0] from ttmp11 to unused bits in ttmp1[27:26] (return PC_HI). + // The second-level trap will restore from ttmp1 for backwards compatibility. + s_and_b32 ttmp2, ttmp11, TTMP11_SCHED_MODE_MASK + s_andn2_b32 ttmp1, ttmp1, TTMP11_SCHED_MODE_MASK + s_or_b32 ttmp1, ttmp1, ttmp2 +#endif s_load_dword ttmp2, [ttmp14, ttmp15], 0x10 scope:SCOPE_SYS // debug trap enabled flag s_wait_idle @@ -287,6 +312,10 @@ L_EXIT_TRAP: // STATE_PRIV.BARRIER_COMPLETE may have changed since we read it. // Only restore fields which the trap handler changes. s_lshr_b32 s_save_state_priv, s_save_state_priv, SQ_WAVE_STATE_PRIV_SCC_SHIFT + + // Assume relaxed scheduling mode after this point. + restore_sched_mode(ttmp2) + s_setreg_b32 hwreg(HW_REG_WAVE_STATE_PRIV, SQ_WAVE_STATE_PRIV_SCC_SHIFT, \ SQ_WAVE_STATE_PRIV_POISON_ERR_SHIFT - SQ_WAVE_STATE_PRIV_SCC_SHIFT + 1), s_save_state_priv @@ -1043,6 +1072,9 @@ L_SKIP_BARRIER_RESTORE: s_and_b64 exec, exec, exec // Restore STATUS.EXECZ, not writable by s_setreg_b32 s_and_b64 vcc, vcc, vcc // Restore STATUS.VCCZ, not writable by s_setreg_b32 + // Assume relaxed scheduling mode after this point. + restore_sched_mode(s_restore_tmp) + s_setreg_b32 hwreg(HW_REG_WAVE_STATE_PRIV), s_restore_state_priv // SCC is included, which is changed by previous salu // Make barrier and LDS state visible to all waves in the group. @@ -1134,3 +1166,8 @@ function valu_sgpr_hazard end #endif end + +function restore_sched_mode(s_tmp) + s_bfe_u32 s_tmp, ttmp11, (TTMP11_SCHED_MODE_SHIFT | (TTMP11_SCHED_MODE_SIZE << 0x10)) + s_setreg_b32 hwreg(HW_REG_WAVE_SCHED_MODE), s_tmp +end From 5457bdfab729c8523220d302565e10adb57cc1a1 Mon Sep 17 00:00:00 2001 From: Ard Biesheuvel Date: Fri, 5 Dec 2025 12:35:01 +0100 Subject: [PATCH 2045/3902] drm/i915: Fix format string truncation warning MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit commit 1c7f9e528f8f488b060b786bfb90b40540854db3 upstream. GCC notices that the 16-byte uabi_name field could theoretically be too small for the formatted string if the instance number exceeds 100. So grow the field to 20 bytes. drivers/gpu/drm/i915/intel_memory_region.c: In function ‘intel_memory_region_create’: drivers/gpu/drm/i915/intel_memory_region.c:273:61: error: ‘%u’ directive output may be truncated writing between 1 and 5 bytes into a region of size between 3 and 11 [-Werror=format-truncation=] 273 | snprintf(mem->uabi_name, sizeof(mem->uabi_name), "%s%u", | ^~ drivers/gpu/drm/i915/intel_memory_region.c:273:58: note: directive argument in the range [0, 65535] 273 | snprintf(mem->uabi_name, sizeof(mem->uabi_name), "%s%u", | ^~~~~~ drivers/gpu/drm/i915/intel_memory_region.c:273:9: note: ‘snprintf’ output between 7 and 19 bytes into a destination of size 16 273 | snprintf(mem->uabi_name, sizeof(mem->uabi_name), "%s%u", | ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 274 | intel_memory_type_str(type), instance); | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Fixes: 3b38d3515753 ("drm/i915: Add stable memory region names") Cc: # v6.8+ Signed-off-by: Ard Biesheuvel Signed-off-by: Tvrtko Ursulin Link: https://lore.kernel.org/r/20251205113500.684286-2-ardb@kernel.org (cherry picked from commit 18476087f1a18dc279d200d934ad94fba1fb51d5) Signed-off-by: Jani Nikula Signed-off-by: Greg Kroah-Hartman --- drivers/gpu/drm/i915/intel_memory_region.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/gpu/drm/i915/intel_memory_region.h b/drivers/gpu/drm/i915/intel_memory_region.h index b3b75be9ced5d3..e9a4e6090fe0a0 100644 --- a/drivers/gpu/drm/i915/intel_memory_region.h +++ b/drivers/gpu/drm/i915/intel_memory_region.h @@ -72,7 +72,7 @@ struct intel_memory_region { u16 instance; enum intel_region_id id; char name[16]; - char uabi_name[16]; + char uabi_name[20]; bool private; /* not for userspace */ struct { From 71be8825e83c90c1e020feb77b29e6a99629e642 Mon Sep 17 00:00:00 2001 From: "Kory Maincent (TI.com)" Date: Tue, 25 Nov 2025 10:05:44 +0100 Subject: [PATCH 2046/3902] drm/tilcdc: Fix removal actions in case of failed probe commit a585c7ef9cabda58088916baedc6573e9a5cd2a7 upstream. The drm_kms_helper_poll_fini() and drm_atomic_helper_shutdown() helpers should only be called when the device has been successfully registered. Currently, these functions are called unconditionally in tilcdc_fini(), which causes warnings during probe deferral scenarios. [ 7.972317] WARNING: CPU: 0 PID: 23 at drivers/gpu/drm/drm_atomic_state_helper.c:175 drm_atomic_helper_crtc_duplicate_state+0x60/0x68 ... [ 8.005820] drm_atomic_helper_crtc_duplicate_state from drm_atomic_get_crtc_state+0x68/0x108 [ 8.005858] drm_atomic_get_crtc_state from drm_atomic_helper_disable_all+0x90/0x1c8 [ 8.005885] drm_atomic_helper_disable_all from drm_atomic_helper_shutdown+0x90/0x144 [ 8.005911] drm_atomic_helper_shutdown from tilcdc_fini+0x68/0xf8 [tilcdc] [ 8.005957] tilcdc_fini [tilcdc] from tilcdc_pdev_probe+0xb0/0x6d4 [tilcdc] Fix this by rewriting the failed probe cleanup path using the standard goto error handling pattern, which ensures that cleanup functions are only called on successfully initialized resources. Additionally, remove the now-unnecessary is_registered flag. Cc: stable@vger.kernel.org Fixes: 3c4babae3c4a ("drm: Call drm_atomic_helper_shutdown() at shutdown/remove time for misc drivers") Signed-off-by: Kory Maincent (TI.com) Reviewed-by: Douglas Anderson Reviewed-by: Luca Ceresoli Signed-off-by: Douglas Anderson Link: https://patch.msgid.link/20251125090546.137193-1-kory.maincent@bootlin.com Signed-off-by: Greg Kroah-Hartman --- drivers/gpu/drm/tilcdc/tilcdc_crtc.c | 2 +- drivers/gpu/drm/tilcdc/tilcdc_drv.c | 53 ++++++++++++++++++---------- drivers/gpu/drm/tilcdc/tilcdc_drv.h | 2 +- 3 files changed, 37 insertions(+), 20 deletions(-) diff --git a/drivers/gpu/drm/tilcdc/tilcdc_crtc.c b/drivers/gpu/drm/tilcdc/tilcdc_crtc.c index b5f60b2b2d0e72..41802c9bd14759 100644 --- a/drivers/gpu/drm/tilcdc/tilcdc_crtc.c +++ b/drivers/gpu/drm/tilcdc/tilcdc_crtc.c @@ -586,7 +586,7 @@ static void tilcdc_crtc_recover_work(struct work_struct *work) drm_modeset_unlock(&crtc->mutex); } -static void tilcdc_crtc_destroy(struct drm_crtc *crtc) +void tilcdc_crtc_destroy(struct drm_crtc *crtc) { struct tilcdc_drm_private *priv = crtc->dev->dev_private; diff --git a/drivers/gpu/drm/tilcdc/tilcdc_drv.c b/drivers/gpu/drm/tilcdc/tilcdc_drv.c index 7caec4d38ddf0c..3dcbec312bacb4 100644 --- a/drivers/gpu/drm/tilcdc/tilcdc_drv.c +++ b/drivers/gpu/drm/tilcdc/tilcdc_drv.c @@ -172,8 +172,7 @@ static void tilcdc_fini(struct drm_device *dev) if (priv->crtc) tilcdc_crtc_shutdown(priv->crtc); - if (priv->is_registered) - drm_dev_unregister(dev); + drm_dev_unregister(dev); drm_kms_helper_poll_fini(dev); drm_atomic_helper_shutdown(dev); @@ -220,21 +219,21 @@ static int tilcdc_init(const struct drm_driver *ddrv, struct device *dev) priv->wq = alloc_ordered_workqueue("tilcdc", 0); if (!priv->wq) { ret = -ENOMEM; - goto init_failed; + goto put_drm; } priv->mmio = devm_platform_ioremap_resource(pdev, 0); if (IS_ERR(priv->mmio)) { dev_err(dev, "failed to request / ioremap\n"); ret = PTR_ERR(priv->mmio); - goto init_failed; + goto free_wq; } priv->clk = clk_get(dev, "fck"); if (IS_ERR(priv->clk)) { dev_err(dev, "failed to get functional clock\n"); ret = -ENODEV; - goto init_failed; + goto free_wq; } pm_runtime_enable(dev); @@ -313,7 +312,7 @@ static int tilcdc_init(const struct drm_driver *ddrv, struct device *dev) ret = tilcdc_crtc_create(ddev); if (ret < 0) { dev_err(dev, "failed to create crtc\n"); - goto init_failed; + goto disable_pm; } modeset_init(ddev); @@ -324,46 +323,46 @@ static int tilcdc_init(const struct drm_driver *ddrv, struct device *dev) if (ret) { dev_err(dev, "failed to register cpufreq notifier\n"); priv->freq_transition.notifier_call = NULL; - goto init_failed; + goto destroy_crtc; } #endif if (priv->is_componentized) { ret = component_bind_all(dev, ddev); if (ret < 0) - goto init_failed; + goto unregister_cpufreq_notif; ret = tilcdc_add_component_encoder(ddev); if (ret < 0) - goto init_failed; + goto unbind_component; } else { ret = tilcdc_attach_external_device(ddev); if (ret) - goto init_failed; + goto unregister_cpufreq_notif; } if (!priv->external_connector && ((priv->num_encoders == 0) || (priv->num_connectors == 0))) { dev_err(dev, "no encoders/connectors found\n"); ret = -EPROBE_DEFER; - goto init_failed; + goto unbind_component; } ret = drm_vblank_init(ddev, 1); if (ret < 0) { dev_err(dev, "failed to initialize vblank\n"); - goto init_failed; + goto unbind_component; } ret = platform_get_irq(pdev, 0); if (ret < 0) - goto init_failed; + goto unbind_component; priv->irq = ret; ret = tilcdc_irq_install(ddev, priv->irq); if (ret < 0) { dev_err(dev, "failed to install IRQ handler\n"); - goto init_failed; + goto unbind_component; } drm_mode_config_reset(ddev); @@ -372,16 +371,34 @@ static int tilcdc_init(const struct drm_driver *ddrv, struct device *dev) ret = drm_dev_register(ddev, 0); if (ret) - goto init_failed; - priv->is_registered = true; + goto stop_poll; drm_client_setup_with_color_mode(ddev, bpp); return 0; -init_failed: - tilcdc_fini(ddev); +stop_poll: + drm_kms_helper_poll_fini(ddev); + tilcdc_irq_uninstall(ddev); +unbind_component: + if (priv->is_componentized) + component_unbind_all(dev, ddev); +unregister_cpufreq_notif: +#ifdef CONFIG_CPU_FREQ + cpufreq_unregister_notifier(&priv->freq_transition, + CPUFREQ_TRANSITION_NOTIFIER); +destroy_crtc: +#endif + tilcdc_crtc_destroy(priv->crtc); +disable_pm: + pm_runtime_disable(dev); + clk_put(priv->clk); +free_wq: + destroy_workqueue(priv->wq); +put_drm: platform_set_drvdata(pdev, NULL); + ddev->dev_private = NULL; + drm_dev_put(ddev); return ret; } diff --git a/drivers/gpu/drm/tilcdc/tilcdc_drv.h b/drivers/gpu/drm/tilcdc/tilcdc_drv.h index b818448c83f612..58b276f82a669a 100644 --- a/drivers/gpu/drm/tilcdc/tilcdc_drv.h +++ b/drivers/gpu/drm/tilcdc/tilcdc_drv.h @@ -82,7 +82,6 @@ struct tilcdc_drm_private { struct drm_encoder *external_encoder; struct drm_connector *external_connector; - bool is_registered; bool is_componentized; bool irq_enabled; }; @@ -164,6 +163,7 @@ void tilcdc_crtc_set_panel_info(struct drm_crtc *crtc, void tilcdc_crtc_set_simulate_vesa_sync(struct drm_crtc *crtc, bool simulate_vesa_sync); void tilcdc_crtc_shutdown(struct drm_crtc *crtc); +void tilcdc_crtc_destroy(struct drm_crtc *crtc); int tilcdc_crtc_update_fb(struct drm_crtc *crtc, struct drm_framebuffer *fb, struct drm_pending_vblank_event *event); From b94182b3d7228aec18d069cba56d5982e9bfe1b1 Mon Sep 17 00:00:00 2001 From: Simon Richter Date: Tue, 14 Oct 2025 01:11:33 +0900 Subject: [PATCH 2047/3902] drm/ttm: Avoid NULL pointer deref for evicted BOs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit commit 491adc6a0f9903c32b05f284df1148de39e8e644 upstream. It is possible for a BO to exist that is not currently associated with a resource, e.g. because it has been evicted. When devcoredump tries to read the contents of all BOs for dumping, we need to expect this as well -- in this case, ENODATA is recorded instead of the buffer contents. Fixes: 7d08df5d0bd3 ("drm/ttm: Add ttm_bo_access") Fixes: 09ac4fcb3f25 ("drm/ttm: Implement vm_operations_struct.access v2") Cc: stable Closes: https://gitlab.freedesktop.org/drm/xe/kernel/-/issues/6271 Signed-off-by: Simon Richter Reviewed-by: Matthew Brost Reviewed-by: Shuicheng Lin Reviewed-by: Christian König Signed-off-by: Matthew Brost Link: https://patch.msgid.link/20251013161241.709916-1-Simon.Richter@hogyros.de Signed-off-by: Greg Kroah-Hartman --- drivers/gpu/drm/ttm/ttm_bo_vm.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/drivers/gpu/drm/ttm/ttm_bo_vm.c b/drivers/gpu/drm/ttm/ttm_bo_vm.c index b47020fca19923..e6abc7b40b1895 100644 --- a/drivers/gpu/drm/ttm/ttm_bo_vm.c +++ b/drivers/gpu/drm/ttm/ttm_bo_vm.c @@ -434,6 +434,11 @@ int ttm_bo_access(struct ttm_buffer_object *bo, unsigned long offset, if (ret) return ret; + if (!bo->resource) { + ret = -ENODATA; + goto unlock; + } + switch (bo->resource->mem_type) { case TTM_PL_SYSTEM: fallthrough; @@ -448,6 +453,7 @@ int ttm_bo_access(struct ttm_buffer_object *bo, unsigned long offset, ret = -EIO; } +unlock: ttm_bo_unreserve(bo); return ret; From 635c8d6e72f6e6912b11fd8d534f35062221fbd9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20Rebe?= Date: Mon, 8 Dec 2025 14:18:27 +0100 Subject: [PATCH 2048/3902] drm/mgag200: Fix big-endian support MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit commit 6cb31fba137d45e682ce455b8ea364f44d5d4f98 upstream. Unlike the original, deleted Matrox mga driver, the new mgag200 driver has the XRGB frame-buffer byte swapped on big-endian "RISC" systems. Fix by enabling byte swapping "PowerPC" OPMODE for any __BIG_ENDIAN config. Fixes: 414c45310625 ("mgag200: initial g200se driver (v2)") Signed-off-by: René Rebe Cc: stable@kernel.org Reviewed-by: Thomas Zimmermann Signed-off-by: Thomas Zimmermann Link: https://patch.msgid.link/20251208.141827.965103015954471168.rene@exactco.de Signed-off-by: Greg Kroah-Hartman --- drivers/gpu/drm/mgag200/mgag200_mode.c | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/drivers/gpu/drm/mgag200/mgag200_mode.c b/drivers/gpu/drm/mgag200/mgag200_mode.c index 951d715dea3011..d019177462cf7f 100644 --- a/drivers/gpu/drm/mgag200/mgag200_mode.c +++ b/drivers/gpu/drm/mgag200/mgag200_mode.c @@ -161,6 +161,30 @@ static void mgag200_set_startadd(struct mga_device *mdev, WREG_ECRT(0x00, crtcext0); } +/* + * Set the opmode for the hardware swapper for Big-Endian processor + * support for the frame buffer aperture and DMAWIN space. + */ +static void mgag200_set_datasiz(struct mga_device *mdev, u32 format) +{ +#if defined(__BIG_ENDIAN) + u32 opmode = RREG32(MGAREG_OPMODE); + + opmode &= ~(GENMASK(17, 16) | GENMASK(9, 8) | GENMASK(3, 2)); + + /* Big-endian byte-swapping */ + switch (format) { + case DRM_FORMAT_RGB565: + opmode |= 0x10100; + break; + case DRM_FORMAT_XRGB8888: + opmode |= 0x20200; + break; + } + WREG32(MGAREG_OPMODE, opmode); +#endif +} + void mgag200_init_registers(struct mga_device *mdev) { u8 crtc11, misc; @@ -496,6 +520,7 @@ void mgag200_primary_plane_helper_atomic_update(struct drm_plane *plane, struct drm_atomic_helper_damage_iter iter; struct drm_rect damage; + mgag200_set_datasiz(mdev, fb->format->format); drm_atomic_helper_damage_iter_init(&iter, old_plane_state, plane_state); drm_atomic_for_each_plane_damage(&iter, &damage) { mgag200_handle_damage(mdev, shadow_plane_state->data, fb, &damage); From 7b2e6ca716816dfe394e53fdde123a036637f599 Mon Sep 17 00:00:00 2001 From: Karol Wachowski Date: Fri, 12 Dec 2025 14:41:33 +0100 Subject: [PATCH 2049/3902] drm: Fix object leak in DRM_IOCTL_GEM_CHANGE_HANDLE MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit commit 630efee9493cf64ff7b9a1652978807fef385fdd upstream. Add missing drm_gem_object_put() call when drm_gem_object_lookup() successfully returns an object. This fixes a GEM object reference leak that can prevent driver modules from unloading when using prime buffers. Fixes: 53096728b891 ("drm: Add DRM prime interface to reassign GEM handle") Cc: # v6.18+ Signed-off-by: Karol Wachowski Reviewed-by: Christian König Reviewed-by: Maciej Falkowski Signed-off-by: Christian König Link: https://lore.kernel.org/r/20251212134133.475218-1-karol.wachowski@linux.intel.com Signed-off-by: Greg Kroah-Hartman --- drivers/gpu/drm/drm_gem.c | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/drivers/gpu/drm/drm_gem.c b/drivers/gpu/drm/drm_gem.c index f884d155a832a3..3b9df655e83772 100644 --- a/drivers/gpu/drm/drm_gem.c +++ b/drivers/gpu/drm/drm_gem.c @@ -979,8 +979,10 @@ int drm_gem_change_handle_ioctl(struct drm_device *dev, void *data, if (!obj) return -ENOENT; - if (args->handle == args->new_handle) - return 0; + if (args->handle == args->new_handle) { + ret = 0; + goto out; + } mutex_lock(&file_priv->prime.lock); @@ -1012,6 +1014,8 @@ int drm_gem_change_handle_ioctl(struct drm_device *dev, void *data, out_unlock: mutex_unlock(&file_priv->prime.lock); +out: + drm_gem_object_put(obj); return ret; } From a965d4869eefe50767966f1973cc5bb06e2eedf0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20Hellstr=C3=B6m?= Date: Tue, 9 Dec 2025 21:49:20 +0100 Subject: [PATCH 2050/3902] drm/xe/bo: Don't include the CCS metadata in the dma-buf sg-table MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit commit 449bcd5d45eb4ce26740f11f8601082fe734bed2 upstream. Some Xe bos are allocated with extra backing-store for the CCS metadata. It's never been the intention to share the CCS metadata when exporting such bos as dma-buf. Don't include it in the dma-buf sg-table. Fixes: dd08ebf6c352 ("drm/xe: Introduce a new DRM driver for Intel GPUs") Cc: Rodrigo Vivi Cc: Matthew Brost Cc: Maarten Lankhorst Cc: # v6.8+ Signed-off-by: Thomas Hellström Reviewed-by: Matthew Brost Reviewed-by: Karol Wachowski Link: https://patch.msgid.link/20251209204920.224374-1-thomas.hellstrom@linux.intel.com (cherry picked from commit a4ebfb9d95d78a12512b435a698ee6886d712571) Signed-off-by: Thomas Hellström Signed-off-by: Greg Kroah-Hartman --- drivers/gpu/drm/xe/xe_dma_buf.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/gpu/drm/xe/xe_dma_buf.c b/drivers/gpu/drm/xe/xe_dma_buf.c index a7d67725c3ee19..2650c5abb36542 100644 --- a/drivers/gpu/drm/xe/xe_dma_buf.c +++ b/drivers/gpu/drm/xe/xe_dma_buf.c @@ -113,7 +113,7 @@ static struct sg_table *xe_dma_buf_map(struct dma_buf_attachment *attach, case XE_PL_TT: sgt = drm_prime_pages_to_sg(obj->dev, bo->ttm.ttm->pages, - bo->ttm.ttm->num_pages); + obj->size >> PAGE_SHIFT); if (IS_ERR(sgt)) return sgt; From f19cb78876f90d424ca14949b185a6668b05aecd Mon Sep 17 00:00:00 2001 From: Ashutosh Dixit Date: Thu, 11 Dec 2025 22:18:49 -0800 Subject: [PATCH 2051/3902] drm/xe/oa: Disallow 0 OA property values MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit commit 3595114bc31d1eb5e1996164c901485c1ffac6f7 upstream. An OA property value of 0 is invalid and will cause a NPD. Reported-by: Peter Senna Tschudin Closes: https://gitlab.freedesktop.org/drm/xe/kernel/-/issues/6452 Fixes: cc4e6994d5a2 ("drm/xe/oa: Move functions up so they can be reused for config ioctl") Cc: stable@vger.kernel.org Signed-off-by: Ashutosh Dixit Reviewed-by: Harish Chegondi Link: https://patch.msgid.link/20251212061850.1565459-3-ashutosh.dixit@intel.com (cherry picked from commit 7a100e6ddcc47c1f6ba7a19402de86ce24790621) Signed-off-by: Thomas Hellström Signed-off-by: Greg Kroah-Hartman --- drivers/gpu/drm/xe/xe_oa.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/gpu/drm/xe/xe_oa.c b/drivers/gpu/drm/xe/xe_oa.c index 1cb7e4791c6db4..7fbce93f07193f 100644 --- a/drivers/gpu/drm/xe/xe_oa.c +++ b/drivers/gpu/drm/xe/xe_oa.c @@ -1346,7 +1346,7 @@ static int xe_oa_user_ext_set_property(struct xe_oa *oa, enum xe_oa_user_extn_fr ARRAY_SIZE(xe_oa_set_property_funcs_config)); if (XE_IOCTL_DBG(oa->xe, ext.property >= ARRAY_SIZE(xe_oa_set_property_funcs_open)) || - XE_IOCTL_DBG(oa->xe, ext.pad)) + XE_IOCTL_DBG(oa->xe, !ext.property) || XE_IOCTL_DBG(oa->xe, ext.pad)) return -EINVAL; idx = array_index_nospec(ext.property, ARRAY_SIZE(xe_oa_set_property_funcs_open)); From 3570a24eb05e9af3a8a07335c8449ebb0dee1eea Mon Sep 17 00:00:00 2001 From: Ashutosh Dixit Date: Thu, 11 Dec 2025 22:18:50 -0800 Subject: [PATCH 2052/3902] drm/xe/eustall: Disallow 0 EU stall property values MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit commit 3767ca4166ad42fa9e34269efeaf9f15995cd92d upstream. An EU stall property value of 0 is invalid and will cause a NPD. Reported-by: Peter Senna Tschudin Closes: https://gitlab.freedesktop.org/drm/xe/kernel/-/issues/6453 Fixes: 1537ec85ebd7 ("drm/xe/uapi: Introduce API for EU stall sampling") Cc: stable@vger.kernel.org Signed-off-by: Ashutosh Dixit Reviewed-by: Harish Chegondi Link: https://patch.msgid.link/20251212061850.1565459-4-ashutosh.dixit@intel.com (cherry picked from commit 5bf763e908bf795da4ad538d21c1ec41f8021f76) Signed-off-by: Thomas Hellström Signed-off-by: Greg Kroah-Hartman --- drivers/gpu/drm/xe/xe_eu_stall.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/gpu/drm/xe/xe_eu_stall.c b/drivers/gpu/drm/xe/xe_eu_stall.c index f5cfdf29fde345..33a3764e3e71b0 100644 --- a/drivers/gpu/drm/xe/xe_eu_stall.c +++ b/drivers/gpu/drm/xe/xe_eu_stall.c @@ -290,7 +290,7 @@ static int xe_eu_stall_user_ext_set_property(struct xe_device *xe, u64 extension return -EFAULT; if (XE_IOCTL_DBG(xe, ext.property >= ARRAY_SIZE(xe_set_eu_stall_property_funcs)) || - XE_IOCTL_DBG(xe, ext.pad)) + XE_IOCTL_DBG(xe, !ext.property) || XE_IOCTL_DBG(xe, ext.pad)) return -EINVAL; idx = array_index_nospec(ext.property, ARRAY_SIZE(xe_set_eu_stall_property_funcs)); From a7229c1ebeed5768c5263766d0424c480a5e0966 Mon Sep 17 00:00:00 2001 From: Matthew Brost Date: Fri, 12 Dec 2025 10:28:41 -0800 Subject: [PATCH 2053/3902] drm/xe: Adjust long-running workload timeslices to reasonable values MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit commit 6f0f404bd289d79a260b634c5b3f4d330b13472c upstream. A 10ms timeslice for long-running workloads is far too long and causes significant jitter in benchmarks when the system is shared. Adjust the value to 5ms for preempt-fencing VMs, as the resume step there is quite costly as memory is moved around, and set it to zero for pagefault VMs, since switching back to pagefault mode after dma-fence mode is relatively fast. Also change min_run_period_ms to 'unsiged int' type rather than 's64' as only positive values make sense. Fixes: dd08ebf6c352 ("drm/xe: Introduce a new DRM driver for Intel GPUs") Cc: stable@vger.kernel.org Signed-off-by: Matthew Brost Reviewed-by: Thomas Hellström Link: https://patch.msgid.link/20251212182847.1683222-2-matthew.brost@intel.com (cherry picked from commit 33a5abd9a68394aa67f9618b20eee65ee8702ff4) Signed-off-by: Thomas Hellström Signed-off-by: Greg Kroah-Hartman --- drivers/gpu/drm/xe/xe_vm.c | 5 ++++- drivers/gpu/drm/xe/xe_vm_types.h | 2 +- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/drivers/gpu/drm/xe/xe_vm.c b/drivers/gpu/drm/xe/xe_vm.c index f0f699baa9f6e8..747aa8cff60d48 100644 --- a/drivers/gpu/drm/xe/xe_vm.c +++ b/drivers/gpu/drm/xe/xe_vm.c @@ -1481,7 +1481,10 @@ struct xe_vm *xe_vm_create(struct xe_device *xe, u32 flags, struct xe_file *xef) INIT_WORK(&vm->destroy_work, vm_destroy_work_func); INIT_LIST_HEAD(&vm->preempt.exec_queues); - vm->preempt.min_run_period_ms = 10; /* FIXME: Wire up to uAPI */ + if (flags & XE_VM_FLAG_FAULT_MODE) + vm->preempt.min_run_period_ms = 0; + else + vm->preempt.min_run_period_ms = 5; for_each_tile(tile, xe, id) xe_range_fence_tree_init(&vm->rftree[id]); diff --git a/drivers/gpu/drm/xe/xe_vm_types.h b/drivers/gpu/drm/xe/xe_vm_types.h index d6e2a0fdd4b359..fd5b5919c40206 100644 --- a/drivers/gpu/drm/xe/xe_vm_types.h +++ b/drivers/gpu/drm/xe/xe_vm_types.h @@ -268,7 +268,7 @@ struct xe_vm { * @min_run_period_ms: The minimum run period before preempting * an engine again */ - s64 min_run_period_ms; + unsigned int min_run_period_ms; /** @exec_queues: list of exec queues attached to this VM */ struct list_head exec_queues; /** @num_exec_queues: number exec queues attached to this VM */ From e85d0e02121db79de94777dde20ee02be3415690 Mon Sep 17 00:00:00 2001 From: Matthew Brost Date: Fri, 12 Dec 2025 10:28:42 -0800 Subject: [PATCH 2054/3902] drm/xe: Use usleep_range for accurate long-running workload timeslicing MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit commit 80f9c601d9c4d26f00356c0a9c461650e7089273 upstream. msleep is not very accurate in terms of how long it actually sleeps, whereas usleep_range is precise. Replace the timeslice sleep for long-running workloads with the more accurate usleep_range to avoid jitter if the sleep period is less than 20ms. Fixes: dd08ebf6c352 ("drm/xe: Introduce a new DRM driver for Intel GPUs") Cc: stable@vger.kernel.org Signed-off-by: Matthew Brost Reviewed-by: Thomas Hellström Link: https://patch.msgid.link/20251212182847.1683222-3-matthew.brost@intel.com (cherry picked from commit ca415c4d4c17ad676a2c8981e1fcc432221dce79) Signed-off-by: Thomas Hellström Signed-off-by: Greg Kroah-Hartman --- drivers/gpu/drm/xe/xe_guc_submit.c | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/drivers/gpu/drm/xe/xe_guc_submit.c b/drivers/gpu/drm/xe/xe_guc_submit.c index 94ed8159496f10..474789bf6506fa 100644 --- a/drivers/gpu/drm/xe/xe_guc_submit.c +++ b/drivers/gpu/drm/xe/xe_guc_submit.c @@ -670,6 +670,24 @@ static u32 wq_space_until_wrap(struct xe_exec_queue *q) return (WQ_SIZE - q->guc->wqi_tail); } +static inline void relaxed_ms_sleep(unsigned int delay_ms) +{ + unsigned long min_us, max_us; + + if (!delay_ms) + return; + + if (delay_ms > 20) { + msleep(delay_ms); + return; + } + + min_us = mul_u32_u32(delay_ms, 1000); + max_us = min_us + 500; + + usleep_range(min_us, max_us); +} + static int wq_wait_for_space(struct xe_exec_queue *q, u32 wqi_size) { struct xe_guc *guc = exec_queue_to_guc(q); @@ -1559,7 +1577,7 @@ static void __guc_exec_queue_process_msg_suspend(struct xe_sched_msg *msg) since_resume_ms; if (wait_ms > 0 && q->guc->resume_time) - msleep(wait_ms); + relaxed_ms_sleep(wait_ms); set_exec_queue_suspended(q); disable_scheduling(q, false); From 8d9df6d3fa49c18547896d16a5414a09c0c386b0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20Hellstr=C3=B6m?= Date: Wed, 17 Dec 2025 10:34:41 +0100 Subject: [PATCH 2055/3902] drm/xe: Drop preempt-fences when destroying imported dma-bufs. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit commit fe3ccd24138fd391ae8e32289d492c85f67770fc upstream. When imported dma-bufs are destroyed, TTM is not fully individualizing the dma-resv, but it *is* copying the fences that need to be waited for before declaring idle. So in the case where the bo->resv != bo->_resv we can still drop the preempt-fences, but make sure we do that on bo->_resv which contains the fence-pointer copy. In the case where the copying fails, bo->_resv will typically not contain any fences pointers at all, so there will be nothing to drop. In that case, TTM would have ensured all fences that would have been copied are signaled, including any remaining preempt fences. Fixes: dd08ebf6c352 ("drm/xe: Introduce a new DRM driver for Intel GPUs") Fixes: fa0af721bd1f ("drm/ttm: test private resv obj on release/destroy") Cc: Matthew Brost Cc: # v6.16+ Signed-off-by: Thomas Hellström Tested-by: Matthew Brost Reviewed-by: Matthew Brost Link: https://patch.msgid.link/20251217093441.5073-1-thomas.hellstrom@linux.intel.com (cherry picked from commit 425fe550fb513b567bd6d01f397d274092a9c274) Signed-off-by: Thomas Hellström Signed-off-by: Greg Kroah-Hartman --- drivers/gpu/drm/xe/xe_bo.c | 15 ++++----------- 1 file changed, 4 insertions(+), 11 deletions(-) diff --git a/drivers/gpu/drm/xe/xe_bo.c b/drivers/gpu/drm/xe/xe_bo.c index 4410e28dee54be..d5b8332a04ecf8 100644 --- a/drivers/gpu/drm/xe/xe_bo.c +++ b/drivers/gpu/drm/xe/xe_bo.c @@ -1480,7 +1480,7 @@ static bool xe_ttm_bo_lock_in_destructor(struct ttm_buffer_object *ttm_bo) * always succeed here, as long as we hold the lru lock. */ spin_lock(&ttm_bo->bdev->lru_lock); - locked = dma_resv_trylock(ttm_bo->base.resv); + locked = dma_resv_trylock(&ttm_bo->base._resv); spin_unlock(&ttm_bo->bdev->lru_lock); xe_assert(xe, locked); @@ -1500,13 +1500,6 @@ static void xe_ttm_bo_release_notify(struct ttm_buffer_object *ttm_bo) bo = ttm_to_xe_bo(ttm_bo); xe_assert(xe_bo_device(bo), !(bo->created && kref_read(&ttm_bo->base.refcount))); - /* - * Corner case where TTM fails to allocate memory and this BOs resv - * still points the VMs resv - */ - if (ttm_bo->base.resv != &ttm_bo->base._resv) - return; - if (!xe_ttm_bo_lock_in_destructor(ttm_bo)) return; @@ -1516,14 +1509,14 @@ static void xe_ttm_bo_release_notify(struct ttm_buffer_object *ttm_bo) * TODO: Don't do this for external bos once we scrub them after * unbind. */ - dma_resv_for_each_fence(&cursor, ttm_bo->base.resv, + dma_resv_for_each_fence(&cursor, &ttm_bo->base._resv, DMA_RESV_USAGE_BOOKKEEP, fence) { if (xe_fence_is_xe_preempt(fence) && !dma_fence_is_signaled(fence)) { if (!replacement) replacement = dma_fence_get_stub(); - dma_resv_replace_fences(ttm_bo->base.resv, + dma_resv_replace_fences(&ttm_bo->base._resv, fence->context, replacement, DMA_RESV_USAGE_BOOKKEEP); @@ -1531,7 +1524,7 @@ static void xe_ttm_bo_release_notify(struct ttm_buffer_object *ttm_bo) } dma_fence_put(replacement); - dma_resv_unlock(ttm_bo->base.resv); + dma_resv_unlock(&ttm_bo->base._resv); } static void xe_ttm_bo_delete_mem_notify(struct ttm_buffer_object *ttm_bo) From 35ea3282136a630a3fd92b76f5a3a02651145ef1 Mon Sep 17 00:00:00 2001 From: Nikolay Kuratov Date: Thu, 11 Dec 2025 12:36:30 +0300 Subject: [PATCH 2056/3902] drm/msm/dpu: Add missing NULL pointer check for pingpong interface commit 88733a0b64872357e5ecd82b7488121503cb9cc6 upstream. It is checked almost always in dpu_encoder_phys_wb_setup_ctl(), but in a single place the check is missing. Also use convenient locals instead of phys_enc->* where available. Cc: stable@vger.kernel.org Fixes: d7d0e73f7de33 ("drm/msm/dpu: introduce the dpu_encoder_phys_* for writeback") Signed-off-by: Nikolay Kuratov Reviewed-by: Dmitry Baryshkov Patchwork: https://patchwork.freedesktop.org/patch/693860/ Link: https://lore.kernel.org/r/20251211093630.171014-1-kniv@yandex-team.ru Signed-off-by: Dmitry Baryshkov Signed-off-by: Greg Kroah-Hartman --- drivers/gpu/drm/msm/disp/dpu1/dpu_encoder_phys_wb.c | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/drivers/gpu/drm/msm/disp/dpu1/dpu_encoder_phys_wb.c b/drivers/gpu/drm/msm/disp/dpu1/dpu_encoder_phys_wb.c index 46f348972a975d..6d28f2281c7657 100644 --- a/drivers/gpu/drm/msm/disp/dpu1/dpu_encoder_phys_wb.c +++ b/drivers/gpu/drm/msm/disp/dpu1/dpu_encoder_phys_wb.c @@ -247,14 +247,12 @@ static void dpu_encoder_phys_wb_setup_ctl(struct dpu_encoder_phys *phys_enc) if (hw_cdm) intf_cfg.cdm = hw_cdm->idx; - if (phys_enc->hw_pp->merge_3d && phys_enc->hw_pp->merge_3d->ops.setup_3d_mode) - phys_enc->hw_pp->merge_3d->ops.setup_3d_mode(phys_enc->hw_pp->merge_3d, - mode_3d); + if (hw_pp && hw_pp->merge_3d && hw_pp->merge_3d->ops.setup_3d_mode) + hw_pp->merge_3d->ops.setup_3d_mode(hw_pp->merge_3d, mode_3d); /* setup which pp blk will connect to this wb */ - if (hw_pp && phys_enc->hw_wb->ops.bind_pingpong_blk) - phys_enc->hw_wb->ops.bind_pingpong_blk(phys_enc->hw_wb, - phys_enc->hw_pp->idx); + if (hw_pp && hw_wb->ops.bind_pingpong_blk) + hw_wb->ops.bind_pingpong_blk(hw_wb, hw_pp->idx); phys_enc->hw_ctl->ops.setup_intf_cfg(phys_enc->hw_ctl, &intf_cfg); } else if (phys_enc->hw_ctl && phys_enc->hw_ctl->ops.setup_intf_cfg) { From 3165fcd7a1cbdf1d7bf2fed74ef13cbd2367d782 Mon Sep 17 00:00:00 2001 From: Anna Maniscalco Date: Thu, 27 Nov 2025 19:22:35 +0100 Subject: [PATCH 2057/3902] drm/msm: add PERFCTR_CNTL to ifpc_reglist commit 6c6915bfea212d32844b2b7f22bc1aa3669eabc4 upstream. Previously this register would become 0 after IFPC took place which broke all usages of counters. Fixes: a6a0157cc68e ("drm/msm/a6xx: Enable IFPC on Adreno X1-85") Cc: stable@vger.kernel.org Signed-off-by: Anna Maniscalco Reviewed-by: Akhil P Oommen Reviewed-by: Dmitry Baryshkov Patchwork: https://patchwork.freedesktop.org/patch/690960/ Message-ID: <20251127-ifpc_counters-v3-1-fac0a126bc88@gmail.com> Signed-off-by: Rob Clark Signed-off-by: Greg Kroah-Hartman --- drivers/gpu/drm/msm/adreno/a6xx_catalog.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/gpu/drm/msm/adreno/a6xx_catalog.c b/drivers/gpu/drm/msm/adreno/a6xx_catalog.c index 44df6410bce176..2adbc198ecf26d 100644 --- a/drivers/gpu/drm/msm/adreno/a6xx_catalog.c +++ b/drivers/gpu/drm/msm/adreno/a6xx_catalog.c @@ -1360,6 +1360,7 @@ static const u32 a750_ifpc_reglist_regs[] = { REG_A6XX_TPL1_BICUBIC_WEIGHTS_TABLE(2), REG_A6XX_TPL1_BICUBIC_WEIGHTS_TABLE(3), REG_A6XX_TPL1_BICUBIC_WEIGHTS_TABLE(4), + REG_A6XX_RBBM_PERFCTR_CNTL, REG_A6XX_TPL1_NC_MODE_CNTL, REG_A6XX_SP_NC_MODE_CNTL, REG_A6XX_CP_DBG_ECO_CNTL, From 63f23aa2fbb823c8b15a29269fde220d227ce5b3 Mon Sep 17 00:00:00 2001 From: Krzysztof Niemiec Date: Tue, 16 Dec 2025 19:09:01 +0100 Subject: [PATCH 2058/3902] drm/i915/gem: Zero-initialize the eb.vma array in i915_gem_do_execbuffer commit 4fe2bd195435e71c117983d87f278112c5ab364c upstream. Initialize the eb.vma array with values of 0 when the eb structure is first set up. In particular, this sets the eb->vma[i].vma pointers to NULL, simplifying cleanup and getting rid of the bug described below. During the execution of eb_lookup_vmas(), the eb->vma array is successively filled up with struct eb_vma objects. This process includes calling eb_add_vma(), which might fail; however, even in the event of failure, eb->vma[i].vma is set for the currently processed buffer. If eb_add_vma() fails, eb_lookup_vmas() returns with an error, which prompts a call to eb_release_vmas() to clean up the mess. Since eb_lookup_vmas() might fail during processing any (possibly not first) buffer, eb_release_vmas() checks whether a buffer's vma is NULL to know at what point did the lookup function fail. In eb_lookup_vmas(), eb->vma[i].vma is set to NULL if either the helper function eb_lookup_vma() or eb_validate_vma() fails. eb->vma[i+1].vma is set to NULL in case i915_gem_object_userptr_submit_init() fails; the current one needs to be cleaned up by eb_release_vmas() at this point, so the next one is set. If eb_add_vma() fails, neither the current nor the next vma is set to NULL, which is a source of a NULL deref bug described in the issue linked in the Closes tag. When entering eb_lookup_vmas(), the vma pointers are set to the slab poison value, instead of NULL. This doesn't matter for the actual lookup, since it gets overwritten anyway, however the eb_release_vmas() function only recognizes NULL as the stopping value, hence the pointers are being set to NULL as they go in case of intermediate failure. This patch changes the approach to filling them all with NULL at the start instead, rather than handling that manually during failure. Reported-by: Gangmin Kim Closes: https://gitlab.freedesktop.org/drm/i915/kernel/-/issues/15062 Fixes: 544460c33821 ("drm/i915: Multi-BB execbuf") Cc: stable@vger.kernel.org # 5.16.x Signed-off-by: Krzysztof Niemiec Reviewed-by: Janusz Krzysztofik Reviewed-by: Krzysztof Karas Reviewed-by: Andi Shyti Signed-off-by: Andi Shyti Link: https://lore.kernel.org/r/20251216180900.54294-2-krzysztof.niemiec@intel.com (cherry picked from commit 08889b706d4f0b8d2352b7ca29c2d8df4d0787cd) Signed-off-by: Jani Nikula Signed-off-by: Greg Kroah-Hartman --- .../gpu/drm/i915/gem/i915_gem_execbuffer.c | 37 +++++++++---------- 1 file changed, 17 insertions(+), 20 deletions(-) diff --git a/drivers/gpu/drm/i915/gem/i915_gem_execbuffer.c b/drivers/gpu/drm/i915/gem/i915_gem_execbuffer.c index 39c7c32e1e74ed..bcae382acdf565 100644 --- a/drivers/gpu/drm/i915/gem/i915_gem_execbuffer.c +++ b/drivers/gpu/drm/i915/gem/i915_gem_execbuffer.c @@ -950,13 +950,13 @@ static int eb_lookup_vmas(struct i915_execbuffer *eb) vma = eb_lookup_vma(eb, eb->exec[i].handle); if (IS_ERR(vma)) { err = PTR_ERR(vma); - goto err; + return err; } err = eb_validate_vma(eb, &eb->exec[i], vma); if (unlikely(err)) { i915_vma_put(vma); - goto err; + return err; } err = eb_add_vma(eb, ¤t_batch, i, vma); @@ -965,19 +965,8 @@ static int eb_lookup_vmas(struct i915_execbuffer *eb) if (i915_gem_object_is_userptr(vma->obj)) { err = i915_gem_object_userptr_submit_init(vma->obj); - if (err) { - if (i + 1 < eb->buffer_count) { - /* - * Execbuffer code expects last vma entry to be NULL, - * since we already initialized this entry, - * set the next value to NULL or we mess up - * cleanup handling. - */ - eb->vma[i + 1].vma = NULL; - } - + if (err) return err; - } eb->vma[i].flags |= __EXEC_OBJECT_USERPTR_INIT; eb->args->flags |= __EXEC_USERPTR_USED; @@ -985,10 +974,6 @@ static int eb_lookup_vmas(struct i915_execbuffer *eb) } return 0; - -err: - eb->vma[i].vma = NULL; - return err; } static int eb_lock_vmas(struct i915_execbuffer *eb) @@ -3374,7 +3359,8 @@ i915_gem_do_execbuffer(struct drm_device *dev, eb.exec = exec; eb.vma = (struct eb_vma *)(exec + args->buffer_count + 1); - eb.vma[0].vma = NULL; + memset(eb.vma, 0, (args->buffer_count + 1) * sizeof(struct eb_vma)); + eb.batch_pool = NULL; eb.invalid_flags = __EXEC_OBJECT_UNKNOWN_FLAGS; @@ -3583,7 +3569,18 @@ i915_gem_execbuffer2_ioctl(struct drm_device *dev, void *data, if (err) return err; - /* Allocate extra slots for use by the command parser */ + /* + * Allocate extra slots for use by the command parser. + * + * Note that this allocation handles two different arrays (the + * exec2_list array, and the eventual eb.vma array introduced in + * i915_gem_do_execbuffer()), that reside in virtually contiguous + * memory. Also note that the allocation intentionally doesn't fill the + * area with zeros, because the exec2_list part doesn't need to be, as + * it's immediately overwritten by user data a few lines below. + * However, the eb.vma part is explicitly zeroed later in + * i915_gem_do_execbuffer(). + */ exec2_list = kvmalloc_array(count + 2, eb_element_size(), __GFP_NOWARN | GFP_KERNEL); if (exec2_list == NULL) { From 548f139d3c17209a50522e41258b206c31f4ff7e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20Hellstr=C3=B6m?= Date: Fri, 19 Dec 2025 12:32:57 +0100 Subject: [PATCH 2059/3902] drm/xe/svm: Fix a debug printout MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit commit d2d7f5636f0d752a1e0e7eadbbc1839c29177bba upstream. Avoid spamming the log with drm_info(). Use drm_dbg() instead. Fixes: cc795e041034 ("drm/xe/svm: Make xe_svm_range_needs_migrate_to_vram() public") Cc: Matthew Brost Cc: Himal Prasad Ghimiray Cc: # v6.17+ Signed-off-by: Thomas Hellström Reviewed-by: Himal Prasad Ghimiray Link: https://patch.msgid.link/20251219113320.183860-2-thomas.hellstrom@linux.intel.com (cherry picked from commit 72aee5f70ba47b939345a0d3414b51b0639c5b88) Signed-off-by: Thomas Hellström Signed-off-by: Greg Kroah-Hartman --- drivers/gpu/drm/xe/xe_svm.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/gpu/drm/xe/xe_svm.c b/drivers/gpu/drm/xe/xe_svm.c index 129e7818565c82..d0d9415a7b03cf 100644 --- a/drivers/gpu/drm/xe/xe_svm.c +++ b/drivers/gpu/drm/xe/xe_svm.c @@ -942,7 +942,7 @@ bool xe_svm_range_needs_migrate_to_vram(struct xe_svm_range *range, struct xe_vm xe_assert(vm->xe, IS_DGFX(vm->xe)); if (xe_svm_range_in_vram(range)) { - drm_info(&vm->xe->drm, "Range is already in VRAM\n"); + drm_dbg(&vm->xe->drm, "Range is already in VRAM\n"); return false; } From c79ee71f45987a753789c3fede6a34499080584e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20Hellstr=C3=B6m?= Date: Fri, 19 Dec 2025 12:32:59 +0100 Subject: [PATCH 2060/3902] drm/pagemap, drm/xe: Ensure that the devmem allocation is idle before use MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit commit 754c23238438600e9236719f7e67aff2c4d02093 upstream. In situations where no system memory is migrated to devmem, and in upcoming patches where another GPU is performing the migration to the newly allocated devmem buffer, there is nothing to ensure any ongoing clear to the devmem allocation or async eviction from the devmem allocation is complete. Address that by passing a struct dma_fence down to the copy functions, and ensure it is waited for before migration is marked complete. v3: - New patch. v4: - Update the logic used for determining when to wait for the pre_migrate_fence. - Update the logic used for determining when to warn for the pre_migrate_fence since the scheduler fences apparently can signal out-of-order. v5: - Fix a UAF (CI) - Remove references to source P2P migration (Himal) - Put the pre_migrate_fence after migration. v6: - Pipeline the pre_migrate_fence dependency (Matt Brost) Fixes: c5b3eb5a906c ("drm/xe: Add GPUSVM device memory copy vfunc functions") Cc: Matthew Brost Cc: # v6.15+ Signed-off-by: Thomas Hellström Reviewed-by: Matthew Brost Acked-by: Maarten Lankhorst # For merging through drm-xe. Link: https://patch.msgid.link/20251219113320.183860-4-thomas.hellstrom@linux.intel.com (cherry picked from commit 16b5ad31952476fb925c401897fc171cd37f536b) Signed-off-by: Thomas Hellström Signed-off-by: Greg Kroah-Hartman --- drivers/gpu/drm/drm_pagemap.c | 17 +++++++++--- drivers/gpu/drm/xe/xe_migrate.c | 25 +++++++++++++---- drivers/gpu/drm/xe/xe_migrate.h | 6 ++-- drivers/gpu/drm/xe/xe_svm.c | 49 +++++++++++++++++++++++++-------- include/drm/drm_pagemap.h | 17 ++++++++++-- 5 files changed, 88 insertions(+), 26 deletions(-) diff --git a/drivers/gpu/drm/drm_pagemap.c b/drivers/gpu/drm/drm_pagemap.c index 22c44807e3fe97..445cf0e6589fa2 100644 --- a/drivers/gpu/drm/drm_pagemap.c +++ b/drivers/gpu/drm/drm_pagemap.c @@ -3,6 +3,7 @@ * Copyright © 2024-2025 Intel Corporation */ +#include #include #include #include @@ -408,10 +409,14 @@ int drm_pagemap_migrate_to_devmem(struct drm_pagemap_devmem *devmem_allocation, drm_pagemap_get_devmem_page(page, zdd); } - err = ops->copy_to_devmem(pages, pagemap_addr, npages); + err = ops->copy_to_devmem(pages, pagemap_addr, npages, + devmem_allocation->pre_migrate_fence); if (err) goto err_finalize; + dma_fence_put(devmem_allocation->pre_migrate_fence); + devmem_allocation->pre_migrate_fence = NULL; + /* Upon success bind devmem allocation to range and zdd */ devmem_allocation->timeslice_expiration = get_jiffies_64() + msecs_to_jiffies(timeslice_ms); @@ -596,7 +601,7 @@ int drm_pagemap_evict_to_ram(struct drm_pagemap_devmem *devmem_allocation) for (i = 0; i < npages; ++i) pages[i] = migrate_pfn_to_page(src[i]); - err = ops->copy_to_ram(pages, pagemap_addr, npages); + err = ops->copy_to_ram(pages, pagemap_addr, npages, NULL); if (err) goto err_finalize; @@ -732,7 +737,7 @@ static int __drm_pagemap_migrate_to_ram(struct vm_area_struct *vas, for (i = 0; i < npages; ++i) pages[i] = migrate_pfn_to_page(migrate.src[i]); - err = ops->copy_to_ram(pages, pagemap_addr, npages); + err = ops->copy_to_ram(pages, pagemap_addr, npages, NULL); if (err) goto err_finalize; @@ -813,11 +818,14 @@ EXPORT_SYMBOL_GPL(drm_pagemap_pagemap_ops_get); * @ops: Pointer to the operations structure for GPU SVM device memory * @dpagemap: The struct drm_pagemap we're allocating from. * @size: Size of device memory allocation + * @pre_migrate_fence: Fence to wait for or pipeline behind before migration starts. + * (May be NULL). */ void drm_pagemap_devmem_init(struct drm_pagemap_devmem *devmem_allocation, struct device *dev, struct mm_struct *mm, const struct drm_pagemap_devmem_ops *ops, - struct drm_pagemap *dpagemap, size_t size) + struct drm_pagemap *dpagemap, size_t size, + struct dma_fence *pre_migrate_fence) { init_completion(&devmem_allocation->detached); devmem_allocation->dev = dev; @@ -825,6 +833,7 @@ void drm_pagemap_devmem_init(struct drm_pagemap_devmem *devmem_allocation, devmem_allocation->ops = ops; devmem_allocation->dpagemap = dpagemap; devmem_allocation->size = size; + devmem_allocation->pre_migrate_fence = pre_migrate_fence; } EXPORT_SYMBOL_GPL(drm_pagemap_devmem_init); diff --git a/drivers/gpu/drm/xe/xe_migrate.c b/drivers/gpu/drm/xe/xe_migrate.c index a36ce7dce8cc0e..3acdcbf41887f7 100644 --- a/drivers/gpu/drm/xe/xe_migrate.c +++ b/drivers/gpu/drm/xe/xe_migrate.c @@ -1813,6 +1813,7 @@ static struct dma_fence *xe_migrate_vram(struct xe_migrate *m, unsigned long sram_offset, struct drm_pagemap_addr *sram_addr, u64 vram_addr, + struct dma_fence *deps, const enum xe_migrate_copy_dir dir) { struct xe_gt *gt = m->tile->primary_gt; @@ -1890,6 +1891,14 @@ static struct dma_fence *xe_migrate_vram(struct xe_migrate *m, xe_sched_job_add_migrate_flush(job, MI_INVALIDATE_TLB); + if (deps && !dma_fence_is_signaled(deps)) { + dma_fence_get(deps); + err = drm_sched_job_add_dependency(&job->drm, deps); + if (err) + dma_fence_wait(deps, false); + err = 0; + } + mutex_lock(&m->job_mutex); xe_sched_job_arm(job); fence = dma_fence_get(&job->drm.s_fence->finished); @@ -1915,6 +1924,8 @@ static struct dma_fence *xe_migrate_vram(struct xe_migrate *m, * @npages: Number of pages to migrate. * @src_addr: Array of DMA information (source of migrate) * @dst_addr: Device physical address of VRAM (destination of migrate) + * @deps: struct dma_fence representing the dependencies that need + * to be signaled before migration. * * Copy from an array dma addresses to a VRAM device physical address * @@ -1924,10 +1935,11 @@ static struct dma_fence *xe_migrate_vram(struct xe_migrate *m, struct dma_fence *xe_migrate_to_vram(struct xe_migrate *m, unsigned long npages, struct drm_pagemap_addr *src_addr, - u64 dst_addr) + u64 dst_addr, + struct dma_fence *deps) { return xe_migrate_vram(m, npages * PAGE_SIZE, 0, src_addr, dst_addr, - XE_MIGRATE_COPY_TO_VRAM); + deps, XE_MIGRATE_COPY_TO_VRAM); } /** @@ -1936,6 +1948,8 @@ struct dma_fence *xe_migrate_to_vram(struct xe_migrate *m, * @npages: Number of pages to migrate. * @src_addr: Device physical address of VRAM (source of migrate) * @dst_addr: Array of DMA information (destination of migrate) + * @deps: struct dma_fence representing the dependencies that need + * to be signaled before migration. * * Copy from a VRAM device physical address to an array dma addresses * @@ -1945,10 +1959,11 @@ struct dma_fence *xe_migrate_to_vram(struct xe_migrate *m, struct dma_fence *xe_migrate_from_vram(struct xe_migrate *m, unsigned long npages, u64 src_addr, - struct drm_pagemap_addr *dst_addr) + struct drm_pagemap_addr *dst_addr, + struct dma_fence *deps) { return xe_migrate_vram(m, npages * PAGE_SIZE, 0, dst_addr, src_addr, - XE_MIGRATE_COPY_TO_SRAM); + deps, XE_MIGRATE_COPY_TO_SRAM); } static void xe_migrate_dma_unmap(struct xe_device *xe, @@ -2121,7 +2136,7 @@ int xe_migrate_access_memory(struct xe_migrate *m, struct xe_bo *bo, __fence = xe_migrate_vram(m, current_bytes, (unsigned long)buf & ~PAGE_MASK, &pagemap_addr[current_page], - vram_addr, write ? + vram_addr, NULL, write ? XE_MIGRATE_COPY_TO_VRAM : XE_MIGRATE_COPY_TO_SRAM); if (IS_ERR(__fence)) { diff --git a/drivers/gpu/drm/xe/xe_migrate.h b/drivers/gpu/drm/xe/xe_migrate.h index 4fad324b625356..bc55f650204b83 100644 --- a/drivers/gpu/drm/xe/xe_migrate.h +++ b/drivers/gpu/drm/xe/xe_migrate.h @@ -111,12 +111,14 @@ int xe_migrate_init(struct xe_migrate *m); struct dma_fence *xe_migrate_to_vram(struct xe_migrate *m, unsigned long npages, struct drm_pagemap_addr *src_addr, - u64 dst_addr); + u64 dst_addr, + struct dma_fence *deps); struct dma_fence *xe_migrate_from_vram(struct xe_migrate *m, unsigned long npages, u64 src_addr, - struct drm_pagemap_addr *dst_addr); + struct drm_pagemap_addr *dst_addr, + struct dma_fence *deps); struct dma_fence *xe_migrate_copy(struct xe_migrate *m, struct xe_bo *src_bo, diff --git a/drivers/gpu/drm/xe/xe_svm.c b/drivers/gpu/drm/xe/xe_svm.c index d0d9415a7b03cf..65452c20609ed8 100644 --- a/drivers/gpu/drm/xe/xe_svm.c +++ b/drivers/gpu/drm/xe/xe_svm.c @@ -477,7 +477,8 @@ static void xe_svm_copy_us_stats_incr(struct xe_gt *gt, static int xe_svm_copy(struct page **pages, struct drm_pagemap_addr *pagemap_addr, - unsigned long npages, const enum xe_svm_copy_dir dir) + unsigned long npages, const enum xe_svm_copy_dir dir, + struct dma_fence *pre_migrate_fence) { struct xe_vram_region *vr = NULL; struct xe_gt *gt = NULL; @@ -566,7 +567,8 @@ static int xe_svm_copy(struct page **pages, __fence = xe_migrate_from_vram(vr->migrate, i - pos + incr, vram_addr, - &pagemap_addr[pos]); + &pagemap_addr[pos], + pre_migrate_fence); } else { vm_dbg(&xe->drm, "COPY TO VRAM - 0x%016llx -> 0x%016llx, NPAGES=%ld", @@ -575,13 +577,14 @@ static int xe_svm_copy(struct page **pages, __fence = xe_migrate_to_vram(vr->migrate, i - pos + incr, &pagemap_addr[pos], - vram_addr); + vram_addr, + pre_migrate_fence); } if (IS_ERR(__fence)) { err = PTR_ERR(__fence); goto err_out; } - + pre_migrate_fence = NULL; dma_fence_put(fence); fence = __fence; } @@ -604,20 +607,22 @@ static int xe_svm_copy(struct page **pages, vram_addr, (u64)pagemap_addr[pos].addr, 1); __fence = xe_migrate_from_vram(vr->migrate, 1, vram_addr, - &pagemap_addr[pos]); + &pagemap_addr[pos], + pre_migrate_fence); } else { vm_dbg(&xe->drm, "COPY TO VRAM - 0x%016llx -> 0x%016llx, NPAGES=%d", (u64)pagemap_addr[pos].addr, vram_addr, 1); __fence = xe_migrate_to_vram(vr->migrate, 1, &pagemap_addr[pos], - vram_addr); + vram_addr, + pre_migrate_fence); } if (IS_ERR(__fence)) { err = PTR_ERR(__fence); goto err_out; } - + pre_migrate_fence = NULL; dma_fence_put(fence); fence = __fence; } @@ -630,6 +635,8 @@ static int xe_svm_copy(struct page **pages, dma_fence_wait(fence, false); dma_fence_put(fence); } + if (pre_migrate_fence) + dma_fence_wait(pre_migrate_fence, false); /* * XXX: We can't derive the GT here (or anywhere in this functions, but @@ -646,16 +653,20 @@ static int xe_svm_copy(struct page **pages, static int xe_svm_copy_to_devmem(struct page **pages, struct drm_pagemap_addr *pagemap_addr, - unsigned long npages) + unsigned long npages, + struct dma_fence *pre_migrate_fence) { - return xe_svm_copy(pages, pagemap_addr, npages, XE_SVM_COPY_TO_VRAM); + return xe_svm_copy(pages, pagemap_addr, npages, XE_SVM_COPY_TO_VRAM, + pre_migrate_fence); } static int xe_svm_copy_to_ram(struct page **pages, struct drm_pagemap_addr *pagemap_addr, - unsigned long npages) + unsigned long npages, + struct dma_fence *pre_migrate_fence) { - return xe_svm_copy(pages, pagemap_addr, npages, XE_SVM_COPY_TO_SRAM); + return xe_svm_copy(pages, pagemap_addr, npages, XE_SVM_COPY_TO_SRAM, + pre_migrate_fence); } static struct xe_bo *to_xe_bo(struct drm_pagemap_devmem *devmem_allocation) @@ -668,6 +679,7 @@ static void xe_svm_devmem_release(struct drm_pagemap_devmem *devmem_allocation) struct xe_bo *bo = to_xe_bo(devmem_allocation); struct xe_device *xe = xe_bo_device(bo); + dma_fence_put(devmem_allocation->pre_migrate_fence); xe_bo_put_async(bo); xe_pm_runtime_put(xe); } @@ -862,6 +874,7 @@ static int xe_drm_pagemap_populate_mm(struct drm_pagemap *dpagemap, unsigned long timeslice_ms) { struct xe_vram_region *vr = container_of(dpagemap, typeof(*vr), dpagemap); + struct dma_fence *pre_migrate_fence = NULL; struct xe_device *xe = vr->xe; struct device *dev = xe->drm.dev; struct drm_buddy_block *block; @@ -888,8 +901,20 @@ static int xe_drm_pagemap_populate_mm(struct drm_pagemap *dpagemap, break; } + /* Ensure that any clearing or async eviction will complete before migration. */ + if (!dma_resv_test_signaled(bo->ttm.base.resv, DMA_RESV_USAGE_KERNEL)) { + err = dma_resv_get_singleton(bo->ttm.base.resv, DMA_RESV_USAGE_KERNEL, + &pre_migrate_fence); + if (err) + dma_resv_wait_timeout(bo->ttm.base.resv, DMA_RESV_USAGE_KERNEL, + false, MAX_SCHEDULE_TIMEOUT); + else if (pre_migrate_fence) + dma_fence_enable_sw_signaling(pre_migrate_fence); + } + drm_pagemap_devmem_init(&bo->devmem_allocation, dev, mm, - &dpagemap_devmem_ops, dpagemap, end - start); + &dpagemap_devmem_ops, dpagemap, end - start, + pre_migrate_fence); blocks = &to_xe_ttm_vram_mgr_resource(bo->ttm.resource)->blocks; list_for_each_entry(block, blocks, link) diff --git a/include/drm/drm_pagemap.h b/include/drm/drm_pagemap.h index f6e7e234c08926..70a7991f784f9b 100644 --- a/include/drm/drm_pagemap.h +++ b/include/drm/drm_pagemap.h @@ -8,6 +8,7 @@ #define NR_PAGES(order) (1U << (order)) +struct dma_fence; struct drm_pagemap; struct drm_pagemap_zdd; struct device; @@ -174,6 +175,8 @@ struct drm_pagemap_devmem_ops { * @pages: Pointer to array of device memory pages (destination) * @pagemap_addr: Pointer to array of DMA information (source) * @npages: Number of pages to copy + * @pre_migrate_fence: dma-fence to wait for before migration start. + * May be NULL. * * Copy pages to device memory. If the order of a @pagemap_addr entry * is greater than 0, the entry is populated but subsequent entries @@ -183,13 +186,16 @@ struct drm_pagemap_devmem_ops { */ int (*copy_to_devmem)(struct page **pages, struct drm_pagemap_addr *pagemap_addr, - unsigned long npages); + unsigned long npages, + struct dma_fence *pre_migrate_fence); /** * @copy_to_ram: Copy to system RAM (required for migration) * @pages: Pointer to array of device memory pages (source) * @pagemap_addr: Pointer to array of DMA information (destination) * @npages: Number of pages to copy + * @pre_migrate_fence: dma-fence to wait for before migration start. + * May be NULL. * * Copy pages to system RAM. If the order of a @pagemap_addr entry * is greater than 0, the entry is populated but subsequent entries @@ -199,7 +205,8 @@ struct drm_pagemap_devmem_ops { */ int (*copy_to_ram)(struct page **pages, struct drm_pagemap_addr *pagemap_addr, - unsigned long npages); + unsigned long npages, + struct dma_fence *pre_migrate_fence); }; /** @@ -212,6 +219,8 @@ struct drm_pagemap_devmem_ops { * @dpagemap: The struct drm_pagemap of the pages this allocation belongs to. * @size: Size of device memory allocation * @timeslice_expiration: Timeslice expiration in jiffies + * @pre_migrate_fence: Fence to wait for or pipeline behind before migration starts. + * (May be NULL). */ struct drm_pagemap_devmem { struct device *dev; @@ -221,6 +230,7 @@ struct drm_pagemap_devmem { struct drm_pagemap *dpagemap; size_t size; u64 timeslice_expiration; + struct dma_fence *pre_migrate_fence; }; int drm_pagemap_migrate_to_devmem(struct drm_pagemap_devmem *devmem_allocation, @@ -238,7 +248,8 @@ struct drm_pagemap *drm_pagemap_page_to_dpagemap(struct page *page); void drm_pagemap_devmem_init(struct drm_pagemap_devmem *devmem_allocation, struct device *dev, struct mm_struct *mm, const struct drm_pagemap_devmem_ops *ops, - struct drm_pagemap *dpagemap, size_t size); + struct drm_pagemap *dpagemap, size_t size, + struct dma_fence *pre_migrate_fence); int drm_pagemap_populate_mm(struct drm_pagemap *dpagemap, unsigned long start, unsigned long end, From 5487edb7b8ede8a6f2d034a6da6ee4a56f5b1626 Mon Sep 17 00:00:00 2001 From: Lyude Paul Date: Thu, 11 Dec 2025 14:02:54 -0500 Subject: [PATCH 2061/3902] drm/nouveau/dispnv50: Don't call drm_atomic_get_crtc_state() in prepare_fb commit 560271e10b2c86e95ea35afa9e79822e4847f07a upstream. Since we recently started warning about uses of this function after the atomic check phase completes, we've started getting warnings about this in nouveau. It appears a misplaced drm_atomic_get_crtc_state() call has been hiding in our .prepare_fb callback for a while. So, fix this by adding a new nv50_head_atom_get_new() function and use that in our .prepare_fb callback instead. Signed-off-by: Lyude Paul Reviewed-by: Dave Airlie Fixes: 1590700d94ac ("drm/nouveau/kms/nv50-: split each resource type into their own source files") Cc: # v4.18+ Link: https://patch.msgid.link/20251211190256.396742-1-lyude@redhat.com Signed-off-by: Greg Kroah-Hartman --- drivers/gpu/drm/nouveau/dispnv50/atom.h | 13 +++++++++++++ drivers/gpu/drm/nouveau/dispnv50/wndw.c | 2 +- 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/drivers/gpu/drm/nouveau/dispnv50/atom.h b/drivers/gpu/drm/nouveau/dispnv50/atom.h index 93f8f4f645784e..b43c4f9bbcdf58 100644 --- a/drivers/gpu/drm/nouveau/dispnv50/atom.h +++ b/drivers/gpu/drm/nouveau/dispnv50/atom.h @@ -152,8 +152,21 @@ static inline struct nv50_head_atom * nv50_head_atom_get(struct drm_atomic_state *state, struct drm_crtc *crtc) { struct drm_crtc_state *statec = drm_atomic_get_crtc_state(state, crtc); + if (IS_ERR(statec)) return (void *)statec; + + return nv50_head_atom(statec); +} + +static inline struct nv50_head_atom * +nv50_head_atom_get_new(struct drm_atomic_state *state, struct drm_crtc *crtc) +{ + struct drm_crtc_state *statec = drm_atomic_get_new_crtc_state(state, crtc); + + if (!statec) + return NULL; + return nv50_head_atom(statec); } diff --git a/drivers/gpu/drm/nouveau/dispnv50/wndw.c b/drivers/gpu/drm/nouveau/dispnv50/wndw.c index ef9e410babbfba..9a2c20fce0f3eb 100644 --- a/drivers/gpu/drm/nouveau/dispnv50/wndw.c +++ b/drivers/gpu/drm/nouveau/dispnv50/wndw.c @@ -583,7 +583,7 @@ nv50_wndw_prepare_fb(struct drm_plane *plane, struct drm_plane_state *state) asyw->image.offset[0] = nvbo->offset; if (wndw->func->prepare) { - asyh = nv50_head_atom_get(asyw->state.state, asyw->state.crtc); + asyh = nv50_head_atom_get_new(asyw->state.state, asyw->state.crtc); if (IS_ERR(asyh)) return PTR_ERR(asyh); From e8469ef3d480130a47008f523c9808ffc05fe31e Mon Sep 17 00:00:00 2001 From: Alessio Belle Date: Mon, 8 Dec 2025 09:11:00 +0000 Subject: [PATCH 2062/3902] drm/imagination: Disallow exporting of PM/FW protected objects commit 6b991ad8dc3abfe5720fc2e9ee96be63ae43e362 upstream. These objects are meant to be used by the GPU firmware or by the PM unit within the GPU, in which case they may contain physical addresses. This adds a layer of protection against exposing potentially exploitable information outside of the driver. Fixes: ff5f643de0bf ("drm/imagination: Add GEM and VM related code") Signed-off-by: Alessio Belle Cc: stable@vger.kernel.org Link: https://patch.msgid.link/20251208-no-export-pm-fw-obj-v1-1-83ab12c61693@imgtec.com Signed-off-by: Matt Coster Signed-off-by: Greg Kroah-Hartman --- drivers/gpu/drm/imagination/pvr_gem.c | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/drivers/gpu/drm/imagination/pvr_gem.c b/drivers/gpu/drm/imagination/pvr_gem.c index a66cf082af2445..c07c9a91519039 100644 --- a/drivers/gpu/drm/imagination/pvr_gem.c +++ b/drivers/gpu/drm/imagination/pvr_gem.c @@ -28,6 +28,16 @@ static void pvr_gem_object_free(struct drm_gem_object *obj) drm_gem_shmem_object_free(obj); } +static struct dma_buf *pvr_gem_export(struct drm_gem_object *obj, int flags) +{ + struct pvr_gem_object *pvr_obj = gem_to_pvr_gem(obj); + + if (pvr_obj->flags & DRM_PVR_BO_PM_FW_PROTECT) + return ERR_PTR(-EPERM); + + return drm_gem_prime_export(obj, flags); +} + static int pvr_gem_mmap(struct drm_gem_object *gem_obj, struct vm_area_struct *vma) { struct pvr_gem_object *pvr_obj = gem_to_pvr_gem(gem_obj); @@ -42,6 +52,7 @@ static int pvr_gem_mmap(struct drm_gem_object *gem_obj, struct vm_area_struct *v static const struct drm_gem_object_funcs pvr_gem_object_funcs = { .free = pvr_gem_object_free, .print_info = drm_gem_shmem_object_print_info, + .export = pvr_gem_export, .pin = drm_gem_shmem_object_pin, .unpin = drm_gem_shmem_object_unpin, .get_sg_table = drm_gem_shmem_object_get_sg_table, From 28a6c420f0e7832a5f2104ef176f9eec7a4e90a1 Mon Sep 17 00:00:00 2001 From: Junbeom Yeom Date: Fri, 19 Dec 2025 21:40:31 +0900 Subject: [PATCH 2063/3902] erofs: fix unexpected EIO under memory pressure commit 4012d78562193ef5eb613bad4b0c0fa187637cfe upstream. erofs readahead could fail with ENOMEM under the memory pressure because it tries to alloc_page with GFP_NOWAIT | GFP_NORETRY, while GFP_KERNEL for a regular read. And if readahead fails (with non-uptodate folios), the original request will then fall back to synchronous read, and `.read_folio()` should return appropriate errnos. However, in scenarios where readahead and read operations compete, read operation could return an unintended EIO because of an incorrect error propagation. To resolve this, this patch modifies the behavior so that, when the PCL is for read(which means pcl.besteffort is true), it attempts actual decompression instead of propagating the privios error except initial EIO. - Page size: 4K - The original size of FileA: 16K - Compress-ratio per PCL: 50% (Uncompressed 8K -> Compressed 4K) [page0, page1] [page2, page3] [PCL0]---------[PCL1] - functions declaration: . pread(fd, buf, count, offset) . readahead(fd, offset, count) - Thread A tries to read the last 4K - Thread B tries to do readahead 8K from 4K - RA, besteffort == false - R, besteffort == true pread(FileA, buf, 4K, 12K) do readahead(page3) // failed with ENOMEM wait_lock(page3) if (!uptodate(page3)) goto do_read readahead(FileA, 4K, 8K) // Here create PCL-chain like below: // [null, page1] [page2, null] // [PCL0:RA]-----[PCL1:RA] ... do read(page3) // found [PCL1:RA] and add page3 into it, // and then, change PCL1 from RA to R ... // Now, PCL-chain is as below: // [null, page1] [page2, page3] // [PCL0:RA]-----[PCL1:R] // try to decompress PCL-chain... z_erofs_decompress_queue err = 0; // failed with ENOMEM, so page 1 // only for RA will not be uptodated. // it's okay. err = decompress([PCL0:RA], err) // However, ENOMEM propagated to next // PCL, even though PCL is not only // for RA but also for R. As a result, // it just failed with ENOMEM without // trying any decompression, so page2 // and page3 will not be uptodated. ** BUG HERE ** --> err = decompress([PCL1:R], err) return err as ENOMEM ... wait_lock(page3) if (!uptodate(page3)) return EIO <-- Return an unexpected EIO! ... Fixes: 2349d2fa02db ("erofs: sunset unneeded NOFAILs") Cc: stable@vger.kernel.org Reviewed-by: Jaewook Kim Reviewed-by: Sungjong Seo Signed-off-by: Junbeom Yeom Reviewed-by: Gao Xiang Signed-off-by: Gao Xiang Reviewed-by: Gao Xiang Signed-off-by: Gao Xiang Signed-off-by: Greg Kroah-Hartman --- fs/erofs/zdata.c | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/fs/erofs/zdata.c b/fs/erofs/zdata.c index bc80cfe482f73b..683703aee5ef29 100644 --- a/fs/erofs/zdata.c +++ b/fs/erofs/zdata.c @@ -1262,17 +1262,17 @@ static int z_erofs_parse_in_bvecs(struct z_erofs_backend *be, bool *overlapped) return err; } -static int z_erofs_decompress_pcluster(struct z_erofs_backend *be, int err) +static int z_erofs_decompress_pcluster(struct z_erofs_backend *be, bool eio) { struct erofs_sb_info *const sbi = EROFS_SB(be->sb); struct z_erofs_pcluster *pcl = be->pcl; unsigned int pclusterpages = z_erofs_pclusterpages(pcl); const struct z_erofs_decompressor *decomp = z_erofs_decomp[pcl->algorithmformat]; - int i, j, jtop, err2; + bool try_free = true; + int i, j, jtop, err2, err = eio ? -EIO : 0; struct page *page; bool overlapped; - bool try_free = true; mutex_lock(&pcl->lock); be->nr_pages = PAGE_ALIGN(pcl->length + pcl->pageofs_out) >> PAGE_SHIFT; @@ -1400,12 +1400,12 @@ static int z_erofs_decompress_queue(const struct z_erofs_decompressqueue *io, .pcl = io->head, }; struct z_erofs_pcluster *next; - int err = io->eio ? -EIO : 0; + int err = 0; for (; be.pcl != Z_EROFS_PCLUSTER_TAIL; be.pcl = next) { DBG_BUGON(!be.pcl); next = READ_ONCE(be.pcl->next); - err = z_erofs_decompress_pcluster(&be, err) ?: err; + err = z_erofs_decompress_pcluster(&be, io->eio) ?: err; } return err; } From 142dbd7ed190679c7cac335833f313d56e0f1dd2 Mon Sep 17 00:00:00 2001 From: Damien Le Moal Date: Thu, 13 Nov 2025 22:40:26 +0900 Subject: [PATCH 2064/3902] block: fix NULL pointer dereference in blk_zone_reset_all_bio_endio() commit c2b8d20628ca789640f64074a642f9440eefc623 upstream. For zoned block devices that do not need zone write plugs (e.g. most device mapper devices that support zones), the disk hash table of zone write plugs is NULL. For such devices, blk_zone_reset_all_bio_endio() should not attempt to scan this has table as that causes a NULL pointer dereference. Fix this by checking that the disk does have zone write plugs using the atomic counter. This is equivalent to checking for a non-NULL hash table but has the advantage to also speed up the execution of blk_zone_reset_all_bio_endio() for devices that do use zone write plugs but do not have any plug in the hash table (e.g. a disk with only full zones). Fixes: efae226c2ef1 ("block: handle zone management operations completions") Reported-by: Shin'ichiro Kawasaki Signed-off-by: Damien Le Moal Reviewed-by: Christoph Hellwig Signed-off-by: Jens Axboe Signed-off-by: Greg Kroah-Hartman --- block/blk-zoned.c | 21 ++++++++++++--------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/block/blk-zoned.c b/block/blk-zoned.c index 3a3cbee60591b5..0c812f3bd7df38 100644 --- a/block/blk-zoned.c +++ b/block/blk-zoned.c @@ -736,17 +736,20 @@ static void blk_zone_reset_all_bio_endio(struct bio *bio) unsigned long flags; unsigned int i; - /* Update the condition of all zone write plugs. */ - rcu_read_lock(); - for (i = 0; i < disk_zone_wplugs_hash_size(disk); i++) { - hlist_for_each_entry_rcu(zwplug, &disk->zone_wplugs_hash[i], - node) { - spin_lock_irqsave(&zwplug->lock, flags); - disk_zone_wplug_set_wp_offset(disk, zwplug, 0); - spin_unlock_irqrestore(&zwplug->lock, flags); + if (atomic_read(&disk->nr_zone_wplugs)) { + /* Update the condition of all zone write plugs. */ + rcu_read_lock(); + for (i = 0; i < disk_zone_wplugs_hash_size(disk); i++) { + hlist_for_each_entry_rcu(zwplug, + &disk->zone_wplugs_hash[i], + node) { + spin_lock_irqsave(&zwplug->lock, flags); + disk_zone_wplug_set_wp_offset(disk, zwplug, 0); + spin_unlock_irqrestore(&zwplug->lock, flags); + } } + rcu_read_unlock(); } - rcu_read_unlock(); } static void blk_zone_finish_bio_endio(struct bio *bio) From 77aa0f5223ffc33545712ec73392822614ed2e9d Mon Sep 17 00:00:00 2001 From: Srinivas Pandruvada Date: Thu, 23 Oct 2025 10:45:32 -0700 Subject: [PATCH 2065/3902] powercap: intel_rapl: Add support for Wildcat Lake platform commit 39f421f2e301f995c17c35b783e2863155b3f647 upstream. Add Wildcat Lake to the list of supported processors for RAPL. Signed-off-by: Srinivas Pandruvada Link: https://patch.msgid.link/20251023174532.1882008-1-srinivas.pandruvada@linux.intel.com Signed-off-by: Rafael J. Wysocki Signed-off-by: Greg Kroah-Hartman --- drivers/powercap/intel_rapl_common.c | 1 + drivers/powercap/intel_rapl_msr.c | 1 + 2 files changed, 2 insertions(+) diff --git a/drivers/powercap/intel_rapl_common.c b/drivers/powercap/intel_rapl_common.c index c7e7f9bf531371..cdb4363589e9c3 100644 --- a/drivers/powercap/intel_rapl_common.c +++ b/drivers/powercap/intel_rapl_common.c @@ -1284,6 +1284,7 @@ static const struct x86_cpu_id rapl_ids[] __initconst = { X86_MATCH_VFM(INTEL_EMERALDRAPIDS_X, &rapl_defaults_spr_server), X86_MATCH_VFM(INTEL_LUNARLAKE_M, &rapl_defaults_core), X86_MATCH_VFM(INTEL_PANTHERLAKE_L, &rapl_defaults_core), + X86_MATCH_VFM(INTEL_WILDCATLAKE_L, &rapl_defaults_core), X86_MATCH_VFM(INTEL_ARROWLAKE_H, &rapl_defaults_core), X86_MATCH_VFM(INTEL_ARROWLAKE, &rapl_defaults_core), X86_MATCH_VFM(INTEL_ARROWLAKE_U, &rapl_defaults_core), diff --git a/drivers/powercap/intel_rapl_msr.c b/drivers/powercap/intel_rapl_msr.c index 4ed06c71a3ac43..c4d536c2f98935 100644 --- a/drivers/powercap/intel_rapl_msr.c +++ b/drivers/powercap/intel_rapl_msr.c @@ -151,6 +151,7 @@ static const struct x86_cpu_id pl4_support_ids[] = { X86_MATCH_VFM(INTEL_ARROWLAKE_U, NULL), X86_MATCH_VFM(INTEL_ARROWLAKE_H, NULL), X86_MATCH_VFM(INTEL_PANTHERLAKE_L, NULL), + X86_MATCH_VFM(INTEL_WILDCATLAKE_L, NULL), {} }; From 845306163962454083f97d9a2e22f67f94ac8e1f Mon Sep 17 00:00:00 2001 From: Kaushlendra Kumar Date: Tue, 28 Oct 2025 15:48:14 +0530 Subject: [PATCH 2066/3902] powercap: intel_rapl: Add support for Nova Lake processors commit 58075aec92a8141fd7f42e1c36d1bc54552c015e upstream. Add RAPL support for Intel Nova Lake and Nova Lake L processors using the core defaults configuration. Signed-off-by: Kaushlendra Kumar [ rjw: Subject and changelog edits, rebase ] Link: https://patch.msgid.link/20251028101814.3482508-1-kaushlendra.kumar@intel.com Signed-off-by: Rafael J. Wysocki Signed-off-by: Greg Kroah-Hartman --- drivers/powercap/intel_rapl_common.c | 2 ++ drivers/powercap/intel_rapl_msr.c | 2 ++ 2 files changed, 4 insertions(+) diff --git a/drivers/powercap/intel_rapl_common.c b/drivers/powercap/intel_rapl_common.c index cdb4363589e9c3..57bebd07c7d0df 100644 --- a/drivers/powercap/intel_rapl_common.c +++ b/drivers/powercap/intel_rapl_common.c @@ -1285,6 +1285,8 @@ static const struct x86_cpu_id rapl_ids[] __initconst = { X86_MATCH_VFM(INTEL_LUNARLAKE_M, &rapl_defaults_core), X86_MATCH_VFM(INTEL_PANTHERLAKE_L, &rapl_defaults_core), X86_MATCH_VFM(INTEL_WILDCATLAKE_L, &rapl_defaults_core), + X86_MATCH_VFM(INTEL_NOVALAKE, &rapl_defaults_core), + X86_MATCH_VFM(INTEL_NOVALAKE_L, &rapl_defaults_core), X86_MATCH_VFM(INTEL_ARROWLAKE_H, &rapl_defaults_core), X86_MATCH_VFM(INTEL_ARROWLAKE, &rapl_defaults_core), X86_MATCH_VFM(INTEL_ARROWLAKE_U, &rapl_defaults_core), diff --git a/drivers/powercap/intel_rapl_msr.c b/drivers/powercap/intel_rapl_msr.c index c4d536c2f98935..c6b9a7debc3545 100644 --- a/drivers/powercap/intel_rapl_msr.c +++ b/drivers/powercap/intel_rapl_msr.c @@ -152,6 +152,8 @@ static const struct x86_cpu_id pl4_support_ids[] = { X86_MATCH_VFM(INTEL_ARROWLAKE_H, NULL), X86_MATCH_VFM(INTEL_PANTHERLAKE_L, NULL), X86_MATCH_VFM(INTEL_WILDCATLAKE_L, NULL), + X86_MATCH_VFM(INTEL_NOVALAKE, NULL), + X86_MATCH_VFM(INTEL_NOVALAKE_L, NULL), {} }; From e7a7d7e629c2abcc0ade47b148ebe71d5832ead9 Mon Sep 17 00:00:00 2001 From: Chenghao Duan Date: Tue, 6 Jan 2026 10:55:02 +0800 Subject: [PATCH 2067/3902] LoongArch: BPF: Enhance the bpf_arch_text_poke() function commit 73721d8676771c6c7b06d4e636cc053fc76afefd upstream. Enhance the bpf_arch_text_poke() function to enable accurate location of BPF program entry points. When modifying the entry point of a BPF program, skip the "move t0, ra" instruction to ensure the correct logic and copy of the jump address. Cc: stable@vger.kernel.org Fixes: 677e6123e3d2 ("LoongArch: BPF: Disable trampoline for kernel module function trace") Signed-off-by: Chenghao Duan Signed-off-by: Huacai Chen Signed-off-by: Greg Kroah-Hartman --- arch/loongarch/net/bpf_jit.c | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/arch/loongarch/net/bpf_jit.c b/arch/loongarch/net/bpf_jit.c index 95c214e2cf096a..87ff025137873a 100644 --- a/arch/loongarch/net/bpf_jit.c +++ b/arch/loongarch/net/bpf_jit.c @@ -1307,6 +1307,10 @@ int bpf_arch_text_poke(void *ip, enum bpf_text_poke_type poke_type, void *old_addr, void *new_addr) { int ret; + unsigned long size = 0; + unsigned long offset = 0; + void *image = NULL; + char namebuf[KSYM_NAME_LEN]; bool is_call = (poke_type == BPF_MOD_CALL); u32 old_insns[LOONGARCH_LONG_JUMP_NINSNS] = {[0 ... 4] = INSN_NOP}; u32 new_insns[LOONGARCH_LONG_JUMP_NINSNS] = {[0 ... 4] = INSN_NOP}; @@ -1314,9 +1318,20 @@ int bpf_arch_text_poke(void *ip, enum bpf_text_poke_type poke_type, /* Only poking bpf text is supported. Since kernel function entry * is set up by ftrace, we rely on ftrace to poke kernel functions. */ - if (!is_bpf_text_address((unsigned long)ip)) + if (!__bpf_address_lookup((unsigned long)ip, &size, &offset, namebuf)) return -ENOTSUPP; + image = ip - offset; + + /* zero offset means we're poking bpf prog entry */ + if (offset == 0) { + /* skip to the nop instruction in bpf prog entry: + * move t0, ra + * nop + */ + ip = image + LOONGARCH_INSN_SIZE; + } + ret = emit_jump_or_nops(old_addr, ip, old_insns, is_call); if (ret) return ret; From a9040eac434b1f0dbc85bbe50bce50d94afc6a91 Mon Sep 17 00:00:00 2001 From: Kevin Tian Date: Tue, 6 Jan 2026 02:11:44 +0000 Subject: [PATCH 2068/3902] vfio/pci: Disable qword access to the PCI ROM bar [ Upstream commit dc85a46928c41423ad89869baf05a589e2975575 ] Commit 2b938e3db335 ("vfio/pci: Enable iowrite64 and ioread64 for vfio pci") enables qword access to the PCI bar resources. However certain devices (e.g. Intel X710) are observed with problem upon qword accesses to the rom bar, e.g. triggering PCI aer errors. This is triggered by Qemu which caches the rom content by simply does a pread() of the remaining size until it gets the full contents. The other bars would only perform operations at the same access width as their guest drivers. Instead of trying to identify all broken devices, universally disable qword access to the rom bar i.e. going back to the old way which worked reliably for years. Reported-by: Farrah Chen Closes: https://bugzilla.kernel.org/show_bug.cgi?id=220740 Fixes: 2b938e3db335 ("vfio/pci: Enable iowrite64 and ioread64 for vfio pci") Cc: stable@vger.kernel.org Signed-off-by: Kevin Tian Tested-by: Farrah Chen Link: https://lore.kernel.org/r/20251218081650.555015-2-kevin.tian@intel.com Signed-off-by: Alex Williamson Signed-off-by: Greg Kroah-Hartman --- drivers/vfio/pci/nvgrace-gpu/main.c | 4 ++-- drivers/vfio/pci/vfio_pci_rdwr.c | 25 ++++++++++++++++++------- include/linux/vfio_pci_core.h | 10 +++++++++- 3 files changed, 29 insertions(+), 10 deletions(-) diff --git a/drivers/vfio/pci/nvgrace-gpu/main.c b/drivers/vfio/pci/nvgrace-gpu/main.c index e346392b72f6ab..3dc3c2432b5e08 100644 --- a/drivers/vfio/pci/nvgrace-gpu/main.c +++ b/drivers/vfio/pci/nvgrace-gpu/main.c @@ -491,7 +491,7 @@ nvgrace_gpu_map_and_read(struct nvgrace_gpu_pci_core_device *nvdev, ret = vfio_pci_core_do_io_rw(&nvdev->core_device, false, nvdev->resmem.ioaddr, buf, offset, mem_count, - 0, 0, false); + 0, 0, false, VFIO_PCI_IO_WIDTH_8); } return ret; @@ -609,7 +609,7 @@ nvgrace_gpu_map_and_write(struct nvgrace_gpu_pci_core_device *nvdev, ret = vfio_pci_core_do_io_rw(&nvdev->core_device, false, nvdev->resmem.ioaddr, (char __user *)buf, pos, mem_count, - 0, 0, true); + 0, 0, true, VFIO_PCI_IO_WIDTH_8); } return ret; diff --git a/drivers/vfio/pci/vfio_pci_rdwr.c b/drivers/vfio/pci/vfio_pci_rdwr.c index 6192788c8ba39f..25380b7dfe18a7 100644 --- a/drivers/vfio/pci/vfio_pci_rdwr.c +++ b/drivers/vfio/pci/vfio_pci_rdwr.c @@ -135,7 +135,8 @@ VFIO_IORDWR(64) ssize_t vfio_pci_core_do_io_rw(struct vfio_pci_core_device *vdev, bool test_mem, void __iomem *io, char __user *buf, loff_t off, size_t count, size_t x_start, - size_t x_end, bool iswrite) + size_t x_end, bool iswrite, + enum vfio_pci_io_width max_width) { ssize_t done = 0; int ret; @@ -150,20 +151,19 @@ ssize_t vfio_pci_core_do_io_rw(struct vfio_pci_core_device *vdev, bool test_mem, else fillable = 0; - if (fillable >= 8 && !(off % 8)) { + if (fillable >= 8 && !(off % 8) && max_width >= 8) { ret = vfio_pci_iordwr64(vdev, iswrite, test_mem, io, buf, off, &filled); if (ret) return ret; - } else - if (fillable >= 4 && !(off % 4)) { + } else if (fillable >= 4 && !(off % 4) && max_width >= 4) { ret = vfio_pci_iordwr32(vdev, iswrite, test_mem, io, buf, off, &filled); if (ret) return ret; - } else if (fillable >= 2 && !(off % 2)) { + } else if (fillable >= 2 && !(off % 2) && max_width >= 2) { ret = vfio_pci_iordwr16(vdev, iswrite, test_mem, io, buf, off, &filled); if (ret) @@ -234,6 +234,7 @@ ssize_t vfio_pci_bar_rw(struct vfio_pci_core_device *vdev, char __user *buf, void __iomem *io; struct resource *res = &vdev->pdev->resource[bar]; ssize_t done; + enum vfio_pci_io_width max_width = VFIO_PCI_IO_WIDTH_8; if (pci_resource_start(pdev, bar)) end = pci_resource_len(pdev, bar); @@ -262,6 +263,16 @@ ssize_t vfio_pci_bar_rw(struct vfio_pci_core_device *vdev, char __user *buf, if (!io) return -ENOMEM; x_end = end; + + /* + * Certain devices (e.g. Intel X710) don't support qword + * access to the ROM bar. Otherwise PCI AER errors might be + * triggered. + * + * Disable qword access to the ROM bar universally, which + * worked reliably for years before qword access is enabled. + */ + max_width = VFIO_PCI_IO_WIDTH_4; } else { int ret = vfio_pci_core_setup_barmap(vdev, bar); if (ret) { @@ -278,7 +289,7 @@ ssize_t vfio_pci_bar_rw(struct vfio_pci_core_device *vdev, char __user *buf, } done = vfio_pci_core_do_io_rw(vdev, res->flags & IORESOURCE_MEM, io, buf, pos, - count, x_start, x_end, iswrite); + count, x_start, x_end, iswrite, max_width); if (done >= 0) *ppos += done; @@ -352,7 +363,7 @@ ssize_t vfio_pci_vga_rw(struct vfio_pci_core_device *vdev, char __user *buf, * to the memory enable bit in the command register. */ done = vfio_pci_core_do_io_rw(vdev, false, iomem, buf, off, count, - 0, 0, iswrite); + 0, 0, iswrite, VFIO_PCI_IO_WIDTH_8); vga_put(vdev->pdev, rsrc); diff --git a/include/linux/vfio_pci_core.h b/include/linux/vfio_pci_core.h index f5c93787f8e0b5..7aa29428982aa5 100644 --- a/include/linux/vfio_pci_core.h +++ b/include/linux/vfio_pci_core.h @@ -102,6 +102,13 @@ struct vfio_pci_core_device { struct rw_semaphore memory_lock; }; +enum vfio_pci_io_width { + VFIO_PCI_IO_WIDTH_1 = 1, + VFIO_PCI_IO_WIDTH_2 = 2, + VFIO_PCI_IO_WIDTH_4 = 4, + VFIO_PCI_IO_WIDTH_8 = 8, +}; + /* Will be exported for vfio pci drivers usage */ int vfio_pci_core_register_dev_region(struct vfio_pci_core_device *vdev, unsigned int type, unsigned int subtype, @@ -139,7 +146,8 @@ pci_ers_result_t vfio_pci_core_aer_err_detected(struct pci_dev *pdev, ssize_t vfio_pci_core_do_io_rw(struct vfio_pci_core_device *vdev, bool test_mem, void __iomem *io, char __user *buf, loff_t off, size_t count, size_t x_start, - size_t x_end, bool iswrite); + size_t x_end, bool iswrite, + enum vfio_pci_io_width max_width); bool vfio_pci_core_range_intersect_range(loff_t buf_start, size_t buf_cnt, loff_t reg_start, size_t reg_cnt, loff_t *buf_offset, From 1ba137c89ff03e27dbf01abb0b04fe751d254e31 Mon Sep 17 00:00:00 2001 From: SeongJae Park Date: Mon, 5 Jan 2026 17:42:49 -0800 Subject: [PATCH 2069/3902] mm/damon/tests/core-kunit: handle alloc failures on damon_test_split_regions_of() damon_test_split_regions_of() is assuming all dynamic memory allocation in it will succeed. Those are indeed likely in the real use cases since those allocations are too small to fail, but theoretically those could fail. In the case, inappropriate memory access can happen. Fix it by appropriately cleanup pre-allocated memory and skip the execution of the remaining tests in the failure cases. Link: https://lkml.kernel.org/r/20251101182021.74868-9-sj@kernel.org Fixes: 17ccae8bb5c9 ("mm/damon: add kunit tests") Signed-off-by: SeongJae Park Cc: Brendan Higgins Cc: David Gow Cc: Kefeng Wang Cc: [5.15+] Signed-off-by: Andrew Morton (cherry picked from commit eded254cb69044bd4abde87394ea44909708d7c0) Signed-off-by: SeongJae Park Signed-off-by: Greg Kroah-Hartman --- mm/damon/tests/core-kunit.h | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/mm/damon/tests/core-kunit.h b/mm/damon/tests/core-kunit.h index 63d10ca934f611..66877787527287 100644 --- a/mm/damon/tests/core-kunit.h +++ b/mm/damon/tests/core-kunit.h @@ -278,15 +278,35 @@ static void damon_test_split_regions_of(struct kunit *test) struct damon_target *t; struct damon_region *r; + if (!c) + kunit_skip(test, "ctx alloc fail"); t = damon_new_target(); + if (!t) { + damon_destroy_ctx(c); + kunit_skip(test, "target alloc fail"); + } r = damon_new_region(0, 22); + if (!r) { + damon_destroy_ctx(c); + damon_free_target(t); + kunit_skip(test, "region alloc fail"); + } damon_add_region(r, t); damon_split_regions_of(t, 2, DAMON_MIN_REGION); KUNIT_EXPECT_LE(test, damon_nr_regions(t), 2u); damon_free_target(t); t = damon_new_target(); + if (!t) { + damon_destroy_ctx(c); + kunit_skip(test, "second target alloc fail"); + } r = damon_new_region(0, 220); + if (!r) { + damon_destroy_ctx(c); + damon_free_target(t); + kunit_skip(test, "second region alloc fail"); + } damon_add_region(r, t); damon_split_regions_of(t, 4, DAMON_MIN_REGION); KUNIT_EXPECT_LE(test, damon_nr_regions(t), 4u); From 2d48340ccc9d49b22f922e3a0562da32866e218b Mon Sep 17 00:00:00 2001 From: Charles Keepax Date: Tue, 6 Jan 2026 15:53:21 +0000 Subject: [PATCH 2070/3902] Revert "gpio: swnode: don't use the swnode's name as the key for GPIO lookup" This reverts commit e5d527be7e6984882306b49c067f1fec18920735. This software node change doesn't actually fix any current issues with the kernel, it is an improvement to the lookup process rather than fixing a live bug. It also causes a couple of regressions with shipping laptops, which relied on the label based lookup. There is a fix for the regressions in mainline, the first 5 patches of [1]. However, those patches are fairly substantial changes and given the patch causing the regression doesn't actually fix a bug it seems better to just revert it in stable. CC: stable@vger.kernel.org # 6.18 Link: https://lore.kernel.org/linux-sound/20251120-reset-gpios-swnodes-v7-0-a100493a0f4b@linaro.org/ [1] Closes: https://github.com/thesofproject/linux/issues/5599 Closes: https://github.com/thesofproject/linux/issues/5603 Acked-by: Bartosz Golaszewski Signed-off-by: Charles Keepax Signed-off-by: Greg Kroah-Hartman --- drivers/gpio/gpiolib-swnode.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/gpio/gpiolib-swnode.c b/drivers/gpio/gpiolib-swnode.c index e3806db1c0e077..f21dbc28cf2c8c 100644 --- a/drivers/gpio/gpiolib-swnode.c +++ b/drivers/gpio/gpiolib-swnode.c @@ -41,7 +41,7 @@ static struct gpio_device *swnode_get_gpio_device(struct fwnode_handle *fwnode) !strcmp(gdev_node->name, GPIOLIB_SWNODE_UNDEFINED_NAME)) return ERR_PTR(-ENOENT); - gdev = gpio_device_find_by_fwnode(fwnode); + gdev = gpio_device_find_by_label(gdev_node->name); return gdev ?: ERR_PTR(-EPROBE_DEFER); } From 3aa9aac0e8b767a7c6fac33ae626a332c2ba1389 Mon Sep 17 00:00:00 2001 From: Greg Kroah-Hartman Date: Thu, 8 Jan 2026 10:17:22 +0100 Subject: [PATCH 2071/3902] Linux 6.18.4 Link: https://lore.kernel.org/r/20260106170547.832845344@linuxfoundation.org Tested-by: Brett A C Sheffield Tested-by: Ronald Warsow Tested-by: Peter Schneider Tested-by: Shuah Khan Tested-by: Justin M. Forbes Tested-by: Florian Fainelli Tested-by: Christian Heusel Tested-by: Ron Economos Tested-by: Mark Brown Tested-by: Jeffrin Jose T Tested-by: Salvatore Bonaccorso Tested-by: Takeshi Ogasawara Tested-by: Miguel Ojeda Tested-by: Jon Hunter Tested-by: Hardik Garg Tested-by: Brett Mastbergen Signed-off-by: Greg Kroah-Hartman --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 17415fdcd358dc..7b431af09b3229 100644 --- a/Makefile +++ b/Makefile @@ -1,7 +1,7 @@ # SPDX-License-Identifier: GPL-2.0 VERSION = 6 PATCHLEVEL = 18 -SUBLEVEL = 3 +SUBLEVEL = 4 EXTRAVERSION = NAME = Baby Opossum Posse From 4398719b8c5fadb107fd2d4e44637d36a5d5ecc9 Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Tue, 6 Jan 2026 16:27:00 +0100 Subject: [PATCH 2072/3902] fixup! soc: apple: Add DockChannel driver use devm_of_platform_populate() instead of manually iterating over all nodes. Signed-off-by: Janne Grunau --- drivers/soc/apple/dockchannel.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/drivers/soc/apple/dockchannel.c b/drivers/soc/apple/dockchannel.c index 3a0d7964007c95..8f67c639da4d20 100644 --- a/drivers/soc/apple/dockchannel.c +++ b/drivers/soc/apple/dockchannel.c @@ -361,8 +361,7 @@ static int dockchannel_probe(struct platform_device *pdev) irq_set_handler_data(dcc->irq, dcc); irq_set_chained_handler(dcc->irq, dockchannel_irq); - for_each_child_of_node(dev->of_node, child) - of_platform_device_create(child, NULL, dev); + devm_of_platform_populate(dev); return 0; } From e7c31ab04e51d954044e86cc23003b61dbf96f17 Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Tue, 6 Jan 2026 20:04:47 +0100 Subject: [PATCH 2073/3902] drm/apple: Relax locking for back light updates Locking all modeset locks was the obviously correct solution and the overlocking wasn't much of an issue when only a single CRTC/display output was supported. Now with more output the over locking is becomming an issue and I even ran into a deadlock. Ideally the backlight related data either should live in a private object or in sub-classed CRTC state. In practice just locking the CRTC for the internal display with backlight should be good enough. Signed-off-by: Janne Grunau --- drivers/gpu/drm/apple/dcp_backlight.c | 22 ++++++++++++++++++---- 1 file changed, 18 insertions(+), 4 deletions(-) diff --git a/drivers/gpu/drm/apple/dcp_backlight.c b/drivers/gpu/drm/apple/dcp_backlight.c index 1397000c27935c..9eb0c7d4eb5345 100644 --- a/drivers/gpu/drm/apple/dcp_backlight.c +++ b/drivers/gpu/drm/apple/dcp_backlight.c @@ -144,7 +144,14 @@ static int drm_crtc_set_brightness(struct apple_dcp *dcp) struct drm_crtc *crtc = &dcp->crtc->base; int ret = 0; - DRM_MODESET_LOCK_ALL_BEGIN(crtc->dev, ctx, 0, ret); + drm_modeset_acquire_init(&ctx, DRM_MODESET_ACQUIRE_INTERRUPTIBLE); + ret = drm_modeset_lock(&crtc->mutex, &ctx); + if (ret == -EDEADLK) { + drm_modeset_backoff(&ctx); + return -EDEADLK; + } else if (ret == -ERESTARTSYS) { + return -ERESTARTSYS; + } if (!dcp->brightness.update) goto done; @@ -169,7 +176,7 @@ static int drm_crtc_set_brightness(struct apple_dcp *dcp) fail: drm_atomic_state_put(state); done: - DRM_MODESET_LOCK_ALL_END(crtc->dev, ctx, ret); + drm_modeset_drop_locks(&ctx); return ret; } @@ -199,12 +206,19 @@ static int dcp_set_brightness(struct backlight_device *bd) struct drm_modeset_acquire_ctx ctx; int brightness = backlight_get_brightness(bd); - DRM_MODESET_LOCK_ALL_BEGIN(dcp->crtc->base.dev, ctx, 0, ret); + drm_modeset_acquire_init(&ctx, DRM_MODESET_ACQUIRE_INTERRUPTIBLE); + ret = drm_modeset_lock(&dcp->crtc->base.mutex, &ctx); + if (ret == -EDEADLK) { + drm_modeset_backoff(&ctx); + return -EDEADLK; + } else if (ret == -ERESTARTSYS) { + return -ERESTARTSYS; + } dcp->brightness.dac = calculate_dac(dcp, brightness); dcp->brightness.update = true; - DRM_MODESET_LOCK_ALL_END(dcp->crtc->base.dev, ctx, ret); + drm_modeset_drop_locks(&ctx); return dcp_backlight_update(dcp); } From d95aeb17bbc89f88b7b2449bf89ecb04470fa533 Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Tue, 6 Jan 2026 22:23:53 +0100 Subject: [PATCH 2074/3902] drm/apple: Send HPD event on disconnect only connector is cconected Fixes a deadlock while disabling the CRTC from HPD event via drm_client. Signed-off-by: Janne Grunau --- drivers/gpu/drm/apple/dcp.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/gpu/drm/apple/dcp.c b/drivers/gpu/drm/apple/dcp.c index e7ff930d4bdc1c..c929c293ff60b0 100644 --- a/drivers/gpu/drm/apple/dcp.c +++ b/drivers/gpu/drm/apple/dcp.c @@ -426,7 +426,7 @@ static int dcp_dptx_connect(struct apple_dcp *dcp, u32 port) static void disconnected_hpd_event(struct apple_connector *con) { - if (con) { + if (con && con->connected) { con->connected = 0; drm_kms_helper_connector_hotplug_event(&con->base); } From 3bc56e01a784b1dfac4f4ac18ca10a6e4e83a4ff Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Tue, 6 Jan 2026 22:32:41 +0100 Subject: [PATCH 2075/3902] drm/apple: dcp: Do not call dcp_dptx_connect() from resume() It will be called from dcp_poweron() triggered by drm_mode_config_helper_resume() from the apple_drv's resume(). Signed-off-by: Janne Grunau --- drivers/gpu/drm/apple/dcp.c | 7 ------- 1 file changed, 7 deletions(-) diff --git a/drivers/gpu/drm/apple/dcp.c b/drivers/gpu/drm/apple/dcp.c index c929c293ff60b0..861aa0314ac19a 100644 --- a/drivers/gpu/drm/apple/dcp.c +++ b/drivers/gpu/drm/apple/dcp.c @@ -1302,13 +1302,6 @@ static int dcp_platform_resume(struct device *dev) if (dcp->hdmi_hpd_irq) enable_irq(dcp->hdmi_hpd_irq); - if (dcp->hdmi_hpd) { - bool connected = gpiod_get_value_cansleep(dcp->hdmi_hpd); - dev_info(dcp->dev, "resume: HPD connected:%d\n", connected); - if (connected) - dcp_dptx_connect(dcp, 0); - } - if (dcp->avep) av_service_connect(dcp); From 90f352b664eeb0174f36e9698fc3da50f16765ac Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Tue, 6 Jan 2026 22:47:35 +0100 Subject: [PATCH 2076/3902] drm/apple: Add device link between display-subsystem and each dcp* Fixes resume/suspend order between both devices. Signed-off-by: Janne Grunau --- drivers/gpu/drm/apple/apple_drv.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/drivers/gpu/drm/apple/apple_drv.c b/drivers/gpu/drm/apple/apple_drv.c index 9624d94611abae..4cfc357e0eb819 100644 --- a/drivers/gpu/drm/apple/apple_drv.c +++ b/drivers/gpu/drm/apple/apple_drv.c @@ -402,6 +402,8 @@ static int apple_drm_init_dcp(struct device *dev) if (!dcp[num_dcp]) continue; + device_link_add(dev, &dcp[num_dcp]->dev, DL_FLAG_AUTOREMOVE_SUPPLIER); + ret = apple_probe_per_dcp(dev, &apple->drm, dcp[num_dcp], num_dcp, dcp_ext); if (ret) From d26a4ec187a1d85c31048a7b800b84b19820f18d Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Sat, 3 Jan 2026 12:24:53 +0100 Subject: [PATCH 2077/3902] Revert "rust: drm: gpuvm: Add a missing lock" This reverts commit 0475333af1c8f67c53bd9151f418fe012c6cc421. due to NULL ptr deref, see https://github.com/AsahiLinux/linux/pull/433 Signed-off-by: Janne Grunau --- rust/kernel/drm/gpuvm.rs | 2 -- 1 file changed, 2 deletions(-) diff --git a/rust/kernel/drm/gpuvm.rs b/rust/kernel/drm/gpuvm.rs index 29b0aa42f361e4..e7b073cffe0a58 100644 --- a/rust/kernel/drm/gpuvm.rs +++ b/rust/kernel/drm/gpuvm.rs @@ -337,9 +337,7 @@ unsafe impl AlwaysRefCounted for GpuVmBo { unsafe { let resv = (*obj.as_mut().bo.obj).resv; bindings::dma_resv_lock(resv, core::ptr::null_mut()); - obj.as_ref().lock_gpuva(); bindings::drm_gpuvm_bo_put(&mut obj.as_mut().bo); - obj.as_ref().unlock_gpuva(); bindings::dma_resv_unlock(resv); } } From 481d2c486faa19958b9eff77f3313b03e01b450b Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Fri, 9 Jan 2026 20:49:51 +0100 Subject: [PATCH 2078/3902] Revert "usb: typec: tipd: Do not request duplicate role switches" This reverts commit 2ad915e90fe6363ab51622602be66d1b2b5659e7. Issue solved in dwc3-apple instead. Signed-off-by: Janne Grunau --- drivers/usb/typec/tipd/core.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/drivers/usb/typec/tipd/core.c b/drivers/usb/typec/tipd/core.c index 65575250443161..923477105483b1 100644 --- a/drivers/usb/typec/tipd/core.c +++ b/drivers/usb/typec/tipd/core.c @@ -841,8 +841,7 @@ static void cd321x_update_work(struct work_struct *work) cd321x_typec_update_mode(tps, &st); /* Launch the USB role switch */ - if ((new_role != old_role) || was_disconnected) - usb_role_switch_set_role(tps->role_sw, new_role); + usb_role_switch_set_role(tps->role_sw, new_role); power_supply_changed(tps->psy); } From ca76e6974be5552e57fe74ea5012b9f51931497d Mon Sep 17 00:00:00 2001 From: Sven Peter Date: Fri, 9 Jan 2026 17:43:34 +0100 Subject: [PATCH 2079/3902] usb: dwc3: apple: Set USB2 PHY mode before dwc3 init Now that the upstream code has been getting broader test coverage by our users we occasionally see issues with USB2 devices plugged in during boot. Before Linux is running, the USB2 PHY has usually been running in device mode and it turns out that sometimes host->device or device->host transitions don't work. The root cause: If the role inside the USB2 PHY is re-configured when it has already been powered on or when dwc3 has already enabled the ULPI interface the new configuration sometimes doesn't take affect until dwc3 is reset again. Fix this rare issue by configuring the role much earlier. Note that the USB3 PHY does not suffer from this issue and actually requires dwc3 to be up before the correct role can be configured there. Reported-by: James Calligeros Reported-by: Janne Grunau Fixes: 0ec946d32ef7 ("usb: dwc3: Add Apple Silicon DWC3 glue layer driver") Cc: stable@vger.kernel.org Tested-by: Janne Grunau Reviewed-by: Janne Grunau Acked-by: Thinh Nguyen Signed-off-by: Sven Peter --- drivers/usb/dwc3/dwc3-apple.c | 48 ++++++++++++++++++++++++----------- 1 file changed, 33 insertions(+), 15 deletions(-) diff --git a/drivers/usb/dwc3/dwc3-apple.c b/drivers/usb/dwc3/dwc3-apple.c index cc47cad232e397..c2ae8eb21d514e 100644 --- a/drivers/usb/dwc3/dwc3-apple.c +++ b/drivers/usb/dwc3/dwc3-apple.c @@ -218,25 +218,31 @@ static int dwc3_apple_core_init(struct dwc3_apple *appledwc) return ret; } -static void dwc3_apple_phy_set_mode(struct dwc3_apple *appledwc, enum phy_mode mode) -{ - lockdep_assert_held(&appledwc->lock); - - /* - * This platform requires SUSPHY to be enabled here already in order to properly configure - * the PHY and switch dwc3's PIPE interface to USB3 PHY. - */ - dwc3_enable_susphy(&appledwc->dwc, true); - phy_set_mode(appledwc->dwc.usb2_generic_phy[0], mode); - phy_set_mode(appledwc->dwc.usb3_generic_phy[0], mode); -} - static int dwc3_apple_init(struct dwc3_apple *appledwc, enum dwc3_apple_state state) { int ret, ret_reset; lockdep_assert_held(&appledwc->lock); + /* + * The USB2 PHY on this platform must be configured for host or device mode while it is + * still powered off and before dwc3 tries to access it. Otherwise, the new configuration + * will sometimes only take affect after the *next* time dwc3 is brought up which causes + * the connected device to just not work. + * The USB3 PHY must be configured later after dwc3 has already been initialized. + */ + switch (state) { + case DWC3_APPLE_HOST: + phy_set_mode(appledwc->dwc.usb2_generic_phy[0], PHY_MODE_USB_HOST); + break; + case DWC3_APPLE_DEVICE: + phy_set_mode(appledwc->dwc.usb2_generic_phy[0], PHY_MODE_USB_DEVICE); + break; + default: + /* Unreachable unless there's a bug in this driver */ + return -EINVAL; + } + ret = reset_control_deassert(appledwc->reset); if (ret) { dev_err(appledwc->dev, "Failed to deassert reset, err=%d\n", ret); @@ -257,7 +263,13 @@ static int dwc3_apple_init(struct dwc3_apple *appledwc, enum dwc3_apple_state st case DWC3_APPLE_HOST: appledwc->dwc.dr_mode = USB_DR_MODE_HOST; dwc3_apple_set_ptrcap(appledwc, DWC3_GCTL_PRTCAP_HOST); - dwc3_apple_phy_set_mode(appledwc, PHY_MODE_USB_HOST); + /* + * This platform requires SUSPHY to be enabled here already in order to properly + * configure the PHY and switch dwc3's PIPE interface to USB3 PHY. The USB2 PHY + * has already been configured to the correct mode earlier. + */ + dwc3_enable_susphy(&appledwc->dwc, true); + phy_set_mode(appledwc->dwc.usb3_generic_phy[0], PHY_MODE_USB_HOST); ret = dwc3_host_init(&appledwc->dwc); if (ret) { dev_err(appledwc->dev, "Failed to initialize host, ret=%d\n", ret); @@ -268,7 +280,13 @@ static int dwc3_apple_init(struct dwc3_apple *appledwc, enum dwc3_apple_state st case DWC3_APPLE_DEVICE: appledwc->dwc.dr_mode = USB_DR_MODE_PERIPHERAL; dwc3_apple_set_ptrcap(appledwc, DWC3_GCTL_PRTCAP_DEVICE); - dwc3_apple_phy_set_mode(appledwc, PHY_MODE_USB_DEVICE); + /* + * This platform requires SUSPHY to be enabled here already in order to properly + * configure the PHY and switch dwc3's PIPE interface to USB3 PHY. The USB2 PHY + * has already been configured to the correct mode earlier. + */ + dwc3_enable_susphy(&appledwc->dwc, true); + phy_set_mode(appledwc->dwc.usb3_generic_phy[0], PHY_MODE_USB_DEVICE); ret = dwc3_gadget_init(&appledwc->dwc); if (ret) { dev_err(appledwc->dev, "Failed to initialize gadget, ret=%d\n", ret); From eefe4aa7dbaa65f5258fc11a5ddb1fd884f4c09b Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Fri, 9 Jan 2026 11:13:48 +0100 Subject: [PATCH 2080/3902] usb: dwc3: apple: Ignore USB role switches to the active role Ignore USB role switches if dwc3-apple is already in the desired state. The USB-C port controller on M2 and M1/M2 Pro/Max/Ultra devices issues additional interrupts which result in USB role switches to the already active role. Ignore these USB role switches to ensure the USB-C port controller and dwc3-apple are always in a consistent state. This matches the behaviour in __dwc3_set_mode() in core.c. Fixes detecting USB 2.0 and 3.x devices on the affected systems. The reset caused by the additional role switch appears to leave the USB devices in a state which prevents detection when the phy and dwc3 is brought back up again. Fixes: 0ec946d32ef7 ("usb: dwc3: Add Apple Silicon DWC3 glue layer driver") Cc: stable@vger.kernel.org Signed-off-by: Janne Grunau --- drivers/usb/dwc3/dwc3-apple.c | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/drivers/usb/dwc3/dwc3-apple.c b/drivers/usb/dwc3/dwc3-apple.c index c2ae8eb21d514e..40c3ccfddb6702 100644 --- a/drivers/usb/dwc3/dwc3-apple.c +++ b/drivers/usb/dwc3/dwc3-apple.c @@ -357,6 +357,22 @@ static int dwc3_usb_role_switch_set(struct usb_role_switch *sw, enum usb_role ro guard(mutex)(&appledwc->lock); + /* + * Skip role switches if appledwc is already in the desired state. The + * USB-C port controller on M2 and M1/M2 Pro/Max/Ultra devices issues + * additional interrupts which results in usb_role_switch_set_role() + * calls with the current role. + * Ignore those calls here to ensure the USB-C port controller and + * appledwc are in a consistent state. + * This matches the behaviour in __dwc3_set_mode(). + * Do no handle USB_ROLE_NONE for DWC3_APPLE_NO_CABLE and + * DWC3_APPLE_PROBE_PENDING since that is no-op anyway. + */ + if (appledwc->state == DWC3_APPLE_HOST && role == USB_ROLE_HOST) + return 0; + if (appledwc->state == DWC3_APPLE_DEVICE && role == USB_ROLE_DEVICE) + return 0; + /* * We need to tear all of dwc3 down and re-initialize it every time a cable is * connected or disconnected or when the mode changes. See the documentation for enum From d4377ca7d3d421727fc4ea12349491d6e68fec9f Mon Sep 17 00:00:00 2001 From: Sven Peter Date: Sun, 4 Jan 2026 22:56:39 +0100 Subject: [PATCH 2081/3902] phy: apple: atc: Actually check return value of devm_apple_tunable_parse Let's actually check the return value of devm_apple_tunable_parse instead of trying to check IS_ERR on a pointer to the return value which is always going to be valid. This prevent a oops when the tunables are invalid or when they don't exist: [ 57.664567] Unable to handle kernel paging request at virtual address fffffffffffffffe [ 57.664584] Mem abort info: [ 57.664589] ESR = 0x0000000096000007 [ 57.664595] EC = 0x25: DABT (current EL), IL = 32 bits [ 57.664602] SET = 0, FnV = 0 [ 57.664607] EA = 0, S1PTW = 0 [ 57.664611] FSC = 0x07: level 3 translation fault [ 57.664617] Data abort info: [ 57.664621] ISV = 0, ISS = 0x00000007, ISS2 = 0x00000000 [ 57.664626] CM = 0, WnR = 0, TnD = 0, TagAccess = 0 [ 57.664631] GCS = 0, Overlay = 0, DirtyBit = 0, Xs = 0 [ 57.664640] swapper pgtable: 16k pages, 47-bit VAs, pgdp=0000000b4391c000 [ 57.664647] [fffffffffffffffe] pgd=0000000000000000, p4d=0000000000000000, pud=0000000b44188403, pmd=0000000b4418c403, pte=0000000000000000 [ 57.664670] Internal error: Oops: 0000000096000007 [#1] SMP [ 57.665047] CPU: 1 UID: 0 PID: 23 Comm: kworker/1:0 Tainted: G S 6.18.2+ #2 PREEMPTLAZY [ 57.665061] Tainted: [S]=CPU_OUT_OF_SPEC [ 57.665066] Hardware name: Apple Mac mini (M1, 2020) (DT) [ 57.665072] Workqueue: events cd321x_update_work [tps6598x] [ 57.665100] pstate: 61400009 (nZCv daif +PAN -UAO -TCO +DIT -SSBS BTYPE=--) [ 57.665111] pc : apple_tunable_apply+0x8/0x80 [apple_tunable] [ 57.665121] lr : atcphy_mux_set+0x3e0/0x1138 [phy_apple_atc] [ 57.665133] sp : ffffc000802a7c00 [ 57.665138] x29: ffffc000802a7c00 x28: 0000000000000003 x27: ffff800016c84080 [ 57.665151] x26: 0000000000000002 x25: ffff800016c84090 x24: ffff800016c8408f [ 57.665163] x23: 0000000000020004 x22: 0000000000000001 x21: 0000000000000006 [ 57.665175] x20: ffff80000d6da9b0 x19: ffff80000d6da880 x18: 0000000000000002 [ 57.665188] x17: 0000000000000000 x16: ffffe22de59e0e38 x15: 0000000000000002 [ 57.665199] x14: ffffe22de76ecff8 x13: 0000000000000001 x12: ffff9dd5f90bc000 [ 57.665211] x11: 00000000000000c0 x10: 048abc15ceba0919 x9 : ffffe22dbc5fde10 [ 57.665223] x8 : ffff80000175e0d8 x7 : 0000000000000004 x6 : 0000000000000000 [ 57.665234] x5 : 0000000000000001 x4 : 0000000d6d132db7 x3 : 00000000000155db [ 57.665246] x2 : 0000000000000000 x1 : fffffffffffffffe x0 : ffffc00082b80000 [ 57.665258] Call trace: [ 57.665265] apple_tunable_apply+0x8/0x80 [apple_tunable] (P) [ 57.665276] typec_mux_set+0x74/0xe0 [typec] [ 57.665315] cd321x_update_work+0x440/0x8c0 [tps6598x] [ 57.665332] process_one_work+0x178/0x3d0 [ 57.665346] worker_thread+0x260/0x390 [ 57.665354] kthread+0x150/0x250 [ 57.665369] ret_from_fork+0x10/0x20 [ 57.665386] Code: e69a0ae8 ffffe22d aa1e03e9 d503201f (f9400022) [ 57.665394] ---[ end trace 0000000000000000 ]--- Reported-by: Thomas Glanzmann Fixes: 8e98ca1e74db ("phy: apple: Add Apple Type-C PHY") Signed-off-by: Sven Peter Reviewed-by: Neil Armstrong --- drivers/phy/apple/atc.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/drivers/phy/apple/atc.c b/drivers/phy/apple/atc.c index c8a58ee64b7aad..716c1e70de38ca 100644 --- a/drivers/phy/apple/atc.c +++ b/drivers/phy/apple/atc.c @@ -2178,10 +2178,10 @@ static int atcphy_load_tunables(struct apple_atcphy *atcphy) for (int i = 0; i < ARRAY_SIZE(tunables); i++) { *tunables[i].tunable = devm_apple_tunable_parse( atcphy->dev, atcphy->np, tunables[i].dt_name, tunables[i].res); - if (IS_ERR(tunables[i].tunable)) { + if (IS_ERR(*tunables[i].tunable)) { dev_err(atcphy->dev, "Failed to read tunable %s: %ld\n", - tunables[i].dt_name, PTR_ERR(tunables[i].tunable)); - return PTR_ERR(tunables[i].tunable); + tunables[i].dt_name, PTR_ERR(*tunables[i].tunable)); + return PTR_ERR(*tunables[i].tunable); } } From 36eef98abcb315ca7a6d03ace2d4d33027e488a1 Mon Sep 17 00:00:00 2001 From: Sven Peter Date: Thu, 8 Jan 2026 20:12:06 +0100 Subject: [PATCH 2082/3902] phy: apple: atc: Reset USB2 PHY during probe as well Now that the upstream Type-C PHY code is getting broader test coverage we got reports of USB devices plugged in during boot or those plugged in for the first time after boot occasionally not working correctly. This is partially caused by the USB2 parts of the PHY being left in an unknown state by the previous boot stages. We reset all other parts during probe but forgot about the USB2 PHY so let's fix that and actually reset and power off the USB2 PHY as well. Reported-by: James Calligeros Reported-by: Janne Grunau Fixes: 8e98ca1e74db ("phy: apple: Add Apple Type-C PHY") Signed-off-by: Sven Peter Reviewed-by: Janne Grunau Tested-by: Janne Grunau --- drivers/phy/apple/atc.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/phy/apple/atc.c b/drivers/phy/apple/atc.c index 716c1e70de38ca..dc867f368b6874 100644 --- a/drivers/phy/apple/atc.c +++ b/drivers/phy/apple/atc.c @@ -2227,6 +2227,7 @@ static int atcphy_probe_finalize(struct apple_atcphy *atcphy) _atcphy_dwc3_reset_assert(atcphy); /* Reset atcphy to clear any state potentially left by the bootloader */ + atcphy_usb2_power_off(atcphy); atcphy_power_off(atcphy); atcphy_setup_pipehandler(atcphy); From d654fb30dc9b43b4314cd0e6702162cc08918bf3 Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Thu, 8 Jan 2026 22:04:01 +0100 Subject: [PATCH 2083/3902] arm64: dts: apple: t8112-j473: Keep the HDMI port powered on Add the display controller and DPTX phy power-domains to the framebuffer node to keep the framebuffer and display out working after device probing finished. The OS has more control about the display pipeline used for the HDMI output on M2 based devices. The HDMI output is driven by an integrated DisplayPort to HDMI converter (Parade PS190). The DPTX phy is now controlled by the OS and no longer by firmware running on the display co-processor. This allows using the second display controller on the second USB type-c port or tunneling 2 DisplayPort connections over USB4/Thunderbolt. The m1n1 bootloader uses the second display controller to drive the HDMI output. Adjust for this difference compared to the notebooks as well. Fixes: 2d5ce3fbef32 ("arm64: dts: apple: t8112: Initial t8112 (M2) device trees") Cc: stable@vger.kernel.org Signed-off-by: Janne Grunau --- arch/arm64/boot/dts/apple/t8112-j473.dts | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/arch/arm64/boot/dts/apple/t8112-j473.dts b/arch/arm64/boot/dts/apple/t8112-j473.dts index 06fe257f08be49..4ae1ce919dafc4 100644 --- a/arch/arm64/boot/dts/apple/t8112-j473.dts +++ b/arch/arm64/boot/dts/apple/t8112-j473.dts @@ -21,6 +21,25 @@ }; }; +/* + * Keep the power-domains used for the HDMI port on. + */ +&framebuffer0 { + power-domains = <&ps_dispext_cpu0>, <&ps_dptx_ext_phy>; +}; + +/* + * The M2 Mac mini uses dispext for the HDMI output so it's not necessary to + * keep disp0 power-domains always-on. + */ +&ps_disp0_sys { + /delete-property/ apple,always-on; +}; + +&ps_disp0_fe { + /delete-property/ apple,always-on; +}; + /* * Force the bus number assignments so that we can declare some of the * on-board devices and properties that are populated by the bootloader From 0b6848deba81ad6cbed79d5fc7da17f88ff370af Mon Sep 17 00:00:00 2001 From: Hector Martin Date: Thu, 8 Jan 2026 22:04:02 +0100 Subject: [PATCH 2084/3902] arm64: dts: apple: t8103: Mark ATC USB AON domains as always-on Shutting these down breaks dwc3 init done by the firmware. We probably never want to do this anyway. It might be possible remove this once a PHY driver is in place to do the init properly, but it may not be worth it. "always-on" is a plausible interpretation of the "aon" suffix. The t8112, t600x and t602x "ps_atc?_usb_aon" power-controller nodes are have already "apple,always-on" properties. Signed-off-by: Hector Martin Signed-off-by: Janne Grunau --- arch/arm64/boot/dts/apple/t8103-pmgr.dtsi | 2 ++ 1 file changed, 2 insertions(+) diff --git a/arch/arm64/boot/dts/apple/t8103-pmgr.dtsi b/arch/arm64/boot/dts/apple/t8103-pmgr.dtsi index c41c57d63997a5..4bfe0d2de30ad6 100644 --- a/arch/arm64/boot/dts/apple/t8103-pmgr.dtsi +++ b/arch/arm64/boot/dts/apple/t8103-pmgr.dtsi @@ -1103,6 +1103,7 @@ #power-domain-cells = <0>; #reset-cells = <0>; label = "atc0_usb_aon"; + apple,always-on; /* Needs to stay on for dwc3 to work */ }; ps_atc1_usb_aon: power-controller@90 { @@ -1111,6 +1112,7 @@ #power-domain-cells = <0>; #reset-cells = <0>; label = "atc1_usb_aon"; + apple,always-on; /* Needs to stay on for dwc3 to work */ }; ps_atc0_usb: power-controller@98 { From 9586ca2a9cf682992a581c86a647124efb6400bb Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Thu, 8 Jan 2026 22:04:03 +0100 Subject: [PATCH 2085/3902] arm64: dts: apple: t8103: Add ps_pmp dependency to ps_gfx AGX appears to have a hidden communication channel to pmp, a power management related co-processor already brought up by Apple's bootloader. As there is not driver for this co-processor its power-domain gets shut down after the initial boot. This crashes the firmware running on AGX immediately. Until there is a pmp driver and the dependency between AGX and pmp is understood keep "ps_pmp" as dependency of "ps_gfx". Signed-off-by: Janne Grunau --- arch/arm64/boot/dts/apple/t8103-pmgr.dtsi | 1 + 1 file changed, 1 insertion(+) diff --git a/arch/arm64/boot/dts/apple/t8103-pmgr.dtsi b/arch/arm64/boot/dts/apple/t8103-pmgr.dtsi index 4bfe0d2de30ad6..fef8a4058f1415 100644 --- a/arch/arm64/boot/dts/apple/t8103-pmgr.dtsi +++ b/arch/arm64/boot/dts/apple/t8103-pmgr.dtsi @@ -733,6 +733,7 @@ #power-domain-cells = <0>; #reset-cells = <0>; label = "gfx"; + power-domains = <&ps_pmp>; }; ps_dcs4: power-controller@320 { From 76edb92a29a7eb3870a9acdbec6c1eabf52e1c9f Mon Sep 17 00:00:00 2001 From: Hector Martin Date: Fri, 9 Jan 2026 15:07:04 +0100 Subject: [PATCH 2086/3902] arm64: dts: apple: t8103: Add nodes for integrated USB Type-C ports Add device nodes and connections to support USB 3.x on the SoC's integrated USB-C ports of M1-based devices. Each Type-C port has an Apple Type-C PHY for USB 2.0, USB 3.x, USB4/Thunderbolt, and DisplayPort, a Synopsys Designware USB 3.x controller, two DART iommu instances and a CD321x USB PD controller. The iMac variant with four USB-C ports has two SoC integrated USB-C ports and two additional USB-C ports driven by an AsMedia PCIe USB controller. The latter ports are not covered by this change. The port labels use Apple's established naming scheme for the ports. Signed-off-by: Hector Martin Co-developed-by: Sven Peter Signed-off-by: Sven Peter Reviewed-by: Neal Gompa Signed-off-by: Janne Grunau --- arch/arm64/boot/dts/apple/t8103-j274.dts | 12 ++ arch/arm64/boot/dts/apple/t8103-j293.dts | 12 ++ arch/arm64/boot/dts/apple/t8103-j313.dts | 12 ++ arch/arm64/boot/dts/apple/t8103-j456.dts | 12 ++ arch/arm64/boot/dts/apple/t8103-j457.dts | 12 ++ arch/arm64/boot/dts/apple/t8103-jxxx.dtsi | 134 ++++++++++++++++++++++ arch/arm64/boot/dts/apple/t8103.dtsi | 105 +++++++++++++++++ 7 files changed, 299 insertions(+) diff --git a/arch/arm64/boot/dts/apple/t8103-j274.dts b/arch/arm64/boot/dts/apple/t8103-j274.dts index 1c3e37f86d46d7..968fe22163d443 100644 --- a/arch/arm64/boot/dts/apple/t8103-j274.dts +++ b/arch/arm64/boot/dts/apple/t8103-j274.dts @@ -29,6 +29,18 @@ brcm,board-type = "apple,atlantisb"; }; +/* + * Provide labels for the USB type C ports. + */ + +&typec0 { + label = "USB-C Back-left"; +}; + +&typec1 { + label = "USB-C Back-right"; +}; + /* * Force the bus number assignments so that we can declare some of the * on-board devices and properties that are populated by the bootloader diff --git a/arch/arm64/boot/dts/apple/t8103-j293.dts b/arch/arm64/boot/dts/apple/t8103-j293.dts index 5b3c42e9f0e677..678f89c3d47fbf 100644 --- a/arch/arm64/boot/dts/apple/t8103-j293.dts +++ b/arch/arm64/boot/dts/apple/t8103-j293.dts @@ -46,6 +46,18 @@ brcm,board-type = "apple,honshu"; }; +/* + * Provide labels for the USB type C ports. + */ + +&typec0 { + label = "USB-C Left-back"; +}; + +&typec1 { + label = "USB-C Left-front"; +}; + &i2c2 { status = "okay"; }; diff --git a/arch/arm64/boot/dts/apple/t8103-j313.dts b/arch/arm64/boot/dts/apple/t8103-j313.dts index 97a4344d8dca68..bce9b911009e2b 100644 --- a/arch/arm64/boot/dts/apple/t8103-j313.dts +++ b/arch/arm64/boot/dts/apple/t8103-j313.dts @@ -41,3 +41,15 @@ &fpwm1 { status = "okay"; }; + +/* + * Provide labels for the USB type C ports. + */ + +&typec0 { + label = "USB-C Left-back"; +}; + +&typec1 { + label = "USB-C Left-front"; +}; diff --git a/arch/arm64/boot/dts/apple/t8103-j456.dts b/arch/arm64/boot/dts/apple/t8103-j456.dts index 58c8e43789b486..9983e11cacdf19 100644 --- a/arch/arm64/boot/dts/apple/t8103-j456.dts +++ b/arch/arm64/boot/dts/apple/t8103-j456.dts @@ -47,6 +47,18 @@ }; }; +/* + * Provide labels for the USB type C ports. + */ + +&typec0 { + label = "USB-C Back-right"; +}; + +&typec1 { + label = "USB-C Back-right-middle"; +}; + /* * Force the bus number assignments so that we can declare some of the * on-board devices and properties that are populated by the bootloader diff --git a/arch/arm64/boot/dts/apple/t8103-j457.dts b/arch/arm64/boot/dts/apple/t8103-j457.dts index 7089ccf3ce5566..6703f4a3d3db4c 100644 --- a/arch/arm64/boot/dts/apple/t8103-j457.dts +++ b/arch/arm64/boot/dts/apple/t8103-j457.dts @@ -37,6 +37,18 @@ brcm,board-type = "apple,santorini"; }; +/* + * Provide labels for the USB type C ports. + */ + +&typec0 { + label = "USB-C Back-right"; +}; + +&typec1 { + label = "USB-C Back-left"; +}; + /* * Force the bus number assignments so that we can declare some of the * on-board devices and properties that are populated by the bootloader diff --git a/arch/arm64/boot/dts/apple/t8103-jxxx.dtsi b/arch/arm64/boot/dts/apple/t8103-jxxx.dtsi index 0c8206156bfefd..686fb1dd215d2d 100644 --- a/arch/arm64/boot/dts/apple/t8103-jxxx.dtsi +++ b/arch/arm64/boot/dts/apple/t8103-jxxx.dtsi @@ -15,6 +15,8 @@ serial0 = &serial0; serial2 = &serial2; wifi0 = &wifi0; + atcphy0 = &atcphy0; + atcphy1 = &atcphy1; }; chosen { @@ -53,6 +55,29 @@ interrupt-parent = <&pinctrl_ap>; interrupts = <106 IRQ_TYPE_LEVEL_LOW>; interrupt-names = "irq"; + + typec0: connector { + compatible = "usb-c-connector"; + power-role = "dual"; + data-role = "dual"; + + ports { + #address-cells = <1>; + #size-cells = <0>; + port@0 { + reg = <0>; + typec0_connector_hs: endpoint { + remote-endpoint = <&dwc3_0_hs>; + }; + }; + port@1 { + reg = <1>; + typec0_connector_ss: endpoint { + remote-endpoint = <&atcphy0_typec_lanes>; + }; + }; + }; + }; }; hpm1: usb-pd@3f { @@ -61,6 +86,115 @@ interrupt-parent = <&pinctrl_ap>; interrupts = <106 IRQ_TYPE_LEVEL_LOW>; interrupt-names = "irq"; + + typec1: connector { + compatible = "usb-c-connector"; + power-role = "dual"; + data-role = "dual"; + + ports { + #address-cells = <1>; + #size-cells = <0>; + port@0 { + reg = <0>; + typec1_connector_hs: endpoint { + remote-endpoint = <&dwc3_1_hs>; + }; + }; + port@1 { + reg = <1>; + typec1_connector_ss: endpoint { + remote-endpoint = <&atcphy1_typec_lanes>; + }; + }; + }; + }; + }; +}; + +/* USB controllers */ +&dwc3_0 { + ports { + #address-cells = <1>; + #size-cells = <0>; + + port@0 { + reg = <0>; + dwc3_0_hs: endpoint { + remote-endpoint = <&typec0_connector_hs>; + }; + }; + + port@1 { + reg = <1>; + dwc3_0_ss: endpoint { + remote-endpoint = <&atcphy0_usb3>; + }; + }; + }; +}; + +&dwc3_1 { + ports { + #address-cells = <1>; + #size-cells = <0>; + + port@0 { + reg = <0>; + dwc3_1_hs: endpoint { + remote-endpoint = <&typec1_connector_hs>; + }; + }; + + port@1 { + reg = <1>; + dwc3_1_ss: endpoint { + remote-endpoint = <&atcphy1_usb3>; + }; + }; + }; +}; + +/* Type-C PHYs */ +&atcphy0 { + ports { + #address-cells = <1>; + #size-cells = <0>; + + port@0 { + reg = <0>; + atcphy0_typec_lanes: endpoint { + remote-endpoint = <&typec0_connector_ss>; + }; + }; + + port@1 { + reg = <1>; + atcphy0_usb3: endpoint { + remote-endpoint = <&dwc3_0_ss>; + }; + }; + }; +}; + +&atcphy1 { + ports { + #address-cells = <1>; + #size-cells = <0>; + + port@0 { + reg = <0>; + atcphy1_typec_lanes: endpoint { + remote-endpoint = <&typec1_connector_ss>; + }; + }; + + port@1 { + reg = <1>; + atcphy1_usb3: endpoint { + remote-endpoint = <&dwc3_1_ss>; + }; + }; }; }; diff --git a/arch/arm64/boot/dts/apple/t8103.dtsi b/arch/arm64/boot/dts/apple/t8103.dtsi index 8b7b2788796874..c48b10ac65efcc 100644 --- a/arch/arm64/boot/dts/apple/t8103.dtsi +++ b/arch/arm64/boot/dts/apple/t8103.dtsi @@ -12,6 +12,7 @@ #include #include #include +#include / { compatible = "apple,t8103", "apple,arm-platform"; @@ -1007,6 +1008,110 @@ resets = <&ps_ans2>; }; + dwc3_0: usb@382280000 { + compatible = "apple,t8103-dwc3"; + reg = <0x3 0x82280000 0x0 0xcd00>, <0x3 0x8228cd00 0x0 0x3200>; + reg-names = "dwc3-core", "dwc3-apple"; + interrupt-parent = <&aic>; + interrupts = ; + dr_mode = "otg"; + usb-role-switch; + role-switch-default-mode = "host"; + iommus = <&dwc3_0_dart_0 0>, <&dwc3_0_dart_1 1>; + power-domains = <&ps_atc0_usb>; + resets = <&atcphy0>; + phys = <&atcphy0 PHY_TYPE_USB2>, <&atcphy0 PHY_TYPE_USB3>; + phy-names = "usb2-phy", "usb3-phy"; + }; + + dwc3_0_dart_0: iommu@382f00000 { + compatible = "apple,t8103-dart"; + reg = <0x3 0x82f00000 0x0 0x4000>; + interrupt-parent = <&aic>; + interrupts = ; + #iommu-cells = <1>; + power-domains = <&ps_atc0_usb>; + }; + + dwc3_0_dart_1: iommu@382f80000 { + compatible = "apple,t8103-dart"; + reg = <0x3 0x82f80000 0x0 0x4000>; + interrupt-parent = <&aic>; + interrupts = ; + #iommu-cells = <1>; + power-domains = <&ps_atc0_usb>; + }; + + atcphy0: phy@383000000 { + compatible = "apple,t8103-atcphy"; + reg = <0x3 0x83000000 0x0 0x4c000>, + <0x3 0x83050000 0x0 0x8000>, + <0x3 0x80000000 0x0 0x4000>, + <0x3 0x82a90000 0x0 0x4000>, + <0x3 0x82a84000 0x0 0x4000>; + reg-names = "core", "lpdptx", "axi2af", "usb2phy", + "pipehandler"; + + #phy-cells = <1>; + #reset-cells = <0>; + + orientation-switch; + mode-switch; + power-domains = <&ps_atc0_usb>; + }; + + dwc3_1: usb@502280000 { + compatible = "apple,t8103-dwc3"; + reg = <0x5 0x02280000 0x0 0xcd00>, <0x5 0x0228cd00 0x0 0x3200>; + reg-names = "dwc3-core", "dwc3-apple"; + interrupt-parent = <&aic>; + interrupts = ; + dr_mode = "otg"; + usb-role-switch; + role-switch-default-mode = "host"; + iommus = <&dwc3_1_dart_0 0>, <&dwc3_1_dart_1 1>; + power-domains = <&ps_atc1_usb>; + resets = <&atcphy1>; + phys = <&atcphy1 PHY_TYPE_USB2>, <&atcphy1 PHY_TYPE_USB3>; + phy-names = "usb2-phy", "usb3-phy"; + }; + + dwc3_1_dart_0: iommu@502f00000 { + compatible = "apple,t8103-dart"; + reg = <0x5 0x02f00000 0x0 0x4000>; + interrupt-parent = <&aic>; + interrupts = ; + #iommu-cells = <1>; + power-domains = <&ps_atc1_usb>; + }; + + dwc3_1_dart_1: iommu@502f80000 { + compatible = "apple,t8103-dart"; + reg = <0x5 0x02f80000 0x0 0x4000>; + interrupt-parent = <&aic>; + interrupts = ; + #iommu-cells = <1>; + power-domains = <&ps_atc1_usb>; + }; + + atcphy1: phy@503000000 { + compatible = "apple,t8103-atcphy"; + reg = <0x5 0x03000000 0x0 0x4c000>, + <0x5 0x03050000 0x0 0x8000>, + <0x5 0x0 0x0 0x4000>, + <0x5 0x02a90000 0x0 0x4000>, + <0x5 0x02a84000 0x0 0x4000>; + reg-names = "core", "lpdptx", "axi2af", "usb2phy", + "pipehandler"; + + #phy-cells = <1>; + #reset-cells = <0>; + + orientation-switch; + mode-switch; + power-domains = <&ps_atc1_usb>; + }; + pcie0_dart_0: iommu@681008000 { compatible = "apple,t8103-dart"; reg = <0x6 0x81008000 0x0 0x4000>; From 3815074c9b42a5601f700c3636fa6973e576ad42 Mon Sep 17 00:00:00 2001 From: Hector Martin Date: Fri, 9 Jan 2026 15:07:05 +0100 Subject: [PATCH 2087/3902] arm64: dts: apple: t8112: Add nodes for integrated USB Type-C ports Add device nodes and connections to support USB 3.x on the SoC's integrated USBi Type-C ports of M2-based devices. Each Type-C port has an Apple Type-C PHY for USB 2.0, USB 3.x, USB4/Thunderbolt, and DisplayPort, a Synopsys Designware USB 3.x controller, two DART iommu instances and a CD321x USB PD controller. The port labels use Apple's established naming scheme for the ports. Signed-off-by: Hector Martin Co-developed-by: Janne Grunau Reviewed-by: Neal Gompa Signed-off-by: Janne Grunau --- arch/arm64/boot/dts/apple/t8112-j413.dts | 12 ++ arch/arm64/boot/dts/apple/t8112-j415.dts | 12 ++ arch/arm64/boot/dts/apple/t8112-j473.dts | 12 ++ arch/arm64/boot/dts/apple/t8112-j493.dts | 12 ++ arch/arm64/boot/dts/apple/t8112-jxxx.dtsi | 134 ++++++++++++++++++++++ arch/arm64/boot/dts/apple/t8112.dtsi | 105 +++++++++++++++++ 6 files changed, 287 insertions(+) diff --git a/arch/arm64/boot/dts/apple/t8112-j413.dts b/arch/arm64/boot/dts/apple/t8112-j413.dts index 6f69658623bf89..21e81a8899d8d7 100644 --- a/arch/arm64/boot/dts/apple/t8112-j413.dts +++ b/arch/arm64/boot/dts/apple/t8112-j413.dts @@ -60,6 +60,18 @@ }; }; +/* + * Provide labels for the USB type C ports. + */ + +&typec0 { + label = "USB-C Left-back"; +}; + +&typec1 { + label = "USB-C Left-front"; +}; + &i2c0 { /* MagSafe port */ hpm5: usb-pd@3a { diff --git a/arch/arm64/boot/dts/apple/t8112-j415.dts b/arch/arm64/boot/dts/apple/t8112-j415.dts index b54e218e5384ca..b4f72fbafaf104 100644 --- a/arch/arm64/boot/dts/apple/t8112-j415.dts +++ b/arch/arm64/boot/dts/apple/t8112-j415.dts @@ -60,6 +60,18 @@ }; }; +/* + * Provide labels for the USB type C ports. + */ + +&typec0 { + label = "USB-C Left-back"; +}; + +&typec1 { + label = "USB-C Left-front"; +}; + &i2c0 { /* MagSafe port */ hpm5: usb-pd@3a { diff --git a/arch/arm64/boot/dts/apple/t8112-j473.dts b/arch/arm64/boot/dts/apple/t8112-j473.dts index 4ae1ce919dafc4..0ab7009d2cf7a2 100644 --- a/arch/arm64/boot/dts/apple/t8112-j473.dts +++ b/arch/arm64/boot/dts/apple/t8112-j473.dts @@ -71,3 +71,15 @@ &pcie2_dart { status = "okay"; }; + +/* + * Provide labels for the USB type C ports. + */ + +&typec0 { + label = "USB-C Back-left"; +}; + +&typec1 { + label = "USB-C Back-right"; +}; diff --git a/arch/arm64/boot/dts/apple/t8112-j493.dts b/arch/arm64/boot/dts/apple/t8112-j493.dts index fb8ad7d4c65a8f..f8e442152ff23f 100644 --- a/arch/arm64/boot/dts/apple/t8112-j493.dts +++ b/arch/arm64/boot/dts/apple/t8112-j493.dts @@ -108,6 +108,18 @@ }; }; +/* + * Provide labels for the USB type C ports. + */ + +&typec0 { + label = "USB-C Left-back"; +}; + +&typec1 { + label = "USB-C Left-front"; +}; + &i2c4 { status = "okay"; }; diff --git a/arch/arm64/boot/dts/apple/t8112-jxxx.dtsi b/arch/arm64/boot/dts/apple/t8112-jxxx.dtsi index 6da35496a4c88d..562e7a25a1e884 100644 --- a/arch/arm64/boot/dts/apple/t8112-jxxx.dtsi +++ b/arch/arm64/boot/dts/apple/t8112-jxxx.dtsi @@ -11,6 +11,8 @@ / { aliases { + atcphy0 = &atcphy0; + atcphy1 = &atcphy1; serial0 = &serial0; serial2 = &serial2; }; @@ -53,6 +55,29 @@ interrupt-parent = <&pinctrl_ap>; interrupts = <8 IRQ_TYPE_LEVEL_LOW>; interrupt-names = "irq"; + + typec0: connector { + compatible = "usb-c-connector"; + power-role = "dual"; + data-role = "dual"; + + ports { + #address-cells = <1>; + #size-cells = <0>; + port@0 { + reg = <0>; + typec0_connector_hs: endpoint { + remote-endpoint = <&dwc3_0_hs>; + }; + }; + port@1 { + reg = <1>; + typec0_connector_ss: endpoint { + remote-endpoint = <&atcphy0_typec_lanes>; + }; + }; + }; + }; }; hpm1: usb-pd@3f { @@ -61,6 +86,115 @@ interrupt-parent = <&pinctrl_ap>; interrupts = <8 IRQ_TYPE_LEVEL_LOW>; interrupt-names = "irq"; + + typec1: connector { + compatible = "usb-c-connector"; + power-role = "dual"; + data-role = "dual"; + + ports { + #address-cells = <1>; + #size-cells = <0>; + port@0 { + reg = <0>; + typec1_connector_hs: endpoint { + remote-endpoint = <&dwc3_1_hs>; + }; + }; + port@1 { + reg = <1>; + typec1_connector_ss: endpoint { + remote-endpoint = <&atcphy1_typec_lanes>; + }; + }; + }; + }; + }; +}; + +/* USB controllers */ +&dwc3_0 { + ports { + #address-cells = <1>; + #size-cells = <0>; + + port@0 { + reg = <0>; + dwc3_0_hs: endpoint { + remote-endpoint = <&typec0_connector_hs>; + }; + }; + + port@1 { + reg = <1>; + dwc3_0_ss: endpoint { + remote-endpoint = <&atcphy0_usb3>; + }; + }; + }; +}; + +&dwc3_1 { + ports { + #address-cells = <1>; + #size-cells = <0>; + + port@0 { + reg = <0>; + dwc3_1_hs: endpoint { + remote-endpoint = <&typec1_connector_hs>; + }; + }; + + port@1 { + reg = <1>; + dwc3_1_ss: endpoint { + remote-endpoint = <&atcphy1_usb3>; + }; + }; + }; +}; + +/* Type-C PHYs */ +&atcphy0 { + ports { + #address-cells = <1>; + #size-cells = <0>; + + port@0 { + reg = <0>; + atcphy0_typec_lanes: endpoint { + remote-endpoint = <&typec0_connector_ss>; + }; + }; + + port@1 { + reg = <1>; + atcphy0_usb3: endpoint { + remote-endpoint = <&dwc3_0_ss>; + }; + }; + }; +}; + +&atcphy1 { + ports { + #address-cells = <1>; + #size-cells = <0>; + + port@0 { + reg = <0>; + atcphy1_typec_lanes: endpoint { + remote-endpoint = <&typec1_connector_ss>; + }; + }; + + port@1 { + reg = <1>; + atcphy1_usb3: endpoint { + remote-endpoint = <&dwc3_1_ss>; + }; + }; }; }; diff --git a/arch/arm64/boot/dts/apple/t8112.dtsi b/arch/arm64/boot/dts/apple/t8112.dtsi index 3f79878b25af1f..2d645cdec4a75f 100644 --- a/arch/arm64/boot/dts/apple/t8112.dtsi +++ b/arch/arm64/boot/dts/apple/t8112.dtsi @@ -11,6 +11,7 @@ #include #include #include +#include #include / { @@ -1010,6 +1011,110 @@ resets = <&ps_ans>; }; + dwc3_0: usb@382280000 { + compatible = "apple,t8112-dwc3", "apple,t8103-dwc3"; + reg = <0x3 0x82280000 0x0 0xcd00>, <0x3 0x8228cd00 0x0 0x3200>; + reg-names = "dwc3-core", "dwc3-apple"; + interrupt-parent = <&aic>; + interrupts = ; + dr_mode = "otg"; + usb-role-switch; + role-switch-default-mode = "host"; + iommus = <&dwc3_0_dart_0 0>, <&dwc3_0_dart_1 1>; + power-domains = <&ps_atc0_usb>; + resets = <&atcphy0>; + phys = <&atcphy0 PHY_TYPE_USB2>, <&atcphy0 PHY_TYPE_USB3>; + phy-names = "usb2-phy", "usb3-phy"; + }; + + dwc3_0_dart_0: iommu@382f00000 { + compatible = "apple,t8110-dart"; + reg = <0x3 0x82f00000 0x0 0x4000>; + interrupt-parent = <&aic>; + interrupts = ; + #iommu-cells = <1>; + power-domains = <&ps_atc0_usb>; + }; + + dwc3_0_dart_1: iommu@382f80000 { + compatible = "apple,t8110-dart"; + reg = <0x3 0x82f80000 0x0 0x4000>; + interrupt-parent = <&aic>; + interrupts = ; + #iommu-cells = <1>; + power-domains = <&ps_atc0_usb>; + }; + + atcphy0: phy@383000000 { + compatible = "apple,t8112-atcphy", "apple,t8103-atcphy"; + reg = <0x3 0x83000000 0x0 0x4c000>, + <0x3 0x83050000 0x0 0x8000>, + <0x3 0x80000000 0x0 0x4000>, + <0x3 0x82a90000 0x0 0x4000>, + <0x3 0x82a84000 0x0 0x4000>; + reg-names = "core", "lpdptx", "axi2af", "usb2phy", + "pipehandler"; + + #phy-cells = <1>; + #reset-cells = <0>; + + orientation-switch; + mode-switch; + power-domains = <&ps_atc0_usb>; + }; + + dwc3_1: usb@502280000 { + compatible = "apple,t8112-dwc3", "apple,t8103-dwc3"; + reg = <0x5 0x02280000 0x0 0xcd00>, <0x5 0x0228cd00 0x0 0x3200>; + reg-names = "dwc3-core", "dwc3-apple"; + interrupt-parent = <&aic>; + interrupts = ; + dr_mode = "otg"; + usb-role-switch; + role-switch-default-mode = "host"; + iommus = <&dwc3_1_dart_0 0>, <&dwc3_1_dart_1 1>; + power-domains = <&ps_atc1_usb>; + resets = <&atcphy1>; + phys = <&atcphy1 PHY_TYPE_USB2>, <&atcphy1 PHY_TYPE_USB3>; + phy-names = "usb2-phy", "usb3-phy"; + }; + + dwc3_1_dart_0: iommu@502f00000 { + compatible = "apple,t8110-dart"; + reg = <0x5 0x02f00000 0x0 0x4000>; + interrupt-parent = <&aic>; + interrupts = ; + #iommu-cells = <1>; + power-domains = <&ps_atc1_usb>; + }; + + dwc3_1_dart_1: iommu@502f80000 { + compatible = "apple,t8110-dart"; + reg = <0x5 0x02f80000 0x0 0x4000>; + interrupt-parent = <&aic>; + interrupts = ; + #iommu-cells = <1>; + power-domains = <&ps_atc1_usb>; + }; + + atcphy1: phy@503000000 { + compatible = "apple,t8112-atcphy", "apple,t8103-atcphy"; + reg = <0x5 0x03000000 0x0 0x4c000>, + <0x5 0x03050000 0x0 0x8000>, + <0x5 0x0 0x0 0x4000>, + <0x5 0x02a90000 0x0 0x4000>, + <0x5 0x02a84000 0x0 0x4000>; + reg-names = "core", "lpdptx", "axi2af", "usb2phy", + "pipehandler"; + + #phy-cells = <1>; + #reset-cells = <0>; + + orientation-switch; + mode-switch; + power-domains = <&ps_atc1_usb>; + }; + pcie0_dart: iommu@681008000 { compatible = "apple,t8110-dart"; reg = <0x6 0x81008000 0x0 0x4000>; From 42f9d12faafb1806e1e574e671448e9a6337fc2a Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Fri, 9 Jan 2026 15:07:06 +0100 Subject: [PATCH 2088/3902] arm64: dts: apple: t60xx: Add nodes for integrated USB Type-C ports Add device nodes and connections to support USB 3.x on the SoC's integrated Type-C ports of M1 and M2 Pro, Max and Ultra based devices. Each Type-C port has an Apple Type-C PHY for USB 2.0, USB 3.x, USB4/Thunderbolt, and DisplayPort, a Synopsys Designware USB 3.x controller, two DART iommu instances and a CD321x USB PD controller. M1 and M2 Max based Mac Studio device have two additional USB Type-C ports on the front which are driven by an AsMedia PCIe USB controller and integrated USB hub. These ports are not covered by this change. The port labels use Apple's established naming scheme for the ports. Co-developed-by: R Signed-off-by: R Co-developed-by: Hector Martin Signed-off-by: Hector Martin Reviewed-by: Neal Gompa Signed-off-by: Janne Grunau --- arch/arm64/boot/dts/apple/t6001.dtsi | 1 + arch/arm64/boot/dts/apple/t6002-j375d.dts | 150 +++++++ arch/arm64/boot/dts/apple/t6002.dtsi | 1 + arch/arm64/boot/dts/apple/t600x-dieX.dtsi | 212 +++++++++ .../arm64/boot/dts/apple/t600x-j314-j316.dtsi | 234 ++++++++++ arch/arm64/boot/dts/apple/t600x-j375.dtsi | 270 ++++++++++++ arch/arm64/boot/dts/apple/t6022-j180d.dts | 415 ++++++++++++++++++ arch/arm64/boot/dts/apple/t6022-j475d.dts | 31 ++ arch/arm64/boot/dts/apple/t6022-jxxxd.dtsi | 133 ++++++ arch/arm64/boot/dts/apple/t602x-dieX.dtsi | 212 +++++++++ 10 files changed, 1659 insertions(+) diff --git a/arch/arm64/boot/dts/apple/t6001.dtsi b/arch/arm64/boot/dts/apple/t6001.dtsi index ffbe823b71bc8d..6dcb71a1d65a8d 100644 --- a/arch/arm64/boot/dts/apple/t6001.dtsi +++ b/arch/arm64/boot/dts/apple/t6001.dtsi @@ -11,6 +11,7 @@ #include #include #include +#include #include #include "multi-die-cpp.h" diff --git a/arch/arm64/boot/dts/apple/t6002-j375d.dts b/arch/arm64/boot/dts/apple/t6002-j375d.dts index 2b7f80119618ad..a2a24d028cbbf5 100644 --- a/arch/arm64/boot/dts/apple/t6002-j375d.dts +++ b/arch/arm64/boot/dts/apple/t6002-j375d.dts @@ -15,6 +15,10 @@ / { compatible = "apple,j375d", "apple,t6002", "apple,arm-platform"; model = "Apple Mac Studio (M1 Ultra, 2022)"; + aliases { + atcphy4 = &atcphy0_die1; + atcphy5 = &atcphy1_die1; + }; }; /* USB Type C */ @@ -26,6 +30,30 @@ interrupt-parent = <&pinctrl_ap>; interrupts = <174 IRQ_TYPE_LEVEL_LOW>; interrupt-names = "irq"; + + typec4: connector { + compatible = "usb-c-connector"; + label = "USB-C Front Right"; + power-role = "dual"; + data-role = "dual"; + + ports { + #address-cells = <1>; + #size-cells = <0>; + port@0 { + reg = <0>; + typec4_connector_hs: endpoint { + remote-endpoint = <&dwc3_4_hs>; + }; + }; + port@1 { + reg = <1>; + typec4_connector_ss: endpoint { + remote-endpoint = <&atcphy4_typec_lanes>; + }; + }; + }; + }; }; /* front-left */ @@ -35,6 +63,30 @@ interrupt-parent = <&pinctrl_ap>; interrupts = <174 IRQ_TYPE_LEVEL_LOW>; interrupt-names = "irq"; + + typec5: connector { + compatible = "usb-c-connector"; + label = "USB-C Front Left"; + power-role = "dual"; + data-role = "dual"; + + ports { + #address-cells = <1>; + #size-cells = <0>; + port@0 { + reg = <0>; + typec5_connector_hs: endpoint { + remote-endpoint = <&dwc3_5_hs>; + }; + }; + port@1 { + reg = <1>; + typec5_connector_ss: endpoint { + remote-endpoint = <&atcphy5_typec_lanes>; + }; + }; + }; + }; }; }; @@ -46,6 +98,104 @@ brcm,board-type = "apple,okinawa"; }; +/* USB controllers on die 1 */ +&dwc3_0_die1 { + ports { + #address-cells = <1>; + #size-cells = <0>; + + port@0 { + reg = <0>; + dwc3_4_hs: endpoint { + remote-endpoint = <&typec4_connector_hs>; + }; + }; + + port@1 { + reg = <1>; + dwc3_4_ss: endpoint { + remote-endpoint = <&atcphy4_usb3>; + }; + }; + }; +}; + +&dwc3_1_die1 { + ports { + #address-cells = <1>; + #size-cells = <0>; + + port@0 { + reg = <0>; + dwc3_5_hs: endpoint { + remote-endpoint = <&typec5_connector_hs>; + }; + }; + + port@1 { + reg = <1>; + dwc3_5_ss: endpoint { + remote-endpoint = <&atcphy5_usb3>; + }; + }; + }; +}; + +/* Type-C PHYs */ +&atcphy0_die1 { + ports { + #address-cells = <1>; + #size-cells = <0>; + + port@0 { + reg = <0>; + atcphy4_typec_lanes: endpoint { + remote-endpoint = <&typec4_connector_ss>; + }; + }; + + port@1 { + reg = <1>; + atcphy4_usb3: endpoint { + remote-endpoint = <&dwc3_4_ss>; + }; + }; + }; +}; + +&atcphy1_die1 { + ports { + #address-cells = <1>; + #size-cells = <0>; + + port@0 { + reg = <0>; + atcphy5_typec_lanes: endpoint { + remote-endpoint = <&typec5_connector_ss>; + }; + }; + + port@1 { + reg = <1>; + atcphy5_usb3: endpoint { + remote-endpoint = <&dwc3_5_ss>; + }; + }; + }; +}; + +/* delete unused USB nodes on die 1 */ + +/delete-node/ &dwc3_2_dart_0_die1; +/delete-node/ &dwc3_2_dart_1_die1; +/delete-node/ &dwc3_2_die1; +/delete-node/ &atcphy2_die1; + +/delete-node/ &dwc3_3_dart_0_die1; +/delete-node/ &dwc3_3_dart_1_die1; +/delete-node/ &dwc3_3_die1; +/delete-node/ &atcphy3_die1; + /* delete unused always-on power-domains on die 1 */ /delete-node/ &ps_atc2_usb_aon_die1; diff --git a/arch/arm64/boot/dts/apple/t6002.dtsi b/arch/arm64/boot/dts/apple/t6002.dtsi index 8fb648836b538b..a532e5401c4ec4 100644 --- a/arch/arm64/boot/dts/apple/t6002.dtsi +++ b/arch/arm64/boot/dts/apple/t6002.dtsi @@ -11,6 +11,7 @@ #include #include #include +#include #include #include "multi-die-cpp.h" diff --git a/arch/arm64/boot/dts/apple/t600x-dieX.dtsi b/arch/arm64/boot/dts/apple/t600x-dieX.dtsi index a32ff0c9d7b0c2..9676d5127039b7 100644 --- a/arch/arm64/boot/dts/apple/t600x-dieX.dtsi +++ b/arch/arm64/boot/dts/apple/t600x-dieX.dtsi @@ -119,3 +119,215 @@ interrupt-controller; #interrupt-cells = <2>; }; + + DIE_NODE(dwc3_0): usb@702280000 { + compatible = "apple,t6000-dwc3", "apple,t8103-dwc3"; + reg = <0x7 0x02280000 0x0 0xcd00>, <0x7 0x0228cd00 0x0 0x3200>; + reg-names = "dwc3-core", "dwc3-apple"; + interrupt-parent = <&aic>; + interrupts = ; + dr_mode = "otg"; + usb-role-switch; + role-switch-default-mode = "host"; + iommus = <&DIE_NODE(dwc3_0_dart_0) 0>, + <&DIE_NODE(dwc3_0_dart_1) 1>; + power-domains = <&DIE_NODE(ps_atc0_usb)>; + resets = <&DIE_NODE(atcphy0)>; + phys = <&DIE_NODE(atcphy0) PHY_TYPE_USB2>, <&DIE_NODE(atcphy0) PHY_TYPE_USB3>; + phy-names = "usb2-phy", "usb3-phy"; + }; + + DIE_NODE(dwc3_0_dart_0): iommu@702f00000 { + compatible = "apple,t6000-dart"; + reg = <0x7 0x02f00000 0x0 0x4000>; + interrupt-parent = <&aic>; + interrupts = ; + power-domains = <&DIE_NODE(ps_atc0_usb)>; + #iommu-cells = <1>; + }; + + DIE_NODE(dwc3_0_dart_1): iommu@702f80000 { + compatible = "apple,t6000-dart"; + reg = <0x7 0x02f80000 0x0 0x4000>; + interrupt-parent = <&aic>; + interrupts = ; + power-domains = <&DIE_NODE(ps_atc0_usb)>; + #iommu-cells = <1>; + }; + + DIE_NODE(atcphy0): phy@703000000 { + compatible = "apple,t6000-atcphy", "apple,t8103-atcphy"; + reg = <0x7 0x03000000 0x0 0x4c000>, + <0x7 0x03050000 0x0 0x8000>, + <0x7 0x00000000 0x0 0x4000>, + <0x7 0x02a90000 0x0 0x4000>, + <0x7 0x02a84000 0x0 0x4000>; + reg-names = "core", "lpdptx", "axi2af", "usb2phy", + "pipehandler"; + + #phy-cells = <1>; + #reset-cells = <0>; + + orientation-switch; + mode-switch; + power-domains = <&DIE_NODE(ps_atc0_usb)>; + }; + + DIE_NODE(dwc3_1): usb@b02280000 { + compatible = "apple,t6000-dwc3", "apple,t8103-dwc3"; + reg = <0xb 0x02280000 0x0 0xcd00>, <0xb 0x0228cd00 0x0 0x3200>; + reg-names = "dwc3-core", "dwc3-apple"; + interrupt-parent = <&aic>; + interrupts = ; + dr_mode = "otg"; + usb-role-switch; + role-switch-default-mode = "host"; + iommus = <&DIE_NODE(dwc3_1_dart_0) 0>, + <&DIE_NODE(dwc3_1_dart_1) 1>; + power-domains = <&DIE_NODE(ps_atc1_usb)>; + resets = <&DIE_NODE(atcphy1)>; + phys = <&DIE_NODE(atcphy1) PHY_TYPE_USB2>, <&DIE_NODE(atcphy1) PHY_TYPE_USB3>; + phy-names = "usb2-phy", "usb3-phy"; + }; + + DIE_NODE(dwc3_1_dart_0): iommu@b02f00000 { + compatible = "apple,t6000-dart"; + reg = <0xb 0x02f00000 0x0 0x4000>; + interrupt-parent = <&aic>; + interrupts = ; + power-domains = <&DIE_NODE(ps_atc1_usb)>; + #iommu-cells = <1>; + }; + + DIE_NODE(dwc3_1_dart_1): iommu@b02f80000 { + compatible = "apple,t6000-dart"; + reg = <0xb 0x02f80000 0x0 0x4000>; + interrupt-parent = <&aic>; + interrupts = ; + power-domains = <&DIE_NODE(ps_atc1_usb)>; + #iommu-cells = <1>; + }; + + DIE_NODE(atcphy1): phy@b03000000 { + compatible = "apple,t6000-atcphy", "apple,t8103-atcphy"; + reg = <0xb 0x03000000 0x0 0x4c000>, + <0xb 0x03050000 0x0 0x8000>, + <0xb 0x00000000 0x0 0x4000>, + <0xb 0x02a90000 0x0 0x4000>, + <0xb 0x02a84000 0x0 0x4000>; + reg-names = "core", "lpdptx", "axi2af", "usb2phy", + "pipehandler"; + + #phy-cells = <1>; + #reset-cells = <0>; + + orientation-switch; + mode-switch; + power-domains = <&DIE_NODE(ps_atc1_usb)>; + }; + + DIE_NODE(dwc3_2): usb@f02280000 { + compatible = "apple,t6000-dwc3", "apple,t8103-dwc3"; + reg = <0xf 0x02280000 0x0 0xcd00>, <0xf 0x0228cd00 0x0 0x3200>; + reg-names = "dwc3-core", "dwc3-apple"; + interrupt-parent = <&aic>; + interrupts = ; + dr_mode = "otg"; + usb-role-switch; + role-switch-default-mode = "host"; + iommus = <&DIE_NODE(dwc3_2_dart_0) 0>, + <&DIE_NODE(dwc3_2_dart_1) 1>; + power-domains = <&DIE_NODE(ps_atc2_usb)>; + resets = <&DIE_NODE(atcphy2)>; + phys = <&DIE_NODE(atcphy2) PHY_TYPE_USB2>, <&DIE_NODE(atcphy2) PHY_TYPE_USB3>; + phy-names = "usb2-phy", "usb3-phy"; + }; + + DIE_NODE(dwc3_2_dart_0): iommu@f02f00000 { + compatible = "apple,t6000-dart"; + reg = <0xf 0x02f00000 0x0 0x4000>; + interrupt-parent = <&aic>; + interrupts = ; + power-domains = <&DIE_NODE(ps_atc2_usb)>; + #iommu-cells = <1>; + }; + + DIE_NODE(dwc3_2_dart_1): iommu@f02f80000 { + compatible = "apple,t6000-dart"; + reg = <0xf 0x02f80000 0x0 0x4000>; + interrupt-parent = <&aic>; + interrupts = ; + power-domains = <&DIE_NODE(ps_atc2_usb)>; + #iommu-cells = <1>; + }; + + DIE_NODE(atcphy2): phy@f03000000 { + compatible = "apple,t6000-atcphy", "apple,t8103-atcphy"; + reg = <0xf 0x03000000 0x0 0x4c000>, + <0xf 0x03050000 0x0 0x8000>, + <0xf 0x00000000 0x0 0x4000>, + <0xf 0x02a90000 0x0 0x4000>, + <0xf 0x02a84000 0x0 0x4000>; + reg-names = "core", "lpdptx", "axi2af", "usb2phy", + "pipehandler"; + + #phy-cells = <1>; + #reset-cells = <0>; + + orientation-switch; + mode-switch; + power-domains = <&DIE_NODE(ps_atc2_usb)>; + }; + + DIE_NODE(dwc3_3): usb@1302280000 { + compatible = "apple,t6000-dwc3", "apple,t8103-dwc3"; + reg = <0x13 0x02280000 0x0 0xcd00>, <0x13 0x0228cd00 0x0 0x3200>; + reg-names = "dwc3-core", "dwc3-apple"; + interrupt-parent = <&aic>; + interrupts = ; + dr_mode = "otg"; + usb-role-switch; + role-switch-default-mode = "host"; + iommus = <&DIE_NODE(dwc3_3_dart_0) 0>, + <&DIE_NODE(dwc3_3_dart_1) 1>; + power-domains = <&DIE_NODE(ps_atc3_usb)>; + resets = <&DIE_NODE(atcphy3)>; + phys = <&DIE_NODE(atcphy3) PHY_TYPE_USB2>, <&DIE_NODE(atcphy3) PHY_TYPE_USB3>; + phy-names = "usb2-phy", "usb3-phy"; + }; + + DIE_NODE(dwc3_3_dart_0): iommu@1302f00000 { + compatible = "apple,t6000-dart"; + reg = <0x13 0x02f00000 0x0 0x4000>; + interrupt-parent = <&aic>; + interrupts = ; + power-domains = <&DIE_NODE(ps_atc3_usb)>; + #iommu-cells = <1>; + }; + + DIE_NODE(dwc3_3_dart_1): iommu@1302f80000 { + compatible = "apple,t6000-dart"; + reg = <0x13 0x02f80000 0x0 0x4000>; + interrupt-parent = <&aic>; + interrupts = ; + power-domains = <&DIE_NODE(ps_atc3_usb)>; + #iommu-cells = <1>; + }; + + DIE_NODE(atcphy3): phy@1303000000 { + compatible = "apple,t6000-atcphy", "apple,t8103-atcphy"; + reg = <0x13 0x03000000 0x0 0x4c000>, + <0x13 0x03050000 0x0 0x8000>, + <0x13 0x00000000 0x0 0x4000>, + <0x13 0x02a90000 0x0 0x4000>, + <0x13 0x02a84000 0x0 0x4000>; + reg-names = "core", "lpdptx", "axi2af", "usb2phy", + "pipehandler"; + + #phy-cells = <1>; + #reset-cells = <0>; + + orientation-switch; + mode-switch; + power-domains = <&DIE_NODE(ps_atc3_usb)>; + }; diff --git a/arch/arm64/boot/dts/apple/t600x-j314-j316.dtsi b/arch/arm64/boot/dts/apple/t600x-j314-j316.dtsi index c0aac59a6fae4f..6b3b3272308184 100644 --- a/arch/arm64/boot/dts/apple/t600x-j314-j316.dtsi +++ b/arch/arm64/boot/dts/apple/t600x-j314-j316.dtsi @@ -13,6 +13,10 @@ / { aliases { + atcphy0 = &atcphy0; + atcphy1 = &atcphy1; + atcphy2 = &atcphy2; + atcphy3 = &atcphy3; bluetooth0 = &bluetooth0; serial0 = &serial0; wifi0 = &wifi0; @@ -63,6 +67,30 @@ interrupt-parent = <&pinctrl_ap>; interrupts = <174 IRQ_TYPE_LEVEL_LOW>; interrupt-names = "irq"; + + typec0: connector { + compatible = "usb-c-connector"; + label = "USB-C Left Rear"; + power-role = "dual"; + data-role = "dual"; + + ports { + #address-cells = <1>; + #size-cells = <0>; + port@0 { + reg = <0>; + typec0_connector_hs: endpoint { + remote-endpoint = <&dwc3_0_hs>; + }; + }; + port@1 { + reg = <1>; + typec0_connector_ss: endpoint { + remote-endpoint = <&atcphy0_typec_lanes>; + }; + }; + }; + }; }; hpm1: usb-pd@3f { @@ -71,6 +99,30 @@ interrupt-parent = <&pinctrl_ap>; interrupts = <174 IRQ_TYPE_LEVEL_LOW>; interrupt-names = "irq"; + + typec1: connector { + compatible = "usb-c-connector"; + label = "USB-C Left Front"; + power-role = "dual"; + data-role = "dual"; + + ports { + #address-cells = <1>; + #size-cells = <0>; + port@0 { + reg = <0>; + typec1_connector_hs: endpoint { + remote-endpoint = <&dwc3_1_hs>; + }; + }; + port@1 { + reg = <1>; + typec1_connector_ss: endpoint { + remote-endpoint = <&atcphy1_typec_lanes>; + }; + }; + }; + }; }; hpm2: usb-pd@3b { @@ -79,6 +131,30 @@ interrupt-parent = <&pinctrl_ap>; interrupts = <174 IRQ_TYPE_LEVEL_LOW>; interrupt-names = "irq"; + + typec2: connector { + compatible = "usb-c-connector"; + label = "USB-C Right"; + power-role = "dual"; + data-role = "dual"; + + ports { + #address-cells = <1>; + #size-cells = <0>; + port@0 { + reg = <0>; + typec2_connector_hs: endpoint { + remote-endpoint = <&dwc3_2_hs>; + }; + }; + port@1 { + reg = <1>; + typec2_connector_ss: endpoint { + remote-endpoint = <&atcphy2_typec_lanes>; + }; + }; + }; + }; }; /* MagSafe port */ @@ -130,4 +206,162 @@ status = "okay"; }; +/* USB controllers */ +&dwc3_0 { + ports { + #address-cells = <1>; + #size-cells = <0>; + + port@0 { + reg = <0>; + dwc3_0_hs: endpoint { + remote-endpoint = <&typec0_connector_hs>; + }; + }; + + port@1 { + reg = <1>; + dwc3_0_ss: endpoint { + remote-endpoint = <&atcphy0_usb3>; + }; + }; + }; +}; + +&dwc3_1 { + ports { + #address-cells = <1>; + #size-cells = <0>; + + port@0 { + reg = <0>; + dwc3_1_hs: endpoint { + remote-endpoint = <&typec1_connector_hs>; + }; + }; + + port@1 { + reg = <1>; + dwc3_1_ss: endpoint { + remote-endpoint = <&atcphy1_usb3>; + }; + }; + }; +}; + +&dwc3_2 { + ports { + #address-cells = <1>; + #size-cells = <0>; + + port@0 { + reg = <0>; + dwc3_2_hs: endpoint { + remote-endpoint = <&typec2_connector_hs>; + }; + }; + + port@1 { + reg = <1>; + dwc3_2_ss: endpoint { + remote-endpoint = <&atcphy2_usb3>; + }; + }; + }; +}; + +/* + * ps_atc3_usb_aon power-domain is always-on to keep dwc3 working over suspend. + * atc3 is used exclusively for the DP-to-HDMI so do not keep this always on. + */ +&ps_atc3_usb_aon { + /delete-property/ apple,always-on; +}; + +/* ATC3 is used for DisplayPort -> HDMI only */ +&dwc3_3_dart_0 { + status = "disabled"; +}; + +&dwc3_3_dart_1 { + status = "disabled"; +}; + +&dwc3_3 { + status = "disabled"; +}; + +/* Delete unused dwc3_3 to prevent dt_disable_missing_devs() from disabling + * atcphy3 via phandle references from a disablecd device. + */ +/delete-node/ &dwc3_3; + +/* Type-C PHYs */ +&atcphy0 { + ports { + #address-cells = <1>; + #size-cells = <0>; + + port@0 { + reg = <0>; + atcphy0_typec_lanes: endpoint { + remote-endpoint = <&typec0_connector_ss>; + }; + }; + + port@1 { + reg = <1>; + atcphy0_usb3: endpoint { + remote-endpoint = <&dwc3_0_ss>; + }; + }; + }; +}; + +&atcphy1 { + ports { + #address-cells = <1>; + #size-cells = <0>; + + port@0 { + reg = <0>; + atcphy1_typec_lanes: endpoint { + remote-endpoint = <&typec1_connector_ss>; + }; + }; + + port@1 { + reg = <1>; + atcphy1_usb3: endpoint { + remote-endpoint = <&dwc3_1_ss>; + }; + }; + }; +}; + +&atcphy2 { + ports { + #address-cells = <1>; + #size-cells = <0>; + + port@0 { + reg = <0>; + atcphy2_typec_lanes: endpoint { + remote-endpoint = <&typec2_connector_ss>; + }; + }; + + port@1 { + reg = <1>; + atcphy2_usb3: endpoint { + remote-endpoint = <&dwc3_2_ss>; + }; + }; + }; +}; + +&atcphy3 { + status = "disabled"; +}; + #include "spi1-nvram.dtsi" diff --git a/arch/arm64/boot/dts/apple/t600x-j375.dtsi b/arch/arm64/boot/dts/apple/t600x-j375.dtsi index c0fb93ae72f4d4..a4bb5cbb67a067 100644 --- a/arch/arm64/boot/dts/apple/t600x-j375.dtsi +++ b/arch/arm64/boot/dts/apple/t600x-j375.dtsi @@ -11,6 +11,10 @@ / { aliases { + atcphy0 = &atcphy0; + atcphy1 = &atcphy1; + atcphy2 = &atcphy2; + atcphy3 = &atcphy3; bluetooth0 = &bluetooth0; ethernet0 = ðernet0; serial0 = &serial0; @@ -50,6 +54,30 @@ interrupt-parent = <&pinctrl_ap>; interrupts = <174 IRQ_TYPE_LEVEL_LOW>; interrupt-names = "irq"; + + typec0: connector { + compatible = "usb-c-connector"; + label = "USB-C Back Left"; + power-role = "dual"; + data-role = "dual"; + + ports { + #address-cells = <1>; + #size-cells = <0>; + port@0 { + reg = <0>; + typec0_connector_hs: endpoint { + remote-endpoint = <&dwc3_0_hs>; + }; + }; + port@1 { + reg = <1>; + typec0_connector_ss: endpoint { + remote-endpoint = <&atcphy0_typec_lanes>; + }; + }; + }; + }; }; hpm1: usb-pd@3f { @@ -58,6 +86,30 @@ interrupt-parent = <&pinctrl_ap>; interrupts = <174 IRQ_TYPE_LEVEL_LOW>; interrupt-names = "irq"; + + typec1: connector { + compatible = "usb-c-connector"; + label = "USB-C Back Left Middle"; + power-role = "dual"; + data-role = "dual"; + + ports { + #address-cells = <1>; + #size-cells = <0>; + port@0 { + reg = <0>; + typec1_connector_hs: endpoint { + remote-endpoint = <&dwc3_1_hs>; + }; + }; + port@1 { + reg = <1>; + typec1_connector_ss: endpoint { + remote-endpoint = <&atcphy1_typec_lanes>; + }; + }; + }; + }; }; hpm2: usb-pd@3b { @@ -66,6 +118,30 @@ interrupt-parent = <&pinctrl_ap>; interrupts = <174 IRQ_TYPE_LEVEL_LOW>; interrupt-names = "irq"; + + typec2: connector { + compatible = "usb-c-connector"; + label = "USB-C Back Right Middle"; + power-role = "dual"; + data-role = "dual"; + + ports { + #address-cells = <1>; + #size-cells = <0>; + port@0 { + reg = <0>; + typec2_connector_hs: endpoint { + remote-endpoint = <&dwc3_2_hs>; + }; + }; + port@1 { + reg = <1>; + typec2_connector_ss: endpoint { + remote-endpoint = <&atcphy2_typec_lanes>; + }; + }; + }; + }; }; hpm3: usb-pd@3c { @@ -74,6 +150,200 @@ interrupt-parent = <&pinctrl_ap>; interrupts = <174 IRQ_TYPE_LEVEL_LOW>; interrupt-names = "irq"; + + typec3: connector { + compatible = "usb-c-connector"; + label = "USB-C Back Right"; + power-role = "dual"; + data-role = "dual"; + + ports { + #address-cells = <1>; + #size-cells = <0>; + port@0 { + reg = <0>; + typec3_connector_hs: endpoint { + remote-endpoint = <&dwc3_3_hs>; + }; + }; + port@1 { + reg = <1>; + typec3_connector_ss: endpoint { + remote-endpoint = <&atcphy3_typec_lanes>; + }; + }; + }; + }; + }; +}; + +/* USB controllers */ +&dwc3_0 { + ports { + #address-cells = <1>; + #size-cells = <0>; + + port@0 { + reg = <0>; + dwc3_0_hs: endpoint { + remote-endpoint = <&typec0_connector_hs>; + }; + }; + + port@1 { + reg = <1>; + dwc3_0_ss: endpoint { + remote-endpoint = <&atcphy0_usb3>; + }; + }; + }; +}; + +&dwc3_1 { + ports { + #address-cells = <1>; + #size-cells = <0>; + + port@0 { + reg = <0>; + dwc3_1_hs: endpoint { + remote-endpoint = <&typec1_connector_hs>; + }; + }; + + port@1 { + reg = <1>; + dwc3_1_ss: endpoint { + remote-endpoint = <&atcphy1_usb3>; + }; + }; + }; +}; + +&dwc3_2 { + ports { + #address-cells = <1>; + #size-cells = <0>; + + port@0 { + reg = <0>; + dwc3_2_hs: endpoint { + remote-endpoint = <&typec2_connector_hs>; + }; + }; + + port@1 { + reg = <1>; + dwc3_2_ss: endpoint { + remote-endpoint = <&atcphy2_usb3>; + }; + }; + }; +}; + +&dwc3_3 { + ports { + #address-cells = <1>; + #size-cells = <0>; + + port@0 { + reg = <0>; + dwc3_3_hs: endpoint { + remote-endpoint = <&typec3_connector_hs>; + }; + }; + + port@1 { + reg = <1>; + dwc3_3_ss: endpoint { + remote-endpoint = <&atcphy3_usb3>; + }; + }; + }; +}; + +/* Type-C PHYs */ +&atcphy0 { + ports { + #address-cells = <1>; + #size-cells = <0>; + + port@0 { + reg = <0>; + atcphy0_typec_lanes: endpoint { + remote-endpoint = <&typec0_connector_ss>; + }; + }; + + port@1 { + reg = <1>; + atcphy0_usb3: endpoint { + remote-endpoint = <&dwc3_0_ss>; + }; + }; + }; +}; + +&atcphy1 { + ports { + #address-cells = <1>; + #size-cells = <0>; + + port@0 { + reg = <0>; + atcphy1_typec_lanes: endpoint { + remote-endpoint = <&typec1_connector_ss>; + }; + }; + + port@1 { + reg = <1>; + atcphy1_usb3: endpoint { + remote-endpoint = <&dwc3_1_ss>; + }; + }; + }; +}; + +&atcphy2 { + ports { + #address-cells = <1>; + #size-cells = <0>; + + port@0 { + reg = <0>; + atcphy2_typec_lanes: endpoint { + remote-endpoint = <&typec2_connector_ss>; + }; + }; + + port@1 { + reg = <1>; + atcphy2_usb3: endpoint { + remote-endpoint = <&dwc3_2_ss>; + }; + }; + }; +}; + +&atcphy3 { + ports { + #address-cells = <1>; + #size-cells = <0>; + + port@0 { + reg = <0>; + atcphy3_typec_lanes: endpoint { + remote-endpoint = <&typec3_connector_ss>; + }; + }; + + port@1 { + reg = <1>; + atcphy3_usb3: endpoint { + remote-endpoint = <&dwc3_3_ss>; + }; + }; }; }; diff --git a/arch/arm64/boot/dts/apple/t6022-j180d.dts b/arch/arm64/boot/dts/apple/t6022-j180d.dts index dca6bd167c225a..741199d456fba4 100644 --- a/arch/arm64/boot/dts/apple/t6022-j180d.dts +++ b/arch/arm64/boot/dts/apple/t6022-j180d.dts @@ -16,6 +16,14 @@ compatible = "apple,j180d", "apple,t6022", "apple,arm-platform"; model = "Apple Mac Pro (M2 Ultra, 2023)"; aliases { + atcphy0 = &atcphy0; + atcphy1 = &atcphy1; + atcphy2 = &atcphy2; + atcphy3 = &atcphy3; + atcphy4 = &atcphy0_die1; + atcphy5 = &atcphy1_die1; + atcphy6 = &atcphy2_die1; + atcphy7 = &atcphy3_die1; nvram = &nvram; serial0 = &serial0; }; @@ -54,6 +62,30 @@ interrupt-parent = <&pinctrl_ap>; interrupts = <44 IRQ_TYPE_LEVEL_LOW>; interrupt-names = "irq"; + + typec2: connector { + compatible = "usb-c-connector"; + label = "USB-C Back 1"; + power-role = "dual"; + data-role = "dual"; + + ports { + #address-cells = <1>; + #size-cells = <0>; + port@0 { + reg = <0>; + typec2_connector_hs: endpoint { + remote-endpoint = <&dwc3_2_hs>; + }; + }; + port@1 { + reg = <1>; + typec2_connector_ss: endpoint { + remote-endpoint = <&atcphy2_typec_lanes>; + }; + }; + }; + }; }; hpm3: usb-pd@3c { @@ -62,6 +94,30 @@ interrupt-parent = <&pinctrl_ap>; interrupts = <44 IRQ_TYPE_LEVEL_LOW>; interrupt-names = "irq"; + + typec3: connector { + compatible = "usb-c-connector"; + label = "USB-C Back 2"; + power-role = "dual"; + data-role = "dual"; + + ports { + #address-cells = <1>; + #size-cells = <0>; + port@0 { + reg = <0>; + typec3_connector_hs: endpoint { + remote-endpoint = <&dwc3_3_hs>; + }; + }; + port@1 { + reg = <1>; + typec3_connector_ss: endpoint { + remote-endpoint = <&atcphy3_typec_lanes>; + }; + }; + }; + }; }; /* hpm4 and hpm5 included from t6022-jxxxd.dtsi */ @@ -72,6 +128,30 @@ interrupt-parent = <&pinctrl_ap>; interrupts = <44 IRQ_TYPE_LEVEL_LOW>; interrupt-names = "irq"; + + typec6: connector { + compatible = "usb-c-connector"; + label = "USB-C Back 5"; + power-role = "dual"; + data-role = "dual"; + + ports { + #address-cells = <1>; + #size-cells = <0>; + port@0 { + reg = <0>; + typec6_connector_hs: endpoint { + remote-endpoint = <&dwc3_6_hs>; + }; + }; + port@1 { + reg = <1>; + typec6_connector_ss: endpoint { + remote-endpoint = <&atcphy6_typec_lanes>; + }; + }; + }; + }; }; hpm7: usb-pd@3e { @@ -80,9 +160,41 @@ interrupt-parent = <&pinctrl_ap>; interrupts = <44 IRQ_TYPE_LEVEL_LOW>; interrupt-names = "irq"; + + typec7: connector { + compatible = "usb-c-connector"; + label = "USB-C Back 6"; + power-role = "dual"; + data-role = "dual"; + + ports { + #address-cells = <1>; + #size-cells = <0>; + port@0 { + reg = <0>; + typec7_connector_hs: endpoint { + remote-endpoint = <&dwc3_7_hs>; + }; + }; + port@1 { + reg = <1>; + typec7_connector_ss: endpoint { + remote-endpoint = <&atcphy7_typec_lanes>; + }; + }; + }; + }; }; }; +&typec4 { + label = "USB-C Back 3"; +}; + +&typec5 { + label = "USB-C Back 4"; +}; + /* USB Type C Front */ &i2c3 { status = "okay"; @@ -93,6 +205,30 @@ interrupt-parent = <&pinctrl_ap>; interrupts = <60 IRQ_TYPE_LEVEL_LOW>; interrupt-names = "irq"; + + typec0: connector { + compatible = "usb-c-connector"; + label = "USB-C Top Right"; + power-role = "dual"; + data-role = "dual"; + + ports { + #address-cells = <1>; + #size-cells = <0>; + port@0 { + reg = <0>; + typec0_connector_hs: endpoint { + remote-endpoint = <&dwc3_0_hs>; + }; + }; + port@1 { + reg = <1>; + typec0_connector_ss: endpoint { + remote-endpoint = <&atcphy0_typec_lanes>; + }; + }; + }; + }; }; hpm1: usb-pd@3f { @@ -101,6 +237,285 @@ interrupt-parent = <&pinctrl_ap>; interrupts = <60 IRQ_TYPE_LEVEL_LOW>; interrupt-names = "irq"; + + typec1: connector { + compatible = "usb-c-connector"; + label = "USB-C Top Left"; + power-role = "dual"; + data-role = "dual"; + + ports { + #address-cells = <1>; + #size-cells = <0>; + port@0 { + reg = <0>; + typec1_connector_hs: endpoint { + remote-endpoint = <&dwc3_1_hs>; + }; + }; + port@1 { + reg = <1>; + typec1_connector_ss: endpoint { + remote-endpoint = <&atcphy1_typec_lanes>; + }; + }; + }; + }; + }; +}; + +/* USB controllers */ +&dwc3_0 { + ports { + #address-cells = <1>; + #size-cells = <0>; + + port@0 { + reg = <0>; + dwc3_0_hs: endpoint { + remote-endpoint = <&typec0_connector_hs>; + }; + }; + + port@1 { + reg = <1>; + dwc3_0_ss: endpoint { + remote-endpoint = <&atcphy0_usb3>; + }; + }; + }; +}; + +&dwc3_1 { + ports { + #address-cells = <1>; + #size-cells = <0>; + + port@0 { + reg = <0>; + dwc3_1_hs: endpoint { + remote-endpoint = <&typec1_connector_hs>; + }; + }; + + port@1 { + reg = <1>; + dwc3_1_ss: endpoint { + remote-endpoint = <&atcphy1_usb3>; + }; + }; + }; +}; + +&dwc3_2 { + ports { + #address-cells = <1>; + #size-cells = <0>; + + port@0 { + reg = <0>; + dwc3_2_hs: endpoint { + remote-endpoint = <&typec2_connector_hs>; + }; + }; + + port@1 { + reg = <1>; + dwc3_2_ss: endpoint { + remote-endpoint = <&atcphy2_usb3>; + }; + }; + }; +}; + +&dwc3_3 { + ports { + #address-cells = <1>; + #size-cells = <0>; + + port@0 { + reg = <0>; + dwc3_3_hs: endpoint { + remote-endpoint = <&typec3_connector_hs>; + }; + }; + + port@1 { + reg = <1>; + dwc3_3_ss: endpoint { + remote-endpoint = <&atcphy3_usb3>; + }; + }; + }; +}; + +/* USB controllers on die 1 */ +&dwc3_2_die1 { + ports { + #address-cells = <1>; + #size-cells = <0>; + + port@0 { + reg = <0>; + dwc3_6_hs: endpoint { + remote-endpoint = <&typec6_connector_hs>; + }; + }; + + port@1 { + reg = <1>; + dwc3_6_ss: endpoint { + remote-endpoint = <&atcphy6_usb3>; + }; + }; + }; +}; + +&dwc3_3_die1 { + ports { + #address-cells = <1>; + #size-cells = <0>; + + port@0 { + reg = <0>; + dwc3_7_hs: endpoint { + remote-endpoint = <&typec7_connector_hs>; + }; + }; + + port@1 { + reg = <1>; + dwc3_7_ss: endpoint { + remote-endpoint = <&atcphy7_usb3>; + }; + }; + }; +}; + +/* Type-C PHYs */ +&atcphy0 { + ports { + #address-cells = <1>; + #size-cells = <0>; + + port@0 { + reg = <0>; + atcphy0_typec_lanes: endpoint { + remote-endpoint = <&typec0_connector_ss>; + }; + }; + + port@1 { + reg = <1>; + atcphy0_usb3: endpoint { + remote-endpoint = <&dwc3_0_ss>; + }; + }; + }; +}; + +&atcphy1 { + ports { + #address-cells = <1>; + #size-cells = <0>; + + port@0 { + reg = <0>; + atcphy1_typec_lanes: endpoint { + remote-endpoint = <&typec1_connector_ss>; + }; + }; + + port@1 { + reg = <1>; + atcphy1_usb3: endpoint { + remote-endpoint = <&dwc3_1_ss>; + }; + }; + }; +}; + +&atcphy2 { + ports { + #address-cells = <1>; + #size-cells = <0>; + + port@0 { + reg = <0>; + atcphy2_typec_lanes: endpoint { + remote-endpoint = <&typec2_connector_ss>; + }; + }; + + port@1 { + reg = <1>; + atcphy2_usb3: endpoint { + remote-endpoint = <&dwc3_2_ss>; + }; + }; + }; +}; + +&atcphy3 { + ports { + #address-cells = <1>; + #size-cells = <0>; + + port@0 { + reg = <0>; + atcphy3_typec_lanes: endpoint { + remote-endpoint = <&typec3_connector_ss>; + }; + }; + + port@1 { + reg = <1>; + atcphy3_usb3: endpoint { + remote-endpoint = <&dwc3_3_ss>; + }; + }; + }; +}; + +&atcphy2_die1 { + ports { + #address-cells = <1>; + #size-cells = <0>; + + port@0 { + reg = <0>; + atcphy6_typec_lanes: endpoint { + remote-endpoint = <&typec6_connector_ss>; + }; + }; + + port@1 { + reg = <1>; + atcphy6_usb3: endpoint { + remote-endpoint = <&dwc3_6_ss>; + }; + }; + }; +}; + +&atcphy3_die1 { + ports { + #address-cells = <1>; + #size-cells = <0>; + + port@0 { + reg = <0>; + atcphy7_typec_lanes: endpoint { + remote-endpoint = <&typec7_connector_ss>; + }; + }; + + port@1 { + reg = <1>; + atcphy7_usb3: endpoint { + remote-endpoint = <&dwc3_7_ss>; + }; + }; }; }; diff --git a/arch/arm64/boot/dts/apple/t6022-j475d.dts b/arch/arm64/boot/dts/apple/t6022-j475d.dts index 736594544f79b5..31f24bbda9689b 100644 --- a/arch/arm64/boot/dts/apple/t6022-j475d.dts +++ b/arch/arm64/boot/dts/apple/t6022-j475d.dts @@ -16,6 +16,11 @@ / { compatible = "apple,j475d", "apple,t6022", "apple,arm-platform"; model = "Apple Mac Studio (M2 Ultra, 2023)"; + + aliases { + atcphy4 = &atcphy0_die1; + atcphy5 = &atcphy1_die1; + }; }; &framebuffer0 { @@ -31,6 +36,32 @@ status = "okay"; }; +&typec4 { + label = "USB-C Front Right"; +}; + +&typec5 { + label = "USB-C Front Left"; +}; + +/* delete unused USB nodes on die 1 */ +/delete-node/ &dwc3_2_dart_0_die1; +/delete-node/ &dwc3_2_dart_1_die1; +/delete-node/ &dwc3_2_die1; +/delete-node/ &atcphy2_die1; + +/delete-node/ &dwc3_3_dart_0_die1; +/delete-node/ &dwc3_3_dart_1_die1; +/delete-node/ &dwc3_3_die1; +/delete-node/ &atcphy3_die1; + +/* delete unused always-on power-domains on die 1 */ +/delete-node/ &ps_atc2_usb_aon_die1; +/delete-node/ &ps_atc2_usb_die1; + +/delete-node/ &ps_atc3_usb_aon_die1; +/delete-node/ &ps_atc3_usb_die1; + &wifi0 { compatible = "pci14e4,4434"; brcm,board-type = "apple,canary"; diff --git a/arch/arm64/boot/dts/apple/t6022-jxxxd.dtsi b/arch/arm64/boot/dts/apple/t6022-jxxxd.dtsi index 4f7bf2ebfe397d..dc877bd604f827 100644 --- a/arch/arm64/boot/dts/apple/t6022-jxxxd.dtsi +++ b/arch/arm64/boot/dts/apple/t6022-jxxxd.dtsi @@ -25,6 +25,29 @@ interrupt-parent = <&pinctrl_ap>; interrupts = <44 IRQ_TYPE_LEVEL_LOW>; interrupt-names = "irq"; + + typec4: connector { + compatible = "usb-c-connector"; + power-role = "dual"; + data-role = "dual"; + + ports { + #address-cells = <1>; + #size-cells = <0>; + port@0 { + reg = <0>; + typec4_connector_hs: endpoint { + remote-endpoint = <&dwc3_4_hs>; + }; + }; + port@1 { + reg = <1>; + typec4_connector_ss: endpoint { + remote-endpoint = <&atcphy4_typec_lanes>; + }; + }; + }; + }; }; /* front-left */ @@ -34,5 +57,115 @@ interrupt-parent = <&pinctrl_ap>; interrupts = <44 IRQ_TYPE_LEVEL_LOW>; interrupt-names = "irq"; + + typec5: connector { + compatible = "usb-c-connector"; + power-role = "dual"; + data-role = "dual"; + + ports { + #address-cells = <1>; + #size-cells = <0>; + port@0 { + reg = <0>; + typec5_connector_hs: endpoint { + remote-endpoint = <&dwc3_5_hs>; + }; + }; + port@1 { + reg = <1>; + typec5_connector_ss: endpoint { + remote-endpoint = <&atcphy5_typec_lanes>; + }; + }; + }; + }; + }; +}; + + +/* USB controllers on die 1 */ +&dwc3_0_die1 { + ports { + #address-cells = <1>; + #size-cells = <0>; + + port@0 { + reg = <0>; + dwc3_4_hs: endpoint { + remote-endpoint = <&typec4_connector_hs>; + }; + }; + + port@1 { + reg = <1>; + dwc3_4_ss: endpoint { + remote-endpoint = <&atcphy4_usb3>; + }; + }; + }; +}; + +&dwc3_1_die1 { + ports { + #address-cells = <1>; + #size-cells = <0>; + + port@0 { + reg = <0>; + dwc3_5_hs: endpoint { + remote-endpoint = <&typec5_connector_hs>; + }; + }; + + port@1 { + reg = <1>; + dwc3_5_ss: endpoint { + remote-endpoint = <&atcphy5_usb3>; + }; + }; + }; +}; + +/* Type-C PHYs */ +&atcphy0_die1 { + ports { + #address-cells = <1>; + #size-cells = <0>; + + port@0 { + reg = <0>; + atcphy4_typec_lanes: endpoint { + remote-endpoint = <&typec4_connector_ss>; + }; + }; + + port@1 { + reg = <1>; + atcphy4_usb3: endpoint { + remote-endpoint = <&dwc3_4_ss>; + }; + }; + }; +}; + +&atcphy1_die1 { + ports { + #address-cells = <1>; + #size-cells = <0>; + + port@0 { + reg = <0>; + atcphy5_typec_lanes: endpoint { + remote-endpoint = <&typec5_connector_ss>; + }; + }; + + port@1 { + reg = <1>; + atcphy5_usb3: endpoint { + remote-endpoint = <&dwc3_5_ss>; + }; + }; }; }; diff --git a/arch/arm64/boot/dts/apple/t602x-dieX.dtsi b/arch/arm64/boot/dts/apple/t602x-dieX.dtsi index cb07fd82b32e67..ae3d535c5acb37 100644 --- a/arch/arm64/boot/dts/apple/t602x-dieX.dtsi +++ b/arch/arm64/boot/dts/apple/t602x-dieX.dtsi @@ -126,3 +126,215 @@ reg = <0x4 0x4e80000 0 0x4000>; }; + + DIE_NODE(dwc3_0): usb@702280000 { + compatible = "apple,t6020-dwc3", "apple,t8103-dwc3"; + reg = <0x7 0x02280000 0x0 0xcd00>, <0x7 0x0228cd00 0x0 0x3200>; + reg-names = "dwc3-core", "dwc3-apple"; + interrupt-parent = <&aic>; + interrupts = ; + dr_mode = "otg"; + usb-role-switch; + role-switch-default-mode = "host"; + iommus = <&DIE_NODE(dwc3_0_dart_0) 0>, + <&DIE_NODE(dwc3_0_dart_1) 1>; + power-domains = <&DIE_NODE(ps_atc0_usb)>; + resets = <&DIE_NODE(atcphy0)>; + phys = <&DIE_NODE(atcphy0) PHY_TYPE_USB2>, <&DIE_NODE(atcphy0) PHY_TYPE_USB3>; + phy-names = "usb2-phy", "usb3-phy"; + }; + + DIE_NODE(dwc3_0_dart_0): iommu@702f00000 { + compatible = "apple,t6020-dart", "apple,t8110-dart"; + reg = <0x7 0x02f00000 0x0 0x4000>; + interrupt-parent = <&aic>; + interrupts = ; + power-domains = <&DIE_NODE(ps_atc0_usb)>; + #iommu-cells = <1>; + }; + + DIE_NODE(dwc3_0_dart_1): iommu@702f80000 { + compatible = "apple,t6020-dart", "apple,t8110-dart"; + reg = <0x7 0x02f80000 0x0 0x4000>; + interrupt-parent = <&aic>; + interrupts = ; + power-domains = <&DIE_NODE(ps_atc0_usb)>; + #iommu-cells = <1>; + }; + + DIE_NODE(atcphy0): phy@703000000 { + compatible = "apple,t6020-atcphy", "apple,t8103-atcphy"; + reg = <0x7 0x03000000 0x0 0x4c000>, + <0x7 0x03050000 0x0 0x8000>, + <0x7 0x00000000 0x0 0x4000>, + <0x7 0x02a90000 0x0 0x4000>, + <0x7 0x02a84000 0x0 0x4000>; + reg-names = "core", "lpdptx", "axi2af", "usb2phy", + "pipehandler"; + + #phy-cells = <1>; + #reset-cells = <0>; + + orientation-switch; + mode-switch; + power-domains = <&DIE_NODE(ps_atc0_usb)>; + }; + + DIE_NODE(dwc3_1): usb@b02280000 { + compatible = "apple,t6020-dwc3", "apple,t8103-dwc3"; + reg = <0xb 0x02280000 0x0 0xcd00>, <0xb 0x0228cd00 0x0 0x3200>; + reg-names = "dwc3-core", "dwc3-apple"; + interrupt-parent = <&aic>; + interrupts = ; + dr_mode = "otg"; + usb-role-switch; + role-switch-default-mode = "host"; + iommus = <&DIE_NODE(dwc3_1_dart_0) 0>, + <&DIE_NODE(dwc3_1_dart_1) 1>; + power-domains = <&DIE_NODE(ps_atc1_usb)>; + resets = <&DIE_NODE(atcphy1)>; + phys = <&DIE_NODE(atcphy1) PHY_TYPE_USB2>, <&DIE_NODE(atcphy1) PHY_TYPE_USB3>; + phy-names = "usb2-phy", "usb3-phy"; + }; + + DIE_NODE(dwc3_1_dart_0): iommu@b02f00000 { + compatible = "apple,t6020-dart", "apple,t8110-dart"; + reg = <0xb 0x02f00000 0x0 0x4000>; + interrupt-parent = <&aic>; + interrupts = ; + power-domains = <&DIE_NODE(ps_atc1_usb)>; + #iommu-cells = <1>; + }; + + DIE_NODE(dwc3_1_dart_1): iommu@b02f80000 { + compatible = "apple,t6020-dart", "apple,t8110-dart"; + reg = <0xb 0x02f80000 0x0 0x4000>; + interrupt-parent = <&aic>; + interrupts = ; + power-domains = <&DIE_NODE(ps_atc1_usb)>; + #iommu-cells = <1>; + }; + + DIE_NODE(atcphy1): phy@b03000000 { + compatible = "apple,t6020-atcphy", "apple,t8103-atcphy"; + reg = <0xb 0x03000000 0x0 0x4c000>, + <0xb 0x03050000 0x0 0x8000>, + <0xb 0x00000000 0x0 0x4000>, + <0xb 0x02a90000 0x0 0x4000>, + <0xb 0x02a84000 0x0 0x4000>; + reg-names = "core", "lpdptx", "axi2af", "usb2phy", + "pipehandler"; + + #phy-cells = <1>; + #reset-cells = <0>; + + orientation-switch; + mode-switch; + power-domains = <&DIE_NODE(ps_atc1_usb)>; + }; + + DIE_NODE(dwc3_2): usb@f02280000 { + compatible = "apple,t6020-dwc3", "apple,t8103-dwc3"; + reg = <0xf 0x02280000 0x0 0xcd00>, <0xf 0x0228cd00 0x0 0x3200>; + reg-names = "dwc3-core", "dwc3-apple"; + interrupt-parent = <&aic>; + interrupts = ; + dr_mode = "otg"; + usb-role-switch; + role-switch-default-mode = "host"; + iommus = <&DIE_NODE(dwc3_2_dart_0) 0>, + <&DIE_NODE(dwc3_2_dart_1) 1>; + power-domains = <&DIE_NODE(ps_atc2_usb)>; + resets = <&DIE_NODE(atcphy2)>; + phys = <&DIE_NODE(atcphy2) PHY_TYPE_USB2>, <&DIE_NODE(atcphy2) PHY_TYPE_USB3>; + phy-names = "usb2-phy", "usb3-phy"; + }; + + DIE_NODE(dwc3_2_dart_0): iommu@f02f00000 { + compatible = "apple,t6020-dart", "apple,t8110-dart"; + reg = <0xf 0x02f00000 0x0 0x4000>; + interrupt-parent = <&aic>; + interrupts = ; + power-domains = <&DIE_NODE(ps_atc2_usb)>; + #iommu-cells = <1>; + }; + + DIE_NODE(dwc3_2_dart_1): iommu@f02f80000 { + compatible = "apple,t6020-dart", "apple,t8110-dart"; + reg = <0xf 0x02f80000 0x0 0x4000>; + interrupt-parent = <&aic>; + interrupts = ; + power-domains = <&DIE_NODE(ps_atc2_usb)>; + #iommu-cells = <1>; + }; + + DIE_NODE(atcphy2): phy@f03000000 { + compatible = "apple,t6020-atcphy", "apple,t8103-atcphy"; + reg = <0xf 0x03000000 0x0 0x4c000>, + <0xf 0x03050000 0x0 0x8000>, + <0xf 0x00000000 0x0 0x4000>, + <0xf 0x02a90000 0x0 0x4000>, + <0xf 0x02a84000 0x0 0x4000>; + reg-names = "core", "lpdptx", "axi2af", "usb2phy", + "pipehandler"; + + #phy-cells = <1>; + #reset-cells = <0>; + + orientation-switch; + mode-switch; + power-domains = <&DIE_NODE(ps_atc2_usb)>; + }; + + DIE_NODE(dwc3_3): usb@1302280000 { + compatible = "apple,t6020-dwc3", "apple,t8103-dwc3"; + reg = <0x13 0x02280000 0x0 0xcd00>, <0x13 0x0228cd00 0x0 0x3200>; + reg-names = "dwc3-core", "dwc3-apple"; + interrupt-parent = <&aic>; + interrupts = ; + dr_mode = "otg"; + usb-role-switch; + role-switch-default-mode = "host"; + iommus = <&DIE_NODE(dwc3_3_dart_0) 0>, + <&DIE_NODE(dwc3_3_dart_1) 1>; + power-domains = <&DIE_NODE(ps_atc3_usb)>; + resets = <&DIE_NODE(atcphy3)>; + phys = <&DIE_NODE(atcphy3) PHY_TYPE_USB2>, <&DIE_NODE(atcphy3) PHY_TYPE_USB3>; + phy-names = "usb2-phy", "usb3-phy"; + }; + + DIE_NODE(dwc3_3_dart_0): iommu@1302f00000 { + compatible = "apple,t6020-dart", "apple,t8110-dart"; + reg = <0x13 0x02f00000 0x0 0x4000>; + interrupt-parent = <&aic>; + interrupts = ; + power-domains = <&DIE_NODE(ps_atc3_usb)>; + #iommu-cells = <1>; + }; + + DIE_NODE(dwc3_3_dart_1): iommu@1302f80000 { + compatible = "apple,t6020-dart", "apple,t8110-dart"; + reg = <0x13 0x02f80000 0x0 0x4000>; + interrupt-parent = <&aic>; + interrupts = ; + power-domains = <&DIE_NODE(ps_atc3_usb)>; + #iommu-cells = <1>; + }; + + DIE_NODE(atcphy3): phy@1303000000 { + compatible = "apple,t6020-atcphy", "apple,t8103-atcphy"; + reg = <0x13 0x03000000 0x0 0x4c000>, + <0x13 0x03050000 0x0 0x8000>, + <0x13 0x00000000 0x0 0x4000>, + <0x13 0x02a90000 0x0 0x4000>, + <0x13 0x02a84000 0x0 0x4000>; + reg-names = "core", "lpdptx", "axi2af", "usb2phy", + "pipehandler"; + + #phy-cells = <1>; + #reset-cells = <0>; + + orientation-switch; + mode-switch; + power-domains = <&DIE_NODE(ps_atc3_usb)>; + }; From ef6ced7c726db11471035b0a7f481d98d7d5af16 Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Fri, 9 Jan 2026 16:25:43 +0100 Subject: [PATCH 2089/3902] arm64: dts: apple: Add chassis-type property for all Macbooks All Macbook Air and Pro devices are laptops so annotate this as chassis-tpe in the root node. Reviewed-by: Neal Gompa Signed-off-by: Janne Grunau --- arch/arm64/boot/dts/apple/t600x-j314-j316.dtsi | 2 ++ arch/arm64/boot/dts/apple/t8103-j293.dts | 1 + arch/arm64/boot/dts/apple/t8103-j313.dts | 1 + arch/arm64/boot/dts/apple/t8112-j413.dts | 1 + arch/arm64/boot/dts/apple/t8112-j415.dts | 1 + arch/arm64/boot/dts/apple/t8112-j493.dts | 1 + 6 files changed, 7 insertions(+) diff --git a/arch/arm64/boot/dts/apple/t600x-j314-j316.dtsi b/arch/arm64/boot/dts/apple/t600x-j314-j316.dtsi index 6b3b3272308184..fee84f809a9cc3 100644 --- a/arch/arm64/boot/dts/apple/t600x-j314-j316.dtsi +++ b/arch/arm64/boot/dts/apple/t600x-j314-j316.dtsi @@ -12,6 +12,8 @@ #include / { + chassis-type = "laptop"; + aliases { atcphy0 = &atcphy0; atcphy1 = &atcphy1; diff --git a/arch/arm64/boot/dts/apple/t8103-j293.dts b/arch/arm64/boot/dts/apple/t8103-j293.dts index 678f89c3d47fbf..52f63ae7a58dd8 100644 --- a/arch/arm64/boot/dts/apple/t8103-j293.dts +++ b/arch/arm64/boot/dts/apple/t8103-j293.dts @@ -16,6 +16,7 @@ / { compatible = "apple,j293", "apple,t8103", "apple,arm-platform"; model = "Apple MacBook Pro (13-inch, M1, 2020)"; + chassis-type = "laptop"; /* * All of those are used by the bootloader to pass calibration diff --git a/arch/arm64/boot/dts/apple/t8103-j313.dts b/arch/arm64/boot/dts/apple/t8103-j313.dts index bce9b911009e2b..9eb2825d25dcba 100644 --- a/arch/arm64/boot/dts/apple/t8103-j313.dts +++ b/arch/arm64/boot/dts/apple/t8103-j313.dts @@ -16,6 +16,7 @@ / { compatible = "apple,j313", "apple,t8103", "apple,arm-platform"; model = "Apple MacBook Air (M1, 2020)"; + chassis-type = "laptop"; led-controller { compatible = "pwm-leds"; diff --git a/arch/arm64/boot/dts/apple/t8112-j413.dts b/arch/arm64/boot/dts/apple/t8112-j413.dts index 21e81a8899d8d7..1a08a41f369bfe 100644 --- a/arch/arm64/boot/dts/apple/t8112-j413.dts +++ b/arch/arm64/boot/dts/apple/t8112-j413.dts @@ -16,6 +16,7 @@ / { compatible = "apple,j413", "apple,t8112", "apple,arm-platform"; model = "Apple MacBook Air (13-inch, M2, 2022)"; + chassis-type = "laptop"; aliases { bluetooth0 = &bluetooth0; diff --git a/arch/arm64/boot/dts/apple/t8112-j415.dts b/arch/arm64/boot/dts/apple/t8112-j415.dts index b4f72fbafaf104..e37c56d9fb4d66 100644 --- a/arch/arm64/boot/dts/apple/t8112-j415.dts +++ b/arch/arm64/boot/dts/apple/t8112-j415.dts @@ -16,6 +16,7 @@ / { compatible = "apple,j415", "apple,t8112", "apple,arm-platform"; model = "Apple MacBook Air (15-inch, M2, 2023)"; + chassis-type = "laptop"; aliases { bluetooth0 = &bluetooth0; diff --git a/arch/arm64/boot/dts/apple/t8112-j493.dts b/arch/arm64/boot/dts/apple/t8112-j493.dts index f8e442152ff23f..ec116da3e4dd9e 100644 --- a/arch/arm64/boot/dts/apple/t8112-j493.dts +++ b/arch/arm64/boot/dts/apple/t8112-j493.dts @@ -16,6 +16,7 @@ / { compatible = "apple,j493", "apple,t8112", "apple,arm-platform"; model = "Apple MacBook Pro (13-inch, M2, 2022)"; + chassis-type = "laptop"; /* * All of those are used by the bootloader to pass calibration From 4f8f97c8e16cd753e52ab8ab2fc7dadf53169246 Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Fri, 9 Jan 2026 16:25:44 +0100 Subject: [PATCH 2090/3902] arm64: dts: apple: Add chassis-type property for Apple desktop devices Apple's Mac mini and Studio are desktop devices. The SMBIOS has chassis types which might be more accurate like "low profile desktop" or "mini pc" but without clear definition what those are use plain "desktop" as chassis-type in the root node. Reviewed-by: Neal Gompa Signed-off-by: Janne Grunau --- arch/arm64/boot/dts/apple/t600x-j375.dtsi | 2 ++ arch/arm64/boot/dts/apple/t8103-j274.dts | 1 + arch/arm64/boot/dts/apple/t8112-j473.dts | 1 + 3 files changed, 4 insertions(+) diff --git a/arch/arm64/boot/dts/apple/t600x-j375.dtsi b/arch/arm64/boot/dts/apple/t600x-j375.dtsi index a4bb5cbb67a067..8a1494949e4c58 100644 --- a/arch/arm64/boot/dts/apple/t600x-j375.dtsi +++ b/arch/arm64/boot/dts/apple/t600x-j375.dtsi @@ -10,6 +10,8 @@ */ / { + chassis-type = "desktop"; + aliases { atcphy0 = &atcphy0; atcphy1 = &atcphy1; diff --git a/arch/arm64/boot/dts/apple/t8103-j274.dts b/arch/arm64/boot/dts/apple/t8103-j274.dts index 968fe22163d443..52965258200da3 100644 --- a/arch/arm64/boot/dts/apple/t8103-j274.dts +++ b/arch/arm64/boot/dts/apple/t8103-j274.dts @@ -15,6 +15,7 @@ / { compatible = "apple,j274", "apple,t8103", "apple,arm-platform"; model = "Apple Mac mini (M1, 2020)"; + chassis-type = "desktop"; aliases { ethernet0 = ðernet0; diff --git a/arch/arm64/boot/dts/apple/t8112-j473.dts b/arch/arm64/boot/dts/apple/t8112-j473.dts index 0ab7009d2cf7a2..438f972546b813 100644 --- a/arch/arm64/boot/dts/apple/t8112-j473.dts +++ b/arch/arm64/boot/dts/apple/t8112-j473.dts @@ -15,6 +15,7 @@ / { compatible = "apple,j473", "apple,t8112", "apple,arm-platform"; model = "Apple Mac mini (M2, 2023)"; + chassis-type = "desktop"; aliases { ethernet0 = ðernet0; From c17775986ffef65e05e3d73eaaf0f642b6440d01 Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Fri, 9 Jan 2026 16:25:45 +0100 Subject: [PATCH 2091/3902] arm64: dts: apple: Add chassis-type property for Mac Pro The tower and rack mount Mac Pro variants share the same .dts file and are identical except for the chassis. There doesn't appear to be a property in Apple's device tree to distinguish these two devices so use "server" as chassis type which describes both if one doesn't look too carefully. Reviewed-by: Neal Gompa Signed-off-by: Janne Grunau --- arch/arm64/boot/dts/apple/t6022-j180d.dts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/arch/arm64/boot/dts/apple/t6022-j180d.dts b/arch/arm64/boot/dts/apple/t6022-j180d.dts index 741199d456fba4..f76b887429dd27 100644 --- a/arch/arm64/boot/dts/apple/t6022-j180d.dts +++ b/arch/arm64/boot/dts/apple/t6022-j180d.dts @@ -15,6 +15,8 @@ / { compatible = "apple,j180d", "apple,t6022", "apple,arm-platform"; model = "Apple Mac Pro (M2 Ultra, 2023)"; + chassis-type = "server"; + aliases { atcphy0 = &atcphy0; atcphy1 = &atcphy1; From 9b18a6f4b9e9884ea31769993d503cdcb0d98be6 Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Fri, 9 Jan 2026 16:25:46 +0100 Subject: [PATCH 2092/3902] arm64: dts: apple: Add chassis-type property for Apple iMacs Apple iMac (M1, 2021) are all-in-one devices with an integrated display. Reviewed-by: Neal Gompa Signed-off-by: Janne Grunau --- arch/arm64/boot/dts/apple/t8103-j456.dts | 1 + arch/arm64/boot/dts/apple/t8103-j457.dts | 1 + 2 files changed, 2 insertions(+) diff --git a/arch/arm64/boot/dts/apple/t8103-j456.dts b/arch/arm64/boot/dts/apple/t8103-j456.dts index 9983e11cacdf19..090c97bb781b32 100644 --- a/arch/arm64/boot/dts/apple/t8103-j456.dts +++ b/arch/arm64/boot/dts/apple/t8103-j456.dts @@ -15,6 +15,7 @@ / { compatible = "apple,j456", "apple,t8103", "apple,arm-platform"; model = "Apple iMac (24-inch, 4x USB-C, M1, 2021)"; + chassis-type = "all-in-one"; aliases { ethernet0 = ðernet0; diff --git a/arch/arm64/boot/dts/apple/t8103-j457.dts b/arch/arm64/boot/dts/apple/t8103-j457.dts index 6703f4a3d3db4c..ebddde75455c69 100644 --- a/arch/arm64/boot/dts/apple/t8103-j457.dts +++ b/arch/arm64/boot/dts/apple/t8103-j457.dts @@ -15,6 +15,7 @@ / { compatible = "apple,j457", "apple,t8103", "apple,arm-platform"; model = "Apple iMac (24-inch, 2x USB-C, M1, 2021)"; + chassis-type = "all-in-one"; aliases { ethernet0 = ðernet0; From 4394505f7f9e50f4dfdb9d4d590c33c5e553c00f Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Wed, 3 Sep 2025 16:03:11 +0200 Subject: [PATCH 2093/3902] arm64: dts: apple: t8103: Add smc_rtc node Signed-off-by: Janne Grunau --- arch/arm64/boot/dts/apple/t8103.dtsi | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/arch/arm64/boot/dts/apple/t8103.dtsi b/arch/arm64/boot/dts/apple/t8103.dtsi index c48b10ac65efcc..0a7620d2a1f003 100644 --- a/arch/arm64/boot/dts/apple/t8103.dtsi +++ b/arch/arm64/boot/dts/apple/t8103.dtsi @@ -910,6 +910,12 @@ #gpio-cells = <2>; }; + smc_rtc: rtc { + compatible = "apple,smc-rtc"; + nvmem-cells = <&rtc_offset>; + nvmem-cell-names = "rtc_offset"; + }; + smc_reboot: reboot { compatible = "apple,smc-reboot"; nvmem-cells = <&shutdown_flag>, <&boot_stage>, From 592ac0a69c20156e5d2b8a1b74565615b3456add Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Wed, 3 Sep 2025 16:02:26 +0200 Subject: [PATCH 2094/3902] arm64: dts: apple: t600x: Add smc_rtc node Signed-off-by: Janne Grunau --- arch/arm64/boot/dts/apple/t600x-die0.dtsi | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/arch/arm64/boot/dts/apple/t600x-die0.dtsi b/arch/arm64/boot/dts/apple/t600x-die0.dtsi index 3603b276a2abcf..34f30a19d789fe 100644 --- a/arch/arm64/boot/dts/apple/t600x-die0.dtsi +++ b/arch/arm64/boot/dts/apple/t600x-die0.dtsi @@ -37,6 +37,12 @@ #gpio-cells = <2>; }; + smc_rtc: rtc { + compatible = "apple,smc-rtc"; + nvmem-cells = <&rtc_offset>; + nvmem-cell-names = "rtc_offset"; + }; + smc_reboot: reboot { compatible = "apple,smc-reboot"; nvmem-cells = <&shutdown_flag>, <&boot_stage>, From e500cdbd3c815c93fb256fa50212ee13bb69764f Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Wed, 3 Sep 2025 16:03:32 +0200 Subject: [PATCH 2095/3902] arm64: dts: apple: t8112: Add smc_rtc node Signed-off-by: Janne Grunau --- arch/arm64/boot/dts/apple/t8112.dtsi | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/arch/arm64/boot/dts/apple/t8112.dtsi b/arch/arm64/boot/dts/apple/t8112.dtsi index 2d645cdec4a75f..319ef168a25924 100644 --- a/arch/arm64/boot/dts/apple/t8112.dtsi +++ b/arch/arm64/boot/dts/apple/t8112.dtsi @@ -913,6 +913,12 @@ #gpio-cells = <2>; }; + smc_rtc: rtc { + compatible = "apple,smc-rtc"; + nvmem-cells = <&rtc_offset>; + nvmem-cell-names = "rtc_offset"; + }; + smc_reboot: reboot { compatible = "apple,smc-reboot"; nvmem-cells = <&shutdown_flag>, <&boot_stage>, From 254985040f316435ceb4f75ce4086c5ea72aea7d Mon Sep 17 00:00:00 2001 From: Hector Martin Date: Sun, 9 Apr 2023 23:48:38 +0900 Subject: [PATCH 2096/3902] arm64: dts: apple: t6022-j180d: Add audio nodes Signed-off-by: Hector Martin Co-developed-by: Janne Grunau Signed-off-by: Janne Grunau --- arch/arm64/boot/dts/apple/t6022-j180d.dts | 76 +++++++++++++++++++++++ 1 file changed, 76 insertions(+) diff --git a/arch/arm64/boot/dts/apple/t6022-j180d.dts b/arch/arm64/boot/dts/apple/t6022-j180d.dts index f76b887429dd27..7d92505e3fd232 100644 --- a/arch/arm64/boot/dts/apple/t6022-j180d.dts +++ b/arch/arm64/boot/dts/apple/t6022-j180d.dts @@ -521,6 +521,82 @@ }; }; +/* Audio */ +&i2c1 { + status = "okay"; + + speaker_tweeter: codec@38 { + compatible = "ti,sn012776", "ti,tas2764"; + reg = <0x38>; + shutdown-gpios = <&pinctrl_ap 57 GPIO_ACTIVE_HIGH>; + #sound-dai-cells = <0>; + sound-name-prefix = "Tweeter"; + interrupts-extended = <&pinctrl_ap 58 IRQ_TYPE_LEVEL_LOW>; + }; + + speaker_woofer: codec@39 { + compatible = "ti,sn012776", "ti,tas2764"; + reg = <0x39>; + shutdown-gpios = <&pinctrl_ap 57 GPIO_ACTIVE_HIGH>; + #sound-dai-cells = <0>; + sound-name-prefix = "Woofer"; + interrupts-extended = <&pinctrl_ap 58 IRQ_TYPE_LEVEL_LOW>; + }; +}; + +&i2c2 { + status = "okay"; + + jack_codec: codec@4b { + compatible = "cirrus,cs42l84"; + reg = <0x4b>; + reset-gpios = <&pinctrl_nub 8 GPIO_ACTIVE_HIGH>; + #sound-dai-cells = <0>; + interrupts-extended = <&pinctrl_ap 59 IRQ_TYPE_LEVEL_LOW>; + sound-name-prefix = "Jack"; + }; +}; + +&nco_clkref { + clock-frequency = <1068000000>; +}; + +/ { + sound: sound { + compatible = "apple,j180-macaudio", "apple,macaudio"; + model = "Mac Pro J180"; + + dai-link@0 { + link-name = "Speakers"; + /* + * DANGER ZONE: You can blow your speakers! + * + * The drivers are not ready, and unless you are careful + * to attenuate the audio stream, you run the risk of + * blowing your speakers. + */ + status = "disabled"; + cpu { + sound-dai = <&mca 0>; + }; + codec { + sound-dai = <&speaker_woofer>, <&speaker_tweeter>; + }; + }; + + dai-link@1 { + link-name = "Headphone Jack"; + + cpu { + sound-dai = <&mca 2>; + }; + codec { + sound-dai = <&jack_codec>; + }; + }; + }; +}; + /* * Delete unused PCIe nodes, the Mac Pro uses slightly different PCIe * controllers with a single port connected to a PM40100 PCIe switch From 809cb71b31b7ebcfa4961230ae789376d43f4463 Mon Sep 17 00:00:00 2001 From: Sven Peter Date: Tue, 15 Nov 2022 12:09:48 +0100 Subject: [PATCH 2097/3902] arm64: dts: apple: t8103: Add eFuses node and ATC phy fuses to be dropped with asahi-6.18 + 2 Signed-off-by: Sven Peter --- arch/arm64/boot/dts/apple/t8103.dtsi | 141 +++++++++++++++++++++++++++ 1 file changed, 141 insertions(+) diff --git a/arch/arm64/boot/dts/apple/t8103.dtsi b/arch/arm64/boot/dts/apple/t8103.dtsi index 0a7620d2a1f003..84a6ddfe433d76 100644 --- a/arch/arm64/boot/dts/apple/t8103.dtsi +++ b/arch/arm64/boot/dts/apple/t8103.dtsi @@ -1014,6 +1014,107 @@ resets = <&ps_ans2>; }; + efuse@23d2bc000 { + compatible = "apple,t8103-efuses", "apple,efuses"; + reg = <0x2 0x3d2bc000 0x0 0x1000>; + #address-cells = <1>; + #size-cells = <1>; + atcphy0_auspll_rodco_bias_adjust: efuse@430,26 { + reg = <0x430 4>; + bits = <26 3>; + }; + + atcphy0_auspll_rodco_encap: efuse@430,29 { + reg = <0x430 4>; + bits = <29 2>; + }; + + atcphy0_auspll_dtc_vreg_adjust: efuse@430,31 { + reg = <0x430 8>; + bits = <31 3>; + }; + + atcphy0_auspll_fracn_dll_start_capcode: efuse@434,2 { + reg = <0x434 4>; + bits = <2 2>; + }; + + atcphy0_aus_cmn_shm_vreg_trim: efuse@434,4 { + reg = <0x434 4>; + bits = <4 5>; + }; + + atcphy0_cio3pll_dco_coarsebin0: efuse@434,9 { + reg = <0x434 4>; + bits = <9 6>; + }; + + atcphy0_cio3pll_dco_coarsebin1: efuse@434,15 { + reg = <0x434 4>; + bits = <15 6>; + }; + + atcphy0_cio3pll_dll_start_capcode: efuse@434,21 { + reg = <0x434 4>; + bits = <21 2>; + }; + + atcphy0_cio3pll_dtc_vreg_adjust: efuse@434,23 { + reg = <0x434 0x4>; + bits = <23 3>; + }; + + atcphy1_auspll_rodco_bias_adjust: efuse@438,4 { + reg = <0x438 4>; + bits = <4 3>; + }; + + atcphy1_auspll_rodco_encap: efuse@438,7 { + reg = <0x438 4>; + bits = <7 2>; + }; + + atcphy1_auspll_dtc_vreg_adjust: efuse@438,9 { + reg = <0x438 4>; + bits = <9 3>; + }; + + atcphy1_auspll_fracn_dll_start_capcode: efuse@438,12 { + reg = <0x438 4>; + bits = <12 2>; + }; + + atcphy1_aus_cmn_shm_vreg_trim: efuse@438,14 { + reg = <0x438 4>; + bits = <14 5>; + }; + + atcphy1_cio3pll_dco_coarsebin0: efuse@438,19 { + reg = <0x438 4>; + bits = <19 6>; + }; + + atcphy1_cio3pll_dco_coarsebin1: efuse@438,25 { + reg = <0x438 4>; + bits = <25 6>; + }; + + atcphy1_cio3pll_dll_start_capcode: efuse@438,31 { + reg = <0x438 4>; + bits = <31 1>; + }; + + atcphy1_cio3pll_dll_start_capcode_workaround: efuse@43c,0 { + reg = <0x43c 0x4>; + bits = <0 1>; + }; + + atcphy1_cio3pll_dtc_vreg_adjust: efuse@43c,1 { + reg = <0x43c 0x4>; + bits = <1 3>; + }; + }; + dwc3_0: usb@382280000 { compatible = "apple,t8103-dwc3"; reg = <0x3 0x82280000 0x0 0xcd00>, <0x3 0x8228cd00 0x0 0x3200>; @@ -1061,6 +1162,25 @@ #phy-cells = <1>; #reset-cells = <0>; + nvmem-cells = <&atcphy0_aus_cmn_shm_vreg_trim>, + <&atcphy0_auspll_rodco_encap>, + <&atcphy0_auspll_rodco_bias_adjust>, + <&atcphy0_auspll_fracn_dll_start_capcode>, + <&atcphy0_auspll_dtc_vreg_adjust>, + <&atcphy0_cio3pll_dco_coarsebin0>, + <&atcphy0_cio3pll_dco_coarsebin1>, + <&atcphy0_cio3pll_dll_start_capcode>, + <&atcphy0_cio3pll_dtc_vreg_adjust>; + nvmem-cell-names = "aus_cmn_shm_vreg_trim", + "auspll_rodco_encap", + "auspll_rodco_bias_adjust", + "auspll_fracn_dll_start_capcode", + "auspll_dtc_vreg_adjust", + "cio3pll_dco_coarsebin0", + "cio3pll_dco_coarsebin1", + "cio3pll_dll_start_capcode", + "cio3pll_dtc_vreg_adjust"; + orientation-switch; mode-switch; power-domains = <&ps_atc0_usb>; @@ -1110,6 +1230,27 @@ reg-names = "core", "lpdptx", "axi2af", "usb2phy", "pipehandler"; + nvmem-cells = <&atcphy1_aus_cmn_shm_vreg_trim>, + <&atcphy1_auspll_rodco_encap>, + <&atcphy1_auspll_rodco_bias_adjust>, + <&atcphy1_auspll_fracn_dll_start_capcode>, + <&atcphy1_auspll_dtc_vreg_adjust>, + <&atcphy1_cio3pll_dco_coarsebin0>, + <&atcphy1_cio3pll_dco_coarsebin1>, + <&atcphy1_cio3pll_dll_start_capcode>, + <&atcphy1_cio3pll_dtc_vreg_adjust>, + <&atcphy1_cio3pll_dll_start_capcode_workaround>; + nvmem-cell-names = "aus_cmn_shm_vreg_trim", + "auspll_rodco_encap", + "auspll_rodco_bias_adjust", + "auspll_fracn_dll_start_capcode", + "auspll_dtc_vreg_adjust", + "cio3pll_dco_coarsebin0", + "cio3pll_dco_coarsebin1", + "cio3pll_dll_start_capcode", + "cio3pll_dtc_vreg_adjust", + "cio3pll_dll_start_capcode_workaround"; + #phy-cells = <1>; #reset-cells = <0>; From caf684c8c61de7a1c9ee80e05e4276064886d029 Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Thu, 4 Sep 2025 11:01:38 +0200 Subject: [PATCH 2098/3902] arm64: dts: apple: t8112: Add eFuses node and ATC phy fuses to be dropped with asahi-6.18 + 2 Signed-off-by: Janne Grunau --- arch/arm64/boot/dts/apple/t8112.dtsi | 135 +++++++++++++++++++++++++++ 1 file changed, 135 insertions(+) diff --git a/arch/arm64/boot/dts/apple/t8112.dtsi b/arch/arm64/boot/dts/apple/t8112.dtsi index 319ef168a25924..516c5d6ecec370 100644 --- a/arch/arm64/boot/dts/apple/t8112.dtsi +++ b/arch/arm64/boot/dts/apple/t8112.dtsi @@ -843,6 +843,103 @@ interrupts = ; }; + efuse@23d2c8000 { + compatible = "apple,t8112-efuses", "apple,efuses"; + reg = <0x2 0x3d2c8000 0x0 0x1000>; + #address-cells = <1>; + #size-cells = <1>; + + atcphy0_auspll_rodco_bias_adjust: efuse@480,20 { + reg = <0x480 4>; + bits = <20 3>; + }; + + atcphy0_auspll_rodco_encap: efuse@480,23 { + reg = <0x480 4>; + bits = <23 2>; + }; + + atcphy0_auspll_dtc_vreg_adjust: efuse@480,25 { + reg = <0x480 4>; + bits = <25 3>; + }; + + atcphy0_auspll_fracn_dll_start_capcode: efuse@480,28 { + reg = <0x480 4>; + bits = <28 2>; + }; + + atcphy0_aus_cmn_shm_vreg_trim: efuse@480,30 { + reg = <0x480 8>; + bits = <30 5>; + }; + + atcphy0_cio3pll_dco_coarsebin0: efuse@484,3 { + reg = <0x484 4>; + bits = <3 6>; + }; + + atcphy0_cio3pll_dco_coarsebin1: efuse@484,9 { + reg = <0x484 4>; + bits = <9 6>; + }; + + atcphy0_cio3pll_dll_start_capcode: efuse@484,15 { + reg = <0x484 4>; + bits = <15 2>; + }; + + atcphy0_cio3pll_dtc_vreg_adjust: efuse@484,17 { + reg = <0x484 0x4>; + bits = <17 3>; + }; + + atcphy1_auspll_rodco_bias_adjust: efuse@484,30 { + reg = <0x484 8>; + bits = <30 3>; + }; + + atcphy1_auspll_rodco_encap: efuse@488,1 { + reg = <0x488 8>; + bits = <1 2>; + }; + + atcphy1_auspll_dtc_vreg_adjust: efuse@488,3 { + reg = <0x488 4>; + bits = <3 3>; + }; + + atcphy1_auspll_fracn_dll_start_capcode: efuse@488,6 { + reg = <0x488 4>; + bits = <6 2>; + }; + + atcphy1_aus_cmn_shm_vreg_trim: efuse@488,8 { + reg = <0x488 4>; + bits = <8 5>; + }; + + atcphy1_cio3pll_dco_coarsebin0: efuse@488,13 { + reg = <0x488 4>; + bits = <13 6>; + }; + + atcphy1_cio3pll_dco_coarsebin1: efuse@488,19 { + reg = <0x488 4>; + bits = <19 6>; + }; + + atcphy1_cio3pll_dll_start_capcode: efuse@488,25 { + reg = <0x488 4>; + bits = <25 2>; + }; + + atcphy1_cio3pll_dtc_vreg_adjust: efuse@488,27 { + reg = <0x488 0x4>; + bits = <27 3>; + }; + }; + nub_spmi: spmi@23d714000 { compatible = "apple,t8112-spmi", "apple,spmi"; reg = <0x2 0x3d714000 0x0 0x100>; @@ -1064,6 +1161,25 @@ #phy-cells = <1>; #reset-cells = <0>; + nvmem-cells = <&atcphy0_aus_cmn_shm_vreg_trim>, + <&atcphy0_auspll_rodco_encap>, + <&atcphy0_auspll_rodco_bias_adjust>, + <&atcphy0_auspll_fracn_dll_start_capcode>, + <&atcphy0_auspll_dtc_vreg_adjust>, + <&atcphy0_cio3pll_dco_coarsebin0>, + <&atcphy0_cio3pll_dco_coarsebin1>, + <&atcphy0_cio3pll_dll_start_capcode>, + <&atcphy0_cio3pll_dtc_vreg_adjust>; + nvmem-cell-names = "aus_cmn_shm_vreg_trim", + "auspll_rodco_encap", + "auspll_rodco_bias_adjust", + "auspll_fracn_dll_start_capcode", + "auspll_dtc_vreg_adjust", + "cio3pll_dco_coarsebin0", + "cio3pll_dco_coarsebin1", + "cio3pll_dll_start_capcode", + "cio3pll_dtc_vreg_adjust"; + orientation-switch; mode-switch; power-domains = <&ps_atc0_usb>; @@ -1116,6 +1232,25 @@ #phy-cells = <1>; #reset-cells = <0>; + nvmem-cells = <&atcphy1_aus_cmn_shm_vreg_trim>, + <&atcphy1_auspll_rodco_encap>, + <&atcphy1_auspll_rodco_bias_adjust>, + <&atcphy1_auspll_fracn_dll_start_capcode>, + <&atcphy1_auspll_dtc_vreg_adjust>, + <&atcphy1_cio3pll_dco_coarsebin0>, + <&atcphy1_cio3pll_dco_coarsebin1>, + <&atcphy1_cio3pll_dll_start_capcode>, + <&atcphy1_cio3pll_dtc_vreg_adjust>; + nvmem-cell-names = "aus_cmn_shm_vreg_trim", + "auspll_rodco_encap", + "auspll_rodco_bias_adjust", + "auspll_fracn_dll_start_capcode", + "auspll_dtc_vreg_adjust", + "cio3pll_dco_coarsebin0", + "cio3pll_dco_coarsebin1", + "cio3pll_dll_start_capcode", + "cio3pll_dtc_vreg_adjust"; + orientation-switch; mode-switch; power-domains = <&ps_atc1_usb>; From 93c4b4677cd75d45fda1de0e515e66e51fb2e47c Mon Sep 17 00:00:00 2001 From: R Date: Tue, 15 Nov 2022 12:09:52 +0100 Subject: [PATCH 2099/3902] arm64: dts: apple: t6000: Add eFuses node and ATC phy fuses to be dropped with asahi-6.18 + 2 Signed-off-by: R --- arch/arm64/boot/dts/apple/t600x-dieX.dtsi | 263 ++++++++++++++++++++++ 1 file changed, 263 insertions(+) diff --git a/arch/arm64/boot/dts/apple/t600x-dieX.dtsi b/arch/arm64/boot/dts/apple/t600x-dieX.dtsi index 9676d5127039b7..cd64fe0405d0b6 100644 --- a/arch/arm64/boot/dts/apple/t600x-dieX.dtsi +++ b/arch/arm64/boot/dts/apple/t600x-dieX.dtsi @@ -74,6 +74,193 @@ reg = <0x2 0x92280000 0 0x4000>; }; + DIE_NODE(efuse): efuse@2922bc000 { + compatible = "apple,t6000-efuses", "apple,efuses"; + reg = <0x2 0x922bc000 0x0 0x2000>; + #address-cells = <1>; + #size-cells = <1>; + + DIE_NODE(atcphy0_auspll_rodco_bias_adjust): efuse@a10,22 { + reg = <0xa10 4>; + bits = <22 3>; + }; + + DIE_NODE(atcphy0_auspll_rodco_encap): efuse@a10,25 { + reg = <0xa10 4>; + bits = <25 2>; + }; + + DIE_NODE(atcphy0_auspll_dtc_vreg_adjust): efuse@a10,27 { + reg = <0xa10 4>; + bits = <27 3>; + }; + + DIE_NODE(atcphy0_auspll_fracn_dll_start_capcode): efuse@a10,30 { + reg = <0xa10 4>; + bits = <30 2>; + }; + + DIE_NODE(atcphy0_aus_cmn_shm_vreg_trim): efuse@a14,0 { + reg = <0xa14 4>; + bits = <0 5>; + }; + + DIE_NODE(atcphy0_cio3pll_dco_coarsebin0): efuse@a14,5 { + reg = <0xa14 4>; + bits = <5 6>; + }; + + DIE_NODE(atcphy0_cio3pll_dco_coarsebin1): efuse@a14,11 { + reg = <0xa14 4>; + bits = <11 6>; + }; + + DIE_NODE(atcphy0_cio3pll_dll_start_capcode): efuse@a14,17 { + reg = <0xa14 4>; + bits = <17 2>; + }; + + DIE_NODE(atcphy0_cio3pll_dtc_vreg_adjust): efuse@a14,19 { + reg = <0xa14 4>; + bits = <19 3>; + }; + + DIE_NODE(atcphy1_auspll_rodco_bias_adjust): efuse@a18,0 { + reg = <0xa18 4>; + bits = <0 3>; + }; + + DIE_NODE(atcphy1_auspll_rodco_encap): efuse@a18,3 { + reg = <0xa18 4>; + bits = <3 2>; + }; + + DIE_NODE(atcphy1_auspll_dtc_vreg_adjust): efuse@a18,5 { + reg = <0xa18 4>; + bits = <5 3>; + }; + + DIE_NODE(atcphy1_auspll_fracn_dll_start_capcode): efuse@a18,8 { + reg = <0xa18 4>; + bits = <8 2>; + }; + + DIE_NODE(atcphy1_aus_cmn_shm_vreg_trim): efuse@a18,10 { + reg = <0xa18 4>; + bits = <10 5>; + }; + + DIE_NODE(atcphy1_cio3pll_dco_coarsebin0): efuse@a18,15 { + reg = <0xa18 4>; + bits = <15 6>; + }; + + DIE_NODE(atcphy1_cio3pll_dco_coarsebin1): efuse@a18,21 { + reg = <0xa18 4>; + bits = <21 6>; + }; + + DIE_NODE(atcphy1_cio3pll_dll_start_capcode): efuse@a18,27 { + reg = <0xa18 4>; + bits = <27 2>; + }; + + DIE_NODE(atcphy1_cio3pll_dtc_vreg_adjust): efuse@a18,29 { + reg = <0xa18 4>; + bits = <29 3>; + }; + + DIE_NODE(atcphy2_auspll_rodco_bias_adjust): efuse@a1c,10 { + reg = <0xa1c 4>; + bits = <10 3>; + }; + + DIE_NODE(atcphy2_auspll_rodco_encap): efuse@a1c,13 { + reg = <0xa1c 4>; + bits = <13 2>; + }; + + DIE_NODE(atcphy2_auspll_dtc_vreg_adjust): efuse@a1c,15 { + reg = <0xa1c 4>; + bits = <15 3>; + }; + + DIE_NODE(atcphy2_auspll_fracn_dll_start_capcode): efuse@a1c,18 { + reg = <0xa1c 4>; + bits = <18 2>; + }; + + DIE_NODE(atcphy2_aus_cmn_shm_vreg_trim): efuse@a1c,20 { + reg = <0xa1c 4>; + bits = <20 5>; + }; + + DIE_NODE(atcphy2_cio3pll_dco_coarsebin0): efuse@a1c,25 { + reg = <0xa1c 4>; + bits = <25 6>; + }; + + DIE_NODE(atcphy2_cio3pll_dco_coarsebin1): efuse@a1c,31 { + reg = <0xa1c 8>; + bits = <31 6>; + }; + + DIE_NODE(atcphy2_cio3pll_dll_start_capcode): efuse@a20,5 { + reg = <0xa20 4>; + bits = <5 2>; + }; + + DIE_NODE(atcphy2_cio3pll_dtc_vreg_adjust): efuse@a20,7 { + reg = <0xa20 4>; + bits = <7 3>; + }; + + DIE_NODE(atcphy3_auspll_rodco_bias_adjust): efuse@a20,20 { + reg = <0xa20 4>; + bits = <20 3>; + }; + + DIE_NODE(atcphy3_auspll_rodco_encap): efuse@a20,23 { + reg = <0xa20 4>; + bits = <23 2>; + }; + + DIE_NODE(atcphy3_auspll_dtc_vreg_adjust): efuse@a20,25 { + reg = <0xa20 4>; + bits = <25 3>; + }; + + DIE_NODE(atcphy3_auspll_fracn_dll_start_capcode): efuse@a20,28 { + reg = <0xa20 4>; + bits = <28 2>; + }; + + DIE_NODE(atcphy3_aus_cmn_shm_vreg_trim): efuse@a20,30 { + reg = <0xa20 8>; + bits = <30 5>; + }; + + DIE_NODE(atcphy3_cio3pll_dco_coarsebin0): efuse@a24,3 { + reg = <0xa24 4>; + bits = <3 6>; + }; + + DIE_NODE(atcphy3_cio3pll_dco_coarsebin1): efuse@a24,9 { + reg = <0xa24 4>; + bits = <9 6>; + }; + + DIE_NODE(atcphy3_cio3pll_dll_start_capcode): efuse@a24,15 { + reg = <0xa24 4>; + bits = <15 2>; + }; + + DIE_NODE(atcphy3_cio3pll_dtc_vreg_adjust): efuse@a24,17 { + reg = <0xa24 4>; + bits = <17 3>; + }; + }; + DIE_NODE(pinctrl_aop): pinctrl@293820000 { compatible = "apple,t6000-pinctrl", "apple,pinctrl"; reg = <0x2 0x93820000 0x0 0x4000>; @@ -168,6 +355,25 @@ #phy-cells = <1>; #reset-cells = <0>; + nvmem-cells = <&DIE_NODE(atcphy0_aus_cmn_shm_vreg_trim)>, + <&DIE_NODE(atcphy0_auspll_rodco_encap)>, + <&DIE_NODE(atcphy0_auspll_rodco_bias_adjust)>, + <&DIE_NODE(atcphy0_auspll_fracn_dll_start_capcode)>, + <&DIE_NODE(atcphy0_auspll_dtc_vreg_adjust)>, + <&DIE_NODE(atcphy0_cio3pll_dco_coarsebin0)>, + <&DIE_NODE(atcphy0_cio3pll_dco_coarsebin1)>, + <&DIE_NODE(atcphy0_cio3pll_dll_start_capcode)>, + <&DIE_NODE(atcphy0_cio3pll_dtc_vreg_adjust)>; + nvmem-cell-names = "aus_cmn_shm_vreg_trim", + "auspll_rodco_encap", + "auspll_rodco_bias_adjust", + "auspll_fracn_dll_start_capcode", + "auspll_dtc_vreg_adjust", + "cio3pll_dco_coarsebin0", + "cio3pll_dco_coarsebin1", + "cio3pll_dll_start_capcode", + "cio3pll_dtc_vreg_adjust"; + orientation-switch; mode-switch; power-domains = <&DIE_NODE(ps_atc0_usb)>; @@ -221,6 +427,25 @@ #phy-cells = <1>; #reset-cells = <0>; + nvmem-cells = <&DIE_NODE(atcphy1_aus_cmn_shm_vreg_trim)>, + <&DIE_NODE(atcphy1_auspll_rodco_encap)>, + <&DIE_NODE(atcphy1_auspll_rodco_bias_adjust)>, + <&DIE_NODE(atcphy1_auspll_fracn_dll_start_capcode)>, + <&DIE_NODE(atcphy1_auspll_dtc_vreg_adjust)>, + <&DIE_NODE(atcphy1_cio3pll_dco_coarsebin0)>, + <&DIE_NODE(atcphy1_cio3pll_dco_coarsebin1)>, + <&DIE_NODE(atcphy1_cio3pll_dll_start_capcode)>, + <&DIE_NODE(atcphy1_cio3pll_dtc_vreg_adjust)>; + nvmem-cell-names = "aus_cmn_shm_vreg_trim", + "auspll_rodco_encap", + "auspll_rodco_bias_adjust", + "auspll_fracn_dll_start_capcode", + "auspll_dtc_vreg_adjust", + "cio3pll_dco_coarsebin0", + "cio3pll_dco_coarsebin1", + "cio3pll_dll_start_capcode", + "cio3pll_dtc_vreg_adjust"; + orientation-switch; mode-switch; power-domains = <&DIE_NODE(ps_atc1_usb)>; @@ -274,6 +499,25 @@ #phy-cells = <1>; #reset-cells = <0>; + nvmem-cells = <&DIE_NODE(atcphy2_aus_cmn_shm_vreg_trim)>, + <&DIE_NODE(atcphy2_auspll_rodco_encap)>, + <&DIE_NODE(atcphy2_auspll_rodco_bias_adjust)>, + <&DIE_NODE(atcphy2_auspll_fracn_dll_start_capcode)>, + <&DIE_NODE(atcphy2_auspll_dtc_vreg_adjust)>, + <&DIE_NODE(atcphy2_cio3pll_dco_coarsebin0)>, + <&DIE_NODE(atcphy2_cio3pll_dco_coarsebin1)>, + <&DIE_NODE(atcphy2_cio3pll_dll_start_capcode)>, + <&DIE_NODE(atcphy2_cio3pll_dtc_vreg_adjust)>; + nvmem-cell-names = "aus_cmn_shm_vreg_trim", + "auspll_rodco_encap", + "auspll_rodco_bias_adjust", + "auspll_fracn_dll_start_capcode", + "auspll_dtc_vreg_adjust", + "cio3pll_dco_coarsebin0", + "cio3pll_dco_coarsebin1", + "cio3pll_dll_start_capcode", + "cio3pll_dtc_vreg_adjust"; + orientation-switch; mode-switch; power-domains = <&DIE_NODE(ps_atc2_usb)>; @@ -327,6 +571,25 @@ #phy-cells = <1>; #reset-cells = <0>; + nvmem-cells = <&DIE_NODE(atcphy3_aus_cmn_shm_vreg_trim)>, + <&DIE_NODE(atcphy3_auspll_rodco_encap)>, + <&DIE_NODE(atcphy3_auspll_rodco_bias_adjust)>, + <&DIE_NODE(atcphy3_auspll_fracn_dll_start_capcode)>, + <&DIE_NODE(atcphy3_auspll_dtc_vreg_adjust)>, + <&DIE_NODE(atcphy3_cio3pll_dco_coarsebin0)>, + <&DIE_NODE(atcphy3_cio3pll_dco_coarsebin1)>, + <&DIE_NODE(atcphy3_cio3pll_dll_start_capcode)>, + <&DIE_NODE(atcphy3_cio3pll_dtc_vreg_adjust)>; + nvmem-cell-names = "aus_cmn_shm_vreg_trim", + "auspll_rodco_encap", + "auspll_rodco_bias_adjust", + "auspll_fracn_dll_start_capcode", + "auspll_dtc_vreg_adjust", + "cio3pll_dco_coarsebin0", + "cio3pll_dco_coarsebin1", + "cio3pll_dll_start_capcode", + "cio3pll_dtc_vreg_adjust"; + orientation-switch; mode-switch; power-domains = <&DIE_NODE(ps_atc3_usb)>; From 023dc67494860a60cb3b3272acac75b0a63a2494 Mon Sep 17 00:00:00 2001 From: R Date: Tue, 15 Nov 2022 12:09:52 +0100 Subject: [PATCH 2100/3902] arm64: dts: apple: t602x: Add eFuses node and ATC phy fuses to be dropped with asahi-6.18 + 2 Signed-off-by: R --- arch/arm64/boot/dts/apple/t602x-dieX.dtsi | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/arch/arm64/boot/dts/apple/t602x-dieX.dtsi b/arch/arm64/boot/dts/apple/t602x-dieX.dtsi index ae3d535c5acb37..1446e9e2c29c37 100644 --- a/arch/arm64/boot/dts/apple/t602x-dieX.dtsi +++ b/arch/arm64/boot/dts/apple/t602x-dieX.dtsi @@ -73,6 +73,13 @@ reg = <0x2 0x9e280000 0 0x4000>; }; + DIE_NODE(efuse): efuse@29e2cc000 { + compatible = "apple,t6020-efuses", "apple,efuses"; + reg = <0x2 0x9e2cc000 0x0 0x2000>; + #address-cells = <1>; + #size-cells = <1>; + }; + DIE_NODE(pinctrl_aop): pinctrl@2a6820000 { compatible = "apple,t6020-pinctrl", "apple,t8103-pinctrl"; reg = <0x2 0xa6820000 0x0 0x4000>; From 9d9ad66d43dfec99b833a6271bda8ba4c044f80e Mon Sep 17 00:00:00 2001 From: Hector Martin Date: Sun, 6 Feb 2022 21:22:29 +0900 Subject: [PATCH 2101/3902] arm64: dts: apple: t8103: Add PCI power enable GPIOs t8103: - WLAN (SMC PMU GPIO #13) Signed-off-by: Hector Martin --- arch/arm64/boot/dts/apple/t8103-jxxx.dtsi | 1 + 1 file changed, 1 insertion(+) diff --git a/arch/arm64/boot/dts/apple/t8103-jxxx.dtsi b/arch/arm64/boot/dts/apple/t8103-jxxx.dtsi index 686fb1dd215d2d..96121fdb8468c4 100644 --- a/arch/arm64/boot/dts/apple/t8103-jxxx.dtsi +++ b/arch/arm64/boot/dts/apple/t8103-jxxx.dtsi @@ -205,6 +205,7 @@ */ &port00 { bus-range = <1 1>; + pwren-gpios = <&smc_gpio 13 GPIO_ACTIVE_HIGH>; wifi0: wifi@0,0 { compatible = "pci14e4,4425"; reg = <0x10000 0x0 0x0 0x0 0x0>; From 8d664fac113bb4cd7bea658abf6e6f78af0e6561 Mon Sep 17 00:00:00 2001 From: Hector Martin Date: Sun, 6 Feb 2022 21:22:29 +0900 Subject: [PATCH 2102/3902] arm64: dts: apple: t600x: Add PCI power enable GPIOs - WLAN (SMC PMU GPIO #13) - SD (SMC PMU GPIO #26) Signed-off-by: Hector Martin --- arch/arm64/boot/dts/apple/t600x-j314-j316.dtsi | 2 ++ arch/arm64/boot/dts/apple/t600x-j375.dtsi | 3 +++ 2 files changed, 5 insertions(+) diff --git a/arch/arm64/boot/dts/apple/t600x-j314-j316.dtsi b/arch/arm64/boot/dts/apple/t600x-j314-j316.dtsi index fee84f809a9cc3..acd43bd86a4072 100644 --- a/arch/arm64/boot/dts/apple/t600x-j314-j316.dtsi +++ b/arch/arm64/boot/dts/apple/t600x-j314-j316.dtsi @@ -177,6 +177,7 @@ &port00 { /* WLAN */ bus-range = <1 1>; + pwren-gpios = <&smc_gpio 13 GPIO_ACTIVE_HIGH>; wifi0: wifi@0,0 { compatible = "pci14e4,4433"; reg = <0x10000 0x0 0x0 0x0 0x0>; @@ -196,6 +197,7 @@ &port01 { /* SD card reader */ bus-range = <2 2>; + pwren-gpios = <&smc_gpio 26 GPIO_ACTIVE_HIGH>; sdhci0: mmc@0,0 { compatible = "pci17a0,9755"; reg = <0x20000 0x0 0x0 0x0 0x0>; diff --git a/arch/arm64/boot/dts/apple/t600x-j375.dtsi b/arch/arm64/boot/dts/apple/t600x-j375.dtsi index 8a1494949e4c58..d2c8977bc01532 100644 --- a/arch/arm64/boot/dts/apple/t600x-j375.dtsi +++ b/arch/arm64/boot/dts/apple/t600x-j375.dtsi @@ -357,6 +357,7 @@ &port00 { /* WLAN */ bus-range = <1 1>; + pwren-gpios = <&smc_gpio 13 GPIO_ACTIVE_HIGH>; wifi0: wifi@0,0 { compatible = "pci14e4,4433"; reg = <0x10000 0x0 0x0 0x0 0x0>; @@ -376,6 +377,7 @@ &port01 { /* SD card reader */ bus-range = <2 2>; + pwren-gpios = <&smc_gpio 26 GPIO_ACTIVE_HIGH>; sdhci0: mmc@0,0 { compatible = "pci17a0,9755"; reg = <0x20000 0x0 0x0 0x0 0x0>; @@ -398,6 +400,7 @@ &port03 { /* USB xHCI */ bus-range = <4 4>; + pwren-gpios = <&smc_gpio 20 GPIO_ACTIVE_HIGH>; status = "okay"; }; From 26b990105e94dbdf1d3a2e226df114e605ea6c99 Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Tue, 14 Feb 2023 10:07:49 +0100 Subject: [PATCH 2103/3902] arm64: dts: apple: t8112-j473: Add wlan/bt PCIe device nodes Signed-off-by: Janne Grunau --- arch/arm64/boot/dts/apple/t8112-j473.dts | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/arch/arm64/boot/dts/apple/t8112-j473.dts b/arch/arm64/boot/dts/apple/t8112-j473.dts index 438f972546b813..8c2fba8a1b83d0 100644 --- a/arch/arm64/boot/dts/apple/t8112-j473.dts +++ b/arch/arm64/boot/dts/apple/t8112-j473.dts @@ -18,7 +18,9 @@ chassis-type = "desktop"; aliases { + bluetooth0 = &bluetooth0; ethernet0 = ðernet0; + wifi0 = &wifi0; }; }; @@ -48,6 +50,22 @@ */ &port00 { bus-range = <1 1>; + wifi0: wifi@0,0 { + compatible = "pci14e4,4434"; + reg = <0x10000 0x0 0x0 0x0 0x0>; + /* To be filled by the loader */ + local-mac-address = [00 10 18 00 00 10]; + apple,antenna-sku = "XX"; + brcm,board-type = "apple,miyake"; + }; + + bluetooth0: bluetooth@0,1 { + compatible = "pci14e4,5f72"; + reg = <0x10100 0x0 0x0 0x0 0x0>; + /* To be filled by the loader */ + local-bd-address = [00 00 00 00 00 00]; + brcm,board-type = "apple,miyake"; + }; }; &port01 { From a2171d48c60dee16367c3d97a8c12a604ae6dbf8 Mon Sep 17 00:00:00 2001 From: Hector Martin Date: Fri, 4 Feb 2022 12:59:39 +0900 Subject: [PATCH 2104/3902] arm64: dts: apple: t8112: Add PCI power enable GPIOs Signed-off-by: Hector Martin --- arch/arm64/boot/dts/apple/t8112-j413.dts | 1 + arch/arm64/boot/dts/apple/t8112-j415.dts | 1 + arch/arm64/boot/dts/apple/t8112-j473.dts | 2 ++ arch/arm64/boot/dts/apple/t8112-j493.dts | 1 + 4 files changed, 5 insertions(+) diff --git a/arch/arm64/boot/dts/apple/t8112-j413.dts b/arch/arm64/boot/dts/apple/t8112-j413.dts index 1a08a41f369bfe..67ee47d3818cad 100644 --- a/arch/arm64/boot/dts/apple/t8112-j413.dts +++ b/arch/arm64/boot/dts/apple/t8112-j413.dts @@ -43,6 +43,7 @@ */ &port00 { bus-range = <1 1>; + pwren-gpios = <&smc_gpio 13 GPIO_ACTIVE_HIGH>; wifi0: wifi@0,0 { compatible = "pci14e4,4433"; reg = <0x10000 0x0 0x0 0x0 0x0>; diff --git a/arch/arm64/boot/dts/apple/t8112-j415.dts b/arch/arm64/boot/dts/apple/t8112-j415.dts index e37c56d9fb4d66..4f146043bca2d3 100644 --- a/arch/arm64/boot/dts/apple/t8112-j415.dts +++ b/arch/arm64/boot/dts/apple/t8112-j415.dts @@ -43,6 +43,7 @@ */ &port00 { bus-range = <1 1>; + pwren-gpios = <&smc_gpio 13 GPIO_ACTIVE_HIGH>; wifi0: wifi@0,0 { compatible = "pci14e4,4433"; reg = <0x10000 0x0 0x0 0x0 0x0>; diff --git a/arch/arm64/boot/dts/apple/t8112-j473.dts b/arch/arm64/boot/dts/apple/t8112-j473.dts index 8c2fba8a1b83d0..320178e7ddfe8b 100644 --- a/arch/arm64/boot/dts/apple/t8112-j473.dts +++ b/arch/arm64/boot/dts/apple/t8112-j473.dts @@ -50,6 +50,7 @@ */ &port00 { bus-range = <1 1>; + pwren-gpios = <&smc_gpio 13 GPIO_ACTIVE_HIGH>; wifi0: wifi@0,0 { compatible = "pci14e4,4434"; reg = <0x10000 0x0 0x0 0x0 0x0>; @@ -70,6 +71,7 @@ &port01 { bus-range = <2 2>; + pwren-gpios = <&smc_gpio 24 GPIO_ACTIVE_HIGH>; status = "okay"; }; diff --git a/arch/arm64/boot/dts/apple/t8112-j493.dts b/arch/arm64/boot/dts/apple/t8112-j493.dts index ec116da3e4dd9e..74adcd90974f52 100644 --- a/arch/arm64/boot/dts/apple/t8112-j493.dts +++ b/arch/arm64/boot/dts/apple/t8112-j493.dts @@ -91,6 +91,7 @@ */ &port00 { bus-range = <1 1>; + pwren-gpios = <&smc_gpio 13 GPIO_ACTIVE_HIGH>; wifi0: wifi@0,0 { compatible = "pci14e4,4425"; reg = <0x10000 0x0 0x0 0x0 0x0>; From 37e83c4f6508e45b11e014ee544f9419894aca06 Mon Sep 17 00:00:00 2001 From: Hector Martin Date: Thu, 17 Mar 2022 23:49:07 +0900 Subject: [PATCH 2105/3902] arm64: dts: apple: t8103: Keep PCIe power domain on This causes flakiness if shut down; don't do it until we find out what's going on. Signed-off-by: Hector Martin --- arch/arm64/boot/dts/apple/t8103-pmgr.dtsi | 1 + 1 file changed, 1 insertion(+) diff --git a/arch/arm64/boot/dts/apple/t8103-pmgr.dtsi b/arch/arm64/boot/dts/apple/t8103-pmgr.dtsi index fef8a4058f1415..a55b1fd2b52cc8 100644 --- a/arch/arm64/boot/dts/apple/t8103-pmgr.dtsi +++ b/arch/arm64/boot/dts/apple/t8103-pmgr.dtsi @@ -717,6 +717,7 @@ #reset-cells = <0>; label = "apcie_gp"; power-domains = <&ps_apcie>; + apple,always-on; /* Breaks things if shut down */ }; ps_ans2: power-controller@3f0 { From 0fc4a935b65798cc2f40083b89d1dbba5f3f7d74 Mon Sep 17 00:00:00 2001 From: Hector Martin Date: Mon, 24 Apr 2023 23:31:20 +0900 Subject: [PATCH 2106/3902] arm64: dts: apple: t8112: Remove always-on from the PMP node This should now work properly with power domain dependencies. With "apple,always-on" removed from ps_pmp add it as dependency for the dcp* power-domains. Fixes dcp crashes on power state changes. TODO: investigate if it is enough to power ps_pmp on during SetPowerState calls. Signed-off-by: Janne Grunau --- arch/arm64/boot/dts/apple/t8112-pmgr.dtsi | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/arch/arm64/boot/dts/apple/t8112-pmgr.dtsi b/arch/arm64/boot/dts/apple/t8112-pmgr.dtsi index 7c050c6f2707a1..118694dd9b5f06 100644 --- a/arch/arm64/boot/dts/apple/t8112-pmgr.dtsi +++ b/arch/arm64/boot/dts/apple/t8112-pmgr.dtsi @@ -672,7 +672,7 @@ #power-domain-cells = <0>; #reset-cells = <0>; label = "disp0_fe"; - power-domains = <&ps_disp0_sys>; + power-domains = <&ps_disp0_sys>, <&ps_pmp>; apple,always-on; /* TODO: figure out if we can enable PM here */ }; @@ -691,7 +691,7 @@ #power-domain-cells = <0>; #reset-cells = <0>; label = "dispext_fe"; - power-domains = <&ps_dispext_sys>; + power-domains = <&ps_dispext_sys>, <&ps_pmp>; }; ps_dispext_cpu0: power-controller@3c8 { @@ -773,7 +773,6 @@ #power-domain-cells = <0>; #reset-cells = <0>; label = "pmp"; - apple,always-on; }; ps_pms_sram: power-controller@418 { From dbeb03f9577d18f833068fbc05da9062c09e66cd Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Fri, 26 Nov 2021 00:24:15 +0100 Subject: [PATCH 2107/3902] arm64: dts: apple: t8103: Add spi3 keyboard node Enables keyboard and touchpad input on MacBook Air (M1, 2020) and MacBook Pro (13-inch, M1, 2020). Signed-off-by: Janne Grunau --- arch/arm64/boot/dts/apple/t8103-j293.dts | 21 +++++++++++++++++++++ arch/arm64/boot/dts/apple/t8103-j313.dts | 21 +++++++++++++++++++++ 2 files changed, 42 insertions(+) diff --git a/arch/arm64/boot/dts/apple/t8103-j293.dts b/arch/arm64/boot/dts/apple/t8103-j293.dts index 52f63ae7a58dd8..c23de799024abb 100644 --- a/arch/arm64/boot/dts/apple/t8103-j293.dts +++ b/arch/arm64/boot/dts/apple/t8103-j293.dts @@ -59,6 +59,27 @@ label = "USB-C Left-front"; }; +&spi3 { + status = "okay"; + + hid-transport@0 { + compatible = "apple,spi-hid-transport"; + reg = <0>; + spi-max-frequency = <8000000>; + /* + * Apple's ADT specifies 20us CS change delays, and the + * SPI HID interface metadata specifies 45us. Using either + * seems not to be reliable, but adding both works, so + * best guess is they are cumulative. + */ + spi-cs-setup-delay-ns = <65000>; + spi-cs-hold-delay-ns = <65000>; + spi-cs-inactive-delay-ns = <250000>; + spien-gpios = <&pinctrl_ap 195 0>; + interrupts-extended = <&pinctrl_nub 13 IRQ_TYPE_LEVEL_LOW>; + }; +}; + &i2c2 { status = "okay"; }; diff --git a/arch/arm64/boot/dts/apple/t8103-j313.dts b/arch/arm64/boot/dts/apple/t8103-j313.dts index 9eb2825d25dcba..9c9547e922a676 100644 --- a/arch/arm64/boot/dts/apple/t8103-j313.dts +++ b/arch/arm64/boot/dts/apple/t8103-j313.dts @@ -54,3 +54,24 @@ &typec1 { label = "USB-C Left-front"; }; + +&spi3 { + status = "okay"; + + hid-transport@0 { + compatible = "apple,spi-hid-transport"; + reg = <0>; + spi-max-frequency = <8000000>; + /* + * Apple's ADT specifies 20us CS change delays, and the + * SPI HID interface metadata specifies 45us. Using either + * seems not to be reliable, but adding both works, so + * best guess is they are cumulative. + */ + spi-cs-setup-delay-ns = <65000>; + spi-cs-hold-delay-ns = <65000>; + spi-cs-inactive-delay-ns = <250000>; + spien-gpios = <&pinctrl_ap 195 0>; + interrupts-extended = <&pinctrl_nub 13 IRQ_TYPE_LEVEL_LOW>; + }; +}; From b82ed0ae9a8cc32849a1f991d0f2870fc975fa2f Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Thu, 11 Nov 2021 21:31:21 +0100 Subject: [PATCH 2108/3902] arm64: dts: apple: j31[46]: Add keyboard nodes Enables keyboard and touchpad input on MacBook Pro (14/16-inch, M1 Pro/Max, 2021). Signed-off-by: Janne Grunau --- .../arm64/boot/dts/apple/t600x-j314-j316.dtsi | 21 +++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/arch/arm64/boot/dts/apple/t600x-j314-j316.dtsi b/arch/arm64/boot/dts/apple/t600x-j314-j316.dtsi index acd43bd86a4072..e9515a3885aefb 100644 --- a/arch/arm64/boot/dts/apple/t600x-j314-j316.dtsi +++ b/arch/arm64/boot/dts/apple/t600x-j314-j316.dtsi @@ -173,6 +173,27 @@ clock-frequency = <1068000000>; }; +&spi3 { + status = "okay"; + + hid-transport@0 { + compatible = "apple,spi-hid-transport"; + reg = <0>; + spi-max-frequency = <8000000>; + /* + * Apple's ADT specifies 20us CS change delays, and the + * SPI HID interface metadata specifies 45us. Using either + * seems not to be reliable, but adding both works, so + * best guess is they are cumulative. + */ + spi-cs-setup-delay-ns = <65000>; + spi-cs-hold-delay-ns = <65000>; + spi-cs-inactive-delay-ns = <250000>; + spien-gpios = <&pinctrl_ap 194 0>; + interrupts-extended = <&pinctrl_nub 6 IRQ_TYPE_LEVEL_LOW>; + }; +}; + /* PCIe devices */ &port00 { /* WLAN */ From a448f724c78ea29f36e28cf12188b5d95fa51f08 Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Thu, 2 Feb 2023 11:15:35 +0100 Subject: [PATCH 2109/3902] arm64: dts: apple: t8112: Add mtp device nodes for j413/j493 Those provide trackpad and keyboard for j413/j493. Add keyboard alias & layout props for t8112 laptops Signed-off-by: Hector Martin Signed-off-by: Janne Grunau --- arch/arm64/boot/dts/apple/t8112-j413.dts | 36 ++++++++++++ arch/arm64/boot/dts/apple/t8112-j415.dts | 36 ++++++++++++ arch/arm64/boot/dts/apple/t8112-j493.dts | 36 ++++++++++++ arch/arm64/boot/dts/apple/t8112.dtsi | 73 ++++++++++++++++++++++++ 4 files changed, 181 insertions(+) diff --git a/arch/arm64/boot/dts/apple/t8112-j413.dts b/arch/arm64/boot/dts/apple/t8112-j413.dts index 67ee47d3818cad..ba94990a995f11 100644 --- a/arch/arm64/boot/dts/apple/t8112-j413.dts +++ b/arch/arm64/boot/dts/apple/t8112-j413.dts @@ -20,6 +20,7 @@ aliases { bluetooth0 = &bluetooth0; + keyboard = &keyboard; wifi0 = &wifi0; }; @@ -92,3 +93,38 @@ &fpwm1 { status = "okay"; }; + +&mtp { + status = "okay"; +}; +&mtp_mbox { + status = "okay"; +}; +&mtp_dart { + status = "okay"; +}; +&mtp_dockchannel { + status = "okay"; +}; +&mtp_hid { + apple,afe-reset-gpios = <&smc_gpio 8 GPIO_ACTIVE_LOW>; + apple,stm-reset-gpios = <&smc_gpio 24 GPIO_ACTIVE_LOW>; + + multi-touch { + firmware-name = "apple/tpmtfw-j413.bin"; + }; + + keyboard: keyboard { + hid-country-code = <0>; + apple,keyboard-layout-id = <0>; + }; + + stm { + }; + + actuator { + }; + + tp_accel { + }; +}; diff --git a/arch/arm64/boot/dts/apple/t8112-j415.dts b/arch/arm64/boot/dts/apple/t8112-j415.dts index 4f146043bca2d3..bc8b533677df84 100644 --- a/arch/arm64/boot/dts/apple/t8112-j415.dts +++ b/arch/arm64/boot/dts/apple/t8112-j415.dts @@ -20,6 +20,7 @@ aliases { bluetooth0 = &bluetooth0; + keyboard = &keyboard; wifi0 = &wifi0; }; @@ -92,3 +93,38 @@ &fpwm1 { status = "okay"; }; + +&mtp { + status = "okay"; +}; +&mtp_mbox { + status = "okay"; +}; +&mtp_dart { + status = "okay"; +}; +&mtp_dockchannel { + status = "okay"; +}; +&mtp_hid { + apple,afe-reset-gpios = <&smc_gpio 8 GPIO_ACTIVE_LOW>; + apple,stm-reset-gpios = <&smc_gpio 24 GPIO_ACTIVE_LOW>; + + multi-touch { + firmware-name = "apple/tpmtfw-j415.bin"; + }; + + keyboard: keyboard { + hid-country-code = <0>; + apple,keyboard-layout-id = <0>; + }; + + stm { + }; + + actuator { + }; + + tp_accel { + }; +}; diff --git a/arch/arm64/boot/dts/apple/t8112-j493.dts b/arch/arm64/boot/dts/apple/t8112-j493.dts index 74adcd90974f52..40e241b8e2d57b 100644 --- a/arch/arm64/boot/dts/apple/t8112-j493.dts +++ b/arch/arm64/boot/dts/apple/t8112-j493.dts @@ -24,6 +24,7 @@ */ aliases { bluetooth0 = &bluetooth0; + keyboard = &keyboard; touchbar0 = &touchbar0; wifi0 = &wifi0; }; @@ -147,3 +148,38 @@ touchscreen-inverted-y; }; }; + +&mtp { + status = "okay"; +}; +&mtp_mbox { + status = "okay"; +}; +&mtp_dart { + status = "okay"; +}; +&mtp_dockchannel { + status = "okay"; +}; +&mtp_hid { + apple,afe-reset-gpios = <&smc_gpio 8 GPIO_ACTIVE_LOW>; + apple,stm-reset-gpios = <&smc_gpio 24 GPIO_ACTIVE_LOW>; + + multi-touch { + firmware-name = "apple/tpmtfw-j493.bin"; + }; + + keyboard: keyboard { + hid-country-code = <0>; + apple,keyboard-layout-id = <0>; + }; + + stm { + }; + + actuator { + }; + + tp_accel { + }; +}; diff --git a/arch/arm64/boot/dts/apple/t8112.dtsi b/arch/arm64/boot/dts/apple/t8112.dtsi index 516c5d6ecec370..99cf4a21a6af96 100644 --- a/arch/arm64/boot/dts/apple/t8112.dtsi +++ b/arch/arm64/boot/dts/apple/t8112.dtsi @@ -1080,6 +1080,79 @@ ; }; + mtp: mtp@24e400000 { + compatible = "apple,t8112-mtp", "apple,t8112-rtk-helper-asc4", "apple,mtp", "apple,rtk-helper-asc4"; + reg = <0x2 0x4e400000 0x0 0x4000>, + <0x2 0x4ec00000 0x0 0x100000>; + reg-names = "asc", "sram"; + mboxes = <&mtp_mbox>; + iommus = <&mtp_dart 1>; + #helper-cells = <0>; + + status = "disabled"; + }; + + mtp_mbox: mbox@24e408000 { + compatible = "apple,t8112-asc-mailbox", "apple,asc-mailbox-v4"; + reg = <0x2 0x4e408000 0x0 0x4000>; + interrupt-parent = <&aic>; + interrupts = , + , + , + ; + interrupt-names = "send-empty", "send-not-empty", + "recv-empty", "recv-not-empty"; + #mbox-cells = <0>; + + status = "disabled"; + }; + + mtp_dart: iommu@24e808000 { + compatible = "apple,t8112-dart", "apple,t8110-dart"; + reg = <0x2 0x4e808000 0x0 0x4000>; + interrupt-parent = <&aic>; + interrupts = ; + #iommu-cells = <1>; + + status = "disabled"; + }; + + mtp_dockchannel: fifo@24eb14000 { + compatible = "apple,t8112-dockchannel", "apple,dockchannel"; + reg = <0x2 0x4eb14000 0x0 0x4000>; + reg-names = "irq"; + interrupt-parent = <&aic>; + interrupts = ; + + ranges = <0 0x2 0x4eb28000 0x20000>; + #address-cells = <1>; + #size-cells = <1>; + + interrupt-controller; + #interrupt-cells = <2>; + + status = "disabled"; + + mtp_hid: input@8000 { + compatible = "apple,dockchannel-hid"; + reg = <0x8000 0x4000>, + <0xc000 0x4000>, + <0x0000 0x4000>, + <0x4000 0x4000>; + reg-names = "config", "data", + "rmt-config", "rmt-data"; + iommus = <&mtp_dart 1>; + interrupt-parent = <&mtp_dockchannel>; + interrupts = <2 IRQ_TYPE_LEVEL_HIGH>, + <3 IRQ_TYPE_LEVEL_HIGH>; + interrupt-names = "tx", "rx"; + + apple,fifo-size = <0x800>; + apple,helper-cpu = <&mtp>; + }; + + }; + ans_mbox: mbox@277408000 { compatible = "apple,t8112-asc-mailbox", "apple,asc-mailbox-v4"; reg = <0x2 0x77408000 0x0 0x4000>; From 7ca02b2c4c3f658fe30da4af3b7f8b77d44eb01f Mon Sep 17 00:00:00 2001 From: Hector Martin Date: Sun, 9 Apr 2023 23:49:01 +0900 Subject: [PATCH 2110/3902] arm64: dts: apple: Fix t600x mca IRQs Signed-off-by: Hector Martin --- arch/arm64/boot/dts/apple/t600x-die0.dtsi | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/arch/arm64/boot/dts/apple/t600x-die0.dtsi b/arch/arm64/boot/dts/apple/t600x-die0.dtsi index 34f30a19d789fe..9797880488d080 100644 --- a/arch/arm64/boot/dts/apple/t600x-die0.dtsi +++ b/arch/arm64/boot/dts/apple/t600x-die0.dtsi @@ -333,10 +333,10 @@ "tx2a", "rx2a", "tx2b", "rx2b", "tx3a", "rx3a", "tx3b", "rx3b"; interrupt-parent = <&aic>; - interrupts = , + interrupts = , + , , - , - ; + ; power-domains = <&ps_audio_p>, <&ps_mca0>, <&ps_mca1>, <&ps_mca2>, <&ps_mca3>; resets = <&ps_audio_p>; From 4e8ff19e6a2c2c31d7e7c4c56dab3b44228b6d4d Mon Sep 17 00:00:00 2001 From: Hector Martin Date: Thu, 12 Oct 2023 23:20:26 +0900 Subject: [PATCH 2111/3902] arm64: dts: apple: t600x: Mark MCA power states as externally-clocked Signed-off-by: Hector Martin --- arch/arm64/boot/dts/apple/t600x-pmgr.dtsi | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/arch/arm64/boot/dts/apple/t600x-pmgr.dtsi b/arch/arm64/boot/dts/apple/t600x-pmgr.dtsi index 0bd44753b76a0c..cc2627eafc899d 100644 --- a/arch/arm64/boot/dts/apple/t600x-pmgr.dtsi +++ b/arch/arm64/boot/dts/apple/t600x-pmgr.dtsi @@ -1113,6 +1113,7 @@ #reset-cells = <0>; label = DIE_LABEL(mca0); power-domains = <&DIE_NODE(ps_audio_p)>, <&DIE_NODE(ps_sio_adma)>; + apple,externally-clocked; }; DIE_NODE(ps_mca1): power-controller@290 { @@ -1122,6 +1123,7 @@ #reset-cells = <0>; label = DIE_LABEL(mca1); power-domains = <&DIE_NODE(ps_audio_p)>, <&DIE_NODE(ps_sio_adma)>; + apple,externally-clocked; }; DIE_NODE(ps_mca2): power-controller@298 { @@ -1131,6 +1133,7 @@ #reset-cells = <0>; label = DIE_LABEL(mca2); power-domains = <&DIE_NODE(ps_audio_p)>, <&DIE_NODE(ps_sio_adma)>; + apple,externally-clocked; }; DIE_NODE(ps_mca3): power-controller@2a0 { @@ -1140,6 +1143,7 @@ #reset-cells = <0>; label = DIE_LABEL(mca3); power-domains = <&DIE_NODE(ps_audio_p)>, <&DIE_NODE(ps_sio_adma)>; + apple,externally-clocked; }; DIE_NODE(ps_dpa0): power-controller@2a8 { From 01bce9e5a005a6f781621e68b5281c74d491658d Mon Sep 17 00:00:00 2001 From: Hector Martin Date: Thu, 12 Oct 2023 23:20:47 +0900 Subject: [PATCH 2112/3902] arm64: dts: apple: t8103: Mark MCA power states as externally-clocked Signed-off-by: Hector Martin --- arch/arm64/boot/dts/apple/t8103-pmgr.dtsi | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/arch/arm64/boot/dts/apple/t8103-pmgr.dtsi b/arch/arm64/boot/dts/apple/t8103-pmgr.dtsi index a55b1fd2b52cc8..9bddd5bf866000 100644 --- a/arch/arm64/boot/dts/apple/t8103-pmgr.dtsi +++ b/arch/arm64/boot/dts/apple/t8103-pmgr.dtsi @@ -493,6 +493,7 @@ #reset-cells = <0>; label = "mca0"; power-domains = <&ps_audio_p>, <&ps_sio_adma>; + apple,externally-clocked; }; ps_mca1: power-controller@2c0 { @@ -502,6 +503,7 @@ #reset-cells = <0>; label = "mca1"; power-domains = <&ps_audio_p>, <&ps_sio_adma>; + apple,externally-clocked; }; ps_mca2: power-controller@2c8 { @@ -511,6 +513,7 @@ #reset-cells = <0>; label = "mca2"; power-domains = <&ps_audio_p>, <&ps_sio_adma>; + apple,externally-clocked; }; ps_mca3: power-controller@2d0 { @@ -520,6 +523,7 @@ #reset-cells = <0>; label = "mca3"; power-domains = <&ps_audio_p>, <&ps_sio_adma>; + apple,externally-clocked; }; ps_mca4: power-controller@2d8 { @@ -529,6 +533,7 @@ #reset-cells = <0>; label = "mca4"; power-domains = <&ps_audio_p>, <&ps_sio_adma>; + apple,externally-clocked; }; ps_mca5: power-controller@2e0 { @@ -538,6 +543,7 @@ #reset-cells = <0>; label = "mca5"; power-domains = <&ps_audio_p>, <&ps_sio_adma>; + apple,externally-clocked; }; ps_dpa0: power-controller@2e8 { From c8db40084f9a731daf4b7ff0748c140d4604cc76 Mon Sep 17 00:00:00 2001 From: Hector Martin Date: Thu, 12 Oct 2023 23:20:56 +0900 Subject: [PATCH 2113/3902] arm64: dts: apple: t8112: Mark MCA power states as externally-clocked Signed-off-by: Hector Martin --- arch/arm64/boot/dts/apple/t8112-pmgr.dtsi | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/arch/arm64/boot/dts/apple/t8112-pmgr.dtsi b/arch/arm64/boot/dts/apple/t8112-pmgr.dtsi index 118694dd9b5f06..8b3297d75992d3 100644 --- a/arch/arm64/boot/dts/apple/t8112-pmgr.dtsi +++ b/arch/arm64/boot/dts/apple/t8112-pmgr.dtsi @@ -465,6 +465,7 @@ #reset-cells = <0>; label = "mca0"; power-domains = <&ps_sio_adma>, <&ps_audio_p>; + apple,externally-clocked; }; ps_mca1: power-controller@2c8 { @@ -474,6 +475,7 @@ #reset-cells = <0>; label = "mca1"; power-domains = <&ps_sio_adma>, <&ps_audio_p>; + apple,externally-clocked; }; ps_mca2: power-controller@2d0 { @@ -483,6 +485,7 @@ #reset-cells = <0>; label = "mca2"; power-domains = <&ps_sio_adma>, <&ps_audio_p>; + apple,externally-clocked; }; ps_mca3: power-controller@2d8 { @@ -492,6 +495,7 @@ #reset-cells = <0>; label = "mca3"; power-domains = <&ps_sio_adma>, <&ps_audio_p>; + apple,externally-clocked; }; ps_mca4: power-controller@2e0 { @@ -501,6 +505,7 @@ #reset-cells = <0>; label = "mca4"; power-domains = <&ps_sio_adma>, <&ps_audio_p>; + apple,externally-clocked; }; ps_mca5: power-controller@2e8 { @@ -510,6 +515,7 @@ #reset-cells = <0>; label = "mca5"; power-domains = <&ps_sio_adma>, <&ps_audio_p>; + apple,externally-clocked; }; ps_mcc: power-controller@2f0 { From f2fd254139deca2cd6a6bb2c1a05752f4a8cd56b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20Povi=C5=A1er?= Date: Sat, 19 Feb 2022 09:49:59 +0100 Subject: [PATCH 2114/3902] arm64: dts: apple: t8103*: Put in audio nodes MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit arm64: dts: apple: t8103-j274: Add speaker I/V sense slots Specify TDM slots for the speaker amp IC to transmit I/V sense measurements in. arm64: dts: apple: j293/j313: Model SDZ GPIO as a regulator Signed-off-by: Martin Povišer Co-developed-by: Hector Martin Signed-off-by: Hector Martin --- arch/arm64/boot/dts/apple/t8103-j274.dts | 53 ++++++++++++ arch/arm64/boot/dts/apple/t8103-j293.dts | 106 +++++++++++++++++++++++ arch/arm64/boot/dts/apple/t8103-j313.dts | 79 +++++++++++++++++ arch/arm64/boot/dts/apple/t8103-j456.dts | 31 +++++++ arch/arm64/boot/dts/apple/t8103-j457.dts | 31 +++++++ 5 files changed, 300 insertions(+) diff --git a/arch/arm64/boot/dts/apple/t8103-j274.dts b/arch/arm64/boot/dts/apple/t8103-j274.dts index 52965258200da3..f3b7204618c8ce 100644 --- a/arch/arm64/boot/dts/apple/t8103-j274.dts +++ b/arch/arm64/boot/dts/apple/t8103-j274.dts @@ -71,6 +71,59 @@ status = "okay"; }; +&i2c1 { + speaker_amp: codec@31 { + compatible = "ti,tas5770l", "ti,tas2770"; + reg = <0x31>; + shutdown-gpios = <&pinctrl_ap 181 GPIO_ACTIVE_HIGH>; + #sound-dai-cells = <0>; + ti,imon-slot-no = <0>; + ti,vmon-slot-no = <2>; + ti,sdout-zero-fill; + }; +}; + &i2c2 { status = "okay"; + + jack_codec: codec@48 { + compatible = "cirrus,cs42l83"; + reg = <0x48>; + reset-gpios = <&pinctrl_nub 11 GPIO_ACTIVE_HIGH>; + interrupt-parent = <&pinctrl_ap>; + interrupts = <183 IRQ_TYPE_LEVEL_LOW>; + #sound-dai-cells = <0>; + cirrus,ts-inv = <1>; + sound-name-prefix = "Jack"; + }; +}; + +/ { + sound { + compatible = "apple,j274-macaudio", "apple,macaudio"; + model = "Mac mini J274"; + + dai-link@0 { + link-name = "Speaker"; + + cpu { + sound-dai = <&mca 0>; + }; + codec { + sound-dai = <&speaker_amp>; + }; + }; + + dai-link@1 { + link-name = "Headphone Jack"; + + cpu { + sound-dai = <&mca 2>; + }; + codec { + sound-dai = <&jack_codec>; + }; + }; + + }; }; diff --git a/arch/arm64/boot/dts/apple/t8103-j293.dts b/arch/arm64/boot/dts/apple/t8103-j293.dts index c23de799024abb..753c9c4d9adefc 100644 --- a/arch/arm64/boot/dts/apple/t8103-j293.dts +++ b/arch/arm64/boot/dts/apple/t8103-j293.dts @@ -80,8 +80,84 @@ }; }; +/* Virtual regulator representing the shared shutdown GPIO */ +/ { + speaker_sdz: fixed-regulator-tas5770-sdz { + compatible = "regulator-fixed"; + regulator-name = "tas5770-sdz"; + startup-delay-us = <5000>; + gpios = <&pinctrl_ap 181 GPIO_ACTIVE_HIGH>; + enable-active-high; + }; +}; + +&i2c1 { + speaker_left_rear: codec@31 { + compatible = "ti,tas5770l", "ti,tas2770"; + reg = <0x31>; + SDZ-supply = <&speaker_sdz>; + #sound-dai-cells = <0>; + sound-name-prefix = "Left Rear"; + interrupts-extended = <&pinctrl_ap 182 IRQ_TYPE_LEVEL_LOW>; + ti,imon-slot-no = <8>; + ti,vmon-slot-no = <10>; + ti,pdm-slot-no = <12>; + }; + + speaker_left_front: codec@32 { + compatible = "ti,tas5770l", "ti,tas2770"; + reg = <0x32>; + SDZ-supply = <&speaker_sdz>; + #sound-dai-cells = <0>; + sound-name-prefix = "Left Front"; + interrupts-extended = <&pinctrl_ap 182 IRQ_TYPE_LEVEL_LOW>; + ti,imon-slot-no = <0>; + ti,vmon-slot-no = <2>; + ti,pdm-slot-no = <4>; + ti,sdout-pull-down; + }; +}; + &i2c2 { status = "okay"; + + jack_codec: codec@48 { + compatible = "cirrus,cs42l83"; + reg = <0x48>; + reset-gpios = <&pinctrl_nub 11 GPIO_ACTIVE_HIGH>; + interrupt-parent = <&pinctrl_ap>; + interrupts = <183 IRQ_TYPE_LEVEL_LOW>; + #sound-dai-cells = <0>; + cirrus,ts-inv = <1>; + sound-name-prefix = "Jack"; + }; +}; + +&i2c3 { + speaker_right_rear: codec@34 { + compatible = "ti,tas5770l", "ti,tas2770"; + reg = <0x34>; + SDZ-supply = <&speaker_sdz>; + #sound-dai-cells = <0>; + sound-name-prefix = "Right Rear"; + interrupts-extended = <&pinctrl_ap 182 IRQ_TYPE_LEVEL_LOW>; + ti,imon-slot-no = <12>; + ti,vmon-slot-no = <14>; + ti,pdm-slot-no = <16>; + }; + + speaker_right_front: codec@35 { + compatible = "ti,tas5770l", "ti,tas2770"; + reg = <0x35>; + SDZ-supply = <&speaker_sdz>; + #sound-dai-cells = <0>; + sound-name-prefix = "Right Front"; + interrupts-extended = <&pinctrl_ap 182 IRQ_TYPE_LEVEL_LOW>; + ti,imon-slot-no = <4>; + ti,vmon-slot-no = <6>; + ti,pdm-slot-no = <8>; + ti,sdout-pull-down; + }; }; &i2c4 { @@ -153,3 +229,33 @@ &displaydfr_dart { status = "okay"; }; + +/ { + sound { + compatible = "apple,j293-macaudio", "apple,macaudio"; + model = "MacBook Pro J293"; + + dai-link@0 { + link-name = "Speakers"; + + cpu { + sound-dai = <&mca 0>, <&mca 1>; + }; + codec { + sound-dai = <&speaker_left_front>, <&speaker_left_rear>, + <&speaker_right_front>, <&speaker_right_rear>; + }; + }; + + dai-link@1 { + link-name = "Headphone Jack"; + + cpu { + sound-dai = <&mca 2>; + }; + codec { + sound-dai = <&jack_codec>; + }; + }; + }; +}; diff --git a/arch/arm64/boot/dts/apple/t8103-j313.dts b/arch/arm64/boot/dts/apple/t8103-j313.dts index 9c9547e922a676..43620b57a63d2e 100644 --- a/arch/arm64/boot/dts/apple/t8103-j313.dts +++ b/arch/arm64/boot/dts/apple/t8103-j313.dts @@ -75,3 +75,82 @@ interrupts-extended = <&pinctrl_nub 13 IRQ_TYPE_LEVEL_LOW>; }; }; + +/* Virtual regulator representing the shared shutdown GPIO */ +/ { + speaker_sdz: fixed-regulator-tas5770-sdz { + compatible = "regulator-fixed"; + regulator-name = "tas5770-sdz"; + startup-delay-us = <5000>; + gpios = <&pinctrl_ap 181 GPIO_ACTIVE_HIGH>; + enable-active-high; + }; +}; + +&i2c1 { + speaker_left: codec@31 { + compatible = "ti,tas5770l", "ti,tas2770"; + reg = <0x31>; + SDZ-supply = <&speaker_sdz>; + #sound-dai-cells = <0>; + sound-name-prefix = "Left"; + interrupts-extended = <&pinctrl_ap 182 IRQ_TYPE_LEVEL_LOW>; + ti,imon-slot-no = <0>; + ti,vmon-slot-no = <2>; + ti,sdout-zero-fill; + }; +}; + +&i2c3 { + speaker_right: codec@34 { + compatible = "ti,tas5770l", "ti,tas2770"; + reg = <0x34>; + SDZ-supply = <&speaker_sdz>; + #sound-dai-cells = <0>; + sound-name-prefix = "Right"; + interrupts-extended = <&pinctrl_ap 182 IRQ_TYPE_LEVEL_LOW>; + ti,imon-slot-no = <4>; + ti,vmon-slot-no = <6>; + ti,sdout-zero-fill; + }; + + jack_codec: codec@48 { + compatible = "cirrus,cs42l83"; + reg = <0x48>; + reset-gpios = <&pinctrl_nub 11 GPIO_ACTIVE_HIGH>; + interrupt-parent = <&pinctrl_ap>; + interrupts = <183 IRQ_TYPE_LEVEL_LOW>; + #sound-dai-cells = <0>; + cirrus,ts-inv = <1>; + sound-name-prefix = "Jack"; + }; +}; + +/ { + sound { + compatible = "apple,j313-macaudio", "apple,macaudio"; + model = "MacBook Air J313"; + + dai-link@0 { + link-name = "Speakers"; + + cpu { + sound-dai = <&mca 0>, <&mca 1>; + }; + codec { + sound-dai = <&speaker_left>, <&speaker_right>; + }; + }; + + dai-link@1 { + link-name = "Headphone Jack"; + + cpu { + sound-dai = <&mca 2>; + }; + codec { + sound-dai = <&jack_codec>; + }; + }; + }; +}; diff --git a/arch/arm64/boot/dts/apple/t8103-j456.dts b/arch/arm64/boot/dts/apple/t8103-j456.dts index 090c97bb781b32..d277704b988bcc 100644 --- a/arch/arm64/boot/dts/apple/t8103-j456.dts +++ b/arch/arm64/boot/dts/apple/t8103-j456.dts @@ -88,3 +88,34 @@ &pcie0_dart_2 { status = "okay"; }; + +&i2c1 { + jack_codec: codec@48 { + compatible = "cirrus,cs42l83"; + reg = <0x48>; + reset-gpios = <&pinctrl_nub 11 GPIO_ACTIVE_HIGH>; + interrupt-parent = <&pinctrl_ap>; + interrupts = <183 IRQ_TYPE_LEVEL_LOW>; + #sound-dai-cells = <0>; + cirrus,ts-inv = <1>; + sound-name-prefix = "Jack"; + }; +}; + +/ { + sound { + compatible = "apple,j456-macaudio", "apple,macaudio"; + model = "iMac J456"; + + dai-link@1 { + link-name = "Headphone Jack"; + + cpu { + sound-dai = <&mca 2>; + }; + codec { + sound-dai = <&jack_codec>; + }; + }; + }; +}; diff --git a/arch/arm64/boot/dts/apple/t8103-j457.dts b/arch/arm64/boot/dts/apple/t8103-j457.dts index ebddde75455c69..2e7d344f88a54f 100644 --- a/arch/arm64/boot/dts/apple/t8103-j457.dts +++ b/arch/arm64/boot/dts/apple/t8103-j457.dts @@ -69,3 +69,34 @@ &pcie0_dart_2 { status = "okay"; }; + +&i2c1 { + jack_codec: codec@48 { + compatible = "cirrus,cs42l83"; + reg = <0x48>; + reset-gpios = <&pinctrl_nub 11 GPIO_ACTIVE_HIGH>; + interrupt-parent = <&pinctrl_ap>; + interrupts = <183 IRQ_TYPE_LEVEL_LOW>; + #sound-dai-cells = <0>; + cirrus,ts-inv = <1>; + sound-name-prefix = "Jack"; + }; +}; + +/ { + sound { + compatible = "apple,j457-macaudio", "apple,macaudio"; + model = "iMac J457"; + + dai-link@1 { + link-name = "Headphone Jack"; + + cpu { + sound-dai = <&mca 2>; + }; + codec { + sound-dai = <&jack_codec>; + }; + }; + }; +}; From c3f730dddeb1b459037344a013bdd16ba07ee6f6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20Povi=C5=A1er?= Date: Fri, 11 Mar 2022 22:16:25 +0100 Subject: [PATCH 2115/3902] arm64: dts: apple: t600x-jxxx: Put in audio nodes MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit arm64: dts: apple: t600x-j314-j316: Add speaker I/V sense slots Specify TDM slots for the speaker amp IC to transmit I/V sense measurements in. Make sure the channel order mirrors that of the playback PCM. arm64: dts: apple: t600x-j314-j316: Zero out unused speaker sense slots Make one left codec and one right codec zero out the unused slots on their respective speaker sense buses. Internally, inside the SoC, the left and right sense buses are ORed, and zeroing-out the unused slots on one bus is required so as not to corrupt the data on the other. arm64: dts: apple: t600x: describe shared SDZ GPIO for tas2764 machines with the tas2764 amp codec share a GPIO line for asserting/deasserting the SDZ pin on the chips. describe this as a regulator to facilitate chip reset on suspend/resume Signed-off-by: Martin Povišer Co-developed-by: Hector Martin Signed-off-by: Hector Martin Co-developed-by: James Calligeros Signed-off-by: James Calligeros --- arch/arm64/boot/dts/apple/t6000-j314s.dts | 5 + arch/arm64/boot/dts/apple/t6000-j316s.dts | 5 + arch/arm64/boot/dts/apple/t6001-j314c.dts | 5 + arch/arm64/boot/dts/apple/t6001-j316c.dts | 5 + arch/arm64/boot/dts/apple/t6001-j375c.dts | 5 + arch/arm64/boot/dts/apple/t6002-j375d.dts | 5 + .../arm64/boot/dts/apple/t600x-j314-j316.dtsi | 133 ++++++++++++++++++ arch/arm64/boot/dts/apple/t600x-j375.dtsi | 56 ++++++++ 8 files changed, 219 insertions(+) diff --git a/arch/arm64/boot/dts/apple/t6000-j314s.dts b/arch/arm64/boot/dts/apple/t6000-j314s.dts index 1430b91ff1b152..dab8e99fa32496 100644 --- a/arch/arm64/boot/dts/apple/t6000-j314s.dts +++ b/arch/arm64/boot/dts/apple/t6000-j314s.dts @@ -24,3 +24,8 @@ &bluetooth0 { brcm,board-type = "apple,maldives"; }; + +&sound { + compatible = "apple,j314-macaudio", "apple,macaudio"; + model = "MacBook Pro J314"; +}; diff --git a/arch/arm64/boot/dts/apple/t6000-j316s.dts b/arch/arm64/boot/dts/apple/t6000-j316s.dts index da0cbe7d96736b..2cdfac3c40c842 100644 --- a/arch/arm64/boot/dts/apple/t6000-j316s.dts +++ b/arch/arm64/boot/dts/apple/t6000-j316s.dts @@ -24,3 +24,8 @@ &bluetooth0 { brcm,board-type = "apple,madagascar"; }; + +&sound { + compatible = "apple,j316-macaudio", "apple,macaudio"; + model = "MacBook Pro J316"; +}; diff --git a/arch/arm64/boot/dts/apple/t6001-j314c.dts b/arch/arm64/boot/dts/apple/t6001-j314c.dts index c37097dcfdb304..7495698beb0258 100644 --- a/arch/arm64/boot/dts/apple/t6001-j314c.dts +++ b/arch/arm64/boot/dts/apple/t6001-j314c.dts @@ -24,3 +24,8 @@ &bluetooth0 { brcm,board-type = "apple,maldives"; }; + +&sound { + compatible = "apple,j314-macaudio", "apple,macaudio"; + model = "MacBook Pro J314"; +}; diff --git a/arch/arm64/boot/dts/apple/t6001-j316c.dts b/arch/arm64/boot/dts/apple/t6001-j316c.dts index 3bc6e0c3294cf9..6622b6e225a600 100644 --- a/arch/arm64/boot/dts/apple/t6001-j316c.dts +++ b/arch/arm64/boot/dts/apple/t6001-j316c.dts @@ -24,3 +24,8 @@ &bluetooth0 { brcm,board-type = "apple,madagascar"; }; + +&sound { + compatible = "apple,j316-macaudio", "apple,macaudio"; + model = "MacBook Pro J316"; +}; diff --git a/arch/arm64/boot/dts/apple/t6001-j375c.dts b/arch/arm64/boot/dts/apple/t6001-j375c.dts index 2e7c23714d4d00..a8694a94fa2793 100644 --- a/arch/arm64/boot/dts/apple/t6001-j375c.dts +++ b/arch/arm64/boot/dts/apple/t6001-j375c.dts @@ -24,3 +24,8 @@ &bluetooth0 { brcm,board-type = "apple,okinawa"; }; + +&sound { + compatible = "apple,j375-macaudio", "apple,macaudio"; + model = "Mac Studio J375"; +}; diff --git a/arch/arm64/boot/dts/apple/t6002-j375d.dts b/arch/arm64/boot/dts/apple/t6002-j375d.dts index a2a24d028cbbf5..65743fea3f1068 100644 --- a/arch/arm64/boot/dts/apple/t6002-j375d.dts +++ b/arch/arm64/boot/dts/apple/t6002-j375d.dts @@ -21,6 +21,11 @@ }; }; +&sound { + compatible = "apple,j375-macaudio", "apple,macaudio"; + model = "Mac Studio J375"; +}; + /* USB Type C */ &i2c0 { /* front-right */ diff --git a/arch/arm64/boot/dts/apple/t600x-j314-j316.dtsi b/arch/arm64/boot/dts/apple/t600x-j314-j316.dtsi index e9515a3885aefb..6ebaabe49a8130 100644 --- a/arch/arm64/boot/dts/apple/t600x-j314-j316.dtsi +++ b/arch/arm64/boot/dts/apple/t600x-j314-j316.dtsi @@ -169,6 +169,106 @@ }; }; +/* Virtual regulator representing the shared shutdown GPIO */ +/ { + speaker_sdz: fixed-regulator-sn012776-sdz { + compatible = "regulator-fixed"; + regulator-name = "sn012776-sdz"; + startup-delay-us = <5000>; + gpios = <&pinctrl_ap 178 GPIO_ACTIVE_HIGH>; + enable-active-high; + }; +}; + +&i2c1 { + status = "okay"; + + speaker_left_tweet: codec@3a { + compatible = "ti,sn012776", "ti,tas2764"; + reg = <0x3a>; + SDZ-supply = <&speaker_sdz>; + #sound-dai-cells = <0>; + sound-name-prefix = "Left Tweeter"; + interrupts-extended = <&pinctrl_ap 179 IRQ_TYPE_LEVEL_LOW>; + ti,imon-slot-no = <8>; + ti,vmon-slot-no = <10>; + }; + + speaker_left_woof1: codec@38 { + compatible = "ti,sn012776", "ti,tas2764"; + reg = <0x38>; + SDZ-supply = <&speaker_sdz>; + #sound-dai-cells = <0>; + sound-name-prefix = "Left Woofer 1"; + interrupts-extended = <&pinctrl_ap 179 IRQ_TYPE_LEVEL_LOW>; + ti,imon-slot-no = <0>; + ti,vmon-slot-no = <2>; + ti,sdout-force-zero-mask = <0xf0f0f0>; + }; + + speaker_left_woof2: codec@39 { + compatible = "ti,sn012776", "ti,tas2764"; + reg = <0x39>; + SDZ-supply = <&speaker_sdz>; + #sound-dai-cells = <0>; + sound-name-prefix = "Left Woofer 2"; + interrupts-extended = <&pinctrl_ap 179 IRQ_TYPE_LEVEL_LOW>; + ti,imon-slot-no = <16>; + ti,vmon-slot-no = <18>; + }; +}; + +&i2c2 { + status = "okay"; + + jack_codec: codec@4b { + compatible = "cirrus,cs42l84"; + reg = <0x4b>; + reset-gpios = <&pinctrl_nub 4 GPIO_ACTIVE_HIGH>; + #sound-dai-cells = <0>; + interrupts-extended = <&pinctrl_ap 180 IRQ_TYPE_LEVEL_LOW>; + sound-name-prefix = "Jack"; + }; +}; + +&i2c3 { + status = "okay"; + + speaker_right_tweet: codec@3d { + compatible = "ti,sn012776", "ti,tas2764"; + reg = <0x3d>; + SDZ-supply = <&speaker_sdz>; + #sound-dai-cells = <0>; + sound-name-prefix = "Right Tweeter"; + interrupts-extended = <&pinctrl_ap 179 IRQ_TYPE_LEVEL_LOW>; + ti,imon-slot-no = <12>; + ti,vmon-slot-no = <14>; + }; + + speaker_right_woof1: codec@3b { + compatible = "ti,sn012776", "ti,tas2764"; + reg = <0x3b>; + SDZ-supply = <&speaker_sdz>; + #sound-dai-cells = <0>; + sound-name-prefix = "Right Woofer 1"; + interrupts-extended = <&pinctrl_ap 179 IRQ_TYPE_LEVEL_LOW>; + ti,imon-slot-no = <4>; + ti,vmon-slot-no = <6>; + ti,sdout-force-zero-mask = <0x0f0f0f>; + }; + + speaker_right_woof2: codec@3c { + compatible = "ti,sn012776", "ti,tas2764"; + reg = <0x3c>; + SDZ-supply = <&speaker_sdz>; + #sound-dai-cells = <0>; + sound-name-prefix = "Right Woofer 2"; + interrupts-extended = <&pinctrl_ap 179 IRQ_TYPE_LEVEL_LOW>; + ti,imon-slot-no = <20>; + ti,vmon-slot-no = <22>; + }; +}; + &nco_clkref { clock-frequency = <1068000000>; }; @@ -389,4 +489,37 @@ status = "disabled"; }; +/ { + sound: sound { + /* compatible is set per machine */ + + dai-link@0 { + link-name = "Speakers"; + + cpu { + sound-dai = <&mca 0>, <&mca 1>; + }; + codec { + sound-dai = <&speaker_left_woof1>, + <&speaker_left_tweet>, + <&speaker_left_woof2>, + <&speaker_right_woof1>, + <&speaker_right_tweet>, + <&speaker_right_woof2>; + }; + }; + + dai-link@1 { + link-name = "Headphone Jack"; + + cpu { + sound-dai = <&mca 2>; + }; + codec { + sound-dai = <&jack_codec>; + }; + }; + }; +}; + #include "spi1-nvram.dtsi" diff --git a/arch/arm64/boot/dts/apple/t600x-j375.dtsi b/arch/arm64/boot/dts/apple/t600x-j375.dtsi index d2c8977bc01532..65b2abd521b10c 100644 --- a/arch/arm64/boot/dts/apple/t600x-j375.dtsi +++ b/arch/arm64/boot/dts/apple/t600x-j375.dtsi @@ -349,10 +349,66 @@ }; }; +/* Audio */ +&i2c1 { + status = "okay"; + + speaker: codec@38 { + compatible = "ti,sn012776", "ti,tas2764"; + reg = <0x38>; + shutdown-gpios = <&pinctrl_ap 178 GPIO_ACTIVE_HIGH>; + #sound-dai-cells = <0>; + interrupts-extended = <&pinctrl_ap 179 IRQ_TYPE_LEVEL_LOW>; + ti,imon-slot-no = <0>; + ti,vmon-slot-no = <2>; + }; +}; + +&i2c2 { + status = "okay"; + + jack_codec: codec@4b { + compatible = "cirrus,cs42l84"; + reg = <0x4b>; + reset-gpios = <&pinctrl_nub 4 GPIO_ACTIVE_HIGH>; + #sound-dai-cells = <0>; + interrupts-extended = <&pinctrl_ap 180 IRQ_TYPE_LEVEL_LOW>; + sound-name-prefix = "Jack"; + }; +}; + &nco_clkref { clock-frequency = <1068000000>; }; +/ { + sound: sound { + /* compatible is set per machine */ + + dai-link@0 { + link-name = "Speaker"; + + cpu { + sound-dai = <&mca 0>; + }; + codec { + sound-dai = <&speaker>; + }; + }; + + dai-link@1 { + link-name = "Headphone Jack"; + + cpu { + sound-dai = <&mca 2>; + }; + codec { + sound-dai = <&jack_codec>; + }; + }; + }; +}; + /* PCIe devices */ &port00 { /* WLAN */ From 719d1f988d97583ddbe3f3a744f6c0e8fde11574 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20Povi=C5=A1er?= Date: Sat, 19 Feb 2022 09:49:59 +0100 Subject: [PATCH 2116/3902] arm64: dts: apple: t8112: Put in audio nodes MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit arm64: dts: apple: t8112: describe shared SDZ GPIO for tas2764 machines with the tas2764 amp codec share a GPIO line for asserting/deasserting the SDZ pin on the chips. describe this as a regulator to facilitate chip reset on suspend/resume Co-developed-by: Hector Martin Signed-off-by: Hector Martin Co-developed-by: James Calligeros Signed-off-by: James Calligeros Signed-off-by: Martin Povišer --- arch/arm64/boot/dts/apple/t8112-j413.dts | 100 ++++++++++++++++++ arch/arm64/boot/dts/apple/t8112-j415.dts | 126 +++++++++++++++++++++++ arch/arm64/boot/dts/apple/t8112-j473.dts | 54 ++++++++++ arch/arm64/boot/dts/apple/t8112-j493.dts | 100 ++++++++++++++++++ 4 files changed, 380 insertions(+) diff --git a/arch/arm64/boot/dts/apple/t8112-j413.dts b/arch/arm64/boot/dts/apple/t8112-j413.dts index ba94990a995f11..f6450ef3a0c6e1 100644 --- a/arch/arm64/boot/dts/apple/t8112-j413.dts +++ b/arch/arm64/boot/dts/apple/t8112-j413.dts @@ -86,6 +86,76 @@ }; }; +/* Virtual regulator representing the shared shutdown GPIO */ +/ { + speaker_sdz: fixed-regulator-sn012776-sdz { + compatible = "regulator-fixed"; + regulator-name = "sn012776-sdz"; + startup-delay-us = <5000>; + gpios = <&pinctrl_ap 88 GPIO_ACTIVE_HIGH>; + enable-active-high; + }; +}; + +&i2c1 { + speaker_left_woof: codec@38 { + compatible = "ti,sn012776", "ti,tas2764"; + reg = <0x38>; + SDZ-supply = <&speaker_sdz>; + #sound-dai-cells = <0>; + sound-name-prefix = "Left Woofer"; + interrupts-extended = <&pinctrl_ap 11 IRQ_TYPE_LEVEL_LOW>; + ti,imon-slot-no = <0>; + ti,vmon-slot-no = <2>; + ti,sdout-force-zero-mask = <0xf0f0>; + }; + + speaker_left_tweet: codec@39 { + compatible = "ti,sn012776", "ti,tas2764"; + reg = <0x39>; + SDZ-supply = <&speaker_sdz>; + #sound-dai-cells = <0>; + sound-name-prefix = "Left Tweeter"; + interrupts-extended = <&pinctrl_ap 11 IRQ_TYPE_LEVEL_LOW>; + ti,imon-slot-no = <8>; + ti,vmon-slot-no = <10>; + }; +}; + +&i2c3 { + speaker_right_woof: codec@3b { + compatible = "ti,sn012776", "ti,tas2764"; + reg = <0x3b>; + SDZ-supply = <&speaker_sdz>; + #sound-dai-cells = <0>; + sound-name-prefix = "Right Woofer"; + interrupts-extended = <&pinctrl_ap 11 IRQ_TYPE_LEVEL_LOW>; + ti,imon-slot-no = <4>; + ti,vmon-slot-no = <6>; + ti,sdout-force-zero-mask = <0x0f0f>; + }; + + speaker_right_tweet: codec@3c { + compatible = "ti,sn012776", "ti,tas2764"; + reg = <0x3c>; + SDZ-supply = <&speaker_sdz>; + #sound-dai-cells = <0>; + sound-name-prefix = "Right Tweeter"; + interrupts-extended = <&pinctrl_ap 11 IRQ_TYPE_LEVEL_LOW>; + ti,imon-slot-no = <12>; + ti,vmon-slot-no = <14>; + }; + + jack_codec: codec@4b { + compatible = "cirrus,cs42l84"; + reg = <0x4b>; + reset-gpios = <&pinctrl_nub 12 GPIO_ACTIVE_HIGH>; + #sound-dai-cells = <0>; + interrupts-extended = <&pinctrl_ap 149 IRQ_TYPE_LEVEL_LOW>; + sound-name-prefix = "Jack"; + }; +}; + &i2c4 { status = "okay"; }; @@ -94,6 +164,36 @@ status = "okay"; }; +/ { + sound { + compatible = "apple,j413-macaudio", "apple,macaudio"; + model = "MacBook Air J413"; + + dai-link@0 { + link-name = "Speakers"; + + cpu { + sound-dai = <&mca 0>, <&mca 1>; + }; + codec { + sound-dai = <&speaker_left_woof>, <&speaker_left_tweet>, + <&speaker_right_woof>, <&speaker_right_tweet>; + }; + }; + + dai-link@1 { + link-name = "Headphone Jack"; + + cpu { + sound-dai = <&mca 2>; + }; + codec { + sound-dai = <&jack_codec>; + }; + }; + }; +}; + &mtp { status = "okay"; }; diff --git a/arch/arm64/boot/dts/apple/t8112-j415.dts b/arch/arm64/boot/dts/apple/t8112-j415.dts index bc8b533677df84..2c969eb767a7d8 100644 --- a/arch/arm64/boot/dts/apple/t8112-j415.dts +++ b/arch/arm64/boot/dts/apple/t8112-j415.dts @@ -86,6 +86,98 @@ }; }; +/* Virtual regulator representing the shared shutdown GPIO */ +/ { + speaker_sdz: fixed-regulator-sn012776-sdz { + compatible = "regulator-fixed"; + regulator-name = "sn012776-sdz"; + startup-delay-us = <5000>; + gpios = <&pinctrl_ap 88 GPIO_ACTIVE_HIGH>; + enable-active-high; + }; +}; + +&i2c1 { + speaker_left_woof1: codec@38 { + compatible = "ti,sn012776", "ti,tas2764"; + reg = <0x38>; + SDZ-supply = <&speaker_sdz>; + #sound-dai-cells = <0>; + sound-name-prefix = "Left Woofer 1"; + interrupts-extended = <&pinctrl_ap 11 IRQ_TYPE_LEVEL_LOW>; + ti,imon-slot-no = <0>; + ti,vmon-slot-no = <2>; + ti,sdout-force-zero-mask = <0xf0f0f0>; + }; + + speaker_left_tweet: codec@39 { + compatible = "ti,sn012776", "ti,tas2764"; + reg = <0x39>; + SDZ-supply = <&speaker_sdz>; + #sound-dai-cells = <0>; + sound-name-prefix = "Left Tweeter"; + interrupts-extended = <&pinctrl_ap 11 IRQ_TYPE_LEVEL_LOW>; + ti,imon-slot-no = <8>; + ti,vmon-slot-no = <10>; + }; + + speaker_left_woof2: codec@3a { + compatible = "ti,sn012776", "ti,tas2764"; + reg = <0x3a>; + SDZ-supply = <&speaker_sdz>; + #sound-dai-cells = <0>; + sound-name-prefix = "Left Woofer 2"; + interrupts-extended = <&pinctrl_ap 11 IRQ_TYPE_LEVEL_LOW>; + ti,imon-slot-no = <16>; + ti,vmon-slot-no = <18>; + }; +}; + +&i2c3 { + speaker_right_woof1: codec@3b { + compatible = "ti,sn012776", "ti,tas2764"; + reg = <0x3b>; + SDZ-supply = <&speaker_sdz>; + #sound-dai-cells = <0>; + sound-name-prefix = "Right Woofer 1"; + interrupts-extended = <&pinctrl_ap 11 IRQ_TYPE_LEVEL_LOW>; + ti,imon-slot-no = <4>; + ti,vmon-slot-no = <6>; + ti,sdout-force-zero-mask = <0x0f0f0f>; + }; + + speaker_right_tweet: codec@3c { + compatible = "ti,sn012776", "ti,tas2764"; + reg = <0x3c>; + SDZ-supply = <&speaker_sdz>; + #sound-dai-cells = <0>; + sound-name-prefix = "Right Tweeter"; + interrupts-extended = <&pinctrl_ap 11 IRQ_TYPE_LEVEL_LOW>; + ti,imon-slot-no = <12>; + ti,vmon-slot-no = <14>; + }; + + speaker_right_woof2: codec@3d { + compatible = "ti,sn012776", "ti,tas2764"; + reg = <0x3d>; + SDZ-supply = <&speaker_sdz>; + #sound-dai-cells = <0>; + sound-name-prefix = "Right Woofer 2"; + interrupts-extended = <&pinctrl_ap 11 IRQ_TYPE_LEVEL_LOW>; + ti,imon-slot-no = <20>; + ti,vmon-slot-no = <22>; + }; + + jack_codec: codec@4b { + compatible = "cirrus,cs42l84"; + reg = <0x4b>; + reset-gpios = <&pinctrl_nub 12 GPIO_ACTIVE_HIGH>; + #sound-dai-cells = <0>; + interrupts-extended = <&pinctrl_ap 149 IRQ_TYPE_LEVEL_LOW>; + sound-name-prefix = "Jack"; + }; +}; + &i2c4 { status = "okay"; }; @@ -94,6 +186,40 @@ status = "okay"; }; +/ { + sound { + compatible = "apple,j415-macaudio", "apple,macaudio"; + model = "MacBook Air J415"; + + dai-link@0 { + link-name = "Speakers"; + + cpu { + sound-dai = <&mca 0>, <&mca 1>; + }; + codec { + sound-dai = <&speaker_left_woof1>, + <&speaker_left_tweet>, + <&speaker_left_woof2>, + <&speaker_right_woof1>, + <&speaker_right_tweet>, + <&speaker_right_woof2>; + }; + }; + + dai-link@1 { + link-name = "Headphone Jack"; + + cpu { + sound-dai = <&mca 2>; + }; + codec { + sound-dai = <&jack_codec>; + }; + }; + }; +}; + &mtp { status = "okay"; }; diff --git a/arch/arm64/boot/dts/apple/t8112-j473.dts b/arch/arm64/boot/dts/apple/t8112-j473.dts index 320178e7ddfe8b..effdfae8646949 100644 --- a/arch/arm64/boot/dts/apple/t8112-j473.dts +++ b/arch/arm64/boot/dts/apple/t8112-j473.dts @@ -104,3 +104,57 @@ &typec1 { label = "USB-C Back-right"; }; + +&i2c1 { + speaker_amp: codec@38 { + compatible = "ti,sn012776", "ti,tas2764"; + reg = <0x38>; + shutdown-gpios = <&pinctrl_ap 88 GPIO_ACTIVE_HIGH>; + #sound-dai-cells = <0>; + interrupt-parent = <&pinctrl_ap>; + interrupts = <11 IRQ_TYPE_LEVEL_LOW>; + ti,imon-slot-no = <0>; + ti,vmon-slot-no = <2>; + }; + + jack_codec: codec@4b { + compatible = "cirrus,cs42l84"; + reg = <0x4b>; + reset-gpios = <&pinctrl_nub 12 GPIO_ACTIVE_HIGH>; + interrupt-parent = <&pinctrl_ap>; + interrupts = <149 IRQ_TYPE_LEVEL_LOW>; + #sound-dai-cells = <0>; + cirrus,ts-inv = <1>; + sound-name-prefix = "Jack"; + }; +}; + +/ { + sound { + compatible = "apple,j473-macaudio", "apple,macaudio"; + model = "Mac mini J473"; + + dai-link@0 { + link-name = "Speaker"; + + cpu { + sound-dai = <&mca 0>; + }; + codec { + sound-dai = <&speaker_amp>; + }; + }; + + dai-link@1 { + link-name = "Headphone Jack"; + + cpu { + sound-dai = <&mca 2>; + }; + codec { + sound-dai = <&jack_codec>; + }; + }; + + }; +}; diff --git a/arch/arm64/boot/dts/apple/t8112-j493.dts b/arch/arm64/boot/dts/apple/t8112-j493.dts index 40e241b8e2d57b..d25794fd88e355 100644 --- a/arch/arm64/boot/dts/apple/t8112-j493.dts +++ b/arch/arm64/boot/dts/apple/t8112-j493.dts @@ -123,6 +123,76 @@ label = "USB-C Left-front"; }; +/* Virtual regulator representing the shared shutdown GPIO */ +/ { + speaker_sdz: fixed-regulator-sn012776-sdz { + compatible = "regulator-fixed"; + regulator-name = "sn012776-sdz"; + startup-delay-us = <5000>; + gpios = <&pinctrl_ap 88 GPIO_ACTIVE_HIGH>; + enable-active-high; + }; +}; + +&i2c1 { + speaker_left_rear: codec@38 { + compatible = "ti,sn012776", "ti,tas2764"; + reg = <0x38>; + SDZ-supply = <&speaker_sdz>; + #sound-dai-cells = <0>; + sound-name-prefix = "Left Rear"; + interrupts-extended = <&pinctrl_ap 11 IRQ_TYPE_LEVEL_LOW>; + ti,imon-slot-no = <8>; + ti,vmon-slot-no = <10>; + }; + + speaker_left_front: codec@39 { + compatible = "ti,sn012776", "ti,tas2764"; + reg = <0x39>; + SDZ-supply = <&speaker_sdz>; + #sound-dai-cells = <0>; + sound-name-prefix = "Left Front"; + interrupts-extended = <&pinctrl_ap 11 IRQ_TYPE_LEVEL_LOW>; + ti,imon-slot-no = <0>; + ti,vmon-slot-no = <2>; + ti,sdout-force-zero-mask = <0xf0f0>; + }; +}; + +&i2c3 { + speaker_right_rear: codec@3b { + compatible = "ti,sn012776", "ti,tas2764"; + reg = <0x3b>; + SDZ-supply = <&speaker_sdz>; + #sound-dai-cells = <0>; + sound-name-prefix = "Right Rear"; + interrupts-extended = <&pinctrl_ap 11 IRQ_TYPE_LEVEL_LOW>; + ti,imon-slot-no = <12>; + ti,vmon-slot-no = <14>; + }; + + speaker_right_front: codec@3c { + compatible = "ti,sn012776", "ti,tas2764"; + reg = <0x3c>; + SDZ-supply = <&speaker_sdz>; + #sound-dai-cells = <0>; + sound-name-prefix = "Right Front"; + interrupts-extended = <&pinctrl_ap 11 IRQ_TYPE_LEVEL_LOW>; + ti,imon-slot-no = <4>; + ti,vmon-slot-no = <6>; + ti,sdout-force-zero-mask = <0x0f0f>; + }; + + jack_codec: codec@4b { + compatible = "cirrus,cs42l84"; + reg = <0x4b>; + reset-gpios = <&pinctrl_nub 12 GPIO_ACTIVE_HIGH>; + #sound-dai-cells = <0>; + interrupts-extended = <&pinctrl_ap 149 IRQ_TYPE_LEVEL_LOW>; + sound-name-prefix = "Jack"; + }; +}; + &i2c4 { status = "okay"; }; @@ -149,6 +219,36 @@ }; }; +/ { + sound { + compatible = "apple,j493-macaudio", "apple,macaudio"; + model = "MacBook Pro J493"; + + dai-link@0 { + link-name = "Speakers"; + + cpu { + sound-dai = <&mca 0>, <&mca 1>; + }; + codec { + sound-dai = <&speaker_left_front>, <&speaker_left_rear>, + <&speaker_right_front>, <&speaker_right_rear>; + }; + }; + + dai-link@1 { + link-name = "Headphone Jack"; + + cpu { + sound-dai = <&mca 2>; + }; + codec { + sound-dai = <&jack_codec>; + }; + }; + }; +}; + &mtp { status = "okay"; }; From 1b36196194ab7da94fdf36b5fedb32b5964b87da Mon Sep 17 00:00:00 2001 From: Hector Martin Date: Mon, 17 Oct 2022 18:29:28 +0900 Subject: [PATCH 2117/3902] arm64: dts: apple: t6001-j375c: Add USB3 hub GPIO initialization The Mac Studio M1 Max (t6001) model has a built-in USB3 hub. This hub has a firmware flash which is also connected to an AP SPI controller. The hub starts out in reset and the host is expected to bring it out of reset, potentially after upgrading/validating the firmware. We won't be doing anything with the firmware, so just use gpio-hog to flip the two GPIOs needed to bring up the hub chip. Signed-off-by: Hector Martin --- arch/arm64/boot/dts/apple/t6001-j375c.dts | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/arch/arm64/boot/dts/apple/t6001-j375c.dts b/arch/arm64/boot/dts/apple/t6001-j375c.dts index a8694a94fa2793..fb7213e6f996ea 100644 --- a/arch/arm64/boot/dts/apple/t6001-j375c.dts +++ b/arch/arm64/boot/dts/apple/t6001-j375c.dts @@ -29,3 +29,19 @@ compatible = "apple,j375-macaudio", "apple,macaudio"; model = "Mac Studio J375"; }; + +&pinctrl_ap { + usb_hub_oe-hog { + gpio-hog; + gpios = <230 0>; + input; + line-name = "usb-hub-oe"; + }; + + usb_hub_rst-hog { + gpio-hog; + gpios = <231 GPIO_ACTIVE_LOW>; + output-low; + line-name = "usb-hub-rst"; + }; +}; From cf4cf906263034f22906e182e9f33a4ec37514f1 Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Tue, 25 Jan 2022 21:50:59 +0100 Subject: [PATCH 2118/3902] arm64: apple: Add missing power state deps for display The dcp co-processor crashes on HDMI unplug while it apparently tries to notify pmp. Handle "notify_pmp" as a parent dependency for "ps_disp0_fe" and "ps_dispext_fe". Signed-off-by: Janne Grunau --- arch/arm64/boot/dts/apple/t8103-pmgr.dtsi | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/arch/arm64/boot/dts/apple/t8103-pmgr.dtsi b/arch/arm64/boot/dts/apple/t8103-pmgr.dtsi index 9bddd5bf866000..1969123490770c 100644 --- a/arch/arm64/boot/dts/apple/t8103-pmgr.dtsi +++ b/arch/arm64/boot/dts/apple/t8103-pmgr.dtsi @@ -651,7 +651,7 @@ #power-domain-cells = <0>; #reset-cells = <0>; label = "disp0_fe"; - power-domains = <&ps_rmx>; + power-domains = <&ps_rmx>, <&ps_pmp>; apple,always-on; /* TODO: figure out if we can enable PM here */ }; @@ -661,7 +661,7 @@ #power-domain-cells = <0>; #reset-cells = <0>; label = "dispext_fe"; - power-domains = <&ps_rmx>; + power-domains = <&ps_rmx>, <&ps_pmp>; }; ps_dispext_cpu0: power-controller@378 { From 569f5a8f013fd62ea54d8ea3c11f7bfadd93fc13 Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Sun, 24 Apr 2022 11:20:31 +0200 Subject: [PATCH 2119/3902] arm64: apple: t600x: Mark PCIe node as "dma-coherent" Signed-off-by: Janne Grunau --- arch/arm64/boot/dts/apple/t600x-die0.dtsi | 2 ++ 1 file changed, 2 insertions(+) diff --git a/arch/arm64/boot/dts/apple/t600x-die0.dtsi b/arch/arm64/boot/dts/apple/t600x-die0.dtsi index 9797880488d080..867d7450fa921f 100644 --- a/arch/arm64/boot/dts/apple/t600x-die0.dtsi +++ b/arch/arm64/boot/dts/apple/t600x-die0.dtsi @@ -448,6 +448,8 @@ pinctrl-0 = <&pcie_pins>; pinctrl-names = "default"; + dma-coherent; + port00: pci@0,0 { device_type = "pci"; reg = <0x0 0x0 0x0 0x0 0x0>; From 58413a5eb5e5b3302a5b3c277549cb796fd6cf7c Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Fri, 19 Sep 2025 20:32:09 +0200 Subject: [PATCH 2120/3902] arm64: dts: apple: t8103: Mark pcie node as dma-coherent Signed-off-by: Janne Grunau --- arch/arm64/boot/dts/apple/t8103.dtsi | 2 ++ 1 file changed, 2 insertions(+) diff --git a/arch/arm64/boot/dts/apple/t8103.dtsi b/arch/arm64/boot/dts/apple/t8103.dtsi index 84a6ddfe433d76..876195af6a8fa2 100644 --- a/arch/arm64/boot/dts/apple/t8103.dtsi +++ b/arch/arm64/boot/dts/apple/t8103.dtsi @@ -1324,6 +1324,8 @@ pinctrl-0 = <&pcie_pins>; pinctrl-names = "default"; + dma-coherent; + port00: pci@0,0 { device_type = "pci"; reg = <0x0 0x0 0x0 0x0 0x0>; From e0b7309a30e3840bc54919f498f9559f9e6b0493 Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Fri, 19 Sep 2025 20:32:09 +0200 Subject: [PATCH 2121/3902] arm64: dts: apple: t8112: Mark pcie node as dma-coherent Signed-off-by: Janne Grunau --- arch/arm64/boot/dts/apple/t8112.dtsi | 2 ++ 1 file changed, 2 insertions(+) diff --git a/arch/arm64/boot/dts/apple/t8112.dtsi b/arch/arm64/boot/dts/apple/t8112.dtsi index 99cf4a21a6af96..723383efc8581f 100644 --- a/arch/arm64/boot/dts/apple/t8112.dtsi +++ b/arch/arm64/boot/dts/apple/t8112.dtsi @@ -1406,6 +1406,8 @@ pinctrl-0 = <&pcie_pins>; pinctrl-names = "default"; + dma-coherent; + port00: pci@0,0 { device_type = "pci"; reg = <0x0 0x0 0x0 0x0 0x0>; From 4e8aa71bda9e7cb4eb39f1c85e77f23da5c204ff Mon Sep 17 00:00:00 2001 From: Hector Martin Date: Mon, 20 Sep 2021 02:27:09 +0900 Subject: [PATCH 2122/3902] arm64: apple: t8103: Add display controller related device tree nodes The display system is initialized by the bootloader to provide a simple framebuffer at startup. Memory for the framebuffer and heap for the display co-processor are alreay mapped through the IOMMU. IOMMU intialization must preserve this mappings to avoid crashing the display co-processor. The exisitng mappings are caried in the devicetree. They are applied during device attach to ensure the IOMMU framework is aware of these mapping. Mappings are filled by m1n1 during boot. Based on https://lore.kernel.org/asahi/20220923123557.866972-1-thierry.reding@gmail.com arch: arm64: apple: t8103: Add connector type property for DCP* arch: arm64: apple: Add dcp panel node for t8103 based laptops and imacs The panel node will contain among other properties backlight control related properties from the "backlight" node in the ADT. arm64: dts: apple: t8103: Add "ps_disp0_cpu0" as resets for dcp Signed-off-by: Janne Grunau --- arch/arm64/boot/dts/apple/t8103-j274.dts | 4 ++ arch/arm64/boot/dts/apple/t8103-j293.dts | 14 ++++ arch/arm64/boot/dts/apple/t8103-j313.dts | 14 ++++ arch/arm64/boot/dts/apple/t8103-j456.dts | 14 ++++ arch/arm64/boot/dts/apple/t8103-j457.dts | 14 ++++ arch/arm64/boot/dts/apple/t8103-jxxx.dtsi | 10 +++ arch/arm64/boot/dts/apple/t8103.dtsi | 86 +++++++++++++++++++++++ 7 files changed, 156 insertions(+) diff --git a/arch/arm64/boot/dts/apple/t8103-j274.dts b/arch/arm64/boot/dts/apple/t8103-j274.dts index f3b7204618c8ce..2768a1d9ed7af0 100644 --- a/arch/arm64/boot/dts/apple/t8103-j274.dts +++ b/arch/arm64/boot/dts/apple/t8103-j274.dts @@ -22,6 +22,10 @@ }; }; +&dcp { + apple,connector-type = "HDMI-A"; +}; + &bluetooth0 { brcm,board-type = "apple,atlantisb"; }; diff --git a/arch/arm64/boot/dts/apple/t8103-j293.dts b/arch/arm64/boot/dts/apple/t8103-j293.dts index 753c9c4d9adefc..50043beb65db0a 100644 --- a/arch/arm64/boot/dts/apple/t8103-j293.dts +++ b/arch/arm64/boot/dts/apple/t8103-j293.dts @@ -39,6 +39,20 @@ }; }; +&dcp { + panel: panel { + compatible = "apple,panel-j293", "apple,panel"; + width-mm = <286>; + height-mm = <179>; + apple,max-brightness = <525>; + }; +}; + +&framebuffer0 { + panel = <&panel>; + post-init-providers = <&panel>; +}; + &bluetooth0 { brcm,board-type = "apple,honshu"; }; diff --git a/arch/arm64/boot/dts/apple/t8103-j313.dts b/arch/arm64/boot/dts/apple/t8103-j313.dts index 43620b57a63d2e..7e77fe091c6345 100644 --- a/arch/arm64/boot/dts/apple/t8103-j313.dts +++ b/arch/arm64/boot/dts/apple/t8103-j313.dts @@ -31,6 +31,20 @@ }; }; +&dcp { + panel: panel { + compatible = "apple,panel-j313", "apple,panel"; + width-mm = <286>; + height-mm = <179>; + apple,max-brightness = <420>; + }; +}; + +&framebuffer0 { + panel = <&panel>; + post-init-providers = <&panel>; +}; + &bluetooth0 { brcm,board-type = "apple,shikoku"; }; diff --git a/arch/arm64/boot/dts/apple/t8103-j456.dts b/arch/arm64/boot/dts/apple/t8103-j456.dts index d277704b988bcc..0b89b44b12e6da 100644 --- a/arch/arm64/boot/dts/apple/t8103-j456.dts +++ b/arch/arm64/boot/dts/apple/t8103-j456.dts @@ -22,6 +22,20 @@ }; }; +&dcp { + panel: panel { + compatible = "apple,panel-j456", "apple,panel"; + width-mm = <522>; + height-mm = <294>; + apple,max-brightness = <525>; + }; +}; + +&framebuffer0 { + panel = <&panel>; + post-init-providers = <&panel>; +}; + &bluetooth0 { brcm,board-type = "apple,capri"; }; diff --git a/arch/arm64/boot/dts/apple/t8103-j457.dts b/arch/arm64/boot/dts/apple/t8103-j457.dts index 2e7d344f88a54f..c1007dc4385e7c 100644 --- a/arch/arm64/boot/dts/apple/t8103-j457.dts +++ b/arch/arm64/boot/dts/apple/t8103-j457.dts @@ -22,6 +22,20 @@ }; }; +&dcp { + panel: panel { + compatible = "apple,panel-j457", "apple,panel"; + width-mm = <522>; + height-mm = <294>; + apple,max-brightness = <525>; + }; +}; + +&framebuffer0 { + panel = <&panel>; + post-init-providers = <&panel>; +}; + /* * Adjust pcie0's iommu-map to account for the disabled port01. */ diff --git a/arch/arm64/boot/dts/apple/t8103-jxxx.dtsi b/arch/arm64/boot/dts/apple/t8103-jxxx.dtsi index 96121fdb8468c4..59558d9a511ae4 100644 --- a/arch/arm64/boot/dts/apple/t8103-jxxx.dtsi +++ b/arch/arm64/boot/dts/apple/t8103-jxxx.dtsi @@ -12,6 +12,9 @@ / { aliases { bluetooth0 = &bluetooth0; + dcp = &dcp; + disp0 = &display; + disp0_piodma = &disp0_piodma; serial0 = &serial0; serial2 = &serial2; wifi0 = &wifi0; @@ -34,6 +37,13 @@ }; }; + reserved-memory { + #address-cells = <2>; + #size-cells = <2>; + ranges; + /* To be filled by loader */ + }; + memory@800000000 { device_type = "memory"; reg = <0x8 0 0x2 0>; /* To be filled by loader */ diff --git a/arch/arm64/boot/dts/apple/t8103.dtsi b/arch/arm64/boot/dts/apple/t8103.dtsi index 876195af6a8fa2..c54bb7e1a1ade5 100644 --- a/arch/arm64/boot/dts/apple/t8103.dtsi +++ b/arch/arm64/boot/dts/apple/t8103.dtsi @@ -346,6 +346,14 @@ clock-output-names = "clk_200m"; }; + /* Pixel clock? frequency in Hz (compare: 4K@60 VGA clock 533.250 MHz) */ + clk_disp0: clock-disp0 { + compatible = "fixed-clock"; + #clock-cells = <0>; + clock-frequency = <533333328>; + clock-output-names = "clk_disp0"; + }; + /* * This is a fabulated representation of the input clock * to NCO since we don't know the true clock tree. @@ -493,6 +501,76 @@ }; }; + disp0_dart: iommu@231304000 { + compatible = "apple,t8103-dart"; + reg = <0x2 0x31304000 0x0 0x4000>; + #iommu-cells = <1>; + interrupt-parent = <&aic>; + interrupts = ; + status = "disabled"; + }; + + dcp_dart: iommu@23130c000 { + compatible = "apple,t8103-dart"; + reg = <0x2 0x3130c000 0x0 0x4000>; + #iommu-cells = <1>; + interrupt-parent = <&aic>; + interrupts = ; + }; + + dcp_mbox: mbox@231c08000 { + compatible = "apple,t8103-asc-mailbox", "apple,asc-mailbox-v4"; + reg = <0x2 0x31c08000 0x0 0x4000>; + interrupt-parent = <&aic>; + interrupts = , + , + , + ; + interrupt-names = "send-empty", "send-not-empty", + "recv-empty", "recv-not-empty"; + #mbox-cells = <0>; + power-domains = <&ps_disp0_cpu0>; + resets = <&ps_disp0_cpu0>; + }; + + dcp: dcp@231c00000 { + compatible = "apple,t8103-dcp", "apple,dcp"; + mboxes = <&dcp_mbox>; + mbox-names = "mbox"; + iommus = <&dcp_dart 0>; + + reg-names = "coproc", "disp-0", "disp-1", "disp-2", + "disp-3", "disp-4"; + reg = <0x2 0x31c00000 0x0 0x4000>, + <0x2 0x30000000 0x0 0x3e8000>, + <0x2 0x31320000 0x0 0x4000>, + <0x2 0x31344000 0x0 0x4000>, + <0x2 0x31800000 0x0 0x800000>, + <0x2 0x3b3d0000 0x0 0x4000>; + apple,bw-scratch = <&pmgr_dcp 0 5 0x14>; + apple,bw-doorbell = <&pmgr_dcp 1 6>; + power-domains = <&ps_disp0_cpu0>; + resets = <&ps_disp0_cpu0>; + clocks = <&clk_disp0>; + apple,asc-dram-mask = <0xf 0x00000000>; + phandle = <&dcp>; + // required bus properties for 'piodma' subdevice + #address-cells = <2>; + #size-cells = <2>; + + disp0_piodma: piodma { + iommus = <&disp0_dart 4>; + phandle = <&disp0_piodma>; + }; + }; + + display: display-subsystem { + compatible = "apple,display-subsystem"; + iommus = <&disp0_dart 0>; + /* generate phandle explicitly for use in loader */ + phandle = <&display>; + }; + sio_dart: iommu@235004000 { compatible = "apple,t8103-dart"; reg = <0x2 0x35004000 0x0 0x4000>; @@ -730,6 +808,14 @@ reg = <0x2 0x3b700000 0 0x14000>; }; + pmgr_dcp: power-management@23b738000 { + reg = <0x2 0x3b738000 0x0 0x1000>, + <0x2 0x3bc3c000 0x0 0x1000>; + reg-names = "dcp-bw-scratch", "dcp-bw-doorbell"; + #apple,bw-scratch-cells = <3>; + #apple,bw-doorbell-cells = <2>; + }; + pinctrl_ap: pinctrl@23c100000 { compatible = "apple,t8103-pinctrl", "apple,pinctrl"; reg = <0x2 0x3c100000 0x0 0x100000>; From 27c674ca0ee938d56f7e9ca218880916d906c22b Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Fri, 11 Mar 2022 22:14:52 +0100 Subject: [PATCH 2123/3902] arm64: apple: t600x: Add display controller related device tree nodes The display system is initialized by the bootloader to provide a simple framebuffer at startup. Memory for the framebuffer and heap for the display co-processor are alreay mapped through the IOMMU. IOMMU intialization must preserve this mappings to avoid crashing the display co-processor. The exisitng mappings are caried in the devicetree. They are applied during device attach to ensure the IOMMU framework is aware of these mapping. Mappings are filled by m1n1 during boot. Based on https://lore.kernel.org/asahi/20220923123557.866972-1-thierry.reding@gmail.com arch: arm64: apple: t600x: Add connector type property for DCP* arch: arm64: apple: Add dcp panel node for t600x based laptops The panel node will contain among other properties backlight control related properties from the "backlight" node in the ADT. arm64: dts: apple: t600x: Add "ps_disp0_cpu0" as resets for dcp Signed-off-by: Janne Grunau --- arch/arm64/boot/dts/apple/t6000-j314s.dts | 7 ++ arch/arm64/boot/dts/apple/t6000-j316s.dts | 7 ++ arch/arm64/boot/dts/apple/t6001-j314c.dts | 7 ++ arch/arm64/boot/dts/apple/t6001-j316c.dts | 7 ++ arch/arm64/boot/dts/apple/t600x-common.dtsi | 6 ++ arch/arm64/boot/dts/apple/t600x-die0.dtsi | 72 +++++++++++++++++++ .../arm64/boot/dts/apple/t600x-j314-j316.dtsi | 10 +++ arch/arm64/boot/dts/apple/t600x-j375.dtsi | 7 ++ 8 files changed, 123 insertions(+) diff --git a/arch/arm64/boot/dts/apple/t6000-j314s.dts b/arch/arm64/boot/dts/apple/t6000-j314s.dts index dab8e99fa32496..ae79e3236614be 100644 --- a/arch/arm64/boot/dts/apple/t6000-j314s.dts +++ b/arch/arm64/boot/dts/apple/t6000-j314s.dts @@ -25,6 +25,13 @@ brcm,board-type = "apple,maldives"; }; +&panel { + compatible = "apple,panel-j314", "apple,panel-mini-led", "apple,panel"; + width-mm = <302>; + height-mm = <196>; + adj-height-mm = <189>; +}; + &sound { compatible = "apple,j314-macaudio", "apple,macaudio"; model = "MacBook Pro J314"; diff --git a/arch/arm64/boot/dts/apple/t6000-j316s.dts b/arch/arm64/boot/dts/apple/t6000-j316s.dts index 2cdfac3c40c842..272fa1c1712479 100644 --- a/arch/arm64/boot/dts/apple/t6000-j316s.dts +++ b/arch/arm64/boot/dts/apple/t6000-j316s.dts @@ -25,6 +25,13 @@ brcm,board-type = "apple,madagascar"; }; +&panel { + compatible = "apple,panel-j316", "apple,panel-mini-led", "apple,panel"; + width-mm = <346>; + height-mm = <223>; + adj-height-mm = <216>; +}; + &sound { compatible = "apple,j316-macaudio", "apple,macaudio"; model = "MacBook Pro J316"; diff --git a/arch/arm64/boot/dts/apple/t6001-j314c.dts b/arch/arm64/boot/dts/apple/t6001-j314c.dts index 7495698beb0258..81d34507ed81ff 100644 --- a/arch/arm64/boot/dts/apple/t6001-j314c.dts +++ b/arch/arm64/boot/dts/apple/t6001-j314c.dts @@ -25,6 +25,13 @@ brcm,board-type = "apple,maldives"; }; +&panel { + compatible = "apple,panel-j314", "apple,panel-mini-led", "apple,panel"; + width-mm = <302>; + height-mm = <196>; + adj-height-mm = <189>; +}; + &sound { compatible = "apple,j314-macaudio", "apple,macaudio"; model = "MacBook Pro J314"; diff --git a/arch/arm64/boot/dts/apple/t6001-j316c.dts b/arch/arm64/boot/dts/apple/t6001-j316c.dts index 6622b6e225a600..564d927f2fecbd 100644 --- a/arch/arm64/boot/dts/apple/t6001-j316c.dts +++ b/arch/arm64/boot/dts/apple/t6001-j316c.dts @@ -25,6 +25,13 @@ brcm,board-type = "apple,madagascar"; }; +&panel { + compatible = "apple,panel-j316", "apple,panel-mini-led", "apple,panel"; + width-mm = <346>; + height-mm = <223>; + adj-height-mm = <216>; +}; + &sound { compatible = "apple,j316-macaudio", "apple,macaudio"; model = "MacBook Pro J316"; diff --git a/arch/arm64/boot/dts/apple/t600x-common.dtsi b/arch/arm64/boot/dts/apple/t600x-common.dtsi index e20234ef213538..186f0459d6b7e6 100644 --- a/arch/arm64/boot/dts/apple/t600x-common.dtsi +++ b/arch/arm64/boot/dts/apple/t600x-common.dtsi @@ -373,6 +373,12 @@ clock-output-names = "clk_200m"; }; + clk_disp0: clock-disp0 { + compatible = "fixed-clock"; + #clock-cells = <0>; + clock-frequency = <237333328>; + clock-output-names = "clk_disp0"; + }; /* * This is a fabulated representation of the input clock * to NCO since we don't know the true clock tree. diff --git a/arch/arm64/boot/dts/apple/t600x-die0.dtsi b/arch/arm64/boot/dts/apple/t600x-die0.dtsi index 867d7450fa921f..67262dd9726076 100644 --- a/arch/arm64/boot/dts/apple/t600x-die0.dtsi +++ b/arch/arm64/boot/dts/apple/t600x-die0.dtsi @@ -24,6 +24,12 @@ power-domains = <&ps_aic>; }; + pmgr_dcp: power-management@28e3d0000 { + reg = <0x2 0x8e3d0000 0x0 0x4000>; + reg-names = "dcp-fw-pmgr"; + #apple,bw-scratch-cells = <3>; + }; + smc: smc@290400000 { compatible = "apple,t6000-smc", "apple,smc"; reg = <0x2 0x90400000 0x0 0x4000>, @@ -151,6 +157,72 @@ interrupts = ; }; + disp0_dart: iommu@38b304000 { + compatible = "apple,t6000-dart"; + reg = <0x3 0x8b304000 0x0 0x4000>; + #iommu-cells = <1>; + interrupt-parent = <&aic>; + interrupts = ; + status = "disabled"; + }; + + dcp_dart: iommu@38b30c000 { + compatible = "apple,t6000-dart"; + reg = <0x3 0x8b30c000 0x0 0x4000>; + #iommu-cells = <1>; + interrupt-parent = <&aic>; + interrupts = ; + }; + + dcp_mbox: mbox@38bc08000 { + compatible = "apple,t6000-asc-mailbox", "apple,asc-mailbox-v4"; + reg = <0x3 0x8bc08000 0x0 0x4000>; + interrupt-parent = <&aic>; + interrupts = , + , + , + ; + interrupt-names = "send-empty", "send-not-empty", + "recv-empty", "recv-not-empty"; + #mbox-cells = <0>; + power-domains = <&ps_disp0_cpu0>; + }; + + dcp: dcp@38bc00000 { + compatible = "apple,t6000-dcp", "apple,dcp"; + mboxes = <&dcp_mbox>; + mbox-names = "mbox"; + iommus = <&dcp_dart 0>; + + reg-names = "coproc", "disp-0", "disp-1", "disp-2", "disp-3"; + reg = <0x3 0x8bc00000 0x0 0x4000>, + <0x3 0x8a000000 0x0 0x3000000>, + <0x3 0x8b320000 0x0 0x4000>, + <0x3 0x8b344000 0x0 0x4000>, + <0x3 0x8b800000 0x0 0x800000>; + apple,bw-scratch = <&pmgr_dcp 0 4 0x988>; + power-domains = <&ps_disp0_cpu0>; + resets = <&ps_disp0_cpu0>; + clocks = <&clk_disp0>; + apple,asc-dram-mask = <0x1f0 0x00000000>; + phandle = <&dcp>; + // required bus properties for 'piodma' subdevice + #address-cells = <2>; + #size-cells = <2>; + + disp0_piodma: piodma { + iommus = <&disp0_dart 4>; + phandle = <&disp0_piodma>; + }; + }; + + display: display-subsystem { + compatible = "apple,display-subsystem"; + iommus = <&disp0_dart 0>; + /* generate phandle explicitly for use in loader */ + phandle = <&display>; + }; + sio_dart_0: iommu@39b004000 { compatible = "apple,t6000-dart"; reg = <0x3 0x9b004000 0x0 0x4000>; diff --git a/arch/arm64/boot/dts/apple/t600x-j314-j316.dtsi b/arch/arm64/boot/dts/apple/t600x-j314-j316.dtsi index 6ebaabe49a8130..a6530d5db4d872 100644 --- a/arch/arm64/boot/dts/apple/t600x-j314-j316.dtsi +++ b/arch/arm64/boot/dts/apple/t600x-j314-j316.dtsi @@ -20,6 +20,9 @@ atcphy2 = &atcphy2; atcphy3 = &atcphy3; bluetooth0 = &bluetooth0; + dcp = &dcp; + disp0 = &display; + disp0_piodma = &disp0_piodma; serial0 = &serial0; wifi0 = &wifi0; }; @@ -36,6 +39,7 @@ reg = <0 0 0 0>; /* To be filled by loader */ /* Format properties will be added by loader */ status = "disabled"; + panel = &panel; }; }; @@ -61,6 +65,12 @@ status = "okay"; }; +&dcp { + panel: panel { + apple,max-brightness = <500>; + }; +}; + /* USB Type C */ &i2c0 { hpm0: usb-pd@38 { diff --git a/arch/arm64/boot/dts/apple/t600x-j375.dtsi b/arch/arm64/boot/dts/apple/t600x-j375.dtsi index 65b2abd521b10c..0bd2ae5bd802b1 100644 --- a/arch/arm64/boot/dts/apple/t600x-j375.dtsi +++ b/arch/arm64/boot/dts/apple/t600x-j375.dtsi @@ -18,6 +18,9 @@ atcphy2 = &atcphy2; atcphy3 = &atcphy3; bluetooth0 = &bluetooth0; + dcp = &dcp; + disp0 = &display; + disp0_piodma = &disp0_piodma; ethernet0 = ðernet0; serial0 = &serial0; wifi0 = &wifi0; @@ -48,6 +51,10 @@ status = "okay"; }; +&dcp { + apple,connector-type = "HDMI-A"; +}; + /* USB Type C */ &i2c0 { hpm0: usb-pd@38 { From 61b24428dcd9b9d0f53e1d27a77e8628eecce707 Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Sun, 20 Nov 2022 20:22:57 +0100 Subject: [PATCH 2124/3902] arm64: dts: apple: t8112: Add dcp/disp0 nodes arm64: dts: apple: t8112: Add "ps_disp0_cpu0" as resets for dcp arm64: dts: apple: t8112-j473: Add dptx-phy power-domain The HDMI output used by framebuffer0 requires the display controller and external DP phy power-domains to remain active. Signed-off-by: Janne Grunau --- arch/arm64/boot/dts/apple/t8112-j413.dts | 15 ++++ arch/arm64/boot/dts/apple/t8112-j415.dts | 15 ++++ arch/arm64/boot/dts/apple/t8112-j473.dts | 9 +++ arch/arm64/boot/dts/apple/t8112-j493.dts | 14 ++++ arch/arm64/boot/dts/apple/t8112-jxxx.dtsi | 3 + arch/arm64/boot/dts/apple/t8112.dtsi | 83 +++++++++++++++++++++++ 6 files changed, 139 insertions(+) diff --git a/arch/arm64/boot/dts/apple/t8112-j413.dts b/arch/arm64/boot/dts/apple/t8112-j413.dts index f6450ef3a0c6e1..0077ce45cc5154 100644 --- a/arch/arm64/boot/dts/apple/t8112-j413.dts +++ b/arch/arm64/boot/dts/apple/t8112-j413.dts @@ -37,6 +37,21 @@ }; }; +&dcp { + panel: panel { + compatible = "apple,panel-j413", "apple,panel"; + width-mm = <290>; + height-mm = <189>; + adj-height-mm = <181>; + apple,max-brightness = <525>; + }; +}; + +&framebuffer0 { + panel = <&panel>; + post-init-providers = <&panel>; +}; + /* * Force the bus number assignments so that we can declare some of the * on-board devices and properties that are populated by the bootloader diff --git a/arch/arm64/boot/dts/apple/t8112-j415.dts b/arch/arm64/boot/dts/apple/t8112-j415.dts index 2c969eb767a7d8..09387fc5ca46f0 100644 --- a/arch/arm64/boot/dts/apple/t8112-j415.dts +++ b/arch/arm64/boot/dts/apple/t8112-j415.dts @@ -37,6 +37,21 @@ }; }; +&dcp { + panel: panel { + compatible = "apple,panel-j415", "apple,panel"; + width-mm = <327>; + height-mm = <211>; + adj-height-mm = <204>; + apple,max-brightness = <500>; + }; +}; + +&framebuffer0 { + panel = <&panel>; + post-init-providers = <&panel>; +}; + /* * Force the bus number assignments so that we can declare some of the * on-board devices and properties that are populated by the bootloader diff --git a/arch/arm64/boot/dts/apple/t8112-j473.dts b/arch/arm64/boot/dts/apple/t8112-j473.dts index effdfae8646949..6d8eb2114415c6 100644 --- a/arch/arm64/boot/dts/apple/t8112-j473.dts +++ b/arch/arm64/boot/dts/apple/t8112-j473.dts @@ -24,6 +24,15 @@ }; }; +&framebuffer0 { + power-domains = <&ps_disp0_cpu0>, <&ps_dptx_ext_phy>; +}; + +/* disable dcp until it is supported */ +&dcp { + status = "disabled"; +}; + /* * Keep the power-domains used for the HDMI port on. */ diff --git a/arch/arm64/boot/dts/apple/t8112-j493.dts b/arch/arm64/boot/dts/apple/t8112-j493.dts index d25794fd88e355..03cb807cf59d71 100644 --- a/arch/arm64/boot/dts/apple/t8112-j493.dts +++ b/arch/arm64/boot/dts/apple/t8112-j493.dts @@ -52,6 +52,20 @@ apple,always-on; }; +&dcp { + panel: panel { + compatible = "apple,panel-j493", "apple,panel"; + width-mm = <286>; + height-mm = <179>; + apple,max-brightness = <525>; + }; +}; + +&framebuffer0 { + panel = <&panel>; + post-init-providers = <&panel>; +}; + &display_dfr { status = "okay"; }; diff --git a/arch/arm64/boot/dts/apple/t8112-jxxx.dtsi b/arch/arm64/boot/dts/apple/t8112-jxxx.dtsi index 562e7a25a1e884..98f2d6af828d2d 100644 --- a/arch/arm64/boot/dts/apple/t8112-jxxx.dtsi +++ b/arch/arm64/boot/dts/apple/t8112-jxxx.dtsi @@ -13,6 +13,9 @@ aliases { atcphy0 = &atcphy0; atcphy1 = &atcphy1; + dcp = &dcp; + disp0 = &display; + disp0_piodma = &disp0_piodma; serial0 = &serial0; serial2 = &serial2; }; diff --git a/arch/arm64/boot/dts/apple/t8112.dtsi b/arch/arm64/boot/dts/apple/t8112.dtsi index 723383efc8581f..07807287b75b18 100644 --- a/arch/arm64/boot/dts/apple/t8112.dtsi +++ b/arch/arm64/boot/dts/apple/t8112.dtsi @@ -371,6 +371,14 @@ clock-output-names = "nco_ref"; }; + /* Pixel clock? frequency in Hz (compare: 4K@60 VGA clock 533.250 MHz) */ + clk_disp0: clock-disp0 { + compatible = "fixed-clock"; + #clock-cells = <0>; + clock-frequency = <533333328>; + clock-output-names = "clk_disp0"; + }; + reserved-memory { #address-cells = <2>; #size-cells = <2>; @@ -508,6 +516,75 @@ }; }; + disp0_dart: iommu@231304000 { + compatible = "apple,t8112-dart", "apple,t8110-dart"; + reg = <0x2 0x31304000 0x0 0x4000>; + #iommu-cells = <1>; + interrupt-parent = <&aic>; + interrupts = ; + status = "disabled"; + }; + + dcp_dart: iommu@23130c000 { + compatible = "apple,t8112-dart", "apple,t8110-dart"; + reg = <0x2 0x3130c000 0x0 0x4000>; + #iommu-cells = <1>; + interrupt-parent = <&aic>; + interrupts = ; + }; + + dcp_mbox: mbox@231c08000 { + compatible = "apple,t8112-asc-mailbox", "apple,asc-mailbox-v4"; + reg = <0x2 0x31c08000 0x0 0x4000>; + interrupt-parent = <&aic>; + interrupts = , + , + , + ; + interrupt-names = "send-empty", "send-not-empty", + "recv-empty", "recv-not-empty"; + #mbox-cells = <0>; + power-domains = <&ps_disp0_cpu0>; + resets = <&ps_disp0_cpu0>; + }; + + dcp: dcp@231c00000 { + compatible = "apple,t8112-dcp", "apple,dcp"; + mboxes = <&dcp_mbox>; + mbox-names = "mbox"; + iommus = <&dcp_dart 5>; + + /* the ADT has 2 additional regs which seems to be unused */ + reg-names = "coproc", "disp-0", "disp-1", "disp-2", "disp-3"; + reg = <0x2 0x31c00000 0x0 0x4000>, + <0x2 0x30000000 0x0 0x61c000>, + <0x2 0x31320000 0x0 0x4000>, + <0x2 0x31344000 0x0 0x4000>, + <0x2 0x31800000 0x0 0x800000>; + apple,bw-scratch = <&pmgr_dcp 0 4 0x5d8>; + power-domains = <&ps_disp0_cpu0>; + resets = <&ps_disp0_cpu0>; + clocks = <&clk_disp0>; + apple,asc-dram-mask = <0x0 0x0>; + phandle = <&dcp>; + // required bus properties for 'piodma' subdevice + #address-cells = <2>; + #size-cells = <2>; + + disp0_piodma: piodma { + iommus = <&disp0_dart 4>; + phandle = <&disp0_piodma>; + }; + }; + + display: display-subsystem { + compatible = "apple,display-subsystem"; + /* disp_dart0 must be 1st since it is locked */ + iommus = <&disp0_dart 0>; + /* generate phandle explicitly for use in loader */ + phandle = <&display>; + }; + sio_dart: iommu@235004000 { compatible = "apple,t8110-dart"; reg = <0x2 0x35004000 0x0 0x4000>; @@ -737,6 +814,12 @@ /* child nodes are added in t8103-pmgr.dtsi */ }; + pmgr_dcp: power-management@23b3d0000 { + reg = <0x2 0x3b3d0000 0x0 0x4000>; + reg-names = "dcp-bw-scratch"; + #apple,bw-scratch-cells = <3>; + }; + pinctrl_ap: pinctrl@23c100000 { compatible = "apple,t8112-pinctrl", "apple,pinctrl"; reg = <0x2 0x3c100000 0x0 0x100000>; From c4836b9aa1ffbd94fbbb8c00b022b755f6447dc7 Mon Sep 17 00:00:00 2001 From: Hector Martin Date: Tue, 13 Dec 2022 00:17:35 +0900 Subject: [PATCH 2125/3902] arm64: dts: apple: t600x: Add DCP power domain to missing devices Signed-off-by: Hector Martin --- arch/arm64/boot/dts/apple/t600x-die0.dtsi | 2 ++ arch/arm64/boot/dts/apple/t600x-j314-j316.dtsi | 4 +++- arch/arm64/boot/dts/apple/t600x-j375.dtsi | 1 + arch/arm64/boot/dts/apple/t600x-pmgr.dtsi | 2 -- 4 files changed, 6 insertions(+), 3 deletions(-) diff --git a/arch/arm64/boot/dts/apple/t600x-die0.dtsi b/arch/arm64/boot/dts/apple/t600x-die0.dtsi index 67262dd9726076..8de99a936adc21 100644 --- a/arch/arm64/boot/dts/apple/t600x-die0.dtsi +++ b/arch/arm64/boot/dts/apple/t600x-die0.dtsi @@ -164,6 +164,7 @@ interrupt-parent = <&aic>; interrupts = ; status = "disabled"; + power-domains = <&ps_disp0_cpu0>; }; dcp_dart: iommu@38b30c000 { @@ -172,6 +173,7 @@ #iommu-cells = <1>; interrupt-parent = <&aic>; interrupts = ; + power-domains = <&ps_disp0_cpu0>; }; dcp_mbox: mbox@38bc08000 { diff --git a/arch/arm64/boot/dts/apple/t600x-j314-j316.dtsi b/arch/arm64/boot/dts/apple/t600x-j314-j316.dtsi index a6530d5db4d872..c17a77bacd43ed 100644 --- a/arch/arm64/boot/dts/apple/t600x-j314-j316.dtsi +++ b/arch/arm64/boot/dts/apple/t600x-j314-j316.dtsi @@ -39,7 +39,9 @@ reg = <0 0 0 0>; /* To be filled by loader */ /* Format properties will be added by loader */ status = "disabled"; - panel = &panel; + panel = <&panel>; + post-init-providers = <&panel>; + power-domains = <&ps_disp0_cpu0>; }; }; diff --git a/arch/arm64/boot/dts/apple/t600x-j375.dtsi b/arch/arm64/boot/dts/apple/t600x-j375.dtsi index 0bd2ae5bd802b1..30d549bc32f820 100644 --- a/arch/arm64/boot/dts/apple/t600x-j375.dtsi +++ b/arch/arm64/boot/dts/apple/t600x-j375.dtsi @@ -38,6 +38,7 @@ reg = <0 0 0 0>; /* To be filled by loader */ /* Format properties will be added by loader */ status = "disabled"; + power-domains = <&ps_disp0_cpu0>; }; }; diff --git a/arch/arm64/boot/dts/apple/t600x-pmgr.dtsi b/arch/arm64/boot/dts/apple/t600x-pmgr.dtsi index cc2627eafc899d..3f507cbc65f0c8 100644 --- a/arch/arm64/boot/dts/apple/t600x-pmgr.dtsi +++ b/arch/arm64/boot/dts/apple/t600x-pmgr.dtsi @@ -1297,7 +1297,6 @@ #reset-cells = <0>; label = DIE_LABEL(disp0_fe); power-domains = <&DIE_NODE(ps_afnc2_lw0)>; - apple,always-on; /* TODO: figure out if we can enable PM here */ }; DIE_NODE(ps_disp0_cpu0): power-controller@350 { @@ -1307,7 +1306,6 @@ #reset-cells = <0>; label = DIE_LABEL(disp0_cpu0); power-domains = <&DIE_NODE(ps_disp0_fe)>; - apple,always-on; /* TODO: figure out if we can enable PM here */ apple,min-state = <4>; }; From 784bb756e9d8b20d1ec85141e49a34d681ad16dd Mon Sep 17 00:00:00 2001 From: Hector Martin Date: Tue, 13 Dec 2022 00:17:35 +0900 Subject: [PATCH 2126/3902] arm64: dts: apple: t8103: Add DCP power domain to missing devices Removes the "apple,always-on" property from ps_disp0_fe/cpu0. Signed-off-by: Janne Grunau --- arch/arm64/boot/dts/apple/t8103-jxxx.dtsi | 1 + arch/arm64/boot/dts/apple/t8103-pmgr.dtsi | 2 -- arch/arm64/boot/dts/apple/t8103.dtsi | 2 ++ 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/arch/arm64/boot/dts/apple/t8103-jxxx.dtsi b/arch/arm64/boot/dts/apple/t8103-jxxx.dtsi index 59558d9a511ae4..7ca9da15c8171d 100644 --- a/arch/arm64/boot/dts/apple/t8103-jxxx.dtsi +++ b/arch/arm64/boot/dts/apple/t8103-jxxx.dtsi @@ -32,6 +32,7 @@ framebuffer0: framebuffer@0 { compatible = "apple,simple-framebuffer", "simple-framebuffer"; reg = <0 0 0 0>; /* To be filled by loader */ + power-domains = <&ps_disp0_cpu0>; /* Format properties will be added by loader */ status = "disabled"; }; diff --git a/arch/arm64/boot/dts/apple/t8103-pmgr.dtsi b/arch/arm64/boot/dts/apple/t8103-pmgr.dtsi index 1969123490770c..f0ae11bf6ce688 100644 --- a/arch/arm64/boot/dts/apple/t8103-pmgr.dtsi +++ b/arch/arm64/boot/dts/apple/t8103-pmgr.dtsi @@ -652,7 +652,6 @@ #reset-cells = <0>; label = "disp0_fe"; power-domains = <&ps_rmx>, <&ps_pmp>; - apple,always-on; /* TODO: figure out if we can enable PM here */ }; ps_dispext_fe: power-controller@368 { @@ -1008,7 +1007,6 @@ #reset-cells = <0>; label = "disp0_cpu0"; power-domains = <&ps_disp0_fe>; - apple,always-on; /* TODO: figure out if we can enable PM here */ apple,min-state = <4>; }; }; diff --git a/arch/arm64/boot/dts/apple/t8103.dtsi b/arch/arm64/boot/dts/apple/t8103.dtsi index c54bb7e1a1ade5..0f3fe781929efb 100644 --- a/arch/arm64/boot/dts/apple/t8103.dtsi +++ b/arch/arm64/boot/dts/apple/t8103.dtsi @@ -507,6 +507,7 @@ #iommu-cells = <1>; interrupt-parent = <&aic>; interrupts = ; + power-domains = <&ps_disp0_cpu0>; status = "disabled"; }; @@ -516,6 +517,7 @@ #iommu-cells = <1>; interrupt-parent = <&aic>; interrupts = ; + power-domains = <&ps_disp0_cpu0>; }; dcp_mbox: mbox@231c08000 { From cb6aa33a46e77186f98daa1731541644d8c90894 Mon Sep 17 00:00:00 2001 From: Hector Martin Date: Tue, 13 Dec 2022 00:17:35 +0900 Subject: [PATCH 2127/3902] arm64: dts: apple: t8112: Add DCP power domain to missing devices Signed-off-by: Janne Grunau --- arch/arm64/boot/dts/apple/t8112-jxxx.dtsi | 1 + arch/arm64/boot/dts/apple/t8112-pmgr.dtsi | 2 -- arch/arm64/boot/dts/apple/t8112.dtsi | 2 ++ 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/arch/arm64/boot/dts/apple/t8112-jxxx.dtsi b/arch/arm64/boot/dts/apple/t8112-jxxx.dtsi index 98f2d6af828d2d..35565dbf535381 100644 --- a/arch/arm64/boot/dts/apple/t8112-jxxx.dtsi +++ b/arch/arm64/boot/dts/apple/t8112-jxxx.dtsi @@ -30,6 +30,7 @@ framebuffer0: framebuffer@0 { compatible = "apple,simple-framebuffer", "simple-framebuffer"; reg = <0 0 0 0>; /* To be filled by loader */ + power-domains = <&ps_disp0_cpu0>; /* Format properties will be added by loader */ status = "disabled"; }; diff --git a/arch/arm64/boot/dts/apple/t8112-pmgr.dtsi b/arch/arm64/boot/dts/apple/t8112-pmgr.dtsi index 8b3297d75992d3..276f1ab35f06a3 100644 --- a/arch/arm64/boot/dts/apple/t8112-pmgr.dtsi +++ b/arch/arm64/boot/dts/apple/t8112-pmgr.dtsi @@ -669,7 +669,6 @@ #reset-cells = <0>; label = "disp0_sys"; power-domains = <&ps_rmx1>; - apple,always-on; /* TODO: figure out if we can enable PM here */ }; ps_disp0_fe: power-controller@378 { @@ -679,7 +678,6 @@ #reset-cells = <0>; label = "disp0_fe"; power-domains = <&ps_disp0_sys>, <&ps_pmp>; - apple,always-on; /* TODO: figure out if we can enable PM here */ }; ps_dispext_sys: power-controller@380 { diff --git a/arch/arm64/boot/dts/apple/t8112.dtsi b/arch/arm64/boot/dts/apple/t8112.dtsi index 07807287b75b18..f5e01a722fe6a4 100644 --- a/arch/arm64/boot/dts/apple/t8112.dtsi +++ b/arch/arm64/boot/dts/apple/t8112.dtsi @@ -522,6 +522,7 @@ #iommu-cells = <1>; interrupt-parent = <&aic>; interrupts = ; + power-domains = <&ps_disp0_cpu0>; status = "disabled"; }; @@ -531,6 +532,7 @@ #iommu-cells = <1>; interrupt-parent = <&aic>; interrupts = ; + power-domains = <&ps_disp0_cpu0>; }; dcp_mbox: mbox@231c08000 { From ea591b4c0647058d43b9ccba4e1ef687596a3e19 Mon Sep 17 00:00:00 2001 From: Asahi Lina Date: Wed, 2 Nov 2022 15:58:07 +0900 Subject: [PATCH 2128/3902] scripts/dtc: Add support for floating-point literals Signed-off-by: Asahi Lina --- scripts/dtc/data.c | 27 +++++++++++++++++++++++++++ scripts/dtc/dtc-lexer.l | 22 ++++++++++++++++++++++ scripts/dtc/dtc-parser.y | 16 ++++++++++++++++ scripts/dtc/dtc.h | 1 + 4 files changed, 66 insertions(+) diff --git a/scripts/dtc/data.c b/scripts/dtc/data.c index 5b25aa06041613..ce449824c80a06 100644 --- a/scripts/dtc/data.c +++ b/scripts/dtc/data.c @@ -184,6 +184,33 @@ struct data data_append_integer(struct data d, uint64_t value, int bits) } } +struct data data_append_float(struct data d, double value, int bits) +{ + float f32; + uint32_t u32; + double f64; + uint64_t u64; + fdt32_t value_32; + fdt64_t value_64; + + switch (bits) { + case 32: + f32 = value; + memcpy(&u32, &f32, sizeof(u32)); + value_32 = cpu_to_fdt32(u32); + return data_append_data(d, &value_32, 4); + + case 64: + f64 = value; + memcpy(&u64, &f64, sizeof(u64)); + value_64 = cpu_to_fdt64(u64); + return data_append_data(d, &value_64, 8); + + default: + die("Invalid literal size (%d)\n", bits); + } +} + struct data data_append_re(struct data d, uint64_t address, uint64_t size) { struct fdt_reserve_entry re; diff --git a/scripts/dtc/dtc-lexer.l b/scripts/dtc/dtc-lexer.l index 15d585c8079802..bd750717aa3a54 100644 --- a/scripts/dtc/dtc-lexer.l +++ b/scripts/dtc/dtc-lexer.l @@ -151,6 +151,28 @@ static void PRINTF(1, 2) lexical_error(const char *fmt, ...); return DT_LABEL; } +[-+]?(([0-9]+\.[0-9]*)|([0-9]*\.[0-9]+))(e[-+]?[0-9]+)?f? { + char *e; + DPRINT("Floating-point Literal: '%s'\n", yytext); + + errno = 0; + yylval.floating = strtod(yytext, &e); + + if (*e && (*e != 'f' || e[1])) { + lexical_error("Bad floating-point literal '%s'", + yytext); + } + + if (errno == ERANGE) + lexical_error("Floating-point literal '%s' out of range", + yytext); + else + /* ERANGE is the only strtod error triggerable + * by strings matching the pattern */ + assert(errno == 0); + return DT_FP_LITERAL; + } + {LABEL} { /* Missed includes or macro definitions while * preprocessing can lead to unexpected identifiers in diff --git a/scripts/dtc/dtc-parser.y b/scripts/dtc/dtc-parser.y index 4d5eece5262434..225a6b41b14fcf 100644 --- a/scripts/dtc/dtc-parser.y +++ b/scripts/dtc/dtc-parser.y @@ -48,6 +48,7 @@ static bool is_ref_relative(const char *ref) struct node *nodelist; struct reserve_info *re; uint64_t integer; + double floating; unsigned int flags; } @@ -61,6 +62,7 @@ static bool is_ref_relative(const char *ref) %token DT_OMIT_NO_REF %token DT_PROPNODENAME %token DT_LITERAL +%token DT_FP_LITERAL %token DT_CHAR_LITERAL %token DT_BYTE %token DT_STRING @@ -86,6 +88,7 @@ static bool is_ref_relative(const char *ref) %type subnode %type subnodes +%type floating_prim %type integer_prim %type integer_unary %type integer_mul @@ -395,6 +398,15 @@ arrayprefix: $$.data = data_add_marker(empty_data, TYPE_UINT32, NULL); $$.bits = 32; } + | arrayprefix floating_prim + { + if ($1.bits < 32) { + ERROR(&@2, "Floating-point values must be" + " 32-bit or 64-bit"); + } + + $$.data = data_append_float($1.data, $2, $1.bits); + } | arrayprefix integer_prim { if ($1.bits < 64) { @@ -439,6 +451,10 @@ arrayprefix: } ; +floating_prim: + DT_FP_LITERAL + ; + integer_prim: DT_LITERAL | DT_CHAR_LITERAL diff --git a/scripts/dtc/dtc.h b/scripts/dtc/dtc.h index 3a220b9afc99f9..cad8be1440bd69 100644 --- a/scripts/dtc/dtc.h +++ b/scripts/dtc/dtc.h @@ -177,6 +177,7 @@ struct data data_insert_at_marker(struct data d, struct marker *m, struct data data_merge(struct data d1, struct data d2); struct data data_append_cell(struct data d, cell_t word); struct data data_append_integer(struct data d, uint64_t word, int bits); +struct data data_append_float(struct data d, double value, int bits); struct data data_append_re(struct data d, uint64_t address, uint64_t size); struct data data_append_addr(struct data d, uint64_t addr); struct data data_append_byte(struct data d, uint8_t byte); From 64fd800a96b0fd656972c3c0ab44188bcb81cad8 Mon Sep 17 00:00:00 2001 From: Asahi Lina Date: Thu, 18 Aug 2022 02:15:43 +0900 Subject: [PATCH 2129/3902] arm64: dts: apple: t8103: Add downstream gpu properties to be dropped Signed-off-by: Asahi Lina --- arch/arm64/boot/dts/apple/t8103-j274.dts | 4 ++ arch/arm64/boot/dts/apple/t8103-j456.dts | 4 ++ arch/arm64/boot/dts/apple/t8103-j457.dts | 4 ++ arch/arm64/boot/dts/apple/t8103.dtsi | 86 ++++++++++++++++++++++-- 4 files changed, 94 insertions(+), 4 deletions(-) diff --git a/arch/arm64/boot/dts/apple/t8103-j274.dts b/arch/arm64/boot/dts/apple/t8103-j274.dts index 2768a1d9ed7af0..9396c8a010ab3d 100644 --- a/arch/arm64/boot/dts/apple/t8103-j274.dts +++ b/arch/arm64/boot/dts/apple/t8103-j274.dts @@ -131,3 +131,7 @@ }; }; + +&gpu { + apple,perf-base-pstate = <3>; +}; diff --git a/arch/arm64/boot/dts/apple/t8103-j456.dts b/arch/arm64/boot/dts/apple/t8103-j456.dts index 0b89b44b12e6da..c384d4dfd19a36 100644 --- a/arch/arm64/boot/dts/apple/t8103-j456.dts +++ b/arch/arm64/boot/dts/apple/t8103-j456.dts @@ -133,3 +133,7 @@ }; }; }; + +&gpu { + apple,perf-base-pstate = <3>; +}; diff --git a/arch/arm64/boot/dts/apple/t8103-j457.dts b/arch/arm64/boot/dts/apple/t8103-j457.dts index c1007dc4385e7c..28e3eedfc35bf6 100644 --- a/arch/arm64/boot/dts/apple/t8103-j457.dts +++ b/arch/arm64/boot/dts/apple/t8103-j457.dts @@ -114,3 +114,7 @@ }; }; }; + +&gpu { + apple,perf-base-pstate = <3>; +}; diff --git a/arch/arm64/boot/dts/apple/t8103.dtsi b/arch/arm64/boot/dts/apple/t8103.dtsi index 0f3fe781929efb..7e7f35776418f8 100644 --- a/arch/arm64/boot/dts/apple/t8103.dtsi +++ b/arch/arm64/boot/dts/apple/t8103.dtsi @@ -303,6 +303,50 @@ #endif }; + gpu_opp: opp-table-gpu { + compatible = "operating-points-v2"; + + /* + * NOTE: The voltage and power values are device-specific and + * must be filled in by the bootloader. + */ + opp00 { + opp-hz = /bits/ 64 <0>; + opp-microvolt = <400000>; + opp-microwatt = <0>; + }; + opp01 { + opp-hz = /bits/ 64 <396000000>; + opp-microvolt = <603000>; + opp-microwatt = <3714690>; + }; + opp02 { + opp-hz = /bits/ 64 <528000000>; + opp-microvolt = <640000>; + opp-microwatt = <5083260>; + }; + opp03 { + opp-hz = /bits/ 64 <720000000>; + opp-microvolt = <690000>; + opp-microwatt = <7429380>; + }; + opp04 { + opp-hz = /bits/ 64 <924000000>; + opp-microvolt = <784000>; + opp-microwatt = <11730600>; + }; + opp05 { + opp-hz = /bits/ 64 <1128000000>; + opp-microvolt = <862000>; + opp-microwatt = <17009370>; + }; + opp06 { + opp-hz = /bits/ 64 <1278000000>; + opp-microvolt = <931000>; + opp-microwatt = <19551000>; + }; + }; + timer { compatible = "arm,armv8-timer"; interrupt-parent = <&aic>; @@ -382,15 +426,15 @@ }; uat_handoff: uat-handoff { - status = "disabled"; + reg = <0 0 0 0>; }; uat_pagetables: uat-pagetables { - status = "disabled"; + reg = <0 0 0 0>; }; uat_ttbs: uat-ttbs { - status = "disabled"; + reg = <0 0 0 0>; }; }; @@ -403,7 +447,7 @@ nonposted-mmio; gpu: gpu@206400000 { - compatible = "apple,agx-g13g"; + compatible = "apple,agx-t8103", "apple,agx-g13g"; reg = <0x2 0x6400000 0 0x40000>, <0x2 0x4000000 0 0x1000000>; reg-names = "asc", "sgx"; @@ -415,6 +459,40 @@ "hw-cal-a", "hw-cal-b", "globals"; apple,firmware-abi = <0 0 0>; + + apple,firmware-version = <12 3 0>; + apple,firmware-compat = <12 3 0>; + + operating-points-v2 = <&gpu_opp>; + apple,perf-base-pstate = <1>; + apple,min-sram-microvolt = <850000>; + apple,avg-power-filter-tc-ms = <1000>; + apple,avg-power-ki-only = <7.5>; + apple,avg-power-kp = <4.0>; + apple,avg-power-min-duty-cycle = <40>; + apple,avg-power-target-filter-tc = <125>; + apple,fast-die0-integral-gain = <200.0>; + apple,fast-die0-proportional-gain = <5.0>; + apple,perf-filter-drop-threshold = <0>; + apple,perf-filter-time-constant = <5>; + apple,perf-filter-time-constant2 = <50>; + apple,perf-integral-gain2 = <0.197392>; + apple,perf-integral-min-clamp = <0>; + apple,perf-proportional-gain2 = <6.853981>; + apple,perf-tgt-utilization = <85>; + apple,power-sample-period = <8>; + apple,power-zones = <30000 100 6875>; + apple,ppm-filter-time-constant-ms = <100>; + apple,ppm-ki = <91.5>; + apple,ppm-kp = <6.9>; + apple,pwr-filter-time-constant = <313>; + apple,pwr-integral-gain = <0.0202129>; + apple,pwr-integral-min-clamp = <0>; + apple,pwr-min-duty-cycle = <40>; + apple,pwr-proportional-gain = <5.2831855>; + + apple,core-leak-coef = <1000.0>; + apple,sram-leak-coef = <45.0>; }; agx_mbox: mbox@206408000 { From badbb5733917c2ca8785ce2b44a38b966e95a36b Mon Sep 17 00:00:00 2001 From: Asahi Lina Date: Thu, 3 Nov 2022 01:03:44 +0900 Subject: [PATCH 2130/3902] arm64: dts: apple: t600x: Add downstream gpu properties to be dropped Signed-off-by: Asahi Lina --- arch/arm64/boot/dts/apple/t6000.dtsi | 4 +- arch/arm64/boot/dts/apple/t6001-j375c.dts | 9 ++++ arch/arm64/boot/dts/apple/t6001.dtsi | 6 ++- arch/arm64/boot/dts/apple/t6002-j375d.dts | 9 ++++ arch/arm64/boot/dts/apple/t6002.dtsi | 6 ++- arch/arm64/boot/dts/apple/t600x-common.dtsi | 50 +++++++++++++++++++-- arch/arm64/boot/dts/apple/t600x-die0.dtsi | 37 +++++++++++++++ 7 files changed, 115 insertions(+), 6 deletions(-) diff --git a/arch/arm64/boot/dts/apple/t6000.dtsi b/arch/arm64/boot/dts/apple/t6000.dtsi index 0ad77c98073fe6..c9e4e52d9aac92 100644 --- a/arch/arm64/boot/dts/apple/t6000.dtsi +++ b/arch/arm64/boot/dts/apple/t6000.dtsi @@ -9,6 +9,8 @@ /* This chip is just a cut down version of t6001, so include it and disable the missing parts */ +#define GPU_REPEAT(x) + #include "t6001.dtsi" / { @@ -18,5 +20,5 @@ /delete-node/ &pmgr_south; &gpu { - compatible = "apple,agx-g13s"; + compatible = "apple,agx-t6000", "apple,agx-g13x"; }; diff --git a/arch/arm64/boot/dts/apple/t6001-j375c.dts b/arch/arm64/boot/dts/apple/t6001-j375c.dts index fb7213e6f996ea..68e2b120117840 100644 --- a/arch/arm64/boot/dts/apple/t6001-j375c.dts +++ b/arch/arm64/boot/dts/apple/t6001-j375c.dts @@ -45,3 +45,12 @@ line-name = "usb-hub-rst"; }; }; + +&gpu { + apple,avg-power-ki-only = <0.6375>; + apple,avg-power-kp = <0.58>; + apple,avg-power-target-filter-tc = <1>; + apple,perf-base-pstate = <3>; + apple,ppm-ki = <5.8>; + apple,ppm-kp = <0.355>; +}; diff --git a/arch/arm64/boot/dts/apple/t6001.dtsi b/arch/arm64/boot/dts/apple/t6001.dtsi index 6dcb71a1d65a8d..9dffa61db0cef5 100644 --- a/arch/arm64/boot/dts/apple/t6001.dtsi +++ b/arch/arm64/boot/dts/apple/t6001.dtsi @@ -16,6 +16,10 @@ #include "multi-die-cpp.h" +#ifndef GPU_REPEAT +# define GPU_REPEAT(x) +#endif + #include "t600x-common.dtsi" / { @@ -65,5 +69,5 @@ }; &gpu { - compatible = "apple,agx-g13c", "apple,agx-g13s"; + compatible = "apple,agx-t6001", "apple,agx-g13c", "apple,agx-g13s"; }; diff --git a/arch/arm64/boot/dts/apple/t6002-j375d.dts b/arch/arm64/boot/dts/apple/t6002-j375d.dts index 65743fea3f1068..c04597225b6ade 100644 --- a/arch/arm64/boot/dts/apple/t6002-j375d.dts +++ b/arch/arm64/boot/dts/apple/t6002-j375d.dts @@ -211,3 +211,12 @@ /delete-node/ &ps_disp0_cpu0_die1; /delete-node/ &ps_disp0_fe_die1; + +&gpu { + apple,avg-power-ki-only = <0.6375>; + apple,avg-power-kp = <0.58>; + apple,avg-power-target-filter-tc = <1>; + apple,perf-base-pstate = <3>; + apple,ppm-ki = <5.8>; + apple,ppm-kp = <0.355>; +}; diff --git a/arch/arm64/boot/dts/apple/t6002.dtsi b/arch/arm64/boot/dts/apple/t6002.dtsi index a532e5401c4ec4..ce88211c0c22da 100644 --- a/arch/arm64/boot/dts/apple/t6002.dtsi +++ b/arch/arm64/boot/dts/apple/t6002.dtsi @@ -16,6 +16,10 @@ #include "multi-die-cpp.h" +#ifndef GPU_REPEAT +# define GPU_REPEAT(x) +#endif + #include "t600x-common.dtsi" / { @@ -303,5 +307,5 @@ }; &gpu { - compatible = "apple,agx-g13d", "apple,agx-g13s"; + compatible = "apple,agx-t6002", "apple,agx-g13d", "apple,agx-g13s"; }; diff --git a/arch/arm64/boot/dts/apple/t600x-common.dtsi b/arch/arm64/boot/dts/apple/t600x-common.dtsi index 186f0459d6b7e6..5e54b03cf142f0 100644 --- a/arch/arm64/boot/dts/apple/t600x-common.dtsi +++ b/arch/arm64/boot/dts/apple/t600x-common.dtsi @@ -337,6 +337,50 @@ */ }; + gpu_opp: opp-table-gpu { + compatible = "operating-points-v2"; + + /* + * NOTE: The voltage and power values are device-specific and + * must be filled in by the bootloader. + */ + opp00 { + opp-hz = /bits/ 64 <0>; + opp-microvolt = GPU_REPEAT(400000); + opp-microwatt = <0>; + }; + opp01 { + opp-hz = /bits/ 64 <388800000>; + opp-microvolt = GPU_REPEAT(634000); + opp-microwatt = <25011450>; + }; + opp02 { + opp-hz = /bits/ 64 <486000000>; + opp-microvolt = GPU_REPEAT(650000); + opp-microwatt = <31681170>; + }; + opp03 { + opp-hz = /bits/ 64 <648000000>; + opp-microvolt = GPU_REPEAT(668000); + opp-microwatt = <41685750>; + }; + opp04 { + opp-hz = /bits/ 64 <777600000>; + opp-microvolt = GPU_REPEAT(715000); + opp-microwatt = <56692620>; + }; + opp05 { + opp-hz = /bits/ 64 <972000000>; + opp-microvolt = GPU_REPEAT(778000); + opp-microwatt = <83371500>; + }; + opp06 { + opp-hz = /bits/ 64 <1296000000>; + opp-microvolt = GPU_REPEAT(903000); + opp-microwatt = <166743000>; + }; + }; + pmu-e { compatible = "apple,icestorm-pmu"; interrupt-parent = <&aic>; @@ -407,15 +451,15 @@ }; uat_handoff: uat-handoff { - status = "disabled"; + reg = <0 0 0 0>; }; uat_pagetables: uat-pagetables { - status = "disabled"; + reg = <0 0 0 0>; }; uat_ttbs: uat-ttbs { - status = "disabled"; + reg = <0 0 0 0>; }; }; }; diff --git a/arch/arm64/boot/dts/apple/t600x-die0.dtsi b/arch/arm64/boot/dts/apple/t600x-die0.dtsi index 8de99a936adc21..29012bcc003d6a 100644 --- a/arch/arm64/boot/dts/apple/t600x-die0.dtsi +++ b/arch/arm64/boot/dts/apple/t600x-die0.dtsi @@ -430,6 +430,43 @@ "hw-cal-a", "hw-cal-b", "globals"; apple,firmware-abi = <0 0 0>; + + apple,firmware-version = <12 3 0>; + apple,firmware-compat = <12 3 0>; + + operating-points-v2 = <&gpu_opp>; + apple,perf-base-pstate = <1>; + apple,min-sram-microvolt = <790000>; + apple,avg-power-filter-tc-ms = <1000>; + apple,avg-power-ki-only = <2.4>; + apple,avg-power-kp = <1.5>; + apple,avg-power-min-duty-cycle = <40>; + apple,avg-power-target-filter-tc = <125>; + apple,fast-die0-integral-gain = <500.0>; + apple,fast-die0-proportional-gain = <72.0>; + apple,perf-boost-ce-step = <50>; + apple,perf-boost-min-util = <90>; + apple,perf-filter-drop-threshold = <0>; + apple,perf-filter-time-constant = <5>; + apple,perf-filter-time-constant2 = <50>; + apple,perf-integral-gain = <6.3>; + apple,perf-integral-gain2 = <0.197392>; + apple,perf-integral-min-clamp = <0>; + apple,perf-proportional-gain = <15.75>; + apple,perf-proportional-gain2 = <6.853981>; + apple,perf-tgt-utilization = <85>; + apple,power-sample-period = <8>; + apple,ppm-filter-time-constant-ms = <100>; + apple,ppm-ki = <30.0>; + apple,ppm-kp = <1.5>; + apple,pwr-filter-time-constant = <313>; + apple,pwr-integral-gain = <0.0202129>; + apple,pwr-integral-min-clamp = <0>; + apple,pwr-min-duty-cycle = <40>; + apple,pwr-proportional-gain = <5.2831855>; + + apple,core-leak-coef = GPU_REPEAT(1200.0); + apple,sram-leak-coef = GPU_REPEAT(20.0); }; agx_mbox: mbox@406408000 { From 305df519d15b27a8bf8c4f487a27e9e398168e29 Mon Sep 17 00:00:00 2001 From: Asahi Lina Date: Fri, 25 Nov 2022 23:06:59 +0900 Subject: [PATCH 2131/3902] arm64: dts: apple: t8112: Add downstream gpu properties to be dropped Signed-off-by: Asahi Lina --- arch/arm64/boot/dts/apple/t8112-j473.dts | 4 + arch/arm64/boot/dts/apple/t8112.dtsi | 93 +++++++++++++++++++++++- 2 files changed, 93 insertions(+), 4 deletions(-) diff --git a/arch/arm64/boot/dts/apple/t8112-j473.dts b/arch/arm64/boot/dts/apple/t8112-j473.dts index 6d8eb2114415c6..cf24579ca7b325 100644 --- a/arch/arm64/boot/dts/apple/t8112-j473.dts +++ b/arch/arm64/boot/dts/apple/t8112-j473.dts @@ -167,3 +167,7 @@ }; }; + +&gpu { + apple,perf-base-pstate = <3>; +}; diff --git a/arch/arm64/boot/dts/apple/t8112.dtsi b/arch/arm64/boot/dts/apple/t8112.dtsi index f5e01a722fe6a4..7ecfd89a5e8ba1 100644 --- a/arch/arm64/boot/dts/apple/t8112.dtsi +++ b/arch/arm64/boot/dts/apple/t8112.dtsi @@ -325,6 +325,60 @@ #endif }; + gpu_opp: opp-table-gpu { + compatible = "operating-points-v2"; + + /* + * NOTE: The voltage and power values are device-specific and + * must be filled in by the bootloader. + */ + opp00 { + opp-hz = /bits/ 64 <0>; + opp-microvolt = <400000>; + opp-microwatt = <0>; + }; + opp01 { + opp-hz = /bits/ 64 <444000000>; + opp-microvolt = <603000>; + opp-microwatt = <4295000>; + }; + opp02 { + opp-hz = /bits/ 64 <612000000>; + opp-microvolt = <675000>; + opp-microwatt = <6251000>; + }; + opp03 { + opp-hz = /bits/ 64 <808000000>; + opp-microvolt = <710000>; + opp-microwatt = <8625000>; + }; + opp04 { + opp-hz = /bits/ 64 <968000000>; + opp-microvolt = <775000>; + opp-microwatt = <11948000>; + }; + opp05 { + opp-hz = /bits/ 64 <1110000000>; + opp-microvolt = <820000>; + opp-microwatt = <15071000>; + }; + opp06 { + opp-hz = /bits/ 64 <1236000000>; + opp-microvolt = <875000>; + opp-microwatt = <18891000>; + }; + opp07 { + opp-hz = /bits/ 64 <1338000000>; + opp-microvolt = <915000>; + opp-microwatt = <21960000>; + }; + opp08 { + opp-hz = /bits/ 64 <1398000000>; + opp-microvolt = <950000>; + opp-microwatt = <22800000>; + }; + }; + timer { compatible = "arm,armv8-timer"; interrupt-parent = <&aic>; @@ -397,15 +451,15 @@ }; uat_handoff: uat-handoff { - status = "disabled"; + reg = <0 0 0 0>; }; uat_pagetables: uat-pagetables { - status = "disabled"; + reg = <0 0 0 0>; }; uat_ttbs: uat-ttbs { - status = "disabled"; + reg = <0 0 0 0>; }; }; @@ -418,7 +472,7 @@ nonposted-mmio; gpu: gpu@206400000 { - compatible = "apple,agx-g14g"; + compatible = "apple,agx-t8112", "apple,agx-g14g"; reg = <0x2 0x6400000 0 0x40000>, <0x2 0x4000000 0 0x1000000>; reg-names = "asc", "sgx"; @@ -430,6 +484,37 @@ "hw-cal-a", "hw-cal-b", "globals"; apple,firmware-abi = <0 0 0>; + apple,firmware-version = <12 4 0>; + apple,firmware-compat = <12 4 0>; + + operating-points-v2 = <&gpu_opp>; + apple,perf-base-pstate = <1>; + apple,min-sram-microvolt = <780000>; + apple,avg-power-filter-tc-ms = <300>; + apple,avg-power-ki-only = <9.375>; + apple,avg-power-kp = <3.22>; + apple,avg-power-min-duty-cycle = <40>; + apple,avg-power-target-filter-tc = <1>; + apple,fast-die0-integral-gain = <200.0>; + apple,fast-die0-proportional-gain = <5.0>; + apple,perf-boost-ce-step = <50>; + apple,perf-boost-min-util = <90>; + apple,perf-filter-drop-threshold = <0>; + apple,perf-filter-time-constant = <5>; + apple,perf-filter-time-constant2 = <200>; + apple,perf-integral-gain = <5.94>; + apple,perf-integral-gain2 = <5.94>; + apple,perf-integral-min-clamp = <0>; + apple,perf-proportional-gain = <14.85>; + apple,perf-proportional-gain2 = <14.85>; + apple,perf-tgt-utilization = <85>; + apple,power-sample-period = <8>; + apple,ppm-filter-time-constant-ms = <34>; + apple,ppm-ki = <205.0>; + apple,ppm-kp = <0.75>; + apple,pwr-min-duty-cycle = <40>; + apple,core-leak-coef = <1920.0>; + apple,sram-leak-coef = <74.0>; }; agx_mbox: mbox@206408000 { From 9c1ece372bf8db234052e9f2ea66932ca2e7453f Mon Sep 17 00:00:00 2001 From: Hector Martin Date: Mon, 24 Apr 2023 23:27:52 +0900 Subject: [PATCH 2132/3902] arm64: dts: apple: t600x: Remove obsolete comment in ans2 power domain Signed-off-by: Hector Martin --- arch/arm64/boot/dts/apple/t600x-pmgr.dtsi | 6 ------ 1 file changed, 6 deletions(-) diff --git a/arch/arm64/boot/dts/apple/t600x-pmgr.dtsi b/arch/arm64/boot/dts/apple/t600x-pmgr.dtsi index 3f507cbc65f0c8..3315b392b21d72 100644 --- a/arch/arm64/boot/dts/apple/t600x-pmgr.dtsi +++ b/arch/arm64/boot/dts/apple/t600x-pmgr.dtsi @@ -1387,12 +1387,6 @@ #power-domain-cells = <0>; #reset-cells = <0>; label = DIE_LABEL(ans2); - /* - * The ADT makes ps_apcie_st[1]_sys depend on ps_ans2 instead, - * but we'd rather have a single power domain for the downstream - * device to depend on, so use this node as the child. - * This makes more sense anyway (since ANS2 uses APCIE_ST). - */ power-domains = <&DIE_NODE(ps_afnc2_lw0)>; }; From 9162493229fb377eb7fcf0cc99515a8170c0439b Mon Sep 17 00:00:00 2001 From: Hector Martin Date: Thu, 27 Apr 2023 13:53:35 +0900 Subject: [PATCH 2133/3902] arm64: dts: apple: t600x: Enable turbo CPU p-states These should work now that we have cpuidle. Signed-off-by: Hector Martin --- arch/arm64/boot/dts/apple/t600x-common.dtsi | 2 -- 1 file changed, 2 deletions(-) diff --git a/arch/arm64/boot/dts/apple/t600x-common.dtsi b/arch/arm64/boot/dts/apple/t600x-common.dtsi index 5e54b03cf142f0..f434d724096e58 100644 --- a/arch/arm64/boot/dts/apple/t600x-common.dtsi +++ b/arch/arm64/boot/dts/apple/t600x-common.dtsi @@ -315,7 +315,6 @@ opp-level = <12>; clock-latency-ns = <56000>; }; - /* Not available until CPU deep sleep is implemented opp13 { opp-hz = /bits/ 64 <3132000000>; opp-level = <13>; @@ -334,7 +333,6 @@ clock-latency-ns = <56000>; turbo-mode; }; - */ }; gpu_opp: opp-table-gpu { From 0e14b9161642aa56f6a77dd5314b95cd62e2a0fa Mon Sep 17 00:00:00 2001 From: Hector Martin Date: Thu, 27 Apr 2023 13:54:17 +0900 Subject: [PATCH 2134/3902] arm64: dts: apple: t8103: Enable turbo CPU p-states These should work now that we have cpuidle. Signed-off-by: Hector Martin --- arch/arm64/boot/dts/apple/t8103.dtsi | 2 -- 1 file changed, 2 deletions(-) diff --git a/arch/arm64/boot/dts/apple/t8103.dtsi b/arch/arm64/boot/dts/apple/t8103.dtsi index 7e7f35776418f8..b0947ac9359be6 100644 --- a/arch/arm64/boot/dts/apple/t8103.dtsi +++ b/arch/arm64/boot/dts/apple/t8103.dtsi @@ -280,7 +280,6 @@ opp-level = <12>; clock-latency-ns = <55000>; }; -#if 0 /* Not available until CPU deep sleep is implemented */ opp13 { opp-hz = /bits/ 64 <3096000000>; @@ -300,7 +299,6 @@ clock-latency-ns = <56000>; turbo-mode; }; -#endif }; gpu_opp: opp-table-gpu { From 21f6bed9199cbf78a39cb5c0ca506b8095e82066 Mon Sep 17 00:00:00 2001 From: Hector Martin Date: Thu, 27 Apr 2023 13:54:30 +0900 Subject: [PATCH 2135/3902] arm64: dts: apple: t8112: Enable turbo CPU p-states These should work now that we have cpuidle. Signed-off-by: Hector Martin --- arch/arm64/boot/dts/apple/t8112.dtsi | 3 --- 1 file changed, 3 deletions(-) diff --git a/arch/arm64/boot/dts/apple/t8112.dtsi b/arch/arm64/boot/dts/apple/t8112.dtsi index 7ecfd89a5e8ba1..a9b198c38a3a27 100644 --- a/arch/arm64/boot/dts/apple/t8112.dtsi +++ b/arch/arm64/boot/dts/apple/t8112.dtsi @@ -302,8 +302,6 @@ opp-level = <14>; clock-latency-ns = <46000>; }; - /* Not available until CPU deep sleep is implemented */ -#if 0 opp15 { opp-hz = /bits/ 64 <3324000000>; opp-level = <15>; @@ -322,7 +320,6 @@ clock-latency-ns = <62000>; turbo-mode; }; -#endif }; gpu_opp: opp-table-gpu { From c886f844e4a4aa59194fff3b4afd8e2af1c9177e Mon Sep 17 00:00:00 2001 From: Hector Martin Date: Tue, 11 Apr 2023 02:34:01 +0900 Subject: [PATCH 2136/3902] arm64: dts: apple: Add identity dma-ranges mapping Without this, the OF core ends up limiting all DMA masks to the default 32-bit, since that runs before drivers set up the proper DMA mask. Skipping the highest page because it is impossible to express a full 64-bit range in the DT. Signed-off-by: Hector Martin --- arch/arm64/boot/dts/apple/t6001.dtsi | 2 ++ arch/arm64/boot/dts/apple/t6002.dtsi | 4 ++++ arch/arm64/boot/dts/apple/t8103.dtsi | 2 ++ arch/arm64/boot/dts/apple/t8112.dtsi | 2 ++ 4 files changed, 10 insertions(+) diff --git a/arch/arm64/boot/dts/apple/t6001.dtsi b/arch/arm64/boot/dts/apple/t6001.dtsi index 9dffa61db0cef5..3ac838c9b803b6 100644 --- a/arch/arm64/boot/dts/apple/t6001.dtsi +++ b/arch/arm64/boot/dts/apple/t6001.dtsi @@ -32,6 +32,8 @@ ranges; nonposted-mmio; + /* Required to get >32-bit DMA via DARTs */ + dma-ranges = <0 0 0 0 0xffffffff 0xffffc000>; // filled via templated includes at the end of the file }; diff --git a/arch/arm64/boot/dts/apple/t6002.dtsi b/arch/arm64/boot/dts/apple/t6002.dtsi index ce88211c0c22da..04265fa3ea1ec1 100644 --- a/arch/arm64/boot/dts/apple/t6002.dtsi +++ b/arch/arm64/boot/dts/apple/t6002.dtsi @@ -240,6 +240,8 @@ <0x5 0x80000000 0x5 0x80000000 0x1 0x80000000>, <0x7 0x0 0x7 0x0 0xf 0x80000000>; nonposted-mmio; + /* Required to get >32-bit DMA via DARTs */ + dma-ranges = <0 0 0 0 0xffffffff 0xffffc000>; // filled via templated includes at the end of the file }; @@ -251,6 +253,8 @@ ranges = <0x2 0x0 0x22 0x0 0x4 0x0>, <0x7 0x0 0x27 0x0 0xf 0x80000000>; nonposted-mmio; + /* Required to get >32-bit DMA via DARTs */ + dma-ranges = <0 0 0 0 0xffffffff 0xffffc000>; // filled via templated includes at the end of the file }; diff --git a/arch/arm64/boot/dts/apple/t8103.dtsi b/arch/arm64/boot/dts/apple/t8103.dtsi index b0947ac9359be6..ce3f7469a8a28e 100644 --- a/arch/arm64/boot/dts/apple/t8103.dtsi +++ b/arch/arm64/boot/dts/apple/t8103.dtsi @@ -443,6 +443,8 @@ ranges; nonposted-mmio; + /* Required to get >32-bit DMA via DARTs */ + dma-ranges = <0 0 0 0 0xffffffff 0xffffc000>; gpu: gpu@206400000 { compatible = "apple,agx-t8103", "apple,agx-g13g"; diff --git a/arch/arm64/boot/dts/apple/t8112.dtsi b/arch/arm64/boot/dts/apple/t8112.dtsi index a9b198c38a3a27..da75e865d863cd 100644 --- a/arch/arm64/boot/dts/apple/t8112.dtsi +++ b/arch/arm64/boot/dts/apple/t8112.dtsi @@ -467,6 +467,8 @@ ranges; nonposted-mmio; + /* Required to get >32-bit DMA via DARTs */ + dma-ranges = <0 0 0 0 0xffffffff 0xffffc000>; gpu: gpu@206400000 { compatible = "apple,agx-t8112", "apple,agx-g14g"; From 6b0fd6678150cad936834a884cf4313b8d6d6032 Mon Sep 17 00:00:00 2001 From: Hector Martin Date: Sun, 9 Apr 2023 23:48:38 +0900 Subject: [PATCH 2137/3902] arm64: dts: apple: t602x: Add missing devices Still contains the downstream commits: arm64: dts: apple: t6022: Disable dcp thouroughly Also disables "display" until it can be supported via dispext*. arm64: dts: apple: t602x: Add initial Mac Studio (2023) device trees They use the same GPIO pins and interrupts as the Mac Mini (M2 Pro, 2023) so use a common .dtsi for those definitions. Squashed commits to ease rebasing onto upstream t602x device trees which contains changes from above commits but reordered them in hindsight of knowing the full rooster of t602x devices. Signed-off-by: Hector Martin Co-developed-by: Asahi Lina Signed-off-by: Asahi Lina Co-developed-by: Janne Grunau Signed-off-by: Janne Grunau --- .../arm64/boot/dts/apple/t600x-j314-j316.dtsi | 7 + arch/arm64/boot/dts/apple/t600x-j375.dtsi | 2 + arch/arm64/boot/dts/apple/t6020-j414s.dts | 12 ++ arch/arm64/boot/dts/apple/t6020-j416s.dts | 12 ++ arch/arm64/boot/dts/apple/t6020-j474s.dts | 22 +++ arch/arm64/boot/dts/apple/t6020.dtsi | 11 +- arch/arm64/boot/dts/apple/t6021-j414c.dts | 12 ++ arch/arm64/boot/dts/apple/t6021-j416c.dts | 32 ++++ arch/arm64/boot/dts/apple/t6021-j475c.dts | 30 +++ arch/arm64/boot/dts/apple/t6021.dtsi | 16 +- arch/arm64/boot/dts/apple/t6022-j180d.dts | 12 +- arch/arm64/boot/dts/apple/t6022-j475d.dts | 8 + arch/arm64/boot/dts/apple/t6022-jxxxd.dtsi | 13 ++ arch/arm64/boot/dts/apple/t6022.dtsi | 22 ++- arch/arm64/boot/dts/apple/t602x-common.dtsi | 138 ++++++++++++++ arch/arm64/boot/dts/apple/t602x-die0.dtsi | 172 ++++++++++++++++-- .../arm64/boot/dts/apple/t602x-j414-j416.dtsi | 46 +++++ .../arm64/boot/dts/apple/t602x-j474-j475.dtsi | 25 +++ arch/arm64/boot/dts/apple/t602x-pmgr.dtsi | 3 + 19 files changed, 575 insertions(+), 20 deletions(-) diff --git a/arch/arm64/boot/dts/apple/t600x-j314-j316.dtsi b/arch/arm64/boot/dts/apple/t600x-j314-j316.dtsi index c17a77bacd43ed..37024d1d5c9c37 100644 --- a/arch/arm64/boot/dts/apple/t600x-j314-j316.dtsi +++ b/arch/arm64/boot/dts/apple/t600x-j314-j316.dtsi @@ -285,6 +285,7 @@ clock-frequency = <1068000000>; }; +#ifndef NO_SPI_TRACKPAD &spi3 { status = "okay"; @@ -305,6 +306,7 @@ interrupts-extended = <&pinctrl_nub 6 IRQ_TYPE_LEVEL_LOW>; }; }; +#endif /* PCIe devices */ &port00 { @@ -331,6 +333,7 @@ /* SD card reader */ bus-range = <2 2>; pwren-gpios = <&smc_gpio 26 GPIO_ACTIVE_HIGH>; + status = "okay"; sdhci0: mmc@0,0 { compatible = "pci17a0,9755"; reg = <0x20000 0x0 0x0 0x0 0x0>; @@ -343,6 +346,10 @@ status = "okay"; }; +&pcie0_dart_1 { + status = "okay"; +}; + /* USB controllers */ &dwc3_0 { ports { diff --git a/arch/arm64/boot/dts/apple/t600x-j375.dtsi b/arch/arm64/boot/dts/apple/t600x-j375.dtsi index 30d549bc32f820..ce962404b2581d 100644 --- a/arch/arm64/boot/dts/apple/t600x-j375.dtsi +++ b/arch/arm64/boot/dts/apple/t600x-j375.dtsi @@ -18,9 +18,11 @@ atcphy2 = &atcphy2; atcphy3 = &atcphy3; bluetooth0 = &bluetooth0; + #ifndef NO_DCP dcp = &dcp; disp0 = &display; disp0_piodma = &disp0_piodma; + #endif ethernet0 = ðernet0; serial0 = &serial0; wifi0 = &wifi0; diff --git a/arch/arm64/boot/dts/apple/t6020-j414s.dts b/arch/arm64/boot/dts/apple/t6020-j414s.dts index 631c54c5f03dee..18cc67a3076def 100644 --- a/arch/arm64/boot/dts/apple/t6020-j414s.dts +++ b/arch/arm64/boot/dts/apple/t6020-j414s.dts @@ -24,3 +24,15 @@ &bluetooth0 { brcm,board-type = "apple,tokara"; }; + +&panel { + compatible = "apple,panel-j414", "apple,panel-mini-led", "apple,panel"; + width-mm = <302>; + height-mm = <196>; + adj-height-mm = <189>; +}; + +&sound { + compatible = "apple,j414-macaudio", "apple,j314-macaudio", "apple,macaudio"; + model = "MacBook Pro J414"; +}; diff --git a/arch/arm64/boot/dts/apple/t6020-j416s.dts b/arch/arm64/boot/dts/apple/t6020-j416s.dts index c277ed5889a214..b9e0973ba37c30 100644 --- a/arch/arm64/boot/dts/apple/t6020-j416s.dts +++ b/arch/arm64/boot/dts/apple/t6020-j416s.dts @@ -24,3 +24,15 @@ &bluetooth0 { brcm,board-type = "apple,amami"; }; + +&panel { + compatible = "apple,panel-j416", "apple,panel-mini-led", "apple,panel"; + width-mm = <346>; + height-mm = <223>; + adj-height-mm = <216>; +}; + +&sound { + compatible = "apple,j416-macaudio", "apple,j316-macaudio", "apple,macaudio"; + model = "MacBook Pro J416"; +}; diff --git a/arch/arm64/boot/dts/apple/t6020-j474s.dts b/arch/arm64/boot/dts/apple/t6020-j474s.dts index 7c7ad5b8ad189e..17c72b0bb87721 100644 --- a/arch/arm64/boot/dts/apple/t6020-j474s.dts +++ b/arch/arm64/boot/dts/apple/t6020-j474s.dts @@ -45,3 +45,25 @@ <0x200 &pcie0_dart_2 1 1>, <0x300 &pcie0_dart_3 1 1>; }; + +&port02 { + bus-range = <2 2>; +}; + +ðernet0 { + reg = <0x20000 0x0 0x0 0x0 0x0>; +}; + +&port03 { + bus-range = <3 3>; +}; + +&sound { + compatible = "apple,j474-macaudio", "apple,j473-macaudio", "apple,macaudio"; + model = "Mac mini J474"; +}; + +&gpu { + /* Apple does not do this, but they probably should */ + apple,perf-base-pstate = <3>; +}; diff --git a/arch/arm64/boot/dts/apple/t6020.dtsi b/arch/arm64/boot/dts/apple/t6020.dtsi index bffa66a3ffff3f..482a1e5f53d0a6 100644 --- a/arch/arm64/boot/dts/apple/t6020.dtsi +++ b/arch/arm64/boot/dts/apple/t6020.dtsi @@ -9,6 +9,8 @@ /* This chip is just a cut down version of t6021, so include it and disable the missing parts */ +#define GPU_REPEAT(x) + #include "t6021.dtsi" / { @@ -18,5 +20,12 @@ /delete-node/ &pmgr_south; &gpu { - compatible = "apple,agx-g14s"; + compatible = "apple,agx-t6020", "apple,agx-g14x", "apple,agx-g14s"; + + apple,avg-power-filter-tc-ms = <302>; + apple,avg-power-ki-only = <2.6375>; + apple,avg-power-kp = <0.18>; + apple,fast-die0-integral-gain = <1350.0>; + apple,ppm-filter-time-constant-ms = <32>; + apple,ppm-ki = <28.0>; }; diff --git a/arch/arm64/boot/dts/apple/t6021-j414c.dts b/arch/arm64/boot/dts/apple/t6021-j414c.dts index cdcf0740714dcf..b173caf0df0fce 100644 --- a/arch/arm64/boot/dts/apple/t6021-j414c.dts +++ b/arch/arm64/boot/dts/apple/t6021-j414c.dts @@ -24,3 +24,15 @@ &bluetooth0 { brcm,board-type = "apple,tokara"; }; + +&panel { + compatible = "apple,panel-j414", "apple,panel-mini-led", "apple,panel"; + width-mm = <302>; + height-mm = <196>; + adj-height-mm = <189>; +}; + +&sound { + compatible = "apple,j414-macaudio", "apple,j314-macaudio", "apple,macaudio"; + model = "MacBook Pro J414"; +}; diff --git a/arch/arm64/boot/dts/apple/t6021-j416c.dts b/arch/arm64/boot/dts/apple/t6021-j416c.dts index 6d8146b9417036..2fbb00b364c72b 100644 --- a/arch/arm64/boot/dts/apple/t6021-j416c.dts +++ b/arch/arm64/boot/dts/apple/t6021-j416c.dts @@ -17,6 +17,26 @@ model = "Apple MacBook Pro (16-inch, M2 Max, 2023)"; }; +/* This machine model (only) has two extra boost CPU P-states * + * Disabled: Only the highest CPU bin (38 GPU cores) has this. + * Keep this disabled until m1n1 learns how to remove these OPPs + * for unsupported machines, otherwise it breaks cpufreq. +&avalanche_opp { + opp18 { + opp-hz = /bits/ 64 <3528000000>; + opp-level = <18>; + clock-latency-ns = <67000>; + turbo-mode; + }; + opp19 { + opp-hz = /bits/ 64 <3696000000>; + opp-level = <19>; + clock-latency-ns = <67000>; + turbo-mode; + }; +}; +*/ + &wifi0 { brcm,board-type = "apple,amami"; }; @@ -24,3 +44,15 @@ &bluetooth0 { brcm,board-type = "apple,amami"; }; + +&panel { + compatible = "apple,panel-j416", "apple,panel-mini-led", "apple,panel"; + width-mm = <346>; + height-mm = <223>; + adj-height-mm = <216>; +}; + +&sound { + compatible = "apple,j416-macaudio", "apple,j316-macaudio", "apple,macaudio"; + model = "MacBook Pro J416"; +}; diff --git a/arch/arm64/boot/dts/apple/t6021-j475c.dts b/arch/arm64/boot/dts/apple/t6021-j475c.dts index 533e3577487469..ebc3ec8c387b30 100644 --- a/arch/arm64/boot/dts/apple/t6021-j475c.dts +++ b/arch/arm64/boot/dts/apple/t6021-j475c.dts @@ -29,9 +29,39 @@ /* enable PCIe port01 with SDHCI */ &port01 { + pwren-gpios = <&smc_gpio 22 GPIO_ACTIVE_HIGH>; status = "okay"; }; &pcie0_dart_1 { status = "okay"; }; + +&pinctrl_ap { + usb_hub_oe-hog { + gpio-hog; + gpios = <231 0>; + input; + line-name = "usb-hub-oe"; + }; + + usb_hub_rst-hog { + gpio-hog; + gpios = <232 GPIO_ACTIVE_LOW>; + output-low; + line-name = "usb-hub-rst"; + }; +}; + +&sound { + compatible = "apple,j475-macaudio", "apple,j375-macaudio", "apple,macaudio"; + model = "Mac Studio J475"; +}; + +&gpu { + apple,idleoff-standby-timer = <3000>; + apple,perf-base-pstate = <5>; + apple,perf-boost-ce-step = <100>; + apple,perf-boost-min-util = <75>; + apple,perf-tgt-utilization = <70>; +}; diff --git a/arch/arm64/boot/dts/apple/t6021.dtsi b/arch/arm64/boot/dts/apple/t6021.dtsi index 62907ad6a54683..1205a43da383f7 100644 --- a/arch/arm64/boot/dts/apple/t6021.dtsi +++ b/arch/arm64/boot/dts/apple/t6021.dtsi @@ -16,6 +16,13 @@ #include "multi-die-cpp.h" +#ifndef GPU_REPEAT +# define GPU_REPEAT(x) +#endif +#ifndef GPU_DIE_REPEAT +# define GPU_DIE_REPEAT(x) +#endif + #include "t602x-common.dtsi" / { @@ -65,5 +72,12 @@ }; &gpu { - compatible = "apple,agx-g14c", "apple,agx-g14s"; + compatible = "apple,agx-t6021", "apple,agx-g14x", "apple,agx-g14c", "apple,agx-g14s"; + + apple,avg-power-filter-tc-ms = <300>; + apple,avg-power-ki-only = <1.5125>; + apple,avg-power-kp = <0.38>; + apple,fast-die0-integral-gain = <700.0>; + apple,ppm-filter-time-constant-ms = <34>; + apple,ppm-ki = <18.0>; }; diff --git a/arch/arm64/boot/dts/apple/t6022-j180d.dts b/arch/arm64/boot/dts/apple/t6022-j180d.dts index 7d92505e3fd232..59e5825a0368fa 100644 --- a/arch/arm64/boot/dts/apple/t6022-j180d.dts +++ b/arch/arm64/boot/dts/apple/t6022-j180d.dts @@ -26,8 +26,11 @@ atcphy5 = &atcphy1_die1; atcphy6 = &atcphy2_die1; atcphy7 = &atcphy3_die1; - nvram = &nvram; + //bluetooth0 = &bluetooth0; + //ethernet0 = ðernet0; + //ethernet1 = ðernet1; serial0 = &serial0; + //wifi0 = &wifi0; }; chosen { @@ -46,6 +49,13 @@ }; }; + reserved-memory { + #address-cells = <2>; + #size-cells = <2>; + ranges; + /* To be filled by loader */ + }; + memory@10000000000 { device_type = "memory"; reg = <0x100 0 0x2 0>; /* To be filled by loader */ diff --git a/arch/arm64/boot/dts/apple/t6022-j475d.dts b/arch/arm64/boot/dts/apple/t6022-j475d.dts index 31f24bbda9689b..141c8497b8890b 100644 --- a/arch/arm64/boot/dts/apple/t6022-j475d.dts +++ b/arch/arm64/boot/dts/apple/t6022-j475d.dts @@ -9,6 +9,8 @@ /dts-v1/; +#define NO_DCP + #include "t6022.dtsi" #include "t602x-j474-j475.dtsi" #include "t6022-jxxxd.dtsi" @@ -29,6 +31,7 @@ /* enable PCIe port01 with SDHCI */ &port01 { + pwren-gpios = <&smc_gpio 22 GPIO_ACTIVE_HIGH>; status = "okay"; }; @@ -71,3 +74,8 @@ compatible = "pci14e4,5f72"; brcm,board-type = "apple,canary"; }; + +&sound { + compatible = "apple,j475-macaudio", "apple,j375-macaudio", "apple,macaudio"; + model = "Mac Studio J475"; +}; diff --git a/arch/arm64/boot/dts/apple/t6022-jxxxd.dtsi b/arch/arm64/boot/dts/apple/t6022-jxxxd.dtsi index dc877bd604f827..5b7b41ce07c3d8 100644 --- a/arch/arm64/boot/dts/apple/t6022-jxxxd.dtsi +++ b/arch/arm64/boot/dts/apple/t6022-jxxxd.dtsi @@ -9,6 +9,19 @@ * Copyright The Asahi Linux Contributors */ +/* disable unused display node */ + +&display { + status = "disabled"; + iommus = <>; /* <&dispext0_dart_die1 0>; */ +}; + +/* delete missing dcp0/disp0 */ +/delete-node/ &disp0_dart; +/delete-node/ &dcp_dart; +/delete-node/ &dcp_mbox; +/delete-node/ &dcp; + /* delete power-domains for missing disp0 / disp0_die1 */ /delete-node/ &ps_disp0_cpu0; /delete-node/ &ps_disp0_fe; diff --git a/arch/arm64/boot/dts/apple/t6022.dtsi b/arch/arm64/boot/dts/apple/t6022.dtsi index e73bf2f7510ae2..bc05cddf68f4f7 100644 --- a/arch/arm64/boot/dts/apple/t6022.dtsi +++ b/arch/arm64/boot/dts/apple/t6022.dtsi @@ -16,6 +16,13 @@ #include "multi-die-cpp.h" +#ifndef GPU_REPEAT +# define GPU_REPEAT(x) +#endif +#ifndef GPU_DIE_REPEAT +# define GPU_DIE_REPEAT(x) +#endif + #include "t602x-common.dtsi" / { @@ -345,5 +352,18 @@ }; &gpu { - compatible = "apple,agx-g14d", "apple,agx-g14s"; + compatible = "apple,agx-t6022", "apple,agx-g14x", "apple,agx-g14d", "apple,agx-g14s"; + + apple,avg-power-filter-tc-ms = <302>; + apple,avg-power-ki-only = <1.0125>; + apple,avg-power-kp = <0.15>; + apple,fast-die0-integral-gain = <9.6>; + apple,fast-die0-proportional-gain = <24.0>; + apple,idleoff-standby-timer = <3000>; + apple,perf-base-pstate = <5>; + apple,perf-boost-ce-step = <100>; + apple,perf-boost-min-util = <75>; + apple,perf-tgt-utilization = <70>; + apple,ppm-ki = <11.0>; + apple,ppm-kp = <0.15>; }; diff --git a/arch/arm64/boot/dts/apple/t602x-common.dtsi b/arch/arm64/boot/dts/apple/t602x-common.dtsi index 9c800a391e7e87..3eeb5139fcde05 100644 --- a/arch/arm64/boot/dts/apple/t602x-common.dtsi +++ b/arch/arm64/boot/dts/apple/t602x-common.dtsi @@ -387,6 +387,134 @@ }; }; + gpu_opp: opp-table-gpu { + compatible = "operating-points-v2"; + + /* + * NOTE: The voltage and power values are device-specific and + * must be filled in by the bootloader. + */ + opp00 { + opp-hz = /bits/ 64 <0>; + opp-microvolt = GPU_REPEAT(400000); + opp-microwatt = <0>; + }; + opp01 { + opp-hz = /bits/ 64 <444000000>; + opp-microvolt = GPU_REPEAT(637000); + opp-microwatt = <4295000>; + }; + opp02 { + opp-hz = /bits/ 64 <612000000>; + opp-microvolt = GPU_REPEAT(656000); + opp-microwatt = <6251000>; + }; + opp03 { + opp-hz = /bits/ 64 <808000000>; + opp-microvolt = GPU_REPEAT(687000); + opp-microwatt = <8625000>; + }; + opp04 { + opp-hz = /bits/ 64 <968000000>; + opp-microvolt = GPU_REPEAT(725000); + opp-microwatt = <11948000>; + }; + opp05 { + opp-hz = /bits/ 64 <1110000000>; + opp-microvolt = GPU_REPEAT(790000); + opp-microwatt = <15071000>; + }; + opp06 { + opp-hz = /bits/ 64 <1236000000>; + opp-microvolt = GPU_REPEAT(843000); + opp-microwatt = <18891000>; + }; + opp07 { + opp-hz = /bits/ 64 <1338000000>; + opp-microvolt = GPU_REPEAT(887000); + opp-microwatt = <21960000>; + }; + opp08 { + opp-hz = /bits/ 64 <1398000000>; + opp-microvolt = GPU_REPEAT(918000); + opp-microwatt = <22800000>; + }; + }; + + gpu_cs_opp: opp-table-gpu-cs { + compatible = "operating-points-v2"; + + /* + * NOTE: The voltage and power values are device-specific and + * must be filled in by the bootloader. + */ + opp00 { + opp-hz = /bits/ 64 <24>; + opp-microvolt = GPU_DIE_REPEAT(668000); + }; + opp01 { + opp-hz = /bits/ 64 <444000000>; + opp-microvolt = GPU_DIE_REPEAT(668000); + }; + opp02 { + opp-hz = /bits/ 64 <612000000>; + opp-microvolt = GPU_DIE_REPEAT(678000); + }; + opp03 { + opp-hz = /bits/ 64 <808000000>; + opp-microvolt = GPU_DIE_REPEAT(737000); + }; + opp04 { + opp-hz = /bits/ 64 <1024000000>; + opp-microvolt = GPU_DIE_REPEAT(815000); + }; + opp05 { + opp-hz = /bits/ 64 <1140000000>; + opp-microvolt = GPU_DIE_REPEAT(862000); + }; + opp06 { + opp-hz = /bits/ 64 <1236000000>; + opp-microvolt = GPU_DIE_REPEAT(893000); + }; + }; + + gpu_afr_opp: opp-table-gpu-afr { + compatible = "operating-points-v2"; + + /* + * NOTE: The voltage and power values are device-specific and + * must be filled in by the bootloader. + */ + opp00 { + opp-hz = /bits/ 64 <24>; + opp-microvolt = GPU_DIE_REPEAT(668000); + }; + opp01 { + opp-hz = /bits/ 64 <400000000>; + opp-microvolt = GPU_DIE_REPEAT(668000); + }; + opp02 { + opp-hz = /bits/ 64 <552000000>; + opp-microvolt = GPU_DIE_REPEAT(678000); + }; + opp03 { + opp-hz = /bits/ 64 <760000000>; + opp-microvolt = GPU_DIE_REPEAT(737000); + }; + opp04 { + opp-hz = /bits/ 64 <980000000>; + opp-microvolt = GPU_DIE_REPEAT(815000); + }; + opp05 { + opp-hz = /bits/ 64 <1098000000>; + opp-microvolt = GPU_DIE_REPEAT(862000); + }; + opp06 { + opp-hz = /bits/ 64 <1200000000>; + opp-microvolt = GPU_DIE_REPEAT(893000); + }; + }; + pmu-e { compatible = "apple,blizzard-pmu"; interrupt-parent = <&aic>; @@ -423,6 +551,13 @@ clock-output-names = "clk_200m"; }; + clk_disp0: clock-disp0 { + compatible = "fixed-clock"; + #clock-cells = <0>; + clock-frequency = <257142848>; /* TODO: check */ + clock-output-names = "clk_disp0"; + }; + /* * This is a fabulated representation of the input clock * to NCO since we don't know the true clock tree. @@ -452,14 +587,17 @@ uat_handoff: uat-handoff { status = "disabled"; + reg = <0 0 0 0>; }; uat_pagetables: uat-pagetables { status = "disabled"; + reg = <0 0 0 0>; }; uat_ttbs: uat-ttbs { status = "disabled"; + reg = <0 0 0 0>; }; }; }; diff --git a/arch/arm64/boot/dts/apple/t602x-die0.dtsi b/arch/arm64/boot/dts/apple/t602x-die0.dtsi index 2e7d2bf08ddc82..599bb5bc654f3e 100644 --- a/arch/arm64/boot/dts/apple/t602x-die0.dtsi +++ b/arch/arm64/boot/dts/apple/t602x-die0.dtsi @@ -23,6 +23,12 @@ power-domains = <&ps_aic>; }; + pmgr_dcp: power-management@28e3d0000 { + reg = <0x2 0x8e3d0000 0x0 0x4000>; + reg-names = "dcp-fw-pmgr"; + #apple,bw-scratch-cells = <3>; + }; + nub_spmi0: spmi@29e114000 { compatible = "apple,t6020-spmi", "apple,t8103-spmi"; reg = <0x2 0x9e114000 0x0 0x100>; @@ -88,21 +94,8 @@ interrupts = ; }; - smc_mbox: mbox@2a2408000 { - compatible = "apple,t6020-asc-mailbox", "apple,asc-mailbox-v4"; - reg = <0x2 0xa2408000 0x0 0x4000>; - interrupt-parent = <&aic>; - interrupts = , - , - , - ; - interrupt-names = "send-empty", "send-not-empty", - "recv-empty", "recv-not-empty"; - #mbox-cells = <0>; - }; - smc: smc@2a2400000 { - compatible = "apple,t6020-smc", "apple,t8103-smc"; + compatible = "apple,t6020-smc", "apple,smc"; reg = <0x2 0xa2400000 0x0 0x4000>, <0x2 0xa3e00000 0x0 0x100000>; reg-names = "smc", "sram"; @@ -114,15 +107,34 @@ #gpio-cells = <2>; }; + smc_rtc: rtc { + compatible = "apple,smc-rtc"; + nvmem-cells = <&rtc_offset>; + nvmem-cell-names = "rtc_offset"; + }; + smc_reboot: reboot { compatible = "apple,smc-reboot"; nvmem-cells = <&shutdown_flag>, <&boot_stage>, - <&boot_error_count>, <&panic_count>; + <&boot_error_count>, <&panic_count>, <&pm_setting>; nvmem-cell-names = "shutdown_flag", "boot_stage", - "boot_error_count", "panic_count"; + "boot_error_count", "panic_count", "pm_setting"; }; }; + smc_mbox: mbox@2a2408000 { + compatible = "apple,t6020-asc-mailbox", "apple,asc-mailbox-v4"; + reg = <0x2 0xa2408000 0x0 0x4000>; + interrupt-parent = <&aic>; + interrupts = , + , + , + ; + interrupt-names = "send-empty", "send-not-empty", + "recv-empty", "recv-not-empty"; + #mbox-cells = <0>; + }; + pinctrl_smc: pinctrl@2a2820000 { compatible = "apple,t6020-pinctrl", "apple,t8103-pinctrl"; reg = <0x2 0xa2820000 0x0 0x4000>; @@ -144,6 +156,75 @@ ; }; + disp0_dart: iommu@389304000 { + compatible = "apple,t6020-dart", "apple,t8110-dart"; + reg = <0x3 0x89304000 0x0 0x4000>; + #iommu-cells = <1>; + interrupt-parent = <&aic>; + interrupts = ; + status = "disabled"; + power-domains = <&ps_disp0_cpu0>; + apple,dma-range = <0x100 0x0 0x10 0x0>; + }; + + dcp_dart: iommu@38930c000 { + compatible = "apple,t6020-dart", "apple,t8110-dart"; + reg = <0x3 0x8930c000 0x0 0x4000>; + #iommu-cells = <1>; + interrupt-parent = <&aic>; + interrupts = ; + power-domains = <&ps_disp0_cpu0>; + apple,dma-range = <0x100 0x0 0x10 0x0>; + }; + + dcp_mbox: mbox@389c08000 { + compatible = "apple,t6020-asc-mailbox", "apple,asc-mailbox-v4"; + reg = <0x3 0x89c08000 0x0 0x4000>; + interrupt-parent = <&aic>; + interrupts = , + , + , + ; + interrupt-names = "send-empty", "send-not-empty", + "recv-empty", "recv-not-empty"; + #mbox-cells = <0>; + power-domains = <&ps_disp0_cpu0>; + }; + + dcp: dcp@389c00000 { + compatible = "apple,t6020-dcp", "apple,dcp"; + mboxes = <&dcp_mbox>; + mbox-names = "mbox"; + iommus = <&dcp_dart 5>; + + reg-names = "coproc", "disp-0", "disp-1", "disp-2", "disp-3"; + reg = <0x3 0x89c00000 0x0 0x4000>, // check? + <0x3 0x88000000 0x0 0x61c000>, + <0x3 0x89320000 0x0 0x4000>, + <0x3 0x89344000 0x0 0x4000>, + <0x3 0x89800000 0x0 0x800000>; + apple,bw-scratch = <&pmgr_dcp 0 4 0x1208>; + power-domains = <&ps_disp0_cpu0>; + resets = <&ps_disp0_cpu0>; + clocks = <&clk_disp0>; + phandle = <&dcp>; + // required bus properties for 'piodma' subdevice + #address-cells = <2>; + #size-cells = <2>; + + disp0_piodma: piodma { + iommus = <&disp0_dart 4>; + phandle = <&disp0_piodma>; + }; + }; + + display: display-subsystem { + compatible = "apple,display-subsystem"; + iommus = <&disp0_dart 0>; + /* generate phandle explicitly for use in loader */ + phandle = <&display>; + }; + sio_dart: iommu@39b008000 { compatible = "apple,t6020-dart", "apple,t8110-dart"; reg = <0x3 0x9b008000 0x0 0x8000>; @@ -388,6 +469,14 @@ reg = <0x4 0x6400000 0 0x40000>, <0x4 0x4000000 0 0x1000000>; reg-names = "asc", "sgx"; + interrupt-parent = <&aic>; + interrupts = , + , + , + , + , + , + ; mboxes = <&agx_mbox>; power-domains = <&ps_gfx>; memory-region = <&uat_ttbs>, <&uat_pagetables>, <&uat_handoff>, @@ -396,6 +485,55 @@ "hw-cal-a", "hw-cal-b", "globals"; apple,firmware-abi = <0 0 0>; + apple,firmware-version = <0 0 0>; + apple,firmware-compat = <0 0 0>; + + operating-points-v2 = <&gpu_opp>; + apple,cs-opp = <&gpu_cs_opp>; + apple,afr-opp = <&gpu_afr_opp>; + + apple,min-sram-microvolt = <790000>; + apple,csafr-min-sram-microvolt = <812000>; + apple,perf-base-pstate = <1>; + + apple,avg-power-min-duty-cycle = <40>; + apple,avg-power-target-filter-tc = <1>; + apple,fast-die0-proportional-gain = <34.0>; + apple,perf-boost-ce-step = <50>; + apple,perf-boost-min-util = <90>; + apple,perf-filter-drop-threshold = <0>; + apple,perf-filter-time-constant = <5>; + apple,perf-filter-time-constant2 = <200>; + apple,perf-integral-gain = <1.62>; + apple,perf-integral-gain2 = <1.62>; + apple,perf-integral-min-clamp = <0>; + apple,perf-proportional-gain2 = <5.4>; + apple,perf-proportional-gain = <5.4>; + apple,perf-tgt-utilization = <85>; + apple,power-sample-period = <8>; + apple,ppm-filter-time-constant-ms = <34>; + apple,ppm-ki = <18.0>; + apple,ppm-kp = <0.1>; + apple,pwr-filter-time-constant = <313>; + apple,pwr-integral-gain = <0.0202129>; + apple,pwr-integral-min-clamp = <0>; + apple,pwr-min-duty-cycle = <40>; + apple,pwr-proportional-gain = <5.2831855>; + apple,pwr-sample-period-aic-clks = <200000>; + apple,se-engagement-criteria = <700>; + apple,se-filter-time-constant = <9>; + apple,se-filter-time-constant-1 = <3>; + apple,se-inactive-threshold = <2500>; + apple,se-ki = <-50.0>; + apple,se-ki-1 = <-100.0>; + apple,se-kp = <-5.0>; + apple,se-kp-1 = <-10.0>; + apple,se-reset-criteria = <50>; + + apple,core-leak-coef = GPU_REPEAT(1200.0); + apple,sram-leak-coef = GPU_REPEAT(20.0); + apple,cs-leak-coef = GPU_DIE_REPEAT(400.0); + apple,afr-leak-coef = GPU_DIE_REPEAT(200.0); }; agx_mbox: mbox@406408000 { @@ -455,6 +593,8 @@ pinctrl-0 = <&pcie_pins>; pinctrl-names = "default"; + dma-coherent; + port00: pci@0,0 { device_type = "pci"; reg = <0x0 0x0 0x0 0x0 0x0>; diff --git a/arch/arm64/boot/dts/apple/t602x-j414-j416.dtsi b/arch/arm64/boot/dts/apple/t602x-j414-j416.dtsi index 0e806d8ddf81b1..b0fa34282681de 100644 --- a/arch/arm64/boot/dts/apple/t602x-j414-j416.dtsi +++ b/arch/arm64/boot/dts/apple/t602x-j414-j416.dtsi @@ -14,8 +14,15 @@ * the GPIO indices. */ +#define NO_SPI_TRACKPAD #include "t600x-j314-j316.dtsi" +/ { + aliases { + keyboard = &keyboard; + }; +}; + &framebuffer0 { power-domains = <&ps_disp0_cpu0>, <&ps_dptx_phy_ps>; }; @@ -36,6 +43,41 @@ interrupts = <44 IRQ_TYPE_LEVEL_LOW>; }; +&speaker_left_tweet { + shutdown-gpios = <&pinctrl_ap 57 GPIO_ACTIVE_HIGH>; + interrupts-extended = <&pinctrl_ap 58 IRQ_TYPE_LEVEL_LOW>; +}; + +&speaker_left_woof1 { + shutdown-gpios = <&pinctrl_ap 57 GPIO_ACTIVE_HIGH>; + interrupts-extended = <&pinctrl_ap 58 IRQ_TYPE_LEVEL_LOW>; +}; + +&speaker_left_woof2 { + shutdown-gpios = <&pinctrl_ap 57 GPIO_ACTIVE_HIGH>; + interrupts-extended = <&pinctrl_ap 58 IRQ_TYPE_LEVEL_LOW>; +}; + +&speaker_right_tweet { + shutdown-gpios = <&pinctrl_ap 57 GPIO_ACTIVE_HIGH>; + interrupts-extended = <&pinctrl_ap 58 IRQ_TYPE_LEVEL_LOW>; +}; + +&speaker_right_woof1 { + shutdown-gpios = <&pinctrl_ap 57 GPIO_ACTIVE_HIGH>; + interrupts-extended = <&pinctrl_ap 58 IRQ_TYPE_LEVEL_LOW>; +}; + +&speaker_right_woof2 { + shutdown-gpios = <&pinctrl_ap 57 GPIO_ACTIVE_HIGH>; + interrupts-extended = <&pinctrl_ap 58 IRQ_TYPE_LEVEL_LOW>; +}; + +&jack_codec { + reset-gpios = <&pinctrl_nub 8 GPIO_ACTIVE_HIGH>; + interrupts-extended = <&pinctrl_ap 59 IRQ_TYPE_LEVEL_LOW>; +}; + &wifi0 { compatible = "pci14e4,4434"; }; @@ -43,3 +85,7 @@ &bluetooth0 { compatible = "pci14e4,5f72"; }; + +&port01 { + pwren-gpios = <&smc_gpio 22 GPIO_ACTIVE_HIGH>; +}; diff --git a/arch/arm64/boot/dts/apple/t602x-j474-j475.dtsi b/arch/arm64/boot/dts/apple/t602x-j474-j475.dtsi index ee12fea5b12cb3..25c0e6bf41724b 100644 --- a/arch/arm64/boot/dts/apple/t602x-j474-j475.dtsi +++ b/arch/arm64/boot/dts/apple/t602x-j474-j475.dtsi @@ -21,6 +21,11 @@ power-domains = <&ps_dispext0_cpu0>, <&ps_dptx_phy_ps>; }; +/* disable dcp until it is supported */ +&dcp { + status = "disabled"; +}; + &hpm0 { interrupts = <44 IRQ_TYPE_LEVEL_LOW>; }; @@ -36,3 +41,23 @@ &hpm3 { interrupts = <44 IRQ_TYPE_LEVEL_LOW>; }; + +/* PCIe devices */ +&port00 { + pwren-gpios = <&smc_gpio 13 GPIO_ACTIVE_HIGH>; +}; + +&port03 { + /* USB xHCI */ + pwren-gpios = <&smc_gpio 19 GPIO_ACTIVE_HIGH>; +}; + +&speaker { + shutdown-gpios = <&pinctrl_ap 57 GPIO_ACTIVE_HIGH>; + interrupts-extended = <&pinctrl_ap 58 IRQ_TYPE_LEVEL_LOW>; +}; + +&jack_codec { + reset-gpios = <&pinctrl_nub 8 GPIO_ACTIVE_HIGH>; + interrupts-extended = <&pinctrl_ap 59 IRQ_TYPE_LEVEL_LOW>; +}; diff --git a/arch/arm64/boot/dts/apple/t602x-pmgr.dtsi b/arch/arm64/boot/dts/apple/t602x-pmgr.dtsi index f5382a2faf0b25..5fa2a93ad9ec20 100644 --- a/arch/arm64/boot/dts/apple/t602x-pmgr.dtsi +++ b/arch/arm64/boot/dts/apple/t602x-pmgr.dtsi @@ -447,6 +447,7 @@ power-domains = <&DIE_NODE(ps_dispext0_sys)>; }; + /* PMP is only present on die 0 of the M2 Ultra */ DIE_NODE(ps_pmp): power-controller@2c8 { compatible = "apple,t6020-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; reg = <0x2c8 4>; @@ -1449,6 +1450,7 @@ #power-domain-cells = <0>; #reset-cells = <0>; label = DIE_LABEL(dptx_phy_ps); + apple,always-on; power-domains = <&DIE_NODE(ps_sio)>; }; @@ -1853,6 +1855,7 @@ #reset-cells = <0>; label = DIE_LABEL(isp_cpu); /* power-domains = <&DIE_NODE(ps_isp_sys)>; */ + apple,force-disable; }; DIE_NODE(ps_isp_fe): power-controller@4008 { From 3d1f842a3b2bb24dc7f5590b2d50f2a0fee3fa86 Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Sun, 16 Nov 2025 13:06:48 +0100 Subject: [PATCH 2138/3902] HACK: arm64: dts: apple: t602x: Add generic compatibility Upstream disliked the generic "apple,*" compatibility strings so the t602x device trees upstream submission did not use them. m1n1 will add them back but carry them for 6.18 and 6.19 based downstream kernels. Drop with 6.18 + 2 Signed-off-by: Janne Grunau --- arch/arm64/boot/dts/apple/t602x-die0.dtsi | 36 +- arch/arm64/boot/dts/apple/t602x-dieX.dtsi | 6 +- arch/arm64/boot/dts/apple/t602x-nvme.dtsi | 2 +- arch/arm64/boot/dts/apple/t602x-pmgr.dtsi | 490 +++++++++++----------- 4 files changed, 267 insertions(+), 267 deletions(-) diff --git a/arch/arm64/boot/dts/apple/t602x-die0.dtsi b/arch/arm64/boot/dts/apple/t602x-die0.dtsi index 599bb5bc654f3e..dd3de629527e0d 100644 --- a/arch/arm64/boot/dts/apple/t602x-die0.dtsi +++ b/arch/arm64/boot/dts/apple/t602x-die0.dtsi @@ -7,7 +7,7 @@ */ nco: clock-controller@28e03c000 { - compatible = "apple,t6020-nco", "apple,t8103-nco"; + compatible = "apple,t6020-nco", "apple,t8103-nco", "apple,nco"; reg = <0x2 0x8e03c000 0x0 0x14000>; clocks = <&nco_clkref>; #clock-cells = <1>; @@ -30,7 +30,7 @@ }; nub_spmi0: spmi@29e114000 { - compatible = "apple,t6020-spmi", "apple,t8103-spmi"; + compatible = "apple,t6020-spmi", "apple,t8103-spmi", "apple,spmi"; reg = <0x2 0x9e114000 0x0 0x100>; #address-cells = <2>; #size-cells = <0>; @@ -87,7 +87,7 @@ }; wdt: watchdog@29e2c4000 { - compatible = "apple,t6020-wdt", "apple,t8103-wdt"; + compatible = "apple,t6020-wdt", "apple,t8103-wdt", "apple,wdt"; reg = <0x2 0x9e2c4000 0x0 0x4000>; clocks = <&clkref>; interrupt-parent = <&aic>; @@ -136,7 +136,7 @@ }; pinctrl_smc: pinctrl@2a2820000 { - compatible = "apple,t6020-pinctrl", "apple,t8103-pinctrl"; + compatible = "apple,t6020-pinctrl", "apple,t8103-pinctrl", "apple,pinctrl"; reg = <0x2 0xa2820000 0x0 0x4000>; gpio-controller; @@ -244,7 +244,7 @@ }; i2c0: i2c@39b040000 { - compatible = "apple,t6020-i2c", "apple,t8103-i2c"; + compatible = "apple,t6020-i2c", "apple,t8103-i2c", "apple,i2c"; reg = <0x3 0x9b040000 0x0 0x4000>; clocks = <&clkref>; interrupt-parent = <&aic>; @@ -257,7 +257,7 @@ }; i2c1: i2c@39b044000 { - compatible = "apple,t6020-i2c", "apple,t8103-i2c"; + compatible = "apple,t6020-i2c", "apple,t8103-i2c", "apple,i2c"; reg = <0x3 0x9b044000 0x0 0x4000>; clocks = <&clkref>; interrupt-parent = <&aic>; @@ -271,7 +271,7 @@ }; i2c2: i2c@39b048000 { - compatible = "apple,t6020-i2c", "apple,t8103-i2c"; + compatible = "apple,t6020-i2c", "apple,t8103-i2c", "apple,i2c"; reg = <0x3 0x9b048000 0x0 0x4000>; clocks = <&clkref>; interrupt-parent = <&aic>; @@ -285,7 +285,7 @@ }; i2c3: i2c@39b04c000 { - compatible = "apple,t6020-i2c", "apple,t8103-i2c"; + compatible = "apple,t6020-i2c", "apple,t8103-i2c", "apple,i2c"; reg = <0x3 0x9b04c000 0x0 0x4000>; clocks = <&clkref>; interrupt-parent = <&aic>; @@ -299,7 +299,7 @@ }; i2c4: i2c@39b050000 { - compatible = "apple,t6020-i2c", "apple,t8103-i2c"; + compatible = "apple,t6020-i2c", "apple,t8103-i2c", "apple,i2c"; reg = <0x3 0x9b050000 0x0 0x4000>; clocks = <&clkref>; interrupt-parent = <&aic>; @@ -313,7 +313,7 @@ }; i2c5: i2c@39b054000 { - compatible = "apple,t6020-i2c", "apple,t8103-i2c"; + compatible = "apple,t6020-i2c", "apple,t8103-i2c", "apple,i2c"; reg = <0x3 0x9b054000 0x0 0x4000>; clocks = <&clkref>; interrupt-parent = <&aic>; @@ -327,7 +327,7 @@ }; i2c6: i2c@39b054000 { - compatible = "apple,t6020-i2c", "apple,t8103-i2c"; + compatible = "apple,t6020-i2c", "apple,t8103-i2c", "apple,i2c"; reg = <0x3 0x9b054000 0x0 0x4000>; clocks = <&clkref>; interrupt-parent = <&aic>; @@ -341,7 +341,7 @@ }; i2c7: i2c@39b054000 { - compatible = "apple,t6020-i2c", "apple,t8103-i2c"; + compatible = "apple,t6020-i2c", "apple,t8103-i2c", "apple,i2c"; reg = <0x3 0x9b054000 0x0 0x4000>; clocks = <&clkref>; interrupt-parent = <&aic>; @@ -355,7 +355,7 @@ }; i2c8: i2c@39b054000 { - compatible = "apple,t6020-i2c", "apple,t8103-i2c"; + compatible = "apple,t6020-i2c", "apple,t8103-i2c", "apple,i2c"; reg = <0x3 0x9b054000 0x0 0x4000>; clocks = <&clkref>; interrupt-parent = <&aic>; @@ -369,7 +369,7 @@ }; spi1: spi@39b104000 { - compatible = "apple,t6020-spi", "apple,t8103-spi"; + compatible = "apple,t6020-spi", "apple,t8103-spi", "apple,spi"; reg = <0x3 0x9b104000 0x0 0x4000>; interrupt-parent = <&aic>; interrupts = ; @@ -383,7 +383,7 @@ }; spi2: spi@39b108000 { - compatible = "apple,t6020-spi", "apple,t8103-spi"; + compatible = "apple,t6020-spi", "apple,t8103-spi", "apple,spi"; reg = <0x3 0x9b108000 0x0 0x4000>; interrupt-parent = <&aic>; interrupts = ; @@ -397,7 +397,7 @@ }; spi4: spi@39b110000 { - compatible = "apple,t6020-spi", "apple,t8103-spi"; + compatible = "apple,t6020-spi", "apple,t8103-spi", "apple,spi"; reg = <0x3 0x9b110000 0x0 0x4000>; interrupt-parent = <&aic>; interrupts = ; @@ -427,7 +427,7 @@ }; admac: dma-controller@39b400000 { - compatible = "apple,t6020-admac", "apple,t8103-admac"; + compatible = "apple,t6020-admac", "apple,t8103-admac", "apple,admac"; reg = <0x3 0x9b400000 0x0 0x34000>; #dma-cells = <1>; dma-channels = <16>; @@ -441,7 +441,7 @@ }; mca: mca@39b600000 { - compatible = "apple,t6020-mca", "apple,t8103-mca"; + compatible = "apple,t6020-mca", "apple,t8103-mca", "apple,mca"; reg = <0x3 0x9b600000 0x0 0x10000>, <0x3 0x9b500000 0x0 0x20000>; clocks = <&nco 0>, <&nco 1>, <&nco 2>, <&nco 3>; diff --git a/arch/arm64/boot/dts/apple/t602x-dieX.dtsi b/arch/arm64/boot/dts/apple/t602x-dieX.dtsi index 1446e9e2c29c37..be6be79ca1d370 100644 --- a/arch/arm64/boot/dts/apple/t602x-dieX.dtsi +++ b/arch/arm64/boot/dts/apple/t602x-dieX.dtsi @@ -45,7 +45,7 @@ }; DIE_NODE(pinctrl_nub): pinctrl@29e1f0000 { - compatible = "apple,t6020-pinctrl", "apple,t8103-pinctrl"; + compatible = "apple,t6020-pinctrl", "apple,t8103-pinctrl", "apple,pinctrl"; reg = <0x2 0x9e1f0000 0x0 0x4000>; power-domains = <&DIE_NODE(ps_nub_gpio)>; @@ -81,7 +81,7 @@ }; DIE_NODE(pinctrl_aop): pinctrl@2a6820000 { - compatible = "apple,t6020-pinctrl", "apple,t8103-pinctrl"; + compatible = "apple,t6020-pinctrl", "apple,t8103-pinctrl", "apple,pinctrl"; reg = <0x2 0xa6820000 0x0 0x4000>; gpio-controller; @@ -102,7 +102,7 @@ }; DIE_NODE(pinctrl_ap): pinctrl@39b028000 { - compatible = "apple,t6020-pinctrl", "apple,t8103-pinctrl"; + compatible = "apple,t6020-pinctrl", "apple,t8103-pinctrl", "apple,pinctrl"; reg = <0x3 0x9b028000 0x0 0x4000>; interrupt-parent = <&aic>; diff --git a/arch/arm64/boot/dts/apple/t602x-nvme.dtsi b/arch/arm64/boot/dts/apple/t602x-nvme.dtsi index 590cec8ac804c0..eb8c4e359079e5 100644 --- a/arch/arm64/boot/dts/apple/t602x-nvme.dtsi +++ b/arch/arm64/boot/dts/apple/t602x-nvme.dtsi @@ -26,7 +26,7 @@ }; DIE_NODE(nvme): nvme@34bcc0000 { - compatible = "apple,t6020-nvme-ans2", "apple,t8103-nvme-ans2"; + compatible = "apple,t6020-nvme-ans2", "apple,t8103-nvme-ans2", "apple,nvme-ans2"; reg = <0x3 0x4bcc0000 0x0 0x40000>, <0x3 0x47400000 0x0 0x4000>; reg-names = "nvme", "ans"; interrupt-parent = <&aic>; diff --git a/arch/arm64/boot/dts/apple/t602x-pmgr.dtsi b/arch/arm64/boot/dts/apple/t602x-pmgr.dtsi index 5fa2a93ad9ec20..f5ebd5bc19b33a 100644 --- a/arch/arm64/boot/dts/apple/t602x-pmgr.dtsi +++ b/arch/arm64/boot/dts/apple/t602x-pmgr.dtsi @@ -7,7 +7,7 @@ &DIE_NODE(pmgr) { DIE_NODE(ps_afi): power-controller@100 { - compatible = "apple,t6020-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + compatible = "apple,t6020-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate", "apple,pmgr-pwrstate"; reg = <0x100 4>; #power-domain-cells = <0>; #reset-cells = <0>; @@ -16,7 +16,7 @@ }; DIE_NODE(ps_aic): power-controller@108 { - compatible = "apple,t6020-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + compatible = "apple,t6020-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate", "apple,pmgr-pwrstate"; reg = <0x108 4>; #power-domain-cells = <0>; #reset-cells = <0>; @@ -25,7 +25,7 @@ }; DIE_NODE(ps_dwi): power-controller@110 { - compatible = "apple,t6020-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + compatible = "apple,t6020-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate", "apple,pmgr-pwrstate"; reg = <0x110 4>; #power-domain-cells = <0>; #reset-cells = <0>; @@ -33,7 +33,7 @@ }; DIE_NODE(ps_pms): power-controller@118 { - compatible = "apple,t6020-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + compatible = "apple,t6020-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate", "apple,pmgr-pwrstate"; reg = <0x118 4>; #power-domain-cells = <0>; #reset-cells = <0>; @@ -42,7 +42,7 @@ }; DIE_NODE(ps_gpio): power-controller@120 { - compatible = "apple,t6020-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + compatible = "apple,t6020-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate", "apple,pmgr-pwrstate"; reg = <0x120 4>; #power-domain-cells = <0>; #reset-cells = <0>; @@ -51,7 +51,7 @@ }; DIE_NODE(ps_soc_dpe): power-controller@128 { - compatible = "apple,t6020-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + compatible = "apple,t6020-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate", "apple,pmgr-pwrstate"; reg = <0x128 4>; #power-domain-cells = <0>; #reset-cells = <0>; @@ -60,7 +60,7 @@ }; DIE_NODE(ps_pms_c1ppt): power-controller@130 { - compatible = "apple,t6020-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + compatible = "apple,t6020-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate", "apple,pmgr-pwrstate"; reg = <0x130 4>; #power-domain-cells = <0>; #reset-cells = <0>; @@ -69,7 +69,7 @@ }; DIE_NODE(ps_pmgr_soc_ocla): power-controller@138 { - compatible = "apple,t6020-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + compatible = "apple,t6020-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate", "apple,pmgr-pwrstate"; reg = <0x138 4>; #power-domain-cells = <0>; #reset-cells = <0>; @@ -78,7 +78,7 @@ }; DIE_NODE(ps_amcc0): power-controller@168 { - compatible = "apple,t6020-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + compatible = "apple,t6020-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate", "apple,pmgr-pwrstate"; reg = <0x168 4>; #power-domain-cells = <0>; #reset-cells = <0>; @@ -87,7 +87,7 @@ }; DIE_NODE(ps_amcc2): power-controller@170 { - compatible = "apple,t6020-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + compatible = "apple,t6020-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate", "apple,pmgr-pwrstate"; reg = <0x170 4>; #power-domain-cells = <0>; #reset-cells = <0>; @@ -96,7 +96,7 @@ }; DIE_NODE(ps_dcs_00): power-controller@178 { - compatible = "apple,t6020-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + compatible = "apple,t6020-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate", "apple,pmgr-pwrstate"; reg = <0x178 4>; #power-domain-cells = <0>; #reset-cells = <0>; @@ -105,7 +105,7 @@ }; DIE_NODE(ps_dcs_01): power-controller@180 { - compatible = "apple,t6020-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + compatible = "apple,t6020-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate", "apple,pmgr-pwrstate"; reg = <0x180 4>; #power-domain-cells = <0>; #reset-cells = <0>; @@ -114,7 +114,7 @@ }; DIE_NODE(ps_dcs_02): power-controller@188 { - compatible = "apple,t6020-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + compatible = "apple,t6020-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate", "apple,pmgr-pwrstate"; reg = <0x188 4>; #power-domain-cells = <0>; #reset-cells = <0>; @@ -123,7 +123,7 @@ }; DIE_NODE(ps_dcs_03): power-controller@190 { - compatible = "apple,t6020-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + compatible = "apple,t6020-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate", "apple,pmgr-pwrstate"; reg = <0x190 4>; #power-domain-cells = <0>; #reset-cells = <0>; @@ -132,7 +132,7 @@ }; DIE_NODE(ps_dcs_08): power-controller@198 { - compatible = "apple,t6020-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + compatible = "apple,t6020-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate", "apple,pmgr-pwrstate"; reg = <0x198 4>; #power-domain-cells = <0>; #reset-cells = <0>; @@ -141,7 +141,7 @@ }; DIE_NODE(ps_dcs_09): power-controller@1a0 { - compatible = "apple,t6020-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + compatible = "apple,t6020-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate", "apple,pmgr-pwrstate"; reg = <0x1a0 4>; #power-domain-cells = <0>; #reset-cells = <0>; @@ -150,7 +150,7 @@ }; DIE_NODE(ps_dcs_10): power-controller@1a8 { - compatible = "apple,t6020-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + compatible = "apple,t6020-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate", "apple,pmgr-pwrstate"; reg = <0x1a8 4>; #power-domain-cells = <0>; #reset-cells = <0>; @@ -159,7 +159,7 @@ }; DIE_NODE(ps_dcs_11): power-controller@1b0 { - compatible = "apple,t6020-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + compatible = "apple,t6020-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate", "apple,pmgr-pwrstate"; reg = <0x1b0 4>; #power-domain-cells = <0>; #reset-cells = <0>; @@ -168,7 +168,7 @@ }; DIE_NODE(ps_afnc1_ioa): power-controller@1b8 { - compatible = "apple,t6020-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + compatible = "apple,t6020-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate", "apple,pmgr-pwrstate"; reg = <0x1b8 4>; #power-domain-cells = <0>; #reset-cells = <0>; @@ -178,7 +178,7 @@ }; DIE_NODE(ps_afc): power-controller@1d0 { - compatible = "apple,t6020-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + compatible = "apple,t6020-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate", "apple,pmgr-pwrstate"; reg = <0x1d0 4>; #power-domain-cells = <0>; #reset-cells = <0>; @@ -187,7 +187,7 @@ }; DIE_NODE(ps_afnc0_ioa): power-controller@1e8 { - compatible = "apple,t6020-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + compatible = "apple,t6020-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate", "apple,pmgr-pwrstate"; reg = <0x1e8 4>; #power-domain-cells = <0>; #reset-cells = <0>; @@ -197,7 +197,7 @@ }; DIE_NODE(ps_afnc1_ls): power-controller@1f0 { - compatible = "apple,t6020-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + compatible = "apple,t6020-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate", "apple,pmgr-pwrstate"; reg = <0x1f0 4>; #power-domain-cells = <0>; #reset-cells = <0>; @@ -207,7 +207,7 @@ }; DIE_NODE(ps_afnc0_ls): power-controller@1f8 { - compatible = "apple,t6020-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + compatible = "apple,t6020-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate", "apple,pmgr-pwrstate"; reg = <0x1f8 4>; #power-domain-cells = <0>; #reset-cells = <0>; @@ -217,7 +217,7 @@ }; DIE_NODE(ps_afnc1_lw0): power-controller@200 { - compatible = "apple,t6020-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + compatible = "apple,t6020-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate", "apple,pmgr-pwrstate"; reg = <0x200 4>; #power-domain-cells = <0>; #reset-cells = <0>; @@ -227,7 +227,7 @@ }; DIE_NODE(ps_afnc1_lw1): power-controller@208 { - compatible = "apple,t6020-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + compatible = "apple,t6020-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate", "apple,pmgr-pwrstate"; reg = <0x208 4>; #power-domain-cells = <0>; #reset-cells = <0>; @@ -237,7 +237,7 @@ }; DIE_NODE(ps_afnc1_lw2): power-controller@210 { - compatible = "apple,t6020-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + compatible = "apple,t6020-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate", "apple,pmgr-pwrstate"; reg = <0x210 4>; #power-domain-cells = <0>; #reset-cells = <0>; @@ -247,7 +247,7 @@ }; DIE_NODE(ps_afnc0_lw0): power-controller@218 { - compatible = "apple,t6020-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + compatible = "apple,t6020-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate", "apple,pmgr-pwrstate"; reg = <0x218 4>; #power-domain-cells = <0>; #reset-cells = <0>; @@ -257,7 +257,7 @@ }; DIE_NODE(ps_scodec): power-controller@220 { - compatible = "apple,t6020-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + compatible = "apple,t6020-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate", "apple,pmgr-pwrstate"; reg = <0x220 4>; #power-domain-cells = <0>; #reset-cells = <0>; @@ -266,7 +266,7 @@ }; DIE_NODE(ps_atc0_common): power-controller@228 { - compatible = "apple,t6020-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + compatible = "apple,t6020-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate", "apple,pmgr-pwrstate"; reg = <0x228 4>; #power-domain-cells = <0>; #reset-cells = <0>; @@ -275,7 +275,7 @@ }; DIE_NODE(ps_atc1_common): power-controller@230 { - compatible = "apple,t6020-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + compatible = "apple,t6020-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate", "apple,pmgr-pwrstate"; reg = <0x230 4>; #power-domain-cells = <0>; #reset-cells = <0>; @@ -284,7 +284,7 @@ }; DIE_NODE(ps_atc2_common): power-controller@238 { - compatible = "apple,t6020-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + compatible = "apple,t6020-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate", "apple,pmgr-pwrstate"; reg = <0x238 4>; #power-domain-cells = <0>; #reset-cells = <0>; @@ -293,7 +293,7 @@ }; DIE_NODE(ps_atc3_common): power-controller@240 { - compatible = "apple,t6020-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + compatible = "apple,t6020-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate", "apple,pmgr-pwrstate"; reg = <0x240 4>; #power-domain-cells = <0>; #reset-cells = <0>; @@ -302,7 +302,7 @@ }; DIE_NODE(ps_dispext1_sys): power-controller@248 { - compatible = "apple,t6020-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + compatible = "apple,t6020-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate", "apple,pmgr-pwrstate"; reg = <0x248 4>; #power-domain-cells = <0>; #reset-cells = <0>; @@ -311,7 +311,7 @@ }; DIE_NODE(ps_pms_bridge): power-controller@250 { - compatible = "apple,t6020-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + compatible = "apple,t6020-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate", "apple,pmgr-pwrstate"; reg = <0x250 4>; #power-domain-cells = <0>; #reset-cells = <0>; @@ -321,7 +321,7 @@ }; DIE_NODE(ps_dispext0_sys): power-controller@258 { - compatible = "apple,t6020-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + compatible = "apple,t6020-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate", "apple,pmgr-pwrstate"; reg = <0x258 4>; #power-domain-cells = <0>; #reset-cells = <0>; @@ -330,7 +330,7 @@ }; DIE_NODE(ps_ane_sys): power-controller@260 { - compatible = "apple,t6020-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + compatible = "apple,t6020-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate", "apple,pmgr-pwrstate"; reg = <0x260 4>; #power-domain-cells = <0>; #reset-cells = <0>; @@ -339,7 +339,7 @@ }; DIE_NODE(ps_avd_sys): power-controller@268 { - compatible = "apple,t6020-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + compatible = "apple,t6020-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate", "apple,pmgr-pwrstate"; reg = <0x268 4>; #power-domain-cells = <0>; #reset-cells = <0>; @@ -348,7 +348,7 @@ }; DIE_NODE(ps_atc0_cio): power-controller@270 { - compatible = "apple,t6020-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + compatible = "apple,t6020-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate", "apple,pmgr-pwrstate"; reg = <0x270 4>; #power-domain-cells = <0>; #reset-cells = <0>; @@ -357,7 +357,7 @@ }; DIE_NODE(ps_atc0_pcie): power-controller@278 { - compatible = "apple,t6020-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + compatible = "apple,t6020-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate", "apple,pmgr-pwrstate"; reg = <0x278 4>; #power-domain-cells = <0>; #reset-cells = <0>; @@ -366,7 +366,7 @@ }; DIE_NODE(ps_atc1_cio): power-controller@280 { - compatible = "apple,t6020-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + compatible = "apple,t6020-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate", "apple,pmgr-pwrstate"; reg = <0x280 4>; #power-domain-cells = <0>; #reset-cells = <0>; @@ -375,7 +375,7 @@ }; DIE_NODE(ps_atc1_pcie): power-controller@288 { - compatible = "apple,t6020-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + compatible = "apple,t6020-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate", "apple,pmgr-pwrstate"; reg = <0x288 4>; #power-domain-cells = <0>; #reset-cells = <0>; @@ -384,7 +384,7 @@ }; DIE_NODE(ps_atc2_cio): power-controller@290 { - compatible = "apple,t6020-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + compatible = "apple,t6020-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate", "apple,pmgr-pwrstate"; reg = <0x290 4>; #power-domain-cells = <0>; #reset-cells = <0>; @@ -393,7 +393,7 @@ }; DIE_NODE(ps_atc2_pcie): power-controller@298 { - compatible = "apple,t6020-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + compatible = "apple,t6020-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate", "apple,pmgr-pwrstate"; reg = <0x298 4>; #power-domain-cells = <0>; #reset-cells = <0>; @@ -402,7 +402,7 @@ }; DIE_NODE(ps_atc3_cio): power-controller@2a0 { - compatible = "apple,t6020-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + compatible = "apple,t6020-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate", "apple,pmgr-pwrstate"; reg = <0x2a0 4>; #power-domain-cells = <0>; #reset-cells = <0>; @@ -411,7 +411,7 @@ }; DIE_NODE(ps_atc3_pcie): power-controller@2a8 { - compatible = "apple,t6020-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + compatible = "apple,t6020-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate", "apple,pmgr-pwrstate"; reg = <0x2a8 4>; #power-domain-cells = <0>; #reset-cells = <0>; @@ -420,7 +420,7 @@ }; DIE_NODE(ps_dispext1_fe): power-controller@2b0 { - compatible = "apple,t6020-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + compatible = "apple,t6020-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate", "apple,pmgr-pwrstate"; reg = <0x2b0 4>; #power-domain-cells = <0>; #reset-cells = <0>; @@ -429,7 +429,7 @@ }; DIE_NODE(ps_dispext1_cpu0): power-controller@2b8 { - compatible = "apple,t6020-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + compatible = "apple,t6020-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate", "apple,pmgr-pwrstate"; reg = <0x2b8 4>; #power-domain-cells = <0>; #reset-cells = <0>; @@ -439,7 +439,7 @@ }; DIE_NODE(ps_dispext0_fe): power-controller@2c0 { - compatible = "apple,t6020-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + compatible = "apple,t6020-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate", "apple,pmgr-pwrstate"; reg = <0x2c0 4>; #power-domain-cells = <0>; #reset-cells = <0>; @@ -449,7 +449,7 @@ /* PMP is only present on die 0 of the M2 Ultra */ DIE_NODE(ps_pmp): power-controller@2c8 { - compatible = "apple,t6020-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + compatible = "apple,t6020-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate", "apple,pmgr-pwrstate"; reg = <0x2c8 4>; #power-domain-cells = <0>; #reset-cells = <0>; @@ -457,7 +457,7 @@ }; DIE_NODE(ps_pms_sram): power-controller@2d0 { - compatible = "apple,t6020-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + compatible = "apple,t6020-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate", "apple,pmgr-pwrstate"; reg = <0x2d0 4>; #power-domain-cells = <0>; #reset-cells = <0>; @@ -465,7 +465,7 @@ }; DIE_NODE(ps_dispext0_cpu0): power-controller@2d8 { - compatible = "apple,t6020-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + compatible = "apple,t6020-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate", "apple,pmgr-pwrstate"; reg = <0x2d8 4>; #power-domain-cells = <0>; #reset-cells = <0>; @@ -475,7 +475,7 @@ }; DIE_NODE(ps_ane_cpu): power-controller@2e0 { - compatible = "apple,t6020-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + compatible = "apple,t6020-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate", "apple,pmgr-pwrstate"; reg = <0x2e0 4>; #power-domain-cells = <0>; #reset-cells = <0>; @@ -484,7 +484,7 @@ }; DIE_NODE(ps_atc0_cio_pcie): power-controller@2e8 { - compatible = "apple,t6020-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + compatible = "apple,t6020-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate", "apple,pmgr-pwrstate"; reg = <0x2e8 4>; #power-domain-cells = <0>; #reset-cells = <0>; @@ -493,7 +493,7 @@ }; DIE_NODE(ps_atc0_cio_usb): power-controller@2f0 { - compatible = "apple,t6020-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + compatible = "apple,t6020-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate", "apple,pmgr-pwrstate"; reg = <0x2f0 4>; #power-domain-cells = <0>; #reset-cells = <0>; @@ -502,7 +502,7 @@ }; DIE_NODE(ps_atc1_cio_pcie): power-controller@2f8 { - compatible = "apple,t6020-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + compatible = "apple,t6020-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate", "apple,pmgr-pwrstate"; reg = <0x2f8 4>; #power-domain-cells = <0>; #reset-cells = <0>; @@ -511,7 +511,7 @@ }; DIE_NODE(ps_atc1_cio_usb): power-controller@300 { - compatible = "apple,t6020-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + compatible = "apple,t6020-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate", "apple,pmgr-pwrstate"; reg = <0x300 4>; #power-domain-cells = <0>; #reset-cells = <0>; @@ -520,7 +520,7 @@ }; DIE_NODE(ps_atc2_cio_pcie): power-controller@308 { - compatible = "apple,t6020-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + compatible = "apple,t6020-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate", "apple,pmgr-pwrstate"; reg = <0x308 4>; #power-domain-cells = <0>; #reset-cells = <0>; @@ -529,7 +529,7 @@ }; DIE_NODE(ps_atc2_cio_usb): power-controller@310 { - compatible = "apple,t6020-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + compatible = "apple,t6020-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate", "apple,pmgr-pwrstate"; reg = <0x310 4>; #power-domain-cells = <0>; #reset-cells = <0>; @@ -538,7 +538,7 @@ }; DIE_NODE(ps_atc3_cio_pcie): power-controller@318 { - compatible = "apple,t6020-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + compatible = "apple,t6020-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate", "apple,pmgr-pwrstate"; reg = <0x318 4>; #power-domain-cells = <0>; #reset-cells = <0>; @@ -547,7 +547,7 @@ }; DIE_NODE(ps_atc3_cio_usb): power-controller@320 { - compatible = "apple,t6020-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + compatible = "apple,t6020-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate", "apple,pmgr-pwrstate"; reg = <0x320 4>; #power-domain-cells = <0>; #reset-cells = <0>; @@ -556,7 +556,7 @@ }; DIE_NODE(ps_trace_fab): power-controller@390 { - compatible = "apple,t6020-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + compatible = "apple,t6020-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate", "apple,pmgr-pwrstate"; reg = <0x390 4>; #power-domain-cells = <0>; #reset-cells = <0>; @@ -564,7 +564,7 @@ }; DIE_NODE(ps_ane_sys_mpm): power-controller@4000 { - compatible = "apple,t6020-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + compatible = "apple,t6020-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate", "apple,pmgr-pwrstate"; reg = <0x4000 4>; #power-domain-cells = <0>; #reset-cells = <0>; @@ -573,7 +573,7 @@ }; DIE_NODE(ps_ane_td): power-controller@4008 { - compatible = "apple,t6020-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + compatible = "apple,t6020-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate", "apple,pmgr-pwrstate"; reg = <0x4008 4>; #power-domain-cells = <0>; #reset-cells = <0>; @@ -582,7 +582,7 @@ }; DIE_NODE(ps_ane_base): power-controller@4010 { - compatible = "apple,t6020-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + compatible = "apple,t6020-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate", "apple,pmgr-pwrstate"; reg = <0x4010 4>; #power-domain-cells = <0>; #reset-cells = <0>; @@ -591,7 +591,7 @@ }; DIE_NODE(ps_ane_set1): power-controller@4018 { - compatible = "apple,t6020-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + compatible = "apple,t6020-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate", "apple,pmgr-pwrstate"; reg = <0x4018 4>; #power-domain-cells = <0>; #reset-cells = <0>; @@ -600,7 +600,7 @@ }; DIE_NODE(ps_ane_set2): power-controller@4020 { - compatible = "apple,t6020-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + compatible = "apple,t6020-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate", "apple,pmgr-pwrstate"; reg = <0x4020 4>; #power-domain-cells = <0>; #reset-cells = <0>; @@ -609,7 +609,7 @@ }; DIE_NODE(ps_ane_set3): power-controller@4028 { - compatible = "apple,t6020-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + compatible = "apple,t6020-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate", "apple,pmgr-pwrstate"; reg = <0x4028 4>; #power-domain-cells = <0>; #reset-cells = <0>; @@ -618,7 +618,7 @@ }; DIE_NODE(ps_ane_set4): power-controller@4030 { - compatible = "apple,t6020-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + compatible = "apple,t6020-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate", "apple,pmgr-pwrstate"; reg = <0x4030 4>; #power-domain-cells = <0>; #reset-cells = <0>; @@ -629,7 +629,7 @@ &DIE_NODE(pmgr_south) { DIE_NODE(ps_amcc4): power-controller@100 { - compatible = "apple,t6020-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + compatible = "apple,t6020-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate", "apple,pmgr-pwrstate"; reg = <0x100 4>; #power-domain-cells = <0>; #reset-cells = <0>; @@ -638,7 +638,7 @@ }; DIE_NODE(ps_amcc5): power-controller@108 { - compatible = "apple,t6020-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + compatible = "apple,t6020-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate", "apple,pmgr-pwrstate"; reg = <0x108 4>; #power-domain-cells = <0>; #reset-cells = <0>; @@ -647,7 +647,7 @@ }; DIE_NODE(ps_amcc6): power-controller@110 { - compatible = "apple,t6020-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + compatible = "apple,t6020-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate", "apple,pmgr-pwrstate"; reg = <0x110 4>; #power-domain-cells = <0>; #reset-cells = <0>; @@ -656,7 +656,7 @@ }; DIE_NODE(ps_amcc7): power-controller@118 { - compatible = "apple,t6020-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + compatible = "apple,t6020-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate", "apple,pmgr-pwrstate"; reg = <0x118 4>; #power-domain-cells = <0>; #reset-cells = <0>; @@ -665,7 +665,7 @@ }; DIE_NODE(ps_dcs_16): power-controller@120 { - compatible = "apple,t6020-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + compatible = "apple,t6020-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate", "apple,pmgr-pwrstate"; reg = <0x120 4>; #power-domain-cells = <0>; #reset-cells = <0>; @@ -674,7 +674,7 @@ }; DIE_NODE(ps_dcs_17): power-controller@128 { - compatible = "apple,t6020-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + compatible = "apple,t6020-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate", "apple,pmgr-pwrstate"; reg = <0x128 4>; #power-domain-cells = <0>; #reset-cells = <0>; @@ -683,7 +683,7 @@ }; DIE_NODE(ps_dcs_18): power-controller@130 { - compatible = "apple,t6020-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + compatible = "apple,t6020-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate", "apple,pmgr-pwrstate"; reg = <0x130 4>; #power-domain-cells = <0>; #reset-cells = <0>; @@ -692,7 +692,7 @@ }; DIE_NODE(ps_dcs_19): power-controller@138 { - compatible = "apple,t6020-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + compatible = "apple,t6020-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate", "apple,pmgr-pwrstate"; reg = <0x138 4>; #power-domain-cells = <0>; #reset-cells = <0>; @@ -701,7 +701,7 @@ }; DIE_NODE(ps_dcs_20): power-controller@140 { - compatible = "apple,t6020-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + compatible = "apple,t6020-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate", "apple,pmgr-pwrstate"; reg = <0x140 4>; #power-domain-cells = <0>; #reset-cells = <0>; @@ -710,7 +710,7 @@ }; DIE_NODE(ps_dcs_21): power-controller@148 { - compatible = "apple,t6020-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + compatible = "apple,t6020-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate", "apple,pmgr-pwrstate"; reg = <0x148 4>; #power-domain-cells = <0>; #reset-cells = <0>; @@ -719,7 +719,7 @@ }; DIE_NODE(ps_dcs_22): power-controller@150 { - compatible = "apple,t6020-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + compatible = "apple,t6020-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate", "apple,pmgr-pwrstate"; reg = <0x150 4>; #power-domain-cells = <0>; #reset-cells = <0>; @@ -728,7 +728,7 @@ }; DIE_NODE(ps_dcs_23): power-controller@158 { - compatible = "apple,t6020-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + compatible = "apple,t6020-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate", "apple,pmgr-pwrstate"; reg = <0x158 4>; #power-domain-cells = <0>; #reset-cells = <0>; @@ -737,7 +737,7 @@ }; DIE_NODE(ps_dcs_24): power-controller@160 { - compatible = "apple,t6020-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + compatible = "apple,t6020-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate", "apple,pmgr-pwrstate"; reg = <0x160 4>; #power-domain-cells = <0>; #reset-cells = <0>; @@ -746,7 +746,7 @@ }; DIE_NODE(ps_dcs_25): power-controller@168 { - compatible = "apple,t6020-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + compatible = "apple,t6020-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate", "apple,pmgr-pwrstate"; reg = <0x168 4>; #power-domain-cells = <0>; #reset-cells = <0>; @@ -755,7 +755,7 @@ }; DIE_NODE(ps_dcs_26): power-controller@170 { - compatible = "apple,t6020-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + compatible = "apple,t6020-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate", "apple,pmgr-pwrstate"; reg = <0x170 4>; #power-domain-cells = <0>; #reset-cells = <0>; @@ -764,7 +764,7 @@ }; DIE_NODE(ps_dcs_27): power-controller@178 { - compatible = "apple,t6020-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + compatible = "apple,t6020-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate", "apple,pmgr-pwrstate"; reg = <0x178 4>; #power-domain-cells = <0>; #reset-cells = <0>; @@ -773,7 +773,7 @@ }; DIE_NODE(ps_dcs_28): power-controller@180 { - compatible = "apple,t6020-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + compatible = "apple,t6020-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate", "apple,pmgr-pwrstate"; reg = <0x180 4>; #power-domain-cells = <0>; #reset-cells = <0>; @@ -782,7 +782,7 @@ }; DIE_NODE(ps_dcs_29): power-controller@188 { - compatible = "apple,t6020-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + compatible = "apple,t6020-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate", "apple,pmgr-pwrstate"; reg = <0x188 4>; #power-domain-cells = <0>; #reset-cells = <0>; @@ -791,7 +791,7 @@ }; DIE_NODE(ps_dcs_30): power-controller@190 { - compatible = "apple,t6020-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + compatible = "apple,t6020-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate", "apple,pmgr-pwrstate"; reg = <0x190 4>; #power-domain-cells = <0>; #reset-cells = <0>; @@ -800,7 +800,7 @@ }; DIE_NODE(ps_dcs_31): power-controller@198 { - compatible = "apple,t6020-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + compatible = "apple,t6020-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate", "apple,pmgr-pwrstate"; reg = <0x198 4>; #power-domain-cells = <0>; #reset-cells = <0>; @@ -809,7 +809,7 @@ }; DIE_NODE(ps_afnc4_ioa): power-controller@1a0 { - compatible = "apple,t6020-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + compatible = "apple,t6020-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate", "apple,pmgr-pwrstate"; reg = <0x1a0 4>; #power-domain-cells = <0>; #reset-cells = <0>; @@ -819,7 +819,7 @@ }; DIE_NODE(ps_afnc4_ls): power-controller@1a8 { - compatible = "apple,t6020-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + compatible = "apple,t6020-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate", "apple,pmgr-pwrstate"; reg = <0x1a8 4>; #power-domain-cells = <0>; #reset-cells = <0>; @@ -829,7 +829,7 @@ }; DIE_NODE(ps_afnc4_lw0): power-controller@1b0 { - compatible = "apple,t6020-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + compatible = "apple,t6020-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate", "apple,pmgr-pwrstate"; reg = <0x1b0 4>; #power-domain-cells = <0>; #reset-cells = <0>; @@ -839,7 +839,7 @@ }; DIE_NODE(ps_afnc5_ioa): power-controller@1b8 { - compatible = "apple,t6020-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + compatible = "apple,t6020-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate", "apple,pmgr-pwrstate"; reg = <0x1b8 4>; #power-domain-cells = <0>; #reset-cells = <0>; @@ -849,7 +849,7 @@ }; DIE_NODE(ps_afnc5_ls): power-controller@1c0 { - compatible = "apple,t6020-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + compatible = "apple,t6020-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate", "apple,pmgr-pwrstate"; reg = <0x1c0 4>; #power-domain-cells = <0>; #reset-cells = <0>; @@ -859,7 +859,7 @@ }; DIE_NODE(ps_afnc5_lw0): power-controller@1c8 { - compatible = "apple,t6020-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + compatible = "apple,t6020-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate", "apple,pmgr-pwrstate"; reg = <0x1c8 4>; #power-domain-cells = <0>; #reset-cells = <0>; @@ -869,7 +869,7 @@ }; DIE_NODE(ps_dispext2_sys): power-controller@1d0 { - compatible = "apple,t6020-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + compatible = "apple,t6020-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate", "apple,pmgr-pwrstate"; reg = <0x1d0 4>; #power-domain-cells = <0>; #reset-cells = <0>; @@ -877,7 +877,7 @@ }; DIE_NODE(ps_msr1): power-controller@1d8 { - compatible = "apple,t6020-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + compatible = "apple,t6020-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate", "apple,pmgr-pwrstate"; reg = <0x1d8 4>; #power-domain-cells = <0>; #reset-cells = <0>; @@ -885,7 +885,7 @@ }; DIE_NODE(ps_dispext2_fe): power-controller@1e0 { - compatible = "apple,t6020-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + compatible = "apple,t6020-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate", "apple,pmgr-pwrstate"; reg = <0x1e0 4>; #power-domain-cells = <0>; #reset-cells = <0>; @@ -894,7 +894,7 @@ }; DIE_NODE(ps_dispext2_cpu0): power-controller@1e8 { - compatible = "apple,t6020-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + compatible = "apple,t6020-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate", "apple,pmgr-pwrstate"; reg = <0x1e8 4>; #power-domain-cells = <0>; #reset-cells = <0>; @@ -904,7 +904,7 @@ }; DIE_NODE(ps_msr1_ase_core): power-controller@1f0 { - compatible = "apple,t6020-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + compatible = "apple,t6020-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate", "apple,pmgr-pwrstate"; reg = <0x1f0 4>; #power-domain-cells = <0>; #reset-cells = <0>; @@ -913,7 +913,7 @@ }; DIE_NODE(ps_dispext3_sys): power-controller@220 { - compatible = "apple,t6020-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + compatible = "apple,t6020-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate", "apple,pmgr-pwrstate"; reg = <0x220 4>; #power-domain-cells = <0>; #reset-cells = <0>; @@ -921,7 +921,7 @@ }; DIE_NODE(ps_venc1_sys): power-controller@228 { - compatible = "apple,t6020-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + compatible = "apple,t6020-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate", "apple,pmgr-pwrstate"; reg = <0x228 4>; #power-domain-cells = <0>; #reset-cells = <0>; @@ -929,7 +929,7 @@ }; DIE_NODE(ps_dispext3_fe): power-controller@230 { - compatible = "apple,t6020-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + compatible = "apple,t6020-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate", "apple,pmgr-pwrstate"; reg = <0x230 4>; #power-domain-cells = <0>; #reset-cells = <0>; @@ -938,7 +938,7 @@ }; DIE_NODE(ps_dispext3_cpu0): power-controller@238 { - compatible = "apple,t6020-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + compatible = "apple,t6020-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate", "apple,pmgr-pwrstate"; reg = <0x238 4>; #power-domain-cells = <0>; #reset-cells = <0>; @@ -948,7 +948,7 @@ }; DIE_NODE(ps_venc1_dma): power-controller@4000 { - compatible = "apple,t6020-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + compatible = "apple,t6020-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate", "apple,pmgr-pwrstate"; reg = <0x4000 4>; #power-domain-cells = <0>; #reset-cells = <0>; @@ -957,7 +957,7 @@ }; DIE_NODE(ps_venc1_pipe4): power-controller@4008 { - compatible = "apple,t6020-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + compatible = "apple,t6020-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate", "apple,pmgr-pwrstate"; reg = <0x4008 4>; #power-domain-cells = <0>; #reset-cells = <0>; @@ -966,7 +966,7 @@ }; DIE_NODE(ps_venc1_pipe5): power-controller@4010 { - compatible = "apple,t6020-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + compatible = "apple,t6020-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate", "apple,pmgr-pwrstate"; reg = <0x4010 4>; #power-domain-cells = <0>; #reset-cells = <0>; @@ -975,7 +975,7 @@ }; DIE_NODE(ps_venc1_me0): power-controller@4018 { - compatible = "apple,t6020-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + compatible = "apple,t6020-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate", "apple,pmgr-pwrstate"; reg = <0x4018 4>; #power-domain-cells = <0>; #reset-cells = <0>; @@ -984,7 +984,7 @@ }; DIE_NODE(ps_venc1_me1): power-controller@4020 { - compatible = "apple,t6020-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + compatible = "apple,t6020-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate", "apple,pmgr-pwrstate"; reg = <0x4020 4>; #power-domain-cells = <0>; #reset-cells = <0>; @@ -995,7 +995,7 @@ &DIE_NODE(pmgr_east) { DIE_NODE(ps_clvr_spmi0): power-controller@100 { - compatible = "apple,t6020-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + compatible = "apple,t6020-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate", "apple,pmgr-pwrstate"; reg = <0x100 4>; #power-domain-cells = <0>; #reset-cells = <0>; @@ -1004,7 +1004,7 @@ }; DIE_NODE(ps_clvr_spmi1): power-controller@108 { - compatible = "apple,t6020-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + compatible = "apple,t6020-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate", "apple,pmgr-pwrstate"; reg = <0x108 4>; #power-domain-cells = <0>; #reset-cells = <0>; @@ -1013,7 +1013,7 @@ }; DIE_NODE(ps_clvr_spmi2): power-controller@110 { - compatible = "apple,t6020-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + compatible = "apple,t6020-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate", "apple,pmgr-pwrstate"; reg = <0x110 4>; #power-domain-cells = <0>; #reset-cells = <0>; @@ -1022,7 +1022,7 @@ }; DIE_NODE(ps_clvr_spmi3): power-controller@118 { - compatible = "apple,t6020-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + compatible = "apple,t6020-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate", "apple,pmgr-pwrstate"; reg = <0x118 4>; #power-domain-cells = <0>; #reset-cells = <0>; @@ -1031,7 +1031,7 @@ }; DIE_NODE(ps_clvr_spmi4): power-controller@120 { - compatible = "apple,t6020-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + compatible = "apple,t6020-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate", "apple,pmgr-pwrstate"; reg = <0x120 4>; #power-domain-cells = <0>; #reset-cells = <0>; @@ -1040,7 +1040,7 @@ }; DIE_NODE(ps_ispsens0): power-controller@128 { - compatible = "apple,t6020-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + compatible = "apple,t6020-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate", "apple,pmgr-pwrstate"; reg = <0x128 4>; #power-domain-cells = <0>; #reset-cells = <0>; @@ -1048,7 +1048,7 @@ }; DIE_NODE(ps_ispsens1): power-controller@130 { - compatible = "apple,t6020-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + compatible = "apple,t6020-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate", "apple,pmgr-pwrstate"; reg = <0x130 4>; #power-domain-cells = <0>; #reset-cells = <0>; @@ -1056,7 +1056,7 @@ }; DIE_NODE(ps_ispsens2): power-controller@138 { - compatible = "apple,t6020-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + compatible = "apple,t6020-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate", "apple,pmgr-pwrstate"; reg = <0x138 4>; #power-domain-cells = <0>; #reset-cells = <0>; @@ -1064,7 +1064,7 @@ }; DIE_NODE(ps_ispsens3): power-controller@140 { - compatible = "apple,t6020-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + compatible = "apple,t6020-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate", "apple,pmgr-pwrstate"; reg = <0x140 4>; #power-domain-cells = <0>; #reset-cells = <0>; @@ -1072,7 +1072,7 @@ }; DIE_NODE(ps_afnc6_ioa): power-controller@148 { - compatible = "apple,t6020-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + compatible = "apple,t6020-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate", "apple,pmgr-pwrstate"; reg = <0x148 4>; #power-domain-cells = <0>; #reset-cells = <0>; @@ -1082,7 +1082,7 @@ }; DIE_NODE(ps_afnc6_ls): power-controller@150 { - compatible = "apple,t6020-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + compatible = "apple,t6020-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate", "apple,pmgr-pwrstate"; reg = <0x150 4>; #power-domain-cells = <0>; #reset-cells = <0>; @@ -1092,7 +1092,7 @@ }; DIE_NODE(ps_afnc6_lw0): power-controller@158 { - compatible = "apple,t6020-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + compatible = "apple,t6020-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate", "apple,pmgr-pwrstate"; reg = <0x158 4>; #power-domain-cells = <0>; #reset-cells = <0>; @@ -1102,7 +1102,7 @@ }; DIE_NODE(ps_afnc2_ioa): power-controller@160 { - compatible = "apple,t6020-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + compatible = "apple,t6020-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate", "apple,pmgr-pwrstate"; reg = <0x160 4>; #power-domain-cells = <0>; #reset-cells = <0>; @@ -1112,7 +1112,7 @@ }; DIE_NODE(ps_afnc2_ls): power-controller@168 { - compatible = "apple,t6020-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + compatible = "apple,t6020-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate", "apple,pmgr-pwrstate"; reg = <0x168 4>; #power-domain-cells = <0>; #reset-cells = <0>; @@ -1122,7 +1122,7 @@ }; DIE_NODE(ps_afnc2_lw0): power-controller@170 { - compatible = "apple,t6020-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + compatible = "apple,t6020-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate", "apple,pmgr-pwrstate"; reg = <0x170 4>; #power-domain-cells = <0>; #reset-cells = <0>; @@ -1132,7 +1132,7 @@ }; DIE_NODE(ps_afnc2_lw1): power-controller@178 { - compatible = "apple,t6020-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + compatible = "apple,t6020-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate", "apple,pmgr-pwrstate"; reg = <0x178 4>; #power-domain-cells = <0>; #reset-cells = <0>; @@ -1142,7 +1142,7 @@ }; DIE_NODE(ps_afnc3_ioa): power-controller@180 { - compatible = "apple,t6020-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + compatible = "apple,t6020-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate", "apple,pmgr-pwrstate"; reg = <0x180 4>; #power-domain-cells = <0>; #reset-cells = <0>; @@ -1152,7 +1152,7 @@ }; DIE_NODE(ps_afnc3_ls): power-controller@188 { - compatible = "apple,t6020-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + compatible = "apple,t6020-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate", "apple,pmgr-pwrstate"; reg = <0x188 4>; #power-domain-cells = <0>; #reset-cells = <0>; @@ -1162,7 +1162,7 @@ }; DIE_NODE(ps_afnc3_lw0): power-controller@190 { - compatible = "apple,t6020-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + compatible = "apple,t6020-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate", "apple,pmgr-pwrstate"; reg = <0x190 4>; #power-domain-cells = <0>; #reset-cells = <0>; @@ -1172,7 +1172,7 @@ }; DIE_NODE(ps_apcie_gp): power-controller@198 { - compatible = "apple,t6020-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + compatible = "apple,t6020-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate", "apple,pmgr-pwrstate"; reg = <0x198 4>; #power-domain-cells = <0>; #reset-cells = <0>; @@ -1181,7 +1181,7 @@ }; DIE_NODE(ps_apcie_st): power-controller@1a0 { - compatible = "apple,t6020-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + compatible = "apple,t6020-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate", "apple,pmgr-pwrstate"; reg = <0x1a0 4>; #power-domain-cells = <0>; #reset-cells = <0>; @@ -1190,7 +1190,7 @@ }; DIE_NODE(ps_ans2): power-controller@1a8 { - compatible = "apple,t6020-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + compatible = "apple,t6020-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate", "apple,pmgr-pwrstate"; reg = <0x1a8 4>; #power-domain-cells = <0>; #reset-cells = <0>; @@ -1199,7 +1199,7 @@ }; DIE_NODE(ps_disp0_sys): power-controller@1b0 { - compatible = "apple,t6020-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + compatible = "apple,t6020-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate", "apple,pmgr-pwrstate"; reg = <0x1b0 4>; #power-domain-cells = <0>; #reset-cells = <0>; @@ -1208,7 +1208,7 @@ }; DIE_NODE(ps_jpg): power-controller@1b8 { - compatible = "apple,t6020-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + compatible = "apple,t6020-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate", "apple,pmgr-pwrstate"; reg = <0x1b8 4>; #power-domain-cells = <0>; #reset-cells = <0>; @@ -1217,7 +1217,7 @@ }; DIE_NODE(ps_sio): power-controller@1c0 { - compatible = "apple,t6020-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + compatible = "apple,t6020-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate", "apple,pmgr-pwrstate"; reg = <0x1c0 4>; #power-domain-cells = <0>; #reset-cells = <0>; @@ -1226,7 +1226,7 @@ }; DIE_NODE(ps_isp_sys): power-controller@1c8 { - compatible = "apple,t6020-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + compatible = "apple,t6020-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate", "apple,pmgr-pwrstate"; reg = <0x1c8 4>; #power-domain-cells = <0>; #reset-cells = <0>; @@ -1236,7 +1236,7 @@ }; DIE_NODE(ps_disp0_fe): power-controller@1d0 { - compatible = "apple,t6020-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + compatible = "apple,t6020-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate", "apple,pmgr-pwrstate"; reg = <0x1d0 4>; #power-domain-cells = <0>; #reset-cells = <0>; @@ -1245,7 +1245,7 @@ }; DIE_NODE(ps_disp0_cpu0): power-controller@1d8 { - compatible = "apple,t6020-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + compatible = "apple,t6020-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate", "apple,pmgr-pwrstate"; reg = <0x1d8 4>; #power-domain-cells = <0>; #reset-cells = <0>; @@ -1255,7 +1255,7 @@ }; DIE_NODE(ps_sio_cpu): power-controller@1e0 { - compatible = "apple,t6020-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + compatible = "apple,t6020-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate", "apple,pmgr-pwrstate"; reg = <0x1e0 4>; #power-domain-cells = <0>; #reset-cells = <0>; @@ -1264,7 +1264,7 @@ }; DIE_NODE(ps_fpwm0): power-controller@1e8 { - compatible = "apple,t6020-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + compatible = "apple,t6020-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate", "apple,pmgr-pwrstate"; reg = <0x1e8 4>; #power-domain-cells = <0>; #reset-cells = <0>; @@ -1273,7 +1273,7 @@ }; DIE_NODE(ps_fpwm1): power-controller@1f0 { - compatible = "apple,t6020-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + compatible = "apple,t6020-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate", "apple,pmgr-pwrstate"; reg = <0x1f0 4>; #power-domain-cells = <0>; #reset-cells = <0>; @@ -1282,7 +1282,7 @@ }; DIE_NODE(ps_fpwm2): power-controller@1f8 { - compatible = "apple,t6020-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + compatible = "apple,t6020-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate", "apple,pmgr-pwrstate"; reg = <0x1f8 4>; #power-domain-cells = <0>; #reset-cells = <0>; @@ -1291,7 +1291,7 @@ }; DIE_NODE(ps_i2c0): power-controller@200 { - compatible = "apple,t6020-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + compatible = "apple,t6020-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate", "apple,pmgr-pwrstate"; reg = <0x200 4>; #power-domain-cells = <0>; #reset-cells = <0>; @@ -1300,7 +1300,7 @@ }; DIE_NODE(ps_i2c1): power-controller@208 { - compatible = "apple,t6020-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + compatible = "apple,t6020-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate", "apple,pmgr-pwrstate"; reg = <0x208 4>; #power-domain-cells = <0>; #reset-cells = <0>; @@ -1309,7 +1309,7 @@ }; DIE_NODE(ps_i2c2): power-controller@210 { - compatible = "apple,t6020-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + compatible = "apple,t6020-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate", "apple,pmgr-pwrstate"; reg = <0x210 4>; #power-domain-cells = <0>; #reset-cells = <0>; @@ -1318,7 +1318,7 @@ }; DIE_NODE(ps_i2c3): power-controller@218 { - compatible = "apple,t6020-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + compatible = "apple,t6020-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate", "apple,pmgr-pwrstate"; reg = <0x218 4>; #power-domain-cells = <0>; #reset-cells = <0>; @@ -1327,7 +1327,7 @@ }; DIE_NODE(ps_i2c4): power-controller@220 { - compatible = "apple,t6020-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + compatible = "apple,t6020-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate", "apple,pmgr-pwrstate"; reg = <0x220 4>; #power-domain-cells = <0>; #reset-cells = <0>; @@ -1336,7 +1336,7 @@ }; DIE_NODE(ps_i2c5): power-controller@228 { - compatible = "apple,t6020-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + compatible = "apple,t6020-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate", "apple,pmgr-pwrstate"; reg = <0x228 4>; #power-domain-cells = <0>; #reset-cells = <0>; @@ -1345,7 +1345,7 @@ }; DIE_NODE(ps_i2c6): power-controller@230 { - compatible = "apple,t6020-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + compatible = "apple,t6020-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate", "apple,pmgr-pwrstate"; reg = <0x230 4>; #power-domain-cells = <0>; #reset-cells = <0>; @@ -1354,7 +1354,7 @@ }; DIE_NODE(ps_i2c7): power-controller@238 { - compatible = "apple,t6020-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + compatible = "apple,t6020-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate", "apple,pmgr-pwrstate"; reg = <0x238 4>; #power-domain-cells = <0>; #reset-cells = <0>; @@ -1363,7 +1363,7 @@ }; DIE_NODE(ps_i2c8): power-controller@240 { - compatible = "apple,t6020-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + compatible = "apple,t6020-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate", "apple,pmgr-pwrstate"; reg = <0x240 4>; #power-domain-cells = <0>; #reset-cells = <0>; @@ -1372,7 +1372,7 @@ }; DIE_NODE(ps_spi_p): power-controller@248 { - compatible = "apple,t6020-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + compatible = "apple,t6020-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate", "apple,pmgr-pwrstate"; reg = <0x248 4>; #power-domain-cells = <0>; #reset-cells = <0>; @@ -1381,7 +1381,7 @@ }; DIE_NODE(ps_sio_spmi0): power-controller@250 { - compatible = "apple,t6020-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + compatible = "apple,t6020-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate", "apple,pmgr-pwrstate"; reg = <0x250 4>; #power-domain-cells = <0>; #reset-cells = <0>; @@ -1390,7 +1390,7 @@ }; DIE_NODE(ps_sio_spmi1): power-controller@258 { - compatible = "apple,t6020-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + compatible = "apple,t6020-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate", "apple,pmgr-pwrstate"; reg = <0x258 4>; #power-domain-cells = <0>; #reset-cells = <0>; @@ -1399,7 +1399,7 @@ }; DIE_NODE(ps_sio_spmi2): power-controller@260 { - compatible = "apple,t6020-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + compatible = "apple,t6020-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate", "apple,pmgr-pwrstate"; reg = <0x260 4>; #power-domain-cells = <0>; #reset-cells = <0>; @@ -1408,7 +1408,7 @@ }; DIE_NODE(ps_uart_p): power-controller@268 { - compatible = "apple,t6020-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + compatible = "apple,t6020-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate", "apple,pmgr-pwrstate"; reg = <0x268 4>; #power-domain-cells = <0>; #reset-cells = <0>; @@ -1417,7 +1417,7 @@ }; DIE_NODE(ps_audio_p): power-controller@270 { - compatible = "apple,t6020-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + compatible = "apple,t6020-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate", "apple,pmgr-pwrstate"; reg = <0x270 4>; #power-domain-cells = <0>; #reset-cells = <0>; @@ -1426,7 +1426,7 @@ }; DIE_NODE(ps_sio_adma): power-controller@278 { - compatible = "apple,t6020-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + compatible = "apple,t6020-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate", "apple,pmgr-pwrstate"; reg = <0x278 4>; #power-domain-cells = <0>; #reset-cells = <0>; @@ -1435,7 +1435,7 @@ }; DIE_NODE(ps_aes): power-controller@280 { - compatible = "apple,t6020-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + compatible = "apple,t6020-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate", "apple,pmgr-pwrstate"; reg = <0x280 4>; #power-domain-cells = <0>; #reset-cells = <0>; @@ -1445,7 +1445,7 @@ }; DIE_NODE(ps_dptx_phy_ps): power-controller@288 { - compatible = "apple,t6020-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + compatible = "apple,t6020-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate", "apple,pmgr-pwrstate"; reg = <0x288 4>; #power-domain-cells = <0>; #reset-cells = <0>; @@ -1455,7 +1455,7 @@ }; DIE_NODE(ps_spi0): power-controller@2d8 { - compatible = "apple,t6020-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + compatible = "apple,t6020-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate", "apple,pmgr-pwrstate"; reg = <0x2d8 4>; #power-domain-cells = <0>; #reset-cells = <0>; @@ -1464,7 +1464,7 @@ }; DIE_NODE(ps_spi1): power-controller@2e0 { - compatible = "apple,t6020-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + compatible = "apple,t6020-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate", "apple,pmgr-pwrstate"; reg = <0x2e0 4>; #power-domain-cells = <0>; #reset-cells = <0>; @@ -1473,7 +1473,7 @@ }; DIE_NODE(ps_spi2): power-controller@2e8 { - compatible = "apple,t6020-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + compatible = "apple,t6020-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate", "apple,pmgr-pwrstate"; reg = <0x2e8 4>; #power-domain-cells = <0>; #reset-cells = <0>; @@ -1482,7 +1482,7 @@ }; DIE_NODE(ps_spi3): power-controller@2f0 { - compatible = "apple,t6020-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + compatible = "apple,t6020-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate", "apple,pmgr-pwrstate"; reg = <0x2f0 4>; #power-domain-cells = <0>; #reset-cells = <0>; @@ -1491,7 +1491,7 @@ }; DIE_NODE(ps_spi4): power-controller@2f8 { - compatible = "apple,t6020-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + compatible = "apple,t6020-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate", "apple,pmgr-pwrstate"; reg = <0x2f8 4>; #power-domain-cells = <0>; #reset-cells = <0>; @@ -1500,7 +1500,7 @@ }; DIE_NODE(ps_spi5): power-controller@300 { - compatible = "apple,t6020-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + compatible = "apple,t6020-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate", "apple,pmgr-pwrstate"; reg = <0x300 4>; #power-domain-cells = <0>; #reset-cells = <0>; @@ -1509,7 +1509,7 @@ }; DIE_NODE(ps_uart_n): power-controller@308 { - compatible = "apple,t6020-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + compatible = "apple,t6020-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate", "apple,pmgr-pwrstate"; reg = <0x308 4>; #power-domain-cells = <0>; #reset-cells = <0>; @@ -1518,7 +1518,7 @@ }; DIE_NODE(ps_uart0): power-controller@310 { - compatible = "apple,t6020-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + compatible = "apple,t6020-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate", "apple,pmgr-pwrstate"; reg = <0x310 4>; #power-domain-cells = <0>; #reset-cells = <0>; @@ -1527,7 +1527,7 @@ }; DIE_NODE(ps_amcc1): power-controller@318 { - compatible = "apple,t6020-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + compatible = "apple,t6020-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate", "apple,pmgr-pwrstate"; reg = <0x318 4>; #power-domain-cells = <0>; #reset-cells = <0>; @@ -1536,7 +1536,7 @@ }; DIE_NODE(ps_amcc3): power-controller@320 { - compatible = "apple,t6020-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + compatible = "apple,t6020-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate", "apple,pmgr-pwrstate"; reg = <0x320 4>; #power-domain-cells = <0>; #reset-cells = <0>; @@ -1545,7 +1545,7 @@ }; DIE_NODE(ps_dcs_04): power-controller@328 { - compatible = "apple,t6020-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + compatible = "apple,t6020-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate", "apple,pmgr-pwrstate"; reg = <0x328 4>; #power-domain-cells = <0>; #reset-cells = <0>; @@ -1554,7 +1554,7 @@ }; DIE_NODE(ps_dcs_05): power-controller@330 { - compatible = "apple,t6020-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + compatible = "apple,t6020-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate", "apple,pmgr-pwrstate"; reg = <0x330 4>; #power-domain-cells = <0>; #reset-cells = <0>; @@ -1563,7 +1563,7 @@ }; DIE_NODE(ps_dcs_06): power-controller@338 { - compatible = "apple,t6020-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + compatible = "apple,t6020-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate", "apple,pmgr-pwrstate"; reg = <0x338 4>; #power-domain-cells = <0>; #reset-cells = <0>; @@ -1572,7 +1572,7 @@ }; DIE_NODE(ps_dcs_07): power-controller@340 { - compatible = "apple,t6020-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + compatible = "apple,t6020-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate", "apple,pmgr-pwrstate"; reg = <0x340 4>; #power-domain-cells = <0>; #reset-cells = <0>; @@ -1581,7 +1581,7 @@ }; DIE_NODE(ps_dcs_12): power-controller@348 { - compatible = "apple,t6020-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + compatible = "apple,t6020-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate", "apple,pmgr-pwrstate"; reg = <0x348 4>; #power-domain-cells = <0>; #reset-cells = <0>; @@ -1590,7 +1590,7 @@ }; DIE_NODE(ps_dcs_13): power-controller@350 { - compatible = "apple,t6020-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + compatible = "apple,t6020-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate", "apple,pmgr-pwrstate"; reg = <0x350 4>; #power-domain-cells = <0>; #reset-cells = <0>; @@ -1599,7 +1599,7 @@ }; DIE_NODE(ps_dcs_14): power-controller@358 { - compatible = "apple,t6020-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + compatible = "apple,t6020-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate", "apple,pmgr-pwrstate"; reg = <0x358 4>; #power-domain-cells = <0>; #reset-cells = <0>; @@ -1608,7 +1608,7 @@ }; DIE_NODE(ps_dcs_15): power-controller@360 { - compatible = "apple,t6020-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + compatible = "apple,t6020-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate", "apple,pmgr-pwrstate"; reg = <0x360 4>; #power-domain-cells = <0>; #reset-cells = <0>; @@ -1617,7 +1617,7 @@ }; DIE_NODE(ps_uart1): power-controller@368 { - compatible = "apple,t6020-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + compatible = "apple,t6020-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate", "apple,pmgr-pwrstate"; reg = <0x368 4>; #power-domain-cells = <0>; #reset-cells = <0>; @@ -1626,7 +1626,7 @@ }; DIE_NODE(ps_uart2): power-controller@370 { - compatible = "apple,t6020-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + compatible = "apple,t6020-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate", "apple,pmgr-pwrstate"; reg = <0x370 4>; #power-domain-cells = <0>; #reset-cells = <0>; @@ -1635,7 +1635,7 @@ }; DIE_NODE(ps_uart3): power-controller@378 { - compatible = "apple,t6020-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + compatible = "apple,t6020-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate", "apple,pmgr-pwrstate"; reg = <0x378 4>; #power-domain-cells = <0>; #reset-cells = <0>; @@ -1644,7 +1644,7 @@ }; DIE_NODE(ps_uart4): power-controller@380 { - compatible = "apple,t6020-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + compatible = "apple,t6020-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate", "apple,pmgr-pwrstate"; reg = <0x380 4>; #power-domain-cells = <0>; #reset-cells = <0>; @@ -1653,7 +1653,7 @@ }; DIE_NODE(ps_uart5): power-controller@388 { - compatible = "apple,t6020-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + compatible = "apple,t6020-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate", "apple,pmgr-pwrstate"; reg = <0x388 4>; #power-domain-cells = <0>; #reset-cells = <0>; @@ -1662,7 +1662,7 @@ }; DIE_NODE(ps_uart6): power-controller@390 { - compatible = "apple,t6020-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + compatible = "apple,t6020-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate", "apple,pmgr-pwrstate"; reg = <0x390 4>; #power-domain-cells = <0>; #reset-cells = <0>; @@ -1671,7 +1671,7 @@ }; DIE_NODE(ps_mca0): power-controller@398 { - compatible = "apple,t6020-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + compatible = "apple,t6020-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate", "apple,pmgr-pwrstate"; reg = <0x398 4>; #power-domain-cells = <0>; #reset-cells = <0>; @@ -1680,7 +1680,7 @@ }; DIE_NODE(ps_mca1): power-controller@3a0 { - compatible = "apple,t6020-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + compatible = "apple,t6020-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate", "apple,pmgr-pwrstate"; reg = <0x3a0 4>; #power-domain-cells = <0>; #reset-cells = <0>; @@ -1689,7 +1689,7 @@ }; DIE_NODE(ps_mca2): power-controller@3a8 { - compatible = "apple,t6020-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + compatible = "apple,t6020-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate", "apple,pmgr-pwrstate"; reg = <0x3a8 4>; #power-domain-cells = <0>; #reset-cells = <0>; @@ -1698,7 +1698,7 @@ }; DIE_NODE(ps_mca3): power-controller@3b0 { - compatible = "apple,t6020-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + compatible = "apple,t6020-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate", "apple,pmgr-pwrstate"; reg = <0x3b0 4>; #power-domain-cells = <0>; #reset-cells = <0>; @@ -1707,7 +1707,7 @@ }; DIE_NODE(ps_dpa0): power-controller@3b8 { - compatible = "apple,t6020-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + compatible = "apple,t6020-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate", "apple,pmgr-pwrstate"; reg = <0x3b8 4>; #power-domain-cells = <0>; #reset-cells = <0>; @@ -1716,7 +1716,7 @@ }; DIE_NODE(ps_dpa1): power-controller@3c0 { - compatible = "apple,t6020-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + compatible = "apple,t6020-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate", "apple,pmgr-pwrstate"; reg = <0x3c0 4>; #power-domain-cells = <0>; #reset-cells = <0>; @@ -1725,7 +1725,7 @@ }; DIE_NODE(ps_dpa2): power-controller@3c8 { - compatible = "apple,t6020-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + compatible = "apple,t6020-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate", "apple,pmgr-pwrstate"; reg = <0x3c8 4>; #power-domain-cells = <0>; #reset-cells = <0>; @@ -1734,7 +1734,7 @@ }; DIE_NODE(ps_dpa3): power-controller@3d0 { - compatible = "apple,t6020-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + compatible = "apple,t6020-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate", "apple,pmgr-pwrstate"; reg = <0x3d0 4>; #power-domain-cells = <0>; #reset-cells = <0>; @@ -1743,7 +1743,7 @@ }; DIE_NODE(ps_msr0): power-controller@3d8 { - compatible = "apple,t6020-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + compatible = "apple,t6020-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate", "apple,pmgr-pwrstate"; reg = <0x3d8 4>; #power-domain-cells = <0>; #reset-cells = <0>; @@ -1751,7 +1751,7 @@ }; DIE_NODE(ps_venc_sys): power-controller@3e0 { - compatible = "apple,t6020-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + compatible = "apple,t6020-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate", "apple,pmgr-pwrstate"; reg = <0x3e0 4>; #power-domain-cells = <0>; #reset-cells = <0>; @@ -1759,7 +1759,7 @@ }; DIE_NODE(ps_dpa4): power-controller@3e8 { - compatible = "apple,t6020-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + compatible = "apple,t6020-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate", "apple,pmgr-pwrstate"; reg = <0x3e8 4>; #power-domain-cells = <0>; #reset-cells = <0>; @@ -1768,7 +1768,7 @@ }; DIE_NODE(ps_msr0_ase_core): power-controller@3f0 { - compatible = "apple,t6020-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + compatible = "apple,t6020-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate", "apple,pmgr-pwrstate"; reg = <0x3f0 4>; #power-domain-cells = <0>; #reset-cells = <0>; @@ -1777,7 +1777,7 @@ }; DIE_NODE(ps_apcie_gpshr_sys): power-controller@3f8 { - compatible = "apple,t6020-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + compatible = "apple,t6020-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate", "apple,pmgr-pwrstate"; reg = <0x3f8 4>; #power-domain-cells = <0>; #reset-cells = <0>; @@ -1786,7 +1786,7 @@ }; DIE_NODE(ps_apcie_st_sys): power-controller@408 { - compatible = "apple,t6020-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + compatible = "apple,t6020-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate", "apple,pmgr-pwrstate"; reg = <0x408 4>; #power-domain-cells = <0>; #reset-cells = <0>; @@ -1795,7 +1795,7 @@ }; DIE_NODE(ps_apcie_st1_sys): power-controller@410 { - compatible = "apple,t6020-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + compatible = "apple,t6020-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate", "apple,pmgr-pwrstate"; reg = <0x410 4>; #power-domain-cells = <0>; #reset-cells = <0>; @@ -1804,7 +1804,7 @@ }; DIE_NODE(ps_apcie_gp_sys): power-controller@418 { - compatible = "apple,t6020-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + compatible = "apple,t6020-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate", "apple,pmgr-pwrstate"; reg = <0x418 4>; #power-domain-cells = <0>; #reset-cells = <0>; @@ -1814,7 +1814,7 @@ }; DIE_NODE(ps_apcie_ge_sys): power-controller@420 { - compatible = "apple,t6020-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + compatible = "apple,t6020-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate", "apple,pmgr-pwrstate"; reg = <0x420 4>; #power-domain-cells = <0>; #reset-cells = <0>; @@ -1823,7 +1823,7 @@ }; DIE_NODE(ps_apcie_phy_sw): power-controller@428 { - compatible = "apple,t6020-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + compatible = "apple,t6020-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate", "apple,pmgr-pwrstate"; reg = <0x428 4>; #power-domain-cells = <0>; #reset-cells = <0>; @@ -1832,7 +1832,7 @@ }; DIE_NODE(ps_sep): power-controller@c00 { - compatible = "apple,t6020-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + compatible = "apple,t6020-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate", "apple,pmgr-pwrstate"; reg = <0xc00 4>; #power-domain-cells = <0>; #reset-cells = <0>; @@ -1849,7 +1849,7 @@ * have to enable/disable everything in the per-model DTs. */ DIE_NODE(ps_isp_cpu): power-controller@4000 { - compatible = "apple,t6020-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + compatible = "apple,t6020-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate", "apple,pmgr-pwrstate"; reg = <0x4000 4>; #power-domain-cells = <0>; #reset-cells = <0>; @@ -1859,7 +1859,7 @@ }; DIE_NODE(ps_isp_fe): power-controller@4008 { - compatible = "apple,t6020-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + compatible = "apple,t6020-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate", "apple,pmgr-pwrstate"; reg = <0x4008 4>; #power-domain-cells = <0>; #reset-cells = <0>; @@ -1868,7 +1868,7 @@ }; DIE_NODE(ps_dprx): power-controller@4010 { - compatible = "apple,t6020-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + compatible = "apple,t6020-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate", "apple,pmgr-pwrstate"; reg = <0x4010 4>; #power-domain-cells = <0>; #reset-cells = <0>; @@ -1877,7 +1877,7 @@ }; DIE_NODE(ps_isp_vis): power-controller@4018 { - compatible = "apple,t6020-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + compatible = "apple,t6020-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate", "apple,pmgr-pwrstate"; reg = <0x4018 4>; #power-domain-cells = <0>; #reset-cells = <0>; @@ -1886,7 +1886,7 @@ }; DIE_NODE(ps_isp_be): power-controller@4020 { - compatible = "apple,t6020-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + compatible = "apple,t6020-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate", "apple,pmgr-pwrstate"; reg = <0x4020 4>; #power-domain-cells = <0>; #reset-cells = <0>; @@ -1895,7 +1895,7 @@ }; DIE_NODE(ps_isp_raw): power-controller@4028 { - compatible = "apple,t6020-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + compatible = "apple,t6020-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate", "apple,pmgr-pwrstate"; reg = <0x4028 4>; #power-domain-cells = <0>; #reset-cells = <0>; @@ -1904,7 +1904,7 @@ }; DIE_NODE(ps_isp_clr): power-controller@4030 { - compatible = "apple,t6020-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + compatible = "apple,t6020-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate", "apple,pmgr-pwrstate"; reg = <0x4030 4>; #power-domain-cells = <0>; #reset-cells = <0>; @@ -1913,7 +1913,7 @@ }; DIE_NODE(ps_venc_dma): power-controller@8000 { - compatible = "apple,t6020-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + compatible = "apple,t6020-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate", "apple,pmgr-pwrstate"; reg = <0x8000 4>; #power-domain-cells = <0>; #reset-cells = <0>; @@ -1922,7 +1922,7 @@ }; DIE_NODE(ps_venc_pipe4): power-controller@8008 { - compatible = "apple,t6020-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + compatible = "apple,t6020-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate", "apple,pmgr-pwrstate"; reg = <0x8008 4>; #power-domain-cells = <0>; #reset-cells = <0>; @@ -1931,7 +1931,7 @@ }; DIE_NODE(ps_venc_pipe5): power-controller@8010 { - compatible = "apple,t6020-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + compatible = "apple,t6020-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate", "apple,pmgr-pwrstate"; reg = <0x8010 4>; #power-domain-cells = <0>; #reset-cells = <0>; @@ -1940,7 +1940,7 @@ }; DIE_NODE(ps_venc_me0): power-controller@8018 { - compatible = "apple,t6020-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + compatible = "apple,t6020-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate", "apple,pmgr-pwrstate"; reg = <0x8018 4>; #power-domain-cells = <0>; #reset-cells = <0>; @@ -1949,7 +1949,7 @@ }; DIE_NODE(ps_venc_me1): power-controller@8020 { - compatible = "apple,t6020-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + compatible = "apple,t6020-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate", "apple,pmgr-pwrstate"; reg = <0x8020 4>; #power-domain-cells = <0>; #reset-cells = <0>; @@ -1958,7 +1958,7 @@ }; DIE_NODE(ps_prores): power-controller@c000 { - compatible = "apple,t6020-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + compatible = "apple,t6020-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate", "apple,pmgr-pwrstate"; reg = <0xc000 4>; #power-domain-cells = <0>; #reset-cells = <0>; @@ -1969,7 +1969,7 @@ &DIE_NODE(pmgr_mini) { DIE_NODE(ps_debug): power-controller@58 { - compatible = "apple,t6020-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + compatible = "apple,t6020-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate", "apple,pmgr-pwrstate"; reg = <0x58 4>; #power-domain-cells = <0>; #reset-cells = <0>; @@ -1978,7 +1978,7 @@ }; DIE_NODE(ps_nub_spmi0): power-controller@60 { - compatible = "apple,t6020-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + compatible = "apple,t6020-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate", "apple,pmgr-pwrstate"; reg = <0x60 4>; #power-domain-cells = <0>; #reset-cells = <0>; @@ -1987,7 +1987,7 @@ }; DIE_NODE(ps_nub_spmi1): power-controller@68 { - compatible = "apple,t6020-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + compatible = "apple,t6020-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate", "apple,pmgr-pwrstate"; reg = <0x68 4>; #power-domain-cells = <0>; #reset-cells = <0>; @@ -1996,7 +1996,7 @@ }; DIE_NODE(ps_nub_aon): power-controller@70 { - compatible = "apple,t6020-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + compatible = "apple,t6020-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate", "apple,pmgr-pwrstate"; reg = <0x70 4>; #power-domain-cells = <0>; #reset-cells = <0>; @@ -2005,7 +2005,7 @@ }; DIE_NODE(ps_msg): power-controller@78 { - compatible = "apple,t6020-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + compatible = "apple,t6020-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate", "apple,pmgr-pwrstate"; reg = <0x78 4>; #power-domain-cells = <0>; #reset-cells = <0>; @@ -2014,7 +2014,7 @@ }; DIE_NODE(ps_nub_gpio): power-controller@80 { - compatible = "apple,t6020-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + compatible = "apple,t6020-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate", "apple,pmgr-pwrstate"; reg = <0x80 4>; #power-domain-cells = <0>; #reset-cells = <0>; @@ -2023,7 +2023,7 @@ }; DIE_NODE(ps_nub_fabric): power-controller@88 { - compatible = "apple,t6020-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + compatible = "apple,t6020-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate", "apple,pmgr-pwrstate"; reg = <0x88 4>; #power-domain-cells = <0>; #reset-cells = <0>; @@ -2032,7 +2032,7 @@ }; DIE_NODE(ps_atc0_usb_aon): power-controller@90 { - compatible = "apple,t6020-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + compatible = "apple,t6020-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate", "apple,pmgr-pwrstate"; reg = <0x90 4>; #power-domain-cells = <0>; #reset-cells = <0>; @@ -2041,7 +2041,7 @@ }; DIE_NODE(ps_atc1_usb_aon): power-controller@98 { - compatible = "apple,t6020-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + compatible = "apple,t6020-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate", "apple,pmgr-pwrstate"; reg = <0x98 4>; #power-domain-cells = <0>; #reset-cells = <0>; @@ -2050,7 +2050,7 @@ }; DIE_NODE(ps_atc2_usb_aon): power-controller@a0 { - compatible = "apple,t6020-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + compatible = "apple,t6020-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate", "apple,pmgr-pwrstate"; reg = <0xa0 4>; #power-domain-cells = <0>; #reset-cells = <0>; @@ -2059,7 +2059,7 @@ }; DIE_NODE(ps_atc3_usb_aon): power-controller@a8 { - compatible = "apple,t6020-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + compatible = "apple,t6020-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate", "apple,pmgr-pwrstate"; reg = <0xa8 4>; #power-domain-cells = <0>; #reset-cells = <0>; @@ -2068,7 +2068,7 @@ }; DIE_NODE(ps_mtp_fabric): power-controller@b0 { - compatible = "apple,t6020-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + compatible = "apple,t6020-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate", "apple,pmgr-pwrstate"; reg = <0xb0 4>; #power-domain-cells = <0>; #reset-cells = <0>; @@ -2079,7 +2079,7 @@ }; DIE_NODE(ps_nub_sram): power-controller@b8 { - compatible = "apple,t6020-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + compatible = "apple,t6020-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate", "apple,pmgr-pwrstate"; reg = <0xb8 4>; #power-domain-cells = <0>; #reset-cells = <0>; @@ -2088,7 +2088,7 @@ }; DIE_NODE(ps_debug_switch): power-controller@c0 { - compatible = "apple,t6020-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + compatible = "apple,t6020-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate", "apple,pmgr-pwrstate"; reg = <0xc0 4>; #power-domain-cells = <0>; #reset-cells = <0>; @@ -2097,7 +2097,7 @@ }; DIE_NODE(ps_atc0_usb): power-controller@c8 { - compatible = "apple,t6020-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + compatible = "apple,t6020-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate", "apple,pmgr-pwrstate"; reg = <0xc8 4>; #power-domain-cells = <0>; #reset-cells = <0>; @@ -2106,7 +2106,7 @@ }; DIE_NODE(ps_atc1_usb): power-controller@d0 { - compatible = "apple,t6020-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + compatible = "apple,t6020-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate", "apple,pmgr-pwrstate"; reg = <0xd0 4>; #power-domain-cells = <0>; #reset-cells = <0>; @@ -2115,7 +2115,7 @@ }; DIE_NODE(ps_atc2_usb): power-controller@d8 { - compatible = "apple,t6020-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + compatible = "apple,t6020-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate", "apple,pmgr-pwrstate"; reg = <0xd8 4>; #power-domain-cells = <0>; #reset-cells = <0>; @@ -2124,7 +2124,7 @@ }; DIE_NODE(ps_atc3_usb): power-controller@e0 { - compatible = "apple,t6020-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + compatible = "apple,t6020-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate", "apple,pmgr-pwrstate"; reg = <0xe0 4>; #power-domain-cells = <0>; #reset-cells = <0>; @@ -2135,7 +2135,7 @@ #if 0 /* MTP stuff is self-managed */ DIE_NODE(ps_mtp_gpio): power-controller@e8 { - compatible = "apple,t6020-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + compatible = "apple,t6020-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate", "apple,pmgr-pwrstate"; reg = <0xe8 4>; #power-domain-cells = <0>; #reset-cells = <0>; @@ -2145,7 +2145,7 @@ }; DIE_NODE(ps_mtp_base): power-controller@f0 { - compatible = "apple,t6020-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + compatible = "apple,t6020-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate", "apple,pmgr-pwrstate"; reg = <0xf0 4>; #power-domain-cells = <0>; #reset-cells = <0>; @@ -2155,7 +2155,7 @@ }; DIE_NODE(ps_mtp_periph): power-controller@f8 { - compatible = "apple,t6020-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + compatible = "apple,t6020-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate", "apple,pmgr-pwrstate"; reg = <0xf8 4>; #power-domain-cells = <0>; #reset-cells = <0>; @@ -2165,7 +2165,7 @@ }; DIE_NODE(ps_mtp_spi0): power-controller@100 { - compatible = "apple,t6020-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + compatible = "apple,t6020-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate", "apple,pmgr-pwrstate"; reg = <0x100 4>; #power-domain-cells = <0>; #reset-cells = <0>; @@ -2175,7 +2175,7 @@ }; DIE_NODE(ps_mtp_i2cm0): power-controller@108 { - compatible = "apple,t6020-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + compatible = "apple,t6020-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate", "apple,pmgr-pwrstate"; reg = <0x108 4>; #power-domain-cells = <0>; #reset-cells = <0>; @@ -2185,7 +2185,7 @@ }; DIE_NODE(ps_mtp_uart0): power-controller@110 { - compatible = "apple,t6020-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + compatible = "apple,t6020-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate", "apple,pmgr-pwrstate"; reg = <0x110 4>; #power-domain-cells = <0>; #reset-cells = <0>; @@ -2195,7 +2195,7 @@ }; DIE_NODE(ps_mtp_cpu): power-controller@118 { - compatible = "apple,t6020-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + compatible = "apple,t6020-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate", "apple,pmgr-pwrstate"; reg = <0x118 4>; #power-domain-cells = <0>; #reset-cells = <0>; @@ -2205,7 +2205,7 @@ }; DIE_NODE(ps_mtp_scm_fabric): power-controller@120 { - compatible = "apple,t6020-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + compatible = "apple,t6020-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate", "apple,pmgr-pwrstate"; reg = <0x120 4>; #power-domain-cells = <0>; #reset-cells = <0>; @@ -2215,7 +2215,7 @@ }; DIE_NODE(ps_mtp_sram): power-controller@128 { - compatible = "apple,t6020-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + compatible = "apple,t6020-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate", "apple,pmgr-pwrstate"; reg = <0x128 4>; #power-domain-cells = <0>; #reset-cells = <0>; @@ -2225,7 +2225,7 @@ }; DIE_NODE(ps_mtp_dma): power-controller@130 { - compatible = "apple,t6020-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + compatible = "apple,t6020-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate", "apple,pmgr-pwrstate"; reg = <0x130 4>; #power-domain-cells = <0>; #reset-cells = <0>; @@ -2238,7 +2238,7 @@ &DIE_NODE(pmgr_gfx) { DIE_NODE(ps_gpx): power-controller@0 { - compatible = "apple,t6020-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + compatible = "apple,t6020-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate", "apple,pmgr-pwrstate"; reg = <0x0 4>; #power-domain-cells = <0>; #reset-cells = <0>; @@ -2248,7 +2248,7 @@ }; DIE_NODE(ps_afr): power-controller@100 { - compatible = "apple,t6020-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + compatible = "apple,t6020-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate", "apple,pmgr-pwrstate"; reg = <0x100 4>; #power-domain-cells = <0>; #reset-cells = <0>; @@ -2258,7 +2258,7 @@ }; DIE_NODE(ps_gfx): power-controller@108 { - compatible = "apple,t6020-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + compatible = "apple,t6020-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate", "apple,pmgr-pwrstate"; reg = <0x108 4>; #power-domain-cells = <0>; #reset-cells = <0>; From 54bf776b7a00420c71b7a9cc50208fc53b0db1da Mon Sep 17 00:00:00 2001 From: Hector Martin Date: Mon, 10 Apr 2023 18:15:33 +0900 Subject: [PATCH 2139/3902] arm64: dts: apple: Add MTP nodes to t6020x Signed-off-by: Hector Martin --- arch/arm64/boot/dts/apple/t6020-j414s.dts | 4 + arch/arm64/boot/dts/apple/t6020-j416s.dts | 4 + arch/arm64/boot/dts/apple/t6021-j414c.dts | 4 + arch/arm64/boot/dts/apple/t6021-j416c.dts | 4 + arch/arm64/boot/dts/apple/t602x-die0.dtsi | 76 +++++++++++++++++++ .../arm64/boot/dts/apple/t602x-j414-j416.dtsi | 42 ++++++++++ arch/arm64/boot/dts/apple/t8112.dtsi | 1 + 7 files changed, 135 insertions(+) diff --git a/arch/arm64/boot/dts/apple/t6020-j414s.dts b/arch/arm64/boot/dts/apple/t6020-j414s.dts index 18cc67a3076def..5dd97df71efc4b 100644 --- a/arch/arm64/boot/dts/apple/t6020-j414s.dts +++ b/arch/arm64/boot/dts/apple/t6020-j414s.dts @@ -36,3 +36,7 @@ compatible = "apple,j414-macaudio", "apple,j314-macaudio", "apple,macaudio"; model = "MacBook Pro J414"; }; + +&mtp_mt { + firmware-name = "apple/tpmtfw-j414s.bin"; +}; diff --git a/arch/arm64/boot/dts/apple/t6020-j416s.dts b/arch/arm64/boot/dts/apple/t6020-j416s.dts index b9e0973ba37c30..56ddf7c61de634 100644 --- a/arch/arm64/boot/dts/apple/t6020-j416s.dts +++ b/arch/arm64/boot/dts/apple/t6020-j416s.dts @@ -36,3 +36,7 @@ compatible = "apple,j416-macaudio", "apple,j316-macaudio", "apple,macaudio"; model = "MacBook Pro J416"; }; + +&mtp_mt { + firmware-name = "apple/tpmtfw-j416s.bin"; +}; diff --git a/arch/arm64/boot/dts/apple/t6021-j414c.dts b/arch/arm64/boot/dts/apple/t6021-j414c.dts index b173caf0df0fce..6905c7d39db0ce 100644 --- a/arch/arm64/boot/dts/apple/t6021-j414c.dts +++ b/arch/arm64/boot/dts/apple/t6021-j414c.dts @@ -36,3 +36,7 @@ compatible = "apple,j414-macaudio", "apple,j314-macaudio", "apple,macaudio"; model = "MacBook Pro J414"; }; + +&mtp_mt { + firmware-name = "apple/tpmtfw-j414c.bin"; +}; diff --git a/arch/arm64/boot/dts/apple/t6021-j416c.dts b/arch/arm64/boot/dts/apple/t6021-j416c.dts index 2fbb00b364c72b..786ac2393d7535 100644 --- a/arch/arm64/boot/dts/apple/t6021-j416c.dts +++ b/arch/arm64/boot/dts/apple/t6021-j416c.dts @@ -56,3 +56,7 @@ compatible = "apple,j416-macaudio", "apple,j316-macaudio", "apple,macaudio"; model = "MacBook Pro J416"; }; + +&mtp_mt { + firmware-name = "apple/tpmtfw-j416c.bin"; +}; diff --git a/arch/arm64/boot/dts/apple/t602x-die0.dtsi b/arch/arm64/boot/dts/apple/t602x-die0.dtsi index dd3de629527e0d..1318921733a817 100644 --- a/arch/arm64/boot/dts/apple/t602x-die0.dtsi +++ b/arch/arm64/boot/dts/apple/t602x-die0.dtsi @@ -156,6 +156,82 @@ ; }; + mtp: mtp@2a9400000 { + compatible = "apple,t6020-mtp", "apple,t6020-rtk-helper-asc4", "apple,mtp", "apple,rtk-helper-asc4"; + reg = <0x2 0xa9400000 0x0 0x4000>, + <0x2 0xa9c00000 0x0 0x100000>; + reg-names = "asc", "sram"; + mboxes = <&mtp_mbox>; + iommus = <&mtp_dart 1>; + #helper-cells = <0>; + + status = "disabled"; + }; + + mtp_mbox: mbox@2a9408000 { + compatible = "apple,t6020-asc-mailbox", "apple,asc-mailbox-v4"; + reg = <0x2 0xa9408000 0x0 0x4000>; + interrupt-parent = <&aic>; + interrupts = , + , + , + ; + interrupt-names = "send-empty", "send-not-empty", + "recv-empty", "recv-not-empty"; + #mbox-cells = <0>; + + status = "disabled"; + }; + + mtp_dart: iommu@2a9808000 { + compatible = "apple,t6020-dart", "apple,t8110-dart"; + reg = <0x2 0xa9808000 0x0 0x4000>; + interrupt-parent = <&aic>; + interrupts = ; + #iommu-cells = <1>; + + apple,dma-range = <0x100 0x0 0x1 0x0>; + + status = "disabled"; + }; + + mtp_dockchannel: fifo@2a9b14000 { + compatible = "apple,t6020-dockchannel", "apple,dockchannel"; + reg = <0x2 0xa9b14000 0x0 0x4000>; + reg-names = "irq"; + interrupt-parent = <&aic>; + interrupts = ; + + ranges = <0 0x2 0xa9b28000 0x20000>; + nonposted-mmio; + #address-cells = <1>; + #size-cells = <1>; + + interrupt-controller; + #interrupt-cells = <2>; + + status = "disabled"; + + mtp_hid: input@8000 { + compatible = "apple,dockchannel-hid"; + reg = <0x8000 0x4000>, + <0xc000 0x4000>, + <0x0000 0x4000>, + <0x4000 0x4000>; + reg-names = "config", "data", + "rmt-config", "rmt-data"; + iommus = <&mtp_dart 1>; + interrupt-parent = <&mtp_dockchannel>; + interrupts = <2 IRQ_TYPE_LEVEL_HIGH>, + <3 IRQ_TYPE_LEVEL_HIGH>; + interrupt-names = "tx", "rx"; + + apple,fifo-size = <0x800>; + apple,helper-cpu = <&mtp>; + }; + + }; + disp0_dart: iommu@389304000 { compatible = "apple,t6020-dart", "apple,t8110-dart"; reg = <0x3 0x89304000 0x0 0x4000>; diff --git a/arch/arm64/boot/dts/apple/t602x-j414-j416.dtsi b/arch/arm64/boot/dts/apple/t602x-j414-j416.dtsi index b0fa34282681de..5df64390ef6812 100644 --- a/arch/arm64/boot/dts/apple/t602x-j414-j416.dtsi +++ b/arch/arm64/boot/dts/apple/t602x-j414-j416.dtsi @@ -89,3 +89,45 @@ &port01 { pwren-gpios = <&smc_gpio 22 GPIO_ACTIVE_HIGH>; }; + +&ps_mtp_fabric { + status = "okay"; +}; + +&mtp { + status = "okay"; +}; + +&mtp_mbox { + status = "okay"; +}; + +&mtp_dart { + status = "okay"; +}; + +&mtp_dockchannel { + status = "okay"; +}; + +&mtp_hid { + apple,afe-reset-gpios = <&smc_gpio 25 GPIO_ACTIVE_LOW>; + apple,stm-reset-gpios = <&smc_gpio 26 GPIO_ACTIVE_LOW>; + + mtp_mt: multi-touch { + }; + + keyboard: keyboard { + hid-country-code = <0>; + apple,keyboard-layout-id = <0>; + }; + + stm { + }; + + actuator { + }; + + tp_accel { + }; +}; diff --git a/arch/arm64/boot/dts/apple/t8112.dtsi b/arch/arm64/boot/dts/apple/t8112.dtsi index da75e865d863cd..9ee9eeb07308a5 100644 --- a/arch/arm64/boot/dts/apple/t8112.dtsi +++ b/arch/arm64/boot/dts/apple/t8112.dtsi @@ -1294,6 +1294,7 @@ interrupts = ; ranges = <0 0x2 0x4eb28000 0x20000>; + nonposted-mmio; #address-cells = <1>; #size-cells = <1>; From 28cb80ce713e83ce6bf902171b6108412f830ee6 Mon Sep 17 00:00:00 2001 From: Hector Martin Date: Thu, 12 Oct 2023 23:20:39 +0900 Subject: [PATCH 2140/3902] arm64: dts: apple: t602x: Mark MCA power states as externally-clocked Signed-off-by: Hector Martin --- arch/arm64/boot/dts/apple/t602x-pmgr.dtsi | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/arch/arm64/boot/dts/apple/t602x-pmgr.dtsi b/arch/arm64/boot/dts/apple/t602x-pmgr.dtsi index f5ebd5bc19b33a..8633a939592a86 100644 --- a/arch/arm64/boot/dts/apple/t602x-pmgr.dtsi +++ b/arch/arm64/boot/dts/apple/t602x-pmgr.dtsi @@ -1677,6 +1677,7 @@ #reset-cells = <0>; label = DIE_LABEL(mca0); power-domains = <&DIE_NODE(ps_audio_p)>, <&DIE_NODE(ps_sio_adma)>; + apple,externally-clocked; }; DIE_NODE(ps_mca1): power-controller@3a0 { @@ -1686,6 +1687,7 @@ #reset-cells = <0>; label = DIE_LABEL(mca1); power-domains = <&DIE_NODE(ps_audio_p)>, <&DIE_NODE(ps_sio_adma)>; + apple,externally-clocked; }; DIE_NODE(ps_mca2): power-controller@3a8 { @@ -1695,6 +1697,7 @@ #reset-cells = <0>; label = DIE_LABEL(mca2); power-domains = <&DIE_NODE(ps_audio_p)>, <&DIE_NODE(ps_sio_adma)>; + apple,externally-clocked; }; DIE_NODE(ps_mca3): power-controller@3b0 { @@ -1704,6 +1707,7 @@ #reset-cells = <0>; label = DIE_LABEL(mca3); power-domains = <&DIE_NODE(ps_audio_p)>, <&DIE_NODE(ps_sio_adma)>; + apple,externally-clocked; }; DIE_NODE(ps_dpa0): power-controller@3b8 { From 63d7e4a850e5ec05f43bf011ac988091c16cd956 Mon Sep 17 00:00:00 2001 From: James Calligeros Date: Sun, 29 Oct 2023 08:55:40 +1000 Subject: [PATCH 2141/3902] arm64: dts: apple: t602x: describe shared SDZ GPIO for tas2764 machines with the tas2764 amp codec share a GPIO line for asserting/deasserting the SDZ pin on the chips. describe this as a regulator to facilitate chip reset on suspend/resume Signed-off-by: James Calligeros --- arch/arm64/boot/dts/apple/t602x-j414-j416.dtsi | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/arch/arm64/boot/dts/apple/t602x-j414-j416.dtsi b/arch/arm64/boot/dts/apple/t602x-j414-j416.dtsi index 5df64390ef6812..37d1d523d1cdec 100644 --- a/arch/arm64/boot/dts/apple/t602x-j414-j416.dtsi +++ b/arch/arm64/boot/dts/apple/t602x-j414-j416.dtsi @@ -43,33 +43,32 @@ interrupts = <44 IRQ_TYPE_LEVEL_LOW>; }; +/* Redefine GPIO for SDZ */ +&speaker_sdz { + gpios = <&pinctrl_ap 57 GPIO_ACTIVE_HIGH>; +}; + &speaker_left_tweet { - shutdown-gpios = <&pinctrl_ap 57 GPIO_ACTIVE_HIGH>; interrupts-extended = <&pinctrl_ap 58 IRQ_TYPE_LEVEL_LOW>; }; &speaker_left_woof1 { - shutdown-gpios = <&pinctrl_ap 57 GPIO_ACTIVE_HIGH>; interrupts-extended = <&pinctrl_ap 58 IRQ_TYPE_LEVEL_LOW>; }; &speaker_left_woof2 { - shutdown-gpios = <&pinctrl_ap 57 GPIO_ACTIVE_HIGH>; interrupts-extended = <&pinctrl_ap 58 IRQ_TYPE_LEVEL_LOW>; }; &speaker_right_tweet { - shutdown-gpios = <&pinctrl_ap 57 GPIO_ACTIVE_HIGH>; interrupts-extended = <&pinctrl_ap 58 IRQ_TYPE_LEVEL_LOW>; }; &speaker_right_woof1 { - shutdown-gpios = <&pinctrl_ap 57 GPIO_ACTIVE_HIGH>; interrupts-extended = <&pinctrl_ap 58 IRQ_TYPE_LEVEL_LOW>; }; &speaker_right_woof2 { - shutdown-gpios = <&pinctrl_ap 57 GPIO_ACTIVE_HIGH>; interrupts-extended = <&pinctrl_ap 58 IRQ_TYPE_LEVEL_LOW>; }; From a80ed46db1a709882a4b673885078e28838d96d7 Mon Sep 17 00:00:00 2001 From: Hector Martin Date: Fri, 13 Oct 2023 01:37:23 +0900 Subject: [PATCH 2142/3902] arm64: dts: apple: t602x-j180d: Add I/VMON slots to amps Signed-off-by: Hector Martin --- arch/arm64/boot/dts/apple/t6022-j180d.dts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/arch/arm64/boot/dts/apple/t6022-j180d.dts b/arch/arm64/boot/dts/apple/t6022-j180d.dts index 59e5825a0368fa..c0674aad5c49c9 100644 --- a/arch/arm64/boot/dts/apple/t6022-j180d.dts +++ b/arch/arm64/boot/dts/apple/t6022-j180d.dts @@ -542,6 +542,8 @@ #sound-dai-cells = <0>; sound-name-prefix = "Tweeter"; interrupts-extended = <&pinctrl_ap 58 IRQ_TYPE_LEVEL_LOW>; + ti,imon-slot-no = <4>; + ti,vmon-slot-no = <6>; }; speaker_woofer: codec@39 { @@ -551,6 +553,8 @@ #sound-dai-cells = <0>; sound-name-prefix = "Woofer"; interrupts-extended = <&pinctrl_ap 58 IRQ_TYPE_LEVEL_LOW>; + ti,imon-slot-no = <0>; + ti,vmon-slot-no = <2>; }; }; From 16cf29fd35df599ef4e03de481562d2a00ebe6fa Mon Sep 17 00:00:00 2001 From: Hector Martin Date: Tue, 11 Apr 2023 02:34:01 +0900 Subject: [PATCH 2143/3902] arm64: dts: apple: t602x: Add identity dma-ranges mapping Without this, the OF core ends up limiting all DMA masks to the default 32-bit, since that runs before drivers set up the proper DMA mask. Skipping the highest page because it is impossible to express a full 64-bit range in the DT. Signed-off-by: Hector Martin --- arch/arm64/boot/dts/apple/t6021.dtsi | 2 ++ 1 file changed, 2 insertions(+) diff --git a/arch/arm64/boot/dts/apple/t6021.dtsi b/arch/arm64/boot/dts/apple/t6021.dtsi index 1205a43da383f7..bb0e66851f1b59 100644 --- a/arch/arm64/boot/dts/apple/t6021.dtsi +++ b/arch/arm64/boot/dts/apple/t6021.dtsi @@ -35,6 +35,8 @@ ranges; nonposted-mmio; + /* Required to get >32-bit DMA via DARTs */ + dma-ranges = <0 0 0 0 0xffffffff 0xffffc000>; // filled via templated includes at the end of the file }; From 9865162fefbbfbae4af0080634d3cd85595c7665 Mon Sep 17 00:00:00 2001 From: Hector Martin Date: Tue, 18 Apr 2023 05:05:57 +0900 Subject: [PATCH 2144/3902] arm64: dts: apple: Add pmgr-misc nodes to t60xx --- arch/arm64/boot/dts/apple/t600x-die0.dtsi | 10 ++++++++++ arch/arm64/boot/dts/apple/t602x-die0.dtsi | 9 +++++++++ 2 files changed, 19 insertions(+) diff --git a/arch/arm64/boot/dts/apple/t600x-die0.dtsi b/arch/arm64/boot/dts/apple/t600x-die0.dtsi index 29012bcc003d6a..11ebfaaa2dc945 100644 --- a/arch/arm64/boot/dts/apple/t600x-die0.dtsi +++ b/arch/arm64/boot/dts/apple/t600x-die0.dtsi @@ -24,6 +24,16 @@ power-domains = <&ps_aic>; }; + pmgr_misc: power-management@28e20c000 { + compatible = "apple,t6000-pmgr-misc"; + #address-cells = <1>; + #size-cells = <1>; + reg = <0x2 0x8e20c000 0 0x400>, + <0x2 0x8e20c800 0 0x400>; + reg-names = "fabric-ps", "dcs-ps"; + apple,dcs-min-ps = <7>; + }; + pmgr_dcp: power-management@28e3d0000 { reg = <0x2 0x8e3d0000 0x0 0x4000>; reg-names = "dcp-fw-pmgr"; diff --git a/arch/arm64/boot/dts/apple/t602x-die0.dtsi b/arch/arm64/boot/dts/apple/t602x-die0.dtsi index 1318921733a817..0662febce379e0 100644 --- a/arch/arm64/boot/dts/apple/t602x-die0.dtsi +++ b/arch/arm64/boot/dts/apple/t602x-die0.dtsi @@ -23,6 +23,15 @@ power-domains = <&ps_aic>; }; + pmgr_misc: power-management@28e20c000 { + compatible = "apple,t6020-pmgr-misc", "apple,t6000-pmgr-misc"; + #address-cells = <1>; + #size-cells = <1>; + reg = <0x2 0x8e20c000 0 0x400>, + <0x2 0x8e20c400 0 0x400>; + reg-names = "fabric-ps", "dcs-ps"; + }; + pmgr_dcp: power-management@28e3d0000 { reg = <0x2 0x8e3d0000 0x0 0x4000>; reg-names = "dcp-fw-pmgr"; From d9214603700393d3abbe8917f897328d5aff7aa9 Mon Sep 17 00:00:00 2001 From: Hector Martin Date: Wed, 26 Apr 2023 02:17:26 +0900 Subject: [PATCH 2145/3902] arm64: dts: apple: Make ps_msg always-on Apple has it that way, and it might be important. Let's not risk it. Signed-off-by: Hector Martin --- arch/arm64/boot/dts/apple/t600x-pmgr.dtsi | 1 + arch/arm64/boot/dts/apple/t8103-pmgr.dtsi | 1 + arch/arm64/boot/dts/apple/t8112-pmgr.dtsi | 1 + 3 files changed, 3 insertions(+) diff --git a/arch/arm64/boot/dts/apple/t600x-pmgr.dtsi b/arch/arm64/boot/dts/apple/t600x-pmgr.dtsi index 3315b392b21d72..84d5e126e2320e 100644 --- a/arch/arm64/boot/dts/apple/t600x-pmgr.dtsi +++ b/arch/arm64/boot/dts/apple/t600x-pmgr.dtsi @@ -1877,6 +1877,7 @@ #power-domain-cells = <0>; #reset-cells = <0>; label = DIE_LABEL(msg); + apple,always-on; /* Core AON device? */ }; DIE_NODE(ps_nub_gpio): power-controller@80 { diff --git a/arch/arm64/boot/dts/apple/t8103-pmgr.dtsi b/arch/arm64/boot/dts/apple/t8103-pmgr.dtsi index f0ae11bf6ce688..a97d64b665730f 100644 --- a/arch/arm64/boot/dts/apple/t8103-pmgr.dtsi +++ b/arch/arm64/boot/dts/apple/t8103-pmgr.dtsi @@ -1101,6 +1101,7 @@ #power-domain-cells = <0>; #reset-cells = <0>; label = "msg"; + apple,always-on; /* Core AON device? */ }; ps_atc0_usb_aon: power-controller@88 { diff --git a/arch/arm64/boot/dts/apple/t8112-pmgr.dtsi b/arch/arm64/boot/dts/apple/t8112-pmgr.dtsi index 276f1ab35f06a3..7ff5052d1cdcbc 100644 --- a/arch/arm64/boot/dts/apple/t8112-pmgr.dtsi +++ b/arch/arm64/boot/dts/apple/t8112-pmgr.dtsi @@ -1067,6 +1067,7 @@ #power-domain-cells = <0>; #reset-cells = <0>; label = "msg"; + apple,always-on; /* Core AON device? */ }; ps_nub_gpio: power-controller@80 { From a2e7bb56270a1a3180a9be5830589b752ff4c36c Mon Sep 17 00:00:00 2001 From: Hector Martin Date: Mon, 7 Aug 2023 19:53:50 +0900 Subject: [PATCH 2146/3902] arm64: dts: apple: t6022: Add APCIE-GE nodes arm64: dts: apple: t6022-j180d: Add node for built-in PCIe devices Currently only the two ethernet controllers and the SATA-AHCI are detected. The USB controller (internal USB-A port and USB-A ports on the I/O board) are missing code to toggle the reset gpio pin. The Broadcom Wlan/BT device needs in addition the SMC power enable GPIO. The "bluetooth0" and "wifi0" aliases can not be added since the ADT misses calibration data for Wlan and BT. arm64: dts: apple: Move PCIe-GE nodes intro their own file These are only used on the Mac Pro (M2 Ultra, 2023) so do not bloat all other DTBs. Signed-off-by: Hector Martin Co-developed-by: Janne Grunau Signed-off-by: Janne Grunau --- arch/arm64/boot/dts/apple/t6022-j180d.dts | 352 +++++++++++++++++- arch/arm64/boot/dts/apple/t6022-pcie-ge.dtsi | 27 ++ arch/arm64/boot/dts/apple/t6022.dtsi | 6 + .../arm64/boot/dts/apple/t602x-gpio-pins.dtsi | 4 + arch/arm64/boot/dts/apple/t602x-pcie-ge.dtsi | 87 +++++ 5 files changed, 472 insertions(+), 4 deletions(-) create mode 100644 arch/arm64/boot/dts/apple/t6022-pcie-ge.dtsi create mode 100644 arch/arm64/boot/dts/apple/t602x-pcie-ge.dtsi diff --git a/arch/arm64/boot/dts/apple/t6022-j180d.dts b/arch/arm64/boot/dts/apple/t6022-j180d.dts index c0674aad5c49c9..e22dd039bbae85 100644 --- a/arch/arm64/boot/dts/apple/t6022-j180d.dts +++ b/arch/arm64/boot/dts/apple/t6022-j180d.dts @@ -11,6 +11,7 @@ #include "t6022.dtsi" #include "t6022-jxxxd.dtsi" +#include "t6022-pcie-ge.dtsi" / { compatible = "apple,j180d", "apple,t6022", "apple,arm-platform"; @@ -26,11 +27,11 @@ atcphy5 = &atcphy1_die1; atcphy6 = &atcphy2_die1; atcphy7 = &atcphy3_die1; - //bluetooth0 = &bluetooth0; - //ethernet0 = ðernet0; - //ethernet1 = ðernet1; + bluetooth0 = &bluetooth0; + ethernet0 = ðernet0; + ethernet1 = ðernet1; serial0 = &serial0; - //wifi0 = &wifi0; + wifi0 = &wifi0; }; chosen { @@ -611,6 +612,349 @@ }; }; +/* PCIe devices */ +&port_ge00 { + bus-range = <0x01 0x09>; + + pci@0,0 { + // compatible = "pci11f8,4000", "pciclass,060400", "pciclass,0604"; + device_type = "pci"; + reg = <0x10000 0x00 0x00 0x00 0x00>; + bus-range = <0x02 0x07>; + + #address-cells = <0x03>; + #size-cells = <0x02>; + ranges = <0x82010000 0x00 0x80000000 0x82010000 0x00 0x80000000 0x00 0x500000>, + <0xc3010000 0x18 0x00000000 0xc3010000 0x18 0x00000000 0x00 0x500000>; + + #interrupt-cells = <0x01>; + interrupt-map-mask = <0xffff00 0x00 0x00 0x07>; + interrupt-map = <0x20000 0x00 0x00 0x01 &port_ge00 0x00 0x00 0x00 0x00>, + <0x20000 0x00 0x00 0x02 &port_ge00 0x00 0x00 0x00 0x01>, + <0x20000 0x00 0x00 0x03 &port_ge00 0x00 0x00 0x00 0x02>, + <0x20000 0x00 0x00 0x04 &port_ge00 0x00 0x00 0x00 0x03>, + <0x20800 0x00 0x00 0x01 &port_ge00 0x00 0x00 0x00 0x01>, + <0x20800 0x00 0x00 0x02 &port_ge00 0x00 0x00 0x00 0x02>, + <0x20800 0x00 0x00 0x03 &port_ge00 0x00 0x00 0x00 0x03>, + <0x20800 0x00 0x00 0x04 &port_ge00 0x00 0x00 0x00 0x00>, + <0x21000 0x00 0x00 0x01 &port_ge00 0x00 0x00 0x00 0x02>, + <0x21000 0x00 0x00 0x02 &port_ge00 0x00 0x00 0x00 0x03>, + <0x21000 0x00 0x00 0x03 &port_ge00 0x00 0x00 0x00 0x00>, + <0x21000 0x00 0x00 0x04 &port_ge00 0x00 0x00 0x00 0x01>, + <0x21800 0x00 0x00 0x01 &port_ge00 0x00 0x00 0x00 0x03>, + <0x21800 0x00 0x00 0x02 &port_ge00 0x00 0x00 0x00 0x00>, + <0x21800 0x00 0x00 0x03 &port_ge00 0x00 0x00 0x00 0x01>, + <0x21800 0x00 0x00 0x04 &port_ge00 0x00 0x00 0x00 0x02>, + <0x22000 0x00 0x00 0x01 &port_ge00 0x00 0x00 0x00 0x00>, + <0x22000 0x00 0x00 0x02 &port_ge00 0x00 0x00 0x00 0x01>, + <0x22000 0x00 0x00 0x03 &port_ge00 0x00 0x00 0x00 0x02>, + <0x22000 0x00 0x00 0x04 &port_ge00 0x00 0x00 0x00 0x03>; + + /* pci-slot1-dsp, PCIe slot-1 */ + pci@0,0 { + compatible = "pci11f8,4000", "pciclass,060400", "pciclass,0604"; + device_type = "pci"; + reg = <0x20000 0x00 0x00 0x00 0x00>; + bus-range = <0x03 0x03>; + + #address-cells = <0x03>; + #size-cells = <0x02>; + ranges; + }; + + /* pci-slot2-dsp, PCIe slot-2 */ + pci@1,0 { + compatible = "pci11f8,4000", "pciclass,060400", "pciclass,0604"; + device_type = "pci"; + reg = <0x20800 0x00 0x00 0x00 0x00>; + bus-range = <0x04 0x04>; + + #address-cells = <0x03>; + #size-cells = <0x02>; + ranges; + }; + + /* pci-slot3-dsp, PCIe slot-3 */ + pci@2,0 { + compatible = "pci11f8,4000", "pciclass,060400", "pciclass,0604"; + device_type = "pci"; + reg = <0x21000 0x00 0x00 0x00 0x00>; + bus-range = <0x05 0x05>; + + #address-cells = <0x03>; + #size-cells = <0x02>; + ranges; + }; + + /* pci-slot4-dsp, PCIe slot-4 */ + pci@3,0 { + compatible = "pci11f8,4000", "pciclass,060400", "pciclass,0604"; + device_type = "pci"; + reg = <0x21800 0x00 0x00 0x00 0x00>; + bus-range = <0x06 0x06>; + + #address-cells = <0x03>; + #size-cells = <0x02>; + ranges; + }; + + /* pci-slot5-dsp, PCIe slot-5 */ + pci@4,0 { + compatible = "pci11f8,4000", "pciclass,060400", "pciclass,0604"; + device_type = "pci"; + reg = <0x22000 0x00 0x00 0x00 0x00>; + bus-range = <0x07 0x07>; + + #address-cells = <0x03>; + #size-cells = <0x02>; + ranges; + }; + + }; +}; + +&port_ge00_die1 { + bus-range = <0x01 0x09>; + + /* + * Add mulptiple "reset-gpios" since there is no mechanismen to access + * PERST# for devices behind the PCIe switch. + * The "pwren" GPIO is from the wifi/bt chip which faces the same + * problem without pci-pwrctrl integration. + */ + reset-gpios = <&pinctrl_ap 4 GPIO_ACTIVE_LOW>, + <&pinctrl_ap 6 GPIO_ACTIVE_LOW>, + <&pinctrl_ap 7 GPIO_ACTIVE_LOW>, + <&pinctrl_ap_die1 9 GPIO_ACTIVE_LOW>; + pwren-gpios = <&smc_gpio 13 GPIO_ACTIVE_HIGH>; + + pci@0,0 { + device_type = "pci"; + reg = <0x10000 0x00 0x00 0x00 0x00>; + bus-range = <0x02 0x09>; + + #address-cells = <3>; + #size-cells = <2>; + ranges; + + interrupt-controller; + #interrupt-cells = <1>; + interrupt-map-mask = <0xffff00 0x00 0x00 0x07>; + interrupt-map = <0x20000 0x00 0x00 0x01 &port_ge00_die1 0x00 0x00 0x00 0x00>, + <0x20000 0x00 0x00 0x02 &port_ge00_die1 0x00 0x00 0x00 0x01>, + <0x20000 0x00 0x00 0x03 &port_ge00_die1 0x00 0x00 0x00 0x02>, + <0x20000 0x00 0x00 0x04 &port_ge00_die1 0x00 0x00 0x00 0x03>, + <0x20800 0x00 0x00 0x01 &port_ge00_die1 0x00 0x00 0x00 0x01>, + <0x20800 0x00 0x00 0x02 &port_ge00_die1 0x00 0x00 0x00 0x02>, + <0x20800 0x00 0x00 0x03 &port_ge00_die1 0x00 0x00 0x00 0x03>, + <0x20800 0x00 0x00 0x04 &port_ge00_die1 0x00 0x00 0x00 0x00>, + <0x21000 0x00 0x00 0x01 &port_ge00_die1 0x00 0x00 0x00 0x02>, + <0x21000 0x00 0x00 0x02 &port_ge00_die1 0x00 0x00 0x00 0x03>, + <0x21000 0x00 0x00 0x03 &port_ge00_die1 0x00 0x00 0x00 0x00>, + <0x21000 0x00 0x00 0x04 &port_ge00_die1 0x00 0x00 0x00 0x01>, + <0x21800 0x00 0x00 0x01 &port_ge00_die1 0x00 0x00 0x00 0x03>, + <0x21800 0x00 0x00 0x02 &port_ge00_die1 0x00 0x00 0x00 0x00>, + <0x21800 0x00 0x00 0x03 &port_ge00_die1 0x00 0x00 0x00 0x01>, + <0x21800 0x00 0x00 0x04 &port_ge00_die1 0x00 0x00 0x00 0x02>, + <0x22000 0x00 0x00 0x01 &port_ge00_die1 0x00 0x00 0x00 0x00>, + <0x22000 0x00 0x00 0x02 &port_ge00_die1 0x00 0x00 0x00 0x01>, + <0x22000 0x00 0x00 0x03 &port_ge00_die1 0x00 0x00 0x00 0x02>, + <0x22000 0x00 0x00 0x04 &port_ge00_die1 0x00 0x00 0x00 0x03>, + <0x22800 0x00 0x00 0x01 &port_ge00_die1 0x00 0x00 0x00 0x01>, + <0x22800 0x00 0x00 0x02 &port_ge00_die1 0x00 0x00 0x00 0x02>, + <0x22800 0x00 0x00 0x03 &port_ge00_die1 0x00 0x00 0x00 0x03>, + <0x22800 0x00 0x00 0x04 &port_ge00_die1 0x00 0x00 0x00 0x00>, + <0x23000 0x00 0x00 0x01 &port_ge00_die1 0x00 0x00 0x00 0x02>, + <0x23000 0x00 0x00 0x02 &port_ge00_die1 0x00 0x00 0x00 0x03>, + <0x23000 0x00 0x00 0x03 &port_ge00_die1 0x00 0x00 0x00 0x00>, + <0x23000 0x00 0x00 0x04 &port_ge00_die1 0x00 0x00 0x00 0x01>; + + /* pci-usba-dsp, internal USB-A port */ + pci@0,0 { + compatible = "pci11f8,4000", "pciclass,060400", "pciclass,0604"; + device_type = "pci"; + reg = <0x20000 0x00 0x00 0x00 0x00>; + bus-range = <0x03 0x03>; + + #address-cells = <3>; + #size-cells = <2>; + ranges; + + interrupt-controller; + #interrupt-cells = <1>; + interrupt-map-mask = <0 0 0 7>; + interrupt-map = <0x30000 0x00 0x00 0x02 &port_ge00_die1 0x00 0x00 0x00 0x00>, + <0x30000 0x00 0x00 0x03 &port_ge00_die1 0x00 0x00 0x00 0x01>, + <0x30000 0x00 0x00 0x04 &port_ge00_die1 0x00 0x00 0x00 0x02>; + + /* temporarily handled in the root port */ + // reset-gpios = <&pinctrl_ap 6 GPIO_ACTIVE_LOW>; + }; + + /* pci-sata-dsp, internal AHCI controller */ + pci@1,0 { + compatible = "pci11f8,4000", "pciclass,060400", "pciclass,0604"; + device_type = "pci"; + reg = <0x20800 0x00 0x00 0x00 0x00>; + bus-range = <0x04 0x04>; + + #address-cells = <3>; + #size-cells = <2>; + ranges; + + interrupt-controller; + #interrupt-cells = <1>; + interrupt-map-mask = <0 0 0 7>; + interrupt-map = <0x40000 0x00 0x00 0x01 &port_ge00_die1 0x00 0x00 0x00 0x00>, + <0x40000 0x00 0x00 0x02 &port_ge00_die1 0x00 0x00 0x00 0x01>, + <0x40000 0x00 0x00 0x03 &port_ge00_die1 0x00 0x00 0x00 0x02>; + }; + + /* pci-bio-dsp, I/O board USB-A ports */ + pci@2,0 { + compatible = "pci11f8,4000", "pciclass,060400", "pciclass,0604"; + device_type = "pci"; + reg = <0x21000 0x00 0x00 0x00 0x00>; + bus-range = <0x05 0x05>; + + #address-cells = <3>; + #size-cells = <2>; + ranges; + + interrupt-controller; + #interrupt-cells = <1>; + interrupt-map-mask = <0 0 0 7>; + interrupt-map = <0x50000 0x00 0x00 0x01 &port_ge00_die1 0x00 0x00 0x00 0x01>, + <0x50000 0x00 0x00 0x02 &port_ge00_die1 0x00 0x00 0x00 0x02>, + <0x50000 0x00 0x00 0x04 &port_ge00_die1 0x00 0x00 0x00 0x00>; + + /* temporarily handled in the root port */ + // reset-gpios = <&pinctrl_ap 7 GPIO_ACTIVE_LOW>; + }; + + /* pci-lan-dsp, Qtion AQC113 10G etherner controller (0) */ + pci@3,0 { + compatible = "pci11f8,4000", "pciclass,060400", "pciclass,0604"; + device_type = "pci"; + reg = <0x21800 0x00 0x00 0x00 0x00>; + bus-range = <0x06 0x06>; + + #address-cells = <3>; + #size-cells = <2>; + ranges; + + interrupt-controller; + #interrupt-cells = <1>; + interrupt-map-mask = <0 0 0 7>; + interrupt-map = <0x60000 0x00 0x00 0x01 &port_ge00_die1 0x00 0x00 0x00 0x02>, + <0x60000 0x00 0x00 0x03 &port_ge00_die1 0x00 0x00 0x00 0x00>, + <0x60000 0x00 0x00 0x04 &port_ge00_die1 0x00 0x00 0x00 0x01>; + + ethernet0: ethernet@0,0 { + reg = <0x60000 0x0 0x0 0x0 0x0>; + /* To be filled by the loader */ + local-mac-address = [00 10 18 00 00 00]; + }; + }; + + /* pci-lan-b-dsp, Qtion AQC113 10G etherner controller (1) */ + pci@4,0 { + compatible = "pci11f8,4000", "pciclass,060400", "pciclass,0604"; + device_type = "pci"; + reg = <0x22000 0x00 0x00 0x00 0x00>; + bus-range = <0x07 0x07>; + + #address-cells = <3>; + #size-cells = <2>; + ranges; + + interrupt-controller; + #interrupt-cells = <1>; + interrupt-map-mask = <0 0 0 7>; + interrupt-map = <0x70000 0x00 0x00 0x02 &port_ge00_die1 0x00 0x00 0x00 0x00>, + <0x70000 0x00 0x00 0x03 &port_ge00_die1 0x00 0x00 0x00 0x01>, + <0x70000 0x00 0x00 0x04 &port_ge00_die1 0x00 0x00 0x00 0x02>; + + ethernet1: ethernet@0,0 { + reg = <0x70000 0x0 0x0 0x0 0x0>; + /* To be filled by the loader */ + local-mac-address = [00 10 18 00 00 00]; + }; + }; + + /* pci-wifibt-dsp, Broadcom BCM4388 Wlan/BT */ + pci@5,0 { + compatible = "pci11f8,4000", "pciclass,060400", "pciclass,0604"; + device_type = "pci"; + reg = <0x22800 0x00 0x00 0x00 0x00>; + bus-range = <0x08 0x08>; + + #address-cells = <3>; + #size-cells = <2>; + ranges; + + interrupt-controller; + #interrupt-cells = <1>; + interrupt-map-mask = <0 0 0 7>; + interrupt-map = <0x80000 0x00 0x00 0x01 &port_ge00_die1 0x00 0x00 0x00 0x00>, + <0x80000 0x00 0x00 0x02 &port_ge00_die1 0x00 0x00 0x00 0x01>, + <0x80000 0x00 0x00 0x03 &port_ge00_die1 0x00 0x00 0x00 0x02>, + <0x80100 0x00 0x00 0x01 &port_ge00_die1 0x00 0x00 0x00 0x00>, + <0x80100 0x00 0x00 0x02 &port_ge00_die1 0x00 0x00 0x00 0x01>, + <0x80100 0x00 0x00 0x03 &port_ge00_die1 0x00 0x00 0x00 0x02>; + + /* temporarily handled in the root port */ + // reset-gpios = <&pinctrl_ap 4 GPIO_ACTIVE_LOW>; + // pwren-gpios = <&smc_gpio 13 GPIO_ACTIVE_HIGH>; + + wifi0: wifi@0,0 { + reg = <0x80000 0x0 0x0 0x0 0x0>; + compatible = "pci14e4,4433"; + brcm,board-type = "apple,sumatra"; + apple,antenna-sku = "XX"; + /* To be filled by the loader */ + local-mac-address = [00 10 18 00 00 10]; + }; + + bluetooth0: network@0,1 { + compatible = "pci14e4,5f71"; + brcm,board-type = "apple,sumatra"; + // reg = <0x80100 0x0 0x0 0x0 0x0>; + /* To be filled by the loader */ + local-bd-address = [00 00 00 00 00 00]; + }; + }; + + /* pci-slot6-dsp, PCIe slot-6 */ + pci@6,0 { + compatible = "pci11f8,4000", "pciclass,060400", "pciclass,0604"; + device_type = "pci"; + reg = <0x23000 0x00 0x00 0x00 0x00>; + bus-range = <0x09 0x09>; + + #address-cells = <3>; + #size-cells = <2>; + ranges; + }; + }; +}; + +&pcie_ge { + status = "ok"; +}; + +&pcie_ge_dart { + status = "ok"; +}; + +&pcie_ge_die1 { + status = "ok"; +}; + +&pcie_ge_dart_die1 { + status = "ok"; +}; + /* * Delete unused PCIe nodes, the Mac Pro uses slightly different PCIe * controllers with a single port connected to a PM40100 PCIe switch diff --git a/arch/arm64/boot/dts/apple/t6022-pcie-ge.dtsi b/arch/arm64/boot/dts/apple/t6022-pcie-ge.dtsi new file mode 100644 index 00000000000000..f78c483c29133f --- /dev/null +++ b/arch/arm64/boot/dts/apple/t6022-pcie-ge.dtsi @@ -0,0 +1,27 @@ +// SPDX-License-Identifier: GPL-2.0+ OR MIT +/* + * Include PCIe-GE nodes presen on both dies of T6022 (M2 Ultra) in the + * Mac Pro (2023). + * + * Copyright The Asahi Linux Contributors + */ + +#define DIE +#define DIE_NO 0 + +&die0 { + #include "t602x-pcie-ge.dtsi" +}; + +#undef DIE +#undef DIE_NO + +#define DIE _die1 +#define DIE_NO 1 + +&die1 { + #include "t602x-pcie-ge.dtsi" +}; + +#undef DIE +#undef DIE_NO diff --git a/arch/arm64/boot/dts/apple/t6022.dtsi b/arch/arm64/boot/dts/apple/t6022.dtsi index bc05cddf68f4f7..b7d13dafc7a265 100644 --- a/arch/arm64/boot/dts/apple/t6022.dtsi +++ b/arch/arm64/boot/dts/apple/t6022.dtsi @@ -367,3 +367,9 @@ apple,ppm-ki = <11.0>; apple,ppm-kp = <0.15>; }; + +&pinctrl_ap_die1 { + pcie_ge_pins_die1: pcie-ge1-pins { + pinmux = ; + }; +}; diff --git a/arch/arm64/boot/dts/apple/t602x-gpio-pins.dtsi b/arch/arm64/boot/dts/apple/t602x-gpio-pins.dtsi index e41b6475f79218..c5de99bd2e5cf3 100644 --- a/arch/arm64/boot/dts/apple/t602x-gpio-pins.dtsi +++ b/arch/arm64/boot/dts/apple/t602x-gpio-pins.dtsi @@ -78,4 +78,8 @@ , ; }; + + pcie_ge_pins: pcie-ge-pins { + pinmux = ; + }; }; diff --git a/arch/arm64/boot/dts/apple/t602x-pcie-ge.dtsi b/arch/arm64/boot/dts/apple/t602x-pcie-ge.dtsi new file mode 100644 index 00000000000000..4a509cae0e5766 --- /dev/null +++ b/arch/arm64/boot/dts/apple/t602x-pcie-ge.dtsi @@ -0,0 +1,87 @@ +// SPDX-License-Identifier: GPL-2.0+ OR MIT +/* + * PCIe-GE Nodes present on both dies of a T6022 (M2 Ultra) and M2 Pro/Max but + * only used on T6022 in the Mac Pro (2023). + * + * Copyright The Asahi Linux Contributors + */ + + DIE_NODE(pcie_ge): pcie@1680000000 { + compatible = "apple,t6020-pcie-ge", "apple,t6020-pcie"; + device_type = "pci"; + + reg = <0x16 0x80000000 0x0 0x1000000>, /* config */ + <0x16 0x91000000 0x0 0x4000>, /* rc */ + <0x16 0x94008000 0x0 0x4000>, /* port0 */ + <0x16 0x9e01c000 0x0 0x4000>, /* phy0 */ + <0x16 0x9401c000 0x0 0x1000>; /* ltssm0 */ + reg-names = "config", "rc", "port0", "phy0", "ltssm0"; + + interrupt-parent = <&aic>; + interrupts = ; + + msi-controller; + msi-parent = <&DIE_NODE(pcie_ge)>; + msi-ranges = <&aic AIC_IRQ DIE_NO 1672 IRQ_TYPE_EDGE_RISING 128>; + + iommu-map = <0x000 &DIE_NODE(pcie_ge_dart) 0 0>, + <0x100 &DIE_NODE(pcie_ge_dart) 1 1>, + <0x200 &DIE_NODE(pcie_ge_dart) 2 2>, + <0x300 &DIE_NODE(pcie_ge_dart) 3 3>, + <0x400 &DIE_NODE(pcie_ge_dart) 4 4>, + <0x500 &DIE_NODE(pcie_ge_dart) 5 5>, + <0x600 &DIE_NODE(pcie_ge_dart) 6 6>, + <0x700 &DIE_NODE(pcie_ge_dart) 7 7>, + <0x800 &DIE_NODE(pcie_ge_dart) 8 8>, + <0x900 &DIE_NODE(pcie_ge_dart) 9 9>, + <0xa00 &DIE_NODE(pcie_ge_dart) 10 10>, + <0xb00 &DIE_NODE(pcie_ge_dart) 11 11>, + <0xc00 &DIE_NODE(pcie_ge_dart) 12 12>, + <0xd00 &DIE_NODE(pcie_ge_dart) 13 13>, + <0xe00 &DIE_NODE(pcie_ge_dart) 14 14>, + <0xf00 &DIE_NODE(pcie_ge_dart) 15 15>; + iommu-map-mask = <0xff00>; + + bus-range = <0 15>; + #address-cells = <3>; + #size-cells = <2>; + ranges = <0x43000000 0x18 0x00000000 0x18 0x00000000 0x4 0x00000000>, + <0x02000000 0x00 0x80000000 0x17 0x80000000 0x0 0x80000000>; + + power-domains = <&DIE_NODE(ps_apcie_ge_sys)>; + pinctrl-0 = <&DIE_NODE(pcie_ge_pins)>; + pinctrl-names = "default"; + + dma-coherent; + + status = "disabled"; + + DIE_NODE(port_ge00): pci@0,0 { + device_type = "pci"; + reg = <0x0 0x0 0x0 0x0 0x0>; + reset-gpios = <&DIE_NODE(pinctrl_ap) 9 GPIO_ACTIVE_LOW>; + + #address-cells = <3>; + #size-cells = <2>; + ranges; + + interrupt-controller; + #interrupt-cells = <1>; + + interrupt-map-mask = <0 0 0 7>; + interrupt-map = <0 0 0 1 &DIE_NODE(port_ge00) 0 0 0 0>, + <0 0 0 2 &DIE_NODE(port_ge00) 0 0 0 1>, + <0 0 0 3 &DIE_NODE(port_ge00) 0 0 0 2>, + <0 0 0 4 &DIE_NODE(port_ge00) 0 0 0 3>; + }; + }; + + DIE_NODE(pcie_ge_dart): iommu@1694000000 { + compatible = "apple,t6020-dart", "apple,t8110-dart"; + reg = <0x16 0x94000000 0x0 0x4000>; + #iommu-cells = <1>; + interrupt-parent = <&aic>; + interrupts = ; + power-domains = <&DIE_NODE(ps_apcie_ge_sys)>; + status = "disabled"; + }; From 984c77669b0bbaefe0004ccbbb2872e30e0d960c Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Mon, 25 Sep 2023 19:55:58 +0200 Subject: [PATCH 2147/3902] arm64: dts: apple: t6020x: Mark dptx_phy_ps only on laptops always-on The desktops will need to handle this on their own. On laptops it is a little weird since dcp seems to handle the programming of the phy which is apparently used for the internal display. It might be possible to move this to the panel node once dcp is upstream ready. The chosen.framebuffer node should reference the panel then. In the meantime keep it always-on on notebooks. Signed-off-by: Janne Grunau --- arch/arm64/boot/dts/apple/t602x-j414-j416.dtsi | 7 +++++++ arch/arm64/boot/dts/apple/t602x-pmgr.dtsi | 1 - 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/arch/arm64/boot/dts/apple/t602x-j414-j416.dtsi b/arch/arm64/boot/dts/apple/t602x-j414-j416.dtsi index 37d1d523d1cdec..5aff7936721375 100644 --- a/arch/arm64/boot/dts/apple/t602x-j414-j416.dtsi +++ b/arch/arm64/boot/dts/apple/t602x-j414-j416.dtsi @@ -27,6 +27,13 @@ power-domains = <&ps_disp0_cpu0>, <&ps_dptx_phy_ps>; }; +/* HACK: keep dptx_phy_ps power-domain always-on + * it is unclear how to sequence with dcp for the integrated display + */ +&ps_dptx_phy_ps { + apple,always-on; +}; + &hpm0 { interrupts = <44 IRQ_TYPE_LEVEL_LOW>; }; diff --git a/arch/arm64/boot/dts/apple/t602x-pmgr.dtsi b/arch/arm64/boot/dts/apple/t602x-pmgr.dtsi index 8633a939592a86..7d70e8bb08185a 100644 --- a/arch/arm64/boot/dts/apple/t602x-pmgr.dtsi +++ b/arch/arm64/boot/dts/apple/t602x-pmgr.dtsi @@ -1450,7 +1450,6 @@ #power-domain-cells = <0>; #reset-cells = <0>; label = DIE_LABEL(dptx_phy_ps); - apple,always-on; power-domains = <&DIE_NODE(ps_sio)>; }; From 56699c6f6e429884e5fd89fe636961876f3b9a77 Mon Sep 17 00:00:00 2001 From: James Calligeros Date: Mon, 6 Nov 2023 20:33:51 +1000 Subject: [PATCH 2148/3902] arm64: dts: apple: add opp-microwatt to t8103/t600x This patch adds measured opp-microwatt values for the Firestorm and Icestorm application cores found in Apple's T8103 (M1), T6000 (M1 Pro), T6001 (M1 Max) and T6002 (M1 Ultra) SoCs. Values were measured from the System Management Controller's core cluster power meter. A version of freqbench modified to read this power meter was used to orchestrate testing, running 1,000,000 iterations of coremark on a single core from each cluster at each operating point. The bulk of the testing was done on a T6000. Note that Apple calibrates voltage regulator settings for each SoC as they come off the assembly line, introducing some natural variance between machines. Testing across multiple machines with identical SoCs reveals no measurable impact on the accuracy of the EM subsystem's cost calculations. Signed-off-by: James Calligeros --- arch/arm64/boot/dts/apple/t600x-common.dtsi | 20 ++++++++++++++++++++ arch/arm64/boot/dts/apple/t8103.dtsi | 20 ++++++++++++++++++++ 2 files changed, 40 insertions(+) diff --git a/arch/arm64/boot/dts/apple/t600x-common.dtsi b/arch/arm64/boot/dts/apple/t600x-common.dtsi index f434d724096e58..58a535fd707d4d 100644 --- a/arch/arm64/boot/dts/apple/t600x-common.dtsi +++ b/arch/arm64/boot/dts/apple/t600x-common.dtsi @@ -229,26 +229,31 @@ opp-hz = /bits/ 64 <600000000>; opp-level = <1>; clock-latency-ns = <7500>; + opp-microwatt = <47296>; }; opp02 { opp-hz = /bits/ 64 <972000000>; opp-level = <2>; clock-latency-ns = <23000>; + opp-microwatt = <99715>; }; opp03 { opp-hz = /bits/ 64 <1332000000>; opp-level = <3>; clock-latency-ns = <29000>; + opp-microwatt = <188860>; }; opp04 { opp-hz = /bits/ 64 <1704000000>; opp-level = <4>; clock-latency-ns = <40000>; + opp-microwatt = <288891>; }; opp05 { opp-hz = /bits/ 64 <2064000000>; opp-level = <5>; clock-latency-ns = <50000>; + opp-microwatt = <412979>; }; }; @@ -259,78 +264,93 @@ opp-hz = /bits/ 64 <600000000>; opp-level = <1>; clock-latency-ns = <8000>; + opp-microwatt = <290230>; }; opp02 { opp-hz = /bits/ 64 <828000000>; opp-level = <2>; clock-latency-ns = <18000>; + opp-microwatt = <449013>; }; opp03 { opp-hz = /bits/ 64 <1056000000>; opp-level = <3>; clock-latency-ns = <19000>; + opp-microwatt = <647097>; }; opp04 { opp-hz = /bits/ 64 <1296000000>; opp-level = <4>; clock-latency-ns = <23000>; + opp-microwatt = <865620>; }; opp05 { opp-hz = /bits/ 64 <1524000000>; opp-level = <5>; clock-latency-ns = <24000>; + opp-microwatt = <1112838>; }; opp06 { opp-hz = /bits/ 64 <1752000000>; opp-level = <6>; clock-latency-ns = <28000>; + opp-microwatt = <1453271>; }; opp07 { opp-hz = /bits/ 64 <1980000000>; opp-level = <7>; clock-latency-ns = <31000>; + opp-microwatt = <1776667>; }; opp08 { opp-hz = /bits/ 64 <2208000000>; opp-level = <8>; clock-latency-ns = <45000>; + opp-microwatt = <2366690>; }; opp09 { opp-hz = /bits/ 64 <2448000000>; opp-level = <9>; clock-latency-ns = <49000>; + opp-microwatt = <2892193>; }; opp10 { opp-hz = /bits/ 64 <2676000000>; opp-level = <10>; clock-latency-ns = <53000>; + opp-microwatt = <3475417>; }; opp11 { opp-hz = /bits/ 64 <2904000000>; opp-level = <11>; clock-latency-ns = <56000>; + opp-microwatt = <3959410>; }; opp12 { opp-hz = /bits/ 64 <3036000000>; opp-level = <12>; clock-latency-ns = <56000>; + opp-microwatt = <4540620>; }; opp13 { opp-hz = /bits/ 64 <3132000000>; opp-level = <13>; clock-latency-ns = <56000>; + opp-microwatt = <4745031>; turbo-mode; }; opp14 { opp-hz = /bits/ 64 <3168000000>; opp-level = <14>; clock-latency-ns = <56000>; + opp-microwatt = <4822390>; turbo-mode; }; opp15 { opp-hz = /bits/ 64 <3228000000>; opp-level = <15>; clock-latency-ns = <56000>; + opp-microwatt = <4951324>; turbo-mode; }; }; diff --git a/arch/arm64/boot/dts/apple/t8103.dtsi b/arch/arm64/boot/dts/apple/t8103.dtsi index ce3f7469a8a28e..97cbc7935e9434 100644 --- a/arch/arm64/boot/dts/apple/t8103.dtsi +++ b/arch/arm64/boot/dts/apple/t8103.dtsi @@ -194,26 +194,31 @@ opp-hz = /bits/ 64 <600000000>; opp-level = <1>; clock-latency-ns = <7500>; + opp-microwatt = <47296>; }; opp02 { opp-hz = /bits/ 64 <972000000>; opp-level = <2>; clock-latency-ns = <22000>; + opp-microwatt = <99715>; }; opp03 { opp-hz = /bits/ 64 <1332000000>; opp-level = <3>; clock-latency-ns = <27000>; + opp-microwatt = <188860>; }; opp04 { opp-hz = /bits/ 64 <1704000000>; opp-level = <4>; clock-latency-ns = <33000>; + opp-microwatt = <288891>; }; opp05 { opp-hz = /bits/ 64 <2064000000>; opp-level = <5>; clock-latency-ns = <50000>; + opp-microwatt = <412979>; }; }; @@ -224,79 +229,94 @@ opp-hz = /bits/ 64 <600000000>; opp-level = <1>; clock-latency-ns = <8000>; + opp-microwatt = <290230>; }; opp02 { opp-hz = /bits/ 64 <828000000>; opp-level = <2>; clock-latency-ns = <19000>; + opp-microwatt = <449013>; }; opp03 { opp-hz = /bits/ 64 <1056000000>; opp-level = <3>; clock-latency-ns = <21000>; + opp-microwatt = <647097>; }; opp04 { opp-hz = /bits/ 64 <1284000000>; opp-level = <4>; clock-latency-ns = <23000>; + opp-microwatt = <865620>; }; opp05 { opp-hz = /bits/ 64 <1500000000>; opp-level = <5>; clock-latency-ns = <24000>; + opp-microwatt = <1112838>; }; opp06 { opp-hz = /bits/ 64 <1728000000>; opp-level = <6>; clock-latency-ns = <29000>; + opp-microwatt = <1453271>; }; opp07 { opp-hz = /bits/ 64 <1956000000>; opp-level = <7>; clock-latency-ns = <31000>; + opp-microwatt = <1776667>; }; opp08 { opp-hz = /bits/ 64 <2184000000>; opp-level = <8>; clock-latency-ns = <34000>; + opp-microwatt = <2366690>; }; opp09 { opp-hz = /bits/ 64 <2388000000>; opp-level = <9>; clock-latency-ns = <36000>; + opp-microwatt = <2892193>; }; opp10 { opp-hz = /bits/ 64 <2592000000>; opp-level = <10>; clock-latency-ns = <51000>; + opp-microwatt = <3475417>; }; opp11 { opp-hz = /bits/ 64 <2772000000>; opp-level = <11>; clock-latency-ns = <54000>; + opp-microwatt = <3959410>; }; opp12 { opp-hz = /bits/ 64 <2988000000>; opp-level = <12>; clock-latency-ns = <55000>; + opp-microwatt = <4540620>; }; /* Not available until CPU deep sleep is implemented */ opp13 { opp-hz = /bits/ 64 <3096000000>; opp-level = <13>; clock-latency-ns = <55000>; + opp-microwatt = <4745031>; turbo-mode; }; opp14 { opp-hz = /bits/ 64 <3144000000>; opp-level = <14>; clock-latency-ns = <56000>; + opp-microwatt = <4822390>; turbo-mode; }; opp15 { opp-hz = /bits/ 64 <3204000000>; opp-level = <15>; clock-latency-ns = <56000>; + opp-microwatt = <4951324>; turbo-mode; }; }; From d31a56bfc45c85d54425658b8728e73490d33667 Mon Sep 17 00:00:00 2001 From: James Calligeros Date: Sun, 3 Sep 2023 16:41:27 +1000 Subject: [PATCH 2149/3902] arm64: dts: apple: t8112: add opp-microwatt props to avalanche/blizzard Enable energy-aware scheduling on devices with the Apple M2 SoC (T8112) by adding experimentally measured opp-microwatt values to the application core OPP tables. Values are an approximation calculated by the System Management Controller, and collected using freqbench. Signed-off-by: James Calligeros --- arch/arm64/boot/dts/apple/t8112.dtsi | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/arch/arm64/boot/dts/apple/t8112.dtsi b/arch/arm64/boot/dts/apple/t8112.dtsi index 9ee9eeb07308a5..714d9b64b39d37 100644 --- a/arch/arm64/boot/dts/apple/t8112.dtsi +++ b/arch/arm64/boot/dts/apple/t8112.dtsi @@ -195,36 +195,43 @@ opp-hz = /bits/ 64 <600000000>; opp-level = <1>; clock-latency-ns = <7500>; + opp-microwatt = <26000>; }; opp02 { opp-hz = /bits/ 64 <912000000>; opp-level = <2>; clock-latency-ns = <20000>; + opp-microwatt = <56000>; }; opp03 { opp-hz = /bits/ 64 <1284000000>; opp-level = <3>; clock-latency-ns = <22000>; + opp-microwatt = <88000>; }; opp04 { opp-hz = /bits/ 64 <1752000000>; opp-level = <4>; clock-latency-ns = <30000>; + opp-microwatt = <155000>; }; opp05 { opp-hz = /bits/ 64 <2004000000>; opp-level = <5>; clock-latency-ns = <35000>; + opp-microwatt = <231000>; }; opp06 { opp-hz = /bits/ 64 <2256000000>; opp-level = <6>; clock-latency-ns = <39000>; + opp-microwatt = <254000>; }; opp07 { opp-hz = /bits/ 64 <2424000000>; opp-level = <7>; clock-latency-ns = <53000>; + opp-microwatt = <351000>; }; }; @@ -236,88 +243,105 @@ opp-hz = /bits/ 64 <660000000>; opp-level = <1>; clock-latency-ns = <9000>; + opp-microwatt = <133000>; }; opp02 { opp-hz = /bits/ 64 <924000000>; opp-level = <2>; clock-latency-ns = <19000>; + opp-microwatt = <212000>; }; opp03 { opp-hz = /bits/ 64 <1188000000>; opp-level = <3>; clock-latency-ns = <22000>; + opp-microwatt = <261000>; }; opp04 { opp-hz = /bits/ 64 <1452000000>; opp-level = <4>; clock-latency-ns = <24000>; + opp-microwatt = <345000>; }; opp05 { opp-hz = /bits/ 64 <1704000000>; opp-level = <5>; clock-latency-ns = <26000>; + opp-microwatt = <441000>; }; opp06 { opp-hz = /bits/ 64 <1968000000>; opp-level = <6>; clock-latency-ns = <28000>; + opp-microwatt = <619000>; }; opp07 { opp-hz = /bits/ 64 <2208000000>; opp-level = <7>; clock-latency-ns = <30000>; + opp-microwatt = <740000>; }; opp08 { opp-hz = /bits/ 64 <2400000000>; opp-level = <8>; clock-latency-ns = <33000>; + opp-microwatt = <855000>; }; opp09 { opp-hz = /bits/ 64 <2568000000>; opp-level = <9>; clock-latency-ns = <34000>; + opp-microwatt = <1006000>; }; opp10 { opp-hz = /bits/ 64 <2724000000>; opp-level = <10>; clock-latency-ns = <36000>; + opp-microwatt = <1217000>; }; opp11 { opp-hz = /bits/ 64 <2868000000>; opp-level = <11>; clock-latency-ns = <41000>; + opp-microwatt = <1534000>; }; opp12 { opp-hz = /bits/ 64 <2988000000>; opp-level = <12>; clock-latency-ns = <42000>; + opp-microwatt = <1714000>; }; opp13 { opp-hz = /bits/ 64 <3096000000>; opp-level = <13>; clock-latency-ns = <44000>; + opp-microwatt = <1877000>; }; opp14 { opp-hz = /bits/ 64 <3204000000>; opp-level = <14>; clock-latency-ns = <46000>; + opp-microwatt = <2159000>; }; opp15 { opp-hz = /bits/ 64 <3324000000>; opp-level = <15>; clock-latency-ns = <62000>; + opp-microwatt = <2393000>; turbo-mode; }; opp16 { opp-hz = /bits/ 64 <3408000000>; opp-level = <16>; clock-latency-ns = <62000>; + opp-microwatt = <2497000>; turbo-mode; }; opp17 { opp-hz = /bits/ 64 <3504000000>; opp-level = <17>; clock-latency-ns = <62000>; + opp-microwatt = <2648000>; turbo-mode; }; }; From e0d32d52ff73d431cc3a10d9d8447ac8ae7444f9 Mon Sep 17 00:00:00 2001 From: Eileen Yoon Date: Thu, 31 Aug 2023 19:10:27 +0900 Subject: [PATCH 2150/3902] arm64: dts: apple: t8103: Add ISP nodes Signed-off-by: Eileen Yoon --- arch/arm64/boot/dts/apple/t8103-pmgr.dtsi | 117 ++++++++++++++++++++++ arch/arm64/boot/dts/apple/t8103.dtsi | 55 ++++++++++ 2 files changed, 172 insertions(+) diff --git a/arch/arm64/boot/dts/apple/t8103-pmgr.dtsi b/arch/arm64/boot/dts/apple/t8103-pmgr.dtsi index a97d64b665730f..4d1422d7e8b5b4 100644 --- a/arch/arm64/boot/dts/apple/t8103-pmgr.dtsi +++ b/arch/arm64/boot/dts/apple/t8103-pmgr.dtsi @@ -1009,6 +1009,123 @@ power-domains = <&ps_disp0_fe>; apple,min-state = <4>; }; + + /* There is a dependency tree involved with these PDs, + * but we do not express it here since the ISP driver + * is supposed to sequence them in the right order anyway + * (and we do not know the exact tree structure). + * + * This also works around spurious parent PD activation + * on machines with ISP disabled (desktops). + */ + ps_isp_set0: power-controller@4000 { + compatible = "apple,t8103-pmgr-pwrstate", "apple,pmgr-pwrstate"; + reg = <0x4000 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "isp_set0"; + apple,force-disable; + }; + + ps_isp_set1: power-controller@4008 { + compatible = "apple,t8103-pmgr-pwrstate", "apple,pmgr-pwrstate"; + reg = <0x4008 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "isp_set1"; + apple,force-disable; + apple,force-reset; + }; + + ps_isp_set2: power-controller@4010 { + compatible = "apple,t8103-pmgr-pwrstate", "apple,pmgr-pwrstate"; + reg = <0x4010 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "isp_set2"; + apple,force-disable; + apple,force-reset; + }; + + ps_isp_fe: power-controller@4018 { + compatible = "apple,t8103-pmgr-pwrstate", "apple,pmgr-pwrstate"; + reg = <0x4018 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "isp_fe"; + }; + + ps_isp_set4: power-controller@4020 { + compatible = "apple,t8103-pmgr-pwrstate", "apple,pmgr-pwrstate"; + reg = <0x4020 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "isp_set4"; + }; + + ps_isp_set5: power-controller@4028 { + compatible = "apple,t8103-pmgr-pwrstate", "apple,pmgr-pwrstate"; + reg = <0x4028 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "isp_set5"; + }; + + ps_isp_set6: power-controller@4030 { + compatible = "apple,t8103-pmgr-pwrstate", "apple,pmgr-pwrstate"; + reg = <0x4030 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "isp_set6"; + }; + + ps_isp_set7: power-controller@4038 { + compatible = "apple,t8103-pmgr-pwrstate", "apple,pmgr-pwrstate"; + reg = <0x4038 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "isp_set7"; + }; + + ps_isp_set8: power-controller@4040 { + compatible = "apple,t8103-pmgr-pwrstate", "apple,pmgr-pwrstate"; + reg = <0x4040 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "isp_set8"; + }; + + ps_isp_set9: power-controller@4048 { + compatible = "apple,t8103-pmgr-pwrstate", "apple,pmgr-pwrstate"; + reg = <0x4048 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "isp_set9"; + }; + + ps_isp_set10: power-controller@4050 { + compatible = "apple,t8103-pmgr-pwrstate", "apple,pmgr-pwrstate"; + reg = <0x4050 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "isp_set10"; + }; + + ps_isp_set11: power-controller@4058 { + compatible = "apple,t8103-pmgr-pwrstate", "apple,pmgr-pwrstate"; + reg = <0x4058 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "isp_set11"; + }; + + ps_isp_set12: power-controller@4060 { + compatible = "apple,t8103-pmgr-pwrstate", "apple,pmgr-pwrstate"; + reg = <0x4060 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "isp_set12"; + }; }; &pmgr_mini { diff --git a/arch/arm64/boot/dts/apple/t8103.dtsi b/arch/arm64/boot/dts/apple/t8103.dtsi index 97cbc7935e9434..7fc8ea84592c2b 100644 --- a/arch/arm64/boot/dts/apple/t8103.dtsi +++ b/arch/arm64/boot/dts/apple/t8103.dtsi @@ -671,6 +671,61 @@ phandle = <&display>; }; + isp_dart0: iommu@22c0e8000 { + compatible = "apple,t8103-dart"; + reg = <0x2 0x2c0e8000 0x0 0x4000>; + #iommu-cells = <1>; + interrupt-parent = <&aic>; + interrupts = ; + power-domains = <&ps_isp_sys>; + + status = "disabled"; + }; + + isp_dart1: iommu@22c0f4000 { + compatible = "apple,t8103-dart"; + reg = <0x2 0x2c0f4000 0x0 0x4000>; + #iommu-cells = <1>; + interrupt-parent = <&aic>; + interrupts = ; + power-domains = <&ps_isp_sys>; + + status = "disabled"; + }; + + isp_dart2: iommu@22c0fc000 { + compatible = "apple,t8103-dart"; + reg = <0x2 0x2c0fc000 0x0 0x4000>; + #iommu-cells = <1>; + interrupt-parent = <&aic>; + interrupts = ; + power-domains = <&ps_isp_sys>; + + status = "disabled"; + }; + + isp: isp@22a000000 { + compatible = "apple,t8103-isp", "apple,isp"; + iommus = <&isp_dart0 0>, <&isp_dart1 0>, <&isp_dart2 0>; + reg-names = "coproc", "mbox", "gpio", "mbox2"; + reg = <0x2 0x2a000000 0x0 0x2000000>, + <0x2 0x2c104000 0x0 0x100>, + <0x2 0x2c104170 0x0 0x100>, + <0x2 0x2c1043f0 0x0 0x100>; + interrupt-parent = <&aic>; + interrupts = ; + power-domains = <&ps_isp_sys>, <&ps_isp_set0>, + <&ps_isp_set1>, <&ps_isp_set2>, <&ps_isp_fe>, + <&ps_isp_set4>, <&ps_isp_set5>, <&ps_isp_set6>, + <&ps_isp_set7>, <&ps_isp_set8>, <&ps_isp_set9>, + <&ps_isp_set10>, <&ps_isp_set11>, + <&ps_isp_set12>; + + apple,dart-vm-size = <0x0 0xa0000000>; + + status = "disabled"; + }; + sio_dart: iommu@235004000 { compatible = "apple,t8103-dart"; reg = <0x2 0x35004000 0x0 0x4000>; From cec4b2f6d6a82130626af3de4c7d00c0057e1c06 Mon Sep 17 00:00:00 2001 From: Eileen Yoon Date: Sat, 2 Sep 2023 01:39:10 +0900 Subject: [PATCH 2151/3902] arm64: dts: apple: t6000: Add ISP nodes Signed-off-by: Eileen Yoon --- arch/arm64/boot/dts/apple/t600x-die0.dtsi | 49 ++++++++++++++ arch/arm64/boot/dts/apple/t600x-pmgr.dtsi | 80 +++++++++++++++++++++++ 2 files changed, 129 insertions(+) diff --git a/arch/arm64/boot/dts/apple/t600x-die0.dtsi b/arch/arm64/boot/dts/apple/t600x-die0.dtsi index 11ebfaaa2dc945..6cd052f0e36383 100644 --- a/arch/arm64/boot/dts/apple/t600x-die0.dtsi +++ b/arch/arm64/boot/dts/apple/t600x-die0.dtsi @@ -492,6 +492,55 @@ #mbox-cells = <0>; }; + isp_dart0: iommu@3860e8000 { + compatible = "apple,t6000-dart"; + reg = <0x3 0x860e8000 0x0 0x4000>; + #iommu-cells = <1>; + interrupt-parent = <&aic>; + interrupts = ; + power-domains = <&ps_isp_sys>; + status = "disabled"; + }; + + isp_dart1: iommu@3860f4000 { + compatible = "apple,t6000-dart"; + reg = <0x3 0x860f4000 0x0 0x4000>; + #iommu-cells = <1>; + interrupt-parent = <&aic>; + interrupts = ; + power-domains = <&ps_isp_sys>; + status = "disabled"; + }; + + isp_dart2: iommu@3860fc000 { + compatible = "apple,t6000-dart"; + reg = <0x3 0x860fc000 0x0 0x4000>; + #iommu-cells = <1>; + interrupt-parent = <&aic>; + interrupts = ; + power-domains = <&ps_isp_sys>; + status = "disabled"; + }; + + isp: isp@384000000 { + compatible = "apple,t6000-isp", "apple,isp"; + iommus = <&isp_dart0 0>, <&isp_dart1 0>, <&isp_dart2 0>; + reg-names = "coproc", "mbox", "gpio", "mbox2"; + reg = <0x3 0x84000000 0x0 0x2000000>, + <0x3 0x86104000 0x0 0x100>, + <0x3 0x86104170 0x0 0x100>, + <0x3 0x861043f0 0x0 0x100>; + interrupt-parent = <&aic>; + interrupts = ; + power-domains = <&ps_isp_sys>, <&ps_isp_set0>, + <&ps_isp_set1>, <&ps_isp_fe>, <&ps_isp_set3>, + <&ps_isp_set4>, <&ps_isp_set5>, <&ps_isp_set6>, + <&ps_isp_set7>, <&ps_isp_set8>; + apple,dart-vm-size = <0x0 0xa0000000>; + + status = "disabled"; + }; + pcie0_dart_0: iommu@581008000 { compatible = "apple,t6000-dart"; reg = <0x5 0x81008000 0x0 0x4000>; diff --git a/arch/arm64/boot/dts/apple/t600x-pmgr.dtsi b/arch/arm64/boot/dts/apple/t600x-pmgr.dtsi index 84d5e126e2320e..b6a46662358a50 100644 --- a/arch/arm64/boot/dts/apple/t600x-pmgr.dtsi +++ b/arch/arm64/boot/dts/apple/t600x-pmgr.dtsi @@ -1452,6 +1452,86 @@ label = DIE_LABEL(venc_me1); power-domains = <&DIE_NODE(ps_venc_me0)>; }; + + /* There is a dependency tree involved with these PDs, + * but we do not express it here since the ISP driver + * is supposed to sequence them in the right order anyway + * (and we do not know the exact tree structure). + * + * This also works around spurious parent PD activation + * on machines with ISP disabled (desktops). + */ + DIE_NODE(ps_isp_set0): power-controller@4000 { + compatible = "apple,t6000-pmgr-pwrstate", "apple,pmgr-pwrstate"; + reg = <0x4000 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = DIE_LABEL(isp_set0); + }; + + DIE_NODE(ps_isp_set1): power-controller@4010 { + compatible = "apple,t6000-pmgr-pwrstate", "apple,pmgr-pwrstate"; + reg = <0x4010 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = DIE_LABEL(isp_set1); + }; + + DIE_NODE(ps_isp_fe): power-controller@4008 { + compatible = "apple,t6000-pmgr-pwrstate", "apple,pmgr-pwrstate"; + reg = <0x4008 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = DIE_LABEL(ps_isp_fe); + }; + + DIE_NODE(ps_isp_set3): power-controller@4028 { + compatible = "apple,t6000-pmgr-pwrstate", "apple,pmgr-pwrstate"; + reg = <0x4028 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = DIE_LABEL(isp_set3); + }; + + DIE_NODE(ps_isp_set4): power-controller@4020 { + compatible = "apple,t6000-pmgr-pwrstate", "apple,pmgr-pwrstate"; + reg = <0x4020 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = DIE_LABEL(isp_set4); + }; + + DIE_NODE(ps_isp_set5): power-controller@4030 { + compatible = "apple,t6000-pmgr-pwrstate", "apple,pmgr-pwrstate"; + reg = <0x4030 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = DIE_LABEL(isp_set5); + }; + + DIE_NODE(ps_isp_set6): power-controller@4018 { + compatible = "apple,t6000-pmgr-pwrstate", "apple,pmgr-pwrstate"; + reg = <0x4018 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = DIE_LABEL(isp_set6); + }; + + DIE_NODE(ps_isp_set7): power-controller@4038 { + compatible = "apple,t6000-pmgr-pwrstate", "apple,pmgr-pwrstate"; + reg = <0x4038 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = DIE_LABEL(isp_set7); + }; + + DIE_NODE(ps_isp_set8): power-controller@4040 { + compatible = "apple,t6000-pmgr-pwrstate", "apple,pmgr-pwrstate"; + reg = <0x4040 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = DIE_LABEL(isp_set8); + }; }; &DIE_NODE(pmgr_south) { From 0742ad3c401787b2e8bcd06e9ae9d9a501190c3c Mon Sep 17 00:00:00 2001 From: Hector Martin Date: Fri, 8 Sep 2023 00:46:11 +0900 Subject: [PATCH 2152/3902] arm64: dts: apple: t8112: Add ISP nodes Co-developed-by: Janne Grunau Signed-off-by: Janne Grunau Signed-off-by: Hector Martin --- arch/arm64/boot/dts/apple/t8112-pmgr.dtsi | 117 ++++++++++++++++++++++ arch/arm64/boot/dts/apple/t8112.dtsi | 51 ++++++++++ 2 files changed, 168 insertions(+) diff --git a/arch/arm64/boot/dts/apple/t8112-pmgr.dtsi b/arch/arm64/boot/dts/apple/t8112-pmgr.dtsi index 7ff5052d1cdcbc..9ed831031ae6f0 100644 --- a/arch/arm64/boot/dts/apple/t8112-pmgr.dtsi +++ b/arch/arm64/boot/dts/apple/t8112-pmgr.dtsi @@ -967,6 +967,123 @@ apple,always-on; }; + /* There is a dependency tree involved with these PDs, + * but we do not express it here since the ISP driver + * is supposed to sequence them in the right order anyway + * (and we do not know the exact tree structure). + * + * This also works around spurious parent PD activation + * on machines with ISP disabled (desktops). + */ + ps_isp_set0: power-controller@4000 { + compatible = "apple,t8103-pmgr-pwrstate", "apple,pmgr-pwrstate"; + reg = <0x4000 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "isp_set0"; + apple,force-disable; + }; + + ps_isp_set1: power-controller@4008 { + compatible = "apple,t8103-pmgr-pwrstate", "apple,pmgr-pwrstate"; + reg = <0x4008 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "isp_set1"; + apple,force-disable; + apple,force-reset; + }; + + ps_isp_set2: power-controller@4010 { + compatible = "apple,t8103-pmgr-pwrstate", "apple,pmgr-pwrstate"; + reg = <0x4010 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "isp_set2"; + apple,force-disable; + apple,force-reset; + }; + + ps_isp_fe: power-controller@4018 { + compatible = "apple,t8103-pmgr-pwrstate", "apple,pmgr-pwrstate"; + reg = <0x4018 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "isp_fe"; + }; + + ps_isp_set4: power-controller@4020 { + compatible = "apple,t8103-pmgr-pwrstate", "apple,pmgr-pwrstate"; + reg = <0x4020 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "isp_set4"; + }; + + ps_isp_set5: power-controller@4028 { + compatible = "apple,t8103-pmgr-pwrstate", "apple,pmgr-pwrstate"; + reg = <0x4028 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "isp_set5"; + }; + + ps_isp_set6: power-controller@4030 { + compatible = "apple,t8103-pmgr-pwrstate", "apple,pmgr-pwrstate"; + reg = <0x4030 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "isp_set6"; + }; + + ps_isp_set7: power-controller@4038 { + compatible = "apple,t8103-pmgr-pwrstate", "apple,pmgr-pwrstate"; + reg = <0x4038 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "isp_set7"; + }; + + ps_isp_set8: power-controller@4040 { + compatible = "apple,t8103-pmgr-pwrstate", "apple,pmgr-pwrstate"; + reg = <0x4040 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "isp_set8"; + }; + + ps_isp_set9: power-controller@4048 { + compatible = "apple,t8103-pmgr-pwrstate", "apple,pmgr-pwrstate"; + reg = <0x4048 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "isp_set9"; + }; + + ps_isp_set12: power-controller@4050 { + compatible = "apple,t8103-pmgr-pwrstate", "apple,pmgr-pwrstate"; + reg = <0x4050 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "isp_set10"; + }; + + ps_isp_set10: power-controller@4058 { + compatible = "apple,t8103-pmgr-pwrstate", "apple,pmgr-pwrstate"; + reg = <0x4058 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "isp_set11"; + }; + + ps_isp_set11: power-controller@4060 { + compatible = "apple,t8103-pmgr-pwrstate", "apple,pmgr-pwrstate"; + reg = <0x4060 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "isp_set12"; + }; + ps_venc_dma: power-controller@8000 { compatible = "apple,t8112-pmgr-pwrstate", "apple,pmgr-pwrstate"; reg = <0x8000 4>; diff --git a/arch/arm64/boot/dts/apple/t8112.dtsi b/arch/arm64/boot/dts/apple/t8112.dtsi index 714d9b64b39d37..fb42c57b0660a2 100644 --- a/arch/arm64/boot/dts/apple/t8112.dtsi +++ b/arch/arm64/boot/dts/apple/t8112.dtsi @@ -624,6 +624,57 @@ }; }; + isp_dart0: iommu@22c4a8000 { + compatible = "apple,t8110-dart"; + reg = <0x2 0x2c4a8000 0x0 0x4000>; + #iommu-cells = <1>; + interrupt-parent = <&aic>; + interrupts = ; + power-domains = <&ps_isp_sys>; + status = "disabled"; + }; + + isp_dart1: iommu@22c4b4000 { + compatible = "apple,t8110-dart"; + reg = <0x2 0x2c4b4000 0x0 0x4000>; + #iommu-cells = <1>; + interrupt-parent = <&aic>; + interrupts = ; + power-domains = <&ps_isp_sys>; + status = "disabled"; + }; + + isp_dart2: iommu@22c4bc000 { + compatible = "apple,t8110-dart"; + reg = <0x2 0x2c4bc000 0x0 0x4000>; + #iommu-cells = <1>; + interrupt-parent = <&aic>; + interrupts = ; + power-domains = <&ps_isp_sys>; + status = "disabled"; + }; + + isp: isp@22a000000 { + compatible = "apple,t8112-isp", "apple,isp"; + iommus = <&isp_dart0 0>, <&isp_dart1 0>, <&isp_dart2 0>; + reg-names = "coproc", "mbox", "gpio", "mbox2"; + reg = <0x2 0x2a000000 0x0 0x2000000>, + <0x2 0x2c4c4000 0x0 0x100>, + <0x2 0x2c4c41b0 0x0 0x100>, + <0x2 0x2c4c4430 0x0 0x100>; + interrupt-parent = <&aic>; + interrupts = ; + power-domains = <&ps_isp_sys>, <&ps_isp_set0>, + <&ps_isp_set1>, <&ps_isp_set2>, <&ps_isp_fe>, + <&ps_isp_set4>, <&ps_isp_set5>, <&ps_isp_set6>, + <&ps_isp_set7>, <&ps_isp_set8>, <&ps_isp_set9>, + <&ps_isp_set10>, <&ps_isp_set11>, + <&ps_isp_set12>; + + apple,dart-vm-size = <0x0 0xa0000000>; + status = "disabled"; + }; + disp0_dart: iommu@231304000 { compatible = "apple,t8112-dart", "apple,t8110-dart"; reg = <0x2 0x31304000 0x0 0x4000>; From 54c4169c970789a0532af75d9106c05c998b9ccb Mon Sep 17 00:00:00 2001 From: Asahi Lina Date: Sun, 24 Sep 2023 01:01:10 +0900 Subject: [PATCH 2153/3902] arm64: dts: apple: t602x: Add ISP nodes Signed-off-by: Asahi Lina --- arch/arm64/boot/dts/apple/t602x-die0.dtsi | 54 +++++++++++++++++++++++ 1 file changed, 54 insertions(+) diff --git a/arch/arm64/boot/dts/apple/t602x-die0.dtsi b/arch/arm64/boot/dts/apple/t602x-die0.dtsi index 0662febce379e0..25f5f01a645c2c 100644 --- a/arch/arm64/boot/dts/apple/t602x-die0.dtsi +++ b/arch/arm64/boot/dts/apple/t602x-die0.dtsi @@ -241,6 +241,60 @@ }; + isp_dart0: iommu@3860e8000 { + compatible = "apple,t6020-dart", "apple,t8110-dart"; + reg = <0x3 0x860e8000 0x0 0x4000>; + #iommu-cells = <1>; + interrupt-parent = <&aic>; + interrupts = ; + power-domains = <&ps_isp_sys>; + + apple,dma-range = <0x100 0x0 0x1 0x0>; + status = "disabled"; + }; + + isp_dart1: iommu@3860f4000 { + compatible = "apple,t6020-dart", "apple,t8110-dart"; + reg = <0x3 0x860f4000 0x0 0x4000>; + #iommu-cells = <1>; + interrupt-parent = <&aic>; + interrupts = ; + power-domains = <&ps_isp_sys>; + + apple,dma-range = <0x100 0x0 0x1 0x0>; + status = "disabled"; + }; + + isp_dart2: iommu@3860fc000 { + compatible = "apple,t6020-dart", "apple,t8110-dart"; + reg = <0x3 0x860fc000 0x0 0x4000>; + #iommu-cells = <1>; + interrupt-parent = <&aic>; + interrupts = ; + power-domains = <&ps_isp_sys>; + + apple,dma-range = <0x100 0x0 0x1 0x0>; + status = "disabled"; + }; + + isp: isp@384000000 { + compatible = "apple,t6020-isp", "apple,isp"; + iommus = <&isp_dart0 0>, <&isp_dart1 0>, <&isp_dart2 0>; + reg-names = "coproc", "mbox", "gpio", "mbox2"; + reg = <0x3 0x84000000 0x0 0x2000000>, + <0x3 0x86104000 0x0 0x100>, + <0x3 0x86104170 0x0 0x100>, + <0x3 0x861043f0 0x0 0x100>; + interrupt-parent = <&aic>; + interrupts = ; + power-domains = <&ps_isp_cpu>, <&ps_isp_fe>, + <&ps_dprx>, <&ps_isp_vis>, <&ps_isp_be>, + <&ps_isp_clr>, <&ps_isp_raw>; + apple,dart-vm-size = <0x0 0xa0000000>; + + status = "disabled"; + }; + disp0_dart: iommu@389304000 { compatible = "apple,t6020-dart", "apple,t8110-dart"; reg = <0x3 0x89304000 0x0 0x4000>; From ffce8a26f787645041ba311ed9d69c4a70b6daac Mon Sep 17 00:00:00 2001 From: Asahi Lina Date: Thu, 28 Sep 2023 02:02:43 +0900 Subject: [PATCH 2154/3902] arm64: dts: ISP platform configs Signed-off-by: Asahi Lina --- arch/arm64/boot/dts/apple/isp-common.dtsi | 43 +++++++++ arch/arm64/boot/dts/apple/isp-imx248.dtsi | 22 +++++ arch/arm64/boot/dts/apple/isp-imx364.dtsi | 71 ++++++++++++++ .../arm64/boot/dts/apple/isp-imx558-cfg0.dtsi | 92 +++++++++++++++++++ arch/arm64/boot/dts/apple/isp-imx558.dtsi | 50 ++++++++++ .../arm64/boot/dts/apple/t600x-j314-j316.dtsi | 6 ++ .../arm64/boot/dts/apple/t602x-j414-j416.dtsi | 7 ++ arch/arm64/boot/dts/apple/t8103-j293.dts | 6 ++ arch/arm64/boot/dts/apple/t8103-j313.dts | 6 ++ arch/arm64/boot/dts/apple/t8103-j456.dts | 6 ++ arch/arm64/boot/dts/apple/t8103-j457.dts | 6 ++ arch/arm64/boot/dts/apple/t8112-j413.dts | 7 ++ arch/arm64/boot/dts/apple/t8112-j415.dts | 7 ++ arch/arm64/boot/dts/apple/t8112-j493.dts | 6 ++ 14 files changed, 335 insertions(+) create mode 100644 arch/arm64/boot/dts/apple/isp-common.dtsi create mode 100644 arch/arm64/boot/dts/apple/isp-imx248.dtsi create mode 100644 arch/arm64/boot/dts/apple/isp-imx364.dtsi create mode 100644 arch/arm64/boot/dts/apple/isp-imx558-cfg0.dtsi create mode 100644 arch/arm64/boot/dts/apple/isp-imx558.dtsi diff --git a/arch/arm64/boot/dts/apple/isp-common.dtsi b/arch/arm64/boot/dts/apple/isp-common.dtsi new file mode 100644 index 00000000000000..bf406772469b67 --- /dev/null +++ b/arch/arm64/boot/dts/apple/isp-common.dtsi @@ -0,0 +1,43 @@ +// SPDX-License-Identifier: GPL-2.0+ OR MIT +/* + * Common ISP configuration for Apple silicon platforms. + * + * Copyright The Asahi Linux Contributors + */ + +/ { + aliases { + isp = &isp; + }; + + reserved-memory { + #address-cells = <2>; + #size-cells = <2>; + ranges; + + isp_heap: isp-heap { + compatible = "apple,asc-mem"; + /* Filled in by bootloder */ + reg = <0 0 0 0>; + no-map; + }; + }; +}; + +&isp { + memory-region = <&isp_heap>; + memory-region-names = "heap"; + status = "okay"; +}; + +&isp_dart0 { + status = "okay"; +}; + +&isp_dart1 { + status = "okay"; +}; + +&isp_dart2 { + status = "okay"; +}; diff --git a/arch/arm64/boot/dts/apple/isp-imx248.dtsi b/arch/arm64/boot/dts/apple/isp-imx248.dtsi new file mode 100644 index 00000000000000..acad3ecf0331ef --- /dev/null +++ b/arch/arm64/boot/dts/apple/isp-imx248.dtsi @@ -0,0 +1,22 @@ +// SPDX-License-Identifier: GPL-2.0+ OR MIT +/* + * ISP configuration for platforms with IMX248 sensor. + * + * Copyright The Asahi Linux Contributors + */ + +#include "isp-common.dtsi" + +&isp { + apple,temporal-filter = <0>; + + sensor-presets { + /* 1280x720 */ + preset0 { + apple,config-index = <0>; + apple,input-size = <1296 736>; + apple,output-size = <1280 720>; + apple,crop = <8 8 1280 720>; + }; + }; +}; diff --git a/arch/arm64/boot/dts/apple/isp-imx364.dtsi b/arch/arm64/boot/dts/apple/isp-imx364.dtsi new file mode 100644 index 00000000000000..55484d86523657 --- /dev/null +++ b/arch/arm64/boot/dts/apple/isp-imx364.dtsi @@ -0,0 +1,71 @@ +// SPDX-License-Identifier: GPL-2.0+ OR MIT +/* + * ISP configuration for platforms with IMX364 sensor. + * + * Copyright The Asahi Linux Contributors + */ + +#include "isp-common.dtsi" + +&isp { + apple,temporal-filter = <0>; + + sensor-presets { + /* 1920x1080 */ + preset0 { + apple,config-index = <0>; + apple,input-size = <1920 1080>; + apple,output-size = <1920 1080>; + apple,crop = <0 0 1920 1080>; + }; + /* 1440x720 (4:3) */ + preset1 { + apple,config-index = <0>; + apple,input-size = <1920 1080>; + apple,output-size = <1440 1080>; + apple,crop = <240 0 1440 1080>; + }; + /* 1280x720 (16:9) */ + preset2 { + apple,config-index = <0>; + apple,input-size = <1920 1080>; + apple,output-size = <1280 720>; + apple,crop = <0 0 1920 1080>; + }; + /* 960x720 (4:3) */ + preset3{ + apple,config-index = <0>; + apple,input-size = <1920 1080>; + apple,output-size = <960 720>; + apple,crop = <240 0 1440 1080>; + }; + /* 960x540 (16:9) */ + preset4 { + apple,config-index = <0>; + apple,input-size = <1920 1080>; + apple,output-size = <960 540>; + apple,crop = <0 0 1920 1080>; + }; + /* 640x480 (4:3) */ + preset5 { + apple,config-index = <0>; + apple,input-size = <1920 1080>; + apple,output-size = <640 480>; + apple,crop = <240 0 1440 1080>; + }; + /* 640x360 (16:9) */ + preset6 { + apple,config-index = <0>; + apple,input-size = <1920 1080>; + apple,output-size = <640 360>; + apple,crop = <0 0 1920 1080>; + }; + /* 320x180 (16:9) */ + preset7 { + apple,config-index = <0>; + apple,input-size = <1920 1080>; + apple,output-size = <320 180>; + apple,crop = <0 0 1920 1080>; + }; + }; +}; diff --git a/arch/arm64/boot/dts/apple/isp-imx558-cfg0.dtsi b/arch/arm64/boot/dts/apple/isp-imx558-cfg0.dtsi new file mode 100644 index 00000000000000..729b97829cbb7e --- /dev/null +++ b/arch/arm64/boot/dts/apple/isp-imx558-cfg0.dtsi @@ -0,0 +1,92 @@ +// SPDX-License-Identifier: GPL-2.0+ OR MIT +/* + * ISP configuration for platforms with IMX558 sensor in + * config #0 mode. + * + * These platforms enable MLVNR for all configs except + * #0, which we don't support. Config #0 is an uncropped + * square 1920x1920 sensor, with dark corners. + * Therefore, we synthesize common resolutions by using + * crop/scale while always choosing config #0. + * + * Copyright The Asahi Linux Contributors + */ + +#include "isp-common.dtsi" + +&isp { + apple,temporal-filter = <0>; + + sensor-presets { + /* 1920x1080 */ + preset0 { + apple,config-index = <0>; + apple,input-size = <1920 1920>; + apple,output-size = <1920 1080>; + apple,crop = <0 420 1920 1080>; + }; + /* 1080x1920 */ + preset1 { + apple,config-index = <0>; + apple,input-size = <1920 1920>; + apple,output-size = <1080 1920>; + apple,crop = <420 0 1080 1920>; + }; + /* 1920x1440 */ + preset2 { + apple,config-index = <0>; + apple,input-size = <1920 1920>; + apple,output-size = <1920 1440>; + apple,crop = <0 240 1920 1440>; + }; + /* 1440x1920 */ + preset3 { + apple,config-index = <0>; + apple,input-size = <1920 1920>; + apple,output-size = <1440 1920>; + apple,crop = <240 0 1440 1920>; + }; + /* 1280x720 */ + preset4 { + apple,config-index = <0>; + apple,input-size = <1920 1920>; + apple,output-size = <1280 720>; + apple,crop = <0 420 1920 1080>; + }; + /* 720x1280 */ + preset5 { + apple,config-index = <0>; + apple,input-size = <1920 1920>; + apple,output-size = <720 1280>; + apple,crop = <420 0 1080 1920>; + }; + /* 1280x960 */ + preset6 { + apple,config-index = <0>; + apple,input-size = <1920 1920>; + apple,output-size = <1280 960>; + apple,crop = <0 240 1920 1440>; + }; + /* 960x1280 */ + preset7 { + apple,config-index = <0>; + apple,input-size = <1920 1920>; + apple,output-size = <960 1280>; + apple,crop = <240 0 1440 1920>; + }; + /* 640x480 */ + preset8 { + apple,config-index = <0>; + apple,input-size = <1920 1920>; + apple,output-size = <640 480>; + apple,crop = <0 240 1920 1440>; + }; + /* 480x640 */ + preset9 { + apple,config-index = <0>; + apple,input-size = <1920 1920>; + apple,output-size = <480 640>; + apple,crop = <240 0 1440 1920>; + }; + }; +}; diff --git a/arch/arm64/boot/dts/apple/isp-imx558.dtsi b/arch/arm64/boot/dts/apple/isp-imx558.dtsi new file mode 100644 index 00000000000000..a23785b7d5e65a --- /dev/null +++ b/arch/arm64/boot/dts/apple/isp-imx558.dtsi @@ -0,0 +1,50 @@ +// SPDX-License-Identifier: GPL-2.0+ OR MIT +/* + * ISP configuration for platforms with IMX558 sensor. + * + * Copyright The Asahi Linux Contributors + */ + +#include "isp-common.dtsi" + +&isp { + apple,temporal-filter = <0>; + + sensor-presets { + /* 1920x1080 */ + preset0 { + apple,config-index = <1>; + apple,input-size = <1920 1080>; + apple,output-size = <1920 1080>; + apple,crop = <0 0 1920 1080>; + }; + /* 1080x1920 */ + preset1 { + apple,config-index = <2>; + apple,input-size = <1080 1920>; + apple,output-size = <1080 1920>; + apple,crop = <0 0 1080 1920>; + }; + /* 1760x1328 */ + preset2 { + apple,config-index = <3>; + apple,input-size = <1760 1328>; + apple,output-size = <1760 1328>; + apple,crop = <0 0 1760 1328>; + }; + /* 1328x1760 */ + preset3 { + apple,config-index = <4>; + apple,input-size = <1328 1760>; + apple,output-size = < 1328 1760>; + apple,crop = <0 0 1328 1760>; + }; + /* 1152x1152 */ + preset4 { + apple,config-index = <5>; + apple,input-size = <1152 1152>; + apple,output-size = <1152 1152>; + apple,crop = <0 0 1152 1152>; + }; + }; +}; diff --git a/arch/arm64/boot/dts/apple/t600x-j314-j316.dtsi b/arch/arm64/boot/dts/apple/t600x-j314-j316.dtsi index 37024d1d5c9c37..187e132d77281b 100644 --- a/arch/arm64/boot/dts/apple/t600x-j314-j316.dtsi +++ b/arch/arm64/boot/dts/apple/t600x-j314-j316.dtsi @@ -542,3 +542,9 @@ }; #include "spi1-nvram.dtsi" + +#include "isp-imx558.dtsi" + +&isp { + apple,platform-id = <3>; +}; diff --git a/arch/arm64/boot/dts/apple/t602x-j414-j416.dtsi b/arch/arm64/boot/dts/apple/t602x-j414-j416.dtsi index 5aff7936721375..b9aee8ec432b9a 100644 --- a/arch/arm64/boot/dts/apple/t602x-j414-j416.dtsi +++ b/arch/arm64/boot/dts/apple/t602x-j414-j416.dtsi @@ -137,3 +137,10 @@ tp_accel { }; }; + +&isp { + apple,platform-id = <7>; + /delete-node/ sensor-presets; /* Override j31[46] below */ +}; + +#include "isp-imx558-cfg0.dtsi" diff --git a/arch/arm64/boot/dts/apple/t8103-j293.dts b/arch/arm64/boot/dts/apple/t8103-j293.dts index 50043beb65db0a..a113b9a57e1a6e 100644 --- a/arch/arm64/boot/dts/apple/t8103-j293.dts +++ b/arch/arm64/boot/dts/apple/t8103-j293.dts @@ -273,3 +273,9 @@ }; }; }; + +#include "isp-imx248.dtsi" + +&isp { + apple,platform-id = <1>; +}; diff --git a/arch/arm64/boot/dts/apple/t8103-j313.dts b/arch/arm64/boot/dts/apple/t8103-j313.dts index 7e77fe091c6345..52940e43db9155 100644 --- a/arch/arm64/boot/dts/apple/t8103-j313.dts +++ b/arch/arm64/boot/dts/apple/t8103-j313.dts @@ -168,3 +168,9 @@ }; }; }; + +#include "isp-imx248.dtsi" + +&isp { + apple,platform-id = <1>; +}; diff --git a/arch/arm64/boot/dts/apple/t8103-j456.dts b/arch/arm64/boot/dts/apple/t8103-j456.dts index c384d4dfd19a36..a3638871f3660e 100644 --- a/arch/arm64/boot/dts/apple/t8103-j456.dts +++ b/arch/arm64/boot/dts/apple/t8103-j456.dts @@ -137,3 +137,9 @@ &gpu { apple,perf-base-pstate = <3>; }; + +#include "isp-imx364.dtsi" + +&isp { + apple,platform-id = <2>; +}; diff --git a/arch/arm64/boot/dts/apple/t8103-j457.dts b/arch/arm64/boot/dts/apple/t8103-j457.dts index 28e3eedfc35bf6..4c1adb310ba91f 100644 --- a/arch/arm64/boot/dts/apple/t8103-j457.dts +++ b/arch/arm64/boot/dts/apple/t8103-j457.dts @@ -118,3 +118,9 @@ &gpu { apple,perf-base-pstate = <3>; }; + +#include "isp-imx364.dtsi" + +&isp { + apple,platform-id = <2>; +}; diff --git a/arch/arm64/boot/dts/apple/t8112-j413.dts b/arch/arm64/boot/dts/apple/t8112-j413.dts index 0077ce45cc5154..5c0cb7fc235ad5 100644 --- a/arch/arm64/boot/dts/apple/t8112-j413.dts +++ b/arch/arm64/boot/dts/apple/t8112-j413.dts @@ -243,3 +243,10 @@ tp_accel { }; }; + +#include "isp-imx558-cfg0.dtsi" + +&isp { + apple,platform-id = <14>; + apple,temporal-filter = <1>; +}; diff --git a/arch/arm64/boot/dts/apple/t8112-j415.dts b/arch/arm64/boot/dts/apple/t8112-j415.dts index 09387fc5ca46f0..7e39a477728e3f 100644 --- a/arch/arm64/boot/dts/apple/t8112-j415.dts +++ b/arch/arm64/boot/dts/apple/t8112-j415.dts @@ -269,3 +269,10 @@ tp_accel { }; }; + +#include "isp-imx558-cfg0.dtsi" + +&isp { + apple,platform-id = <15>; + apple,temporal-filter = <1>; +}; diff --git a/arch/arm64/boot/dts/apple/t8112-j493.dts b/arch/arm64/boot/dts/apple/t8112-j493.dts index 03cb807cf59d71..b6c60ec51b4a1a 100644 --- a/arch/arm64/boot/dts/apple/t8112-j493.dts +++ b/arch/arm64/boot/dts/apple/t8112-j493.dts @@ -297,3 +297,9 @@ tp_accel { }; }; + +#include "isp-imx248.dtsi" + +&isp { + apple,platform-id = <6>; +}; From 2dab41b5e0fa95e46cf943d1d034c828cec95c63 Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Sat, 4 Nov 2023 22:44:17 +0100 Subject: [PATCH 2155/3902] arm64: dts: apple: Disable ps_isp_sys unless it is used Seems to be fuxed off on t602x devices without camera and causes annoying kernel log splat. Signed-off-by: Janne Grunau --- arch/arm64/boot/dts/apple/isp-common.dtsi | 4 ++++ arch/arm64/boot/dts/apple/t600x-pmgr.dtsi | 1 + arch/arm64/boot/dts/apple/t8103-pmgr.dtsi | 1 + arch/arm64/boot/dts/apple/t8112-pmgr.dtsi | 1 + 4 files changed, 7 insertions(+) diff --git a/arch/arm64/boot/dts/apple/isp-common.dtsi b/arch/arm64/boot/dts/apple/isp-common.dtsi index bf406772469b67..739e6e9e66e740 100644 --- a/arch/arm64/boot/dts/apple/isp-common.dtsi +++ b/arch/arm64/boot/dts/apple/isp-common.dtsi @@ -41,3 +41,7 @@ &isp_dart2 { status = "okay"; }; + +&ps_isp_sys { + status = "okay"; +}; diff --git a/arch/arm64/boot/dts/apple/t600x-pmgr.dtsi b/arch/arm64/boot/dts/apple/t600x-pmgr.dtsi index b6a46662358a50..cf274aaf632c91 100644 --- a/arch/arm64/boot/dts/apple/t600x-pmgr.dtsi +++ b/arch/arm64/boot/dts/apple/t600x-pmgr.dtsi @@ -1370,6 +1370,7 @@ #reset-cells = <0>; label = DIE_LABEL(isp_sys); power-domains = <&DIE_NODE(ps_afnc2_lw1)>; + status = "disabled"; }; DIE_NODE(ps_venc_sys): power-controller@3b0 { diff --git a/arch/arm64/boot/dts/apple/t8103-pmgr.dtsi b/arch/arm64/boot/dts/apple/t8103-pmgr.dtsi index 4d1422d7e8b5b4..10facd0c01e420 100644 --- a/arch/arm64/boot/dts/apple/t8103-pmgr.dtsi +++ b/arch/arm64/boot/dts/apple/t8103-pmgr.dtsi @@ -812,6 +812,7 @@ #reset-cells = <0>; label = "isp_sys"; power-domains = <&ps_rmx>; + status = "disabled"; }; ps_venc_sys: power-controller@408 { diff --git a/arch/arm64/boot/dts/apple/t8112-pmgr.dtsi b/arch/arm64/boot/dts/apple/t8112-pmgr.dtsi index 9ed831031ae6f0..102ff3ad0535d0 100644 --- a/arch/arm64/boot/dts/apple/t8112-pmgr.dtsi +++ b/arch/arm64/boot/dts/apple/t8112-pmgr.dtsi @@ -821,6 +821,7 @@ #reset-cells = <0>; label = "isp_sys"; power-domains = <&ps_rmx1>; + status = "disabled"; }; ps_venc_sys: power-controller@440 { From 864a7f3458221f6fb6b88448366fb29cb227916b Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Sat, 7 Oct 2023 00:38:24 +0200 Subject: [PATCH 2156/3902] arm64: dts: apple: imx248: Add scaled and cropped presets Adds following resolution presets: - 960x720 (4:3) - 960x540 (16:9) - 640x480 (4:3) - 640x360 (16:9) - 320x180 (16:9) Signed-off-by: Janne Grunau --- arch/arm64/boot/dts/apple/isp-imx248.dtsi | 35 +++++++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/arch/arm64/boot/dts/apple/isp-imx248.dtsi b/arch/arm64/boot/dts/apple/isp-imx248.dtsi index acad3ecf0331ef..0a4ac1a0152c2c 100644 --- a/arch/arm64/boot/dts/apple/isp-imx248.dtsi +++ b/arch/arm64/boot/dts/apple/isp-imx248.dtsi @@ -18,5 +18,40 @@ apple,output-size = <1280 720>; apple,crop = <8 8 1280 720>; }; + /* 960x720 (4:3) */ + preset1 { + apple,config-index = <0>; + apple,input-size = <1296 736>; + apple,output-size = <960 720>; + apple,crop = <168 8 960 720>; + }; + /* 960x540 (16:9) */ + preset2 { + apple,config-index = <0>; + apple,input-size = <1296 736>; + apple,output-size = <960 540>; + apple,crop = <8 8 1280 720>; + }; + /* 640x480 (4:3) */ + preset3 { + apple,config-index = <0>; + apple,input-size = <1296 736>; + apple,output-size = <640 480>; + apple,crop = <168 8 960 720>; + }; + /* 640x360 (16:9) */ + preset4 { + apple,config-index = <0>; + apple,input-size = <1296 736>; + apple,output-size = <640 360>; + apple,crop = <8 8 1280 720>; + }; + /* 320x180 (16:9) */ + preset5 { + apple,config-index = <0>; + apple,input-size = <1296 736>; + apple,output-size = <320 180>; + apple,crop = <8 8 1280 720>; + }; }; }; From 53e58f18ac2d8d78c4e1b328db28b083c9d5dfc6 Mon Sep 17 00:00:00 2001 From: Hector Martin Date: Sun, 8 Oct 2023 19:53:27 +0900 Subject: [PATCH 2157/3902] arm64: dts: apple: imx558: Add downscaled resolution presets To match those from cfg0. The 4:3 crops are different and this also has a 1:1 config, so we might want to unify things at some point... Signed-off-by: Hector Martin --- arch/arm64/boot/dts/apple/isp-imx558.dtsi | 42 +++++++++++++++++++++++ 1 file changed, 42 insertions(+) diff --git a/arch/arm64/boot/dts/apple/isp-imx558.dtsi b/arch/arm64/boot/dts/apple/isp-imx558.dtsi index a23785b7d5e65a..d55854c883f5b6 100644 --- a/arch/arm64/boot/dts/apple/isp-imx558.dtsi +++ b/arch/arm64/boot/dts/apple/isp-imx558.dtsi @@ -46,5 +46,47 @@ apple,output-size = <1152 1152>; apple,crop = <0 0 1152 1152>; }; + /* 1280x720 */ + preset5 { + apple,config-index = <1>; + apple,input-size = <1920 1080>; + apple,output-size = <1280 720>; + apple,crop = <0 0 1920 1080>; + }; + /* 720x1280 */ + preset6 { + apple,config-index = <2>; + apple,input-size = <1080 1920>; + apple,output-size = <720 1280>; + apple,crop = <0 0 1080 1920>; + }; + /* 1280x960 */ + preset7 { + apple,config-index = <3>; + apple,input-size = <1760 1328>; + apple,output-size = <1280 960>; + apple,crop = <0 4 1760 1320>; + }; + /* 960x1280 */ + preset8 { + apple,config-index = <4>; + apple,input-size = <1328 1760>; + apple,output-size = <960 1280>; + apple,crop = <4 0 1320 1760>; + }; + /* 640x480 */ + preset9 { + apple,config-index = <3>; + apple,input-size = <1760 1328>; + apple,output-size = <640 480>; + apple,crop = <0 4 1760 1320>; + }; + /* 480x640 */ + preset10 { + apple,config-index = <4>; + apple,input-size = <1328 1760>; + apple,output-size = <480 640>; + apple,crop = <4 0 1320 1760>; + }; }; }; From 3b3fa21feb0f3c3b88afe9dc4e2133934dad1b93 Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Mon, 6 Nov 2023 00:51:53 +0100 Subject: [PATCH 2158/3902] arm64: dts: apple: t600x: Switch to apple,dma-range Obsoletes the use of "apple,asc-dram-mask" in the device tree and the dcp driver. Signed-off-by: Janne Grunau --- arch/arm64/boot/dts/apple/t600x-die0.dtsi | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/arch/arm64/boot/dts/apple/t600x-die0.dtsi b/arch/arm64/boot/dts/apple/t600x-die0.dtsi index 6cd052f0e36383..abed5352c5df76 100644 --- a/arch/arm64/boot/dts/apple/t600x-die0.dtsi +++ b/arch/arm64/boot/dts/apple/t600x-die0.dtsi @@ -175,6 +175,7 @@ interrupts = ; status = "disabled"; power-domains = <&ps_disp0_cpu0>; + apple,dma-range = <0x0 0x0 0x0 0xfc000000>; }; dcp_dart: iommu@38b30c000 { @@ -184,6 +185,7 @@ interrupt-parent = <&aic>; interrupts = ; power-domains = <&ps_disp0_cpu0>; + apple,dma-range = <0x1f0 0x0 0x0 0xfc000000>; }; dcp_mbox: mbox@38bc08000 { @@ -216,7 +218,6 @@ power-domains = <&ps_disp0_cpu0>; resets = <&ps_disp0_cpu0>; clocks = <&clk_disp0>; - apple,asc-dram-mask = <0x1f0 0x00000000>; phandle = <&dcp>; // required bus properties for 'piodma' subdevice #address-cells = <2>; From 2e5af9c4b832601b28736f0bd84592312c213107 Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Mon, 6 Nov 2023 00:51:53 +0100 Subject: [PATCH 2159/3902] arm64: dts: apple: t8103: Switch to apple,dma-range Obsoletes the use of "apple,asc-dram-mask" in the device tree and the dcp driver. Signed-off-by: Janne Grunau --- arch/arm64/boot/dts/apple/t8103.dtsi | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/arch/arm64/boot/dts/apple/t8103.dtsi b/arch/arm64/boot/dts/apple/t8103.dtsi index 7fc8ea84592c2b..28d21e4b910483 100644 --- a/arch/arm64/boot/dts/apple/t8103.dtsi +++ b/arch/arm64/boot/dts/apple/t8103.dtsi @@ -606,6 +606,7 @@ interrupt-parent = <&aic>; interrupts = ; power-domains = <&ps_disp0_cpu0>; + apple,dma-range = <0x0 0x0 0x0 0xfc000000>; status = "disabled"; }; @@ -615,6 +616,7 @@ #iommu-cells = <1>; interrupt-parent = <&aic>; interrupts = ; + apple,dma-range = <0xf 0x00000000 0x0 0xfc000000>; power-domains = <&ps_disp0_cpu0>; }; @@ -652,7 +654,6 @@ power-domains = <&ps_disp0_cpu0>; resets = <&ps_disp0_cpu0>; clocks = <&clk_disp0>; - apple,asc-dram-mask = <0xf 0x00000000>; phandle = <&dcp>; // required bus properties for 'piodma' subdevice #address-cells = <2>; From 371647bb15918a6eb466d58a40a376b1a2df0edf Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Mon, 6 Nov 2023 20:04:09 +0100 Subject: [PATCH 2160/3902] arm64: dts: apple: t8112: Switch to apple,dma-range Signed-off-by: Janne Grunau --- arch/arm64/boot/dts/apple/t8112.dtsi | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/arch/arm64/boot/dts/apple/t8112.dtsi b/arch/arm64/boot/dts/apple/t8112.dtsi index fb42c57b0660a2..1d2c231458c5e8 100644 --- a/arch/arm64/boot/dts/apple/t8112.dtsi +++ b/arch/arm64/boot/dts/apple/t8112.dtsi @@ -682,6 +682,7 @@ interrupt-parent = <&aic>; interrupts = ; power-domains = <&ps_disp0_cpu0>; + apple,dma-range = <0x0 0x0 0xf 0xffff0000>; status = "disabled"; }; @@ -692,6 +693,7 @@ interrupt-parent = <&aic>; interrupts = ; power-domains = <&ps_disp0_cpu0>; + apple,dma-range = <0x8 0x00000000 0x7 0xffff0000>; }; dcp_mbox: mbox@231c08000 { @@ -726,7 +728,6 @@ power-domains = <&ps_disp0_cpu0>; resets = <&ps_disp0_cpu0>; clocks = <&clk_disp0>; - apple,asc-dram-mask = <0x0 0x0>; phandle = <&dcp>; // required bus properties for 'piodma' subdevice #address-cells = <2>; From 2eab7ecda837868a2c7ebad8269c85e218a51cc4 Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Mon, 6 Nov 2023 00:46:53 +0100 Subject: [PATCH 2161/3902] arm64: dts: apple: t600x: Add "apple,min-state" to ps_dispextN_cpu0 DCP ASC co-processors do not come back up from lower power states. Signed-off-by: Janne Grunau --- arch/arm64/boot/dts/apple/t600x-pmgr.dtsi | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/arch/arm64/boot/dts/apple/t600x-pmgr.dtsi b/arch/arm64/boot/dts/apple/t600x-pmgr.dtsi index cf274aaf632c91..a8f85e41baa4fe 100644 --- a/arch/arm64/boot/dts/apple/t600x-pmgr.dtsi +++ b/arch/arm64/boot/dts/apple/t600x-pmgr.dtsi @@ -396,6 +396,7 @@ #reset-cells = <0>; label = DIE_LABEL(dispext0_cpu0); power-domains = <&DIE_NODE(ps_dispext0_fe)>; + apple,min-state = <4>; }; DIE_NODE(ps_dispext1_cpu0): power-controller@2a8 { @@ -405,6 +406,7 @@ #reset-cells = <0>; label = DIE_LABEL(dispext1_cpu0); power-domains = <&DIE_NODE(ps_dispext1_fe)>; + apple,min-state = <4>; }; DIE_NODE(ps_ane_sys_cpu): power-controller@2c8 { @@ -1792,6 +1794,7 @@ #reset-cells = <0>; label = DIE_LABEL(dispext2_cpu0); power-domains = <&DIE_NODE(ps_dispext2_fe)>; + apple,min-state = <4>; }; DIE_NODE(ps_dispext3_fe): power-controller@210 { @@ -1810,6 +1813,7 @@ #reset-cells = <0>; label = DIE_LABEL(dispext3_cpu0); power-domains = <&DIE_NODE(ps_dispext3_fe)>; + apple,min-state = <4>; }; DIE_NODE(ps_msr1): power-controller@250 { From 8f90332ea3786c23553ad7dcbeb9b259df8b0227 Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Fri, 30 Sep 2022 22:30:13 +0200 Subject: [PATCH 2162/3902] arm64: dts: apple: t8103: Add dcpext/dispext0 nodes Signed-off-by: Janne Grunau --- arch/arm64/boot/dts/apple/t8103.dtsi | 77 ++++++++++++++++++++++++++++ 1 file changed, 77 insertions(+) diff --git a/arch/arm64/boot/dts/apple/t8103.dtsi b/arch/arm64/boot/dts/apple/t8103.dtsi index 28d21e4b910483..36a39c0877c0e1 100644 --- a/arch/arm64/boot/dts/apple/t8103.dtsi +++ b/arch/arm64/boot/dts/apple/t8103.dtsi @@ -416,6 +416,14 @@ clock-output-names = "clk_disp0"; }; + /* Pixel clock? frequency in Hz (compare: 4K@60 VGA clock 533.250 MHz) */ + clk_dispext0: clock-dispext0 { + compatible = "fixed-clock"; + #clock-cells = <0>; + clock-frequency = <0>; + clock-output-names = "clk_dispext0"; + }; + /* * This is a fabulated representation of the input clock * to NCO since we don't know the true clock tree. @@ -667,6 +675,7 @@ display: display-subsystem { compatible = "apple,display-subsystem"; + /* disp_dart0 must be 1st since it is locked */ iommus = <&disp0_dart 0>; /* generate phandle explicitly for use in loader */ phandle = <&display>; @@ -1222,6 +1231,74 @@ ; }; + dispext0_dart: iommu@271304000 { + compatible = "apple,t8103-dart"; + reg = <0x2 0x71304000 0x0 0x4000>; + #iommu-cells = <1>; + interrupt-parent = <&aic>; + interrupts = ; + power-domains = <&ps_dispext_cpu0>; + apple,dma-range = <0x0 0x0 0x0 0xfc000000>; + status = "disabled"; + }; + + dcpext_dart: iommu@27130c000 { + compatible = "apple,t8103-dart"; + reg = <0x2 0x7130c000 0x0 0x4000>; + #iommu-cells = <1>; + interrupt-parent = <&aic>; + interrupts = ; + power-domains = <&ps_dispext_cpu0>; + apple,dma-range = <0xf 0x00000000 0x0 0xfc000000>; + status = "disabled"; + }; + + dcpext_mbox: mbox@271c08000 { + compatible = "apple,t8103-asc-mailbox", "apple,asc-mailbox-v4"; + reg = <0x2 0x71c08000 0x0 0x4000>; + interrupt-parent = <&aic>; + interrupts = , + , + , + ; + interrupt-names = "send-empty", "send-not-empty", + "recv-empty", "recv-not-empty"; + #mbox-cells = <0>; + power-domains = <&ps_dispext_cpu0>; + resets = <&ps_dispext_cpu0>; + status = "disabled"; + }; + + dcpext: dcp@271c00000 { + compatible = "apple,t8103-dcpext", "apple,dcpext"; + mboxes = <&dcpext_mbox>; + mbox-names = "mbox"; + iommus = <&dcpext_dart 0>; + phandle = <&dcpext>; + + reg-names = "coproc", "disp-0", "disp-1", "disp-2", + "disp-3", "disp-4"; + reg = <0x2 0x71c00000 0x0 0x4000>, + <0x2 0x70000000 0x0 0x118000>, + <0x2 0x71320000 0x0 0x4000>, + <0x2 0x71344000 0x0 0x4000>, + <0x2 0x71800000 0x0 0x800000>, + <0x2 0x3b3d0000 0x0 0x4000>; + apple,bw-scratch = <&pmgr_dcp 0 5 0x18>; + apple,bw-doorbell = <&pmgr_dcp 1 6>; + power-domains = <&ps_dispext_cpu0>; + resets = <&ps_dispext_cpu0>; + clocks = <&clk_dispext0>; + status = "disabled"; + // required bus properties for 'piodma' subdevice + #address-cells = <2>; + #size-cells = <2>; + + piodma { + iommus = <&dispext0_dart 4>; + }; + }; + ans_mbox: mbox@277408000 { compatible = "apple,t8103-asc-mailbox", "apple,asc-mailbox-v4"; reg = <0x2 0x77408000 0x0 0x4000>; From c2f95bb9e4ec56e56be4bdb6523cd71f5469e3ba Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Sat, 3 Dec 2022 22:12:25 +0100 Subject: [PATCH 2163/3902] arm64: dts: apple: t8112: Add dcpext/dispext0 nodes Signed-off-by: Janne Grunau --- arch/arm64/boot/dts/apple/t8112.dtsi | 75 ++++++++++++++++++++++++++++ 1 file changed, 75 insertions(+) diff --git a/arch/arm64/boot/dts/apple/t8112.dtsi b/arch/arm64/boot/dts/apple/t8112.dtsi index 1d2c231458c5e8..37033f1006b657 100644 --- a/arch/arm64/boot/dts/apple/t8112.dtsi +++ b/arch/arm64/boot/dts/apple/t8112.dtsi @@ -454,6 +454,14 @@ clock-output-names = "clk_disp0"; }; + /* Pixel clock? frequency in Hz (compare: 4K@60 VGA clock 533.250 MHz) */ + clk_dispext0: clock-dispext0 { + compatible = "fixed-clock"; + #clock-cells = <0>; + clock-frequency = <0>; + clock-output-names = "clk_dispext0"; + }; + reserved-memory { #address-cells = <2>; #size-cells = <2>; @@ -1399,6 +1407,73 @@ }; + dispext0_dart: iommu@271304000 { + compatible = "apple,t8112-dart", "apple,t8110-dart"; + reg = <0x2 0x71304000 0x0 0x4000>; + #iommu-cells = <1>; + apple,dma-range = <0x0 0x0 0xf 0xffff0000>; + interrupt-parent = <&aic>; + interrupts = ; + power-domains = <&ps_dispext_cpu0>; + status = "disabled"; + }; + + dcpext_dart: iommu@27130c000 { + compatible = "apple,t8112-dart", "apple,t8110-dart"; + reg = <0x2 0x7130c000 0x0 0x4000>; + #iommu-cells = <1>; + apple,dma-range = <0x8 0x0 0x7 0xffff0000>; + interrupt-parent = <&aic>; + interrupts = ; + power-domains = <&ps_dispext_cpu0>; + status = "disabled"; + }; + + dcpext_mbox: mbox@271c08000 { + compatible = "apple,t8112-asc-mailbox", "apple,asc-mailbox-v4"; + reg = <0x2 0x71c08000 0x0 0x4000>; + interrupt-parent = <&aic>; + interrupts = , + , + , + ; + interrupt-names = "send-empty", "send-not-empty", + "recv-empty", "recv-not-empty"; + #mbox-cells = <0>; + power-domains = <&ps_dispext_cpu0>; + resets = <&ps_dispext_cpu0>; + status = "disabled"; + }; + + dcpext: dcp@271c00000 { + compatible = "apple,t8112-dcpext", "apple,dcpext"; + mboxes = <&dcpext_mbox>; + mbox-names = "mbox"; + iommus = <&dcpext_dart 5>; + phandle = <&dcpext>; + + /* the ADT has 2 additional regs which seems to be unused */ + reg-names = "coproc", "disp-0", "disp-1", "disp-2", "disp-3"; + reg = <0x2 0x71c00000 0x0 0x4000>, + <0x2 0x70000000 0x0 0x61C000>, + <0x2 0x71320000 0x0 0x4000>, + <0x2 0x71344000 0x0 0x4000>, + <0x2 0x71800000 0x0 0x800000>; + apple,bw-scratch = <&pmgr_dcp 0 4 0x5e0>; + power-domains = <&ps_dispext_cpu0>; + resets = <&ps_dispext_cpu0>; + clocks = <&clk_dispext0>; + apple,dcp-index = <1>; + status = "disabled"; + // required bus properties for 'piodma' subdevice + #address-cells = <2>; + #size-cells = <2>; + + piodma { + iommus = <&dispext0_dart 4>; + }; + }; + ans_mbox: mbox@277408000 { compatible = "apple,t8112-asc-mailbox", "apple,asc-mailbox-v4"; reg = <0x2 0x77408000 0x0 0x4000>; From 79a3b4ace135e8091dc1605ddbaa91386703d554 Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Thu, 20 Oct 2022 20:44:02 +0200 Subject: [PATCH 2164/3902] arm64: dts: apple: t600x: Add t6000 dispext device nodes While thunderbolt and DP-altmode are not working 2 dispext/dcpext devices are enough. "dispext0" will be used for the HDMI output and dispext1 can be used for DP-altmopde experiments. All nodes are disabled and have be enabled explicitly in device .dts or .dtsi. Signed-off-by: Janne Grunau --- arch/arm64/boot/dts/apple/t6002.dtsi | 10 ++ arch/arm64/boot/dts/apple/t600x-common.dtsi | 28 +++++ arch/arm64/boot/dts/apple/t600x-dieX.dtsi | 132 ++++++++++++++++++++ 3 files changed, 170 insertions(+) diff --git a/arch/arm64/boot/dts/apple/t6002.dtsi b/arch/arm64/boot/dts/apple/t6002.dtsi index 04265fa3ea1ec1..9bf333e0cf2d66 100644 --- a/arch/arm64/boot/dts/apple/t6002.dtsi +++ b/arch/arm64/boot/dts/apple/t6002.dtsi @@ -305,6 +305,16 @@ }; }; +&dcpext0_die1 { + // TODO: verify + apple,bw-scratch = <&pmgr_dcp 0 4 0x9c0>; +}; + +&dcpext1_die1 { + // TODO: verify + apple,bw-scratch = <&pmgr_dcp 0 4 0x9c8>; +}; + &ps_gfx { // On t6002, the die0 GPU power domain needs both AFR power domains power-domains = <&ps_afr>, <&ps_afr_die1>; diff --git a/arch/arm64/boot/dts/apple/t600x-common.dtsi b/arch/arm64/boot/dts/apple/t600x-common.dtsi index 58a535fd707d4d..f37feaea4c2191 100644 --- a/arch/arm64/boot/dts/apple/t600x-common.dtsi +++ b/arch/arm64/boot/dts/apple/t600x-common.dtsi @@ -441,6 +441,34 @@ clock-frequency = <237333328>; clock-output-names = "clk_disp0"; }; + + clk_dispext0: clock-dispext0 { + compatible = "fixed-clock"; + #clock-cells = <0>; + clock-frequency = <0>; + clock-output-names = "clk_dispext0"; + }; + + clk_dispext0_die1: clock-dispext0_die1 { + compatible = "fixed-clock"; + #clock-cells = <0>; + clock-frequency = <0>; + clock-output-names = "clk_dispext0_die1"; + }; + + clk_dispext1: clock-dispext1 { + compatible = "fixed-clock"; + #clock-cells = <0>; + clock-frequency = <0>; + clock-output-names = "clk_dispext1"; + }; + + clk_dispext1_die1: clock-dispext1_die1 { + compatible = "fixed-clock"; + #clock-cells = <0>; + clock-frequency = <0>; + clock-output-names = "clk_dispext1_die1"; + }; /* * This is a fabulated representation of the input clock * to NCO since we don't know the true clock tree. diff --git a/arch/arm64/boot/dts/apple/t600x-dieX.dtsi b/arch/arm64/boot/dts/apple/t600x-dieX.dtsi index cd64fe0405d0b6..67bd445fa9798d 100644 --- a/arch/arm64/boot/dts/apple/t600x-dieX.dtsi +++ b/arch/arm64/boot/dts/apple/t600x-dieX.dtsi @@ -24,6 +24,138 @@ #performance-domain-cells = <0>; }; + DIE_NODE(dispext0_dart): iommu@289304000 { + compatible = "apple,t6000-dart"; + reg = <0x2 0x89304000 0x0 0x4000>; + #iommu-cells = <1>; + interrupt-parent = <&aic>; + interrupts = ; + power-domains = <&DIE_NODE(ps_dispext0_cpu0)>; + apple,dma-range = <0x0 0x0 0x0 0xfc000000>; + status = "disabled"; + }; + + DIE_NODE(dcpext0_dart): iommu@28930c000 { + compatible = "apple,t6000-dart"; + reg = <0x2 0x8930c000 0x0 0x4000>; + #iommu-cells = <1>; + interrupt-parent = <&aic>; + interrupts = ; + power-domains = <&DIE_NODE(ps_dispext0_cpu0)>; + apple,dma-range = <0x1f0 0x0 0x0 0xfc000000>; + status = "disabled"; + }; + + DIE_NODE(dcpext0_mbox): mbox@289c08000 { + compatible = "apple,t6000-asc-mailbox", "apple,asc-mailbox-v4"; + reg = <0x2 0x89c08000 0x0 0x4000>; + interrupt-parent = <&aic>; + interrupts = , + , + , + ; + interrupt-names = "send-empty", "send-not-empty", + "recv-empty", "recv-not-empty"; + #mbox-cells = <0>; + power-domains = <&DIE_NODE(ps_dispext0_cpu0)>; + resets = <&DIE_NODE(ps_dispext0_cpu0)>; + status = "disabled"; + }; + + DIE_NODE(dcpext0): dcp@289c00000 { + compatible = "apple,t6000-dcpext", "apple,dcpext"; + mboxes = <&DIE_NODE(dcpext0_mbox)>; + mbox-names = "mbox"; + iommus = <&DIE_NODE(dcpext0_dart) 0>; + + reg-names = "coproc", "disp-0", "disp-1", "disp-2", "disp-3"; + reg = <0x2 0x89c00000 0x0 0x4000>, + <0x2 0x88000000 0x0 0x3000000>, + <0x2 0x89320000 0x0 0x4000>, + <0x2 0x89344000 0x0 0x4000>, + <0x2 0x89800000 0x0 0x800000>; + apple,bw-scratch = <&pmgr_dcp 0 4 0x990>; + power-domains = <&DIE_NODE(ps_dispext0_cpu0)>; + resets = <&DIE_NODE(ps_dispext0_cpu0)>; + clocks = <&DIE_NODE(clk_dispext0)>; + phandle = <&DIE_NODE(dcpext0)>; + apple,dcp-index = <1>; + status = "disabled"; + // required bus properties for 'piodma' subdevice + #address-cells = <2>; + #size-cells = <2>; + + piodma { + iommus = <&DIE_NODE(dispext0_dart) 4>; + }; + }; + + DIE_NODE(dispext1_dart): iommu@28c304000 { + compatible = "apple,t6000-dart", "apple,t8110-dart"; + reg = <0x2 0x8c304000 0x0 0x4000>; + #iommu-cells = <1>; + interrupt-parent = <&aic>; + interrupts = ; + power-domains = <&DIE_NODE(ps_dispext1_cpu0)>; + apple,dma-range = <0x0 0x0 0x0 0xfc000000>; + status = "disabled"; + }; + + DIE_NODE(dcpext1_dart): iommu@28c30c000 { + compatible = "apple,t6000-dart", "apple,t8110-dart"; + reg = <0x2 0x8c30c000 0x0 0x4000>; + #iommu-cells = <1>; + interrupt-parent = <&aic>; + interrupts = ; + power-domains = <&DIE_NODE(ps_dispext1_cpu0)>; + apple,dma-range = <0x1f0 0x0 0x0 0xfc000000>; + status = "disabled"; + }; + + DIE_NODE(dcpext1_mbox): mbox@28cc08000 { + compatible = "apple,t6000-asc-mailbox", "apple,asc-mailbox-v4"; + reg = <0x2 0x8cc08000 0x0 0x4000>; + interrupt-parent = <&aic>; + interrupts = , + , + , + ; + interrupt-names = "send-empty", "send-not-empty", + "recv-empty", "recv-not-empty"; + #mbox-cells = <0>; + power-domains = <&DIE_NODE(ps_dispext1_cpu0)>; + resets = <&DIE_NODE(ps_dispext1_cpu0)>; + status = "disabled"; + }; + + DIE_NODE(dcpext1): dcp@28cc00000 { + compatible = "apple,t6000-dcpext", "apple,dcpext"; + mboxes = <&DIE_NODE(dcpext1_mbox)>; + mbox-names = "mbox"; + iommus = <&DIE_NODE(dcpext1_dart) 0>; + + reg-names = "coproc", "disp-0", "disp-1", "disp-2", "disp-3"; + reg = <0x2 0x8cc00000 0x0 0x4000>, + <0x2 0x8b000000 0x0 0x3000000>, + <0x2 0x8c320000 0x0 0x4000>, + <0x2 0x8c344000 0x0 0x4000>, + <0x2 0x8c800000 0x0 0x800000>; + apple,bw-scratch = <&pmgr_dcp 0 4 0x998>; + power-domains = <&DIE_NODE(ps_dispext1_cpu0)>; + resets = <&DIE_NODE(ps_dispext1_cpu0)>; + clocks = <&DIE_NODE(clk_dispext1)>; + phandle = <&DIE_NODE(dcpext1)>; + apple,dcp-index = <2>; + status = "disabled"; + // required bus properties for 'piodma' subdevice + #address-cells = <2>; + #size-cells = <2>; + + piodma { + iommus = <&DIE_NODE(dispext1_dart) 4>; + }; + }; + DIE_NODE(pmgr): power-management@28e080000 { compatible = "apple,t6000-pmgr", "apple,pmgr", "syscon", "simple-mfd"; #address-cells = <1>; From d1b7930a56a0a1ae224d1fa5ea8c194716e58a13 Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Thu, 17 Aug 2023 19:45:46 +0200 Subject: [PATCH 2165/3902] arm64: dts: apple: t602x: Add t6020 dispext device nodes While thunderbolt and DP-altmode are not working 2 dispext/dcpext devices are enough. "dispext0" will be used for the HDMI output and dispext1 can be used for DP-altmopde experiments. All nodes are disabled and have be enabled explicitly in device .dts or .dtsi. Signed-off-by: Janne Grunau --- arch/arm64/boot/dts/apple/t6022.dtsi | 8 ++ arch/arm64/boot/dts/apple/t602x-common.dtsi | 28 +++++ arch/arm64/boot/dts/apple/t602x-dieX.dtsi | 132 ++++++++++++++++++++ 3 files changed, 168 insertions(+) diff --git a/arch/arm64/boot/dts/apple/t6022.dtsi b/arch/arm64/boot/dts/apple/t6022.dtsi index b7d13dafc7a265..f17c9a4f59e482 100644 --- a/arch/arm64/boot/dts/apple/t6022.dtsi +++ b/arch/arm64/boot/dts/apple/t6022.dtsi @@ -346,6 +346,14 @@ }; }; +&dcpext0_die1 { + apple,bw-scratch = <&pmgr_dcp 0 4 0x1240>; +}; + +&dcpext1_die1 { + apple,bw-scratch = <&pmgr_dcp 0 4 0x1248>; +}; + &ps_gfx { // On t6022, the die0 GPU power domain needs both AFR power domains power-domains = <&ps_afr>, <&ps_afr_die1>; diff --git a/arch/arm64/boot/dts/apple/t602x-common.dtsi b/arch/arm64/boot/dts/apple/t602x-common.dtsi index 3eeb5139fcde05..fe888cbb81e475 100644 --- a/arch/arm64/boot/dts/apple/t602x-common.dtsi +++ b/arch/arm64/boot/dts/apple/t602x-common.dtsi @@ -558,6 +558,34 @@ clock-output-names = "clk_disp0"; }; + clk_dispext0: clock-dispext0 { + compatible = "fixed-clock"; + #clock-cells = <0>; + clock-frequency = <0>; + clock-output-names = "clk_dispext0"; + }; + + clk_dispext0_die1: clock-dispext0_die1 { + compatible = "fixed-clock"; + #clock-cells = <0>; + clock-frequency = <0>; + clock-output-names = "clk_dispext0_die1"; + }; + + clk_dispext1: clock-dispext1 { + compatible = "fixed-clock"; + #clock-cells = <0>; + clock-frequency = <0>; + clock-output-names = "clk_dispext1"; + }; + + clk_dispext1_die1: clock-dispext1_die1 { + compatible = "fixed-clock"; + #clock-cells = <0>; + clock-frequency = <0>; + clock-output-names = "clk_dispext1_die1"; + }; + /* * This is a fabulated representation of the input clock * to NCO since we don't know the true clock tree. diff --git a/arch/arm64/boot/dts/apple/t602x-dieX.dtsi b/arch/arm64/boot/dts/apple/t602x-dieX.dtsi index be6be79ca1d370..459efc80193db9 100644 --- a/arch/arm64/boot/dts/apple/t602x-dieX.dtsi +++ b/arch/arm64/boot/dts/apple/t602x-dieX.dtsi @@ -23,6 +23,72 @@ #performance-domain-cells = <0>; }; + DIE_NODE(dispext0_dart): iommu@289304000 { + compatible = "apple,t6020-dart", "apple,t8110-dart"; + reg = <0x2 0x89304000 0x0 0x4000>; + #iommu-cells = <1>; + interrupt-parent = <&aic>; + interrupts = ; + power-domains = <&DIE_NODE(ps_dispext0_cpu0)>; + apple,dma-range = <0x100 0x0 0x10 0x0>; + status = "disabled"; + }; + + DIE_NODE(dcpext0_dart): iommu@28930c000 { + compatible = "apple,t6020-dart", "apple,t8110-dart"; + reg = <0x2 0x8930c000 0x0 0x4000>; + #iommu-cells = <1>; + interrupt-parent = <&aic>; + interrupts = ; + power-domains = <&DIE_NODE(ps_dispext0_cpu0)>; + apple,dma-range = <0x100 0x0 0x10 0x0>; + status = "disabled"; + }; + + DIE_NODE(dcpext0_mbox): mbox@289c08000 { + compatible = "apple,t6020-asc-mailbox", "apple,asc-mailbox-v4"; + reg = <0x2 0x89c08000 0x0 0x4000>; + interrupt-parent = <&aic>; + interrupts = , + , + , + ; + interrupt-names = "send-empty", "send-not-empty", + "recv-empty", "recv-not-empty"; + #mbox-cells = <0>; + power-domains = <&DIE_NODE(ps_dispext0_cpu0)>; + resets = <&DIE_NODE(ps_dispext0_cpu0)>; + status = "disabled"; + }; + + DIE_NODE(dcpext0): dcp@289c00000 { + compatible = "apple,t6020-dcpext", "apple,dcpext"; + mboxes = <&DIE_NODE(dcpext0_mbox)>; + mbox-names = "mbox"; + iommus = <&DIE_NODE(dcpext0_dart) 5>; + + reg-names = "coproc", "disp-0", "disp-1", "disp-2", "disp-3"; + reg = <0x2 0x89c00000 0x0 0x4000>, + <0x2 0x88000000 0x0 0x4000000>, + <0x2 0x89320000 0x0 0x4000>, + <0x2 0x89344000 0x0 0x4000>, + <0x2 0x89800000 0x0 0x800000>; + apple,bw-scratch = <&pmgr_dcp 0 4 0x1210>; + power-domains = <&DIE_NODE(ps_dispext0_cpu0)>; + resets = <&DIE_NODE(ps_dispext0_cpu0)>; + clocks = <&DIE_NODE(clk_dispext0)>; + phandle = <&DIE_NODE(dcpext0)>; + apple,dcp-index = <1>; + status = "disabled"; + // required bus properties for 'piodma' subdevice + #address-cells = <2>; + #size-cells = <2>; + + piodma { + iommus = <&DIE_NODE(dispext0_dart) 4>; + }; + }; + DIE_NODE(pmgr): power-management@28e080000 { compatible = "apple,t6020-pmgr", "apple,t8103-pmgr", "syscon", "simple-mfd"; #address-cells = <1>; @@ -101,6 +167,72 @@ ; }; + DIE_NODE(dispext1_dart): iommu@315304000 { + compatible = "apple,t6020-dart", "apple,t8110-dart"; + reg = <0x3 0x15304000 0x0 0x4000>; + #iommu-cells = <1>; + interrupt-parent = <&aic>; + interrupts = ; + power-domains = <&DIE_NODE(ps_dispext1_cpu0)>; + apple,dma-range = <0x100 0x0 0x10 0x0>; + status = "disabled"; + }; + + DIE_NODE(dcpext1_dart): iommu@31530c000 { + compatible = "apple,t6020-dart", "apple,t8110-dart"; + reg = <0x3 0x1530c000 0x0 0x4000>; + #iommu-cells = <1>; + interrupt-parent = <&aic>; + interrupts = ; + power-domains = <&DIE_NODE(ps_dispext1_cpu0)>; + apple,dma-range = <0x100 0x0 0x10 0x0>; + status = "disabled"; + }; + + DIE_NODE(dcpext1_mbox): mbox@315c08000 { + compatible = "apple,t6020-asc-mailbox", "apple,asc-mailbox-v4"; + reg = <0x3 0x15c08000 0x0 0x4000>; + interrupt-parent = <&aic>; + interrupts = , + , + , + ; + interrupt-names = "send-empty", "send-not-empty", + "recv-empty", "recv-not-empty"; + #mbox-cells = <0>; + power-domains = <&DIE_NODE(ps_dispext1_cpu0)>; + resets = <&DIE_NODE(ps_dispext1_cpu0)>; + status = "disabled"; + }; + + DIE_NODE(dcpext1): dcp@315c00000 { + compatible = "apple,t6020-dcpext", "apple,dcpext"; + mboxes = <&DIE_NODE(dcpext1_mbox)>; + mbox-names = "mbox"; + iommus = <&DIE_NODE(dcpext1_dart) 5>; + + reg-names = "coproc", "disp-0", "disp-1", "disp-2", "disp-3"; + reg = <0x3 0x15c00000 0x0 0x4000>, + <0x3 0x14000000 0x0 0x4000000>, + <0x3 0x15320000 0x0 0x4000>, + <0x3 0x15344000 0x0 0x4000>, + <0x3 0x15800000 0x0 0x800000>; + apple,bw-scratch = <&pmgr_dcp 0 4 0x1218>; + power-domains = <&DIE_NODE(ps_dispext1_cpu0)>; + resets = <&DIE_NODE(ps_dispext1_cpu0)>; + clocks = <&DIE_NODE(clk_dispext1)>; + phandle = <&DIE_NODE(dcpext1)>; + apple,dcp-index = <2>; + status = "disabled"; + // required bus properties for 'piodma' subdevice + #address-cells = <2>; + #size-cells = <2>; + + piodma { + iommus = <&DIE_NODE(dispext1_dart) 4>; + }; + }; + DIE_NODE(pinctrl_ap): pinctrl@39b028000 { compatible = "apple,t6020-pinctrl", "apple,t8103-pinctrl", "apple,pinctrl"; reg = <0x3 0x9b028000 0x0 0x4000>; From fbf9ae8cb833a120b02b0e34c03e7cd678682fff Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Sat, 28 Oct 2023 23:12:33 +0200 Subject: [PATCH 2166/3902] arm64: dts: apple: t8112: Add dptx-phy node On M2 desktop devices more parts of the HDMI output pipeline are under the OS' control. One of this parts is the primary DPTX phy which drives the the HDMI port through an integrated MCDP29XX DP to HDMI converter. Signed-off-by: Janne Grunau --- arch/arm64/boot/dts/apple/t8112.dtsi | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/arch/arm64/boot/dts/apple/t8112.dtsi b/arch/arm64/boot/dts/apple/t8112.dtsi index 37033f1006b657..440d46e3d2b05f 100644 --- a/arch/arm64/boot/dts/apple/t8112.dtsi +++ b/arch/arm64/boot/dts/apple/t8112.dtsi @@ -1058,6 +1058,17 @@ }; }; + dptxphy: phy@23c500000 { + compatible = "apple,t8112-dptx-phy", "apple,dptx-phy"; + reg = <0x2 0x3c500000 0x0 0x4000>, + <0x2 0x3c540000 0x0 0xc000>; + reg-names = "core", "dptx"; + power-domains = <&ps_dptx_ext_phy>; + #phy-cells = <0>; + #reset-cells = <0>; + status = "disabled"; /* only used on j473 */ + }; + pinctrl_nub: pinctrl@23d1f0000 { compatible = "apple,t8112-pinctrl", "apple,pinctrl"; reg = <0x2 0x3d1f0000 0x0 0x4000>; From b90d1af95a2202d54c5cecb032bf13051d99e41e Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Sun, 29 Oct 2023 10:39:17 +0100 Subject: [PATCH 2167/3902] arm64: dts: apple: t602x: Add lpdptx-phy node On M2 desktop devices more parts of the HDMI output pipeline are under the OS' control. One of this parts is the primary DPTX phy which drives the the HDMI port through an integrated MCDP29XX DP to HDMI converter. Signed-off-by: Janne Grunau --- arch/arm64/boot/dts/apple/t602x-dieX.dtsi | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/arch/arm64/boot/dts/apple/t602x-dieX.dtsi b/arch/arm64/boot/dts/apple/t602x-dieX.dtsi index 459efc80193db9..fd93971e32161f 100644 --- a/arch/arm64/boot/dts/apple/t602x-dieX.dtsi +++ b/arch/arm64/boot/dts/apple/t602x-dieX.dtsi @@ -258,6 +258,17 @@ #interrupt-cells = <2>; }; + DIE_NODE(lpdptxphy): phy@39c000000 { + compatible = "apple,t6020-dptx-phy", "apple,dptx-phy"; + reg = <0x3 0x9c000000 0x0 0x4000>, + <0x3 0x9c040000 0x0 0xc000>; + reg-names = "core", "dptx"; + power-domains = <&DIE_NODE(ps_dptx_phy_ps)>; + #phy-cells = <0>; + #reset-cells = <0>; + status = "disabled"; /* only exposed on desktop devices */ + }; + DIE_NODE(pmgr_gfx): power-management@404e80000 { compatible = "apple,t6020-pmgr", "apple,t8103-pmgr", "syscon", "simple-mfd"; #address-cells = <1>; From 3e0101f294316db1056fb8cef06793026c6127e0 Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Mon, 6 Nov 2023 00:35:54 +0100 Subject: [PATCH 2168/3902] arm64: dts: apple: t600x: Add device nodes for atc DP crossbar Signed-off-by: Janne Grunau --- arch/arm64/boot/dts/apple/t6002-j375d.dts | 2 ++ arch/arm64/boot/dts/apple/t600x-dieX.dtsi | 32 +++++++++++++++++++++++ 2 files changed, 34 insertions(+) diff --git a/arch/arm64/boot/dts/apple/t6002-j375d.dts b/arch/arm64/boot/dts/apple/t6002-j375d.dts index c04597225b6ade..9d61a601c6bf18 100644 --- a/arch/arm64/boot/dts/apple/t6002-j375d.dts +++ b/arch/arm64/boot/dts/apple/t6002-j375d.dts @@ -195,11 +195,13 @@ /delete-node/ &dwc3_2_dart_1_die1; /delete-node/ &dwc3_2_die1; /delete-node/ &atcphy2_die1; +/delete-node/ &atcphy2_xbar_die1; /delete-node/ &dwc3_3_dart_0_die1; /delete-node/ &dwc3_3_dart_1_die1; /delete-node/ &dwc3_3_die1; /delete-node/ &atcphy3_die1; +/delete-node/ &atcphy3_xbar_die1; /* delete unused always-on power-domains on die 1 */ diff --git a/arch/arm64/boot/dts/apple/t600x-dieX.dtsi b/arch/arm64/boot/dts/apple/t600x-dieX.dtsi index 67bd445fa9798d..7d1e8036e27d25 100644 --- a/arch/arm64/boot/dts/apple/t600x-dieX.dtsi +++ b/arch/arm64/boot/dts/apple/t600x-dieX.dtsi @@ -511,6 +511,14 @@ power-domains = <&DIE_NODE(ps_atc0_usb)>; }; + DIE_NODE(atcphy0_xbar): mux@70304c000 { + compatible = "apple,t6000-display-crossbar"; + reg = <0x7 0x0304c000 0x0 0x4000>; + #mux-control-cells = <1>; + power-domains = <&DIE_NODE(ps_atc0_usb)>; + status = "disabled"; + }; + DIE_NODE(dwc3_1): usb@b02280000 { compatible = "apple,t6000-dwc3", "apple,t8103-dwc3"; reg = <0xb 0x02280000 0x0 0xcd00>, <0xb 0x0228cd00 0x0 0x3200>; @@ -583,6 +591,14 @@ power-domains = <&DIE_NODE(ps_atc1_usb)>; }; + DIE_NODE(atcphy1_xbar): mux@b0304c000 { + compatible = "apple,t6000-display-crossbar"; + reg = <0xb 0x0304c000 0x0 0x4000>; + #mux-control-cells = <1>; + power-domains = <&DIE_NODE(ps_atc1_usb)>; + status = "disabled"; + }; + DIE_NODE(dwc3_2): usb@f02280000 { compatible = "apple,t6000-dwc3", "apple,t8103-dwc3"; reg = <0xf 0x02280000 0x0 0xcd00>, <0xf 0x0228cd00 0x0 0x3200>; @@ -655,6 +671,14 @@ power-domains = <&DIE_NODE(ps_atc2_usb)>; }; + DIE_NODE(atcphy2_xbar): mux@f0304c000 { + compatible = "apple,t6000-display-crossbar"; + reg = <0xf 0x0304c000 0x0 0x4000>; + #mux-control-cells = <1>; + power-domains = <&DIE_NODE(ps_atc2_usb)>; + status = "disabled"; + }; + DIE_NODE(dwc3_3): usb@1302280000 { compatible = "apple,t6000-dwc3", "apple,t8103-dwc3"; reg = <0x13 0x02280000 0x0 0xcd00>, <0x13 0x0228cd00 0x0 0x3200>; @@ -726,3 +750,11 @@ mode-switch; power-domains = <&DIE_NODE(ps_atc3_usb)>; }; + + DIE_NODE(atcphy3_xbar): mux@130304c000 { + compatible = "apple,t6000-display-crossbar"; + reg = <0x13 0x0304c000 0x0 0x4000>; + #mux-control-cells = <1>; + power-domains = <&DIE_NODE(ps_atc3_usb)>; + status = "disabled"; + }; From e6299b6cec28b7cdec3d4f9398f6802e3739aa2d Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Thu, 17 Aug 2023 23:17:13 +0200 Subject: [PATCH 2169/3902] arm64: dts: apple: t602x: Add device nodes for atc DP crossbar Signed-off-by: Janne Grunau --- arch/arm64/boot/dts/apple/t6022-j475d.dts | 2 ++ arch/arm64/boot/dts/apple/t602x-dieX.dtsi | 32 +++++++++++++++++++++++ 2 files changed, 34 insertions(+) diff --git a/arch/arm64/boot/dts/apple/t6022-j475d.dts b/arch/arm64/boot/dts/apple/t6022-j475d.dts index 141c8497b8890b..227c574be91981 100644 --- a/arch/arm64/boot/dts/apple/t6022-j475d.dts +++ b/arch/arm64/boot/dts/apple/t6022-j475d.dts @@ -52,11 +52,13 @@ /delete-node/ &dwc3_2_dart_1_die1; /delete-node/ &dwc3_2_die1; /delete-node/ &atcphy2_die1; +/delete-node/ &atcphy2_xbar_die1; /delete-node/ &dwc3_3_dart_0_die1; /delete-node/ &dwc3_3_dart_1_die1; /delete-node/ &dwc3_3_die1; /delete-node/ &atcphy3_die1; +/delete-node/ &atcphy3_xbar_die1; /* delete unused always-on power-domains on die 1 */ /delete-node/ &ps_atc2_usb_aon_die1; diff --git a/arch/arm64/boot/dts/apple/t602x-dieX.dtsi b/arch/arm64/boot/dts/apple/t602x-dieX.dtsi index fd93971e32161f..c28747bf56915b 100644 --- a/arch/arm64/boot/dts/apple/t602x-dieX.dtsi +++ b/arch/arm64/boot/dts/apple/t602x-dieX.dtsi @@ -330,6 +330,14 @@ power-domains = <&DIE_NODE(ps_atc0_usb)>; }; + DIE_NODE(atcphy0_xbar): mux@70304c000 { + compatible = "apple,t6020-display-crossbar"; + reg = <0x7 0x0304c000 0x0 0x4000>; + #mux-control-cells = <1>; + power-domains = <&DIE_NODE(ps_atc0_usb)>; + status = "disabled"; + }; + DIE_NODE(dwc3_1): usb@b02280000 { compatible = "apple,t6020-dwc3", "apple,t8103-dwc3"; reg = <0xb 0x02280000 0x0 0xcd00>, <0xb 0x0228cd00 0x0 0x3200>; @@ -383,6 +391,14 @@ power-domains = <&DIE_NODE(ps_atc1_usb)>; }; + DIE_NODE(atcphy1_xbar): mux@b0304c000 { + compatible = "apple,t6020-display-crossbar"; + reg = <0xb 0x0304c000 0x0 0x4000>; + #mux-control-cells = <1>; + power-domains = <&DIE_NODE(ps_atc1_usb)>; + status = "disabled"; + }; + DIE_NODE(dwc3_2): usb@f02280000 { compatible = "apple,t6020-dwc3", "apple,t8103-dwc3"; reg = <0xf 0x02280000 0x0 0xcd00>, <0xf 0x0228cd00 0x0 0x3200>; @@ -436,6 +452,14 @@ power-domains = <&DIE_NODE(ps_atc2_usb)>; }; + DIE_NODE(atcphy2_xbar): mux@f0304c000 { + compatible = "apple,t6020-display-crossbar"; + reg = <0xf 0x0304c000 0x0 0x4000>; + #mux-control-cells = <1>; + power-domains = <&DIE_NODE(ps_atc2_usb)>; + status = "disabled"; + }; + DIE_NODE(dwc3_3): usb@1302280000 { compatible = "apple,t6020-dwc3", "apple,t8103-dwc3"; reg = <0x13 0x02280000 0x0 0xcd00>, <0x13 0x0228cd00 0x0 0x3200>; @@ -488,3 +512,11 @@ mode-switch; power-domains = <&DIE_NODE(ps_atc3_usb)>; }; + + DIE_NODE(atcphy3_xbar): mux@130304c000 { + compatible = "apple,t6020-display-crossbar"; + reg = <0x13 0x0304c000 0x0 0x4000>; + #mux-control-cells = <1>; + power-domains = <&DIE_NODE(ps_atc3_usb)>; + status = "disabled"; + }; From d892a450d4a5569480d4a842d47f8c192d4922d7 Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Sat, 28 Oct 2023 23:40:47 +0200 Subject: [PATCH 2170/3902] arm64: dts: apple: t8112-j473: Enable dcpext0/dptx-phy/dp2hdmi After all parts are in place enable DCP on the M2 Mac Mini for HDMI output. Use dcpext for HDMI out dcp on t8112 and t602x does not wake up after sleep + reset but dcpext* does. Use dcpext0 for sharing the code with M1* devices. My interpretation of the tea leaves from Apple's marketing department suggests that dcpext is more capable (6k 60Hz vs 5k 60Hz) so use dcpext as long as only one is used. Signed-off-by: Janne Grunau --- arch/arm64/boot/dts/apple/t8112-j473.dts | 36 ++++++++++++++++++++++-- 1 file changed, 34 insertions(+), 2 deletions(-) diff --git a/arch/arm64/boot/dts/apple/t8112-j473.dts b/arch/arm64/boot/dts/apple/t8112-j473.dts index cf24579ca7b325..62e0fab7dd05d5 100644 --- a/arch/arm64/boot/dts/apple/t8112-j473.dts +++ b/arch/arm64/boot/dts/apple/t8112-j473.dts @@ -19,20 +19,52 @@ aliases { bluetooth0 = &bluetooth0; + /delete-property/ dcp; + dcpext = &dcpext; ethernet0 = ðernet0; wifi0 = &wifi0; }; }; &framebuffer0 { - power-domains = <&ps_disp0_cpu0>, <&ps_dptx_ext_phy>; + power-domains = <&ps_dispext_cpu0>, <&ps_dptx_ext_phy>; +}; + +&dptxphy { + status = "okay"; }; -/* disable dcp until it is supported */ &dcp { status = "disabled"; }; +&display { + iommus = <&dispext0_dart 0>; +}; +&dispext0_dart { + status = "okay"; +}; +&dcpext_dart { + status = "okay"; +}; +&dcpext_mbox { + status = "okay"; +}; +&dcpext { + status = "okay"; + apple,connector-type = "HDMI-A"; + + /* HDMI HPD gpio, used as interrupt*/ + hdmi-hpd-gpios = <&pinctrl_aop 49 GPIO_ACTIVE_HIGH>; + + hdmi-pwren-gpios = <&smc_gpio 21 GPIO_ACTIVE_HIGH>; + dp2hdmi-pwren-gpios = <&smc_gpio 22 GPIO_ACTIVE_HIGH>; + + phys = <&dptxphy>; + phy-names = "dp-phy"; + apple,dptx-phy = <5>; +}; + /* * Keep the power-domains used for the HDMI port on. */ From 5bfae3a7ee5a40655e27728eb9a62afd62fc1bc0 Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Mon, 6 Nov 2023 21:47:19 +0100 Subject: [PATCH 2171/3902] arm64: dts: apple: t6020-j474,t6021-j475: Enable dcpext0/dptx-phy/dp2hdmi After all parts are in place enable the DCP on the M2 Pro Mac Mini and the M2 Max Mac Studio for HDMI output. Use dcpext0 for HDMI out. dcp on t8112 and t602x does not wake up after sleep + reset but dcpext* does. Use dcpext0 for sharing the code with M1* devices. My interpretation of the tea leaves from Apple's marketing department suggests that dcpext is more capable (6k 60Hz vs 5k 60Hz) so use dcpext as long as only one is used. Signed-off-by: Janne Grunau --- arch/arm64/boot/dts/apple/t6020-j474s.dts | 51 ++++++++++++++++++ arch/arm64/boot/dts/apple/t6021-j475c.dts | 52 +++++++++++++++++++ .../arm64/boot/dts/apple/t602x-j474-j475.dtsi | 5 -- 3 files changed, 103 insertions(+), 5 deletions(-) diff --git a/arch/arm64/boot/dts/apple/t6020-j474s.dts b/arch/arm64/boot/dts/apple/t6020-j474s.dts index 17c72b0bb87721..89b6c46a036eca 100644 --- a/arch/arm64/boot/dts/apple/t6020-j474s.dts +++ b/arch/arm64/boot/dts/apple/t6020-j474s.dts @@ -63,6 +63,57 @@ model = "Mac mini J474"; }; +&lpdptxphy { + status = "okay"; +}; + +#define USE_DCPEXT0 1 + +#if USE_DCPEXT0 +/ { + aliases { + dcpext0 = &dcpext0; + /delete-property/ dcp; + }; +}; + +&framebuffer0 { + power-domains = <&ps_dispext0_cpu0>, <&ps_dptx_phy_ps>; +}; + +&dcp { + status = "disabled"; +}; +&display { + iommus = <&dispext0_dart 0>; +}; +&dispext0_dart { + status = "okay"; +}; +&dcpext0_dart { + status = "okay"; +}; +&dcpext0_mbox { + status = "okay"; +}; +&dcpext0 { +#else +&dcp { +#endif + status = "okay"; + apple,connector-type = "HDMI-A"; + + /* HDMI HPD gpio, used as interrupt*/ + hdmi-hpd-gpios = <&pinctrl_aop 25 GPIO_ACTIVE_HIGH>; + + hdmi-pwren-gpios = <&smc_gpio 23 GPIO_ACTIVE_HIGH>; + dp2hdmi-pwren-gpios = <&smc_gpio 25 GPIO_ACTIVE_HIGH>; + + phys = <&lpdptxphy>; + phy-names = "dp-phy"; + apple,dptx-phy = <4>; +}; + &gpu { /* Apple does not do this, but they probably should */ apple,perf-base-pstate = <3>; diff --git a/arch/arm64/boot/dts/apple/t6021-j475c.dts b/arch/arm64/boot/dts/apple/t6021-j475c.dts index ebc3ec8c387b30..07d6f5d366830a 100644 --- a/arch/arm64/boot/dts/apple/t6021-j475c.dts +++ b/arch/arm64/boot/dts/apple/t6021-j475c.dts @@ -58,6 +58,58 @@ model = "Mac Studio J475"; }; +&lpdptxphy { + status = "okay"; +}; + + +#define USE_DCPEXT0 1 + +#if USE_DCPEXT0 +/ { + aliases { + dcpext0 = &dcpext0; + /delete-property/ dcp; + }; +}; + +&framebuffer0 { + power-domains = <&ps_dispext0_cpu0>, <&ps_dptx_phy_ps>; +}; + +&dcp { + status = "disabled"; +}; +&display { + iommus = <&dispext0_dart 0>; +}; +&dispext0_dart { + status = "okay"; +}; +&dcpext0_dart { + status = "okay"; +}; +&dcpext0_mbox { + status = "okay"; +}; +&dcpext0 { +#else +&dcp { +#endif + status = "okay"; + apple,connector-type = "HDMI-A"; + + /* HDMI HPD gpio, used as interrupt*/ + hdmi-hpd-gpios = <&pinctrl_aop 25 GPIO_ACTIVE_HIGH>; + + hdmi-pwren-gpios = <&smc_gpio 23 GPIO_ACTIVE_HIGH>; + dp2hdmi-pwren-gpios = <&smc_gpio 25 GPIO_ACTIVE_HIGH>; + + phys = <&lpdptxphy>; + phy-names = "dp-phy"; + apple,dptx-phy = <4>; +}; + &gpu { apple,idleoff-standby-timer = <3000>; apple,perf-base-pstate = <5>; diff --git a/arch/arm64/boot/dts/apple/t602x-j474-j475.dtsi b/arch/arm64/boot/dts/apple/t602x-j474-j475.dtsi index 25c0e6bf41724b..287348628eb177 100644 --- a/arch/arm64/boot/dts/apple/t602x-j474-j475.dtsi +++ b/arch/arm64/boot/dts/apple/t602x-j474-j475.dtsi @@ -21,11 +21,6 @@ power-domains = <&ps_dispext0_cpu0>, <&ps_dptx_phy_ps>; }; -/* disable dcp until it is supported */ -&dcp { - status = "disabled"; -}; - &hpm0 { interrupts = <44 IRQ_TYPE_LEVEL_LOW>; }; From b17fd35e11f995cf88a0582979a18a5b1178a105 Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Sat, 4 Nov 2023 22:33:29 +0100 Subject: [PATCH 2172/3902] arm64: dts: apple: t6022-{j180,j475}: Enable dcpext0/dptx-phy/dp2hdmi After all parts are in place enable dcpext on M2 Ultra Mac Pro and Studio. On the Mac Pro only the HDMI output connected to die1 is enabled. Signed-off-by: Janne Grunau --- arch/arm64/boot/dts/apple/t6022-j475d.dts | 6 +++ arch/arm64/boot/dts/apple/t6022-jxxxd.dtsi | 43 ++++++++++++++++++++-- 2 files changed, 46 insertions(+), 3 deletions(-) diff --git a/arch/arm64/boot/dts/apple/t6022-j475d.dts b/arch/arm64/boot/dts/apple/t6022-j475d.dts index 227c574be91981..74a75d680345d8 100644 --- a/arch/arm64/boot/dts/apple/t6022-j475d.dts +++ b/arch/arm64/boot/dts/apple/t6022-j475d.dts @@ -22,6 +22,7 @@ aliases { atcphy4 = &atcphy0_die1; atcphy5 = &atcphy1_die1; + /delete-property/ dcp; }; }; @@ -29,6 +30,11 @@ power-domains = <&ps_dispext0_cpu0_die1>, <&ps_dptx_phy_ps_die1>; }; +&dcpext0_die1 { + // J180 misses "function-dp2hdmi_pwr_en" + dp2hdmi-pwren-gpios = <&smc_gpio 25 GPIO_ACTIVE_HIGH>; +}; + /* enable PCIe port01 with SDHCI */ &port01 { pwren-gpios = <&smc_gpio 22 GPIO_ACTIVE_HIGH>; diff --git a/arch/arm64/boot/dts/apple/t6022-jxxxd.dtsi b/arch/arm64/boot/dts/apple/t6022-jxxxd.dtsi index 5b7b41ce07c3d8..545f1087aa80dc 100644 --- a/arch/arm64/boot/dts/apple/t6022-jxxxd.dtsi +++ b/arch/arm64/boot/dts/apple/t6022-jxxxd.dtsi @@ -9,11 +9,48 @@ * Copyright The Asahi Linux Contributors */ -/* disable unused display node */ +/ { + aliases { + dcpext4 = &dcpext0_die1; + disp0 = &display; + }; +}; + +&lpdptxphy_die1 { + status = "okay"; +}; &display { - status = "disabled"; - iommus = <>; /* <&dispext0_dart_die1 0>; */ + iommus = <&dispext0_dart_die1 0>; +}; + +&dispext0_dart_die1 { + status = "okay"; +}; + +&dcpext0_dart_die1 { + status = "okay"; +}; + +&dcpext0_mbox_die1 { + status = "okay"; +}; + +&dcpext0_die1 { + status = "okay"; + apple,connector-type = "HDMI-A"; + + /* HDMI HPD gpio, used as interrupt*/ + hdmi-hpd-gpios = <&pinctrl_aop 41 GPIO_ACTIVE_HIGH>; + + hdmi-pwren-gpios = <&smc_gpio 23 GPIO_ACTIVE_HIGH>; + // J180 misses "function-dp2hdmi_pwr_en" + // dp2hdmi-pwren-gpios = <&smc_gpio 25 GPIO_ACTIVE_HIGH>; + + phys = <&lpdptxphy_die1>; + phy-names = "dp-phy"; + apple,dptx-phy = <4>; + apple,dptx-die = <1>; }; /* delete missing dcp0/disp0 */ From 69a9e199d8c557bae4609eed4281160552c9ee0b Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Mon, 6 Nov 2023 20:39:42 +0100 Subject: [PATCH 2173/3902] arm64: dts: apple: Fill device node for dp2hdmi on Macbook Pros The HDMI output on the 14 and 16 inch Macbook Pros with M1/M2 Pro/Max is driven by an unused ATC port using the phy and crossbar. The DP output from any dcpext display controller is routed to a Kinetic DP2HDMI converter (MCDP2920 and a unknown HDMI 2.1 capable variant). Signed-off-by: Janne Grunau --- .../arm64/boot/dts/apple/t600x-j314-j316.dtsi | 39 +++++++++++++++++++ .../arm64/boot/dts/apple/t602x-j414-j416.dtsi | 5 +++ 2 files changed, 44 insertions(+) diff --git a/arch/arm64/boot/dts/apple/t600x-j314-j316.dtsi b/arch/arm64/boot/dts/apple/t600x-j314-j316.dtsi index 187e132d77281b..6899c5f4a80df1 100644 --- a/arch/arm64/boot/dts/apple/t600x-j314-j316.dtsi +++ b/arch/arm64/boot/dts/apple/t600x-j314-j316.dtsi @@ -21,6 +21,7 @@ atcphy3 = &atcphy3; bluetooth0 = &bluetooth0; dcp = &dcp; + dcpext0 = &dcpext0; disp0 = &display; disp0_piodma = &disp0_piodma; serial0 = &serial0; @@ -73,6 +74,44 @@ }; }; +&display { + iommus = <&disp0_dart 0>, <&dispext0_dart 0>; +}; + +&dispext0_dart { + status = "okay"; +}; + +&dcpext0_dart { + status = "okay"; +}; + +&dcpext0_mbox { + status = "okay"; +}; + +&dcpext0 { + /* enabled by the loader */ + apple,connector-type = "HDMI-A"; + + /* HDMI HPD gpio, used as interrupt*/ + hdmi-hpd-gpios = <&pinctrl_nub 15 GPIO_ACTIVE_HIGH>; + + hdmi-pwren-gpios = <&smc_gpio 23 GPIO_ACTIVE_HIGH>; + dp2hdmi-pwren-gpios = <&smc_gpio 6 GPIO_ACTIVE_HIGH>; + + phys = <&atcphy3 PHY_TYPE_DP>; + phy-names = "dp-phy"; + mux-controls = <&atcphy3_xbar 0>; + mux-control-names = "dp-xbar"; + mux-index = <0>; + apple,dptx-phy = <3>; +}; + +&atcphy3_xbar { + status = "okay"; +}; + /* USB Type C */ &i2c0 { hpm0: usb-pd@38 { diff --git a/arch/arm64/boot/dts/apple/t602x-j414-j416.dtsi b/arch/arm64/boot/dts/apple/t602x-j414-j416.dtsi index b9aee8ec432b9a..0057e6a9465f9d 100644 --- a/arch/arm64/boot/dts/apple/t602x-j414-j416.dtsi +++ b/arch/arm64/boot/dts/apple/t602x-j414-j416.dtsi @@ -34,6 +34,11 @@ apple,always-on; }; +&dcpext0 { + /* HDMI HPD gpio, used as interrupt*/ + hdmi-hpd-gpios = <&pinctrl_aop 25 GPIO_ACTIVE_HIGH>; +}; + &hpm0 { interrupts = <44 IRQ_TYPE_LEVEL_LOW>; }; From f2c510b3be059cebe4d0c23c1646e949869c5c1f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20Povi=C5=A1er?= Date: Sun, 12 Feb 2023 15:26:30 +0100 Subject: [PATCH 2174/3902] arm64: apple: t8103-pmgr: SIO: Add audio, spi and uart power-domains MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The power-domains AUDIO_P, SPI_P and UART_P are necessary for SIO's ASC firmware to run. This is not explicitly expressed in the ADT (probably since the power-domains are implicitly turned on when macOS uses SIO). Since we plan to use SIO only for DP/HDMI audio add the power-domains explicitly as dependency of ps_sio_cpu. They might be better placed directly into the SIO node but the SIO driver doesn't support multiple power-domains. Signed-off-by: Janne Grunau Signed-off-by: Martin Povišer --- arch/arm64/boot/dts/apple/t8103-pmgr.dtsi | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/arch/arm64/boot/dts/apple/t8103-pmgr.dtsi b/arch/arm64/boot/dts/apple/t8103-pmgr.dtsi index 10facd0c01e420..5d3846d44e3578 100644 --- a/arch/arm64/boot/dts/apple/t8103-pmgr.dtsi +++ b/arch/arm64/boot/dts/apple/t8103-pmgr.dtsi @@ -234,7 +234,7 @@ #power-domain-cells = <0>; #reset-cells = <0>; label = "sio_cpu"; - power-domains = <&ps_sio>; + power-domains = <&ps_sio &ps_uart_p &ps_spi_p &ps_dpa0>; }; ps_fpwm0: power-controller@1d8 { From 7588f705404d69fc9c77dd06fd9063bc008e0e8d Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Mon, 13 Nov 2023 23:58:10 +0100 Subject: [PATCH 2175/3902] arm64: apple: t8112-pmgr: SIO: Add audio, spi and uart power-domains The power-domains AUDIO_P, SPI_P and UART_P are necessary for SIO's ASC firmware to run. This is not explicitly expressed in the ADT (probably since the power-domains are implicitly turned on when macOS uses SIO). Since we plan to use SIO only for DP/HDMI audio add the power-domains explicitly as dependency of ps_sio_cpu. They might be better placed directly into the SIO node but the SIO driver doesn't support multiple power-domains. Signed-off-by: Janne Grunau --- arch/arm64/boot/dts/apple/t8112-pmgr.dtsi | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/arch/arm64/boot/dts/apple/t8112-pmgr.dtsi b/arch/arm64/boot/dts/apple/t8112-pmgr.dtsi index 102ff3ad0535d0..ab8ec9bd4e4401 100644 --- a/arch/arm64/boot/dts/apple/t8112-pmgr.dtsi +++ b/arch/arm64/boot/dts/apple/t8112-pmgr.dtsi @@ -176,7 +176,7 @@ #power-domain-cells = <0>; #reset-cells = <0>; label = "sio_cpu"; - power-domains = <&ps_sio>; + power-domains = <&ps_sio &ps_uart_p &ps_spi_p &ps_dpa0>; }; ps_fpwm0: power-controller@1c8 { From 45a2e0538597d7e34ebc39d1af1b80a299e22c0c Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Sun, 21 Apr 2024 18:01:37 +0200 Subject: [PATCH 2176/3902] arm64: apple: t600x: pmgr: SIO: Add audio, spi and uart power-domains The power-domains AUDIO_P, SPI_P and UART_P are necessary for SIO's ASC firmware to run. This is not explicitly expressed in the ADT (probably since the power-domains are implicitly turned on when macOS uses SIO). Since we plan to use SIO only for DP/HDMI audio add the power-domains explicitly as dependency of ps_sio_cpu. They might be better placed directly into the SIO node butr the SIO driver doesn't support multiple power-domains. Signed-off-by: Janne Grunau --- arch/arm64/boot/dts/apple/t600x-pmgr.dtsi | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/arch/arm64/boot/dts/apple/t600x-pmgr.dtsi b/arch/arm64/boot/dts/apple/t600x-pmgr.dtsi index a8f85e41baa4fe..1429554ed54505 100644 --- a/arch/arm64/boot/dts/apple/t600x-pmgr.dtsi +++ b/arch/arm64/boot/dts/apple/t600x-pmgr.dtsi @@ -826,7 +826,7 @@ #power-domain-cells = <0>; #reset-cells = <0>; label = DIE_LABEL(sio_cpu); - power-domains = <&DIE_NODE(ps_sio)>; + power-domains = <&DIE_NODE(ps_sio) &DIE_NODE(ps_uart_p) &DIE_NODE(ps_spi_p) &DIE_NODE(ps_audio_p)>; }; DIE_NODE(ps_fpwm0): power-controller@190 { From 254f3c4fffc6c97af757171d0b0946fbe8d47955 Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Sun, 21 Apr 2024 18:01:37 +0200 Subject: [PATCH 2177/3902] arm64: apple: t602x: pmgr: SIO: Add audio, spi and uart power-domains The power-domains AUDIO_P, SPI_P and UART_P are necessary for SIO's ASC firmware to run. This is not explicitly expressed in the ADT (probably since the power-domains are implicitly turned on when macOS uses SIO). Since we plan to use SIO only for DP/HDMI audio add the power-domains explicitly as dependency of ps_sio_cpu. They might be better placed directly into the SIO node butr the SIO driver doesn't support multiple power-domains. Signed-off-by: Janne Grunau --- arch/arm64/boot/dts/apple/t602x-pmgr.dtsi | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/arch/arm64/boot/dts/apple/t602x-pmgr.dtsi b/arch/arm64/boot/dts/apple/t602x-pmgr.dtsi index 7d70e8bb08185a..b9233f252e6ca7 100644 --- a/arch/arm64/boot/dts/apple/t602x-pmgr.dtsi +++ b/arch/arm64/boot/dts/apple/t602x-pmgr.dtsi @@ -1260,7 +1260,7 @@ #power-domain-cells = <0>; #reset-cells = <0>; label = DIE_LABEL(sio_cpu); - power-domains = <&DIE_NODE(ps_sio)>; + power-domains = <&DIE_NODE(ps_sio) &DIE_NODE(ps_uart_p) &DIE_NODE(ps_spi_p) &DIE_NODE(ps_audio_p)>; }; DIE_NODE(ps_fpwm0): power-controller@1e8 { From 12ae72a6659b480d364872d8778ef6962a17f38f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20Povi=C5=A1er?= Date: Mon, 28 Nov 2022 17:10:01 +0100 Subject: [PATCH 2178/3902] arm64: apple: t8103: Add SIO, DPA nodes; hook up to DCP MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Martin Povišer --- arch/arm64/boot/dts/apple/t8103-j274.dts | 5 ++ arch/arm64/boot/dts/apple/t8103.dtsi | 90 ++++++++++++++++++++++++ 2 files changed, 95 insertions(+) diff --git a/arch/arm64/boot/dts/apple/t8103-j274.dts b/arch/arm64/boot/dts/apple/t8103-j274.dts index 9396c8a010ab3d..ee38c4832d1dc0 100644 --- a/arch/arm64/boot/dts/apple/t8103-j274.dts +++ b/arch/arm64/boot/dts/apple/t8103-j274.dts @@ -19,6 +19,7 @@ aliases { ethernet0 = ðernet0; + sio = &sio; }; }; @@ -26,6 +27,10 @@ apple,connector-type = "HDMI-A"; }; +&dpaudio0 { + status = "okay"; +}; + &bluetooth0 { brcm,board-type = "apple,atlantisb"; }; diff --git a/arch/arm64/boot/dts/apple/t8103.dtsi b/arch/arm64/boot/dts/apple/t8103.dtsi index 36a39c0877c0e1..e7765ffb9f2202 100644 --- a/arch/arm64/boot/dts/apple/t8103.dtsi +++ b/arch/arm64/boot/dts/apple/t8103.dtsi @@ -671,6 +671,17 @@ iommus = <&disp0_dart 4>; phandle = <&disp0_piodma>; }; + + ports { + #address-cells = <1>; + #size-cells = <0>; + port@0 { + reg = <0>; + dcp_audio: endpoint { + remote-endpoint = <&dpaudio0_dcp>; + }; + }; + }; }; display: display-subsystem { @@ -891,6 +902,32 @@ status = "disabled"; }; + sio_mbox: mbox@236408000 { + compatible = "apple,t8103-asc-mailbox", "apple,asc-mailbox-v4"; + reg = <0x2 0x36408000 0x0 0x4000>; + interrupt-parent = <&aic>; + interrupts = , + , + , + ; + interrupt-names = "send-empty", "send-not-empty", + "recv-empty", "recv-not-empty"; + #mbox-cells = <0>; + power-domains = <&ps_sio>; + }; + + sio: sio@236400000 { + compatible = "apple,t8103-sio", "apple,sio"; + reg = <0x2 0x36400000 0x0 0x8000>; + dma-channels = <128>; + #dma-cells = <1>; + mboxes = <&sio_mbox>; + iommus = <&sio_dart 0>; + power-domains = <&ps_sio_cpu>; + resets = <&ps_sio>; /* TODO: verify reset does something */ + status = "disabled"; + }; + admac: dma-controller@238200000 { compatible = "apple,t8103-admac", "apple,admac"; reg = <0x2 0x38200000 0x0 0x34000>; @@ -905,6 +942,48 @@ resets = <&ps_audio_p>; }; + dpaudio0: audio-controller@238330000 { + compatible = "apple,t8103-dpaudio", "apple,dpaudio"; + reg = <0x2 0x38330000 0x0 0x4000>; + dmas = <&sio 0x64>; + dma-names = "tx"; + power-domains = <&ps_dpa0>; + reset-domains = <&ps_dpa0>; + status = "disabled"; + + ports { + #address-cells = <1>; + #size-cells = <0>; + port@0 { + reg = <0>; + dpaudio0_dcp: endpoint { + remote-endpoint = <&dcp_audio>; + }; + }; + }; + }; + + dpaudio1: audio-controller@238334000 { + compatible = "apple,t8103-dpaudio", "apple,dpaudio"; + reg = <0x2 0x38334000 0x0 0x4000>; + dmas = <&sio 0x66>; + dma-names = "tx"; + power-domains = <&ps_dpa1>; + reset-domains = <&ps_dpa1>; + status = "disabled"; + + ports { + #address-cells = <1>; + #size-cells = <0>; + port@0 { + reg = <0>; + dpaudio1_dcp: endpoint { + remote-endpoint = <&dcpext_audio>; + }; + }; + }; + }; + mca: i2s@238400000 { compatible = "apple,t8103-mca", "apple,mca"; reg = <0x2 0x38400000 0x0 0x18000>, @@ -1297,6 +1376,17 @@ piodma { iommus = <&dispext0_dart 4>; }; + + ports { + #address-cells = <1>; + #size-cells = <0>; + port@0 { + reg = <0>; + dcpext_audio: endpoint { + remote-endpoint = <&dpaudio1_dcp>; + }; + }; + }; }; ans_mbox: mbox@277408000 { From d5a52f02a7feab79d52d7ef9aee2ab3045975bca Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Mon, 13 Nov 2023 23:35:07 +0100 Subject: [PATCH 2179/3902] arm64: apple: t8112: Add SIO, DPA nodes; hook up to DCP Signed-off-by: Janne Grunau --- arch/arm64/boot/dts/apple/t8112-j473.dts | 5 ++ arch/arm64/boot/dts/apple/t8112.dtsi | 90 ++++++++++++++++++++++++ 2 files changed, 95 insertions(+) diff --git a/arch/arm64/boot/dts/apple/t8112-j473.dts b/arch/arm64/boot/dts/apple/t8112-j473.dts index 62e0fab7dd05d5..4df9b82c5d40e4 100644 --- a/arch/arm64/boot/dts/apple/t8112-j473.dts +++ b/arch/arm64/boot/dts/apple/t8112-j473.dts @@ -22,6 +22,7 @@ /delete-property/ dcp; dcpext = &dcpext; ethernet0 = ðernet0; + sio = &sio; wifi0 = &wifi0; }; }; @@ -65,6 +66,10 @@ apple,dptx-phy = <5>; }; +&dpaudio1 { + status = "okay"; +}; + /* * Keep the power-domains used for the HDMI port on. */ diff --git a/arch/arm64/boot/dts/apple/t8112.dtsi b/arch/arm64/boot/dts/apple/t8112.dtsi index 440d46e3d2b05f..959dff0f31fc8b 100644 --- a/arch/arm64/boot/dts/apple/t8112.dtsi +++ b/arch/arm64/boot/dts/apple/t8112.dtsi @@ -745,6 +745,17 @@ iommus = <&disp0_dart 4>; phandle = <&disp0_piodma>; }; + + ports { + #address-cells = <1>; + #size-cells = <0>; + port@0 { + reg = <0>; + dcp_audio: endpoint { + remote-endpoint = <&dpaudio0_dcp>; + }; + }; + }; }; display: display-subsystem { @@ -899,6 +910,32 @@ status = "disabled"; }; + sio_mbox: mbox@236408000 { + compatible = "apple,t8112-asc-mailbox", "apple,asc-mailbox-v4"; + reg = <0x2 0x36408000 0x0 0x4000>; + interrupt-parent = <&aic>; + interrupts = , + , + , + ; + interrupt-names = "send-empty", "send-not-empty", + "recv-empty", "recv-not-empty"; + #mbox-cells = <0>; + power-domains = <&ps_sio_cpu>; + }; + + sio: sio@236400000 { + compatible = "apple,t8112-sio", "apple,sio"; + reg = <0x2 0x36400000 0x0 0x8000>; + dma-channels = <128>; + #dma-cells = <1>; + mboxes = <&sio_mbox>; + iommus = <&sio_dart 0>; + power-domains = <&ps_sio_cpu>; + resets = <&ps_sio>; /* TODO: verify reset does something */ + status = "disabled"; + }; + admac: dma-controller@238200000 { compatible = "apple,t8112-admac", "apple,admac"; reg = <0x2 0x38200000 0x0 0x34000>; @@ -913,6 +950,48 @@ resets = <&ps_audio_p>; }; + dpaudio0: audio-controller@238330000 { + compatible = "apple,t8112-dpaudio", "apple,dpaudio"; + reg = <0x2 0x38330000 0x0 0x4000>; + dmas = <&sio 0x64>; + dma-names = "tx"; + power-domains = <&ps_dpa0>; + reset-domains = <&ps_dpa0>; + status = "disabled"; + + ports { + #address-cells = <1>; + #size-cells = <0>; + port@0 { + reg = <0>; + dpaudio0_dcp: endpoint { + remote-endpoint = <&dcp_audio>; + }; + }; + }; + }; + + dpaudio1: audio-controller@238334000 { + compatible = "apple,t8112-dpaudio", "apple,dpaudio"; + reg = <0x2 0x38334000 0x0 0x4000>; + dmas = <&sio 0x66>; + dma-names = "tx"; + power-domains = <&ps_dpa1>; + reset-domains = <&ps_dpa1>; + status = "disabled"; + + ports { + #address-cells = <1>; + #size-cells = <0>; + port@0 { + reg = <0>; + dpaudio1_dcp: endpoint { + remote-endpoint = <&dcpext_audio>; + }; + }; + }; + }; + mca: i2s@238400000 { compatible = "apple,t8112-mca", "apple,mca"; reg = <0x2 0x38400000 0x0 0x18000>, @@ -1483,6 +1562,17 @@ piodma { iommus = <&dispext0_dart 4>; }; + + ports { + #address-cells = <1>; + #size-cells = <0>; + port@0 { + reg = <0>; + dcpext_audio: endpoint { + remote-endpoint = <&dpaudio1_dcp>; + }; + }; + }; }; ans_mbox: mbox@277408000 { From 2ea0f2203253480a9f28040e4d7badc22dfd0f60 Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Sun, 19 Nov 2023 14:55:00 +0100 Subject: [PATCH 2180/3902] arm64: apple: t600x: Move dart_sio* to dieX j375d uses SIO on the second die for DP audio for its dcpexts. Signed-off-by: Janne Grunau --- arch/arm64/boot/dts/apple/t600x-die0.dtsi | 18 ------------------ arch/arm64/boot/dts/apple/t600x-dieX.dtsi | 18 ++++++++++++++++++ 2 files changed, 18 insertions(+), 18 deletions(-) diff --git a/arch/arm64/boot/dts/apple/t600x-die0.dtsi b/arch/arm64/boot/dts/apple/t600x-die0.dtsi index abed5352c5df76..d997f8ef8f9d24 100644 --- a/arch/arm64/boot/dts/apple/t600x-die0.dtsi +++ b/arch/arm64/boot/dts/apple/t600x-die0.dtsi @@ -236,24 +236,6 @@ phandle = <&display>; }; - sio_dart_0: iommu@39b004000 { - compatible = "apple,t6000-dart"; - reg = <0x3 0x9b004000 0x0 0x4000>; - interrupt-parent = <&aic>; - interrupts = ; - #iommu-cells = <1>; - power-domains = <&ps_sio_cpu>; - }; - - sio_dart_1: iommu@39b008000 { - compatible = "apple,t6000-dart"; - reg = <0x3 0x9b008000 0x0 0x8000>; - interrupt-parent = <&aic>; - interrupts = ; - #iommu-cells = <1>; - power-domains = <&ps_sio_cpu>; - }; - fpwm0: pwm@39b030000 { compatible = "apple,t6000-fpwm", "apple,s5l-fpwm"; reg = <0x3 0x9b030000 0x0 0x4000>; diff --git a/arch/arm64/boot/dts/apple/t600x-dieX.dtsi b/arch/arm64/boot/dts/apple/t600x-dieX.dtsi index 7d1e8036e27d25..06a9270871ba2a 100644 --- a/arch/arm64/boot/dts/apple/t600x-dieX.dtsi +++ b/arch/arm64/boot/dts/apple/t600x-dieX.dtsi @@ -414,6 +414,24 @@ ; }; + DIE_NODE(sio_dart_0): iommu@39b004000 { + compatible = "apple,t6000-dart"; + reg = <0x3 0x9b004000 0x0 0x4000>; + interrupt-parent = <&aic>; + interrupts = ; + #iommu-cells = <1>; + power-domains = <&DIE_NODE(ps_sio_cpu)>; + }; + + DIE_NODE(sio_dart_1): iommu@39b008000 { + compatible = "apple,t6000-dart"; + reg = <0x3 0x9b008000 0x0 0x8000>; + interrupt-parent = <&aic>; + interrupts = ; + #iommu-cells = <1>; + power-domains = <&DIE_NODE(ps_sio_cpu)>; + }; + DIE_NODE(pinctrl_ap): pinctrl@39b028000 { compatible = "apple,t6000-pinctrl", "apple,pinctrl"; reg = <0x3 0x9b028000 0x0 0x4000>; From e2780f6bd5a35c2cf4fef7c97f49827fb9c32061 Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Sun, 19 Nov 2023 14:57:09 +0100 Subject: [PATCH 2181/3902] arm64: apple: t600x: Add sio and dpaudio device nodes Signed-off-by: Janne Grunau --- arch/arm64/boot/dts/apple/t600x-die0.dtsi | 32 +++++ arch/arm64/boot/dts/apple/t600x-dieX.dtsi | 136 ++++++++++++++++++++++ 2 files changed, 168 insertions(+) diff --git a/arch/arm64/boot/dts/apple/t600x-die0.dtsi b/arch/arm64/boot/dts/apple/t600x-die0.dtsi index d997f8ef8f9d24..1fc9f7ad4a6ed6 100644 --- a/arch/arm64/boot/dts/apple/t600x-die0.dtsi +++ b/arch/arm64/boot/dts/apple/t600x-die0.dtsi @@ -188,6 +188,27 @@ apple,dma-range = <0x1f0 0x0 0x0 0xfc000000>; }; + dpaudio0: audio-controller@39b500000 { + compatible = "apple,t6000-dpaudio", "apple,dpaudio"; + reg = <0x3 0x9b500000 0x0 0x4000>; + dmas = <&sio 0x64>; + dma-names = "tx"; + power-domains = <&ps_dpa0>; + reset-domains = <&ps_dpa0>; + status = "disabled"; + + ports { + #address-cells = <1>; + #size-cells = <0>; + port@0 { + reg = <0>; + dpaudio0_dcp: endpoint { + remote-endpoint = <&dcp_audio>; + }; + }; + }; + }; + dcp_mbox: mbox@38bc08000 { compatible = "apple,t6000-asc-mailbox", "apple,asc-mailbox-v4"; reg = <0x3 0x8bc08000 0x0 0x4000>; @@ -227,6 +248,17 @@ iommus = <&disp0_dart 4>; phandle = <&disp0_piodma>; }; + + ports { + #address-cells = <1>; + #size-cells = <0>; + port@0 { + reg = <0>; + dcp_audio: endpoint { + remote-endpoint = <&dpaudio0_dcp>; + }; + }; + }; }; display: display-subsystem { diff --git a/arch/arm64/boot/dts/apple/t600x-dieX.dtsi b/arch/arm64/boot/dts/apple/t600x-dieX.dtsi index 06a9270871ba2a..9916450e30bbb6 100644 --- a/arch/arm64/boot/dts/apple/t600x-dieX.dtsi +++ b/arch/arm64/boot/dts/apple/t600x-dieX.dtsi @@ -88,6 +88,17 @@ piodma { iommus = <&DIE_NODE(dispext0_dart) 4>; }; + + ports { + #address-cells = <1>; + #size-cells = <0>; + port@0 { + reg = <0>; + DIE_NODE(dcpext0_audio): endpoint { + remote-endpoint = <&DIE_NODE(dpaudio1_dcp)>; + }; + }; + }; }; DIE_NODE(dispext1_dart): iommu@28c304000 { @@ -154,6 +165,17 @@ piodma { iommus = <&DIE_NODE(dispext1_dart) 4>; }; + + ports { + #address-cells = <1>; + #size-cells = <0>; + port@0 { + reg = <0>; + DIE_NODE(dcpext1_audio): endpoint { + remote-endpoint = <&DIE_NODE(dpaudio2_dcp)>; + }; + }; + }; }; DIE_NODE(pmgr): power-management@28e080000 { @@ -457,6 +479,120 @@ #interrupt-cells = <2>; }; + DIE_NODE(sio_mbox): mbox@39bc08000 { + compatible = "apple,t6000-asc-mailbox", "apple,asc-mailbox-v4"; + reg = <0x3 0x9bc08000 0x0 0x4000>; + interrupt-parent = <&aic>; + interrupts = , + , + , + ; + interrupt-names = "send-empty", "send-not-empty", + "recv-empty", "recv-not-empty"; + #mbox-cells = <0>; + power-domains = <&DIE_NODE(ps_sio_cpu)>; + }; + + DIE_NODE(sio): sio@39bc00000 { + compatible = "apple,t6000-sio", "apple,sio"; + reg = <0x3 0x9bc00000 0x0 0x8000>; + dma-channels = <128>; + #dma-cells = <1>; + mboxes = <&DIE_NODE(sio_mbox)>; + iommus = <&DIE_NODE(sio_dart_0) 0>, <&DIE_NODE(sio_dart_1) 0>; + power-domains = <&DIE_NODE(ps_sio_cpu)>; + resets = <&DIE_NODE(ps_sio)>; /* TODO: verify reset does something */ + status = "disabled"; + }; + + DIE_NODE(dpaudio1): audio-controller@39b504000 { + compatible = "apple,t6000-dpaudio", "apple,dpaudio"; + reg = <0x3 0x9b540000 0x0 0x4000>; + dmas = <&DIE_NODE(sio) 0x66>; + dma-names = "tx"; + power-domains = <&DIE_NODE(ps_dpa1)>; + reset-domains = <&DIE_NODE(ps_dpa1)>; + status = "disabled"; + + ports { + #address-cells = <1>; + #size-cells = <0>; + port@0 { + reg = <0>; + DIE_NODE(dpaudio1_dcp): endpoint { + remote-endpoint = <&DIE_NODE(dcpext0_audio)>; + }; + }; + }; + }; + + DIE_NODE(dpaudio2): audio-controller@39b508000 { + compatible = "apple,t6000-dpaudio", "apple,dpaudio"; + reg = <0x3 0x9b580000 0x0 0x4000>; + dmas = <&DIE_NODE(sio) 0x68>; + dma-names = "tx"; + power-domains = <&DIE_NODE(ps_dpa2)>; + reset-domains = <&DIE_NODE(ps_dpa2)>; + status = "disabled"; + + ports { + #address-cells = <1>; + #size-cells = <0>; + port@0 { + reg = <0>; + DIE_NODE(dpaudio2_dcp): endpoint { + remote-endpoint = <&DIE_NODE(dcpext1_audio)>; + }; + }; + }; + }; + + /* + * omit dpaudio3 / 4 as long as the linked dcpext nodes don't exist + * + DIE_NODE(dpaudio3): audio-controller@39b50c000 { + compatible = "apple,t6000-dpaudio", "apple,dpaudio"; + reg = <0x3 0x9b5c0000 0x0 0x4000>; + dmas = <&DIE_NODE(sio) 0x6a>; + dma-names = "tx"; + power-domains = <&DIE_NODE(ps_dpa3)>; + reset-domains = <&DIE_NODE(ps_dpa3)>; + status = "disabled"; + + ports { + #address-cells = <1>; + #size-cells = <0>; + port@0 { + reg = <0>; + DIE_NODE(dpaudio3_dcp): endpoint { + remote-endpoint = <&DIE_NODE(dcpext2_audio)>; + }; + }; + }; + }; + + DIE_NODE(dpaudio4): audio-controller@39b510000 { + compatible = "apple,t6000-dpaudio", "apple,dpaudio"; + reg = <0x3 0x9b500000 0x0 0x4000>; + dmas = <&DIE_NODE(sio) 0x6c>; + dma-names = "tx"; + power-domains = <&DIE_NODE(ps_dpa4)>; + reset-domains = <&DIE_NODE(ps_dpa4)>; + status = "disabled"; + + ports { + #address-cells = <1>; + #size-cells = <0>; + port@0 { + reg = <0>; + DIE_NODE(dpaudio4_dcp): endpoint { + remote-endpoint = <&DIE_NODE(dcpext3_audio)>; + }; + }; + }; + }; + */ + DIE_NODE(dwc3_0): usb@702280000 { compatible = "apple,t6000-dwc3", "apple,t8103-dwc3"; reg = <0x7 0x02280000 0x0 0xcd00>, <0x7 0x0228cd00 0x0 0x3200>; From 0a1c5f3e5a700c8302457f6bf49dcd1b84e93ced Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Sun, 21 Jan 2024 14:45:49 +0100 Subject: [PATCH 2182/3902] arm64: apple: t602x: Add sio and dpaudio device nodes Signed-off-by: Janne Grunau --- arch/arm64/boot/dts/apple/t6022-jxxxd.dtsi | 1 + arch/arm64/boot/dts/apple/t602x-die0.dtsi | 41 ++++-- arch/arm64/boot/dts/apple/t602x-dieX.dtsi | 146 +++++++++++++++++++++ 3 files changed, 179 insertions(+), 9 deletions(-) diff --git a/arch/arm64/boot/dts/apple/t6022-jxxxd.dtsi b/arch/arm64/boot/dts/apple/t6022-jxxxd.dtsi index 545f1087aa80dc..17e97eee64bde6 100644 --- a/arch/arm64/boot/dts/apple/t6022-jxxxd.dtsi +++ b/arch/arm64/boot/dts/apple/t6022-jxxxd.dtsi @@ -58,6 +58,7 @@ /delete-node/ &dcp_dart; /delete-node/ &dcp_mbox; /delete-node/ &dcp; +/delete-node/ &dpaudio0; /* delete power-domains for missing disp0 / disp0_die1 */ /delete-node/ &ps_disp0_cpu0; diff --git a/arch/arm64/boot/dts/apple/t602x-die0.dtsi b/arch/arm64/boot/dts/apple/t602x-die0.dtsi index 25f5f01a645c2c..59181d7c6a6b8c 100644 --- a/arch/arm64/boot/dts/apple/t602x-die0.dtsi +++ b/arch/arm64/boot/dts/apple/t602x-die0.dtsi @@ -355,6 +355,17 @@ iommus = <&disp0_dart 4>; phandle = <&disp0_piodma>; }; + + ports { + #address-cells = <1>; + #size-cells = <0>; + port@0 { + reg = <0>; + dcp_audio: endpoint { + remote-endpoint = <&dpaudio0_dcp>; + }; + }; + }; }; display: display-subsystem { @@ -364,15 +375,6 @@ phandle = <&display>; }; - sio_dart: iommu@39b008000 { - compatible = "apple,t6020-dart", "apple,t8110-dart"; - reg = <0x3 0x9b008000 0x0 0x8000>; - interrupt-parent = <&aic>; - interrupts = ; - #iommu-cells = <1>; - power-domains = <&ps_sio_cpu>; - }; - fpwm0: pwm@39b030000 { compatible = "apple,t6020-fpwm", "apple,s5l-fpwm"; reg = <0x3 0x9b030000 0x0 0x4000>; @@ -579,6 +581,27 @@ resets = <&ps_audio_p>; }; + dpaudio0: audio-controller@39b500000 { + compatible = "apple,t6020-dpaudio", "apple,dpaudio"; + reg = <0x3 0x9b500000 0x0 0x4000>; + dmas = <&sio 0x64>; + dma-names = "tx"; + power-domains = <&ps_dpa0>; + reset-domains = <&ps_dpa0>; + status = "disabled"; + + ports { + #address-cells = <1>; + #size-cells = <0>; + port@0 { + reg = <0>; + dpaudio0_dcp: endpoint { + remote-endpoint = <&dcp_audio>; + }; + }; + }; + }; + mca: mca@39b600000 { compatible = "apple,t6020-mca", "apple,t8103-mca", "apple,mca"; reg = <0x3 0x9b600000 0x0 0x10000>, diff --git a/arch/arm64/boot/dts/apple/t602x-dieX.dtsi b/arch/arm64/boot/dts/apple/t602x-dieX.dtsi index c28747bf56915b..8d0b6ec133a9ad 100644 --- a/arch/arm64/boot/dts/apple/t602x-dieX.dtsi +++ b/arch/arm64/boot/dts/apple/t602x-dieX.dtsi @@ -87,6 +87,17 @@ piodma { iommus = <&DIE_NODE(dispext0_dart) 4>; }; + + ports { + #address-cells = <1>; + #size-cells = <0>; + port@0 { + reg = <0>; + DIE_NODE(dcpext0_audio): endpoint { + remote-endpoint = <&DIE_NODE(dpaudio1_dcp)>; + }; + }; + }; }; DIE_NODE(pmgr): power-management@28e080000 { @@ -231,6 +242,27 @@ piodma { iommus = <&DIE_NODE(dispext1_dart) 4>; }; + + ports { + #address-cells = <1>; + #size-cells = <0>; + port@0 { + reg = <0>; + DIE_NODE(dcpext1_audio): endpoint { + remote-endpoint = <&DIE_NODE(dpaudio2_dcp)>; + }; + }; + }; + }; + + DIE_NODE(sio_dart): iommu@39b008000 { + compatible = "apple,t6020-dart", "apple,t8110-dart"; + reg = <0x3 0x9b008000 0x0 0x4000>; + interrupt-parent = <&aic>; + interrupts = ; + #iommu-cells = <1>; + power-domains = <&DIE_NODE(ps_sio)>; + //apple,dma-range = <0x100 0x0001c000 0x2ff 0xfffe4000>; }; DIE_NODE(pinctrl_ap): pinctrl@39b028000 { @@ -258,6 +290,120 @@ #interrupt-cells = <2>; }; + DIE_NODE(sio_mbox): mbox@39bc08000 { + compatible = "apple,t6020-asc-mailbox", "apple,asc-mailbox-v4"; + reg = <0x3 0x9bc08000 0x0 0x4000>; + interrupt-parent = <&aic>; + interrupts = , + , + , + ; + interrupt-names = "send-empty", "send-not-empty", + "recv-empty", "recv-not-empty"; + #mbox-cells = <0>; + power-domains = <&DIE_NODE(ps_sio_cpu)>; + }; + + DIE_NODE(sio): sio@39bc00000 { + compatible = "apple,t6020-sio", "apple,sio"; + reg = <0x3 0x9bc00000 0x0 0x8000>; + dma-channels = <128>; + #dma-cells = <1>; + mboxes = <&DIE_NODE(sio_mbox)>; + iommus = <&DIE_NODE(sio_dart) 0>; + power-domains = <&DIE_NODE(ps_sio_cpu)>; + resets = <&DIE_NODE(ps_sio_cpu)>; + status = "disabled"; + }; + + DIE_NODE(dpaudio1): audio-controller@39b504000 { + compatible = "apple,t6020-dpaudio", "apple,dpaudio"; + reg = <0x3 0x9b540000 0x0 0x4000>; + dmas = <&DIE_NODE(sio) 0x66>; + dma-names = "tx"; + power-domains = <&DIE_NODE(ps_dpa1)>; + reset-domains = <&DIE_NODE(ps_dpa1)>; + status = "disabled"; + + ports { + #address-cells = <1>; + #size-cells = <0>; + port@0 { + reg = <0>; + DIE_NODE(dpaudio1_dcp): endpoint { + remote-endpoint = <&DIE_NODE(dcpext0_audio)>; + }; + }; + }; + }; + + DIE_NODE(dpaudio2): audio-controller@39b508000 { + compatible = "apple,t6020-dpaudio", "apple,dpaudio"; + reg = <0x3 0x9b580000 0x0 0x4000>; + dmas = <&DIE_NODE(sio) 0x68>; + dma-names = "tx"; + power-domains = <&DIE_NODE(ps_dpa2)>; + reset-domains = <&DIE_NODE(ps_dpa2)>; + status = "disabled"; + + ports { + #address-cells = <1>; + #size-cells = <0>; + port@0 { + reg = <0>; + DIE_NODE(dpaudio2_dcp): endpoint { + remote-endpoint = <&DIE_NODE(dcpext1_audio)>; + }; + }; + }; + }; + + /* + * omit dpaudio3 / 4 as long as the linked dcpext nodes don't exist + * + DIE_NODE(dpaudio3): audio-controller@39b50c000 { + compatible = "apple,t6020-dpaudio", "apple,dpaudio"; + reg = <0x3 0x9b5c0000 0x0 0x4000>; + dmas = <&DIE_NODE(sio) 0x6a>; + dma-names = "tx"; + power-domains = <&DIE_NODE(ps_dpa3)>; + reset-domains = <&DIE_NODE(ps_dpa3)>; + status = "disabled"; + + ports { + #address-cells = <1>; + #size-cells = <0>; + port@0 { + reg = <0>; + DIE_NODE(dpaudio3_dcp): endpoint { + remote-endpoint = <&DIE_NODE(dcpext2_audio)>; + }; + }; + }; + }; + + DIE_NODE(dpaudio4): audio-controller@39b510000 { + compatible = "apple,t6020-dpaudio", "apple,dpaudio"; + reg = <0x3 0x9b500000 0x0 0x4000>; + dmas = <&DIE_NODE(sio) 0x6c>; + dma-names = "tx"; + power-domains = <&DIE_NODE(ps_dpa4)>; + reset-domains = <&DIE_NODE(ps_dpa4)>; + status = "disabled"; + + ports { + #address-cells = <1>; + #size-cells = <0>; + port@0 { + reg = <0>; + DIE_NODE(dpaudio4_dcp): endpoint { + remote-endpoint = <&DIE_NODE(dcpext3_audio)>; + }; + }; + }; + }; + */ + DIE_NODE(lpdptxphy): phy@39c000000 { compatible = "apple,t6020-dptx-phy", "apple,dptx-phy"; reg = <0x3 0x9c000000 0x0 0x4000>, From 5fa35775457cc237ec49ab74f967fdfcc652299a Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Sun, 7 Apr 2024 23:10:55 +0200 Subject: [PATCH 2183/3902] arm64: apple: t60xx: Enable DP/HMI audio nodes on all devices Signed-off-by: Janne Grunau --- arch/arm64/boot/dts/apple/t6001-j375c.dts | 4 ++++ arch/arm64/boot/dts/apple/t6002-j375d.dts | 4 ++++ arch/arm64/boot/dts/apple/t600x-j314-j316.dtsi | 5 +++++ arch/arm64/boot/dts/apple/t600x-j375.dtsi | 1 + arch/arm64/boot/dts/apple/t6020-j474s.dts | 6 ++++++ arch/arm64/boot/dts/apple/t6021-j475c.dts | 6 ++++++ arch/arm64/boot/dts/apple/t6022-j475d.dts | 5 +++++ arch/arm64/boot/dts/apple/t6022-jxxxd.dtsi | 5 +++++ 8 files changed, 36 insertions(+) diff --git a/arch/arm64/boot/dts/apple/t6001-j375c.dts b/arch/arm64/boot/dts/apple/t6001-j375c.dts index 68e2b120117840..4028571143ac87 100644 --- a/arch/arm64/boot/dts/apple/t6001-j375c.dts +++ b/arch/arm64/boot/dts/apple/t6001-j375c.dts @@ -25,6 +25,10 @@ brcm,board-type = "apple,okinawa"; }; +&dpaudio0 { + status = "okay"; +}; + &sound { compatible = "apple,j375-macaudio", "apple,macaudio"; model = "Mac Studio J375"; diff --git a/arch/arm64/boot/dts/apple/t6002-j375d.dts b/arch/arm64/boot/dts/apple/t6002-j375d.dts index 9d61a601c6bf18..e17c71ff18913c 100644 --- a/arch/arm64/boot/dts/apple/t6002-j375d.dts +++ b/arch/arm64/boot/dts/apple/t6002-j375d.dts @@ -21,6 +21,10 @@ }; }; +&dpaudio0 { + status = "okay"; +}; + &sound { compatible = "apple,j375-macaudio", "apple,macaudio"; model = "Mac Studio J375"; diff --git a/arch/arm64/boot/dts/apple/t600x-j314-j316.dtsi b/arch/arm64/boot/dts/apple/t600x-j314-j316.dtsi index 6899c5f4a80df1..797ac0c75481ae 100644 --- a/arch/arm64/boot/dts/apple/t600x-j314-j316.dtsi +++ b/arch/arm64/boot/dts/apple/t600x-j314-j316.dtsi @@ -25,6 +25,7 @@ disp0 = &display; disp0_piodma = &disp0_piodma; serial0 = &serial0; + sio = &sio; wifi0 = &wifi0; }; @@ -108,6 +109,10 @@ apple,dptx-phy = <3>; }; +&dpaudio1 { + status = "okay"; +}; + &atcphy3_xbar { status = "okay"; }; diff --git a/arch/arm64/boot/dts/apple/t600x-j375.dtsi b/arch/arm64/boot/dts/apple/t600x-j375.dtsi index ce962404b2581d..6336cff863b5eb 100644 --- a/arch/arm64/boot/dts/apple/t600x-j375.dtsi +++ b/arch/arm64/boot/dts/apple/t600x-j375.dtsi @@ -25,6 +25,7 @@ #endif ethernet0 = ðernet0; serial0 = &serial0; + sio = &sio; wifi0 = &wifi0; }; diff --git a/arch/arm64/boot/dts/apple/t6020-j474s.dts b/arch/arm64/boot/dts/apple/t6020-j474s.dts index 89b6c46a036eca..f904582f98bb91 100644 --- a/arch/arm64/boot/dts/apple/t6020-j474s.dts +++ b/arch/arm64/boot/dts/apple/t6020-j474s.dts @@ -96,8 +96,14 @@ &dcpext0_mbox { status = "okay"; }; +&dpaudio1 { + status = "okay"; +}; &dcpext0 { #else +&dpaudio0 { + status = "okay"; +}; &dcp { #endif status = "okay"; diff --git a/arch/arm64/boot/dts/apple/t6021-j475c.dts b/arch/arm64/boot/dts/apple/t6021-j475c.dts index 07d6f5d366830a..09f477dbdf28b1 100644 --- a/arch/arm64/boot/dts/apple/t6021-j475c.dts +++ b/arch/arm64/boot/dts/apple/t6021-j475c.dts @@ -92,8 +92,14 @@ &dcpext0_mbox { status = "okay"; }; +&dpaudio1 { + status = "okay"; +}; &dcpext0 { #else +&dpaudio0 { + status = "okay"; +}; &dcp { #endif status = "okay"; diff --git a/arch/arm64/boot/dts/apple/t6022-j475d.dts b/arch/arm64/boot/dts/apple/t6022-j475d.dts index 74a75d680345d8..a9020a23a7e976 100644 --- a/arch/arm64/boot/dts/apple/t6022-j475d.dts +++ b/arch/arm64/boot/dts/apple/t6022-j475d.dts @@ -23,9 +23,14 @@ atcphy4 = &atcphy0_die1; atcphy5 = &atcphy1_die1; /delete-property/ dcp; + /delete-property/ sio; }; }; +&sio { + status = "disabled"; +}; + &framebuffer0 { power-domains = <&ps_dispext0_cpu0_die1>, <&ps_dptx_phy_ps_die1>; }; diff --git a/arch/arm64/boot/dts/apple/t6022-jxxxd.dtsi b/arch/arm64/boot/dts/apple/t6022-jxxxd.dtsi index 17e97eee64bde6..f11b017dc0496f 100644 --- a/arch/arm64/boot/dts/apple/t6022-jxxxd.dtsi +++ b/arch/arm64/boot/dts/apple/t6022-jxxxd.dtsi @@ -13,6 +13,7 @@ aliases { dcpext4 = &dcpext0_die1; disp0 = &display; + sio1 = &sio_die1; }; }; @@ -53,6 +54,10 @@ apple,dptx-die = <1>; }; +&dpaudio1_die1 { + status = "okay"; +}; + /* delete missing dcp0/disp0 */ /delete-node/ &disp0_dart; /delete-node/ &dcp_dart; From c264bf83236ccd3649800fcec21c531dc893ff9a Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Sat, 27 Apr 2024 12:57:52 +0200 Subject: [PATCH 2184/3902] arm64: apple: t60x0/t60x1: Enable sio explicitly To be removed after m1n1 does this after proper setup. Signed-off-by: Janne Grunau --- arch/arm64/boot/dts/apple/t600x-j314-j316.dtsi | 5 +++++ arch/arm64/boot/dts/apple/t600x-j375.dtsi | 5 +++++ 2 files changed, 10 insertions(+) diff --git a/arch/arm64/boot/dts/apple/t600x-j314-j316.dtsi b/arch/arm64/boot/dts/apple/t600x-j314-j316.dtsi index 797ac0c75481ae..4ebaa197a2370c 100644 --- a/arch/arm64/boot/dts/apple/t600x-j314-j316.dtsi +++ b/arch/arm64/boot/dts/apple/t600x-j314-j316.dtsi @@ -109,6 +109,11 @@ apple,dptx-phy = <3>; }; +/* remove once m1n1 enables sio nodes after setup */ +&sio { + status = "okay"; +}; + &dpaudio1 { status = "okay"; }; diff --git a/arch/arm64/boot/dts/apple/t600x-j375.dtsi b/arch/arm64/boot/dts/apple/t600x-j375.dtsi index 6336cff863b5eb..f95559b9f8797f 100644 --- a/arch/arm64/boot/dts/apple/t600x-j375.dtsi +++ b/arch/arm64/boot/dts/apple/t600x-j375.dtsi @@ -59,6 +59,11 @@ apple,connector-type = "HDMI-A"; }; +/* remove once m1n1 enables sio nodes after setup */ +&sio { + status = "okay"; +}; + /* USB Type C */ &i2c0 { hpm0: usb-pd@38 { From f26b4b8ed37939ad4869e060038bdab87ce5aaf1 Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Sat, 27 Apr 2024 12:57:52 +0200 Subject: [PATCH 2185/3902] arm64: apple: t8103-j274: Enable sio explicitly To be removed after m1n1 does this after proper setup. Signed-off-by: Janne Grunau --- arch/arm64/boot/dts/apple/t8103-j274.dts | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/arch/arm64/boot/dts/apple/t8103-j274.dts b/arch/arm64/boot/dts/apple/t8103-j274.dts index ee38c4832d1dc0..22552bfbdb1950 100644 --- a/arch/arm64/boot/dts/apple/t8103-j274.dts +++ b/arch/arm64/boot/dts/apple/t8103-j274.dts @@ -27,6 +27,11 @@ apple,connector-type = "HDMI-A"; }; +/* remove once m1n1 enables sio nodes after setup */ +&sio { + status = "okay"; +}; + &dpaudio0 { status = "okay"; }; From 89c121b5abbfeb325595aff7191334ab3a9a7b5f Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Sat, 27 Apr 2024 12:57:52 +0200 Subject: [PATCH 2186/3902] arm64: apple: t8112-j473: Enable sio explicitly To be removed after m1n1 does this after proper setup. Signed-off-by: Janne Grunau --- arch/arm64/boot/dts/apple/t8112-j473.dts | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/arch/arm64/boot/dts/apple/t8112-j473.dts b/arch/arm64/boot/dts/apple/t8112-j473.dts index 4df9b82c5d40e4..2cff118565f9e5 100644 --- a/arch/arm64/boot/dts/apple/t8112-j473.dts +++ b/arch/arm64/boot/dts/apple/t8112-j473.dts @@ -66,6 +66,11 @@ apple,dptx-phy = <5>; }; +/* remove once m1n1 enables sio nodes after setup */ +&sio { + status = "okay"; +}; + &dpaudio1 { status = "okay"; }; From 20481c028978c37c2f2923108df2f2ca534a4e2e Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Sat, 12 Apr 2025 15:25:24 +0200 Subject: [PATCH 2187/3902] arm64: dts: apple: t6022-j180d: Enable second HDMI port Signed-off-by: Janne Grunau --- arch/arm64/boot/dts/apple/t6022-j180d.dts | 41 +++++++++++++++++++++++ 1 file changed, 41 insertions(+) diff --git a/arch/arm64/boot/dts/apple/t6022-j180d.dts b/arch/arm64/boot/dts/apple/t6022-j180d.dts index e22dd039bbae85..4a528c28031805 100644 --- a/arch/arm64/boot/dts/apple/t6022-j180d.dts +++ b/arch/arm64/boot/dts/apple/t6022-j180d.dts @@ -28,6 +28,7 @@ atcphy6 = &atcphy2_die1; atcphy7 = &atcphy3_die1; bluetooth0 = &bluetooth0; + dcpext0 = &dcpext0; ethernet0 = ðernet0; ethernet1 = ðernet1; serial0 = &serial0; @@ -67,6 +68,46 @@ status = "okay"; }; +&lpdptxphy { + status = "okay"; +}; + +&display { + iommus = <&dispext0_dart_die1 0>, <&dispext0_dart 0>; +}; + +&dispext0_dart { + status = "okay"; +}; + +&dcpext0_dart { + status = "okay"; +}; + +&dcpext0_mbox { + status = "okay"; +}; + +&dcpext0 { + status = "okay"; + apple,connector-type = "HDMI-A"; + + /* HDMI HPD gpio, used as interrupt*/ + hdmi-hpd-gpios = <&pinctrl_aop 25 GPIO_ACTIVE_HIGH>; + + // shared between dp2hdmi-gpio0 / dp2hdmi-gpio1 + // hdmi-pwren-gpios = <&smc_gpio 23 GPIO_ACTIVE_HIGH>; + + phys = <&lpdptxphy>; + phy-names = "dp-phy"; + apple,dptx-phy = <4>; + apple,dptx-die = <0>; +}; + +&dpaudio1 { + status = "okay"; +}; + /* USB Type C Rear */ &i2c0 { hpm2: usb-pd@3b { From 974289bf0fe744b1ca1a9fc4c27759e397382757 Mon Sep 17 00:00:00 2001 From: James Calligeros Date: Mon, 1 Jul 2024 10:07:12 +1000 Subject: [PATCH 2188/3902] arm64: dts: apple: add common hwmon keys and fans Each SoC's SMC exposes a different set of hardware sensor keys, however there are a number that are shared between all currently supported SoCs. Describe these in a .dtsi so that we don't need to duplicate them across every SoC. Likewise, the fans on every machine are exposed as the same set of keys on each. Add .dtsis for these too. Co-developed-by: Janne Grunau Signed-off-by: Janne Grunau Signed-off-by: James Calligeros --- arch/arm64/boot/dts/apple/hwmon-common.dtsi | 43 +++++++++++++++++++ arch/arm64/boot/dts/apple/hwmon-fan-dual.dtsi | 26 +++++++++++ arch/arm64/boot/dts/apple/hwmon-fan.dtsi | 21 +++++++++ arch/arm64/boot/dts/apple/hwmon-laptop.dtsi | 41 ++++++++++++++++++ arch/arm64/boot/dts/apple/hwmon-mini.dtsi | 20 +++++++++ 5 files changed, 151 insertions(+) create mode 100644 arch/arm64/boot/dts/apple/hwmon-common.dtsi create mode 100644 arch/arm64/boot/dts/apple/hwmon-fan-dual.dtsi create mode 100644 arch/arm64/boot/dts/apple/hwmon-fan.dtsi create mode 100644 arch/arm64/boot/dts/apple/hwmon-laptop.dtsi create mode 100644 arch/arm64/boot/dts/apple/hwmon-mini.dtsi diff --git a/arch/arm64/boot/dts/apple/hwmon-common.dtsi b/arch/arm64/boot/dts/apple/hwmon-common.dtsi new file mode 100644 index 00000000000000..1f9a2435e14cb7 --- /dev/null +++ b/arch/arm64/boot/dts/apple/hwmon-common.dtsi @@ -0,0 +1,43 @@ +// SPDX-License-Identifier: GPL-2.0+ OR MIT +/* + * hwmon sensors expected on all systems + * + * Copyright The Asahi Linux Contributors + */ + +&smc { + hwmon { + apple,power-keys { + power-PSTR { + apple,key-id = "PSTR"; + label = "Total System Power"; + }; + power-PDTR { + apple,key-id = "PDTR"; + label = "AC Input Power"; + }; + power-PMVR { + apple,key-id = "PMVR"; + label = "3.8 V Rail Power"; + }; + }; + apple,temp-keys { + temp-TH0x { + apple,key-id = "TH0x"; + label = "NAND Flash Temperature"; + }; + }; + apple,volt-keys { + volt-VD0R { + apple,key-id = "VD0R"; + label = "AC Input Voltage"; + }; + }; + apple,current-keys { + current-ID0R { + apple,key-id = "ID0R"; + label = "AC Input Current"; + }; + }; + }; +}; diff --git a/arch/arm64/boot/dts/apple/hwmon-fan-dual.dtsi b/arch/arm64/boot/dts/apple/hwmon-fan-dual.dtsi new file mode 100644 index 00000000000000..782b6051a3866e --- /dev/null +++ b/arch/arm64/boot/dts/apple/hwmon-fan-dual.dtsi @@ -0,0 +1,26 @@ +// SPDX-License-Identifier: GPL-2.0+ OR MIT +/* + * Copyright The Asahi Linux Contributors + * + * Fan hwmon sensors for machines with 2 fan. + */ + +#include "hwmon-fan.dtsi" + +&smc { + hwmon { + apple,fan-keys { + fan-F0Ac { + label = "Fan 1"; + }; + fan-F1Ac { + apple,key-id = "F1Ac"; + label = "Fan 2"; + apple,fan-minimum = "F1Mn"; + apple,fan-maximum = "F1Mx"; + apple,fan-target = "F1Tg"; + apple,fan-mode = "F1Md"; + }; + }; + }; +}; diff --git a/arch/arm64/boot/dts/apple/hwmon-fan.dtsi b/arch/arm64/boot/dts/apple/hwmon-fan.dtsi new file mode 100644 index 00000000000000..8f329ac4ff9fef --- /dev/null +++ b/arch/arm64/boot/dts/apple/hwmon-fan.dtsi @@ -0,0 +1,21 @@ +// SPDX-License-Identifier: GPL-2.0+ OR MIT +/* + * Copyright The Asahi Linux Contributors + * + * Fan hwmon sensors for machines with a single fan. + */ + +&smc { + hwmon { + apple,fan-keys { + fan-F0Ac { + apple,key-id = "F0Ac"; + label = "Fan"; + apple,fan-minimum = "F0Mn"; + apple,fan-maximum = "F0Mx"; + apple,fan-target = "F0Tg"; + apple,fan-mode = "F0Md"; + }; + }; + }; +}; diff --git a/arch/arm64/boot/dts/apple/hwmon-laptop.dtsi b/arch/arm64/boot/dts/apple/hwmon-laptop.dtsi new file mode 100644 index 00000000000000..2583ef379dfac9 --- /dev/null +++ b/arch/arm64/boot/dts/apple/hwmon-laptop.dtsi @@ -0,0 +1,41 @@ +// SPDX-License-Identifier: GPL-2.0+ OR MIT +/* + * hwmon sensors expected on all laptops + * + * Copyright The Asahi Linux Contributors + */ + +&smc { + hwmon { + apple,power-keys { + power-PHPC { + apple,key-id = "PHPC"; + label = "Heatpipe Power"; + }; + }; + apple,temp-keys { + temp-TB0T { + apple,key-id = "TB0T"; + label = "Battery Hotspot"; + }; + temp-TCHP { + apple,key-id = "TCHP"; + label = "Charge Regulator Temp"; + }; + temp-TW0P { + apple,key-id = "TW0P"; + label = "WiFi/BT Module Temp"; + }; + }; + apple,volt-keys { + volt-SBAV { + apple,key-id = "SBAV"; + label = "Battery Voltage"; + }; + volt-VD0R { + apple,key-id = "VD0R"; + label = "Charger Input Voltage"; + }; + }; + }; +}; diff --git a/arch/arm64/boot/dts/apple/hwmon-mini.dtsi b/arch/arm64/boot/dts/apple/hwmon-mini.dtsi new file mode 100644 index 00000000000000..bd0c22786d4226 --- /dev/null +++ b/arch/arm64/boot/dts/apple/hwmon-mini.dtsi @@ -0,0 +1,20 @@ +// SPDX-License-Identifier: GPL-2.0+ OR MIT +/* + * hwmon sensors common to the Mac mini desktop + * models, but not the Studio or Pro. + * + * Copyright The Asahi Linux Contributors + */ + +#include "hwmon-fan.dtsi" + +&smc { + hwmon { + apple,temp-keys { + temp-TW0P { + apple,key-id = "TW0P"; + label = "WiFi/BT Module Temp"; + }; + }; + }; +}; From 7a08b2fd2706f2c369672d806922753a70a83d6f Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Thu, 11 Jul 2024 21:29:10 +0200 Subject: [PATCH 2189/3902] arm64: dts: apple: t8103: Add SMC hwmon sensors Signed-off-by: Janne Grunau --- arch/arm64/boot/dts/apple/t8103-j274.dts | 2 ++ arch/arm64/boot/dts/apple/t8103-j293.dts | 3 +++ arch/arm64/boot/dts/apple/t8103-j313.dts | 2 ++ arch/arm64/boot/dts/apple/t8103-j456.dts | 2 ++ arch/arm64/boot/dts/apple/t8103-j457.dts | 2 ++ arch/arm64/boot/dts/apple/t8103-jxxx.dtsi | 2 ++ 6 files changed, 13 insertions(+) diff --git a/arch/arm64/boot/dts/apple/t8103-j274.dts b/arch/arm64/boot/dts/apple/t8103-j274.dts index 22552bfbdb1950..f55683c48784b8 100644 --- a/arch/arm64/boot/dts/apple/t8103-j274.dts +++ b/arch/arm64/boot/dts/apple/t8103-j274.dts @@ -145,3 +145,5 @@ &gpu { apple,perf-base-pstate = <3>; }; + +#include "hwmon-mini.dtsi" diff --git a/arch/arm64/boot/dts/apple/t8103-j293.dts b/arch/arm64/boot/dts/apple/t8103-j293.dts index a113b9a57e1a6e..e26329ee7d019d 100644 --- a/arch/arm64/boot/dts/apple/t8103-j293.dts +++ b/arch/arm64/boot/dts/apple/t8103-j293.dts @@ -279,3 +279,6 @@ &isp { apple,platform-id = <1>; }; + +#include "hwmon-fan.dtsi" +#include "hwmon-laptop.dtsi" diff --git a/arch/arm64/boot/dts/apple/t8103-j313.dts b/arch/arm64/boot/dts/apple/t8103-j313.dts index 52940e43db9155..92e82b8247b021 100644 --- a/arch/arm64/boot/dts/apple/t8103-j313.dts +++ b/arch/arm64/boot/dts/apple/t8103-j313.dts @@ -174,3 +174,5 @@ &isp { apple,platform-id = <1>; }; + +#include "hwmon-laptop.dtsi" diff --git a/arch/arm64/boot/dts/apple/t8103-j456.dts b/arch/arm64/boot/dts/apple/t8103-j456.dts index a3638871f3660e..2fdbc4061d6048 100644 --- a/arch/arm64/boot/dts/apple/t8103-j456.dts +++ b/arch/arm64/boot/dts/apple/t8103-j456.dts @@ -143,3 +143,5 @@ &isp { apple,platform-id = <2>; }; + +#include "hwmon-fan-dual.dtsi" diff --git a/arch/arm64/boot/dts/apple/t8103-j457.dts b/arch/arm64/boot/dts/apple/t8103-j457.dts index 4c1adb310ba91f..80067e95db1d82 100644 --- a/arch/arm64/boot/dts/apple/t8103-j457.dts +++ b/arch/arm64/boot/dts/apple/t8103-j457.dts @@ -124,3 +124,5 @@ &isp { apple,platform-id = <2>; }; + +#include "hwmon-fan.dtsi" diff --git a/arch/arm64/boot/dts/apple/t8103-jxxx.dtsi b/arch/arm64/boot/dts/apple/t8103-jxxx.dtsi index 7ca9da15c8171d..67a57fc507df92 100644 --- a/arch/arm64/boot/dts/apple/t8103-jxxx.dtsi +++ b/arch/arm64/boot/dts/apple/t8103-jxxx.dtsi @@ -237,4 +237,6 @@ clock-frequency = <900000000>; }; +#include "hwmon-common.dtsi" + #include "spi1-nvram.dtsi" From 7bb0f8b82c3c6b168f35cf6645474824213241cb Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Thu, 11 Jul 2024 21:31:30 +0200 Subject: [PATCH 2190/3902] arm64: dts: apple: t8112: Add SMC hwmon sensors Signed-off-by: Janne Grunau --- arch/arm64/boot/dts/apple/t8112-j413.dts | 2 ++ arch/arm64/boot/dts/apple/t8112-j415.dts | 2 ++ arch/arm64/boot/dts/apple/t8112-j473.dts | 2 ++ arch/arm64/boot/dts/apple/t8112-j493.dts | 3 +++ arch/arm64/boot/dts/apple/t8112-jxxx.dtsi | 2 ++ 5 files changed, 11 insertions(+) diff --git a/arch/arm64/boot/dts/apple/t8112-j413.dts b/arch/arm64/boot/dts/apple/t8112-j413.dts index 5c0cb7fc235ad5..43977e7c9491e7 100644 --- a/arch/arm64/boot/dts/apple/t8112-j413.dts +++ b/arch/arm64/boot/dts/apple/t8112-j413.dts @@ -250,3 +250,5 @@ apple,platform-id = <14>; apple,temporal-filter = <1>; }; + +#include "hwmon-laptop.dtsi" diff --git a/arch/arm64/boot/dts/apple/t8112-j415.dts b/arch/arm64/boot/dts/apple/t8112-j415.dts index 7e39a477728e3f..da41960b2c455a 100644 --- a/arch/arm64/boot/dts/apple/t8112-j415.dts +++ b/arch/arm64/boot/dts/apple/t8112-j415.dts @@ -276,3 +276,5 @@ apple,platform-id = <15>; apple,temporal-filter = <1>; }; + +#include "hwmon-laptop.dtsi" diff --git a/arch/arm64/boot/dts/apple/t8112-j473.dts b/arch/arm64/boot/dts/apple/t8112-j473.dts index 2cff118565f9e5..4fc96779806ea3 100644 --- a/arch/arm64/boot/dts/apple/t8112-j473.dts +++ b/arch/arm64/boot/dts/apple/t8112-j473.dts @@ -213,3 +213,5 @@ &gpu { apple,perf-base-pstate = <3>; }; + +#include "hwmon-mini.dtsi" diff --git a/arch/arm64/boot/dts/apple/t8112-j493.dts b/arch/arm64/boot/dts/apple/t8112-j493.dts index b6c60ec51b4a1a..7ff910ef8925ab 100644 --- a/arch/arm64/boot/dts/apple/t8112-j493.dts +++ b/arch/arm64/boot/dts/apple/t8112-j493.dts @@ -303,3 +303,6 @@ &isp { apple,platform-id = <6>; }; + +#include "hwmon-fan.dtsi" +#include "hwmon-laptop.dtsi" diff --git a/arch/arm64/boot/dts/apple/t8112-jxxx.dtsi b/arch/arm64/boot/dts/apple/t8112-jxxx.dtsi index 35565dbf535381..fb957f785d82c5 100644 --- a/arch/arm64/boot/dts/apple/t8112-jxxx.dtsi +++ b/arch/arm64/boot/dts/apple/t8112-jxxx.dtsi @@ -218,4 +218,6 @@ clock-frequency = <900000000>; }; +#include "hwmon-common.dtsi" + #include "spi1-nvram.dtsi" From 94be044f860a3dd360c499647f8fd0920ef0a5cf Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Thu, 11 Jul 2024 21:34:02 +0200 Subject: [PATCH 2191/3902] arm64: dts: apple: t600x-j3xx: Add SMC hwmon sensors Signed-off-by: Janne Grunau --- arch/arm64/boot/dts/apple/t6001-j375c.dts | 2 ++ arch/arm64/boot/dts/apple/t6002-j375d.dts | 2 ++ arch/arm64/boot/dts/apple/t600x-j314-j316.dtsi | 4 ++++ arch/arm64/boot/dts/apple/t600x-j375.dtsi | 2 ++ 4 files changed, 10 insertions(+) diff --git a/arch/arm64/boot/dts/apple/t6001-j375c.dts b/arch/arm64/boot/dts/apple/t6001-j375c.dts index 4028571143ac87..f3f98f03800908 100644 --- a/arch/arm64/boot/dts/apple/t6001-j375c.dts +++ b/arch/arm64/boot/dts/apple/t6001-j375c.dts @@ -58,3 +58,5 @@ apple,ppm-ki = <5.8>; apple,ppm-kp = <0.355>; }; + +#include "hwmon-fan-dual.dtsi" diff --git a/arch/arm64/boot/dts/apple/t6002-j375d.dts b/arch/arm64/boot/dts/apple/t6002-j375d.dts index e17c71ff18913c..5cf30cd162b679 100644 --- a/arch/arm64/boot/dts/apple/t6002-j375d.dts +++ b/arch/arm64/boot/dts/apple/t6002-j375d.dts @@ -226,3 +226,5 @@ apple,ppm-ki = <5.8>; apple,ppm-kp = <0.355>; }; + +#include "hwmon-fan-dual.dtsi" diff --git a/arch/arm64/boot/dts/apple/t600x-j314-j316.dtsi b/arch/arm64/boot/dts/apple/t600x-j314-j316.dtsi index 4ebaa197a2370c..dccf030556db8f 100644 --- a/arch/arm64/boot/dts/apple/t600x-j314-j316.dtsi +++ b/arch/arm64/boot/dts/apple/t600x-j314-j316.dtsi @@ -597,3 +597,7 @@ &isp { apple,platform-id = <3>; }; + +#include "hwmon-common.dtsi" +#include "hwmon-fan-dual.dtsi" +#include "hwmon-laptop.dtsi" diff --git a/arch/arm64/boot/dts/apple/t600x-j375.dtsi b/arch/arm64/boot/dts/apple/t600x-j375.dtsi index f95559b9f8797f..a5ee97241214d6 100644 --- a/arch/arm64/boot/dts/apple/t600x-j375.dtsi +++ b/arch/arm64/boot/dts/apple/t600x-j375.dtsi @@ -486,3 +486,5 @@ }; #include "spi1-nvram.dtsi" + +#include "hwmon-common.dtsi" From fd5b487296f4885f9ec16af05d23b4bac8c8396f Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Thu, 11 Jul 2024 21:36:29 +0200 Subject: [PATCH 2192/3902] arm64: dts: apple: t602x-j4xx: Add SMC hwmon sensors Signed-off-by: Janne Grunau --- arch/arm64/boot/dts/apple/t6020-j474s.dts | 2 ++ arch/arm64/boot/dts/apple/t6021-j475c.dts | 2 ++ arch/arm64/boot/dts/apple/t6022-j475d.dts | 2 ++ 3 files changed, 6 insertions(+) diff --git a/arch/arm64/boot/dts/apple/t6020-j474s.dts b/arch/arm64/boot/dts/apple/t6020-j474s.dts index f904582f98bb91..12dfe9693502ad 100644 --- a/arch/arm64/boot/dts/apple/t6020-j474s.dts +++ b/arch/arm64/boot/dts/apple/t6020-j474s.dts @@ -124,3 +124,5 @@ /* Apple does not do this, but they probably should */ apple,perf-base-pstate = <3>; }; + +#include "hwmon-mini.dtsi" diff --git a/arch/arm64/boot/dts/apple/t6021-j475c.dts b/arch/arm64/boot/dts/apple/t6021-j475c.dts index 09f477dbdf28b1..e4321cfc556838 100644 --- a/arch/arm64/boot/dts/apple/t6021-j475c.dts +++ b/arch/arm64/boot/dts/apple/t6021-j475c.dts @@ -123,3 +123,5 @@ apple,perf-boost-min-util = <75>; apple,perf-tgt-utilization = <70>; }; + +#include "hwmon-fan-dual.dtsi" diff --git a/arch/arm64/boot/dts/apple/t6022-j475d.dts b/arch/arm64/boot/dts/apple/t6022-j475d.dts index a9020a23a7e976..cdfc78a1703c7a 100644 --- a/arch/arm64/boot/dts/apple/t6022-j475d.dts +++ b/arch/arm64/boot/dts/apple/t6022-j475d.dts @@ -92,3 +92,5 @@ compatible = "apple,j475-macaudio", "apple,j375-macaudio", "apple,macaudio"; model = "Mac Studio J475"; }; + +#include "hwmon-fan-dual.dtsi" From 545059f9c83a823883cc0cc80547b9c23802348a Mon Sep 17 00:00:00 2001 From: Sasha Finkelstein Date: Sat, 9 Nov 2024 18:44:45 +0100 Subject: [PATCH 2193/3902] arm64: dts: apple: Add AOP and subdevices Signed-off-by: Sasha Finkelstein --- arch/arm64/boot/dts/apple/t600x-die0.dtsi | 62 ++++++++++++++++++++++ arch/arm64/boot/dts/apple/t602x-die0.dtsi | 63 +++++++++++++++++++++++ arch/arm64/boot/dts/apple/t8103.dtsi | 62 ++++++++++++++++++++++ arch/arm64/boot/dts/apple/t8112.dtsi | 62 ++++++++++++++++++++++ 4 files changed, 249 insertions(+) diff --git a/arch/arm64/boot/dts/apple/t600x-die0.dtsi b/arch/arm64/boot/dts/apple/t600x-die0.dtsi index 1fc9f7ad4a6ed6..9a32e23e8c844a 100644 --- a/arch/arm64/boot/dts/apple/t600x-die0.dtsi +++ b/arch/arm64/boot/dts/apple/t600x-die0.dtsi @@ -167,6 +167,68 @@ interrupts = ; }; + aop_mbox: mbox@293408000 { + compatible = "apple,t6000-asc-mailbox", "apple,asc-mailbox-v4"; + reg = <0x2 0x93408000 0x0 0x4000>; + interrupt-parent = <&aic>; + interrupts = , + , + , + ; + interrupt-names = "send-empty", "send-not-empty", + "recv-empty", "recv-not-empty"; + #mbox-cells = <0>; + status = "disabled"; + }; + + aop_dart: iommu@293808000 { + compatible = "apple,t6000-dart"; + reg = <0x2 0x93808000 0x0 0x4000>; + #iommu-cells = <1>; + interrupt-parent = <&aic>; + interrupts = ; + status = "disabled"; + }; + + aop_admac: dma-controller@293980000 { + compatible = "apple,t6000-admac", "apple,admac"; + reg = <0x2 0x93980000 0x0 0x34000>; + #dma-cells = <1>; + dma-channels = <16>; + interrupts-extended = <0>, + <0>, + <&aic AIC_IRQ 0 600 IRQ_TYPE_LEVEL_HIGH>, + <0>; + iommus = <&aop_dart 7>; + status = "disabled"; + }; + + aop: aop@293c00000 { + compatible = "apple,t6000-aop"; + reg = <0x2 0x93c00000 0x0 0x250000>, + <0x2 0x93400000 0x0 0x6c000>; + mboxes = <&aop_mbox>; + mbox-names = "mbox"; + iommus = <&aop_dart 0>; + + status = "disabled"; + + aop_audio: audio { + compatible = "apple,t6000-aop-audio", "apple,aop-audio"; + dmas = <&aop_admac 1>; + dma-names = "dma"; + }; + + aop_als: als { + compatible = "apple,t6000-aop-als", "apple,aop-als"; + // intentionally empty + }; + + las { + compatible = "apple,t6000-aop-las", "apple,aop-las"; + }; + }; + disp0_dart: iommu@38b304000 { compatible = "apple,t6000-dart"; reg = <0x3 0x8b304000 0x0 0x4000>; diff --git a/arch/arm64/boot/dts/apple/t602x-die0.dtsi b/arch/arm64/boot/dts/apple/t602x-die0.dtsi index 59181d7c6a6b8c..d5126174c8a557 100644 --- a/arch/arm64/boot/dts/apple/t602x-die0.dtsi +++ b/arch/arm64/boot/dts/apple/t602x-die0.dtsi @@ -165,6 +165,69 @@ ; }; + aop_mbox: mbox@2a6408000 { + compatible = "apple,t6020-asc-mailbox", "apple,asc-mailbox-v4"; + reg = <0x2 0xa6408000 0x0 0x4000>; + interrupt-parent = <&aic>; + interrupts = , + , + , + ; + interrupt-names = "send-empty", "send-not-empty", + "recv-empty", "recv-not-empty"; + #mbox-cells = <0>; + status = "disabled"; + }; + + aop_dart: iommu@2a6808000 { + compatible = "apple,t6020-dart", "apple,t8110-dart"; + reg = <0x2 0xa6808000 0x0 0x4000>; + #iommu-cells = <1>; + interrupt-parent = <&aic>; + interrupts = ; + status = "disabled"; + // apple,dma-range = <0x100 0x0 0x300 0x0>; + }; + + aop_admac: dma-controller@2a6980000 { + compatible = "apple,t6020-admac", "apple,admac"; + reg = <0x2 0xa6980000 0x0 0x34000>; + #dma-cells = <1>; + dma-channels = <16>; + interrupts-extended = <0>, + <0>, + <&aic AIC_IRQ 0 631 IRQ_TYPE_LEVEL_HIGH>, + <0>; + iommus = <&aop_dart 10>; + status = "disabled"; + }; + + aop: aop@2a6c00000 { + compatible = "apple,t6020-aop"; + reg = <0x2 0xa6c00000 0x0 0x250000>, + <0x2 0xa6400000 0x0 0x6c000>; + mboxes = <&aop_mbox>; + mbox-names = "mbox"; + iommus = <&aop_dart 0>; + + status = "disabled"; + + aop_audio: audio { + compatible = "apple,t6020-aop-audio", "apple,aop-audio"; + dmas = <&aop_admac 1>; + dma-names = "dma"; + }; + + aop_als: als { + compatible = "apple,t6020-aop-als", "apple,aop-als"; + // intentionally empty + }; + + las { + compatible = "apple,t6020-aop-las", "apple,aop-las"; + }; + }; + mtp: mtp@2a9400000 { compatible = "apple,t6020-mtp", "apple,t6020-rtk-helper-asc4", "apple,mtp", "apple,rtk-helper-asc4"; reg = <0x2 0xa9400000 0x0 0x4000>, diff --git a/arch/arm64/boot/dts/apple/t8103.dtsi b/arch/arm64/boot/dts/apple/t8103.dtsi index e7765ffb9f2202..55b654c3f3de97 100644 --- a/arch/arm64/boot/dts/apple/t8103.dtsi +++ b/arch/arm64/boot/dts/apple/t8103.dtsi @@ -1310,6 +1310,68 @@ ; }; + aop_mbox: mbox@24a408000 { + compatible = "apple,t8103-asc-mailbox", "apple,asc-mailbox-v4"; + reg = <0x2 0x4a408000 0x0 0x4000>; + interrupt-parent = <&aic>; + interrupts = , + , + , + ; + interrupt-names = "send-empty", "send-not-empty", + "recv-empty", "recv-not-empty"; + #mbox-cells = <0>; + status = "disabled"; + }; + + aop_dart: iommu@24a808000 { + compatible = "apple,t8103-dart"; + reg = <0x2 0x4a808000 0x0 0x4000>; + #iommu-cells = <1>; + interrupt-parent = <&aic>; + interrupts = ; + status = "disabled"; + }; + + aop_admac: dma-controller@24a980000 { + compatible = "apple,t8103-admac", "apple,admac"; + reg = <0x2 0x4a980000 0x0 0x34000>; + #dma-cells = <1>; + dma-channels = <16>; + interrupts-extended = <0>, + <0>, + <&aic AIC_IRQ 321 IRQ_TYPE_LEVEL_HIGH>, + <0>; + iommus = <&aop_dart 7>; + status = "disabled"; + }; + + aop: aop@24ac00000 { + compatible = "apple,t8103-aop"; + reg = <0x2 0x4ac00000 0x0 0x1e0000>, + <0x2 0x4a400000 0x0 0x6c000>; + mboxes = <&aop_mbox>; + mbox-names = "mbox"; + iommus = <&aop_dart 0>; + + status = "disabled"; + + aop_audio: audio { + compatible = "apple,t8103-aop-audio", "apple,aop-audio"; + dmas = <&aop_admac 1>; + dma-names = "dma"; + }; + + aop_als: als { + compatible = "apple,t8103-aop-als", "apple,aop-als"; + // intentionally empty + }; + + las { + compatible = "apple,t8103-aop-las", "apple,aop-las"; + }; + }; + dispext0_dart: iommu@271304000 { compatible = "apple,t8103-dart"; reg = <0x2 0x71304000 0x0 0x4000>; diff --git a/arch/arm64/boot/dts/apple/t8112.dtsi b/arch/arm64/boot/dts/apple/t8112.dtsi index 959dff0f31fc8b..3de977d0117f66 100644 --- a/arch/arm64/boot/dts/apple/t8112.dtsi +++ b/arch/arm64/boot/dts/apple/t8112.dtsi @@ -1423,6 +1423,68 @@ ; }; + aop_mbox: mbox@24a408000 { + compatible = "apple,t8112-asc-mailbox", "apple,asc-mailbox-v4"; + reg = <0x2 0x4a408000 0x0 0x4000>; + interrupt-parent = <&aic>; + interrupts = , + , + , + ; + interrupt-names = "send-empty", "send-not-empty", + "recv-empty", "recv-not-empty"; + #mbox-cells = <0>; + status = "disabled"; + }; + + aop_dart: iommu@24a808000 { + compatible = "apple,t8112-dart", "apple,t8110-dart"; + reg = <0x2 0x4a808000 0x0 0x4000>; + #iommu-cells = <1>; + interrupt-parent = <&aic>; + interrupts = ; + status = "disabled"; + }; + + aop_admac: dma-controller@24a980000 { + compatible = "apple,t8112-admac", "apple,admac"; + reg = <0x2 0x4a980000 0x0 0x34000>; + #dma-cells = <1>; + dma-channels = <16>; + interrupts-extended = <0>, + <0>, + <&aic AIC_IRQ 359 IRQ_TYPE_LEVEL_HIGH>, + <0>; + iommus = <&aop_dart 10>; + status = "disabled"; + }; + + aop: aop@24ac00000 { + compatible = "apple,t8112-aop"; + reg = <0x2 0x4ac00000 0x0 0x1e0000>, + <0x2 0x4a400000 0x0 0x6c000>; + mboxes = <&aop_mbox>; + mbox-names = "mbox"; + iommus = <&aop_dart 0>; + + status = "disabled"; + + aop_audio: audio { + compatible = "apple,t8112-aop-audio", "apple,aop-audio"; + dmas = <&aop_admac 1>; + dma-names = "dma"; + }; + + aop_als: als { + compatible = "apple,t8112-aop-als", "apple,aop-als"; + // intentionally empty + }; + + las { + compatible = "apple,t8112-aop-las", "apple,aop-las"; + }; + }; + mtp: mtp@24e400000 { compatible = "apple,t8112-mtp", "apple,t8112-rtk-helper-asc4", "apple,mtp", "apple,rtk-helper-asc4"; reg = <0x2 0x4e400000 0x0 0x4000>, From 7b79398b5ddff38fda791915904e43cad6063f0f Mon Sep 17 00:00:00 2001 From: Sasha Finkelstein Date: Sun, 10 Nov 2024 23:25:31 +0100 Subject: [PATCH 2194/3902] arm64: dts: apple: Add SEP device tree nodes Signed-off-by: Sasha Finkelstein --- arch/arm64/boot/dts/apple/t600x-die0.dtsi | 31 ++++++++++++++++++++++ arch/arm64/boot/dts/apple/t602x-die0.dtsi | 32 +++++++++++++++++++++++ arch/arm64/boot/dts/apple/t8103.dtsi | 30 +++++++++++++++++++++ arch/arm64/boot/dts/apple/t8112.dtsi | 30 +++++++++++++++++++++ 4 files changed, 123 insertions(+) diff --git a/arch/arm64/boot/dts/apple/t600x-die0.dtsi b/arch/arm64/boot/dts/apple/t600x-die0.dtsi index 9a32e23e8c844a..86ce1dcc6059ee 100644 --- a/arch/arm64/boot/dts/apple/t600x-die0.dtsi +++ b/arch/arm64/boot/dts/apple/t600x-die0.dtsi @@ -250,6 +250,37 @@ apple,dma-range = <0x1f0 0x0 0x0 0xfc000000>; }; + sep_dart: iommu@3952c0000 { + compatible = "apple,t6000-dart"; + reg = <0x3 0x952c0000 0x0 0x4000>; + #iommu-cells = <1>; + interrupt-parent = <&aic>; + interrupts = ; + }; + + sep: sep@396400000 { + compatible = "apple,sep"; + reg = <0x3 0x96400000 0x0 0x6C000>; + mboxes = <&sep_mbox>; + mbox-names = "mbox"; + iommus = <&sep_dart 0>; + power-domains = <&ps_sep>; + status = "disabled"; + }; + + sep_mbox: mbox@396408000 { + compatible = "apple,t6000-asc-mailbox", "apple,asc-mailbox-v4"; + reg = <0x3 0x96408000 0x0 0x4000>; + interrupt-parent = <&aic>; + interrupts = , + , + , + ; + interrupt-names = "send-empty", "send-not-empty", + "recv-empty", "recv-not-empty"; + #mbox-cells = <0>; + }; + dpaudio0: audio-controller@39b500000 { compatible = "apple,t6000-dpaudio", "apple,dpaudio"; reg = <0x3 0x9b500000 0x0 0x4000>; diff --git a/arch/arm64/boot/dts/apple/t602x-die0.dtsi b/arch/arm64/boot/dts/apple/t602x-die0.dtsi index d5126174c8a557..876f001a8961ac 100644 --- a/arch/arm64/boot/dts/apple/t602x-die0.dtsi +++ b/arch/arm64/boot/dts/apple/t602x-die0.dtsi @@ -438,6 +438,38 @@ phandle = <&display>; }; + sep_dart: iommu@394ac0000 { + compatible = "apple,t6020-dart", "apple,t8110-dart"; + reg = <0x3 0x94ac0000 0x0 0x4000>; + #iommu-cells = <1>; + interrupt-parent = <&aic>; + interrupts = ; + status = "disabled"; + }; + + sep: sep@396400000 { + compatible = "apple,sep"; + reg = <0x3 0x96400000 0x0 0x6C000>; + mboxes = <&sep_mbox>; + mbox-names = "mbox"; + iommus = <&sep_dart 0>; + power-domains = <&ps_sep>; + status = "disabled"; + }; + + sep_mbox: mbox@396408000 { + compatible = "apple,t6020-asc-mailbox", "apple,asc-mailbox-v4"; + reg = <0x3 0x96408000 0x0 0x4000>; + interrupt-parent = <&aic>; + interrupts = , + , + , + ; + interrupt-names = "send-empty", "send-not-empty", + "recv-empty", "recv-not-empty"; + #mbox-cells = <0>; + }; + fpwm0: pwm@39b030000 { compatible = "apple,t6020-fpwm", "apple,s5l-fpwm"; reg = <0x3 0x9b030000 0x0 0x4000>; diff --git a/arch/arm64/boot/dts/apple/t8103.dtsi b/arch/arm64/boot/dts/apple/t8103.dtsi index 55b654c3f3de97..702ca57224c0dd 100644 --- a/arch/arm64/boot/dts/apple/t8103.dtsi +++ b/arch/arm64/boot/dts/apple/t8103.dtsi @@ -1289,6 +1289,36 @@ ; }; + sep_dart: iommu@2412c0000 { + compatible = "apple,t8103-dart"; + reg = <0x2 0x412c0000 0x0 0x4000>; + #iommu-cells = <1>; + interrupt-parent = <&aic>; + interrupts = ; + }; + + sep: sep@242400000 { + compatible = "apple,sep"; + reg = <0x2 0x42400000 0x0 0x6C000>; + mboxes = <&sep_mbox>; + mbox-names = "mbox"; + iommus = <&sep_dart 0>; + status = "disabled"; + }; + + sep_mbox: mbox@242408000 { + compatible = "apple,t8103-asc-mailbox", "apple,asc-mailbox-v4"; + reg = <0x2 0x42408000 0x0 0x4000>; + interrupt-parent = <&aic>; + interrupts = , + , + , + ; + interrupt-names = "send-empty", "send-not-empty", + "recv-empty", "recv-not-empty"; + #mbox-cells = <0>; + }; + pinctrl_aop: pinctrl@24a820000 { compatible = "apple,t8103-pinctrl", "apple,pinctrl"; reg = <0x2 0x4a820000 0x0 0x4000>; diff --git a/arch/arm64/boot/dts/apple/t8112.dtsi b/arch/arm64/boot/dts/apple/t8112.dtsi index 3de977d0117f66..764033edb625cd 100644 --- a/arch/arm64/boot/dts/apple/t8112.dtsi +++ b/arch/arm64/boot/dts/apple/t8112.dtsi @@ -1559,6 +1559,36 @@ }; + sep_dart: iommu@25d2c0000 { + compatible = "apple,t8112-dart", "apple,t8110-dart"; + reg = <0x2 0x5d2c0000 0x0 0x4000>; + #iommu-cells = <1>; + interrupt-parent = <&aic>; + interrupts = ; + }; + + sep: sep@25e400000 { + compatible = "apple,sep"; + reg = <0x2 0x5e400000 0x0 0x6C000>; + mboxes = <&sep_mbox>; + mbox-names = "mbox"; + iommus = <&sep_dart 0>; + status = "disabled"; + }; + + sep_mbox: mbox@25e408000 { + compatible = "apple,t8112-asc-mailbox", "apple,asc-mailbox-v4"; + reg = <0x2 0x5e408000 0x0 0x4000>; + interrupt-parent = <&aic>; + interrupts = , + , + , + ; + interrupt-names = "send-empty", "send-not-empty", + "recv-empty", "recv-not-empty"; + #mbox-cells = <0>; + }; + dispext0_dart: iommu@271304000 { compatible = "apple,t8112-dart", "apple,t8110-dart"; reg = <0x2 0x71304000 0x0 0x4000>; From c87483a4dad93a5fd01cac5b9994c2d998ba2c38 Mon Sep 17 00:00:00 2001 From: Sasha Finkelstein Date: Sat, 9 Nov 2024 18:44:45 +0100 Subject: [PATCH 2195/3902] arm64: dts: apple: Add AOP audio identifiers Signed-off-by: Sasha Finkelstein --- arch/arm64/boot/dts/apple/t6000-j314s.dts | 5 +++++ arch/arm64/boot/dts/apple/t6000-j316s.dts | 5 +++++ arch/arm64/boot/dts/apple/t6001-j314c.dts | 5 +++++ arch/arm64/boot/dts/apple/t6001-j316c.dts | 5 +++++ arch/arm64/boot/dts/apple/t6020-j414s.dts | 5 +++++ arch/arm64/boot/dts/apple/t6020-j416s.dts | 5 +++++ arch/arm64/boot/dts/apple/t6021-j414c.dts | 5 +++++ arch/arm64/boot/dts/apple/t6021-j416c.dts | 5 +++++ arch/arm64/boot/dts/apple/t8103-j293.dts | 5 +++++ arch/arm64/boot/dts/apple/t8103-j313.dts | 5 +++++ arch/arm64/boot/dts/apple/t8103-j456.dts | 5 +++++ arch/arm64/boot/dts/apple/t8103-j457.dts | 5 +++++ arch/arm64/boot/dts/apple/t8112-j413.dts | 5 +++++ arch/arm64/boot/dts/apple/t8112-j415.dts | 5 +++++ arch/arm64/boot/dts/apple/t8112-j493.dts | 5 +++++ 15 files changed, 75 insertions(+) diff --git a/arch/arm64/boot/dts/apple/t6000-j314s.dts b/arch/arm64/boot/dts/apple/t6000-j314s.dts index ae79e3236614be..afa86668440f04 100644 --- a/arch/arm64/boot/dts/apple/t6000-j314s.dts +++ b/arch/arm64/boot/dts/apple/t6000-j314s.dts @@ -32,6 +32,11 @@ adj-height-mm = <189>; }; +&aop_audio { + apple,chassis-name = "J314"; + apple,machine-kind = "MacBook Pro"; +}; + &sound { compatible = "apple,j314-macaudio", "apple,macaudio"; model = "MacBook Pro J314"; diff --git a/arch/arm64/boot/dts/apple/t6000-j316s.dts b/arch/arm64/boot/dts/apple/t6000-j316s.dts index 272fa1c1712479..ddfc3c530923c7 100644 --- a/arch/arm64/boot/dts/apple/t6000-j316s.dts +++ b/arch/arm64/boot/dts/apple/t6000-j316s.dts @@ -32,6 +32,11 @@ adj-height-mm = <216>; }; +&aop_audio { + apple,chassis-name = "J316"; + apple,machine-kind = "MacBook Pro"; +}; + &sound { compatible = "apple,j316-macaudio", "apple,macaudio"; model = "MacBook Pro J316"; diff --git a/arch/arm64/boot/dts/apple/t6001-j314c.dts b/arch/arm64/boot/dts/apple/t6001-j314c.dts index 81d34507ed81ff..245df6d03ee422 100644 --- a/arch/arm64/boot/dts/apple/t6001-j314c.dts +++ b/arch/arm64/boot/dts/apple/t6001-j314c.dts @@ -32,6 +32,11 @@ adj-height-mm = <189>; }; +&aop_audio { + apple,chassis-name = "J314"; + apple,machine-kind = "MacBook Pro"; +}; + &sound { compatible = "apple,j314-macaudio", "apple,macaudio"; model = "MacBook Pro J314"; diff --git a/arch/arm64/boot/dts/apple/t6001-j316c.dts b/arch/arm64/boot/dts/apple/t6001-j316c.dts index 564d927f2fecbd..a000d497b705fa 100644 --- a/arch/arm64/boot/dts/apple/t6001-j316c.dts +++ b/arch/arm64/boot/dts/apple/t6001-j316c.dts @@ -32,6 +32,11 @@ adj-height-mm = <216>; }; +&aop_audio { + apple,chassis-name = "J316"; + apple,machine-kind = "MacBook Pro"; +}; + &sound { compatible = "apple,j316-macaudio", "apple,macaudio"; model = "MacBook Pro J316"; diff --git a/arch/arm64/boot/dts/apple/t6020-j414s.dts b/arch/arm64/boot/dts/apple/t6020-j414s.dts index 5dd97df71efc4b..a227069727dd8f 100644 --- a/arch/arm64/boot/dts/apple/t6020-j414s.dts +++ b/arch/arm64/boot/dts/apple/t6020-j414s.dts @@ -32,6 +32,11 @@ adj-height-mm = <189>; }; +&aop_audio { + apple,chassis-name = "J414"; + apple,machine-kind = "MacBook Pro"; +}; + &sound { compatible = "apple,j414-macaudio", "apple,j314-macaudio", "apple,macaudio"; model = "MacBook Pro J414"; diff --git a/arch/arm64/boot/dts/apple/t6020-j416s.dts b/arch/arm64/boot/dts/apple/t6020-j416s.dts index 56ddf7c61de634..3ea2b1d52593e2 100644 --- a/arch/arm64/boot/dts/apple/t6020-j416s.dts +++ b/arch/arm64/boot/dts/apple/t6020-j416s.dts @@ -32,6 +32,11 @@ adj-height-mm = <216>; }; +&aop_audio { + apple,chassis-name = "J416"; + apple,machine-kind = "MacBook Pro"; +}; + &sound { compatible = "apple,j416-macaudio", "apple,j316-macaudio", "apple,macaudio"; model = "MacBook Pro J416"; diff --git a/arch/arm64/boot/dts/apple/t6021-j414c.dts b/arch/arm64/boot/dts/apple/t6021-j414c.dts index 6905c7d39db0ce..fab3b03ff3c452 100644 --- a/arch/arm64/boot/dts/apple/t6021-j414c.dts +++ b/arch/arm64/boot/dts/apple/t6021-j414c.dts @@ -32,6 +32,11 @@ adj-height-mm = <189>; }; +&aop_audio { + apple,chassis-name = "J414"; + apple,machine-kind = "MacBook Pro"; +}; + &sound { compatible = "apple,j414-macaudio", "apple,j314-macaudio", "apple,macaudio"; model = "MacBook Pro J414"; diff --git a/arch/arm64/boot/dts/apple/t6021-j416c.dts b/arch/arm64/boot/dts/apple/t6021-j416c.dts index 786ac2393d7535..b476e235639ffc 100644 --- a/arch/arm64/boot/dts/apple/t6021-j416c.dts +++ b/arch/arm64/boot/dts/apple/t6021-j416c.dts @@ -52,6 +52,11 @@ adj-height-mm = <216>; }; +&aop_audio { + apple,chassis-name = "J416"; + apple,machine-kind = "MacBook Pro"; +}; + &sound { compatible = "apple,j416-macaudio", "apple,j316-macaudio", "apple,macaudio"; model = "MacBook Pro J416"; diff --git a/arch/arm64/boot/dts/apple/t8103-j293.dts b/arch/arm64/boot/dts/apple/t8103-j293.dts index e26329ee7d019d..82773f2468a37f 100644 --- a/arch/arm64/boot/dts/apple/t8103-j293.dts +++ b/arch/arm64/boot/dts/apple/t8103-j293.dts @@ -244,6 +244,11 @@ status = "okay"; }; +&aop_audio { + apple,chassis-name = "J293"; + apple,machine-kind = "MacBook Pro"; +}; + / { sound { compatible = "apple,j293-macaudio", "apple,macaudio"; diff --git a/arch/arm64/boot/dts/apple/t8103-j313.dts b/arch/arm64/boot/dts/apple/t8103-j313.dts index 92e82b8247b021..376f111b34ef22 100644 --- a/arch/arm64/boot/dts/apple/t8103-j313.dts +++ b/arch/arm64/boot/dts/apple/t8103-j313.dts @@ -140,6 +140,11 @@ }; }; +&aop_audio { + apple,chassis-name = "J313"; + apple,machine-kind = "MacBook Air"; +}; + / { sound { compatible = "apple,j313-macaudio", "apple,macaudio"; diff --git a/arch/arm64/boot/dts/apple/t8103-j456.dts b/arch/arm64/boot/dts/apple/t8103-j456.dts index 2fdbc4061d6048..155b4c94636857 100644 --- a/arch/arm64/boot/dts/apple/t8103-j456.dts +++ b/arch/arm64/boot/dts/apple/t8103-j456.dts @@ -116,6 +116,11 @@ }; }; +&aop_audio { + apple,chassis-name = "J456"; + apple,machine-kind = "iMac"; +}; + / { sound { compatible = "apple,j456-macaudio", "apple,macaudio"; diff --git a/arch/arm64/boot/dts/apple/t8103-j457.dts b/arch/arm64/boot/dts/apple/t8103-j457.dts index 80067e95db1d82..7bec55944f6a5d 100644 --- a/arch/arm64/boot/dts/apple/t8103-j457.dts +++ b/arch/arm64/boot/dts/apple/t8103-j457.dts @@ -97,6 +97,11 @@ }; }; +&aop_audio { + apple,chassis-name = "J457"; + apple,machine-kind = "iMac"; +}; + / { sound { compatible = "apple,j457-macaudio", "apple,macaudio"; diff --git a/arch/arm64/boot/dts/apple/t8112-j413.dts b/arch/arm64/boot/dts/apple/t8112-j413.dts index 43977e7c9491e7..e298a3d90a2c4f 100644 --- a/arch/arm64/boot/dts/apple/t8112-j413.dts +++ b/arch/arm64/boot/dts/apple/t8112-j413.dts @@ -179,6 +179,11 @@ status = "okay"; }; +&aop_audio { + apple,chassis-name = "J413"; + apple,machine-kind = "MacBook Air"; +}; + / { sound { compatible = "apple,j413-macaudio", "apple,macaudio"; diff --git a/arch/arm64/boot/dts/apple/t8112-j415.dts b/arch/arm64/boot/dts/apple/t8112-j415.dts index da41960b2c455a..a789f6bc736173 100644 --- a/arch/arm64/boot/dts/apple/t8112-j415.dts +++ b/arch/arm64/boot/dts/apple/t8112-j415.dts @@ -201,6 +201,11 @@ status = "okay"; }; +&aop_audio { + apple,chassis-name = "J415"; + apple,machine-kind = "MacBook Air"; +}; + / { sound { compatible = "apple,j415-macaudio", "apple,macaudio"; diff --git a/arch/arm64/boot/dts/apple/t8112-j493.dts b/arch/arm64/boot/dts/apple/t8112-j493.dts index 7ff910ef8925ab..c7e10df168e832 100644 --- a/arch/arm64/boot/dts/apple/t8112-j493.dts +++ b/arch/arm64/boot/dts/apple/t8112-j493.dts @@ -233,6 +233,11 @@ }; }; +&aop_audio { + apple,chassis-name = "J493"; + apple,machine-kind = "MacBook Pro"; +}; + / { sound { compatible = "apple,j493-macaudio", "apple,macaudio"; From 6035600723e590f995132f57bdf47422488aaebd Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Mon, 30 Dec 2024 14:55:05 +0100 Subject: [PATCH 2196/3902] arm64: dts: apple: t600x-j314-j316: Enable AOP Signed-off-by: Janne Grunau --- arch/arm64/boot/dts/apple/t600x-j314-j316.dtsi | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/arch/arm64/boot/dts/apple/t600x-j314-j316.dtsi b/arch/arm64/boot/dts/apple/t600x-j314-j316.dtsi index dccf030556db8f..5424db9e021e29 100644 --- a/arch/arm64/boot/dts/apple/t600x-j314-j316.dtsi +++ b/arch/arm64/boot/dts/apple/t600x-j314-j316.dtsi @@ -557,6 +557,22 @@ status = "disabled"; }; +&aop_mbox { + status = "okay"; +}; + +&aop_dart { + status = "okay"; +}; + +&aop_admac { + status = "okay"; +}; + +&aop { + status = "okay"; +}; + / { sound: sound { /* compatible is set per machine */ From 3fdc55a52a096a4c4b7a2e082dac9a6c9b602475 Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Mon, 30 Dec 2024 15:08:45 +0100 Subject: [PATCH 2197/3902] arm64: dts: apple: t8103-j293: Enable AOP Signed-off-by: Janne Grunau --- arch/arm64/boot/dts/apple/t8103-j293.dts | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/arch/arm64/boot/dts/apple/t8103-j293.dts b/arch/arm64/boot/dts/apple/t8103-j293.dts index 82773f2468a37f..6fa04626b1d08b 100644 --- a/arch/arm64/boot/dts/apple/t8103-j293.dts +++ b/arch/arm64/boot/dts/apple/t8103-j293.dts @@ -24,6 +24,7 @@ */ aliases { touchbar0 = &touchbar0; + sep = &sep; }; led-controller { @@ -244,6 +245,26 @@ status = "okay"; }; +&aop_mbox { + status = "okay"; +}; + +&aop_dart { + status = "okay"; +}; + +&aop_admac { + status = "okay"; +}; + +&aop { + status = "okay"; +}; + +&sep { + status = "okay"; +}; + &aop_audio { apple,chassis-name = "J293"; apple,machine-kind = "MacBook Pro"; From 95b2e254dabced908295cc8485b33e3c027f9c51 Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Mon, 30 Dec 2024 15:09:31 +0100 Subject: [PATCH 2198/3902] arm64: dts: apple: t8103-j313: Enable AOP Signed-off-by: Janne Grunau --- arch/arm64/boot/dts/apple/t8103-j313.dts | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/arch/arm64/boot/dts/apple/t8103-j313.dts b/arch/arm64/boot/dts/apple/t8103-j313.dts index 376f111b34ef22..883ba4a1f0100a 100644 --- a/arch/arm64/boot/dts/apple/t8103-j313.dts +++ b/arch/arm64/boot/dts/apple/t8103-j313.dts @@ -18,6 +18,10 @@ model = "Apple MacBook Air (M1, 2020)"; chassis-type = "laptop"; + aliases { + sep = &sep; + }; + led-controller { compatible = "pwm-leds"; led-0 { @@ -140,6 +144,26 @@ }; }; +&aop_mbox { + status = "okay"; +}; + +&aop_dart { + status = "okay"; +}; + +&aop_admac { + status = "okay"; +}; + +&aop { + status = "okay"; +}; + +&sep { + status = "okay"; +}; + &aop_audio { apple,chassis-name = "J313"; apple,machine-kind = "MacBook Air"; From fa48a794b5edf6e8720894b562b3506ae72fec63 Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Mon, 30 Dec 2024 15:19:31 +0100 Subject: [PATCH 2199/3902] arm64: dts: apple: t8103-j45x: Enable AOP Probing is blocked by the "apple,no-beamforming" property until userspace is ready. Signed-off-by: Janne Grunau --- arch/arm64/boot/dts/apple/t8103-j456.dts | 21 +++++++++++++++++++++ arch/arm64/boot/dts/apple/t8103-j457.dts | 21 +++++++++++++++++++++ 2 files changed, 42 insertions(+) diff --git a/arch/arm64/boot/dts/apple/t8103-j456.dts b/arch/arm64/boot/dts/apple/t8103-j456.dts index 155b4c94636857..c7da4815fb94c0 100644 --- a/arch/arm64/boot/dts/apple/t8103-j456.dts +++ b/arch/arm64/boot/dts/apple/t8103-j456.dts @@ -116,9 +116,30 @@ }; }; +&aop_mbox { + status = "okay"; +}; + +&aop_dart { + status = "okay"; +}; + +&aop_admac { + status = "okay"; +}; + +&aop { + status = "okay"; +}; + +&sep { + status = "okay"; +}; + &aop_audio { apple,chassis-name = "J456"; apple,machine-kind = "iMac"; + apple,no-beamforming; }; / { diff --git a/arch/arm64/boot/dts/apple/t8103-j457.dts b/arch/arm64/boot/dts/apple/t8103-j457.dts index 7bec55944f6a5d..fc0f28fb1c4367 100644 --- a/arch/arm64/boot/dts/apple/t8103-j457.dts +++ b/arch/arm64/boot/dts/apple/t8103-j457.dts @@ -97,9 +97,30 @@ }; }; +&aop_mbox { + status = "okay"; +}; + +&aop_dart { + status = "okay"; +}; + +&aop_admac { + status = "okay"; +}; + +&aop { + status = "okay"; +}; + +&sep { + status = "okay"; +}; + &aop_audio { apple,chassis-name = "J457"; apple,machine-kind = "iMac"; + apple,no-beamforming; }; / { From e3ce9d28c403b988f045d2a56b76ce6bf9415d5d Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Mon, 30 Dec 2024 15:10:42 +0100 Subject: [PATCH 2200/3902] arm64: dts: apple: t8112-j413: Enable AOP Signed-off-by: Janne Grunau --- arch/arm64/boot/dts/apple/t8112-j413.dts | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/arch/arm64/boot/dts/apple/t8112-j413.dts b/arch/arm64/boot/dts/apple/t8112-j413.dts index e298a3d90a2c4f..20285be747d965 100644 --- a/arch/arm64/boot/dts/apple/t8112-j413.dts +++ b/arch/arm64/boot/dts/apple/t8112-j413.dts @@ -179,6 +179,22 @@ status = "okay"; }; +&aop_mbox { + status = "okay"; +}; + +&aop_dart { + status = "okay"; +}; + +&aop_admac { + status = "okay"; +}; + +&aop { + status = "okay"; +}; + &aop_audio { apple,chassis-name = "J413"; apple,machine-kind = "MacBook Air"; From e6a816ad4e07f30f9b31f8e30c3eef4ac4ebf9c0 Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Mon, 30 Dec 2024 15:11:36 +0100 Subject: [PATCH 2201/3902] arm64: dts: apple: t8112-j415: Enable AOP Signed-off-by: Janne Grunau --- arch/arm64/boot/dts/apple/t8112-j415.dts | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/arch/arm64/boot/dts/apple/t8112-j415.dts b/arch/arm64/boot/dts/apple/t8112-j415.dts index a789f6bc736173..c2c32ca5577eff 100644 --- a/arch/arm64/boot/dts/apple/t8112-j415.dts +++ b/arch/arm64/boot/dts/apple/t8112-j415.dts @@ -201,6 +201,22 @@ status = "okay"; }; +&aop_mbox { + status = "okay"; +}; + +&aop_dart { + status = "okay"; +}; + +&aop_admac { + status = "okay"; +}; + +&aop { + status = "okay"; +}; + &aop_audio { apple,chassis-name = "J415"; apple,machine-kind = "MacBook Air"; From 9b4b6dd2effe0a10bfe396adff9603d55dc99db4 Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Mon, 30 Dec 2024 15:12:15 +0100 Subject: [PATCH 2202/3902] arm64: dts: apple: t8112-j493: Enable AOP Signed-off-by: Janne Grunau --- arch/arm64/boot/dts/apple/t8112-j493.dts | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/arch/arm64/boot/dts/apple/t8112-j493.dts b/arch/arm64/boot/dts/apple/t8112-j493.dts index c7e10df168e832..368c4a9cc01758 100644 --- a/arch/arm64/boot/dts/apple/t8112-j493.dts +++ b/arch/arm64/boot/dts/apple/t8112-j493.dts @@ -233,6 +233,22 @@ }; }; +&aop_mbox { + status = "okay"; +}; + +&aop_dart { + status = "okay"; +}; + +&aop_admac { + status = "okay"; +}; + +&aop { + status = "okay"; +}; + &aop_audio { apple,chassis-name = "J493"; apple,machine-kind = "MacBook Pro"; From 895837a58e7845677b5e6e69c9b2c0dfe5399f95 Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Sat, 6 Sep 2025 11:23:03 +0200 Subject: [PATCH 2203/3902] arm64: dts: apple: t600x: Add "pm_setting" for smc_reboot For backawrds compatibility with the downstream driver. Signed-off-by: Janne Grunau --- arch/arm64/boot/dts/apple/t600x-die0.dtsi | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/arch/arm64/boot/dts/apple/t600x-die0.dtsi b/arch/arm64/boot/dts/apple/t600x-die0.dtsi index 86ce1dcc6059ee..07065f572d7f5d 100644 --- a/arch/arm64/boot/dts/apple/t600x-die0.dtsi +++ b/arch/arm64/boot/dts/apple/t600x-die0.dtsi @@ -62,9 +62,9 @@ smc_reboot: reboot { compatible = "apple,smc-reboot"; nvmem-cells = <&shutdown_flag>, <&boot_stage>, - <&boot_error_count>, <&panic_count>; + <&boot_error_count>, <&panic_count>, <&pm_setting>; nvmem-cell-names = "shutdown_flag", "boot_stage", - "boot_error_count", "panic_count"; + "boot_error_count", "panic_count", "pm_setting"; }; }; From b3beb768fb14242c2c92fece0956e5e46536445c Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Sat, 6 Sep 2025 11:23:03 +0200 Subject: [PATCH 2204/3902] arm64: dts: apple: t8103: Add "pm_setting" for smc_reboot For backawrds compatibility with the downstream driver. Signed-off-by: Janne Grunau --- arch/arm64/boot/dts/apple/t8103.dtsi | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/arch/arm64/boot/dts/apple/t8103.dtsi b/arch/arm64/boot/dts/apple/t8103.dtsi index 702ca57224c0dd..55df0e054ba443 100644 --- a/arch/arm64/boot/dts/apple/t8103.dtsi +++ b/arch/arm64/boot/dts/apple/t8103.dtsi @@ -1249,9 +1249,9 @@ smc_reboot: reboot { compatible = "apple,smc-reboot"; nvmem-cells = <&shutdown_flag>, <&boot_stage>, - <&boot_error_count>, <&panic_count>; + <&boot_error_count>, <&panic_count>, <&pm_setting>; nvmem-cell-names = "shutdown_flag", "boot_stage", - "boot_error_count", "panic_count"; + "boot_error_count", "panic_count", "pm_setting"; }; }; From 111fc6b7f46b4d382dc1ac9aa6a58645b6698601 Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Sat, 6 Sep 2025 11:23:04 +0200 Subject: [PATCH 2205/3902] arm64: dts: apple: t8112: Add "pm_setting" for smc_reboot For backawrds compatibility with the downstream driver. Signed-off-by: Janne Grunau --- arch/arm64/boot/dts/apple/t8112.dtsi | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/arch/arm64/boot/dts/apple/t8112.dtsi b/arch/arm64/boot/dts/apple/t8112.dtsi index 764033edb625cd..0d92dafff93107 100644 --- a/arch/arm64/boot/dts/apple/t8112.dtsi +++ b/arch/arm64/boot/dts/apple/t8112.dtsi @@ -1362,9 +1362,9 @@ smc_reboot: reboot { compatible = "apple,smc-reboot"; nvmem-cells = <&shutdown_flag>, <&boot_stage>, - <&boot_error_count>, <&panic_count>; + <&boot_error_count>, <&panic_count>, <&pm_setting>; nvmem-cell-names = "shutdown_flag", "boot_stage", - "boot_error_count", "panic_count"; + "boot_error_count", "panic_count", "pm_setting"; }; }; From dc9a3acbef0787dbc2fce4d2977cdb8254b32d20 Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Fri, 26 Sep 2025 10:24:04 +0200 Subject: [PATCH 2206/3902] arm64: dts: apple: Add SMC hwmon node for t600x,t602x,t8103,t8112 Signed-off-by: Janne Grunau --- arch/arm64/boot/dts/apple/t600x-die0.dtsi | 4 ++++ arch/arm64/boot/dts/apple/t602x-die0.dtsi | 4 ++++ arch/arm64/boot/dts/apple/t8103.dtsi | 4 ++++ arch/arm64/boot/dts/apple/t8112.dtsi | 4 ++++ 4 files changed, 16 insertions(+) diff --git a/arch/arm64/boot/dts/apple/t600x-die0.dtsi b/arch/arm64/boot/dts/apple/t600x-die0.dtsi index 07065f572d7f5d..95ac52ef753a62 100644 --- a/arch/arm64/boot/dts/apple/t600x-die0.dtsi +++ b/arch/arm64/boot/dts/apple/t600x-die0.dtsi @@ -53,6 +53,10 @@ #gpio-cells = <2>; }; + smc_hwmon: hwmon { + compatible = "apple,smc-hwmon"; + }; + smc_rtc: rtc { compatible = "apple,smc-rtc"; nvmem-cells = <&rtc_offset>; diff --git a/arch/arm64/boot/dts/apple/t602x-die0.dtsi b/arch/arm64/boot/dts/apple/t602x-die0.dtsi index 876f001a8961ac..8221037da27000 100644 --- a/arch/arm64/boot/dts/apple/t602x-die0.dtsi +++ b/arch/arm64/boot/dts/apple/t602x-die0.dtsi @@ -116,6 +116,10 @@ #gpio-cells = <2>; }; + smc_hwmon: hwmon { + compatible = "apple,smc-hwmon"; + }; + smc_rtc: rtc { compatible = "apple,smc-rtc"; nvmem-cells = <&rtc_offset>; diff --git a/arch/arm64/boot/dts/apple/t8103.dtsi b/arch/arm64/boot/dts/apple/t8103.dtsi index 55df0e054ba443..98a25e4d26f2f1 100644 --- a/arch/arm64/boot/dts/apple/t8103.dtsi +++ b/arch/arm64/boot/dts/apple/t8103.dtsi @@ -1240,6 +1240,10 @@ #gpio-cells = <2>; }; + smc_hwmon: hwmon { + compatible = "apple,smc-hwmon"; + }; + smc_rtc: rtc { compatible = "apple,smc-rtc"; nvmem-cells = <&rtc_offset>; diff --git a/arch/arm64/boot/dts/apple/t8112.dtsi b/arch/arm64/boot/dts/apple/t8112.dtsi index 0d92dafff93107..3789be15f95a75 100644 --- a/arch/arm64/boot/dts/apple/t8112.dtsi +++ b/arch/arm64/boot/dts/apple/t8112.dtsi @@ -1353,6 +1353,10 @@ #gpio-cells = <2>; }; + smc_hwmon: hwmon { + compatible = "apple,smc-hwmon"; + }; + smc_rtc: rtc { compatible = "apple,smc-rtc"; nvmem-cells = <&rtc_offset>; From 9481e29388f573a9d8863472e920ef76d3474bd0 Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Fri, 26 Sep 2025 10:25:37 +0200 Subject: [PATCH 2207/3902] arm64: dts: apple: Adjust all hwmon sensors for upstream driver Signed-off-by: Janne Grunau --- arch/arm64/boot/dts/apple/hwmon-common.dtsi | 58 ++++++++----------- arch/arm64/boot/dts/apple/hwmon-fan-dual.dtsi | 26 ++++----- arch/arm64/boot/dts/apple/hwmon-fan.dtsi | 20 +++---- arch/arm64/boot/dts/apple/hwmon-laptop.dtsi | 56 ++++++++---------- arch/arm64/boot/dts/apple/hwmon-mini.dtsi | 12 ++-- 5 files changed, 71 insertions(+), 101 deletions(-) diff --git a/arch/arm64/boot/dts/apple/hwmon-common.dtsi b/arch/arm64/boot/dts/apple/hwmon-common.dtsi index 1f9a2435e14cb7..2a74d9a114abb6 100644 --- a/arch/arm64/boot/dts/apple/hwmon-common.dtsi +++ b/arch/arm64/boot/dts/apple/hwmon-common.dtsi @@ -5,39 +5,29 @@ * Copyright The Asahi Linux Contributors */ -&smc { - hwmon { - apple,power-keys { - power-PSTR { - apple,key-id = "PSTR"; - label = "Total System Power"; - }; - power-PDTR { - apple,key-id = "PDTR"; - label = "AC Input Power"; - }; - power-PMVR { - apple,key-id = "PMVR"; - label = "3.8 V Rail Power"; - }; - }; - apple,temp-keys { - temp-TH0x { - apple,key-id = "TH0x"; - label = "NAND Flash Temperature"; - }; - }; - apple,volt-keys { - volt-VD0R { - apple,key-id = "VD0R"; - label = "AC Input Voltage"; - }; - }; - apple,current-keys { - current-ID0R { - apple,key-id = "ID0R"; - label = "AC Input Current"; - }; - }; +&smc_hwmon { + power-PSTR { + apple,key-id = "PSTR"; + label = "Total System Power"; + }; + power-PDTR { + apple,key-id = "PDTR"; + label = "AC Input Power"; + }; + power-PMVR { + apple,key-id = "PMVR"; + label = "3.8 V Rail Power"; + }; + temperature-TH0x { + apple,key-id = "TH0x"; + label = "NAND Flash Temperature"; + }; + voltage-VD0R { + apple,key-id = "VD0R"; + label = "AC Input Voltage"; + }; + current-ID0R { + apple,key-id = "ID0R"; + label = "AC Input Current"; }; }; diff --git a/arch/arm64/boot/dts/apple/hwmon-fan-dual.dtsi b/arch/arm64/boot/dts/apple/hwmon-fan-dual.dtsi index 782b6051a3866e..61c34692f1cd5a 100644 --- a/arch/arm64/boot/dts/apple/hwmon-fan-dual.dtsi +++ b/arch/arm64/boot/dts/apple/hwmon-fan-dual.dtsi @@ -7,20 +7,16 @@ #include "hwmon-fan.dtsi" -&smc { - hwmon { - apple,fan-keys { - fan-F0Ac { - label = "Fan 1"; - }; - fan-F1Ac { - apple,key-id = "F1Ac"; - label = "Fan 2"; - apple,fan-minimum = "F1Mn"; - apple,fan-maximum = "F1Mx"; - apple,fan-target = "F1Tg"; - apple,fan-mode = "F1Md"; - }; - }; +&smc_hwmon { + fan-F0Ac { + label = "Fan 1"; + }; + fan-F1Ac { + apple,key-id = "F1Ac"; + label = "Fan 2"; + apple,fan-minimum = "F1Mn"; + apple,fan-maximum = "F1Mx"; + apple,fan-target = "F1Tg"; + apple,fan-mode = "F1Md"; }; }; diff --git a/arch/arm64/boot/dts/apple/hwmon-fan.dtsi b/arch/arm64/boot/dts/apple/hwmon-fan.dtsi index 8f329ac4ff9fef..180eb8d7441f44 100644 --- a/arch/arm64/boot/dts/apple/hwmon-fan.dtsi +++ b/arch/arm64/boot/dts/apple/hwmon-fan.dtsi @@ -5,17 +5,13 @@ * Fan hwmon sensors for machines with a single fan. */ -&smc { - hwmon { - apple,fan-keys { - fan-F0Ac { - apple,key-id = "F0Ac"; - label = "Fan"; - apple,fan-minimum = "F0Mn"; - apple,fan-maximum = "F0Mx"; - apple,fan-target = "F0Tg"; - apple,fan-mode = "F0Md"; - }; - }; +&smc_hwmon { + fan-F0Ac { + apple,key-id = "F0Ac"; + label = "Fan"; + apple,fan-minimum = "F0Mn"; + apple,fan-maximum = "F0Mx"; + apple,fan-target = "F0Tg"; + apple,fan-mode = "F0Md"; }; }; diff --git a/arch/arm64/boot/dts/apple/hwmon-laptop.dtsi b/arch/arm64/boot/dts/apple/hwmon-laptop.dtsi index 2583ef379dfac9..4afb91ee69fe76 100644 --- a/arch/arm64/boot/dts/apple/hwmon-laptop.dtsi +++ b/arch/arm64/boot/dts/apple/hwmon-laptop.dtsi @@ -5,37 +5,29 @@ * Copyright The Asahi Linux Contributors */ -&smc { - hwmon { - apple,power-keys { - power-PHPC { - apple,key-id = "PHPC"; - label = "Heatpipe Power"; - }; - }; - apple,temp-keys { - temp-TB0T { - apple,key-id = "TB0T"; - label = "Battery Hotspot"; - }; - temp-TCHP { - apple,key-id = "TCHP"; - label = "Charge Regulator Temp"; - }; - temp-TW0P { - apple,key-id = "TW0P"; - label = "WiFi/BT Module Temp"; - }; - }; - apple,volt-keys { - volt-SBAV { - apple,key-id = "SBAV"; - label = "Battery Voltage"; - }; - volt-VD0R { - apple,key-id = "VD0R"; - label = "Charger Input Voltage"; - }; - }; +&smc_hwmon { + power-PHPC { + apple,key-id = "PHPC"; + label = "Heatpipe Power"; + }; + temperature-TB0T { + apple,key-id = "TB0T"; + label = "Battery Hotspot"; + }; + temperature-TCHP { + apple,key-id = "TCHP"; + label = "Charge Regulator Temp"; + }; + temperature-TW0P { + apple,key-id = "TW0P"; + label = "WiFi/BT Module Temp"; + }; + voltage-SBAV { + apple,key-id = "SBAV"; + label = "Battery Voltage"; + }; + voltage-VD0R { + apple,key-id = "VD0R"; + label = "Charger Input Voltage"; }; }; diff --git a/arch/arm64/boot/dts/apple/hwmon-mini.dtsi b/arch/arm64/boot/dts/apple/hwmon-mini.dtsi index bd0c22786d4226..7fd86e911acfe7 100644 --- a/arch/arm64/boot/dts/apple/hwmon-mini.dtsi +++ b/arch/arm64/boot/dts/apple/hwmon-mini.dtsi @@ -8,13 +8,9 @@ #include "hwmon-fan.dtsi" -&smc { - hwmon { - apple,temp-keys { - temp-TW0P { - apple,key-id = "TW0P"; - label = "WiFi/BT Module Temp"; - }; - }; +&smc_hwmon { + temperature-TW0P { + apple,key-id = "TW0P"; + label = "WiFi/BT Module Temp"; }; }; From ae4d6290bb1d898fa76c604c20bcea710f4363b3 Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Tue, 16 Dec 2025 21:50:05 +0100 Subject: [PATCH 2208/3902] arm64: apple: t602x: Remove disabled status from uat reserved-mem regions m1n1 unfortunately doesn't enable these. Drop 3 months after a m1n1 which enables these is released. Signed-off-by: Janne Grunau --- arch/arm64/boot/dts/apple/t602x-common.dtsi | 3 --- 1 file changed, 3 deletions(-) diff --git a/arch/arm64/boot/dts/apple/t602x-common.dtsi b/arch/arm64/boot/dts/apple/t602x-common.dtsi index fe888cbb81e475..2905234ad6d40b 100644 --- a/arch/arm64/boot/dts/apple/t602x-common.dtsi +++ b/arch/arm64/boot/dts/apple/t602x-common.dtsi @@ -614,17 +614,14 @@ }; uat_handoff: uat-handoff { - status = "disabled"; reg = <0 0 0 0>; }; uat_pagetables: uat-pagetables { - status = "disabled"; reg = <0 0 0 0>; }; uat_ttbs: uat-ttbs { - status = "disabled"; reg = <0 0 0 0>; }; }; From b956e25c7ea21077fc992f5c5cb7c317a3c9c2ee Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Mon, 22 Dec 2025 22:33:21 +0100 Subject: [PATCH 2209/3902] rm64: dts: apple: t8103: Add ATC display crossbar devices These are mux devices which control which DCP source is routed to DP complex in ATC. The display signals are either routed to the DP phy for DP-altmode or one of two DP in Thunderbolt/USB4 tunnels. Signed-off-by: Janne Grunau --- arch/arm64/boot/dts/apple/t8103.dtsi | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/arch/arm64/boot/dts/apple/t8103.dtsi b/arch/arm64/boot/dts/apple/t8103.dtsi index 98a25e4d26f2f1..06f54772813ff8 100644 --- a/arch/arm64/boot/dts/apple/t8103.dtsi +++ b/arch/arm64/boot/dts/apple/t8103.dtsi @@ -1691,6 +1691,13 @@ power-domains = <&ps_atc0_usb>; }; + atcphy0_xbar: mux@38304c000 { + compatible = "apple,t8103-display-crossbar"; + reg = <0x3 0x8304c000 0x0 0x4000>; + #mux-control-cells = <1>; + power-domains = <&ps_atc0_usb>; + }; + dwc3_1: usb@502280000 { compatible = "apple,t8103-dwc3"; reg = <0x5 0x02280000 0x0 0xcd00>, <0x5 0x0228cd00 0x0 0x3200>; @@ -1764,6 +1771,13 @@ power-domains = <&ps_atc1_usb>; }; + atcphy1_xbar: mux@50304c000 { + compatible = "apple,t8103-display-crossbar"; + reg = <0x5 0x0304c000 0x0 0x4000>; + #mux-control-cells = <1>; + power-domains = <&ps_atc1_usb>; + }; + pcie0_dart_0: iommu@681008000 { compatible = "apple,t8103-dart"; reg = <0x6 0x81008000 0x0 0x4000>; From a7ddd9eca37826eaa9a1dece10fa3f040c2c73a2 Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Mon, 22 Dec 2025 22:33:21 +0100 Subject: [PATCH 2210/3902] rm64: dts: apple: t8112: Add ATC display crossbar devices These are mux devices which control which DCP source is routed to DP complex in ATC. The display signals are either routed to the DP phy for DP-altmode or one of two DP in Thunderbolt/USB4 tunnels. Signed-off-by: Janne Grunau --- arch/arm64/boot/dts/apple/t8112.dtsi | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/arch/arm64/boot/dts/apple/t8112.dtsi b/arch/arm64/boot/dts/apple/t8112.dtsi index 3789be15f95a75..89c902f5289b9e 100644 --- a/arch/arm64/boot/dts/apple/t8112.dtsi +++ b/arch/arm64/boot/dts/apple/t8112.dtsi @@ -1776,6 +1776,13 @@ power-domains = <&ps_atc0_usb>; }; + atcphy0_xbar: mux@38304c000 { + compatible = "apple,t8112-display-crossbar", "apple,t8103-display-crossbar"; + reg = <0x3 0x8304c000 0x0 0x4000>; + #mux-control-cells = <1>; + power-domains = <&ps_atc0_usb>; + }; + dwc3_1: usb@502280000 { compatible = "apple,t8112-dwc3", "apple,t8103-dwc3"; reg = <0x5 0x02280000 0x0 0xcd00>, <0x5 0x0228cd00 0x0 0x3200>; @@ -1847,6 +1854,13 @@ power-domains = <&ps_atc1_usb>; }; + atcphy1_xbar: mux@50304c000 { + compatible = "apple,t8112-display-crossbar", "apple,t8103-display-crossbar"; + reg = <0x5 0x0304c000 0x0 0x4000>; + #mux-control-cells = <1>; + power-domains = <&ps_atc1_usb>; + }; + pcie0_dart: iommu@681008000 { compatible = "apple,t8110-dart"; reg = <0x6 0x81008000 0x0 0x4000>; From 8f57fec2fe9e3f6baa8d94770367a9fcf7bc7e6b Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Sun, 21 Dec 2025 20:25:56 +0100 Subject: [PATCH 2211/3902] arm64: dts: apple: Connect dcp and atc-phy for dp2hdmi on Macbook Pros The type-c mux lookup requires a graph connection between dcp and atc-phy. Signed-off-by: Janne Grunau --- .../arm64/boot/dts/apple/t600x-j314-j316.dtsi | 25 ++++++++++++++++++- 1 file changed, 24 insertions(+), 1 deletion(-) diff --git a/arch/arm64/boot/dts/apple/t600x-j314-j316.dtsi b/arch/arm64/boot/dts/apple/t600x-j314-j316.dtsi index 5424db9e021e29..3d064a83daef05 100644 --- a/arch/arm64/boot/dts/apple/t600x-j314-j316.dtsi +++ b/arch/arm64/boot/dts/apple/t600x-j314-j316.dtsi @@ -107,6 +107,19 @@ mux-control-names = "dp-xbar"; mux-index = <0>; apple,dptx-phy = <3>; + + ports { + #address-cells = <1>; + #size-cells = <0>; + + port@1 { + reg = <1>; + + decpext0_dpout: endpoint { + remote-endpoint = <&atcphy3_dp>; + }; + }; + }; }; /* remove once m1n1 enables sio nodes after setup */ @@ -554,7 +567,17 @@ }; &atcphy3 { - status = "disabled"; + ports { + #address-cells = <1>; + #size-cells = <0>; + + port@2 { + reg = <2>; + atcphy3_dp: endpoint { + remote-endpoint = <&decpext0_dpout>; + }; + }; + }; }; &aop_mbox { From 38c0d0c6764d5759f2da1ed710401a00bbf59e9e Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Thu, 25 Dec 2025 17:47:37 +0100 Subject: [PATCH 2212/3902] arm64: dts: apple: j[34]1[46]: Mark ps_atc3_common as always-on This works around missing (or incomplete) suspend/resume handling in atc/dcp for the the HDMI output on 14 and 16-inch Macbook Pros. Signed-off-by: Janne Grunau --- arch/arm64/boot/dts/apple/t600x-j314-j316.dtsi | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/arch/arm64/boot/dts/apple/t600x-j314-j316.dtsi b/arch/arm64/boot/dts/apple/t600x-j314-j316.dtsi index 3d064a83daef05..8d030df13bf5f8 100644 --- a/arch/arm64/boot/dts/apple/t600x-j314-j316.dtsi +++ b/arch/arm64/boot/dts/apple/t600x-j314-j316.dtsi @@ -135,6 +135,10 @@ status = "okay"; }; +&ps_atc3_common { + apple,always-on; /* Needs to stay on for HDMI resume */ +}; + /* USB Type C */ &i2c0 { hpm0: usb-pd@38 { From 5272c70cc1ab992844f11543c5c685d7f18add9a Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Sat, 10 Jan 2026 14:56:10 +0100 Subject: [PATCH 2213/3902] fixup! arm64: configs: Add asahi.config fragment Signed-off-by: Janne Grunau --- arch/arm64/configs/asahi.config | 1 + 1 file changed, 1 insertion(+) diff --git a/arch/arm64/configs/asahi.config b/arch/arm64/configs/asahi.config index 4f8780350ca842..7ed0a173544025 100644 --- a/arch/arm64/configs/asahi.config +++ b/arch/arm64/configs/asahi.config @@ -63,6 +63,7 @@ CONFIG_APPLE_SEP=m CONFIG_APPLE_PMGR_PWRSTATE=y CONFIG_IIO_AOP_SENSOR_LAS=m CONFIG_IIO_AOP_SENSOR_ALS=m +CONFIG_RUST_FW_LOADER_ABSTRACTIONS=y CONFIG_PWM_APPLE=m CONFIG_APPLE_AIC=y CONFIG_PHY_APPLE_ATC=m From c04b2d258f2682995f395cc3a78b0f4a1761d61c Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Sat, 10 Jan 2026 14:56:48 +0100 Subject: [PATCH 2214/3902] fixup! iio: common: Add AOP sensor drivers Signed-off-by: Janne Grunau --- drivers/iio/common/aop_sensors/Kconfig | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/iio/common/aop_sensors/Kconfig b/drivers/iio/common/aop_sensors/Kconfig index 2c092e63a95884..6f6949bdf6a5d8 100644 --- a/drivers/iio/common/aop_sensors/Kconfig +++ b/drivers/iio/common/aop_sensors/Kconfig @@ -15,6 +15,7 @@ config IIO_AOP_SENSOR_ALS depends on ARCH_APPLE || COMPILE_TEST depends on RUST depends on SYSFS + depends on RUST_FW_LOADER_ABSTRACTIONS select APPLE_AOP help Module to handle the ambient light sensor attached to the AOP From f1a77dfc3b045c3dd5f6e64189b9f52b90399f07 Mon Sep 17 00:00:00 2001 From: Paolo Abeni Date: Tue, 6 Jan 2026 15:55:58 -0500 Subject: [PATCH 2215/3902] mptcp: ensure context reset on disconnect() [ Upstream commit 86730ac255b0497a272704de9a1df559f5d6602e ] After the blamed commit below, if the MPC subflow is already in TCP_CLOSE status or has fallback to TCP at mptcp_disconnect() time, mptcp_do_fastclose() skips setting the `send_fastclose flag` and the later __mptcp_close_ssk() does not reset anymore the related subflow context. Any later connection will be created with both the `request_mptcp` flag and the msk-level fallback status off (it is unconditionally cleared at MPTCP disconnect time), leading to a warning in subflow_data_ready(): WARNING: CPU: 26 PID: 8996 at net/mptcp/subflow.c:1519 subflow_data_ready (net/mptcp/subflow.c:1519 (discriminator 13)) Modules linked in: CPU: 26 UID: 0 PID: 8996 Comm: syz.22.39 Not tainted 6.18.0-rc7-05427-g11fc074f6c36 #1 PREEMPT(voluntary) Hardware name: Bochs Bochs, BIOS Bochs 01/01/2011 RIP: 0010:subflow_data_ready (net/mptcp/subflow.c:1519 (discriminator 13)) Code: 90 0f 0b 90 90 e9 04 fe ff ff e8 b7 1e f5 fe 89 ee bf 07 00 00 00 e8 db 19 f5 fe 83 fd 07 0f 84 35 ff ff ff e8 9d 1e f5 fe 90 <0f> 0b 90 e9 27 ff ff ff e8 8f 1e f5 fe 4c 89 e7 48 89 de e8 14 09 RSP: 0018:ffffc9002646fb30 EFLAGS: 00010293 RAX: 0000000000000000 RBX: ffff88813b218000 RCX: ffffffff825c8435 RDX: ffff8881300b3580 RSI: ffffffff825c8443 RDI: 0000000000000005 RBP: 000000000000000b R08: ffffffff825c8435 R09: 000000000000000b R10: 0000000000000005 R11: 0000000000000007 R12: ffff888131ac0000 R13: 0000000000000000 R14: 0000000000000000 R15: 0000000000000000 FS: 00007f88330af6c0(0000) GS:ffff888a93dd2000(0000) knlGS:0000000000000000 CS: 0010 DS: 0000 ES: 0000 CR0: 0000000080050033 CR2: 00007f88330aefe8 CR3: 000000010ff59000 CR4: 0000000000350ef0 Call Trace: tcp_data_ready (net/ipv4/tcp_input.c:5356) tcp_data_queue (net/ipv4/tcp_input.c:5445) tcp_rcv_state_process (net/ipv4/tcp_input.c:7165) tcp_v4_do_rcv (net/ipv4/tcp_ipv4.c:1955) __release_sock (include/net/sock.h:1158 (discriminator 6) net/core/sock.c:3180 (discriminator 6)) release_sock (net/core/sock.c:3737) mptcp_sendmsg (net/mptcp/protocol.c:1763 net/mptcp/protocol.c:1857) inet_sendmsg (net/ipv4/af_inet.c:853 (discriminator 7)) __sys_sendto (net/socket.c:727 (discriminator 15) net/socket.c:742 (discriminator 15) net/socket.c:2244 (discriminator 15)) __x64_sys_sendto (net/socket.c:2247) do_syscall_64 (arch/x86/entry/syscall_64.c:63 (discriminator 1) arch/x86/entry/syscall_64.c:94 (discriminator 1)) entry_SYSCALL_64_after_hwframe (arch/x86/entry/entry_64.S:130) RIP: 0033:0x7f883326702d Address the issue setting an explicit `fastclosing` flag at fastclose time, and checking such flag after mptcp_do_fastclose(). Fixes: ae155060247b ("mptcp: fix duplicate reset on fastclose") Cc: stable@vger.kernel.org Signed-off-by: Paolo Abeni Reviewed-by: Matthieu Baerts (NGI0) Signed-off-by: Matthieu Baerts (NGI0) Link: https://patch.msgid.link/20251212-net-mptcp-subflow_data_ready-warn-v1-2-d1f9fd1c36c8@kernel.org Signed-off-by: Paolo Abeni [ Adjust context ] Signed-off-by: Sasha Levin Signed-off-by: Greg Kroah-Hartman --- net/mptcp/protocol.c | 8 +++++--- net/mptcp/protocol.h | 3 ++- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/net/mptcp/protocol.c b/net/mptcp/protocol.c index 221a5ea019e697..d4e3111ba643f6 100644 --- a/net/mptcp/protocol.c +++ b/net/mptcp/protocol.c @@ -2421,10 +2421,10 @@ bool __mptcp_retransmit_pending_data(struct sock *sk) */ static void __mptcp_subflow_disconnect(struct sock *ssk, struct mptcp_subflow_context *subflow, - unsigned int flags) + bool fastclosing) { if (((1 << ssk->sk_state) & (TCPF_CLOSE | TCPF_LISTEN)) || - subflow->send_fastclose) { + fastclosing) { /* The MPTCP code never wait on the subflow sockets, TCP-level * disconnect should never fail */ @@ -2476,7 +2476,7 @@ static void __mptcp_close_ssk(struct sock *sk, struct sock *ssk, need_push = (flags & MPTCP_CF_PUSH) && __mptcp_retransmit_pending_data(sk); if (!dispose_it) { - __mptcp_subflow_disconnect(ssk, subflow, flags); + __mptcp_subflow_disconnect(ssk, subflow, msk->fastclosing); release_sock(ssk); goto out; @@ -2789,6 +2789,7 @@ static void mptcp_do_fastclose(struct sock *sk) struct mptcp_sock *msk = mptcp_sk(sk); mptcp_set_state(sk, TCP_CLOSE); + msk->fastclosing = 1; /* Explicitly send the fastclose reset as need */ if (__mptcp_check_fallback(msk)) @@ -3299,6 +3300,7 @@ static int mptcp_disconnect(struct sock *sk, int flags) msk->bytes_sent = 0; msk->bytes_retrans = 0; msk->rcvspace_init = 0; + msk->fastclosing = 0; WRITE_ONCE(sk->sk_shutdown, 0); sk_error_report(sk); diff --git a/net/mptcp/protocol.h b/net/mptcp/protocol.h index 0e8b0a650108cf..30d5e57197936e 100644 --- a/net/mptcp/protocol.h +++ b/net/mptcp/protocol.h @@ -321,7 +321,8 @@ struct mptcp_sock { fastopening:1, in_accept_queue:1, free_first:1, - rcvspace_init:1; + rcvspace_init:1, + fastclosing:1; u32 notsent_lowat; int keepalive_cnt; int keepalive_idle; From c7ca7e0ff6f0f55ef57c1596286076492f199f9a Mon Sep 17 00:00:00 2001 From: Peter Zijlstra Date: Fri, 7 Nov 2025 17:01:24 +0100 Subject: [PATCH 2216/3902] sched/fair: Small cleanup to sched_balance_newidle() commit e78e70dbf603c1425f15f32b455ca148c932f6c1 upstream. Pull out the !sd check to simplify code. Signed-off-by: Peter Zijlstra (Intel) Reviewed-by: Dietmar Eggemann Tested-by: Dietmar Eggemann Tested-by: Chris Mason Link: https://patch.msgid.link/20251107161739.525916173@infradead.org Signed-off-by: Greg Kroah-Hartman --- kernel/sched/fair.c | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/kernel/sched/fair.c b/kernel/sched/fair.c index bfce451f1210ce..3dd12dcf6e6d1d 100644 --- a/kernel/sched/fair.c +++ b/kernel/sched/fair.c @@ -12787,14 +12787,16 @@ static int sched_balance_newidle(struct rq *this_rq, struct rq_flags *rf) rcu_read_lock(); sd = rcu_dereference_check_sched_domain(this_rq->sd); + if (!sd) { + rcu_read_unlock(); + goto out; + } if (!get_rd_overloaded(this_rq->rd) || - (sd && this_rq->avg_idle < sd->max_newidle_lb_cost)) { + this_rq->avg_idle < sd->max_newidle_lb_cost) { - if (sd) - update_next_balance(sd, &next_balance); + update_next_balance(sd, &next_balance); rcu_read_unlock(); - goto out; } rcu_read_unlock(); From d4ffb9ce8e6501bebbf833cd7ae3f34eab1f76ba Mon Sep 17 00:00:00 2001 From: Peter Zijlstra Date: Fri, 7 Nov 2025 17:01:27 +0100 Subject: [PATCH 2217/3902] sched/fair: Small cleanup to update_newidle_cost() commit 08d473dd8718e4a4d698b1113a14a40ad64a909b upstream. Simplify code by adding a few variables. Signed-off-by: Peter Zijlstra (Intel) Reviewed-by: Dietmar Eggemann Tested-by: Dietmar Eggemann Tested-by: Chris Mason Link: https://patch.msgid.link/20251107161739.655208666@infradead.org Signed-off-by: Greg Kroah-Hartman --- kernel/sched/fair.c | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/kernel/sched/fair.c b/kernel/sched/fair.c index 3dd12dcf6e6d1d..8369dadf19580f 100644 --- a/kernel/sched/fair.c +++ b/kernel/sched/fair.c @@ -12124,22 +12124,25 @@ void update_max_interval(void) static inline bool update_newidle_cost(struct sched_domain *sd, u64 cost) { + unsigned long next_decay = sd->last_decay_max_lb_cost + HZ; + unsigned long now = jiffies; + if (cost > sd->max_newidle_lb_cost) { /* * Track max cost of a domain to make sure to not delay the * next wakeup on the CPU. */ sd->max_newidle_lb_cost = cost; - sd->last_decay_max_lb_cost = jiffies; - } else if (time_after(jiffies, sd->last_decay_max_lb_cost + HZ)) { + sd->last_decay_max_lb_cost = now; + + } else if (time_after(now, next_decay)) { /* * Decay the newidle max times by ~1% per second to ensure that * it is not outdated and the current max cost is actually * shorter. */ sd->max_newidle_lb_cost = (sd->max_newidle_lb_cost * 253) / 256; - sd->last_decay_max_lb_cost = jiffies; - + sd->last_decay_max_lb_cost = now; return true; } From 98a26893fad4180d8ea210d8749392790dfddc81 Mon Sep 17 00:00:00 2001 From: Peter Zijlstra Date: Fri, 7 Nov 2025 17:01:31 +0100 Subject: [PATCH 2218/3902] sched/fair: Proportional newidle balance commit 33cf66d88306663d16e4759e9d24766b0aaa2e17 upstream. Add a randomized algorithm that runs newidle balancing proportional to its success rate. This improves schbench significantly: 6.18-rc4: 2.22 Mrps/s 6.18-rc4+revert: 2.04 Mrps/s 6.18-rc4+revert+random: 2.18 Mrps/S Conversely, per Adam Li this affects SpecJBB slightly, reducing it by 1%: 6.17: -6% 6.17+revert: 0% 6.17+revert+random: -1% Signed-off-by: Peter Zijlstra (Intel) Reviewed-by: Dietmar Eggemann Tested-by: Dietmar Eggemann Tested-by: Chris Mason Link: https://lkml.kernel.org/r/6825c50d-7fa7-45d8-9b81-c6e7e25738e2@meta.com Link: https://patch.msgid.link/20251107161739.770122091@infradead.org Signed-off-by: Greg Kroah-Hartman --- include/linux/sched/topology.h | 3 +++ kernel/sched/core.c | 3 +++ kernel/sched/fair.c | 44 ++++++++++++++++++++++++++++++---- kernel/sched/features.h | 5 ++++ kernel/sched/sched.h | 7 ++++++ kernel/sched/topology.c | 6 +++++ 6 files changed, 64 insertions(+), 4 deletions(-) diff --git a/include/linux/sched/topology.h b/include/linux/sched/topology.h index bbcfdf12aa6e57..45c0022b91ced3 100644 --- a/include/linux/sched/topology.h +++ b/include/linux/sched/topology.h @@ -92,6 +92,9 @@ struct sched_domain { unsigned int nr_balance_failed; /* initialise to 0 */ /* idle_balance() stats */ + unsigned int newidle_call; + unsigned int newidle_success; + unsigned int newidle_ratio; u64 max_newidle_lb_cost; unsigned long last_decay_max_lb_cost; diff --git a/kernel/sched/core.c b/kernel/sched/core.c index f754a60de84849..eb47d294e2c5a1 100644 --- a/kernel/sched/core.c +++ b/kernel/sched/core.c @@ -121,6 +121,7 @@ EXPORT_TRACEPOINT_SYMBOL_GPL(sched_update_nr_running_tp); EXPORT_TRACEPOINT_SYMBOL_GPL(sched_compute_energy_tp); DEFINE_PER_CPU_SHARED_ALIGNED(struct rq, runqueues); +DEFINE_PER_CPU(struct rnd_state, sched_rnd_state); #ifdef CONFIG_SCHED_PROXY_EXEC DEFINE_STATIC_KEY_TRUE(__sched_proxy_exec); @@ -8591,6 +8592,8 @@ void __init sched_init_smp(void) { sched_init_numa(NUMA_NO_NODE); + prandom_init_once(&sched_rnd_state); + /* * There's no userspace yet to cause hotplug operations; hence all the * CPU masks are stable and all blatant races in the below code cannot diff --git a/kernel/sched/fair.c b/kernel/sched/fair.c index 8369dadf19580f..d1206f81f8b2e0 100644 --- a/kernel/sched/fair.c +++ b/kernel/sched/fair.c @@ -12122,11 +12122,27 @@ void update_max_interval(void) max_load_balance_interval = HZ*num_online_cpus()/10; } -static inline bool update_newidle_cost(struct sched_domain *sd, u64 cost) +static inline void update_newidle_stats(struct sched_domain *sd, unsigned int success) +{ + sd->newidle_call++; + sd->newidle_success += success; + + if (sd->newidle_call >= 1024) { + sd->newidle_ratio = sd->newidle_success; + sd->newidle_call /= 2; + sd->newidle_success /= 2; + } +} + +static inline bool +update_newidle_cost(struct sched_domain *sd, u64 cost, unsigned int success) { unsigned long next_decay = sd->last_decay_max_lb_cost + HZ; unsigned long now = jiffies; + if (cost) + update_newidle_stats(sd, success); + if (cost > sd->max_newidle_lb_cost) { /* * Track max cost of a domain to make sure to not delay the @@ -12174,7 +12190,7 @@ static void sched_balance_domains(struct rq *rq, enum cpu_idle_type idle) * Decay the newidle max times here because this is a regular * visit to all the domains. */ - need_decay = update_newidle_cost(sd, 0); + need_decay = update_newidle_cost(sd, 0, 0); max_cost += sd->max_newidle_lb_cost; /* @@ -12819,6 +12835,22 @@ static int sched_balance_newidle(struct rq *this_rq, struct rq_flags *rf) break; if (sd->flags & SD_BALANCE_NEWIDLE) { + unsigned int weight = 1; + + if (sched_feat(NI_RANDOM)) { + /* + * Throw a 1k sided dice; and only run + * newidle_balance according to the success + * rate. + */ + u32 d1k = sched_rng() % 1024; + weight = 1 + sd->newidle_ratio; + if (d1k > weight) { + update_newidle_stats(sd, 0); + continue; + } + weight = (1024 + weight/2) / weight; + } pulled_task = sched_balance_rq(this_cpu, this_rq, sd, CPU_NEWLY_IDLE, @@ -12826,10 +12858,14 @@ static int sched_balance_newidle(struct rq *this_rq, struct rq_flags *rf) t1 = sched_clock_cpu(this_cpu); domain_cost = t1 - t0; - update_newidle_cost(sd, domain_cost); - curr_cost += domain_cost; t0 = t1; + + /* + * Track max cost of a domain to make sure to not delay the + * next wakeup on the CPU. + */ + update_newidle_cost(sd, domain_cost, weight * !!pulled_task); } /* diff --git a/kernel/sched/features.h b/kernel/sched/features.h index 3c12d9f93331d6..136a6584be7974 100644 --- a/kernel/sched/features.h +++ b/kernel/sched/features.h @@ -121,3 +121,8 @@ SCHED_FEAT(WA_BIAS, true) SCHED_FEAT(UTIL_EST, true) SCHED_FEAT(LATENCY_WARN, false) + +/* + * Do newidle balancing proportional to its success rate using randomization. + */ +SCHED_FEAT(NI_RANDOM, true) diff --git a/kernel/sched/sched.h b/kernel/sched/sched.h index 92ec751799f555..2f8b06b12a98f4 100644 --- a/kernel/sched/sched.h +++ b/kernel/sched/sched.h @@ -5,6 +5,7 @@ #ifndef _KERNEL_SCHED_SCHED_H #define _KERNEL_SCHED_SCHED_H +#include #include #include #include @@ -1349,6 +1350,12 @@ static inline bool is_migration_disabled(struct task_struct *p) } DECLARE_PER_CPU_SHARED_ALIGNED(struct rq, runqueues); +DECLARE_PER_CPU(struct rnd_state, sched_rnd_state); + +static inline u32 sched_rng(void) +{ + return prandom_u32_state(this_cpu_ptr(&sched_rnd_state)); +} #define cpu_rq(cpu) (&per_cpu(runqueues, (cpu))) #define this_rq() this_cpu_ptr(&runqueues) diff --git a/kernel/sched/topology.c b/kernel/sched/topology.c index 444bdfdab73180..c7a4d2fff57189 100644 --- a/kernel/sched/topology.c +++ b/kernel/sched/topology.c @@ -1662,6 +1662,12 @@ sd_init(struct sched_domain_topology_level *tl, .last_balance = jiffies, .balance_interval = sd_weight, + + /* 50% success rate */ + .newidle_call = 512, + .newidle_success = 256, + .newidle_ratio = 512, + .max_newidle_lb_cost = 0, .last_decay_max_lb_cost = jiffies, .child = child, From 7a28d65e4beb7627738b75c6a23f36ae54470f93 Mon Sep 17 00:00:00 2001 From: Mike Snitzer Date: Wed, 26 Nov 2025 01:01:25 -0500 Subject: [PATCH 2219/3902] nfs/localio: fix regression due to out-of-order __put_cred commit 3af870aedbff10bfed220e280b57a405e972229f upstream. Commit f2060bdc21d7 ("nfs/localio: add refcounting for each iocb IO associated with NFS pgio header") inadvertantly reintroduced the same potential for __put_cred() triggering BUG_ON(cred == current->cred) that commit 992203a1fba5 ("nfs/localio: restore creds before releasing pageio data") fixed. Fix this by saving and restoring the cred around each {read,write}_iter call within the respective for loop of nfs_local_call_{read,write} using scoped_with_creds(). NOTE: this fix started by first reverting the following commits: 94afb627dfc2 ("nfs: use credential guards in nfs_local_call_read()") bff3c841f7bd ("nfs: use credential guards in nfs_local_call_write()") 1d18101a644e ("Merge tag 'kernel-6.19-rc1.cred' of git://git.kernel.org/pub/scm/linux/kernel/git/vfs/vfs") followed by narrowly fixing the cred lifetime issue by using scoped_with_creds(). In doing so, this commit's changes appear more extensive than they really are (as evidenced by comparing to v6.18's fs/nfs/localio.c). Reported-by: Zorro Lang Signed-off-by: Mike Snitzer Acked-by: Trond Myklebust Reviewed-by: Christian Brauner Link: https://lore.kernel.org/linux-next/20251205111942.4150b06f@canb.auug.org.au/ Signed-off-by: Linus Torvalds Signed-off-by: Trond Myklebust Signed-off-by: Greg Kroah-Hartman --- fs/nfs/localio.c | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/fs/nfs/localio.c b/fs/nfs/localio.c index b98bb292fef0cc..ed2a7efaf8f20c 100644 --- a/fs/nfs/localio.c +++ b/fs/nfs/localio.c @@ -623,8 +623,6 @@ static void nfs_local_call_read(struct work_struct *work) ssize_t status; int n_iters; - save_cred = override_creds(filp->f_cred); - n_iters = atomic_read(&iocb->n_iters); for (int i = 0; i < n_iters ; i++) { if (iocb->iter_is_dio_aligned[i]) { @@ -637,7 +635,10 @@ static void nfs_local_call_read(struct work_struct *work) } else iocb->kiocb.ki_flags &= ~IOCB_DIRECT; + save_cred = override_creds(filp->f_cred); status = filp->f_op->read_iter(&iocb->kiocb, &iocb->iters[i]); + revert_creds(save_cred); + if (status != -EIOCBQUEUED) { if (unlikely(status >= 0 && status < iocb->iters[i].count)) force_done = true; /* Partial read */ @@ -647,8 +648,6 @@ static void nfs_local_call_read(struct work_struct *work) } } } - - revert_creds(save_cred); } static int @@ -830,7 +829,6 @@ static void nfs_local_call_write(struct work_struct *work) int n_iters; current->flags |= PF_LOCAL_THROTTLE | PF_MEMALLOC_NOIO; - save_cred = override_creds(filp->f_cred); file_start_write(filp); n_iters = atomic_read(&iocb->n_iters); @@ -845,7 +843,10 @@ static void nfs_local_call_write(struct work_struct *work) } else iocb->kiocb.ki_flags &= ~IOCB_DIRECT; + save_cred = override_creds(filp->f_cred); status = filp->f_op->write_iter(&iocb->kiocb, &iocb->iters[i]); + revert_creds(save_cred); + if (status != -EIOCBQUEUED) { if (unlikely(status >= 0 && status < iocb->iters[i].count)) force_done = true; /* Partial write */ @@ -857,7 +858,6 @@ static void nfs_local_call_write(struct work_struct *work) } file_end_write(filp); - revert_creds(save_cred); current->flags = old_flags; } From dc554c8fb361f13580da3f5a98ad8b494a788666 Mon Sep 17 00:00:00 2001 From: Greg Kroah-Hartman Date: Sun, 11 Jan 2026 15:26:20 +0100 Subject: [PATCH 2220/3902] Linux 6.18.5 Link: https://lore.kernel.org/r/20260109111950.344681501@linuxfoundation.org Tested-by: Ronald Warsow Tested-by: Slade Watkins Tested-by: Achill Gilgenast = Tested-by: Jon Hunter Tested-by: Brett A C Sheffield Tested-by: Brett Mastbergen Tested-by: Florian Fainelli Tested-by: Shuah Khan Tested-by: Peter Schneider Tested-by: Takeshi Ogasawara Tested-by: Ron Economos Tested-by: Salvatore Bonaccorso Tested-by: Jeffrin Jose T Tested-by: Mark Brown Tested-by: Miguel Ojeda Signed-off-by: Greg Kroah-Hartman --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 7b431af09b3229..30c332829b0fcf 100644 --- a/Makefile +++ b/Makefile @@ -1,7 +1,7 @@ # SPDX-License-Identifier: GPL-2.0 VERSION = 6 PATCHLEVEL = 18 -SUBLEVEL = 4 +SUBLEVEL = 5 EXTRAVERSION = NAME = Baby Opossum Posse From d57d0e1b6639403b2ca949cf987017931605d3f0 Mon Sep 17 00:00:00 2001 From: Sven Peter Date: Sun, 11 Jan 2026 15:19:09 +0100 Subject: [PATCH 2221/3902] power: hibernate: Disable hibernation on Apple Silicon Signed-off-by: Sven Peter --- kernel/power/hibernate.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/kernel/power/hibernate.c b/kernel/power/hibernate.c index 26e45f86b9554e..6503cda89645bc 100644 --- a/kernel/power/hibernate.c +++ b/kernel/power/hibernate.c @@ -33,6 +33,7 @@ #include #include #include +#include #include #include "power.h" @@ -110,7 +111,8 @@ bool hibernation_available(void) { return nohibernate == 0 && !security_locked_down(LOCKDOWN_HIBERNATION) && - !secretmem_active() && !cxl_mem_active(); + !secretmem_active() && !cxl_mem_active() && + !of_machine_is_compatible("apple,arm-platform"); } /** From 01c686dd2de21c114ef8a5b1531a063980945fa7 Mon Sep 17 00:00:00 2001 From: Michael Reeves Date: Fri, 2 Jan 2026 14:10:30 +1100 Subject: [PATCH 2222/3902] power: supply: macsmc: support charge_behaviour on newer SMC firmware Newer Apple SMC firmware (found on M3 devices and updated M1/M2) has removed the legacy `CH0C` (Inhibit Charge) and `CH0I` (Force Discharge) keys. Reading these missing keys results in -EIO (-5) errors, causing the `charge_behaviour` sysfs property to fail completely. This patch adds support for the new `CHTE` key used for charge inhibition on these devices. For now, it seems that `auto` and `inhibit-charge` are the only possible behaviours to set using this new key, however further macOS tracing may reveal additional behaviour states in future. Changes: 1. Detects the presence of `CHTE`, `CH0C`, and `CH0I` during probe. 2. Only exposes `force_discharge` capability if `CH0I` is actually present. 3. Implements read/write support for `CHTE` using raw byte buffers (this is to avoid endianness issues with the kernel's u32 helpers) Fully backwards compatible with both old and new firmwares. Tested on M3 with new firmware. Signed-off-by: Michael Reeves --- drivers/power/supply/macsmc-power.c | 158 ++++++++++++++++++++++------ 1 file changed, 125 insertions(+), 33 deletions(-) diff --git a/drivers/power/supply/macsmc-power.c b/drivers/power/supply/macsmc-power.c index 93f4fef365eaef..dc3ec5ef2b81cf 100644 --- a/drivers/power/supply/macsmc-power.c +++ b/drivers/power/supply/macsmc-power.c @@ -39,8 +39,13 @@ struct macsmc_power { char model_name[MAX_STRING_LENGTH]; char serial_number[MAX_STRING_LENGTH]; char mfg_date[MAX_STRING_LENGTH]; + bool has_chwa; bool has_chls; + bool has_ch0i; + bool has_ch0c; + bool has_chte; + u8 num_cells; int nominal_voltage_mv; @@ -57,8 +62,8 @@ struct macsmc_power { static int macsmc_log_power_set(const char *val, const struct kernel_param *kp); static const struct kernel_param_ops macsmc_log_power_ops = { - .set = macsmc_log_power_set, - .get = param_get_bool, + .set = macsmc_log_power_set, + .get = param_get_bool, }; static bool log_power = false; @@ -242,6 +247,7 @@ static int macsmc_battery_get_status(struct macsmc_power *power) */ if (power->has_chls) { u16 vu16; + ret = apple_smc_read_u16(power->smc, SMC_KEY(CHLS), &vu16); if (ret == sizeof(vu16) && (vu16 & 0xff) >= CHLS_MIN_END_THRESHOLD) charge_limit = (vu16 & 0xff) - CHWA_CHLS_FIXED_START_OFFSET; @@ -253,6 +259,7 @@ static int macsmc_battery_get_status(struct macsmc_power *power) if (charge_limit > 0) { u8 buic = 0; + if (apple_smc_read_u8(power->smc, SMC_KEY(BUIC), &buic) >= 0 && buic >= charge_limit) limited = true; @@ -291,55 +298,113 @@ static int macsmc_battery_get_status(struct macsmc_power *power) static int macsmc_battery_get_charge_behaviour(struct macsmc_power *power) { int ret; - u8 val; + u8 val8; + u8 chte_buf[4]; + + if (power->has_ch0i) { + /* CH0I returns a bitmask like the low byte of CH0R */ + ret = apple_smc_read_u8(power->smc, SMC_KEY(CH0I), &val8); + if (ret) + return ret; + if (val8 & CH0R_NOAC_CH0I) + return POWER_SUPPLY_CHARGE_BEHAVIOUR_FORCE_DISCHARGE; + } - /* CH0I returns a bitmask like the low byte of CH0R */ - ret = apple_smc_read_u8(power->smc, SMC_KEY(CH0I), &val); - if (ret) - return ret; - if (val & CH0R_NOAC_CH0I) - return POWER_SUPPLY_CHARGE_BEHAVIOUR_FORCE_DISCHARGE; + /* Prefer CHTE available in newer firmwares */ + if (power->has_chte) { + ret = apple_smc_read(power->smc, SMC_KEY(CHTE), chte_buf, 4); + if (ret < 0) + return ret; + + if (chte_buf[0] == 0x01) + return POWER_SUPPLY_CHARGE_BEHAVIOUR_INHIBIT_CHARGE; + + } else if (power->has_ch0c) { + /* CH0C returns a bitmask containing CH0B/CH0C flags */ + ret = apple_smc_read_u8(power->smc, SMC_KEY(CH0C), &val8); + if (ret) + return ret; + if (val8 & CH0X_CH0C) + return POWER_SUPPLY_CHARGE_BEHAVIOUR_INHIBIT_CHARGE; + } - /* CH0C returns a bitmask containing CH0B/CH0C flags */ - ret = apple_smc_read_u8(power->smc, SMC_KEY(CH0C), &val); - if (ret) - return ret; - if (val & CH0X_CH0C) - return POWER_SUPPLY_CHARGE_BEHAVIOUR_INHIBIT_CHARGE; - else - return POWER_SUPPLY_CHARGE_BEHAVIOUR_AUTO; + return POWER_SUPPLY_CHARGE_BEHAVIOUR_AUTO; } static int macsmc_battery_set_charge_behaviour(struct macsmc_power *power, int val) { - u8 ch0i, ch0c; int ret; /* - * CH0I/CH0C are "hard" controls that will allow the battery to run down to 0. + * apple_smc_write_u32 does weird things with endianess, + * so we write raw bytes to ensure correctness of CHTE + */ + u8 chte_inhibit[4] = {0x01, 0x00, 0x00, 0x00}; + u8 chte_auto[4] = {0x00, 0x00, 0x00, 0x00}; + + /* + * CH0I/CH0C/CHTE are "hard" controls that will allow the battery to run down to 0. * CH0K/CH0B are "soft" controls that are reset to 0 when SOC drops below 50%; * we don't expose these yet. */ switch (val) { case POWER_SUPPLY_CHARGE_BEHAVIOUR_AUTO: - ch0i = ch0c = 0; + if (power->has_ch0i) { + ret = apple_smc_write_u8(power->smc, SMC_KEY(CH0I), 0); + if (ret) + return ret; + } + + if (power->has_chte) { + ret = apple_smc_write(power->smc, SMC_KEY(CHTE), chte_auto, 4); + if (ret) + return ret; + } else if (power->has_ch0c) { + ret = apple_smc_write_u8(power->smc, SMC_KEY(CH0C), 0); + if (ret) + return ret; + } break; + case POWER_SUPPLY_CHARGE_BEHAVIOUR_INHIBIT_CHARGE: - ch0i = 0; - ch0c = 1; + if (power->has_ch0i) { + ret = apple_smc_write_u8(power->smc, SMC_KEY(CH0I), 0); + if (ret) + return ret; + } + + /* Prefer CHTE available in newer firmwares */ + if (power->has_chte) + return apple_smc_write(power->smc, SMC_KEY(CHTE), chte_inhibit, 4); + else if (power->has_ch0c) + return apple_smc_write_u8(power->smc, SMC_KEY(CH0C), 1); + else + return -EINVAL; break; + case POWER_SUPPLY_CHARGE_BEHAVIOUR_FORCE_DISCHARGE: - ch0i = 1; - ch0c = 0; - break; + if (!power->has_ch0i) + return -EINVAL; + + /* Prefer CHTE available in newer firmwares */ + if (power->has_chte) { + ret = apple_smc_write(power->smc, SMC_KEY(CHTE), chte_auto, 4); + if (ret) + return ret; + } else if (power->has_ch0c) { + ret = apple_smc_write_u8(power->smc, SMC_KEY(CH0C), 0); + if (ret) + return ret; + } + + return apple_smc_write_u8(power->smc, SMC_KEY(CH0I), 1); + default: return -EINVAL; } - ret = apple_smc_write_u8(power->smc, SMC_KEY(CH0I), ch0i); - if (ret) - return ret; - return apple_smc_write_u8(power->smc, SMC_KEY(CH0C), ch0c); + + return 0; } static int macsmc_battery_get_date(const char *s, int *out) @@ -539,8 +604,7 @@ static int macsmc_battery_get_property(struct power_supply *psy, val->intval = vu16 & 0xff; if (val->intval < CHLS_MIN_END_THRESHOLD || val->intval >= 100) val->intval = 100; - } - else if (power->has_chwa) { + } else if (power->has_chwa) { flag = false; ret = apple_smc_read_flag(power->smc, SMC_KEY(CHWA), &flag); val->intval = flag ? CHWA_FIXED_END_THRESHOLD : 100; @@ -853,8 +917,9 @@ static int macsmc_power_probe(struct platform_device *pdev) struct power_supply_config psy_cfg = {}; struct macsmc_power *power; bool flag; - u32 val; + u8 val8; u16 vu16; + u32 val32; int ret; power = devm_kzalloc(&pdev->dev, sizeof(*power), GFP_KERNEL); @@ -876,10 +941,37 @@ static int macsmc_power_probe(struct platform_device *pdev) apple_smc_read(smc, SMC_KEY(BMSN), power->serial_number, sizeof(power->serial_number) - 1); apple_smc_read(smc, SMC_KEY(BMDT), power->mfg_date, sizeof(power->mfg_date) - 1); + if (apple_smc_read_u32(power->smc, SMC_KEY(CHTE), &val32) >= 0) + power->has_chte = true; + + if (apple_smc_read_u8(power->smc, SMC_KEY(CH0C), &val8) >= 0) + power->has_ch0c = true; + + if (apple_smc_read_u8(power->smc, SMC_KEY(CH0I), &val8) >= 0) + power->has_ch0i = true; + /* Turn off the "optimized battery charging" flags, in case macOS left them on */ + if (power->has_chte) + apple_smc_write_u32(power->smc, SMC_KEY(CHTE), 0); + else if (power->has_ch0c) + apple_smc_write_u8(power->smc, SMC_KEY(CH0C), 0); + + if (power->has_ch0i) + apple_smc_write_u8(power->smc, SMC_KEY(CH0I), 0); + apple_smc_write_u8(power->smc, SMC_KEY(CH0K), 0); apple_smc_write_u8(power->smc, SMC_KEY(CH0B), 0); + power->batt_desc.charge_behaviours = BIT(POWER_SUPPLY_CHARGE_BEHAVIOUR_AUTO); + + /* Newer firmwares do not have force discharge, so check if it's supported */ + if (power->has_ch0i) + power->batt_desc.charge_behaviours |= BIT(POWER_SUPPLY_CHARGE_BEHAVIOUR_FORCE_DISCHARGE); + + /* Older firmware uses CH0C, and newer firmware uses CHTE, so check if at least one is present*/ + if (power->has_chte || power->has_ch0c) + power->batt_desc.charge_behaviours |= BIT(POWER_SUPPLY_CHARGE_BEHAVIOUR_INHIBIT_CHARGE); + /* * Prefer CHWA as the SMC firmware from iBoot-10151.1.1 is not compatible with * this CHLS usage. @@ -897,7 +989,7 @@ static int macsmc_power_probe(struct platform_device *pdev) power->nominal_voltage_mv = MACSMC_NOMINAL_CELL_VOLTAGE_MV * power->num_cells; /* Doing one read of this flag enables critical shutdown notifications */ - apple_smc_read_u32(power->smc, SMC_KEY(BCF0), &val); + apple_smc_read_u32(power->smc, SMC_KEY(BCF0), &val32); psy_cfg.drv_data = power; power->batt = devm_power_supply_register(&pdev->dev, &power->batt_desc, &psy_cfg); From a339b0168251e4d7fa84725b77f4a46f77f70472 Mon Sep 17 00:00:00 2001 From: Scott Mayhew Date: Thu, 11 Dec 2025 07:34:34 -0500 Subject: [PATCH 2223/3902] NFSD: Fix permission check for read access to executable-only files commit e901c7fce59e72d9f3c92733c379849c4034ac50 upstream. Commit abc02e5602f7 ("NFSD: Support write delegations in LAYOUTGET") added NFSD_MAY_OWNER_OVERRIDE to the access flags passed from nfsd4_layoutget() to fh_verify(). This causes LAYOUTGET to fail for executable-only files, and causes xfstests generic/126 to fail on pNFS SCSI. To allow read access to executable-only files, what we really want is: 1. The "permissions" portion of the access flags (the lower 6 bits) must be exactly NFSD_MAY_READ 2. The "hints" portion of the access flags (the upper 26 bits) can contain any combination of NFSD_MAY_OWNER_OVERRIDE and NFSD_MAY_READ_IF_EXEC Fixes: abc02e5602f7 ("NFSD: Support write delegations in LAYOUTGET") Cc: stable@vger.kernel.org # v6.6+ Signed-off-by: Scott Mayhew Reviewed-by: Jeff Layton Reviewed-by: NeilBrown Signed-off-by: Chuck Lever Signed-off-by: Greg Kroah-Hartman --- fs/nfsd/vfs.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/fs/nfsd/vfs.c b/fs/nfsd/vfs.c index 9f0b8bbc41442a..e32a5fcd6ac851 100644 --- a/fs/nfsd/vfs.c +++ b/fs/nfsd/vfs.c @@ -2683,8 +2683,8 @@ nfsd_permission(struct svc_cred *cred, struct svc_export *exp, /* Allow read access to binaries even when mode 111 */ if (err == -EACCES && S_ISREG(inode->i_mode) && - (acc == (NFSD_MAY_READ | NFSD_MAY_OWNER_OVERRIDE) || - acc == (NFSD_MAY_READ | NFSD_MAY_READ_IF_EXEC))) + (((acc & NFSD_MAY_MASK) == NFSD_MAY_READ) && + (acc & (NFSD_MAY_OWNER_OVERRIDE | NFSD_MAY_READ_IF_EXEC)))) err = inode_permission(&nop_mnt_idmap, inode, MAY_EXEC); return err? nfserrno(err) : 0; From 53f07d095e7e680c5e4569a55a019f2c0348cdc6 Mon Sep 17 00:00:00 2001 From: NeilBrown Date: Sat, 13 Dec 2025 13:41:59 -0500 Subject: [PATCH 2224/3902] nfsd: provide locking for v4_end_grace commit 2857bd59feb63fcf40fe4baf55401baea6b4feb4 upstream. Writing to v4_end_grace can race with server shutdown and result in memory being accessed after it was freed - reclaim_str_hashtbl in particularly. We cannot hold nfsd_mutex across the nfsd4_end_grace() call as that is held while client_tracking_op->init() is called and that can wait for an upcall to nfsdcltrack which can write to v4_end_grace, resulting in a deadlock. nfsd4_end_grace() is also called by the landromat work queue and this doesn't require locking as server shutdown will stop the work and wait for it before freeing anything that nfsd4_end_grace() might access. However, we must be sure that writing to v4_end_grace doesn't restart the work item after shutdown has already waited for it. For this we add a new flag protected with nn->client_lock. It is set only while it is safe to make client tracking calls, and v4_end_grace only schedules work while the flag is set with the spinlock held. So this patch adds a nfsd_net field "client_tracking_active" which is set as described. Another field "grace_end_forced", is set when v4_end_grace is written. After this is set, and providing client_tracking_active is set, the laundromat is scheduled. This "grace_end_forced" field bypasses other checks for whether the grace period has finished. This resolves a race which can result in use-after-free. Reported-by: Li Lingfeng Closes: https://lore.kernel.org/linux-nfs/20250623030015.2353515-1-neil@brown.name/T/#t Fixes: 7f5ef2e900d9 ("nfsd: add a v4_end_grace file to /proc/fs/nfsd") Cc: stable@vger.kernel.org Signed-off-by: NeilBrown Tested-by: Li Lingfeng Reviewed-by: Jeff Layton Signed-off-by: Chuck Lever Signed-off-by: Greg Kroah-Hartman --- fs/nfsd/netns.h | 2 ++ fs/nfsd/nfs4state.c | 42 ++++++++++++++++++++++++++++++++++++++++-- fs/nfsd/nfsctl.c | 3 +-- fs/nfsd/state.h | 2 +- 4 files changed, 44 insertions(+), 5 deletions(-) diff --git a/fs/nfsd/netns.h b/fs/nfsd/netns.h index 3e2d0fde80a7ce..fe8338735e7cc5 100644 --- a/fs/nfsd/netns.h +++ b/fs/nfsd/netns.h @@ -66,6 +66,8 @@ struct nfsd_net { struct lock_manager nfsd4_manager; bool grace_ended; + bool grace_end_forced; + bool client_tracking_active; time64_t boot_time; struct dentry *nfsd_client_dir; diff --git a/fs/nfsd/nfs4state.c b/fs/nfsd/nfs4state.c index bdd20fddbb9860..70b5c478541b6a 100644 --- a/fs/nfsd/nfs4state.c +++ b/fs/nfsd/nfs4state.c @@ -84,7 +84,7 @@ static u64 current_sessionid = 1; /* forward declarations */ static bool check_for_locks(struct nfs4_file *fp, struct nfs4_lockowner *lowner); static void nfs4_free_ol_stateid(struct nfs4_stid *stid); -void nfsd4_end_grace(struct nfsd_net *nn); +static void nfsd4_end_grace(struct nfsd_net *nn); static void _free_cpntf_state_locked(struct nfsd_net *nn, struct nfs4_cpntf_state *cps); static void nfsd4_file_hash_remove(struct nfs4_file *fi); static void deleg_reaper(struct nfsd_net *nn); @@ -6597,7 +6597,7 @@ nfsd4_renew(struct svc_rqst *rqstp, struct nfsd4_compound_state *cstate, return nfs_ok; } -void +static void nfsd4_end_grace(struct nfsd_net *nn) { /* do nothing if grace period already ended */ @@ -6630,6 +6630,33 @@ nfsd4_end_grace(struct nfsd_net *nn) */ } +/** + * nfsd4_force_end_grace - forcibly end the NFSv4 grace period + * @nn: network namespace for the server instance to be updated + * + * Forces bypass of normal grace period completion, then schedules + * the laundromat to end the grace period immediately. Does not wait + * for the grace period to fully terminate before returning. + * + * Return values: + * %true: Grace termination schedule + * %false: No action was taken + */ +bool nfsd4_force_end_grace(struct nfsd_net *nn) +{ + if (!nn->client_tracking_ops) + return false; + spin_lock(&nn->client_lock); + if (nn->grace_ended || !nn->client_tracking_active) { + spin_unlock(&nn->client_lock); + return false; + } + WRITE_ONCE(nn->grace_end_forced, true); + mod_delayed_work(laundry_wq, &nn->laundromat_work, 0); + spin_unlock(&nn->client_lock); + return true; +} + /* * If we've waited a lease period but there are still clients trying to * reclaim, wait a little longer to give them a chance to finish. @@ -6639,6 +6666,8 @@ static bool clients_still_reclaiming(struct nfsd_net *nn) time64_t double_grace_period_end = nn->boot_time + 2 * nn->nfsd4_lease; + if (READ_ONCE(nn->grace_end_forced)) + return false; if (nn->track_reclaim_completes && atomic_read(&nn->nr_reclaim_complete) == nn->reclaim_str_hashtbl_size) @@ -8942,6 +8971,8 @@ static int nfs4_state_create_net(struct net *net) nn->unconf_name_tree = RB_ROOT; nn->boot_time = ktime_get_real_seconds(); nn->grace_ended = false; + nn->grace_end_forced = false; + nn->client_tracking_active = false; nn->nfsd4_manager.block_opens = true; INIT_LIST_HEAD(&nn->nfsd4_manager.list); INIT_LIST_HEAD(&nn->client_lru); @@ -9022,6 +9053,10 @@ nfs4_state_start_net(struct net *net) return ret; locks_start_grace(net, &nn->nfsd4_manager); nfsd4_client_tracking_init(net); + /* safe for laundromat to run now */ + spin_lock(&nn->client_lock); + nn->client_tracking_active = true; + spin_unlock(&nn->client_lock); if (nn->track_reclaim_completes && nn->reclaim_str_hashtbl_size == 0) goto skip_grace; printk(KERN_INFO "NFSD: starting %lld-second grace period (net %x)\n", @@ -9070,6 +9105,9 @@ nfs4_state_shutdown_net(struct net *net) shrinker_free(nn->nfsd_client_shrinker); cancel_work_sync(&nn->nfsd_shrinker_work); + spin_lock(&nn->client_lock); + nn->client_tracking_active = false; + spin_unlock(&nn->client_lock); cancel_delayed_work_sync(&nn->laundromat_work); locks_end_grace(&nn->nfsd4_manager); diff --git a/fs/nfsd/nfsctl.c b/fs/nfsd/nfsctl.c index 2b79129703d54b..36ce3ca97d978f 100644 --- a/fs/nfsd/nfsctl.c +++ b/fs/nfsd/nfsctl.c @@ -1082,10 +1082,9 @@ static ssize_t write_v4_end_grace(struct file *file, char *buf, size_t size) case 'Y': case 'y': case '1': - if (!nn->nfsd_serv) + if (!nfsd4_force_end_grace(nn)) return -EBUSY; trace_nfsd_end_grace(netns(file)); - nfsd4_end_grace(nn); break; default: return -EINVAL; diff --git a/fs/nfsd/state.h b/fs/nfsd/state.h index 1e736f4024263f..50d2b296339031 100644 --- a/fs/nfsd/state.h +++ b/fs/nfsd/state.h @@ -849,7 +849,7 @@ static inline void nfsd4_revoke_states(struct net *net, struct super_block *sb) #endif /* grace period management */ -void nfsd4_end_grace(struct nfsd_net *nn); +bool nfsd4_force_end_grace(struct nfsd_net *nn); /* nfs4recover operations */ extern int nfsd4_client_tracking_init(struct net *net); From 099a880ef541ea599c16e7957b51675b7676b062 Mon Sep 17 00:00:00 2001 From: NeilBrown Date: Mon, 15 Dec 2025 08:07:28 +1100 Subject: [PATCH 2225/3902] nfsd: use correct loop termination in nfsd4_revoke_states() commit fb321998de7639f1954430674475e469fb529d9c upstream. The loop in nfsd4_revoke_states() stops one too early because the end value given is CLIENT_HASH_MASK where it should be CLIENT_HASH_SIZE. This means that an admin request to drop all locks for a filesystem will miss locks held by clients which hash to the maximum possible hash value. Fixes: 1ac3629bf012 ("nfsd: prepare for supporting admin-revocation of state") Cc: stable@vger.kernel.org Signed-off-by: NeilBrown Reviewed-by: Jeff Layton Signed-off-by: Chuck Lever Signed-off-by: Greg Kroah-Hartman --- fs/nfsd/nfs4state.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fs/nfsd/nfs4state.c b/fs/nfsd/nfs4state.c index 70b5c478541b6a..ae39e563c2641c 100644 --- a/fs/nfsd/nfs4state.c +++ b/fs/nfsd/nfs4state.c @@ -1780,7 +1780,7 @@ void nfsd4_revoke_states(struct net *net, struct super_block *sb) sc_types = SC_TYPE_OPEN | SC_TYPE_LOCK | SC_TYPE_DELEG | SC_TYPE_LAYOUT; spin_lock(&nn->client_lock); - for (idhashval = 0; idhashval < CLIENT_HASH_MASK; idhashval++) { + for (idhashval = 0; idhashval < CLIENT_HASH_SIZE; idhashval++) { struct list_head *head = &nn->conf_id_hashtbl[idhashval]; struct nfs4_client *clp; retry: From e06c9f6c0f554148d4921c2a15bd054260a054ac Mon Sep 17 00:00:00 2001 From: Olga Kornievskaia Date: Mon, 15 Dec 2025 14:10:36 -0500 Subject: [PATCH 2226/3902] nfsd: check that server is running in unlock_filesystem commit d0424066fcd294977f310964bed6f2a487fa4515 upstream. If we are trying to unlock the filesystem via an administrative interface and nfsd isn't running, it crashes the server. This happens currently because nfsd4_revoke_states() access state structures (eg., conf_id_hashtbl) that has been freed as a part of the server shutdown. [ 59.465072] Call trace: [ 59.465308] nfsd4_revoke_states+0x1b4/0x898 [nfsd] (P) [ 59.465830] write_unlock_fs+0x258/0x440 [nfsd] [ 59.466278] nfsctl_transaction_write+0xb0/0x120 [nfsd] [ 59.466780] vfs_write+0x1f0/0x938 [ 59.467088] ksys_write+0xfc/0x1f8 [ 59.467395] __arm64_sys_write+0x74/0xb8 [ 59.467746] invoke_syscall.constprop.0+0xdc/0x1e8 [ 59.468177] do_el0_svc+0x154/0x1d8 [ 59.468489] el0_svc+0x40/0xe0 [ 59.468767] el0t_64_sync_handler+0xa0/0xe8 [ 59.469138] el0t_64_sync+0x1ac/0x1b0 Ensure this can't happen by taking the nfsd_mutex and checking that the server is still up, and then holding the mutex across the call to nfsd4_revoke_states(). Reviewed-by: NeilBrown Reviewed-by: Jeff Layton Fixes: 1ac3629bf0125 ("nfsd: prepare for supporting admin-revocation of state") Cc: stable@vger.kernel.org Signed-off-by: Olga Kornievskaia Signed-off-by: Chuck Lever Signed-off-by: Greg Kroah-Hartman --- fs/nfsd/nfs4state.c | 5 ++--- fs/nfsd/nfsctl.c | 9 ++++++++- fs/nfsd/state.h | 4 ++-- 3 files changed, 12 insertions(+), 6 deletions(-) diff --git a/fs/nfsd/nfs4state.c b/fs/nfsd/nfs4state.c index ae39e563c2641c..740c40eb5b3669 100644 --- a/fs/nfsd/nfs4state.c +++ b/fs/nfsd/nfs4state.c @@ -1759,7 +1759,7 @@ static struct nfs4_stid *find_one_sb_stid(struct nfs4_client *clp, /** * nfsd4_revoke_states - revoke all nfsv4 states associated with given filesystem - * @net: used to identify instance of nfsd (there is one per net namespace) + * @nn: used to identify instance of nfsd (there is one per net namespace) * @sb: super_block used to identify target filesystem * * All nfs4 states (open, lock, delegation, layout) held by the server instance @@ -1771,9 +1771,8 @@ static struct nfs4_stid *find_one_sb_stid(struct nfs4_client *clp, * The clients which own the states will subsequently being notified that the * states have been "admin-revoked". */ -void nfsd4_revoke_states(struct net *net, struct super_block *sb) +void nfsd4_revoke_states(struct nfsd_net *nn, struct super_block *sb) { - struct nfsd_net *nn = net_generic(net, nfsd_net_id); unsigned int idhashval; unsigned int sc_types; diff --git a/fs/nfsd/nfsctl.c b/fs/nfsd/nfsctl.c index 36ce3ca97d978f..8cbfb9dc3abb7f 100644 --- a/fs/nfsd/nfsctl.c +++ b/fs/nfsd/nfsctl.c @@ -259,6 +259,7 @@ static ssize_t write_unlock_fs(struct file *file, char *buf, size_t size) struct path path; char *fo_path; int error; + struct nfsd_net *nn; /* sanity check */ if (size == 0) @@ -285,7 +286,13 @@ static ssize_t write_unlock_fs(struct file *file, char *buf, size_t size) * 3. Is that directory the root of an exported file system? */ error = nlmsvc_unlock_all_by_sb(path.dentry->d_sb); - nfsd4_revoke_states(netns(file), path.dentry->d_sb); + mutex_lock(&nfsd_mutex); + nn = net_generic(netns(file), nfsd_net_id); + if (nn->nfsd_serv) + nfsd4_revoke_states(nn, path.dentry->d_sb); + else + error = -EINVAL; + mutex_unlock(&nfsd_mutex); path_put(&path); return error; diff --git a/fs/nfsd/state.h b/fs/nfsd/state.h index 50d2b296339031..c75bf3abec40ad 100644 --- a/fs/nfsd/state.h +++ b/fs/nfsd/state.h @@ -841,9 +841,9 @@ static inline void get_nfs4_file(struct nfs4_file *fi) struct nfsd_file *find_any_file(struct nfs4_file *f); #ifdef CONFIG_NFSD_V4 -void nfsd4_revoke_states(struct net *net, struct super_block *sb); +void nfsd4_revoke_states(struct nfsd_net *nn, struct super_block *sb); #else -static inline void nfsd4_revoke_states(struct net *net, struct super_block *sb) +static inline void nfsd4_revoke_states(struct nfsd_net *nn, struct super_block *sb) { } #endif From db78fa4b9f1fbedcb979b5d0c486d4f451ee7799 Mon Sep 17 00:00:00 2001 From: Edward Adam Davis Date: Tue, 16 Dec 2025 18:27:37 +0800 Subject: [PATCH 2227/3902] NFSD: net ref data still needs to be freed even if net hasn't startup commit 0b88bfa42e5468baff71909c2f324a495318532b upstream. When the NFSD instance doesn't to startup, the net ref data memory is not properly reclaimed, which triggers the memory leak issue reported by syzbot [1]. To avoid the problem reported in [1], the net ref data memory reclamation action is moved outside of nfsd_net_up when the net is shutdown. [1] unreferenced object 0xffff88812a39dfc0 (size 64): backtrace (crc a2262fc6): percpu_ref_init+0x94/0x1e0 lib/percpu-refcount.c:76 nfsd_create_serv+0xbe/0x260 fs/nfsd/nfssvc.c:605 nfsd_nl_listener_set_doit+0x62/0xb00 fs/nfsd/nfsctl.c:1882 genl_family_rcv_msg_doit+0x11e/0x190 net/netlink/genetlink.c:1115 genl_family_rcv_msg net/netlink/genetlink.c:1195 [inline] genl_rcv_msg+0x2fd/0x440 net/netlink/genetlink.c:1210 BUG: memory leak Reported-by: syzbot+6ee3b889bdeada0a6226@syzkaller.appspotmail.com Closes: https://syzkaller.appspot.com/bug?extid=6ee3b889bdeada0a6226 Fixes: 39972494e318 ("nfsd: update percpu_ref to manage references on nfsd_net") Cc: stable@vger.kernel.org Signed-off-by: Edward Adam Davis Signed-off-by: Chuck Lever Signed-off-by: Greg Kroah-Hartman --- fs/nfsd/nfssvc.c | 30 +++++++++++++++--------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/fs/nfsd/nfssvc.c b/fs/nfsd/nfssvc.c index 32cc03a7e7bec5..fcb47f344e32f0 100644 --- a/fs/nfsd/nfssvc.c +++ b/fs/nfsd/nfssvc.c @@ -424,26 +424,26 @@ static void nfsd_shutdown_net(struct net *net) { struct nfsd_net *nn = net_generic(net, nfsd_net_id); - if (!nn->nfsd_net_up) - return; - - percpu_ref_kill_and_confirm(&nn->nfsd_net_ref, nfsd_net_done); - wait_for_completion(&nn->nfsd_net_confirm_done); - - nfsd_export_flush(net); - nfs4_state_shutdown_net(net); - nfsd_reply_cache_shutdown(nn); - nfsd_file_cache_shutdown_net(net); - if (nn->lockd_up) { - lockd_down(net); - nn->lockd_up = false; + if (nn->nfsd_net_up) { + percpu_ref_kill_and_confirm(&nn->nfsd_net_ref, nfsd_net_done); + wait_for_completion(&nn->nfsd_net_confirm_done); + + nfsd_export_flush(net); + nfs4_state_shutdown_net(net); + nfsd_reply_cache_shutdown(nn); + nfsd_file_cache_shutdown_net(net); + if (nn->lockd_up) { + lockd_down(net); + nn->lockd_up = false; + } + wait_for_completion(&nn->nfsd_net_free_done); } - wait_for_completion(&nn->nfsd_net_free_done); percpu_ref_exit(&nn->nfsd_net_ref); + if (nn->nfsd_net_up) + nfsd_shutdown_generic(); nn->nfsd_net_up = false; - nfsd_shutdown_generic(); } static DEFINE_SPINLOCK(nfsd_notifier_lock); From 0f7fb819d63f3339c1a4eaf81d9edc28c34fca2b Mon Sep 17 00:00:00 2001 From: Chuck Lever Date: Tue, 9 Dec 2025 19:28:49 -0500 Subject: [PATCH 2228/3902] NFSD: Remove NFSERR_EAGAIN commit c6c209ceb87f64a6ceebe61761951dcbbf4a0baa upstream. I haven't found an NFSERR_EAGAIN in RFCs 1094, 1813, 7530, or 8881. None of these RFCs have an NFS status code that match the numeric value "11". Based on the meaning of the EAGAIN errno, I presume the use of this status in NFSD means NFS4ERR_DELAY. So replace the one usage of nfserr_eagain, and remove it from NFSD's NFS status conversion tables. As far as I can tell, NFSERR_EAGAIN has existed since the pre-git era, but was not actually used by any code until commit f4e44b393389 ("NFSD: delay unmount source's export after inter-server copy completed."), at which time it become possible for NFSD to return a status code of 11 (which is not valid NFS protocol). Fixes: f4e44b393389 ("NFSD: delay unmount source's export after inter-server copy completed.") Cc: stable@vger.kernel.org Reviewed-by: NeilBrown Reviewed-by: Jeff Layton Signed-off-by: Chuck Lever Signed-off-by: Greg Kroah-Hartman --- fs/nfs_common/common.c | 1 - fs/nfsd/nfs4proc.c | 2 +- fs/nfsd/nfsd.h | 1 - include/trace/misc/nfs.h | 2 -- include/uapi/linux/nfs.h | 1 - 5 files changed, 1 insertion(+), 6 deletions(-) diff --git a/fs/nfs_common/common.c b/fs/nfs_common/common.c index af09aed09fd279..0778743ae2c2c2 100644 --- a/fs/nfs_common/common.c +++ b/fs/nfs_common/common.c @@ -17,7 +17,6 @@ static const struct { { NFSERR_NOENT, -ENOENT }, { NFSERR_IO, -EIO }, { NFSERR_NXIO, -ENXIO }, -/* { NFSERR_EAGAIN, -EAGAIN }, */ { NFSERR_ACCES, -EACCES }, { NFSERR_EXIST, -EEXIST }, { NFSERR_XDEV, -EXDEV }, diff --git a/fs/nfsd/nfs4proc.c b/fs/nfsd/nfs4proc.c index 7f7e6bb23a90d9..42a6b914c0fe64 100644 --- a/fs/nfsd/nfs4proc.c +++ b/fs/nfsd/nfs4proc.c @@ -1506,7 +1506,7 @@ static __be32 nfsd4_ssc_setup_dul(struct nfsd_net *nn, char *ipaddr, (schedule_timeout(20*HZ) == 0)) { finish_wait(&nn->nfsd_ssc_waitq, &wait); kfree(work); - return nfserr_eagain; + return nfserr_jukebox; } finish_wait(&nn->nfsd_ssc_waitq, &wait); goto try_again; diff --git a/fs/nfsd/nfsd.h b/fs/nfsd/nfsd.h index 191cbf9320c0fb..8cecccdaf9ee5b 100644 --- a/fs/nfsd/nfsd.h +++ b/fs/nfsd/nfsd.h @@ -232,7 +232,6 @@ void nfsd_lockd_shutdown(void); #define nfserr_noent cpu_to_be32(NFSERR_NOENT) #define nfserr_io cpu_to_be32(NFSERR_IO) #define nfserr_nxio cpu_to_be32(NFSERR_NXIO) -#define nfserr_eagain cpu_to_be32(NFSERR_EAGAIN) #define nfserr_acces cpu_to_be32(NFSERR_ACCES) #define nfserr_exist cpu_to_be32(NFSERR_EXIST) #define nfserr_xdev cpu_to_be32(NFSERR_XDEV) diff --git a/include/trace/misc/nfs.h b/include/trace/misc/nfs.h index c82233e950ac06..a394b4d38e18fa 100644 --- a/include/trace/misc/nfs.h +++ b/include/trace/misc/nfs.h @@ -16,7 +16,6 @@ TRACE_DEFINE_ENUM(NFSERR_PERM); TRACE_DEFINE_ENUM(NFSERR_NOENT); TRACE_DEFINE_ENUM(NFSERR_IO); TRACE_DEFINE_ENUM(NFSERR_NXIO); -TRACE_DEFINE_ENUM(NFSERR_EAGAIN); TRACE_DEFINE_ENUM(NFSERR_ACCES); TRACE_DEFINE_ENUM(NFSERR_EXIST); TRACE_DEFINE_ENUM(NFSERR_XDEV); @@ -52,7 +51,6 @@ TRACE_DEFINE_ENUM(NFSERR_JUKEBOX); { NFSERR_NXIO, "NXIO" }, \ { ECHILD, "CHILD" }, \ { ETIMEDOUT, "TIMEDOUT" }, \ - { NFSERR_EAGAIN, "AGAIN" }, \ { NFSERR_ACCES, "ACCES" }, \ { NFSERR_EXIST, "EXIST" }, \ { NFSERR_XDEV, "XDEV" }, \ diff --git a/include/uapi/linux/nfs.h b/include/uapi/linux/nfs.h index f356f2ba381428..71c7196d32817d 100644 --- a/include/uapi/linux/nfs.h +++ b/include/uapi/linux/nfs.h @@ -49,7 +49,6 @@ NFSERR_NOENT = 2, /* v2 v3 v4 */ NFSERR_IO = 5, /* v2 v3 v4 */ NFSERR_NXIO = 6, /* v2 v3 v4 */ - NFSERR_EAGAIN = 11, /* v2 v3 */ NFSERR_ACCES = 13, /* v2 v3 v4 */ NFSERR_EXIST = 17, /* v2 v3 v4 */ NFSERR_XDEV = 18, /* v3 v4 */ From 4ae815bfcfadeb173b47a1de62ca00d36c815460 Mon Sep 17 00:00:00 2001 From: Thomas Fourier Date: Wed, 7 Jan 2026 10:01:36 +0100 Subject: [PATCH 2229/3902] atm: Fix dma_free_coherent() size commit 4d984b0574ff708e66152763fbfdef24ea40933f upstream. The size of the buffer is not the same when alloc'd with dma_alloc_coherent() in he_init_tpdrq() and freed. Fixes: 1da177e4c3f4 ("Linux-2.6.12-rc2") Cc: Signed-off-by: Thomas Fourier Link: https://patch.msgid.link/20260107090141.80900-2-fourier.thomas@gmail.com Signed-off-by: Jakub Kicinski Signed-off-by: Greg Kroah-Hartman --- drivers/atm/he.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/drivers/atm/he.c b/drivers/atm/he.c index ad91cc6a34fc52..92a041d5387bd8 100644 --- a/drivers/atm/he.c +++ b/drivers/atm/he.c @@ -1587,7 +1587,8 @@ he_stop(struct he_dev *he_dev) he_dev->tbrq_base, he_dev->tbrq_phys); if (he_dev->tpdrq_base) - dma_free_coherent(&he_dev->pci_dev->dev, CONFIG_TBRQ_SIZE * sizeof(struct he_tbrq), + dma_free_coherent(&he_dev->pci_dev->dev, + CONFIG_TPDRQ_SIZE * sizeof(struct he_tpdrq), he_dev->tpdrq_base, he_dev->tpdrq_phys); dma_pool_destroy(he_dev->tpd_pool); From d82796a57cc0dac1dbef19d913c8f02a8cc7b1a7 Mon Sep 17 00:00:00 2001 From: Thomas Fourier Date: Tue, 6 Jan 2026 10:47:21 +0100 Subject: [PATCH 2230/3902] net: 3com: 3c59x: fix possible null dereference in vortex_probe1() commit a4e305ed60f7c41bbf9aabc16dd75267194e0de3 upstream. pdev can be null and free_ring: can be called in 1297 with a null pdev. Fixes: 55c82617c3e8 ("3c59x: convert to generic DMA API") Cc: Signed-off-by: Thomas Fourier Link: https://patch.msgid.link/20260106094731.25819-2-fourier.thomas@gmail.com Signed-off-by: Jakub Kicinski Signed-off-by: Greg Kroah-Hartman --- drivers/net/ethernet/3com/3c59x.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/net/ethernet/3com/3c59x.c b/drivers/net/ethernet/3com/3c59x.c index 8c9cc97efd4eef..4fe4efdb3737fd 100644 --- a/drivers/net/ethernet/3com/3c59x.c +++ b/drivers/net/ethernet/3com/3c59x.c @@ -1473,7 +1473,7 @@ static int vortex_probe1(struct device *gendev, void __iomem *ioaddr, int irq, return 0; free_ring: - dma_free_coherent(&pdev->dev, + dma_free_coherent(gendev, sizeof(struct boom_rx_desc) * RX_RING_SIZE + sizeof(struct boom_tx_desc) * TX_RING_SIZE, vp->rx_ring, vp->rx_ring_dma); From ffa2be496ef65055b28b39c6bd9a7d66943ee89a Mon Sep 17 00:00:00 2001 From: Willem de Bruijn Date: Tue, 6 Jan 2026 10:05:46 -0500 Subject: [PATCH 2231/3902] net: do not write to msg_get_inq in callee commit 7d11e047eda5f98514ae62507065ac961981c025 upstream. NULL pointer dereference fix. msg_get_inq is an input field from caller to callee. Don't set it in the callee, as the caller may not clear it on struct reuse. This is a kernel-internal variant of msghdr only, and the only user does reinitialize the field. So this is not critical for that reason. But it is more robust to avoid the write, and slightly simpler code. And it fixes a bug, see below. Callers set msg_get_inq to request the input queue length to be returned in msg_inq. This is equivalent to but independent from the SO_INQ request to return that same info as a cmsg (tp->recvmsg_inq). To reduce branching in the hot path the second also sets the msg_inq. That is WAI. This is a fix to commit 4d1442979e4a ("af_unix: don't post cmsg for SO_INQ unless explicitly asked for"), which fixed the inverse. Also avoid NULL pointer dereference in unix_stream_read_generic if state->msg is NULL and msg->msg_get_inq is written. A NULL state->msg can happen when splicing as of commit 2b514574f7e8 ("net: af_unix: implement splice for stream af_unix sockets"). Also collapse two branches using a bitwise or. Cc: stable@vger.kernel.org Fixes: 4d1442979e4a ("af_unix: don't post cmsg for SO_INQ unless explicitly asked for") Link: https://lore.kernel.org/netdev/willemdebruijn.kernel.24d8030f7a3de@gmail.com/ Signed-off-by: Willem de Bruijn Reviewed-by: Jens Axboe Reviewed-by: Eric Dumazet Reviewed-by: Kuniyuki Iwashima Link: https://patch.msgid.link/20260106150626.3944363-1-willemdebruijn.kernel@gmail.com Signed-off-by: Jakub Kicinski Signed-off-by: Greg Kroah-Hartman --- net/ipv4/tcp.c | 8 +++----- net/unix/af_unix.c | 8 +++----- 2 files changed, 6 insertions(+), 10 deletions(-) diff --git a/net/ipv4/tcp.c b/net/ipv4/tcp.c index 8a18aeca7ab074..74079eab898040 100644 --- a/net/ipv4/tcp.c +++ b/net/ipv4/tcp.c @@ -2651,10 +2651,8 @@ static int tcp_recvmsg_locked(struct sock *sk, struct msghdr *msg, size_t len, if (sk->sk_state == TCP_LISTEN) goto out; - if (tp->recvmsg_inq) { + if (tp->recvmsg_inq) *cmsg_flags = TCP_CMSG_INQ; - msg->msg_get_inq = 1; - } timeo = sock_rcvtimeo(sk, flags & MSG_DONTWAIT); /* Urgent data needs to be handled specially. */ @@ -2928,10 +2926,10 @@ int tcp_recvmsg(struct sock *sk, struct msghdr *msg, size_t len, int flags, ret = tcp_recvmsg_locked(sk, msg, len, flags, &tss, &cmsg_flags); release_sock(sk); - if ((cmsg_flags || msg->msg_get_inq) && ret >= 0) { + if ((cmsg_flags | msg->msg_get_inq) && ret >= 0) { if (cmsg_flags & TCP_CMSG_TS) tcp_recv_timestamp(msg, sk, &tss); - if (msg->msg_get_inq) { + if ((cmsg_flags & TCP_CMSG_INQ) | msg->msg_get_inq) { msg->msg_inq = tcp_inq_hint(sk); if (cmsg_flags & TCP_CMSG_INQ) put_cmsg(msg, SOL_TCP, TCP_CM_INQ, diff --git a/net/unix/af_unix.c b/net/unix/af_unix.c index f6f01f514933b4..c634a7fc860906 100644 --- a/net/unix/af_unix.c +++ b/net/unix/af_unix.c @@ -2929,7 +2929,6 @@ static int unix_stream_read_generic(struct unix_stream_read_state *state, unsigned int last_len; struct unix_sock *u; int copied = 0; - bool do_cmsg; int err = 0; long timeo; int target; @@ -2955,9 +2954,6 @@ static int unix_stream_read_generic(struct unix_stream_read_state *state, u = unix_sk(sk); - do_cmsg = READ_ONCE(u->recvmsg_inq); - if (do_cmsg) - msg->msg_get_inq = 1; redo: /* Lock the socket to prevent queue disordering * while sleeps in memcpy_tomsg @@ -3115,9 +3111,11 @@ static int unix_stream_read_generic(struct unix_stream_read_state *state, mutex_unlock(&u->iolock); if (msg) { + bool do_cmsg = READ_ONCE(u->recvmsg_inq); + scm_recv_unix(sock, msg, &scm, flags); - if (msg->msg_get_inq && (copied ?: err) >= 0) { + if ((do_cmsg | msg->msg_get_inq) && (copied ?: err) >= 0) { msg->msg_inq = READ_ONCE(u->inq_len); if (do_cmsg) put_cmsg(msg, SOL_SOCKET, SCM_INQ, From e86c4ee5a029665e5cfcca990d896a07b2403e51 Mon Sep 17 00:00:00 2001 From: Yeoreum Yun Date: Wed, 7 Jan 2026 16:21:15 +0000 Subject: [PATCH 2232/3902] arm64: Fix cleared E0POE bit after cpu_suspend()/resume() commit bdf3f4176092df5281877cacf42f843063b4784d upstream. TCR2_ELx.E0POE is set during smp_init(). However, this bit is not reprogrammed when the CPU enters suspension and later resumes via cpu_resume(), as __cpu_setup() does not re-enable E0POE and there is no save/restore logic for the TCR2_ELx system register. As a result, the E0POE feature no longer works after cpu_resume(). To address this, save and restore TCR2_EL1 in the cpu_suspend()/cpu_resume() path, rather than adding related logic to __cpu_setup(), taking into account possible future extensions of the TCR2_ELx feature. Fixes: bf83dae90fbc ("arm64: enable the Permission Overlay Extension for EL0") Cc: # 6.12.x Signed-off-by: Yeoreum Yun Reviewed-by: Anshuman Khandual Reviewed-by: Kevin Brodsky Signed-off-by: Catalin Marinas Signed-off-by: Greg Kroah-Hartman --- arch/arm64/include/asm/suspend.h | 2 +- arch/arm64/mm/proc.S | 8 ++++++++ 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/arch/arm64/include/asm/suspend.h b/arch/arm64/include/asm/suspend.h index 0cde2f473971e8..eb60c973555305 100644 --- a/arch/arm64/include/asm/suspend.h +++ b/arch/arm64/include/asm/suspend.h @@ -2,7 +2,7 @@ #ifndef __ASM_SUSPEND_H #define __ASM_SUSPEND_H -#define NR_CTX_REGS 13 +#define NR_CTX_REGS 14 #define NR_CALLEE_SAVED_REGS 12 /* diff --git a/arch/arm64/mm/proc.S b/arch/arm64/mm/proc.S index 86818511962b61..17df19647556fe 100644 --- a/arch/arm64/mm/proc.S +++ b/arch/arm64/mm/proc.S @@ -100,6 +100,10 @@ SYM_FUNC_START(cpu_do_suspend) * call stack. */ str x18, [x0, #96] +alternative_if ARM64_HAS_TCR2 + mrs x2, REG_TCR2_EL1 + str x2, [x0, #104] +alternative_else_nop_endif ret SYM_FUNC_END(cpu_do_suspend) @@ -134,6 +138,10 @@ SYM_FUNC_START(cpu_do_resume) msr tcr_el1, x8 msr vbar_el1, x9 msr mdscr_el1, x10 +alternative_if ARM64_HAS_TCR2 + ldr x2, [x0, #104] + msr REG_TCR2_EL1, x2 +alternative_else_nop_endif msr sctlr_el1, x12 set_this_cpu_offset x13 From 0174d5466caefc22f03a36c43b2a3cce7e332627 Mon Sep 17 00:00:00 2001 From: Breno Leitao Date: Tue, 6 Jan 2026 06:31:14 -0800 Subject: [PATCH 2233/3902] bnxt_en: Fix NULL pointer crash in bnxt_ptp_enable during error cleanup commit 3358995b1a7f9dcb52a56ec8251570d71024dad0 upstream. When bnxt_init_one() fails during initialization (e.g., bnxt_init_int_mode returns -ENODEV), the error path calls bnxt_free_hwrm_resources() which destroys the DMA pool and sets bp->hwrm_dma_pool to NULL. Subsequently, bnxt_ptp_clear() is called, which invokes ptp_clock_unregister(). Since commit a60fc3294a37 ("ptp: rework ptp_clock_unregister() to disable events"), ptp_clock_unregister() now calls ptp_disable_all_events(), which in turn invokes the driver's .enable() callback (bnxt_ptp_enable()) to disable PTP events before completing the unregistration. bnxt_ptp_enable() attempts to send HWRM commands via bnxt_ptp_cfg_pin() and bnxt_ptp_cfg_event(), both of which call hwrm_req_init(). This function tries to allocate from bp->hwrm_dma_pool, causing a NULL pointer dereference: bnxt_en 0000:01:00.0 (unnamed net_device) (uninitialized): bnxt_init_int_mode err: ffffffed KASAN: null-ptr-deref in range [0x0000000000000028-0x000000000000002f] Call Trace: __hwrm_req_init (drivers/net/ethernet/broadcom/bnxt/bnxt_hwrm.c:72) bnxt_ptp_enable (drivers/net/ethernet/broadcom/bnxt/bnxt_ptp.c:323 drivers/net/ethernet/broadcom/bnxt/bnxt_ptp.c:517) ptp_disable_all_events (drivers/ptp/ptp_chardev.c:66) ptp_clock_unregister (drivers/ptp/ptp_clock.c:518) bnxt_ptp_clear (drivers/net/ethernet/broadcom/bnxt/bnxt_ptp.c:1134) bnxt_init_one (drivers/net/ethernet/broadcom/bnxt/bnxt.c:16889) Lines are against commit f8f9c1f4d0c7 ("Linux 6.19-rc3") Fix this by clearing and unregistering ptp (bnxt_ptp_clear()) before freeing HWRM resources. Suggested-by: Pavan Chebbi Signed-off-by: Breno Leitao Fixes: a60fc3294a37 ("ptp: rework ptp_clock_unregister() to disable events") Cc: stable@vger.kernel.org Reviewed-by: Pavan Chebbi Link: https://patch.msgid.link/20260106-bnxt-v3-1-71f37e11446a@debian.org Signed-off-by: Jakub Kicinski Signed-off-by: Greg Kroah-Hartman --- drivers/net/ethernet/broadcom/bnxt/bnxt.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/drivers/net/ethernet/broadcom/bnxt/bnxt.c b/drivers/net/ethernet/broadcom/bnxt/bnxt.c index a625e7c311dd7e..0366323ab0676b 100644 --- a/drivers/net/ethernet/broadcom/bnxt/bnxt.c +++ b/drivers/net/ethernet/broadcom/bnxt/bnxt.c @@ -16856,12 +16856,12 @@ static int bnxt_init_one(struct pci_dev *pdev, const struct pci_device_id *ent) init_err_pci_clean: bnxt_hwrm_func_drv_unrgtr(bp); - bnxt_free_hwrm_resources(bp); - bnxt_hwmon_uninit(bp); - bnxt_ethtool_free(bp); bnxt_ptp_clear(bp); kfree(bp->ptp_cfg); bp->ptp_cfg = NULL; + bnxt_free_hwrm_resources(bp); + bnxt_hwmon_uninit(bp); + bnxt_ethtool_free(bp); kfree(bp->fw_health); bp->fw_health = NULL; bnxt_cleanup_pci(bp); From d52af58dd463821c5c516aebb031a58934f696ea Mon Sep 17 00:00:00 2001 From: Filipe Manana Date: Thu, 11 Dec 2025 15:06:26 +0000 Subject: [PATCH 2234/3902] btrfs: always detect conflicting inodes when logging inode refs commit 7ba0b6461bc4edb3005ea6e00cdae189bcf908a5 upstream. After rename exchanging (either with the rename exchange operation or regular renames in multiple non-atomic steps) two inodes and at least one of them is a directory, we can end up with a log tree that contains only of the inodes and after a power failure that can result in an attempt to delete the other inode when it should not because it was not deleted before the power failure. In some case that delete attempt fails when the target inode is a directory that contains a subvolume inside it, since the log replay code is not prepared to deal with directory entries that point to root items (only inode items). 1) We have directories "dir1" (inode A) and "dir2" (inode B) under the same parent directory; 2) We have a file (inode C) under directory "dir1" (inode A); 3) We have a subvolume inside directory "dir2" (inode B); 4) All these inodes were persisted in a past transaction and we are currently at transaction N; 5) We rename the file (inode C), so at btrfs_log_new_name() we update inode C's last_unlink_trans to N; 6) We get a rename exchange for "dir1" (inode A) and "dir2" (inode B), so after the exchange "dir1" is inode B and "dir2" is inode A. During the rename exchange we call btrfs_log_new_name() for inodes A and B, but because they are directories, we don't update their last_unlink_trans to N; 7) An fsync against the file (inode C) is done, and because its inode has a last_unlink_trans with a value of N we log its parent directory (inode A) (through btrfs_log_all_parents(), called from btrfs_log_inode_parent()). 8) So we end up with inode B not logged, which now has the old name of inode A. At copy_inode_items_to_log(), when logging inode A, we did not check if we had any conflicting inode to log because inode A has a generation lower than the current transaction (created in a past transaction); 9) After a power failure, when replaying the log tree, since we find that inode A has a new name that conflicts with the name of inode B in the fs tree, we attempt to delete inode B... this is wrong since that directory was never deleted before the power failure, and because there is a subvolume inside that directory, attempting to delete it will fail since replay_dir_deletes() and btrfs_unlink_inode() are not prepared to deal with dir items that point to roots instead of inodes. When that happens the mount fails and we get a stack trace like the following: [87.2314] BTRFS info (device dm-0): start tree-log replay [87.2318] BTRFS critical (device dm-0): failed to delete reference to subvol, root 5 inode 256 parent 259 [87.2332] ------------[ cut here ]------------ [87.2338] BTRFS: Transaction aborted (error -2) [87.2346] WARNING: CPU: 1 PID: 638968 at fs/btrfs/inode.c:4345 __btrfs_unlink_inode+0x416/0x440 [btrfs] [87.2368] Modules linked in: btrfs loop dm_thin_pool (...) [87.2470] CPU: 1 UID: 0 PID: 638968 Comm: mount Tainted: G W 6.18.0-rc7-btrfs-next-218+ #2 PREEMPT(full) [87.2489] Tainted: [W]=WARN [87.2494] Hardware name: QEMU Standard PC (i440FX + PIIX, 1996), BIOS rel-1.16.2-0-gea1b7a073390-prebuilt.qemu.org 04/01/2014 [87.2514] RIP: 0010:__btrfs_unlink_inode+0x416/0x440 [btrfs] [87.2538] Code: c0 89 04 24 (...) [87.2568] RSP: 0018:ffffc0e741f4b9b8 EFLAGS: 00010286 [87.2574] RAX: 0000000000000000 RBX: ffff9d3ec8a6cf60 RCX: 0000000000000000 [87.2582] RDX: 0000000000000002 RSI: ffffffff84ab45a1 RDI: 00000000ffffffff [87.2591] RBP: ffff9d3ec8a6ef20 R08: 0000000000000000 R09: ffffc0e741f4b840 [87.2599] R10: ffff9d45dc1fffa8 R11: 0000000000000003 R12: ffff9d3ee26d77e0 [87.2608] R13: ffffc0e741f4ba98 R14: ffff9d4458040800 R15: ffff9d44b6b7ca10 [87.2618] FS: 00007f7b9603a840(0000) GS:ffff9d4658982000(0000) knlGS:0000000000000000 [87.2629] CS: 0010 DS: 0000 ES: 0000 CR0: 0000000080050033 [87.2637] CR2: 00007ffc9ec33b98 CR3: 000000011273e003 CR4: 0000000000370ef0 [87.2648] Call Trace: [87.2651] [87.2654] btrfs_unlink_inode+0x15/0x40 [btrfs] [87.2661] unlink_inode_for_log_replay+0x27/0xf0 [btrfs] [87.2669] check_item_in_log+0x1ea/0x2c0 [btrfs] [87.2676] replay_dir_deletes+0x16b/0x380 [btrfs] [87.2684] fixup_inode_link_count+0x34b/0x370 [btrfs] [87.2696] fixup_inode_link_counts+0x41/0x160 [btrfs] [87.2703] btrfs_recover_log_trees+0x1ff/0x7c0 [btrfs] [87.2711] ? __pfx_replay_one_buffer+0x10/0x10 [btrfs] [87.2719] open_ctree+0x10bb/0x15f0 [btrfs] [87.2726] btrfs_get_tree.cold+0xb/0x16c [btrfs] [87.2734] ? fscontext_read+0x15c/0x180 [87.2740] ? rw_verify_area+0x50/0x180 [87.2746] vfs_get_tree+0x25/0xd0 [87.2750] vfs_cmd_create+0x59/0xe0 [87.2755] __do_sys_fsconfig+0x4f6/0x6b0 [87.2760] do_syscall_64+0x50/0x1220 [87.2764] entry_SYSCALL_64_after_hwframe+0x76/0x7e [87.2770] RIP: 0033:0x7f7b9625f4aa [87.2775] Code: 73 01 c3 48 (...) [87.2803] RSP: 002b:00007ffc9ec35b08 EFLAGS: 00000246 ORIG_RAX: 00000000000001af [87.2817] RAX: ffffffffffffffda RBX: 0000558bfa91ac20 RCX: 00007f7b9625f4aa [87.2829] RDX: 0000000000000000 RSI: 0000000000000006 RDI: 0000000000000003 [87.2842] RBP: 0000558bfa91b120 R08: 0000000000000000 R09: 0000000000000000 [87.2854] R10: 0000000000000000 R11: 0000000000000246 R12: 0000000000000000 [87.2864] R13: 00007f7b963f1580 R14: 00007f7b963f326c R15: 00007f7b963d8a23 [87.2877] [87.2882] ---[ end trace 0000000000000000 ]--- [87.2891] BTRFS: error (device dm-0 state A) in __btrfs_unlink_inode:4345: errno=-2 No such entry [87.2904] BTRFS: error (device dm-0 state EAO) in do_abort_log_replay:191: errno=-2 No such entry [87.2915] BTRFS critical (device dm-0 state EAO): log tree (for root 5) leaf currently being processed (slot 7 key (258 12 257)): [87.2929] BTRFS info (device dm-0 state EAO): leaf 30736384 gen 10 total ptrs 7 free space 15712 owner 18446744073709551610 [87.2929] BTRFS info (device dm-0 state EAO): refs 3 lock_owner 0 current 638968 [87.2929] item 0 key (257 INODE_ITEM 0) itemoff 16123 itemsize 160 [87.2929] inode generation 9 transid 10 size 0 nbytes 0 [87.2929] block group 0 mode 40755 links 1 uid 0 gid 0 [87.2929] rdev 0 sequence 7 flags 0x0 [87.2929] atime 1765464494.678070921 [87.2929] ctime 1765464494.686606513 [87.2929] mtime 1765464494.686606513 [87.2929] otime 1765464494.678070921 [87.2929] item 1 key (257 INODE_REF 256) itemoff 16109 itemsize 14 [87.2929] index 4 name_len 4 [87.2929] item 2 key (257 DIR_LOG_INDEX 2) itemoff 16101 itemsize 8 [87.2929] dir log end 2 [87.2929] item 3 key (257 DIR_LOG_INDEX 3) itemoff 16093 itemsize 8 [87.2929] dir log end 18446744073709551615 [87.2930] item 4 key (257 DIR_INDEX 3) itemoff 16060 itemsize 33 [87.2930] location key (258 1 0) type 1 [87.2930] transid 10 data_len 0 name_len 3 [87.2930] item 5 key (258 INODE_ITEM 0) itemoff 15900 itemsize 160 [87.2930] inode generation 9 transid 10 size 0 nbytes 0 [87.2930] block group 0 mode 100644 links 1 uid 0 gid 0 [87.2930] rdev 0 sequence 2 flags 0x0 [87.2930] atime 1765464494.678456467 [87.2930] ctime 1765464494.686606513 [87.2930] mtime 1765464494.678456467 [87.2930] otime 1765464494.678456467 [87.2930] item 6 key (258 INODE_REF 257) itemoff 15887 itemsize 13 [87.2930] index 3 name_len 3 [87.2930] BTRFS critical (device dm-0 state EAO): log replay failed in unlink_inode_for_log_replay:1045 for root 5, stage 3, with error -2: failed to unlink inode 256 parent dir 259 name subvol root 5 [87.2963] BTRFS: error (device dm-0 state EAO) in btrfs_recover_log_trees:7743: errno=-2 No such entry [87.2981] BTRFS: error (device dm-0 state EAO) in btrfs_replay_log:2083: errno=-2 No such entry (Failed to recover log tr So fix this by changing copy_inode_items_to_log() to always detect if there are conflicting inodes for the ref/extref of the inode being logged even if the inode was created in a past transaction. A test case for fstests will follow soon. CC: stable@vger.kernel.org # 6.1+ Signed-off-by: Filipe Manana Signed-off-by: David Sterba Signed-off-by: Greg Kroah-Hartman --- fs/btrfs/tree-log.c | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/fs/btrfs/tree-log.c b/fs/btrfs/tree-log.c index e0b0750696a15b..de9ea9d52482f5 100644 --- a/fs/btrfs/tree-log.c +++ b/fs/btrfs/tree-log.c @@ -6348,10 +6348,8 @@ static int copy_inode_items_to_log(struct btrfs_trans_handle *trans, * and no keys greater than that, so bail out. */ break; - } else if ((min_key->type == BTRFS_INODE_REF_KEY || - min_key->type == BTRFS_INODE_EXTREF_KEY) && - (inode->generation == trans->transid || - ctx->logging_conflict_inodes)) { + } else if (min_key->type == BTRFS_INODE_REF_KEY || + min_key->type == BTRFS_INODE_EXTREF_KEY) { u64 other_ino = 0; u64 other_parent = 0; From 86e49948b0e62a72b3b91f8485ca74fe5b71e81b Mon Sep 17 00:00:00 2001 From: Alexander Usyskin Date: Mon, 15 Dec 2025 12:59:15 +0200 Subject: [PATCH 2235/3902] mei: me: add nova lake point S DID commit 420f423defcf6d0af2263d38da870ca4a20c0990 upstream. Add Nova Lake S device id. Cc: stable Co-developed-by: Tomas Winkler Signed-off-by: Tomas Winkler Signed-off-by: Alexander Usyskin Link: https://patch.msgid.link/20251215105915.1672659-1-alexander.usyskin@intel.com Signed-off-by: Greg Kroah-Hartman --- drivers/misc/mei/hw-me-regs.h | 2 ++ drivers/misc/mei/pci-me.c | 2 ++ 2 files changed, 4 insertions(+) diff --git a/drivers/misc/mei/hw-me-regs.h b/drivers/misc/mei/hw-me-regs.h index a4f75dc3692920..fa30899a5fa260 100644 --- a/drivers/misc/mei/hw-me-regs.h +++ b/drivers/misc/mei/hw-me-regs.h @@ -122,6 +122,8 @@ #define MEI_DEV_ID_WCL_P 0x4D70 /* Wildcat Lake P */ +#define MEI_DEV_ID_NVL_S 0x6E68 /* Nova Lake Point S */ + /* * MEI HW Section */ diff --git a/drivers/misc/mei/pci-me.c b/drivers/misc/mei/pci-me.c index 73cad914be9f9b..2a6e569558b943 100644 --- a/drivers/misc/mei/pci-me.c +++ b/drivers/misc/mei/pci-me.c @@ -129,6 +129,8 @@ static const struct pci_device_id mei_me_pci_tbl[] = { {MEI_PCI_DEVICE(MEI_DEV_ID_WCL_P, MEI_ME_PCH15_CFG)}, + {MEI_PCI_DEVICE(MEI_DEV_ID_NVL_S, MEI_ME_PCH15_CFG)}, + /* required last entry */ {0, } }; From 30a98c97f7874031f2e1de19c777ce011143cba4 Mon Sep 17 00:00:00 2001 From: Alice Ryhl Date: Tue, 2 Dec 2025 11:24:24 +0000 Subject: [PATCH 2236/3902] rust_binder: remove spin_lock() in rust_shrink_free_page() commit 361e0ff456a8daf9753c18030533256e4133ce7a upstream. When forward-porting Rust Binder to 6.18, I neglected to take commit fb56fdf8b9a2 ("mm/list_lru: split the lock to per-cgroup scope") into account, and apparently I did not end up running the shrinker callback when I sanity tested the driver before submission. This leads to crashes like the following: ============================================ WARNING: possible recursive locking detected 6.18.0-mainline-maybe-dirty #1 Tainted: G IO -------------------------------------------- kswapd0/68 is trying to acquire lock: ffff956000fa18b0 (&l->lock){+.+.}-{2:2}, at: lock_list_lru_of_memcg+0x128/0x230 but task is already holding lock: ffff956000fa18b0 (&l->lock){+.+.}-{2:2}, at: rust_helper_spin_lock+0xd/0x20 other info that might help us debug this: Possible unsafe locking scenario: CPU0 ---- lock(&l->lock); lock(&l->lock); *** DEADLOCK *** May be due to missing lock nesting notation 3 locks held by kswapd0/68: #0: ffffffff90d2e260 (fs_reclaim){+.+.}-{0:0}, at: kswapd+0x597/0x1160 #1: ffff956000fa18b0 (&l->lock){+.+.}-{2:2}, at: rust_helper_spin_lock+0xd/0x20 #2: ffffffff90cf3680 (rcu_read_lock){....}-{1:2}, at: lock_list_lru_of_memcg+0x2d/0x230 To fix this, remove the spin_lock() call from rust_shrink_free_page(). Cc: stable Fixes: eafedbc7c050 ("rust_binder: add Rust Binder driver") Signed-off-by: Alice Ryhl Link: https://patch.msgid.link/20251202-binder-shrink-unspin-v1-1-263efb9ad625@google.com Signed-off-by: Greg Kroah-Hartman --- drivers/android/binder/page_range.rs | 3 --- 1 file changed, 3 deletions(-) diff --git a/drivers/android/binder/page_range.rs b/drivers/android/binder/page_range.rs index 9379038f61f513..fdd97112ef5c8b 100644 --- a/drivers/android/binder/page_range.rs +++ b/drivers/android/binder/page_range.rs @@ -727,8 +727,5 @@ unsafe extern "C" fn rust_shrink_free_page( drop(mm); drop(page); - // SAFETY: We just unlocked the lru lock, but it should be locked when we return. - unsafe { bindings::spin_lock(&raw mut (*lru).lock) }; - LRU_REMOVED_ENTRY } From a0b28dd06a9a754da0f0fcb94296e92048e6f911 Mon Sep 17 00:00:00 2001 From: Eric Biggers Date: Tue, 6 Jan 2026 21:20:23 -0800 Subject: [PATCH 2237/3902] lib/crypto: aes: Fix missing MMU protection for AES S-box commit 74d74bb78aeccc9edc10db216d6be121cf7ec176 upstream. __cacheline_aligned puts the data in the ".data..cacheline_aligned" section, which isn't marked read-only i.e. it doesn't receive MMU protection. Replace it with ____cacheline_aligned which does the right thing and just aligns the data while keeping it in ".rodata". Fixes: b5e0b032b6c3 ("crypto: aes - add generic time invariant AES cipher") Cc: stable@vger.kernel.org Reported-by: Qingfang Deng Closes: https://lore.kernel.org/r/20260105074712.498-1-dqfext@gmail.com/ Acked-by: Ard Biesheuvel Link: https://lore.kernel.org/r/20260107052023.174620-1-ebiggers@kernel.org Signed-off-by: Eric Biggers Signed-off-by: Greg Kroah-Hartman --- lib/crypto/aes.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/crypto/aes.c b/lib/crypto/aes.c index b57fda3460f1b3..102aaa76bc8d76 100644 --- a/lib/crypto/aes.c +++ b/lib/crypto/aes.c @@ -13,7 +13,7 @@ * Emit the sbox as volatile const to prevent the compiler from doing * constant folding on sbox references involving fixed indexes. */ -static volatile const u8 __cacheline_aligned aes_sbox[] = { +static volatile const u8 ____cacheline_aligned aes_sbox[] = { 0x63, 0x7c, 0x77, 0x7b, 0xf2, 0x6b, 0x6f, 0xc5, 0x30, 0x01, 0x67, 0x2b, 0xfe, 0xd7, 0xab, 0x76, 0xca, 0x82, 0xc9, 0x7d, 0xfa, 0x59, 0x47, 0xf0, @@ -48,7 +48,7 @@ static volatile const u8 __cacheline_aligned aes_sbox[] = { 0x41, 0x99, 0x2d, 0x0f, 0xb0, 0x54, 0xbb, 0x16, }; -static volatile const u8 __cacheline_aligned aes_inv_sbox[] = { +static volatile const u8 ____cacheline_aligned aes_inv_sbox[] = { 0x52, 0x09, 0x6a, 0xd5, 0x30, 0x36, 0xa5, 0x38, 0xbf, 0x40, 0xa3, 0x9e, 0x81, 0xf3, 0xd7, 0xfb, 0x7c, 0xe3, 0x39, 0x82, 0x9b, 0x2f, 0xff, 0x87, From 844f521604bdef93fd3b648673e75528d8e9ef78 Mon Sep 17 00:00:00 2001 From: Haotian Zhang Date: Mon, 15 Dec 2025 10:01:14 +0800 Subject: [PATCH 2238/3902] counter: 104-quad-8: Fix incorrect return value in IRQ handler commit 9517d76dd160208b7a432301ce7bec8fc1ddc305 upstream. quad8_irq_handler() should return irqreturn_t enum values, but it directly returns negative errno codes from regmap operations on error. Return IRQ_NONE if the interrupt status cannot be read. If clearing the interrupt fails, return IRQ_HANDLED to prevent the kernel from disabling the IRQ line due to a spurious interrupt storm. Also, log these regmap failures with dev_WARN_ONCE. Fixes: 98ffe0252911 ("counter: 104-quad-8: Migrate to the regmap API") Suggested-by: Andy Shevchenko Signed-off-by: Haotian Zhang Link: https://lore.kernel.org/r/20251215020114.1913-1-vulab@iscas.ac.cn Cc: stable@vger.kernel.org Signed-off-by: William Breathitt Gray Signed-off-by: Greg Kroah-Hartman --- drivers/counter/104-quad-8.c | 20 ++++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/drivers/counter/104-quad-8.c b/drivers/counter/104-quad-8.c index ce81fc4e1ae763..573b2fe93253e5 100644 --- a/drivers/counter/104-quad-8.c +++ b/drivers/counter/104-quad-8.c @@ -1192,6 +1192,7 @@ static irqreturn_t quad8_irq_handler(int irq, void *private) { struct counter_device *counter = private; struct quad8 *const priv = counter_priv(counter); + struct device *dev = counter->parent; unsigned int status; unsigned long irq_status; unsigned long channel; @@ -1200,8 +1201,11 @@ static irqreturn_t quad8_irq_handler(int irq, void *private) int ret; ret = regmap_read(priv->map, QUAD8_INTERRUPT_STATUS, &status); - if (ret) - return ret; + if (ret) { + dev_WARN_ONCE(dev, true, + "Attempt to read Interrupt Status Register failed: %d\n", ret); + return IRQ_NONE; + } if (!status) return IRQ_NONE; @@ -1223,8 +1227,9 @@ static irqreturn_t quad8_irq_handler(int irq, void *private) break; default: /* should never reach this path */ - WARN_ONCE(true, "invalid interrupt trigger function %u configured for channel %lu\n", - flg_pins, channel); + dev_WARN_ONCE(dev, true, + "invalid interrupt trigger function %u configured for channel %lu\n", + flg_pins, channel); continue; } @@ -1232,8 +1237,11 @@ static irqreturn_t quad8_irq_handler(int irq, void *private) } ret = regmap_write(priv->map, QUAD8_CHANNEL_OPERATION, CLEAR_PENDING_INTERRUPTS); - if (ret) - return ret; + if (ret) { + dev_WARN_ONCE(dev, true, + "Attempt to clear pending interrupts by writing to Channel Operation Register failed: %d\n", ret); + return IRQ_HANDLED; + } return IRQ_HANDLED; } From 425886b1f8304621b3f16632b274357067d5f13f Mon Sep 17 00:00:00 2001 From: Alexander Sverdlin Date: Tue, 18 Nov 2025 09:35:48 +0100 Subject: [PATCH 2239/3902] counter: interrupt-cnt: Drop IRQF_NO_THREAD flag commit 23f9485510c338476b9735d516c1d4aacb810d46 upstream. An IRQ handler can either be IRQF_NO_THREAD or acquire spinlock_t, as CONFIG_PROVE_RAW_LOCK_NESTING warns: ============================= [ BUG: Invalid wait context ] 6.18.0-rc1+git... #1 ----------------------------- some-user-space-process/1251 is trying to lock: (&counter->events_list_lock){....}-{3:3}, at: counter_push_event [counter] other info that might help us debug this: context-{2:2} no locks held by some-user-space-process/.... stack backtrace: CPU: 0 UID: 0 PID: 1251 Comm: some-user-space-process 6.18.0-rc1+git... #1 PREEMPT Call trace: show_stack (C) dump_stack_lvl dump_stack __lock_acquire lock_acquire _raw_spin_lock_irqsave counter_push_event [counter] interrupt_cnt_isr [interrupt_cnt] __handle_irq_event_percpu handle_irq_event handle_simple_irq handle_irq_desc generic_handle_domain_irq gpio_irq_handler handle_irq_desc generic_handle_domain_irq gic_handle_irq call_on_irq_stack do_interrupt_handler el0_interrupt __el0_irq_handler_common el0t_64_irq_handler el0t_64_irq ... and Sebastian correctly points out. Remove IRQF_NO_THREAD as an alternative to switching to raw_spinlock_t, because the latter would limit all potential nested locks to raw_spinlock_t only. Cc: Sebastian Andrzej Siewior Cc: stable@vger.kernel.org Link: https://lore.kernel.org/all/20251117151314.xwLAZrWY@linutronix.de/ Fixes: a55ebd47f21f ("counter: add IRQ or GPIO based counter") Signed-off-by: Alexander Sverdlin Reviewed-by: Sebastian Andrzej Siewior Reviewed-by: Oleksij Rempel Link: https://lore.kernel.org/r/20251118083603.778626-1-alexander.sverdlin@siemens.com Signed-off-by: William Breathitt Gray Signed-off-by: Greg Kroah-Hartman --- drivers/counter/interrupt-cnt.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/drivers/counter/interrupt-cnt.c b/drivers/counter/interrupt-cnt.c index 6c0c1d2d7027d5..e6100b5fb082e0 100644 --- a/drivers/counter/interrupt-cnt.c +++ b/drivers/counter/interrupt-cnt.c @@ -229,8 +229,7 @@ static int interrupt_cnt_probe(struct platform_device *pdev) irq_set_status_flags(priv->irq, IRQ_NOAUTOEN); ret = devm_request_irq(dev, priv->irq, interrupt_cnt_isr, - IRQF_TRIGGER_RISING | IRQF_NO_THREAD, - dev_name(dev), counter); + IRQF_TRIGGER_RISING, dev_name(dev), counter); if (ret) return ret; From 5b7f91acffd2c4c000971553d22efa1e1bb4feae Mon Sep 17 00:00:00 2001 From: Steven Rostedt Date: Mon, 5 Jan 2026 20:31:41 -0500 Subject: [PATCH 2240/3902] tracing: Add recursion protection in kernel stack trace recording commit 5f1ef0dfcb5b7f4a91a9b0e0ba533efd9f7e2cdb upstream. A bug was reported about an infinite recursion caused by tracing the rcu events with the kernel stack trace trigger enabled. The stack trace code called back into RCU which then called the stack trace again. Expand the ftrace recursion protection to add a set of bits to protect events from recursion. Each bit represents the context that the event is in (normal, softirq, interrupt and NMI). Have the stack trace code use the interrupt context to protect against recursion. Note, the bug showed an issue in both the RCU code as well as the tracing stacktrace code. This only handles the tracing stack trace side of the bug. The RCU fix will be handled separately. Link: https://lore.kernel.org/all/20260102122807.7025fc87@gandalf.local.home/ Cc: stable@vger.kernel.org Cc: Masami Hiramatsu Cc: Mathieu Desnoyers Cc: Joel Fernandes Cc: "Paul E. McKenney" Cc: Boqun Feng Link: https://patch.msgid.link/20260105203141.515cd49f@gandalf.local.home Reported-by: Yao Kai Tested-by: Yao Kai Fixes: 5f5fa7ea89dc ("rcu: Don't use negative nesting depth in __rcu_read_unlock()") Signed-off-by: Steven Rostedt (Google) Signed-off-by: Greg Kroah-Hartman --- include/linux/trace_recursion.h | 9 +++++++++ kernel/trace/trace.c | 6 ++++++ 2 files changed, 15 insertions(+) diff --git a/include/linux/trace_recursion.h b/include/linux/trace_recursion.h index ae04054a1be360..e6ca052b2a85a7 100644 --- a/include/linux/trace_recursion.h +++ b/include/linux/trace_recursion.h @@ -34,6 +34,13 @@ enum { TRACE_INTERNAL_SIRQ_BIT, TRACE_INTERNAL_TRANSITION_BIT, + /* Internal event use recursion bits */ + TRACE_INTERNAL_EVENT_BIT, + TRACE_INTERNAL_EVENT_NMI_BIT, + TRACE_INTERNAL_EVENT_IRQ_BIT, + TRACE_INTERNAL_EVENT_SIRQ_BIT, + TRACE_INTERNAL_EVENT_TRANSITION_BIT, + TRACE_BRANCH_BIT, /* * Abuse of the trace_recursion. @@ -58,6 +65,8 @@ enum { #define TRACE_LIST_START TRACE_INTERNAL_BIT +#define TRACE_EVENT_START TRACE_INTERNAL_EVENT_BIT + #define TRACE_CONTEXT_MASK ((1 << (TRACE_LIST_START + TRACE_CONTEXT_BITS)) - 1) /* diff --git a/kernel/trace/trace.c b/kernel/trace/trace.c index 304e93597126e5..ddff2af3cd3f7d 100644 --- a/kernel/trace/trace.c +++ b/kernel/trace/trace.c @@ -3003,6 +3003,11 @@ static void __ftrace_trace_stack(struct trace_array *tr, struct ftrace_stack *fstack; struct stack_entry *entry; int stackidx; + int bit; + + bit = trace_test_and_set_recursion(_THIS_IP_, _RET_IP_, TRACE_EVENT_START); + if (bit < 0) + return; /* * Add one, for this function and the call to save_stack_trace() @@ -3071,6 +3076,7 @@ static void __ftrace_trace_stack(struct trace_array *tr, /* Again, don't let gcc optimize things here */ barrier(); __this_cpu_dec(ftrace_stack_reserve); + trace_clear_recursion(bit); } static inline void ftrace_trace_stack(struct trace_array *tr, From 64a0d47e0c3227b13d08bf01de78508e3bf8058d Mon Sep 17 00:00:00 2001 From: Vivian Wang Date: Tue, 30 Dec 2025 21:39:17 +0800 Subject: [PATCH 2241/3902] riscv: boot: Always make Image from vmlinux, not vmlinux.unstripped commit 66562b66dcbc8f93c1e28632299f449bb2f5c47d upstream. Since commit 4b47a3aefb29 ("kbuild: Restore pattern to avoid stripping .rela.dyn from vmlinux") vmlinux has .rel*.dyn preserved. Therefore, use vmlinux to produce Image, not vmlinux.unstripped. Doing so fixes booting a RELOCATABLE=y Image with kexec. The problem is caused by this chain of events: - Since commit 3e86e4d74c04 ("kbuild: keep .modinfo section in vmlinux.unstripped"), vmlinux.unstripped gets a .modinfo section. - The .modinfo section has SHF_ALLOC, so it ends up in Image, at the end of it. - The Image header's image_size field does not expect to include .modinfo and does not account for it, since it should not be in Image. - If .modinfo is large enough, the file size of Image ends up larger than image_size, which eventually leads to it failing sanity_check_segment_list(). Using vmlinux instead of vmlinux.unstripped means that the unexpected .modinfo section is gone from Image, fixing the file size problem. Cc: stable@vger.kernel.org Fixes: 3e86e4d74c04 ("kbuild: keep .modinfo section in vmlinux.unstripped") Signed-off-by: Vivian Wang Reviewed-by: Nathan Chancellor Tested-by: Han Gao Link: https://patch.msgid.link/20251230-riscv-vmlinux-not-unstripped-v1-1-15f49df880df@iscas.ac.cn Signed-off-by: Paul Walmsley Signed-off-by: Greg Kroah-Hartman --- arch/riscv/boot/Makefile | 4 ---- 1 file changed, 4 deletions(-) diff --git a/arch/riscv/boot/Makefile b/arch/riscv/boot/Makefile index bfc3d0b75b9b2b..5301adf5f3f5d2 100644 --- a/arch/riscv/boot/Makefile +++ b/arch/riscv/boot/Makefile @@ -31,11 +31,7 @@ $(obj)/xipImage: vmlinux FORCE endif -ifdef CONFIG_RELOCATABLE -$(obj)/Image: vmlinux.unstripped FORCE -else $(obj)/Image: vmlinux FORCE -endif $(call if_changed,objcopy) $(obj)/Image.gz: $(obj)/Image FORCE From 0ce45b2426f966736459758f9829a1fe79a07338 Mon Sep 17 00:00:00 2001 From: Dave Airlie Date: Fri, 2 Jan 2026 14:18:29 +1000 Subject: [PATCH 2242/3902] nouveau: don't attempt fwsec on sb on newer platforms. commit e8b3627bec357698f2d4d6dbf27cdcfa0e9d8715 upstream. The changes to always loads fwsec sb causes problems on newer GPUs which don't use this path. Add hooks and pass through the device specific layers. Fixes: da67179e5538 ("drm/nouveau/gsp: Allocate fwsec-sb at boot") Cc: # v6.16+ Cc: Lyude Paul Cc: Timur Tabi Tested-by: Matthew Schwartz Tested-by: Christopher Snowhill Reviewed-by: Lyude Paul Signed-off-by: Dave Airlie Link: https://patch.msgid.link/20260102041829.2748009-1-airlied@gmail.com Signed-off-by: Greg Kroah-Hartman --- .../gpu/drm/nouveau/nvkm/subdev/gsp/ad102.c | 3 +++ .../gpu/drm/nouveau/nvkm/subdev/gsp/fwsec.c | 8 +------ .../gpu/drm/nouveau/nvkm/subdev/gsp/ga100.c | 3 +++ .../gpu/drm/nouveau/nvkm/subdev/gsp/ga102.c | 3 +++ .../gpu/drm/nouveau/nvkm/subdev/gsp/priv.h | 23 +++++++++++++++++-- .../gpu/drm/nouveau/nvkm/subdev/gsp/tu102.c | 15 ++++++++++++ .../gpu/drm/nouveau/nvkm/subdev/gsp/tu116.c | 3 +++ 7 files changed, 49 insertions(+), 9 deletions(-) diff --git a/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/ad102.c b/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/ad102.c index 35d1fcef520bf0..c456a96268231a 100644 --- a/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/ad102.c +++ b/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/ad102.c @@ -30,6 +30,9 @@ ad102_gsp = { .booter.ctor = ga102_gsp_booter_ctor, + .fwsec_sb.ctor = tu102_gsp_fwsec_sb_ctor, + .fwsec_sb.dtor = tu102_gsp_fwsec_sb_dtor, + .dtor = r535_gsp_dtor, .oneinit = tu102_gsp_oneinit, .init = tu102_gsp_init, diff --git a/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/fwsec.c b/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/fwsec.c index 50376024666042..851140e8012200 100644 --- a/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/fwsec.c +++ b/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/fwsec.c @@ -337,18 +337,12 @@ nvkm_gsp_fwsec_sb(struct nvkm_gsp *gsp) } int -nvkm_gsp_fwsec_sb_ctor(struct nvkm_gsp *gsp) +nvkm_gsp_fwsec_sb_init(struct nvkm_gsp *gsp) { return nvkm_gsp_fwsec_init(gsp, &gsp->fws.falcon.sb, "fwsec-sb", NVFW_FALCON_APPIF_DMEMMAPPER_CMD_SB); } -void -nvkm_gsp_fwsec_sb_dtor(struct nvkm_gsp *gsp) -{ - nvkm_falcon_fw_dtor(&gsp->fws.falcon.sb); -} - int nvkm_gsp_fwsec_frts(struct nvkm_gsp *gsp) { diff --git a/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/ga100.c b/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/ga100.c index d201e8697226be..27a13aeccd3cb3 100644 --- a/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/ga100.c +++ b/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/ga100.c @@ -47,6 +47,9 @@ ga100_gsp = { .booter.ctor = tu102_gsp_booter_ctor, + .fwsec_sb.ctor = tu102_gsp_fwsec_sb_ctor, + .fwsec_sb.dtor = tu102_gsp_fwsec_sb_dtor, + .dtor = r535_gsp_dtor, .oneinit = tu102_gsp_oneinit, .init = tu102_gsp_init, diff --git a/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/ga102.c b/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/ga102.c index 917f7e2f6c466a..b6b3eb6f4c006e 100644 --- a/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/ga102.c +++ b/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/ga102.c @@ -158,6 +158,9 @@ ga102_gsp_r535 = { .booter.ctor = ga102_gsp_booter_ctor, + .fwsec_sb.ctor = tu102_gsp_fwsec_sb_ctor, + .fwsec_sb.dtor = tu102_gsp_fwsec_sb_dtor, + .dtor = r535_gsp_dtor, .oneinit = tu102_gsp_oneinit, .init = tu102_gsp_init, diff --git a/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/priv.h b/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/priv.h index 86bdd203bc107c..9dd66a2e38017b 100644 --- a/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/priv.h +++ b/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/priv.h @@ -7,9 +7,8 @@ enum nvkm_acr_lsf_id; int nvkm_gsp_fwsec_frts(struct nvkm_gsp *); -int nvkm_gsp_fwsec_sb_ctor(struct nvkm_gsp *); int nvkm_gsp_fwsec_sb(struct nvkm_gsp *); -void nvkm_gsp_fwsec_sb_dtor(struct nvkm_gsp *); +int nvkm_gsp_fwsec_sb_init(struct nvkm_gsp *gsp); struct nvkm_gsp_fwif { int version; @@ -52,6 +51,11 @@ struct nvkm_gsp_func { struct nvkm_falcon *, struct nvkm_falcon_fw *); } booter; + struct { + int (*ctor)(struct nvkm_gsp *); + void (*dtor)(struct nvkm_gsp *); + } fwsec_sb; + void (*dtor)(struct nvkm_gsp *); int (*oneinit)(struct nvkm_gsp *); int (*init)(struct nvkm_gsp *); @@ -67,6 +71,8 @@ extern const struct nvkm_falcon_func tu102_gsp_flcn; extern const struct nvkm_falcon_fw_func tu102_gsp_fwsec; int tu102_gsp_booter_ctor(struct nvkm_gsp *, const char *, const struct firmware *, struct nvkm_falcon *, struct nvkm_falcon_fw *); +int tu102_gsp_fwsec_sb_ctor(struct nvkm_gsp *); +void tu102_gsp_fwsec_sb_dtor(struct nvkm_gsp *); int tu102_gsp_oneinit(struct nvkm_gsp *); int tu102_gsp_init(struct nvkm_gsp *); int tu102_gsp_fini(struct nvkm_gsp *, bool suspend); @@ -91,5 +97,18 @@ int r535_gsp_fini(struct nvkm_gsp *, bool suspend); int nvkm_gsp_new_(const struct nvkm_gsp_fwif *, struct nvkm_device *, enum nvkm_subdev_type, int, struct nvkm_gsp **); +static inline int nvkm_gsp_fwsec_sb_ctor(struct nvkm_gsp *gsp) +{ + if (gsp->func->fwsec_sb.ctor) + return gsp->func->fwsec_sb.ctor(gsp); + return 0; +} + +static inline void nvkm_gsp_fwsec_sb_dtor(struct nvkm_gsp *gsp) +{ + if (gsp->func->fwsec_sb.dtor) + gsp->func->fwsec_sb.dtor(gsp); +} + extern const struct nvkm_gsp_func gv100_gsp; #endif diff --git a/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/tu102.c b/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/tu102.c index 81e56da0474a1d..04b642a1f73051 100644 --- a/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/tu102.c +++ b/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/tu102.c @@ -30,6 +30,18 @@ #include #include +int +tu102_gsp_fwsec_sb_ctor(struct nvkm_gsp *gsp) +{ + return nvkm_gsp_fwsec_sb_init(gsp); +} + +void +tu102_gsp_fwsec_sb_dtor(struct nvkm_gsp *gsp) +{ + nvkm_falcon_fw_dtor(&gsp->fws.falcon.sb); +} + static int tu102_gsp_booter_unload(struct nvkm_gsp *gsp, u32 mbox0, u32 mbox1) { @@ -370,6 +382,9 @@ tu102_gsp = { .booter.ctor = tu102_gsp_booter_ctor, + .fwsec_sb.ctor = tu102_gsp_fwsec_sb_ctor, + .fwsec_sb.dtor = tu102_gsp_fwsec_sb_dtor, + .dtor = r535_gsp_dtor, .oneinit = tu102_gsp_oneinit, .init = tu102_gsp_init, diff --git a/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/tu116.c b/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/tu116.c index 97eb046c25d07d..58cf2584242182 100644 --- a/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/tu116.c +++ b/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/tu116.c @@ -30,6 +30,9 @@ tu116_gsp = { .booter.ctor = tu102_gsp_booter_ctor, + .fwsec_sb.ctor = tu102_gsp_fwsec_sb_ctor, + .fwsec_sb.dtor = tu102_gsp_fwsec_sb_dtor, + .dtor = r535_gsp_dtor, .oneinit = tu102_gsp_oneinit, .init = tu102_gsp_init, From c12df0f5ca410ce09198d695206b24c4e6bfd073 Mon Sep 17 00:00:00 2001 From: Tomi Valkeinen Date: Fri, 5 Dec 2025 11:51:48 +0200 Subject: [PATCH 2243/3902] Revert "drm/atomic-helper: Re-order bridge chain pre-enable and post-disable" commit c1ef9a6cabb34dbc09e31417b0c0a672fe0de13a upstream. This reverts commit c9b1150a68d9362a0827609fc0dc1664c0d8bfe1. Changing the enable/disable sequence has caused regressions on multiple platforms: R-Car, MCDE, Rockchip. A series (see link below) was sent to fix these, but it was decided that it's better to revert the original patch and change the enable/disable sequence only in the tidss driver. Reverting this commit breaks tidss's DSI and OLDI outputs, which will be fixed in the following commits. Signed-off-by: Tomi Valkeinen Link: https://lore.kernel.org/all/20251202-mcde-drm-regression-thirdfix-v6-0-f1bffd4ec0fa%40kernel.org/ Fixes: c9b1150a68d9 ("drm/atomic-helper: Re-order bridge chain pre-enable and post-disable") Cc: stable@vger.kernel.org # v6.17+ Reviewed-by: Aradhya Bhatia Reviewed-by: Maxime Ripard Reviewed-by: Linus Walleij Tested-by: Linus Walleij Signed-off-by: Linus Walleij Link: https://patch.msgid.link/20251205-drm-seq-fix-v1-1-fda68fa1b3de@ideasonboard.com Signed-off-by: Greg Kroah-Hartman --- drivers/gpu/drm/drm_atomic_helper.c | 8 +- include/drm/drm_bridge.h | 249 ++++++++-------------------- 2 files changed, 70 insertions(+), 187 deletions(-) diff --git a/drivers/gpu/drm/drm_atomic_helper.c b/drivers/gpu/drm/drm_atomic_helper.c index d5ebe6ea0acbc5..1f5808b8fd5d98 100644 --- a/drivers/gpu/drm/drm_atomic_helper.c +++ b/drivers/gpu/drm/drm_atomic_helper.c @@ -1341,9 +1341,9 @@ disable_outputs(struct drm_device *dev, struct drm_atomic_state *state) { encoder_bridge_disable(dev, state); - crtc_disable(dev, state); - encoder_bridge_post_disable(dev, state); + + crtc_disable(dev, state); } /** @@ -1682,10 +1682,10 @@ encoder_bridge_enable(struct drm_device *dev, struct drm_atomic_state *state) void drm_atomic_helper_commit_modeset_enables(struct drm_device *dev, struct drm_atomic_state *state) { - encoder_bridge_pre_enable(dev, state); - crtc_enable(dev, state); + encoder_bridge_pre_enable(dev, state); + encoder_bridge_enable(dev, state); drm_atomic_helper_commit_writebacks(dev, state); diff --git a/include/drm/drm_bridge.h b/include/drm/drm_bridge.h index 76e05930f50e00..57d9a3c0621246 100644 --- a/include/drm/drm_bridge.h +++ b/include/drm/drm_bridge.h @@ -176,33 +176,17 @@ struct drm_bridge_funcs { /** * @disable: * - * The @disable callback should disable the bridge. + * This callback should disable the bridge. It is called right before + * the preceding element in the display pipe is disabled. If the + * preceding element is a bridge this means it's called before that + * bridge's @disable vfunc. If the preceding element is a &drm_encoder + * it's called right before the &drm_encoder_helper_funcs.disable, + * &drm_encoder_helper_funcs.prepare or &drm_encoder_helper_funcs.dpms + * hook. * * The bridge can assume that the display pipe (i.e. clocks and timing * signals) feeding it is still running when this callback is called. * - * - * If the preceding element is a &drm_bridge, then this is called before - * that bridge is disabled via one of: - * - * - &drm_bridge_funcs.disable - * - &drm_bridge_funcs.atomic_disable - * - * If the preceding element of the bridge is a display controller, then - * this callback is called before the encoder is disabled via one of: - * - * - &drm_encoder_helper_funcs.atomic_disable - * - &drm_encoder_helper_funcs.prepare - * - &drm_encoder_helper_funcs.disable - * - &drm_encoder_helper_funcs.dpms - * - * and the CRTC is disabled via one of: - * - * - &drm_crtc_helper_funcs.prepare - * - &drm_crtc_helper_funcs.atomic_disable - * - &drm_crtc_helper_funcs.disable - * - &drm_crtc_helper_funcs.dpms. - * * The @disable callback is optional. * * NOTE: @@ -215,34 +199,17 @@ struct drm_bridge_funcs { /** * @post_disable: * - * The bridge must assume that the display pipe (i.e. clocks and timing - * signals) feeding this bridge is no longer running when the - * @post_disable is called. + * This callback should disable the bridge. It is called right after the + * preceding element in the display pipe is disabled. If the preceding + * element is a bridge this means it's called after that bridge's + * @post_disable function. If the preceding element is a &drm_encoder + * it's called right after the encoder's + * &drm_encoder_helper_funcs.disable, &drm_encoder_helper_funcs.prepare + * or &drm_encoder_helper_funcs.dpms hook. * - * This callback should perform all the actions required by the hardware - * after it has stopped receiving signals from the preceding element. - * - * If the preceding element is a &drm_bridge, then this is called after - * that bridge is post-disabled (unless marked otherwise by the - * @pre_enable_prev_first flag) via one of: - * - * - &drm_bridge_funcs.post_disable - * - &drm_bridge_funcs.atomic_post_disable - * - * If the preceding element of the bridge is a display controller, then - * this callback is called after the encoder is disabled via one of: - * - * - &drm_encoder_helper_funcs.atomic_disable - * - &drm_encoder_helper_funcs.prepare - * - &drm_encoder_helper_funcs.disable - * - &drm_encoder_helper_funcs.dpms - * - * and the CRTC is disabled via one of: - * - * - &drm_crtc_helper_funcs.prepare - * - &drm_crtc_helper_funcs.atomic_disable - * - &drm_crtc_helper_funcs.disable - * - &drm_crtc_helper_funcs.dpms + * The bridge must assume that the display pipe (i.e. clocks and timing + * signals) feeding it is no longer running when this callback is + * called. * * The @post_disable callback is optional. * @@ -285,30 +252,18 @@ struct drm_bridge_funcs { /** * @pre_enable: * - * The display pipe (i.e. clocks and timing signals) feeding this bridge - * will not yet be running when the @pre_enable is called. - * - * This callback should perform all the necessary actions to prepare the - * bridge to accept signals from the preceding element. - * - * If the preceding element is a &drm_bridge, then this is called before - * that bridge is pre-enabled (unless marked otherwise by - * @pre_enable_prev_first flag) via one of: - * - * - &drm_bridge_funcs.pre_enable - * - &drm_bridge_funcs.atomic_pre_enable - * - * If the preceding element of the bridge is a display controller, then - * this callback is called before the CRTC is enabled via one of: - * - * - &drm_crtc_helper_funcs.atomic_enable - * - &drm_crtc_helper_funcs.commit - * - * and the encoder is enabled via one of: + * This callback should enable the bridge. It is called right before + * the preceding element in the display pipe is enabled. If the + * preceding element is a bridge this means it's called before that + * bridge's @pre_enable function. If the preceding element is a + * &drm_encoder it's called right before the encoder's + * &drm_encoder_helper_funcs.enable, &drm_encoder_helper_funcs.commit or + * &drm_encoder_helper_funcs.dpms hook. * - * - &drm_encoder_helper_funcs.atomic_enable - * - &drm_encoder_helper_funcs.enable - * - &drm_encoder_helper_funcs.commit + * The display pipe (i.e. clocks and timing signals) feeding this bridge + * will not yet be running when this callback is called. The bridge must + * not enable the display link feeding the next bridge in the chain (if + * there is one) when this callback is called. * * The @pre_enable callback is optional. * @@ -322,31 +277,19 @@ struct drm_bridge_funcs { /** * @enable: * - * The @enable callback should enable the bridge. + * This callback should enable the bridge. It is called right after + * the preceding element in the display pipe is enabled. If the + * preceding element is a bridge this means it's called after that + * bridge's @enable function. If the preceding element is a + * &drm_encoder it's called right after the encoder's + * &drm_encoder_helper_funcs.enable, &drm_encoder_helper_funcs.commit or + * &drm_encoder_helper_funcs.dpms hook. * * The bridge can assume that the display pipe (i.e. clocks and timing * signals) feeding it is running when this callback is called. This * callback must enable the display link feeding the next bridge in the * chain if there is one. * - * If the preceding element is a &drm_bridge, then this is called after - * that bridge is enabled via one of: - * - * - &drm_bridge_funcs.enable - * - &drm_bridge_funcs.atomic_enable - * - * If the preceding element of the bridge is a display controller, then - * this callback is called after the CRTC is enabled via one of: - * - * - &drm_crtc_helper_funcs.atomic_enable - * - &drm_crtc_helper_funcs.commit - * - * and the encoder is enabled via one of: - * - * - &drm_encoder_helper_funcs.atomic_enable - * - &drm_encoder_helper_funcs.enable - * - drm_encoder_helper_funcs.commit - * * The @enable callback is optional. * * NOTE: @@ -359,30 +302,17 @@ struct drm_bridge_funcs { /** * @atomic_pre_enable: * - * The display pipe (i.e. clocks and timing signals) feeding this bridge - * will not yet be running when the @atomic_pre_enable is called. - * - * This callback should perform all the necessary actions to prepare the - * bridge to accept signals from the preceding element. - * - * If the preceding element is a &drm_bridge, then this is called before - * that bridge is pre-enabled (unless marked otherwise by - * @pre_enable_prev_first flag) via one of: - * - * - &drm_bridge_funcs.pre_enable - * - &drm_bridge_funcs.atomic_pre_enable + * This callback should enable the bridge. It is called right before + * the preceding element in the display pipe is enabled. If the + * preceding element is a bridge this means it's called before that + * bridge's @atomic_pre_enable or @pre_enable function. If the preceding + * element is a &drm_encoder it's called right before the encoder's + * &drm_encoder_helper_funcs.atomic_enable hook. * - * If the preceding element of the bridge is a display controller, then - * this callback is called before the CRTC is enabled via one of: - * - * - &drm_crtc_helper_funcs.atomic_enable - * - &drm_crtc_helper_funcs.commit - * - * and the encoder is enabled via one of: - * - * - &drm_encoder_helper_funcs.atomic_enable - * - &drm_encoder_helper_funcs.enable - * - &drm_encoder_helper_funcs.commit + * The display pipe (i.e. clocks and timing signals) feeding this bridge + * will not yet be running when this callback is called. The bridge must + * not enable the display link feeding the next bridge in the chain (if + * there is one) when this callback is called. * * The @atomic_pre_enable callback is optional. */ @@ -392,31 +322,18 @@ struct drm_bridge_funcs { /** * @atomic_enable: * - * The @atomic_enable callback should enable the bridge. + * This callback should enable the bridge. It is called right after + * the preceding element in the display pipe is enabled. If the + * preceding element is a bridge this means it's called after that + * bridge's @atomic_enable or @enable function. If the preceding element + * is a &drm_encoder it's called right after the encoder's + * &drm_encoder_helper_funcs.atomic_enable hook. * * The bridge can assume that the display pipe (i.e. clocks and timing * signals) feeding it is running when this callback is called. This * callback must enable the display link feeding the next bridge in the * chain if there is one. * - * If the preceding element is a &drm_bridge, then this is called after - * that bridge is enabled via one of: - * - * - &drm_bridge_funcs.enable - * - &drm_bridge_funcs.atomic_enable - * - * If the preceding element of the bridge is a display controller, then - * this callback is called after the CRTC is enabled via one of: - * - * - &drm_crtc_helper_funcs.atomic_enable - * - &drm_crtc_helper_funcs.commit - * - * and the encoder is enabled via one of: - * - * - &drm_encoder_helper_funcs.atomic_enable - * - &drm_encoder_helper_funcs.enable - * - drm_encoder_helper_funcs.commit - * * The @atomic_enable callback is optional. */ void (*atomic_enable)(struct drm_bridge *bridge, @@ -424,32 +341,16 @@ struct drm_bridge_funcs { /** * @atomic_disable: * - * The @atomic_disable callback should disable the bridge. + * This callback should disable the bridge. It is called right before + * the preceding element in the display pipe is disabled. If the + * preceding element is a bridge this means it's called before that + * bridge's @atomic_disable or @disable vfunc. If the preceding element + * is a &drm_encoder it's called right before the + * &drm_encoder_helper_funcs.atomic_disable hook. * * The bridge can assume that the display pipe (i.e. clocks and timing * signals) feeding it is still running when this callback is called. * - * If the preceding element is a &drm_bridge, then this is called before - * that bridge is disabled via one of: - * - * - &drm_bridge_funcs.disable - * - &drm_bridge_funcs.atomic_disable - * - * If the preceding element of the bridge is a display controller, then - * this callback is called before the encoder is disabled via one of: - * - * - &drm_encoder_helper_funcs.atomic_disable - * - &drm_encoder_helper_funcs.prepare - * - &drm_encoder_helper_funcs.disable - * - &drm_encoder_helper_funcs.dpms - * - * and the CRTC is disabled via one of: - * - * - &drm_crtc_helper_funcs.prepare - * - &drm_crtc_helper_funcs.atomic_disable - * - &drm_crtc_helper_funcs.disable - * - &drm_crtc_helper_funcs.dpms. - * * The @atomic_disable callback is optional. */ void (*atomic_disable)(struct drm_bridge *bridge, @@ -458,34 +359,16 @@ struct drm_bridge_funcs { /** * @atomic_post_disable: * - * The bridge must assume that the display pipe (i.e. clocks and timing - * signals) feeding this bridge is no longer running when the - * @atomic_post_disable is called. - * - * This callback should perform all the actions required by the hardware - * after it has stopped receiving signals from the preceding element. + * This callback should disable the bridge. It is called right after the + * preceding element in the display pipe is disabled. If the preceding + * element is a bridge this means it's called after that bridge's + * @atomic_post_disable or @post_disable function. If the preceding + * element is a &drm_encoder it's called right after the encoder's + * &drm_encoder_helper_funcs.atomic_disable hook. * - * If the preceding element is a &drm_bridge, then this is called after - * that bridge is post-disabled (unless marked otherwise by the - * @pre_enable_prev_first flag) via one of: - * - * - &drm_bridge_funcs.post_disable - * - &drm_bridge_funcs.atomic_post_disable - * - * If the preceding element of the bridge is a display controller, then - * this callback is called after the encoder is disabled via one of: - * - * - &drm_encoder_helper_funcs.atomic_disable - * - &drm_encoder_helper_funcs.prepare - * - &drm_encoder_helper_funcs.disable - * - &drm_encoder_helper_funcs.dpms - * - * and the CRTC is disabled via one of: - * - * - &drm_crtc_helper_funcs.prepare - * - &drm_crtc_helper_funcs.atomic_disable - * - &drm_crtc_helper_funcs.disable - * - &drm_crtc_helper_funcs.dpms + * The bridge must assume that the display pipe (i.e. clocks and timing + * signals) feeding it is no longer running when this callback is + * called. * * The @atomic_post_disable callback is optional. */ From cb73d37ac18bc1716690ff5255a0ef1952827e9e Mon Sep 17 00:00:00 2001 From: Haoxiang Li Date: Sat, 20 Dec 2025 00:28:45 +0800 Subject: [PATCH 2244/3902] ALSA: ac97: fix a double free in snd_ac97_controller_register() commit 830988b6cf197e6dcffdfe2008c5738e6c6c3c0f upstream. If ac97_add_adapter() fails, put_device() is the correct way to drop the device reference. kfree() is not required. Add kfree() if idr_alloc() fails and in ac97_adapter_release() to do the cleanup. Found by code review. Fixes: 74426fbff66e ("ALSA: ac97: add an ac97 bus") Cc: stable@vger.kernel.org Signed-off-by: Haoxiang Li Link: https://patch.msgid.link/20251219162845.657525-1-lihaoxiang@isrc.iscas.ac.cn Signed-off-by: Takashi Iwai Signed-off-by: Greg Kroah-Hartman --- sound/ac97/bus.c | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/sound/ac97/bus.c b/sound/ac97/bus.c index f4254703d29f75..bb9b795e022626 100644 --- a/sound/ac97/bus.c +++ b/sound/ac97/bus.c @@ -298,6 +298,7 @@ static void ac97_adapter_release(struct device *dev) idr_remove(&ac97_adapter_idr, ac97_ctrl->nr); dev_dbg(&ac97_ctrl->adap, "adapter unregistered by %s\n", dev_name(ac97_ctrl->parent)); + kfree(ac97_ctrl); } static const struct device_type ac97_adapter_type = { @@ -319,7 +320,9 @@ static int ac97_add_adapter(struct ac97_controller *ac97_ctrl) ret = device_register(&ac97_ctrl->adap); if (ret) put_device(&ac97_ctrl->adap); - } + } else + kfree(ac97_ctrl); + if (!ret) { list_add(&ac97_ctrl->controllers, &ac97_controllers); dev_dbg(&ac97_ctrl->adap, "adapter registered by %s\n", @@ -361,14 +364,11 @@ struct ac97_controller *snd_ac97_controller_register( ret = ac97_add_adapter(ac97_ctrl); if (ret) - goto err; + return ERR_PTR(ret); ac97_bus_reset(ac97_ctrl); ac97_bus_scan(ac97_ctrl); return ac97_ctrl; -err: - kfree(ac97_ctrl); - return ERR_PTR(ret); } EXPORT_SYMBOL_GPL(snd_ac97_controller_register); From 00d880c469b75ab4ad9e436e8079229e1e7ae4e4 Mon Sep 17 00:00:00 2001 From: August Wikerfors Date: Mon, 22 Dec 2025 20:47:04 +0100 Subject: [PATCH 2245/3902] ALSA: hda/tas2781: properly initialize speaker_id for TAS2563 commit e340663bbf2a75dae5d4fddf90b49281f5c9df3f upstream. After speaker id retrieval was refactored to happen in tas2781_read_acpi, devices that do not use a speaker id need a negative speaker_id value instead of NULL, but no initialization was added to the TAS2563 code path. This causes the driver to attempt to load a non-existent firmware file name with a speaker id of 0 ("TAS2XXX38700.bin") instead of the correct file name without a speaker id ("TAS2XXX3870.bin"), resulting in low volume and these dmesg errors: tas2781-hda i2c-INT8866:00: Direct firmware load for TAS2XXX38700.bin failed with error -2 tas2781-hda i2c-INT8866:00: tasdevice_dsp_parser: load TAS2XXX38700.bin error tas2781-hda i2c-INT8866:00: dspfw load TAS2XXX38700.bin error [...] tas2781-hda i2c-INT8866:00: tasdevice_prmg_load: Firmware is NULL Fix this by setting speaker_id to -1 as is done for other models. Fixes: 945865a0ddf3 ("ALSA: hda/tas2781: fix speaker id retrieval for multiple probes") Cc: stable@vger.kernel.org Signed-off-by: August Wikerfors Link: https://patch.msgid.link/20251222194704.87232-1-git@augustwikerfors.se Signed-off-by: Takashi Iwai Signed-off-by: Greg Kroah-Hartman --- sound/hda/codecs/side-codecs/tas2781_hda_i2c.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/sound/hda/codecs/side-codecs/tas2781_hda_i2c.c b/sound/hda/codecs/side-codecs/tas2781_hda_i2c.c index c8619995b1d78c..f7a7f216d5865f 100644 --- a/sound/hda/codecs/side-codecs/tas2781_hda_i2c.c +++ b/sound/hda/codecs/side-codecs/tas2781_hda_i2c.c @@ -111,8 +111,10 @@ static int tas2781_read_acpi(struct tasdevice_priv *p, const char *hid) sub = acpi_get_subsystem_id(ACPI_HANDLE(physdev)); if (IS_ERR(sub)) { /* No subsys id in older tas2563 projects. */ - if (!strncmp(hid, "INT8866", sizeof("INT8866"))) + if (!strncmp(hid, "INT8866", sizeof("INT8866"))) { + p->speaker_id = -1; goto end_2563; + } dev_err(p->dev, "Failed to get SUBSYS ID.\n"); ret = PTR_ERR(sub); goto err; From 1d8b1ac985c912cbdc47b9a992af377113fa8f2e Mon Sep 17 00:00:00 2001 From: Carlos Song Date: Tue, 18 Nov 2025 14:28:54 +0800 Subject: [PATCH 2246/3902] arm64: dts: imx95: correct I3C2 pclk to IMX95_CLK_BUSWAKEUP commit cd0caaf2005547eaef8170356939aaabfcad4837 upstream. I3C2 is in WAKEUP domain. Its pclk should be IMX95_CLK_BUSWAKEUP. Fixes: 969497ebefcf ("arm64: dts: imx95: Add i3c1 and i3c2") Signed-off-by: Carlos Song Cc: stable@vger.kernel.org Reviewed-by: Frank Li Signed-off-by: Shawn Guo Signed-off-by: Greg Kroah-Hartman --- arch/arm64/boot/dts/freescale/imx95.dtsi | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/arch/arm64/boot/dts/freescale/imx95.dtsi b/arch/arm64/boot/dts/freescale/imx95.dtsi index 6da961eb3fe5cc..583c5f7a84b546 100644 --- a/arch/arm64/boot/dts/freescale/imx95.dtsi +++ b/arch/arm64/boot/dts/freescale/imx95.dtsi @@ -806,7 +806,7 @@ interrupts = ; #address-cells = <3>; #size-cells = <0>; - clocks = <&scmi_clk IMX95_CLK_BUSAON>, + clocks = <&scmi_clk IMX95_CLK_BUSWAKEUP>, <&scmi_clk IMX95_CLK_I3C2SLOW>; clock-names = "pclk", "fast_clk"; status = "disabled"; From b023b3f236e84ce57da67ca96d6da44b6db1f7ed Mon Sep 17 00:00:00 2001 From: Nathan Chancellor Date: Sat, 13 Dec 2025 15:16:43 +0900 Subject: [PATCH 2247/3902] drm/amd/display: Apply e4479aecf658 to dml commit 70740454377f1ba3ff32f5df4acd965db99d055b upstream. After an innocuous optimization change in clang-22, allmodconfig (which enables CONFIG_KASAN and CONFIG_WERROR) breaks with: drivers/gpu/drm/amd/amdgpu/../display/dc/dml/dcn32/display_mode_vba_32.c:1724:6: error: stack frame size (3144) exceeds limit (3072) in 'dml32_ModeSupportAndSystemConfigurationFull' [-Werror,-Wframe-larger-than] 1724 | void dml32_ModeSupportAndSystemConfigurationFull(struct display_mode_lib *mode_lib) | ^ With clang-21, this function was already pretty close to the existing limit of 3072 bytes. drivers/gpu/drm/amd/amdgpu/../display/dc/dml/dcn32/display_mode_vba_32.c:1724:6: error: stack frame size (2904) exceeds limit (2048) in 'dml32_ModeSupportAndSystemConfigurationFull' [-Werror,-Wframe-larger-than] 1724 | void dml32_ModeSupportAndSystemConfigurationFull(struct display_mode_lib *mode_lib) | ^ A similar situation occurred in dml2, which was resolved by commit e4479aecf658 ("drm/amd/display: Increase sanitizer frame larger than limit when compile testing with clang") by increasing the limit for clang when compile testing with certain sanitizer enabled, so that allmodconfig (an easy testing target) continues to work. Apply that same change to the dml folder to clear up the warning for allmodconfig, unbreaking the build. Closes: https://github.com/ClangBuiltLinux/linux/issues/2135 Signed-off-by: Nathan Chancellor Signed-off-by: Alex Deucher (cherry picked from commit 25314b453cf812150e9951a32007a32bba85707e) Cc: stable@vger.kernel.org Signed-off-by: Greg Kroah-Hartman --- drivers/gpu/drm/amd/display/dc/dml/Makefile | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/drivers/gpu/drm/amd/display/dc/dml/Makefile b/drivers/gpu/drm/amd/display/dc/dml/Makefile index b357683b4255af..268b5fbdb48bd5 100644 --- a/drivers/gpu/drm/amd/display/dc/dml/Makefile +++ b/drivers/gpu/drm/amd/display/dc/dml/Makefile @@ -30,7 +30,11 @@ dml_rcflags := $(CC_FLAGS_NO_FPU) ifneq ($(CONFIG_FRAME_WARN),0) ifeq ($(filter y,$(CONFIG_KASAN)$(CONFIG_KCSAN)),y) - frame_warn_limit := 3072 + ifeq ($(CONFIG_CC_IS_CLANG)$(CONFIG_COMPILE_TEST),yy) + frame_warn_limit := 4096 + else + frame_warn_limit := 3072 + endif else frame_warn_limit := 2048 endif From 1f50931f79dce903c7040bcb15be6bf9a49a7df2 Mon Sep 17 00:00:00 2001 From: Alan Liu Date: Mon, 22 Dec 2025 12:26:35 +0800 Subject: [PATCH 2248/3902] drm/amdgpu: Fix query for VPE block_type and ip_count commit 72d7f4573660287f1b66c30319efecd6fcde92ee upstream. [Why] Query for VPE block_type and ip_count is missing. [How] Add VPE case in ip_block_type and hw_ip_count query. Reviewed-by: Lang Yu Signed-off-by: Alan Liu Signed-off-by: Alex Deucher (cherry picked from commit a6ea0a430aca5932b9c75d8e38deeb45665dd2ae) Cc: stable@vger.kernel.org Signed-off-by: Greg Kroah-Hartman --- drivers/gpu/drm/amd/amdgpu/amdgpu_kms.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_kms.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_kms.c index b3e6b3fcdf2cb1..a8b507fd856739 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_kms.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_kms.c @@ -201,6 +201,9 @@ static enum amd_ip_block_type type = (amdgpu_device_ip_get_ip_block(adev, AMD_IP_BLOCK_TYPE_JPEG)) ? AMD_IP_BLOCK_TYPE_JPEG : AMD_IP_BLOCK_TYPE_VCN; break; + case AMDGPU_HW_IP_VPE: + type = AMD_IP_BLOCK_TYPE_VPE; + break; default: type = AMD_IP_BLOCK_TYPE_NUM; break; @@ -721,6 +724,9 @@ int amdgpu_info_ioctl(struct drm_device *dev, void *data, struct drm_file *filp) case AMD_IP_BLOCK_TYPE_UVD: count = adev->uvd.num_uvd_inst; break; + case AMD_IP_BLOCK_TYPE_VPE: + count = adev->vpe.num_instances; + break; /* For all other IP block types not listed in the switch statement * the ip status is valid here and the instance count is one. */ From 8a8bf7ed823045b6b97f75b5e62f615fbb8c6ba1 Mon Sep 17 00:00:00 2001 From: Linus Walleij Date: Fri, 5 Dec 2025 11:51:50 +0200 Subject: [PATCH 2249/3902] drm/atomic-helper: Export and namespace some functions commit d1c7dc57ff2400b141e6582a8d2dc5170108cf81 upstream. Export and namespace those not prefixed with drm_* so it becomes possible to write custom commit tail functions in individual drivers using the helper infrastructure. Tested-by: Marek Vasut Reviewed-by: Maxime Ripard Signed-off-by: Tomi Valkeinen Cc: stable@vger.kernel.org # v6.17+ Fixes: c9b1150a68d9 ("drm/atomic-helper: Re-order bridge chain pre-enable and post-disable") Reviewed-by: Aradhya Bhatia Reviewed-by: Linus Walleij Tested-by: Linus Walleij Signed-off-by: Linus Walleij Link: https://patch.msgid.link/20251205-drm-seq-fix-v1-3-fda68fa1b3de@ideasonboard.com Signed-off-by: Greg Kroah-Hartman --- drivers/gpu/drm/drm_atomic_helper.c | 122 ++++++++++++++++++++++------ include/drm/drm_atomic_helper.h | 22 +++++ 2 files changed, 121 insertions(+), 23 deletions(-) diff --git a/drivers/gpu/drm/drm_atomic_helper.c b/drivers/gpu/drm/drm_atomic_helper.c index 1f5808b8fd5d98..bbec1c184f6521 100644 --- a/drivers/gpu/drm/drm_atomic_helper.c +++ b/drivers/gpu/drm/drm_atomic_helper.c @@ -1162,8 +1162,18 @@ crtc_needs_disable(struct drm_crtc_state *old_state, new_state->self_refresh_active; } -static void -encoder_bridge_disable(struct drm_device *dev, struct drm_atomic_state *state) +/** + * drm_atomic_helper_commit_encoder_bridge_disable - disable bridges and encoder + * @dev: DRM device + * @state: the driver state object + * + * Loops over all connectors in the current state and if the CRTC needs + * it, disables the bridge chain all the way, then disables the encoder + * afterwards. + */ +void +drm_atomic_helper_commit_encoder_bridge_disable(struct drm_device *dev, + struct drm_atomic_state *state) { struct drm_connector *connector; struct drm_connector_state *old_conn_state, *new_conn_state; @@ -1229,9 +1239,18 @@ encoder_bridge_disable(struct drm_device *dev, struct drm_atomic_state *state) } } } +EXPORT_SYMBOL(drm_atomic_helper_commit_encoder_bridge_disable); -static void -crtc_disable(struct drm_device *dev, struct drm_atomic_state *state) +/** + * drm_atomic_helper_commit_crtc_disable - disable CRTSs + * @dev: DRM device + * @state: the driver state object + * + * Loops over all CRTCs in the current state and if the CRTC needs + * it, disables it. + */ +void +drm_atomic_helper_commit_crtc_disable(struct drm_device *dev, struct drm_atomic_state *state) { struct drm_crtc *crtc; struct drm_crtc_state *old_crtc_state, *new_crtc_state; @@ -1282,9 +1301,18 @@ crtc_disable(struct drm_device *dev, struct drm_atomic_state *state) drm_crtc_vblank_put(crtc); } } +EXPORT_SYMBOL(drm_atomic_helper_commit_crtc_disable); -static void -encoder_bridge_post_disable(struct drm_device *dev, struct drm_atomic_state *state) +/** + * drm_atomic_helper_commit_encoder_bridge_post_disable - post-disable encoder bridges + * @dev: DRM device + * @state: the driver state object + * + * Loops over all connectors in the current state and if the CRTC needs + * it, post-disables all encoder bridges. + */ +void +drm_atomic_helper_commit_encoder_bridge_post_disable(struct drm_device *dev, struct drm_atomic_state *state) { struct drm_connector *connector; struct drm_connector_state *old_conn_state, *new_conn_state; @@ -1335,15 +1363,16 @@ encoder_bridge_post_disable(struct drm_device *dev, struct drm_atomic_state *sta drm_bridge_put(bridge); } } +EXPORT_SYMBOL(drm_atomic_helper_commit_encoder_bridge_post_disable); static void disable_outputs(struct drm_device *dev, struct drm_atomic_state *state) { - encoder_bridge_disable(dev, state); + drm_atomic_helper_commit_encoder_bridge_disable(dev, state); - encoder_bridge_post_disable(dev, state); + drm_atomic_helper_commit_encoder_bridge_post_disable(dev, state); - crtc_disable(dev, state); + drm_atomic_helper_commit_crtc_disable(dev, state); } /** @@ -1446,8 +1475,17 @@ void drm_atomic_helper_calc_timestamping_constants(struct drm_atomic_state *stat } EXPORT_SYMBOL(drm_atomic_helper_calc_timestamping_constants); -static void -crtc_set_mode(struct drm_device *dev, struct drm_atomic_state *state) +/** + * drm_atomic_helper_commit_crtc_set_mode - set the new mode + * @dev: DRM device + * @state: the driver state object + * + * Loops over all connectors in the current state and if the mode has + * changed, change the mode of the CRTC, then call down the bridge + * chain and change the mode in all bridges as well. + */ +void +drm_atomic_helper_commit_crtc_set_mode(struct drm_device *dev, struct drm_atomic_state *state) { struct drm_crtc *crtc; struct drm_crtc_state *new_crtc_state; @@ -1508,6 +1546,7 @@ crtc_set_mode(struct drm_device *dev, struct drm_atomic_state *state) drm_bridge_put(bridge); } } +EXPORT_SYMBOL(drm_atomic_helper_commit_crtc_set_mode); /** * drm_atomic_helper_commit_modeset_disables - modeset commit to disable outputs @@ -1531,12 +1570,21 @@ void drm_atomic_helper_commit_modeset_disables(struct drm_device *dev, drm_atomic_helper_update_legacy_modeset_state(dev, state); drm_atomic_helper_calc_timestamping_constants(state); - crtc_set_mode(dev, state); + drm_atomic_helper_commit_crtc_set_mode(dev, state); } EXPORT_SYMBOL(drm_atomic_helper_commit_modeset_disables); -static void drm_atomic_helper_commit_writebacks(struct drm_device *dev, - struct drm_atomic_state *state) +/** + * drm_atomic_helper_commit_writebacks - issue writebacks + * @dev: DRM device + * @state: atomic state object being committed + * + * This loops over the connectors, checks if the new state requires + * a writeback job to be issued and in that case issues an atomic + * commit on each connector. + */ +void drm_atomic_helper_commit_writebacks(struct drm_device *dev, + struct drm_atomic_state *state) { struct drm_connector *connector; struct drm_connector_state *new_conn_state; @@ -1555,9 +1603,18 @@ static void drm_atomic_helper_commit_writebacks(struct drm_device *dev, } } } +EXPORT_SYMBOL(drm_atomic_helper_commit_writebacks); -static void -encoder_bridge_pre_enable(struct drm_device *dev, struct drm_atomic_state *state) +/** + * drm_atomic_helper_commit_encoder_bridge_pre_enable - pre-enable bridges + * @dev: DRM device + * @state: atomic state object being committed + * + * This loops over the connectors and if the CRTC needs it, pre-enables + * the entire bridge chain. + */ +void +drm_atomic_helper_commit_encoder_bridge_pre_enable(struct drm_device *dev, struct drm_atomic_state *state) { struct drm_connector *connector; struct drm_connector_state *new_conn_state; @@ -1588,9 +1645,18 @@ encoder_bridge_pre_enable(struct drm_device *dev, struct drm_atomic_state *state drm_bridge_put(bridge); } } +EXPORT_SYMBOL(drm_atomic_helper_commit_encoder_bridge_pre_enable); -static void -crtc_enable(struct drm_device *dev, struct drm_atomic_state *state) +/** + * drm_atomic_helper_commit_crtc_enable - enables the CRTCs + * @dev: DRM device + * @state: atomic state object being committed + * + * This loops over CRTCs in the new state, and of the CRTC needs + * it, enables it. + */ +void +drm_atomic_helper_commit_crtc_enable(struct drm_device *dev, struct drm_atomic_state *state) { struct drm_crtc *crtc; struct drm_crtc_state *old_crtc_state; @@ -1619,9 +1685,18 @@ crtc_enable(struct drm_device *dev, struct drm_atomic_state *state) } } } +EXPORT_SYMBOL(drm_atomic_helper_commit_crtc_enable); -static void -encoder_bridge_enable(struct drm_device *dev, struct drm_atomic_state *state) +/** + * drm_atomic_helper_commit_encoder_bridge_enable - enables the bridges + * @dev: DRM device + * @state: atomic state object being committed + * + * This loops over all connectors in the new state, and of the CRTC needs + * it, enables the entire bridge chain. + */ +void +drm_atomic_helper_commit_encoder_bridge_enable(struct drm_device *dev, struct drm_atomic_state *state) { struct drm_connector *connector; struct drm_connector_state *new_conn_state; @@ -1664,6 +1739,7 @@ encoder_bridge_enable(struct drm_device *dev, struct drm_atomic_state *state) drm_bridge_put(bridge); } } +EXPORT_SYMBOL(drm_atomic_helper_commit_encoder_bridge_enable); /** * drm_atomic_helper_commit_modeset_enables - modeset commit to enable outputs @@ -1682,11 +1758,11 @@ encoder_bridge_enable(struct drm_device *dev, struct drm_atomic_state *state) void drm_atomic_helper_commit_modeset_enables(struct drm_device *dev, struct drm_atomic_state *state) { - crtc_enable(dev, state); + drm_atomic_helper_commit_crtc_enable(dev, state); - encoder_bridge_pre_enable(dev, state); + drm_atomic_helper_commit_encoder_bridge_pre_enable(dev, state); - encoder_bridge_enable(dev, state); + drm_atomic_helper_commit_encoder_bridge_enable(dev, state); drm_atomic_helper_commit_writebacks(dev, state); } diff --git a/include/drm/drm_atomic_helper.h b/include/drm/drm_atomic_helper.h index 53382fe93537bb..e154ee4f0696c0 100644 --- a/include/drm/drm_atomic_helper.h +++ b/include/drm/drm_atomic_helper.h @@ -60,6 +60,12 @@ int drm_atomic_helper_check_plane_state(struct drm_plane_state *plane_state, int drm_atomic_helper_check_planes(struct drm_device *dev, struct drm_atomic_state *state); int drm_atomic_helper_check_crtc_primary_plane(struct drm_crtc_state *crtc_state); +void drm_atomic_helper_commit_encoder_bridge_disable(struct drm_device *dev, + struct drm_atomic_state *state); +void drm_atomic_helper_commit_crtc_disable(struct drm_device *dev, + struct drm_atomic_state *state); +void drm_atomic_helper_commit_encoder_bridge_post_disable(struct drm_device *dev, + struct drm_atomic_state *state); int drm_atomic_helper_check(struct drm_device *dev, struct drm_atomic_state *state); void drm_atomic_helper_commit_tail(struct drm_atomic_state *state); @@ -89,8 +95,24 @@ drm_atomic_helper_update_legacy_modeset_state(struct drm_device *dev, void drm_atomic_helper_calc_timestamping_constants(struct drm_atomic_state *state); +void drm_atomic_helper_commit_crtc_set_mode(struct drm_device *dev, + struct drm_atomic_state *state); + void drm_atomic_helper_commit_modeset_disables(struct drm_device *dev, struct drm_atomic_state *state); + +void drm_atomic_helper_commit_writebacks(struct drm_device *dev, + struct drm_atomic_state *state); + +void drm_atomic_helper_commit_encoder_bridge_pre_enable(struct drm_device *dev, + struct drm_atomic_state *state); + +void drm_atomic_helper_commit_crtc_enable(struct drm_device *dev, + struct drm_atomic_state *state); + +void drm_atomic_helper_commit_encoder_bridge_enable(struct drm_device *dev, + struct drm_atomic_state *state); + void drm_atomic_helper_commit_modeset_enables(struct drm_device *dev, struct drm_atomic_state *old_state); From 124b9d19e1d5013b821ce1a50b6f6e873b06d7ed Mon Sep 17 00:00:00 2001 From: Miaoqian Lin Date: Thu, 11 Dec 2025 16:33:44 +0400 Subject: [PATCH 2250/3902] drm/pl111: Fix error handling in pl111_amba_probe commit 0ddd3bb4b14c9102c0267b3fd916c81fe5ab89c1 upstream. Jump to the existing dev_put label when devm_request_irq() fails so drm_dev_put() and of_reserved_mem_device_release() run instead of returning early and leaking resources. Found via static analysis and code review. Fixes: bed41005e617 ("drm/pl111: Initial drm/kms driver for pl111") Cc: stable@vger.kernel.org Signed-off-by: Miaoqian Lin Reviewed-by: Javier Martinez Canillas Signed-off-by: Linus Walleij Link: https://patch.msgid.link/20251211123345.2392065-1-linmq006@gmail.com Signed-off-by: Greg Kroah-Hartman --- drivers/gpu/drm/pl111/pl111_drv.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/gpu/drm/pl111/pl111_drv.c b/drivers/gpu/drm/pl111/pl111_drv.c index 56ff6a3fb48348..d7dc83cf7b00ac 100644 --- a/drivers/gpu/drm/pl111/pl111_drv.c +++ b/drivers/gpu/drm/pl111/pl111_drv.c @@ -295,7 +295,7 @@ static int pl111_amba_probe(struct amba_device *amba_dev, variant->name, priv); if (ret != 0) { dev_err(dev, "%s failed irq %d\n", __func__, ret); - return ret; + goto dev_put; } ret = pl111_modeset_init(drm); From 227997d16a535822efb5ac8e7209cea3ae334b89 Mon Sep 17 00:00:00 2001 From: Tomi Valkeinen Date: Fri, 5 Dec 2025 11:51:51 +0200 Subject: [PATCH 2251/3902] drm/tidss: Fix enable/disable order commit 2fc04340cf30d7960eed2525d26ffb8905aca02b upstream. TI's OLDI and DSI encoders need to be set up before the crtc is enabled, but the DRM helpers will enable the crtc first. This causes various issues on TI platforms, like visual artifacts or crtc sync lost warnings. Thus drm_atomic_helper_commit_modeset_enables() and drm_atomic_helper_commit_modeset_disables() cannot be used, as they enable the crtc before bridges' pre-enable, and disable the crtc after bridges' post-disable. Open code the drm_atomic_helper_commit_modeset_enables() and drm_atomic_helper_commit_modeset_disables(), and first call the bridges' pre-enables, then crtc enable, then bridges' post-enable (and vice versa for disable). Signed-off-by: Tomi Valkeinen Cc: stable@vger.kernel.org # v6.17+ Fixes: c9b1150a68d9 ("drm/atomic-helper: Re-order bridge chain pre-enable and post-disable") Reviewed-by: Aradhya Bhatia Reviewed-by: Maxime Ripard Reviewed-by: Linus Walleij Tested-by: Linus Walleij Signed-off-by: Linus Walleij Link: https://patch.msgid.link/20251205-drm-seq-fix-v1-4-fda68fa1b3de@ideasonboard.com Signed-off-by: Greg Kroah-Hartman --- drivers/gpu/drm/tidss/tidss_kms.c | 30 +++++++++++++++++++++++++++--- 1 file changed, 27 insertions(+), 3 deletions(-) diff --git a/drivers/gpu/drm/tidss/tidss_kms.c b/drivers/gpu/drm/tidss/tidss_kms.c index c34eb90cddbeac..9f5f98e707f2cc 100644 --- a/drivers/gpu/drm/tidss/tidss_kms.c +++ b/drivers/gpu/drm/tidss/tidss_kms.c @@ -28,9 +28,33 @@ static void tidss_atomic_commit_tail(struct drm_atomic_state *old_state) tidss_runtime_get(tidss); - drm_atomic_helper_commit_modeset_disables(ddev, old_state); - drm_atomic_helper_commit_planes(ddev, old_state, DRM_PLANE_COMMIT_ACTIVE_ONLY); - drm_atomic_helper_commit_modeset_enables(ddev, old_state); + /* + * TI's OLDI and DSI encoders need to be set up before the crtc is + * enabled. Thus drm_atomic_helper_commit_modeset_enables() and + * drm_atomic_helper_commit_modeset_disables() cannot be used here, as + * they enable the crtc before bridges' pre-enable, and disable the crtc + * after bridges' post-disable. + * + * Open code the functions here and first call the bridges' pre-enables, + * then crtc enable, then bridges' post-enable (and vice versa for + * disable). + */ + + drm_atomic_helper_commit_encoder_bridge_disable(ddev, old_state); + drm_atomic_helper_commit_crtc_disable(ddev, old_state); + drm_atomic_helper_commit_encoder_bridge_post_disable(ddev, old_state); + + drm_atomic_helper_update_legacy_modeset_state(ddev, old_state); + drm_atomic_helper_calc_timestamping_constants(old_state); + drm_atomic_helper_commit_crtc_set_mode(ddev, old_state); + + drm_atomic_helper_commit_planes(ddev, old_state, + DRM_PLANE_COMMIT_ACTIVE_ONLY); + + drm_atomic_helper_commit_encoder_bridge_pre_enable(ddev, old_state); + drm_atomic_helper_commit_crtc_enable(ddev, old_state); + drm_atomic_helper_commit_encoder_bridge_enable(ddev, old_state); + drm_atomic_helper_commit_writebacks(ddev, old_state); drm_atomic_helper_commit_hw_done(old_state); drm_atomic_helper_wait_for_flip_done(ddev, old_state); From b88191562d92ca1ba6b5a06baec8a288531fa801 Mon Sep 17 00:00:00 2001 From: Alex Deucher Date: Mon, 30 Jun 2025 10:47:09 -0400 Subject: [PATCH 2252/3902] drm/radeon: Remove __counted_by from ClockInfoArray.clockInfo[] commit 19158c7332468bc28572bdca428e89c7954ee1b1 upstream. clockInfo[] is a generic uchar pointer to variable sized structures which vary from ASIC to ASIC. Closes: https://gitlab.freedesktop.org/drm/amd/-/issues/4374 Reviewed-by: Lijo Lazar Signed-off-by: Alex Deucher (cherry picked from commit dc135aa73561b5acc74eadf776e48530996529a3) Cc: stable@vger.kernel.org Signed-off-by: Greg Kroah-Hartman --- drivers/gpu/drm/radeon/pptable.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/gpu/drm/radeon/pptable.h b/drivers/gpu/drm/radeon/pptable.h index 969a8fb0ee9e0b..f4e71046dc91c2 100644 --- a/drivers/gpu/drm/radeon/pptable.h +++ b/drivers/gpu/drm/radeon/pptable.h @@ -450,7 +450,7 @@ typedef struct _ClockInfoArray{ //sizeof(ATOM_PPLIB_CLOCK_INFO) UCHAR ucEntrySize; - UCHAR clockInfo[] __counted_by(ucNumEntries); + UCHAR clockInfo[] /*__counted_by(ucNumEntries)*/; }ClockInfoArray; typedef struct _NonClockInfoArray{ From 3aa67687d5582e4fac911a041d375708766ff15a Mon Sep 17 00:00:00 2001 From: Bartosz Golaszewski Date: Tue, 6 Jan 2026 10:00:11 +0100 Subject: [PATCH 2253/3902] gpio: rockchip: mark the GPIO controller as sleeping commit 20cf2aed89ac6d78a0122e31c875228e15247194 upstream. The GPIO controller is configured as non-sleeping but it uses generic pinctrl helpers which use a mutex for synchronization. This can cause the following lockdep splat with shared GPIOs enabled on boards which have multiple devices using the same GPIO: BUG: sleeping function called from invalid context at kernel/locking/mutex.c:591 in_atomic(): 1, irqs_disabled(): 1, non_block: 0, pid: 12, name: kworker/u16:0 preempt_count: 1, expected: 0 RCU nest depth: 0, expected: 0 6 locks held by kworker/u16:0/12: #0: ffff0001f0018d48 ((wq_completion)events_unbound#2){+.+.}-{0:0}, at: process_one_work+0x18c/0x604 #1: ffff8000842dbdf0 (deferred_probe_work){+.+.}-{0:0}, at: process_one_work+0x1b4/0x604 #2: ffff0001f18498f8 (&dev->mutex){....}-{4:4}, at: __device_attach+0x38/0x1b0 #3: ffff0001f75f1e90 (&gdev->srcu){.+.?}-{0:0}, at: gpiod_direction_output_raw_commit+0x0/0x360 #4: ffff0001f46e3db8 (&shared_desc->spinlock){....}-{3:3}, at: gpio_shared_proxy_direction_output+0xd0/0x144 [gpio_shared_proxy] #5: ffff0001f180ee90 (&gdev->srcu){.+.?}-{0:0}, at: gpiod_direction_output_raw_commit+0x0/0x360 irq event stamp: 81450 hardirqs last enabled at (81449): [] _raw_spin_unlock_irqrestore+0x74/0x78 hardirqs last disabled at (81450): [] _raw_spin_lock_irqsave+0x84/0x88 softirqs last enabled at (79616): [] __alloc_skb+0x17c/0x1e8 softirqs last disabled at (79614): [] __alloc_skb+0x17c/0x1e8 CPU: 2 UID: 0 PID: 12 Comm: kworker/u16:0 Not tainted 6.19.0-rc4-next-20260105+ #11975 PREEMPT Hardware name: Hardkernel ODROID-M1 (DT) Workqueue: events_unbound deferred_probe_work_func Call trace: show_stack+0x18/0x24 (C) dump_stack_lvl+0x90/0xd0 dump_stack+0x18/0x24 __might_resched+0x144/0x248 __might_sleep+0x48/0x98 __mutex_lock+0x5c/0x894 mutex_lock_nested+0x24/0x30 pinctrl_get_device_gpio_range+0x44/0x128 pinctrl_gpio_direction+0x3c/0xe0 pinctrl_gpio_direction_output+0x14/0x20 rockchip_gpio_direction_output+0xb8/0x19c gpiochip_direction_output+0x38/0x94 gpiod_direction_output_raw_commit+0x1d8/0x360 gpiod_direction_output_nonotify+0x7c/0x230 gpiod_direction_output+0x34/0xf8 gpio_shared_proxy_direction_output+0xec/0x144 [gpio_shared_proxy] gpiochip_direction_output+0x38/0x94 gpiod_direction_output_raw_commit+0x1d8/0x360 gpiod_direction_output_nonotify+0x7c/0x230 gpiod_configure_flags+0xbc/0x480 gpiod_find_and_request+0x1a0/0x574 gpiod_get_index+0x58/0x84 devm_gpiod_get_index+0x20/0xb4 devm_gpiod_get_optional+0x18/0x30 rockchip_pcie_probe+0x98/0x380 platform_probe+0x5c/0xac really_probe+0xbc/0x298 Fixes: 936ee2675eee ("gpio/rockchip: add driver for rockchip gpio") Cc: stable@vger.kernel.org Reported-by: Marek Szyprowski Closes: https://lore.kernel.org/all/d035fc29-3b03-4cd6-b8ec-001f93540bc6@samsung.com/ Acked-by: Heiko Stuebner Link: https://lore.kernel.org/r/20260106090011.21603-1-bartosz.golaszewski@oss.qualcomm.com Signed-off-by: Bartosz Golaszewski Signed-off-by: Greg Kroah-Hartman --- drivers/gpio/gpio-rockchip.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/gpio/gpio-rockchip.c b/drivers/gpio/gpio-rockchip.c index 47174eb3ba76fb..bae2061f15fc47 100644 --- a/drivers/gpio/gpio-rockchip.c +++ b/drivers/gpio/gpio-rockchip.c @@ -593,6 +593,7 @@ static int rockchip_gpiolib_register(struct rockchip_pin_bank *bank) gc->ngpio = bank->nr_pins; gc->label = bank->name; gc->parent = bank->dev; + gc->can_sleep = true; ret = gpiochip_add_data(gc, bank); if (ret) { From 2b9c15286a178190e5bc7ee0a1b200c38953fdf4 Mon Sep 17 00:00:00 2001 From: Jens Axboe Date: Mon, 5 Jan 2026 07:42:48 -0700 Subject: [PATCH 2254/3902] io_uring/io-wq: fix incorrect io_wq_for_each_worker() termination logic commit e0392a10c9e80a3991855a81317da3039fcbe32c upstream. A previous commit added this helper, and had it terminate if false is returned from the handler. However, that is completely opposite, it should abort the loop if true is returned. Fix this up by having io_wq_for_each_worker() keep iterating as long as false is returned, and only abort if true is returned. Cc: stable@vger.kernel.org Fixes: 751eedc4b4b7 ("io_uring/io-wq: move worker lists to struct io_wq_acct") Reported-by: Lewis Campbell Reviewed-by: Gabriel Krisman Bertazi Signed-off-by: Jens Axboe Signed-off-by: Greg Kroah-Hartman --- io_uring/io-wq.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/io_uring/io-wq.c b/io_uring/io-wq.c index 1d03b2fc4b2594..55961da19f3b1d 100644 --- a/io_uring/io-wq.c +++ b/io_uring/io-wq.c @@ -951,11 +951,11 @@ static bool io_wq_for_each_worker(struct io_wq *wq, void *data) { for (int i = 0; i < IO_WQ_ACCT_NR; i++) { - if (!io_acct_for_each_worker(&wq->acct[i], func, data)) - return false; + if (io_acct_for_each_worker(&wq->acct[i], func, data)) + return true; } - return true; + return false; } static bool io_wq_worker_wake(struct io_worker *worker, void *data) From 391adca8e2135a9af701a9bec9c843cc4c48cc43 Mon Sep 17 00:00:00 2001 From: Bjorn Helgaas Date: Mon, 3 Nov 2025 16:19:26 -0600 Subject: [PATCH 2255/3902] PCI: meson: Report that link is up while in ASPM L0s and L1 states commit df27c03b9e3ef2baa9e9c9f56a771d463a84489d upstream. Previously meson_pcie_link_up() only returned true if the link was in the L0 state. This was incorrect because hardware autonomously manages transitions between L0, L0s, and L1 while both components on the link stay in D0. Those states should all be treated as "link is active". Returning false when the device was in L0s or L1 broke config accesses because dw_pcie_other_conf_map_bus() fails if the link is down, which caused errors like this: meson-pcie fc000000.pcie: error: wait linkup timeout pci 0000:01:00.0: BAR 0: error updating (0xfc700004 != 0xffffffff) Remove the LTSSM state check, timeout, speed check, and error message from meson_pcie_link_up(), the dw_pcie_ops.link_up() method, so it is a simple boolean check of whether the link is active. Timeouts and error messages are handled at a higher level, e.g., dw_pcie_wait_for_link(). Fixes: 9c0ef6d34fdb ("PCI: amlogic: Add the Amlogic Meson PCIe controller driver") Reported-by: Linnaea Lavia Closes: https://lore.kernel.org/r/DM4PR05MB102707B8CDF84D776C39F22F2C7F0A@DM4PR05MB10270.namprd05.prod.outlook.com [bhelgaas: squash removal of unused WAIT_LINKUP_TIMEOUT by Martin Blumenstingl : https://patch.msgid.link/20260105125625.239497-1-martin.blumenstingl@googlemail.com] Signed-off-by: Bjorn Helgaas Tested-by: Linnaea Lavia Tested-by: Neil Armstrong # on BananaPi M2S Reviewed-by: Neil Armstrong Cc: stable@vger.kernel.org Link: https://patch.msgid.link/20251103221930.1831376-1-helgaas@kernel.org Link: https://patch.msgid.link/20260105125625.239497-1-martin.blumenstingl@googlemail.com Signed-off-by: Greg Kroah-Hartman --- drivers/pci/controller/dwc/pci-meson.c | 39 +++----------------------- 1 file changed, 4 insertions(+), 35 deletions(-) diff --git a/drivers/pci/controller/dwc/pci-meson.c b/drivers/pci/controller/dwc/pci-meson.c index 54b6a4196f1767..0694084f612b79 100644 --- a/drivers/pci/controller/dwc/pci-meson.c +++ b/drivers/pci/controller/dwc/pci-meson.c @@ -37,7 +37,6 @@ #define PCIE_CFG_STATUS17 0x44 #define PM_CURRENT_STATE(x) (((x) >> 7) & 0x1) -#define WAIT_LINKUP_TIMEOUT 4000 #define PORT_CLK_RATE 100000000UL #define MAX_PAYLOAD_SIZE 256 #define MAX_READ_REQ_SIZE 256 @@ -350,40 +349,10 @@ static struct pci_ops meson_pci_ops = { static bool meson_pcie_link_up(struct dw_pcie *pci) { struct meson_pcie *mp = to_meson_pcie(pci); - struct device *dev = pci->dev; - u32 speed_okay = 0; - u32 cnt = 0; - u32 state12, state17, smlh_up, ltssm_up, rdlh_up; - - do { - state12 = meson_cfg_readl(mp, PCIE_CFG_STATUS12); - state17 = meson_cfg_readl(mp, PCIE_CFG_STATUS17); - smlh_up = IS_SMLH_LINK_UP(state12); - rdlh_up = IS_RDLH_LINK_UP(state12); - ltssm_up = IS_LTSSM_UP(state12); - - if (PM_CURRENT_STATE(state17) < PCIE_GEN3) - speed_okay = 1; - - if (smlh_up) - dev_dbg(dev, "smlh_link_up is on\n"); - if (rdlh_up) - dev_dbg(dev, "rdlh_link_up is on\n"); - if (ltssm_up) - dev_dbg(dev, "ltssm_up is on\n"); - if (speed_okay) - dev_dbg(dev, "speed_okay\n"); - - if (smlh_up && rdlh_up && ltssm_up && speed_okay) - return true; - - cnt++; - - udelay(10); - } while (cnt < WAIT_LINKUP_TIMEOUT); - - dev_err(dev, "error: wait linkup timeout\n"); - return false; + u32 state12; + + state12 = meson_cfg_readl(mp, PCIE_CFG_STATUS12); + return IS_SMLH_LINK_UP(state12) && IS_RDLH_LINK_UP(state12); } static int meson_pcie_host_init(struct dw_pcie_rp *pp) From ea7a54393d50c60943c71f15e607d8fac0c686fc Mon Sep 17 00:00:00 2001 From: Bartosz Golaszewski Date: Wed, 26 Nov 2025 13:22:19 +0100 Subject: [PATCH 2256/3902] pinctrl: qcom: lpass-lpi: mark the GPIO controller as sleeping commit ebc18e9854e5a2b62a041fb57b216a903af45b85 upstream. The gpio_chip settings in this driver say the controller can't sleep but it actually uses a mutex for synchronization. This triggers the following BUG(): [ 9.233659] BUG: sleeping function called from invalid context at kernel/locking/mutex.c:281 [ 9.233665] in_atomic(): 1, irqs_disabled(): 1, non_block: 0, pid: 554, name: (udev-worker) [ 9.233669] preempt_count: 1, expected: 0 [ 9.233673] RCU nest depth: 0, expected: 0 [ 9.233688] Tainted: [W]=WARN [ 9.233690] Hardware name: Dell Inc. Latitude 7455/0FK7MX, BIOS 2.10.1 05/20/2025 [ 9.233694] Call trace: [ 9.233696] show_stack+0x24/0x38 (C) [ 9.233709] dump_stack_lvl+0x40/0x88 [ 9.233716] dump_stack+0x18/0x24 [ 9.233722] __might_resched+0x148/0x160 [ 9.233731] __might_sleep+0x38/0x98 [ 9.233736] mutex_lock+0x30/0xd8 [ 9.233749] lpi_config_set+0x2e8/0x3c8 [pinctrl_lpass_lpi] [ 9.233757] lpi_gpio_direction_output+0x58/0x90 [pinctrl_lpass_lpi] [ 9.233761] gpiod_direction_output_raw_commit+0x110/0x428 [ 9.233772] gpiod_direction_output_nonotify+0x234/0x358 [ 9.233779] gpiod_direction_output+0x38/0xd0 [ 9.233786] gpio_shared_proxy_direction_output+0xb8/0x2a8 [gpio_shared_proxy] [ 9.233792] gpiod_direction_output_raw_commit+0x110/0x428 [ 9.233799] gpiod_direction_output_nonotify+0x234/0x358 [ 9.233806] gpiod_configure_flags+0x2c0/0x580 [ 9.233812] gpiod_find_and_request+0x358/0x4f8 [ 9.233819] gpiod_get_index+0x7c/0x98 [ 9.233826] devm_gpiod_get+0x34/0xb0 [ 9.233829] reset_gpio_probe+0x58/0x128 [reset_gpio] [ 9.233836] auxiliary_bus_probe+0xb0/0xf0 [ 9.233845] really_probe+0x14c/0x450 [ 9.233853] __driver_probe_device+0xb0/0x188 [ 9.233858] driver_probe_device+0x4c/0x250 [ 9.233863] __driver_attach+0xf8/0x2a0 [ 9.233868] bus_for_each_dev+0xf8/0x158 [ 9.233872] driver_attach+0x30/0x48 [ 9.233876] bus_add_driver+0x158/0x2b8 [ 9.233880] driver_register+0x74/0x118 [ 9.233886] __auxiliary_driver_register+0x94/0xe8 [ 9.233893] init_module+0x34/0xfd0 [reset_gpio] [ 9.233898] do_one_initcall+0xec/0x300 [ 9.233903] do_init_module+0x64/0x260 [ 9.233910] load_module+0x16c4/0x1900 [ 9.233915] __arm64_sys_finit_module+0x24c/0x378 [ 9.233919] invoke_syscall+0x4c/0xe8 [ 9.233925] el0_svc_common+0x8c/0xf0 [ 9.233929] do_el0_svc+0x28/0x40 [ 9.233934] el0_svc+0x38/0x100 [ 9.233938] el0t_64_sync_handler+0x84/0x130 [ 9.233943] el0t_64_sync+0x17c/0x180 Mark the controller as sleeping. Fixes: 6e261d1090d6 ("pinctrl: qcom: Add sm8250 lpass lpi pinctrl driver") Cc: stable@vger.kernel.org Reported-by: Val Packett Closes: https://lore.kernel.org/all/98c0f185-b0e0-49ea-896c-f3972dd011ca@packett.cool/ Signed-off-by: Bartosz Golaszewski Reviewed-by: Dmitry Baryshkov Reviewed-by: Bjorn Andersson Signed-off-by: Linus Walleij Signed-off-by: Greg Kroah-Hartman --- drivers/pinctrl/qcom/pinctrl-lpass-lpi.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/pinctrl/qcom/pinctrl-lpass-lpi.c b/drivers/pinctrl/qcom/pinctrl-lpass-lpi.c index 1c97ec44aa5ff7..78212f9928430c 100644 --- a/drivers/pinctrl/qcom/pinctrl-lpass-lpi.c +++ b/drivers/pinctrl/qcom/pinctrl-lpass-lpi.c @@ -498,7 +498,7 @@ int lpi_pinctrl_probe(struct platform_device *pdev) pctrl->chip.base = -1; pctrl->chip.ngpio = data->npins; pctrl->chip.label = dev_name(dev); - pctrl->chip.can_sleep = false; + pctrl->chip.can_sleep = true; mutex_init(&pctrl->lock); From b7a883b0135dbc6817e90a829421c9fc8cd94bad Mon Sep 17 00:00:00 2001 From: Malaya Kumar Rout Date: Tue, 30 Dec 2025 17:26:13 +0530 Subject: [PATCH 2257/3902] PM: hibernate: Fix crash when freeing invalid crypto compressor commit 7966cf0ebe32c981bfa3db252cb5fc3bb1bf2e77 upstream. When crypto_alloc_acomp() fails, it returns an ERR_PTR value, not NULL. The cleanup code in save_compressed_image() and load_compressed_image() unconditionally calls crypto_free_acomp() without checking for ERR_PTR, which causes crypto_acomp_tfm() to dereference an invalid pointer and crash the kernel. This can be triggered when the compression algorithm is unavailable (e.g., CONFIG_CRYPTO_LZO not enabled). Fix by adding IS_ERR_OR_NULL() checks before calling crypto_free_acomp() and acomp_request_free(), similar to the existing kthread_stop() check. Fixes: b03d542c3c95 ("PM: hibernate: Use crypto_acomp interface") Signed-off-by: Malaya Kumar Rout Cc: 6.15+ # 6.15+ [ rjw: Added 2 empty code lines ] Link: https://patch.msgid.link/20251230115613.64080-1-mrout@redhat.com Signed-off-by: Rafael J. Wysocki Signed-off-by: Greg Kroah-Hartman --- kernel/power/swap.c | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/kernel/power/swap.c b/kernel/power/swap.c index 70ae21f7370d4c..f910a250ccddb9 100644 --- a/kernel/power/swap.c +++ b/kernel/power/swap.c @@ -897,8 +897,11 @@ static int save_compressed_image(struct swap_map_handle *handle, for (thr = 0; thr < nr_threads; thr++) { if (data[thr].thr) kthread_stop(data[thr].thr); - acomp_request_free(data[thr].cr); - crypto_free_acomp(data[thr].cc); + if (data[thr].cr) + acomp_request_free(data[thr].cr); + + if (!IS_ERR_OR_NULL(data[thr].cc)) + crypto_free_acomp(data[thr].cc); } vfree(data); } @@ -1519,8 +1522,11 @@ static int load_compressed_image(struct swap_map_handle *handle, for (thr = 0; thr < nr_threads; thr++) { if (data[thr].thr) kthread_stop(data[thr].thr); - acomp_request_free(data[thr].cr); - crypto_free_acomp(data[thr].cc); + if (data[thr].cr) + acomp_request_free(data[thr].cr); + + if (!IS_ERR_OR_NULL(data[thr].cc)) + crypto_free_acomp(data[thr].cc); } vfree(data); } From 5c12a13522f45f4ce0c22c2715879234e38bc12b Mon Sep 17 00:00:00 2001 From: Tomi Valkeinen Date: Fri, 5 Dec 2025 11:51:49 +0200 Subject: [PATCH 2258/3902] Revert "drm/mediatek: dsi: Fix DSI host and panel bridge pre-enable order" commit 33e8150bd32d7dc25c977bb455f1f5d54bfd5241 upstream. This reverts commit f5b1819193667bf62c3c99d3921b9429997a14b2. As the original commit (c9b1150a68d9 ("drm/atomic-helper: Re-order bridge chain pre-enable and post-disable")) causing the issue has been reverted, let's revert the fix for mediatek. Signed-off-by: Tomi Valkeinen Cc: stable@vger.kernel.org # v6.17+ Fixes: c9b1150a68d9 ("drm/atomic-helper: Re-order bridge chain pre-enable and post-disable") Reviewed-by: Maxime Ripard Reviewed-by: Linus Walleij Tested-by: Linus Walleij Signed-off-by: Linus Walleij Link: https://patch.msgid.link/20251205-drm-seq-fix-v1-2-fda68fa1b3de@ideasonboard.com Signed-off-by: Greg Kroah-Hartman --- drivers/gpu/drm/mediatek/mtk_dsi.c | 6 ------ 1 file changed, 6 deletions(-) diff --git a/drivers/gpu/drm/mediatek/mtk_dsi.c b/drivers/gpu/drm/mediatek/mtk_dsi.c index 0e2bcd5f67b767..d7726091819c47 100644 --- a/drivers/gpu/drm/mediatek/mtk_dsi.c +++ b/drivers/gpu/drm/mediatek/mtk_dsi.c @@ -1002,12 +1002,6 @@ static int mtk_dsi_host_attach(struct mipi_dsi_host *host, return PTR_ERR(dsi->next_bridge); } - /* - * set flag to request the DSI host bridge be pre-enabled before device bridge - * in the chain, so the DSI host is ready when the device bridge is pre-enabled - */ - dsi->next_bridge->pre_enable_prev_first = true; - drm_bridge_add(&dsi->bridge); ret = component_add(host->dev, &mtk_dsi_component_ops); From e3c35177103ead4658b8a62f41e3080d45885464 Mon Sep 17 00:00:00 2001 From: Eric Dumazet Date: Thu, 8 Jan 2026 10:19:27 +0000 Subject: [PATCH 2259/3902] wifi: avoid kernel-infoleak from struct iw_point commit 21cbf883d073abbfe09e3924466aa5e0449e7261 upstream. struct iw_point has a 32bit hole on 64bit arches. struct iw_point { void __user *pointer; /* Pointer to the data (in user space) */ __u16 length; /* number of fields or size in bytes */ __u16 flags; /* Optional params */ }; Make sure to zero the structure to avoid disclosing 32bits of kernel data to user space. Fixes: 87de87d5e47f ("wext: Dispatch and handle compat ioctls entirely in net/wireless/wext.c") Reported-by: syzbot+bfc7323743ca6dbcc3d3@syzkaller.appspotmail.com Closes: https://lore.kernel.org/netdev/695f83f3.050a0220.1c677c.0392.GAE@google.com/T/#u Signed-off-by: Eric Dumazet Cc: stable@vger.kernel.org Link: https://patch.msgid.link/20260108101927.857582-1-edumazet@google.com Signed-off-by: Johannes Berg Signed-off-by: Greg Kroah-Hartman --- net/wireless/wext-core.c | 4 ++++ net/wireless/wext-priv.c | 4 ++++ 2 files changed, 8 insertions(+) diff --git a/net/wireless/wext-core.c b/net/wireless/wext-core.c index c32a7c6903d536..7b8e94214b0722 100644 --- a/net/wireless/wext-core.c +++ b/net/wireless/wext-core.c @@ -1101,6 +1101,10 @@ static int compat_standard_call(struct net_device *dev, return ioctl_standard_call(dev, iwr, cmd, info, handler); iwp_compat = (struct compat_iw_point *) &iwr->u.data; + + /* struct iw_point has a 32bit hole on 64bit arches. */ + memset(&iwp, 0, sizeof(iwp)); + iwp.pointer = compat_ptr(iwp_compat->pointer); iwp.length = iwp_compat->length; iwp.flags = iwp_compat->flags; diff --git a/net/wireless/wext-priv.c b/net/wireless/wext-priv.c index 674d426a9d24f9..37d1147019c2ba 100644 --- a/net/wireless/wext-priv.c +++ b/net/wireless/wext-priv.c @@ -228,6 +228,10 @@ int compat_private_call(struct net_device *dev, struct iwreq *iwr, struct iw_point iwp; iwp_compat = (struct compat_iw_point *) &iwr->u.data; + + /* struct iw_point has a 32bit hole on 64bit arches. */ + memset(&iwp, 0, sizeof(iwp)); + iwp.pointer = compat_ptr(iwp_compat->pointer); iwp.length = iwp_compat->length; iwp.flags = iwp_compat->flags; From b97be67dc06ef480ba48c8194fa11697760f7f4a Mon Sep 17 00:00:00 2001 From: Johannes Berg Date: Tue, 16 Dec 2025 11:52:42 +0100 Subject: [PATCH 2260/3902] wifi: mac80211: restore non-chanctx injection behaviour commit d594cc6f2c588810888df70c83a9654b6bc7942d upstream. During the transition to use channel contexts throughout, the ability to do injection while in monitor mode concurrent with another interface was lost, since the (virtual) monitor won't have a chanctx assigned in this scenario. It's harder to fix drivers that actually transitioned to using channel contexts themselves, such as mt76, but it's easy to do those that are (still) just using the emulation. Do that. Cc: stable@vger.kernel.org Link: https://bugzilla.kernel.org/show_bug.cgi?id=218763 Reported-and-tested-by: Oscar Alfonso Diaz Fixes: 0a44dfc07074 ("wifi: mac80211: simplify non-chanctx drivers") Link: https://patch.msgid.link/20251216105242.18366-2-johannes@sipsolutions.net Signed-off-by: Johannes Berg Signed-off-by: Greg Kroah-Hartman --- net/mac80211/tx.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/net/mac80211/tx.c b/net/mac80211/tx.c index e7b141c55f7a90..160667be3f4d23 100644 --- a/net/mac80211/tx.c +++ b/net/mac80211/tx.c @@ -2395,6 +2395,8 @@ netdev_tx_t ieee80211_monitor_start_xmit(struct sk_buff *skb, if (chanctx_conf) chandef = &chanctx_conf->def; + else if (local->emulate_chanctx) + chandef = &local->hw.conf.chandef; else goto fail_rcu; From 2d653bb63d598ae4b096dd678744bdcc34ee89e8 Mon Sep 17 00:00:00 2001 From: ziming zhang Date: Thu, 11 Dec 2025 16:52:58 +0800 Subject: [PATCH 2261/3902] libceph: prevent potential out-of-bounds reads in handle_auth_done() commit 818156caffbf55cb4d368f9c3cac64e458fb49c9 upstream. Perform an explicit bounds check on payload_len to avoid a possible out-of-bounds access in the callout. [ idryomov: changelog ] Cc: stable@vger.kernel.org Signed-off-by: ziming zhang Reviewed-by: Ilya Dryomov Signed-off-by: Ilya Dryomov Signed-off-by: Greg Kroah-Hartman --- net/ceph/messenger_v2.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/net/ceph/messenger_v2.c b/net/ceph/messenger_v2.c index 9e48623018a365..061eaa047f7651 100644 --- a/net/ceph/messenger_v2.c +++ b/net/ceph/messenger_v2.c @@ -2377,7 +2377,9 @@ static int process_auth_done(struct ceph_connection *con, void *p, void *end) ceph_decode_64_safe(&p, end, global_id, bad); ceph_decode_32_safe(&p, end, con->v2.con_mode, bad); + ceph_decode_32_safe(&p, end, payload_len, bad); + ceph_decode_need(&p, end, payload_len, bad); dout("%s con %p global_id %llu con_mode %d payload_len %d\n", __func__, con, global_id, con->v2.con_mode, payload_len); From 6348d70af847b79805374fe628d3809a63fd7df3 Mon Sep 17 00:00:00 2001 From: Ilya Dryomov Date: Mon, 15 Dec 2025 11:53:31 +0100 Subject: [PATCH 2262/3902] libceph: replace overzealous BUG_ON in osdmap_apply_incremental() commit e00c3f71b5cf75681dbd74ee3f982a99cb690c2b upstream. If the osdmap is (maliciously) corrupted such that the incremental osdmap epoch is different from what is expected, there is no need to BUG. Instead, just declare the incremental osdmap to be invalid. Cc: stable@vger.kernel.org Reported-by: ziming zhang Signed-off-by: Ilya Dryomov Signed-off-by: Greg Kroah-Hartman --- net/ceph/osdmap.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/net/ceph/osdmap.c b/net/ceph/osdmap.c index f5f60deb680ae8..0722e9347a646b 100644 --- a/net/ceph/osdmap.c +++ b/net/ceph/osdmap.c @@ -1979,11 +1979,13 @@ struct ceph_osdmap *osdmap_apply_incremental(void **p, void *end, bool msgr2, sizeof(u64) + sizeof(u32), e_inval); ceph_decode_copy(p, &fsid, sizeof(fsid)); epoch = ceph_decode_32(p); - BUG_ON(epoch != map->epoch+1); ceph_decode_copy(p, &modified, sizeof(modified)); new_pool_max = ceph_decode_64(p); new_flags = ceph_decode_32(p); + if (epoch != map->epoch + 1) + goto e_inval; + /* full map? */ ceph_decode_32_safe(p, end, len, e_inval); if (len > 0) { From f21c3fdb96833aac2f533506899fe38c19cf49d5 Mon Sep 17 00:00:00 2001 From: Tuo Li Date: Sun, 21 Dec 2025 02:11:49 +0800 Subject: [PATCH 2263/3902] libceph: make free_choose_arg_map() resilient to partial allocation commit e3fe30e57649c551757a02e1cad073c47e1e075e upstream. free_choose_arg_map() may dereference a NULL pointer if its caller fails after a partial allocation. For example, in decode_choose_args(), if allocation of arg_map->args fails, execution jumps to the fail label and free_choose_arg_map() is called. Since arg_map->size is updated to a non-zero value before memory allocation, free_choose_arg_map() will iterate over arg_map->args and dereference a NULL pointer. To prevent this potential NULL pointer dereference and make free_choose_arg_map() more resilient, add checks for pointers before iterating. Cc: stable@vger.kernel.org Co-authored-by: Ilya Dryomov Signed-off-by: Tuo Li Reviewed-by: Viacheslav Dubeyko Signed-off-by: Ilya Dryomov Signed-off-by: Greg Kroah-Hartman --- net/ceph/osdmap.c | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/net/ceph/osdmap.c b/net/ceph/osdmap.c index 0722e9347a646b..7c76eb9d6ceec5 100644 --- a/net/ceph/osdmap.c +++ b/net/ceph/osdmap.c @@ -241,22 +241,26 @@ static struct crush_choose_arg_map *alloc_choose_arg_map(void) static void free_choose_arg_map(struct crush_choose_arg_map *arg_map) { - if (arg_map) { - int i, j; + int i, j; + + if (!arg_map) + return; - WARN_ON(!RB_EMPTY_NODE(&arg_map->node)); + WARN_ON(!RB_EMPTY_NODE(&arg_map->node)); + if (arg_map->args) { for (i = 0; i < arg_map->size; i++) { struct crush_choose_arg *arg = &arg_map->args[i]; - - for (j = 0; j < arg->weight_set_size; j++) - kfree(arg->weight_set[j].weights); - kfree(arg->weight_set); + if (arg->weight_set) { + for (j = 0; j < arg->weight_set_size; j++) + kfree(arg->weight_set[j].weights); + kfree(arg->weight_set); + } kfree(arg->ids); } kfree(arg_map->args); - kfree(arg_map); } + kfree(arg_map); } DEFINE_RB_FUNCS(choose_arg_map, struct crush_choose_arg_map, choose_args_index, From 9e0101e57534ef0e7578dd09608a6106736b82e5 Mon Sep 17 00:00:00 2001 From: Ilya Dryomov Date: Mon, 29 Dec 2025 15:14:48 +0100 Subject: [PATCH 2264/3902] libceph: return the handler error from mon_handle_auth_done() commit e84b48d31b5008932c0a0902982809fbaa1d3b70 upstream. Currently any error from ceph_auth_handle_reply_done() is propagated via finish_auth() but isn't returned from mon_handle_auth_done(). This results in higher layers learning that (despite the monitor considering us to be successfully authenticated) something went wrong in the authentication phase and reacting accordingly, but msgr2 still trying to proceed with establishing the session in the background. In the case of secure mode this can trigger a WARN in setup_crypto() and later lead to a NULL pointer dereference inside of prepare_auth_signature(). Cc: stable@vger.kernel.org Fixes: cd1a677cad99 ("libceph, ceph: implement msgr2.1 protocol (crc and secure modes)") Signed-off-by: Ilya Dryomov Reviewed-by: Viacheslav Dubeyko Signed-off-by: Greg Kroah-Hartman --- net/ceph/mon_client.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/net/ceph/mon_client.c b/net/ceph/mon_client.c index c227ececa9254c..fa8dd2a20f7d28 100644 --- a/net/ceph/mon_client.c +++ b/net/ceph/mon_client.c @@ -1417,7 +1417,7 @@ static int mon_handle_auth_done(struct ceph_connection *con, if (!ret) finish_hunting(monc); mutex_unlock(&monc->mutex); - return 0; + return ret; } static int mon_handle_auth_bad_method(struct ceph_connection *con, From 10b7c72810364226f7b27916ea3e2a4f870bc04b Mon Sep 17 00:00:00 2001 From: Sam Edwards Date: Tue, 30 Dec 2025 20:05:06 -0800 Subject: [PATCH 2265/3902] libceph: reset sparse-read state in osd_fault() commit 11194b416ef95012c2cfe5f546d71af07b639e93 upstream. When a fault occurs, the connection is abandoned, reestablished, and any pending operations are retried. The OSD client tracks the progress of a sparse-read reply using a separate state machine, largely independent of the messenger's state. If a connection is lost mid-payload or the sparse-read state machine returns an error, the sparse-read state is not reset. The OSD client will then interpret the beginning of a new reply as the continuation of the old one. If this makes the sparse-read machinery enter a failure state, it may never recover, producing loops like: libceph: [0] got 0 extents libceph: data len 142248331 != extent len 0 libceph: osd0 (1)...:6801 socket error on read libceph: data len 142248331 != extent len 0 libceph: osd0 (1)...:6801 socket error on read Therefore, reset the sparse-read state in osd_fault(), ensuring retries start from a clean state. Cc: stable@vger.kernel.org Fixes: f628d7999727 ("libceph: add sparse read support to OSD client") Signed-off-by: Sam Edwards Reviewed-by: Ilya Dryomov Signed-off-by: Ilya Dryomov Signed-off-by: Greg Kroah-Hartman --- net/ceph/osd_client.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/net/ceph/osd_client.c b/net/ceph/osd_client.c index 6664ea73ccf81b..67f7430c775018 100644 --- a/net/ceph/osd_client.c +++ b/net/ceph/osd_client.c @@ -4283,6 +4283,9 @@ static void osd_fault(struct ceph_connection *con) goto out_unlock; } + osd->o_sparse_op_idx = -1; + ceph_init_sparse_read(&osd->o_sparse_read); + if (!reopen_osd(osd)) kick_osd_requests(osd); maybe_request_map(osdc); From 5647d42c47b535573b63e073e91164d6a5bb058c Mon Sep 17 00:00:00 2001 From: Ilya Dryomov Date: Mon, 5 Jan 2026 19:23:19 +0100 Subject: [PATCH 2266/3902] libceph: make calc_target() set t->paused, not just clear it commit c0fe2994f9a9d0a2ec9e42441ea5ba74b6a16176 upstream. Currently calc_target() clears t->paused if the request shouldn't be paused anymore, but doesn't ever set t->paused even though it's able to determine when the request should be paused. Setting t->paused is left to __submit_request() which is fine for regular requests but doesn't work for linger requests -- since __submit_request() doesn't operate on linger requests, there is nowhere for lreq->t.paused to be set. One consequence of this is that watches don't get reestablished on paused -> unpaused transitions in cases where requests have been paused long enough for the (paused) unwatch request to time out and for the subsequent (re)watch request to enter the paused state. On top of the watch not getting reestablished, rbd_reregister_watch() gets stuck with rbd_dev->watch_mutex held: rbd_register_watch __rbd_register_watch ceph_osdc_watch linger_reg_commit_wait It's waiting for lreq->reg_commit_wait to be completed, but for that to happen the respective request needs to end up on need_resend_linger list and be kicked when requests are unpaused. There is no chance for that if the request in question is never marked paused in the first place. The fact that rbd_dev->watch_mutex remains taken out forever then prevents the image from getting unmapped -- "rbd unmap" would inevitably hang in D state on an attempt to grab the mutex. Cc: stable@vger.kernel.org Reported-by: Raphael Zimmer Signed-off-by: Ilya Dryomov Reviewed-by: Viacheslav Dubeyko Signed-off-by: Greg Kroah-Hartman --- net/ceph/osd_client.c | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/net/ceph/osd_client.c b/net/ceph/osd_client.c index 67f7430c775018..6d7d8c7d7d3fed 100644 --- a/net/ceph/osd_client.c +++ b/net/ceph/osd_client.c @@ -1588,6 +1588,7 @@ static enum calc_target_result calc_target(struct ceph_osd_client *osdc, struct ceph_pg_pool_info *pi; struct ceph_pg pgid, last_pgid; struct ceph_osds up, acting; + bool should_be_paused; bool is_read = t->flags & CEPH_OSD_FLAG_READ; bool is_write = t->flags & CEPH_OSD_FLAG_WRITE; bool force_resend = false; @@ -1656,10 +1657,16 @@ static enum calc_target_result calc_target(struct ceph_osd_client *osdc, &last_pgid)) force_resend = true; - if (t->paused && !target_should_be_paused(osdc, t, pi)) { - t->paused = false; + should_be_paused = target_should_be_paused(osdc, t, pi); + if (t->paused && !should_be_paused) { unpaused = true; } + if (t->paused != should_be_paused) { + dout("%s t %p paused %d -> %d\n", __func__, t, t->paused, + should_be_paused); + t->paused = should_be_paused; + } + legacy_change = ceph_pg_compare(&t->pgid, &pgid) || ceph_osds_changed(&t->acting, &acting, t->used_replica || any_change); From a1d420370a248cbd940e134ebf2db6d9e9d19b81 Mon Sep 17 00:00:00 2001 From: Ming Lei Date: Sat, 1 Nov 2025 21:31:16 +0800 Subject: [PATCH 2267/3902] ublk: reorder tag_set initialization before queue allocation commit 011af85ccd871526df36988c7ff20ca375fb804d upstream. Move ublk_add_tag_set() before ublk_init_queues() in the device initialization path. This allows us to use the blk-mq CPU-to-queue mapping established by the tag_set to determine the appropriate NUMA node for each queue allocation. The error handling paths are also reordered accordingly. Reviewed-by: Caleb Sander Mateos Signed-off-by: Ming Lei Signed-off-by: Jens Axboe [ Upstream commit 529d4d632788 ("ublk: implement NUMA-aware memory allocation") is ported to linux-6.18.y, but it depends on commit 011af85ccd87 ("ublk: reorder tag_set initialization before queue allocation"). kernel panic is reported on 6.18.y: https://github.com/ublk-org/ublksrv/issues/174 ] Signed-off-by: Greg Kroah-Hartman --- drivers/block/ublk_drv.c | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/drivers/block/ublk_drv.c b/drivers/block/ublk_drv.c index 23aba73d24dc2d..babb58d2dcaf7d 100644 --- a/drivers/block/ublk_drv.c +++ b/drivers/block/ublk_drv.c @@ -3280,17 +3280,17 @@ static int ublk_ctrl_add_dev(const struct ublksrv_ctrl_cmd *header) ub->dev_info.nr_hw_queues, nr_cpu_ids); ublk_align_max_io_size(ub); - ret = ublk_init_queues(ub); + ret = ublk_add_tag_set(ub); if (ret) goto out_free_dev_number; - ret = ublk_add_tag_set(ub); + ret = ublk_init_queues(ub); if (ret) - goto out_deinit_queues; + goto out_free_tag_set; ret = -EFAULT; if (copy_to_user(argp, &ub->dev_info, sizeof(info))) - goto out_free_tag_set; + goto out_deinit_queues; /* * Add the char dev so that ublksrv daemon can be setup. @@ -3299,10 +3299,10 @@ static int ublk_ctrl_add_dev(const struct ublksrv_ctrl_cmd *header) ret = ublk_add_chdev(ub); goto out_unlock; -out_free_tag_set: - blk_mq_free_tag_set(&ub->tag_set); out_deinit_queues: ublk_deinit_queues(ub); +out_free_tag_set: + blk_mq_free_tag_set(&ub->tag_set); out_free_dev_number: ublk_free_dev_number(ub); out_free_ub: From c88717b10792210ab48e9df8698a94f12550c220 Mon Sep 17 00:00:00 2001 From: Takashi Iwai Date: Wed, 10 Dec 2025 14:15:51 +0100 Subject: [PATCH 2268/3902] ALSA: hda: intel-dsp-config: Prefer legacy driver as fallback commit 161a0c617ab172bbcda7ce61803addeb2124dbff upstream. When config table entries don't match with the device to be probed, currently we fall back to SND_INTEL_DSP_DRIVER_ANY, which means to allow any drivers to bind with it. This was set so with the assumption (or hope) that all controller drivers should cover the devices generally, but in practice, this caused a problem as reported recently. Namely, when a specific kconfig for SOF isn't set for the modern Intel chips like Alderlake, a wrong driver (AVS) got probed and failed. This is because we have entries like: #if IS_ENABLED(CONFIG_SND_SOC_SOF_ALDERLAKE) /* Alder Lake / Raptor Lake */ { .flags = FLAG_SOF | FLAG_SOF_ONLY_IF_DMIC_OR_SOUNDWIRE, .device = PCI_DEVICE_ID_INTEL_HDA_ADL_S, }, .... #endif so this entry is effective only when CONFIG_SND_SOC_SOF_ALDERLAKE is set. If not set, there is no matching entry, hence it returns SND_INTEL_DSP_DRIVER_ANY as fallback. OTOH, if the kconfig is set, it explicitly falls back to SND_INTEL_DSP_DRIVER_LEGACY when no DMIC or SoundWire is found -- that was the working scenario. That being said, the current setup may be broken for modern Intel chips that are supposed to work with either SOF or legacy driver when the corresponding kconfig were missing. For addressing the problem above, this patch changes the fallback driver to the legacy driver, i.e. return SND_INTEL_DSP_DRIVER_LEGACY type as much as possible. When CONFIG_SND_HDA_INTEL is also disabled, the fallback is set to SND_INTEL_DSP_DRIVER_ANY type, just to be sure. Reported-by: Askar Safin Closes: https://lore.kernel.org/all/20251014034156.4480-1-safinaskar@gmail.com/ Tested-by: Askar Safin Reviewed-by: Peter Ujfalusi Signed-off-by: Takashi Iwai Link: https://patch.msgid.link/20251210131553.184404-1-tiwai@suse.de Cc: Askar Safin Signed-off-by: Greg Kroah-Hartman --- sound/hda/core/intel-dsp-config.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/sound/hda/core/intel-dsp-config.c b/sound/hda/core/intel-dsp-config.c index 2a9e35cddcf7c6..ddb349bc46bbd0 100644 --- a/sound/hda/core/intel-dsp-config.c +++ b/sound/hda/core/intel-dsp-config.c @@ -710,7 +710,8 @@ int snd_intel_dsp_driver_probe(struct pci_dev *pci) /* find the configuration for the specific device */ cfg = snd_intel_dsp_find_config(pci, config_table, ARRAY_SIZE(config_table)); if (!cfg) - return SND_INTEL_DSP_DRIVER_ANY; + return IS_ENABLED(CONFIG_SND_HDA_INTEL) ? + SND_INTEL_DSP_DRIVER_LEGACY : SND_INTEL_DSP_DRIVER_ANY; if (cfg->flags & FLAG_SOF) { if (cfg->flags & FLAG_SOF_ONLY_IF_SOUNDWIRE && From 8ad9e930fb910da2daf82f7dc0080ff4c8882826 Mon Sep 17 00:00:00 2001 From: Yang Li Date: Wed, 16 Oct 2024 17:56:26 +0800 Subject: [PATCH 2269/3902] csky: fix csky_cmpxchg_fixup not working [ Upstream commit 809ef03d6d21d5fea016bbf6babeec462e37e68c ] In the csky_cmpxchg_fixup function, it is incorrect to use the global variable csky_cmpxchg_stw to determine the address where the exception occurred.The global variable csky_cmpxchg_stw stores the opcode at the time of the exception, while &csky_cmpxchg_stw shows the address where the exception occurred. Signed-off-by: Yang Li Signed-off-by: Guo Ren Signed-off-by: Sasha Levin --- arch/csky/mm/fault.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/arch/csky/mm/fault.c b/arch/csky/mm/fault.c index a6ca7dff421532..7ff40110898500 100644 --- a/arch/csky/mm/fault.c +++ b/arch/csky/mm/fault.c @@ -45,8 +45,8 @@ static inline void csky_cmpxchg_fixup(struct pt_regs *regs) if (trap_no(regs) != VEC_TLBMODIFIED) return; - if (instruction_pointer(regs) == csky_cmpxchg_stw) - instruction_pointer_set(regs, csky_cmpxchg_ldw); + if (instruction_pointer(regs) == (unsigned long)&csky_cmpxchg_stw) + instruction_pointer_set(regs, (unsigned long)&csky_cmpxchg_ldw); return; } #endif From 9f53cfb9f4c20739bcff20cd52790fb1561d8f85 Mon Sep 17 00:00:00 2001 From: Sebastian Andrzej Siewior Date: Tue, 11 Nov 2025 16:54:37 +0100 Subject: [PATCH 2270/3902] ARM: 9461/1: Disable HIGHPTE on PREEMPT_RT kernels [ Upstream commit fedadc4137234c3d00c4785eeed3e747fe9036ae ] gup_pgd_range() is invoked with disabled interrupts and invokes __kmap_local_page_prot() via pte_offset_map(), gup_p4d_range(). With HIGHPTE enabled, __kmap_local_page_prot() invokes kmap_high_get() which uses a spinlock_t via lock_kmap_any(). This leads to an sleeping-while-atomic error on PREEMPT_RT because spinlock_t becomes a sleeping lock and must not be acquired in atomic context. The loop in map_new_virtual() uses wait_queue_head_t for wake up which also is using a spinlock_t. Since HIGHPTE is rarely needed at all, turn it off for PREEMPT_RT to allow the use of get_user_pages_fast(). [arnd: rework patch to turn off HIGHPTE instead of HAVE_PAST_GUP] Co-developed-by: Arnd Bergmann Acked-by: Linus Walleij Reviewed-by: Arnd Bergmann Signed-off-by: Sebastian Andrzej Siewior Signed-off-by: Russell King (Oracle) Signed-off-by: Sasha Levin --- arch/arm/Kconfig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/arch/arm/Kconfig b/arch/arm/Kconfig index 4fb985b76e97f7..70cd3b5b5a0599 100644 --- a/arch/arm/Kconfig +++ b/arch/arm/Kconfig @@ -1215,7 +1215,7 @@ config HIGHMEM config HIGHPTE bool "Allocate 2nd-level pagetables from highmem" if EXPERT - depends on HIGHMEM + depends on HIGHMEM && !PREEMPT_RT default y help The VM uses one page of physical memory for each page table. From fea0f86e46133c612a69de2e48fe8772128c70a9 Mon Sep 17 00:00:00 2001 From: Sam James Date: Fri, 5 Dec 2025 08:14:57 +0000 Subject: [PATCH 2271/3902] alpha: don't reference obsolete termio struct for TC* constants [ Upstream commit 9aeed9041929812a10a6d693af050846942a1d16 ] Similar in nature to ab107276607af90b13a5994997e19b7b9731e251. glibc-2.42 drops the legacy termio struct, but the ioctls.h header still defines some TC* constants in terms of termio (via sizeof). Hardcode the values instead. This fixes building Python for example, which falls over like: ./Modules/termios.c:1119:16: error: invalid application of 'sizeof' to incomplete type 'struct termio' Link: https://bugs.gentoo.org/961769 Link: https://bugs.gentoo.org/962600 Signed-off-by: Sam James Reviewed-by: Magnus Lindholm Link: https://lore.kernel.org/r/6ebd3451908785cad53b50ca6bc46cfe9d6bc03c.1764922497.git.sam@gentoo.org Signed-off-by: Magnus Lindholm Signed-off-by: Sasha Levin --- arch/alpha/include/uapi/asm/ioctls.h | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/arch/alpha/include/uapi/asm/ioctls.h b/arch/alpha/include/uapi/asm/ioctls.h index 971311605288fa..a09d04b49cc658 100644 --- a/arch/alpha/include/uapi/asm/ioctls.h +++ b/arch/alpha/include/uapi/asm/ioctls.h @@ -23,10 +23,10 @@ #define TCSETSW _IOW('t', 21, struct termios) #define TCSETSF _IOW('t', 22, struct termios) -#define TCGETA _IOR('t', 23, struct termio) -#define TCSETA _IOW('t', 24, struct termio) -#define TCSETAW _IOW('t', 25, struct termio) -#define TCSETAF _IOW('t', 28, struct termio) +#define TCGETA 0x40127417 +#define TCSETA 0x80127418 +#define TCSETAW 0x80127419 +#define TCSETAF 0x8012741c #define TCSBRK _IO('t', 29) #define TCXONC _IO('t', 30) From 232948cf600fba69aff36b25d85ef91a73a35756 Mon Sep 17 00:00:00 2001 From: Mikulas Patocka Date: Fri, 14 Nov 2025 16:54:01 +0100 Subject: [PATCH 2272/3902] dm-verity: disable recursive forward error correction [ Upstream commit d9f3e47d3fae0c101d9094bc956ed24e7a0ee801 ] There are two problems with the recursive correction: 1. It may cause denial-of-service. In fec_read_bufs, there is a loop that has 253 iterations. For each iteration, we may call verity_hash_for_block recursively. There is a limit of 4 nested recursions - that means that there may be at most 253^4 (4 billion) iterations. Red Hat QE team actually created an image that pushes dm-verity to this limit - and this image just makes the udev-worker process get stuck in the 'D' state. 2. It doesn't work. In fec_read_bufs we store data into the variable "fio->bufs", but fio bufs is shared between recursive invocations, if "verity_hash_for_block" invoked correction recursively, it would overwrite partially filled fio->bufs. Signed-off-by: Mikulas Patocka Reported-by: Guangwu Zhang Reviewed-by: Sami Tolvanen Reviewed-by: Eric Biggers Signed-off-by: Sasha Levin --- drivers/md/dm-verity-fec.c | 4 +--- drivers/md/dm-verity-fec.h | 3 --- drivers/md/dm-verity-target.c | 2 +- 3 files changed, 2 insertions(+), 7 deletions(-) diff --git a/drivers/md/dm-verity-fec.c b/drivers/md/dm-verity-fec.c index 72047b47a7a0a3..e41bde1d3b15b8 100644 --- a/drivers/md/dm-verity-fec.c +++ b/drivers/md/dm-verity-fec.c @@ -413,10 +413,8 @@ int verity_fec_decode(struct dm_verity *v, struct dm_verity_io *io, if (!verity_fec_is_enabled(v)) return -EOPNOTSUPP; - if (fio->level >= DM_VERITY_FEC_MAX_RECURSION) { - DMWARN_LIMIT("%s: FEC: recursion too deep", v->data_dev->name); + if (fio->level) return -EIO; - } fio->level++; diff --git a/drivers/md/dm-verity-fec.h b/drivers/md/dm-verity-fec.h index 09123a61295387..ec37e607cb3f09 100644 --- a/drivers/md/dm-verity-fec.h +++ b/drivers/md/dm-verity-fec.h @@ -23,9 +23,6 @@ #define DM_VERITY_FEC_BUF_MAX \ (1 << (PAGE_SHIFT - DM_VERITY_FEC_BUF_RS_BITS)) -/* maximum recursion level for verity_fec_decode */ -#define DM_VERITY_FEC_MAX_RECURSION 4 - #define DM_VERITY_OPT_FEC_DEV "use_fec_from_device" #define DM_VERITY_OPT_FEC_BLOCKS "fec_blocks" #define DM_VERITY_OPT_FEC_START "fec_start" diff --git a/drivers/md/dm-verity-target.c b/drivers/md/dm-verity-target.c index 66a00a8ccb398b..c8695c079cfe08 100644 --- a/drivers/md/dm-verity-target.c +++ b/drivers/md/dm-verity-target.c @@ -1690,7 +1690,7 @@ static struct target_type verity_target = { .name = "verity", /* Note: the LSMs depend on the singleton and immutable features */ .features = DM_TARGET_SINGLETON | DM_TARGET_IMMUTABLE, - .version = {1, 12, 0}, + .version = {1, 13, 0}, .module = THIS_MODULE, .ctr = verity_ctr, .dtr = verity_dtr, From 6930c0229fb3b3561619c82425712a2928f72be5 Mon Sep 17 00:00:00 2001 From: Mikulas Patocka Date: Mon, 1 Dec 2025 22:13:10 +0100 Subject: [PATCH 2273/3902] dm-snapshot: fix 'scheduling while atomic' on real-time kernels [ Upstream commit 8581b19eb2c5ccf06c195d3b5468c3c9d17a5020 ] There is reported 'scheduling while atomic' bug when using dm-snapshot on real-time kernels. The reason for the bug is that the hlist_bl code does preempt_disable() when taking the lock and the kernel attempts to take other spinlocks while holding the hlist_bl lock. Fix this by converting a hlist_bl spinlock into a regular spinlock. Signed-off-by: Mikulas Patocka Reported-by: Jiping Ma Signed-off-by: Sasha Levin --- drivers/md/dm-exception-store.h | 2 +- drivers/md/dm-snap.c | 73 +++++++++++++++------------------ 2 files changed, 35 insertions(+), 40 deletions(-) diff --git a/drivers/md/dm-exception-store.h b/drivers/md/dm-exception-store.h index b6797663753818..061b4d3108132c 100644 --- a/drivers/md/dm-exception-store.h +++ b/drivers/md/dm-exception-store.h @@ -29,7 +29,7 @@ typedef sector_t chunk_t; * chunk within the device. */ struct dm_exception { - struct hlist_bl_node hash_list; + struct hlist_node hash_list; chunk_t old_chunk; chunk_t new_chunk; diff --git a/drivers/md/dm-snap.c b/drivers/md/dm-snap.c index f40c18da400007..dbd148967de425 100644 --- a/drivers/md/dm-snap.c +++ b/drivers/md/dm-snap.c @@ -40,10 +40,15 @@ static const char dm_snapshot_merge_target_name[] = "snapshot-merge"; #define DM_TRACKED_CHUNK_HASH(x) ((unsigned long)(x) & \ (DM_TRACKED_CHUNK_HASH_SIZE - 1)) +struct dm_hlist_head { + struct hlist_head head; + spinlock_t lock; +}; + struct dm_exception_table { uint32_t hash_mask; unsigned int hash_shift; - struct hlist_bl_head *table; + struct dm_hlist_head *table; }; struct dm_snapshot { @@ -628,8 +633,8 @@ static uint32_t exception_hash(struct dm_exception_table *et, chunk_t chunk); /* Lock to protect access to the completed and pending exception hash tables. */ struct dm_exception_table_lock { - struct hlist_bl_head *complete_slot; - struct hlist_bl_head *pending_slot; + spinlock_t *complete_slot; + spinlock_t *pending_slot; }; static void dm_exception_table_lock_init(struct dm_snapshot *s, chunk_t chunk, @@ -638,20 +643,20 @@ static void dm_exception_table_lock_init(struct dm_snapshot *s, chunk_t chunk, struct dm_exception_table *complete = &s->complete; struct dm_exception_table *pending = &s->pending; - lock->complete_slot = &complete->table[exception_hash(complete, chunk)]; - lock->pending_slot = &pending->table[exception_hash(pending, chunk)]; + lock->complete_slot = &complete->table[exception_hash(complete, chunk)].lock; + lock->pending_slot = &pending->table[exception_hash(pending, chunk)].lock; } static void dm_exception_table_lock(struct dm_exception_table_lock *lock) { - hlist_bl_lock(lock->complete_slot); - hlist_bl_lock(lock->pending_slot); + spin_lock_nested(lock->complete_slot, 1); + spin_lock_nested(lock->pending_slot, 2); } static void dm_exception_table_unlock(struct dm_exception_table_lock *lock) { - hlist_bl_unlock(lock->pending_slot); - hlist_bl_unlock(lock->complete_slot); + spin_unlock(lock->pending_slot); + spin_unlock(lock->complete_slot); } static int dm_exception_table_init(struct dm_exception_table *et, @@ -661,13 +666,15 @@ static int dm_exception_table_init(struct dm_exception_table *et, et->hash_shift = hash_shift; et->hash_mask = size - 1; - et->table = kvmalloc_array(size, sizeof(struct hlist_bl_head), + et->table = kvmalloc_array(size, sizeof(struct dm_hlist_head), GFP_KERNEL); if (!et->table) return -ENOMEM; - for (i = 0; i < size; i++) - INIT_HLIST_BL_HEAD(et->table + i); + for (i = 0; i < size; i++) { + INIT_HLIST_HEAD(&et->table[i].head); + spin_lock_init(&et->table[i].lock); + } return 0; } @@ -675,16 +682,17 @@ static int dm_exception_table_init(struct dm_exception_table *et, static void dm_exception_table_exit(struct dm_exception_table *et, struct kmem_cache *mem) { - struct hlist_bl_head *slot; + struct dm_hlist_head *slot; struct dm_exception *ex; - struct hlist_bl_node *pos, *n; + struct hlist_node *pos; int i, size; size = et->hash_mask + 1; for (i = 0; i < size; i++) { slot = et->table + i; - hlist_bl_for_each_entry_safe(ex, pos, n, slot, hash_list) { + hlist_for_each_entry_safe(ex, pos, &slot->head, hash_list) { + hlist_del(&ex->hash_list); kmem_cache_free(mem, ex); cond_resched(); } @@ -700,7 +708,7 @@ static uint32_t exception_hash(struct dm_exception_table *et, chunk_t chunk) static void dm_remove_exception(struct dm_exception *e) { - hlist_bl_del(&e->hash_list); + hlist_del(&e->hash_list); } /* @@ -710,12 +718,11 @@ static void dm_remove_exception(struct dm_exception *e) static struct dm_exception *dm_lookup_exception(struct dm_exception_table *et, chunk_t chunk) { - struct hlist_bl_head *slot; - struct hlist_bl_node *pos; + struct hlist_head *slot; struct dm_exception *e; - slot = &et->table[exception_hash(et, chunk)]; - hlist_bl_for_each_entry(e, pos, slot, hash_list) + slot = &et->table[exception_hash(et, chunk)].head; + hlist_for_each_entry(e, slot, hash_list) if (chunk >= e->old_chunk && chunk <= e->old_chunk + dm_consecutive_chunk_count(e)) return e; @@ -762,18 +769,17 @@ static void free_pending_exception(struct dm_snap_pending_exception *pe) static void dm_insert_exception(struct dm_exception_table *eh, struct dm_exception *new_e) { - struct hlist_bl_head *l; - struct hlist_bl_node *pos; + struct hlist_head *l; struct dm_exception *e = NULL; - l = &eh->table[exception_hash(eh, new_e->old_chunk)]; + l = &eh->table[exception_hash(eh, new_e->old_chunk)].head; /* Add immediately if this table doesn't support consecutive chunks */ if (!eh->hash_shift) goto out; /* List is ordered by old_chunk */ - hlist_bl_for_each_entry(e, pos, l, hash_list) { + hlist_for_each_entry(e, l, hash_list) { /* Insert after an existing chunk? */ if (new_e->old_chunk == (e->old_chunk + dm_consecutive_chunk_count(e) + 1) && @@ -804,13 +810,13 @@ static void dm_insert_exception(struct dm_exception_table *eh, * Either the table doesn't support consecutive chunks or slot * l is empty. */ - hlist_bl_add_head(&new_e->hash_list, l); + hlist_add_head(&new_e->hash_list, l); } else if (new_e->old_chunk < e->old_chunk) { /* Add before an existing exception */ - hlist_bl_add_before(&new_e->hash_list, &e->hash_list); + hlist_add_before(&new_e->hash_list, &e->hash_list); } else { /* Add to l's tail: e is the last exception in this slot */ - hlist_bl_add_behind(&new_e->hash_list, &e->hash_list); + hlist_add_behind(&new_e->hash_list, &e->hash_list); } } @@ -820,7 +826,6 @@ static void dm_insert_exception(struct dm_exception_table *eh, */ static int dm_add_exception(void *context, chunk_t old, chunk_t new) { - struct dm_exception_table_lock lock; struct dm_snapshot *s = context; struct dm_exception *e; @@ -833,17 +838,7 @@ static int dm_add_exception(void *context, chunk_t old, chunk_t new) /* Consecutive_count is implicitly initialised to zero */ e->new_chunk = new; - /* - * Although there is no need to lock access to the exception tables - * here, if we don't then hlist_bl_add_head(), called by - * dm_insert_exception(), will complain about accessing the - * corresponding list without locking it first. - */ - dm_exception_table_lock_init(s, old, &lock); - - dm_exception_table_lock(&lock); dm_insert_exception(&s->complete, e); - dm_exception_table_unlock(&lock); return 0; } @@ -873,7 +868,7 @@ static int calc_max_buckets(void) /* use a fixed size of 2MB */ unsigned long mem = 2 * 1024 * 1024; - mem /= sizeof(struct hlist_bl_head); + mem /= sizeof(struct dm_hlist_head); return mem; } From d82f6d1d43b316264c5c9d11f2ad99e7048fe498 Mon Sep 17 00:00:00 2001 From: Scott Mayhew Date: Mon, 3 Nov 2025 10:44:15 -0500 Subject: [PATCH 2274/3902] NFSv4: ensure the open stateid seqid doesn't go backwards [ Upstream commit 2e47c3cc64b44b0b06cd68c2801db92ff143f2b2 ] We have observed an NFSv4 client receiving a LOCK reply with a status of NFS4ERR_OLD_STATEID and subsequently retrying the LOCK request with an earlier seqid value in the stateid. As this was for a new lockowner, that would imply that nfs_set_open_stateid_locked() had updated the open stateid seqid with an earlier value. Looking at nfs_set_open_stateid_locked(), if the incoming seqid is out of sequence, the task will sleep on the state->waitq for up to 5 seconds. If the task waits for the full 5 seconds, then after finishing the wait it'll update the open stateid seqid with whatever value the incoming seqid has. If there are multiple waiters in this scenario, then the last one to perform said update may not be the one with the highest seqid. Add a check to ensure that the seqid can only be incremented, and add a tracepoint to indicate when old seqids are skipped. Signed-off-by: Scott Mayhew Reviewed-by: Benjamin Coddington Signed-off-by: Trond Myklebust Signed-off-by: Sasha Levin --- fs/nfs/nfs4proc.c | 13 +++++++++++-- fs/nfs/nfs4trace.h | 1 + 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/fs/nfs/nfs4proc.c b/fs/nfs/nfs4proc.c index 6f4e14fb7b9b8c..3b436ba2ed3bff 100644 --- a/fs/nfs/nfs4proc.c +++ b/fs/nfs/nfs4proc.c @@ -1780,8 +1780,17 @@ static void nfs_set_open_stateid_locked(struct nfs4_state *state, if (nfs_stateid_is_sequential(state, stateid)) break; - if (status) - break; + if (status) { + if (nfs4_stateid_match_other(stateid, &state->open_stateid) && + !nfs4_stateid_is_newer(stateid, &state->open_stateid)) { + trace_nfs4_open_stateid_update_skip(state->inode, + stateid, status); + return; + } else { + break; + } + } + /* Rely on seqids for serialisation with NFSv4.0 */ if (!nfs4_has_session(NFS_SERVER(state->inode)->nfs_client)) break; diff --git a/fs/nfs/nfs4trace.h b/fs/nfs/nfs4trace.h index 9776d220cec33f..6285128e631a5a 100644 --- a/fs/nfs/nfs4trace.h +++ b/fs/nfs/nfs4trace.h @@ -1353,6 +1353,7 @@ DEFINE_NFS4_INODE_STATEID_EVENT(nfs4_setattr); DEFINE_NFS4_INODE_STATEID_EVENT(nfs4_delegreturn); DEFINE_NFS4_INODE_STATEID_EVENT(nfs4_open_stateid_update); DEFINE_NFS4_INODE_STATEID_EVENT(nfs4_open_stateid_update_wait); +DEFINE_NFS4_INODE_STATEID_EVENT(nfs4_open_stateid_update_skip); DEFINE_NFS4_INODE_STATEID_EVENT(nfs4_close_stateid_update_wait); DECLARE_EVENT_CLASS(nfs4_getattr_event, From 8e73e0ee4530a3569e5285a06ac3b0f8d825c72e Mon Sep 17 00:00:00 2001 From: Krzysztof Kozlowski Date: Wed, 3 Dec 2025 15:16:45 +0100 Subject: [PATCH 2275/3902] ASoC: rockchip: Fix Wvoid-pointer-to-enum-cast warning (again) [ Upstream commit 57d508b5f718730f74b11e0dc9609ac7976802d1 ] 'version' is an enum, thus cast of pointer on 64-bit compile test with clang W=1 causes: rockchip_pdm.c:583:17: error: cast to smaller integer type 'enum rk_pdm_version' from 'const void *' [-Werror,-Wvoid-pointer-to-enum-cast] This was already fixed in commit 49a4a8d12612 ("ASoC: rockchip: Fix Wvoid-pointer-to-enum-cast warning") but then got bad in commit 9958d85968ed ("ASoC: Use device_get_match_data()"). Discussion on LKML also pointed out that 'uintptr_t' is not the correct type and either 'kernel_ulong_t' or 'unsigned long' should be used, with several arguments towards the latter [1]. Link: https://lore.kernel.org/r/CAMuHMdX7t=mabqFE5O-Cii3REMuyaePHmqX+j_mqyrn6XXzsoA@mail.gmail.com/ [1] Signed-off-by: Krzysztof Kozlowski Link: https://patch.msgid.link/20251203141644.106459-2-krzysztof.kozlowski@oss.qualcomm.com Signed-off-by: Mark Brown Signed-off-by: Sasha Levin --- sound/soc/rockchip/rockchip_pdm.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sound/soc/rockchip/rockchip_pdm.c b/sound/soc/rockchip/rockchip_pdm.c index c1ee470ec6079d..c69cdd6f24994c 100644 --- a/sound/soc/rockchip/rockchip_pdm.c +++ b/sound/soc/rockchip/rockchip_pdm.c @@ -580,7 +580,7 @@ static int rockchip_pdm_probe(struct platform_device *pdev) if (!pdm) return -ENOMEM; - pdm->version = (enum rk_pdm_version)device_get_match_data(&pdev->dev); + pdm->version = (unsigned long)device_get_match_data(&pdev->dev); if (pdm->version == RK_PDM_RK3308) { pdm->reset = devm_reset_control_get(&pdev->dev, "pdm-m"); if (IS_ERR(pdm->reset)) From ab775cc784f698ced8506baf7003e73fb3935c6c Mon Sep 17 00:00:00 2001 From: Trond Myklebust Date: Fri, 28 Nov 2025 18:56:46 -0500 Subject: [PATCH 2276/3902] NFS: Fix up the automount fs_context to use the correct cred [ Upstream commit a2a8fc27dd668e7562b5326b5ed2f1604cb1e2e9 ] When automounting, the fs_context should be fixed up to use the cred from the parent filesystem, since the operation is just extending the namespace. Authorisation to enter that namespace will already have been provided by the preceding lookup. Signed-off-by: Trond Myklebust Signed-off-by: Sasha Levin --- fs/nfs/namespace.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/fs/nfs/namespace.c b/fs/nfs/namespace.c index 9e4d94f41fc674..af9be0c5f51630 100644 --- a/fs/nfs/namespace.c +++ b/fs/nfs/namespace.c @@ -170,6 +170,11 @@ struct vfsmount *nfs_d_automount(struct path *path) if (!ctx->clone_data.fattr) goto out_fc; + if (fc->cred != server->cred) { + put_cred(fc->cred); + fc->cred = get_cred(server->cred); + } + if (fc->net_ns != client->cl_net) { put_net(fc->net_ns); fc->net_ns = get_net(client->cl_net); From 93ee5471731b8ac93927ec725f6969026b7fcb47 Mon Sep 17 00:00:00 2001 From: Stefan Binding Date: Fri, 5 Dec 2025 15:05:55 +0000 Subject: [PATCH 2277/3902] ALSA: hda/realtek: Add support for ASUS UM3406GA [ Upstream commit 826c0b1ed09e5335abcae07292440ce72346e578 ] Laptops use 2 CS35L41 Amps with HDA, using External boost, with I2C Signed-off-by: Stefan Binding Link: https://patch.msgid.link/20251205150614.49590-3-sbinding@opensource.cirrus.com Signed-off-by: Takashi Iwai Signed-off-by: Sasha Levin --- sound/hda/codecs/realtek/alc269.c | 1 + 1 file changed, 1 insertion(+) diff --git a/sound/hda/codecs/realtek/alc269.c b/sound/hda/codecs/realtek/alc269.c index b3582e390dfa35..eea8399e32588e 100644 --- a/sound/hda/codecs/realtek/alc269.c +++ b/sound/hda/codecs/realtek/alc269.c @@ -6761,6 +6761,7 @@ static const struct hda_quirk alc269_fixup_tbl[] = { SND_PCI_QUIRK(0x1043, 0x1517, "Asus Zenbook UX31A", ALC269VB_FIXUP_ASUS_ZENBOOK_UX31A), SND_PCI_QUIRK(0x1043, 0x1533, "ASUS GV302XA/XJ/XQ/XU/XV/XI", ALC287_FIXUP_CS35L41_I2C_2), SND_PCI_QUIRK(0x1043, 0x1573, "ASUS GZ301VV/VQ/VU/VJ/VA/VC/VE/VVC/VQC/VUC/VJC/VEC/VCC", ALC285_FIXUP_ASUS_HEADSET_MIC), + SND_PCI_QUIRK(0x1043, 0x1584, "ASUS UM3406GA ", ALC287_FIXUP_CS35L41_I2C_2), SND_PCI_QUIRK(0x1043, 0x1652, "ASUS ROG Zephyrus Do 15 SE", ALC289_FIXUP_ASUS_ZEPHYRUS_DUAL_SPK), SND_PCI_QUIRK(0x1043, 0x1662, "ASUS GV301QH", ALC294_FIXUP_ASUS_DUAL_SPK), SND_PCI_QUIRK(0x1043, 0x1663, "ASUS GU603ZI/ZJ/ZQ/ZU/ZV", ALC285_FIXUP_ASUS_HEADSET_MIC), From f1ae589310e1aa58255e8b02c87a6ae97443ccb3 Mon Sep 17 00:00:00 2001 From: Rosen Penev Date: Sat, 8 Nov 2025 09:40:47 -0800 Subject: [PATCH 2278/3902] drm/amd/display: shrink struct members [ Upstream commit 7329417fc9ac128729c3a092b006c8f1fd0d04a6 ] On a 32-bit ARM system, the audio_decoder struct ends up being too large for dp_retrain_link_dp_test. link_dp_cts.c:157:1: error: the frame size of 1328 bytes is larger than 1280 bytes [-Werror=frame-larger-than=] This is mitigated by shrinking the members of the struct and avoids having to deal with dynamic allocation. feed_back_divider is assigned but otherwise unused. Remove both. pixel_repetition looks like it should be a bool since it's only ever assigned to 1. But there are checks for 2 and 4. Reduce to uint8_t. Remove ss_percentage_divider. Unused. Shrink refresh_rate as it gets assigned to at most a 3 digit integer value. Signed-off-by: Rosen Penev Reviewed-by: Alex Hung Signed-off-by: Alex Deucher (cherry picked from commit 3849efdc7888d537f09c3dcfaea4b3cd377a102e) Signed-off-by: Sasha Levin --- .../drm/amd/display/dc/hwss/dce110/dce110_hwseq.c | 3 --- drivers/gpu/drm/amd/display/include/audio_types.h | 12 +++++------- 2 files changed, 5 insertions(+), 10 deletions(-) diff --git a/drivers/gpu/drm/amd/display/dc/hwss/dce110/dce110_hwseq.c b/drivers/gpu/drm/amd/display/dc/hwss/dce110/dce110_hwseq.c index ebc220b29d1427..b94fec83474008 100644 --- a/drivers/gpu/drm/amd/display/dc/hwss/dce110/dce110_hwseq.c +++ b/drivers/gpu/drm/amd/display/dc/hwss/dce110/dce110_hwseq.c @@ -1458,9 +1458,6 @@ void build_audio_output( state->clk_mgr); } - audio_output->pll_info.feed_back_divider = - pipe_ctx->pll_settings.feedback_divider; - audio_output->pll_info.dto_source = translate_to_dto_source( pipe_ctx->stream_res.tg->inst + 1); diff --git a/drivers/gpu/drm/amd/display/include/audio_types.h b/drivers/gpu/drm/amd/display/include/audio_types.h index e4a26143f14c94..6699ad4fa825e3 100644 --- a/drivers/gpu/drm/amd/display/include/audio_types.h +++ b/drivers/gpu/drm/amd/display/include/audio_types.h @@ -47,15 +47,15 @@ struct audio_crtc_info { uint32_t h_total; uint32_t h_active; uint32_t v_active; - uint32_t pixel_repetition; uint32_t requested_pixel_clock_100Hz; /* in 100Hz */ uint32_t calculated_pixel_clock_100Hz; /* in 100Hz */ - uint32_t refresh_rate; + uint32_t dsc_bits_per_pixel; + uint32_t dsc_num_slices; enum dc_color_depth color_depth; enum dc_pixel_encoding pixel_encoding; + uint16_t refresh_rate; + uint8_t pixel_repetition; bool interlaced; - uint32_t dsc_bits_per_pixel; - uint32_t dsc_num_slices; }; struct azalia_clock_info { uint32_t pixel_clock_in_10khz; @@ -78,11 +78,9 @@ enum audio_dto_source { struct audio_pll_info { uint32_t audio_dto_source_clock_in_khz; - uint32_t feed_back_divider; + uint32_t ss_percentage; enum audio_dto_source dto_source; bool ss_enabled; - uint32_t ss_percentage; - uint32_t ss_percentage_divider; }; struct audio_channel_associate_info { From e2c1462639663ffc3066f37bd7faf5a9baf9612c Mon Sep 17 00:00:00 2001 From: ChenXiaoSong Date: Sun, 7 Dec 2025 09:22:53 +0800 Subject: [PATCH 2279/3902] smb/client: fix NT_STATUS_UNABLE_TO_FREE_VM value [ Upstream commit 9f99caa8950a76f560a90074e3a4b93cfa8b3d84 ] This was reported by the KUnit tests in the later patches. See MS-ERREF 2.3.1 STATUS_UNABLE_TO_FREE_VM. Keep it consistent with the value in the documentation. Signed-off-by: ChenXiaoSong Acked-by: Paulo Alcantara (Red Hat) Signed-off-by: Steve French Signed-off-by: Sasha Levin --- fs/smb/client/nterr.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fs/smb/client/nterr.h b/fs/smb/client/nterr.h index 180602c22355e1..e3a607b45e7197 100644 --- a/fs/smb/client/nterr.h +++ b/fs/smb/client/nterr.h @@ -70,7 +70,7 @@ extern const struct nt_err_code_struct nt_errs[]; #define NT_STATUS_NO_MEMORY 0xC0000000 | 0x0017 #define NT_STATUS_CONFLICTING_ADDRESSES 0xC0000000 | 0x0018 #define NT_STATUS_NOT_MAPPED_VIEW 0xC0000000 | 0x0019 -#define NT_STATUS_UNABLE_TO_FREE_VM 0x80000000 | 0x001a +#define NT_STATUS_UNABLE_TO_FREE_VM 0xC0000000 | 0x001a #define NT_STATUS_UNABLE_TO_DELETE_SECTION 0xC0000000 | 0x001b #define NT_STATUS_INVALID_SYSTEM_SERVICE 0xC0000000 | 0x001c #define NT_STATUS_ILLEGAL_INSTRUCTION 0xC0000000 | 0x001d From d4959a7900afef6da232bc6321419f1f93750e20 Mon Sep 17 00:00:00 2001 From: ChenXiaoSong Date: Sun, 7 Dec 2025 09:17:57 +0800 Subject: [PATCH 2280/3902] smb/client: fix NT_STATUS_DEVICE_DOOR_OPEN value [ Upstream commit b2b50fca34da5ec231008edba798ddf92986bd7f ] This was reported by the KUnit tests in the later patches. See MS-ERREF 2.3.1 STATUS_DEVICE_DOOR_OPEN. Keep it consistent with the value in the documentation. Signed-off-by: ChenXiaoSong Acked-by: Paulo Alcantara (Red Hat) Signed-off-by: Steve French Signed-off-by: Sasha Levin --- fs/smb/client/nterr.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fs/smb/client/nterr.h b/fs/smb/client/nterr.h index e3a607b45e7197..b3516c71cff77d 100644 --- a/fs/smb/client/nterr.h +++ b/fs/smb/client/nterr.h @@ -44,7 +44,7 @@ extern const struct nt_err_code_struct nt_errs[]; #define NT_STATUS_NO_DATA_DETECTED 0x8000001c #define NT_STATUS_STOPPED_ON_SYMLINK 0x8000002d #define NT_STATUS_DEVICE_REQUIRES_CLEANING 0x80000288 -#define NT_STATUS_DEVICE_DOOR_OPEN 0x80000288 +#define NT_STATUS_DEVICE_DOOR_OPEN 0x80000289 #define NT_STATUS_UNSUCCESSFUL 0xC0000000 | 0x0001 #define NT_STATUS_NOT_IMPLEMENTED 0xC0000000 | 0x0002 #define NT_STATUS_INVALID_INFO_CLASS 0xC0000000 | 0x0003 From aba03b371632a26bee1cdded2cbeb80d84dff06e Mon Sep 17 00:00:00 2001 From: ChenXiaoSong Date: Sun, 7 Dec 2025 09:13:06 +0800 Subject: [PATCH 2281/3902] smb/client: fix NT_STATUS_NO_DATA_DETECTED value [ Upstream commit a1237c203f1757480dc2f3b930608ee00072d3cc ] This was reported by the KUnit tests in the later patches. See MS-ERREF 2.3.1 STATUS_NO_DATA_DETECTED. Keep it consistent with the value in the documentation. Signed-off-by: ChenXiaoSong Acked-by: Paulo Alcantara (Red Hat) Signed-off-by: Steve French Signed-off-by: Sasha Levin --- fs/smb/client/nterr.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fs/smb/client/nterr.h b/fs/smb/client/nterr.h index b3516c71cff77d..09263c91d07a48 100644 --- a/fs/smb/client/nterr.h +++ b/fs/smb/client/nterr.h @@ -41,7 +41,7 @@ extern const struct nt_err_code_struct nt_errs[]; #define NT_STATUS_MEDIA_CHANGED 0x8000001c #define NT_STATUS_END_OF_MEDIA 0x8000001e #define NT_STATUS_MEDIA_CHECK 0x80000020 -#define NT_STATUS_NO_DATA_DETECTED 0x8000001c +#define NT_STATUS_NO_DATA_DETECTED 0x80000022 #define NT_STATUS_STOPPED_ON_SYMLINK 0x8000002d #define NT_STATUS_DEVICE_REQUIRES_CLEANING 0x80000288 #define NT_STATUS_DEVICE_DOOR_OPEN 0x80000289 From 382028023669fea7eb2fd7c13e259abdbc2a8dbf Mon Sep 17 00:00:00 2001 From: Suganath Prabu S Date: Thu, 20 Nov 2025 12:49:55 +0530 Subject: [PATCH 2282/3902] scsi: mpi3mr: Prevent duplicate SAS/SATA device entries in channel 1 [ Upstream commit 4588e65cfd66fc8bbd9969ea730db39b60a36a30 ] Avoid scanning SAS/SATA devices in channel 1 when SAS transport is enabled, as the SAS/SATA devices are exposed through channel 0. Signed-off-by: Suganath Prabu S Signed-off-by: Ranjan Kumar Link: https://lore.kernel.org/stable/20251120071955.463475-1-suganath-prabu.subramani%40broadcom.com Link: https://patch.msgid.link/20251120071955.463475-1-suganath-prabu.subramani@broadcom.com Signed-off-by: Martin K. Petersen Signed-off-by: Sasha Levin --- drivers/scsi/mpi3mr/mpi3mr.h | 4 ++-- drivers/scsi/mpi3mr/mpi3mr_os.c | 4 +++- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/drivers/scsi/mpi3mr/mpi3mr.h b/drivers/scsi/mpi3mr/mpi3mr.h index 6742684e2990a7..31d68c151b2076 100644 --- a/drivers/scsi/mpi3mr/mpi3mr.h +++ b/drivers/scsi/mpi3mr/mpi3mr.h @@ -56,8 +56,8 @@ extern struct list_head mrioc_list; extern int prot_mask; extern atomic64_t event_counter; -#define MPI3MR_DRIVER_VERSION "8.15.0.5.50" -#define MPI3MR_DRIVER_RELDATE "12-August-2025" +#define MPI3MR_DRIVER_VERSION "8.15.0.5.51" +#define MPI3MR_DRIVER_RELDATE "18-November-2025" #define MPI3MR_DRIVER_NAME "mpi3mr" #define MPI3MR_DRIVER_LICENSE "GPL" diff --git a/drivers/scsi/mpi3mr/mpi3mr_os.c b/drivers/scsi/mpi3mr/mpi3mr_os.c index b88633e1efe275..d4ca878d088697 100644 --- a/drivers/scsi/mpi3mr/mpi3mr_os.c +++ b/drivers/scsi/mpi3mr/mpi3mr_os.c @@ -1184,6 +1184,8 @@ static void mpi3mr_update_tgtdev(struct mpi3mr_ioc *mrioc, if (is_added == true) tgtdev->io_throttle_enabled = (flags & MPI3_DEVICE0_FLAGS_IO_THROTTLING_REQUIRED) ? 1 : 0; + if (!mrioc->sas_transport_enabled) + tgtdev->non_stl = 1; switch (flags & MPI3_DEVICE0_FLAGS_MAX_WRITE_SAME_MASK) { case MPI3_DEVICE0_FLAGS_MAX_WRITE_SAME_256_LB: @@ -4844,7 +4846,7 @@ static int mpi3mr_target_alloc(struct scsi_target *starget) spin_lock_irqsave(&mrioc->tgtdev_lock, flags); if (starget->channel == mrioc->scsi_device_channel) { tgt_dev = __mpi3mr_get_tgtdev_by_perst_id(mrioc, starget->id); - if (tgt_dev && !tgt_dev->is_hidden) { + if (tgt_dev && !tgt_dev->is_hidden && tgt_dev->non_stl) { scsi_tgt_priv_data->starget = starget; scsi_tgt_priv_data->dev_handle = tgt_dev->dev_handle; scsi_tgt_priv_data->perst_id = tgt_dev->perst_id; From 3a96f7f2677af43cb668089904c037b7e5584869 Mon Sep 17 00:00:00 2001 From: Wen Xiong Date: Tue, 28 Oct 2025 09:24:26 -0500 Subject: [PATCH 2283/3902] scsi: ipr: Enable/disable IRQD_NO_BALANCING during reset [ Upstream commit 6ac3484fb13b2fc7f31cfc7f56093e7d0ce646a5 ] A dynamic remove/add storage adapter test hits EEH on PowerPC: EEH: [c00000000004f75c] __eeh_send_failure_event+0x7c/0x160 EEH: [c000000000048444] eeh_dev_check_failure.part.0+0x254/0x650 EEH: [c008000001650678] eeh_readl+0x60/0x90 [ipr] EEH: [c00800000166746c] ipr_cancel_op+0x2b8/0x524 [ipr] EEH: [c008000001656524] ipr_eh_abort+0x6c/0x130 [ipr] EEH: [c000000000ab0d20] scmd_eh_abort_handler+0x140/0x440 EEH: [c00000000017e558] process_one_work+0x298/0x590 EEH: [c00000000017eef8] worker_thread+0xa8/0x620 EEH: [c00000000018be34] kthread+0x124/0x130 EEH: [c00000000000cd64] ret_from_kernel_thread+0x5c/0x64 A PCIe bus trace reveals that a vector of MSI-X is cleared to 0 by irqbalance daemon. If we disable irqbalance daemon, we won't see the issue. With debug enabled in ipr driver: [ 44.103071] ipr: Entering __ipr_remove [ 44.103083] ipr: Entering ipr_initiate_ioa_bringdown [ 44.103091] ipr: Entering ipr_reset_shutdown_ioa [ 44.103099] ipr: Leaving ipr_reset_shutdown_ioa [ 44.103105] ipr: Leaving ipr_initiate_ioa_bringdown [ 44.149918] ipr: Entering ipr_reset_ucode_download [ 44.149935] ipr: Entering ipr_reset_alert [ 44.150032] ipr: Entering ipr_reset_start_timer [ 44.150038] ipr: Leaving ipr_reset_alert [ 44.244343] scsi 1:2:3:0: alua: Detached [ 44.254300] ipr: Entering ipr_reset_start_bist [ 44.254320] ipr: Entering ipr_reset_start_timer [ 44.254325] ipr: Leaving ipr_reset_start_bist [ 44.364329] scsi 1:2:4:0: alua: Detached [ 45.134341] scsi 1:2:5:0: alua: Detached [ 45.860949] ipr: Entering ipr_reset_shutdown_ioa [ 45.860962] ipr: Leaving ipr_reset_shutdown_ioa [ 45.860966] ipr: Entering ipr_reset_alert [ 45.861028] ipr: Entering ipr_reset_start_timer [ 45.861035] ipr: Leaving ipr_reset_alert [ 45.964302] ipr: Entering ipr_reset_start_bist [ 45.964309] ipr: Entering ipr_reset_start_timer [ 45.964313] ipr: Leaving ipr_reset_start_bist [ 46.264301] ipr: Entering ipr_reset_bist_done [ 46.264309] ipr: Leaving ipr_reset_bist_done During adapter reset, ipr device driver blocks config space access but can't block MMIO access for MSI-X entries. There is very small window: irqbalance daemon kicks in during adapter reset before ipr driver calls pci_restore_state(pdev) to restore MSI-X table. irqbalance daemon reads back all 0 for that MSI-X vector in __pci_read_msi_msg(). irqbalance daemon: msi_domain_set_affinity() ->irq_chip_set_affinity_patent() ->xive_irq_set_affinity() ->irq_chip_compose_msi_msg() ->pseries_msi_compose_msg() ->__pci_read_msi_msg(): read all 0 since didn't call pci_restore_state ->irq_chip_write_msi_msg() -> pci_write_msg_msi(): write 0 to the msix vector entry When ipr driver calls pci_restore_state(pdev) in ipr_reset_restore_cfg_space(), the MSI-X vector entry has been cleared by irqbalance daemon in pci_write_msg_msix(). pci_restore_state() ->__pci_restore_msix_state() Below is the MSI-X table for ipr adapter after irqbalance daemon kicked in during adapter reset: Dump MSIx table: index=0 address_lo=c800 address_hi=10000000 msg_data=0 Dump MSIx table: index=1 address_lo=c810 address_hi=10000000 msg_data=0 Dump MSIx table: index=2 address_lo=c820 address_hi=10000000 msg_data=0 Dump MSIx table: index=3 address_lo=c830 address_hi=10000000 msg_data=0 Dump MSIx table: index=4 address_lo=c840 address_hi=10000000 msg_data=0 Dump MSIx table: index=5 address_lo=c850 address_hi=10000000 msg_data=0 Dump MSIx table: index=6 address_lo=c860 address_hi=10000000 msg_data=0 Dump MSIx table: index=7 address_lo=c870 address_hi=10000000 msg_data=0 Dump MSIx table: index=8 address_lo=0 address_hi=0 msg_data=0 ---------> Hit EEH since msix vector of index=8 are 0 Dump MSIx table: index=9 address_lo=c890 address_hi=10000000 msg_data=0 Dump MSIx table: index=10 address_lo=c8a0 address_hi=10000000 msg_data=0 Dump MSIx table: index=11 address_lo=c8b0 address_hi=10000000 msg_data=0 Dump MSIx table: index=12 address_lo=c8c0 address_hi=10000000 msg_data=0 Dump MSIx table: index=13 address_lo=c8d0 address_hi=10000000 msg_data=0 Dump MSIx table: index=14 address_lo=c8e0 address_hi=10000000 msg_data=0 Dump MSIx table: index=15 address_lo=c8f0 address_hi=10000000 msg_data=0 [ 46.264312] ipr: Entering ipr_reset_restore_cfg_space [ 46.267439] ipr: Entering ipr_fail_all_ops [ 46.267447] ipr: Leaving ipr_fail_all_ops [ 46.267451] ipr: Leaving ipr_reset_restore_cfg_space [ 46.267454] ipr: Entering ipr_ioa_bringdown_done [ 46.267458] ipr: Leaving ipr_ioa_bringdown_done [ 46.267467] ipr: Entering ipr_worker_thread [ 46.267470] ipr: Leaving ipr_worker_thread IRQ balancing is not required during adapter reset. Enable "IRQ_NO_BALANCING" flag before starting adapter reset and disable it after calling pci_restore_state(). The irqbalance daemon is disabled for this short period of time (~2s). Co-developed-by: Kyle Mahlkuch Signed-off-by: Kyle Mahlkuch Signed-off-by: Wen Xiong Link: https://patch.msgid.link/20251028142427.3969819-2-wenxiong@linux.ibm.com Signed-off-by: Martin K. Petersen Signed-off-by: Sasha Levin --- drivers/scsi/ipr.c | 28 +++++++++++++++++++++++++++- 1 file changed, 27 insertions(+), 1 deletion(-) diff --git a/drivers/scsi/ipr.c b/drivers/scsi/ipr.c index 44214884deaf50..d62bb7d0e4164e 100644 --- a/drivers/scsi/ipr.c +++ b/drivers/scsi/ipr.c @@ -61,8 +61,8 @@ #include #include #include +#include #include -#include #include #include #include @@ -7843,6 +7843,30 @@ static int ipr_dump_mailbox_wait(struct ipr_cmnd *ipr_cmd) return IPR_RC_JOB_RETURN; } +/** + * ipr_set_affinity_nobalance + * @ioa_cfg: ipr_ioa_cfg struct for an ipr device + * @flag: bool + * true: ensable "IRQ_NO_BALANCING" bit for msix interrupt + * false: disable "IRQ_NO_BALANCING" bit for msix interrupt + * Description: This function will be called to disable/enable + * "IRQ_NO_BALANCING" to avoid irqbalance daemon + * kicking in during adapter reset. + **/ +static void ipr_set_affinity_nobalance(struct ipr_ioa_cfg *ioa_cfg, bool flag) +{ + int irq, i; + + for (i = 0; i < ioa_cfg->nvectors; i++) { + irq = pci_irq_vector(ioa_cfg->pdev, i); + + if (flag) + irq_set_status_flags(irq, IRQ_NO_BALANCING); + else + irq_clear_status_flags(irq, IRQ_NO_BALANCING); + } +} + /** * ipr_reset_restore_cfg_space - Restore PCI config space. * @ipr_cmd: ipr command struct @@ -7867,6 +7891,7 @@ static int ipr_reset_restore_cfg_space(struct ipr_cmnd *ipr_cmd) return IPR_RC_JOB_CONTINUE; } + ipr_set_affinity_nobalance(ioa_cfg, false); ipr_fail_all_ops(ioa_cfg); if (ioa_cfg->sis64) { @@ -7946,6 +7971,7 @@ static int ipr_reset_start_bist(struct ipr_cmnd *ipr_cmd) rc = pci_write_config_byte(ioa_cfg->pdev, PCI_BIST, PCI_BIST_START); if (rc == PCIBIOS_SUCCESSFUL) { + ipr_set_affinity_nobalance(ioa_cfg, true); ipr_cmd->job_step = ipr_reset_bist_done; ipr_reset_start_timer(ipr_cmd, IPR_WAIT_FOR_BIST_TIMEOUT); rc = IPR_RC_JOB_RETURN; From f1e2d448ccc1d7db2e626a9edcb9ab0930fb6724 Mon Sep 17 00:00:00 2001 From: Brian Kao Date: Wed, 12 Nov 2025 06:32:02 +0000 Subject: [PATCH 2284/3902] scsi: ufs: core: Fix EH failure after W-LUN resume error [ Upstream commit b4bb6daf4ac4d4560044ecdd81e93aa2f6acbb06 ] When a W-LUN resume fails, its parent devices in the SCSI hierarchy, including the scsi_target, may be runtime suspended. Subsequently, the error handler in ufshcd_recover_pm_error() fails to set the W-LUN device back to active because the parent target is not active. This results in the following errors: google-ufshcd 3c2d0000.ufs: ufshcd_err_handler started; HBA state eh_fatal; ... ufs_device_wlun 0:0:0:49488: START_STOP failed for power mode: 1, result 40000 ufs_device_wlun 0:0:0:49488: ufshcd_wl_runtime_resume failed: -5 ... ufs_device_wlun 0:0:0:49488: runtime PM trying to activate child device 0:0:0:49488 but parent (target0:0:0) is not active Address this by: 1. Ensuring the W-LUN's parent scsi_target is runtime resumed before attempting to set the W-LUN to active within ufshcd_recover_pm_error(). 2. Explicitly checking for power.runtime_error on the HBA and W-LUN devices before calling pm_runtime_set_active() to clear the error state. 3. Adding pm_runtime_get_sync(hba->dev) in ufshcd_err_handling_prepare() to ensure the HBA itself is active during error recovery, even if a child device resume failed. These changes ensure the device power states are managed correctly during error recovery. Signed-off-by: Brian Kao Tested-by: Brian Kao Reviewed-by: Bart Van Assche Link: https://patch.msgid.link/20251112063214.1195761-1-powenkao@google.com Signed-off-by: Martin K. Petersen Signed-off-by: Sasha Levin --- drivers/ufs/core/ufshcd.c | 36 ++++++++++++++++++++++++++++-------- 1 file changed, 28 insertions(+), 8 deletions(-) diff --git a/drivers/ufs/core/ufshcd.c b/drivers/ufs/core/ufshcd.c index ba1bb3953cf69a..022810b524e96b 100644 --- a/drivers/ufs/core/ufshcd.c +++ b/drivers/ufs/core/ufshcd.c @@ -6498,6 +6498,11 @@ static void ufshcd_clk_scaling_suspend(struct ufs_hba *hba, bool suspend) static void ufshcd_err_handling_prepare(struct ufs_hba *hba) { + /* + * A WLUN resume failure could potentially lead to the HBA being + * runtime suspended, so take an extra reference on hba->dev. + */ + pm_runtime_get_sync(hba->dev); ufshcd_rpm_get_sync(hba); if (pm_runtime_status_suspended(&hba->ufs_device_wlun->sdev_gendev) || hba->is_sys_suspended) { @@ -6537,6 +6542,7 @@ static void ufshcd_err_handling_unprepare(struct ufs_hba *hba) if (ufshcd_is_clkscaling_supported(hba)) ufshcd_clk_scaling_suspend(hba, false); ufshcd_rpm_put(hba); + pm_runtime_put(hba->dev); } static inline bool ufshcd_err_handling_should_stop(struct ufs_hba *hba) @@ -6551,28 +6557,42 @@ static inline bool ufshcd_err_handling_should_stop(struct ufs_hba *hba) #ifdef CONFIG_PM static void ufshcd_recover_pm_error(struct ufs_hba *hba) { + struct scsi_target *starget = hba->ufs_device_wlun->sdev_target; struct Scsi_Host *shost = hba->host; struct scsi_device *sdev; struct request_queue *q; - int ret; + bool resume_sdev_queues = false; hba->is_sys_suspended = false; + /* - * Set RPM status of wlun device to RPM_ACTIVE, - * this also clears its runtime error. + * Ensure the parent's error status is cleared before proceeding + * to the child, as the parent must be active to activate the child. */ - ret = pm_runtime_set_active(&hba->ufs_device_wlun->sdev_gendev); + if (hba->dev->power.runtime_error) { + /* hba->dev has no functional parent thus simplily set RPM_ACTIVE */ + pm_runtime_set_active(hba->dev); + resume_sdev_queues = true; + } + + if (hba->ufs_device_wlun->sdev_gendev.power.runtime_error) { + /* + * starget, parent of wlun, might be suspended if wlun resume failed. + * Make sure parent is resumed before set child (wlun) active. + */ + pm_runtime_get_sync(&starget->dev); + pm_runtime_set_active(&hba->ufs_device_wlun->sdev_gendev); + pm_runtime_put_sync(&starget->dev); + resume_sdev_queues = true; + } - /* hba device might have a runtime error otherwise */ - if (ret) - ret = pm_runtime_set_active(hba->dev); /* * If wlun device had runtime error, we also need to resume those * consumer scsi devices in case any of them has failed to be * resumed due to supplier runtime resume failure. This is to unblock * blk_queue_enter in case there are bios waiting inside it. */ - if (!ret) { + if (resume_sdev_queues) { shost_for_each_device(sdev, shost) { q = sdev->request_queue; if (q->dev && (q->rpm_status == RPM_SUSPENDED || From 5f0fd06d7571abf0a56e7e291e05132efc416a82 Mon Sep 17 00:00:00 2001 From: Xingui Yang Date: Tue, 2 Dec 2025 14:56:27 +0800 Subject: [PATCH 2285/3902] scsi: Revert "scsi: libsas: Fix exp-attached device scan after probe failure scanned in again after probe failed" [ Upstream commit 278712d20bc8ec29d1ad6ef9bdae9000ef2c220c ] This reverts commit ab2068a6fb84751836a84c26ca72b3beb349619d. When probing the exp-attached sata device, libsas/libata will issue a hard reset in sas_probe_sata() -> ata_sas_async_probe(), then a broadcast event will be received after the disk probe fails, and this commit causes the probe will be re-executed on the disk, and a faulty disk may get into an indefinite loop of probe. Therefore, revert this commit, although it can fix some temporary issues with disk probe failure. Signed-off-by: Xingui Yang Reviewed-by: Jason Yan Reviewed-by: John Garry Link: https://patch.msgid.link/20251202065627.140361-1-yangxingui@huawei.com Signed-off-by: Martin K. Petersen Signed-off-by: Sasha Levin --- drivers/scsi/libsas/sas_internal.h | 14 -------------- 1 file changed, 14 deletions(-) diff --git a/drivers/scsi/libsas/sas_internal.h b/drivers/scsi/libsas/sas_internal.h index 6706f2be8d274b..da5408c701cddf 100644 --- a/drivers/scsi/libsas/sas_internal.h +++ b/drivers/scsi/libsas/sas_internal.h @@ -145,20 +145,6 @@ static inline void sas_fail_probe(struct domain_device *dev, const char *func, i func, dev->parent ? "exp-attached" : "direct-attached", SAS_ADDR(dev->sas_addr), err); - - /* - * If the device probe failed, the expander phy attached address - * needs to be reset so that the phy will not be treated as flutter - * in the next revalidation - */ - if (dev->parent && !dev_is_expander(dev->dev_type)) { - struct sas_phy *phy = dev->phy; - struct domain_device *parent = dev->parent; - struct ex_phy *ex_phy = &parent->ex_dev.ex_phy[phy->number]; - - memset(ex_phy->attached_sas_addr, 0, SAS_ADDR_SIZE); - } - sas_unregister_dev(dev->port, dev); } From 7d59377ada9f6986395f3d8a75eceec8ca49c083 Mon Sep 17 00:00:00 2001 From: Boris Burkov Date: Mon, 1 Dec 2025 12:47:14 -0800 Subject: [PATCH 2286/3902] btrfs: fix qgroup_snapshot_quick_inherit() squota bug [ Upstream commit 7ee19a59a75e3d5b9ec00499b86af8e2a46fbe86 ] qgroup_snapshot_quick_inherit() detects conditions where the snapshot destination would land in the same parent qgroup as the snapshot source subvolume. In this case we can avoid costly qgroup calculations and just add the nodesize of the new snapshot to the parent. However, in the case of squotas this is actually a double count, and also an undercount for deeper qgroup nestings. The following annotated script shows the issue: btrfs quota enable --simple "$mnt" # Create 2-level qgroup hierarchy btrfs qgroup create 2/100 "$mnt" # Q2 (level 2) btrfs qgroup create 1/100 "$mnt" # Q1 (level 1) btrfs qgroup assign 1/100 2/100 "$mnt" # Create base subvolume btrfs subvolume create "$mnt/base" >/dev/null base_id=$(btrfs subvolume show "$mnt/base" | grep 'Subvolume ID:' | awk '{print $3}') # Create intermediate snapshot and add to Q1 btrfs subvolume snapshot "$mnt/base" "$mnt/intermediate" >/dev/null inter_id=$(btrfs subvolume show "$mnt/intermediate" | grep 'Subvolume ID:' | awk '{print $3}') btrfs qgroup assign "0/$inter_id" 1/100 "$mnt" # Create working snapshot with --inherit (auto-adds to Q1) # src=intermediate (in only Q1) # dst=snap (inheriting only into Q1) # This double counts the 16k nodesize of the snapshot in Q1, and # undercounts it in Q2. btrfs subvolume snapshot -i 1/100 "$mnt/intermediate" "$mnt/snap" >/dev/null snap_id=$(btrfs subvolume show "$mnt/snap" | grep 'Subvolume ID:' | awk '{print $3}') # Fully complete snapshot creation sync # Delete working snapshot # Q1 and Q2 will lose the full snap usage btrfs subvolume delete "$mnt/snap" >/dev/null # Delete intermediate and remove from Q1 # Q1 and Q2 will lose the full intermediate usage btrfs qgroup remove "0/$inter_id" 1/100 "$mnt" btrfs subvolume delete "$mnt/intermediate" >/dev/null # Q1 should be at 0, but still has 16k. Q2 is "correct" at 0 (for now...) # Trigger cleaner, wait for deletions mount -o remount,sync=1 "$mnt" btrfs subvolume sync "$mnt" "$snap_id" btrfs subvolume sync "$mnt" "$inter_id" # Remove Q1 from Q2 # Frees 16k more from Q2, underflowing it to 16EiB btrfs qgroup remove 1/100 2/100 "$mnt" # And show the bad state: btrfs qgroup show -pc "$mnt" Qgroupid Referenced Exclusive Parent Child Path -------- ---------- --------- ------ ----- ---- 0/5 16.00KiB 16.00KiB - - 0/256 16.00KiB 16.00KiB - - base 1/100 16.00KiB 16.00KiB - - <0 member qgroups> 2/100 16.00EiB 16.00EiB - - <0 member qgroups> Fix this by simply not doing this quick inheritance with squotas. I suspect that it is also wrong in normal qgroups to not recurse up the qgroup tree in the quick inherit case, though other consistency checks will likely fix it anyway. Fixes: b20fe56cd285 ("btrfs: qgroup: allow quick inherit if snapshot is created and added to the same parent") Reviewed-by: Qu Wenruo Signed-off-by: Boris Burkov Signed-off-by: David Sterba Signed-off-by: Sasha Levin --- fs/btrfs/qgroup.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/fs/btrfs/qgroup.c b/fs/btrfs/qgroup.c index 31ad8580322a6e..7faaa777010d71 100644 --- a/fs/btrfs/qgroup.c +++ b/fs/btrfs/qgroup.c @@ -3247,6 +3247,9 @@ static int qgroup_snapshot_quick_inherit(struct btrfs_fs_info *fs_info, struct btrfs_qgroup_list *list; int nr_parents = 0; + if (btrfs_qgroup_mode(fs_info) != BTRFS_QGROUP_MODE_FULL) + return 0; + src = find_qgroup_rb(fs_info, srcid); if (!src) return -ENOENT; From 1ee62906cbd9a7d78aee765d15afa2b18aaf5e16 Mon Sep 17 00:00:00 2001 From: Qu Wenruo Date: Thu, 4 Dec 2025 14:38:23 +1030 Subject: [PATCH 2287/3902] btrfs: qgroup: update all parent qgroups when doing quick inherit [ Upstream commit 68d4b3fa18d72b7f649e83012e7e08f1881f6b75 ] [BUG] There is a bug that if a subvolume has multi-level parent qgroups, and is able to do a quick inherit, only the direct parent qgroup got updated: mkfs.btrfs -f -O quota $dev mount $dev $mnt btrfs subv create $mnt/subv1 btrfs qgroup create 1/100 $mnt btrfs qgroup create 2/100 $mnt btrfs qgroup assign 1/100 2/100 $mnt btrfs qgroup assign 0/256 1/100 $mnt btrfs qgroup show -p --sync $mnt Qgroupid Referenced Exclusive Parent Path -------- ---------- --------- ------ ---- 0/5 16.00KiB 16.00KiB - 0/256 16.00KiB 16.00KiB 1/100 subv1 1/100 16.00KiB 16.00KiB 2/100 2/100<1 member qgroup> 2/100 16.00KiB 16.00KiB - <0 member qgroups> btrfs subv snap -i 1/100 $mnt/subv1 $mnt/snap1 btrfs qgroup show -p --sync $mnt Qgroupid Referenced Exclusive Parent Path -------- ---------- --------- ------ ---- 0/5 16.00KiB 16.00KiB - 0/256 16.00KiB 16.00KiB 1/100 subv1 0/257 16.00KiB 16.00KiB 1/100 snap1 1/100 32.00KiB 32.00KiB 2/100 2/100<1 member qgroup> 2/100 16.00KiB 16.00KiB - <0 member qgroups> # Note that 2/100 is not updated, and qgroup numbers are inconsistent umount $mnt [CAUSE] If the snapshot source subvolume belongs to a parent qgroup, and the new snapshot target is also added to the new same parent qgroup, we allow a quick update without marking qgroup inconsistent. But that quick update only update the parent qgroup, without checking if there is any more parent qgroups. [FIX] Iterate through all parent qgroups during the quick inherit. Reported-by: Boris Burkov Fixes: b20fe56cd285 ("btrfs: qgroup: allow quick inherit if snapshot is created and added to the same parent") Reviewed-by: Boris Burkov Signed-off-by: Qu Wenruo Signed-off-by: David Sterba Signed-off-by: Sasha Levin --- fs/btrfs/qgroup.c | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/fs/btrfs/qgroup.c b/fs/btrfs/qgroup.c index 7faaa777010d71..febc22d1b64878 100644 --- a/fs/btrfs/qgroup.c +++ b/fs/btrfs/qgroup.c @@ -3244,7 +3244,10 @@ static int qgroup_snapshot_quick_inherit(struct btrfs_fs_info *fs_info, { struct btrfs_qgroup *src; struct btrfs_qgroup *parent; + struct btrfs_qgroup *qgroup; struct btrfs_qgroup_list *list; + LIST_HEAD(qgroup_list); + const u32 nodesize = fs_info->nodesize; int nr_parents = 0; if (btrfs_qgroup_mode(fs_info) != BTRFS_QGROUP_MODE_FULL) @@ -3284,8 +3287,19 @@ static int qgroup_snapshot_quick_inherit(struct btrfs_fs_info *fs_info, if (parent->excl != parent->rfer) return 1; - parent->excl += fs_info->nodesize; - parent->rfer += fs_info->nodesize; + qgroup_iterator_add(&qgroup_list, parent); + list_for_each_entry(qgroup, &qgroup_list, iterator) { + qgroup->rfer += nodesize; + qgroup->rfer_cmpr += nodesize; + qgroup->excl += nodesize; + qgroup->excl_cmpr += nodesize; + qgroup_dirty(fs_info, qgroup); + + /* Append parent qgroups to @qgroup_list. */ + list_for_each_entry(list, &qgroup->groups, next_group) + qgroup_iterator_add(&qgroup_list, list->group); + } + qgroup_iterator_clean(&qgroup_list); return 0; } From 99e057f3d3ef24b99a7b1d84e01dd1bd890098da Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miquel=20Sabat=C3=A9=20Sol=C3=A0?= Date: Tue, 21 Oct 2025 11:11:25 +0200 Subject: [PATCH 2288/3902] btrfs: fix NULL dereference on root when tracing inode eviction MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [ Upstream commit f157dd661339fc6f5f2b574fe2429c43bd309534 ] When evicting an inode the first thing we do is to setup tracing for it, which implies fetching the root's id. But in btrfs_evict_inode() the root might be NULL, as implied in the next check that we do in btrfs_evict_inode(). Hence, we either should set the ->root_objectid to 0 in case the root is NULL, or we move tracing setup after checking that the root is not NULL. Setting the rootid to 0 at least gives us the possibility to trace this call even in the case when the root is NULL, so that's the solution taken here. Fixes: 1abe9b8a138c ("Btrfs: add initial tracepoint support for btrfs") Reported-by: syzbot+d991fea1b4b23b1f6bf8@syzkaller.appspotmail.com Closes: https://syzkaller.appspot.com/bug?extid=d991fea1b4b23b1f6bf8 Signed-off-by: Miquel Sabaté Solà Reviewed-by: David Sterba Signed-off-by: David Sterba Signed-off-by: Sasha Levin --- include/trace/events/btrfs.h | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/include/trace/events/btrfs.h b/include/trace/events/btrfs.h index 7e418f065b945a..125bdc166bfed7 100644 --- a/include/trace/events/btrfs.h +++ b/include/trace/events/btrfs.h @@ -224,7 +224,8 @@ DECLARE_EVENT_CLASS(btrfs__inode, __entry->generation = BTRFS_I(inode)->generation; __entry->last_trans = BTRFS_I(inode)->last_trans; __entry->logged_trans = BTRFS_I(inode)->logged_trans; - __entry->root_objectid = btrfs_root_id(BTRFS_I(inode)->root); + __entry->root_objectid = BTRFS_I(inode)->root ? + btrfs_root_id(BTRFS_I(inode)->root) : 0; ), TP_printk_btrfs("root=%llu(%s) gen=%llu ino=%llu blocks=%llu " From c8385851a5435f4006281828d428e5d0b0bbf8af Mon Sep 17 00:00:00 2001 From: Leo Martins Date: Fri, 12 Dec 2025 17:26:26 -0800 Subject: [PATCH 2289/3902] btrfs: fix use-after-free warning in btrfs_get_or_create_delayed_node() [ Upstream commit 83f59076a1ae6f5c6845d6f7ed3a1a373d883684 ] Previously, btrfs_get_or_create_delayed_node() set the delayed_node's refcount before acquiring the root->delayed_nodes lock. Commit e8513c012de7 ("btrfs: implement ref_tracker for delayed_nodes") moved refcount_set inside the critical section, which means there is no longer a memory barrier between setting the refcount and setting btrfs_inode->delayed_node. Without that barrier, the stores to node->refs and btrfs_inode->delayed_node may become visible out of order. Another thread can then read btrfs_inode->delayed_node and attempt to increment a refcount that hasn't been set yet, leading to a refcounting bug and a use-after-free warning. The fix is to move refcount_set back to where it was to take advantage of the implicit memory barrier provided by lock acquisition. Because the allocations now happen outside of the lock's critical section, they can use GFP_NOFS instead of GFP_ATOMIC. Reported-by: kernel test robot Closes: https://lore.kernel.org/oe-lkp/202511262228.6dda231e-lkp@intel.com Fixes: e8513c012de7 ("btrfs: implement ref_tracker for delayed_nodes") Tested-by: kernel test robot Reviewed-by: Filipe Manana Signed-off-by: Leo Martins Signed-off-by: David Sterba Signed-off-by: Sasha Levin --- fs/btrfs/delayed-inode.c | 32 +++++++++++++++++--------------- 1 file changed, 17 insertions(+), 15 deletions(-) diff --git a/fs/btrfs/delayed-inode.c b/fs/btrfs/delayed-inode.c index 3df7b9d7fbe8d3..59b489d7e4b588 100644 --- a/fs/btrfs/delayed-inode.c +++ b/fs/btrfs/delayed-inode.c @@ -152,37 +152,39 @@ static struct btrfs_delayed_node *btrfs_get_or_create_delayed_node( return ERR_PTR(-ENOMEM); btrfs_init_delayed_node(node, root, ino); + /* Cached in the inode and can be accessed. */ + refcount_set(&node->refs, 2); + btrfs_delayed_node_ref_tracker_alloc(node, tracker, GFP_NOFS); + btrfs_delayed_node_ref_tracker_alloc(node, &node->inode_cache_tracker, GFP_NOFS); + /* Allocate and reserve the slot, from now it can return a NULL from xa_load(). */ ret = xa_reserve(&root->delayed_nodes, ino, GFP_NOFS); - if (ret == -ENOMEM) { - btrfs_delayed_node_ref_tracker_dir_exit(node); - kmem_cache_free(delayed_node_cache, node); - return ERR_PTR(-ENOMEM); - } + if (ret == -ENOMEM) + goto cleanup; + xa_lock(&root->delayed_nodes); ptr = xa_load(&root->delayed_nodes, ino); if (ptr) { /* Somebody inserted it, go back and read it. */ xa_unlock(&root->delayed_nodes); - btrfs_delayed_node_ref_tracker_dir_exit(node); - kmem_cache_free(delayed_node_cache, node); - node = NULL; - goto again; + goto cleanup; } ptr = __xa_store(&root->delayed_nodes, ino, node, GFP_ATOMIC); ASSERT(xa_err(ptr) != -EINVAL); ASSERT(xa_err(ptr) != -ENOMEM); ASSERT(ptr == NULL); - - /* Cached in the inode and can be accessed. */ - refcount_set(&node->refs, 2); - btrfs_delayed_node_ref_tracker_alloc(node, tracker, GFP_ATOMIC); - btrfs_delayed_node_ref_tracker_alloc(node, &node->inode_cache_tracker, GFP_ATOMIC); - btrfs_inode->delayed_node = node; xa_unlock(&root->delayed_nodes); return node; +cleanup: + btrfs_delayed_node_ref_tracker_free(node, tracker); + btrfs_delayed_node_ref_tracker_free(node, &node->inode_cache_tracker); + btrfs_delayed_node_ref_tracker_dir_exit(node); + kmem_cache_free(delayed_node_cache, node); + if (ret) + return ERR_PTR(ret); + goto again; } /* From f09b0f705bd7197863b90256ef533a6414d1db2c Mon Sep 17 00:00:00 2001 From: Zilin Guan Date: Wed, 31 Dec 2025 11:49:15 +0000 Subject: [PATCH 2290/3902] of: unittest: Fix memory leak in unittest_data_add() [ Upstream commit 235a1eb8d2dcc49a6cf0a5ee1aa85544a5d0054b ] In unittest_data_add(), if of_resolve_phandles() fails, the allocated unittest_data is not freed, leading to a memory leak. Fix this by using scope-based cleanup helper __free(kfree) for automatic resource cleanup. This ensures unittest_data is automatically freed when it goes out of scope in error paths. For the success path, use retain_and_null_ptr() to transfer ownership of the memory to the device tree and prevent double freeing. Fixes: 2eb46da2a760 ("of/selftest: Use the resolver to fixup phandles") Suggested-by: Rob Herring Co-developed-by: Jianhao Xu Signed-off-by: Jianhao Xu Signed-off-by: Zilin Guan Link: https://patch.msgid.link/20251231114915.234638-1-zilin@seu.edu.cn Signed-off-by: Rob Herring (Arm) Signed-off-by: Sasha Levin --- drivers/of/unittest.c | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/drivers/of/unittest.c b/drivers/of/unittest.c index 388e9ec2cccf83..3b773aaf9d0503 100644 --- a/drivers/of/unittest.c +++ b/drivers/of/unittest.c @@ -1985,7 +1985,6 @@ static void attach_node_and_children(struct device_node *np) */ static int __init unittest_data_add(void) { - void *unittest_data; void *unittest_data_align; struct device_node *unittest_data_node = NULL, *np; /* @@ -2004,7 +2003,7 @@ static int __init unittest_data_add(void) } /* creating copy */ - unittest_data = kmalloc(size + FDT_ALIGN_SIZE, GFP_KERNEL); + void *unittest_data __free(kfree) = kmalloc(size + FDT_ALIGN_SIZE, GFP_KERNEL); if (!unittest_data) return -ENOMEM; @@ -2014,12 +2013,10 @@ static int __init unittest_data_add(void) ret = of_fdt_unflatten_tree(unittest_data_align, NULL, &unittest_data_node); if (!ret) { pr_warn("%s: unflatten testcases tree failed\n", __func__); - kfree(unittest_data); return -ENODATA; } if (!unittest_data_node) { pr_warn("%s: testcases tree is empty\n", __func__); - kfree(unittest_data); return -ENODATA; } @@ -2038,7 +2035,6 @@ static int __init unittest_data_add(void) /* attach the sub-tree to live tree */ if (!of_root) { pr_warn("%s: no live tree to attach sub-tree\n", __func__); - kfree(unittest_data); rc = -ENODEV; goto unlock; } @@ -2059,6 +2055,8 @@ static int __init unittest_data_add(void) EXPECT_END(KERN_INFO, "Duplicate name in testcase-data, renamed to \"duplicate-name#1\""); + retain_and_null_ptr(unittest_data); + unlock: of_overlay_mutex_unlock(); From 0d4087c7486977960f82b764a4f4df19bba367a6 Mon Sep 17 00:00:00 2001 From: Wadim Egorov Date: Thu, 27 Nov 2025 13:27:31 +0100 Subject: [PATCH 2291/3902] arm64: dts: ti: k3-am642-phyboard-electra-peb-c-010: Fix icssg-prueth schema warning [ Upstream commit 05bbe52d0be5637dcd3c880348e3688f7ec64eb7 ] Reduce length of dma-names and dmas properties for icssg1-ethernet node to comply with ti,icssg-prueth schema constraints. The previous entries exceeded the allowed count and triggered dtschema warnings during validation. Fixes: e53fbf955ea7 ("arm64: dts: ti: k3-am642-phyboard-electra: Add PEB-C-010 Overlay") Signed-off-by: Wadim Egorov Link: https://patch.msgid.link/20251127122733.2523367-1-w.egorov@phytec.de Signed-off-by: Nishanth Menon Signed-off-by: Sasha Levin --- .../boot/dts/ti/k3-am642-phyboard-electra-peb-c-010.dtso | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/arch/arm64/boot/dts/ti/k3-am642-phyboard-electra-peb-c-010.dtso b/arch/arm64/boot/dts/ti/k3-am642-phyboard-electra-peb-c-010.dtso index 7fc73cfacadb8c..1176a52d560b71 100644 --- a/arch/arm64/boot/dts/ti/k3-am642-phyboard-electra-peb-c-010.dtso +++ b/arch/arm64/boot/dts/ti/k3-am642-phyboard-electra-peb-c-010.dtso @@ -30,13 +30,10 @@ <&main_pktdma 0xc206 15>, /* egress slice 1 */ <&main_pktdma 0xc207 15>, /* egress slice 1 */ <&main_pktdma 0x4200 15>, /* ingress slice 0 */ - <&main_pktdma 0x4201 15>, /* ingress slice 1 */ - <&main_pktdma 0x4202 0>, /* mgmnt rsp slice 0 */ - <&main_pktdma 0x4203 0>; /* mgmnt rsp slice 1 */ + <&main_pktdma 0x4201 15>; /* ingress slice 1 */ dma-names = "tx0-0", "tx0-1", "tx0-2", "tx0-3", "tx1-0", "tx1-1", "tx1-2", "tx1-3", - "rx0", "rx1", - "rxmgm0", "rxmgm1"; + "rx0", "rx1"; firmware-name = "ti-pruss/am65x-sr2-pru0-prueth-fw.elf", "ti-pruss/am65x-sr2-rtu0-prueth-fw.elf", From c5ebc38066ce10cb329b4f6630f8c74b5dc8496c Mon Sep 17 00:00:00 2001 From: Wadim Egorov Date: Thu, 27 Nov 2025 13:27:32 +0100 Subject: [PATCH 2292/3902] arm64: dts: ti: k3-am642-phyboard-electra-x27-gpio1-spi1-uart3: Fix schema warnings [ Upstream commit d876bb9353d87dee0ae620300106e8def189c785 ] Rename pinctrl nodes to comply with naming conventions required by pinctrl-single schema. Also, replace invalid integer assignment in SPI node with a boolean to align with omap-spi schema. Fixes: 638ab30ce4c6 ("arm64: dts: ti: am64-phyboard-electra: Add DT overlay for X27 connector") Signed-off-by: Wadim Egorov Link: https://patch.msgid.link/20251127122733.2523367-2-w.egorov@phytec.de Signed-off-by: Nishanth Menon Signed-off-by: Sasha Levin --- .../k3-am642-phyboard-electra-x27-gpio1-spi1-uart3.dtso | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/arch/arm64/boot/dts/ti/k3-am642-phyboard-electra-x27-gpio1-spi1-uart3.dtso b/arch/arm64/boot/dts/ti/k3-am642-phyboard-electra-x27-gpio1-spi1-uart3.dtso index 996c42ec4253e2..bea8efa3e90941 100644 --- a/arch/arm64/boot/dts/ti/k3-am642-phyboard-electra-x27-gpio1-spi1-uart3.dtso +++ b/arch/arm64/boot/dts/ti/k3-am642-phyboard-electra-x27-gpio1-spi1-uart3.dtso @@ -20,13 +20,13 @@ }; &main_pmx0 { - main_gpio1_exp_header_gpio_pins_default: main-gpio1-exp-header-gpio-pins-default { + main_gpio1_exp_header_gpio_pins_default: main-gpio1-exp-header-gpio-default-pins { pinctrl-single,pins = < AM64X_IOPAD(0x0220, PIN_INPUT, 7) /* (D14) SPI1_CS1.GPIO1_48 */ >; }; - main_spi1_pins_default: main-spi1-pins-default { + main_spi1_pins_default: main-spi1-default-pins { pinctrl-single,pins = < AM64X_IOPAD(0x0224, PIN_INPUT, 0) /* (C14) SPI1_CLK */ AM64X_IOPAD(0x021C, PIN_OUTPUT, 0) /* (B14) SPI1_CS0 */ @@ -35,7 +35,7 @@ >; }; - main_uart3_pins_default: main-uart3-pins-default { + main_uart3_pins_default: main-uart3-default-pins { pinctrl-single,pins = < AM64X_IOPAD(0x0048, PIN_INPUT, 2) /* (U20) GPMC0_AD3.UART3_RXD */ AM64X_IOPAD(0x004c, PIN_OUTPUT, 2) /* (U18) GPMC0_AD4.UART3_TXD */ @@ -52,7 +52,7 @@ &main_spi1 { pinctrl-names = "default"; pinctrl-0 = <&main_spi1_pins_default>; - ti,pindir-d0-out-d1-in = <1>; + ti,pindir-d0-out-d1-in; status = "okay"; }; From 93900292af115ac5a7a378c8f3b658d51863fce9 Mon Sep 17 00:00:00 2001 From: Wadim Egorov Date: Thu, 27 Nov 2025 13:27:33 +0100 Subject: [PATCH 2293/3902] arm64: dts: ti: k3-am62-lp-sk-nand: Rename pinctrls to fix schema warnings [ Upstream commit cf5e8adebe77917a4cc95e43e461cdbd857591ce ] Rename pinctrl nodes to comply with naming conventions required by pinctrl-single schema. Fixes: e569152274fec ("arm64: dts: ti: am62-lp-sk: Add overlay for NAND expansion card") Signed-off-by: Wadim Egorov Link: https://patch.msgid.link/20251127122733.2523367-3-w.egorov@phytec.de Signed-off-by: Nishanth Menon Signed-off-by: Sasha Levin --- arch/arm64/boot/dts/ti/k3-am62-lp-sk-nand.dtso | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/arch/arm64/boot/dts/ti/k3-am62-lp-sk-nand.dtso b/arch/arm64/boot/dts/ti/k3-am62-lp-sk-nand.dtso index 173ac60723b649..b4daa674eaa1e6 100644 --- a/arch/arm64/boot/dts/ti/k3-am62-lp-sk-nand.dtso +++ b/arch/arm64/boot/dts/ti/k3-am62-lp-sk-nand.dtso @@ -14,7 +14,7 @@ }; &main_pmx0 { - gpmc0_pins_default: gpmc0-pins-default { + gpmc0_pins_default: gpmc0-default-pins { pinctrl-single,pins = < AM62X_IOPAD(0x003c, PIN_INPUT, 0) /* (K19) GPMC0_AD0 */ AM62X_IOPAD(0x0040, PIN_INPUT, 0) /* (L19) GPMC0_AD1 */ From 43c2e3670334f6b9c77626bea03cfc363904b9b2 Mon Sep 17 00:00:00 2001 From: Alexandre Courbot Date: Wed, 5 Nov 2025 09:40:09 +0900 Subject: [PATCH 2294/3902] gpu: nova-core: select RUST_FW_LOADER_ABSTRACTIONS [ Upstream commit 3d3352e73a55a4ccf110f8b3419bbe2fbfd8a030 ] RUST_FW_LOADER_ABSTRACTIONS was depended on by NOVA_CORE, but NOVA_CORE is selected by DRM_NOVA. This creates a situation where, if DRM_NOVA is selected, NOVA_CORE gets enabled but not RUST_FW_LOADER_ABSTRACTIONS, which results in a build error. Since the firmware loader is an implementation detail of the driver, it should be enabled along with it, so change the "depends on" to a "select". Fixes: 54e6baf123fd ("gpu: nova-core: add initial driver stub") Closes: https://lore.kernel.org/oe-kbuild-all/202512061721.rxKGnt5q-lkp@intel.com/ Tested-by: Alyssa Ross Acked-by: Danilo Krummrich Link: https://patch.msgid.link/20251106-b4-select-rust-fw-v3-2-771172257755@nvidia.com Signed-off-by: Alexandre Courbot Signed-off-by: Sasha Levin --- drivers/gpu/nova-core/Kconfig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/gpu/nova-core/Kconfig b/drivers/gpu/nova-core/Kconfig index 20d3e6d0d796ef..527920f9c4d39c 100644 --- a/drivers/gpu/nova-core/Kconfig +++ b/drivers/gpu/nova-core/Kconfig @@ -3,7 +3,7 @@ config NOVA_CORE depends on 64BIT depends on PCI depends on RUST - depends on RUST_FW_LOADER_ABSTRACTIONS + select RUST_FW_LOADER_ABSTRACTIONS select AUXILIARY_BUS default n help From 737f341080f4c7f3e2455b54042b8c2a30742f18 Mon Sep 17 00:00:00 2001 From: Bartosz Golaszewski Date: Wed, 10 Dec 2025 06:50:26 +0100 Subject: [PATCH 2295/3902] gpio: it87: balance superio enter/exit calls in error path [ Upstream commit a05543d6b05ba998fdbb4b383319ae5121bb7407 ] We always call superio_enter() in it87_gpio_direction_out() but only call superio_exit() if the call to it87_gpio_set() succeeds. Move the label to balance the calls in error path as well. Fixes: ef877a159072 ("gpio: it87: use new line value setter callbacks") Reported-by: Daniel Gibson Closes: https://lore.kernel.org/all/bd0a00e3-9b8c-43e8-8772-e67b91f4c71e@gibson.sh/ Link: https://lore.kernel.org/r/20251210055026.23146-1-bartosz.golaszewski@oss.qualcomm.com Signed-off-by: Bartosz Golaszewski Signed-off-by: Sasha Levin --- drivers/gpio/gpio-it87.c | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/drivers/gpio/gpio-it87.c b/drivers/gpio/gpio-it87.c index 5d677bcfccf26f..2ad3c239367bcc 100644 --- a/drivers/gpio/gpio-it87.c +++ b/drivers/gpio/gpio-it87.c @@ -12,6 +12,7 @@ #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt +#include #include #include #include @@ -241,23 +242,17 @@ static int it87_gpio_direction_out(struct gpio_chip *chip, mask = 1 << (gpio_num % 8); group = (gpio_num / 8); - spin_lock(&it87_gpio->lock); + guard(spinlock)(&it87_gpio->lock); rc = superio_enter(); if (rc) - goto exit; + return rc; /* set the output enable bit */ superio_set_mask(mask, group + it87_gpio->output_base); rc = it87_gpio_set(chip, gpio_num, val); - if (rc) - goto exit; - superio_exit(); - -exit: - spin_unlock(&it87_gpio->lock); return rc; } From a5eeebb994bf6456d9bdc8af12d19c9ea38ed8b1 Mon Sep 17 00:00:00 2001 From: Thomas Fourier Date: Wed, 3 Dec 2025 17:56:35 +0100 Subject: [PATCH 2296/3902] HID: Intel-thc-hid: Intel-thc: fix dma_unmap_sg() nents value [ Upstream commit 0e13150c1a13a3a3d6184c24bfd080d5999945d1 ] The `dma_unmap_sg()` functions should be called with the same nents as the `dma_map_sg()`, not the value the map function returned. Save the number of entries in struct thc_dma_configuration. Fixes: a688404b2e20 ("HID: intel-thc-hid: intel-thc: Add THC DMA interfaces") Signed-off-by: Thomas Fourier Reviewed-by: Even Xu Reviewed-by: Andy Shevchenko Signed-off-by: Benjamin Tissoires Signed-off-by: Sasha Levin --- drivers/hid/intel-thc-hid/intel-thc/intel-thc-dma.c | 4 +++- drivers/hid/intel-thc-hid/intel-thc/intel-thc-dma.h | 2 ++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/drivers/hid/intel-thc-hid/intel-thc/intel-thc-dma.c b/drivers/hid/intel-thc-hid/intel-thc/intel-thc-dma.c index 82b8854843e052..a0c368aa7979c4 100644 --- a/drivers/hid/intel-thc-hid/intel-thc/intel-thc-dma.c +++ b/drivers/hid/intel-thc-hid/intel-thc/intel-thc-dma.c @@ -232,6 +232,7 @@ static int setup_dma_buffers(struct thc_device *dev, return 0; memset(config->sgls, 0, sizeof(config->sgls)); + memset(config->sgls_nent_pages, 0, sizeof(config->sgls_nent_pages)); memset(config->sgls_nent, 0, sizeof(config->sgls_nent)); cpu_addr = dma_alloc_coherent(dev->dev, prd_tbls_size, @@ -254,6 +255,7 @@ static int setup_dma_buffers(struct thc_device *dev, } count = dma_map_sg(dev->dev, config->sgls[i], nent, dir); + config->sgls_nent_pages[i] = nent; config->sgls_nent[i] = count; } @@ -299,7 +301,7 @@ static void release_dma_buffers(struct thc_device *dev, continue; dma_unmap_sg(dev->dev, config->sgls[i], - config->sgls_nent[i], + config->sgls_nent_pages[i], config->dir); sgl_free(config->sgls[i]); diff --git a/drivers/hid/intel-thc-hid/intel-thc/intel-thc-dma.h b/drivers/hid/intel-thc-hid/intel-thc/intel-thc-dma.h index 78917400492caf..541d33995baf36 100644 --- a/drivers/hid/intel-thc-hid/intel-thc/intel-thc-dma.h +++ b/drivers/hid/intel-thc-hid/intel-thc/intel-thc-dma.h @@ -91,6 +91,7 @@ struct thc_prd_table { * @dir: Direction of DMA for this config * @prd_tbls: PRD tables for current DMA * @sgls: Array of pointers to scatter-gather lists + * @sgls_nent_pages: Number of pages per scatter-gather list * @sgls_nent: Actual number of entries per scatter-gather list * @prd_tbl_num: Actual number of PRD tables * @max_packet_size: Size of the buffer needed for 1 DMA message (1 PRD table) @@ -107,6 +108,7 @@ struct thc_dma_configuration { struct thc_prd_table *prd_tbls; struct scatterlist *sgls[PRD_TABLES_NUM]; + u8 sgls_nent_pages[PRD_TABLES_NUM]; u8 sgls_nent[PRD_TABLES_NUM]; u8 prd_tbl_num; From b95a6e3b0d5d7c5643cd223dbbc80ee5ad923b07 Mon Sep 17 00:00:00 2001 From: Even Xu Date: Fri, 19 Dec 2025 09:14:38 +0800 Subject: [PATCH 2297/3902] HID: Intel-thc-hid: Intel-thc: Fix wrong register reading [ Upstream commit f39006965dd37e7be823dba6ca484adccc7a4dff ] Correct the read register for the setting of max input size and interrupt delay. Fixes: 22da60f0304b ("HID: Intel-thc-hid: Intel-thc: Introduce interrupt delay control") Fixes: 45e92a093099 ("HID: Intel-thc-hid: Intel-thc: Introduce max input size control") Signed-off-by: Even Xu Signed-off-by: Benjamin Tissoires Signed-off-by: Sasha Levin --- drivers/hid/intel-thc-hid/intel-thc/intel-thc-dev.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/hid/intel-thc-hid/intel-thc/intel-thc-dev.c b/drivers/hid/intel-thc-hid/intel-thc/intel-thc-dev.c index 636a6830650150..7e220a4c5ded7b 100644 --- a/drivers/hid/intel-thc-hid/intel-thc/intel-thc-dev.c +++ b/drivers/hid/intel-thc-hid/intel-thc/intel-thc-dev.c @@ -1593,7 +1593,7 @@ int thc_i2c_set_rx_max_size(struct thc_device *dev, u32 max_rx_size) if (!max_rx_size) return -EOPNOTSUPP; - ret = regmap_read(dev->thc_regmap, THC_M_PRT_SW_SEQ_STS_OFFSET, &val); + ret = regmap_read(dev->thc_regmap, THC_M_PRT_SPI_ICRRD_OPCODE_OFFSET, &val); if (ret) return ret; @@ -1662,7 +1662,7 @@ int thc_i2c_set_rx_int_delay(struct thc_device *dev, u32 delay_us) if (!delay_us) return -EOPNOTSUPP; - ret = regmap_read(dev->thc_regmap, THC_M_PRT_SW_SEQ_STS_OFFSET, &val); + ret = regmap_read(dev->thc_regmap, THC_M_PRT_SPI_ICRRD_OPCODE_OFFSET, &val); if (ret) return ret; From 5b5482c0e5ee740b35a70759d3582477aea8e8e4 Mon Sep 17 00:00:00 2001 From: David Howells Date: Sat, 20 Dec 2025 12:31:40 +0000 Subject: [PATCH 2298/3902] netfs: Fix early read unlock of page with EOF in middle [ Upstream commit 570ad253a3455a520f03c2136af8714bc780186d ] The read result collection for buffered reads seems to run ahead of the completion of subrequests under some circumstances, as can be seen in the following log snippet: 9p_client_res: client 18446612686390831168 response P9_TREAD tag 0 err 0 ... netfs_sreq: R=00001b55[1] DOWN TERM f=192 s=0 5fb2/5fb2 s=5 e=0 ... netfs_collect_folio: R=00001b55 ix=00004 r=4000-5000 t=4000/5fb2 netfs_folio: i=157f3 ix=00004-00004 read-done netfs_folio: i=157f3 ix=00004-00004 read-unlock netfs_collect_folio: R=00001b55 ix=00005 r=5000-5fb2 t=5000/5fb2 netfs_folio: i=157f3 ix=00005-00005 read-done netfs_folio: i=157f3 ix=00005-00005 read-unlock ... netfs_collect_stream: R=00001b55[0:] cto=5fb2 frn=ffffffff netfs_collect_state: R=00001b55 col=5fb2 cln=6000 n=c netfs_collect_stream: R=00001b55[0:] cto=5fb2 frn=ffffffff netfs_collect_state: R=00001b55 col=5fb2 cln=6000 n=8 ... netfs_sreq: R=00001b55[2] ZERO SUBMT f=000 s=5fb2 0/4e s=0 e=0 netfs_sreq: R=00001b55[2] ZERO TERM f=102 s=5fb2 4e/4e s=5 e=0 The 'cto=5fb2' indicates the collected file pos we've collected results to so far - but we still have 0x4e more bytes to go - so we shouldn't have collected folio ix=00005 yet. The 'ZERO' subreq that clears the tail happens after we unlock the folio, allowing the application to see the uncleared tail through mmap. The problem is that netfs_read_unlock_folios() will unlock a folio in which the amount of read results collected hits EOF position - but the ZERO subreq lies beyond that and so happens after. Fix this by changing the end check to always be the end of the folio and never the end of the file. In the future, I should look at clearing to the end of the folio here rather than adding a ZERO subreq to do this. On the other hand, the ZERO subreq can run in parallel with an async READ subreq. Further, the ZERO subreq may still be necessary to, say, handle extents in a ceph file that don't have any backing store and are thus implicitly all zeros. This can be reproduced by creating a file, the size of which doesn't align to a page boundary, e.g. 24998 (0x5fb2) bytes and then doing something like: xfs_io -c "mmap -r 0 0x6000" -c "madvise -d 0 0x6000" \ -c "mread -v 0 0x6000" /xfstest.test/x The last 0x4e bytes should all be 00, but if the tail hasn't been cleared yet, you may see rubbish there. This can be reproduced with kafs by modifying the kernel to disable the call to netfs_read_subreq_progress() and to stop afs_issue_read() from doing the async call for NETFS_READAHEAD. Reproduction can be made easier by inserting an mdelay(100) in netfs_issue_read() for the ZERO-subreq case. AFS and CIFS are normally unlikely to show this as they dispatch READ ops asynchronously, which allows the ZERO-subreq to finish first. 9P's READ op is completely synchronous, so the ZERO-subreq will always happen after. It isn't seen all the time, though, because the collection may be done in a worker thread. Reported-by: Christian Schoenebeck Link: https://lore.kernel.org/r/8622834.T7Z3S40VBb@weasel/ Signed-off-by: David Howells Link: https://patch.msgid.link/938162.1766233900@warthog.procyon.org.uk Fixes: e2d46f2ec332 ("netfs: Change the read result collector to only use one work item") Tested-by: Christian Schoenebeck Acked-by: Dominique Martinet Suggested-by: Dominique Martinet cc: Dominique Martinet cc: Christian Schoenebeck cc: v9fs@lists.linux.dev cc: netfs@lists.linux.dev cc: linux-fsdevel@vger.kernel.org Signed-off-by: Christian Brauner Signed-off-by: Sasha Levin --- fs/netfs/read_collect.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fs/netfs/read_collect.c b/fs/netfs/read_collect.c index a95e7aadafd072..7a0ffa675fb17a 100644 --- a/fs/netfs/read_collect.c +++ b/fs/netfs/read_collect.c @@ -137,7 +137,7 @@ static void netfs_read_unlock_folios(struct netfs_io_request *rreq, rreq->front_folio_order = order; fsize = PAGE_SIZE << order; fpos = folio_pos(folio); - fend = umin(fpos + fsize, rreq->i_size); + fend = fpos + fsize; trace_netfs_collect_folio(rreq, folio, fend, collected_to); From 7fd534abaa3972cf8b8d63654b36ee26ddfdefaf Mon Sep 17 00:00:00 2001 From: Louis-Alexis Eyraud Date: Wed, 3 Dec 2025 12:32:42 +0100 Subject: [PATCH 2299/3902] pinctrl: mediatek: mt8189: restore previous register base name array order [ Upstream commit fa917d3d570279dc3d699cbd947d0da0fde2e402 ] In mt8189-pinctrl driver, a previous commit changed the register base name array (mt8189_pinctrl_register_base_names) entry name and order to align it with the same name and order as the "mediatek,mt8189-pinctrl" devicetree bindings. The new order (by ascending register address) now causes an issue with MT8189 pinctrl configuration. MT8189 SoC has multiple base addresses for the pin configuration registers. Several constant data structures, declaring each pin configuration, are using PIN_FIELD_BASE() macro which i_base parameter indicates for a given pin the lookup index in the base register address array of the driver internal data for the configuration register read/write accesses. But in practice, this parameter is given a hardcoded numerical value that corresponds to the expected base register entry index in mt8189_pinctrl_register_base_names array. Since this array reordering, the i_base index matching is no more correct. So, in order to avoid modifying over a thousand of PIN_FIELD_BASE() calls, restore previous mt8189_pinctrl_register_base_names entry order. Fixes: 518919276c41 ("pinctrl: mediatek: mt8189: align register base names to dt-bindings ones") Signed-off-by: Louis-Alexis Eyraud Signed-off-by: Linus Walleij Signed-off-by: Sasha Levin --- drivers/pinctrl/mediatek/pinctrl-mt8189.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/pinctrl/mediatek/pinctrl-mt8189.c b/drivers/pinctrl/mediatek/pinctrl-mt8189.c index f6a3e584588b0e..cd4cdff309a12b 100644 --- a/drivers/pinctrl/mediatek/pinctrl-mt8189.c +++ b/drivers/pinctrl/mediatek/pinctrl-mt8189.c @@ -1642,7 +1642,7 @@ static const struct mtk_pin_reg_calc mt8189_reg_cals[PINCTRL_PIN_REG_MAX] = { }; static const char * const mt8189_pinctrl_register_base_names[] = { - "base", "lm", "rb0", "rb1", "bm0", "bm1", "bm2", "lt0", "lt1", "rt", + "base", "bm0", "bm1", "bm2", "lm", "lt0", "lt1", "rb0", "rb1", "rt", }; static const struct mtk_eint_hw mt8189_eint_hw = { From 42b66f4557a2b66268e1048e80f833859d17dc05 Mon Sep 17 00:00:00 2001 From: Harshita Bhilwaria Date: Wed, 17 Dec 2025 11:16:06 +0530 Subject: [PATCH 2300/3902] crypto: qat - fix duplicate restarting msg during AER error [ Upstream commit 961ac9d97be72267255f1ed841aabf6694b17454 ] The restarting message from PF to VF is sent twice during AER error handling: once from adf_error_detected() and again from adf_disable_sriov(). This causes userspace subservices to shutdown unexpectedly when they receive a duplicate restarting message after already being restarted. Avoid calling adf_pf2vf_notify_restarting() and adf_pf2vf_wait_for_restarting_complete() from adf_error_detected() so that the restarting msg is sent only once from PF to VF. Fixes: 9567d3dc760931 ("crypto: qat - improve aer error reset handling") Signed-off-by: Harshita Bhilwaria Reviewed-by: Giovanni Cabiddu Reviewed-by: Ahsan Atta Reviewed-by: Ravikumar PM Reviewed-by: Srikanth Thokala Signed-off-by: Herbert Xu Signed-off-by: Sasha Levin --- drivers/crypto/intel/qat/qat_common/adf_aer.c | 2 -- 1 file changed, 2 deletions(-) diff --git a/drivers/crypto/intel/qat/qat_common/adf_aer.c b/drivers/crypto/intel/qat/qat_common/adf_aer.c index 35679b21ff63bb..a098689ab5b75d 100644 --- a/drivers/crypto/intel/qat/qat_common/adf_aer.c +++ b/drivers/crypto/intel/qat/qat_common/adf_aer.c @@ -41,8 +41,6 @@ static pci_ers_result_t adf_error_detected(struct pci_dev *pdev, adf_error_notifier(accel_dev); adf_pf2vf_notify_fatal_error(accel_dev); adf_dev_restarting_notify(accel_dev); - adf_pf2vf_notify_restarting(accel_dev); - adf_pf2vf_wait_for_restarting_complete(accel_dev); pci_clear_master(pdev); adf_dev_down(accel_dev); From c200328fd57fe74742738f8a822f45cc30b9b3fe Mon Sep 17 00:00:00 2001 From: Haibo Chen Date: Wed, 19 Nov 2025 11:22:39 +0800 Subject: [PATCH 2301/3902] arm64: dts: imx8qm-mek: correct the light sensor interrupt type to low level [ Upstream commit e0d8678c2f09dca22e6197321f223fa9a0ca2839 ] light sensor isl29023 share the interrupt with lsm303arg, but these two devices use different interrupt type. According to the datasheet of these two devides, both support low level trigger type, so correct the interrupt type here to avoid the following error log: irq: type mismatch, failed to map hwirq-11 for gpio@5d0c0000! Fixes: 9918092cbb0e ("arm64: dts: imx8qm-mek: add i2c0 and children devices") Fixes: 1d8a9f043a77 ("arm64: dts: imx8: use defines for interrupts") Signed-off-by: Haibo Chen Reviewed-by: Frank Li Signed-off-by: Shawn Guo Signed-off-by: Sasha Levin --- arch/arm64/boot/dts/freescale/imx8qm-mek.dts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/arch/arm64/boot/dts/freescale/imx8qm-mek.dts b/arch/arm64/boot/dts/freescale/imx8qm-mek.dts index 9c0b6b8d6459de..d4b13cfd87a920 100644 --- a/arch/arm64/boot/dts/freescale/imx8qm-mek.dts +++ b/arch/arm64/boot/dts/freescale/imx8qm-mek.dts @@ -560,7 +560,7 @@ compatible = "isil,isl29023"; reg = <0x44>; interrupt-parent = <&lsio_gpio4>; - interrupts = <11 IRQ_TYPE_EDGE_FALLING>; + interrupts = <11 IRQ_TYPE_LEVEL_LOW>; }; pressure-sensor@60 { From 88244021003ba364704746c837799268842939c4 Mon Sep 17 00:00:00 2001 From: Haibo Chen Date: Wed, 19 Nov 2025 11:22:40 +0800 Subject: [PATCH 2302/3902] arm64: dts: add off-on-delay-us for usdhc2 regulator [ Upstream commit ca643894a37a25713029b36cfe7d1bae515cac08 ] For SD card, according to the spec requirement, for sd card power reset operation, it need sd card supply voltage to be lower than 0.5v and keep over 1ms, otherwise, next time power back the sd card supply voltage to 3.3v, sd card can't support SD3.0 mode again. To match such requirement on imx8qm-mek board, add 4.8ms delay between sd power off and power on. Fixes: 307fd14d4b14 ("arm64: dts: imx: add imx8qm mek support") Reviewed-by: Frank Li Signed-off-by: Haibo Chen Signed-off-by: Shawn Guo Signed-off-by: Sasha Levin --- arch/arm64/boot/dts/freescale/imx8qm-mek.dts | 1 + 1 file changed, 1 insertion(+) diff --git a/arch/arm64/boot/dts/freescale/imx8qm-mek.dts b/arch/arm64/boot/dts/freescale/imx8qm-mek.dts index d4b13cfd87a920..df99fe88cf4ac2 100644 --- a/arch/arm64/boot/dts/freescale/imx8qm-mek.dts +++ b/arch/arm64/boot/dts/freescale/imx8qm-mek.dts @@ -256,6 +256,7 @@ regulator-max-microvolt = <3000000>; gpio = <&lsio_gpio4 7 GPIO_ACTIVE_HIGH>; enable-active-high; + off-on-delay-us = <4800>; }; reg_audio: regulator-audio { From 2c3f04f1f70c6177a7681f69876963be92fb52c5 Mon Sep 17 00:00:00 2001 From: Ian Ray Date: Mon, 1 Dec 2025 11:56:05 +0200 Subject: [PATCH 2303/3902] ARM: dts: imx6q-ba16: fix RTC interrupt level [ Upstream commit e6a4eedd49ce27c16a80506c66a04707e0ee0116 ] RTC interrupt level should be set to "LOW". This was revealed by the introduction of commit: f181987ef477 ("rtc: m41t80: use IRQ flags obtained from fwnode") which changed the way IRQ type is obtained. Fixes: 56c27310c1b4 ("ARM: dts: imx: Add Advantech BA-16 Qseven module") Signed-off-by: Ian Ray Signed-off-by: Shawn Guo Signed-off-by: Sasha Levin --- arch/arm/boot/dts/nxp/imx/imx6q-ba16.dtsi | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/arch/arm/boot/dts/nxp/imx/imx6q-ba16.dtsi b/arch/arm/boot/dts/nxp/imx/imx6q-ba16.dtsi index 53013b12c2ecbf..02d66523668d26 100644 --- a/arch/arm/boot/dts/nxp/imx/imx6q-ba16.dtsi +++ b/arch/arm/boot/dts/nxp/imx/imx6q-ba16.dtsi @@ -337,7 +337,7 @@ pinctrl-0 = <&pinctrl_rtc>; reg = <0x32>; interrupt-parent = <&gpio4>; - interrupts = <10 IRQ_TYPE_LEVEL_HIGH>; + interrupts = <10 IRQ_TYPE_LEVEL_LOW>; }; }; From f267ced2bcb0c9d5f489a75328eb64f8f0af8c40 Mon Sep 17 00:00:00 2001 From: Maud Spierings Date: Mon, 1 Dec 2025 12:56:51 +0100 Subject: [PATCH 2304/3902] arm64: dts: freescale: moduline-display: fix compatible [ Upstream commit 056c68875122dd342782e5956ed145fe9e059614 ] The compatibles should include the SoM compatible, this board is based on the Ka-Ro TX8P-ML81 SoM, so add it to allow using shared code in the bootloader which uses upstream Linux devicetrees as a base. Also add the hardware revision to the board compatible to handle revision specific quirks in the bootloader/userspace. This is a breaking change, but it is early enough that it can be corrected without causing any issues. Fixes: 03f07be54cdc ("arm64: dts: freescale: Add the GOcontroll Moduline Display baseboard") Signed-off-by: Maud Spierings Signed-off-by: Shawn Guo Signed-off-by: Sasha Levin --- .../dts/freescale/imx8mp-tx8p-ml81-moduline-display-106.dts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/arch/arm64/boot/dts/freescale/imx8mp-tx8p-ml81-moduline-display-106.dts b/arch/arm64/boot/dts/freescale/imx8mp-tx8p-ml81-moduline-display-106.dts index 88ad422c27603b..399230144ce39c 100644 --- a/arch/arm64/boot/dts/freescale/imx8mp-tx8p-ml81-moduline-display-106.dts +++ b/arch/arm64/boot/dts/freescale/imx8mp-tx8p-ml81-moduline-display-106.dts @@ -9,7 +9,7 @@ #include "imx8mp-tx8p-ml81.dtsi" / { - compatible = "gocontroll,moduline-display", "fsl,imx8mp"; + compatible = "gocontroll,moduline-display-106", "karo,tx8p-ml81", "fsl,imx8mp"; chassis-type = "embedded"; hardware = "Moduline Display V1.06"; model = "GOcontroll Moduline Display baseboard"; From 5b5ef7049ebed65de138ce0106fcfd4ea8f31c3a Mon Sep 17 00:00:00 2001 From: Maud Spierings Date: Mon, 1 Dec 2025 12:56:52 +0100 Subject: [PATCH 2305/3902] arm64: dts: freescale: tx8p-ml81: fix eqos nvmem-cells [ Upstream commit cdf4e631eec5ddd49bb625df9fb144d6ecdd6f15 ] On this SoM eqos is the primary ethernet interface, Ka-Ro fuses the address for it in eth_mac1, eth_mac2 seems to be left unfused. In their downstream u-boot they fetch it from eth_mac1 [1][2], by setting alias of eqos to ethernet0, the driver then fetches the mac address based on the alias number. Set eqos to read from eth_mac1 instead of eth_mac2. Also set fec to point at eth_mac2 as it may be fused later even though it is disabled by default. With this changed barebox is now capable of loading the correct address. Link: https://github.com/karo-electronics/karo-tx-uboot/blob/380543278410bbf04264d80a3bfbe340b8e62439/drivers/net/dwc_eth_qos.c#L1167 [1] Link: https://github.com/karo-electronics/karo-tx-uboot/blob/380543278410bbf04264d80a3bfbe340b8e62439/arch/arm/dts/imx8mp-karo.dtsi#L12 [2] Fixes: bac63d7c5f46 ("arm64: dts: freescale: add Ka-Ro Electronics tx8p-ml81 COM") Signed-off-by: Maud Spierings Signed-off-by: Shawn Guo Signed-off-by: Sasha Levin --- arch/arm64/boot/dts/freescale/imx8mp-tx8p-ml81.dtsi | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/arch/arm64/boot/dts/freescale/imx8mp-tx8p-ml81.dtsi b/arch/arm64/boot/dts/freescale/imx8mp-tx8p-ml81.dtsi index fe8ba16eb40e71..761ee046eb72ef 100644 --- a/arch/arm64/boot/dts/freescale/imx8mp-tx8p-ml81.dtsi +++ b/arch/arm64/boot/dts/freescale/imx8mp-tx8p-ml81.dtsi @@ -47,6 +47,7 @@ <&clk IMX8MP_SYS_PLL2_100M>, <&clk IMX8MP_SYS_PLL2_50M>; assigned-clock-rates = <266000000>, <100000000>, <50000000>; + nvmem-cells = <ð_mac1>; phy-handle = <ðphy0>; phy-mode = "rmii"; pinctrl-0 = <&pinctrl_eqos>; @@ -75,6 +76,10 @@ }; }; +&fec { + nvmem-cells = <ð_mac2>; +}; + &gpio1 { gpio-line-names = "SODIMM_152", "SODIMM_42", From 4977cac699a47fcabb9f25f8ff391ed7d01714c2 Mon Sep 17 00:00:00 2001 From: Marek Vasut Date: Tue, 2 Dec 2025 14:41:51 +0100 Subject: [PATCH 2306/3902] arm64: dts: imx8mp: Fix LAN8740Ai PHY reference clock on DH electronics i.MX8M Plus DHCOM [ Upstream commit c63749a7ddc59ac6ec0b05abfa0a21af9f2c1d38 ] Add missing 'clocks' property to LAN8740Ai PHY node, to allow the PHY driver to manage LAN8740Ai CLKIN reference clock supply. This fixes sporadic link bouncing caused by interruptions on the PHY reference clock, by letting the PHY driver manage the reference clock and assure there are no interruptions. This follows the matching PHY driver recommendation described in commit bedd8d78aba3 ("net: phy: smsc: LAN8710/20: add phy refclk in support") Fixes: 8d6712695bc8 ("arm64: dts: imx8mp: Add support for DH electronics i.MX8M Plus DHCOM and PDK2") Signed-off-by: Marek Vasut Tested-by: Christoph Niedermaier Signed-off-by: Shawn Guo Signed-off-by: Sasha Levin --- arch/arm64/boot/dts/freescale/imx8mp-dhcom-som.dtsi | 1 + 1 file changed, 1 insertion(+) diff --git a/arch/arm64/boot/dts/freescale/imx8mp-dhcom-som.dtsi b/arch/arm64/boot/dts/freescale/imx8mp-dhcom-som.dtsi index 68c2e0156a5c81..f8303b7e2bd22b 100644 --- a/arch/arm64/boot/dts/freescale/imx8mp-dhcom-som.dtsi +++ b/arch/arm64/boot/dts/freescale/imx8mp-dhcom-som.dtsi @@ -113,6 +113,7 @@ ethphy0f: ethernet-phy@1 { /* SMSC LAN8740Ai */ compatible = "ethernet-phy-id0007.c110", "ethernet-phy-ieee802.3-c22"; + clocks = <&clk IMX8MP_CLK_ENET_QOS>; interrupt-parent = <&gpio3>; interrupts = <19 IRQ_TYPE_LEVEL_LOW>; pinctrl-0 = <&pinctrl_ethphy0>; From 646d415f9860edb755361de18b7461dad6c08d28 Mon Sep 17 00:00:00 2001 From: Sherry Sun Date: Wed, 3 Dec 2025 09:59:56 +0800 Subject: [PATCH 2307/3902] arm64: dts: imx8qm-ss-dma: correct the dma channels of lpuart [ Upstream commit a988caeed9d918452aa0a68de2c6e94d86aa43ba ] The commit 616effc0272b5 ("arm64: dts: imx8: Fix lpuart DMA channel order") swap uart rx and tx channel at common imx8-ss-dma.dtsi. But miss update imx8qm-ss-dma.dtsi. The commit 5a8e9b022e569 ("arm64: dts: imx8qm-ss-dma: Pass lpuart dma-names") just simple add dma-names as binding doc requirement. Correct lpuart0 - lpuart3 dma rx and tx channels, and use defines for the FSL_EDMA_RX flag. Fixes: 5a8e9b022e56 ("arm64: dts: imx8qm-ss-dma: Pass lpuart dma-names") Signed-off-by: Sherry Sun Reviewed-by: Frank Li Reviewed-by: Alexander Stein Signed-off-by: Shawn Guo Signed-off-by: Sasha Levin --- arch/arm64/boot/dts/freescale/imx8qm-ss-dma.dtsi | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/arch/arm64/boot/dts/freescale/imx8qm-ss-dma.dtsi b/arch/arm64/boot/dts/freescale/imx8qm-ss-dma.dtsi index d4856b8590e0c5..e186c31bfd482e 100644 --- a/arch/arm64/boot/dts/freescale/imx8qm-ss-dma.dtsi +++ b/arch/arm64/boot/dts/freescale/imx8qm-ss-dma.dtsi @@ -171,25 +171,25 @@ &lpuart0 { compatible = "fsl,imx8qm-lpuart", "fsl,imx8qxp-lpuart"; - dmas = <&edma2 13 0 0>, <&edma2 12 0 1>; + dmas = <&edma2 12 0 FSL_EDMA_RX>, <&edma2 13 0 0>; dma-names = "rx","tx"; }; &lpuart1 { compatible = "fsl,imx8qm-lpuart", "fsl,imx8qxp-lpuart"; - dmas = <&edma2 15 0 0>, <&edma2 14 0 1>; + dmas = <&edma2 14 0 FSL_EDMA_RX>, <&edma2 15 0 0>; dma-names = "rx","tx"; }; &lpuart2 { compatible = "fsl,imx8qm-lpuart", "fsl,imx8qxp-lpuart"; - dmas = <&edma2 17 0 0>, <&edma2 16 0 1>; + dmas = <&edma2 16 0 FSL_EDMA_RX>, <&edma2 17 0 0>; dma-names = "rx","tx"; }; &lpuart3 { compatible = "fsl,imx8qm-lpuart", "fsl,imx8qxp-lpuart"; - dmas = <&edma2 19 0 0>, <&edma2 18 0 1>; + dmas = <&edma2 18 0 FSL_EDMA_RX>, <&edma2 19 0 0>; dma-names = "rx","tx"; }; From 499c0db5862f10ef35402bf99821eac3e91eb743 Mon Sep 17 00:00:00 2001 From: Alexander Stein Date: Tue, 16 Dec 2025 14:15:28 +0100 Subject: [PATCH 2308/3902] arm64: dts: mba8mx: Fix Ethernet PHY IRQ support [ Upstream commit 89e87d0dc87eb3654c9ae01afc4a18c1c6d1e523 ] Ethernet PHY interrupt mode is level triggered. Adjust the mode accordingly. Signed-off-by: Alexander Stein Reviewed-by: Andrew Lunn Fixes: 70cf622bb16e ("arm64: dts: mba8mx: Add Ethernet PHY IRQ support") Signed-off-by: Shawn Guo Signed-off-by: Sasha Levin --- arch/arm64/boot/dts/freescale/mba8mx.dtsi | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/arch/arm64/boot/dts/freescale/mba8mx.dtsi b/arch/arm64/boot/dts/freescale/mba8mx.dtsi index 79daba930ad648..3e41da4d6122be 100644 --- a/arch/arm64/boot/dts/freescale/mba8mx.dtsi +++ b/arch/arm64/boot/dts/freescale/mba8mx.dtsi @@ -185,7 +185,7 @@ reset-assert-us = <500000>; reset-deassert-us = <500>; interrupt-parent = <&expander2>; - interrupts = <6 IRQ_TYPE_EDGE_FALLING>; + interrupts = <6 IRQ_TYPE_LEVEL_LOW>; }; }; }; From 704c0258f0d79c876accfa82aa93cc7c2acc91d0 Mon Sep 17 00:00:00 2001 From: Florian Westphal Date: Thu, 4 Dec 2025 12:20:35 +0100 Subject: [PATCH 2309/3902] netfilter: nft_set_pipapo: fix range overlap detection [ Upstream commit 7711f4bb4b360d9c0ff84db1c0ec91e385625047 ] set->klen has to be used, not sizeof(). The latter only compares a single register but a full check of the entire key is needed. Example: table ip t { map s { typeof iifname . ip saddr : verdict flags interval } } nft add element t s '{ "lo" . 10.0.0.0/24 : drop }' # no error, expected nft add element t s '{ "lo" . 10.0.0.0/24 : drop }' # no error, expected nft add element t s '{ "lo" . 10.0.0.0/8 : drop }' # bug: no error The 3rd 'add element' should be rejected via -ENOTEMPTY, not -EEXIST, so userspace / nft can report an error to the user. The latter is only correct for the 2nd case (re-add of existing element). As-is, userspace is told that the command was successful, but no elements were added. After this patch, 3rd command gives: Error: Could not process rule: File exists add element t s { "lo" . 127.0.0.0/8 . "lo" : drop } ^^^^^^^^^^^^^^^^^^^^^^^^^ Fixes: 0eb4b5ee33f2 ("netfilter: nft_set_pipapo: Separate partial and complete overlap cases on insertion") Signed-off-by: Florian Westphal Signed-off-by: Sasha Levin --- net/netfilter/nft_set_pipapo.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/net/netfilter/nft_set_pipapo.c b/net/netfilter/nft_set_pipapo.c index 112fe46788b6fb..6d77a5f0088ad0 100644 --- a/net/netfilter/nft_set_pipapo.c +++ b/net/netfilter/nft_set_pipapo.c @@ -1317,8 +1317,8 @@ static int nft_pipapo_insert(const struct net *net, const struct nft_set *set, else dup_end = dup_key; - if (!memcmp(start, dup_key->data, sizeof(*dup_key->data)) && - !memcmp(end, dup_end->data, sizeof(*dup_end->data))) { + if (!memcmp(start, dup_key->data, set->klen) && + !memcmp(end, dup_end->data, set->klen)) { *elem_priv = &dup->priv; return -EEXIST; } From 75bfb05067d682e4518898f56d370a341df2b566 Mon Sep 17 00:00:00 2001 From: Fernando Fernandez Mancera Date: Wed, 17 Dec 2025 21:21:59 +0100 Subject: [PATCH 2310/3902] netfilter: nft_synproxy: avoid possible data-race on update operation [ Upstream commit 36a3200575642846a96436d503d46544533bb943 ] During nft_synproxy eval we are reading nf_synproxy_info struct which can be modified on update operation concurrently. As nf_synproxy_info struct fits in 32 bits, use READ_ONCE/WRITE_ONCE annotations. Fixes: ee394f96ad75 ("netfilter: nft_synproxy: add synproxy stateful object support") Signed-off-by: Fernando Fernandez Mancera Signed-off-by: Florian Westphal Signed-off-by: Sasha Levin --- net/netfilter/nft_synproxy.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/net/netfilter/nft_synproxy.c b/net/netfilter/nft_synproxy.c index 5d3e5182598596..4d3e5a31b4125d 100644 --- a/net/netfilter/nft_synproxy.c +++ b/net/netfilter/nft_synproxy.c @@ -48,7 +48,7 @@ static void nft_synproxy_eval_v4(const struct nft_synproxy *priv, struct tcphdr *_tcph, struct synproxy_options *opts) { - struct nf_synproxy_info info = priv->info; + struct nf_synproxy_info info = READ_ONCE(priv->info); struct net *net = nft_net(pkt); struct synproxy_net *snet = synproxy_pernet(net); struct sk_buff *skb = pkt->skb; @@ -79,7 +79,7 @@ static void nft_synproxy_eval_v6(const struct nft_synproxy *priv, struct tcphdr *_tcph, struct synproxy_options *opts) { - struct nf_synproxy_info info = priv->info; + struct nf_synproxy_info info = READ_ONCE(priv->info); struct net *net = nft_net(pkt); struct synproxy_net *snet = synproxy_pernet(net); struct sk_buff *skb = pkt->skb; @@ -340,7 +340,7 @@ static void nft_synproxy_obj_update(struct nft_object *obj, struct nft_synproxy *newpriv = nft_obj_data(newobj); struct nft_synproxy *priv = nft_obj_data(obj); - priv->info = newpriv->info; + WRITE_ONCE(priv->info, newpriv->info); } static struct nft_object_type nft_synproxy_obj_type; From 97531f8e50d99a289e0024e020cfdda5eec36f4d Mon Sep 17 00:00:00 2001 From: Bartosz Golaszewski Date: Mon, 22 Sep 2025 11:54:02 +0200 Subject: [PATCH 2311/3902] gpiolib: remove unnecessary 'out of memory' messages [ Upstream commit 0ba6f1ed3808b1f095fbdb490006f0ecd00f52bd ] We don't need to add additional logs when returning -ENOMEM so remove unnecessary error messages. Reviewed-by: Linus Walleij Signed-off-by: Bartosz Golaszewski Stable-dep-of: a7ac22d53d09 ("gpiolib: fix race condition for gdev->srcu") Signed-off-by: Sasha Levin --- drivers/gpio/gpiolib.c | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/drivers/gpio/gpiolib.c b/drivers/gpio/gpiolib.c index cd8800ba5825ff..f2ed234b4135e2 100644 --- a/drivers/gpio/gpiolib.c +++ b/drivers/gpio/gpiolib.c @@ -2316,10 +2316,8 @@ int gpiochip_add_pingroup_range(struct gpio_chip *gc, int ret; pin_range = kzalloc(sizeof(*pin_range), GFP_KERNEL); - if (!pin_range) { - chip_err(gc, "failed to allocate pin ranges\n"); + if (!pin_range) return -ENOMEM; - } /* Use local offset as range ID */ pin_range->range.id = gpio_offset; @@ -2379,10 +2377,8 @@ int gpiochip_add_pin_range_with_pins(struct gpio_chip *gc, int ret; pin_range = kzalloc(sizeof(*pin_range), GFP_KERNEL); - if (!pin_range) { - chip_err(gc, "failed to allocate pin ranges\n"); + if (!pin_range) return -ENOMEM; - } /* Use local offset as range ID */ pin_range->range.id = gpio_offset; From aaa24eeb63ad0b8de7ebeec603776610c5f805a6 Mon Sep 17 00:00:00 2001 From: Bartosz Golaszewski Date: Mon, 22 Sep 2025 11:54:03 +0200 Subject: [PATCH 2312/3902] gpiolib: rename GPIO chip printk macros [ Upstream commit d4f335b410ddbe3e99f48f8b5ea78a25041274f1 ] The chip_$level() macros take struct gpio_chip as argument so make it follow the convention of using the 'gpiochip_' prefix. Reviewed-by: Linus Walleij Signed-off-by: Bartosz Golaszewski Stable-dep-of: a7ac22d53d09 ("gpiolib: fix race condition for gdev->srcu") Signed-off-by: Sasha Levin --- drivers/gpio/gpiolib-cdev.c | 2 +- drivers/gpio/gpiolib-sysfs.c | 2 +- drivers/gpio/gpiolib.c | 80 ++++++++++++++++++------------------ drivers/gpio/gpiolib.h | 8 ++-- 4 files changed, 45 insertions(+), 47 deletions(-) diff --git a/drivers/gpio/gpiolib-cdev.c b/drivers/gpio/gpiolib-cdev.c index d8d93059ac04c8..d925e75d1dce10 100644 --- a/drivers/gpio/gpiolib-cdev.c +++ b/drivers/gpio/gpiolib-cdev.c @@ -2828,7 +2828,7 @@ int gpiolib_cdev_register(struct gpio_device *gdev, dev_t devt) if (!gc) return -ENODEV; - chip_dbg(gc, "added GPIO chardev (%d:%d)\n", MAJOR(devt), gdev->id); + gpiochip_dbg(gc, "added GPIO chardev (%d:%d)\n", MAJOR(devt), gdev->id); return 0; } diff --git a/drivers/gpio/gpiolib-sysfs.c b/drivers/gpio/gpiolib-sysfs.c index 9a849245b35880..7d5fc1ea2aa54f 100644 --- a/drivers/gpio/gpiolib-sysfs.c +++ b/drivers/gpio/gpiolib-sysfs.c @@ -1091,7 +1091,7 @@ static int gpiofind_sysfs_register(struct gpio_chip *gc, const void *data) ret = gpiochip_sysfs_register(gdev); if (ret) - chip_err(gc, "failed to register the sysfs entry: %d\n", ret); + gpiochip_err(gc, "failed to register the sysfs entry: %d\n", ret); return 0; } diff --git a/drivers/gpio/gpiolib.c b/drivers/gpio/gpiolib.c index f2ed234b4135e2..9a4395a29f68eb 100644 --- a/drivers/gpio/gpiolib.c +++ b/drivers/gpio/gpiolib.c @@ -921,8 +921,8 @@ static void gpiochip_machine_hog(struct gpio_chip *gc, struct gpiod_hog *hog) desc = gpiochip_get_desc(gc, hog->chip_hwnum); if (IS_ERR(desc)) { - chip_err(gc, "%s: unable to get GPIO desc: %ld\n", __func__, - PTR_ERR(desc)); + gpiochip_err(gc, "%s: unable to get GPIO desc: %ld\n", + __func__, PTR_ERR(desc)); return; } @@ -1124,7 +1124,7 @@ int gpiochip_add_data_with_key(struct gpio_chip *gc, void *data, ret = gpiodev_add_to_list_unlocked(gdev); if (ret) { - chip_err(gc, "GPIO integer space overlap, cannot add chip\n"); + gpiochip_err(gc, "GPIO integer space overlap, cannot add chip\n"); goto err_free_label; } } @@ -1528,8 +1528,7 @@ static void gpiochip_set_hierarchical_irqchip(struct gpio_chip *gc, &parent_hwirq, &parent_type); if (ret) { - chip_err(gc, "skip set-up on hwirq %d\n", - i); + gpiochip_err(gc, "skip set-up on hwirq %d\n", i); continue; } @@ -1542,15 +1541,14 @@ static void gpiochip_set_hierarchical_irqchip(struct gpio_chip *gc, ret = irq_domain_alloc_irqs(gc->irq.domain, 1, NUMA_NO_NODE, &fwspec); if (ret < 0) { - chip_err(gc, - "can not allocate irq for GPIO line %d parent hwirq %d in hierarchy domain: %d\n", - i, parent_hwirq, - ret); + gpiochip_err(gc, + "can not allocate irq for GPIO line %d parent hwirq %d in hierarchy domain: %d\n", + i, parent_hwirq, ret); } } } - chip_err(gc, "%s unknown fwnode type proceed anyway\n", __func__); + gpiochip_err(gc, "%s unknown fwnode type proceed anyway\n", __func__); return; } @@ -1602,15 +1600,15 @@ static int gpiochip_hierarchy_irq_domain_alloc(struct irq_domain *d, if (ret) return ret; - chip_dbg(gc, "allocate IRQ %d, hwirq %lu\n", irq, hwirq); + gpiochip_dbg(gc, "allocate IRQ %d, hwirq %lu\n", irq, hwirq); ret = girq->child_to_parent_hwirq(gc, hwirq, type, &parent_hwirq, &parent_type); if (ret) { - chip_err(gc, "can't look up hwirq %lu\n", hwirq); + gpiochip_err(gc, "can't look up hwirq %lu\n", hwirq); return ret; } - chip_dbg(gc, "found parent hwirq %u\n", parent_hwirq); + gpiochip_dbg(gc, "found parent hwirq %u\n", parent_hwirq); /* * We set handle_bad_irq because the .set_type() should @@ -1631,8 +1629,8 @@ static int gpiochip_hierarchy_irq_domain_alloc(struct irq_domain *d, if (ret) return ret; - chip_dbg(gc, "alloc_irqs_parent for %d parent hwirq %d\n", - irq, parent_hwirq); + gpiochip_dbg(gc, "alloc_irqs_parent for %d parent hwirq %d\n", + irq, parent_hwirq); irq_set_lockdep_class(irq, gc->irq.lock_key, gc->irq.request_key); ret = irq_domain_alloc_irqs_parent(d, irq, 1, &gpio_parent_fwspec); /* @@ -1642,9 +1640,9 @@ static int gpiochip_hierarchy_irq_domain_alloc(struct irq_domain *d, if (irq_domain_is_msi(d->parent) && (ret == -EEXIST)) ret = 0; if (ret) - chip_err(gc, - "failed to allocate parent hwirq %d for hwirq %lu\n", - parent_hwirq, hwirq); + gpiochip_err(gc, + "failed to allocate parent hwirq %d for hwirq %lu\n", + parent_hwirq, hwirq); return ret; } @@ -1720,7 +1718,7 @@ static struct irq_domain *gpiochip_hierarchy_create_domain(struct gpio_chip *gc) if (!gc->irq.child_to_parent_hwirq || !gc->irq.fwnode) { - chip_err(gc, "missing irqdomain vital data\n"); + gpiochip_err(gc, "missing irqdomain vital data\n"); return ERR_PTR(-EINVAL); } @@ -1993,7 +1991,7 @@ static void gpiochip_set_irq_hooks(struct gpio_chip *gc) if (irqchip->flags & IRQCHIP_IMMUTABLE) return; - chip_warn(gc, "not an immutable chip, please consider fixing it!\n"); + gpiochip_warn(gc, "not an immutable chip, please consider fixing it!\n"); if (!irqchip->irq_request_resources && !irqchip->irq_release_resources) { @@ -2009,8 +2007,8 @@ static void gpiochip_set_irq_hooks(struct gpio_chip *gc) * ...and if so, give a gentle warning that this is bad * practice. */ - chip_info(gc, - "detected irqchip that is shared with multiple gpiochips: please fix the driver.\n"); + gpiochip_info(gc, + "detected irqchip that is shared with multiple gpiochips: please fix the driver.\n"); return; } @@ -2039,7 +2037,8 @@ static int gpiochip_irqchip_add_allocated_domain(struct gpio_chip *gc, return -EINVAL; if (gc->to_irq) - chip_warn(gc, "to_irq is redefined in %s and you shouldn't rely on it\n", __func__); + gpiochip_warn(gc, "to_irq is redefined in %s and you shouldn't rely on it\n", + __func__); gc->to_irq = gpiochip_to_irq; gc->irq.domain = domain; @@ -2080,7 +2079,7 @@ static int gpiochip_add_irqchip(struct gpio_chip *gc, return 0; if (gc->irq.parent_handler && gc->can_sleep) { - chip_err(gc, "you cannot have chained interrupts on a chip that may sleep\n"); + gpiochip_err(gc, "you cannot have chained interrupts on a chip that may sleep\n"); return -EINVAL; } @@ -2336,7 +2335,7 @@ int gpiochip_add_pingroup_range(struct gpio_chip *gc, pinctrl_add_gpio_range(pctldev, &pin_range->range); - chip_dbg(gc, "created GPIO range %d->%d ==> %s PINGRP %s\n", + gpiochip_dbg(gc, "created GPIO range %d->%d ==> %s PINGRP %s\n", gpio_offset, gpio_offset + pin_range->range.npins - 1, pinctrl_dev_get_devname(pctldev), pin_group); @@ -2392,19 +2391,18 @@ int gpiochip_add_pin_range_with_pins(struct gpio_chip *gc, &pin_range->range); if (IS_ERR(pin_range->pctldev)) { ret = PTR_ERR(pin_range->pctldev); - chip_err(gc, "could not create pin range\n"); + gpiochip_err(gc, "could not create pin range\n"); kfree(pin_range); return ret; } if (pin_range->range.pins) - chip_dbg(gc, "created GPIO range %d->%d ==> %s %d sparse PIN range { %d, ... }", - gpio_offset, gpio_offset + npins - 1, - pinctl_name, npins, pins[0]); + gpiochip_dbg(gc, "created GPIO range %d->%d ==> %s %d sparse PIN range { %d, ... }", + gpio_offset, gpio_offset + npins - 1, + pinctl_name, npins, pins[0]); else - chip_dbg(gc, "created GPIO range %d->%d ==> %s PIN %d->%d\n", - gpio_offset, gpio_offset + npins - 1, - pinctl_name, - pin_offset, pin_offset + npins - 1); + gpiochip_dbg(gc, "created GPIO range %d->%d ==> %s PIN %d->%d\n", + gpio_offset, gpio_offset + npins - 1, pinctl_name, + pin_offset, pin_offset + npins - 1); list_add_tail(&pin_range->node, &gdev->pin_ranges); @@ -2614,7 +2612,7 @@ struct gpio_desc *gpiochip_request_own_desc(struct gpio_chip *gc, int ret; if (IS_ERR(desc)) { - chip_err(gc, "failed to get GPIO %s descriptor\n", name); + gpiochip_err(gc, "failed to get GPIO %s descriptor\n", name); return desc; } @@ -2625,7 +2623,7 @@ struct gpio_desc *gpiochip_request_own_desc(struct gpio_chip *gc, ret = gpiod_configure_flags(desc, label, lflags, dflags); if (ret) { gpiod_free_commit(desc); - chip_err(gc, "setup of own GPIO %s failed\n", name); + gpiochip_err(gc, "setup of own GPIO %s failed\n", name); return ERR_PTR(ret); } @@ -4052,8 +4050,8 @@ int gpiochip_lock_as_irq(struct gpio_chip *gc, unsigned int offset) int dir = gpiod_get_direction(desc); if (dir < 0) { - chip_err(gc, "%s: cannot get GPIO direction\n", - __func__); + gpiochip_err(gc, "%s: cannot get GPIO direction\n", + __func__); return dir; } } @@ -4061,9 +4059,9 @@ int gpiochip_lock_as_irq(struct gpio_chip *gc, unsigned int offset) /* To be valid for IRQ the line needs to be input or open drain */ if (test_bit(GPIOD_FLAG_IS_OUT, &desc->flags) && !test_bit(GPIOD_FLAG_OPEN_DRAIN, &desc->flags)) { - chip_err(gc, - "%s: tried to flag a GPIO set as output for IRQ\n", - __func__); + gpiochip_err(gc, + "%s: tried to flag a GPIO set as output for IRQ\n", + __func__); return -EIO; } @@ -4140,7 +4138,7 @@ int gpiochip_reqres_irq(struct gpio_chip *gc, unsigned int offset) ret = gpiochip_lock_as_irq(gc, offset); if (ret) { - chip_err(gc, "unable to lock HW IRQ %u for IRQ\n", offset); + gpiochip_err(gc, "unable to lock HW IRQ %u for IRQ\n", offset); module_put(gc->gpiodev->owner); return ret; } diff --git a/drivers/gpio/gpiolib.h b/drivers/gpio/gpiolib.h index 2a003a7311e7ac..6ee29d0222393d 100644 --- a/drivers/gpio/gpiolib.h +++ b/drivers/gpio/gpiolib.h @@ -309,13 +309,13 @@ do { \ /* With chip prefix */ -#define chip_err(gc, fmt, ...) \ +#define gpiochip_err(gc, fmt, ...) \ dev_err(&gc->gpiodev->dev, "(%s): " fmt, gc->label, ##__VA_ARGS__) -#define chip_warn(gc, fmt, ...) \ +#define gpiochip_warn(gc, fmt, ...) \ dev_warn(&gc->gpiodev->dev, "(%s): " fmt, gc->label, ##__VA_ARGS__) -#define chip_info(gc, fmt, ...) \ +#define gpiochip_info(gc, fmt, ...) \ dev_info(&gc->gpiodev->dev, "(%s): " fmt, gc->label, ##__VA_ARGS__) -#define chip_dbg(gc, fmt, ...) \ +#define gpiochip_dbg(gc, fmt, ...) \ dev_dbg(&gc->gpiodev->dev, "(%s): " fmt, gc->label, ##__VA_ARGS__) #endif /* GPIOLIB_H */ From fb674c8f1a5d8dd3113a7326030f963fa2d79c02 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20Narewski?= Date: Wed, 24 Dec 2025 09:26:40 +0100 Subject: [PATCH 2313/3902] gpiolib: fix race condition for gdev->srcu MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [ Upstream commit a7ac22d53d0990152b108c3f4fe30df45fcb0181 ] If two drivers were calling gpiochip_add_data_with_key(), one may be traversing the srcu-protected list in gpio_name_to_desc(), meanwhile other has just added its gdev in gpiodev_add_to_list_unlocked(). This creates a non-mutexed and non-protected timeframe, when one instance is dereferencing and using &gdev->srcu, before the other has initialized it, resulting in crash: [ 4.935481] Unable to handle kernel paging request at virtual address ffff800272bcc000 [ 4.943396] Mem abort info: [ 4.943400] ESR = 0x0000000096000005 [ 4.943403] EC = 0x25: DABT (current EL), IL = 32 bits [ 4.943407] SET = 0, FnV = 0 [ 4.943410] EA = 0, S1PTW = 0 [ 4.943413] FSC = 0x05: level 1 translation fault [ 4.943416] Data abort info: [ 4.943418] ISV = 0, ISS = 0x00000005, ISS2 = 0x00000000 [ 4.946220] CM = 0, WnR = 0, TnD = 0, TagAccess = 0 [ 4.955261] GCS = 0, Overlay = 0, DirtyBit = 0, Xs = 0 [ 4.955268] swapper pgtable: 4k pages, 48-bit VAs, pgdp=0000000038e6c000 [ 4.961449] [ffff800272bcc000] pgd=0000000000000000 [ 4.969203] , p4d=1000000039739003 [ 4.979730] , pud=0000000000000000 [ 4.980210] phandle (CPU): 0x0000005e, phandle (BE): 0x5e000000 for node "reset" [ 4.991736] Internal error: Oops: 0000000096000005 [#1] PREEMPT SMP ... [ 5.121359] pc : __srcu_read_lock+0x44/0x98 [ 5.131091] lr : gpio_name_to_desc+0x60/0x1a0 [ 5.153671] sp : ffff8000833bb430 [ 5.298440] [ 5.298443] Call trace: [ 5.298445] __srcu_read_lock+0x44/0x98 [ 5.309484] gpio_name_to_desc+0x60/0x1a0 [ 5.320692] gpiochip_add_data_with_key+0x488/0xf00 5.946419] ---[ end trace 0000000000000000 ]--- Move initialization code for gdev fields before it is added to gpio_devices, with adjacent initialization code. Adjust goto statements to reflect modified order of operations Fixes: 47d8b4c1d868 ("gpio: add SRCU infrastructure to struct gpio_device") Reviewed-by: Jakub Lewalski Signed-off-by: Paweł Narewski [Bartosz: fixed a build issue, removed stray newline] Link: https://lore.kernel.org/r/20251224082641.10769-1-bartosz.golaszewski@oss.qualcomm.com Signed-off-by: Bartosz Golaszewski Signed-off-by: Sasha Levin --- drivers/gpio/gpiolib.c | 40 ++++++++++++++++++++-------------------- 1 file changed, 20 insertions(+), 20 deletions(-) diff --git a/drivers/gpio/gpiolib.c b/drivers/gpio/gpiolib.c index 9a4395a29f68eb..9aa6ddf6389ccd 100644 --- a/drivers/gpio/gpiolib.c +++ b/drivers/gpio/gpiolib.c @@ -1091,6 +1091,18 @@ int gpiochip_add_data_with_key(struct gpio_chip *gc, void *data, gdev->ngpio = gc->ngpio; gdev->can_sleep = gc->can_sleep; + rwlock_init(&gdev->line_state_lock); + RAW_INIT_NOTIFIER_HEAD(&gdev->line_state_notifier); + BLOCKING_INIT_NOTIFIER_HEAD(&gdev->device_notifier); + + ret = init_srcu_struct(&gdev->srcu); + if (ret) + goto err_free_label; + + ret = init_srcu_struct(&gdev->desc_srcu); + if (ret) + goto err_cleanup_gdev_srcu; + scoped_guard(mutex, &gpio_devices_lock) { /* * TODO: this allocates a Linux GPIO number base in the global @@ -1105,7 +1117,7 @@ int gpiochip_add_data_with_key(struct gpio_chip *gc, void *data, if (base < 0) { ret = base; base = 0; - goto err_free_label; + goto err_cleanup_desc_srcu; } /* @@ -1125,22 +1137,10 @@ int gpiochip_add_data_with_key(struct gpio_chip *gc, void *data, ret = gpiodev_add_to_list_unlocked(gdev); if (ret) { gpiochip_err(gc, "GPIO integer space overlap, cannot add chip\n"); - goto err_free_label; + goto err_cleanup_desc_srcu; } } - rwlock_init(&gdev->line_state_lock); - RAW_INIT_NOTIFIER_HEAD(&gdev->line_state_notifier); - BLOCKING_INIT_NOTIFIER_HEAD(&gdev->device_notifier); - - ret = init_srcu_struct(&gdev->srcu); - if (ret) - goto err_remove_from_list; - - ret = init_srcu_struct(&gdev->desc_srcu); - if (ret) - goto err_cleanup_gdev_srcu; - #ifdef CONFIG_PINCTRL INIT_LIST_HEAD(&gdev->pin_ranges); #endif @@ -1150,11 +1150,11 @@ int gpiochip_add_data_with_key(struct gpio_chip *gc, void *data, ret = gpiochip_set_names(gc); if (ret) - goto err_cleanup_desc_srcu; + goto err_remove_from_list; ret = gpiochip_init_valid_mask(gc); if (ret) - goto err_cleanup_desc_srcu; + goto err_remove_from_list; for (desc_index = 0; desc_index < gc->ngpio; desc_index++) { struct gpio_desc *desc = &gdev->descs[desc_index]; @@ -1227,10 +1227,6 @@ int gpiochip_add_data_with_key(struct gpio_chip *gc, void *data, of_gpiochip_remove(gc); err_free_valid_mask: gpiochip_free_valid_mask(gc); -err_cleanup_desc_srcu: - cleanup_srcu_struct(&gdev->desc_srcu); -err_cleanup_gdev_srcu: - cleanup_srcu_struct(&gdev->srcu); err_remove_from_list: scoped_guard(mutex, &gpio_devices_lock) list_del_rcu(&gdev->list); @@ -1240,6 +1236,10 @@ int gpiochip_add_data_with_key(struct gpio_chip *gc, void *data, gpio_device_put(gdev); goto err_print_message; } +err_cleanup_desc_srcu: + cleanup_srcu_struct(&gdev->desc_srcu); +err_cleanup_gdev_srcu: + cleanup_srcu_struct(&gdev->srcu); err_free_label: kfree_const(gdev->label); err_free_descs: From 58dac9b28a57163310fc684fbd038a5a2e1c1a6d Mon Sep 17 00:00:00 2001 From: Ernest Van Hoecke Date: Wed, 17 Dec 2025 16:30:25 +0100 Subject: [PATCH 2314/3902] gpio: pca953x: handle short interrupt pulses on PCAL devices MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [ Upstream commit 014a17deb41201449f76df2b20c857a9c3294a7c ] GPIO drivers with latch input support may miss short pulses on input pins even when input latching is enabled. The generic interrupt logic in the pca953x driver reports interrupts by comparing the current input value against the previously sampled one and only signals an event when a level change is observed between two reads. For short pulses, the first edge is captured when the input register is read, but if the signal returns to its previous level before the read, the second edge is not observed. As a result, successive pulses can produce identical input values at read time and no level change is detected, causing interrupts to be missed. Below timing diagram shows this situation where the top signal is the input pin level and the bottom signal indicates the latched value. ─────┐ ┌──*───────────────┐ ┌──*─────────────────┐ ┌──*─── │ │ . │ │ . │ │ . │ │ │ │ │ │ │ │ │ └──*──┘ │ └──*──┘ │ └──*──┘ │ Input │ │ │ │ │ │ ▼ │ ▼ │ ▼ │ IRQ │ IRQ │ IRQ │ . . . ─────┐ .┌──────────────┐ .┌────────────────┐ .┌── │ │ │ │ │ │ │ │ │ │ │ │ └────────*┘ └────────*┘ └────────*┘ Latched │ │ │ ▼ ▼ ▼ READ 0 READ 0 READ 0 NO CHANGE NO CHANGE PCAL variants provide an interrupt status register that records which pins triggered an interrupt, but the status and input registers cannot be read atomically. The interrupt status is only cleared when the input port is read, and the input value must also be read to determine the triggering edge. If another interrupt occurs on a different line after the status register has been read but before the input register is sampled, that event will not be reflected in the earlier status snapshot, so relying solely on the interrupt status register is also insufficient. Support for input latching and interrupt status handling was previously added by [1], but the interrupt status-based logic was reverted by [2] due to these issues. This patch addresses the original problem by combining both sources of information. Events indicated by the interrupt status register are merged with events detected through the existing level-change logic. As a result: * short pulses, whose second edges are invisible, are detected via the interrupt status register, and * interrupts that occur between the status and input reads are still caught by the generic level-change logic. This significantly improves robustness on devices that signal interrupts as short pulses, while avoiding the issues that led to the earlier reversion. In practice, even if only the first edge of a pulse is observable, the interrupt is reliably detected. This fixes missed interrupts from an Ilitek touch controller with its interrupt line connected to a PCAL6416A, where active-low pulses are approximately 200 us long. [1] commit 44896beae605 ("gpio: pca953x: add PCAL9535 interrupt support for Galileo Gen2") [2] commit d6179f6c6204 ("gpio: pca953x: Improve interrupt support") Fixes: d6179f6c6204 ("gpio: pca953x: Improve interrupt support") Signed-off-by: Ernest Van Hoecke Reviewed-by: Andy Shevchenko Link: https://lore.kernel.org/r/20251217153050.142057-1-ernestvanhoecke@gmail.com Signed-off-by: Bartosz Golaszewski Signed-off-by: Sasha Levin --- drivers/gpio/gpio-pca953x.c | 25 ++++++++++++++++++++++++- 1 file changed, 24 insertions(+), 1 deletion(-) diff --git a/drivers/gpio/gpio-pca953x.c b/drivers/gpio/gpio-pca953x.c index b46927f550389a..b2de916107f425 100644 --- a/drivers/gpio/gpio-pca953x.c +++ b/drivers/gpio/gpio-pca953x.c @@ -940,14 +940,35 @@ static bool pca953x_irq_pending(struct pca953x_chip *chip, unsigned long *pendin DECLARE_BITMAP(old_stat, MAX_LINE); DECLARE_BITMAP(cur_stat, MAX_LINE); DECLARE_BITMAP(new_stat, MAX_LINE); + DECLARE_BITMAP(int_stat, MAX_LINE); DECLARE_BITMAP(trigger, MAX_LINE); DECLARE_BITMAP(edges, MAX_LINE); int ret; + if (chip->driver_data & PCA_PCAL) { + /* Read INT_STAT before it is cleared by the input-port read. */ + ret = pca953x_read_regs(chip, PCAL953X_INT_STAT, int_stat); + if (ret) + return false; + } + ret = pca953x_read_regs(chip, chip->regs->input, cur_stat); if (ret) return false; + if (chip->driver_data & PCA_PCAL) { + /* Detect short pulses via INT_STAT. */ + bitmap_and(trigger, int_stat, chip->irq_mask, gc->ngpio); + + /* Apply filter for rising/falling edge selection. */ + bitmap_replace(new_stat, chip->irq_trig_fall, chip->irq_trig_raise, + cur_stat, gc->ngpio); + + bitmap_and(int_stat, new_stat, trigger, gc->ngpio); + } else { + bitmap_zero(int_stat, gc->ngpio); + } + /* Remove output pins from the equation */ pca953x_read_regs(chip, chip->regs->direction, reg_direction); @@ -961,7 +982,8 @@ static bool pca953x_irq_pending(struct pca953x_chip *chip, unsigned long *pendin if (bitmap_empty(chip->irq_trig_level_high, gc->ngpio) && bitmap_empty(chip->irq_trig_level_low, gc->ngpio)) { - if (bitmap_empty(trigger, gc->ngpio)) + if (bitmap_empty(trigger, gc->ngpio) && + bitmap_empty(int_stat, gc->ngpio)) return false; } @@ -969,6 +991,7 @@ static bool pca953x_irq_pending(struct pca953x_chip *chip, unsigned long *pendin bitmap_and(old_stat, chip->irq_trig_raise, new_stat, gc->ngpio); bitmap_or(edges, old_stat, cur_stat, gc->ngpio); bitmap_and(pending, edges, trigger, gc->ngpio); + bitmap_or(pending, pending, int_stat, gc->ngpio); bitmap_and(cur_stat, new_stat, chip->irq_trig_level_high, gc->ngpio); bitmap_and(cur_stat, cur_stat, chip->irq_mask, gc->ngpio); From 7f066cba650c584f55c6732d2d99a967928515f7 Mon Sep 17 00:00:00 2001 From: Zilin Guan Date: Wed, 24 Dec 2025 12:48:26 +0000 Subject: [PATCH 2315/3902] netfilter: nf_tables: fix memory leak in nf_tables_newrule() [ Upstream commit d077e8119ddbb4fca67540f1a52453631a47f221 ] In nf_tables_newrule(), if nft_use_inc() fails, the function jumps to the err_release_rule label without freeing the allocated flow, leading to a memory leak. Fix this by adding a new label err_destroy_flow and jumping to it when nft_use_inc() fails. This ensures that the flow is properly released in this error case. Fixes: 1689f25924ada ("netfilter: nf_tables: report use refcount overflow") Signed-off-by: Zilin Guan Signed-off-by: Florian Westphal Signed-off-by: Sasha Levin --- net/netfilter/nf_tables_api.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/net/netfilter/nf_tables_api.c b/net/netfilter/nf_tables_api.c index 1a204f6371ad14..a3669acd68a324 100644 --- a/net/netfilter/nf_tables_api.c +++ b/net/netfilter/nf_tables_api.c @@ -4378,7 +4378,7 @@ static int nf_tables_newrule(struct sk_buff *skb, const struct nfnl_info *info, if (!nft_use_inc(&chain->use)) { err = -EMFILE; - goto err_release_rule; + goto err_destroy_flow; } if (info->nlh->nlmsg_flags & NLM_F_REPLACE) { @@ -4428,6 +4428,7 @@ static int nf_tables_newrule(struct sk_buff *skb, const struct nfnl_info *info, err_destroy_flow_rule: nft_use_dec_restore(&chain->use); +err_destroy_flow: if (flow) nft_flow_rule_destroy(flow); err_release_rule: From 8bdafdf4900040a81422056cabe5e00a37bd101a Mon Sep 17 00:00:00 2001 From: Fernando Fernandez Mancera Date: Wed, 17 Dec 2025 15:46:40 +0100 Subject: [PATCH 2316/3902] netfilter: nf_conncount: update last_gc only when GC has been performed [ Upstream commit 7811ba452402d58628e68faedf38745b3d485e3c ] Currently last_gc is being updated everytime a new connection is tracked, that means that it is updated even if a GC wasn't performed. With a sufficiently high packet rate, it is possible to always bypass the GC, causing the list to grow infinitely. Update the last_gc value only when a GC has been actually performed. Fixes: d265929930e2 ("netfilter: nf_conncount: reduce unnecessary GC") Signed-off-by: Fernando Fernandez Mancera Signed-off-by: Florian Westphal Signed-off-by: Sasha Levin --- net/netfilter/nf_conncount.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/net/netfilter/nf_conncount.c b/net/netfilter/nf_conncount.c index 3c1b155f7a0ea3..828d5c64c68a3d 100644 --- a/net/netfilter/nf_conncount.c +++ b/net/netfilter/nf_conncount.c @@ -229,6 +229,7 @@ static int __nf_conncount_add(struct net *net, nf_ct_put(found_ct); } + list->last_gc = (u32)jiffies; add_new_node: if (WARN_ON_ONCE(list->count > INT_MAX)) { @@ -248,7 +249,6 @@ static int __nf_conncount_add(struct net *net, conn->jiffies32 = (u32)jiffies; list_add_tail(&conn->node, &list->head); list->count++; - list->last_gc = (u32)jiffies; out_put: if (refcounted) From 326a4b7e61d01db3507f71c8bb5e85362f607064 Mon Sep 17 00:00:00 2001 From: Alok Tiwari Date: Mon, 29 Dec 2025 21:21:18 -0800 Subject: [PATCH 2317/3902] net: marvell: prestera: fix NULL dereference on devlink_alloc() failure [ Upstream commit a428e0da1248c353557970848994f35fd3f005e2 ] devlink_alloc() may return NULL on allocation failure, but prestera_devlink_alloc() unconditionally calls devlink_priv() on the returned pointer. This leads to a NULL pointer dereference if devlink allocation fails. Add a check for a NULL devlink pointer and return NULL early to avoid the crash. Fixes: 34dd1710f5a3 ("net: marvell: prestera: Add basic devlink support") Signed-off-by: Alok Tiwari Acked-by: Elad Nachman Link: https://patch.msgid.link/20251230052124.897012-1-alok.a.tiwari@oracle.com Signed-off-by: Jakub Kicinski Signed-off-by: Sasha Levin --- drivers/net/ethernet/marvell/prestera/prestera_devlink.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/drivers/net/ethernet/marvell/prestera/prestera_devlink.c b/drivers/net/ethernet/marvell/prestera/prestera_devlink.c index 2a4c9df4eb7972..e63d95c1842f3d 100644 --- a/drivers/net/ethernet/marvell/prestera/prestera_devlink.c +++ b/drivers/net/ethernet/marvell/prestera/prestera_devlink.c @@ -387,6 +387,8 @@ struct prestera_switch *prestera_devlink_alloc(struct prestera_device *dev) dl = devlink_alloc(&prestera_dl_ops, sizeof(struct prestera_switch), dev->dev); + if (!dl) + return NULL; return devlink_priv(dl); } From b53fca69f45500eb83f379c1804275b8b578b031 Mon Sep 17 00:00:00 2001 From: Alexandre Knecht Date: Sun, 28 Dec 2025 03:00:57 +0100 Subject: [PATCH 2318/3902] bridge: fix C-VLAN preservation in 802.1ad vlan_tunnel egress [ Upstream commit 3128df6be147768fe536986fbb85db1d37806a9f ] When using an 802.1ad bridge with vlan_tunnel, the C-VLAN tag is incorrectly stripped from frames during egress processing. br_handle_egress_vlan_tunnel() uses skb_vlan_pop() to remove the S-VLAN from hwaccel before VXLAN encapsulation. However, skb_vlan_pop() also moves any "next" VLAN from the payload into hwaccel: /* move next vlan tag to hw accel tag */ __skb_vlan_pop(skb, &vlan_tci); __vlan_hwaccel_put_tag(skb, vlan_proto, vlan_tci); For QinQ frames where the C-VLAN sits in the payload, this moves it to hwaccel where it gets lost during VXLAN encapsulation. Fix by calling __vlan_hwaccel_clear_tag() directly, which clears only the hwaccel S-VLAN and leaves the payload untouched. This path is only taken when vlan_tunnel is enabled and tunnel_info is configured, so 802.1Q bridges are unaffected. Tested with 802.1ad bridge + VXLAN vlan_tunnel, verified C-VLAN preserved in VXLAN payload via tcpdump. Fixes: 11538d039ac6 ("bridge: vlan dst_metadata hooks in ingress and egress paths") Signed-off-by: Alexandre Knecht Reviewed-by: Ido Schimmel Acked-by: Nikolay Aleksandrov Link: https://patch.msgid.link/20251228020057.2788865-1-knecht.alexandre@gmail.com Signed-off-by: Jakub Kicinski Signed-off-by: Sasha Levin --- net/bridge/br_vlan_tunnel.c | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/net/bridge/br_vlan_tunnel.c b/net/bridge/br_vlan_tunnel.c index a966a6ec826343..257cae9f156983 100644 --- a/net/bridge/br_vlan_tunnel.c +++ b/net/bridge/br_vlan_tunnel.c @@ -189,7 +189,6 @@ int br_handle_egress_vlan_tunnel(struct sk_buff *skb, IP_TUNNEL_DECLARE_FLAGS(flags) = { }; struct metadata_dst *tunnel_dst; __be64 tunnel_id; - int err; if (!vlan) return 0; @@ -199,9 +198,13 @@ int br_handle_egress_vlan_tunnel(struct sk_buff *skb, return 0; skb_dst_drop(skb); - err = skb_vlan_pop(skb); - if (err) - return err; + /* For 802.1ad (QinQ), skb_vlan_pop() incorrectly moves the C-VLAN + * from payload to hwaccel after clearing S-VLAN. We only need to + * clear the hwaccel S-VLAN; the C-VLAN must stay in payload for + * correct VXLAN encapsulation. This is also correct for 802.1Q + * where no C-VLAN exists in payload. + */ + __vlan_hwaccel_clear_tag(skb); if (BR_INPUT_SKB_CB(skb)->backup_nhid) { __set_bit(IP_TUNNEL_KEY_BIT, flags); From f490af47bbee02441e356a1e0b86e3b3dd5120ff Mon Sep 17 00:00:00 2001 From: Jerry Wu Date: Thu, 25 Dec 2025 20:36:17 +0000 Subject: [PATCH 2319/3902] net: mscc: ocelot: Fix crash when adding interface under a lag [ Upstream commit 34f3ff52cb9fa7dbf04f5c734fcc4cb6ed5d1a95 ] Commit 15faa1f67ab4 ("lan966x: Fix crash when adding interface under a lag") fixed a similar issue in the lan966x driver caused by a NULL pointer dereference. The ocelot_set_aggr_pgids() function in the ocelot driver has similar logic and is susceptible to the same crash. This issue specifically affects the ocelot_vsc7514.c frontend, which leaves unused ports as NULL pointers. The felix_vsc9959.c frontend is unaffected as it uses the DSA framework which registers all ports. Fix this by checking if the port pointer is valid before accessing it. Fixes: 528d3f190c98 ("net: mscc: ocelot: drop the use of the "lags" array") Signed-off-by: Jerry Wu Reviewed-by: Vladimir Oltean Link: https://patch.msgid.link/tencent_75EF812B305E26B0869C673DD1160866C90A@qq.com Signed-off-by: Jakub Kicinski Signed-off-by: Sasha Levin --- drivers/net/ethernet/mscc/ocelot.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/drivers/net/ethernet/mscc/ocelot.c b/drivers/net/ethernet/mscc/ocelot.c index 08bee56aea35f3..c345d9b17c8921 100644 --- a/drivers/net/ethernet/mscc/ocelot.c +++ b/drivers/net/ethernet/mscc/ocelot.c @@ -2307,14 +2307,16 @@ static void ocelot_set_aggr_pgids(struct ocelot *ocelot) /* Now, set PGIDs for each active LAG */ for (lag = 0; lag < ocelot->num_phys_ports; lag++) { - struct net_device *bond = ocelot->ports[lag]->bond; + struct ocelot_port *ocelot_port = ocelot->ports[lag]; int num_active_ports = 0; + struct net_device *bond; unsigned long bond_mask; u8 aggr_idx[16]; - if (!bond || (visited & BIT(lag))) + if (!ocelot_port || !ocelot_port->bond || (visited & BIT(lag))) continue; + bond = ocelot_port->bond; bond_mask = ocelot_get_bond_mask(ocelot, bond); for_each_set_bit(port, &bond_mask, ocelot->num_phys_ports) { From 1511ba2d68467ab1259d3524dd804d371aadb9dc Mon Sep 17 00:00:00 2001 From: "yuan.gao" Date: Wed, 24 Dec 2025 14:31:45 +0800 Subject: [PATCH 2320/3902] inet: ping: Fix icmp out counting [ Upstream commit 4c0856c225b39b1def6c9a6bc56faca79550da13 ] When the ping program uses an IPPROTO_ICMP socket to send ICMP_ECHO messages, ICMP_MIB_OUTMSGS is counted twice. ping_v4_sendmsg ping_v4_push_pending_frames ip_push_pending_frames ip_finish_skb __ip_make_skb icmp_out_count(net, icmp_type); // first count icmp_out_count(sock_net(sk), user_icmph.type); // second count However, when the ping program uses an IPPROTO_RAW socket, ICMP_MIB_OUTMSGS is counted correctly only once. Therefore, the first count should be removed. Fixes: c319b4d76b9e ("net: ipv4: add IPPROTO_ICMP socket kind") Signed-off-by: yuan.gao Reviewed-by: Ido Schimmel Tested-by: Ido Schimmel Link: https://patch.msgid.link/20251224063145.3615282-1-yuan.gao@ucloud.cn Signed-off-by: Jakub Kicinski Signed-off-by: Sasha Levin --- net/ipv4/ping.c | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/net/ipv4/ping.c b/net/ipv4/ping.c index 5321c5801c64dd..a5227d23bb0b5b 100644 --- a/net/ipv4/ping.c +++ b/net/ipv4/ping.c @@ -828,10 +828,8 @@ static int ping_v4_sendmsg(struct sock *sk, struct msghdr *msg, size_t len) out_free: if (free) kfree(ipc.opt); - if (!err) { - icmp_out_count(sock_net(sk), user_icmph.type); + if (!err) return len; - } return err; do_confirm: From fe71b71f0fe05e38fdab34a71f26c53e3b09f17c Mon Sep 17 00:00:00 2001 From: Stefano Radaelli Date: Tue, 23 Dec 2025 13:09:39 +0100 Subject: [PATCH 2321/3902] net: phy: mxl-86110: Add power management and soft reset support [ Upstream commit 62f7edd59964eb588e96fce1ad35a2327ea54424 ] Implement soft_reset, suspend, and resume callbacks using genphy_soft_reset(), genphy_suspend(), and genphy_resume() to fix PHY initialization and power management issues. The soft_reset callback is needed to properly recover the PHY after an ifconfig down/up cycle. Without it, the PHY can remain in power-down state, causing MDIO register access failures during config_init(). The soft reset ensures the PHY is operational before configuration. The suspend/resume callbacks enable proper power management during system suspend/resume cycles. Fixes: b2908a989c59 ("net: phy: add driver for MaxLinear MxL86110 PHY") Signed-off-by: Stefano Radaelli Link: https://patch.msgid.link/20251223120940.407195-1-stefano.r@variscite.com Signed-off-by: Jakub Kicinski Signed-off-by: Sasha Levin --- drivers/net/phy/mxl-86110.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/drivers/net/phy/mxl-86110.c b/drivers/net/phy/mxl-86110.c index e5d137a37a1d44..42a5fe3f115f42 100644 --- a/drivers/net/phy/mxl-86110.c +++ b/drivers/net/phy/mxl-86110.c @@ -938,6 +938,9 @@ static struct phy_driver mxl_phy_drvs[] = { PHY_ID_MATCH_EXACT(PHY_ID_MXL86110), .name = "MXL86110 Gigabit Ethernet", .config_init = mxl86110_config_init, + .suspend = genphy_suspend, + .resume = genphy_resume, + .soft_reset = genphy_soft_reset, .get_wol = mxl86110_get_wol, .set_wol = mxl86110_set_wol, .led_brightness_set = mxl86110_led_brightness_set, From e00b169eaac5f7cdbf710c354c8fa76d02009115 Mon Sep 17 00:00:00 2001 From: Weiming Shi Date: Wed, 24 Dec 2025 04:35:35 +0800 Subject: [PATCH 2322/3902] net: sock: fix hardened usercopy panic in sock_recv_errqueue [ Upstream commit 2a71a1a8d0ed718b1c7a9ac61f07e5755c47ae20 ] skbuff_fclone_cache was created without defining a usercopy region, [1] unlike skbuff_head_cache which properly whitelists the cb[] field. [2] This causes a usercopy BUG() when CONFIG_HARDENED_USERCOPY is enabled and the kernel attempts to copy sk_buff.cb data to userspace via sock_recv_errqueue() -> put_cmsg(). The crash occurs when: 1. TCP allocates an skb using alloc_skb_fclone() (from skbuff_fclone_cache) [1] 2. The skb is cloned via skb_clone() using the pre-allocated fclone [3] 3. The cloned skb is queued to sk_error_queue for timestamp reporting 4. Userspace reads the error queue via recvmsg(MSG_ERRQUEUE) 5. sock_recv_errqueue() calls put_cmsg() to copy serr->ee from skb->cb [4] 6. __check_heap_object() fails because skbuff_fclone_cache has no usercopy whitelist [5] When cloned skbs allocated from skbuff_fclone_cache are used in the socket error queue, accessing the sock_exterr_skb structure in skb->cb via put_cmsg() triggers a usercopy hardening violation: [ 5.379589] usercopy: Kernel memory exposure attempt detected from SLUB object 'skbuff_fclone_cache' (offset 296, size 16)! [ 5.382796] kernel BUG at mm/usercopy.c:102! [ 5.383923] Oops: invalid opcode: 0000 [#1] SMP KASAN NOPTI [ 5.384903] CPU: 1 UID: 0 PID: 138 Comm: poc_put_cmsg Not tainted 6.12.57 #7 [ 5.384903] Hardware name: QEMU Standard PC (i440FX + PIIX, 1996), BIOS rel-1.16.3-0-ga6ed6b701f0a-prebuilt.qemu.org 04/01/2014 [ 5.384903] RIP: 0010:usercopy_abort+0x6c/0x80 [ 5.384903] Code: 1a 86 51 48 c7 c2 40 15 1a 86 41 52 48 c7 c7 c0 15 1a 86 48 0f 45 d6 48 c7 c6 80 15 1a 86 48 89 c1 49 0f 45 f3 e8 84 27 88 ff <0f> 0b 490 [ 5.384903] RSP: 0018:ffffc900006f77a8 EFLAGS: 00010246 [ 5.384903] RAX: 000000000000006f RBX: ffff88800f0ad2a8 RCX: 1ffffffff0f72e74 [ 5.384903] RDX: 0000000000000000 RSI: 0000000000000004 RDI: ffffffff87b973a0 [ 5.384903] RBP: 0000000000000010 R08: 0000000000000000 R09: fffffbfff0f72e74 [ 5.384903] R10: 0000000000000003 R11: 79706f6372657375 R12: 0000000000000001 [ 5.384903] R13: ffff88800f0ad2b8 R14: ffffea00003c2b40 R15: ffffea00003c2b00 [ 5.384903] FS: 0000000011bc4380(0000) GS:ffff8880bf100000(0000) knlGS:0000000000000000 [ 5.384903] CS: 0010 DS: 0000 ES: 0000 CR0: 0000000080050033 [ 5.384903] CR2: 000056aa3b8e5fe4 CR3: 000000000ea26004 CR4: 0000000000770ef0 [ 5.384903] PKRU: 55555554 [ 5.384903] Call Trace: [ 5.384903] [ 5.384903] __check_heap_object+0x9a/0xd0 [ 5.384903] __check_object_size+0x46c/0x690 [ 5.384903] put_cmsg+0x129/0x5e0 [ 5.384903] sock_recv_errqueue+0x22f/0x380 [ 5.384903] tls_sw_recvmsg+0x7ed/0x1960 [ 5.384903] ? srso_alias_return_thunk+0x5/0xfbef5 [ 5.384903] ? schedule+0x6d/0x270 [ 5.384903] ? srso_alias_return_thunk+0x5/0xfbef5 [ 5.384903] ? mutex_unlock+0x81/0xd0 [ 5.384903] ? __pfx_mutex_unlock+0x10/0x10 [ 5.384903] ? __pfx_tls_sw_recvmsg+0x10/0x10 [ 5.384903] ? _raw_spin_lock_irqsave+0x8f/0xf0 [ 5.384903] ? _raw_read_unlock_irqrestore+0x20/0x40 [ 5.384903] ? srso_alias_return_thunk+0x5/0xfbef5 The crash offset 296 corresponds to skb2->cb within skbuff_fclones: - sizeof(struct sk_buff) = 232 - offsetof(struct sk_buff, cb) = 40 - offset of skb2.cb in fclones = 232 + 40 = 272 - crash offset 296 = 272 + 24 (inside sock_exterr_skb.ee) This patch uses a local stack variable as a bounce buffer to avoid the hardened usercopy check failure. [1] https://elixir.bootlin.com/linux/v6.12.62/source/net/ipv4/tcp.c#L885 [2] https://elixir.bootlin.com/linux/v6.12.62/source/net/core/skbuff.c#L5104 [3] https://elixir.bootlin.com/linux/v6.12.62/source/net/core/skbuff.c#L5566 [4] https://elixir.bootlin.com/linux/v6.12.62/source/net/core/skbuff.c#L5491 [5] https://elixir.bootlin.com/linux/v6.12.62/source/mm/slub.c#L5719 Fixes: 6d07d1cd300f ("usercopy: Restrict non-usercopy caches to size 0") Reported-by: Xiang Mei Signed-off-by: Weiming Shi Reviewed-by: Eric Dumazet Link: https://patch.msgid.link/20251223203534.1392218-2-bestswngs@gmail.com Signed-off-by: Jakub Kicinski Signed-off-by: Sasha Levin --- net/core/sock.c | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/net/core/sock.c b/net/core/sock.c index dc03d4b5909a2a..5a38837a583843 100644 --- a/net/core/sock.c +++ b/net/core/sock.c @@ -3836,7 +3836,7 @@ void sock_enable_timestamp(struct sock *sk, enum sock_flags flag) int sock_recv_errqueue(struct sock *sk, struct msghdr *msg, int len, int level, int type) { - struct sock_exterr_skb *serr; + struct sock_extended_err ee; struct sk_buff *skb; int copied, err; @@ -3856,8 +3856,9 @@ int sock_recv_errqueue(struct sock *sk, struct msghdr *msg, int len, sock_recv_timestamp(msg, sk, skb); - serr = SKB_EXT_ERR(skb); - put_cmsg(msg, level, type, sizeof(serr->ee), &serr->ee); + /* We must use a bounce buffer for CONFIG_HARDENED_USERCOPY=y */ + ee = SKB_EXT_ERR(skb)->ee; + put_cmsg(msg, level, type, sizeof(ee), &ee); msg->msg_flags |= MSG_ERRQUEUE; err = copied; From 8bdc624bf33d36763bcc1211e54ff27494de8a6b Mon Sep 17 00:00:00 2001 From: Di Zhu Date: Wed, 24 Dec 2025 09:22:24 +0800 Subject: [PATCH 2323/3902] netdev: preserve NETIF_F_ALL_FOR_ALL across TSO updates [ Upstream commit 02d1e1a3f9239cdb3ecf2c6d365fb959d1bf39df ] Directly increment the TSO features incurs a side effect: it will also directly clear the flags in NETIF_F_ALL_FOR_ALL on the master device, which can cause issues such as the inability to enable the nocache copy feature on the bonding driver. The fix is to include NETIF_F_ALL_FOR_ALL in the update mask, thereby preventing it from being cleared. Fixes: b0ce3508b25e ("bonding: allow TSO being set on bonding master") Signed-off-by: Di Zhu Link: https://patch.msgid.link/20251224012224.56185-1-zhud@hygon.cn Signed-off-by: Jakub Kicinski Signed-off-by: Sasha Levin --- include/linux/netdevice.h | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/include/linux/netdevice.h b/include/linux/netdevice.h index 77c46a2823eca1..c6c04cd0a6816a 100644 --- a/include/linux/netdevice.h +++ b/include/linux/netdevice.h @@ -5299,7 +5299,8 @@ netdev_features_t netdev_increment_features(netdev_features_t all, static inline netdev_features_t netdev_add_tso_features(netdev_features_t features, netdev_features_t mask) { - return netdev_increment_features(features, NETIF_F_ALL_TSO, mask); + return netdev_increment_features(features, NETIF_F_ALL_TSO | + NETIF_F_ALL_FOR_ALL, mask); } int __netdev_update_features(struct net_device *dev); From 6584963258249da7fc6aee5ed27e90cf0d4065a8 Mon Sep 17 00:00:00 2001 From: Patrisious Haddad Date: Thu, 25 Dec 2025 15:27:13 +0200 Subject: [PATCH 2324/3902] net/mlx5: Lag, multipath, give priority for routes with smaller network prefix [ Upstream commit 31057979cdadfee9f934746fd84046b43506ba61 ] Today multipath offload is controlled by a single route and the route controlling is selected if it meets one of the following criteria: 1. No controlling route is set. 2. New route destination is the same as old one. 3. New route metric is lower than old route metric. This can cause unwanted behaviour in case a new route is added with a smaller network prefix which should get the priority. Fix this by adding a new criteria to give priority to new route with a smaller network prefix. Fixes: ad11c4f1d8fd ("net/mlx5e: Lag, Only handle events from highest priority multipath entry") Signed-off-by: Patrisious Haddad Signed-off-by: Mark Bloch Link: https://patch.msgid.link/20251225132717.358820-2-mbloch@nvidia.com Signed-off-by: Jakub Kicinski Signed-off-by: Sasha Levin --- drivers/net/ethernet/mellanox/mlx5/core/lag/mp.c | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/drivers/net/ethernet/mellanox/mlx5/core/lag/mp.c b/drivers/net/ethernet/mellanox/mlx5/core/lag/mp.c index aee17fcf3b36c3..cdc99fe5c95686 100644 --- a/drivers/net/ethernet/mellanox/mlx5/core/lag/mp.c +++ b/drivers/net/ethernet/mellanox/mlx5/core/lag/mp.c @@ -173,10 +173,15 @@ static void mlx5_lag_fib_route_event(struct mlx5_lag *ldev, unsigned long event, } /* Handle multipath entry with lower priority value */ - if (mp->fib.mfi && mp->fib.mfi != fi && + if (mp->fib.mfi && (mp->fib.dst != fen_info->dst || mp->fib.dst_len != fen_info->dst_len) && - fi->fib_priority >= mp->fib.priority) + mp->fib.dst_len <= fen_info->dst_len && + !(mp->fib.dst_len == fen_info->dst_len && + fi->fib_priority < mp->fib.priority)) { + mlx5_core_dbg(ldev->pf[idx].dev, + "Multipath entry with lower priority was rejected\n"); return; + } nh_dev0 = mlx5_lag_get_next_fib_dev(ldev, fi, NULL); nh_dev1 = mlx5_lag_get_next_fib_dev(ldev, fi, nh_dev0); From cba6cc0f46540350d250e8355c84e339d99310b5 Mon Sep 17 00:00:00 2001 From: Alexei Lazar Date: Thu, 25 Dec 2025 15:27:14 +0200 Subject: [PATCH 2325/3902] net/mlx5e: Don't gate FEC histograms on ppcnt_statistical_group [ Upstream commit 6c75dc9de40ff91ec2b621b78f6cd9031762067c ] Currently, the ppcnt_statistical_group capability check incorrectly gates access to FEC histogram statistics. This capability applies only to statistical and physical counter groups, not for histogram data. Restrict the ppcnt_statistical_group check to the Physical_Layer_Counters and Physical_Layer_Statistical_Counters groups. Histogram statistics access remains gated by the pphcr capability. The issue is harmless as of today, as it happens that ppcnt_statistical_group is set on all existing devices that have pphcr set. Fixes: 6b81b8a0b197 ("net/mlx5e: Don't query FEC statistics when FEC is disabled") Signed-off-by: Alexei Lazar Reviewed-by: Tariq Toukan Signed-off-by: Mark Bloch Link: https://patch.msgid.link/20251225132717.358820-3-mbloch@nvidia.com Signed-off-by: Jakub Kicinski Signed-off-by: Sasha Levin --- drivers/net/ethernet/mellanox/mlx5/core/en_stats.c | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/drivers/net/ethernet/mellanox/mlx5/core/en_stats.c b/drivers/net/ethernet/mellanox/mlx5/core/en_stats.c index a2802cfc9b9894..a8af84fc976383 100644 --- a/drivers/net/ethernet/mellanox/mlx5/core/en_stats.c +++ b/drivers/net/ethernet/mellanox/mlx5/core/en_stats.c @@ -1608,12 +1608,13 @@ void mlx5e_stats_fec_get(struct mlx5e_priv *priv, { int mode = fec_active_mode(priv->mdev); - if (mode == MLX5E_FEC_NOFEC || - !MLX5_CAP_PCAM_FEATURE(priv->mdev, ppcnt_statistical_group)) + if (mode == MLX5E_FEC_NOFEC) return; - fec_set_corrected_bits_total(priv, fec_stats); - fec_set_block_stats(priv, mode, fec_stats); + if (MLX5_CAP_PCAM_FEATURE(priv->mdev, ppcnt_statistical_group)) { + fec_set_corrected_bits_total(priv, fec_stats); + fec_set_block_stats(priv, mode, fec_stats); + } if (MLX5_CAP_PCAM_REG(priv->mdev, pphcr)) fec_set_histograms_stats(priv, mode, hist); From d35ab9fb57945e3f1eba71fe28ffa43d517217fd Mon Sep 17 00:00:00 2001 From: Gal Pressman Date: Thu, 25 Dec 2025 15:27:16 +0200 Subject: [PATCH 2326/3902] net/mlx5e: Don't print error message due to invalid module [ Upstream commit 144297e2a24e3e54aee1180ec21120ea38822b97 ] Dumping module EEPROM on newer modules is supported through the netlink interface only. Querying with old userspace ethtool (or other tools, such as 'lshw') which still uses the ioctl interface results in an error message that could flood dmesg (in addition to the expected error return value). The original message was added under the assumption that the driver should be able to handle all module types, but now that such flows are easily triggered from userspace, it doesn't serve its purpose. Change the log level of the print in mlx5_query_module_eeprom() to debug. Fixes: bb64143eee8c ("net/mlx5e: Add ethtool support for dump module EEPROM") Signed-off-by: Gal Pressman Reviewed-by: Tariq Toukan Signed-off-by: Mark Bloch Link: https://patch.msgid.link/20251225132717.358820-5-mbloch@nvidia.com Signed-off-by: Jakub Kicinski Signed-off-by: Sasha Levin --- drivers/net/ethernet/mellanox/mlx5/core/port.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/drivers/net/ethernet/mellanox/mlx5/core/port.c b/drivers/net/ethernet/mellanox/mlx5/core/port.c index aa9f2b0a77d36f..876e648c91ba8b 100644 --- a/drivers/net/ethernet/mellanox/mlx5/core/port.c +++ b/drivers/net/ethernet/mellanox/mlx5/core/port.c @@ -430,7 +430,8 @@ int mlx5_query_module_eeprom(struct mlx5_core_dev *dev, mlx5_qsfp_eeprom_params_set(&query.i2c_address, &query.page, &offset); break; default: - mlx5_core_err(dev, "Module ID not recognized: 0x%x\n", module_id); + mlx5_core_dbg(dev, "Module ID not recognized: 0x%x\n", + module_id); return -EINVAL; } From b46675e1c593673f667e75ae14d82bc1f24d87a5 Mon Sep 17 00:00:00 2001 From: Cosmin Ratiu Date: Thu, 25 Dec 2025 15:27:17 +0200 Subject: [PATCH 2327/3902] net/mlx5e: Dealloc forgotten PSP RX modify header [ Upstream commit 0462a15d2d1fafd3d48cf3c7c67393e42d03908c ] The commit which added RX steering rules for PSP forgot to free a modify header HW object on the cleanup path, which lead to health errors when reloading the driver and uninitializing the device: mlx5_core 0000:08:00.0: poll_health:803:(pid 3021): Fatal error 3 detected Fix that by saving the modify header pointer in the PSP steering struct and deallocating it after freeing the rule which references it. Fixes: 9536fbe10c9d ("net/mlx5e: Add PSP steering in local NIC RX") Signed-off-by: Cosmin Ratiu Reviewed-by: Dragos Tatulea Reviewed-by: Tariq Toukan Signed-off-by: Mark Bloch Link: https://patch.msgid.link/20251225132717.358820-6-mbloch@nvidia.com Signed-off-by: Jakub Kicinski Signed-off-by: Sasha Levin --- .../net/ethernet/mellanox/mlx5/core/en_accel/psp.c | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/drivers/net/ethernet/mellanox/mlx5/core/en_accel/psp.c b/drivers/net/ethernet/mellanox/mlx5/core/en_accel/psp.c index 8565cfe8d7dce8..943d6fc6e7a047 100644 --- a/drivers/net/ethernet/mellanox/mlx5/core/en_accel/psp.c +++ b/drivers/net/ethernet/mellanox/mlx5/core/en_accel/psp.c @@ -41,6 +41,7 @@ struct mlx5e_accel_fs_psp_prot { struct mlx5_flow_table *ft; struct mlx5_flow_group *miss_group; struct mlx5_flow_handle *miss_rule; + struct mlx5_modify_hdr *rx_modify_hdr; struct mlx5_flow_destination default_dest; struct mlx5e_psp_rx_err rx_err; u32 refcnt; @@ -217,13 +218,19 @@ static int accel_psp_fs_rx_err_create_ft(struct mlx5e_psp_fs *fs, return err; } -static void accel_psp_fs_rx_fs_destroy(struct mlx5e_accel_fs_psp_prot *fs_prot) +static void accel_psp_fs_rx_fs_destroy(struct mlx5e_psp_fs *fs, + struct mlx5e_accel_fs_psp_prot *fs_prot) { if (fs_prot->def_rule) { mlx5_del_flow_rules(fs_prot->def_rule); fs_prot->def_rule = NULL; } + if (fs_prot->rx_modify_hdr) { + mlx5_modify_header_dealloc(fs->mdev, fs_prot->rx_modify_hdr); + fs_prot->rx_modify_hdr = NULL; + } + if (fs_prot->miss_rule) { mlx5_del_flow_rules(fs_prot->miss_rule); fs_prot->miss_rule = NULL; @@ -327,6 +334,7 @@ static int accel_psp_fs_rx_create_ft(struct mlx5e_psp_fs *fs, modify_hdr = NULL; goto out_err; } + fs_prot->rx_modify_hdr = modify_hdr; flow_act.action = MLX5_FLOW_CONTEXT_ACTION_FWD_DEST | MLX5_FLOW_CONTEXT_ACTION_CRYPTO_DECRYPT | @@ -347,7 +355,7 @@ static int accel_psp_fs_rx_create_ft(struct mlx5e_psp_fs *fs, goto out; out_err: - accel_psp_fs_rx_fs_destroy(fs_prot); + accel_psp_fs_rx_fs_destroy(fs, fs_prot); out: kvfree(flow_group_in); kvfree(spec); @@ -364,7 +372,7 @@ static int accel_psp_fs_rx_destroy(struct mlx5e_psp_fs *fs, enum accel_fs_psp_ty /* The netdev unreg already happened, so all offloaded rule are already removed */ fs_prot = &accel_psp->fs_prot[type]; - accel_psp_fs_rx_fs_destroy(fs_prot); + accel_psp_fs_rx_fs_destroy(fs, fs_prot); accel_psp_fs_rx_err_destroy_ft(fs, &fs_prot->rx_err); From f2c4bcfa193eef1b7457a56be9c47a8de015f225 Mon Sep 17 00:00:00 2001 From: Frank Liang Date: Wed, 31 Dec 2025 22:58:08 +0800 Subject: [PATCH 2328/3902] net/ena: fix missing lock when update devlink params [ Upstream commit 8da901ffe497a53fa4ecc3ceed0e6d771586f88e ] Fix assert lock warning while calling devl_param_driverinit_value_set() in ena. WARNING: net/devlink/core.c:261 at devl_assert_locked+0x62/0x90, CPU#0: kworker/0:0/9 CPU: 0 UID: 0 PID: 9 Comm: kworker/0:0 Not tainted 6.19.0-rc2+ #1 PREEMPT(lazy) Hardware name: Amazon EC2 m8i-flex.4xlarge/, BIOS 1.0 10/16/2017 Workqueue: events work_for_cpu_fn RIP: 0010:devl_assert_locked+0x62/0x90 Call Trace: devl_param_driverinit_value_set+0x15/0x1c0 ena_devlink_alloc+0x18c/0x220 [ena] ? __pfx_ena_devlink_alloc+0x10/0x10 [ena] ? trace_hardirqs_on+0x18/0x140 ? lockdep_hardirqs_on+0x8c/0x130 ? __raw_spin_unlock_irqrestore+0x5d/0x80 ? __raw_spin_unlock_irqrestore+0x46/0x80 ? devm_ioremap_wc+0x9a/0xd0 ena_probe+0x4d2/0x1b20 [ena] ? __lock_acquire+0x56a/0xbd0 ? __pfx_ena_probe+0x10/0x10 [ena] ? local_clock+0x15/0x30 ? __lock_release.isra.0+0x1c9/0x340 ? mark_held_locks+0x40/0x70 ? lockdep_hardirqs_on_prepare.part.0+0x92/0x170 ? trace_hardirqs_on+0x18/0x140 ? lockdep_hardirqs_on+0x8c/0x130 ? __raw_spin_unlock_irqrestore+0x5d/0x80 ? __raw_spin_unlock_irqrestore+0x46/0x80 ? __pfx_ena_probe+0x10/0x10 [ena] ...... Fixes: 816b52624cf6 ("net: ena: Control PHC enable through devlink") Signed-off-by: Frank Liang Reviewed-by: David Arinzon Reviewed-by: Jiri Pirko Link: https://patch.msgid.link/20251231145808.6103-1-xiliang@redhat.com Signed-off-by: Jakub Kicinski Signed-off-by: Sasha Levin --- drivers/net/ethernet/amazon/ena/ena_devlink.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/drivers/net/ethernet/amazon/ena/ena_devlink.c b/drivers/net/ethernet/amazon/ena/ena_devlink.c index ac81c24016dd47..4772185e669d29 100644 --- a/drivers/net/ethernet/amazon/ena/ena_devlink.c +++ b/drivers/net/ethernet/amazon/ena/ena_devlink.c @@ -53,10 +53,12 @@ void ena_devlink_disable_phc_param(struct devlink *devlink) { union devlink_param_value value; + devl_lock(devlink); value.vbool = false; devl_param_driverinit_value_set(devlink, DEVLINK_PARAM_GENERIC_ID_ENABLE_PHC, value); + devl_unlock(devlink); } static void ena_devlink_port_register(struct devlink *devlink) @@ -145,10 +147,12 @@ static int ena_devlink_configure_params(struct devlink *devlink) return rc; } + devl_lock(devlink); value.vbool = ena_phc_is_enabled(adapter); devl_param_driverinit_value_set(devlink, DEVLINK_PARAM_GENERIC_ID_ENABLE_PHC, value); + devl_unlock(devlink); return 0; } From 8181c79757cc9383f9697745d24793555429c888 Mon Sep 17 00:00:00 2001 From: Zilin Guan Date: Tue, 30 Dec 2025 07:18:53 +0000 Subject: [PATCH 2329/3902] net: wwan: iosm: Fix memory leak in ipc_mux_deinit() [ Upstream commit 92e6e0a87f6860a4710f9494f8c704d498ae60f8 ] Commit 1f52d7b62285 ("net: wwan: iosm: Enable M.2 7360 WWAN card support") allocated memory for pp_qlt in ipc_mux_init() but did not free it in ipc_mux_deinit(). This results in a memory leak when the driver is unloaded. Free the allocated memory in ipc_mux_deinit() to fix the leak. Fixes: 1f52d7b62285 ("net: wwan: iosm: Enable M.2 7360 WWAN card support") Co-developed-by: Jianhao Xu Signed-off-by: Jianhao Xu Signed-off-by: Zilin Guan Reviewed-by: Loic Poulain Link: https://patch.msgid.link/20251230071853.1062223-1-zilin@seu.edu.cn Signed-off-by: Jakub Kicinski Signed-off-by: Sasha Levin --- drivers/net/wwan/iosm/iosm_ipc_mux.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/drivers/net/wwan/iosm/iosm_ipc_mux.c b/drivers/net/wwan/iosm/iosm_ipc_mux.c index fc928b298a9840..b846889fcb0997 100644 --- a/drivers/net/wwan/iosm/iosm_ipc_mux.c +++ b/drivers/net/wwan/iosm/iosm_ipc_mux.c @@ -456,6 +456,7 @@ void ipc_mux_deinit(struct iosm_mux *ipc_mux) struct sk_buff_head *free_list; union mux_msg mux_msg; struct sk_buff *skb; + int i; if (!ipc_mux->initialized) return; @@ -479,5 +480,10 @@ void ipc_mux_deinit(struct iosm_mux *ipc_mux) ipc_mux->channel->dl_pipe.is_open = false; } + if (ipc_mux->protocol != MUX_LITE) { + for (i = 0; i < IPC_MEM_MUX_IP_SESSION_ENTRIES; i++) + kfree(ipc_mux->ul_adb.pp_qlt[i]); + } + kfree(ipc_mux); } From e3c7381389759ec88aea65b266e7dc7f1f010b25 Mon Sep 17 00:00:00 2001 From: Srijit Bose Date: Wed, 31 Dec 2025 00:36:25 -0800 Subject: [PATCH 2330/3902] bnxt_en: Fix potential data corruption with HW GRO/LRO [ Upstream commit ffeafa65b2b26df2f5b5a6118d3174f17bd12ec5 ] Fix the max number of bits passed to find_first_zero_bit() in bnxt_alloc_agg_idx(). We were incorrectly passing the number of long words. find_first_zero_bit() may fail to find a zero bit and cause a wrong ID to be used. If the wrong ID is already in use, this can cause data corruption. Sometimes an error like this can also be seen: bnxt_en 0000:83:00.0 enp131s0np0: TPA end agg_buf 2 != expected agg_bufs 1 Fix it by passing the correct number of bits MAX_TPA_P5. Use DECLARE_BITMAP() to more cleanly define the bitmap. Add a sanity check to warn if a bit cannot be found and reset the ring [MChan]. Fixes: ec4d8e7cf024 ("bnxt_en: Add TPA ID mapping logic for 57500 chips.") Reviewed-by: Ray Jui Signed-off-by: Srijit Bose Signed-off-by: Michael Chan Reviewed-by: Vadim Fedorenko Link: https://patch.msgid.link/20251231083625.3911652-1-michael.chan@broadcom.com Signed-off-by: Jakub Kicinski Signed-off-by: Sasha Levin --- drivers/net/ethernet/broadcom/bnxt/bnxt.c | 15 ++++++++++++--- drivers/net/ethernet/broadcom/bnxt/bnxt.h | 4 +--- 2 files changed, 13 insertions(+), 6 deletions(-) diff --git a/drivers/net/ethernet/broadcom/bnxt/bnxt.c b/drivers/net/ethernet/broadcom/bnxt/bnxt.c index 0366323ab0676b..95c774d98da66a 100644 --- a/drivers/net/ethernet/broadcom/bnxt/bnxt.c +++ b/drivers/net/ethernet/broadcom/bnxt/bnxt.c @@ -1482,9 +1482,11 @@ static u16 bnxt_alloc_agg_idx(struct bnxt_rx_ring_info *rxr, u16 agg_id) struct bnxt_tpa_idx_map *map = rxr->rx_tpa_idx_map; u16 idx = agg_id & MAX_TPA_P5_MASK; - if (test_bit(idx, map->agg_idx_bmap)) - idx = find_first_zero_bit(map->agg_idx_bmap, - BNXT_AGG_IDX_BMAP_SIZE); + if (test_bit(idx, map->agg_idx_bmap)) { + idx = find_first_zero_bit(map->agg_idx_bmap, MAX_TPA_P5); + if (idx >= MAX_TPA_P5) + return INVALID_HW_RING_ID; + } __set_bit(idx, map->agg_idx_bmap); map->agg_id_tbl[agg_id] = idx; return idx; @@ -1548,6 +1550,13 @@ static void bnxt_tpa_start(struct bnxt *bp, struct bnxt_rx_ring_info *rxr, if (bp->flags & BNXT_FLAG_CHIP_P5_PLUS) { agg_id = TPA_START_AGG_ID_P5(tpa_start); agg_id = bnxt_alloc_agg_idx(rxr, agg_id); + if (unlikely(agg_id == INVALID_HW_RING_ID)) { + netdev_warn(bp->dev, "Unable to allocate agg ID for ring %d, agg 0x%x\n", + rxr->bnapi->index, + TPA_START_AGG_ID_P5(tpa_start)); + bnxt_sched_reset_rxr(bp, rxr); + return; + } } else { agg_id = TPA_START_AGG_ID(tpa_start); } diff --git a/drivers/net/ethernet/broadcom/bnxt/bnxt.h b/drivers/net/ethernet/broadcom/bnxt/bnxt.h index 3613a172483a16..45bbaec75ded48 100644 --- a/drivers/net/ethernet/broadcom/bnxt/bnxt.h +++ b/drivers/net/ethernet/broadcom/bnxt/bnxt.h @@ -1079,11 +1079,9 @@ struct bnxt_tpa_info { struct rx_agg_cmp *agg_arr; }; -#define BNXT_AGG_IDX_BMAP_SIZE (MAX_TPA_P5 / BITS_PER_LONG) - struct bnxt_tpa_idx_map { u16 agg_id_tbl[1024]; - unsigned long agg_idx_bmap[BNXT_AGG_IDX_BMAP_SIZE]; + DECLARE_BITMAP(agg_idx_bmap, MAX_TPA_P5); }; struct bnxt_rx_ring_info { From a5e2d902f64c76169c771f584559c82b588090e3 Mon Sep 17 00:00:00 2001 From: Kommula Shiva Shankar Date: Fri, 2 Jan 2026 15:49:00 +0530 Subject: [PATCH 2331/3902] virtio_net: fix device mismatch in devm_kzalloc/devm_kfree [ Upstream commit acb4bc6e1ba34ae1a34a9334a1ce8474c909466e ] Initial rss_hdr allocation uses virtio_device->device, but virtnet_set_queues() frees using net_device->device. This device mismatch causing below devres warning [ 3788.514041] ------------[ cut here ]------------ [ 3788.514044] WARNING: drivers/base/devres.c:1095 at devm_kfree+0x84/0x98, CPU#16: vdpa/1463 [ 3788.514054] Modules linked in: octep_vdpa virtio_net virtio_vdpa [last unloaded: virtio_vdpa] [ 3788.514064] CPU: 16 UID: 0 PID: 1463 Comm: vdpa Tainted: G W 6.18.0 #10 PREEMPT [ 3788.514067] Tainted: [W]=WARN [ 3788.514069] Hardware name: Marvell CN106XX board (DT) [ 3788.514071] pstate: 63400009 (nZCv daif +PAN -UAO +TCO +DIT -SSBS BTYPE=--) [ 3788.514074] pc : devm_kfree+0x84/0x98 [ 3788.514076] lr : devm_kfree+0x54/0x98 [ 3788.514079] sp : ffff800084e2f220 [ 3788.514080] x29: ffff800084e2f220 x28: ffff0003b2366000 x27: 000000000000003f [ 3788.514085] x26: 000000000000003f x25: ffff000106f17c10 x24: 0000000000000080 [ 3788.514089] x23: ffff00045bb8ab08 x22: ffff00045bb8a000 x21: 0000000000000018 [ 3788.514093] x20: ffff0004355c3080 x19: ffff00045bb8aa00 x18: 0000000000080000 [ 3788.514098] x17: 0000000000000040 x16: 000000000000001f x15: 000000000007ffff [ 3788.514102] x14: 0000000000000488 x13: 0000000000000005 x12: 00000000000fffff [ 3788.514106] x11: ffffffffffffffff x10: 0000000000000005 x9 : ffff800080c8c05c [ 3788.514110] x8 : ffff800084e2eeb8 x7 : 0000000000000000 x6 : 000000000000003f [ 3788.514115] x5 : ffff8000831bafe0 x4 : ffff800080c8b010 x3 : ffff0004355c3080 [ 3788.514119] x2 : ffff0004355c3080 x1 : 0000000000000000 x0 : 0000000000000000 [ 3788.514123] Call trace: [ 3788.514125] devm_kfree+0x84/0x98 (P) [ 3788.514129] virtnet_set_queues+0x134/0x2e8 [virtio_net] [ 3788.514135] virtnet_probe+0x9c0/0xe00 [virtio_net] [ 3788.514139] virtio_dev_probe+0x1e0/0x338 [ 3788.514144] really_probe+0xc8/0x3a0 [ 3788.514149] __driver_probe_device+0x84/0x170 [ 3788.514152] driver_probe_device+0x44/0x120 [ 3788.514155] __device_attach_driver+0xc4/0x168 [ 3788.514158] bus_for_each_drv+0x8c/0xf0 [ 3788.514161] __device_attach+0xa4/0x1c0 [ 3788.514164] device_initial_probe+0x1c/0x30 [ 3788.514168] bus_probe_device+0xb4/0xc0 [ 3788.514170] device_add+0x614/0x828 [ 3788.514173] register_virtio_device+0x214/0x258 [ 3788.514175] virtio_vdpa_probe+0xa0/0x110 [virtio_vdpa] [ 3788.514179] vdpa_dev_probe+0xa8/0xd8 [ 3788.514183] really_probe+0xc8/0x3a0 [ 3788.514186] __driver_probe_device+0x84/0x170 [ 3788.514189] driver_probe_device+0x44/0x120 [ 3788.514192] __device_attach_driver+0xc4/0x168 [ 3788.514195] bus_for_each_drv+0x8c/0xf0 [ 3788.514197] __device_attach+0xa4/0x1c0 [ 3788.514200] device_initial_probe+0x1c/0x30 [ 3788.514203] bus_probe_device+0xb4/0xc0 [ 3788.514206] device_add+0x614/0x828 [ 3788.514209] _vdpa_register_device+0x58/0x88 [ 3788.514211] octep_vdpa_dev_add+0x104/0x228 [octep_vdpa] [ 3788.514215] vdpa_nl_cmd_dev_add_set_doit+0x2d0/0x3c0 [ 3788.514218] genl_family_rcv_msg_doit+0xe4/0x158 [ 3788.514222] genl_rcv_msg+0x218/0x298 [ 3788.514225] netlink_rcv_skb+0x64/0x138 [ 3788.514229] genl_rcv+0x40/0x60 [ 3788.514233] netlink_unicast+0x32c/0x3b0 [ 3788.514237] netlink_sendmsg+0x170/0x3b8 [ 3788.514241] __sys_sendto+0x12c/0x1c0 [ 3788.514246] __arm64_sys_sendto+0x30/0x48 [ 3788.514249] invoke_syscall.constprop.0+0x58/0xf8 [ 3788.514255] do_el0_svc+0x48/0xd0 [ 3788.514259] el0_svc+0x48/0x210 [ 3788.514264] el0t_64_sync_handler+0xa0/0xe8 [ 3788.514268] el0t_64_sync+0x198/0x1a0 [ 3788.514271] ---[ end trace 0000000000000000 ]--- Fix by using virtio_device->device consistently for allocation and deallocation Fixes: 4944be2f5ad8c ("virtio_net: Allocate rss_hdr with devres") Signed-off-by: Kommula Shiva Shankar Acked-by: Michael S. Tsirkin Acked-by: Jason Wang Reviewed-by: Xuan Zhuo Link: https://patch.msgid.link/20260102101900.692770-1-kshankar@marvell.com Signed-off-by: Jakub Kicinski Signed-off-by: Sasha Levin --- drivers/net/virtio_net.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/drivers/net/virtio_net.c b/drivers/net/virtio_net.c index 8e04adb57f52ad..4e1a5291099a5e 100644 --- a/drivers/net/virtio_net.c +++ b/drivers/net/virtio_net.c @@ -3783,7 +3783,7 @@ static int virtnet_set_queues(struct virtnet_info *vi, u16 queue_pairs) if (vi->has_rss && !netif_is_rxfh_configured(dev)) { old_rss_hdr = vi->rss_hdr; old_rss_trailer = vi->rss_trailer; - vi->rss_hdr = devm_kzalloc(&dev->dev, virtnet_rss_hdr_size(vi), GFP_KERNEL); + vi->rss_hdr = devm_kzalloc(&vi->vdev->dev, virtnet_rss_hdr_size(vi), GFP_KERNEL); if (!vi->rss_hdr) { vi->rss_hdr = old_rss_hdr; return -ENOMEM; @@ -3794,7 +3794,7 @@ static int virtnet_set_queues(struct virtnet_info *vi, u16 queue_pairs) if (!virtnet_commit_rss_command(vi)) { /* restore ctrl_rss if commit_rss_command failed */ - devm_kfree(&dev->dev, vi->rss_hdr); + devm_kfree(&vi->vdev->dev, vi->rss_hdr); vi->rss_hdr = old_rss_hdr; vi->rss_trailer = old_rss_trailer; @@ -3802,7 +3802,7 @@ static int virtnet_set_queues(struct virtnet_info *vi, u16 queue_pairs) queue_pairs); return -EINVAL; } - devm_kfree(&dev->dev, old_rss_hdr); + devm_kfree(&vi->vdev->dev, old_rss_hdr); goto succ; } From 088ca99dbb039c444c3ff987c5412a73f4f0cbf8 Mon Sep 17 00:00:00 2001 From: Florian Westphal Date: Fri, 2 Jan 2026 15:00:07 +0100 Subject: [PATCH 2332/3902] inet: frags: drop fraglist conntrack references [ Upstream commit 2ef02ac38d3c17f34a00c4b267d961a8d4b45d1a ] Jakub added a warning in nf_conntrack_cleanup_net_list() to make debugging leaked skbs/conntrack references more obvious. syzbot reports this as triggering, and I can also reproduce this via ip_defrag.sh selftest: conntrack cleanup blocked for 60s WARNING: net/netfilter/nf_conntrack_core.c:2512 [..] conntrack clenups gets stuck because there are skbs with still hold nf_conn references via their frag_list. net.core.skb_defer_max=0 makes the hang disappear. Eric Dumazet points out that skb_release_head_state() doesn't follow the fraglist. ip_defrag.sh can only reproduce this problem since commit 6471658dc66c ("udp: use skb_attempt_defer_free()"), but AFAICS this problem could happen with TCP as well if pmtu discovery is off. The relevant problem path for udp is: 1. netns emits fragmented packets 2. nf_defrag_v6_hook reassembles them (in output hook) 3. reassembled skb is tracked (skb owns nf_conn reference) 4. ip6_output refragments 5. refragmented packets also own nf_conn reference (ip6_fragment calls ip6_copy_metadata()) 6. on input path, nf_defrag_v6_hook skips defragmentation: the fragments already have skb->nf_conn attached 7. skbs are reassembled via ipv6_frag_rcv() 8. skb_consume_udp -> skb_attempt_defer_free() -> skb ends up in pcpu freelist, but still has nf_conn reference. Possible solutions: 1 let defrag engine drop nf_conn entry, OR 2 export kick_defer_list_purge() and call it from the conntrack netns exit callback, OR 3 add skb_has_frag_list() check to skb_attempt_defer_free() 2 & 3 also solve ip_defrag.sh hang but share same drawback: Such reassembled skbs, queued to socket, can prevent conntrack module removal until userspace has consumed the packet. While both tcp and udp stack do call nf_reset_ct() before placing skb on socket queue, that function doesn't iterate frag_list skbs. Therefore drop nf_conn entries when they are placed in defrag queue. Keep the nf_conn entry of the first (offset 0) skb so that reassembled skb retains nf_conn entry for sake of TX path. Note that fixes tag is incorrect; it points to the commit introducing the 'ip_defrag.sh reproducible problem': no need to backport this patch to every stable kernel. Reported-by: syzbot+4393c47753b7808dac7d@syzkaller.appspotmail.com Closes: https://lore.kernel.org/netdev/693b0fa7.050a0220.4004e.040d.GAE@google.com/ Fixes: 6471658dc66c ("udp: use skb_attempt_defer_free()") Signed-off-by: Florian Westphal Reviewed-by: Eric Dumazet Link: https://patch.msgid.link/20260102140030.32367-1-fw@strlen.de Signed-off-by: Jakub Kicinski Signed-off-by: Sasha Levin --- net/ipv4/inet_fragment.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/net/ipv4/inet_fragment.c b/net/ipv4/inet_fragment.c index 001ee5c4d962e3..4e6d7467ed444b 100644 --- a/net/ipv4/inet_fragment.c +++ b/net/ipv4/inet_fragment.c @@ -488,6 +488,8 @@ int inet_frag_queue_insert(struct inet_frag_queue *q, struct sk_buff *skb, } FRAG_CB(skb)->ip_defrag_offset = offset; + if (offset) + nf_reset_ct(skb); return IPFRAG_OK; } From deee9dfb111ab00f9dfd46c0c7e36656b80f5235 Mon Sep 17 00:00:00 2001 From: Peter Zijlstra Date: Sat, 20 Dec 2025 14:14:41 +0100 Subject: [PATCH 2333/3902] perf: Ensure swevent hrtimer is properly destroyed [ Upstream commit ff5860f5088e9076ebcccf05a6ca709d5935cfa9 ] With the change to hrtimer_try_to_cancel() in perf_swevent_cancel_hrtimer() it appears possible for the hrtimer to still be active by the time the event gets freed. Make sure the event does a full hrtimer_cancel() on the free path by installing a perf_event::destroy handler. Fixes: eb3182ef0405 ("perf/core: Fix system hang caused by cpu-clock usage") Reported-by: CyberUnicorns Tested-by: CyberUnicorns Debugged-by: Thomas Gleixner Signed-off-by: Peter Zijlstra (Intel) Signed-off-by: Sasha Levin --- kernel/events/core.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/kernel/events/core.c b/kernel/events/core.c index 413b88a4e00fb9..d95f9dce018f4c 100644 --- a/kernel/events/core.c +++ b/kernel/events/core.c @@ -11835,6 +11835,11 @@ static void perf_swevent_cancel_hrtimer(struct perf_event *event) } } +static void perf_swevent_destroy_hrtimer(struct perf_event *event) +{ + hrtimer_cancel(&event->hw.hrtimer); +} + static void perf_swevent_init_hrtimer(struct perf_event *event) { struct hw_perf_event *hwc = &event->hw; @@ -11843,6 +11848,7 @@ static void perf_swevent_init_hrtimer(struct perf_event *event) return; hrtimer_setup(&hwc->hrtimer, perf_swevent_hrtimer, CLOCK_MONOTONIC, HRTIMER_MODE_REL_HARD); + event->destroy = perf_swevent_destroy_hrtimer; /* * Since hrtimers have a fixed rate, we can do a static freq->period From a1c0a4ccbe6dbe7198eab6cb0e9d23ea3f47d05a Mon Sep 17 00:00:00 2001 From: Yang Wang Date: Thu, 11 Dec 2025 10:47:18 +0800 Subject: [PATCH 2334/3902] drm/amd/pm: fix wrong pcie parameter on navi1x [ Upstream commit 4f74c2dd970611d3ec3bb0d58215e73af5cd7214 ] fix wrong pcie dpm parameter on navi1x Fixes: 1a18607c07bb ("drm/amd/pm: override pcie dpm parameters only if it is necessary") Closes: https://gitlab.freedesktop.org/drm/amd/-/issues/4671 Signed-off-by: Yang Wang Co-developed-by: Kenneth Feng Signed-off-by: Kenneth Feng Acked-by: Alex Deucher Reviewed-by: Lijo Lazar Signed-off-by: Alex Deucher (cherry picked from commit 5c5189cf4b0cc0a22bac74a40743ee711cff07f8) Signed-off-by: Sasha Levin --- drivers/gpu/drm/amd/pm/swsmu/smu11/navi10_ppt.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/gpu/drm/amd/pm/swsmu/smu11/navi10_ppt.c b/drivers/gpu/drm/amd/pm/swsmu/smu11/navi10_ppt.c index 0028f10ead4237..d0fd9537e62365 100644 --- a/drivers/gpu/drm/amd/pm/swsmu/smu11/navi10_ppt.c +++ b/drivers/gpu/drm/amd/pm/swsmu/smu11/navi10_ppt.c @@ -2463,8 +2463,8 @@ static int navi10_update_pcie_parameters(struct smu_context *smu, pptable->PcieLaneCount[i] > pcie_width_cap ? pcie_width_cap : pptable->PcieLaneCount[i]; smu_pcie_arg = i << 16; - smu_pcie_arg |= pcie_gen_cap << 8; - smu_pcie_arg |= pcie_width_cap; + smu_pcie_arg |= dpm_context->dpm_tables.pcie_table.pcie_gen[i] << 8; + smu_pcie_arg |= dpm_context->dpm_tables.pcie_table.pcie_lane[i]; ret = smu_cmn_send_smc_msg_with_param(smu, SMU_MSG_OverridePcieParameters, smu_pcie_arg, From 543f8537b470340a82e78d7002f293b7aff79619 Mon Sep 17 00:00:00 2001 From: Yang Wang Date: Mon, 15 Dec 2025 17:51:11 +0800 Subject: [PATCH 2335/3902] drm/amd/pm: force send pcie parmater on navi1x [ Upstream commit dc8a887de1a7d397ab4131f45676e89565417aa8 ] v1: the PMFW didn't initialize the PCIe DPM parameters and requires the KMD to actively provide these parameters. v2: clean & remove unused code logic (lijo) Fixes: 1a18607c07bb ("drm/amd/pm: override pcie dpm parameters only if it is necessary") Closes: https://gitlab.freedesktop.org/drm/amd/-/issues/4671 Signed-off-by: Yang Wang Reviewed-by: Lijo Lazar Signed-off-by: Alex Deucher (cherry picked from commit b0dbd5db7cf1f81e4aaedd25cb5e72ce369387b2) Signed-off-by: Sasha Levin --- .../gpu/drm/amd/pm/swsmu/smu11/navi10_ppt.c | 33 +++++++++---------- 1 file changed, 15 insertions(+), 18 deletions(-) diff --git a/drivers/gpu/drm/amd/pm/swsmu/smu11/navi10_ppt.c b/drivers/gpu/drm/amd/pm/swsmu/smu11/navi10_ppt.c index d0fd9537e62365..a2fcf678182b4e 100644 --- a/drivers/gpu/drm/amd/pm/swsmu/smu11/navi10_ppt.c +++ b/drivers/gpu/drm/amd/pm/swsmu/smu11/navi10_ppt.c @@ -2454,24 +2454,21 @@ static int navi10_update_pcie_parameters(struct smu_context *smu, } for (i = 0; i < NUM_LINK_LEVELS; i++) { - if (pptable->PcieGenSpeed[i] > pcie_gen_cap || - pptable->PcieLaneCount[i] > pcie_width_cap) { - dpm_context->dpm_tables.pcie_table.pcie_gen[i] = - pptable->PcieGenSpeed[i] > pcie_gen_cap ? - pcie_gen_cap : pptable->PcieGenSpeed[i]; - dpm_context->dpm_tables.pcie_table.pcie_lane[i] = - pptable->PcieLaneCount[i] > pcie_width_cap ? - pcie_width_cap : pptable->PcieLaneCount[i]; - smu_pcie_arg = i << 16; - smu_pcie_arg |= dpm_context->dpm_tables.pcie_table.pcie_gen[i] << 8; - smu_pcie_arg |= dpm_context->dpm_tables.pcie_table.pcie_lane[i]; - ret = smu_cmn_send_smc_msg_with_param(smu, - SMU_MSG_OverridePcieParameters, - smu_pcie_arg, - NULL); - if (ret) - break; - } + dpm_context->dpm_tables.pcie_table.pcie_gen[i] = + pptable->PcieGenSpeed[i] > pcie_gen_cap ? + pcie_gen_cap : pptable->PcieGenSpeed[i]; + dpm_context->dpm_tables.pcie_table.pcie_lane[i] = + pptable->PcieLaneCount[i] > pcie_width_cap ? + pcie_width_cap : pptable->PcieLaneCount[i]; + smu_pcie_arg = i << 16; + smu_pcie_arg |= dpm_context->dpm_tables.pcie_table.pcie_gen[i] << 8; + smu_pcie_arg |= dpm_context->dpm_tables.pcie_table.pcie_lane[i]; + ret = smu_cmn_send_smc_msg_with_param(smu, + SMU_MSG_OverridePcieParameters, + smu_pcie_arg, + NULL); + if (ret) + return ret; } return ret; From 0185dc4b5b0e229b0e188dc6cd693e2ebbf58355 Mon Sep 17 00:00:00 2001 From: Michal Luczaj Date: Mon, 29 Dec 2025 20:43:10 +0100 Subject: [PATCH 2336/3902] vsock: Make accept()ed sockets use custom setsockopt() [ Upstream commit ce5e612dd411de096aa041b9e9325ba1bec5f9f4 ] SO_ZEROCOPY handling in vsock_connectible_setsockopt() does not get called on accept()ed sockets due to a missing flag. Flip it. Fixes: e0718bd82e27 ("vsock: enable setting SO_ZEROCOPY") Signed-off-by: Michal Luczaj Link: https://patch.msgid.link/20251229-vsock-child-sock-custom-sockopt-v2-1-64778d6c4f88@rbox.co Signed-off-by: Jakub Kicinski Signed-off-by: Sasha Levin --- net/vmw_vsock/af_vsock.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/net/vmw_vsock/af_vsock.c b/net/vmw_vsock/af_vsock.c index a9ca9c3b87b31a..cbd649bf01459d 100644 --- a/net/vmw_vsock/af_vsock.c +++ b/net/vmw_vsock/af_vsock.c @@ -1787,6 +1787,10 @@ static int vsock_accept(struct socket *sock, struct socket *newsock, } else { newsock->state = SS_CONNECTED; sock_graft(connected, newsock); + + set_bit(SOCK_CUSTOM_SOCKOPT, + &connected->sk_socket->flags); + if (vsock_msgzerocopy_allow(vconnected->transport)) set_bit(SOCK_SUPPORT_ZC, &connected->sk_socket->flags); From 92a5590851144f034adc51fee55e6878ccac716e Mon Sep 17 00:00:00 2001 From: Filipe Manana Date: Tue, 16 Dec 2025 14:51:52 +0000 Subject: [PATCH 2337/3902] btrfs: release path before initializing extent tree in btrfs_read_locked_inode() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [ Upstream commit 8731f2c50b0b1d2b58ed5b9671ef2c4bdc2f8347 ] In btrfs_read_locked_inode() we are calling btrfs_init_file_extent_tree() while holding a path with a read locked leaf from a subvolume tree, and btrfs_init_file_extent_tree() may do a GFP_KERNEL allocation, which can trigger reclaim. This can create a circular lock dependency which lockdep warns about with the following splat: [6.1433] ====================================================== [6.1574] WARNING: possible circular locking dependency detected [6.1583] 6.18.0+ #4 Tainted: G U [6.1591] ------------------------------------------------------ [6.1599] kswapd0/117 is trying to acquire lock: [6.1606] ffff8d9b6333c5b8 (&delayed_node->mutex){+.+.}-{3:3}, at: __btrfs_release_delayed_node.part.0+0x39/0x2f0 [6.1625] but task is already holding lock: [6.1633] ffffffffa4ab8ce0 (fs_reclaim){+.+.}-{0:0}, at: balance_pgdat+0x195/0xc60 [6.1646] which lock already depends on the new lock. [6.1657] the existing dependency chain (in reverse order) is: [6.1667] -> #2 (fs_reclaim){+.+.}-{0:0}: [6.1677] fs_reclaim_acquire+0x9d/0xd0 [6.1685] __kmalloc_cache_noprof+0x59/0x750 [6.1694] btrfs_init_file_extent_tree+0x90/0x100 [6.1702] btrfs_read_locked_inode+0xc3/0x6b0 [6.1710] btrfs_iget+0xbb/0xf0 [6.1716] btrfs_lookup_dentry+0x3c5/0x8e0 [6.1724] btrfs_lookup+0x12/0x30 [6.1731] lookup_open.isra.0+0x1aa/0x6a0 [6.1739] path_openat+0x5f7/0xc60 [6.1746] do_filp_open+0xd6/0x180 [6.1753] do_sys_openat2+0x8b/0xe0 [6.1760] __x64_sys_openat+0x54/0xa0 [6.1768] do_syscall_64+0x97/0x3e0 [6.1776] entry_SYSCALL_64_after_hwframe+0x76/0x7e [6.1784] -> #1 (btrfs-tree-00){++++}-{3:3}: [6.1794] lock_release+0x127/0x2a0 [6.1801] up_read+0x1b/0x30 [6.1808] btrfs_search_slot+0x8e0/0xff0 [6.1817] btrfs_lookup_inode+0x52/0xd0 [6.1825] __btrfs_update_delayed_inode+0x73/0x520 [6.1833] btrfs_commit_inode_delayed_inode+0x11a/0x120 [6.1842] btrfs_log_inode+0x608/0x1aa0 [6.1849] btrfs_log_inode_parent+0x249/0xf80 [6.1857] btrfs_log_dentry_safe+0x3e/0x60 [6.1865] btrfs_sync_file+0x431/0x690 [6.1872] do_fsync+0x39/0x80 [6.1879] __x64_sys_fsync+0x13/0x20 [6.1887] do_syscall_64+0x97/0x3e0 [6.1894] entry_SYSCALL_64_after_hwframe+0x76/0x7e [6.1903] -> #0 (&delayed_node->mutex){+.+.}-{3:3}: [6.1913] __lock_acquire+0x15e9/0x2820 [6.1920] lock_acquire+0xc9/0x2d0 [6.1927] __mutex_lock+0xcc/0x10a0 [6.1934] __btrfs_release_delayed_node.part.0+0x39/0x2f0 [6.1944] btrfs_evict_inode+0x20b/0x4b0 [6.1952] evict+0x15a/0x2f0 [6.1958] prune_icache_sb+0x91/0xd0 [6.1966] super_cache_scan+0x150/0x1d0 [6.1974] do_shrink_slab+0x155/0x6f0 [6.1981] shrink_slab+0x48e/0x890 [6.1988] shrink_one+0x11a/0x1f0 [6.1995] shrink_node+0xbfd/0x1320 [6.1002] balance_pgdat+0x67f/0xc60 [6.1321] kswapd+0x1dc/0x3e0 [6.1643] kthread+0xff/0x240 [6.1965] ret_from_fork+0x223/0x280 [6.1287] ret_from_fork_asm+0x1a/0x30 [6.1616] other info that might help us debug this: [6.1561] Chain exists of: &delayed_node->mutex --> btrfs-tree-00 --> fs_reclaim [6.1503] Possible unsafe locking scenario: [6.1110] CPU0 CPU1 [6.1411] ---- ---- [6.1707] lock(fs_reclaim); [6.1998] lock(btrfs-tree-00); [6.1291] lock(fs_reclaim); [6.1581] lock(&delayed_node->mutex); [6.1874] *** DEADLOCK *** [6.1716] 2 locks held by kswapd0/117: [6.1999] #0: ffffffffa4ab8ce0 (fs_reclaim){+.+.}-{0:0}, at: balance_pgdat+0x195/0xc60 [6.1294] #1: ffff8d998344b0e0 (&type->s_umount_key#40){++++}- {3:3}, at: super_cache_scan+0x37/0x1d0 [6.1596] stack backtrace: [6.1183] CPU: 11 UID: 0 PID: 117 Comm: kswapd0 Tainted: G U 6.18.0+ #4 PREEMPT(lazy) [6.1185] Tainted: [U]=USER [6.1186] Hardware name: ASUS System Product Name/PRIME B560M-A AC, BIOS 2001 02/01/2023 [6.1187] Call Trace: [6.1187] [6.1189] dump_stack_lvl+0x6e/0xa0 [6.1192] print_circular_bug.cold+0x17a/0x1c0 [6.1194] check_noncircular+0x175/0x190 [6.1197] __lock_acquire+0x15e9/0x2820 [6.1200] lock_acquire+0xc9/0x2d0 [6.1201] ? __btrfs_release_delayed_node.part.0+0x39/0x2f0 [6.1204] __mutex_lock+0xcc/0x10a0 [6.1206] ? __btrfs_release_delayed_node.part.0+0x39/0x2f0 [6.1208] ? __btrfs_release_delayed_node.part.0+0x39/0x2f0 [6.1211] ? __btrfs_release_delayed_node.part.0+0x39/0x2f0 [6.1213] __btrfs_release_delayed_node.part.0+0x39/0x2f0 [6.1215] btrfs_evict_inode+0x20b/0x4b0 [6.1217] ? lock_acquire+0xc9/0x2d0 [6.1220] evict+0x15a/0x2f0 [6.1222] prune_icache_sb+0x91/0xd0 [6.1224] super_cache_scan+0x150/0x1d0 [6.1226] do_shrink_slab+0x155/0x6f0 [6.1228] shrink_slab+0x48e/0x890 [6.1229] ? shrink_slab+0x2d2/0x890 [6.1231] shrink_one+0x11a/0x1f0 [6.1234] shrink_node+0xbfd/0x1320 [6.1236] ? shrink_node+0xa2d/0x1320 [6.1236] ? shrink_node+0xbd3/0x1320 [6.1239] ? balance_pgdat+0x67f/0xc60 [6.1239] balance_pgdat+0x67f/0xc60 [6.1241] ? finish_task_switch.isra.0+0xc4/0x2a0 [6.1246] kswapd+0x1dc/0x3e0 [6.1247] ? __pfx_autoremove_wake_function+0x10/0x10 [6.1249] ? __pfx_kswapd+0x10/0x10 [6.1250] kthread+0xff/0x240 [6.1251] ? __pfx_kthread+0x10/0x10 [6.1253] ret_from_fork+0x223/0x280 [6.1255] ? __pfx_kthread+0x10/0x10 [6.1257] ret_from_fork_asm+0x1a/0x30 [6.1260] This is because: 1) The fsync task is holding an inode's delayed node mutex (for a directory) while calling __btrfs_update_delayed_inode() and that needs to do a search on the subvolume's btree (therefore read lock some extent buffers); 2) The lookup task, at btrfs_lookup(), triggered reclaim with the GFP_KERNEL allocation done by btrfs_init_file_extent_tree() while holding a read lock on a subvolume leaf; 3) The reclaim triggered kswapd which is doing inode eviction for the directory inode the fsync task is using as an argument to btrfs_commit_inode_delayed_inode() - but in that call chain we are trying to read lock the same leaf that the lookup task is holding while calling btrfs_init_file_extent_tree() and doing the GFP_KERNEL allocation. Fix this by calling btrfs_init_file_extent_tree() after we don't need the path anymore and release it in btrfs_read_locked_inode(). Reported-by: Thomas Hellström Link: https://lore.kernel.org/linux-btrfs/6e55113a22347c3925458a5d840a18401a38b276.camel@linux.intel.com/ Fixes: 8679d2687c35 ("btrfs: initialize inode::file_extent_tree after i_mode has been set") Reviewed-by: Qu Wenruo Signed-off-by: Filipe Manana Reviewed-by: David Sterba Signed-off-by: David Sterba Signed-off-by: Sasha Levin --- fs/btrfs/inode.c | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/fs/btrfs/inode.c b/fs/btrfs/inode.c index 51401d586a7b65..27a562bad6e872 100644 --- a/fs/btrfs/inode.c +++ b/fs/btrfs/inode.c @@ -3976,11 +3976,6 @@ static int btrfs_read_locked_inode(struct btrfs_inode *inode, struct btrfs_path btrfs_set_inode_mapping_order(inode); cache_index: - ret = btrfs_init_file_extent_tree(inode); - if (ret) - goto out; - btrfs_inode_set_file_extent_range(inode, 0, - round_up(i_size_read(vfs_inode), fs_info->sectorsize)); /* * If we were modified in the current generation and evicted from memory * and then re-read we need to do a full sync since we don't have any @@ -4067,6 +4062,20 @@ static int btrfs_read_locked_inode(struct btrfs_inode *inode, struct btrfs_path btrfs_ino(inode), btrfs_root_id(root), ret); } + /* + * We don't need the path anymore, so release it to avoid holding a read + * lock on a leaf while calling btrfs_init_file_extent_tree(), which can + * allocate memory that triggers reclaim (GFP_KERNEL) and cause a locking + * dependency. + */ + btrfs_release_path(path); + + ret = btrfs_init_file_extent_tree(inode); + if (ret) + goto out; + btrfs_inode_set_file_extent_range(inode, 0, + round_up(i_size_read(vfs_inode), fs_info->sectorsize)); + if (!maybe_acls) cache_no_acl(vfs_inode); From 07effd536ddc102bfae763fa836171fff5e6a7fb Mon Sep 17 00:00:00 2001 From: Qu Wenruo Date: Thu, 18 Dec 2025 15:15:28 +1030 Subject: [PATCH 2338/3902] btrfs: only enforce free space tree if v1 cache is required for bs < ps cases [ Upstream commit 30bcf4e824aa37d305502f52e1527c7b1eabef3d ] [BUG] Since the introduction of btrfs bs < ps support, v1 cache was never on the plan due to its hard coded PAGE_SIZE usage, and the future plan to properly deprecate it. However for bs < ps cases, even if 'nospace_cache,clear_cache' mount option is specified, it's never respected and free space tree is always enabled: mkfs.btrfs -f -O ^bgt,fst $dev mount $dev $mnt -o clear_cache,nospace_cache umount $mnt btrfs ins dump-super $dev ... compat_ro_flags 0x3 ( FREE_SPACE_TREE | FREE_SPACE_TREE_VALID ) ... This means a different behavior compared to bs >= ps cases. [CAUSE] The forcing usage of v2 space cache is done inside btrfs_set_free_space_cache_settings(), however it never checks if we're even using space cache but always enabling v2 cache. [FIX] Instead unconditionally enable v2 cache, only forcing v2 cache if the old v1 cache is required. Now v2 space cache can be properly disabled on bs < ps cases: mkfs.btrfs -f -O ^bgt,fst $dev mount $dev $mnt -o clear_cache,nospace_cache umount $mnt btrfs ins dump-super $dev ... compat_ro_flags 0x0 ... Fixes: 9f73f1aef98b ("btrfs: force v2 space cache usage for subpage mount") Reviewed-by: Filipe Manana Signed-off-by: Qu Wenruo Reviewed-by: David Sterba Signed-off-by: David Sterba Signed-off-by: Sasha Levin --- fs/btrfs/super.c | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/fs/btrfs/super.c b/fs/btrfs/super.c index 430e7419349c9a..c40944ca7b948f 100644 --- a/fs/btrfs/super.c +++ b/fs/btrfs/super.c @@ -736,14 +736,12 @@ bool btrfs_check_options(const struct btrfs_fs_info *info, */ void btrfs_set_free_space_cache_settings(struct btrfs_fs_info *fs_info) { - if (fs_info->sectorsize < PAGE_SIZE) { + if (fs_info->sectorsize < PAGE_SIZE && btrfs_test_opt(fs_info, SPACE_CACHE)) { + btrfs_info(fs_info, + "forcing free space tree for sector size %u with page size %lu", + fs_info->sectorsize, PAGE_SIZE); btrfs_clear_opt(fs_info->mount_opt, SPACE_CACHE); - if (!btrfs_test_opt(fs_info, FREE_SPACE_TREE)) { - btrfs_info(fs_info, - "forcing free space tree for sector size %u with page size %lu", - fs_info->sectorsize, PAGE_SIZE); - btrfs_set_opt(fs_info->mount_opt, FREE_SPACE_TREE); - } + btrfs_set_opt(fs_info->mount_opt, FREE_SPACE_TREE); } /* From 6d1b61b8e1e44888c643d89225ab819b10649b2e Mon Sep 17 00:00:00 2001 From: Suchit Karunakaran Date: Fri, 19 Dec 2025 22:44:34 +0530 Subject: [PATCH 2339/3902] btrfs: fix NULL pointer dereference in do_abort_log_replay() [ Upstream commit 530e3d4af566ca44807d79359b90794dea24c4f3 ] Coverity reported a NULL pointer dereference issue (CID 1666756) in do_abort_log_replay(). When btrfs_alloc_path() fails in replay_one_buffer(), wc->subvol_path is NULL, but btrfs_abort_log_replay() calls do_abort_log_replay() which unconditionally dereferences wc->subvol_path when attempting to print debug information. Fix this by adding a NULL check before dereferencing wc->subvol_path in do_abort_log_replay(). Fixes: 2753e4917624 ("btrfs: dump detailed info and specific messages on log replay failures") Reviewed-by: Filipe Manana Signed-off-by: Suchit Karunakaran Signed-off-by: Filipe Manana Signed-off-by: David Sterba Signed-off-by: Sasha Levin --- fs/btrfs/tree-log.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fs/btrfs/tree-log.c b/fs/btrfs/tree-log.c index de9ea9d52482f5..1444857de9fe85 100644 --- a/fs/btrfs/tree-log.c +++ b/fs/btrfs/tree-log.c @@ -189,7 +189,7 @@ static void do_abort_log_replay(struct walk_control *wc, const char *function, btrfs_abort_transaction(wc->trans, error); - if (wc->subvol_path->nodes[0]) { + if (wc->subvol_path && wc->subvol_path->nodes[0]) { btrfs_crit(fs_info, "subvolume (root %llu) leaf currently being processed:", btrfs_root_id(wc->root)); From 8a6b410e3d0f7ece64533a0f4d954fcada0f5d83 Mon Sep 17 00:00:00 2001 From: Lorenzo Bianconi Date: Fri, 2 Jan 2026 12:29:38 +0100 Subject: [PATCH 2340/3902] net: airoha: Fix npu rx DMA definitions [ Upstream commit a7fc8c641cab855824c45e5e8877e40fd528b5df ] Fix typos in npu rx DMA descriptor definitions. Fixes: b3ef7bdec66fb ("net: airoha: Add airoha_offload.h header") Signed-off-by: Lorenzo Bianconi Link: https://patch.msgid.link/20260102-airoha-npu-dma-rx-def-fixes-v1-1-205fc6bf7d94@kernel.org Signed-off-by: Jakub Kicinski Signed-off-by: Sasha Levin --- include/linux/soc/airoha/airoha_offload.h | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/include/linux/soc/airoha/airoha_offload.h b/include/linux/soc/airoha/airoha_offload.h index 6f66eb339b3fc6..1a33f846afafa9 100644 --- a/include/linux/soc/airoha/airoha_offload.h +++ b/include/linux/soc/airoha/airoha_offload.h @@ -70,12 +70,12 @@ static inline void airoha_ppe_dev_check_skb(struct airoha_ppe_dev *dev, #define NPU_RX1_DESC_NUM 512 /* CTRL */ -#define NPU_RX_DMA_DESC_LAST_MASK BIT(29) -#define NPU_RX_DMA_DESC_LEN_MASK GENMASK(28, 15) -#define NPU_RX_DMA_DESC_CUR_LEN_MASK GENMASK(14, 1) +#define NPU_RX_DMA_DESC_LAST_MASK BIT(27) +#define NPU_RX_DMA_DESC_LEN_MASK GENMASK(26, 14) +#define NPU_RX_DMA_DESC_CUR_LEN_MASK GENMASK(13, 1) #define NPU_RX_DMA_DESC_DONE_MASK BIT(0) /* INFO */ -#define NPU_RX_DMA_PKT_COUNT_MASK GENMASK(31, 28) +#define NPU_RX_DMA_PKT_COUNT_MASK GENMASK(31, 29) #define NPU_RX_DMA_PKT_ID_MASK GENMASK(28, 26) #define NPU_RX_DMA_SRC_PORT_MASK GENMASK(25, 21) #define NPU_RX_DMA_CRSN_MASK GENMASK(20, 16) From 5bfaf4fa3dc29826fd3f0d102636da5af4f8a544 Mon Sep 17 00:00:00 2001 From: Guodong Xu Date: Tue, 23 Dec 2025 10:44:27 +0800 Subject: [PATCH 2341/3902] riscv: cpufeature: Fix Zk bundled extension missing Zknh MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [ Upstream commit 8632180daf735074a746ce2b3808a8f2c079310e ] The Zk extension is a bundle consisting of Zkn, Zkr, and Zkt. The Zkn extension itself is a bundle consisting of Zbkb, Zbkc, Zbkx, Zknd, Zkne, and Zknh. The current implementation of riscv_zk_bundled_exts manually listed the dependencies but missed RISCV_ISA_EXT_ZKNH. Fix this by introducing a RISCV_ISA_EXT_ZKN macro that lists the Zkn components and using it in both riscv_zk_bundled_exts and riscv_zkn_bundled_exts. This adds the missing Zknh extension to Zk and reduces code duplication. Fixes: 0d8295ed975b ("riscv: add ISA extension parsing for scalar crypto") Link: https://patch.msgid.link/20231114141256.126749-4-cleger@rivosinc.com/ Signed-off-by: Guodong Xu Reviewed-by: Clément Léger Link: https://patch.msgid.link/20251223-zk-missing-zknh-v1-1-b627c990ee1a@riscstar.com Signed-off-by: Paul Walmsley Signed-off-by: Sasha Levin --- arch/riscv/kernel/cpufeature.c | 23 +++++++++++------------ 1 file changed, 11 insertions(+), 12 deletions(-) diff --git a/arch/riscv/kernel/cpufeature.c b/arch/riscv/kernel/cpufeature.c index 72ca768f4e9191..2367e9755524ad 100644 --- a/arch/riscv/kernel/cpufeature.c +++ b/arch/riscv/kernel/cpufeature.c @@ -279,23 +279,22 @@ static const unsigned int riscv_a_exts[] = { RISCV_ISA_EXT_ZALRSC, }; +#define RISCV_ISA_EXT_ZKN \ + RISCV_ISA_EXT_ZBKB, \ + RISCV_ISA_EXT_ZBKC, \ + RISCV_ISA_EXT_ZBKX, \ + RISCV_ISA_EXT_ZKND, \ + RISCV_ISA_EXT_ZKNE, \ + RISCV_ISA_EXT_ZKNH + static const unsigned int riscv_zk_bundled_exts[] = { - RISCV_ISA_EXT_ZBKB, - RISCV_ISA_EXT_ZBKC, - RISCV_ISA_EXT_ZBKX, - RISCV_ISA_EXT_ZKND, - RISCV_ISA_EXT_ZKNE, + RISCV_ISA_EXT_ZKN, RISCV_ISA_EXT_ZKR, - RISCV_ISA_EXT_ZKT, + RISCV_ISA_EXT_ZKT }; static const unsigned int riscv_zkn_bundled_exts[] = { - RISCV_ISA_EXT_ZBKB, - RISCV_ISA_EXT_ZBKC, - RISCV_ISA_EXT_ZBKX, - RISCV_ISA_EXT_ZKND, - RISCV_ISA_EXT_ZKNE, - RISCV_ISA_EXT_ZKNH, + RISCV_ISA_EXT_ZKN }; static const unsigned int riscv_zks_bundled_exts[] = { From 177c71d2709f861ad95caa429e86e03978474d8d Mon Sep 17 00:00:00 2001 From: "Guo Ren (Alibaba DAMO Academy)" Date: Sun, 30 Nov 2025 19:58:50 -0500 Subject: [PATCH 2342/3902] riscv: pgtable: Cleanup useless VA_USER_XXX definitions [ Upstream commit 5e5be092ffadcab0093464ccd9e30f0c5cce16b9 ] These marcos are not used after commit b5b4287accd7 ("riscv: mm: Use hint address in mmap if available"). Cleanup VA_USER_XXX definitions in asm/pgtable.h. Fixes: b5b4287accd7 ("riscv: mm: Use hint address in mmap if available") Signed-off-by: Guo Ren (Alibaba DAMO Academy) Reviewed-by: Jinjie Ruan Link: https://patch.msgid.link/20251201005850.702569-1-guoren@kernel.org Signed-off-by: Paul Walmsley Signed-off-by: Sasha Levin --- arch/riscv/include/asm/pgtable.h | 4 ---- 1 file changed, 4 deletions(-) diff --git a/arch/riscv/include/asm/pgtable.h b/arch/riscv/include/asm/pgtable.h index 5a08eb5fe99fc4..30d1ea93dde347 100644 --- a/arch/riscv/include/asm/pgtable.h +++ b/arch/riscv/include/asm/pgtable.h @@ -124,10 +124,6 @@ #ifdef CONFIG_64BIT #include -#define VA_USER_SV39 (UL(1) << (VA_BITS_SV39 - 1)) -#define VA_USER_SV48 (UL(1) << (VA_BITS_SV48 - 1)) -#define VA_USER_SV57 (UL(1) << (VA_BITS_SV57 - 1)) - #define MMAP_VA_BITS_64 ((VA_BITS >= VA_BITS_SV48) ? VA_BITS_SV48 : VA_BITS) #define MMAP_MIN_VA_BITS_64 (VA_BITS_SV39) #define MMAP_VA_BITS (is_compat_task() ? VA_BITS_SV32 : MMAP_VA_BITS_64) From c114a32a2e70b82d447f409f7ffcfa3058f9d5bd Mon Sep 17 00:00:00 2001 From: Mohammad Heib Date: Sun, 4 Jan 2026 23:31:01 +0200 Subject: [PATCH 2343/3902] net: fix memory leak in skb_segment_list for GRO packets [ Upstream commit 238e03d0466239410b72294b79494e43d4fabe77 ] When skb_segment_list() is called during packet forwarding, it handles packets that were aggregated by the GRO engine. Historically, the segmentation logic in skb_segment_list assumes that individual segments are split from a parent SKB and may need to carry their own socket memory accounting. Accordingly, the code transfers truesize from the parent to the newly created segments. Prior to commit ed4cccef64c1 ("gro: fix ownership transfer"), this truesize subtraction in skb_segment_list() was valid because fragments still carry a reference to the original socket. However, commit ed4cccef64c1 ("gro: fix ownership transfer") changed this behavior by ensuring that fraglist entries are explicitly orphaned (skb->sk = NULL) to prevent illegal orphaning later in the stack. This change meant that the entire socket memory charge remained with the head SKB, but the corresponding accounting logic in skb_segment_list() was never updated. As a result, the current code unconditionally adds each fragment's truesize to delta_truesize and subtracts it from the parent SKB. Since the fragments are no longer charged to the socket, this subtraction results in an effective under-count of memory when the head is freed. This causes sk_wmem_alloc to remain non-zero, preventing socket destruction and leading to a persistent memory leak. The leak can be observed via KMEMLEAK when tearing down the networking environment: unreferenced object 0xffff8881e6eb9100 (size 2048): comm "ping", pid 6720, jiffies 4295492526 backtrace: kmem_cache_alloc_noprof+0x5c6/0x800 sk_prot_alloc+0x5b/0x220 sk_alloc+0x35/0xa00 inet6_create.part.0+0x303/0x10d0 __sock_create+0x248/0x640 __sys_socket+0x11b/0x1d0 Since skb_segment_list() is exclusively used for SKB_GSO_FRAGLIST packets constructed by GRO, the truesize adjustment is removed. The call to skb_release_head_state() must be preserved. As documented in commit cf673ed0e057 ("net: fix fraglist segmentation reference count leak"), it is still required to correctly drop references to SKB extensions that may be overwritten during __copy_skb_header(). Fixes: ed4cccef64c1 ("gro: fix ownership transfer") Signed-off-by: Mohammad Heib Reviewed-by: Willem de Bruijn Link: https://patch.msgid.link/20260104213101.352887-1-mheib@redhat.com Signed-off-by: Jakub Kicinski Signed-off-by: Sasha Levin --- net/core/skbuff.c | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/net/core/skbuff.c b/net/core/skbuff.c index 6be01454f262a2..9a763d120925a8 100644 --- a/net/core/skbuff.c +++ b/net/core/skbuff.c @@ -4597,12 +4597,14 @@ struct sk_buff *skb_segment_list(struct sk_buff *skb, { struct sk_buff *list_skb = skb_shinfo(skb)->frag_list; unsigned int tnl_hlen = skb_tnl_header_len(skb); - unsigned int delta_truesize = 0; unsigned int delta_len = 0; struct sk_buff *tail = NULL; struct sk_buff *nskb, *tmp; int len_diff, err; + /* Only skb_gro_receive_list generated skbs arrive here */ + DEBUG_NET_WARN_ON_ONCE(!(skb_shinfo(skb)->gso_type & SKB_GSO_FRAGLIST)); + skb_push(skb, -skb_network_offset(skb) + offset); /* Ensure the head is writeable before touching the shared info */ @@ -4616,8 +4618,9 @@ struct sk_buff *skb_segment_list(struct sk_buff *skb, nskb = list_skb; list_skb = list_skb->next; + DEBUG_NET_WARN_ON_ONCE(nskb->sk); + err = 0; - delta_truesize += nskb->truesize; if (skb_shared(nskb)) { tmp = skb_clone(nskb, GFP_ATOMIC); if (tmp) { @@ -4660,7 +4663,6 @@ struct sk_buff *skb_segment_list(struct sk_buff *skb, goto err_linearize; } - skb->truesize = skb->truesize - delta_truesize; skb->data_len = skb->data_len - delta_len; skb->len = skb->len - delta_len; From 309a4c2fa676af3be92c3ef742cca96872852cc3 Mon Sep 17 00:00:00 2001 From: "Mario Limonciello (AMD)" Date: Mon, 5 Jan 2026 22:46:38 -0600 Subject: [PATCH 2344/3902] PCI/VGA: Don't assume the only VGA device on a system is `boot_vga` [ Upstream commit fd390ff144513eb0310c350b1cf5fa8d6ddd0c53 ] Some systems ship with multiple display class devices but not all of them are VGA devices. If the "only" VGA device on the system is not used for displaying the image on the screen marking it as `boot_vga` because nothing was found is totally wrong. This behavior actually leads to mistakes of the wrong device being advertised to userspace and then userspace can make incorrect decisions. As there is an accurate `boot_display` sysfs file stop lying about `boot_vga` by assuming if nothing is found it's the right device. Reported-by: Aaron Erhardt Closes: https://bugzilla.kernel.org/show_bug.cgi?id=220712 Tested-by: Aaron Erhardt Acked-by: Thomas Zimmermann Fixes: ad90860bd10ee ("fbcon: Use screen info to find primary device") Tested-by: Luke D. Jones Signed-off-by: Mario Limonciello (AMD) Signed-off-by: Thomas Zimmermann Link: https://patch.msgid.link/20260106044638.52906-1-superm1@kernel.org Signed-off-by: Sasha Levin --- drivers/pci/vgaarb.c | 7 ------- 1 file changed, 7 deletions(-) diff --git a/drivers/pci/vgaarb.c b/drivers/pci/vgaarb.c index 436fa7f4c3873b..baa242b1409930 100644 --- a/drivers/pci/vgaarb.c +++ b/drivers/pci/vgaarb.c @@ -652,13 +652,6 @@ static bool vga_is_boot_device(struct vga_device *vgadev) return true; } - /* - * Vgadev has neither IO nor MEM enabled. If we haven't found any - * other VGA devices, it is the best candidate so far. - */ - if (!boot_vga) - return true; - return false; } From 14c4fea115361b69a9026f0c7bc46524d4ddd06a Mon Sep 17 00:00:00 2001 From: Emil Tantilov Date: Thu, 20 Nov 2025 16:12:14 -0800 Subject: [PATCH 2345/3902] idpf: keep the netdev when a reset fails [ Upstream commit 083029bd8b445595222a3cd14076b880781c1765 ] During a successful reset the driver would re-allocate vport resources while keeping the netdevs intact. However, in case of an error in the init task, the netdev of the failing vport will be unregistered, effectively removing the network interface: [ 121.211076] idpf 0000:83:00.0: enabling device (0100 -> 0102) [ 121.221976] idpf 0000:83:00.0: Device HW Reset initiated [ 124.161229] idpf 0000:83:00.0 ens801f0: renamed from eth0 [ 124.163364] idpf 0000:83:00.0 ens801f0d1: renamed from eth1 [ 125.934656] idpf 0000:83:00.0 ens801f0d2: renamed from eth2 [ 128.218429] idpf 0000:83:00.0 ens801f0d3: renamed from eth3 ip -br a ens801f0 UP ens801f0d1 UP ens801f0d2 UP ens801f0d3 UP echo 1 > /sys/class/net/ens801f0/device/reset [ 145.885537] idpf 0000:83:00.0: resetting [ 145.990280] idpf 0000:83:00.0: reset done [ 146.284766] idpf 0000:83:00.0: HW reset detected [ 146.296610] idpf 0000:83:00.0: Device HW Reset initiated [ 211.556719] idpf 0000:83:00.0: Transaction timed-out (op:526 cookie:7700 vc_op:526 salt:77 timeout:60000ms) [ 272.996705] idpf 0000:83:00.0: Transaction timed-out (op:502 cookie:7800 vc_op:502 salt:78 timeout:60000ms) ip -br a ens801f0d1 DOWN ens801f0d2 DOWN ens801f0d3 DOWN Re-shuffle the logic in the error path of the init task to make sure the netdevs remain intact. This will allow the driver to attempt recovery via subsequent resets, provided the FW is still functional. The main change is to make sure that idpf_decfg_netdev() is not called should the init task fail during a reset. The error handling is consolidated under unwind_vports, as the removed labels had the same cleanup logic split depending on the point of failure. Fixes: ce1b75d0635c ("idpf: add ptypes and MAC filter support") Signed-off-by: Emil Tantilov Reviewed-by: Aleksandr Loktionov Tested-by: Samuel Salin Signed-off-by: Tony Nguyen Signed-off-by: Sasha Levin --- drivers/net/ethernet/intel/idpf/idpf_lib.c | 17 ++++++----------- 1 file changed, 6 insertions(+), 11 deletions(-) diff --git a/drivers/net/ethernet/intel/idpf/idpf_lib.c b/drivers/net/ethernet/intel/idpf/idpf_lib.c index aaafe40f5eaf74..452f3107378cbb 100644 --- a/drivers/net/ethernet/intel/idpf/idpf_lib.c +++ b/drivers/net/ethernet/intel/idpf/idpf_lib.c @@ -1579,6 +1579,10 @@ void idpf_init_task(struct work_struct *work) goto unwind_vports; } + err = idpf_send_get_rx_ptype_msg(vport); + if (err) + goto unwind_vports; + index = vport->idx; vport_config = adapter->vport_config[index]; @@ -1590,15 +1594,11 @@ void idpf_init_task(struct work_struct *work) err = idpf_check_supported_desc_ids(vport); if (err) { dev_err(&pdev->dev, "failed to get required descriptor ids\n"); - goto cfg_netdev_err; + goto unwind_vports; } if (idpf_cfg_netdev(vport)) - goto cfg_netdev_err; - - err = idpf_send_get_rx_ptype_msg(vport); - if (err) - goto handle_err; + goto unwind_vports; /* Once state is put into DOWN, driver is ready for dev_open */ np = netdev_priv(vport->netdev); @@ -1645,11 +1645,6 @@ void idpf_init_task(struct work_struct *work) return; -handle_err: - idpf_decfg_netdev(vport); -cfg_netdev_err: - idpf_vport_rel(vport); - adapter->vports[index] = NULL; unwind_vports: if (default_vport) { for (index = 0; index < adapter->max_vports; index++) { From a9f5b61591d34cc27a60878e61c929e791fb526b Mon Sep 17 00:00:00 2001 From: Emil Tantilov Date: Tue, 25 Nov 2025 14:36:24 -0800 Subject: [PATCH 2346/3902] idpf: convert vport state to bitmap [ Upstream commit 8dd72ebc73f37b216410db17340f15e6fb2cdb7b ] Convert vport state to a bitmap and remove the DOWN state which is redundant in the existing logic. There are no functional changes aside from the use of bitwise operations when setting and checking the states. Removed the double underscore to be consistent with the naming of other bitmaps in the header and renamed current_state to vport_is_up to match the meaning of the new variable. Reviewed-by: Przemek Kitszel Reviewed-by: Aleksandr Loktionov Reviewed-by: Chittim Madhu Signed-off-by: Emil Tantilov Tested-by: Samuel Salin Signed-off-by: Tony Nguyen Link: https://patch.msgid.link/20251125223632.1857532-6-anthony.l.nguyen@intel.com Signed-off-by: Jakub Kicinski Stable-dep-of: 2e281e1155fc ("idpf: detach and close netdevs while handling a reset") Signed-off-by: Sasha Levin --- drivers/net/ethernet/intel/idpf/idpf.h | 12 ++++------ .../net/ethernet/intel/idpf/idpf_ethtool.c | 12 +++++----- drivers/net/ethernet/intel/idpf/idpf_lib.c | 24 +++++++++---------- .../ethernet/intel/idpf/idpf_singleq_txrx.c | 2 +- drivers/net/ethernet/intel/idpf/idpf_txrx.c | 2 +- .../net/ethernet/intel/idpf/idpf_virtchnl.c | 4 ++-- drivers/net/ethernet/intel/idpf/xdp.c | 2 +- 7 files changed, 28 insertions(+), 30 deletions(-) diff --git a/drivers/net/ethernet/intel/idpf/idpf.h b/drivers/net/ethernet/intel/idpf/idpf.h index ca4da0c8997947..64142f8163fed7 100644 --- a/drivers/net/ethernet/intel/idpf/idpf.h +++ b/drivers/net/ethernet/intel/idpf/idpf.h @@ -131,14 +131,12 @@ enum idpf_cap_field { /** * enum idpf_vport_state - Current vport state - * @__IDPF_VPORT_DOWN: Vport is down - * @__IDPF_VPORT_UP: Vport is up - * @__IDPF_VPORT_STATE_LAST: Must be last, number of states + * @IDPF_VPORT_UP: Vport is up + * @IDPF_VPORT_STATE_NBITS: Must be last, number of states */ enum idpf_vport_state { - __IDPF_VPORT_DOWN, - __IDPF_VPORT_UP, - __IDPF_VPORT_STATE_LAST, + IDPF_VPORT_UP, + IDPF_VPORT_STATE_NBITS }; /** @@ -162,7 +160,7 @@ struct idpf_netdev_priv { u16 vport_idx; u16 max_tx_hdr_size; u16 tx_max_bufs; - enum idpf_vport_state state; + DECLARE_BITMAP(state, IDPF_VPORT_STATE_NBITS); struct rtnl_link_stats64 netstats; spinlock_t stats_lock; }; diff --git a/drivers/net/ethernet/intel/idpf/idpf_ethtool.c b/drivers/net/ethernet/intel/idpf/idpf_ethtool.c index a5a1eec9ade8bd..eed166bc46f38e 100644 --- a/drivers/net/ethernet/intel/idpf/idpf_ethtool.c +++ b/drivers/net/ethernet/intel/idpf/idpf_ethtool.c @@ -386,7 +386,7 @@ static int idpf_get_rxfh(struct net_device *netdev, } rss_data = &adapter->vport_config[np->vport_idx]->user_config.rss_data; - if (np->state != __IDPF_VPORT_UP) + if (!test_bit(IDPF_VPORT_UP, np->state)) goto unlock_mutex; rxfh->hfunc = ETH_RSS_HASH_TOP; @@ -436,7 +436,7 @@ static int idpf_set_rxfh(struct net_device *netdev, } rss_data = &adapter->vport_config[vport->idx]->user_config.rss_data; - if (np->state != __IDPF_VPORT_UP) + if (!test_bit(IDPF_VPORT_UP, np->state)) goto unlock_mutex; if (rxfh->hfunc != ETH_RSS_HASH_NO_CHANGE && @@ -1167,7 +1167,7 @@ static void idpf_get_ethtool_stats(struct net_device *netdev, idpf_vport_ctrl_lock(netdev); vport = idpf_netdev_to_vport(netdev); - if (np->state != __IDPF_VPORT_UP) { + if (!test_bit(IDPF_VPORT_UP, np->state)) { idpf_vport_ctrl_unlock(netdev); return; @@ -1319,7 +1319,7 @@ static int idpf_get_q_coalesce(struct net_device *netdev, idpf_vport_ctrl_lock(netdev); vport = idpf_netdev_to_vport(netdev); - if (np->state != __IDPF_VPORT_UP) + if (!test_bit(IDPF_VPORT_UP, np->state)) goto unlock_mutex; if (q_num >= vport->num_rxq && q_num >= vport->num_txq) { @@ -1507,7 +1507,7 @@ static int idpf_set_coalesce(struct net_device *netdev, idpf_vport_ctrl_lock(netdev); vport = idpf_netdev_to_vport(netdev); - if (np->state != __IDPF_VPORT_UP) + if (!test_bit(IDPF_VPORT_UP, np->state)) goto unlock_mutex; for (i = 0; i < vport->num_txq; i++) { @@ -1710,7 +1710,7 @@ static void idpf_get_ts_stats(struct net_device *netdev, ts_stats->err = u64_stats_read(&vport->tstamp_stats.discarded); } while (u64_stats_fetch_retry(&vport->tstamp_stats.stats_sync, start)); - if (np->state != __IDPF_VPORT_UP) + if (!test_bit(IDPF_VPORT_UP, np->state)) goto exit; for (u16 i = 0; i < vport->num_txq_grp; i++) { diff --git a/drivers/net/ethernet/intel/idpf/idpf_lib.c b/drivers/net/ethernet/intel/idpf/idpf_lib.c index 452f3107378cbb..313803c088478a 100644 --- a/drivers/net/ethernet/intel/idpf/idpf_lib.c +++ b/drivers/net/ethernet/intel/idpf/idpf_lib.c @@ -519,7 +519,7 @@ static int idpf_del_mac_filter(struct idpf_vport *vport, } spin_unlock_bh(&vport_config->mac_filter_list_lock); - if (np->state == __IDPF_VPORT_UP) { + if (test_bit(IDPF_VPORT_UP, np->state)) { int err; err = idpf_add_del_mac_filters(vport, np, false, async); @@ -590,7 +590,7 @@ static int idpf_add_mac_filter(struct idpf_vport *vport, if (err) return err; - if (np->state == __IDPF_VPORT_UP) + if (test_bit(IDPF_VPORT_UP, np->state)) err = idpf_add_del_mac_filters(vport, np, true, async); return err; @@ -894,7 +894,7 @@ static void idpf_vport_stop(struct idpf_vport *vport, bool rtnl) { struct idpf_netdev_priv *np = netdev_priv(vport->netdev); - if (np->state <= __IDPF_VPORT_DOWN) + if (!test_bit(IDPF_VPORT_UP, np->state)) return; if (rtnl) @@ -921,7 +921,7 @@ static void idpf_vport_stop(struct idpf_vport *vport, bool rtnl) idpf_xdp_rxq_info_deinit_all(vport); idpf_vport_queues_rel(vport); idpf_vport_intr_rel(vport); - np->state = __IDPF_VPORT_DOWN; + clear_bit(IDPF_VPORT_UP, np->state); if (rtnl) rtnl_unlock(); @@ -1345,7 +1345,7 @@ static int idpf_up_complete(struct idpf_vport *vport) netif_tx_start_all_queues(vport->netdev); } - np->state = __IDPF_VPORT_UP; + set_bit(IDPF_VPORT_UP, np->state); return 0; } @@ -1391,7 +1391,7 @@ static int idpf_vport_open(struct idpf_vport *vport, bool rtnl) struct idpf_vport_config *vport_config; int err; - if (np->state != __IDPF_VPORT_DOWN) + if (test_bit(IDPF_VPORT_UP, np->state)) return -EBUSY; if (rtnl) @@ -1602,7 +1602,7 @@ void idpf_init_task(struct work_struct *work) /* Once state is put into DOWN, driver is ready for dev_open */ np = netdev_priv(vport->netdev); - np->state = __IDPF_VPORT_DOWN; + clear_bit(IDPF_VPORT_UP, np->state); if (test_and_clear_bit(IDPF_VPORT_UP_REQUESTED, vport_config->flags)) idpf_vport_open(vport, true); @@ -1796,7 +1796,7 @@ static void idpf_set_vport_state(struct idpf_adapter *adapter) continue; np = netdev_priv(adapter->netdevs[i]); - if (np->state == __IDPF_VPORT_UP) + if (test_bit(IDPF_VPORT_UP, np->state)) set_bit(IDPF_VPORT_UP_REQUESTED, adapter->vport_config[i]->flags); } @@ -1934,7 +1934,7 @@ int idpf_initiate_soft_reset(struct idpf_vport *vport, enum idpf_vport_reset_cause reset_cause) { struct idpf_netdev_priv *np = netdev_priv(vport->netdev); - enum idpf_vport_state current_state = np->state; + bool vport_is_up = test_bit(IDPF_VPORT_UP, np->state); struct idpf_adapter *adapter = vport->adapter; struct idpf_vport *new_vport; int err; @@ -1985,7 +1985,7 @@ int idpf_initiate_soft_reset(struct idpf_vport *vport, goto free_vport; } - if (current_state <= __IDPF_VPORT_DOWN) { + if (!vport_is_up) { idpf_send_delete_queues_msg(vport); } else { set_bit(IDPF_VPORT_DEL_QUEUES, vport->flags); @@ -2018,7 +2018,7 @@ int idpf_initiate_soft_reset(struct idpf_vport *vport, if (err) goto err_open; - if (current_state == __IDPF_VPORT_UP) + if (vport_is_up) err = idpf_vport_open(vport, false); goto free_vport; @@ -2028,7 +2028,7 @@ int idpf_initiate_soft_reset(struct idpf_vport *vport, vport->num_rxq, vport->num_bufq); err_open: - if (current_state == __IDPF_VPORT_UP) + if (vport_is_up) idpf_vport_open(vport, false); free_vport: diff --git a/drivers/net/ethernet/intel/idpf/idpf_singleq_txrx.c b/drivers/net/ethernet/intel/idpf/idpf_singleq_txrx.c index 61e61306614045..e3ddf18dcbf51b 100644 --- a/drivers/net/ethernet/intel/idpf/idpf_singleq_txrx.c +++ b/drivers/net/ethernet/intel/idpf/idpf_singleq_txrx.c @@ -570,7 +570,7 @@ static bool idpf_tx_singleq_clean(struct idpf_tx_queue *tx_q, int napi_budget, np = netdev_priv(tx_q->netdev); nq = netdev_get_tx_queue(tx_q->netdev, tx_q->idx); - dont_wake = np->state != __IDPF_VPORT_UP || + dont_wake = !test_bit(IDPF_VPORT_UP, np->state) || !netif_carrier_ok(tx_q->netdev); __netif_txq_completed_wake(nq, ss.packets, ss.bytes, IDPF_DESC_UNUSED(tx_q), IDPF_TX_WAKE_THRESH, diff --git a/drivers/net/ethernet/intel/idpf/idpf_txrx.c b/drivers/net/ethernet/intel/idpf/idpf_txrx.c index 828f7c444d3093..1993a3b0da59bd 100644 --- a/drivers/net/ethernet/intel/idpf/idpf_txrx.c +++ b/drivers/net/ethernet/intel/idpf/idpf_txrx.c @@ -2275,7 +2275,7 @@ static bool idpf_tx_clean_complq(struct idpf_compl_queue *complq, int budget, /* Update BQL */ nq = netdev_get_tx_queue(tx_q->netdev, tx_q->idx); - dont_wake = !complq_ok || np->state != __IDPF_VPORT_UP || + dont_wake = !complq_ok || !test_bit(IDPF_VPORT_UP, np->state) || !netif_carrier_ok(tx_q->netdev); /* Check if the TXQ needs to and can be restarted */ __netif_txq_completed_wake(nq, tx_q->cleaned_pkts, tx_q->cleaned_bytes, diff --git a/drivers/net/ethernet/intel/idpf/idpf_virtchnl.c b/drivers/net/ethernet/intel/idpf/idpf_virtchnl.c index fc03d55bc9b908..5bbe7d9294c141 100644 --- a/drivers/net/ethernet/intel/idpf/idpf_virtchnl.c +++ b/drivers/net/ethernet/intel/idpf/idpf_virtchnl.c @@ -68,7 +68,7 @@ static void idpf_handle_event_link(struct idpf_adapter *adapter, vport->link_up = v2e->link_status; - if (np->state != __IDPF_VPORT_UP) + if (!test_bit(IDPF_VPORT_UP, np->state)) return; if (vport->link_up) { @@ -2760,7 +2760,7 @@ int idpf_send_get_stats_msg(struct idpf_vport *vport) /* Don't send get_stats message if the link is down */ - if (np->state <= __IDPF_VPORT_DOWN) + if (!test_bit(IDPF_VPORT_UP, np->state)) return 0; stats_msg.vport_id = cpu_to_le32(vport->vport_id); diff --git a/drivers/net/ethernet/intel/idpf/xdp.c b/drivers/net/ethernet/intel/idpf/xdp.c index 21ce25b0567f6f..958d16f874248d 100644 --- a/drivers/net/ethernet/intel/idpf/xdp.c +++ b/drivers/net/ethernet/intel/idpf/xdp.c @@ -418,7 +418,7 @@ static int idpf_xdp_setup_prog(struct idpf_vport *vport, if (test_bit(IDPF_REMOVE_IN_PROG, vport->adapter->flags) || !test_bit(IDPF_VPORT_REG_NETDEV, cfg->flags) || !!vport->xdp_prog == !!prog) { - if (np->state == __IDPF_VPORT_UP) + if (test_bit(IDPF_VPORT_UP, np->state)) idpf_xdp_copy_prog_to_rqs(vport, prog); old = xchg(&vport->xdp_prog, prog); From ac122f5fb050903b3d262001562c452be95eaf70 Mon Sep 17 00:00:00 2001 From: Emil Tantilov Date: Thu, 20 Nov 2025 16:12:15 -0800 Subject: [PATCH 2347/3902] idpf: detach and close netdevs while handling a reset [ Upstream commit 2e281e1155fc476c571c0bd2ffbfe28ab829a5c3 ] Protect the reset path from callbacks by setting the netdevs to detached state and close any netdevs in UP state until the reset handling has completed. During a reset, the driver will de-allocate resources for the vport, and there is no guarantee that those will recover, which is why the existing vport_ctrl_lock does not provide sufficient protection. idpf_detach_and_close() is called right before reset handling. If the reset handling succeeds, the netdevs state is recovered via call to idpf_attach_and_open(). If the reset handling fails the netdevs remain down. The detach/down calls are protected with RTNL lock to avoid racing with callbacks. On the recovery side the attach can be done without holding the RTNL lock as there are no callbacks expected at that point, due to detach/close always being done first in that flow. The previous logic restoring the netdevs state based on the IDPF_VPORT_UP_REQUESTED flag in the init task is not needed anymore, hence the removal of idpf_set_vport_state(). The IDPF_VPORT_UP_REQUESTED is still being used to restore the state of the netdevs following the reset, but has no use outside of the reset handling flow. idpf_init_hard_reset() is converted to void, since it was used as such and there is no error handling being done based on its return value. Before this change, invoking hard and soft resets simultaneously will cause the driver to lose the vport state: ip -br a UP echo 1 > /sys/class/net/ens801f0/device/reset& \ ethtool -L ens801f0 combined 8 ip -br a DOWN ip link set up ip -br a DOWN Also in case of a failure in the reset path, the netdev is left exposed to external callbacks, while vport resources are not initialized, leading to a crash on subsequent ifup/down: [408471.398966] idpf 0000:83:00.0: HW reset detected [408471.411744] idpf 0000:83:00.0: Device HW Reset initiated [408472.277901] idpf 0000:83:00.0: The driver was unable to contact the device's firmware. Check that the FW is running. Driver state= 0x2 [408508.125551] BUG: kernel NULL pointer dereference, address: 0000000000000078 [408508.126112] #PF: supervisor read access in kernel mode [408508.126687] #PF: error_code(0x0000) - not-present page [408508.127256] PGD 2aae2f067 P4D 0 [408508.127824] Oops: Oops: 0000 [#1] SMP NOPTI ... [408508.130871] RIP: 0010:idpf_stop+0x39/0x70 [idpf] ... [408508.139193] Call Trace: [408508.139637] [408508.140077] __dev_close_many+0xbb/0x260 [408508.140533] __dev_change_flags+0x1cf/0x280 [408508.140987] netif_change_flags+0x26/0x70 [408508.141434] dev_change_flags+0x3d/0xb0 [408508.141878] devinet_ioctl+0x460/0x890 [408508.142321] inet_ioctl+0x18e/0x1d0 [408508.142762] ? _copy_to_user+0x22/0x70 [408508.143207] sock_do_ioctl+0x3d/0xe0 [408508.143652] sock_ioctl+0x10e/0x330 [408508.144091] ? find_held_lock+0x2b/0x80 [408508.144537] __x64_sys_ioctl+0x96/0xe0 [408508.144979] do_syscall_64+0x79/0x3d0 [408508.145415] entry_SYSCALL_64_after_hwframe+0x76/0x7e [408508.145860] RIP: 0033:0x7f3e0bb4caff Fixes: 0fe45467a104 ("idpf: add create vport and netdev configuration") Signed-off-by: Emil Tantilov Reviewed-by: Madhu Chittim Tested-by: Samuel Salin Signed-off-by: Tony Nguyen Signed-off-by: Sasha Levin --- drivers/net/ethernet/intel/idpf/idpf_lib.c | 121 ++++++++++++--------- 1 file changed, 72 insertions(+), 49 deletions(-) diff --git a/drivers/net/ethernet/intel/idpf/idpf_lib.c b/drivers/net/ethernet/intel/idpf/idpf_lib.c index 313803c088478a..a964e0f5891eba 100644 --- a/drivers/net/ethernet/intel/idpf/idpf_lib.c +++ b/drivers/net/ethernet/intel/idpf/idpf_lib.c @@ -729,6 +729,65 @@ static int idpf_init_mac_addr(struct idpf_vport *vport, return 0; } +static void idpf_detach_and_close(struct idpf_adapter *adapter) +{ + int max_vports = adapter->max_vports; + + for (int i = 0; i < max_vports; i++) { + struct net_device *netdev = adapter->netdevs[i]; + + /* If the interface is in detached state, that means the + * previous reset was not handled successfully for this + * vport. + */ + if (!netif_device_present(netdev)) + continue; + + /* Hold RTNL to protect racing with callbacks */ + rtnl_lock(); + netif_device_detach(netdev); + if (netif_running(netdev)) { + set_bit(IDPF_VPORT_UP_REQUESTED, + adapter->vport_config[i]->flags); + dev_close(netdev); + } + rtnl_unlock(); + } +} + +static void idpf_attach_and_open(struct idpf_adapter *adapter) +{ + int max_vports = adapter->max_vports; + + for (int i = 0; i < max_vports; i++) { + struct idpf_vport *vport = adapter->vports[i]; + struct idpf_vport_config *vport_config; + struct net_device *netdev; + + /* In case of a critical error in the init task, the vport + * will be freed. Only continue to restore the netdevs + * if the vport is allocated. + */ + if (!vport) + continue; + + /* No need for RTNL on attach as this function is called + * following detach and dev_close(). We do take RTNL for + * dev_open() below as it can race with external callbacks + * following the call to netif_device_attach(). + */ + netdev = adapter->netdevs[i]; + netif_device_attach(netdev); + vport_config = adapter->vport_config[vport->idx]; + if (test_and_clear_bit(IDPF_VPORT_UP_REQUESTED, + vport_config->flags)) { + rtnl_lock(); + dev_open(netdev, NULL); + rtnl_unlock(); + } + } +} + /** * idpf_cfg_netdev - Allocate, configure and register a netdev * @vport: main vport structure @@ -1041,10 +1100,11 @@ static void idpf_vport_dealloc(struct idpf_vport *vport) idpf_idc_deinit_vport_aux_device(vport->vdev_info); idpf_deinit_mac_addr(vport); - idpf_vport_stop(vport, true); - if (!test_bit(IDPF_HR_RESET_IN_PROG, adapter->flags)) + if (!test_bit(IDPF_HR_RESET_IN_PROG, adapter->flags)) { + idpf_vport_stop(vport, true); idpf_decfg_netdev(vport); + } if (test_bit(IDPF_REMOVE_IN_PROG, adapter->flags)) idpf_del_all_mac_filters(vport); @@ -1544,7 +1604,6 @@ void idpf_init_task(struct work_struct *work) struct idpf_vport_config *vport_config; struct idpf_vport_max_q max_q; struct idpf_adapter *adapter; - struct idpf_netdev_priv *np; struct idpf_vport *vport; u16 num_default_vports; struct pci_dev *pdev; @@ -1600,12 +1659,6 @@ void idpf_init_task(struct work_struct *work) if (idpf_cfg_netdev(vport)) goto unwind_vports; - /* Once state is put into DOWN, driver is ready for dev_open */ - np = netdev_priv(vport->netdev); - clear_bit(IDPF_VPORT_UP, np->state); - if (test_and_clear_bit(IDPF_VPORT_UP_REQUESTED, vport_config->flags)) - idpf_vport_open(vport, true); - /* Spawn and return 'idpf_init_task' work queue until all the * default vports are created */ @@ -1781,27 +1834,6 @@ static int idpf_check_reset_complete(struct idpf_hw *hw, return -EBUSY; } -/** - * idpf_set_vport_state - Set the vport state to be after the reset - * @adapter: Driver specific private structure - */ -static void idpf_set_vport_state(struct idpf_adapter *adapter) -{ - u16 i; - - for (i = 0; i < adapter->max_vports; i++) { - struct idpf_netdev_priv *np; - - if (!adapter->netdevs[i]) - continue; - - np = netdev_priv(adapter->netdevs[i]); - if (test_bit(IDPF_VPORT_UP, np->state)) - set_bit(IDPF_VPORT_UP_REQUESTED, - adapter->vport_config[i]->flags); - } -} - /** * idpf_init_hard_reset - Initiate a hardware reset * @adapter: Driver specific private structure @@ -1810,28 +1842,17 @@ static void idpf_set_vport_state(struct idpf_adapter *adapter) * reallocate. Also reinitialize the mailbox. Return 0 on success, * negative on failure. */ -static int idpf_init_hard_reset(struct idpf_adapter *adapter) +static void idpf_init_hard_reset(struct idpf_adapter *adapter) { struct idpf_reg_ops *reg_ops = &adapter->dev_ops.reg_ops; struct device *dev = &adapter->pdev->dev; - struct net_device *netdev; int err; - u16 i; + idpf_detach_and_close(adapter); mutex_lock(&adapter->vport_ctrl_lock); dev_info(dev, "Device HW Reset initiated\n"); - /* Avoid TX hangs on reset */ - for (i = 0; i < adapter->max_vports; i++) { - netdev = adapter->netdevs[i]; - if (!netdev) - continue; - - netif_carrier_off(netdev); - netif_tx_disable(netdev); - } - /* Prepare for reset */ if (test_and_clear_bit(IDPF_HR_DRV_LOAD, adapter->flags)) { reg_ops->trigger_reset(adapter, IDPF_HR_DRV_LOAD); @@ -1840,7 +1861,6 @@ static int idpf_init_hard_reset(struct idpf_adapter *adapter) idpf_idc_issue_reset_event(adapter->cdev_info); - idpf_set_vport_state(adapter); idpf_vc_core_deinit(adapter); if (!is_reset) reg_ops->trigger_reset(adapter, IDPF_HR_FUNC_RESET); @@ -1887,11 +1907,14 @@ static int idpf_init_hard_reset(struct idpf_adapter *adapter) unlock_mutex: mutex_unlock(&adapter->vport_ctrl_lock); - /* Wait until all vports are created to init RDMA CORE AUX */ - if (!err) - err = idpf_idc_init(adapter); - - return err; + /* Attempt to restore netdevs and initialize RDMA CORE AUX device, + * provided vc_core_init succeeded. It is still possible that + * vports are not allocated at this point if the init task failed. + */ + if (!err) { + idpf_attach_and_open(adapter); + idpf_idc_init(adapter); + } } /** From ec602a2a4071eb956d656ba968c58fee09f0622d Mon Sep 17 00:00:00 2001 From: Emil Tantilov Date: Thu, 20 Nov 2025 16:12:16 -0800 Subject: [PATCH 2348/3902] idpf: fix memory leak in idpf_vport_rel() [ Upstream commit f6242b354605faff263ca45882b148200915a3f6 ] Free vport->rx_ptype_lkup in idpf_vport_rel() to avoid leaking memory during a reset. Reported by kmemleak: unreferenced object 0xff450acac838a000 (size 4096): comm "kworker/u258:5", pid 7732, jiffies 4296830044 hex dump (first 32 bytes): 00 00 00 00 00 10 00 00 00 10 00 00 00 00 00 00 ................ 00 00 00 00 00 00 00 00 00 10 00 00 00 00 00 00 ................ backtrace (crc 3da81902): __kmalloc_cache_noprof+0x469/0x7a0 idpf_send_get_rx_ptype_msg+0x90/0x570 [idpf] idpf_init_task+0x1ec/0x8d0 [idpf] process_one_work+0x226/0x6d0 worker_thread+0x19e/0x340 kthread+0x10f/0x250 ret_from_fork+0x251/0x2b0 ret_from_fork_asm+0x1a/0x30 Fixes: 0fe45467a104 ("idpf: add create vport and netdev configuration") Signed-off-by: Emil Tantilov Reviewed-by: Aleksandr Loktionov Reviewed-by: Madhu Chittim Tested-by: Samuel Salin Signed-off-by: Tony Nguyen Signed-off-by: Sasha Levin --- drivers/net/ethernet/intel/idpf/idpf_lib.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/drivers/net/ethernet/intel/idpf/idpf_lib.c b/drivers/net/ethernet/intel/idpf/idpf_lib.c index a964e0f5891eba..04af10cfaa8cb8 100644 --- a/drivers/net/ethernet/intel/idpf/idpf_lib.c +++ b/drivers/net/ethernet/intel/idpf/idpf_lib.c @@ -1082,6 +1082,8 @@ static void idpf_vport_rel(struct idpf_vport *vport) kfree(adapter->vport_config[idx]->req_qs_chunks); adapter->vport_config[idx]->req_qs_chunks = NULL; } + kfree(vport->rx_ptype_lkup); + vport->rx_ptype_lkup = NULL; kfree(vport); adapter->num_alloc_vports--; } From 23391db8a00c23854915b8b72ec1aa10080aa540 Mon Sep 17 00:00:00 2001 From: Emil Tantilov Date: Thu, 20 Nov 2025 16:12:17 -0800 Subject: [PATCH 2349/3902] idpf: fix memory leak in idpf_vc_core_deinit() [ Upstream commit e111cbc4adf9f9974eed040aeece7e17460f6bff ] Make sure to free hw->lan_regs. Reported by kmemleak during reset: unreferenced object 0xff1b913d02a936c0 (size 96): comm "kworker/u258:14", pid 2174, jiffies 4294958305 hex dump (first 32 bytes): 00 00 00 c0 a8 ba 2d ff 00 00 00 00 00 00 00 00 ......-......... 00 00 40 08 00 00 00 00 00 00 25 b3 a8 ba 2d ff ..@.......%...-. backtrace (crc 36063c4f): __kmalloc_noprof+0x48f/0x890 idpf_vc_core_init+0x6ce/0x9b0 [idpf] idpf_vc_event_task+0x1fb/0x350 [idpf] process_one_work+0x226/0x6d0 worker_thread+0x19e/0x340 kthread+0x10f/0x250 ret_from_fork+0x251/0x2b0 ret_from_fork_asm+0x1a/0x30 Fixes: 6aa53e861c1a ("idpf: implement get LAN MMIO memory regions") Signed-off-by: Emil Tantilov Reviewed-by: Aleksandr Loktionov Reviewed-by: Joshua Hay Tested-by: Samuel Salin Signed-off-by: Tony Nguyen Signed-off-by: Sasha Levin --- drivers/net/ethernet/intel/idpf/idpf_virtchnl.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/drivers/net/ethernet/intel/idpf/idpf_virtchnl.c b/drivers/net/ethernet/intel/idpf/idpf_virtchnl.c index 5bbe7d9294c141..01bbd12a642a07 100644 --- a/drivers/net/ethernet/intel/idpf/idpf_virtchnl.c +++ b/drivers/net/ethernet/intel/idpf/idpf_virtchnl.c @@ -3570,6 +3570,7 @@ int idpf_vc_core_init(struct idpf_adapter *adapter) */ void idpf_vc_core_deinit(struct idpf_adapter *adapter) { + struct idpf_hw *hw = &adapter->hw; bool remove_in_prog; if (!test_bit(IDPF_VC_CORE_INIT, adapter->flags)) @@ -3593,6 +3594,9 @@ void idpf_vc_core_deinit(struct idpf_adapter *adapter) idpf_vport_params_buf_rel(adapter); + kfree(hw->lan_regs); + hw->lan_regs = NULL; + kfree(adapter->vports); adapter->vports = NULL; From a514c374edcd33581cdcccf8faa7cc606a600319 Mon Sep 17 00:00:00 2001 From: Emil Tantilov Date: Thu, 20 Nov 2025 16:12:18 -0800 Subject: [PATCH 2350/3902] idpf: fix error handling in the init_task on load [ Upstream commit 4d792219fe6f891b5b557a607ac8a0a14eda6e38 ] If the init_task fails during a driver load, we end up without vports and netdevs, effectively failing the entire process. In that state a subsequent reset will result in a crash as the service task attempts to access uninitialized resources. Following trace is from an error in the init_task where the CREATE_VPORT (op 501) is rejected by the FW: [40922.763136] idpf 0000:83:00.0: Device HW Reset initiated [40924.449797] idpf 0000:83:00.0: Transaction failed (op 501) [40958.148190] idpf 0000:83:00.0: HW reset detected [40958.161202] BUG: kernel NULL pointer dereference, address: 00000000000000a8 ... [40958.168094] Workqueue: idpf-0000:83:00.0-vc_event idpf_vc_event_task [idpf] [40958.168865] RIP: 0010:idpf_vc_event_task+0x9b/0x350 [idpf] ... [40958.177932] Call Trace: [40958.178491] [40958.179040] process_one_work+0x226/0x6d0 [40958.179609] worker_thread+0x19e/0x340 [40958.180158] ? __pfx_worker_thread+0x10/0x10 [40958.180702] kthread+0x10f/0x250 [40958.181238] ? __pfx_kthread+0x10/0x10 [40958.181774] ret_from_fork+0x251/0x2b0 [40958.182307] ? __pfx_kthread+0x10/0x10 [40958.182834] ret_from_fork_asm+0x1a/0x30 [40958.183370] Fix the error handling in the init_task to make sure the service and mailbox tasks are disabled if the error happens during load. These are started in idpf_vc_core_init(), which spawns the init_task and has no way of knowing if it failed. If the error happens on reset, following successful driver load, the tasks can still run, as that will allow the netdevs to attempt recovery through another reset. Stop the PTP callbacks either way as those will be restarted by the call to idpf_vc_core_init() during a successful reset. Fixes: 0fe45467a104 ("idpf: add create vport and netdev configuration") Reported-by: Vivek Kumar Signed-off-by: Emil Tantilov Reviewed-by: Madhu Chittim Tested-by: Samuel Salin Signed-off-by: Tony Nguyen Signed-off-by: Sasha Levin --- drivers/net/ethernet/intel/idpf/idpf_lib.c | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/drivers/net/ethernet/intel/idpf/idpf_lib.c b/drivers/net/ethernet/intel/idpf/idpf_lib.c index 04af10cfaa8cb8..e2ee8b137421f2 100644 --- a/drivers/net/ethernet/intel/idpf/idpf_lib.c +++ b/drivers/net/ethernet/intel/idpf/idpf_lib.c @@ -1690,10 +1690,9 @@ void idpf_init_task(struct work_struct *work) set_bit(IDPF_VPORT_REG_NETDEV, vport_config->flags); } - /* As all the required vports are created, clear the reset flag - * unconditionally here in case we were in reset and the link was down. - */ + /* Clear the reset and load bits as all vports are created */ clear_bit(IDPF_HR_RESET_IN_PROG, adapter->flags); + clear_bit(IDPF_HR_DRV_LOAD, adapter->flags); /* Start the statistics task now */ queue_delayed_work(adapter->stats_wq, &adapter->stats_task, msecs_to_jiffies(10 * (pdev->devfn & 0x07))); @@ -1707,6 +1706,15 @@ void idpf_init_task(struct work_struct *work) idpf_vport_dealloc(adapter->vports[index]); } } + /* Cleanup after vc_core_init, which has no way of knowing the + * init task failed on driver load. + */ + if (test_and_clear_bit(IDPF_HR_DRV_LOAD, adapter->flags)) { + cancel_delayed_work_sync(&adapter->serv_task); + cancel_delayed_work_sync(&adapter->mbx_task); + } + idpf_ptp_release(adapter); + clear_bit(IDPF_HR_RESET_IN_PROG, adapter->flags); } @@ -1856,7 +1864,7 @@ static void idpf_init_hard_reset(struct idpf_adapter *adapter) dev_info(dev, "Device HW Reset initiated\n"); /* Prepare for reset */ - if (test_and_clear_bit(IDPF_HR_DRV_LOAD, adapter->flags)) { + if (test_bit(IDPF_HR_DRV_LOAD, adapter->flags)) { reg_ops->trigger_reset(adapter, IDPF_HR_DRV_LOAD); } else if (test_and_clear_bit(IDPF_HR_FUNC_RESET, adapter->flags)) { bool is_reset = idpf_is_reset_detected(adapter); From 1aedff70a5e97628eaaf17b169774cb6a45a1dc5 Mon Sep 17 00:00:00 2001 From: Sreedevi Joshi Date: Tue, 30 Sep 2025 16:23:51 -0500 Subject: [PATCH 2351/3902] idpf: fix memory leak of flow steer list on rmmod [ Upstream commit f9841bd28b600526ca4f6713b0ca49bf7bb98452 ] The flow steering list maintains entries that are added and removed as ethtool creates and deletes flow steering rules. Module removal with active entries causes memory leak as the list is not properly cleaned up. Prevent this by iterating through the remaining entries in the list and freeing the associated memory during module removal. Add a spinlock (flow_steer_list_lock) to protect the list access from multiple threads. Fixes: ada3e24b84a0 ("idpf: add flow steering support") Reviewed-by: Przemek Kitszel Reviewed-by: Aleksandr Loktionov Signed-off-by: Sreedevi Joshi Reviewed-by: Simon Horman Tested-by: Mina Almasry Signed-off-by: Tony Nguyen Signed-off-by: Sasha Levin --- drivers/net/ethernet/intel/idpf/idpf.h | 2 ++ .../net/ethernet/intel/idpf/idpf_ethtool.c | 15 ++++++++-- drivers/net/ethernet/intel/idpf/idpf_lib.c | 28 ++++++++++++++++++- 3 files changed, 42 insertions(+), 3 deletions(-) diff --git a/drivers/net/ethernet/intel/idpf/idpf.h b/drivers/net/ethernet/intel/idpf/idpf.h index 64142f8163fed7..af8deb5fa80f02 100644 --- a/drivers/net/ethernet/intel/idpf/idpf.h +++ b/drivers/net/ethernet/intel/idpf/idpf.h @@ -558,6 +558,7 @@ struct idpf_vector_lifo { * @max_q: Maximum possible queues * @req_qs_chunks: Queue chunk data for requested queues * @mac_filter_list_lock: Lock to protect mac filters + * @flow_steer_list_lock: Lock to protect fsteer filters * @flags: See enum idpf_vport_config_flags */ struct idpf_vport_config { @@ -565,6 +566,7 @@ struct idpf_vport_config { struct idpf_vport_max_q max_q; struct virtchnl2_add_queues *req_qs_chunks; spinlock_t mac_filter_list_lock; + spinlock_t flow_steer_list_lock; DECLARE_BITMAP(flags, IDPF_VPORT_CONFIG_FLAGS_NBITS); }; diff --git a/drivers/net/ethernet/intel/idpf/idpf_ethtool.c b/drivers/net/ethernet/intel/idpf/idpf_ethtool.c index eed166bc46f38e..8477e7ba287063 100644 --- a/drivers/net/ethernet/intel/idpf/idpf_ethtool.c +++ b/drivers/net/ethernet/intel/idpf/idpf_ethtool.c @@ -18,6 +18,7 @@ static int idpf_get_rxnfc(struct net_device *netdev, struct ethtool_rxnfc *cmd, { struct idpf_netdev_priv *np = netdev_priv(netdev); struct idpf_vport_user_config_data *user_config; + struct idpf_vport_config *vport_config; struct idpf_fsteer_fltr *f; struct idpf_vport *vport; unsigned int cnt = 0; @@ -25,7 +26,8 @@ static int idpf_get_rxnfc(struct net_device *netdev, struct ethtool_rxnfc *cmd, idpf_vport_ctrl_lock(netdev); vport = idpf_netdev_to_vport(netdev); - user_config = &np->adapter->vport_config[np->vport_idx]->user_config; + vport_config = np->adapter->vport_config[np->vport_idx]; + user_config = &vport_config->user_config; switch (cmd->cmd) { case ETHTOOL_GRXRINGS: @@ -37,15 +39,18 @@ static int idpf_get_rxnfc(struct net_device *netdev, struct ethtool_rxnfc *cmd, break; case ETHTOOL_GRXCLSRULE: err = -EINVAL; + spin_lock_bh(&vport_config->flow_steer_list_lock); list_for_each_entry(f, &user_config->flow_steer_list, list) if (f->loc == cmd->fs.location) { cmd->fs.ring_cookie = f->q_index; err = 0; break; } + spin_unlock_bh(&vport_config->flow_steer_list_lock); break; case ETHTOOL_GRXCLSRLALL: cmd->data = idpf_fsteer_max_rules(vport); + spin_lock_bh(&vport_config->flow_steer_list_lock); list_for_each_entry(f, &user_config->flow_steer_list, list) { if (cnt == cmd->rule_cnt) { err = -EMSGSIZE; @@ -56,6 +61,7 @@ static int idpf_get_rxnfc(struct net_device *netdev, struct ethtool_rxnfc *cmd, } if (!err) cmd->rule_cnt = user_config->num_fsteer_fltrs; + spin_unlock_bh(&vport_config->flow_steer_list_lock); break; default: break; @@ -224,6 +230,7 @@ static int idpf_add_flow_steer(struct net_device *netdev, fltr->loc = fsp->location; fltr->q_index = q_index; + spin_lock_bh(&vport_config->flow_steer_list_lock); list_for_each_entry(f, &user_config->flow_steer_list, list) { if (f->loc >= fltr->loc) break; @@ -234,6 +241,7 @@ static int idpf_add_flow_steer(struct net_device *netdev, list_add(&fltr->list, &user_config->flow_steer_list); user_config->num_fsteer_fltrs++; + spin_unlock_bh(&vport_config->flow_steer_list_lock); out: kfree(rule); @@ -286,17 +294,20 @@ static int idpf_del_flow_steer(struct net_device *netdev, goto out; } + spin_lock_bh(&vport_config->flow_steer_list_lock); list_for_each_entry_safe(f, iter, &user_config->flow_steer_list, list) { if (f->loc == fsp->location) { list_del(&f->list); kfree(f); user_config->num_fsteer_fltrs--; - goto out; + goto out_unlock; } } err = -EINVAL; +out_unlock: + spin_unlock_bh(&vport_config->flow_steer_list_lock); out: kfree(rule); return err; diff --git a/drivers/net/ethernet/intel/idpf/idpf_lib.c b/drivers/net/ethernet/intel/idpf/idpf_lib.c index e2ee8b137421f2..d56366e676cf74 100644 --- a/drivers/net/ethernet/intel/idpf/idpf_lib.c +++ b/drivers/net/ethernet/intel/idpf/idpf_lib.c @@ -442,6 +442,29 @@ int idpf_intr_req(struct idpf_adapter *adapter) return err; } +/** + * idpf_del_all_flow_steer_filters - Delete all flow steer filters in list + * @vport: main vport struct + * + * Takes flow_steer_list_lock spinlock. Deletes all filters + */ +static void idpf_del_all_flow_steer_filters(struct idpf_vport *vport) +{ + struct idpf_vport_config *vport_config; + struct idpf_fsteer_fltr *f, *ftmp; + + vport_config = vport->adapter->vport_config[vport->idx]; + + spin_lock_bh(&vport_config->flow_steer_list_lock); + list_for_each_entry_safe(f, ftmp, &vport_config->user_config.flow_steer_list, + list) { + list_del(&f->list); + kfree(f); + } + vport_config->user_config.num_fsteer_fltrs = 0; + spin_unlock_bh(&vport_config->flow_steer_list_lock); +} + /** * idpf_find_mac_filter - Search filter list for specific mac filter * @vconfig: Vport config structure @@ -1107,8 +1130,10 @@ static void idpf_vport_dealloc(struct idpf_vport *vport) idpf_vport_stop(vport, true); idpf_decfg_netdev(vport); } - if (test_bit(IDPF_REMOVE_IN_PROG, adapter->flags)) + if (test_bit(IDPF_REMOVE_IN_PROG, adapter->flags)) { idpf_del_all_mac_filters(vport); + idpf_del_all_flow_steer_filters(vport); + } if (adapter->netdevs[i]) { struct idpf_netdev_priv *np = netdev_priv(adapter->netdevs[i]); @@ -1648,6 +1673,7 @@ void idpf_init_task(struct work_struct *work) vport_config = adapter->vport_config[index]; spin_lock_init(&vport_config->mac_filter_list_lock); + spin_lock_init(&vport_config->flow_steer_list_lock); INIT_LIST_HEAD(&vport_config->user_config.mac_filter_list); INIT_LIST_HEAD(&vport_config->user_config.flow_steer_list); From fe33b4252e4bf9c1af1dcdb2fdce5457d5eb6f3a Mon Sep 17 00:00:00 2001 From: Erik Gabriel Carrillo Date: Tue, 30 Sep 2025 16:23:52 -0500 Subject: [PATCH 2352/3902] idpf: fix issue with ethtool -n command display [ Upstream commit 36aae2ea6bd76b8246caa50e34a4f4824f0a3be8 ] When ethtool -n is executed on an interface to display the flow steering rules, "rxclass: Unknown flow type" error is generated. The flow steering list maintained in the driver currently stores only the location and q_index but other fields of the ethtool_rx_flow_spec are not stored. This may be enough for the virtchnl command to delete the entry. However, when the ethtool -n command is used to query the flow steering rules, the ethtool_rx_flow_spec returned is not complete causing the error below. Resolve this by storing the flow spec (fsp) when rules are added and returning the complete flow spec when rules are queried. Also, change the return value from EINVAL to ENOENT when flow steering entry is not found during query by location or when deleting an entry. Add logic to detect and reject duplicate filter entries at the same location and change logic to perform upfront validation of all error conditions before adding flow rules through virtchnl. This avoids the need for additional virtchnl delete messages when subsequent operations fail, which was missing in the original upstream code. Example: Before the fix: ethtool -n eth1 2 RX rings available Total 2 rules rxclass: Unknown flow type rxclass: Unknown flow type After the fix: ethtool -n eth1 2 RX rings available Total 2 rules Filter: 0 Rule Type: TCP over IPv4 Src IP addr: 10.0.0.1 mask: 0.0.0.0 Dest IP addr: 0.0.0.0 mask: 255.255.255.255 TOS: 0x0 mask: 0xff Src port: 0 mask: 0xffff Dest port: 0 mask: 0xffff Action: Direct to queue 0 Filter: 1 Rule Type: UDP over IPv4 Src IP addr: 10.0.0.1 mask: 0.0.0.0 Dest IP addr: 0.0.0.0 mask: 255.255.255.255 TOS: 0x0 mask: 0xff Src port: 0 mask: 0xffff Dest port: 0 mask: 0xffff Action: Direct to queue 0 Fixes: ada3e24b84a0 ("idpf: add flow steering support") Signed-off-by: Erik Gabriel Carrillo Co-developed-by: Sreedevi Joshi Signed-off-by: Sreedevi Joshi Reviewed-by: Przemek Kitszel Reviewed-by: Aleksandr Loktionov Reviewed-by: Simon Horman Tested-by: Mina Almasry Signed-off-by: Tony Nguyen Signed-off-by: Sasha Levin --- drivers/net/ethernet/intel/idpf/idpf.h | 3 +- .../net/ethernet/intel/idpf/idpf_ethtool.c | 59 ++++++++++++------- 2 files changed, 40 insertions(+), 22 deletions(-) diff --git a/drivers/net/ethernet/intel/idpf/idpf.h b/drivers/net/ethernet/intel/idpf/idpf.h index af8deb5fa80f02..df64d252b56423 100644 --- a/drivers/net/ethernet/intel/idpf/idpf.h +++ b/drivers/net/ethernet/intel/idpf/idpf.h @@ -284,8 +284,7 @@ struct idpf_port_stats { struct idpf_fsteer_fltr { struct list_head list; - u32 loc; - u32 q_index; + struct ethtool_rx_flow_spec fs; }; /** diff --git a/drivers/net/ethernet/intel/idpf/idpf_ethtool.c b/drivers/net/ethernet/intel/idpf/idpf_ethtool.c index 8477e7ba287063..8e9a93125b4aa6 100644 --- a/drivers/net/ethernet/intel/idpf/idpf_ethtool.c +++ b/drivers/net/ethernet/intel/idpf/idpf_ethtool.c @@ -38,11 +38,15 @@ static int idpf_get_rxnfc(struct net_device *netdev, struct ethtool_rxnfc *cmd, cmd->data = idpf_fsteer_max_rules(vport); break; case ETHTOOL_GRXCLSRULE: - err = -EINVAL; + err = -ENOENT; spin_lock_bh(&vport_config->flow_steer_list_lock); list_for_each_entry(f, &user_config->flow_steer_list, list) - if (f->loc == cmd->fs.location) { - cmd->fs.ring_cookie = f->q_index; + if (f->fs.location == cmd->fs.location) { + /* Avoid infoleak from padding: zero first, + * then assign fields + */ + memset(&cmd->fs, 0, sizeof(cmd->fs)); + cmd->fs = f->fs; err = 0; break; } @@ -56,7 +60,7 @@ static int idpf_get_rxnfc(struct net_device *netdev, struct ethtool_rxnfc *cmd, err = -EMSGSIZE; break; } - rule_locs[cnt] = f->loc; + rule_locs[cnt] = f->fs.location; cnt++; } if (!err) @@ -158,7 +162,7 @@ static int idpf_add_flow_steer(struct net_device *netdev, struct idpf_vport *vport; u32 flow_type, q_index; u16 num_rxq; - int err; + int err = 0; vport = idpf_netdev_to_vport(netdev); vport_config = vport->adapter->vport_config[np->vport_idx]; @@ -184,6 +188,29 @@ static int idpf_add_flow_steer(struct net_device *netdev, if (!rule) return -ENOMEM; + fltr = kzalloc(sizeof(*fltr), GFP_KERNEL); + if (!fltr) { + err = -ENOMEM; + goto out_free_rule; + } + + /* detect duplicate entry and reject before adding rules */ + spin_lock_bh(&vport_config->flow_steer_list_lock); + list_for_each_entry(f, &user_config->flow_steer_list, list) { + if (f->fs.location == fsp->location) { + err = -EEXIST; + break; + } + + if (f->fs.location > fsp->location) + break; + parent = f; + } + spin_unlock_bh(&vport_config->flow_steer_list_lock); + + if (err) + goto out; + rule->vport_id = cpu_to_le32(vport->vport_id); rule->count = cpu_to_le32(1); info = &rule->rule_info[0]; @@ -222,28 +249,20 @@ static int idpf_add_flow_steer(struct net_device *netdev, goto out; } - fltr = kzalloc(sizeof(*fltr), GFP_KERNEL); - if (!fltr) { - err = -ENOMEM; - goto out; - } + /* Save a copy of the user's flow spec so ethtool can later retrieve it */ + fltr->fs = *fsp; - fltr->loc = fsp->location; - fltr->q_index = q_index; spin_lock_bh(&vport_config->flow_steer_list_lock); - list_for_each_entry(f, &user_config->flow_steer_list, list) { - if (f->loc >= fltr->loc) - break; - parent = f; - } - parent ? list_add(&fltr->list, &parent->list) : list_add(&fltr->list, &user_config->flow_steer_list); user_config->num_fsteer_fltrs++; spin_unlock_bh(&vport_config->flow_steer_list_lock); + goto out_free_rule; out: + kfree(fltr); +out_free_rule: kfree(rule); return err; } @@ -297,14 +316,14 @@ static int idpf_del_flow_steer(struct net_device *netdev, spin_lock_bh(&vport_config->flow_steer_list_lock); list_for_each_entry_safe(f, iter, &user_config->flow_steer_list, list) { - if (f->loc == fsp->location) { + if (f->fs.location == fsp->location) { list_del(&f->list); kfree(f); user_config->num_fsteer_fltrs--; goto out_unlock; } } - err = -EINVAL; + err = -ENOENT; out_unlock: spin_unlock_bh(&vport_config->flow_steer_list_lock); From b29a5a7dd1f4293ee49c469938c25bf85a5aa802 Mon Sep 17 00:00:00 2001 From: Sreedevi Joshi Date: Mon, 24 Nov 2025 12:47:48 -0600 Subject: [PATCH 2353/3902] idpf: Fix RSS LUT NULL pointer crash on early ethtool operations [ Upstream commit 83f38f210b85676f40ba8586b5a8edae19b56995 ] The RSS LUT is not initialized until the interface comes up, causing the following NULL pointer crash when ethtool operations like rxhash on/off are performed before the interface is brought up for the first time. Move RSS LUT initialization from ndo_open to vport creation to ensure LUT is always available. This enables RSS configuration via ethtool before bringing the interface up. Simplify LUT management by maintaining all changes in the driver's soft copy and programming zeros to the indirection table when rxhash is disabled. Defer HW programming until the interface comes up if it is down during rxhash and LUT configuration changes. Steps to reproduce: ** Load idpf driver; interfaces will be created modprobe idpf ** Before bringing the interfaces up, turn rxhash off ethtool -K eth2 rxhash off [89408.371875] BUG: kernel NULL pointer dereference, address: 0000000000000000 [89408.371908] #PF: supervisor read access in kernel mode [89408.371924] #PF: error_code(0x0000) - not-present page [89408.371940] PGD 0 P4D 0 [89408.371953] Oops: Oops: 0000 [#1] SMP NOPTI [89408.372052] RIP: 0010:memcpy_orig+0x16/0x130 [89408.372310] Call Trace: [89408.372317] [89408.372326] ? idpf_set_features+0xfc/0x180 [idpf] [89408.372363] __netdev_update_features+0x295/0xde0 [89408.372384] ethnl_set_features+0x15e/0x460 [89408.372406] genl_family_rcv_msg_doit+0x11f/0x180 [89408.372429] genl_rcv_msg+0x1ad/0x2b0 [89408.372446] ? __pfx_ethnl_set_features+0x10/0x10 [89408.372465] ? __pfx_genl_rcv_msg+0x10/0x10 [89408.372482] netlink_rcv_skb+0x58/0x100 [89408.372502] genl_rcv+0x2c/0x50 [89408.372516] netlink_unicast+0x289/0x3e0 [89408.372533] netlink_sendmsg+0x215/0x440 [89408.372551] __sys_sendto+0x234/0x240 [89408.372571] __x64_sys_sendto+0x28/0x30 [89408.372585] x64_sys_call+0x1909/0x1da0 [89408.372604] do_syscall_64+0x7a/0xfa0 [89408.373140] ? clear_bhb_loop+0x60/0xb0 [89408.373647] entry_SYSCALL_64_after_hwframe+0x76/0x7e [89408.378887] Fixes: a251eee62133 ("idpf: add SRIOV support and other ndo_ops") Signed-off-by: Sreedevi Joshi Reviewed-by: Sridhar Samudrala Reviewed-by: Emil Tantilov Reviewed-by: Aleksandr Loktionov Reviewed-by: Paul Menzel Reviewed-by: Simon Horman Tested-by: Samuel Salin Signed-off-by: Tony Nguyen Signed-off-by: Sasha Levin --- drivers/net/ethernet/intel/idpf/idpf.h | 2 - drivers/net/ethernet/intel/idpf/idpf_lib.c | 94 +++++++++---------- drivers/net/ethernet/intel/idpf/idpf_txrx.c | 36 +++---- drivers/net/ethernet/intel/idpf/idpf_txrx.h | 4 +- .../net/ethernet/intel/idpf/idpf_virtchnl.c | 9 +- 5 files changed, 66 insertions(+), 79 deletions(-) diff --git a/drivers/net/ethernet/intel/idpf/idpf.h b/drivers/net/ethernet/intel/idpf/idpf.h index df64d252b56423..c74c47bc0b9b9f 100644 --- a/drivers/net/ethernet/intel/idpf/idpf.h +++ b/drivers/net/ethernet/intel/idpf/idpf.h @@ -423,14 +423,12 @@ enum idpf_user_flags { * @rss_key: RSS hash key * @rss_lut_size: Size of RSS lookup table * @rss_lut: RSS lookup table - * @cached_lut: Used to restore previously init RSS lut */ struct idpf_rss_data { u16 rss_key_size; u8 *rss_key; u16 rss_lut_size; u32 *rss_lut; - u32 *cached_lut; }; /** diff --git a/drivers/net/ethernet/intel/idpf/idpf_lib.c b/drivers/net/ethernet/intel/idpf/idpf_lib.c index d56366e676cf74..51716e5a84ef31 100644 --- a/drivers/net/ethernet/intel/idpf/idpf_lib.c +++ b/drivers/net/ethernet/intel/idpf/idpf_lib.c @@ -1073,7 +1073,7 @@ static void idpf_vport_rel(struct idpf_vport *vport) u16 idx = vport->idx; vport_config = adapter->vport_config[vport->idx]; - idpf_deinit_rss(vport); + idpf_deinit_rss_lut(vport); rss_data = &vport_config->user_config.rss_data; kfree(rss_data->rss_key); rss_data->rss_key = NULL; @@ -1226,6 +1226,7 @@ static struct idpf_vport *idpf_vport_alloc(struct idpf_adapter *adapter, u16 idx = adapter->next_vport; struct idpf_vport *vport; u16 num_max_q; + int err; if (idx == IDPF_NO_FREE_SLOT) return NULL; @@ -1276,10 +1277,11 @@ static struct idpf_vport *idpf_vport_alloc(struct idpf_adapter *adapter, idpf_vport_init(vport, max_q); - /* This alloc is done separate from the LUT because it's not strictly - * dependent on how many queues we have. If we change number of queues - * and soft reset we'll need a new LUT but the key can remain the same - * for as long as the vport exists. + /* LUT and key are both initialized here. Key is not strictly dependent + * on how many queues we have. If we change number of queues and soft + * reset is initiated, LUT will be freed and a new LUT will be allocated + * as per the updated number of queues during vport bringup. However, + * the key remains the same for as long as the vport exists. */ rss_data = &adapter->vport_config[idx]->user_config.rss_data; rss_data->rss_key = kzalloc(rss_data->rss_key_size, GFP_KERNEL); @@ -1289,6 +1291,11 @@ static struct idpf_vport *idpf_vport_alloc(struct idpf_adapter *adapter, /* Initialize default rss key */ netdev_rss_key_fill((void *)rss_data->rss_key, rss_data->rss_key_size); + /* Initialize default rss LUT */ + err = idpf_init_rss_lut(vport); + if (err) + goto free_rss_key; + /* fill vport slot in the adapter struct */ adapter->vports[idx] = vport; adapter->vport_ids[idx] = idpf_get_vport_id(vport); @@ -1299,6 +1306,8 @@ static struct idpf_vport *idpf_vport_alloc(struct idpf_adapter *adapter, return vport; +free_rss_key: + kfree(rss_data->rss_key); free_vector_idxs: kfree(vport->q_vector_idxs); free_vport: @@ -1476,6 +1485,7 @@ static int idpf_vport_open(struct idpf_vport *vport, bool rtnl) struct idpf_netdev_priv *np = netdev_priv(vport->netdev); struct idpf_adapter *adapter = vport->adapter; struct idpf_vport_config *vport_config; + struct idpf_rss_data *rss_data; int err; if (test_bit(IDPF_VPORT_UP, np->state)) @@ -1570,12 +1580,21 @@ static int idpf_vport_open(struct idpf_vport *vport, bool rtnl) idpf_restore_features(vport); vport_config = adapter->vport_config[vport->idx]; - if (vport_config->user_config.rss_data.rss_lut) - err = idpf_config_rss(vport); - else - err = idpf_init_rss(vport); + rss_data = &vport_config->user_config.rss_data; + + if (!rss_data->rss_lut) { + err = idpf_init_rss_lut(vport); + if (err) { + dev_err(&adapter->pdev->dev, + "Failed to initialize RSS LUT for vport %u: %d\n", + vport->vport_id, err); + goto disable_vport; + } + } + + err = idpf_config_rss(vport); if (err) { - dev_err(&adapter->pdev->dev, "Failed to initialize RSS for vport %u: %d\n", + dev_err(&adapter->pdev->dev, "Failed to configure RSS for vport %u: %d\n", vport->vport_id, err); goto disable_vport; } @@ -1584,7 +1603,7 @@ static int idpf_vport_open(struct idpf_vport *vport, bool rtnl) if (err) { dev_err(&adapter->pdev->dev, "Failed to complete interface up for vport %u: %d\n", vport->vport_id, err); - goto deinit_rss; + goto disable_vport; } if (rtnl) @@ -1592,8 +1611,6 @@ static int idpf_vport_open(struct idpf_vport *vport, bool rtnl) return 0; -deinit_rss: - idpf_deinit_rss(vport); disable_vport: idpf_send_disable_vport_msg(vport); disable_queues: @@ -2051,7 +2068,7 @@ int idpf_initiate_soft_reset(struct idpf_vport *vport, idpf_vport_stop(vport, false); } - idpf_deinit_rss(vport); + idpf_deinit_rss_lut(vport); /* We're passing in vport here because we need its wait_queue * to send a message and it should be getting all the vport * config data out of the adapter but we need to be careful not @@ -2219,40 +2236,6 @@ static void idpf_set_rx_mode(struct net_device *netdev) dev_err(dev, "Failed to set promiscuous mode: %d\n", err); } -/** - * idpf_vport_manage_rss_lut - disable/enable RSS - * @vport: the vport being changed - * - * In the event of disable request for RSS, this function will zero out RSS - * LUT, while in the event of enable request for RSS, it will reconfigure RSS - * LUT with the default LUT configuration. - */ -static int idpf_vport_manage_rss_lut(struct idpf_vport *vport) -{ - bool ena = idpf_is_feature_ena(vport, NETIF_F_RXHASH); - struct idpf_rss_data *rss_data; - u16 idx = vport->idx; - int lut_size; - - rss_data = &vport->adapter->vport_config[idx]->user_config.rss_data; - lut_size = rss_data->rss_lut_size * sizeof(u32); - - if (ena) { - /* This will contain the default or user configured LUT */ - memcpy(rss_data->rss_lut, rss_data->cached_lut, lut_size); - } else { - /* Save a copy of the current LUT to be restored later if - * requested. - */ - memcpy(rss_data->cached_lut, rss_data->rss_lut, lut_size); - - /* Zero out the current LUT to disable */ - memset(rss_data->rss_lut, 0, lut_size); - } - - return idpf_config_rss(vport); -} - /** * idpf_set_features - set the netdev feature flags * @netdev: ptr to the netdev being adjusted @@ -2278,10 +2261,19 @@ static int idpf_set_features(struct net_device *netdev, } if (changed & NETIF_F_RXHASH) { + struct idpf_netdev_priv *np = netdev_priv(netdev); + netdev->features ^= NETIF_F_RXHASH; - err = idpf_vport_manage_rss_lut(vport); - if (err) - goto unlock_mutex; + + /* If the interface is not up when changing the rxhash, update + * to the HW is skipped. The updated LUT will be committed to + * the HW when the interface is brought up. + */ + if (test_bit(IDPF_VPORT_UP, np->state)) { + err = idpf_config_rss(vport); + if (err) + goto unlock_mutex; + } } if (changed & NETIF_F_GRO_HW) { diff --git a/drivers/net/ethernet/intel/idpf/idpf_txrx.c b/drivers/net/ethernet/intel/idpf/idpf_txrx.c index 1993a3b0da59bd..39553689ffdbf5 100644 --- a/drivers/net/ethernet/intel/idpf/idpf_txrx.c +++ b/drivers/net/ethernet/intel/idpf/idpf_txrx.c @@ -4650,57 +4650,47 @@ static void idpf_fill_dflt_rss_lut(struct idpf_vport *vport) rss_data = &adapter->vport_config[vport->idx]->user_config.rss_data; - for (i = 0; i < rss_data->rss_lut_size; i++) { + for (i = 0; i < rss_data->rss_lut_size; i++) rss_data->rss_lut[i] = i % num_active_rxq; - rss_data->cached_lut[i] = rss_data->rss_lut[i]; - } } /** - * idpf_init_rss - Allocate and initialize RSS resources + * idpf_init_rss_lut - Allocate and initialize RSS LUT * @vport: virtual port * - * Return 0 on success, negative on failure + * Return: 0 on success, negative on failure */ -int idpf_init_rss(struct idpf_vport *vport) +int idpf_init_rss_lut(struct idpf_vport *vport) { struct idpf_adapter *adapter = vport->adapter; struct idpf_rss_data *rss_data; - u32 lut_size; rss_data = &adapter->vport_config[vport->idx]->user_config.rss_data; + if (!rss_data->rss_lut) { + u32 lut_size; - lut_size = rss_data->rss_lut_size * sizeof(u32); - rss_data->rss_lut = kzalloc(lut_size, GFP_KERNEL); - if (!rss_data->rss_lut) - return -ENOMEM; - - rss_data->cached_lut = kzalloc(lut_size, GFP_KERNEL); - if (!rss_data->cached_lut) { - kfree(rss_data->rss_lut); - rss_data->rss_lut = NULL; - - return -ENOMEM; + lut_size = rss_data->rss_lut_size * sizeof(u32); + rss_data->rss_lut = kzalloc(lut_size, GFP_KERNEL); + if (!rss_data->rss_lut) + return -ENOMEM; } /* Fill the default RSS lut values */ idpf_fill_dflt_rss_lut(vport); - return idpf_config_rss(vport); + return 0; } /** - * idpf_deinit_rss - Release RSS resources + * idpf_deinit_rss_lut - Release RSS LUT * @vport: virtual port */ -void idpf_deinit_rss(struct idpf_vport *vport) +void idpf_deinit_rss_lut(struct idpf_vport *vport) { struct idpf_adapter *adapter = vport->adapter; struct idpf_rss_data *rss_data; rss_data = &adapter->vport_config[vport->idx]->user_config.rss_data; - kfree(rss_data->cached_lut); - rss_data->cached_lut = NULL; kfree(rss_data->rss_lut); rss_data->rss_lut = NULL; } diff --git a/drivers/net/ethernet/intel/idpf/idpf_txrx.h b/drivers/net/ethernet/intel/idpf/idpf_txrx.h index 75b977094741fd..7d20593bd87782 100644 --- a/drivers/net/ethernet/intel/idpf/idpf_txrx.h +++ b/drivers/net/ethernet/intel/idpf/idpf_txrx.h @@ -1086,8 +1086,8 @@ void idpf_vport_intr_deinit(struct idpf_vport *vport); int idpf_vport_intr_init(struct idpf_vport *vport); void idpf_vport_intr_ena(struct idpf_vport *vport); int idpf_config_rss(struct idpf_vport *vport); -int idpf_init_rss(struct idpf_vport *vport); -void idpf_deinit_rss(struct idpf_vport *vport); +int idpf_init_rss_lut(struct idpf_vport *vport); +void idpf_deinit_rss_lut(struct idpf_vport *vport); int idpf_rx_bufs_init_all(struct idpf_vport *vport); struct idpf_q_vector *idpf_find_rxq_vec(const struct idpf_vport *vport, diff --git a/drivers/net/ethernet/intel/idpf/idpf_virtchnl.c b/drivers/net/ethernet/intel/idpf/idpf_virtchnl.c index 01bbd12a642a07..cb702eac86c804 100644 --- a/drivers/net/ethernet/intel/idpf/idpf_virtchnl.c +++ b/drivers/net/ethernet/intel/idpf/idpf_virtchnl.c @@ -2804,6 +2804,10 @@ int idpf_send_get_stats_msg(struct idpf_vport *vport) * @vport: virtual port data structure * @get: flag to set or get rss look up table * + * When rxhash is disabled, RSS LUT will be configured with zeros. If rxhash + * is enabled, the LUT values stored in driver's soft copy will be used to setup + * the HW. + * * Returns 0 on success, negative on failure. */ int idpf_send_get_set_rss_lut_msg(struct idpf_vport *vport, bool get) @@ -2814,10 +2818,12 @@ int idpf_send_get_set_rss_lut_msg(struct idpf_vport *vport, bool get) struct idpf_rss_data *rss_data; int buf_size, lut_buf_size; ssize_t reply_sz; + bool rxhash_ena; int i; rss_data = &vport->adapter->vport_config[vport->idx]->user_config.rss_data; + rxhash_ena = idpf_is_feature_ena(vport, NETIF_F_RXHASH); buf_size = struct_size(rl, lut, rss_data->rss_lut_size); rl = kzalloc(buf_size, GFP_KERNEL); if (!rl) @@ -2839,7 +2845,8 @@ int idpf_send_get_set_rss_lut_msg(struct idpf_vport *vport, bool get) } else { rl->lut_entries = cpu_to_le16(rss_data->rss_lut_size); for (i = 0; i < rss_data->rss_lut_size; i++) - rl->lut[i] = cpu_to_le32(rss_data->rss_lut[i]); + rl->lut[i] = rxhash_ena ? + cpu_to_le32(rss_data->rss_lut[i]) : 0; xn_params.vc_op = VIRTCHNL2_OP_SET_RSS_LUT; } From 9abe73eff87d66241413fe7cb0f54b4fd0869880 Mon Sep 17 00:00:00 2001 From: Sreedevi Joshi Date: Mon, 24 Nov 2025 12:47:49 -0600 Subject: [PATCH 2354/3902] idpf: Fix RSS LUT configuration on down interfaces [ Upstream commit 445b49d13787da2fe8d51891ee196e5077feef44 ] RSS LUT provisioning and queries on a down interface currently return silently without effect. Users should be able to configure RSS settings even when the interface is down. Fix by maintaining RSS configuration changes in the driver's soft copy and deferring HW programming until the interface comes up. Fixes: 02cbfba1add5 ("idpf: add ethtool callbacks") Signed-off-by: Sreedevi Joshi Reviewed-by: Aleksandr Loktionov Reviewed-by: Sridhar Samudrala Reviewed-by: Emil Tantilov Tested-by: Samuel Salin Signed-off-by: Tony Nguyen Signed-off-by: Sasha Levin --- drivers/net/ethernet/intel/idpf/idpf_ethtool.c | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/drivers/net/ethernet/intel/idpf/idpf_ethtool.c b/drivers/net/ethernet/intel/idpf/idpf_ethtool.c index 8e9a93125b4aa6..3e191cf528b690 100644 --- a/drivers/net/ethernet/intel/idpf/idpf_ethtool.c +++ b/drivers/net/ethernet/intel/idpf/idpf_ethtool.c @@ -395,7 +395,10 @@ static u32 idpf_get_rxfh_indir_size(struct net_device *netdev) * @netdev: network interface device structure * @rxfh: pointer to param struct (indir, key, hfunc) * - * Reads the indirection table directly from the hardware. Always returns 0. + * RSS LUT and Key information are read from driver's cached + * copy. When rxhash is off, rss lut will be displayed as zeros. + * + * Return: 0 on success, -errno otherwise. */ static int idpf_get_rxfh(struct net_device *netdev, struct ethtool_rxfh_param *rxfh) @@ -403,10 +406,13 @@ static int idpf_get_rxfh(struct net_device *netdev, struct idpf_netdev_priv *np = netdev_priv(netdev); struct idpf_rss_data *rss_data; struct idpf_adapter *adapter; + struct idpf_vport *vport; + bool rxhash_ena; int err = 0; u16 i; idpf_vport_ctrl_lock(netdev); + vport = idpf_netdev_to_vport(netdev); adapter = np->adapter; @@ -416,9 +422,8 @@ static int idpf_get_rxfh(struct net_device *netdev, } rss_data = &adapter->vport_config[np->vport_idx]->user_config.rss_data; - if (!test_bit(IDPF_VPORT_UP, np->state)) - goto unlock_mutex; + rxhash_ena = idpf_is_feature_ena(vport, NETIF_F_RXHASH); rxfh->hfunc = ETH_RSS_HASH_TOP; if (rxfh->key) @@ -426,7 +431,7 @@ static int idpf_get_rxfh(struct net_device *netdev, if (rxfh->indir) { for (i = 0; i < rss_data->rss_lut_size; i++) - rxfh->indir[i] = rss_data->rss_lut[i]; + rxfh->indir[i] = rxhash_ena ? rss_data->rss_lut[i] : 0; } unlock_mutex: @@ -466,8 +471,6 @@ static int idpf_set_rxfh(struct net_device *netdev, } rss_data = &adapter->vport_config[vport->idx]->user_config.rss_data; - if (!test_bit(IDPF_VPORT_UP, np->state)) - goto unlock_mutex; if (rxfh->hfunc != ETH_RSS_HASH_NO_CHANGE && rxfh->hfunc != ETH_RSS_HASH_TOP) { @@ -483,7 +486,8 @@ static int idpf_set_rxfh(struct net_device *netdev, rss_data->rss_lut[lut] = rxfh->indir[lut]; } - err = idpf_config_rss(vport); + if (test_bit(IDPF_VPORT_UP, np->state)) + err = idpf_config_rss(vport); unlock_mutex: idpf_vport_ctrl_unlock(netdev); From ab92fa4dd81beaaed4e93a851f7a37c9b2d9776f Mon Sep 17 00:00:00 2001 From: Sreedevi Joshi Date: Mon, 24 Nov 2025 12:47:50 -0600 Subject: [PATCH 2355/3902] idpf: Fix RSS LUT NULL ptr issue after soft reset [ Upstream commit ebecca5b093895da801b3eba1a55b4ec4027d196 ] During soft reset, the RSS LUT is freed and not restored unless the interface is up. If an ethtool command that accesses the rss lut is attempted immediately after reset, it will result in NULL ptr dereference. Also, there is no need to reset the rss lut if the soft reset does not involve queue count change. After soft reset, set the RSS LUT to default values based on the updated queue count only if the reset was a result of a queue count change and the LUT was not configured by the user. In all other cases, don't touch the LUT. Steps to reproduce: ** Bring the interface down (if up) ifconfig eth1 down ** update the queue count (eg., 27->20) ethtool -L eth1 combined 20 ** display the RSS LUT ethtool -x eth1 [82375.558338] BUG: kernel NULL pointer dereference, address: 0000000000000000 [82375.558373] #PF: supervisor read access in kernel mode [82375.558391] #PF: error_code(0x0000) - not-present page [82375.558408] PGD 0 P4D 0 [82375.558421] Oops: Oops: 0000 [#1] SMP NOPTI [82375.558516] RIP: 0010:idpf_get_rxfh+0x108/0x150 [idpf] [82375.558786] Call Trace: [82375.558793] [82375.558804] rss_prepare.isra.0+0x187/0x2a0 [82375.558827] rss_prepare_data+0x3a/0x50 [82375.558845] ethnl_default_doit+0x13d/0x3e0 [82375.558863] genl_family_rcv_msg_doit+0x11f/0x180 [82375.558886] genl_rcv_msg+0x1ad/0x2b0 [82375.558902] ? __pfx_ethnl_default_doit+0x10/0x10 [82375.558920] ? __pfx_genl_rcv_msg+0x10/0x10 [82375.558937] netlink_rcv_skb+0x58/0x100 [82375.558957] genl_rcv+0x2c/0x50 [82375.558971] netlink_unicast+0x289/0x3e0 [82375.558988] netlink_sendmsg+0x215/0x440 [82375.559005] __sys_sendto+0x234/0x240 [82375.559555] __x64_sys_sendto+0x28/0x30 [82375.560068] x64_sys_call+0x1909/0x1da0 [82375.560576] do_syscall_64+0x7a/0xfa0 [82375.561076] ? clear_bhb_loop+0x60/0xb0 [82375.561567] entry_SYSCALL_64_after_hwframe+0x76/0x7e Fixes: 02cbfba1add5 ("idpf: add ethtool callbacks") Signed-off-by: Sreedevi Joshi Reviewed-by: Aleksandr Loktionov Reviewed-by: Sridhar Samudrala Reviewed-by: Emil Tantilov Reviewed-by: Simon Horman Tested-by: Samuel Salin Signed-off-by: Tony Nguyen Signed-off-by: Sasha Levin --- drivers/net/ethernet/intel/idpf/idpf_lib.c | 20 ++++---------------- drivers/net/ethernet/intel/idpf/idpf_txrx.c | 2 +- drivers/net/ethernet/intel/idpf/idpf_txrx.h | 1 + 3 files changed, 6 insertions(+), 17 deletions(-) diff --git a/drivers/net/ethernet/intel/idpf/idpf_lib.c b/drivers/net/ethernet/intel/idpf/idpf_lib.c index 51716e5a84ef31..003bab3ce5ae6c 100644 --- a/drivers/net/ethernet/intel/idpf/idpf_lib.c +++ b/drivers/net/ethernet/intel/idpf/idpf_lib.c @@ -1484,8 +1484,6 @@ static int idpf_vport_open(struct idpf_vport *vport, bool rtnl) { struct idpf_netdev_priv *np = netdev_priv(vport->netdev); struct idpf_adapter *adapter = vport->adapter; - struct idpf_vport_config *vport_config; - struct idpf_rss_data *rss_data; int err; if (test_bit(IDPF_VPORT_UP, np->state)) @@ -1579,19 +1577,6 @@ static int idpf_vport_open(struct idpf_vport *vport, bool rtnl) idpf_restore_features(vport); - vport_config = adapter->vport_config[vport->idx]; - rss_data = &vport_config->user_config.rss_data; - - if (!rss_data->rss_lut) { - err = idpf_init_rss_lut(vport); - if (err) { - dev_err(&adapter->pdev->dev, - "Failed to initialize RSS LUT for vport %u: %d\n", - vport->vport_id, err); - goto disable_vport; - } - } - err = idpf_config_rss(vport); if (err) { dev_err(&adapter->pdev->dev, "Failed to configure RSS for vport %u: %d\n", @@ -2068,7 +2053,6 @@ int idpf_initiate_soft_reset(struct idpf_vport *vport, idpf_vport_stop(vport, false); } - idpf_deinit_rss_lut(vport); /* We're passing in vport here because we need its wait_queue * to send a message and it should be getting all the vport * config data out of the adapter but we need to be careful not @@ -2094,6 +2078,10 @@ int idpf_initiate_soft_reset(struct idpf_vport *vport, if (err) goto err_open; + if (reset_cause == IDPF_SR_Q_CHANGE && + !netif_is_rxfh_configured(vport->netdev)) + idpf_fill_dflt_rss_lut(vport); + if (vport_is_up) err = idpf_vport_open(vport, false); diff --git a/drivers/net/ethernet/intel/idpf/idpf_txrx.c b/drivers/net/ethernet/intel/idpf/idpf_txrx.c index 39553689ffdbf5..3698979b4c9ee6 100644 --- a/drivers/net/ethernet/intel/idpf/idpf_txrx.c +++ b/drivers/net/ethernet/intel/idpf/idpf_txrx.c @@ -4641,7 +4641,7 @@ int idpf_config_rss(struct idpf_vport *vport) * idpf_fill_dflt_rss_lut - Fill the indirection table with the default values * @vport: virtual port structure */ -static void idpf_fill_dflt_rss_lut(struct idpf_vport *vport) +void idpf_fill_dflt_rss_lut(struct idpf_vport *vport) { struct idpf_adapter *adapter = vport->adapter; u16 num_active_rxq = vport->num_rxq; diff --git a/drivers/net/ethernet/intel/idpf/idpf_txrx.h b/drivers/net/ethernet/intel/idpf/idpf_txrx.h index 7d20593bd87782..0472698ca19270 100644 --- a/drivers/net/ethernet/intel/idpf/idpf_txrx.h +++ b/drivers/net/ethernet/intel/idpf/idpf_txrx.h @@ -1085,6 +1085,7 @@ void idpf_vport_intr_update_itr_ena_irq(struct idpf_q_vector *q_vector); void idpf_vport_intr_deinit(struct idpf_vport *vport); int idpf_vport_intr_init(struct idpf_vport *vport); void idpf_vport_intr_ena(struct idpf_vport *vport); +void idpf_fill_dflt_rss_lut(struct idpf_vport *vport); int idpf_config_rss(struct idpf_vport *vport); int idpf_init_rss_lut(struct idpf_vport *vport); void idpf_deinit_rss_lut(struct idpf_vport *vport); From bfeb4dfc805003fe4cfb1923f932581bd7e8db92 Mon Sep 17 00:00:00 2001 From: Sreedevi Joshi Date: Tue, 2 Dec 2025 17:12:46 -0600 Subject: [PATCH 2356/3902] idpf: Fix error handling in idpf_vport_open() [ Upstream commit 87b8ee64685bc096a087af833d4594b2332bfdb1 ] Fix error handling to properly cleanup interrupts when idpf_vport_queue_ids_init() or idpf_rx_bufs_init_all() fail. Jump to 'intr_deinit' instead of 'queues_rel' to ensure interrupts are cleaned up before releasing other resources. Fixes: d4d558718266 ("idpf: initialize interrupts and enable vport") Signed-off-by: Sreedevi Joshi Reviewed-by: Madhu Chittim Reviewed-by: Aleksandr Loktionov Reviewed-by: Simon Horman Tested-by: Samuel Salin Signed-off-by: Tony Nguyen Signed-off-by: Sasha Levin --- drivers/net/ethernet/intel/idpf/idpf_lib.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/net/ethernet/intel/idpf/idpf_lib.c b/drivers/net/ethernet/intel/idpf/idpf_lib.c index 003bab3ce5ae6c..131a8121839bd0 100644 --- a/drivers/net/ethernet/intel/idpf/idpf_lib.c +++ b/drivers/net/ethernet/intel/idpf/idpf_lib.c @@ -1524,14 +1524,14 @@ static int idpf_vport_open(struct idpf_vport *vport, bool rtnl) if (err) { dev_err(&adapter->pdev->dev, "Failed to initialize queue registers for vport %u: %d\n", vport->vport_id, err); - goto queues_rel; + goto intr_deinit; } err = idpf_rx_bufs_init_all(vport); if (err) { dev_err(&adapter->pdev->dev, "Failed to initialize RX buffers for vport %u: %d\n", vport->vport_id, err); - goto queues_rel; + goto intr_deinit; } idpf_rx_init_buf_tail(vport); From 258e7c55f9395b4711afa590fb0230911c602b8c Mon Sep 17 00:00:00 2001 From: Joshua Hay Date: Mon, 3 Nov 2025 13:20:36 -0800 Subject: [PATCH 2357/3902] idpf: cap maximum Rx buffer size [ Upstream commit 086efe0a1ecc36cffe46640ce12649a4cd3ff171 ] The HW only supports a maximum Rx buffer size of 16K-128. On systems using large pages, the libeth logic can configure the buffer size to be larger than this. The upper bound is PAGE_SIZE while the lower bound is MTU rounded up to the nearest power of 2. For example, ARM systems with a 64K page size and an mtu of 9000 will set the Rx buffer size to 16K, which will cause the config Rx queues message to fail. Initialize the bufq/fill queue buf_len field to the maximum supported size. This will trigger the libeth logic to cap the maximum Rx buffer size by reducing the upper bound. Fixes: 74d1412ac8f37 ("idpf: use libeth Rx buffer management for payload buffer") Signed-off-by: Joshua Hay Acked-by: Alexander Lobakin Reviewed-by: Madhu Chittim Reviewed-by: Jacob Keller Reviewed-by: Aleksandr Loktionov Reviewed-by: David Decotigny Signed-off-by: Tony Nguyen Signed-off-by: Sasha Levin --- drivers/net/ethernet/intel/idpf/idpf_txrx.c | 8 +++++--- drivers/net/ethernet/intel/idpf/idpf_txrx.h | 1 + 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/drivers/net/ethernet/intel/idpf/idpf_txrx.c b/drivers/net/ethernet/intel/idpf/idpf_txrx.c index 3698979b4c9ee6..f66948f5de78bb 100644 --- a/drivers/net/ethernet/intel/idpf/idpf_txrx.c +++ b/drivers/net/ethernet/intel/idpf/idpf_txrx.c @@ -695,9 +695,10 @@ static int idpf_rx_buf_alloc_singleq(struct idpf_rx_queue *rxq) static int idpf_rx_bufs_init_singleq(struct idpf_rx_queue *rxq) { struct libeth_fq fq = { - .count = rxq->desc_count, - .type = LIBETH_FQE_MTU, - .nid = idpf_q_vector_to_mem(rxq->q_vector), + .count = rxq->desc_count, + .type = LIBETH_FQE_MTU, + .buf_len = IDPF_RX_MAX_BUF_SZ, + .nid = idpf_q_vector_to_mem(rxq->q_vector), }; int ret; @@ -754,6 +755,7 @@ static int idpf_rx_bufs_init(struct idpf_buf_queue *bufq, .truesize = bufq->truesize, .count = bufq->desc_count, .type = type, + .buf_len = IDPF_RX_MAX_BUF_SZ, .hsplit = idpf_queue_has(HSPLIT_EN, bufq), .xdp = idpf_xdp_enabled(bufq->q_vector->vport), .nid = idpf_q_vector_to_mem(bufq->q_vector), diff --git a/drivers/net/ethernet/intel/idpf/idpf_txrx.h b/drivers/net/ethernet/intel/idpf/idpf_txrx.h index 0472698ca19270..423cc9486dce77 100644 --- a/drivers/net/ethernet/intel/idpf/idpf_txrx.h +++ b/drivers/net/ethernet/intel/idpf/idpf_txrx.h @@ -101,6 +101,7 @@ do { \ idx = 0; \ } while (0) +#define IDPF_RX_MAX_BUF_SZ (16384 - 128) #define IDPF_RX_BUF_STRIDE 32 #define IDPF_RX_BUF_POST_STRIDE 16 #define IDPF_LOW_WATERMARK 64 From 0ad6d6e50e9d8bf596cfe77a882ddc20b29f525a Mon Sep 17 00:00:00 2001 From: Larysa Zaremba Date: Mon, 17 Nov 2025 08:03:49 +0100 Subject: [PATCH 2358/3902] idpf: fix aux device unplugging when rdma is not supported by vport [ Upstream commit 4648fb2f2e7210c53b85220ee07d42d1e4bae3f9 ] If vport flags do not contain VIRTCHNL2_VPORT_ENABLE_RDMA, driver does not allocate vdev_info for this vport. This leads to kernel NULL pointer dereference in idpf_idc_vport_dev_down(), which references vdev_info for every vport regardless. Check, if vdev_info was ever allocated before unplugging aux device. Fixes: be91128c579c ("idpf: implement RDMA vport auxiliary dev create, init, and destroy") Reviewed-by: Madhu Chittim Signed-off-by: Larysa Zaremba Reviewed-by: Paul Menzel Reviewed-by: Aleksandr Loktionov Tested-by: Krishneil Singh Signed-off-by: Tony Nguyen Signed-off-by: Sasha Levin --- drivers/net/ethernet/intel/idpf/idpf_idc.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/net/ethernet/intel/idpf/idpf_idc.c b/drivers/net/ethernet/intel/idpf/idpf_idc.c index 7e20a07e98e538..6dad0593f7f222 100644 --- a/drivers/net/ethernet/intel/idpf/idpf_idc.c +++ b/drivers/net/ethernet/intel/idpf/idpf_idc.c @@ -322,7 +322,7 @@ static void idpf_idc_vport_dev_down(struct idpf_adapter *adapter) for (i = 0; i < adapter->num_alloc_vports; i++) { struct idpf_vport *vport = adapter->vports[i]; - if (!vport) + if (!vport || !vport->vdev_info) continue; idpf_unplug_aux_dev(vport->vdev_info->adev); From 2d6cde9953f9fdb5773825d75e932341365977d8 Mon Sep 17 00:00:00 2001 From: Vladimir Oltean Date: Sun, 4 Jan 2026 11:39:52 +0200 Subject: [PATCH 2359/3902] Revert "dsa: mv88e6xxx: make serdes SGMII/Fiber tx amplitude configurable" [ Upstream commit 7801edc9badd972cb62cf11c0427e70b6dca239d ] This reverts commit 926eae604403acfa27ba5b072af458e87e634a50, which never could have produced the intended effect: https://lore.kernel.org/netdev/AM0PR06MB10396BBF8B568D77556FC46F8F7DEA@AM0PR06MB10396.eurprd06.prod.outlook.com/ The reason why it is broken beyond repair in this form is that the mv88e6xxx driver outsources its "tx-p2p-microvolt" property to the OF node of an external Ethernet PHY. This: (a) does not work if there is no external PHY (chip-to-chip connection, or SFP module) (b) pollutes the OF property namespace / bindings of said external PHY ("tx-p2p-microvolt" could have meaning for the Ethernet PHY's SerDes interface as well) We can revisit the idea of making SerDes amplitude configurable once we have proper bindings for the mv88e6xxx SerDes. Until then, remove the code that leaves us with unnecessary baggage. Fixes: 926eae604403 ("dsa: mv88e6xxx: make serdes SGMII/Fiber tx amplitude configurable") Cc: Holger Brunck Signed-off-by: Vladimir Oltean Reviewed-by: Andrew Lunn Link: https://patch.msgid.link/20260104093952.486606-1-vladimir.oltean@nxp.com Signed-off-by: Jakub Kicinski Signed-off-by: Sasha Levin --- drivers/net/dsa/mv88e6xxx/chip.c | 23 --------------- drivers/net/dsa/mv88e6xxx/chip.h | 4 --- drivers/net/dsa/mv88e6xxx/serdes.c | 46 ------------------------------ drivers/net/dsa/mv88e6xxx/serdes.h | 5 ---- 4 files changed, 78 deletions(-) diff --git a/drivers/net/dsa/mv88e6xxx/chip.c b/drivers/net/dsa/mv88e6xxx/chip.c index b4d48997bf4674..09002c853b78e1 100644 --- a/drivers/net/dsa/mv88e6xxx/chip.c +++ b/drivers/net/dsa/mv88e6xxx/chip.c @@ -3364,13 +3364,10 @@ static int mv88e6xxx_setup_upstream_port(struct mv88e6xxx_chip *chip, int port) static int mv88e6xxx_setup_port(struct mv88e6xxx_chip *chip, int port) { - struct device_node *phy_handle = NULL; struct fwnode_handle *ports_fwnode; struct fwnode_handle *port_fwnode; struct dsa_switch *ds = chip->ds; struct mv88e6xxx_port *p; - struct dsa_port *dp; - int tx_amp; int err; u16 reg; u32 val; @@ -3582,23 +3579,6 @@ static int mv88e6xxx_setup_port(struct mv88e6xxx_chip *chip, int port) return err; } - if (chip->info->ops->serdes_set_tx_amplitude) { - dp = dsa_to_port(ds, port); - if (dp) - phy_handle = of_parse_phandle(dp->dn, "phy-handle", 0); - - if (phy_handle && !of_property_read_u32(phy_handle, - "tx-p2p-microvolt", - &tx_amp)) - err = chip->info->ops->serdes_set_tx_amplitude(chip, - port, tx_amp); - if (phy_handle) { - of_node_put(phy_handle); - if (err) - return err; - } - } - /* Port based VLAN map: give each port the same default address * database, and allow bidirectional communication between the * CPU and DSA port(s), and the other ports. @@ -4768,7 +4748,6 @@ static const struct mv88e6xxx_ops mv88e6176_ops = { .serdes_irq_mapping = mv88e6352_serdes_irq_mapping, .serdes_get_regs_len = mv88e6352_serdes_get_regs_len, .serdes_get_regs = mv88e6352_serdes_get_regs, - .serdes_set_tx_amplitude = mv88e6352_serdes_set_tx_amplitude, .gpio_ops = &mv88e6352_gpio_ops, .phylink_get_caps = mv88e6352_phylink_get_caps, .pcs_ops = &mv88e6352_pcs_ops, @@ -5044,7 +5023,6 @@ static const struct mv88e6xxx_ops mv88e6240_ops = { .serdes_irq_mapping = mv88e6352_serdes_irq_mapping, .serdes_get_regs_len = mv88e6352_serdes_get_regs_len, .serdes_get_regs = mv88e6352_serdes_get_regs, - .serdes_set_tx_amplitude = mv88e6352_serdes_set_tx_amplitude, .gpio_ops = &mv88e6352_gpio_ops, .avb_ops = &mv88e6352_avb_ops, .ptp_ops = &mv88e6352_ptp_ops, @@ -5481,7 +5459,6 @@ static const struct mv88e6xxx_ops mv88e6352_ops = { .serdes_get_stats = mv88e6352_serdes_get_stats, .serdes_get_regs_len = mv88e6352_serdes_get_regs_len, .serdes_get_regs = mv88e6352_serdes_get_regs, - .serdes_set_tx_amplitude = mv88e6352_serdes_set_tx_amplitude, .phylink_get_caps = mv88e6352_phylink_get_caps, .pcs_ops = &mv88e6352_pcs_ops, }; diff --git a/drivers/net/dsa/mv88e6xxx/chip.h b/drivers/net/dsa/mv88e6xxx/chip.h index 2f211e55cb47b0..e073446ee7d02b 100644 --- a/drivers/net/dsa/mv88e6xxx/chip.h +++ b/drivers/net/dsa/mv88e6xxx/chip.h @@ -642,10 +642,6 @@ struct mv88e6xxx_ops { void (*serdes_get_regs)(struct mv88e6xxx_chip *chip, int port, void *_p); - /* SERDES SGMII/Fiber Output Amplitude */ - int (*serdes_set_tx_amplitude)(struct mv88e6xxx_chip *chip, int port, - int val); - /* Address Translation Unit operations */ int (*atu_get_hash)(struct mv88e6xxx_chip *chip, u8 *hash); int (*atu_set_hash)(struct mv88e6xxx_chip *chip, u8 hash); diff --git a/drivers/net/dsa/mv88e6xxx/serdes.c b/drivers/net/dsa/mv88e6xxx/serdes.c index b3330211edbcaa..a936ee80ce006f 100644 --- a/drivers/net/dsa/mv88e6xxx/serdes.c +++ b/drivers/net/dsa/mv88e6xxx/serdes.c @@ -25,14 +25,6 @@ static int mv88e6352_serdes_read(struct mv88e6xxx_chip *chip, int reg, reg, val); } -static int mv88e6352_serdes_write(struct mv88e6xxx_chip *chip, int reg, - u16 val) -{ - return mv88e6xxx_phy_page_write(chip, MV88E6352_ADDR_SERDES, - MV88E6352_SERDES_PAGE_FIBER, - reg, val); -} - static int mv88e6390_serdes_read(struct mv88e6xxx_chip *chip, int lane, int device, int reg, u16 *val) { @@ -506,41 +498,3 @@ void mv88e6390_serdes_get_regs(struct mv88e6xxx_chip *chip, int port, void *_p) p[i] = reg; } } - -static const int mv88e6352_serdes_p2p_to_reg[] = { - /* Index of value in microvolts corresponds to the register value */ - 14000, 112000, 210000, 308000, 406000, 504000, 602000, 700000, -}; - -int mv88e6352_serdes_set_tx_amplitude(struct mv88e6xxx_chip *chip, int port, - int val) -{ - bool found = false; - u16 ctrl, reg; - int err; - int i; - - err = mv88e6352_g2_scratch_port_has_serdes(chip, port); - if (err <= 0) - return err; - - for (i = 0; i < ARRAY_SIZE(mv88e6352_serdes_p2p_to_reg); ++i) { - if (mv88e6352_serdes_p2p_to_reg[i] == val) { - reg = i; - found = true; - break; - } - } - - if (!found) - return -EINVAL; - - err = mv88e6352_serdes_read(chip, MV88E6352_SERDES_SPEC_CTRL2, &ctrl); - if (err) - return err; - - ctrl &= ~MV88E6352_SERDES_OUT_AMP_MASK; - ctrl |= reg; - - return mv88e6352_serdes_write(chip, MV88E6352_SERDES_SPEC_CTRL2, ctrl); -} diff --git a/drivers/net/dsa/mv88e6xxx/serdes.h b/drivers/net/dsa/mv88e6xxx/serdes.h index ad887d8601bcbb..17a3e85fabaa34 100644 --- a/drivers/net/dsa/mv88e6xxx/serdes.h +++ b/drivers/net/dsa/mv88e6xxx/serdes.h @@ -29,8 +29,6 @@ struct phylink_link_state; #define MV88E6352_SERDES_INT_FIBRE_ENERGY BIT(4) #define MV88E6352_SERDES_INT_STATUS 0x13 -#define MV88E6352_SERDES_SPEC_CTRL2 0x1a -#define MV88E6352_SERDES_OUT_AMP_MASK 0x0007 #define MV88E6341_PORT5_LANE 0x15 @@ -140,9 +138,6 @@ void mv88e6352_serdes_get_regs(struct mv88e6xxx_chip *chip, int port, void *_p); int mv88e6390_serdes_get_regs_len(struct mv88e6xxx_chip *chip, int port); void mv88e6390_serdes_get_regs(struct mv88e6xxx_chip *chip, int port, void *_p); -int mv88e6352_serdes_set_tx_amplitude(struct mv88e6xxx_chip *chip, int port, - int val); - /* Return the (first) SERDES lane address a port is using, -errno otherwise. */ static inline int mv88e6xxx_serdes_get_lane(struct mv88e6xxx_chip *chip, int port) From 0c63d5683eae6a7b4d81382bcbecb2a19feff90d Mon Sep 17 00:00:00 2001 From: Eric Dumazet Date: Mon, 5 Jan 2026 09:36:30 +0000 Subject: [PATCH 2360/3902] udp: call skb_orphan() before skb_attempt_defer_free() [ Upstream commit e5c8eda39a9fc1547d1398d707aa06c1d080abdd ] Standard UDP receive path does not use skb->destructor. But skmsg layer does use it, since it calls skb_set_owner_sk_safe() from udp_read_skb(). This then triggers this warning in skb_attempt_defer_free(): DEBUG_NET_WARN_ON_ONCE(skb->destructor); We must call skb_orphan() to fix this issue. Fixes: 6471658dc66c ("udp: use skb_attempt_defer_free()") Reported-by: syzbot+3e68572cf2286ce5ebe9@syzkaller.appspotmail.com Closes: https://lore.kernel.org/netdev/695b83bd.050a0220.1c9965.002b.GAE@google.com/T/#u Signed-off-by: Eric Dumazet Link: https://patch.msgid.link/20260105093630.1976085-1-edumazet@google.com Signed-off-by: Jakub Kicinski Signed-off-by: Sasha Levin --- net/ipv4/udp.c | 1 + 1 file changed, 1 insertion(+) diff --git a/net/ipv4/udp.c b/net/ipv4/udp.c index 30dfbf73729dad..860bd61ff047ff 100644 --- a/net/ipv4/udp.c +++ b/net/ipv4/udp.c @@ -1851,6 +1851,7 @@ void skb_consume_udp(struct sock *sk, struct sk_buff *skb, int len) sk_peek_offset_bwd(sk, len); if (!skb_shared(skb)) { + skb_orphan(skb); skb_attempt_defer_free(skb); return; } From 06dc322dcb20c71748d8247828895043e9486a74 Mon Sep 17 00:00:00 2001 From: Maxime Chevallier Date: Mon, 5 Jan 2026 16:18:39 +0100 Subject: [PATCH 2361/3902] net: sfp: return the number of written bytes for smbus single byte access [ Upstream commit 13ff3e724207f579d3c814ee05516fefcb4f32e8 ] We expect the SFP write accessors to return the number of written bytes. We fail to do so for single-byte smbus accesses, which may cause errors when setting a module's high-power state and for some cotsworks modules. Let's return the amount of written bytes, as expected. Fixes: 7662abf4db94 ("net: phy: sfp: Add support for SMBus module access") Signed-off-by: Maxime Chevallier Reviewed-by: Andrew Lunn Link: https://patch.msgid.link/20260105151840.144552-1-maxime.chevallier@bootlin.com Signed-off-by: Jakub Kicinski Signed-off-by: Sasha Levin --- drivers/net/phy/sfp.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/net/phy/sfp.c b/drivers/net/phy/sfp.c index 0401fa6b24d257..6b4dd906b804f4 100644 --- a/drivers/net/phy/sfp.c +++ b/drivers/net/phy/sfp.c @@ -763,7 +763,7 @@ static int sfp_smbus_byte_write(struct sfp *sfp, bool a2, u8 dev_addr, dev_addr++; } - return 0; + return data - (u8 *)buf; } static int sfp_i2c_configure(struct sfp *sfp, struct i2c_adapter *i2c) From 67550a1130b647bb0d093c9c0a810c69aa6a30a8 Mon Sep 17 00:00:00 2001 From: Shivani Gupta Date: Mon, 5 Jan 2026 00:59:05 +0000 Subject: [PATCH 2362/3902] net/sched: act_api: avoid dereferencing ERR_PTR in tcf_idrinfo_destroy [ Upstream commit adb25a46dc0a43173f5ea5f5f58fc8ba28970c7c ] syzbot reported a crash in tc_act_in_hw() during netns teardown where tcf_idrinfo_destroy() passed an ERR_PTR(-EBUSY) value as a tc_action pointer, leading to an invalid dereference. Guard against ERR_PTR entries when iterating the action IDR so teardown does not call tc_act_in_hw() on an error pointer. Fixes: 84a7d6797e6a ("net/sched: acp_api: no longer acquire RTNL in tc_action_net_exit()") Link: https://syzkaller.appspot.com/bug?extid=8f1c492ffa4644ff3826 Reported-by: syzbot+8f1c492ffa4644ff3826@syzkaller.appspotmail.com Closes: https://syzkaller.appspot.com/bug?extid=8f1c492ffa4644ff3826 Signed-off-by: Shivani Gupta Link: https://patch.msgid.link/20260105005905.243423-1-shivani07g@gmail.com Signed-off-by: Jakub Kicinski Signed-off-by: Sasha Levin --- net/sched/act_api.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/net/sched/act_api.c b/net/sched/act_api.c index ff6be5cfe2b050..e1ab0faeb8113e 100644 --- a/net/sched/act_api.c +++ b/net/sched/act_api.c @@ -940,6 +940,8 @@ void tcf_idrinfo_destroy(const struct tc_action_ops *ops, int ret; idr_for_each_entry_ul(idr, p, tmp, id) { + if (IS_ERR(p)) + continue; if (tc_act_in_hw(p) && !mutex_taken) { rtnl_lock(); mutex_taken = true; From 334bbbbf4c809c31722b5cae6c2158d9ff2fdc9f Mon Sep 17 00:00:00 2001 From: Gal Pressman Date: Mon, 5 Jan 2026 18:33:19 +0200 Subject: [PATCH 2363/3902] selftests: drv-net: Bring back tool() to driver __init__s [ Upstream commit 353cfc0ef3f34ef7fe313ae38dac37f2454a7cf5 ] The pp_alloc_fail.py test (which doesn't run in NIPA CI?) uses tool, add back the import. Resolves: ImportError: cannot import name 'tool' from 'lib.py' Fixes: 68a052239fc4 ("selftests: drv-net: update remaining Python init files") Reviewed-by: Nimrod Oren Signed-off-by: Gal Pressman Link: https://patch.msgid.link/20260105163319.47619-1-gal@nvidia.com Signed-off-by: Jakub Kicinski Signed-off-by: Sasha Levin --- tools/testing/selftests/drivers/net/hw/lib/py/__init__.py | 4 ++-- tools/testing/selftests/net/lib/py/__init__.py | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/tools/testing/selftests/drivers/net/hw/lib/py/__init__.py b/tools/testing/selftests/drivers/net/hw/lib/py/__init__.py index fb010a48a5a196..a86f5c311fdcba 100644 --- a/tools/testing/selftests/drivers/net/hw/lib/py/__init__.py +++ b/tools/testing/selftests/drivers/net/hw/lib/py/__init__.py @@ -22,7 +22,7 @@ NlError, RtnlFamily, DevlinkFamily, PSPFamily from net.lib.py import CmdExitFailure from net.lib.py import bkg, cmd, bpftool, bpftrace, defer, ethtool, \ - fd_read_timeout, ip, rand_port, wait_port_listen, wait_file + fd_read_timeout, ip, rand_port, wait_port_listen, wait_file, tool from net.lib.py import KsftSkipEx, KsftFailEx, KsftXfailEx from net.lib.py import ksft_disruptive, ksft_exit, ksft_pr, ksft_run, \ ksft_setup @@ -37,7 +37,7 @@ "CmdExitFailure", "bkg", "cmd", "bpftool", "bpftrace", "defer", "ethtool", "fd_read_timeout", "ip", "rand_port", - "wait_port_listen", "wait_file", + "wait_port_listen", "wait_file", "tool", "KsftSkipEx", "KsftFailEx", "KsftXfailEx", "ksft_disruptive", "ksft_exit", "ksft_pr", "ksft_run", "ksft_setup", diff --git a/tools/testing/selftests/net/lib/py/__init__.py b/tools/testing/selftests/net/lib/py/__init__.py index 97b7cf2b20eb93..8f2da17d535106 100644 --- a/tools/testing/selftests/net/lib/py/__init__.py +++ b/tools/testing/selftests/net/lib/py/__init__.py @@ -12,7 +12,7 @@ from .netns import NetNS, NetNSEnter from .nsim import NetdevSim, NetdevSimDev from .utils import CmdExitFailure, fd_read_timeout, cmd, bkg, defer, \ - bpftool, ip, ethtool, bpftrace, rand_port, wait_port_listen, wait_file + bpftool, ip, ethtool, bpftrace, rand_port, wait_port_listen, wait_file, tool from .ynl import NlError, YnlFamily, EthtoolFamily, NetdevFamily, RtnlFamily, RtnlAddrFamily from .ynl import NetshaperFamily, DevlinkFamily, PSPFamily @@ -25,7 +25,7 @@ "NetNS", "NetNSEnter", "CmdExitFailure", "fd_read_timeout", "cmd", "bkg", "defer", "bpftool", "ip", "ethtool", "bpftrace", "rand_port", - "wait_port_listen", "wait_file", + "wait_port_listen", "wait_file", "tool", "NetdevSim", "NetdevSimDev", "NetshaperFamily", "DevlinkFamily", "PSPFamily", "NlError", "YnlFamily", "EthtoolFamily", "NetdevFamily", "RtnlFamily", From 086e2928c1dacc2840d63eccabcfb557997695e6 Mon Sep 17 00:00:00 2001 From: Yohei Kojima Date: Tue, 6 Jan 2026 00:17:32 +0900 Subject: [PATCH 2364/3902] net: netdevsim: fix inconsistent carrier state after link/unlink [ Upstream commit d83dddffe1904e4a576d11a541878850a8e64cd2 ] This patch fixes the edge case behavior on ifup/ifdown and linking/unlinking two netdevsim interfaces: 1. unlink two interfaces netdevsim1 and netdevsim2 2. ifdown netdevsim1 3. ifup netdevsim1 4. link two interfaces netdevsim1 and netdevsim2 5. (Now two interfaces are linked in terms of netdevsim peer, but carrier state of the two interfaces remains DOWN.) This inconsistent behavior is caused by the current implementation, which only cares about the "link, then ifup" order, not "ifup, then link" order. This patch fixes the inconsistency by calling netif_carrier_on() when two netdevsim interfaces are linked. This patch fixes buggy behavior on NetworkManager-based systems which causes the netdevsim test to fail with the following error: # timeout set to 600 # selftests: drivers/net/netdevsim: peer.sh # 2025/12/25 00:54:03 socat[9115] W address is opened in read-write mode but only supports read-only # 2025/12/25 00:56:17 socat[9115] W connect(7, AF=2 192.168.1.1:1234, 16): Connection timed out # 2025/12/25 00:56:17 socat[9115] E TCP:192.168.1.1:1234: Connection timed out # expected 3 bytes, got 0 # 2025/12/25 00:56:17 socat[9109] W exiting on signal 15 not ok 13 selftests: drivers/net/netdevsim: peer.sh # exit=1 This patch also solves timeout on TCP Fast Open (TFO) test in NetworkManager-based systems because it also depends on netdevsim's carrier consistency. Fixes: 1a8fed52f7be ("netdevsim: set the carrier when the device goes up") Signed-off-by: Yohei Kojima Reviewed-by: Breno Leitao Link: https://patch.msgid.link/602c9e1ba5bb2ee1997bb38b1d866c9c3b807ae9.1767624906.git.yk@y-koj.net Signed-off-by: Jakub Kicinski Signed-off-by: Sasha Levin --- drivers/net/netdevsim/bus.c | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/drivers/net/netdevsim/bus.c b/drivers/net/netdevsim/bus.c index 70e8c38ddad6b6..d16b95304aa7e3 100644 --- a/drivers/net/netdevsim/bus.c +++ b/drivers/net/netdevsim/bus.c @@ -332,6 +332,11 @@ static ssize_t link_device_store(const struct bus_type *bus, const char *buf, si rcu_assign_pointer(nsim_a->peer, nsim_b); rcu_assign_pointer(nsim_b->peer, nsim_a); + if (netif_running(dev_a) && netif_running(dev_b)) { + netif_carrier_on(dev_a); + netif_carrier_on(dev_b); + } + out_err: put_net(ns_b); put_net(ns_a); @@ -381,6 +386,9 @@ static ssize_t unlink_device_store(const struct bus_type *bus, const char *buf, if (!peer) goto out_put_netns; + netif_carrier_off(dev); + netif_carrier_off(peer->netdev); + err = 0; RCU_INIT_POINTER(nsim->peer, NULL); RCU_INIT_POINTER(peer->peer, NULL); From 6c0d642e8c589bf4ca64b4f4abe1bfe1b9c9be10 Mon Sep 17 00:00:00 2001 From: Caleb Sander Mateos Date: Tue, 6 Jan 2026 13:08:37 -0700 Subject: [PATCH 2365/3902] block: don't merge bios with different app_tags [ Upstream commit 6acd4ac5f8f0ec9b946875553e52907700bcfc77 ] nvme_set_app_tag() uses the app_tag value from the bio_integrity_payload of the struct request's first bio. This assumes all the request's bios have the same app_tag. However, it is possible for bios with different app_tag values to be merged into a single request. Add a check in blk_integrity_merge_{bio,rq}() to prevent the merging of bios/requests with different app_tag values if BIP_CHECK_APPTAG is set. Signed-off-by: Caleb Sander Mateos Fixes: 3d8b5a22d404 ("block: add support to pass user meta buffer") Signed-off-by: Jens Axboe Signed-off-by: Sasha Levin --- block/blk-integrity.c | 23 ++++++++++++++++++----- 1 file changed, 18 insertions(+), 5 deletions(-) diff --git a/block/blk-integrity.c b/block/blk-integrity.c index 9b27963680dc34..964eebbee14d04 100644 --- a/block/blk-integrity.c +++ b/block/blk-integrity.c @@ -140,14 +140,21 @@ EXPORT_SYMBOL_GPL(blk_rq_integrity_map_user); bool blk_integrity_merge_rq(struct request_queue *q, struct request *req, struct request *next) { + struct bio_integrity_payload *bip, *bip_next; + if (blk_integrity_rq(req) == 0 && blk_integrity_rq(next) == 0) return true; if (blk_integrity_rq(req) == 0 || blk_integrity_rq(next) == 0) return false; - if (bio_integrity(req->bio)->bip_flags != - bio_integrity(next->bio)->bip_flags) + bip = bio_integrity(req->bio); + bip_next = bio_integrity(next->bio); + if (bip->bip_flags != bip_next->bip_flags) + return false; + + if (bip->bip_flags & BIP_CHECK_APPTAG && + bip->app_tag != bip_next->app_tag) return false; if (req->nr_integrity_segments + next->nr_integrity_segments > @@ -163,15 +170,21 @@ bool blk_integrity_merge_rq(struct request_queue *q, struct request *req, bool blk_integrity_merge_bio(struct request_queue *q, struct request *req, struct bio *bio) { + struct bio_integrity_payload *bip, *bip_bio = bio_integrity(bio); int nr_integrity_segs; - if (blk_integrity_rq(req) == 0 && bio_integrity(bio) == NULL) + if (blk_integrity_rq(req) == 0 && bip_bio == NULL) return true; - if (blk_integrity_rq(req) == 0 || bio_integrity(bio) == NULL) + if (blk_integrity_rq(req) == 0 || bip_bio == NULL) + return false; + + bip = bio_integrity(req->bio); + if (bip->bip_flags != bip_bio->bip_flags) return false; - if (bio_integrity(req->bio)->bip_flags != bio_integrity(bio)->bip_flags) + if (bip->bip_flags & BIP_CHECK_APPTAG && + bip->app_tag != bip_bio->app_tag) return false; nr_integrity_segs = blk_rq_count_integrity_sg(q, bio); From 397691633112c6ea3b458211a58f79f678bbeaef Mon Sep 17 00:00:00 2001 From: Ben Dooks Date: Tue, 6 Jan 2026 23:10:54 +0000 Subject: [PATCH 2366/3902] trace: ftrace_dump_on_oops[] is not exported, make it static [ Upstream commit 1e2ed4bfd50ace3c4272cfab7e9aa90956fb7ae0 ] The ftrace_dump_on_oops string is not used outside of trace.c so make it static to avoid the export warning from sparse: kernel/trace/trace.c:141:6: warning: symbol 'ftrace_dump_on_oops' was not declared. Should it be static? Fixes: dd293df6395a2 ("tracing: Move trace sysctls into trace.c") Link: https://patch.msgid.link/20260106231054.84270-1-ben.dooks@codethink.co.uk Signed-off-by: Ben Dooks Signed-off-by: Steven Rostedt (Google) Signed-off-by: Sasha Levin --- kernel/trace/trace.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/kernel/trace/trace.c b/kernel/trace/trace.c index ddff2af3cd3f7d..142e3b737f0bcf 100644 --- a/kernel/trace/trace.c +++ b/kernel/trace/trace.c @@ -141,7 +141,7 @@ cpumask_var_t __read_mostly tracing_buffer_mask; * by commas. */ /* Set to string format zero to disable by default */ -char ftrace_dump_on_oops[MAX_TRACER_SIZE] = "0"; +static char ftrace_dump_on_oops[MAX_TRACER_SIZE] = "0"; /* When set, tracing will stop when a WARN*() is hit */ static int __disable_trace_on_warning; From fb08fec72131991bf55c9aee42df3c17669bc634 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ilpo=20J=C3=A4rvinen?= Date: Mon, 24 Nov 2025 19:04:11 +0200 Subject: [PATCH 2367/3902] sparc/PCI: Correct 64-bit non-pref -> pref BAR resources MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [ Upstream commit bdb32359eab94013e80cf7e3d40a3fd4972da93a ] SPARC T5-2 dts describes some PCI BARs as 64-bit resources without the pref(etchable) bit (0x83... vs 0xc3... in assigned-addresses) for address ranges above the 4G threshold. Such resources cannot be placed into a non-prefetchable PCI bridge window that is capable only of 32-bit addressing. As such, it looks like the platform is improperly described by the dts. The kernel detects this problem (see the IORESOURCE_PREFETCH check in pci_find_parent_resource()) and fails to assign these BAR resources to the resource tree due to lack of a compatible bridge window. Prior to 754babaaf333 ("sparc/PCI: Remove pcibios_enable_device() as they do nothing extra") SPARC arch code did not test whether device resources were successfully in the resource tree when enabling a device, effectively hiding the problem. After removing the arch-specific enable code, pci_enable_resources() refuses to enable the device when it finds not all mem resources are assigned, and therefore mpt3sas can't be enabled: pci 0001:04:00.0: reg 0x14: [mem 0x801110000000-0x80111000ffff 64bit] pci 0001:04:00.0: reg 0x1c: [mem 0x801110040000-0x80111007ffff 64bit] pci 0001:04:00.0: BAR 1 [mem 0x801110000000-0x80111000ffff 64bit]: can't claim; no compatible bridge window pci 0001:04:00.0: BAR 3 [mem 0x801110040000-0x80111007ffff 64bit]: can't claim; no compatible bridge window mpt3sas 0001:04:00.0: BAR 1 [mem size 0x00010000 64bit]: not assigned; can't enable device For clarity, this filtered log only shows failures for one mpt3sas device but other devices fail similarly. In the reported case, the end result with all the failures is an unbootable system. Things appeared to "work" before 754babaaf333 ("sparc/PCI: Remove pcibios_enable_device() as they do nothing extra") because the resource tree is agnostic to whether PCI BAR resources are properly in the tree or not. So as long as there was a parent resource (e.g. a root bus resource) that contains the address range, the resource tree code just places resource request underneath it without any consideration to the intermediate BAR resource. While it worked, it's incorrect setup still. Add an OF fixup to set the IORESOURCE_PREFETCH flag for a 64-bit PCI resource that has the end address above 4G requiring placement into the prefetchable window. Also log the issue. Fixes: 754babaaf333 ("sparc/PCI: Remove pcibios_enable_device() as they do nothing extra") Reported-by: Nathaniel Roach Closes: https://github.com/sparclinux/issues/issues/22 Signed-off-by: Ilpo Järvinen Signed-off-by: Bjorn Helgaas Tested-by: Nathaniel Roach Link: https://patch.msgid.link/20251124170411.3709-1-ilpo.jarvinen@linux.intel.com Signed-off-by: Sasha Levin --- arch/sparc/kernel/pci.c | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/arch/sparc/kernel/pci.c b/arch/sparc/kernel/pci.c index a9448088e762e0..b290107170e94a 100644 --- a/arch/sparc/kernel/pci.c +++ b/arch/sparc/kernel/pci.c @@ -181,6 +181,28 @@ static int __init ofpci_debug(char *str) __setup("ofpci_debug=", ofpci_debug); +static void of_fixup_pci_pref(struct pci_dev *dev, int index, + struct resource *res) +{ + struct pci_bus_region region; + + if (!(res->flags & IORESOURCE_MEM_64)) + return; + + if (!resource_size(res)) + return; + + pcibios_resource_to_bus(dev->bus, ®ion, res); + if (region.end <= ~((u32)0)) + return; + + if (!(res->flags & IORESOURCE_PREFETCH)) { + res->flags |= IORESOURCE_PREFETCH; + pci_info(dev, "reg 0x%x: fixup: pref added to 64-bit resource\n", + index); + } +} + static unsigned long pci_parse_of_flags(u32 addr0) { unsigned long flags = 0; @@ -244,6 +266,7 @@ static void pci_parse_of_addrs(struct platform_device *op, res->end = op_res->end; res->flags = flags; res->name = pci_name(dev); + of_fixup_pci_pref(dev, i, res); pci_info(dev, "reg 0x%x: %pR\n", i, res); } From 9e17d06663f3c316248435b9d9b972c09db35f2b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20Rebe?= Date: Fri, 28 Nov 2025 13:46:41 +0100 Subject: [PATCH 2368/3902] HID: quirks: work around VID/PID conflict for appledisplay MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [ Upstream commit c7fabe4ad9219866c203164a214c474c95b36bf2 ] For years I wondered why the Apple Cinema Display driver would not just work for me. Turns out the hidraw driver instantly takes it over. Fix by adding appledisplay VID/PIDs to hid_have_special_driver. Fixes: 069e8a65cd79 ("Driver for Apple Cinema Display") Signed-off-by: René Rebe Signed-off-by: Jiri Kosina Signed-off-by: Sasha Levin --- drivers/hid/hid-quirks.c | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/drivers/hid/hid-quirks.c b/drivers/hid/hid-quirks.c index c89a015686c07e..6a8a7ca3d80474 100644 --- a/drivers/hid/hid-quirks.c +++ b/drivers/hid/hid-quirks.c @@ -232,6 +232,15 @@ static const struct hid_device_id hid_quirks[] = { * used as a driver. See hid_scan_report(). */ static const struct hid_device_id hid_have_special_driver[] = { +#if IS_ENABLED(CONFIG_APPLEDISPLAY) + { HID_USB_DEVICE(USB_VENDOR_ID_APPLE, 0x9218) }, + { HID_USB_DEVICE(USB_VENDOR_ID_APPLE, 0x9219) }, + { HID_USB_DEVICE(USB_VENDOR_ID_APPLE, 0x921c) }, + { HID_USB_DEVICE(USB_VENDOR_ID_APPLE, 0x921d) }, + { HID_USB_DEVICE(USB_VENDOR_ID_APPLE, 0x9222) }, + { HID_USB_DEVICE(USB_VENDOR_ID_APPLE, 0x9226) }, + { HID_USB_DEVICE(USB_VENDOR_ID_APPLE, 0x9236) }, +#endif #if IS_ENABLED(CONFIG_HID_A4TECH) { HID_USB_DEVICE(USB_VENDOR_ID_A4TECH, USB_DEVICE_ID_A4TECH_WCP32PU) }, { HID_USB_DEVICE(USB_VENDOR_ID_A4TECH, USB_DEVICE_ID_A4TECH_X5_005D) }, From 72f93dc6d1a6b157e0c3d247085c74935893cca0 Mon Sep 17 00:00:00 2001 From: Lorenzo Bianconi Date: Mon, 5 Jan 2026 09:43:31 +0100 Subject: [PATCH 2369/3902] net: airoha: Fix schedule while atomic in airoha_ppe_deinit() [ Upstream commit 6abcf751bc084804a9e5b3051442e8a2ce67f48a ] airoha_ppe_deinit() runs airoha_npu_ppe_deinit() in atomic context. airoha_npu_ppe_deinit routine allocates ppe_data buffer with GFP_KERNEL flag. Rely on rcu_replace_pointer in airoha_ppe_deinit routine in order to fix schedule while atomic issue in airoha_npu_ppe_deinit() since we do not need atomic context there. Fixes: 00a7678310fe3 ("net: airoha: Introduce flowtable offload support") Signed-off-by: Lorenzo Bianconi Link: https://patch.msgid.link/20260105-airoha-fw-ethtool-v2-1-3b32b158cc31@kernel.org Signed-off-by: Paolo Abeni Signed-off-by: Sasha Levin --- drivers/net/ethernet/airoha/airoha_ppe.c | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/drivers/net/ethernet/airoha/airoha_ppe.c b/drivers/net/ethernet/airoha/airoha_ppe.c index c0e17035db18ed..190d98970014fa 100644 --- a/drivers/net/ethernet/airoha/airoha_ppe.c +++ b/drivers/net/ethernet/airoha/airoha_ppe.c @@ -1466,13 +1466,16 @@ void airoha_ppe_deinit(struct airoha_eth *eth) { struct airoha_npu *npu; - rcu_read_lock(); - npu = rcu_dereference(eth->npu); + mutex_lock(&flow_offload_mutex); + + npu = rcu_replace_pointer(eth->npu, NULL, + lockdep_is_held(&flow_offload_mutex)); if (npu) { npu->ops.ppe_deinit(npu); airoha_npu_put(npu); } - rcu_read_unlock(); + + mutex_unlock(&flow_offload_mutex); rhashtable_destroy(ð->ppe->l2_flows); rhashtable_destroy(ð->flow_table); From 1251bbdb8f5b2ea86ca9b4268a2e6aa34372ab33 Mon Sep 17 00:00:00 2001 From: Benjamin Berg Date: Wed, 7 Jan 2026 14:36:51 +0100 Subject: [PATCH 2370/3902] wifi: mac80211_hwsim: fix typo in frequency notification [ Upstream commit 333418872bfecf4843f1ded7a4151685dfcf07d5 ] The NAN notification is for 5745 MHz which corresponds to channel 149 and not 5475 which is not actually a valid channel. This could result in a NULL pointer dereference in cfg80211_next_nan_dw_notif. Fixes: a37a6f54439b ("wifi: mac80211_hwsim: Add simulation support for NAN device") Signed-off-by: Benjamin Berg Reviewed-by: Ilan Peer Reviewed-by: Miriam Rachel Korenblit Link: https://patch.msgid.link/20260107143652.7dab2035836f.Iacbaf7bb94ed5c14a0928a625827e4137d8bfede@changeid Signed-off-by: Johannes Berg Signed-off-by: Sasha Levin --- drivers/net/wireless/virtual/mac80211_hwsim.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/net/wireless/virtual/mac80211_hwsim.c b/drivers/net/wireless/virtual/mac80211_hwsim.c index 5903d82e1ab1e8..2f263d89d2d692 100644 --- a/drivers/net/wireless/virtual/mac80211_hwsim.c +++ b/drivers/net/wireless/virtual/mac80211_hwsim.c @@ -4040,7 +4040,7 @@ mac80211_hwsim_nan_dw_start(struct hrtimer *timer) ieee80211_vif_to_wdev(data->nan_device_vif); if (data->nan_curr_dw_band == NL80211_BAND_5GHZ) - ch = ieee80211_get_channel(hw->wiphy, 5475); + ch = ieee80211_get_channel(hw->wiphy, 5745); else ch = ieee80211_get_channel(hw->wiphy, 2437); From 51ffd447bc37bf1a5776b85523f51d2bc69977f6 Mon Sep 17 00:00:00 2001 From: Xiang Mei Date: Mon, 5 Jan 2026 20:41:00 -0700 Subject: [PATCH 2371/3902] net/sched: sch_qfq: Fix NULL deref when deactivating inactive aggregate in qfq_reset [ Upstream commit c1d73b1480235731e35c81df70b08f4714a7d095 ] `qfq_class->leaf_qdisc->q.qlen > 0` does not imply that the class itself is active. Two qfq_class objects may point to the same leaf_qdisc. This happens when: 1. one QFQ qdisc is attached to the dev as the root qdisc, and 2. another QFQ qdisc is temporarily referenced (e.g., via qdisc_get() / qdisc_put()) and is pending to be destroyed, as in function tc_new_tfilter. When packets are enqueued through the root QFQ qdisc, the shared leaf_qdisc->q.qlen increases. At the same time, the second QFQ qdisc triggers qdisc_put and qdisc_destroy: the qdisc enters qfq_reset() with its own q->q.qlen == 0, but its class's leaf qdisc->q.qlen > 0. Therefore, the qfq_reset would wrongly deactivate an inactive aggregate and trigger a null-deref in qfq_deactivate_agg: [ 0.903172] BUG: kernel NULL pointer dereference, address: 0000000000000000 [ 0.903571] #PF: supervisor write access in kernel mode [ 0.903860] #PF: error_code(0x0002) - not-present page [ 0.904177] PGD 10299b067 P4D 10299b067 PUD 10299c067 PMD 0 [ 0.904502] Oops: Oops: 0002 [#1] SMP NOPTI [ 0.904737] CPU: 0 UID: 0 PID: 135 Comm: exploit Not tainted 6.19.0-rc3+ #2 NONE [ 0.905157] Hardware name: QEMU Standard PC (i440FX + PIIX, 1996), BIOS rel-1.17.0-0-gb52ca86e094d-prebuilt.qemu.org 04/01/2014 [ 0.905754] RIP: 0010:qfq_deactivate_agg (include/linux/list.h:992 (discriminator 2) include/linux/list.h:1006 (discriminator 2) net/sched/sch_qfq.c:1367 (discriminator 2) net/sched/sch_qfq.c:1393 (discriminator 2)) [ 0.906046] Code: 0f 84 4d 01 00 00 48 89 70 18 8b 4b 10 48 c7 c2 ff ff ff ff 48 8b 78 08 48 d3 e2 48 21 f2 48 2b 13 48 8b 30 48 d3 ea 8b 4b 18 0 Code starting with the faulting instruction =========================================== 0: 0f 84 4d 01 00 00 je 0x153 6: 48 89 70 18 mov %rsi,0x18(%rax) a: 8b 4b 10 mov 0x10(%rbx),%ecx d: 48 c7 c2 ff ff ff ff mov $0xffffffffffffffff,%rdx 14: 48 8b 78 08 mov 0x8(%rax),%rdi 18: 48 d3 e2 shl %cl,%rdx 1b: 48 21 f2 and %rsi,%rdx 1e: 48 2b 13 sub (%rbx),%rdx 21: 48 8b 30 mov (%rax),%rsi 24: 48 d3 ea shr %cl,%rdx 27: 8b 4b 18 mov 0x18(%rbx),%ecx ... [ 0.907095] RSP: 0018:ffffc900004a39a0 EFLAGS: 00010246 [ 0.907368] RAX: ffff8881043a0880 RBX: ffff888102953340 RCX: 0000000000000000 [ 0.907723] RDX: 0000000000000000 RSI: 0000000000000000 RDI: 0000000000000000 [ 0.908100] RBP: ffff888102952180 R08: 0000000000000000 R09: 0000000000000000 [ 0.908451] R10: ffff8881043a0000 R11: 0000000000000000 R12: ffff888102952000 [ 0.908804] R13: ffff888102952180 R14: ffff8881043a0ad8 R15: ffff8881043a0880 [ 0.909179] FS: 000000002a1a0380(0000) GS:ffff888196d8d000(0000) knlGS:0000000000000000 [ 0.909572] CS: 0010 DS: 0000 ES: 0000 CR0: 0000000080050033 [ 0.909857] CR2: 0000000000000000 CR3: 0000000102993002 CR4: 0000000000772ef0 [ 0.910247] PKRU: 55555554 [ 0.910391] Call Trace: [ 0.910527] [ 0.910638] qfq_reset_qdisc (net/sched/sch_qfq.c:357 net/sched/sch_qfq.c:1485) [ 0.910826] qdisc_reset (include/linux/skbuff.h:2195 include/linux/skbuff.h:2501 include/linux/skbuff.h:3424 include/linux/skbuff.h:3430 net/sched/sch_generic.c:1036) [ 0.911040] __qdisc_destroy (net/sched/sch_generic.c:1076) [ 0.911236] tc_new_tfilter (net/sched/cls_api.c:2447) [ 0.911447] rtnetlink_rcv_msg (net/core/rtnetlink.c:6958) [ 0.911663] ? __pfx_rtnetlink_rcv_msg (net/core/rtnetlink.c:6861) [ 0.911894] netlink_rcv_skb (net/netlink/af_netlink.c:2550) [ 0.912100] netlink_unicast (net/netlink/af_netlink.c:1319 net/netlink/af_netlink.c:1344) [ 0.912296] ? __alloc_skb (net/core/skbuff.c:706) [ 0.912484] netlink_sendmsg (net/netlink/af_netlink.c:1894) [ 0.912682] sock_write_iter (net/socket.c:727 (discriminator 1) net/socket.c:742 (discriminator 1) net/socket.c:1195 (discriminator 1)) [ 0.912880] vfs_write (fs/read_write.c:593 fs/read_write.c:686) [ 0.913077] ksys_write (fs/read_write.c:738) [ 0.913252] do_syscall_64 (arch/x86/entry/syscall_64.c:63 (discriminator 1) arch/x86/entry/syscall_64.c:94 (discriminator 1)) [ 0.913438] entry_SYSCALL_64_after_hwframe (arch/x86/entry/entry_64.S:131) [ 0.913687] RIP: 0033:0x424c34 [ 0.913844] Code: 89 02 48 c7 c0 ff ff ff ff eb bd 66 2e 0f 1f 84 00 00 00 00 00 90 f3 0f 1e fa 80 3d 2d 44 09 00 00 74 13 b8 01 00 00 00 0f 05 9 Code starting with the faulting instruction =========================================== 0: 89 02 mov %eax,(%rdx) 2: 48 c7 c0 ff ff ff ff mov $0xffffffffffffffff,%rax 9: eb bd jmp 0xffffffffffffffc8 b: 66 2e 0f 1f 84 00 00 cs nopw 0x0(%rax,%rax,1) 12: 00 00 00 15: 90 nop 16: f3 0f 1e fa endbr64 1a: 80 3d 2d 44 09 00 00 cmpb $0x0,0x9442d(%rip) # 0x9444e 21: 74 13 je 0x36 23: b8 01 00 00 00 mov $0x1,%eax 28: 0f 05 syscall 2a: 09 .byte 0x9 [ 0.914807] RSP: 002b:00007ffea1938b78 EFLAGS: 00000202 ORIG_RAX: 0000000000000001 [ 0.915197] RAX: ffffffffffffffda RBX: 0000000000000001 RCX: 0000000000424c34 [ 0.915556] RDX: 000000000000003c RSI: 000000002af378c0 RDI: 0000000000000003 [ 0.915912] RBP: 00007ffea1938bc0 R08: 00000000004b8820 R09: 0000000000000000 [ 0.916297] R10: 0000000000000001 R11: 0000000000000202 R12: 00007ffea1938d28 [ 0.916652] R13: 00007ffea1938d38 R14: 00000000004b3828 R15: 0000000000000001 [ 0.917039] [ 0.917158] Modules linked in: [ 0.917316] CR2: 0000000000000000 [ 0.917484] ---[ end trace 0000000000000000 ]--- [ 0.917717] RIP: 0010:qfq_deactivate_agg (include/linux/list.h:992 (discriminator 2) include/linux/list.h:1006 (discriminator 2) net/sched/sch_qfq.c:1367 (discriminator 2) net/sched/sch_qfq.c:1393 (discriminator 2)) [ 0.917978] Code: 0f 84 4d 01 00 00 48 89 70 18 8b 4b 10 48 c7 c2 ff ff ff ff 48 8b 78 08 48 d3 e2 48 21 f2 48 2b 13 48 8b 30 48 d3 ea 8b 4b 18 0 Code starting with the faulting instruction =========================================== 0: 0f 84 4d 01 00 00 je 0x153 6: 48 89 70 18 mov %rsi,0x18(%rax) a: 8b 4b 10 mov 0x10(%rbx),%ecx d: 48 c7 c2 ff ff ff ff mov $0xffffffffffffffff,%rdx 14: 48 8b 78 08 mov 0x8(%rax),%rdi 18: 48 d3 e2 shl %cl,%rdx 1b: 48 21 f2 and %rsi,%rdx 1e: 48 2b 13 sub (%rbx),%rdx 21: 48 8b 30 mov (%rax),%rsi 24: 48 d3 ea shr %cl,%rdx 27: 8b 4b 18 mov 0x18(%rbx),%ecx ... [ 0.918902] RSP: 0018:ffffc900004a39a0 EFLAGS: 00010246 [ 0.919198] RAX: ffff8881043a0880 RBX: ffff888102953340 RCX: 0000000000000000 [ 0.919559] RDX: 0000000000000000 RSI: 0000000000000000 RDI: 0000000000000000 [ 0.919908] RBP: ffff888102952180 R08: 0000000000000000 R09: 0000000000000000 [ 0.920289] R10: ffff8881043a0000 R11: 0000000000000000 R12: ffff888102952000 [ 0.920648] R13: ffff888102952180 R14: ffff8881043a0ad8 R15: ffff8881043a0880 [ 0.921014] FS: 000000002a1a0380(0000) GS:ffff888196d8d000(0000) knlGS:0000000000000000 [ 0.921424] CS: 0010 DS: 0000 ES: 0000 CR0: 0000000080050033 [ 0.921710] CR2: 0000000000000000 CR3: 0000000102993002 CR4: 0000000000772ef0 [ 0.922097] PKRU: 55555554 [ 0.922240] Kernel panic - not syncing: Fatal exception [ 0.922590] Kernel Offset: disabled Fixes: 0545a3037773 ("pkt_sched: QFQ - quick fair queue scheduler") Signed-off-by: Xiang Mei Link: https://patch.msgid.link/20260106034100.1780779-1-xmei5@asu.edu Signed-off-by: Jakub Kicinski Signed-off-by: Sasha Levin --- net/sched/sch_qfq.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/net/sched/sch_qfq.c b/net/sched/sch_qfq.c index 2255355e51d350..a91a5bac8f7374 100644 --- a/net/sched/sch_qfq.c +++ b/net/sched/sch_qfq.c @@ -1481,7 +1481,7 @@ static void qfq_reset_qdisc(struct Qdisc *sch) for (i = 0; i < q->clhash.hashsize; i++) { hlist_for_each_entry(cl, &q->clhash.hash[i], common.hnode) { - if (cl->qdisc->q.qlen > 0) + if (cl_is_active(cl)) qfq_deactivate_class(q, cl); qdisc_reset(cl->qdisc); From ce6eef731aba23a988decea1df3b08cf978f7b01 Mon Sep 17 00:00:00 2001 From: Petko Manolov Date: Tue, 6 Jan 2026 10:48:21 +0200 Subject: [PATCH 2372/3902] net: usb: pegasus: fix memory leak in update_eth_regs_async() [ Upstream commit afa27621a28af317523e0836dad430bec551eb54 ] When asynchronously writing to the device registers and if usb_submit_urb() fail, the code fail to release allocated to this point resources. Fixes: 323b34963d11 ("drivers: net: usb: pegasus: fix control urb submission") Signed-off-by: Petko Manolov Link: https://patch.msgid.link/20260106084821.3746677-1-petko.manolov@konsulko.com Signed-off-by: Jakub Kicinski Signed-off-by: Sasha Levin --- drivers/net/usb/pegasus.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/drivers/net/usb/pegasus.c b/drivers/net/usb/pegasus.c index 81ca64debc5b94..c514483134f05f 100644 --- a/drivers/net/usb/pegasus.c +++ b/drivers/net/usb/pegasus.c @@ -168,6 +168,8 @@ static int update_eth_regs_async(pegasus_t *pegasus) netif_device_detach(pegasus->net); netif_err(pegasus, drv, pegasus->net, "%s returned %d\n", __func__, ret); + usb_free_urb(async_urb); + kfree(req); } return ret; } From d93ba83fc3f5b9a8314ba7b8f633aac7ffb92107 Mon Sep 17 00:00:00 2001 From: Wei Fang Date: Wed, 7 Jan 2026 17:12:04 +0800 Subject: [PATCH 2373/3902] net: enetc: fix build warning when PAGE_SIZE is greater than 128K [ Upstream commit 4b5bdabb5449b652122e43f507f73789041d4abe ] The max buffer size of ENETC RX BD is 0xFFFF bytes, so if the PAGE_SIZE is greater than 128K, ENETC_RXB_DMA_SIZE and ENETC_RXB_DMA_SIZE_XDP will be greater than 0xFFFF, thus causing a build warning. This will not cause any practical issues because ENETC is currently only used on the ARM64 platform, and the max PAGE_SIZE is 64K. So this patch is only for fixing the build warning that occurs when compiling ENETC drivers for other platforms. Reported-by: kernel test robot Closes: https://lore.kernel.org/oe-kbuild-all/202601050637.kHEKKOG7-lkp@intel.com/ Fixes: e59bc32df2e9 ("net: enetc: correct the value of ENETC_RXB_TRUESIZE") Signed-off-by: Wei Fang Reviewed-by: Frank Li Link: https://patch.msgid.link/20260107091204.1980222-1-wei.fang@nxp.com Signed-off-by: Jakub Kicinski Signed-off-by: Sasha Levin --- drivers/net/ethernet/freescale/enetc/enetc.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/net/ethernet/freescale/enetc/enetc.h b/drivers/net/ethernet/freescale/enetc/enetc.h index f279fa597991ec..60c7205ea9ff51 100644 --- a/drivers/net/ethernet/freescale/enetc/enetc.h +++ b/drivers/net/ethernet/freescale/enetc/enetc.h @@ -79,9 +79,9 @@ struct enetc_lso_t { #define ENETC_RXB_TRUESIZE (PAGE_SIZE >> 1) #define ENETC_RXB_PAD NET_SKB_PAD /* add extra space if needed */ #define ENETC_RXB_DMA_SIZE \ - (SKB_WITH_OVERHEAD(ENETC_RXB_TRUESIZE) - ENETC_RXB_PAD) + min(SKB_WITH_OVERHEAD(ENETC_RXB_TRUESIZE) - ENETC_RXB_PAD, 0xffff) #define ENETC_RXB_DMA_SIZE_XDP \ - (SKB_WITH_OVERHEAD(ENETC_RXB_TRUESIZE) - XDP_PACKET_HEADROOM) + min(SKB_WITH_OVERHEAD(ENETC_RXB_TRUESIZE) - XDP_PACKET_HEADROOM, 0xffff) struct enetc_rx_swbd { dma_addr_t dma; From 949647e7771a4a01963fe953a96d81fba7acecf3 Mon Sep 17 00:00:00 2001 From: Eric Dumazet Date: Wed, 7 Jan 2026 21:22:50 +0000 Subject: [PATCH 2374/3902] arp: do not assume dev_hard_header() does not change skb->head [ Upstream commit c92510f5e3f82ba11c95991824a41e59a9c5ed81 ] arp_create() is the only dev_hard_header() caller making assumption about skb->head being unchanged. A recent commit broke this assumption. Initialize @arp pointer after dev_hard_header() call. Fixes: db5b4e39c4e6 ("ip6_gre: make ip6gre_header() robust") Reported-by: syzbot+58b44a770a1585795351@syzkaller.appspotmail.com Signed-off-by: Eric Dumazet Link: https://patch.msgid.link/20260107212250.384552-1-edumazet@google.com Signed-off-by: Jakub Kicinski Signed-off-by: Sasha Levin --- net/ipv4/arp.c | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/net/ipv4/arp.c b/net/ipv4/arp.c index 833f2cf97178ee..3ce1664e8632a0 100644 --- a/net/ipv4/arp.c +++ b/net/ipv4/arp.c @@ -564,7 +564,7 @@ struct sk_buff *arp_create(int type, int ptype, __be32 dest_ip, skb_reserve(skb, hlen); skb_reset_network_header(skb); - arp = skb_put(skb, arp_hdr_len(dev)); + skb_put(skb, arp_hdr_len(dev)); skb->dev = dev; skb->protocol = htons(ETH_P_ARP); if (!src_hw) @@ -572,12 +572,13 @@ struct sk_buff *arp_create(int type, int ptype, __be32 dest_ip, if (!dest_hw) dest_hw = dev->broadcast; - /* - * Fill the device header for the ARP frame + /* Fill the device header for the ARP frame. + * Note: skb->head can be changed. */ if (dev_hard_header(skb, dev, ptype, dest_hw, src_hw, skb->len) < 0) goto out; + arp = arp_hdr(skb); /* * Fill out the arp protocol part. * From 72e28774e9644c2bdbb4920842fbf77103a15a85 Mon Sep 17 00:00:00 2001 From: Ming Lei Date: Fri, 9 Jan 2026 20:14:54 +0800 Subject: [PATCH 2375/3902] ublk: fix use-after-free in ublk_partition_scan_work [ Upstream commit f0d385f6689f37a2828c686fb279121df006b4cb ] A race condition exists between the async partition scan work and device teardown that can lead to a use-after-free of ub->ub_disk: 1. ublk_ctrl_start_dev() schedules partition_scan_work after add_disk() 2. ublk_stop_dev() calls ublk_stop_dev_unlocked() which does: - del_gendisk(ub->ub_disk) - ublk_detach_disk() sets ub->ub_disk = NULL - put_disk() which may free the disk 3. The worker ublk_partition_scan_work() then dereferences ub->ub_disk leading to UAF Fix this by using ublk_get_disk()/ublk_put_disk() in the worker to hold a reference to the disk during the partition scan. The spinlock in ublk_get_disk() synchronizes with ublk_detach_disk() ensuring the worker either gets a valid reference or sees NULL and exits early. Also change flush_work() to cancel_work_sync() to avoid running the partition scan work unnecessarily when the disk is already detached. Fixes: 7fc4da6a304b ("ublk: scan partition in async way") Reported-by: Ruikai Peng Signed-off-by: Ming Lei Signed-off-by: Jens Axboe Signed-off-by: Sasha Levin --- drivers/block/ublk_drv.c | 37 ++++++++++++++++++++++--------------- 1 file changed, 22 insertions(+), 15 deletions(-) diff --git a/drivers/block/ublk_drv.c b/drivers/block/ublk_drv.c index babb58d2dcaf7d..e09c1b5999b75c 100644 --- a/drivers/block/ublk_drv.c +++ b/drivers/block/ublk_drv.c @@ -255,20 +255,6 @@ static inline struct request *__ublk_check_and_get_req(struct ublk_device *ub, u16 q_id, u16 tag, struct ublk_io *io, size_t offset); static inline unsigned int ublk_req_build_flags(struct request *req); -static void ublk_partition_scan_work(struct work_struct *work) -{ - struct ublk_device *ub = - container_of(work, struct ublk_device, partition_scan_work); - - if (WARN_ON_ONCE(!test_and_clear_bit(GD_SUPPRESS_PART_SCAN, - &ub->ub_disk->state))) - return; - - mutex_lock(&ub->ub_disk->open_mutex); - bdev_disk_changed(ub->ub_disk, false); - mutex_unlock(&ub->ub_disk->open_mutex); -} - static inline struct ublksrv_io_desc * ublk_get_iod(const struct ublk_queue *ubq, unsigned tag) { @@ -1663,6 +1649,27 @@ static void ublk_put_disk(struct gendisk *disk) put_device(disk_to_dev(disk)); } +static void ublk_partition_scan_work(struct work_struct *work) +{ + struct ublk_device *ub = + container_of(work, struct ublk_device, partition_scan_work); + /* Hold disk reference to prevent UAF during concurrent teardown */ + struct gendisk *disk = ublk_get_disk(ub); + + if (!disk) + return; + + if (WARN_ON_ONCE(!test_and_clear_bit(GD_SUPPRESS_PART_SCAN, + &disk->state))) + goto out; + + mutex_lock(&disk->open_mutex); + bdev_disk_changed(disk, false); + mutex_unlock(&disk->open_mutex); +out: + ublk_put_disk(disk); +} + /* * Use this function to ensure that ->canceling is consistently set for * the device and all queues. Do not set these flags directly. @@ -2107,7 +2114,7 @@ static void ublk_stop_dev(struct ublk_device *ub) mutex_lock(&ub->mutex); ublk_stop_dev_unlocked(ub); mutex_unlock(&ub->mutex); - flush_work(&ub->partition_scan_work); + cancel_work_sync(&ub->partition_scan_work); ublk_cancel_dev(ub); } From 234409e0db771c77c861134ea7c22c90a411c6eb Mon Sep 17 00:00:00 2001 From: Lorenzo Pieralisi Date: Mon, 22 Dec 2025 11:22:50 +0100 Subject: [PATCH 2376/3902] irqchip/gic-v5: Fix gicv5_its_map_event() ITTE read endianness [ Upstream commit 1690eeb0cb2bb77096cb6c826b6849ef05013e34 ] Kbuild bot (through sparse) reported that the ITTE read to carry out a valid check in gicv5_its_map_event() lacks proper endianness handling. Add the missing endianess conversion. Fixes: 57d72196dfc8 ("irqchip/gic-v5: Add GICv5 ITS support") Reported-by: kernel test robot Signed-off-by: Lorenzo Pieralisi Signed-off-by: Thomas Gleixner Acked-by: Marc Zyngier Link: https://patch.msgid.link/20251222102250.435460-1-lpieralisi@kernel.org Closes: https://lore.kernel.org/oe-kbuild-all/202512131849.30ZRTBeR-lkp@intel.com/ Signed-off-by: Sasha Levin --- drivers/irqchip/irq-gic-v5-its.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/irqchip/irq-gic-v5-its.c b/drivers/irqchip/irq-gic-v5-its.c index 554485f0be1fbf..8e22134b9f4867 100644 --- a/drivers/irqchip/irq-gic-v5-its.c +++ b/drivers/irqchip/irq-gic-v5-its.c @@ -849,7 +849,7 @@ static int gicv5_its_map_event(struct gicv5_its_dev *its_dev, u16 event_id, u32 itte = gicv5_its_device_get_itte_ref(its_dev, event_id); - if (FIELD_GET(GICV5_ITTL2E_VALID, *itte)) + if (FIELD_GET(GICV5_ITTL2E_VALID, le64_to_cpu(*itte))) return -EEXIST; itt_entry = FIELD_PREP(GICV5_ITTL2E_LPI_ID, lpi) | From c65f0bafc98f1dab9d03d5a013bc06f168908c05 Mon Sep 17 00:00:00 2001 From: Gao Xiang Date: Thu, 8 Jan 2026 10:38:31 +0800 Subject: [PATCH 2377/3902] erofs: don't bother with s_stack_depth increasing for now MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [ Upstream commit 072a7c7cdbea4f91df854ee2bb216256cd619f2a ] Previously, commit d53cd891f0e4 ("erofs: limit the level of fs stacking for file-backed mounts") bumped `s_stack_depth` by one to avoid kernel stack overflow when stacking an unlimited number of EROFS on top of each other. This fix breaks composefs mounts, which need EROFS+ovl^2 sometimes (and such setups are already used in production for quite a long time). One way to fix this regression is to bump FILESYSTEM_MAX_STACK_DEPTH from 2 to 3, but proving that this is safe in general is a high bar. After a long discussion on GitHub issues [1] about possible solutions, one conclusion is that there is no need to support nesting file-backed EROFS mounts on stacked filesystems, because there is always the option to use loopback devices as a fallback. As a quick fix for the composefs regression for this cycle, instead of bumping `s_stack_depth` for file backed EROFS mounts, we disallow nesting file-backed EROFS over EROFS and over filesystems with `s_stack_depth` > 0. This works for all known file-backed mount use cases (composefs, containerd, and Android APEX for some Android vendors), and the fix is self-contained. Essentially, we are allowing one extra unaccounted fs stacking level of EROFS below stacking filesystems, but EROFS can only be used in the read path (i.e. overlayfs lower layers), which typically has much lower stack usage than the write path. We can consider increasing FILESYSTEM_MAX_STACK_DEPTH later, after more stack usage analysis or using alternative approaches, such as splitting the `s_stack_depth` limitation according to different combinations of stacking. Fixes: d53cd891f0e4 ("erofs: limit the level of fs stacking for file-backed mounts") Reported-and-tested-by: Dusty Mabe Reported-by: Timothée Ravier Closes: https://github.com/coreos/fedora-coreos-tracker/issues/2087 [1] Reported-by: "Alekséi Naidénov" Closes: https://lore.kernel.org/r/CAFHtUiYv4+=+JP_-JjARWjo6OwcvBj1wtYN=z0QXwCpec9sXtg@mail.gmail.com Acked-by: Amir Goldstein Acked-by: Alexander Larsson Reviewed-and-tested-by: Sheng Yong Reviewed-by: Zhiguo Niu Reviewed-by: Chao Yu Cc: Christian Brauner Cc: Miklos Szeredi Signed-off-by: Gao Xiang Signed-off-by: Sasha Levin --- fs/erofs/super.c | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/fs/erofs/super.c b/fs/erofs/super.c index 937a215f626c1c..e93264034b5db6 100644 --- a/fs/erofs/super.c +++ b/fs/erofs/super.c @@ -644,14 +644,20 @@ static int erofs_fc_fill_super(struct super_block *sb, struct fs_context *fc) * fs contexts (including its own) due to self-controlled RO * accesses/contexts and no side-effect changes that need to * context save & restore so it can reuse the current thread - * context. However, it still needs to bump `s_stack_depth` to - * avoid kernel stack overflow from nested filesystems. + * context. + * However, we still need to prevent kernel stack overflow due + * to filesystem nesting: just ensure that s_stack_depth is 0 + * to disallow mounting EROFS on stacked filesystems. + * Note: s_stack_depth is not incremented here for now, since + * EROFS is the only fs supporting file-backed mounts for now. + * It MUST change if another fs plans to support them, which + * may also require adjusting FILESYSTEM_MAX_STACK_DEPTH. */ if (erofs_is_fileio_mode(sbi)) { - sb->s_stack_depth = - file_inode(sbi->dif0.file)->i_sb->s_stack_depth + 1; - if (sb->s_stack_depth > FILESYSTEM_MAX_STACK_DEPTH) { - erofs_err(sb, "maximum fs stacking depth exceeded"); + inode = file_inode(sbi->dif0.file); + if ((inode->i_sb->s_op == &erofs_sops && !sb->s_bdev) || + inode->i_sb->s_stack_depth) { + erofs_err(sb, "file-backed mounts cannot be applied to stacked fses"); return -ENOTBLK; } } From ebdc9934539969dfbb2e35ab17799dddcfd50efe Mon Sep 17 00:00:00 2001 From: Gao Xiang Date: Sat, 10 Jan 2026 19:47:03 +0800 Subject: [PATCH 2378/3902] erofs: fix file-backed mounts no longer working on EROFS partitions [ Upstream commit 7893cc12251f6f19e7689a4cf3ba803bddbd8437 ] Sheng Yong reported [1] that Android APEX images didn't work with commit 072a7c7cdbea ("erofs: don't bother with s_stack_depth increasing for now") because "EROFS-formatted APEX file images can be stored within an EROFS-formatted Android system partition." In response, I sent a quick fat-fingered [PATCH v3] to address the report. Unfortunately, the updated condition was incorrect: if (erofs_is_fileio_mode(sbi)) { - sb->s_stack_depth = - file_inode(sbi->dif0.file)->i_sb->s_stack_depth + 1; - if (sb->s_stack_depth > FILESYSTEM_MAX_STACK_DEPTH) { - erofs_err(sb, "maximum fs stacking depth exceeded"); + inode = file_inode(sbi->dif0.file); + if ((inode->i_sb->s_op == &erofs_sops && !sb->s_bdev) || + inode->i_sb->s_stack_depth) { The condition `!sb->s_bdev` is always true for all file-backed EROFS mounts, making the check effectively a no-op. The real fix tested and confirmed by Sheng Yong [2] at that time was [PATCH v3 RESEND], which correctly ensures the following EROFS^2 setup works: EROFS (on a block device) + EROFS (file-backed mount) But sadly I screwed it up again by upstreaming the outdated [PATCH v3]. This patch applies the same logic as the delta between the upstream [PATCH v3] and the real fix [PATCH v3 RESEND]. Reported-by: Sheng Yong Closes: https://lore.kernel.org/r/3acec686-4020-4609-aee4-5dae7b9b0093@gmail.com [1] Fixes: 072a7c7cdbea ("erofs: don't bother with s_stack_depth increasing for now") Link: https://lore.kernel.org/r/243f57b8-246f-47e7-9fb1-27a771e8e9e8@gmail.com [2] Signed-off-by: Gao Xiang Signed-off-by: Linus Torvalds Signed-off-by: Sasha Levin --- fs/erofs/super.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/fs/erofs/super.c b/fs/erofs/super.c index e93264034b5db6..5136cda5972a98 100644 --- a/fs/erofs/super.c +++ b/fs/erofs/super.c @@ -655,7 +655,8 @@ static int erofs_fc_fill_super(struct super_block *sb, struct fs_context *fc) */ if (erofs_is_fileio_mode(sbi)) { inode = file_inode(sbi->dif0.file); - if ((inode->i_sb->s_op == &erofs_sops && !sb->s_bdev) || + if ((inode->i_sb->s_op == &erofs_sops && + !inode->i_sb->s_bdev) || inode->i_sb->s_stack_depth) { erofs_err(sb, "file-backed mounts cannot be applied to stacked fses"); return -ENOTBLK; From 3b7ca18f350970132cd31a634b811ea30c8735b0 Mon Sep 17 00:00:00 2001 From: Filipe Manana Date: Mon, 12 Jan 2026 08:54:49 -0500 Subject: [PATCH 2379/3902] btrfs: truncate ordered extent when skipping writeback past i_size [ Upstream commit 18de34daa7c62c830be533aace6b7c271e8e95cf ] While running test case btrfs/192 from fstests with support for large folios (needs CONFIG_BTRFS_EXPERIMENTAL=y) I ended up getting very sporadic btrfs check failures reporting that csum items were missing. Looking into the issue it turned out that btrfs check searches for csum items of a file extent item with a range that spans beyond the i_size of a file and we don't have any, because the kernel's writeback code skips submitting bios for ranges beyond eof. It's not expected however to find a file extent item that crosses the rounded up (by the sector size) i_size value, but there is a short time window where we can end up with a transaction commit leaving this small inconsistency between the i_size and the last file extent item. Example btrfs check output when this happens: $ btrfs check /dev/sdc Opening filesystem to check... Checking filesystem on /dev/sdc UUID: 69642c61-5efb-4367-aa31-cdfd4067f713 [1/8] checking log skipped (none written) [2/8] checking root items [3/8] checking extents [4/8] checking free space tree [5/8] checking fs roots root 5 inode 332 errors 1000, some csum missing ERROR: errors found in fs roots (...) Looking at a tree dump of the fs tree (root 5) for inode 332 we have: $ btrfs inspect-internal dump-tree -t 5 /dev/sdc (...) item 28 key (332 INODE_ITEM 0) itemoff 2006 itemsize 160 generation 17 transid 19 size 610969 nbytes 86016 block group 0 mode 100666 links 1 uid 0 gid 0 rdev 0 sequence 11 flags 0x0(none) atime 1759851068.391327881 (2025-10-07 16:31:08) ctime 1759851068.410098267 (2025-10-07 16:31:08) mtime 1759851068.410098267 (2025-10-07 16:31:08) otime 1759851068.391327881 (2025-10-07 16:31:08) item 29 key (332 INODE_REF 340) itemoff 1993 itemsize 13 index 2 namelen 3 name: f1f item 30 key (332 EXTENT_DATA 589824) itemoff 1940 itemsize 53 generation 19 type 1 (regular) extent data disk byte 21745664 nr 65536 extent data offset 0 nr 65536 ram 65536 extent compression 0 (none) (...) We can see that the file extent item for file offset 589824 has a length of 64K and its number of bytes is 64K. Looking at the inode item we see that its i_size is 610969 bytes which falls within the range of that file extent item [589824, 655360[. Looking into the csum tree: $ btrfs inspect-internal dump-tree /dev/sdc (...) item 15 key (EXTENT_CSUM EXTENT_CSUM 21565440) itemoff 991 itemsize 200 range start 21565440 end 21770240 length 204800 item 16 key (EXTENT_CSUM EXTENT_CSUM 1104576512) itemoff 983 itemsize 8 range start 1104576512 end 1104584704 length 8192 (..) We see that the csum item number 15 covers the first 24K of the file extent item - it ends at offset 21770240 and the extent's disk_bytenr is 21745664, so we have: 21770240 - 21745664 = 24K We see that the next csum item (number 16) is completely outside the range, so the remaining 40K of the extent doesn't have csum items in the tree. If we round up the i_size to the sector size, we get: round_up(610969, 4096) = 614400 If we subtract from that the file offset for the extent item we get: 614400 - 589824 = 24K So the missing 40K corresponds to the end of the file extent item's range minus the rounded up i_size: 655360 - 614400 = 40K Normally we don't expect a file extent item to span over the rounded up i_size of an inode, since when truncating, doing hole punching and other operations that trim a file extent item, the number of bytes is adjusted. There is however a short time window where the kernel can end up, temporarily,persisting an inode with an i_size that falls in the middle of the last file extent item and the file extent item was not yet trimmed (its number of bytes reduced so that it doesn't cross i_size rounded up by the sector size). The steps (in the kernel) that lead to such scenario are the following: 1) We have inode I as an empty file, no allocated extents, i_size is 0; 2) A buffered write is done for file range [589824, 655360[ (length of 64K) and the i_size is updated to 655360. Note that we got a single large folio for the range (64K); 3) A truncate operation starts that reduces the inode's i_size down to 610969 bytes. The truncate sets the inode's new i_size at btrfs_setsize() by calling truncate_setsize() and before calling btrfs_truncate(); 4) At btrfs_truncate() we trigger writeback for the range starting at 610304 (which is the new i_size rounded down to the sector size) and ending at (u64)-1; 5) During the writeback, at extent_write_cache_pages(), we get from the call to filemap_get_folios_tag(), the 64K folio that starts at file offset 589824 since it contains the start offset of the writeback range (610304); 6) At writepage_delalloc() we find the whole range of the folio is dirty and therefore we run delalloc for that 64K range ([589824, 655360[), reserving a 64K extent, creating an ordered extent, etc; 7) At extent_writepage_io() we submit IO only for subrange [589824, 614400[ because the inode's i_size is 610969 bytes (rounded up by sector size is 614400). There, in the while loop we intentionally skip IO beyond i_size to avoid any unnecessay work and just call btrfs_mark_ordered_io_finished() for the range [614400, 655360[ (which has a 40K length); 8) Once the IO finishes we finish the ordered extent by ending up at btrfs_finish_one_ordered(), join transaction N, insert a file extent item in the inode's subvolume tree for file offset 589824 with a number of bytes of 64K, and update the inode's delayed inode item or directly the inode item with a call to btrfs_update_inode_fallback(), which results in storing the new i_size of 610969 bytes; 9) Transaction N is committed either by the transaction kthread or some other task committed it (in response to a sync or fsync for example). At this point we have inode I persisted with an i_size of 610969 bytes and file extent item that starts at file offset 589824 and has a number of bytes of 64K, ending at an offset of 655360 which is beyond the i_size rounded up to the sector size (614400). --> So after a crash or power failure here, the btrfs check program reports that error about missing checksum items for this inode, as it tries to lookup for checksums covering the whole range of the extent; 10) Only after transaction N is committed that at btrfs_truncate() the call to btrfs_start_transaction() starts a new transaction, N + 1, instead of joining transaction N. And it's with transaction N + 1 that it calls btrfs_truncate_inode_items() which updates the file extent item at file offset 589824 to reduce its number of bytes from 64K down to 24K, so that the file extent item's range ends at the i_size rounded up to the sector size (614400 bytes). Fix this by truncating the ordered extent at extent_writepage_io() when we skip writeback because the current offset in the folio is beyond i_size. This ensures we don't ever persist a file extent item with a number of bytes beyond the rounded up (by sector size) value of the i_size. Reviewed-by: Qu Wenruo Reviewed-by: Anand Jain Signed-off-by: Filipe Manana Signed-off-by: David Sterba Stable-dep-of: e9e3b22ddfa7 ("btrfs: fix beyond-EOF write handling") Signed-off-by: Sasha Levin Signed-off-by: Greg Kroah-Hartman --- fs/btrfs/extent_io.c | 21 +++++++++++++++++++-- fs/btrfs/ordered-data.c | 5 +++-- 2 files changed, 22 insertions(+), 4 deletions(-) diff --git a/fs/btrfs/extent_io.c b/fs/btrfs/extent_io.c index 23273d0e6f2245..7cbccd4604c508 100644 --- a/fs/btrfs/extent_io.c +++ b/fs/btrfs/extent_io.c @@ -1692,13 +1692,13 @@ static noinline_for_stack int extent_writepage_io(struct btrfs_inode *inode, bool submitted_io = false; int found_error = 0; const u64 folio_start = folio_pos(folio); + const u64 folio_end = folio_start + folio_size(folio); const unsigned int blocks_per_folio = btrfs_blocks_per_folio(fs_info, folio); u64 cur; int bit; int ret = 0; - ASSERT(start >= folio_start && - start + len <= folio_start + folio_size(folio)); + ASSERT(start >= folio_start && start + len <= folio_end); ret = btrfs_writepage_cow_fixup(folio); if (ret == -EAGAIN) { @@ -1725,6 +1725,23 @@ static noinline_for_stack int extent_writepage_io(struct btrfs_inode *inode, cur = folio_pos(folio) + (bit << fs_info->sectorsize_bits); if (cur >= i_size) { + struct btrfs_ordered_extent *ordered; + unsigned long flags; + + ordered = btrfs_lookup_first_ordered_range(inode, cur, + folio_end - cur); + /* + * We have just run delalloc before getting here, so + * there must be an ordered extent. + */ + ASSERT(ordered != NULL); + spin_lock_irqsave(&inode->ordered_tree_lock, flags); + set_bit(BTRFS_ORDERED_TRUNCATED, &ordered->flags); + ordered->truncated_len = min(ordered->truncated_len, + cur - ordered->file_offset); + spin_unlock_irqrestore(&inode->ordered_tree_lock, flags); + btrfs_put_ordered_extent(ordered); + btrfs_mark_ordered_io_finished(inode, folio, cur, start + len - cur, true); /* diff --git a/fs/btrfs/ordered-data.c b/fs/btrfs/ordered-data.c index 2829f20d7bb59d..8a8aa6ed405bd4 100644 --- a/fs/btrfs/ordered-data.c +++ b/fs/btrfs/ordered-data.c @@ -1098,8 +1098,9 @@ struct btrfs_ordered_extent *btrfs_lookup_first_ordered_range( struct rb_node *prev; struct rb_node *next; struct btrfs_ordered_extent *entry = NULL; + unsigned long flags; - spin_lock_irq(&inode->ordered_tree_lock); + spin_lock_irqsave(&inode->ordered_tree_lock, flags); node = inode->ordered_tree.rb_node; /* * Here we don't want to use tree_search() which will use tree->last @@ -1154,7 +1155,7 @@ struct btrfs_ordered_extent *btrfs_lookup_first_ordered_range( trace_btrfs_ordered_extent_lookup_first_range(inode, entry); } - spin_unlock_irq(&inode->ordered_tree_lock); + spin_unlock_irqrestore(&inode->ordered_tree_lock, flags); return entry; } From 9cb1a586c78ad35f9c5c679d8696e7cf38c5e734 Mon Sep 17 00:00:00 2001 From: Filipe Manana Date: Mon, 12 Jan 2026 08:54:50 -0500 Subject: [PATCH 2380/3902] btrfs: use variable for end offset in extent_writepage_io() [ Upstream commit 46a23908598f4b8e61483f04ea9f471b2affc58a ] Instead of repeating the expression "start + len" multiple times, store it in a variable and use it where needed. Reviewed-by: Qu Wenruo Reviewed-by: Anand Jain Signed-off-by: Filipe Manana Reviewed-by: David Sterba Signed-off-by: David Sterba Stable-dep-of: e9e3b22ddfa7 ("btrfs: fix beyond-EOF write handling") Signed-off-by: Sasha Levin Signed-off-by: Greg Kroah-Hartman --- fs/btrfs/extent_io.c | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/fs/btrfs/extent_io.c b/fs/btrfs/extent_io.c index 7cbccd4604c508..ca09f42d26aa19 100644 --- a/fs/btrfs/extent_io.c +++ b/fs/btrfs/extent_io.c @@ -1691,6 +1691,7 @@ static noinline_for_stack int extent_writepage_io(struct btrfs_inode *inode, unsigned long range_bitmap = 0; bool submitted_io = false; int found_error = 0; + const u64 end = start + len; const u64 folio_start = folio_pos(folio); const u64 folio_end = folio_start + folio_size(folio); const unsigned int blocks_per_folio = btrfs_blocks_per_folio(fs_info, folio); @@ -1698,7 +1699,7 @@ static noinline_for_stack int extent_writepage_io(struct btrfs_inode *inode, int bit; int ret = 0; - ASSERT(start >= folio_start && start + len <= folio_end); + ASSERT(start >= folio_start && end <= folio_end); ret = btrfs_writepage_cow_fixup(folio); if (ret == -EAGAIN) { @@ -1714,7 +1715,7 @@ static noinline_for_stack int extent_writepage_io(struct btrfs_inode *inode, return ret; } - for (cur = start; cur < start + len; cur += fs_info->sectorsize) + for (cur = start; cur < end; cur += fs_info->sectorsize) set_bit((cur - folio_start) >> fs_info->sectorsize_bits, &range_bitmap); bitmap_and(&bio_ctrl->submit_bitmap, &bio_ctrl->submit_bitmap, &range_bitmap, blocks_per_folio); @@ -1743,7 +1744,7 @@ static noinline_for_stack int extent_writepage_io(struct btrfs_inode *inode, btrfs_put_ordered_extent(ordered); btrfs_mark_ordered_io_finished(inode, folio, cur, - start + len - cur, true); + end - cur, true); /* * This range is beyond i_size, thus we don't need to * bother writing back. @@ -1752,8 +1753,7 @@ static noinline_for_stack int extent_writepage_io(struct btrfs_inode *inode, * writeback the sectors with subpage dirty bits, * causing writeback without ordered extent. */ - btrfs_folio_clear_dirty(fs_info, folio, cur, - start + len - cur); + btrfs_folio_clear_dirty(fs_info, folio, cur, end - cur); break; } ret = submit_one_sector(inode, folio, cur, bio_ctrl, i_size); From 4374a1cac13e1b55a801d44bc9e3204395a252b2 Mon Sep 17 00:00:00 2001 From: Qu Wenruo Date: Mon, 12 Jan 2026 08:54:51 -0500 Subject: [PATCH 2381/3902] btrfs: fix beyond-EOF write handling [ Upstream commit e9e3b22ddfa760762b696ac6417c8d6edd182e49 ] [BUG] For the following write sequence with 64K page size and 4K fs block size, it will lead to file extent items to be inserted without any data checksum: mkfs.btrfs -s 4k -f $dev > /dev/null mount $dev $mnt xfs_io -f -c "pwrite 0 16k" -c "pwrite 32k 4k" -c pwrite "60k 64K" \ -c "truncate 16k" $mnt/foobar umount $mnt This will result the following 2 file extent items to be inserted (extra trace point added to insert_ordered_extent_file_extent()): btrfs_finish_one_ordered: root=5 ino=257 file_off=61440 num_bytes=4096 csum_bytes=0 btrfs_finish_one_ordered: root=5 ino=257 file_off=0 num_bytes=16384 csum_bytes=16384 Note for file offset 60K, we're inserting a file extent without any data checksum. Also note that range [32K, 36K) didn't reach insert_ordered_extent_file_extent(), which is the correct behavior as that OE is fully truncated, should not result any file extent. Although file extent at 60K will be later dropped by btrfs_truncate(), if the transaction got committed after file extent inserted but before the file extent dropping, we will have a small window where we have a file extent beyond EOF and without any data checksum. That will cause "btrfs check" to report error. [CAUSE] The sequence happens like this: - Buffered write dirtied the page cache and updated isize Now the inode size is 64K, with the following page cache layout: 0 16K 32K 48K 64K |/////////////| |//| |//| - Truncate the inode to 16K Which will trigger writeback through: btrfs_setsize() |- truncate_setsize() | Now the inode size is set to 16K | |- btrfs_truncate() |- btrfs_wait_ordered_range() for [16K, u64(-1)] |- btrfs_fdatawrite_range() for [16K, u64(-1)} |- extent_writepage() for folio 0 |- writepage_delalloc() | Generated OE for [0, 16K), [32K, 36K] and [60K, 64K) | |- extent_writepage_io() Then inside extent_writepage_io(), the dirty fs blocks are handled differently: - Submit write for range [0, 16K) As they are still inside the inode size (16K). - Mark OE [32K, 36K) as truncated Since we only call btrfs_lookup_first_ordered_range() once, which returned the first OE after file offset 16K. - Mark all OEs inside range [16K, 64K) as finished Which will mark OE ranges [32K, 36K) and [60K, 64K) as finished. For OE [32K, 36K) since it's already marked as truncated, and its truncated length is 0, no file extent will be inserted. For OE [60K, 64K) it has never been submitted thus has no data checksum, and we insert the file extent as usual. This is the root cause of file extent at 60K to be inserted without any data checksum. - Clear dirty flags for range [16K, 64K) It is the function btrfs_folio_clear_dirty() which searches and clears any dirty blocks inside that range. [FIX] The bug itself was introduced a long time ago, way before subpage and large folio support. At that time, fs block size must match page size, thus the range [cur, end) is just one fs block. But later with subpage and large folios, the same range [cur, end) can have multiple blocks and ordered extents. Later commit 18de34daa7c6 ("btrfs: truncate ordered extent when skipping writeback past i_size") was fixing a bug related to subpage/large folios, but it's still utilizing the old range [cur, end), meaning only the first OE will be marked as truncated. The proper fix here is to make EOF handling block-by-block, not trying to handle the whole range to @end. By this we always locate and truncate the OE for every dirty block. CC: stable@vger.kernel.org # 5.15+ Reviewed-by: Filipe Manana Signed-off-by: Qu Wenruo Signed-off-by: David Sterba Signed-off-by: Sasha Levin Signed-off-by: Greg Kroah-Hartman --- fs/btrfs/extent_io.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/fs/btrfs/extent_io.c b/fs/btrfs/extent_io.c index ca09f42d26aa19..1a07edaefaa0f0 100644 --- a/fs/btrfs/extent_io.c +++ b/fs/btrfs/extent_io.c @@ -1730,7 +1730,7 @@ static noinline_for_stack int extent_writepage_io(struct btrfs_inode *inode, unsigned long flags; ordered = btrfs_lookup_first_ordered_range(inode, cur, - folio_end - cur); + fs_info->sectorsize); /* * We have just run delalloc before getting here, so * there must be an ordered extent. @@ -1744,7 +1744,7 @@ static noinline_for_stack int extent_writepage_io(struct btrfs_inode *inode, btrfs_put_ordered_extent(ordered); btrfs_mark_ordered_io_finished(inode, folio, cur, - end - cur, true); + fs_info->sectorsize, true); /* * This range is beyond i_size, thus we don't need to * bother writing back. @@ -1753,8 +1753,8 @@ static noinline_for_stack int extent_writepage_io(struct btrfs_inode *inode, * writeback the sectors with subpage dirty bits, * causing writeback without ordered extent. */ - btrfs_folio_clear_dirty(fs_info, folio, cur, end - cur); - break; + btrfs_folio_clear_dirty(fs_info, folio, cur, fs_info->sectorsize); + continue; } ret = submit_one_sector(inode, folio, cur, bio_ctrl, i_size); if (unlikely(ret < 0)) { From 472d900c8bcac301ae0e40fdca7db799bd989ff5 Mon Sep 17 00:00:00 2001 From: Mary Strodl Date: Mon, 12 Jan 2026 12:44:39 -0500 Subject: [PATCH 2382/3902] gpio: mpsse: ensure worker is torn down [ Upstream commit 179ef1127d7a4f09f0e741fa9f30b8a8e7886271 ] When an IRQ worker is running, unplugging the device would cause a crash. The sealevel hardware this driver was written for was not hotpluggable, so I never realized it. This change uses a spinlock to protect a list of workers, which it tears down on disconnect. Signed-off-by: Mary Strodl Reviewed-by: Linus Walleij Link: https://lore.kernel.org/r/20251014133530.3592716-3-mstrodl@csh.rit.edu Signed-off-by: Bartosz Golaszewski Stable-dep-of: 1e876e5a0875 ("gpio: mpsse: fix reference leak in gpio_mpsse_probe() error paths") Signed-off-by: Sasha Levin Signed-off-by: Greg Kroah-Hartman --- drivers/gpio/gpio-mpsse.c | 106 +++++++++++++++++++++++++++++++++++--- 1 file changed, 99 insertions(+), 7 deletions(-) diff --git a/drivers/gpio/gpio-mpsse.c b/drivers/gpio/gpio-mpsse.c index 9f42bb30b4ec7c..8147b3ddb4a276 100644 --- a/drivers/gpio/gpio-mpsse.c +++ b/drivers/gpio/gpio-mpsse.c @@ -10,6 +10,7 @@ #include #include #include +#include #include struct mpsse_priv { @@ -17,8 +18,10 @@ struct mpsse_priv { struct usb_device *udev; /* USB device encompassing all MPSSEs */ struct usb_interface *intf; /* USB interface for this MPSSE */ u8 intf_id; /* USB interface number for this MPSSE */ - struct work_struct irq_work; /* polling work thread */ + struct list_head workers; /* polling work threads */ struct mutex irq_mutex; /* lock over irq_data */ + struct mutex irq_race; /* race for polling worker teardown */ + raw_spinlock_t irq_spin; /* protects worker list */ atomic_t irq_type[16]; /* pin -> edge detection type */ atomic_t irq_enabled; int id; @@ -34,6 +37,14 @@ struct mpsse_priv { struct mutex io_mutex; /* sync I/O with disconnect */ }; +struct mpsse_worker { + struct mpsse_priv *priv; + struct work_struct work; + atomic_t cancelled; + struct list_head list; /* linked list */ + struct list_head destroy; /* teardown linked list */ +}; + struct bulk_desc { bool tx; /* direction of bulk transfer */ u8 *data; /* input (tx) or output (rx) */ @@ -284,18 +295,62 @@ static int gpio_mpsse_get_direction(struct gpio_chip *chip, return ret; } -static void gpio_mpsse_poll(struct work_struct *work) +/* + * Stops all workers except `my_worker`. + * Safe to call only when `irq_race` is held. + */ +static void gpio_mpsse_stop_all_except(struct mpsse_priv *priv, + struct mpsse_worker *my_worker) +{ + struct mpsse_worker *worker, *worker_tmp; + struct list_head destructors = LIST_HEAD_INIT(destructors); + + scoped_guard(raw_spinlock_irqsave, &priv->irq_spin) { + list_for_each_entry_safe(worker, worker_tmp, + &priv->workers, list) { + /* Don't stop ourselves */ + if (worker == my_worker) + continue; + + list_del(&worker->list); + + /* Give worker a chance to terminate itself */ + atomic_set(&worker->cancelled, 1); + /* Keep track of stuff to cancel */ + INIT_LIST_HEAD(&worker->destroy); + list_add(&worker->destroy, &destructors); + } + } + + list_for_each_entry_safe(worker, worker_tmp, + &destructors, destroy) { + list_del(&worker->destroy); + cancel_work_sync(&worker->work); + kfree(worker); + } +} + +static void gpio_mpsse_poll(struct work_struct *my_work) { unsigned long pin_mask, pin_states, flags; int irq_enabled, offset, err, value, fire_irq, irq, old_value[16], irq_type[16]; - struct mpsse_priv *priv = container_of(work, struct mpsse_priv, - irq_work); + struct mpsse_worker *my_worker = container_of(my_work, struct mpsse_worker, work); + struct mpsse_priv *priv = my_worker->priv; for (offset = 0; offset < priv->gpio.ngpio; ++offset) old_value[offset] = -1; - while ((irq_enabled = atomic_read(&priv->irq_enabled))) { + /* + * We only want one worker. Workers race to acquire irq_race and tear + * down all other workers. This is a cond guard so that we don't deadlock + * trying to cancel a worker. + */ + scoped_cond_guard(mutex_try, return, &priv->irq_race) + gpio_mpsse_stop_all_except(priv, my_worker); + + while ((irq_enabled = atomic_read(&priv->irq_enabled)) && + !atomic_read(&my_worker->cancelled)) { usleep_range(MPSSE_POLL_INTERVAL, MPSSE_POLL_INTERVAL + 1000); /* Cleanup will trigger at the end of the loop */ guard(mutex)(&priv->irq_mutex); @@ -370,21 +425,45 @@ static int gpio_mpsse_set_irq_type(struct irq_data *irqd, unsigned int type) static void gpio_mpsse_irq_disable(struct irq_data *irqd) { + struct mpsse_worker *worker; struct mpsse_priv *priv = irq_data_get_irq_chip_data(irqd); atomic_and(~BIT(irqd->hwirq), &priv->irq_enabled); gpiochip_disable_irq(&priv->gpio, irqd->hwirq); + + /* + * Can't actually do teardown in IRQ context (it blocks). + * As a result, these workers will stick around until irq is reenabled + * or device gets disconnected + */ + scoped_guard(raw_spinlock_irqsave, &priv->irq_spin) + list_for_each_entry(worker, &priv->workers, list) + atomic_set(&worker->cancelled, 1); } static void gpio_mpsse_irq_enable(struct irq_data *irqd) { + struct mpsse_worker *worker; struct mpsse_priv *priv = irq_data_get_irq_chip_data(irqd); gpiochip_enable_irq(&priv->gpio, irqd->hwirq); /* If no-one else was using the IRQ, enable it */ if (!atomic_fetch_or(BIT(irqd->hwirq), &priv->irq_enabled)) { - INIT_WORK(&priv->irq_work, gpio_mpsse_poll); - schedule_work(&priv->irq_work); + /* + * Can't be devm because it uses a non-raw spinlock (illegal in + * this context, where a raw spinlock is held by our caller) + */ + worker = kzalloc(sizeof(*worker), GFP_NOWAIT); + if (!worker) + return; + + worker->priv = priv; + INIT_LIST_HEAD(&worker->list); + INIT_WORK(&worker->work, gpio_mpsse_poll); + schedule_work(&worker->work); + + scoped_guard(raw_spinlock_irqsave, &priv->irq_spin) + list_add(&worker->list, &priv->workers); } } @@ -436,6 +515,12 @@ static int gpio_mpsse_probe(struct usb_interface *interface, if (err) return err; + err = devm_mutex_init(dev, &priv->irq_race); + if (err) + return err; + + raw_spin_lock_init(&priv->irq_spin); + priv->gpio.label = devm_kasprintf(dev, GFP_KERNEL, "gpio-mpsse.%d.%d", priv->id, priv->intf_id); @@ -506,6 +591,13 @@ static void gpio_mpsse_disconnect(struct usb_interface *intf) { struct mpsse_priv *priv = usb_get_intfdata(intf); + /* + * Lock prevents double-free of worker from here and the teardown + * step at the beginning of gpio_mpsse_poll + */ + scoped_guard(mutex, &priv->irq_race) + gpio_mpsse_stop_all_except(priv, NULL); + priv->intf = NULL; usb_set_intfdata(intf, NULL); usb_put_dev(priv->udev); From 7882637ea5ccba08ec3674628f03ff0baf48d935 Mon Sep 17 00:00:00 2001 From: Mary Strodl Date: Mon, 12 Jan 2026 12:44:40 -0500 Subject: [PATCH 2383/3902] gpio: mpsse: add quirk support [ Upstream commit f13b0f72af238d63bb9a2e417657da8b45d72544 ] Builds out a facility for specifying compatible lines directions and labels for MPSSE-based devices. * dir_in/out are bitmask of lines that can go in/out. 1 means compatible, 0 means incompatible. * names is an array of line names which will be exposed to userspace. Also changes the chip label format to include some more useful information about the device to help identify it from userspace. Signed-off-by: Mary Strodl Reviewed-by: Dan Carpenter Reviewed-by: Linus Walleij Link: https://lore.kernel.org/r/20251014133530.3592716-4-mstrodl@csh.rit.edu Signed-off-by: Bartosz Golaszewski Stable-dep-of: 1e876e5a0875 ("gpio: mpsse: fix reference leak in gpio_mpsse_probe() error paths") Signed-off-by: Sasha Levin Signed-off-by: Greg Kroah-Hartman --- drivers/gpio/gpio-mpsse.c | 109 ++++++++++++++++++++++++++++++++++++-- 1 file changed, 106 insertions(+), 3 deletions(-) diff --git a/drivers/gpio/gpio-mpsse.c b/drivers/gpio/gpio-mpsse.c index 8147b3ddb4a276..e68179caafa62f 100644 --- a/drivers/gpio/gpio-mpsse.c +++ b/drivers/gpio/gpio-mpsse.c @@ -29,6 +29,9 @@ struct mpsse_priv { u8 gpio_outputs[2]; /* Output states for GPIOs [L, H] */ u8 gpio_dir[2]; /* Directions for GPIOs [L, H] */ + unsigned long dir_in; /* Bitmask of valid input pins */ + unsigned long dir_out; /* Bitmask of valid output pins */ + u8 *bulk_in_buf; /* Extra recv buffer to grab status bytes */ struct usb_endpoint_descriptor *bulk_in; @@ -54,6 +57,14 @@ struct bulk_desc { int timeout; }; +#define MPSSE_NGPIO 16 + +struct mpsse_quirk { + const char *names[MPSSE_NGPIO]; /* Pin names, if applicable */ + unsigned long dir_in; /* Bitmask of valid input pins */ + unsigned long dir_out; /* Bitmask of valid output pins */ +}; + static const struct usb_device_id gpio_mpsse_table[] = { { USB_DEVICE(0x0c52, 0xa064) }, /* SeaLevel Systems, Inc. */ { } /* Terminating entry */ @@ -171,6 +182,32 @@ static int gpio_mpsse_get_bank(struct mpsse_priv *priv, u8 bank) return buf; } +static int mpsse_ensure_supported(struct gpio_chip *chip, + unsigned long mask, int direction) +{ + unsigned long supported, unsupported; + char *type = "input"; + struct mpsse_priv *priv = gpiochip_get_data(chip); + + supported = priv->dir_in; + if (direction == GPIO_LINE_DIRECTION_OUT) { + supported = priv->dir_out; + type = "output"; + } + + /* An invalid bit was in the provided mask */ + unsupported = mask & ~supported; + if (unsupported) { + dev_err(&priv->udev->dev, + "mpsse: GPIO %lu doesn't support %s\n", + find_first_bit(&unsupported, sizeof(unsupported) * 8), + type); + return -EOPNOTSUPP; + } + + return 0; +} + static int gpio_mpsse_set_multiple(struct gpio_chip *chip, unsigned long *mask, unsigned long *bits) { @@ -178,6 +215,10 @@ static int gpio_mpsse_set_multiple(struct gpio_chip *chip, unsigned long *mask, int ret; struct mpsse_priv *priv = gpiochip_get_data(chip); + ret = mpsse_ensure_supported(chip, *mask, GPIO_LINE_DIRECTION_OUT); + if (ret) + return ret; + guard(mutex)(&priv->io_mutex); for_each_set_clump8(i, bank_mask, mask, chip->ngpio) { bank = i / 8; @@ -205,6 +246,10 @@ static int gpio_mpsse_get_multiple(struct gpio_chip *chip, unsigned long *mask, int ret; struct mpsse_priv *priv = gpiochip_get_data(chip); + ret = mpsse_ensure_supported(chip, *mask, GPIO_LINE_DIRECTION_IN); + if (ret) + return ret; + guard(mutex)(&priv->io_mutex); for_each_set_clump8(i, bank_mask, mask, chip->ngpio) { bank = i / 8; @@ -253,10 +298,15 @@ static int gpio_mpsse_gpio_set(struct gpio_chip *chip, unsigned int offset, static int gpio_mpsse_direction_output(struct gpio_chip *chip, unsigned int offset, int value) { + int ret; struct mpsse_priv *priv = gpiochip_get_data(chip); int bank = (offset & 8) >> 3; int bank_offset = offset & 7; + ret = mpsse_ensure_supported(chip, BIT(offset), GPIO_LINE_DIRECTION_OUT); + if (ret) + return ret; + scoped_guard(mutex, &priv->io_mutex) priv->gpio_dir[bank] |= BIT(bank_offset); @@ -266,10 +316,15 @@ static int gpio_mpsse_direction_output(struct gpio_chip *chip, static int gpio_mpsse_direction_input(struct gpio_chip *chip, unsigned int offset) { + int ret; struct mpsse_priv *priv = gpiochip_get_data(chip); int bank = (offset & 8) >> 3; int bank_offset = offset & 7; + ret = mpsse_ensure_supported(chip, BIT(offset), GPIO_LINE_DIRECTION_IN); + if (ret) + return ret; + guard(mutex)(&priv->io_mutex); priv->gpio_dir[bank] &= ~BIT(bank_offset); gpio_mpsse_set_bank(priv, bank); @@ -483,18 +538,49 @@ static void gpio_mpsse_ida_remove(void *data) ida_free(&gpio_mpsse_ida, priv->id); } +static int mpsse_init_valid_mask(struct gpio_chip *chip, + unsigned long *valid_mask, + unsigned int ngpios) +{ + struct mpsse_priv *priv = gpiochip_get_data(chip); + + if (WARN_ON(priv == NULL)) + return -ENODEV; + + *valid_mask = priv->dir_in | priv->dir_out; + + return 0; +} + +static void mpsse_irq_init_valid_mask(struct gpio_chip *chip, + unsigned long *valid_mask, + unsigned int ngpios) +{ + struct mpsse_priv *priv = gpiochip_get_data(chip); + + if (WARN_ON(priv == NULL)) + return; + + /* Can only use IRQ on input capable pins */ + *valid_mask = priv->dir_in; +} + static int gpio_mpsse_probe(struct usb_interface *interface, const struct usb_device_id *id) { struct mpsse_priv *priv; struct device *dev; + char *serial; int err; + struct mpsse_quirk *quirk = (void *)id->driver_info; dev = &interface->dev; priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); if (!priv) return -ENOMEM; + INIT_LIST_HEAD(&priv->workers); + priv->udev = usb_get_dev(interface_to_usbdev(interface)); priv->intf = interface; priv->intf_id = interface->cur_altsetting->desc.bInterfaceNumber; @@ -521,9 +607,15 @@ static int gpio_mpsse_probe(struct usb_interface *interface, raw_spin_lock_init(&priv->irq_spin); + serial = priv->udev->serial; + if (!serial) + serial = "NONE"; + priv->gpio.label = devm_kasprintf(dev, GFP_KERNEL, - "gpio-mpsse.%d.%d", - priv->id, priv->intf_id); + "MPSSE%04x:%04x.%d.%d.%s", + id->idVendor, id->idProduct, + priv->intf_id, priv->id, + serial); if (!priv->gpio.label) return -ENOMEM; @@ -537,10 +629,20 @@ static int gpio_mpsse_probe(struct usb_interface *interface, priv->gpio.get_multiple = gpio_mpsse_get_multiple; priv->gpio.set_multiple = gpio_mpsse_set_multiple; priv->gpio.base = -1; - priv->gpio.ngpio = 16; + priv->gpio.ngpio = MPSSE_NGPIO; priv->gpio.offset = priv->intf_id * priv->gpio.ngpio; priv->gpio.can_sleep = 1; + if (quirk) { + priv->dir_out = quirk->dir_out; + priv->dir_in = quirk->dir_in; + priv->gpio.names = quirk->names; + priv->gpio.init_valid_mask = mpsse_init_valid_mask; + } else { + priv->dir_in = U16_MAX; + priv->dir_out = U16_MAX; + } + err = usb_find_common_endpoints(interface->cur_altsetting, &priv->bulk_in, &priv->bulk_out, NULL, NULL); @@ -579,6 +681,7 @@ static int gpio_mpsse_probe(struct usb_interface *interface, priv->gpio.irq.parents = NULL; priv->gpio.irq.default_type = IRQ_TYPE_NONE; priv->gpio.irq.handler = handle_simple_irq; + priv->gpio.irq.init_valid_mask = mpsse_irq_init_valid_mask; err = devm_gpiochip_add_data(dev, &priv->gpio, priv); if (err) From 7ea26e6dcabc270433b6ded2a1aee85b215d1b28 Mon Sep 17 00:00:00 2001 From: Abdun Nihaal Date: Mon, 12 Jan 2026 12:44:41 -0500 Subject: [PATCH 2384/3902] gpio: mpsse: fix reference leak in gpio_mpsse_probe() error paths [ Upstream commit 1e876e5a0875e71e34148c9feb2eedd3bf6b2b43 ] The reference obtained by calling usb_get_dev() is not released in the gpio_mpsse_probe() error paths. Fix that by using device managed helper functions. Also remove the usb_put_dev() call in the disconnect function since now it will be released automatically. Cc: stable@vger.kernel.org Fixes: c46a74ff05c0 ("gpio: add support for FTDI's MPSSE as GPIO") Signed-off-by: Abdun Nihaal Link: https://lore.kernel.org/r/20251226060414.20785-1-nihaal@cse.iitm.ac.in Signed-off-by: Bartosz Golaszewski Signed-off-by: Sasha Levin Signed-off-by: Greg Kroah-Hartman --- drivers/gpio/gpio-mpsse.c | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/drivers/gpio/gpio-mpsse.c b/drivers/gpio/gpio-mpsse.c index e68179caafa62f..120b27183b1d9e 100644 --- a/drivers/gpio/gpio-mpsse.c +++ b/drivers/gpio/gpio-mpsse.c @@ -538,6 +538,13 @@ static void gpio_mpsse_ida_remove(void *data) ida_free(&gpio_mpsse_ida, priv->id); } +static void gpio_mpsse_usb_put_dev(void *data) +{ + struct mpsse_priv *priv = data; + + usb_put_dev(priv->udev); +} + static int mpsse_init_valid_mask(struct gpio_chip *chip, unsigned long *valid_mask, unsigned int ngpios) @@ -582,6 +589,10 @@ static int gpio_mpsse_probe(struct usb_interface *interface, INIT_LIST_HEAD(&priv->workers); priv->udev = usb_get_dev(interface_to_usbdev(interface)); + err = devm_add_action_or_reset(dev, gpio_mpsse_usb_put_dev, priv); + if (err) + return err; + priv->intf = interface; priv->intf_id = interface->cur_altsetting->desc.bInterfaceNumber; @@ -703,7 +714,6 @@ static void gpio_mpsse_disconnect(struct usb_interface *intf) priv->intf = NULL; usb_set_intfdata(intf, NULL); - usb_put_dev(priv->udev); } static struct usb_driver gpio_mpsse_driver = { From 31e37f44b60679d90b9f999c91371b15291be8e0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Toke=20H=C3=B8iland-J=C3=B8rgensen?= Date: Mon, 5 Jan 2026 12:47:45 +0100 Subject: [PATCH 2385/3902] bpf, test_run: Subtract size of xdp_frame from allowed metadata size MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [ Upstream commit e558cca217790286e799a8baacd1610bda31b261 ] The xdp_frame structure takes up part of the XDP frame headroom, limiting the size of the metadata. However, in bpf_test_run, we don't take this into account, which makes it possible for userspace to supply a metadata size that is too large (taking up the entire headroom). If userspace supplies such a large metadata size in live packet mode, the xdp_update_frame_from_buff() call in xdp_test_run_init_page() call will fail, after which packet transmission proceeds with an uninitialised frame structure, leading to the usual Bad Stuff. The commit in the Fixes tag fixed a related bug where the second check in xdp_update_frame_from_buff() could fail, but did not add any additional constraints on the metadata size. Complete the fix by adding an additional check on the metadata size. Reorder the checks slightly to make the logic clearer and add a comment. Link: https://lore.kernel.org/r/fa2be179-bad7-4ee3-8668-4903d1853461@hust.edu.cn Fixes: b6f1f780b393 ("bpf, test_run: Fix packet size check for live packet mode") Reported-by: Yinhao Hu Reported-by: Kaiyan Mei Signed-off-by: Toke Høiland-Jørgensen Reviewed-by: Amery Hung Link: https://lore.kernel.org/r/20260105114747.1358750-1-toke@redhat.com Signed-off-by: Alexei Starovoitov Signed-off-by: Sasha Levin --- net/bpf/test_run.c | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/net/bpf/test_run.c b/net/bpf/test_run.c index 55a79337ac51fd..59620fdd5cfda9 100644 --- a/net/bpf/test_run.c +++ b/net/bpf/test_run.c @@ -1231,8 +1231,6 @@ int bpf_prog_test_run_xdp(struct bpf_prog *prog, const union bpf_attr *kattr, batch_size = NAPI_POLL_WEIGHT; else if (batch_size > TEST_XDP_MAX_BATCH) return -E2BIG; - - headroom += sizeof(struct xdp_page_head); } else if (batch_size) { return -EINVAL; } @@ -1245,16 +1243,26 @@ int bpf_prog_test_run_xdp(struct bpf_prog *prog, const union bpf_attr *kattr, /* There can't be user provided data before the meta data */ if (ctx->data_meta || ctx->data_end > kattr->test.data_size_in || ctx->data > ctx->data_end || - unlikely(xdp_metalen_invalid(ctx->data)) || (do_live && (kattr->test.data_out || kattr->test.ctx_out))) goto free_ctx; - /* Meta data is allocated from the headroom */ - headroom -= ctx->data; meta_sz = ctx->data; + if (xdp_metalen_invalid(meta_sz) || meta_sz > headroom - sizeof(struct xdp_frame)) + goto free_ctx; + + /* Meta data is allocated from the headroom */ + headroom -= meta_sz; linear_sz = ctx->data_end; } + /* The xdp_page_head structure takes up space in each page, limiting the + * size of the packet data; add the extra size to headroom here to make + * sure it's accounted in the length checks below, but not in the + * metadata size check above. + */ + if (do_live) + headroom += sizeof(struct xdp_page_head); + max_linear_sz = PAGE_SIZE - headroom - tailroom; linear_sz = min_t(u32, linear_sz, max_linear_sz); From 737be05a765761d7d7c9f7fe92274bd8e6f6951e Mon Sep 17 00:00:00 2001 From: Tetsuo Handa Date: Thu, 8 Jan 2026 21:36:48 +0900 Subject: [PATCH 2386/3902] bpf: Fix reference count leak in bpf_prog_test_run_xdp() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [ Upstream commit ec69daabe45256f98ac86c651b8ad1b2574489a7 ] syzbot is reporting unregister_netdevice: waiting for sit0 to become free. Usage count = 2 problem. A debug printk() patch found that a refcount is obtained at xdp_convert_md_to_buff() from bpf_prog_test_run_xdp(). According to commit ec94670fcb3b ("bpf: Support specifying ingress via xdp_md context in BPF_PROG_TEST_RUN"), the refcount obtained by xdp_convert_md_to_buff() will be released by xdp_convert_buff_to_md(). Therefore, we can consider that the error handling path introduced by commit 1c1949982524 ("bpf: introduce frags support to bpf_prog_test_run_xdp()") forgot to call xdp_convert_buff_to_md(). Reported-by: syzbot+881d65229ca4f9ae8c84@syzkaller.appspotmail.com Closes: https://syzkaller.appspot.com/bug?extid=881d65229ca4f9ae8c84 Fixes: 1c1949982524 ("bpf: introduce frags support to bpf_prog_test_run_xdp()") Signed-off-by: Tetsuo Handa Reviewed-by: Toke Høiland-Jørgensen Link: https://lore.kernel.org/r/af090e53-9d9b-4412-8acb-957733b3975c@I-love.SAKURA.ne.jp Signed-off-by: Alexei Starovoitov Signed-off-by: Sasha Levin --- net/bpf/test_run.c | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/net/bpf/test_run.c b/net/bpf/test_run.c index 59620fdd5cfda9..6b04f47301c1ef 100644 --- a/net/bpf/test_run.c +++ b/net/bpf/test_run.c @@ -1300,13 +1300,13 @@ int bpf_prog_test_run_xdp(struct bpf_prog *prog, const union bpf_attr *kattr, if (sinfo->nr_frags == MAX_SKB_FRAGS) { ret = -ENOMEM; - goto out; + goto out_put_dev; } page = alloc_page(GFP_KERNEL); if (!page) { ret = -ENOMEM; - goto out; + goto out_put_dev; } frag = &sinfo->frags[sinfo->nr_frags++]; @@ -1318,7 +1318,7 @@ int bpf_prog_test_run_xdp(struct bpf_prog *prog, const union bpf_attr *kattr, if (copy_from_user(page_address(page), data_in + size, data_len)) { ret = -EFAULT; - goto out; + goto out_put_dev; } sinfo->xdp_frags_size += data_len; size += data_len; @@ -1333,6 +1333,7 @@ int bpf_prog_test_run_xdp(struct bpf_prog *prog, const union bpf_attr *kattr, ret = bpf_test_run_xdp_live(prog, &xdp, repeat, batch_size, &duration); else ret = bpf_test_run(prog, &xdp, repeat, &retval, &duration, true); +out_put_dev: /* We convert the xdp_buff back to an xdp_md before checking the return * code so the reference count of any held netdevice will be decremented * even if the test run failed. From cbbf6c1fe62b919bcf43f8d0a45a64696158a941 Mon Sep 17 00:00:00 2001 From: Marcus Hughes Date: Sun, 7 Dec 2025 21:03:55 +0000 Subject: [PATCH 2387/3902] net: sfp: extend Potron XGSPON quirk to cover additional EEPROM variant [ Upstream commit 71cfa7c893a05d09e7dc14713b27a8309fd4a2db ] Some Potron SFP+ XGSPON ONU sticks are shipped with different EEPROM vendor ID and vendor name strings, but are otherwise functionally identical to the existing "Potron SFP+ XGSPON ONU Stick" handled by sfp_quirk_potron(). These modules, including units distributed under the "Better Internet" branding, use the same UART pin assignment and require the same TX_FAULT/LOS behaviour and boot delay. Re-use the existing Potron quirk for this EEPROM variant. Signed-off-by: Marcus Hughes Link: https://patch.msgid.link/20251207210355.333451-1-marcus.hughes@betterinternet.ltd Signed-off-by: Jakub Kicinski Signed-off-by: Sasha Levin --- drivers/net/phy/sfp.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/drivers/net/phy/sfp.c b/drivers/net/phy/sfp.c index 6b4dd906b804f4..84bef5099dda6f 100644 --- a/drivers/net/phy/sfp.c +++ b/drivers/net/phy/sfp.c @@ -497,6 +497,8 @@ static const struct sfp_quirk sfp_quirks[] = { SFP_QUIRK("ALCATELLUCENT", "3FE46541AA", sfp_quirk_2500basex, sfp_fixup_nokia), + SFP_QUIRK_F("BIDB", "X-ONU-SFPP", sfp_fixup_potron), + // FLYPRO SFP-10GT-CS-30M uses Rollball protocol to talk to the PHY. SFP_QUIRK_F("FLYPRO", "SFP-10GT-CS-30M", sfp_fixup_rollball), From d33cc39e9f7ef457b0ac4c24be0244ddde4cefca Mon Sep 17 00:00:00 2001 From: Sumeet Pawnikar Date: Sat, 6 Dec 2025 00:32:16 +0530 Subject: [PATCH 2388/3902] powercap: fix race condition in register_control_type() [ Upstream commit 7bda1910c4bccd4b8d4726620bb3d6bbfb62286e ] The device becomes visible to userspace via device_register() even before it fully initialized by idr_init(). If userspace or another thread tries to register a zone immediately after device_register(), the control_type_valid() will fail because the control_type is not yet in the list. The IDR is not yet initialized, so this race condition causes zone registration failure. Move idr_init() and list addition before device_register() fix the race condition. Signed-off-by: Sumeet Pawnikar [ rjw: Subject adjustment, empty line added ] Link: https://patch.msgid.link/20251205190216.5032-1-sumeet4linux@gmail.com Signed-off-by: Rafael J. Wysocki Signed-off-by: Sasha Levin --- drivers/powercap/powercap_sys.c | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/drivers/powercap/powercap_sys.c b/drivers/powercap/powercap_sys.c index 4112a009733826..d14b36b75189d7 100644 --- a/drivers/powercap/powercap_sys.c +++ b/drivers/powercap/powercap_sys.c @@ -625,17 +625,23 @@ struct powercap_control_type *powercap_register_control_type( INIT_LIST_HEAD(&control_type->node); control_type->dev.class = &powercap_class; dev_set_name(&control_type->dev, "%s", name); - result = device_register(&control_type->dev); - if (result) { - put_device(&control_type->dev); - return ERR_PTR(result); - } idr_init(&control_type->idr); mutex_lock(&powercap_cntrl_list_lock); list_add_tail(&control_type->node, &powercap_cntrl_list); mutex_unlock(&powercap_cntrl_list_lock); + result = device_register(&control_type->dev); + if (result) { + mutex_lock(&powercap_cntrl_list_lock); + list_del(&control_type->node); + mutex_unlock(&powercap_cntrl_list_lock); + + idr_destroy(&control_type->idr); + put_device(&control_type->dev); + return ERR_PTR(result); + } + return control_type; } EXPORT_SYMBOL_GPL(powercap_register_control_type); From 9e2fcfc32467bc088d6014ae0337dd8be0680880 Mon Sep 17 00:00:00 2001 From: Sumeet Pawnikar Date: Sun, 7 Dec 2025 20:45:48 +0530 Subject: [PATCH 2389/3902] powercap: fix sscanf() error return value handling [ Upstream commit efc4c35b741af973de90f6826bf35d3b3ac36bf1 ] Fix inconsistent error handling for sscanf() return value check. Implicit boolean conversion is used instead of explicit return value checks. The code checks if (!sscanf(...)) which is incorrect because: 1. sscanf returns the number of successfully parsed items 2. On success, it returns 1 (one item passed) 3. On failure, it returns 0 or EOF 4. The check 'if (!sscanf(...))' is wrong because it treats success (1) as failure All occurrences of sscanf() now uses explicit return value check. With this behavior it returns '-EINVAL' when parsing fails (returns 0 or EOF), and continues when parsing succeeds (returns 1). Signed-off-by: Sumeet Pawnikar [ rjw: Subject and changelog edits ] Link: https://patch.msgid.link/20251207151549.202452-1-sumeet4linux@gmail.com Signed-off-by: Rafael J. Wysocki Signed-off-by: Sasha Levin --- drivers/powercap/powercap_sys.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/drivers/powercap/powercap_sys.c b/drivers/powercap/powercap_sys.c index d14b36b75189d7..1ff369880beb28 100644 --- a/drivers/powercap/powercap_sys.c +++ b/drivers/powercap/powercap_sys.c @@ -68,7 +68,7 @@ static ssize_t show_constraint_##_attr(struct device *dev, \ int id; \ struct powercap_zone_constraint *pconst;\ \ - if (!sscanf(dev_attr->attr.name, "constraint_%d_", &id)) \ + if (sscanf(dev_attr->attr.name, "constraint_%d_", &id) != 1) \ return -EINVAL; \ if (id >= power_zone->const_id_cnt) \ return -EINVAL; \ @@ -93,7 +93,7 @@ static ssize_t store_constraint_##_attr(struct device *dev,\ int id; \ struct powercap_zone_constraint *pconst;\ \ - if (!sscanf(dev_attr->attr.name, "constraint_%d_", &id)) \ + if (sscanf(dev_attr->attr.name, "constraint_%d_", &id) != 1) \ return -EINVAL; \ if (id >= power_zone->const_id_cnt) \ return -EINVAL; \ @@ -162,7 +162,7 @@ static ssize_t show_constraint_name(struct device *dev, ssize_t len = -ENODATA; struct powercap_zone_constraint *pconst; - if (!sscanf(dev_attr->attr.name, "constraint_%d_", &id)) + if (sscanf(dev_attr->attr.name, "constraint_%d_", &id) != 1) return -EINVAL; if (id >= power_zone->const_id_cnt) return -EINVAL; From 09d6074995c186e449979fe6c1b0f1a69cf9bd3b Mon Sep 17 00:00:00 2001 From: Florian Westphal Date: Sun, 7 Jul 2024 01:18:25 +0200 Subject: [PATCH 2390/3902] netfilter: nf_tables: avoid chain re-validation if possible [ Upstream commit 8e1a1bc4f5a42747c08130b8242ebebd1210b32f ] Hamza Mahfooz reports cpu soft lock-ups in nft_chain_validate(): watchdog: BUG: soft lockup - CPU#1 stuck for 27s! [iptables-nft-re:37547] [..] RIP: 0010:nft_chain_validate+0xcb/0x110 [nf_tables] [..] nft_immediate_validate+0x36/0x50 [nf_tables] nft_chain_validate+0xc9/0x110 [nf_tables] nft_immediate_validate+0x36/0x50 [nf_tables] nft_chain_validate+0xc9/0x110 [nf_tables] nft_immediate_validate+0x36/0x50 [nf_tables] nft_chain_validate+0xc9/0x110 [nf_tables] nft_immediate_validate+0x36/0x50 [nf_tables] nft_chain_validate+0xc9/0x110 [nf_tables] nft_immediate_validate+0x36/0x50 [nf_tables] nft_chain_validate+0xc9/0x110 [nf_tables] nft_immediate_validate+0x36/0x50 [nf_tables] nft_chain_validate+0xc9/0x110 [nf_tables] nft_table_validate+0x6b/0xb0 [nf_tables] nf_tables_validate+0x8b/0xa0 [nf_tables] nf_tables_commit+0x1df/0x1eb0 [nf_tables] [..] Currently nf_tables will traverse the entire table (chain graph), starting from the entry points (base chains), exploring all possible paths (chain jumps). But there are cases where we could avoid revalidation. Consider: 1 input -> j2 -> j3 2 input -> j2 -> j3 3 input -> j1 -> j2 -> j3 Then the second rule does not need to revalidate j2, and, by extension j3, because this was already checked during validation of the first rule. We need to validate it only for rule 3. This is needed because chain loop detection also ensures we do not exceed the jump stack: Just because we know that j2 is cycle free, its last jump might now exceed the allowed stack size. We also need to update all reachable chains with the new largest observed call depth. Care has to be taken to revalidate even if the chain depth won't be an issue: chain validation also ensures that expressions are not called from invalid base chains. For example, the masquerade expression can only be called from NAT postrouting base chains. Therefore we also need to keep record of the base chain context (type, hooknum) and revalidate if the chain becomes reachable from a different hook location. Reported-by: Hamza Mahfooz Closes: https://lore.kernel.org/netfilter-devel/20251118221735.GA5477@linuxonhyperv3.guj3yctzbm1etfxqx2vob5hsef.xx.internal.cloudapp.net/ Tested-by: Hamza Mahfooz Signed-off-by: Florian Westphal Signed-off-by: Sasha Levin --- include/net/netfilter/nf_tables.h | 34 +++++++++++---- net/netfilter/nf_tables_api.c | 69 +++++++++++++++++++++++++++++-- 2 files changed, 91 insertions(+), 12 deletions(-) diff --git a/include/net/netfilter/nf_tables.h b/include/net/netfilter/nf_tables.h index fab7dc73f738cb..0e266c2d0e7f07 100644 --- a/include/net/netfilter/nf_tables.h +++ b/include/net/netfilter/nf_tables.h @@ -1091,6 +1091,29 @@ struct nft_rule_blob { __attribute__((aligned(__alignof__(struct nft_rule_dp)))); }; +enum nft_chain_types { + NFT_CHAIN_T_DEFAULT = 0, + NFT_CHAIN_T_ROUTE, + NFT_CHAIN_T_NAT, + NFT_CHAIN_T_MAX +}; + +/** + * struct nft_chain_validate_state - validation state + * + * If a chain is encountered again during table validation it is + * possible to avoid revalidation provided the calling context is + * compatible. This structure stores relevant calling context of + * previous validations. + * + * @hook_mask: the hook numbers and locations the chain is linked to + * @depth: the deepest call chain level the chain is linked to + */ +struct nft_chain_validate_state { + u8 hook_mask[NFT_CHAIN_T_MAX]; + u8 depth; +}; + /** * struct nft_chain - nf_tables chain * @@ -1109,6 +1132,7 @@ struct nft_rule_blob { * @udlen: user data length * @udata: user data in the chain * @blob_next: rule blob pointer to the next in the chain + * @vstate: validation state */ struct nft_chain { struct nft_rule_blob __rcu *blob_gen_0; @@ -1128,9 +1152,10 @@ struct nft_chain { /* Only used during control plane commit phase: */ struct nft_rule_blob *blob_next; + struct nft_chain_validate_state vstate; }; -int nft_chain_validate(const struct nft_ctx *ctx, const struct nft_chain *chain); +int nft_chain_validate(const struct nft_ctx *ctx, struct nft_chain *chain); int nft_setelem_validate(const struct nft_ctx *ctx, struct nft_set *set, const struct nft_set_iter *iter, struct nft_elem_priv *elem_priv); @@ -1138,13 +1163,6 @@ int nft_set_catchall_validate(const struct nft_ctx *ctx, struct nft_set *set); int nf_tables_bind_chain(const struct nft_ctx *ctx, struct nft_chain *chain); void nf_tables_unbind_chain(const struct nft_ctx *ctx, struct nft_chain *chain); -enum nft_chain_types { - NFT_CHAIN_T_DEFAULT = 0, - NFT_CHAIN_T_ROUTE, - NFT_CHAIN_T_NAT, - NFT_CHAIN_T_MAX -}; - /** * struct nft_chain_type - nf_tables chain type info * diff --git a/net/netfilter/nf_tables_api.c b/net/netfilter/nf_tables_api.c index a3669acd68a324..3cbf2573b9e908 100644 --- a/net/netfilter/nf_tables_api.c +++ b/net/netfilter/nf_tables_api.c @@ -123,6 +123,29 @@ static void nft_validate_state_update(struct nft_table *table, u8 new_validate_s table->validate_state = new_validate_state; } + +static bool nft_chain_vstate_valid(const struct nft_ctx *ctx, + const struct nft_chain *chain) +{ + const struct nft_base_chain *base_chain; + enum nft_chain_types type; + u8 hooknum; + + if (WARN_ON_ONCE(!nft_is_base_chain(ctx->chain))) + return false; + + base_chain = nft_base_chain(ctx->chain); + hooknum = base_chain->ops.hooknum; + type = base_chain->type->type; + + /* chain is already validated for this call depth */ + if (chain->vstate.depth >= ctx->level && + chain->vstate.hook_mask[type] & BIT(hooknum)) + return true; + + return false; +} + static void nf_tables_trans_destroy_work(struct work_struct *w); static void nft_trans_gc_work(struct work_struct *work); @@ -4079,6 +4102,29 @@ static void nf_tables_rule_release(const struct nft_ctx *ctx, struct nft_rule *r nf_tables_rule_destroy(ctx, rule); } +static void nft_chain_vstate_update(const struct nft_ctx *ctx, struct nft_chain *chain) +{ + const struct nft_base_chain *base_chain; + enum nft_chain_types type; + u8 hooknum; + + /* ctx->chain must hold the calling base chain. */ + if (WARN_ON_ONCE(!nft_is_base_chain(ctx->chain))) { + memset(&chain->vstate, 0, sizeof(chain->vstate)); + return; + } + + base_chain = nft_base_chain(ctx->chain); + hooknum = base_chain->ops.hooknum; + type = base_chain->type->type; + + BUILD_BUG_ON(BIT(NF_INET_NUMHOOKS) > U8_MAX); + + chain->vstate.hook_mask[type] |= BIT(hooknum); + if (chain->vstate.depth < ctx->level) + chain->vstate.depth = ctx->level; +} + /** nft_chain_validate - loop detection and hook validation * * @ctx: context containing call depth and base chain @@ -4088,15 +4134,25 @@ static void nf_tables_rule_release(const struct nft_ctx *ctx, struct nft_rule *r * and set lookups until either the jump limit is hit or all reachable * chains have been validated. */ -int nft_chain_validate(const struct nft_ctx *ctx, const struct nft_chain *chain) +int nft_chain_validate(const struct nft_ctx *ctx, struct nft_chain *chain) { struct nft_expr *expr, *last; struct nft_rule *rule; int err; + BUILD_BUG_ON(NFT_JUMP_STACK_SIZE > 255); if (ctx->level == NFT_JUMP_STACK_SIZE) return -EMLINK; + if (ctx->level > 0) { + /* jumps to base chains are not allowed. */ + if (nft_is_base_chain(chain)) + return -ELOOP; + + if (nft_chain_vstate_valid(ctx, chain)) + return 0; + } + list_for_each_entry(rule, &chain->rules, list) { if (fatal_signal_pending(current)) return -EINTR; @@ -4117,6 +4173,7 @@ int nft_chain_validate(const struct nft_ctx *ctx, const struct nft_chain *chain) } } + nft_chain_vstate_update(ctx, chain); return 0; } EXPORT_SYMBOL_GPL(nft_chain_validate); @@ -4128,7 +4185,7 @@ static int nft_table_validate(struct net *net, const struct nft_table *table) .net = net, .family = table->family, }; - int err; + int err = 0; list_for_each_entry(chain, &table->chains, list) { if (!nft_is_base_chain(chain)) @@ -4137,12 +4194,16 @@ static int nft_table_validate(struct net *net, const struct nft_table *table) ctx.chain = chain; err = nft_chain_validate(&ctx, chain); if (err < 0) - return err; + goto err; cond_resched(); } - return 0; +err: + list_for_each_entry(chain, &table->chains, list) + memset(&chain->vstate, 0, sizeof(chain->vstate)); + + return err; } int nft_setelem_validate(const struct nft_ctx *ctx, struct nft_set *set, From a70fd483c4b936d17771ad77580f78b9c0f65cae Mon Sep 17 00:00:00 2001 From: Niklas Cassel Date: Tue, 9 Dec 2025 05:24:00 +0100 Subject: [PATCH 2391/3902] ata: libata-core: Disable LPM on ST2000DM008-2FR102 [ Upstream commit ba624ba88d9f5c3e2ace9bb6697dbeb05b2dbc44 ] According to a user report, the ST2000DM008-2FR102 has problems with LPM. Reported-by: Emerson Pinter Closes: https://bugzilla.kernel.org/show_bug.cgi?id=220693 Signed-off-by: Niklas Cassel Signed-off-by: Damien Le Moal Signed-off-by: Sasha Levin --- drivers/ata/libata-core.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/drivers/ata/libata-core.c b/drivers/ata/libata-core.c index f48fb63d7e8549..1216b4f2eb9047 100644 --- a/drivers/ata/libata-core.c +++ b/drivers/ata/libata-core.c @@ -4132,6 +4132,9 @@ static const struct ata_dev_quirks_entry __ata_dev_quirks[] = { { "ST3320[68]13AS", "SD1[5-9]", ATA_QUIRK_NONCQ | ATA_QUIRK_FIRMWARE_WARN }, + /* Seagate disks with LPM issues */ + { "ST2000DM008-2FR102", NULL, ATA_QUIRK_NOLPM }, + /* drives which fail FPDMA_AA activation (some may freeze afterwards) the ST disks also have LPM issues */ { "ST1000LM024 HN-M101MBB", NULL, ATA_QUIRK_BROKEN_FPDMA_AA | From 6a2049b7391b5cd92839d350614b4e00fa3dfb2f Mon Sep 17 00:00:00 2001 From: "Mario Limonciello (AMD)" Date: Fri, 12 Dec 2025 23:44:47 -0600 Subject: [PATCH 2392/3902] accel/amdxdna: Block running under a hypervisor [ Upstream commit 7bbf6d15e935abbb3d604c1fa157350e84a26f98 ] SVA support is required, which isn't configured by hypervisor solutions. Closes: https://github.com/QubesOS/qubes-issues/issues/10275 Closes: https://gitlab.freedesktop.org/drm/amd/-/issues/4656 Reviewed-by: Lizhi Hou Link: https://patch.msgid.link/20251213054513.87925-1-superm1@kernel.org Signed-off-by: Mario Limonciello (AMD) Signed-off-by: Sasha Levin --- drivers/accel/amdxdna/aie2_pci.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/drivers/accel/amdxdna/aie2_pci.c b/drivers/accel/amdxdna/aie2_pci.c index 43f725e1a2d761..6e07793bbeacf8 100644 --- a/drivers/accel/amdxdna/aie2_pci.c +++ b/drivers/accel/amdxdna/aie2_pci.c @@ -17,6 +17,7 @@ #include #include #include +#include #include "aie2_msg_priv.h" #include "aie2_pci.h" @@ -486,6 +487,11 @@ static int aie2_init(struct amdxdna_dev *xdna) unsigned long bars = 0; int i, nvec, ret; + if (!hypervisor_is_type(X86_HYPER_NATIVE)) { + XDNA_ERR(xdna, "Running under hypervisor not supported"); + return -EINVAL; + } + ndev = drmm_kzalloc(&xdna->ddev, sizeof(*ndev), GFP_KERNEL); if (!ndev) return -ENOMEM; From 6db8e186c977763542f1c813011bdcad691a73b8 Mon Sep 17 00:00:00 2001 From: Charlene Liu Date: Fri, 28 Nov 2025 19:38:31 -0500 Subject: [PATCH 2393/3902] drm/amd/display: Fix DP no audio issue [ Upstream commit 3886b198bd6e49c801fe9552fcfbfc387a49fbbc ] [why] need to enable APG_CLOCK_ENABLE enable first also need to wake up az from D3 before access az block Reviewed-by: Swapnil Patel Signed-off-by: Charlene Liu Signed-off-by: Chenyu Chen Tested-by: Daniel Wheeler Signed-off-by: Alex Deucher (cherry picked from commit bf5e396957acafd46003318965500914d5f4edfa) Signed-off-by: Sasha Levin --- drivers/gpu/drm/amd/display/dc/hwss/dce110/dce110_hwseq.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/drivers/gpu/drm/amd/display/dc/hwss/dce110/dce110_hwseq.c b/drivers/gpu/drm/amd/display/dc/hwss/dce110/dce110_hwseq.c index b94fec83474008..39be5a58f837a5 100644 --- a/drivers/gpu/drm/amd/display/dc/hwss/dce110/dce110_hwseq.c +++ b/drivers/gpu/drm/amd/display/dc/hwss/dce110/dce110_hwseq.c @@ -1098,13 +1098,13 @@ void dce110_enable_audio_stream(struct pipe_ctx *pipe_ctx) if (dc->current_state->res_ctx.pipe_ctx[i].stream_res.audio != NULL) num_audio++; } + if (num_audio >= 1 && clk_mgr->funcs->enable_pme_wa) { + /*wake AZ from D3 first before access az endpoint*/ + clk_mgr->funcs->enable_pme_wa(clk_mgr); + } pipe_ctx->stream_res.audio->funcs->az_enable(pipe_ctx->stream_res.audio); - if (num_audio >= 1 && clk_mgr->funcs->enable_pme_wa) - /*this is the first audio. apply the PME w/a in order to wake AZ from D3*/ - clk_mgr->funcs->enable_pme_wa(clk_mgr); - link_hwss->enable_audio_packet(pipe_ctx); if (pipe_ctx->stream_res.audio) From 6fcfcf792ce8a46c95317ea0099aff5290dda9a6 Mon Sep 17 00:00:00 2001 From: Fei Shao Date: Wed, 17 Dec 2025 18:10:47 +0800 Subject: [PATCH 2394/3902] spi: mt65xx: Use IRQF_ONESHOT with threaded IRQ [ Upstream commit 8c04b77f87e6e321ae6acd28ce1de5553916153f ] This driver is migrated to use threaded IRQ since commit 5972eb05ca32 ("spi: spi-mt65xx: Use threaded interrupt for non-SPIMEM transfer"), and we almost always want to disable the interrupt line to avoid excess interrupts while the threaded handler is processing SPI transfer. Use IRQF_ONESHOT for that purpose. In practice, we see MediaTek devices show SPI transfer timeout errors when communicating with ChromeOS EC in certain scenarios, and with IRQF_ONESHOT, the issue goes away. Signed-off-by: Fei Shao Link: https://patch.msgid.link/20251217101131.1975131-1-fshao@chromium.org Signed-off-by: Mark Brown Signed-off-by: Sasha Levin --- drivers/spi/spi-mt65xx.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/spi/spi-mt65xx.c b/drivers/spi/spi-mt65xx.c index 4b40985af1eaf0..90e5813cfdc332 100644 --- a/drivers/spi/spi-mt65xx.c +++ b/drivers/spi/spi-mt65xx.c @@ -1320,7 +1320,7 @@ static int mtk_spi_probe(struct platform_device *pdev) ret = devm_request_threaded_irq(dev, irq, mtk_spi_interrupt, mtk_spi_interrupt_thread, - IRQF_TRIGGER_NONE, dev_name(dev), host); + IRQF_ONESHOT, dev_name(dev), host); if (ret) return dev_err_probe(dev, ret, "failed to register irq\n"); From 9fd86747daa6c607c33a5b2dfedc2f430d36e4af Mon Sep 17 00:00:00 2001 From: Brian Kocoloski Date: Thu, 20 Nov 2025 13:57:19 -0500 Subject: [PATCH 2395/3902] drm/amdkfd: Fix improper NULL termination of queue restore SMI event string [ Upstream commit 969faea4e9d01787c58bab4d945f7ad82dad222d ] Pass character "0" rather than NULL terminator to properly format queue restoration SMI events. Currently, the NULL terminator precedes the newline character that is intended to delineate separate events in the SMI event buffer, which can break userspace parsers. Signed-off-by: Brian Kocoloski Reviewed-by: Philip Yang Signed-off-by: Alex Deucher (cherry picked from commit 6e7143e5e6e21f9d5572e0390f7089e6d53edf3c) Signed-off-by: Sasha Levin --- drivers/gpu/drm/amd/amdkfd/kfd_smi_events.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/gpu/drm/amd/amdkfd/kfd_smi_events.c b/drivers/gpu/drm/amd/amdkfd/kfd_smi_events.c index a499449fcb068a..d2bc169e84b0b6 100644 --- a/drivers/gpu/drm/amd/amdkfd/kfd_smi_events.c +++ b/drivers/gpu/drm/amd/amdkfd/kfd_smi_events.c @@ -312,7 +312,7 @@ void kfd_smi_event_queue_restore(struct kfd_node *node, pid_t pid) { kfd_smi_event_add(pid, node, KFD_SMI_EVENT_QUEUE_RESTORE, KFD_EVENT_FMT_QUEUE_RESTORE(ktime_get_boottime_ns(), pid, - node->id, 0)); + node->id, '0')); } void kfd_smi_event_queue_restore_rescheduled(struct mm_struct *mm) From 79dd3f1d9dd310c2af89b09c71f34d93973b200f Mon Sep 17 00:00:00 2001 From: Tetsuo Handa Date: Tue, 25 Nov 2025 22:39:59 +0900 Subject: [PATCH 2396/3902] can: j1939: make j1939_session_activate() fail if device is no longer registered [ Upstream commit 5d5602236f5db19e8b337a2cd87a90ace5ea776d ] syzbot is still reporting unregister_netdevice: waiting for vcan0 to become free. Usage count = 2 even after commit 93a27b5891b8 ("can: j1939: add missing calls in NETDEV_UNREGISTER notification handler") was added. A debug printk() patch found that j1939_session_activate() can succeed even after j1939_cancel_active_session() from j1939_netdev_notify(NETDEV_UNREGISTER) has completed. Since j1939_cancel_active_session() is processed with the session list lock held, checking ndev->reg_state in j1939_session_activate() with the session list lock held can reliably close the race window. Reported-by: syzbot Closes: https://syzkaller.appspot.com/bug?extid=881d65229ca4f9ae8c84 Signed-off-by: Tetsuo Handa Acked-by: Oleksij Rempel Link: https://patch.msgid.link/b9653191-d479-4c8b-8536-1326d028db5c@I-love.SAKURA.ne.jp Signed-off-by: Marc Kleine-Budde Signed-off-by: Sasha Levin --- net/can/j1939/transport.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/net/can/j1939/transport.c b/net/can/j1939/transport.c index fbf5c8001c9df3..613a911dda100b 100644 --- a/net/can/j1939/transport.c +++ b/net/can/j1939/transport.c @@ -1567,6 +1567,8 @@ int j1939_session_activate(struct j1939_session *session) if (active) { j1939_session_put(active); ret = -EAGAIN; + } else if (priv->ndev->reg_state != NETREG_REGISTERED) { + ret = -ENODEV; } else { WARN_ON_ONCE(session->state != J1939_SESSION_NEW); list_add_tail(&session->active_session_list_entry, From 530ec35c211ed1cf8cac1da4a742500932343d4f Mon Sep 17 00:00:00 2001 From: Caleb Sander Mateos Date: Tue, 16 Dec 2025 22:34:35 -0700 Subject: [PATCH 2397/3902] block: validate pi_offset integrity limit [ Upstream commit ccb8a3c08adf8121e2afb8e704f007ce99324d79 ] The PI tuple must be contained within the metadata value, so validate that pi_offset + pi_tuple_size <= metadata_size. This guards against block drivers that report invalid pi_offset values. Signed-off-by: Caleb Sander Mateos Reviewed-by: Christoph Hellwig Signed-off-by: Jens Axboe Signed-off-by: Sasha Levin --- block/blk-settings.c | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/block/blk-settings.c b/block/blk-settings.c index d74b13ec8e548c..f2c1940fe6f1ae 100644 --- a/block/blk-settings.c +++ b/block/blk-settings.c @@ -148,10 +148,9 @@ static int blk_validate_integrity_limits(struct queue_limits *lim) return -EINVAL; } - if (bi->pi_tuple_size > bi->metadata_size) { - pr_warn("pi_tuple_size (%u) exceeds metadata_size (%u)\n", - bi->pi_tuple_size, - bi->metadata_size); + if (bi->pi_offset + bi->pi_tuple_size > bi->metadata_size) { + pr_warn("pi_offset (%u) + pi_tuple_size (%u) exceeds metadata_size (%u)\n", + bi->pi_offset, bi->pi_tuple_size, bi->metadata_size); return -EINVAL; } From 003994f3b01495575a05c2042b46ae059db6259c Mon Sep 17 00:00:00 2001 From: Jussi Laako Date: Thu, 11 Dec 2025 17:22:21 +0200 Subject: [PATCH 2398/3902] ALSA: usb-audio: Update for native DSD support quirks [ Upstream commit da3a7efff64ec0d63af4499eea3a46a2e13b5797 ] Maintenance patch for native DSD support. Add set of missing device and vendor quirks; TEAC, Esoteric, Luxman and Musical Fidelity. Signed-off-by: Jussi Laako Signed-off-by: Takashi Iwai Link: https://patch.msgid.link/20251211152224.1780782-1-jussi@sonarnerd.net Signed-off-by: Sasha Levin --- sound/usb/quirks.c | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/sound/usb/quirks.c b/sound/usb/quirks.c index 61bd61ffb1b230..94a8fdc9c6d3ce 100644 --- a/sound/usb/quirks.c +++ b/sound/usb/quirks.c @@ -2230,6 +2230,12 @@ static const struct usb_audio_quirk_flags_table quirk_flags_table[] = { DEVICE_FLG(0x0644, 0x806b, /* TEAC UD-701 */ QUIRK_FLAG_ITF_USB_DSD_DAC | QUIRK_FLAG_CTL_MSG_DELAY | QUIRK_FLAG_IFACE_DELAY), + DEVICE_FLG(0x0644, 0x807d, /* TEAC UD-507 */ + QUIRK_FLAG_ITF_USB_DSD_DAC | QUIRK_FLAG_CTL_MSG_DELAY | + QUIRK_FLAG_IFACE_DELAY), + DEVICE_FLG(0x0644, 0x806c, /* Esoteric XD */ + QUIRK_FLAG_ITF_USB_DSD_DAC | QUIRK_FLAG_CTL_MSG_DELAY | + QUIRK_FLAG_IFACE_DELAY), DEVICE_FLG(0x06f8, 0xb000, /* Hercules DJ Console (Windows Edition) */ QUIRK_FLAG_IGNORE_CTL_ERROR), DEVICE_FLG(0x06f8, 0xd002, /* Hercules DJ Console (Macintosh Edition) */ @@ -2388,6 +2394,8 @@ static const struct usb_audio_quirk_flags_table quirk_flags_table[] = { QUIRK_FLAG_CTL_MSG_DELAY_1M), DEVICE_FLG(0x30be, 0x0101, /* Schiit Hel */ QUIRK_FLAG_IGNORE_CTL_ERROR), + DEVICE_FLG(0x3255, 0x0000, /* Luxman D-10X */ + QUIRK_FLAG_ITF_USB_DSD_DAC | QUIRK_FLAG_CTL_MSG_DELAY), DEVICE_FLG(0x339b, 0x3a07, /* Synaptics HONOR USB-C HEADSET */ QUIRK_FLAG_MIXER_PLAYBACK_MIN_MUTE), DEVICE_FLG(0x413c, 0xa506, /* Dell AE515 sound bar */ @@ -2431,6 +2439,8 @@ static const struct usb_audio_quirk_flags_table quirk_flags_table[] = { QUIRK_FLAG_DSD_RAW), VENDOR_FLG(0x2622, /* IAG Limited devices */ QUIRK_FLAG_DSD_RAW), + VENDOR_FLG(0x2772, /* Musical Fidelity devices */ + QUIRK_FLAG_DSD_RAW), VENDOR_FLG(0x278b, /* Rotel? */ QUIRK_FLAG_DSD_RAW), VENDOR_FLG(0x292b, /* Gustard/Ess based devices */ From 65f6ae1fdf22ac111ffd68d7f66d89484af90c16 Mon Sep 17 00:00:00 2001 From: Andrew Elantsev Date: Wed, 10 Dec 2025 23:38:00 +0300 Subject: [PATCH 2399/3902] ASoC: amd: yc: Add quirk for Honor MagicBook X16 2025 [ Upstream commit e2cb8ef0372665854fca6fa7b30b20dd35acffeb ] Add a DMI quirk for the Honor MagicBook X16 2025 laptop fixing the issue where the internal microphone was not detected. Signed-off-by: Andrew Elantsev Link: https://patch.msgid.link/20251210203800.142822-1-elantsew.andrew@gmail.com Signed-off-by: Mark Brown Signed-off-by: Sasha Levin --- sound/soc/amd/yc/acp6x-mach.c | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/sound/soc/amd/yc/acp6x-mach.c b/sound/soc/amd/yc/acp6x-mach.c index f210a253da9f56..bf4d9d33656173 100644 --- a/sound/soc/amd/yc/acp6x-mach.c +++ b/sound/soc/amd/yc/acp6x-mach.c @@ -661,6 +661,13 @@ static const struct dmi_system_id yc_acp_quirk_table[] = { DMI_MATCH(DMI_PRODUCT_NAME, "Bravo 15 C7UCX"), } }, + { + .driver_data = &acp6x_card, + .matches = { + DMI_MATCH(DMI_BOARD_VENDOR, "HONOR"), + DMI_MATCH(DMI_PRODUCT_NAME, "GOH-X"), + } + }, {} }; From b3f89f6582e0d5329822f2052134c39f2c3d387d Mon Sep 17 00:00:00 2001 From: Kai Vehmanen Date: Fri, 12 Dec 2025 19:46:58 +0200 Subject: [PATCH 2400/3902] ALSA: hda/realtek: enable woofer speakers on Medion NM14LNL [ Upstream commit e64826e5e367ad45539ab245b92f009ee165025c ] The ALC233 codec on these Medion NM14LNL (SPRCHRGD 14 S2) systems requires a quirk to enable all speakers. Tested-by: davplsm Link: https://github.com/thesofproject/linux/issues/5611 Signed-off-by: Kai Vehmanen Link: https://patch.msgid.link/20251212174658.752641-1-kai.vehmanen@linux.intel.com Signed-off-by: Takashi Iwai Signed-off-by: Sasha Levin --- sound/hda/codecs/realtek/alc269.c | 1 + 1 file changed, 1 insertion(+) diff --git a/sound/hda/codecs/realtek/alc269.c b/sound/hda/codecs/realtek/alc269.c index eea8399e32588e..eb6197d19078c1 100644 --- a/sound/hda/codecs/realtek/alc269.c +++ b/sound/hda/codecs/realtek/alc269.c @@ -7211,6 +7211,7 @@ static const struct hda_quirk alc269_fixup_tbl[] = { SND_PCI_QUIRK(0x1d72, 0x1901, "RedmiBook 14", ALC256_FIXUP_ASUS_HEADSET_MIC), SND_PCI_QUIRK(0x1d72, 0x1945, "Redmi G", ALC256_FIXUP_ASUS_HEADSET_MIC), SND_PCI_QUIRK(0x1d72, 0x1947, "RedmiBook Air", ALC255_FIXUP_XIAOMI_HEADSET_MIC), + SND_PCI_QUIRK(0x1e39, 0xca14, "MEDION NM14LNL", ALC233_FIXUP_MEDION_MTL_SPK), SND_PCI_QUIRK(0x1ee7, 0x2078, "HONOR BRB-X M1010", ALC2XX_FIXUP_HEADSET_MIC), SND_PCI_QUIRK(0x1f66, 0x0105, "Ayaneo Portable Game Player", ALC287_FIXUP_CS35L41_I2C_2), SND_PCI_QUIRK(0x2014, 0x800a, "Positivo ARN50", ALC269_FIXUP_LIMIT_INT_MIC_BOOST), From 90f5dd44d9bf911fa988667d22f668ce5586efdf Mon Sep 17 00:00:00 2001 From: Alexander Stein Date: Tue, 16 Dec 2025 11:22:45 +0100 Subject: [PATCH 2401/3902] ASoC: fsl_sai: Add missing registers to cache default [ Upstream commit 90ed688792a6b7012b3e8a2f858bc3fe7454d0eb ] Drivers does cache sync during runtime resume, setting all writable registers. Not all writable registers are set in cache default, resulting in the erorr message: fsl-sai 30c30000.sai: using zero-initialized flat cache, this may cause unexpected behavior Fix this by adding missing writable register defaults. Signed-off-by: Alexander Stein Link: https://patch.msgid.link/20251216102246.676181-1-alexander.stein@ew.tq-group.com Signed-off-by: Mark Brown Signed-off-by: Sasha Levin --- sound/soc/fsl/fsl_sai.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/sound/soc/fsl/fsl_sai.c b/sound/soc/fsl/fsl_sai.c index 86730c2149146e..2fa14fbdfe1a83 100644 --- a/sound/soc/fsl/fsl_sai.c +++ b/sound/soc/fsl/fsl_sai.c @@ -1081,6 +1081,7 @@ static const struct reg_default fsl_sai_reg_defaults_ofs0[] = { {FSL_SAI_TDR6, 0}, {FSL_SAI_TDR7, 0}, {FSL_SAI_TMR, 0}, + {FSL_SAI_TTCTL, 0}, {FSL_SAI_RCR1(0), 0}, {FSL_SAI_RCR2(0), 0}, {FSL_SAI_RCR3(0), 0}, @@ -1104,12 +1105,14 @@ static const struct reg_default fsl_sai_reg_defaults_ofs8[] = { {FSL_SAI_TDR6, 0}, {FSL_SAI_TDR7, 0}, {FSL_SAI_TMR, 0}, + {FSL_SAI_TTCTL, 0}, {FSL_SAI_RCR1(8), 0}, {FSL_SAI_RCR2(8), 0}, {FSL_SAI_RCR3(8), 0}, {FSL_SAI_RCR4(8), 0}, {FSL_SAI_RCR5(8), 0}, {FSL_SAI_RMR, 0}, + {FSL_SAI_RTCTL, 0}, {FSL_SAI_MCTL, 0}, {FSL_SAI_MDIV, 0}, }; From 81531bdea972c3cd8f147f61ca6027fa24e08fc3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Michal=20R=C3=A1bek?= Date: Fri, 12 Dec 2025 17:08:23 +0100 Subject: [PATCH 2402/3902] scsi: sg: Fix occasional bogus elapsed time that exceeds timeout MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [ Upstream commit 0e1677654259a2f3ccf728de1edde922a3c4ba57 ] A race condition was found in sg_proc_debug_helper(). It was observed on a system using an IBM LTO-9 SAS Tape Drive (ULTRIUM-TD9) and monitoring /proc/scsi/sg/debug every second. A very large elapsed time would sometimes appear. This is caused by two race conditions. We reproduced the issue with an IBM ULTRIUM-HH9 tape drive on an x86_64 architecture. A patched kernel was built, and the race condition could not be observed anymore after the application of this patch. A reproducer C program utilising the scsi_debug module was also built by Changhui Zhong and can be viewed here: https://github.com/MichaelRabek/linux-tests/blob/master/drivers/scsi/sg/sg_race_trigger.c The first race happens between the reading of hp->duration in sg_proc_debug_helper() and request completion in sg_rq_end_io(). The hp->duration member variable may hold either of two types of information: #1 - The start time of the request. This value is present while the request is not yet finished. #2 - The total execution time of the request (end_time - start_time). If sg_proc_debug_helper() executes *after* the value of hp->duration was changed from #1 to #2, but *before* srp->done is set to 1 in sg_rq_end_io(), a fresh timestamp is taken in the else branch, and the elapsed time (value type #2) is subtracted from a timestamp, which cannot yield a valid elapsed time (which is a type #2 value as well). To fix this issue, the value of hp->duration must change under the protection of the sfp->rq_list_lock in sg_rq_end_io(). Since sg_proc_debug_helper() takes this read lock, the change to srp->done and srp->header.duration will happen atomically from the perspective of sg_proc_debug_helper() and the race condition is thus eliminated. The second race condition happens between sg_proc_debug_helper() and sg_new_write(). Even though hp->duration is set to the current time stamp in sg_add_request() under the write lock's protection, it gets overwritten by a call to get_sg_io_hdr(), which calls copy_from_user() to copy struct sg_io_hdr from userspace into kernel space. hp->duration is set to the start time again in sg_common_write(). If sg_proc_debug_helper() is called between these two calls, an arbitrary value set by userspace (usually zero) is used to compute the elapsed time. To fix this issue, hp->duration must be set to the current timestamp again after get_sg_io_hdr() returns successfully. A small race window still exists between get_sg_io_hdr() and setting hp->duration, but this window is only a few instructions wide and does not result in observable issues in practice, as confirmed by testing. Additionally, we fix the format specifier from %d to %u for printing unsigned int values in sg_proc_debug_helper(). Signed-off-by: Michal Rábek Suggested-by: Tomas Henzl Tested-by: Changhui Zhong Reviewed-by: Ewan D. Milne Reviewed-by: John Meneghini Reviewed-by: Tomas Henzl Link: https://patch.msgid.link/20251212160900.64924-1-mrabek@redhat.com Signed-off-by: Martin K. Petersen Signed-off-by: Sasha Levin --- drivers/scsi/sg.c | 20 +++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/drivers/scsi/sg.c b/drivers/scsi/sg.c index b3af9b78fa123b..57fba34832ad11 100644 --- a/drivers/scsi/sg.c +++ b/drivers/scsi/sg.c @@ -731,6 +731,8 @@ sg_new_write(Sg_fd *sfp, struct file *file, const char __user *buf, sg_remove_request(sfp, srp); return -EFAULT; } + hp->duration = jiffies_to_msecs(jiffies); + if (hp->interface_id != 'S') { sg_remove_request(sfp, srp); return -ENOSYS; @@ -815,7 +817,6 @@ sg_common_write(Sg_fd * sfp, Sg_request * srp, return -ENODEV; } - hp->duration = jiffies_to_msecs(jiffies); if (hp->interface_id != '\0' && /* v3 (or later) interface */ (SG_FLAG_Q_AT_TAIL & hp->flags)) at_head = 0; @@ -1338,9 +1339,6 @@ sg_rq_end_io(struct request *rq, blk_status_t status) "sg_cmd_done: pack_id=%d, res=0x%x\n", srp->header.pack_id, result)); srp->header.resid = resid; - ms = jiffies_to_msecs(jiffies); - srp->header.duration = (ms > srp->header.duration) ? - (ms - srp->header.duration) : 0; if (0 != result) { struct scsi_sense_hdr sshdr; @@ -1389,6 +1387,9 @@ sg_rq_end_io(struct request *rq, blk_status_t status) done = 0; } srp->done = done; + ms = jiffies_to_msecs(jiffies); + srp->header.duration = (ms > srp->header.duration) ? + (ms - srp->header.duration) : 0; write_unlock_irqrestore(&sfp->rq_list_lock, iflags); if (likely(done)) { @@ -2533,6 +2534,7 @@ static void sg_proc_debug_helper(struct seq_file *s, Sg_device * sdp) const sg_io_hdr_t *hp; const char * cp; unsigned int ms; + unsigned int duration; k = 0; list_for_each_entry(fp, &sdp->sfds, sfd_siblings) { @@ -2570,13 +2572,17 @@ static void sg_proc_debug_helper(struct seq_file *s, Sg_device * sdp) seq_printf(s, " id=%d blen=%d", srp->header.pack_id, blen); if (srp->done) - seq_printf(s, " dur=%d", hp->duration); + seq_printf(s, " dur=%u", hp->duration); else { ms = jiffies_to_msecs(jiffies); - seq_printf(s, " t_o/elap=%d/%d", + duration = READ_ONCE(hp->duration); + if (duration) + duration = (ms > duration ? + ms - duration : 0); + seq_printf(s, " t_o/elap=%u/%u", (new_interface ? hp->timeout : jiffies_to_msecs(fp->timeout)), - (ms > hp->duration ? ms - hp->duration : 0)); + duration); } seq_printf(s, "ms sgat=%d op=0x%02x\n", usg, (int) srp->data.cmd_opcode); From 69695f5331d4d670eff76376faa3aeb5d68733e3 Mon Sep 17 00:00:00 2001 From: Mateusz Litwin Date: Thu, 18 Dec 2025 22:33:04 +0100 Subject: [PATCH 2403/3902] spi: cadence-quadspi: Prevent lost complete() call during indirect read [ Upstream commit d67396c9d697041b385d70ff2fd59cb07ae167e8 ] A race condition exists between the read loop and IRQ `complete()` call. An interrupt could call the complete() between the inner loop and reinit_completion(), potentially losing the completion event and causing an unnecessary timeout. Moving reinit_completion() before the loop prevents this. A premature signal will only result in a spurious wakeup and another wait cycle, which is preferable to waiting for a timeout. Signed-off-by: Mateusz Litwin Link: https://patch.msgid.link/20251218-cqspi_indirect_read_improve-v2-1-396079972f2a@nokia.com Signed-off-by: Mark Brown Signed-off-by: Sasha Levin --- drivers/spi/spi-cadence-quadspi.c | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/drivers/spi/spi-cadence-quadspi.c b/drivers/spi/spi-cadence-quadspi.c index 3231bdaf9bd06e..1cca9d87fbde4c 100644 --- a/drivers/spi/spi-cadence-quadspi.c +++ b/drivers/spi/spi-cadence-quadspi.c @@ -769,6 +769,7 @@ static int cqspi_indirect_read_execute(struct cqspi_flash_pdata *f_pdata, readl(reg_base + CQSPI_REG_INDIRECTRD); /* Flush posted write. */ while (remaining > 0) { + ret = 0; if (use_irq && !wait_for_completion_timeout(&cqspi->transfer_complete, msecs_to_jiffies(CQSPI_READ_TIMEOUT_MS))) @@ -781,6 +782,14 @@ static int cqspi_indirect_read_execute(struct cqspi_flash_pdata *f_pdata, if (cqspi->slow_sram) writel(0x0, reg_base + CQSPI_REG_IRQMASK); + /* + * Prevent lost interrupt and race condition by reinitializing early. + * A spurious wakeup and another wait cycle can occur here, + * which is preferable to waiting until timeout if interrupt is lost. + */ + if (use_irq) + reinit_completion(&cqspi->transfer_complete); + bytes_to_read = cqspi_get_rd_sram_level(cqspi); if (ret && bytes_to_read == 0) { @@ -813,7 +822,6 @@ static int cqspi_indirect_read_execute(struct cqspi_flash_pdata *f_pdata, } if (use_irq && remaining > 0) { - reinit_completion(&cqspi->transfer_complete); if (cqspi->slow_sram) writel(CQSPI_REG_IRQ_WATERMARK, reg_base + CQSPI_REG_IRQMASK); } From b6fe42bc55af3fb17c8c03def2f8a1f7fa907af6 Mon Sep 17 00:00:00 2001 From: Greg Kroah-Hartman Date: Sat, 17 Jan 2026 16:35:34 +0100 Subject: [PATCH 2404/3902] Linux 6.18.6 Link: https://lore.kernel.org/r/20260115164202.305475649@linuxfoundation.org Tested-by: Ronald Warsow Tested-by: Brett A C Sheffield Tested-by: Slade Watkins Tested-by: Shuah Khan Tested-by: Florian Fainelli Tested-by: Takeshi Ogasawara Tested-by: Salvatore Bonaccorso Tested-by: Ron Economos Tested-by: Jon Hunter Tested-by: Peter Schneider Tested-by: Mark Brown Tested-by: Hardik Garg Tested-by: Brett Mastbergen Tested-by: Miguel Ojeda Signed-off-by: Greg Kroah-Hartman --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 30c332829b0fcf..23a575ce425cd0 100644 --- a/Makefile +++ b/Makefile @@ -1,7 +1,7 @@ # SPDX-License-Identifier: GPL-2.0 VERSION = 6 PATCHLEVEL = 18 -SUBLEVEL = 5 +SUBLEVEL = 6 EXTRAVERSION = NAME = Baby Opossum Posse From 58796d352d74669eacc4715e0901949e9e66f770 Mon Sep 17 00:00:00 2001 From: Peng Fan Date: Fri, 17 Oct 2025 09:56:27 +0800 Subject: [PATCH 2405/3902] firmware: imx: scu-irq: Set mu_resource_id before get handle commit ff3f9913bc0749364fbfd86ea62ba2d31c6136c8 upstream. mu_resource_id is referenced in imx_scu_irq_get_status() and imx_scu_irq_group_enable() which could be used by other modules, so need to set correct value before using imx_sc_irq_ipc_handle in SCU API call. Reviewed-by: Frank Li Signed-off-by: Peng Fan Signed-off-by: Shawn Guo Fixes: 81fb53feb66a ("firmware: imx: scu-irq: Init workqueue before request mbox channel") Cc: Ben Hutchings Signed-off-by: Greg Kroah-Hartman --- drivers/firmware/imx/imx-scu-irq.c | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/drivers/firmware/imx/imx-scu-irq.c b/drivers/firmware/imx/imx-scu-irq.c index b9f6128d56f708..f587abcd7ca3aa 100644 --- a/drivers/firmware/imx/imx-scu-irq.c +++ b/drivers/firmware/imx/imx-scu-irq.c @@ -203,6 +203,18 @@ int imx_scu_enable_general_irq_channel(struct device *dev) struct mbox_chan *ch; int ret = 0, i = 0; + if (!of_parse_phandle_with_args(dev->of_node, "mboxes", + "#mbox-cells", 0, &spec)) { + i = of_alias_get_id(spec.np, "mu"); + of_node_put(spec.np); + } + + /* use mu1 as general mu irq channel if failed */ + if (i < 0) + i = 1; + + mu_resource_id = IMX_SC_R_MU_0A + i; + ret = imx_scu_get_handle(&imx_sc_irq_ipc_handle); if (ret) return ret; @@ -225,18 +237,6 @@ int imx_scu_enable_general_irq_channel(struct device *dev) return ret; } - if (!of_parse_phandle_with_args(dev->of_node, "mboxes", - "#mbox-cells", 0, &spec)) { - i = of_alias_get_id(spec.np, "mu"); - of_node_put(spec.np); - } - - /* use mu1 as general mu irq channel if failed */ - if (i < 0) - i = 1; - - mu_resource_id = IMX_SC_R_MU_0A + i; - /* Create directory under /sysfs/firmware */ wakeup_obj = kobject_create_and_add("scu_wakeup_source", firmware_kobj); if (!wakeup_obj) { From a2542fe353a40b9b03bffdcedc64107ac9322aab Mon Sep 17 00:00:00 2001 From: Morduan Zang Date: Wed, 14 Jan 2026 13:30:33 +0800 Subject: [PATCH 2406/3902] efi/cper: Fix cper_bits_to_str buffer handling and return value commit d7f1b4bdc7108be1b178e1617b5f45c8918e88d7 upstream. The return value calculation was incorrect: `return len - buf_size;` Initially `len = buf_size`, then `len` decreases with each operation. This results in a negative return value on success. Fix by returning `buf_size - len` which correctly calculates the actual number of bytes written. Fixes: a976d790f494 ("efi/cper: Add a new helper function to print bitmasks") Signed-off-by: Morduan Zang Signed-off-by: Ard Biesheuvel Signed-off-by: Greg Kroah-Hartman --- drivers/firmware/efi/cper.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/firmware/efi/cper.c b/drivers/firmware/efi/cper.c index 79ba688a64f8da..322c6bdefb6110 100644 --- a/drivers/firmware/efi/cper.c +++ b/drivers/firmware/efi/cper.c @@ -162,7 +162,7 @@ int cper_bits_to_str(char *buf, int buf_size, unsigned long bits, len -= size; str += size; } - return len - buf_size; + return buf_size - len; } EXPORT_SYMBOL_GPL(cper_bits_to_str); From a685f246ad7909b4e24938d570ba5eaad81abfbd Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Wed, 31 Dec 2025 11:10:57 +0100 Subject: [PATCH 2407/3902] nvme-apple: add "apple,t8103-nvme-ans2" as compatible commit 7d3fa7e954934fbda0a017ac1c305b7b10ecceef upstream. After discussion with the devicetree maintainers we agreed to not extend lists with the generic compatible "apple,nvme-ans2" anymore [1]. Add "apple,t8103-nvme-ans2" as fallback compatible as it is the SoC the driver and bindings were written for. [1]: https://lore.kernel.org/asahi/12ab93b7-1fc2-4ce0-926e-c8141cfe81bf@kernel.org/ Cc: stable@vger.kernel.org # v6.18+ Fixes: 5bd2927aceba ("nvme-apple: Add initial Apple SoC NVMe driver") Reviewed-by: Neal Gompa Reviewed-by: Christoph Hellwig Signed-off-by: Janne Grunau Signed-off-by: Keith Busch Signed-off-by: Greg Kroah-Hartman --- drivers/nvme/host/apple.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/nvme/host/apple.c b/drivers/nvme/host/apple.c index f35d3f71d14f32..63819304290a1c 100644 --- a/drivers/nvme/host/apple.c +++ b/drivers/nvme/host/apple.c @@ -1703,6 +1703,7 @@ static const struct apple_nvme_hw apple_nvme_t8103_hw = { static const struct of_device_id apple_nvme_of_match[] = { { .compatible = "apple,t8015-nvme-ans2", .data = &apple_nvme_t8015_hw }, + { .compatible = "apple,t8103-nvme-ans2", .data = &apple_nvme_t8103_hw }, { .compatible = "apple,nvme-ans2", .data = &apple_nvme_t8103_hw }, {}, }; From c6a38672067b9332eb6946f7cd04784b2ec9ac9a Mon Sep 17 00:00:00 2001 From: Andreas Gruenbacher Date: Mon, 12 Jan 2026 11:47:35 +0100 Subject: [PATCH 2408/3902] Revert "gfs2: Fix use of bio_chain" commit 469d71512d135907bf5ea0972dfab8c420f57848 upstream. This reverts commit 8a157e0a0aa5143b5d94201508c0ca1bb8cfb941. That commit incorrectly assumed that the bio_chain() arguments were swapped in gfs2. However, gfs2 intentionally constructs bio chains so that the first bio's bi_end_io callback is invoked when all bios in the chain have completed, unlike bio chains where the last bio's callback is invoked. Fixes: 8a157e0a0aa5 ("gfs2: Fix use of bio_chain") Cc: stable@vger.kernel.org Signed-off-by: Andreas Gruenbacher Signed-off-by: Greg Kroah-Hartman --- fs/gfs2/lops.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fs/gfs2/lops.c b/fs/gfs2/lops.c index 914d03f6c4e829..9c8c305a75c468 100644 --- a/fs/gfs2/lops.c +++ b/fs/gfs2/lops.c @@ -487,7 +487,7 @@ static struct bio *gfs2_chain_bio(struct bio *prev, unsigned int nr_iovecs) new = bio_alloc(prev->bi_bdev, nr_iovecs, prev->bi_opf, GFP_NOIO); bio_clone_blkg_association(new, prev); new->bi_iter.bi_sector = bio_end_sector(prev); - bio_chain(prev, new); + bio_chain(new, prev); submit_bio(prev); return new; } From eea6f395ca502c4528314c8112da9b5d65f685eb Mon Sep 17 00:00:00 2001 From: Sean Christopherson Date: Wed, 31 Dec 2025 16:43:15 +0100 Subject: [PATCH 2409/3902] x86/fpu: Clear XSTATE_BV[i] in guest XSAVE state whenever XFD[i]=1 commit b45f721775947a84996deb5c661602254ce25ce6 upstream. When loading guest XSAVE state via KVM_SET_XSAVE, and when updating XFD in response to a guest WRMSR, clear XFD-disabled features in the saved (or to be restored) XSTATE_BV to ensure KVM doesn't attempt to load state for features that are disabled via the guest's XFD. Because the kernel executes XRSTOR with the guest's XFD, saving XSTATE_BV[i]=1 with XFD[i]=1 will cause XRSTOR to #NM and panic the kernel. E.g. if fpu_update_guest_xfd() sets XFD without clearing XSTATE_BV: ------------[ cut here ]------------ WARNING: arch/x86/kernel/traps.c:1524 at exc_device_not_available+0x101/0x110, CPU#29: amx_test/848 Modules linked in: kvm_intel kvm irqbypass CPU: 29 UID: 1000 PID: 848 Comm: amx_test Not tainted 6.19.0-rc2-ffa07f7fd437-x86_amx_nm_xfd_non_init-vm #171 NONE Hardware name: QEMU Standard PC (Q35 + ICH9, 2009), BIOS 0.0.0 02/06/2015 RIP: 0010:exc_device_not_available+0x101/0x110 Call Trace: asm_exc_device_not_available+0x1a/0x20 RIP: 0010:restore_fpregs_from_fpstate+0x36/0x90 switch_fpu_return+0x4a/0xb0 kvm_arch_vcpu_ioctl_run+0x1245/0x1e40 [kvm] kvm_vcpu_ioctl+0x2c3/0x8f0 [kvm] __x64_sys_ioctl+0x8f/0xd0 do_syscall_64+0x62/0x940 entry_SYSCALL_64_after_hwframe+0x4b/0x53 ---[ end trace 0000000000000000 ]--- This can happen if the guest executes WRMSR(MSR_IA32_XFD) to set XFD[18] = 1, and a host IRQ triggers kernel_fpu_begin() prior to the vmexit handler's call to fpu_update_guest_xfd(). and if userspace stuffs XSTATE_BV[i]=1 via KVM_SET_XSAVE: ------------[ cut here ]------------ WARNING: arch/x86/kernel/traps.c:1524 at exc_device_not_available+0x101/0x110, CPU#14: amx_test/867 Modules linked in: kvm_intel kvm irqbypass CPU: 14 UID: 1000 PID: 867 Comm: amx_test Not tainted 6.19.0-rc2-2dace9faccd6-x86_amx_nm_xfd_non_init-vm #168 NONE Hardware name: QEMU Standard PC (Q35 + ICH9, 2009), BIOS 0.0.0 02/06/2015 RIP: 0010:exc_device_not_available+0x101/0x110 Call Trace: asm_exc_device_not_available+0x1a/0x20 RIP: 0010:restore_fpregs_from_fpstate+0x36/0x90 fpu_swap_kvm_fpstate+0x6b/0x120 kvm_load_guest_fpu+0x30/0x80 [kvm] kvm_arch_vcpu_ioctl_run+0x85/0x1e40 [kvm] kvm_vcpu_ioctl+0x2c3/0x8f0 [kvm] __x64_sys_ioctl+0x8f/0xd0 do_syscall_64+0x62/0x940 entry_SYSCALL_64_after_hwframe+0x4b/0x53 ---[ end trace 0000000000000000 ]--- The new behavior is consistent with the AMX architecture. Per Intel's SDM, XSAVE saves XSTATE_BV as '0' for components that are disabled via XFD (and non-compacted XSAVE saves the initial configuration of the state component): If XSAVE, XSAVEC, XSAVEOPT, or XSAVES is saving the state component i, the instruction does not generate #NM when XCR0[i] = IA32_XFD[i] = 1; instead, it operates as if XINUSE[i] = 0 (and the state component was in its initial state): it saves bit i of XSTATE_BV field of the XSAVE header as 0; in addition, XSAVE saves the initial configuration of the state component (the other instructions do not save state component i). Alternatively, KVM could always do XRSTOR with XFD=0, e.g. by using a constant XFD based on the set of enabled features when XSAVEing for a struct fpu_guest. However, having XSTATE_BV[i]=1 for XFD-disabled features can only happen in the above interrupt case, or in similar scenarios involving preemption on preemptible kernels, because fpu_swap_kvm_fpstate()'s call to save_fpregs_to_fpstate() saves the outgoing FPU state with the current XFD; and that is (on all but the first WRMSR to XFD) the guest XFD. Therefore, XFD can only go out of sync with XSTATE_BV in the above interrupt case, or in similar scenarios involving preemption on preemptible kernels, and it we can consider it (de facto) part of KVM ABI that KVM_GET_XSAVE returns XSTATE_BV[i]=0 for XFD-disabled features. Reported-by: Paolo Bonzini Cc: stable@vger.kernel.org Fixes: 820a6ee944e7 ("kvm: x86: Add emulation for IA32_XFD", 2022-01-14) Signed-off-by: Sean Christopherson [Move clearing of XSTATE_BV from fpu_copy_uabi_to_guest_fpstate to kvm_vcpu_ioctl_x86_set_xsave. - Paolo] Reviewed-by: Binbin Wu Signed-off-by: Paolo Bonzini Signed-off-by: Greg Kroah-Hartman --- arch/x86/kernel/fpu/core.c | 32 +++++++++++++++++++++++++++++--- arch/x86/kvm/x86.c | 9 +++++++++ 2 files changed, 38 insertions(+), 3 deletions(-) diff --git a/arch/x86/kernel/fpu/core.c b/arch/x86/kernel/fpu/core.c index e88eacb1b5bbfa..9badb21e5a5bce 100644 --- a/arch/x86/kernel/fpu/core.c +++ b/arch/x86/kernel/fpu/core.c @@ -318,10 +318,29 @@ EXPORT_SYMBOL_GPL(fpu_enable_guest_xfd_features); #ifdef CONFIG_X86_64 void fpu_update_guest_xfd(struct fpu_guest *guest_fpu, u64 xfd) { + struct fpstate *fpstate = guest_fpu->fpstate; + fpregs_lock(); - guest_fpu->fpstate->xfd = xfd; - if (guest_fpu->fpstate->in_use) - xfd_update_state(guest_fpu->fpstate); + + /* + * KVM's guest ABI is that setting XFD[i]=1 *can* immediately revert the + * save state to its initial configuration. Likewise, KVM_GET_XSAVE does + * the same as XSAVE and returns XSTATE_BV[i]=0 whenever XFD[i]=1. + * + * If the guest's FPU state is in hardware, just update XFD: the XSAVE + * in fpu_swap_kvm_fpstate will clear XSTATE_BV[i] whenever XFD[i]=1. + * + * If however the guest's FPU state is NOT resident in hardware, clear + * disabled components in XSTATE_BV now, or a subsequent XRSTOR will + * attempt to load disabled components and generate #NM _in the host_. + */ + if (xfd && test_thread_flag(TIF_NEED_FPU_LOAD)) + fpstate->regs.xsave.header.xfeatures &= ~xfd; + + fpstate->xfd = xfd; + if (fpstate->in_use) + xfd_update_state(fpstate); + fpregs_unlock(); } EXPORT_SYMBOL_GPL(fpu_update_guest_xfd); @@ -429,6 +448,13 @@ int fpu_copy_uabi_to_guest_fpstate(struct fpu_guest *gfpu, const void *buf, if (ustate->xsave.header.xfeatures & ~xcr0) return -EINVAL; + /* + * Disabled features must be in their initial state, otherwise XRSTOR + * causes an exception. + */ + if (WARN_ON_ONCE(ustate->xsave.header.xfeatures & kstate->xfd)) + return -EINVAL; + /* * Nullify @vpkru to preserve its current value if PKRU's bit isn't set * in the header. KVM's odd ABI is to leave PKRU untouched in this diff --git a/arch/x86/kvm/x86.c b/arch/x86/kvm/x86.c index 19d2d6d9e64ab6..43fb2a05a91c96 100644 --- a/arch/x86/kvm/x86.c +++ b/arch/x86/kvm/x86.c @@ -5842,9 +5842,18 @@ static int kvm_vcpu_ioctl_x86_get_xsave(struct kvm_vcpu *vcpu, static int kvm_vcpu_ioctl_x86_set_xsave(struct kvm_vcpu *vcpu, struct kvm_xsave *guest_xsave) { + union fpregs_state *xstate = (union fpregs_state *)guest_xsave->region; + if (fpstate_is_confidential(&vcpu->arch.guest_fpu)) return vcpu->kvm->arch.has_protected_state ? -EINVAL : 0; + /* + * For backwards compatibility, do not expect disabled features to be in + * their initial state. XSTATE_BV[i] must still be cleared whenever + * XFD[i]=1, or XRSTOR would cause a #NM. + */ + xstate->xsave.header.xfeatures &= ~vcpu->arch.guest_fpu.fpstate->xfd; + return fpu_copy_uabi_to_guest_fpstate(&vcpu->arch.guest_fpu, guest_xsave->region, kvm_caps.supported_xcr0, From 6711f723018ccdf231be9b6c49ba2d6ebbb3a4e5 Mon Sep 17 00:00:00 2001 From: Alice Ryhl Date: Mon, 5 Jan 2026 10:44:06 +0000 Subject: [PATCH 2410/3902] rust: bitops: fix missing _find_* functions on 32-bit ARM commit 6a069876eb1402478900ee0eb7d7fe276bb1f4e3 upstream. On 32-bit ARM, you may encounter linker errors such as this one: ld.lld: error: undefined symbol: _find_next_zero_bit >>> referenced by rust_binder_main.43196037ba7bcee1-cgu.0 >>> drivers/android/binder/rust_binder_main.o:(::insert_or_update_handle) in archive vmlinux.a >>> referenced by rust_binder_main.43196037ba7bcee1-cgu.0 >>> drivers/android/binder/rust_binder_main.o:(::insert_or_update_handle) in archive vmlinux.a This error occurs because even though the functions are declared by include/linux/find.h, the definition is #ifdef'd out on 32-bit ARM. This is because arch/arm/include/asm/bitops.h contains: #define find_first_zero_bit(p,sz) _find_first_zero_bit_le(p,sz) #define find_next_zero_bit(p,sz,off) _find_next_zero_bit_le(p,sz,off) #define find_first_bit(p,sz) _find_first_bit_le(p,sz) #define find_next_bit(p,sz,off) _find_next_bit_le(p,sz,off) And the underscore-prefixed function is conditional on #ifndef of the non-underscore-prefixed name, but the declaration in find.h is *not* conditional on that #ifndef. To fix the linker error, we ensure that the symbols in question exist when compiling Rust code. We do this by defining them in rust/helpers/ whenever the normal definition is #ifndef'd out. Note that these helpers are somewhat unusual in that they do not have the rust_helper_ prefix that most helpers have. Adding the rust_helper_ prefix does not compile, as 'bindings::_find_next_zero_bit()' will result in a call to a symbol called _find_next_zero_bit as defined by include/linux/find.h rather than a symbol with the rust_helper_ prefix. This is because when a symbol is present in both include/ and rust/helpers/, the one from include/ wins under the assumption that the current configuration is one where that helper is unnecessary. This heuristic fails for _find_next_zero_bit() because the header file always declares it even if the symbol does not exist. The functions still use the __rust_helper annotation. This lets the wrapper function be inlined into Rust code even if full kernel LTO is not used once the patch series for that feature lands. Yury: arches are free to implement they own find_bit() functions. Most rely on generic implementation, but arm32 and m86k - not; so they require custom handling. Alice confirmed it fixes the build for both. Cc: stable@vger.kernel.org Fixes: 6cf93a9ed39e ("rust: add bindings for bitops.h") Reported-by: Andreas Hindborg Closes: https://rust-for-linux.zulipchat.com/#narrow/channel/x/topic/x/near/561677301 Tested-by: Andreas Hindborg Reviewed-by: Dirk Behme Signed-off-by: Alice Ryhl Signed-off-by: Yury Norov (NVIDIA) Signed-off-by: Greg Kroah-Hartman --- rust/helpers/bitops.c | 42 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 42 insertions(+) diff --git a/rust/helpers/bitops.c b/rust/helpers/bitops.c index 5d0861d29d3f0d..e79ef9e6d98f96 100644 --- a/rust/helpers/bitops.c +++ b/rust/helpers/bitops.c @@ -1,6 +1,7 @@ // SPDX-License-Identifier: GPL-2.0 #include +#include void rust_helper___set_bit(unsigned long nr, unsigned long *addr) { @@ -21,3 +22,44 @@ void rust_helper_clear_bit(unsigned long nr, volatile unsigned long *addr) { clear_bit(nr, addr); } + +/* + * The rust_helper_ prefix is intentionally omitted below so that the + * declarations in include/linux/find.h are compatible with these helpers. + * + * Note that the below #ifdefs mean that the helper is only created if C does + * not provide a definition. + */ +#ifdef find_first_zero_bit +__rust_helper +unsigned long _find_first_zero_bit(const unsigned long *p, unsigned long size) +{ + return find_first_zero_bit(p, size); +} +#endif /* find_first_zero_bit */ + +#ifdef find_next_zero_bit +__rust_helper +unsigned long _find_next_zero_bit(const unsigned long *addr, + unsigned long size, unsigned long offset) +{ + return find_next_zero_bit(addr, size, offset); +} +#endif /* find_next_zero_bit */ + +#ifdef find_first_bit +__rust_helper +unsigned long _find_first_bit(const unsigned long *addr, unsigned long size) +{ + return find_first_bit(addr, size); +} +#endif /* find_first_bit */ + +#ifdef find_next_bit +__rust_helper +unsigned long _find_next_bit(const unsigned long *addr, unsigned long size, + unsigned long offset) +{ + return find_next_bit(addr, size, offset); +} +#endif /* find_next_bit */ From afd7591a4ca92f2853eebe8668a64019e97003c2 Mon Sep 17 00:00:00 2001 From: Johan Hovold Date: Fri, 2 Jan 2026 12:14:12 +0100 Subject: [PATCH 2411/3902] ASoC: codecs: wsa884x: fix codec initialisation commit 120f3e6ff76209ee2f62a64e5e7e9d70274df42b upstream. The soundwire update_status() callback may be called multiple times with the same ATTACHED status but initialisation should only be done when transitioning from UNATTACHED to ATTACHED. Fix the inverted hw_init flag which was set to false instead of true after initialisation which defeats its purpose and may result in repeated unnecessary initialisation. Similarly, the initial state of the flag was also inverted so that the codec would only be initialised and brought out of regmap cache only mode if its status first transitions to UNATTACHED. Fixes: aa21a7d4f68a ("ASoC: codecs: wsa884x: Add WSA884x family of speakers") Cc: stable@vger.kernel.org # 6.5 Cc: Krzysztof Kozlowski Signed-off-by: Johan Hovold Reviewed-by: Krzysztof Kozlowski Tested-by: Krzysztof Kozlowski Reviewed-by: Srinivas Kandagatla Link: https://patch.msgid.link/20260102111413.9605-4-johan@kernel.org Signed-off-by: Mark Brown Signed-off-by: Greg Kroah-Hartman --- sound/soc/codecs/wsa884x.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/sound/soc/codecs/wsa884x.c b/sound/soc/codecs/wsa884x.c index 2484d4b8e2d947..5ecd7962d77ae1 100644 --- a/sound/soc/codecs/wsa884x.c +++ b/sound/soc/codecs/wsa884x.c @@ -1534,7 +1534,7 @@ static void wsa884x_init(struct wsa884x_priv *wsa884x) wsa884x_set_gain_parameters(wsa884x); - wsa884x->hw_init = false; + wsa884x->hw_init = true; } static int wsa884x_update_status(struct sdw_slave *slave, @@ -2109,7 +2109,6 @@ static int wsa884x_probe(struct sdw_slave *pdev, /* Start in cache-only until device is enumerated */ regcache_cache_only(wsa884x->regmap, true); - wsa884x->hw_init = true; if (IS_REACHABLE(CONFIG_HWMON)) { struct device *hwmon; From 67dfd9e8567cc4a1063013bdd14f45ead6ab3297 Mon Sep 17 00:00:00 2001 From: Johan Hovold Date: Fri, 2 Jan 2026 12:14:10 +0100 Subject: [PATCH 2412/3902] ASoC: codecs: wsa883x: fix unnecessary initialisation commit 49aadf830eb048134d33ad7329d92ecff45d8dbb upstream. The soundwire update_status() callback may be called multiple times with the same ATTACHED status but initialisation should only be done when transitioning from UNATTACHED to ATTACHED. This avoids repeated initialisation of the codecs during boot of machines like the Lenovo ThinkPad X13s: [ 11.614523] wsa883x-codec sdw:1:0:0217:0202:00:1: WSA883X Version 1_1, Variant: WSA8835_V2 [ 11.618022] wsa883x-codec sdw:1:0:0217:0202:00:1: WSA883X Version 1_1, Variant: WSA8835_V2 [ 11.621377] wsa883x-codec sdw:1:0:0217:0202:00:1: WSA883X Version 1_1, Variant: WSA8835_V2 [ 11.624065] wsa883x-codec sdw:1:0:0217:0202:00:1: WSA883X Version 1_1, Variant: WSA8835_V2 [ 11.631382] wsa883x-codec sdw:1:0:0217:0202:00:2: WSA883X Version 1_1, Variant: WSA8835_V2 [ 11.634424] wsa883x-codec sdw:1:0:0217:0202:00:2: WSA883X Version 1_1, Variant: WSA8835_V2 Fixes: 43b8c7dc85a1 ("ASoC: codecs: add wsa883x amplifier support") Cc: stable@vger.kernel.org # 6.0 Cc: Srinivas Kandagatla Signed-off-by: Johan Hovold Reviewed-by: Krzysztof Kozlowski Reviewed-by: Srinivas Kandagatla Link: https://patch.msgid.link/20260102111413.9605-2-johan@kernel.org Signed-off-by: Mark Brown Signed-off-by: Greg Kroah-Hartman --- sound/soc/codecs/wsa883x.c | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/sound/soc/codecs/wsa883x.c b/sound/soc/codecs/wsa883x.c index ca4520ade79aa2..5f2ced650dcc81 100644 --- a/sound/soc/codecs/wsa883x.c +++ b/sound/soc/codecs/wsa883x.c @@ -475,6 +475,7 @@ struct wsa883x_priv { int active_ports; int dev_mode; int comp_offset; + bool hw_init; /* * Protects temperature reading code (related to speaker protection) and * fields: temperature and pa_on. @@ -1043,6 +1044,9 @@ static int wsa883x_init(struct wsa883x_priv *wsa883x) struct regmap *regmap = wsa883x->regmap; int variant, version, ret; + if (wsa883x->hw_init) + return 0; + ret = regmap_read(regmap, WSA883X_OTP_REG_0, &variant); if (ret) return ret; @@ -1085,6 +1089,8 @@ static int wsa883x_init(struct wsa883x_priv *wsa883x) wsa883x->comp_offset); } + wsa883x->hw_init = true; + return 0; } @@ -1093,6 +1099,9 @@ static int wsa883x_update_status(struct sdw_slave *slave, { struct wsa883x_priv *wsa883x = dev_get_drvdata(&slave->dev); + if (status == SDW_SLAVE_UNATTACHED) + wsa883x->hw_init = false; + if (status == SDW_SLAVE_ATTACHED && slave->dev_num > 0) return wsa883x_init(wsa883x); From a255ec07f91d4c73a361a28b7a3d82f5710245f1 Mon Sep 17 00:00:00 2001 From: Shenghao Yang Date: Wed, 31 Dec 2025 13:50:26 +0800 Subject: [PATCH 2413/3902] drm/gud: fix NULL fb and crtc dereferences on USB disconnect commit dc2d5ddb193e363187bae2ad358245642d2721fb upstream. On disconnect drm_atomic_helper_disable_all() is called which sets both the fb and crtc for a plane to NULL before invoking a commit. This causes a kernel oops on every display disconnect. Add guards for those dereferences. Cc: # 6.18.x Fixes: 73cfd166e045 ("drm/gud: Replace simple display pipe with DRM atomic helpers") Signed-off-by: Shenghao Yang Reviewed-by: Ruben Wauters Signed-off-by: Ruben Wauters Link: https://patch.msgid.link/20251231055039.44266-1-me@shenghaoyang.info Signed-off-by: Greg Kroah-Hartman --- drivers/gpu/drm/gud/gud_pipe.c | 20 ++++++++------------ 1 file changed, 8 insertions(+), 12 deletions(-) diff --git a/drivers/gpu/drm/gud/gud_pipe.c b/drivers/gpu/drm/gud/gud_pipe.c index 54d9aa9998e596..cfd66c879ae407 100644 --- a/drivers/gpu/drm/gud/gud_pipe.c +++ b/drivers/gpu/drm/gud/gud_pipe.c @@ -457,27 +457,20 @@ int gud_plane_atomic_check(struct drm_plane *plane, struct drm_plane_state *old_plane_state = drm_atomic_get_old_plane_state(state, plane); struct drm_plane_state *new_plane_state = drm_atomic_get_new_plane_state(state, plane); struct drm_crtc *crtc = new_plane_state->crtc; - struct drm_crtc_state *crtc_state; + struct drm_crtc_state *crtc_state = NULL; const struct drm_display_mode *mode; struct drm_framebuffer *old_fb = old_plane_state->fb; struct drm_connector_state *connector_state = NULL; struct drm_framebuffer *fb = new_plane_state->fb; - const struct drm_format_info *format = fb->format; + const struct drm_format_info *format; struct drm_connector *connector; unsigned int i, num_properties; struct gud_state_req *req; int idx, ret; size_t len; - if (drm_WARN_ON_ONCE(plane->dev, !fb)) - return -EINVAL; - - if (drm_WARN_ON_ONCE(plane->dev, !crtc)) - return -EINVAL; - - crtc_state = drm_atomic_get_new_crtc_state(state, crtc); - - mode = &crtc_state->mode; + if (crtc) + crtc_state = drm_atomic_get_new_crtc_state(state, crtc); ret = drm_atomic_helper_check_plane_state(new_plane_state, crtc_state, DRM_PLANE_NO_SCALING, @@ -492,6 +485,9 @@ int gud_plane_atomic_check(struct drm_plane *plane, if (old_plane_state->rotation != new_plane_state->rotation) crtc_state->mode_changed = true; + mode = &crtc_state->mode; + format = fb->format; + if (old_fb && old_fb->format != format) crtc_state->mode_changed = true; @@ -598,7 +594,7 @@ void gud_plane_atomic_update(struct drm_plane *plane, struct drm_atomic_helper_damage_iter iter; int ret, idx; - if (crtc->state->mode_changed || !crtc->state->enable) { + if (!crtc || crtc->state->mode_changed || !crtc->state->enable) { cancel_work_sync(&gdrm->work); mutex_lock(&gdrm->damage_lock); if (gdrm->fb) { From ae48108c2310f1dd700e0dbb655c2f1d92ed00fc Mon Sep 17 00:00:00 2001 From: "Gustavo A. R. Silva" Date: Sat, 10 Jan 2026 17:07:17 +0900 Subject: [PATCH 2414/3902] virtio_net: Fix misalignment bug in struct virtnet_info commit 4156c3745f06bc197094b9ee97a9584e69ed00bf upstream. Use the new TRAILING_OVERLAP() helper to fix a misalignment bug along with the following warning: drivers/net/virtio_net.c:429:46: warning: structure containing a flexible array member is not at the end of another structure [-Wflex-array-member-not-at-end] This helper creates a union between a flexible-array member (FAM) and a set of members that would otherwise follow it (in this case `u8 rss_hash_key_data[VIRTIO_NET_RSS_MAX_KEY_SIZE];`). This overlays the trailing members (rss_hash_key_data) onto the FAM (hash_key_data) while keeping the FAM and the start of MEMBERS aligned. The static_assert() ensures this alignment remains. Notice that due to tail padding in flexible `struct virtio_net_rss_config_trailer`, `rss_trailer.hash_key_data` (at offset 83 in struct virtnet_info) and `rss_hash_key_data` (at offset 84 in struct virtnet_info) are misaligned by one byte. See below: struct virtio_net_rss_config_trailer { __le16 max_tx_vq; /* 0 2 */ __u8 hash_key_length; /* 2 1 */ __u8 hash_key_data[]; /* 3 0 */ /* size: 4, cachelines: 1, members: 3 */ /* padding: 1 */ /* last cacheline: 4 bytes */ }; struct virtnet_info { ... struct virtio_net_rss_config_trailer rss_trailer; /* 80 4 */ /* XXX last struct has 1 byte of padding */ u8 rss_hash_key_data[40]; /* 84 40 */ ... /* size: 832, cachelines: 13, members: 48 */ /* sum members: 801, holes: 8, sum holes: 31 */ /* paddings: 2, sum paddings: 5 */ }; After changes, those members are correctly aligned at offset 795: struct virtnet_info { ... union { struct virtio_net_rss_config_trailer rss_trailer; /* 792 4 */ struct { unsigned char __offset_to_hash_key_data[3]; /* 792 3 */ u8 rss_hash_key_data[40]; /* 795 40 */ }; /* 792 43 */ }; /* 792 44 */ ... /* size: 840, cachelines: 14, members: 47 */ /* sum members: 801, holes: 8, sum holes: 35 */ /* padding: 4 */ /* paddings: 1, sum paddings: 4 */ /* last cacheline: 8 bytes */ }; As a result, the RSS key passed to the device is shifted by 1 byte: the last byte is cut off, and instead a (possibly uninitialized) byte is added at the beginning. As a last note `struct virtio_net_rss_config_hdr *rss_hdr;` is also moved to the end, since it seems those three members should stick around together. :) Cc: stable@vger.kernel.org Fixes: ed3100e90d0d ("virtio_net: Use new RSS config structs") Signed-off-by: Gustavo A. R. Silva Acked-by: Michael S. Tsirkin Link: https://patch.msgid.link/aWIItWq5dV9XTTCJ@kspp Signed-off-by: Paolo Abeni Signed-off-by: Greg Kroah-Hartman --- drivers/net/virtio_net.c | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/drivers/net/virtio_net.c b/drivers/net/virtio_net.c index 4e1a5291099a5e..6b3115cefc2481 100644 --- a/drivers/net/virtio_net.c +++ b/drivers/net/virtio_net.c @@ -425,9 +425,6 @@ struct virtnet_info { u16 rss_indir_table_size; u32 rss_hash_types_supported; u32 rss_hash_types_saved; - struct virtio_net_rss_config_hdr *rss_hdr; - struct virtio_net_rss_config_trailer rss_trailer; - u8 rss_hash_key_data[VIRTIO_NET_RSS_MAX_KEY_SIZE]; /* Has control virtqueue */ bool has_cvq; @@ -493,7 +490,16 @@ struct virtnet_info { struct failover *failover; u64 device_stats_cap; + + struct virtio_net_rss_config_hdr *rss_hdr; + + /* Must be last as it ends in a flexible-array member. */ + TRAILING_OVERLAP(struct virtio_net_rss_config_trailer, rss_trailer, hash_key_data, + u8 rss_hash_key_data[VIRTIO_NET_RSS_MAX_KEY_SIZE]; + ); }; +static_assert(offsetof(struct virtnet_info, rss_trailer.hash_key_data) == + offsetof(struct virtnet_info, rss_hash_key_data)); struct padded_vnet_hdr { struct virtio_net_hdr_v1_hash hdr; From 704cced8eda4453e96622ea8176434cbf68a8add Mon Sep 17 00:00:00 2001 From: Ming Lei Date: Wed, 14 Jan 2026 16:54:05 +0800 Subject: [PATCH 2415/3902] io_uring: move local task_work in exit cancel loop commit da579f05ef0faada3559e7faddf761c75cdf85e1 upstream. With IORING_SETUP_DEFER_TASKRUN, task work is queued to ctx->work_llist (local work) rather than the fallback list. During io_ring_exit_work(), io_move_task_work_from_local() was called once before the cancel loop, moving work from work_llist to fallback_llist. However, task work can be added to work_llist during the cancel loop itself. There are two cases: 1) io_kill_timeouts() is called from io_uring_try_cancel_requests() to cancel pending timeouts, and it adds task work via io_req_queue_tw_complete() for each cancelled timeout: 2) URING_CMD requests like ublk can be completed via io_uring_cmd_complete_in_task() from ublk_queue_rq() during canceling, given ublk request queue is only quiesced when canceling the 1st uring_cmd. Since io_allowed_defer_tw_run() returns false in io_ring_exit_work() (kworker != submitter_task), io_run_local_work() is never invoked, and the work_llist entries are never processed. This causes io_uring_try_cancel_requests() to loop indefinitely, resulting in 100% CPU usage in kworker threads. Fix this by moving io_move_task_work_from_local() inside the cancel loop, ensuring any work on work_llist is moved to fallback before each cancel attempt. Cc: stable@vger.kernel.org Fixes: c0e0d6ba25f1 ("io_uring: add IORING_SETUP_DEFER_TASKRUN") Signed-off-by: Ming Lei Signed-off-by: Jens Axboe Signed-off-by: Greg Kroah-Hartman --- io_uring/io_uring.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/io_uring/io_uring.c b/io_uring/io_uring.c index 60adab71ad2d26..e97c495c180656 100644 --- a/io_uring/io_uring.c +++ b/io_uring/io_uring.c @@ -3017,12 +3017,12 @@ static __cold void io_ring_exit_work(struct work_struct *work) mutex_unlock(&ctx->uring_lock); } - if (ctx->flags & IORING_SETUP_DEFER_TASKRUN) - io_move_task_work_from_local(ctx); - /* The SQPOLL thread never reaches this path */ - while (io_uring_try_cancel_requests(ctx, NULL, true, false)) + do { + if (ctx->flags & IORING_SETUP_DEFER_TASKRUN) + io_move_task_work_from_local(ctx); cond_resched(); + } while (io_uring_try_cancel_requests(ctx, NULL, true, false)); if (ctx->sq_data) { struct io_sq_data *sqd = ctx->sq_data; From df1fae862639f518190b132e2119aa98c58ea97d Mon Sep 17 00:00:00 2001 From: Jianbo Liu Date: Thu, 20 Nov 2025 05:56:09 +0200 Subject: [PATCH 2416/3902] xfrm: Fix inner mode lookup in tunnel mode GSO segmentation [ Upstream commit 3d5221af9c7711b7aec8da1298c8fc393ef6183d ] Commit 61fafbee6cfe ("xfrm: Determine inner GSO type from packet inner protocol") attempted to fix GSO segmentation by reading the inner protocol from XFRM_MODE_SKB_CB(skb)->protocol. This was incorrect because the field holds the inner L4 protocol (TCP/UDP) instead of the required tunnel protocol. Also, the memory location (shared by XFRM_SKB_CB(skb) which could be overwritten by xfrm_replay_overflow()) is prone to corruption. This combination caused the kernel to select the wrong inner mode and get the wrong address family. The correct value is in xfrm_offload(skb)->proto, which is set from the outer tunnel header's protocol field by esp[4|6]_gso_encap(). It is initialized by xfrm[4|6]_tunnel_encap_add() to either IPPROTO_IPIP or IPPROTO_IPV6, using xfrm_af2proto() and correctly reflects the inner packet's address family. Fixes: 61fafbee6cfe ("xfrm: Determine inner GSO type from packet inner protocol") Signed-off-by: Jianbo Liu Reviewed-by: Sabrina Dubroca Signed-off-by: Steffen Klassert Signed-off-by: Sasha Levin --- net/ipv4/esp4_offload.c | 4 ++-- net/ipv6/esp6_offload.c | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/net/ipv4/esp4_offload.c b/net/ipv4/esp4_offload.c index 05828d4cb6cdbb..abd77162f5e75e 100644 --- a/net/ipv4/esp4_offload.c +++ b/net/ipv4/esp4_offload.c @@ -122,8 +122,8 @@ static struct sk_buff *xfrm4_tunnel_gso_segment(struct xfrm_state *x, struct sk_buff *skb, netdev_features_t features) { - const struct xfrm_mode *inner_mode = xfrm_ip2inner_mode(x, - XFRM_MODE_SKB_CB(skb)->protocol); + struct xfrm_offload *xo = xfrm_offload(skb); + const struct xfrm_mode *inner_mode = xfrm_ip2inner_mode(x, xo->proto); __be16 type = inner_mode->family == AF_INET6 ? htons(ETH_P_IPV6) : htons(ETH_P_IP); diff --git a/net/ipv6/esp6_offload.c b/net/ipv6/esp6_offload.c index 22410243ebe88d..22895521a57d09 100644 --- a/net/ipv6/esp6_offload.c +++ b/net/ipv6/esp6_offload.c @@ -158,8 +158,8 @@ static struct sk_buff *xfrm6_tunnel_gso_segment(struct xfrm_state *x, struct sk_buff *skb, netdev_features_t features) { - const struct xfrm_mode *inner_mode = xfrm_ip2inner_mode(x, - XFRM_MODE_SKB_CB(skb)->protocol); + struct xfrm_offload *xo = xfrm_offload(skb); + const struct xfrm_mode *inner_mode = xfrm_ip2inner_mode(x, xo->proto); __be16 type = inner_mode->family == AF_INET ? htons(ETH_P_IP) : htons(ETH_P_IPV6); From b7f9587f317d9faaafb1593b4e9d6b65214f8a5e Mon Sep 17 00:00:00 2001 From: Antony Antony Date: Thu, 11 Dec 2025 11:30:27 +0100 Subject: [PATCH 2417/3902] xfrm: set ipv4 no_pmtu_disc flag only on output sa when direction is set [ Upstream commit c196def07bbc6e8306d7a274433913444b0db20a ] The XFRM_STATE_NOPMTUDISC flag is only meaningful for output SAs, but it was being applied regardless of the SA direction when the sysctl ip_no_pmtu_disc is enabled. This can unintentionally affect input SAs. Limit setting XFRM_STATE_NOPMTUDISC to output SAs when the SA direction is configured. Closes: https://github.com/strongswan/strongswan/issues/2946 Fixes: a4a87fa4e96c ("xfrm: Add Direction to the SA in or out") Signed-off-by: Antony Antony Signed-off-by: Steffen Klassert Signed-off-by: Sasha Levin --- net/xfrm/xfrm_state.c | 1 + 1 file changed, 1 insertion(+) diff --git a/net/xfrm/xfrm_state.c b/net/xfrm/xfrm_state.c index 9e14e453b55cc9..98b362d518363b 100644 --- a/net/xfrm/xfrm_state.c +++ b/net/xfrm/xfrm_state.c @@ -3151,6 +3151,7 @@ int __xfrm_init_state(struct xfrm_state *x, struct netlink_ext_ack *extack) int err; if (family == AF_INET && + (!x->dir || x->dir == XFRM_SA_DIR_OUT) && READ_ONCE(xs_net(x)->ipv4.sysctl_ip_no_pmtu_disc)) x->props.flags |= XFRM_STATE_NOPMTUDISC; From d6c75aa9d607044d1e5c8498eff0259eed356c32 Mon Sep 17 00:00:00 2001 From: Trond Myklebust Date: Mon, 8 Dec 2025 14:45:00 -0500 Subject: [PATCH 2418/3902] pNFS: Fix a deadlock when returning a delegation during open() [ Upstream commit 857bf9056291a16785ae3be1d291026b2437fc48 ] Ben Coddington reports seeing a hang in the following stack trace: 0 [ffffd0b50e1774e0] __schedule at ffffffff9ca05415 1 [ffffd0b50e177548] schedule at ffffffff9ca05717 2 [ffffd0b50e177558] bit_wait at ffffffff9ca061e1 3 [ffffd0b50e177568] __wait_on_bit at ffffffff9ca05cfb 4 [ffffd0b50e1775c8] out_of_line_wait_on_bit at ffffffff9ca05ea5 5 [ffffd0b50e177618] pnfs_roc at ffffffffc154207b [nfsv4] 6 [ffffd0b50e1776b8] _nfs4_proc_delegreturn at ffffffffc1506586 [nfsv4] 7 [ffffd0b50e177788] nfs4_proc_delegreturn at ffffffffc1507480 [nfsv4] 8 [ffffd0b50e1777f8] nfs_do_return_delegation at ffffffffc1523e41 [nfsv4] 9 [ffffd0b50e177838] nfs_inode_set_delegation at ffffffffc1524a75 [nfsv4] 10 [ffffd0b50e177888] nfs4_process_delegation at ffffffffc14f41dd [nfsv4] 11 [ffffd0b50e1778a0] _nfs4_opendata_to_nfs4_state at ffffffffc1503edf [nfsv4] 12 [ffffd0b50e1778c0] _nfs4_open_and_get_state at ffffffffc1504e56 [nfsv4] 13 [ffffd0b50e177978] _nfs4_do_open at ffffffffc15051b8 [nfsv4] 14 [ffffd0b50e1779f8] nfs4_do_open at ffffffffc150559c [nfsv4] 15 [ffffd0b50e177a80] nfs4_atomic_open at ffffffffc15057fb [nfsv4] 16 [ffffd0b50e177ad0] nfs4_file_open at ffffffffc15219be [nfsv4] 17 [ffffd0b50e177b78] do_dentry_open at ffffffff9c09e6ea 18 [ffffd0b50e177ba8] vfs_open at ffffffff9c0a082e 19 [ffffd0b50e177bd0] dentry_open at ffffffff9c0a0935 The issue is that the delegreturn is being asked to wait for a layout return that cannot complete because a state recovery was initiated. The state recovery cannot complete until the open() finishes processing the delegations it was given. The solution is to propagate the existing flags that indicate a non-blocking call to the function pnfs_roc(), so that it knows not to wait in this situation. Reported-by: Benjamin Coddington Fixes: 29ade5db1293 ("pNFS: Wait on outstanding layoutreturns to complete in pnfs_roc()") Signed-off-by: Trond Myklebust Signed-off-by: Sasha Levin --- fs/nfs/nfs4proc.c | 6 ++--- fs/nfs/pnfs.c | 58 +++++++++++++++++++++++++++++++++-------------- fs/nfs/pnfs.h | 17 ++++++-------- 3 files changed, 51 insertions(+), 30 deletions(-) diff --git a/fs/nfs/nfs4proc.c b/fs/nfs/nfs4proc.c index 3b436ba2ed3bff..3745c59f0af25f 100644 --- a/fs/nfs/nfs4proc.c +++ b/fs/nfs/nfs4proc.c @@ -3894,8 +3894,8 @@ int nfs4_do_close(struct nfs4_state *state, gfp_t gfp_mask, int wait) calldata->res.seqid = calldata->arg.seqid; calldata->res.server = server; calldata->res.lr_ret = -NFS4ERR_NOMATCHING_LAYOUT; - calldata->lr.roc = pnfs_roc(state->inode, - &calldata->lr.arg, &calldata->lr.res, msg.rpc_cred); + calldata->lr.roc = pnfs_roc(state->inode, &calldata->lr.arg, + &calldata->lr.res, msg.rpc_cred, wait); if (calldata->lr.roc) { calldata->arg.lr_args = &calldata->lr.arg; calldata->res.lr_res = &calldata->lr.res; @@ -6946,7 +6946,7 @@ static int _nfs4_proc_delegreturn(struct inode *inode, const struct cred *cred, data->inode = nfs_igrab_and_active(inode); if (data->inode || issync) { data->lr.roc = pnfs_roc(inode, &data->lr.arg, &data->lr.res, - cred); + cred, issync); if (data->lr.roc) { data->args.lr_args = &data->lr.arg; data->res.lr_res = &data->lr.res; diff --git a/fs/nfs/pnfs.c b/fs/nfs/pnfs.c index 7ce2e840217cfc..33bc6db0dc92f8 100644 --- a/fs/nfs/pnfs.c +++ b/fs/nfs/pnfs.c @@ -1533,10 +1533,9 @@ static int pnfs_layout_return_on_reboot(struct pnfs_layout_hdr *lo) PNFS_FL_LAYOUTRETURN_PRIVILEGED); } -bool pnfs_roc(struct inode *ino, - struct nfs4_layoutreturn_args *args, - struct nfs4_layoutreturn_res *res, - const struct cred *cred) +bool pnfs_roc(struct inode *ino, struct nfs4_layoutreturn_args *args, + struct nfs4_layoutreturn_res *res, const struct cred *cred, + bool sync) { struct nfs_inode *nfsi = NFS_I(ino); struct nfs_open_context *ctx; @@ -1547,7 +1546,7 @@ bool pnfs_roc(struct inode *ino, nfs4_stateid stateid; enum pnfs_iomode iomode = 0; bool layoutreturn = false, roc = false; - bool skip_read = false; + bool skip_read; if (!nfs_have_layout(ino)) return false; @@ -1560,20 +1559,14 @@ bool pnfs_roc(struct inode *ino, lo = NULL; goto out_noroc; } - pnfs_get_layout_hdr(lo); - if (test_bit(NFS_LAYOUT_RETURN_LOCK, &lo->plh_flags)) { - spin_unlock(&ino->i_lock); - rcu_read_unlock(); - wait_on_bit(&lo->plh_flags, NFS_LAYOUT_RETURN, - TASK_UNINTERRUPTIBLE); - pnfs_put_layout_hdr(lo); - goto retry; - } /* no roc if we hold a delegation */ + skip_read = false; if (nfs4_check_delegation(ino, FMODE_READ)) { - if (nfs4_check_delegation(ino, FMODE_WRITE)) + if (nfs4_check_delegation(ino, FMODE_WRITE)) { + lo = NULL; goto out_noroc; + } skip_read = true; } @@ -1582,12 +1575,43 @@ bool pnfs_roc(struct inode *ino, if (state == NULL) continue; /* Don't return layout if there is open file state */ - if (state->state & FMODE_WRITE) + if (state->state & FMODE_WRITE) { + lo = NULL; goto out_noroc; + } if (state->state & FMODE_READ) skip_read = true; } + if (skip_read) { + bool writes = false; + + list_for_each_entry(lseg, &lo->plh_segs, pls_list) { + if (lseg->pls_range.iomode != IOMODE_READ) { + writes = true; + break; + } + } + if (!writes) { + lo = NULL; + goto out_noroc; + } + } + + pnfs_get_layout_hdr(lo); + if (test_bit(NFS_LAYOUT_RETURN_LOCK, &lo->plh_flags)) { + if (!sync) { + pnfs_set_plh_return_info( + lo, skip_read ? IOMODE_RW : IOMODE_ANY, 0); + goto out_noroc; + } + spin_unlock(&ino->i_lock); + rcu_read_unlock(); + wait_on_bit(&lo->plh_flags, NFS_LAYOUT_RETURN, + TASK_UNINTERRUPTIBLE); + pnfs_put_layout_hdr(lo); + goto retry; + } list_for_each_entry_safe(lseg, next, &lo->plh_segs, pls_list) { if (skip_read && lseg->pls_range.iomode == IOMODE_READ) @@ -1627,7 +1651,7 @@ bool pnfs_roc(struct inode *ino, out_noroc: spin_unlock(&ino->i_lock); rcu_read_unlock(); - pnfs_layoutcommit_inode(ino, true); + pnfs_layoutcommit_inode(ino, sync); if (roc) { struct pnfs_layoutdriver_type *ld = NFS_SERVER(ino)->pnfs_curr_ld; if (ld->prepare_layoutreturn) diff --git a/fs/nfs/pnfs.h b/fs/nfs/pnfs.h index 91ff877185c8af..3db8f13d8fe4e5 100644 --- a/fs/nfs/pnfs.h +++ b/fs/nfs/pnfs.h @@ -303,10 +303,9 @@ int pnfs_mark_matching_lsegs_return(struct pnfs_layout_hdr *lo, u32 seq); int pnfs_mark_layout_stateid_invalid(struct pnfs_layout_hdr *lo, struct list_head *lseg_list); -bool pnfs_roc(struct inode *ino, - struct nfs4_layoutreturn_args *args, - struct nfs4_layoutreturn_res *res, - const struct cred *cred); +bool pnfs_roc(struct inode *ino, struct nfs4_layoutreturn_args *args, + struct nfs4_layoutreturn_res *res, const struct cred *cred, + bool sync); int pnfs_roc_done(struct rpc_task *task, struct nfs4_layoutreturn_args **argpp, struct nfs4_layoutreturn_res **respp, int *ret); void pnfs_roc_release(struct nfs4_layoutreturn_args *args, @@ -773,12 +772,10 @@ pnfs_layoutcommit_outstanding(struct inode *inode) return false; } - -static inline bool -pnfs_roc(struct inode *ino, - struct nfs4_layoutreturn_args *args, - struct nfs4_layoutreturn_res *res, - const struct cred *cred) +static inline bool pnfs_roc(struct inode *ino, + struct nfs4_layoutreturn_args *args, + struct nfs4_layoutreturn_res *res, + const struct cred *cred, bool sync) { return false; } From 19b4d9ab5e77843eac0429c019470c02f8710b55 Mon Sep 17 00:00:00 2001 From: Trond Myklebust Date: Wed, 31 Dec 2025 11:42:31 -0500 Subject: [PATCH 2419/3902] NFS: Fix a deadlock involving nfs_release_folio() [ Upstream commit cce0be6eb4971456b703aaeafd571650d314bcca ] Wang Zhaolong reports a deadlock involving NFSv4.1 state recovery waiting on kthreadd, which is attempting to reclaim memory by calling nfs_release_folio(). The latter cannot make progress due to state recovery being needed. It seems that the only safe thing to do here is to kick off a writeback of the folio, without waiting for completion, or else kicking off an asynchronous commit. Reported-by: Wang Zhaolong Fixes: 96780ca55e3c ("NFS: fix up nfs_release_folio() to try to release the page") Signed-off-by: Trond Myklebust Signed-off-by: Sasha Levin --- fs/nfs/file.c | 3 ++- fs/nfs/nfstrace.h | 3 +++ fs/nfs/write.c | 33 +++++++++++++++++++++++++++++++++ include/linux/nfs_fs.h | 1 + 4 files changed, 39 insertions(+), 1 deletion(-) diff --git a/fs/nfs/file.c b/fs/nfs/file.c index d020aab40c64eb..d1c138a416cfb5 100644 --- a/fs/nfs/file.c +++ b/fs/nfs/file.c @@ -511,7 +511,8 @@ static bool nfs_release_folio(struct folio *folio, gfp_t gfp) if ((current_gfp_context(gfp) & GFP_KERNEL) != GFP_KERNEL || current_is_kswapd() || current_is_kcompactd()) return false; - if (nfs_wb_folio(folio->mapping->host, folio) < 0) + if (nfs_wb_folio_reclaim(folio->mapping->host, folio) < 0 || + folio_test_private(folio)) return false; } return nfs_fscache_release_folio(folio, gfp); diff --git a/fs/nfs/nfstrace.h b/fs/nfs/nfstrace.h index 6ce55e8e6b67c0..9f9ce4a565ea68 100644 --- a/fs/nfs/nfstrace.h +++ b/fs/nfs/nfstrace.h @@ -1062,6 +1062,9 @@ DECLARE_EVENT_CLASS(nfs_folio_event_done, DEFINE_NFS_FOLIO_EVENT(nfs_aop_readpage); DEFINE_NFS_FOLIO_EVENT_DONE(nfs_aop_readpage_done); +DEFINE_NFS_FOLIO_EVENT(nfs_writeback_folio_reclaim); +DEFINE_NFS_FOLIO_EVENT_DONE(nfs_writeback_folio_reclaim_done); + DEFINE_NFS_FOLIO_EVENT(nfs_writeback_folio); DEFINE_NFS_FOLIO_EVENT_DONE(nfs_writeback_folio_done); diff --git a/fs/nfs/write.c b/fs/nfs/write.c index 336c510f375020..bf412455e8edff 100644 --- a/fs/nfs/write.c +++ b/fs/nfs/write.c @@ -2024,6 +2024,39 @@ int nfs_wb_folio_cancel(struct inode *inode, struct folio *folio) return ret; } +/** + * nfs_wb_folio_reclaim - Write back all requests on one page + * @inode: pointer to page + * @folio: pointer to folio + * + * Assumes that the folio has been locked by the caller + */ +int nfs_wb_folio_reclaim(struct inode *inode, struct folio *folio) +{ + loff_t range_start = folio_pos(folio); + size_t len = folio_size(folio); + struct writeback_control wbc = { + .sync_mode = WB_SYNC_ALL, + .nr_to_write = 0, + .range_start = range_start, + .range_end = range_start + len - 1, + .for_sync = 1, + }; + int ret; + + if (folio_test_writeback(folio)) + return -EBUSY; + if (folio_clear_dirty_for_io(folio)) { + trace_nfs_writeback_folio_reclaim(inode, range_start, len); + ret = nfs_writepage_locked(folio, &wbc); + trace_nfs_writeback_folio_reclaim_done(inode, range_start, len, + ret); + return ret; + } + nfs_commit_inode(inode, 0); + return 0; +} + /** * nfs_wb_folio - Write back all requests on one page * @inode: pointer to page diff --git a/include/linux/nfs_fs.h b/include/linux/nfs_fs.h index c585939b6cd60f..2cf490a3a239bf 100644 --- a/include/linux/nfs_fs.h +++ b/include/linux/nfs_fs.h @@ -636,6 +636,7 @@ extern int nfs_update_folio(struct file *file, struct folio *folio, extern int nfs_sync_inode(struct inode *inode); extern int nfs_wb_all(struct inode *inode); extern int nfs_wb_folio(struct inode *inode, struct folio *folio); +extern int nfs_wb_folio_reclaim(struct inode *inode, struct folio *folio); int nfs_wb_folio_cancel(struct inode *inode, struct folio *folio); extern int nfs_commit_inode(struct inode *, int); extern struct nfs_commit_data *nfs_commitdata_alloc(void); From ed5d3f2f6885eb99f729e6ffd946e3aa058bd3eb Mon Sep 17 00:00:00 2001 From: Zilin Guan Date: Thu, 25 Dec 2025 07:41:03 +0000 Subject: [PATCH 2420/3902] pnfs/flexfiles: Fix memory leak in nfs4_ff_alloc_deviceid_node() [ Upstream commit 0c728083654f0066f5e10a1d2b0bd0907af19a58 ] In nfs4_ff_alloc_deviceid_node(), if the allocation for ds_versions fails, the function jumps to the out_scratch label without freeing the already allocated dsaddrs list, leading to a memory leak. Fix this by jumping to the out_err_drain_dsaddrs label, which properly frees the dsaddrs list before cleaning up other resources. Fixes: d67ae825a59d6 ("pnfs/flexfiles: Add the FlexFile Layout Driver") Signed-off-by: Zilin Guan Signed-off-by: Trond Myklebust Signed-off-by: Sasha Levin --- fs/nfs/flexfilelayout/flexfilelayoutdev.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fs/nfs/flexfilelayout/flexfilelayoutdev.c b/fs/nfs/flexfilelayout/flexfilelayoutdev.c index c55ea8fa3bfa57..c2d8a13a9dbdd7 100644 --- a/fs/nfs/flexfilelayout/flexfilelayoutdev.c +++ b/fs/nfs/flexfilelayout/flexfilelayoutdev.c @@ -103,7 +103,7 @@ nfs4_ff_alloc_deviceid_node(struct nfs_server *server, struct pnfs_device *pdev, sizeof(struct nfs4_ff_ds_version), gfp_flags); if (!ds_versions) - goto out_scratch; + goto out_err_drain_dsaddrs; for (i = 0; i < version_count; i++) { /* 20 = version(4) + minor_version(4) + rsize(4) + wsize(4) + From 51926204465e7d2ce71c396e0403aed58140a292 Mon Sep 17 00:00:00 2001 From: Zilin Guan Date: Thu, 25 Dec 2025 08:45:26 +0000 Subject: [PATCH 2421/3902] pnfs/blocklayout: Fix memory leak in bl_parse_scsi() [ Upstream commit 5a74af51c3a6f4cd22c128b0c1c019f68fa90011 ] In bl_parse_scsi(), if the block device length is zero, the function returns immediately without releasing the file reference obtained via bl_open_path(), leading to a memory leak. Fix this by jumping to the out_blkdev_put label to ensure the file reference is properly released. Fixes: d76c769c8db4c ("pnfs/blocklayout: Don't add zero-length pnfs_block_dev") Signed-off-by: Zilin Guan Signed-off-by: Trond Myklebust Signed-off-by: Sasha Levin --- fs/nfs/blocklayout/dev.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/fs/nfs/blocklayout/dev.c b/fs/nfs/blocklayout/dev.c index ab76120705e206..134d7f760a33a6 100644 --- a/fs/nfs/blocklayout/dev.c +++ b/fs/nfs/blocklayout/dev.c @@ -417,8 +417,10 @@ bl_parse_scsi(struct nfs_server *server, struct pnfs_block_dev *d, d->map = bl_map_simple; d->pr_key = v->scsi.pr_key; - if (d->len == 0) - return -ENODEV; + if (d->len == 0) { + error = -ENODEV; + goto out_blkdev_put; + } ops = bdev->bd_disk->fops->pr_ops; if (!ops) { From 282061a7f9f3077c614166444d1f00afbe52bfe1 Mon Sep 17 00:00:00 2001 From: Sebastian Reichel Date: Tue, 14 Oct 2025 18:00:57 +0200 Subject: [PATCH 2422/3902] drm/bridge: dw-hdmi-qp: Fix spurious IRQ on resume [ Upstream commit 14adddc65340f2034751c95616861c0e888e2bb1 ] After resume from suspend to RAM, the following splash is generated if the HDMI driver is probed (independent of a connected cable): [ 1194.484052] irq 80: nobody cared (try booting with the "irqpoll" option) [ 1194.484074] CPU: 0 UID: 0 PID: 627 Comm: rtcwake Not tainted 6.17.0-rc7-g96f1a11414b3 #1 PREEMPT [ 1194.484082] Hardware name: Rockchip RK3576 EVB V10 Board (DT) [ 1194.484085] Call trace: [ 1194.484087] ... (stripped) [ 1194.484283] handlers: [ 1194.484285] [<00000000bc363dcb>] dw_hdmi_qp_main_hardirq [dw_hdmi_qp] [ 1194.484302] Disabling IRQ #80 Apparently the HDMI IP is losing part of its state while the system is suspended and generates spurious interrupts during resume. The bug has not yet been noticed, as system suspend does not yet work properly on upstream kernel with either the Rockchip RK3588 or RK3576 platform. Fixes: 128a9bf8ace2 ("drm/rockchip: Add basic RK3588 HDMI output support") Signed-off-by: Sebastian Reichel Reviewed-by: Cristian Ciocaltea Signed-off-by: Heiko Stuebner Link: https://patch.msgid.link/20251014-rockchip-hdmi-suspend-fix-v1-1-983fcbf44839@collabora.com Signed-off-by: Sasha Levin --- drivers/gpu/drm/bridge/synopsys/dw-hdmi-qp.c | 9 +++++++++ drivers/gpu/drm/rockchip/dw_hdmi_qp-rockchip.c | 12 +++++++++++- include/drm/bridge/dw_hdmi_qp.h | 1 + 3 files changed, 21 insertions(+), 1 deletion(-) diff --git a/drivers/gpu/drm/bridge/synopsys/dw-hdmi-qp.c b/drivers/gpu/drm/bridge/synopsys/dw-hdmi-qp.c index 39332c57f2c542..c85eb340e5a352 100644 --- a/drivers/gpu/drm/bridge/synopsys/dw-hdmi-qp.c +++ b/drivers/gpu/drm/bridge/synopsys/dw-hdmi-qp.c @@ -143,6 +143,7 @@ struct dw_hdmi_qp { } phy; struct regmap *regm; + int main_irq; unsigned long tmds_char_rate; }; @@ -1068,6 +1069,7 @@ struct dw_hdmi_qp *dw_hdmi_qp_bind(struct platform_device *pdev, dw_hdmi_qp_init_hw(hdmi); + hdmi->main_irq = plat_data->main_irq; ret = devm_request_threaded_irq(dev, plat_data->main_irq, dw_hdmi_qp_main_hardirq, NULL, IRQF_SHARED, dev_name(dev), hdmi); @@ -1106,9 +1108,16 @@ struct dw_hdmi_qp *dw_hdmi_qp_bind(struct platform_device *pdev, } EXPORT_SYMBOL_GPL(dw_hdmi_qp_bind); +void dw_hdmi_qp_suspend(struct device *dev, struct dw_hdmi_qp *hdmi) +{ + disable_irq(hdmi->main_irq); +} +EXPORT_SYMBOL_GPL(dw_hdmi_qp_suspend); + void dw_hdmi_qp_resume(struct device *dev, struct dw_hdmi_qp *hdmi) { dw_hdmi_qp_init_hw(hdmi); + enable_irq(hdmi->main_irq); } EXPORT_SYMBOL_GPL(dw_hdmi_qp_resume); diff --git a/drivers/gpu/drm/rockchip/dw_hdmi_qp-rockchip.c b/drivers/gpu/drm/rockchip/dw_hdmi_qp-rockchip.c index ed6e8f036f4b3d..9ac45e7bc987a6 100644 --- a/drivers/gpu/drm/rockchip/dw_hdmi_qp-rockchip.c +++ b/drivers/gpu/drm/rockchip/dw_hdmi_qp-rockchip.c @@ -597,6 +597,15 @@ static void dw_hdmi_qp_rockchip_remove(struct platform_device *pdev) component_del(&pdev->dev, &dw_hdmi_qp_rockchip_ops); } +static int __maybe_unused dw_hdmi_qp_rockchip_suspend(struct device *dev) +{ + struct rockchip_hdmi_qp *hdmi = dev_get_drvdata(dev); + + dw_hdmi_qp_suspend(dev, hdmi->hdmi); + + return 0; +} + static int __maybe_unused dw_hdmi_qp_rockchip_resume(struct device *dev) { struct rockchip_hdmi_qp *hdmi = dev_get_drvdata(dev); @@ -612,7 +621,8 @@ static int __maybe_unused dw_hdmi_qp_rockchip_resume(struct device *dev) } static const struct dev_pm_ops dw_hdmi_qp_rockchip_pm = { - SET_SYSTEM_SLEEP_PM_OPS(NULL, dw_hdmi_qp_rockchip_resume) + SET_SYSTEM_SLEEP_PM_OPS(dw_hdmi_qp_rockchip_suspend, + dw_hdmi_qp_rockchip_resume) }; struct platform_driver dw_hdmi_qp_rockchip_pltfm_driver = { diff --git a/include/drm/bridge/dw_hdmi_qp.h b/include/drm/bridge/dw_hdmi_qp.h index e9be6d507ad9cd..8955450663e530 100644 --- a/include/drm/bridge/dw_hdmi_qp.h +++ b/include/drm/bridge/dw_hdmi_qp.h @@ -28,5 +28,6 @@ struct dw_hdmi_qp_plat_data { struct dw_hdmi_qp *dw_hdmi_qp_bind(struct platform_device *pdev, struct drm_encoder *encoder, const struct dw_hdmi_qp_plat_data *plat_data); +void dw_hdmi_qp_suspend(struct device *dev, struct dw_hdmi_qp *hdmi); void dw_hdmi_qp_resume(struct device *dev, struct dw_hdmi_qp *hdmi); #endif /* __DW_HDMI_QP__ */ From a91bdd21d5efb3072beefbec13762b7722200c49 Mon Sep 17 00:00:00 2001 From: Ian Forbes Date: Fri, 14 Nov 2025 14:37:03 -0600 Subject: [PATCH 2423/3902] drm/vmwgfx: Fix KMS with 3D on HW version 10 [ Upstream commit d9186faeae6efb7d0841a5e8eb213ff4c7966614 ] HW version 10 does not have GB Surfaces so there is no backing buffer for surface backed FBs. This would result in a nullptr dereference and crash the driver causing a black screen. Fixes: 965544150d1c ("drm/vmwgfx: Refactor cursor handling") Signed-off-by: Ian Forbes Reviewed-by: Zack Rusin Signed-off-by: Zack Rusin Link: https://patch.msgid.link/20251114203703.1946616-1-ian.forbes@broadcom.com Signed-off-by: Sasha Levin --- drivers/gpu/drm/vmwgfx/vmwgfx_kms.c | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/drivers/gpu/drm/vmwgfx/vmwgfx_kms.c b/drivers/gpu/drm/vmwgfx/vmwgfx_kms.c index 54ea1b513950a8..535d844191e7a6 100644 --- a/drivers/gpu/drm/vmwgfx/vmwgfx_kms.c +++ b/drivers/gpu/drm/vmwgfx/vmwgfx_kms.c @@ -763,13 +763,15 @@ static struct drm_framebuffer *vmw_kms_fb_create(struct drm_device *dev, return ERR_PTR(ret); } - ttm_bo_reserve(&bo->tbo, false, false, NULL); - ret = vmw_bo_dirty_add(bo); - if (!ret && surface && surface->res.func->dirty_alloc) { - surface->res.coherent = true; - ret = surface->res.func->dirty_alloc(&surface->res); + if (bo) { + ttm_bo_reserve(&bo->tbo, false, false, NULL); + ret = vmw_bo_dirty_add(bo); + if (!ret && surface && surface->res.func->dirty_alloc) { + surface->res.coherent = true; + ret = surface->res.func->dirty_alloc(&surface->res); + } + ttm_bo_unreserve(&bo->tbo); } - ttm_bo_unreserve(&bo->tbo); return &vfb->base; } From 0674f22eef471f9d1f9ae69bc63e9e14f6ad0049 Mon Sep 17 00:00:00 2001 From: Ian Forbes Date: Wed, 7 Jan 2026 09:20:59 -0600 Subject: [PATCH 2424/3902] drm/vmwgfx: Merge vmw_bo_release and vmw_bo_free functions [ Upstream commit 37a0cff4551c14aca4cfa6ef3f2f0e0f61d66825 ] Some of the warnings need to be reordered between these two functions in order to be correct. This has happened multiple times. Merging them solves this problem once and for all. Fixes: d6667f0ddf46 ("drm/vmwgfx: Fix handling of dumb buffers") Signed-off-by: Ian Forbes Signed-off-by: Zack Rusin Link: https://patch.msgid.link/20260107152059.3048329-1-ian.forbes@broadcom.com Signed-off-by: Sasha Levin --- drivers/gpu/drm/vmwgfx/vmwgfx_bo.c | 22 ++++++++-------------- 1 file changed, 8 insertions(+), 14 deletions(-) diff --git a/drivers/gpu/drm/vmwgfx/vmwgfx_bo.c b/drivers/gpu/drm/vmwgfx/vmwgfx_bo.c index f031a312c78356..b22887e8c8815d 100644 --- a/drivers/gpu/drm/vmwgfx/vmwgfx_bo.c +++ b/drivers/gpu/drm/vmwgfx/vmwgfx_bo.c @@ -32,9 +32,15 @@ #include -static void vmw_bo_release(struct vmw_bo *vbo) +/** + * vmw_bo_free - vmw_bo destructor + * + * @bo: Pointer to the embedded struct ttm_buffer_object + */ +static void vmw_bo_free(struct ttm_buffer_object *bo) { struct vmw_resource *res; + struct vmw_bo *vbo = to_vmw_bo(&bo->base); WARN_ON(kref_read(&vbo->tbo.base.refcount) != 0); vmw_bo_unmap(vbo); @@ -62,20 +68,8 @@ static void vmw_bo_release(struct vmw_bo *vbo) } vmw_surface_unreference(&vbo->dumb_surface); } - drm_gem_object_release(&vbo->tbo.base); -} - -/** - * vmw_bo_free - vmw_bo destructor - * - * @bo: Pointer to the embedded struct ttm_buffer_object - */ -static void vmw_bo_free(struct ttm_buffer_object *bo) -{ - struct vmw_bo *vbo = to_vmw_bo(&bo->base); - WARN_ON(!RB_EMPTY_ROOT(&vbo->res_tree)); - vmw_bo_release(vbo); + drm_gem_object_release(&vbo->tbo.base); WARN_ON(vbo->dirty); kfree(vbo); } From d6ea0b7394a443fb6ddd1a3619ccc2f0781a900c Mon Sep 17 00:00:00 2001 From: Trond Myklebust Date: Fri, 2 Jan 2026 18:55:08 -0500 Subject: [PATCH 2425/3902] NFS/localio: Deal with page bases that are > PAGE_SIZE [ Upstream commit 60699ab7cbf0a4eb19929cce243002b39c67917d ] When resending requests, etc, the page base can quickly grow larger than the page size. Fixes: 091bdcfcece0 ("nfs/localio: refactor iocb and iov_iter_bvec initialization") Signed-off-by: Trond Myklebust Reviewed-by: Mike Snitzer Signed-off-by: Sasha Levin --- fs/nfs/localio.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/fs/nfs/localio.c b/fs/nfs/localio.c index ed2a7efaf8f20c..f537bc3386bf26 100644 --- a/fs/nfs/localio.c +++ b/fs/nfs/localio.c @@ -461,6 +461,8 @@ nfs_local_iters_init(struct nfs_local_kiocb *iocb, int rw) v = 0; total = hdr->args.count; base = hdr->args.pgbase; + pagevec += base >> PAGE_SHIFT; + base &= ~PAGE_MASK; while (total && v < hdr->page_array.npages) { len = min_t(size_t, total, PAGE_SIZE - base); bvec_set_page(&iocb->bvec[v], *pagevec, len, base); From 47244c00bc5456d57c2e4d2d82ab4e52adf050cc Mon Sep 17 00:00:00 2001 From: Andy Yan Date: Fri, 18 Jul 2025 14:41:13 +0800 Subject: [PATCH 2426/3902] drm/rockchip: vop2: Add delay between poll registers [ Upstream commit 9fae82450d8a5f9c8fa016cd15186e975609b2ac ] According to the implementation of read_poll_timeout_atomic, if the delay time is 0, it will only use a simple loop based on timeout_us to decrement the count. Therefore, the final timeout time will differ significantly from the set timeout time. So, here we set a specific delay time to ensure that the calculation of the timeout duration is accurate. Fixes: 3e89a8c68354 ("drm/rockchip: vop2: Fix the update of LAYER/PORT select registers when there are multi display output on rk3588/rk3568") Signed-off-by: Andy Yan Signed-off-by: Heiko Stuebner Link: https://patch.msgid.link/20250718064120.8811-1-andyshrk@163.com Signed-off-by: Sasha Levin --- drivers/gpu/drm/rockchip/rockchip_vop2_reg.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/gpu/drm/rockchip/rockchip_vop2_reg.c b/drivers/gpu/drm/rockchip/rockchip_vop2_reg.c index cd8380f0eddc84..855386a6a9f5c6 100644 --- a/drivers/gpu/drm/rockchip/rockchip_vop2_reg.c +++ b/drivers/gpu/drm/rockchip/rockchip_vop2_reg.c @@ -2104,7 +2104,7 @@ static void rk3568_vop2_wait_for_port_mux_done(struct vop2 *vop2) * Spin until the previous port_mux figuration is done. */ ret = readx_poll_timeout_atomic(rk3568_vop2_read_port_mux, vop2, port_mux_sel, - port_mux_sel == vop2->old_port_sel, 0, 50 * 1000); + port_mux_sel == vop2->old_port_sel, 10, 50 * 1000); if (ret) DRM_DEV_ERROR(vop2->dev, "wait port_mux done timeout: 0x%x--0x%x\n", port_mux_sel, vop2->old_port_sel); @@ -2124,7 +2124,7 @@ static void rk3568_vop2_wait_for_layer_cfg_done(struct vop2 *vop2, u32 cfg) * Spin until the previous layer configuration is done. */ ret = readx_poll_timeout_atomic(rk3568_vop2_read_layer_cfg, vop2, atv_layer_cfg, - atv_layer_cfg == cfg, 0, 50 * 1000); + atv_layer_cfg == cfg, 10, 50 * 1000); if (ret) DRM_DEV_ERROR(vop2->dev, "wait layer cfg done timeout: 0x%x--0x%x\n", atv_layer_cfg, cfg); From 9a88b6c3c8695b708fd83523b3fe5a93e82ef7a4 Mon Sep 17 00:00:00 2001 From: Andy Yan Date: Fri, 18 Jul 2025 14:41:14 +0800 Subject: [PATCH 2427/3902] drm/rockchip: vop2: Only wait for changed layer cfg done when there is pending cfgdone bits [ Upstream commit 7f6721b767e219343cfe9a894f5bd869ff5b9d3a ] The write of cfgdone bits always done at .atomic_flush. When userspace makes plane zpos changes of two crtc within one commit, at the .atomic_begin stage, crtcN will never receive the "layer change cfg done" event of crtcM because crtcM has not yet written "cfgdone". So only wait when there is pending cfgdone bits to avoid long timeout. Fixes: 3e89a8c68354 ("drm/rockchip: vop2: Fix the update of LAYER/PORT select registers when there are multi display output on rk3588/rk3568") Signed-off-by: Andy Yan Signed-off-by: Heiko Stuebner Link: https://patch.msgid.link/20250718064120.8811-2-andyshrk@163.com Signed-off-by: Sasha Levin --- drivers/gpu/drm/rockchip/rockchip_vop2_reg.c | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/drivers/gpu/drm/rockchip/rockchip_vop2_reg.c b/drivers/gpu/drm/rockchip/rockchip_vop2_reg.c index 855386a6a9f5c6..f3950e8476a75b 100644 --- a/drivers/gpu/drm/rockchip/rockchip_vop2_reg.c +++ b/drivers/gpu/drm/rockchip/rockchip_vop2_reg.c @@ -2144,6 +2144,7 @@ static void rk3568_vop2_setup_layer_mixer(struct vop2_video_port *vp) u8 layer_sel_id; unsigned int ofs; u32 ovl_ctrl; + u32 cfg_done; int i; struct vop2_video_port *vp0 = &vop2->vps[0]; struct vop2_video_port *vp1 = &vop2->vps[1]; @@ -2298,8 +2299,16 @@ static void rk3568_vop2_setup_layer_mixer(struct vop2_video_port *vp) rk3568_vop2_wait_for_port_mux_done(vop2); } - if (layer_sel != old_layer_sel && atv_layer_sel != old_layer_sel) - rk3568_vop2_wait_for_layer_cfg_done(vop2, vop2->old_layer_sel); + if (layer_sel != old_layer_sel && atv_layer_sel != old_layer_sel) { + cfg_done = vop2_readl(vop2, RK3568_REG_CFG_DONE); + cfg_done &= (BIT(vop2->data->nr_vps) - 1); + cfg_done &= ~BIT(vp->id); + /* + * Changes of other VPs' overlays have not taken effect + */ + if (cfg_done) + rk3568_vop2_wait_for_layer_cfg_done(vop2, vop2->old_layer_sel); + } vop2_writel(vop2, RK3568_OVL_LAYER_SEL, layer_sel); mutex_unlock(&vop2->ovl_lock); From 754cfada1bbcbeeb922e54007c3c3aaaeeacf23e Mon Sep 17 00:00:00 2001 From: Yaxiong Tian Date: Tue, 30 Dec 2025 14:15:34 +0800 Subject: [PATCH 2428/3902] PM: EM: Fix incorrect description of the cost field in struct em_perf_state [ Upstream commit 54b603f2db6b95495bc33a8f2bde80f044baff9a ] Due to commit 1b600da51073 ("PM: EM: Optimize em_cpu_energy() and remove division"), the logic for energy consumption calculation has been modified. The actual calculation of cost is 10 * power * max_frequency / frequency instead of power * max_frequency / frequency. Therefore, the comment for cost has been updated to reflect the correct content. Fixes: 1b600da51073 ("PM: EM: Optimize em_cpu_energy() and remove division") Signed-off-by: Yaxiong Tian Reviewed-by: Lukasz Luba [ rjw: Added Fixes: tag ] Link: https://patch.msgid.link/20251230061534.816894-1-tianyaxiong@kylinos.cn Signed-off-by: Rafael J. Wysocki Signed-off-by: Sasha Levin --- include/linux/energy_model.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/linux/energy_model.h b/include/linux/energy_model.h index 61d50571ad88ac..ce2db5447d2214 100644 --- a/include/linux/energy_model.h +++ b/include/linux/energy_model.h @@ -18,7 +18,7 @@ * @power: The power consumed at this level (by 1 CPU or by a registered * device). It can be a total power: static and dynamic. * @cost: The cost coefficient associated with this level, used during - * energy calculation. Equal to: power * max_frequency / frequency + * energy calculation. Equal to: 10 * power * max_frequency / frequency * @flags: see "em_perf_state flags" description below. */ struct em_perf_state { From 5584aa64e806282f4f15e61a2ce7f7b62ad81503 Mon Sep 17 00:00:00 2001 From: Eric Dumazet Date: Tue, 6 Jan 2026 17:24:26 +0000 Subject: [PATCH 2429/3902] ipv4: ip_tunnel: spread netdev_lockdep_set_classes() [ Upstream commit 872ac785e7680dac9ec7f8c5ccd4f667f49d6997 ] Inspired by yet another syzbot report. IPv6 tunnels call netdev_lockdep_set_classes() for each tunnel type, while IPv4 currently centralizes netdev_lockdep_set_classes() call from ip_tunnel_init(). Make ip_tunnel_init() a macro, so that we have different lockdep classes per tunnel type. Fixes: 0bef512012b1 ("net: add netdev_lockdep_set_classes() to virtual drivers") Reported-by: syzbot+1240b33467289f5ab50b@syzkaller.appspotmail.com Closes: https://lore.kernel.org/netdev/695d439f.050a0220.1c677c.0347.GAE@google.com/T/#u Signed-off-by: Eric Dumazet Link: https://patch.msgid.link/20260106172426.1760721-1-edumazet@google.com Signed-off-by: Jakub Kicinski Signed-off-by: Sasha Levin --- include/net/ip_tunnels.h | 13 ++++++++++++- net/ipv4/ip_tunnel.c | 5 ++--- 2 files changed, 14 insertions(+), 4 deletions(-) diff --git a/include/net/ip_tunnels.h b/include/net/ip_tunnels.h index ecae35512b9b44..4021e6a73e32b8 100644 --- a/include/net/ip_tunnels.h +++ b/include/net/ip_tunnels.h @@ -19,6 +19,7 @@ #include #include #include +#include #if IS_ENABLED(CONFIG_IPV6) #include @@ -372,7 +373,17 @@ static inline void ip_tunnel_init_flow(struct flowi4 *fl4, fl4->flowi4_flags = flow_flags; } -int ip_tunnel_init(struct net_device *dev); +int __ip_tunnel_init(struct net_device *dev); +#define ip_tunnel_init(DEV) \ +({ \ + struct net_device *__dev = (DEV); \ + int __res = __ip_tunnel_init(__dev); \ + \ + if (!__res) \ + netdev_lockdep_set_classes(__dev);\ + __res; \ +}) + void ip_tunnel_uninit(struct net_device *dev); void ip_tunnel_dellink(struct net_device *dev, struct list_head *head); struct net *ip_tunnel_get_link_net(const struct net_device *dev); diff --git a/net/ipv4/ip_tunnel.c b/net/ipv4/ip_tunnel.c index 158a30ae7c5f2f..50d0f5fe4e4c6d 100644 --- a/net/ipv4/ip_tunnel.c +++ b/net/ipv4/ip_tunnel.c @@ -1281,7 +1281,7 @@ int ip_tunnel_changelink(struct net_device *dev, struct nlattr *tb[], } EXPORT_SYMBOL_GPL(ip_tunnel_changelink); -int ip_tunnel_init(struct net_device *dev) +int __ip_tunnel_init(struct net_device *dev) { struct ip_tunnel *tunnel = netdev_priv(dev); struct iphdr *iph = &tunnel->parms.iph; @@ -1308,10 +1308,9 @@ int ip_tunnel_init(struct net_device *dev) if (tunnel->collect_md) netif_keep_dst(dev); - netdev_lockdep_set_classes(dev); return 0; } -EXPORT_SYMBOL_GPL(ip_tunnel_init); +EXPORT_SYMBOL_GPL(__ip_tunnel_init); void ip_tunnel_uninit(struct net_device *dev) { From 6c5124a60989051799037834f0a1a4b428718157 Mon Sep 17 00:00:00 2001 From: Szymon Wilczek Date: Tue, 23 Dec 2025 02:17:32 +0100 Subject: [PATCH 2430/3902] can: etas_es58x: allow partial RX URB allocation to succeed [ Upstream commit b1979778e98569c1e78c2c7f16bb24d76541ab00 ] When es58x_alloc_rx_urbs() fails to allocate the requested number of URBs but succeeds in allocating some, it returns an error code. This causes es58x_open() to return early, skipping the cleanup label 'free_urbs', which leads to the anchored URBs being leaked. As pointed out by maintainer Vincent Mailhol, the driver is designed to handle partial URB allocation gracefully. Therefore, partial allocation should not be treated as a fatal error. Modify es58x_alloc_rx_urbs() to return 0 if at least one URB has been allocated, restoring the intended behavior and preventing the leak in es58x_open(). Fixes: 8537257874e9 ("can: etas_es58x: add core support for ETAS ES58X CAN USB interfaces") Reported-by: syzbot+e8cb6691a7cf68256cb8@syzkaller.appspotmail.com Closes: https://syzkaller.appspot.com/bug?extid=e8cb6691a7cf68256cb8 Signed-off-by: Szymon Wilczek Reviewed-by: Vincent Mailhol Link: https://patch.msgid.link/20251223011732.39361-1-swilczek.lx@gmail.com Signed-off-by: Marc Kleine-Budde Signed-off-by: Sasha Levin --- drivers/net/can/usb/etas_es58x/es58x_core.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/net/can/usb/etas_es58x/es58x_core.c b/drivers/net/can/usb/etas_es58x/es58x_core.c index adc91873c083f9..6eeba9baa13179 100644 --- a/drivers/net/can/usb/etas_es58x/es58x_core.c +++ b/drivers/net/can/usb/etas_es58x/es58x_core.c @@ -1736,7 +1736,7 @@ static int es58x_alloc_rx_urbs(struct es58x_device *es58x_dev) dev_dbg(dev, "%s: Allocated %d rx URBs each of size %u\n", __func__, i, rx_buf_len); - return ret; + return 0; } /** From 374b095e265fa27465f34780e0eb162ff1bef913 Mon Sep 17 00:00:00 2001 From: Shivam Kumar Date: Sat, 13 Dec 2025 13:57:48 -0500 Subject: [PATCH 2431/3902] nvme-tcp: fix NULL pointer dereferences in nvmet_tcp_build_pdu_iovec MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [ Upstream commit 32b63acd78f577b332d976aa06b56e70d054cbba ] Commit efa56305908b ("nvmet-tcp: Fix a kernel panic when host sends an invalid H2C PDU length") added ttag bounds checking and data_offset validation in nvmet_tcp_handle_h2c_data_pdu(), but it did not validate whether the command's data structures (cmd->req.sg and cmd->iov) have been properly initialized before processing H2C_DATA PDUs. The nvmet_tcp_build_pdu_iovec() function dereferences these pointers without NULL checks. This can be triggered by sending H2C_DATA PDU immediately after the ICREQ/ICRESP handshake, before sending a CONNECT command or NVMe write command. Attack vectors that trigger NULL pointer dereferences: 1. H2C_DATA PDU sent before CONNECT → both pointers NULL 2. H2C_DATA PDU for READ command → cmd->req.sg allocated, cmd->iov NULL 3. H2C_DATA PDU for uninitialized command slot → both pointers NULL The fix validates both cmd->req.sg and cmd->iov before calling nvmet_tcp_build_pdu_iovec(). Both checks are required because: - Uninitialized commands: both NULL - READ commands: cmd->req.sg allocated, cmd->iov NULL - WRITE commands: both allocated Fixes: efa56305908b ("nvmet-tcp: Fix a kernel panic when host sends an invalid H2C PDU length") Reviewed-by: Sagi Grimberg Signed-off-by: Shivam Kumar Signed-off-by: Keith Busch Signed-off-by: Sasha Levin --- drivers/nvme/target/tcp.c | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/drivers/nvme/target/tcp.c b/drivers/nvme/target/tcp.c index 470bf37e5a6372..41b6fd05519e4c 100644 --- a/drivers/nvme/target/tcp.c +++ b/drivers/nvme/target/tcp.c @@ -982,6 +982,18 @@ static int nvmet_tcp_handle_h2c_data_pdu(struct nvmet_tcp_queue *queue) pr_err("H2CData PDU len %u is invalid\n", cmd->pdu_len); goto err_proto; } + /* + * Ensure command data structures are initialized. We must check both + * cmd->req.sg and cmd->iov because they can have different NULL states: + * - Uninitialized commands: both NULL + * - READ commands: cmd->req.sg allocated, cmd->iov NULL + * - WRITE commands: both allocated + */ + if (unlikely(!cmd->req.sg || !cmd->iov)) { + pr_err("queue %d: H2CData PDU received for invalid command state (ttag %u)\n", + queue->idx, data->ttag); + goto err_proto; + } cmd->pdu_recv = 0; nvmet_tcp_build_pdu_iovec(cmd); queue->cmd = cmd; From 48f6cec8aa56bcbca34a9d156e7578ef70529df2 Mon Sep 17 00:00:00 2001 From: Robert Richter Date: Thu, 8 Jan 2026 11:13:23 +0100 Subject: [PATCH 2432/3902] cxl/port: Fix target list setup for multiple decoders sharing the same dport [ Upstream commit 3e8aaacdad4f66641f87ab441fe644b45f8ebdff ] If a switch port has more than one decoder that is using the same downstream port, the enumeration of the target lists may fail with: # dmesg | grep target.list update_decoder_targets: cxl decoder1.0: dport3 found in target list, index 3 update_decoder_targets: cxl decoder1.0: dport2 found in target list, index 2 update_decoder_targets: cxl decoder1.0: dport0 found in target list, index 0 update_decoder_targets: cxl decoder2.0: dport3 found in target list, index 1 update_decoder_targets: cxl decoder4.0: dport3 found in target list, index 1 cxl_mem mem6: failed to find endpoint12:0000:00:01.4 in target list of decoder2.1 cxl_mem mem8: failed to find endpoint13:0000:20:01.4 in target list of decoder4.1 The case, that the same downstream port can be used in multiple target lists, is allowed and possible. Fix the update of the target list. Enumerate all children of the switch port and do not stop the iteration after the first matching target was found. With the fix applied: # dmesg | grep target.list update_decoder_targets: cxl decoder1.0: dport2 found in target list, index 2 update_decoder_targets: cxl decoder1.0: dport0 found in target list, index 0 update_decoder_targets: cxl decoder1.0: dport3 found in target list, index 3 update_decoder_targets: cxl decoder2.0: dport3 found in target list, index 1 update_decoder_targets: cxl decoder2.1: dport3 found in target list, index 1 update_decoder_targets: cxl decoder4.0: dport3 found in target list, index 1 update_decoder_targets: cxl decoder4.1: dport3 found in target list, index 1 Analyzing the conditions when this happens: 1) A dport is shared by multiple decoders. 2) The decoders have interleaving configured (ways > 1). The configuration above has the following hierarchy details (fixed version): root0 |_ | | | decoder0.1 | ways: 2 | target_list: 0,1 |_______________________________________ | | | dport0 | dport1 | | port2 port4 | | |___________________ |_____________________ | | | | | | | decoder2.0 decoder2.1 | decoder4.0 decoder4.1 | ways: 2 ways: 2 | ways: 2 ways: 2 | target_list: 2,3 target_list: 2,3 | target_list: 2,3 target_list: 2,3 |___________________ |___________________ | | | | | dport2 | dport3 | dport2 | dport3 | | | | endpoint7 endpoint12 endpoint9 endpoint13 |_ |_ |_ |_ | | | | | | | | | decoder7.0 | decoder12.0 | decoder9.0 | decoder13.0 | decoder7.2 | decoder12.2 | decoder9.2 | decoder13.2 | | | | mem3 mem5 mem6 mem8 Note: Device numbers vary for every boot. Current kernel fails to enumerate endpoint12 and endpoint13 as the target list is not updated for the second decoder. Fixes: 4f06d81e7c6a ("cxl: Defer dport allocation for switch ports") Reviewed-by: Dave Jiang Reviewed-by: Alison Schofield Reviewed-by: Jonathan Cameron Signed-off-by: Robert Richter Link: https://patch.msgid.link/20260108101324.509667-1-rrichter@amd.com Signed-off-by: Dave Jiang Signed-off-by: Sasha Levin --- drivers/cxl/core/port.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/cxl/core/port.c b/drivers/cxl/core/port.c index 8128fd2b5b317f..804e4a48540f6f 100644 --- a/drivers/cxl/core/port.c +++ b/drivers/cxl/core/port.c @@ -1591,7 +1591,7 @@ static int update_decoder_targets(struct device *dev, void *data) cxlsd->target[i] = dport; dev_dbg(dev, "dport%d found in target list, index %d\n", dport->port_id, i); - return 1; + return 0; } } From 65241e3ddda60b53a4ee3ae12721fc9ee21d5827 Mon Sep 17 00:00:00 2001 From: Filipe Manana Date: Fri, 19 Dec 2025 11:26:02 +0000 Subject: [PATCH 2433/3902] btrfs: release path before iget_failed() in btrfs_read_locked_inode() [ Upstream commit 1e1f2055ad5a7a5d548789b334a4473a7665c418 ] In btrfs_read_locked_inode() if we fail to lookup the inode, we jump to the 'out' label with a path that has a read locked leaf and then we call iget_failed(). This can result in a ABBA deadlock, since iget_failed() triggers inode eviction and that causes the release of the delayed inode, which must lock the delayed inode's mutex, and a task updating a delayed inode starts by taking the node's mutex and then modifying the inode's subvolume btree. Syzbot reported the following lockdep splat for this: ====================================================== WARNING: possible circular locking dependency detected syzkaller #0 Not tainted ------------------------------------------------------ btrfs-cleaner/8725 is trying to acquire lock: ffff0000d6826a48 (&delayed_node->mutex){+.+.}-{4:4}, at: __btrfs_release_delayed_node+0xa0/0x9b0 fs/btrfs/delayed-inode.c:290 but task is already holding lock: ffff0000dbeba878 (btrfs-tree-00){++++}-{4:4}, at: btrfs_tree_read_lock_nested+0x44/0x2ec fs/btrfs/locking.c:145 which lock already depends on the new lock. the existing dependency chain (in reverse order) is: -> #1 (btrfs-tree-00){++++}-{4:4}: __lock_release kernel/locking/lockdep.c:5574 [inline] lock_release+0x198/0x39c kernel/locking/lockdep.c:5889 up_read+0x24/0x3c kernel/locking/rwsem.c:1632 btrfs_tree_read_unlock+0xdc/0x298 fs/btrfs/locking.c:169 btrfs_tree_unlock_rw fs/btrfs/locking.h:218 [inline] btrfs_search_slot+0xa6c/0x223c fs/btrfs/ctree.c:2133 btrfs_lookup_inode+0xd8/0x38c fs/btrfs/inode-item.c:395 __btrfs_update_delayed_inode+0x124/0xed0 fs/btrfs/delayed-inode.c:1032 btrfs_update_delayed_inode fs/btrfs/delayed-inode.c:1118 [inline] __btrfs_commit_inode_delayed_items+0x15f8/0x1748 fs/btrfs/delayed-inode.c:1141 __btrfs_run_delayed_items+0x1ac/0x514 fs/btrfs/delayed-inode.c:1176 btrfs_run_delayed_items_nr+0x28/0x38 fs/btrfs/delayed-inode.c:1219 flush_space+0x26c/0xb68 fs/btrfs/space-info.c:828 do_async_reclaim_metadata_space+0x110/0x364 fs/btrfs/space-info.c:1158 btrfs_async_reclaim_metadata_space+0x90/0xd8 fs/btrfs/space-info.c:1226 process_one_work+0x7e8/0x155c kernel/workqueue.c:3263 process_scheduled_works kernel/workqueue.c:3346 [inline] worker_thread+0x958/0xed8 kernel/workqueue.c:3427 kthread+0x5fc/0x75c kernel/kthread.c:463 ret_from_fork+0x10/0x20 arch/arm64/kernel/entry.S:844 -> #0 (&delayed_node->mutex){+.+.}-{4:4}: check_prev_add kernel/locking/lockdep.c:3165 [inline] check_prevs_add kernel/locking/lockdep.c:3284 [inline] validate_chain kernel/locking/lockdep.c:3908 [inline] __lock_acquire+0x1774/0x30a4 kernel/locking/lockdep.c:5237 lock_acquire+0x14c/0x2e0 kernel/locking/lockdep.c:5868 __mutex_lock_common+0x1d0/0x2678 kernel/locking/mutex.c:598 __mutex_lock kernel/locking/mutex.c:760 [inline] mutex_lock_nested+0x2c/0x38 kernel/locking/mutex.c:812 __btrfs_release_delayed_node+0xa0/0x9b0 fs/btrfs/delayed-inode.c:290 btrfs_release_delayed_node fs/btrfs/delayed-inode.c:315 [inline] btrfs_remove_delayed_node+0x68/0x84 fs/btrfs/delayed-inode.c:1326 btrfs_evict_inode+0x578/0xe28 fs/btrfs/inode.c:5587 evict+0x414/0x928 fs/inode.c:810 iput_final fs/inode.c:1914 [inline] iput+0x95c/0xad4 fs/inode.c:1966 iget_failed+0xec/0x134 fs/bad_inode.c:248 btrfs_read_locked_inode+0xe1c/0x1234 fs/btrfs/inode.c:4101 btrfs_iget+0x1b0/0x264 fs/btrfs/inode.c:5837 btrfs_run_defrag_inode fs/btrfs/defrag.c:237 [inline] btrfs_run_defrag_inodes+0x520/0xdc4 fs/btrfs/defrag.c:309 cleaner_kthread+0x21c/0x418 fs/btrfs/disk-io.c:1516 kthread+0x5fc/0x75c kernel/kthread.c:463 ret_from_fork+0x10/0x20 arch/arm64/kernel/entry.S:844 other info that might help us debug this: Possible unsafe locking scenario: CPU0 CPU1 ---- ---- rlock(btrfs-tree-00); lock(&delayed_node->mutex); lock(btrfs-tree-00); lock(&delayed_node->mutex); *** DEADLOCK *** 1 lock held by btrfs-cleaner/8725: #0: ffff0000dbeba878 (btrfs-tree-00){++++}-{4:4}, at: btrfs_tree_read_lock_nested+0x44/0x2ec fs/btrfs/locking.c:145 stack backtrace: CPU: 0 UID: 0 PID: 8725 Comm: btrfs-cleaner Not tainted syzkaller #0 PREEMPT Hardware name: Google Google Compute Engine/Google Compute Engine, BIOS Google 10/03/2025 Call trace: show_stack+0x2c/0x3c arch/arm64/kernel/stacktrace.c:499 (C) __dump_stack+0x30/0x40 lib/dump_stack.c:94 dump_stack_lvl+0xd8/0x12c lib/dump_stack.c:120 dump_stack+0x1c/0x28 lib/dump_stack.c:129 print_circular_bug+0x324/0x32c kernel/locking/lockdep.c:2043 check_noncircular+0x154/0x174 kernel/locking/lockdep.c:2175 check_prev_add kernel/locking/lockdep.c:3165 [inline] check_prevs_add kernel/locking/lockdep.c:3284 [inline] validate_chain kernel/locking/lockdep.c:3908 [inline] __lock_acquire+0x1774/0x30a4 kernel/locking/lockdep.c:5237 lock_acquire+0x14c/0x2e0 kernel/locking/lockdep.c:5868 __mutex_lock_common+0x1d0/0x2678 kernel/locking/mutex.c:598 __mutex_lock kernel/locking/mutex.c:760 [inline] mutex_lock_nested+0x2c/0x38 kernel/locking/mutex.c:812 __btrfs_release_delayed_node+0xa0/0x9b0 fs/btrfs/delayed-inode.c:290 btrfs_release_delayed_node fs/btrfs/delayed-inode.c:315 [inline] btrfs_remove_delayed_node+0x68/0x84 fs/btrfs/delayed-inode.c:1326 btrfs_evict_inode+0x578/0xe28 fs/btrfs/inode.c:5587 evict+0x414/0x928 fs/inode.c:810 iput_final fs/inode.c:1914 [inline] iput+0x95c/0xad4 fs/inode.c:1966 iget_failed+0xec/0x134 fs/bad_inode.c:248 btrfs_read_locked_inode+0xe1c/0x1234 fs/btrfs/inode.c:4101 btrfs_iget+0x1b0/0x264 fs/btrfs/inode.c:5837 btrfs_run_defrag_inode fs/btrfs/defrag.c:237 [inline] btrfs_run_defrag_inodes+0x520/0xdc4 fs/btrfs/defrag.c:309 cleaner_kthread+0x21c/0x418 fs/btrfs/disk-io.c:1516 kthread+0x5fc/0x75c kernel/kthread.c:463 ret_from_fork+0x10/0x20 arch/arm64/kernel/entry.S:844 Fix this by releasing the path before calling iget_failed(). Reported-by: syzbot+c1c6edb02bea1da754d8@syzkaller.appspotmail.com Link: https://lore.kernel.org/linux-btrfs/694530c2.a70a0220.207337.010d.GAE@google.com/ Fixes: 69673992b1ae ("btrfs: push cleanup into btrfs_read_locked_inode()") Reviewed-by: Boris Burkov Signed-off-by: Filipe Manana Signed-off-by: David Sterba Signed-off-by: Sasha Levin --- fs/btrfs/inode.c | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/fs/btrfs/inode.c b/fs/btrfs/inode.c index 27a562bad6e872..1af9b05328ce86 100644 --- a/fs/btrfs/inode.c +++ b/fs/btrfs/inode.c @@ -4108,6 +4108,15 @@ static int btrfs_read_locked_inode(struct btrfs_inode *inode, struct btrfs_path return 0; out: + /* + * We may have a read locked leaf and iget_failed() triggers inode + * eviction which needs to release the delayed inode and that needs + * to lock the delayed inode's mutex. This can cause a ABBA deadlock + * with a task running delayed items, as that require first locking + * the delayed inode's mutex and then modifying its subvolume btree. + * So release the path before iget_failed(). + */ + btrfs_release_path(path); iget_failed(vfs_inode); return ret; } From 39f83f10772310ba4a77f2b5256aaf36994ef7e8 Mon Sep 17 00:00:00 2001 From: Qu Wenruo Date: Tue, 6 Jan 2026 20:26:40 +1030 Subject: [PATCH 2434/3902] btrfs: send: check for inline extents in range_is_hole_in_parent() [ Upstream commit 08b096c1372cd69627f4f559fb47c9fb67a52b39 ] Before accessing the disk_bytenr field of a file extent item we need to check if we are dealing with an inline extent. This is because for inline extents their data starts at the offset of the disk_bytenr field. So accessing the disk_bytenr means we are accessing inline data or in case the inline data is less than 8 bytes we can actually cause an invalid memory access if this inline extent item is the first item in the leaf or access metadata from other items. Fixes: 82bfb2e7b645 ("Btrfs: incremental send, fix unnecessary hole writes for sparse files") Reviewed-by: Filipe Manana Signed-off-by: Qu Wenruo Reviewed-by: David Sterba Signed-off-by: David Sterba Signed-off-by: Sasha Levin --- fs/btrfs/send.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/fs/btrfs/send.c b/fs/btrfs/send.c index 96a030d28e0910..9012ce7a742f49 100644 --- a/fs/btrfs/send.c +++ b/fs/btrfs/send.c @@ -6399,6 +6399,8 @@ static int range_is_hole_in_parent(struct send_ctx *sctx, extent_end = btrfs_file_extent_end(path); if (extent_end <= start) goto next; + if (btrfs_file_extent_type(leaf, fi) == BTRFS_FILE_EXTENT_INLINE) + return 0; if (btrfs_file_extent_disk_bytenr(leaf, fi) == 0) { search_start = extent_end; goto next; From 368251745679ea3a055c4d6185a315db7510fb4f Mon Sep 17 00:00:00 2001 From: Yang Li Date: Fri, 19 Dec 2025 10:43:09 +0800 Subject: [PATCH 2435/3902] Bluetooth: hci_sync: enable PA Sync Lost event [ Upstream commit ab749bfe6a1fc233213f2d00facea5233139d509 ] Enable the PA Sync Lost event mask to ensure PA sync loss is properly reported and handled. Fixes: 485e0626e587 ("Bluetooth: hci_event: Fix not handling PA Sync Lost event") Signed-off-by: Yang Li Signed-off-by: Luiz Augusto von Dentz Signed-off-by: Sasha Levin --- net/bluetooth/hci_sync.c | 1 + 1 file changed, 1 insertion(+) diff --git a/net/bluetooth/hci_sync.c b/net/bluetooth/hci_sync.c index 6e76798ec786b1..f5896c023a9fac 100644 --- a/net/bluetooth/hci_sync.c +++ b/net/bluetooth/hci_sync.c @@ -4402,6 +4402,7 @@ static int hci_le_set_event_mask_sync(struct hci_dev *hdev) if (bis_capable(hdev)) { events[1] |= 0x20; /* LE PA Report */ events[1] |= 0x40; /* LE PA Sync Established */ + events[1] |= 0x80; /* LE PA Sync Lost */ events[3] |= 0x04; /* LE Create BIG Complete */ events[3] |= 0x08; /* LE Terminate BIG Complete */ events[3] |= 0x10; /* LE BIG Sync Established */ From 17071fb5cb9c20cdbcd20322ebe2dd1220228590 Mon Sep 17 00:00:00 2001 From: Eric Dumazet Date: Thu, 8 Jan 2026 09:38:06 +0000 Subject: [PATCH 2436/3902] net: bridge: annotate data-races around fdb->{updated,used} [ Upstream commit b25a0b4a2193407aa72a4cd1df66a7ed07dd4f1e ] fdb->updated and fdb->used are read and written locklessly. Add READ_ONCE()/WRITE_ONCE() annotations. Fixes: 31cbc39b6344 ("net: bridge: add option to allow activity notifications for any fdb entries") Reported-by: syzbot+bfab43087ad57222ce96@syzkaller.appspotmail.com Closes: https://lore.kernel.org/netdev/695e3d74.050a0220.1c677c.035f.GAE@google.com/ Signed-off-by: Eric Dumazet Acked-by: Nikolay Aleksandrov Reviewed-by: Ido Schimmel Link: https://patch.msgid.link/20260108093806.834459-1-edumazet@google.com Signed-off-by: Jakub Kicinski Signed-off-by: Sasha Levin --- net/bridge/br_fdb.c | 28 ++++++++++++++++------------ net/bridge/br_input.c | 4 ++-- 2 files changed, 18 insertions(+), 14 deletions(-) diff --git a/net/bridge/br_fdb.c b/net/bridge/br_fdb.c index 58d22e2b85fc35..0501ffcb8a3ddb 100644 --- a/net/bridge/br_fdb.c +++ b/net/bridge/br_fdb.c @@ -70,7 +70,7 @@ static inline int has_expired(const struct net_bridge *br, { return !test_bit(BR_FDB_STATIC, &fdb->flags) && !test_bit(BR_FDB_ADDED_BY_EXT_LEARN, &fdb->flags) && - time_before_eq(fdb->updated + hold_time(br), jiffies); + time_before_eq(READ_ONCE(fdb->updated) + hold_time(br), jiffies); } static int fdb_to_nud(const struct net_bridge *br, @@ -126,9 +126,9 @@ static int fdb_fill_info(struct sk_buff *skb, const struct net_bridge *br, if (nla_put_u32(skb, NDA_FLAGS_EXT, ext_flags)) goto nla_put_failure; - ci.ndm_used = jiffies_to_clock_t(now - fdb->used); + ci.ndm_used = jiffies_to_clock_t(now - READ_ONCE(fdb->used)); ci.ndm_confirmed = 0; - ci.ndm_updated = jiffies_to_clock_t(now - fdb->updated); + ci.ndm_updated = jiffies_to_clock_t(now - READ_ONCE(fdb->updated)); ci.ndm_refcnt = 0; if (nla_put(skb, NDA_CACHEINFO, sizeof(ci), &ci)) goto nla_put_failure; @@ -551,7 +551,7 @@ void br_fdb_cleanup(struct work_struct *work) */ rcu_read_lock(); hlist_for_each_entry_rcu(f, &br->fdb_list, fdb_node) { - unsigned long this_timer = f->updated + delay; + unsigned long this_timer = READ_ONCE(f->updated) + delay; if (test_bit(BR_FDB_STATIC, &f->flags) || test_bit(BR_FDB_ADDED_BY_EXT_LEARN, &f->flags)) { @@ -924,6 +924,7 @@ int br_fdb_fillbuf(struct net_bridge *br, void *buf, { struct net_bridge_fdb_entry *f; struct __fdb_entry *fe = buf; + unsigned long delta; int num = 0; memset(buf, 0, maxnum*sizeof(struct __fdb_entry)); @@ -953,8 +954,11 @@ int br_fdb_fillbuf(struct net_bridge *br, void *buf, fe->port_hi = f->dst->port_no >> 8; fe->is_local = test_bit(BR_FDB_LOCAL, &f->flags); - if (!test_bit(BR_FDB_STATIC, &f->flags)) - fe->ageing_timer_value = jiffies_delta_to_clock_t(jiffies - f->updated); + if (!test_bit(BR_FDB_STATIC, &f->flags)) { + delta = jiffies - READ_ONCE(f->updated); + fe->ageing_timer_value = + jiffies_delta_to_clock_t(delta); + } ++fe; ++num; } @@ -1002,8 +1006,8 @@ void br_fdb_update(struct net_bridge *br, struct net_bridge_port *source, unsigned long now = jiffies; bool fdb_modified = false; - if (now != fdb->updated) { - fdb->updated = now; + if (now != READ_ONCE(fdb->updated)) { + WRITE_ONCE(fdb->updated, now); fdb_modified = __fdb_mark_active(fdb); } @@ -1242,10 +1246,10 @@ static int fdb_add_entry(struct net_bridge *br, struct net_bridge_port *source, if (fdb_handle_notify(fdb, notify)) modified = true; - fdb->used = jiffies; + WRITE_ONCE(fdb->used, jiffies); if (modified) { if (refresh) - fdb->updated = jiffies; + WRITE_ONCE(fdb->updated, jiffies); fdb_notify(br, fdb, RTM_NEWNEIGH, true); } @@ -1556,7 +1560,7 @@ int br_fdb_external_learn_add(struct net_bridge *br, struct net_bridge_port *p, goto err_unlock; } - fdb->updated = jiffies; + WRITE_ONCE(fdb->updated, jiffies); if (READ_ONCE(fdb->dst) != p) { WRITE_ONCE(fdb->dst, p); @@ -1565,7 +1569,7 @@ int br_fdb_external_learn_add(struct net_bridge *br, struct net_bridge_port *p, if (test_and_set_bit(BR_FDB_ADDED_BY_EXT_LEARN, &fdb->flags)) { /* Refresh entry */ - fdb->used = jiffies; + WRITE_ONCE(fdb->used, jiffies); } else { modified = true; } diff --git a/net/bridge/br_input.c b/net/bridge/br_input.c index 777fa869c1a144..e355a15bf5ab13 100644 --- a/net/bridge/br_input.c +++ b/net/bridge/br_input.c @@ -221,8 +221,8 @@ int br_handle_frame_finish(struct net *net, struct sock *sk, struct sk_buff *skb if (test_bit(BR_FDB_LOCAL, &dst->flags)) return br_pass_frame_up(skb, false); - if (now != dst->used) - dst->used = now; + if (now != READ_ONCE(dst->used)) + WRITE_ONCE(dst->used, now); br_forward(dst->dst, skb, local_rcv, false); } else { if (!mcast_hit) From b9f915340f25cae1562f18e1eb52deafca328414 Mon Sep 17 00:00:00 2001 From: Eric Dumazet Date: Wed, 7 Jan 2026 16:31:09 +0000 Subject: [PATCH 2437/3902] ip6_tunnel: use skb_vlan_inet_prepare() in __ip6_tnl_rcv() [ Upstream commit 81c734dae203757fb3c9eee6f9896386940776bd ] Blamed commit did not take care of VLAN encapsulations as spotted by syzbot [1]. Use skb_vlan_inet_prepare() instead of pskb_inet_may_pull(). [1] BUG: KMSAN: uninit-value in __INET_ECN_decapsulate include/net/inet_ecn.h:253 [inline] BUG: KMSAN: uninit-value in INET_ECN_decapsulate include/net/inet_ecn.h:275 [inline] BUG: KMSAN: uninit-value in IP6_ECN_decapsulate+0x7a8/0x1fa0 include/net/inet_ecn.h:321 __INET_ECN_decapsulate include/net/inet_ecn.h:253 [inline] INET_ECN_decapsulate include/net/inet_ecn.h:275 [inline] IP6_ECN_decapsulate+0x7a8/0x1fa0 include/net/inet_ecn.h:321 ip6ip6_dscp_ecn_decapsulate+0x16f/0x1b0 net/ipv6/ip6_tunnel.c:729 __ip6_tnl_rcv+0xed9/0x1b50 net/ipv6/ip6_tunnel.c:860 ip6_tnl_rcv+0xc3/0x100 net/ipv6/ip6_tunnel.c:903 gre_rcv+0x1529/0x1b90 net/ipv6/ip6_gre.c:-1 ip6_protocol_deliver_rcu+0x1c89/0x2c60 net/ipv6/ip6_input.c:438 ip6_input_finish+0x1f4/0x4a0 net/ipv6/ip6_input.c:489 NF_HOOK include/linux/netfilter.h:318 [inline] ip6_input+0x9c/0x330 net/ipv6/ip6_input.c:500 ip6_mc_input+0x7ca/0xc10 net/ipv6/ip6_input.c:590 dst_input include/net/dst.h:474 [inline] ip6_rcv_finish+0x958/0x990 net/ipv6/ip6_input.c:79 NF_HOOK include/linux/netfilter.h:318 [inline] ipv6_rcv+0xf1/0x3c0 net/ipv6/ip6_input.c:311 __netif_receive_skb_one_core net/core/dev.c:6139 [inline] __netif_receive_skb+0x1df/0xac0 net/core/dev.c:6252 netif_receive_skb_internal net/core/dev.c:6338 [inline] netif_receive_skb+0x57/0x630 net/core/dev.c:6397 tun_rx_batched+0x1df/0x980 drivers/net/tun.c:1485 tun_get_user+0x5c0e/0x6c60 drivers/net/tun.c:1953 tun_chr_write_iter+0x3e9/0x5c0 drivers/net/tun.c:1999 new_sync_write fs/read_write.c:593 [inline] vfs_write+0xbe2/0x15d0 fs/read_write.c:686 ksys_write fs/read_write.c:738 [inline] __do_sys_write fs/read_write.c:749 [inline] __se_sys_write fs/read_write.c:746 [inline] __x64_sys_write+0x1fb/0x4d0 fs/read_write.c:746 x64_sys_call+0x30ab/0x3e70 arch/x86/include/generated/asm/syscalls_64.h:2 do_syscall_x64 arch/x86/entry/syscall_64.c:63 [inline] do_syscall_64+0xd3/0xf80 arch/x86/entry/syscall_64.c:94 entry_SYSCALL_64_after_hwframe+0x77/0x7f Uninit was created at: slab_post_alloc_hook mm/slub.c:4960 [inline] slab_alloc_node mm/slub.c:5263 [inline] kmem_cache_alloc_node_noprof+0x9e7/0x17a0 mm/slub.c:5315 kmalloc_reserve+0x13c/0x4b0 net/core/skbuff.c:586 __alloc_skb+0x805/0x1040 net/core/skbuff.c:690 alloc_skb include/linux/skbuff.h:1383 [inline] alloc_skb_with_frags+0xc5/0xa60 net/core/skbuff.c:6712 sock_alloc_send_pskb+0xacc/0xc60 net/core/sock.c:2995 tun_alloc_skb drivers/net/tun.c:1461 [inline] tun_get_user+0x1142/0x6c60 drivers/net/tun.c:1794 tun_chr_write_iter+0x3e9/0x5c0 drivers/net/tun.c:1999 new_sync_write fs/read_write.c:593 [inline] vfs_write+0xbe2/0x15d0 fs/read_write.c:686 ksys_write fs/read_write.c:738 [inline] __do_sys_write fs/read_write.c:749 [inline] __se_sys_write fs/read_write.c:746 [inline] __x64_sys_write+0x1fb/0x4d0 fs/read_write.c:746 x64_sys_call+0x30ab/0x3e70 arch/x86/include/generated/asm/syscalls_64.h:2 do_syscall_x64 arch/x86/entry/syscall_64.c:63 [inline] do_syscall_64+0xd3/0xf80 arch/x86/entry/syscall_64.c:94 entry_SYSCALL_64_after_hwframe+0x77/0x7f CPU: 0 UID: 0 PID: 6465 Comm: syz.0.17 Not tainted syzkaller #0 PREEMPT(none) Hardware name: Google Google Compute Engine/Google Compute Engine, BIOS Google 10/25/2025 Fixes: 8d975c15c0cd ("ip6_tunnel: make sure to pull inner header in __ip6_tnl_rcv()") Reported-by: syzbot+d4dda070f833dc5dc89a@syzkaller.appspotmail.com Closes: https://lore.kernel.org/netdev/695e88b2.050a0220.1c677c.036d.GAE@google.com/T/#u Signed-off-by: Eric Dumazet Link: https://patch.msgid.link/20260107163109.4188620-1-edumazet@google.com Signed-off-by: Jakub Kicinski Signed-off-by: Sasha Levin --- net/ipv6/ip6_tunnel.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/net/ipv6/ip6_tunnel.c b/net/ipv6/ip6_tunnel.c index 6405072050e0ef..c1f39735a23676 100644 --- a/net/ipv6/ip6_tunnel.c +++ b/net/ipv6/ip6_tunnel.c @@ -844,7 +844,7 @@ static int __ip6_tnl_rcv(struct ip6_tnl *tunnel, struct sk_buff *skb, skb_reset_network_header(skb); - if (!pskb_inet_may_pull(skb)) { + if (skb_vlan_inet_prepare(skb, true)) { DEV_STATS_INC(tunnel->dev, rx_length_errors); DEV_STATS_INC(tunnel->dev, rx_errors); goto drop; From da6d0370eb74e6d15724558117097ccb6bd8482c Mon Sep 17 00:00:00 2001 From: Eric Dumazet Date: Thu, 8 Jan 2026 09:32:44 +0000 Subject: [PATCH 2438/3902] net: update netdev_lock_{type,name} [ Upstream commit eb74c19fe10872ee1f29a8f90ca5ce943921afe9 ] Add missing entries in netdev_lock_type[] and netdev_lock_name[] : CAN, MCTP, RAWIP, CAIF, IP6GRE, 6LOWPAN, NETLINK, VSOCKMON, IEEE802154_MONITOR. Also add a WARN_ONCE() in netdev_lock_pos() to help future bug hunting next time a protocol is added without updating these arrays. Fixes: 1a33e10e4a95 ("net: partially revert dynamic lockdep key changes") Signed-off-by: Eric Dumazet Link: https://patch.msgid.link/20260108093244.830280-1-edumazet@google.com Signed-off-by: Jakub Kicinski Signed-off-by: Sasha Levin --- net/core/dev.c | 25 +++++++++++++++++++------ 1 file changed, 19 insertions(+), 6 deletions(-) diff --git a/net/core/dev.c b/net/core/dev.c index 2acfa44927daad..5b536860138d19 100644 --- a/net/core/dev.c +++ b/net/core/dev.c @@ -478,15 +478,21 @@ static const unsigned short netdev_lock_type[] = { ARPHRD_IEEE1394, ARPHRD_EUI64, ARPHRD_INFINIBAND, ARPHRD_SLIP, ARPHRD_CSLIP, ARPHRD_SLIP6, ARPHRD_CSLIP6, ARPHRD_RSRVD, ARPHRD_ADAPT, ARPHRD_ROSE, ARPHRD_X25, ARPHRD_HWX25, + ARPHRD_CAN, ARPHRD_MCTP, ARPHRD_PPP, ARPHRD_CISCO, ARPHRD_LAPB, ARPHRD_DDCMP, - ARPHRD_RAWHDLC, ARPHRD_TUNNEL, ARPHRD_TUNNEL6, ARPHRD_FRAD, + ARPHRD_RAWHDLC, ARPHRD_RAWIP, + ARPHRD_TUNNEL, ARPHRD_TUNNEL6, ARPHRD_FRAD, ARPHRD_SKIP, ARPHRD_LOOPBACK, ARPHRD_LOCALTLK, ARPHRD_FDDI, ARPHRD_BIF, ARPHRD_SIT, ARPHRD_IPDDP, ARPHRD_IPGRE, ARPHRD_PIMREG, ARPHRD_HIPPI, ARPHRD_ASH, ARPHRD_ECONET, ARPHRD_IRDA, ARPHRD_FCPP, ARPHRD_FCAL, ARPHRD_FCPL, ARPHRD_FCFABRIC, ARPHRD_IEEE80211, ARPHRD_IEEE80211_PRISM, - ARPHRD_IEEE80211_RADIOTAP, ARPHRD_PHONET, ARPHRD_PHONET_PIPE, - ARPHRD_IEEE802154, ARPHRD_VOID, ARPHRD_NONE}; + ARPHRD_IEEE80211_RADIOTAP, + ARPHRD_IEEE802154, ARPHRD_IEEE802154_MONITOR, + ARPHRD_PHONET, ARPHRD_PHONET_PIPE, + ARPHRD_CAIF, ARPHRD_IP6GRE, ARPHRD_NETLINK, ARPHRD_6LOWPAN, + ARPHRD_VSOCKMON, + ARPHRD_VOID, ARPHRD_NONE}; static const char *const netdev_lock_name[] = { "_xmit_NETROM", "_xmit_ETHER", "_xmit_EETHER", "_xmit_AX25", @@ -495,15 +501,21 @@ static const char *const netdev_lock_name[] = { "_xmit_IEEE1394", "_xmit_EUI64", "_xmit_INFINIBAND", "_xmit_SLIP", "_xmit_CSLIP", "_xmit_SLIP6", "_xmit_CSLIP6", "_xmit_RSRVD", "_xmit_ADAPT", "_xmit_ROSE", "_xmit_X25", "_xmit_HWX25", + "_xmit_CAN", "_xmit_MCTP", "_xmit_PPP", "_xmit_CISCO", "_xmit_LAPB", "_xmit_DDCMP", - "_xmit_RAWHDLC", "_xmit_TUNNEL", "_xmit_TUNNEL6", "_xmit_FRAD", + "_xmit_RAWHDLC", "_xmit_RAWIP", + "_xmit_TUNNEL", "_xmit_TUNNEL6", "_xmit_FRAD", "_xmit_SKIP", "_xmit_LOOPBACK", "_xmit_LOCALTLK", "_xmit_FDDI", "_xmit_BIF", "_xmit_SIT", "_xmit_IPDDP", "_xmit_IPGRE", "_xmit_PIMREG", "_xmit_HIPPI", "_xmit_ASH", "_xmit_ECONET", "_xmit_IRDA", "_xmit_FCPP", "_xmit_FCAL", "_xmit_FCPL", "_xmit_FCFABRIC", "_xmit_IEEE80211", "_xmit_IEEE80211_PRISM", - "_xmit_IEEE80211_RADIOTAP", "_xmit_PHONET", "_xmit_PHONET_PIPE", - "_xmit_IEEE802154", "_xmit_VOID", "_xmit_NONE"}; + "_xmit_IEEE80211_RADIOTAP", + "_xmit_IEEE802154", "_xmit_IEEE802154_MONITOR", + "_xmit_PHONET", "_xmit_PHONET_PIPE", + "_xmit_CAIF", "_xmit_IP6GRE", "_xmit_NETLINK", "_xmit_6LOWPAN", + "_xmit_VSOCKMON", + "_xmit_VOID", "_xmit_NONE"}; static struct lock_class_key netdev_xmit_lock_key[ARRAY_SIZE(netdev_lock_type)]; static struct lock_class_key netdev_addr_lock_key[ARRAY_SIZE(netdev_lock_type)]; @@ -516,6 +528,7 @@ static inline unsigned short netdev_lock_pos(unsigned short dev_type) if (netdev_lock_type[i] == dev_type) return i; /* the last key is used by default */ + WARN_ONCE(1, "netdev_lock_pos() could not find dev_type=%u\n", dev_type); return ARRAY_SIZE(netdev_lock_type) - 1; } From 6dbead9c7677186f22b7981dd085a0feec1f038e Mon Sep 17 00:00:00 2001 From: Eric Dumazet Date: Thu, 8 Jan 2026 13:36:51 +0000 Subject: [PATCH 2439/3902] macvlan: fix possible UAF in macvlan_forward_source() [ Upstream commit 7470a7a63dc162f07c26dbf960e41ee1e248d80e ] Add RCU protection on (struct macvlan_source_entry)->vlan. Whenever macvlan_hash_del_source() is called, we must clear entry->vlan pointer before RCU grace period starts. This allows macvlan_forward_source() to skip over entries queued for freeing. Note that macvlan_dev are already RCU protected, as they are embedded in a standard netdev (netdev_priv(ndev)). Fixes: 79cf79abce71 ("macvlan: add source mode") Reported-by: syzbot+7182fbe91e58602ec1fe@syzkaller.appspotmail.com https: //lore.kernel.org/netdev/695fb1e8.050a0220.1c677c.039f.GAE@google.com/T/#u Signed-off-by: Eric Dumazet Link: https://patch.msgid.link/20260108133651.1130486-1-edumazet@google.com Signed-off-by: Jakub Kicinski Signed-off-by: Sasha Levin --- drivers/net/macvlan.c | 20 +++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/drivers/net/macvlan.c b/drivers/net/macvlan.c index 7966545512cfea..b4df7e184791d0 100644 --- a/drivers/net/macvlan.c +++ b/drivers/net/macvlan.c @@ -59,7 +59,7 @@ struct macvlan_port { struct macvlan_source_entry { struct hlist_node hlist; - struct macvlan_dev *vlan; + struct macvlan_dev __rcu *vlan; unsigned char addr[6+2] __aligned(sizeof(u16)); struct rcu_head rcu; }; @@ -146,7 +146,7 @@ static struct macvlan_source_entry *macvlan_hash_lookup_source( hlist_for_each_entry_rcu(entry, h, hlist, lockdep_rtnl_is_held()) { if (ether_addr_equal_64bits(entry->addr, addr) && - entry->vlan == vlan) + rcu_access_pointer(entry->vlan) == vlan) return entry; } return NULL; @@ -168,7 +168,7 @@ static int macvlan_hash_add_source(struct macvlan_dev *vlan, return -ENOMEM; ether_addr_copy(entry->addr, addr); - entry->vlan = vlan; + RCU_INIT_POINTER(entry->vlan, vlan); h = &port->vlan_source_hash[macvlan_eth_hash(addr)]; hlist_add_head_rcu(&entry->hlist, h); vlan->macaddr_count++; @@ -187,6 +187,7 @@ static void macvlan_hash_add(struct macvlan_dev *vlan) static void macvlan_hash_del_source(struct macvlan_source_entry *entry) { + RCU_INIT_POINTER(entry->vlan, NULL); hlist_del_rcu(&entry->hlist); kfree_rcu(entry, rcu); } @@ -390,7 +391,7 @@ static void macvlan_flush_sources(struct macvlan_port *port, int i; hash_for_each_safe(port->vlan_source_hash, i, next, entry, hlist) - if (entry->vlan == vlan) + if (rcu_access_pointer(entry->vlan) == vlan) macvlan_hash_del_source(entry); vlan->macaddr_count = 0; @@ -433,9 +434,14 @@ static bool macvlan_forward_source(struct sk_buff *skb, hlist_for_each_entry_rcu(entry, h, hlist) { if (ether_addr_equal_64bits(entry->addr, addr)) { - if (entry->vlan->flags & MACVLAN_FLAG_NODST) + struct macvlan_dev *vlan = rcu_dereference(entry->vlan); + + if (!vlan) + continue; + + if (vlan->flags & MACVLAN_FLAG_NODST) consume = true; - macvlan_forward_source_one(skb, entry->vlan); + macvlan_forward_source_one(skb, vlan); } } @@ -1680,7 +1686,7 @@ static int macvlan_fill_info_macaddr(struct sk_buff *skb, struct macvlan_source_entry *entry; hlist_for_each_entry_rcu(entry, h, hlist, lockdep_rtnl_is_held()) { - if (entry->vlan != vlan) + if (rcu_access_pointer(entry->vlan) != vlan) continue; if (nla_put(skb, IFLA_MACVLAN_MACADDR, ETH_ALEN, entry->addr)) return 1; From d6072557b90e0c557df319a56f4a9dc482706d2c Mon Sep 17 00:00:00 2001 From: Caleb Sander Mateos Date: Thu, 8 Jan 2026 10:22:10 -0700 Subject: [PATCH 2440/3902] block: zero non-PI portion of auto integrity buffer [ Upstream commit ca22c566b89164f6e670af56ecc45f47ef3df819 ] The auto-generated integrity buffer for writes needs to be fully initialized before being passed to the underlying block device, otherwise the uninitialized memory can be read back by userspace or anyone with physical access to the storage device. If protection information is generated, that portion of the integrity buffer is already initialized. The integrity data is also zeroed if PI generation is disabled via sysfs or the PI tuple size is 0. However, this misses the case where PI is generated and the PI tuple size is nonzero, but the metadata size is larger than the PI tuple. In this case, the remainder ("opaque") of the metadata is left uninitialized. Generalize the BLK_INTEGRITY_CSUM_NONE check to cover any case when the metadata is larger than just the PI tuple. Signed-off-by: Caleb Sander Mateos Fixes: c546d6f43833 ("block: only zero non-PI metadata tuples in bio_integrity_prep") Reviewed-by: Anuj Gupta Reviewed-by: Christoph Hellwig Reviewed-by: Martin K. Petersen Signed-off-by: Jens Axboe Signed-off-by: Sasha Levin --- block/bio-integrity-auto.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/block/bio-integrity-auto.c b/block/bio-integrity-auto.c index 687952f63bbbfe..b8b7587be9679a 100644 --- a/block/bio-integrity-auto.c +++ b/block/bio-integrity-auto.c @@ -142,7 +142,7 @@ bool bio_integrity_prep(struct bio *bio) return true; set_flags = false; gfp |= __GFP_ZERO; - } else if (bi->csum_type == BLK_INTEGRITY_CSUM_NONE) + } else if (bi->metadata_size > bi->pi_tuple_size) gfp |= __GFP_ZERO; break; default: From 554201ed0a8f4d32e719f42caeaeb2735a9ed6ca Mon Sep 17 00:00:00 2001 From: Eric Dumazet Date: Thu, 8 Jan 2026 19:02:14 +0000 Subject: [PATCH 2441/3902] ipv4: ip_gre: make ipgre_header() robust [ Upstream commit e67c577d89894811ce4dcd1a9ed29d8b63476667 ] Analog to commit db5b4e39c4e6 ("ip6_gre: make ip6gre_header() robust") Over the years, syzbot found many ways to crash the kernel in ipgre_header() [1]. This involves team or bonding drivers ability to dynamically change their dev->needed_headroom and/or dev->hard_header_len In this particular crash mld_newpack() allocated an skb with a too small reserve/headroom, and by the time mld_sendpack() was called, syzbot managed to attach an ipgre device. [1] skbuff: skb_under_panic: text:ffffffff89ea3cb7 len:2030915468 put:2030915372 head:ffff888058b43000 data:ffff887fdfa6e194 tail:0x120 end:0x6c0 dev:team0 kernel BUG at net/core/skbuff.c:213 ! Oops: invalid opcode: 0000 [#1] SMP KASAN PTI CPU: 1 UID: 0 PID: 1322 Comm: kworker/1:9 Not tainted syzkaller #0 PREEMPT(full) Hardware name: Google Google Compute Engine/Google Compute Engine, BIOS Google 10/25/2025 Workqueue: mld mld_ifc_work RIP: 0010:skb_panic+0x157/0x160 net/core/skbuff.c:213 Call Trace: skb_under_panic net/core/skbuff.c:223 [inline] skb_push+0xc3/0xe0 net/core/skbuff.c:2641 ipgre_header+0x67/0x290 net/ipv4/ip_gre.c:897 dev_hard_header include/linux/netdevice.h:3436 [inline] neigh_connected_output+0x286/0x460 net/core/neighbour.c:1618 NF_HOOK_COND include/linux/netfilter.h:307 [inline] ip6_output+0x340/0x550 net/ipv6/ip6_output.c:247 NF_HOOK+0x9e/0x380 include/linux/netfilter.h:318 mld_sendpack+0x8d4/0xe60 net/ipv6/mcast.c:1855 mld_send_cr net/ipv6/mcast.c:2154 [inline] mld_ifc_work+0x83e/0xd60 net/ipv6/mcast.c:2693 process_one_work kernel/workqueue.c:3257 [inline] process_scheduled_works+0xad1/0x1770 kernel/workqueue.c:3340 worker_thread+0x8a0/0xda0 kernel/workqueue.c:3421 kthread+0x711/0x8a0 kernel/kthread.c:463 ret_from_fork+0x510/0xa50 arch/x86/kernel/process.c:158 ret_from_fork_asm+0x1a/0x30 arch/x86/entry/entry_64.S:246 Fixes: c54419321455 ("GRE: Refactor GRE tunneling code.") Reported-by: syzbot+7c134e1c3aa3283790b9@syzkaller.appspotmail.com Closes: https://www.spinics.net/lists/netdev/msg1147302.html Signed-off-by: Eric Dumazet Link: https://patch.msgid.link/20260108190214.1667040-1-edumazet@google.com Signed-off-by: Jakub Kicinski Signed-off-by: Sasha Levin --- net/ipv4/ip_gre.c | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/net/ipv4/ip_gre.c b/net/ipv4/ip_gre.c index 8178c44a3cdd48..e13244729ad8d5 100644 --- a/net/ipv4/ip_gre.c +++ b/net/ipv4/ip_gre.c @@ -891,10 +891,17 @@ static int ipgre_header(struct sk_buff *skb, struct net_device *dev, const void *daddr, const void *saddr, unsigned int len) { struct ip_tunnel *t = netdev_priv(dev); - struct iphdr *iph; struct gre_base_hdr *greh; + struct iphdr *iph; + int needed; + + needed = t->hlen + sizeof(*iph); + if (skb_headroom(skb) < needed && + pskb_expand_head(skb, HH_DATA_ALIGN(needed - skb_headroom(skb)), + 0, GFP_ATOMIC)) + return -needed; - iph = skb_push(skb, t->hlen + sizeof(*iph)); + iph = skb_push(skb, needed); greh = (struct gre_base_hdr *)(iph+1); greh->flags = gre_tnl_flags_to_gre_flags(t->parms.o_flags); greh->protocol = htons(type); From f39ab11f118b2d22db5a1313260e3977bff02b27 Mon Sep 17 00:00:00 2001 From: Stefano Garzarella Date: Thu, 8 Jan 2026 12:44:19 +0100 Subject: [PATCH 2442/3902] vsock/test: add a final full barrier after run all tests [ Upstream commit c39a6a277e0e67ffff6a8efcbbf7e7e23ce9e38c ] If the last test fails, the other side still completes correctly, which could lead to false positives. Let's add a final barrier that ensures that the last test has finished correctly on both sides, but also that the two sides agree on the number of tests to be performed. Fixes: 2f65b44e199c ("VSOCK: add full barrier between test cases") Reviewed-by: Luigi Leonardi Signed-off-by: Stefano Garzarella Link: https://patch.msgid.link/20260108114419.52747-1-sgarzare@redhat.com Signed-off-by: Jakub Kicinski Signed-off-by: Sasha Levin --- tools/testing/vsock/util.c | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/tools/testing/vsock/util.c b/tools/testing/vsock/util.c index d843643ced6b7f..9430ef5b8bc3ea 100644 --- a/tools/testing/vsock/util.c +++ b/tools/testing/vsock/util.c @@ -511,6 +511,18 @@ void run_tests(const struct test_case *test_cases, printf("ok\n"); } + + printf("All tests have been executed. Waiting other peer..."); + fflush(stdout); + + /* + * Final full barrier, to ensure that all tests have been run and + * that even the last one has been successful on both sides. + */ + control_writeln("COMPLETED"); + control_expectln("COMPLETED"); + + printf("ok\n"); } void list_tests(const struct test_case *test_cases) From e05b8084a20f6bd5827d338c928e5e0fcbafa496 Mon Sep 17 00:00:00 2001 From: Saeed Mahameed Date: Thu, 8 Jan 2026 13:26:54 -0800 Subject: [PATCH 2443/3902] net/mlx5e: Fix crash on profile change rollback failure [ Upstream commit 4dadc4077e3f77d6d31e199a925fc7a705e7adeb ] mlx5e_netdev_change_profile can fail to attach a new profile and can fail to rollback to old profile, in such case, we could end up with a dangling netdev with a fully reset netdev_priv. A retry to change profile, e.g. another attempt to call mlx5e_netdev_change_profile via switchdev mode change, will crash trying to access the now NULL priv->mdev. This fix allows mlx5e_netdev_change_profile() to handle previous failures and an empty priv, by not assuming priv is valid. Pass netdev and mdev to all flows requiring mlx5e_netdev_change_profile() and avoid passing priv. In mlx5e_netdev_change_profile() check if current priv is valid, and if not, just attach the new profile without trying to access the old one. This fixes the following oops, when enabling switchdev mode for the 2nd time after first time failure: ## Enabling switchdev mode first time: mlx5_core 0012:03:00.1: E-Switch: Supported tc chains and prios offload workqueue: Failed to create a rescuer kthread for wq "mlx5e": -EINTR mlx5_core 0012:03:00.1: mlx5e_netdev_init_profile:6214:(pid 37199): mlx5e_priv_init failed, err=-12 mlx5_core 0012:03:00.1 gpu3rdma1: mlx5e_netdev_change_profile: new profile init failed, -12 workqueue: Failed to create a rescuer kthread for wq "mlx5e": -EINTR mlx5_core 0012:03:00.1: mlx5e_netdev_init_profile:6214:(pid 37199): mlx5e_priv_init failed, err=-12 mlx5_core 0012:03:00.1 gpu3rdma1: mlx5e_netdev_change_profile: failed to rollback to orig profile, -12 ^^^^^^^^ mlx5_core 0000:00:03.0: E-Switch: Disable: mode(LEGACY), nvfs(0), necvfs(0), active vports(0) ## retry: Enabling switchdev mode 2nd time: mlx5_core 0000:00:03.0: E-Switch: Supported tc chains and prios offload BUG: kernel NULL pointer dereference, address: 0000000000000038 #PF: supervisor read access in kernel mode #PF: error_code(0x0000) - not-present page PGD 0 P4D 0 Oops: Oops: 0000 [#1] SMP NOPTI CPU: 13 UID: 0 PID: 520 Comm: devlink Not tainted 6.18.0-rc4+ #91 PREEMPT(voluntary) Hardware name: QEMU Standard PC (Q35 + ICH9, 2009), BIOS 1.16.3-2.fc40 04/01/2014 RIP: 0010:mlx5e_detach_netdev+0x3c/0x90 Code: 50 00 00 f0 80 4f 78 02 48 8b bf e8 07 00 00 48 85 ff 74 16 48 8b 73 78 48 d1 ee 83 e6 01 83 f6 01 40 0f b6 f6 e8 c4 42 00 00 <48> 8b 45 38 48 85 c0 74 08 48 89 df e8 cc 47 40 1e 48 8b bb f0 07 RSP: 0018:ffffc90000673890 EFLAGS: 00010246 RAX: 0000000000000000 RBX: ffff8881036a89c0 RCX: 0000000000000000 RDX: ffff888113f63800 RSI: ffffffff822fe720 RDI: 0000000000000000 RBP: 0000000000000000 R08: 0000000000002dcd R09: 0000000000000000 R10: ffffc900006738e8 R11: 00000000ffffffff R12: 0000000000000000 R13: 0000000000000000 R14: ffff8881036a89c0 R15: 0000000000000000 FS: 00007fdfb8384740(0000) GS:ffff88856a9d6000(0000) knlGS:0000000000000000 CS: 0010 DS: 0000 ES: 0000 CR0: 0000000080050033 CR2: 0000000000000038 CR3: 0000000112ae0005 CR4: 0000000000370ef0 Call Trace: mlx5e_netdev_change_profile+0x45/0xb0 mlx5e_vport_rep_load+0x27b/0x2d0 mlx5_esw_offloads_rep_load+0x72/0xf0 esw_offloads_enable+0x5d0/0x970 mlx5_eswitch_enable_locked+0x349/0x430 ? is_mp_supported+0x57/0xb0 mlx5_devlink_eswitch_mode_set+0x26b/0x430 devlink_nl_eswitch_set_doit+0x6f/0xf0 genl_family_rcv_msg_doit+0xe8/0x140 genl_rcv_msg+0x18b/0x290 ? __pfx_devlink_nl_pre_doit+0x10/0x10 ? __pfx_devlink_nl_eswitch_set_doit+0x10/0x10 ? __pfx_devlink_nl_post_doit+0x10/0x10 ? __pfx_genl_rcv_msg+0x10/0x10 netlink_rcv_skb+0x52/0x100 genl_rcv+0x28/0x40 netlink_unicast+0x282/0x3e0 ? __alloc_skb+0xd6/0x190 netlink_sendmsg+0x1f7/0x430 __sys_sendto+0x213/0x220 ? __sys_recvmsg+0x6a/0xd0 __x64_sys_sendto+0x24/0x30 do_syscall_64+0x50/0x1f0 entry_SYSCALL_64_after_hwframe+0x76/0x7e RIP: 0033:0x7fdfb8495047 Fixes: c4d7eb57687f ("net/mxl5e: Add change profile method") Signed-off-by: Saeed Mahameed Reviewed-by: Tariq Toukan Link: https://patch.msgid.link/20260108212657.25090-2-saeed@kernel.org Signed-off-by: Jakub Kicinski Signed-off-by: Sasha Levin --- drivers/net/ethernet/mellanox/mlx5/core/en.h | 9 ++-- .../net/ethernet/mellanox/mlx5/core/en_main.c | 48 +++++++++++++------ .../net/ethernet/mellanox/mlx5/core/en_rep.c | 11 ++--- 3 files changed, 44 insertions(+), 24 deletions(-) diff --git a/drivers/net/ethernet/mellanox/mlx5/core/en.h b/drivers/net/ethernet/mellanox/mlx5/core/en.h index a6479e4d8d8c60..cfdbeb21b61cfe 100644 --- a/drivers/net/ethernet/mellanox/mlx5/core/en.h +++ b/drivers/net/ethernet/mellanox/mlx5/core/en.h @@ -1239,9 +1239,12 @@ mlx5e_create_netdev(struct mlx5_core_dev *mdev, const struct mlx5e_profile *prof int mlx5e_attach_netdev(struct mlx5e_priv *priv); void mlx5e_detach_netdev(struct mlx5e_priv *priv); void mlx5e_destroy_netdev(struct mlx5e_priv *priv); -int mlx5e_netdev_change_profile(struct mlx5e_priv *priv, - const struct mlx5e_profile *new_profile, void *new_ppriv); -void mlx5e_netdev_attach_nic_profile(struct mlx5e_priv *priv); +int mlx5e_netdev_change_profile(struct net_device *netdev, + struct mlx5_core_dev *mdev, + const struct mlx5e_profile *new_profile, + void *new_ppriv); +void mlx5e_netdev_attach_nic_profile(struct net_device *netdev, + struct mlx5_core_dev *mdev); void mlx5e_set_netdev_mtu_boundaries(struct mlx5e_priv *priv); void mlx5e_build_nic_params(struct mlx5e_priv *priv, struct mlx5e_xsk *xsk, u16 mtu); diff --git a/drivers/net/ethernet/mellanox/mlx5/core/en_main.c b/drivers/net/ethernet/mellanox/mlx5/core/en_main.c index 1545f9c008f49c..3850c267dfc024 100644 --- a/drivers/net/ethernet/mellanox/mlx5/core/en_main.c +++ b/drivers/net/ethernet/mellanox/mlx5/core/en_main.c @@ -6564,19 +6564,28 @@ mlx5e_netdev_attach_profile(struct net_device *netdev, struct mlx5_core_dev *mde return err; } -int mlx5e_netdev_change_profile(struct mlx5e_priv *priv, - const struct mlx5e_profile *new_profile, void *new_ppriv) +int mlx5e_netdev_change_profile(struct net_device *netdev, + struct mlx5_core_dev *mdev, + const struct mlx5e_profile *new_profile, + void *new_ppriv) { - const struct mlx5e_profile *orig_profile = priv->profile; - struct net_device *netdev = priv->netdev; - struct mlx5_core_dev *mdev = priv->mdev; - void *orig_ppriv = priv->ppriv; + struct mlx5e_priv *priv = netdev_priv(netdev); + const struct mlx5e_profile *orig_profile; int err, rollback_err; + void *orig_ppriv; - /* cleanup old profile */ - mlx5e_detach_netdev(priv); - priv->profile->cleanup(priv); - mlx5e_priv_cleanup(priv); + orig_profile = priv->profile; + orig_ppriv = priv->ppriv; + + /* NULL could happen if previous change_profile failed to rollback */ + if (priv->profile) { + WARN_ON_ONCE(priv->mdev != mdev); + /* cleanup old profile */ + mlx5e_detach_netdev(priv); + priv->profile->cleanup(priv); + mlx5e_priv_cleanup(priv); + } + /* priv members are not valid from this point ... */ if (mdev->state == MLX5_DEVICE_STATE_INTERNAL_ERROR) { mlx5e_netdev_init_profile(netdev, mdev, new_profile, new_ppriv); @@ -6593,16 +6602,25 @@ int mlx5e_netdev_change_profile(struct mlx5e_priv *priv, return 0; rollback: + if (!orig_profile) { + netdev_warn(netdev, "no original profile to rollback to\n"); + priv->profile = NULL; + return err; + } + rollback_err = mlx5e_netdev_attach_profile(netdev, mdev, orig_profile, orig_ppriv); - if (rollback_err) - netdev_err(netdev, "%s: failed to rollback to orig profile, %d\n", - __func__, rollback_err); + if (rollback_err) { + netdev_err(netdev, "failed to rollback to orig profile, %d\n", + rollback_err); + priv->profile = NULL; + } return err; } -void mlx5e_netdev_attach_nic_profile(struct mlx5e_priv *priv) +void mlx5e_netdev_attach_nic_profile(struct net_device *netdev, + struct mlx5_core_dev *mdev) { - mlx5e_netdev_change_profile(priv, &mlx5e_nic_profile, NULL); + mlx5e_netdev_change_profile(netdev, mdev, &mlx5e_nic_profile, NULL); } void mlx5e_destroy_netdev(struct mlx5e_priv *priv) diff --git a/drivers/net/ethernet/mellanox/mlx5/core/en_rep.c b/drivers/net/ethernet/mellanox/mlx5/core/en_rep.c index 0335ca8277efaa..2f6aa5e61747c9 100644 --- a/drivers/net/ethernet/mellanox/mlx5/core/en_rep.c +++ b/drivers/net/ethernet/mellanox/mlx5/core/en_rep.c @@ -1508,17 +1508,16 @@ mlx5e_vport_uplink_rep_load(struct mlx5_core_dev *dev, struct mlx5_eswitch_rep * { struct mlx5e_rep_priv *rpriv = mlx5e_rep_to_rep_priv(rep); struct net_device *netdev; - struct mlx5e_priv *priv; int err; netdev = mlx5_uplink_netdev_get(dev); if (!netdev) return 0; - priv = netdev_priv(netdev); - rpriv->netdev = priv->netdev; - err = mlx5e_netdev_change_profile(priv, &mlx5e_uplink_rep_profile, - rpriv); + /* must not use netdev_priv(netdev), it might not be initialized yet */ + rpriv->netdev = netdev; + err = mlx5e_netdev_change_profile(netdev, dev, + &mlx5e_uplink_rep_profile, rpriv); mlx5_uplink_netdev_put(dev, netdev); return err; } @@ -1546,7 +1545,7 @@ mlx5e_vport_uplink_rep_unload(struct mlx5e_rep_priv *rpriv) if (!(priv->mdev->priv.flags & MLX5_PRIV_FLAGS_SWITCH_LEGACY)) unregister_netdev(netdev); - mlx5e_netdev_attach_nic_profile(priv); + mlx5e_netdev_attach_nic_profile(netdev, priv->mdev); } static int From a3d4f87d41f5140f1cf5c02fce5cdad2637f6244 Mon Sep 17 00:00:00 2001 From: Saeed Mahameed Date: Thu, 8 Jan 2026 13:26:55 -0800 Subject: [PATCH 2444/3902] net/mlx5e: Don't store mlx5e_priv in mlx5e_dev devlink priv [ Upstream commit 123eda2e5b1638e298e3a66bb1e64a8da92de5e1 ] mlx5e_priv is an unstable structure that can be memset(0) if profile attaching fails, mlx5e_priv in mlx5e_dev devlink private is used to reference the netdev and mdev associated with that struct. Instead, store netdev directly into mlx5e_dev and get mdev from the containing mlx5_adev aux device structure. This fixes a kernel oops in mlx5e_remove when switchdev mode fails due to change profile failure. $ devlink dev eswitch set pci/0000:00:03.0 mode switchdev Error: mlx5_core: Failed setting eswitch to offloads. dmesg: workqueue: Failed to create a rescuer kthread for wq "mlx5e": -EINTR mlx5_core 0012:03:00.1: mlx5e_netdev_init_profile:6214:(pid 37199): mlx5e_priv_init failed, err=-12 mlx5_core 0012:03:00.1 gpu3rdma1: mlx5e_netdev_change_profile: new profile init failed, -12 workqueue: Failed to create a rescuer kthread for wq "mlx5e": -EINTR mlx5_core 0012:03:00.1: mlx5e_netdev_init_profile:6214:(pid 37199): mlx5e_priv_init failed, err=-12 mlx5_core 0012:03:00.1 gpu3rdma1: mlx5e_netdev_change_profile: failed to rollback to orig profile, -12 $ devlink dev reload pci/0000:00:03.0 ==> oops BUG: kernel NULL pointer dereference, address: 0000000000000520 #PF: supervisor read access in kernel mode #PF: error_code(0x0000) - not-present page PGD 0 P4D 0 Oops: Oops: 0000 [#1] SMP NOPTI CPU: 3 UID: 0 PID: 521 Comm: devlink Not tainted 6.18.0-rc5+ #117 PREEMPT(voluntary) Hardware name: QEMU Standard PC (Q35 + ICH9, 2009), BIOS 1.16.3-2.fc40 04/01/2014 RIP: 0010:mlx5e_remove+0x68/0x130 RSP: 0018:ffffc900034838f0 EFLAGS: 00010246 RAX: ffff88810283c380 RBX: ffff888101874400 RCX: ffffffff826ffc45 RDX: 0000000000000000 RSI: 0000000000000001 RDI: 0000000000000000 RBP: ffff888102d789c0 R08: ffff8881007137f0 R09: ffff888100264e10 R10: ffffc90003483898 R11: ffffc900034838a0 R12: ffff888100d261a0 R13: ffff888100d261a0 R14: ffff8881018749a0 R15: ffff888101874400 FS: 00007f8565fea740(0000) GS:ffff88856a759000(0000) knlGS:0000000000000000 CS: 0010 DS: 0000 ES: 0000 CR0: 0000000080050033 CR2: 0000000000000520 CR3: 000000010b11a004 CR4: 0000000000370ef0 Call Trace: device_release_driver_internal+0x19c/0x200 bus_remove_device+0xc6/0x130 device_del+0x160/0x3d0 ? devl_param_driverinit_value_get+0x2d/0x90 mlx5_detach_device+0x89/0xe0 mlx5_unload_one_devl_locked+0x3a/0x70 mlx5_devlink_reload_down+0xc8/0x220 devlink_reload+0x7d/0x260 devlink_nl_reload_doit+0x45b/0x5a0 genl_family_rcv_msg_doit+0xe8/0x140 Fixes: ee75f1fc44dd ("net/mlx5e: Create separate devlink instance for ethernet auxiliary device") Fixes: c4d7eb57687f ("net/mxl5e: Add change profile method") Signed-off-by: Saeed Mahameed Link: https://patch.msgid.link/20260108212657.25090-3-saeed@kernel.org Signed-off-by: Jakub Kicinski Signed-off-by: Sasha Levin --- drivers/net/ethernet/mellanox/mlx5/core/en.h | 2 +- .../net/ethernet/mellanox/mlx5/core/en_main.c | 20 ++++++++++--------- 2 files changed, 12 insertions(+), 10 deletions(-) diff --git a/drivers/net/ethernet/mellanox/mlx5/core/en.h b/drivers/net/ethernet/mellanox/mlx5/core/en.h index cfdbeb21b61cfe..bc1b343f89a25a 100644 --- a/drivers/net/ethernet/mellanox/mlx5/core/en.h +++ b/drivers/net/ethernet/mellanox/mlx5/core/en.h @@ -963,7 +963,7 @@ struct mlx5e_priv { }; struct mlx5e_dev { - struct mlx5e_priv *priv; + struct net_device *netdev; struct devlink_port dl_port; }; diff --git a/drivers/net/ethernet/mellanox/mlx5/core/en_main.c b/drivers/net/ethernet/mellanox/mlx5/core/en_main.c index 3850c267dfc024..dcf1cd34887093 100644 --- a/drivers/net/ethernet/mellanox/mlx5/core/en_main.c +++ b/drivers/net/ethernet/mellanox/mlx5/core/en_main.c @@ -6635,8 +6635,8 @@ static int _mlx5e_resume(struct auxiliary_device *adev) { struct mlx5_adev *edev = container_of(adev, struct mlx5_adev, adev); struct mlx5e_dev *mlx5e_dev = auxiliary_get_drvdata(adev); - struct mlx5e_priv *priv = mlx5e_dev->priv; - struct net_device *netdev = priv->netdev; + struct mlx5e_priv *priv = netdev_priv(mlx5e_dev->netdev); + struct net_device *netdev = mlx5e_dev->netdev; struct mlx5_core_dev *mdev = edev->mdev; struct mlx5_core_dev *pos, *to; int err, i; @@ -6682,10 +6682,11 @@ static int mlx5e_resume(struct auxiliary_device *adev) static int _mlx5e_suspend(struct auxiliary_device *adev, bool pre_netdev_reg) { + struct mlx5_adev *edev = container_of(adev, struct mlx5_adev, adev); struct mlx5e_dev *mlx5e_dev = auxiliary_get_drvdata(adev); - struct mlx5e_priv *priv = mlx5e_dev->priv; - struct net_device *netdev = priv->netdev; - struct mlx5_core_dev *mdev = priv->mdev; + struct mlx5e_priv *priv = netdev_priv(mlx5e_dev->netdev); + struct net_device *netdev = mlx5e_dev->netdev; + struct mlx5_core_dev *mdev = edev->mdev; struct mlx5_core_dev *pos; int i; @@ -6746,11 +6747,11 @@ static int _mlx5e_probe(struct auxiliary_device *adev) goto err_devlink_port_unregister; } SET_NETDEV_DEVLINK_PORT(netdev, &mlx5e_dev->dl_port); + mlx5e_dev->netdev = netdev; mlx5e_build_nic_netdev(netdev); priv = netdev_priv(netdev); - mlx5e_dev->priv = priv; priv->profile = profile; priv->ppriv = NULL; @@ -6813,7 +6814,8 @@ static void _mlx5e_remove(struct auxiliary_device *adev) { struct mlx5_adev *edev = container_of(adev, struct mlx5_adev, adev); struct mlx5e_dev *mlx5e_dev = auxiliary_get_drvdata(adev); - struct mlx5e_priv *priv = mlx5e_dev->priv; + struct net_device *netdev = mlx5e_dev->netdev; + struct mlx5e_priv *priv = netdev_priv(netdev); struct mlx5_core_dev *mdev = edev->mdev; mlx5_core_uplink_netdev_set(mdev, NULL); @@ -6822,8 +6824,8 @@ static void _mlx5e_remove(struct auxiliary_device *adev) * if it's from legacy mode. If from switchdev mode, it * is already unregistered before changing to NIC profile. */ - if (priv->netdev->reg_state == NETREG_REGISTERED) { - unregister_netdev(priv->netdev); + if (netdev->reg_state == NETREG_REGISTERED) { + unregister_netdev(netdev); _mlx5e_suspend(adev, false); } else { struct mlx5_core_dev *pos; From 66a25f6b7c0bfd84e6d27b536f5d24116dbd52da Mon Sep 17 00:00:00 2001 From: Saeed Mahameed Date: Thu, 8 Jan 2026 13:26:56 -0800 Subject: [PATCH 2445/3902] net/mlx5e: Pass netdev to mlx5e_destroy_netdev instead of priv [ Upstream commit 4ef8512e1427111f7ba92b4a847d181ff0aeec42 ] mlx5e_priv is an unstable structure that can be memset(0) if profile attaching fails. Pass netdev to mlx5e_destroy_netdev() to guarantee it will work on a valid netdev. On mlx5e_remove: Check validity of priv->profile, before attempting to cleanup any resources that might be not there. This fixes a kernel oops in mlx5e_remove when switchdev mode fails due to change profile failure. $ devlink dev eswitch set pci/0000:00:03.0 mode switchdev Error: mlx5_core: Failed setting eswitch to offloads. dmesg: workqueue: Failed to create a rescuer kthread for wq "mlx5e": -EINTR mlx5_core 0012:03:00.1: mlx5e_netdev_init_profile:6214:(pid 37199): mlx5e_priv_init failed, err=-12 mlx5_core 0012:03:00.1 gpu3rdma1: mlx5e_netdev_change_profile: new profile init failed, -12 workqueue: Failed to create a rescuer kthread for wq "mlx5e": -EINTR mlx5_core 0012:03:00.1: mlx5e_netdev_init_profile:6214:(pid 37199): mlx5e_priv_init failed, err=-12 mlx5_core 0012:03:00.1 gpu3rdma1: mlx5e_netdev_change_profile: failed to rollback to orig profile, -12 $ devlink dev reload pci/0000:00:03.0 ==> oops BUG: kernel NULL pointer dereference, address: 0000000000000370 PGD 0 P4D 0 Oops: Oops: 0000 [#1] SMP NOPTI CPU: 15 UID: 0 PID: 520 Comm: devlink Not tainted 6.18.0-rc5+ #115 PREEMPT(voluntary) Hardware name: QEMU Standard PC (Q35 + ICH9, 2009), BIOS 1.16.3-2.fc40 04/01/2014 RIP: 0010:mlx5e_dcbnl_dscp_app+0x23/0x100 RSP: 0018:ffffc9000083f8b8 EFLAGS: 00010286 RAX: ffff8881126fc380 RBX: ffff8881015ac400 RCX: ffffffff826ffc45 RDX: 0000000000000000 RSI: 0000000000000001 RDI: ffff8881035109c0 RBP: ffff8881035109c0 R08: ffff888101e3e838 R09: ffff888100264e10 R10: ffffc9000083f898 R11: ffffc9000083f8a0 R12: ffff888101b921a0 R13: ffff888101b921a0 R14: ffff8881015ac9a0 R15: ffff8881015ac400 FS: 00007f789a3c8740(0000) GS:ffff88856aa59000(0000) knlGS:0000000000000000 CS: 0010 DS: 0000 ES: 0000 CR0: 0000000080050033 CR2: 0000000000000370 CR3: 000000010b6c0001 CR4: 0000000000370ef0 Call Trace: mlx5e_remove+0x57/0x110 device_release_driver_internal+0x19c/0x200 bus_remove_device+0xc6/0x130 device_del+0x160/0x3d0 ? devl_param_driverinit_value_get+0x2d/0x90 mlx5_detach_device+0x89/0xe0 mlx5_unload_one_devl_locked+0x3a/0x70 mlx5_devlink_reload_down+0xc8/0x220 devlink_reload+0x7d/0x260 devlink_nl_reload_doit+0x45b/0x5a0 genl_family_rcv_msg_doit+0xe8/0x140 Fixes: c4d7eb57687f ("net/mxl5e: Add change profile method") Signed-off-by: Saeed Mahameed Reviewed-by: Shay Drori Reviewed-by: Tariq Toukan Link: https://patch.msgid.link/20260108212657.25090-4-saeed@kernel.org Signed-off-by: Jakub Kicinski Signed-off-by: Sasha Levin --- drivers/net/ethernet/mellanox/mlx5/core/en.h | 2 +- drivers/net/ethernet/mellanox/mlx5/core/en_main.c | 15 +++++++++------ drivers/net/ethernet/mellanox/mlx5/core/en_rep.c | 4 ++-- 3 files changed, 12 insertions(+), 9 deletions(-) diff --git a/drivers/net/ethernet/mellanox/mlx5/core/en.h b/drivers/net/ethernet/mellanox/mlx5/core/en.h index bc1b343f89a25a..b34b85539f3b1c 100644 --- a/drivers/net/ethernet/mellanox/mlx5/core/en.h +++ b/drivers/net/ethernet/mellanox/mlx5/core/en.h @@ -1238,7 +1238,7 @@ struct net_device * mlx5e_create_netdev(struct mlx5_core_dev *mdev, const struct mlx5e_profile *profile); int mlx5e_attach_netdev(struct mlx5e_priv *priv); void mlx5e_detach_netdev(struct mlx5e_priv *priv); -void mlx5e_destroy_netdev(struct mlx5e_priv *priv); +void mlx5e_destroy_netdev(struct net_device *netdev); int mlx5e_netdev_change_profile(struct net_device *netdev, struct mlx5_core_dev *mdev, const struct mlx5e_profile *new_profile, diff --git a/drivers/net/ethernet/mellanox/mlx5/core/en_main.c b/drivers/net/ethernet/mellanox/mlx5/core/en_main.c index dcf1cd34887093..3863fb40ff929c 100644 --- a/drivers/net/ethernet/mellanox/mlx5/core/en_main.c +++ b/drivers/net/ethernet/mellanox/mlx5/core/en_main.c @@ -6623,11 +6623,12 @@ void mlx5e_netdev_attach_nic_profile(struct net_device *netdev, mlx5e_netdev_change_profile(netdev, mdev, &mlx5e_nic_profile, NULL); } -void mlx5e_destroy_netdev(struct mlx5e_priv *priv) +void mlx5e_destroy_netdev(struct net_device *netdev) { - struct net_device *netdev = priv->netdev; + struct mlx5e_priv *priv = netdev_priv(netdev); - mlx5e_priv_cleanup(priv); + if (priv->profile) + mlx5e_priv_cleanup(priv); free_netdev(netdev); } @@ -6784,7 +6785,7 @@ static int _mlx5e_probe(struct auxiliary_device *adev) err_profile_cleanup: profile->cleanup(priv); err_destroy_netdev: - mlx5e_destroy_netdev(priv); + mlx5e_destroy_netdev(netdev); err_devlink_port_unregister: mlx5e_devlink_port_unregister(mlx5e_dev); err_devlink_unregister: @@ -6819,7 +6820,9 @@ static void _mlx5e_remove(struct auxiliary_device *adev) struct mlx5_core_dev *mdev = edev->mdev; mlx5_core_uplink_netdev_set(mdev, NULL); - mlx5e_dcbnl_delete_app(priv); + + if (priv->profile) + mlx5e_dcbnl_delete_app(priv); /* When unload driver, the netdev is in registered state * if it's from legacy mode. If from switchdev mode, it * is already unregistered before changing to NIC profile. @@ -6840,7 +6843,7 @@ static void _mlx5e_remove(struct auxiliary_device *adev) /* Avoid cleanup if profile rollback failed. */ if (priv->profile) priv->profile->cleanup(priv); - mlx5e_destroy_netdev(priv); + mlx5e_destroy_netdev(netdev); mlx5e_devlink_port_unregister(mlx5e_dev); mlx5e_destroy_devlink(mlx5e_dev); } diff --git a/drivers/net/ethernet/mellanox/mlx5/core/en_rep.c b/drivers/net/ethernet/mellanox/mlx5/core/en_rep.c index 2f6aa5e61747c9..8b65441246244b 100644 --- a/drivers/net/ethernet/mellanox/mlx5/core/en_rep.c +++ b/drivers/net/ethernet/mellanox/mlx5/core/en_rep.c @@ -1611,7 +1611,7 @@ mlx5e_vport_vf_rep_load(struct mlx5_core_dev *dev, struct mlx5_eswitch_rep *rep) priv->profile->cleanup(priv); err_destroy_netdev: - mlx5e_destroy_netdev(netdev_priv(netdev)); + mlx5e_destroy_netdev(netdev); return err; } @@ -1666,7 +1666,7 @@ mlx5e_vport_rep_unload(struct mlx5_eswitch_rep *rep) mlx5e_rep_vnic_reporter_destroy(priv); mlx5e_detach_netdev(priv); priv->profile->cleanup(priv); - mlx5e_destroy_netdev(priv); + mlx5e_destroy_netdev(netdev); free_ppriv: kvfree(ppriv); /* mlx5e_rep_priv */ } From fcae8e1b9acd8756971fd5bbf1ec1365fd1f68e7 Mon Sep 17 00:00:00 2001 From: Saeed Mahameed Date: Thu, 8 Jan 2026 13:26:57 -0800 Subject: [PATCH 2446/3902] net/mlx5e: Restore destroying state bit after profile cleanup [ Upstream commit 5629f8859dca7ef74d7314b60de6a957f23166c0 ] Profile rollback can fail in mlx5e_netdev_change_profile() and we will end up with invalid mlx5e_priv memset to 0, we must maintain the 'destroying' bit in order to gracefully shutdown even if the profile/priv are not valid. This patch maintains the previous state of the 'destroying' state of mlx5e_priv after priv cleanup, to allow the remove flow to cleanup common resources from mlx5_core to avoid FW fatal errors as seen below: $ devlink dev eswitch set pci/0000:00:03.0 mode switchdev Error: mlx5_core: Failed setting eswitch to offloads. dmesg: mlx5_core 0000:00:03.0 enp0s3np0: failed to rollback to orig profile, ... $ devlink dev reload pci/0000:00:03.0 mlx5_core 0000:00:03.0: E-Switch: Disable: mode(LEGACY), nvfs(0), necvfs(0), active vports(0) mlx5_core 0000:00:03.0: poll_health:803:(pid 519): Fatal error 3 detected mlx5_core 0000:00:03.0: firmware version: 28.41.1000 mlx5_core 0000:00:03.0: 0.000 Gb/s available PCIe bandwidth (Unknown x255 link) mlx5_core 0000:00:03.0: mlx5_function_enable:1200:(pid 519): enable hca failed mlx5_core 0000:00:03.0: mlx5_function_enable:1200:(pid 519): enable hca failed mlx5_core 0000:00:03.0: mlx5_health_try_recover:340:(pid 141): handling bad device here mlx5_core 0000:00:03.0: mlx5_handle_bad_state:285:(pid 141): Expected to see disabled NIC but it is full driver mlx5_core 0000:00:03.0: mlx5_error_sw_reset:236:(pid 141): start mlx5_core 0000:00:03.0: NIC IFC still 0 after 4000ms. Fixes: c4d7eb57687f ("net/mxl5e: Add change profile method") Signed-off-by: Saeed Mahameed Reviewed-by: Tariq Toukan Link: https://patch.msgid.link/20260108212657.25090-5-saeed@kernel.org Signed-off-by: Jakub Kicinski Signed-off-by: Sasha Levin --- drivers/net/ethernet/mellanox/mlx5/core/en_main.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/drivers/net/ethernet/mellanox/mlx5/core/en_main.c b/drivers/net/ethernet/mellanox/mlx5/core/en_main.c index 3863fb40ff929c..f8d9968542d9c1 100644 --- a/drivers/net/ethernet/mellanox/mlx5/core/en_main.c +++ b/drivers/net/ethernet/mellanox/mlx5/core/en_main.c @@ -6305,6 +6305,7 @@ int mlx5e_priv_init(struct mlx5e_priv *priv, void mlx5e_priv_cleanup(struct mlx5e_priv *priv) { + bool destroying = test_bit(MLX5E_STATE_DESTROYING, &priv->state); int i; /* bail if change profile failed and also rollback failed */ @@ -6332,6 +6333,8 @@ void mlx5e_priv_cleanup(struct mlx5e_priv *priv) } memset(priv, 0, sizeof(*priv)); + if (destroying) /* restore destroying bit, to allow unload */ + set_bit(MLX5E_STATE_DESTROYING, &priv->state); } static unsigned int mlx5e_get_max_num_txqs(struct mlx5_core_dev *mdev, From 6cb008f1bb23e023dfe615cca5df14570dfc8da5 Mon Sep 17 00:00:00 2001 From: Jiasheng Jiang Date: Sun, 11 Jan 2026 19:20:37 +0000 Subject: [PATCH 2447/3902] btrfs: fix memory leaks in create_space_info() error paths [ Upstream commit a11224a016d6d1d46a4d9b6573244448a80d4d7f ] In create_space_info(), the 'space_info' object is allocated at the beginning of the function. However, there are two error paths where the function returns an error code without freeing the allocated memory: 1. When create_space_info_sub_group() fails in zoned mode. 2. When btrfs_sysfs_add_space_info_type() fails. In both cases, 'space_info' has not yet been added to the fs_info->space_info list, resulting in a memory leak. Fix this by adding an error handling label to kfree(space_info) before returning. Fixes: 2be12ef79fe9 ("btrfs: Separate space_info create/update") Reviewed-by: Qu Wenruo Signed-off-by: Jiasheng Jiang Signed-off-by: David Sterba Signed-off-by: Sasha Levin --- fs/btrfs/space-info.c | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/fs/btrfs/space-info.c b/fs/btrfs/space-info.c index 85c466c85910ac..a6f94e9f559158 100644 --- a/fs/btrfs/space-info.c +++ b/fs/btrfs/space-info.c @@ -305,18 +305,22 @@ static int create_space_info(struct btrfs_fs_info *info, u64 flags) 0); if (ret) - return ret; + goto out_free; } ret = btrfs_sysfs_add_space_info_type(info, space_info); if (ret) - return ret; + goto out_free; list_add(&space_info->list, &info->space_info); if (flags & BTRFS_BLOCK_GROUP_DATA) info->data_sinfo = space_info; return ret; + +out_free: + kfree(space_info); + return ret; } int btrfs_init_space_info(struct btrfs_fs_info *fs_info) From 1b3ed6c512cfd49a46977eccd82c5cc1add0e626 Mon Sep 17 00:00:00 2001 From: Li Ming Date: Mon, 12 Jan 2026 20:05:26 +0800 Subject: [PATCH 2448/3902] cxl/hdm: Fix potential infinite loop in __cxl_dpa_reserve() [ Upstream commit d4026a44626490dc4eca4dd2c4d0816338fa179b ] In __cxl_dpa_reserve(), it will check if the new resource range is included in one of paritions of the cxl memory device. cxlds->nr_paritions is used to represent how many partitions information the cxl memory device has. In the loop, if driver cannot find a partition including the new resource range, it will be an infinite loop. [ dj: Removed incorrect fixes tag ] Fixes: 991d98f17d31 ("cxl: Make cxl_dpa_alloc() DPA partition number agnostic") Signed-off-by: Li Ming Reviewed-by: Ira Weiny Reviewed-by: Dave Jiang Link: https://patch.msgid.link/20260112120526.530232-1-ming.li@zohomail.com Signed-off-by: Dave Jiang Signed-off-by: Sasha Levin --- drivers/cxl/core/hdm.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/cxl/core/hdm.c b/drivers/cxl/core/hdm.c index d3a094ca01ad9e..20dd6381080626 100644 --- a/drivers/cxl/core/hdm.c +++ b/drivers/cxl/core/hdm.c @@ -403,7 +403,7 @@ static int __cxl_dpa_reserve(struct cxl_endpoint_decoder *cxled, * is not set. */ if (cxled->part < 0) - for (int i = 0; cxlds->nr_partitions; i++) + for (int i = 0; i < cxlds->nr_partitions; i++) if (resource_contains(&cxlds->part[i].res, res)) { cxled->part = i; break; From aa4c066229b05fc3d3c5f42693d25b1828533b6e Mon Sep 17 00:00:00 2001 From: Kery Qi Date: Fri, 9 Jan 2026 00:42:57 +0800 Subject: [PATCH 2449/3902] net: octeon_ep_vf: fix free_irq dev_id mismatch in IRQ rollback [ Upstream commit f93fc5d12d69012788f82151bee55fce937e1432 ] octep_vf_request_irqs() requests MSI-X queue IRQs with dev_id set to ioq_vector. If request_irq() fails part-way, the rollback loop calls free_irq() with dev_id set to 'oct', which does not match the original dev_id and may leave the irqaction registered. This can keep IRQ handlers alive while ioq_vector is later freed during unwind/teardown, leading to a use-after-free or crash when an interrupt fires. Fix the error path to free IRQs with the same ioq_vector dev_id used during request_irq(). Fixes: 1cd3b407977c ("octeon_ep_vf: add Tx/Rx processing and interrupt support") Signed-off-by: Kery Qi Link: https://patch.msgid.link/20260108164256.1749-2-qikeyu2017@gmail.com Signed-off-by: Jakub Kicinski Signed-off-by: Sasha Levin --- drivers/net/ethernet/marvell/octeon_ep_vf/octep_vf_main.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/net/ethernet/marvell/octeon_ep_vf/octep_vf_main.c b/drivers/net/ethernet/marvell/octeon_ep_vf/octep_vf_main.c index 420c3f4cf7417f..1d9760b4b8f471 100644 --- a/drivers/net/ethernet/marvell/octeon_ep_vf/octep_vf_main.c +++ b/drivers/net/ethernet/marvell/octeon_ep_vf/octep_vf_main.c @@ -218,7 +218,7 @@ static int octep_vf_request_irqs(struct octep_vf_device *oct) ioq_irq_err: while (i) { --i; - free_irq(oct->msix_entries[i].vector, oct); + free_irq(oct->msix_entries[i].vector, oct->ioq_vector[i]); } return -1; } From d4416963970880139b31ac25a955b58cd54f4b95 Mon Sep 17 00:00:00 2001 From: Jijie Shao Date: Thu, 8 Jan 2026 15:14:09 +0800 Subject: [PATCH 2450/3902] net: phy: motorcomm: fix duplex setting error for phy leds [ Upstream commit e02f2a0f1f9b6d4f0c620de2ce037d4436b58f70 ] fix duplex setting error for phy leds Fixes: 355b82c54c12 ("net: phy: motorcomm: Add support for PHY LEDs on YT8521") Signed-off-by: Jijie Shao Reviewed-by: Andrew Lunn Link: https://patch.msgid.link/20260108071409.2750607-1-shaojijie@huawei.com Signed-off-by: Jakub Kicinski Signed-off-by: Sasha Levin --- drivers/net/phy/motorcomm.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/net/phy/motorcomm.c b/drivers/net/phy/motorcomm.c index a3593e66305941..b49897500a5925 100644 --- a/drivers/net/phy/motorcomm.c +++ b/drivers/net/phy/motorcomm.c @@ -1741,10 +1741,10 @@ static int yt8521_led_hw_control_set(struct phy_device *phydev, u8 index, val |= YT8521_LED_1000_ON_EN; if (test_bit(TRIGGER_NETDEV_FULL_DUPLEX, &rules)) - val |= YT8521_LED_HDX_ON_EN; + val |= YT8521_LED_FDX_ON_EN; if (test_bit(TRIGGER_NETDEV_HALF_DUPLEX, &rules)) - val |= YT8521_LED_FDX_ON_EN; + val |= YT8521_LED_HDX_ON_EN; if (test_bit(TRIGGER_NETDEV_TX, &rules) || test_bit(TRIGGER_NETDEV_RX, &rules)) From e1a4a4795c5a1214b32d3b75b454a41f943d7764 Mon Sep 17 00:00:00 2001 From: Lorenzo Bianconi Date: Fri, 9 Jan 2026 10:29:06 +0100 Subject: [PATCH 2451/3902] net: airoha: Fix typo in airoha_ppe_setup_tc_block_cb definition [ Upstream commit dfdf774656205515b2d6ad94fce63c7ccbe92d91 ] Fix Typo in airoha_ppe_dev_setup_tc_block_cb routine definition when CONFIG_NET_AIROHA is not enabled. Reported-by: kernel test robot Closes: https://lore.kernel.org/oe-kbuild-all/202601090517.Fj6v501r-lkp@intel.com/ Fixes: f45fc18b6de04 ("net: airoha: Add airoha_ppe_dev struct definition") Signed-off-by: Lorenzo Bianconi Link: https://patch.msgid.link/20260109-airoha_ppe_dev_setup_tc_block_cb-typo-v1-1-282e8834a9f9@kernel.org Signed-off-by: Jakub Kicinski Signed-off-by: Sasha Levin --- include/linux/soc/airoha/airoha_offload.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/include/linux/soc/airoha/airoha_offload.h b/include/linux/soc/airoha/airoha_offload.h index 1a33f846afafa9..0e82f1f4d36c4a 100644 --- a/include/linux/soc/airoha/airoha_offload.h +++ b/include/linux/soc/airoha/airoha_offload.h @@ -51,8 +51,8 @@ static inline void airoha_ppe_put_dev(struct airoha_ppe_dev *dev) { } -static inline int airoha_ppe_setup_tc_block_cb(struct airoha_ppe_dev *dev, - void *type_data) +static inline int airoha_ppe_dev_setup_tc_block_cb(struct airoha_ppe_dev *dev, + void *type_data) { return -EOPNOTSUPP; } From aaa969ffe3b4da17c96c54d7c88ea838d99cf10e Mon Sep 17 00:00:00 2001 From: Richard Fitzgerald Date: Tue, 13 Jan 2026 13:09:54 +0000 Subject: [PATCH 2452/3902] ALSA: hda/cirrus_scodec_test: Fix incorrect setup of gpiochip [ Upstream commit c5e96e54eca3876d4ce8857e2e22adbe9f44f4a2 ] Set gpiochip parent to the struct device of the dummy GPIO driver so that the software node will be associated with the GPIO chip. The recent commit e5d527be7e698 ("gpio: swnode: don't use the swnode's name as the key for GPIO lookup") broke cirrus_scodec_test, because the software node no longer gets associated with the GPIO driver by name. Instead, setting struct gpio_chip.parent to the owning struct device will find the node using a normal fwnode lookup. Signed-off-by: Richard Fitzgerald Fixes: 2144833e7b414 ("ALSA: hda: cirrus_scodec: Add KUnit test") Link: https://patch.msgid.link/20260113130954.574670-1-rf@opensource.cirrus.com Signed-off-by: Takashi Iwai Signed-off-by: Sasha Levin --- sound/hda/codecs/side-codecs/cirrus_scodec_test.c | 1 + 1 file changed, 1 insertion(+) diff --git a/sound/hda/codecs/side-codecs/cirrus_scodec_test.c b/sound/hda/codecs/side-codecs/cirrus_scodec_test.c index 3cca750857b689..159ac80a931440 100644 --- a/sound/hda/codecs/side-codecs/cirrus_scodec_test.c +++ b/sound/hda/codecs/side-codecs/cirrus_scodec_test.c @@ -103,6 +103,7 @@ static int cirrus_scodec_test_gpio_probe(struct platform_device *pdev) /* GPIO core modifies our struct gpio_chip so use a copy */ gpio_priv->chip = cirrus_scodec_test_gpio_chip; + gpio_priv->chip.parent = &pdev->dev; ret = devm_gpiochip_add_data(&pdev->dev, &gpio_priv->chip, gpio_priv); if (ret) return dev_err_probe(&pdev->dev, ret, "Failed to add gpiochip\n"); From 78ee2958b096c36b5c8b501f758b06ed64dd47ab Mon Sep 17 00:00:00 2001 From: Richard Fitzgerald Date: Tue, 13 Jan 2026 13:40:56 +0000 Subject: [PATCH 2453/3902] ALSA: hda/cirrus_scodec_test: Fix test suite name [ Upstream commit 6a0243c4020636482797acfd48d7d9b0ea2f2a20 ] Change the test suite name string to "snd-hda-cirrus-scodec-test". It was incorrectly named "snd-hda-scodec-cs35l56-test", a leftover from when the code under test was actually in the cs35l56 driver. Signed-off-by: Richard Fitzgerald Fixes: 2144833e7b414 ("ALSA: hda: cirrus_scodec: Add KUnit test") Link: https://patch.msgid.link/20260113134056.619051-1-rf@opensource.cirrus.com Signed-off-by: Takashi Iwai Signed-off-by: Sasha Levin --- sound/hda/codecs/side-codecs/cirrus_scodec_test.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sound/hda/codecs/side-codecs/cirrus_scodec_test.c b/sound/hda/codecs/side-codecs/cirrus_scodec_test.c index 159ac80a931440..dc35932b6b22f1 100644 --- a/sound/hda/codecs/side-codecs/cirrus_scodec_test.c +++ b/sound/hda/codecs/side-codecs/cirrus_scodec_test.c @@ -320,7 +320,7 @@ static struct kunit_case cirrus_scodec_test_cases[] = { }; static struct kunit_suite cirrus_scodec_test_suite = { - .name = "snd-hda-scodec-cs35l56-test", + .name = "snd-hda-cirrus-scodec-test", .init = cirrus_scodec_test_case_init, .test_cases = cirrus_scodec_test_cases, }; From 11dd9a9ef4dc4507a15a69b8511a0013c6c28fa3 Mon Sep 17 00:00:00 2001 From: Aditya Garg Date: Mon, 12 Jan 2026 02:01:33 -0800 Subject: [PATCH 2454/3902] net: hv_netvsc: reject RSS hash key programming without RX indirection table [ Upstream commit d23564955811da493f34412d7de60fa268c8cb50 ] RSS configuration requires a valid RX indirection table. When the device reports a single receive queue, rndis_filter_device_add() does not allocate an indirection table, accepting RSS hash key updates in this state leads to a hang. Fix this by gating netvsc_set_rxfh() on ndc->rx_table_sz and return -EOPNOTSUPP when the table is absent. This aligns set_rxfh with the device capabilities and prevents incorrect behavior. Fixes: 962f3fee83a4 ("netvsc: add ethtool ops to get/set RSS key") Signed-off-by: Aditya Garg Reviewed-by: Dipayaan Roy Reviewed-by: Haiyang Zhang Link: https://patch.msgid.link/1768212093-1594-1-git-send-email-gargaditya@linux.microsoft.com Signed-off-by: Jakub Kicinski Signed-off-by: Sasha Levin --- drivers/net/hyperv/netvsc_drv.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/drivers/net/hyperv/netvsc_drv.c b/drivers/net/hyperv/netvsc_drv.c index 39c892e46cb017..25a358524a096e 100644 --- a/drivers/net/hyperv/netvsc_drv.c +++ b/drivers/net/hyperv/netvsc_drv.c @@ -1757,6 +1757,9 @@ static int netvsc_set_rxfh(struct net_device *dev, rxfh->hfunc != ETH_RSS_HASH_TOP) return -EOPNOTSUPP; + if (!ndc->rx_table_sz) + return -EOPNOTSUPP; + rndis_dev = ndev->extension; if (rxfh->indir) { for (i = 0; i < ndc->rx_table_sz; i++) From 722de945216144af7cd4d39bdeb936108d2595a7 Mon Sep 17 00:00:00 2001 From: Eric Dumazet Date: Mon, 12 Jan 2026 10:38:25 +0000 Subject: [PATCH 2455/3902] dst: fix races in rt6_uncached_list_del() and rt_del_uncached_list() [ Upstream commit 9a6f0c4d5796ab89b5a28a890ce542344d58bd69 ] syzbot was able to crash the kernel in rt6_uncached_list_flush_dev() in an interesting way [1] Crash happens in list_del_init()/INIT_LIST_HEAD() while writing list->prev, while the prior write on list->next went well. static inline void INIT_LIST_HEAD(struct list_head *list) { WRITE_ONCE(list->next, list); // This went well WRITE_ONCE(list->prev, list); // Crash, @list has been freed. } Issue here is that rt6_uncached_list_del() did not attempt to lock ul->lock, as list_empty(&rt->dst.rt_uncached) returned true because the WRITE_ONCE(list->next, list) happened on the other CPU. We might use list_del_init_careful() and list_empty_careful(), or make sure rt6_uncached_list_del() always grabs the spinlock whenever rt->dst.rt_uncached_list has been set. A similar fix is neeed for IPv4. [1] BUG: KASAN: slab-use-after-free in INIT_LIST_HEAD include/linux/list.h:46 [inline] BUG: KASAN: slab-use-after-free in list_del_init include/linux/list.h:296 [inline] BUG: KASAN: slab-use-after-free in rt6_uncached_list_flush_dev net/ipv6/route.c:191 [inline] BUG: KASAN: slab-use-after-free in rt6_disable_ip+0x633/0x730 net/ipv6/route.c:5020 Write of size 8 at addr ffff8880294cfa78 by task kworker/u8:14/3450 CPU: 0 UID: 0 PID: 3450 Comm: kworker/u8:14 Tainted: G L syzkaller #0 PREEMPT_{RT,(full)} Tainted: [L]=SOFTLOCKUP Hardware name: Google Google Compute Engine/Google Compute Engine, BIOS Google 10/25/2025 Workqueue: netns cleanup_net Call Trace: dump_stack_lvl+0xe8/0x150 lib/dump_stack.c:120 print_address_description mm/kasan/report.c:378 [inline] print_report+0xca/0x240 mm/kasan/report.c:482 kasan_report+0x118/0x150 mm/kasan/report.c:595 INIT_LIST_HEAD include/linux/list.h:46 [inline] list_del_init include/linux/list.h:296 [inline] rt6_uncached_list_flush_dev net/ipv6/route.c:191 [inline] rt6_disable_ip+0x633/0x730 net/ipv6/route.c:5020 addrconf_ifdown+0x143/0x18a0 net/ipv6/addrconf.c:3853 addrconf_notify+0x1bc/0x1050 net/ipv6/addrconf.c:-1 notifier_call_chain+0x19d/0x3a0 kernel/notifier.c:85 call_netdevice_notifiers_extack net/core/dev.c:2268 [inline] call_netdevice_notifiers net/core/dev.c:2282 [inline] netif_close_many+0x29c/0x410 net/core/dev.c:1785 unregister_netdevice_many_notify+0xb50/0x2330 net/core/dev.c:12353 ops_exit_rtnl_list net/core/net_namespace.c:187 [inline] ops_undo_list+0x3dc/0x990 net/core/net_namespace.c:248 cleanup_net+0x4de/0x7b0 net/core/net_namespace.c:696 process_one_work kernel/workqueue.c:3257 [inline] process_scheduled_works+0xad1/0x1770 kernel/workqueue.c:3340 worker_thread+0x8a0/0xda0 kernel/workqueue.c:3421 kthread+0x711/0x8a0 kernel/kthread.c:463 ret_from_fork+0x510/0xa50 arch/x86/kernel/process.c:158 ret_from_fork_asm+0x1a/0x30 arch/x86/entry/entry_64.S:246 Allocated by task 803: kasan_save_stack mm/kasan/common.c:57 [inline] kasan_save_track+0x3e/0x80 mm/kasan/common.c:78 unpoison_slab_object mm/kasan/common.c:340 [inline] __kasan_slab_alloc+0x6c/0x80 mm/kasan/common.c:366 kasan_slab_alloc include/linux/kasan.h:253 [inline] slab_post_alloc_hook mm/slub.c:4953 [inline] slab_alloc_node mm/slub.c:5263 [inline] kmem_cache_alloc_noprof+0x18d/0x6c0 mm/slub.c:5270 dst_alloc+0x105/0x170 net/core/dst.c:89 ip6_dst_alloc net/ipv6/route.c:342 [inline] icmp6_dst_alloc+0x75/0x460 net/ipv6/route.c:3333 mld_sendpack+0x683/0xe60 net/ipv6/mcast.c:1844 mld_send_cr net/ipv6/mcast.c:2154 [inline] mld_ifc_work+0x83e/0xd60 net/ipv6/mcast.c:2693 process_one_work kernel/workqueue.c:3257 [inline] process_scheduled_works+0xad1/0x1770 kernel/workqueue.c:3340 worker_thread+0x8a0/0xda0 kernel/workqueue.c:3421 kthread+0x711/0x8a0 kernel/kthread.c:463 ret_from_fork+0x510/0xa50 arch/x86/kernel/process.c:158 ret_from_fork_asm+0x1a/0x30 arch/x86/entry/entry_64.S:246 Freed by task 20: kasan_save_stack mm/kasan/common.c:57 [inline] kasan_save_track+0x3e/0x80 mm/kasan/common.c:78 kasan_save_free_info+0x46/0x50 mm/kasan/generic.c:584 poison_slab_object mm/kasan/common.c:253 [inline] __kasan_slab_free+0x5c/0x80 mm/kasan/common.c:285 kasan_slab_free include/linux/kasan.h:235 [inline] slab_free_hook mm/slub.c:2540 [inline] slab_free mm/slub.c:6670 [inline] kmem_cache_free+0x18f/0x8d0 mm/slub.c:6781 dst_destroy+0x235/0x350 net/core/dst.c:121 rcu_do_batch kernel/rcu/tree.c:2605 [inline] rcu_core kernel/rcu/tree.c:2857 [inline] rcu_cpu_kthread+0xba5/0x1af0 kernel/rcu/tree.c:2945 smpboot_thread_fn+0x542/0xa60 kernel/smpboot.c:160 kthread+0x711/0x8a0 kernel/kthread.c:463 ret_from_fork+0x510/0xa50 arch/x86/kernel/process.c:158 ret_from_fork_asm+0x1a/0x30 arch/x86/entry/entry_64.S:246 Last potentially related work creation: kasan_save_stack+0x3e/0x60 mm/kasan/common.c:57 kasan_record_aux_stack+0xbd/0xd0 mm/kasan/generic.c:556 __call_rcu_common kernel/rcu/tree.c:3119 [inline] call_rcu+0xee/0x890 kernel/rcu/tree.c:3239 refdst_drop include/net/dst.h:266 [inline] skb_dst_drop include/net/dst.h:278 [inline] skb_release_head_state+0x71/0x360 net/core/skbuff.c:1156 skb_release_all net/core/skbuff.c:1180 [inline] __kfree_skb net/core/skbuff.c:1196 [inline] sk_skb_reason_drop+0xe9/0x170 net/core/skbuff.c:1234 kfree_skb_reason include/linux/skbuff.h:1322 [inline] tcf_kfree_skb_list include/net/sch_generic.h:1127 [inline] __dev_xmit_skb net/core/dev.c:4260 [inline] __dev_queue_xmit+0x26aa/0x3210 net/core/dev.c:4785 NF_HOOK_COND include/linux/netfilter.h:307 [inline] ip6_output+0x340/0x550 net/ipv6/ip6_output.c:247 NF_HOOK+0x9e/0x380 include/linux/netfilter.h:318 mld_sendpack+0x8d4/0xe60 net/ipv6/mcast.c:1855 mld_send_cr net/ipv6/mcast.c:2154 [inline] mld_ifc_work+0x83e/0xd60 net/ipv6/mcast.c:2693 process_one_work kernel/workqueue.c:3257 [inline] process_scheduled_works+0xad1/0x1770 kernel/workqueue.c:3340 worker_thread+0x8a0/0xda0 kernel/workqueue.c:3421 kthread+0x711/0x8a0 kernel/kthread.c:463 ret_from_fork+0x510/0xa50 arch/x86/kernel/process.c:158 ret_from_fork_asm+0x1a/0x30 arch/x86/entry/entry_64.S:246 The buggy address belongs to the object at ffff8880294cfa00 which belongs to the cache ip6_dst_cache of size 232 The buggy address is located 120 bytes inside of freed 232-byte region [ffff8880294cfa00, ffff8880294cfae8) The buggy address belongs to the physical page: page: refcount:0 mapcount:0 mapping:0000000000000000 index:0x0 pfn:0x294cf memcg:ffff88803536b781 flags: 0x80000000000000(node=0|zone=1) page_type: f5(slab) raw: 0080000000000000 ffff88802ff1c8c0 ffffea0000bf2bc0 dead000000000006 raw: 0000000000000000 00000000800c000c 00000000f5000000 ffff88803536b781 page dumped because: kasan: bad access detected page_owner tracks the page as allocated page last allocated via order 0, migratetype Unmovable, gfp_mask 0x52820(GFP_ATOMIC|__GFP_NOWARN|__GFP_NORETRY|__GFP_COMP), pid 9, tgid 9 (kworker/0:0), ts 91119585830, free_ts 91088628818 set_page_owner include/linux/page_owner.h:32 [inline] post_alloc_hook+0x234/0x290 mm/page_alloc.c:1857 prep_new_page mm/page_alloc.c:1865 [inline] get_page_from_freelist+0x28c0/0x2960 mm/page_alloc.c:3915 __alloc_frozen_pages_noprof+0x181/0x370 mm/page_alloc.c:5210 alloc_pages_mpol+0xd1/0x380 mm/mempolicy.c:2486 alloc_slab_page mm/slub.c:3075 [inline] allocate_slab+0x86/0x3b0 mm/slub.c:3248 new_slab mm/slub.c:3302 [inline] ___slab_alloc+0xb10/0x13e0 mm/slub.c:4656 __slab_alloc+0xc6/0x1f0 mm/slub.c:4779 __slab_alloc_node mm/slub.c:4855 [inline] slab_alloc_node mm/slub.c:5251 [inline] kmem_cache_alloc_noprof+0x101/0x6c0 mm/slub.c:5270 dst_alloc+0x105/0x170 net/core/dst.c:89 ip6_dst_alloc net/ipv6/route.c:342 [inline] icmp6_dst_alloc+0x75/0x460 net/ipv6/route.c:3333 mld_sendpack+0x683/0xe60 net/ipv6/mcast.c:1844 mld_send_cr net/ipv6/mcast.c:2154 [inline] mld_ifc_work+0x83e/0xd60 net/ipv6/mcast.c:2693 process_one_work kernel/workqueue.c:3257 [inline] process_scheduled_works+0xad1/0x1770 kernel/workqueue.c:3340 worker_thread+0x8a0/0xda0 kernel/workqueue.c:3421 kthread+0x711/0x8a0 kernel/kthread.c:463 ret_from_fork+0x510/0xa50 arch/x86/kernel/process.c:158 page last free pid 5859 tgid 5859 stack trace: reset_page_owner include/linux/page_owner.h:25 [inline] free_pages_prepare mm/page_alloc.c:1406 [inline] __free_frozen_pages+0xfe1/0x1170 mm/page_alloc.c:2943 discard_slab mm/slub.c:3346 [inline] __put_partials+0x149/0x170 mm/slub.c:3886 __slab_free+0x2af/0x330 mm/slub.c:5952 qlink_free mm/kasan/quarantine.c:163 [inline] qlist_free_all+0x97/0x100 mm/kasan/quarantine.c:179 kasan_quarantine_reduce+0x148/0x160 mm/kasan/quarantine.c:286 __kasan_slab_alloc+0x22/0x80 mm/kasan/common.c:350 kasan_slab_alloc include/linux/kasan.h:253 [inline] slab_post_alloc_hook mm/slub.c:4953 [inline] slab_alloc_node mm/slub.c:5263 [inline] kmem_cache_alloc_noprof+0x18d/0x6c0 mm/slub.c:5270 getname_flags+0xb8/0x540 fs/namei.c:146 getname include/linux/fs.h:2498 [inline] do_sys_openat2+0xbc/0x200 fs/open.c:1426 do_sys_open fs/open.c:1436 [inline] __do_sys_openat fs/open.c:1452 [inline] __se_sys_openat fs/open.c:1447 [inline] __x64_sys_openat+0x138/0x170 fs/open.c:1447 do_syscall_x64 arch/x86/entry/syscall_64.c:63 [inline] do_syscall_64+0xec/0xf80 arch/x86/entry/syscall_64.c:94 Fixes: 8d0b94afdca8 ("ipv6: Keep track of DST_NOCACHE routes in case of iface down/unregister") Fixes: 78df76a065ae ("ipv4: take rt_uncached_lock only if needed") Reported-by: syzbot+179fc225724092b8b2b2@syzkaller.appspotmail.com Closes: https://lore.kernel.org/netdev/6964cdf2.050a0220.eaf7.009d.GAE@google.com/T/#u Signed-off-by: Eric Dumazet Cc: Martin KaFai Lau Reviewed-by: David Ahern Link: https://patch.msgid.link/20260112103825.3810713-1-edumazet@google.com Signed-off-by: Jakub Kicinski Signed-off-by: Sasha Levin --- net/core/dst.c | 1 + net/ipv4/route.c | 4 ++-- net/ipv6/route.c | 4 ++-- 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/net/core/dst.c b/net/core/dst.c index e9d35f49c9e780..1dae26c51ebec0 100644 --- a/net/core/dst.c +++ b/net/core/dst.c @@ -68,6 +68,7 @@ void dst_init(struct dst_entry *dst, struct dst_ops *ops, dst->lwtstate = NULL; rcuref_init(&dst->__rcuref, 1); INIT_LIST_HEAD(&dst->rt_uncached); + dst->rt_uncached_list = NULL; dst->__use = 0; dst->lastuse = jiffies; dst->flags = flags; diff --git a/net/ipv4/route.c b/net/ipv4/route.c index b549d6a573073b..11d990703d31a9 100644 --- a/net/ipv4/route.c +++ b/net/ipv4/route.c @@ -1537,9 +1537,9 @@ void rt_add_uncached_list(struct rtable *rt) void rt_del_uncached_list(struct rtable *rt) { - if (!list_empty(&rt->dst.rt_uncached)) { - struct uncached_list *ul = rt->dst.rt_uncached_list; + struct uncached_list *ul = rt->dst.rt_uncached_list; + if (ul) { spin_lock_bh(&ul->lock); list_del_init(&rt->dst.rt_uncached); spin_unlock_bh(&ul->lock); diff --git a/net/ipv6/route.c b/net/ipv6/route.c index a3e051dc66ee07..e3a260a5564ba8 100644 --- a/net/ipv6/route.c +++ b/net/ipv6/route.c @@ -148,9 +148,9 @@ void rt6_uncached_list_add(struct rt6_info *rt) void rt6_uncached_list_del(struct rt6_info *rt) { - if (!list_empty(&rt->dst.rt_uncached)) { - struct uncached_list *ul = rt->dst.rt_uncached_list; + struct uncached_list *ul = rt->dst.rt_uncached_list; + if (ul) { spin_lock_bh(&ul->lock); list_del_init(&rt->dst.rt_uncached); spin_unlock_bh(&ul->lock); From 8b6dcb565e419846bd521e31d5e1f98e4d0e1179 Mon Sep 17 00:00:00 2001 From: Kuniyuki Iwashima Date: Tue, 13 Jan 2026 01:05:08 +0000 Subject: [PATCH 2456/3902] ipv6: Fix use-after-free in inet6_addr_del(). [ Upstream commit ddf96c393a33aef4887e2e406c76c2f8cda1419c ] syzbot reported use-after-free of inet6_ifaddr in inet6_addr_del(). [0] The cited commit accidentally moved ipv6_del_addr() for mngtmpaddr before reading its ifp->flags for temporary addresses in inet6_addr_del(). Let's move ipv6_del_addr() down to fix the UAF. [0]: BUG: KASAN: slab-use-after-free in inet6_addr_del.constprop.0+0x67a/0x6b0 net/ipv6/addrconf.c:3117 Read of size 4 at addr ffff88807b89c86c by task syz.3.1618/9593 CPU: 0 UID: 0 PID: 9593 Comm: syz.3.1618 Not tainted syzkaller #0 PREEMPT(full) Hardware name: Google Google Compute Engine/Google Compute Engine, BIOS Google 10/25/2025 Call Trace: __dump_stack lib/dump_stack.c:94 [inline] dump_stack_lvl+0x116/0x1f0 lib/dump_stack.c:120 print_address_description mm/kasan/report.c:378 [inline] print_report+0xcd/0x630 mm/kasan/report.c:482 kasan_report+0xe0/0x110 mm/kasan/report.c:595 inet6_addr_del.constprop.0+0x67a/0x6b0 net/ipv6/addrconf.c:3117 addrconf_del_ifaddr+0x11e/0x190 net/ipv6/addrconf.c:3181 inet6_ioctl+0x1e5/0x2b0 net/ipv6/af_inet6.c:582 sock_do_ioctl+0x118/0x280 net/socket.c:1254 sock_ioctl+0x227/0x6b0 net/socket.c:1375 vfs_ioctl fs/ioctl.c:51 [inline] __do_sys_ioctl fs/ioctl.c:597 [inline] __se_sys_ioctl fs/ioctl.c:583 [inline] __x64_sys_ioctl+0x18e/0x210 fs/ioctl.c:583 do_syscall_x64 arch/x86/entry/syscall_64.c:63 [inline] do_syscall_64+0xcd/0xf80 arch/x86/entry/syscall_64.c:94 entry_SYSCALL_64_after_hwframe+0x77/0x7f RIP: 0033:0x7f164cf8f749 Code: ff ff c3 66 2e 0f 1f 84 00 00 00 00 00 0f 1f 40 00 48 89 f8 48 89 f7 48 89 d6 48 89 ca 4d 89 c2 4d 89 c8 4c 8b 4c 24 08 0f 05 <48> 3d 01 f0 ff ff 73 01 c3 48 c7 c1 a8 ff ff ff f7 d8 64 89 01 48 RSP: 002b:00007f164de64038 EFLAGS: 00000246 ORIG_RAX: 0000000000000010 RAX: ffffffffffffffda RBX: 00007f164d1e5fa0 RCX: 00007f164cf8f749 RDX: 0000200000000000 RSI: 0000000000008936 RDI: 0000000000000003 RBP: 00007f164d013f91 R08: 0000000000000000 R09: 0000000000000000 R10: 0000000000000000 R11: 0000000000000246 R12: 0000000000000000 R13: 00007f164d1e6038 R14: 00007f164d1e5fa0 R15: 00007ffde15c8288 Allocated by task 9593: kasan_save_stack+0x33/0x60 mm/kasan/common.c:56 kasan_save_track+0x14/0x30 mm/kasan/common.c:77 poison_kmalloc_redzone mm/kasan/common.c:397 [inline] __kasan_kmalloc+0xaa/0xb0 mm/kasan/common.c:414 kmalloc_noprof include/linux/slab.h:957 [inline] kzalloc_noprof include/linux/slab.h:1094 [inline] ipv6_add_addr+0x4e3/0x2010 net/ipv6/addrconf.c:1120 inet6_addr_add+0x256/0x9b0 net/ipv6/addrconf.c:3050 addrconf_add_ifaddr+0x1fc/0x450 net/ipv6/addrconf.c:3160 inet6_ioctl+0x103/0x2b0 net/ipv6/af_inet6.c:580 sock_do_ioctl+0x118/0x280 net/socket.c:1254 sock_ioctl+0x227/0x6b0 net/socket.c:1375 vfs_ioctl fs/ioctl.c:51 [inline] __do_sys_ioctl fs/ioctl.c:597 [inline] __se_sys_ioctl fs/ioctl.c:583 [inline] __x64_sys_ioctl+0x18e/0x210 fs/ioctl.c:583 do_syscall_x64 arch/x86/entry/syscall_64.c:63 [inline] do_syscall_64+0xcd/0xf80 arch/x86/entry/syscall_64.c:94 entry_SYSCALL_64_after_hwframe+0x77/0x7f Freed by task 6099: kasan_save_stack+0x33/0x60 mm/kasan/common.c:56 kasan_save_track+0x14/0x30 mm/kasan/common.c:77 kasan_save_free_info+0x3b/0x60 mm/kasan/generic.c:584 poison_slab_object mm/kasan/common.c:252 [inline] __kasan_slab_free+0x5f/0x80 mm/kasan/common.c:284 kasan_slab_free include/linux/kasan.h:234 [inline] slab_free_hook mm/slub.c:2540 [inline] slab_free_freelist_hook mm/slub.c:2569 [inline] slab_free_bulk mm/slub.c:6696 [inline] kmem_cache_free_bulk mm/slub.c:7383 [inline] kmem_cache_free_bulk+0x2bf/0x680 mm/slub.c:7362 kfree_bulk include/linux/slab.h:830 [inline] kvfree_rcu_bulk+0x1b7/0x1e0 mm/slab_common.c:1523 kvfree_rcu_drain_ready mm/slab_common.c:1728 [inline] kfree_rcu_monitor+0x1d0/0x2f0 mm/slab_common.c:1801 process_one_work+0x9ba/0x1b20 kernel/workqueue.c:3257 process_scheduled_works kernel/workqueue.c:3340 [inline] worker_thread+0x6c8/0xf10 kernel/workqueue.c:3421 kthread+0x3c5/0x780 kernel/kthread.c:463 ret_from_fork+0x983/0xb10 arch/x86/kernel/process.c:158 ret_from_fork_asm+0x1a/0x30 arch/x86/entry/entry_64.S:246 Fixes: 00b5b7aab9e42 ("net/ipv6: delete temporary address if mngtmpaddr is removed or unmanaged") Reported-by: syzbot+72e610f4f1a930ca9d8a@syzkaller.appspotmail.com Closes: https://lore.kernel.org/netdev/696598e9.050a0220.3be5c5.0009.GAE@google.com/ Signed-off-by: Kuniyuki Iwashima Reviewed-by: Hangbin Liu Reviewed-by: Eric Dumazet Link: https://patch.msgid.link/20260113010538.2019411-1-kuniyu@google.com Signed-off-by: Jakub Kicinski Signed-off-by: Sasha Levin --- net/ipv6/addrconf.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/net/ipv6/addrconf.c b/net/ipv6/addrconf.c index 40e9c336f6c55e..cad5e4ab8c3dbf 100644 --- a/net/ipv6/addrconf.c +++ b/net/ipv6/addrconf.c @@ -3112,12 +3112,12 @@ static int inet6_addr_del(struct net *net, int ifindex, u32 ifa_flags, in6_ifa_hold(ifp); read_unlock_bh(&idev->lock); - ipv6_del_addr(ifp); - if (!(ifp->flags & IFA_F_TEMPORARY) && (ifp->flags & IFA_F_MANAGETEMPADDR)) delete_tempaddrs(idev, ifp); + ipv6_del_addr(ifp); + addrconf_verify_rtnl(net); if (ipv6_addr_is_multicast(pfx)) { ipv6_mc_config(net->ipv6.mc_autojoin_sk, From a3a4296d8b5b10135248c61e0c06c867c17d80e2 Mon Sep 17 00:00:00 2001 From: Gal Pressman Date: Mon, 12 Jan 2026 19:37:15 +0200 Subject: [PATCH 2457/3902] selftests: drv-net: fix RPS mask handling for high CPU numbers [ Upstream commit cf055f8c000445aa688c53a706ef4f580818eedb ] The RPS bitmask bounds check uses ~(RPS_MAX_CPUS - 1) which equals ~15 = 0xfff0, only allowing CPUs 0-3. Change the mask to ~((1UL << RPS_MAX_CPUS) - 1) = ~0xffff to allow CPUs 0-15. Fixes: 5ebfb4cc3048 ("selftests/net: toeplitz test") Reviewed-by: Nimrod Oren Signed-off-by: Gal Pressman Reviewed-by: Willem de Bruijn Link: https://patch.msgid.link/20260112173715.384843-3-gal@nvidia.com Signed-off-by: Jakub Kicinski Signed-off-by: Sasha Levin --- tools/testing/selftests/net/toeplitz.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tools/testing/selftests/net/toeplitz.c b/tools/testing/selftests/net/toeplitz.c index 9ba03164d73a69..5099157f01b9ab 100644 --- a/tools/testing/selftests/net/toeplitz.c +++ b/tools/testing/selftests/net/toeplitz.c @@ -473,8 +473,8 @@ static void parse_rps_bitmap(const char *arg) bitmap = strtoul(arg, NULL, 0); - if (bitmap & ~(RPS_MAX_CPUS - 1)) - error(1, 0, "rps bitmap 0x%lx out of bounds 0..%lu", + if (bitmap & ~((1UL << RPS_MAX_CPUS) - 1)) + error(1, 0, "rps bitmap 0x%lx out of bounds, max cpu %lu", bitmap, RPS_MAX_CPUS - 1); for (i = 0; i < RPS_MAX_CPUS; i++) From e9d8f11652fa08c647bf7bba7dd8163241a332cd Mon Sep 17 00:00:00 2001 From: Eric Dumazet Date: Mon, 12 Jan 2026 17:56:56 +0000 Subject: [PATCH 2458/3902] net/sched: sch_qfq: do not free existing class in qfq_change_class() [ Upstream commit 3879cffd9d07aa0377c4b8835c4f64b4fb24ac78 ] Fixes qfq_change_class() error case. cl->qdisc and cl should only be freed if a new class and qdisc were allocated, or we risk various UAF. Fixes: 462dbc9101ac ("pkt_sched: QFQ Plus: fair-queueing service at DRR cost") Reported-by: syzbot+07f3f38f723c335f106d@syzkaller.appspotmail.com Closes: https://lore.kernel.org/netdev/6965351d.050a0220.eaf7.00c5.GAE@google.com/T/#u Signed-off-by: Eric Dumazet Reviewed-by: Jamal Hadi Salim Link: https://patch.msgid.link/20260112175656.17605-1-edumazet@google.com Signed-off-by: Jakub Kicinski Signed-off-by: Sasha Levin --- net/sched/sch_qfq.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/net/sched/sch_qfq.c b/net/sched/sch_qfq.c index a91a5bac8f7374..9b16ad431028f6 100644 --- a/net/sched/sch_qfq.c +++ b/net/sched/sch_qfq.c @@ -529,8 +529,10 @@ static int qfq_change_class(struct Qdisc *sch, u32 classid, u32 parentid, return 0; destroy_class: - qdisc_put(cl->qdisc); - kfree(cl); + if (!existing) { + qdisc_put(cl->qdisc); + kfree(cl); + } return err; } From 07d6d21b85137c6dfc73ba92cf021f1e9f88c5a0 Mon Sep 17 00:00:00 2001 From: Cole Leavitt Date: Tue, 13 Jan 2026 19:55:18 -0700 Subject: [PATCH 2459/3902] ASoC: sdw_utils: cs42l43: Enable Headphone pin for LINEOUT jack type [ Upstream commit 390caeed0897fcac75f3c414dbdd85d593183d9c ] The CS42L43 codec's load detection can return different impedance values that map to either HEADPHONE or LINEOUT jack types. However, the soc_jack_pins array only maps SND_JACK_HEADPHONE to the "Headphone" DAPM pin, not SND_JACK_LINEOUT. When headphones are detected with an impedance that maps to LINEOUT (such as impedance value 0x2), the driver reports SND_JACK_LINEOUT. Since this doesn't match the jack pin mask, the "Headphone" DAPM pin is not activated, and no audio is routed to the headphone outputs. Fix by adding SND_JACK_LINEOUT to the Headphone pin mask, so that both headphone and line-out detection properly enable the headphone output path. This fixes no audio output on devices like the Lenovo ThinkPad P16 Gen 3 where headphones are detected with LINEOUT impedance. Fixes: d74bad3b7452 ("ASoC: intel: sof_sdw_cs42l43: Create separate jacks for hp and mic") Reviewed-by: Charles Keepax Signed-off-by: Cole Leavitt Link: https://patch.msgid.link/20260114025518.28519-1-cole@unwrap.rs Signed-off-by: Mark Brown Signed-off-by: Sasha Levin --- sound/soc/sdw_utils/soc_sdw_cs42l43.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sound/soc/sdw_utils/soc_sdw_cs42l43.c b/sound/soc/sdw_utils/soc_sdw_cs42l43.c index b415d45d520d0c..3e8e2e3bdf7c5c 100644 --- a/sound/soc/sdw_utils/soc_sdw_cs42l43.c +++ b/sound/soc/sdw_utils/soc_sdw_cs42l43.c @@ -44,7 +44,7 @@ static const struct snd_soc_dapm_route cs42l43_dmic_map[] = { static struct snd_soc_jack_pin soc_jack_pins[] = { { .pin = "Headphone", - .mask = SND_JACK_HEADPHONE, + .mask = SND_JACK_HEADPHONE | SND_JACK_LINEOUT, }, { .pin = "Headset Mic", From 53bd838ed5950cb18927e4b2e8ee841b7cb10929 Mon Sep 17 00:00:00 2001 From: Emil Svendsen Date: Tue, 13 Jan 2026 11:58:45 +0100 Subject: [PATCH 2460/3902] ASoC: tlv320adcx140: fix null pointer [ Upstream commit be7664c81d3129fc313ef62ff275fd3d33cfecd4 ] The "snd_soc_component" in "adcx140_priv" was only used once but never set. It was only used for reaching "dev" which is already present in "adcx140_priv". Fixes: 4e82971f7b55 ("ASoC: tlv320adcx140: Add a new kcontrol") Signed-off-by: Emil Svendsen Signed-off-by: Sascha Hauer Link: https://patch.msgid.link/20260113-sound-soc-codecs-tvl320adcx140-v4-2-8f7ecec525c8@pengutronix.de Signed-off-by: Mark Brown Signed-off-by: Sasha Levin --- sound/soc/codecs/tlv320adcx140.c | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/sound/soc/codecs/tlv320adcx140.c b/sound/soc/codecs/tlv320adcx140.c index d594bf166c0e79..ccfec4c0c159aa 100644 --- a/sound/soc/codecs/tlv320adcx140.c +++ b/sound/soc/codecs/tlv320adcx140.c @@ -23,7 +23,6 @@ #include "tlv320adcx140.h" struct adcx140_priv { - struct snd_soc_component *component; struct regulator *supply_areg; struct gpio_desc *gpio_reset; struct regmap *regmap; @@ -701,7 +700,6 @@ static void adcx140_pwr_ctrl(struct adcx140_priv *adcx140, bool power_state) { int pwr_ctrl = 0; int ret = 0; - struct snd_soc_component *component = adcx140->component; if (power_state) pwr_ctrl = ADCX140_PWR_CFG_ADC_PDZ | ADCX140_PWR_CFG_PLL_PDZ; @@ -713,7 +711,7 @@ static void adcx140_pwr_ctrl(struct adcx140_priv *adcx140, bool power_state) ret = regmap_write(adcx140->regmap, ADCX140_PHASE_CALIB, adcx140->phase_calib_on ? 0x00 : 0x40); if (ret) - dev_err(component->dev, "%s: register write error %d\n", + dev_err(adcx140->dev, "%s: register write error %d\n", __func__, ret); } From 7481e7b9d7366d92d17d29a41d2b96a7add007eb Mon Sep 17 00:00:00 2001 From: Emil Svendsen Date: Tue, 13 Jan 2026 11:58:47 +0100 Subject: [PATCH 2461/3902] ASoC: tlv320adcx140: fix word length [ Upstream commit 46378ab9fcb796dca46b51e10646f636e2c661f9 ] The word length is the physical width of the channel slots. So the hw_params would misconfigure when format width and physical width doesn't match. Like S24_LE which has data width of 24 bits but physical width of 32 bits. So if using asymmetric formats you will get a lot of noise. Fixes: 689c7655b50c5 ("ASoC: tlv320adcx140: Add the tlv320adcx140 codec driver family") Signed-off-by: Emil Svendsen Signed-off-by: Sascha Hauer Link: https://patch.msgid.link/20260113-sound-soc-codecs-tvl320adcx140-v4-4-8f7ecec525c8@pengutronix.de Signed-off-by: Mark Brown Signed-off-by: Sasha Levin --- sound/soc/codecs/tlv320adcx140.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/sound/soc/codecs/tlv320adcx140.c b/sound/soc/codecs/tlv320adcx140.c index ccfec4c0c159aa..62d936c2838c96 100644 --- a/sound/soc/codecs/tlv320adcx140.c +++ b/sound/soc/codecs/tlv320adcx140.c @@ -727,7 +727,7 @@ static int adcx140_hw_params(struct snd_pcm_substream *substream, struct adcx140_priv *adcx140 = snd_soc_component_get_drvdata(component); u8 data = 0; - switch (params_width(params)) { + switch (params_physical_width(params)) { case 16: data = ADCX140_16_BIT_WORD; break; @@ -742,7 +742,7 @@ static int adcx140_hw_params(struct snd_pcm_substream *substream, break; default: dev_err(component->dev, "%s: Unsupported width %d\n", - __func__, params_width(params)); + __func__, params_physical_width(params)); return -EINVAL; } From a1aedf4053af7dad3772b94b057a7d1f5473055f Mon Sep 17 00:00:00 2001 From: Lu Yao Date: Tue, 6 Jan 2026 10:37:12 +0800 Subject: [PATCH 2462/3902] drm/amdgpu: fix drm panic null pointer when driver not support atomic [ Upstream commit 9cb6278b44c38899961b36d303d7b18b38be2a6e ] When driver not support atomic, fb using plane->fb rather than plane->state->fb. Fixes: fe151ed7af54 ("drm/amdgpu: add generic display panic helper code") Signed-off-by: Lu Yao Signed-off-by: Alex Deucher (cherry picked from commit 2f2a72de673513247cd6fae14e53f6c40c5841ef) Signed-off-by: Sasha Levin --- drivers/gpu/drm/amd/amdgpu/amdgpu_display.c | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_display.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_display.c index 51bab32fd8c6fc..2f416d12e2e7ee 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_display.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_display.c @@ -1824,7 +1824,12 @@ int amdgpu_display_get_scanout_buffer(struct drm_plane *plane, struct drm_scanout_buffer *sb) { struct amdgpu_bo *abo; - struct drm_framebuffer *fb = plane->state->fb; + struct drm_framebuffer *fb; + + if (drm_drv_uses_atomic_modeset(plane->dev)) + fb = plane->state->fb; + else + fb = plane->fb; if (!fb) return -EINVAL; From e17e32903684e104566768be5a07a94301498b59 Mon Sep 17 00:00:00 2001 From: "Mario Limonciello (AMD)" Date: Sun, 14 Dec 2025 08:59:16 -0600 Subject: [PATCH 2463/3902] drm/amd/display: Show link name in PSR status message [ Upstream commit 0a1253ba5096f531eaaef40caa4c069da6ad48ae ] [Why] The PSR message was moved in commit 4321742c394e ("drm/amd/display: Move PSR support message into amdgpu_dm"). This message however shows for every single link without showing which link is which. This can send a confusing message to the user. [How] Add link name into the message. Fixes: 4321742c394e ("drm/amd/display: Move PSR support message into amdgpu_dm") Reviewed-by: Alex Hung Signed-off-by: Mario Limonciello (AMD) Signed-off-by: Matthew Stewart Tested-by: Dan Wheeler Signed-off-by: Alex Deucher (cherry picked from commit 99f77f6229c0766b980ae05affcf9f742d97de6a) Signed-off-by: Sasha Levin --- drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c index 7fe40bbba26588..f4381d44864f16 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c @@ -5545,7 +5545,8 @@ static int amdgpu_dm_initialize_drm_device(struct amdgpu_device *adev) if (psr_feature_enabled) { amdgpu_dm_set_psr_caps(link); - drm_info(adev_to_drm(adev), "PSR support %d, DC PSR ver %d, sink PSR ver %d DPCD caps 0x%x su_y_granularity %d\n", + drm_info(adev_to_drm(adev), "%s: PSR support %d, DC PSR ver %d, sink PSR ver %d DPCD caps 0x%x su_y_granularity %d\n", + aconnector->base.name, link->psr_settings.psr_feature_enabled, link->psr_settings.psr_version, link->dpcd_caps.psr_info.psr_version, From 226d5d24c7ce12df74fd1a9ebeacff043a75abed Mon Sep 17 00:00:00 2001 From: Yang Wang Date: Tue, 6 Jan 2026 14:42:40 +0800 Subject: [PATCH 2464/3902] drm/amd/pm: fix smu overdrive data type wrong issue on smu 14.0.2 [ Upstream commit 90dbc0bc2aa60021615969841fed06790c992bde ] resolving the issue of incorrect type definitions potentially causing calculation errors. Fixes: 54f7f3ca982a ("drm/amdgpu/swm14: Update power limit logic") Signed-off-by: Yang Wang Reviewed-by: Hawking Zhang Signed-off-by: Alex Deucher (cherry picked from commit e3a03d0ae16d6b56e893cce8e52b44140e1ed985) Signed-off-by: Sasha Levin --- drivers/gpu/drm/amd/pm/swsmu/smu14/smu_v14_0_2_ppt.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/drivers/gpu/drm/amd/pm/swsmu/smu14/smu_v14_0_2_ppt.c b/drivers/gpu/drm/amd/pm/swsmu/smu14/smu_v14_0_2_ppt.c index 086501cc5213be..e735da7ab61265 100644 --- a/drivers/gpu/drm/amd/pm/swsmu/smu14/smu_v14_0_2_ppt.c +++ b/drivers/gpu/drm/amd/pm/swsmu/smu14/smu_v14_0_2_ppt.c @@ -1701,8 +1701,9 @@ static int smu_v14_0_2_get_power_limit(struct smu_context *smu, table_context->power_play_table; PPTable_t *pptable = table_context->driver_pptable; CustomSkuTable_t *skutable = &pptable->CustomSkuTable; - uint32_t power_limit, od_percent_upper = 0, od_percent_lower = 0; + int16_t od_percent_upper = 0, od_percent_lower = 0; uint32_t msg_limit = pptable->SkuTable.MsgLimits.Power[PPT_THROTTLER_PPT0][POWER_SOURCE_AC]; + uint32_t power_limit; if (smu_v14_0_get_current_power_limit(smu, &power_limit)) power_limit = smu->adev->pm.ac_power ? From 3d05c5e1015212ca663561dc91f608ceb612ac8c Mon Sep 17 00:00:00 2001 From: Harish Kasiviswanathan Date: Sun, 11 Jan 2026 16:53:18 -0500 Subject: [PATCH 2465/3902] drm/amdkfd: No need to suspend whole MES to evict process [ Upstream commit 18dbcfb46f692e665c3fe3eee804e56c4eae53d6 ] Each queue of the process is individually removed and there is not need to suspend whole mes. Suspending mes stops kernel mode queues also causing unnecessary timeouts when running mixed work loads Fixes: 079ae5118e1f ("drm/amdkfd: fix suspend/resume all calls in mes based eviction path") Closes: https://gitlab.freedesktop.org/drm/amd/-/issues/4765 Signed-off-by: Harish Kasiviswanathan Reviewed-by: Alex Deucher Signed-off-by: Alex Deucher (cherry picked from commit 3fd20580b96a6e9da65b94ac3b58ee288239b731) Signed-off-by: Sasha Levin --- .../gpu/drm/amd/amdkfd/kfd_device_queue_manager.c | 12 +----------- 1 file changed, 1 insertion(+), 11 deletions(-) diff --git a/drivers/gpu/drm/amd/amdkfd/kfd_device_queue_manager.c b/drivers/gpu/drm/amd/amdkfd/kfd_device_queue_manager.c index 6e7bc983fc0b68..36fb3db16572a4 100644 --- a/drivers/gpu/drm/amd/amdkfd/kfd_device_queue_manager.c +++ b/drivers/gpu/drm/amd/amdkfd/kfd_device_queue_manager.c @@ -1209,14 +1209,8 @@ static int evict_process_queues_cpsch(struct device_queue_manager *dqm, pr_debug_ratelimited("Evicting process pid %d queues\n", pdd->process->lead_thread->pid); - if (dqm->dev->kfd->shared_resources.enable_mes) { + if (dqm->dev->kfd->shared_resources.enable_mes) pdd->last_evict_timestamp = get_jiffies_64(); - retval = suspend_all_queues_mes(dqm); - if (retval) { - dev_err(dev, "Suspending all queues failed"); - goto out; - } - } /* Mark all queues as evicted. Deactivate all active queues on * the qpd. @@ -1246,10 +1240,6 @@ static int evict_process_queues_cpsch(struct device_queue_manager *dqm, KFD_UNMAP_QUEUES_FILTER_ALL_QUEUES : KFD_UNMAP_QUEUES_FILTER_DYNAMIC_QUEUES, 0, USE_DEFAULT_GRACE_PERIOD); - } else { - retval = resume_all_queues_mes(dqm); - if (retval) - dev_err(dev, "Resuming all queues failed"); } out: From e1a30e1ab33fc522785d04bbf7e1b13a5c5c9175 Mon Sep 17 00:00:00 2001 From: Srinivasan Shanmugam Date: Wed, 14 Jan 2026 16:14:53 +0530 Subject: [PATCH 2466/3902] drm/amdgpu/userq: Fix fence reference leak on queue teardown v2 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [ Upstream commit b2426a211dba6432e32a2e70e9183c6e134475c6 ] The user mode queue keeps a pointer to the most recent fence in userq->last_fence. This pointer holds an extra dma_fence reference. When the queue is destroyed, we free the fence driver and its xarray, but we forgot to drop the last_fence reference. Because of the missing dma_fence_put(), the last fence object can stay alive when the driver unloads. This leaves an allocated object in the amdgpu_userq_fence slab cache and triggers This is visible during driver unload as: BUG amdgpu_userq_fence: Objects remaining on __kmem_cache_shutdown() kmem_cache_destroy amdgpu_userq_fence: Slab cache still has objects Call Trace: kmem_cache_destroy amdgpu_userq_fence_slab_fini amdgpu_exit __do_sys_delete_module Fix this by putting userq->last_fence and clearing the pointer during amdgpu_userq_fence_driver_free(). This makes sure the fence reference is released and the slab cache is empty when the module exits. v2: Update to only release userq->last_fence with dma_fence_put() (Christian) Fixes: edc762a51c71 ("drm/amdgpu/userq: move some code around") Cc: Alex Deucher Cc: Christian König Signed-off-by: Srinivasan Shanmugam Reviewed-by: Christian König Signed-off-by: Alex Deucher (cherry picked from commit 8e051e38a8d45caf6a866d4ff842105b577953bb) Signed-off-by: Sasha Levin --- drivers/gpu/drm/amd/amdgpu/amdgpu_userq_fence.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_userq_fence.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_userq_fence.c index 4d0096d0baa9d0..53fe10931fab04 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_userq_fence.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_userq_fence.c @@ -141,6 +141,8 @@ static void amdgpu_userq_walk_and_drop_fence_drv(struct xarray *xa) void amdgpu_userq_fence_driver_free(struct amdgpu_usermode_queue *userq) { + dma_fence_put(userq->last_fence); + amdgpu_userq_walk_and_drop_fence_drv(&userq->fence_drv_xa); xa_destroy(&userq->fence_drv_xa); /* Drop the fence_drv reference held by user queue */ From cf6d059b5372880488859f5c41f0bf102c7f7b0c Mon Sep 17 00:00:00 2001 From: Bagas Sanjaya Date: Fri, 19 Dec 2025 08:40:04 +0700 Subject: [PATCH 2467/3902] mm: describe @flags parameter in memalloc_flags_save() [ Upstream commit e2fb7836b01747815f8bb94981c35f2688afb120 ] Patch series "mm kernel-doc fixes". Here are kernel-doc fixes for mm subsystem. I'm also including textsearch fix since there's currently no maintainer for include/linux/textsearch.h (get_maintainer.pl only shows LKML). This patch (of 4): Sphinx reports kernel-doc warning: WARNING: ./include/linux/sched/mm.h:332 function parameter 'flags' not described in 'memalloc_flags_save' Describe @flags to fix it. Link: https://lkml.kernel.org/r/20251219014006.16328-2-bagasdotme@gmail.com Link: https://lkml.kernel.org/r/20251219014006.16328-3-bagasdotme@gmail.com Signed-off-by: Bagas Sanjaya Fixes: 3f6d5e6a468d ("mm: introduce memalloc_flags_{save,restore}") Acked-by: David Hildenbrand (Red Hat) Acked-by: Harry Yoo Signed-off-by: Andrew Morton Signed-off-by: Sasha Levin --- include/linux/sched/mm.h | 1 + 1 file changed, 1 insertion(+) diff --git a/include/linux/sched/mm.h b/include/linux/sched/mm.h index 0232d983b71539..a3094379b5790e 100644 --- a/include/linux/sched/mm.h +++ b/include/linux/sched/mm.h @@ -323,6 +323,7 @@ static inline void might_alloc(gfp_t gfp_mask) /** * memalloc_flags_save - Add a PF_* flag to current->flags, save old value + * @flags: Flags to add. * * This allows PF_* flags to be conveniently added, irrespective of current * value, and then the old version restored with memalloc_flags_restore(). From cf85a91aa82eaf2886297c5fd38de1b9214e5737 Mon Sep 17 00:00:00 2001 From: Bagas Sanjaya Date: Fri, 19 Dec 2025 08:40:05 +0700 Subject: [PATCH 2468/3902] textsearch: describe @list member in ts_ops search [ Upstream commit f26528478bb102c28e7ac0cbfc8ec8185afdafc7 ] Sphinx reports kernel-doc warning: WARNING: ./include/linux/textsearch.h:49 struct member 'list' not described in 'ts_ops' Describe @list member to fix it. Link: https://lkml.kernel.org/r/20251219014006.16328-4-bagasdotme@gmail.com Fixes: 2de4ff7bd658 ("[LIB]: Textsearch infrastructure.") Signed-off-by: Bagas Sanjaya Cc: Thomas Graf Cc: "David S. Miller" Signed-off-by: Andrew Morton Signed-off-by: Sasha Levin --- include/linux/textsearch.h | 1 + 1 file changed, 1 insertion(+) diff --git a/include/linux/textsearch.h b/include/linux/textsearch.h index 6673e4d4ac2e1b..4933777404d618 100644 --- a/include/linux/textsearch.h +++ b/include/linux/textsearch.h @@ -35,6 +35,7 @@ struct ts_state * @get_pattern: return head of pattern * @get_pattern_len: return length of pattern * @owner: module reference to algorithm + * @list: list to search */ struct ts_ops { From 2a582a2ca8dae01c5eda1bab528734132da3c50b Mon Sep 17 00:00:00 2001 From: Bagas Sanjaya Date: Fri, 19 Dec 2025 08:40:07 +0700 Subject: [PATCH 2469/3902] mm, kfence: describe @slab parameter in __kfence_obj_info() [ Upstream commit 6cfab50e1440fde19af7c614aacd85e11aa4dcea ] Sphinx reports kernel-doc warning: WARNING: ./include/linux/kfence.h:220 function parameter 'slab' not described in '__kfence_obj_info' Fix it by describing @slab parameter. Link: https://lkml.kernel.org/r/20251219014006.16328-6-bagasdotme@gmail.com Fixes: 2dfe63e61cc3 ("mm, kfence: support kmem_dump_obj() for KFENCE objects") Signed-off-by: Bagas Sanjaya Acked-by: Marco Elver Acked-by: David Hildenbrand (Red Hat) Acked-by: Harry Yoo Signed-off-by: Andrew Morton Signed-off-by: Sasha Levin --- include/linux/kfence.h | 1 + 1 file changed, 1 insertion(+) diff --git a/include/linux/kfence.h b/include/linux/kfence.h index 0ad1ddbb8b996a..e5822f6e7f2794 100644 --- a/include/linux/kfence.h +++ b/include/linux/kfence.h @@ -211,6 +211,7 @@ struct kmem_obj_info; * __kfence_obj_info() - fill kmem_obj_info struct * @kpp: kmem_obj_info to be filled * @object: the object + * @slab: the slab * * Return: * * false - not a KFENCE object From aec20f3aa684baea78b65ce929670ae087f620a4 Mon Sep 17 00:00:00 2001 From: "Mike Rapoport (Microsoft)" Date: Wed, 31 Dec 2025 12:57:01 +0200 Subject: [PATCH 2470/3902] mips: fix HIGHMEM initialization [ Upstream commit f171b55f1441294344b86edfeaa575ea9673fd23 ] Commit 6faea3422e3b ("arch, mm: streamline HIGHMEM freeing") overzealously removed mem_init_free_highmem() function that beside freeing high memory pages checked for CPU support for high memory as a prerequisite. Partially restore mem_init_free_highmem() with a new highmem_init() name and make it discard high memory in case there is no CPU support for it. Link: https://lkml.kernel.org/r/20251231105701.519711-1-rppt@kernel.org Fixes: 6faea3422e3b ("arch, mm: streamline HIGHMEM freeing") Signed-off-by: Mike Rapoport (Microsoft) Reported-by: Markus Stockhausen Cc: Chris Packham Cc: Hauke Mehrtens Cc: Jonas Jelonek Cc: Thomas Bogendoerfer Cc: Thomas Gleinxer Signed-off-by: Andrew Morton Signed-off-by: Sasha Levin --- arch/mips/mm/init.c | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/arch/mips/mm/init.c b/arch/mips/mm/init.c index a673d3d68254bd..8986048f9b110b 100644 --- a/arch/mips/mm/init.c +++ b/arch/mips/mm/init.c @@ -425,6 +425,28 @@ void __init paging_init(void) static struct kcore_list kcore_kseg0; #endif +static inline void __init highmem_init(void) +{ +#ifdef CONFIG_HIGHMEM + unsigned long tmp; + + /* + * If CPU cannot support HIGHMEM discard the memory above highstart_pfn + */ + if (cpu_has_dc_aliases) { + memblock_remove(PFN_PHYS(highstart_pfn), -1); + return; + } + + for (tmp = highstart_pfn; tmp < highend_pfn; tmp++) { + struct page *page = pfn_to_page(tmp); + + if (!memblock_is_memory(PFN_PHYS(tmp))) + SetPageReserved(page); + } +#endif +} + void __init arch_mm_preinit(void) { /* @@ -435,6 +457,7 @@ void __init arch_mm_preinit(void) maar_init(); setup_zero_pages(); /* Setup zeroed pages. */ + highmem_init(); #ifdef CONFIG_64BIT if ((unsigned long) &_text > (unsigned long) CKSEG0) From 8bc6d92b9a3ed4aa94d944cd9cb2e7a2d2cf07a5 Mon Sep 17 00:00:00 2001 From: John Groves Date: Sat, 10 Jan 2026 13:18:04 -0600 Subject: [PATCH 2471/3902] drivers/dax: add some missing kerneldoc comment fields for struct dev_dax [ Upstream commit 3e8e590fd65d0572584ab7bba89a35e6d19931f1 ] Add the missing @align and @memmap_on_memory fields to kerneldoc comment header for struct dev_dax. Also, some other fields were followed by '-' and others by ':'. Fix all to be ':' for actual kerneldoc compliance. Link: https://lkml.kernel.org/r/20260110191804.5739-1-john@groves.net Fixes: 33cf94d71766 ("device-dax: make align a per-device property") Fixes: 4eca0ef49af9 ("dax/kmem: allow kmem to add memory with memmap_on_memory") Signed-off-by: John Groves Cc: Dan Williams Cc: Joao Martins Cc: Vishal Verma Signed-off-by: Andrew Morton Signed-off-by: Sasha Levin --- drivers/dax/dax-private.h | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/drivers/dax/dax-private.h b/drivers/dax/dax-private.h index 0867115aeef2e1..c6ae27c982f439 100644 --- a/drivers/dax/dax-private.h +++ b/drivers/dax/dax-private.h @@ -67,14 +67,16 @@ struct dev_dax_range { /** * struct dev_dax - instance data for a subdivision of a dax region, and * data while the device is activated in the driver. - * @region - parent region - * @dax_dev - core dax functionality + * @region: parent region + * @dax_dev: core dax functionality + * @align: alignment of this instance * @target_node: effective numa node if dev_dax memory range is onlined * @dyn_id: is this a dynamic or statically created instance * @id: ida allocated id when the dax_region is not static * @ida: mapping id allocator - * @dev - device core - * @pgmap - pgmap for memmap setup / lifetime (driver owned) + * @dev: device core + * @pgmap: pgmap for memmap setup / lifetime (driver owned) + * @memmap_on_memory: allow kmem to put the memmap in the memory * @nr_range: size of @ranges * @ranges: range tuples of memory used */ From 94413a84067c31a787c8973c9b150088601ff4e7 Mon Sep 17 00:00:00 2001 From: Trond Myklebust Date: Sat, 10 Jan 2026 18:53:34 -0500 Subject: [PATCH 2472/3902] NFS: Fix size read races in truncate, fallocate and copy offload [ Upstream commit d5811e6297f3fd9020ac31f51fc317dfdb260cb0 ] If the pre-operation file size is read before locking the inode and quiescing O_DIRECT writes, then nfs_truncate_last_folio() might end up overwriting valid file data. Fixes: b1817b18ff20 ("NFS: Protect against 'eof page pollution'") Signed-off-by: Trond Myklebust Signed-off-by: Sasha Levin --- fs/nfs/inode.c | 10 ++++++---- fs/nfs/io.c | 2 ++ fs/nfs/nfs42proc.c | 29 +++++++++++++++++++---------- 3 files changed, 27 insertions(+), 14 deletions(-) diff --git a/fs/nfs/inode.c b/fs/nfs/inode.c index 13ad70fc00d84b..8c2bfcc323e02f 100644 --- a/fs/nfs/inode.c +++ b/fs/nfs/inode.c @@ -716,7 +716,7 @@ nfs_setattr(struct mnt_idmap *idmap, struct dentry *dentry, { struct inode *inode = d_inode(dentry); struct nfs_fattr *fattr; - loff_t oldsize = i_size_read(inode); + loff_t oldsize; int error = 0; kuid_t task_uid = current_fsuid(); kuid_t owner_uid = inode->i_uid; @@ -727,6 +727,10 @@ nfs_setattr(struct mnt_idmap *idmap, struct dentry *dentry, if (attr->ia_valid & (ATTR_KILL_SUID | ATTR_KILL_SGID)) attr->ia_valid &= ~ATTR_MODE; + if (S_ISREG(inode->i_mode)) + nfs_file_block_o_direct(NFS_I(inode)); + + oldsize = i_size_read(inode); if (attr->ia_valid & ATTR_SIZE) { BUG_ON(!S_ISREG(inode->i_mode)); @@ -774,10 +778,8 @@ nfs_setattr(struct mnt_idmap *idmap, struct dentry *dentry, trace_nfs_setattr_enter(inode); /* Write all dirty data */ - if (S_ISREG(inode->i_mode)) { - nfs_file_block_o_direct(NFS_I(inode)); + if (S_ISREG(inode->i_mode)) nfs_sync_inode(inode); - } fattr = nfs_alloc_fattr_with_label(NFS_SERVER(inode)); if (fattr == NULL) { diff --git a/fs/nfs/io.c b/fs/nfs/io.c index d275b0a250bf3b..8337f0ae852d42 100644 --- a/fs/nfs/io.c +++ b/fs/nfs/io.c @@ -84,6 +84,7 @@ nfs_start_io_write(struct inode *inode) nfs_file_block_o_direct(NFS_I(inode)); return err; } +EXPORT_SYMBOL_GPL(nfs_start_io_write); /** * nfs_end_io_write - declare that the buffered write operation is done @@ -97,6 +98,7 @@ nfs_end_io_write(struct inode *inode) { up_write(&inode->i_rwsem); } +EXPORT_SYMBOL_GPL(nfs_end_io_write); /* Call with exclusively locked inode->i_rwsem */ static void nfs_block_buffered(struct nfs_inode *nfsi, struct inode *inode) diff --git a/fs/nfs/nfs42proc.c b/fs/nfs/nfs42proc.c index d537fb0c230e8b..c08520828708bf 100644 --- a/fs/nfs/nfs42proc.c +++ b/fs/nfs/nfs42proc.c @@ -114,7 +114,6 @@ static int nfs42_proc_fallocate(struct rpc_message *msg, struct file *filep, exception.inode = inode; exception.state = lock->open_context->state; - nfs_file_block_o_direct(NFS_I(inode)); err = nfs_sync_inode(inode); if (err) goto out; @@ -138,13 +137,17 @@ int nfs42_proc_allocate(struct file *filep, loff_t offset, loff_t len) .rpc_proc = &nfs4_procedures[NFSPROC4_CLNT_ALLOCATE], }; struct inode *inode = file_inode(filep); - loff_t oldsize = i_size_read(inode); + loff_t oldsize; int err; if (!nfs_server_capable(inode, NFS_CAP_ALLOCATE)) return -EOPNOTSUPP; - inode_lock(inode); + err = nfs_start_io_write(inode); + if (err) + return err; + + oldsize = i_size_read(inode); err = nfs42_proc_fallocate(&msg, filep, offset, len); @@ -155,7 +158,7 @@ int nfs42_proc_allocate(struct file *filep, loff_t offset, loff_t len) NFS_SERVER(inode)->caps &= ~(NFS_CAP_ALLOCATE | NFS_CAP_ZERO_RANGE); - inode_unlock(inode); + nfs_end_io_write(inode); return err; } @@ -170,7 +173,9 @@ int nfs42_proc_deallocate(struct file *filep, loff_t offset, loff_t len) if (!nfs_server_capable(inode, NFS_CAP_DEALLOCATE)) return -EOPNOTSUPP; - inode_lock(inode); + err = nfs_start_io_write(inode); + if (err) + return err; err = nfs42_proc_fallocate(&msg, filep, offset, len); if (err == 0) @@ -179,7 +184,7 @@ int nfs42_proc_deallocate(struct file *filep, loff_t offset, loff_t len) NFS_SERVER(inode)->caps &= ~(NFS_CAP_DEALLOCATE | NFS_CAP_ZERO_RANGE); - inode_unlock(inode); + nfs_end_io_write(inode); return err; } @@ -189,14 +194,17 @@ int nfs42_proc_zero_range(struct file *filep, loff_t offset, loff_t len) .rpc_proc = &nfs4_procedures[NFSPROC4_CLNT_ZERO_RANGE], }; struct inode *inode = file_inode(filep); - loff_t oldsize = i_size_read(inode); + loff_t oldsize; int err; if (!nfs_server_capable(inode, NFS_CAP_ZERO_RANGE)) return -EOPNOTSUPP; - inode_lock(inode); + err = nfs_start_io_write(inode); + if (err) + return err; + oldsize = i_size_read(inode); err = nfs42_proc_fallocate(&msg, filep, offset, len); if (err == 0) { nfs_truncate_last_folio(inode->i_mapping, oldsize, @@ -205,7 +213,7 @@ int nfs42_proc_zero_range(struct file *filep, loff_t offset, loff_t len) } else if (err == -EOPNOTSUPP) NFS_SERVER(inode)->caps &= ~NFS_CAP_ZERO_RANGE; - inode_unlock(inode); + nfs_end_io_write(inode); return err; } @@ -416,7 +424,7 @@ static ssize_t _nfs42_proc_copy(struct file *src, struct nfs_server *src_server = NFS_SERVER(src_inode); loff_t pos_src = args->src_pos; loff_t pos_dst = args->dst_pos; - loff_t oldsize_dst = i_size_read(dst_inode); + loff_t oldsize_dst; size_t count = args->count; ssize_t status; @@ -461,6 +469,7 @@ static ssize_t _nfs42_proc_copy(struct file *src, &src_lock->open_context->state->flags); set_bit(NFS_CLNT_DST_SSC_COPY_STATE, &dst_lock->open_context->state->flags); + oldsize_dst = i_size_read(dst_inode); status = nfs4_call_sync(dst_server->client, dst_server, &msg, &args->seq_args, &res->seq_res, 0); From 974f241095aaf62293b6a98d515ca4bd3b08fbb0 Mon Sep 17 00:00:00 2001 From: Guodong Xu Date: Thu, 18 Sep 2025 22:27:27 +0800 Subject: [PATCH 2473/3902] dmaengine: mmp_pdma: fix DMA mask handling [ Upstream commit 49400b701eca849c1b53717b1f5d779a8d066ec0 ] The driver's existing logic for setting the DMA mask for "marvell,pdma-1.0" was flawed. It incorrectly relied on pdev->dev->coherent_dma_mask instead of declaring the hardware's fixed addressing capability. A cleaner and more correct approach is to define the mask directly based on the hardware limitations. The MMP/PXA PDMA controller is a 32-bit DMA engine. This is supported by datasheets and various dtsi files for PXA25x, PXA27x, PXA3xx, and MMP2, all of which are 32-bit systems. This patch simplifies the driver's logic by replacing the 'u64 dma_mask' field with a simpler 'u32 dma_width' to store the addressing capability in bits. The complex if/else block in probe() is then replaced with a single, clear call to dma_set_mask_and_coherent(). This sets a fixed 32-bit DMA mask for "marvell,pdma-1.0" and a 64-bit mask for "spacemit,k1-pdma," matching each device's hardware capabilities. Finally, this change also works around a specific build error encountered with clang-20 on x86_64 allyesconfig. The shift-count-overflow error is caused by a known clang compiler issue where the DMA_BIT_MASK(n) macro's ternary operator is not correctly evaluated in static initializers. By moving the macro's evaluation into the probe() function, the driver avoids this compiler bug. Fixes: 5cfe585d8624 ("dmaengine: mmp_pdma: Add SpacemiT K1 PDMA support with 64-bit addressing") Reported-by: Naresh Kamboju Closes: https://lore.kernel.org/lkml/CA+G9fYsPcMfW-e_0_TRqu4cnwqOqYF3aJOeKUYk6Z4qRStdFvg@mail.gmail.com Suggested-by: Arnd Bergmann Signed-off-by: Guodong Xu Reviewed-by: Arnd Bergmann Tested-by: Nathan Chancellor # build Tested-by: Naresh Kamboju Signed-off-by: Vinod Koul Signed-off-by: Sasha Levin --- drivers/dma/mmp_pdma.c | 20 ++++++++------------ 1 file changed, 8 insertions(+), 12 deletions(-) diff --git a/drivers/dma/mmp_pdma.c b/drivers/dma/mmp_pdma.c index d07229a748868b..86661eb3cde1ff 100644 --- a/drivers/dma/mmp_pdma.c +++ b/drivers/dma/mmp_pdma.c @@ -152,8 +152,8 @@ struct mmp_pdma_phy { * * Controller Configuration: * @run_bits: Control bits in DCSR register for channel start/stop - * @dma_mask: DMA addressing capability of controller. 0 to use OF/platform - * settings, or explicit mask like DMA_BIT_MASK(32/64) + * @dma_width: DMA addressing width in bits (32 or 64). Determines the + * DMA mask capability of the controller hardware. */ struct mmp_pdma_ops { /* Hardware Register Operations */ @@ -173,7 +173,7 @@ struct mmp_pdma_ops { /* Controller Configuration */ u32 run_bits; - u64 dma_mask; + u32 dma_width; }; struct mmp_pdma_device { @@ -1172,7 +1172,7 @@ static const struct mmp_pdma_ops marvell_pdma_v1_ops = { .get_desc_src_addr = get_desc_src_addr_32, .get_desc_dst_addr = get_desc_dst_addr_32, .run_bits = (DCSR_RUN), - .dma_mask = 0, /* let OF/platform set DMA mask */ + .dma_width = 32, }; static const struct mmp_pdma_ops spacemit_k1_pdma_ops = { @@ -1185,7 +1185,7 @@ static const struct mmp_pdma_ops spacemit_k1_pdma_ops = { .get_desc_src_addr = get_desc_src_addr_64, .get_desc_dst_addr = get_desc_dst_addr_64, .run_bits = (DCSR_RUN | DCSR_LPAEEN), - .dma_mask = DMA_BIT_MASK(64), /* force 64-bit DMA addr capability */ + .dma_width = 64, }; static const struct of_device_id mmp_pdma_dt_ids[] = { @@ -1314,13 +1314,9 @@ static int mmp_pdma_probe(struct platform_device *op) pdev->device.directions = BIT(DMA_MEM_TO_DEV) | BIT(DMA_DEV_TO_MEM); pdev->device.residue_granularity = DMA_RESIDUE_GRANULARITY_DESCRIPTOR; - /* Set DMA mask based on ops->dma_mask, or OF/platform */ - if (pdev->ops->dma_mask) - dma_set_mask(pdev->dev, pdev->ops->dma_mask); - else if (pdev->dev->coherent_dma_mask) - dma_set_mask(pdev->dev, pdev->dev->coherent_dma_mask); - else - dma_set_mask(pdev->dev, DMA_BIT_MASK(64)); + /* Set DMA mask based on controller hardware capabilities */ + dma_set_mask_and_coherent(pdev->dev, + DMA_BIT_MASK(pdev->ops->dma_width)); ret = dma_async_device_register(&pdev->device); if (ret) { From 5e7ad329d259cf5bed7530d6d2525bcf7cb487a1 Mon Sep 17 00:00:00 2001 From: Anthony Brandon Date: Mon, 13 Oct 2025 17:48:49 +0200 Subject: [PATCH 2474/3902] dmaengine: xilinx: xdma: Fix regmap max_register [ Upstream commit c7d436a6c1a274c1ac28d5fb3b8eb8f03b6d0e10 ] The max_register field is assigned the size of the register memory region instead of the offset of the last register. The result is that reading from the regmap via debugfs can cause a segmentation fault: tail /sys/kernel/debug/regmap/xdma.1.auto/registers Unable to handle kernel paging request at virtual address ffff800082f70000 Mem abort info: ESR = 0x0000000096000007 EC = 0x25: DABT (current EL), IL = 32 bits SET = 0, FnV = 0 EA = 0, S1PTW = 0 FSC = 0x07: level 3 translation fault [...] Call trace: regmap_mmio_read32le+0x10/0x30 _regmap_bus_reg_read+0x74/0xc0 _regmap_read+0x68/0x198 regmap_read+0x54/0x88 regmap_read_debugfs+0x140/0x380 regmap_map_read_file+0x30/0x48 full_proxy_read+0x68/0xc8 vfs_read+0xcc/0x310 ksys_read+0x7c/0x120 __arm64_sys_read+0x24/0x40 invoke_syscall.constprop.0+0x64/0x108 do_el0_svc+0xb0/0xd8 el0_svc+0x38/0x130 el0t_64_sync_handler+0x120/0x138 el0t_64_sync+0x194/0x198 Code: aa1e03e9 d503201f f9400000 8b214000 (b9400000) ---[ end trace 0000000000000000 ]--- note: tail[1217] exited with irqs disabled note: tail[1217] exited with preempt_count 1 Segmentation fault Fixes: 17ce252266c7 ("dmaengine: xilinx: xdma: Add xilinx xdma driver") Reviewed-by: Lizhi Hou Reviewed-by: Radhey Shyam Pandey Reviewed-by: Alexander Stein Signed-off-by: Anthony Brandon Signed-off-by: Vinod Koul Signed-off-by: Sasha Levin --- drivers/dma/xilinx/xdma-regs.h | 1 + drivers/dma/xilinx/xdma.c | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/drivers/dma/xilinx/xdma-regs.h b/drivers/dma/xilinx/xdma-regs.h index 6ad08878e93862..70bca92621aa41 100644 --- a/drivers/dma/xilinx/xdma-regs.h +++ b/drivers/dma/xilinx/xdma-regs.h @@ -9,6 +9,7 @@ /* The length of register space exposed to host */ #define XDMA_REG_SPACE_LEN 65536 +#define XDMA_MAX_REG_OFFSET (XDMA_REG_SPACE_LEN - 4) /* * maximum number of DMA channels for each direction: diff --git a/drivers/dma/xilinx/xdma.c b/drivers/dma/xilinx/xdma.c index 0d88b1a670e142..5ecf8223c112e4 100644 --- a/drivers/dma/xilinx/xdma.c +++ b/drivers/dma/xilinx/xdma.c @@ -38,7 +38,7 @@ static const struct regmap_config xdma_regmap_config = { .reg_bits = 32, .val_bits = 32, .reg_stride = 4, - .max_register = XDMA_REG_SPACE_LEN, + .max_register = XDMA_MAX_REG_OFFSET, }; /** From be655c3736b3546f39bc8116ffbf2a3b6cac96c4 Mon Sep 17 00:00:00 2001 From: Sheetal Date: Mon, 10 Nov 2025 19:54:45 +0530 Subject: [PATCH 2475/3902] dmaengine: tegra-adma: Fix use-after-free [ Upstream commit 2efd07a7c36949e6fa36a69183df24d368bf9e96 ] A use-after-free bug exists in the Tegra ADMA driver when audio streams are terminated, particularly during XRUN conditions. The issue occurs when the DMA buffer is freed by tegra_adma_terminate_all() before the vchan completion tasklet finishes accessing it. The race condition follows this sequence: 1. DMA transfer completes, triggering an interrupt that schedules the completion tasklet (tasklet has not executed yet) 2. Audio playback stops, calling tegra_adma_terminate_all() which frees the DMA buffer memory via kfree() 3. The scheduled tasklet finally executes, calling vchan_complete() which attempts to access the already-freed memory Since tasklets can execute at any time after being scheduled, there is no guarantee that the buffer will remain valid when vchan_complete() runs. Fix this by properly synchronizing the virtual channel completion: - Calling vchan_terminate_vdesc() in tegra_adma_stop() to mark the descriptors as terminated instead of freeing the descriptor. - Add the callback tegra_adma_synchronize() that calls vchan_synchronize() which kills any pending tasklets and frees any terminated descriptors. Crash logs: [ 337.427523] BUG: KASAN: use-after-free in vchan_complete+0x124/0x3b0 [ 337.427544] Read of size 8 at addr ffff000132055428 by task swapper/0/0 [ 337.427562] Call trace: [ 337.427564] dump_backtrace+0x0/0x320 [ 337.427571] show_stack+0x20/0x30 [ 337.427575] dump_stack_lvl+0x68/0x84 [ 337.427584] print_address_description.constprop.0+0x74/0x2b8 [ 337.427590] kasan_report+0x1f4/0x210 [ 337.427598] __asan_load8+0xa0/0xd0 [ 337.427603] vchan_complete+0x124/0x3b0 [ 337.427609] tasklet_action_common.constprop.0+0x190/0x1d0 [ 337.427617] tasklet_action+0x30/0x40 [ 337.427623] __do_softirq+0x1a0/0x5c4 [ 337.427628] irq_exit+0x110/0x140 [ 337.427633] handle_domain_irq+0xa4/0xe0 [ 337.427640] gic_handle_irq+0x64/0x160 [ 337.427644] call_on_irq_stack+0x20/0x4c [ 337.427649] do_interrupt_handler+0x7c/0x90 [ 337.427654] el1_interrupt+0x30/0x80 [ 337.427659] el1h_64_irq_handler+0x18/0x30 [ 337.427663] el1h_64_irq+0x7c/0x80 [ 337.427667] cpuidle_enter_state+0xe4/0x540 [ 337.427674] cpuidle_enter+0x54/0x80 [ 337.427679] do_idle+0x2e0/0x380 [ 337.427685] cpu_startup_entry+0x2c/0x70 [ 337.427690] rest_init+0x114/0x130 [ 337.427695] arch_call_rest_init+0x18/0x24 [ 337.427702] start_kernel+0x380/0x3b4 [ 337.427706] __primary_switched+0xc0/0xc8 Fixes: f46b195799b5 ("dmaengine: tegra-adma: Add support for Tegra210 ADMA") Signed-off-by: Sheetal Acked-by: Thierry Reding Link: https://patch.msgid.link/20251110142445.3842036-1-sheetal@nvidia.com Signed-off-by: Vinod Koul Signed-off-by: Sasha Levin --- drivers/dma/tegra210-adma.c | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/drivers/dma/tegra210-adma.c b/drivers/dma/tegra210-adma.c index fad896ff29a2d3..812f64569e6d88 100644 --- a/drivers/dma/tegra210-adma.c +++ b/drivers/dma/tegra210-adma.c @@ -429,10 +429,17 @@ static void tegra_adma_stop(struct tegra_adma_chan *tdc) return; } - kfree(tdc->desc); + vchan_terminate_vdesc(&tdc->desc->vd); tdc->desc = NULL; } +static void tegra_adma_synchronize(struct dma_chan *dc) +{ + struct tegra_adma_chan *tdc = to_tegra_adma_chan(dc); + + vchan_synchronize(&tdc->vc); +} + static void tegra_adma_start(struct tegra_adma_chan *tdc) { struct virt_dma_desc *vd = vchan_next_desc(&tdc->vc); @@ -1155,6 +1162,7 @@ static int tegra_adma_probe(struct platform_device *pdev) tdma->dma_dev.device_config = tegra_adma_slave_config; tdma->dma_dev.device_tx_status = tegra_adma_tx_status; tdma->dma_dev.device_terminate_all = tegra_adma_terminate_all; + tdma->dma_dev.device_synchronize = tegra_adma_synchronize; tdma->dma_dev.src_addr_widths = BIT(DMA_SLAVE_BUSWIDTH_4_BYTES); tdma->dma_dev.dst_addr_widths = BIT(DMA_SLAVE_BUSWIDTH_4_BYTES); tdma->dma_dev.directions = BIT(DMA_DEV_TO_MEM) | BIT(DMA_MEM_TO_DEV); From f93c7033689218698b5ebe5c763f2da36dec8eef Mon Sep 17 00:00:00 2001 From: Suraj Gupta Date: Wed, 22 Oct 2025 00:00:06 +0530 Subject: [PATCH 2476/3902] dmaengine: xilinx_dma: Fix uninitialized addr_width when "xlnx,addrwidth" property is missing [ Upstream commit c0732fe78728718c853ef8e7af5bbb05262acbd1 ] When device tree lacks optional "xlnx,addrwidth" property, the addr_width variable remained uninitialized with garbage values, causing incorrect DMA mask configuration and subsequent probe failure. The fix ensures a fallback to the default 32-bit address width when this property is missing. Signed-off-by: Suraj Gupta Fixes: b72db4005fe4 ("dmaengine: vdma: Add 64 bit addressing support to the driver") Reviewed-by: Radhey Shyam Pandey Reviewed-by: Folker Schwesinger Link: https://patch.msgid.link/20251021183006.3434495-1-suraj.gupta2@amd.com Signed-off-by: Vinod Koul Signed-off-by: Sasha Levin --- drivers/dma/xilinx/xilinx_dma.c | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/drivers/dma/xilinx/xilinx_dma.c b/drivers/dma/xilinx/xilinx_dma.c index fabff602065f6c..89a8254d9cdc62 100644 --- a/drivers/dma/xilinx/xilinx_dma.c +++ b/drivers/dma/xilinx/xilinx_dma.c @@ -131,6 +131,7 @@ #define XILINX_MCDMA_MAX_CHANS_PER_DEVICE 0x20 #define XILINX_DMA_MAX_CHANS_PER_DEVICE 0x2 #define XILINX_CDMA_MAX_CHANS_PER_DEVICE 0x1 +#define XILINX_DMA_DFAULT_ADDRWIDTH 0x20 #define XILINX_DMA_DMAXR_ALL_IRQ_MASK \ (XILINX_DMA_DMASR_FRM_CNT_IRQ | \ @@ -3159,7 +3160,7 @@ static int xilinx_dma_probe(struct platform_device *pdev) struct device_node *node = pdev->dev.of_node; struct xilinx_dma_device *xdev; struct device_node *child, *np = pdev->dev.of_node; - u32 num_frames, addr_width, len_width; + u32 num_frames, addr_width = XILINX_DMA_DFAULT_ADDRWIDTH, len_width; int i, err; /* Allocate and initialize the DMA engine structure */ @@ -3235,7 +3236,9 @@ static int xilinx_dma_probe(struct platform_device *pdev) err = of_property_read_u32(node, "xlnx,addrwidth", &addr_width); if (err < 0) - dev_warn(xdev->dev, "missing xlnx,addrwidth property\n"); + dev_warn(xdev->dev, + "missing xlnx,addrwidth property, using default value %d\n", + XILINX_DMA_DFAULT_ADDRWIDTH); if (addr_width > 32) xdev->ext_addr = true; From d279f7e17b6d408f2fdbeba795abccabf352bd78 Mon Sep 17 00:00:00 2001 From: Stefano Radaelli Date: Fri, 19 Dec 2025 17:09:12 +0100 Subject: [PATCH 2477/3902] phy: fsl-imx8mq-usb: Clear the PCS_TX_SWING_FULL field before using it [ Upstream commit 8becf9179a4b45104a1701010ed666b55bf4b3a6 ] Clear the PCS_TX_SWING_FULL field mask before setting the new value in PHY_CTRL5 register. Without clearing the mask first, the OR operation could leave previously set bits, resulting in incorrect register configuration. Fixes: 63c85ad0cd81 ("phy: fsl-imx8mp-usb: add support for phy tuning") Suggested-by: Leonid Segal Acked-by: Pierluigi Passaro Signed-off-by: Stefano Radaelli Reviewed-by: Xu Yang Reviewed-by: Frank Li Reviewed-by: Fabio Estevam Reviewed-by: Ahmad Fatoum Link: https://patch.msgid.link/20251219160912.561431-1-stefano.r@variscite.com Signed-off-by: Vinod Koul Signed-off-by: Sasha Levin --- drivers/phy/freescale/phy-fsl-imx8mq-usb.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/phy/freescale/phy-fsl-imx8mq-usb.c b/drivers/phy/freescale/phy-fsl-imx8mq-usb.c index b94f242420fc73..0c84f5f7a82cb2 100644 --- a/drivers/phy/freescale/phy-fsl-imx8mq-usb.c +++ b/drivers/phy/freescale/phy-fsl-imx8mq-usb.c @@ -502,6 +502,7 @@ static void imx8m_phy_tune(struct imx8mq_usb_phy *imx_phy) if (imx_phy->pcs_tx_swing_full != PHY_TUNE_DEFAULT) { value = readl(imx_phy->base + PHY_CTRL5); + value &= ~PHY_CTRL5_PCS_TX_SWING_FULL_MASK; value |= FIELD_PREP(PHY_CTRL5_PCS_TX_SWING_FULL_MASK, imx_phy->pcs_tx_swing_full); writel(value, imx_phy->base + PHY_CTRL5); From 4ac15caa27ff842b068a54f1c6a8ff8b31f658e7 Mon Sep 17 00:00:00 2001 From: Loic Poulain Date: Fri, 19 Dec 2025 09:56:40 +0100 Subject: [PATCH 2478/3902] phy: qcom-qusb2: Fix NULL pointer dereference on early suspend [ Upstream commit 1ca52c0983c34fca506921791202ed5bdafd5306 ] Enabling runtime PM before attaching the QPHY instance as driver data can lead to a NULL pointer dereference in runtime PM callbacks that expect valid driver data. There is a small window where the suspend callback may run after PM runtime enabling and before runtime forbid. This causes a sporadic crash during boot: ``` Unable to handle kernel NULL pointer dereference at virtual address 00000000000000a1 [...] CPU: 0 UID: 0 PID: 11 Comm: kworker/0:1 Not tainted 6.16.7+ #116 PREEMPT Workqueue: pm pm_runtime_work pstate: 20000005 (nzCv daif -PAN -UAO -TCO -DIT -SSBS BTYPE=--) pc : qusb2_phy_runtime_suspend+0x14/0x1e0 [phy_qcom_qusb2] lr : pm_generic_runtime_suspend+0x2c/0x44 [...] ``` Attach the QPHY instance as driver data before enabling runtime PM to prevent NULL pointer dereference in runtime PM callbacks. Reorder pm_runtime_enable() and pm_runtime_forbid() to prevent a short window where an unnecessary runtime suspend can occur. Use the devres-managed version to ensure PM runtime is symmetrically disabled during driver removal for proper cleanup. Fixes: 891a96f65ac3 ("phy: qcom-qusb2: Add support for runtime PM") Signed-off-by: Loic Poulain Reviewed-by: Dmitry Baryshkov Reviewed-by: Abel Vesa Link: https://patch.msgid.link/20251219085640.114473-1-loic.poulain@oss.qualcomm.com Signed-off-by: Vinod Koul Signed-off-by: Sasha Levin --- drivers/phy/qualcomm/phy-qcom-qusb2.c | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/drivers/phy/qualcomm/phy-qcom-qusb2.c b/drivers/phy/qualcomm/phy-qcom-qusb2.c index b5514a32ff8ff5..eb93015be841f5 100644 --- a/drivers/phy/qualcomm/phy-qcom-qusb2.c +++ b/drivers/phy/qualcomm/phy-qcom-qusb2.c @@ -1093,29 +1093,29 @@ static int qusb2_phy_probe(struct platform_device *pdev) or->hsdisc_trim.override = true; } - pm_runtime_set_active(dev); - pm_runtime_enable(dev); + dev_set_drvdata(dev, qphy); + /* - * Prevent runtime pm from being ON by default. Users can enable - * it using power/control in sysfs. + * Enable runtime PM support, but forbid it by default. + * Users can allow it again via the power/control attribute in sysfs. */ + pm_runtime_set_active(dev); pm_runtime_forbid(dev); + ret = devm_pm_runtime_enable(dev); + if (ret) + return ret; generic_phy = devm_phy_create(dev, NULL, &qusb2_phy_gen_ops); if (IS_ERR(generic_phy)) { ret = PTR_ERR(generic_phy); dev_err(dev, "failed to create phy, %d\n", ret); - pm_runtime_disable(dev); return ret; } qphy->phy = generic_phy; - dev_set_drvdata(dev, qphy); phy_set_drvdata(generic_phy, qphy); phy_provider = devm_of_phy_provider_register(dev, of_phy_simple_xlate); - if (IS_ERR(phy_provider)) - pm_runtime_disable(dev); return PTR_ERR_OR_ZERO(phy_provider); } From 7c27eaf183563b86d815ff6e9cca0210b4cfa051 Mon Sep 17 00:00:00 2001 From: Dan Carpenter Date: Tue, 9 Dec 2025 09:53:36 +0300 Subject: [PATCH 2479/3902] phy: stm32-usphyc: Fix off by one in probe() [ Upstream commit cabd25b57216ddc132efbcc31f972baa03aad15a ] The "index" variable is used as an index into the usbphyc->phys[] array which has usbphyc->nphys elements. So if it is equal to usbphyc->nphys then it is one element out of bounds. The "index" comes from the device tree so it's data that we trust and it's unlikely to be wrong, however it's obviously still worth fixing the bug. Change the > to >=. Fixes: 94c358da3a05 ("phy: stm32: add support for STM32 USB PHY Controller (USBPHYC)") Signed-off-by: Dan Carpenter Reviewed-by: Amelie Delaunay Link: https://patch.msgid.link/aTfHcMJK1wFVnvEe@stanley.mountain Signed-off-by: Vinod Koul Signed-off-by: Sasha Levin --- drivers/phy/st/phy-stm32-usbphyc.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/phy/st/phy-stm32-usbphyc.c b/drivers/phy/st/phy-stm32-usbphyc.c index 27fe92f73f3313..b44afbff8616ba 100644 --- a/drivers/phy/st/phy-stm32-usbphyc.c +++ b/drivers/phy/st/phy-stm32-usbphyc.c @@ -712,7 +712,7 @@ static int stm32_usbphyc_probe(struct platform_device *pdev) } ret = of_property_read_u32(child, "reg", &index); - if (ret || index > usbphyc->nphys) { + if (ret || index >= usbphyc->nphys) { dev_err(&phy->dev, "invalid reg property: %d\n", ret); if (!ret) ret = -EINVAL; From 15dfbe9079987eb517e842a444306b90927b4a69 Mon Sep 17 00:00:00 2001 From: Haotian Zhang Date: Mon, 24 Nov 2025 18:57:34 +0800 Subject: [PATCH 2480/3902] phy: ti: da8xx-usb: Handle devm_pm_runtime_enable() errors [ Upstream commit 08aa19de72110df8ac10c9e67349dd884eeed41d ] devm_pm_runtime_enable() can fail due to memory allocation. The current code ignores its return value after calling pm_runtime_set_active(), leaving the device in an inconsistent state if runtime PM initialization fails. Check the return value of devm_pm_runtime_enable() and return on failure. Also move the declaration of 'ret' to the function scope to support this check. Fixes: ee8e41b5044f ("phy: ti: phy-da8xx-usb: Add runtime PM support") Suggested-by: Neil Armstrong Signed-off-by: Haotian Zhang Reviewed-by: Neil Armstrong Link: https://patch.msgid.link/20251124105734.1027-1-vulab@iscas.ac.cn Signed-off-by: Vinod Koul Signed-off-by: Sasha Levin --- drivers/phy/ti/phy-da8xx-usb.c | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/drivers/phy/ti/phy-da8xx-usb.c b/drivers/phy/ti/phy-da8xx-usb.c index 1d81a1e6ec6b6b..62fa6f89c0e61e 100644 --- a/drivers/phy/ti/phy-da8xx-usb.c +++ b/drivers/phy/ti/phy-da8xx-usb.c @@ -180,6 +180,7 @@ static int da8xx_usb_phy_probe(struct platform_device *pdev) struct da8xx_usb_phy_platform_data *pdata = dev->platform_data; struct device_node *node = dev->of_node; struct da8xx_usb_phy *d_phy; + int ret; d_phy = devm_kzalloc(dev, sizeof(*d_phy), GFP_KERNEL); if (!d_phy) @@ -233,8 +234,6 @@ static int da8xx_usb_phy_probe(struct platform_device *pdev) return PTR_ERR(d_phy->phy_provider); } } else { - int ret; - ret = phy_create_lookup(d_phy->usb11_phy, "usb-phy", "ohci-da8xx"); if (ret) @@ -249,7 +248,9 @@ static int da8xx_usb_phy_probe(struct platform_device *pdev) PHY_INIT_BITS, PHY_INIT_BITS); pm_runtime_set_active(dev); - devm_pm_runtime_enable(dev); + ret = devm_pm_runtime_enable(dev); + if (ret) + return ret; /* * Prevent runtime pm from being ON by default. Users can enable * it using power/control in sysfs. From 75ef8c94d4303e53a85e51c2d437af5468dd0710 Mon Sep 17 00:00:00 2001 From: Matthieu Buffet Date: Mon, 27 Oct 2025 20:07:26 +0100 Subject: [PATCH 2481/3902] landlock: Fix TCP handling of short AF_UNSPEC addresses MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [ Upstream commit e4d82cbce2258f454634307fdabf33aa46b61ab0 ] current_check_access_socket() treats AF_UNSPEC addresses as AF_INET ones, and only later adds special case handling to allow connect(AF_UNSPEC), and on IPv4 sockets bind(AF_UNSPEC+INADDR_ANY). This would be fine except AF_UNSPEC addresses can be as short as a bare AF_UNSPEC sa_family_t field, and nothing more. The AF_INET code path incorrectly enforces a length of sizeof(struct sockaddr_in) instead. Move AF_UNSPEC edge case handling up inside the switch-case, before the address is (potentially incorrectly) treated as AF_INET. Fixes: fff69fb03dde ("landlock: Support network rules with TCP bind and connect") Signed-off-by: Matthieu Buffet Link: https://lore.kernel.org/r/20251027190726.626244-4-matthieu@buffet.re Signed-off-by: Mickaël Salaün Signed-off-by: Sasha Levin --- security/landlock/net.c | 118 +++++++++++++++++++++++----------------- 1 file changed, 67 insertions(+), 51 deletions(-) diff --git a/security/landlock/net.c b/security/landlock/net.c index 1f3915a90a808b..e6367e30e5b0ec 100644 --- a/security/landlock/net.c +++ b/security/landlock/net.c @@ -71,6 +71,61 @@ static int current_check_access_socket(struct socket *const sock, switch (address->sa_family) { case AF_UNSPEC: + if (access_request == LANDLOCK_ACCESS_NET_CONNECT_TCP) { + /* + * Connecting to an address with AF_UNSPEC dissolves + * the TCP association, which have the same effect as + * closing the connection while retaining the socket + * object (i.e., the file descriptor). As for dropping + * privileges, closing connections is always allowed. + * + * For a TCP access control system, this request is + * legitimate. Let the network stack handle potential + * inconsistencies and return -EINVAL if needed. + */ + return 0; + } else if (access_request == LANDLOCK_ACCESS_NET_BIND_TCP) { + /* + * Binding to an AF_UNSPEC address is treated + * differently by IPv4 and IPv6 sockets. The socket's + * family may change under our feet due to + * setsockopt(IPV6_ADDRFORM), but that's ok: we either + * reject entirely or require + * %LANDLOCK_ACCESS_NET_BIND_TCP for the given port, so + * it cannot be used to bypass the policy. + * + * IPv4 sockets map AF_UNSPEC to AF_INET for + * retrocompatibility for bind accesses, only if the + * address is INADDR_ANY (cf. __inet_bind). IPv6 + * sockets always reject it. + * + * Checking the address is required to not wrongfully + * return -EACCES instead of -EAFNOSUPPORT or -EINVAL. + * We could return 0 and let the network stack handle + * these checks, but it is safer to return a proper + * error and test consistency thanks to kselftest. + */ + if (sock->sk->__sk_common.skc_family == AF_INET) { + const struct sockaddr_in *const sockaddr = + (struct sockaddr_in *)address; + + if (addrlen < sizeof(struct sockaddr_in)) + return -EINVAL; + + if (sockaddr->sin_addr.s_addr != + htonl(INADDR_ANY)) + return -EAFNOSUPPORT; + } else { + if (addrlen < SIN6_LEN_RFC2133) + return -EINVAL; + else + return -EAFNOSUPPORT; + } + } else { + WARN_ON_ONCE(1); + } + /* Only for bind(AF_UNSPEC+INADDR_ANY) on IPv4 socket. */ + fallthrough; case AF_INET: { const struct sockaddr_in *addr4; @@ -119,57 +174,18 @@ static int current_check_access_socket(struct socket *const sock, return 0; } - /* Specific AF_UNSPEC handling. */ - if (address->sa_family == AF_UNSPEC) { - /* - * Connecting to an address with AF_UNSPEC dissolves the TCP - * association, which have the same effect as closing the - * connection while retaining the socket object (i.e., the file - * descriptor). As for dropping privileges, closing - * connections is always allowed. - * - * For a TCP access control system, this request is legitimate. - * Let the network stack handle potential inconsistencies and - * return -EINVAL if needed. - */ - if (access_request == LANDLOCK_ACCESS_NET_CONNECT_TCP) - return 0; - - /* - * For compatibility reason, accept AF_UNSPEC for bind - * accesses (mapped to AF_INET) only if the address is - * INADDR_ANY (cf. __inet_bind). Checking the address is - * required to not wrongfully return -EACCES instead of - * -EAFNOSUPPORT. - * - * We could return 0 and let the network stack handle these - * checks, but it is safer to return a proper error and test - * consistency thanks to kselftest. - */ - if (access_request == LANDLOCK_ACCESS_NET_BIND_TCP) { - /* addrlen has already been checked for AF_UNSPEC. */ - const struct sockaddr_in *const sockaddr = - (struct sockaddr_in *)address; - - if (sock->sk->__sk_common.skc_family != AF_INET) - return -EINVAL; - - if (sockaddr->sin_addr.s_addr != htonl(INADDR_ANY)) - return -EAFNOSUPPORT; - } - } else { - /* - * Checks sa_family consistency to not wrongfully return - * -EACCES instead of -EINVAL. Valid sa_family changes are - * only (from AF_INET or AF_INET6) to AF_UNSPEC. - * - * We could return 0 and let the network stack handle this - * check, but it is safer to return a proper error and test - * consistency thanks to kselftest. - */ - if (address->sa_family != sock->sk->__sk_common.skc_family) - return -EINVAL; - } + /* + * Checks sa_family consistency to not wrongfully return + * -EACCES instead of -EINVAL. Valid sa_family changes are + * only (from AF_INET or AF_INET6) to AF_UNSPEC. + * + * We could return 0 and let the network stack handle this + * check, but it is safer to return a proper error and test + * consistency thanks to kselftest. + */ + if (address->sa_family != sock->sk->__sk_common.skc_family && + address->sa_family != AF_UNSPEC) + return -EINVAL; id.key.data = (__force uintptr_t)port; BUILD_BUG_ON(sizeof(port) > sizeof(id.key.data)); From c589bc64336672ebdc3fd83c563a6529aa138735 Mon Sep 17 00:00:00 2001 From: Matthieu Buffet Date: Mon, 27 Oct 2025 20:07:24 +0100 Subject: [PATCH 2482/3902] selftests/landlock: Fix TCP bind(AF_UNSPEC) test case MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [ Upstream commit bd09d9a05cf04028f639e209b416bacaeffd4909 ] The nominal error code for bind(AF_UNSPEC) on an IPv6 socket is -EAFNOSUPPORT, not -EINVAL. -EINVAL is only returned when the supplied address struct is too short, which happens to be the case in current selftests because they treat AF_UNSPEC like IPv4 sockets do: as an alias for AF_INET (which is a 16-byte struct instead of the 24 bytes required by IPv6 sockets). Make the union large enough for any address (by adding struct sockaddr_storage to the union), and make AF_UNSPEC addresses large enough for any family. Test for -EAFNOSUPPORT instead, and add a dedicated test case for truncated inputs with -EINVAL. Fixes: a549d055a22e ("selftests/landlock: Add network tests") Signed-off-by: Matthieu Buffet Link: https://lore.kernel.org/r/20251027190726.626244-2-matthieu@buffet.re Signed-off-by: Mickaël Salaün Signed-off-by: Sasha Levin --- tools/testing/selftests/landlock/common.h | 1 + tools/testing/selftests/landlock/net_test.c | 16 +++++++++++++++- 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/tools/testing/selftests/landlock/common.h b/tools/testing/selftests/landlock/common.h index 9acecae36f51ba..98c2362954e210 100644 --- a/tools/testing/selftests/landlock/common.h +++ b/tools/testing/selftests/landlock/common.h @@ -237,6 +237,7 @@ struct service_fixture { struct sockaddr_un unix_addr; socklen_t unix_addr_len; }; + struct sockaddr_storage _largest; }; }; diff --git a/tools/testing/selftests/landlock/net_test.c b/tools/testing/selftests/landlock/net_test.c index 2a45208551e61a..3bbc0508420b1c 100644 --- a/tools/testing/selftests/landlock/net_test.c +++ b/tools/testing/selftests/landlock/net_test.c @@ -121,6 +121,10 @@ static socklen_t get_addrlen(const struct service_fixture *const srv, { switch (srv->protocol.domain) { case AF_UNSPEC: + if (minimal) + return sizeof(sa_family_t); + return sizeof(struct sockaddr_storage); + case AF_INET: return sizeof(srv->ipv4_addr); @@ -758,6 +762,11 @@ TEST_F(protocol, bind_unspec) bind_fd = socket_variant(&self->srv0); ASSERT_LE(0, bind_fd); + /* Tries to bind with too small addrlen. */ + EXPECT_EQ(-EINVAL, bind_variant_addrlen( + bind_fd, &self->unspec_any0, + get_addrlen(&self->unspec_any0, true) - 1)); + /* Allowed bind on AF_UNSPEC/INADDR_ANY. */ ret = bind_variant(bind_fd, &self->unspec_any0); if (variant->prot.domain == AF_INET) { @@ -766,6 +775,8 @@ TEST_F(protocol, bind_unspec) TH_LOG("Failed to bind to unspec/any socket: %s", strerror(errno)); } + } else if (variant->prot.domain == AF_INET6) { + EXPECT_EQ(-EAFNOSUPPORT, ret); } else { EXPECT_EQ(-EINVAL, ret); } @@ -792,6 +803,8 @@ TEST_F(protocol, bind_unspec) } else { EXPECT_EQ(0, ret); } + } else if (variant->prot.domain == AF_INET6) { + EXPECT_EQ(-EAFNOSUPPORT, ret); } else { EXPECT_EQ(-EINVAL, ret); } @@ -801,7 +814,8 @@ TEST_F(protocol, bind_unspec) bind_fd = socket_variant(&self->srv0); ASSERT_LE(0, bind_fd); ret = bind_variant(bind_fd, &self->unspec_srv0); - if (variant->prot.domain == AF_INET) { + if (variant->prot.domain == AF_INET || + variant->prot.domain == AF_INET6) { EXPECT_EQ(-EAFNOSUPPORT, ret); } else { EXPECT_EQ(-EINVAL, ret) From a45001796aa004025c7c352c42362582a79f595d Mon Sep 17 00:00:00 2001 From: Matthieu Buffet Date: Mon, 1 Dec 2025 01:36:31 +0100 Subject: [PATCH 2483/3902] selftests/landlock: Remove invalid unix socket bind() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [ Upstream commit e1a57c33590a50a6639798e60a597af4a23b0340 ] Remove bind() call on a client socket that doesn't make sense. Since strlen(cli_un.sun_path) returns a random value depending on stack garbage, that many uninitialized bytes are read from the stack as an unix socket address. This creates random test failures due to the bind address being invalid or already in use if the same stack value comes up twice. Fixes: f83d51a5bdfe ("selftests/landlock: Check IOCTL restrictions for named UNIX domain sockets") Signed-off-by: Matthieu Buffet Reviewed-by: Günther Noack Link: https://lore.kernel.org/r/20251201003631.190817-1-matthieu@buffet.re Signed-off-by: Mickaël Salaün Signed-off-by: Sasha Levin --- tools/testing/selftests/landlock/fs_test.c | 3 --- 1 file changed, 3 deletions(-) diff --git a/tools/testing/selftests/landlock/fs_test.c b/tools/testing/selftests/landlock/fs_test.c index fa0f18ec62c41e..a6eb9681791a53 100644 --- a/tools/testing/selftests/landlock/fs_test.c +++ b/tools/testing/selftests/landlock/fs_test.c @@ -4375,9 +4375,6 @@ TEST_F_FORK(layout1, named_unix_domain_socket_ioctl) cli_fd = socket(AF_UNIX, SOCK_STREAM, 0); ASSERT_LE(0, cli_fd); - size = offsetof(struct sockaddr_un, sun_path) + strlen(cli_un.sun_path); - ASSERT_EQ(0, bind(cli_fd, (struct sockaddr *)&cli_un, size)); - bzero(&cli_un, sizeof(cli_un)); cli_un.sun_family = AF_UNIX; strncpy(cli_un.sun_path, path, sizeof(cli_un.sun_path)); From f73f911a4cdf04d8a76181d8ce4765cf195c5527 Mon Sep 17 00:00:00 2001 From: Tingmao Wang Date: Sat, 6 Dec 2025 17:11:06 +0000 Subject: [PATCH 2484/3902] landlock: Fix wrong type usage MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [ Upstream commit 29fbfa46e4287c596bdc77e2c599e3a1bbf8bb67 ] I think, based on my best understanding, that this type is likely a typo (even though in the end both are u16) Signed-off-by: Tingmao Wang Fixes: 2fc80c69df82 ("landlock: Log file-related denials") Reviewed-by: Günther Noack Link: https://lore.kernel.org/r/7339ad7b47f998affd84ca629a334a71f913616d.1765040503.git.m@maowtm.org Signed-off-by: Mickaël Salaün Signed-off-by: Sasha Levin --- security/landlock/audit.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/security/landlock/audit.c b/security/landlock/audit.c index c52d079cdb77ba..e899995f1fd595 100644 --- a/security/landlock/audit.c +++ b/security/landlock/audit.c @@ -191,7 +191,7 @@ static size_t get_denied_layer(const struct landlock_ruleset *const domain, long youngest_layer = -1; for_each_set_bit(access_bit, &access_req, layer_masks_size) { - const access_mask_t mask = (*layer_masks)[access_bit]; + const layer_mask_t mask = (*layer_masks)[access_bit]; long layer; if (!mask) From cdcaec67c55ef516666a5d9a1695750f037b1b2b Mon Sep 17 00:00:00 2001 From: Krzysztof Kozlowski Date: Wed, 24 Dec 2025 12:55:34 +0100 Subject: [PATCH 2485/3902] phy: broadcom: ns-usb3: Fix Wvoid-pointer-to-enum-cast warning (again) [ Upstream commit fb21116099bbea1fc59efa9207e63c4be390ab72 ] "family" is an enum, thus cast of pointer on 64-bit compile test with clang W=1 causes: phy-bcm-ns-usb3.c:206:17: error: cast to smaller integer type 'enum bcm_ns_family' from 'const void *' [-Werror,-Wvoid-pointer-to-enum-cast] This was already fixed in commit bd6e74a2f0a0 ("phy: broadcom: ns-usb3: fix Wvoid-pointer-to-enum-cast warning") but then got bad in commit 21bf6fc47a1e ("phy: Use device_get_match_data()"). Note that after various discussions the preferred cast is via "unsigned long", not "uintptr_t". Fixes: 21bf6fc47a1e ("phy: Use device_get_match_data()") Signed-off-by: Krzysztof Kozlowski Link: https://patch.msgid.link/20251224115533.154162-2-krzysztof.kozlowski@oss.qualcomm.com Signed-off-by: Vinod Koul Signed-off-by: Sasha Levin --- drivers/phy/broadcom/phy-bcm-ns-usb3.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/phy/broadcom/phy-bcm-ns-usb3.c b/drivers/phy/broadcom/phy-bcm-ns-usb3.c index 9f995e156f7559..6e56498d0644b3 100644 --- a/drivers/phy/broadcom/phy-bcm-ns-usb3.c +++ b/drivers/phy/broadcom/phy-bcm-ns-usb3.c @@ -203,7 +203,7 @@ static int bcm_ns_usb3_mdio_probe(struct mdio_device *mdiodev) usb3->dev = dev; usb3->mdiodev = mdiodev; - usb3->family = (enum bcm_ns_family)device_get_match_data(dev); + usb3->family = (unsigned long)device_get_match_data(dev); syscon_np = of_parse_phandle(dev->of_node, "usb3-dmp-syscon", 0); err = of_address_to_resource(syscon_np, 0, &res); From d91cee31906a873bcc123d06eda4efa4835c8d0f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?G=C3=BCnther=20Noack?= Date: Thu, 1 Jan 2026 14:40:58 +0100 Subject: [PATCH 2486/3902] selftests/landlock: Properly close a file descriptor MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [ Upstream commit 15e8d739fda1084d81f7d3813e9600eba6e0f134 ] Add a missing close(srv_fd) call, and use EXPECT_EQ() to check the result. Signed-off-by: Günther Noack Fixes: f83d51a5bdfe ("selftests/landlock: Check IOCTL restrictions for named UNIX domain sockets") Link: https://lore.kernel.org/r/20260101134102.25938-2-gnoack3000@gmail.com [mic: Use EXPECT_EQ() and update commit message] Signed-off-by: Mickaël Salaün Signed-off-by: Sasha Levin --- tools/testing/selftests/landlock/fs_test.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tools/testing/selftests/landlock/fs_test.c b/tools/testing/selftests/landlock/fs_test.c index a6eb9681791a53..29cdbb8367358c 100644 --- a/tools/testing/selftests/landlock/fs_test.c +++ b/tools/testing/selftests/landlock/fs_test.c @@ -4385,7 +4385,8 @@ TEST_F_FORK(layout1, named_unix_domain_socket_ioctl) /* FIONREAD and other IOCTLs should not be forbidden. */ EXPECT_EQ(0, test_fionread_ioctl(cli_fd)); - ASSERT_EQ(0, close(cli_fd)); + EXPECT_EQ(0, close(cli_fd)); + EXPECT_EQ(0, close(srv_fd)); } /* clang-format off */ From 829b00481734dd54e72f755fd6584bce6fbffbb0 Mon Sep 17 00:00:00 2001 From: Haotian Zhang Date: Mon, 3 Nov 2025 15:30:18 +0800 Subject: [PATCH 2487/3902] dmaengine: omap-dma: fix dma_pool resource leak in error paths [ Upstream commit 2e1136acf8a8887c29f52e35a77b537309af321f ] The dma_pool created by dma_pool_create() is not destroyed when dma_async_device_register() or of_dma_controller_register() fails, causing a resource leak in the probe error paths. Add dma_pool_destroy() in both error paths to properly release the allocated dma_pool resource. Fixes: 7bedaa553760 ("dmaengine: add OMAP DMA engine driver") Signed-off-by: Haotian Zhang Link: https://patch.msgid.link/20251103073018.643-1-vulab@iscas.ac.cn Signed-off-by: Vinod Koul Signed-off-by: Sasha Levin --- drivers/dma/ti/omap-dma.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/drivers/dma/ti/omap-dma.c b/drivers/dma/ti/omap-dma.c index 8c023c6e623a5a..73ed4b79463047 100644 --- a/drivers/dma/ti/omap-dma.c +++ b/drivers/dma/ti/omap-dma.c @@ -1808,6 +1808,8 @@ static int omap_dma_probe(struct platform_device *pdev) if (rc) { pr_warn("OMAP-DMA: failed to register slave DMA engine device: %d\n", rc); + if (od->ll123_supported) + dma_pool_destroy(od->desc_pool); omap_dma_free(od); return rc; } @@ -1823,6 +1825,8 @@ static int omap_dma_probe(struct platform_device *pdev) if (rc) { pr_warn("OMAP-DMA: failed to register DMA controller\n"); dma_async_device_unregister(&od->ddev); + if (od->ll123_supported) + dma_pool_destroy(od->desc_pool); omap_dma_free(od); } } From 612cbe1aebb32e874798766b54987484c4cfc820 Mon Sep 17 00:00:00 2001 From: Harshit Mogalapalli Date: Sat, 10 Jan 2026 12:19:58 -0800 Subject: [PATCH 2488/3902] soundwire: bus: fix off-by-one when allocating slave IDs [ Upstream commit 12d4fd9a657174496677cff2841315090f1c11fc ] ida_alloc_max() interprets its max argument as inclusive. Using SDW_FW_MAX_DEVICES(16) therefore allows an ID of 16 to be allocated, but the IRQ domain created for the bus is sized for IDs 0-15. If 16 is returned, irq_create_mapping() fails and the driver ends up with an invalid IRQ mapping. Limit the allocation to 0-15 by passing SDW_FW_MAX_DEVICES - 1. Reported-by: kernel test robot Reported-by: Dan Carpenter Closes: https://lore.kernel.org/r/202512240450.hlDH3nCs-lkp@intel.com/ Fixes: aab12022b076 ("soundwire: bus: Add internal slave ID and use for IRQs") Signed-off-by: Harshit Mogalapalli Reviewed-by: Charles Keepax Link: https://patch.msgid.link/20260110201959.2523024-1-harshit.m.mogalapalli@oracle.com Signed-off-by: Vinod Koul Signed-off-by: Sasha Levin --- drivers/soundwire/bus_type.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/soundwire/bus_type.c b/drivers/soundwire/bus_type.c index 91e70cb46fb575..5c67c13e57357d 100644 --- a/drivers/soundwire/bus_type.c +++ b/drivers/soundwire/bus_type.c @@ -105,7 +105,7 @@ static int sdw_drv_probe(struct device *dev) if (ret) return ret; - ret = ida_alloc_max(&slave->bus->slave_ida, SDW_FW_MAX_DEVICES, GFP_KERNEL); + ret = ida_alloc_max(&slave->bus->slave_ida, SDW_FW_MAX_DEVICES - 1, GFP_KERNEL); if (ret < 0) { dev_err(dev, "Failed to allocated ID: %d\n", ret); return ret; From 11ae04ebbe6e580ac769bd15382a0432e46a5c6f Mon Sep 17 00:00:00 2001 From: Neil Armstrong Date: Wed, 29 Oct 2025 19:07:42 +0100 Subject: [PATCH 2489/3902] i2c: qcom-geni: make sure I2C hub controllers can't use SE DMA [ Upstream commit c0c50e3743e467ec4752c638e10e97f89c8644e2 ] The I2C Hub controller is a simpler GENI I2C variant that doesn't support DMA at all, add a no_dma flag to make sure it nevers selects the SE DMA mode with mappable 32bytes long transfers. Fixes: cacd9643eca7 ("i2c: qcom-geni: add support for I2C Master Hub variant") Signed-off-by: Neil Armstrong Reviewed-by: Konrad Dybcio Reviewed-by: Mukesh Kumar Savaliya > Signed-off-by: Wolfram Sang Signed-off-by: Sasha Levin --- drivers/i2c/busses/i2c-qcom-geni.c | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/drivers/i2c/busses/i2c-qcom-geni.c b/drivers/i2c/busses/i2c-qcom-geni.c index 43fdd89b8bebc8..bfb352b04902c4 100644 --- a/drivers/i2c/busses/i2c-qcom-geni.c +++ b/drivers/i2c/busses/i2c-qcom-geni.c @@ -97,6 +97,7 @@ struct geni_i2c_dev { dma_addr_t dma_addr; struct dma_chan *tx_c; struct dma_chan *rx_c; + bool no_dma; bool gpi_mode; bool abort_done; }; @@ -425,7 +426,7 @@ static int geni_i2c_rx_one_msg(struct geni_i2c_dev *gi2c, struct i2c_msg *msg, size_t len = msg->len; struct i2c_msg *cur; - dma_buf = i2c_get_dma_safe_msg_buf(msg, 32); + dma_buf = gi2c->no_dma ? NULL : i2c_get_dma_safe_msg_buf(msg, 32); if (dma_buf) geni_se_select_mode(se, GENI_SE_DMA); else @@ -464,7 +465,7 @@ static int geni_i2c_tx_one_msg(struct geni_i2c_dev *gi2c, struct i2c_msg *msg, size_t len = msg->len; struct i2c_msg *cur; - dma_buf = i2c_get_dma_safe_msg_buf(msg, 32); + dma_buf = gi2c->no_dma ? NULL : i2c_get_dma_safe_msg_buf(msg, 32); if (dma_buf) geni_se_select_mode(se, GENI_SE_DMA); else @@ -880,10 +881,12 @@ static int geni_i2c_probe(struct platform_device *pdev) goto err_resources; } - if (desc && desc->no_dma_support) + if (desc && desc->no_dma_support) { fifo_disable = false; - else + gi2c->no_dma = true; + } else { fifo_disable = readl_relaxed(gi2c->se.base + GENI_IF_DISABLE_RO) & FIFO_IF_DISABLE; + } if (fifo_disable) { /* FIFO is disabled, so we can only use GPI DMA */ From cf40c73ab25bcc9b2729c022f41866e44a029914 Mon Sep 17 00:00:00 2001 From: Carlos Song Date: Fri, 21 Nov 2025 11:00:30 +0800 Subject: [PATCH 2490/3902] i2c: imx-lpi2c: change to PIO mode in system-wide suspend/resume progress [ Upstream commit f2a3f51365bf672dab4b58d1e8954926a9196b44 ] EDMA resumes early and suspends late in the system power transition sequence, while LPI2C enters the NOIRQ stage for both suspend and resume. This means LPI2C resources become available before EDMA is fully resumed. Once IRQs are enabled, a slave device may immediately trigger an LPI2C transfer. If the transfer length meets DMA requirements, the driver will attempt to use EDMA even though EDMA may still be unavailable. This timing gap can lead to transfer failures. To prevent this, force LPI2C to use PIO mode during system-wide suspend and resume transitions. This reduces dependency on EDMA and avoids using an unready DMA resource. Fixes: a09c8b3f9047 ("i2c: imx-lpi2c: add eDMA mode support for LPI2C") Signed-off-by: Carlos Song Reviewed-by: Frank Li Signed-off-by: Wolfram Sang Signed-off-by: Sasha Levin --- drivers/i2c/busses/i2c-imx-lpi2c.c | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/drivers/i2c/busses/i2c-imx-lpi2c.c b/drivers/i2c/busses/i2c-imx-lpi2c.c index 2a0962a0b44175..d882126c1778cb 100644 --- a/drivers/i2c/busses/i2c-imx-lpi2c.c +++ b/drivers/i2c/busses/i2c-imx-lpi2c.c @@ -592,6 +592,13 @@ static bool is_use_dma(struct lpi2c_imx_struct *lpi2c_imx, struct i2c_msg *msg) if (!lpi2c_imx->can_use_dma) return false; + /* + * A system-wide suspend or resume transition is in progress. LPI2C should use PIO to + * transfer data to avoid issue caused by no ready DMA HW resource. + */ + if (pm_suspend_in_progress()) + return false; + /* * When the length of data is less than I2C_DMA_THRESHOLD, * cpu mode is used directly to avoid low performance. From f32d9e475a41f521634a133f6bf98ee0e2aae445 Mon Sep 17 00:00:00 2001 From: Peter Zijlstra Date: Tue, 13 Jan 2026 12:57:14 +0100 Subject: [PATCH 2491/3902] sched/deadline: Avoid double update_rq_clock() [ Upstream commit 4de9ff76067b40c3660df73efaea57389e62ea7a ] When setup_new_dl_entity() is called from enqueue_task_dl() -> enqueue_dl_entity(), the rq-clock should already be updated, and calling update_rq_clock() again is not right. Move the update_rq_clock() to the one other caller of setup_new_dl_entity(): sched_init_dl_server(). Fixes: 9f239df55546 ("sched/deadline: Initialize dl_servers after SMP") Reported-by: Pierre Gondois Signed-off-by: Peter Zijlstra (Intel) Tested-by: Pierre Gondois Link: https://patch.msgid.link/20260113115622.GA831285@noisy.programming.kicks-ass.net Signed-off-by: Sasha Levin --- kernel/sched/deadline.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/kernel/sched/deadline.c b/kernel/sched/deadline.c index d3be71d5a9ccc9..465592fa530ef7 100644 --- a/kernel/sched/deadline.c +++ b/kernel/sched/deadline.c @@ -761,8 +761,6 @@ static inline void setup_new_dl_entity(struct sched_dl_entity *dl_se) struct dl_rq *dl_rq = dl_rq_of_se(dl_se); struct rq *rq = rq_of_dl_rq(dl_rq); - update_rq_clock(rq); - WARN_ON(is_dl_boosted(dl_se)); WARN_ON(dl_time_before(rq_clock(rq), dl_se->deadline)); @@ -1623,6 +1621,7 @@ void sched_init_dl_servers(void) rq = cpu_rq(cpu); guard(rq_lock_irq)(rq); + update_rq_clock(rq); dl_se = &rq->fair_server; From 0754d5caac719f649dbb73365741dcb90fbf793a Mon Sep 17 00:00:00 2001 From: Peter Zijlstra Date: Thu, 15 Jan 2026 09:25:37 +0100 Subject: [PATCH 2492/3902] sched: Deadline has dynamic priority [ Upstream commit e008ec6c7904ed99d3b2cb634b6545b008a99288 ] While FIFO/RR have static priority, DEADLINE is a dynamic priority scheme. Notably it has static priority -1. Do not assume the priority doesn't change for deadline tasks just because the static priority doesn't change. This ensures DL always sees {DE,EN}QUEUE_MOVE where appropriate. Fixes: ff77e4685359 ("sched/rt: Fix PI handling vs. sched_setscheduler()") Signed-off-by: Peter Zijlstra (Intel) Tested-by: Pierre Gondois Tested-by: Juri Lelli Link: https://patch.msgid.link/20260114130528.GB831285@noisy.programming.kicks-ass.net Signed-off-by: Sasha Levin --- kernel/sched/core.c | 2 +- kernel/sched/syscalls.c | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/kernel/sched/core.c b/kernel/sched/core.c index eb47d294e2c5a1..e460c22de8ad49 100644 --- a/kernel/sched/core.c +++ b/kernel/sched/core.c @@ -7383,7 +7383,7 @@ void rt_mutex_setprio(struct task_struct *p, struct task_struct *pi_task) trace_sched_pi_setprio(p, pi_task); oldprio = p->prio; - if (oldprio == prio) + if (oldprio == prio && !dl_prio(prio)) queue_flag &= ~DEQUEUE_MOVE; prev_class = p->sched_class; diff --git a/kernel/sched/syscalls.c b/kernel/sched/syscalls.c index bf360a6fbb800e..6805a63d47af70 100644 --- a/kernel/sched/syscalls.c +++ b/kernel/sched/syscalls.c @@ -688,7 +688,7 @@ int __sched_setscheduler(struct task_struct *p, * itself. */ newprio = rt_effective_prio(p, newprio); - if (newprio == oldprio) + if (newprio == oldprio && !dl_prio(newprio)) queue_flags &= ~DEQUEUE_MOVE; } From f57953023f736a890076a8b7eda95c6b9478f55e Mon Sep 17 00:00:00 2001 From: Benjamin Tissoires Date: Mon, 15 Dec 2025 12:57:21 +0100 Subject: [PATCH 2493/3902] HID: usbhid: paper over wrong bNumDescriptor field commit f28beb69c51517aec7067dfb2074e7c751542384 upstream. Some faulty devices (ZWO EFWmini) have a wrong optional HID class descriptor count compared to the provided length. Given that we plainly ignore those optional descriptor, we can attempt to fix the provided number so we do not lock out those devices. Signed-off-by: Benjamin Tissoires Cc: Salvatore Bonaccorso Signed-off-by: Greg Kroah-Hartman --- drivers/hid/usbhid/hid-core.c | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/drivers/hid/usbhid/hid-core.c b/drivers/hid/usbhid/hid-core.c index aac0051a2cf655..758eb21430cda0 100644 --- a/drivers/hid/usbhid/hid-core.c +++ b/drivers/hid/usbhid/hid-core.c @@ -985,6 +985,7 @@ static int usbhid_parse(struct hid_device *hid) struct usb_device *dev = interface_to_usbdev (intf); struct hid_descriptor *hdesc; struct hid_class_descriptor *hcdesc; + __u8 fixed_opt_descriptors_size; u32 quirks = 0; unsigned int rsize = 0; char *rdesc; @@ -1015,7 +1016,21 @@ static int usbhid_parse(struct hid_device *hid) (hdesc->bNumDescriptors - 1) * sizeof(*hcdesc)) { dbg_hid("hid descriptor invalid, bLen=%hhu bNum=%hhu\n", hdesc->bLength, hdesc->bNumDescriptors); - return -EINVAL; + + /* + * Some devices may expose a wrong number of descriptors compared + * to the provided length. + * However, we ignore the optional hid class descriptors entirely + * so we can safely recompute the proper field. + */ + if (hdesc->bLength >= sizeof(*hdesc)) { + fixed_opt_descriptors_size = hdesc->bLength - sizeof(*hdesc); + + hid_warn(intf, "fixing wrong optional hid class descriptors count\n"); + hdesc->bNumDescriptors = fixed_opt_descriptors_size / sizeof(*hcdesc) + 1; + } else { + return -EINVAL; + } } hid->version = le16_to_cpu(hdesc->bcdHID); From 3667af036eeadd3ae261d6007ea2aa4c99878d7b Mon Sep 17 00:00:00 2001 From: Yonghong Song Date: Mon, 13 Oct 2025 22:16:39 -0700 Subject: [PATCH 2494/3902] selftests/bpf: Fix selftest verif_scale_strobemeta failure with llvm22 commit 4f8543b5f20f851cedbb23f8eade159871d84e2a upstream. With latest llvm22, I hit the verif_scale_strobemeta selftest failure below: $ ./test_progs -n 618 libbpf: prog 'on_event': BPF program load failed: -E2BIG libbpf: prog 'on_event': -- BEGIN PROG LOAD LOG -- BPF program is too large. Processed 1000001 insn verification time 7019091 usec stack depth 488 processed 1000001 insns (limit 1000000) max_states_per_insn 28 total_states 33927 peak_states 12813 mark_read 0 -- END PROG LOAD LOG -- libbpf: prog 'on_event': failed to load: -E2BIG libbpf: failed to load object 'strobemeta.bpf.o' scale_test:FAIL:expect_success unexpected error: -7 (errno 7) #618 verif_scale_strobemeta:FAIL But if I increase the verificaiton insn limit from 1M to 10M, the above test_progs run actually will succeed. The below is the result from veristat: $ ./veristat strobemeta.bpf.o Processing 'strobemeta.bpf.o'... File Program Verdict Duration (us) Insns States Program size Jited size ---------------- -------- ------- ------------- ------- ------ ------------ ---------- strobemeta.bpf.o on_event success 90250893 9777685 358230 15954 80794 ---------------- -------- ------- ------------- ------- ------ ------------ ---------- Done. Processed 1 files, 0 programs. Skipped 1 files, 0 programs. Further debugging shows the llvm commit [1] is responsible for the verificaiton failure as it tries to convert certain switch statement to if-condition. Such change may cause different transformation compared to original switch statement. In bpf program strobemeta.c case, the initial llvm ir for read_int_var() function is define internal void @read_int_var(ptr noundef %0, i64 noundef %1, ptr noundef %2, ptr noundef %3, ptr noundef %4) #2 !dbg !535 { %6 = alloca ptr, align 8 %7 = alloca i64, align 8 %8 = alloca ptr, align 8 %9 = alloca ptr, align 8 %10 = alloca ptr, align 8 %11 = alloca ptr, align 8 %12 = alloca i32, align 4 ... %20 = icmp ne ptr %19, null, !dbg !561 br i1 %20, label %22, label %21, !dbg !562 21: ; preds = %5 store i32 1, ptr %12, align 4 br label %48, !dbg !563 22: %23 = load ptr, ptr %9, align 8, !dbg !564 ... 47: ; preds = %38, %22 store i32 0, ptr %12, align 4, !dbg !588 br label %48, !dbg !588 48: ; preds = %47, %21 call void @llvm.lifetime.end.p0(ptr %11) #4, !dbg !588 %49 = load i32, ptr %12, align 4 switch i32 %49, label %51 [ i32 0, label %50 i32 1, label %50 ] 50: ; preds = %48, %48 ret void, !dbg !589 51: ; preds = %48 unreachable } Note that the above 'switch' statement is added by clang frontend. Without [1], the switch statement will survive until SelectionDag, so the switch statement acts like a 'barrier' and prevents some transformation involved with both 'before' and 'after' the switch statement. But with [1], the switch statement will be removed during middle end optimization and later middle end passes (esp. after inlining) have more freedom to reorder the code. The following is the related source code: static void *calc_location(struct strobe_value_loc *loc, void *tls_base): bpf_probe_read_user(&tls_ptr, sizeof(void *), dtv); /* if pointer has (void *)-1 value, then TLS wasn't initialized yet */ return tls_ptr && tls_ptr != (void *)-1 ? tls_ptr + tls_index.offset : NULL; In read_int_var() func, we have: void *location = calc_location(&cfg->int_locs[idx], tls_base); if (!location) return; bpf_probe_read_user(value, sizeof(struct strobe_value_generic), location); ... The static func calc_location() is called inside read_int_var(). The asm code without [1]: 77: .123....89 (85) call bpf_probe_read_user#112 78: ........89 (79) r1 = *(u64 *)(r10 -368) 79: .1......89 (79) r2 = *(u64 *)(r10 -8) 80: .12.....89 (bf) r3 = r2 81: .123....89 (0f) r3 += r1 82: ..23....89 (07) r2 += 1 83: ..23....89 (79) r4 = *(u64 *)(r10 -464) 84: ..234...89 (a5) if r2 < 0x2 goto pc+13 85: ...34...89 (15) if r3 == 0x0 goto pc+12 86: ...3....89 (bf) r1 = r10 87: .1.3....89 (07) r1 += -400 88: .1.3....89 (b4) w2 = 16 In this case, 'r2 < 0x2' and 'r3 == 0x0' go to null 'locaiton' place, so the verifier actually prefers to do verification first at 'r1 = r10' etc. The asm code with [1]: 119: .123....89 (85) call bpf_probe_read_user#112 120: ........89 (79) r1 = *(u64 *)(r10 -368) 121: .1......89 (79) r2 = *(u64 *)(r10 -8) 122: .12.....89 (bf) r3 = r2 123: .123....89 (0f) r3 += r1 124: ..23....89 (07) r2 += -1 125: ..23....89 (a5) if r2 < 0xfffffffe goto pc+6 126: ........89 (05) goto pc+17 ... 144: ........89 (b4) w1 = 0 145: .1......89 (6b) *(u16 *)(r8 +80) = r1 In this case, if 'r2 < 0xfffffffe' is true, the control will go to non-null 'location' branch, so 'goto pc+17' will actually go to null 'location' branch. This seems causing tremendous amount of verificaiton state. To fix the issue, rewrite the following code return tls_ptr && tls_ptr != (void *)-1 ? tls_ptr + tls_index.offset : NULL; to if/then statement and hopefully these explicit if/then statements are sticky during middle-end optimizations. Test with llvm20 and llvm21 as well and all strobemeta related selftests are passed. [1] https://github.com/llvm/llvm-project/pull/161000 Signed-off-by: Yonghong Song Link: https://lore.kernel.org/r/20251014051639.1996331-1-yonghong.song@linux.dev Signed-off-by: Alexei Starovoitov Signed-off-by: Shung-Hsi Yu Signed-off-by: Greg Kroah-Hartman --- tools/testing/selftests/bpf/progs/strobemeta.h | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tools/testing/selftests/bpf/progs/strobemeta.h b/tools/testing/selftests/bpf/progs/strobemeta.h index a5c74d31a2444c..6e1918deaf2627 100644 --- a/tools/testing/selftests/bpf/progs/strobemeta.h +++ b/tools/testing/selftests/bpf/progs/strobemeta.h @@ -330,9 +330,9 @@ static void *calc_location(struct strobe_value_loc *loc, void *tls_base) } bpf_probe_read_user(&tls_ptr, sizeof(void *), dtv); /* if pointer has (void *)-1 value, then TLS wasn't initialized yet */ - return tls_ptr && tls_ptr != (void *)-1 - ? tls_ptr + tls_index.offset - : NULL; + if (!tls_ptr || tls_ptr == (void *)-1) + return NULL; + return tls_ptr + tls_index.offset; } #ifdef SUBPROGS From 2674004ddc1fb1b940961b69f432c6f55f3290fc Mon Sep 17 00:00:00 2001 From: Brian Kao Date: Thu, 18 Dec 2025 03:17:23 +0000 Subject: [PATCH 2495/3902] scsi: core: Fix error handler encryption support commit 9a49157deeb23581fc5c8189b486340d7343264a upstream. Some low-level drivers (LLD) access block layer crypto fields, such as rq->crypt_keyslot and rq->crypt_ctx within `struct request`, to configure hardware for inline encryption. However, SCSI Error Handling (EH) commands (e.g., TEST UNIT READY, START STOP UNIT) should not involve any encryption setup. To prevent drivers from erroneously applying crypto settings during EH, this patch saves the original values of rq->crypt_keyslot and rq->crypt_ctx before an EH command is prepared via scsi_eh_prep_cmnd(). These fields in the 'struct request' are then set to NULL. The original values are restored in scsi_eh_restore_cmnd() after the EH command completes. This ensures that the block layer crypto context does not leak into EH command execution. Signed-off-by: Brian Kao Link: https://patch.msgid.link/20251218031726.2642834-1-powenkao@google.com Cc: stable@vger.kernel.org Reviewed-by: Bart Van Assche Signed-off-by: Martin K. Petersen Signed-off-by: Greg Kroah-Hartman --- drivers/scsi/scsi_error.c | 24 ++++++++++++++++++++++++ include/scsi/scsi_eh.h | 6 ++++++ 2 files changed, 30 insertions(+) diff --git a/drivers/scsi/scsi_error.c b/drivers/scsi/scsi_error.c index 1c13812a3f0358..830429483319ad 100644 --- a/drivers/scsi/scsi_error.c +++ b/drivers/scsi/scsi_error.c @@ -1060,6 +1060,9 @@ void scsi_eh_prep_cmnd(struct scsi_cmnd *scmd, struct scsi_eh_save *ses, unsigned char *cmnd, int cmnd_size, unsigned sense_bytes) { struct scsi_device *sdev = scmd->device; +#ifdef CONFIG_BLK_INLINE_ENCRYPTION + struct request *rq = scsi_cmd_to_rq(scmd); +#endif /* * We need saved copies of a number of fields - this is because @@ -1111,6 +1114,18 @@ void scsi_eh_prep_cmnd(struct scsi_cmnd *scmd, struct scsi_eh_save *ses, scmd->cmnd[1] = (scmd->cmnd[1] & 0x1f) | (sdev->lun << 5 & 0xe0); + /* + * Encryption must be disabled for the commands submitted by the error handler. + * Hence, clear the encryption context information. + */ +#ifdef CONFIG_BLK_INLINE_ENCRYPTION + ses->rq_crypt_keyslot = rq->crypt_keyslot; + ses->rq_crypt_ctx = rq->crypt_ctx; + + rq->crypt_keyslot = NULL; + rq->crypt_ctx = NULL; +#endif + /* * Zero the sense buffer. The scsi spec mandates that any * untransferred sense data should be interpreted as being zero. @@ -1128,6 +1143,10 @@ EXPORT_SYMBOL(scsi_eh_prep_cmnd); */ void scsi_eh_restore_cmnd(struct scsi_cmnd* scmd, struct scsi_eh_save *ses) { +#ifdef CONFIG_BLK_INLINE_ENCRYPTION + struct request *rq = scsi_cmd_to_rq(scmd); +#endif + /* * Restore original data */ @@ -1140,6 +1159,11 @@ void scsi_eh_restore_cmnd(struct scsi_cmnd* scmd, struct scsi_eh_save *ses) scmd->underflow = ses->underflow; scmd->prot_op = ses->prot_op; scmd->eh_eflags = ses->eh_eflags; + +#ifdef CONFIG_BLK_INLINE_ENCRYPTION + rq->crypt_keyslot = ses->rq_crypt_keyslot; + rq->crypt_ctx = ses->rq_crypt_ctx; +#endif } EXPORT_SYMBOL(scsi_eh_restore_cmnd); diff --git a/include/scsi/scsi_eh.h b/include/scsi/scsi_eh.h index 1ae08e81339fac..15679be90c5c3d 100644 --- a/include/scsi/scsi_eh.h +++ b/include/scsi/scsi_eh.h @@ -41,6 +41,12 @@ struct scsi_eh_save { unsigned char cmnd[32]; struct scsi_data_buffer sdb; struct scatterlist sense_sgl; + + /* struct request fields */ +#ifdef CONFIG_BLK_INLINE_ENCRYPTION + struct bio_crypt_ctx *rq_crypt_ctx; + struct blk_crypto_keyslot *rq_crypt_keyslot; +#endif }; extern void scsi_eh_prep_cmnd(struct scsi_cmnd *scmd, From 580581bd4af55259e0b35157ee9f52960b165e57 Mon Sep 17 00:00:00 2001 From: Paolo Bonzini Date: Wed, 24 Dec 2025 00:44:49 +0100 Subject: [PATCH 2496/3902] selftests: kvm: replace numbered sync points with actions commit a1025dcd377ef92d9a09af03b70ce80be281ee22 upstream. Rework the guest=>host syncs in the AMX test to use named actions instead of arbitrary, incrementing numbers. The "stage" of the test has no real meaning, what matters is what action the test wants the host to perform. The incrementing numbers are somewhat helpful for triaging failures, but fully debugging failures almost always requires a much deeper dive into the test (and KVM). Using named actions not only makes it easier to extend the test without having to shift all sync point numbers, it makes the code easier to read. [Commit message by Sean Christopherson] Cc: stable@vger.kernel.org Signed-off-by: Paolo Bonzini Signed-off-by: Greg Kroah-Hartman --- tools/testing/selftests/kvm/x86/amx_test.c | 88 +++++++++++----------- 1 file changed, 43 insertions(+), 45 deletions(-) diff --git a/tools/testing/selftests/kvm/x86/amx_test.c b/tools/testing/selftests/kvm/x86/amx_test.c index f4ce5a185a7ddf..3de4402ac17dee 100644 --- a/tools/testing/selftests/kvm/x86/amx_test.c +++ b/tools/testing/selftests/kvm/x86/amx_test.c @@ -124,6 +124,14 @@ static void set_tilecfg(struct tile_config *cfg) } } +enum { + /* Check TMM0 against tiledata */ + TEST_COMPARE_TILEDATA = 1, + + /* Full VM save/restore */ + TEST_SAVE_RESTORE = 2, +}; + static void __attribute__((__flatten__)) guest_code(struct tile_config *amx_cfg, struct tile_data *tiledata, struct xstate *xstate) @@ -131,20 +139,20 @@ static void __attribute__((__flatten__)) guest_code(struct tile_config *amx_cfg, GUEST_ASSERT(this_cpu_has(X86_FEATURE_XSAVE) && this_cpu_has(X86_FEATURE_OSXSAVE)); check_xtile_info(); - GUEST_SYNC(1); + GUEST_SYNC(TEST_SAVE_RESTORE); /* xfd=0, enable amx */ wrmsr(MSR_IA32_XFD, 0); - GUEST_SYNC(2); + GUEST_SYNC(TEST_SAVE_RESTORE); GUEST_ASSERT(rdmsr(MSR_IA32_XFD) == 0); set_tilecfg(amx_cfg); __ldtilecfg(amx_cfg); - GUEST_SYNC(3); + GUEST_SYNC(TEST_SAVE_RESTORE); /* Check save/restore when trap to userspace */ __tileloadd(tiledata); - GUEST_SYNC(4); + GUEST_SYNC(TEST_COMPARE_TILEDATA | TEST_SAVE_RESTORE); __tilerelease(); - GUEST_SYNC(5); + GUEST_SYNC(TEST_SAVE_RESTORE); /* * After XSAVEC, XTILEDATA is cleared in the xstate_bv but is set in * the xcomp_bv. @@ -154,6 +162,8 @@ static void __attribute__((__flatten__)) guest_code(struct tile_config *amx_cfg, GUEST_ASSERT(!(xstate->header.xstate_bv & XFEATURE_MASK_XTILE_DATA)); GUEST_ASSERT(xstate->header.xcomp_bv & XFEATURE_MASK_XTILE_DATA); + /* #NM test */ + /* xfd=0x40000, disable amx tiledata */ wrmsr(MSR_IA32_XFD, XFEATURE_MASK_XTILE_DATA); @@ -166,13 +176,13 @@ static void __attribute__((__flatten__)) guest_code(struct tile_config *amx_cfg, GUEST_ASSERT(!(xstate->header.xstate_bv & XFEATURE_MASK_XTILE_DATA)); GUEST_ASSERT((xstate->header.xcomp_bv & XFEATURE_MASK_XTILE_DATA)); - GUEST_SYNC(6); + GUEST_SYNC(TEST_SAVE_RESTORE); GUEST_ASSERT(rdmsr(MSR_IA32_XFD) == XFEATURE_MASK_XTILE_DATA); set_tilecfg(amx_cfg); __ldtilecfg(amx_cfg); /* Trigger #NM exception */ __tileloadd(tiledata); - GUEST_SYNC(10); + GUEST_SYNC(TEST_COMPARE_TILEDATA | TEST_SAVE_RESTORE); GUEST_DONE(); } @@ -180,18 +190,18 @@ static void __attribute__((__flatten__)) guest_code(struct tile_config *amx_cfg, void guest_nm_handler(struct ex_regs *regs) { /* Check if #NM is triggered by XFEATURE_MASK_XTILE_DATA */ - GUEST_SYNC(7); + GUEST_SYNC(TEST_SAVE_RESTORE); GUEST_ASSERT(!(get_cr0() & X86_CR0_TS)); GUEST_ASSERT(rdmsr(MSR_IA32_XFD_ERR) == XFEATURE_MASK_XTILE_DATA); GUEST_ASSERT(rdmsr(MSR_IA32_XFD) == XFEATURE_MASK_XTILE_DATA); - GUEST_SYNC(8); + GUEST_SYNC(TEST_SAVE_RESTORE); GUEST_ASSERT(rdmsr(MSR_IA32_XFD_ERR) == XFEATURE_MASK_XTILE_DATA); GUEST_ASSERT(rdmsr(MSR_IA32_XFD) == XFEATURE_MASK_XTILE_DATA); /* Clear xfd_err */ wrmsr(MSR_IA32_XFD_ERR, 0); /* xfd=0, enable amx */ wrmsr(MSR_IA32_XFD, 0); - GUEST_SYNC(9); + GUEST_SYNC(TEST_SAVE_RESTORE); } int main(int argc, char *argv[]) @@ -244,6 +254,7 @@ int main(int argc, char *argv[]) memset(addr_gva2hva(vm, xstate), 0, PAGE_SIZE * DIV_ROUND_UP(XSAVE_SIZE, PAGE_SIZE)); vcpu_args_set(vcpu, 3, amx_cfg, tiledata, xstate); + int iter = 0; for (;;) { vcpu_run(vcpu); TEST_ASSERT_KVM_EXIT_REASON(vcpu, KVM_EXIT_IO); @@ -253,20 +264,9 @@ int main(int argc, char *argv[]) REPORT_GUEST_ASSERT(uc); /* NOT REACHED */ case UCALL_SYNC: - switch (uc.args[1]) { - case 1: - case 2: - case 3: - case 5: - case 6: - case 7: - case 8: - fprintf(stderr, "GUEST_SYNC(%ld)\n", uc.args[1]); - break; - case 4: - case 10: - fprintf(stderr, - "GUEST_SYNC(%ld), check save/restore status\n", uc.args[1]); + ++iter; + if (uc.args[1] & TEST_COMPARE_TILEDATA) { + fprintf(stderr, "GUEST_SYNC #%d, check TMM0 contents\n", iter); /* Compacted mode, get amx offset by xsave area * size subtract 8K amx size. @@ -279,11 +279,25 @@ int main(int argc, char *argv[]) ret = memcmp(amx_start, tiles_data, TILE_SIZE); TEST_ASSERT(ret == 0, "memcmp failed, ret=%d", ret); kvm_x86_state_cleanup(state); - break; - case 9: - fprintf(stderr, - "GUEST_SYNC(%ld), #NM exception and enable amx\n", uc.args[1]); - break; + } + if (uc.args[1] & TEST_SAVE_RESTORE) { + fprintf(stderr, "GUEST_SYNC #%d, save/restore VM state\n", iter); + state = vcpu_save_state(vcpu); + memset(®s1, 0, sizeof(regs1)); + vcpu_regs_get(vcpu, ®s1); + + kvm_vm_release(vm); + + /* Restore state in a new VM. */ + vcpu = vm_recreate_with_one_vcpu(vm); + vcpu_load_state(vcpu, state); + kvm_x86_state_cleanup(state); + + memset(®s2, 0, sizeof(regs2)); + vcpu_regs_get(vcpu, ®s2); + TEST_ASSERT(!memcmp(®s1, ®s2, sizeof(regs2)), + "Unexpected register values after vcpu_load_state; rdi: %lx rsi: %lx", + (ulong) regs2.rdi, (ulong) regs2.rsi); } break; case UCALL_DONE: @@ -293,22 +307,6 @@ int main(int argc, char *argv[]) TEST_FAIL("Unknown ucall %lu", uc.cmd); } - state = vcpu_save_state(vcpu); - memset(®s1, 0, sizeof(regs1)); - vcpu_regs_get(vcpu, ®s1); - - kvm_vm_release(vm); - - /* Restore state in a new VM. */ - vcpu = vm_recreate_with_one_vcpu(vm); - vcpu_load_state(vcpu, state); - kvm_x86_state_cleanup(state); - - memset(®s2, 0, sizeof(regs2)); - vcpu_regs_get(vcpu, ®s2); - TEST_ASSERT(!memcmp(®s1, ®s2, sizeof(regs2)), - "Unexpected register values after vcpu_load_state; rdi: %lx rsi: %lx", - (ulong) regs2.rdi, (ulong) regs2.rsi); } done: kvm_vm_free(vm); From 522a38f455bffda228e2853ba6dab09ab7a6fcd4 Mon Sep 17 00:00:00 2001 From: Paolo Bonzini Date: Wed, 31 Dec 2025 16:47:26 +0100 Subject: [PATCH 2497/3902] selftests: kvm: try getting XFD and XSAVE state out of sync commit 0383a8edef396cf0a6884b0be81d62bde60737b0 upstream. The host is allowed to set FPU state that includes a disabled xstate component. Check that this does not cause bad effects. Cc: stable@vger.kernel.org Signed-off-by: Paolo Bonzini Signed-off-by: Greg Kroah-Hartman --- tools/testing/selftests/kvm/x86/amx_test.c | 38 +++++++++++++++++----- 1 file changed, 30 insertions(+), 8 deletions(-) diff --git a/tools/testing/selftests/kvm/x86/amx_test.c b/tools/testing/selftests/kvm/x86/amx_test.c index 3de4402ac17dee..bee56c1f783399 100644 --- a/tools/testing/selftests/kvm/x86/amx_test.c +++ b/tools/testing/selftests/kvm/x86/amx_test.c @@ -125,11 +125,17 @@ static void set_tilecfg(struct tile_config *cfg) } enum { + /* Retrieve TMM0 from guest, stash it for TEST_RESTORE_TILEDATA */ + TEST_SAVE_TILEDATA = 1, + /* Check TMM0 against tiledata */ - TEST_COMPARE_TILEDATA = 1, + TEST_COMPARE_TILEDATA = 2, + + /* Restore TMM0 from earlier save */ + TEST_RESTORE_TILEDATA = 4, /* Full VM save/restore */ - TEST_SAVE_RESTORE = 2, + TEST_SAVE_RESTORE = 8, }; static void __attribute__((__flatten__)) guest_code(struct tile_config *amx_cfg, @@ -150,7 +156,16 @@ static void __attribute__((__flatten__)) guest_code(struct tile_config *amx_cfg, GUEST_SYNC(TEST_SAVE_RESTORE); /* Check save/restore when trap to userspace */ __tileloadd(tiledata); - GUEST_SYNC(TEST_COMPARE_TILEDATA | TEST_SAVE_RESTORE); + GUEST_SYNC(TEST_SAVE_TILEDATA | TEST_COMPARE_TILEDATA | TEST_SAVE_RESTORE); + + /* xfd=0x40000, disable amx tiledata */ + wrmsr(MSR_IA32_XFD, XFEATURE_MASK_XTILE_DATA); + + /* host tries setting tiledata while guest XFD is set */ + GUEST_SYNC(TEST_RESTORE_TILEDATA); + GUEST_SYNC(TEST_SAVE_RESTORE); + + wrmsr(MSR_IA32_XFD, 0); __tilerelease(); GUEST_SYNC(TEST_SAVE_RESTORE); /* @@ -210,10 +225,10 @@ int main(int argc, char *argv[]) struct kvm_vcpu *vcpu; struct kvm_vm *vm; struct kvm_x86_state *state; + struct kvm_x86_state *tile_state = NULL; int xsave_restore_size; vm_vaddr_t amx_cfg, tiledata, xstate; struct ucall uc; - u32 amx_offset; int ret; /* @@ -265,20 +280,27 @@ int main(int argc, char *argv[]) /* NOT REACHED */ case UCALL_SYNC: ++iter; + if (uc.args[1] & TEST_SAVE_TILEDATA) { + fprintf(stderr, "GUEST_SYNC #%d, save tiledata\n", iter); + tile_state = vcpu_save_state(vcpu); + } if (uc.args[1] & TEST_COMPARE_TILEDATA) { fprintf(stderr, "GUEST_SYNC #%d, check TMM0 contents\n", iter); /* Compacted mode, get amx offset by xsave area * size subtract 8K amx size. */ - amx_offset = xsave_restore_size - NUM_TILES*TILE_SIZE; - state = vcpu_save_state(vcpu); - void *amx_start = (void *)state->xsave + amx_offset; + u32 amx_offset = xsave_restore_size - NUM_TILES*TILE_SIZE; + void *amx_start = (void *)tile_state->xsave + amx_offset; void *tiles_data = (void *)addr_gva2hva(vm, tiledata); /* Only check TMM0 register, 1 tile */ ret = memcmp(amx_start, tiles_data, TILE_SIZE); TEST_ASSERT(ret == 0, "memcmp failed, ret=%d", ret); - kvm_x86_state_cleanup(state); + } + if (uc.args[1] & TEST_RESTORE_TILEDATA) { + fprintf(stderr, "GUEST_SYNC #%d, before KVM_SET_XSAVE\n", iter); + vcpu_xsave_set(vcpu, tile_state->xsave); + fprintf(stderr, "GUEST_SYNC #%d, after KVM_SET_XSAVE\n", iter); } if (uc.args[1] & TEST_SAVE_RESTORE) { fprintf(stderr, "GUEST_SYNC #%d, save/restore VM state\n", iter); From 670cd1c2384acd367da438032084429d1c131b2a Mon Sep 17 00:00:00 2001 From: Jaroslav Kysela Date: Wed, 7 Jan 2026 22:36:42 +0100 Subject: [PATCH 2498/3902] ALSA: pcm: Improve the fix for race of buffer access at PCM OSS layer commit 47c27c9c9c720bc93fdc69605d0ecd9382e99047 upstream. Handle the error code from snd_pcm_buffer_access_lock() in snd_pcm_runtime_buffer_set_silence() function. Found by Alexandros Panagiotou Fixes: 93a81ca06577 ("ALSA: pcm: Fix race of buffer access at PCM OSS layer") Cc: stable@vger.kernel.org # 6.15 Signed-off-by: Jaroslav Kysela Link: https://patch.msgid.link/20260107213642.332954-1-perex@perex.cz Signed-off-by: Takashi Iwai Signed-off-by: Greg Kroah-Hartman --- include/sound/pcm.h | 2 +- sound/core/oss/pcm_oss.c | 4 +++- sound/core/pcm_native.c | 9 +++++++-- 3 files changed, 11 insertions(+), 4 deletions(-) diff --git a/include/sound/pcm.h b/include/sound/pcm.h index 58fd6e84f9613f..a7860c047503a8 100644 --- a/include/sound/pcm.h +++ b/include/sound/pcm.h @@ -1402,7 +1402,7 @@ int snd_pcm_lib_mmap_iomem(struct snd_pcm_substream *substream, struct vm_area_s #define snd_pcm_lib_mmap_iomem NULL #endif -void snd_pcm_runtime_buffer_set_silence(struct snd_pcm_runtime *runtime); +int snd_pcm_runtime_buffer_set_silence(struct snd_pcm_runtime *runtime); /** * snd_pcm_limit_isa_dma_size - Get the max size fitting with ISA DMA transfer diff --git a/sound/core/oss/pcm_oss.c b/sound/core/oss/pcm_oss.c index a82dd155e1d3a6..b12df5b5ddfc17 100644 --- a/sound/core/oss/pcm_oss.c +++ b/sound/core/oss/pcm_oss.c @@ -1074,7 +1074,9 @@ static int snd_pcm_oss_change_params_locked(struct snd_pcm_substream *substream) runtime->oss.params = 0; runtime->oss.prepare = 1; runtime->oss.buffer_used = 0; - snd_pcm_runtime_buffer_set_silence(runtime); + err = snd_pcm_runtime_buffer_set_silence(runtime); + if (err < 0) + goto failure; runtime->oss.period_frames = snd_pcm_alsa_frames(substream, oss_period_size); diff --git a/sound/core/pcm_native.c b/sound/core/pcm_native.c index 68bee40c9adafd..932a9bf98cbc09 100644 --- a/sound/core/pcm_native.c +++ b/sound/core/pcm_native.c @@ -730,13 +730,18 @@ static void snd_pcm_buffer_access_unlock(struct snd_pcm_runtime *runtime) } /* fill the PCM buffer with the current silence format; called from pcm_oss.c */ -void snd_pcm_runtime_buffer_set_silence(struct snd_pcm_runtime *runtime) +int snd_pcm_runtime_buffer_set_silence(struct snd_pcm_runtime *runtime) { - snd_pcm_buffer_access_lock(runtime); + int err; + + err = snd_pcm_buffer_access_lock(runtime); + if (err < 0) + return err; if (runtime->dma_area) snd_pcm_format_set_silence(runtime->format, runtime->dma_area, bytes_to_samples(runtime, runtime->dma_bytes)); snd_pcm_buffer_access_unlock(runtime); + return 0; } EXPORT_SYMBOL_GPL(snd_pcm_runtime_buffer_set_silence); From 819268882628fccedbf19796a64ea34402b85027 Mon Sep 17 00:00:00 2001 From: Matthew Schwartz Date: Thu, 8 Jan 2026 01:36:50 -0800 Subject: [PATCH 2499/3902] ALSA: hda/tas2781: Skip UEFI calibration on ASUS ROG Xbox Ally X commit b7e26c8bdae70832d7c4b31ec2995b1812a60169 upstream. There is currently an issue with UEFI calibration data parsing for some TAS devices, like the ASUS ROG Xbox Ally X (RC73XA), that causes audio quality issues such as gaps in playback. Until the issue is root caused and fixed, add a quirk to skip using the UEFI calibration data and fall back to using the calibration data provided by the DSP firmware, which restores full speaker functionality on affected devices. Cc: stable@vger.kernel.org # 6.18 Link: https://lore.kernel.org/all/160aef32646c4d5498cbfd624fd683cc@ti.com/ Closes: https://lore.kernel.org/all/0ba100d0-9b6f-4a3b-bffa-61abe1b46cd5@linux.dev/ Suggested-by: Baojun Xu Signed-off-by: Matthew Schwartz Reviewed-by: Antheas Kapenekakis Link: https://patch.msgid.link/20260108093650.1142176-1-matthew.schwartz@linux.dev Signed-off-by: Takashi Iwai Signed-off-by: Greg Kroah-Hartman --- sound/hda/codecs/side-codecs/tas2781_hda_i2c.c | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/sound/hda/codecs/side-codecs/tas2781_hda_i2c.c b/sound/hda/codecs/side-codecs/tas2781_hda_i2c.c index f7a7f216d5865f..0e4bda3a544ea4 100644 --- a/sound/hda/codecs/side-codecs/tas2781_hda_i2c.c +++ b/sound/hda/codecs/side-codecs/tas2781_hda_i2c.c @@ -60,6 +60,7 @@ struct tas2781_hda_i2c_priv { int (*save_calibration)(struct tas2781_hda *h); int hda_chip_id; + bool skip_calibration; }; static int tas2781_get_i2c_res(struct acpi_resource *ares, void *data) @@ -491,7 +492,8 @@ static void tasdevice_dspfw_init(void *context) /* If calibrated data occurs error, dsp will still works with default * calibrated data inside algo. */ - hda_priv->save_calibration(tas_hda); + if (!hda_priv->skip_calibration) + hda_priv->save_calibration(tas_hda); } static void tasdev_fw_ready(const struct firmware *fmw, void *context) @@ -548,6 +550,7 @@ static int tas2781_hda_bind(struct device *dev, struct device *master, void *master_data) { struct tas2781_hda *tas_hda = dev_get_drvdata(dev); + struct tas2781_hda_i2c_priv *hda_priv = tas_hda->hda_priv; struct hda_component_parent *parent = master_data; struct hda_component *comp; struct hda_codec *codec; @@ -573,6 +576,14 @@ static int tas2781_hda_bind(struct device *dev, struct device *master, break; } + /* + * Using ASUS ROG Xbox Ally X (RC73XA) UEFI calibration data + * causes audio dropouts during playback, use fallback data + * from DSP firmware as a workaround. + */ + if (codec->core.subsystem_id == 0x10431384) + hda_priv->skip_calibration = true; + pm_runtime_get_sync(dev); comp->dev = dev; From ded801af28a99df4bfbfef6d9b1696042b8787f8 Mon Sep 17 00:00:00 2001 From: Zhang Heng Date: Thu, 15 Jan 2026 09:58:44 +0800 Subject: [PATCH 2500/3902] ALSA: hda/realtek: Add quirk for HP Pavilion x360 to enable mute LED commit ab2be3af8c4ea57f779474cd2a2fe8dd4ad537a6 upstream. This quirk enables mute LED on HP Pavilion x360 2-in-1 Laptop 14-ek0xxx, which use ALC245 codec. Link: https://bugzilla.kernel.org/show_bug.cgi?id=220220 Cc: Signed-off-by: Zhang Heng Link: https://patch.msgid.link/20260115015844.3129890-1-zhangheng@kylinos.cn Signed-off-by: Takashi Iwai Signed-off-by: Greg Kroah-Hartman --- sound/hda/codecs/realtek/alc269.c | 1 + 1 file changed, 1 insertion(+) diff --git a/sound/hda/codecs/realtek/alc269.c b/sound/hda/codecs/realtek/alc269.c index eb6197d19078c1..b5aa8da1e50a89 100644 --- a/sound/hda/codecs/realtek/alc269.c +++ b/sound/hda/codecs/realtek/alc269.c @@ -6529,6 +6529,7 @@ static const struct hda_quirk alc269_fixup_tbl[] = { SND_PCI_QUIRK(0x103c, 0x8a2e, "HP Envy 16", ALC287_FIXUP_CS35L41_I2C_2), SND_PCI_QUIRK(0x103c, 0x8a30, "HP Envy 17", ALC287_FIXUP_CS35L41_I2C_2), SND_PCI_QUIRK(0x103c, 0x8a31, "HP Envy 15", ALC287_FIXUP_CS35L41_I2C_2), + SND_PCI_QUIRK(0x103c, 0x8a34, "HP Pavilion x360 2-in-1 Laptop 14-ek0xxx", ALC245_FIXUP_HP_MUTE_LED_COEFBIT), SND_PCI_QUIRK(0x103c, 0x8a4f, "HP Victus 15-fa0xxx (MB 8A4F)", ALC245_FIXUP_HP_MUTE_LED_COEFBIT), SND_PCI_QUIRK(0x103c, 0x8a6e, "HP EDNA 360", ALC287_FIXUP_CS35L41_I2C_4), SND_PCI_QUIRK(0x103c, 0x8a74, "HP ProBook 440 G8 Notebook PC", ALC236_FIXUP_HP_GPIO_LED), From f1718da051282698aa8fa150bebb9724f6389fda Mon Sep 17 00:00:00 2001 From: Nilay Shroff Date: Tue, 13 Jan 2026 12:27:22 +0530 Subject: [PATCH 2501/3902] null_blk: fix kmemleak by releasing references to fault configfs items commit 40b94ec7edbbb867c4e26a1a43d2b898f04b93c5 upstream. When CONFIG_BLK_DEV_NULL_BLK_FAULT_INJECTION is enabled, the null-blk driver sets up fault injection support by creating the timeout_inject, requeue_inject, and init_hctx_fault_inject configfs items as children of the top-level nullbX configfs group. However, when the nullbX device is removed, the references taken to these fault-config configfs items are not released. As a result, kmemleak reports a memory leak, for example: unreferenced object 0xc00000021ff25c40 (size 32): comm "mkdir", pid 10665, jiffies 4322121578 hex dump (first 32 bytes): 69 6e 69 74 5f 68 63 74 78 5f 66 61 75 6c 74 5f init_hctx_fault_ 69 6e 6a 65 63 74 00 88 00 00 00 00 00 00 00 00 inject.......... backtrace (crc 1a018c86): __kmalloc_node_track_caller_noprof+0x494/0xbd8 kvasprintf+0x74/0xf4 config_item_set_name+0xf0/0x104 config_group_init_type_name+0x48/0xfc fault_config_init+0x48/0xf0 0xc0080000180559e4 configfs_mkdir+0x304/0x814 vfs_mkdir+0x49c/0x604 do_mkdirat+0x314/0x3d0 sys_mkdir+0xa0/0xd8 system_call_exception+0x1b0/0x4f0 system_call_vectored_common+0x15c/0x2ec Fix this by explicitly releasing the references to the fault-config configfs items when dropping the reference to the top-level nullbX configfs group. Cc: stable@vger.kernel.org Reviewed-by: Chaitanya Kulkarni Fixes: bb4c19e030f4 ("block: null_blk: make fault-injection dynamically configurable per device") Signed-off-by: Nilay Shroff Signed-off-by: Jens Axboe Signed-off-by: Greg Kroah-Hartman --- drivers/block/null_blk/main.c | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/drivers/block/null_blk/main.c b/drivers/block/null_blk/main.c index 0ee55f889cfdd3..50938881219bca 100644 --- a/drivers/block/null_blk/main.c +++ b/drivers/block/null_blk/main.c @@ -665,12 +665,22 @@ static void nullb_add_fault_config(struct nullb_device *dev) configfs_add_default_group(&dev->init_hctx_fault_config.group, &dev->group); } +static void nullb_del_fault_config(struct nullb_device *dev) +{ + config_item_put(&dev->init_hctx_fault_config.group.cg_item); + config_item_put(&dev->requeue_config.group.cg_item); + config_item_put(&dev->timeout_config.group.cg_item); +} + #else static void nullb_add_fault_config(struct nullb_device *dev) { } +static void nullb_del_fault_config(struct nullb_device *dev) +{ +} #endif static struct @@ -702,7 +712,7 @@ nullb_group_drop_item(struct config_group *group, struct config_item *item) null_del_dev(dev->nullb); mutex_unlock(&lock); } - + nullb_del_fault_config(dev); config_item_put(item); } From 9f669a38ca70839229b7ba0f851820850a2fe1f7 Mon Sep 17 00:00:00 2001 From: Marc Kleine-Budde Date: Tue, 23 Dec 2025 21:21:39 +0100 Subject: [PATCH 2502/3902] can: gs_usb: gs_usb_receive_bulk_callback(): fix URB memory leak commit 7352e1d5932a0e777e39fa4b619801191f57e603 upstream. In gs_can_open(), the URBs for USB-in transfers are allocated, added to the parent->rx_submitted anchor and submitted. In the complete callback gs_usb_receive_bulk_callback(), the URB is processed and resubmitted. In gs_can_close() the URBs are freed by calling usb_kill_anchored_urbs(parent->rx_submitted). However, this does not take into account that the USB framework unanchors the URB before the complete function is called. This means that once an in-URB has been completed, it is no longer anchored and is ultimately not released in gs_can_close(). Fix the memory leak by anchoring the URB in the gs_usb_receive_bulk_callback() to the parent->rx_submitted anchor. Fixes: d08e973a77d1 ("can: gs_usb: Added support for the GS_USB CAN devices") Cc: stable@vger.kernel.org Link: https://patch.msgid.link/20260105-gs_usb-fix-memory-leak-v2-1-cc6ed6438034@pengutronix.de Signed-off-by: Marc Kleine-Budde Signed-off-by: Greg Kroah-Hartman --- drivers/net/can/usb/gs_usb.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/drivers/net/can/usb/gs_usb.c b/drivers/net/can/usb/gs_usb.c index db6885bcba28dd..b14b132ad8e6ac 100644 --- a/drivers/net/can/usb/gs_usb.c +++ b/drivers/net/can/usb/gs_usb.c @@ -751,6 +751,8 @@ static void gs_usb_receive_bulk_callback(struct urb *urb) hf, parent->hf_size_rx, gs_usb_receive_bulk_callback, parent); + usb_anchor_urb(urb, &parent->rx_submitted); + rc = usb_submit_urb(urb, GFP_ATOMIC); /* USB failure take down all interfaces */ From d922e7c5bb6424ea3b2c27d628af3cbbfb786d17 Mon Sep 17 00:00:00 2001 From: Ondrej Ille Date: Mon, 5 Jan 2026 12:16:20 +0100 Subject: [PATCH 2503/3902] can: ctucanfd: fix SSP_SRC in cases when bit-rate is higher than 1 MBit. commit e707c591a139d1bfa4ddc83036fc820ca006a140 upstream. The Secondary Sample Point Source field has been set to an incorrect value by some mistake in the past 0b01 - SSP_SRC_NO_SSP - SSP is not used. for data bitrates above 1 MBit/s. The correct/default value already used for lower bitrates is 0b00 - SSP_SRC_MEAS_N_OFFSET - SSP position = TRV_DELAY (Measured Transmitter delay) + SSP_OFFSET. The related configuration register structure is described in section 3.1.46 SSP_CFG of the CTU CAN FD IP CORE Datasheet. The analysis leading to the proper configuration is described in section 2.8.3 Secondary sampling point of the datasheet. The change has been tested on AMD/Xilinx Zynq with the next CTU CN FD IP core versions: - 2.6 aka master in the "integration with Zynq-7000 system" test 6.12.43-rt12+ #1 SMP PREEMPT_RT kernel with CTU CAN FD git driver (change already included in the driver repo) - older 2.5 snapshot with mainline kernels with this patch applied locally in the multiple CAN latency tester nightly runs 6.18.0-rc4-rt3-dut #1 SMP PREEMPT_RT 6.19.0-rc3-dut The logs, the datasheet and sources are available at https://canbus.pages.fel.cvut.cz/ Signed-off-by: Ondrej Ille Signed-off-by: Pavel Pisa Link: https://patch.msgid.link/20260105111620.16580-1-pisa@fel.cvut.cz Fixes: 2dcb8e8782d8 ("can: ctucanfd: add support for CTU CAN FD open-source IP core - bus independent part.") Cc: stable@vger.kernel.org Signed-off-by: Marc Kleine-Budde Signed-off-by: Greg Kroah-Hartman --- drivers/net/can/ctucanfd/ctucanfd_base.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/net/can/ctucanfd/ctucanfd_base.c b/drivers/net/can/ctucanfd/ctucanfd_base.c index 8bd3f0fc385c30..33a5ec43462786 100644 --- a/drivers/net/can/ctucanfd/ctucanfd_base.c +++ b/drivers/net/can/ctucanfd/ctucanfd_base.c @@ -310,7 +310,7 @@ static int ctucan_set_secondary_sample_point(struct net_device *ndev) } ssp_cfg = FIELD_PREP(REG_TRV_DELAY_SSP_OFFSET, ssp_offset); - ssp_cfg |= FIELD_PREP(REG_TRV_DELAY_SSP_SRC, 0x1); + ssp_cfg |= FIELD_PREP(REG_TRV_DELAY_SSP_SRC, 0x0); } ctucan_write32(priv, CTUCANFD_TRV_DELAY, ssp_cfg); From 6121b7564c725b632ffe4764abe85aa239d37703 Mon Sep 17 00:00:00 2001 From: Tetsuo Handa Date: Wed, 14 Jan 2026 00:28:47 +0900 Subject: [PATCH 2504/3902] net: can: j1939: j1939_xtp_rx_rts_session_active(): deactivate session upon receiving the second rts commit 1809c82aa073a11b7d335ae932d81ce51a588a4a upstream. Since j1939_session_deactivate_activate_next() in j1939_tp_rxtimer() is called only when the timer is enabled, we need to call j1939_session_deactivate_activate_next() if we cancelled the timer. Otherwise, refcount for j1939_session leaks, which will later appear as | unregister_netdevice: waiting for vcan0 to become free. Usage count = 2. problem. Reported-by: syzbot Closes: https://syzkaller.appspot.com/bug?extid=881d65229ca4f9ae8c84 Signed-off-by: Tetsuo Handa Tested-by: Oleksij Rempel Acked-by: Oleksij Rempel Fixes: 9d71dd0c7009 ("can: add support of SAE J1939 protocol") Link: https://patch.msgid.link/b1212653-8fa1-44e1-be9d-12f950fb3a07@I-love.SAKURA.ne.jp Cc: stable@vger.kernel.org Signed-off-by: Marc Kleine-Budde Signed-off-by: Greg Kroah-Hartman --- net/can/j1939/transport.c | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/net/can/j1939/transport.c b/net/can/j1939/transport.c index 613a911dda100b..8656ab388c83e6 100644 --- a/net/can/j1939/transport.c +++ b/net/can/j1939/transport.c @@ -1695,8 +1695,16 @@ static int j1939_xtp_rx_rts_session_active(struct j1939_session *session, j1939_session_timers_cancel(session); j1939_session_cancel(session, J1939_XTP_ABORT_BUSY); - if (session->transmission) + if (session->transmission) { j1939_session_deactivate_activate_next(session); + } else if (session->state == J1939_SESSION_WAITING_ABORT) { + /* Force deactivation for the receiver. + * If we rely on the timer starting in j1939_session_cancel, + * a second RTS call here will cancel that timer and fail + * to restart it because the state is already WAITING_ABORT. + */ + j1939_session_deactivate_activate_next(session); + } return -EBUSY; } From 6ca76572c6049787e4487953802d95d097292eb5 Mon Sep 17 00:00:00 2001 From: Lorenzo Stoakes Date: Mon, 5 Jan 2026 20:11:48 +0000 Subject: [PATCH 2505/3902] tools/testing/selftests: add tests for !tgt, src mremap() merges commit 0ace8f2db6b3b4b0677e559d1a7ab7fd625d61ec upstream. Test that mremap()'ing a VMA into a position such that the target VMA on merge is unfaulted and the source faulted is correctly performed. We cover 4 cases: 1. Previous VMA unfaulted: copied -----| v |-----------|.............| | unfaulted |(faulted VMA)| |-----------|.............| prev target = prev, expand prev to cover. 2. Next VMA unfaulted: copied -----| v |.............|-----------| |(faulted VMA)| unfaulted | |.............|-----------| next target = next, expand next to cover. 3. Both adjacent VMAs unfaulted: copied -----| v |-----------|.............|-----------| | unfaulted |(faulted VMA)| unfaulted | |-----------|.............|-----------| prev next target = prev, expand prev to cover. 4. prev unfaulted, next faulted: copied -----| v |-----------|.............|-----------| | unfaulted |(faulted VMA)| faulted | |-----------|.............|-----------| prev next target = prev, expand prev to cover. Essentially equivalent to 3, but with additional requirement that next's anon_vma is the same as the copied VMA's. Each of these are performed with MREMAP_DONTUNMAP set, which will cause a KASAN assert for UAF or an assert on zero refcount anon_vma if a bug exists with correctly propagating anon_vma state in each scenario. Link: https://lkml.kernel.org/r/f903af2930c7c2c6e0948c886b58d0f42d8e8ba3.1767638272.git.lorenzo.stoakes@oracle.com Fixes: 879bca0a2c4f ("mm/vma: fix incorrectly disallowed anonymous VMA merges") Signed-off-by: Lorenzo Stoakes Cc: David Hildenbrand (Red Hat) Cc: Jann Horn Cc: Jeongjun Park Cc: Liam Howlett Cc: Pedro Falcato Cc: Rik van Riel Cc: Vlastimil Babka Cc: Yeoreum Yun Cc: Harry Yoo Cc: Signed-off-by: Andrew Morton Signed-off-by: Greg Kroah-Hartman --- tools/testing/selftests/mm/merge.c | 232 +++++++++++++++++++++++++++++ 1 file changed, 232 insertions(+) diff --git a/tools/testing/selftests/mm/merge.c b/tools/testing/selftests/mm/merge.c index cc4253f47f10b4..63cb0e8b937aeb 100644 --- a/tools/testing/selftests/mm/merge.c +++ b/tools/testing/selftests/mm/merge.c @@ -1171,4 +1171,236 @@ TEST_F(merge, mremap_correct_placed_faulted) ASSERT_EQ(procmap->query.vma_end, (unsigned long)ptr + 15 * page_size); } +TEST_F(merge, mremap_faulted_to_unfaulted_prev) +{ + struct procmap_fd *procmap = &self->procmap; + unsigned int page_size = self->page_size; + char *ptr_a, *ptr_b; + + /* + * mremap() such that A and B merge: + * + * |------------| + * | \ | + * |-----------| | / |---------| + * | unfaulted | v \ | faulted | + * |-----------| / |---------| + * B \ A + */ + + /* Map VMA A into place. */ + ptr_a = mmap(&self->carveout[page_size + 3 * page_size], + 3 * page_size, + PROT_READ | PROT_WRITE, + MAP_PRIVATE | MAP_ANON | MAP_FIXED, -1, 0); + ASSERT_NE(ptr_a, MAP_FAILED); + /* Fault it in. */ + ptr_a[0] = 'x'; + + /* + * Now move it out of the way so we can place VMA B in position, + * unfaulted. + */ + ptr_a = mremap(ptr_a, 3 * page_size, 3 * page_size, + MREMAP_FIXED | MREMAP_MAYMOVE, &self->carveout[20 * page_size]); + ASSERT_NE(ptr_a, MAP_FAILED); + + /* Map VMA B into place. */ + ptr_b = mmap(&self->carveout[page_size], 3 * page_size, + PROT_READ | PROT_WRITE, + MAP_PRIVATE | MAP_ANON | MAP_FIXED, -1, 0); + ASSERT_NE(ptr_b, MAP_FAILED); + + /* + * Now move VMA A into position with MREMAP_DONTUNMAP to catch incorrect + * anon_vma propagation. + */ + ptr_a = mremap(ptr_a, 3 * page_size, 3 * page_size, + MREMAP_FIXED | MREMAP_MAYMOVE | MREMAP_DONTUNMAP, + &self->carveout[page_size + 3 * page_size]); + ASSERT_NE(ptr_a, MAP_FAILED); + + /* The VMAs should have merged. */ + ASSERT_TRUE(find_vma_procmap(procmap, ptr_b)); + ASSERT_EQ(procmap->query.vma_start, (unsigned long)ptr_b); + ASSERT_EQ(procmap->query.vma_end, (unsigned long)ptr_b + 6 * page_size); +} + +TEST_F(merge, mremap_faulted_to_unfaulted_next) +{ + struct procmap_fd *procmap = &self->procmap; + unsigned int page_size = self->page_size; + char *ptr_a, *ptr_b; + + /* + * mremap() such that A and B merge: + * + * |---------------------------| + * | \ | + * | |-----------| / |---------| + * v | unfaulted | \ | faulted | + * |-----------| / |---------| + * B \ A + * + * Then unmap VMA A to trigger the bug. + */ + + /* Map VMA A into place. */ + ptr_a = mmap(&self->carveout[page_size], 3 * page_size, + PROT_READ | PROT_WRITE, + MAP_PRIVATE | MAP_ANON | MAP_FIXED, -1, 0); + ASSERT_NE(ptr_a, MAP_FAILED); + /* Fault it in. */ + ptr_a[0] = 'x'; + + /* + * Now move it out of the way so we can place VMA B in position, + * unfaulted. + */ + ptr_a = mremap(ptr_a, 3 * page_size, 3 * page_size, + MREMAP_FIXED | MREMAP_MAYMOVE, &self->carveout[20 * page_size]); + ASSERT_NE(ptr_a, MAP_FAILED); + + /* Map VMA B into place. */ + ptr_b = mmap(&self->carveout[page_size + 3 * page_size], 3 * page_size, + PROT_READ | PROT_WRITE, + MAP_PRIVATE | MAP_ANON | MAP_FIXED, -1, 0); + ASSERT_NE(ptr_b, MAP_FAILED); + + /* + * Now move VMA A into position with MREMAP_DONTUNMAP to catch incorrect + * anon_vma propagation. + */ + ptr_a = mremap(ptr_a, 3 * page_size, 3 * page_size, + MREMAP_FIXED | MREMAP_MAYMOVE | MREMAP_DONTUNMAP, + &self->carveout[page_size]); + ASSERT_NE(ptr_a, MAP_FAILED); + + /* The VMAs should have merged. */ + ASSERT_TRUE(find_vma_procmap(procmap, ptr_a)); + ASSERT_EQ(procmap->query.vma_start, (unsigned long)ptr_a); + ASSERT_EQ(procmap->query.vma_end, (unsigned long)ptr_a + 6 * page_size); +} + +TEST_F(merge, mremap_faulted_to_unfaulted_prev_unfaulted_next) +{ + struct procmap_fd *procmap = &self->procmap; + unsigned int page_size = self->page_size; + char *ptr_a, *ptr_b, *ptr_c; + + /* + * mremap() with MREMAP_DONTUNMAP such that A, B and C merge: + * + * |---------------------------| + * | \ | + * |-----------| | |-----------| / |---------| + * | unfaulted | v | unfaulted | \ | faulted | + * |-----------| |-----------| / |---------| + * A C \ B + */ + + /* Map VMA B into place. */ + ptr_b = mmap(&self->carveout[page_size + 3 * page_size], 3 * page_size, + PROT_READ | PROT_WRITE, + MAP_PRIVATE | MAP_ANON | MAP_FIXED, -1, 0); + ASSERT_NE(ptr_b, MAP_FAILED); + /* Fault it in. */ + ptr_b[0] = 'x'; + + /* + * Now move it out of the way so we can place VMAs A, C in position, + * unfaulted. + */ + ptr_b = mremap(ptr_b, 3 * page_size, 3 * page_size, + MREMAP_FIXED | MREMAP_MAYMOVE, &self->carveout[20 * page_size]); + ASSERT_NE(ptr_b, MAP_FAILED); + + /* Map VMA A into place. */ + + ptr_a = mmap(&self->carveout[page_size], 3 * page_size, + PROT_READ | PROT_WRITE, + MAP_PRIVATE | MAP_ANON | MAP_FIXED, -1, 0); + ASSERT_NE(ptr_a, MAP_FAILED); + + /* Map VMA C into place. */ + ptr_c = mmap(&self->carveout[page_size + 3 * page_size + 3 * page_size], + 3 * page_size, PROT_READ | PROT_WRITE, + MAP_PRIVATE | MAP_ANON | MAP_FIXED, -1, 0); + ASSERT_NE(ptr_c, MAP_FAILED); + + /* + * Now move VMA B into position with MREMAP_DONTUNMAP to catch incorrect + * anon_vma propagation. + */ + ptr_b = mremap(ptr_b, 3 * page_size, 3 * page_size, + MREMAP_FIXED | MREMAP_MAYMOVE | MREMAP_DONTUNMAP, + &self->carveout[page_size + 3 * page_size]); + ASSERT_NE(ptr_b, MAP_FAILED); + + /* The VMAs should have merged. */ + ASSERT_TRUE(find_vma_procmap(procmap, ptr_a)); + ASSERT_EQ(procmap->query.vma_start, (unsigned long)ptr_a); + ASSERT_EQ(procmap->query.vma_end, (unsigned long)ptr_a + 9 * page_size); +} + +TEST_F(merge, mremap_faulted_to_unfaulted_prev_faulted_next) +{ + struct procmap_fd *procmap = &self->procmap; + unsigned int page_size = self->page_size; + char *ptr_a, *ptr_b, *ptr_bc; + + /* + * mremap() with MREMAP_DONTUNMAP such that A, B and C merge: + * + * |---------------------------| + * | \ | + * |-----------| | |-----------| / |---------| + * | unfaulted | v | faulted | \ | faulted | + * |-----------| |-----------| / |---------| + * A C \ B + */ + + /* + * Map VMA B and C into place. We have to map them together so their + * anon_vma is the same and the vma->vm_pgoff's are correctly aligned. + */ + ptr_bc = mmap(&self->carveout[page_size + 3 * page_size], + 3 * page_size + 3 * page_size, + PROT_READ | PROT_WRITE, + MAP_PRIVATE | MAP_ANON | MAP_FIXED, -1, 0); + ASSERT_NE(ptr_bc, MAP_FAILED); + + /* Fault it in. */ + ptr_bc[0] = 'x'; + + /* + * Now move VMA B out the way (splitting VMA BC) so we can place VMA A + * in position, unfaulted, and leave the remainder of the VMA we just + * moved in place, faulted, as VMA C. + */ + ptr_b = mremap(ptr_bc, 3 * page_size, 3 * page_size, + MREMAP_FIXED | MREMAP_MAYMOVE, &self->carveout[20 * page_size]); + ASSERT_NE(ptr_b, MAP_FAILED); + + /* Map VMA A into place. */ + ptr_a = mmap(&self->carveout[page_size], 3 * page_size, + PROT_READ | PROT_WRITE, + MAP_PRIVATE | MAP_ANON | MAP_FIXED, -1, 0); + ASSERT_NE(ptr_a, MAP_FAILED); + + /* + * Now move VMA B into position with MREMAP_DONTUNMAP to catch incorrect + * anon_vma propagation. + */ + ptr_b = mremap(ptr_b, 3 * page_size, 3 * page_size, + MREMAP_FIXED | MREMAP_MAYMOVE | MREMAP_DONTUNMAP, + &self->carveout[page_size + 3 * page_size]); + ASSERT_NE(ptr_b, MAP_FAILED); + + /* The VMAs should have merged. */ + ASSERT_TRUE(find_vma_procmap(procmap, ptr_a)); + ASSERT_EQ(procmap->query.vma_start, (unsigned long)ptr_a); + ASSERT_EQ(procmap->query.vma_end, (unsigned long)ptr_a + 9 * page_size); +} + TEST_HARNESS_MAIN From 3a1c3cd770f48cc437e7b42cb7ed3c02377a1d34 Mon Sep 17 00:00:00 2001 From: Lorenzo Stoakes Date: Mon, 5 Jan 2026 20:11:50 +0000 Subject: [PATCH 2506/3902] tools/testing/selftests: add forked (un)/faulted VMA merge tests commit fb39444732f02c32a8312c168d97e33d872c14d3 upstream. Now we correctly handle forked faulted/unfaulted merge on mremap(), exhaustively assert that we handle this correctly. Do this in the less duplicative way by adding a new merge_with_fork fixture and forked/unforked variants, and abstract the forking logic as necessary to avoid code duplication with this also. Link: https://lkml.kernel.org/r/1daf76d89fdb9d96f38a6a0152d8f3c2e9e30ac7.1767638272.git.lorenzo.stoakes@oracle.com Fixes: 879bca0a2c4f ("mm/vma: fix incorrectly disallowed anonymous VMA merges") Signed-off-by: Lorenzo Stoakes Cc: David Hildenbrand (Red Hat) Cc: Jann Horn Cc: Jeongjun Park Cc: Liam Howlett Cc: Pedro Falcato Cc: Rik van Riel Cc: Vlastimil Babka Cc: Yeoreum Yun Cc: Harry Yoo Cc: Signed-off-by: Andrew Morton Signed-off-by: Greg Kroah-Hartman --- tools/testing/selftests/mm/merge.c | 180 ++++++++++++++++++++++------- 1 file changed, 139 insertions(+), 41 deletions(-) diff --git a/tools/testing/selftests/mm/merge.c b/tools/testing/selftests/mm/merge.c index 63cb0e8b937aeb..cd0e6cc06a0251 100644 --- a/tools/testing/selftests/mm/merge.c +++ b/tools/testing/selftests/mm/merge.c @@ -22,12 +22,37 @@ FIXTURE(merge) struct procmap_fd procmap; }; +static char *map_carveout(unsigned int page_size) +{ + return mmap(NULL, 30 * page_size, PROT_NONE, + MAP_ANON | MAP_PRIVATE, -1, 0); +} + +static pid_t do_fork(struct procmap_fd *procmap) +{ + pid_t pid = fork(); + + if (pid == -1) + return -1; + if (pid != 0) { + wait(NULL); + return pid; + } + + /* Reopen for child. */ + if (close_procmap(procmap)) + return -1; + if (open_self_procmap(procmap)) + return -1; + + return 0; +} + FIXTURE_SETUP(merge) { self->page_size = psize(); /* Carve out PROT_NONE region to map over. */ - self->carveout = mmap(NULL, 30 * self->page_size, PROT_NONE, - MAP_ANON | MAP_PRIVATE, -1, 0); + self->carveout = map_carveout(self->page_size); ASSERT_NE(self->carveout, MAP_FAILED); /* Setup PROCMAP_QUERY interface. */ ASSERT_EQ(open_self_procmap(&self->procmap), 0); @@ -36,7 +61,8 @@ FIXTURE_SETUP(merge) FIXTURE_TEARDOWN(merge) { ASSERT_EQ(munmap(self->carveout, 30 * self->page_size), 0); - ASSERT_EQ(close_procmap(&self->procmap), 0); + /* May fail for parent of forked process. */ + close_procmap(&self->procmap); /* * Clear unconditionally, as some tests set this. It is no issue if this * fails (KSM may be disabled for instance). @@ -44,6 +70,44 @@ FIXTURE_TEARDOWN(merge) prctl(PR_SET_MEMORY_MERGE, 0, 0, 0, 0); } +FIXTURE(merge_with_fork) +{ + unsigned int page_size; + char *carveout; + struct procmap_fd procmap; +}; + +FIXTURE_VARIANT(merge_with_fork) +{ + bool forked; +}; + +FIXTURE_VARIANT_ADD(merge_with_fork, forked) +{ + .forked = true, +}; + +FIXTURE_VARIANT_ADD(merge_with_fork, unforked) +{ + .forked = false, +}; + +FIXTURE_SETUP(merge_with_fork) +{ + self->page_size = psize(); + self->carveout = map_carveout(self->page_size); + ASSERT_NE(self->carveout, MAP_FAILED); + ASSERT_EQ(open_self_procmap(&self->procmap), 0); +} + +FIXTURE_TEARDOWN(merge_with_fork) +{ + ASSERT_EQ(munmap(self->carveout, 30 * self->page_size), 0); + ASSERT_EQ(close_procmap(&self->procmap), 0); + /* See above. */ + prctl(PR_SET_MEMORY_MERGE, 0, 0, 0, 0); +} + TEST_F(merge, mprotect_unfaulted_left) { unsigned int page_size = self->page_size; @@ -322,8 +386,8 @@ TEST_F(merge, forked_target_vma) unsigned int page_size = self->page_size; char *carveout = self->carveout; struct procmap_fd *procmap = &self->procmap; - pid_t pid; char *ptr, *ptr2; + pid_t pid; int i; /* @@ -344,19 +408,10 @@ TEST_F(merge, forked_target_vma) */ ptr[0] = 'x'; - pid = fork(); + pid = do_fork(&self->procmap); ASSERT_NE(pid, -1); - - if (pid != 0) { - wait(NULL); + if (pid != 0) return; - } - - /* Child process below: */ - - /* Reopen for child. */ - ASSERT_EQ(close_procmap(&self->procmap), 0); - ASSERT_EQ(open_self_procmap(&self->procmap), 0); /* unCOWing everything does not cause the AVC to go away. */ for (i = 0; i < 5 * page_size; i += page_size) @@ -386,8 +441,8 @@ TEST_F(merge, forked_source_vma) unsigned int page_size = self->page_size; char *carveout = self->carveout; struct procmap_fd *procmap = &self->procmap; - pid_t pid; char *ptr, *ptr2; + pid_t pid; int i; /* @@ -408,19 +463,10 @@ TEST_F(merge, forked_source_vma) */ ptr[0] = 'x'; - pid = fork(); + pid = do_fork(&self->procmap); ASSERT_NE(pid, -1); - - if (pid != 0) { - wait(NULL); + if (pid != 0) return; - } - - /* Child process below: */ - - /* Reopen for child. */ - ASSERT_EQ(close_procmap(&self->procmap), 0); - ASSERT_EQ(open_self_procmap(&self->procmap), 0); /* unCOWing everything does not cause the AVC to go away. */ for (i = 0; i < 5 * page_size; i += page_size) @@ -1171,10 +1217,11 @@ TEST_F(merge, mremap_correct_placed_faulted) ASSERT_EQ(procmap->query.vma_end, (unsigned long)ptr + 15 * page_size); } -TEST_F(merge, mremap_faulted_to_unfaulted_prev) +TEST_F(merge_with_fork, mremap_faulted_to_unfaulted_prev) { struct procmap_fd *procmap = &self->procmap; unsigned int page_size = self->page_size; + unsigned long offset; char *ptr_a, *ptr_b; /* @@ -1197,6 +1244,14 @@ TEST_F(merge, mremap_faulted_to_unfaulted_prev) /* Fault it in. */ ptr_a[0] = 'x'; + if (variant->forked) { + pid_t pid = do_fork(&self->procmap); + + ASSERT_NE(pid, -1); + if (pid != 0) + return; + } + /* * Now move it out of the way so we can place VMA B in position, * unfaulted. @@ -1220,16 +1275,19 @@ TEST_F(merge, mremap_faulted_to_unfaulted_prev) &self->carveout[page_size + 3 * page_size]); ASSERT_NE(ptr_a, MAP_FAILED); - /* The VMAs should have merged. */ + /* The VMAs should have merged, if not forked. */ ASSERT_TRUE(find_vma_procmap(procmap, ptr_b)); ASSERT_EQ(procmap->query.vma_start, (unsigned long)ptr_b); - ASSERT_EQ(procmap->query.vma_end, (unsigned long)ptr_b + 6 * page_size); + + offset = variant->forked ? 3 * page_size : 6 * page_size; + ASSERT_EQ(procmap->query.vma_end, (unsigned long)ptr_b + offset); } -TEST_F(merge, mremap_faulted_to_unfaulted_next) +TEST_F(merge_with_fork, mremap_faulted_to_unfaulted_next) { struct procmap_fd *procmap = &self->procmap; unsigned int page_size = self->page_size; + unsigned long offset; char *ptr_a, *ptr_b; /* @@ -1253,6 +1311,14 @@ TEST_F(merge, mremap_faulted_to_unfaulted_next) /* Fault it in. */ ptr_a[0] = 'x'; + if (variant->forked) { + pid_t pid = do_fork(&self->procmap); + + ASSERT_NE(pid, -1); + if (pid != 0) + return; + } + /* * Now move it out of the way so we can place VMA B in position, * unfaulted. @@ -1276,16 +1342,18 @@ TEST_F(merge, mremap_faulted_to_unfaulted_next) &self->carveout[page_size]); ASSERT_NE(ptr_a, MAP_FAILED); - /* The VMAs should have merged. */ + /* The VMAs should have merged, if not forked. */ ASSERT_TRUE(find_vma_procmap(procmap, ptr_a)); ASSERT_EQ(procmap->query.vma_start, (unsigned long)ptr_a); - ASSERT_EQ(procmap->query.vma_end, (unsigned long)ptr_a + 6 * page_size); + offset = variant->forked ? 3 * page_size : 6 * page_size; + ASSERT_EQ(procmap->query.vma_end, (unsigned long)ptr_a + offset); } -TEST_F(merge, mremap_faulted_to_unfaulted_prev_unfaulted_next) +TEST_F(merge_with_fork, mremap_faulted_to_unfaulted_prev_unfaulted_next) { struct procmap_fd *procmap = &self->procmap; unsigned int page_size = self->page_size; + unsigned long offset; char *ptr_a, *ptr_b, *ptr_c; /* @@ -1307,6 +1375,14 @@ TEST_F(merge, mremap_faulted_to_unfaulted_prev_unfaulted_next) /* Fault it in. */ ptr_b[0] = 'x'; + if (variant->forked) { + pid_t pid = do_fork(&self->procmap); + + ASSERT_NE(pid, -1); + if (pid != 0) + return; + } + /* * Now move it out of the way so we can place VMAs A, C in position, * unfaulted. @@ -1337,13 +1413,21 @@ TEST_F(merge, mremap_faulted_to_unfaulted_prev_unfaulted_next) &self->carveout[page_size + 3 * page_size]); ASSERT_NE(ptr_b, MAP_FAILED); - /* The VMAs should have merged. */ + /* The VMAs should have merged, if not forked. */ ASSERT_TRUE(find_vma_procmap(procmap, ptr_a)); ASSERT_EQ(procmap->query.vma_start, (unsigned long)ptr_a); - ASSERT_EQ(procmap->query.vma_end, (unsigned long)ptr_a + 9 * page_size); + offset = variant->forked ? 3 * page_size : 9 * page_size; + ASSERT_EQ(procmap->query.vma_end, (unsigned long)ptr_a + offset); + + /* If forked, B and C should also not have merged. */ + if (variant->forked) { + ASSERT_TRUE(find_vma_procmap(procmap, ptr_b)); + ASSERT_EQ(procmap->query.vma_start, (unsigned long)ptr_b); + ASSERT_EQ(procmap->query.vma_end, (unsigned long)ptr_b + 3 * page_size); + } } -TEST_F(merge, mremap_faulted_to_unfaulted_prev_faulted_next) +TEST_F(merge_with_fork, mremap_faulted_to_unfaulted_prev_faulted_next) { struct procmap_fd *procmap = &self->procmap; unsigned int page_size = self->page_size; @@ -1373,6 +1457,14 @@ TEST_F(merge, mremap_faulted_to_unfaulted_prev_faulted_next) /* Fault it in. */ ptr_bc[0] = 'x'; + if (variant->forked) { + pid_t pid = do_fork(&self->procmap); + + ASSERT_NE(pid, -1); + if (pid != 0) + return; + } + /* * Now move VMA B out the way (splitting VMA BC) so we can place VMA A * in position, unfaulted, and leave the remainder of the VMA we just @@ -1397,10 +1489,16 @@ TEST_F(merge, mremap_faulted_to_unfaulted_prev_faulted_next) &self->carveout[page_size + 3 * page_size]); ASSERT_NE(ptr_b, MAP_FAILED); - /* The VMAs should have merged. */ - ASSERT_TRUE(find_vma_procmap(procmap, ptr_a)); - ASSERT_EQ(procmap->query.vma_start, (unsigned long)ptr_a); - ASSERT_EQ(procmap->query.vma_end, (unsigned long)ptr_a + 9 * page_size); + /* The VMAs should have merged. A,B,C if unforked, B, C if forked. */ + if (variant->forked) { + ASSERT_TRUE(find_vma_procmap(procmap, ptr_b)); + ASSERT_EQ(procmap->query.vma_start, (unsigned long)ptr_b); + ASSERT_EQ(procmap->query.vma_end, (unsigned long)ptr_b + 6 * page_size); + } else { + ASSERT_TRUE(find_vma_procmap(procmap, ptr_a)); + ASSERT_EQ(procmap->query.vma_start, (unsigned long)ptr_a); + ASSERT_EQ(procmap->query.vma_end, (unsigned long)ptr_a + 9 * page_size); + } } TEST_HARNESS_MAIN From ab09a7816c746376e8cf4cc37386c0f07962154f Mon Sep 17 00:00:00 2001 From: Lorenzo Stoakes Date: Tue, 6 Jan 2026 15:45:47 +0000 Subject: [PATCH 2507/3902] tools/testing/selftests: fix gup_longterm for unknown fs commit 21c68ad1d9771d331198cc73cbf6e908d7915f35 upstream. Commit 66bce7afbaca ("selftests/mm: fix test result reporting in gup_longterm") introduced a small bug causing unknown filesystems to always result in a test failure. This is because do_test() was updated to use a common reporting path, but this case appears to have been missed. This is problematic for e.g. virtme-ng which uses an overlayfs file system, causing gup_longterm to appear to fail each time due to a test count mismatch: # Planned tests != run tests (50 != 46) # Totals: pass:24 fail:0 xfail:0 xpass:0 skip:22 error:0 The fix is to simply change the return into a break. Link: https://lkml.kernel.org/r/20260106154547.214907-1-lorenzo.stoakes@oracle.com Fixes: 66bce7afbaca ("selftests/mm: fix test result reporting in gup_longterm") Signed-off-by: Lorenzo Stoakes Reviewed-by: David Hildenbrand (Red Hat) Cc: Jason Gunthorpe Cc: John Hubbard Cc: Liam Howlett Cc: "Liam R. Howlett" Cc: Mark Brown Cc: Michal Hocko Cc: Mike Rapoport Cc: Peter Xu Cc: Shuah Khan Cc: Suren Baghdasaryan Cc: Vlastimil Babka Cc: Signed-off-by: Andrew Morton Signed-off-by: Greg Kroah-Hartman --- tools/testing/selftests/mm/gup_longterm.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/testing/selftests/mm/gup_longterm.c b/tools/testing/selftests/mm/gup_longterm.c index 268dadb8ce4385..e15fdd5c62d682 100644 --- a/tools/testing/selftests/mm/gup_longterm.c +++ b/tools/testing/selftests/mm/gup_longterm.c @@ -179,7 +179,7 @@ static void do_test(int fd, size_t size, enum test_type type, bool shared) if (rw && shared && fs_is_unknown(fs_type)) { ksft_print_msg("Unknown filesystem\n"); result = KSFT_SKIP; - return; + break; } /* * R/O pinning or pinning in a private mapping is always From 9aef476717994e96dadfb359641c4b82b521aa36 Mon Sep 17 00:00:00 2001 From: Guenter Roeck Date: Tue, 13 Jan 2026 07:22:42 -0800 Subject: [PATCH 2508/3902] ftrace: Do not over-allocate ftrace memory commit be55257fab181b93af38f8c4b1b3cb453a78d742 upstream. The pg_remaining calculation in ftrace_process_locs() assumes that ENTRIES_PER_PAGE multiplied by 2^order equals the actual capacity of the allocated page group. However, ENTRIES_PER_PAGE is PAGE_SIZE / ENTRY_SIZE (integer division). When PAGE_SIZE is not a multiple of ENTRY_SIZE (e.g. 4096 / 24 = 170 with remainder 16), high-order allocations (like 256 pages) have significantly more capacity than 256 * 170. This leads to pg_remaining being underestimated, which in turn makes skip (derived from skipped - pg_remaining) larger than expected, causing the WARN(skip != remaining) to trigger. Extra allocated pages for ftrace: 2 with 654 skipped WARNING: CPU: 0 PID: 0 at kernel/trace/ftrace.c:7295 ftrace_process_locs+0x5bf/0x5e0 A similar problem in ftrace_allocate_records() can result in allocating too many pages. This can trigger the second warning in ftrace_process_locs(). Extra allocated pages for ftrace WARNING: CPU: 0 PID: 0 at kernel/trace/ftrace.c:7276 ftrace_process_locs+0x548/0x580 Use the actual capacity of a page group to determine the number of pages to allocate. Have ftrace_allocate_pages() return the number of allocated pages to avoid having to calculate it. Use the actual page group capacity when validating the number of unused pages due to skipped entries. Drop the definition of ENTRIES_PER_PAGE since it is no longer used. Cc: stable@vger.kernel.org Fixes: 4a3efc6baff93 ("ftrace: Update the mcount_loc check of skipped entries") Link: https://patch.msgid.link/20260113152243.3557219-1-linux@roeck-us.net Signed-off-by: Guenter Roeck Signed-off-by: Steven Rostedt (Google) Signed-off-by: Greg Kroah-Hartman --- kernel/trace/ftrace.c | 29 +++++++++++++++-------------- 1 file changed, 15 insertions(+), 14 deletions(-) diff --git a/kernel/trace/ftrace.c b/kernel/trace/ftrace.c index 59cfacb8a5bbdc..e95408a47c1d06 100644 --- a/kernel/trace/ftrace.c +++ b/kernel/trace/ftrace.c @@ -1122,7 +1122,6 @@ struct ftrace_page { }; #define ENTRY_SIZE sizeof(struct dyn_ftrace) -#define ENTRIES_PER_PAGE (PAGE_SIZE / ENTRY_SIZE) static struct ftrace_page *ftrace_pages_start; static struct ftrace_page *ftrace_pages; @@ -3808,7 +3807,8 @@ static int ftrace_update_code(struct module *mod, struct ftrace_page *new_pgs) return 0; } -static int ftrace_allocate_records(struct ftrace_page *pg, int count) +static int ftrace_allocate_records(struct ftrace_page *pg, int count, + unsigned long *num_pages) { int order; int pages; @@ -3818,7 +3818,7 @@ static int ftrace_allocate_records(struct ftrace_page *pg, int count) return -EINVAL; /* We want to fill as much as possible, with no empty pages */ - pages = DIV_ROUND_UP(count, ENTRIES_PER_PAGE); + pages = DIV_ROUND_UP(count * ENTRY_SIZE, PAGE_SIZE); order = fls(pages) - 1; again: @@ -3833,6 +3833,7 @@ static int ftrace_allocate_records(struct ftrace_page *pg, int count) } ftrace_number_of_pages += 1 << order; + *num_pages += 1 << order; ftrace_number_of_groups++; cnt = (PAGE_SIZE << order) / ENTRY_SIZE; @@ -3861,12 +3862,14 @@ static void ftrace_free_pages(struct ftrace_page *pages) } static struct ftrace_page * -ftrace_allocate_pages(unsigned long num_to_init) +ftrace_allocate_pages(unsigned long num_to_init, unsigned long *num_pages) { struct ftrace_page *start_pg; struct ftrace_page *pg; int cnt; + *num_pages = 0; + if (!num_to_init) return NULL; @@ -3880,7 +3883,7 @@ ftrace_allocate_pages(unsigned long num_to_init) * waste as little space as possible. */ for (;;) { - cnt = ftrace_allocate_records(pg, num_to_init); + cnt = ftrace_allocate_records(pg, num_to_init, num_pages); if (cnt < 0) goto free_pages; @@ -7148,8 +7151,6 @@ static int ftrace_process_locs(struct module *mod, if (!count) return 0; - pages = DIV_ROUND_UP(count, ENTRIES_PER_PAGE); - /* * Sorting mcount in vmlinux at build time depend on * CONFIG_BUILDTIME_MCOUNT_SORT, while mcount loc in @@ -7162,7 +7163,7 @@ static int ftrace_process_locs(struct module *mod, test_is_sorted(start, count); } - start_pg = ftrace_allocate_pages(count); + start_pg = ftrace_allocate_pages(count, &pages); if (!start_pg) return -ENOMEM; @@ -7261,27 +7262,27 @@ static int ftrace_process_locs(struct module *mod, /* We should have used all pages unless we skipped some */ if (pg_unuse) { unsigned long pg_remaining, remaining = 0; - unsigned long skip; + long skip; /* Count the number of entries unused and compare it to skipped. */ - pg_remaining = (ENTRIES_PER_PAGE << pg->order) - pg->index; + pg_remaining = (PAGE_SIZE << pg->order) / ENTRY_SIZE - pg->index; if (!WARN(skipped < pg_remaining, "Extra allocated pages for ftrace")) { skip = skipped - pg_remaining; - for (pg = pg_unuse; pg; pg = pg->next) + for (pg = pg_unuse; pg && skip > 0; pg = pg->next) { remaining += 1 << pg->order; + skip -= (PAGE_SIZE << pg->order) / ENTRY_SIZE; + } pages -= remaining; - skip = DIV_ROUND_UP(skip, ENTRIES_PER_PAGE); - /* * Check to see if the number of pages remaining would * just fit the number of entries skipped. */ - WARN(skip != remaining, "Extra allocated pages for ftrace: %lu with %lu skipped", + WARN(pg || skip > 0, "Extra allocated pages for ftrace: %lu with %lu skipped", remaining, skipped); } /* Need to synchronize with ftrace_location_range() */ From bca07e57e47291f8738ea757084b88bb6cc756fb Mon Sep 17 00:00:00 2001 From: Brian Foster Date: Fri, 9 Jan 2026 12:49:05 -0500 Subject: [PATCH 2509/3902] xfs: set max_agbno to allow sparse alloc of last full inode chunk commit c360004c0160dbe345870f59f24595519008926f upstream. Sparse inode cluster allocation sets min/max agbno values to avoid allocating an inode cluster that might map to an invalid inode chunk. For example, we can't have an inode record mapped to agbno 0 or that extends past the end of a runt AG of misaligned size. The initial calculation of max_agbno is unnecessarily conservative, however. This has triggered a corner case allocation failure where a small runt AG (i.e. 2063 blocks) is mostly full save for an extent to the EOFS boundary: [2050,13]. max_agbno is set to 2048 in this case, which happens to be the offset of the last possible valid inode chunk in the AG. In practice, we should be able to allocate the 4-block cluster at agbno 2052 to map to the parent inode record at agbno 2048, but the max_agbno value precludes it. Note that this can result in filesystem shutdown via dirty trans cancel on stable kernels prior to commit 9eb775968b68 ("xfs: walk all AGs if TRYLOCK passed to xfs_alloc_vextent_iterate_ags") because the tail AG selection by the allocator sets t_highest_agno on the transaction. If the inode allocator spins around and finds an inode chunk with free inodes in an earlier AG, the subsequent dir name creation path may still fail to allocate due to the AG restriction and cancel. To avoid this problem, update the max_agbno calculation to the agbno prior to the last chunk aligned agbno in the AG. This is not necessarily the last valid allocation target for a sparse chunk, but since inode chunks (i.e. records) are chunk aligned and sparse allocs are cluster sized/aligned, this allows the sb_spino_align alignment restriction to take over and round down the max effective agbno to within the last valid inode chunk in the AG. Note that even though the allocator improvements in the aforementioned commit seem to avoid this particular dirty trans cancel situation, the max_agbno logic improvement still applies as we should be able to allocate from an AG that has been appropriately selected. The more important target for this patch however are older/stable kernels prior to this allocator rework/improvement. Cc: stable@vger.kernel.org # v4.2 Fixes: 56d1115c9bc7 ("xfs: allocate sparse inode chunks on full chunk allocation failure") Signed-off-by: Brian Foster Reviewed-by: Darrick J. Wong Signed-off-by: Carlos Maiolino Signed-off-by: Greg Kroah-Hartman --- fs/xfs/libxfs/xfs_ialloc.c | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/fs/xfs/libxfs/xfs_ialloc.c b/fs/xfs/libxfs/xfs_ialloc.c index d97295eaebe631..c19d6d713780ca 100644 --- a/fs/xfs/libxfs/xfs_ialloc.c +++ b/fs/xfs/libxfs/xfs_ialloc.c @@ -848,15 +848,16 @@ xfs_ialloc_ag_alloc( * invalid inode records, such as records that start at agbno 0 * or extend beyond the AG. * - * Set min agbno to the first aligned, non-zero agbno and max to - * the last aligned agbno that is at least one full chunk from - * the end of the AG. + * Set min agbno to the first chunk aligned, non-zero agbno and + * max to one less than the last chunk aligned agbno from the + * end of the AG. We subtract 1 from max so that the cluster + * allocation alignment takes over and allows allocation within + * the last full inode chunk in the AG. */ args.min_agbno = args.mp->m_sb.sb_inoalignmt; args.max_agbno = round_down(xfs_ag_block_count(args.mp, pag_agno(pag)), - args.mp->m_sb.sb_inoalignmt) - - igeo->ialloc_blks; + args.mp->m_sb.sb_inoalignmt) - 1; error = xfs_alloc_vextent_near_bno(&args, xfs_agbno_to_fsb(pag, From ccd18d250ad8f1b0bb4462bde905d90574c3593b Mon Sep 17 00:00:00 2001 From: "Nirjhar Roy (IBM)" Date: Mon, 12 Jan 2026 15:35:23 +0530 Subject: [PATCH 2510/3902] xfs: Fix the return value of xfs_rtcopy_summary() commit 6b2d155366581705a848833a9b626bfea41d5a8d upstream. xfs_rtcopy_summary() should return the appropriate error code instead of always returning 0. The caller of this function which is xfs_growfs_rt_bmblock() is already handling the error. Fixes: e94b53ff699c ("xfs: cache last bitmap block in realtime allocator") Signed-off-by: Nirjhar Roy (IBM) Reviewed-by: Darrick J. Wong Reviewed-by: Christoph Hellwig Cc: stable@vger.kernel.org # v6.7 Signed-off-by: Carlos Maiolino Signed-off-by: Greg Kroah-Hartman --- fs/xfs/xfs_rtalloc.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fs/xfs/xfs_rtalloc.c b/fs/xfs/xfs_rtalloc.c index e063f4f2f2e617..59fad43063a781 100644 --- a/fs/xfs/xfs_rtalloc.c +++ b/fs/xfs/xfs_rtalloc.c @@ -126,7 +126,7 @@ xfs_rtcopy_summary( error = 0; out: xfs_rtbuf_cache_relse(oargs); - return 0; + return error; } /* * Mark an extent specified by start and len allocated. From ccc578d2e1540b97e0687b25f554ad5d2bee520e Mon Sep 17 00:00:00 2001 From: Bui Quang Minh Date: Tue, 6 Jan 2026 22:04:36 +0700 Subject: [PATCH 2511/3902] virtio-net: don't schedule delayed refill worker commit fcdef3bcbb2c04e06ae89f8faff2cd6416b3a467 upstream. When we fail to refill the receive buffers, we schedule a delayed worker to retry later. However, this worker creates some concurrency issues. For example, when the worker runs concurrently with virtnet_xdp_set, both need to temporarily disable queue's NAPI before enabling again. Without proper synchronization, a deadlock can happen when napi_disable() is called on an already disabled NAPI. That napi_disable() call will be stuck and so will the subsequent napi_enable() call. To simplify the logic and avoid further problems, we will instead retry refilling in the next NAPI poll. Fixes: 4bc12818b363 ("virtio-net: disable delayed refill when pausing rx") Reported-by: Paolo Abeni Closes: https://lore.kernel.org/526b5396-459d-4d02-8635-a222d07b46d7@redhat.com Cc: stable@vger.kernel.org Suggested-by: Xuan Zhuo Signed-off-by: Bui Quang Minh Acked-by: Michael S. Tsirkin Link: https://patch.msgid.link/20260106150438.7425-2-minhquangbui99@gmail.com Signed-off-by: Jakub Kicinski Signed-off-by: Greg Kroah-Hartman --- drivers/net/virtio_net.c | 47 ++++++++++++++++++++-------------------- 1 file changed, 24 insertions(+), 23 deletions(-) diff --git a/drivers/net/virtio_net.c b/drivers/net/virtio_net.c index 6b3115cefc2481..b67dbe346c8070 100644 --- a/drivers/net/virtio_net.c +++ b/drivers/net/virtio_net.c @@ -3037,16 +3037,16 @@ static int virtnet_receive(struct receive_queue *rq, int budget, else packets = virtnet_receive_packets(vi, rq, budget, xdp_xmit, &stats); + u64_stats_set(&stats.packets, packets); if (rq->vq->num_free > min((unsigned int)budget, virtqueue_get_vring_size(rq->vq)) / 2) { - if (!try_fill_recv(vi, rq, GFP_ATOMIC)) { - spin_lock(&vi->refill_lock); - if (vi->refill_enabled) - schedule_delayed_work(&vi->refill, 0); - spin_unlock(&vi->refill_lock); - } + if (!try_fill_recv(vi, rq, GFP_ATOMIC)) + /* We need to retry refilling in the next NAPI poll so + * we must return budget to make sure the NAPI is + * repolled. + */ + packets = budget; } - u64_stats_set(&stats.packets, packets); u64_stats_update_begin(&rq->stats.syncp); for (i = 0; i < ARRAY_SIZE(virtnet_rq_stats_desc); i++) { size_t offset = virtnet_rq_stats_desc[i].offset; @@ -3226,9 +3226,10 @@ static int virtnet_open(struct net_device *dev) for (i = 0; i < vi->max_queue_pairs; i++) { if (i < vi->curr_queue_pairs) - /* Make sure we have some buffers: if oom use wq. */ - if (!try_fill_recv(vi, &vi->rq[i], GFP_KERNEL)) - schedule_delayed_work(&vi->refill, 0); + /* Pre-fill rq agressively, to make sure we are ready to + * get packets immediately. + */ + try_fill_recv(vi, &vi->rq[i], GFP_KERNEL); err = virtnet_enable_queue_pair(vi, i); if (err < 0) @@ -3473,16 +3474,15 @@ static void __virtnet_rx_resume(struct virtnet_info *vi, struct receive_queue *rq, bool refill) { - bool running = netif_running(vi->dev); - bool schedule_refill = false; + if (netif_running(vi->dev)) { + /* Pre-fill rq agressively, to make sure we are ready to get + * packets immediately. + */ + if (refill) + try_fill_recv(vi, rq, GFP_KERNEL); - if (refill && !try_fill_recv(vi, rq, GFP_KERNEL)) - schedule_refill = true; - if (running) virtnet_napi_enable(rq); - - if (schedule_refill) - schedule_delayed_work(&vi->refill, 0); + } } static void virtnet_rx_resume_all(struct virtnet_info *vi) @@ -3827,11 +3827,12 @@ static int virtnet_set_queues(struct virtnet_info *vi, u16 queue_pairs) } succ: vi->curr_queue_pairs = queue_pairs; - /* virtnet_open() will refill when device is going to up. */ - spin_lock_bh(&vi->refill_lock); - if (dev->flags & IFF_UP && vi->refill_enabled) - schedule_delayed_work(&vi->refill, 0); - spin_unlock_bh(&vi->refill_lock); + if (dev->flags & IFF_UP) { + local_bh_disable(); + for (int i = 0; i < vi->curr_queue_pairs; ++i) + virtqueue_napi_schedule(&vi->rq[i].napi, vi->rq[i].vq); + local_bh_enable(); + } return 0; } From 568aeb3476c770a3863c755dd2a199c212434286 Mon Sep 17 00:00:00 2001 From: Shakeel Butt Date: Mon, 22 Dec 2025 12:58:59 -0800 Subject: [PATCH 2512/3902] lib/buildid: use __kernel_read() for sleepable context commit 777a8560fd29738350c5094d4166fe5499452409 upstream. Prevent a "BUG: unable to handle kernel NULL pointer dereference in filemap_read_folio". For the sleepable context, convert freader to use __kernel_read() instead of direct page cache access via read_cache_folio(). This simplifies the faultable code path by using the standard kernel file reading interface which handles all the complexity of reading file data. At the moment we are not changing the code for non-sleepable context which uses filemap_get_folio() and only succeeds if the target folios are already in memory and up-to-date. The reason is to keep the patch simple and easier to backport to stable kernels. Syzbot repro does not crash the kernel anymore and the selftests run successfully. In the follow up we will make __kernel_read() with IOCB_NOWAIT work for non-sleepable contexts. In addition, I would like to replace the secretmem check with a more generic approach and will add fstest for the buildid code. Link: https://lkml.kernel.org/r/20251222205859.3968077-1-shakeel.butt@linux.dev Fixes: ad41251c290d ("lib/buildid: implement sleepable build_id_parse() API") Reported-by: syzbot+09b7d050e4806540153d@syzkaller.appspotmail.com Closes: https://syzkaller.appspot.com/bug?extid=09b7d050e4806540153d Signed-off-by: Shakeel Butt Reviewed-by: Christoph Hellwig Tested-by: Jinchao Wang Link: https://lkml.kernel.org/r/aUteBPWPYzVWIZFH@ndev Reviewed-by: Christian Brauner Cc: Alexei Starovoitov Cc: Andrii Nakryiko Cc: Daniel Borkman Cc: "Darrick J. Wong" Cc: Matthew Wilcox (Oracle) Cc: Signed-off-by: Andrew Morton Signed-off-by: Greg Kroah-Hartman --- lib/buildid.c | 32 ++++++++++++++++++++------------ 1 file changed, 20 insertions(+), 12 deletions(-) diff --git a/lib/buildid.c b/lib/buildid.c index c4b0f376fb3410..a80592ddafd18a 100644 --- a/lib/buildid.c +++ b/lib/buildid.c @@ -5,6 +5,7 @@ #include #include #include +#include #include #define BUILD_ID 3 @@ -65,20 +66,9 @@ static int freader_get_folio(struct freader *r, loff_t file_off) freader_put_folio(r); - /* reject secretmem folios created with memfd_secret() */ - if (secretmem_mapping(r->file->f_mapping)) - return -EFAULT; - + /* only use page cache lookup - fail if not already cached */ r->folio = filemap_get_folio(r->file->f_mapping, file_off >> PAGE_SHIFT); - /* if sleeping is allowed, wait for the page, if necessary */ - if (r->may_fault && (IS_ERR(r->folio) || !folio_test_uptodate(r->folio))) { - filemap_invalidate_lock_shared(r->file->f_mapping); - r->folio = read_cache_folio(r->file->f_mapping, file_off >> PAGE_SHIFT, - NULL, r->file); - filemap_invalidate_unlock_shared(r->file->f_mapping); - } - if (IS_ERR(r->folio) || !folio_test_uptodate(r->folio)) { if (!IS_ERR(r->folio)) folio_put(r->folio); @@ -116,6 +106,24 @@ static const void *freader_fetch(struct freader *r, loff_t file_off, size_t sz) return r->data + file_off; } + /* reject secretmem folios created with memfd_secret() */ + if (secretmem_mapping(r->file->f_mapping)) { + r->err = -EFAULT; + return NULL; + } + + /* use __kernel_read() for sleepable context */ + if (r->may_fault) { + ssize_t ret; + + ret = __kernel_read(r->file, r->buf, sz, &file_off); + if (ret != sz) { + r->err = (ret < 0) ? ret : -EIO; + return NULL; + } + return r->buf; + } + /* fetch or reuse folio for given file offset */ r->err = freader_get_folio(r, file_off); if (r->err) From 5a08dc1d8de3fc48bd355885e943c78f931d9eba Mon Sep 17 00:00:00 2001 From: Dan Williams Date: Thu, 6 Nov 2025 15:13:50 -0800 Subject: [PATCH 2513/3902] x86/kaslr: Recognize all ZONE_DEVICE users as physaddr consumers commit 269031b15c1433ff39e30fa7ea3ab8f0be9d6ae2 upstream. Commit 7ffb791423c7 ("x86/kaslr: Reduce KASLR entropy on most x86 systems") is too narrow. The effect being mitigated in that commit is caused by ZONE_DEVICE which PCI_P2PDMA has a dependency. ZONE_DEVICE, in general, lets any physical address be added to the direct-map. I.e. not only ACPI hotplug ranges, CXL Memory Windows, or EFI Specific Purpose Memory, but also any PCI MMIO range for the DEVICE_PRIVATE and PCI_P2PDMA cases. Update the mitigation, limit KASLR entropy, to apply in all ZONE_DEVICE=y cases. Distro kernels typically have PCI_P2PDMA=y, so the practical exposure of this problem is limited to the PCI_P2PDMA=n case. A potential path to recover entropy would be to walk ACPI and determine the limits for hotplug and PCI MMIO before kernel_randomize_memory(). On smaller systems that could yield some KASLR address bits. This needs additional investigation to determine if some limited ACPI table scanning can happen this early without an open coded solution like arch/x86/boot/compressed/acpi.c needs to deploy. Cc: Ingo Molnar Cc: Kees Cook Cc: Bjorn Helgaas Cc: Peter Zijlstra Cc: Andy Lutomirski Cc: Logan Gunthorpe Cc: Andrew Morton Cc: David Hildenbrand Cc: Lorenzo Stoakes Cc: "Liam R. Howlett" Cc: Vlastimil Babka Cc: Mike Rapoport Cc: Suren Baghdasaryan Cc: Michal Hocko Fixes: 7ffb791423c7 ("x86/kaslr: Reduce KASLR entropy on most x86 systems") Cc: Signed-off-by: Dan Williams Reviewed-by: Balbir Singh Tested-by: Yasunori Goto Acked-by: Dave Hansen Link: http://patch.msgid.link/692e08b2516d4_261c1100a3@dwillia2-mobl4.notmuch Signed-off-by: Dave Jiang Signed-off-by: Greg Kroah-Hartman --- arch/x86/mm/kaslr.c | 10 +++++----- drivers/pci/Kconfig | 6 ------ mm/Kconfig | 12 ++++++++---- 3 files changed, 13 insertions(+), 15 deletions(-) diff --git a/arch/x86/mm/kaslr.c b/arch/x86/mm/kaslr.c index 3c306de52fd4da..834641c6049a53 100644 --- a/arch/x86/mm/kaslr.c +++ b/arch/x86/mm/kaslr.c @@ -115,12 +115,12 @@ void __init kernel_randomize_memory(void) /* * Adapt physical memory region size based on available memory, - * except when CONFIG_PCI_P2PDMA is enabled. P2PDMA exposes the - * device BAR space assuming the direct map space is large enough - * for creating a ZONE_DEVICE mapping in the direct map corresponding - * to the physical BAR address. + * except when CONFIG_ZONE_DEVICE is enabled. ZONE_DEVICE wants to map + * any physical address into the direct-map. KASLR wants to reliably + * steal some physical address bits. Those design choices are in direct + * conflict. */ - if (!IS_ENABLED(CONFIG_PCI_P2PDMA) && (memory_tb < kaslr_regions[0].size_tb)) + if (!IS_ENABLED(CONFIG_ZONE_DEVICE) && (memory_tb < kaslr_regions[0].size_tb)) kaslr_regions[0].size_tb = memory_tb; /* diff --git a/drivers/pci/Kconfig b/drivers/pci/Kconfig index f94f5d384362e5..47e466946bedb1 100644 --- a/drivers/pci/Kconfig +++ b/drivers/pci/Kconfig @@ -207,12 +207,6 @@ config PCI_P2PDMA P2P DMA transactions must be between devices behind the same root port. - Enabling this option will reduce the entropy of x86 KASLR memory - regions. For example - on a 46 bit system, the entropy goes down - from 16 bits to 15 bits. The actual reduction in entropy depends - on the physical address bits, on processor features, kernel config - (5 level page table) and physical memory present on the system. - If unsure, say N. config PCI_LABEL diff --git a/mm/Kconfig b/mm/Kconfig index ca3f146bc7053a..5033e2aa328e47 100644 --- a/mm/Kconfig +++ b/mm/Kconfig @@ -1135,10 +1135,14 @@ config ZONE_DEVICE Device memory hotplug support allows for establishing pmem, or other device driver discovered memory regions, in the memmap. This allows pfn_to_page() lookups of otherwise - "device-physical" addresses which is needed for using a DAX - mapping in an O_DIRECT operation, among other things. - - If FS_DAX is enabled, then say Y. + "device-physical" addresses which is needed for DAX, PCI_P2PDMA, and + DEVICE_PRIVATE features among others. + + Enabling this option will reduce the entropy of x86 KASLR memory + regions. For example - on a 46 bit system, the entropy goes down + from 16 bits to 15 bits. The actual reduction in entropy depends + on the physical address bits, on processor features, kernel config + (5 level page table) and physical memory present on the system. # # Helpers to mirror range of the CPU page tables of a process into device page From d51f1db5a5f8cb93cd4298f33d6e2e19708cb854 Mon Sep 17 00:00:00 2001 From: Luca Ceresoli Date: Thu, 27 Nov 2025 11:26:17 +0100 Subject: [PATCH 2514/3902] phy: rockchip: inno-usb2: fix communication disruption in gadget mode MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit commit 7d8f725b79e35fa47e42c88716aad8711e1168d8 upstream. When the OTG USB port is used to power to SoC, configured as peripheral and used in gadget mode, communication stops without notice about 6 seconds after the gadget is configured and enumerated. The problem was observed on a Radxa Rock Pi S board, which can only be powered by the only USB-C connector. That connector is the only one usable in gadget mode. This implies the USB cable is connected from before boot and never disconnects while the kernel runs. The related code flow in the PHY driver code can be summarized as: * the first time chg_detect_work starts (6 seconds after gadget is configured and enumerated) -> rockchip_chg_detect_work(): if chg_state is UNDEFINED: property_enable(base, &rphy->phy_cfg->chg_det.opmode, false); [Y] * rockchip_chg_detect_work() changes state and re-triggers itself a few times until it reaches the DETECTED state: -> rockchip_chg_detect_work(): if chg_state is DETECTED: property_enable(base, &rphy->phy_cfg->chg_det.opmode, true); [Z] At [Y] all existing communications stop. E.g. using a CDC serial gadget, the /dev/tty* devices are still present on both host and device, but no data is transferred anymore. The later call with a 'true' argument at [Z] does not restore it. Due to the lack of documentation, what chg_det.opmode does exactly is not clear, however by code inspection it seems reasonable that is disables something needed to keep the communication working, and testing proves that disabling these lines lets gadget mode keep working. So prevent changes to chg_det.opmode when there is a cable connected (VBUS present). Fixes: 98898f3bc83c ("phy: rockchip-inno-usb2: support otg-port for rk3399") Cc: stable@vger.kernel.org Closes: https://lore.kernel.org/lkml/20250414185458.7767aabc@booty/ Signed-off-by: Luca Ceresoli Reviewed-by: Théo Lebrun Link: https://patch.msgid.link/20251127-rk3308-fix-usb-gadget-phy-disconnect-v2-2-dac8a02cd2ca@bootlin.com Signed-off-by: Vinod Koul Signed-off-by: Greg Kroah-Hartman --- drivers/phy/rockchip/phy-rockchip-inno-usb2.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/drivers/phy/rockchip/phy-rockchip-inno-usb2.c b/drivers/phy/rockchip/phy-rockchip-inno-usb2.c index b0f23690ec3002..0b5d49b6f7aab7 100644 --- a/drivers/phy/rockchip/phy-rockchip-inno-usb2.c +++ b/drivers/phy/rockchip/phy-rockchip-inno-usb2.c @@ -831,7 +831,8 @@ static void rockchip_chg_detect_work(struct work_struct *work) if (!rport->suspended) rockchip_usb2phy_power_off(rport->phy); /* put the controller in non-driving mode */ - property_enable(base, &rphy->phy_cfg->chg_det.opmode, false); + if (!vbus_attach) + property_enable(base, &rphy->phy_cfg->chg_det.opmode, false); /* Start DCD processing stage 1 */ rockchip_chg_enable_dcd(rphy, true); rphy->chg_state = USB_CHG_STATE_WAIT_FOR_DCD; @@ -894,7 +895,8 @@ static void rockchip_chg_detect_work(struct work_struct *work) fallthrough; case USB_CHG_STATE_DETECTED: /* put the controller in normal mode */ - property_enable(base, &rphy->phy_cfg->chg_det.opmode, true); + if (!vbus_attach) + property_enable(base, &rphy->phy_cfg->chg_det.opmode, true); rockchip_usb2phy_otg_sm_work(&rport->otg_sm_work.work); dev_dbg(&rport->phy->dev, "charger = %s\n", chg_to_string(rphy->chg_type)); From 8b125923f4ea51be02b0aa25ccfea1344e5d1406 Mon Sep 17 00:00:00 2001 From: Johan Hovold Date: Thu, 27 Nov 2025 14:48:34 +0100 Subject: [PATCH 2515/3902] phy: ti: gmii-sel: fix regmap leak on probe failure commit 4914d67da947031d6f645c81c74f7879e0844d5d upstream. The mmio regmap that may be allocated during probe is never freed. Switch to using the device managed allocator so that the regmap is released on probe failures (e.g. probe deferral) and on driver unbind. Fixes: 5ab90f40121a ("phy: ti: gmii-sel: Do not use syscon helper to build regmap") Cc: stable@vger.kernel.org # 6.14 Cc: Andrew Davis Signed-off-by: Johan Hovold Acked-by: Andrew Davis Link: https://patch.msgid.link/20251127134834.2030-1-johan@kernel.org Signed-off-by: Vinod Koul Signed-off-by: Greg Kroah-Hartman --- drivers/phy/ti/phy-gmii-sel.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/phy/ti/phy-gmii-sel.c b/drivers/phy/ti/phy-gmii-sel.c index 50adabb867cb12..26209a89703a8a 100644 --- a/drivers/phy/ti/phy-gmii-sel.c +++ b/drivers/phy/ti/phy-gmii-sel.c @@ -512,7 +512,7 @@ static int phy_gmii_sel_probe(struct platform_device *pdev) return dev_err_probe(dev, PTR_ERR(base), "failed to get base memory resource\n"); - priv->regmap = regmap_init_mmio(dev, base, &phy_gmii_sel_regmap_cfg); + priv->regmap = devm_regmap_init_mmio(dev, base, &phy_gmii_sel_regmap_cfg); if (IS_ERR(priv->regmap)) return dev_err_probe(dev, PTR_ERR(priv->regmap), "Failed to get syscon\n"); From efe92ee7a111fe0f4d75f3ed6b7e3f86322279d5 Mon Sep 17 00:00:00 2001 From: Wentao Liang Date: Fri, 9 Jan 2026 15:46:26 +0000 Subject: [PATCH 2516/3902] phy: rockchip: inno-usb2: Fix a double free bug in rockchip_usb2phy_probe() commit e07dea3de508cd6950c937cec42de7603190e1ca upstream. The for_each_available_child_of_node() calls of_node_put() to release child_np in each success loop. After breaking from the loop with the child_np has been released, the code will jump to the put_child label and will call the of_node_put() again if the devm_request_threaded_irq() fails. These cause a double free bug. Fix by returning directly to avoid the duplicate of_node_put(). Fixes: ed2b5a8e6b98 ("phy: phy-rockchip-inno-usb2: support muxed interrupts") Cc: stable@vger.kernel.org Signed-off-by: Wentao Liang Reviewed-by: Neil Armstrong Link: https://patch.msgid.link/20260109154626.2452034-1-vulab@iscas.ac.cn Signed-off-by: Vinod Koul Signed-off-by: Greg Kroah-Hartman --- drivers/phy/rockchip/phy-rockchip-inno-usb2.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/phy/rockchip/phy-rockchip-inno-usb2.c b/drivers/phy/rockchip/phy-rockchip-inno-usb2.c index 0b5d49b6f7aab7..a53cc9c86b7c92 100644 --- a/drivers/phy/rockchip/phy-rockchip-inno-usb2.c +++ b/drivers/phy/rockchip/phy-rockchip-inno-usb2.c @@ -1493,7 +1493,7 @@ static int rockchip_usb2phy_probe(struct platform_device *pdev) rphy); if (ret) { dev_err_probe(rphy->dev, ret, "failed to request usb2phy irq handle\n"); - goto put_child; + return ret; } } From 33194f81b68677e89c9ac302a49c94193b3b8918 Mon Sep 17 00:00:00 2001 From: Rafael Beims Date: Tue, 23 Dec 2025 12:02:54 -0300 Subject: [PATCH 2517/3902] phy: freescale: imx8m-pcie: assert phy reset during power on commit f2ec4723defbc66a50e0abafa830ae9f8bceb0d7 upstream. After U-Boot initializes PCIe with "pcie enum", Linux fails to detect an NVMe disk on some boot cycles with: phy phy-32f00000.pcie-phy.0: phy poweron failed --> -110 Discussion with NXP identified that the iMX8MP PCIe PHY PLL may fail to lock when re-initialized without a reset cycle [1]. The issue reproduces on 7% of tested hardware platforms, with a 30-40% failure rate per affected device across boot cycles. Insert a reset cycle in the power-on routine to ensure the PHY is initialized from a known state. [1] https://community.nxp.com/t5/i-MX-Processors/iMX8MP-PCIe-initialization-in-U-Boot/m-p/2248437#M242401 Signed-off-by: Rafael Beims Cc: stable@vger.kernel.org Link: https://patch.msgid.link/20251223150254.1075221-1-rafael@beims.me Signed-off-by: Vinod Koul Signed-off-by: Greg Kroah-Hartman --- drivers/phy/freescale/phy-fsl-imx8m-pcie.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/drivers/phy/freescale/phy-fsl-imx8m-pcie.c b/drivers/phy/freescale/phy-fsl-imx8m-pcie.c index 68fcc8114d750a..7f5600103a0019 100644 --- a/drivers/phy/freescale/phy-fsl-imx8m-pcie.c +++ b/drivers/phy/freescale/phy-fsl-imx8m-pcie.c @@ -89,7 +89,8 @@ static int imx8_pcie_phy_power_on(struct phy *phy) writel(imx8_phy->tx_deemph_gen2, imx8_phy->base + PCIE_PHY_TRSV_REG6); break; - case IMX8MP: /* Do nothing. */ + case IMX8MP: + reset_control_assert(imx8_phy->reset); break; } From e26755512623ffc643ccb6bffc595417e2d1c587 Mon Sep 17 00:00:00 2001 From: Louis Chauvet Date: Thu, 27 Nov 2025 11:26:16 +0100 Subject: [PATCH 2518/3902] phy: rockchip: inno-usb2: fix disconnection in gadget mode MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit commit 028e8ca7b20fb7324f3e5db34ba8bd366d9d3acc upstream. When the OTG USB port is used to power the SoC, configured as peripheral and used in gadget mode, there is a disconnection about 6 seconds after the gadget is configured and enumerated. The problem was observed on a Radxa Rock Pi S board, which can only be powered by the only USB-C connector. That connector is the only one usable in gadget mode. This implies the USB cable is connected from before boot and never disconnects while the kernel runs. The problem happens because of the PHY driver code flow, summarized as: * UDC start code (triggered via configfs at any time after boot) -> phy_init -> rockchip_usb2phy_init -> schedule_delayed_work(otg_sm_work [A], 6 sec) -> phy_power_on -> rockchip_usb2phy_power_on -> enable clock -> rockchip_usb2phy_reset * Now the gadget interface is up and running. * 6 seconds later otg_sm_work starts [A] -> rockchip_usb2phy_otg_sm_work(): if (B_IDLE state && VBUS present && ...): schedule_delayed_work(&rport->chg_work [B], 0); * immediately the chg_detect_work starts [B] -> rockchip_chg_detect_work(): if chg_state is UNDEFINED: if (!rport->suspended): rockchip_usb2phy_power_off() <--- [X] At [X], the PHY is powered off, causing a disconnection. This quickly triggers a new connection and following re-enumeration, but any connection that had been established during the 6 seconds is broken. The code already checks for !rport->suspended (which, somewhat counter-intuitively, means the PHY is powered on), so add a guard for VBUS as well to avoid a disconnection when a cable is connected. Fixes: 98898f3bc83c ("phy: rockchip-inno-usb2: support otg-port for rk3399") Cc: stable@vger.kernel.org Closes: https://lore.kernel.org/lkml/20250414185458.7767aabc@booty/ Signed-off-by: Louis Chauvet Co-developed-by: Luca Ceresoli Signed-off-by: Luca Ceresoli Reviewed-by: Théo Lebrun Link: https://patch.msgid.link/20251127-rk3308-fix-usb-gadget-phy-disconnect-v2-1-dac8a02cd2ca@bootlin.com Signed-off-by: Vinod Koul Signed-off-by: Greg Kroah-Hartman --- drivers/phy/rockchip/phy-rockchip-inno-usb2.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/drivers/phy/rockchip/phy-rockchip-inno-usb2.c b/drivers/phy/rockchip/phy-rockchip-inno-usb2.c index a53cc9c86b7c92..8f4c08e599aa22 100644 --- a/drivers/phy/rockchip/phy-rockchip-inno-usb2.c +++ b/drivers/phy/rockchip/phy-rockchip-inno-usb2.c @@ -821,14 +821,16 @@ static void rockchip_chg_detect_work(struct work_struct *work) container_of(work, struct rockchip_usb2phy_port, chg_work.work); struct rockchip_usb2phy *rphy = dev_get_drvdata(rport->phy->dev.parent); struct regmap *base = get_reg_base(rphy); - bool is_dcd, tmout, vout; + bool is_dcd, tmout, vout, vbus_attach; unsigned long delay; + vbus_attach = property_enabled(rphy->grf, &rport->port_cfg->utmi_bvalid); + dev_dbg(&rport->phy->dev, "chg detection work state = %d\n", rphy->chg_state); switch (rphy->chg_state) { case USB_CHG_STATE_UNDEFINED: - if (!rport->suspended) + if (!rport->suspended && !vbus_attach) rockchip_usb2phy_power_off(rport->phy); /* put the controller in non-driving mode */ if (!vbus_attach) From b74edae2a0c7a234a50796e1eb6d4c01df2170d0 Mon Sep 17 00:00:00 2001 From: Franz Schnyder Date: Wed, 26 Nov 2025 15:01:33 +0100 Subject: [PATCH 2519/3902] phy: fsl-imx8mq-usb: fix typec orientation switch when built as module commit 49ccab4bedd4779899246107dc19fb01c5b6fea3 upstream. Currently, the PHY only registers the typec orientation switch when it is built in. If the typec driver is built as a module, the switch registration is skipped due to the preprocessor condition, causing orientation detection to fail. With commit 45fe729be9a6 ("usb: typec: Stub out typec_switch APIs when CONFIG_TYPEC=n") the preprocessor condition is not needed anymore and the orientation switch is correctly registered for both built-in and module builds. Fixes: b58f0f86fd61 ("phy: fsl-imx8mq-usb: add tca function driver for imx95") Cc: stable@vger.kernel.org Suggested-by: Xu Yang Signed-off-by: Franz Schnyder Reviewed-by: Frank Li Reviewed-by: Xu Yang Link: https://patch.msgid.link/20251126140136.1202241-1-fra.schnyder@gmail.com Signed-off-by: Vinod Koul Signed-off-by: Greg Kroah-Hartman --- drivers/phy/freescale/phy-fsl-imx8mq-usb.c | 14 -------------- 1 file changed, 14 deletions(-) diff --git a/drivers/phy/freescale/phy-fsl-imx8mq-usb.c b/drivers/phy/freescale/phy-fsl-imx8mq-usb.c index 0c84f5f7a82cb2..f6cac4c049c431 100644 --- a/drivers/phy/freescale/phy-fsl-imx8mq-usb.c +++ b/drivers/phy/freescale/phy-fsl-imx8mq-usb.c @@ -124,8 +124,6 @@ struct imx8mq_usb_phy { static void tca_blk_orientation_set(struct tca_blk *tca, enum typec_orientation orientation); -#ifdef CONFIG_TYPEC - static int tca_blk_typec_switch_set(struct typec_switch_dev *sw, enum typec_orientation orientation) { @@ -173,18 +171,6 @@ static void tca_blk_put_typec_switch(struct typec_switch_dev *sw) typec_switch_unregister(sw); } -#else - -static struct typec_switch_dev *tca_blk_get_typec_switch(struct platform_device *pdev, - struct imx8mq_usb_phy *imx_phy) -{ - return NULL; -} - -static void tca_blk_put_typec_switch(struct typec_switch_dev *sw) {} - -#endif /* CONFIG_TYPEC */ - static void tca_blk_orientation_set(struct tca_blk *tca, enum typec_orientation orientation) { From 69d1c05268a86a6df31e2484b8cca026300ae6d0 Mon Sep 17 00:00:00 2001 From: Wayne Chang Date: Fri, 12 Dec 2025 11:21:16 +0800 Subject: [PATCH 2520/3902] phy: tegra: xusb: Explicitly configure HS_DISCON_LEVEL to 0x7 commit b246caa68037aa495390a60d080acaeb84f45fff upstream. The USB2 Bias Pad Control register manages analog parameters for signal detection. Previously, the HS_DISCON_LEVEL relied on hardware reset values, which may lead to the detection failure. Explicitly configure HS_DISCON_LEVEL to 0x7. This ensures the disconnect threshold is sufficient to guarantee reliable detection. Fixes: bbf711682cd5 ("phy: tegra: xusb: Add Tegra186 support") Cc: stable@vger.kernel.org Signed-off-by: Wayne Chang Link: https://patch.msgid.link/20251212032116.768307-1-waynec@nvidia.com Signed-off-by: Vinod Koul Signed-off-by: Greg Kroah-Hartman --- drivers/phy/tegra/xusb-tegra186.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/drivers/phy/tegra/xusb-tegra186.c b/drivers/phy/tegra/xusb-tegra186.c index e818f6c3980e6b..bec9616c4a2e0d 100644 --- a/drivers/phy/tegra/xusb-tegra186.c +++ b/drivers/phy/tegra/xusb-tegra186.c @@ -84,6 +84,7 @@ #define XUSB_PADCTL_USB2_BIAS_PAD_CTL0 0x284 #define BIAS_PAD_PD BIT(11) #define HS_SQUELCH_LEVEL(x) (((x) & 0x7) << 0) +#define HS_DISCON_LEVEL(x) (((x) & 0x7) << 3) #define XUSB_PADCTL_USB2_BIAS_PAD_CTL1 0x288 #define USB2_TRK_START_TIMER(x) (((x) & 0x7f) << 12) @@ -623,6 +624,8 @@ static void tegra186_utmi_bias_pad_power_on(struct tegra_xusb_padctl *padctl) value &= ~BIAS_PAD_PD; value &= ~HS_SQUELCH_LEVEL(~0); value |= HS_SQUELCH_LEVEL(priv->calib.hs_squelch); + value &= ~HS_DISCON_LEVEL(~0); + value |= HS_DISCON_LEVEL(0x7); padctl_writel(padctl, value, XUSB_PADCTL_USB2_BIAS_PAD_CTL0); udelay(1); From 11c82acb4876c7fc1fc1cbe1edfc0cc5a8ff27d0 Mon Sep 17 00:00:00 2001 From: Wayne Chang Date: Mon, 12 Jan 2026 22:56:53 +0800 Subject: [PATCH 2521/3902] usb: host: xhci-tegra: Use platform_get_irq_optional() for wake IRQs commit d13b6a128a12e528bb18f971f2969feb286f45c7 upstream. When some wake IRQs are disabled in the device tree, the corresponding interrupt entries are removed from DT. In such cases, the driver currently calls platform_get_irq(), which returns -ENXIO and logs an error like: tegra-xusb 3610000.usb: error -ENXIO: IRQ index 2 not found However, not all wake IRQs are mandatory. The hardware can operate normally even if some wake sources are not defined in DT. To avoid this false alarm and allow missing wake IRQs gracefully, use platform_get_irq_optional() instead of platform_get_irq(). Fixes: 5df186e2ef11 ("usb: xhci: tegra: Support USB wakeup function for Tegra234") Cc: stable Signed-off-by: Wayne Chang Signed-off-by: Wei-Cheng Chen Reviewed-by: Jon Hunter Tested-by: Jon Hunter Link: https://patch.msgid.link/20260112145653.95691-1-weichengc@nvidia.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/host/xhci-tegra.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/usb/host/xhci-tegra.c b/drivers/usb/host/xhci-tegra.c index 5255b10028931c..c78bed0aa844ea 100644 --- a/drivers/usb/host/xhci-tegra.c +++ b/drivers/usb/host/xhci-tegra.c @@ -1564,7 +1564,7 @@ static int tegra_xusb_setup_wakeup(struct platform_device *pdev, struct tegra_xu for (i = 0; i < tegra->soc->max_num_wakes; i++) { struct irq_data *data; - tegra->wake_irqs[i] = platform_get_irq(pdev, i + WAKE_IRQ_START_INDEX); + tegra->wake_irqs[i] = platform_get_irq_optional(pdev, i + WAKE_IRQ_START_INDEX); if (tegra->wake_irqs[i] < 0) break; From 34f6634dba87ef72b3c3a3a524be663adef7ab42 Mon Sep 17 00:00:00 2001 From: Mathias Nyman Date: Fri, 16 Jan 2026 01:37:58 +0200 Subject: [PATCH 2522/3902] xhci: sideband: don't dereference freed ring when removing sideband endpoint MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit commit dd83dc1249737b837ac5d57c81f2b0977c613d9f upstream. xhci_sideband_remove_endpoint() incorrecly assumes that the endpoint is running and has a valid transfer ring. Lianqin reported a crash during suspend/wake-up stress testing, and found the cause to be dereferencing a non-existing transfer ring 'ep->ring' during xhci_sideband_remove_endpoint(). The endpoint and its ring may be in unknown state if this function is called after xHCI was reinitialized in resume (lost power), or if device is being re-enumerated, disconnected or endpoint already dropped. Fix this by both removing unnecessary ring access, and by checking ep->ring exists before dereferencing it. Also make sure endpoint is running before attempting to stop it. Remove the xhci_initialize_ring_info() call during sideband endpoint removal as is it only initializes ring structure enqueue, dequeue and cycle state values to their starting values without changing actual hardware enqueue, dequeue and cycle state. Leaving them out of sync is worse than leaving it as it is. The endpoint will get freed in after this in most usecases. If the (audio) class driver want's to reuse the endpoint after offload then it is up to the class driver to ensure endpoint is properly set up. Reported-by: 胡连勤 Closes: https://lore.kernel.org/linux-usb/TYUPR06MB6217B105B059A7730C4F6EC8D2B9A@TYUPR06MB6217.apcprd06.prod.outlook.com/ Tested-by: 胡连勤 Fixes: de66754e9f80 ("xhci: sideband: add initial api to register a secondary interrupter entity") Cc: stable@vger.kernel.org Signed-off-by: Mathias Nyman Link: https://patch.msgid.link/20260115233758.364097-2-mathias.nyman@linux.intel.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/host/xhci-sideband.c | 1 - drivers/usb/host/xhci.c | 15 ++++++++++++--- 2 files changed, 12 insertions(+), 4 deletions(-) diff --git a/drivers/usb/host/xhci-sideband.c b/drivers/usb/host/xhci-sideband.c index a85f62a73313ab..2bd77255032b97 100644 --- a/drivers/usb/host/xhci-sideband.c +++ b/drivers/usb/host/xhci-sideband.c @@ -210,7 +210,6 @@ xhci_sideband_remove_endpoint(struct xhci_sideband *sb, return -ENODEV; __xhci_sideband_remove_endpoint(sb, ep); - xhci_initialize_ring_info(ep->ring); return 0; } diff --git a/drivers/usb/host/xhci.c b/drivers/usb/host/xhci.c index a148a128012638..4161c8c7721dd9 100644 --- a/drivers/usb/host/xhci.c +++ b/drivers/usb/host/xhci.c @@ -2891,16 +2891,25 @@ int xhci_stop_endpoint_sync(struct xhci_hcd *xhci, struct xhci_virt_ep *ep, int gfp_t gfp_flags) { struct xhci_command *command; + struct xhci_ep_ctx *ep_ctx; unsigned long flags; - int ret; + int ret = -ENODEV; command = xhci_alloc_command(xhci, true, gfp_flags); if (!command) return -ENOMEM; spin_lock_irqsave(&xhci->lock, flags); - ret = xhci_queue_stop_endpoint(xhci, command, ep->vdev->slot_id, - ep->ep_index, suspend); + + /* make sure endpoint exists and is running before stopping it */ + if (ep->ring) { + ep_ctx = xhci_get_ep_ctx(xhci, ep->vdev->out_ctx, ep->ep_index); + if (GET_EP_CTX_STATE(ep_ctx) == EP_STATE_RUNNING) + ret = xhci_queue_stop_endpoint(xhci, command, + ep->vdev->slot_id, + ep->ep_index, suspend); + } + if (ret < 0) { spin_unlock_irqrestore(&xhci->lock, flags); goto out; From aee473246134b33dbb5e2b58926b59632a84a223 Mon Sep 17 00:00:00 2001 From: Xu Yang Date: Tue, 13 Jan 2026 17:53:08 +0800 Subject: [PATCH 2523/3902] usb: gadget: uvc: fix interval_duration calculation commit 010dc57cb5163e5f4a32430dd5091cc29efd0471 upstream. According to USB specification: For full-/high-speed isochronous endpoints, the bInterval value is used as the exponent for a 2^(bInterval-1) value. To correctly convert bInterval as interval_duration: interval_duration = 2^(bInterval-1) * frame_interval Because the unit of video->interval is 100ns, add a comment info to make it clear. Fixes: 48dbe731171e ("usb: gadget: uvc: set req_size and n_requests based on the frame interval") Cc: stable@vger.kernel.org Reviewed-by: Frank Li Signed-off-by: Xu Yang Link: https://patch.msgid.link/20260113-uvc-gadget-fix-patch-v2-2-62950ef5bcb5@nxp.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/gadget/function/uvc.h | 2 +- drivers/usb/gadget/function/uvc_video.c | 7 +++++-- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/drivers/usb/gadget/function/uvc.h b/drivers/usb/gadget/function/uvc.h index 9e79cbe5071579..791a3b956cf29c 100644 --- a/drivers/usb/gadget/function/uvc.h +++ b/drivers/usb/gadget/function/uvc.h @@ -107,7 +107,7 @@ struct uvc_video { unsigned int width; unsigned int height; unsigned int imagesize; - unsigned int interval; + unsigned int interval; /* in 100ns units */ struct mutex mutex; /* protects frame parameters */ unsigned int uvc_num_requests; diff --git a/drivers/usb/gadget/function/uvc_video.c b/drivers/usb/gadget/function/uvc_video.c index fb77b0b2179017..7f5690713bc0e1 100644 --- a/drivers/usb/gadget/function/uvc_video.c +++ b/drivers/usb/gadget/function/uvc_video.c @@ -499,7 +499,7 @@ uvc_video_prep_requests(struct uvc_video *video) { struct uvc_device *uvc = container_of(video, struct uvc_device, video); struct usb_composite_dev *cdev = uvc->func.config->cdev; - unsigned int interval_duration = video->ep->desc->bInterval * 1250; + unsigned int interval_duration; unsigned int max_req_size, req_size, header_size; unsigned int nreq; @@ -515,8 +515,11 @@ uvc_video_prep_requests(struct uvc_video *video) return; } + interval_duration = 2 << (video->ep->desc->bInterval - 1); if (cdev->gadget->speed < USB_SPEED_HIGH) - interval_duration = video->ep->desc->bInterval * 10000; + interval_duration *= 10000; + else + interval_duration *= 1250; nreq = DIV_ROUND_UP(video->interval, interval_duration); From 01120b22c57c56c69f35dc7b803cd4b884bc8fbc Mon Sep 17 00:00:00 2001 From: Xu Yang Date: Tue, 13 Jan 2026 17:53:07 +0800 Subject: [PATCH 2524/3902] usb: gadget: uvc: fix req_payload_size calculation commit 2edc1acb1a2512843425aa19d0c6060a0a924605 upstream. Current req_payload_size calculation has 2 issue: (1) When the first time calculate req_payload_size for all the buffers, reqs_per_frame = 0 will be the divisor of DIV_ROUND_UP(). So the result is undefined. This happens because VIDIOC_STREAMON is always executed after VIDIOC_QBUF. So video->reqs_per_frame will be 0 until VIDIOC_STREAMON is run. (2) The buf->req_payload_size may be bigger than max_req_size. Take YUYV pixel format as example: If bInterval = 1, video->interval = 666666, high-speed: video->reqs_per_frame = 666666 / 1250 = 534 720p: buf->req_payload_size = 1843200 / 534 = 3452 1080p: buf->req_payload_size = 4147200 / 534 = 7766 Based on such req_payload_size, the controller can't run normally. To fix above issue, assign max_req_size to buf->req_payload_size when video->reqs_per_frame = 0. And limit buf->req_payload_size to video->req_size if it's large than video->req_size. Since max_req_size is used at many place, add it to struct uvc_video and set the value once endpoint is enabled. Fixes: 98ad03291560 ("usb: gadget: uvc: set req_length based on payload by nreqs instead of req_size") Cc: stable@vger.kernel.org Reviewed-by: Frank Li Signed-off-by: Xu Yang Link: https://patch.msgid.link/20260113-uvc-gadget-fix-patch-v2-1-62950ef5bcb5@nxp.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/gadget/function/f_uvc.c | 4 ++++ drivers/usb/gadget/function/uvc.h | 1 + drivers/usb/gadget/function/uvc_queue.c | 15 +++++++++++---- drivers/usb/gadget/function/uvc_video.c | 4 +--- 4 files changed, 17 insertions(+), 7 deletions(-) diff --git a/drivers/usb/gadget/function/f_uvc.c b/drivers/usb/gadget/function/f_uvc.c index aa6ab666741a95..a96476507d2fdf 100644 --- a/drivers/usb/gadget/function/f_uvc.c +++ b/drivers/usb/gadget/function/f_uvc.c @@ -362,6 +362,10 @@ uvc_function_set_alt(struct usb_function *f, unsigned interface, unsigned alt) return ret; usb_ep_enable(uvc->video.ep); + uvc->video.max_req_size = uvc->video.ep->maxpacket + * max_t(unsigned int, uvc->video.ep->maxburst, 1) + * (uvc->video.ep->mult); + memset(&v4l2_event, 0, sizeof(v4l2_event)); v4l2_event.type = UVC_EVENT_STREAMON; v4l2_event_queue(&uvc->vdev, &v4l2_event); diff --git a/drivers/usb/gadget/function/uvc.h b/drivers/usb/gadget/function/uvc.h index 791a3b956cf29c..676419a049762f 100644 --- a/drivers/usb/gadget/function/uvc.h +++ b/drivers/usb/gadget/function/uvc.h @@ -117,6 +117,7 @@ struct uvc_video { /* Requests */ bool is_enabled; /* tracks whether video stream is enabled */ unsigned int req_size; + unsigned int max_req_size; struct list_head ureqs; /* all uvc_requests allocated by uvc_video */ /* USB requests that the video pump thread can encode into */ diff --git a/drivers/usb/gadget/function/uvc_queue.c b/drivers/usb/gadget/function/uvc_queue.c index 9a1bbd79ff5af9..21d80322cb6148 100644 --- a/drivers/usb/gadget/function/uvc_queue.c +++ b/drivers/usb/gadget/function/uvc_queue.c @@ -86,10 +86,17 @@ static int uvc_buffer_prepare(struct vb2_buffer *vb) buf->bytesused = 0; } else { buf->bytesused = vb2_get_plane_payload(vb, 0); - buf->req_payload_size = - DIV_ROUND_UP(buf->bytesused + - (video->reqs_per_frame * UVCG_REQUEST_HEADER_LEN), - video->reqs_per_frame); + + if (video->reqs_per_frame != 0) { + buf->req_payload_size = + DIV_ROUND_UP(buf->bytesused + + (video->reqs_per_frame * UVCG_REQUEST_HEADER_LEN), + video->reqs_per_frame); + if (buf->req_payload_size > video->req_size) + buf->req_payload_size = video->req_size; + } else { + buf->req_payload_size = video->max_req_size; + } } return 0; diff --git a/drivers/usb/gadget/function/uvc_video.c b/drivers/usb/gadget/function/uvc_video.c index 7f5690713bc0e1..9dc3af16e2f389 100644 --- a/drivers/usb/gadget/function/uvc_video.c +++ b/drivers/usb/gadget/function/uvc_video.c @@ -503,9 +503,7 @@ uvc_video_prep_requests(struct uvc_video *video) unsigned int max_req_size, req_size, header_size; unsigned int nreq; - max_req_size = video->ep->maxpacket - * max_t(unsigned int, video->ep->maxburst, 1) - * (video->ep->mult); + max_req_size = video->max_req_size; if (!usb_endpoint_xfer_isoc(video->ep->desc)) { video->req_size = max_req_size; From 5468051245c2309420a5b42aa2a22ee480221055 Mon Sep 17 00:00:00 2001 From: Thinh Nguyen Date: Fri, 2 Jan 2026 21:53:46 +0000 Subject: [PATCH 2525/3902] usb: dwc3: Check for USB4 IP_NAME commit 0ed91d47959cb7573c17e06487f0fb891d59dfb3 upstream. Synopsys renamed DWC_usb32 IP to DWC_usb4 as of IP version 1.30. No functional change except checking for the IP_NAME here. The driver will treat the new IP_NAME as if it's DWC_usb32. Additional features for USB4 will be introduced and checked separately. Cc: stable@vger.kernel.org Signed-off-by: Thinh Nguyen Link: https://patch.msgid.link/e6f1827754c7a7ddc5eb7382add20bfe3a9b312f.1767390747.git.Thinh.Nguyen@synopsys.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/dwc3/core.c | 2 ++ drivers/usb/dwc3/core.h | 1 + 2 files changed, 3 insertions(+) diff --git a/drivers/usb/dwc3/core.c b/drivers/usb/dwc3/core.c index c2ce2f5e60a19c..eb10490a6d92ca 100644 --- a/drivers/usb/dwc3/core.c +++ b/drivers/usb/dwc3/core.c @@ -991,6 +991,8 @@ static bool dwc3_core_is_valid(struct dwc3 *dwc) reg = dwc3_readl(dwc->regs, DWC3_GSNPSID); dwc->ip = DWC3_GSNPS_ID(reg); + if (dwc->ip == DWC4_IP) + dwc->ip = DWC32_IP; /* This should read as U3 followed by revision number */ if (DWC3_IP_IS(DWC3)) { diff --git a/drivers/usb/dwc3/core.h b/drivers/usb/dwc3/core.h index a5fc92c4ffa3bc..45757169b672fd 100644 --- a/drivers/usb/dwc3/core.h +++ b/drivers/usb/dwc3/core.h @@ -1265,6 +1265,7 @@ struct dwc3 { #define DWC3_IP 0x5533 #define DWC31_IP 0x3331 #define DWC32_IP 0x3332 +#define DWC4_IP 0x3430 u32 revision; From 6e4663c6ec273f05b8e78fd38d5713ce37afc627 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20Br=C3=BCderl?= Date: Sun, 7 Dec 2025 10:02:20 +0100 Subject: [PATCH 2526/3902] usb: core: add USB_QUIRK_NO_BOS for devices that hang on BOS descriptor MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit commit 2740ac33c87b3d0dfa022efd6ba04c6261b1abbd upstream. Add USB_QUIRK_NO_BOS quirk flag to skip requesting the BOS descriptor for devices that cannot handle it. Add Elgato 4K X (0fd9:009b) to the quirk table. This device hangs when the BOS descriptor is requested at SuperSpeed Plus (10Gbps). Link: https://bugzilla.kernel.org/show_bug.cgi?id=220027 Cc: stable Signed-off-by: Johannes Brüderl Link: https://patch.msgid.link/20251207090220.14807-1-johannes.bruederl@gmail.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/core/config.c | 5 +++++ drivers/usb/core/quirks.c | 3 +++ include/linux/usb/quirks.h | 3 +++ 3 files changed, 11 insertions(+) diff --git a/drivers/usb/core/config.c b/drivers/usb/core/config.c index baf5bc844b6ffa..2bb1ceb9d621a3 100644 --- a/drivers/usb/core/config.c +++ b/drivers/usb/core/config.c @@ -1040,6 +1040,11 @@ int usb_get_bos_descriptor(struct usb_device *dev) __u8 cap_type; int ret; + if (dev->quirks & USB_QUIRK_NO_BOS) { + dev_dbg(ddev, "skipping BOS descriptor\n"); + return -ENOMSG; + } + bos = kzalloc(sizeof(*bos), GFP_KERNEL); if (!bos) return -ENOMEM; diff --git a/drivers/usb/core/quirks.c b/drivers/usb/core/quirks.c index 47f589c4104a39..c4d85089d19b13 100644 --- a/drivers/usb/core/quirks.c +++ b/drivers/usb/core/quirks.c @@ -450,6 +450,9 @@ static const struct usb_device_id usb_quirk_list[] = { { USB_DEVICE(0x0c45, 0x7056), .driver_info = USB_QUIRK_IGNORE_REMOTE_WAKEUP }, + /* Elgato 4K X - BOS descriptor fetch hangs at SuperSpeed Plus */ + { USB_DEVICE(0x0fd9, 0x009b), .driver_info = USB_QUIRK_NO_BOS }, + /* Sony Xperia XZ1 Compact (lilac) smartphone in fastboot mode */ { USB_DEVICE(0x0fce, 0x0dde), .driver_info = USB_QUIRK_NO_LPM }, diff --git a/include/linux/usb/quirks.h b/include/linux/usb/quirks.h index 59409c1fc3dee7..2f7bd2fdc6164b 100644 --- a/include/linux/usb/quirks.h +++ b/include/linux/usb/quirks.h @@ -75,4 +75,7 @@ /* short SET_ADDRESS request timeout */ #define USB_QUIRK_SHORT_SET_ADDRESS_REQ_TIMEOUT BIT(16) +/* skip BOS descriptor request */ +#define USB_QUIRK_NO_BOS BIT(17) + #endif /* __LINUX_USB_QUIRKS_H */ From 525779e2df9e5ea4f11c98ab030910fbaf679b15 Mon Sep 17 00:00:00 2001 From: Huacai Chen Date: Mon, 12 Jan 2026 16:48:02 +0800 Subject: [PATCH 2527/3902] USB: OHCI/UHCI: Add soft dependencies on ehci_platform commit 01ef7f1b8713a78ab1a9512cf8096d2474c70633 upstream. Commit 9beeee6584b9aa4f ("USB: EHCI: log a warning if ehci-hcd is not loaded first") said that ehci-hcd should be loaded before ohci-hcd and uhci-hcd. However, commit 05c92da0c52494ca ("usb: ohci/uhci - add soft dependencies on ehci_pci") only makes ohci-pci/uhci-pci depend on ehci- pci, which is not enough and we may still see the warnings in boot log. To eliminate the warnings we should make ohci-hcd/uhci-hcd depend on ehci-hcd. But Alan said that the warning introduced by 9beeee6584b9aa4f is bogus, we only need the soft dependencies in the PCI level rather than the HCD level. However, there is really another neccessary soft dependencies between ohci-platform/uhci-platform and ehci-platform, which is added by this patch. The boot logs are below. 1. ohci-platform loaded before ehci-platform: ohci-platform 1f058000.usb: Generic Platform OHCI controller ohci-platform 1f058000.usb: new USB bus registered, assigned bus number 1 ohci-platform 1f058000.usb: irq 28, io mem 0x1f058000 hub 1-0:1.0: USB hub found hub 1-0:1.0: 4 ports detected Warning! ehci_hcd should always be loaded before uhci_hcd and ohci_hcd, not after usb 1-4: new low-speed USB device number 2 using ohci-platform ehci-platform 1f050000.usb: EHCI Host Controller ehci-platform 1f050000.usb: new USB bus registered, assigned bus number 2 ehci-platform 1f050000.usb: irq 29, io mem 0x1f050000 ehci-platform 1f050000.usb: USB 2.0 started, EHCI 1.00 usb 1-4: device descriptor read/all, error -62 hub 2-0:1.0: USB hub found hub 2-0:1.0: 4 ports detected usb 1-4: new low-speed USB device number 3 using ohci-platform input: YSPRINGTECH USB OPTICAL MOUSE as /devices/platform/bus@10000000/1f058000.usb/usb1/1-4/1-4:1.0/0003:10C4:8105.0001/input/input0 hid-generic 0003:10C4:8105.0001: input,hidraw0: USB HID v1.11 Mouse [YSPRINGTECH USB OPTICAL MOUSE] on usb-1f058000.usb-4/input0 2. ehci-platform loaded before ohci-platform: ehci-platform 1f050000.usb: EHCI Host Controller ehci-platform 1f050000.usb: new USB bus registered, assigned bus number 1 ehci-platform 1f050000.usb: irq 28, io mem 0x1f050000 ehci-platform 1f050000.usb: USB 2.0 started, EHCI 1.00 hub 1-0:1.0: USB hub found hub 1-0:1.0: 4 ports detected ohci-platform 1f058000.usb: Generic Platform OHCI controller ohci-platform 1f058000.usb: new USB bus registered, assigned bus number 2 ohci-platform 1f058000.usb: irq 29, io mem 0x1f058000 hub 2-0:1.0: USB hub found hub 2-0:1.0: 4 ports detected usb 2-4: new low-speed USB device number 2 using ohci-platform input: YSPRINGTECH USB OPTICAL MOUSE as /devices/platform/bus@10000000/1f058000.usb/usb2/2-4/2-4:1.0/0003:10C4:8105.0001/input/input0 hid-generic 0003:10C4:8105.0001: input,hidraw0: USB HID v1.11 Mouse [YSPRINGTECH USB OPTICAL MOUSE] on usb-1f058000.usb-4/input0 In the later case, there is no re-connection for USB-1.0/1.1 devices, which is expected. Cc: stable Reported-by: Shengwen Xiao Signed-off-by: Huacai Chen Reviewed-by: Alan Stern Link: https://patch.msgid.link/20260112084802.1995923-1-chenhuacai@loongson.cn Signed-off-by: Greg Kroah-Hartman --- drivers/usb/host/ohci-platform.c | 1 + drivers/usb/host/uhci-platform.c | 1 + 2 files changed, 2 insertions(+) diff --git a/drivers/usb/host/ohci-platform.c b/drivers/usb/host/ohci-platform.c index f47ae12cde6a26..059621210d2c29 100644 --- a/drivers/usb/host/ohci-platform.c +++ b/drivers/usb/host/ohci-platform.c @@ -376,3 +376,4 @@ MODULE_DESCRIPTION(DRIVER_DESC); MODULE_AUTHOR("Hauke Mehrtens"); MODULE_AUTHOR("Alan Stern"); MODULE_LICENSE("GPL"); +MODULE_SOFTDEP("pre: ehci_platform"); diff --git a/drivers/usb/host/uhci-platform.c b/drivers/usb/host/uhci-platform.c index 62318291f5664c..be8cba0242892f 100644 --- a/drivers/usb/host/uhci-platform.c +++ b/drivers/usb/host/uhci-platform.c @@ -191,3 +191,4 @@ static struct platform_driver uhci_platform_driver = { .of_match_table = platform_uhci_ids, }, }; +MODULE_SOFTDEP("pre: ehci_platform"); From 37bbcfa2bac9ba8087470329831603a1f94d5ef7 Mon Sep 17 00:00:00 2001 From: Ulrich Mohr Date: Tue, 9 Dec 2025 21:08:41 +0100 Subject: [PATCH 2528/3902] USB: serial: option: add Telit LE910 MBIM composition commit 8af4274ab5999831f4757dfd5bd11665ba3b1569 upstream. Add support for Telit LE910 module when operating in MBIM composition with additional ttys. This USB product ID is used by the module when AT#USBCFG is set to 7. 0x1252: MBIM + tty(NMEA) + tty(MODEM) + tty(MODEM) + SAP T: Bus=01 Lev=01 Prnt=01 Port=01 Cnt=01 Dev#= 2 Spd=480 MxCh= 0 D: Ver= 2.00 Cls=ef(misc ) Sub=02 Prot=01 MxPS=64 #Cfgs= 1 P: Vendor=1bc7 ProdID=1252 Rev=03.18 S: Manufacturer=Android S: Product=LE910C1-EU S: SerialNumber=0123456789ABCDEF C: #Ifs= 6 Cfg#= 1 Atr=a0 MxPwr=500mA I: If#= 0 Alt= 0 #EPs= 1 Cls=02(commc) Sub=0e Prot=00 Driver=cdc_mbim E: Ad=82(I) Atr=03(Int.) MxPS= 64 Ivl=32ms I: If#= 1 Alt= 1 #EPs= 2 Cls=0a(data ) Sub=00 Prot=02 Driver=cdc_mbim E: Ad=01(O) Atr=02(Bulk) MxPS= 512 Ivl=0ms E: Ad=81(I) Atr=02(Bulk) MxPS= 512 Ivl=0ms I: If#= 2 Alt= 0 #EPs= 3 Cls=ff(vend.) Sub=00 Prot=00 Driver=option E: Ad=02(O) Atr=02(Bulk) MxPS= 512 Ivl=0ms E: Ad=83(I) Atr=02(Bulk) MxPS= 512 Ivl=0ms E: Ad=84(I) Atr=03(Int.) MxPS= 10 Ivl=32ms I: If#= 3 Alt= 0 #EPs= 3 Cls=ff(vend.) Sub=00 Prot=00 Driver=option E: Ad=03(O) Atr=02(Bulk) MxPS= 512 Ivl=0ms E: Ad=85(I) Atr=02(Bulk) MxPS= 512 Ivl=0ms E: Ad=86(I) Atr=03(Int.) MxPS= 10 Ivl=32ms I: If#= 4 Alt= 0 #EPs= 3 Cls=ff(vend.) Sub=00 Prot=00 Driver=option E: Ad=04(O) Atr=02(Bulk) MxPS= 512 Ivl=0ms E: Ad=87(I) Atr=02(Bulk) MxPS= 512 Ivl=0ms E: Ad=88(I) Atr=03(Int.) MxPS= 10 Ivl=32ms I: If#= 5 Alt= 0 #EPs= 3 Cls=ff(vend.) Sub=00 Prot=00 Driver=option E: Ad=05(O) Atr=02(Bulk) MxPS= 512 Ivl=0ms E: Ad=89(I) Atr=02(Bulk) MxPS= 512 Ivl=0ms E: Ad=8a(I) Atr=03(Int.) MxPS= 10 Ivl=32ms Signed-off-by: Ulrich Mohr Cc: stable@vger.kernel.org Signed-off-by: Johan Hovold Signed-off-by: Greg Kroah-Hartman --- drivers/usb/serial/option.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/usb/serial/option.c b/drivers/usb/serial/option.c index 4c0e5a3ab557b2..9f2cc5fb9f4562 100644 --- a/drivers/usb/serial/option.c +++ b/drivers/usb/serial/option.c @@ -1505,6 +1505,7 @@ static const struct usb_device_id option_ids[] = { { USB_DEVICE_INTERFACE_CLASS(TELIT_VENDOR_ID, 0x1231, 0xff), /* Telit LE910Cx (RNDIS) */ .driver_info = NCTRL(2) | RSVD(3) }, { USB_DEVICE_AND_INTERFACE_INFO(TELIT_VENDOR_ID, 0x1250, 0xff, 0x00, 0x00) }, /* Telit LE910Cx (rmnet) */ + { USB_DEVICE_INTERFACE_CLASS(TELIT_VENDOR_ID, 0x1252, 0xff) }, /* Telit LE910Cx (MBIM) */ { USB_DEVICE(TELIT_VENDOR_ID, 0x1260), .driver_info = NCTRL(0) | RSVD(1) | RSVD(2) }, { USB_DEVICE(TELIT_VENDOR_ID, 0x1261), From a045dab08049ea292db93888e0b017de54d9ee72 Mon Sep 17 00:00:00 2001 From: Ethan Nelson-Moore Date: Wed, 10 Dec 2025 18:01:17 -0800 Subject: [PATCH 2529/3902] USB: serial: ftdi_sio: add support for PICAXE AXE027 cable commit c0afe95e62984ceea171c3ea319beaf84a21181c upstream. The vendor provides instructions to write "0403 bd90" to /sys/bus/usb-serial/drivers/ftdi_sio/new_id; see: https://picaxe.com/docs/picaxe_linux_instructions.pdf Cc: stable@vger.kernel.org Signed-off-by: Ethan Nelson-Moore Signed-off-by: Johan Hovold Signed-off-by: Greg Kroah-Hartman --- drivers/usb/serial/ftdi_sio.c | 1 + drivers/usb/serial/ftdi_sio_ids.h | 2 ++ 2 files changed, 3 insertions(+) diff --git a/drivers/usb/serial/ftdi_sio.c b/drivers/usb/serial/ftdi_sio.c index 9993a5123344e3..ceddfb1852e165 100644 --- a/drivers/usb/serial/ftdi_sio.c +++ b/drivers/usb/serial/ftdi_sio.c @@ -850,6 +850,7 @@ static const struct usb_device_id id_table_combined[] = { { USB_DEVICE_INTERFACE_NUMBER(FTDI_VID, LMI_LM3S_DEVEL_BOARD_PID, 1) }, { USB_DEVICE_INTERFACE_NUMBER(FTDI_VID, LMI_LM3S_EVAL_BOARD_PID, 1) }, { USB_DEVICE_INTERFACE_NUMBER(FTDI_VID, LMI_LM3S_ICDI_BOARD_PID, 1) }, + { USB_DEVICE(FTDI_VID, FTDI_AXE027_PID) }, { USB_DEVICE_INTERFACE_NUMBER(FTDI_VID, FTDI_TURTELIZER_PID, 1) }, { USB_DEVICE(RATOC_VENDOR_ID, RATOC_PRODUCT_ID_USB60F) }, { USB_DEVICE(RATOC_VENDOR_ID, RATOC_PRODUCT_ID_SCU18) }, diff --git a/drivers/usb/serial/ftdi_sio_ids.h b/drivers/usb/serial/ftdi_sio_ids.h index 2539b9e2f712c3..6c76cfebfd0e42 100644 --- a/drivers/usb/serial/ftdi_sio_ids.h +++ b/drivers/usb/serial/ftdi_sio_ids.h @@ -96,6 +96,8 @@ #define LMI_LM3S_EVAL_BOARD_PID 0xbcd9 #define LMI_LM3S_ICDI_BOARD_PID 0xbcda +#define FTDI_AXE027_PID 0xBD90 /* PICAXE AXE027 USB download cable */ + #define FTDI_TURTELIZER_PID 0xBDC8 /* JTAG/RS-232 adapter by egnite GmbH */ /* OpenDCC (www.opendcc.de) product id */ From 2de213de610315019cc81d763e6a025dab94fc15 Mon Sep 17 00:00:00 2001 From: Ilikara Zheng Date: Mon, 8 Dec 2025 21:23:40 +0800 Subject: [PATCH 2530/3902] nvme-pci: disable secondary temp for Wodposit WPBSNM8 commit 340f4fc5508c2905a1f30de229e2a4b299d55735 upstream. Secondary temperature thresholds (temp2_{min,max}) were not reported properly on this NVMe SSD. This resulted in an error while attempting to read these values with sensors(1): ERROR: Can't get value of subfeature temp2_min: I/O error ERROR: Can't get value of subfeature temp2_max: I/O error Add the device to the nvme_id_table with the NVME_QUIRK_NO_SECONDARY_TEMP_THRESH flag to suppress access to all non- composite temperature thresholds. Cc: stable@vger.kernel.org Tested-by: Wu Haotian Signed-off-by: Ilikara Zheng Signed-off-by: Keith Busch Signed-off-by: Greg Kroah-Hartman --- drivers/nvme/host/pci.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/drivers/nvme/host/pci.c b/drivers/nvme/host/pci.c index 72fb675a696f49..eeffdd7bb2297b 100644 --- a/drivers/nvme/host/pci.c +++ b/drivers/nvme/host/pci.c @@ -3917,6 +3917,8 @@ static const struct pci_device_id nvme_id_table[] = { .driver_data = NVME_QUIRK_NO_DEEPEST_PS, }, { PCI_DEVICE(0x1e49, 0x0041), /* ZHITAI TiPro7000 NVMe SSD */ .driver_data = NVME_QUIRK_NO_DEEPEST_PS, }, + { PCI_DEVICE(0x1fa0, 0x2283), /* Wodposit WPBSNM8-256GTP */ + .driver_data = NVME_QUIRK_NO_SECONDARY_TEMP_THRESH, }, { PCI_DEVICE(0x025e, 0xf1ac), /* SOLIDIGM P44 pro SSDPFKKW020X7 */ .driver_data = NVME_QUIRK_NO_DEEPEST_PS, }, { PCI_DEVICE(0xc0a9, 0x540a), /* Crucial P2 */ From 5caac66da5ba8af82397c4f50c26025530db960c Mon Sep 17 00:00:00 2001 From: Johan Hovold Date: Fri, 2 Jan 2026 12:14:11 +0100 Subject: [PATCH 2531/3902] ASoC: codecs: wsa881x: fix unnecessary initialisation commit 29d71b8a5a40708b3eed9ba4953bfc2312c9c776 upstream. The soundwire update_status() callback may be called multiple times with the same ATTACHED status but initialisation should only be done when transitioning from UNATTACHED to ATTACHED. Fixes: a0aab9e1404a ("ASoC: codecs: add wsa881x amplifier support") Cc: stable@vger.kernel.org # 5.6 Cc: Srinivas Kandagatla Signed-off-by: Johan Hovold Reviewed-by: Krzysztof Kozlowski Reviewed-by: Srinivas Kandagatla Link: https://patch.msgid.link/20260102111413.9605-3-johan@kernel.org Signed-off-by: Mark Brown Signed-off-by: Greg Kroah-Hartman --- sound/soc/codecs/wsa881x.c | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/sound/soc/codecs/wsa881x.c b/sound/soc/codecs/wsa881x.c index 636e59abc3772f..561950ecb8cc7c 100644 --- a/sound/soc/codecs/wsa881x.c +++ b/sound/soc/codecs/wsa881x.c @@ -678,6 +678,7 @@ struct wsa881x_priv { */ unsigned int sd_n_val; int active_ports; + bool hw_init; bool port_prepared[WSA881X_MAX_SWR_PORTS]; bool port_enable[WSA881X_MAX_SWR_PORTS]; }; @@ -687,6 +688,9 @@ static void wsa881x_init(struct wsa881x_priv *wsa881x) struct regmap *rm = wsa881x->regmap; unsigned int val = 0; + if (wsa881x->hw_init) + return; + regmap_register_patch(wsa881x->regmap, wsa881x_rev_2_0, ARRAY_SIZE(wsa881x_rev_2_0)); @@ -724,6 +728,8 @@ static void wsa881x_init(struct wsa881x_priv *wsa881x) regmap_update_bits(rm, WSA881X_OTP_REG_28, 0x3F, 0x3A); regmap_update_bits(rm, WSA881X_BONGO_RESRV_REG1, 0xFF, 0xB2); regmap_update_bits(rm, WSA881X_BONGO_RESRV_REG2, 0xFF, 0x05); + + wsa881x->hw_init = true; } static int wsa881x_component_probe(struct snd_soc_component *comp) @@ -1067,6 +1073,9 @@ static int wsa881x_update_status(struct sdw_slave *slave, { struct wsa881x_priv *wsa881x = dev_get_drvdata(&slave->dev); + if (status == SDW_SLAVE_UNATTACHED) + wsa881x->hw_init = false; + if (status == SDW_SLAVE_ATTACHED && slave->dev_num > 0) wsa881x_init(wsa881x); From d1b24a57662b68c9637d62defc4acb41f28eccfc Mon Sep 17 00:00:00 2001 From: Arnd Bergmann Date: Thu, 4 Dec 2025 11:19:10 +0100 Subject: [PATCH 2532/3902] ext4: fix ext4_tune_sb_params padding commit cd16edba1c6a24af138e1a5ded2711231fffa99f upstream. The padding at the end of struct ext4_tune_sb_params is architecture specific and in particular is different between x86-32 and x86-64, since the __u64 member only enforces struct alignment on the latter. This shows up as a new warning when test-building the headers with -Wpadded: include/linux/ext4.h:144:1: error: padding struct size to alignment boundary with 4 bytes [-Werror=padded] All members inside the structure are naturally aligned, so the only difference here is the amount of padding at the end. Make the padding explicit, to have a consistent sizeof(struct ext4_tune_sb_params) of 232 on all architectures and avoid adding compat ioctl handling for EXT4_IOC_GET_TUNE_SB_PARAM/EXT4_IOC_SET_TUNE_SB_PARAM. This is an ABI break on x86-32 but hopefully this can go into 6.18.y early enough as a fixup so no actual users will be affected. Alternatively, the kernel could handle the ioctl commands for both sizes (232 and 228 bytes) on all architectures. Fixes: 04a91570ac67 ("ext4: implemet new ioctls to set and get superblock parameters") Signed-off-by: Arnd Bergmann Reviewed-by: Jan Kara Link: https://patch.msgid.link/20251204101914.1037148-1-arnd@kernel.org Signed-off-by: Theodore Ts'o Cc: stable@kernel.org Signed-off-by: Greg Kroah-Hartman --- include/uapi/linux/ext4.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/uapi/linux/ext4.h b/include/uapi/linux/ext4.h index 411dcc1e4a35c8..9c683991c32f96 100644 --- a/include/uapi/linux/ext4.h +++ b/include/uapi/linux/ext4.h @@ -139,7 +139,7 @@ struct ext4_tune_sb_params { __u32 clear_feature_incompat_mask; __u32 clear_feature_ro_compat_mask; __u8 mount_opts[64]; - __u8 pad[64]; + __u8 pad[68]; }; #define EXT4_TUNE_FL_ERRORS_BEHAVIOR 0x00000001 From 06e26287f2e349a28ad363941ffd9076bfed8b2e Mon Sep 17 00:00:00 2001 From: Yang Erkun Date: Sat, 13 Dec 2025 13:57:06 +0800 Subject: [PATCH 2533/3902] ext4: fix iloc.bh leak in ext4_xattr_inode_update_ref commit d250bdf531d9cd4096fedbb9f172bb2ca660c868 upstream. The error branch for ext4_xattr_inode_update_ref forget to release the refcount for iloc.bh. Find this when review code. Fixes: 57295e835408 ("ext4: guard against EA inode refcount underflow in xattr update") Signed-off-by: Yang Erkun Reviewed-by: Baokun Li Reviewed-by: Zhang Yi Link: https://patch.msgid.link/20251213055706.3417529-1-yangerkun@huawei.com Signed-off-by: Theodore Ts'o Cc: stable@kernel.org Signed-off-by: Greg Kroah-Hartman --- fs/ext4/xattr.c | 1 + 1 file changed, 1 insertion(+) diff --git a/fs/ext4/xattr.c b/fs/ext4/xattr.c index 2e02efbddaacca..4ed8ddf2a60b31 100644 --- a/fs/ext4/xattr.c +++ b/fs/ext4/xattr.c @@ -1037,6 +1037,7 @@ static int ext4_xattr_inode_update_ref(handle_t *handle, struct inode *ea_inode, ext4_error_inode(ea_inode, __func__, __LINE__, 0, "EA inode %lu ref wraparound: ref_count=%lld ref_change=%d", ea_inode->i_ino, ref_count, ref_change); + brelse(iloc.bh); ret = -EFSCORRUPTED; goto out; } From d53b8e05be46aec274b0067590a0c4f989e00830 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20Wei=C3=9Fschuh?= Date: Wed, 7 Jan 2026 11:39:24 +0100 Subject: [PATCH 2534/3902] hrtimer: Fix softirq base check in update_needs_ipi() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit commit 05dc4a9fc8b36d4c99d76bbc02aa9ec0132de4c2 upstream. The 'clockid' field is not the correct way to check for a softirq base. Fix the check to correctly compare the base type instead of the clockid. Fixes: 1e7f7fbcd40c ("hrtimer: Avoid more SMP function calls in clock_was_set()") Signed-off-by: Thomas Weißschuh Signed-off-by: Thomas Gleixner Cc: stable@vger.kernel.org Link: https://patch.msgid.link/20260107-hrtimer-clock-base-check-v1-1-afb5dbce94a1@linutronix.de Signed-off-by: Greg Kroah-Hartman --- kernel/time/hrtimer.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/kernel/time/hrtimer.c b/kernel/time/hrtimer.c index 88aa062b8a556d..e618addb58641c 100644 --- a/kernel/time/hrtimer.c +++ b/kernel/time/hrtimer.c @@ -913,7 +913,7 @@ static bool update_needs_ipi(struct hrtimer_cpu_base *cpu_base, return true; /* Extra check for softirq clock bases */ - if (base->clockid < HRTIMER_BASE_MONOTONIC_SOFT) + if (base->index < HRTIMER_BASE_MONOTONIC_SOFT) continue; if (cpu_base->softirq_activated) continue; From 3981650922417ea3d2e96272d70b08ad534295be Mon Sep 17 00:00:00 2001 From: Haoxiang Li Date: Tue, 23 Dec 2025 20:43:50 +0800 Subject: [PATCH 2535/3902] EDAC/x38: Fix a resource leak in x38_probe1() commit 0ff7c44106b4715fc27a2e455d9f57f1dfcfd54f upstream. If edac_mc_alloc() fails, also unmap the window. [ bp: Use separate labels, turning it into the classic unwind pattern. ] Fixes: df8bc08c192f ("edac x38: new MC driver module") Signed-off-by: Haoxiang Li Signed-off-by: Borislav Petkov (AMD) Cc: stable@vger.kernel.org Link: https://patch.msgid.link/20251223124350.1496325-1-lihaoxiang@isrc.iscas.ac.cn Signed-off-by: Greg Kroah-Hartman --- drivers/edac/x38_edac.c | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/drivers/edac/x38_edac.c b/drivers/edac/x38_edac.c index 49ab5721aab25c..292dda754c2366 100644 --- a/drivers/edac/x38_edac.c +++ b/drivers/edac/x38_edac.c @@ -341,9 +341,12 @@ static int x38_probe1(struct pci_dev *pdev, int dev_idx) layers[1].type = EDAC_MC_LAYER_CHANNEL; layers[1].size = x38_channel_num; layers[1].is_virt_csrow = false; + + + rc = -ENOMEM; mci = edac_mc_alloc(0, ARRAY_SIZE(layers), layers, 0); if (!mci) - return -ENOMEM; + goto unmap; edac_dbg(3, "MC: init mci\n"); @@ -403,9 +406,9 @@ static int x38_probe1(struct pci_dev *pdev, int dev_idx) return 0; fail: + edac_mc_free(mci); +unmap: iounmap(window); - if (mci) - edac_mc_free(mci); return rc; } From 51afd139fac44f56ba113a1cd9abdf3bc4b3a8d3 Mon Sep 17 00:00:00 2001 From: Haoxiang Li Date: Tue, 23 Dec 2025 20:32:02 +0800 Subject: [PATCH 2536/3902] EDAC/i3200: Fix a resource leak in i3200_probe1() commit d42d5715dcb559342ff356327b241c53a67584d9 upstream. If edac_mc_alloc() fails, also unmap the window. [ bp: Use separate labels, turning it into the classic unwind pattern. ] Fixes: dd8ef1db87a4 ("edac: i3200 memory controller driver") Signed-off-by: Haoxiang Li Signed-off-by: Borislav Petkov (AMD) Cc: stable@vger.kernel.org Link: https://patch.msgid.link/20251223123202.1492038-1-lihaoxiang@isrc.iscas.ac.cn Signed-off-by: Greg Kroah-Hartman --- drivers/edac/i3200_edac.c | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/drivers/edac/i3200_edac.c b/drivers/edac/i3200_edac.c index afccdebf5ac1eb..6cade6d7ceff62 100644 --- a/drivers/edac/i3200_edac.c +++ b/drivers/edac/i3200_edac.c @@ -358,10 +358,11 @@ static int i3200_probe1(struct pci_dev *pdev, int dev_idx) layers[1].type = EDAC_MC_LAYER_CHANNEL; layers[1].size = nr_channels; layers[1].is_virt_csrow = false; - mci = edac_mc_alloc(0, ARRAY_SIZE(layers), layers, - sizeof(struct i3200_priv)); + + rc = -ENOMEM; + mci = edac_mc_alloc(0, ARRAY_SIZE(layers), layers, sizeof(struct i3200_priv)); if (!mci) - return -ENOMEM; + goto unmap; edac_dbg(3, "MC: init mci\n"); @@ -421,9 +422,9 @@ static int i3200_probe1(struct pci_dev *pdev, int dev_idx) return 0; fail: + edac_mc_free(mci); +unmap: iounmap(window); - if (mci) - edac_mc_free(mci); return rc; } From bd4e97674582ae40b3af796b56bd890618423148 Mon Sep 17 00:00:00 2001 From: Arnaud Ferraris Date: Mon, 5 Jan 2026 09:43:23 +0100 Subject: [PATCH 2537/3902] tcpm: allow looking for role_sw device in the main node commit 1366cd228b0c67b60a2c0c26ef37fe9f7cfedb7f upstream. If ports are defined in the tcpc main node, fwnode_usb_role_switch_get() returns an error, meaning usb_role_switch_get() (which would succeed) never gets a chance to run as port->role_sw isn't NULL, causing a regression on devices where this is the case. Fix this by turning the NULL check into IS_ERR_OR_NULL(), so usb_role_switch_get() can actually run and the device get properly probed. Fixes: 2d8713f807a4 ("tcpm: switch check for role_sw device with fw_node") Cc: stable Reviewed-by: Heikki Krogerus Reviewed-by: Dragan Simic Signed-off-by: Arnaud Ferraris Link: https://patch.msgid.link/20260105-fix-ppp-power-v2-1-6924f5a41224@collabora.com Signed-off-by: Greg Kroah-Hartman Signed-off-by: Greg Kroah-Hartman --- drivers/usb/typec/tcpm/tcpm.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/usb/typec/tcpm/tcpm.c b/drivers/usb/typec/tcpm/tcpm.c index cc78770509dbc6..37698204d48d25 100644 --- a/drivers/usb/typec/tcpm/tcpm.c +++ b/drivers/usb/typec/tcpm/tcpm.c @@ -7877,7 +7877,7 @@ struct tcpm_port *tcpm_register_port(struct device *dev, struct tcpc_dev *tcpc) port->partner_desc.identity = &port->partner_ident; port->role_sw = fwnode_usb_role_switch_get(tcpc->fwnode); - if (!port->role_sw) + if (IS_ERR_OR_NULL(port->role_sw)) port->role_sw = usb_role_switch_get(port->dev); if (IS_ERR(port->role_sw)) { err = PTR_ERR(port->role_sw); From 0b4c0fbbe00b7de76bdaea7fa771017d7a979b0d Mon Sep 17 00:00:00 2001 From: Tommaso Merciai Date: Thu, 18 Dec 2025 16:10:21 +0100 Subject: [PATCH 2538/3902] i2c: riic: Move suspend handling to NOIRQ phase commit e383f0961422f983451ac4dd6aed1a3d3311f2be upstream. Commit 53326135d0e0 ("i2c: riic: Add suspend/resume support") added suspend support for the Renesas I2C driver and following this change on RZ/G3E the following WARNING is seen on entering suspend ... [ 134.275704] Freezing remaining freezable tasks completed (elapsed 0.001 seconds) [ 134.285536] ------------[ cut here ]------------ [ 134.290298] i2c i2c-2: Transfer while suspended [ 134.295174] WARNING: drivers/i2c/i2c-core.h:56 at __i2c_smbus_xfer+0x1e4/0x214, CPU#0: systemd-sleep/388 [ 134.365507] Tainted: [W]=WARN [ 134.368485] Hardware name: Renesas SMARC EVK version 2 based on r9a09g047e57 (DT) [ 134.375961] pstate: 60400005 (nZCv daif +PAN -UAO -TCO -DIT -SSBS BTYPE=--) [ 134.382935] pc : __i2c_smbus_xfer+0x1e4/0x214 [ 134.387329] lr : __i2c_smbus_xfer+0x1e4/0x214 [ 134.391717] sp : ffff800083f23860 [ 134.395040] x29: ffff800083f23860 x28: 0000000000000000 x27: ffff800082ed5d60 [ 134.402226] x26: 0000001f4395fd74 x25: 0000000000000007 x24: 0000000000000001 [ 134.409408] x23: 0000000000000000 x22: 000000000000006f x21: ffff800083f23936 [ 134.416589] x20: ffff0000c090e140 x19: ffff0000c090e0d0 x18: 0000000000000006 [ 134.423771] x17: 6f63657320313030 x16: 2e30206465737061 x15: ffff800083f23280 [ 134.430953] x14: 0000000000000000 x13: ffff800082b16ce8 x12: 0000000000000f09 [ 134.438134] x11: 0000000000000503 x10: ffff800082b6ece8 x9 : ffff800082b16ce8 [ 134.445315] x8 : 00000000ffffefff x7 : ffff800082b6ece8 x6 : 80000000fffff000 [ 134.452495] x5 : 0000000000000504 x4 : 0000000000000000 x3 : 0000000000000000 [ 134.459672] x2 : 0000000000000000 x1 : 0000000000000000 x0 : ffff0000c9ee9e80 [ 134.466851] Call trace: [ 134.469311] __i2c_smbus_xfer+0x1e4/0x214 (P) [ 134.473715] i2c_smbus_xfer+0xbc/0x120 [ 134.477507] i2c_smbus_read_byte_data+0x4c/0x84 [ 134.482077] isl1208_i2c_read_time+0x44/0x178 [rtc_isl1208] [ 134.487703] isl1208_rtc_read_time+0x14/0x20 [rtc_isl1208] [ 134.493226] __rtc_read_time+0x44/0x88 [ 134.497012] rtc_read_time+0x3c/0x68 [ 134.500622] rtc_suspend+0x9c/0x170 The warning is triggered because I2C transfers can still be attempted while the controller is already suspended, due to inappropriate ordering of the system sleep callbacks. If the controller is autosuspended, there is no way to wake it up once runtime PM disabled (in suspend_late()). During system resume, the I2C controller will be available only after runtime PM is re-enabled (in resume_early()). However, this may be too late for some devices. Wake up the controller in the suspend() callback while runtime PM is still enabled. The I2C controller will remain available until the suspend_noirq() callback (pm_runtime_force_suspend()) is called. During resume, the I2C controller can be restored by the resume_noirq() callback (pm_runtime_force_resume()). Finally, the resume() callback re-enables autosuspend. As a result, the I2C controller can remain available until the system enters suspend_noirq() and from resume_noirq(). Cc: stable@vger.kernel.org Fixes: 53326135d0e0 ("i2c: riic: Add suspend/resume support") Signed-off-by: Tommaso Merciai Reviewed-by: Biju Das Tested-by: Biju Das Signed-off-by: Wolfram Sang Signed-off-by: Greg Kroah-Hartman --- drivers/i2c/busses/i2c-riic.c | 46 +++++++++++++++++++++++++++++------ 1 file changed, 39 insertions(+), 7 deletions(-) diff --git a/drivers/i2c/busses/i2c-riic.c b/drivers/i2c/busses/i2c-riic.c index 3e8f126cb7f749..9e3595b3623e4a 100644 --- a/drivers/i2c/busses/i2c-riic.c +++ b/drivers/i2c/busses/i2c-riic.c @@ -670,12 +670,39 @@ static const struct riic_of_data riic_rz_t2h_info = { static int riic_i2c_suspend(struct device *dev) { - struct riic_dev *riic = dev_get_drvdata(dev); - int ret; + /* + * Some I2C devices may need the I2C controller to remain active + * during resume_noirq() or suspend_noirq(). If the controller is + * autosuspended, there is no way to wake it up once runtime PM is + * disabled (in suspend_late()). + * + * During system resume, the I2C controller will be available only + * after runtime PM is re-enabled (in resume_early()). However, this + * may be too late for some devices. + * + * Wake up the controller in the suspend() callback while runtime PM + * is still enabled. The I2C controller will remain available until + * the suspend_noirq() callback (pm_runtime_force_suspend()) is + * called. During resume, the I2C controller can be restored by the + * resume_noirq() callback (pm_runtime_force_resume()). + * + * Finally, the resume() callback re-enables autosuspend, ensuring + * the I2C controller remains available until the system enters + * suspend_noirq() and from resume_noirq(). + */ + return pm_runtime_resume_and_get(dev); +} - ret = pm_runtime_resume_and_get(dev); - if (ret) - return ret; +static int riic_i2c_resume(struct device *dev) +{ + pm_runtime_put_autosuspend(dev); + + return 0; +} + +static int riic_i2c_suspend_noirq(struct device *dev) +{ + struct riic_dev *riic = dev_get_drvdata(dev); i2c_mark_adapter_suspended(&riic->adapter); @@ -683,12 +710,12 @@ static int riic_i2c_suspend(struct device *dev) riic_clear_set_bit(riic, ICCR1_ICE, 0, RIIC_ICCR1); pm_runtime_mark_last_busy(dev); - pm_runtime_put_sync(dev); + pm_runtime_force_suspend(dev); return reset_control_assert(riic->rstc); } -static int riic_i2c_resume(struct device *dev) +static int riic_i2c_resume_noirq(struct device *dev) { struct riic_dev *riic = dev_get_drvdata(dev); int ret; @@ -697,6 +724,10 @@ static int riic_i2c_resume(struct device *dev) if (ret) return ret; + ret = pm_runtime_force_resume(dev); + if (ret) + return ret; + ret = riic_init_hw(riic); if (ret) { /* @@ -714,6 +745,7 @@ static int riic_i2c_resume(struct device *dev) } static const struct dev_pm_ops riic_i2c_pm_ops = { + NOIRQ_SYSTEM_SLEEP_PM_OPS(riic_i2c_suspend_noirq, riic_i2c_resume_noirq) SYSTEM_SLEEP_PM_OPS(riic_i2c_suspend, riic_i2c_resume) }; From 1144298c1008963dd95b019ece25dce4fe4e3ffd Mon Sep 17 00:00:00 2001 From: Xiaochen Shen Date: Tue, 9 Dec 2025 14:26:49 +0800 Subject: [PATCH 2539/3902] x86/resctrl: Add missing resctrl initialization for Hygon commit 6ee98aabdc700b5705e4f1833e2edc82a826b53b upstream. Hygon CPUs supporting Platform QoS features currently undergo partial resctrl initialization through resctrl_cpu_detect() in the Hygon BSP init helper and AMD/Hygon common initialization code. However, several critical data structures remain uninitialized for Hygon CPUs in the following paths: - get_mem_config()-> __rdt_get_mem_config_amd(): rdt_resource::membw,alloc_capable hw_res::num_closid - rdt_init_res_defs()->rdt_init_res_defs_amd(): rdt_resource::cache hw_res::msr_base,msr_update Add the missing AMD/Hygon common initialization to ensure proper Platform QoS functionality on Hygon CPUs. Fixes: d8df126349da ("x86/cpu/hygon: Add missing resctrl_cpu_detect() in bsp_init helper") Signed-off-by: Xiaochen Shen Signed-off-by: Borislav Petkov (AMD) Reviewed-by: Reinette Chatre Cc: stable@vger.kernel.org Link: https://patch.msgid.link/20251209062650.1536952-2-shenxiaochen@open-hieco.net Signed-off-by: Greg Kroah-Hartman --- arch/x86/kernel/cpu/resctrl/core.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/arch/x86/kernel/cpu/resctrl/core.c b/arch/x86/kernel/cpu/resctrl/core.c index 06ca5a30140c2f..c0dfbb31394efd 100644 --- a/arch/x86/kernel/cpu/resctrl/core.c +++ b/arch/x86/kernel/cpu/resctrl/core.c @@ -818,7 +818,8 @@ static __init bool get_mem_config(void) if (boot_cpu_data.x86_vendor == X86_VENDOR_INTEL) return __get_mem_config_intel(&hw_res->r_resctrl); - else if (boot_cpu_data.x86_vendor == X86_VENDOR_AMD) + else if (boot_cpu_data.x86_vendor == X86_VENDOR_AMD || + boot_cpu_data.x86_vendor == X86_VENDOR_HYGON) return __rdt_get_mem_config_amd(&hw_res->r_resctrl); return false; @@ -978,7 +979,8 @@ static __init void rdt_init_res_defs(void) { if (boot_cpu_data.x86_vendor == X86_VENDOR_INTEL) rdt_init_res_defs_intel(); - else if (boot_cpu_data.x86_vendor == X86_VENDOR_AMD) + else if (boot_cpu_data.x86_vendor == X86_VENDOR_AMD || + boot_cpu_data.x86_vendor == X86_VENDOR_HYGON) rdt_init_res_defs_amd(); } From 43d8f1f001072718b5b42c624f00efcecd6aecc3 Mon Sep 17 00:00:00 2001 From: Xiaochen Shen Date: Tue, 9 Dec 2025 14:26:50 +0800 Subject: [PATCH 2540/3902] x86/resctrl: Fix memory bandwidth counter width for Hygon commit 7517e899e1b87b4c22a92c7e40d8733c48e4ec3c upstream. The memory bandwidth calculation relies on reading the hardware counter and measuring the delta between samples. To ensure accurate measurement, the software reads the counter frequently enough to prevent it from rolling over twice between reads. The default Memory Bandwidth Monitoring (MBM) counter width is 24 bits. Hygon CPUs provide a 32-bit width counter, but they do not support the MBM capability CPUID leaf (0xF.[ECX=1]:EAX) to report the width offset (from 24 bits). Consequently, the kernel falls back to the 24-bit default counter width, which causes incorrect overflow handling on Hygon CPUs. Fix this by explicitly setting the counter width offset to 8 bits (resulting in a 32-bit total counter width) for Hygon CPUs. Fixes: d8df126349da ("x86/cpu/hygon: Add missing resctrl_cpu_detect() in bsp_init helper") Signed-off-by: Xiaochen Shen Signed-off-by: Borislav Petkov (AMD) Reviewed-by: Tony Luck Reviewed-by: Reinette Chatre Cc: stable@vger.kernel.org Link: https://patch.msgid.link/20251209062650.1536952-3-shenxiaochen@open-hieco.net Signed-off-by: Greg Kroah-Hartman --- arch/x86/kernel/cpu/resctrl/core.c | 15 +++++++++++++-- arch/x86/kernel/cpu/resctrl/internal.h | 3 +++ 2 files changed, 16 insertions(+), 2 deletions(-) diff --git a/arch/x86/kernel/cpu/resctrl/core.c b/arch/x86/kernel/cpu/resctrl/core.c index c0dfbb31394efd..79a1f2d5f5019c 100644 --- a/arch/x86/kernel/cpu/resctrl/core.c +++ b/arch/x86/kernel/cpu/resctrl/core.c @@ -1012,8 +1012,19 @@ void resctrl_cpu_detect(struct cpuinfo_x86 *c) c->x86_cache_occ_scale = ebx; c->x86_cache_mbm_width_offset = eax & 0xff; - if (c->x86_vendor == X86_VENDOR_AMD && !c->x86_cache_mbm_width_offset) - c->x86_cache_mbm_width_offset = MBM_CNTR_WIDTH_OFFSET_AMD; + if (!c->x86_cache_mbm_width_offset) { + switch (c->x86_vendor) { + case X86_VENDOR_AMD: + c->x86_cache_mbm_width_offset = MBM_CNTR_WIDTH_OFFSET_AMD; + break; + case X86_VENDOR_HYGON: + c->x86_cache_mbm_width_offset = MBM_CNTR_WIDTH_OFFSET_HYGON; + break; + default: + /* Leave c->x86_cache_mbm_width_offset as 0 */ + break; + } + } } } diff --git a/arch/x86/kernel/cpu/resctrl/internal.h b/arch/x86/kernel/cpu/resctrl/internal.h index 9f4c2f0aaf5c80..6da9bd1a188b96 100644 --- a/arch/x86/kernel/cpu/resctrl/internal.h +++ b/arch/x86/kernel/cpu/resctrl/internal.h @@ -14,6 +14,9 @@ #define MBM_CNTR_WIDTH_OFFSET_AMD 20 +/* Hygon MBM counter width as an offset from MBM_CNTR_WIDTH_BASE */ +#define MBM_CNTR_WIDTH_OFFSET_HYGON 8 + #define RMID_VAL_ERROR BIT_ULL(63) #define RMID_VAL_UNAVAIL BIT_ULL(62) From 17f95d348589b2d65b0799f35a3c617e61cc4a28 Mon Sep 17 00:00:00 2001 From: Nilay Shroff Date: Wed, 14 Jan 2026 12:54:13 +0530 Subject: [PATCH 2541/3902] nvme: fix PCIe subsystem reset controller state transition MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit commit 0edb475ac0a7d153318a24d4dca175a270a5cc4f upstream. The commit d2fe192348f9 (“nvme: only allow entering LIVE from CONNECTING state”) disallows controller state transitions directly from RESETTING to LIVE. However, the NVMe PCIe subsystem reset path relies on this transition to recover the controller on PowerPC (PPC) systems. On PPC systems, issuing a subsystem reset causes a temporary loss of communication with the NVMe adapter. A subsequent PCIe MMIO read then triggers EEH recovery, which restores the PCIe link and brings the controller back online. For EEH recovery to proceed correctly, the controller must transition back to the LIVE state. Due to the changes introduced by commit d2fe192348f9 (“nvme: only allow entering LIVE from CONNECTING state”), the controller can no longer transition directly from RESETTING to LIVE. As a result, EEH recovery exits prematurely, leaving the controller stuck in the RESETTING state. Fix this by explicitly transitioning the controller state from RESETTING to CONNECTING and then to LIVE. This satisfies the updated state transition rules and allows the controller to be successfully recovered on PPC systems following a PCIe subsystem reset. Cc: stable@vger.kernel.org Fixes: d2fe192348f9 ("nvme: only allow entering LIVE from CONNECTING state") Reviewed-by: Daniel Wagner Signed-off-by: Nilay Shroff Signed-off-by: Keith Busch Signed-off-by: Greg Kroah-Hartman --- drivers/nvme/host/pci.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/drivers/nvme/host/pci.c b/drivers/nvme/host/pci.c index eeffdd7bb2297b..28f638413e122f 100644 --- a/drivers/nvme/host/pci.c +++ b/drivers/nvme/host/pci.c @@ -1461,7 +1461,10 @@ static int nvme_pci_subsystem_reset(struct nvme_ctrl *ctrl) } writel(NVME_SUBSYS_RESET, dev->bar + NVME_REG_NSSR); - nvme_change_ctrl_state(ctrl, NVME_CTRL_LIVE); + + if (!nvme_change_ctrl_state(ctrl, NVME_CTRL_CONNECTING) || + !nvme_change_ctrl_state(ctrl, NVME_CTRL_LIVE)) + goto unlock; /* * Read controller status to flush the previous write and trigger a From b5cb343f0c3d071848452ca856a3bacef7943933 Mon Sep 17 00:00:00 2001 From: Ryan Roberts Date: Sun, 4 Jan 2026 13:43:47 +0000 Subject: [PATCH 2542/3902] mm: kmsan: fix poisoning of high-order non-compound pages commit 4795d205d78690a46b60164f44b8bb7b3e800865 upstream. kmsan_free_page() is called by the page allocator's free_pages_prepare() during page freeing. Its job is to poison all the memory covered by the page. It can be called with an order-0 page, a compound high-order page or a non-compound high-order page. But page_size() only works for order-0 and compound pages. For a non-compound high-order page it will incorrectly return PAGE_SIZE. The implication is that the tail pages of a high-order non-compound page do not get poisoned at free, so any invalid access while they are free could go unnoticed. It looks like the pages will be poisoned again at allocation time, so that would bookend the window. Fix this by using the order parameter to calculate the size. Link: https://lkml.kernel.org/r/20260104134348.3544298-1-ryan.roberts@arm.com Fixes: b073d7f8aee4 ("mm: kmsan: maintain KMSAN metadata for page operations") Signed-off-by: Ryan Roberts Reviewed-by: Alexander Potapenko Tested-by: Alexander Potapenko Cc: Dmitriy Vyukov Cc: Dmitry Vyukov Cc: Marco Elver Cc: Ryan Roberts Cc: Signed-off-by: Andrew Morton Signed-off-by: Greg Kroah-Hartman --- mm/kmsan/shadow.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mm/kmsan/shadow.c b/mm/kmsan/shadow.c index 55fdea199aaf01..8bd50f5b1092e7 100644 --- a/mm/kmsan/shadow.c +++ b/mm/kmsan/shadow.c @@ -207,7 +207,7 @@ void kmsan_free_page(struct page *page, unsigned int order) if (!kmsan_enabled || kmsan_in_runtime()) return; kmsan_enter_runtime(); - kmsan_internal_poison_memory(page_address(page), page_size(page), + kmsan_internal_poison_memory(page_address(page), PAGE_SIZE << order, GFP_KERNEL & ~(__GFP_RECLAIM), KMSAN_POISON_CHECK | KMSAN_POISON_FREE); kmsan_leave_runtime(); From 2a0db14ff1bc7ecd32f581497fd3c9fdb8111ec3 Mon Sep 17 00:00:00 2001 From: Ben Dooks Date: Thu, 8 Jan 2026 10:15:39 +0000 Subject: [PATCH 2543/3902] mm: numa,memblock: include for 'numa_nodes_parsed' commit f46c26f1bcd9164d7f3377f15ca75488a3e44362 upstream. The 'numa_nodes_parsed' is defined in but this file is not included in mm/numa_memblks.c (build x86_64) so add this to the incldues to fix the following sparse warning: mm/numa_memblks.c:13:12: warning: symbol 'numa_nodes_parsed' was not declared. Should it be static? Link: https://lkml.kernel.org/r/20260108101539.229192-1-ben.dooks@codethink.co.uk Fixes: 87482708210f ("mm: introduce numa_memblks") Signed-off-by: Ben Dooks Reviewed-by: Mike Rapoport (Microsoft) Cc: Ben Dooks Cc: Signed-off-by: Andrew Morton Signed-off-by: Greg Kroah-Hartman --- mm/numa_memblks.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/mm/numa_memblks.c b/mm/numa_memblks.c index 5b009a9cd8b4c2..8f5735fda0a218 100644 --- a/mm/numa_memblks.c +++ b/mm/numa_memblks.c @@ -7,6 +7,8 @@ #include #include +#include + int numa_distance_cnt; static u8 *numa_distance; From 2ff7e1d125f79d8db48ecb2c5ff50c8a5cddc155 Mon Sep 17 00:00:00 2001 From: Pavel Butsykin Date: Wed, 31 Dec 2025 11:46:38 +0400 Subject: [PATCH 2544/3902] mm/zswap: fix error pointer free in zswap_cpu_comp_prepare() commit 590b13669b813d55844fecd9142c56abd567914d upstream. crypto_alloc_acomp_node() may return ERR_PTR(), but the fail path checks only for NULL and can pass an error pointer to crypto_free_acomp(). Use IS_ERR_OR_NULL() to only free valid acomp instances. Link: https://lkml.kernel.org/r/20251231074638.2564302-1-pbutsykin@cloudlinux.com Fixes: 779b9955f643 ("mm: zswap: move allocations during CPU init outside the lock") Signed-off-by: Pavel Butsykin Reviewed-by: SeongJae Park Acked-by: Yosry Ahmed Acked-by: Nhat Pham Cc: Johannes Weiner Cc: Chengming Zhou Cc: Signed-off-by: Andrew Morton Signed-off-by: Greg Kroah-Hartman --- mm/zswap.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mm/zswap.c b/mm/zswap.c index c1af782e54ec60..12fb0e3954129c 100644 --- a/mm/zswap.c +++ b/mm/zswap.c @@ -787,7 +787,7 @@ static int zswap_cpu_comp_prepare(unsigned int cpu, struct hlist_node *node) return 0; fail: - if (acomp) + if (!IS_ERR_OR_NULL(acomp)) crypto_free_acomp(acomp); kfree(buffer); return ret; From 0e8838c91e24ffd2862728b5ac287a6f7c7f9684 Mon Sep 17 00:00:00 2001 From: Aboorva Devarajan Date: Mon, 1 Dec 2025 11:30:09 +0530 Subject: [PATCH 2545/3902] mm/page_alloc: make percpu_pagelist_high_fraction reads lock-free commit b9efe36b5e3eb2e91aa3d706066428648af034fc upstream. When page isolation loops indefinitely during memory offline, reading /proc/sys/vm/percpu_pagelist_high_fraction blocks on pcp_batch_high_lock, causing hung task warnings. Make procfs reads lock-free since percpu_pagelist_high_fraction is a simple integer with naturally atomic reads, writers still serialize via the mutex. This prevents hung task warnings when reading the procfs file during long-running memory offline operations. [akpm@linux-foundation.org: add comment, per Michal] Link: https://lkml.kernel.org/r/aS_y9AuJQFydLEXo@tiehlicka Link: https://lkml.kernel.org/r/20251201060009.1420792-1-aboorvad@linux.ibm.com Signed-off-by: Aboorva Devarajan Acked-by: Michal Hocko Cc: Brendan Jackman Cc: Johannes Weiner Cc: Suren Baghdasaryan Cc: Vlastimil Babka Cc: Zi Yan Cc: Signed-off-by: Andrew Morton Signed-off-by: Greg Kroah-Hartman --- mm/page_alloc.c | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/mm/page_alloc.c b/mm/page_alloc.c index 6e644f2744c2d6..764b7aabaf69f3 100644 --- a/mm/page_alloc.c +++ b/mm/page_alloc.c @@ -6611,11 +6611,19 @@ static int percpu_pagelist_high_fraction_sysctl_handler(const struct ctl_table * int old_percpu_pagelist_high_fraction; int ret; + /* + * Avoid using pcp_batch_high_lock for reads as the value is read + * atomically and a race with offlining is harmless. + */ + + if (!write) + return proc_dointvec_minmax(table, write, buffer, length, ppos); + mutex_lock(&pcp_batch_high_lock); old_percpu_pagelist_high_fraction = percpu_pagelist_high_fraction; ret = proc_dointvec_minmax(table, write, buffer, length, ppos); - if (!write || ret < 0) + if (ret < 0) goto out; /* Sanity checking to avoid pcp imbalance */ From 23b061f421eef03647b512f3df48861706c87db3 Mon Sep 17 00:00:00 2001 From: SeongJae Park Date: Tue, 30 Dec 2025 17:23:13 -0800 Subject: [PATCH 2546/3902] mm/damon/core: remove call_control in inactive contexts commit f9132fbc2e83baf2c45a77043672a63a675c9394 upstream. If damon_call() is executed against a DAMON context that is not running, the function returns error while keeping the damon_call_control object linked to the context's call_controls list. Let's suppose the object is deallocated after the damon_call(), and yet another damon_call() is executed against the same context. The function tries to add the new damon_call_control object to the call_controls list, which still has the pointer to the previous damon_call_control object, which is deallocated. As a result, use-after-free happens. This can actually be triggered using the DAMON sysfs interface. It is not easily exploitable since it requires the sysfs write permission and making a definitely weird file writes, though. Please refer to the report for more details about the issue reproduction steps. Fix the issue by making two changes. Firstly, move the final kdamond_call() for cancelling all existing damon_call() requests from terminating DAMON context to be done before the ctx->kdamond reset. This makes any code that sees NULL ctx->kdamond can safely assume the context may not access damon_call() requests anymore. Secondly, let damon_call() to cleanup the damon_call_control objects that were added to the already-terminated DAMON context, before returning the error. Link: https://lkml.kernel.org/r/20251231012315.75835-1-sj@kernel.org Fixes: 004ded6bee11 ("mm/damon: accept parallel damon_call() requests") Signed-off-by: SeongJae Park Reported-by: JaeJoon Jung Closes: https://lore.kernel.org/20251224094401.20384-1-rgbi3307@gmail.com Cc: # 6.17.x Signed-off-by: Andrew Morton Signed-off-by: Greg Kroah-Hartman --- mm/damon/core.c | 33 +++++++++++++++++++++++++++++++-- 1 file changed, 31 insertions(+), 2 deletions(-) diff --git a/mm/damon/core.c b/mm/damon/core.c index 109b050c795adb..b787cdb07cb25d 100644 --- a/mm/damon/core.c +++ b/mm/damon/core.c @@ -1426,6 +1426,35 @@ bool damon_is_running(struct damon_ctx *ctx) return running; } +/* + * damon_call_handle_inactive_ctx() - handle DAMON call request that added to + * an inactive context. + * @ctx: The inactive DAMON context. + * @control: Control variable of the call request. + * + * This function is called in a case that @control is added to @ctx but @ctx is + * not running (inactive). See if @ctx handled @control or not, and cleanup + * @control if it was not handled. + * + * Returns 0 if @control was handled by @ctx, negative error code otherwise. + */ +static int damon_call_handle_inactive_ctx( + struct damon_ctx *ctx, struct damon_call_control *control) +{ + struct damon_call_control *c; + + mutex_lock(&ctx->call_controls_lock); + list_for_each_entry(c, &ctx->call_controls, list) { + if (c == control) { + list_del(&control->list); + mutex_unlock(&ctx->call_controls_lock); + return -EINVAL; + } + } + mutex_unlock(&ctx->call_controls_lock); + return 0; +} + /** * damon_call() - Invoke a given function on DAMON worker thread (kdamond). * @ctx: DAMON context to call the function for. @@ -1456,7 +1485,7 @@ int damon_call(struct damon_ctx *ctx, struct damon_call_control *control) list_add_tail(&control->list, &ctx->call_controls); mutex_unlock(&ctx->call_controls_lock); if (!damon_is_running(ctx)) - return -EINVAL; + return damon_call_handle_inactive_ctx(ctx, control); if (control->repeat) return 0; wait_for_completion(&control->completion); @@ -2704,13 +2733,13 @@ static int kdamond_fn(void *data) if (ctx->ops.cleanup) ctx->ops.cleanup(ctx); kfree(ctx->regions_score_histogram); + kdamond_call(ctx, true); pr_debug("kdamond (%d) finishes\n", current->pid); mutex_lock(&ctx->kdamond_lock); ctx->kdamond = NULL; mutex_unlock(&ctx->kdamond_lock); - kdamond_call(ctx, true); damos_walk_cancel(ctx); mutex_lock(&damon_lock); From 253b8f56667ff43826dc7236bdc0fc4aeff5f75e Mon Sep 17 00:00:00 2001 From: SeongJae Park Date: Wed, 24 Dec 2025 18:30:36 -0800 Subject: [PATCH 2547/3902] mm/damon/sysfs-scheme: cleanup quotas subdirs on scheme dir setup failure commit dc7e1d75fd8c505096d0cddeca9e2efb2b55aaf9 upstream. When a DAMOS-scheme DAMON sysfs directory setup fails after setup of quotas/ directory, subdirectories of quotas/ directory are not cleaned up. As a result, DAMON sysfs interface is nearly broken until the system reboots, and the memory for the unremoved directory is leaked. Cleanup the directories under such failures. Link: https://lkml.kernel.org/r/20251225023043.18579-4-sj@kernel.org Fixes: 1b32234ab087 ("mm/damon/sysfs: support DAMOS watermarks") Signed-off-by: SeongJae Park Cc: chongjiapeng Cc: # 5.18.x Signed-off-by: Andrew Morton Signed-off-by: Greg Kroah-Hartman --- mm/damon/sysfs-schemes.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/mm/damon/sysfs-schemes.c b/mm/damon/sysfs-schemes.c index 6536f16006c90f..2501380e26c357 100644 --- a/mm/damon/sysfs-schemes.c +++ b/mm/damon/sysfs-schemes.c @@ -2117,7 +2117,7 @@ static int damon_sysfs_scheme_add_dirs(struct damon_sysfs_scheme *scheme) goto put_dests_out; err = damon_sysfs_scheme_set_watermarks(scheme); if (err) - goto put_quotas_access_pattern_out; + goto rmdir_put_quotas_access_pattern_out; err = damos_sysfs_set_filter_dirs(scheme); if (err) goto put_watermarks_quotas_access_pattern_out; @@ -2142,7 +2142,8 @@ static int damon_sysfs_scheme_add_dirs(struct damon_sysfs_scheme *scheme) put_watermarks_quotas_access_pattern_out: kobject_put(&scheme->watermarks->kobj); scheme->watermarks = NULL; -put_quotas_access_pattern_out: +rmdir_put_quotas_access_pattern_out: + damon_sysfs_quotas_rm_dirs(scheme->quotas); kobject_put(&scheme->quotas->kobj); scheme->quotas = NULL; put_dests_out: From 725d4fdaa01bd1161782081f419e1568cc7432e0 Mon Sep 17 00:00:00 2001 From: SeongJae Park Date: Wed, 24 Dec 2025 18:30:37 -0800 Subject: [PATCH 2548/3902] mm/damon/sysfs-scheme: cleanup access_pattern subdirs on scheme dir setup failure commit 392b3d9d595f34877dd745b470c711e8ebcd225c upstream. When a DAMOS-scheme DAMON sysfs directory setup fails after setup of access_pattern/ directory, subdirectories of access_pattern/ directory are not cleaned up. As a result, DAMON sysfs interface is nearly broken until the system reboots, and the memory for the unremoved directory is leaked. Cleanup the directories under such failures. Link: https://lkml.kernel.org/r/20251225023043.18579-5-sj@kernel.org Fixes: 9bbb820a5bd5 ("mm/damon/sysfs: support DAMOS quotas") Signed-off-by: SeongJae Park Cc: chongjiapeng Cc: # 5.18.x Signed-off-by: Andrew Morton Signed-off-by: Greg Kroah-Hartman --- mm/damon/sysfs-schemes.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/mm/damon/sysfs-schemes.c b/mm/damon/sysfs-schemes.c index 2501380e26c357..50d000d61c9076 100644 --- a/mm/damon/sysfs-schemes.c +++ b/mm/damon/sysfs-schemes.c @@ -2111,7 +2111,7 @@ static int damon_sysfs_scheme_add_dirs(struct damon_sysfs_scheme *scheme) return err; err = damos_sysfs_set_dests(scheme); if (err) - goto put_access_pattern_out; + goto rmdir_put_access_pattern_out; err = damon_sysfs_scheme_set_quotas(scheme); if (err) goto put_dests_out; @@ -2149,7 +2149,8 @@ static int damon_sysfs_scheme_add_dirs(struct damon_sysfs_scheme *scheme) put_dests_out: kobject_put(&scheme->dests->kobj); scheme->dests = NULL; -put_access_pattern_out: +rmdir_put_access_pattern_out: + damon_sysfs_access_pattern_rm_dirs(scheme->access_pattern); kobject_put(&scheme->access_pattern->kobj); scheme->access_pattern = NULL; return err; From 9dc11b365e4eba41b5fe5808b7df2916283a5337 Mon Sep 17 00:00:00 2001 From: SeongJae Park Date: Wed, 24 Dec 2025 18:30:34 -0800 Subject: [PATCH 2549/3902] mm/damon/sysfs: cleanup intervals subdirs on attrs dir setup failure commit a24ca8ebb0cd5ea07a1462b77be0f0823c40f319 upstream. Patch series "mm/damon/sysfs: free setup failures generated zombie sub-sub dirs". Some DAMON sysfs directory setup functions generates its sub and sub-sub directories. For example, 'monitoring_attrs/' directory setup creates 'intervals/' and 'intervals/intervals_goal/' directories under 'monitoring_attrs/' directory. When such sub-sub directories are successfully made but followup setup is failed, the setup function should recursively clean up the subdirectories. However, such setup functions are only dereferencing sub directory reference counters. As a result, under certain setup failures, the sub-sub directories keep having non-zero reference counters. It means the directories cannot be removed like zombies, and the memory for the directories cannot be freed. The user impact of this issue is limited due to the following reasons. When the issue happens, the zombie directories are still taking the path. Hence attempts to generate the directories again will fail, without additional memory leak. This means the upper bound memory leak is limited. Nonetheless this also implies controlling DAMON with a feature that requires the setup-failed sysfs files will be impossible until the system reboots. Also, the setup operations are quite simple. The certain failures would hence only rarely happen, and are difficult to artificially trigger. This patch (of 4): When attrs/ DAMON sysfs directory setup is failed after setup of intervals/ directory, intervals/intervals_goal/ directory is not cleaned up. As a result, DAMON sysfs interface is nearly broken until the system reboots, and the memory for the unremoved directory is leaked. Cleanup the directory under such failures. Link: https://lkml.kernel.org/r/20251225023043.18579-1-sj@kernel.org Link: https://lkml.kernel.org/r/20251225023043.18579-2-sj@kernel.org Fixes: 8fbbcbeaafeb ("mm/damon/sysfs: implement intervals tuning goal directory") Signed-off-by: SeongJae Park Cc: chongjiapeng Cc: # 6.15.x Signed-off-by: Andrew Morton Signed-off-by: Greg Kroah-Hartman --- mm/damon/sysfs.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/mm/damon/sysfs.c b/mm/damon/sysfs.c index 3c0d727788c875..2b4920a722e495 100644 --- a/mm/damon/sysfs.c +++ b/mm/damon/sysfs.c @@ -764,7 +764,7 @@ static int damon_sysfs_attrs_add_dirs(struct damon_sysfs_attrs *attrs) nr_regions_range = damon_sysfs_ul_range_alloc(10, 1000); if (!nr_regions_range) { err = -ENOMEM; - goto put_intervals_out; + goto rmdir_put_intervals_out; } err = kobject_init_and_add(&nr_regions_range->kobj, @@ -778,6 +778,8 @@ static int damon_sysfs_attrs_add_dirs(struct damon_sysfs_attrs *attrs) put_nr_regions_intervals_out: kobject_put(&nr_regions_range->kobj); attrs->nr_regions_range = NULL; +rmdir_put_intervals_out: + damon_sysfs_intervals_rm_dirs(intervals); put_intervals_out: kobject_put(&intervals->kobj); attrs->intervals = NULL; From 78b4eb99751ebd37ceade78810bf94de80f7fb3a Mon Sep 17 00:00:00 2001 From: SeongJae Park Date: Wed, 24 Dec 2025 18:30:35 -0800 Subject: [PATCH 2550/3902] mm/damon/sysfs: cleanup attrs subdirs on context dir setup failure commit 9814cc832b88bd040fc2a1817c2b5469d0f7e862 upstream. When a context DAMON sysfs directory setup is failed after setup of attrs/ directory, subdirectories of attrs/ directory are not cleaned up. As a result, DAMON sysfs interface is nearly broken until the system reboots, and the memory for the unremoved directory is leaked. Cleanup the directories under such failures. Link: https://lkml.kernel.org/r/20251225023043.18579-3-sj@kernel.org Fixes: c951cd3b8901 ("mm/damon: implement a minimal stub for sysfs-based DAMON interface") Signed-off-by: SeongJae Park Cc: chongjiapeng Cc: # 5.18.x Signed-off-by: Andrew Morton Signed-off-by: Greg Kroah-Hartman --- mm/damon/sysfs.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/mm/damon/sysfs.c b/mm/damon/sysfs.c index 2b4920a722e495..2caeca5624ce8a 100644 --- a/mm/damon/sysfs.c +++ b/mm/damon/sysfs.c @@ -922,7 +922,7 @@ static int damon_sysfs_context_add_dirs(struct damon_sysfs_context *context) err = damon_sysfs_context_set_targets(context); if (err) - goto put_attrs_out; + goto rmdir_put_attrs_out; err = damon_sysfs_context_set_schemes(context); if (err) @@ -932,7 +932,8 @@ static int damon_sysfs_context_add_dirs(struct damon_sysfs_context *context) put_targets_attrs_out: kobject_put(&context->targets->kobj); context->targets = NULL; -put_attrs_out: +rmdir_put_attrs_out: + damon_sysfs_attrs_rm_dirs(context->attrs); kobject_put(&context->attrs->kobj); context->attrs = NULL; return err; From 5b14ce4975a02e67ffdc6d9359f823f9b83602cf Mon Sep 17 00:00:00 2001 From: Lisa Robinson Date: Sat, 17 Jan 2026 10:56:43 +0800 Subject: [PATCH 2551/3902] LoongArch: Fix PMU counter allocation for mixed-type event groups commit a91f86e27087f250a5d9c89bb4a427b9c30fd815 upstream. When validating a perf event group, validate_group() unconditionally attempts to allocate hardware PMU counters for the leader, sibling events and the new event being added. This is incorrect for mixed-type groups. If a PERF_TYPE_SOFTWARE event is part of the group, the current code still tries to allocate a hardware PMU counter for it, which can wrongly consume hardware PMU resources and cause spurious allocation failures. Fix this by only allocating PMU counters for hardware events during group validation, and skipping software events. A trimmed down reproducer is as simple as this: #include #include #include #include #include #include int main (int argc, char *argv[]) { struct perf_event_attr attr = { 0 }; int fds[5]; attr.disabled = 1; attr.exclude_kernel = 1; attr.exclude_hv = 1; attr.read_format = PERF_FORMAT_TOTAL_TIME_ENABLED | PERF_FORMAT_TOTAL_TIME_RUNNING | PERF_FORMAT_ID | PERF_FORMAT_GROUP; attr.size = sizeof (attr); attr.type = PERF_TYPE_SOFTWARE; attr.config = PERF_COUNT_SW_DUMMY; fds[0] = syscall (SYS_perf_event_open, &attr, 0, -1, -1, 0); assert (fds[0] >= 0); attr.type = PERF_TYPE_HARDWARE; attr.config = PERF_COUNT_HW_CPU_CYCLES; fds[1] = syscall (SYS_perf_event_open, &attr, 0, -1, fds[0], 0); assert (fds[1] >= 0); attr.type = PERF_TYPE_HARDWARE; attr.config = PERF_COUNT_HW_INSTRUCTIONS; fds[2] = syscall (SYS_perf_event_open, &attr, 0, -1, fds[0], 0); assert (fds[2] >= 0); attr.type = PERF_TYPE_HARDWARE; attr.config = PERF_COUNT_HW_BRANCH_MISSES; fds[3] = syscall (SYS_perf_event_open, &attr, 0, -1, fds[0], 0); assert (fds[3] >= 0); attr.type = PERF_TYPE_HARDWARE; attr.config = PERF_COUNT_HW_CACHE_REFERENCES; fds[4] = syscall (SYS_perf_event_open, &attr, 0, -1, fds[0], 0); assert (fds[4] >= 0); printf ("PASSED\n"); return 0; } Cc: stable@vger.kernel.org Fixes: b37042b2bb7c ("LoongArch: Add perf events support") Signed-off-by: Lisa Robinson Signed-off-by: Huacai Chen Signed-off-by: Greg Kroah-Hartman --- arch/loongarch/kernel/perf_event.c | 21 ++++++++++++++++++--- 1 file changed, 18 insertions(+), 3 deletions(-) diff --git a/arch/loongarch/kernel/perf_event.c b/arch/loongarch/kernel/perf_event.c index 9d257c8519c902..e34a6fb33e11c1 100644 --- a/arch/loongarch/kernel/perf_event.c +++ b/arch/loongarch/kernel/perf_event.c @@ -626,6 +626,18 @@ static const struct loongarch_perf_event *loongarch_pmu_map_cache_event(u64 conf return pev; } +static inline bool loongarch_pmu_event_requires_counter(const struct perf_event *event) +{ + switch (event->attr.type) { + case PERF_TYPE_HARDWARE: + case PERF_TYPE_HW_CACHE: + case PERF_TYPE_RAW: + return true; + default: + return false; + } +} + static int validate_group(struct perf_event *event) { struct cpu_hw_events fake_cpuc; @@ -633,15 +645,18 @@ static int validate_group(struct perf_event *event) memset(&fake_cpuc, 0, sizeof(fake_cpuc)); - if (loongarch_pmu_alloc_counter(&fake_cpuc, &leader->hw) < 0) + if (loongarch_pmu_event_requires_counter(leader) && + loongarch_pmu_alloc_counter(&fake_cpuc, &leader->hw) < 0) return -EINVAL; for_each_sibling_event(sibling, leader) { - if (loongarch_pmu_alloc_counter(&fake_cpuc, &sibling->hw) < 0) + if (loongarch_pmu_event_requires_counter(sibling) && + loongarch_pmu_alloc_counter(&fake_cpuc, &sibling->hw) < 0) return -EINVAL; } - if (loongarch_pmu_alloc_counter(&fake_cpuc, &event->hw) < 0) + if (loongarch_pmu_event_requires_counter(event) && + loongarch_pmu_alloc_counter(&fake_cpuc, &event->hw) < 0) return -EINVAL; return 0; From c1cc55ea5e956f60ff54a03c9712285f82737065 Mon Sep 17 00:00:00 2001 From: Yao Zi Date: Sat, 17 Jan 2026 10:56:52 +0800 Subject: [PATCH 2552/3902] LoongArch: dts: Describe PCI sideband IRQ through interrupt-extended commit 762cf75bec2ad9d17899087899a34336b1757238 upstream. SoC integrated peripherals on LS2K1000 and LS2K2000 could be discovered as PCI devices, but require sideband interrupts to function, which are previously described by interrupts and interrupt-parent properties. However, pci/pci-device.yaml allows interrupts property to only specify PCI INTx interrupts, not sideband ones. Convert these devices to use interrupt-extended property, which describes sideband interrupts used by PCI devices since dt-schema commit e6ea659d2baa ("schemas: pci-device: Allow interrupts-extended for sideband interrupts"), eliminating dtbs_check warnings. Cc: stable@vger.kernel.org Fixes: 30a5532a3206 ("LoongArch: dts: DeviceTree for Loongson-2K1000") Signed-off-by: Yao Zi Signed-off-by: Binbin Zhou Signed-off-by: Huacai Chen Signed-off-by: Greg Kroah-Hartman --- arch/loongarch/boot/dts/loongson-2k1000.dtsi | 25 ++++++--------- arch/loongarch/boot/dts/loongson-2k2000.dtsi | 32 ++++++++------------ 2 files changed, 21 insertions(+), 36 deletions(-) diff --git a/arch/loongarch/boot/dts/loongson-2k1000.dtsi b/arch/loongarch/boot/dts/loongson-2k1000.dtsi index d8e01e2534dde1..0f8a24d81f7c6a 100644 --- a/arch/loongarch/boot/dts/loongson-2k1000.dtsi +++ b/arch/loongarch/boot/dts/loongson-2k1000.dtsi @@ -437,54 +437,47 @@ gmac0: ethernet@3,0 { reg = <0x1800 0x0 0x0 0x0 0x0>; - interrupt-parent = <&liointc0>; - interrupts = <12 IRQ_TYPE_LEVEL_HIGH>, - <13 IRQ_TYPE_LEVEL_HIGH>; + interrupts-extended = <&liointc0 12 IRQ_TYPE_LEVEL_HIGH>, + <&liointc0 13 IRQ_TYPE_LEVEL_HIGH>; interrupt-names = "macirq", "eth_lpi"; status = "disabled"; }; gmac1: ethernet@3,1 { reg = <0x1900 0x0 0x0 0x0 0x0>; - interrupt-parent = <&liointc0>; - interrupts = <14 IRQ_TYPE_LEVEL_HIGH>, - <15 IRQ_TYPE_LEVEL_HIGH>; + interrupts-extended = <&liointc0 14 IRQ_TYPE_LEVEL_HIGH>, + <&liointc0 15 IRQ_TYPE_LEVEL_HIGH>; interrupt-names = "macirq", "eth_lpi"; status = "disabled"; }; ehci0: usb@4,1 { reg = <0x2100 0x0 0x0 0x0 0x0>; - interrupt-parent = <&liointc1>; - interrupts = <18 IRQ_TYPE_LEVEL_HIGH>; + interrupts-extended = <&liointc1 18 IRQ_TYPE_LEVEL_HIGH>; status = "disabled"; }; ohci0: usb@4,2 { reg = <0x2200 0x0 0x0 0x0 0x0>; - interrupt-parent = <&liointc1>; - interrupts = <19 IRQ_TYPE_LEVEL_HIGH>; + interrupts-extended = <&liointc1 19 IRQ_TYPE_LEVEL_HIGH>; status = "disabled"; }; display@6,0 { reg = <0x3000 0x0 0x0 0x0 0x0>; - interrupt-parent = <&liointc0>; - interrupts = <28 IRQ_TYPE_LEVEL_HIGH>; + interrupts-extended = <&liointc0 28 IRQ_TYPE_LEVEL_HIGH>; status = "disabled"; }; hda@7,0 { reg = <0x3800 0x0 0x0 0x0 0x0>; - interrupt-parent = <&liointc0>; - interrupts = <4 IRQ_TYPE_LEVEL_HIGH>; + interrupts-extended = <&liointc0 4 IRQ_TYPE_LEVEL_HIGH>; status = "disabled"; }; sata: sata@8,0 { reg = <0x4000 0x0 0x0 0x0 0x0>; - interrupt-parent = <&liointc0>; - interrupts = <19 IRQ_TYPE_LEVEL_HIGH>; + interrupts-extended = <&liointc0 19 IRQ_TYPE_LEVEL_HIGH>; status = "disabled"; }; diff --git a/arch/loongarch/boot/dts/loongson-2k2000.dtsi b/arch/loongarch/boot/dts/loongson-2k2000.dtsi index 00cc485b753b1c..18b192d8c93ce6 100644 --- a/arch/loongarch/boot/dts/loongson-2k2000.dtsi +++ b/arch/loongarch/boot/dts/loongson-2k2000.dtsi @@ -291,65 +291,57 @@ gmac0: ethernet@3,0 { reg = <0x1800 0x0 0x0 0x0 0x0>; - interrupts = <12 IRQ_TYPE_LEVEL_HIGH>, - <13 IRQ_TYPE_LEVEL_HIGH>; + interrupts-extended = <&pic 12 IRQ_TYPE_LEVEL_HIGH>, + <&pic 13 IRQ_TYPE_LEVEL_HIGH>; interrupt-names = "macirq", "eth_lpi"; - interrupt-parent = <&pic>; status = "disabled"; }; gmac1: ethernet@3,1 { reg = <0x1900 0x0 0x0 0x0 0x0>; - interrupts = <14 IRQ_TYPE_LEVEL_HIGH>, - <15 IRQ_TYPE_LEVEL_HIGH>; + interrupts-extended = <&pic 14 IRQ_TYPE_LEVEL_HIGH>, + <&pic 15 IRQ_TYPE_LEVEL_HIGH>; interrupt-names = "macirq", "eth_lpi"; - interrupt-parent = <&pic>; status = "disabled"; }; gmac2: ethernet@3,2 { reg = <0x1a00 0x0 0x0 0x0 0x0>; - interrupts = <17 IRQ_TYPE_LEVEL_HIGH>, - <18 IRQ_TYPE_LEVEL_HIGH>; + interrupts-extended = <&pic 17 IRQ_TYPE_LEVEL_HIGH>, + <&pic 18 IRQ_TYPE_LEVEL_HIGH>; interrupt-names = "macirq", "eth_lpi"; - interrupt-parent = <&pic>; status = "disabled"; }; xhci0: usb@4,0 { reg = <0x2000 0x0 0x0 0x0 0x0>; - interrupts = <48 IRQ_TYPE_LEVEL_HIGH>; - interrupt-parent = <&pic>; + interrupts-extended = <&pic 48 IRQ_TYPE_LEVEL_HIGH>; status = "disabled"; }; xhci1: usb@19,0 { reg = <0xc800 0x0 0x0 0x0 0x0>; - interrupts = <22 IRQ_TYPE_LEVEL_HIGH>; - interrupt-parent = <&pic>; + interrupts-extended = <&pic 22 IRQ_TYPE_LEVEL_HIGH>; status = "disabled"; }; display@6,1 { reg = <0x3100 0x0 0x0 0x0 0x0>; - interrupts = <28 IRQ_TYPE_LEVEL_HIGH>; - interrupt-parent = <&pic>; + interrupts-extended = <&pic 28 IRQ_TYPE_LEVEL_HIGH>; status = "disabled"; }; i2s@7,0 { reg = <0x3800 0x0 0x0 0x0 0x0>; - interrupts = <78 IRQ_TYPE_LEVEL_HIGH>, - <79 IRQ_TYPE_LEVEL_HIGH>; + interrupts-extended = <&pic 78 IRQ_TYPE_LEVEL_HIGH>, + <&pic 79 IRQ_TYPE_LEVEL_HIGH>; interrupt-names = "tx", "rx"; - interrupt-parent = <&pic>; status = "disabled"; }; sata: sata@8,0 { reg = <0x4000 0x0 0x0 0x0 0x0>; - interrupts = <16 IRQ_TYPE_LEVEL_HIGH>; - interrupt-parent = <&pic>; + interrupts-extended = <&pic 16 IRQ_TYPE_LEVEL_HIGH>; status = "disabled"; }; From 26ed15a42b743c5a326834c4ad2c989a9a59bdbd Mon Sep 17 00:00:00 2001 From: Mario Limonciello Date: Mon, 15 Dec 2025 14:08:30 -0600 Subject: [PATCH 2553/3902] drm/amd/display: Bump the HDMI clock to 340MHz commit fee50077656d8a58011f13bca48f743d1b6d6015 upstream. [Why] DP-HDMI dongles can execeed bandwidth requirements on high resolution monitors. This can lead to pruning the high resolution modes. HDMI 1.3 bumped the clock to 340MHz, but display code never matched it. [How] Set default to (DVI) 165MHz. Once HDMI display is identified update to 340MHz. Reported-by: Dianne Skoll Closes: https://gitlab.freedesktop.org/drm/amd/-/issues/4780 Reviewed-by: Chris Park Signed-off-by: Mario Limonciello Signed-off-by: Matthew Stewart Tested-by: Dan Wheeler Signed-off-by: Alex Deucher (cherry picked from commit ac1e65d8ade46c09fb184579b81acadf36dcb91e) Cc: stable@vger.kernel.org Signed-off-by: Greg Kroah-Hartman --- drivers/gpu/drm/amd/display/dc/dc_hdmi_types.h | 2 +- drivers/gpu/drm/amd/display/dc/link/link_detection.c | 4 +++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/drivers/gpu/drm/amd/display/dc/dc_hdmi_types.h b/drivers/gpu/drm/amd/display/dc/dc_hdmi_types.h index b015e80672ec9b..fcd3ab4b004594 100644 --- a/drivers/gpu/drm/amd/display/dc/dc_hdmi_types.h +++ b/drivers/gpu/drm/amd/display/dc/dc_hdmi_types.h @@ -41,7 +41,7 @@ /* kHZ*/ #define DP_ADAPTOR_DVI_MAX_TMDS_CLK 165000 /* kHZ*/ -#define DP_ADAPTOR_HDMI_SAFE_MAX_TMDS_CLK 165000 +#define DP_ADAPTOR_HDMI_SAFE_MAX_TMDS_CLK 340000 struct dp_hdmi_dongle_signature_data { int8_t id[15];/* "DP-HDMI ADAPTOR"*/ diff --git a/drivers/gpu/drm/amd/display/dc/link/link_detection.c b/drivers/gpu/drm/amd/display/dc/link/link_detection.c index 1173c53359b009..31b404e9b9ae07 100644 --- a/drivers/gpu/drm/amd/display/dc/link/link_detection.c +++ b/drivers/gpu/drm/amd/display/dc/link/link_detection.c @@ -332,7 +332,7 @@ static void query_dp_dual_mode_adaptor( /* Assume we have no valid DP passive dongle connected */ *dongle = DISPLAY_DONGLE_NONE; - sink_cap->max_hdmi_pixel_clock = DP_ADAPTOR_HDMI_SAFE_MAX_TMDS_CLK; + sink_cap->max_hdmi_pixel_clock = DP_ADAPTOR_DVI_MAX_TMDS_CLK; /* Read DP-HDMI dongle I2c (no response interpreted as DP-DVI dongle)*/ if (!i2c_read( @@ -388,6 +388,8 @@ static void query_dp_dual_mode_adaptor( } } + if (is_valid_hdmi_signature) + sink_cap->max_hdmi_pixel_clock = DP_ADAPTOR_HDMI_SAFE_MAX_TMDS_CLK; if (is_type2_dongle) { uint32_t max_tmds_clk = From 568c29c7c7c830097ffc0b03a68af022380a54e5 Mon Sep 17 00:00:00 2001 From: Vivek Das Mohapatra Date: Mon, 12 Jan 2026 15:28:56 +0000 Subject: [PATCH 2554/3902] drm/amd/display: Initialise backlight level values from hw commit 52d3d115e9cc975b90b1fc49abf6d36ad5e8847a upstream. Internal backlight levels are initialised from ACPI but the values are sometimes out of sync with the levels in effect until there has been a read from hardware (eg triggered by reading from sysfs). This means that the first drm_commit can cause the levels to be set to a different value than the actual starting one, which results in a sudden change in brightness. This path shows the problem (when the values are out of sync): amdgpu_dm_atomic_commit_tail() -> amdgpu_dm_commit_streams() -> amdgpu_dm_backlight_set_level(..., dm->brightness[n]) This patch calls the backlight ops get_brightness explicitly at the end of backlight registration to make sure dm->brightness[n] is in sync with the actual hardware levels. Fixes: 2fe87f54abdc ("drm/amd/display: Set default brightness according to ACPI") Signed-off-by: Vivek Das Mohapatra Reviewed-by: Mario Limonciello (AMD) Signed-off-by: Alex Deucher (cherry picked from commit 318b1c36d82a0cd2b06a4bb43272fa6f1bc8adc1) Cc: stable@vger.kernel.org Signed-off-by: Greg Kroah-Hartman --- .../gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c index f4381d44864f16..6252afd1d087fd 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c @@ -5193,6 +5193,8 @@ amdgpu_dm_register_backlight_device(struct amdgpu_dm_connector *aconnector) struct amdgpu_dm_backlight_caps *caps; char bl_name[16]; int min, max; + int real_brightness; + int init_brightness; if (aconnector->bl_idx == -1) return; @@ -5217,6 +5219,8 @@ amdgpu_dm_register_backlight_device(struct amdgpu_dm_connector *aconnector) } else props.brightness = props.max_brightness = MAX_BACKLIGHT_LEVEL; + init_brightness = props.brightness; + if (caps->data_points && !(amdgpu_dc_debug_mask & DC_DISABLE_CUSTOM_BRIGHTNESS_CURVE)) { drm_info(drm, "Using custom brightness curve\n"); props.scale = BACKLIGHT_SCALE_NON_LINEAR; @@ -5235,8 +5239,20 @@ amdgpu_dm_register_backlight_device(struct amdgpu_dm_connector *aconnector) if (IS_ERR(dm->backlight_dev[aconnector->bl_idx])) { drm_err(drm, "DM: Backlight registration failed!\n"); dm->backlight_dev[aconnector->bl_idx] = NULL; - } else + } else { + /* + * dm->brightness[x] can be inconsistent just after startup until + * ops.get_brightness is called. + */ + real_brightness = + amdgpu_dm_backlight_ops.get_brightness(dm->backlight_dev[aconnector->bl_idx]); + + if (real_brightness != init_brightness) { + dm->actual_brightness[aconnector->bl_idx] = real_brightness; + dm->brightness[aconnector->bl_idx] = real_brightness; + } drm_dbg_driver(drm, "DM: Registered Backlight device: %s\n", bl_name); + } } static int initialize_plane(struct amdgpu_display_manager *dm, From b705daaf5f8c4ebcd5f963a1c98b159b5a1b103f Mon Sep 17 00:00:00 2001 From: "Mario Limonciello (AMD)" Date: Wed, 7 Jan 2026 15:37:28 -0600 Subject: [PATCH 2555/3902] drm/amd: Clean up kfd node on surprise disconnect commit 28695ca09d326461f8078332aa01db516983e8a2 upstream. When an eGPU is unplugged the KFD topology should also be destroyed for that GPU. This never happens because the fini_sw callbacks never get to run. Run them manually before calling amdgpu_device_ip_fini_early() when a device has already been disconnected. This location is intentionally chosen to make sure that the kfd locking refcount doesn't get incremented unintentionally. Cc: kent.russell@amd.com Closes: https://community.frame.work/t/amd-egpu-on-linux/8691/33 Signed-off-by: Mario Limonciello (AMD) Reviewed-by: Kent Russell Signed-off-by: Alex Deucher (cherry picked from commit 6a23e7b4332c10f8b56c33a9c5431b52ecff9aab) Cc: stable@vger.kernel.org Signed-off-by: Greg Kroah-Hartman --- drivers/gpu/drm/amd/amdgpu/amdgpu_device.c | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_device.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_device.c index 0cba31ec025c07..49107475af619e 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_device.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_device.c @@ -4985,6 +4985,14 @@ void amdgpu_device_fini_hw(struct amdgpu_device *adev) amdgpu_ttm_set_buffer_funcs_status(adev, false); + /* + * device went through surprise hotplug; we need to destroy topology + * before ip_fini_early to prevent kfd locking refcount issues by calling + * amdgpu_amdkfd_suspend() + */ + if (drm_dev_is_unplugged(adev_to_drm(adev))) + amdgpu_amdkfd_device_fini_sw(adev); + amdgpu_device_ip_fini_early(adev); amdgpu_irq_fini_hw(adev); From 531b1b83cfa0b8edefad8080d053f7f26877e988 Mon Sep 17 00:00:00 2001 From: Philip Yang Date: Thu, 4 Dec 2025 12:13:05 -0500 Subject: [PATCH 2556/3902] drm/amdgpu: Fix gfx9 update PTE mtype flag MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit commit 292e5757b2229c0c6f1d059123a85f8a28f4464d upstream. Fix copy&paste error, that should have been an assignment instead of an or, otherwise MTYPE_UC 0x3 can not be updated to MTYPE_RW 0x1. Signed-off-by: Philip Yang Reviewed-by: Christian König Signed-off-by: Alex Deucher (cherry picked from commit fc1366016abe4103c0f0fac882811aea961ef213) Cc: stable@vger.kernel.org Signed-off-by: Greg Kroah-Hartman --- drivers/gpu/drm/amd/amdgpu/gmc_v9_0.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/drivers/gpu/drm/amd/amdgpu/gmc_v9_0.c b/drivers/gpu/drm/amd/amdgpu/gmc_v9_0.c index 0d1dd587db5f49..fd9485e9dd1931 100644 --- a/drivers/gpu/drm/amd/amdgpu/gmc_v9_0.c +++ b/drivers/gpu/drm/amd/amdgpu/gmc_v9_0.c @@ -1233,16 +1233,16 @@ static void gmc_v9_0_get_vm_pte(struct amdgpu_device *adev, *flags = AMDGPU_PTE_MTYPE_VG10(*flags, MTYPE_NC); break; case AMDGPU_VM_MTYPE_WC: - *flags |= AMDGPU_PTE_MTYPE_VG10(*flags, MTYPE_WC); + *flags = AMDGPU_PTE_MTYPE_VG10(*flags, MTYPE_WC); break; case AMDGPU_VM_MTYPE_RW: - *flags |= AMDGPU_PTE_MTYPE_VG10(*flags, MTYPE_RW); + *flags = AMDGPU_PTE_MTYPE_VG10(*flags, MTYPE_RW); break; case AMDGPU_VM_MTYPE_CC: - *flags |= AMDGPU_PTE_MTYPE_VG10(*flags, MTYPE_CC); + *flags = AMDGPU_PTE_MTYPE_VG10(*flags, MTYPE_CC); break; case AMDGPU_VM_MTYPE_UC: - *flags |= AMDGPU_PTE_MTYPE_VG10(*flags, MTYPE_UC); + *flags = AMDGPU_PTE_MTYPE_VG10(*flags, MTYPE_UC); break; } From 0080a3f3fd917327c36f9a4f2660b693004c21fc Mon Sep 17 00:00:00 2001 From: Alex Deucher Date: Fri, 9 Jan 2026 08:54:55 -0500 Subject: [PATCH 2557/3902] drm/amdgpu: make sure userqs are enabled in userq IOCTLs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit commit b6dff005fcf32dd072f6f2d08ca461394a21bd4f upstream. These IOCTLs shouldn't be called when userqs are not enabled. Make sure they are enabled before executing the IOCTLs. Reviewed-by: Christian König Signed-off-by: Alex Deucher (cherry picked from commit d967509651601cddce7ff2a9f09479f3636f684d) Cc: stable@vger.kernel.org Signed-off-by: Greg Kroah-Hartman --- drivers/gpu/drm/amd/amdgpu/amdgpu_userq.c | 16 ++++++++++++++++ drivers/gpu/drm/amd/amdgpu/amdgpu_userq.h | 1 + drivers/gpu/drm/amd/amdgpu/amdgpu_userq_fence.c | 6 ++++++ 3 files changed, 23 insertions(+) diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_userq.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_userq.c index 79c7fa0a9ff7bb..8c41951feb437a 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_userq.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_userq.c @@ -681,12 +681,28 @@ static int amdgpu_userq_input_args_validate(struct drm_device *dev, return 0; } +bool amdgpu_userq_enabled(struct drm_device *dev) +{ + struct amdgpu_device *adev = drm_to_adev(dev); + int i; + + for (i = 0; i < AMDGPU_HW_IP_NUM; i++) { + if (adev->userq_funcs[i]) + return true; + } + + return false; +} + int amdgpu_userq_ioctl(struct drm_device *dev, void *data, struct drm_file *filp) { union drm_amdgpu_userq *args = data; int r; + if (!amdgpu_userq_enabled(dev)) + return -ENOTSUPP; + if (amdgpu_userq_input_args_validate(dev, args, filp) < 0) return -EINVAL; diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_userq.h b/drivers/gpu/drm/amd/amdgpu/amdgpu_userq.h index dbc13a807ca821..d78532f9d507b9 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_userq.h +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_userq.h @@ -135,6 +135,7 @@ uint64_t amdgpu_userq_get_doorbell_index(struct amdgpu_userq_mgr *uq_mgr, struct drm_file *filp); u32 amdgpu_userq_get_supported_ip_mask(struct amdgpu_device *adev); +bool amdgpu_userq_enabled(struct drm_device *dev); int amdgpu_userq_suspend(struct amdgpu_device *adev); int amdgpu_userq_resume(struct amdgpu_device *adev); diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_userq_fence.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_userq_fence.c index 53fe10931fab04..5c181ac75d548f 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_userq_fence.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_userq_fence.c @@ -472,6 +472,9 @@ int amdgpu_userq_signal_ioctl(struct drm_device *dev, void *data, struct drm_exec exec; u64 wptr; + if (!amdgpu_userq_enabled(dev)) + return -ENOTSUPP; + num_syncobj_handles = args->num_syncobj_handles; syncobj_handles = memdup_user(u64_to_user_ptr(args->syncobj_handles), size_mul(sizeof(u32), num_syncobj_handles)); @@ -654,6 +657,9 @@ int amdgpu_userq_wait_ioctl(struct drm_device *dev, void *data, int r, i, rentry, wentry, cnt; struct drm_exec exec; + if (!amdgpu_userq_enabled(dev)) + return -ENOTSUPP; + num_read_bo_handles = wait_info->num_bo_read_handles; bo_handles_read = memdup_user(u64_to_user_ptr(wait_info->bo_read_handles), size_mul(sizeof(u32), num_read_bo_handles)); From a92ef24071f395accb28a5cc7bf1223d38c50371 Mon Sep 17 00:00:00 2001 From: Haoxiang Li Date: Thu, 8 Jan 2026 15:18:22 +0800 Subject: [PATCH 2558/3902] drm/amdkfd: fix a memory leak in device_queue_manager_init() commit 80614c509810fc051312d1a7ccac8d0012d6b8d0 upstream. If dqm->ops.initialize() fails, add deallocate_hiq_sdma_mqd() to release the memory allocated by allocate_hiq_sdma_mqd(). Move deallocate_hiq_sdma_mqd() up to ensure proper function visibility at the point of use. Fixes: 11614c36bc8f ("drm/amdkfd: Allocate MQD trunk for HIQ and SDMA") Signed-off-by: Haoxiang Li Signed-off-by: Felix Kuehling Reviewed-by: Oak Zeng Reviewed-by: Felix Kuehling Signed-off-by: Alex Deucher (cherry picked from commit b7cccc8286bb9919a0952c812872da1dcfe9d390) Cc: stable@vger.kernel.org Signed-off-by: Greg Kroah-Hartman --- .../drm/amd/amdkfd/kfd_device_queue_manager.c | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/drivers/gpu/drm/amd/amdkfd/kfd_device_queue_manager.c b/drivers/gpu/drm/amd/amdkfd/kfd_device_queue_manager.c index 36fb3db16572a4..58c5acf50a2204 100644 --- a/drivers/gpu/drm/amd/amdkfd/kfd_device_queue_manager.c +++ b/drivers/gpu/drm/amd/amdkfd/kfd_device_queue_manager.c @@ -2905,6 +2905,14 @@ static int allocate_hiq_sdma_mqd(struct device_queue_manager *dqm) return retval; } +static void deallocate_hiq_sdma_mqd(struct kfd_node *dev, + struct kfd_mem_obj *mqd) +{ + WARN(!mqd, "No hiq sdma mqd trunk to free"); + + amdgpu_amdkfd_free_gtt_mem(dev->adev, &mqd->gtt_mem); +} + struct device_queue_manager *device_queue_manager_init(struct kfd_node *dev) { struct device_queue_manager *dqm; @@ -3028,19 +3036,14 @@ struct device_queue_manager *device_queue_manager_init(struct kfd_node *dev) return dqm; } + if (!dev->kfd->shared_resources.enable_mes) + deallocate_hiq_sdma_mqd(dev, &dqm->hiq_sdma_mqd); + out_free: kfree(dqm); return NULL; } -static void deallocate_hiq_sdma_mqd(struct kfd_node *dev, - struct kfd_mem_obj *mqd) -{ - WARN(!mqd, "No hiq sdma mqd trunk to free"); - - amdgpu_amdkfd_free_gtt_mem(dev->adev, &mqd->gtt_mem); -} - void device_queue_manager_uninit(struct device_queue_manager *dqm) { dqm->ops.stop(dqm); From d4373630fd3314b1d06c4f793504e3c8ba35dba6 Mon Sep 17 00:00:00 2001 From: Lyude Paul Date: Fri, 19 Dec 2025 16:52:02 -0500 Subject: [PATCH 2559/3902] drm/nouveau/disp/nv50-: Set lock_core in curs507a_prepare commit 9e9bc6be0fa0b6b6b73f4f831f3b77716d0a8d9e upstream. For a while, I've been seeing a strange issue where some (usually not all) of the display DMA channels will suddenly hang, particularly when there is a visible cursor on the screen that is being frequently updated, and especially when said cursor happens to go between two screens. While this brings back lovely memories of fixing Intel Skylake bugs, I would quite like to fix it :). It turns out the problem that's happening here is that we're managing to reach nv50_head_flush_set() in our atomic commit path without actually holding nv50_disp->mutex. This means that cursor updates happening in parallel (along with any other atomic updates that need to use the core channel) will race with eachother, which eventually causes us to corrupt the pushbuffer - leading to a plethora of various GSP errors, usually: nouveau 0000:c1:00.0: gsp: Xid:56 CMDre 00000000 00000218 00102680 00000004 00800003 nouveau 0000:c1:00.0: gsp: Xid:56 CMDre 00000000 0000021c 00040509 00000004 00000001 nouveau 0000:c1:00.0: gsp: Xid:56 CMDre 00000000 00000000 00000000 00000001 00000001 The reason this is happening is because generally we check whether we need to set nv50_atom->lock_core at the end of nv50_head_atomic_check(). However, curs507a_prepare is called from the fb_prepare callback, which happens after the atomic check phase. As a result, this can lead to commits that both touch the core channel but also don't grab nv50_disp->mutex. So, fix this by making sure that we set nv50_atom->lock_core in cus507a_prepare(). Reviewed-by: Dave Airlie Signed-off-by: Lyude Paul Fixes: 1590700d94ac ("drm/nouveau/kms/nv50-: split each resource type into their own source files") Cc: # v4.18+ Link: https://patch.msgid.link/20251219215344.170852-2-lyude@redhat.com Signed-off-by: Greg Kroah-Hartman --- drivers/gpu/drm/nouveau/dispnv50/curs507a.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/gpu/drm/nouveau/dispnv50/curs507a.c b/drivers/gpu/drm/nouveau/dispnv50/curs507a.c index a95ee5dcc2e394..1a889139cb0530 100644 --- a/drivers/gpu/drm/nouveau/dispnv50/curs507a.c +++ b/drivers/gpu/drm/nouveau/dispnv50/curs507a.c @@ -84,6 +84,7 @@ curs507a_prepare(struct nv50_wndw *wndw, struct nv50_head_atom *asyh, asyh->curs.handle = handle; asyh->curs.offset = offset; asyh->set.curs = asyh->curs.visible; + nv50_atom(asyh->state.state)->lock_core = true; } } From f7940d3ec1dc6bf719eddc69d4b8e52cc2201896 Mon Sep 17 00:00:00 2001 From: Marek Vasut Date: Sat, 10 Jan 2026 16:27:28 +0100 Subject: [PATCH 2560/3902] drm/panel-simple: fix connector type for DataImage SCF0700C48GGU18 panel commit 6ab3d4353bf75005eaa375677c9fed31148154d6 upstream. The connector type for the DataImage SCF0700C48GGU18 panel is missing and devm_drm_panel_bridge_add() requires connector type to be set. This leads to a warning and a backtrace in the kernel log and panel does not work: " WARNING: CPU: 3 PID: 38 at drivers/gpu/drm/bridge/panel.c:379 devm_drm_of_get_bridge+0xac/0xb8 " The warning is triggered by a check for valid connector type in devm_drm_panel_bridge_add(). If there is no valid connector type set for a panel, the warning is printed and panel is not added. Fill in the missing connector type to fix the warning and make the panel operational once again. Cc: stable@vger.kernel.org Fixes: 97ceb1fb08b6 ("drm/panel: simple: Add support for DataImage SCF0700C48GGU18") Signed-off-by: Marek Vasut Reviewed-by: Neil Armstrong Signed-off-by: Neil Armstrong Link: https://patch.msgid.link/20260110152750.73848-1-marex@nabladev.com Signed-off-by: Greg Kroah-Hartman --- drivers/gpu/drm/panel/panel-simple.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/gpu/drm/panel/panel-simple.c b/drivers/gpu/drm/panel/panel-simple.c index 0019de93be1b66..a0010c31d882d1 100644 --- a/drivers/gpu/drm/panel/panel-simple.c +++ b/drivers/gpu/drm/panel/panel-simple.c @@ -1900,6 +1900,7 @@ static const struct panel_desc dataimage_scf0700c48ggu18 = { }, .bus_format = MEDIA_BUS_FMT_RGB888_1X24, .bus_flags = DRM_BUS_FLAG_DE_HIGH | DRM_BUS_FLAG_PIXDATA_DRIVE_POSEDGE, + .connector_type = DRM_MODE_CONNECTOR_DPI, }; static const struct display_timing dlc_dlc0700yzg_1_timing = { From 9c676c7a054bcc7764357024eb8a83d7695d799d Mon Sep 17 00:00:00 2001 From: Ludovic Desroches Date: Thu, 18 Dec 2025 14:34:43 +0100 Subject: [PATCH 2561/3902] drm/panel: simple: restore connector_type fallback commit 9380dc33cd6ae4a6857818fcefce31cf716f3fae upstream. The switch from devm_kzalloc() + drm_panel_init() to devm_drm_panel_alloc() introduced a regression. Several panel descriptors do not set connector_type. For those panels, panel_simple_probe() used to compute a connector type (currently DPI as a fallback) and pass that value to drm_panel_init(). After the conversion to devm_drm_panel_alloc(), the call unconditionally used desc->connector_type instead, ignoring the computed fallback and potentially passing DRM_MODE_CONNECTOR_Unknown, which drm_panel_bridge_add() does not allow. Move the connector_type validation / fallback logic before the devm_drm_panel_alloc() call and pass the computed connector_type to devm_drm_panel_alloc(), so panels without an explicit connector_type once again get the DPI default. Signed-off-by: Ludovic Desroches Fixes: de04bb0089a9 ("drm/panel/panel-simple: Use the new allocation in place of devm_kzalloc()") Cc: stable@vger.kernel.org Reviewed-by: Luca Ceresoli Link: https://lore.kernel.org/stable/20251126-lcd_panel_connector_type_fix-v2-1-c15835d1f7cb%40microchip.com Signed-off-by: Neil Armstrong Link: https://patch.msgid.link/20251218-lcd_panel_connector_type_fix-v3-1-ddcea6d8d7ef@microchip.com Signed-off-by: Greg Kroah-Hartman --- drivers/gpu/drm/panel/panel-simple.c | 89 ++++++++++++++-------------- 1 file changed, 44 insertions(+), 45 deletions(-) diff --git a/drivers/gpu/drm/panel/panel-simple.c b/drivers/gpu/drm/panel/panel-simple.c index a0010c31d882d1..271f9339919376 100644 --- a/drivers/gpu/drm/panel/panel-simple.c +++ b/drivers/gpu/drm/panel/panel-simple.c @@ -623,49 +623,6 @@ static struct panel_simple *panel_simple_probe(struct device *dev) if (IS_ERR(desc)) return ERR_CAST(desc); - panel = devm_drm_panel_alloc(dev, struct panel_simple, base, - &panel_simple_funcs, desc->connector_type); - if (IS_ERR(panel)) - return ERR_CAST(panel); - - panel->desc = desc; - - panel->supply = devm_regulator_get(dev, "power"); - if (IS_ERR(panel->supply)) - return ERR_CAST(panel->supply); - - panel->enable_gpio = devm_gpiod_get_optional(dev, "enable", - GPIOD_OUT_LOW); - if (IS_ERR(panel->enable_gpio)) - return dev_err_cast_probe(dev, panel->enable_gpio, - "failed to request GPIO\n"); - - err = of_drm_get_panel_orientation(dev->of_node, &panel->orientation); - if (err) { - dev_err(dev, "%pOF: failed to get orientation %d\n", dev->of_node, err); - return ERR_PTR(err); - } - - ddc = of_parse_phandle(dev->of_node, "ddc-i2c-bus", 0); - if (ddc) { - panel->ddc = of_find_i2c_adapter_by_node(ddc); - of_node_put(ddc); - - if (!panel->ddc) - return ERR_PTR(-EPROBE_DEFER); - } - - if (!of_device_is_compatible(dev->of_node, "panel-dpi") && - !of_get_display_timing(dev->of_node, "panel-timing", &dt)) - panel_simple_parse_panel_timing_node(dev, panel, &dt); - - if (desc->connector_type == DRM_MODE_CONNECTOR_LVDS) { - /* Optional data-mapping property for overriding bus format */ - err = panel_simple_override_nondefault_lvds_datamapping(dev, panel); - if (err) - goto free_ddc; - } - connector_type = desc->connector_type; /* Catch common mistakes for panels. */ switch (connector_type) { @@ -690,8 +647,7 @@ static struct panel_simple *panel_simple_probe(struct device *dev) break; case DRM_MODE_CONNECTOR_eDP: dev_warn(dev, "eDP panels moved to panel-edp\n"); - err = -EINVAL; - goto free_ddc; + return ERR_PTR(-EINVAL); case DRM_MODE_CONNECTOR_DSI: if (desc->bpc != 6 && desc->bpc != 8) dev_warn(dev, "Expected bpc in {6,8} but got: %u\n", desc->bpc); @@ -720,6 +676,49 @@ static struct panel_simple *panel_simple_probe(struct device *dev) break; } + panel = devm_drm_panel_alloc(dev, struct panel_simple, base, + &panel_simple_funcs, connector_type); + if (IS_ERR(panel)) + return ERR_CAST(panel); + + panel->desc = desc; + + panel->supply = devm_regulator_get(dev, "power"); + if (IS_ERR(panel->supply)) + return ERR_CAST(panel->supply); + + panel->enable_gpio = devm_gpiod_get_optional(dev, "enable", + GPIOD_OUT_LOW); + if (IS_ERR(panel->enable_gpio)) + return dev_err_cast_probe(dev, panel->enable_gpio, + "failed to request GPIO\n"); + + err = of_drm_get_panel_orientation(dev->of_node, &panel->orientation); + if (err) { + dev_err(dev, "%pOF: failed to get orientation %d\n", dev->of_node, err); + return ERR_PTR(err); + } + + ddc = of_parse_phandle(dev->of_node, "ddc-i2c-bus", 0); + if (ddc) { + panel->ddc = of_find_i2c_adapter_by_node(ddc); + of_node_put(ddc); + + if (!panel->ddc) + return ERR_PTR(-EPROBE_DEFER); + } + + if (!of_device_is_compatible(dev->of_node, "panel-dpi") && + !of_get_display_timing(dev->of_node, "panel-timing", &dt)) + panel_simple_parse_panel_timing_node(dev, panel, &dt); + + if (desc->connector_type == DRM_MODE_CONNECTOR_LVDS) { + /* Optional data-mapping property for overriding bus format */ + err = panel_simple_override_nondefault_lvds_datamapping(dev, panel); + if (err) + goto free_ddc; + } + dev_set_drvdata(dev, panel); /* From cf60e6b1bf0c3e0e76f86fc10f9820f2f4c03532 Mon Sep 17 00:00:00 2001 From: Thomas Zimmermann Date: Thu, 8 Jan 2026 15:19:46 +0100 Subject: [PATCH 2562/3902] drm/sysfb: Remove duplicate declarations commit b91a565ed14fcf900b4d95e86882b4b763860986 upstream. Commit 6046b49bafff ("drm/sysfb: Share helpers for integer validation") and commit e8c086880b2b ("drm/sysfb: Share helpers for screen_info validation") added duplicate function declarations. Remove the latter ones. Signed-off-by: Thomas Zimmermann Fixes: e8c086880b2b ("drm/sysfb: Share helpers for screen_info validation") Cc: Thomas Zimmermann Cc: Javier Martinez Canillas Cc: dri-devel@lists.freedesktop.org Cc: # v6.16+ Reviewed-by: Javier Martinez Canillas Link: https://patch.msgid.link/20260108145058.56943-7-tzimmermann@suse.de Signed-off-by: Greg Kroah-Hartman --- drivers/gpu/drm/sysfb/drm_sysfb_helper.h | 9 --------- 1 file changed, 9 deletions(-) diff --git a/drivers/gpu/drm/sysfb/drm_sysfb_helper.h b/drivers/gpu/drm/sysfb/drm_sysfb_helper.h index 89633e30ca625a..265a1b8a934932 100644 --- a/drivers/gpu/drm/sysfb/drm_sysfb_helper.h +++ b/drivers/gpu/drm/sysfb/drm_sysfb_helper.h @@ -47,15 +47,6 @@ const struct drm_format_info *drm_sysfb_get_format_si(struct drm_device *dev, const struct screen_info *si); #endif -/* - * Input parsing - */ - -int drm_sysfb_get_validated_int(struct drm_device *dev, const char *name, - u64 value, u32 max); -int drm_sysfb_get_validated_int0(struct drm_device *dev, const char *name, - u64 value, u32 max); - /* * Display modes */ From 621fcd0aec180fea86f46be22d500cc3fbad46df Mon Sep 17 00:00:00 2001 From: Haoxiang Li Date: Wed, 24 Dec 2025 17:11:05 +0800 Subject: [PATCH 2563/3902] drm/vmwgfx: Fix an error return check in vmw_compat_shader_add() commit bf72b4b7bb7dbb643d204fa41e7463894a95999f upstream. In vmw_compat_shader_add(), the return value check of vmw_shader_alloc() is not proper. Modify the check for the return pointer 'res'. Found by code review and compiled on ubuntu 20.04. Fixes: 18e4a4669c50 ("drm/vmwgfx: Fix compat shader namespace") Cc: stable@vger.kernel.org Signed-off-by: Haoxiang Li Signed-off-by: Zack Rusin Link: https://patch.msgid.link/20251224091105.1569464-1-lihaoxiang@isrc.iscas.ac.cn Signed-off-by: Greg Kroah-Hartman --- drivers/gpu/drm/vmwgfx/vmwgfx_shader.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/drivers/gpu/drm/vmwgfx/vmwgfx_shader.c b/drivers/gpu/drm/vmwgfx/vmwgfx_shader.c index 69dfe69ce0f87d..a8c8c9375d2973 100644 --- a/drivers/gpu/drm/vmwgfx/vmwgfx_shader.c +++ b/drivers/gpu/drm/vmwgfx/vmwgfx_shader.c @@ -923,8 +923,10 @@ int vmw_compat_shader_add(struct vmw_private *dev_priv, ttm_bo_unreserve(&buf->tbo); res = vmw_shader_alloc(dev_priv, buf, size, 0, shader_type); - if (unlikely(ret != 0)) + if (IS_ERR(res)) { + ret = PTR_ERR(res); goto no_reserve; + } ret = vmw_cmdbuf_res_add(man, vmw_cmdbuf_res_shader, vmw_shader_key(user_key, shader_type), From aa866e4a130801056d8495d2851aa9b9b7331a68 Mon Sep 17 00:00:00 2001 From: Binbin Zhou Date: Sat, 17 Jan 2026 10:56:52 +0800 Subject: [PATCH 2564/3902] LoongArch: dts: loongson-2k0500: Add default interrupt controller address cells commit c4461754e6fe7e12a3ff198cce4707e3e20e43d4 upstream. Add missing address-cells 0 to the Local I/O and Extend I/O interrupt controller node to silence W=1 warning: loongson-2k0500.dtsi:513.5-51: Warning (interrupt_map): /bus@10000000/pcie@1a000000/pcie@0,0:interrupt-map: Missing property '#address-cells' in node /bus@10000000/interrupt-controller@1fe11600, using 0 as fallback Value '0' is correct because: 1. The Local I/O & Extend I/O interrupt controller do not have children, 2. interrupt-map property (in PCI node) consists of five components and the fourth component "parent unit address", which size is defined by '#address-cells' of the node pointed to by the interrupt-parent component, is not used (=0) Cc: stable@vger.kernel.org Signed-off-by: Binbin Zhou Signed-off-by: Huacai Chen Signed-off-by: Greg Kroah-Hartman --- arch/loongarch/boot/dts/loongson-2k0500.dtsi | 3 +++ 1 file changed, 3 insertions(+) diff --git a/arch/loongarch/boot/dts/loongson-2k0500.dtsi b/arch/loongarch/boot/dts/loongson-2k0500.dtsi index 588ebc3bded40f..51d990890803cc 100644 --- a/arch/loongarch/boot/dts/loongson-2k0500.dtsi +++ b/arch/loongarch/boot/dts/loongson-2k0500.dtsi @@ -131,6 +131,7 @@ reg-names = "main", "isr0"; interrupt-controller; + #address-cells = <0>; #interrupt-cells = <2>; interrupt-parent = <&cpuintc>; interrupts = <2>; @@ -149,6 +150,7 @@ reg-names = "main", "isr0"; interrupt-controller; + #address-cells = <0>; #interrupt-cells = <2>; interrupt-parent = <&cpuintc>; interrupts = <4>; @@ -164,6 +166,7 @@ compatible = "loongson,ls2k0500-eiointc"; reg = <0x0 0x1fe11600 0x0 0xea00>; interrupt-controller; + #address-cells = <0>; #interrupt-cells = <1>; interrupt-parent = <&cpuintc>; interrupts = <3>; From dea25e8f6b7448c2db05d9e476688d6c94b60467 Mon Sep 17 00:00:00 2001 From: Binbin Zhou Date: Sat, 17 Jan 2026 10:56:53 +0800 Subject: [PATCH 2565/3902] LoongArch: dts: loongson-2k1000: Add default interrupt controller address cells commit 81e8cb7e504a5adbcc48f7f954bf3c2aa9b417f8 upstream. Add missing address-cells 0 to the Local I/O interrupt controller node to silence W=1 warning: loongson-2k1000.dtsi:498.5-55: Warning (interrupt_map): /bus@10000000/pcie@1a000000/pcie@9,0:interrupt-map: Missing property '#address-cells' in node /bus@10000000/interrupt-controller@1fe01440, using 0 as fallback Value '0' is correct because: 1. The Local I/O interrupt controller does not have children, 2. interrupt-map property (in PCI node) consists of five components and the fourth component "parent unit address", which size is defined by '#address-cells' of the node pointed to by the interrupt-parent component, is not used (=0) Cc: stable@vger.kernel.org Signed-off-by: Binbin Zhou Signed-off-by: Huacai Chen Signed-off-by: Greg Kroah-Hartman --- arch/loongarch/boot/dts/loongson-2k1000.dtsi | 2 ++ 1 file changed, 2 insertions(+) diff --git a/arch/loongarch/boot/dts/loongson-2k1000.dtsi b/arch/loongarch/boot/dts/loongson-2k1000.dtsi index 0f8a24d81f7c6a..a6d5acb5c1a2d2 100644 --- a/arch/loongarch/boot/dts/loongson-2k1000.dtsi +++ b/arch/loongarch/boot/dts/loongson-2k1000.dtsi @@ -114,6 +114,7 @@ <0x0 0x1fe01140 0x0 0x8>; reg-names = "main", "isr0", "isr1"; interrupt-controller; + #address-cells = <0>; #interrupt-cells = <2>; interrupt-parent = <&cpuintc>; interrupts = <2>; @@ -131,6 +132,7 @@ <0x0 0x1fe01148 0x0 0x8>; reg-names = "main", "isr0", "isr1"; interrupt-controller; + #address-cells = <0>; #interrupt-cells = <2>; interrupt-parent = <&cpuintc>; interrupts = <3>; From 8a692ca14f4ca6b6f99fabe9bb186052697a9655 Mon Sep 17 00:00:00 2001 From: Binbin Zhou Date: Sat, 17 Jan 2026 10:56:53 +0800 Subject: [PATCH 2566/3902] LoongArch: dts: loongson-2k1000: Fix i2c-gpio node names commit 14ea5a3625881d79f75418c66e3a7d98db8518e1 upstream. The binding wants the node to be named "i2c-number", but those are named "i2c-gpio-number" instead. Thus rename those to i2c-0, i2c-1 to adhere to the binding and suppress dtbs_check warnings. Cc: stable@vger.kernel.org Reviewed-by: Krzysztof Kozlowski Signed-off-by: Binbin Zhou Signed-off-by: Huacai Chen Signed-off-by: Greg Kroah-Hartman --- arch/loongarch/boot/dts/loongson-2k1000.dtsi | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/arch/loongarch/boot/dts/loongson-2k1000.dtsi b/arch/loongarch/boot/dts/loongson-2k1000.dtsi index a6d5acb5c1a2d2..830815a5da8637 100644 --- a/arch/loongarch/boot/dts/loongson-2k1000.dtsi +++ b/arch/loongarch/boot/dts/loongson-2k1000.dtsi @@ -46,7 +46,7 @@ }; /* i2c of the dvi eeprom edid */ - i2c-gpio-0 { + i2c-0 { compatible = "i2c-gpio"; scl-gpios = <&gpio0 0 (GPIO_ACTIVE_HIGH | GPIO_OPEN_DRAIN)>; sda-gpios = <&gpio0 1 (GPIO_ACTIVE_HIGH | GPIO_OPEN_DRAIN)>; @@ -57,7 +57,7 @@ }; /* i2c of the eeprom edid */ - i2c-gpio-1 { + i2c-1 { compatible = "i2c-gpio"; scl-gpios = <&gpio0 33 (GPIO_ACTIVE_HIGH | GPIO_OPEN_DRAIN)>; sda-gpios = <&gpio0 32 (GPIO_ACTIVE_HIGH | GPIO_OPEN_DRAIN)>; From a227c3c98f092595a90eda7a71f485e09446b106 Mon Sep 17 00:00:00 2001 From: Binbin Zhou Date: Sat, 17 Jan 2026 10:56:53 +0800 Subject: [PATCH 2567/3902] LoongArch: dts: loongson-2k2000: Add default interrupt controller address cells commit e65df3f77ecd59d3a8647d19df82b22a6ce210a9 upstream. Add missing address-cells 0 to the Local I/O, Extend I/O and PCH-PIC Interrupt Controller node to silence W=1 warning: loongson-2k2000.dtsi:364.5-49: Warning (interrupt_map): /bus@10000000/pcie@1a000000/pcie@9,0:interrupt-map: Missing property '#address-cells' in node /bus@10000000/interrupt-controller@10000000, using 0 as fallback Value '0' is correct because: 1. The LIO/EIO/PCH interrupt controller does not have children, 2. interrupt-map property (in PCI node) consists of five components and the fourth component "parent unit address", which size is defined by '#address-cells' of the node pointed to by the interrupt-parent component, is not used (=0) Cc: stable@vger.kernel.org Signed-off-by: Binbin Zhou Signed-off-by: Huacai Chen Signed-off-by: Greg Kroah-Hartman --- arch/loongarch/boot/dts/loongson-2k2000.dtsi | 3 +++ 1 file changed, 3 insertions(+) diff --git a/arch/loongarch/boot/dts/loongson-2k2000.dtsi b/arch/loongarch/boot/dts/loongson-2k2000.dtsi index 18b192d8c93ce6..ff094e60af1c9f 100644 --- a/arch/loongarch/boot/dts/loongson-2k2000.dtsi +++ b/arch/loongarch/boot/dts/loongson-2k2000.dtsi @@ -126,6 +126,7 @@ reg = <0x0 0x1fe01400 0x0 0x64>; interrupt-controller; + #address-cells = <0>; #interrupt-cells = <2>; interrupt-parent = <&cpuintc>; interrupts = <2>; @@ -140,6 +141,7 @@ compatible = "loongson,ls2k2000-eiointc"; reg = <0x0 0x1fe01600 0x0 0xea00>; interrupt-controller; + #address-cells = <0>; #interrupt-cells = <1>; interrupt-parent = <&cpuintc>; interrupts = <3>; @@ -149,6 +151,7 @@ compatible = "loongson,pch-pic-1.0"; reg = <0x0 0x10000000 0x0 0x400>; interrupt-controller; + #address-cells = <0>; #interrupt-cells = <2>; loongson,pic-base-vec = <0>; interrupt-parent = <&eiointc>; From e94ec9661c5820d157d2cc4b6cf4a6ab656a7b4d Mon Sep 17 00:00:00 2001 From: Qiang Ma Date: Sat, 17 Jan 2026 10:57:02 +0800 Subject: [PATCH 2568/3902] LoongArch: KVM: Fix kvm_device leak in kvm_eiointc_destroy() commit 7d8553fc75aefa7ec936af0cf8443ff90b51732e upstream. In kvm_ioctl_create_device(), kvm_device has allocated memory, kvm_device->destroy() seems to be supposed to free its kvm_device struct, but kvm_eiointc_destroy() is not currently doing this, that would lead to a memory leak. So, fix it. Cc: stable@vger.kernel.org Reviewed-by: Bibo Mao Signed-off-by: Qiang Ma Signed-off-by: Huacai Chen Signed-off-by: Greg Kroah-Hartman --- arch/loongarch/kvm/intc/eiointc.c | 1 + 1 file changed, 1 insertion(+) diff --git a/arch/loongarch/kvm/intc/eiointc.c b/arch/loongarch/kvm/intc/eiointc.c index a1cc116b4daceb..945ce4ed7e0b7e 100644 --- a/arch/loongarch/kvm/intc/eiointc.c +++ b/arch/loongarch/kvm/intc/eiointc.c @@ -679,6 +679,7 @@ static void kvm_eiointc_destroy(struct kvm_device *dev) kvm_io_bus_unregister_dev(kvm, KVM_IOCSR_BUS, &eiointc->device); kvm_io_bus_unregister_dev(kvm, KVM_IOCSR_BUS, &eiointc->device_vext); kfree(eiointc); + kfree(dev); } static struct kvm_device_ops kvm_eiointc_dev_ops = { From 5defcc2f9c22e6e09b5be68234ad10f4ba0292b7 Mon Sep 17 00:00:00 2001 From: Qiang Ma Date: Sat, 17 Jan 2026 10:57:02 +0800 Subject: [PATCH 2569/3902] LoongArch: KVM: Fix kvm_device leak in kvm_ipi_destroy() commit 0bf58cb7288a4d3de6d8ecbb3a65928a9362bf21 upstream. In kvm_ioctl_create_device(), kvm_device has allocated memory, kvm_device->destroy() seems to be supposed to free its kvm_device struct, but kvm_ipi_destroy() is not currently doing this, that would lead to a memory leak. So, fix it. Cc: stable@vger.kernel.org Reviewed-by: Bibo Mao Signed-off-by: Qiang Ma Signed-off-by: Huacai Chen Signed-off-by: Greg Kroah-Hartman --- arch/loongarch/kvm/intc/ipi.c | 1 + 1 file changed, 1 insertion(+) diff --git a/arch/loongarch/kvm/intc/ipi.c b/arch/loongarch/kvm/intc/ipi.c index 05cefd29282e8e..1058c13dba7f4f 100644 --- a/arch/loongarch/kvm/intc/ipi.c +++ b/arch/loongarch/kvm/intc/ipi.c @@ -459,6 +459,7 @@ static void kvm_ipi_destroy(struct kvm_device *dev) ipi = kvm->arch.ipi; kvm_io_bus_unregister_dev(kvm, KVM_IOCSR_BUS, &ipi->device); kfree(ipi); + kfree(dev); } static struct kvm_device_ops kvm_ipi_dev_ops = { From fc53a66227af08d868face4b33fa8b2e1ba187ed Mon Sep 17 00:00:00 2001 From: Qiang Ma Date: Sat, 17 Jan 2026 10:57:03 +0800 Subject: [PATCH 2570/3902] LoongArch: KVM: Fix kvm_device leak in kvm_pch_pic_destroy() commit 1cf342a7c3adc5877837b53bbceb5cc9eff60bbf upstream. In kvm_ioctl_create_device(), kvm_device has allocated memory, kvm_device->destroy() seems to be supposed to free its kvm_device struct, but kvm_pch_pic_destroy() is not currently doing this, that would lead to a memory leak. So, fix it. Cc: stable@vger.kernel.org Reviewed-by: Bibo Mao Signed-off-by: Qiang Ma Signed-off-by: Huacai Chen Signed-off-by: Greg Kroah-Hartman --- arch/loongarch/kvm/intc/pch_pic.c | 1 + 1 file changed, 1 insertion(+) diff --git a/arch/loongarch/kvm/intc/pch_pic.c b/arch/loongarch/kvm/intc/pch_pic.c index a698a73de399bc..4addb34bf432b1 100644 --- a/arch/loongarch/kvm/intc/pch_pic.c +++ b/arch/loongarch/kvm/intc/pch_pic.c @@ -475,6 +475,7 @@ static void kvm_pch_pic_destroy(struct kvm_device *dev) /* unregister pch pic device and free it's memory */ kvm_io_bus_unregister_dev(kvm, KVM_MMIO_BUS, &s->device); kfree(s); + kfree(dev); } static struct kvm_device_ops kvm_pch_pic_dev_ops = { From 3706be7cbcd5f9981dd9e0296edb6743596fdd10 Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Wed, 31 Dec 2025 13:34:59 +0100 Subject: [PATCH 2571/3902] dmaengine: apple-admac: Add "apple,t8103-admac" compatible commit 76cba1e60b69c9cd53b9127d017a7dc5945455b1 upstream. After discussion with the devicetree maintainers we agreed to not extend lists with the generic compatible "apple,admac" anymore [1]. Use "apple,t8103-admac" as base compatible as it is the SoC the driver and bindings were written for. [1]: https://lore.kernel.org/asahi/12ab93b7-1fc2-4ce0-926e-c8141cfe81bf@kernel.org/ Fixes: b127315d9a78 ("dmaengine: apple-admac: Add Apple ADMAC driver") Cc: stable@vger.kernel.org Reviewed-by: Neal Gompa Signed-off-by: Janne Grunau Link: https://patch.msgid.link/20251231-apple-admac-t8103-base-compat-v1-1-ec24a3708f76@jannau.net Signed-off-by: Vinod Koul Signed-off-by: Greg Kroah-Hartman --- drivers/dma/apple-admac.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/dma/apple-admac.c b/drivers/dma/apple-admac.c index bd49f037429121..04bbd774b3b444 100644 --- a/drivers/dma/apple-admac.c +++ b/drivers/dma/apple-admac.c @@ -936,6 +936,7 @@ static void admac_remove(struct platform_device *pdev) } static const struct of_device_id admac_of_match[] = { + { .compatible = "apple,t8103-admac", }, { .compatible = "apple,admac", }, { } }; From f3c23b7e941349505c3d40de2cc0acd93d9ac057 Mon Sep 17 00:00:00 2001 From: Johan Hovold Date: Mon, 17 Nov 2025 17:12:43 +0100 Subject: [PATCH 2572/3902] dmaengine: at_hdmac: fix device leak on of_dma_xlate() commit b9074b2d7a230b6e28caa23165e9d8bc0677d333 upstream. Make sure to drop the reference taken when looking up the DMA platform device during of_dma_xlate() when releasing channel resources. Note that commit 3832b78b3ec2 ("dmaengine: at_hdmac: add missing put_device() call in at_dma_xlate()") fixed the leak in a couple of error paths but the reference is still leaking on successful allocation. Fixes: bbe89c8e3d59 ("at_hdmac: move to generic DMA binding") Fixes: 3832b78b3ec2 ("dmaengine: at_hdmac: add missing put_device() call in at_dma_xlate()") Cc: stable@vger.kernel.org # 3.10: 3832b78b3ec2 Cc: Yu Kuai Signed-off-by: Johan Hovold Link: https://patch.msgid.link/20251117161258.10679-2-johan@kernel.org Signed-off-by: Vinod Koul Signed-off-by: Greg Kroah-Hartman --- drivers/dma/at_hdmac.c | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/drivers/dma/at_hdmac.c b/drivers/dma/at_hdmac.c index 2d147712cbc69b..dffe5becd6c3cd 100644 --- a/drivers/dma/at_hdmac.c +++ b/drivers/dma/at_hdmac.c @@ -1765,6 +1765,7 @@ static int atc_alloc_chan_resources(struct dma_chan *chan) static void atc_free_chan_resources(struct dma_chan *chan) { struct at_dma_chan *atchan = to_at_dma_chan(chan); + struct at_dma_slave *atslave; BUG_ON(atc_chan_is_enabled(atchan)); @@ -1774,8 +1775,12 @@ static void atc_free_chan_resources(struct dma_chan *chan) /* * Free atslave allocated in at_dma_xlate() */ - kfree(chan->private); - chan->private = NULL; + atslave = chan->private; + if (atslave) { + put_device(atslave->dma_dev); + kfree(atslave); + chan->private = NULL; + } dev_vdbg(chan2dev(chan), "free_chan_resources: done\n"); } From 2ed1a9de1f2d727ccae5bc9cc7c63ee3519c0c8b Mon Sep 17 00:00:00 2001 From: Johan Hovold Date: Mon, 17 Nov 2025 17:12:45 +0100 Subject: [PATCH 2573/3902] dmaengine: bcm-sba-raid: fix device leak on probe commit 7c3a46ebf15a9796b763a54272407fdbf945bed8 upstream. Make sure to drop the reference taken when looking up the mailbox device during probe on probe failures and on driver unbind. Fixes: 743e1c8ffe4e ("dmaengine: Add Broadcom SBA RAID driver") Cc: stable@vger.kernel.org # 4.13 Signed-off-by: Johan Hovold Link: https://patch.msgid.link/20251117161258.10679-4-johan@kernel.org Signed-off-by: Vinod Koul Signed-off-by: Greg Kroah-Hartman --- drivers/dma/bcm-sba-raid.c | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/drivers/dma/bcm-sba-raid.c b/drivers/dma/bcm-sba-raid.c index 7f0e76439ce58e..ed037fa883f6f9 100644 --- a/drivers/dma/bcm-sba-raid.c +++ b/drivers/dma/bcm-sba-raid.c @@ -1699,7 +1699,7 @@ static int sba_probe(struct platform_device *pdev) /* Prealloc channel resource */ ret = sba_prealloc_channel_resources(sba); if (ret) - goto fail_free_mchan; + goto fail_put_mbox; /* Check availability of debugfs */ if (!debugfs_initialized()) @@ -1729,6 +1729,8 @@ static int sba_probe(struct platform_device *pdev) fail_free_resources: debugfs_remove_recursive(sba->root); sba_freeup_channel_resources(sba); +fail_put_mbox: + put_device(sba->mbox_dev); fail_free_mchan: mbox_free_channel(sba->mchan); return ret; @@ -1744,6 +1746,8 @@ static void sba_remove(struct platform_device *pdev) sba_freeup_channel_resources(sba); + put_device(sba->mbox_dev); + mbox_free_channel(sba->mchan); } From 9b3cff9f4007a4bd1ac7092bfe9381ce4b7da156 Mon Sep 17 00:00:00 2001 From: Johan Hovold Date: Mon, 17 Nov 2025 17:12:46 +0100 Subject: [PATCH 2574/3902] dmaengine: cv1800b-dmamux: fix device leak on route allocation commit 7bb7d696e0361bbfc1411462c784998cca0afcbb upstream. Make sure to drop the reference taken when looking up the DMA mux platform device during route allocation. Note that holding a reference to a device does not prevent its driver data from going away so there is no point in keeping the reference. Fixes: db7d07b5add4 ("dmaengine: add driver for Sophgo CV18XX/SG200X dmamux") Cc: stable@vger.kernel.org # 6.17 Cc: Inochi Amaoto Signed-off-by: Johan Hovold Link: https://patch.msgid.link/20251117161258.10679-5-johan@kernel.org Signed-off-by: Vinod Koul Signed-off-by: Greg Kroah-Hartman --- drivers/dma/cv1800b-dmamux.c | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/drivers/dma/cv1800b-dmamux.c b/drivers/dma/cv1800b-dmamux.c index e900d65956171f..f7a952fcbc7d76 100644 --- a/drivers/dma/cv1800b-dmamux.c +++ b/drivers/dma/cv1800b-dmamux.c @@ -102,11 +102,11 @@ static void *cv1800_dmamux_route_allocate(struct of_phandle_args *dma_spec, struct llist_node *node; unsigned long flags; unsigned int chid, devid, cpuid; - int ret; + int ret = -EINVAL; if (dma_spec->args_count != DMAMUX_NCELLS) { dev_err(&pdev->dev, "invalid number of dma mux args\n"); - return ERR_PTR(-EINVAL); + goto err_put_pdev; } devid = dma_spec->args[0]; @@ -115,18 +115,18 @@ static void *cv1800_dmamux_route_allocate(struct of_phandle_args *dma_spec, if (devid > MAX_DMA_MAPPING_ID) { dev_err(&pdev->dev, "invalid device id: %u\n", devid); - return ERR_PTR(-EINVAL); + goto err_put_pdev; } if (cpuid > MAX_DMA_CPU_ID) { dev_err(&pdev->dev, "invalid cpu id: %u\n", cpuid); - return ERR_PTR(-EINVAL); + goto err_put_pdev; } dma_spec->np = of_parse_phandle(ofdma->of_node, "dma-masters", 0); if (!dma_spec->np) { dev_err(&pdev->dev, "can't get dma master\n"); - return ERR_PTR(-EINVAL); + goto err_put_pdev; } spin_lock_irqsave(&dmamux->lock, flags); @@ -136,8 +136,6 @@ static void *cv1800_dmamux_route_allocate(struct of_phandle_args *dma_spec, if (map->peripheral == devid && map->cpu == cpuid) goto found; } - - ret = -EINVAL; goto failed; } else { node = llist_del_first(&dmamux->free_maps); @@ -171,12 +169,17 @@ static void *cv1800_dmamux_route_allocate(struct of_phandle_args *dma_spec, dev_dbg(&pdev->dev, "register channel %u for req %u (cpu %u)\n", chid, devid, cpuid); + put_device(&pdev->dev); + return map; failed: spin_unlock_irqrestore(&dmamux->lock, flags); of_node_put(dma_spec->np); dev_err(&pdev->dev, "errno %d\n", ret); +err_put_pdev: + put_device(&pdev->dev); + return ERR_PTR(ret); } From eabe40f8a53c29f531e92778ea243e379f4f7978 Mon Sep 17 00:00:00 2001 From: Johan Hovold Date: Mon, 17 Nov 2025 17:12:47 +0100 Subject: [PATCH 2575/3902] dmaengine: dw: dmamux: fix OF node leak on route allocation failure commit ec25e60f9f95464aa11411db31d0906b3fb7b9f2 upstream. Make sure to drop the reference taken to the DMA master OF node also on late route allocation failures. Fixes: 134d9c52fca2 ("dmaengine: dw: dmamux: Introduce RZN1 DMA router support") Cc: stable@vger.kernel.org # 5.19 Cc: Miquel Raynal Signed-off-by: Johan Hovold Reviewed-by: Miquel Raynal Link: https://patch.msgid.link/20251117161258.10679-6-johan@kernel.org Signed-off-by: Vinod Koul Signed-off-by: Greg Kroah-Hartman --- drivers/dma/dw/rzn1-dmamux.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/drivers/dma/dw/rzn1-dmamux.c b/drivers/dma/dw/rzn1-dmamux.c index deadf135681b67..cbec277af4dd36 100644 --- a/drivers/dma/dw/rzn1-dmamux.c +++ b/drivers/dma/dw/rzn1-dmamux.c @@ -90,7 +90,7 @@ static void *rzn1_dmamux_route_allocate(struct of_phandle_args *dma_spec, if (test_and_set_bit(map->req_idx, dmamux->used_chans)) { ret = -EBUSY; - goto free_map; + goto put_dma_spec_np; } mask = BIT(map->req_idx); @@ -103,6 +103,8 @@ static void *rzn1_dmamux_route_allocate(struct of_phandle_args *dma_spec, clear_bitmap: clear_bit(map->req_idx, dmamux->used_chans); +put_dma_spec_np: + of_node_put(dma_spec->np); free_map: kfree(map); put_device: From dd5d96722a967da35806dba22988f00cfe092db2 Mon Sep 17 00:00:00 2001 From: Zhen Ni Date: Tue, 14 Oct 2025 17:05:22 +0800 Subject: [PATCH 2576/3902] dmaengine: fsl-edma: Fix clk leak on alloc_chan_resources failure commit b18cd8b210417f90537d914ffb96e390c85a7379 upstream. When fsl_edma_alloc_chan_resources() fails after clk_prepare_enable(), the error paths only free IRQs and destroy the TCD pool, but forget to call clk_disable_unprepare(). This causes the channel clock to remain enabled, leaking power and resources. Fix it by disabling the channel clock in the error unwind path. Fixes: d8d4355861d8 ("dmaengine: fsl-edma: add i.MX8ULP edma support") Cc: stable@vger.kernel.org Suggested-by: Frank Li Signed-off-by: Zhen Ni Reviewed-by: Frank Li Link: https://patch.msgid.link/20251014090522.827726-1-zhen.ni@easystack.cn Signed-off-by: Vinod Koul Signed-off-by: Greg Kroah-Hartman --- drivers/dma/fsl-edma-common.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/dma/fsl-edma-common.c b/drivers/dma/fsl-edma-common.c index 4976d7dde08090..11655dcc4d6c15 100644 --- a/drivers/dma/fsl-edma-common.c +++ b/drivers/dma/fsl-edma-common.c @@ -852,6 +852,7 @@ int fsl_edma_alloc_chan_resources(struct dma_chan *chan) free_irq(fsl_chan->txirq, fsl_chan); err_txirq: dma_pool_destroy(fsl_chan->tcd_pool); + clk_disable_unprepare(fsl_chan->clk); return ret; } From a7226fd61def74b60dd8e47ec84cabafc39d575b Mon Sep 17 00:00:00 2001 From: Johan Hovold Date: Mon, 17 Nov 2025 17:12:48 +0100 Subject: [PATCH 2577/3902] dmaengine: idxd: fix device leaks on compat bind and unbind commit 799900f01792cf8b525a44764f065f83fcafd468 upstream. Make sure to drop the reference taken when looking up the idxd device as part of the compat bind and unbind sysfs interface. Fixes: 6e7f3ee97bbe ("dmaengine: idxd: move dsa_drv support to compatible mode") Cc: stable@vger.kernel.org # 5.15 Cc: Dave Jiang Signed-off-by: Johan Hovold Link: https://patch.msgid.link/20251117161258.10679-7-johan@kernel.org Signed-off-by: Vinod Koul Signed-off-by: Greg Kroah-Hartman --- drivers/dma/idxd/compat.c | 23 +++++++++++++++++++---- 1 file changed, 19 insertions(+), 4 deletions(-) diff --git a/drivers/dma/idxd/compat.c b/drivers/dma/idxd/compat.c index eff9943f1a42e2..95b8ef95863386 100644 --- a/drivers/dma/idxd/compat.c +++ b/drivers/dma/idxd/compat.c @@ -20,11 +20,16 @@ static ssize_t unbind_store(struct device_driver *drv, const char *buf, size_t c int rc = -ENODEV; dev = bus_find_device_by_name(bus, NULL, buf); - if (dev && dev->driver) { + if (!dev) + return -ENODEV; + + if (dev->driver) { device_driver_detach(dev); rc = count; } + put_device(dev); + return rc; } static DRIVER_ATTR_IGNORE_LOCKDEP(unbind, 0200, NULL, unbind_store); @@ -38,9 +43,12 @@ static ssize_t bind_store(struct device_driver *drv, const char *buf, size_t cou struct idxd_dev *idxd_dev; dev = bus_find_device_by_name(bus, NULL, buf); - if (!dev || dev->driver || drv != &dsa_drv.drv) + if (!dev) return -ENODEV; + if (dev->driver || drv != &dsa_drv.drv) + goto err_put_dev; + idxd_dev = confdev_to_idxd_dev(dev); if (is_idxd_dev(idxd_dev)) { alt_drv = driver_find("idxd", bus); @@ -53,13 +61,20 @@ static ssize_t bind_store(struct device_driver *drv, const char *buf, size_t cou alt_drv = driver_find("user", bus); } if (!alt_drv) - return -ENODEV; + goto err_put_dev; rc = device_driver_attach(alt_drv, dev); if (rc < 0) - return rc; + goto err_put_dev; + + put_device(dev); return count; + +err_put_dev: + put_device(dev); + + return rc; } static DRIVER_ATTR_IGNORE_LOCKDEP(bind, 0200, NULL, bind_store); From 1e47d80f6720f0224efd19bcf081d39637569c10 Mon Sep 17 00:00:00 2001 From: Johan Hovold Date: Mon, 17 Nov 2025 17:12:49 +0100 Subject: [PATCH 2578/3902] dmaengine: lpc18xx-dmamux: fix device leak on route allocation commit d4d63059dee7e7cae0c4d9a532ed558bc90efb55 upstream. Make sure to drop the reference taken when looking up the DMA mux platform device during route allocation. Note that holding a reference to a device does not prevent its driver data from going away so there is no point in keeping the reference. Fixes: e5f4ae84be74 ("dmaengine: add driver for lpc18xx dmamux") Cc: stable@vger.kernel.org # 4.3 Signed-off-by: Johan Hovold Reviewed-by: Vladimir Zapolskiy Link: https://patch.msgid.link/20251117161258.10679-8-johan@kernel.org Signed-off-by: Vinod Koul Signed-off-by: Greg Kroah-Hartman --- drivers/dma/lpc18xx-dmamux.c | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/drivers/dma/lpc18xx-dmamux.c b/drivers/dma/lpc18xx-dmamux.c index 2b6436f4b19374..d3ff521951b835 100644 --- a/drivers/dma/lpc18xx-dmamux.c +++ b/drivers/dma/lpc18xx-dmamux.c @@ -57,30 +57,31 @@ static void *lpc18xx_dmamux_reserve(struct of_phandle_args *dma_spec, struct lpc18xx_dmamux_data *dmamux = platform_get_drvdata(pdev); unsigned long flags; unsigned mux; + int ret = -EINVAL; if (dma_spec->args_count != 3) { dev_err(&pdev->dev, "invalid number of dma mux args\n"); - return ERR_PTR(-EINVAL); + goto err_put_pdev; } mux = dma_spec->args[0]; if (mux >= dmamux->dma_master_requests) { dev_err(&pdev->dev, "invalid mux number: %d\n", dma_spec->args[0]); - return ERR_PTR(-EINVAL); + goto err_put_pdev; } if (dma_spec->args[1] > LPC18XX_DMAMUX_MAX_VAL) { dev_err(&pdev->dev, "invalid dma mux value: %d\n", dma_spec->args[1]); - return ERR_PTR(-EINVAL); + goto err_put_pdev; } /* The of_node_put() will be done in the core for the node */ dma_spec->np = of_parse_phandle(ofdma->of_node, "dma-masters", 0); if (!dma_spec->np) { dev_err(&pdev->dev, "can't get dma master\n"); - return ERR_PTR(-EINVAL); + goto err_put_pdev; } spin_lock_irqsave(&dmamux->lock, flags); @@ -89,7 +90,8 @@ static void *lpc18xx_dmamux_reserve(struct of_phandle_args *dma_spec, dev_err(&pdev->dev, "dma request %u busy with %u.%u\n", mux, mux, dmamux->muxes[mux].value); of_node_put(dma_spec->np); - return ERR_PTR(-EBUSY); + ret = -EBUSY; + goto err_put_pdev; } dmamux->muxes[mux].busy = true; @@ -106,7 +108,14 @@ static void *lpc18xx_dmamux_reserve(struct of_phandle_args *dma_spec, dev_dbg(&pdev->dev, "mapping dmamux %u.%u to dma request %u\n", mux, dmamux->muxes[mux].value, mux); + put_device(&pdev->dev); + return &dmamux->muxes[mux]; + +err_put_pdev: + put_device(&pdev->dev); + + return ERR_PTR(ret); } static int lpc18xx_dmamux_probe(struct platform_device *pdev) From 9249462c55d56da2d093202db1d850a987d99d61 Mon Sep 17 00:00:00 2001 From: Johan Hovold Date: Mon, 17 Nov 2025 17:12:50 +0100 Subject: [PATCH 2579/3902] dmaengine: lpc32xx-dmamux: fix device leak on route allocation commit d9847e6d1d91462890ba297f7888fa598d47e76e upstream. Make sure to drop the reference taken when looking up the DMA mux platform device during route allocation. Note that holding a reference to a device does not prevent its driver data from going away so there is no point in keeping the reference. Fixes: 5d318b595982 ("dmaengine: Add dma router for pl08x in LPC32XX SoC") Cc: stable@vger.kernel.org # 6.12 Cc: Piotr Wojtaszczyk Signed-off-by: Johan Hovold Reviewed-by: Vladimir Zapolskiy Link: https://patch.msgid.link/20251117161258.10679-9-johan@kernel.org Signed-off-by: Vinod Koul Signed-off-by: Greg Kroah-Hartman --- drivers/dma/lpc32xx-dmamux.c | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/drivers/dma/lpc32xx-dmamux.c b/drivers/dma/lpc32xx-dmamux.c index 351d7e23e6156e..33be714740ddfb 100644 --- a/drivers/dma/lpc32xx-dmamux.c +++ b/drivers/dma/lpc32xx-dmamux.c @@ -95,11 +95,12 @@ static void *lpc32xx_dmamux_reserve(struct of_phandle_args *dma_spec, struct lpc32xx_dmamux_data *dmamux = platform_get_drvdata(pdev); unsigned long flags; struct lpc32xx_dmamux *mux = NULL; + int ret = -EINVAL; int i; if (dma_spec->args_count != 3) { dev_err(&pdev->dev, "invalid number of dma mux args\n"); - return ERR_PTR(-EINVAL); + goto err_put_pdev; } for (i = 0; i < ARRAY_SIZE(lpc32xx_muxes); i++) { @@ -111,20 +112,20 @@ static void *lpc32xx_dmamux_reserve(struct of_phandle_args *dma_spec, if (!mux) { dev_err(&pdev->dev, "invalid mux request number: %d\n", dma_spec->args[0]); - return ERR_PTR(-EINVAL); + goto err_put_pdev; } if (dma_spec->args[2] > 1) { dev_err(&pdev->dev, "invalid dma mux value: %d\n", dma_spec->args[1]); - return ERR_PTR(-EINVAL); + goto err_put_pdev; } /* The of_node_put() will be done in the core for the node */ dma_spec->np = of_parse_phandle(ofdma->of_node, "dma-masters", 0); if (!dma_spec->np) { dev_err(&pdev->dev, "can't get dma master\n"); - return ERR_PTR(-EINVAL); + goto err_put_pdev; } spin_lock_irqsave(&dmamux->lock, flags); @@ -133,7 +134,8 @@ static void *lpc32xx_dmamux_reserve(struct of_phandle_args *dma_spec, dev_err(dev, "dma request signal %d busy, routed to %s\n", mux->signal, mux->muxval ? mux->name_sel1 : mux->name_sel1); of_node_put(dma_spec->np); - return ERR_PTR(-EBUSY); + ret = -EBUSY; + goto err_put_pdev; } mux->busy = true; @@ -148,7 +150,14 @@ static void *lpc32xx_dmamux_reserve(struct of_phandle_args *dma_spec, dev_dbg(dev, "dma request signal %d routed to %s\n", mux->signal, mux->muxval ? mux->name_sel1 : mux->name_sel1); + put_device(&pdev->dev); + return mux; + +err_put_pdev: + put_device(&pdev->dev); + + return ERR_PTR(ret); } static int lpc32xx_dmamux_probe(struct platform_device *pdev) From 55a67ba5ac4cebfd54cc8305d4d57a0f1dfe6a85 Mon Sep 17 00:00:00 2001 From: Miaoqian Lin Date: Wed, 29 Oct 2025 20:34:19 +0800 Subject: [PATCH 2580/3902] dmaengine: qcom: gpi: Fix memory leak in gpi_peripheral_config() commit 3f747004bbd641131d9396d87b5d2d3d1e182728 upstream. Fix a memory leak in gpi_peripheral_config() where the original memory pointed to by gchan->config could be lost if krealloc() fails. The issue occurs when: 1. gchan->config points to previously allocated memory 2. krealloc() fails and returns NULL 3. The function directly assigns NULL to gchan->config, losing the reference to the original memory 4. The original memory becomes unreachable and cannot be freed Fix this by using a temporary variable to hold the krealloc() result and only updating gchan->config when the allocation succeeds. Found via static analysis and code review. Fixes: 5d0c3533a19f ("dmaengine: qcom: Add GPI dma driver") Cc: stable@vger.kernel.org Signed-off-by: Miaoqian Lin Reviewed-by: Bjorn Andersson Link: https://patch.msgid.link/20251029123421.91973-1-linmq006@gmail.com Signed-off-by: Vinod Koul Signed-off-by: Greg Kroah-Hartman --- drivers/dma/qcom/gpi.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/drivers/dma/qcom/gpi.c b/drivers/dma/qcom/gpi.c index 8e87738086b25a..8908b7c7190074 100644 --- a/drivers/dma/qcom/gpi.c +++ b/drivers/dma/qcom/gpi.c @@ -1605,14 +1605,16 @@ static int gpi_peripheral_config(struct dma_chan *chan, struct dma_slave_config *config) { struct gchan *gchan = to_gchan(chan); + void *new_config; if (!config->peripheral_config) return -EINVAL; - gchan->config = krealloc(gchan->config, config->peripheral_size, GFP_NOWAIT); - if (!gchan->config) + new_config = krealloc(gchan->config, config->peripheral_size, GFP_NOWAIT); + if (!new_config) return -ENOMEM; + gchan->config = new_config; memcpy(gchan->config, config->peripheral_config, config->peripheral_size); return 0; From 926d1666420c227eab50962a8622c1b8444720e8 Mon Sep 17 00:00:00 2001 From: Johan Hovold Date: Mon, 17 Nov 2025 17:12:51 +0100 Subject: [PATCH 2581/3902] dmaengine: sh: rz-dmac: fix device leak on probe failure commit 9fb490323997dcb6f749cd2660a17a39854600cd upstream. Make sure to drop the reference taken when looking up the ICU device during probe also on probe failures (e.g. probe deferral). Fixes: 7de873201c44 ("dmaengine: sh: rz-dmac: Add RZ/V2H(P) support") Cc: stable@vger.kernel.org # 6.16 Cc: Fabrizio Castro Signed-off-by: Johan Hovold Reviewed-by: Fabrizio Castro Link: https://patch.msgid.link/20251117161258.10679-10-johan@kernel.org Signed-off-by: Vinod Koul Signed-off-by: Greg Kroah-Hartman --- drivers/dma/sh/rz-dmac.c | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/drivers/dma/sh/rz-dmac.c b/drivers/dma/sh/rz-dmac.c index 1f687b08d6b867..38137e8d80b9f5 100644 --- a/drivers/dma/sh/rz-dmac.c +++ b/drivers/dma/sh/rz-dmac.c @@ -854,6 +854,13 @@ static int rz_dmac_chan_probe(struct rz_dmac *dmac, return 0; } +static void rz_dmac_put_device(void *_dev) +{ + struct device *dev = _dev; + + put_device(dev); +} + static int rz_dmac_parse_of_icu(struct device *dev, struct rz_dmac *dmac) { struct device_node *np = dev->of_node; @@ -876,6 +883,10 @@ static int rz_dmac_parse_of_icu(struct device *dev, struct rz_dmac *dmac) return -ENODEV; } + ret = devm_add_action_or_reset(dev, rz_dmac_put_device, &dmac->icu.pdev->dev); + if (ret) + return ret; + dmac_index = args.args[0]; if (dmac_index > RZV2H_MAX_DMAC_INDEX) { dev_err(dev, "DMAC index %u invalid.\n", dmac_index); @@ -1055,8 +1066,6 @@ static void rz_dmac_remove(struct platform_device *pdev) reset_control_assert(dmac->rstc); pm_runtime_put(&pdev->dev); pm_runtime_disable(&pdev->dev); - - platform_device_put(dmac->icu.pdev); } static const struct of_device_id of_rz_dmac_match[] = { From 8dd65e98ce20985453c712b550566c6330774b5c Mon Sep 17 00:00:00 2001 From: Biju Das Date: Thu, 13 Nov 2025 19:50:48 +0000 Subject: [PATCH 2582/3902] dmaengine: sh: rz-dmac: Fix rz_dmac_terminate_all() commit 747213b08a1ab6a76e3e3b3e7a209cc1d402b5d0 upstream. After audio full duplex testing, playing the recorded file contains a few playback frames from the previous time. The rz_dmac_terminate_all() does not reset all the hardware descriptors queued previously, leading to the wrong descriptor being picked up during the next DMA transfer. Fix the above issue by resetting all the descriptor headers for a channel in rz_dmac_terminate_all() as rz_dmac_lmdesc_recycle() points to the proper descriptor header filled by the rz_dmac_prepare_descs_for_slave_sg(). Cc: stable@kernel.org Fixes: 5000d37042a6 ("dmaengine: sh: Add DMAC driver for RZ/G2L SoC") Reviewed-by: Geert Uytterhoeven Signed-off-by: Biju Das Reviewed-by: Claudiu Beznea Tested-by: Claudiu Beznea Link: https://patch.msgid.link/20251113195052.564338-1-biju.das.jz@bp.renesas.com Signed-off-by: Vinod Koul Signed-off-by: Greg Kroah-Hartman --- drivers/dma/sh/rz-dmac.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/drivers/dma/sh/rz-dmac.c b/drivers/dma/sh/rz-dmac.c index 38137e8d80b9f5..9e5f088355e226 100644 --- a/drivers/dma/sh/rz-dmac.c +++ b/drivers/dma/sh/rz-dmac.c @@ -557,11 +557,16 @@ rz_dmac_prep_slave_sg(struct dma_chan *chan, struct scatterlist *sgl, static int rz_dmac_terminate_all(struct dma_chan *chan) { struct rz_dmac_chan *channel = to_rz_dmac_chan(chan); + struct rz_lmdesc *lmdesc = channel->lmdesc.base; unsigned long flags; + unsigned int i; LIST_HEAD(head); rz_dmac_disable_hw(channel); spin_lock_irqsave(&channel->vc.lock, flags); + for (i = 0; i < DMAC_NR_LMDESC; i++) + lmdesc[i].header = 0; + list_splice_tail_init(&channel->ld_active, &channel->ld_free); list_splice_tail_init(&channel->ld_queue, &channel->ld_free); vchan_get_all_descriptors(&channel->vc, &head); From 3ef52d31cce8ba816739085a61efe07b63c6cf27 Mon Sep 17 00:00:00 2001 From: Johan Hovold Date: Mon, 17 Nov 2025 17:12:52 +0100 Subject: [PATCH 2583/3902] dmaengine: stm32: dmamux: fix device leak on route allocation commit dd6e4943889fb354efa3f700e42739da9bddb6ef upstream. Make sure to drop the reference taken when looking up the DMA mux platform device during route allocation. Note that holding a reference to a device does not prevent its driver data from going away so there is no point in keeping the reference. Fixes: df7e762db5f6 ("dmaengine: Add STM32 DMAMUX driver") Cc: stable@vger.kernel.org # 4.15 Cc: Pierre-Yves MORDRET Signed-off-by: Johan Hovold Reviewed-by: Amelie Delaunay Link: https://patch.msgid.link/20251117161258.10679-11-johan@kernel.org Signed-off-by: Vinod Koul Signed-off-by: Greg Kroah-Hartman --- drivers/dma/stm32/stm32-dmamux.c | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/drivers/dma/stm32/stm32-dmamux.c b/drivers/dma/stm32/stm32-dmamux.c index 8d77e2a7939a0d..79117976078245 100644 --- a/drivers/dma/stm32/stm32-dmamux.c +++ b/drivers/dma/stm32/stm32-dmamux.c @@ -90,23 +90,25 @@ static void *stm32_dmamux_route_allocate(struct of_phandle_args *dma_spec, struct stm32_dmamux_data *dmamux = platform_get_drvdata(pdev); struct stm32_dmamux *mux; u32 i, min, max; - int ret; + int ret = -EINVAL; unsigned long flags; if (dma_spec->args_count != 3) { dev_err(&pdev->dev, "invalid number of dma mux args\n"); - return ERR_PTR(-EINVAL); + goto err_put_pdev; } if (dma_spec->args[0] > dmamux->dmamux_requests) { dev_err(&pdev->dev, "invalid mux request number: %d\n", dma_spec->args[0]); - return ERR_PTR(-EINVAL); + goto err_put_pdev; } mux = kzalloc(sizeof(*mux), GFP_KERNEL); - if (!mux) - return ERR_PTR(-ENOMEM); + if (!mux) { + ret = -ENOMEM; + goto err_put_pdev; + } spin_lock_irqsave(&dmamux->lock, flags); mux->chan_id = find_first_zero_bit(dmamux->dma_inuse, @@ -133,7 +135,6 @@ static void *stm32_dmamux_route_allocate(struct of_phandle_args *dma_spec, dma_spec->np = of_parse_phandle(ofdma->of_node, "dma-masters", i - 1); if (!dma_spec->np) { dev_err(&pdev->dev, "can't get dma master\n"); - ret = -EINVAL; goto error; } @@ -160,6 +161,8 @@ static void *stm32_dmamux_route_allocate(struct of_phandle_args *dma_spec, dev_dbg(&pdev->dev, "Mapping DMAMUX(%u) to DMA%u(%u)\n", mux->request, mux->master, mux->chan_id); + put_device(&pdev->dev); + return mux; error: @@ -167,6 +170,9 @@ static void *stm32_dmamux_route_allocate(struct of_phandle_args *dma_spec, error_chan_id: kfree(mux); +err_put_pdev: + put_device(&pdev->dev); + return ERR_PTR(ret); } From f45b1d8bf9d0a9b45ebadcb66b6f93017b10f7a1 Mon Sep 17 00:00:00 2001 From: Johan Hovold Date: Mon, 17 Nov 2025 17:12:53 +0100 Subject: [PATCH 2584/3902] dmaengine: stm32: dmamux: fix OF node leak on route allocation failure commit b1b590a590af13ded598e70f0b72bc1e515787a1 upstream. Make sure to drop the reference taken to the DMA master OF node also on late route allocation failures. Fixes: df7e762db5f6 ("dmaengine: Add STM32 DMAMUX driver") Cc: stable@vger.kernel.org # 4.15 Cc: Pierre-Yves MORDRET Signed-off-by: Johan Hovold Reviewed-by: Amelie Delaunay Link: https://patch.msgid.link/20251117161258.10679-12-johan@kernel.org Signed-off-by: Vinod Koul Signed-off-by: Greg Kroah-Hartman --- drivers/dma/stm32/stm32-dmamux.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/drivers/dma/stm32/stm32-dmamux.c b/drivers/dma/stm32/stm32-dmamux.c index 79117976078245..2bd218dbabbb15 100644 --- a/drivers/dma/stm32/stm32-dmamux.c +++ b/drivers/dma/stm32/stm32-dmamux.c @@ -143,7 +143,7 @@ static void *stm32_dmamux_route_allocate(struct of_phandle_args *dma_spec, ret = pm_runtime_resume_and_get(&pdev->dev); if (ret < 0) { spin_unlock_irqrestore(&dmamux->lock, flags); - goto error; + goto err_put_dma_spec_np; } spin_unlock_irqrestore(&dmamux->lock, flags); @@ -165,6 +165,8 @@ static void *stm32_dmamux_route_allocate(struct of_phandle_args *dma_spec, return mux; +err_put_dma_spec_np: + of_node_put(dma_spec->np); error: clear_bit(mux->chan_id, dmamux->dma_inuse); From 61e8970b0de16f13df1a1fc60106f6d07a45f77d Mon Sep 17 00:00:00 2001 From: Johan Hovold Date: Mon, 17 Nov 2025 17:12:55 +0100 Subject: [PATCH 2585/3902] dmaengine: ti: dma-crossbar: fix device leak on dra7x route allocation commit dc7e44db01fc2498644e3106db3e62a9883a93d5 upstream. Make sure to drop the reference taken when looking up the crossbar platform device during dra7x route allocation. Note that commit 615a4bfc426e ("dmaengine: ti: Add missing put_device in ti_dra7_xbar_route_allocate") fixed the leak in the error paths but the reference is still leaking on successful allocation. Fixes: a074ae38f859 ("dmaengine: Add driver for TI DMA crossbar on DRA7x") Fixes: 615a4bfc426e ("dmaengine: ti: Add missing put_device in ti_dra7_xbar_route_allocate") Cc: stable@vger.kernel.org # 4.2: 615a4bfc426e Cc: Peter Ujfalusi Cc: Miaoqian Lin Signed-off-by: Johan Hovold Link: https://patch.msgid.link/20251117161258.10679-14-johan@kernel.org Signed-off-by: Vinod Koul Signed-off-by: Greg Kroah-Hartman --- drivers/dma/ti/dma-crossbar.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/drivers/dma/ti/dma-crossbar.c b/drivers/dma/ti/dma-crossbar.c index 7f17ee87a6dce3..e52b0e1399008f 100644 --- a/drivers/dma/ti/dma-crossbar.c +++ b/drivers/dma/ti/dma-crossbar.c @@ -288,6 +288,8 @@ static void *ti_dra7_xbar_route_allocate(struct of_phandle_args *dma_spec, ti_dra7_xbar_write(xbar->iomem, map->xbar_out, map->xbar_in); + put_device(&pdev->dev); + return map; } From 30352277d8e09c972436f883a5efd1f1b763ac14 Mon Sep 17 00:00:00 2001 From: Johan Hovold Date: Mon, 17 Nov 2025 17:12:56 +0100 Subject: [PATCH 2586/3902] dmaengine: ti: dma-crossbar: fix device leak on am335x route allocation commit 4fc17b1c6d2e04ad13fd6c21cfbac68043ec03f9 upstream. Make sure to drop the reference taken when looking up the crossbar platform device during am335x route allocation. Fixes: 42dbdcc6bf96 ("dmaengine: ti-dma-crossbar: Add support for crossbar on AM33xx/AM43xx") Cc: stable@vger.kernel.org # 4.4 Cc: Peter Ujfalusi Signed-off-by: Johan Hovold Link: https://patch.msgid.link/20251117161258.10679-15-johan@kernel.org Signed-off-by: Vinod Koul Signed-off-by: Greg Kroah-Hartman --- drivers/dma/ti/dma-crossbar.c | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/drivers/dma/ti/dma-crossbar.c b/drivers/dma/ti/dma-crossbar.c index e52b0e1399008f..ff05b150ad3723 100644 --- a/drivers/dma/ti/dma-crossbar.c +++ b/drivers/dma/ti/dma-crossbar.c @@ -79,34 +79,35 @@ static void *ti_am335x_xbar_route_allocate(struct of_phandle_args *dma_spec, { struct platform_device *pdev = of_find_device_by_node(ofdma->of_node); struct ti_am335x_xbar_data *xbar = platform_get_drvdata(pdev); - struct ti_am335x_xbar_map *map; + struct ti_am335x_xbar_map *map = ERR_PTR(-EINVAL); if (dma_spec->args_count != 3) - return ERR_PTR(-EINVAL); + goto out_put_pdev; if (dma_spec->args[2] >= xbar->xbar_events) { dev_err(&pdev->dev, "Invalid XBAR event number: %d\n", dma_spec->args[2]); - return ERR_PTR(-EINVAL); + goto out_put_pdev; } if (dma_spec->args[0] >= xbar->dma_requests) { dev_err(&pdev->dev, "Invalid DMA request line number: %d\n", dma_spec->args[0]); - return ERR_PTR(-EINVAL); + goto out_put_pdev; } /* The of_node_put() will be done in the core for the node */ dma_spec->np = of_parse_phandle(ofdma->of_node, "dma-masters", 0); if (!dma_spec->np) { dev_err(&pdev->dev, "Can't get DMA master\n"); - return ERR_PTR(-EINVAL); + goto out_put_pdev; } map = kzalloc(sizeof(*map), GFP_KERNEL); if (!map) { of_node_put(dma_spec->np); - return ERR_PTR(-ENOMEM); + map = ERR_PTR(-ENOMEM); + goto out_put_pdev; } map->dma_line = (u16)dma_spec->args[0]; @@ -120,6 +121,9 @@ static void *ti_am335x_xbar_route_allocate(struct of_phandle_args *dma_spec, ti_am335x_xbar_write(xbar->iomem, map->dma_line, map->mux_val); +out_put_pdev: + put_device(&pdev->dev); + return map; } From b3bbbf9da38c2735dc25f50e62775216dfd756f6 Mon Sep 17 00:00:00 2001 From: Johan Hovold Date: Mon, 17 Nov 2025 17:12:58 +0100 Subject: [PATCH 2587/3902] dmaengine: ti: k3-udma: fix device leak on udma lookup commit 430f7803b69cd5e5694e5dfc884c6628870af36e upstream. Make sure to drop the reference taken when looking up the UDMA platform device. Note that holding a reference to a platform device does not prevent its driver data from going away so there is no point in keeping the reference after the lookup helper returns. Fixes: d70241913413 ("dmaengine: ti: k3-udma: Add glue layer for non DMAengine users") Fixes: 1438cde8fe9c ("dmaengine: ti: k3-udma: add missing put_device() call in of_xudma_dev_get()") Cc: stable@vger.kernel.org # 5.6: 1438cde8fe9c Cc: Grygorii Strashko Cc: Yu Kuai Signed-off-by: Johan Hovold Link: https://patch.msgid.link/20251117161258.10679-17-johan@kernel.org Signed-off-by: Vinod Koul Signed-off-by: Greg Kroah-Hartman --- drivers/dma/ti/k3-udma-private.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/dma/ti/k3-udma-private.c b/drivers/dma/ti/k3-udma-private.c index 05228bf0003332..624360423ef170 100644 --- a/drivers/dma/ti/k3-udma-private.c +++ b/drivers/dma/ti/k3-udma-private.c @@ -42,9 +42,9 @@ struct udma_dev *of_xudma_dev_get(struct device_node *np, const char *property) } ud = platform_get_drvdata(pdev); + put_device(&pdev->dev); if (!ud) { pr_debug("UDMA has not been probed\n"); - put_device(&pdev->dev); return ERR_PTR(-EPROBE_DEFER); } From 1ca0f9e97f315348354b973ffaa3cc7c0328ab10 Mon Sep 17 00:00:00 2001 From: Dave Hansen Date: Wed, 22 Oct 2025 16:26:28 +0800 Subject: [PATCH 2588/3902] mm: add a ptdesc flag to mark kernel page tables commit 27bfafac65d87c58639f5d7af1353ec1e7886963 upstream. The page tables used to map the kernel and userspace often have very different handling rules. There are frequently *_kernel() variants of functions just for kernel page tables. That's not great and has lead to code duplication. Instead of having completely separate call paths, allow a 'ptdesc' to be marked as being for kernel mappings. Introduce helpers to set and clear this status. Note: this uses the PG_referenced bit. Page flags are a great fit for this since it is truly a single bit of information. Use PG_referenced itself because it's a fairly benign flag (as opposed to things like PG_lock). It's also (according to Willy) unlikely to go away any time soon. PG_referenced is not in PAGE_FLAGS_CHECK_AT_FREE. It does not need to be cleared before freeing the page, and pages coming out of the allocator should have it cleared. Regardless, introduce an API to clear it anyway. Having symmetry in the API makes it easier to change the underlying implementation later, like if there was a need to move to a PAGE_FLAGS_CHECK_AT_FREE bit. Link: https://lkml.kernel.org/r/20251022082635.2462433-3-baolu.lu@linux.intel.com Signed-off-by: Dave Hansen Signed-off-by: Lu Baolu Reviewed-by: Jason Gunthorpe Reviewed-by: Kevin Tian Acked-by: David Hildenbrand Acked-by: Mike Rapoport (Microsoft) Cc: Alistair Popple Cc: Andy Lutomirski Cc: Borislav Betkov Cc: Ingo Molnar Cc: Jann Horn Cc: Jean-Philippe Brucker Cc: Joerg Roedel Cc: Liam Howlett Cc: Lorenzo Stoakes Cc: Matthew Wilcox (Oracle) Cc: Michal Hocko Cc: Peter Zijlstra Cc: Robin Murohy Cc: Thomas Gleinxer Cc: "Uladzislau Rezki (Sony)" Cc: Vasant Hegde Cc: Vinicius Costa Gomes Cc: Vlastimil Babka Cc: Will Deacon Cc: Yi Lai Signed-off-by: Andrew Morton Signed-off-by: Greg Kroah-Hartman --- include/linux/mm.h | 41 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 41 insertions(+) diff --git a/include/linux/mm.h b/include/linux/mm.h index 8631c9424987da..d622756f4e38b9 100644 --- a/include/linux/mm.h +++ b/include/linux/mm.h @@ -2947,6 +2947,7 @@ static inline pmd_t *pmd_alloc(struct mm_struct *mm, pud_t *pud, unsigned long a #endif /* CONFIG_MMU */ enum pt_flags { + PT_kernel = PG_referenced, PT_reserved = PG_reserved, /* High bits are used for zone/node/section */ }; @@ -2972,6 +2973,46 @@ static inline bool pagetable_is_reserved(struct ptdesc *pt) return test_bit(PT_reserved, &pt->pt_flags.f); } +/** + * ptdesc_set_kernel - Mark a ptdesc used to map the kernel + * @ptdesc: The ptdesc to be marked + * + * Kernel page tables often need special handling. Set a flag so that + * the handling code knows this ptdesc will not be used for userspace. + */ +static inline void ptdesc_set_kernel(struct ptdesc *ptdesc) +{ + set_bit(PT_kernel, &ptdesc->pt_flags.f); +} + +/** + * ptdesc_clear_kernel - Mark a ptdesc as no longer used to map the kernel + * @ptdesc: The ptdesc to be unmarked + * + * Use when the ptdesc is no longer used to map the kernel and no longer + * needs special handling. + */ +static inline void ptdesc_clear_kernel(struct ptdesc *ptdesc) +{ + /* + * Note: the 'PG_referenced' bit does not strictly need to be + * cleared before freeing the page. But this is nice for + * symmetry. + */ + clear_bit(PT_kernel, &ptdesc->pt_flags.f); +} + +/** + * ptdesc_test_kernel - Check if a ptdesc is used to map the kernel + * @ptdesc: The ptdesc being tested + * + * Call to tell if the ptdesc used to map the kernel. + */ +static inline bool ptdesc_test_kernel(const struct ptdesc *ptdesc) +{ + return test_bit(PT_kernel, &ptdesc->pt_flags.f); +} + /** * pagetable_alloc - Allocate pagetables * @gfp: GFP flags From 83ce8bf84846a8e205473156ade89c823549cd06 Mon Sep 17 00:00:00 2001 From: Dave Hansen Date: Wed, 22 Oct 2025 16:26:29 +0800 Subject: [PATCH 2589/3902] mm: actually mark kernel page table pages commit 977870522af34359b461060597ee3a86f27450d6 upstream. Now that the API is in place, mark kernel page table pages just after they are allocated. Unmark them just before they are freed. Note: Unconditionally clearing the 'kernel' marking (via ptdesc_clear_kernel()) would be functionally identical to what is here. But having the if() makes it logically clear that this function can be used for kernel and non-kernel page tables. Link: https://lkml.kernel.org/r/20251022082635.2462433-4-baolu.lu@linux.intel.com Signed-off-by: Dave Hansen Signed-off-by: Lu Baolu Reviewed-by: Jason Gunthorpe Reviewed-by: Kevin Tian Acked-by: David Hildenbrand Acked-by: Mike Rapoport (Microsoft) Cc: Alistair Popple Cc: Andy Lutomirski Cc: Borislav Betkov Cc: Ingo Molnar Cc: Jann Horn Cc: Jean-Philippe Brucker Cc: Joerg Roedel Cc: Liam Howlett Cc: Lorenzo Stoakes Cc: Matthew Wilcox (Oracle) Cc: Michal Hocko Cc: Peter Zijlstra Cc: Robin Murohy Cc: Thomas Gleinxer Cc: "Uladzislau Rezki (Sony)" Cc: Vasant Hegde Cc: Vinicius Costa Gomes Cc: Vlastimil Babka Cc: Will Deacon Cc: Yi Lai Signed-off-by: Andrew Morton Signed-off-by: Greg Kroah-Hartman --- include/asm-generic/pgalloc.h | 18 ++++++++++++++++++ include/linux/mm.h | 3 +++ 2 files changed, 21 insertions(+) diff --git a/include/asm-generic/pgalloc.h b/include/asm-generic/pgalloc.h index 3c8ec3bfea4478..b9d2a7c79b93a1 100644 --- a/include/asm-generic/pgalloc.h +++ b/include/asm-generic/pgalloc.h @@ -28,6 +28,8 @@ static inline pte_t *__pte_alloc_one_kernel_noprof(struct mm_struct *mm) return NULL; } + ptdesc_set_kernel(ptdesc); + return ptdesc_address(ptdesc); } #define __pte_alloc_one_kernel(...) alloc_hooks(__pte_alloc_one_kernel_noprof(__VA_ARGS__)) @@ -146,6 +148,10 @@ static inline pmd_t *pmd_alloc_one_noprof(struct mm_struct *mm, unsigned long ad pagetable_free(ptdesc); return NULL; } + + if (mm == &init_mm) + ptdesc_set_kernel(ptdesc); + return ptdesc_address(ptdesc); } #define pmd_alloc_one(...) alloc_hooks(pmd_alloc_one_noprof(__VA_ARGS__)) @@ -179,6 +185,10 @@ static inline pud_t *__pud_alloc_one_noprof(struct mm_struct *mm, unsigned long return NULL; pagetable_pud_ctor(ptdesc); + + if (mm == &init_mm) + ptdesc_set_kernel(ptdesc); + return ptdesc_address(ptdesc); } #define __pud_alloc_one(...) alloc_hooks(__pud_alloc_one_noprof(__VA_ARGS__)) @@ -233,6 +243,10 @@ static inline p4d_t *__p4d_alloc_one_noprof(struct mm_struct *mm, unsigned long return NULL; pagetable_p4d_ctor(ptdesc); + + if (mm == &init_mm) + ptdesc_set_kernel(ptdesc); + return ptdesc_address(ptdesc); } #define __p4d_alloc_one(...) alloc_hooks(__p4d_alloc_one_noprof(__VA_ARGS__)) @@ -277,6 +291,10 @@ static inline pgd_t *__pgd_alloc_noprof(struct mm_struct *mm, unsigned int order return NULL; pagetable_pgd_ctor(ptdesc); + + if (mm == &init_mm) + ptdesc_set_kernel(ptdesc); + return ptdesc_address(ptdesc); } #define __pgd_alloc(...) alloc_hooks(__pgd_alloc_noprof(__VA_ARGS__)) diff --git a/include/linux/mm.h b/include/linux/mm.h index d622756f4e38b9..1f4305693d0ff1 100644 --- a/include/linux/mm.h +++ b/include/linux/mm.h @@ -3042,6 +3042,9 @@ static inline void pagetable_free(struct ptdesc *pt) { struct page *page = ptdesc_page(pt); + if (ptdesc_test_kernel(pt)) + ptdesc_clear_kernel(pt); + __free_pages(page, compound_order(page)); } From b63c129bc3adbc2110fbdcf87402e6712edbff13 Mon Sep 17 00:00:00 2001 From: Dave Hansen Date: Wed, 22 Oct 2025 16:26:30 +0800 Subject: [PATCH 2590/3902] x86/mm: use 'ptdesc' when freeing PMD pages commit 412d000346ea38ac4b9bb715a86c73ef89d90dea upstream. There are a billion ways to refer to a physical memory address. One of the x86 PMD freeing code location chooses to use a 'pte_t *' to point to a PMD page and then call a PTE-specific freeing function for it. That's a bit wonky. Just use a 'struct ptdesc *' instead. Its entire purpose is to refer to page table pages. It also means being able to remove an explicit cast. Right now, pte_free_kernel() is a one-liner that calls pagetable_dtor_free(). Effectively, all this patch does is remove one superfluous __pa(__va(paddr)) conversion and then call pagetable_dtor_free() directly instead of through a helper. Link: https://lkml.kernel.org/r/20251022082635.2462433-5-baolu.lu@linux.intel.com Signed-off-by: Dave Hansen Signed-off-by: Lu Baolu Reviewed-by: Jason Gunthorpe Reviewed-by: Kevin Tian Cc: Alistair Popple Cc: Andy Lutomirski Cc: Borislav Betkov Cc: David Hildenbrand Cc: Ingo Molnar Cc: Jann Horn Cc: Jean-Philippe Brucker Cc: Joerg Roedel Cc: Liam Howlett Cc: Lorenzo Stoakes Cc: Matthew Wilcox (Oracle) Cc: Michal Hocko Cc: Mike Rapoport (Microsoft) Cc: Peter Zijlstra Cc: Robin Murohy Cc: Thomas Gleinxer Cc: "Uladzislau Rezki (Sony)" Cc: Vasant Hegde Cc: Vinicius Costa Gomes Cc: Vlastimil Babka Cc: Will Deacon Cc: Yi Lai Signed-off-by: Andrew Morton Signed-off-by: Greg Kroah-Hartman --- arch/x86/mm/pgtable.c | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/arch/x86/mm/pgtable.c b/arch/x86/mm/pgtable.c index ddf248c3ee7dc2..2e5ecfdce73c31 100644 --- a/arch/x86/mm/pgtable.c +++ b/arch/x86/mm/pgtable.c @@ -729,7 +729,7 @@ int pmd_clear_huge(pmd_t *pmd) int pud_free_pmd_page(pud_t *pud, unsigned long addr) { pmd_t *pmd, *pmd_sv; - pte_t *pte; + struct ptdesc *pt; int i; pmd = pud_pgtable(*pud); @@ -750,8 +750,8 @@ int pud_free_pmd_page(pud_t *pud, unsigned long addr) for (i = 0; i < PTRS_PER_PMD; i++) { if (!pmd_none(pmd_sv[i])) { - pte = (pte_t *)pmd_page_vaddr(pmd_sv[i]); - pte_free_kernel(&init_mm, pte); + pt = page_ptdesc(pmd_page(pmd_sv[i])); + pagetable_dtor_free(pt); } } @@ -772,15 +772,15 @@ int pud_free_pmd_page(pud_t *pud, unsigned long addr) */ int pmd_free_pte_page(pmd_t *pmd, unsigned long addr) { - pte_t *pte; + struct ptdesc *pt; - pte = (pte_t *)pmd_page_vaddr(*pmd); + pt = page_ptdesc(pmd_page(*pmd)); pmd_clear(pmd); /* INVLPG to clear all paging-structure caches */ flush_tlb_kernel_range(addr, addr + PAGE_SIZE-1); - pte_free_kernel(&init_mm, pte); + pagetable_dtor_free(pt); return 1; } From c32806bf45b6224370bf74e001d9ea7f047e7b8b Mon Sep 17 00:00:00 2001 From: Dave Hansen Date: Wed, 22 Oct 2025 16:26:31 +0800 Subject: [PATCH 2591/3902] mm: introduce pure page table freeing function commit 01894295672335ff304beed4359f30d14d5765f2 upstream. The pages used for ptdescs are currently freed back to the allocator in a single location. They will shortly be freed from a second location. Create a simple helper that just frees them back to the allocator. Link: https://lkml.kernel.org/r/20251022082635.2462433-6-baolu.lu@linux.intel.com Signed-off-by: Dave Hansen Signed-off-by: Lu Baolu Reviewed-by: Jason Gunthorpe Reviewed-by: Kevin Tian Acked-by: David Hildenbrand Acked-by: Mike Rapoport (Microsoft) Cc: Alistair Popple Cc: Andy Lutomirski Cc: Borislav Betkov Cc: Ingo Molnar Cc: Jann Horn Cc: Jean-Philippe Brucker Cc: Joerg Roedel Cc: Liam Howlett Cc: Lorenzo Stoakes Cc: Matthew Wilcox (Oracle) Cc: Michal Hocko Cc: Peter Zijlstra Cc: Robin Murohy Cc: Thomas Gleinxer Cc: "Uladzislau Rezki (Sony)" Cc: Vasant Hegde Cc: Vinicius Costa Gomes Cc: Vlastimil Babka Cc: Will Deacon Cc: Yi Lai Signed-off-by: Andrew Morton Signed-off-by: Greg Kroah-Hartman --- include/linux/mm.h | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/include/linux/mm.h b/include/linux/mm.h index 1f4305693d0ff1..525842553c1d43 100644 --- a/include/linux/mm.h +++ b/include/linux/mm.h @@ -3031,6 +3031,13 @@ static inline struct ptdesc *pagetable_alloc_noprof(gfp_t gfp, unsigned int orde } #define pagetable_alloc(...) alloc_hooks(pagetable_alloc_noprof(__VA_ARGS__)) +static inline void __pagetable_free(struct ptdesc *pt) +{ + struct page *page = ptdesc_page(pt); + + __free_pages(page, compound_order(page)); +} + /** * pagetable_free - Free pagetables * @pt: The page table descriptor @@ -3040,12 +3047,10 @@ static inline struct ptdesc *pagetable_alloc_noprof(gfp_t gfp, unsigned int orde */ static inline void pagetable_free(struct ptdesc *pt) { - struct page *page = ptdesc_page(pt); - if (ptdesc_test_kernel(pt)) ptdesc_clear_kernel(pt); - __free_pages(page, compound_order(page)); + __pagetable_free(pt); } #if defined(CONFIG_SPLIT_PTE_PTLOCKS) From a1593c90896babf33e947910c7aecb9f50bab993 Mon Sep 17 00:00:00 2001 From: Lu Baolu Date: Wed, 22 Oct 2025 16:26:32 +0800 Subject: [PATCH 2592/3902] x86/mm: use pagetable_free() commit bf9e4e30f3538391745a99bc2268ec4f5e4a401e upstream. The kernel's memory management subsystem provides a dedicated interface, pagetable_free(), for freeing page table pages. Updates two call sites to use pagetable_free() instead of the lower-level __free_page() or free_pages(). This improves code consistency and clarity, and ensures the correct freeing mechanism is used. Link: https://lkml.kernel.org/r/20251022082635.2462433-7-baolu.lu@linux.intel.com Signed-off-by: Lu Baolu Reviewed-by: Jason Gunthorpe Acked-by: David Hildenbrand Acked-by: Mike Rapoport (Microsoft) Cc: Alistair Popple Cc: Andy Lutomirski Cc: Borislav Betkov Cc: Dave Hansen Cc: Ingo Molnar Cc: Jann Horn Cc: Jean-Philippe Brucker Cc: Joerg Roedel Cc: Kevin Tian Cc: Liam Howlett Cc: Lorenzo Stoakes Cc: Matthew Wilcox (Oracle) Cc: Michal Hocko Cc: Peter Zijlstra Cc: Robin Murohy Cc: Thomas Gleinxer Cc: "Uladzislau Rezki (Sony)" Cc: Vasant Hegde Cc: Vinicius Costa Gomes Cc: Vlastimil Babka Cc: Will Deacon Cc: Yi Lai Signed-off-by: Andrew Morton Signed-off-by: Greg Kroah-Hartman --- arch/x86/mm/init_64.c | 2 +- arch/x86/mm/pat/set_memory.c | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/arch/x86/mm/init_64.c b/arch/x86/mm/init_64.c index 0e4270e20fadb5..3d9a5e4ccaa434 100644 --- a/arch/x86/mm/init_64.c +++ b/arch/x86/mm/init_64.c @@ -1031,7 +1031,7 @@ static void __meminit free_pagetable(struct page *page, int order) free_reserved_pages(page, nr_pages); #endif } else { - __free_pages(page, order); + pagetable_free(page_ptdesc(page)); } } diff --git a/arch/x86/mm/pat/set_memory.c b/arch/x86/mm/pat/set_memory.c index 970981893c9b80..fffb6ef1997d26 100644 --- a/arch/x86/mm/pat/set_memory.c +++ b/arch/x86/mm/pat/set_memory.c @@ -429,7 +429,7 @@ static void cpa_collapse_large_pages(struct cpa_data *cpa) list_for_each_entry_safe(ptdesc, tmp, &pgtables, pt_list) { list_del(&ptdesc->pt_list); - __free_page(ptdesc_page(ptdesc)); + pagetable_free(ptdesc); } } From b3039c526f3e1744db0cbb7ae1f0213f5e27d3f4 Mon Sep 17 00:00:00 2001 From: Dave Hansen Date: Wed, 22 Oct 2025 16:26:33 +0800 Subject: [PATCH 2593/3902] mm: introduce deferred freeing for kernel page tables commit 5ba2f0a1556479638ac11a3c201421f5515e89f5 upstream. This introduces a conditional asynchronous mechanism, enabled by CONFIG_ASYNC_KERNEL_PGTABLE_FREE. When enabled, this mechanism defers the freeing of pages that are used as page tables for kernel address mappings. These pages are now queued to a work struct instead of being freed immediately. This deferred freeing allows for batch-freeing of page tables, providing a safe context for performing a single expensive operation (TLB flush) for a batch of kernel page tables instead of performing that expensive operation for each page table. Link: https://lkml.kernel.org/r/20251022082635.2462433-8-baolu.lu@linux.intel.com Signed-off-by: Dave Hansen Signed-off-by: Lu Baolu Reviewed-by: Jason Gunthorpe Reviewed-by: Kevin Tian Acked-by: David Hildenbrand Acked-by: Mike Rapoport (Microsoft) Cc: Alistair Popple Cc: Andy Lutomirski Cc: Borislav Betkov Cc: Ingo Molnar Cc: Jann Horn Cc: Jean-Philippe Brucker Cc: Joerg Roedel Cc: Liam Howlett Cc: Lorenzo Stoakes Cc: Matthew Wilcox (Oracle) Cc: Michal Hocko Cc: Peter Zijlstra Cc: Robin Murohy Cc: Thomas Gleinxer Cc: "Uladzislau Rezki (Sony)" Cc: Vasant Hegde Cc: Vinicius Costa Gomes Cc: Vlastimil Babka Cc: Will Deacon Cc: Yi Lai Signed-off-by: Andrew Morton Signed-off-by: Greg Kroah-Hartman --- include/linux/mm.h | 16 +++++++++++++--- mm/Kconfig | 3 +++ mm/pgtable-generic.c | 37 +++++++++++++++++++++++++++++++++++++ 3 files changed, 53 insertions(+), 3 deletions(-) diff --git a/include/linux/mm.h b/include/linux/mm.h index 525842553c1d43..86b60c2a981579 100644 --- a/include/linux/mm.h +++ b/include/linux/mm.h @@ -3038,6 +3038,14 @@ static inline void __pagetable_free(struct ptdesc *pt) __free_pages(page, compound_order(page)); } +#ifdef CONFIG_ASYNC_KERNEL_PGTABLE_FREE +void pagetable_free_kernel(struct ptdesc *pt); +#else +static inline void pagetable_free_kernel(struct ptdesc *pt) +{ + __pagetable_free(pt); +} +#endif /** * pagetable_free - Free pagetables * @pt: The page table descriptor @@ -3047,10 +3055,12 @@ static inline void __pagetable_free(struct ptdesc *pt) */ static inline void pagetable_free(struct ptdesc *pt) { - if (ptdesc_test_kernel(pt)) + if (ptdesc_test_kernel(pt)) { ptdesc_clear_kernel(pt); - - __pagetable_free(pt); + pagetable_free_kernel(pt); + } else { + __pagetable_free(pt); + } } #if defined(CONFIG_SPLIT_PTE_PTLOCKS) diff --git a/mm/Kconfig b/mm/Kconfig index 5033e2aa328e47..76001e9ba04f1c 100644 --- a/mm/Kconfig +++ b/mm/Kconfig @@ -915,6 +915,9 @@ config HAVE_GIGANTIC_FOLIOS def_bool (HUGETLB_PAGE && ARCH_HAS_GIGANTIC_PAGE) || \ (ZONE_DEVICE && HAVE_ARCH_TRANSPARENT_HUGEPAGE_PUD) +config ASYNC_KERNEL_PGTABLE_FREE + def_bool n + # TODO: Allow to be enabled without THP config ARCH_SUPPORTS_HUGE_PFNMAP def_bool n diff --git a/mm/pgtable-generic.c b/mm/pgtable-generic.c index 567e2d084071e3..1c7caa8ef164c4 100644 --- a/mm/pgtable-generic.c +++ b/mm/pgtable-generic.c @@ -406,3 +406,40 @@ pte_t *__pte_offset_map_lock(struct mm_struct *mm, pmd_t *pmd, pte_unmap_unlock(pte, ptl); goto again; } + +#ifdef CONFIG_ASYNC_KERNEL_PGTABLE_FREE +static void kernel_pgtable_work_func(struct work_struct *work); + +static struct { + struct list_head list; + /* protect above ptdesc lists */ + spinlock_t lock; + struct work_struct work; +} kernel_pgtable_work = { + .list = LIST_HEAD_INIT(kernel_pgtable_work.list), + .lock = __SPIN_LOCK_UNLOCKED(kernel_pgtable_work.lock), + .work = __WORK_INITIALIZER(kernel_pgtable_work.work, kernel_pgtable_work_func), +}; + +static void kernel_pgtable_work_func(struct work_struct *work) +{ + struct ptdesc *pt, *next; + LIST_HEAD(page_list); + + spin_lock(&kernel_pgtable_work.lock); + list_splice_tail_init(&kernel_pgtable_work.list, &page_list); + spin_unlock(&kernel_pgtable_work.lock); + + list_for_each_entry_safe(pt, next, &page_list, pt_list) + __pagetable_free(pt); +} + +void pagetable_free_kernel(struct ptdesc *pt) +{ + spin_lock(&kernel_pgtable_work.lock); + list_add(&pt->pt_list, &kernel_pgtable_work.list); + spin_unlock(&kernel_pgtable_work.lock); + + schedule_work(&kernel_pgtable_work.work); +} +#endif From 9f0a7ab700f8620e433b05c57fbd26c92ea186d9 Mon Sep 17 00:00:00 2001 From: Lu Baolu Date: Wed, 22 Oct 2025 16:26:34 +0800 Subject: [PATCH 2594/3902] iommu/sva: invalidate stale IOTLB entries for kernel address space commit e37d5a2d60a338c5917c45296bac65da1382eda5 upstream. Introduce a new IOMMU interface to flush IOTLB paging cache entries for the CPU kernel address space. This interface is invoked from the x86 architecture code that manages combined user and kernel page tables, specifically before any kernel page table page is freed and reused. This addresses the main issue with vfree() which is a common occurrence and can be triggered by unprivileged users. While this resolves the primary problem, it doesn't address some extremely rare case related to memory unplug of memory that was present as reserved memory at boot, which cannot be triggered by unprivileged users. The discussion can be found at the link below. Enable SVA on x86 architecture since the IOMMU can now receive notification to flush the paging cache before freeing the CPU kernel page table pages. Link: https://lkml.kernel.org/r/20251022082635.2462433-9-baolu.lu@linux.intel.com Link: https://lore.kernel.org/linux-iommu/04983c62-3b1d-40d4-93ae-34ca04b827e5@intel.com/ Co-developed-by: Jason Gunthorpe Signed-off-by: Jason Gunthorpe Signed-off-by: Lu Baolu Suggested-by: Jann Horn Reviewed-by: Jason Gunthorpe Reviewed-by: Vasant Hegde Reviewed-by: Kevin Tian Cc: Alistair Popple Cc: Andy Lutomirski Cc: Borislav Betkov Cc: Dave Hansen Cc: David Hildenbrand Cc: Ingo Molnar Cc: Jean-Philippe Brucker Cc: Joerg Roedel Cc: Liam Howlett Cc: Lorenzo Stoakes Cc: Matthew Wilcox (Oracle) Cc: Michal Hocko Cc: Mike Rapoport (Microsoft) Cc: Peter Zijlstra Cc: Robin Murohy Cc: Thomas Gleinxer Cc: "Uladzislau Rezki (Sony)" Cc: Vinicius Costa Gomes Cc: Vlastimil Babka Cc: Will Deacon Cc: Yi Lai Signed-off-by: Andrew Morton Signed-off-by: Greg Kroah-Hartman --- arch/x86/Kconfig | 1 + drivers/iommu/iommu-sva.c | 32 ++++++++++++++++++++++++++++---- include/linux/iommu.h | 4 ++++ mm/pgtable-generic.c | 2 ++ 4 files changed, 35 insertions(+), 4 deletions(-) diff --git a/arch/x86/Kconfig b/arch/x86/Kconfig index fa3b616af03a2d..a3700766a8c084 100644 --- a/arch/x86/Kconfig +++ b/arch/x86/Kconfig @@ -279,6 +279,7 @@ config X86 select HAVE_PCI select HAVE_PERF_REGS select HAVE_PERF_USER_STACK_DUMP + select ASYNC_KERNEL_PGTABLE_FREE if IOMMU_SVA select MMU_GATHER_RCU_TABLE_FREE select MMU_GATHER_MERGE_VMAS select HAVE_POSIX_CPU_TIMERS_TASK_WORK diff --git a/drivers/iommu/iommu-sva.c b/drivers/iommu/iommu-sva.c index a0442faad952ca..d236aef80a8d50 100644 --- a/drivers/iommu/iommu-sva.c +++ b/drivers/iommu/iommu-sva.c @@ -10,6 +10,8 @@ #include "iommu-priv.h" static DEFINE_MUTEX(iommu_sva_lock); +static bool iommu_sva_present; +static LIST_HEAD(iommu_sva_mms); static struct iommu_domain *iommu_sva_domain_alloc(struct device *dev, struct mm_struct *mm); @@ -42,6 +44,7 @@ static struct iommu_mm_data *iommu_alloc_mm_data(struct mm_struct *mm, struct de return ERR_PTR(-ENOSPC); } iommu_mm->pasid = pasid; + iommu_mm->mm = mm; INIT_LIST_HEAD(&iommu_mm->sva_domains); /* * Make sure the write to mm->iommu_mm is not reordered in front of @@ -77,9 +80,6 @@ struct iommu_sva *iommu_sva_bind_device(struct device *dev, struct mm_struct *mm if (!group) return ERR_PTR(-ENODEV); - if (IS_ENABLED(CONFIG_X86)) - return ERR_PTR(-EOPNOTSUPP); - mutex_lock(&iommu_sva_lock); /* Allocate mm->pasid if necessary. */ @@ -135,8 +135,13 @@ struct iommu_sva *iommu_sva_bind_device(struct device *dev, struct mm_struct *mm if (ret) goto out_free_domain; domain->users = 1; - list_add(&domain->next, &mm->iommu_mm->sva_domains); + if (list_empty(&iommu_mm->sva_domains)) { + if (list_empty(&iommu_sva_mms)) + iommu_sva_present = true; + list_add(&iommu_mm->mm_list_elm, &iommu_sva_mms); + } + list_add(&domain->next, &iommu_mm->sva_domains); out: refcount_set(&handle->users, 1); mutex_unlock(&iommu_sva_lock); @@ -178,6 +183,13 @@ void iommu_sva_unbind_device(struct iommu_sva *handle) list_del(&domain->next); iommu_domain_free(domain); } + + if (list_empty(&iommu_mm->sva_domains)) { + list_del(&iommu_mm->mm_list_elm); + if (list_empty(&iommu_sva_mms)) + iommu_sva_present = false; + } + mutex_unlock(&iommu_sva_lock); kfree(handle); } @@ -315,3 +327,15 @@ static struct iommu_domain *iommu_sva_domain_alloc(struct device *dev, return domain; } + +void iommu_sva_invalidate_kva_range(unsigned long start, unsigned long end) +{ + struct iommu_mm_data *iommu_mm; + + guard(mutex)(&iommu_sva_lock); + if (!iommu_sva_present) + return; + + list_for_each_entry(iommu_mm, &iommu_sva_mms, mm_list_elm) + mmu_notifier_arch_invalidate_secondary_tlbs(iommu_mm->mm, start, end); +} diff --git a/include/linux/iommu.h b/include/linux/iommu.h index c30d12e16473df..66e4abb2df0dcd 100644 --- a/include/linux/iommu.h +++ b/include/linux/iommu.h @@ -1134,7 +1134,9 @@ struct iommu_sva { struct iommu_mm_data { u32 pasid; + struct mm_struct *mm; struct list_head sva_domains; + struct list_head mm_list_elm; }; int iommu_fwspec_init(struct device *dev, struct fwnode_handle *iommu_fwnode); @@ -1615,6 +1617,7 @@ struct iommu_sva *iommu_sva_bind_device(struct device *dev, struct mm_struct *mm); void iommu_sva_unbind_device(struct iommu_sva *handle); u32 iommu_sva_get_pasid(struct iommu_sva *handle); +void iommu_sva_invalidate_kva_range(unsigned long start, unsigned long end); #else static inline struct iommu_sva * iommu_sva_bind_device(struct device *dev, struct mm_struct *mm) @@ -1639,6 +1642,7 @@ static inline u32 mm_get_enqcmd_pasid(struct mm_struct *mm) } static inline void mm_pasid_drop(struct mm_struct *mm) {} +static inline void iommu_sva_invalidate_kva_range(unsigned long start, unsigned long end) {} #endif /* CONFIG_IOMMU_SVA */ #ifdef CONFIG_IOMMU_IOPF diff --git a/mm/pgtable-generic.c b/mm/pgtable-generic.c index 1c7caa8ef164c4..8c22be79b73439 100644 --- a/mm/pgtable-generic.c +++ b/mm/pgtable-generic.c @@ -13,6 +13,7 @@ #include #include #include +#include #include #include @@ -430,6 +431,7 @@ static void kernel_pgtable_work_func(struct work_struct *work) list_splice_tail_init(&kernel_pgtable_work.list, &page_list); spin_unlock(&kernel_pgtable_work.lock); + iommu_sva_invalidate_kva_range(PAGE_OFFSET, TLB_FLUSH_ALL); list_for_each_entry_safe(pt, next, &page_list, pt_list) __pagetable_free(pt); } From 3d72fadb764c47e37da7017ca52518bf283932e9 Mon Sep 17 00:00:00 2001 From: Zhang Lixu Date: Fri, 10 Oct 2025 13:52:54 +0800 Subject: [PATCH 2595/3902] HID: intel-ish-hid: Use dedicated unbound workqueues to prevent resume blocking commit 0d30dae38fe01cd1de358c6039a0b1184689fe51 upstream. During suspend/resume tests with S2IDLE, some ISH functional failures were observed because of delay in executing ISH resume handler. Here schedule_work() is used from resume handler to do actual work. schedule_work() uses system_wq, which is a per CPU work queue. Although the queuing is not bound to a CPU, but it prefers local CPU of the caller, unless prohibited. Users of this work queue are not supposed to queue long running work. But in practice, there are scenarios where long running work items are queued on other unbound workqueues, occupying the CPU. As a result, the ISH resume handler may not get a chance to execute in a timely manner. In one scenario, one of the ish_resume_handler() executions was delayed nearly 1 second because another work item on an unbound workqueue occupied the same CPU. This delay causes ISH functionality failures. A similar issue was previously observed where the ISH HID driver timed out while getting the HID descriptor during S4 resume in the recovery kernel, likely caused by the same workqueue contention problem. Create dedicated unbound workqueues for all ISH operations to allow work items to execute on any available CPU, eliminating CPU-specific bottlenecks and improving resume reliability under varying system loads. Also ISH has three different components, a bus driver which implements ISH protocols, a PCI interface layer and HID interface. Use one dedicated work queue for all of them. Signed-off-by: Zhang Lixu Signed-off-by: Jiri Kosina Signed-off-by: Greg Kroah-Hartman --- drivers/hid/intel-ish-hid/ipc/ipc.c | 21 +++++++++++++++++++- drivers/hid/intel-ish-hid/ipc/pci-ish.c | 2 +- drivers/hid/intel-ish-hid/ishtp-hid-client.c | 4 ++-- drivers/hid/intel-ish-hid/ishtp/bus.c | 18 ++++++++++++++++- drivers/hid/intel-ish-hid/ishtp/hbm.c | 4 ++-- drivers/hid/intel-ish-hid/ishtp/ishtp-dev.h | 3 +++ include/linux/intel-ish-client-if.h | 2 ++ 7 files changed, 47 insertions(+), 7 deletions(-) diff --git a/drivers/hid/intel-ish-hid/ipc/ipc.c b/drivers/hid/intel-ish-hid/ipc/ipc.c index 3ddaa2cd39d555..9958f2968c4ffd 100644 --- a/drivers/hid/intel-ish-hid/ipc/ipc.c +++ b/drivers/hid/intel-ish-hid/ipc/ipc.c @@ -628,7 +628,7 @@ static void recv_ipc(struct ishtp_device *dev, uint32_t doorbell_val) if (!ishtp_dev) { ishtp_dev = dev; } - schedule_work(&fw_reset_work); + queue_work(dev->unbound_wq, &fw_reset_work); break; case MNG_RESET_NOTIFY_ACK: @@ -933,6 +933,21 @@ static const struct ishtp_hw_ops ish_hw_ops = { .dma_no_cache_snooping = _dma_no_cache_snooping }; +static struct workqueue_struct *devm_ishtp_alloc_workqueue(struct device *dev) +{ + struct workqueue_struct *wq; + + wq = alloc_workqueue("ishtp_unbound_%d", WQ_UNBOUND, 0, dev->id); + if (!wq) + return NULL; + + if (devm_add_action_or_reset(dev, (void (*)(void *))destroy_workqueue, + wq)) + return NULL; + + return wq; +} + /** * ish_dev_init() -Initialize ISH devoce * @pdev: PCI device @@ -953,6 +968,10 @@ struct ishtp_device *ish_dev_init(struct pci_dev *pdev) if (!dev) return NULL; + dev->unbound_wq = devm_ishtp_alloc_workqueue(&pdev->dev); + if (!dev->unbound_wq) + return NULL; + dev->devc = &pdev->dev; ishtp_device_init(dev); diff --git a/drivers/hid/intel-ish-hid/ipc/pci-ish.c b/drivers/hid/intel-ish-hid/ipc/pci-ish.c index 9d150ce234f253..b748ac6fbfdc73 100644 --- a/drivers/hid/intel-ish-hid/ipc/pci-ish.c +++ b/drivers/hid/intel-ish-hid/ipc/pci-ish.c @@ -384,7 +384,7 @@ static int __maybe_unused ish_resume(struct device *device) ish_resume_device = device; dev->resume_flag = 1; - schedule_work(&resume_work); + queue_work(dev->unbound_wq, &resume_work); return 0; } diff --git a/drivers/hid/intel-ish-hid/ishtp-hid-client.c b/drivers/hid/intel-ish-hid/ishtp-hid-client.c index d8c3c54a8c0f2a..f61add862b6b3f 100644 --- a/drivers/hid/intel-ish-hid/ishtp-hid-client.c +++ b/drivers/hid/intel-ish-hid/ishtp-hid-client.c @@ -860,7 +860,7 @@ static int hid_ishtp_cl_reset(struct ishtp_cl_device *cl_device) hid_ishtp_trace(client_data, "%s hid_ishtp_cl %p\n", __func__, hid_ishtp_cl); - schedule_work(&client_data->work); + queue_work(ishtp_get_workqueue(cl_device), &client_data->work); return 0; } @@ -902,7 +902,7 @@ static int hid_ishtp_cl_resume(struct device *device) hid_ishtp_trace(client_data, "%s hid_ishtp_cl %p\n", __func__, hid_ishtp_cl); - schedule_work(&client_data->resume_work); + queue_work(ishtp_get_workqueue(cl_device), &client_data->resume_work); return 0; } diff --git a/drivers/hid/intel-ish-hid/ishtp/bus.c b/drivers/hid/intel-ish-hid/ishtp/bus.c index 93a0432e705814..c6ce37244e4975 100644 --- a/drivers/hid/intel-ish-hid/ishtp/bus.c +++ b/drivers/hid/intel-ish-hid/ishtp/bus.c @@ -541,7 +541,7 @@ void ishtp_cl_bus_rx_event(struct ishtp_cl_device *device) return; if (device->event_cb) - schedule_work(&device->event_work); + queue_work(device->ishtp_dev->unbound_wq, &device->event_work); } /** @@ -876,6 +876,22 @@ struct device *ishtp_get_pci_device(struct ishtp_cl_device *device) } EXPORT_SYMBOL(ishtp_get_pci_device); +/** + * ishtp_get_workqueue - Retrieve the workqueue associated with an ISHTP device + * @cl_device: Pointer to the ISHTP client device structure + * + * Returns the workqueue_struct pointer (unbound_wq) associated with the given + * ISHTP client device. This workqueue is typically used for scheduling work + * related to the device. + * + * Return: Pointer to struct workqueue_struct. + */ +struct workqueue_struct *ishtp_get_workqueue(struct ishtp_cl_device *cl_device) +{ + return cl_device->ishtp_dev->unbound_wq; +} +EXPORT_SYMBOL(ishtp_get_workqueue); + /** * ishtp_trace_callback() - Return trace callback * @cl_device: ISH-TP client device instance diff --git a/drivers/hid/intel-ish-hid/ishtp/hbm.c b/drivers/hid/intel-ish-hid/ishtp/hbm.c index 8ee5467127d872..97c4fcd9e3c6f2 100644 --- a/drivers/hid/intel-ish-hid/ishtp/hbm.c +++ b/drivers/hid/intel-ish-hid/ishtp/hbm.c @@ -573,7 +573,7 @@ void ishtp_hbm_dispatch(struct ishtp_device *dev, /* Start firmware loading process if it has loader capability */ if (version_res->host_version_supported & ISHTP_SUPPORT_CAP_LOADER) - schedule_work(&dev->work_fw_loader); + queue_work(dev->unbound_wq, &dev->work_fw_loader); dev->version.major_version = HBM_MAJOR_VERSION; dev->version.minor_version = HBM_MINOR_VERSION; @@ -864,7 +864,7 @@ void recv_hbm(struct ishtp_device *dev, struct ishtp_msg_hdr *ishtp_hdr) dev->rd_msg_fifo_tail = (dev->rd_msg_fifo_tail + IPC_PAYLOAD_SIZE) % (RD_INT_FIFO_SIZE * IPC_PAYLOAD_SIZE); spin_unlock_irqrestore(&dev->rd_msg_spinlock, flags); - schedule_work(&dev->bh_hbm_work); + queue_work(dev->unbound_wq, &dev->bh_hbm_work); eoi: return; } diff --git a/drivers/hid/intel-ish-hid/ishtp/ishtp-dev.h b/drivers/hid/intel-ish-hid/ishtp/ishtp-dev.h index 23db97ecf21cd9..4b0596eadf1cce 100644 --- a/drivers/hid/intel-ish-hid/ishtp/ishtp-dev.h +++ b/drivers/hid/intel-ish-hid/ishtp/ishtp-dev.h @@ -175,6 +175,9 @@ struct ishtp_device { struct hbm_version version; int transfer_path; /* Choice of transfer path: IPC or DMA */ + /* Alloc a dedicated unbound workqueue for ishtp device */ + struct workqueue_struct *unbound_wq; + /* work structure for scheduling firmware loading tasks */ struct work_struct work_fw_loader; /* waitq for waiting for command response from the firmware loader */ diff --git a/include/linux/intel-ish-client-if.h b/include/linux/intel-ish-client-if.h index dfbf7d9d7bb5a8..b235fd84f47804 100644 --- a/include/linux/intel-ish-client-if.h +++ b/include/linux/intel-ish-client-if.h @@ -87,6 +87,8 @@ bool ishtp_wait_resume(struct ishtp_device *dev); ishtp_print_log ishtp_trace_callback(struct ishtp_cl_device *cl_device); /* Get device pointer of PCI device for DMA acces */ struct device *ishtp_get_pci_device(struct ishtp_cl_device *cl_device); +/* Get the ISHTP workqueue */ +struct workqueue_struct *ishtp_get_workqueue(struct ishtp_cl_device *cl_device); struct ishtp_cl *ishtp_cl_allocate(struct ishtp_cl_device *cl_device); void ishtp_cl_free(struct ishtp_cl *cl); From 7e58addb8e05379e437e4722534a7cb1cabd767b Mon Sep 17 00:00:00 2001 From: Nathan Chancellor Date: Wed, 22 Oct 2025 00:49:08 +0200 Subject: [PATCH 2596/3902] HID: intel-ish-hid: Fix -Wcast-function-type-strict in devm_ishtp_alloc_workqueue() commit 3644f4411713f52bf231574aa8759e3d8e20b341 upstream. Clang warns (or errors with CONFIG_WERROR=y / W=e): drivers/hid/intel-ish-hid/ipc/ipc.c:935:36: error: cast from 'void (*)(struct workqueue_struct *)' to 'void (*)(void *)' converts to incompatible function type [-Werror,-Wcast-function-type-strict] 935 | if (devm_add_action_or_reset(dev, (void (*)(void *))destroy_workqueue, | ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ include/linux/device/devres.h:168:34: note: expanded from macro 'devm_add_action_or_reset' 168 | __devm_add_action_or_ireset(dev, action, data, #action) | ^~~~~~ This warning is pointing out a kernel control flow integrity (kCFI / CONFIG_CFI=y) violation will occur due to this function cast when the destroy_workqueue() is indirectly called via devm_action_release() because the prototype of destroy_workqueue() does not match the prototype of (*action)(). Use a local function with the correct prototype to wrap destroy_workqueue() to resolve the warning and CFI violation. Reported-by: kernel test robot Closes: https://lore.kernel.org/oe-kbuild-all/202510190103.qTZvfdjj-lkp@intel.com/ Closes: https://github.com/ClangBuiltLinux/linux/issues/2139 Fixes: 0d30dae38fe0 ("HID: intel-ish-hid: Use dedicated unbound workqueues to prevent resume blocking") Signed-off-by: Nathan Chancellor Acked-by: Srinivas Pandruvada Reviewed-by: Zhang Lixu Signed-off-by: Jiri Kosina Signed-off-by: Greg Kroah-Hartman --- drivers/hid/intel-ish-hid/ipc/ipc.c | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/drivers/hid/intel-ish-hid/ipc/ipc.c b/drivers/hid/intel-ish-hid/ipc/ipc.c index 9958f2968c4ffd..3692d1db3bc764 100644 --- a/drivers/hid/intel-ish-hid/ipc/ipc.c +++ b/drivers/hid/intel-ish-hid/ipc/ipc.c @@ -933,6 +933,11 @@ static const struct ishtp_hw_ops ish_hw_ops = { .dma_no_cache_snooping = _dma_no_cache_snooping }; +static void ishtp_free_workqueue(void *wq) +{ + destroy_workqueue(wq); +} + static struct workqueue_struct *devm_ishtp_alloc_workqueue(struct device *dev) { struct workqueue_struct *wq; @@ -941,8 +946,7 @@ static struct workqueue_struct *devm_ishtp_alloc_workqueue(struct device *dev) if (!wq) return NULL; - if (devm_add_action_or_reset(dev, (void (*)(void *))destroy_workqueue, - wq)) + if (devm_add_action_or_reset(dev, ishtp_free_workqueue, wq)) return NULL; return wq; From 9ac63333d600732a56b35ee1fa46836da671eb50 Mon Sep 17 00:00:00 2001 From: Robbie Ko Date: Thu, 11 Dec 2025 13:30:33 +0800 Subject: [PATCH 2597/3902] btrfs: fix deadlock in wait_current_trans() due to ignored transaction type MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit commit 5037b342825df7094a4906d1e2a9674baab50cb2 upstream. When wait_current_trans() is called during start_transaction(), it currently waits for a blocked transaction without considering whether the given transaction type actually needs to wait for that particular transaction state. The btrfs_blocked_trans_types[] array already defines which transaction types should wait for which transaction states, but this check was missing in wait_current_trans(). This can lead to a deadlock scenario involving two transactions and pending ordered extents: 1. Transaction A is in TRANS_STATE_COMMIT_DOING state 2. A worker processing an ordered extent calls start_transaction() with TRANS_JOIN 3. join_transaction() returns -EBUSY because Transaction A is in TRANS_STATE_COMMIT_DOING 4. Transaction A moves to TRANS_STATE_UNBLOCKED and completes 5. A new Transaction B is created (TRANS_STATE_RUNNING) 6. The ordered extent from step 2 is added to Transaction B's pending ordered extents 7. Transaction B immediately starts commit by another task and enters TRANS_STATE_COMMIT_START 8. The worker finally reaches wait_current_trans(), sees Transaction B in TRANS_STATE_COMMIT_START (a blocked state), and waits unconditionally 9. However, TRANS_JOIN should NOT wait for TRANS_STATE_COMMIT_START according to btrfs_blocked_trans_types[] 10. Transaction B is waiting for pending ordered extents to complete 11. Deadlock: Transaction B waits for ordered extent, ordered extent waits for Transaction B This can be illustrated by the following call stacks: CPU0 CPU1 btrfs_finish_ordered_io() start_transaction(TRANS_JOIN) join_transaction() # -EBUSY (Transaction A is # TRANS_STATE_COMMIT_DOING) # Transaction A completes # Transaction B created # ordered extent added to # Transaction B's pending list btrfs_commit_transaction() # Transaction B enters # TRANS_STATE_COMMIT_START # waiting for pending ordered # extents wait_current_trans() # waits for Transaction B # (should not wait!) Task bstore_kv_sync in btrfs_commit_transaction waiting for ordered extents: __schedule+0x2e7/0x8a0 schedule+0x64/0xe0 btrfs_commit_transaction+0xbf7/0xda0 [btrfs] btrfs_sync_file+0x342/0x4d0 [btrfs] __x64_sys_fdatasync+0x4b/0x80 do_syscall_64+0x33/0x40 entry_SYSCALL_64_after_hwframe+0x44/0xa9 Task kworker in wait_current_trans waiting for transaction commit: Workqueue: btrfs-syno_nocow btrfs_work_helper [btrfs] __schedule+0x2e7/0x8a0 schedule+0x64/0xe0 wait_current_trans+0xb0/0x110 [btrfs] start_transaction+0x346/0x5b0 [btrfs] btrfs_finish_ordered_io.isra.0+0x49b/0x9c0 [btrfs] btrfs_work_helper+0xe8/0x350 [btrfs] process_one_work+0x1d3/0x3c0 worker_thread+0x4d/0x3e0 kthread+0x12d/0x150 ret_from_fork+0x1f/0x30 Fix this by passing the transaction type to wait_current_trans() and checking btrfs_blocked_trans_types[cur_trans->state] against the given type before deciding to wait. This ensures that transaction types which are allowed to join during certain blocked states will not unnecessarily wait and cause deadlocks. Reviewed-by: Filipe Manana Signed-off-by: Robbie Ko Signed-off-by: Filipe Manana Reviewed-by: David Sterba Signed-off-by: David Sterba Cc: Motiejus Jakštys Signed-off-by: Greg Kroah-Hartman --- fs/btrfs/transaction.c | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/fs/btrfs/transaction.c b/fs/btrfs/transaction.c index 89ae0c7a610aa5..c457316c2788be 100644 --- a/fs/btrfs/transaction.c +++ b/fs/btrfs/transaction.c @@ -518,13 +518,14 @@ static inline int is_transaction_blocked(struct btrfs_transaction *trans) * when this is done, it is safe to start a new transaction, but the current * transaction might not be fully on disk. */ -static void wait_current_trans(struct btrfs_fs_info *fs_info) +static void wait_current_trans(struct btrfs_fs_info *fs_info, unsigned int type) { struct btrfs_transaction *cur_trans; spin_lock(&fs_info->trans_lock); cur_trans = fs_info->running_transaction; - if (cur_trans && is_transaction_blocked(cur_trans)) { + if (cur_trans && is_transaction_blocked(cur_trans) && + (btrfs_blocked_trans_types[cur_trans->state] & type)) { refcount_inc(&cur_trans->use_count); spin_unlock(&fs_info->trans_lock); @@ -699,12 +700,12 @@ start_transaction(struct btrfs_root *root, unsigned int num_items, sb_start_intwrite(fs_info->sb); if (may_wait_transaction(fs_info, type)) - wait_current_trans(fs_info); + wait_current_trans(fs_info, type); do { ret = join_transaction(fs_info, type); if (ret == -EBUSY) { - wait_current_trans(fs_info); + wait_current_trans(fs_info, type); if (unlikely(type == TRANS_ATTACH || type == TRANS_JOIN_NOSTART)) ret = -ENOENT; @@ -1001,7 +1002,7 @@ int btrfs_wait_for_commit(struct btrfs_fs_info *fs_info, u64 transid) void btrfs_throttle(struct btrfs_fs_info *fs_info) { - wait_current_trans(fs_info); + wait_current_trans(fs_info, TRANS_START); } bool btrfs_should_end_transaction(struct btrfs_trans_handle *trans) From 2a72a8ddf1888d56744ad7242be8f88d0c9df17b Mon Sep 17 00:00:00 2001 From: Joshua Hahn Date: Tue, 14 Oct 2025 07:50:08 -0700 Subject: [PATCH 2598/3902] mm/page_alloc/vmstat: simplify refresh_cpu_vm_stats change detection commit 0acc67c4030c39f39ac90413cc5d0abddd3a9527 upstream. Patch series "mm/page_alloc: Batch callers of free_pcppages_bulk", v5. Motivation & Approach ===================== While testing workloads with high sustained memory pressure on large machines in the Meta fleet (1Tb memory, 316 CPUs), we saw an unexpectedly high number of softlockups. Further investigation showed that the zone lock in free_pcppages_bulk was being held for a long time, and was called to free 2k+ pages over 100 times just during boot. This causes starvation in other processes for the zone lock, which can lead to the system stalling as multiple threads cannot make progress without the locks. We can see these issues manifesting as warnings: [ 4512.591979] rcu: INFO: rcu_sched self-detected stall on CPU [ 4512.604370] rcu: 20-....: (9312 ticks this GP) idle=a654/1/0x4000000000000000 softirq=309340/309344 fqs=5426 [ 4512.626401] rcu: hardirqs softirqs csw/system [ 4512.638793] rcu: number: 0 145 0 [ 4512.651177] rcu: cputime: 30 10410 174 ==> 10558(ms) [ 4512.666657] rcu: (t=21077 jiffies g=783665 q=1242213 ncpus=316) While these warnings don't indicate a crash or a kernel panic, they do point to the underlying issue of lock contention. To prevent starvation in both locks, batch the freeing of pages using pcp->batch. Because free_pcppages_bulk is called with the pcp lock and acquires the zone lock, relinquishing and reacquiring the locks are only effective when both of them are broken together (unless the system was built with queued spinlocks). Thus, instead of modifying free_pcppages_bulk to break both locks, batch the freeing from its callers instead. A similar fix has been implemented in the Meta fleet, and we have seen significantly less softlockups. Testing ======= The following are a few synthetic benchmarks, made on three machines. The first is a large machine with 754GiB memory and 316 processors. The second is a relatively smaller machine with 251GiB memory and 176 processors. The third and final is the smallest of the three, which has 62GiB memory and 36 processors. On all machines, I kick off a kernel build with -j$(nproc). Negative delta is better (faster compilation). Large machine (754GiB memory, 316 processors) make -j$(nproc) +------------+---------------+-----------+ | Metric (s) | Variation (%) | Delta(%) | +------------+---------------+-----------+ | real | 0.8070 | - 1.4865 | | user | 0.2823 | + 0.4081 | | sys | 5.0267 | -11.8737 | +------------+---------------+-----------+ Medium machine (251GiB memory, 176 processors) make -j$(nproc) +------------+---------------+----------+ | Metric (s) | Variation (%) | Delta(%) | +------------+---------------+----------+ | real | 0.2806 | +0.0351 | | user | 0.0994 | +0.3170 | | sys | 0.6229 | -0.6277 | +------------+---------------+----------+ Small machine (62GiB memory, 36 processors) make -j$(nproc) +------------+---------------+----------+ | Metric (s) | Variation (%) | Delta(%) | +------------+---------------+----------+ | real | 0.1503 | -2.6585 | | user | 0.0431 | -2.2984 | | sys | 0.1870 | -3.2013 | +------------+---------------+----------+ Here, variation is the coefficient of variation, i.e. standard deviation / mean. Based on these results, it seems like there are varying degrees to how much lock contention this reduces. For the largest and smallest machines that I ran the tests on, it seems like there is quite some significant reduction. There is also some performance increases visible from userspace. Interestingly, the performance gains don't scale with the size of the machine, but rather there seems to be a dip in the gain there is for the medium-sized machine. One possible theory is that because the high watermark depends on both memory and the number of local CPUs, what impacts zone contention the most is not these individual values, but rather the ratio of mem:processors. This patch (of 5): Currently, refresh_cpu_vm_stats returns an int, indicating how many changes were made during its updates. Using this information, callers like vmstat_update can heuristically determine if more work will be done in the future. However, all of refresh_cpu_vm_stats's callers either (a) ignore the result, only caring about performing the updates, or (b) only care about whether changes were made, but not *how many* changes were made. Simplify the code by returning a bool instead to indicate if updates were made. In addition, simplify fold_diff and decay_pcp_high to return a bool for the same reason. Link: https://lkml.kernel.org/r/20251014145011.3427205-1-joshua.hahnjy@gmail.com Link: https://lkml.kernel.org/r/20251014145011.3427205-2-joshua.hahnjy@gmail.com Signed-off-by: Joshua Hahn Reviewed-by: Vlastimil Babka Reviewed-by: SeongJae Park Cc: Brendan Jackman Cc: Chris Mason Cc: Johannes Weiner Cc: "Kirill A. Shutemov" Cc: Michal Hocko Cc: Suren Baghdasaryan Cc: Zi Yan Signed-off-by: Andrew Morton Stable-dep-of: 038a102535eb ("mm/page_alloc: prevent pcp corruption with SMP=n") Signed-off-by: Sasha Levin Signed-off-by: Greg Kroah-Hartman --- include/linux/gfp.h | 2 +- mm/page_alloc.c | 8 ++++---- mm/vmstat.c | 28 +++++++++++++++------------- 3 files changed, 20 insertions(+), 18 deletions(-) diff --git a/include/linux/gfp.h b/include/linux/gfp.h index 623bee335383ef..b155929af5b110 100644 --- a/include/linux/gfp.h +++ b/include/linux/gfp.h @@ -387,7 +387,7 @@ extern void free_pages(unsigned long addr, unsigned int order); #define free_page(addr) free_pages((addr), 0) void page_alloc_init_cpuhp(void); -int decay_pcp_high(struct zone *zone, struct per_cpu_pages *pcp); +bool decay_pcp_high(struct zone *zone, struct per_cpu_pages *pcp); void drain_zone_pages(struct zone *zone, struct per_cpu_pages *pcp); void drain_all_pages(struct zone *zone); void drain_local_pages(struct zone *zone); diff --git a/mm/page_alloc.c b/mm/page_alloc.c index 764b7aabaf69f3..4db42673cd8776 100644 --- a/mm/page_alloc.c +++ b/mm/page_alloc.c @@ -2552,10 +2552,10 @@ static int rmqueue_bulk(struct zone *zone, unsigned int order, * Called from the vmstat counter updater to decay the PCP high. * Return whether there are addition works to do. */ -int decay_pcp_high(struct zone *zone, struct per_cpu_pages *pcp) +bool decay_pcp_high(struct zone *zone, struct per_cpu_pages *pcp) { int high_min, to_drain, batch; - int todo = 0; + bool todo = false; high_min = READ_ONCE(pcp->high_min); batch = READ_ONCE(pcp->batch); @@ -2568,7 +2568,7 @@ int decay_pcp_high(struct zone *zone, struct per_cpu_pages *pcp) pcp->high = max3(pcp->count - (batch << CONFIG_PCP_BATCH_SCALE_MAX), pcp->high - (pcp->high >> 3), high_min); if (pcp->high > high_min) - todo++; + todo = true; } to_drain = pcp->count - pcp->high; @@ -2576,7 +2576,7 @@ int decay_pcp_high(struct zone *zone, struct per_cpu_pages *pcp) spin_lock(&pcp->lock); free_pcppages_bulk(zone, to_drain, pcp, 0); spin_unlock(&pcp->lock); - todo++; + todo = true; } return todo; diff --git a/mm/vmstat.c b/mm/vmstat.c index bb09c032eecfa2..98855f31294dd2 100644 --- a/mm/vmstat.c +++ b/mm/vmstat.c @@ -771,25 +771,25 @@ EXPORT_SYMBOL(dec_node_page_state); /* * Fold a differential into the global counters. - * Returns the number of counters updated. + * Returns whether counters were updated. */ static int fold_diff(int *zone_diff, int *node_diff) { int i; - int changes = 0; + bool changed = false; for (i = 0; i < NR_VM_ZONE_STAT_ITEMS; i++) if (zone_diff[i]) { atomic_long_add(zone_diff[i], &vm_zone_stat[i]); - changes++; + changed = true; } for (i = 0; i < NR_VM_NODE_STAT_ITEMS; i++) if (node_diff[i]) { atomic_long_add(node_diff[i], &vm_node_stat[i]); - changes++; + changed = true; } - return changes; + return changed; } /* @@ -806,16 +806,16 @@ static int fold_diff(int *zone_diff, int *node_diff) * with the global counters. These could cause remote node cache line * bouncing and will have to be only done when necessary. * - * The function returns the number of global counters updated. + * The function returns whether global counters were updated. */ -static int refresh_cpu_vm_stats(bool do_pagesets) +static bool refresh_cpu_vm_stats(bool do_pagesets) { struct pglist_data *pgdat; struct zone *zone; int i; int global_zone_diff[NR_VM_ZONE_STAT_ITEMS] = { 0, }; int global_node_diff[NR_VM_NODE_STAT_ITEMS] = { 0, }; - int changes = 0; + bool changed = false; for_each_populated_zone(zone) { struct per_cpu_zonestat __percpu *pzstats = zone->per_cpu_zonestats; @@ -839,7 +839,8 @@ static int refresh_cpu_vm_stats(bool do_pagesets) if (do_pagesets) { cond_resched(); - changes += decay_pcp_high(zone, this_cpu_ptr(pcp)); + if (decay_pcp_high(zone, this_cpu_ptr(pcp))) + changed = true; #ifdef CONFIG_NUMA /* * Deal with draining the remote pageset of this @@ -861,13 +862,13 @@ static int refresh_cpu_vm_stats(bool do_pagesets) } if (__this_cpu_dec_return(pcp->expire)) { - changes++; + changed = true; continue; } if (__this_cpu_read(pcp->count)) { drain_zone_pages(zone, this_cpu_ptr(pcp)); - changes++; + changed = true; } #endif } @@ -887,8 +888,9 @@ static int refresh_cpu_vm_stats(bool do_pagesets) } } - changes += fold_diff(global_zone_diff, global_node_diff); - return changes; + if (fold_diff(global_zone_diff, global_node_diff)) + changed = true; + return changed; } /* From baea24956aea96546975f9fb55534abd04db5ad9 Mon Sep 17 00:00:00 2001 From: Joshua Hahn Date: Tue, 14 Oct 2025 07:50:09 -0700 Subject: [PATCH 2599/3902] mm/page_alloc: batch page freeing in decay_pcp_high commit fc4b909c368f3a7b08c895dd5926476b58e85312 upstream. It is possible for pcp->count - pcp->high to exceed pcp->batch by a lot. When this happens, we should perform batching to ensure that free_pcppages_bulk isn't called with too many pages to free at once and starve out other threads that need the pcp or zone lock. Since we are still only freeing the difference between the initial pcp->count and pcp->high values, there should be no change to how many pages are freed. Link: https://lkml.kernel.org/r/20251014145011.3427205-3-joshua.hahnjy@gmail.com Signed-off-by: Joshua Hahn Suggested-by: Chris Mason Suggested-by: Andrew Morton Co-developed-by: Johannes Weiner Reviewed-by: Vlastimil Babka Cc: Brendan Jackman Cc: "Kirill A. Shutemov" Cc: Michal Hocko Cc: SeongJae Park Cc: Suren Baghdasaryan Cc: Zi Yan Signed-off-by: Andrew Morton Stable-dep-of: 038a102535eb ("mm/page_alloc: prevent pcp corruption with SMP=n") Signed-off-by: Sasha Levin Signed-off-by: Greg Kroah-Hartman --- mm/page_alloc.c | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/mm/page_alloc.c b/mm/page_alloc.c index 4db42673cd8776..33b881489727f5 100644 --- a/mm/page_alloc.c +++ b/mm/page_alloc.c @@ -2554,7 +2554,7 @@ static int rmqueue_bulk(struct zone *zone, unsigned int order, */ bool decay_pcp_high(struct zone *zone, struct per_cpu_pages *pcp) { - int high_min, to_drain, batch; + int high_min, to_drain, to_drain_batched, batch; bool todo = false; high_min = READ_ONCE(pcp->high_min); @@ -2572,11 +2572,14 @@ bool decay_pcp_high(struct zone *zone, struct per_cpu_pages *pcp) } to_drain = pcp->count - pcp->high; - if (to_drain > 0) { + while (to_drain > 0) { + to_drain_batched = min(to_drain, batch); spin_lock(&pcp->lock); - free_pcppages_bulk(zone, to_drain, pcp, 0); + free_pcppages_bulk(zone, to_drain_batched, pcp, 0); spin_unlock(&pcp->lock); todo = true; + + to_drain -= to_drain_batched; } return todo; From 3098f8f7c7b0686c74827aec42a2c45e69801ff8 Mon Sep 17 00:00:00 2001 From: Vlastimil Babka Date: Mon, 5 Jan 2026 16:08:56 +0100 Subject: [PATCH 2600/3902] mm/page_alloc: prevent pcp corruption with SMP=n commit 038a102535eb49e10e93eafac54352fcc5d78847 upstream. The kernel test robot has reported: BUG: spinlock trylock failure on UP on CPU#0, kcompactd0/28 lock: 0xffff888807e35ef0, .magic: dead4ead, .owner: kcompactd0/28, .owner_cpu: 0 CPU: 0 UID: 0 PID: 28 Comm: kcompactd0 Not tainted 6.18.0-rc5-00127-ga06157804399 #1 PREEMPT 8cc09ef94dcec767faa911515ce9e609c45db470 Call Trace: __dump_stack (lib/dump_stack.c:95) dump_stack_lvl (lib/dump_stack.c:123) dump_stack (lib/dump_stack.c:130) spin_dump (kernel/locking/spinlock_debug.c:71) do_raw_spin_trylock (kernel/locking/spinlock_debug.c:?) _raw_spin_trylock (include/linux/spinlock_api_smp.h:89 kernel/locking/spinlock.c:138) __free_frozen_pages (mm/page_alloc.c:2973) ___free_pages (mm/page_alloc.c:5295) __free_pages (mm/page_alloc.c:5334) tlb_remove_table_rcu (include/linux/mm.h:? include/linux/mm.h:3122 include/asm-generic/tlb.h:220 mm/mmu_gather.c:227 mm/mmu_gather.c:290) ? __cfi_tlb_remove_table_rcu (mm/mmu_gather.c:289) ? rcu_core (kernel/rcu/tree.c:?) rcu_core (include/linux/rcupdate.h:341 kernel/rcu/tree.c:2607 kernel/rcu/tree.c:2861) rcu_core_si (kernel/rcu/tree.c:2879) handle_softirqs (arch/x86/include/asm/jump_label.h:36 include/trace/events/irq.h:142 kernel/softirq.c:623) __irq_exit_rcu (arch/x86/include/asm/jump_label.h:36 kernel/softirq.c:725) irq_exit_rcu (kernel/softirq.c:741) sysvec_apic_timer_interrupt (arch/x86/kernel/apic/apic.c:1052) RIP: 0010:_raw_spin_unlock_irqrestore (arch/x86/include/asm/preempt.h:95 include/linux/spinlock_api_smp.h:152 kernel/locking/spinlock.c:194) free_pcppages_bulk (mm/page_alloc.c:1494) drain_pages_zone (include/linux/spinlock.h:391 mm/page_alloc.c:2632) __drain_all_pages (mm/page_alloc.c:2731) drain_all_pages (mm/page_alloc.c:2747) kcompactd (mm/compaction.c:3115) kthread (kernel/kthread.c:465) ? __cfi_kcompactd (mm/compaction.c:3166) ? __cfi_kthread (kernel/kthread.c:412) ret_from_fork (arch/x86/kernel/process.c:164) ? __cfi_kthread (kernel/kthread.c:412) ret_from_fork_asm (arch/x86/entry/entry_64.S:255) Matthew has analyzed the report and identified that in drain_page_zone() we are in a section protected by spin_lock(&pcp->lock) and then get an interrupt that attempts spin_trylock() on the same lock. The code is designed to work this way without disabling IRQs and occasionally fail the trylock with a fallback. However, the SMP=n spinlock implementation assumes spin_trylock() will always succeed, and thus it's normally a no-op. Here the enabled lock debugging catches the problem, but otherwise it could cause a corruption of the pcp structure. The problem has been introduced by commit 574907741599 ("mm/page_alloc: leave IRQs enabled for per-cpu page allocations"). The pcp locking scheme recognizes the need for disabling IRQs to prevent nesting spin_trylock() sections on SMP=n, but the need to prevent the nesting in spin_lock() has not been recognized. Fix it by introducing local wrappers that change the spin_lock() to spin_lock_iqsave() with SMP=n and use them in all places that do spin_lock(&pcp->lock). [vbabka@suse.cz: add pcp_ prefix to the spin_lock_irqsave wrappers, per Steven] Link: https://lkml.kernel.org/r/20260105-fix-pcp-up-v1-1-5579662d2071@suse.cz Fixes: 574907741599 ("mm/page_alloc: leave IRQs enabled for per-cpu page allocations") Signed-off-by: Vlastimil Babka Reported-by: kernel test robot Closes: https://lore.kernel.org/oe-lkp/202512101320.e2f2dd6f-lkp@intel.com Analyzed-by: Matthew Wilcox Link: https://lore.kernel.org/all/aUW05pyc9nZkvY-1@casper.infradead.org/ Acked-by: Mel Gorman Cc: Brendan Jackman Cc: Johannes Weiner Cc: Michal Hocko Cc: Sebastian Andrzej Siewior Cc: Steven Rostedt Cc: Suren Baghdasaryan Cc: Zi Yan Cc: Signed-off-by: Andrew Morton Signed-off-by: Sasha Levin Signed-off-by: Greg Kroah-Hartman --- mm/page_alloc.c | 47 +++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 39 insertions(+), 8 deletions(-) diff --git a/mm/page_alloc.c b/mm/page_alloc.c index 33b881489727f5..623f6e5b583ab6 100644 --- a/mm/page_alloc.c +++ b/mm/page_alloc.c @@ -166,6 +166,33 @@ static DEFINE_MUTEX(pcp_batch_high_lock); #define pcp_spin_unlock(ptr) \ pcpu_spin_unlock(lock, ptr) +/* + * With the UP spinlock implementation, when we spin_lock(&pcp->lock) (for i.e. + * a potentially remote cpu drain) and get interrupted by an operation that + * attempts pcp_spin_trylock(), we can't rely on the trylock failure due to UP + * spinlock assumptions making the trylock a no-op. So we have to turn that + * spin_lock() to a spin_lock_irqsave(). This works because on UP there are no + * remote cpu's so we can only be locking the only existing local one. + */ +#if defined(CONFIG_SMP) || defined(CONFIG_PREEMPT_RT) +static inline void __flags_noop(unsigned long *flags) { } +#define pcp_spin_lock_maybe_irqsave(ptr, flags) \ +({ \ + __flags_noop(&(flags)); \ + spin_lock(&(ptr)->lock); \ +}) +#define pcp_spin_unlock_maybe_irqrestore(ptr, flags) \ +({ \ + spin_unlock(&(ptr)->lock); \ + __flags_noop(&(flags)); \ +}) +#else +#define pcp_spin_lock_maybe_irqsave(ptr, flags) \ + spin_lock_irqsave(&(ptr)->lock, flags) +#define pcp_spin_unlock_maybe_irqrestore(ptr, flags) \ + spin_unlock_irqrestore(&(ptr)->lock, flags) +#endif + #ifdef CONFIG_USE_PERCPU_NUMA_NODE_ID DEFINE_PER_CPU(int, numa_node); EXPORT_PER_CPU_SYMBOL(numa_node); @@ -2555,6 +2582,7 @@ static int rmqueue_bulk(struct zone *zone, unsigned int order, bool decay_pcp_high(struct zone *zone, struct per_cpu_pages *pcp) { int high_min, to_drain, to_drain_batched, batch; + unsigned long UP_flags; bool todo = false; high_min = READ_ONCE(pcp->high_min); @@ -2574,9 +2602,9 @@ bool decay_pcp_high(struct zone *zone, struct per_cpu_pages *pcp) to_drain = pcp->count - pcp->high; while (to_drain > 0) { to_drain_batched = min(to_drain, batch); - spin_lock(&pcp->lock); + pcp_spin_lock_maybe_irqsave(pcp, UP_flags); free_pcppages_bulk(zone, to_drain_batched, pcp, 0); - spin_unlock(&pcp->lock); + pcp_spin_unlock_maybe_irqrestore(pcp, UP_flags); todo = true; to_drain -= to_drain_batched; @@ -2593,14 +2621,15 @@ bool decay_pcp_high(struct zone *zone, struct per_cpu_pages *pcp) */ void drain_zone_pages(struct zone *zone, struct per_cpu_pages *pcp) { + unsigned long UP_flags; int to_drain, batch; batch = READ_ONCE(pcp->batch); to_drain = min(pcp->count, batch); if (to_drain > 0) { - spin_lock(&pcp->lock); + pcp_spin_lock_maybe_irqsave(pcp, UP_flags); free_pcppages_bulk(zone, to_drain, pcp, 0); - spin_unlock(&pcp->lock); + pcp_spin_unlock_maybe_irqrestore(pcp, UP_flags); } } #endif @@ -2611,10 +2640,11 @@ void drain_zone_pages(struct zone *zone, struct per_cpu_pages *pcp) static void drain_pages_zone(unsigned int cpu, struct zone *zone) { struct per_cpu_pages *pcp = per_cpu_ptr(zone->per_cpu_pageset, cpu); + unsigned long UP_flags; int count; do { - spin_lock(&pcp->lock); + pcp_spin_lock_maybe_irqsave(pcp, UP_flags); count = pcp->count; if (count) { int to_drain = min(count, @@ -2623,7 +2653,7 @@ static void drain_pages_zone(unsigned int cpu, struct zone *zone) free_pcppages_bulk(zone, to_drain, pcp, 0); count -= to_drain; } - spin_unlock(&pcp->lock); + pcp_spin_unlock_maybe_irqrestore(pcp, UP_flags); } while (count); } @@ -6081,6 +6111,7 @@ static void zone_pcp_update_cacheinfo(struct zone *zone, unsigned int cpu) { struct per_cpu_pages *pcp; struct cpu_cacheinfo *cci; + unsigned long UP_flags; pcp = per_cpu_ptr(zone->per_cpu_pageset, cpu); cci = get_cpu_cacheinfo(cpu); @@ -6091,12 +6122,12 @@ static void zone_pcp_update_cacheinfo(struct zone *zone, unsigned int cpu) * This can reduce zone lock contention without hurting * cache-hot pages sharing. */ - spin_lock(&pcp->lock); + pcp_spin_lock_maybe_irqsave(pcp, UP_flags); if ((cci->per_cpu_data_slice_size >> PAGE_SHIFT) > 3 * pcp->batch) pcp->flags |= PCPF_FREE_HIGH_BATCH; else pcp->flags &= ~PCPF_FREE_HIGH_BATCH; - spin_unlock(&pcp->lock); + pcp_spin_unlock_maybe_irqrestore(pcp, UP_flags); } void setup_pcp_cacheinfo(unsigned int cpu) From 2d1bf4a7b8ed87ab32adaa40f53dfe332755451d Mon Sep 17 00:00:00 2001 From: Greg Kroah-Hartman Date: Wed, 21 Jan 2026 18:21:16 +0100 Subject: [PATCH 2601/3902] Revert "functionfs: fix the open/removal races" This reverts commit b49c766856fb5901490de577e046149ebf15e39d which is commit e5bf5ee266633cb18fff6f98f0b7d59a62819eee upstream. It has been reported to cause test problems in Android devices. As the other functionfs changes were not also backported at the same time, something is out of sync. So just revert this one for now and it can come back in the future as a patch series if it is tested. Cc: Al Viro Cc: Sasha Levin Signed-off-by: Greg Kroah-Hartman --- drivers/usb/gadget/function/f_fs.c | 53 ++++++------------------------ 1 file changed, 10 insertions(+), 43 deletions(-) diff --git a/drivers/usb/gadget/function/f_fs.c b/drivers/usb/gadget/function/f_fs.c index 69f6e3c0f7e00d..47cfbe41fdff8d 100644 --- a/drivers/usb/gadget/function/f_fs.c +++ b/drivers/usb/gadget/function/f_fs.c @@ -640,22 +640,13 @@ static ssize_t ffs_ep0_read(struct file *file, char __user *buf, static int ffs_ep0_open(struct inode *inode, struct file *file) { - struct ffs_data *ffs = inode->i_sb->s_fs_info; - int ret; + struct ffs_data *ffs = inode->i_private; - /* Acquire mutex */ - ret = ffs_mutex_lock(&ffs->mutex, file->f_flags & O_NONBLOCK); - if (ret < 0) - return ret; - - ffs_data_opened(ffs); - if (ffs->state == FFS_CLOSING) { - ffs_data_closed(ffs); - mutex_unlock(&ffs->mutex); + if (ffs->state == FFS_CLOSING) return -EBUSY; - } - mutex_unlock(&ffs->mutex); + file->private_data = ffs; + ffs_data_opened(ffs); return stream_open(inode, file); } @@ -1202,33 +1193,14 @@ static ssize_t ffs_epfile_io(struct file *file, struct ffs_io_data *io_data) static int ffs_epfile_open(struct inode *inode, struct file *file) { - struct ffs_data *ffs = inode->i_sb->s_fs_info; - struct ffs_epfile *epfile; - int ret; + struct ffs_epfile *epfile = inode->i_private; - /* Acquire mutex */ - ret = ffs_mutex_lock(&ffs->mutex, file->f_flags & O_NONBLOCK); - if (ret < 0) - return ret; - - if (!atomic_inc_not_zero(&ffs->opened)) { - mutex_unlock(&ffs->mutex); - return -ENODEV; - } - /* - * we want the state to be FFS_ACTIVE; FFS_ACTIVE alone is - * not enough, though - we might have been through FFS_CLOSING - * and back to FFS_ACTIVE, with our file already removed. - */ - epfile = smp_load_acquire(&inode->i_private); - if (unlikely(ffs->state != FFS_ACTIVE || !epfile)) { - mutex_unlock(&ffs->mutex); - ffs_data_closed(ffs); + if (WARN_ON(epfile->ffs->state != FFS_ACTIVE)) return -ENODEV; - } - mutex_unlock(&ffs->mutex); file->private_data = epfile; + ffs_data_opened(epfile->ffs); + return stream_open(inode, file); } @@ -1360,7 +1332,7 @@ static void ffs_dmabuf_put(struct dma_buf_attachment *attach) static int ffs_epfile_release(struct inode *inode, struct file *file) { - struct ffs_epfile *epfile = file->private_data; + struct ffs_epfile *epfile = inode->i_private; struct ffs_dmabuf_priv *priv, *tmp; struct ffs_data *ffs = epfile->ffs; @@ -2380,11 +2352,6 @@ static int ffs_epfiles_create(struct ffs_data *ffs) return 0; } -static void clear_one(struct dentry *dentry) -{ - smp_store_release(&dentry->d_inode->i_private, NULL); -} - static void ffs_epfiles_destroy(struct ffs_epfile *epfiles, unsigned count) { struct ffs_epfile *epfile = epfiles; @@ -2392,7 +2359,7 @@ static void ffs_epfiles_destroy(struct ffs_epfile *epfiles, unsigned count) for (; count; --count, ++epfile) { BUG_ON(mutex_is_locked(&epfile->mutex)); if (epfile->dentry) { - simple_recursive_removal(epfile->dentry, clear_one); + simple_recursive_removal(epfile->dentry, NULL); epfile->dentry = NULL; } } From d6a25e6ee3ec37a9bcba6ca00c4e7b397250acf7 Mon Sep 17 00:00:00 2001 From: Carlos Llamas Date: Mon, 5 Jan 2026 19:07:46 +0000 Subject: [PATCH 2602/3902] iommu/sva: include mmu_notifier.h header commit 4b5c493ff762bb0433529ca6870b284f0a2a5ca8 upstream. A call to mmu_notifier_arch_invalidate_secondary_tlbs() was introduced in commit e37d5a2d60a3 ("iommu/sva: invalidate stale IOTLB entries for kernel address space") but without explicitly adding its corresponding header file . This was evidenced while trying to enable compile testing support for IOMMU_SVA: config IOMMU_SVA select IOMMU_MM_DATA - bool + bool "Shared Virtual Addressing" if COMPILE_TEST The thing is for certain architectures this header file is indirectly included via . However, for others such as 32-bit arm the header is missing and it results in a build failure: $ make ARCH=arm allmodconfig [...] drivers/iommu/iommu-sva.c:340:3: error: call to undeclared function 'mmu_notifier_arch_invalidate_secondary_tlbs' [...] 340 | mmu_notifier_arch_invalidate_secondary_tlbs(iommu_mm->mm, start, end); | ^ Fix this by including the appropriate header file. Link: https://lkml.kernel.org/r/20260105190747.625082-1-cmllamas@google.com Fixes: e37d5a2d60a3 ("iommu/sva: invalidate stale IOTLB entries for kernel address space") Signed-off-by: Carlos Llamas Cc: Baolu Lu Cc: Jason Gunthorpe Cc: Joerg Roedel Cc: Kevin Tian Cc: Robin Murphy Cc: Vasant Hegde Cc: Will Deacon Signed-off-by: Andrew Morton Signed-off-by: Greg Kroah-Hartman --- drivers/iommu/iommu-sva.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/iommu/iommu-sva.c b/drivers/iommu/iommu-sva.c index d236aef80a8d50..e1e63c2be82b2a 100644 --- a/drivers/iommu/iommu-sva.c +++ b/drivers/iommu/iommu-sva.c @@ -3,6 +3,7 @@ * Helpers for IOMMU drivers implementing SVA */ #include +#include #include #include #include From 5dfbc5357c34bdf81c84aa78bc8e3d6d9ba10aad Mon Sep 17 00:00:00 2001 From: Greg Kroah-Hartman Date: Fri, 23 Jan 2026 11:21:37 +0100 Subject: [PATCH 2603/3902] Linux 6.18.7 Link: https://lore.kernel.org/r/20260121181418.537774329@linuxfoundation.org Tested-by: Salvatore Bonaccorso Tested-by: Ronald Warsow Tested-by: Shuah Khan Tested-by: Florian Fainelli Tested-by: Justin M. Forbes Tested-by: Takeshi Ogasawara Tested-by: Brett A C Sheffield Tested-by: Shung-Hsi Yu Tested-by: Jon Hunter Tested-by: Ron Economos Tested-by: Brett Mastbergen Tested-by: Peter Schneider Tested-by: Mark Brown Signed-off-by: Greg Kroah-Hartman --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 23a575ce425cd0..e3e3b82d94c013 100644 --- a/Makefile +++ b/Makefile @@ -1,7 +1,7 @@ # SPDX-License-Identifier: GPL-2.0 VERSION = 6 PATCHLEVEL = 18 -SUBLEVEL = 6 +SUBLEVEL = 7 EXTRAVERSION = NAME = Baby Opossum Posse From 68290f89bd12b4dc45fb36211ff9789fb3096ba3 Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Sat, 24 Jan 2026 14:48:37 +0100 Subject: [PATCH 2604/3902] Revert "cpuidle: apple: Do not load on unsupported Apple platforms" This reverts commit a8b65183b4b4630fd9a9c885a89fedcccfd881a2. --- drivers/cpuidle/cpuidle-apple.c | 8 -------- 1 file changed, 8 deletions(-) diff --git a/drivers/cpuidle/cpuidle-apple.c b/drivers/cpuidle/cpuidle-apple.c index 27b9144b979d3a..1dfb10cdb5e4d6 100644 --- a/drivers/cpuidle/cpuidle-apple.c +++ b/drivers/cpuidle/cpuidle-apple.c @@ -6,15 +6,12 @@ */ #include -#include #include #include #include #include #include -#define DEEP_WFI_STATE_RETENTION BIT(2) // retains base CPU registers in deep WFI - enum idle_state { STATE_WFI, STATE_PWRDOWN, @@ -149,11 +146,6 @@ static int __init apple_cpuidle_init(void) if (!of_machine_is_compatible("apple,arm-platform")) return 0; - if (!FIELD_GET(DEEP_WFI_STATE_RETENTION, read_sysreg(aidr_el1))) { - pr_info("cpuidle-apple: CPU does not retain state in deep WFI\n"); - return 0; - } - pdev = platform_device_register_simple("cpuidle-apple", -1, NULL, 0); if (IS_ERR(pdev)) { platform_driver_unregister(&apple_cpuidle_driver); From 5b77b7e73b6271e8b314119245756b63dad69f2b Mon Sep 17 00:00:00 2001 From: Yureka Date: Sat, 24 Jan 2026 10:02:15 +0100 Subject: [PATCH 2605/3902] cpuidle-apple: only load on machines where it is known to be needed This handles M4 which no longer has the Apple specific deep WFI mode but the register-based check still reports it as if it has. Attempting to use the code on M4 causes SErrors on attempting to write the s3_5_c15_c5_0 register. Signed-off-by: Yureka --- drivers/cpuidle/cpuidle-apple.c | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/drivers/cpuidle/cpuidle-apple.c b/drivers/cpuidle/cpuidle-apple.c index 1dfb10cdb5e4d6..b7504066d75aa8 100644 --- a/drivers/cpuidle/cpuidle-apple.c +++ b/drivers/cpuidle/cpuidle-apple.c @@ -146,6 +146,16 @@ static int __init apple_cpuidle_init(void) if (!of_machine_is_compatible("apple,arm-platform")) return 0; + if (!(of_machine_is_compatible("apple,t8103") || + of_machine_is_compatible("apple,t8112") || + of_machine_is_compatible("apple,t6000") || + of_machine_is_compatible("apple,t6001") || + of_machine_is_compatible("apple,t6002") || + of_machine_is_compatible("apple,t6020") || + of_machine_is_compatible("apple,t6021") || + of_machine_is_compatible("apple,t6022"))) + return 0; + pdev = platform_device_register_simple("cpuidle-apple", -1, NULL, 0); if (IS_ERR(pdev)) { platform_driver_unregister(&apple_cpuidle_driver); From a21d08e45e0b579872828f793eaface554443467 Mon Sep 17 00:00:00 2001 From: NoisyCoil Date: Sat, 17 Jan 2026 17:35:26 +0100 Subject: [PATCH 2606/3902] fixup! iio: common: Add AOP sensor drivers str::from_utf8 was stabilized in rustc 1.87, and the minimum supported rustc is currently 1.78, so builds fail with some supported rustc versions. The rest of the kernel uses core::str::from_utf8. Signed-off-by: NoisyCoil --- drivers/iio/common/aop_sensors/aop_als.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/iio/common/aop_sensors/aop_als.rs b/drivers/iio/common/aop_sensors/aop_als.rs index 324316c4be3465..41994e2f7ffbc2 100644 --- a/drivers/iio/common/aop_sensors/aop_als.rs +++ b/drivers/iio/common/aop_sensors/aop_als.rs @@ -30,7 +30,7 @@ fn get_lux_offset(aop: &dyn AOP, dev: &platform::Device, svc: &EPICService) -> R dev_warn!( dev.as_ref(), "Unknown sensor type {:?}", - str::from_utf8(&name) + core::str::from_utf8(&name) ); Err(EIO) } From b1ffa7c12d3c0afb5b78c72ec3e392ef53b3f8b3 Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Sat, 24 Jan 2026 14:14:52 +0100 Subject: [PATCH 2607/3902] drm/apple: Only assume RGB planes on internal displays are sRGB For external displays with EDID user space might use the colorimetry information therein and use color mapping with the expectation of using that color space. DCP's native color space is the correct choice for that. Fixes: 667ca85b1804 ("drm: apple: Assume all RGB planes are sRGB") Signed-off-by: Janne Grunau --- drivers/gpu/drm/apple/iomfb_template.c | 8 ++++++++ drivers/gpu/drm/apple/plane.c | 2 +- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/drivers/gpu/drm/apple/iomfb_template.c b/drivers/gpu/drm/apple/iomfb_template.c index c5f6765733d4bb..113a55b4a8a2b7 100644 --- a/drivers/gpu/drm/apple/iomfb_template.c +++ b/drivers/gpu/drm/apple/iomfb_template.c @@ -1362,6 +1362,14 @@ void DCP_FW_NAME(iomfb_flush)(struct apple_dcp *dcp, struct drm_crtc *crtc, stru req->surf_iova[l] = apple_state->iova; req->surf[l].base = apple_state->surf; + /* Use sRGB colorspace only for internal panels. External + * displays are expected to have EDID and user space can use + * the contained colorimetry information to provide native + * colors. + */ + if (dcp->connector_type == DRM_MODE_CONNECTOR_eDP && + req->surf[l].base.colorspace == DCP_COLORSPACE_BG_SRGB) + req->surf[l].base.colorspace = DCP_COLORSPACE_NATIVE; } if (!has_surface && !crtc_state->color_mgmt_changed) { diff --git a/drivers/gpu/drm/apple/plane.c b/drivers/gpu/drm/apple/plane.c index fac214f63090c8..df29b709600b9a 100644 --- a/drivers/gpu/drm/apple/plane.c +++ b/drivers/gpu/drm/apple/plane.c @@ -191,7 +191,7 @@ static enum dcp_colorspace get_colorspace(bool is_yuv, enum drm_color_encoding enc) { if (!is_yuv) - return DCP_COLORSPACE_BG_SRGB; + return DCP_COLORSPACE_NATIVE; switch (enc) { case DRM_COLOR_YCBCR_BT601: From 534f486d58d3309d5dda4dec7ff5912c0e8a93c1 Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Sat, 24 Jan 2026 15:24:56 +0100 Subject: [PATCH 2608/3902] Revert "irqchip/apple-aic: Add support for AICv3" This reverts commit 45c85eeea6b879db982a9798a08279ac320d8c13. --- drivers/irqchip/irq-apple-aic.c | 39 ++++----------------------------- 1 file changed, 4 insertions(+), 35 deletions(-) diff --git a/drivers/irqchip/irq-apple-aic.c b/drivers/irqchip/irq-apple-aic.c index cb7850b2dbb8eb..032d66dceb8ec4 100644 --- a/drivers/irqchip/irq-apple-aic.c +++ b/drivers/irqchip/irq-apple-aic.c @@ -54,7 +54,6 @@ #include #include #include -#include #include #include #include @@ -252,9 +251,6 @@ struct aic_info { u32 mask_set; u32 mask_clr; - u32 cap0_off; - u32 maxnumirq_off; - u32 die_stride; /* Features */ @@ -292,14 +288,6 @@ static const struct aic_info aic2_info __initconst = { .version = 2, .irq_cfg = AIC2_IRQ_CFG, - .cap0_off = AIC2_INFO1, - .maxnumirq_off = AIC2_INFO3, - - .fast_ipi = true, -}; - -static const struct aic_info aic3_info __initconst = { - .version = 3, .fast_ipi = true, .local_fast_ipi = true, @@ -322,10 +310,6 @@ static const struct of_device_id aic_info_match[] = { .compatible = "apple,aic2", .data = &aic2_info, }, - { - .compatible = "apple,aic3", - .data = &aic3_info, - }, {} }; @@ -640,7 +624,7 @@ static int aic_irq_domain_map(struct irq_domain *id, unsigned int irq, u32 type = FIELD_GET(AIC_EVENT_TYPE, hw); struct irq_chip *chip = &aic_chip; - if (ic->info.version == 2 || ic->info.version == 3) + if (ic->info.version == 2) chip = &aic2_chip; if (type == AIC_EVENT_TYPE_IRQ) { @@ -947,7 +931,6 @@ static void build_fiq_affinity(struct aic_irq_chip *ic, struct device_node *aff) static int __init aic_of_ic_init(struct device_node *node, struct device_node *parent) { - int ret; int i, die; u32 off, start_off; void __iomem *regs; @@ -991,24 +974,11 @@ static int __init aic_of_ic_init(struct device_node *node, struct device_node *p break; } - case 3: - /* read offsets from device tree for aic version 3 */ - /* extint-baseaddress? */ - ret = of_property_read_u32(node, "config-offset", &irqc->info.irq_cfg); - if (ret < 0) - return ret; - ret = of_property_read_u32(node, "cap0-offset", &irqc->info.cap0_off); - if (ret < 0) - return ret; - ret = of_property_read_u32(node, "maxnumirq-offset", &irqc->info.maxnumirq_off); - if (ret < 0) - return ret; - fallthrough; case 2: { u32 info1, info3; - info1 = aic_ic_read(irqc, irqc->info.cap0_off); - info3 = aic_ic_read(irqc, irqc->info.maxnumirq_off); + info1 = aic_ic_read(irqc, AIC2_INFO1); + info3 = aic_ic_read(irqc, AIC2_INFO3); irqc->nr_irq = FIELD_GET(AIC2_INFO1_NR_IRQ, info1); irqc->max_irq = FIELD_GET(AIC2_INFO3_MAX_IRQ, info3); @@ -1078,7 +1048,7 @@ static int __init aic_of_ic_init(struct device_node *node, struct device_node *p off += irqc->info.die_stride; } - if (irqc->info.version == 2 || irqc->info.version == 3) { + if (irqc->info.version == 2) { u32 config = aic_ic_read(irqc, AIC2_CONFIG); config |= AIC2_CONFIG_ENABLE; @@ -1129,4 +1099,3 @@ static int __init aic_of_ic_init(struct device_node *node, struct device_node *p IRQCHIP_DECLARE(apple_aic, "apple,aic", aic_of_ic_init); IRQCHIP_DECLARE(apple_aic2, "apple,aic2", aic_of_ic_init); -IRQCHIP_DECLARE(apple_aic3, "apple,aic3", aic_of_ic_init); From 76721d217e197bde479d43010d9d0ba6ab613044 Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Sat, 2 Nov 2024 22:44:51 +0100 Subject: [PATCH 2609/3902] irqchip/apple-aic: Add support for "apple,t8122-aic3" Introduce support for the new AICv3 hardware block in t8122 and t603x SoCs. AICv3 is similar to AICv2 but has an increased IRQ config offset. These MMIO offsets are coded as properties of the "aic,3" node in Apple's device tree. The actual offsets are the same for all SoCs starting from M3 through at least M5. So do not bother to follow suit but use AICv3 specific defines in the driver. The compatible string is SoC specific so future SoCs with AICv3 and different offsets would just use their own compatible string as base and add their new offsets. Signed-off-by: Janne Grunau --- drivers/irqchip/irq-apple-aic.c | 27 +++++++++++++++++++++++++-- 1 file changed, 25 insertions(+), 2 deletions(-) diff --git a/drivers/irqchip/irq-apple-aic.c b/drivers/irqchip/irq-apple-aic.c index 032d66dceb8ec4..ad04d1fcc5e7dc 100644 --- a/drivers/irqchip/irq-apple-aic.c +++ b/drivers/irqchip/irq-apple-aic.c @@ -54,6 +54,7 @@ #include #include #include +#include #include #include #include @@ -134,8 +135,15 @@ #define AIC2_IRQ_CFG 0x2000 +/* + * AIC v3 registers (MMIO) + */ + +#define AIC3_IRQ_CFG 0x10000 + /* * AIC2 registers are laid out like this, starting at AIC2_IRQ_CFG: + * AIC3 registers use the same layout but start at AIC3_IRQ_CFG: * * Repeat for each die: * IRQ_CFG: u32 * MAX_IRQS @@ -293,6 +301,15 @@ static const struct aic_info aic2_info __initconst = { .local_fast_ipi = true, }; +static const struct aic_info aic3_info __initconst = { + .version = 3, + + .irq_cfg = AIC3_IRQ_CFG, + + .fast_ipi = true, + .local_fast_ipi = true, +}; + static const struct of_device_id aic_info_match[] = { { .compatible = "apple,t8103-aic", @@ -310,6 +327,10 @@ static const struct of_device_id aic_info_match[] = { .compatible = "apple,aic2", .data = &aic2_info, }, + { + .compatible = "apple,t8122-aic3", + .data = &aic3_info, + }, {} }; @@ -624,7 +645,7 @@ static int aic_irq_domain_map(struct irq_domain *id, unsigned int irq, u32 type = FIELD_GET(AIC_EVENT_TYPE, hw); struct irq_chip *chip = &aic_chip; - if (ic->info.version == 2) + if (ic->info.version == 2 || ic->info.version == 3) chip = &aic2_chip; if (type == AIC_EVENT_TYPE_IRQ) { @@ -974,6 +995,7 @@ static int __init aic_of_ic_init(struct device_node *node, struct device_node *p break; } + case 3: case 2: { u32 info1, info3; @@ -1048,7 +1070,7 @@ static int __init aic_of_ic_init(struct device_node *node, struct device_node *p off += irqc->info.die_stride; } - if (irqc->info.version == 2) { + if (irqc->info.version == 2 || irqc->info.version == 3) { u32 config = aic_ic_read(irqc, AIC2_CONFIG); config |= AIC2_CONFIG_ENABLE; @@ -1099,3 +1121,4 @@ static int __init aic_of_ic_init(struct device_node *node, struct device_node *p IRQCHIP_DECLARE(apple_aic, "apple,aic", aic_of_ic_init); IRQCHIP_DECLARE(apple_aic2, "apple,aic2", aic_of_ic_init); +IRQCHIP_DECLARE(apple_aic3, "apple,t8122-aic3", aic_of_ic_init); From 64fedb905ea3c9e34adf95c25cab7f637d452a4a Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Sat, 24 Jan 2026 19:27:08 +0100 Subject: [PATCH 2610/3902] fixup! drm: apple: Move plane bits out of apple_drv/iomfb_flush --- drivers/gpu/drm/apple/plane.c | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/drivers/gpu/drm/apple/plane.c b/drivers/gpu/drm/apple/plane.c index df29b709600b9a..ec972b3467d479 100644 --- a/drivers/gpu/drm/apple/plane.c +++ b/drivers/gpu/drm/apple/plane.c @@ -303,11 +303,28 @@ static const struct drm_plane_helper_funcs apple_plane_helper_funcs = { .atomic_update = apple_plane_atomic_update, }; +// Duplicate drm_atomic_helper_plane_reset but allocate struct apple_plane_state +static void apple_plane_reset(struct drm_plane *plane) +{ + struct apple_plane_state *state = to_apple_plane_state(plane->state); + if (state) + __drm_atomic_helper_plane_destroy_state(&state->base); + + kfree(state); + plane->state = NULL; + state = kzalloc(sizeof(*state), GFP_KERNEL); + if (state) + __drm_atomic_helper_plane_reset(plane, &state->base); +} + static struct drm_plane_state * apple_plane_duplicate_state(struct drm_plane *plane) { struct apple_plane_state *apple_plane_state, *old_apple_plane_state; + if (!plane->state) + return NULL; + old_apple_plane_state = to_apple_plane_state(plane->state); apple_plane_state = kzalloc(sizeof(*apple_plane_state), GFP_KERNEL); if (!apple_plane_state) @@ -329,7 +346,7 @@ apple_plane_duplicate_state(struct drm_plane *plane) static const struct drm_plane_funcs apple_plane_funcs = { .update_plane = drm_atomic_helper_update_plane, .disable_plane = drm_atomic_helper_disable_plane, - .reset = drm_atomic_helper_plane_reset, + .reset = apple_plane_reset, .atomic_duplicate_state = apple_plane_duplicate_state, // .atomic_destroy_state = apple_plane_destroy_state, .atomic_destroy_state = drm_atomic_helper_plane_destroy_state, From 67e5cec167f41c6a07a635f9bc6a0ee5fe6c9f74 Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Mon, 26 Jan 2026 00:43:20 +0100 Subject: [PATCH 2611/3902] dt-bindings: arm: apple: Add M3 devices (t8112 and t603x) One noteable difference is that M3 Max has two variants variants in t6031 and t6034. T6034 appears to be smaller design with 14 CPU cores, 30 GPU cores and 300 GB/s memory bandwidth compared to t6031 with 16 CPU cores, 40 GPU cores and 400 GB/s memory bandwidth. These are the only apparent differences between those two SoCs. Signed-off-by: Janne Grunau --- .../devicetree/bindings/arm/apple.yaml | 56 +++++++++++++++++++ 1 file changed, 56 insertions(+) diff --git a/Documentation/devicetree/bindings/arm/apple.yaml b/Documentation/devicetree/bindings/arm/apple.yaml index 5c2629ec3d4cbc..3d0338f6f9cb22 100644 --- a/Documentation/devicetree/bindings/arm/apple.yaml +++ b/Documentation/devicetree/bindings/arm/apple.yaml @@ -96,6 +96,13 @@ description: | - MacBook Pro (13-inch, M2, 2022) - Mac mini (M2, 2023) + Devices based on the "M3" SoC: + + - MacBook Air (13-inch, M3, 2024) + - MacBook Air (15-inch, M3, 2024) + - MacBook Pro (14-inch, M3, 2023) + - iMac (24-inch, M3, 2023) + Devices based on the "M1 Pro", "M1 Max" and "M1 Ultra" SoCs: - MacBook Pro (14-inch, M1 Pro, 2021) @@ -116,6 +123,14 @@ description: | - Mac Studio (M2 Ultra, 2023) - Mac Pro (M2 Ultra, 2023) + Devices based on the "M3 Pro", "M3 Max" and "M3 Ultra" SoCs: + + - MacBook Pro (14-inch, M3 Pro, 2023) + - MacBook Pro (14-inch, M3 Max, 2023) + - MacBook Pro (16-inch, M3 Pro, 2023) + - MacBook Pro (16-inch, M3 Max, 2023) + - Mac Studio (M3 Ultra, 2025) + The compatible property should follow this format: compatible = "apple,", "apple,", "apple,arm-platform"; @@ -297,6 +312,17 @@ properties: - const: apple,t8112 - const: apple,arm-platform + - description: Apple M2 SoC based platforms + items: + - enum: + - apple,j504 # MacBook Pro (13-inch, M3, 2023) + - apple,j613 # MacBook Air (13-inch, M3, 2024) + - apple,j615 # MacBook Air (15-inch, M3, 2024) + - apple,j433 # iMac (24-inch, 2x USB-C, M3, 2023) + - apple,j434 # iMac (24-inch, 4x USB-C, M3, 2023) + - const: apple,t8122 + - const: apple,arm-platform + - description: Apple M1 Pro SoC based platforms items: - enum: @@ -347,6 +373,36 @@ properties: - const: apple,t6022 - const: apple,arm-platform + - description: Apple M3 Pro SoC based platforms + items: + - enum: + - apple,j514s # MacBook Pro (14-inch, M3 Pro, 2023) + - apple,j516s # MacBook Pro (16-inch, M3 Pro, 2023) + - const: apple,t6030 + - const: apple,arm-platform + + - description: Apple M3 Max SoC based platforms + oneOf: + - items: + - enum: + - apple,j414c # MacBook Pro (14-inch, M3 Max, 16 cores, 2023) + - apple,j416c # MacBook Pro (16-inch, M3 Max, 16 cores, 2023) + - const: apple,t6031 + - const: apple,arm-platform + - items: + - enum: + - apple,j414m # MacBook Pro (14-inch, M3 Max, 14 cores, 2023) + - apple,j416m # MacBook Pro (16-inch, M3 Max, 14 cores, 2023) + - const: apple,t6034 + - const: apple,arm-platform + + - description: Apple M3 Ultra SoC based platforms + items: + - enum: + - apple,j575d # Mac Studio (M3 Ultra, 2025) + - const: apple,t6032 + - const: apple,arm-platform + additionalProperties: true ... From d1b8a2a792a8ddbf77cc4b119b3c7528d0d1ee40 Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Tue, 27 Jan 2026 09:18:50 +0100 Subject: [PATCH 2612/3902] dt-bindings: arm: cpus: Add Apple M3 cores Add the following CPU cores: - apple,everest: M3 performance cores - apple,sawtooth: M3 efficiency cores Signed-off-by: Janne Grunau --- Documentation/devicetree/bindings/arm/cpus.yaml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Documentation/devicetree/bindings/arm/cpus.yaml b/Documentation/devicetree/bindings/arm/cpus.yaml index 736b7ab1bd0a02..e1fd73281657df 100644 --- a/Documentation/devicetree/bindings/arm/cpus.yaml +++ b/Documentation/devicetree/bindings/arm/cpus.yaml @@ -85,11 +85,13 @@ properties: - apple,avalanche - apple,blizzard - apple,cyclone + - apple,everest - apple,firestorm - apple,hurricane-zephyr - apple,icestorm - apple,mistral - apple,monsoon + - apple,sawtooth - apple,twister - apple,typhoon - arm,arm710t From 19a07b24ddbd963a85597d89ead57588e6a85f77 Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Sat, 24 Jan 2026 12:12:18 +0100 Subject: [PATCH 2613/3902] dt-bindings: interrupt-controller: apple,aic2: Add AICv3 AIC version 3 as found on the Apple M3 (t8122) is very similar to AICv2 in its base functionality. It can use the same device tree bindings as AICv2 so add it to the AICv2 bindings. This interrupt controller is used on all Apple SoCs starting with M3 up to at least M5. The only apparent difference is the increased IRQ config offset. Apple's device tree codes this new offset as property of the "aic" node but the value stayed constant for all SoCs with "aic,3". Since the SoC specific compatible "apple,t8122-aic3" will be used in the driver this offset can remain a driver implementation detail. Signed-off-by: Janne Grunau --- .../interrupt-controller/apple,aic2.yaml | 39 ++++++++++++++----- 1 file changed, 30 insertions(+), 9 deletions(-) diff --git a/Documentation/devicetree/bindings/interrupt-controller/apple,aic2.yaml b/Documentation/devicetree/bindings/interrupt-controller/apple,aic2.yaml index ee5a0dfff43781..f43249c469bea3 100644 --- a/Documentation/devicetree/bindings/interrupt-controller/apple,aic2.yaml +++ b/Documentation/devicetree/bindings/interrupt-controller/apple,aic2.yaml @@ -4,10 +4,10 @@ $id: http://devicetree.org/schemas/interrupt-controller/apple,aic2.yaml# $schema: http://devicetree.org/meta-schemas/core.yaml# -title: Apple Interrupt Controller 2 +title: Apple Interrupt Controller 2 and 3 maintainers: - - Hector Martin + - Janne Grunau description: | The Apple Interrupt Controller 2 is a simple interrupt controller present on @@ -28,14 +28,24 @@ description: | which do not go through a discrete interrupt controller. It also handles FIQ-based Fast IPIs. + The Apple Interrupt Controller 3 is in its base functionality very similar to + the Apple Interrupt Controller 2 and uses the same device tree bindings. It is + found on Apple ARM SoCs platforms starting with t8122 (M3). + properties: compatible: - items: - - enum: - - apple,t8112-aic - - apple,t6000-aic - - apple,t6020-aic - - const: apple,aic2 + oneOf: + - items: + - enum: + - apple,t8112-aic + - apple,t6000-aic + - apple,t6020-aic + - const: apple,aic2 + - items: + - enum: + - apple,t6030-aic3 + - const: apple,t8122-aic3 + - const: apple,t8122-aic3 interrupt-controller: true @@ -117,7 +127,9 @@ allOf: properties: compatible: contains: - const: apple,t8112-aic + enum: + - apple,t8112-aic + - apple,t8122-aic3 then: properties: '#interrupt-cells': @@ -141,4 +153,13 @@ examples: <0x2 0x8e10c000 0x0 0x4>; reg-names = "core", "event"; }; + + aic-t8122: interrupt-controller@2d1000000 { + compatible = "apple,t8122-aic3"; + #interrupt-cells = <3>; + interrupt-controller; + reg = <0x2 0xd1000000 0x0 0xc000>, + <0x2 0xd1040000 0x0 0x4>; + reg-names = "core", "event"; + }; }; From cb85c24353639e1c8c5dcd2c39de683a090c3b06 Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Mon, 26 Jan 2026 20:15:10 +0100 Subject: [PATCH 2614/3902] dt-bindings: arm: apple: apple,pmgr: Add M3 compatibles The block on Apple M3 and M3 Pro, Max and Ultra SoCs is compatible with the existing driver so add their per-SoC compatibles. Add "apple,t8122-pmgr" for the M3 and "apple,t6030-pmgr" for M3 Pro, Max and Ultra as compatibles. --- Documentation/devicetree/bindings/arm/apple/apple,pmgr.yaml | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/Documentation/devicetree/bindings/arm/apple/apple,pmgr.yaml b/Documentation/devicetree/bindings/arm/apple/apple,pmgr.yaml index b88f41a225a385..44f1bd3e9e2491 100644 --- a/Documentation/devicetree/bindings/arm/apple/apple,pmgr.yaml +++ b/Documentation/devicetree/bindings/arm/apple/apple,pmgr.yaml @@ -36,7 +36,10 @@ properties: - const: syscon - const: simple-mfd - items: - - const: apple,t6020-pmgr + - enum: + - apple,t6020-pmgr + - apple,t6030-pmgr + - apple,t8122-pmgr - const: apple,t8103-pmgr - const: syscon - const: simple-mfd From 013e95f0d114bac831fecd40ce580720179b68c0 Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Mon, 26 Jan 2026 20:20:36 +0100 Subject: [PATCH 2615/3902] dt-bindings: power: apple,pmgr-pwrstate: Add M3 compatibles The blocks on Apple M3 and M3 Pro, Max and Ultra SoCs are compatible with the existing driver so add their per-SoC compatibles. Add "apple,t8122-pmgr-pwrstate" for the M3 and "apple,t6030-pmgr-pwrstate" for M3 Pro, Max and Ultra as compatibles. --- .../devicetree/bindings/power/apple,pmgr-pwrstate.yaml | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/Documentation/devicetree/bindings/power/apple,pmgr-pwrstate.yaml b/Documentation/devicetree/bindings/power/apple,pmgr-pwrstate.yaml index caf15188099921..542c46d489a7e1 100644 --- a/Documentation/devicetree/bindings/power/apple,pmgr-pwrstate.yaml +++ b/Documentation/devicetree/bindings/power/apple,pmgr-pwrstate.yaml @@ -43,7 +43,10 @@ properties: - apple,t6000-pmgr-pwrstate - const: apple,pmgr-pwrstate - items: - - const: apple,t6020-pmgr-pwrstate + - enum: + - apple,t6020-pmgr-pwrstate + - apple,t6030-pmgr-pwrstate + - apple,t8122-pmgr-pwrstate - const: apple,t8103-pmgr-pwrstate reg: From 349c970cdd47b1b335297a356a8e3c9b563eeece Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Tue, 27 Jan 2026 09:29:55 +0100 Subject: [PATCH 2616/3902] dt-bindings: watchdog: apple,wdt: Add Apple M3 compatibles The watchdog on Apple silicon M3 SoCs is compatible with the t8103 (M1) one. Add "apple,t8122-wdt" and "apple,t6030-wdt" as compatibles for M3 and M3 Pro, Max and Ultra. Signed-off-by: Janne Grunau --- Documentation/devicetree/bindings/watchdog/apple,wdt.yaml | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/Documentation/devicetree/bindings/watchdog/apple,wdt.yaml b/Documentation/devicetree/bindings/watchdog/apple,wdt.yaml index 05602678c070d7..db0d11aa0a3e15 100644 --- a/Documentation/devicetree/bindings/watchdog/apple,wdt.yaml +++ b/Documentation/devicetree/bindings/watchdog/apple,wdt.yaml @@ -16,7 +16,10 @@ properties: compatible: oneOf: - items: - - const: apple,t6020-wdt + - enum: + - apple,t6020-wdt + - apple,t6030-wdt + - apple,t8122-wdt - const: apple,t8103-wdt - items: - enum: From d6704d1be4103ca80642e6dfc0a7afd9e38c5268 Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Tue, 27 Jan 2026 09:27:14 +0100 Subject: [PATCH 2617/3902] dt-bindings: pinctrl: apple,pinctrl: Add Apple M3 compatibles The pin controller on Apple silicon M3 SoCs is compatible with the t8103 (M1) one. Add "apple,t8122-pinctrl" and "apple,t6030-pinctrl" as compatibles for M3 and M3 Pro, Max and Ultra. Signed-off-by: Janne Grunau --- Documentation/devicetree/bindings/pinctrl/apple,pinctrl.yaml | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/Documentation/devicetree/bindings/pinctrl/apple,pinctrl.yaml b/Documentation/devicetree/bindings/pinctrl/apple,pinctrl.yaml index 665ec79a69f1db..1d9e101745fbc7 100644 --- a/Documentation/devicetree/bindings/pinctrl/apple,pinctrl.yaml +++ b/Documentation/devicetree/bindings/pinctrl/apple,pinctrl.yaml @@ -18,7 +18,10 @@ properties: compatible: oneOf: - items: - - const: apple,t6020-pinctrl + - enum: + - apple,t6020-pinctrl + - apple,t6030-pinctrl + - apple,t8122-pinctrl - const: apple,t8103-pinctrl - items: # Do not add additional SoC to this list. From 5e2c1c0bebd3d74dd059ca7819f5d7629d33fe02 Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Tue, 27 Jan 2026 09:22:29 +0100 Subject: [PATCH 2618/3902] dt-bindings: i2c: apple,i2c: Add Apple M3 compatibles The i2c block on Apple silicon M3 SoCs is compatible with the t8103 (M1) one. Add "apple,t8122-i2c" and "apple,t6030-i2c" as compatibles for M3 and M3 Pro, Max and Ultra. Signed-off-by: Janne Grunau --- Documentation/devicetree/bindings/i2c/apple,i2c.yaml | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/Documentation/devicetree/bindings/i2c/apple,i2c.yaml b/Documentation/devicetree/bindings/i2c/apple,i2c.yaml index 500a965bdb7a84..ea320e8a22b02e 100644 --- a/Documentation/devicetree/bindings/i2c/apple,i2c.yaml +++ b/Documentation/devicetree/bindings/i2c/apple,i2c.yaml @@ -22,7 +22,10 @@ properties: compatible: oneOf: - items: - - const: apple,t6020-i2c + - enum: + - apple,t6020-i2c + - apple,t6030-i2c + - apple,t8122-i2c - const: apple,t8103-i2c - items: - enum: From 04d3eae7ae4cd9cc6cf52b0ad8681db816531da6 Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Tue, 27 Jan 2026 09:32:38 +0100 Subject: [PATCH 2619/3902] dt-bindings: pwm: apple,s5l-fpwm: Add Apple M3 compatibles The PWM controller on Apple silicon M3 SoCs is compatible with the t8103 (M1) one. Add "apple,t8122-pinctrl" and "apple,t6030-pinctrl" as compatibles for M3 and M3 Pro, Max and Ultra. Note that SoCs of the t603{0,1,2,4} family share "apple,t6030-fpwm" as compatible where the hardware is 100% compatible, which is usually the case in this highly related set of SoCs. Signed-off-by: Janne Grunau --- Documentation/devicetree/bindings/pwm/apple,s5l-fpwm.yaml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Documentation/devicetree/bindings/pwm/apple,s5l-fpwm.yaml b/Documentation/devicetree/bindings/pwm/apple,s5l-fpwm.yaml index 04519b0c581d0e..dffb72896da47b 100644 --- a/Documentation/devicetree/bindings/pwm/apple,s5l-fpwm.yaml +++ b/Documentation/devicetree/bindings/pwm/apple,s5l-fpwm.yaml @@ -18,8 +18,10 @@ properties: - enum: - apple,t8103-fpwm - apple,t8112-fpwm + - apple,t8122-fpwm - apple,t6000-fpwm - apple,t6020-fpwm + - apple,t6030-fpwm - const: apple,s5l-fpwm reg: From c12c61e9c0c52bfc97669154843f6e48df3e1422 Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Fri, 8 Mar 2024 11:39:08 +0100 Subject: [PATCH 2620/3902] arm64: dts: apple: Initial t8122 (M3) device tree Contains a minimal device tree for t8122-j504 (Macbook Pro (14-inch, M3, 2023)) with CPU cores, interrupt controller, power states, watchdog, serial, pin controller, i2c, PWM based keyboard LED illumination and the boot framebuffer. Signed-off-by: Janne Grunau --- arch/arm64/boot/dts/apple/Makefile | 1 + arch/arm64/boot/dts/apple/t8122-j504.dts | 63 ++ arch/arm64/boot/dts/apple/t8122-jxxx.dtsi | 48 + arch/arm64/boot/dts/apple/t8122-pmgr.dtsi | 1165 +++++++++++++++++++++ arch/arm64/boot/dts/apple/t8122.dtsi | 451 ++++++++ 5 files changed, 1728 insertions(+) create mode 100644 arch/arm64/boot/dts/apple/t8122-j504.dts create mode 100644 arch/arm64/boot/dts/apple/t8122-jxxx.dtsi create mode 100644 arch/arm64/boot/dts/apple/t8122-pmgr.dtsi create mode 100644 arch/arm64/boot/dts/apple/t8122.dtsi diff --git a/arch/arm64/boot/dts/apple/Makefile b/arch/arm64/boot/dts/apple/Makefile index 4eebcd85c90fcf..a17f75bd5a2750 100644 --- a/arch/arm64/boot/dts/apple/Makefile +++ b/arch/arm64/boot/dts/apple/Makefile @@ -91,3 +91,4 @@ dtb-$(CONFIG_ARCH_APPLE) += t8112-j413.dtb dtb-$(CONFIG_ARCH_APPLE) += t8112-j415.dtb dtb-$(CONFIG_ARCH_APPLE) += t8112-j473.dtb dtb-$(CONFIG_ARCH_APPLE) += t8112-j493.dtb +dtb-$(CONFIG_ARCH_APPLE) += t8122-j504.dtb diff --git a/arch/arm64/boot/dts/apple/t8122-j504.dts b/arch/arm64/boot/dts/apple/t8122-j504.dts new file mode 100644 index 00000000000000..c876a447d4f9c5 --- /dev/null +++ b/arch/arm64/boot/dts/apple/t8122-j504.dts @@ -0,0 +1,63 @@ +// SPDX-License-Identifier: GPL-2.0+ OR MIT +/* + * Apple MacBook Pro (14-inch, M3, 2023) + * + * target-type: J504 + * + * Copyright The Asahi Linux Contributors + */ + +/dts-v1/; + +#include "t8122.dtsi" +#include "t8122-jxxx.dtsi" +#include + +/ { + compatible = "apple,j504", "apple,t8122", "apple,arm-platform"; + model = "Apple MacBook Pro (14-inch, M3, 2023)"; + + led-controller { + compatible = "pwm-leds"; + led-0 { + pwms = <&fpwm1 0 40000>; + label = "kbd_backlight"; + function = LED_FUNCTION_KBD_BACKLIGHT; + color = ; + max-brightness = <255>; + default-state = "keep"; + }; + }; +}; + +&framebuffer0 { + // panel = &panel; + power-domains = <&ps_disp_cpu>, <&ps_dptx_ext_phy>; +}; + +&i2c0 { + status = "okay"; + + hpm0: usb-pd@38 { + compatible = "apple,cd321x"; + reg = <0x38>; + interrupt-parent = <&pinctrl_ap>; + interrupts = <8 IRQ_TYPE_LEVEL_LOW>; + interrupt-names = "irq"; + }; + + hpm1: usb-pd@3f { + compatible = "apple,cd321x"; + reg = <0x3f>; + interrupt-parent = <&pinctrl_ap>; + interrupts = <8 IRQ_TYPE_LEVEL_LOW>; + interrupt-names = "irq"; + }; +}; + + + +&fpwm1 { + status = "okay"; +}; + diff --git a/arch/arm64/boot/dts/apple/t8122-jxxx.dtsi b/arch/arm64/boot/dts/apple/t8122-jxxx.dtsi new file mode 100644 index 00000000000000..fedf09bf22c194 --- /dev/null +++ b/arch/arm64/boot/dts/apple/t8122-jxxx.dtsi @@ -0,0 +1,48 @@ +// SPDX-License-Identifier: GPL-2.0+ OR MIT +/* + * Apple M3 MacBook Air/Pro and iMac (M3, 2023/2024) + * + * This file contains parts common to all Apple M3 devices using the t8122. + * + * target-type: J433, J434, J504, J513, J515 + * + * Copyright The Asahi Linux Contributors + */ + +/ { + aliases { + serial0 = &serial0; + }; + + chosen { + #address-cells = <2>; + #size-cells = <2>; + ranges; + + stdout-path = "serial0"; + + framebuffer0: framebuffer@0 { + compatible = "apple,simple-framebuffer", "simple-framebuffer"; + reg = <0 0 0 0>; /* To be filled by loader */ + power-domains = <&ps_disp_cpu>; + /* Format properties will be added by loader */ + status = "disabled"; + }; + }; + + reserved-memory { + #address-cells = <2>; + #size-cells = <2>; + ranges; + /* To be filled by loader */ + }; + + memory@800000000 { + device_type = "memory"; + reg = <0x8 0 0x2 0>; /* To be filled by loader */ + }; +}; + +&serial0 { + status = "okay"; +}; diff --git a/arch/arm64/boot/dts/apple/t8122-pmgr.dtsi b/arch/arm64/boot/dts/apple/t8122-pmgr.dtsi new file mode 100644 index 00000000000000..e5c5602b85fe66 --- /dev/null +++ b/arch/arm64/boot/dts/apple/t8122-pmgr.dtsi @@ -0,0 +1,1165 @@ +// SPDX-License-Identifier: GPL-2.0+ OR MIT +/* + * PMGR Power domains for the Apple T8122 "M3" SoC + * + * Copyright The Asahi Linux Contributors + */ + +&pmgr { + ps_ecpm: power-controller@40 { + compatible = "apple,t8122-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + reg = <0x40 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "ecpm"; + }; + + ps_pcpm: power-controller@48 { + compatible = "apple,t8122-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + reg = <0x48 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "pcpm"; + }; + + ps_sbr: power-controller@100 { + compatible = "apple,t8122-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + reg = <0x100 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "sbr"; + apple,always-on; /* Core device */ + }; + + ps_msg: power-controller@108 { + compatible = "apple,t8122-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + reg = <0x108 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "msg"; + }; + + ps_aic: power-controller@110 { + compatible = "apple,t8122-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + reg = <0x110 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "aic"; + apple,always-on; /* Core device */ + }; + + ps_dwi: power-controller@118 { + compatible = "apple,t8122-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + reg = <0x118 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "dwi"; + }; + + ps_gpio: power-controller@120 { + compatible = "apple,t8122-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + reg = <0x120 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "gpio"; + }; + + ps_pms_busif: power-controller@128 { + compatible = "apple,t8122-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + reg = <0x128 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "pms_busif"; + apple,always-on; /* Core device */ + }; + + ps_pms: power-controller@130 { + compatible = "apple,t8122-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + reg = <0x130 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "pms"; + apple,always-on; /* Core device */ + }; + + ps_pms_fpwm0: power-controller@138 { + compatible = "apple,t8122-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + reg = <0x138 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "pms_fpwm0"; + power-domains = <&ps_pms>; + }; + + ps_pms_fpwm1: power-controller@140 { + compatible = "apple,t8122-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + reg = <0x140 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "pms_fpwm1"; + power-domains = <&ps_pms>; + }; + + ps_pms_fpwm2: power-controller@148 { + compatible = "apple,t8122-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + reg = <0x148 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "pms_fpwm2"; + power-domains = <&ps_pms>; + }; + + ps_pms_fpwm3: power-controller@150 { + compatible = "apple,t8122-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + reg = <0x150 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "pms_fpwm3"; + power-domains = <&ps_pms>; + }; + + ps_pms_fpwm4: power-controller@158 { + compatible = "apple,t8122-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + reg = <0x158 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "pms_fpwm4"; + power-domains = <&ps_pms>; + }; + + ps_pms_c1ppt: power-controller@160 { + compatible = "apple,t8122-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + reg = <0x160 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "pms_c1ppt"; + }; + + ps_soc_rc: power-controller@168 { + compatible = "apple,t8122-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + reg = <0x168 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "soc_rc"; + }; + + ps_soc_dpe: power-controller@170 { + compatible = "apple,t8122-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + reg = <0x170 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "soc_dpe"; + apple,always-on; + }; + + ps_pmgr_soc_ocla: power-controller@178 { + compatible = "apple,t8122-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + reg = <0x178 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "pmgr_soc_ocla"; + power-domains = <&ps_pms>; + }; + + ps_ispsens0: power-controller@180 { + compatible = "apple,t8122-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + reg = <0x180 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "ispsens0"; + }; + + ps_ispsens1: power-controller@188 { + compatible = "apple,t8122-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + reg = <0x188 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "ispsens1"; + }; + + ps_ispsens2: power-controller@190 { + compatible = "apple,t8122-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + reg = <0x190 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "ispsens2"; + }; + + ps_ispsens3: power-controller@198 { + compatible = "apple,t8122-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + reg = <0x198 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "ispsens3"; + }; + + ps_aft0: power-controller@1a8 { + compatible = "apple,t8122-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + reg = <0x1a8 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "aft0"; + }; + + ps_ioa0: power-controller@1b0 { + compatible = "apple,t8122-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + reg = <0x1b0 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "ioa0"; + apple,always-on; + }; + + ps_ap_tmm: power-controller@1b8 { + compatible = "apple,t8122-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + reg = <0x1b8 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "ap_tmm"; + }; + + ps_disp_sys: power-controller@1d8 { + compatible = "apple,t8122-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + reg = <0x1d8 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "disp_sys"; + apple,always-on; /* TODO: figure out if we can enable PM here */ + }; + + ps_gfx: power-controller@1e0 { + compatible = "apple,t8122-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + reg = <0x1e0 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "gfx"; + }; + + ps_isp_sys: power-controller@1e8 { + compatible = "apple,t8122-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + reg = <0x1e8 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "isp_sys"; + }; + + ps_avd_sys: power-controller@1f0 { + compatible = "apple,t8122-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + reg = <0x1f0 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "avd_sys"; + }; + + ps_jpg: power-controller@200 { + compatible = "apple,t8122-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + reg = <0x200 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "jpg"; + }; + + ps_disp_fe: power-controller@208 { + compatible = "apple,t8122-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + reg = <0x208 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "disp_fe"; + power-domains = <&ps_disp_sys>; + apple,always-on; /* TODO: figure out if we can enable PM here */ + }; + + ps_sio_cpu: power-controller@210 { + compatible = "apple,t8122-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + reg = <0x210 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "sio_cpu"; + }; + + ps_fpwm0: power-controller@218 { + compatible = "apple,t8122-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + reg = <0x218 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "fpwm0"; + }; + + ps_fpwm1: power-controller@220 { + compatible = "apple,t8122-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + reg = <0x220 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "fpwm1"; + }; + + ps_fpwm2: power-controller@228 { + compatible = "apple,t8122-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + reg = <0x228 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "fpwm2"; + }; + + ps_i2c0: power-controller@230 { + compatible = "apple,t8122-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + reg = <0x230 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "i2c0"; + }; + + ps_i2c1: power-controller@238 { + compatible = "apple,t8122-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + reg = <0x238 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "i2c1"; + }; + + ps_i2c2: power-controller@240 { + compatible = "apple,t8122-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + reg = <0x240 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "i2c2"; + }; + + ps_i2c3: power-controller@248 { + compatible = "apple,t8122-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + reg = <0x248 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "i2c3"; + }; + + ps_i2c4: power-controller@250 { + compatible = "apple,t8122-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + reg = <0x250 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "i2c4"; + }; + + ps_i2c5: power-controller@258 { + compatible = "apple,t8122-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + reg = <0x258 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "i2c5"; + }; + + ps_i2c6: power-controller@260 { + compatible = "apple,t8122-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + reg = <0x260 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "i2c6"; + }; + + ps_i2c7: power-controller@268 { + compatible = "apple,t8122-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + reg = <0x268 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "i2c7"; + }; + + ps_i2c8: power-controller@270 { + compatible = "apple,t8122-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + reg = <0x270 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "i2c8"; + }; + + ps_spi_p: power-controller@278 { + compatible = "apple,t8122-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + reg = <0x278 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "spi_p"; + }; + + ps_uart_p: power-controller@280 { + compatible = "apple,t8122-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + reg = <0x280 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "uart_p"; + }; + + ps_audio_p: power-controller@288 { + compatible = "apple,t8122-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + reg = <0x288 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "audio_p"; + }; + + ps_aes: power-controller@290 { + compatible = "apple,t8122-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + reg = <0x290 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "aes"; + }; + + ps_spi0: power-controller@298 { + compatible = "apple,t8122-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + reg = <0x298 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "spi0"; + power-domains = <&ps_spi_p>; + }; + + ps_spi1: power-controller@2a0 { + compatible = "apple,t8122-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + reg = <0x2a0 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "spi1"; + power-domains = <&ps_spi_p>; + }; + + ps_spi2: power-controller@2a8 { + compatible = "apple,t8122-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + reg = <0x2a8 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "spi2"; + power-domains = <&ps_spi_p>; + }; + + ps_spi3: power-controller@2b0 { + compatible = "apple,t8122-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + reg = <0x2b0 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "spi3"; + power-domains = <&ps_spi_p>; + }; + + ps_spi4: power-controller@2b8 { + compatible = "apple,t8122-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + reg = <0x2b8 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "spi4"; + power-domains = <&ps_spi_p>; + }; + + ps_spi5: power-controller@2c0 { + compatible = "apple,t8122-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + reg = <0x2c0 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "spi5"; + power-domains = <&ps_spi_p>; + }; + + ps_qspi: power-controller@2c8 { + compatible = "apple,t8122-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + reg = <0x2c8 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "qspi"; + power-domains = <&ps_spi_p>; + }; + + ps_uart_n: power-controller@2d0 { + compatible = "apple,t8122-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + reg = <0x2d0 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "uart_n"; + power-domains = <&ps_uart_p>; + }; + + ps_uart0: power-controller@2d8 { + compatible = "apple,t8122-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + reg = <0x2d8 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "uart0"; + power-domains = <&ps_uart_p>; + }; + + ps_uart1: power-controller@2e0 { + compatible = "apple,t8122-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + reg = <0x2e0 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "uart1"; + power-domains = <&ps_uart_p>; + }; + + ps_uart2: power-controller@2e8 { + compatible = "apple,t8122-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + reg = <0x2e8 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "uart2"; + power-domains = <&ps_uart_p>; + }; + + ps_uart3: power-controller@2f0 { + compatible = "apple,t8122-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + reg = <0x2f0 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "uart3"; + power-domains = <&ps_uart_p>; + }; + + ps_uart4: power-controller@2f8 { + compatible = "apple,t8122-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + reg = <0x2f8 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "uart4"; + power-domains = <&ps_uart_p>; + }; + + ps_uart5: power-controller@300 { + compatible = "apple,t8122-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + reg = <0x300 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "uart5"; + power-domains = <&ps_uart_p>; + }; + + ps_uart6: power-controller@308 { + compatible = "apple,t8122-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + reg = <0x308 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "uart6"; + power-domains = <&ps_uart_p>; + }; + + ps_sio_adma: power-controller@310 { + compatible = "apple,t8122-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + reg = <0x310 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "sio_adma"; + power-domains = <&ps_fpwm0>; + }; + + ps_dpa0: power-controller@318 { + compatible = "apple,t8122-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + reg = <0x318 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "dpa0"; + power-domains = <&ps_audio_p>; + }; + + ps_dcs0: power-controller@330 { + compatible = "apple,t8122-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + reg = <0x330 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "dcs0"; + apple,always-on; + }; + + ps_dcs2: power-controller@338 { + compatible = "apple,t8122-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + reg = <0x338 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "dcs2"; + apple,always-on; + }; + + ps_dcs1: power-controller@340 { + compatible = "apple,t8122-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + reg = <0x340 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "dcs1"; + apple,always-on; + }; + + ps_dcs3: power-controller@348 { + compatible = "apple,t8122-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + reg = <0x348 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "dcs3"; + apple,always-on; + }; + + ps_dcs4: power-controller@358 { + compatible = "apple,t8122-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + reg = <0x358 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "dcs4"; + apple,always-on; + }; + + ps_dcs5: power-controller@360 { + compatible = "apple,t8122-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + reg = <0x360 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "dcs5"; + apple,always-on; + }; + + ps_dcs6: power-controller@368 { + compatible = "apple,t8122-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + reg = <0x368 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "dcs6"; + apple,always-on; + }; + + ps_dcs7: power-controller@370 { + compatible = "apple,t8122-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + reg = <0x370 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "dcs7"; + apple,always-on; + }; + + ps_dpa1: power-controller@378 { + compatible = "apple,t8122-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + reg = <0x378 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "dpa1"; + power-domains = <&ps_audio_p>; + }; + + ps_dpa2: power-controller@380 { + compatible = "apple,t8122-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + reg = <0x380 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "dpa2"; + power-domains = <&ps_audio_p>; + }; + + ps_dpa3: power-controller@388 { + compatible = "apple,t8122-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + reg = <0x388 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "dpa3"; + power-domains = <&ps_audio_p>; + }; + + ps_dpa4: power-controller@390 { + compatible = "apple,t8122-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + reg = <0x390 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "dpa4"; + power-domains = <&ps_audio_p>; + }; + + ps_mca0: power-controller@398 { + compatible = "apple,t8122-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + reg = <0x398 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "mca0"; + power-domains = <&ps_sio_adma>, <&ps_audio_p>; + }; + + ps_mca1: power-controller@3a0 { + compatible = "apple,t8122-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + reg = <0x3a0 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "mca1"; + power-domains = <&ps_sio_adma>, <&ps_audio_p>; + }; + + ps_mca2: power-controller@3a8 { + compatible = "apple,t8122-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + reg = <0x3a8 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "mca2"; + power-domains = <&ps_sio_adma>, <&ps_audio_p>; + }; + + ps_trace_fab: power-controller@3b0 { + compatible = "apple,t8122-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + reg = <0x3b0 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "trace_fab"; + }; + + ps_mca3: power-controller@3b8 { + compatible = "apple,t8122-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + reg = <0x3b8 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "mca3"; + power-domains = <&ps_sio_adma>, <&ps_audio_p>; + }; + + ps_ioa1: power-controller@3c0 { + compatible = "apple,t8122-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + reg = <0x3c0 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "ioa1"; + apple,always-on; + }; + + ps_apcie: power-controller@3f0 { + compatible = "apple,t8122-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + reg = <0x3f0 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "apcie"; + }; + + ps_ans: power-controller@3f8 { + compatible = "apple,t8122-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + reg = <0x3f8 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "ans"; + }; + + ps_atc0_common: power-controller@400 { + compatible = "apple,t8122-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + reg = <0x400 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "atc0_common"; + }; + + ps_atc1_common: power-controller@408 { + compatible = "apple,t8122-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + reg = <0x408 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "atc1_common"; + }; + + ps_dispext_sys: power-controller@410 { + compatible = "apple,t8122-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + reg = <0x410 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "dispext_sys"; + }; + + ps_venc_sys: power-controller@418 { + compatible = "apple,t8122-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + reg = <0x418 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "venc_sys"; + }; + + ps_scodec: power-controller@420 { + compatible = "apple,t8122-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + reg = <0x420 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "scodec"; + }; + + ps_msr: power-controller@428 { + compatible = "apple,t8122-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + reg = <0x428 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "msr"; + power-domains = <&ps_aft0>; + }; + + ps_dptx_ext_phy: power-controller@430 { + compatible = "apple,t8122-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + reg = <0x430 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "dptx_ext_phy"; + }; + + ps_ane_sys: power-controller@438 { + compatible = "apple,t8122-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + reg = <0x438 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "ane_sys"; + }; + + ps_apcie_gp: power-controller@440 { + compatible = "apple,t8122-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + reg = <0x440 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "apcie_gp"; + power-domains = <&ps_apcie>; + }; + + ps_apcie_st: power-controller@448 { + compatible = "apple,t8122-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + reg = <0x448 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "apcie_st"; + power-domains = <&ps_ans>, <&ps_apcie>; + }; + + ps_pmp: power-controller@450 { + compatible = "apple,t8122-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + reg = <0x450 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "pmp"; + apple,always-on; + }; + + ps_pms_sram: power-controller@458 { + compatible = "apple,t8122-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + reg = <0x458 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "pms_sram"; + apple,always-on; + }; + + ps_atc0_pcie: power-controller@460 { + compatible = "apple,t8122-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + reg = <0x460 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "atc0_pcie"; + power-domains = <&ps_atc0_common>; + }; + + ps_atc0_cio: power-controller@468 { + compatible = "apple,t8122-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + reg = <0x468 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "atc0_cio"; + power-domains = <&ps_atc0_common>; + }; + + ps_atc1_pcie: power-controller@470 { + compatible = "apple,t8122-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + reg = <0x470 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "atc1_pcie"; + power-domains = <&ps_atc1_common>; + }; + + ps_atc1_cio: power-controller@478 { + compatible = "apple,t8122-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + reg = <0x478 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "atc1_cio"; + power-domains = <&ps_atc1_common>; + }; + + ps_dispext_fe: power-controller@480 { + compatible = "apple,t8122-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + reg = <0x480 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "dispext_fe"; + power-domains = <&ps_dispext_sys>; + }; + + ps_dispext_cpu: power-controller@488 { + compatible = "apple,t8122-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + reg = <0x488 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "dispext_cpu"; + power-domains = <&ps_dispext_fe>; + apple,min-state = <4>; + }; + + ps_scodec_stream: power-controller@490 { + compatible = "apple,t8122-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + reg = <0x490 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "scodec_stream"; + power-domains = <&ps_scodec>; + }; + + ps_msr_ase_core: power-controller@498 { + compatible = "apple,t8122-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + reg = <0x498 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "msr_ase_core"; + power-domains = <&ps_msr>; + }; + + ps_apcie_phy_sw: power-controller@4a0 { + compatible = "apple,t8122-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + reg = <0x4a0 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "apcie_phy_sw"; + power-domains = <&ps_apcie_st>, <&ps_apcie_gp>; + }; + + ps_atc0_cio_pcie: power-controller@4a8 { + compatible = "apple,t8122-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + reg = <0x4a8 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "atc0_cio_pcie"; + power-domains = <&ps_atc0_cio>; + }; + + ps_atc0_cio_usb: power-controller@4b0 { + compatible = "apple,t8122-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + reg = <0x4b0 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "atc0_cio_usb"; + power-domains = <&ps_atc0_cio>; + }; + + ps_atc1_cio_pcie: power-controller@4b8 { + compatible = "apple,t8122-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + reg = <0x4b8 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "atc1_cio_pcie"; + power-domains = <&ps_atc1_cio>; + }; + + ps_atc1_cio_usb: power-controller@4c0 { + compatible = "apple,t8122-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + reg = <0x4c0 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "atc1_cio_usb"; + power-domains = <&ps_atc1_cio>; + }; + + ps_sep: power-controller@c00 { + compatible = "apple,t8122-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + reg = <0xc00 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "sep"; + apple,always-on; + }; + + ps_venc_dma: power-controller@8000 { + compatible = "apple,t8122-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + reg = <0x8000 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "venc_dma"; + power-domains = <&ps_venc_sys>; + }; + + ps_venc_pipe4: power-controller@8008 { + compatible = "apple,t8122-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + reg = <0x8008 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "venc_pipe4"; + power-domains = <&ps_venc_dma>; + }; + + ps_venc_pipe5: power-controller@8010 { + compatible = "apple,t8122-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + reg = <0x8010 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "venc_pipe5"; + power-domains = <&ps_venc_dma>; + }; + + ps_venc_me0: power-controller@8018 { + compatible = "apple,t8122-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + reg = <0x8018 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "venc_me0"; + power-domains = <&ps_venc_dma>; + }; + + ps_venc_me1: power-controller@8020 { + compatible = "apple,t8122-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + reg = <0x8020 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "venc_me1"; + power-domains = <&ps_venc_me0>; + }; + + ps_disp_cpu: power-controller@10000 { + compatible = "apple,t8122-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + reg = <0x10000 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "disp_cpu"; + power-domains = <&ps_disp_fe>; + apple,min-state = <4>; + }; +}; + +&pmgr_mini { + + ps_debug_gated: power-controller@0 { + compatible = "apple,t8122-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + reg = <0x0 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "debug_gated"; + apple,always-on; + }; + + ps_nub_spmi0: power-controller@58 { + compatible = "apple,t8122-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + reg = <0x58 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "nub_spmi0"; + apple,always-on; + }; + + ps_nub_spmi1: power-controller@60 { + compatible = "apple,t8122-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + reg = <0x60 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "nub_spmi1"; + apple,always-on; + }; + + ps_nub_spmi2: power-controller@68 { + compatible = "apple,t8122-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + reg = <0x68 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "nub_spmi2"; + apple,always-on; + }; + + ps_nub_spmi_a0: power-controller@70 { + compatible = "apple,t8122-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + reg = <0x70 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "nub_spmi_a0"; + apple,always-on; + }; + + ps_nub_aon: power-controller@78 { + compatible = "apple,t8122-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + reg = <0x78 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "nub_aon"; + apple,always-on; + }; + + ps_nub_spi0: power-controller@80 { + compatible = "apple,t8122-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + reg = <0x80 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "nub_spi0"; + apple,always-on; + }; + + ps_nub_ocla: power-controller@88 { + compatible = "apple,t8122-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + reg = <0x88 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "nub_ocla"; + apple,always-on; + }; + + ps_nub_gpio: power-controller@90 { + compatible = "apple,t8122-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + reg = <0x90 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "nub_gpio"; + apple,always-on; + }; + + ps_nub_fabric: power-controller@98 { + compatible = "apple,t8122-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + reg = <0x98 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "nub_fabric"; + apple,always-on; + }; + + ps_nub_sram: power-controller@a0 { + compatible = "apple,t8122-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + reg = <0xa0 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "nub_sram"; + apple,always-on; + }; + + ps_debug_switch: power-controller@a8 { + compatible = "apple,t8122-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + reg = <0xa8 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "debug_switch"; + apple,always-on; + }; + + ps_atc0_usb_aon: power-controller@b0 { + compatible = "apple,t8122-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + reg = <0xb0 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "atc0_usb_aon"; + }; + + ps_atc1_usb_aon: power-controller@b8 { + compatible = "apple,t8122-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + reg = <0xb8 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "atc1_usb_aon"; + }; + + ps_atc0_usb: power-controller@c0 { + compatible = "apple,t8122-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + reg = <0xc0 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "atc0_usb"; + power-domains = <&ps_atc0_usb_aon>, <&ps_atc0_common>; + }; + + ps_atc1_usb: power-controller@c8 { + compatible = "apple,t8122-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + reg = <0xc8 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "atc1_usb"; + power-domains = <&ps_atc1_usb_aon>, <&ps_atc1_common>; + }; +}; diff --git a/arch/arm64/boot/dts/apple/t8122.dtsi b/arch/arm64/boot/dts/apple/t8122.dtsi new file mode 100644 index 00000000000000..2e2cf30f3cd510 --- /dev/null +++ b/arch/arm64/boot/dts/apple/t8122.dtsi @@ -0,0 +1,451 @@ +// SPDX-License-Identifier: GPL-2.0+ OR MIT +/* + * Apple T8122 "M3" SoC + * + * Other names: H15G + * + * Copyright The Asahi Linux Contributors + */ + +#include +#include +#include +#include +#include +#include + +/ { + compatible = "apple,t8122", "apple,arm-platform"; + + #address-cells = <2>; + #size-cells = <2>; + + cpus { + #address-cells = <2>; + #size-cells = <0>; + + cpu-map { + cluster0 { + core0 { + cpu = <&cpu_e0>; + }; + core1 { + cpu = <&cpu_e1>; + }; + core2 { + cpu = <&cpu_e2>; + }; + core3 { + cpu = <&cpu_e3>; + }; + }; + + cluster1 { + core0 { + cpu = <&cpu_p0>; + }; + core1 { + cpu = <&cpu_p1>; + }; + core2 { + cpu = <&cpu_p2>; + }; + core3 { + cpu = <&cpu_p3>; + }; + }; + }; + + cpu_e0: cpu@0 { + compatible = "apple,sawtooth"; + device_type = "cpu"; + reg = <0x0 0x0>; + enable-method = "spin-table"; + cpu-release-addr = <0 0>; /* To be filled by loader */ + next-level-cache = <&l2_cache_0>; + i-cache-size = <0x20000>; + d-cache-size = <0x10000>; + }; + + cpu_e1: cpu@1 { + compatible = "apple,sawtooth"; + device_type = "cpu"; + reg = <0x0 0x1>; + enable-method = "spin-table"; + cpu-release-addr = <0 0>; /* To be filled by loader */ + next-level-cache = <&l2_cache_0>; + i-cache-size = <0x20000>; + d-cache-size = <0x10000>; + }; + + cpu_e2: cpu@2 { + compatible = "apple,sawtooth"; + device_type = "cpu"; + reg = <0x0 0x2>; + enable-method = "spin-table"; + cpu-release-addr = <0 0>; /* To be filled by loader */ + next-level-cache = <&l2_cache_0>; + i-cache-size = <0x20000>; + d-cache-size = <0x10000>; + }; + + cpu_e3: cpu@3 { + compatible = "apple,sawtooth"; + device_type = "cpu"; + reg = <0x0 0x3>; + enable-method = "spin-table"; + cpu-release-addr = <0 0>; /* To be filled by loader */ + next-level-cache = <&l2_cache_0>; + i-cache-size = <0x20000>; + d-cache-size = <0x10000>; + }; + + cpu_p0: cpu@10100 { + compatible = "apple,everest"; + device_type = "cpu"; + reg = <0x0 0x10100>; + enable-method = "spin-table"; + cpu-release-addr = <0 0>; /* To be filled by loader */ + next-level-cache = <&l2_cache_1>; + i-cache-size = <0x30000>; + d-cache-size = <0x20000>; + }; + + cpu_p1: cpu@10101 { + compatible = "apple,everest"; + device_type = "cpu"; + reg = <0x0 0x10101>; + enable-method = "spin-table"; + cpu-release-addr = <0 0>; /* To be filled by loader */ + next-level-cache = <&l2_cache_1>; + i-cache-size = <0x30000>; + d-cache-size = <0x20000>; + }; + + cpu_p2: cpu@10102 { + compatible = "apple,everest"; + device_type = "cpu"; + reg = <0x0 0x10102>; + enable-method = "spin-table"; + cpu-release-addr = <0 0>; /* To be filled by loader */ + next-level-cache = <&l2_cache_1>; + i-cache-size = <0x30000>; + d-cache-size = <0x20000>; + }; + + cpu_p3: cpu@10103 { + compatible = "apple,everest"; + device_type = "cpu"; + reg = <0x0 0x10103>; + enable-method = "spin-table"; + cpu-release-addr = <0 0>; /* To be filled by loader */ + next-level-cache = <&l2_cache_1>; + i-cache-size = <0x30000>; + d-cache-size = <0x20000>; + }; + + l2_cache_0: l2-cache-0 { + compatible = "cache"; + cache-level = <2>; + cache-unified; + cache-size = <0x400000>; + }; + + l2_cache_1: l2-cache-1 { + compatible = "cache"; + cache-level = <2>; + cache-unified; + cache-size = <0x1000000>; + }; + }; + + timer { + compatible = "arm,armv8-timer"; + interrupt-parent = <&aic>; + interrupt-names = "phys", "virt", "hyp-phys", "hyp-virt"; + interrupts = , + , + , + ; + }; + + clkref: clock-ref { + compatible = "fixed-clock"; + #clock-cells = <0>; + clock-frequency = <24000000>; + clock-output-names = "clkref"; + }; + + // clk_200m: clock-200m { + // compatible = "fixed-clock"; + // #clock-cells = <0>; + // clock-frequency = <200000000>; + // clock-output-names = "clk_200m"; + // }; + + soc { + compatible = "simple-bus"; + #address-cells = <2>; + #size-cells = <2>; + + ranges; + nonposted-mmio; + /* Required to get >32-bit DMA via DARTs */ + dma-ranges = <0 0 0 0 0xffffffff 0xffffc000>; + + i2c0: i2c@235010000 { + compatible = "apple,t8122-i2c", "apple,t8103-i2c"; + reg = <0x2 0x35010000 0x0 0x4000>; + clocks = <&clkref>; + interrupt-parent = <&aic>; + interrupts = ; + pinctrl-0 = <&i2c0_pins>; + pinctrl-names = "default"; + #address-cells = <0x1>; + #size-cells = <0x0>; + power-domains = <&ps_i2c0>; + status = "disabled"; + }; + + i2c1: i2c@235014000 { + compatible = "apple,t8122-i2c", "apple,t8103-i2c"; + reg = <0x2 0x35014000 0x0 0x4000>; + clocks = <&clkref>; + interrupt-parent = <&aic>; + interrupts = ; + pinctrl-0 = <&i2c1_pins>; + pinctrl-names = "default"; + #address-cells = <0x1>; + #size-cells = <0x0>; + power-domains = <&ps_i2c1>; + status = "disabled"; + }; + + i2c2: i2c@235018000 { + compatible = "apple,t8122-i2c", "apple,t8103-i2c"; + reg = <0x2 0x35018000 0x0 0x4000>; + clocks = <&clkref>; + interrupt-parent = <&aic>; + interrupts = ; + pinctrl-0 = <&i2c2_pins>; + pinctrl-names = "default"; + #address-cells = <0x1>; + #size-cells = <0x0>; + power-domains = <&ps_i2c2>; + status = "disabled"; + }; + + i2c3: i2c@23501c000 { + compatible = "apple,t8122-i2c", "apple,t8103-i2c"; + reg = <0x2 0x3501c000 0x0 0x4000>; + clocks = <&clkref>; + interrupt-parent = <&aic>; + interrupts = ; + pinctrl-0 = <&i2c3_pins>; + pinctrl-names = "default"; + #address-cells = <0x1>; + #size-cells = <0x0>; + power-domains = <&ps_i2c3>; + status = "disabled"; + }; + + i2c4: i2c@235020000 { + compatible = "apple,t8122-i2c", "apple,t8103-i2c"; + reg = <0x2 0x35020000 0x0 0x4000>; + clocks = <&clkref>; + interrupt-parent = <&aic>; + interrupts = ; + pinctrl-0 = <&i2c4_pins>; + pinctrl-names = "default"; + #address-cells = <0x1>; + #size-cells = <0x0>; + power-domains = <&ps_i2c4>; + status = "disabled"; + }; + + fpwm1: pwm@2a1044000 { + compatible = "apple,t8122-fpwm", "apple,s5l-fpwm"; + reg = <0x2 0xa1044000 0x0 0x4000>; + power-domains = <&ps_fpwm1>; + clocks = <&clkref>; + #pwm-cells = <2>; + status = "disabled"; + }; + + serial0: serial@2a1200000 { + compatible = "apple,s5l-uart"; + reg = <0x2 0xa1200000 0x0 0x1000>; + reg-io-width = <4>; + interrupt-parent = <&aic>; + interrupts = ; + /* + * TODO: figure out the clocking properly, there may + * be a third selectable clock. + */ + clocks = <&clkref>, <&clkref>; + clock-names = "uart", "clk_uart_baud0"; + power-domains = <&ps_uart0>; + status = "disabled"; + }; + + aic: interrupt-controller@2d1000000 { + compatible = "apple,t8122-aic3"; + #interrupt-cells = <3>; + interrupt-controller; + reg = <0x2 0xd1000000 0x0 0x184000>, + <0x2 0xd1040000 0x0 0x4>; + reg-names = "core", "event"; + power-domains = <&ps_aic>; + + affinities { + e-core-pmu-affinity { + apple,fiq-index = ; + cpus = <&cpu_e0 &cpu_e1 &cpu_e2 &cpu_e3>; + }; + + p-core-pmu-affinity { + apple,fiq-index = ; + cpus = <&cpu_p0 &cpu_p1 &cpu_p2 &cpu_p3>; + }; + }; + }; + + pmgr: power-management@2d0700000 { + compatible = "apple,t8122-pmgr", "apple,t8103-pmgr", "syscon", "simple-mfd"; + #address-cells = <1>; + #size-cells = <1>; + reg = <0x2 0xd0700000 0 0x10000>; + /* child nodes are added in t8122-pmgr.dtsi */ + }; + + pinctrl_ap: pinctrl@2c7100000 { + compatible = "apple,t8122-pinctrl", "apple,t8103-pinctrl"; + reg = <0x2 0xc7100000 0x0 0x100000>; + power-domains = <&ps_gpio>; + + gpio-controller; + #gpio-cells = <2>; + gpio-ranges = <&pinctrl_ap 0 0 224>; + apple,npins = <224>; + + interrupt-controller; + #interrupt-cells = <2>; + interrupt-parent = <&aic>; + interrupts = , + , + , + , + , + , + ; + + i2c0_pins: i2c0-pins { + pinmux = , + ; + }; + + i2c1_pins: i2c1-pins { + pinmux = , + ; + }; + + i2c2_pins: i2c2-pins { + pinmux = , + ; + }; + + i2c3_pins: i2c3-pins { + pinmux = , + ; + }; + + i2c4_pins: i2c4-pins { + pinmux = , + ; + }; + + }; + + pinctrl_nub: pinctrl@2e41f0000 { + compatible = "apple,t8122-pinctrl", "apple,t8103-pinctrl"; + reg = <0x2 0xe41f0000 0x0 0x4000>; + power-domains = <&ps_nub_gpio>; + + gpio-controller; + #gpio-cells = <2>; + gpio-ranges = <&pinctrl_nub 0 0 32>; + apple,npins = <32>; + + interrupt-controller; + #interrupt-cells = <2>; + interrupt-parent = <&aic>; + interrupts = , + , + , + , + , + , + ; + }; + + pmgr_mini: power-management@2e4280000 { + compatible = "apple,t8122-pmgr", "apple,t8103-pmgr", "syscon", "simple-mfd"; + #address-cells = <1>; + #size-cells = <1>; + reg = <0x2 0xe4280000 0 0x4000>; + /* child nodes are added in t8122-pmgr.dtsi */ + }; + + wdt: watchdog@2e42b0000 { + compatible = "apple,t8122-wdt", "apple,t8103-wdt"; + reg = <0x2 0xe42b0000 0x0 0x4000>; + clocks = <&clkref>; + interrupt-parent = <&aic>; + interrupts = ; + }; + + pinctrl_smc: pinctrl@2ec820000 { + compatible = "apple,t8122-pinctrl", "apple,t8103-pinctrl"; + reg = <0x2 0xec820000 0x0 0x4000>; + + gpio-controller; + #gpio-cells = <2>; + gpio-ranges = <&pinctrl_smc 0 0 18>; + apple,npins = <18>; + + interrupt-controller; + #interrupt-cells = <2>; + interrupt-parent = <&aic>; + interrupts = , + , + , + , + , + , + ; + }; + + pinctrl_aop: pinctrl@2f4824000 { + compatible = "apple,t8122-pinctrl", "apple,t8103-pinctrl"; + reg = <0x2 0xf4824000 0x0 0x4000>; + + gpio-controller; + #gpio-cells = <2>; + gpio-ranges = <&pinctrl_aop 0 0 54>; + apple,npins = <54>; + + interrupt-controller; + #interrupt-cells = <2>; + interrupt-parent = <&aic>; + interrupts = , + , + , + , + , + , + ; + }; + }; +}; + +#include "t8122-pmgr.dtsi" From aaa1042f7a2c3191ce8a025b786d0f99c864acb8 Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Wed, 28 Jan 2026 21:35:20 +0100 Subject: [PATCH 2621/3902] drm/asahi: Do not use l10r for 12.3 DCP firmware DCP firmware 12.3 (tested as 12.4 on a M2) does not support l10r as buffer format. Drop 30-bit support for 12.x firmware. 12.3 based installs are considered legacy and support for 12.4 (M2 only) was dropped. This ensures such installs remain usable without complicating the driver too much. DCP complains on syslog with > UPPipeDCP_H13P.cpp:3302: IOMFB verify_surfaces: No support for format l10r Fixes: b6a8d6ba54f3 ("drm: apple: Advertise ARGB2101010 support") Signed-off-by: Janne Grunau --- drivers/gpu/drm/apple/apple_drv.c | 7 ++-- drivers/gpu/drm/apple/dcp.c | 8 +++++ drivers/gpu/drm/apple/dcp.h | 1 + drivers/gpu/drm/apple/plane.c | 54 ++++++++++++++++++++++++++++--- drivers/gpu/drm/apple/plane.h | 1 + 5 files changed, 65 insertions(+), 6 deletions(-) diff --git a/drivers/gpu/drm/apple/apple_drv.c b/drivers/gpu/drm/apple/apple_drv.c index 4cfc357e0eb819..4c1a4a7f5e2001 100644 --- a/drivers/gpu/drm/apple/apple_drv.c +++ b/drivers/gpu/drm/apple/apple_drv.c @@ -273,8 +273,10 @@ static int apple_probe_per_dcp(struct device *dev, struct drm_plane *planes[DCP_MAX_PLANES]; int ret, i; int immutable_zpos = 0; + bool supports_l10r = !dcp_fw_compat_is_12_x(dcp); - planes[0] = apple_plane_init(drm, 1U << num, DRM_PLANE_TYPE_PRIMARY); + planes[0] = apple_plane_init(drm, 1U << num, supports_l10r, + DRM_PLANE_TYPE_PRIMARY); if (IS_ERR(planes[0])) return PTR_ERR(planes[0]); ret = drm_plane_create_zpos_immutable_property(planes[0], immutable_zpos); @@ -285,7 +287,8 @@ static int apple_probe_per_dcp(struct device *dev, /* Set up our other planes */ for (i = 1; i < DCP_MAX_PLANES; i++) { - planes[i] = apple_plane_init(drm, 1U << num, DRM_PLANE_TYPE_OVERLAY); + planes[i] = apple_plane_init(drm, 1U << num, supports_l10r, + DRM_PLANE_TYPE_OVERLAY); if (IS_ERR(planes[i])) return PTR_ERR(planes[i]); immutable_zpos++; diff --git a/drivers/gpu/drm/apple/dcp.c b/drivers/gpu/drm/apple/dcp.c index 861aa0314ac19a..ff1b59eef989c4 100644 --- a/drivers/gpu/drm/apple/dcp.c +++ b/drivers/gpu/drm/apple/dcp.c @@ -501,6 +501,14 @@ void dcp_link(struct platform_device *pdev, struct apple_crtc *crtc, dcp->connector = connector; } + +bool dcp_fw_compat_is_12_x(struct platform_device *pdev) +{ + struct apple_dcp *dcp = platform_get_drvdata(pdev); + + return dcp->fw_compat == DCP_FIRMWARE_V_12_3; +} + int dcp_start(struct platform_device *pdev) { struct apple_dcp *dcp = platform_get_drvdata(pdev); diff --git a/drivers/gpu/drm/apple/dcp.h b/drivers/gpu/drm/apple/dcp.h index bb93ef8c3cd5f3..f3301fc9129cfc 100644 --- a/drivers/gpu/drm/apple/dcp.h +++ b/drivers/gpu/drm/apple/dcp.h @@ -34,6 +34,7 @@ void dcp_poweron(struct platform_device *pdev); int dcp_set_crc(struct drm_crtc *crtc, bool enabled); int dcp_crtc_atomic_check(struct drm_crtc *crtc, struct drm_atomic_state *state); int dcp_get_connector_type(struct platform_device *pdev); +bool dcp_fw_compat_is_12_x(struct platform_device *pdev); void dcp_link(struct platform_device *pdev, struct apple_crtc *apple, struct apple_connector *connector); int dcp_start(struct platform_device *pdev); diff --git a/drivers/gpu/drm/apple/plane.c b/drivers/gpu/drm/apple/plane.c index ec972b3467d479..2f0b76ad84ad65 100644 --- a/drivers/gpu/drm/apple/plane.c +++ b/drivers/gpu/drm/apple/plane.c @@ -394,6 +394,37 @@ static const u32 dcp_overlay_formats[] = { #endif }; +/* + * Formats for the 12.x firmware which does not support "l10r" / ARGB2101010 + */ +static const u32 dcp_primary_formats_12_x[] = { + DRM_FORMAT_XRGB8888, + DRM_FORMAT_ARGB8888, + DRM_FORMAT_XBGR8888, + DRM_FORMAT_ABGR8888, + DRM_FORMAT_NV12, + DRM_FORMAT_NV16, + DRM_FORMAT_NV24, + DRM_FORMAT_P010, + DRM_FORMAT_P210, +#if defined(DRM_FORMAT_P410) + DRM_FORMAT_P410, +#endif +}; + +static const u32 dcp_overlay_formats_12_x[] = { + DRM_FORMAT_ARGB8888, + DRM_FORMAT_ABGR8888, + DRM_FORMAT_NV12, + DRM_FORMAT_NV16, + DRM_FORMAT_NV24, + DRM_FORMAT_P010, + DRM_FORMAT_P210, +#if defined(DRM_FORMAT_P410) + DRM_FORMAT_P410, +#endif +}; + u64 apple_format_modifiers[] = { DRM_FORMAT_MOD_LINEAR, DRM_FORMAT_MOD_INVALID @@ -405,22 +436,37 @@ struct apple_plane { struct drm_plane *apple_plane_init(struct drm_device *dev, unsigned long possible_crtcs, + bool supports_l10r, enum drm_plane_type type) { struct apple_plane *plane; + const u32 *fmts; + u32 num_fmts; switch (type) { case DRM_PLANE_TYPE_PRIMARY: + if (supports_l10r) { + fmts = dcp_primary_formats; + num_fmts = ARRAY_SIZE(dcp_primary_formats); + } else { + fmts = dcp_primary_formats_12_x; + num_fmts = ARRAY_SIZE(dcp_primary_formats_12_x); + } plane = drmm_universal_plane_alloc(dev, struct apple_plane, base, possible_crtcs, - &apple_plane_funcs, - dcp_primary_formats, ARRAY_SIZE(dcp_primary_formats), + &apple_plane_funcs, fmts, num_fmts, apple_format_modifiers, type, NULL); break; case DRM_PLANE_TYPE_OVERLAY: case DRM_PLANE_TYPE_CURSOR: + if (supports_l10r) { + fmts = dcp_overlay_formats; + num_fmts = ARRAY_SIZE(dcp_overlay_formats); + } else { + fmts = dcp_overlay_formats_12_x; + num_fmts = ARRAY_SIZE(dcp_overlay_formats_12_x); + } plane = drmm_universal_plane_alloc(dev, struct apple_plane, base, possible_crtcs, - &apple_plane_funcs, - dcp_overlay_formats, ARRAY_SIZE(dcp_overlay_formats), + &apple_plane_funcs, fmts, num_fmts, apple_format_modifiers, type, NULL); break; default: diff --git a/drivers/gpu/drm/apple/plane.h b/drivers/gpu/drm/apple/plane.h index b03c3fdfed7dec..67d15938cf0dcb 100644 --- a/drivers/gpu/drm/apple/plane.h +++ b/drivers/gpu/drm/apple/plane.h @@ -24,6 +24,7 @@ struct apple_plane_state { struct drm_plane *apple_plane_init(struct drm_device *dev, unsigned long possible_crtcs, + bool supports_l10r, enum drm_plane_type type); #endif /* __APPLE_PLANE_H__ */ From ee7ad817033098e4bc179737b8ed4bdd4e79b25a Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Thu, 29 Jan 2026 00:04:52 +0100 Subject: [PATCH 2622/3902] ASoC: macaudio: Set long_name during probe() This prevents filling the long_name from DMI data soon to be provided by u-boot's SMBIOS support. This is necessary since ALSA ucm uses the long name to load the ucm config file. The asahi ucm config files are use the "model" property from devicetree. The DMI information is not always present and should not be used on these systems. Signed-off-by: Janne Grunau --- sound/soc/apple/macaudio.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/sound/soc/apple/macaudio.c b/sound/soc/apple/macaudio.c index 31f6ec45f80979..1a32679f41ea90 100644 --- a/sound/soc/apple/macaudio.c +++ b/sound/soc/apple/macaudio.c @@ -489,6 +489,11 @@ static int macaudio_parse_of(struct macaudio_snd_data *ma) dev_err_probe(dev, ret, "parsing card name\n"); return ret; } + /* + * Set long_name to prevent snd_soc_set_dmi_name() from setting one from + * make believe data u-boot provides in its SMBIOS emulation. + */ + card->long_name = card->name; /* Populate links, start with the fixed number of FE links */ num_links = ARRAY_SIZE(macaudio_fe_links); From 27357b61522f7c06e81ab71cc20329f695f8f346 Mon Sep 17 00:00:00 2001 From: Konrad Dybcio Date: Tue, 2 Dec 2025 18:36:22 +0100 Subject: [PATCH 2623/3902] arm64: dts: qcom: sc8280xp: Add missing VDD_MXC links [ Upstream commit 868b979c5328b867c95a6d5a93ba13ad0d3cd2f1 ] To make sure that power rail is voted for, wire it up to its consumers. Fixes: 152d1faf1e2f ("arm64: dts: qcom: add SC8280XP platform") Signed-off-by: Konrad Dybcio Reviewed-by: Ulf Hansson Link: https://lore.kernel.org/r/20251202-topic-8280_mxc-v2-3-46cdf47a829e@oss.qualcomm.com Signed-off-by: Bjorn Andersson Signed-off-by: Sasha Levin --- arch/arm64/boot/dts/qcom/sc8280xp.dtsi | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/arch/arm64/boot/dts/qcom/sc8280xp.dtsi b/arch/arm64/boot/dts/qcom/sc8280xp.dtsi index 963ce2362a52eb..d89938e17e0930 100644 --- a/arch/arm64/boot/dts/qcom/sc8280xp.dtsi +++ b/arch/arm64/boot/dts/qcom/sc8280xp.dtsi @@ -5773,8 +5773,12 @@ clocks = <&rpmhcc RPMH_CXO_CLK>; clock-names = "xo"; - power-domains = <&rpmhpd SC8280XP_NSP>; - power-domain-names = "nsp"; + power-domains = <&rpmhpd SC8280XP_NSP>, + <&rpmhpd SC8280XP_CX>, + <&rpmhpd SC8280XP_MXC>; + power-domain-names = "nsp", + "cx", + "mxc"; memory-region = <&pil_nsp0_mem>; @@ -5904,8 +5908,12 @@ clocks = <&rpmhcc RPMH_CXO_CLK>; clock-names = "xo"; - power-domains = <&rpmhpd SC8280XP_NSP>; - power-domain-names = "nsp"; + power-domains = <&rpmhpd SC8280XP_NSP>, + <&rpmhpd SC8280XP_CX>, + <&rpmhpd SC8280XP_MXC>; + power-domain-names = "nsp", + "cx", + "mxc"; memory-region = <&pil_nsp1_mem>; From 0e5ad343afa303d09655b1a97ba4395e0773fcc8 Mon Sep 17 00:00:00 2001 From: Krishna Kurapati Date: Wed, 3 Dec 2025 20:18:55 +0530 Subject: [PATCH 2624/3902] arm64: dts: qcom: sm8550: Fix compile warnings in USB controller node [ Upstream commit 9dbc9bed01837717b8ab755cf5067a6f8d35b00f ] With W=1, the following error comes up: Warning (avoid_unnecessary_addr_size): /soc@0/usb@a600000: unnecessary #address-cells/#size-cells without "ranges", "dma-ranges" or child "reg" or "ranges" property This is because the child node being removed during flattening and moving to latest bindings. Fixes: 33450878adfc ("arm64: dts: qcom: sm8550: Flatten the USB nodes") Signed-off-by: Krishna Kurapati Reviewed-by: Krzysztof Kozlowski Link: https://lore.kernel.org/r/20251203144856.2711440-2-krishna.kurapati@oss.qualcomm.com Signed-off-by: Bjorn Andersson Signed-off-by: Sasha Levin --- arch/arm64/boot/dts/qcom/sm8550.dtsi | 2 -- 1 file changed, 2 deletions(-) diff --git a/arch/arm64/boot/dts/qcom/sm8550.dtsi b/arch/arm64/boot/dts/qcom/sm8550.dtsi index 7724dba75db79a..e294dc9c68c9ac 100644 --- a/arch/arm64/boot/dts/qcom/sm8550.dtsi +++ b/arch/arm64/boot/dts/qcom/sm8550.dtsi @@ -4097,8 +4097,6 @@ usb_1: usb@a600000 { compatible = "qcom,sm8550-dwc3", "qcom,snps-dwc3"; reg = <0x0 0x0a600000 0x0 0xfc100>; - #address-cells = <1>; - #size-cells = <0>; clocks = <&gcc GCC_CFG_NOC_USB3_PRIM_AXI_CLK>, <&gcc GCC_USB30_PRIM_MASTER_CLK>, From 2d73b3ed28be1096402ead62e8bce73a83212753 Mon Sep 17 00:00:00 2001 From: Krishna Kurapati Date: Wed, 3 Dec 2025 20:18:56 +0530 Subject: [PATCH 2625/3902] arm64: dts: qcom: sm8650: Fix compile warnings in USB controller node [ Upstream commit 1f6ca557088eb96c8c554f853eb7c60862f8a0a8 ] With W=1, the following error comes up: Warning (avoid_unnecessary_addr_size): /soc@0/usb@a600000: unnecessary #address-cells/#size-cells without "ranges", "dma-ranges" or child "reg" or "ranges" property This is because the child node being removed during flattening and moving to latest bindings. Fixes: 77e1f16b9302 ("arm64: dts: qcom: sm8650: Flatten the USB nodes") Signed-off-by: Krishna Kurapati Reviewed-by: Krzysztof Kozlowski Link: https://lore.kernel.org/r/20251203144856.2711440-3-krishna.kurapati@oss.qualcomm.com Signed-off-by: Bjorn Andersson Signed-off-by: Sasha Levin --- arch/arm64/boot/dts/qcom/sm8650.dtsi | 3 --- 1 file changed, 3 deletions(-) diff --git a/arch/arm64/boot/dts/qcom/sm8650.dtsi b/arch/arm64/boot/dts/qcom/sm8650.dtsi index 3b03c135393863..d22a26a416ccc1 100644 --- a/arch/arm64/boot/dts/qcom/sm8650.dtsi +++ b/arch/arm64/boot/dts/qcom/sm8650.dtsi @@ -5115,9 +5115,6 @@ dma-coherent; - #address-cells = <1>; - #size-cells = <0>; - status = "disabled"; ports { From 83eeeb8c1acb763b6f633678eb3dbf553208bcf6 Mon Sep 17 00:00:00 2001 From: Chaoyi Chen Date: Tue, 6 Jan 2026 15:15:13 +0800 Subject: [PATCH 2626/3902] arm64: dts: rockchip: Fix wrong register range of rk3576 gpu [ Upstream commit 955b263c421c6fe5075369c52199f278289ec8c4 ] According to RK3576 TRM part1 Table 1-1 Address Mapping, the size of the GPU registers is 128 KB. The current mapping incorrectly includes the addresses of multiple following IP like the eInk interface at 0x27900000. This has not been detected by the DT tooling as none of the extra mapped IP is described in the upstream RK3576 DT so far. Fixes: 57b1ce903966 ("arm64: dts: rockchip: Add rk3576 SoC base DT") Signed-off-by: Chaoyi Chen Reviewed-by: Nicolas Frattaroli Reviewed-by: Sebastian Reichel Link: https://patch.msgid.link/20260106071513.209-1-kernel@airkyi.com Signed-off-by: Heiko Stuebner Signed-off-by: Sasha Levin --- arch/arm64/boot/dts/rockchip/rk3576.dtsi | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/arch/arm64/boot/dts/rockchip/rk3576.dtsi b/arch/arm64/boot/dts/rockchip/rk3576.dtsi index a86fc6b4e8c458..c72343e7a0456e 100644 --- a/arch/arm64/boot/dts/rockchip/rk3576.dtsi +++ b/arch/arm64/boot/dts/rockchip/rk3576.dtsi @@ -1261,7 +1261,7 @@ gpu: gpu@27800000 { compatible = "rockchip,rk3576-mali", "arm,mali-bifrost"; - reg = <0x0 0x27800000 0x0 0x200000>; + reg = <0x0 0x27800000 0x0 0x20000>; assigned-clocks = <&scmi_clk SCMI_CLK_GPU>; assigned-clock-rates = <198000000>; clocks = <&cru CLK_GPU>; From 4f6884288e4b78efcd64c679973598c201d1db93 Mon Sep 17 00:00:00 2001 From: Faisal Bukhari Date: Mon, 22 Sep 2025 23:38:34 +0530 Subject: [PATCH 2627/3902] perf parse-events: Fix evsel allocation failure [ Upstream commit 1eb217ab2e737609f8a861b517649e82e7236d05 ] If evsel__new_idx() returns NULL, the function currently jumps to label 'out_err'. Here, references to `cpus` and `pmu_cpus` are dropped. Also, resources held by evsel->name and evsel->metric_id are freed. But if evsel__new_idx() returns NULL, it can lead to NULL pointer dereference. Fixes: cd63c22168257a0b ("perf parse-events: Minor __add_event refactoring") Signed-off-by: Faisal Bukhari Reviewed-by: Arnaldo Carvalho de Melo Signed-off-by: Namhyung Kim Signed-off-by: Sasha Levin --- tools/perf/util/parse-events.c | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/tools/perf/util/parse-events.c b/tools/perf/util/parse-events.c index cd9315d3ca1171..4723c2955f22ec 100644 --- a/tools/perf/util/parse-events.c +++ b/tools/perf/util/parse-events.c @@ -286,8 +286,11 @@ __add_event(struct list_head *list, int *idx, event_attr_init(attr); evsel = evsel__new_idx(attr, *idx); - if (!evsel) - goto out_err; + if (!evsel) { + perf_cpu_map__put(cpus); + perf_cpu_map__put(pmu_cpus); + return NULL; + } if (name) { evsel->name = strdup(name); From 6abad2eb9a1bdbb8965ef1d2b5aae3573d3b7d27 Mon Sep 17 00:00:00 2001 From: Michael Kelley Date: Wed, 31 Dec 2025 12:14:47 -0800 Subject: [PATCH 2628/3902] Drivers: hv: Always do Hyper-V panic notification in hv_kmsg_dump() [ Upstream commit 49f49d47af67f8a7b221db1d758fc634242dc91a ] hv_kmsg_dump() currently skips the panic notification entirely if it doesn't get any message bytes to pass to Hyper-V due to an error from kmsg_dump_get_buffer(). Skipping the notification is undesirable because it leaves the Hyper-V host uncertain about the state of a panic'ed guest. Fix this by always doing the panic notification, even if bytes_written is zero. Also ensure that bytes_written is initialized, which fixes a kernel test robot warning. The warning is actually bogus because kmsg_dump_get_buffer() happens to set bytes_written even if it fails, and in the kernel test robot's CONFIG_PRINTK not set case, hv_kmsg_dump() is never called. But do the initialization for robustness and to quiet the static checker. Fixes: 9c318a1d9b50 ("Drivers: hv: move panic report code from vmbus to hv early init code") Reported-by: kernel test robot Reported-by: Dan Carpenter Closes: https://lore.kernel.org/all/202512172103.OcUspn1Z-lkp@intel.com/ Signed-off-by: Michael Kelley Reviewed-by: Roman Kisel Signed-off-by: Wei Liu Signed-off-by: Sasha Levin --- drivers/hv/hv_common.c | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/drivers/hv/hv_common.c b/drivers/hv/hv_common.c index e109a620c83fc8..71fd3ea4fa8bd5 100644 --- a/drivers/hv/hv_common.c +++ b/drivers/hv/hv_common.c @@ -195,13 +195,15 @@ static void hv_kmsg_dump(struct kmsg_dumper *dumper, /* * Write dump contents to the page. No need to synchronize; panic should - * be single-threaded. + * be single-threaded. Ignore failures from kmsg_dump_get_buffer() since + * panic notification should be done even if there is no message data. + * Don't assume bytes_written is set in case of failure, so initialize it. */ kmsg_dump_rewind(&iter); - kmsg_dump_get_buffer(&iter, false, hv_panic_page, HV_HYP_PAGE_SIZE, + bytes_written = 0; + (void)kmsg_dump_get_buffer(&iter, false, hv_panic_page, HV_HYP_PAGE_SIZE, &bytes_written); - if (!bytes_written) - return; + /* * P3 to contain the physical address of the panic page & P4 to * contain the size of the panic data in that page. Rest of the @@ -210,7 +212,7 @@ static void hv_kmsg_dump(struct kmsg_dumper *dumper, hv_set_msr(HV_MSR_CRASH_P0, 0); hv_set_msr(HV_MSR_CRASH_P1, 0); hv_set_msr(HV_MSR_CRASH_P2, 0); - hv_set_msr(HV_MSR_CRASH_P3, virt_to_phys(hv_panic_page)); + hv_set_msr(HV_MSR_CRASH_P3, bytes_written ? virt_to_phys(hv_panic_page) : 0); hv_set_msr(HV_MSR_CRASH_P4, bytes_written); /* From 4818f28cd902334980800b34e719521d3572ccff Mon Sep 17 00:00:00 2001 From: Mark Harmstone Date: Tue, 13 Jan 2026 18:37:56 +0000 Subject: [PATCH 2629/3902] btrfs: fix missing fields in superblock backup with BLOCK_GROUP_TREE [ Upstream commit 1d8f69f453c2e8a2d99b158e58e02ed65031fa6d ] When the BLOCK_GROUP_TREE compat_ro flag is set, the extent root and csum root fields are getting missed. This is because EXTENT_TREE_V2 treated these differently, and when they were split off this special-casing was mistakenly assigned to BGT rather than the rump EXTENT_TREE_V2. There's no reason why the existence of the block group tree should mean that we don't record the details of the last commit's extent root and csum root. Fix the code in backup_super_roots() so that the correct check gets made. Fixes: 1c56ab991903 ("btrfs: separate BLOCK_GROUP_TREE compat RO flag from EXTENT_TREE_V2") Reviewed-by: Qu Wenruo Signed-off-by: Mark Harmstone Reviewed-by: David Sterba Signed-off-by: David Sterba Signed-off-by: Sasha Levin --- fs/btrfs/disk-io.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fs/btrfs/disk-io.c b/fs/btrfs/disk-io.c index 0aa7e5d1b05f6c..a5336f530c8ed9 100644 --- a/fs/btrfs/disk-io.c +++ b/fs/btrfs/disk-io.c @@ -1666,7 +1666,7 @@ static void backup_super_roots(struct btrfs_fs_info *info) btrfs_set_backup_chunk_root_level(root_backup, btrfs_header_level(info->chunk_root->node)); - if (!btrfs_fs_compat_ro(info, BLOCK_GROUP_TREE)) { + if (!btrfs_fs_incompat(info, EXTENT_TREE_V2)) { struct btrfs_root *extent_root = btrfs_extent_root(info, 0); struct btrfs_root *csum_root = btrfs_csum_root(info, 0); From 0f56c3f23ed5638d9f0a6918aa929738d8f1e74a Mon Sep 17 00:00:00 2001 From: Konrad Dybcio Date: Tue, 2 Dec 2025 18:36:20 +0100 Subject: [PATCH 2630/3902] dt-bindings: power: qcom,rpmpd: Add SC8280XP_MXC_AO [ Upstream commit 45e1be5ddec98db71e7481fa7a3005673200d85c ] Not sure how useful it's gonna be in practice, but the definition is missing (unlike the previously-unused SC8280XP_MXC-non-_AO), so add it to allow the driver to create the corresponding pmdomain. Fixes: dbfb5f94e084 ("dt-bindings: power: rpmpd: Add sc8280xp RPMh power-domains") Acked-by: Rob Herring (Arm) Signed-off-by: Konrad Dybcio Reviewed-by: Ulf Hansson Link: https://lore.kernel.org/r/20251202-topic-8280_mxc-v2-1-46cdf47a829e@oss.qualcomm.com Signed-off-by: Bjorn Andersson Signed-off-by: Sasha Levin --- include/dt-bindings/power/qcom,rpmhpd.h | 1 + 1 file changed, 1 insertion(+) diff --git a/include/dt-bindings/power/qcom,rpmhpd.h b/include/dt-bindings/power/qcom,rpmhpd.h index 73cceb88953f70..269b73ff866a8f 100644 --- a/include/dt-bindings/power/qcom,rpmhpd.h +++ b/include/dt-bindings/power/qcom,rpmhpd.h @@ -261,5 +261,6 @@ #define SC8280XP_NSP 13 #define SC8280XP_QPHY 14 #define SC8280XP_XO 15 +#define SC8280XP_MXC_AO 16 #endif From d395afac28adb3a8cb45f2456cfbbc980d38d754 Mon Sep 17 00:00:00 2001 From: Konrad Dybcio Date: Tue, 2 Dec 2025 18:36:21 +0100 Subject: [PATCH 2631/3902] pmdomain: qcom: rpmhpd: Add MXC to SC8280XP [ Upstream commit 5bc3e720e725cd5fa34875fa1e5434d565858067 ] This was apparently accounted for in dt-bindings, but never made its way into the driver. Fix it for SC8280XP and its VDD_GFX-less cousin, SA8540P. Fixes: f68f1cb3437d ("soc: qcom: rpmhpd: add sc8280xp & sa8540p rpmh power-domains") Reviewed-by: Dmitry Baryshkov Signed-off-by: Konrad Dybcio Reviewed-by: Ulf Hansson Link: https://lore.kernel.org/r/20251202-topic-8280_mxc-v2-2-46cdf47a829e@oss.qualcomm.com Signed-off-by: Bjorn Andersson Signed-off-by: Sasha Levin --- drivers/pmdomain/qcom/rpmhpd.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/drivers/pmdomain/qcom/rpmhpd.c b/drivers/pmdomain/qcom/rpmhpd.c index 4faa8a25618621..4c3cbf3abc7504 100644 --- a/drivers/pmdomain/qcom/rpmhpd.c +++ b/drivers/pmdomain/qcom/rpmhpd.c @@ -246,6 +246,8 @@ static struct rpmhpd *sa8540p_rpmhpds[] = { [SC8280XP_MMCX_AO] = &mmcx_ao, [SC8280XP_MX] = &mx, [SC8280XP_MX_AO] = &mx_ao, + [SC8280XP_MXC] = &mxc, + [SC8280XP_MXC_AO] = &mxc_ao, [SC8280XP_NSP] = &nsp, }; @@ -675,6 +677,8 @@ static struct rpmhpd *sc8280xp_rpmhpds[] = { [SC8280XP_MMCX_AO] = &mmcx_ao, [SC8280XP_MX] = &mx, [SC8280XP_MX_AO] = &mx_ao, + [SC8280XP_MXC] = &mxc, + [SC8280XP_MXC_AO] = &mxc_ao, [SC8280XP_NSP] = &nsp, [SC8280XP_QPHY] = &qphy, }; From a57459614aa695c570db6fd55932bb2b35d9b934 Mon Sep 17 00:00:00 2001 From: Baochen Qiang Date: Thu, 8 Jan 2026 11:21:46 +0800 Subject: [PATCH 2632/3902] wifi: ath12k: don't force radio frequency check in freq_to_idx() [ Upstream commit 1fed08c5519d2f929457f354d3c06c6a8c33829c ] freq_to_idx() is used to map a channel to a survey index. Commit acc152f9be20 ("wifi: ath12k: combine channel list for split-phy devices in single-wiphy") adds radio specific frequency range check in this helper to make sure an invalid index is returned if the channel falls outside that range. However, this check introduces a race, resulting in below warnings as reported in [1]. ath12k_pci 0000:08:00.0: chan info: invalid frequency 6455 (idx 101 out of bounds) ath12k_pci 0000:08:00.0: chan info: invalid frequency 6535 (idx 101 out of bounds) ath12k_pci 0000:08:00.0: chan info: invalid frequency 6615 (idx 101 out of bounds) ath12k_pci 0000:08:00.0: chan info: invalid frequency 6695 (idx 101 out of bounds) ath12k_pci 0000:08:00.0: chan info: invalid frequency 6775 (idx 101 out of bounds) ath12k_pci 0000:08:00.0: chan info: invalid frequency 6855 (idx 101 out of bounds) ath12k_pci 0000:08:00.0: chan info: invalid frequency 6935 (idx 101 out of bounds) ath12k_pci 0000:08:00.0: chan info: invalid frequency 7015 (idx 101 out of bounds) ath12k_pci 0000:08:00.0: chan info: invalid frequency 7095 (idx 101 out of bounds) ath12k_pci 0000:08:00.0: chan info: invalid frequency 6435 (idx 101 out of bounds) Race scenario: 1) A regdomain covering below frequency range is uploaded to host via WMI_REG_CHAN_LIST_CC_EXT_EVENTID event: Country 00, CFG Regdomain UNSET FW Regdomain 0, num_reg_rules 6 1. (2402 - 2472 @ 40) (0, 20) (0 ms) (FLAGS 360448) (0, 0) 2. (2457 - 2477 @ 20) (0, 20) (0 ms) (FLAGS 360576) (0, 0) 3. (5170 - 5330 @ 160) (0, 20) (0 ms) (FLAGS 264320) (0, 0) 4. (5490 - 5730 @ 160) (0, 20) (0 ms) (FLAGS 264320) (0, 0) 5. (5735 - 5895 @ 160) (0, 20) (0 ms) (FLAGS 264320) (0, 0) 6. (5925 - 7125 @ 320) (0, 24) (0 ms) (FLAGS 2056) (0, 255) As a result, radio frequency range is updated as [2402, 7125] ath12k_pci 0000:08:00.0: mac pdev 0 freq limit updated. New range 2402->7125 MHz If no scan in progress or after scan finished, command WMI_SCAN_CHAN_LIST_CMDID is sent to firmware notifying that firmware is allowed to do scan on all channels within that range. The running path is: /* redomain uploaded */ 1. WMI_REG_CHAN_LIST_CC_EXT_EVENTID 2. ath12k_reg_chan_list_event() 3. ath12k_reg_handle_chan_list() 4. queue_work(..., &ar->regd_update_work) 5. ath12k_regd_update_work() 6. ath12k_regd_update() /* update radio frequency range */ 7. ath12k_mac_update_freq_range() 8. regulatory_set_wiphy_regd() 9. ath12k_reg_notifier() 10. ath12k_reg_update_chan_list() 11. queue_work(..., &ar->regd_channel_update_work) 12. ath12k_regd_update_chan_list_work() /* wait scan finishes */ 13. wait_for_completion_timeout(&ar->scan.completed, ...) /* command notifying list of valid channels */ 14. ath12k_wmi_send_scan_chan_list_cmd() 2) Hardware scan is triggered on all allowed channels. 3) Before scan completed, 11D mechanism detects a new country code ath12k_pci 0000:08:00.0: wmi 11d new cc GB With this code sent to firmware, firmware uploads a new regdomain Country GB, CFG Regdomain ETSI FW Regdomain 2, num_reg_rules 9 1. (2402 - 2482 @ 40) (0, 20) (0 ms) (FLAGS 360448) (0, 0) 2. (5170 - 5250 @ 80) (0, 23) (0 ms) (FLAGS 264192) (0, 0) 3. (5250 - 5330 @ 80) (0, 23) (0 ms) (FLAGS 264216) (0, 0) 4. (5490 - 5590 @ 80) (0, 30) (0 ms) (FLAGS 264208) 5. (5590 - 5650 @ 40) (0, 30) (600000 ms) (FLAGS 264208) 6. (5650 - 5730 @ 80) (0, 30) (0 ms) (FLAGS 264208) 7. (5735 - 5875 @ 80) (0, 14) (0 ms) (FLAGS 264192) (0, 0) 8. (5855 - 5875 @ 20) (0, 14) (0 ms) (FLAGS 264192) (0, 0) 9. (5945 - 6425 @ 320) (0, 24) (0 ms) (FLAGS 2056) (0, 11) Then radio frequency range is updated as [2402, 6425] ath12k_pci 0000:08:00.0: mac pdev 0 freq limit updated. New range 2402->6425 MHz Please note this is a smaller range than the previous one. Later host runs the same path for the purpose of notifying the new channel list. However since scan not completed, host just waits there. Meanwhile, firmware is possibly scanning channels outside the new range. As a result, WMI_CHAN_INFO_EVENTID events for those channels fail freq_to_idx() check and triggers warnings above. Fix this issue by removing radio frequency check in freq_to_idx(). This is valid because channels being scanned do not synchronize with frequency range update. Besides, this won't cause any problem, since freq_to_idx() is only used for survey data. Even out-of-range channels filled in the survey, they won't get delivered to userspace due to the range check already there in ath12k_mac_op_get_survey(). Tested-on: WCN7850 hw2.0 PCI WLAN.HMT.1.1.c5-00302-QCAHMTSWPL_V1.0_V2.0_SILICONZ-1.115823.3 Fixes: acc152f9be20 ("wifi: ath12k: combine channel list for split-phy devices in single-wiphy") Closes: https://bugzilla.kernel.org/show_bug.cgi?id=220871 # 1 Signed-off-by: Baochen Qiang Link: https://patch.msgid.link/20260108-ath12k-fix-freq-to-idx-v1-1-b2458cf7aa0d@oss.qualcomm.com Signed-off-by: Jeff Johnson Signed-off-by: Sasha Levin --- drivers/net/wireless/ath/ath12k/wmi.c | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/drivers/net/wireless/ath/ath12k/wmi.c b/drivers/net/wireless/ath/ath12k/wmi.c index e647b842a6a1cd..44e99b47e445d4 100644 --- a/drivers/net/wireless/ath/ath12k/wmi.c +++ b/drivers/net/wireless/ath/ath12k/wmi.c @@ -6520,16 +6520,9 @@ static int freq_to_idx(struct ath12k *ar, int freq) if (!sband) continue; - for (ch = 0; ch < sband->n_channels; ch++, idx++) { - if (sband->channels[ch].center_freq < - KHZ_TO_MHZ(ar->freq_range.start_freq) || - sband->channels[ch].center_freq > - KHZ_TO_MHZ(ar->freq_range.end_freq)) - continue; - + for (ch = 0; ch < sband->n_channels; ch++, idx++) if (sband->channels[ch].center_freq == freq) goto exit; - } } exit: From 62ced8e065787d065445d10f6af9f468160dc735 Mon Sep 17 00:00:00 2001 From: Niklas Cassel Date: Mon, 12 Jan 2026 13:20:46 +0100 Subject: [PATCH 2633/3902] ata: ahci: Do not read the per port area for unimplemented ports MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [ Upstream commit ea4d4ea6d10a561043922d285f1765c7e4bfd32a ] An AHCI HBA specifies the number of ports it supports using CAP.NP. The HBA is free to only make a subset of the number of ports available using the PI (Ports Implemented) register. libata currently creates dummy ports for HBA ports that are provided by the HBA, but which are marked as "unavailable" using the PI register. Each port will have a per port area of registers in the HBA, regardless if the port is marked as "unavailable" or not. ahci_mark_external_port() currently reads this per port area of registers using readl() to see if the port is marked as external/hotplug-capable. However, AHCI 1.3.1, section "3.1.4 Offset 0Ch: PI – Ports Implemented" states: "Software must not read or write to registers within unavailable ports." Thus, make sure that we only call ahci_mark_external_port() and ahci_update_initial_lpm_policy() for ports that are implemented. From a libata perspective, this should not change anything related to LPM, as dummy ports do not provide any ap->ops (they do not have a .set_lpm() callback), so even if EH were to call .set_lpm() on a dummy port, it was already a no-op. Fixes: f7131935238d ("ata: ahci: move marking of external port earlier") Signed-off-by: Niklas Cassel Tested-by: Wolf Signed-off-by: Damien Le Moal Signed-off-by: Sasha Levin --- drivers/ata/ahci.c | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/drivers/ata/ahci.c b/drivers/ata/ahci.c index 7a7f88b3fa2b18..931d0081169b9c 100644 --- a/drivers/ata/ahci.c +++ b/drivers/ata/ahci.c @@ -2094,13 +2094,13 @@ static int ahci_init_one(struct pci_dev *pdev, const struct pci_device_id *ent) if (ap->flags & ATA_FLAG_EM) ap->em_message_type = hpriv->em_msg_type; - ahci_mark_external_port(ap); - - ahci_update_initial_lpm_policy(ap); - /* disabled/not-implemented port */ - if (!(hpriv->port_map & (1 << i))) + if (!(hpriv->port_map & (1 << i))) { ap->ops = &ata_dummy_port_ops; + } else { + ahci_mark_external_port(ap); + ahci_update_initial_lpm_policy(ap); + } } /* apply workaround for ASUS P5W DH Deluxe mainboard */ From 05db73211387f6957c8c4f44b3ab62e807318997 Mon Sep 17 00:00:00 2001 From: Niklas Cassel Date: Mon, 12 Jan 2026 13:20:47 +0100 Subject: [PATCH 2634/3902] ata: libata: Call ata_dev_config_lpm() for ATAPI devices [ Upstream commit 8f3fb33f8f3f825c708ece800c921977c157f9b6 ] Commit d360121832d8 ("ata: libata-core: Introduce ata_dev_config_lpm()") introduced ata_dev_config_lpm(). However, it only called this function for ATA_DEV_ATA and ATA_DEV_ZAC devices, not for ATA_DEV_ATAPI devices. Additionally, commit d99a9142e782 ("ata: libata-core: Move device LPM quirk settings to ata_dev_config_lpm()") moved the LPM quirk application from ata_dev_configure() to ata_dev_config_lpm(), causing LPM quirks for ATAPI devices to no longer be applied. Call ata_dev_config_lpm() also for ATAPI devices, such that LPM quirks are applied for ATAPI devices with an entry in __ata_dev_quirks once again. Fixes: d360121832d8 ("ata: libata-core: Introduce ata_dev_config_lpm()") Fixes: d99a9142e782 ("ata: libata-core: Move device LPM quirk settings to ata_dev_config_lpm()") Signed-off-by: Niklas Cassel Tested-by: Wolf Signed-off-by: Damien Le Moal Signed-off-by: Sasha Levin --- drivers/ata/libata-core.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/drivers/ata/libata-core.c b/drivers/ata/libata-core.c index 1216b4f2eb9047..0a21804b133a48 100644 --- a/drivers/ata/libata-core.c +++ b/drivers/ata/libata-core.c @@ -3116,6 +3116,8 @@ int ata_dev_configure(struct ata_device *dev) ata_mode_string(xfer_mask), cdb_intr_string, atapi_an_string, dma_dir_string); + + ata_dev_config_lpm(dev); } /* determine max_sectors */ From 7fe7e7858b32d250c011ef82993af9ed361cb4b3 Mon Sep 17 00:00:00 2001 From: Niklas Cassel Date: Mon, 12 Jan 2026 13:20:48 +0100 Subject: [PATCH 2635/3902] ata: libata-sata: Improve link_power_management_supported sysfs attribute [ Upstream commit ce83767ea323baf8509a75eb0c783cd203e14789 ] The link_power_management_supported sysfs attribute is currently set as true even for ata ports that lack a .set_lpm() callback, e.g. dummy ports. This is a bit silly, because while writing to the link_power_management_policy sysfs attribute will make ata_scsi_lpm_store() update ap->target_lpm_policy (thus sysfs will reflect the new value) and call ata_port_schedule_eh() for the port, it is essentially a no-op. This is because for a port without a .set_lpm() callback, once EH gets to run, the ata_eh_link_set_lpm() will simply return, since the port does not provide a .set_lpm() callback. Thus, make sure that the link_power_management_supported sysfs attribute is set to false for ports that lack a .set_lpm() callback. This way the link_power_management_policy sysfs attribute will no longer be writable, so we will no longer be misleading users to think that their sysfs write actually does something. Fixes: 0060beec0bfa ("ata: libata-sata: Add link_power_management_supported sysfs attribute") Signed-off-by: Niklas Cassel Tested-by: Wolf Signed-off-by: Damien Le Moal Signed-off-by: Sasha Levin --- drivers/ata/libata-sata.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/ata/libata-sata.c b/drivers/ata/libata-sata.c index b2817a2995d6ba..04e1e774645e24 100644 --- a/drivers/ata/libata-sata.c +++ b/drivers/ata/libata-sata.c @@ -909,7 +909,7 @@ static bool ata_scsi_lpm_supported(struct ata_port *ap) struct ata_link *link; struct ata_device *dev; - if (ap->flags & ATA_FLAG_NO_LPM) + if ((ap->flags & ATA_FLAG_NO_LPM) || !ap->ops->set_lpm) return false; ata_for_each_link(link, ap, EDGE) { From 93f484d63f87352ea85169d95bf648aa181eee4e Mon Sep 17 00:00:00 2001 From: Niklas Cassel Date: Mon, 12 Jan 2026 13:20:49 +0100 Subject: [PATCH 2636/3902] ata: libata: Add cpr_log to ata_dev_print_features() early return [ Upstream commit a6bee5e5243ad02cae575becc4c83df66fc29573 ] ata_dev_print_features() is supposed to return early and not print anything if there are no features supported. However, commit fe22e1c2f705 ("libata: support concurrent positioning ranges log") added another feature to ata_dev_print_features() without updating the early return conditional. Add the missing feature to the early return conditional. Fixes: fe22e1c2f705 ("libata: support concurrent positioning ranges log") Signed-off-by: Niklas Cassel Tested-by: Wolf Signed-off-by: Damien Le Moal Signed-off-by: Sasha Levin --- drivers/ata/libata-core.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/ata/libata-core.c b/drivers/ata/libata-core.c index 0a21804b133a48..490cc0d628d3b8 100644 --- a/drivers/ata/libata-core.c +++ b/drivers/ata/libata-core.c @@ -2872,7 +2872,7 @@ static void ata_dev_config_lpm(struct ata_device *dev) static void ata_dev_print_features(struct ata_device *dev) { - if (!(dev->flags & ATA_DFLAG_FEATURES_MASK)) + if (!(dev->flags & ATA_DFLAG_FEATURES_MASK) && !dev->cpr_log) return; ata_dev_info(dev, From d5529d36e76469d70b29fefa7945bf06e2590c7d Mon Sep 17 00:00:00 2001 From: Niklas Cassel Date: Mon, 12 Jan 2026 13:20:50 +0100 Subject: [PATCH 2637/3902] ata: libata: Add DIPM and HIPM to ata_dev_print_features() early return [ Upstream commit 89531b68fc293e91187bf0992147e8d22c65cff3 ] ata_dev_print_features() is supposed to return early and not print anything if there are no features supported. However, commit b1f5af54f1f5 ("ata: libata-core: Advertize device support for DIPM and HIPM features") added additional features to ata_dev_print_features() without updating the early return conditional. Add the missing features to the early return conditional. Fixes: b1f5af54f1f5 ("ata: libata-core: Advertize device support for DIPM and HIPM features") Signed-off-by: Niklas Cassel Tested-by: Wolf Signed-off-by: Damien Le Moal Signed-off-by: Sasha Levin --- drivers/ata/libata-core.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/drivers/ata/libata-core.c b/drivers/ata/libata-core.c index 490cc0d628d3b8..c41714bea77e8f 100644 --- a/drivers/ata/libata-core.c +++ b/drivers/ata/libata-core.c @@ -2872,7 +2872,8 @@ static void ata_dev_config_lpm(struct ata_device *dev) static void ata_dev_print_features(struct ata_device *dev) { - if (!(dev->flags & ATA_DFLAG_FEATURES_MASK) && !dev->cpr_log) + if (!(dev->flags & ATA_DFLAG_FEATURES_MASK) && !dev->cpr_log && + !ata_id_has_hipm(dev->id) && !ata_id_has_dipm(dev->id)) return; ata_dev_info(dev, From 887032ba3ff23f1c7999b15a8e04e54d3dc6db2d Mon Sep 17 00:00:00 2001 From: Niklas Cassel Date: Mon, 12 Jan 2026 13:20:51 +0100 Subject: [PATCH 2638/3902] ata: libata: Print features also for ATAPI devices [ Upstream commit c8c6fb886f57d5bf71fb6de6334a143608d35707 ] Commit d633b8a702ab ("libata: print feature list on device scan") added a print of the features supported by the device for ATA_DEV_ATA and ATA_DEV_ZAC devices, but not for ATA_DEV_ATAPI devices. Fix this by printing the features also for ATAPI devices. Before changes: ata1.00: ATAPI: Slimtype DVD A DU8AESH, 6C2M, max UDMA/133 After changes: ata1.00: ATAPI: Slimtype DVD A DU8AESH, 6C2M, max UDMA/133 ata1.00: Features: Dev-Attention HIPM DIPM Fixes: d633b8a702ab ("libata: print feature list on device scan") Signed-off-by: Niklas Cassel Tested-by: Wolf Signed-off-by: Damien Le Moal Signed-off-by: Sasha Levin --- drivers/ata/libata-core.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/drivers/ata/libata-core.c b/drivers/ata/libata-core.c index c41714bea77e8f..699919e4579e19 100644 --- a/drivers/ata/libata-core.c +++ b/drivers/ata/libata-core.c @@ -3119,6 +3119,9 @@ int ata_dev_configure(struct ata_device *dev) dma_dir_string); ata_dev_config_lpm(dev); + + if (print_info) + ata_dev_print_features(dev); } /* determine max_sectors */ From 1aa669892459567b43365474e5ef76cbc211f20e Mon Sep 17 00:00:00 2001 From: Manish Dharanenthiran Date: Wed, 7 Jan 2026 11:32:35 +0530 Subject: [PATCH 2639/3902] wifi: ath12k: cancel scan only on active scan vdev [ Upstream commit 39c90b1a1dbe6d7c49d19da6e5aec00980c55d8b ] Cancel the scheduled scan request only on the vdev that has an active scan running. Currently, ahvif->links_map is used to obtain the links, but this includes links for which no scan is scheduled. In failure cases where the scan fails due to an invalid channel definition, other links which are not yet brought up (vdev not created) may also be accessed, leading to the following trace: Unable to handle kernel paging request at virtual address 0000000000004c8c pc : _raw_spin_lock_bh+0x1c/0x54 lr : ath12k_scan_abort+0x20/0xc8 [ath12k] Call trace: _raw_spin_lock_bh+0x1c/0x54 (P) ath12k_mac_op_cancel_hw_scan+0xac/0xc4 [ath12k] ieee80211_scan_cancel+0xcc/0x12c [mac80211] ieee80211_do_stop+0x6c4/0x7a8 [mac80211] ieee80211_stop+0x60/0xd8 [mac80211] Skip links that are not created or are not the current scan vdev. This ensures only the scan for the matching links is aborted and avoids aborting unrelated links during cancellation, thus aligning with how start/cleanup manage ar->scan.arvif. Also, remove the redundant arvif->is_started check from ath12k_mac_op_cancel_hw_scan() that was introduced in commit 3863f014ad23 ("wifi: ath12k: symmetrize scan vdev creation and deletion during HW scan") to avoid deleting the scan interface if the scan is triggered on the existing AP vdev as this use case is already handled in ath12k_scan_vdev_clean_work(). Tested-on: QCN9274 hw2.0 PCI WLAN.WBE.1.4.1-00199-QCAHKSWPL_SILICONZ-1 Fixes: feed05f1526e ("wifi: ath12k: Split scan request for split band device") Signed-off-by: Manish Dharanenthiran Reviewed-by: Baochen Qiang Reviewed-by: Vasanthakumar Thiagarajan Link: https://patch.msgid.link/20260107-scan_vdev-v1-1-b600aedc645a@qti.qualcomm.com Signed-off-by: Jeff Johnson Signed-off-by: Sasha Levin --- drivers/net/wireless/ath/ath12k/mac.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/drivers/net/wireless/ath/ath12k/mac.c b/drivers/net/wireless/ath/ath12k/mac.c index 095b49a39683ce..ffeb6677343588 100644 --- a/drivers/net/wireless/ath/ath12k/mac.c +++ b/drivers/net/wireless/ath/ath12k/mac.c @@ -5254,7 +5254,8 @@ static void ath12k_mac_op_cancel_hw_scan(struct ieee80211_hw *hw, for_each_set_bit(link_id, &links_map, ATH12K_NUM_MAX_LINKS) { arvif = wiphy_dereference(hw->wiphy, ahvif->link[link_id]); - if (!arvif || arvif->is_started) + if (!arvif || !arvif->is_created || + arvif->ar->scan.arvif != arvif) continue; ar = arvif->ar; From c8e5f49667f5f689a88240d00b39c8e93fdab058 Mon Sep 17 00:00:00 2001 From: Yingying Tang Date: Mon, 12 Jan 2026 19:55:16 +0800 Subject: [PATCH 2640/3902] wifi: ath12k: Fix scan state stuck in ABORTING after cancel_remain_on_channel [ Upstream commit 8b8d6ee53dfdee61b0beff66afe3f712456e707a ] Scan finish workqueue was introduced in __ath12k_mac_scan_finish() by [1]. During ath12k_mac_op_cancel_remain_on_channel(), scan state is set to ABORTING and should be reset to IDLE in the queued work. However, wiphy_work_cancel() is called before exiting ath12k_mac_op_cancel_remain_on_channel(), which prevents the work from running and leaves the state in ABORTING. This blocks all subsequent scan requests. Replace wiphy_work_cancel() with wiphy_work_flush() to ensure the queued work runs and scan state is reset to IDLE. Tested-on: WCN7850 hw2.0 PCI WLAN.HMT.1.1.c5-00302-QCAHMTSWPL_V1.0_V2.0_SILICONZ-1.115823.3 Fixes: 3863f014ad23 ("wifi: ath12k: symmetrize scan vdev creation and deletion during HW scan") # [1] Signed-off-by: Yingying Tang Reviewed-by: Vasanthakumar Thiagarajan Reviewed-by: Baochen Qiang Link: https://patch.msgid.link/20260112115516.2144219-1-yingying.tang@oss.qualcomm.com Signed-off-by: Jeff Johnson Signed-off-by: Sasha Levin --- drivers/net/wireless/ath/ath12k/mac.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/net/wireless/ath/ath12k/mac.c b/drivers/net/wireless/ath/ath12k/mac.c index ffeb6677343588..00b3bf4d882a5f 100644 --- a/drivers/net/wireless/ath/ath12k/mac.c +++ b/drivers/net/wireless/ath/ath12k/mac.c @@ -12833,7 +12833,7 @@ static int ath12k_mac_op_cancel_remain_on_channel(struct ieee80211_hw *hw, ath12k_scan_abort(ar); cancel_delayed_work_sync(&ar->scan.timeout); - wiphy_work_cancel(hw->wiphy, &ar->scan.vdev_clean_wk); + wiphy_work_flush(hw->wiphy, &ar->scan.vdev_clean_wk); return 0; } From 06ac2aa13f701a0296e92f5f54ae24224d426b28 Mon Sep 17 00:00:00 2001 From: Baochen Qiang Date: Tue, 13 Jan 2026 09:48:11 +0800 Subject: [PATCH 2641/3902] wifi: ath12k: fix dead lock while flushing management frames [ Upstream commit f88e9fc30a261d63946ddc6cc6a33405e6aa27c3 ] Commit [1] converted the management transmission work item into a wiphy work. Since a wiphy work can only run under wiphy lock protection, a race condition happens in below scenario: 1. a management frame is queued for transmission. 2. ath12k_mac_op_flush() gets called to flush pending frames associated with the hardware (i.e, vif being NULL). Then in ath12k_mac_flush() the process waits for the transmission done. 3. Since wiphy lock has been taken by the flush process, the transmission work item has no chance to run, hence the dead lock. >From user view, this dead lock results in below issue: wlp8s0: authenticate with xxxxxx (local address=xxxxxx) wlp8s0: send auth to xxxxxx (try 1/3) wlp8s0: authenticate with xxxxxx (local address=xxxxxx) wlp8s0: send auth to xxxxxx (try 1/3) wlp8s0: authenticated wlp8s0: associate with xxxxxx (try 1/3) wlp8s0: aborting association with xxxxxx by local choice (Reason: 3=DEAUTH_LEAVING) ath12k_pci 0000:08:00.0: failed to flush mgmt transmit queue, mgmt pkts pending 1 The dead lock can be avoided by invoking wiphy_work_flush() to proactively run the queued work item. Note actually it is already present in ath12k_mac_op_flush(), however it does not protect the case where vif being NULL. Hence move it ahead to cover this case as well. Tested-on: WCN7850 hw2.0 PCI WLAN.HMT.1.1.c5-00302-QCAHMTSWPL_V1.0_V2.0_SILICONZ-1.115823.3 Fixes: 56dcbf0b5207 ("wifi: ath12k: convert struct ath12k::wmi_mgmt_tx_work to struct wiphy_work") # [1] Reported-by: Stuart Hayhurst Closes: https://bugzilla.kernel.org/show_bug.cgi?id=220959 Signed-off-by: Baochen Qiang Reviewed-by: Vasanthakumar Thiagarajan Link: https://patch.msgid.link/20260113-ath12k-fix-dead-lock-while-flushing-v1-1-9713621f3a0f@oss.qualcomm.com Signed-off-by: Jeff Johnson Signed-off-by: Sasha Levin --- drivers/net/wireless/ath/ath12k/mac.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/drivers/net/wireless/ath/ath12k/mac.c b/drivers/net/wireless/ath/ath12k/mac.c index 00b3bf4d882a5f..d6a44c19e22455 100644 --- a/drivers/net/wireless/ath/ath12k/mac.c +++ b/drivers/net/wireless/ath/ath12k/mac.c @@ -11798,6 +11798,9 @@ static void ath12k_mac_op_flush(struct ieee80211_hw *hw, struct ieee80211_vif *v if (drop) return; + for_each_ar(ah, ar, i) + wiphy_work_flush(hw->wiphy, &ar->wmi_mgmt_tx_work); + /* vif can be NULL when flush() is considered for hw */ if (!vif) { for_each_ar(ah, ar, i) @@ -11805,9 +11808,6 @@ static void ath12k_mac_op_flush(struct ieee80211_hw *hw, struct ieee80211_vif *v return; } - for_each_ar(ah, ar, i) - wiphy_work_flush(hw->wiphy, &ar->wmi_mgmt_tx_work); - ahvif = ath12k_vif_to_ahvif(vif); links = ahvif->links_map; for_each_set_bit(link_id, &links, IEEE80211_MLD_MAX_NUM_LINKS) { From c35a0cb145f2cf79b2c24e4656701da68f201e1c Mon Sep 17 00:00:00 2001 From: Yingying Tang Date: Tue, 13 Jan 2026 13:46:36 +0800 Subject: [PATCH 2642/3902] wifi: ath12k: Fix wrong P2P device link id issue [ Upstream commit 31707572108da55a005e7fed32cc3869c16b7c16 ] Wrong P2P device link id value of 0 was introduced in ath12k_mac_op_tx() by [1]. During the P2P negotiation process, there is only one scan vdev with link ID 15. Currently, the device link ID is incorrectly set to 0 in ath12k_mac_op_tx() during the P2P negotiation process, which leads to TX failures. Set the correct P2P device link ID to 15 to fix the TX failure issue. Tested-on: WCN7850 hw2.0 PCI WLAN.HMT.1.1.c5-00302-QCAHMTSWPL_V1.0_V2.0_SILICONZ-1.115823.3 Fixes: 648a121bafa3 ("wifi: ath12k: ath12k_mac_op_tx(): MLO support") # [1] Signed-off-by: Yingying Tang Reviewed-by: Baochen Qiang Reviewed-by: Vasanthakumar Thiagarajan Cc: linux-next@vger.kernel.org Cc: netdev@vger.kernel.org Link: https://patch.msgid.link/20260113054636.2620035-1-yingying.tang@oss.qualcomm.com Signed-off-by: Jeff Johnson --- drivers/net/wireless/ath/ath12k/mac.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/drivers/net/wireless/ath/ath12k/mac.c b/drivers/net/wireless/ath/ath12k/mac.c index d6a44c19e22455..256ffae4d7f7dc 100644 --- a/drivers/net/wireless/ath/ath12k/mac.c +++ b/drivers/net/wireless/ath/ath12k/mac.c @@ -8840,7 +8840,10 @@ static void ath12k_mac_op_tx(struct ieee80211_hw *hw, return; } } else { - link_id = 0; + if (vif->type == NL80211_IFTYPE_P2P_DEVICE) + link_id = ATH12K_FIRST_SCAN_LINK; + else + link_id = 0; } arvif = rcu_dereference(ahvif->link[link_id]); From 0bc8d94bf66949c5e5207cc5d302a3af39b8f323 Mon Sep 17 00:00:00 2001 From: Jacob Keller Date: Thu, 20 Nov 2025 12:20:41 -0800 Subject: [PATCH 2643/3902] ice: initialize ring_stats->syncp [ Upstream commit 8439016c3b8b5ab687c2420317b1691585106611 ] The u64_stats_sync structure is empty on 64-bit systems. However, on 32-bit systems it contains a seqcount_t which needs to be initialized. While the memory is zero-initialized, a lack of u64_stats_init means that lockdep won't get initialized properly. Fix this by adding u64_stats_init() calls to the rings just after allocation. Fixes: 2b245cb29421 ("ice: Implement transmit and NAPI support") Reviewed-by: Aleksandr Loktionov Signed-off-by: Jacob Keller Reviewed-by: Simon Horman Tested-by: Rinitha S (A Contingent worker at Intel) Signed-off-by: Tony Nguyen Signed-off-by: Sasha Levin --- drivers/net/ethernet/intel/ice/ice_lib.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/drivers/net/ethernet/intel/ice/ice_lib.c b/drivers/net/ethernet/intel/ice/ice_lib.c index 4479c824561e9b..c0d221d4b4f47e 100644 --- a/drivers/net/ethernet/intel/ice/ice_lib.c +++ b/drivers/net/ethernet/intel/ice/ice_lib.c @@ -398,6 +398,8 @@ static int ice_vsi_alloc_ring_stats(struct ice_vsi *vsi) if (!ring_stats) goto err_out; + u64_stats_init(&ring_stats->syncp); + WRITE_ONCE(tx_ring_stats[i], ring_stats); } @@ -417,6 +419,8 @@ static int ice_vsi_alloc_ring_stats(struct ice_vsi *vsi) if (!ring_stats) goto err_out; + u64_stats_init(&ring_stats->syncp); + WRITE_ONCE(rx_ring_stats[i], ring_stats); } From 753171a695b9342388930b3b15fe5ee485f0e7d5 Mon Sep 17 00:00:00 2001 From: Dave Ertman Date: Thu, 20 Nov 2025 09:58:26 -0800 Subject: [PATCH 2644/3902] ice: Avoid detrimental cleanup for bond during interface stop [ Upstream commit a9d45c22ed120cdd15ff56d0a6e4700c46451901 ] When the user issues an administrative down to an interface that is the primary for an aggregate bond, the prune lists are being purged. This breaks communication to the secondary interface, which shares a prune list on the main switch block while bonded together. For the primary interface of an aggregate, avoid deleting these prune lists during stop, and since they are hardcoded to specific values for the default vlan and QinQ vlans, the attempt to re-add them during the up phase will quietly fail without any additional problem. Fixes: 1e0f9881ef79 ("ice: Flesh out implementation of support for SRIOV on bonded interface") Reviewed-by: Jacob Keller Reviewed-by: Marcin Szycik Signed-off-by: Dave Ertman Tested-by: Rinitha S (A Contingent worker at Intel) Signed-off-by: Tony Nguyen Signed-off-by: Sasha Levin --- drivers/net/ethernet/intel/ice/ice_lib.c | 25 ++++++++++++++++-------- 1 file changed, 17 insertions(+), 8 deletions(-) diff --git a/drivers/net/ethernet/intel/ice/ice_lib.c b/drivers/net/ethernet/intel/ice/ice_lib.c index c0d221d4b4f47e..5a3e7d6697325f 100644 --- a/drivers/net/ethernet/intel/ice/ice_lib.c +++ b/drivers/net/ethernet/intel/ice/ice_lib.c @@ -3810,22 +3810,31 @@ int ice_vsi_add_vlan_zero(struct ice_vsi *vsi) int ice_vsi_del_vlan_zero(struct ice_vsi *vsi) { struct ice_vsi_vlan_ops *vlan_ops = ice_get_compat_vsi_vlan_ops(vsi); + struct ice_pf *pf = vsi->back; struct ice_vlan vlan; int err; - vlan = ICE_VLAN(0, 0, 0); - err = vlan_ops->del_vlan(vsi, &vlan); - if (err && err != -EEXIST) - return err; + if (pf->lag && pf->lag->primary) { + dev_dbg(ice_pf_to_dev(pf), "Interface is primary in aggregate - not deleting prune list\n"); + } else { + vlan = ICE_VLAN(0, 0, 0); + err = vlan_ops->del_vlan(vsi, &vlan); + if (err && err != -EEXIST) + return err; + } /* in SVM both VLAN 0 filters are identical */ if (!ice_is_dvm_ena(&vsi->back->hw)) return 0; - vlan = ICE_VLAN(ETH_P_8021Q, 0, 0); - err = vlan_ops->del_vlan(vsi, &vlan); - if (err && err != -EEXIST) - return err; + if (pf->lag && pf->lag->primary) { + dev_dbg(ice_pf_to_dev(pf), "Interface is primary in aggregate - not deleting QinQ prune list\n"); + } else { + vlan = ICE_VLAN(ETH_P_8021Q, 0, 0); + err = vlan_ops->del_vlan(vsi, &vlan); + if (err && err != -EEXIST) + return err; + } /* when deleting the last VLAN filter, make sure to disable the VLAN * promisc mode so the filter isn't left by accident From 695c909d1815b185f857181ce7e6c0cf38d1d868 Mon Sep 17 00:00:00 2001 From: Ding Hui Date: Sat, 6 Dec 2025 21:46:09 +0800 Subject: [PATCH 2645/3902] ice: Fix incorrect timeout ice_release_res() [ Upstream commit 01139a2ce532d77379e1593230127caa261a8036 ] The commit 5f6df173f92e ("ice: implement and use rd32_poll_timeout for ice_sq_done timeout") converted ICE_CTL_Q_SQ_CMD_TIMEOUT from jiffies to microseconds. But the ice_release_res() function was missed, and its logic still treats ICE_CTL_Q_SQ_CMD_TIMEOUT as a jiffies value. So correct the issue by usecs_to_jiffies(). Found by inspection of the DDP downloading process. Compile and modprobe tested only. Fixes: 5f6df173f92e ("ice: implement and use rd32_poll_timeout for ice_sq_done timeout") Signed-off-by: Ding Hui Reviewed-by: Simon Horman Reviewed-by: Aleksandr Loktionov Reviewed-by: Jacob Keller Reviewed-by: Paul Menzel Tested-by: Rinitha S (A Contingent worker at Intel) Signed-off-by: Tony Nguyen Signed-off-by: Sasha Levin --- drivers/net/ethernet/intel/ice/ice_common.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/net/ethernet/intel/ice/ice_common.c b/drivers/net/ethernet/intel/ice/ice_common.c index 6edeb06b4dce25..eb148c8d9e0834 100644 --- a/drivers/net/ethernet/intel/ice/ice_common.c +++ b/drivers/net/ethernet/intel/ice/ice_common.c @@ -2251,7 +2251,7 @@ void ice_release_res(struct ice_hw *hw, enum ice_aq_res_ids res) /* there are some rare cases when trying to release the resource * results in an admin queue timeout, so handle them correctly */ - timeout = jiffies + 10 * ICE_CTL_Q_SQ_CMD_TIMEOUT; + timeout = jiffies + 10 * usecs_to_jiffies(ICE_CTL_Q_SQ_CMD_TIMEOUT); do { status = ice_aq_release_res(hw, res, 0, NULL); if (status != -EIO) From a38d2c624639c0a14dcfb24cc574a721e40914e8 Mon Sep 17 00:00:00 2001 From: Kurt Kanzenbach Date: Thu, 20 Nov 2025 09:18:29 +0100 Subject: [PATCH 2646/3902] igc: Restore default Qbv schedule when changing channels [ Upstream commit 41a9a6826f20a524242a6c984845c4855f629841 ] The Multi-queue Priority (MQPRIO) and Earliest TxTime First (ETF) offloads utilize the Time Sensitive Networking (TSN) Tx mode. This mode is always coupled to IEEE 802.1Qbv time aware shaper (Qbv). Therefore, the driver sets a default Qbv schedule of all gates opened and a cycle time of 1s. This schedule is set during probe. However, the following sequence of events lead to Tx issues: - Boot a dual core system igc_probe(): igc_tsn_clear_schedule(): -> Default Schedule is set Note: At this point the driver has allocated two Tx/Rx queues, because there are only two CPUs. - ethtool -L enp3s0 combined 4 igc_ethtool_set_channels(): igc_reinit_queues() -> Default schedule is gone, per Tx ring start and end time are zero - tc qdisc replace dev enp3s0 handle 100 parent root mqprio \ num_tc 4 map 3 3 2 2 0 1 1 1 3 3 3 3 3 3 3 3 \ queues 1@0 1@1 1@2 1@3 hw 1 igc_tsn_offload_apply(): igc_tsn_enable_offload(): -> Writes zeros to IGC_STQT(i) and IGC_ENDQT(i), causing Tx to stall/fail Therefore, restore the default Qbv schedule after changing the number of channels. Furthermore, add a restriction to not allow queue reconfiguration when TSN/Qbv is enabled, because it may lead to inconsistent states. Fixes: c814a2d2d48f ("igc: Use default cycle 'start' and 'end' values for queues") Signed-off-by: Kurt Kanzenbach Reviewed-by: Aleksandr Loktionov Tested-by: Avigail Dahan Acked-by: Vinicius Costa Gomes Signed-off-by: Tony Nguyen Signed-off-by: Sasha Levin --- drivers/net/ethernet/intel/igc/igc_ethtool.c | 4 ++-- drivers/net/ethernet/intel/igc/igc_main.c | 5 +++++ 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/drivers/net/ethernet/intel/igc/igc_ethtool.c b/drivers/net/ethernet/intel/igc/igc_ethtool.c index bb783042d1af9c..4b39329e9e32bf 100644 --- a/drivers/net/ethernet/intel/igc/igc_ethtool.c +++ b/drivers/net/ethernet/intel/igc/igc_ethtool.c @@ -1561,8 +1561,8 @@ static int igc_ethtool_set_channels(struct net_device *netdev, if (ch->other_count != NON_Q_VECTORS) return -EINVAL; - /* Do not allow channel reconfiguration when mqprio is enabled */ - if (adapter->strict_priority_enable) + /* Do not allow channel reconfiguration when any TSN qdisc is enabled */ + if (adapter->flags & IGC_FLAG_TSN_ANY_ENABLED) return -EINVAL; /* Verify the number of channels doesn't exceed hw limits */ diff --git a/drivers/net/ethernet/intel/igc/igc_main.c b/drivers/net/ethernet/intel/igc/igc_main.c index 728d7ca5338bf2..21e67e7534562c 100644 --- a/drivers/net/ethernet/intel/igc/igc_main.c +++ b/drivers/net/ethernet/intel/igc/igc_main.c @@ -7761,6 +7761,11 @@ int igc_reinit_queues(struct igc_adapter *adapter) if (netif_running(netdev)) err = igc_open(netdev); + if (!err) { + /* Restore default IEEE 802.1Qbv schedule after queue reinit */ + igc_tsn_clear_schedule(adapter); + } + return err; } From 41d7a27c47b8ad205483bba6049e036bd6136245 Mon Sep 17 00:00:00 2001 From: Chwee-Lin Choong Date: Fri, 28 Nov 2025 18:53:04 +0800 Subject: [PATCH 2647/3902] igc: fix race condition in TX timestamp read for register 0 [ Upstream commit 6990dc392a9ab10e52af37e0bee8c7b753756dc4 ] The current HW bug workaround checks the TXTT_0 ready bit first, then reads TXSTMPL_0 twice (before and after reading TXSTMPH_0) to detect whether a new timestamp was captured by timestamp register 0 during the workaround. This sequence has a race: if a new timestamp is captured after checking the TXTT_0 bit but before the first TXSTMPL_0 read, the detection fails because both the "old" and "new" values come from the same timestamp. Fix by reading TXSTMPL_0 first to establish a baseline, then checking the TXTT_0 bit. This ensures any timestamp captured during the race window will be detected. Old sequence: 1. Check TXTT_0 ready bit 2. Read TXSTMPL_0 (baseline) 3. Read TXSTMPH_0 (interrupt workaround) 4. Read TXSTMPL_0 (detect changes vs baseline) New sequence: 1. Read TXSTMPL_0 (baseline) 2. Check TXTT_0 ready bit 3. Read TXSTMPH_0 (interrupt workaround) 4. Read TXSTMPL_0 (detect changes vs baseline) Fixes: c789ad7cbebc ("igc: Work around HW bug causing missing timestamps") Suggested-by: Avi Shalev Reviewed-by: Aleksandr Loktionov Co-developed-by: Song Yoong Siang Signed-off-by: Song Yoong Siang Signed-off-by: Chwee-Lin Choong Tested-by: Avigail Dahan Signed-off-by: Tony Nguyen Signed-off-by: Sasha Levin --- drivers/net/ethernet/intel/igc/igc_ptp.c | 43 ++++++++++++++---------- 1 file changed, 25 insertions(+), 18 deletions(-) diff --git a/drivers/net/ethernet/intel/igc/igc_ptp.c b/drivers/net/ethernet/intel/igc/igc_ptp.c index b7b46d863bee44..7aae83c108fd76 100644 --- a/drivers/net/ethernet/intel/igc/igc_ptp.c +++ b/drivers/net/ethernet/intel/igc/igc_ptp.c @@ -774,36 +774,43 @@ static void igc_ptp_tx_reg_to_stamp(struct igc_adapter *adapter, static void igc_ptp_tx_hwtstamp(struct igc_adapter *adapter) { struct igc_hw *hw = &adapter->hw; + u32 txstmpl_old; u64 regval; u32 mask; int i; + /* Establish baseline of TXSTMPL_0 before checking TXTT_0. + * This baseline is used to detect if a new timestamp arrives in + * register 0 during the hardware bug workaround below. + */ + txstmpl_old = rd32(IGC_TXSTMPL); + mask = rd32(IGC_TSYNCTXCTL) & IGC_TSYNCTXCTL_TXTT_ANY; if (mask & IGC_TSYNCTXCTL_TXTT_0) { regval = rd32(IGC_TXSTMPL); regval |= (u64)rd32(IGC_TXSTMPH) << 32; } else { - /* There's a bug in the hardware that could cause - * missing interrupts for TX timestamping. The issue - * is that for new interrupts to be triggered, the - * IGC_TXSTMPH_0 register must be read. + /* TXTT_0 not set - register 0 has no new timestamp initially. + * + * Hardware bug: Future timestamp interrupts won't fire unless + * TXSTMPH_0 is read, even if the timestamp was captured in + * registers 1-3. * - * To avoid discarding a valid timestamp that just - * happened at the "wrong" time, we need to confirm - * that there was no timestamp captured, we do that by - * assuming that no two timestamps in sequence have - * the same nanosecond value. + * Workaround: Read TXSTMPH_0 here to enable future interrupts. + * However, this read clears TXTT_0. If a timestamp arrives in + * register 0 after checking TXTT_0 but before this read, it + * would be lost. * - * So, we read the "low" register, read the "high" - * register (to latch a new timestamp) and read the - * "low" register again, if "old" and "new" versions - * of the "low" register are different, a valid - * timestamp was captured, we can read the "high" - * register again. + * To detect this race: We saved a baseline read of TXSTMPL_0 + * before TXTT_0 check. After performing the workaround read of + * TXSTMPH_0, we read TXSTMPL_0 again. Since consecutive + * timestamps never share the same nanosecond value, a change + * between the baseline and new TXSTMPL_0 indicates a timestamp + * arrived during the race window. If so, read the complete + * timestamp. */ - u32 txstmpl_old, txstmpl_new; + u32 txstmpl_new; - txstmpl_old = rd32(IGC_TXSTMPL); rd32(IGC_TXSTMPH); txstmpl_new = rd32(IGC_TXSTMPL); @@ -818,7 +825,7 @@ static void igc_ptp_tx_hwtstamp(struct igc_adapter *adapter) done: /* Now that the problematic first register was handled, we can - * use retrieve the timestamps from the other registers + * retrieve the timestamps from the other registers * (starting from '1') with less complications. */ for (i = 1; i < IGC_MAX_TX_TSTAMP_REGS; i++) { From 88037973c8ef6032bf84e9955595f8b20bc14c21 Mon Sep 17 00:00:00 2001 From: Chwee-Lin Choong Date: Thu, 4 Dec 2025 20:21:50 +0800 Subject: [PATCH 2648/3902] igc: Reduce TSN TX packet buffer from 7KB to 5KB per queue [ Upstream commit 8ad1b6c1e63d25f5465b7a8aa403bdcee84b86f9 ] The previous 7 KB per queue caused TX unit hangs under heavy timestamping load. Reducing to 5 KB avoids these hangs and matches the TSN recommendation in I225/I226 SW User Manual Section 7.5.4. The 8 KB "freed" by this change is currently unused. This reduction is not expected to impact throughput, as the i226 is PCIe-limited for small TSN packets rather than TX-buffer-limited. Fixes: 0d58cdc902da ("igc: optimize TX packet buffer utilization for TSN mode") Reported-by: Zdenek Bouska Closes: https://lore.kernel.org/netdev/AS1PR10MB5675DBFE7CE5F2A9336ABFA4EBEAA@AS1PR10MB5675.EURPRD10.PROD.OUTLOOK.COM/ Reviewed-by: Paul Menzel Reviewed-by: Simon Horman Reviewed-by: Aleksandr Loktionov Signed-off-by: Chwee-Lin Choong Tested-by: Avigail Dahan Signed-off-by: Tony Nguyen Signed-off-by: Sasha Levin --- drivers/net/ethernet/intel/igc/igc_defines.h | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/drivers/net/ethernet/intel/igc/igc_defines.h b/drivers/net/ethernet/intel/igc/igc_defines.h index 498ba1522ca4d4..9482ab11f050f7 100644 --- a/drivers/net/ethernet/intel/igc/igc_defines.h +++ b/drivers/net/ethernet/intel/igc/igc_defines.h @@ -443,9 +443,10 @@ #define IGC_TXPBSIZE_DEFAULT ( \ IGC_TXPB0SIZE(20) | IGC_TXPB1SIZE(0) | IGC_TXPB2SIZE(0) | \ IGC_TXPB3SIZE(0) | IGC_OS2BMCPBSIZE(4)) +/* TSN value following I225/I226 SW User Manual Section 7.5.4 */ #define IGC_TXPBSIZE_TSN ( \ - IGC_TXPB0SIZE(7) | IGC_TXPB1SIZE(7) | IGC_TXPB2SIZE(7) | \ - IGC_TXPB3SIZE(7) | IGC_OS2BMCPBSIZE(4)) + IGC_TXPB0SIZE(5) | IGC_TXPB1SIZE(5) | IGC_TXPB2SIZE(5) | \ + IGC_TXPB3SIZE(5) | IGC_OS2BMCPBSIZE(4)) #define IGC_DTXMXPKTSZ_TSN 0x19 /* 1600 bytes of max TX DMA packet size */ #define IGC_DTXMXPKTSZ_DEFAULT 0x98 /* 9728-byte Jumbo frames */ From 63ef9b300bd09e24c57050c5dbe68feedce42e72 Mon Sep 17 00:00:00 2001 From: Michal Luczaj Date: Tue, 13 Jan 2026 16:08:18 +0100 Subject: [PATCH 2649/3902] vsock/virtio: Coalesce only linear skb [ Upstream commit 0386bd321d0f95d041a7b3d7b07643411b044a96 ] vsock/virtio common tries to coalesce buffers in rx queue: if a linear skb (with a spare tail room) is followed by a small skb (length limited by GOOD_COPY_LEN = 128), an attempt is made to join them. Since the introduction of MSG_ZEROCOPY support, assumption that a small skb will always be linear is incorrect. In the zerocopy case, data is lost and the linear skb is appended with uninitialized kernel memory. Of all 3 supported virtio-based transports, only loopback-transport is affected. G2H virtio-transport rx queue operates on explicitly linear skbs; see virtio_vsock_alloc_linear_skb() in virtio_vsock_rx_fill(). H2G vhost-transport may allocate non-linear skbs, but only for sizes that are not considered for coalescence; see PAGE_ALLOC_COSTLY_ORDER in virtio_vsock_alloc_skb(). Ensure only linear skbs are coalesced. Note that skb_tailroom(last_skb) > 0 guarantees last_skb is linear. Fixes: 581512a6dc93 ("vsock/virtio: MSG_ZEROCOPY flag support") Signed-off-by: Michal Luczaj Reviewed-by: Stefano Garzarella Link: https://patch.msgid.link/20260113-vsock-recv-coalescence-v2-1-552b17837cf4@rbox.co Signed-off-by: Jakub Kicinski Signed-off-by: Sasha Levin --- net/vmw_vsock/virtio_transport_common.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/net/vmw_vsock/virtio_transport_common.c b/net/vmw_vsock/virtio_transport_common.c index dcc8a1d5851e6a..26b979ad71f09e 100644 --- a/net/vmw_vsock/virtio_transport_common.c +++ b/net/vmw_vsock/virtio_transport_common.c @@ -1359,9 +1359,11 @@ virtio_transport_recv_enqueue(struct vsock_sock *vsk, /* Try to copy small packets into the buffer of last packet queued, * to avoid wasting memory queueing the entire buffer with a small - * payload. + * payload. Skip non-linear (e.g. zerocopy) skbs; these carry payload + * in skb_shinfo. */ - if (len <= GOOD_COPY_LEN && !skb_queue_empty(&vvs->rx_queue)) { + if (len <= GOOD_COPY_LEN && !skb_queue_empty(&vvs->rx_queue) && + !skb_is_nonlinear(skb)) { struct virtio_vsock_hdr *last_hdr; struct sk_buff *last_skb; From b9f0896f8e229aa1b159a69b1e0f3a32d2d8f994 Mon Sep 17 00:00:00 2001 From: Ethan Nelson-Moore Date: Mon, 12 Jan 2026 22:39:24 -0800 Subject: [PATCH 2650/3902] net: usb: dm9601: remove broken SR9700 support [ Upstream commit 7d7dbafefbe74f5a25efc4807af093b857a7612e ] The SR9700 chip sends more than one packet in a USB transaction, like the DM962x chips can optionally do, but the dm9601 driver does not support this mode, and the hardware does not have the DM962x MODE_CTL register to disable it, so this driver drops packets on SR9700 devices. The sr9700 driver correctly handles receiving more than one packet per transaction. While the dm9601 driver could be improved to handle this, the easiest way to fix this issue in the short term is to remove the SR9700 device ID from the dm9601 driver so the sr9700 driver is always used. This device ID should not have been in more than one driver to begin with. The "Fixes" commit was chosen so that the patch is automatically included in all kernels that have the sr9700 driver, even though the issue affects dm9601. Fixes: c9b37458e956 ("USB2NET : SR9700 : One chip USB 1.1 USB2NET SR9700Device Driver Support") Signed-off-by: Ethan Nelson-Moore Acked-by: Peter Korsgaard Link: https://patch.msgid.link/20260113063924.74464-1-enelsonmoore@gmail.com Signed-off-by: Jakub Kicinski Signed-off-by: Sasha Levin --- drivers/net/usb/dm9601.c | 4 ---- 1 file changed, 4 deletions(-) diff --git a/drivers/net/usb/dm9601.c b/drivers/net/usb/dm9601.c index 8b6d6a1b3c2eca..2b4716ccf0c5ba 100644 --- a/drivers/net/usb/dm9601.c +++ b/drivers/net/usb/dm9601.c @@ -603,10 +603,6 @@ static const struct usb_device_id products[] = { USB_DEVICE(0x0fe6, 0x8101), /* DM9601 USB to Fast Ethernet Adapter */ .driver_info = (unsigned long)&dm9601_info, }, - { - USB_DEVICE(0x0fe6, 0x9700), /* DM9601 USB to Fast Ethernet Adapter */ - .driver_info = (unsigned long)&dm9601_info, - }, { USB_DEVICE(0x0a46, 0x9000), /* DM9000E */ .driver_info = (unsigned long)&dm9601_info, From 43dee6f7ef1d228821de1b61c292af3744c8d7da Mon Sep 17 00:00:00 2001 From: Eric Dumazet Date: Tue, 13 Jan 2026 19:12:01 +0000 Subject: [PATCH 2651/3902] bonding: limit BOND_MODE_8023AD to Ethernet devices [ Upstream commit c84fcb79e5dbde0b8d5aeeaf04282d2149aebcf6 ] BOND_MODE_8023AD makes sense for ARPHRD_ETHER only. syzbot reported: BUG: KASAN: global-out-of-bounds in __hw_addr_create net/core/dev_addr_lists.c:63 [inline] BUG: KASAN: global-out-of-bounds in __hw_addr_add_ex+0x25d/0x760 net/core/dev_addr_lists.c:118 Read of size 16 at addr ffffffff8bf94040 by task syz.1.3580/19497 CPU: 1 UID: 0 PID: 19497 Comm: syz.1.3580 Tainted: G L syzkaller #0 PREEMPT(full) Tainted: [L]=SOFTLOCKUP Hardware name: Google Google Compute Engine/Google Compute Engine, BIOS Google 10/25/2025 Call Trace: dump_stack_lvl+0xe8/0x150 lib/dump_stack.c:120 print_address_description mm/kasan/report.c:378 [inline] print_report+0xca/0x240 mm/kasan/report.c:482 kasan_report+0x118/0x150 mm/kasan/report.c:595 check_region_inline mm/kasan/generic.c:-1 [inline] kasan_check_range+0x2b0/0x2c0 mm/kasan/generic.c:200 __asan_memcpy+0x29/0x70 mm/kasan/shadow.c:105 __hw_addr_create net/core/dev_addr_lists.c:63 [inline] __hw_addr_add_ex+0x25d/0x760 net/core/dev_addr_lists.c:118 __dev_mc_add net/core/dev_addr_lists.c:868 [inline] dev_mc_add+0xa1/0x120 net/core/dev_addr_lists.c:886 bond_enslave+0x2b8b/0x3ac0 drivers/net/bonding/bond_main.c:2180 do_set_master+0x533/0x6d0 net/core/rtnetlink.c:2963 do_setlink+0xcf0/0x41c0 net/core/rtnetlink.c:3165 rtnl_changelink net/core/rtnetlink.c:3776 [inline] __rtnl_newlink net/core/rtnetlink.c:3935 [inline] rtnl_newlink+0x161c/0x1c90 net/core/rtnetlink.c:4072 rtnetlink_rcv_msg+0x7cf/0xb70 net/core/rtnetlink.c:6958 netlink_rcv_skb+0x208/0x470 net/netlink/af_netlink.c:2550 netlink_unicast_kernel net/netlink/af_netlink.c:1318 [inline] netlink_unicast+0x82f/0x9e0 net/netlink/af_netlink.c:1344 netlink_sendmsg+0x805/0xb30 net/netlink/af_netlink.c:1894 sock_sendmsg_nosec net/socket.c:727 [inline] __sock_sendmsg+0x21c/0x270 net/socket.c:742 ____sys_sendmsg+0x505/0x820 net/socket.c:2592 ___sys_sendmsg+0x21f/0x2a0 net/socket.c:2646 __sys_sendmsg+0x164/0x220 net/socket.c:2678 do_syscall_32_irqs_on arch/x86/entry/syscall_32.c:83 [inline] __do_fast_syscall_32+0x1dc/0x560 arch/x86/entry/syscall_32.c:307 do_fast_syscall_32+0x34/0x80 arch/x86/entry/syscall_32.c:332 entry_SYSENTER_compat_after_hwframe+0x84/0x8e The buggy address belongs to the variable: lacpdu_mcast_addr+0x0/0x40 Fixes: 872254dd6b1f ("net/bonding: Enable bonding to enslave non ARPHRD_ETHER") Reported-by: syzbot+9c081b17773615f24672@syzkaller.appspotmail.com Closes: https://lore.kernel.org/netdev/6966946b.a70a0220.245e30.0002.GAE@google.com/T/#u Signed-off-by: Eric Dumazet Cc: Andrew Lunn Acked-by: Jay Vosburgh Link: https://patch.msgid.link/20260113191201.3970737-1-edumazet@google.com Signed-off-by: Jakub Kicinski Signed-off-by: Sasha Levin --- drivers/net/bonding/bond_main.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/drivers/net/bonding/bond_main.c b/drivers/net/bonding/bond_main.c index 5abef8a3b7758e..c66cb2d43dcf15 100644 --- a/drivers/net/bonding/bond_main.c +++ b/drivers/net/bonding/bond_main.c @@ -1953,6 +1953,12 @@ int bond_enslave(struct net_device *bond_dev, struct net_device *slave_dev, */ if (!bond_has_slaves(bond)) { if (bond_dev->type != slave_dev->type) { + if (slave_dev->type != ARPHRD_ETHER && + BOND_MODE(bond) == BOND_MODE_8023AD) { + SLAVE_NL_ERR(bond_dev, slave_dev, extack, + "8023AD mode requires Ethernet devices"); + return -EINVAL; + } slave_dbg(bond_dev, slave_dev, "change device type from %d to %d\n", bond_dev->type, slave_dev->type); From d4ce79e6dce2a4a49eebceea7b4caf5dc0f0ef3d Mon Sep 17 00:00:00 2001 From: Kuniyuki Iwashima Date: Tue, 13 Jan 2026 18:54:44 +0000 Subject: [PATCH 2652/3902] l2tp: Fix memleak in l2tp_udp_encap_recv(). [ Upstream commit 4d10edfd1475b69dbd4c47f34b61a3772ece83ca ] syzbot reported memleak of struct l2tp_session, l2tp_tunnel, sock, etc. [0] The cited commit moved down the validation of the protocol version in l2tp_udp_encap_recv(). The new place requires an extra error handling to avoid the memleak. Let's call l2tp_session_put() there. [0]: BUG: memory leak unreferenced object 0xffff88810a290200 (size 512): comm "syz.0.17", pid 6086, jiffies 4294944299 hex dump (first 32 bytes): 7d eb 04 0c 00 00 00 00 01 00 00 00 00 00 00 00 }............... 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ backtrace (crc babb6a4f): kmemleak_alloc_recursive include/linux/kmemleak.h:44 [inline] slab_post_alloc_hook mm/slub.c:4958 [inline] slab_alloc_node mm/slub.c:5263 [inline] __do_kmalloc_node mm/slub.c:5656 [inline] __kmalloc_noprof+0x3e0/0x660 mm/slub.c:5669 kmalloc_noprof include/linux/slab.h:961 [inline] kzalloc_noprof include/linux/slab.h:1094 [inline] l2tp_session_create+0x3a/0x3b0 net/l2tp/l2tp_core.c:1778 pppol2tp_connect+0x48b/0x920 net/l2tp/l2tp_ppp.c:755 __sys_connect_file+0x7a/0xb0 net/socket.c:2089 __sys_connect+0xde/0x110 net/socket.c:2108 __do_sys_connect net/socket.c:2114 [inline] __se_sys_connect net/socket.c:2111 [inline] __x64_sys_connect+0x1c/0x30 net/socket.c:2111 do_syscall_x64 arch/x86/entry/syscall_64.c:63 [inline] do_syscall_64+0xa4/0xf80 arch/x86/entry/syscall_64.c:94 entry_SYSCALL_64_after_hwframe+0x77/0x7f Fixes: 364798056f518 ("l2tp: Support different protocol versions with same IP/port quadruple") Reported-by: syzbot+2c42ea4485b29beb0643@syzkaller.appspotmail.com Closes: https://lore.kernel.org/netdev/696693f2.a70a0220.245e30.0001.GAE@google.com/ Signed-off-by: Kuniyuki Iwashima Reviewed-by: Guillaume Nault Link: https://patch.msgid.link/20260113185446.2533333-1-kuniyu@google.com Signed-off-by: Jakub Kicinski Signed-off-by: Sasha Levin --- net/l2tp/l2tp_core.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/net/l2tp/l2tp_core.c b/net/l2tp/l2tp_core.c index 0710281dd95aac..d6f4bef0236dc8 100644 --- a/net/l2tp/l2tp_core.c +++ b/net/l2tp/l2tp_core.c @@ -1086,8 +1086,10 @@ int l2tp_udp_encap_recv(struct sock *sk, struct sk_buff *skb) tunnel = session->tunnel; /* Check protocol version */ - if (version != tunnel->version) + if (version != tunnel->version) { + l2tp_session_put(session); goto invalid; + } if (version == L2TP_HDR_VER_3 && l2tp_v3_ensure_opt_in_linear(session, skb, &ptr, &optr)) { From 5ebc24f9dbe4f0f96fa1887174f51925d2bdcce0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ricardo=20B=2E=20Marli=C3=A8re?= Date: Tue, 13 Jan 2026 12:37:44 -0300 Subject: [PATCH 2653/3902] selftests: net: fib-onlink-tests: Convert to use namespaces by default MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [ Upstream commit 4f5f148dd7c0459229d2ab9a769b2e820f9ee6a2 ] Currently, the test breaks if the SUT already has a default route configured for IPv6. Fix by avoiding the use of the default namespace. Fixes: 4ed591c8ab44 ("net/ipv6: Allow onlink routes to have a device mismatch if it is the default route") Suggested-by: Fernando Fernandez Mancera Signed-off-by: Ricardo B. Marlière Reviewed-by: Ido Schimmel Reviewed-by: Fernando Fernandez Mancera Link: https://patch.msgid.link/20260113-selftests-net-fib-onlink-v2-1-89de2b931389@suse.com Signed-off-by: Jakub Kicinski Signed-off-by: Sasha Levin --- .../testing/selftests/net/fib-onlink-tests.sh | 71 ++++++++----------- 1 file changed, 30 insertions(+), 41 deletions(-) diff --git a/tools/testing/selftests/net/fib-onlink-tests.sh b/tools/testing/selftests/net/fib-onlink-tests.sh index ec2d6ceb1f08d3..c01be076b210dd 100755 --- a/tools/testing/selftests/net/fib-onlink-tests.sh +++ b/tools/testing/selftests/net/fib-onlink-tests.sh @@ -120,7 +120,7 @@ log_subsection() run_cmd() { - local cmd="$*" + local cmd="$1" local out local rc @@ -145,7 +145,7 @@ get_linklocal() local pfx local addr - addr=$(${pfx} ip -6 -br addr show dev ${dev} | \ + addr=$(${pfx} ${IP} -6 -br addr show dev ${dev} | \ awk '{ for (i = 3; i <= NF; ++i) { if ($i ~ /^fe80/) @@ -173,58 +173,48 @@ setup() set -e - # create namespace - setup_ns PEER_NS + # create namespaces + setup_ns ns1 + IP="ip -netns $ns1" + setup_ns ns2 # add vrf table - ip li add ${VRF} type vrf table ${VRF_TABLE} - ip li set ${VRF} up - ip ro add table ${VRF_TABLE} unreachable default metric 8192 - ip -6 ro add table ${VRF_TABLE} unreachable default metric 8192 + ${IP} li add ${VRF} type vrf table ${VRF_TABLE} + ${IP} li set ${VRF} up + ${IP} ro add table ${VRF_TABLE} unreachable default metric 8192 + ${IP} -6 ro add table ${VRF_TABLE} unreachable default metric 8192 # create test interfaces - ip li add ${NETIFS[p1]} type veth peer name ${NETIFS[p2]} - ip li add ${NETIFS[p3]} type veth peer name ${NETIFS[p4]} - ip li add ${NETIFS[p5]} type veth peer name ${NETIFS[p6]} - ip li add ${NETIFS[p7]} type veth peer name ${NETIFS[p8]} + ${IP} li add ${NETIFS[p1]} type veth peer name ${NETIFS[p2]} + ${IP} li add ${NETIFS[p3]} type veth peer name ${NETIFS[p4]} + ${IP} li add ${NETIFS[p5]} type veth peer name ${NETIFS[p6]} + ${IP} li add ${NETIFS[p7]} type veth peer name ${NETIFS[p8]} # enslave vrf interfaces for n in 5 7; do - ip li set ${NETIFS[p${n}]} vrf ${VRF} + ${IP} li set ${NETIFS[p${n}]} vrf ${VRF} done # add addresses for n in 1 3 5 7; do - ip li set ${NETIFS[p${n}]} up - ip addr add ${V4ADDRS[p${n}]}/24 dev ${NETIFS[p${n}]} - ip addr add ${V6ADDRS[p${n}]}/64 dev ${NETIFS[p${n}]} nodad + ${IP} li set ${NETIFS[p${n}]} up + ${IP} addr add ${V4ADDRS[p${n}]}/24 dev ${NETIFS[p${n}]} + ${IP} addr add ${V6ADDRS[p${n}]}/64 dev ${NETIFS[p${n}]} nodad done # move peer interfaces to namespace and add addresses for n in 2 4 6 8; do - ip li set ${NETIFS[p${n}]} netns ${PEER_NS} up - ip -netns ${PEER_NS} addr add ${V4ADDRS[p${n}]}/24 dev ${NETIFS[p${n}]} - ip -netns ${PEER_NS} addr add ${V6ADDRS[p${n}]}/64 dev ${NETIFS[p${n}]} nodad + ${IP} li set ${NETIFS[p${n}]} netns ${ns2} up + ip -netns $ns2 addr add ${V4ADDRS[p${n}]}/24 dev ${NETIFS[p${n}]} + ip -netns $ns2 addr add ${V6ADDRS[p${n}]}/64 dev ${NETIFS[p${n}]} nodad done - ip -6 ro add default via ${V6ADDRS[p3]/::[0-9]/::64} - ip -6 ro add table ${VRF_TABLE} default via ${V6ADDRS[p7]/::[0-9]/::64} + ${IP} -6 ro add default via ${V6ADDRS[p3]/::[0-9]/::64} + ${IP} -6 ro add table ${VRF_TABLE} default via ${V6ADDRS[p7]/::[0-9]/::64} set +e } -cleanup() -{ - # make sure we start from a clean slate - cleanup_ns ${PEER_NS} 2>/dev/null - for n in 1 3 5 7; do - ip link del ${NETIFS[p${n}]} 2>/dev/null - done - ip link del ${VRF} 2>/dev/null - ip ro flush table ${VRF_TABLE} - ip -6 ro flush table ${VRF_TABLE} -} - ################################################################################ # IPv4 tests # @@ -241,7 +231,7 @@ run_ip() # dev arg may be empty [ -n "${dev}" ] && dev="dev ${dev}" - run_cmd ip ro add table "${table}" "${prefix}"/32 via "${gw}" "${dev}" onlink + run_cmd "${IP} ro add table ${table} ${prefix}/32 via ${gw} ${dev} onlink" log_test $? ${exp_rc} "${desc}" } @@ -257,8 +247,8 @@ run_ip_mpath() # dev arg may be empty [ -n "${dev}" ] && dev="dev ${dev}" - run_cmd ip ro add table "${table}" "${prefix}"/32 \ - nexthop via ${nh1} nexthop via ${nh2} + run_cmd "${IP} ro add table ${table} ${prefix}/32 \ + nexthop via ${nh1} nexthop via ${nh2}" log_test $? ${exp_rc} "${desc}" } @@ -339,7 +329,7 @@ run_ip6() # dev arg may be empty [ -n "${dev}" ] && dev="dev ${dev}" - run_cmd ip -6 ro add table "${table}" "${prefix}"/128 via "${gw}" "${dev}" onlink + run_cmd "${IP} -6 ro add table ${table} ${prefix}/128 via ${gw} ${dev} onlink" log_test $? ${exp_rc} "${desc}" } @@ -353,8 +343,8 @@ run_ip6_mpath() local exp_rc="$6" local desc="$7" - run_cmd ip -6 ro add table "${table}" "${prefix}"/128 "${opts}" \ - nexthop via ${nh1} nexthop via ${nh2} + run_cmd "${IP} -6 ro add table ${table} ${prefix}/128 ${opts} \ + nexthop via ${nh1} nexthop via ${nh2}" log_test $? ${exp_rc} "${desc}" } @@ -491,10 +481,9 @@ do esac done -cleanup setup run_onlink_tests -cleanup +cleanup_ns ${ns1} ${ns2} if [ "$TESTS" != "none" ]; then printf "\nTests passed: %3d\n" ${nsuccess} From 4f727c422b7fca140852bb914581e25444482287 Mon Sep 17 00:00:00 2001 From: Maxime Chevallier Date: Wed, 14 Jan 2026 09:02:46 +0100 Subject: [PATCH 2654/3902] net: freescale: ucc_geth: Return early when TBI PHY can't be found [ Upstream commit a74c7a58ca2ca1cbb93f4c01421cf24b8642b962 ] In ucc_geth's .mac_config(), we configure the TBI Serdes block represented by a struct phy_device that we get from firmware. While porting to phylink, a check was missed to make sure we don't try to access the TBI PHY if we can't get it. Let's add it and return early in case of error Reported-by: kernel test robot Reported-by: Dan Carpenter Closes: https://lore.kernel.org/r/202601130843.rFGNXA5a-lkp@intel.com/ Fixes: 53036aa8d031 ("net: freescale: ucc_geth: phylink conversion") Signed-off-by: Maxime Chevallier Link: https://patch.msgid.link/20260114080247.366252-1-maxime.chevallier@bootlin.com Signed-off-by: Jakub Kicinski Signed-off-by: Sasha Levin --- drivers/net/ethernet/freescale/ucc_geth.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/drivers/net/ethernet/freescale/ucc_geth.c b/drivers/net/ethernet/freescale/ucc_geth.c index affd5a6c44e7b3..131d1210dc4a84 100644 --- a/drivers/net/ethernet/freescale/ucc_geth.c +++ b/drivers/net/ethernet/freescale/ucc_geth.c @@ -1602,8 +1602,10 @@ static void ugeth_mac_config(struct phylink_config *config, unsigned int mode, pr_warn("TBI mode requires that the device tree specify a tbi-handle\n"); tbiphy = of_phy_find_device(ug_info->tbi_node); - if (!tbiphy) + if (!tbiphy) { pr_warn("Could not get TBI device\n"); + return; + } value = phy_read(tbiphy, ENET_TBI_MII_CR); value &= ~0x1000; /* Turn off autonegotiation */ From c3edc14da81a8d8398682f6e4ab819f09f37c0b7 Mon Sep 17 00:00:00 2001 From: Marc Kleine-Budde Date: Fri, 16 Jan 2026 14:10:10 +0100 Subject: [PATCH 2655/3902] can: gs_usb: gs_usb_receive_bulk_callback(): unanchor URL on usb_submit_urb() error [ Upstream commit 79a6d1bfe1148bc921b8d7f3371a7fbce44e30f7 ] In commit 7352e1d5932a ("can: gs_usb: gs_usb_receive_bulk_callback(): fix URB memory leak"), the URB was re-anchored before usb_submit_urb() in gs_usb_receive_bulk_callback() to prevent a leak of this URB during cleanup. However, this patch did not take into account that usb_submit_urb() could fail. The URB remains anchored and usb_kill_anchored_urbs(&parent->rx_submitted) in gs_can_close() loops infinitely since the anchor list never becomes empty. To fix the bug, unanchor the URB when an usb_submit_urb() error occurs, also print an info message. Fixes: 7352e1d5932a ("can: gs_usb: gs_usb_receive_bulk_callback(): fix URB memory leak") Reported-by: Jakub Kicinski Closes: https://lore.kernel.org/all/20260110223836.3890248-1-kuba@kernel.org/ Link: https://patch.msgid.link/20260116-can_usb-fix-reanchor-v1-1-9d74e7289225@pengutronix.de Signed-off-by: Marc Kleine-Budde Signed-off-by: Sasha Levin --- drivers/net/can/usb/gs_usb.c | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/drivers/net/can/usb/gs_usb.c b/drivers/net/can/usb/gs_usb.c index b14b132ad8e6ac..fd7fb21b109893 100644 --- a/drivers/net/can/usb/gs_usb.c +++ b/drivers/net/can/usb/gs_usb.c @@ -754,6 +754,10 @@ static void gs_usb_receive_bulk_callback(struct urb *urb) usb_anchor_urb(urb, &parent->rx_submitted); rc = usb_submit_urb(urb, GFP_ATOMIC); + if (!rc) + return; + + usb_unanchor_urb(urb); /* USB failure take down all interfaces */ if (rc == -ENODEV) { @@ -762,6 +766,9 @@ static void gs_usb_receive_bulk_callback(struct urb *urb) if (parent->canch[rc]) netif_device_detach(parent->canch[rc]->netdev); } + } else if (rc != -ESHUTDOWN && net_ratelimit()) { + netdev_info(netdev, "failed to re-submit IN URB: %pe\n", + ERR_PTR(urb->status)); } } From 0c4adb1f391a7b92a0405e9d7c05624c0d9f8a65 Mon Sep 17 00:00:00 2001 From: Xin Long Date: Tue, 13 Jan 2026 12:10:26 -0500 Subject: [PATCH 2656/3902] sctp: move SCTP_CMD_ASSOC_SHKEY right after SCTP_CMD_PEER_INIT [ Upstream commit a80c9d945aef55b23b54838334345f20251dad83 ] A null-ptr-deref was reported in the SCTP transmit path when SCTP-AUTH key initialization fails: ================================================================== KASAN: null-ptr-deref in range [0x0000000000000018-0x000000000000001f] CPU: 0 PID: 16 Comm: ksoftirqd/0 Tainted: G W 6.6.0 #2 RIP: 0010:sctp_packet_bundle_auth net/sctp/output.c:264 [inline] RIP: 0010:sctp_packet_append_chunk+0xb36/0x1260 net/sctp/output.c:401 Call Trace: sctp_packet_transmit_chunk+0x31/0x250 net/sctp/output.c:189 sctp_outq_flush_data+0xa29/0x26d0 net/sctp/outqueue.c:1111 sctp_outq_flush+0xc80/0x1240 net/sctp/outqueue.c:1217 sctp_cmd_interpreter.isra.0+0x19a5/0x62c0 net/sctp/sm_sideeffect.c:1787 sctp_side_effects net/sctp/sm_sideeffect.c:1198 [inline] sctp_do_sm+0x1a3/0x670 net/sctp/sm_sideeffect.c:1169 sctp_assoc_bh_rcv+0x33e/0x640 net/sctp/associola.c:1052 sctp_inq_push+0x1dd/0x280 net/sctp/inqueue.c:88 sctp_rcv+0x11ae/0x3100 net/sctp/input.c:243 sctp6_rcv+0x3d/0x60 net/sctp/ipv6.c:1127 The issue is triggered when sctp_auth_asoc_init_active_key() fails in sctp_sf_do_5_1C_ack() while processing an INIT_ACK. In this case, the command sequence is currently: - SCTP_CMD_PEER_INIT - SCTP_CMD_TIMER_STOP (T1_INIT) - SCTP_CMD_TIMER_START (T1_COOKIE) - SCTP_CMD_NEW_STATE (COOKIE_ECHOED) - SCTP_CMD_ASSOC_SHKEY - SCTP_CMD_GEN_COOKIE_ECHO If SCTP_CMD_ASSOC_SHKEY fails, asoc->shkey remains NULL, while asoc->peer.auth_capable and asoc->peer.peer_chunks have already been set by SCTP_CMD_PEER_INIT. This allows a DATA chunk with auth = 1 and shkey = NULL to be queued by sctp_datamsg_from_user(). Since command interpretation stops on failure, no COOKIE_ECHO should been sent via SCTP_CMD_GEN_COOKIE_ECHO. However, the T1_COOKIE timer has already been started, and it may enqueue a COOKIE_ECHO into the outqueue later. As a result, the DATA chunk can be transmitted together with the COOKIE_ECHO in sctp_outq_flush_data(), leading to the observed issue. Similar to the other places where it calls sctp_auth_asoc_init_active_key() right after sctp_process_init(), this patch moves the SCTP_CMD_ASSOC_SHKEY immediately after SCTP_CMD_PEER_INIT, before stopping T1_INIT and starting T1_COOKIE. This ensures that if shared key generation fails, authenticated DATA cannot be sent. It also allows the T1_INIT timer to retransmit INIT, giving the client another chance to process INIT_ACK and retry key setup. Fixes: 730fc3d05cd4 ("[SCTP]: Implete SCTP-AUTH parameter processing") Reported-by: Zhen Chen Tested-by: Zhen Chen Signed-off-by: Xin Long Link: https://patch.msgid.link/44881224b375aa8853f5e19b4055a1a56d895813.1768324226.git.lucien.xin@gmail.com Signed-off-by: Jakub Kicinski Signed-off-by: Sasha Levin --- net/sctp/sm_statefuns.c | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/net/sctp/sm_statefuns.c b/net/sctp/sm_statefuns.c index 3755ba079d077c..7b823d75914191 100644 --- a/net/sctp/sm_statefuns.c +++ b/net/sctp/sm_statefuns.c @@ -603,6 +603,11 @@ enum sctp_disposition sctp_sf_do_5_1C_ack(struct net *net, sctp_add_cmd_sf(commands, SCTP_CMD_PEER_INIT, SCTP_PEER_INIT(initchunk)); + /* SCTP-AUTH: generate the association shared keys so that + * we can potentially sign the COOKIE-ECHO. + */ + sctp_add_cmd_sf(commands, SCTP_CMD_ASSOC_SHKEY, SCTP_NULL()); + /* Reset init error count upon receipt of INIT-ACK. */ sctp_add_cmd_sf(commands, SCTP_CMD_INIT_COUNTER_RESET, SCTP_NULL()); @@ -617,11 +622,6 @@ enum sctp_disposition sctp_sf_do_5_1C_ack(struct net *net, sctp_add_cmd_sf(commands, SCTP_CMD_NEW_STATE, SCTP_STATE(SCTP_STATE_COOKIE_ECHOED)); - /* SCTP-AUTH: generate the association shared keys so that - * we can potentially sign the COOKIE-ECHO. - */ - sctp_add_cmd_sf(commands, SCTP_CMD_ASSOC_SHKEY, SCTP_NULL()); - /* 5.1 C) "A" shall then send the State Cookie received in the * INIT ACK chunk in a COOKIE ECHO chunk, ... */ From 392b28d404b742baad460adab8c75bddf3ec0481 Mon Sep 17 00:00:00 2001 From: Raju Rangoju Date: Wed, 14 Jan 2026 22:00:37 +0530 Subject: [PATCH 2657/3902] amd-xgbe: avoid misleading per-packet error log [ Upstream commit c158f985cf6c2c36c99c4f67af2ff3f5ebe09f8f ] On the receive path, packet can be damaged because of buffer overflow in Rx FIFO. Avoid misleading per-packet error log when packet->errors is set, this can flood the log. Instead, rely on the standard rtnl_link_stats64 stats. Fixes: c5aa9e3b8156 ("amd-xgbe: Initial AMD 10GbE platform driver") Signed-off-by: Raju Rangoju Link: https://patch.msgid.link/20260114163037.2062606-1-Raju.Rangoju@amd.com Signed-off-by: Jakub Kicinski Signed-off-by: Sasha Levin --- drivers/net/ethernet/amd/xgbe/xgbe-drv.c | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/drivers/net/ethernet/amd/xgbe/xgbe-drv.c b/drivers/net/ethernet/amd/xgbe/xgbe-drv.c index 4dc631af793324..ba5e728ae63089 100644 --- a/drivers/net/ethernet/amd/xgbe/xgbe-drv.c +++ b/drivers/net/ethernet/amd/xgbe/xgbe-drv.c @@ -1823,7 +1823,7 @@ static void xgbe_get_stats64(struct net_device *netdev, s->multicast = pstats->rxmulticastframes_g; s->rx_length_errors = pstats->rxlengtherror; s->rx_crc_errors = pstats->rxcrcerror; - s->rx_fifo_errors = pstats->rxfifooverflow; + s->rx_over_errors = pstats->rxfifooverflow; s->tx_packets = pstats->txframecount_gb; s->tx_bytes = pstats->txoctetcount_gb; @@ -2277,9 +2277,6 @@ static int xgbe_rx_poll(struct xgbe_channel *channel, int budget) goto read_again; if (error || packet->errors) { - if (packet->errors) - netif_err(pdata, rx_err, netdev, - "error in received packet\n"); dev_kfree_skb(skb); goto next_packet; } From 5437a279804ced8088cabb945dba88a26d828f8c Mon Sep 17 00:00:00 2001 From: Kuniyuki Iwashima Date: Thu, 15 Jan 2026 17:24:46 +0000 Subject: [PATCH 2658/3902] gue: Fix skb memleak with inner IP protocol 0. [ Upstream commit 9a56796ad258786d3624eef5aefba394fc9bdded ] syzbot reported skb memleak below. [0] The repro generated a GUE packet with its inner protocol 0. gue_udp_recv() returns -guehdr->proto_ctype for "resubmit" in ip_protocol_deliver_rcu(), but this only works with non-zero protocol number. Let's drop such packets. Note that 0 is a valid number (IPv6 Hop-by-Hop Option). I think it is not practical to encap HOPOPT in GUE, so once someone starts to complain, we could pass down a resubmit flag pointer to distinguish two zeros from the upper layer: * no error * resubmit HOPOPT [0] BUG: memory leak unreferenced object 0xffff888109695a00 (size 240): comm "syz.0.17", pid 6088, jiffies 4294943096 hex dump (first 32 bytes): 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 00 40 c2 10 81 88 ff ff 00 00 00 00 00 00 00 00 .@.............. backtrace (crc a84b336f): kmemleak_alloc_recursive include/linux/kmemleak.h:44 [inline] slab_post_alloc_hook mm/slub.c:4958 [inline] slab_alloc_node mm/slub.c:5263 [inline] kmem_cache_alloc_noprof+0x3b4/0x590 mm/slub.c:5270 __build_skb+0x23/0x60 net/core/skbuff.c:474 build_skb+0x20/0x190 net/core/skbuff.c:490 __tun_build_skb drivers/net/tun.c:1541 [inline] tun_build_skb+0x4a1/0xa40 drivers/net/tun.c:1636 tun_get_user+0xc12/0x2030 drivers/net/tun.c:1770 tun_chr_write_iter+0x71/0x120 drivers/net/tun.c:1999 new_sync_write fs/read_write.c:593 [inline] vfs_write+0x45d/0x710 fs/read_write.c:686 ksys_write+0xa7/0x170 fs/read_write.c:738 do_syscall_x64 arch/x86/entry/syscall_64.c:63 [inline] do_syscall_64+0xa4/0xf80 arch/x86/entry/syscall_64.c:94 entry_SYSCALL_64_after_hwframe+0x77/0x7f Fixes: 37dd0247797b1 ("gue: Receive side for Generic UDP Encapsulation") Reported-by: syzbot+4d8c7d16b0e95c0d0f0d@syzkaller.appspotmail.com Closes: https://lore.kernel.org/netdev/6965534b.050a0220.38aacd.0001.GAE@google.com/ Signed-off-by: Kuniyuki Iwashima Reviewed-by: Eric Dumazet Link: https://patch.msgid.link/20260115172533.693652-2-kuniyu@google.com Signed-off-by: Jakub Kicinski Signed-off-by: Sasha Levin --- net/ipv4/fou_core.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/net/ipv4/fou_core.c b/net/ipv4/fou_core.c index 3970b6b7ace537..ab8f309f8925d6 100644 --- a/net/ipv4/fou_core.c +++ b/net/ipv4/fou_core.c @@ -215,6 +215,9 @@ static int gue_udp_recv(struct sock *sk, struct sk_buff *skb) return gue_control_message(skb, guehdr); proto_ctype = guehdr->proto_ctype; + if (unlikely(!proto_ctype)) + goto drop; + __skb_pull(skb, sizeof(struct udphdr) + hdrlen); skb_reset_transport_header(skb); From 18da5acb3c03d30263022095bf771d3c824ab67d Mon Sep 17 00:00:00 2001 From: Kuniyuki Iwashima Date: Thu, 15 Jan 2026 17:24:47 +0000 Subject: [PATCH 2659/3902] tools: ynl: Specify --no-line-number in ynl-regen.sh. [ Upstream commit 68578370f9b3a2aba5964b273312d51c581b6aad ] If grep.lineNumber is enabled in .gitconfig, [grep] lineNumber = true ynl-regen.sh fails with the following error: $ ./tools/net/ynl/ynl-regen.sh -f ... ynl_gen_c.py: error: argument --mode: invalid choice: '4:' (choose from user, kernel, uapi) GEN 4: net/ipv4/fou_nl.c Let's specify --no-line-number explicitly. Fixes: be5bea1cc0bf ("net: add basic C code generators for Netlink") Suggested-by: Jakub Kicinski Signed-off-by: Kuniyuki Iwashima Reviewed-by: Eric Dumazet Link: https://patch.msgid.link/20260115172533.693652-3-kuniyu@google.com Signed-off-by: Jakub Kicinski Signed-off-by: Sasha Levin --- tools/net/ynl/ynl-regen.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/net/ynl/ynl-regen.sh b/tools/net/ynl/ynl-regen.sh index 81b4ecd8910068..d9809276db9826 100755 --- a/tools/net/ynl/ynl-regen.sh +++ b/tools/net/ynl/ynl-regen.sh @@ -21,7 +21,7 @@ files=$(git grep --files-with-matches '^/\* YNL-GEN \(kernel\|uapi\|user\)') for f in $files; do # params: 0 1 2 3 # $YAML YNL-GEN kernel $mode - params=( $(git grep -B1 -h '/\* YNL-GEN' $f | sed 's@/\*\(.*\)\*/@\1@') ) + params=( $(git grep --no-line-number -B1 -h '/\* YNL-GEN' $f | sed 's@/\*\(.*\)\*/@\1@') ) args=$(sed -n 's@/\* YNL-ARG \(.*\) \*/@\1@p' $f) if [ $f -nt ${params[0]} -a -z "$force" ]; then From 9b75dff8446ec871030d8daf5a69e74f5fe8b956 Mon Sep 17 00:00:00 2001 From: Kuniyuki Iwashima Date: Thu, 15 Jan 2026 17:24:48 +0000 Subject: [PATCH 2660/3902] fou: Don't allow 0 for FOU_ATTR_IPPROTO. [ Upstream commit 7a9bc9e3f42391e4c187e099263cf7a1c4b69ff5 ] fou_udp_recv() has the same problem mentioned in the previous patch. If FOU_ATTR_IPPROTO is set to 0, skb is not freed by fou_udp_recv() nor "resubmit"-ted in ip_protocol_deliver_rcu(). Let's forbid 0 for FOU_ATTR_IPPROTO. Fixes: 23461551c0062 ("fou: Support for foo-over-udp RX path") Signed-off-by: Kuniyuki Iwashima Reviewed-by: Eric Dumazet Link: https://patch.msgid.link/20260115172533.693652-4-kuniyu@google.com Signed-off-by: Jakub Kicinski Signed-off-by: Sasha Levin --- Documentation/netlink/specs/fou.yaml | 2 ++ net/ipv4/fou_nl.c | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/Documentation/netlink/specs/fou.yaml b/Documentation/netlink/specs/fou.yaml index 8e7974ec453fca..331f1b342b3adb 100644 --- a/Documentation/netlink/specs/fou.yaml +++ b/Documentation/netlink/specs/fou.yaml @@ -39,6 +39,8 @@ attribute-sets: - name: ipproto type: u8 + checks: + min: 1 - name: type type: u8 diff --git a/net/ipv4/fou_nl.c b/net/ipv4/fou_nl.c index 506260b4a4dc26..9ff7797ef7c4f9 100644 --- a/net/ipv4/fou_nl.c +++ b/net/ipv4/fou_nl.c @@ -14,7 +14,7 @@ const struct nla_policy fou_nl_policy[FOU_ATTR_IFINDEX + 1] = { [FOU_ATTR_PORT] = { .type = NLA_BE16, }, [FOU_ATTR_AF] = { .type = NLA_U8, }, - [FOU_ATTR_IPPROTO] = { .type = NLA_U8, }, + [FOU_ATTR_IPPROTO] = NLA_POLICY_MIN(NLA_U8, 1), [FOU_ATTR_TYPE] = { .type = NLA_U8, }, [FOU_ATTR_REMCSUM_NOPARTIAL] = { .type = NLA_FLAG, }, [FOU_ATTR_LOCAL_V4] = { .type = NLA_U32, }, From 0f6e1ff56cd27d3d669673155366e63f05ffed60 Mon Sep 17 00:00:00 2001 From: David Yang Date: Wed, 14 Jan 2026 20:24:45 +0800 Subject: [PATCH 2661/3902] veth: fix data race in veth_get_ethtool_stats [ Upstream commit b47adaab8b3d443868096bac08fdbb3d403194ba ] In veth_get_ethtool_stats(), some statistics protected by u64_stats_sync, are read and accumulated in ignorance of possible u64_stats_fetch_retry() events. These statistics, peer_tq_xdp_xmit and peer_tq_xdp_xmit_err, are already accumulated by veth_xdp_xmit(). Fix this by reading them into a temporary buffer first. Fixes: 5fe6e56776ba ("veth: rely on peer veth_rq for ndo_xdp_xmit accounting") Signed-off-by: David Yang Link: https://patch.msgid.link/20260114122450.227982-1-mmyangfl@gmail.com Signed-off-by: Jakub Kicinski Signed-off-by: Sasha Levin --- drivers/net/veth.c | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/drivers/net/veth.c b/drivers/net/veth.c index cc502bf022d55c..b00613cb07cf07 100644 --- a/drivers/net/veth.c +++ b/drivers/net/veth.c @@ -228,16 +228,20 @@ static void veth_get_ethtool_stats(struct net_device *dev, const struct veth_rq_stats *rq_stats = &rcv_priv->rq[i].stats; const void *base = (void *)&rq_stats->vs; unsigned int start, tx_idx = idx; + u64 buf[VETH_TQ_STATS_LEN]; size_t offset; - tx_idx += (i % dev->real_num_tx_queues) * VETH_TQ_STATS_LEN; do { start = u64_stats_fetch_begin(&rq_stats->syncp); for (j = 0; j < VETH_TQ_STATS_LEN; j++) { offset = veth_tq_stats_desc[j].offset; - data[tx_idx + j] += *(u64 *)(base + offset); + buf[j] = *(u64 *)(base + offset); } } while (u64_stats_fetch_retry(&rq_stats->syncp, start)); + + tx_idx += (i % dev->real_num_tx_queues) * VETH_TQ_STATS_LEN; + for (j = 0; j < VETH_TQ_STATS_LEN; j++) + data[tx_idx + j] += buf[j]; } pp_idx = idx + dev->real_num_tx_queues * VETH_TQ_STATS_LEN; From 4230e8cd2f1b49e1021c463bbdc06fb10b22f630 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Uwe=20Kleine-K=C3=B6nig?= Date: Mon, 19 Jan 2026 16:13:26 +0100 Subject: [PATCH 2662/3902] pwm: Ensure ioctl() returns a negative errno on error MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [ Upstream commit c198b7773ca5bc3bdfb15b85e414fb9a99a5e5ba ] copy_to_user() returns the number of bytes not copied, thus if there is a problem a positive number. However the ioctl callback is supposed to return a negative error code on error. This error is a unfortunate as strictly speaking it became ABI with the introduction of pwm character devices. However I never saw the issue in real life -- I found this by code inspection -- and it only affects an error case where readonly memory is passed to the ioctls or the address mapping changes while the ioctl is active. Also there are already error cases returning negative values, so the calling code must be prepared to see such values already. Fixes: 9c06f26ba5f5 ("pwm: Add support for pwmchip devices for faster and easier userspace access") Signed-off-by: Uwe Kleine-König Link: https://patch.msgid.link/20260119151325.571857-2-u.kleine-koenig@baylibre.com Signed-off-by: Uwe Kleine-König Signed-off-by: Sasha Levin --- drivers/pwm/core.c | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/drivers/pwm/core.c b/drivers/pwm/core.c index 7dd1cf2ba40252..462c91a034c8e6 100644 --- a/drivers/pwm/core.c +++ b/drivers/pwm/core.c @@ -2294,8 +2294,9 @@ static long pwm_cdev_ioctl(struct file *file, unsigned int cmd, unsigned long ar .duty_offset_ns = wf.duty_offset_ns, }; - return copy_to_user((struct pwmchip_waveform __user *)arg, - &cwf, sizeof(cwf)); + ret = copy_to_user((struct pwmchip_waveform __user *)arg, + &cwf, sizeof(cwf)); + return ret ? -EFAULT : 0; } case PWM_IOCTL_GETWF: @@ -2328,8 +2329,9 @@ static long pwm_cdev_ioctl(struct file *file, unsigned int cmd, unsigned long ar .duty_offset_ns = wf.duty_offset_ns, }; - return copy_to_user((struct pwmchip_waveform __user *)arg, - &cwf, sizeof(cwf)); + ret = copy_to_user((struct pwmchip_waveform __user *)arg, + &cwf, sizeof(cwf)); + return ret ? -EFAULT : 0; } case PWM_IOCTL_SETROUNDEDWF: From a635ae2a9fdb84f8b08757ca6a73fdf63247e541 Mon Sep 17 00:00:00 2001 From: Richard Genoud Date: Tue, 13 Jan 2026 17:39:07 +0100 Subject: [PATCH 2663/3902] pwm: max7360: Populate missing .sizeof_wfhw in max7360_pwm_ops MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [ Upstream commit 63faf32666e03a78cc985bcbae196418cf7d7938 ] The sizeof_wfhw field wasn't populated in max7360_pwm_ops so it was set to 0 by default. While this is ok for now because: sizeof(struct max7360_pwm_waveform) < PWM_WFHWSIZE in the future, if struct max7360_pwm_waveform grows, it could lead to stack corruption. Fixes: d93a75d94b79 ("pwm: max7360: Add MAX7360 PWM support") Signed-off-by: Richard Genoud Link: https://patch.msgid.link/20260113163907.368919-1-richard.genoud@bootlin.com Signed-off-by: Uwe Kleine-König Signed-off-by: Sasha Levin --- drivers/pwm/pwm-max7360.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/pwm/pwm-max7360.c b/drivers/pwm/pwm-max7360.c index ebf93a7aee5be4..31972bd00ebe95 100644 --- a/drivers/pwm/pwm-max7360.c +++ b/drivers/pwm/pwm-max7360.c @@ -153,6 +153,7 @@ static int max7360_pwm_read_waveform(struct pwm_chip *chip, } static const struct pwm_ops max7360_pwm_ops = { + .sizeof_wfhw = sizeof(struct max7360_pwm_waveform), .request = max7360_pwm_request, .round_waveform_tohw = max7360_pwm_round_waveform_tohw, .round_waveform_fromhw = max7360_pwm_round_waveform_fromhw, From 32d417497b79efb403d75f4c185fe6fd9d64b94f Mon Sep 17 00:00:00 2001 From: Eric Dumazet Date: Thu, 15 Jan 2026 09:21:39 +0000 Subject: [PATCH 2664/3902] l2tp: avoid one data-race in l2tp_tunnel_del_work() [ Upstream commit 7a29f6bf60f2590fe5e9c4decb451e19afad2bcf ] We should read sk->sk_socket only when dealing with kernel sockets. syzbot reported the following data-race: BUG: KCSAN: data-race in l2tp_tunnel_del_work / sk_common_release write to 0xffff88811c182b20 of 8 bytes by task 5365 on cpu 0: sk_set_socket include/net/sock.h:2092 [inline] sock_orphan include/net/sock.h:2118 [inline] sk_common_release+0xae/0x230 net/core/sock.c:4003 udp_lib_close+0x15/0x20 include/net/udp.h:325 inet_release+0xce/0xf0 net/ipv4/af_inet.c:437 __sock_release net/socket.c:662 [inline] sock_close+0x6b/0x150 net/socket.c:1455 __fput+0x29b/0x650 fs/file_table.c:468 ____fput+0x1c/0x30 fs/file_table.c:496 task_work_run+0x131/0x1a0 kernel/task_work.c:233 resume_user_mode_work include/linux/resume_user_mode.h:50 [inline] __exit_to_user_mode_loop kernel/entry/common.c:44 [inline] exit_to_user_mode_loop+0x1fe/0x740 kernel/entry/common.c:75 __exit_to_user_mode_prepare include/linux/irq-entry-common.h:226 [inline] syscall_exit_to_user_mode_prepare include/linux/irq-entry-common.h:256 [inline] syscall_exit_to_user_mode_work include/linux/entry-common.h:159 [inline] syscall_exit_to_user_mode include/linux/entry-common.h:194 [inline] do_syscall_64+0x1e1/0x2b0 arch/x86/entry/syscall_64.c:100 entry_SYSCALL_64_after_hwframe+0x77/0x7f read to 0xffff88811c182b20 of 8 bytes by task 827 on cpu 1: l2tp_tunnel_del_work+0x2f/0x1a0 net/l2tp/l2tp_core.c:1418 process_one_work kernel/workqueue.c:3257 [inline] process_scheduled_works+0x4ce/0x9d0 kernel/workqueue.c:3340 worker_thread+0x582/0x770 kernel/workqueue.c:3421 kthread+0x489/0x510 kernel/kthread.c:463 ret_from_fork+0x149/0x290 arch/x86/kernel/process.c:158 ret_from_fork_asm+0x1a/0x30 arch/x86/entry/entry_64.S:246 value changed: 0xffff88811b818000 -> 0x0000000000000000 Fixes: d00fa9adc528 ("l2tp: fix races with tunnel socket close") Reported-by: syzbot+7312e82745f7fa2526db@syzkaller.appspotmail.com Closes: https://lore.kernel.org/netdev/6968b029.050a0220.58bed.0016.GAE@google.com/T/#u Signed-off-by: Eric Dumazet Cc: James Chapman Reviewed-by: Guillaume Nault Link: https://patch.msgid.link/20260115092139.3066180-1-edumazet@google.com Signed-off-by: Jakub Kicinski Signed-off-by: Sasha Levin --- net/l2tp/l2tp_core.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/net/l2tp/l2tp_core.c b/net/l2tp/l2tp_core.c index d6f4bef0236dc8..a0682e63fc6375 100644 --- a/net/l2tp/l2tp_core.c +++ b/net/l2tp/l2tp_core.c @@ -1416,8 +1416,6 @@ static void l2tp_tunnel_del_work(struct work_struct *work) { struct l2tp_tunnel *tunnel = container_of(work, struct l2tp_tunnel, del_work); - struct sock *sk = tunnel->sock; - struct socket *sock = sk->sk_socket; l2tp_tunnel_closeall(tunnel); @@ -1425,6 +1423,8 @@ static void l2tp_tunnel_del_work(struct work_struct *work) * the sk API to release it here. */ if (tunnel->fd < 0) { + struct socket *sock = tunnel->sock->sk_socket; + if (sock) { kernel_sock_shutdown(sock, SHUT_RDWR); sock_release(sock); From 6a81e2db096913d7e43aada1c350c1282e76db39 Mon Sep 17 00:00:00 2001 From: Dmitry Skorodumov Date: Mon, 12 Jan 2026 17:24:06 +0300 Subject: [PATCH 2665/3902] ipvlan: Make the addrs_lock be per port [ Upstream commit d3ba32162488283c0a4c5bedd8817aec91748802 ] Make the addrs_lock be per port, not per ipvlan dev. Initial code seems to be written in the assumption, that any address change must occur under RTNL. But it is not so for the case of IPv6. So 1) Introduce per-port addrs_lock. 2) It was needed to fix places where it was forgotten to take lock (ipvlan_open/ipvlan_close) This appears to be a very minor problem though. Since it's highly unlikely that ipvlan_add_addr() will be called on 2 CPU simultaneously. But nevertheless, this could cause: 1) False-negative of ipvlan_addr_busy(): one interface iterated through all port->ipvlans + ipvlan->addrs under some ipvlan spinlock, and another added IP under its own lock. Though this is only possible for IPv6, since looks like only ipvlan_addr6_event() can be called without rtnl_lock. 2) Race since ipvlan_ht_addr_add(port) is called under different ipvlan->addrs_lock locks This should not affect performance, since add/remove IP is a rare situation and spinlock is not taken on fast paths. Fixes: 8230819494b3 ("ipvlan: use per device spinlock to protect addrs list updates") Signed-off-by: Dmitry Skorodumov Reviewed-by: Paolo Abeni Link: https://patch.msgid.link/20260112142417.4039566-2-skorodumov.dmitry@huawei.com Signed-off-by: Jakub Kicinski Signed-off-by: Sasha Levin --- drivers/net/ipvlan/ipvlan.h | 2 +- drivers/net/ipvlan/ipvlan_core.c | 16 +++++------ drivers/net/ipvlan/ipvlan_main.c | 49 +++++++++++++++++++------------- 3 files changed, 37 insertions(+), 30 deletions(-) diff --git a/drivers/net/ipvlan/ipvlan.h b/drivers/net/ipvlan/ipvlan.h index 50de3ee204dbc9..80f84fc87008b3 100644 --- a/drivers/net/ipvlan/ipvlan.h +++ b/drivers/net/ipvlan/ipvlan.h @@ -69,7 +69,6 @@ struct ipvl_dev { DECLARE_BITMAP(mac_filters, IPVLAN_MAC_FILTER_SIZE); netdev_features_t sfeatures; u32 msg_enable; - spinlock_t addrs_lock; }; struct ipvl_addr { @@ -90,6 +89,7 @@ struct ipvl_port { struct net_device *dev; possible_net_t pnet; struct hlist_head hlhead[IPVLAN_HASH_SIZE]; + spinlock_t addrs_lock; /* guards hash-table and addrs */ struct list_head ipvlans; u16 mode; u16 flags; diff --git a/drivers/net/ipvlan/ipvlan_core.c b/drivers/net/ipvlan/ipvlan_core.c index baf2ef3bcd54b6..6c6677ded82ef8 100644 --- a/drivers/net/ipvlan/ipvlan_core.c +++ b/drivers/net/ipvlan/ipvlan_core.c @@ -107,17 +107,15 @@ void ipvlan_ht_addr_del(struct ipvl_addr *addr) struct ipvl_addr *ipvlan_find_addr(const struct ipvl_dev *ipvlan, const void *iaddr, bool is_v6) { - struct ipvl_addr *addr, *ret = NULL; + struct ipvl_addr *addr; - rcu_read_lock(); - list_for_each_entry_rcu(addr, &ipvlan->addrs, anode) { - if (addr_equal(is_v6, addr, iaddr)) { - ret = addr; - break; - } + assert_spin_locked(&ipvlan->port->addrs_lock); + + list_for_each_entry(addr, &ipvlan->addrs, anode) { + if (addr_equal(is_v6, addr, iaddr)) + return addr; } - rcu_read_unlock(); - return ret; + return NULL; } bool ipvlan_addr_busy(struct ipvl_port *port, void *iaddr, bool is_v6) diff --git a/drivers/net/ipvlan/ipvlan_main.c b/drivers/net/ipvlan/ipvlan_main.c index 660f3db117664e..baccdad695fdad 100644 --- a/drivers/net/ipvlan/ipvlan_main.c +++ b/drivers/net/ipvlan/ipvlan_main.c @@ -75,6 +75,7 @@ static int ipvlan_port_create(struct net_device *dev) for (idx = 0; idx < IPVLAN_HASH_SIZE; idx++) INIT_HLIST_HEAD(&port->hlhead[idx]); + spin_lock_init(&port->addrs_lock); skb_queue_head_init(&port->backlog); INIT_WORK(&port->wq, ipvlan_process_multicast); ida_init(&port->ida); @@ -181,6 +182,7 @@ static void ipvlan_uninit(struct net_device *dev) static int ipvlan_open(struct net_device *dev) { struct ipvl_dev *ipvlan = netdev_priv(dev); + struct ipvl_port *port = ipvlan->port; struct ipvl_addr *addr; if (ipvlan->port->mode == IPVLAN_MODE_L3 || @@ -189,10 +191,10 @@ static int ipvlan_open(struct net_device *dev) else dev->flags &= ~IFF_NOARP; - rcu_read_lock(); - list_for_each_entry_rcu(addr, &ipvlan->addrs, anode) + spin_lock_bh(&port->addrs_lock); + list_for_each_entry(addr, &ipvlan->addrs, anode) ipvlan_ht_addr_add(ipvlan, addr); - rcu_read_unlock(); + spin_unlock_bh(&port->addrs_lock); return 0; } @@ -206,10 +208,10 @@ static int ipvlan_stop(struct net_device *dev) dev_uc_unsync(phy_dev, dev); dev_mc_unsync(phy_dev, dev); - rcu_read_lock(); - list_for_each_entry_rcu(addr, &ipvlan->addrs, anode) + spin_lock_bh(&ipvlan->port->addrs_lock); + list_for_each_entry(addr, &ipvlan->addrs, anode) ipvlan_ht_addr_del(addr); - rcu_read_unlock(); + spin_unlock_bh(&ipvlan->port->addrs_lock); return 0; } @@ -579,7 +581,6 @@ int ipvlan_link_new(struct net_device *dev, struct rtnl_newlink_params *params, if (!tb[IFLA_MTU]) ipvlan_adjust_mtu(ipvlan, phy_dev); INIT_LIST_HEAD(&ipvlan->addrs); - spin_lock_init(&ipvlan->addrs_lock); /* TODO Probably put random address here to be presented to the * world but keep using the physical-dev address for the outgoing @@ -657,13 +658,13 @@ void ipvlan_link_delete(struct net_device *dev, struct list_head *head) struct ipvl_dev *ipvlan = netdev_priv(dev); struct ipvl_addr *addr, *next; - spin_lock_bh(&ipvlan->addrs_lock); + spin_lock_bh(&ipvlan->port->addrs_lock); list_for_each_entry_safe(addr, next, &ipvlan->addrs, anode) { ipvlan_ht_addr_del(addr); list_del_rcu(&addr->anode); kfree_rcu(addr, rcu); } - spin_unlock_bh(&ipvlan->addrs_lock); + spin_unlock_bh(&ipvlan->port->addrs_lock); ida_free(&ipvlan->port->ida, dev->dev_id); list_del_rcu(&ipvlan->pnode); @@ -817,6 +818,8 @@ static int ipvlan_add_addr(struct ipvl_dev *ipvlan, void *iaddr, bool is_v6) { struct ipvl_addr *addr; + assert_spin_locked(&ipvlan->port->addrs_lock); + addr = kzalloc(sizeof(struct ipvl_addr), GFP_ATOMIC); if (!addr) return -ENOMEM; @@ -847,16 +850,16 @@ static void ipvlan_del_addr(struct ipvl_dev *ipvlan, void *iaddr, bool is_v6) { struct ipvl_addr *addr; - spin_lock_bh(&ipvlan->addrs_lock); + spin_lock_bh(&ipvlan->port->addrs_lock); addr = ipvlan_find_addr(ipvlan, iaddr, is_v6); if (!addr) { - spin_unlock_bh(&ipvlan->addrs_lock); + spin_unlock_bh(&ipvlan->port->addrs_lock); return; } ipvlan_ht_addr_del(addr); list_del_rcu(&addr->anode); - spin_unlock_bh(&ipvlan->addrs_lock); + spin_unlock_bh(&ipvlan->port->addrs_lock); kfree_rcu(addr, rcu); } @@ -878,14 +881,14 @@ static int ipvlan_add_addr6(struct ipvl_dev *ipvlan, struct in6_addr *ip6_addr) { int ret = -EINVAL; - spin_lock_bh(&ipvlan->addrs_lock); + spin_lock_bh(&ipvlan->port->addrs_lock); if (ipvlan_addr_busy(ipvlan->port, ip6_addr, true)) netif_err(ipvlan, ifup, ipvlan->dev, "Failed to add IPv6=%pI6c addr for %s intf\n", ip6_addr, ipvlan->dev->name); else ret = ipvlan_add_addr(ipvlan, ip6_addr, true); - spin_unlock_bh(&ipvlan->addrs_lock); + spin_unlock_bh(&ipvlan->port->addrs_lock); return ret; } @@ -924,21 +927,24 @@ static int ipvlan_addr6_validator_event(struct notifier_block *unused, struct in6_validator_info *i6vi = (struct in6_validator_info *)ptr; struct net_device *dev = (struct net_device *)i6vi->i6vi_dev->dev; struct ipvl_dev *ipvlan = netdev_priv(dev); + int ret = NOTIFY_OK; if (!ipvlan_is_valid_dev(dev)) return NOTIFY_DONE; switch (event) { case NETDEV_UP: + spin_lock_bh(&ipvlan->port->addrs_lock); if (ipvlan_addr_busy(ipvlan->port, &i6vi->i6vi_addr, true)) { NL_SET_ERR_MSG(i6vi->extack, "Address already assigned to an ipvlan device"); - return notifier_from_errno(-EADDRINUSE); + ret = notifier_from_errno(-EADDRINUSE); } + spin_unlock_bh(&ipvlan->port->addrs_lock); break; } - return NOTIFY_OK; + return ret; } #endif @@ -946,14 +952,14 @@ static int ipvlan_add_addr4(struct ipvl_dev *ipvlan, struct in_addr *ip4_addr) { int ret = -EINVAL; - spin_lock_bh(&ipvlan->addrs_lock); + spin_lock_bh(&ipvlan->port->addrs_lock); if (ipvlan_addr_busy(ipvlan->port, ip4_addr, false)) netif_err(ipvlan, ifup, ipvlan->dev, "Failed to add IPv4=%pI4 on %s intf.\n", ip4_addr, ipvlan->dev->name); else ret = ipvlan_add_addr(ipvlan, ip4_addr, false); - spin_unlock_bh(&ipvlan->addrs_lock); + spin_unlock_bh(&ipvlan->port->addrs_lock); return ret; } @@ -995,21 +1001,24 @@ static int ipvlan_addr4_validator_event(struct notifier_block *unused, struct in_validator_info *ivi = (struct in_validator_info *)ptr; struct net_device *dev = (struct net_device *)ivi->ivi_dev->dev; struct ipvl_dev *ipvlan = netdev_priv(dev); + int ret = NOTIFY_OK; if (!ipvlan_is_valid_dev(dev)) return NOTIFY_DONE; switch (event) { case NETDEV_UP: + spin_lock_bh(&ipvlan->port->addrs_lock); if (ipvlan_addr_busy(ipvlan->port, &ivi->ivi_addr, false)) { NL_SET_ERR_MSG(ivi->extack, "Address already assigned to an ipvlan device"); - return notifier_from_errno(-EADDRINUSE); + ret = notifier_from_errno(-EADDRINUSE); } + spin_unlock_bh(&ipvlan->port->addrs_lock); break; } - return NOTIFY_OK; + return ret; } static struct notifier_block ipvlan_addr4_notifier_block __read_mostly = { From ed80f605edd6c2701a85a45ea5c6c983560524c7 Mon Sep 17 00:00:00 2001 From: Alok Tiwari Date: Fri, 16 Jan 2026 08:47:12 -0800 Subject: [PATCH 2666/3902] octeontx2: cn10k: fix RX flowid TCAM mask handling [ Upstream commit ab9b218a1521133a4410722907fa7189566be9bc ] The RX flowid programming initializes the TCAM mask to all ones, but then overwrites it when clearing the MAC DA mask bits. This results in losing the intended initialization and may affect other match fields. Update the code to clear the MAC DA bits using an AND operation, making the handling of mask[0] consistent with mask[1], where the field-specific bits are cleared after initializing the mask to ~0ULL. Fixes: 57d00d4364f3 ("octeontx2-pf: mcs: Match macsec ethertype along with DMAC") Signed-off-by: Alok Tiwari Reviewed-by: Subbaraya Sundeep Link: https://patch.msgid.link/20260116164724.2733511-1-alok.a.tiwari@oracle.com Signed-off-by: Jakub Kicinski Signed-off-by: Sasha Levin --- drivers/net/ethernet/marvell/octeontx2/nic/cn10k_macsec.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/net/ethernet/marvell/octeontx2/nic/cn10k_macsec.c b/drivers/net/ethernet/marvell/octeontx2/nic/cn10k_macsec.c index 4c7e0f345cb5ba..060c715ebad0a1 100644 --- a/drivers/net/ethernet/marvell/octeontx2/nic/cn10k_macsec.c +++ b/drivers/net/ethernet/marvell/octeontx2/nic/cn10k_macsec.c @@ -328,7 +328,7 @@ static int cn10k_mcs_write_rx_flowid(struct otx2_nic *pfvf, req->data[0] = FIELD_PREP(MCS_TCAM0_MAC_DA_MASK, mac_da); req->mask[0] = ~0ULL; - req->mask[0] = ~MCS_TCAM0_MAC_DA_MASK; + req->mask[0] &= ~MCS_TCAM0_MAC_DA_MASK; req->data[1] = FIELD_PREP(MCS_TCAM1_ETYPE_MASK, ETH_P_MACSEC); req->mask[1] = ~0ULL; From 16ed73c1282d376b956bff23e5139add061767ba Mon Sep 17 00:00:00 2001 From: Jamal Hadi Salim Date: Wed, 14 Jan 2026 11:02:41 -0500 Subject: [PATCH 2667/3902] net/sched: Enforce that teql can only be used as root qdisc MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [ Upstream commit 50da4b9d07a7a463e2cfb738f3ad4cff6b2c9c3b ] Design intent of teql is that it is only supposed to be used as root qdisc. We need to check for that constraint. Although not important, I will describe the scenario that unearthed this issue for the curious. GangMin Kim managed to concot a scenario as follows: ROOT qdisc 1:0 (QFQ) ├── class 1:1 (weight=15, lmax=16384) netem with delay 6.4s └── class 1:2 (weight=1, lmax=1514) teql GangMin sends a packet which is enqueued to 1:1 (netem). Any invocation of dequeue by QFQ from this class will not return a packet until after 6.4s. In the meantime, a second packet is sent and it lands on 1:2. teql's enqueue will return success and this will activate class 1:2. Main issue is that teql only updates the parent visible qlen (sch->q.qlen) at dequeue. Since QFQ will only call dequeue if peek succeeds (and teql's peek always returns NULL), dequeue will never be called and thus the qlen will remain as 0. With that in mind, when GangMin updates 1:2's lmax value, the qfq_change_class calls qfq_deact_rm_from_agg. Since the child qdisc's qlen was not incremented, qfq fails to deactivate the class, but still frees its pointers from the aggregate. So when the first packet is rescheduled after 6.4 seconds (netem's delay), a dangling pointer is accessed causing GangMin's causing a UAF. Fixes: 1da177e4c3f4 ("Linux-2.6.12-rc2") Reported-by: GangMin Kim Tested-by: Victor Nogueira Signed-off-by: Jamal Hadi Salim Link: https://patch.msgid.link/20260114160243.913069-2-jhs@mojatatu.com Signed-off-by: Jakub Kicinski Signed-off-by: Sasha Levin --- net/sched/sch_teql.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/net/sched/sch_teql.c b/net/sched/sch_teql.c index 8badec6d82a247..6e4bdaa876ed68 100644 --- a/net/sched/sch_teql.c +++ b/net/sched/sch_teql.c @@ -178,6 +178,11 @@ static int teql_qdisc_init(struct Qdisc *sch, struct nlattr *opt, if (m->dev == dev) return -ELOOP; + if (sch->parent != TC_H_ROOT) { + NL_SET_ERR_MSG_MOD(extack, "teql can only be used as root"); + return -EOPNOTSUPP; + } + q->m = m; skb_queue_head_init(&q->q); From 77f1afd0bb4d5da95236f6114e6d0dfcde187ff6 Mon Sep 17 00:00:00 2001 From: Jamal Hadi Salim Date: Wed, 14 Jan 2026 11:02:42 -0500 Subject: [PATCH 2668/3902] net/sched: qfq: Use cl_is_active to determine whether class is active in qfq_rm_from_ag [ Upstream commit d837fbee92453fbb829f950c8e7cf76207d73f33 ] This is more of a preventive patch to make the code more consistent and to prevent possible exploits that employ child qlen manipulations on qfq. use cl_is_active instead of relying on the child qdisc's qlen to determine class activation. Fixes: 462dbc9101acd ("pkt_sched: QFQ Plus: fair-queueing service at DRR cost") Signed-off-by: Jamal Hadi Salim Link: https://patch.msgid.link/20260114160243.913069-3-jhs@mojatatu.com Signed-off-by: Jakub Kicinski Signed-off-by: Sasha Levin --- net/sched/sch_qfq.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/net/sched/sch_qfq.c b/net/sched/sch_qfq.c index 9b16ad431028f6..f94c9c9c90424c 100644 --- a/net/sched/sch_qfq.c +++ b/net/sched/sch_qfq.c @@ -373,7 +373,7 @@ static void qfq_rm_from_agg(struct qfq_sched *q, struct qfq_class *cl) /* Deschedule class and remove it from its parent aggregate. */ static void qfq_deact_rm_from_agg(struct qfq_sched *q, struct qfq_class *cl) { - if (cl->qdisc->q.qlen > 0) /* class is active */ + if (cl_is_active(cl)) /* class is active */ qfq_deactivate_class(q, cl); qfq_rm_from_agg(q, cl); From 9532ff0d0e90ff78a214299f594ab9bac81defe4 Mon Sep 17 00:00:00 2001 From: Taeyang Lee <0wn@theori.io> Date: Fri, 16 Jan 2026 16:03:58 +0900 Subject: [PATCH 2669/3902] crypto: authencesn - reject too-short AAD (assoclen<8) to match ESP/ESN spec [ Upstream commit 2397e9264676be7794f8f7f1e9763d90bd3c7335 ] authencesn assumes an ESP/ESN-formatted AAD. When assoclen is shorter than the minimum expected length, crypto_authenc_esn_decrypt() can advance past the end of the destination scatterlist and trigger a NULL pointer dereference in scatterwalk_map_and_copy(), leading to a kernel panic (DoS). Add a minimum AAD length check to fail fast on invalid inputs. Fixes: 104880a6b470 ("crypto: authencesn - Convert to new AEAD interface") Reported-By: Taeyang Lee <0wn@theori.io> Signed-off-by: Taeyang Lee <0wn@theori.io> Signed-off-by: Herbert Xu Signed-off-by: Sasha Levin --- crypto/authencesn.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/crypto/authencesn.c b/crypto/authencesn.c index d1bf0fda3f2ef6..542a978663b9e7 100644 --- a/crypto/authencesn.c +++ b/crypto/authencesn.c @@ -169,6 +169,9 @@ static int crypto_authenc_esn_encrypt(struct aead_request *req) struct scatterlist *src, *dst; int err; + if (assoclen < 8) + return -EINVAL; + sg_init_table(areq_ctx->src, 2); src = scatterwalk_ffwd(areq_ctx->src, req->src, assoclen); dst = src; @@ -256,6 +259,9 @@ static int crypto_authenc_esn_decrypt(struct aead_request *req) u32 tmp[2]; int err; + if (assoclen < 8) + return -EINVAL; + cryptlen -= authsize; if (req->src != dst) From f4748bfa3d3e2486028d4a7d7597dd6a2fc880f4 Mon Sep 17 00:00:00 2001 From: Lachlan Hodges Date: Tue, 20 Jan 2026 14:11:21 +1100 Subject: [PATCH 2670/3902] wifi: mac80211: don't perform DA check on S1G beacon [ Upstream commit 5dc6975566f5d142ec53eb7e97af688c45dd314d ] S1G beacons don't contain the DA field as per IEEE80211-2024 9.3.4.3, so the DA broadcast check reads the SA address of the S1G beacon which will subsequently lead to the beacon being dropped. As a result, passive scanning is not possible. Fix this by only performing the check on non-S1G beacons to allow S1G long beacons to be processed during a passive scan. Fixes: ddf82e752f8a ("wifi: mac80211: Allow beacons to update BSS table regardless of scan") Signed-off-by: Lachlan Hodges Link: https://patch.msgid.link/20260120031122.309942-1-lachlan.hodges@morsemicro.com Signed-off-by: Johannes Berg Signed-off-by: Sasha Levin --- net/mac80211/scan.c | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/net/mac80211/scan.c b/net/mac80211/scan.c index bb9563f50e7b47..1e06a465b49e36 100644 --- a/net/mac80211/scan.c +++ b/net/mac80211/scan.c @@ -343,8 +343,13 @@ void ieee80211_scan_rx(struct ieee80211_local *local, struct sk_buff *skb) mgmt->da)) return; } else { - /* Beacons are expected only with broadcast address */ - if (!is_broadcast_ether_addr(mgmt->da)) + /* + * Non-S1G beacons are expected only with broadcast address. + * S1G beacons only carry the SA so no DA check is required + * nor possible. + */ + if (!ieee80211_is_s1g_beacon(mgmt->frame_control) && + !is_broadcast_ether_addr(mgmt->da)) return; } From 0b7383aceaad1b0b8f19d901cdcb2e3a1657e20b Mon Sep 17 00:00:00 2001 From: Marnix Rijnart Date: Mon, 12 Jan 2026 01:08:23 +0100 Subject: [PATCH 2671/3902] serial: 8250_pci: Fix broken RS485 for F81504/508/512 commit 27aff0a56b3c77ea1a73641c9b3c4172a8f7238f upstream. Fintek F81504/508/512 can support both RTS_ON_SEND and RTS_AFTER_SEND, but pci_fintek_rs485_supported only announces the former. This makes it impossible to unset SER_RS485_RTS_ON_SEND from userspace because of uart_sanitize_serial_rs485(). Some devices with these chips need RTS low on TX, so they are effectively broken. Fix this by announcing the support for SER_RS485_RTS_AFTER_SEND, similar to commit 068d35a7be65 ("serial: sc16is7xx: announce support for SER_RS485_RTS_ON_SEND"). Fixes: 4afeced55baa ("serial: core: fix sanitizing check for RTS settings") Cc: stable Signed-off-by: Marnix Rijnart Link: https://patch.msgid.link/20260112000931.61703-1-marnix.rijnart@iwell.eu Signed-off-by: Greg Kroah-Hartman --- drivers/tty/serial/8250/8250_pci.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/tty/serial/8250/8250_pci.c b/drivers/tty/serial/8250/8250_pci.c index 12e8ceffab65f2..93b3922bb5b6eb 100644 --- a/drivers/tty/serial/8250/8250_pci.c +++ b/drivers/tty/serial/8250/8250_pci.c @@ -1650,7 +1650,7 @@ static int pci_fintek_rs485_config(struct uart_port *port, struct ktermios *term } static const struct serial_rs485 pci_fintek_rs485_supported = { - .flags = SER_RS485_ENABLED | SER_RS485_RTS_ON_SEND, + .flags = SER_RS485_ENABLED | SER_RS485_RTS_ON_SEND | SER_RS485_RTS_AFTER_SEND, /* F81504/508/512 does not support RTS delay before or after send */ }; From 2501c49306238b54a2de0f93de43d50ab6e76c84 Mon Sep 17 00:00:00 2001 From: Krzysztof Kozlowski Date: Fri, 23 Jan 2026 08:21:40 +0100 Subject: [PATCH 2672/3902] serial: Fix not set tty->port race condition commit 32f37e57583f869140cff445feedeea8a5fea986 upstream. Revert commit bfc467db60b7 ("serial: remove redundant tty_port_link_device()") because the tty_port_link_device() is not redundant: the tty->port has to be confured before we call uart_configure_port(), otherwise user-space can open console without TTY linked to the driver. This tty_port_link_device() was added explicitly to avoid this exact issue in commit fb2b90014d78 ("tty: link tty and port before configuring it as console"), so offending commit basically reverted the fix saying it is redundant without addressing the actual race condition presented there. Reproducible always as tty->port warning on Qualcomm SoC with most of devices disabled, so with very fast boot, and one serial device being the console: printk: legacy console [ttyMSM0] enabled printk: legacy console [ttyMSM0] enabled printk: legacy bootconsole [qcom_geni0] disabled printk: legacy bootconsole [qcom_geni0] disabled ------------[ cut here ]------------ tty_init_dev: ttyMSM driver does not set tty->port. This would crash the kernel. Fix the driver! WARNING: drivers/tty/tty_io.c:1414 at tty_init_dev.part.0+0x228/0x25c, CPU#2: systemd/1 Modules linked in: socinfo tcsrcc_eliza gcc_eliza sm3_ce fuse ipv6 CPU: 2 UID: 0 PID: 1 Comm: systemd Tainted: G S 6.19.0-rc4-next-20260108-00024-g2202f4d30aa8 #73 PREEMPT Tainted: [S]=CPU_OUT_OF_SPEC Hardware name: Qualcomm Technologies, Inc. Eliza (DT) ... tty_init_dev.part.0 (drivers/tty/tty_io.c:1414 (discriminator 11)) (P) tty_open (arch/arm64/include/asm/atomic_ll_sc.h:95 (discriminator 3) drivers/tty/tty_io.c:2073 (discriminator 3) drivers/tty/tty_io.c:2120 (discriminator 3)) chrdev_open (fs/char_dev.c:411) do_dentry_open (fs/open.c:962) vfs_open (fs/open.c:1094) do_open (fs/namei.c:4634) path_openat (fs/namei.c:4793) do_filp_open (fs/namei.c:4820) do_sys_openat2 (fs/open.c:1391 (discriminator 3)) ... Starting Network Name Resolution... Apparently the flow with this small Yocto-based ramdisk user-space is: driver (qcom_geni_serial.c): user-space: ============================ =========== qcom_geni_serial_probe() uart_add_one_port() serial_core_register_port() serial_core_add_one_port() uart_configure_port() register_console() | | open console | ... | tty_init_dev() | driver->ports[idx] is NULL | tty_port_register_device_attr_serdev() tty_port_link_device() <- set driver->ports[idx] Fixes: bfc467db60b7 ("serial: remove redundant tty_port_link_device()") Cc: stable@vger.kernel.org Signed-off-by: Krzysztof Kozlowski Reviewed-by: Jiri Slaby Link: https://patch.msgid.link/20260123072139.53293-2-krzysztof.kozlowski@oss.qualcomm.com Signed-off-by: Greg Kroah-Hartman --- drivers/tty/serial/serial_core.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/drivers/tty/serial/serial_core.c b/drivers/tty/serial/serial_core.c index 4757293ece8c65..26db27d06a8653 100644 --- a/drivers/tty/serial/serial_core.c +++ b/drivers/tty/serial/serial_core.c @@ -3102,6 +3102,12 @@ static int serial_core_add_one_port(struct uart_driver *drv, struct uart_port *u if (uport->cons && uport->dev) of_console_check(uport->dev->of_node, uport->cons->name, uport->line); + /* + * TTY port has to be linked with the driver before register_console() + * in uart_configure_port(), because user-space could open the console + * immediately after. + */ + tty_port_link_device(port, drv->tty_driver, uport->line); uart_configure_port(drv, state, uport); port->console = uart_console(uport); From 169164fe51b277ec3b1ab1a1292ba686cb7f8fcd Mon Sep 17 00:00:00 2001 From: Ian Abbott Date: Mon, 12 Jan 2026 16:28:35 +0000 Subject: [PATCH 2673/3902] comedi: dmm32at: serialize use of paged registers commit e03b29b55f2b7c345a919a6ee36633b06bf3fb56 upstream. Some of the hardware registers of the DMM-32-AT board are multiplexed, using the least significant two bits of the Miscellaneous Control register to select the function of registers at offsets 12 to 15: 00 => 8254 timer/counter registers are accessible 01 => 8255 digital I/O registers are accessible 10 => Reserved 11 => Calibration registers are accessible The interrupt service routine (`dmm32at_isr()`) clobbers the bottom two bits of the register with value 00, which would interfere with access to the 8255 registers by the `dm32at_8255_io()` function (used for Comedi instruction handling on the digital I/O subdevice). Make use of the generic Comedi device spin-lock `dev->spinlock` (which is otherwise unused by this driver) to serialize access to the miscellaneous control register and paged registers. Fixes: 3c501880ac44 ("Staging: comedi: add dmm32at driver") Cc: stable@vger.kernel.org Signed-off-by: Ian Abbott Link: https://patch.msgid.link/20260112162835.91688-1-abbotti@mev.co.uk Signed-off-by: Greg Kroah-Hartman --- drivers/comedi/drivers/dmm32at.c | 32 ++++++++++++++++++++++++++++++-- 1 file changed, 30 insertions(+), 2 deletions(-) diff --git a/drivers/comedi/drivers/dmm32at.c b/drivers/comedi/drivers/dmm32at.c index 644e3b643c7965..910cd24b1bed57 100644 --- a/drivers/comedi/drivers/dmm32at.c +++ b/drivers/comedi/drivers/dmm32at.c @@ -330,6 +330,7 @@ static int dmm32at_ai_cmdtest(struct comedi_device *dev, static void dmm32at_setaitimer(struct comedi_device *dev, unsigned int nansec) { + unsigned long irq_flags; unsigned char lo1, lo2, hi2; unsigned short both2; @@ -342,6 +343,9 @@ static void dmm32at_setaitimer(struct comedi_device *dev, unsigned int nansec) /* set counter clocks to 10MHz, disable all aux dio */ outb(0, dev->iobase + DMM32AT_CTRDIO_CFG_REG); + /* serialize access to control register and paged registers */ + spin_lock_irqsave(&dev->spinlock, irq_flags); + /* get access to the clock regs */ outb(DMM32AT_CTRL_PAGE_8254, dev->iobase + DMM32AT_CTRL_REG); @@ -354,6 +358,8 @@ static void dmm32at_setaitimer(struct comedi_device *dev, unsigned int nansec) outb(lo2, dev->iobase + DMM32AT_CLK2); outb(hi2, dev->iobase + DMM32AT_CLK2); + spin_unlock_irqrestore(&dev->spinlock, irq_flags); + /* enable the ai conversion interrupt and the clock to start scans */ outb(DMM32AT_INTCLK_ADINT | DMM32AT_INTCLK_CLKEN | DMM32AT_INTCLK_CLKSEL, @@ -363,13 +369,19 @@ static void dmm32at_setaitimer(struct comedi_device *dev, unsigned int nansec) static int dmm32at_ai_cmd(struct comedi_device *dev, struct comedi_subdevice *s) { struct comedi_cmd *cmd = &s->async->cmd; + unsigned long irq_flags; int ret; dmm32at_ai_set_chanspec(dev, s, cmd->chanlist[0], cmd->chanlist_len); + /* serialize access to control register and paged registers */ + spin_lock_irqsave(&dev->spinlock, irq_flags); + /* reset the interrupt just in case */ outb(DMM32AT_CTRL_INTRST, dev->iobase + DMM32AT_CTRL_REG); + spin_unlock_irqrestore(&dev->spinlock, irq_flags); + /* * wait for circuit to settle * we don't have the 'insn' here but it's not needed @@ -429,8 +441,13 @@ static irqreturn_t dmm32at_isr(int irq, void *d) comedi_handle_events(dev, s); } + /* serialize access to control register and paged registers */ + spin_lock(&dev->spinlock); + /* reset the interrupt */ outb(DMM32AT_CTRL_INTRST, dev->iobase + DMM32AT_CTRL_REG); + + spin_unlock(&dev->spinlock); return IRQ_HANDLED; } @@ -481,14 +498,25 @@ static int dmm32at_ao_insn_write(struct comedi_device *dev, static int dmm32at_8255_io(struct comedi_device *dev, int dir, int port, int data, unsigned long regbase) { + unsigned long irq_flags; + int ret; + + /* serialize access to control register and paged registers */ + spin_lock_irqsave(&dev->spinlock, irq_flags); + /* get access to the DIO regs */ outb(DMM32AT_CTRL_PAGE_8255, dev->iobase + DMM32AT_CTRL_REG); if (dir) { outb(data, dev->iobase + regbase + port); - return 0; + ret = 0; + } else { + ret = inb(dev->iobase + regbase + port); } - return inb(dev->iobase + regbase + port); + + spin_unlock_irqrestore(&dev->spinlock, irq_flags); + + return ret; } /* Make sure the board is there and put it to a known state */ From e6b2609af21b5cccc9559339591b8a2cbf884169 Mon Sep 17 00:00:00 2001 From: Thorsten Blum Date: Tue, 16 Dec 2025 15:50:03 +0100 Subject: [PATCH 2674/3902] w1: therm: Fix off-by-one buffer overflow in alarms_store commit 761fcf46a1bd797bd32d23f3ea0141ffd437668a upstream. The sysfs buffer passed to alarms_store() is allocated with 'size + 1' bytes and a NUL terminator is appended. However, the 'size' argument does not account for this extra byte. The original code then allocated 'size' bytes and used strcpy() to copy 'buf', which always writes one byte past the allocated buffer since strcpy() copies until the NUL terminator at index 'size'. Fix this by parsing the 'buf' parameter directly using simple_strtoll() without allocating any intermediate memory or string copying. This removes the overflow while simplifying the code. Cc: stable@vger.kernel.org Fixes: e2c94d6f5720 ("w1_therm: adding alarm sysfs entry") Signed-off-by: Thorsten Blum Link: https://patch.msgid.link/20251216145007.44328-2-thorsten.blum@linux.dev Signed-off-by: Krzysztof Kozlowski Signed-off-by: Greg Kroah-Hartman --- drivers/w1/slaves/w1_therm.c | 62 ++++++++++++------------------------ 1 file changed, 20 insertions(+), 42 deletions(-) diff --git a/drivers/w1/slaves/w1_therm.c b/drivers/w1/slaves/w1_therm.c index 9ccedb3264fb2a..832e3da94b203a 100644 --- a/drivers/w1/slaves/w1_therm.c +++ b/drivers/w1/slaves/w1_therm.c @@ -1836,53 +1836,35 @@ static ssize_t alarms_store(struct device *device, struct w1_slave *sl = dev_to_w1_slave(device); struct therm_info info; u8 new_config_register[3]; /* array of data to be written */ - int temp, ret; - char *token = NULL; + long long temp; + int ret = 0; s8 tl, th; /* 1 byte per value + temp ring order */ - char *p_args, *orig; - - p_args = orig = kmalloc(size, GFP_KERNEL); - /* Safe string copys as buf is const */ - if (!p_args) { - dev_warn(device, - "%s: error unable to allocate memory %d\n", - __func__, -ENOMEM); - return size; - } - strcpy(p_args, buf); - - /* Split string using space char */ - token = strsep(&p_args, " "); - - if (!token) { - dev_info(device, - "%s: error parsing args %d\n", __func__, -EINVAL); - goto free_m; - } - - /* Convert 1st entry to int */ - ret = kstrtoint (token, 10, &temp); + const char *p = buf; + char *endp; + + temp = simple_strtoll(p, &endp, 10); + if (p == endp || *endp != ' ') + ret = -EINVAL; + else if (temp < INT_MIN || temp > INT_MAX) + ret = -ERANGE; if (ret) { dev_info(device, "%s: error parsing args %d\n", __func__, ret); - goto free_m; + return size; } tl = int_to_short(temp); - /* Split string using space char */ - token = strsep(&p_args, " "); - if (!token) { - dev_info(device, - "%s: error parsing args %d\n", __func__, -EINVAL); - goto free_m; - } - /* Convert 2nd entry to int */ - ret = kstrtoint (token, 10, &temp); + p = endp + 1; + temp = simple_strtoll(p, &endp, 10); + if (p == endp) + ret = -EINVAL; + else if (temp < INT_MIN || temp > INT_MAX) + ret = -ERANGE; if (ret) { dev_info(device, "%s: error parsing args %d\n", __func__, ret); - goto free_m; + return size; } /* Prepare to cast to short by eliminating out of range values */ @@ -1905,7 +1887,7 @@ static ssize_t alarms_store(struct device *device, dev_info(device, "%s: error reading from the slave device %d\n", __func__, ret); - goto free_m; + return size; } /* Write data in the device RAM */ @@ -1913,7 +1895,7 @@ static ssize_t alarms_store(struct device *device, dev_info(device, "%s: Device not supported by the driver %d\n", __func__, -ENODEV); - goto free_m; + return size; } ret = SLAVE_SPECIFIC_FUNC(sl)->write_data(sl, new_config_register); @@ -1922,10 +1904,6 @@ static ssize_t alarms_store(struct device *device, "%s: error writing to the slave device %d\n", __func__, ret); -free_m: - /* free allocated memory */ - kfree(orig); - return size; } From 3f5ef08f302ccb79b2ebb1e39d2a42955078abdc Mon Sep 17 00:00:00 2001 From: Haoxiang Li Date: Thu, 18 Dec 2025 19:14:14 +0800 Subject: [PATCH 2675/3902] w1: fix redundant counter decrement in w1_attach_slave_device() commit cc8f92e41eb76f450f05234fef2054afc3633100 upstream. In w1_attach_slave_device(), if __w1_attach_slave_device() fails, put_device() -> w1_slave_release() is called to do the cleanup job. In w1_slave_release(), sl->family->refcnt and sl->master->slave_count have already been decremented. There is no need to decrement twice in w1_attach_slave_device(). Fixes: 2c927c0c73fd ("w1: Fix slave count on 1-Wire bus (resend)") Cc: stable@vger.kernel.org Signed-off-by: Haoxiang Li Link: https://patch.msgid.link/20251218111414.564403-1-lihaoxiang@isrc.iscas.ac.cn Signed-off-by: Krzysztof Kozlowski Signed-off-by: Greg Kroah-Hartman --- drivers/w1/w1.c | 2 -- 1 file changed, 2 deletions(-) diff --git a/drivers/w1/w1.c b/drivers/w1/w1.c index d0474a0532ec1f..47e5a3f38ca339 100644 --- a/drivers/w1/w1.c +++ b/drivers/w1/w1.c @@ -758,8 +758,6 @@ int w1_attach_slave_device(struct w1_master *dev, struct w1_reg_num *rn) if (err < 0) { dev_err(&dev->dev, "%s: Attaching %s failed.\n", __func__, sl->name); - dev->slave_count--; - w1_family_put(sl->family); atomic_dec(&sl->master->refcnt); kfree(sl); return err; From 5d5b227c92721fafe4bf25573777626be3c6930c Mon Sep 17 00:00:00 2001 From: Thadeu Lima de Souza Cascardo Date: Tue, 13 Jan 2026 17:24:58 -0300 Subject: [PATCH 2676/3902] Revert "nfc/nci: Add the inconsistency check between the input data length and count" commit f40ddcc0c0ca1a0122a7f4440b429f97d5832bdf upstream. This reverts commit 068648aab72c9ba7b0597354ef4d81ffaac7b979. NFC packets may have NUL-bytes. Checking for string length is not a correct assumption here. As long as there is a check for the length copied from copy_from_user, all should be fine. The fix only prevented the syzbot reproducer from triggering the bug because the packet is not enqueued anymore and the code that triggers the bug is not exercised. The fix even broke testing/selftests/nci/nci_dev, making all tests there fail. After the revert, 6 out of 8 tests pass. Fixes: 068648aab72c ("nfc/nci: Add the inconsistency check between the input data length and count") Cc: stable@vger.kernel.org Signed-off-by: Thadeu Lima de Souza Cascardo Link: https://patch.msgid.link/20260113202458.449455-1-cascardo@igalia.com Signed-off-by: Jakub Kicinski Signed-off-by: Greg Kroah-Hartman --- drivers/nfc/virtual_ncidev.c | 4 ---- 1 file changed, 4 deletions(-) diff --git a/drivers/nfc/virtual_ncidev.c b/drivers/nfc/virtual_ncidev.c index 9ef8ef2d4363a8..b957fce83b7c88 100644 --- a/drivers/nfc/virtual_ncidev.c +++ b/drivers/nfc/virtual_ncidev.c @@ -125,10 +125,6 @@ static ssize_t virtual_ncidev_write(struct file *file, kfree_skb(skb); return -EFAULT; } - if (strnlen(skb->data, count) != count) { - kfree_skb(skb); - return -EINVAL; - } nci_recv_frame(vdev->ndev, skb); return count; From 2c61ca3c5bed67756dc74e6f8f748c412eaae96e Mon Sep 17 00:00:00 2001 From: gongqi <550230171hxy@gmail.com> Date: Thu, 22 Jan 2026 23:54:59 +0800 Subject: [PATCH 2677/3902] Input: i8042 - add quirks for MECHREVO Wujie 15X Pro commit 19a5d9ba6208e9006a2a9d5962aea4d6e427d8ab upstream. The MECHREVO Wujie 15X Pro requires several i8042 quirks to function correctly. Specifically, NOMUX, RESET_ALWAYS, NOLOOP, and NOPNP are needed to ensure the keyboard and touchpad work reliably. Signed-off-by: gongqi <550230171hxy@gmail.com> Link: https://patch.msgid.link/20260122155501.376199-3-550230171hxy@gmail.com Cc: stable@vger.kernel.org Signed-off-by: Dmitry Torokhov Signed-off-by: Greg Kroah-Hartman --- drivers/input/serio/i8042-acpipnpio.h | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/drivers/input/serio/i8042-acpipnpio.h b/drivers/input/serio/i8042-acpipnpio.h index 654771275ce878..4cd09560c5bfb3 100644 --- a/drivers/input/serio/i8042-acpipnpio.h +++ b/drivers/input/serio/i8042-acpipnpio.h @@ -1176,6 +1176,13 @@ static const struct dmi_system_id i8042_dmi_quirk_table[] __initconst = { .driver_data = (void *)(SERIO_QUIRK_NOMUX | SERIO_QUIRK_RESET_ALWAYS | SERIO_QUIRK_NOLOOP | SERIO_QUIRK_NOPNP) }, + { + .matches = { + DMI_MATCH(DMI_BOARD_NAME, "WUJIE Series-X5SP4NAG"), + }, + .driver_data = (void *)(SERIO_QUIRK_NOMUX | SERIO_QUIRK_RESET_ALWAYS | + SERIO_QUIRK_NOLOOP | SERIO_QUIRK_NOPNP) + }, /* * A lot of modern Clevo barebones have touchpad and/or keyboard issues * after suspend fixable with the forcenorestore quirk. From bfe76b0f9f3e9c956107bd22168691692f74a38b Mon Sep 17 00:00:00 2001 From: feng Date: Sat, 24 Jan 2026 21:44:12 -0800 Subject: [PATCH 2678/3902] Input: i8042 - add quirk for ASUS Zenbook UX425QA_UM425QA commit 2934325f56150ad8dab8ab92cbe2997242831396 upstream. The ASUS Zenbook UX425QA_UM425QA fails to initialize the keyboard after a cold boot. A quirk already exists for "ZenBook UX425", but some Zenbooks report "Zenbook" with a lowercase 'b'. Since DMI matching is case-sensitive, the existing quirk is not applied to these "extra special" Zenbooks. Testing confirms that this model needs the same quirks as the ZenBook UX425 variants. Signed-off-by: feng Link: https://patch.msgid.link/20260122013957.11184-1-alec.jiang@gmail.com Cc: stable@vger.kernel.org Signed-off-by: Dmitry Torokhov Signed-off-by: Greg Kroah-Hartman --- drivers/input/serio/i8042-acpipnpio.h | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/drivers/input/serio/i8042-acpipnpio.h b/drivers/input/serio/i8042-acpipnpio.h index 4cd09560c5bfb3..d2cf940b105a69 100644 --- a/drivers/input/serio/i8042-acpipnpio.h +++ b/drivers/input/serio/i8042-acpipnpio.h @@ -115,6 +115,17 @@ static const struct dmi_system_id i8042_dmi_quirk_table[] __initconst = { }, .driver_data = (void *)(SERIO_QUIRK_NOMUX | SERIO_QUIRK_RESET_NEVER) }, + { + /* + * ASUS Zenbook UX425QA_UM425QA + * Some Zenbooks report "Zenbook" with a lowercase b. + */ + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "ASUSTeK COMPUTER INC."), + DMI_MATCH(DMI_PRODUCT_NAME, "Zenbook UX425QA_UM425QA"), + }, + .driver_data = (void *)(SERIO_QUIRK_PROBE_DEFER | SERIO_QUIRK_RESET_NEVER) + }, { /* ASUS ZenBook UX425UA/QA */ .matches = { From a5e4d969392ceae3867362557a6017065cdda0b7 Mon Sep 17 00:00:00 2001 From: Long Li Date: Fri, 16 Jan 2026 17:03:02 -0800 Subject: [PATCH 2679/3902] scsi: storvsc: Process unsupported MODE_SENSE_10 commit 9eacec5d18f98f89be520eeeef4b377acee3e4b8 upstream. The Hyper-V host does not support MODE_SENSE_10 and MODE_SENSE. The driver handles MODE_SENSE as unsupported command, but not for MODE_SENSE_10. Add MODE_SENSE_10 to the same handling logic and return correct code to SCSI layer. Fixes: 89ae7d709357 ("Staging: hv: storvsc: Move the storage driver out of the staging area") Cc: stable@kernel.org Signed-off-by: Long Li Reviewed-by: Michael Kelley Link: https://patch.msgid.link/20260117010302.294068-1-longli@linux.microsoft.com Signed-off-by: Martin K. Petersen Signed-off-by: Greg Kroah-Hartman --- drivers/scsi/storvsc_drv.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/drivers/scsi/storvsc_drv.c b/drivers/scsi/storvsc_drv.c index 6e4112143c7689..b43d876747b76c 100644 --- a/drivers/scsi/storvsc_drv.c +++ b/drivers/scsi/storvsc_drv.c @@ -1144,7 +1144,7 @@ static void storvsc_on_io_completion(struct storvsc_device *stor_device, * The current SCSI handling on the host side does * not correctly handle: * INQUIRY command with page code parameter set to 0x80 - * MODE_SENSE command with cmd[2] == 0x1c + * MODE_SENSE and MODE_SENSE_10 command with cmd[2] == 0x1c * MAINTENANCE_IN is not supported by HyperV FC passthrough * * Setup srb and scsi status so this won't be fatal. @@ -1154,6 +1154,7 @@ static void storvsc_on_io_completion(struct storvsc_device *stor_device, if ((stor_pkt->vm_srb.cdb[0] == INQUIRY) || (stor_pkt->vm_srb.cdb[0] == MODE_SENSE) || + (stor_pkt->vm_srb.cdb[0] == MODE_SENSE_10) || (stor_pkt->vm_srb.cdb[0] == MAINTENANCE_IN && hv_dev_is_fc(device))) { vstor_packet->vm_srb.scsi_status = 0; From 24c441f0e24da175d7912095663f526ac480dc4f Mon Sep 17 00:00:00 2001 From: Abdun Nihaal Date: Tue, 23 Dec 2025 12:00:11 +0530 Subject: [PATCH 2680/3902] scsi: xen: scsiback: Fix potential memory leak in scsiback_remove() commit 901a5f309daba412e2a30364d7ec1492fa11c32c upstream. Memory allocated for struct vscsiblk_info in scsiback_probe() is not freed in scsiback_remove() leading to potential memory leaks on remove, as well as in the scsiback_probe() error paths. Fix that by freeing it in scsiback_remove(). Cc: stable@vger.kernel.org Fixes: d9d660f6e562 ("xen-scsiback: Add Xen PV SCSI backend driver") Signed-off-by: Abdun Nihaal Reviewed-by: Juergen Gross Link: https://patch.msgid.link/20251223063012.119035-1-nihaal@cse.iitm.ac.in Signed-off-by: Martin K. Petersen Signed-off-by: Greg Kroah-Hartman --- drivers/xen/xen-scsiback.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/xen/xen-scsiback.c b/drivers/xen/xen-scsiback.c index 0c51edfd13dcdf..7d5117e5efe0e9 100644 --- a/drivers/xen/xen-scsiback.c +++ b/drivers/xen/xen-scsiback.c @@ -1262,6 +1262,7 @@ static void scsiback_remove(struct xenbus_device *dev) gnttab_page_cache_shrink(&info->free_pages, 0); dev_set_drvdata(&dev->dev, NULL); + kfree(info); } static int scsiback_probe(struct xenbus_device *dev, From cd3c1f823e213eee335c62513dda4c468c9d6e79 Mon Sep 17 00:00:00 2001 From: Yixun Lan Date: Thu, 22 Jan 2026 07:52:00 +0800 Subject: [PATCH 2681/3902] i2c: spacemit: drop IRQF_ONESHOT flag from IRQ request commit e351836a54e3b0b4483f896abcd6a0dc71097693 upstream. In commit aef30c8d569c ("genirq: Warn about using IRQF_ONESHOT without a threaded handler")[1], it will check IRQF_ONESHOT flag in IRQ request, and gives a warning if there is no threaded handler. Drop this flag to fix this warning. Link: https://lore.kernel.org/r/20260112134013.eQWyReHR@linutronix.de/ [1] Fixes: 5ea558473fa3 ("i2c: spacemit: add support for SpacemiT K1 SoC") Signed-off-by: Yixun Lan Cc: # v6.15+ Reviewed-by: Javier Martinez Canillas Reviewed-by: Troy Mitchell Signed-off-by: Andi Shyti Link: https://lore.kernel.org/r/20260122-05-k1-i2c-irq-v1-1-9b8d94bbcd22@kernel.org Signed-off-by: Greg Kroah-Hartman --- drivers/i2c/busses/i2c-k1.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/i2c/busses/i2c-k1.c b/drivers/i2c/busses/i2c-k1.c index d42c03ef5db598..8ef6d5d1927b72 100644 --- a/drivers/i2c/busses/i2c-k1.c +++ b/drivers/i2c/busses/i2c-k1.c @@ -566,7 +566,7 @@ static int spacemit_i2c_probe(struct platform_device *pdev) return dev_err_probe(dev, i2c->irq, "failed to get irq resource"); ret = devm_request_irq(i2c->dev, i2c->irq, spacemit_i2c_irq_handler, - IRQF_NO_SUSPEND | IRQF_ONESHOT, dev_name(i2c->dev), i2c); + IRQF_NO_SUSPEND, dev_name(i2c->dev), i2c); if (ret) return dev_err_probe(dev, ret, "failed to request irq"); From 5f403d64af721ddb7c4ede6981583c14503f44b0 Mon Sep 17 00:00:00 2001 From: Hari Prasath Gujulan Elango Date: Fri, 2 Jan 2026 18:01:30 +0100 Subject: [PATCH 2682/3902] ARM: dts: microchip: sama7d65: fix the ranges property for flx9 commit aabc977aa472ccf756372ae594d890022c19c9c8 upstream. Update the ranges property for the flexcom9 as per the datasheet and align with the reg property. Fixes: b51e4aea3ecf ("ARM: dts: microchip: sama7d65: Add FLEXCOMs to sama7d65 SoC") Cc: stable@vger.kernel.org # 6.16+ Signed-off-by: Hari Prasath Gujulan Elango Signed-off-by: Nicolas Ferre Link: https://lore.kernel.org/r/20260102170135.70717-2-nicolas.ferre@microchip.com Signed-off-by: Claudiu Beznea Signed-off-by: Greg Kroah-Hartman --- arch/arm/boot/dts/microchip/sama7d65.dtsi | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/arch/arm/boot/dts/microchip/sama7d65.dtsi b/arch/arm/boot/dts/microchip/sama7d65.dtsi index cd2cf9a6f40b86..5f3a7b178aa70b 100644 --- a/arch/arm/boot/dts/microchip/sama7d65.dtsi +++ b/arch/arm/boot/dts/microchip/sama7d65.dtsi @@ -676,7 +676,7 @@ flx9: flexcom@e2820000 { compatible = "microchip,sama7d65-flexcom", "atmel,sama5d2-flexcom"; reg = <0xe2820000 0x200>; - ranges = <0x0 0xe281c000 0x800>; + ranges = <0x0 0xe2820000 0x800>; clocks = <&pmc PMC_TYPE_PERIPHERAL 43>; #address-cells = <1>; #size-cells = <1>; From 808d8755fc6e69277f745dd3ae027eb4f7622465 Mon Sep 17 00:00:00 2001 From: Nicolas Ferre Date: Fri, 2 Jan 2026 18:01:31 +0100 Subject: [PATCH 2683/3902] ARM: dts: microchip: sama7d65: fix size-cells property for i2c3 commit 94ad504e67cd3be94fa1b2fed0cb87da0d8f9396 upstream. Fix the #size-cells property for i2c3 node and remove the dtbs_check error telling that "#size-cells: 0 was expected" from schema atmel,at91sam-i2c.yaml and i2c-controller.yaml. Fixes: b51e4aea3ecf ("ARM: dts: microchip: sama7d65: Add FLEXCOMs to sama7d65 SoC") Cc: stable@vger.kernel.org # 6.16+ Signed-off-by: Nicolas Ferre Link: https://lore.kernel.org/r/20260102170135.70717-3-nicolas.ferre@microchip.com Signed-off-by: Claudiu Beznea Signed-off-by: Greg Kroah-Hartman --- arch/arm/boot/dts/microchip/sama7d65.dtsi | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/arch/arm/boot/dts/microchip/sama7d65.dtsi b/arch/arm/boot/dts/microchip/sama7d65.dtsi index 5f3a7b178aa70b..868045c650a733 100644 --- a/arch/arm/boot/dts/microchip/sama7d65.dtsi +++ b/arch/arm/boot/dts/microchip/sama7d65.dtsi @@ -527,7 +527,7 @@ interrupts = ; clocks = <&pmc PMC_TYPE_PERIPHERAL 37>; #address-cells = <1>; - #size-cells = <1>; + #size-cells = <0>; dmas = <&dma0 AT91_XDMAC_DT_PERID(12)>, <&dma0 AT91_XDMAC_DT_PERID(11)>; dma-names = "tx", "rx"; From c8039a60c31dc2b11d1db6114e1ca44614391538 Mon Sep 17 00:00:00 2001 From: Geraldo Nascimento Date: Mon, 17 Nov 2025 18:47:59 -0300 Subject: [PATCH 2684/3902] arm64: dts: rockchip: remove redundant max-link-speed from nanopi-r4s commit ce652c98a7bfa0b7c675ef5cd85c44c186db96af upstream. This is already the default in rk3399-base.dtsi, remove redundant declaration from rk3399-nanopi-r4s.dtsi. Fixes: db792e9adbf8 ("rockchip: rk3399: Add support for FriendlyARM NanoPi R4S") Cc: stable@vger.kernel.org Reported-by: Dragan Simic Reviewed-by: Dragan Simic Signed-off-by: Geraldo Nascimento Acked-by: Shawn Lin Link: https://patch.msgid.link/6694456a735844177c897581f785cc00c064c7d1.1763415706.git.geraldogabriel@gmail.com Signed-off-by: Heiko Stuebner Signed-off-by: Greg Kroah-Hartman --- arch/arm64/boot/dts/rockchip/rk3399-nanopi-r4s.dtsi | 1 - 1 file changed, 1 deletion(-) diff --git a/arch/arm64/boot/dts/rockchip/rk3399-nanopi-r4s.dtsi b/arch/arm64/boot/dts/rockchip/rk3399-nanopi-r4s.dtsi index 8d94d9f91a5c66..3a9a10f531bdb2 100644 --- a/arch/arm64/boot/dts/rockchip/rk3399-nanopi-r4s.dtsi +++ b/arch/arm64/boot/dts/rockchip/rk3399-nanopi-r4s.dtsi @@ -71,7 +71,6 @@ }; &pcie0 { - max-link-speed = <1>; num-lanes = <1>; vpcie3v3-supply = <&vcc3v3_sys>; }; From 18d2d227ccd77d2aea9893122a5538c5abde48f8 Mon Sep 17 00:00:00 2001 From: Geraldo Nascimento Date: Mon, 17 Nov 2025 18:47:43 -0300 Subject: [PATCH 2685/3902] arm64: dts: rockchip: remove dangerous max-link-speed from helios64 commit 0368e4afcf20f377c81fa77b1c7d0dee4a625a44 upstream. Shawn Lin from Rockchip strongly discourages attempts to use their RK3399 PCIe core at 5.0 GT/s speed, citing concerns about catastrophic failures that may happen. Even if the odds are low, drop from last user of this non-default property for the RK3399 platform, helios64 board dts. Fixes: 755fff528b1b ("arm64: dts: rockchip: add variables for pcie completion to helios64") Link: https://lore.kernel.org/all/e8524bf8-a90c-423f-8a58-9ef05a3db1dd@rock-chips.com/ Cc: stable@vger.kernel.org Reported-by: Shawn Lin Reviewed-by: Dragan Simic Signed-off-by: Geraldo Nascimento Acked-by: Shawn Lin Link: https://patch.msgid.link/43bb639c120f599106fca2deee6c6599b2692c5c.1763415706.git.geraldogabriel@gmail.com Signed-off-by: Heiko Stuebner Signed-off-by: Greg Kroah-Hartman --- arch/arm64/boot/dts/rockchip/rk3399-kobol-helios64.dts | 1 - 1 file changed, 1 deletion(-) diff --git a/arch/arm64/boot/dts/rockchip/rk3399-kobol-helios64.dts b/arch/arm64/boot/dts/rockchip/rk3399-kobol-helios64.dts index e7d4a2f9a95eaa..78a7775c3b2263 100644 --- a/arch/arm64/boot/dts/rockchip/rk3399-kobol-helios64.dts +++ b/arch/arm64/boot/dts/rockchip/rk3399-kobol-helios64.dts @@ -424,7 +424,6 @@ &pcie0 { ep-gpios = <&gpio2 RK_PD4 GPIO_ACTIVE_HIGH>; - max-link-speed = <2>; num-lanes = <2>; pinctrl-names = "default"; status = "okay"; From 3569a00f66a87ef3b4b326d81a2200c720468cf4 Mon Sep 17 00:00:00 2001 From: Ondrej Jirman Date: Mon, 24 Nov 2025 19:47:03 -0800 Subject: [PATCH 2686/3902] arm64: dts: rockchip: Fix voltage threshold for volume keys for Pinephone Pro commit 5497ffe305b2ea31ae62d4a311d7cabfb671f54a upstream. Previously sometimes pressing the volume-down button would register as a volume-up button. Match the thresholds as shown in the Pinephone Pro schematic. Tests: ~ $ evtest // Mashed the volume down ~100 times with varying intensity Event: time xxx, type 1 (EV_KEY), code 114 (KEY_VOLUMEDOWN), value 1 Event: time xxx, type 1 (EV_KEY), code 114 (KEY_VOLUMEDOWN), value 0 // Mashed the volume up ~100 times with varying intensity Event: time xxx, type 1 (EV_KEY), code 115 (KEY_VOLUMEUP), value 1 Event: time xxx, type 1 (EV_KEY), code 115 (KEY_VOLUMEUP), value 0 Fixes: d3150ed53580 ("arm64: dts: rockchip: Add support for volume keys to rk3399-pinephone-pro") Cc: stable@vger.kernel.org Signed-off-by: Ondrej Jirman Signed-off-by: Rudraksha Gupta Reviewed-by: Pavel Machek Link: https://patch.msgid.link/20251124-ppp_light_accel_mag_vol-down-v5-4-f9a10a0a50eb@gmail.com Signed-off-by: Heiko Stuebner Signed-off-by: Greg Kroah-Hartman --- arch/arm64/boot/dts/rockchip/rk3399-pinephone-pro.dts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/arch/arm64/boot/dts/rockchip/rk3399-pinephone-pro.dts b/arch/arm64/boot/dts/rockchip/rk3399-pinephone-pro.dts index 2dca1dca20b8ba..5de964d369b09a 100644 --- a/arch/arm64/boot/dts/rockchip/rk3399-pinephone-pro.dts +++ b/arch/arm64/boot/dts/rockchip/rk3399-pinephone-pro.dts @@ -40,13 +40,13 @@ button-up { label = "Volume Up"; linux,code = ; - press-threshold-microvolt = <100000>; + press-threshold-microvolt = <2000>; }; button-down { label = "Volume Down"; linux,code = ; - press-threshold-microvolt = <600000>; + press-threshold-microvolt = <300000>; }; }; From 7eb3e7787360530fa9bffb5bd8e740c33f84bff6 Mon Sep 17 00:00:00 2001 From: Quentin Schulz Date: Mon, 15 Dec 2025 17:45:56 +0100 Subject: [PATCH 2687/3902] arm64: dts: rockchip: fix unit-address for RK3588 NPU's core1 and core2's IOMMU commit cd8967ea3105d30adb878a9fea0e34a9378df610 upstream. The Device Tree specification specifies[1] that """ Each node in the devicetree is named according to the following convention: node-name@unit-address [...] The unit-address must match the first address specified in the reg property of the node. """ The first address in the reg property is fdaXa000 and not fdaX9000. This is likely a copy-paste error as the IOMMU for core0 has two entries in the reg property, the first one being fdab9000 and the second fdaba000. Let's fix this oversight to match what the spec is expecting. [1] https://github.com/devicetree-org/devicetree-specification/releases/download/v0.4/devicetree-specification-v0.4.pdf 2.2.1 Node Names Fixes: a31dfc060a74 ("arm64: dts: rockchip: Add nodes for NPU and its MMU to rk3588-base") Cc: stable@vger.kernel.org Signed-off-by: Quentin Schulz Link: https://patch.msgid.link/20251215-npu-dt-node-address-v1-1-840093e8a2bf@cherry.de Signed-off-by: Heiko Stuebner Signed-off-by: Greg Kroah-Hartman --- arch/arm64/boot/dts/rockchip/rk3588-base.dtsi | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/arch/arm64/boot/dts/rockchip/rk3588-base.dtsi b/arch/arm64/boot/dts/rockchip/rk3588-base.dtsi index e2500e31c434a8..2973f6bae17165 100644 --- a/arch/arm64/boot/dts/rockchip/rk3588-base.dtsi +++ b/arch/arm64/boot/dts/rockchip/rk3588-base.dtsi @@ -1200,7 +1200,7 @@ status = "disabled"; }; - rknn_mmu_1: iommu@fdac9000 { + rknn_mmu_1: iommu@fdaca000 { compatible = "rockchip,rk3588-iommu", "rockchip,rk3568-iommu"; reg = <0x0 0xfdaca000 0x0 0x100>; interrupts = ; @@ -1230,7 +1230,7 @@ status = "disabled"; }; - rknn_mmu_2: iommu@fdad9000 { + rknn_mmu_2: iommu@fdada000 { compatible = "rockchip,rk3588-iommu", "rockchip,rk3568-iommu"; reg = <0x0 0xfdada000 0x0 0x100>; interrupts = ; From df546b33115821f00718b045dca5d7c03591e26d Mon Sep 17 00:00:00 2001 From: Alexey Charkov Date: Mon, 29 Dec 2025 14:11:58 +0400 Subject: [PATCH 2688/3902] arm64: dts: rockchip: Fix headphones widget name on NanoPi M5 commit 5ab3dd9d0a63af66377f58633fec9dad650e6827 upstream. Fix the mismatch between the simple-audio-card routing table vs. widget names, which caused the following error at boot preventing the sound card from getting added: [ 6.625634] asoc-simple-card sound: ASoC: DAPM unknown pin Headphones [ 6.627247] asoc-simple-card sound: ASoC: Failed to add route HPOL -> Headphones(*) [ 6.627988] asoc-simple-card sound: ASoC: Failed to add route HPOR -> Headphones(*) Fixes: 96cbdfdd3ac2 ("arm64: dts: rockchip: Add FriendlyElec NanoPi M5 support") Cc: stable@vger.kernel.org Signed-off-by: Alexey Charkov Link: https://patch.msgid.link/20251229-rk3576-sound-v1-1-2f59ef0d19b1@gmail.com Signed-off-by: Heiko Stuebner Signed-off-by: Greg Kroah-Hartman --- arch/arm64/boot/dts/rockchip/rk3576-nanopi-m5.dts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/arch/arm64/boot/dts/rockchip/rk3576-nanopi-m5.dts b/arch/arm64/boot/dts/rockchip/rk3576-nanopi-m5.dts index cce34c541f7c5f..37184913f91886 100644 --- a/arch/arm64/boot/dts/rockchip/rk3576-nanopi-m5.dts +++ b/arch/arm64/boot/dts/rockchip/rk3576-nanopi-m5.dts @@ -211,7 +211,7 @@ "Headphones", "HPOR", "IN1P", "Microphone Jack"; simple-audio-card,widgets = - "Headphone", "Headphone Jack", + "Headphone", "Headphones", "Microphone", "Microphone Jack"; simple-audio-card,codec { From 37a63671df78fdf609ebf275036e89407947b328 Mon Sep 17 00:00:00 2001 From: Alexey Charkov Date: Mon, 29 Dec 2025 14:11:59 +0400 Subject: [PATCH 2689/3902] arm64: dts: rockchip: Configure MCLK for analog sound on NanoPi M5 commit 3e4a81881c0929b21a0577bc6e69514c09da5c3f upstream. NanoPi M5 derives its analog sound signal from SAI2 in M0 pin mode, so the MCLK pin should be configured accordingly for the sound codec to get its I2S signal from the SoC. Request the required pin config. The clock itself should also be CLK_SAI2_MCLKOUT_TO_IO for the sound to work (otherwise there is only silence out of the audio out jack). Fixes: 96cbdfdd3ac2 ("arm64: dts: rockchip: Add FriendlyElec NanoPi M5 support") Cc: stable@vger.kernel.org Signed-off-by: Alexey Charkov Link: https://patch.msgid.link/20251229-rk3576-sound-v1-2-2f59ef0d19b1@gmail.com Signed-off-by: Heiko Stuebner Signed-off-by: Greg Kroah-Hartman --- arch/arm64/boot/dts/rockchip/rk3576-nanopi-m5.dts | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/arch/arm64/boot/dts/rockchip/rk3576-nanopi-m5.dts b/arch/arm64/boot/dts/rockchip/rk3576-nanopi-m5.dts index 37184913f91886..bb2cc2814b83f2 100644 --- a/arch/arm64/boot/dts/rockchip/rk3576-nanopi-m5.dts +++ b/arch/arm64/boot/dts/rockchip/rk3576-nanopi-m5.dts @@ -201,6 +201,7 @@ pinctrl-names = "default"; pinctrl-0 = <&hp_det_l>; + simple-audio-card,bitclock-master = <&masterdai>; simple-audio-card,format = "i2s"; simple-audio-card,hp-det-gpios = <&gpio2 RK_PD6 GPIO_ACTIVE_LOW>; simple-audio-card,mclk-fs = <256>; @@ -218,8 +219,9 @@ sound-dai = <&rt5616>; }; - simple-audio-card,cpu { + masterdai: simple-audio-card,cpu { sound-dai = <&sai2>; + system-clock-frequency = <12288000>; }; }; }; @@ -727,10 +729,12 @@ rt5616: audio-codec@1b { compatible = "realtek,rt5616"; reg = <0x1b>; - assigned-clocks = <&cru CLK_SAI2_MCLKOUT>; + assigned-clocks = <&cru CLK_SAI2_MCLKOUT_TO_IO>; assigned-clock-rates = <12288000>; - clocks = <&cru CLK_SAI2_MCLKOUT>; + clocks = <&cru CLK_SAI2_MCLKOUT_TO_IO>; clock-names = "mclk"; + pinctrl-0 = <&sai2m0_mclk>; + pinctrl-names = "default"; #sound-dai-cells = <0>; }; }; From f60ba4a97ae3f94e4818722ed2e4d260bbb17b44 Mon Sep 17 00:00:00 2001 From: Swaraj Gaikwad Date: Tue, 13 Jan 2026 20:36:39 +0530 Subject: [PATCH 2690/3902] slab: fix kmalloc_nolock() context check for PREEMPT_RT commit 99a3e3a1cfc93b8fe318c0a3a5cfb01f1d4ad53c upstream. On PREEMPT_RT kernels, local_lock becomes a sleeping lock. The current check in kmalloc_nolock() only verifies we're not in NMI or hard IRQ context, but misses the case where preemption is disabled. When a BPF program runs from a tracepoint with preemption disabled (preempt_count > 0), kmalloc_nolock() proceeds to call local_lock_irqsave() which attempts to acquire a sleeping lock, triggering: BUG: sleeping function called from invalid context in_atomic(): 1, irqs_disabled(): 0, non_block: 0, pid: 6128 preempt_count: 2, expected: 0 Fix this by checking !preemptible() on PREEMPT_RT, which directly expresses the constraint that we cannot take a sleeping lock when preemption is disabled. This encompasses the previous checks for NMI and hard IRQ contexts while also catching cases where preemption is disabled. Fixes: af92793e52c3 ("slab: Introduce kmalloc_nolock() and kfree_nolock().") Reported-by: syzbot+b1546ad4a95331b2101e@syzkaller.appspotmail.com Closes: https://syzkaller.appspot.com/bug?extid=b1546ad4a95331b2101e Signed-off-by: Swaraj Gaikwad Acked-by: Sebastian Andrzej Siewior Acked-by: Alexei Starovoitov Acked-by: Harry Yoo Link: https://patch.msgid.link/20260113150639.48407-1-swarajgaikwad1925@gmail.co Cc: Signed-off-by: Vlastimil Babka Signed-off-by: Greg Kroah-Hartman --- mm/slub.c | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/mm/slub.c b/mm/slub.c index 507f346102256f..559cb5f2be16cf 100644 --- a/mm/slub.c +++ b/mm/slub.c @@ -5692,8 +5692,12 @@ void *kmalloc_nolock_noprof(size_t size, gfp_t gfp_flags, int node) if (unlikely(!size)) return ZERO_SIZE_PTR; - if (IS_ENABLED(CONFIG_PREEMPT_RT) && (in_nmi() || in_hardirq())) - /* kmalloc_nolock() in PREEMPT_RT is not supported from irq */ + if (IS_ENABLED(CONFIG_PREEMPT_RT) && !preemptible()) + /* + * kmalloc_nolock() in PREEMPT_RT is not supported from + * non-preemptible context because local_lock becomes a + * sleeping lock on RT. + */ return NULL; retry: if (unlikely(size > KMALLOC_MAX_CACHE_SIZE)) From 930114425065f7ace6e0c0630fab4af75e059ea8 Mon Sep 17 00:00:00 2001 From: David Howells Date: Wed, 14 Jan 2026 22:03:23 +0000 Subject: [PATCH 2691/3902] rxrpc: Fix recvmsg() unconditional requeue commit 2c28769a51deb6022d7fbd499987e237a01dd63a upstream. If rxrpc_recvmsg() fails because MSG_DONTWAIT was specified but the call at the front of the recvmsg queue already has its mutex locked, it requeues the call - whether or not the call is already queued. The call may be on the queue because MSG_PEEK was also passed and so the call was not dequeued or because the I/O thread requeued it. The unconditional requeue may then corrupt the recvmsg queue, leading to things like UAFs or refcount underruns. Fix this by only requeuing the call if it isn't already on the queue - and moving it to the front if it is already queued. If we don't queue it, we have to put the ref we obtained by dequeuing it. Also, MSG_PEEK doesn't dequeue the call so shouldn't call rxrpc_notify_socket() for the call if we didn't use up all the data on the queue, so fix that also. Fixes: 540b1c48c37a ("rxrpc: Fix deadlock between call creation and sendmsg/recvmsg") Reported-by: Faith Reported-by: Pumpkin Chang Signed-off-by: David Howells Acked-by: Marc Dionne cc: Nir Ohfeld cc: Willy Tarreau cc: Simon Horman cc: linux-afs@lists.infradead.org cc: stable@kernel.org Link: https://patch.msgid.link/95163.1768428203@warthog.procyon.org.uk Signed-off-by: Jakub Kicinski Signed-off-by: Greg Kroah-Hartman --- include/trace/events/rxrpc.h | 4 ++++ net/rxrpc/recvmsg.c | 19 +++++++++++++++---- 2 files changed, 19 insertions(+), 4 deletions(-) diff --git a/include/trace/events/rxrpc.h b/include/trace/events/rxrpc.h index de6f6d25767c6e..869f97c9bf733e 100644 --- a/include/trace/events/rxrpc.h +++ b/include/trace/events/rxrpc.h @@ -322,6 +322,7 @@ EM(rxrpc_call_put_kernel, "PUT kernel ") \ EM(rxrpc_call_put_poke, "PUT poke ") \ EM(rxrpc_call_put_recvmsg, "PUT recvmsg ") \ + EM(rxrpc_call_put_recvmsg_peek_nowait, "PUT peek-nwt") \ EM(rxrpc_call_put_release_recvmsg_q, "PUT rls-rcmq") \ EM(rxrpc_call_put_release_sock, "PUT rls-sock") \ EM(rxrpc_call_put_release_sock_tba, "PUT rls-sk-a") \ @@ -340,6 +341,9 @@ EM(rxrpc_call_see_input, "SEE input ") \ EM(rxrpc_call_see_notify_released, "SEE nfy-rlsd") \ EM(rxrpc_call_see_recvmsg, "SEE recvmsg ") \ + EM(rxrpc_call_see_recvmsg_requeue, "SEE recv-rqu") \ + EM(rxrpc_call_see_recvmsg_requeue_first, "SEE recv-rqF") \ + EM(rxrpc_call_see_recvmsg_requeue_move, "SEE recv-rqM") \ EM(rxrpc_call_see_release, "SEE release ") \ EM(rxrpc_call_see_userid_exists, "SEE u-exists") \ EM(rxrpc_call_see_waiting_call, "SEE q-conn ") \ diff --git a/net/rxrpc/recvmsg.c b/net/rxrpc/recvmsg.c index 7fa7e77f6bb990..e1f7513a46dbe3 100644 --- a/net/rxrpc/recvmsg.c +++ b/net/rxrpc/recvmsg.c @@ -518,7 +518,8 @@ int rxrpc_recvmsg(struct socket *sock, struct msghdr *msg, size_t len, if (rxrpc_call_has_failed(call)) goto call_failed; - if (!skb_queue_empty(&call->recvmsg_queue)) + if (!(flags & MSG_PEEK) && + !skb_queue_empty(&call->recvmsg_queue)) rxrpc_notify_socket(call); goto not_yet_complete; @@ -549,11 +550,21 @@ int rxrpc_recvmsg(struct socket *sock, struct msghdr *msg, size_t len, error_requeue_call: if (!(flags & MSG_PEEK)) { spin_lock_irq(&rx->recvmsg_lock); - list_add(&call->recvmsg_link, &rx->recvmsg_q); - spin_unlock_irq(&rx->recvmsg_lock); + if (list_empty(&call->recvmsg_link)) { + list_add(&call->recvmsg_link, &rx->recvmsg_q); + rxrpc_see_call(call, rxrpc_call_see_recvmsg_requeue); + spin_unlock_irq(&rx->recvmsg_lock); + } else if (list_is_first(&call->recvmsg_link, &rx->recvmsg_q)) { + spin_unlock_irq(&rx->recvmsg_lock); + rxrpc_put_call(call, rxrpc_call_see_recvmsg_requeue_first); + } else { + list_move(&call->recvmsg_link, &rx->recvmsg_q); + spin_unlock_irq(&rx->recvmsg_lock); + rxrpc_put_call(call, rxrpc_call_see_recvmsg_requeue_move); + } trace_rxrpc_recvmsg(call_debug_id, rxrpc_recvmsg_requeue, 0); } else { - rxrpc_put_call(call, rxrpc_call_put_recvmsg); + rxrpc_put_call(call, rxrpc_call_put_recvmsg_peek_nowait); } error_no_call: release_sock(&rx->sk); From 3fe8abec037f51ddc2d978321d5aa53c39ab43e4 Mon Sep 17 00:00:00 2001 From: Andrew Cooper Date: Tue, 6 Jan 2026 18:04:26 +0000 Subject: [PATCH 2692/3902] x86/kfence: avoid writing L1TF-vulnerable PTEs commit b505f1944535f83d369ae68813e7634d11b990d3 upstream. For native, the choice of PTE is fine. There's real memory backing the non-present PTE. However, for XenPV, Xen complains: (XEN) d1 L1TF-vulnerable L1e 8010000018200066 - Shadowing To explain, some background on XenPV pagetables: Xen PV guests are control their own pagetables; they choose the new PTE value, and use hypercalls to make changes so Xen can audit for safety. In addition to a regular reference count, Xen also maintains a type reference count. e.g. SegDesc (referenced by vGDT/vLDT), Writable (referenced with _PAGE_RW) or L{1..4} (referenced by vCR3 or a lower pagetable level). This is in order to prevent e.g. a page being inserted into the pagetables for which the guest has a writable mapping. For non-present mappings, all other bits become software accessible, and typically contain metadata rather a real frame address. There is nothing that a reference count could sensibly be tied to. As such, even if Xen could recognise the address as currently safe, nothing would prevent that frame from changing owner to another VM in the future. When Xen detects a PV guest writing a L1TF-PTE, it responds by activating shadow paging. This is normally only used for the live phase of migration, and comes with a reasonable overhead. KFENCE only cares about getting #PF to catch wild accesses; it doesn't care about the value for non-present mappings. Use a fully inverted PTE, to avoid hitting the slow path when running under Xen. While adjusting the logic, take the opportunity to skip all actions if the PTE is already in the right state, half the number PVOps callouts, and skip TLB maintenance on a !P -> P transition which benefits non-Xen cases too. Link: https://lkml.kernel.org/r/20260106180426.710013-1-andrew.cooper3@citrix.com Fixes: 1dc0da6e9ec0 ("x86, kfence: enable KFENCE for x86") Signed-off-by: Andrew Cooper Tested-by: Marco Elver Cc: Alexander Potapenko Cc: Marco Elver Cc: Dmitry Vyukov Cc: Thomas Gleixner Cc: Ingo Molnar Cc: Borislav Petkov Cc: Dave Hansen Cc: "H. Peter Anvin" Cc: Jann Horn Cc: Signed-off-by: Andrew Morton Signed-off-by: Greg Kroah-Hartman --- arch/x86/include/asm/kfence.h | 29 ++++++++++++++++++++++++----- 1 file changed, 24 insertions(+), 5 deletions(-) diff --git a/arch/x86/include/asm/kfence.h b/arch/x86/include/asm/kfence.h index ff5c7134a37aa1..acf9ffa1a17183 100644 --- a/arch/x86/include/asm/kfence.h +++ b/arch/x86/include/asm/kfence.h @@ -42,10 +42,34 @@ static inline bool kfence_protect_page(unsigned long addr, bool protect) { unsigned int level; pte_t *pte = lookup_address(addr, &level); + pteval_t val; if (WARN_ON(!pte || level != PG_LEVEL_4K)) return false; + val = pte_val(*pte); + + /* + * protect requires making the page not-present. If the PTE is + * already in the right state, there's nothing to do. + */ + if (protect != !!(val & _PAGE_PRESENT)) + return true; + + /* + * Otherwise, invert the entire PTE. This avoids writing out an + * L1TF-vulnerable PTE (not present, without the high address bits + * set). + */ + set_pte(pte, __pte(~val)); + + /* + * If the page was protected (non-present) and we're making it + * present, there is no need to flush the TLB at all. + */ + if (!protect) + return true; + /* * We need to avoid IPIs, as we may get KFENCE allocations or faults * with interrupts disabled. Therefore, the below is best-effort, and @@ -53,11 +77,6 @@ static inline bool kfence_protect_page(unsigned long addr, bool protect) * lazy fault handling takes care of faults after the page is PRESENT. */ - if (protect) - set_pte(pte, __pte(pte_val(*pte) & ~_PAGE_PRESENT)); - else - set_pte(pte, __pte(pte_val(*pte) | _PAGE_PRESENT)); - /* * Flush this CPU's TLB, assuming whoever did the allocation/free is * likely to continue running on this CPU. From ec56b9f1c1b9bc1044ef39e9f217395a45a13c3c Mon Sep 17 00:00:00 2001 From: Ian Abbott Date: Wed, 3 Dec 2025 16:24:38 +0000 Subject: [PATCH 2693/3902] comedi: Fix getting range information for subdevices 16 to 255 commit 10d28cffb3f6ec7ad67f0a4cd32c2afa92909452 upstream. The `COMEDI_RANGEINFO` ioctl does not work properly for subdevice indices above 15. Currently, the only in-tree COMEDI drivers that support more than 16 subdevices are the "8255" driver and the "comedi_bond" driver. Making the ioctl work for subdevice indices up to 255 is achievable. It needs minor changes to the handling of the `COMEDI_RANGEINFO` and `COMEDI_CHANINFO` ioctls that should be mostly harmless to user-space, apart from making them less broken. Details follow... The `COMEDI_RANGEINFO` ioctl command gets the list of supported ranges (usually with units of volts or milliamps) for a COMEDI subdevice or channel. (Only some subdevices have per-channel range tables, indicated by the `SDF_RANGETYPE` flag in the subdevice information.) It uses a `range_type` value and a user-space pointer, both supplied by user-space, but the `range_type` value should match what was obtained using the `COMEDI_CHANINFO` ioctl (if the subdevice has per-channel range tables) or `COMEDI_SUBDINFO` ioctl (if the subdevice uses a single range table for all channels). Bits 15 to 0 of the `range_type` value contain the length of the range table, which is the only part that user-space should care about (so it can use a suitably sized buffer to fetch the range table). Bits 23 to 16 store the channel index, which is assumed to be no more than 255 if the subdevice has per-channel range tables, and is set to 0 if the subdevice has a single range table. For `range_type` values produced by the `COMEDI_SUBDINFO` ioctl, bits 31 to 24 contain the subdevice index, which is assumed to be no more than 255. But for `range_type` values produced by the `COMEDI_CHANINFO` ioctl, bits 27 to 24 contain the subdevice index, which is assumed to be no more than 15, and bits 31 to 28 contain the COMEDI device's minor device number for some unknown reason lost in the mists of time. The `COMEDI_RANGEINFO` ioctl extract the length from bits 15 to 0 of the user-supplied `range_type` value, extracts the channel index from bits 23 to 16 (only used if the subdevice has per-channel range tables), extracts the subdevice index from bits 27 to 24, and ignores bits 31 to 28. So for subdevice indices 16 to 255, the `COMEDI_SUBDINFO` or `COMEDI_CHANINFO` ioctl will report a `range_type` value that doesn't work with the `COMEDI_RANGEINFO` ioctl. It will either get the range table for the subdevice index modulo 16, or will fail with `-EINVAL`. To fix this, always use bits 31 to 24 of the `range_type` value to hold the subdevice index (assumed to be no more than 255). This affects the `COMEDI_CHANINFO` and `COMEDI_RANGEINFO` ioctls. There should not be anything in user-space that depends on the old, broken usage, although it may now see different values in bits 31 to 28 of the `range_type` values reported by the `COMEDI_CHANINFO` ioctl for subdevices that have per-channel subdevices. User-space should not be trying to decode bits 31 to 16 of the `range_type` values anyway. Fixes: ed9eccbe8970 ("Staging: add comedi core") Cc: stable@vger.kernel.org #5.17+ Signed-off-by: Ian Abbott Link: https://patch.msgid.link/20251203162438.176841-1-abbotti@mev.co.uk Signed-off-by: Greg Kroah-Hartman Signed-off-by: Greg Kroah-Hartman --- drivers/comedi/comedi_fops.c | 2 +- drivers/comedi/range.c | 2 +- include/uapi/linux/comedi.h | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/drivers/comedi/comedi_fops.c b/drivers/comedi/comedi_fops.c index b2e62e04afd994..8c5507d78914f7 100644 --- a/drivers/comedi/comedi_fops.c +++ b/drivers/comedi/comedi_fops.c @@ -1099,7 +1099,7 @@ static int do_chaninfo_ioctl(struct comedi_device *dev, for (i = 0; i < s->n_chan; i++) { int x; - x = (dev->minor << 28) | (it->subdev << 24) | (i << 16) | + x = (it->subdev << 24) | (i << 16) | (s->range_table_list[i]->length); if (put_user(x, it->rangelist + i)) return -EFAULT; diff --git a/drivers/comedi/range.c b/drivers/comedi/range.c index 8f43cf88d784bf..5b8f662365e356 100644 --- a/drivers/comedi/range.c +++ b/drivers/comedi/range.c @@ -52,7 +52,7 @@ int do_rangeinfo_ioctl(struct comedi_device *dev, const struct comedi_lrange *lr; struct comedi_subdevice *s; - subd = (it->range_type >> 24) & 0xf; + subd = (it->range_type >> 24) & 0xff; chan = (it->range_type >> 16) & 0xff; if (!dev->attached) diff --git a/include/uapi/linux/comedi.h b/include/uapi/linux/comedi.h index 7314e5ee0a1e8d..798ec9a39e1298 100644 --- a/include/uapi/linux/comedi.h +++ b/include/uapi/linux/comedi.h @@ -640,7 +640,7 @@ struct comedi_chaninfo { /** * struct comedi_rangeinfo - used to retrieve the range table for a channel - * @range_type: Encodes subdevice index (bits 27:24), channel index + * @range_type: Encodes subdevice index (bits 31:24), channel index * (bits 23:16) and range table length (bits 15:0). * @range_ptr: Pointer to array of @struct comedi_krange to be filled * in with the range table for the channel or subdevice. From 3f4ed5e2b8f111553562507ad6202432c7c57731 Mon Sep 17 00:00:00 2001 From: Joanne Koong Date: Mon, 5 Jan 2026 13:17:27 -0800 Subject: [PATCH 2694/3902] fs/writeback: skip AS_NO_DATA_INTEGRITY mappings in wait_sb_inodes() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit commit f9a49aa302a05e91ca01f69031cb79a0ea33031f upstream. Above the while() loop in wait_sb_inodes(), we document that we must wait for all pages under writeback for data integrity. Consequently, if a mapping, like fuse, traditionally does not have data integrity semantics, there is no need to wait at all; we can simply skip these inodes. This restores fuse back to prior behavior where syncs are no-ops. This fixes a user regression where if a system is running a faulty fuse server that does not reply to issued write requests, this causes wait_sb_inodes() to wait forever. Link: https://lkml.kernel.org/r/20260105211737.4105620-2-joannelkoong@gmail.com Fixes: 0c58a97f919c ("fuse: remove tmp folio for writebacks and internal rb tree") Signed-off-by: Joanne Koong Reported-by: Athul Krishna Reported-by: J. Neuschäfer Reviewed-by: Bernd Schubert Tested-by: J. Neuschäfer Cc: Alexander Viro Cc: Bernd Schubert Cc: Bonaccorso Salvatore Cc: Christian Brauner Cc: David Hildenbrand Cc: Jan Kara Cc: "Liam R. Howlett" Cc: Lorenzo Stoakes Cc: "Matthew Wilcox (Oracle)" Cc: Michal Hocko Cc: Mike Rapoport Cc: Miklos Szeredi Cc: Suren Baghdasaryan Cc: Vlastimil Babka Cc: Signed-off-by: Andrew Morton Signed-off-by: Greg Kroah-Hartman --- fs/fs-writeback.c | 7 ++++++- fs/fuse/file.c | 4 +++- include/linux/pagemap.h | 11 +++++++++++ 3 files changed, 20 insertions(+), 2 deletions(-) diff --git a/fs/fs-writeback.c b/fs/fs-writeback.c index 2b35e80037feed..fedccb991674db 100644 --- a/fs/fs-writeback.c +++ b/fs/fs-writeback.c @@ -2729,8 +2729,13 @@ static void wait_sb_inodes(struct super_block *sb) * The mapping can appear untagged while still on-list since we * do not have the mapping lock. Skip it here, wb completion * will remove it. + * + * If the mapping does not have data integrity semantics, + * there's no need to wait for the writeout to complete, as the + * mapping cannot guarantee that data is persistently stored. */ - if (!mapping_tagged(mapping, PAGECACHE_TAG_WRITEBACK)) + if (!mapping_tagged(mapping, PAGECACHE_TAG_WRITEBACK) || + mapping_no_data_integrity(mapping)) continue; spin_unlock_irq(&sb->s_inode_wblist_lock); diff --git a/fs/fuse/file.c b/fs/fuse/file.c index 6014d588845cd6..37620fdd02055e 100644 --- a/fs/fuse/file.c +++ b/fs/fuse/file.c @@ -3146,8 +3146,10 @@ void fuse_init_file_inode(struct inode *inode, unsigned int flags) inode->i_fop = &fuse_file_operations; inode->i_data.a_ops = &fuse_file_aops; - if (fc->writeback_cache) + if (fc->writeback_cache) { mapping_set_writeback_may_deadlock_on_reclaim(&inode->i_data); + mapping_set_no_data_integrity(&inode->i_data); + } INIT_LIST_HEAD(&fi->write_files); INIT_LIST_HEAD(&fi->queued_writes); diff --git a/include/linux/pagemap.h b/include/linux/pagemap.h index 09b581c1d878d3..e3534d573ebc42 100644 --- a/include/linux/pagemap.h +++ b/include/linux/pagemap.h @@ -213,6 +213,7 @@ enum mapping_flags { AS_WRITEBACK_MAY_DEADLOCK_ON_RECLAIM = 9, AS_KERNEL_FILE = 10, /* mapping for a fake kernel file that shouldn't account usage to user cgroups */ + AS_NO_DATA_INTEGRITY = 11, /* no data integrity guarantees */ /* Bits 16-25 are used for FOLIO_ORDER */ AS_FOLIO_ORDER_BITS = 5, AS_FOLIO_ORDER_MIN = 16, @@ -348,6 +349,16 @@ static inline bool mapping_writeback_may_deadlock_on_reclaim(const struct addres return test_bit(AS_WRITEBACK_MAY_DEADLOCK_ON_RECLAIM, &mapping->flags); } +static inline void mapping_set_no_data_integrity(struct address_space *mapping) +{ + set_bit(AS_NO_DATA_INTEGRITY, &mapping->flags); +} + +static inline bool mapping_no_data_integrity(const struct address_space *mapping) +{ + return test_bit(AS_NO_DATA_INTEGRITY, &mapping->flags); +} + static inline gfp_t mapping_gfp_mask(const struct address_space *mapping) { return mapping->gfp_mask; From 83eae3692c353f8eb645c539007b2209de8a4735 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20Hellstr=C3=B6m?= Date: Wed, 21 Jan 2026 10:10:47 +0100 Subject: [PATCH 2695/3902] drm, drm/xe: Fix xe userptr in the absence of CONFIG_DEVICE_PRIVATE MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit commit bdcdf968be314b6fc8835b99fb4519e7619671e6 upstream. CONFIG_DEVICE_PRIVATE is not selected by default by some distros, for example Fedora, and that leads to a regression in the xe driver since userptr support gets compiled out. It turns out that DRM_GPUSVM, which is needed for xe userptr support compiles also without CONFIG_DEVICE_PRIVATE, but doesn't compile without CONFIG_ZONE_DEVICE. Exclude the drm_pagemap files from compilation with !CONFIG_ZONE_DEVICE, and remove the CONFIG_DEVICE_PRIVATE dependency from CONFIG_DRM_GPUSVM and the xe driver's selection of it, re-enabling xe userptr for those configs. v2: - Don't compile the drm_pagemap files unless CONFIG_ZONE_DEVICE is set. - Adjust the drm_pagemap.h header accordingly. Fixes: 9e9787414882 ("drm/xe/userptr: replace xe_hmm with gpusvm") Cc: Matthew Auld Cc: Himal Prasad Ghimiray Cc: Thomas Hellström Cc: Matthew Brost Cc: "Thomas Hellström" Cc: Rodrigo Vivi Cc: dri-devel@lists.freedesktop.org Cc: # v6.18+ Signed-off-by: Thomas Hellström Reviewed-by: Matthew Auld Acked-by: Maarten Lankhorst Link: https://patch.msgid.link/20260121091048.41371-2-thomas.hellstrom@linux.intel.com (cherry picked from commit 1e372b246199ca7a35f930177fea91b557dac16e) Signed-off-by: Thomas Hellström Signed-off-by: Greg Kroah-Hartman --- drivers/gpu/drm/Kconfig | 2 +- drivers/gpu/drm/Makefile | 4 +++- drivers/gpu/drm/xe/Kconfig | 2 +- include/drm/drm_pagemap.h | 19 +++++++++++++++++-- 4 files changed, 22 insertions(+), 5 deletions(-) diff --git a/drivers/gpu/drm/Kconfig b/drivers/gpu/drm/Kconfig index 7e6bc0b3a589ce..ed85d0ceee3ba5 100644 --- a/drivers/gpu/drm/Kconfig +++ b/drivers/gpu/drm/Kconfig @@ -210,7 +210,7 @@ config DRM_GPUVM config DRM_GPUSVM tristate - depends on DRM && DEVICE_PRIVATE + depends on DRM select HMM_MIRROR select MMU_NOTIFIER help diff --git a/drivers/gpu/drm/Makefile b/drivers/gpu/drm/Makefile index da2565e6de71d8..742f0d590c5afa 100644 --- a/drivers/gpu/drm/Makefile +++ b/drivers/gpu/drm/Makefile @@ -106,8 +106,10 @@ obj-$(CONFIG_DRM_EXEC) += drm_exec.o obj-$(CONFIG_DRM_GPUVM) += drm_gpuvm.o drm_gpusvm_helper-y := \ - drm_gpusvm.o\ + drm_gpusvm.o +drm_gpusvm_helper-$(CONFIG_ZONE_DEVICE) += \ drm_pagemap.o + obj-$(CONFIG_DRM_GPUSVM) += drm_gpusvm_helper.o obj-$(CONFIG_DRM_BUDDY) += drm_buddy.o diff --git a/drivers/gpu/drm/xe/Kconfig b/drivers/gpu/drm/xe/Kconfig index 4b288eb3f5b0e5..c34be1be155b39 100644 --- a/drivers/gpu/drm/xe/Kconfig +++ b/drivers/gpu/drm/xe/Kconfig @@ -39,7 +39,7 @@ config DRM_XE select DRM_TTM select DRM_TTM_HELPER select DRM_EXEC - select DRM_GPUSVM if !UML && DEVICE_PRIVATE + select DRM_GPUSVM if !UML select DRM_GPUVM select DRM_SCHED select MMU_NOTIFIER diff --git a/include/drm/drm_pagemap.h b/include/drm/drm_pagemap.h index 70a7991f784f9b..eb29e5309f0ab6 100644 --- a/include/drm/drm_pagemap.h +++ b/include/drm/drm_pagemap.h @@ -209,6 +209,19 @@ struct drm_pagemap_devmem_ops { struct dma_fence *pre_migrate_fence); }; +#if IS_ENABLED(CONFIG_ZONE_DEVICE) + +struct drm_pagemap *drm_pagemap_page_to_dpagemap(struct page *page); + +#else + +static inline struct drm_pagemap *drm_pagemap_page_to_dpagemap(struct page *page) +{ + return NULL; +} + +#endif /* IS_ENABLED(CONFIG_ZONE_DEVICE) */ + /** * struct drm_pagemap_devmem - Structure representing a GPU SVM device memory allocation * @@ -233,6 +246,8 @@ struct drm_pagemap_devmem { struct dma_fence *pre_migrate_fence; }; +#if IS_ENABLED(CONFIG_ZONE_DEVICE) + int drm_pagemap_migrate_to_devmem(struct drm_pagemap_devmem *devmem_allocation, struct mm_struct *mm, unsigned long start, unsigned long end, @@ -243,8 +258,6 @@ int drm_pagemap_evict_to_ram(struct drm_pagemap_devmem *devmem_allocation); const struct dev_pagemap_ops *drm_pagemap_pagemap_ops_get(void); -struct drm_pagemap *drm_pagemap_page_to_dpagemap(struct page *page); - void drm_pagemap_devmem_init(struct drm_pagemap_devmem *devmem_allocation, struct device *dev, struct mm_struct *mm, const struct drm_pagemap_devmem_ops *ops, @@ -256,4 +269,6 @@ int drm_pagemap_populate_mm(struct drm_pagemap *dpagemap, struct mm_struct *mm, unsigned long timeslice_ms); +#endif /* IS_ENABLED(CONFIG_ZONE_DEVICE) */ + #endif From 800b2767905d6b409b8bbe357121970f0b489a89 Mon Sep 17 00:00:00 2001 From: Mario Limonciello Date: Thu, 15 Jan 2026 14:31:10 -0600 Subject: [PATCH 2696/3902] platform/x86: hp-bioscfg: Fix kobject warnings for empty attribute names MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit commit fdee1b09721605f532352628d0a24623e7062efb upstream. The hp-bioscfg driver attempts to register kobjects with empty names when the HP BIOS returns attributes with empty name strings. This causes multiple kernel warnings: kobject: (00000000135fb5e6): attempted to be registered with empty name! WARNING: CPU: 14 PID: 3336 at lib/kobject.c:219 kobject_add_internal+0x2eb/0x310 Add validation in hp_init_bios_buffer_attribute() to check if the attribute name is empty after parsing it from the WMI buffer. If empty, log a debug message and skip registration of that attribute, allowing the module to continue processing other valid attributes. Cc: stable@vger.kernel.org Fixes: a34fc329b189 ("platform/x86: hp-bioscfg: bioscfg") Signed-off-by: Mario Limonciello Link: https://patch.msgid.link/20260115203725.828434-2-mario.limonciello@amd.com Reviewed-by: Ilpo Järvinen Signed-off-by: Ilpo Järvinen Signed-off-by: Greg Kroah-Hartman --- drivers/platform/x86/hp/hp-bioscfg/bioscfg.c | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/drivers/platform/x86/hp/hp-bioscfg/bioscfg.c b/drivers/platform/x86/hp/hp-bioscfg/bioscfg.c index 5bfa7159f5bcd5..dbe096eefa7582 100644 --- a/drivers/platform/x86/hp/hp-bioscfg/bioscfg.c +++ b/drivers/platform/x86/hp/hp-bioscfg/bioscfg.c @@ -10,6 +10,8 @@ #include #include #include +#include +#include #include #include "bioscfg.h" #include "../../firmware_attributes_class.h" @@ -781,6 +783,12 @@ static int hp_init_bios_buffer_attribute(enum hp_wmi_data_type attr_type, if (ret < 0) goto buff_attr_exit; + if (strlen(str) == 0) { + pr_debug("Ignoring attribute with empty name\n"); + ret = 0; + goto buff_attr_exit; + } + if (attr_type == HPWMI_PASSWORD_TYPE || attr_type == HPWMI_SECURE_PLATFORM_TYPE) temp_kset = bioscfg_drv.authentication_dir_kset; From 193922a23d7294085a47d7719fdb7d66ad0a236f Mon Sep 17 00:00:00 2001 From: Mario Limonciello Date: Thu, 15 Jan 2026 14:31:11 -0600 Subject: [PATCH 2697/3902] platform/x86: hp-bioscfg: Fix kernel panic in GET_INSTANCE_ID macro MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit commit 25150715e0b049b99df664daf05dab12f41c3e13 upstream. The GET_INSTANCE_ID macro that caused a kernel panic when accessing sysfs attributes: 1. Off-by-one error: The loop condition used '<=' instead of '<', causing access beyond array bounds. Since array indices are 0-based and go from 0 to instances_count-1, the loop should use '<'. 2. Missing NULL check: The code dereferenced attr_name_kobj->name without checking if attr_name_kobj was NULL, causing a null pointer dereference in min_length_show() and other attribute show functions. The panic occurred when fwupd tried to read BIOS configuration attributes: Oops: general protection fault [#1] SMP KASAN NOPTI KASAN: null-ptr-deref in range [0x0000000000000000-0x0000000000000007] RIP: 0010:min_length_show+0xcf/0x1d0 [hp_bioscfg] Add a NULL check for attr_name_kobj before dereferencing and corrects the loop boundary to match the pattern used elsewhere in the driver. Cc: stable@vger.kernel.org Fixes: 5f94f181ca25 ("platform/x86: hp-bioscfg: bioscfg-h") Signed-off-by: Mario Limonciello Link: https://patch.msgid.link/20260115203725.828434-3-mario.limonciello@amd.com Reviewed-by: Ilpo Järvinen Signed-off-by: Ilpo Järvinen Signed-off-by: Greg Kroah-Hartman --- drivers/platform/x86/hp/hp-bioscfg/bioscfg.h | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/drivers/platform/x86/hp/hp-bioscfg/bioscfg.h b/drivers/platform/x86/hp/hp-bioscfg/bioscfg.h index 3166ef328ebae8..6b6748e4be2182 100644 --- a/drivers/platform/x86/hp/hp-bioscfg/bioscfg.h +++ b/drivers/platform/x86/hp/hp-bioscfg/bioscfg.h @@ -10,6 +10,7 @@ #include #include +#include #include #include #include @@ -285,8 +286,9 @@ enum hp_wmi_data_elements { { \ int i; \ \ - for (i = 0; i <= bioscfg_drv.type##_instances_count; i++) { \ - if (!strcmp(kobj->name, bioscfg_drv.type##_data[i].attr_name_kobj->name)) \ + for (i = 0; i < bioscfg_drv.type##_instances_count; i++) { \ + if (bioscfg_drv.type##_data[i].attr_name_kobj && \ + !strcmp(kobj->name, bioscfg_drv.type##_data[i].attr_name_kobj->name)) \ return i; \ } \ return -EIO; \ From 69c4e241ff13545d410a8b2a688c932182a858bf Mon Sep 17 00:00:00 2001 From: "David Hildenbrand (Red Hat)" Date: Tue, 23 Dec 2025 22:40:34 +0100 Subject: [PATCH 2698/3902] mm/hugetlb: fix hugetlb_pmd_shared() commit ca1a47cd3f5f4c46ca188b1c9a27af87d1ab2216 upstream. Patch series "mm/hugetlb: fixes for PMD table sharing (incl. using mmu_gather)", v3. One functional fix, one performance regression fix, and two related comment fixes. I cleaned up my prototype I recently shared [1] for the performance fix, deferring most of the cleanups I had in the prototype to a later point. While doing that I identified the other things. The goal of this patch set is to be backported to stable trees "fairly" easily. At least patch #1 and #4. Patch #1 fixes hugetlb_pmd_shared() not detecting any sharing Patch #2 + #3 are simple comment fixes that patch #4 interacts with. Patch #4 is a fix for the reported performance regression due to excessive IPI broadcasts during fork()+exit(). The last patch is all about TLB flushes, IPIs and mmu_gather. Read: complicated There are plenty of cleanups in the future to be had + one reasonable optimization on x86. But that's all out of scope for this series. Runtime tested, with a focus on fixing the performance regression using the original reproducer [2] on x86. This patch (of 4): We switched from (wrongly) using the page count to an independent shared count. Now, shared page tables have a refcount of 1 (excluding speculative references) and instead use ptdesc->pt_share_count to identify sharing. We didn't convert hugetlb_pmd_shared(), so right now, we would never detect a shared PMD table as such, because sharing/unsharing no longer touches the refcount of a PMD table. Page migration, like mbind() or migrate_pages() would allow for migrating folios mapped into such shared PMD tables, even though the folios are not exclusive. In smaps we would account them as "private" although they are "shared", and we would be wrongly setting the PM_MMAP_EXCLUSIVE in the pagemap interface. Fix it by properly using ptdesc_pmd_is_shared() in hugetlb_pmd_shared(). Link: https://lkml.kernel.org/r/20251223214037.580860-1-david@kernel.org Link: https://lkml.kernel.org/r/20251223214037.580860-2-david@kernel.org Link: https://lore.kernel.org/all/8cab934d-4a56-44aa-b641-bfd7e23bd673@kernel.org/ [1] Link: https://lore.kernel.org/all/8cab934d-4a56-44aa-b641-bfd7e23bd673@kernel.org/ [2] Fixes: 59d9094df3d7 ("mm: hugetlb: independent PMD page table shared count") Signed-off-by: David Hildenbrand (Red Hat) Reviewed-by: Rik van Riel Reviewed-by: Lance Yang Tested-by: Lance Yang Reviewed-by: Harry Yoo Tested-by: Laurence Oberman Reviewed-by: Lorenzo Stoakes Acked-by: Oscar Salvador Cc: Liu Shixin Cc: Uschakow, Stanislav" Cc: Signed-off-by: Andrew Morton Signed-off-by: Greg Kroah-Hartman --- include/linux/hugetlb.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/linux/hugetlb.h b/include/linux/hugetlb.h index 8e63e46b8e1f0e..89054f714992fb 100644 --- a/include/linux/hugetlb.h +++ b/include/linux/hugetlb.h @@ -1323,7 +1323,7 @@ static inline __init void hugetlb_cma_reserve(int order) #ifdef CONFIG_HUGETLB_PMD_PAGE_TABLE_SHARING static inline bool hugetlb_pmd_shared(pte_t *pte) { - return page_count(virt_to_page(pte)) > 1; + return ptdesc_pmd_is_shared(virt_to_ptdesc(pte)); } #else static inline bool hugetlb_pmd_shared(pte_t *pte) From f723037e2bfe3c2f83be4e343c1a9a561d3133ed Mon Sep 17 00:00:00 2001 From: "David Hildenbrand (Red Hat)" Date: Tue, 23 Dec 2025 22:40:36 +0100 Subject: [PATCH 2699/3902] mm/rmap: fix two comments related to huge_pmd_unshare() commit a8682d500f691b6dfaa16ae1502d990aeb86e8be upstream. PMD page table unsharing no longer touches the refcount of a PMD page table. Also, it is not about dropping the refcount of a "PMD page" but the "PMD page table". Let's just simplify by saying that the PMD page table was unmapped, consequently also unmapping the folio that was mapped into this page. This code should be deduplicated in the future. Link: https://lkml.kernel.org/r/20251223214037.580860-4-david@kernel.org Fixes: 59d9094df3d7 ("mm: hugetlb: independent PMD page table shared count") Signed-off-by: David Hildenbrand (Red Hat) Reviewed-by: Rik van Riel Tested-by: Laurence Oberman Reviewed-by: Lorenzo Stoakes Acked-by: Oscar Salvador Cc: Liu Shixin Cc: Harry Yoo Cc: Lance Yang Cc: "Uschakow, Stanislav" Cc: Signed-off-by: Andrew Morton Signed-off-by: Greg Kroah-Hartman --- mm/rmap.c | 20 ++++---------------- 1 file changed, 4 insertions(+), 16 deletions(-) diff --git a/mm/rmap.c b/mm/rmap.c index ac4f783d6ec2f5..d52055a026a058 100644 --- a/mm/rmap.c +++ b/mm/rmap.c @@ -2027,14 +2027,8 @@ static bool try_to_unmap_one(struct folio *folio, struct vm_area_struct *vma, flush_tlb_range(vma, range.start, range.end); /* - * The ref count of the PMD page was - * dropped which is part of the way map - * counting is done for shared PMDs. - * Return 'true' here. When there is - * no other sharing, huge_pmd_unshare - * returns false and we will unmap the - * actual page and drop map count - * to zero. + * The PMD table was unmapped, + * consequently unmapping the folio. */ goto walk_done; } @@ -2416,14 +2410,8 @@ static bool try_to_migrate_one(struct folio *folio, struct vm_area_struct *vma, range.start, range.end); /* - * The ref count of the PMD page was - * dropped which is part of the way map - * counting is done for shared PMDs. - * Return 'true' here. When there is - * no other sharing, huge_pmd_unshare - * returns false and we will unmap the - * actual page and drop map count - * to zero. + * The PMD table was unmapped, + * consequently unmapping the folio. */ page_vma_mapped_walk_done(&pvmw); break; From 2fc23eaea11a74b686def2398833d72bbbe78af1 Mon Sep 17 00:00:00 2001 From: Yosry Ahmed Date: Fri, 16 Jan 2026 20:52:47 +0000 Subject: [PATCH 2700/3902] mm: restore per-memcg proactive reclaim with !CONFIG_NUMA commit 16aca2c98a6fdf071e5a1a765a295995d7c7e346 upstream. Commit 2b7226af730c ("mm/memcg: make memory.reclaim interface generic") moved proactive reclaim logic from memory.reclaim handler to a generic user_proactive_reclaim() helper to be used for per-node proactive reclaim. However, user_proactive_reclaim() was only defined under CONFIG_NUMA, with a stub always returning 0 otherwise. This broke memory.reclaim on !CONFIG_NUMA configs, causing it to report success without actually attempting reclaim. Move the definition of user_proactive_reclaim() outside CONFIG_NUMA, and instead define a stub for __node_reclaim() in the !CONFIG_NUMA case. __node_reclaim() is only called from user_proactive_reclaim() when a write is made to sys/devices/system/node/nodeX/reclaim, which is only defined with CONFIG_NUMA. Link: https://lkml.kernel.org/r/20260116205247.928004-1-yosry.ahmed@linux.dev Fixes: 2b7226af730c ("mm/memcg: make memory.reclaim interface generic") Signed-off-by: Yosry Ahmed Acked-by: Shakeel Butt Acked-by: Michal Hocko Cc: Axel Rasmussen Cc: David Hildenbrand Cc: Davidlohr Bueso Cc: Johannes Weiner Cc: Liam Howlett Cc: Lorenzo Stoakes Cc: Mike Rapoport Cc: Qi Zheng Cc: Suren Baghdasaryan Cc: Vlastimil Babka Cc: Wei Xu Cc: Yuanchu Xie Cc: Signed-off-by: Andrew Morton Signed-off-by: Greg Kroah-Hartman --- mm/internal.h | 8 -------- mm/vmscan.c | 13 +++++++++++-- 2 files changed, 11 insertions(+), 10 deletions(-) diff --git a/mm/internal.h b/mm/internal.h index 1561fc2ff5b832..c80c6f566c2d93 100644 --- a/mm/internal.h +++ b/mm/internal.h @@ -538,16 +538,8 @@ extern unsigned long highest_memmap_pfn; bool folio_isolate_lru(struct folio *folio); void folio_putback_lru(struct folio *folio); extern void reclaim_throttle(pg_data_t *pgdat, enum vmscan_throttle_state reason); -#ifdef CONFIG_NUMA int user_proactive_reclaim(char *buf, struct mem_cgroup *memcg, pg_data_t *pgdat); -#else -static inline int user_proactive_reclaim(char *buf, - struct mem_cgroup *memcg, pg_data_t *pgdat) -{ - return 0; -} -#endif /* * in mm/rmap.c: diff --git a/mm/vmscan.c b/mm/vmscan.c index b2fc8b626d3dff..06071995dacc90 100644 --- a/mm/vmscan.c +++ b/mm/vmscan.c @@ -7740,6 +7740,17 @@ int node_reclaim(struct pglist_data *pgdat, gfp_t gfp_mask, unsigned int order) return ret; } +#else + +static unsigned long __node_reclaim(struct pglist_data *pgdat, gfp_t gfp_mask, + unsigned long nr_pages, + struct scan_control *sc) +{ + return 0; +} + +#endif + enum { MEMORY_RECLAIM_SWAPPINESS = 0, MEMORY_RECLAIM_SWAPPINESS_MAX, @@ -7847,8 +7858,6 @@ int user_proactive_reclaim(char *buf, return 0; } -#endif - /** * check_move_unevictable_folios - Move evictable folios to appropriate zone * lru list From 8f7c9dbeaa0be5810e44d323735967d3dba9239d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20Wei=C3=9Fschuh?= Date: Tue, 20 Jan 2026 07:55:55 +0100 Subject: [PATCH 2701/3902] timekeeping: Adjust the leap state for the correct auxiliary timekeeper MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit commit e806f7dde8ba28bc72a7a0898589cac79f6362ac upstream. When __do_ajdtimex() was introduced to handle adjtimex for any timekeeper, this reference to tk_core was not updated. When called on an auxiliary timekeeper, the core timekeeper would be updated incorrectly. This gets caught by the lock debugging diagnostics because the timekeepers sequence lock gets written to without holding its associated spinlock: WARNING: include/linux/seqlock.h:226 at __do_adjtimex+0x394/0x3b0, CPU#2: test/125 aux_clock_adj (kernel/time/timekeeping.c:2979) __do_sys_clock_adjtime (kernel/time/posix-timers.c:1161 kernel/time/posix-timers.c:1173) do_syscall_64 (arch/x86/entry/syscall_64.c:63 (discriminator 1) arch/x86/entry/syscall_64.c:94 (discriminator 1)) entry_SYSCALL_64_after_hwframe (arch/x86/entry/entry_64.S:131) Update the correct auxiliary timekeeper. Fixes: 775f71ebedd3 ("timekeeping: Make do_adjtimex() reusable") Fixes: ecf3e7030491 ("timekeeping: Provide adjtimex() for auxiliary clocks") Signed-off-by: Thomas Weißschuh Signed-off-by: Thomas Gleixner Cc: stable@vger.kernel.org Link: https://patch.msgid.link/20260120-timekeeper-auxclock-leapstate-v1-1-5b358c6b3cfd@linutronix.de Signed-off-by: Greg Kroah-Hartman --- kernel/time/timekeeping.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/kernel/time/timekeeping.c b/kernel/time/timekeeping.c index 4790da895203c2..340fef20bdcd0e 100644 --- a/kernel/time/timekeeping.c +++ b/kernel/time/timekeeping.c @@ -2721,7 +2721,7 @@ static int __do_adjtimex(struct tk_data *tkd, struct __kernel_timex *txc, timekeeping_update_from_shadow(tkd, TK_CLOCK_WAS_SET); result->clock_set = true; } else { - tk_update_leap_state_all(&tk_core); + tk_update_leap_state_all(tkd); } /* Update the multiplier immediately if frequency was set directly */ From bdf0bf73006ea8af9327cdb85cfdff4c23a5f966 Mon Sep 17 00:00:00 2001 From: Jens Axboe Date: Tue, 20 Jan 2026 07:42:50 -0700 Subject: [PATCH 2702/3902] io_uring/io-wq: check IO_WQ_BIT_EXIT inside work run loop commit 10dc959398175736e495f71c771f8641e1ca1907 upstream. Currently this is checked before running the pending work. Normally this is quite fine, as work items either end up blocking (which will create a new worker for other items), or they complete fairly quickly. But syzbot reports an issue where io-wq takes seemingly forever to exit, and with a bit of debugging, this turns out to be because it queues a bunch of big (2GB - 4096b) reads with a /dev/msr* file. Since this file type doesn't support ->read_iter(), loop_rw_iter() ends up handling them. Each read returns 16MB of data read, which takes 20 (!!) seconds. With a bunch of these pending, processing the whole chain can take a long time. Easily longer than the syzbot uninterruptible sleep timeout of 140 seconds. This then triggers a complaint off the io-wq exit path: INFO: task syz.4.135:6326 blocked for more than 143 seconds. Not tainted syzkaller #0 Blocked by coredump. "echo 0 > /proc/sys/kernel/hung_task_timeout_secs" disables this message. task:syz.4.135 state:D stack:26824 pid:6326 tgid:6324 ppid:5957 task_flags:0x400548 flags:0x00080000 Call Trace: context_switch kernel/sched/core.c:5256 [inline] __schedule+0x1139/0x6150 kernel/sched/core.c:6863 __schedule_loop kernel/sched/core.c:6945 [inline] schedule+0xe7/0x3a0 kernel/sched/core.c:6960 schedule_timeout+0x257/0x290 kernel/time/sleep_timeout.c:75 do_wait_for_common kernel/sched/completion.c:100 [inline] __wait_for_common+0x2fc/0x4e0 kernel/sched/completion.c:121 io_wq_exit_workers io_uring/io-wq.c:1328 [inline] io_wq_put_and_exit+0x271/0x8a0 io_uring/io-wq.c:1356 io_uring_clean_tctx+0x10d/0x190 io_uring/tctx.c:203 io_uring_cancel_generic+0x69c/0x9a0 io_uring/cancel.c:651 io_uring_files_cancel include/linux/io_uring.h:19 [inline] do_exit+0x2ce/0x2bd0 kernel/exit.c:911 do_group_exit+0xd3/0x2a0 kernel/exit.c:1112 get_signal+0x2671/0x26d0 kernel/signal.c:3034 arch_do_signal_or_restart+0x8f/0x7e0 arch/x86/kernel/signal.c:337 __exit_to_user_mode_loop kernel/entry/common.c:41 [inline] exit_to_user_mode_loop+0x8c/0x540 kernel/entry/common.c:75 __exit_to_user_mode_prepare include/linux/irq-entry-common.h:226 [inline] syscall_exit_to_user_mode_prepare include/linux/irq-entry-common.h:256 [inline] syscall_exit_to_user_mode_work include/linux/entry-common.h:159 [inline] syscall_exit_to_user_mode include/linux/entry-common.h:194 [inline] do_syscall_64+0x4ee/0xf80 arch/x86/entry/syscall_64.c:100 entry_SYSCALL_64_after_hwframe+0x77/0x7f RIP: 0033:0x7fa02738f749 RSP: 002b:00007fa0281ae0e8 EFLAGS: 00000246 ORIG_RAX: 00000000000000ca RAX: fffffffffffffe00 RBX: 00007fa0275e6098 RCX: 00007fa02738f749 RDX: 0000000000000000 RSI: 0000000000000080 RDI: 00007fa0275e6098 RBP: 00007fa0275e6090 R08: 0000000000000000 R09: 0000000000000000 R10: 0000000000000000 R11: 0000000000000246 R12: 0000000000000000 R13: 00007fa0275e6128 R14: 00007fff14e4fcb0 R15: 00007fff14e4fd98 There's really nothing wrong here, outside of processing these reads will take a LONG time. However, we can speed up the exit by checking the IO_WQ_BIT_EXIT inside the io_worker_handle_work() loop, as syzbot will exit the ring after queueing up all of these reads. Then once the first item is processed, io-wq will simply cancel the rest. That should avoid syzbot running into this complaint again. Cc: stable@vger.kernel.org Link: https://lore.kernel.org/all/68a2decc.050a0220.e29e5.0099.GAE@google.com/ Reported-by: syzbot+4eb282331cab6d5b6588@syzkaller.appspotmail.com Signed-off-by: Jens Axboe Signed-off-by: Greg Kroah-Hartman --- io_uring/io-wq.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/io_uring/io-wq.c b/io_uring/io-wq.c index 55961da19f3b1d..56b6a82579597a 100644 --- a/io_uring/io-wq.c +++ b/io_uring/io-wq.c @@ -598,9 +598,9 @@ static void io_worker_handle_work(struct io_wq_acct *acct, __releases(&acct->lock) { struct io_wq *wq = worker->wq; - bool do_kill = test_bit(IO_WQ_BIT_EXIT, &wq->state); do { + bool do_kill = test_bit(IO_WQ_BIT_EXIT, &wq->state); struct io_wq_work *work; /* From 81ed6e42d6e555dd978c9dd5e3f7c20cb121221b Mon Sep 17 00:00:00 2001 From: Francesco Lavra Date: Mon, 1 Dec 2025 11:00:10 +0100 Subject: [PATCH 2703/3902] iio: imu: st_lsm6dsx: fix iio_chan_spec for sensors without event detection commit c34e2e2d67b3bb8d5a6d09b0d6dac845cdd13fb3 upstream. The st_lsm6dsx_acc_channels array of struct iio_chan_spec has a non-NULL event_spec field, indicating support for IIO events. However, event detection is not supported for all sensors, and if userspace tries to configure accelerometer wakeup events on a sensor device that does not support them (e.g. LSM6DS0), st_lsm6dsx_write_event() dereferences a NULL pointer when trying to write to the wakeup register. Define an additional struct iio_chan_spec array whose members have a NULL event_spec field, and use this array instead of st_lsm6dsx_acc_channels for sensors without event detection capability. Fixes: b5969abfa8b8 ("iio: imu: st_lsm6dsx: add motion events") Signed-off-by: Francesco Lavra Reviewed-by: Andy Shevchenko Acked-by: Lorenzo Bianconi Cc: stable@vger.kernel.org Signed-off-by: Jonathan Cameron Signed-off-by: Greg Kroah-Hartman --- drivers/iio/imu/st_lsm6dsx/st_lsm6dsx_core.c | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/drivers/iio/imu/st_lsm6dsx/st_lsm6dsx_core.c b/drivers/iio/imu/st_lsm6dsx/st_lsm6dsx_core.c index a2daf0c14d965d..28ebb27d892495 100644 --- a/drivers/iio/imu/st_lsm6dsx/st_lsm6dsx_core.c +++ b/drivers/iio/imu/st_lsm6dsx/st_lsm6dsx_core.c @@ -101,6 +101,13 @@ static const struct iio_chan_spec st_lsm6dsx_acc_channels[] = { IIO_CHAN_SOFT_TIMESTAMP(3), }; +static const struct iio_chan_spec st_lsm6ds0_acc_channels[] = { + ST_LSM6DSX_CHANNEL(IIO_ACCEL, 0x28, IIO_MOD_X, 0), + ST_LSM6DSX_CHANNEL(IIO_ACCEL, 0x2a, IIO_MOD_Y, 1), + ST_LSM6DSX_CHANNEL(IIO_ACCEL, 0x2c, IIO_MOD_Z, 2), + IIO_CHAN_SOFT_TIMESTAMP(3), +}; + static const struct iio_chan_spec st_lsm6dsx_gyro_channels[] = { ST_LSM6DSX_CHANNEL(IIO_ANGL_VEL, 0x22, IIO_MOD_X, 0), ST_LSM6DSX_CHANNEL(IIO_ANGL_VEL, 0x24, IIO_MOD_Y, 1), @@ -142,8 +149,8 @@ static const struct st_lsm6dsx_settings st_lsm6dsx_sensor_settings[] = { }, .channels = { [ST_LSM6DSX_ID_ACC] = { - .chan = st_lsm6dsx_acc_channels, - .len = ARRAY_SIZE(st_lsm6dsx_acc_channels), + .chan = st_lsm6ds0_acc_channels, + .len = ARRAY_SIZE(st_lsm6ds0_acc_channels), }, [ST_LSM6DSX_ID_GYRO] = { .chan = st_lsm6ds0_gyro_channels, @@ -1449,8 +1456,8 @@ static const struct st_lsm6dsx_settings st_lsm6dsx_sensor_settings[] = { }, .channels = { [ST_LSM6DSX_ID_ACC] = { - .chan = st_lsm6dsx_acc_channels, - .len = ARRAY_SIZE(st_lsm6dsx_acc_channels), + .chan = st_lsm6ds0_acc_channels, + .len = ARRAY_SIZE(st_lsm6ds0_acc_channels), }, [ST_LSM6DSX_ID_GYRO] = { .chan = st_lsm6dsx_gyro_channels, From 489b88567f24da4e9b42e5c11e8e5ab39b7ec154 Mon Sep 17 00:00:00 2001 From: Pavel Zhigulin Date: Fri, 14 Nov 2025 18:13:01 +0300 Subject: [PATCH 2704/3902] iio: adc: ad7280a: handle spi_setup() errors in probe() [ Upstream commit 6b39824ac4c15783787e6434449772bfb2e31214 ] The probe() function ignored the return value of spi_setup(), leaving SPI configuration failures undetected. If spi_setup() fails, the driver should stop initialization and propagate the error to the caller. Add proper error handling: check the return value of spi_setup() and return it on failure. Found by Linux Verification Center (linuxtesting.org) with SVACE. Fixes: 2051f25d2a26 ("iio: adc: New driver for AD7280A Lithium Ion Battery Monitoring System") Signed-off-by: Pavel Zhigulin Reviewed-by: Marcelo Schmitt Signed-off-by: Jonathan Cameron Signed-off-by: Sasha Levin --- drivers/iio/adc/ad7280a.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/drivers/iio/adc/ad7280a.c b/drivers/iio/adc/ad7280a.c index 50a6ff7c8b1c73..ba12a3796e2b1b 100644 --- a/drivers/iio/adc/ad7280a.c +++ b/drivers/iio/adc/ad7280a.c @@ -1024,7 +1024,9 @@ static int ad7280_probe(struct spi_device *spi) st->spi->max_speed_hz = AD7280A_MAX_SPI_CLK_HZ; st->spi->mode = SPI_MODE_1; - spi_setup(st->spi); + ret = spi_setup(st->spi); + if (ret < 0) + return ret; st->ctrl_lb = FIELD_PREP(AD7280A_CTRL_LB_ACQ_TIME_MSK, st->acquisition_time) | FIELD_PREP(AD7280A_CTRL_LB_THERMISTOR_MSK, st->thermistor_term_en); From bcb9803aa1db73b8abaaf3b7d5eff23ef04dabb2 Mon Sep 17 00:00:00 2001 From: Haotian Zhang Date: Wed, 3 Dec 2025 13:08:44 +0800 Subject: [PATCH 2705/3902] iio: adc: ad7606: Fix incorrect type for error return variable [ Upstream commit c5512e016817a150fd6de97fbb3e74aa799ea3c1 ] The variable ret is declared as unsigned int but is used to store return values from functions returning int, which may be negative error codes. Change ret from unsigned int to int. Fixes: 849cebf8dc67 ("iio: adc: ad7606: Add iio-backend support") Signed-off-by: Haotian Zhang Reviewed-by: Andy Shevchenko Signed-off-by: Jonathan Cameron Signed-off-by: Sasha Levin --- drivers/iio/adc/ad7606_par.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/drivers/iio/adc/ad7606_par.c b/drivers/iio/adc/ad7606_par.c index 634852c4bbd2c5..b81e707ab40c56 100644 --- a/drivers/iio/adc/ad7606_par.c +++ b/drivers/iio/adc/ad7606_par.c @@ -43,7 +43,8 @@ static int ad7606_par_bus_setup_iio_backend(struct device *dev, struct iio_dev *indio_dev) { struct ad7606_state *st = iio_priv(indio_dev); - unsigned int ret, c; + unsigned int c; + int ret; struct iio_backend_data_fmt data = { .sign_extend = true, .enable = true, From 5d7c7e1fb3ec24fdd0f9faa27b666d6789e891e8 Mon Sep 17 00:00:00 2001 From: Georgi Djakov Date: Fri, 9 Jan 2026 14:25:23 +0200 Subject: [PATCH 2706/3902] interconnect: debugfs: initialize src_node and dst_node to empty strings [ Upstream commit 8cc27f5c6dd17dd090f3a696683f04336c162ff5 ] The debugfs_create_str() API assumes that the string pointer is either NULL or points to valid kmalloc() memory. Leaving the pointer uninitialized can cause problems. Initialize src_node and dst_node to empty strings before creating the debugfs entries to guarantee that reads and writes are safe. Fixes: 770c69f037c1 ("interconnect: Add debugfs test client") Signed-off-by: Georgi Djakov Reviewed-by: Kuan-Wei Chiu Tested-by: Kuan-Wei Chiu Link: https://lore.kernel.org/r/20260109122523.125843-1-djakov@kernel.org Signed-off-by: Georgi Djakov Signed-off-by: Sasha Levin --- drivers/interconnect/debugfs-client.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/drivers/interconnect/debugfs-client.c b/drivers/interconnect/debugfs-client.c index 778deeb4a7e8a6..24d7b5a5779450 100644 --- a/drivers/interconnect/debugfs-client.c +++ b/drivers/interconnect/debugfs-client.c @@ -150,6 +150,11 @@ int icc_debugfs_client_init(struct dentry *icc_dir) return ret; } + src_node = devm_kstrdup(&pdev->dev, "", GFP_KERNEL); + dst_node = devm_kstrdup(&pdev->dev, "", GFP_KERNEL); + if (!src_node || !dst_node) + return -ENOMEM; + client_dir = debugfs_create_dir("test_client", icc_dir); debugfs_create_str("src_node", 0600, client_dir, &src_node); From f6d6b3f172df118db582fe5ec43ae223a55d99cf Mon Sep 17 00:00:00 2001 From: Felix Gu Date: Fri, 9 Jan 2026 20:49:53 +0800 Subject: [PATCH 2707/3902] spi: spi-sprd-adi: Fix double free in probe error path [ Upstream commit 383d4f5cffcc8df930d95b06518a9d25a6d74aac ] The driver currently uses spi_alloc_host() to allocate the controller but registers it using devm_spi_register_controller(). If devm_register_restart_handler() fails, the code jumps to the put_ctlr label and calls spi_controller_put(). However, since the controller was registered via a devm function, the device core will automatically call spi_controller_put() again when the probe fails. This results in a double-free of the spi_controller structure. Fix this by switching to devm_spi_alloc_host() and removing the manual spi_controller_put() call. Fixes: ac17750 ("spi: sprd: Add the support of restarting the system") Signed-off-by: Felix Gu Reviewed-by: Baolin Wang Link: https://patch.msgid.link/tencent_AC7D389CE7E24318445E226F7CDCCC2F0D07@qq.com Signed-off-by: Mark Brown Signed-off-by: Sasha Levin --- drivers/spi/spi-sprd-adi.c | 33 ++++++++++----------------------- 1 file changed, 10 insertions(+), 23 deletions(-) diff --git a/drivers/spi/spi-sprd-adi.c b/drivers/spi/spi-sprd-adi.c index 262c11d977ea35..f25b34a91756f4 100644 --- a/drivers/spi/spi-sprd-adi.c +++ b/drivers/spi/spi-sprd-adi.c @@ -528,7 +528,7 @@ static int sprd_adi_probe(struct platform_device *pdev) pdev->id = of_alias_get_id(np, "spi"); num_chipselect = of_get_child_count(np); - ctlr = spi_alloc_host(&pdev->dev, sizeof(struct sprd_adi)); + ctlr = devm_spi_alloc_host(&pdev->dev, sizeof(struct sprd_adi)); if (!ctlr) return -ENOMEM; @@ -536,10 +536,8 @@ static int sprd_adi_probe(struct platform_device *pdev) sadi = spi_controller_get_devdata(ctlr); sadi->base = devm_platform_get_and_ioremap_resource(pdev, 0, &res); - if (IS_ERR(sadi->base)) { - ret = PTR_ERR(sadi->base); - goto put_ctlr; - } + if (IS_ERR(sadi->base)) + return PTR_ERR(sadi->base); sadi->slave_vbase = (unsigned long)sadi->base + data->slave_offset; @@ -551,18 +549,15 @@ static int sprd_adi_probe(struct platform_device *pdev) if (ret > 0 || (IS_ENABLED(CONFIG_HWSPINLOCK) && ret == 0)) { sadi->hwlock = devm_hwspin_lock_request_specific(&pdev->dev, ret); - if (!sadi->hwlock) { - ret = -ENXIO; - goto put_ctlr; - } + if (!sadi->hwlock) + return -ENXIO; } else { switch (ret) { case -ENOENT: dev_info(&pdev->dev, "no hardware spinlock supplied\n"); break; default: - dev_err_probe(&pdev->dev, ret, "failed to find hwlock id\n"); - goto put_ctlr; + return dev_err_probe(&pdev->dev, ret, "failed to find hwlock id\n"); } } @@ -579,26 +574,18 @@ static int sprd_adi_probe(struct platform_device *pdev) ctlr->transfer_one = sprd_adi_transfer_one; ret = devm_spi_register_controller(&pdev->dev, ctlr); - if (ret) { - dev_err(&pdev->dev, "failed to register SPI controller\n"); - goto put_ctlr; - } + if (ret) + return dev_err_probe(&pdev->dev, ret, "failed to register SPI controller\n"); if (sadi->data->restart) { ret = devm_register_restart_handler(&pdev->dev, sadi->data->restart, sadi); - if (ret) { - dev_err(&pdev->dev, "can not register restart handler\n"); - goto put_ctlr; - } + if (ret) + return dev_err_probe(&pdev->dev, ret, "can not register restart handler\n"); } return 0; - -put_ctlr: - spi_controller_put(ctlr); - return ret; } static struct sprd_adi_data sc9860_data = { From c2d2cf710dc3ee1a69e00b4ed8de607a92a07889 Mon Sep 17 00:00:00 2001 From: Cheng-Yu Lee Date: Fri, 9 Jan 2026 11:26:33 +0800 Subject: [PATCH 2708/3902] regmap: Fix race condition in hwspinlock irqsave routine [ Upstream commit 4b58aac989c1e3fafb1c68a733811859df388250 ] Previously, the address of the shared member '&map->spinlock_flags' was passed directly to 'hwspin_lock_timeout_irqsave'. This creates a race condition where multiple contexts contending for the lock could overwrite the shared flags variable, potentially corrupting the state for the current lock owner. Fix this by using a local stack variable 'flags' to store the IRQ state temporarily. Fixes: 8698b9364710 ("regmap: Add hardware spinlock support") Signed-off-by: Cheng-Yu Lee Co-developed-by: Yu-Chun Lin Signed-off-by: Yu-Chun Lin Link: https://patch.msgid.link/20260109032633.8732-1-eleanor.lin@realtek.com Signed-off-by: Mark Brown Signed-off-by: Sasha Levin --- drivers/base/regmap/regmap.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/drivers/base/regmap/regmap.c b/drivers/base/regmap/regmap.c index ce9be3989a218d..ae2215d4e61c3e 100644 --- a/drivers/base/regmap/regmap.c +++ b/drivers/base/regmap/regmap.c @@ -408,9 +408,11 @@ static void regmap_lock_hwlock_irq(void *__map) static void regmap_lock_hwlock_irqsave(void *__map) { struct regmap *map = __map; + unsigned long flags = 0; hwspin_lock_timeout_irqsave(map->hwlock, UINT_MAX, - &map->spinlock_flags); + &flags); + map->spinlock_flags = flags; } static void regmap_unlock_hwlock(void *__map) From 82fb54efe7e0f22fdecebef6246287588b376863 Mon Sep 17 00:00:00 2001 From: Arkadiusz Kozdra Date: Sat, 10 Jan 2026 12:48:08 +0100 Subject: [PATCH 2709/3902] kconfig: fix static linking of nconf [ Upstream commit baaecfcac559bcac73206df447eb5c385fa22f2a ] When running make nconfig with a static linking host toolchain, the libraries are linked in an incorrect order, resulting in errors similar to the following: $ MAKEFLAGS='HOSTCC=cc\ -static' make nconfig /usr/bin/ld: /usr/lib64/gcc/x86_64-unknown-linux-gnu/14.2.1/../../../../lib64/libpanel.a(p_new.o): in function `new_panel': (.text+0x13): undefined reference to `_nc_panelhook_sp' /usr/bin/ld: (.text+0x6c): undefined reference to `_nc_panelhook_sp' Fixes: 1c5af5cf9308 ("kconfig: refactor ncurses package checks for building mconf and nconf") Signed-off-by: Arusekk Link: https://patch.msgid.link/20260110114808.22595-1-floss@arusekk.pl [nsc: Added comment about library order] Signed-off-by: Nicolas Schier Signed-off-by: Sasha Levin --- scripts/kconfig/nconf-cfg.sh | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/scripts/kconfig/nconf-cfg.sh b/scripts/kconfig/nconf-cfg.sh index a20290b1a37d89..4d08453f9bdb7f 100755 --- a/scripts/kconfig/nconf-cfg.sh +++ b/scripts/kconfig/nconf-cfg.sh @@ -6,8 +6,9 @@ set -eu cflags=$1 libs=$2 -PKG="ncursesw menuw panelw" -PKG2="ncurses menu panel" +# Keep library order for static linking (HOSTCC='cc -static') +PKG="menuw panelw ncursesw" +PKG2="menu panel ncurses" if [ -n "$(command -v ${HOSTPKG_CONFIG})" ]; then if ${HOSTPKG_CONFIG} --exists $PKG; then @@ -28,19 +29,19 @@ fi # find ncurses by pkg-config.) if [ -f /usr/include/ncursesw/ncurses.h ]; then echo -D_GNU_SOURCE -I/usr/include/ncursesw > ${cflags} - echo -lncursesw -lmenuw -lpanelw > ${libs} + echo -lmenuw -lpanelw -lncursesw > ${libs} exit 0 fi if [ -f /usr/include/ncurses/ncurses.h ]; then echo -D_GNU_SOURCE -I/usr/include/ncurses > ${cflags} - echo -lncurses -lmenu -lpanel > ${libs} + echo -lmenu -lpanel -lncurses > ${libs} exit 0 fi if [ -f /usr/include/ncurses.h ]; then echo -D_GNU_SOURCE > ${cflags} - echo -lncurses -lmenu -lpanel > ${libs} + echo -lmenu -lpanel -lncurses > ${libs} exit 0 fi From c4b55a4c600fa0142fc1ec4c6d5c080dda649c8e Mon Sep 17 00:00:00 2001 From: Naohiko Shimizu Date: Sun, 4 Jan 2026 22:59:36 +0900 Subject: [PATCH 2710/3902] riscv: clocksource: Fix stimecmp update hazard on RV32 [ Upstream commit eaa9bb1d39d59e7c17b06cec12622b7c586ab629 ] On RV32, updating the 64-bit stimecmp (or vstimecmp) CSR requires two separate 32-bit writes. A race condition exists if the timer triggers during these two writes. The RISC-V Privileged Specification (e.g., Section 3.2.1 for mtimecmp) recommends a specific 3-step sequence to avoid spurious interrupts when updating 64-bit comparison registers on 32-bit systems: 1. Set the low-order bits (stimecmp) to all ones (ULONG_MAX). 2. Set the high-order bits (stimecmph) to the desired value. 3. Set the low-order bits (stimecmp) to the desired value. Current implementation writes the LSB first without ensuring a future value, which may lead to a transient state where the 64-bit comparison is incorrectly evaluated as "expired" by the hardware. This results in spurious timer interrupts. This patch adopts the spec-recommended 3-step sequence to ensure the intermediate 64-bit state is never smaller than the current time. Fixes: 9f7a8ff6391f ("RISC-V: Prefer sstc extension if available") Signed-off-by: Naohiko Shimizu Reviewed-by: Anup Patel Link: https://patch.msgid.link/20260104135938.524-2-naohiko.shimizu@gmail.com Signed-off-by: Paul Walmsley Signed-off-by: Sasha Levin --- drivers/clocksource/timer-riscv.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/drivers/clocksource/timer-riscv.c b/drivers/clocksource/timer-riscv.c index 4d7cf338824a3b..cfc4d83c42c039 100644 --- a/drivers/clocksource/timer-riscv.c +++ b/drivers/clocksource/timer-riscv.c @@ -50,8 +50,9 @@ static int riscv_clock_next_event(unsigned long delta, if (static_branch_likely(&riscv_sstc_available)) { #if defined(CONFIG_32BIT) - csr_write(CSR_STIMECMP, next_tval & 0xFFFFFFFF); + csr_write(CSR_STIMECMP, ULONG_MAX); csr_write(CSR_STIMECMPH, next_tval >> 32); + csr_write(CSR_STIMECMP, next_tval & 0xFFFFFFFF); #else csr_write(CSR_STIMECMP, next_tval); #endif From e150008568a5d63a5250d20a312f6735cb3521fa Mon Sep 17 00:00:00 2001 From: Naohiko Shimizu Date: Sun, 4 Jan 2026 22:59:38 +0900 Subject: [PATCH 2711/3902] riscv: suspend: Fix stimecmp update hazard on RV32 [ Upstream commit 344c5281f43851b22c7cc223fd0250c143fcbc79 ] On RV32, updating the 64-bit stimecmp (or vstimecmp) CSR requires two separate 32-bit writes. A race condition exists if the timer triggers during these two writes. The RISC-V Privileged Specification (e.g., Section 3.2.1 for mtimecmp) recommends a specific 3-step sequence to avoid spurious interrupts when updating 64-bit comparison registers on 32-bit systems: 1. Set the low-order bits (stimecmp) to all ones (ULONG_MAX). 2. Set the high-order bits (stimecmph) to the desired value. 3. Set the low-order bits (stimecmp) to the desired value. Current implementation writes the LSB first without ensuring a future value, which may lead to a transient state where the 64-bit comparison is incorrectly evaluated as "expired" by the hardware. This results in spurious timer interrupts. This patch adopts the spec-recommended 3-step sequence to ensure the intermediate 64-bit state is never smaller than the current time. Fixes: ffef54ad4110 ("riscv: Add stimecmp save and restore") Signed-off-by: Naohiko Shimizu Reviewed-by: Anup Patel Link: https://patch.msgid.link/20260104135938.524-4-naohiko.shimizu@gmail.com Signed-off-by: Paul Walmsley Signed-off-by: Sasha Levin --- arch/riscv/kernel/suspend.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/arch/riscv/kernel/suspend.c b/arch/riscv/kernel/suspend.c index 24b3f57d467f83..aff93090c4efc7 100644 --- a/arch/riscv/kernel/suspend.c +++ b/arch/riscv/kernel/suspend.c @@ -51,10 +51,11 @@ void suspend_restore_csrs(struct suspend_context *context) #ifdef CONFIG_MMU if (riscv_has_extension_unlikely(RISCV_ISA_EXT_SSTC)) { - csr_write(CSR_STIMECMP, context->stimecmp); #if __riscv_xlen < 64 + csr_write(CSR_STIMECMP, ULONG_MAX); csr_write(CSR_STIMECMPH, context->stimecmph); #endif + csr_write(CSR_STIMECMP, context->stimecmp); } csr_write(CSR_SATP, context->satp); From 5f1f79cb90f5e489d9832ec974601be86e28b784 Mon Sep 17 00:00:00 2001 From: Oleksandr Shamray Date: Wed, 7 Jan 2026 16:25:48 +0200 Subject: [PATCH 2712/3902] platform/mellanox: Fix SN5640/SN5610 LED platform data MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [ Upstream commit 3113bcf4ccf06c938f0bc0c34cf6efe03278badc ] In SN5640/SN5610 platforms should be used XDR style LED data with predefined slot index per led_fan. Fixes: 317bbe169c46 ("platform: mellanox: mlx-platform: Add support for new Nvidia system") Signed-off-by: Oleksandr Shamray Reviewed-by: Vadim Pasternak Link: https://patch.msgid.link/20260107142548.916556-1-oleksandrs@nvidia.com Signed-off-by: Ilpo Järvinen Signed-off-by: Sasha Levin --- drivers/platform/mellanox/mlx-platform.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/platform/mellanox/mlx-platform.c b/drivers/platform/mellanox/mlx-platform.c index d0df18be93c76e..efd0c074ad9375 100644 --- a/drivers/platform/mellanox/mlx-platform.c +++ b/drivers/platform/mellanox/mlx-platform.c @@ -7381,7 +7381,7 @@ static int __init mlxplat_dmi_ng400_hi171_matched(const struct dmi_system_id *dm mlxplat_hotplug = &mlxplat_mlxcpld_ng800_hi171_data; mlxplat_hotplug->deferred_nr = mlxplat_msn21xx_channels[MLXPLAT_CPLD_GRP_CHNL_NUM - 1]; - mlxplat_led = &mlxplat_default_ng_led_data; + mlxplat_led = &mlxplat_xdr_led_data; mlxplat_regs_io = &mlxplat_default_ng_regs_io_data; mlxplat_fan = &mlxplat_xdr_fan_data; From 1a0072bd1f1e559eda3e91a24dbc51c9eb025c54 Mon Sep 17 00:00:00 2001 From: Zilin Guan Date: Tue, 6 Jan 2026 09:13:17 +0000 Subject: [PATCH 2713/3902] platform/x86/amd: Fix memory leak in wbrf_record() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [ Upstream commit 2bf1877b7094c684e1d652cac6912cfbc507ad3e ] The tmp buffer is allocated using kcalloc() but is not freed if acpi_evaluate_dsm() fails. This causes a memory leak in the error path. Fix this by explicitly freeing the tmp buffer in the error handling path of acpi_evaluate_dsm(). Fixes: 58e82a62669d ("platform/x86/amd: Add support for AMD ACPI based Wifi band RFI mitigation feature") Suggested-by: Ilpo Järvinen Co-developed-by: Jianhao Xu Signed-off-by: Jianhao Xu Signed-off-by: Zilin Guan Link: https://patch.msgid.link/20260106091318.747019-1-zilin@seu.edu.cn Reviewed-by: Ilpo Järvinen Signed-off-by: Ilpo Järvinen Signed-off-by: Sasha Levin --- drivers/platform/x86/amd/wbrf.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/drivers/platform/x86/amd/wbrf.c b/drivers/platform/x86/amd/wbrf.c index dd197b3aebe06a..0f58d252b620af 100644 --- a/drivers/platform/x86/amd/wbrf.c +++ b/drivers/platform/x86/amd/wbrf.c @@ -104,8 +104,10 @@ static int wbrf_record(struct acpi_device *adev, uint8_t action, struct wbrf_ran obj = acpi_evaluate_dsm(adev->handle, &wifi_acpi_dsm_guid, WBRF_REVISION, WBRF_RECORD, &argv4); - if (!obj) + if (!obj) { + kfree(tmp); return -EINVAL; + } if (obj->type != ACPI_TYPE_INTEGER) { ret = -EINVAL; From 219f009ebfd1ef3970888ee9eef4c8a06357f862 Mon Sep 17 00:00:00 2001 From: David Jeffery Date: Tue, 13 Jan 2026 11:08:13 -0500 Subject: [PATCH 2714/3902] scsi: core: Wake up the error handler when final completions race against each other [ Upstream commit fe2f8ad6f0999db3b318359a01ee0108c703a8c3 ] The fragile ordering between marking commands completed or failed so that the error handler only wakes when the last running command completes or times out has race conditions. These race conditions can cause the SCSI layer to fail to wake the error handler, leaving I/O through the SCSI host stuck as the error state cannot advance. First, there is an memory ordering issue within scsi_dec_host_busy(). The write which clears SCMD_STATE_INFLIGHT may be reordered with reads counting in scsi_host_busy(). While the local CPU will see its own write, reordering can allow other CPUs in scsi_dec_host_busy() or scsi_eh_inc_host_failed() to see a raised busy count, causing no CPU to see a host busy equal to the host_failed count. This race condition can be prevented with a memory barrier on the error path to force the write to be visible before counting host busy commands. Second, there is a general ordering issue with scsi_eh_inc_host_failed(). By counting busy commands before incrementing host_failed, it can race with a final command in scsi_dec_host_busy(), such that scsi_dec_host_busy() does not see host_failed incremented but scsi_eh_inc_host_failed() counts busy commands before SCMD_STATE_INFLIGHT is cleared by scsi_dec_host_busy(), resulting in neither waking the error handler task. This needs the call to scsi_host_busy() to be moved after host_failed is incremented to close the race condition. Fixes: 6eb045e092ef ("scsi: core: avoid host-wide host_busy counter for scsi_mq") Signed-off-by: David Jeffery Reviewed-by: Bart Van Assche Link: https://patch.msgid.link/20260113161036.6730-1-djeffery@redhat.com Signed-off-by: Martin K. Petersen Signed-off-by: Sasha Levin --- drivers/scsi/scsi_error.c | 11 ++++++++++- drivers/scsi/scsi_lib.c | 8 ++++++++ 2 files changed, 18 insertions(+), 1 deletion(-) diff --git a/drivers/scsi/scsi_error.c b/drivers/scsi/scsi_error.c index 830429483319ad..2fd4ca96b3089d 100644 --- a/drivers/scsi/scsi_error.c +++ b/drivers/scsi/scsi_error.c @@ -282,11 +282,20 @@ static void scsi_eh_inc_host_failed(struct rcu_head *head) { struct scsi_cmnd *scmd = container_of(head, typeof(*scmd), rcu); struct Scsi_Host *shost = scmd->device->host; - unsigned int busy = scsi_host_busy(shost); + unsigned int busy; unsigned long flags; spin_lock_irqsave(shost->host_lock, flags); shost->host_failed++; + spin_unlock_irqrestore(shost->host_lock, flags); + /* + * The counting of busy requests needs to occur after adding to + * host_failed or after the lock acquire for adding to host_failed + * to prevent a race with host unbusy and missing an eh wakeup. + */ + busy = scsi_host_busy(shost); + + spin_lock_irqsave(shost->host_lock, flags); scsi_eh_wakeup(shost, busy); spin_unlock_irqrestore(shost->host_lock, flags); } diff --git a/drivers/scsi/scsi_lib.c b/drivers/scsi/scsi_lib.c index d7e42293b86455..7ddb73cd6d9fe5 100644 --- a/drivers/scsi/scsi_lib.c +++ b/drivers/scsi/scsi_lib.c @@ -376,6 +376,14 @@ static void scsi_dec_host_busy(struct Scsi_Host *shost, struct scsi_cmnd *cmd) rcu_read_lock(); __clear_bit(SCMD_STATE_INFLIGHT, &cmd->state); if (unlikely(scsi_host_in_recovery(shost))) { + /* + * Ensure the clear of SCMD_STATE_INFLIGHT is visible to + * other CPUs before counting busy requests. Otherwise, + * reordering can cause CPUs to race and miss an eh wakeup + * when no CPU sees all busy requests as done or timed out. + */ + smp_mb(); + unsigned int busy = scsi_host_busy(shost); spin_lock_irqsave(shost->host_lock, flags); From aa14451fa5d5f2de919384c637e2a8c604e1a1fe Mon Sep 17 00:00:00 2001 From: Jiasheng Jiang Date: Tue, 6 Jan 2026 20:53:44 +0000 Subject: [PATCH 2715/3902] scsi: qla2xxx: Sanitize payload size to prevent member overflow [ Upstream commit 19bc5f2a6962dfaa0e32d0e0bc2271993d85d414 ] In qla27xx_copy_fpin_pkt() and qla27xx_copy_multiple_pkt(), the frame_size reported by firmware is used to calculate the copy length into item->iocb. However, the iocb member is defined as a fixed-size 64-byte array within struct purex_item. If the reported frame_size exceeds 64 bytes, subsequent memcpy calls will overflow the iocb member boundary. While extra memory might be allocated, this cross-member write is unsafe and triggers warnings under CONFIG_FORTIFY_SOURCE. Fix this by capping total_bytes to the size of the iocb member (64 bytes) before allocation and copying. This ensures all copies remain within the bounds of the destination structure member. Fixes: 875386b98857 ("scsi: qla2xxx: Add Unsolicited LS Request and Response Support for NVMe") Signed-off-by: Jiasheng Jiang Reviewed-by: Himanshu Madhani Link: https://patch.msgid.link/20260106205344.18031-1-jiashengjiangcool@gmail.com Signed-off-by: Martin K. Petersen Signed-off-by: Sasha Levin --- drivers/scsi/qla2xxx/qla_isr.c | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/drivers/scsi/qla2xxx/qla_isr.c b/drivers/scsi/qla2xxx/qla_isr.c index a3971afc2dd1e9..a04a5aa0d00572 100644 --- a/drivers/scsi/qla2xxx/qla_isr.c +++ b/drivers/scsi/qla2xxx/qla_isr.c @@ -878,6 +878,9 @@ qla27xx_copy_multiple_pkt(struct scsi_qla_host *vha, void **pkt, payload_size = sizeof(purex->els_frame_payload); } + if (total_bytes > sizeof(item->iocb.iocb)) + total_bytes = sizeof(item->iocb.iocb); + pending_bytes = total_bytes; no_bytes = (pending_bytes > payload_size) ? payload_size : pending_bytes; @@ -1163,6 +1166,10 @@ qla27xx_copy_fpin_pkt(struct scsi_qla_host *vha, void **pkt, total_bytes = (le16_to_cpu(purex->frame_size) & 0x0FFF) - PURX_ELS_HEADER_SIZE; + + if (total_bytes > sizeof(item->iocb.iocb)) + total_bytes = sizeof(item->iocb.iocb); + pending_bytes = total_bytes; entry_count = entry_count_remaining = purex->entry_count; no_bytes = (pending_bytes > sizeof(purex->els_frame_payload)) ? From ecb2c8732680cd5d4977c7d12f3c8ecab0cf8749 Mon Sep 17 00:00:00 2001 From: Arun Raghavan Date: Fri, 16 Jan 2026 14:58:04 -0800 Subject: [PATCH 2716/3902] ALSA: usb: Increase volume range that triggers a warning [ Upstream commit 6b971191fcfc9e3c2c0143eea22534f1f48dbb62 ] On at least the HyperX Cloud III, the range is 18944 (-18944 -> 0 in steps of 1), so the original check for 255 steps is definitely obsolete. Let's give ourselves a little more headroom before we emit a warning. Fixes: 80acefff3bc7 ("ALSA: usb-audio - Add volume range check and warn if it too big") Cc: Jaroslav Kysela Cc: Takashi Iwai Cc: linux-sound@vger.kernel.org Signed-off-by: Arun Raghavan Link: https://patch.msgid.link/20260116225804.3845935-1-arunr@valvesoftware.com Signed-off-by: Takashi Iwai Signed-off-by: Sasha Levin --- sound/usb/mixer.c | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/sound/usb/mixer.c b/sound/usb/mixer.c index 3af71d42b9b9aa..90917c6ea871b4 100644 --- a/sound/usb/mixer.c +++ b/sound/usb/mixer.c @@ -1813,11 +1813,10 @@ static void __build_feature_ctl(struct usb_mixer_interface *mixer, range = (cval->max - cval->min) / cval->res; /* - * Are there devices with volume range more than 255? I use a bit more - * to be sure. 384 is a resolution magic number found on Logitech - * devices. It will definitively catch all buggy Logitech devices. + * There are definitely devices with a range of ~20,000, so let's be + * conservative and allow for a bit more. */ - if (range > 384) { + if (range > 65535) { usb_audio_warn(mixer->chip, "Warning! Unlikely big volume range (=%u), cval->res is probably wrong.", range); From 06657690854fae9d4771dabf63acf3e813e3e22f Mon Sep 17 00:00:00 2001 From: Dave Jiang Date: Thu, 8 Jan 2026 14:09:33 -0700 Subject: [PATCH 2717/3902] ntb: transport: Fix uninitialized mutex [ Upstream commit 2ccb5e8dbcd2dedf13e0270165ac48bd79b7f673 ] When the mutex 'link_event_lock' was introduced, it was never initialized and it triggers kernel warnings when used with locking debug turned on. Add initialization for the mutex. Fixes: 3db835dd8f9a ("ntb: Add mutex to make link_event_callback executed linearly.") Cc: fuyuanli Signed-off-by: Dave Jiang Signed-off-by: Jon Mason Signed-off-by: Sasha Levin --- drivers/ntb/ntb_transport.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/ntb/ntb_transport.c b/drivers/ntb/ntb_transport.c index eb875e3db2e3b9..71d4bb25f7fdd1 100644 --- a/drivers/ntb/ntb_transport.c +++ b/drivers/ntb/ntb_transport.c @@ -1394,6 +1394,7 @@ static int ntb_transport_probe(struct ntb_client *self, struct ntb_dev *ndev) goto err2; } + mutex_init(&nt->link_event_lock); INIT_DELAYED_WORK(&nt->link_work, ntb_transport_link_work); INIT_WORK(&nt->link_cleanup, ntb_transport_link_cleanup_work); From a807e4f4f63a5b5b7f6b45cafcd030b4d21b21b0 Mon Sep 17 00:00:00 2001 From: Vasant Hegde Date: Fri, 16 Jan 2026 05:53:32 +0000 Subject: [PATCH 2718/3902] iommu/amd: Fix error path in amd_iommu_probe_device() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [ Upstream commit 3222b6de5145272c43a90cb8667377d676635ea0 ] Currently, the error path of amd_iommu_probe_device() unconditionally references dev_data, which may not be initialized if an early failure occurs (like iommu_init_device() fails). Move the out_err label to ensure the function exits immediately on failure without accessing potentially uninitialized dev_data. Fixes: 19e5cc156cb ("iommu/amd: Enable support for up to 2K interrupts per function") Cc: Rakuram Eswaran Cc: Jörg Rödel Reported-by: kernel test robot Reported-by: Dan Carpenter Closes: https://lore.kernel.org/r/202512191724.meqJENXe-lkp@intel.com/ Signed-off-by: Vasant Hegde Signed-off-by: Joerg Roedel Signed-off-by: Sasha Levin --- drivers/iommu/amd/iommu.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/drivers/iommu/amd/iommu.c b/drivers/iommu/amd/iommu.c index a38304f1a8df5f..5914bef0c8c19a 100644 --- a/drivers/iommu/amd/iommu.c +++ b/drivers/iommu/amd/iommu.c @@ -2426,8 +2426,6 @@ static struct iommu_device *amd_iommu_probe_device(struct device *dev) goto out_err; } -out_err: - iommu_completion_wait(iommu); if (FEATURE_NUM_INT_REMAP_SUP_2K(amd_iommu_efr2)) @@ -2438,6 +2436,7 @@ static struct iommu_device *amd_iommu_probe_device(struct device *dev) if (dev_is_pci(dev)) pci_prepare_ats(to_pci_dev(dev), PAGE_SHIFT); +out_err: return iommu_dev; } From bc0812416e0897a97b0e054e2d54ccf370a519cb Mon Sep 17 00:00:00 2001 From: Jani Nikula Date: Wed, 7 Jan 2026 17:53:59 +0200 Subject: [PATCH 2719/3902] drm/xe/xe_late_bind_fw: fix enum xe_late_bind_fw_id kernel-doc MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [ Upstream commit dc1d0ffee09740088eb190af84a2c470d279bad9 ] Fix kernel-doc warnings on enum xe_late_bind_fw_id: Warning: ../drivers/gpu/drm/xe/xe_late_bind_fw_types.h:19 cannot understand function prototype: 'enum xe_late_bind_fw_id' Fixes: 45832bf9c10f ("drm/xe/xe_late_bind_fw: Initialize late binding firmware") Cc: Badal Nilawar Cc: Daniele Ceraolo Spurio Cc: Rodrigo Vivi Reviewed-by: Badal Nilawar Link: https://patch.msgid.link/20260107155401.2379127-3-jani.nikula@intel.com Signed-off-by: Jani Nikula (cherry picked from commit a857e6102970c7bd8f2db967fe02d76741179d14) Signed-off-by: Thomas Hellström Signed-off-by: Sasha Levin --- drivers/gpu/drm/xe/xe_late_bind_fw_types.h | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/drivers/gpu/drm/xe/xe_late_bind_fw_types.h b/drivers/gpu/drm/xe/xe_late_bind_fw_types.h index 0f5da89ce98b8a..2a8a985c37e71a 100644 --- a/drivers/gpu/drm/xe/xe_late_bind_fw_types.h +++ b/drivers/gpu/drm/xe/xe_late_bind_fw_types.h @@ -15,10 +15,12 @@ #define XE_LB_MAX_PAYLOAD_SIZE SZ_4K /** - * xe_late_bind_fw_id - enum to determine late binding fw index + * enum xe_late_bind_fw_id - enum to determine late binding fw index */ enum xe_late_bind_fw_id { + /** @XE_LB_FW_FAN_CONTROL: Fan control */ XE_LB_FW_FAN_CONTROL = 0, + /** @XE_LB_FW_MAX_ID: Number of IDs */ XE_LB_FW_MAX_ID }; From aa29fe8c1d86bada1368ce068a7adb61766deecc Mon Sep 17 00:00:00 2001 From: Jani Nikula Date: Wed, 7 Jan 2026 17:54:00 +0200 Subject: [PATCH 2720/3902] drm/xe/vm: fix xe_vm_validation_exec() kernel-doc MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [ Upstream commit 47bf28e22a121b807a9a9680c4209846a78a98a6 ] Fix kernel-doc warnings on xe_vm_validation_exec(): Warning: ../drivers/gpu/drm/xe/xe_vm.h:392 expecting prototype for xe_vm_set_validation_exec(). Prototype was for xe_vm_validation_exec() instead Fixes: 0131514f9789 ("drm/xe: Pass down drm_exec context to validation") Cc: Thomas Hellström Cc: Matthew Brost Reviewed-by: Matt Roper Link: https://patch.msgid.link/20260107155401.2379127-4-jani.nikula@intel.com Signed-off-by: Jani Nikula (cherry picked from commit b3a7767989e6519127ac5e0cde682c50ad587f3b) Signed-off-by: Thomas Hellström Signed-off-by: Sasha Levin --- drivers/gpu/drm/xe/xe_vm.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/gpu/drm/xe/xe_vm.h b/drivers/gpu/drm/xe/xe_vm.h index ef8a5019574e6f..016f6786134cb2 100644 --- a/drivers/gpu/drm/xe/xe_vm.h +++ b/drivers/gpu/drm/xe/xe_vm.h @@ -379,7 +379,7 @@ static inline void xe_vm_set_validation_exec(struct xe_vm *vm, struct drm_exec * } /** - * xe_vm_set_validation_exec() - Accessor to read the drm_exec object + * xe_vm_validation_exec() - Accessor to read the drm_exec object * @vm: The vm we want to register a drm_exec object with. * * Return: The drm_exec object used to lock the vm's resv. The value From fccaa85ffb73c1da8e7f2d4a7a04607898180c2b Mon Sep 17 00:00:00 2001 From: Matthew Brost Date: Fri, 9 Jan 2026 17:27:38 -0800 Subject: [PATCH 2721/3902] drm/xe: Disable timestamp WA on VFs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [ Upstream commit b886aa65eafe3098bbd691f0ca4a9abce03f9d03 ] The timestamp WA does not work on a VF because it requires reading MMIO registers, which are inaccessible on a VF. This timestamp WA confuses LRC sampling on a VF during TDR, as the LRC timestamp would always read as 1 for any active context. Disable the timestamp WA on VFs to avoid this confusion. Signed-off-by: Matthew Brost Reviewed-by: Umesh Nerlige Ramappa Fixes: 617d824c5323 ("drm/xe: Add WA BB to capture active context utilization") Link: https://patch.msgid.link/20260110012739.2888434-7-matthew.brost@intel.com (cherry picked from commit efffd56e4bd894e0935eea00e437f233b6cebc0d) Signed-off-by: Thomas Hellström Signed-off-by: Sasha Levin --- drivers/gpu/drm/xe/xe_lrc.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/drivers/gpu/drm/xe/xe_lrc.c b/drivers/gpu/drm/xe/xe_lrc.c index 47e9df7750725a..d77ef556e994ea 100644 --- a/drivers/gpu/drm/xe/xe_lrc.c +++ b/drivers/gpu/drm/xe/xe_lrc.c @@ -1050,6 +1050,9 @@ static ssize_t setup_utilization_wa(struct xe_lrc *lrc, { u32 *cmd = batch; + if (IS_SRIOV_VF(gt_to_xe(lrc->gt))) + return 0; + if (xe_gt_WARN_ON(lrc->gt, max_len < 12)) return -ENOSPC; From eb98125fa2b33d857679bbf8ef6cbd5d45c655e9 Mon Sep 17 00:00:00 2001 From: Chen-Yu Tsai Date: Wed, 14 Jan 2026 17:22:42 +0800 Subject: [PATCH 2722/3902] drm/mediatek: dpi: Find next bridge during probe [ Upstream commit 21465e73400dc69a5f732ae7bcc2a58bad673cd1 ] Trying to find the next bridge and deferring probe in the bridge attach callback is much too late. At this point the driver has already finished probing and is now running the component bind code path. What's even worse is that in the specific case of the DSI host being the last component to be added as part of the dsi_host_attach callback, the code path that this is in: -> devm_drm_of_get_bridge() mtk_dpi_bridge_attach() drm_bridge_attach() mtk_dpi_bind() ... component_add() mtk_dsi_host_attach() anx7625_attach_dsi() anx7625_link_bridge() - done_probing callback for of_dp_aux_populate_bus() of_dp_aux_populate_bus() anx7625_i2c_probe() _cannot_ return probe defer: anx7625 4-0058: [drm:anx7625_bridge_attach] drm attach mediatek-drm mediatek-drm.15.auto: bound 14014000.dsi (ops mtk_dsi_component_ops) mediatek-drm mediatek-drm.15.auto: error -EPROBE_DEFER: failed to attach bridge /soc/dpi@14015000 to encoder TMDS-37 [drm:mtk_dsi_host_attach] *ERROR* failed to add dsi_host component: -517 anx7625 4-0058: [drm:anx7625_link_bridge] *ERROR* fail to attach dsi to host. panel-simple-dp-aux aux-4-0058: DP AUX done_probing() can't defer panel-simple-dp-aux aux-4-0058: probe with driver panel-simple-dp-aux failed with error -22 anx7625 4-0058: [drm:anx7625_i2c_probe] probe done This results in the whole display driver failing to probe. Perhaps this was an attempt to mirror the structure in the DSI driver; but in the DSI driver the next bridge is retrieved in the DSI attach callback, not the bridge attach callback. Move the code finding the next bridge back to the probe function so that deferred probing works correctly. Also rework the fallback to the old OF graph endpoint numbering scheme so that deferred probing logs in both cases. This issue was found on an MT8183 Jacuzzi device with an extra patch enabling the DPI-based external display pipeline. Also tested on an MT8192 Hayato device with both DSI and DPI display pipelines enabled. Fixes: 4c932840db1d ("drm/mediatek: Implement OF graphs support for display paths") Signed-off-by: Chen-Yu Tsai Reviewed-by: CK Hu Link: https://patchwork.kernel.org/project/dri-devel/patch/20260114092243.3914836-1-wenst@chromium.org/ Signed-off-by: Chun-Kuang Hu Signed-off-by: Sasha Levin --- drivers/gpu/drm/mediatek/mtk_dpi.c | 23 +++++++++-------------- 1 file changed, 9 insertions(+), 14 deletions(-) diff --git a/drivers/gpu/drm/mediatek/mtk_dpi.c b/drivers/gpu/drm/mediatek/mtk_dpi.c index 61cab32e213afa..53360b5d12ba56 100644 --- a/drivers/gpu/drm/mediatek/mtk_dpi.c +++ b/drivers/gpu/drm/mediatek/mtk_dpi.c @@ -836,20 +836,6 @@ static int mtk_dpi_bridge_attach(struct drm_bridge *bridge, enum drm_bridge_attach_flags flags) { struct mtk_dpi *dpi = bridge_to_dpi(bridge); - int ret; - - dpi->next_bridge = devm_drm_of_get_bridge(dpi->dev, dpi->dev->of_node, 1, -1); - if (IS_ERR(dpi->next_bridge)) { - ret = PTR_ERR(dpi->next_bridge); - if (ret == -EPROBE_DEFER) - return ret; - - /* Old devicetree has only one endpoint */ - dpi->next_bridge = devm_drm_of_get_bridge(dpi->dev, dpi->dev->of_node, 0, 0); - if (IS_ERR(dpi->next_bridge)) - return dev_err_probe(dpi->dev, PTR_ERR(dpi->next_bridge), - "Failed to get bridge\n"); - } return drm_bridge_attach(encoder, dpi->next_bridge, &dpi->bridge, flags); @@ -1319,6 +1305,15 @@ static int mtk_dpi_probe(struct platform_device *pdev) if (dpi->irq < 0) return dpi->irq; + dpi->next_bridge = devm_drm_of_get_bridge(dpi->dev, dpi->dev->of_node, 1, -1); + if (IS_ERR(dpi->next_bridge) && PTR_ERR(dpi->next_bridge) == -ENODEV) { + /* Old devicetree has only one endpoint */ + dpi->next_bridge = devm_drm_of_get_bridge(dpi->dev, dpi->dev->of_node, 0, 0); + } + if (IS_ERR(dpi->next_bridge)) + return dev_err_probe(dpi->dev, PTR_ERR(dpi->next_bridge), + "Failed to get bridge\n"); + platform_set_drvdata(pdev, dpi); dpi->bridge.of_node = dev->of_node; From 902fd1026ca429298a0d58f4b113d7f880e2bb84 Mon Sep 17 00:00:00 2001 From: Brajesh Gupta Date: Thu, 8 Jan 2026 04:09:36 +0000 Subject: [PATCH 2723/3902] drm/imagination: Wait for FW trace update command completion [ Upstream commit 812062e74a3945b575dce89d330b67cb50054a77 ] Possibility of no FW trace available after update in the fw_trace_mask due to asynchronous mode of command consumption in the FW. To ensure FW trace is available after update, wait for FW trace log update command completion from the FW. Fixes: cc1aeedb98ad ("drm/imagination: Implement firmware infrastructure and META FW support") Signed-off-by: Brajesh Gupta Reviewed-by: Matt Coster Link: https://patch.msgid.link/20260108040936.129769-1-brajesh.gupta@imgtec.com Signed-off-by: Matt Coster Signed-off-by: Sasha Levin --- drivers/gpu/drm/imagination/pvr_fw_trace.c | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/drivers/gpu/drm/imagination/pvr_fw_trace.c b/drivers/gpu/drm/imagination/pvr_fw_trace.c index a1098b5214856d..e7f4554510fd46 100644 --- a/drivers/gpu/drm/imagination/pvr_fw_trace.c +++ b/drivers/gpu/drm/imagination/pvr_fw_trace.c @@ -136,6 +136,7 @@ update_logtype(struct pvr_device *pvr_dev, u32 group_mask) struct rogue_fwif_kccb_cmd cmd; int idx; int err; + int slot; if (group_mask) fw_trace->tracebuf_ctrl->log_type = ROGUE_FWIF_LOG_TYPE_TRACE | group_mask; @@ -153,8 +154,13 @@ update_logtype(struct pvr_device *pvr_dev, u32 group_mask) cmd.cmd_type = ROGUE_FWIF_KCCB_CMD_LOGTYPE_UPDATE; cmd.kccb_flags = 0; - err = pvr_kccb_send_cmd(pvr_dev, &cmd, NULL); + err = pvr_kccb_send_cmd(pvr_dev, &cmd, &slot); + if (err) + goto err_drm_dev_exit; + + err = pvr_kccb_wait_for_completion(pvr_dev, slot, HZ, NULL); +err_drm_dev_exit: drm_dev_exit(idx); err_up_read: From 9eb4e2396cd7f0834bfdbc49cc88e0d3b30b209e Mon Sep 17 00:00:00 2001 From: Michal Luczaj Date: Fri, 16 Jan 2026 09:52:36 +0100 Subject: [PATCH 2724/3902] vsock/test: Do not filter kallsyms by symbol type [ Upstream commit 5d54aa40c7b7e9dee5746cca99e9ddbcca13e895 ] Blamed commit implemented logic to discover available vsock transports by grepping /proc/kallsyms for known symbols. It incorrectly filtered entries by type 'd'. For some kernel configs having CONFIG_VIRTIO_VSOCKETS=m CONFIG_VSOCKETS_LOOPBACK=y kallsyms reports 0000000000000000 d virtio_transport [vmw_vsock_virtio_transport] 0000000000000000 t loopback_transport Overzealous filtering might have affected vsock test suit, resulting in insufficient/misleading testing. Do not filter symbols by type. It never helped much. Fixes: 3070c05b7afd ("vsock/test: Introduce get_transports()") Signed-off-by: Michal Luczaj Reviewed-by: Stefano Garzarella Link: https://patch.msgid.link/20260116-vsock_test-kallsyms-grep-v1-1-3320bc3346f2@rbox.co Signed-off-by: Paolo Abeni Signed-off-by: Sasha Levin --- tools/testing/vsock/util.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/testing/vsock/util.h b/tools/testing/vsock/util.h index 142c02a6834acb..bf633cde82b073 100644 --- a/tools/testing/vsock/util.h +++ b/tools/testing/vsock/util.h @@ -25,7 +25,7 @@ enum transport { }; static const char * const transport_ksyms[] = { - #define x(name, symbol) "d " symbol "_transport", + #define x(name, symbol) " " symbol "_transport", KNOWN_TRANSPORTS(x) #undef x }; From 68462ecc40ea8f780fb3c74ebfddd05506bb731b Mon Sep 17 00:00:00 2001 From: Yun Lu Date: Fri, 16 Jan 2026 17:53:08 +0800 Subject: [PATCH 2725/3902] netdevsim: fix a race issue related to the operation on bpf_bound_progs list [ Upstream commit b97d5eedf4976cc94321243be83b39efe81a0e15 ] The netdevsim driver lacks a protection mechanism for operations on the bpf_bound_progs list. When the nsim_bpf_create_prog() performs list_add_tail, it is possible that nsim_bpf_destroy_prog() is simultaneously performs list_del. Concurrent operations on the list may lead to list corruption and trigger a kernel crash as follows: [ 417.290971] kernel BUG at lib/list_debug.c:62! [ 417.290983] invalid opcode: 0000 [#1] PREEMPT SMP NOPTI [ 417.290992] CPU: 10 PID: 168 Comm: kworker/10:1 Kdump: loaded Not tainted 6.19.0-rc5 #1 [ 417.291003] Hardware name: QEMU Standard PC (Q35 + ICH9, 2009), BIOS 1.16.3-debian-1.16.3-2 04/01/2014 [ 417.291007] Workqueue: events bpf_prog_free_deferred [ 417.291021] RIP: 0010:__list_del_entry_valid_or_report+0xa7/0xc0 [ 417.291034] Code: a8 ff 0f 0b 48 89 fe 48 89 ca 48 c7 c7 48 a1 eb ae e8 ed fb a8 ff 0f 0b 48 89 fe 48 89 c2 48 c7 c7 80 a1 eb ae e8 d9 fb a8 ff <0f> 0b 48 89 d1 48 c7 c7 d0 a1 eb ae 48 89 f2 48 89 c6 e8 c2 fb a8 [ 417.291040] RSP: 0018:ffffb16a40807df8 EFLAGS: 00010246 [ 417.291046] RAX: 000000000000006d RBX: ffff8e589866f500 RCX: 0000000000000000 [ 417.291051] RDX: 0000000000000000 RSI: ffff8e59f7b23180 RDI: ffff8e59f7b23180 [ 417.291055] RBP: ffffb16a412c9000 R08: 0000000000000000 R09: 0000000000000003 [ 417.291059] R10: ffffb16a40807c80 R11: ffffffffaf9edce8 R12: ffff8e594427ac20 [ 417.291063] R13: ffff8e59f7b44780 R14: ffff8e58800b7a05 R15: 0000000000000000 [ 417.291074] FS: 0000000000000000(0000) GS:ffff8e59f7b00000(0000) knlGS:0000000000000000 [ 417.291079] CS: 0010 DS: 0000 ES: 0000 CR0: 0000000080050033 [ 417.291083] CR2: 00007fc4083efe08 CR3: 00000001c3626006 CR4: 0000000000770ee0 [ 417.291088] PKRU: 55555554 [ 417.291091] Call Trace: [ 417.291096] [ 417.291103] nsim_bpf_destroy_prog+0x31/0x80 [netdevsim] [ 417.291154] __bpf_prog_offload_destroy+0x2a/0x80 [ 417.291163] bpf_prog_dev_bound_destroy+0x6f/0xb0 [ 417.291171] bpf_prog_free_deferred+0x18e/0x1a0 [ 417.291178] process_one_work+0x18a/0x3a0 [ 417.291188] worker_thread+0x27b/0x3a0 [ 417.291197] ? __pfx_worker_thread+0x10/0x10 [ 417.291207] kthread+0xe5/0x120 [ 417.291214] ? __pfx_kthread+0x10/0x10 [ 417.291221] ret_from_fork+0x31/0x50 [ 417.291230] ? __pfx_kthread+0x10/0x10 [ 417.291236] ret_from_fork_asm+0x1a/0x30 [ 417.291246] Add a mutex lock, to prevent simultaneous addition and deletion operations on the list. Fixes: 31d3ad832948 ("netdevsim: add bpf offload support") Reported-by: Yinhao Hu Reported-by: Kaiyan Mei Signed-off-by: Yun Lu Link: https://patch.msgid.link/20260116095308.11441-1-luyun_611@163.com Signed-off-by: Paolo Abeni Signed-off-by: Sasha Levin --- drivers/net/netdevsim/bpf.c | 6 ++++++ drivers/net/netdevsim/dev.c | 2 ++ drivers/net/netdevsim/netdevsim.h | 1 + 3 files changed, 9 insertions(+) diff --git a/drivers/net/netdevsim/bpf.c b/drivers/net/netdevsim/bpf.c index 49537d3c41205c..5f17f68f3c0838 100644 --- a/drivers/net/netdevsim/bpf.c +++ b/drivers/net/netdevsim/bpf.c @@ -244,7 +244,9 @@ static int nsim_bpf_create_prog(struct nsim_dev *nsim_dev, &state->state, &nsim_bpf_string_fops); debugfs_create_bool("loaded", 0400, state->ddir, &state->is_loaded); + mutex_lock(&nsim_dev->progs_list_lock); list_add_tail(&state->l, &nsim_dev->bpf_bound_progs); + mutex_unlock(&nsim_dev->progs_list_lock); prog->aux->offload->dev_priv = state; @@ -273,12 +275,16 @@ static int nsim_bpf_translate(struct bpf_prog *prog) static void nsim_bpf_destroy_prog(struct bpf_prog *prog) { struct nsim_bpf_bound_prog *state; + struct nsim_dev *nsim_dev; state = prog->aux->offload->dev_priv; + nsim_dev = state->nsim_dev; WARN(state->is_loaded, "offload state destroyed while program still bound"); debugfs_remove_recursive(state->ddir); + mutex_lock(&nsim_dev->progs_list_lock); list_del(&state->l); + mutex_unlock(&nsim_dev->progs_list_lock); kfree(state); } diff --git a/drivers/net/netdevsim/dev.c b/drivers/net/netdevsim/dev.c index 95f66c1f59db88..56a47c060f2e19 100644 --- a/drivers/net/netdevsim/dev.c +++ b/drivers/net/netdevsim/dev.c @@ -1591,6 +1591,7 @@ int nsim_drv_probe(struct nsim_bus_dev *nsim_bus_dev) nsim_dev->max_macs = NSIM_DEV_MAX_MACS_DEFAULT; nsim_dev->test1 = NSIM_DEV_TEST1_DEFAULT; spin_lock_init(&nsim_dev->fa_cookie_lock); + mutex_init(&nsim_dev->progs_list_lock); dev_set_drvdata(&nsim_bus_dev->dev, nsim_dev); @@ -1729,6 +1730,7 @@ void nsim_drv_remove(struct nsim_bus_dev *nsim_bus_dev) devl_unregister(devlink); kfree(nsim_dev->vfconfigs); kfree(nsim_dev->fa_cookie); + mutex_destroy(&nsim_dev->progs_list_lock); devl_unlock(devlink); devlink_free(devlink); dev_set_drvdata(&nsim_bus_dev->dev, NULL); diff --git a/drivers/net/netdevsim/netdevsim.h b/drivers/net/netdevsim/netdevsim.h index 02c1c97b700802..d91c0899e536e7 100644 --- a/drivers/net/netdevsim/netdevsim.h +++ b/drivers/net/netdevsim/netdevsim.h @@ -319,6 +319,7 @@ struct nsim_dev { u32 prog_id_gen; struct list_head bpf_bound_progs; struct list_head bpf_bound_maps; + struct mutex progs_list_lock; struct netdev_phys_item_id switch_id; struct list_head port_list; bool fw_update_status; From ec0bfc3ed7e7f619f8a3955785f714e9f0876533 Mon Sep 17 00:00:00 2001 From: Cody Haas Date: Fri, 12 Dec 2025 16:22:26 -0800 Subject: [PATCH 2726/3902] ice: Fix persistent failure in ice_get_rxfh [ Upstream commit f406220eb8e227ca344eef1a6d30aff53706b196 ] Several ioctl functions have the ability to call ice_get_rxfh, however all of these ioctl functions do not provide all of the expected information in ethtool_rxfh_param. For example, ethtool_get_rxfh_indir does not provide an rss_key. This previously caused ethtool_get_rxfh_indir to always fail with -EINVAL. This change draws inspiration from i40e_get_rss to handle this situation, by only calling the appropriate rss helpers when the necessary information has been provided via ethtool_rxfh_param. Fixes: b66a972abb6b ("ice: Refactor ice_set/get_rss into LUT and key specific functions") Signed-off-by: Cody Haas Closes: https://lore.kernel.org/intel-wired-lan/CAH7f-UKkJV8MLY7zCdgCrGE55whRhbGAXvgkDnwgiZ9gUZT7_w@mail.gmail.com/ Reviewed-by: Aleksandr Loktionov Reviewed-by: Przemek Kitszel Tested-by: Rinitha S (A Contingent worker at Intel) Signed-off-by: Tony Nguyen Signed-off-by: Sasha Levin --- drivers/net/ethernet/intel/ice/ice.h | 1 + drivers/net/ethernet/intel/ice/ice_ethtool.c | 6 +---- drivers/net/ethernet/intel/ice/ice_main.c | 28 ++++++++++++++++++++ 3 files changed, 30 insertions(+), 5 deletions(-) diff --git a/drivers/net/ethernet/intel/ice/ice.h b/drivers/net/ethernet/intel/ice/ice.h index 9ee596773f34e7..a23ccd4ba08d2c 100644 --- a/drivers/net/ethernet/intel/ice/ice.h +++ b/drivers/net/ethernet/intel/ice/ice.h @@ -977,6 +977,7 @@ void ice_map_xdp_rings(struct ice_vsi *vsi); int ice_xdp_xmit(struct net_device *dev, int n, struct xdp_frame **frames, u32 flags); +int ice_get_rss(struct ice_vsi *vsi, u8 *seed, u8 *lut, u16 lut_size); int ice_set_rss_lut(struct ice_vsi *vsi, u8 *lut, u16 lut_size); int ice_get_rss_lut(struct ice_vsi *vsi, u8 *lut, u16 lut_size); int ice_set_rss_key(struct ice_vsi *vsi, u8 *seed); diff --git a/drivers/net/ethernet/intel/ice/ice_ethtool.c b/drivers/net/ethernet/intel/ice/ice_ethtool.c index dc131779d4267a..06b5677e9bff81 100644 --- a/drivers/net/ethernet/intel/ice/ice_ethtool.c +++ b/drivers/net/ethernet/intel/ice/ice_ethtool.c @@ -3621,11 +3621,7 @@ ice_get_rxfh(struct net_device *netdev, struct ethtool_rxfh_param *rxfh) if (!lut) return -ENOMEM; - err = ice_get_rss_key(vsi, rxfh->key); - if (err) - goto out; - - err = ice_get_rss_lut(vsi, lut, vsi->rss_table_size); + err = ice_get_rss(vsi, rxfh->key, lut, vsi->rss_table_size); if (err) goto out; diff --git a/drivers/net/ethernet/intel/ice/ice_main.c b/drivers/net/ethernet/intel/ice/ice_main.c index b0f8a96c13b471..6c392495f4a76f 100644 --- a/drivers/net/ethernet/intel/ice/ice_main.c +++ b/drivers/net/ethernet/intel/ice/ice_main.c @@ -8002,6 +8002,34 @@ int ice_get_rss_key(struct ice_vsi *vsi, u8 *seed) return status; } +/** + * ice_get_rss - Get RSS LUT and/or key + * @vsi: Pointer to VSI structure + * @seed: Buffer to store the key in + * @lut: Buffer to store the lookup table entries + * @lut_size: Size of buffer to store the lookup table entries + * + * Return: 0 on success, negative on failure + */ +int ice_get_rss(struct ice_vsi *vsi, u8 *seed, u8 *lut, u16 lut_size) +{ + int err; + + if (seed) { + err = ice_get_rss_key(vsi, seed); + if (err) + return err; + } + + if (lut) { + err = ice_get_rss_lut(vsi, lut, lut_size); + if (err) + return err; + } + + return 0; +} + /** * ice_set_rss_hfunc - Set RSS HASH function * @vsi: Pointer to VSI structure From a3d99e2fbf01446d31a0d0dfc46444e915a1f6d4 Mon Sep 17 00:00:00 2001 From: Paul Greenwalt Date: Thu, 18 Dec 2025 08:36:53 -0500 Subject: [PATCH 2727/3902] ice: add missing ice_deinit_hw() in devlink reinit path [ Upstream commit 42fb5f3deb582cb96440e4683745017dbabb83d6 ] devlink-reload results in ice_init_hw failed error, and then removing the ice driver causes a NULL pointer dereference. [ +0.102213] ice 0000:ca:00.0: ice_init_hw failed: -16 ... [ +0.000001] Call Trace: [ +0.000003] [ +0.000006] ice_unload+0x8f/0x100 [ice] [ +0.000081] ice_remove+0xba/0x300 [ice] Commit 1390b8b3d2be ("ice: remove duplicate call to ice_deinit_hw() on error paths") removed ice_deinit_hw() from ice_deinit_dev(). As a result ice_devlink_reinit_down() no longer calls ice_deinit_hw(), but ice_devlink_reinit_up() still calls ice_init_hw(). Since the control queues are not uninitialized, ice_init_hw() fails with -EBUSY. Add ice_deinit_hw() to ice_devlink_reinit_down() to correspond with ice_init_hw() in ice_devlink_reinit_up(). Fixes: 1390b8b3d2be ("ice: remove duplicate call to ice_deinit_hw() on error paths") Reviewed-by: Aleksandr Loktionov Reviewed-by: Przemek Kitszel Signed-off-by: Paul Greenwalt Reviewed-by: Paul Menzel Tested-by: Rinitha S (A Contingent worker at Intel) Signed-off-by: Tony Nguyen Signed-off-by: Sasha Levin --- drivers/net/ethernet/intel/ice/devlink/devlink.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/net/ethernet/intel/ice/devlink/devlink.c b/drivers/net/ethernet/intel/ice/devlink/devlink.c index 938914abbe0667..ac071c5b4ce387 100644 --- a/drivers/net/ethernet/intel/ice/devlink/devlink.c +++ b/drivers/net/ethernet/intel/ice/devlink/devlink.c @@ -460,6 +460,7 @@ static void ice_devlink_reinit_down(struct ice_pf *pf) ice_vsi_decfg(ice_get_main_vsi(pf)); rtnl_unlock(); ice_deinit_pf(pf); + ice_deinit_hw(&pf->hw); ice_deinit_dev(pf); } From 87c1dacca197cc64e06fedeb269e3dd6699bae60 Mon Sep 17 00:00:00 2001 From: Paul Greenwalt Date: Mon, 29 Dec 2025 03:52:34 -0500 Subject: [PATCH 2728/3902] ice: fix devlink reload call trace [ Upstream commit d3f867e7a04678640ebcbfb81893c59f4af48586 ] Commit 4da71a77fc3b ("ice: read internal temperature sensor") introduced internal temperature sensor reading via HWMON. ice_hwmon_init() was added to ice_init_feature() and ice_hwmon_exit() was added to ice_remove(). As a result if devlink reload is used to reinit the device and then the driver is removed, a call trace can occur. BUG: unable to handle page fault for address: ffffffffc0fd4b5d Call Trace: string+0x48/0xe0 vsnprintf+0x1f9/0x650 sprintf+0x62/0x80 name_show+0x1f/0x30 dev_attr_show+0x19/0x60 The call trace repeats approximately every 10 minutes when system monitoring tools (e.g., sadc) attempt to read the orphaned hwmon sysfs attributes that reference freed module memory. The sequence is: 1. Driver load, ice_hwmon_init() gets called from ice_init_feature() 2. Devlink reload down, flow does not call ice_remove() 3. Devlink reload up, ice_hwmon_init() gets called from ice_init_feature() resulting in a second instance 4. Driver unload, ice_hwmon_exit() called from ice_remove() leaving the first hwmon instance orphaned with dangling pointer Fix this by moving ice_hwmon_exit() from ice_remove() to ice_deinit_features() to ensure proper cleanup symmetry with ice_hwmon_init(). Fixes: 4da71a77fc3b ("ice: read internal temperature sensor") Reviewed-by: Aleksandr Loktionov Signed-off-by: Paul Greenwalt Reviewed-by: Paul Menzel Tested-by: Rinitha S (A Contingent worker at Intel) Signed-off-by: Tony Nguyen Signed-off-by: Sasha Levin --- drivers/net/ethernet/intel/ice/ice_main.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/drivers/net/ethernet/intel/ice/ice_main.c b/drivers/net/ethernet/intel/ice/ice_main.c index 6c392495f4a76f..fc284802e2bcd7 100644 --- a/drivers/net/ethernet/intel/ice/ice_main.c +++ b/drivers/net/ethernet/intel/ice/ice_main.c @@ -4845,6 +4845,7 @@ static void ice_deinit_features(struct ice_pf *pf) ice_dpll_deinit(pf); if (pf->eswitch_mode == DEVLINK_ESWITCH_MODE_SWITCHDEV) xa_destroy(&pf->eswitch.reprs); + ice_hwmon_exit(pf); } static void ice_init_wakeup(struct ice_pf *pf) @@ -5446,8 +5447,6 @@ static void ice_remove(struct pci_dev *pdev) ice_free_vfs(pf); } - ice_hwmon_exit(pf); - if (!ice_is_safe_mode(pf)) ice_remove_arfs(pf); From 2ea06ebaa4abda0bc41582ef1c99eb46464e95c7 Mon Sep 17 00:00:00 2001 From: Mina Almasry Date: Thu, 11 Dec 2025 10:19:29 +0000 Subject: [PATCH 2729/3902] idpf: read lower clock bits inside the time sandwich [ Upstream commit bdfc7b55adcd04834ccc1b6b13e55e3fd7eaa789 ] PCIe reads need to be done inside the time sandwich because PCIe writes may get buffered in the PCIe fabric and posted to the device after the _postts completes. Doing the PCIe read inside the time sandwich guarantees that the write gets flushed before the _postts timestamp is taken. Cc: lrizzo@google.com Cc: namangulati@google.com Cc: willemb@google.com Cc: intel-wired-lan@lists.osuosl.org Cc: milena.olech@intel.com Cc: jacob.e.keller@intel.com Fixes: 5cb8805d2366 ("idpf: negotiate PTP capabilities and get PTP clock") Suggested-by: Shachar Raindel Signed-off-by: Mina Almasry Reviewed-by: Jacob Keller Reviewed-by: Aleksandr Loktionov Tested-by: Samuel Salin Signed-off-by: Tony Nguyen Signed-off-by: Sasha Levin --- drivers/net/ethernet/intel/idpf/idpf_ptp.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/net/ethernet/intel/idpf/idpf_ptp.c b/drivers/net/ethernet/intel/idpf/idpf_ptp.c index 3e1052d070cfdf..0a8b50350b8604 100644 --- a/drivers/net/ethernet/intel/idpf/idpf_ptp.c +++ b/drivers/net/ethernet/intel/idpf/idpf_ptp.c @@ -108,11 +108,11 @@ static u64 idpf_ptp_read_src_clk_reg_direct(struct idpf_adapter *adapter, ptp_read_system_prets(sts); idpf_ptp_enable_shtime(adapter); + lo = readl(ptp->dev_clk_regs.dev_clk_ns_l); /* Read the system timestamp post PHC read */ ptp_read_system_postts(sts); - lo = readl(ptp->dev_clk_regs.dev_clk_ns_l); hi = readl(ptp->dev_clk_regs.dev_clk_ns_h); spin_unlock(&ptp->read_dev_clk_lock); From 1f24dfd556401b75f78e8d9cbd94dd9f31411c3a Mon Sep 17 00:00:00 2001 From: Daniel Golle Date: Mon, 19 Jan 2026 00:41:54 +0000 Subject: [PATCH 2730/3902] net: phy: intel-xway: fix OF node refcount leakage [ Upstream commit 79912b256e14054e6ba177d7e7e631485ce23dbe ] Automated review spotted am OF node reference count leakage when checking if the 'leds' child node exists. Call of_put_node() to correctly maintain the refcount. Link: https://netdev-ai.bots.linux.dev/ai-review.html?id=20f173ba-0c64-422b-a663-fea4b4ad01d0 Fixes: 1758af47b98c1 ("net: phy: intel-xway: add support for PHY LEDs") Signed-off-by: Daniel Golle Link: https://patch.msgid.link/e3275e1c1cdca7e6426bb9c11f33bd84b8d900c8.1768783208.git.daniel@makrotopia.org Signed-off-by: Jakub Kicinski Signed-off-by: Sasha Levin --- drivers/net/phy/intel-xway.c | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/drivers/net/phy/intel-xway.c b/drivers/net/phy/intel-xway.c index 9766dd99afaa0a..12ff4c1f285d24 100644 --- a/drivers/net/phy/intel-xway.c +++ b/drivers/net/phy/intel-xway.c @@ -277,7 +277,7 @@ static int xway_gphy_init_leds(struct phy_device *phydev) static int xway_gphy_config_init(struct phy_device *phydev) { - struct device_node *np = phydev->mdio.dev.of_node; + struct device_node *np; int err; /* Mask all interrupts */ @@ -286,7 +286,10 @@ static int xway_gphy_config_init(struct phy_device *phydev) return err; /* Use default LED configuration if 'leds' node isn't defined */ - if (!of_get_child_by_name(np, "leds")) + np = of_get_child_by_name(phydev->mdio.dev.of_node, "leds"); + if (np) + of_node_put(np); + else xway_gphy_init_leds(phydev); /* Clear all pending interrupts */ From b29f51399626ba4659fe9afe0154ddc78d807c25 Mon Sep 17 00:00:00 2001 From: David Yang Date: Tue, 20 Jan 2026 00:07:37 +0800 Subject: [PATCH 2731/3902] net: hns3: fix data race in hns3_fetch_stats [ Upstream commit 748a81c8ceda1fdbdcd0af595947422e810442aa ] In hns3_fetch_stats(), ring statistics, protected by u64_stats_sync, are read and accumulated in ignorance of possible u64_stats_fetch_retry() events. These statistics are already accumulated by hns3_ring_stats_update(). Fix this by reading them into a temporary buffer first. Fixes: b20d7fe51e0d ("net: hns3: add some statitics info to tx process") Signed-off-by: David Yang Link: https://patch.msgid.link/20260119160759.1455950-1-mmyangfl@gmail.com Signed-off-by: Jakub Kicinski Signed-off-by: Sasha Levin --- .../net/ethernet/hisilicon/hns3/hns3_enet.c | 69 ++++++++++--------- 1 file changed, 36 insertions(+), 33 deletions(-) diff --git a/drivers/net/ethernet/hisilicon/hns3/hns3_enet.c b/drivers/net/ethernet/hisilicon/hns3/hns3_enet.c index bfa5568baa926b..e976a88b952f01 100644 --- a/drivers/net/ethernet/hisilicon/hns3/hns3_enet.c +++ b/drivers/net/ethernet/hisilicon/hns3/hns3_enet.c @@ -2500,44 +2500,47 @@ static netdev_features_t hns3_features_check(struct sk_buff *skb, static void hns3_fetch_stats(struct rtnl_link_stats64 *stats, struct hns3_enet_ring *ring, bool is_tx) { + struct ring_stats ring_stats; unsigned int start; do { start = u64_stats_fetch_begin(&ring->syncp); - if (is_tx) { - stats->tx_bytes += ring->stats.tx_bytes; - stats->tx_packets += ring->stats.tx_pkts; - stats->tx_dropped += ring->stats.sw_err_cnt; - stats->tx_dropped += ring->stats.tx_vlan_err; - stats->tx_dropped += ring->stats.tx_l4_proto_err; - stats->tx_dropped += ring->stats.tx_l2l3l4_err; - stats->tx_dropped += ring->stats.tx_tso_err; - stats->tx_dropped += ring->stats.over_max_recursion; - stats->tx_dropped += ring->stats.hw_limitation; - stats->tx_dropped += ring->stats.copy_bits_err; - stats->tx_dropped += ring->stats.skb2sgl_err; - stats->tx_dropped += ring->stats.map_sg_err; - stats->tx_errors += ring->stats.sw_err_cnt; - stats->tx_errors += ring->stats.tx_vlan_err; - stats->tx_errors += ring->stats.tx_l4_proto_err; - stats->tx_errors += ring->stats.tx_l2l3l4_err; - stats->tx_errors += ring->stats.tx_tso_err; - stats->tx_errors += ring->stats.over_max_recursion; - stats->tx_errors += ring->stats.hw_limitation; - stats->tx_errors += ring->stats.copy_bits_err; - stats->tx_errors += ring->stats.skb2sgl_err; - stats->tx_errors += ring->stats.map_sg_err; - } else { - stats->rx_bytes += ring->stats.rx_bytes; - stats->rx_packets += ring->stats.rx_pkts; - stats->rx_dropped += ring->stats.l2_err; - stats->rx_errors += ring->stats.l2_err; - stats->rx_errors += ring->stats.l3l4_csum_err; - stats->rx_crc_errors += ring->stats.l2_err; - stats->multicast += ring->stats.rx_multicast; - stats->rx_length_errors += ring->stats.err_pkt_len; - } + ring_stats = ring->stats; } while (u64_stats_fetch_retry(&ring->syncp, start)); + + if (is_tx) { + stats->tx_bytes += ring_stats.tx_bytes; + stats->tx_packets += ring_stats.tx_pkts; + stats->tx_dropped += ring_stats.sw_err_cnt; + stats->tx_dropped += ring_stats.tx_vlan_err; + stats->tx_dropped += ring_stats.tx_l4_proto_err; + stats->tx_dropped += ring_stats.tx_l2l3l4_err; + stats->tx_dropped += ring_stats.tx_tso_err; + stats->tx_dropped += ring_stats.over_max_recursion; + stats->tx_dropped += ring_stats.hw_limitation; + stats->tx_dropped += ring_stats.copy_bits_err; + stats->tx_dropped += ring_stats.skb2sgl_err; + stats->tx_dropped += ring_stats.map_sg_err; + stats->tx_errors += ring_stats.sw_err_cnt; + stats->tx_errors += ring_stats.tx_vlan_err; + stats->tx_errors += ring_stats.tx_l4_proto_err; + stats->tx_errors += ring_stats.tx_l2l3l4_err; + stats->tx_errors += ring_stats.tx_tso_err; + stats->tx_errors += ring_stats.over_max_recursion; + stats->tx_errors += ring_stats.hw_limitation; + stats->tx_errors += ring_stats.copy_bits_err; + stats->tx_errors += ring_stats.skb2sgl_err; + stats->tx_errors += ring_stats.map_sg_err; + } else { + stats->rx_bytes += ring_stats.rx_bytes; + stats->rx_packets += ring_stats.rx_pkts; + stats->rx_dropped += ring_stats.l2_err; + stats->rx_errors += ring_stats.l2_err; + stats->rx_errors += ring_stats.l3l4_csum_err; + stats->rx_crc_errors += ring_stats.l2_err; + stats->multicast += ring_stats.rx_multicast; + stats->rx_length_errors += ring_stats.err_pkt_len; + } } static void hns3_nic_get_stats64(struct net_device *netdev, From 7539ae6c67a75fed41d23cbd7c49a82ca7f7174c Mon Sep 17 00:00:00 2001 From: David Yang Date: Tue, 20 Jan 2026 00:27:16 +0800 Subject: [PATCH 2732/3902] idpf: Fix data race in idpf_net_dim [ Upstream commit 5fbe395cd1fdbc883584e7f38369e4ba5ca778d2 ] In idpf_net_dim(), some statistics protected by u64_stats_sync, are read and accumulated in ignorance of possible u64_stats_fetch_retry() events. The correct way to copy statistics is already illustrated by idpf_add_queue_stats(). Fix this by reading them into temporary variables first. Fixes: c2d548cad150 ("idpf: add TX splitq napi poll support") Fixes: 3a8845af66ed ("idpf: add RX splitq napi poll support") Signed-off-by: David Yang Reviewed-by: Eric Dumazet Link: https://patch.msgid.link/20260119162720.1463859-1-mmyangfl@gmail.com Signed-off-by: Jakub Kicinski Signed-off-by: Sasha Levin --- drivers/net/ethernet/intel/idpf/idpf_txrx.c | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/drivers/net/ethernet/intel/idpf/idpf_txrx.c b/drivers/net/ethernet/intel/idpf/idpf_txrx.c index f66948f5de78bb..a48088eb9b8220 100644 --- a/drivers/net/ethernet/intel/idpf/idpf_txrx.c +++ b/drivers/net/ethernet/intel/idpf/idpf_txrx.c @@ -3941,7 +3941,7 @@ static void idpf_update_dim_sample(struct idpf_q_vector *q_vector, static void idpf_net_dim(struct idpf_q_vector *q_vector) { struct dim_sample dim_sample = { }; - u64 packets, bytes; + u64 packets, bytes, pkts, bts; u32 i; if (!IDPF_ITR_IS_DYNAMIC(q_vector->tx_intr_mode)) @@ -3953,9 +3953,12 @@ static void idpf_net_dim(struct idpf_q_vector *q_vector) do { start = u64_stats_fetch_begin(&txq->stats_sync); - packets += u64_stats_read(&txq->q_stats.packets); - bytes += u64_stats_read(&txq->q_stats.bytes); + pkts = u64_stats_read(&txq->q_stats.packets); + bts = u64_stats_read(&txq->q_stats.bytes); } while (u64_stats_fetch_retry(&txq->stats_sync, start)); + + packets += pkts; + bytes += bts; } idpf_update_dim_sample(q_vector, &dim_sample, &q_vector->tx_dim, @@ -3972,9 +3975,12 @@ static void idpf_net_dim(struct idpf_q_vector *q_vector) do { start = u64_stats_fetch_begin(&rxq->stats_sync); - packets += u64_stats_read(&rxq->q_stats.packets); - bytes += u64_stats_read(&rxq->q_stats.bytes); + pkts = u64_stats_read(&rxq->q_stats.packets); + bts = u64_stats_read(&rxq->q_stats.bytes); } while (u64_stats_fetch_retry(&rxq->stats_sync, start)); + + packets += pkts; + bytes += bts; } idpf_update_dim_sample(q_vector, &dim_sample, &q_vector->rx_dim, From 5e5dae04ef971d58ffe15dea7108355237b112b5 Mon Sep 17 00:00:00 2001 From: David Yang Date: Mon, 19 Jan 2026 23:34:36 +0800 Subject: [PATCH 2733/3902] be2net: fix data race in be_get_new_eqd [ Upstream commit 302e5b481caa7b3d11ec0e058434c1fc95195e50 ] In be_get_new_eqd(), statistics of pkts, protected by u64_stats_sync, are read and accumulated in ignorance of possible u64_stats_fetch_retry() events. Before the commit in question, these statistics were retrieved one by one directly from queues. Fix this by reading them into temporary variables first. Fixes: 209477704187 ("be2net: set interrupt moderation for Skyhawk-R using EQ-DB") Signed-off-by: David Yang Reviewed-by: Vadim Fedorenko Link: https://patch.msgid.link/20260119153440.1440578-1-mmyangfl@gmail.com Signed-off-by: Jakub Kicinski Signed-off-by: Sasha Levin --- drivers/net/ethernet/emulex/benet/be_main.c | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/drivers/net/ethernet/emulex/benet/be_main.c b/drivers/net/ethernet/emulex/benet/be_main.c index 5bb31c8fab3913..995c159003d790 100644 --- a/drivers/net/ethernet/emulex/benet/be_main.c +++ b/drivers/net/ethernet/emulex/benet/be_main.c @@ -2141,7 +2141,7 @@ static int be_get_new_eqd(struct be_eq_obj *eqo) struct be_aic_obj *aic; struct be_rx_obj *rxo; struct be_tx_obj *txo; - u64 rx_pkts = 0, tx_pkts = 0; + u64 rx_pkts = 0, tx_pkts = 0, pkts; ulong now; u32 pps, delta; int i; @@ -2157,15 +2157,17 @@ static int be_get_new_eqd(struct be_eq_obj *eqo) for_all_rx_queues_on_eq(adapter, eqo, rxo, i) { do { start = u64_stats_fetch_begin(&rxo->stats.sync); - rx_pkts += rxo->stats.rx_pkts; + pkts = rxo->stats.rx_pkts; } while (u64_stats_fetch_retry(&rxo->stats.sync, start)); + rx_pkts += pkts; } for_all_tx_queues_on_eq(adapter, eqo, txo, i) { do { start = u64_stats_fetch_begin(&txo->stats.sync); - tx_pkts += txo->stats.tx_reqs; + pkts = txo->stats.tx_reqs; } while (u64_stats_fetch_retry(&txo->stats.sync, start)); + tx_pkts += pkts; } /* Skip, if wrapped around or first calculation */ From bb90d1799073db261ca2cb46900dc686e08fee5c Mon Sep 17 00:00:00 2001 From: Jijie Shao Date: Mon, 19 Jan 2026 21:28:39 +0800 Subject: [PATCH 2734/3902] net: hns3: fix wrong GENMASK() for HCLGE_FD_AD_COUNTER_NUM_M [ Upstream commit d57c67c956a1bad15115eba6e59d77a6dfeba01d ] HCLGE_FD_AD_COUNTER_NUM_M should be at GENMASK(19, 13), rather than at GENMASK(20, 13), because bit 20 is HCLGE_FD_AD_NXT_STEP_B. This patch corrects the wrong definition. Fixes: 117328680288 ("net: hns3: Add input key and action config support for flow director") Signed-off-by: Jijie Shao Link: https://patch.msgid.link/20260119132840.410513-2-shaojijie@huawei.com Signed-off-by: Jakub Kicinski Signed-off-by: Sasha Levin --- drivers/net/ethernet/hisilicon/hns3/hns3pf/hclge_cmd.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/net/ethernet/hisilicon/hns3/hns3pf/hclge_cmd.h b/drivers/net/ethernet/hisilicon/hns3/hns3pf/hclge_cmd.h index 9bb708fa42f24e..416e02e7b995fc 100644 --- a/drivers/net/ethernet/hisilicon/hns3/hns3pf/hclge_cmd.h +++ b/drivers/net/ethernet/hisilicon/hns3/hns3pf/hclge_cmd.h @@ -731,7 +731,7 @@ struct hclge_fd_tcam_config_3_cmd { #define HCLGE_FD_AD_QID_M GENMASK(11, 2) #define HCLGE_FD_AD_USE_COUNTER_B 12 #define HCLGE_FD_AD_COUNTER_NUM_S 13 -#define HCLGE_FD_AD_COUNTER_NUM_M GENMASK(20, 13) +#define HCLGE_FD_AD_COUNTER_NUM_M GENMASK(19, 13) #define HCLGE_FD_AD_NXT_STEP_B 20 #define HCLGE_FD_AD_NXT_KEY_S 21 #define HCLGE_FD_AD_NXT_KEY_M GENMASK(25, 21) From d7beeb64be5ca0a0009e52b84075aa0678eca4f4 Mon Sep 17 00:00:00 2001 From: Jijie Shao Date: Mon, 19 Jan 2026 21:28:40 +0800 Subject: [PATCH 2735/3902] net: hns3: fix the HCLGE_FD_AD_NXT_KEY error setting issue [ Upstream commit f87e034d16e43af984380a95c32c25201b7759a7 ] Use next_input_key instead of counter_id to set HCLGE_FD_AD_NXT_KEY. Fixes: 117328680288 ("net: hns3: Add input key and action config support for flow director") Signed-off-by: Jijie Shao Link: https://patch.msgid.link/20260119132840.410513-3-shaojijie@huawei.com Signed-off-by: Jakub Kicinski Signed-off-by: Sasha Levin --- drivers/net/ethernet/hisilicon/hns3/hns3pf/hclge_main.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/net/ethernet/hisilicon/hns3/hns3pf/hclge_main.c b/drivers/net/ethernet/hisilicon/hns3/hns3pf/hclge_main.c index 1b103d1154da96..5cc5ee9dcd982d 100644 --- a/drivers/net/ethernet/hisilicon/hns3/hns3pf/hclge_main.c +++ b/drivers/net/ethernet/hisilicon/hns3/hns3pf/hclge_main.c @@ -5690,7 +5690,7 @@ static int hclge_fd_ad_config(struct hclge_dev *hdev, u8 stage, int loc, HCLGE_FD_AD_COUNTER_NUM_S, action->counter_id); hnae3_set_bit(ad_data, HCLGE_FD_AD_NXT_STEP_B, action->use_next_stage); hnae3_set_field(ad_data, HCLGE_FD_AD_NXT_KEY_M, HCLGE_FD_AD_NXT_KEY_S, - action->counter_id); + action->next_input_key); req->ad_data = cpu_to_le64(ad_data); ret = hclge_cmd_send(&hdev->hw, &desc, 1); From 7ac345a93af31358e18e9606eb7b354691bf6757 Mon Sep 17 00:00:00 2001 From: Eric Dumazet Date: Sun, 18 Jan 2026 13:25:28 +0000 Subject: [PATCH 2736/3902] mISDN: annotate data-race around dev->work [ Upstream commit 8175dbf174d487afab81e936a862a8d9b8a1ccb6 ] dev->work can re read locklessly in mISDN_read() and mISDN_poll(). Add READ_ONCE()/WRITE_ONCE() annotations. BUG: KCSAN: data-race in mISDN_ioctl / mISDN_read write to 0xffff88812d848280 of 4 bytes by task 10864 on cpu 1: misdn_add_timer drivers/isdn/mISDN/timerdev.c:175 [inline] mISDN_ioctl+0x2fb/0x550 drivers/isdn/mISDN/timerdev.c:233 vfs_ioctl fs/ioctl.c:51 [inline] __do_sys_ioctl fs/ioctl.c:597 [inline] __se_sys_ioctl+0xce/0x140 fs/ioctl.c:583 __x64_sys_ioctl+0x43/0x50 fs/ioctl.c:583 x64_sys_call+0x14b0/0x3000 arch/x86/include/generated/asm/syscalls_64.h:17 do_syscall_x64 arch/x86/entry/syscall_64.c:63 [inline] do_syscall_64+0xd8/0x2c0 arch/x86/entry/syscall_64.c:94 entry_SYSCALL_64_after_hwframe+0x77/0x7f read to 0xffff88812d848280 of 4 bytes by task 10857 on cpu 0: mISDN_read+0x1f2/0x470 drivers/isdn/mISDN/timerdev.c:112 do_loop_readv_writev fs/read_write.c:847 [inline] vfs_readv+0x3fb/0x690 fs/read_write.c:1020 do_readv+0xe7/0x210 fs/read_write.c:1080 __do_sys_readv fs/read_write.c:1165 [inline] __se_sys_readv fs/read_write.c:1162 [inline] __x64_sys_readv+0x45/0x50 fs/read_write.c:1162 x64_sys_call+0x2831/0x3000 arch/x86/include/generated/asm/syscalls_64.h:20 do_syscall_x64 arch/x86/entry/syscall_64.c:63 [inline] do_syscall_64+0xd8/0x2c0 arch/x86/entry/syscall_64.c:94 entry_SYSCALL_64_after_hwframe+0x77/0x7f value changed: 0x00000000 -> 0x00000001 Fixes: 1b2b03f8e514 ("Add mISDN core files") Reported-by: syzbot Signed-off-by: Eric Dumazet Link: https://patch.msgid.link/20260118132528.2349573-1-edumazet@google.com Signed-off-by: Jakub Kicinski Signed-off-by: Sasha Levin --- drivers/isdn/mISDN/timerdev.c | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/drivers/isdn/mISDN/timerdev.c b/drivers/isdn/mISDN/timerdev.c index df98144a953946..33521c328a8273 100644 --- a/drivers/isdn/mISDN/timerdev.c +++ b/drivers/isdn/mISDN/timerdev.c @@ -109,14 +109,14 @@ mISDN_read(struct file *filep, char __user *buf, size_t count, loff_t *off) spin_unlock_irq(&dev->lock); if (filep->f_flags & O_NONBLOCK) return -EAGAIN; - wait_event_interruptible(dev->wait, (dev->work || + wait_event_interruptible(dev->wait, (READ_ONCE(dev->work) || !list_empty(list))); if (signal_pending(current)) return -ERESTARTSYS; spin_lock_irq(&dev->lock); } if (dev->work) - dev->work = 0; + WRITE_ONCE(dev->work, 0); if (!list_empty(list)) { timer = list_first_entry(list, struct mISDNtimer, list); list_del(&timer->list); @@ -141,13 +141,16 @@ mISDN_poll(struct file *filep, poll_table *wait) if (*debug & DEBUG_TIMER) printk(KERN_DEBUG "%s(%p, %p)\n", __func__, filep, wait); if (dev) { + u32 work; + poll_wait(filep, &dev->wait, wait); mask = 0; - if (dev->work || !list_empty(&dev->expired)) + work = READ_ONCE(dev->work); + if (work || !list_empty(&dev->expired)) mask |= (EPOLLIN | EPOLLRDNORM); if (*debug & DEBUG_TIMER) printk(KERN_DEBUG "%s work(%d) empty(%d)\n", __func__, - dev->work, list_empty(&dev->expired)); + work, list_empty(&dev->expired)); } return mask; } @@ -172,7 +175,7 @@ misdn_add_timer(struct mISDNtimerdev *dev, int timeout) struct mISDNtimer *timer; if (!timeout) { - dev->work = 1; + WRITE_ONCE(dev->work, 1); wake_up_interruptible(&dev->wait); id = 0; } else { From e3c1040252e598f7b4e33a42dc7c38519bc22428 Mon Sep 17 00:00:00 2001 From: Eric Dumazet Date: Sun, 18 Jan 2026 15:29:41 +0000 Subject: [PATCH 2737/3902] ipv6: annotate data-race in ndisc_router_discovery() [ Upstream commit 9a063f96d87efc3a6cc667f8de096a3d38d74bb5 ] syzbot found that ndisc_router_discovery() could read and write in6_dev->ra_mtu without holding a lock [1] This looks fine, IFLA_INET6_RA_MTU is best effort. Add READ_ONCE()/WRITE_ONCE() to document the race. Note that we might also reject illegal MTU values (mtu < IPV6_MIN_MTU || mtu > skb->dev->mtu) in a future patch. [1] BUG: KCSAN: data-race in ndisc_router_discovery / ndisc_router_discovery read to 0xffff888119809c20 of 4 bytes by task 25817 on cpu 1: ndisc_router_discovery+0x151d/0x1c90 net/ipv6/ndisc.c:1558 ndisc_rcv+0x2ad/0x3d0 net/ipv6/ndisc.c:1841 icmpv6_rcv+0xe5a/0x12f0 net/ipv6/icmp.c:989 ip6_protocol_deliver_rcu+0xb2a/0x10d0 net/ipv6/ip6_input.c:438 ip6_input_finish+0xf0/0x1d0 net/ipv6/ip6_input.c:489 NF_HOOK include/linux/netfilter.h:318 [inline] ip6_input+0x5e/0x140 net/ipv6/ip6_input.c:500 ip6_mc_input+0x27c/0x470 net/ipv6/ip6_input.c:590 dst_input include/net/dst.h:474 [inline] ip6_rcv_finish+0x336/0x340 net/ipv6/ip6_input.c:79 ... write to 0xffff888119809c20 of 4 bytes by task 25816 on cpu 0: ndisc_router_discovery+0x155a/0x1c90 net/ipv6/ndisc.c:1559 ndisc_rcv+0x2ad/0x3d0 net/ipv6/ndisc.c:1841 icmpv6_rcv+0xe5a/0x12f0 net/ipv6/icmp.c:989 ip6_protocol_deliver_rcu+0xb2a/0x10d0 net/ipv6/ip6_input.c:438 ip6_input_finish+0xf0/0x1d0 net/ipv6/ip6_input.c:489 NF_HOOK include/linux/netfilter.h:318 [inline] ip6_input+0x5e/0x140 net/ipv6/ip6_input.c:500 ip6_mc_input+0x27c/0x470 net/ipv6/ip6_input.c:590 dst_input include/net/dst.h:474 [inline] ip6_rcv_finish+0x336/0x340 net/ipv6/ip6_input.c:79 ... value changed: 0x00000000 -> 0xe5400659 Fixes: 49b99da2c9ce ("ipv6: add IFLA_INET6_RA_MTU to expose mtu value") Reported-by: syzbot Signed-off-by: Eric Dumazet Cc: Rocco Yue Link: https://patch.msgid.link/20260118152941.2563857-1-edumazet@google.com Signed-off-by: Jakub Kicinski Signed-off-by: Sasha Levin --- net/ipv6/ndisc.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/net/ipv6/ndisc.c b/net/ipv6/ndisc.c index f427e41e9c49bf..0fd3f53dbb52e6 100644 --- a/net/ipv6/ndisc.c +++ b/net/ipv6/ndisc.c @@ -1555,8 +1555,8 @@ static enum skb_drop_reason ndisc_router_discovery(struct sk_buff *skb) memcpy(&n, ((u8 *)(ndopts.nd_opts_mtu+1))+2, sizeof(mtu)); mtu = ntohl(n); - if (in6_dev->ra_mtu != mtu) { - in6_dev->ra_mtu = mtu; + if (READ_ONCE(in6_dev->ra_mtu) != mtu) { + WRITE_ONCE(in6_dev->ra_mtu, mtu); send_ifinfo_notify = true; } From 00a39a148d2fbdad2f2bc9da11ae553f7d9f2c36 Mon Sep 17 00:00:00 2001 From: Laurent Vivier Date: Mon, 19 Jan 2026 08:55:18 +0100 Subject: [PATCH 2738/3902] usbnet: limit max_mtu based on device's hard_mtu [ Upstream commit c7159e960f1472a5493ac99aff0086ab1d683594 ] The usbnet driver initializes net->max_mtu to ETH_MAX_MTU before calling the device's bind() callback. When the bind() callback sets dev->hard_mtu based the device's actual capability (from CDC Ethernet's wMaxSegmentSize descriptor), max_mtu is never updated to reflect this hardware limitation). This allows userspace (DHCP or IPv6 RA) to configure MTU larger than the device can handle, leading to silent packet drops when the backend sends packet exceeding the device's buffer size. Fix this by limiting net->max_mtu to the device's hard_mtu after the bind callback returns. See https://gitlab.com/qemu-project/qemu/-/issues/3268 and https://bugs.passt.top/attachment.cgi?bugid=189 Fixes: f77f0aee4da4 ("net: use core MTU range checking in USB NIC drivers") Signed-off-by: Laurent Vivier Link: https://bugs.passt.top/show_bug.cgi?id=189 Reviewed-by: Stefano Brivio Link: https://patch.msgid.link/20260119075518.2774373-1-lvivier@redhat.com Signed-off-by: Jakub Kicinski Signed-off-by: Sasha Levin --- drivers/net/usb/usbnet.c | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/drivers/net/usb/usbnet.c b/drivers/net/usb/usbnet.c index 697cd9d866d3d0..ab5ded8f38cf8a 100644 --- a/drivers/net/usb/usbnet.c +++ b/drivers/net/usb/usbnet.c @@ -1803,9 +1803,12 @@ usbnet_probe (struct usb_interface *udev, const struct usb_device_id *prod) if ((dev->driver_info->flags & FLAG_NOARP) != 0) net->flags |= IFF_NOARP; - /* maybe the remote can't receive an Ethernet MTU */ - if (net->mtu > (dev->hard_mtu - net->hard_header_len)) - net->mtu = dev->hard_mtu - net->hard_header_len; + if (net->max_mtu > (dev->hard_mtu - net->hard_header_len)) + net->max_mtu = dev->hard_mtu - net->hard_header_len; + + if (net->mtu > net->max_mtu) + net->mtu = net->max_mtu; + } else if (!info->in || !info->out) status = usbnet_get_endpoints (dev, udev); else { From 0db865321e63d8960410a8cde540843e2ab9aef3 Mon Sep 17 00:00:00 2001 From: Hariprasad Kelam Date: Mon, 19 Jan 2026 15:32:22 +0530 Subject: [PATCH 2739/3902] Octeontx2-pf: Update xdp features [ Upstream commit cdf8de9c6bfe94508d251cb290ee66e34e6f3368 ] In recent testing, verification of XDP_REDIRECT and zero-copy features failed because the driver is not setting the corresponding feature flags. Fixes: efabce290151 ("octeontx2-pf: AF_XDP zero copy receive support") Fixes: 66c0e13ad236 ("drivers: net: turn on XDP features") Signed-off-by: Hariprasad Kelam Link: https://patch.msgid.link/20260119100222.2267925-1-hkelam@marvell.com Signed-off-by: Jakub Kicinski Signed-off-by: Sasha Levin --- drivers/net/ethernet/marvell/octeontx2/nic/otx2_pf.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/drivers/net/ethernet/marvell/octeontx2/nic/otx2_pf.c b/drivers/net/ethernet/marvell/octeontx2/nic/otx2_pf.c index e808995703cfd0..a7a7bc0e1b6757 100644 --- a/drivers/net/ethernet/marvell/octeontx2/nic/otx2_pf.c +++ b/drivers/net/ethernet/marvell/octeontx2/nic/otx2_pf.c @@ -3255,7 +3255,9 @@ static int otx2_probe(struct pci_dev *pdev, const struct pci_device_id *id) netdev->watchdog_timeo = OTX2_TX_TIMEOUT; netdev->netdev_ops = &otx2_netdev_ops; - netdev->xdp_features = NETDEV_XDP_ACT_BASIC | NETDEV_XDP_ACT_REDIRECT; + netdev->xdp_features = NETDEV_XDP_ACT_BASIC | NETDEV_XDP_ACT_REDIRECT | + NETDEV_XDP_ACT_NDO_XMIT | + NETDEV_XDP_ACT_XSK_ZEROCOPY; netdev->min_mtu = OTX2_MIN_MTU; netdev->max_mtu = otx2_get_max_mtu(pf); From 54c19e7eb2636ce77ac22229f03d88433920c387 Mon Sep 17 00:00:00 2001 From: Thomas Gleixner Date: Wed, 17 Dec 2025 18:21:05 +0100 Subject: [PATCH 2740/3902] clocksource: Reduce watchdog readout delay limit to prevent false positives [ Upstream commit c06343be0b4e03fe319910dd7a5d5b9929e1c0cb ] The "valid" readout delay between the two reads of the watchdog is larger than the valid delta between the resulting watchdog and clocksource intervals, which results in false positive watchdog results. Assume TSC is the clocksource and HPET is the watchdog and both have a uncertainty margin of 250us (default). The watchdog readout does: 1) wdnow = read(HPET); 2) csnow = read(TSC); 3) wdend = read(HPET); The valid window for the delta between #1 and #3 is calculated by the uncertainty margins of the watchdog and the clocksource: m = 2 * watchdog.uncertainty_margin + cs.uncertainty margin; which results in 750us for the TSC/HPET case. The actual interval comparison uses a smaller margin: m = watchdog.uncertainty_margin + cs.uncertainty margin; which results in 500us for the TSC/HPET case. That means the following scenario will trigger the watchdog: Watchdog cycle N: 1) wdnow[N] = read(HPET); 2) csnow[N] = read(TSC); 3) wdend[N] = read(HPET); Assume the delay between #1 and #2 is 100us and the delay between #1 and Watchdog cycle N + 1: 4) wdnow[N + 1] = read(HPET); 5) csnow[N + 1] = read(TSC); 6) wdend[N + 1] = read(HPET); If the delay between #4 and #6 is within the 750us margin then any delay between #4 and #5 which is larger than 600us will fail the interval check and mark the TSC unstable because the intervals are calculated against the previous value: wd_int = wdnow[N + 1] - wdnow[N]; cs_int = csnow[N + 1] - csnow[N]; Putting the above delays in place this results in: cs_int = (wdnow[N + 1] + 610us) - (wdnow[N] + 100us); -> cs_int = wd_int + 510us; which is obviously larger than the allowed 500us margin and results in marking TSC unstable. Fix this by using the same margin as the interval comparison. If the delay between two watchdog reads is larger than that, then the readout was either disturbed by interconnect congestion, NMIs or SMIs. Fixes: 4ac1dd3245b9 ("clocksource: Set cs_watchdog_read() checks based on .uncertainty_margin") Reported-by: Daniel J Blueman Signed-off-by: Thomas Gleixner Reviewed-by: Paul E. McKenney Tested-by: Paul E. McKenney Link: https://lore.kernel.org/lkml/20250602223251.496591-1-daniel@quora.org/ Link: https://patch.msgid.link/87bjjxc9dq.ffs@tglx Signed-off-by: Sasha Levin --- kernel/time/clocksource.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/kernel/time/clocksource.c b/kernel/time/clocksource.c index a1890a073196b1..df719496165849 100644 --- a/kernel/time/clocksource.c +++ b/kernel/time/clocksource.c @@ -252,7 +252,7 @@ enum wd_read_status { static enum wd_read_status cs_watchdog_read(struct clocksource *cs, u64 *csnow, u64 *wdnow) { - int64_t md = 2 * watchdog->uncertainty_margin; + int64_t md = watchdog->uncertainty_margin; unsigned int nretries, max_retries; int64_t wd_delay, wd_seq_delay; u64 wd_end, wd_end2; From 9bba27c222e0e7576981bdb61a93b1c61ccb618c Mon Sep 17 00:00:00 2001 From: Matthew Auld Date: Tue, 20 Jan 2026 11:06:10 +0000 Subject: [PATCH 2741/3902] drm/xe/uapi: disallow bind queue sharing MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [ Upstream commit 6f4b7aed61817624250e590ba0ef304146d34614 ] Currently this is very broken if someone attempts to create a bind queue and share it across multiple VMs. For example currently we assume it is safe to acquire the user VM lock to protect some of the bind queue state, but if allow sharing the bind queue with multiple VMs then this quickly breaks down. To fix this reject using a bind queue with any VM that is not the same VM that was originally passed when creating the bind queue. This a uAPI change, however this was more of an oversight on kernel side that we didn't reject this, and expectation is that userspace shouldn't be using bind queues in this way, so in theory this change should go unnoticed. Based on a patch from Matt Brost. v2 (Matt B): - Hold the vm lock over queue create, to ensure it can't be closed as we attach the user_vm to the queue. - Make sure we actually check for NULL user_vm in destruction path. v3: - Fix error path handling. Fixes: dd08ebf6c352 ("drm/xe: Introduce a new DRM driver for Intel GPUs") Reported-by: Thomas Hellström Signed-off-by: Matthew Auld Cc: José Roberto de Souza Cc: Matthew Brost Cc: Michal Mrozek Cc: Carl Zhang Cc: # v6.8+ Acked-by: José Roberto de Souza Reviewed-by: Matthew Brost Reviewed-by: Arvind Yadav Acked-by: Michal Mrozek Link: https://patch.msgid.link/20260120110609.77958-3-matthew.auld@intel.com (cherry picked from commit 9dd08fdecc0c98d6516c2d2d1fa189c1332f8dab) Signed-off-by: Thomas Hellström Stable-dep-of: 772157f626d0 ("drm/xe/migrate: fix job lock assert") Signed-off-by: Sasha Levin --- drivers/gpu/drm/xe/xe_exec_queue.c | 32 +++++++++++++++++++++++- drivers/gpu/drm/xe/xe_exec_queue.h | 1 + drivers/gpu/drm/xe/xe_exec_queue_types.h | 6 +++++ drivers/gpu/drm/xe/xe_sriov_vf_ccs.c | 2 +- drivers/gpu/drm/xe/xe_vm.c | 7 +++++- 5 files changed, 45 insertions(+), 3 deletions(-) diff --git a/drivers/gpu/drm/xe/xe_exec_queue.c b/drivers/gpu/drm/xe/xe_exec_queue.c index cb5f204c08ed6e..231d1fbe5eefa7 100644 --- a/drivers/gpu/drm/xe/xe_exec_queue.c +++ b/drivers/gpu/drm/xe/xe_exec_queue.c @@ -284,6 +284,7 @@ struct xe_exec_queue *xe_exec_queue_create_class(struct xe_device *xe, struct xe * @xe: Xe device. * @tile: tile which bind exec queue belongs to. * @flags: exec queue creation flags + * @user_vm: The user VM which this exec queue belongs to * @extensions: exec queue creation extensions * * Normalize bind exec queue creation. Bind exec queue is tied to migration VM @@ -297,6 +298,7 @@ struct xe_exec_queue *xe_exec_queue_create_class(struct xe_device *xe, struct xe */ struct xe_exec_queue *xe_exec_queue_create_bind(struct xe_device *xe, struct xe_tile *tile, + struct xe_vm *user_vm, u32 flags, u64 extensions) { struct xe_gt *gt = tile->primary_gt; @@ -333,6 +335,9 @@ struct xe_exec_queue *xe_exec_queue_create_bind(struct xe_device *xe, xe_exec_queue_put(q); return ERR_PTR(err); } + + if (user_vm) + q->user_vm = xe_vm_get(user_vm); } return q; @@ -357,6 +362,11 @@ void xe_exec_queue_destroy(struct kref *ref) xe_exec_queue_put(eq); } + if (q->user_vm) { + xe_vm_put(q->user_vm); + q->user_vm = NULL; + } + q->ops->destroy(q); } @@ -692,6 +702,22 @@ int xe_exec_queue_create_ioctl(struct drm_device *dev, void *data, XE_IOCTL_DBG(xe, eci[0].engine_instance != 0)) return -EINVAL; + vm = xe_vm_lookup(xef, args->vm_id); + if (XE_IOCTL_DBG(xe, !vm)) + return -ENOENT; + + err = down_read_interruptible(&vm->lock); + if (err) { + xe_vm_put(vm); + return err; + } + + if (XE_IOCTL_DBG(xe, xe_vm_is_closed_or_banned(vm))) { + up_read(&vm->lock); + xe_vm_put(vm); + return -ENOENT; + } + for_each_tile(tile, xe, id) { struct xe_exec_queue *new; @@ -699,9 +725,11 @@ int xe_exec_queue_create_ioctl(struct drm_device *dev, void *data, if (id) flags |= EXEC_QUEUE_FLAG_BIND_ENGINE_CHILD; - new = xe_exec_queue_create_bind(xe, tile, flags, + new = xe_exec_queue_create_bind(xe, tile, vm, flags, args->extensions); if (IS_ERR(new)) { + up_read(&vm->lock); + xe_vm_put(vm); err = PTR_ERR(new); if (q) goto put_exec_queue; @@ -713,6 +741,8 @@ int xe_exec_queue_create_ioctl(struct drm_device *dev, void *data, list_add_tail(&new->multi_gt_list, &q->multi_gt_link); } + up_read(&vm->lock); + xe_vm_put(vm); } else { logical_mask = calc_validate_logical_mask(xe, eci, args->width, diff --git a/drivers/gpu/drm/xe/xe_exec_queue.h b/drivers/gpu/drm/xe/xe_exec_queue.h index 15ec852e7f7e78..5343c1b8cab540 100644 --- a/drivers/gpu/drm/xe/xe_exec_queue.h +++ b/drivers/gpu/drm/xe/xe_exec_queue.h @@ -24,6 +24,7 @@ struct xe_exec_queue *xe_exec_queue_create_class(struct xe_device *xe, struct xe u32 flags, u64 extensions); struct xe_exec_queue *xe_exec_queue_create_bind(struct xe_device *xe, struct xe_tile *tile, + struct xe_vm *user_vm, u32 flags, u64 extensions); void xe_exec_queue_fini(struct xe_exec_queue *q); diff --git a/drivers/gpu/drm/xe/xe_exec_queue_types.h b/drivers/gpu/drm/xe/xe_exec_queue_types.h index df1c69dc81f17b..38906cb7608ca4 100644 --- a/drivers/gpu/drm/xe/xe_exec_queue_types.h +++ b/drivers/gpu/drm/xe/xe_exec_queue_types.h @@ -54,6 +54,12 @@ struct xe_exec_queue { struct kref refcount; /** @vm: VM (address space) for this exec queue */ struct xe_vm *vm; + /** + * @user_vm: User VM (address space) for this exec queue (bind queues + * only) + */ + struct xe_vm *user_vm; + /** @class: class of this exec queue */ enum xe_engine_class class; /** diff --git a/drivers/gpu/drm/xe/xe_sriov_vf_ccs.c b/drivers/gpu/drm/xe/xe_sriov_vf_ccs.c index 8dec616c37c983..739a3eb180b53c 100644 --- a/drivers/gpu/drm/xe/xe_sriov_vf_ccs.c +++ b/drivers/gpu/drm/xe/xe_sriov_vf_ccs.c @@ -276,7 +276,7 @@ int xe_sriov_vf_ccs_init(struct xe_device *xe) flags = EXEC_QUEUE_FLAG_KERNEL | EXEC_QUEUE_FLAG_PERMANENT | EXEC_QUEUE_FLAG_MIGRATE; - q = xe_exec_queue_create_bind(xe, tile, flags, 0); + q = xe_exec_queue_create_bind(xe, tile, NULL, flags, 0); if (IS_ERR(q)) { err = PTR_ERR(q); goto err_ret; diff --git a/drivers/gpu/drm/xe/xe_vm.c b/drivers/gpu/drm/xe/xe_vm.c index 747aa8cff60d48..145cd9ffa36b3f 100644 --- a/drivers/gpu/drm/xe/xe_vm.c +++ b/drivers/gpu/drm/xe/xe_vm.c @@ -1590,7 +1590,7 @@ struct xe_vm *xe_vm_create(struct xe_device *xe, u32 flags, struct xe_file *xef) if (!vm->pt_root[id]) continue; - q = xe_exec_queue_create_bind(xe, tile, create_flags, 0); + q = xe_exec_queue_create_bind(xe, tile, vm, create_flags, 0); if (IS_ERR(q)) { err = PTR_ERR(q); goto err_close; @@ -3536,6 +3536,11 @@ int xe_vm_bind_ioctl(struct drm_device *dev, void *data, struct drm_file *file) } } + if (XE_IOCTL_DBG(xe, q && vm != q->user_vm)) { + err = -EINVAL; + goto put_exec_queue; + } + /* Ensure all UNMAPs visible */ xe_svm_flush(vm); From e59924c266a45289cb81b78bd04c834492a9d513 Mon Sep 17 00:00:00 2001 From: Matthew Auld Date: Tue, 20 Jan 2026 11:06:11 +0000 Subject: [PATCH 2742/3902] drm/xe/migrate: fix job lock assert MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [ Upstream commit 772157f626d0e1a7c6d49dffb0bbe4b2343a1d44 ] We are meant to be checking the user vm for the bind queue, but actually we are checking the migrate vm. For various reasons this is not currently firing but this will likely change in the future. Now that we have the user_vm attached to the bind queue, we can fix this by directly checking that here. Fixes: dba89840a920 ("drm/xe: Add GT TLB invalidation jobs") Signed-off-by: Matthew Auld Cc: Thomas Hellström Cc: Matthew Brost Reviewed-by: Matthew Brost Reviewed-by: Arvind Yadav Link: https://patch.msgid.link/20260120110609.77958-4-matthew.auld@intel.com (cherry picked from commit 9dd1048bca4fe2aa67c7a286bafb3947537adedb) Signed-off-by: Thomas Hellström Signed-off-by: Sasha Levin --- drivers/gpu/drm/xe/xe_migrate.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/gpu/drm/xe/xe_migrate.c b/drivers/gpu/drm/xe/xe_migrate.c index 3acdcbf41887f7..b6905f35d6c81f 100644 --- a/drivers/gpu/drm/xe/xe_migrate.c +++ b/drivers/gpu/drm/xe/xe_migrate.c @@ -2182,7 +2182,7 @@ void xe_migrate_job_lock(struct xe_migrate *m, struct xe_exec_queue *q) if (is_migrate) mutex_lock(&m->job_mutex); else - xe_vm_assert_held(q->vm); /* User queues VM's should be locked */ + xe_vm_assert_held(q->user_vm); /* User queues VM's should be locked */ } /** @@ -2200,7 +2200,7 @@ void xe_migrate_job_unlock(struct xe_migrate *m, struct xe_exec_queue *q) if (is_migrate) mutex_unlock(&m->job_mutex); else - xe_vm_assert_held(q->vm); /* User queues VM's should be locked */ + xe_vm_assert_held(q->user_vm); /* User queues VM's should be locked */ } #if IS_ENABLED(CONFIG_DRM_XE_KUNIT_TEST) From 7c49c91121194597bc8ff4eaf28fea7055b946f1 Mon Sep 17 00:00:00 2001 From: Matt Roper Date: Tue, 18 Nov 2025 08:43:41 -0800 Subject: [PATCH 2743/3902] drm/xe/pm: Add scope-based cleanup helper for runtime PM MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [ Upstream commit 50a59230fa63989d59253622a8dd6386cca0db07 ] Add a scope-based helpers for runtime PM that may be used to simplify cleanup logic and potentially avoid goto-based cleanup. For example, using guard(xe_pm_runtime)(xe); will get runtime PM and cause a corresponding put to occur automatically when the current scope is exited. 'xe_pm_runtime_noresume' can be used as a guard replacement for the corresponding 'noresume' variant. There's also an xe_pm_runtime_ioctl conditional guard that can be used as a replacement for xe_runtime_ioctl(): ACQUIRE(xe_pm_runtime_ioctl, pm)(xe); if ((ret = ACQUIRE_ERR(xe_pm_runtime_ioctl, &pm)) < 0) /* failed */ In a few rare cases (such as gt_reset_worker()) we need to ensure that runtime PM is dropped when the function is exited by any means (including error paths), but the function does not need to acquire runtime PM because that has already been done earlier by a different function. For these special cases, an 'xe_pm_runtime_release_only' guard can be used to handle the release without doing an acquisition. These guards will be used in future patches to eliminate some of our goto-based cleanup. v2: - Specify success condition for xe_pm runtime_ioctl as _RET >= 0 so that positive values will be properly identified as success and trigger destructor cleanup properly. v3: - Add comments to the kerneldoc for the existing 'get' functions indicating that scope-based handling should be preferred where possible. (Gustavo) Cc: Gustavo Sousa Reviewed-by: Michal Wajdeczko Reviewed-by: Gustavo Sousa Link: https://patch.msgid.link/20251118164338.3572146-31-matthew.d.roper@intel.com Signed-off-by: Matt Roper (cherry picked from commit 59e7528dbfd52efbed05e0f11b2143217a12bc74) Signed-off-by: Thomas Hellström Stable-dep-of: f262015b9797 ("drm/xe: Update wedged.mode only after successful reset policy change") Signed-off-by: Sasha Levin --- drivers/gpu/drm/xe/xe_pm.c | 21 +++++++++++++++++++++ drivers/gpu/drm/xe/xe_pm.h | 17 +++++++++++++++++ 2 files changed, 38 insertions(+) diff --git a/drivers/gpu/drm/xe/xe_pm.c b/drivers/gpu/drm/xe/xe_pm.c index 2c5a44377994b3..a58bf004aee734 100644 --- a/drivers/gpu/drm/xe/xe_pm.c +++ b/drivers/gpu/drm/xe/xe_pm.c @@ -660,6 +660,13 @@ static void xe_pm_runtime_lockdep_prime(void) /** * xe_pm_runtime_get - Get a runtime_pm reference and resume synchronously * @xe: xe device instance + * + * When possible, scope-based runtime PM (through guard(xe_pm_runtime)) is + * be preferred over direct usage of this function. Manual get/put handling + * should only be used when the function contains goto-based logic which + * can break scope-based handling, or when the lifetime of the runtime PM + * reference does not match a specific scope (e.g., runtime PM obtained in one + * function and released in a different one). */ void xe_pm_runtime_get(struct xe_device *xe) { @@ -692,6 +699,13 @@ void xe_pm_runtime_put(struct xe_device *xe) * xe_pm_runtime_get_ioctl - Get a runtime_pm reference before ioctl * @xe: xe device instance * + * When possible, scope-based runtime PM (through + * ACQUIRE(xe_pm_runtime_ioctl, ...)) is be preferred over direct usage of this + * function. Manual get/put handling should only be used when the function + * contains goto-based logic which can break scope-based handling, or when the + * lifetime of the runtime PM reference does not match a specific scope (e.g., + * runtime PM obtained in one function and released in a different one). + * * Returns: Any number greater than or equal to 0 for success, negative error * code otherwise. */ @@ -761,6 +775,13 @@ static bool xe_pm_suspending_or_resuming(struct xe_device *xe) * It will warn if not protected. * The reference should be put back after this function regardless, since it * will always bump the usage counter, regardless. + * + * When possible, scope-based runtime PM (through guard(xe_pm_runtime_noresume)) + * is be preferred over direct usage of this function. Manual get/put handling + * should only be used when the function contains goto-based logic which can + * break scope-based handling, or when the lifetime of the runtime PM reference + * does not match a specific scope (e.g., runtime PM obtained in one function + * and released in a different one). */ void xe_pm_runtime_get_noresume(struct xe_device *xe) { diff --git a/drivers/gpu/drm/xe/xe_pm.h b/drivers/gpu/drm/xe/xe_pm.h index 59678b310e55f1..e8005775be39e6 100644 --- a/drivers/gpu/drm/xe/xe_pm.h +++ b/drivers/gpu/drm/xe/xe_pm.h @@ -6,6 +6,7 @@ #ifndef _XE_PM_H_ #define _XE_PM_H_ +#include #include #define DEFAULT_VRAM_THRESHOLD 300 /* in MB */ @@ -35,4 +36,20 @@ bool xe_rpm_reclaim_safe(const struct xe_device *xe); struct task_struct *xe_pm_read_callback_task(struct xe_device *xe); int xe_pm_module_init(void); +static inline void __xe_pm_runtime_noop(struct xe_device *xe) {} + +DEFINE_GUARD(xe_pm_runtime, struct xe_device *, + xe_pm_runtime_get(_T), xe_pm_runtime_put(_T)) +DEFINE_GUARD(xe_pm_runtime_noresume, struct xe_device *, + xe_pm_runtime_get_noresume(_T), xe_pm_runtime_put(_T)) +DEFINE_GUARD_COND(xe_pm_runtime, _ioctl, xe_pm_runtime_get_ioctl(_T), _RET >= 0) + +/* + * Used when a function needs to release runtime PM in all possible cases + * and error paths, but the wakeref was already acquired by a different + * function (i.e., get() has already happened so only a put() is needed). + */ +DEFINE_GUARD(xe_pm_runtime_release_only, struct xe_device *, + __xe_pm_runtime_noop(_T), xe_pm_runtime_put(_T)); + #endif From 437553d649647772ab524c00bac56222ab8eb966 Mon Sep 17 00:00:00 2001 From: Lukasz Laguna Date: Wed, 21 Jan 2026 15:33:04 +0100 Subject: [PATCH 2744/3902] drm/xe: Update wedged.mode only after successful reset policy change MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [ Upstream commit f262015b9797effdec15e8a81c81b2158ede9578 ] Previously, the driver's internal wedged.mode state was updated without verifying whether the corresponding engine reset policy update in GuC succeeded. This could leave the driver reporting a wedged.mode state that doesn't match the actual reset behavior programmed in GuC. With this change, the reset policy is updated first, and the driver's wedged.mode state is modified only if the policy update succeeds on all available GTs. This patch also introduces two functional improvements: - The policy is sent to GuC only when a change is required. An update is needed only when entering or leaving XE_WEDGED_MODE_UPON_ANY_HANG, because only in that case the reset policy changes. For example, switching between XE_WEDGED_MODE_UPON_CRITICAL_ERROR and XE_WEDGED_MODE_NEVER doesn't affect the reset policy, so there is no need to send the same value to GuC. - An inconsistent_reset flag is added to track cases where reset policy update succeeds only on a subset of GTs. If such inconsistency is detected, future wedged mode configuration will force a retry of the reset policy update to restore a consistent state across all GTs. Fixes: 6b8ef44cc0a9 ("drm/xe: Introduce the wedged_mode debugfs") Signed-off-by: Lukasz Laguna Link: https://patch.msgid.link/20260107174741.29163-3-lukasz.laguna@intel.com Reviewed-by: Rodrigo Vivi Signed-off-by: Rodrigo Vivi (cherry picked from commit 0f13dead4e0385859f5c9c3625a19df116b389d3) Signed-off-by: Thomas Hellström Signed-off-by: Sasha Levin --- drivers/gpu/drm/xe/xe_debugfs.c | 72 ++++++++++++++++++++++------ drivers/gpu/drm/xe/xe_device_types.h | 18 +++++++ drivers/gpu/drm/xe/xe_guc_ads.c | 14 +++--- drivers/gpu/drm/xe/xe_guc_ads.h | 5 +- 4 files changed, 87 insertions(+), 22 deletions(-) diff --git a/drivers/gpu/drm/xe/xe_debugfs.c b/drivers/gpu/drm/xe/xe_debugfs.c index cd977dbd1ef63e..7b48bf90cab8f4 100644 --- a/drivers/gpu/drm/xe/xe_debugfs.c +++ b/drivers/gpu/drm/xe/xe_debugfs.c @@ -255,14 +255,64 @@ static ssize_t wedged_mode_show(struct file *f, char __user *ubuf, return simple_read_from_buffer(ubuf, size, pos, buf, len); } +static int __wedged_mode_set_reset_policy(struct xe_gt *gt, enum xe_wedged_mode mode) +{ + bool enable_engine_reset; + int ret; + + enable_engine_reset = (mode != XE_WEDGED_MODE_UPON_ANY_HANG_NO_RESET); + ret = xe_guc_ads_scheduler_policy_toggle_reset(>->uc.guc.ads, + enable_engine_reset); + if (ret) + xe_gt_err(gt, "Failed to update GuC ADS scheduler policy (%pe)\n", ERR_PTR(ret)); + + return ret; +} + +static int wedged_mode_set_reset_policy(struct xe_device *xe, enum xe_wedged_mode mode) +{ + struct xe_gt *gt; + int ret; + u8 id; + + guard(xe_pm_runtime)(xe); + for_each_gt(gt, xe, id) { + ret = __wedged_mode_set_reset_policy(gt, mode); + if (ret) { + if (id > 0) { + xe->wedged.inconsistent_reset = true; + drm_err(&xe->drm, "Inconsistent reset policy state between GTs\n"); + } + return ret; + } + } + + xe->wedged.inconsistent_reset = false; + + return 0; +} + +static bool wedged_mode_needs_policy_update(struct xe_device *xe, enum xe_wedged_mode mode) +{ + if (xe->wedged.inconsistent_reset) + return true; + + if (xe->wedged.mode == mode) + return false; + + if (xe->wedged.mode == XE_WEDGED_MODE_UPON_ANY_HANG_NO_RESET || + mode == XE_WEDGED_MODE_UPON_ANY_HANG_NO_RESET) + return true; + + return false; +} + static ssize_t wedged_mode_set(struct file *f, const char __user *ubuf, size_t size, loff_t *pos) { struct xe_device *xe = file_inode(f)->i_private; - struct xe_gt *gt; u32 wedged_mode; ssize_t ret; - u8 id; ret = kstrtouint_from_user(ubuf, size, 0, &wedged_mode); if (ret) @@ -271,22 +321,14 @@ static ssize_t wedged_mode_set(struct file *f, const char __user *ubuf, if (wedged_mode > 2) return -EINVAL; - if (xe->wedged.mode == wedged_mode) - return size; + if (wedged_mode_needs_policy_update(xe, wedged_mode)) { + ret = wedged_mode_set_reset_policy(xe, wedged_mode); + if (ret) + return ret; + } xe->wedged.mode = wedged_mode; - xe_pm_runtime_get(xe); - for_each_gt(gt, xe, id) { - ret = xe_guc_ads_scheduler_policy_toggle_reset(>->uc.guc.ads); - if (ret) { - xe_gt_err(gt, "Failed to update GuC ADS scheduler policy. GuC may still cause engine reset even with wedged_mode=2\n"); - xe_pm_runtime_put(xe); - return -EIO; - } - } - xe_pm_runtime_put(xe); - return size; } diff --git a/drivers/gpu/drm/xe/xe_device_types.h b/drivers/gpu/drm/xe/xe_device_types.h index 74d7af830b85d8..0e80f2940c9963 100644 --- a/drivers/gpu/drm/xe/xe_device_types.h +++ b/drivers/gpu/drm/xe/xe_device_types.h @@ -42,6 +42,22 @@ struct xe_pat_ops; struct xe_pxp; struct xe_vram_region; +/** + * enum xe_wedged_mode - possible wedged modes + * @XE_WEDGED_MODE_NEVER: Device will never be declared wedged. + * @XE_WEDGED_MODE_UPON_CRITICAL_ERROR: Device will be declared wedged only + * when critical error occurs like GT reset failure or firmware failure. + * This is the default mode. + * @XE_WEDGED_MODE_UPON_ANY_HANG_NO_RESET: Device will be declared wedged on + * any hang. In this mode, engine resets are disabled to avoid automatic + * recovery attempts. This mode is primarily intended for debugging hangs. + */ +enum xe_wedged_mode { + XE_WEDGED_MODE_NEVER = 0, + XE_WEDGED_MODE_UPON_CRITICAL_ERROR = 1, + XE_WEDGED_MODE_UPON_ANY_HANG_NO_RESET = 2, +}; + #define XE_BO_INVALID_OFFSET LONG_MAX #define GRAPHICS_VER(xe) ((xe)->info.graphics_verx100 / 100) @@ -556,6 +572,8 @@ struct xe_device { int mode; /** @wedged.method: Recovery method to be sent in the drm device wedged uevent */ unsigned long method; + /** @wedged.inconsistent_reset: Inconsistent reset policy state between GTs */ + bool inconsistent_reset; } wedged; /** @bo_device: Struct to control async free of BOs */ diff --git a/drivers/gpu/drm/xe/xe_guc_ads.c b/drivers/gpu/drm/xe/xe_guc_ads.c index 58e0b0294a5bcf..0e2bece1d8b83b 100644 --- a/drivers/gpu/drm/xe/xe_guc_ads.c +++ b/drivers/gpu/drm/xe/xe_guc_ads.c @@ -979,16 +979,17 @@ static int guc_ads_action_update_policies(struct xe_guc_ads *ads, u32 policy_off /** * xe_guc_ads_scheduler_policy_toggle_reset - Toggle reset policy * @ads: Additional data structures object + * @enable_engine_reset: true to enable engine resets, false otherwise * - * This function update the GuC's engine reset policy based on wedged.mode. + * This function update the GuC's engine reset policy. * * Return: 0 on success, and negative error code otherwise. */ -int xe_guc_ads_scheduler_policy_toggle_reset(struct xe_guc_ads *ads) +int xe_guc_ads_scheduler_policy_toggle_reset(struct xe_guc_ads *ads, + bool enable_engine_reset) { struct guc_policies *policies; struct xe_guc *guc = ads_to_guc(ads); - struct xe_device *xe = ads_to_xe(ads); CLASS(xe_guc_buf, buf)(&guc->buf, sizeof(*policies)); if (!xe_guc_buf_is_valid(buf)) @@ -1000,10 +1001,11 @@ int xe_guc_ads_scheduler_policy_toggle_reset(struct xe_guc_ads *ads) policies->dpc_promote_time = ads_blob_read(ads, policies.dpc_promote_time); policies->max_num_work_items = ads_blob_read(ads, policies.max_num_work_items); policies->is_valid = 1; - if (xe->wedged.mode == 2) - policies->global_flags |= GLOBAL_POLICY_DISABLE_ENGINE_RESET; - else + + if (enable_engine_reset) policies->global_flags &= ~GLOBAL_POLICY_DISABLE_ENGINE_RESET; + else + policies->global_flags |= GLOBAL_POLICY_DISABLE_ENGINE_RESET; return guc_ads_action_update_policies(ads, xe_guc_buf_flush(buf)); } diff --git a/drivers/gpu/drm/xe/xe_guc_ads.h b/drivers/gpu/drm/xe/xe_guc_ads.h index 2e6674c760ff91..7a39f361cb17d7 100644 --- a/drivers/gpu/drm/xe/xe_guc_ads.h +++ b/drivers/gpu/drm/xe/xe_guc_ads.h @@ -6,6 +6,8 @@ #ifndef _XE_GUC_ADS_H_ #define _XE_GUC_ADS_H_ +#include + struct xe_guc_ads; int xe_guc_ads_init(struct xe_guc_ads *ads); @@ -13,6 +15,7 @@ int xe_guc_ads_init_post_hwconfig(struct xe_guc_ads *ads); void xe_guc_ads_populate(struct xe_guc_ads *ads); void xe_guc_ads_populate_minimal(struct xe_guc_ads *ads); void xe_guc_ads_populate_post_load(struct xe_guc_ads *ads); -int xe_guc_ads_scheduler_policy_toggle_reset(struct xe_guc_ads *ads); +int xe_guc_ads_scheduler_policy_toggle_reset(struct xe_guc_ads *ads, + bool enable_engine_reset); #endif From 26bf09b6320b8e40aefa6ddc7abab7298813c8ec Mon Sep 17 00:00:00 2001 From: Seamus Connor Date: Wed, 14 Jan 2026 18:59:52 -0800 Subject: [PATCH 2745/3902] ublk: fix ublksrv pid handling for pid namespaces MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [ Upstream commit 47bdf1d29caec7207b7f112230055db36602dfc0 ] When ublksrv runs inside a pid namespace, START/END_RECOVERY compared the stored init-ns tgid against the userspace pid (getpid vnr), so the check failed and control ops could not proceed. Compare against the caller’s init-ns tgid and store that value, then translate it back to the caller’s pid namespace when reporting GET_DEV_INFO so ublk list shows a sensible pid. Testing: start/recover in a pid namespace; `ublk list` shows reasonable pid values in init, child, and sibling namespaces. Fixes: c2c8089f325e ("ublk: validate ublk server pid") Signed-off-by: Seamus Connor Reviewed-by: Caleb Sander Mateos Reviewed-by: Ming Lei Signed-off-by: Jens Axboe Signed-off-by: Sasha Levin --- drivers/block/ublk_drv.c | 39 ++++++++++++++++++++++++++++++++++----- 1 file changed, 34 insertions(+), 5 deletions(-) diff --git a/drivers/block/ublk_drv.c b/drivers/block/ublk_drv.c index e09c1b5999b75c..4b6d7b785d7b36 100644 --- a/drivers/block/ublk_drv.c +++ b/drivers/block/ublk_drv.c @@ -2954,6 +2954,15 @@ static struct ublk_device *ublk_get_device_from_id(int idx) return ub; } +static bool ublk_validate_user_pid(struct ublk_device *ub, pid_t ublksrv_pid) +{ + rcu_read_lock(); + ublksrv_pid = pid_nr(find_vpid(ublksrv_pid)); + rcu_read_unlock(); + + return ub->ublksrv_tgid == ublksrv_pid; +} + static int ublk_ctrl_start_dev(struct ublk_device *ub, const struct ublksrv_ctrl_cmd *header) { @@ -3022,7 +3031,7 @@ static int ublk_ctrl_start_dev(struct ublk_device *ub, if (wait_for_completion_interruptible(&ub->completion) != 0) return -EINTR; - if (ub->ublksrv_tgid != ublksrv_pid) + if (!ublk_validate_user_pid(ub, ublksrv_pid)) return -EINVAL; mutex_lock(&ub->mutex); @@ -3041,7 +3050,7 @@ static int ublk_ctrl_start_dev(struct ublk_device *ub, disk->fops = &ub_fops; disk->private_data = ub; - ub->dev_info.ublksrv_pid = ublksrv_pid; + ub->dev_info.ublksrv_pid = ub->ublksrv_tgid; ub->ub_disk = disk; ublk_apply_params(ub); @@ -3389,12 +3398,32 @@ static int ublk_ctrl_stop_dev(struct ublk_device *ub) static int ublk_ctrl_get_dev_info(struct ublk_device *ub, const struct ublksrv_ctrl_cmd *header) { + struct task_struct *p; + struct pid *pid; + struct ublksrv_ctrl_dev_info dev_info; + pid_t init_ublksrv_tgid = ub->dev_info.ublksrv_pid; void __user *argp = (void __user *)(unsigned long)header->addr; if (header->len < sizeof(struct ublksrv_ctrl_dev_info) || !header->addr) return -EINVAL; - if (copy_to_user(argp, &ub->dev_info, sizeof(ub->dev_info))) + memcpy(&dev_info, &ub->dev_info, sizeof(dev_info)); + dev_info.ublksrv_pid = -1; + + if (init_ublksrv_tgid > 0) { + rcu_read_lock(); + pid = find_pid_ns(init_ublksrv_tgid, &init_pid_ns); + p = pid_task(pid, PIDTYPE_TGID); + if (p) { + int vnr = task_tgid_vnr(p); + + if (vnr) + dev_info.ublksrv_pid = vnr; + } + rcu_read_unlock(); + } + + if (copy_to_user(argp, &dev_info, sizeof(dev_info))) return -EFAULT; return 0; @@ -3539,7 +3568,7 @@ static int ublk_ctrl_end_recovery(struct ublk_device *ub, pr_devel("%s: All FETCH_REQs received, dev id %d\n", __func__, header->dev_id); - if (ub->ublksrv_tgid != ublksrv_pid) + if (!ublk_validate_user_pid(ub, ublksrv_pid)) return -EINVAL; mutex_lock(&ub->mutex); @@ -3550,7 +3579,7 @@ static int ublk_ctrl_end_recovery(struct ublk_device *ub, ret = -EBUSY; goto out_unlock; } - ub->dev_info.ublksrv_pid = ublksrv_pid; + ub->dev_info.ublksrv_pid = ub->ublksrv_tgid; ub->dev_info.state = UBLK_S_DEV_LIVE; pr_devel("%s: new ublksrv_pid %d, dev id %d\n", __func__, ublksrv_pid, header->dev_id); From 814e8643b453d3fe4cbfc46c0130fab7aae4da4b Mon Sep 17 00:00:00 2001 From: Ming Lei Date: Tue, 13 Jan 2026 16:58:00 +0800 Subject: [PATCH 2746/3902] selftests/ublk: fix IO thread idle check [ Upstream commit 75aad5ffe099a1b1a342257236dc260493917ed2 ] Include cmd_inflight in ublk_thread_is_done() check. Without this, the thread may exit before all FETCH commands are completed, which may cause device deletion to hang. Fixes: 6aecda00b7d1 ("selftests: ublk: add kernel selftests for ublk") Signed-off-by: Ming Lei Signed-off-by: Jens Axboe Signed-off-by: Sasha Levin --- tools/testing/selftests/ublk/kublk.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/testing/selftests/ublk/kublk.c b/tools/testing/selftests/ublk/kublk.c index 6b8123c12a7ae6..0e863d13eaee4f 100644 --- a/tools/testing/selftests/ublk/kublk.c +++ b/tools/testing/selftests/ublk/kublk.c @@ -718,7 +718,7 @@ static int ublk_thread_is_idle(struct ublk_thread *t) static int ublk_thread_is_done(struct ublk_thread *t) { - return (t->state & UBLKS_T_STOPPING) && ublk_thread_is_idle(t); + return (t->state & UBLKS_T_STOPPING) && ublk_thread_is_idle(t) && !t->cmd_inflight; } static inline void ublksrv_handle_tgt_cqe(struct ublk_thread *t, From e2f4eac26f01891018f069e96869e7ca4e725adf Mon Sep 17 00:00:00 2001 From: Ming Lei Date: Tue, 13 Jan 2026 16:58:01 +0800 Subject: [PATCH 2747/3902] selftests/ublk: fix error handling for starting device [ Upstream commit 23e62cf75518825aac12e9a22bdc40f062428898 ] Fix error handling in ublk_start_daemon() when start_dev fails: 1. Call ublk_ctrl_stop_dev() to cancel inflight uring_cmd before cleanup. Without this, the device deletion may hang waiting for I/O completion that will never happen. 2. Add fail_start label so that pthread_join() is called on the error path. This ensures proper thread cleanup when startup fails. Fixes: 6aecda00b7d1 ("selftests: ublk: add kernel selftests for ublk") Signed-off-by: Ming Lei Reviewed-by: Caleb Sander Mateos Signed-off-by: Jens Axboe Signed-off-by: Sasha Levin --- tools/testing/selftests/ublk/kublk.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/tools/testing/selftests/ublk/kublk.c b/tools/testing/selftests/ublk/kublk.c index 0e863d13eaee4f..9c05f046ad5eea 100644 --- a/tools/testing/selftests/ublk/kublk.c +++ b/tools/testing/selftests/ublk/kublk.c @@ -1002,7 +1002,9 @@ static int ublk_start_daemon(const struct dev_ctx *ctx, struct ublk_dev *dev) } if (ret < 0) { ublk_err("%s: ublk_ctrl_start_dev failed: %d\n", __func__, ret); - goto fail; + /* stop device so that inflight uring_cmd can be cancelled */ + ublk_ctrl_stop_dev(dev); + goto fail_start; } ublk_ctrl_get_info(dev); @@ -1010,7 +1012,7 @@ static int ublk_start_daemon(const struct dev_ctx *ctx, struct ublk_dev *dev) ublk_ctrl_dump(dev); else ublk_send_dev_event(ctx, dev, dev->dev_info.dev_id); - +fail_start: /* wait until we are terminated */ for (i = 0; i < dev->nthreads; i++) pthread_join(dev->threads[i].thread, &thread_ret); From 9d88a79e9018c03edcbce872c072ef22211c0818 Mon Sep 17 00:00:00 2001 From: Ming Lei Date: Tue, 13 Jan 2026 16:58:02 +0800 Subject: [PATCH 2748/3902] selftests/ublk: fix garbage output in foreground mode [ Upstream commit e7e1cc18f120a415646be12470169a978a1adcd9 ] Initialize _evtfd to -1 in struct dev_ctx to prevent garbage output when running kublk in foreground mode. Without this, _evtfd is zero-initialized to 0 (stdin), and ublk_send_dev_event() writes binary data to stdin which appears as garbage on the terminal. Also fix debug message format string. Fixes: 6aecda00b7d1 ("selftests: ublk: add kernel selftests for ublk") Signed-off-by: Ming Lei Reviewed-by: Caleb Sander Mateos Signed-off-by: Jens Axboe Signed-off-by: Sasha Levin --- tools/testing/selftests/ublk/kublk.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tools/testing/selftests/ublk/kublk.c b/tools/testing/selftests/ublk/kublk.c index 9c05f046ad5eea..cbd23444c8a987 100644 --- a/tools/testing/selftests/ublk/kublk.c +++ b/tools/testing/selftests/ublk/kublk.c @@ -1221,7 +1221,7 @@ static int __cmd_dev_add(const struct dev_ctx *ctx) } ret = ublk_start_daemon(ctx, dev); - ublk_dbg(UBLK_DBG_DEV, "%s: daemon exit %d\b", ret); + ublk_dbg(UBLK_DBG_DEV, "%s: daemon exit %d\n", __func__, ret); if (ret < 0) ublk_ctrl_del_dev(dev); @@ -1566,6 +1566,7 @@ int main(int argc, char *argv[]) int option_idx, opt; const char *cmd = argv[1]; struct dev_ctx ctx = { + ._evtfd = -1, .queue_depth = 128, .nr_hw_queues = 2, .dev_id = -1, From 23c0e4bd93d0b250775162faf456470485ac9fc7 Mon Sep 17 00:00:00 2001 From: Will Rosenberg Date: Mon, 19 Jan 2026 11:49:56 -0700 Subject: [PATCH 2749/3902] perf: Fix refcount warning on event->mmap_count increment [ Upstream commit d06bf78e55d5159c1b00072e606ab924ffbbad35 ] When calling refcount_inc(&event->mmap_count) inside perf_mmap_rb(), the following warning is triggered: refcount_t: addition on 0; use-after-free. WARNING: lib/refcount.c:25 PoC: struct perf_event_attr attr = {0}; int fd = syscall(__NR_perf_event_open, &attr, 0, -1, -1, 0); mmap(NULL, 0x3000, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0); int victim = syscall(__NR_perf_event_open, &attr, 0, -1, fd, PERF_FLAG_FD_OUTPUT); mmap(NULL, 0x3000, PROT_READ | PROT_WRITE, MAP_SHARED, victim, 0); This occurs when creating a group member event with the flag PERF_FLAG_FD_OUTPUT. The group leader should be mmap-ed and then mmap-ing the event triggers the warning. Since the event has copied the output_event in perf_event_set_output(), event->rb is set. As a result, perf_mmap_rb() calls refcount_inc(&event->mmap_count) when event->mmap_count = 0. Disallow the case when event->mmap_count = 0. This also prevents two events from updating the same user_page. Fixes: 448f97fba901 ("perf: Convert mmap() refcounts to refcount_t") Suggested-by: Peter Zijlstra Signed-off-by: Will Rosenberg Signed-off-by: Peter Zijlstra (Intel) Link: https://patch.msgid.link/20260119184956.801238-1-whrosenb@asu.edu Signed-off-by: Sasha Levin --- kernel/events/core.c | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/kernel/events/core.c b/kernel/events/core.c index d95f9dce018f4c..df0717f4592a9b 100644 --- a/kernel/events/core.c +++ b/kernel/events/core.c @@ -6996,6 +6996,15 @@ static int perf_mmap_rb(struct vm_area_struct *vma, struct perf_event *event, if (data_page_nr(event->rb) != nr_pages) return -EINVAL; + /* + * If this event doesn't have mmap_count, we're attempting to + * create an alias of another event's mmap(); this would mean + * both events will end up scribbling the same user_page; + * which makes no sense. + */ + if (!refcount_read(&event->mmap_count)) + return -EBUSY; + if (refcount_inc_not_zero(&event->rb->mmap_count)) { /* * Success -- managed to mmap() the same buffer From 79a074be9b57e921f307bdd48c453d46e372c22e Mon Sep 17 00:00:00 2001 From: Vincent Guittot Date: Wed, 21 Jan 2026 17:33:17 +0100 Subject: [PATCH 2750/3902] sched/fair: Fix pelt clock sync when entering idle [ Upstream commit 98c88dc8a1ace642d9021b103b28cba7b51e3abc ] Samuel and Alex reported regressions of the util_avg of RT rq with commit 17e3e88ed0b6 ("sched/fair: Fix pelt lost idle time detection"). It happens that fair is updating and syncing the pelt clock with task one when pick_next_task_fair() fails to pick a task but before the prev scheduling class got a chance to update its pelt signals. Move update_idle_rq_clock_pelt() in set_next_task_idle() which is called after prev class has been called. Fixes: 17e3e88ed0b6 ("sched/fair: Fix pelt lost idle time detection") Closes: https://lore.kernel.org/all/CAG2KctpO6VKS6GN4QWDji0t92_gNBJ7HjjXrE+6H+RwRXt=iLg@mail.gmail.com/ Closes: https://lore.kernel.org/all/8cf19bf0e0054dcfed70e9935029201694f1bb5a.camel@mediatek.com/ Reported-by: Samuel Wu Reported-by: Alex Hoh Signed-off-by: Vincent Guittot Signed-off-by: Peter Zijlstra (Intel) Tested-by: Samuel Wu Tested-by: Alex Hoh Link: https://patch.msgid.link/20260121163317.505635-1-vincent.guittot@linaro.org Signed-off-by: Sasha Levin --- kernel/sched/fair.c | 6 ------ kernel/sched/idle.c | 6 ++++++ 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/kernel/sched/fair.c b/kernel/sched/fair.c index d1206f81f8b2e0..f0c7c94421beae 100644 --- a/kernel/sched/fair.c +++ b/kernel/sched/fair.c @@ -8901,12 +8901,6 @@ pick_next_task_fair(struct rq *rq, struct task_struct *prev, struct rq_flags *rf goto again; } - /* - * rq is about to be idle, check if we need to update the - * lost_idle_time of clock_pelt - */ - update_idle_rq_clock_pelt(rq); - return NULL; } diff --git a/kernel/sched/idle.c b/kernel/sched/idle.c index c39b089d4f09b6..ac9690805be4f3 100644 --- a/kernel/sched/idle.c +++ b/kernel/sched/idle.c @@ -464,6 +464,12 @@ static void set_next_task_idle(struct rq *rq, struct task_struct *next, bool fir scx_update_idle(rq, true, true); schedstat_inc(rq->sched_goidle); next->se.exec_start = rq_clock_task(rq); + + /* + * rq is about to be idle, check if we need to update the + * lost_idle_time of clock_pelt + */ + update_idle_rq_clock_pelt(rq); } struct task_struct *pick_task_idle(struct rq *rq) From 91243b926ae96df7a4a8a70e64a0d94fe975fd07 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Timur=20Krist=C3=B3f?= Date: Mon, 19 Jan 2026 21:36:22 +0100 Subject: [PATCH 2751/3902] drm/amd/pm: Fix si_dpm mmCG_THERMAL_INT setting MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [ Upstream commit 4ca284c6d15dda481f714e3687a1d5fb70b3bf5c ] Use WREG32 to write mmCG_THERMAL_INT. This is a direct access register. Fixes: 841686df9f7d ("drm/amdgpu: add SI DPM support (v4)") Reviewed-by: Alex Deucher Signed-off-by: Timur Kristóf Signed-off-by: Alex Deucher (cherry picked from commit 2555f4e4a741d31e0496572a8ab4f55941b4e30e) Signed-off-by: Sasha Levin --- drivers/gpu/drm/amd/pm/legacy-dpm/si_dpm.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/drivers/gpu/drm/amd/pm/legacy-dpm/si_dpm.c b/drivers/gpu/drm/amd/pm/legacy-dpm/si_dpm.c index 3a9522c17fee30..70499bf50ad47b 100644 --- a/drivers/gpu/drm/amd/pm/legacy-dpm/si_dpm.c +++ b/drivers/gpu/drm/amd/pm/legacy-dpm/si_dpm.c @@ -7600,12 +7600,12 @@ static int si_dpm_set_interrupt_state(struct amdgpu_device *adev, case AMDGPU_IRQ_STATE_DISABLE: cg_thermal_int = RREG32_SMC(mmCG_THERMAL_INT); cg_thermal_int |= CG_THERMAL_INT__THERM_INT_MASK_HIGH_MASK; - WREG32_SMC(mmCG_THERMAL_INT, cg_thermal_int); + WREG32(mmCG_THERMAL_INT, cg_thermal_int); break; case AMDGPU_IRQ_STATE_ENABLE: cg_thermal_int = RREG32_SMC(mmCG_THERMAL_INT); cg_thermal_int &= ~CG_THERMAL_INT__THERM_INT_MASK_HIGH_MASK; - WREG32_SMC(mmCG_THERMAL_INT, cg_thermal_int); + WREG32(mmCG_THERMAL_INT, cg_thermal_int); break; default: break; @@ -7617,12 +7617,12 @@ static int si_dpm_set_interrupt_state(struct amdgpu_device *adev, case AMDGPU_IRQ_STATE_DISABLE: cg_thermal_int = RREG32_SMC(mmCG_THERMAL_INT); cg_thermal_int |= CG_THERMAL_INT__THERM_INT_MASK_LOW_MASK; - WREG32_SMC(mmCG_THERMAL_INT, cg_thermal_int); + WREG32(mmCG_THERMAL_INT, cg_thermal_int); break; case AMDGPU_IRQ_STATE_ENABLE: cg_thermal_int = RREG32_SMC(mmCG_THERMAL_INT); cg_thermal_int &= ~CG_THERMAL_INT__THERM_INT_MASK_LOW_MASK; - WREG32_SMC(mmCG_THERMAL_INT, cg_thermal_int); + WREG32(mmCG_THERMAL_INT, cg_thermal_int); break; default: break; From c85aac064f5c23d3b32c6034774b695eb812d20c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Timur=20Krist=C3=B3f?= Date: Mon, 19 Jan 2026 21:36:23 +0100 Subject: [PATCH 2752/3902] drm/amd/pm: Don't clear SI SMC table when setting power limit MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [ Upstream commit d5077426e1a76d269e518e048bde2e9fc49b32ad ] There is no reason to clear the SMC table. We also don't need to recalculate the power limit then. Fixes: 841686df9f7d ("drm/amdgpu: add SI DPM support (v4)") Reviewed-by: Alex Deucher Signed-off-by: Timur Kristóf Signed-off-by: Alex Deucher (cherry picked from commit e214d626253f5b180db10dedab161b7caa41f5e9) Signed-off-by: Sasha Levin --- drivers/gpu/drm/amd/pm/legacy-dpm/si_dpm.c | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/drivers/gpu/drm/amd/pm/legacy-dpm/si_dpm.c b/drivers/gpu/drm/amd/pm/legacy-dpm/si_dpm.c index 70499bf50ad47b..08ffa791541113 100644 --- a/drivers/gpu/drm/amd/pm/legacy-dpm/si_dpm.c +++ b/drivers/gpu/drm/amd/pm/legacy-dpm/si_dpm.c @@ -2273,8 +2273,6 @@ static int si_populate_smc_tdp_limits(struct amdgpu_device *adev, if (scaling_factor == 0) return -EINVAL; - memset(smc_table, 0, sizeof(SISLANDS_SMC_STATETABLE)); - ret = si_calculate_adjusted_tdp_limits(adev, false, /* ??? */ adev->pm.dpm.tdp_adjustment, @@ -2328,16 +2326,8 @@ static int si_populate_smc_tdp_limits_2(struct amdgpu_device *adev, if (ni_pi->enable_power_containment) { SISLANDS_SMC_STATETABLE *smc_table = &si_pi->smc_statetable; - u32 scaling_factor = si_get_smc_power_scaling_factor(adev); int ret; - memset(smc_table, 0, sizeof(SISLANDS_SMC_STATETABLE)); - - smc_table->dpm2Params.NearTDPLimit = - cpu_to_be32(si_scale_power_for_smc(adev->pm.dpm.near_tdp_limit_adjusted, scaling_factor) * 1000); - smc_table->dpm2Params.SafePowerLimit = - cpu_to_be32(si_scale_power_for_smc((adev->pm.dpm.near_tdp_limit_adjusted * SISLANDS_DPM2_TDP_SAFE_LIMIT_PERCENT) / 100, scaling_factor) * 1000); - ret = amdgpu_si_copy_bytes_to_smc(adev, (si_pi->state_table_start + offsetof(SISLANDS_SMC_STATETABLE, dpm2Params) + From c6ec1f665f8f71867cc614f09a4f55c03ef6479f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Timur=20Krist=C3=B3f?= Date: Mon, 19 Jan 2026 21:36:24 +0100 Subject: [PATCH 2753/3902] drm/amd/pm: Workaround SI powertune issue on Radeon 430 (v2) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [ Upstream commit 764a90eb02268a23b1bb98be5f4a13671346804a ] Radeon 430 and 520 are OEM GPUs from 2016~2017 They have the same device id: 0x6611 and revision: 0x87 On the Radeon 430, powertune is buggy and throttles the GPU, never allowing it to reach its maximum SCLK. Work around this bug by raising the TDP limits we program to the SMC from 24W (specified by the VBIOS on Radeon 430) to 32W. Disabling powertune entirely is not a viable workaround, because it causes the Radeon 520 to heat up above 100 C, which I prefer to avoid. Additionally, revise the maximum SCLK limit. Considering the above issue, these GPUs never reached a high SCLK on Linux, and the workarounds were added before the GPUs were released, so the workaround likely didn't target these specifically. Use 780 MHz (the maximum SCLK according to the VBIOS on the Radeon 430). Note that the Radeon 520 VBIOS has a higher maximum SCLK: 905 MHz, but in practice it doesn't seem to perform better with the higher clock, only heats up more. v2: Move the workaround to si_populate_smc_tdp_limits. Fixes: 841686df9f7d ("drm/amdgpu: add SI DPM support (v4)") Reviewed-by: Alex Deucher Signed-off-by: Timur Kristóf Signed-off-by: Alex Deucher (cherry picked from commit 966d70f1e160bdfdecaf7ff2b3f22ad088516e9f) Signed-off-by: Sasha Levin --- drivers/gpu/drm/amd/pm/legacy-dpm/si_dpm.c | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/drivers/gpu/drm/amd/pm/legacy-dpm/si_dpm.c b/drivers/gpu/drm/amd/pm/legacy-dpm/si_dpm.c index 08ffa791541113..a1da3e5812ce3c 100644 --- a/drivers/gpu/drm/amd/pm/legacy-dpm/si_dpm.c +++ b/drivers/gpu/drm/amd/pm/legacy-dpm/si_dpm.c @@ -2281,6 +2281,12 @@ static int si_populate_smc_tdp_limits(struct amdgpu_device *adev, if (ret) return ret; + if (adev->pdev->device == 0x6611 && adev->pdev->revision == 0x87) { + /* Workaround buggy powertune on Radeon 430 and 520. */ + tdp_limit = 32; + near_tdp_limit = 28; + } + smc_table->dpm2Params.TDPLimit = cpu_to_be32(si_scale_power_for_smc(tdp_limit, scaling_factor) * 1000); smc_table->dpm2Params.NearTDPLimit = @@ -3468,10 +3474,15 @@ static void si_apply_state_adjust_rules(struct amdgpu_device *adev, (adev->pdev->revision == 0x80) || (adev->pdev->revision == 0x81) || (adev->pdev->revision == 0x83) || - (adev->pdev->revision == 0x87) || + (adev->pdev->revision == 0x87 && + adev->pdev->device != 0x6611) || (adev->pdev->device == 0x6604) || (adev->pdev->device == 0x6605)) { max_sclk = 75000; + } else if (adev->pdev->revision == 0x87 && + adev->pdev->device == 0x6611) { + /* Radeon 430 and 520 */ + max_sclk = 78000; } } From 8fd181dada6ea0a5b09d2dfec0004167da9b3442 Mon Sep 17 00:00:00 2001 From: Alex Deucher Date: Thu, 15 Jan 2026 21:45:43 -0500 Subject: [PATCH 2754/3902] drm/amdgpu: fix type for wptr in ring backup MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [ Upstream commit 095ca815174e51fc0049771712d5455cabd7231e ] Needs to be a u64. Fixes: 77cc0da39c7c ("drm/amdgpu: track ring state associated with a fence") Reviewed-by: Christian König Signed-off-by: Alex Deucher (cherry picked from commit 56fff1941abd3ca3b6f394979614ca7972552f7f) Signed-off-by: Sasha Levin --- drivers/gpu/drm/amd/amdgpu/amdgpu_fence.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_fence.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_fence.c index 18a7829122d246..89a639044d5203 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_fence.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_fence.c @@ -804,7 +804,7 @@ void amdgpu_fence_save_wptr(struct dma_fence *fence) } static void amdgpu_ring_backup_unprocessed_command(struct amdgpu_ring *ring, - u64 start_wptr, u32 end_wptr) + u64 start_wptr, u64 end_wptr) { unsigned int first_idx = start_wptr & ring->buf_mask; unsigned int last_idx = end_wptr & ring->buf_mask; From fbc8d1a39da9f8a33d7e4ef31c62673b9771bff8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alex=20Ram=C3=ADrez?= Date: Fri, 12 Dec 2025 19:53:26 -0500 Subject: [PATCH 2755/3902] drm/nouveau: add missing DCB connector types MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [ Upstream commit 3036b4ce4b209af690fa776e4616925892caba4c ] * Add missing DCB connectors in conn.h as per the NVIDIA DCB specification. A lot of connector logic was rewritten for Linux v6.5; some display connector types went unaccounted-for which caused kernel warnings on devices with the now-unsupported DCB connectors. This patch adds all of the DCB connectors as defined by NVIDIA to the dcb_connector_type enum to bring back support for these connectors to the new logic. Fixes: 8b7d92cad953 ("drm/nouveau/kms/nv50-: create connectors based on nvkm info") Link: https://download.nvidia.com/open-gpu-doc/DCB/1/DCB-4.0-Specification.html#_connector_table_entry Signed-off-by: Alex Ramírez Reviewed-by: Lyude Paul [Lyude: Clarify DCB_CONNECTOR_HDMI_0 weirdness in comments] Signed-off-by: Lyude Paul Link: https://patch.msgid.link/20251213005327.9495-2-lxrmrz732@gmail.com Signed-off-by: Sasha Levin --- .../nouveau/include/nvkm/subdev/bios/conn.h | 95 +++++++++++++++---- 1 file changed, 74 insertions(+), 21 deletions(-) diff --git a/drivers/gpu/drm/nouveau/include/nvkm/subdev/bios/conn.h b/drivers/gpu/drm/nouveau/include/nvkm/subdev/bios/conn.h index d1beaad0c82b62..834ed6587aa528 100644 --- a/drivers/gpu/drm/nouveau/include/nvkm/subdev/bios/conn.h +++ b/drivers/gpu/drm/nouveau/include/nvkm/subdev/bios/conn.h @@ -1,28 +1,81 @@ /* SPDX-License-Identifier: MIT */ #ifndef __NVBIOS_CONN_H__ #define __NVBIOS_CONN_H__ + +/* + * An enumerator representing all of the possible VBIOS connector types defined + * by Nvidia at + * https://nvidia.github.io/open-gpu-doc/DCB/DCB-4.x-Specification.html. + * + * [1] Nvidia's documentation actually claims DCB_CONNECTOR_HDMI_0 is a "3-Pin + * DIN Stereo Connector". This seems very likely to be a documentation typo + * or some sort of funny historical baggage, because we've treated this + * connector type as HDMI for years without issue. + * TODO: Check with Nvidia what's actually happening here. + */ enum dcb_connector_type { - DCB_CONNECTOR_VGA = 0x00, - DCB_CONNECTOR_TV_0 = 0x10, - DCB_CONNECTOR_TV_1 = 0x11, - DCB_CONNECTOR_TV_3 = 0x13, - DCB_CONNECTOR_DVI_I = 0x30, - DCB_CONNECTOR_DVI_D = 0x31, - DCB_CONNECTOR_DMS59_0 = 0x38, - DCB_CONNECTOR_DMS59_1 = 0x39, - DCB_CONNECTOR_LVDS = 0x40, - DCB_CONNECTOR_LVDS_SPWG = 0x41, - DCB_CONNECTOR_DP = 0x46, - DCB_CONNECTOR_eDP = 0x47, - DCB_CONNECTOR_mDP = 0x48, - DCB_CONNECTOR_HDMI_0 = 0x60, - DCB_CONNECTOR_HDMI_1 = 0x61, - DCB_CONNECTOR_HDMI_C = 0x63, - DCB_CONNECTOR_DMS59_DP0 = 0x64, - DCB_CONNECTOR_DMS59_DP1 = 0x65, - DCB_CONNECTOR_WFD = 0x70, - DCB_CONNECTOR_USB_C = 0x71, - DCB_CONNECTOR_NONE = 0xff + /* Analog outputs */ + DCB_CONNECTOR_VGA = 0x00, // VGA 15-pin connector + DCB_CONNECTOR_DVI_A = 0x01, // DVI-A + DCB_CONNECTOR_POD_VGA = 0x02, // Pod - VGA 15-pin connector + DCB_CONNECTOR_TV_0 = 0x10, // TV - Composite Out + DCB_CONNECTOR_TV_1 = 0x11, // TV - S-Video Out + DCB_CONNECTOR_TV_2 = 0x12, // TV - S-Video Breakout - Composite + DCB_CONNECTOR_TV_3 = 0x13, // HDTV Component - YPrPb + DCB_CONNECTOR_TV_SCART = 0x14, // TV - SCART Connector + DCB_CONNECTOR_TV_SCART_D = 0x16, // TV - Composite SCART over D-connector + DCB_CONNECTOR_TV_DTERM = 0x17, // HDTV - D-connector (EIAJ4120) + DCB_CONNECTOR_POD_TV_3 = 0x18, // Pod - HDTV - YPrPb + DCB_CONNECTOR_POD_TV_1 = 0x19, // Pod - S-Video + DCB_CONNECTOR_POD_TV_0 = 0x1a, // Pod - Composite + + /* DVI digital outputs */ + DCB_CONNECTOR_DVI_I_TV_1 = 0x20, // DVI-I-TV-S-Video + DCB_CONNECTOR_DVI_I_TV_0 = 0x21, // DVI-I-TV-Composite + DCB_CONNECTOR_DVI_I_TV_2 = 0x22, // DVI-I-TV-S-Video Breakout-Composite + DCB_CONNECTOR_DVI_I = 0x30, // DVI-I + DCB_CONNECTOR_DVI_D = 0x31, // DVI-D + DCB_CONNECTOR_DVI_ADC = 0x32, // Apple Display Connector (ADC) + DCB_CONNECTOR_DMS59_0 = 0x38, // LFH-DVI-I-1 + DCB_CONNECTOR_DMS59_1 = 0x39, // LFH-DVI-I-2 + DCB_CONNECTOR_BNC = 0x3c, // BNC Connector [for SDI?] + + /* LVDS / TMDS digital outputs */ + DCB_CONNECTOR_LVDS = 0x40, // LVDS-SPWG-Attached [is this name correct?] + DCB_CONNECTOR_LVDS_SPWG = 0x41, // LVDS-OEM-Attached (non-removable) + DCB_CONNECTOR_LVDS_REM = 0x42, // LVDS-SPWG-Detached [following naming above] + DCB_CONNECTOR_LVDS_SPWG_REM = 0x43, // LVDS-OEM-Detached (removable) + DCB_CONNECTOR_TMDS = 0x45, // TMDS-OEM-Attached (non-removable) + + /* DP digital outputs */ + DCB_CONNECTOR_DP = 0x46, // DisplayPort External Connector + DCB_CONNECTOR_eDP = 0x47, // DisplayPort Internal Connector + DCB_CONNECTOR_mDP = 0x48, // DisplayPort (Mini) External Connector + + /* Dock outputs (not used) */ + DCB_CONNECTOR_DOCK_VGA_0 = 0x50, // VGA 15-pin if not docked + DCB_CONNECTOR_DOCK_VGA_1 = 0x51, // VGA 15-pin if docked + DCB_CONNECTOR_DOCK_DVI_I_0 = 0x52, // DVI-I if not docked + DCB_CONNECTOR_DOCK_DVI_I_1 = 0x53, // DVI-I if docked + DCB_CONNECTOR_DOCK_DVI_D_0 = 0x54, // DVI-D if not docked + DCB_CONNECTOR_DOCK_DVI_D_1 = 0x55, // DVI-D if docked + DCB_CONNECTOR_DOCK_DP_0 = 0x56, // DisplayPort if not docked + DCB_CONNECTOR_DOCK_DP_1 = 0x57, // DisplayPort if docked + DCB_CONNECTOR_DOCK_mDP_0 = 0x58, // DisplayPort (Mini) if not docked + DCB_CONNECTOR_DOCK_mDP_1 = 0x59, // DisplayPort (Mini) if docked + + /* HDMI? digital outputs */ + DCB_CONNECTOR_HDMI_0 = 0x60, // HDMI? See [1] in top-level enum comment above + DCB_CONNECTOR_HDMI_1 = 0x61, // HDMI-A connector + DCB_CONNECTOR_SPDIF = 0x62, // Audio S/PDIF connector + DCB_CONNECTOR_HDMI_C = 0x63, // HDMI-C (Mini) connector + + /* Misc. digital outputs */ + DCB_CONNECTOR_DMS59_DP0 = 0x64, // LFH-DP-1 + DCB_CONNECTOR_DMS59_DP1 = 0x65, // LFH-DP-2 + DCB_CONNECTOR_WFD = 0x70, // Virtual connector for Wifi Display (WFD) + DCB_CONNECTOR_USB_C = 0x71, // [DP over USB-C; not present in docs] + DCB_CONNECTOR_NONE = 0xff // Skip Entry }; struct nvbios_connT { From f47c6e5a532cccab450a0afb9790ca0dbb2427f8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alex=20Ram=C3=ADrez?= Date: Fri, 12 Dec 2025 19:53:27 -0500 Subject: [PATCH 2756/3902] drm/nouveau: implement missing DCB connector types; gracefully handle unknown connectors MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [ Upstream commit d0bd10792d6cc3725ddee43f03fd6ee234f24844 ] * Implement missing DCB connectors in uconn.c previously defined in conn.h. * Replace kernel WARN_ON macro with printk message to more gracefully signify an unknown connector was encountered. With this patch, unknown connectors are explicitly marked with value 0 (DCB_CONNECTOR_VGA) to match the tested current behavior. Although 0xff (DCB_CONNECTOR_NONE) may be more suitable, I don't want to introduce a breaking change. Fixes: 8b7d92cad953 ("drm/nouveau/kms/nv50-: create connectors based on nvkm info") Link: https://download.nvidia.com/open-gpu-doc/DCB/1/DCB-4.0-Specification.html#_connector_table_entry Signed-off-by: Alex Ramírez Reviewed-by: Lyude Paul [Lyude: Remove unneeded parenthesis around nvkm_warn()] Signed-off-by: Lyude Paul Link: https://patch.msgid.link/20251213005327.9495-3-lxrmrz732@gmail.com Signed-off-by: Sasha Levin --- .../gpu/drm/nouveau/nvkm/engine/disp/uconn.c | 73 ++++++++++++++----- 1 file changed, 53 insertions(+), 20 deletions(-) diff --git a/drivers/gpu/drm/nouveau/nvkm/engine/disp/uconn.c b/drivers/gpu/drm/nouveau/nvkm/engine/disp/uconn.c index 2dab6612c4fc84..23d1e5c27bb1e4 100644 --- a/drivers/gpu/drm/nouveau/nvkm/engine/disp/uconn.c +++ b/drivers/gpu/drm/nouveau/nvkm/engine/disp/uconn.c @@ -191,27 +191,60 @@ nvkm_uconn_new(const struct nvkm_oclass *oclass, void *argv, u32 argc, struct nv spin_lock(&disp->client.lock); if (!conn->object.func) { switch (conn->info.type) { - case DCB_CONNECTOR_VGA : args->v0.type = NVIF_CONN_V0_VGA; break; - case DCB_CONNECTOR_TV_0 : - case DCB_CONNECTOR_TV_1 : - case DCB_CONNECTOR_TV_3 : args->v0.type = NVIF_CONN_V0_TV; break; - case DCB_CONNECTOR_DMS59_0 : - case DCB_CONNECTOR_DMS59_1 : - case DCB_CONNECTOR_DVI_I : args->v0.type = NVIF_CONN_V0_DVI_I; break; - case DCB_CONNECTOR_DVI_D : args->v0.type = NVIF_CONN_V0_DVI_D; break; - case DCB_CONNECTOR_LVDS : args->v0.type = NVIF_CONN_V0_LVDS; break; - case DCB_CONNECTOR_LVDS_SPWG: args->v0.type = NVIF_CONN_V0_LVDS_SPWG; break; - case DCB_CONNECTOR_DMS59_DP0: - case DCB_CONNECTOR_DMS59_DP1: - case DCB_CONNECTOR_DP : - case DCB_CONNECTOR_mDP : - case DCB_CONNECTOR_USB_C : args->v0.type = NVIF_CONN_V0_DP; break; - case DCB_CONNECTOR_eDP : args->v0.type = NVIF_CONN_V0_EDP; break; - case DCB_CONNECTOR_HDMI_0 : - case DCB_CONNECTOR_HDMI_1 : - case DCB_CONNECTOR_HDMI_C : args->v0.type = NVIF_CONN_V0_HDMI; break; + /* VGA */ + case DCB_CONNECTOR_DVI_A : + case DCB_CONNECTOR_POD_VGA : + case DCB_CONNECTOR_VGA : args->v0.type = NVIF_CONN_V0_VGA; break; + + /* TV */ + case DCB_CONNECTOR_TV_0 : + case DCB_CONNECTOR_TV_1 : + case DCB_CONNECTOR_TV_2 : + case DCB_CONNECTOR_TV_SCART : + case DCB_CONNECTOR_TV_SCART_D : + case DCB_CONNECTOR_TV_DTERM : + case DCB_CONNECTOR_POD_TV_3 : + case DCB_CONNECTOR_POD_TV_1 : + case DCB_CONNECTOR_POD_TV_0 : + case DCB_CONNECTOR_TV_3 : args->v0.type = NVIF_CONN_V0_TV; break; + + /* DVI */ + case DCB_CONNECTOR_DVI_I_TV_1 : + case DCB_CONNECTOR_DVI_I_TV_0 : + case DCB_CONNECTOR_DVI_I_TV_2 : + case DCB_CONNECTOR_DVI_ADC : + case DCB_CONNECTOR_DMS59_0 : + case DCB_CONNECTOR_DMS59_1 : + case DCB_CONNECTOR_DVI_I : args->v0.type = NVIF_CONN_V0_DVI_I; break; + case DCB_CONNECTOR_TMDS : + case DCB_CONNECTOR_DVI_D : args->v0.type = NVIF_CONN_V0_DVI_D; break; + + /* LVDS */ + case DCB_CONNECTOR_LVDS : args->v0.type = NVIF_CONN_V0_LVDS; break; + case DCB_CONNECTOR_LVDS_SPWG : args->v0.type = NVIF_CONN_V0_LVDS_SPWG; break; + + /* DP */ + case DCB_CONNECTOR_DMS59_DP0 : + case DCB_CONNECTOR_DMS59_DP1 : + case DCB_CONNECTOR_DP : + case DCB_CONNECTOR_mDP : + case DCB_CONNECTOR_USB_C : args->v0.type = NVIF_CONN_V0_DP; break; + case DCB_CONNECTOR_eDP : args->v0.type = NVIF_CONN_V0_EDP; break; + + /* HDMI */ + case DCB_CONNECTOR_HDMI_0 : + case DCB_CONNECTOR_HDMI_1 : + case DCB_CONNECTOR_HDMI_C : args->v0.type = NVIF_CONN_V0_HDMI; break; + + /* + * Dock & unused outputs. + * BNC, SPDIF, WFD, and detached LVDS go here. + */ default: - WARN_ON(1); + nvkm_warn(&disp->engine.subdev, + "unimplemented connector type 0x%02x\n", + conn->info.type); + args->v0.type = NVIF_CONN_V0_VGA; ret = -EINVAL; break; } From 31410a01a86bcb98c798d01061abf1f789c4f75a Mon Sep 17 00:00:00 2001 From: Andrey Vatoropin Date: Tue, 20 Jan 2026 11:37:47 +0000 Subject: [PATCH 2757/3902] be2net: Fix NULL pointer dereference in be_cmd_get_mac_from_list [ Upstream commit 8215794403d264739cc676668087512950b2ff31 ] When the parameter pmac_id_valid argument of be_cmd_get_mac_from_list() is set to false, the driver may request the PMAC_ID from the firmware of the network card, and this function will store that PMAC_ID at the provided address pmac_id. This is the contract of this function. However, there is a location within the driver where both pmac_id_valid == false and pmac_id == NULL are being passed. This could result in dereferencing a NULL pointer. To resolve this issue, it is necessary to pass the address of a stub variable to the function. Fixes: 95046b927a54 ("be2net: refactor MAC-addr setup code") Signed-off-by: Andrey Vatoropin Link: https://patch.msgid.link/20260120113734.20193-1-a.vatoropin@crpt.ru Signed-off-by: Jakub Kicinski Signed-off-by: Sasha Levin --- drivers/net/ethernet/emulex/benet/be_cmds.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/drivers/net/ethernet/emulex/benet/be_cmds.c b/drivers/net/ethernet/emulex/benet/be_cmds.c index bb5d2fa157365f..8ed45bceb5379a 100644 --- a/drivers/net/ethernet/emulex/benet/be_cmds.c +++ b/drivers/net/ethernet/emulex/benet/be_cmds.c @@ -3801,6 +3801,7 @@ int be_cmd_get_perm_mac(struct be_adapter *adapter, u8 *mac) { int status; bool pmac_valid = false; + u32 pmac_id; eth_zero_addr(mac); @@ -3813,7 +3814,7 @@ int be_cmd_get_perm_mac(struct be_adapter *adapter, u8 *mac) adapter->if_handle, 0); } else { status = be_cmd_get_mac_from_list(adapter, mac, &pmac_valid, - NULL, adapter->if_handle, 0); + &pmac_id, adapter->if_handle, 0); } return status; From cb32707497d48a3a73d602c3cca902ac7abaa502 Mon Sep 17 00:00:00 2001 From: Taehee Yoo Date: Tue, 20 Jan 2026 13:39:30 +0000 Subject: [PATCH 2758/3902] selftests: net: amt: wait longer for connection before sending packets [ Upstream commit 04708606fd7bdc34b69089a4ff848ff36d7088f9 ] Both send_mcast4() and send_mcast6() use sleep 2 to wait for the tunnel connection between the gateway and the relay, and for the listener socket to be created in the LISTENER namespace. However, tests sometimes fail because packets are sent before the connection is fully established. Increase the waiting time to make the tests more reliable, and use wait_local_port_listen() to explicitly wait for the listener socket. Fixes: c08e8baea78e ("selftests: add amt interface selftest script") Signed-off-by: Taehee Yoo Link: https://patch.msgid.link/20260120133930.863845-1-ap420073@gmail.com Signed-off-by: Jakub Kicinski Signed-off-by: Sasha Levin --- tools/testing/selftests/net/amt.sh | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/tools/testing/selftests/net/amt.sh b/tools/testing/selftests/net/amt.sh index 3ef209cacb8ed3..663744305e5219 100755 --- a/tools/testing/selftests/net/amt.sh +++ b/tools/testing/selftests/net/amt.sh @@ -73,6 +73,8 @@ # +------------------------+ #============================================================================== +source lib.sh + readonly LISTENER=$(mktemp -u listener-XXXXXXXX) readonly GATEWAY=$(mktemp -u gateway-XXXXXXXX) readonly RELAY=$(mktemp -u relay-XXXXXXXX) @@ -246,14 +248,15 @@ test_ipv6_forward() send_mcast4() { - sleep 2 + sleep 5 + wait_local_port_listen ${LISTENER} 4000 udp ip netns exec "${SOURCE}" bash -c \ 'printf "%s %128s" 172.17.0.2 | nc -w 1 -u 239.0.0.1 4000' & } send_mcast6() { - sleep 2 + wait_local_port_listen ${LISTENER} 6000 udp ip netns exec "${SOURCE}" bash -c \ 'printf "%s %128s" 2001:db8:3::2 | nc -w 1 -u ff0e::5:6 6000' & } From de97735a40a144974bf3896ee4cc0270db2e47db Mon Sep 17 00:00:00 2001 From: Eric Dumazet Date: Tue, 20 Jan 2026 16:17:44 +0000 Subject: [PATCH 2759/3902] bonding: provide a net pointer to __skb_flow_dissect() [ Upstream commit 5f9b329096596b7e53e07d041d7fca4cbe1be752 ] After 3cbf4ffba5ee ("net: plumb network namespace into __skb_flow_dissect") we have to provide a net pointer to __skb_flow_dissect(), either via skb->dev, skb->sk, or a user provided pointer. In the following case, syzbot was able to cook a bare skb. WARNING: net/core/flow_dissector.c:1131 at __skb_flow_dissect+0xb57/0x68b0 net/core/flow_dissector.c:1131, CPU#1: syz.2.1418/11053 Call Trace: bond_flow_dissect drivers/net/bonding/bond_main.c:4093 [inline] __bond_xmit_hash+0x2d7/0xba0 drivers/net/bonding/bond_main.c:4157 bond_xmit_hash_xdp drivers/net/bonding/bond_main.c:4208 [inline] bond_xdp_xmit_3ad_xor_slave_get drivers/net/bonding/bond_main.c:5139 [inline] bond_xdp_get_xmit_slave+0x1fd/0x710 drivers/net/bonding/bond_main.c:5515 xdp_master_redirect+0x13f/0x2c0 net/core/filter.c:4388 bpf_prog_run_xdp include/net/xdp.h:700 [inline] bpf_test_run+0x6b2/0x7d0 net/bpf/test_run.c:421 bpf_prog_test_run_xdp+0x795/0x10e0 net/bpf/test_run.c:1390 bpf_prog_test_run+0x2c7/0x340 kernel/bpf/syscall.c:4703 __sys_bpf+0x562/0x860 kernel/bpf/syscall.c:6182 __do_sys_bpf kernel/bpf/syscall.c:6274 [inline] __se_sys_bpf kernel/bpf/syscall.c:6272 [inline] __x64_sys_bpf+0x7c/0x90 kernel/bpf/syscall.c:6272 do_syscall_x64 arch/x86/entry/syscall_64.c:63 [inline] do_syscall_64+0xec/0xf80 arch/x86/entry/syscall_64.c:94 Fixes: 58deb77cc52d ("bonding: balance ICMP echoes in layer3+4 mode") Reported-by: syzbot+c46409299c70a221415e@syzkaller.appspotmail.com Closes: https://lore.kernel.org/netdev/696faa23.050a0220.4cb9c.001f.GAE@google.com/T/#u Signed-off-by: Eric Dumazet Cc: Matteo Croce Acked-by: Stanislav Fomichev Link: https://patch.msgid.link/20260120161744.1893263-1-edumazet@google.com Signed-off-by: Jakub Kicinski Signed-off-by: Sasha Levin --- drivers/net/bonding/bond_main.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/drivers/net/bonding/bond_main.c b/drivers/net/bonding/bond_main.c index c66cb2d43dcf15..595fda2444b1fc 100644 --- a/drivers/net/bonding/bond_main.c +++ b/drivers/net/bonding/bond_main.c @@ -4187,8 +4187,9 @@ static bool bond_flow_dissect(struct bonding *bond, struct sk_buff *skb, const v case BOND_XMIT_POLICY_ENCAP23: case BOND_XMIT_POLICY_ENCAP34: memset(fk, 0, sizeof(*fk)); - return __skb_flow_dissect(NULL, skb, &flow_keys_bonding, - fk, data, l2_proto, nhoff, hlen, 0); + return __skb_flow_dissect(dev_net(bond->dev), skb, + &flow_keys_bonding, fk, data, + l2_proto, nhoff, hlen, 0); default: break; } From 05f8f55899289f3fe63abb2362527678be902ed7 Mon Sep 17 00:00:00 2001 From: Justin Chen Date: Tue, 20 Jan 2026 11:23:39 -0800 Subject: [PATCH 2760/3902] net: bcmasp: Fix network filter wake for asp-3.0 [ Upstream commit bbb11b8d758d17a4ce34b8ed0b49de150568265b ] We need to apply the tx_chan_offset to the netfilter cfg channel or the output channel will be incorrect for asp-3.0 and newer. Fixes: e9f31435ee7d ("net: bcmasp: Add support for asp-v3.0") Signed-off-by: Justin Chen Reviewed-by: Florian Fainelli Link: https://patch.msgid.link/20260120192339.2031648-1-justin.chen@broadcom.com Signed-off-by: Jakub Kicinski Signed-off-by: Sasha Levin --- drivers/net/ethernet/broadcom/asp2/bcmasp.c | 5 +++-- drivers/net/ethernet/broadcom/asp2/bcmasp.h | 1 + 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/drivers/net/ethernet/broadcom/asp2/bcmasp.c b/drivers/net/ethernet/broadcom/asp2/bcmasp.c index fd35f4b4dc50bb..014340f33345a0 100644 --- a/drivers/net/ethernet/broadcom/asp2/bcmasp.c +++ b/drivers/net/ethernet/broadcom/asp2/bcmasp.c @@ -156,7 +156,7 @@ static void bcmasp_netfilt_hw_en_wake(struct bcmasp_priv *priv, ASP_RX_FILTER_NET_OFFSET_L4(32), ASP_RX_FILTER_NET_OFFSET(nfilt->hw_index + 1)); - rx_filter_core_wl(priv, ASP_RX_FILTER_NET_CFG_CH(nfilt->port + 8) | + rx_filter_core_wl(priv, ASP_RX_FILTER_NET_CFG_CH(nfilt->ch) | ASP_RX_FILTER_NET_CFG_EN | ASP_RX_FILTER_NET_CFG_L2_EN | ASP_RX_FILTER_NET_CFG_L3_EN | @@ -166,7 +166,7 @@ static void bcmasp_netfilt_hw_en_wake(struct bcmasp_priv *priv, ASP_RX_FILTER_NET_CFG_UMC(nfilt->port), ASP_RX_FILTER_NET_CFG(nfilt->hw_index)); - rx_filter_core_wl(priv, ASP_RX_FILTER_NET_CFG_CH(nfilt->port + 8) | + rx_filter_core_wl(priv, ASP_RX_FILTER_NET_CFG_CH(nfilt->ch) | ASP_RX_FILTER_NET_CFG_EN | ASP_RX_FILTER_NET_CFG_L2_EN | ASP_RX_FILTER_NET_CFG_L3_EN | @@ -714,6 +714,7 @@ struct bcmasp_net_filter *bcmasp_netfilt_get_init(struct bcmasp_intf *intf, nfilter = &priv->net_filters[open_index]; nfilter->claimed = true; nfilter->port = intf->port; + nfilter->ch = intf->channel + priv->tx_chan_offset; nfilter->hw_index = open_index; } diff --git a/drivers/net/ethernet/broadcom/asp2/bcmasp.h b/drivers/net/ethernet/broadcom/asp2/bcmasp.h index 74adfdb50e11d2..e238507be40afd 100644 --- a/drivers/net/ethernet/broadcom/asp2/bcmasp.h +++ b/drivers/net/ethernet/broadcom/asp2/bcmasp.h @@ -348,6 +348,7 @@ struct bcmasp_net_filter { bool wake_filter; int port; + int ch; unsigned int hw_index; }; From cccf79f688de96f126acd9d40607b86765c40a1b Mon Sep 17 00:00:00 2001 From: Vladimir Oltean Date: Tue, 20 Jan 2026 23:10:39 +0200 Subject: [PATCH 2761/3902] net: dsa: fix off-by-one in maximum bridge ID determination [ Upstream commit dfca045cd4d0ea07ff4198ba392be3e718acaddc ] Prior to the blamed commit, the bridge_num range was from 0 to ds->max_num_bridges - 1. After the commit, it is from 1 to ds->max_num_bridges. So this check: if (bridge_num >= max) return 0; must be updated to: if (bridge_num > max) return 0; in order to allow the last bridge_num value (==max) to be used. This is easiest visible when a driver sets ds->max_num_bridges=1. The observed behaviour is that even the first created bridge triggers the netlink extack "Range of offloadable bridges exceeded" warning, and is handled in software rather than being offloaded. Fixes: 3f9bb0301d50 ("net: dsa: make dp->bridge_num one-based") Signed-off-by: Vladimir Oltean Link: https://patch.msgid.link/20260120211039.3228999-1-vladimir.oltean@nxp.com Signed-off-by: Jakub Kicinski Signed-off-by: Sasha Levin --- net/dsa/dsa.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/net/dsa/dsa.c b/net/dsa/dsa.c index ded9a291e6204e..0505e90033f234 100644 --- a/net/dsa/dsa.c +++ b/net/dsa/dsa.c @@ -157,7 +157,7 @@ unsigned int dsa_bridge_num_get(const struct net_device *bridge_dev, int max) bridge_num = find_next_zero_bit(&dsa_fwd_offloading_bridges, DSA_MAX_NUM_OFFLOADING_BRIDGES, 1); - if (bridge_num >= max) + if (bridge_num > max) return 0; set_bit(bridge_num, &dsa_fwd_offloading_bridges); From b658306ce3835624627bb3b1c04282ef8f0ff7be Mon Sep 17 00:00:00 2001 From: Daniel Golle Date: Wed, 21 Jan 2026 02:23:17 +0000 Subject: [PATCH 2762/3902] net: pcs: pcs-mtk-lynxi: report in-band capability for 2500Base-X [ Upstream commit e8ca461f7d19464b47c64fe4cf2f83162421bcc0 ] It turns out that 2500Base-X actually works fine with in-band status on MediaTek's LynxI PCS -- I wrongly concluded it didn't because it is broken in all the copper SFP modules and GPON sticks I used for testing. Hence report LINK_INBAND_ENABLE also for 2500Base-X mode. This reverts most of commit a003c38d9bbb ("net: pcs: pcs-mtk-lynxi: correctly report in-band status capabilities"). The removal of the QSGMII interface mode was correct and is left untouched. Link: https://github.com/openwrt/openwrt/issues/21436 Fixes: a003c38d9bbb ("net: pcs: pcs-mtk-lynxi: correctly report in-band status capabilities") Signed-off-by: Daniel Golle Link: https://patch.msgid.link/b1cf26157b63fee838be09ae810497fb22fd8104.1768961746.git.daniel@makrotopia.org Signed-off-by: Jakub Kicinski Signed-off-by: Sasha Levin --- drivers/net/pcs/pcs-mtk-lynxi.c | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/drivers/net/pcs/pcs-mtk-lynxi.c b/drivers/net/pcs/pcs-mtk-lynxi.c index 149ddf51d78564..87df3a9dfc9bcc 100644 --- a/drivers/net/pcs/pcs-mtk-lynxi.c +++ b/drivers/net/pcs/pcs-mtk-lynxi.c @@ -93,12 +93,10 @@ static unsigned int mtk_pcs_lynxi_inband_caps(struct phylink_pcs *pcs, { switch (interface) { case PHY_INTERFACE_MODE_1000BASEX: + case PHY_INTERFACE_MODE_2500BASEX: case PHY_INTERFACE_MODE_SGMII: return LINK_INBAND_DISABLE | LINK_INBAND_ENABLE; - case PHY_INTERFACE_MODE_2500BASEX: - return LINK_INBAND_DISABLE; - default: return 0; } From e22381c1299f74cb4f816ed78804fd70c27a2039 Mon Sep 17 00:00:00 2001 From: Ratheesh Kannoth Date: Wed, 21 Jan 2026 09:09:34 +0530 Subject: [PATCH 2763/3902] octeontx2-af: Fix error handling [ Upstream commit 19e4175e997a5b85eab97d522f00cc99abd1873c ] This commit adds error handling and rollback logic to rvu_mbox_handler_attach_resources() to properly clean up partially attached resources when rvu_attach_block() fails. Fixes: 746ea74241fa0 ("octeontx2-af: Add RVU block LF provisioning support") Signed-off-by: Ratheesh Kannoth Link: https://patch.msgid.link/20260121033934.1900761-1-rkannoth@marvell.com Signed-off-by: Jakub Kicinski Signed-off-by: Sasha Levin --- .../net/ethernet/marvell/octeontx2/af/rvu.c | 86 ++++++++++++++----- 1 file changed, 64 insertions(+), 22 deletions(-) diff --git a/drivers/net/ethernet/marvell/octeontx2/af/rvu.c b/drivers/net/ethernet/marvell/octeontx2/af/rvu.c index 2d78e08f985f0c..747fbdf2a908f1 100644 --- a/drivers/net/ethernet/marvell/octeontx2/af/rvu.c +++ b/drivers/net/ethernet/marvell/octeontx2/af/rvu.c @@ -1551,8 +1551,8 @@ static int rvu_get_attach_blkaddr(struct rvu *rvu, int blktype, return -ENODEV; } -static void rvu_attach_block(struct rvu *rvu, int pcifunc, int blktype, - int num_lfs, struct rsrc_attach *attach) +static int rvu_attach_block(struct rvu *rvu, int pcifunc, int blktype, + int num_lfs, struct rsrc_attach *attach) { struct rvu_pfvf *pfvf = rvu_get_pfvf(rvu, pcifunc); struct rvu_hwinfo *hw = rvu->hw; @@ -1562,21 +1562,21 @@ static void rvu_attach_block(struct rvu *rvu, int pcifunc, int blktype, u64 cfg; if (!num_lfs) - return; + return -EINVAL; blkaddr = rvu_get_attach_blkaddr(rvu, blktype, pcifunc, attach); if (blkaddr < 0) - return; + return -EFAULT; block = &hw->block[blkaddr]; if (!block->lf.bmap) - return; + return -ESRCH; for (slot = 0; slot < num_lfs; slot++) { /* Allocate the resource */ lf = rvu_alloc_rsrc(&block->lf); if (lf < 0) - return; + return -EFAULT; cfg = (1ULL << 63) | (pcifunc << 8) | slot; rvu_write64(rvu, blkaddr, block->lfcfg_reg | @@ -1587,6 +1587,8 @@ static void rvu_attach_block(struct rvu *rvu, int pcifunc, int blktype, /* Set start MSIX vector for this LF within this PF/VF */ rvu_set_msix_offset(rvu, pfvf, block, lf); } + + return 0; } static int rvu_check_rsrc_availability(struct rvu *rvu, @@ -1724,22 +1726,31 @@ int rvu_mbox_handler_attach_resources(struct rvu *rvu, int err; /* If first request, detach all existing attached resources */ - if (!attach->modify) - rvu_detach_rsrcs(rvu, NULL, pcifunc); + if (!attach->modify) { + err = rvu_detach_rsrcs(rvu, NULL, pcifunc); + if (err) + return err; + } mutex_lock(&rvu->rsrc_lock); /* Check if the request can be accommodated */ err = rvu_check_rsrc_availability(rvu, attach, pcifunc); if (err) - goto exit; + goto fail1; /* Now attach the requested resources */ - if (attach->npalf) - rvu_attach_block(rvu, pcifunc, BLKTYPE_NPA, 1, attach); + if (attach->npalf) { + err = rvu_attach_block(rvu, pcifunc, BLKTYPE_NPA, 1, attach); + if (err) + goto fail1; + } - if (attach->nixlf) - rvu_attach_block(rvu, pcifunc, BLKTYPE_NIX, 1, attach); + if (attach->nixlf) { + err = rvu_attach_block(rvu, pcifunc, BLKTYPE_NIX, 1, attach); + if (err) + goto fail2; + } if (attach->sso) { /* RVU func doesn't know which exact LF or slot is attached @@ -1749,33 +1760,64 @@ int rvu_mbox_handler_attach_resources(struct rvu *rvu, */ if (attach->modify) rvu_detach_block(rvu, pcifunc, BLKTYPE_SSO); - rvu_attach_block(rvu, pcifunc, BLKTYPE_SSO, - attach->sso, attach); + err = rvu_attach_block(rvu, pcifunc, BLKTYPE_SSO, + attach->sso, attach); + if (err) + goto fail3; } if (attach->ssow) { if (attach->modify) rvu_detach_block(rvu, pcifunc, BLKTYPE_SSOW); - rvu_attach_block(rvu, pcifunc, BLKTYPE_SSOW, - attach->ssow, attach); + err = rvu_attach_block(rvu, pcifunc, BLKTYPE_SSOW, + attach->ssow, attach); + if (err) + goto fail4; } if (attach->timlfs) { if (attach->modify) rvu_detach_block(rvu, pcifunc, BLKTYPE_TIM); - rvu_attach_block(rvu, pcifunc, BLKTYPE_TIM, - attach->timlfs, attach); + err = rvu_attach_block(rvu, pcifunc, BLKTYPE_TIM, + attach->timlfs, attach); + if (err) + goto fail5; } if (attach->cptlfs) { if (attach->modify && rvu_attach_from_same_block(rvu, BLKTYPE_CPT, attach)) rvu_detach_block(rvu, pcifunc, BLKTYPE_CPT); - rvu_attach_block(rvu, pcifunc, BLKTYPE_CPT, - attach->cptlfs, attach); + err = rvu_attach_block(rvu, pcifunc, BLKTYPE_CPT, + attach->cptlfs, attach); + if (err) + goto fail6; } -exit: + mutex_unlock(&rvu->rsrc_lock); + return 0; + +fail6: + if (attach->timlfs) + rvu_detach_block(rvu, pcifunc, BLKTYPE_TIM); + +fail5: + if (attach->ssow) + rvu_detach_block(rvu, pcifunc, BLKTYPE_SSOW); + +fail4: + if (attach->sso) + rvu_detach_block(rvu, pcifunc, BLKTYPE_SSO); + +fail3: + if (attach->nixlf) + rvu_detach_block(rvu, pcifunc, BLKTYPE_NIX); + +fail2: + if (attach->npalf) + rvu_detach_block(rvu, pcifunc, BLKTYPE_NPA); + +fail1: mutex_unlock(&rvu->rsrc_lock); return err; } From 095ec3482a7d33b3fa71636cd74728298bea509b Mon Sep 17 00:00:00 2001 From: David Yang Date: Wed, 21 Jan 2026 15:29:26 +0800 Subject: [PATCH 2764/3902] net: openvswitch: fix data race in ovs_vport_get_upcall_stats [ Upstream commit cc4816bdb08639e5cd9acb295a02d6f0f09736b4 ] In ovs_vport_get_upcall_stats(), some statistics protected by u64_stats_sync, are read and accumulated in ignorance of possible u64_stats_fetch_retry() events. These statistics are already accumulated by u64_stats_inc(). Fix this by reading them into temporary variables first. Fixes: 1933ea365aa7 ("net: openvswitch: Add support to count upcall packets") Signed-off-by: David Yang Acked-by: Ilya Maximets Reviewed-by: Eric Dumazet Reviewed-by: Aaron Conole Link: https://patch.msgid.link/20260121072932.2360971-1-mmyangfl@gmail.com Signed-off-by: Paolo Abeni Signed-off-by: Sasha Levin --- net/openvswitch/vport.c | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/net/openvswitch/vport.c b/net/openvswitch/vport.c index 6bbbc16ab77808..f0ce8ce1dce0e1 100644 --- a/net/openvswitch/vport.c +++ b/net/openvswitch/vport.c @@ -310,22 +310,23 @@ void ovs_vport_get_stats(struct vport *vport, struct ovs_vport_stats *stats) */ int ovs_vport_get_upcall_stats(struct vport *vport, struct sk_buff *skb) { + u64 tx_success = 0, tx_fail = 0; struct nlattr *nla; int i; - __u64 tx_success = 0; - __u64 tx_fail = 0; - for_each_possible_cpu(i) { const struct vport_upcall_stats_percpu *stats; + u64 n_success, n_fail; unsigned int start; stats = per_cpu_ptr(vport->upcall_stats, i); do { start = u64_stats_fetch_begin(&stats->syncp); - tx_success += u64_stats_read(&stats->n_success); - tx_fail += u64_stats_read(&stats->n_fail); + n_success = u64_stats_read(&stats->n_success); + n_fail = u64_stats_read(&stats->n_fail); } while (u64_stats_fetch_retry(&stats->syncp, start)); + tx_success += n_success; + tx_fail += n_fail; } nla = nla_nest_start_noflag(skb, OVS_VPORT_ATTR_UPCALL_STATS); From ec0f1b3da8061be3173d1c39faaf9504f91942c3 Mon Sep 17 00:00:00 2001 From: Melbin K Mathew Date: Wed, 21 Jan 2026 10:36:25 +0100 Subject: [PATCH 2765/3902] vsock/virtio: fix potential underflow in virtio_transport_get_credit() [ Upstream commit 3ef3d52a1a9860d094395c7a3e593f3aa26ff012 ] The credit calculation in virtio_transport_get_credit() uses unsigned arithmetic: ret = vvs->peer_buf_alloc - (vvs->tx_cnt - vvs->peer_fwd_cnt); If the peer shrinks its advertised buffer (peer_buf_alloc) while bytes are in flight, the subtraction can underflow and produce a large positive value, potentially allowing more data to be queued than the peer can handle. Reuse virtio_transport_has_space() which already handles this case and add a comment to make it clear why we are doing that. Fixes: 06a8fc78367d ("VSOCK: Introduce virtio_vsock_common.ko") Suggested-by: Stefano Garzarella Signed-off-by: Melbin K Mathew [Stefano: use virtio_transport_has_space() instead of duplicating the code] [Stefano: tweak the commit message] Signed-off-by: Stefano Garzarella Reviewed-by: Luigi Leonardi Link: https://patch.msgid.link/20260121093628.9941-2-sgarzare@redhat.com Acked-by: Michael S. Tsirkin Signed-off-by: Paolo Abeni Signed-off-by: Sasha Levin --- net/vmw_vsock/virtio_transport_common.c | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/net/vmw_vsock/virtio_transport_common.c b/net/vmw_vsock/virtio_transport_common.c index 26b979ad71f09e..6175124d63d34f 100644 --- a/net/vmw_vsock/virtio_transport_common.c +++ b/net/vmw_vsock/virtio_transport_common.c @@ -28,6 +28,7 @@ static void virtio_transport_cancel_close_work(struct vsock_sock *vsk, bool cancel_timeout); +static s64 virtio_transport_has_space(struct virtio_vsock_sock *vvs); static const struct virtio_transport * virtio_transport_get_ops(struct vsock_sock *vsk) @@ -499,9 +500,7 @@ u32 virtio_transport_get_credit(struct virtio_vsock_sock *vvs, u32 credit) return 0; spin_lock_bh(&vvs->tx_lock); - ret = vvs->peer_buf_alloc - (vvs->tx_cnt - vvs->peer_fwd_cnt); - if (ret > credit) - ret = credit; + ret = min_t(u32, credit, virtio_transport_has_space(vvs)); vvs->tx_cnt += ret; vvs->bytes_unsent += ret; spin_unlock_bh(&vvs->tx_lock); @@ -877,11 +876,14 @@ u32 virtio_transport_seqpacket_has_data(struct vsock_sock *vsk) } EXPORT_SYMBOL_GPL(virtio_transport_seqpacket_has_data); -static s64 virtio_transport_has_space(struct vsock_sock *vsk) +static s64 virtio_transport_has_space(struct virtio_vsock_sock *vvs) { - struct virtio_vsock_sock *vvs = vsk->trans; s64 bytes; + /* Use s64 arithmetic so if the peer shrinks peer_buf_alloc while + * we have bytes in flight (tx_cnt - peer_fwd_cnt), the subtraction + * does not underflow. + */ bytes = (s64)vvs->peer_buf_alloc - (vvs->tx_cnt - vvs->peer_fwd_cnt); if (bytes < 0) bytes = 0; @@ -895,7 +897,7 @@ s64 virtio_transport_stream_has_space(struct vsock_sock *vsk) s64 bytes; spin_lock_bh(&vvs->tx_lock); - bytes = virtio_transport_has_space(vsk); + bytes = virtio_transport_has_space(vvs); spin_unlock_bh(&vvs->tx_lock); return bytes; @@ -1492,7 +1494,7 @@ static bool virtio_transport_space_update(struct sock *sk, spin_lock_bh(&vvs->tx_lock); vvs->peer_buf_alloc = le32_to_cpu(hdr->buf_alloc); vvs->peer_fwd_cnt = le32_to_cpu(hdr->fwd_cnt); - space_available = virtio_transport_has_space(vsk); + space_available = virtio_transport_has_space(vvs); spin_unlock_bh(&vvs->tx_lock); return space_available; } From 0aa25bbfd5ea5f986e4d15d5c9e26732d3b50b89 Mon Sep 17 00:00:00 2001 From: Stefano Garzarella Date: Wed, 21 Jan 2026 10:36:26 +0100 Subject: [PATCH 2766/3902] vsock/test: fix seqpacket message bounds test [ Upstream commit 0a98de80136968bab7db37b16282b37f044694d3 ] The test requires the sender (client) to send all messages before waking up the receiver (server). Since virtio-vsock had a bug and did not respect the size of the TX buffer, this test worked, but now that we are going to fix the bug, the test hangs because the sender would fill the TX buffer before waking up the receiver. Set the buffer size in the sender (client) as well, as we already do for the receiver (server). Fixes: 5c338112e48a ("test/vsock: rework message bounds test") Signed-off-by: Stefano Garzarella Link: https://patch.msgid.link/20260121093628.9941-3-sgarzare@redhat.com Acked-by: Michael S. Tsirkin Signed-off-by: Paolo Abeni Signed-off-by: Sasha Levin --- tools/testing/vsock/vsock_test.c | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/tools/testing/vsock/vsock_test.c b/tools/testing/vsock/vsock_test.c index d4517386e551e3..667f6f0ad6afae 100644 --- a/tools/testing/vsock/vsock_test.c +++ b/tools/testing/vsock/vsock_test.c @@ -351,6 +351,7 @@ static void test_stream_msg_peek_server(const struct test_opts *opts) static void test_seqpacket_msg_bounds_client(const struct test_opts *opts) { + unsigned long long sock_buf_size; unsigned long curr_hash; size_t max_msg_size; int page_size; @@ -363,6 +364,16 @@ static void test_seqpacket_msg_bounds_client(const struct test_opts *opts) exit(EXIT_FAILURE); } + sock_buf_size = SOCK_BUF_SIZE; + + setsockopt_ull_check(fd, AF_VSOCK, SO_VM_SOCKETS_BUFFER_MAX_SIZE, + sock_buf_size, + "setsockopt(SO_VM_SOCKETS_BUFFER_MAX_SIZE)"); + + setsockopt_ull_check(fd, AF_VSOCK, SO_VM_SOCKETS_BUFFER_SIZE, + sock_buf_size, + "setsockopt(SO_VM_SOCKETS_BUFFER_SIZE)"); + /* Wait, until receiver sets buffer size. */ control_expectln("SRVREADY"); From 84ef86aa7120449828d1e0ce438c499014839711 Mon Sep 17 00:00:00 2001 From: Melbin K Mathew Date: Wed, 21 Jan 2026 10:36:27 +0100 Subject: [PATCH 2767/3902] vsock/virtio: cap TX credit to local buffer size [ Upstream commit 8ee784fdf006cbe8739cfa093f54d326cbf54037 ] The virtio transports derives its TX credit directly from peer_buf_alloc, which is set from the remote endpoint's SO_VM_SOCKETS_BUFFER_SIZE value. On the host side this means that the amount of data we are willing to queue for a connection is scaled by a guest-chosen buffer size, rather than the host's own vsock configuration. A malicious guest can advertise a large buffer and read slowly, causing the host to allocate a correspondingly large amount of sk_buff memory. The same thing would happen in the guest with a malicious host, since virtio transports share the same code base. Introduce a small helper, virtio_transport_tx_buf_size(), that returns min(peer_buf_alloc, buf_alloc), and use it wherever we consume peer_buf_alloc. This ensures the effective TX window is bounded by both the peer's advertised buffer and our own buf_alloc (already clamped to buffer_max_size via SO_VM_SOCKETS_BUFFER_MAX_SIZE), so a remote peer cannot force the other to queue more data than allowed by its own vsock settings. On an unpatched Ubuntu 22.04 host (~64 GiB RAM), running a PoC with 32 guest vsock connections advertising 2 GiB each and reading slowly drove Slab/SUnreclaim from ~0.5 GiB to ~57 GiB; the system only recovered after killing the QEMU process. That said, if QEMU memory is limited with cgroups, the maximum memory used will be limited. With this patch applied: Before: MemFree: ~61.6 GiB Slab: ~142 MiB SUnreclaim: ~117 MiB After 32 high-credit connections: MemFree: ~61.5 GiB Slab: ~178 MiB SUnreclaim: ~152 MiB Only ~35 MiB increase in Slab/SUnreclaim, no host OOM, and the guest remains responsive. Compatibility with non-virtio transports: - VMCI uses the AF_VSOCK buffer knobs to size its queue pairs per socket based on the local vsk->buffer_* values; the remote side cannot enlarge those queues beyond what the local endpoint configured. - Hyper-V's vsock transport uses fixed-size VMBus ring buffers and an MTU bound; there is no peer-controlled credit field comparable to peer_buf_alloc, and the remote endpoint cannot drive in-flight kernel memory above those ring sizes. - The loopback path reuses virtio_transport_common.c, so it naturally follows the same semantics as the virtio transport. This change is limited to virtio_transport_common.c and thus affects virtio-vsock, vhost-vsock, and loopback, bringing them in line with the "remote window intersected with local policy" behaviour that VMCI and Hyper-V already effectively have. Fixes: 06a8fc78367d ("VSOCK: Introduce virtio_vsock_common.ko") Suggested-by: Stefano Garzarella Signed-off-by: Melbin K Mathew [Stefano: small adjustments after changing the previous patch] [Stefano: tweak the commit message] Signed-off-by: Stefano Garzarella Reviewed-by: Luigi Leonardi Link: https://patch.msgid.link/20260121093628.9941-4-sgarzare@redhat.com Acked-by: Michael S. Tsirkin Signed-off-by: Paolo Abeni Signed-off-by: Sasha Levin --- net/vmw_vsock/virtio_transport_common.c | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/net/vmw_vsock/virtio_transport_common.c b/net/vmw_vsock/virtio_transport_common.c index 6175124d63d34f..d3e26025ef589a 100644 --- a/net/vmw_vsock/virtio_transport_common.c +++ b/net/vmw_vsock/virtio_transport_common.c @@ -821,6 +821,15 @@ virtio_transport_seqpacket_dequeue(struct vsock_sock *vsk, } EXPORT_SYMBOL_GPL(virtio_transport_seqpacket_dequeue); +static u32 virtio_transport_tx_buf_size(struct virtio_vsock_sock *vvs) +{ + /* The peer advertises its receive buffer via peer_buf_alloc, but we + * cap it to our local buf_alloc so a remote peer cannot force us to + * queue more data than our own buffer configuration allows. + */ + return min(vvs->peer_buf_alloc, vvs->buf_alloc); +} + int virtio_transport_seqpacket_enqueue(struct vsock_sock *vsk, struct msghdr *msg, @@ -830,7 +839,7 @@ virtio_transport_seqpacket_enqueue(struct vsock_sock *vsk, spin_lock_bh(&vvs->tx_lock); - if (len > vvs->peer_buf_alloc) { + if (len > virtio_transport_tx_buf_size(vvs)) { spin_unlock_bh(&vvs->tx_lock); return -EMSGSIZE; } @@ -884,7 +893,8 @@ static s64 virtio_transport_has_space(struct virtio_vsock_sock *vvs) * we have bytes in flight (tx_cnt - peer_fwd_cnt), the subtraction * does not underflow. */ - bytes = (s64)vvs->peer_buf_alloc - (vvs->tx_cnt - vvs->peer_fwd_cnt); + bytes = (s64)virtio_transport_tx_buf_size(vvs) - + (vvs->tx_cnt - vvs->peer_fwd_cnt); if (bytes < 0) bytes = 0; From 3b796b1ecfd3b9a068eb94c4b83f95d45f0add3c Mon Sep 17 00:00:00 2001 From: Fan Gong Date: Thu, 22 Jan 2026 17:41:55 +0800 Subject: [PATCH 2768/3902] hinic3: Fix netif_queue_set_napi queue_index input parameter error [ Upstream commit fb2bb2a1ebf7b9514c32b03bb5c3be5d518d437b ] Incorrectly transmitted interrupt number instead of queue number when using netif_queue_set_napi. Besides, move this to appropriate code location to set napi. Remove redundant netif_stop_subqueue beacuase it is not part of the hinic3_send_one_skb process. Fixes: 17fcb3dc12bb ("hinic3: module initialization and tx/rx logic") Co-developed-by: Zhu Yikai Signed-off-by: Zhu Yikai Signed-off-by: Fan Gong Link: https://patch.msgid.link/7b8e4eb5c53cbd873ee9aaefeb3d9dbbaff52deb.1769070766.git.zhuyikai1@h-partners.com Signed-off-by: Jakub Kicinski Signed-off-by: Sasha Levin --- .../net/ethernet/huawei/hinic3/hinic3_irq.c | 22 +++++++++++-------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/drivers/net/ethernet/huawei/hinic3/hinic3_irq.c b/drivers/net/ethernet/huawei/hinic3/hinic3_irq.c index a69b361225e90a..84bee5d6e638e1 100644 --- a/drivers/net/ethernet/huawei/hinic3/hinic3_irq.c +++ b/drivers/net/ethernet/huawei/hinic3/hinic3_irq.c @@ -43,21 +43,12 @@ static void qp_add_napi(struct hinic3_irq_cfg *irq_cfg) struct hinic3_nic_dev *nic_dev = netdev_priv(irq_cfg->netdev); netif_napi_add(nic_dev->netdev, &irq_cfg->napi, hinic3_poll); - netif_queue_set_napi(irq_cfg->netdev, irq_cfg->irq_id, - NETDEV_QUEUE_TYPE_RX, &irq_cfg->napi); - netif_queue_set_napi(irq_cfg->netdev, irq_cfg->irq_id, - NETDEV_QUEUE_TYPE_TX, &irq_cfg->napi); napi_enable(&irq_cfg->napi); } static void qp_del_napi(struct hinic3_irq_cfg *irq_cfg) { napi_disable(&irq_cfg->napi); - netif_queue_set_napi(irq_cfg->netdev, irq_cfg->irq_id, - NETDEV_QUEUE_TYPE_RX, NULL); - netif_queue_set_napi(irq_cfg->netdev, irq_cfg->irq_id, - NETDEV_QUEUE_TYPE_TX, NULL); - netif_stop_subqueue(irq_cfg->netdev, irq_cfg->irq_id); netif_napi_del(&irq_cfg->napi); } @@ -150,6 +141,11 @@ int hinic3_qps_irq_init(struct net_device *netdev) goto err_release_irqs; } + netif_queue_set_napi(irq_cfg->netdev, q_id, + NETDEV_QUEUE_TYPE_RX, &irq_cfg->napi); + netif_queue_set_napi(irq_cfg->netdev, q_id, + NETDEV_QUEUE_TYPE_TX, &irq_cfg->napi); + hinic3_set_msix_auto_mask_state(nic_dev->hwdev, irq_cfg->msix_entry_idx, HINIC3_SET_MSIX_AUTO_MASK); @@ -164,6 +160,10 @@ int hinic3_qps_irq_init(struct net_device *netdev) q_id--; irq_cfg = &nic_dev->q_params.irq_cfg[q_id]; qp_del_napi(irq_cfg); + netif_queue_set_napi(irq_cfg->netdev, q_id, + NETDEV_QUEUE_TYPE_RX, NULL); + netif_queue_set_napi(irq_cfg->netdev, q_id, + NETDEV_QUEUE_TYPE_TX, NULL); hinic3_set_msix_state(nic_dev->hwdev, irq_cfg->msix_entry_idx, HINIC3_MSIX_DISABLE); hinic3_set_msix_auto_mask_state(nic_dev->hwdev, @@ -184,6 +184,10 @@ void hinic3_qps_irq_uninit(struct net_device *netdev) for (q_id = 0; q_id < nic_dev->q_params.num_qps; q_id++) { irq_cfg = &nic_dev->q_params.irq_cfg[q_id]; qp_del_napi(irq_cfg); + netif_queue_set_napi(irq_cfg->netdev, q_id, + NETDEV_QUEUE_TYPE_RX, NULL); + netif_queue_set_napi(irq_cfg->netdev, q_id, + NETDEV_QUEUE_TYPE_TX, NULL); hinic3_set_msix_state(nic_dev->hwdev, irq_cfg->msix_entry_idx, HINIC3_MSIX_DISABLE); hinic3_set_msix_auto_mask_state(nic_dev->hwdev, From 6c75fed55080014545f262b7055081cec4768b20 Mon Sep 17 00:00:00 2001 From: Eric Dumazet Date: Wed, 21 Jan 2026 13:37:24 +0000 Subject: [PATCH 2769/3902] net/sched: act_ife: avoid possible NULL deref [ Upstream commit 27880b0b0d35ad1c98863d09788254e36f874968 ] tcf_ife_encode() must make sure ife_encode() does not return NULL. syzbot reported: Oops: general protection fault, probably for non-canonical address 0xdffffc0000000000: 0000 [#1] SMP KASAN NOPTI KASAN: null-ptr-deref in range [0x0000000000000000-0x0000000000000007] RIP: 0010:ife_tlv_meta_encode+0x41/0xa0 net/ife/ife.c:166 CPU: 3 UID: 0 PID: 8990 Comm: syz.0.696 Not tainted syzkaller #0 PREEMPT(full) Call Trace: ife_encode_meta_u32+0x153/0x180 net/sched/act_ife.c:101 tcf_ife_encode net/sched/act_ife.c:841 [inline] tcf_ife_act+0x1022/0x1de0 net/sched/act_ife.c:877 tc_act include/net/tc_wrapper.h:130 [inline] tcf_action_exec+0x1c0/0xa20 net/sched/act_api.c:1152 tcf_exts_exec include/net/pkt_cls.h:349 [inline] mall_classify+0x1a0/0x2a0 net/sched/cls_matchall.c:42 tc_classify include/net/tc_wrapper.h:197 [inline] __tcf_classify net/sched/cls_api.c:1764 [inline] tcf_classify+0x7f2/0x1380 net/sched/cls_api.c:1860 multiq_classify net/sched/sch_multiq.c:39 [inline] multiq_enqueue+0xe0/0x510 net/sched/sch_multiq.c:66 dev_qdisc_enqueue+0x45/0x250 net/core/dev.c:4147 __dev_xmit_skb net/core/dev.c:4262 [inline] __dev_queue_xmit+0x2998/0x46c0 net/core/dev.c:4798 Fixes: 295a6e06d21e ("net/sched: act_ife: Change to use ife module") Reported-by: syzbot+5cf914f193dffde3bd3c@syzkaller.appspotmail.com Closes: https://lore.kernel.org/netdev/6970d61d.050a0220.706b.0010.GAE@google.com/T/#u Signed-off-by: Eric Dumazet Cc: Yotam Gigi Reviewed-by: Jamal Hadi Salim Link: https://patch.msgid.link/20260121133724.3400020-1-edumazet@google.com Signed-off-by: Jakub Kicinski Signed-off-by: Sasha Levin --- net/sched/act_ife.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/net/sched/act_ife.c b/net/sched/act_ife.c index 7c6975632fc2ec..c7ab25642d997d 100644 --- a/net/sched/act_ife.c +++ b/net/sched/act_ife.c @@ -821,6 +821,7 @@ static int tcf_ife_encode(struct sk_buff *skb, const struct tc_action *a, /* could be stupid policy setup or mtu config * so lets be conservative.. */ if ((action == TC_ACT_SHOT) || exceed_mtu) { +drop: qstats_drop_inc(this_cpu_ptr(ife->common.cpu_qstats)); return TC_ACT_SHOT; } @@ -829,6 +830,8 @@ static int tcf_ife_encode(struct sk_buff *skb, const struct tc_action *a, skb_push(skb, skb->dev->hard_header_len); ife_meta = ife_encode(skb, metalen); + if (!ife_meta) + goto drop; spin_lock(&ife->tcf_lock); @@ -844,8 +847,7 @@ static int tcf_ife_encode(struct sk_buff *skb, const struct tc_action *a, if (err < 0) { /* too corrupt to keep around if overwritten */ spin_unlock(&ife->tcf_lock); - qstats_drop_inc(this_cpu_ptr(ife->common.cpu_qstats)); - return TC_ACT_SHOT; + goto drop; } skboff += err; } From 236a657422a564859dcd0db7bdb486abb21a721a Mon Sep 17 00:00:00 2001 From: Ivan Vecera Date: Wed, 21 Jan 2026 14:00:11 +0100 Subject: [PATCH 2770/3902] dpll: Prevent duplicate registrations [ Upstream commit f3ddbaaaaf4d0633b40482f471753f9c71294a4a ] Modify the internal registration helpers dpll_xa_ref_{dpll,pin}_add() to reject duplicate registration attempts. Previously, if a caller attempted to register the same pin multiple times (with the same ops, priv, and cookie) on the same device, the core silently increments the reference count and return success. This behavior is incorrect because if the caller makes these duplicate registrations then for the first one dpll_pin_registration is allocated and for others the associated dpll_pin_ref.refcount is incremented. During the first unregistration the associated dpll_pin_registration is freed and for others WARN is fired. Fix this by updating the logic to return `-EEXIST` if a matching registration is found to enforce a strict "register once" policy. Fixes: 9431063ad323 ("dpll: core: Add DPLL framework base functions") Signed-off-by: Ivan Vecera Reviewed-by: Arkadiusz Kubalewski Reviewed-by: Vadim Fedorenko Link: https://patch.msgid.link/20260121130012.112606-1-ivecera@redhat.com Signed-off-by: Jakub Kicinski Signed-off-by: Sasha Levin --- drivers/dpll/dpll_core.c | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/drivers/dpll/dpll_core.c b/drivers/dpll/dpll_core.c index a461095efd8ac7..8879a72351561b 100644 --- a/drivers/dpll/dpll_core.c +++ b/drivers/dpll/dpll_core.c @@ -83,10 +83,8 @@ dpll_xa_ref_pin_add(struct xarray *xa_pins, struct dpll_pin *pin, if (ref->pin != pin) continue; reg = dpll_pin_registration_find(ref, ops, priv, cookie); - if (reg) { - refcount_inc(&ref->refcount); - return 0; - } + if (reg) + return -EEXIST; ref_exists = true; break; } @@ -164,10 +162,8 @@ dpll_xa_ref_dpll_add(struct xarray *xa_dplls, struct dpll_device *dpll, if (ref->dpll != dpll) continue; reg = dpll_pin_registration_find(ref, ops, priv, cookie); - if (reg) { - refcount_inc(&ref->refcount); - return 0; - } + if (reg) + return -EEXIST; ref_exists = true; break; } From e343973fab43c266a40e4e0dabdc4216db6d5eff Mon Sep 17 00:00:00 2001 From: Hariprasad Kelam Date: Wed, 21 Jan 2026 15:18:19 +0530 Subject: [PATCH 2771/3902] Octeontx2-af: Add proper checks for fwdata [ Upstream commit 4a3dba48188208e4f66822800e042686784d29d1 ] firmware populates MAC address, link modes (supported, advertised) and EEPROM data in shared firmware structure which kernel access via MAC block(CGX/RPM). Accessing fwdata, on boards booted with out MAC block leading to kernel panics. Internal error: Oops: 0000000096000005 [#1] SMP [ 10.460721] Modules linked in: [ 10.463779] CPU: 0 UID: 0 PID: 174 Comm: kworker/0:3 Not tainted 6.19.0-rc5-00154-g76ec646abdf7-dirty #3 PREEMPT [ 10.474045] Hardware name: Marvell OcteonTX CN98XX board (DT) [ 10.479793] Workqueue: events work_for_cpu_fn [ 10.484159] pstate: 80400009 (Nzcv daif +PAN -UAO -TCO -DIT -SSBS BTYPE=--) [ 10.491124] pc : rvu_sdp_init+0x18/0x114 [ 10.495051] lr : rvu_probe+0xe58/0x1d18 Fixes: 997814491cee ("Octeontx2-af: Fetch MAC channel info from firmware") Fixes: 5f21226b79fd ("Octeontx2-pf: ethtool: support multi advertise mode") Signed-off-by: Hariprasad Kelam Link: https://patch.msgid.link/20260121094819.2566786-1-hkelam@marvell.com Signed-off-by: Jakub Kicinski Signed-off-by: Sasha Levin --- drivers/net/ethernet/marvell/octeontx2/af/rvu_cgx.c | 3 +++ drivers/net/ethernet/marvell/octeontx2/af/rvu_sdp.c | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/drivers/net/ethernet/marvell/octeontx2/af/rvu_cgx.c b/drivers/net/ethernet/marvell/octeontx2/af/rvu_cgx.c index 3abd750a4bd741..3d91a34f8b57be 100644 --- a/drivers/net/ethernet/marvell/octeontx2/af/rvu_cgx.c +++ b/drivers/net/ethernet/marvell/octeontx2/af/rvu_cgx.c @@ -1222,6 +1222,9 @@ int rvu_mbox_handler_cgx_set_link_mode(struct rvu *rvu, u8 cgx_idx, lmac; void *cgxd; + if (!rvu->fwdata) + return LMAC_AF_ERR_FIRMWARE_DATA_NOT_MAPPED; + if (!is_cgx_config_permitted(rvu, req->hdr.pcifunc)) return -EPERM; diff --git a/drivers/net/ethernet/marvell/octeontx2/af/rvu_sdp.c b/drivers/net/ethernet/marvell/octeontx2/af/rvu_sdp.c index e4a5f9fa6fd46d..bbfd8231aed5ce 100644 --- a/drivers/net/ethernet/marvell/octeontx2/af/rvu_sdp.c +++ b/drivers/net/ethernet/marvell/octeontx2/af/rvu_sdp.c @@ -56,7 +56,7 @@ int rvu_sdp_init(struct rvu *rvu) struct rvu_pfvf *pfvf; u32 i = 0; - if (rvu->fwdata->channel_data.valid) { + if (rvu->fwdata && rvu->fwdata->channel_data.valid) { sdp_pf_num[0] = 0; pfvf = &rvu->pf[sdp_pf_num[0]]; pfvf->sdp_info = &rvu->fwdata->channel_data.info; From 4040b84ec04a62cbd01abf226b47d7b931b935b4 Mon Sep 17 00:00:00 2001 From: Cedric Xing Date: Thu, 22 Jan 2026 18:39:15 -0600 Subject: [PATCH 2772/3902] x86: make page fault handling disable interrupts properly [ Upstream commit 614da1d3d4cdbd6e41aea06bc97ec15aacff6daf ] There's a big comment in the x86 do_page_fault() about our interrupt disabling code: * User address page fault handling might have reenabled * interrupts. Fixing up all potential exit points of * do_user_addr_fault() and its leaf functions is just not * doable w/o creating an unholy mess or turning the code * upside down. but it turns out that comment is subtly wrong, and the code as a result is also wrong. Because it's certainly true that we may have re-enabled interrupts when handling user page faults. And it's most certainly true that we don't want to bother fixing up all the cases. But what isn't true is that it's limited to user address page faults. The confusion stems from the fact that we have logic here that depends on the address range of the access, but other code then depends on the _context_ the access was done in. The two are not related, even though both of them are about user-vs-kernel. In other words, both user and kernel addresses can cause interrupts to have been enabled (eg when __bad_area_nosemaphore() gets called for user accesses to kernel addresses). As a result we should make sure to disable interrupts again regardless of the address range before returning to the low-level fault handling code. The __bad_area_nosemaphore() code actually did disable interrupts again after enabling them, just not consistently. Ironically, as noted in the original comment, fixing up all the cases is just not worth it, when the simple solution is to just do it unconditionally in one single place. So remove the incomplete case that unsuccessfully tried to do what the comment said was "not doable" in commit ca4c6a9858c2 ("x86/traps: Make interrupt enable/disable symmetric in C code"), and just make it do the simple and straightforward thing. Signed-off-by: Cedric Xing Reviewed-by: Dave Hansen Fixes: ca4c6a9858c2 ("x86/traps: Make interrupt enable/disable symmetric in C code") Cc: Peter Zijlstra Cc: Thomas Gleixner Signed-off-by: Linus Torvalds Signed-off-by: Sasha Levin --- arch/x86/mm/fault.c | 15 +++++---------- 1 file changed, 5 insertions(+), 10 deletions(-) diff --git a/arch/x86/mm/fault.c b/arch/x86/mm/fault.c index 998bd807fc7bad..b83a06739b5118 100644 --- a/arch/x86/mm/fault.c +++ b/arch/x86/mm/fault.c @@ -821,8 +821,6 @@ __bad_area_nosemaphore(struct pt_regs *regs, unsigned long error_code, force_sig_pkuerr((void __user *)address, pkey); else force_sig_fault(SIGSEGV, si_code, (void __user *)address); - - local_irq_disable(); } static noinline void @@ -1474,15 +1472,12 @@ handle_page_fault(struct pt_regs *regs, unsigned long error_code, do_kern_addr_fault(regs, error_code, address); } else { do_user_addr_fault(regs, error_code, address); - /* - * User address page fault handling might have reenabled - * interrupts. Fixing up all potential exit points of - * do_user_addr_fault() and its leaf functions is just not - * doable w/o creating an unholy mess or turning the code - * upside down. - */ - local_irq_disable(); } + /* + * page fault handling might have reenabled interrupts, + * make sure to disable them again. + */ + local_irq_disable(); } DEFINE_IDTENTRY_RAW_ERRORCODE(exc_page_fault) From c9aeb168e88d271bd6708c310de8ec5932fcd156 Mon Sep 17 00:00:00 2001 From: Srish Srinivasan Date: Fri, 23 Jan 2026 22:25:03 +0530 Subject: [PATCH 2773/3902] keys/trusted_keys: fix handle passed to tpm_buf_append_name during unseal [ Upstream commit 6342969dafbc63597cfc221aa13c3b123c2800c5 ] TPM2_Unseal[1] expects the handle of a loaded data object, and not the handle of the parent key. But the tpm2_unseal_cmd provides the parent keyhandle instead of blob_handle for the session HMAC calculation. This causes unseal to fail. Fix this by passing blob_handle to tpm_buf_append_name(). References: [1] trustedcomputinggroup.org/wp-content/uploads/ Trusted-Platform-Module-2.0-Library-Part-3-Version-184_pub.pdf Fixes: 6e9722e9a7bf ("tpm2-sessions: Fix out of range indexing in name_size") Signed-off-by: Srish Srinivasan Reviewed-by: Stefan Berger Reviewed-by: Jarkko Sakkinen Signed-off-by: Jarkko Sakkinen Signed-off-by: Sasha Levin --- security/keys/trusted-keys/trusted_tpm2.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/security/keys/trusted-keys/trusted_tpm2.c b/security/keys/trusted-keys/trusted_tpm2.c index 7187768716b78e..74cea80ed9be5e 100644 --- a/security/keys/trusted-keys/trusted_tpm2.c +++ b/security/keys/trusted-keys/trusted_tpm2.c @@ -489,7 +489,7 @@ static int tpm2_load_cmd(struct tpm_chip *chip, } /** - * tpm2_unseal_cmd() - execute a TPM2_Unload command + * tpm2_unseal_cmd() - execute a TPM2_Unseal command * * @chip: TPM chip to use * @payload: the key data in clear and encrypted form @@ -520,7 +520,7 @@ static int tpm2_unseal_cmd(struct tpm_chip *chip, return rc; } - rc = tpm_buf_append_name(chip, &buf, options->keyhandle, NULL); + rc = tpm_buf_append_name(chip, &buf, blob_handle, NULL); if (rc) goto out; From 78822628165f3d817382f67f91129161159ca234 Mon Sep 17 00:00:00 2001 From: Hans de Goede Date: Thu, 11 Dec 2025 17:37:27 +0100 Subject: [PATCH 2774/3902] leds: led-class: Only Add LED to leds_list when it is fully ready commit d1883cefd31752f0504b94c3bcfa1f6d511d6e87 upstream. Before this change the LED was added to leds_list before led_init_core() gets called adding it the list before led_classdev.set_brightness_work gets initialized. This leaves a window where led_trigger_register() of a LED's default trigger will call led_trigger_set() which calls led_set_brightness() which in turn will end up queueing the *uninitialized* led_classdev.set_brightness_work. This race gets hit by the lenovo-thinkpad-t14s EC driver which registers 2 LEDs with a default trigger provided by snd_ctl_led.ko in quick succession. The first led_classdev_register() causes an async modprobe of snd_ctl_led to run and that async modprobe manages to exactly hit the window where the second LED is on the leds_list without led_init_core() being called for it, resulting in: ------------[ cut here ]------------ WARNING: CPU: 11 PID: 5608 at kernel/workqueue.c:4234 __flush_work+0x344/0x390 Hardware name: LENOVO 21N2S01F0B/21N2S01F0B, BIOS N42ET93W (2.23 ) 09/01/2025 ... Call trace: __flush_work+0x344/0x390 (P) flush_work+0x2c/0x50 led_trigger_set+0x1c8/0x340 led_trigger_register+0x17c/0x1c0 led_trigger_register_simple+0x84/0xe8 snd_ctl_led_init+0x40/0xf88 [snd_ctl_led] do_one_initcall+0x5c/0x318 do_init_module+0x9c/0x2b8 load_module+0x7e0/0x998 Close the race window by moving the adding of the LED to leds_list to after the led_init_core() call. Cc: stable@vger.kernel.org Fixes: d23a22a74fde ("leds: delay led_set_brightness if stopping soft-blink") Signed-off-by: Hans de Goede Reviewed-by: Sebastian Reichel Link: https://patch.msgid.link/20251211163727.366441-1-johannes.goede@oss.qualcomm.com Signed-off-by: Lee Jones Signed-off-by: Greg Kroah-Hartman --- drivers/leds/led-class.c | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/drivers/leds/led-class.c b/drivers/leds/led-class.c index f3faf37f9a08ac..6b9fa060c3a117 100644 --- a/drivers/leds/led-class.c +++ b/drivers/leds/led-class.c @@ -560,11 +560,6 @@ int led_classdev_register_ext(struct device *parent, #ifdef CONFIG_LEDS_BRIGHTNESS_HW_CHANGED led_cdev->brightness_hw_changed = -1; #endif - /* add to the list of leds */ - down_write(&leds_list_lock); - list_add_tail(&led_cdev->node, &leds_list); - up_write(&leds_list_lock); - if (!led_cdev->max_brightness) led_cdev->max_brightness = LED_FULL; @@ -574,6 +569,11 @@ int led_classdev_register_ext(struct device *parent, led_init_core(led_cdev); + /* add to the list of leds */ + down_write(&leds_list_lock); + list_add_tail(&led_cdev->node, &leds_list); + up_write(&leds_list_lock); + #ifdef CONFIG_LEDS_TRIGGERS led_trigger_set_default(led_cdev); #endif From 67270881c8cf200b19d3f53528f37bbaf72c8004 Mon Sep 17 00:00:00 2001 From: Gal Pressman Date: Tue, 6 Jan 2026 18:33:21 +0200 Subject: [PATCH 2775/3902] panic: only warn about deprecated panic_print on write access commit 90f3c123247e9564f2ecf861946ec41ceaf5e198 upstream. The panic_print_deprecated() warning is being triggered on both read and write operations to the panic_print parameter. This causes spurious warnings when users run 'sysctl -a' to list all sysctl values, since that command reads /proc/sys/kernel/panic_print and triggers the deprecation notice. Modify the handlers to only emit the deprecation warning when the parameter is actually being set: - sysctl_panic_print_handler(): check 'write' flag before warning. - panic_print_get(): remove the deprecation call entirely. This way, users are only warned when they actively try to use the deprecated parameter, not when passively querying system state. Link: https://lkml.kernel.org/r/20260106163321.83586-1-gal@nvidia.com Fixes: ee13240cd78b ("panic: add note that panic_print sysctl interface is deprecated") Fixes: 2683df6539cb ("panic: add note that 'panic_print' parameter is deprecated") Signed-off-by: Gal Pressman Reviewed-by: Mark Bloch Reviewed-by: Nimrod Oren Cc: Feng Tang Cc: Joel Granados Cc: Petr Mladek Cc: Signed-off-by: Andrew Morton Signed-off-by: Greg Kroah-Hartman --- kernel/panic.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/kernel/panic.c b/kernel/panic.c index 24cc3eec1805fe..b13f040680ac1c 100644 --- a/kernel/panic.c +++ b/kernel/panic.c @@ -131,7 +131,8 @@ static int proc_taint(const struct ctl_table *table, int write, static int sysctl_panic_print_handler(const struct ctl_table *table, int write, void *buffer, size_t *lenp, loff_t *ppos) { - panic_print_deprecated(); + if (write) + panic_print_deprecated(); return proc_doulongvec_minmax(table, write, buffer, lenp, ppos); } @@ -1010,7 +1011,6 @@ static int panic_print_set(const char *val, const struct kernel_param *kp) static int panic_print_get(char *val, const struct kernel_param *kp) { - panic_print_deprecated(); return param_get_ulong(val, kp); } From 7685286dd7a5e91253bbfd6c2f44718eabefadb3 Mon Sep 17 00:00:00 2001 From: Weigang He Date: Sat, 17 Jan 2026 09:12:38 +0000 Subject: [PATCH 2776/3902] of: fix reference count leak in of_alias_scan() commit 81122fba08fa3ccafab6ed272a5c6f2203923a7e upstream. of_find_node_by_path() returns a device_node with its refcount incremented. When kstrtoint() fails or dt_alloc() fails, the function continues to the next iteration without calling of_node_put(), causing a reference count leak. Add of_node_put(np) before continue on both error paths to properly release the device_node reference. Fixes: 611cad720148 ("dt: add of_alias_scan and of_alias_get_id") Cc: stable@vger.kernel.org Signed-off-by: Weigang He Link: https://patch.msgid.link/20260117091238.481243-1-geoffreyhe2@gmail.com Signed-off-by: Rob Herring (Arm) Signed-off-by: Greg Kroah-Hartman --- drivers/of/base.c | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/drivers/of/base.c b/drivers/of/base.c index 7043acd971a0fc..2fd27ea0310c5f 100644 --- a/drivers/of/base.c +++ b/drivers/of/base.c @@ -1895,13 +1895,17 @@ void of_alias_scan(void * (*dt_alloc)(u64 size, u64 align)) end--; len = end - start; - if (kstrtoint(end, 10, &id) < 0) + if (kstrtoint(end, 10, &id) < 0) { + of_node_put(np); continue; + } /* Allocate an alias_prop with enough space for the stem */ ap = dt_alloc(sizeof(*ap) + len + 1, __alignof__(*ap)); - if (!ap) + if (!ap) { + of_node_put(np); continue; + } memset(ap, 0, sizeof(*ap) + len + 1); ap->alias = start; of_alias_add(ap, np, id, start, len); From 1093e0a96d168febd9442d2f5ac7f4162fcb27f4 Mon Sep 17 00:00:00 2001 From: "Rob Herring (Arm)" Date: Tue, 13 Jan 2026 19:51:58 -0600 Subject: [PATCH 2777/3902] of: platform: Use default match table for /firmware commit 48e6a9c4a20870e09f85ff1a3628275d6bce31c0 upstream. Calling of_platform_populate() without a match table will only populate the immediate child nodes under /firmware. This is usually fine, but in the case of something like a "simple-mfd" node such as "raspberrypi,bcm2835-firmware", those child nodes will not be populated. And subsequent calls won't work either because the /firmware node is marked as processed already. Switch the call to of_platform_default_populate() to solve this problem. It should be a nop for existing cases. Fixes: 3aa0582fdb82 ("of: platform: populate /firmware/ node from of_platform_default_populate_init()") Cc: stable@vger.kernel.org Reviewed-by: Sudeep Holla Link: https://patch.msgid.link/20260114015158.692170-2-robh@kernel.org Signed-off-by: Rob Herring (Arm) Signed-off-by: Greg Kroah-Hartman --- drivers/of/platform.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/of/platform.c b/drivers/of/platform.c index f77cb19973a5d1..a6dca3a005aac9 100644 --- a/drivers/of/platform.c +++ b/drivers/of/platform.c @@ -569,7 +569,7 @@ static int __init of_platform_default_populate_init(void) node = of_find_node_by_path("/firmware"); if (node) { - of_platform_populate(node, NULL, NULL, NULL); + of_platform_default_populate(node, NULL, NULL); of_node_put(node); } From 1b68efce6dd483d22f50d0d3800c4cfda14b1305 Mon Sep 17 00:00:00 2001 From: "Matthew Wilcox (Oracle)" Date: Fri, 9 Jan 2026 04:13:42 +0000 Subject: [PATCH 2778/3902] migrate: correct lock ordering for hugetlb file folios commit b7880cb166ab62c2409046b2347261abf701530e upstream. Syzbot has found a deadlock (analyzed by Lance Yang): 1) Task (5749): Holds folio_lock, then tries to acquire i_mmap_rwsem(read lock). 2) Task (5754): Holds i_mmap_rwsem(write lock), then tries to acquire folio_lock. migrate_pages() -> migrate_hugetlbs() -> unmap_and_move_huge_page() <- Takes folio_lock! -> remove_migration_ptes() -> __rmap_walk_file() -> i_mmap_lock_read() <- Waits for i_mmap_rwsem(read lock)! hugetlbfs_fallocate() -> hugetlbfs_punch_hole() <- Takes i_mmap_rwsem(write lock)! -> hugetlbfs_zero_partial_page() -> filemap_lock_hugetlb_folio() -> filemap_lock_folio() -> __filemap_get_folio <- Waits for folio_lock! The migration path is the one taking locks in the wrong order according to the documentation at the top of mm/rmap.c. So expand the scope of the existing i_mmap_lock to cover the calls to remove_migration_ptes() too. This is (mostly) how it used to be after commit c0d0381ade79. That was removed by 336bf30eb765 for both file & anon hugetlb pages when it should only have been removed for anon hugetlb pages. Link: https://lkml.kernel.org/r/20260109041345.3863089-2-willy@infradead.org Signed-off-by: Matthew Wilcox (Oracle) Fixes: 336bf30eb765 ("hugetlbfs: fix anon huge page migration race") Reported-by: syzbot+2d9c96466c978346b55f@syzkaller.appspotmail.com Link: https://lore.kernel.org/all/68e9715a.050a0220.1186a4.000d.GAE@google.com Debugged-by: Lance Yang Acked-by: David Hildenbrand (Red Hat) Acked-by: Zi Yan Cc: Alistair Popple Cc: Byungchul Park Cc: Gregory Price Cc: Jann Horn Cc: Joshua Hahn Cc: Liam Howlett Cc: Lorenzo Stoakes Cc: Matthew Brost Cc: Rakie Kim Cc: Rik van Riel Cc: Vlastimil Babka Cc: Ying Huang Cc: Signed-off-by: Andrew Morton Signed-off-by: Greg Kroah-Hartman --- mm/migrate.c | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/mm/migrate.c b/mm/migrate.c index c0e9f15be2a229..a936623d0b237c 100644 --- a/mm/migrate.c +++ b/mm/migrate.c @@ -1450,6 +1450,7 @@ static int unmap_and_move_huge_page(new_folio_t get_new_folio, int page_was_mapped = 0; struct anon_vma *anon_vma = NULL; struct address_space *mapping = NULL; + enum ttu_flags ttu = 0; if (folio_ref_count(src) == 1) { /* page was freed from under us. So we are done. */ @@ -1490,8 +1491,6 @@ static int unmap_and_move_huge_page(new_folio_t get_new_folio, goto put_anon; if (folio_mapped(src)) { - enum ttu_flags ttu = 0; - if (!folio_test_anon(src)) { /* * In shared mappings, try_to_unmap could potentially @@ -1508,16 +1507,17 @@ static int unmap_and_move_huge_page(new_folio_t get_new_folio, try_to_migrate(src, ttu); page_was_mapped = 1; - - if (ttu & TTU_RMAP_LOCKED) - i_mmap_unlock_write(mapping); } if (!folio_mapped(src)) rc = move_to_new_folio(dst, src, mode); if (page_was_mapped) - remove_migration_ptes(src, !rc ? dst : src, 0); + remove_migration_ptes(src, !rc ? dst : src, + ttu ? RMP_LOCKED : 0); + + if (ttu & TTU_RMAP_LOCKED) + i_mmap_unlock_write(mapping); unlock_put_anon: folio_unlock(dst); From fa322c8bb2ea4742bf759a55231b15bd5812f2b2 Mon Sep 17 00:00:00 2001 From: Francesco Lavra Date: Fri, 28 Nov 2025 18:21:38 +0100 Subject: [PATCH 2779/3902] iio: accel: adxl380: fix handling of unavailable "INT1" interrupt MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit commit 4ff39d6de4bf359ec6d5cd2be34b36d077dd0a07 upstream. fwnode_irq_get_byname() returns a negative value on failure; if a negative value is returned, use it as `err` argument for dev_err_probe(). While at it, add a missing trailing newline to the dev_err_probe() error message. Fixes: df36de13677a ("iio: accel: add ADXL380 driver") Signed-off-by: Francesco Lavra Reviewed-by: Andy Shevchenko Reviewed-by: Nuno Sá Cc: stable@vger.kernel.org Signed-off-by: Jonathan Cameron Signed-off-by: Greg Kroah-Hartman --- drivers/iio/accel/adxl380.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/drivers/iio/accel/adxl380.c b/drivers/iio/accel/adxl380.c index 0cf3c68158293c..6d82873357cb8d 100644 --- a/drivers/iio/accel/adxl380.c +++ b/drivers/iio/accel/adxl380.c @@ -1728,9 +1728,9 @@ static int adxl380_config_irq(struct iio_dev *indio_dev) st->int_map[1] = ADXL380_INT0_MAP1_REG; } else { st->irq = fwnode_irq_get_byname(dev_fwnode(st->dev), "INT1"); - if (st->irq > 0) - return dev_err_probe(st->dev, -ENODEV, - "no interrupt name specified"); + if (st->irq < 0) + return dev_err_probe(st->dev, st->irq, + "no interrupt name specified\n"); st->int_map[0] = ADXL380_INT1_MAP0_REG; st->int_map[1] = ADXL380_INT1_MAP1_REG; } From ad25bb51203d0920f7894c2842b9505560284129 Mon Sep 17 00:00:00 2001 From: Markus Koeniger Date: Wed, 7 Jan 2026 16:32:18 +0100 Subject: [PATCH 2780/3902] iio: accel: iis328dq: fix gain values commit b8f15d1df2e73322e2112de21a4a7f3553c7fb60 upstream. The sensors IIS328DQ and H3LIS331DL share one configuration but H3LIS331DL has different gain parameters, configs therefore need to be split up. The gain parameters for the IIS328DQ are 0.98, 1.95 and 3.91, depending on the selected measurement range. See sensor manuals, chapter 2.1 "mechanical characteristics", parameter "Sensitivity". Datasheet: https://www.st.com/resource/en/datasheet/iis328dq.pdf Datasheet: https://www.st.com/resource/en/datasheet/h3lis331dl.pdf Fixes: 46e33707fe95 ("iio: accel: add support for IIS328DQ variant") Reviewed-by: Dimitri Fedrau Signed-off-by: Markus Koeniger Cc: Signed-off-by: Jonathan Cameron Signed-off-by: Greg Kroah-Hartman --- drivers/iio/accel/st_accel_core.c | 72 ++++++++++++++++++++++++++++++- 1 file changed, 71 insertions(+), 1 deletion(-) diff --git a/drivers/iio/accel/st_accel_core.c b/drivers/iio/accel/st_accel_core.c index a7961c610ed203..1a9447c81b0f70 100644 --- a/drivers/iio/accel/st_accel_core.c +++ b/drivers/iio/accel/st_accel_core.c @@ -517,7 +517,6 @@ static const struct st_sensor_settings st_accel_sensors_settings[] = { .wai_addr = ST_SENSORS_DEFAULT_WAI_ADDRESS, .sensors_supported = { [0] = H3LIS331DL_ACCEL_DEV_NAME, - [1] = IIS328DQ_ACCEL_DEV_NAME, }, .ch = (struct iio_chan_spec *)st_accel_12bit_channels, .odr = { @@ -584,6 +583,77 @@ static const struct st_sensor_settings st_accel_sensors_settings[] = { .multi_read_bit = true, .bootime = 2, }, + { + .wai = 0x32, + .wai_addr = ST_SENSORS_DEFAULT_WAI_ADDRESS, + .sensors_supported = { + [0] = IIS328DQ_ACCEL_DEV_NAME, + }, + .ch = (struct iio_chan_spec *)st_accel_12bit_channels, + .odr = { + .addr = 0x20, + .mask = 0x18, + .odr_avl = { + { .hz = 50, .value = 0x00, }, + { .hz = 100, .value = 0x01, }, + { .hz = 400, .value = 0x02, }, + { .hz = 1000, .value = 0x03, }, + }, + }, + .pw = { + .addr = 0x20, + .mask = 0x20, + .value_on = ST_SENSORS_DEFAULT_POWER_ON_VALUE, + .value_off = ST_SENSORS_DEFAULT_POWER_OFF_VALUE, + }, + .enable_axis = { + .addr = ST_SENSORS_DEFAULT_AXIS_ADDR, + .mask = ST_SENSORS_DEFAULT_AXIS_MASK, + }, + .fs = { + .addr = 0x23, + .mask = 0x30, + .fs_avl = { + [0] = { + .num = ST_ACCEL_FS_AVL_100G, + .value = 0x00, + .gain = IIO_G_TO_M_S_2(980), + }, + [1] = { + .num = ST_ACCEL_FS_AVL_200G, + .value = 0x01, + .gain = IIO_G_TO_M_S_2(1950), + }, + [2] = { + .num = ST_ACCEL_FS_AVL_400G, + .value = 0x03, + .gain = IIO_G_TO_M_S_2(3910), + }, + }, + }, + .bdu = { + .addr = 0x23, + .mask = 0x80, + }, + .drdy_irq = { + .int1 = { + .addr = 0x22, + .mask = 0x02, + }, + .int2 = { + .addr = 0x22, + .mask = 0x10, + }, + .addr_ihl = 0x22, + .mask_ihl = 0x80, + }, + .sim = { + .addr = 0x23, + .value = BIT(0), + }, + .multi_read_bit = true, + .bootime = 2, + }, { /* No WAI register present */ .sensors_supported = { From 348356c91909fce50aa47d2344de83642ad3ef05 Mon Sep 17 00:00:00 2001 From: Tomas Melin Date: Wed, 3 Dec 2025 09:28:11 +0000 Subject: [PATCH 2781/3902] iio: adc: ad9467: fix ad9434 vref mask MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit commit 92452b1760ff2d1d411414965d4d06f75e1bda9a upstream. The mask setting is 5 bits wide for the ad9434 (ref. data sheet register 0x18 FLEX_VREF). Apparently the settings from ad9265 were copied by mistake when support for the device was added to the driver. Fixes: 4606d0f4b05f ("iio: adc: ad9467: add support for AD9434 high-speed ADC") Reviewed-by: Andy Shevchenko Reviewed-by: Nuno Sá Reviewed-by: David Lechner Signed-off-by: Tomas Melin Cc: Signed-off-by: Jonathan Cameron Signed-off-by: Greg Kroah-Hartman --- drivers/iio/adc/ad9467.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/iio/adc/ad9467.c b/drivers/iio/adc/ad9467.c index f7a9f46ea0dc40..2d8f8da3671dac 100644 --- a/drivers/iio/adc/ad9467.c +++ b/drivers/iio/adc/ad9467.c @@ -95,7 +95,7 @@ #define CHIPID_AD9434 0x6A #define AD9434_DEF_OUTPUT_MODE 0x00 -#define AD9434_REG_VREF_MASK 0xC0 +#define AD9434_REG_VREF_MASK GENMASK(4, 0) /* * Analog Devices AD9467 16-Bit, 200/250 MSPS ADC From d890234a91570542c228a20f132ce74f9fedd904 Mon Sep 17 00:00:00 2001 From: Pei Xiao Date: Wed, 29 Oct 2025 10:40:16 +0800 Subject: [PATCH 2782/3902] iio: adc: at91-sama5d2_adc: Fix potential use-after-free in sama5d2_adc driver commit dbdb442218cd9d613adeab31a88ac973f22c4873 upstream. at91_adc_interrupt can call at91_adc_touch_data_handler function to start the work by schedule_work(&st->touch_st.workq). If we remove the module which will call at91_adc_remove to make cleanup, it will free indio_dev through iio_device_unregister but quite a bit later. While the work mentioned above will be used. The sequence of operations that may lead to a UAF bug is as follows: CPU0 CPU1 | at91_adc_workq_handler at91_adc_remove | iio_device_unregister(indio_dev) | //free indio_dev a bit later | | iio_push_to_buffers(indio_dev) | //use indio_dev Fix it by ensuring that the work is canceled before proceeding with the cleanup in at91_adc_remove. Fixes: 23ec2774f1cc ("iio: adc: at91-sama5d2_adc: add support for position and pressure channels") Signed-off-by: Pei Xiao Cc: Signed-off-by: Jonathan Cameron Signed-off-by: Greg Kroah-Hartman --- drivers/iio/adc/at91-sama5d2_adc.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/iio/adc/at91-sama5d2_adc.c b/drivers/iio/adc/at91-sama5d2_adc.c index b4c36e6a7490aa..aa4ba3f5a50603 100644 --- a/drivers/iio/adc/at91-sama5d2_adc.c +++ b/drivers/iio/adc/at91-sama5d2_adc.c @@ -2481,6 +2481,7 @@ static void at91_adc_remove(struct platform_device *pdev) struct at91_adc_state *st = iio_priv(indio_dev); iio_device_unregister(indio_dev); + cancel_work_sync(&st->touch_st.workq); at91_adc_dma_disable(st); From b6ffc2de37253b7e0559d249c5c8d7d8f2296dca Mon Sep 17 00:00:00 2001 From: Johan Hovold Date: Fri, 19 Dec 2025 12:05:45 +0100 Subject: [PATCH 2783/3902] iio: adc: exynos_adc: fix OF populate on driver rebind commit ea6b4feba85e996e840e0b661bc42793df6eb701 upstream. Since commit c6e126de43e7 ("of: Keep track of populated platform devices") child devices will not be created by of_platform_populate() if the devices had previously been deregistered individually so that the OF_POPULATED flag is still set in the corresponding OF nodes. Switch to using of_platform_depopulate() instead of open coding so that the child devices are created if the driver is rebound. Fixes: c6e126de43e7 ("of: Keep track of populated platform devices") Cc: stable@vger.kernel.org # 3.16 Signed-off-by: Johan Hovold Reviewed-by: Krzysztof Kozlowski Signed-off-by: Jonathan Cameron Signed-off-by: Greg Kroah-Hartman --- drivers/iio/adc/exynos_adc.c | 15 ++------------- 1 file changed, 2 insertions(+), 13 deletions(-) diff --git a/drivers/iio/adc/exynos_adc.c b/drivers/iio/adc/exynos_adc.c index 1484adff00df38..f2400897818c5a 100644 --- a/drivers/iio/adc/exynos_adc.c +++ b/drivers/iio/adc/exynos_adc.c @@ -540,15 +540,6 @@ static const struct iio_chan_spec exynos_adc_iio_channels[] = { ADC_CHANNEL(9, "adc9"), }; -static int exynos_adc_remove_devices(struct device *dev, void *c) -{ - struct platform_device *pdev = to_platform_device(dev); - - platform_device_unregister(pdev); - - return 0; -} - static int exynos_adc_probe(struct platform_device *pdev) { struct exynos_adc *info = NULL; @@ -660,8 +651,7 @@ static int exynos_adc_probe(struct platform_device *pdev) return 0; err_of_populate: - device_for_each_child(&indio_dev->dev, NULL, - exynos_adc_remove_devices); + of_platform_depopulate(&indio_dev->dev); iio_device_unregister(indio_dev); err_irq: free_irq(info->irq, info); @@ -681,8 +671,7 @@ static void exynos_adc_remove(struct platform_device *pdev) struct iio_dev *indio_dev = platform_get_drvdata(pdev); struct exynos_adc *info = iio_priv(indio_dev); - device_for_each_child(&indio_dev->dev, NULL, - exynos_adc_remove_devices); + of_platform_depopulate(&indio_dev->dev); iio_device_unregister(indio_dev); free_irq(info->irq, info); if (info->data->exit_hw) From d06a4f6c2b18663d1eaa48ea8a4429b496c7e72e Mon Sep 17 00:00:00 2001 From: Thorsten Blum Date: Tue, 2 Dec 2025 19:13:06 +0100 Subject: [PATCH 2784/3902] iio: adc: pac1934: Fix clamped value in pac1934_reg_snapshot commit da934ef0fdff5ba21e82ec3ab3f95fe73137b0c9 upstream. The local variable 'curr_energy' was never clamped to PAC_193X_MIN_POWER_ACC or PAC_193X_MAX_POWER_ACC because the return value of clamp() was not used. Fix this by assigning the clamped value back to 'curr_energy'. Cc: stable@vger.kernel.org Fixes: 0fb528c8255b ("iio: adc: adding support for PAC193x") Signed-off-by: Thorsten Blum Signed-off-by: Jonathan Cameron Signed-off-by: Greg Kroah-Hartman --- drivers/iio/adc/pac1934.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/drivers/iio/adc/pac1934.c b/drivers/iio/adc/pac1934.c index 48df1650926052..edaff72e9d792a 100644 --- a/drivers/iio/adc/pac1934.c +++ b/drivers/iio/adc/pac1934.c @@ -665,9 +665,9 @@ static int pac1934_reg_snapshot(struct pac1934_chip_info *info, /* add the power_acc field */ curr_energy += inc; - clamp(curr_energy, PAC_193X_MIN_POWER_ACC, PAC_193X_MAX_POWER_ACC); - - reg_data->energy_sec_acc[cnt] = curr_energy; + reg_data->energy_sec_acc[cnt] = clamp(curr_energy, + PAC_193X_MIN_POWER_ACC, + PAC_193X_MAX_POWER_ACC); } offset_reg_data_p += PAC1934_VPOWER_ACC_REG_LEN; From de8755ae2912d483141f413c34fc72da515a5180 Mon Sep 17 00:00:00 2001 From: Fiona Klute Date: Sat, 13 Dec 2025 17:32:26 +0100 Subject: [PATCH 2785/3902] iio: chemical: scd4x: fix reported channel endianness commit 81d5a5366d3c20203fb9d7345e1aa46d668445a2 upstream. The driver converts values read from the sensor from BE to CPU endianness in scd4x_read_meas(). The result is then pushed into the buffer in scd4x_trigger_handler(), so on LE architectures parsing the buffer using the reported BE type gave wrong results. scd4x_read_raw() which provides sysfs *_raw values is not affected, it used the values returned by scd4x_read_meas() without further conversion. Fixes: 49d22b695cbb6 ("drivers: iio: chemical: Add support for Sensirion SCD4x CO2 sensor") Signed-off-by: Fiona Klute Reviewed-by: David Lechner Cc: Signed-off-by: Jonathan Cameron Signed-off-by: Greg Kroah-Hartman --- drivers/iio/chemical/scd4x.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/drivers/iio/chemical/scd4x.c b/drivers/iio/chemical/scd4x.c index 8859f89fb2a942..0fd839176e264e 100644 --- a/drivers/iio/chemical/scd4x.c +++ b/drivers/iio/chemical/scd4x.c @@ -584,7 +584,7 @@ static const struct iio_chan_spec scd4x_channels[] = { .sign = 'u', .realbits = 16, .storagebits = 16, - .endianness = IIO_BE, + .endianness = IIO_CPU, }, }, { @@ -599,7 +599,7 @@ static const struct iio_chan_spec scd4x_channels[] = { .sign = 'u', .realbits = 16, .storagebits = 16, - .endianness = IIO_BE, + .endianness = IIO_CPU, }, }, { @@ -612,7 +612,7 @@ static const struct iio_chan_spec scd4x_channels[] = { .sign = 'u', .realbits = 16, .storagebits = 16, - .endianness = IIO_BE, + .endianness = IIO_CPU, }, }, }; From db16e7c52032c79156930a337ee17232931794ba Mon Sep 17 00:00:00 2001 From: Miaoqian Lin Date: Wed, 7 Jan 2026 22:35:50 +0800 Subject: [PATCH 2786/3902] iio: dac: ad3552r-hs: fix out-of-bound write in ad3552r_hs_write_data_source MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit commit 978d28136c53df38f8f0b747191930e2f95e9084 upstream. When simple_write_to_buffer() succeeds, it returns the number of bytes actually copied to the buffer. The code incorrectly uses 'count' as the index for null termination instead of the actual bytes copied. If count exceeds the buffer size, this leads to out-of-bounds write. Add a check for the count and use the return value as the index. The bug was validated using a demo module that mirrors the original code and was tested under QEMU. Pattern of the bug: - A fixed 64-byte stack buffer is filled using count. - If count > 64, the code still does buf[count] = '\0', causing an - out-of-bounds write on the stack. Steps for reproduce: - Opens the device node. - Writes 128 bytes of A to it. - This overflows the 64-byte stack buffer and KASAN reports the OOB. Found via static analysis. This is similar to the commit da9374819eb3 ("iio: backend: fix out-of-bound write") Fixes: b1c5d68ea66e ("iio: dac: ad3552r-hs: add support for internal ramp") Cc: stable@vger.kernel.org Signed-off-by: Miaoqian Lin Reviewed-by: Nuno Sá Reviewed-by: Andy Shevchenko Reviewed-by: David Lechner Signed-off-by: Jonathan Cameron Signed-off-by: Greg Kroah-Hartman --- drivers/iio/dac/ad3552r-hs.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/drivers/iio/dac/ad3552r-hs.c b/drivers/iio/dac/ad3552r-hs.c index 41b96b48ba98b6..a9578afa7015d2 100644 --- a/drivers/iio/dac/ad3552r-hs.c +++ b/drivers/iio/dac/ad3552r-hs.c @@ -549,12 +549,15 @@ static ssize_t ad3552r_hs_write_data_source(struct file *f, guard(mutex)(&st->lock); + if (count >= sizeof(buf)) + return -ENOSPC; + ret = simple_write_to_buffer(buf, sizeof(buf) - 1, ppos, userbuf, count); if (ret < 0) return ret; - buf[count] = '\0'; + buf[ret] = '\0'; ret = match_string(dbgfs_attr_source, ARRAY_SIZE(dbgfs_attr_source), buf); From 59e54709f9a97b3f03bbb22664e340319fe6b696 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C3=BCbrich=2C=20Andreas?= Date: Mon, 17 Nov 2025 12:35:13 +0000 Subject: [PATCH 2787/3902] iio: dac: ad5686: add AD5695R to ad5686_chip_info_tbl MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit commit 441ac29923c9172bc5e4b2c4f52ae756192f5715 upstream. The chip info for this variant (I2C, four channels, 14 bit, internal reference) seems to have been left out due to oversight, so ad5686_chip_info_tbl[ID_AD5695R] is all zeroes. Initialisation of an AD5695R still succeeds, but the resulting IIO device has no channels and no /dev/iio:device* node. Add the missing chip info to the table. Fixes: 4177381b4401 ("iio:dac:ad5686: Add AD5671R/75R/94/94R/95R/96/96R support") Signed-off-by: Andreas Kübrich Cc: stable@vger.kernel.org Signed-off-by: Jonathan Cameron Signed-off-by: Greg Kroah-Hartman --- drivers/iio/dac/ad5686.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/drivers/iio/dac/ad5686.c b/drivers/iio/dac/ad5686.c index d9cae9555e5df4..4b18498aa0749e 100644 --- a/drivers/iio/dac/ad5686.c +++ b/drivers/iio/dac/ad5686.c @@ -434,6 +434,12 @@ static const struct ad5686_chip_info ad5686_chip_info_tbl[] = { .num_channels = 4, .regmap_type = AD5686_REGMAP, }, + [ID_AD5695R] = { + .channels = ad5685r_channels, + .int_vref_mv = 2500, + .num_channels = 4, + .regmap_type = AD5686_REGMAP, + }, [ID_AD5696] = { .channels = ad5686_channels, .num_channels = 4, From 873e2360d247eeee642878fcc3398babff7e387c Mon Sep 17 00:00:00 2001 From: Takashi Iwai Date: Mon, 19 Jan 2026 14:32:07 +0100 Subject: [PATCH 2788/3902] ALSA: ctxfi: Fix potential OOB access in audio mixer handling commit 61006c540cbdedea83b05577dc7fb7fa18fe1276 upstream. In the audio mixer handling code of ctxfi driver, the conf field is used as a kind of loop index, and it's referred in the index callbacks (amixer_index() and sum_index()). As spotted recently by fuzzers, the current code causes OOB access at those functions. | UBSAN: array-index-out-of-bounds in /build/reproducible-path/linux-6.17.8/sound/pci/ctxfi/ctamixer.c:347:48 | index 8 is out of range for type 'unsigned char [8]' After the analysis, the cause was found to be the lack of the proper (re-)initialization of conj field. This patch addresses those OOB accesses by adding the proper initializations of the loop indices. Reported-by: Salvatore Bonaccorso Tested-by: Karsten Hohmeier Closes: https://bugs.debian.org/1121535 Cc: Link: https://lore.kernel.org/all/aSk8KJI35H7gFru6@eldamar.lan/ Link: https://patch.msgid.link/20260119133212.189129-1-tiwai@suse.de Signed-off-by: Takashi Iwai Signed-off-by: Greg Kroah-Hartman --- sound/pci/ctxfi/ctamixer.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/sound/pci/ctxfi/ctamixer.c b/sound/pci/ctxfi/ctamixer.c index bb465859263693..c30162be27ee94 100644 --- a/sound/pci/ctxfi/ctamixer.c +++ b/sound/pci/ctxfi/ctamixer.c @@ -205,6 +205,7 @@ static int amixer_rsc_init(struct amixer *amixer, /* Set amixer specific operations */ amixer->rsc.ops = &amixer_basic_rsc_ops; + amixer->rsc.conj = 0; amixer->ops = &amixer_ops; amixer->input = NULL; amixer->sum = NULL; @@ -367,6 +368,7 @@ static int sum_rsc_init(struct sum *sum, return err; sum->rsc.ops = &sum_basic_rsc_ops; + sum->rsc.conj = 0; return 0; } From 29f43e8ec7bf017303751a3b0180a8b22d25b3b1 Mon Sep 17 00:00:00 2001 From: Zhang Heng Date: Thu, 22 Jan 2026 16:52:40 +0800 Subject: [PATCH 2789/3902] ALSA: hda/realtek: Add quirk for Samsung 730QED to fix headphone commit c45385ed624eecc5305ff165e1ac5dfa7548bcd5 upstream. After applying this quirk for the ALC256 audio codec, the headphone audio path functions normally; otherwise, headphones produce no sound. Link: https://bugzilla.kernel.org/show_bug.cgi?id=220574 Cc: Signed-off-by: Zhang Heng Link: https://patch.msgid.link/20260122085240.3163975-1-zhangheng@kylinos.cn Signed-off-by: Takashi Iwai Signed-off-by: Greg Kroah-Hartman --- sound/hda/codecs/realtek/alc269.c | 1 + 1 file changed, 1 insertion(+) diff --git a/sound/hda/codecs/realtek/alc269.c b/sound/hda/codecs/realtek/alc269.c index b5aa8da1e50a89..fedbc5afc40674 100644 --- a/sound/hda/codecs/realtek/alc269.c +++ b/sound/hda/codecs/realtek/alc269.c @@ -6906,6 +6906,7 @@ static const struct hda_quirk alc269_fixup_tbl[] = { SND_PCI_QUIRK(0x144d, 0xc812, "Samsung Notebook Pen S (NT950SBE-X58)", ALC298_FIXUP_SAMSUNG_AMP), SND_PCI_QUIRK(0x144d, 0xc830, "Samsung Galaxy Book Ion (NT950XCJ-X716A)", ALC298_FIXUP_SAMSUNG_AMP), SND_PCI_QUIRK(0x144d, 0xc832, "Samsung Galaxy Book Flex Alpha (NP730QCJ)", ALC256_FIXUP_SAMSUNG_HEADPHONE_VERY_QUIET), + SND_PCI_QUIRK(0x144d, 0xc876, "Samsung 730QED (NP730QED-KA2US)", ALC256_FIXUP_SAMSUNG_HEADPHONE_VERY_QUIET), SND_PCI_QUIRK(0x144d, 0xca03, "Samsung Galaxy Book2 Pro 360 (NP930QED)", ALC298_FIXUP_SAMSUNG_AMP), SND_PCI_QUIRK(0x144d, 0xca06, "Samsung Galaxy Book3 360 (NP730QFG)", ALC298_FIXUP_SAMSUNG_HEADPHONE_VERY_QUIET), SND_PCI_QUIRK(0x144d, 0xc868, "Samsung Galaxy Book2 Pro (NP930XED)", ALC298_FIXUP_SAMSUNG_AMP), From 31a3eba5c265a763260976674a22851e83128f6d Mon Sep 17 00:00:00 2001 From: Samasth Norway Ananda Date: Fri, 16 Jan 2026 17:27:06 -0800 Subject: [PATCH 2790/3902] ALSA: scarlett2: Fix buffer overflow in config retrieval commit 6f5c69f72e50d51be3a8c028ae7eda42c82902cb upstream. The scarlett2_usb_get_config() function has a logic error in the endianness conversion code that can cause buffer overflows when count > 1. The code checks `if (size == 2)` where `size` is the total buffer size in bytes, then loops `count` times treating each element as u16 (2 bytes). This causes the loop to access `count * 2` bytes when the buffer only has `size` bytes allocated. Fix by checking the element size (config_item->size) instead of the total buffer size. This ensures the endianness conversion matches the actual element type. Fixes: ac34df733d2d ("ALSA: usb-audio: scarlett2: Update get_config to do endian conversion") Cc: stable@vger.kernel.org Signed-off-by: Samasth Norway Ananda Link: https://patch.msgid.link/20260117012706.1715574-1-samasth.norway.ananda@oracle.com Signed-off-by: Takashi Iwai Signed-off-by: Greg Kroah-Hartman --- sound/usb/mixer_scarlett2.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/sound/usb/mixer_scarlett2.c b/sound/usb/mixer_scarlett2.c index f2446bf3982c66..bef8c9e544dd3d 100644 --- a/sound/usb/mixer_scarlett2.c +++ b/sound/usb/mixer_scarlett2.c @@ -2533,13 +2533,13 @@ static int scarlett2_usb_get_config( err = scarlett2_usb_get(mixer, config_item->offset, buf, size); if (err < 0) return err; - if (size == 2) { + if (config_item->size == 16) { u16 *buf_16 = buf; for (i = 0; i < count; i++, buf_16++) *buf_16 = le16_to_cpu(*(__le16 *)buf_16); - } else if (size == 4) { - u32 *buf_32 = buf; + } else if (config_item->size == 32) { + u32 *buf_32 = (u32 *)buf; for (i = 0; i < count; i++, buf_32++) *buf_32 = le32_to_cpu(*(__le32 *)buf_32); From dc1a5dd80af1ee1f29d8375b12dd7625f6294dad Mon Sep 17 00:00:00 2001 From: Berk Cem Goksel Date: Tue, 20 Jan 2026 13:28:55 +0300 Subject: [PATCH 2791/3902] ALSA: usb-audio: Fix use-after-free in snd_usb_mixer_free() commit 930e69757b74c3ae083b0c3c7419bfe7f0edc7b2 upstream. When snd_usb_create_mixer() fails, snd_usb_mixer_free() frees mixer->id_elems but the controls already added to the card still reference the freed memory. Later when snd_card_register() runs, the OSS mixer layer calls their callbacks and hits a use-after-free read. Call trace: get_ctl_value+0x63f/0x820 sound/usb/mixer.c:411 get_min_max_with_quirks.isra.0+0x240/0x1f40 sound/usb/mixer.c:1241 mixer_ctl_feature_info+0x26b/0x490 sound/usb/mixer.c:1381 snd_mixer_oss_build_test+0x174/0x3a0 sound/core/oss/mixer_oss.c:887 ... snd_card_register+0x4ed/0x6d0 sound/core/init.c:923 usb_audio_probe+0x5ef/0x2a90 sound/usb/card.c:1025 Fix by calling snd_ctl_remove() for all mixer controls before freeing id_elems. We save the next pointer first because snd_ctl_remove() frees the current element. Fixes: 6639b6c2367f ("[ALSA] usb-audio - add mixer control notifications") Cc: stable@vger.kernel.org Cc: Andrey Konovalov Signed-off-by: Berk Cem Goksel Link: https://patch.msgid.link/20260120102855.7300-1-berkcgoksel@gmail.com Signed-off-by: Takashi Iwai Signed-off-by: Greg Kroah-Hartman --- sound/usb/mixer.c | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/sound/usb/mixer.c b/sound/usb/mixer.c index 90917c6ea871b4..bfe15b1cb66c5c 100644 --- a/sound/usb/mixer.c +++ b/sound/usb/mixer.c @@ -2945,10 +2945,23 @@ static int parse_audio_unit(struct mixer_build *state, int unitid) static void snd_usb_mixer_free(struct usb_mixer_interface *mixer) { + struct usb_mixer_elem_list *list, *next; + int id; + /* kill pending URBs */ snd_usb_mixer_disconnect(mixer); - kfree(mixer->id_elems); + /* Unregister controls first, snd_ctl_remove() frees the element */ + if (mixer->id_elems) { + for (id = 0; id < MAX_ID_ELEMS; id++) { + for (list = mixer->id_elems[id]; list; list = next) { + next = list->next_id_elem; + if (list->kctl) + snd_ctl_remove(mixer->chip->card, list->kctl); + } + } + kfree(mixer->id_elems); + } if (mixer->urb) { kfree(mixer->urb->transfer_buffer); usb_free_urb(mixer->urb); From 6c4555bfd4383c10ffd4ccad495f10d9565dac29 Mon Sep 17 00:00:00 2001 From: Matthew Schwartz Date: Mon, 29 Dec 2025 12:45:26 -0800 Subject: [PATCH 2792/3902] mmc: rtsx_pci_sdmmc: implement sdmmc_card_busy function commit 122610220134b32c742cc056eaf64f7017ac8cd9 upstream. rtsx_pci_sdmmc does not have an sdmmc_card_busy function, so any voltage switches cause a kernel warning, "mmc0: cannot verify signal voltage switch." Copy the sdmmc_card_busy function from rtsx_pci_usb to rtsx_pci_sdmmc to fix this. Fixes: ff984e57d36e ("mmc: Add realtek pcie sdmmc host driver") Signed-off-by: Matthew Schwartz Tested-by: Ricky WU Reviewed-by: Ricky WU Cc: stable@vger.kernel.org Signed-off-by: Ulf Hansson Signed-off-by: Greg Kroah-Hartman --- drivers/mmc/host/rtsx_pci_sdmmc.c | 41 +++++++++++++++++++++++++++++++ 1 file changed, 41 insertions(+) diff --git a/drivers/mmc/host/rtsx_pci_sdmmc.c b/drivers/mmc/host/rtsx_pci_sdmmc.c index dc2587ff8519f8..4db3328f46dfbd 100644 --- a/drivers/mmc/host/rtsx_pci_sdmmc.c +++ b/drivers/mmc/host/rtsx_pci_sdmmc.c @@ -1306,6 +1306,46 @@ static int sdmmc_switch_voltage(struct mmc_host *mmc, struct mmc_ios *ios) return err; } +static int sdmmc_card_busy(struct mmc_host *mmc) +{ + struct realtek_pci_sdmmc *host = mmc_priv(mmc); + struct rtsx_pcr *pcr = host->pcr; + int err; + u8 stat; + u8 mask = SD_DAT3_STATUS | SD_DAT2_STATUS | SD_DAT1_STATUS + | SD_DAT0_STATUS; + + mutex_lock(&pcr->pcr_mutex); + + rtsx_pci_start_run(pcr); + + err = rtsx_pci_write_register(pcr, SD_BUS_STAT, + SD_CLK_TOGGLE_EN | SD_CLK_FORCE_STOP, + SD_CLK_TOGGLE_EN); + if (err) + goto out; + + mdelay(1); + + err = rtsx_pci_read_register(pcr, SD_BUS_STAT, &stat); + if (err) + goto out; + + err = rtsx_pci_write_register(pcr, SD_BUS_STAT, + SD_CLK_TOGGLE_EN | SD_CLK_FORCE_STOP, 0); +out: + mutex_unlock(&pcr->pcr_mutex); + + if (err) + return err; + + /* check if any pin between dat[0:3] is low */ + if ((stat & mask) != mask) + return 1; + else + return 0; +} + static int sdmmc_execute_tuning(struct mmc_host *mmc, u32 opcode) { struct realtek_pci_sdmmc *host = mmc_priv(mmc); @@ -1418,6 +1458,7 @@ static const struct mmc_host_ops realtek_pci_sdmmc_ops = { .get_ro = sdmmc_get_ro, .get_cd = sdmmc_get_cd, .start_signal_voltage_switch = sdmmc_switch_voltage, + .card_busy = sdmmc_card_busy, .execute_tuning = sdmmc_execute_tuning, .init_sd_express = sdmmc_init_sd_express, }; From 36be050f21dea7a3a76dff5a031da6274e8ee468 Mon Sep 17 00:00:00 2001 From: Shawn Lin Date: Mon, 22 Dec 2025 15:11:25 +0800 Subject: [PATCH 2793/3902] mmc: sdhci-of-dwcmshc: Prevent illegal clock reduction in HS200/HS400 mode commit 3009738a855cf938bbfc9078bec725031ae623a4 upstream. When operating in HS200 or HS400 timing modes, reducing the clock frequency below 52MHz will lead to link broken as the Rockchip DWC MSHC controller requires maintaining a minimum clock of 52MHz in these modes. Add a check to prevent illegal clock reduction through debugfs: root@debian:/# echo 50000000 > /sys/kernel/debug/mmc0/clock root@debian:/# [ 30.090146] mmc0: running CQE recovery mmc0: cqhci: Failed to halt mmc0: cqhci: spurious TCN for tag 0 WARNING: drivers/mmc/host/cqhci-core.c:797 at cqhci_irq+0x254/0x818, CPU#1: kworker/1:0H/24 Modules linked in: CPU: 1 UID: 0 PID: 24 Comm: kworker/1:0H Not tainted 6.19.0-rc1-00001-g09db0998649d-dirty #204 PREEMPT Hardware name: Rockchip RK3588 EVB1 V10 Board (DT) Workqueue: kblockd blk_mq_run_work_fn pstate: 604000c9 (nZCv daIF +PAN -UAO -TCO -DIT -SSBS BTYPE=--) pc : cqhci_irq+0x254/0x818 lr : cqhci_irq+0x254/0x818 ... Fixes: c6f361cba51c ("mmc: sdhci-of-dwcmshc: add support for rk3588") Cc: Sebastian Reichel Cc: Yifeng Zhao Signed-off-by: Shawn Lin Cc: stable@vger.kernel.org Signed-off-by: Ulf Hansson Signed-off-by: Greg Kroah-Hartman --- drivers/mmc/host/sdhci-of-dwcmshc.c | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/drivers/mmc/host/sdhci-of-dwcmshc.c b/drivers/mmc/host/sdhci-of-dwcmshc.c index 4e256673a0983e..5ae4e11b1e90c7 100644 --- a/drivers/mmc/host/sdhci-of-dwcmshc.c +++ b/drivers/mmc/host/sdhci-of-dwcmshc.c @@ -608,6 +608,13 @@ static void dwcmshc_rk3568_set_clock(struct sdhci_host *host, unsigned int clock sdhci_writel(host, extra, reg); if (clock <= 52000000) { + if (host->mmc->ios.timing == MMC_TIMING_MMC_HS200 || + host->mmc->ios.timing == MMC_TIMING_MMC_HS400) { + dev_err(mmc_dev(host->mmc), + "Can't reduce the clock below 52MHz in HS200/HS400 mode"); + return; + } + /* * Disable DLL and reset both of sample and drive clock. * The bypass bit and start bit need to be set if DLL is not locked. From 41ec6988547819756fb65e94fc24f3e0dddf84ac Mon Sep 17 00:00:00 2001 From: Chaitanya Kulkarni Date: Fri, 19 Dec 2025 15:28:58 -0800 Subject: [PATCH 2794/3902] iommu/io-pgtable-arm: fix size_t signedness bug in unmap path commit 374e7af67d9d9d6103c2cfc8eb32abfecf3a2fd8 upstream. __arm_lpae_unmap() returns size_t but was returning -ENOENT (negative error code) when encountering an unmapped PTE. Since size_t is unsigned, -ENOENT (typically -2) becomes a huge positive value (0xFFFFFFFFFFFFFFFE on 64-bit systems). This corrupted value propagates through the call chain: __arm_lpae_unmap() returns -ENOENT as size_t -> arm_lpae_unmap_pages() returns it -> __iommu_unmap() adds it to iova address -> iommu_pgsize() triggers BUG_ON due to corrupted iova This can cause IOVA address overflow in __iommu_unmap() loop and trigger BUG_ON in iommu_pgsize() from invalid address alignment. Fix by returning 0 instead of -ENOENT. The WARN_ON already signals the error condition, and returning 0 (meaning "nothing unmapped") is the correct semantic for size_t return type. This matches the behavior of other io-pgtable implementations (io-pgtable-arm-v7s, io-pgtable-dart) which return 0 on error conditions. Fixes: 3318f7b5cefb ("iommu/io-pgtable-arm: Add quirk to quiet WARN_ON()") Cc: stable@vger.kernel.org Signed-off-by: Chaitanya Kulkarni Acked-by: Will Deacon Reviewed-by: Jason Gunthorpe Reviewed-by: Rob Clark Signed-off-by: Joerg Roedel Signed-off-by: Greg Kroah-Hartman --- drivers/iommu/io-pgtable-arm.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/iommu/io-pgtable-arm.c b/drivers/iommu/io-pgtable-arm.c index 7e8e2216c294a9..040f5c4672564c 100644 --- a/drivers/iommu/io-pgtable-arm.c +++ b/drivers/iommu/io-pgtable-arm.c @@ -639,7 +639,7 @@ static size_t __arm_lpae_unmap(struct arm_lpae_io_pgtable *data, pte = READ_ONCE(*ptep); if (!pte) { WARN_ON(!(data->iop.cfg.quirks & IO_PGTABLE_QUIRK_NO_WARN)); - return -ENOENT; + return 0; } /* If the size matches this level, we're in the right place */ From 448a2071a843831fe5fa71545cbfa7e15ee8966d Mon Sep 17 00:00:00 2001 From: Lyude Paul Date: Wed, 21 Jan 2026 14:13:10 -0500 Subject: [PATCH 2795/3902] drm/nouveau/disp: Set drm_mode_config_funcs.atomic_(check|commit) commit 604826acb3f53c6648a7ee99a3914ead680ab7fb upstream. Apparently we never actually filled these in, despite the fact that we do in fact technically support atomic modesetting. Since not having these filled in causes us to potentially forget to disable fbdev and friends during suspend/resume, let's fix it. Signed-off-by: Lyude Paul Cc: stable@vger.kernel.org Reviewed-by: Dave Airlie Link: https://patch.msgid.link/20260121191320.210342-1-lyude@redhat.com Signed-off-by: Greg Kroah-Hartman --- drivers/gpu/drm/nouveau/nouveau_display.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/drivers/gpu/drm/nouveau/nouveau_display.c b/drivers/gpu/drm/nouveau/nouveau_display.c index 805d0a87aa5461..e547be5906a055 100644 --- a/drivers/gpu/drm/nouveau/nouveau_display.c +++ b/drivers/gpu/drm/nouveau/nouveau_display.c @@ -351,6 +351,8 @@ nouveau_user_framebuffer_create(struct drm_device *dev, static const struct drm_mode_config_funcs nouveau_mode_config_funcs = { .fb_create = nouveau_user_framebuffer_create, + .atomic_commit = drm_atomic_helper_commit, + .atomic_check = drm_atomic_helper_check, }; From 07f363f305793baecad41816f73056252f3df61e Mon Sep 17 00:00:00 2001 From: Thomas Fourier Date: Mon, 5 Jan 2026 22:04:38 +0100 Subject: [PATCH 2796/3902] wifi: ath10k: fix dma_free_coherent() pointer commit 9282a1e171ad8d2205067e8ec3bbe4e3cef4f29f upstream. dma_alloc_coherent() allocates a DMA mapped buffer and stores the addresses in XXX_unaligned fields. Those should be reused when freeing the buffer rather than the aligned addresses. Fixes: 2a1e1ad3fd37 ("ath10k: Add support for 64 bit ce descriptor") Cc: stable@vger.kernel.org Signed-off-by: Thomas Fourier Reviewed-by: Baochen Qiang Link: https://patch.msgid.link/20260105210439.20131-2-fourier.thomas@gmail.com Signed-off-by: Jeff Johnson Signed-off-by: Greg Kroah-Hartman --- drivers/net/wireless/ath/ath10k/ce.c | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/drivers/net/wireless/ath/ath10k/ce.c b/drivers/net/wireless/ath/ath10k/ce.c index 7bbda46cfd93c3..82f120ee1c66d5 100644 --- a/drivers/net/wireless/ath/ath10k/ce.c +++ b/drivers/net/wireless/ath/ath10k/ce.c @@ -1727,8 +1727,8 @@ static void _ath10k_ce_free_pipe(struct ath10k *ar, int ce_id) (ce_state->src_ring->nentries * sizeof(struct ce_desc) + CE_DESC_RING_ALIGN), - ce_state->src_ring->base_addr_owner_space, - ce_state->src_ring->base_addr_ce_space); + ce_state->src_ring->base_addr_owner_space_unaligned, + ce_state->src_ring->base_addr_ce_space_unaligned); kfree(ce_state->src_ring); } @@ -1737,8 +1737,8 @@ static void _ath10k_ce_free_pipe(struct ath10k *ar, int ce_id) (ce_state->dest_ring->nentries * sizeof(struct ce_desc) + CE_DESC_RING_ALIGN), - ce_state->dest_ring->base_addr_owner_space, - ce_state->dest_ring->base_addr_ce_space); + ce_state->dest_ring->base_addr_owner_space_unaligned, + ce_state->dest_ring->base_addr_ce_space_unaligned); kfree(ce_state->dest_ring); } @@ -1758,8 +1758,8 @@ static void _ath10k_ce_free_pipe_64(struct ath10k *ar, int ce_id) (ce_state->src_ring->nentries * sizeof(struct ce_desc_64) + CE_DESC_RING_ALIGN), - ce_state->src_ring->base_addr_owner_space, - ce_state->src_ring->base_addr_ce_space); + ce_state->src_ring->base_addr_owner_space_unaligned, + ce_state->src_ring->base_addr_ce_space_unaligned); kfree(ce_state->src_ring); } @@ -1768,8 +1768,8 @@ static void _ath10k_ce_free_pipe_64(struct ath10k *ar, int ce_id) (ce_state->dest_ring->nentries * sizeof(struct ce_desc_64) + CE_DESC_RING_ALIGN), - ce_state->dest_ring->base_addr_owner_space, - ce_state->dest_ring->base_addr_ce_space); + ce_state->dest_ring->base_addr_owner_space_unaligned, + ce_state->dest_ring->base_addr_ce_space_unaligned); kfree(ce_state->dest_ring); } From 4846b32be324f4dd3653f38a3f69c049543d52ae Mon Sep 17 00:00:00 2001 From: Thomas Fourier Date: Tue, 6 Jan 2026 09:49:04 +0100 Subject: [PATCH 2797/3902] wifi: ath12k: fix dma_free_coherent() pointer commit bb97131fbf9b708dd9616ac2bdc793ad102b5c48 upstream. dma_alloc_coherent() allocates a DMA mapped buffer and stores the addresses in XXX_unaligned fields. Those should be reused when freeing the buffer rather than the aligned addresses. Fixes: d889913205cf ("wifi: ath12k: driver for Qualcomm Wi-Fi 7 devices") Cc: stable@vger.kernel.org Signed-off-by: Thomas Fourier Reviewed-by: Baochen Qiang Link: https://patch.msgid.link/20260106084905.18622-2-fourier.thomas@gmail.com Signed-off-by: Jeff Johnson Signed-off-by: Greg Kroah-Hartman --- drivers/net/wireless/ath/ath12k/ce.c | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/drivers/net/wireless/ath/ath12k/ce.c b/drivers/net/wireless/ath/ath12k/ce.c index 9a63608838ace3..4aea5844683818 100644 --- a/drivers/net/wireless/ath/ath12k/ce.c +++ b/drivers/net/wireless/ath/ath12k/ce.c @@ -984,8 +984,8 @@ void ath12k_ce_free_pipes(struct ath12k_base *ab) dma_free_coherent(ab->dev, pipe->src_ring->nentries * desc_sz + CE_DESC_RING_ALIGN, - pipe->src_ring->base_addr_owner_space, - pipe->src_ring->base_addr_ce_space); + pipe->src_ring->base_addr_owner_space_unaligned, + pipe->src_ring->base_addr_ce_space_unaligned); kfree(pipe->src_ring); pipe->src_ring = NULL; } @@ -995,8 +995,8 @@ void ath12k_ce_free_pipes(struct ath12k_base *ab) dma_free_coherent(ab->dev, pipe->dest_ring->nentries * desc_sz + CE_DESC_RING_ALIGN, - pipe->dest_ring->base_addr_owner_space, - pipe->dest_ring->base_addr_ce_space); + pipe->dest_ring->base_addr_owner_space_unaligned, + pipe->dest_ring->base_addr_ce_space_unaligned); kfree(pipe->dest_ring); pipe->dest_ring = NULL; } @@ -1007,8 +1007,8 @@ void ath12k_ce_free_pipes(struct ath12k_base *ab) dma_free_coherent(ab->dev, pipe->status_ring->nentries * desc_sz + CE_DESC_RING_ALIGN, - pipe->status_ring->base_addr_owner_space, - pipe->status_ring->base_addr_ce_space); + pipe->status_ring->base_addr_owner_space_unaligned, + pipe->status_ring->base_addr_ce_space_unaligned); kfree(pipe->status_ring); pipe->status_ring = NULL; } From 0adefb6396d7be87dd23c8f499129d6fed1b6a48 Mon Sep 17 00:00:00 2001 From: Dan Carpenter Date: Thu, 8 Jan 2026 23:00:24 +0300 Subject: [PATCH 2798/3902] wifi: mwifiex: Fix a loop in mwifiex_update_ampdu_rxwinsize() commit 2120f3a3738a65730c81bf10447b1ff776078915 upstream. The "i" iterator variable is used to count two different things but unfortunately we can't store two different numbers in the same variable. Use "i" for the outside loop and "j" for the inside loop. Cc: stable@vger.kernel.org Fixes: d219b7eb3792 ("mwifiex: handle BT coex event to adjust Rx BA window size") Signed-off-by: Dan Carpenter Reviewed-by: Jeff Chen Link: https://patch.msgid.link/aWAM2MGUWRP0zWUd@stanley.mountain Signed-off-by: Johannes Berg Signed-off-by: Greg Kroah-Hartman --- drivers/net/wireless/marvell/mwifiex/11n_rxreorder.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/drivers/net/wireless/marvell/mwifiex/11n_rxreorder.c b/drivers/net/wireless/marvell/mwifiex/11n_rxreorder.c index 354c5ce6604550..f3397dc6c422ed 100644 --- a/drivers/net/wireless/marvell/mwifiex/11n_rxreorder.c +++ b/drivers/net/wireless/marvell/mwifiex/11n_rxreorder.c @@ -825,7 +825,7 @@ void mwifiex_update_rxreor_flags(struct mwifiex_adapter *adapter, u8 flags) static void mwifiex_update_ampdu_rxwinsize(struct mwifiex_adapter *adapter, bool coex_flag) { - u8 i; + u8 i, j; u32 rx_win_size; struct mwifiex_private *priv; @@ -863,8 +863,8 @@ static void mwifiex_update_ampdu_rxwinsize(struct mwifiex_adapter *adapter, if (rx_win_size != priv->add_ba_param.rx_win_size) { if (!priv->media_connected) continue; - for (i = 0; i < MAX_NUM_TID; i++) - mwifiex_11n_delba(priv, i); + for (j = 0; j < MAX_NUM_TID; j++) + mwifiex_11n_delba(priv, j); } } } From 31efbcff90884ea5f65bf3d1de01267db51ee3d1 Mon Sep 17 00:00:00 2001 From: Marek Vasut Date: Sat, 10 Jan 2026 00:56:29 +0100 Subject: [PATCH 2799/3902] wifi: rsi: Fix memory corruption due to not set vif driver data size commit 4f431d88ea8093afc7ba55edf4652978c5a68f33 upstream. The struct ieee80211_vif contains trailing space for vif driver data, when struct ieee80211_vif is allocated, the total memory size that is allocated is sizeof(struct ieee80211_vif) + size of vif driver data. The size of vif driver data is set by each WiFi driver as needed. The RSI911x driver does not set vif driver data size, no trailing space for vif driver data is therefore allocated past struct ieee80211_vif . The RSI911x driver does however use the vif driver data to store its vif driver data structure "struct vif_priv". An access to vif->drv_priv leads to access out of struct ieee80211_vif bounds and corruption of some memory. In case of the failure observed locally, rsi_mac80211_add_interface() would write struct vif_priv *vif_info = (struct vif_priv *)vif->drv_priv; vif_info->vap_id = vap_idx. This write corrupts struct fq_tin member struct list_head new_flows . The flow = list_first_entry(head, struct fq_flow, flowchain); in fq_tin_reset() then reports non-NULL bogus address, which when accessed causes a crash. The trigger is very simple, boot the machine with init=/bin/sh , mount devtmpfs, sysfs, procfs, and then do "ip link set wlan0 up", "sleep 1", "ip link set wlan0 down" and the crash occurs. Fix this by setting the correct size of vif driver data, which is the size of "struct vif_priv", so that memory is allocated and the driver can store its driver data in it, instead of corrupting memory around it. Cc: stable@vger.kernel.org Fixes: dad0d04fa7ba ("rsi: Add RS9113 wireless driver") Signed-off-by: Marek Vasut Link: https://patch.msgid.link/20260109235817.150330-1-marex@nabladev.com Signed-off-by: Johannes Berg Signed-off-by: Greg Kroah-Hartman --- drivers/net/wireless/rsi/rsi_91x_mac80211.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/net/wireless/rsi/rsi_91x_mac80211.c b/drivers/net/wireless/rsi/rsi_91x_mac80211.c index f3a853edfc11df..8c8e074a3a7056 100644 --- a/drivers/net/wireless/rsi/rsi_91x_mac80211.c +++ b/drivers/net/wireless/rsi/rsi_91x_mac80211.c @@ -2035,6 +2035,7 @@ int rsi_mac80211_attach(struct rsi_common *common) hw->queues = MAX_HW_QUEUES; hw->extra_tx_headroom = RSI_NEEDED_HEADROOM; + hw->vif_data_size = sizeof(struct vif_priv); hw->max_rates = 1; hw->max_rate_tries = MAX_RETRIES; From 4f39984176e7edcaba3432b6c649c6fe93bf2f80 Mon Sep 17 00:00:00 2001 From: Mark Rutland Date: Tue, 20 Jan 2026 14:51:05 +0000 Subject: [PATCH 2800/3902] arm64/fpsimd: ptrace: Fix SVE writes on !SME systems commit 128a7494a9f15aad60cc6b7e3546bf481ac54a13 upstream. When SVE is supported but SME is not supported, a ptrace write to the NT_ARM_SVE regset can place the tracee into an invalid state where (non-streaming) SVE register data is stored in FP_STATE_SVE format but TIF_SVE is clear. This can result in a later warning from fpsimd_restore_current_state(), e.g. WARNING: CPU: 0 PID: 7214 at arch/arm64/kernel/fpsimd.c:383 fpsimd_restore_current_state+0x50c/0x748 When this happens, fpsimd_restore_current_state() will set TIF_SVE, placing the task into the correct state. This occurs before any other check of TIF_SVE can possibly occur, as other checks of TIF_SVE only happen while the FPSIMD/SVE/SME state is live. Thus, aside from the warning, there is no functional issue. This bug was introduced during rework to error handling in commit: 9f8bf718f2923 ("arm64/fpsimd: ptrace: Gracefully handle errors") ... where the setting of TIF_SVE was moved into a block which is only executed when system_supports_sme() is true. Fix this by removing the system_supports_sme() check. This ensures that TIF_SVE is set for (SVE-formatted) writes to NT_ARM_SVE, at the cost of unconditionally manipulating the tracee's saved svcr value. The manipulation of svcr is benign and inexpensive, and we already do similar elsewhere (e.g. during signal handling), so I don't think it's worth guarding this with system_supports_sme() checks. Aside from the above, there is no functional change. The 'type' argument to sve_set_common() is only set to ARM64_VEC_SME (in ssve_set())) when system_supports_sme(), so the ARM64_VEC_SME case in the switch statement is still unreachable when !system_supports_sme(). When CONFIG_ARM64_SME=n, the only caller of sve_set_common() is sve_set(), and the compiler can constant-fold for the case where type is ARM64_VEC_SVE, removing the logic for other cases. Reported-by: syzbot+d4ab35af21e99d07ce67@syzkaller.appspotmail.com Fixes: 9f8bf718f292 ("arm64/fpsimd: ptrace: Gracefully handle errors") Signed-off-by: Mark Rutland Cc: Cc: Mark Brown Cc: Will Deacon Reviewed-by: Mark Brown Signed-off-by: Catalin Marinas Signed-off-by: Greg Kroah-Hartman --- arch/arm64/kernel/ptrace.c | 26 ++++++++++++-------------- 1 file changed, 12 insertions(+), 14 deletions(-) diff --git a/arch/arm64/kernel/ptrace.c b/arch/arm64/kernel/ptrace.c index 4b001121c72dbe..fd1ba43f2005a9 100644 --- a/arch/arm64/kernel/ptrace.c +++ b/arch/arm64/kernel/ptrace.c @@ -942,20 +942,18 @@ static int sve_set_common(struct task_struct *target, vq = sve_vq_from_vl(task_get_vl(target, type)); /* Enter/exit streaming mode */ - if (system_supports_sme()) { - switch (type) { - case ARM64_VEC_SVE: - target->thread.svcr &= ~SVCR_SM_MASK; - set_tsk_thread_flag(target, TIF_SVE); - break; - case ARM64_VEC_SME: - target->thread.svcr |= SVCR_SM_MASK; - set_tsk_thread_flag(target, TIF_SME); - break; - default: - WARN_ON_ONCE(1); - return -EINVAL; - } + switch (type) { + case ARM64_VEC_SVE: + target->thread.svcr &= ~SVCR_SM_MASK; + set_tsk_thread_flag(target, TIF_SVE); + break; + case ARM64_VEC_SME: + target->thread.svcr |= SVCR_SM_MASK; + set_tsk_thread_flag(target, TIF_SME); + break; + default: + WARN_ON_ONCE(1); + return -EINVAL; } /* Always zero V regs, FPSR, and FPCR */ From 70f7f54566afc23f2c71bf1411af81f5d8009e0f Mon Sep 17 00:00:00 2001 From: Mark Rutland Date: Tue, 20 Jan 2026 14:51:06 +0000 Subject: [PATCH 2801/3902] arm64/fpsimd: signal: Allocate SSVE storage when restoring ZA commit ea8ccfddbce0bee6310da4f3fc560ad520f5e6b4 upstream. The code to restore a ZA context doesn't attempt to allocate the task's sve_state before setting TIF_SME. Consequently, restoring a ZA context can place a task into an invalid state where TIF_SME is set but the task's sve_state is NULL. In legitimate but uncommon cases where the ZA signal context was NOT created by the kernel in the context of the same task (e.g. if the task is saved/restored with something like CRIU), we have no guarantee that sve_state had been allocated previously. In these cases, userspace can enter streaming mode without trapping while sve_state is NULL, causing a later NULL pointer dereference when the kernel attempts to store the register state: | # ./sigreturn-za | Unable to handle kernel NULL pointer dereference at virtual address 0000000000000000 | Mem abort info: | ESR = 0x0000000096000046 | EC = 0x25: DABT (current EL), IL = 32 bits | SET = 0, FnV = 0 | EA = 0, S1PTW = 0 | FSC = 0x06: level 2 translation fault | Data abort info: | ISV = 0, ISS = 0x00000046, ISS2 = 0x00000000 | CM = 0, WnR = 1, TnD = 0, TagAccess = 0 | GCS = 0, Overlay = 0, DirtyBit = 0, Xs = 0 | user pgtable: 4k pages, 52-bit VAs, pgdp=0000000101f47c00 | [0000000000000000] pgd=08000001021d8403, p4d=0800000102274403, pud=0800000102275403, pmd=0000000000000000 | Internal error: Oops: 0000000096000046 [#1] SMP | Modules linked in: | CPU: 0 UID: 0 PID: 153 Comm: sigreturn-za Not tainted 6.19.0-rc1 #1 PREEMPT | Hardware name: linux,dummy-virt (DT) | pstate: 214000c9 (nzCv daIF +PAN -UAO -TCO +DIT -SSBS BTYPE=--) | pc : sve_save_state+0x4/0xf0 | lr : fpsimd_save_user_state+0xb0/0x1c0 | sp : ffff80008070bcc0 | x29: ffff80008070bcc0 x28: fff00000c1ca4c40 x27: 63cfa172fb5cf658 | x26: fff00000c1ca5228 x25: 0000000000000000 x24: 0000000000000000 | x23: 0000000000000000 x22: fff00000c1ca4c40 x21: fff00000c1ca4c40 | x20: 0000000000000020 x19: fff00000ff6900f0 x18: 0000000000000000 | x17: fff05e8e0311f000 x16: 0000000000000000 x15: 028fca8f3bdaf21c | x14: 0000000000000212 x13: fff00000c0209f10 x12: 0000000000000020 | x11: 0000000000200b20 x10: 0000000000000000 x9 : fff00000ff69dcc0 | x8 : 00000000000003f2 x7 : 0000000000000001 x6 : fff00000c1ca5b48 | x5 : fff05e8e0311f000 x4 : 0000000008000000 x3 : 0000000000000000 | x2 : 0000000000000001 x1 : fff00000c1ca5970 x0 : 0000000000000440 | Call trace: | sve_save_state+0x4/0xf0 (P) | fpsimd_thread_switch+0x48/0x198 | __switch_to+0x20/0x1c0 | __schedule+0x36c/0xce0 | schedule+0x34/0x11c | exit_to_user_mode_loop+0x124/0x188 | el0_interrupt+0xc8/0xd8 | __el0_irq_handler_common+0x18/0x24 | el0t_64_irq_handler+0x10/0x1c | el0t_64_irq+0x198/0x19c | Code: 54000040 d51b4408 d65f03c0 d503245f (e5bb5800) | ---[ end trace 0000000000000000 ]--- Fix this by having restore_za_context() ensure that the task's sve_state is allocated, matching what we do when taking an SME trap. Any live SVE/SSVE state (which is restored earlier from a separate signal context) must be preserved, and hence this is not zeroed. Fixes: 39782210eb7e ("arm64/sme: Implement ZA signal handling") Signed-off-by: Mark Rutland Cc: Cc: Mark Brown Cc: Will Deacon Reviewed-by: Mark Brown Signed-off-by: Catalin Marinas Signed-off-by: Greg Kroah-Hartman --- arch/arm64/kernel/signal.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/arch/arm64/kernel/signal.c b/arch/arm64/kernel/signal.c index 1110eeb21f572d..9c2e26e01d7292 100644 --- a/arch/arm64/kernel/signal.c +++ b/arch/arm64/kernel/signal.c @@ -576,6 +576,10 @@ static int restore_za_context(struct user_ctxs *user) if (user->za_size < ZA_SIG_CONTEXT_SIZE(vq)) return -EINVAL; + sve_alloc(current, false); + if (!current->thread.sve_state) + return -ENOMEM; + sme_alloc(current, true); if (!current->thread.sme_state) { current->thread.svcr &= ~SVCR_ZA_MASK; From 7b5a52cf252a0d2e89787b645290ad288878f332 Mon Sep 17 00:00:00 2001 From: Mark Rutland Date: Tue, 20 Jan 2026 14:51:07 +0000 Subject: [PATCH 2802/3902] arm64/fpsimd: signal: Fix restoration of SVE context commit d2907cbe9ea0a54cbe078076f9d089240ee1e2d9 upstream. When SME is supported, Restoring SVE signal context can go wrong in a few ways, including placing the task into an invalid state where the kernel may read from out-of-bounds memory (and may potentially take a fatal fault) and/or may kill the task with a SIGKILL. (1) Restoring a context with SVE_SIG_FLAG_SM set can place the task into an invalid state where SVCR.SM is set (and sve_state is non-NULL) but TIF_SME is clear, consequently resuting in out-of-bounds memory reads and/or killing the task with SIGKILL. This can only occur in unusual (but legitimate) cases where the SVE signal context has either been modified by userspace or was saved in the context of another task (e.g. as with CRIU), as otherwise the presence of an SVE signal context with SVE_SIG_FLAG_SM implies that TIF_SME is already set. While in this state, task_fpsimd_load() will NOT configure SMCR_ELx (leaving some arbitrary value configured in hardware) before restoring SVCR and attempting to restore the streaming mode SVE registers from memory via sve_load_state(). As the value of SMCR_ELx.LEN may be larger than the task's streaming SVE vector length, this may read memory outside of the task's allocated sve_state, reading unrelated data and/or triggering a fault. While this can result in secrets being loaded into streaming SVE registers, these values are never exposed. As TIF_SME is clear, fpsimd_bind_task_to_cpu() will configure CPACR_ELx.SMEN to trap EL0 accesses to streaming mode SVE registers, so these cannot be accessed directly at EL0. As fpsimd_save_user_state() verifies the live vector length before saving (S)SVE state to memory, no secret values can be saved back to memory (and hence cannot be observed via ptrace, signals, etc). When the live vector length doesn't match the expected vector length for the task, fpsimd_save_user_state() will send a fatal SIGKILL signal to the task. Hence the task may be killed after executing userspace for some period of time. (2) Restoring a context with SVE_SIG_FLAG_SM clear does not clear the task's SVCR.SM. If SVCR.SM was set prior to restoring the context, then the task will be left in streaming mode unexpectedly, and some register state will be combined inconsistently, though the task will be left in legitimate state from the kernel's PoV. This can only occur in unusual (but legitimate) cases where ptrace has been used to set SVCR.SM after entry to the sigreturn syscall, as syscall entry clears SVCR.SM. In these cases, the the provided SVE register data will be loaded into the task's sve_state using the non-streaming SVE vector length and the FPSIMD registers will be merged into this using the streaming SVE vector length. Fix (1) by setting TIF_SME when setting SVCR.SM. This also requires ensuring that the task's sme_state has been allocated, but as this could contain live ZA state, it should not be zeroed. Fix (2) by clearing SVCR.SM when restoring a SVE signal context with SVE_SIG_FLAG_SM clear. For consistency, I've pulled the manipulation of SVCR, TIF_SVE, TIF_SME, and fp_type earlier, immediately after the allocation of sve_state/sme_state, before the restore of the actual register state. This makes it easier to ensure that these are always modified consistently, even if a fault is taken while reading the register data from the signal context. I do not expect any software to depend on the exact state restored when a fault is taken while reading the context. Fixes: 85ed24dad290 ("arm64/sme: Implement streaming SVE signal handling") Signed-off-by: Mark Rutland Cc: Cc: Mark Brown Cc: Will Deacon Reviewed-by: Mark Brown Signed-off-by: Catalin Marinas Signed-off-by: Greg Kroah-Hartman --- arch/arm64/kernel/signal.c | 22 ++++++++++++++++------ 1 file changed, 16 insertions(+), 6 deletions(-) diff --git a/arch/arm64/kernel/signal.c b/arch/arm64/kernel/signal.c index 9c2e26e01d7292..08ffc5a5aea4cc 100644 --- a/arch/arm64/kernel/signal.c +++ b/arch/arm64/kernel/signal.c @@ -449,12 +449,28 @@ static int restore_sve_fpsimd_context(struct user_ctxs *user) if (user->sve_size < SVE_SIG_CONTEXT_SIZE(vq)) return -EINVAL; + if (sm) { + sme_alloc(current, false); + if (!current->thread.sme_state) + return -ENOMEM; + } + sve_alloc(current, true); if (!current->thread.sve_state) { clear_thread_flag(TIF_SVE); return -ENOMEM; } + if (sm) { + current->thread.svcr |= SVCR_SM_MASK; + set_thread_flag(TIF_SME); + } else { + current->thread.svcr &= ~SVCR_SM_MASK; + set_thread_flag(TIF_SVE); + } + + current->thread.fp_type = FP_STATE_SVE; + err = __copy_from_user(current->thread.sve_state, (char __user const *)user->sve + SVE_SIG_REGS_OFFSET, @@ -462,12 +478,6 @@ static int restore_sve_fpsimd_context(struct user_ctxs *user) if (err) return -EFAULT; - if (flags & SVE_SIG_FLAG_SM) - current->thread.svcr |= SVCR_SM_MASK; - else - set_thread_flag(TIF_SVE); - current->thread.fp_type = FP_STATE_SVE; - err = read_fpsimd_context(&fpsimd, user); if (err) return err; From 6e32070d29d1a35d8f4b3c03babf6c0e5efd1d08 Mon Sep 17 00:00:00 2001 From: Zhaoyang Huang Date: Thu, 22 Jan 2026 19:49:25 +0800 Subject: [PATCH 2803/3902] arm64: Set __nocfi on swsusp_arch_resume() commit e2f8216ca2d8e61a23cb6ec355616339667e0ba6 upstream. A DABT is reported[1] on an android based system when resume from hiberate. This happens because swsusp_arch_suspend_exit() is marked with SYM_CODE_*() and does not have a CFI hash, but swsusp_arch_resume() will attempt to verify the CFI hash when calling a copy of swsusp_arch_suspend_exit(). Given that there's an existing requirement that the entrypoint to swsusp_arch_suspend_exit() is the first byte of the .hibernate_exit.text section, we cannot fix this by marking swsusp_arch_suspend_exit() with SYM_FUNC_*(). The simplest fix for now is to disable the CFI check in swsusp_arch_resume(). Mark swsusp_arch_resume() as __nocfi to disable the CFI check. [1] [ 22.991934][ T1] Unable to handle kernel paging request at virtual address 0000000109170ffc [ 22.991934][ T1] Mem abort info: [ 22.991934][ T1] ESR = 0x0000000096000007 [ 22.991934][ T1] EC = 0x25: DABT (current EL), IL = 32 bits [ 22.991934][ T1] SET = 0, FnV = 0 [ 22.991934][ T1] EA = 0, S1PTW = 0 [ 22.991934][ T1] FSC = 0x07: level 3 translation fault [ 22.991934][ T1] Data abort info: [ 22.991934][ T1] ISV = 0, ISS = 0x00000007, ISS2 = 0x00000000 [ 22.991934][ T1] CM = 0, WnR = 0, TnD = 0, TagAccess = 0 [ 22.991934][ T1] GCS = 0, Overlay = 0, DirtyBit = 0, Xs = 0 [ 22.991934][ T1] [0000000109170ffc] user address but active_mm is swapper [ 22.991934][ T1] Internal error: Oops: 0000000096000007 [#1] PREEMPT SMP [ 22.991934][ T1] Dumping ftrace buffer: [ 22.991934][ T1] (ftrace buffer empty) [ 22.991934][ T1] Modules linked in: [ 22.991934][ T1] CPU: 0 PID: 1 Comm: swapper/0 Not tainted 6.6.98-android15-8-g0b1d2aee7fc3-dirty-4k #1 688c7060a825a3ac418fe53881730b355915a419 [ 22.991934][ T1] Hardware name: Unisoc UMS9360-base Board (DT) [ 22.991934][ T1] pstate: 804000c5 (Nzcv daIF +PAN -UAO -TCO -DIT -SSBS BTYPE=--) [ 22.991934][ T1] pc : swsusp_arch_resume+0x2ac/0x344 [ 22.991934][ T1] lr : swsusp_arch_resume+0x294/0x344 [ 22.991934][ T1] sp : ffffffc08006b960 [ 22.991934][ T1] x29: ffffffc08006b9c0 x28: 0000000000000000 x27: 0000000000000000 [ 22.991934][ T1] x26: 0000000000000000 x25: 0000000000000000 x24: 0000000000000820 [ 22.991934][ T1] x23: ffffffd0817e3000 x22: ffffffd0817e3000 x21: 0000000000000000 [ 22.991934][ T1] x20: ffffff8089171000 x19: ffffffd08252c8c8 x18: ffffffc080061058 [ 22.991934][ T1] x17: 00000000529c6ef0 x16: 00000000529c6ef0 x15: 0000000000000004 [ 22.991934][ T1] x14: ffffff8178c88000 x13: 0000000000000006 x12: 0000000000000000 [ 22.991934][ T1] x11: 0000000000000015 x10: 0000000000000001 x9 : ffffffd082533000 [ 22.991934][ T1] x8 : 0000000109171000 x7 : 205b5d3433393139 x6 : 392e32322020205b [ 22.991934][ T1] x5 : 000000010916f000 x4 : 000000008164b000 x3 : ffffff808a4e0530 [ 22.991934][ T1] x2 : ffffffd08058e784 x1 : 0000000082326000 x0 : 000000010a283000 [ 22.991934][ T1] Call trace: [ 22.991934][ T1] swsusp_arch_resume+0x2ac/0x344 [ 22.991934][ T1] hibernation_restore+0x158/0x18c [ 22.991934][ T1] load_image_and_restore+0xb0/0xec [ 22.991934][ T1] software_resume+0xf4/0x19c [ 22.991934][ T1] software_resume_initcall+0x34/0x78 [ 22.991934][ T1] do_one_initcall+0xe8/0x370 [ 22.991934][ T1] do_initcall_level+0xc8/0x19c [ 22.991934][ T1] do_initcalls+0x70/0xc0 [ 22.991934][ T1] do_basic_setup+0x1c/0x28 [ 22.991934][ T1] kernel_init_freeable+0xe0/0x148 [ 22.991934][ T1] kernel_init+0x20/0x1a8 [ 22.991934][ T1] ret_from_fork+0x10/0x20 [ 22.991934][ T1] Code: a9400a61 f94013e0 f9438923 f9400a64 (b85fc110) Co-developed-by: Jeson Gao Signed-off-by: Jeson Gao Signed-off-by: Zhaoyang Huang Acked-by: Will Deacon Acked-by: Mark Rutland Cc: [catalin.marinas@arm.com: commit log updated by Mark Rutland] Signed-off-by: Catalin Marinas Signed-off-by: Greg Kroah-Hartman --- arch/arm64/kernel/hibernate.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/arch/arm64/kernel/hibernate.c b/arch/arm64/kernel/hibernate.c index 18749e9a6c2da4..9717568518ba74 100644 --- a/arch/arm64/kernel/hibernate.c +++ b/arch/arm64/kernel/hibernate.c @@ -402,7 +402,7 @@ int swsusp_arch_suspend(void) * Memory allocated by get_safe_page() will be dealt with by the hibernate code, * we don't need to free it here. */ -int swsusp_arch_resume(void) +int __nocfi swsusp_arch_resume(void) { int rc; void *zero_page; From d1943bc9dc9508f5933788a76f8a35d10e43a646 Mon Sep 17 00:00:00 2001 From: Thomas Fourier Date: Fri, 9 Jan 2026 11:38:39 +0100 Subject: [PATCH 2804/3902] ksmbd: smbd: fix dma_unmap_sg() nents commit 98e3e2b561bc88f4dd218d1c05890672874692f6 upstream. The dma_unmap_sg() functions should be called with the same nents as the dma_map_sg(), not the value the map function returned. Fixes: 0626e6641f6b ("cifsd: add server handler for central processing and tranport layers") Cc: Signed-off-by: Thomas Fourier Acked-by: Namjae Jeon Signed-off-by: Steve French Signed-off-by: Greg Kroah-Hartman --- fs/smb/server/transport_rdma.c | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/fs/smb/server/transport_rdma.c b/fs/smb/server/transport_rdma.c index 4e7ab8d9314f61..7d2ad73839e860 100644 --- a/fs/smb/server/transport_rdma.c +++ b/fs/smb/server/transport_rdma.c @@ -1251,14 +1251,12 @@ static int get_sg_list(void *buf, int size, struct scatterlist *sg_list, int nen static int get_mapped_sg_list(struct ib_device *device, void *buf, int size, struct scatterlist *sg_list, int nentries, - enum dma_data_direction dir) + enum dma_data_direction dir, int *npages) { - int npages; - - npages = get_sg_list(buf, size, sg_list, nentries); - if (npages < 0) + *npages = get_sg_list(buf, size, sg_list, nentries); + if (*npages < 0) return -EINVAL; - return ib_dma_map_sg(device, sg_list, npages, dir); + return ib_dma_map_sg(device, sg_list, *npages, dir); } static int post_sendmsg(struct smbdirect_socket *sc, @@ -1329,12 +1327,13 @@ static int smb_direct_post_send_data(struct smbdirect_socket *sc, for (i = 0; i < niov; i++) { struct ib_sge *sge; int sg_cnt; + int npages; sg_init_table(sg, SMBDIRECT_SEND_IO_MAX_SGE - 1); sg_cnt = get_mapped_sg_list(sc->ib.dev, iov[i].iov_base, iov[i].iov_len, sg, SMBDIRECT_SEND_IO_MAX_SGE - 1, - DMA_TO_DEVICE); + DMA_TO_DEVICE, &npages); if (sg_cnt <= 0) { pr_err("failed to map buffer\n"); ret = -ENOMEM; @@ -1342,7 +1341,7 @@ static int smb_direct_post_send_data(struct smbdirect_socket *sc, } else if (sg_cnt + msg->num_sge > SMBDIRECT_SEND_IO_MAX_SGE) { pr_err("buffer not fitted into sges\n"); ret = -E2BIG; - ib_dma_unmap_sg(sc->ib.dev, sg, sg_cnt, + ib_dma_unmap_sg(sc->ib.dev, sg, npages, DMA_TO_DEVICE); goto err; } From b8a26393419514bf7f1dc9ab13c55ecb9db2363d Mon Sep 17 00:00:00 2001 From: Thomas Fourier Date: Wed, 14 Jan 2026 13:31:06 +0100 Subject: [PATCH 2805/3902] octeontx2: Fix otx2_dma_map_page() error return code commit d998b0e5afffa90d0f03770bad31083767079858 upstream. 0 is a valid DMA address [1] so using it as the error value can lead to errors. The error value of dma_map_XXX() functions is DMA_MAPPING_ERROR which is ~0. The callers of otx2_dma_map_page() use dma_mapping_error() to test the return value of otx2_dma_map_page(). This means that they would not detect an error in otx2_dma_map_page(). Make otx2_dma_map_page() return the raw value of dma_map_page_attrs(). [1] https://lore.kernel.org/all/f977f68b-cec5-4ab7-b4bd-2cf6aca46267@intel.com Fixes: caa2da34fd25 ("octeontx2-pf: Initialize and config queues") Cc: Signed-off-by: Thomas Fourier Link: https://patch.msgid.link/20260114123107.42387-2-fourier.thomas@gmail.com Signed-off-by: Jakub Kicinski Signed-off-by: Greg Kroah-Hartman --- drivers/net/ethernet/marvell/octeontx2/nic/otx2_common.h | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/drivers/net/ethernet/marvell/octeontx2/nic/otx2_common.h b/drivers/net/ethernet/marvell/octeontx2/nic/otx2_common.h index 1c8a3c078a647d..844ad55dd34b83 100644 --- a/drivers/net/ethernet/marvell/octeontx2/nic/otx2_common.h +++ b/drivers/net/ethernet/marvell/octeontx2/nic/otx2_common.h @@ -934,13 +934,8 @@ static inline dma_addr_t otx2_dma_map_page(struct otx2_nic *pfvf, size_t offset, size_t size, enum dma_data_direction dir) { - dma_addr_t iova; - - iova = dma_map_page_attrs(pfvf->dev, page, + return dma_map_page_attrs(pfvf->dev, page, offset, size, dir, DMA_ATTR_SKIP_CPU_SYNC); - if (unlikely(dma_mapping_error(pfvf->dev, iova))) - return (dma_addr_t)NULL; - return iova; } static inline void otx2_dma_unmap_page(struct otx2_nic *pfvf, From 5f13c946ca449fb4991f42fa87706643ca83d1a7 Mon Sep 17 00:00:00 2001 From: Johan Hovold Date: Wed, 26 Nov 2025 15:53:25 +0100 Subject: [PATCH 2806/3902] slimbus: core: fix runtime PM imbalance on report present commit 0eb4ff6596114aabba1070a66afa2c2f5593739f upstream. Make sure to balance the runtime PM usage count in case slimbus device or address allocation fails on report present, which would otherwise prevent the controller from suspending. Fixes: 4b14e62ad3c9 ("slimbus: Add support for 'clock-pause' feature") Cc: stable@vger.kernel.org # 4.16 Signed-off-by: Johan Hovold Link: https://patch.msgid.link/20251126145329.5022-3-johan@kernel.org Signed-off-by: Greg Kroah-Hartman --- drivers/slimbus/core.c | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/drivers/slimbus/core.c b/drivers/slimbus/core.c index 005fa2ef100f52..617aac175118cc 100644 --- a/drivers/slimbus/core.c +++ b/drivers/slimbus/core.c @@ -489,21 +489,23 @@ int slim_device_report_present(struct slim_controller *ctrl, if (ctrl->sched.clk_state != SLIM_CLK_ACTIVE) { dev_err(ctrl->dev, "slim ctrl not active,state:%d, ret:%d\n", ctrl->sched.clk_state, ret); - goto slimbus_not_active; + goto out_put_rpm; } sbdev = slim_get_device(ctrl, e_addr); - if (IS_ERR(sbdev)) - return -ENODEV; + if (IS_ERR(sbdev)) { + ret = -ENODEV; + goto out_put_rpm; + } if (sbdev->is_laddr_valid) { *laddr = sbdev->laddr; - return 0; + ret = 0; + } else { + ret = slim_device_alloc_laddr(sbdev, true); } - ret = slim_device_alloc_laddr(sbdev, true); - -slimbus_not_active: +out_put_rpm: pm_runtime_mark_last_busy(ctrl->dev); pm_runtime_put_autosuspend(ctrl->dev); return ret; From 6602bb4d1338e92b5838e50322b87697bdbd2ee0 Mon Sep 17 00:00:00 2001 From: Johan Hovold Date: Wed, 26 Nov 2025 15:53:26 +0100 Subject: [PATCH 2807/3902] slimbus: core: fix device reference leak on report present commit 9391380eb91ea5ac792aae9273535c8da5b9aa01 upstream. Slimbus devices can be allocated dynamically upon reception of report-present messages. Make sure to drop the reference taken when looking up already registered devices. Note that this requires taking an extra reference in case the device has not yet been registered and has to be allocated. Fixes: 46a2bb5a7f7e ("slimbus: core: Add slim controllers support") Cc: stable@vger.kernel.org # 4.16 Signed-off-by: Johan Hovold Link: https://patch.msgid.link/20251126145329.5022-4-johan@kernel.org Signed-off-by: Greg Kroah-Hartman --- drivers/slimbus/core.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/drivers/slimbus/core.c b/drivers/slimbus/core.c index 617aac175118cc..0992ca35cef498 100644 --- a/drivers/slimbus/core.c +++ b/drivers/slimbus/core.c @@ -379,6 +379,8 @@ struct slim_device *slim_get_device(struct slim_controller *ctrl, sbdev = slim_alloc_device(ctrl, e_addr, NULL); if (!sbdev) return ERR_PTR(-ENOMEM); + + get_device(&sbdev->dev); } return sbdev; @@ -505,6 +507,7 @@ int slim_device_report_present(struct slim_controller *ctrl, ret = slim_device_alloc_laddr(sbdev, true); } + put_device(&sbdev->dev); out_put_rpm: pm_runtime_mark_last_busy(ctrl->dev); pm_runtime_put_autosuspend(ctrl->dev); From 3b90d099efa2b67239bd3b3dc3521ec584261748 Mon Sep 17 00:00:00 2001 From: Steven Rostedt Date: Thu, 22 Jan 2026 19:48:24 -0500 Subject: [PATCH 2808/3902] tracing: Fix crash on synthetic stacktrace field usage commit 90f9f5d64cae4e72defd96a2a22760173cb3c9ec upstream. When creating a synthetic event based on an existing synthetic event that had a stacktrace field and the new synthetic event used that field a kernel crash occurred: ~# cd /sys/kernel/tracing ~# echo 's:stack unsigned long stack[];' > dynamic_events ~# echo 'hist:keys=prev_pid:s0=common_stacktrace if prev_state & 3' >> events/sched/sched_switch/trigger ~# echo 'hist:keys=next_pid:s1=$s0:onmatch(sched.sched_switch).trace(stack,$s1)' >> events/sched/sched_switch/trigger The above creates a synthetic event that takes a stacktrace when a task schedules out in a non-running state and passes that stacktrace to the sched_switch event when that task schedules back in. It triggers the "stack" synthetic event that has a stacktrace as its field (called "stack"). ~# echo 's:syscall_stack s64 id; unsigned long stack[];' >> dynamic_events ~# echo 'hist:keys=common_pid:s2=stack' >> events/synthetic/stack/trigger ~# echo 'hist:keys=common_pid:s3=$s2,i0=id:onmatch(synthetic.stack).trace(syscall_stack,$i0,$s3)' >> events/raw_syscalls/sys_exit/trigger The above makes another synthetic event called "syscall_stack" that attaches the first synthetic event (stack) to the sys_exit trace event and records the stacktrace from the stack event with the id of the system call that is exiting. When enabling this event (or using it in a historgram): ~# echo 1 > events/synthetic/syscall_stack/enable Produces a kernel crash! BUG: unable to handle page fault for address: 0000000000400010 #PF: supervisor read access in kernel mode #PF: error_code(0x0000) - not-present page PGD 0 P4D 0 Oops: Oops: 0000 [#1] SMP PTI CPU: 6 UID: 0 PID: 1257 Comm: bash Not tainted 6.16.3+deb14-amd64 #1 PREEMPT(lazy) Debian 6.16.3-1 Hardware name: QEMU Standard PC (Q35 + ICH9, 2009), BIOS 1.17.0-debian-1.17.0-1 04/01/2014 RIP: 0010:trace_event_raw_event_synth+0x90/0x380 Code: c5 00 00 00 00 85 d2 0f 84 e1 00 00 00 31 db eb 34 0f 1f 00 66 66 2e 0f 1f 84 00 00 00 00 00 66 66 2e 0f 1f 84 00 00 00 00 00 <49> 8b 04 24 48 83 c3 01 8d 0c c5 08 00 00 00 01 cd 41 3b 5d 40 0f RSP: 0018:ffffd2670388f958 EFLAGS: 00010202 RAX: ffff8ba1065cc100 RBX: 0000000000000000 RCX: 0000000000000000 RDX: 0000000000000001 RSI: fffff266ffda7b90 RDI: ffffd2670388f9b0 RBP: 0000000000000010 R08: ffff8ba104e76000 R09: ffffd2670388fa50 R10: ffff8ba102dd42e0 R11: ffffffff9a908970 R12: 0000000000400010 R13: ffff8ba10a246400 R14: ffff8ba10a710220 R15: fffff266ffda7b90 FS: 00007fa3bc63f740(0000) GS:ffff8ba2e0f48000(0000) knlGS:0000000000000000 CS: 0010 DS: 0000 ES: 0000 CR0: 0000000080050033 CR2: 0000000000400010 CR3: 0000000107f9e003 CR4: 0000000000172ef0 Call Trace: ? __tracing_map_insert+0x208/0x3a0 action_trace+0x67/0x70 event_hist_trigger+0x633/0x6d0 event_triggers_call+0x82/0x130 trace_event_buffer_commit+0x19d/0x250 trace_event_raw_event_sys_exit+0x62/0xb0 syscall_exit_work+0x9d/0x140 do_syscall_64+0x20a/0x2f0 ? trace_event_raw_event_sched_switch+0x12b/0x170 ? save_fpregs_to_fpstate+0x3e/0x90 ? _raw_spin_unlock+0xe/0x30 ? finish_task_switch.isra.0+0x97/0x2c0 ? __rseq_handle_notify_resume+0xad/0x4c0 ? __schedule+0x4b8/0xd00 ? restore_fpregs_from_fpstate+0x3c/0x90 ? switch_fpu_return+0x5b/0xe0 ? do_syscall_64+0x1ef/0x2f0 ? do_fault+0x2e9/0x540 ? __handle_mm_fault+0x7d1/0xf70 ? count_memcg_events+0x167/0x1d0 ? handle_mm_fault+0x1d7/0x2e0 ? do_user_addr_fault+0x2c3/0x7f0 entry_SYSCALL_64_after_hwframe+0x76/0x7e The reason is that the stacktrace field is not labeled as such, and is treated as a normal field and not as a dynamic event that it is. In trace_event_raw_event_synth() the event is field is still treated as a dynamic array, but the retrieval of the data is considered a normal field, and the reference is just the meta data: // Meta data is retrieved instead of a dynamic array str_val = (char *)(long)var_ref_vals[val_idx]; // Then when it tries to process it: len = *((unsigned long *)str_val) + 1; It triggers a kernel page fault. To fix this, first when defining the fields of the first synthetic event, set the filter type to FILTER_STACKTRACE. This is used later by the second synthetic event to know that this field is a stacktrace. When creating the field of the new synthetic event, have it use this FILTER_STACKTRACE to know to create a stacktrace field to copy the stacktrace into. Cc: stable@vger.kernel.org Cc: Masami Hiramatsu Cc: Mathieu Desnoyers Cc: Tom Zanussi Link: https://patch.msgid.link/20260122194824.6905a38e@gandalf.local.home Fixes: 00cf3d672a9d ("tracing: Allow synthetic events to pass around stacktraces") Signed-off-by: Steven Rostedt (Google) Signed-off-by: Greg Kroah-Hartman --- kernel/trace/trace_events_hist.c | 9 +++++++++ kernel/trace/trace_events_synth.c | 8 +++++++- 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/kernel/trace/trace_events_hist.c b/kernel/trace/trace_events_hist.c index 6bfaf1210dd24e..425ae26064bab0 100644 --- a/kernel/trace/trace_events_hist.c +++ b/kernel/trace/trace_events_hist.c @@ -2057,6 +2057,15 @@ static struct hist_field *create_hist_field(struct hist_trigger_data *hist_data, hist_field->fn_num = HIST_FIELD_FN_RELDYNSTRING; else hist_field->fn_num = HIST_FIELD_FN_PSTRING; + } else if (field->filter_type == FILTER_STACKTRACE) { + flags |= HIST_FIELD_FL_STACKTRACE; + + hist_field->size = MAX_FILTER_STR_VAL; + hist_field->type = kstrdup_const(field->type, GFP_KERNEL); + if (!hist_field->type) + goto free; + + hist_field->fn_num = HIST_FIELD_FN_STACK; } else { hist_field->size = field->size; hist_field->is_signed = field->is_signed; diff --git a/kernel/trace/trace_events_synth.c b/kernel/trace/trace_events_synth.c index 8e1524b8667a73..934dafbb009e59 100644 --- a/kernel/trace/trace_events_synth.c +++ b/kernel/trace/trace_events_synth.c @@ -130,7 +130,9 @@ static int synth_event_define_fields(struct trace_event_call *call) struct synth_event *event = call->data; unsigned int i, size, n_u64; char *name, *type; + int filter_type; bool is_signed; + bool is_stack; int ret = 0; for (i = 0, n_u64 = 0; i < event->n_fields; i++) { @@ -138,8 +140,12 @@ static int synth_event_define_fields(struct trace_event_call *call) is_signed = event->fields[i]->is_signed; type = event->fields[i]->type; name = event->fields[i]->name; + is_stack = event->fields[i]->is_stack; + + filter_type = is_stack ? FILTER_STACKTRACE : FILTER_OTHER; + ret = trace_define_field(call, type, name, offset, size, - is_signed, FILTER_OTHER); + is_signed, filter_type); if (ret) break; From f9b059bda4276f2bb72cb98ec7875a747f042ea2 Mon Sep 17 00:00:00 2001 From: Johan Hovold Date: Mon, 8 Dec 2025 16:35:23 +0100 Subject: [PATCH 2809/3902] intel_th: fix device leak on output open() commit 95fc36a234da24bbc5f476f8104a5a15f99ed3e3 upstream. Make sure to drop the reference taken when looking up the th device during output device open() on errors and on close(). Note that a recent commit fixed the leak in a couple of open() error paths but not all of them, and the reference is still leaking on successful open(). Fixes: 39f4034693b7 ("intel_th: Add driver infrastructure for Intel(R) Trace Hub devices") Fixes: 6d5925b667e4 ("intel_th: Fix error handling in intel_th_output_open") Cc: stable@vger.kernel.org # 4.4: 6d5925b667e4 Cc: Alexander Shishkin Cc: Ma Ke Signed-off-by: Johan Hovold Link: https://patch.msgid.link/20251208153524.68637-2-johan@kernel.org Signed-off-by: Greg Kroah-Hartman --- drivers/hwtracing/intel_th/core.c | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/drivers/hwtracing/intel_th/core.c b/drivers/hwtracing/intel_th/core.c index fdb9d022d8753f..e3a7ab112ea955 100644 --- a/drivers/hwtracing/intel_th/core.c +++ b/drivers/hwtracing/intel_th/core.c @@ -810,9 +810,12 @@ static int intel_th_output_open(struct inode *inode, struct file *file) int err; dev = bus_find_device_by_devt(&intel_th_bus, inode->i_rdev); - if (!dev || !dev->driver) { + if (!dev) + return -ENODEV; + + if (!dev->driver) { err = -ENODEV; - goto out_no_device; + goto out_put_device; } thdrv = to_intel_th_driver(dev->driver); @@ -836,12 +839,22 @@ static int intel_th_output_open(struct inode *inode, struct file *file) out_put_device: put_device(dev); -out_no_device: + return err; } +static int intel_th_output_release(struct inode *inode, struct file *file) +{ + struct intel_th_device *thdev = file->private_data; + + put_device(&thdev->dev); + + return 0; +} + static const struct file_operations intel_th_output_fops = { .open = intel_th_output_open, + .release = intel_th_output_release, .llseek = noop_llseek, }; From f1cb33a82818da98baf94a65756fa23c061207a4 Mon Sep 17 00:00:00 2001 From: Alexander Usyskin Date: Sun, 11 Jan 2026 16:51:25 +0200 Subject: [PATCH 2810/3902] mei: trace: treat reg parameter as string commit 06d5a7afe1d0b47102936d8fba568572c2b4b941 upstream. The commit afd2627f727b ("tracing: Check "%s" dereference via the field and not the TP_printk format") forbids to emit event with a plain char* without a wrapper. The reg parameter always passed as static string and wrapper is not strictly required, contrary to dev parameter. Use the string wrapper anyway to check sanity of the reg parameters, store it value independently and prevent internal kernel data leaks. Since some code refactoring has taken place, explicit backporting may be needed for kernels older than 6.10. Cc: stable@vger.kernel.org # v6.11+ Fixes: a0a927d06d79 ("mei: me: add io register tracing") Signed-off-by: Alexander Usyskin Link: https://patch.msgid.link/20260111145125.1754912-1-alexander.usyskin@intel.com Signed-off-by: Greg Kroah-Hartman --- drivers/misc/mei/mei-trace.h | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/drivers/misc/mei/mei-trace.h b/drivers/misc/mei/mei-trace.h index 5312edbf5190d7..24fa321d88bd8b 100644 --- a/drivers/misc/mei/mei-trace.h +++ b/drivers/misc/mei/mei-trace.h @@ -21,18 +21,18 @@ TRACE_EVENT(mei_reg_read, TP_ARGS(dev, reg, offs, val), TP_STRUCT__entry( __string(dev, dev_name(dev)) - __field(const char *, reg) + __string(reg, reg) __field(u32, offs) __field(u32, val) ), TP_fast_assign( __assign_str(dev); - __entry->reg = reg; + __assign_str(reg); __entry->offs = offs; __entry->val = val; ), TP_printk("[%s] read %s:[%#x] = %#x", - __get_str(dev), __entry->reg, __entry->offs, __entry->val) + __get_str(dev), __get_str(reg), __entry->offs, __entry->val) ); TRACE_EVENT(mei_reg_write, @@ -40,18 +40,18 @@ TRACE_EVENT(mei_reg_write, TP_ARGS(dev, reg, offs, val), TP_STRUCT__entry( __string(dev, dev_name(dev)) - __field(const char *, reg) + __string(reg, reg) __field(u32, offs) __field(u32, val) ), TP_fast_assign( __assign_str(dev); - __entry->reg = reg; + __assign_str(reg); __entry->offs = offs; __entry->val = val; ), TP_printk("[%s] write %s[%#x] = %#x", - __get_str(dev), __entry->reg, __entry->offs, __entry->val) + __get_str(dev), __get_str(reg), __entry->offs, __entry->val) ); TRACE_EVENT(mei_pci_cfg_read, @@ -59,18 +59,18 @@ TRACE_EVENT(mei_pci_cfg_read, TP_ARGS(dev, reg, offs, val), TP_STRUCT__entry( __string(dev, dev_name(dev)) - __field(const char *, reg) + __string(reg, reg) __field(u32, offs) __field(u32, val) ), TP_fast_assign( __assign_str(dev); - __entry->reg = reg; + __assign_str(reg); __entry->offs = offs; __entry->val = val; ), TP_printk("[%s] pci cfg read %s:[%#x] = %#x", - __get_str(dev), __entry->reg, __entry->offs, __entry->val) + __get_str(dev), __get_str(reg), __entry->offs, __entry->val) ); #endif /* _MEI_TRACE_H_ */ From ea1b2081d594b76cd431d46643c86121874e50ef Mon Sep 17 00:00:00 2001 From: Harald Freudenberger Date: Mon, 19 Jan 2026 10:37:28 +0100 Subject: [PATCH 2811/3902] s390/ap: Fix wrong APQN fill calculation commit 3317785a8803db629efc759d811d0f589d3a0b2d upstream. The upper limit of the firmware queue fill state for each APQN is reported by the hwinfo.qd field. This field shows the numbers 0-7 for 1-8 queue spaces available. But the exploiting code assumed the real boundary is stored there and thus stoppes queuing in messages one tick too early. Correct the limit calculation and thus offer a boost of 12.5% performance for high traffic on one APQN. Fixes: d4c53ae8e4948 ("s390/ap: store TAPQ hwinfo in struct ap_card") Cc: stable@vger.kernel.org Reported-by: Ingo Franzki Reviewed-by: Ingo Franzki Signed-off-by: Harald Freudenberger Signed-off-by: Heiko Carstens Signed-off-by: Greg Kroah-Hartman --- drivers/s390/crypto/ap_card.c | 2 +- drivers/s390/crypto/ap_queue.c | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/s390/crypto/ap_card.c b/drivers/s390/crypto/ap_card.c index ce953cbbd56403..64a7f645eaf49c 100644 --- a/drivers/s390/crypto/ap_card.c +++ b/drivers/s390/crypto/ap_card.c @@ -44,7 +44,7 @@ static ssize_t depth_show(struct device *dev, struct device_attribute *attr, { struct ap_card *ac = to_ap_card(dev); - return sysfs_emit(buf, "%d\n", ac->hwinfo.qd); + return sysfs_emit(buf, "%d\n", ac->hwinfo.qd + 1); } static DEVICE_ATTR_RO(depth); diff --git a/drivers/s390/crypto/ap_queue.c b/drivers/s390/crypto/ap_queue.c index 8977866fab1bcd..42ffb22aa1e2e7 100644 --- a/drivers/s390/crypto/ap_queue.c +++ b/drivers/s390/crypto/ap_queue.c @@ -269,7 +269,7 @@ static enum ap_sm_wait ap_sm_write(struct ap_queue *aq) list_move_tail(&ap_msg->list, &aq->pendingq); aq->requestq_count--; aq->pendingq_count++; - if (aq->queue_count < aq->card->hwinfo.qd) { + if (aq->queue_count < aq->card->hwinfo.qd + 1) { aq->sm_state = AP_SM_STATE_WORKING; return AP_SM_WAIT_AGAIN; } From 242cbb3df97af62c4c40db4420cba8326c298755 Mon Sep 17 00:00:00 2001 From: Alexander Egorenkov Date: Wed, 21 Jan 2026 14:59:50 +0100 Subject: [PATCH 2812/3902] s390/boot/vmlinux.lds.S: Ensure bzImage ends with SecureBoot trailer commit ddc6cbef3ef10359b5640b4ee810a520edc73586 upstream. Since commit 3e86e4d74c04 ("kbuild: keep .modinfo section in vmlinux.unstripped") the .modinfo section which has SHF_ALLOC ends up in bzImage after the SecureBoot trailer. This breaks SecureBoot because the bootloader can no longer find the SecureBoot trailer with kernel's signature at the expected location in bzImage. To fix the bug, move discarded sections before the ELF_DETAILS macro and discard the .modinfo section which is not needed by the decompressor. Fixes: 3e86e4d74c04 ("kbuild: keep .modinfo section in vmlinux.unstripped") Cc: stable@vger.kernel.org Suggested-by: Vasily Gorbik Reviewed-by: Vasily Gorbik Tested-by: Vasily Gorbik Signed-off-by: Alexander Egorenkov Signed-off-by: Heiko Carstens Signed-off-by: Greg Kroah-Hartman --- arch/s390/boot/vmlinux.lds.S | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/arch/s390/boot/vmlinux.lds.S b/arch/s390/boot/vmlinux.lds.S index 50988022f9ea32..070bc18babd0ea 100644 --- a/arch/s390/boot/vmlinux.lds.S +++ b/arch/s390/boot/vmlinux.lds.S @@ -137,6 +137,15 @@ SECTIONS } _end = .; + /* Sections to be discarded */ + /DISCARD/ : { + COMMON_DISCARDS + *(.eh_frame) + *(*__ksymtab*) + *(___kcrctab*) + *(.modinfo) + } + DWARF_DEBUG ELF_DETAILS @@ -161,12 +170,4 @@ SECTIONS *(.rela.*) *(.rela_*) } ASSERT(SIZEOF(.rela.dyn) == 0, "Unexpected run-time relocations (.rela) detected!") - - /* Sections to be discarded */ - /DISCARD/ : { - COMMON_DISCARDS - *(.eh_frame) - *(*__ksymtab*) - *(___kcrctab*) - } } From bd2393ed7712513e7e2dbcb6e21464a67ff9e702 Mon Sep 17 00:00:00 2001 From: Wenkai Lin Date: Tue, 2 Dec 2025 14:12:53 +0800 Subject: [PATCH 2813/3902] uacce: fix cdev handling in the cleanup path commit a3bece3678f6c88db1f44c602b2a63e84b4040ac upstream. When cdev_device_add fails, it internally releases the cdev memory, and if cdev_device_del is then executed, it will cause a hang error. To fix it, we check the return value of cdev_device_add() and clear uacce->cdev to avoid calling cdev_device_del in the uacce_remove. Fixes: 015d239ac014 ("uacce: add uacce driver") Cc: stable@vger.kernel.org Signed-off-by: Wenkai Lin Signed-off-by: Chenghai Huang Acked-by: Zhangfei Gao Link: https://patch.msgid.link/20251202061256.4158641-2-huangchenghai2@huawei.com Signed-off-by: Greg Kroah-Hartman Signed-off-by: Greg Kroah-Hartman --- drivers/misc/uacce/uacce.c | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/drivers/misc/uacce/uacce.c b/drivers/misc/uacce/uacce.c index 42e7d2a2a90c6e..43d215fb8c7375 100644 --- a/drivers/misc/uacce/uacce.c +++ b/drivers/misc/uacce/uacce.c @@ -519,6 +519,8 @@ EXPORT_SYMBOL_GPL(uacce_alloc); */ int uacce_register(struct uacce_device *uacce) { + int ret; + if (!uacce) return -ENODEV; @@ -529,7 +531,11 @@ int uacce_register(struct uacce_device *uacce) uacce->cdev->ops = &uacce_fops; uacce->cdev->owner = THIS_MODULE; - return cdev_device_add(uacce->cdev, &uacce->dev); + ret = cdev_device_add(uacce->cdev, &uacce->dev); + if (ret) + uacce->cdev = NULL; + + return ret; } EXPORT_SYMBOL_GPL(uacce_register); From 82821a681d5dcce31475a65190fc39ea8f372cc0 Mon Sep 17 00:00:00 2001 From: Chenghai Huang Date: Tue, 2 Dec 2025 14:12:54 +0800 Subject: [PATCH 2814/3902] uacce: fix isolate sysfs check condition commit 98eec349259b1fd876f350b1c600403bcef8f85d upstream. uacce supports the device isolation feature. If the driver implements the isolate_err_threshold_read and isolate_err_threshold_write callback functions, uacce will create sysfs files now. Users can read and configure the isolation policy through sysfs. Currently, sysfs files are created as long as either isolate_err_threshold_read or isolate_err_threshold_write callback functions are present. However, accessing a non-existent callback function may cause the system to crash. Therefore, intercept the creation of sysfs if neither read nor write exists; create sysfs if either is supported, but intercept unsupported operations at the call site. Fixes: e3e289fbc0b5 ("uacce: supports device isolation feature") Cc: stable@vger.kernel.org Signed-off-by: Chenghai Huang Acked-by: Zhangfei Gao Link: https://patch.msgid.link/20251202061256.4158641-3-huangchenghai2@huawei.com Signed-off-by: Greg Kroah-Hartman --- drivers/misc/uacce/uacce.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/drivers/misc/uacce/uacce.c b/drivers/misc/uacce/uacce.c index 43d215fb8c7375..b0b3c1562d526b 100644 --- a/drivers/misc/uacce/uacce.c +++ b/drivers/misc/uacce/uacce.c @@ -382,6 +382,9 @@ static ssize_t isolate_strategy_show(struct device *dev, struct device_attribute struct uacce_device *uacce = to_uacce_device(dev); u32 val; + if (!uacce->ops->isolate_err_threshold_read) + return -ENOENT; + val = uacce->ops->isolate_err_threshold_read(uacce); return sysfs_emit(buf, "%u\n", val); @@ -394,6 +397,9 @@ static ssize_t isolate_strategy_store(struct device *dev, struct device_attribut unsigned long val; int ret; + if (!uacce->ops->isolate_err_threshold_write) + return -ENOENT; + if (kstrtoul(buf, 0, &val) < 0) return -EINVAL; From ba29b59d124e725e0377f09b2044909c91d657a1 Mon Sep 17 00:00:00 2001 From: Yang Shen Date: Tue, 2 Dec 2025 14:12:55 +0800 Subject: [PATCH 2815/3902] uacce: implement mremap in uacce_vm_ops to return -EPERM commit 02695347be532b628f22488300d40c4eba48b9b7 upstream. The current uacce_vm_ops does not support the mremap operation of vm_operations_struct. Implement .mremap to return -EPERM to remind users. The reason we need to explicitly disable mremap is that when the driver does not implement .mremap, it uses the default mremap method. This could lead to a risk scenario: An application might first mmap address p1, then mremap to p2, followed by munmap(p1), and finally munmap(p2). Since the default mremap copies the original vma's vm_private_data (i.e., q) to the new vma, both munmap operations would trigger vma_close, causing q->qfr to be freed twice(qfr will be set to null here, so repeated release is ok). Fixes: 015d239ac014 ("uacce: add uacce driver") Cc: stable@vger.kernel.org Signed-off-by: Yang Shen Signed-off-by: Chenghai Huang Acked-by: Zhangfei Gao Link: https://patch.msgid.link/20251202061256.4158641-4-huangchenghai2@huawei.com Signed-off-by: Greg Kroah-Hartman --- drivers/misc/uacce/uacce.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/drivers/misc/uacce/uacce.c b/drivers/misc/uacce/uacce.c index b0b3c1562d526b..c061c6fa1c5e77 100644 --- a/drivers/misc/uacce/uacce.c +++ b/drivers/misc/uacce/uacce.c @@ -214,8 +214,14 @@ static void uacce_vma_close(struct vm_area_struct *vma) } } +static int uacce_vma_mremap(struct vm_area_struct *area) +{ + return -EPERM; +} + static const struct vm_operations_struct uacce_vm_ops = { .close = uacce_vma_close, + .mremap = uacce_vma_mremap, }; static int uacce_fops_mmap(struct file *filep, struct vm_area_struct *vma) From 92e4f11e29b98ef424ff72d6371acac03e5d973c Mon Sep 17 00:00:00 2001 From: Chenghai Huang Date: Tue, 2 Dec 2025 14:12:56 +0800 Subject: [PATCH 2816/3902] uacce: ensure safe queue release with state management commit 26c08dabe5475d99a13f353d8dd70e518de45663 upstream. Directly calling `put_queue` carries risks since it cannot guarantee that resources of `uacce_queue` have been fully released beforehand. So adding a `stop_queue` operation for the UACCE_CMD_PUT_Q command and leaving the `put_queue` operation to the final resource release ensures safety. Queue states are defined as follows: - UACCE_Q_ZOMBIE: Initial state - UACCE_Q_INIT: After opening `uacce` - UACCE_Q_STARTED: After `start` is issued via `ioctl` When executing `poweroff -f` in virt while accelerator are still working, `uacce_fops_release` and `uacce_remove` may execute concurrently. This can cause `uacce_put_queue` within `uacce_fops_release` to access a NULL `ops` pointer. Therefore, add state checks to prevent accessing freed pointers. Fixes: 015d239ac014 ("uacce: add uacce driver") Cc: stable@vger.kernel.org Signed-off-by: Chenghai Huang Signed-off-by: Yang Shen Acked-by: Zhangfei Gao Link: https://patch.msgid.link/20251202061256.4158641-5-huangchenghai2@huawei.com Signed-off-by: Greg Kroah-Hartman --- drivers/misc/uacce/uacce.c | 28 +++++++++++++++++++++------- 1 file changed, 21 insertions(+), 7 deletions(-) diff --git a/drivers/misc/uacce/uacce.c b/drivers/misc/uacce/uacce.c index c061c6fa1c5e77..6d71355528d344 100644 --- a/drivers/misc/uacce/uacce.c +++ b/drivers/misc/uacce/uacce.c @@ -40,20 +40,34 @@ static int uacce_start_queue(struct uacce_queue *q) return 0; } -static int uacce_put_queue(struct uacce_queue *q) +static int uacce_stop_queue(struct uacce_queue *q) { struct uacce_device *uacce = q->uacce; - if ((q->state == UACCE_Q_STARTED) && uacce->ops->stop_queue) + if (q->state != UACCE_Q_STARTED) + return 0; + + if (uacce->ops->stop_queue) uacce->ops->stop_queue(q); - if ((q->state == UACCE_Q_INIT || q->state == UACCE_Q_STARTED) && - uacce->ops->put_queue) + q->state = UACCE_Q_INIT; + + return 0; +} + +static void uacce_put_queue(struct uacce_queue *q) +{ + struct uacce_device *uacce = q->uacce; + + uacce_stop_queue(q); + + if (q->state != UACCE_Q_INIT) + return; + + if (uacce->ops->put_queue) uacce->ops->put_queue(q); q->state = UACCE_Q_ZOMBIE; - - return 0; } static long uacce_fops_unl_ioctl(struct file *filep, @@ -80,7 +94,7 @@ static long uacce_fops_unl_ioctl(struct file *filep, ret = uacce_start_queue(q); break; case UACCE_CMD_PUT_Q: - ret = uacce_put_queue(q); + ret = uacce_stop_queue(q); break; default: if (uacce->ops->ioctl) From 9f5fa78d9980fe75a69835521627ab7943cb3d67 Mon Sep 17 00:00:00 2001 From: Jeongjun Park Date: Mon, 19 Jan 2026 15:33:59 +0900 Subject: [PATCH 2817/3902] netrom: fix double-free in nr_route_frame() commit ba1096c315283ee3292765f6aea4cca15816c4f7 upstream. In nr_route_frame(), old_skb is immediately freed without checking if nr_neigh->ax25 pointer is NULL. Therefore, if nr_neigh->ax25 is NULL, the caller function will free old_skb again, causing a double-free bug. Therefore, to prevent this, we need to modify it to check whether nr_neigh->ax25 is NULL before freeing old_skb. Cc: Reported-by: syzbot+999115c3bf275797dc27@syzkaller.appspotmail.com Closes: https://lore.kernel.org/all/69694d6f.050a0220.58bed.0029.GAE@google.com/ Fixes: 1da177e4c3f4 ("Linux-2.6.12-rc2") Signed-off-by: Jeongjun Park Link: https://patch.msgid.link/20260119063359.10604-1-aha310510@gmail.com Signed-off-by: Jakub Kicinski Signed-off-by: Greg Kroah-Hartman --- net/netrom/nr_route.c | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/net/netrom/nr_route.c b/net/netrom/nr_route.c index b94cb2ffbaf8fa..9cc29ae85b06f5 100644 --- a/net/netrom/nr_route.c +++ b/net/netrom/nr_route.c @@ -752,7 +752,7 @@ int nr_route_frame(struct sk_buff *skb, ax25_cb *ax25) unsigned char *dptr; ax25_cb *ax25s; int ret; - struct sk_buff *skbn; + struct sk_buff *nskb, *oskb; /* * Reject malformed packets early. Check that it contains at least 2 @@ -811,14 +811,16 @@ int nr_route_frame(struct sk_buff *skb, ax25_cb *ax25) /* We are going to change the netrom headers so we should get our own skb, we also did not know until now how much header space we had to reserve... - RXQ */ - if ((skbn=skb_copy_expand(skb, dev->hard_header_len, 0, GFP_ATOMIC)) == NULL) { + nskb = skb_copy_expand(skb, dev->hard_header_len, 0, GFP_ATOMIC); + + if (!nskb) { nr_node_unlock(nr_node); nr_node_put(nr_node); dev_put(dev); return 0; } - kfree_skb(skb); - skb=skbn; + oskb = skb; + skb = nskb; skb->data[14]--; dptr = skb_push(skb, 1); @@ -837,6 +839,9 @@ int nr_route_frame(struct sk_buff *skb, ax25_cb *ax25) nr_node_unlock(nr_node); nr_node_put(nr_node); + if (ret) + kfree_skb(oskb); + return ret; } From bbcca5a60c88a8db8154f706516d7af7e56ef02b Mon Sep 17 00:00:00 2001 From: Mario Limonciello Date: Thu, 15 Jan 2026 14:31:12 -0600 Subject: [PATCH 2818/3902] platform/x86: hp-bioscfg: Fix automatic module loading MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit commit 467d4afc6caa64b84a6db1634f8091e931f4a7cb upstream. hp-bioscfg has a MODULE_DEVICE_TABLE with a GUID in it that looks plausible, but the module doesn't automatically load on applicable systems. This is because the GUID has some lower case characters and so it doesn't match the modalias during boot. Update the GUIDs to be all uppercase. Cc: stable@vger.kernel.org Fixes: 5f94f181ca25 ("platform/x86: hp-bioscfg: bioscfg-h") Signed-off-by: Mario Limonciello Link: https://patch.msgid.link/20260115203725.828434-4-mario.limonciello@amd.com Reviewed-by: Ilpo Järvinen Signed-off-by: Ilpo Järvinen Signed-off-by: Greg Kroah-Hartman --- drivers/platform/x86/hp/hp-bioscfg/bioscfg.h | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/drivers/platform/x86/hp/hp-bioscfg/bioscfg.h b/drivers/platform/x86/hp/hp-bioscfg/bioscfg.h index 6b6748e4be2182..f1eec0e4ba075b 100644 --- a/drivers/platform/x86/hp/hp-bioscfg/bioscfg.h +++ b/drivers/platform/x86/hp/hp-bioscfg/bioscfg.h @@ -57,14 +57,14 @@ enum mechanism_values { #define PASSWD_MECHANISM_TYPES "password" -#define HP_WMI_BIOS_GUID "5FB7F034-2C63-45e9-BE91-3D44E2C707E4" +#define HP_WMI_BIOS_GUID "5FB7F034-2C63-45E9-BE91-3D44E2C707E4" -#define HP_WMI_BIOS_STRING_GUID "988D08E3-68F4-4c35-AF3E-6A1B8106F83C" +#define HP_WMI_BIOS_STRING_GUID "988D08E3-68F4-4C35-AF3E-6A1B8106F83C" #define HP_WMI_BIOS_INTEGER_GUID "8232DE3D-663D-4327-A8F4-E293ADB9BF05" #define HP_WMI_BIOS_ENUMERATION_GUID "2D114B49-2DFB-4130-B8FE-4A3C09E75133" #define HP_WMI_BIOS_ORDERED_LIST_GUID "14EA9746-CE1F-4098-A0E0-7045CB4DA745" #define HP_WMI_BIOS_PASSWORD_GUID "322F2028-0F84-4901-988E-015176049E2D" -#define HP_WMI_SET_BIOS_SETTING_GUID "1F4C91EB-DC5C-460b-951D-C7CB9B4B8D5E" +#define HP_WMI_SET_BIOS_SETTING_GUID "1F4C91EB-DC5C-460B-951D-C7CB9B4B8D5E" enum hp_wmi_spm_commandtype { HPWMI_SECUREPLATFORM_GET_STATE = 0x10, From fd675de6bddf7e9bdf42ae3929d4c27ba6d1ef76 Mon Sep 17 00:00:00 2001 From: Ming Qian Date: Fri, 5 Dec 2025 09:54:25 +0800 Subject: [PATCH 2819/3902] pmdomain: imx8m-blk-ctrl: Remove separate rst and clk mask for 8mq vpu commit 3de49966499634454fd59e0e6fecd50baab7febd upstream. For i.MX8MQ platform, the ADB in the VPUMIX domain has no separate reset and clock enable bits, but is ungated and reset together with the VPUs. So we can't reset G1 or G2 separately, it may led to the system hang. Remove rst_mask and clk_mask of imx8mq_vpu_blk_ctl_domain_data. Let imx8mq_vpu_power_notifier() do really vpu reset. Fixes: 608d7c325e85 ("soc: imx: imx8m-blk-ctrl: add i.MX8MQ VPU blk-ctrl") Signed-off-by: Ming Qian Reviewed-by: Benjamin Gaignard Reviewed-by: Peng Fan Reviewed-by: Frank Li Cc: stable@vger.kernel.org Signed-off-by: Ulf Hansson Signed-off-by: Greg Kroah-Hartman --- drivers/pmdomain/imx/imx8m-blk-ctrl.c | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/drivers/pmdomain/imx/imx8m-blk-ctrl.c b/drivers/pmdomain/imx/imx8m-blk-ctrl.c index 5c83e5599f1ea6..74bf4936991d7c 100644 --- a/drivers/pmdomain/imx/imx8m-blk-ctrl.c +++ b/drivers/pmdomain/imx/imx8m-blk-ctrl.c @@ -846,22 +846,25 @@ static int imx8mq_vpu_power_notifier(struct notifier_block *nb, return NOTIFY_OK; } +/* + * For i.MX8MQ, the ADB in the VPUMIX domain has no separate reset and clock + * enable bits, but is ungated and reset together with the VPUs. + * Resetting G1 or G2 separately may led to system hang. + * Remove the rst_mask and clk_mask from the domain data of G1 and G2, + * Let imx8mq_vpu_power_notifier() do really vpu reset. + */ static const struct imx8m_blk_ctrl_domain_data imx8mq_vpu_blk_ctl_domain_data[] = { [IMX8MQ_VPUBLK_PD_G1] = { .name = "vpublk-g1", .clk_names = (const char *[]){ "g1", }, .num_clks = 1, .gpc_name = "g1", - .rst_mask = BIT(1), - .clk_mask = BIT(1), }, [IMX8MQ_VPUBLK_PD_G2] = { .name = "vpublk-g2", .clk_names = (const char *[]){ "g2", }, .num_clks = 1, .gpc_name = "g2", - .rst_mask = BIT(0), - .clk_mask = BIT(0), }, }; From 8776dfa846d5c9dab5d1c3a262b4286b05ece87a Mon Sep 17 00:00:00 2001 From: Frank Zhang Date: Tue, 16 Dec 2025 13:52:47 +0800 Subject: [PATCH 2820/3902] pmdomain:rockchip: Fix init genpd as GENPD_STATE_ON before regulator ready commit 861d21c43c98478eef70e68e31d4ff86400c6ef7 upstream. RK3588_PD_NPU initialize as GENPD_STATE_ON before regulator ready. rknn_iommu initlized success and suspend RK3588_PD_NPU. When rocket driver register, it will resume rknn_iommu. If regulator is still not ready at this point, rknn_iommu resume fail, pm runtime status will be error: -EPROBE_DEFER. This patch set pmdomain to off if it need regulator during probe, consumer device can power on pmdomain after regulator ready. Signed-off-by: Frank Zhang Tested-by: Chaoyi Chen Tested-by: Quentin Schulz Reviewed-by: Sebastian Reichel Fixes: db6df2e3fc16 ("pmdomain: rockchip: add regulator support") Cc: stable@vger.kernel.org Signed-off-by: Ulf Hansson Signed-off-by: Greg Kroah-Hartman --- drivers/pmdomain/rockchip/pm-domains.c | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/drivers/pmdomain/rockchip/pm-domains.c b/drivers/pmdomain/rockchip/pm-domains.c index 1955c6d453e4f6..5baaa6beb21011 100644 --- a/drivers/pmdomain/rockchip/pm-domains.c +++ b/drivers/pmdomain/rockchip/pm-domains.c @@ -861,6 +861,16 @@ static int rockchip_pm_add_one_domain(struct rockchip_pmu *pmu, pd->genpd.name = pd->info->name; else pd->genpd.name = kbasename(node->full_name); + + /* + * power domain's needing a regulator should default to off, since + * the regulator state is unknown at probe time. Also the regulator + * state cannot be checked, since that usually requires IP needing + * (a different) power domain. + */ + if (pd->info->need_regulator) + rockchip_pd_power(pd, false); + pd->genpd.power_off = rockchip_pd_power_off; pd->genpd.power_on = rockchip_pd_power_on; pd->genpd.attach_dev = rockchip_pd_attach_dev; From ad60902a98181270b7ea8c00744028b1ad47c538 Mon Sep 17 00:00:00 2001 From: Alexandre Courbot Date: Mon, 8 Dec 2025 11:47:00 +0900 Subject: [PATCH 2821/3902] rust: io: always inline functions using build_assert with arguments commit 33d19f621641de1b6ec6fe1bb2ac68a7d2c61f6a upstream. `build_assert` relies on the compiler to optimize out its error path. Functions using it with its arguments must thus always be inlined, otherwise the error path of `build_assert` might not be optimized out, triggering a build error. Cc: stable@vger.kernel.org Fixes: ce30d94e6855 ("rust: add `io::{Io, IoRaw}` base types") Reviewed-by: Daniel Almeida Signed-off-by: Alexandre Courbot Tested-by: Timur Tabi Link: https://patch.msgid.link/20251208-io-build-assert-v3-2-98aded02c1ea@nvidia.com Signed-off-by: Danilo Krummrich Signed-off-by: Greg Kroah-Hartman --- rust/kernel/io.rs | 9 ++++++--- rust/kernel/io/resource.rs | 2 ++ 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/rust/kernel/io.rs b/rust/kernel/io.rs index 56a435eb14e3a1..0f793817348355 100644 --- a/rust/kernel/io.rs +++ b/rust/kernel/io.rs @@ -140,7 +140,8 @@ macro_rules! define_read { /// Bound checks are performed on compile time, hence if the offset is not known at compile /// time, the build will fail. $(#[$attr])* - #[inline] + // Always inline to optimize out error path of `io_addr_assert`. + #[inline(always)] pub fn $name(&self, offset: usize) -> $type_name { let addr = self.io_addr_assert::<$type_name>(offset); @@ -169,7 +170,8 @@ macro_rules! define_write { /// Bound checks are performed on compile time, hence if the offset is not known at compile /// time, the build will fail. $(#[$attr])* - #[inline] + // Always inline to optimize out error path of `io_addr_assert`. + #[inline(always)] pub fn $name(&self, value: $type_name, offset: usize) { let addr = self.io_addr_assert::<$type_name>(offset); @@ -237,7 +239,8 @@ impl Io { self.addr().checked_add(offset).ok_or(EINVAL) } - #[inline] + // Always inline to optimize out error path of `build_assert`. + #[inline(always)] fn io_addr_assert(&self, offset: usize) -> usize { build_assert!(Self::offset_valid::(offset, SIZE)); diff --git a/rust/kernel/io/resource.rs b/rust/kernel/io/resource.rs index 0e86ee9c98d840..3d54f2828e7918 100644 --- a/rust/kernel/io/resource.rs +++ b/rust/kernel/io/resource.rs @@ -222,6 +222,8 @@ impl Flags { /// Resource represents a memory region that must be ioremaped using `ioremap_np`. pub const IORESOURCE_MEM_NONPOSTED: Flags = Flags::new(bindings::IORESOURCE_MEM_NONPOSTED); + // Always inline to optimize out error path of `build_assert`. + #[inline(always)] const fn new(value: u32) -> Self { crate::build_assert!(value as u64 <= c_ulong::MAX as u64); Flags(value as c_ulong) From 8a957e6d3d26252683aa1ab987aba81b8a9d28bd Mon Sep 17 00:00:00 2001 From: Alexandre Courbot Date: Mon, 8 Dec 2025 11:47:04 +0900 Subject: [PATCH 2822/3902] rust: irq: always inline functions using build_assert with arguments commit 5d9c4c272ba06055d19e05c2a02e16e58acc8943 upstream. `build_assert` relies on the compiler to optimize out its error path. Functions using it with its arguments must thus always be inlined, otherwise the error path of `build_assert` might not be optimized out, triggering a build error. Cc: stable@vger.kernel.org Fixes: 746680ec6696 ("rust: irq: add flags module") Reviewed-by: Daniel Almeida Signed-off-by: Alexandre Courbot Link: https://patch.msgid.link/20251208-io-build-assert-v3-6-98aded02c1ea@nvidia.com Signed-off-by: Danilo Krummrich Signed-off-by: Greg Kroah-Hartman --- rust/kernel/irq/flags.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/rust/kernel/irq/flags.rs b/rust/kernel/irq/flags.rs index adfde96ec47cf4..d26e25af06eea8 100644 --- a/rust/kernel/irq/flags.rs +++ b/rust/kernel/irq/flags.rs @@ -96,6 +96,8 @@ impl Flags { self.0 } + // Always inline to optimize out error path of `build_assert`. + #[inline(always)] const fn new(value: u32) -> Self { build_assert!(value as u64 <= c_ulong::MAX as u64); Self(value as c_ulong) From f8cf1368e0a5491b27189a695c36f64e48f3d19d Mon Sep 17 00:00:00 2001 From: David Howells Date: Tue, 20 Jan 2026 10:13:05 +0000 Subject: [PATCH 2823/3902] rxrpc: Fix data-race warning and potential load/store tearing commit 5d5fe8bcd331f1e34e0943ec7c18432edfcf0e8b upstream. Fix the following: BUG: KCSAN: data-race in rxrpc_peer_keepalive_worker / rxrpc_send_data_packet which is reporting an issue with the reads and writes to ->last_tx_at in: conn->peer->last_tx_at = ktime_get_seconds(); and: keepalive_at = peer->last_tx_at + RXRPC_KEEPALIVE_TIME; The lockless accesses to these to values aren't actually a problem as the read only needs an approximate time of last transmission for the purposes of deciding whether or not the transmission of a keepalive packet is warranted yet. Also, as ->last_tx_at is a 64-bit value, tearing can occur on a 32-bit arch. Fix both of these by switching to an unsigned int for ->last_tx_at and only storing the LSW of the time64_t. It can then be reconstructed at need provided no more than 68 years has elapsed since the last transmission. Fixes: ace45bec6d77 ("rxrpc: Fix firewall route keepalive") Reported-by: syzbot+6182afad5045e6703b3d@syzkaller.appspotmail.com Closes: https://lore.kernel.org/r/695e7cfb.050a0220.1c677c.036b.GAE@google.com/ Signed-off-by: David Howells cc: Marc Dionne cc: Simon Horman cc: linux-afs@lists.infradead.org cc: stable@kernel.org Link: https://patch.msgid.link/1107124.1768903985@warthog.procyon.org.uk Signed-off-by: Jakub Kicinski Signed-off-by: Greg Kroah-Hartman --- net/rxrpc/ar-internal.h | 9 ++++++++- net/rxrpc/conn_event.c | 2 +- net/rxrpc/output.c | 14 +++++++------- net/rxrpc/peer_event.c | 17 ++++++++++++++++- net/rxrpc/proc.c | 4 ++-- net/rxrpc/rxgk.c | 2 +- net/rxrpc/rxkad.c | 2 +- 7 files changed, 36 insertions(+), 14 deletions(-) diff --git a/net/rxrpc/ar-internal.h b/net/rxrpc/ar-internal.h index 5b7342d4348692..36d6ca0d1089e1 100644 --- a/net/rxrpc/ar-internal.h +++ b/net/rxrpc/ar-internal.h @@ -387,7 +387,7 @@ struct rxrpc_peer { struct rb_root service_conns; /* Service connections */ struct list_head keepalive_link; /* Link in net->peer_keepalive[] */ unsigned long app_data; /* Application data (e.g. afs_server) */ - time64_t last_tx_at; /* Last time packet sent here */ + unsigned int last_tx_at; /* Last time packet sent here (time64_t LSW) */ seqlock_t service_conn_lock; spinlock_t lock; /* access lock */ int debug_id; /* debug ID for printks */ @@ -1379,6 +1379,13 @@ void rxrpc_peer_keepalive_worker(struct work_struct *); void rxrpc_input_probe_for_pmtud(struct rxrpc_connection *conn, rxrpc_serial_t acked_serial, bool sendmsg_fail); +/* Update the last transmission time on a peer for keepalive purposes. */ +static inline void rxrpc_peer_mark_tx(struct rxrpc_peer *peer) +{ + /* To avoid tearing on 32-bit systems, we only keep the LSW. */ + WRITE_ONCE(peer->last_tx_at, ktime_get_seconds()); +} + /* * peer_object.c */ diff --git a/net/rxrpc/conn_event.c b/net/rxrpc/conn_event.c index 232b6986da83e2..98ad9b51ca2cdc 100644 --- a/net/rxrpc/conn_event.c +++ b/net/rxrpc/conn_event.c @@ -194,7 +194,7 @@ void rxrpc_conn_retransmit_call(struct rxrpc_connection *conn, } ret = kernel_sendmsg(conn->local->socket, &msg, iov, ioc, len); - conn->peer->last_tx_at = ktime_get_seconds(); + rxrpc_peer_mark_tx(conn->peer); if (ret < 0) trace_rxrpc_tx_fail(chan->call_debug_id, serial, ret, rxrpc_tx_point_call_final_resend); diff --git a/net/rxrpc/output.c b/net/rxrpc/output.c index 8b5903b6e481ac..d70db367e358db 100644 --- a/net/rxrpc/output.c +++ b/net/rxrpc/output.c @@ -275,7 +275,7 @@ static void rxrpc_send_ack_packet(struct rxrpc_call *call, int nr_kv, size_t len rxrpc_local_dont_fragment(conn->local, why == rxrpc_propose_ack_ping_for_mtu_probe); ret = do_udp_sendmsg(conn->local->socket, &msg, len); - call->peer->last_tx_at = ktime_get_seconds(); + rxrpc_peer_mark_tx(call->peer); if (ret < 0) { trace_rxrpc_tx_fail(call->debug_id, serial, ret, rxrpc_tx_point_call_ack); @@ -411,7 +411,7 @@ int rxrpc_send_abort_packet(struct rxrpc_call *call) iov_iter_kvec(&msg.msg_iter, WRITE, iov, 1, sizeof(pkt)); ret = do_udp_sendmsg(conn->local->socket, &msg, sizeof(pkt)); - conn->peer->last_tx_at = ktime_get_seconds(); + rxrpc_peer_mark_tx(conn->peer); if (ret < 0) trace_rxrpc_tx_fail(call->debug_id, serial, ret, rxrpc_tx_point_call_abort); @@ -698,7 +698,7 @@ void rxrpc_send_data_packet(struct rxrpc_call *call, struct rxrpc_send_data_req ret = 0; trace_rxrpc_tx_data(call, txb->seq, txb->serial, txb->flags, rxrpc_txdata_inject_loss); - conn->peer->last_tx_at = ktime_get_seconds(); + rxrpc_peer_mark_tx(conn->peer); goto done; } } @@ -711,7 +711,7 @@ void rxrpc_send_data_packet(struct rxrpc_call *call, struct rxrpc_send_data_req */ rxrpc_inc_stat(call->rxnet, stat_tx_data_send); ret = do_udp_sendmsg(conn->local->socket, &msg, len); - conn->peer->last_tx_at = ktime_get_seconds(); + rxrpc_peer_mark_tx(conn->peer); if (ret == -EMSGSIZE) { rxrpc_inc_stat(call->rxnet, stat_tx_data_send_msgsize); @@ -797,7 +797,7 @@ void rxrpc_send_conn_abort(struct rxrpc_connection *conn) trace_rxrpc_tx_packet(conn->debug_id, &whdr, rxrpc_tx_point_conn_abort); - conn->peer->last_tx_at = ktime_get_seconds(); + rxrpc_peer_mark_tx(conn->peer); } /* @@ -917,7 +917,7 @@ void rxrpc_send_keepalive(struct rxrpc_peer *peer) trace_rxrpc_tx_packet(peer->debug_id, &whdr, rxrpc_tx_point_version_keepalive); - peer->last_tx_at = ktime_get_seconds(); + rxrpc_peer_mark_tx(peer); _leave(""); } @@ -973,7 +973,7 @@ void rxrpc_send_response(struct rxrpc_connection *conn, struct sk_buff *response if (ret < 0) goto fail; - conn->peer->last_tx_at = ktime_get_seconds(); + rxrpc_peer_mark_tx(conn->peer); return; fail: diff --git a/net/rxrpc/peer_event.c b/net/rxrpc/peer_event.c index 7f4729234957e8..9d02448ac062c7 100644 --- a/net/rxrpc/peer_event.c +++ b/net/rxrpc/peer_event.c @@ -237,6 +237,21 @@ static void rxrpc_distribute_error(struct rxrpc_peer *peer, struct sk_buff *skb, spin_unlock_irq(&peer->lock); } +/* + * Reconstruct the last transmission time. The difference calculated should be + * valid provided no more than ~68 years elapsed since the last transmission. + */ +static time64_t rxrpc_peer_get_tx_mark(const struct rxrpc_peer *peer, time64_t base) +{ + s32 last_tx_at = READ_ONCE(peer->last_tx_at); + s32 base_lsw = base; + s32 diff = last_tx_at - base_lsw; + + diff = clamp(diff, -RXRPC_KEEPALIVE_TIME, RXRPC_KEEPALIVE_TIME); + + return diff + base; +} + /* * Perform keep-alive pings. */ @@ -265,7 +280,7 @@ static void rxrpc_peer_keepalive_dispatch(struct rxrpc_net *rxnet, spin_unlock_bh(&rxnet->peer_hash_lock); if (use) { - keepalive_at = peer->last_tx_at + RXRPC_KEEPALIVE_TIME; + keepalive_at = rxrpc_peer_get_tx_mark(peer, base) + RXRPC_KEEPALIVE_TIME; slot = keepalive_at - base; _debug("%02x peer %u t=%d {%pISp}", cursor, peer->debug_id, slot, &peer->srx.transport); diff --git a/net/rxrpc/proc.c b/net/rxrpc/proc.c index d803562ca0ac1d..59292f7f9205e7 100644 --- a/net/rxrpc/proc.c +++ b/net/rxrpc/proc.c @@ -296,13 +296,13 @@ static int rxrpc_peer_seq_show(struct seq_file *seq, void *v) now = ktime_get_seconds(); seq_printf(seq, - "UDP %-47.47s %-47.47s %3u %4u %5u %6llus %8d %8d\n", + "UDP %-47.47s %-47.47s %3u %4u %5u %6ds %8d %8d\n", lbuff, rbuff, refcount_read(&peer->ref), peer->cong_ssthresh, peer->max_data, - now - peer->last_tx_at, + (s32)now - (s32)READ_ONCE(peer->last_tx_at), READ_ONCE(peer->recent_srtt_us), READ_ONCE(peer->recent_rto_us)); diff --git a/net/rxrpc/rxgk.c b/net/rxrpc/rxgk.c index dce5a3d8a964f8..43cbf9efd89f18 100644 --- a/net/rxrpc/rxgk.c +++ b/net/rxrpc/rxgk.c @@ -678,7 +678,7 @@ static int rxgk_issue_challenge(struct rxrpc_connection *conn) ret = do_udp_sendmsg(conn->local->socket, &msg, len); if (ret > 0) - conn->peer->last_tx_at = ktime_get_seconds(); + rxrpc_peer_mark_tx(conn->peer); __free_page(page); if (ret < 0) { diff --git a/net/rxrpc/rxkad.c b/net/rxrpc/rxkad.c index 3657c0661cdc7f..a756855a0a62d7 100644 --- a/net/rxrpc/rxkad.c +++ b/net/rxrpc/rxkad.c @@ -694,7 +694,7 @@ static int rxkad_issue_challenge(struct rxrpc_connection *conn) return -EAGAIN; } - conn->peer->last_tx_at = ktime_get_seconds(); + rxrpc_peer_mark_tx(conn->peer); trace_rxrpc_tx_packet(conn->debug_id, &whdr, rxrpc_tx_point_rxkad_challenge); _leave(" = 0"); From cdb3f95a94f9f5cad054260de551942942e8a8f7 Mon Sep 17 00:00:00 2001 From: Fernand Sieber Date: Thu, 11 Dec 2025 20:36:04 +0200 Subject: [PATCH 2824/3902] perf/x86/intel: Do not enable BTS for guests MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit commit 91dcfae0ff2b9b9ab03c1ec95babaceefbffb9f4 upstream. By default when users program perf to sample branch instructions (PERF_COUNT_HW_BRANCH_INSTRUCTIONS) with a sample period of 1, perf interprets this as a special case and enables BTS (Branch Trace Store) as an optimization to avoid taking an interrupt on every branch. Since BTS doesn't virtualize, this optimization doesn't make sense when the request originates from a guest. Add an additional check that prevents this optimization for virtualized events (exclude_host). Reported-by: Jan H. Schönherr Suggested-by: Peter Zijlstra Signed-off-by: Fernand Sieber Signed-off-by: Peter Zijlstra (Intel) Cc: Link: https://patch.msgid.link/20251211183604.868641-1-sieberf@amazon.com Signed-off-by: Greg Kroah-Hartman --- arch/x86/events/perf_event.h | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/arch/x86/events/perf_event.h b/arch/x86/events/perf_event.h index 2b969386dcddce..493e6ba51e06dc 100644 --- a/arch/x86/events/perf_event.h +++ b/arch/x86/events/perf_event.h @@ -1558,13 +1558,22 @@ static inline bool intel_pmu_has_bts_period(struct perf_event *event, u64 period struct hw_perf_event *hwc = &event->hw; unsigned int hw_event, bts_event; - if (event->attr.freq) + /* + * Only use BTS for fixed rate period==1 events. + */ + if (event->attr.freq || period != 1) + return false; + + /* + * BTS doesn't virtualize. + */ + if (event->attr.exclude_host) return false; hw_event = hwc->config & INTEL_ARCH_EVENT_MASK; bts_event = x86_pmu.event_map(PERF_COUNT_HW_BRANCH_INSTRUCTIONS); - return hw_event == bts_event && period == 1; + return hw_event == bts_event; } static inline bool intel_pmu_has_bts(struct perf_event *event) From 03faa61eb4b9ca9aa09bd91d4c3773d8e7b1ac98 Mon Sep 17 00:00:00 2001 From: Arnd Bergmann Date: Mon, 19 Jan 2026 21:15:12 +0100 Subject: [PATCH 2825/3902] irqchip/gic-v3-its: Avoid truncating memory addresses commit 8d76a7d89c12d08382b66e2f21f20d0627d14859 upstream. On 32-bit machines with CONFIG_ARM_LPAE, it is possible for lowmem allocations to be backed by addresses physical memory above the 32-bit address limit, as found while experimenting with larger VMSPLIT configurations. This caused the qemu virt model to crash in the GICv3 driver, which allocates the 'itt' object using GFP_KERNEL. Since all memory below the 4GB physical address limit is in ZONE_DMA in this configuration, kmalloc() defaults to higher addresses for ZONE_NORMAL, and the ITS driver stores the physical address in a 32-bit 'unsigned long' variable. Change the itt_addr variable to the correct phys_addr_t type instead, along with all other variables in this driver that hold a physical address. The gicv5 driver correctly uses u64 variables, while all other irqchip drivers don't call virt_to_phys or similar interfaces. It's expected that other device drivers have similar issues, but fixing this one is sufficient for booting a virtio based guest. Fixes: cc2d3216f53c ("irqchip: GICv3: ITS command queue") Signed-off-by: Arnd Bergmann Signed-off-by: Thomas Gleixner Reviewed-by: Marc Zyngier Cc: stable@vger.kernel.org Link: https://patch.msgid.link/20260119201603.2713066-1-arnd@kernel.org Signed-off-by: Greg Kroah-Hartman --- drivers/irqchip/irq-gic-v3-its.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/drivers/irqchip/irq-gic-v3-its.c b/drivers/irqchip/irq-gic-v3-its.c index 467cb78435a9b6..9e020c74be7839 100644 --- a/drivers/irqchip/irq-gic-v3-its.c +++ b/drivers/irqchip/irq-gic-v3-its.c @@ -709,7 +709,7 @@ static struct its_collection *its_build_mapd_cmd(struct its_node *its, struct its_cmd_block *cmd, struct its_cmd_desc *desc) { - unsigned long itt_addr; + phys_addr_t itt_addr; u8 size = ilog2(desc->its_mapd_cmd.dev->nr_ites); itt_addr = virt_to_phys(desc->its_mapd_cmd.dev->itt); @@ -879,7 +879,7 @@ static struct its_vpe *its_build_vmapp_cmd(struct its_node *its, struct its_cmd_desc *desc) { struct its_vpe *vpe = valid_vpe(its, desc->its_vmapp_cmd.vpe); - unsigned long vpt_addr, vconf_addr; + phys_addr_t vpt_addr, vconf_addr; u64 target; bool alloc; @@ -2477,10 +2477,10 @@ static int its_setup_baser(struct its_node *its, struct its_baser *baser, baser->psz = psz; tmp = indirect ? GITS_LVL1_ENTRY_SIZE : esz; - pr_info("ITS@%pa: allocated %d %s @%lx (%s, esz %d, psz %dK, shr %d)\n", + pr_info("ITS@%pa: allocated %d %s @%llx (%s, esz %d, psz %dK, shr %d)\n", &its->phys_base, (int)(PAGE_ORDER_TO_SIZE(order) / (int)tmp), its_base_type_string[type], - (unsigned long)virt_to_phys(base), + (u64)virt_to_phys(base), indirect ? "indirect" : "flat", (int)esz, psz / SZ_1K, (int)shr >> GITS_BASER_SHAREABILITY_SHIFT); From 326470b33c28ac1f4a4795f4c73eac3ba74b14ea Mon Sep 17 00:00:00 2001 From: Clemens Gruber Date: Wed, 21 Jan 2026 09:37:51 +0100 Subject: [PATCH 2826/3902] net: fec: account for VLAN header in frame length calculations commit ca1bb3fedf26a08ed31974131bc0064d4fe33649 upstream. The MAX_FL (maximum frame length) and related calculations used ETH_HLEN, which does not account for the 4-byte VLAN tag in tagged frames. This caused the hardware to reject valid VLAN frames as oversized, resulting in RX errors and dropped packets. Use VLAN_ETH_HLEN instead of ETH_HLEN in the MAX_FL register setup, cut-through mode threshold, buffer allocation, and max_mtu calculation. Cc: stable@kernel.org # v6.18+ Fixes: 62b5bb7be7bc ("net: fec: update MAX_FL based on the current MTU") Fixes: d466c16026e9 ("net: fec: enable the Jumbo frame support for i.MX8QM") Fixes: 59e9bf037d75 ("net: fec: add change_mtu to support dynamic buffer allocation") Fixes: ec2a1681ed4f ("net: fec: use a member variable for maximum buffer size") Signed-off-by: Clemens Gruber Reviewed-by: Wei Fang Link: https://patch.msgid.link/20260121083751.66997-1-mail@clemensgruber.at Signed-off-by: Paolo Abeni Signed-off-by: Greg Kroah-Hartman --- drivers/net/ethernet/freescale/fec_main.c | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/drivers/net/ethernet/freescale/fec_main.c b/drivers/net/ethernet/freescale/fec_main.c index e2b75d1970ae6f..f30e8fabfaded8 100644 --- a/drivers/net/ethernet/freescale/fec_main.c +++ b/drivers/net/ethernet/freescale/fec_main.c @@ -1151,7 +1151,7 @@ fec_restart(struct net_device *ndev) u32 rcntl = FEC_RCR_MII; if (OPT_ARCH_HAS_MAX_FL) - rcntl |= (fep->netdev->mtu + ETH_HLEN + ETH_FCS_LEN) << 16; + rcntl |= (fep->netdev->mtu + VLAN_ETH_HLEN + ETH_FCS_LEN) << 16; if (fep->bufdesc_ex) fec_ptp_save_state(fep); @@ -1286,12 +1286,13 @@ fec_restart(struct net_device *ndev) /* When Jumbo Frame is enabled, the FIFO may not be large enough * to hold an entire frame. In such cases, if the MTU exceeds - * (PKT_MAXBUF_SIZE - ETH_HLEN - ETH_FCS_LEN), configure the interface - * to operate in cut-through mode, triggered by the FIFO threshold. + * (PKT_MAXBUF_SIZE - VLAN_ETH_HLEN - ETH_FCS_LEN), configure + * the interface to operate in cut-through mode, triggered by + * the FIFO threshold. * Otherwise, enable the ENET store-and-forward mode. */ if ((fep->quirks & FEC_QUIRK_JUMBO_FRAME) && - (ndev->mtu > (PKT_MAXBUF_SIZE - ETH_HLEN - ETH_FCS_LEN))) + (ndev->mtu > (PKT_MAXBUF_SIZE - VLAN_ETH_HLEN - ETH_FCS_LEN))) writel(0xF, fep->hwp + FEC_X_WMRK); else writel(FEC_TXWMRK_STRFWD, fep->hwp + FEC_X_WMRK); @@ -4052,7 +4053,7 @@ static int fec_change_mtu(struct net_device *ndev, int new_mtu) if (netif_running(ndev)) return -EBUSY; - order = get_order(new_mtu + ETH_HLEN + ETH_FCS_LEN + order = get_order(new_mtu + VLAN_ETH_HLEN + ETH_FCS_LEN + FEC_DRV_RESERVE_SPACE); fep->rx_frame_size = (PAGE_SIZE << order) - FEC_DRV_RESERVE_SPACE; fep->pagepool_order = order; @@ -4609,7 +4610,7 @@ fec_probe(struct platform_device *pdev) else fep->max_buf_size = PKT_MAXBUF_SIZE; - ndev->max_mtu = fep->max_buf_size - ETH_HLEN - ETH_FCS_LEN; + ndev->max_mtu = fep->max_buf_size - VLAN_ETH_HLEN - ETH_FCS_LEN; ret = register_netdev(ndev); if (ret) From 69b5b028fc130c55d810bd964c2f949433256afa Mon Sep 17 00:00:00 2001 From: Hamza Mahfooz Date: Tue, 13 Jan 2026 18:29:57 -0500 Subject: [PATCH 2827/3902] net: sfp: add potron quirk to the H-COM SPP425H-GAB4 SFP+ Stick commit a92a6c50e35b75a8021265507f3c2a9084df0b94 upstream. This is another one of those XGSPON ONU sticks that's using the X-ONU-SFPP internally, thus it also requires the potron quirk to avoid tx faults. So, add an entry for it in sfp_quirks[]. Cc: stable@vger.kernel.org Signed-off-by: Hamza Mahfooz Link: https://patch.msgid.link/20260113232957.609642-1-someguy@effective-light.com Signed-off-by: Jakub Kicinski Signed-off-by: Greg Kroah-Hartman --- drivers/net/phy/sfp.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/drivers/net/phy/sfp.c b/drivers/net/phy/sfp.c index 84bef5099dda6f..47f095bd91ceab 100644 --- a/drivers/net/phy/sfp.c +++ b/drivers/net/phy/sfp.c @@ -519,6 +519,8 @@ static const struct sfp_quirk sfp_quirks[] = { SFP_QUIRK_F("HALNy", "HL-GSFP", sfp_fixup_halny_gsfp), + SFP_QUIRK_F("H-COM", "SPP425H-GAB4", sfp_fixup_potron), + // HG MXPD-483II-F 2.5G supports 2500Base-X, but incorrectly reports // 2600MBd in their EERPOM SFP_QUIRK_S("HG GENUINE", "MXPD-483II", sfp_quirk_2500basex), From aac95b3dd1c26c01f0f07e33394e70e2db1d2809 Mon Sep 17 00:00:00 2001 From: Jiawen Wu Date: Mon, 19 Jan 2026 14:59:35 +0800 Subject: [PATCH 2828/3902] net: txgbe: remove the redundant data return in SW-FW mailbox commit 3d778e65b4f44c6af4901d83020bb8a0a010f39e upstream. For these two firmware mailbox commands, in txgbe_test_hostif() and txgbe_set_phy_link_hostif(), there is no need to read data from the buffer. Under the current setting, OEM firmware will cause the driver to fail to probe. Because OEM firmware returns more link information, with a larger OEM structure txgbe_hic_ephy_getlink. However, the current driver does not support the OEM function. So just fix it in the way that does not involve reading the returned data. Fixes: d84a3ff9aae8 ("net: txgbe: Restrict the use of mismatched FW versions") Cc: stable@vger.kernel.org Signed-off-by: Jiawen Wu Link: https://patch.msgid.link/2914AB0BC6158DDA+20260119065935.6015-1-jiawenwu@trustnetic.com Signed-off-by: Jakub Kicinski Signed-off-by: Greg Kroah-Hartman --- drivers/net/ethernet/wangxun/txgbe/txgbe_aml.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/net/ethernet/wangxun/txgbe/txgbe_aml.c b/drivers/net/ethernet/wangxun/txgbe/txgbe_aml.c index dc87ccad965240..08b9b426f64846 100644 --- a/drivers/net/ethernet/wangxun/txgbe/txgbe_aml.c +++ b/drivers/net/ethernet/wangxun/txgbe/txgbe_aml.c @@ -65,7 +65,7 @@ int txgbe_test_hostif(struct wx *wx) buffer.hdr.cmd_or_resp.cmd_resv = FW_CEM_CMD_RESERVED; return wx_host_interface_command(wx, (u32 *)&buffer, sizeof(buffer), - WX_HI_COMMAND_TIMEOUT, true); + WX_HI_COMMAND_TIMEOUT, false); } static int txgbe_identify_sfp_hostif(struct wx *wx, struct txgbe_hic_i2c_read *buffer) @@ -103,7 +103,7 @@ static int txgbe_set_phy_link_hostif(struct wx *wx, int speed, int autoneg, int buffer.duplex = duplex; return wx_host_interface_command(wx, (u32 *)&buffer, sizeof(buffer), - WX_HI_COMMAND_TIMEOUT, true); + WX_HI_COMMAND_TIMEOUT, false); } static void txgbe_get_link_capabilities(struct wx *wx) From 68c62b3e53901846b5f68c5a8bade72a5d9c0b87 Mon Sep 17 00:00:00 2001 From: Marc Kleine-Budde Date: Sat, 10 Jan 2026 12:52:27 +0100 Subject: [PATCH 2829/3902] can: ems_usb: ems_usb_read_bulk_callback(): fix URB memory leak commit 0ce73a0eb5a27070957b67fd74059b6da89cc516 upstream. Fix similar memory leak as in commit 7352e1d5932a ("can: gs_usb: gs_usb_receive_bulk_callback(): fix URB memory leak"). In ems_usb_open(), the URBs for USB-in transfers are allocated, added to the dev->rx_submitted anchor and submitted. In the complete callback ems_usb_read_bulk_callback(), the URBs are processed and resubmitted. In ems_usb_close() the URBs are freed by calling usb_kill_anchored_urbs(&dev->rx_submitted). However, this does not take into account that the USB framework unanchors the URB before the complete function is called. This means that once an in-URB has been completed, it is no longer anchored and is ultimately not released in ems_usb_close(). Fix the memory leak by anchoring the URB in the ems_usb_read_bulk_callback() to the dev->rx_submitted anchor. Fixes: 702171adeed3 ("ems_usb: Added support for EMS CPC-USB/ARM7 CAN/USB interface") Cc: stable@vger.kernel.org Link: https://patch.msgid.link/20260116-can_usb-fix-memory-leak-v2-1-4b8cb2915571@pengutronix.de Signed-off-by: Marc Kleine-Budde Signed-off-by: Greg Kroah-Hartman --- drivers/net/can/usb/ems_usb.c | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/drivers/net/can/usb/ems_usb.c b/drivers/net/can/usb/ems_usb.c index 5355bac4dccbe0..fac8ac79df59f7 100644 --- a/drivers/net/can/usb/ems_usb.c +++ b/drivers/net/can/usb/ems_usb.c @@ -486,11 +486,17 @@ static void ems_usb_read_bulk_callback(struct urb *urb) urb->transfer_buffer, RX_BUFFER_SIZE, ems_usb_read_bulk_callback, dev); + usb_anchor_urb(urb, &dev->rx_submitted); + retval = usb_submit_urb(urb, GFP_ATOMIC); + if (!retval) + return; + + usb_unanchor_urb(urb); if (retval == -ENODEV) netif_device_detach(netdev); - else if (retval) + else netdev_err(netdev, "failed resubmitting read bulk urb: %d\n", retval); } From a9503ae43256e80db5cba9d449b238607164c51d Mon Sep 17 00:00:00 2001 From: Marc Kleine-Budde Date: Sat, 10 Jan 2026 12:52:27 +0100 Subject: [PATCH 2830/3902] can: esd_usb: esd_usb_read_bulk_callback(): fix URB memory leak commit 5a4391bdc6c8357242f62f22069c865b792406b3 upstream. Fix similar memory leak as in commit 7352e1d5932a ("can: gs_usb: gs_usb_receive_bulk_callback(): fix URB memory leak"). In esd_usb_open(), the URBs for USB-in transfers are allocated, added to the dev->rx_submitted anchor and submitted. In the complete callback esd_usb_read_bulk_callback(), the URBs are processed and resubmitted. In esd_usb_close() the URBs are freed by calling usb_kill_anchored_urbs(&dev->rx_submitted). However, this does not take into account that the USB framework unanchors the URB before the complete function is called. This means that once an in-URB has been completed, it is no longer anchored and is ultimately not released in esd_usb_close(). Fix the memory leak by anchoring the URB in the esd_usb_read_bulk_callback() to the dev->rx_submitted anchor. Fixes: 96d8e90382dc ("can: Add driver for esd CAN-USB/2 device") Cc: stable@vger.kernel.org Link: https://patch.msgid.link/20260116-can_usb-fix-memory-leak-v2-2-4b8cb2915571@pengutronix.de Signed-off-by: Marc Kleine-Budde Signed-off-by: Greg Kroah-Hartman --- drivers/net/can/usb/esd_usb.c | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/drivers/net/can/usb/esd_usb.c b/drivers/net/can/usb/esd_usb.c index 9bc1824d7be6aa..e58f0bfa84a4ca 100644 --- a/drivers/net/can/usb/esd_usb.c +++ b/drivers/net/can/usb/esd_usb.c @@ -541,13 +541,20 @@ static void esd_usb_read_bulk_callback(struct urb *urb) urb->transfer_buffer, ESD_USB_RX_BUFFER_SIZE, esd_usb_read_bulk_callback, dev); + usb_anchor_urb(urb, &dev->rx_submitted); + err = usb_submit_urb(urb, GFP_ATOMIC); + if (!err) + return; + + usb_unanchor_urb(urb); + if (err == -ENODEV) { for (i = 0; i < dev->net_count; i++) { if (dev->nets[i]) netif_device_detach(dev->nets[i]->netdev); } - } else if (err) { + } else { dev_err(dev->udev->dev.parent, "failed resubmitting read bulk urb: %pe\n", ERR_PTR(err)); } From 3b1a593eab941c3f32417896cc7df564191f2482 Mon Sep 17 00:00:00 2001 From: Marc Kleine-Budde Date: Sat, 10 Jan 2026 12:52:27 +0100 Subject: [PATCH 2831/3902] can: kvaser_usb: kvaser_usb_read_bulk_callback(): fix URB memory leak commit 248e8e1a125fa875158df521b30f2cc7e27eeeaa upstream. Fix similar memory leak as in commit 7352e1d5932a ("can: gs_usb: gs_usb_receive_bulk_callback(): fix URB memory leak"). In kvaser_usb_set_{,data_}bittiming() -> kvaser_usb_setup_rx_urbs(), the URBs for USB-in transfers are allocated, added to the dev->rx_submitted anchor and submitted. In the complete callback kvaser_usb_read_bulk_callback(), the URBs are processed and resubmitted. In kvaser_usb_remove_interfaces() the URBs are freed by calling usb_kill_anchored_urbs(&dev->rx_submitted). However, this does not take into account that the USB framework unanchors the URB before the complete function is called. This means that once an in-URB has been completed, it is no longer anchored and is ultimately not released in usb_kill_anchored_urbs(). Fix the memory leak by anchoring the URB in the kvaser_usb_read_bulk_callback() to the dev->rx_submitted anchor. Fixes: 080f40a6fa28 ("can: kvaser_usb: Add support for Kvaser CAN/USB devices") Cc: stable@vger.kernel.org Link: https://patch.msgid.link/20260116-can_usb-fix-memory-leak-v2-3-4b8cb2915571@pengutronix.de Signed-off-by: Marc Kleine-Budde Signed-off-by: Greg Kroah-Hartman --- drivers/net/can/usb/kvaser_usb/kvaser_usb_core.c | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/drivers/net/can/usb/kvaser_usb/kvaser_usb_core.c b/drivers/net/can/usb/kvaser_usb/kvaser_usb_core.c index 90e77fa0ff4a58..74aa1712686720 100644 --- a/drivers/net/can/usb/kvaser_usb/kvaser_usb_core.c +++ b/drivers/net/can/usb/kvaser_usb/kvaser_usb_core.c @@ -361,7 +361,14 @@ static void kvaser_usb_read_bulk_callback(struct urb *urb) urb->transfer_buffer, KVASER_USB_RX_BUFFER_SIZE, kvaser_usb_read_bulk_callback, dev); + usb_anchor_urb(urb, &dev->rx_submitted); + err = usb_submit_urb(urb, GFP_ATOMIC); + if (!err) + return; + + usb_unanchor_urb(urb); + if (err == -ENODEV) { for (i = 0; i < dev->nchannels; i++) { struct kvaser_usb_net_priv *priv; @@ -372,7 +379,7 @@ static void kvaser_usb_read_bulk_callback(struct urb *urb) netif_device_detach(priv->netdev); } - } else if (err) { + } else { dev_err(&dev->intf->dev, "Failed resubmitting read bulk urb: %d\n", err); } From d374d715e338dfc3804aaa006fa6e470ffebb264 Mon Sep 17 00:00:00 2001 From: Marc Kleine-Budde Date: Sat, 10 Jan 2026 12:52:27 +0100 Subject: [PATCH 2832/3902] can: mcba_usb: mcba_usb_read_bulk_callback(): fix URB memory leak commit 710a7529fb13c5a470258ff5508ed3c498d54729 upstream. Fix similar memory leak as in commit 7352e1d5932a ("can: gs_usb: gs_usb_receive_bulk_callback(): fix URB memory leak"). In mcba_usb_probe() -> mcba_usb_start(), the URBs for USB-in transfers are allocated, added to the priv->rx_submitted anchor and submitted. In the complete callback mcba_usb_read_bulk_callback(), the URBs are processed and resubmitted. In mcba_usb_close() -> mcba_urb_unlink() the URBs are freed by calling usb_kill_anchored_urbs(&priv->rx_submitted). However, this does not take into account that the USB framework unanchors the URB before the complete function is called. This means that once an in-URB has been completed, it is no longer anchored and is ultimately not released in usb_kill_anchored_urbs(). Fix the memory leak by anchoring the URB in the mcba_usb_read_bulk_callback()to the priv->rx_submitted anchor. Fixes: 51f3baad7de9 ("can: mcba_usb: Add support for Microchip CAN BUS Analyzer") Cc: stable@vger.kernel.org Link: https://patch.msgid.link/20260116-can_usb-fix-memory-leak-v2-4-4b8cb2915571@pengutronix.de Signed-off-by: Marc Kleine-Budde Signed-off-by: Greg Kroah-Hartman --- drivers/net/can/usb/mcba_usb.c | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/drivers/net/can/usb/mcba_usb.c b/drivers/net/can/usb/mcba_usb.c index 1f9b915094e64d..40cc158c1a672f 100644 --- a/drivers/net/can/usb/mcba_usb.c +++ b/drivers/net/can/usb/mcba_usb.c @@ -608,11 +608,17 @@ static void mcba_usb_read_bulk_callback(struct urb *urb) urb->transfer_buffer, MCBA_USB_RX_BUFF_SIZE, mcba_usb_read_bulk_callback, priv); + usb_anchor_urb(urb, &priv->rx_submitted); + retval = usb_submit_urb(urb, GFP_ATOMIC); + if (!retval) + return; + + usb_unanchor_urb(urb); if (retval == -ENODEV) netif_device_detach(netdev); - else if (retval) + else netdev_err(netdev, "failed resubmitting read bulk urb: %d\n", retval); } From 07e9373739c6388af9d99797cdb2e79dbbcbe92b Mon Sep 17 00:00:00 2001 From: Marc Kleine-Budde Date: Sat, 10 Jan 2026 12:52:27 +0100 Subject: [PATCH 2833/3902] can: usb_8dev: usb_8dev_read_bulk_callback(): fix URB memory leak commit f7a980b3b8f80fe367f679da376cf76e800f9480 upstream. Fix similar memory leak as in commit 7352e1d5932a ("can: gs_usb: gs_usb_receive_bulk_callback(): fix URB memory leak"). In usb_8dev_open() -> usb_8dev_start(), the URBs for USB-in transfers are allocated, added to the priv->rx_submitted anchor and submitted. In the complete callback usb_8dev_read_bulk_callback(), the URBs are processed and resubmitted. In usb_8dev_close() -> unlink_all_urbs() the URBs are freed by calling usb_kill_anchored_urbs(&priv->rx_submitted). However, this does not take into account that the USB framework unanchors the URB before the complete function is called. This means that once an in-URB has been completed, it is no longer anchored and is ultimately not released in usb_kill_anchored_urbs(). Fix the memory leak by anchoring the URB in the usb_8dev_read_bulk_callback() to the priv->rx_submitted anchor. Fixes: 0024d8ad1639 ("can: usb_8dev: Add support for USB2CAN interface from 8 devices") Cc: stable@vger.kernel.org Link: https://patch.msgid.link/20260116-can_usb-fix-memory-leak-v2-5-4b8cb2915571@pengutronix.de Signed-off-by: Marc Kleine-Budde Signed-off-by: Greg Kroah-Hartman --- drivers/net/can/usb/usb_8dev.c | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/drivers/net/can/usb/usb_8dev.c b/drivers/net/can/usb/usb_8dev.c index 8a5596ce4e4631..0fedfc287f1f63 100644 --- a/drivers/net/can/usb/usb_8dev.c +++ b/drivers/net/can/usb/usb_8dev.c @@ -541,11 +541,17 @@ static void usb_8dev_read_bulk_callback(struct urb *urb) urb->transfer_buffer, RX_BUFFER_SIZE, usb_8dev_read_bulk_callback, priv); + usb_anchor_urb(urb, &priv->rx_submitted); + retval = usb_submit_urb(urb, GFP_ATOMIC); + if (!retval) + return; + + usb_unanchor_urb(urb); if (retval == -ENODEV) netif_device_detach(netdev); - else if (retval) + else netdev_err(netdev, "failed resubmitting read bulk urb: %d\n", retval); } From ce2cca8e27d1680f26aee857c351096600e3b94a Mon Sep 17 00:00:00 2001 From: Likun Gao Date: Mon, 15 Dec 2025 11:33:58 +0800 Subject: [PATCH 2834/3902] drm/amdgpu: remove frame cntl for gfx v12 commit 10343253328e0dbdb465bff709a2619a08fe01ad upstream. Remove emit_frame_cntl function for gfx v12, which is not support. Signed-off-by: Likun Gao Reviewed-by: Hawking Zhang Signed-off-by: Alex Deucher (cherry picked from commit 5aaa5058dec5bfdcb24c42fe17ad91565a3037ca) Cc: stable@vger.kernel.org Signed-off-by: Greg Kroah-Hartman --- drivers/gpu/drm/amd/amdgpu/gfx_v12_0.c | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/drivers/gpu/drm/amd/amdgpu/gfx_v12_0.c b/drivers/gpu/drm/amd/amdgpu/gfx_v12_0.c index 93fde0f9af87f8..0578f1a94b2470 100644 --- a/drivers/gpu/drm/amd/amdgpu/gfx_v12_0.c +++ b/drivers/gpu/drm/amd/amdgpu/gfx_v12_0.c @@ -278,7 +278,6 @@ static void gfx_v12_0_select_se_sh(struct amdgpu_device *adev, u32 se_num, u32 sh_num, u32 instance, int xcc_id); static u32 gfx_v12_0_get_wgp_active_bitmap_per_sh(struct amdgpu_device *adev); -static void gfx_v12_0_ring_emit_frame_cntl(struct amdgpu_ring *ring, bool start, bool secure); static void gfx_v12_0_ring_emit_wreg(struct amdgpu_ring *ring, uint32_t reg, uint32_t val); static int gfx_v12_0_wait_for_rlc_autoload_complete(struct amdgpu_device *adev); @@ -4633,16 +4632,6 @@ static int gfx_v12_0_ring_preempt_ib(struct amdgpu_ring *ring) return r; } -static void gfx_v12_0_ring_emit_frame_cntl(struct amdgpu_ring *ring, - bool start, - bool secure) -{ - uint32_t v = secure ? FRAME_TMZ : 0; - - amdgpu_ring_write(ring, PACKET3(PACKET3_FRAME_CONTROL, 0)); - amdgpu_ring_write(ring, v | FRAME_CMD(start ? 0 : 1)); -} - static void gfx_v12_0_ring_emit_rreg(struct amdgpu_ring *ring, uint32_t reg, uint32_t reg_val_offs) { @@ -5519,7 +5508,6 @@ static const struct amdgpu_ring_funcs gfx_v12_0_ring_funcs_gfx = { .emit_cntxcntl = gfx_v12_0_ring_emit_cntxcntl, .init_cond_exec = gfx_v12_0_ring_emit_init_cond_exec, .preempt_ib = gfx_v12_0_ring_preempt_ib, - .emit_frame_cntl = gfx_v12_0_ring_emit_frame_cntl, .emit_wreg = gfx_v12_0_ring_emit_wreg, .emit_reg_wait = gfx_v12_0_ring_emit_reg_wait, .emit_reg_write_reg_wait = gfx_v12_0_ring_emit_reg_write_reg_wait, From 569ed6a73e927a34cae4ae6de1464c0737a5ec44 Mon Sep 17 00:00:00 2001 From: Osama Abdelkader Date: Fri, 2 Jan 2026 16:55:52 +0100 Subject: [PATCH 2835/3902] drm/bridge: synopsys: dw-dp: fix error paths of dw_dp_bind commit 1a0f69e3c28477b97d3609569b7e8feb4b6162e8 upstream. Fix several issues in dw_dp_bind() error handling: 1. Missing return after drm_bridge_attach() failure - the function continued execution instead of returning an error. 2. Resource leak: drm_dp_aux_register() is not a devm function, so drm_dp_aux_unregister() must be called on all error paths after aux registration succeeds. This affects errors from: - drm_bridge_attach() - phy_init() - devm_add_action_or_reset() - platform_get_irq() - devm_request_threaded_irq() 3. Bug fix: platform_get_irq() returns the IRQ number or a negative error code, but the error path was returning ERR_PTR(ret) instead of ERR_PTR(dp->irq). Use a goto label for cleanup to ensure consistent error handling. Fixes: 86eecc3a9c2e ("drm/bridge: synopsys: Add DW DPTX Controller support library") Cc: stable@vger.kernel.org Signed-off-by: Osama Abdelkader Reviewed-by: Louis Chauvet Reviewed-by: Luca Ceresoli Link: https://patch.msgid.link/20260102155553.13243-1-osama.abdelkader@gmail.com Signed-off-by: Luca Ceresoli Signed-off-by: Greg Kroah-Hartman --- drivers/gpu/drm/bridge/synopsys/dw-dp.c | 20 ++++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/drivers/gpu/drm/bridge/synopsys/dw-dp.c b/drivers/gpu/drm/bridge/synopsys/dw-dp.c index 9bbfe8da3de026..e82960163018aa 100644 --- a/drivers/gpu/drm/bridge/synopsys/dw-dp.c +++ b/drivers/gpu/drm/bridge/synopsys/dw-dp.c @@ -2060,33 +2060,41 @@ struct dw_dp *dw_dp_bind(struct device *dev, struct drm_encoder *encoder, } ret = drm_bridge_attach(encoder, bridge, NULL, DRM_BRIDGE_ATTACH_NO_CONNECTOR); - if (ret) + if (ret) { dev_err_probe(dev, ret, "Failed to attach bridge\n"); + goto unregister_aux; + } dw_dp_init_hw(dp); ret = phy_init(dp->phy); if (ret) { dev_err_probe(dev, ret, "phy init failed\n"); - return ERR_PTR(ret); + goto unregister_aux; } ret = devm_add_action_or_reset(dev, dw_dp_phy_exit, dp); if (ret) - return ERR_PTR(ret); + goto unregister_aux; dp->irq = platform_get_irq(pdev, 0); - if (dp->irq < 0) - return ERR_PTR(ret); + if (dp->irq < 0) { + ret = dp->irq; + goto unregister_aux; + } ret = devm_request_threaded_irq(dev, dp->irq, NULL, dw_dp_irq, IRQF_ONESHOT, dev_name(dev), dp); if (ret) { dev_err_probe(dev, ret, "failed to request irq\n"); - return ERR_PTR(ret); + goto unregister_aux; } return dp; + +unregister_aux: + drm_dp_aux_unregister(&dp->aux); + return ERR_PTR(ret); } EXPORT_SYMBOL_GPL(dw_dp_bind); From 85d4652f9cdcf334a096262ac743a197f84450dd Mon Sep 17 00:00:00 2001 From: Matthew Brost Date: Wed, 7 Jan 2026 12:57:32 -0800 Subject: [PATCH 2836/3902] drm/xe: Adjust page count tracepoints in shrinker MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit commit ca9e5115e870b9a531deb02752055a8a587904e3 upstream. Page accounting can change via the shrinker without calling xe_ttm_tt_unpopulate(), which normally updates page count tracepoints through update_global_total_pages. Add a call to update_global_total_pages when the shrinker successfully shrinks a BO. v2: - Don't adjust global accounting when pinning (Stuart) Cc: stable@vger.kernel.org Fixes: ce3d39fae3d3 ("drm/xe/bo: add GPU memory trace points") Signed-off-by: Matthew Brost Reviewed-by: Stuart Summers Link: https://patch.msgid.link/20260107205732.2267541-1-matthew.brost@intel.com (cherry picked from commit cc54eabdfbf0c5b6638edc50002cfafac1f1e18b) Signed-off-by: Thomas Hellström Signed-off-by: Greg Kroah-Hartman --- drivers/gpu/drm/xe/xe_bo.c | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/drivers/gpu/drm/xe/xe_bo.c b/drivers/gpu/drm/xe/xe_bo.c index d5b8332a04ecf8..e2e28ff73925be 100644 --- a/drivers/gpu/drm/xe/xe_bo.c +++ b/drivers/gpu/drm/xe/xe_bo.c @@ -1008,6 +1008,7 @@ static long xe_bo_shrink_purge(struct ttm_operation_ctx *ctx, unsigned long *scanned) { struct xe_device *xe = ttm_to_xe_device(bo->bdev); + struct ttm_tt *tt = bo->ttm; long lret; /* Fake move to system, without copying data. */ @@ -1032,8 +1033,10 @@ static long xe_bo_shrink_purge(struct ttm_operation_ctx *ctx, .writeback = false, .allow_move = false}); - if (lret > 0) + if (lret > 0) { xe_ttm_tt_account_subtract(xe, bo->ttm); + update_global_total_pages(bo->bdev, -(long)tt->num_pages); + } return lret; } @@ -1119,8 +1122,10 @@ long xe_bo_shrink(struct ttm_operation_ctx *ctx, struct ttm_buffer_object *bo, if (needs_rpm) xe_pm_runtime_put(xe); - if (lret > 0) + if (lret > 0) { xe_ttm_tt_account_subtract(xe, tt); + update_global_total_pages(bo->bdev, -(long)tt->num_pages); + } out_unref: xe_bo_put(xe_bo); From e7200b0a4b2a51c970b687f78f237f11cc7467a0 Mon Sep 17 00:00:00 2001 From: Marco Crivellari Date: Thu, 8 Jan 2026 19:01:48 +0100 Subject: [PATCH 2837/3902] drm/xe: fix WQ_MEM_RECLAIM passed as max_active to alloc_workqueue() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit commit 6f287b1c8d0e255e94e54116ebbe126515f5c911 upstream. Workqueue xe-ggtt-wq has been allocated using WQ_MEM_RECLAIM, but the flag has been passed as 3rd parameter (max_active) instead of 2nd (flags) creating the workqueue as per-cpu with max_active = 8 (the WQ_MEM_RECLAIM value). So change this by set WQ_MEM_RECLAIM as the 2nd parameter with a default max_active. Fixes: 60df57e496e4 ("drm/xe: Mark GGTT work queue with WQ_MEM_RECLAIM") Cc: stable@vger.kernel.org Signed-off-by: Marco Crivellari Reviewed-by: Matthew Brost Signed-off-by: Matthew Brost Link: https://patch.msgid.link/20260108180148.423062-1-marco.crivellari@suse.com (cherry picked from commit aa39abc08e77d66ebb0c8c9ec4cc8d38ded34dc9) Signed-off-by: Thomas Hellström Signed-off-by: Greg Kroah-Hartman --- drivers/gpu/drm/xe/xe_ggtt.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/gpu/drm/xe/xe_ggtt.c b/drivers/gpu/drm/xe/xe_ggtt.c index 5edc0cad47e20d..20d9171bd3d0ab 100644 --- a/drivers/gpu/drm/xe/xe_ggtt.c +++ b/drivers/gpu/drm/xe/xe_ggtt.c @@ -291,7 +291,7 @@ int xe_ggtt_init_early(struct xe_ggtt *ggtt) else ggtt->pt_ops = &xelp_pt_ops; - ggtt->wq = alloc_workqueue("xe-ggtt-wq", 0, WQ_MEM_RECLAIM); + ggtt->wq = alloc_workqueue("xe-ggtt-wq", WQ_MEM_RECLAIM, 0); if (!ggtt->wq) return -ENOMEM; From 919f27eb24b0e82ea232379bec40a5a52e3483d9 Mon Sep 17 00:00:00 2001 From: Tzung-Bi Shih Date: Fri, 16 Jan 2026 08:10:18 +0000 Subject: [PATCH 2838/3902] gpio: cdev: Correct return code on memory allocation failure commit faff6846474e99295a139997f93ef6db222b5cee upstream. -ENOMEM is a more appropriate return code for memory allocation failures. Correct it. Cc: stable@vger.kernel.org Fixes: 20bddcb40b2b ("gpiolib: cdev: replace locking wrappers for gpio_device with guards") Signed-off-by: Tzung-Bi Shih Link: https://lore.kernel.org/r/20260116081036.352286-6-tzungbi@kernel.org Signed-off-by: Bartosz Golaszewski Signed-off-by: Greg Kroah-Hartman --- drivers/gpio/gpiolib-cdev.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/gpio/gpiolib-cdev.c b/drivers/gpio/gpiolib-cdev.c index d925e75d1dce10..858110eda150eb 100644 --- a/drivers/gpio/gpiolib-cdev.c +++ b/drivers/gpio/gpiolib-cdev.c @@ -2720,7 +2720,7 @@ static int gpio_chrdev_open(struct inode *inode, struct file *file) cdev = kzalloc(sizeof(*cdev), GFP_KERNEL); if (!cdev) - return -ENODEV; + return -ENOMEM; cdev->watched_lines = bitmap_zalloc(gdev->ngpio, GFP_KERNEL); if (!cdev->watched_lines) From 16414341b0dd58b650b5df45c79115bc5977bb76 Mon Sep 17 00:00:00 2001 From: Tzung-Bi Shih Date: Tue, 20 Jan 2026 03:08:56 +0000 Subject: [PATCH 2839/3902] gpio: cdev: Fix resource leaks on errors in lineinfo_changed_notify() commit 70b3c280533167749a8f740acaa8ef720f78f984 upstream. On error handling paths, lineinfo_changed_notify() doesn't free the allocated resources which results leaks. Fix it. Cc: stable@vger.kernel.org Fixes: d4cd0902c156 ("gpio: cdev: make sure the cdev fd is still active before emitting events") Signed-off-by: Tzung-Bi Shih Link: https://lore.kernel.org/r/20260120030857.2144847-1-tzungbi@kernel.org Signed-off-by: Bartosz Golaszewski Signed-off-by: Greg Kroah-Hartman --- drivers/gpio/gpiolib-cdev.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/gpio/gpiolib-cdev.c b/drivers/gpio/gpiolib-cdev.c index 858110eda150eb..dadd4682a3b529 100644 --- a/drivers/gpio/gpiolib-cdev.c +++ b/drivers/gpio/gpiolib-cdev.c @@ -2573,6 +2573,7 @@ static int lineinfo_changed_notify(struct notifier_block *nb, ctx = kzalloc(sizeof(*ctx), GFP_ATOMIC); if (!ctx) { pr_err("Failed to allocate memory for line info notification\n"); + fput(fp); return NOTIFY_DONE; } From ab140fc93108360cfb53ff04592be05547aa5c0f Mon Sep 17 00:00:00 2001 From: Tzung-Bi Shih Date: Tue, 20 Jan 2026 09:26:50 +0000 Subject: [PATCH 2840/3902] gpio: cdev: Fix resource leaks on errors in gpiolib_cdev_register() commit 8a8c942cad4cd12f739a8bb60cac77fd173c4e07 upstream. On error handling paths, gpiolib_cdev_register() doesn't free the allocated resources which results leaks. Fix it. Cc: stable@vger.kernel.org Fixes: 7b9b77a8bba9 ("gpiolib: add a per-gpio_device line state notification workqueue") Fixes: d83cee3d2bb1 ("gpio: protect the pointer to gpio_chip in gpio_device with SRCU") Signed-off-by: Tzung-Bi Shih Link: https://lore.kernel.org/r/20260120092650.2305319-1-tzungbi@kernel.org Signed-off-by: Bartosz Golaszewski Signed-off-by: Greg Kroah-Hartman --- drivers/gpio/gpiolib-cdev.c | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/drivers/gpio/gpiolib-cdev.c b/drivers/gpio/gpiolib-cdev.c index dadd4682a3b529..e76bcbd647539b 100644 --- a/drivers/gpio/gpiolib-cdev.c +++ b/drivers/gpio/gpiolib-cdev.c @@ -2821,13 +2821,18 @@ int gpiolib_cdev_register(struct gpio_device *gdev, dev_t devt) return -ENOMEM; ret = cdev_device_add(&gdev->chrdev, &gdev->dev); - if (ret) + if (ret) { + destroy_workqueue(gdev->line_state_wq); return ret; + } guard(srcu)(&gdev->srcu); gc = srcu_dereference(gdev->chip, &gdev->srcu); - if (!gc) + if (!gc) { + cdev_device_del(&gdev->chrdev, &gdev->dev); + destroy_workqueue(gdev->line_state_wq); return -ENODEV; + } gpiochip_dbg(gc, "added GPIO chardev (%d:%d)\n", MAJOR(devt), gdev->id); From 908795c02dbc7f3acc33b05b46c73e219fbbc2f8 Mon Sep 17 00:00:00 2001 From: Ravindra Date: Wed, 15 Oct 2025 15:09:02 +0530 Subject: [PATCH 2841/3902] Bluetooth: btintel_pcie: Support for S4 (Hibernate) commit 1fb0d830dab89d0dc99bb84a7087b0ceca63d2d8 upstream. During S4 (hibernate), the Bluetooth device loses power. Upon resume, the driver performs the following actions: 1. Unregisters hdev 2. Calls function level reset 3. Registers hdev Test case: - run command sudo rtcwake -m disk -s 60 Signed-off-by: Ravindra Signed-off-by: Kiran K Signed-off-by: Luiz Augusto von Dentz Cc: Mariappan Ramasamy Signed-off-by: Greg Kroah-Hartman --- drivers/bluetooth/btintel_pcie.c | 41 ++++++++++++++++++++++++++++++++ drivers/bluetooth/btintel_pcie.h | 2 ++ 2 files changed, 43 insertions(+) diff --git a/drivers/bluetooth/btintel_pcie.c b/drivers/bluetooth/btintel_pcie.c index a075d8ec467738..f280bcc61bbfb7 100644 --- a/drivers/bluetooth/btintel_pcie.c +++ b/drivers/bluetooth/btintel_pcie.c @@ -825,6 +825,11 @@ static inline bool btintel_pcie_in_d0(struct btintel_pcie_data *data) return !(data->boot_stage_cache & BTINTEL_PCIE_CSR_BOOT_STAGE_D3_STATE_READY); } +static inline bool btintel_pcie_in_device_halt(struct btintel_pcie_data *data) +{ + return data->boot_stage_cache & BTINTEL_PCIE_CSR_BOOT_STAGE_DEVICE_HALTED; +} + static void btintel_pcie_wr_sleep_cntrl(struct btintel_pcie_data *data, u32 dxstate) { @@ -2532,6 +2537,8 @@ static int btintel_pcie_suspend_late(struct device *dev, pm_message_t mesg) dxstate = (mesg.event == PM_EVENT_SUSPEND ? BTINTEL_PCIE_STATE_D3_HOT : BTINTEL_PCIE_STATE_D3_COLD); + data->pm_sx_event = mesg.event; + data->gp0_received = false; start = ktime_get(); @@ -2581,6 +2588,20 @@ static int btintel_pcie_resume(struct device *dev) start = ktime_get(); + /* When the system enters S4 (hibernate) mode, bluetooth device loses + * power, which results in the erasure of its loaded firmware. + * Consequently, function level reset (flr) is required on system + * resume to bring the controller back into an operational state by + * initiating a new firmware download. + */ + + if (data->pm_sx_event == PM_EVENT_FREEZE || + data->pm_sx_event == PM_EVENT_HIBERNATE) { + set_bit(BTINTEL_PCIE_CORE_HALTED, &data->flags); + btintel_pcie_reset(data->hdev); + return 0; + } + /* Refer: 6.4.11.7 -> Platform power management */ btintel_pcie_wr_sleep_cntrl(data, BTINTEL_PCIE_STATE_D0); err = wait_event_timeout(data->gp0_wait_q, data->gp0_received, @@ -2589,6 +2610,26 @@ static int btintel_pcie_resume(struct device *dev) bt_dev_err(data->hdev, "Timeout (%u ms) on alive interrupt for D0 entry", BTINTEL_DEFAULT_INTR_TIMEOUT_MS); + + /* Trigger function level reset if the controller is in error + * state during resume() to bring back the controller to + * operational mode + */ + + data->boot_stage_cache = btintel_pcie_rd_reg32(data, + BTINTEL_PCIE_CSR_BOOT_STAGE_REG); + if (btintel_pcie_in_error(data) || + btintel_pcie_in_device_halt(data)) { + bt_dev_err(data->hdev, "Controller in error state for D0 entry"); + if (!test_and_set_bit(BTINTEL_PCIE_COREDUMP_INPROGRESS, + &data->flags)) { + data->dmp_hdr.trigger_reason = + BTINTEL_PCIE_TRIGGER_REASON_FW_ASSERT; + queue_work(data->workqueue, &data->rx_work); + } + set_bit(BTINTEL_PCIE_CORE_HALTED, &data->flags); + btintel_pcie_reset(data->hdev); + } return -EBUSY; } diff --git a/drivers/bluetooth/btintel_pcie.h b/drivers/bluetooth/btintel_pcie.h index 04b21f968ad30f..48e1ae1793e5cf 100644 --- a/drivers/bluetooth/btintel_pcie.h +++ b/drivers/bluetooth/btintel_pcie.h @@ -464,6 +464,7 @@ struct btintel_pcie_dump_header { * @txq: TX Queue struct * @rxq: RX Queue struct * @alive_intr_ctxt: Alive interrupt context + * @pm_sx_event: PM event on which system got suspended */ struct btintel_pcie_data { struct pci_dev *pdev; @@ -513,6 +514,7 @@ struct btintel_pcie_data { u32 alive_intr_ctxt; struct btintel_pcie_dbgc dbgc; struct btintel_pcie_dump_header dmp_hdr; + u8 pm_sx_event; }; static inline u32 btintel_pcie_rd_reg32(struct btintel_pcie_data *data, From 918ba220debc4705e0b2ee3518c15c268c39b84d Mon Sep 17 00:00:00 2001 From: "jianyun.gao" Date: Mon, 26 Jan 2026 14:12:20 -0500 Subject: [PATCH 2842/3902] mm: fix some typos in mm module [ Upstream commit b6c46600bfb28b4be4e9cff7bad4f2cf357e0fb7 ] Below are some typos in the code comments: intevals ==> intervals addesses ==> addresses unavaliable ==> unavailable facor ==> factor droping ==> dropping exlusive ==> exclusive decription ==> description confict ==> conflict desriptions ==> descriptions otherwize ==> otherwise vlaue ==> value cheching ==> checking exisitng ==> existing modifed ==> modified differenciate ==> differentiate refernece ==> reference permissons ==> permissions indepdenent ==> independent spliting ==> splitting Just fix it. Link: https://lkml.kernel.org/r/20250929002608.1633825-1-jianyungao89@gmail.com Signed-off-by: jianyun.gao Reviewed-by: SeongJae Park Reviewed-by: Wei Yang Reviewed-by: Dev Jain Reviewed-by: Liam R. Howlett Acked-by: Chris Li Signed-off-by: Andrew Morton Stable-dep-of: 3937027caecb ("mm/hugetlb: fix two comments related to huge_pmd_unshare()") Signed-off-by: Sasha Levin Signed-off-by: Greg Kroah-Hartman --- mm/damon/sysfs.c | 2 +- mm/gup.c | 2 +- mm/hugetlb.c | 6 +++--- mm/hugetlb_vmemmap.c | 6 +++--- mm/kmsan/core.c | 2 +- mm/ksm.c | 2 +- mm/memory-tiers.c | 2 +- mm/memory.c | 4 ++-- mm/secretmem.c | 2 +- mm/slab_common.c | 2 +- mm/slub.c | 2 +- mm/swapfile.c | 2 +- mm/userfaultfd.c | 2 +- mm/vma.c | 4 ++-- 14 files changed, 20 insertions(+), 20 deletions(-) diff --git a/mm/damon/sysfs.c b/mm/damon/sysfs.c index 2caeca5624ce8a..dec9f5d0d51237 100644 --- a/mm/damon/sysfs.c +++ b/mm/damon/sysfs.c @@ -1267,7 +1267,7 @@ enum damon_sysfs_cmd { DAMON_SYSFS_CMD_UPDATE_SCHEMES_EFFECTIVE_QUOTAS, /* * @DAMON_SYSFS_CMD_UPDATE_TUNED_INTERVALS: Update the tuned monitoring - * intevals. + * intervals. */ DAMON_SYSFS_CMD_UPDATE_TUNED_INTERVALS, /* diff --git a/mm/gup.c b/mm/gup.c index a8ba5112e4d096..d2524fe09338fd 100644 --- a/mm/gup.c +++ b/mm/gup.c @@ -2710,7 +2710,7 @@ EXPORT_SYMBOL(get_user_pages_unlocked); * * *) ptes can be read atomically by the architecture. * - * *) valid user addesses are below TASK_MAX_SIZE + * *) valid user addresses are below TASK_MAX_SIZE * * The last two assumptions can be relaxed by the addition of helper functions. * diff --git a/mm/hugetlb.c b/mm/hugetlb.c index 0455119716ec0c..4e016433e32e5b 100644 --- a/mm/hugetlb.c +++ b/mm/hugetlb.c @@ -2934,7 +2934,7 @@ typedef enum { * NOTE: This is mostly identical to MAP_CHG_NEEDED, except * that currently vma_needs_reservation() has an unwanted side * effect to either use end() or commit() to complete the - * transaction. Hence it needs to differenciate from NEEDED. + * transaction. Hence it needs to differentiate from NEEDED. */ MAP_CHG_ENFORCED = 2, } map_chg_state; @@ -6007,7 +6007,7 @@ void __unmap_hugepage_range(struct mmu_gather *tlb, struct vm_area_struct *vma, /* * If we unshared PMDs, the TLB flush was not recorded in mmu_gather. We * could defer the flush until now, since by holding i_mmap_rwsem we - * guaranteed that the last refernece would not be dropped. But we must + * guaranteed that the last reference would not be dropped. But we must * do the flushing before we return, as otherwise i_mmap_rwsem will be * dropped and the last reference to the shared PMDs page might be * dropped as well. @@ -7193,7 +7193,7 @@ long hugetlb_change_protection(struct vm_area_struct *vma, } else if (unlikely(is_pte_marker(pte))) { /* * Do nothing on a poison marker; page is - * corrupted, permissons do not apply. Here + * corrupted, permissions do not apply. Here * pte_marker_uffd_wp()==true implies !poison * because they're mutual exclusive. */ diff --git a/mm/hugetlb_vmemmap.c b/mm/hugetlb_vmemmap.c index ba0fb1b6a5a8eb..96ee2bd16ee153 100644 --- a/mm/hugetlb_vmemmap.c +++ b/mm/hugetlb_vmemmap.c @@ -75,7 +75,7 @@ static int vmemmap_split_pmd(pmd_t *pmd, struct page *head, unsigned long start, if (likely(pmd_leaf(*pmd))) { /* * Higher order allocations from buddy allocator must be able to - * be treated as indepdenent small pages (as they can be freed + * be treated as independent small pages (as they can be freed * individually). */ if (!PageReserved(head)) @@ -684,7 +684,7 @@ static void __hugetlb_vmemmap_optimize_folios(struct hstate *h, ret = hugetlb_vmemmap_split_folio(h, folio); /* - * Spliting the PMD requires allocating a page, thus lets fail + * Splitting the PMD requires allocating a page, thus let's fail * early once we encounter the first OOM. No point in retrying * as it can be dynamically done on remap with the memory * we get back from the vmemmap deduplication. @@ -715,7 +715,7 @@ static void __hugetlb_vmemmap_optimize_folios(struct hstate *h, /* * Pages to be freed may have been accumulated. If we * encounter an ENOMEM, free what we have and try again. - * This can occur in the case that both spliting fails + * This can occur in the case that both splitting fails * halfway and head page allocation also failed. In this * case __hugetlb_vmemmap_optimize_folio() would free memory * allowing more vmemmap remaps to occur. diff --git a/mm/kmsan/core.c b/mm/kmsan/core.c index 35ceaa8adb41eb..90f427b95a213e 100644 --- a/mm/kmsan/core.c +++ b/mm/kmsan/core.c @@ -33,7 +33,7 @@ bool kmsan_enabled __read_mostly; /* * Per-CPU KMSAN context to be used in interrupts, where current->kmsan is - * unavaliable. + * unavailable. */ DEFINE_PER_CPU(struct kmsan_ctx, kmsan_percpu_ctx); diff --git a/mm/ksm.c b/mm/ksm.c index ba97828f32903a..4f672f4f21407d 100644 --- a/mm/ksm.c +++ b/mm/ksm.c @@ -389,7 +389,7 @@ static unsigned long ewma(unsigned long prev, unsigned long curr) * exponentially weighted moving average. The new pages_to_scan value is * multiplied with that change factor: * - * new_pages_to_scan *= change facor + * new_pages_to_scan *= change factor * * The new_pages_to_scan value is limited by the cpu min and max values. It * calculates the cpu percent for the last scan and calculates the new diff --git a/mm/memory-tiers.c b/mm/memory-tiers.c index 0ea5c13f10a233..864811fff4093f 100644 --- a/mm/memory-tiers.c +++ b/mm/memory-tiers.c @@ -519,7 +519,7 @@ static inline void __init_node_memory_type(int node, struct memory_dev_type *mem * for each device getting added in the same NUMA node * with this specific memtype, bump the map count. We * Only take memtype device reference once, so that - * changing a node memtype can be done by droping the + * changing a node memtype can be done by dropping the * only reference count taken here. */ diff --git a/mm/memory.c b/mm/memory.c index b59ae7ce42ebc7..61748b762876f4 100644 --- a/mm/memory.c +++ b/mm/memory.c @@ -4328,7 +4328,7 @@ static inline bool should_try_to_free_swap(struct folio *folio, * If we want to map a page that's in the swapcache writable, we * have to detect via the refcount if we're really the exclusive * user. Try freeing the swapcache to get rid of the swapcache - * reference only in case it's likely that we'll be the exlusive user. + * reference only in case it's likely that we'll be the exclusive user. */ return (fault_flags & FAULT_FLAG_WRITE) && !folio_test_ksm(folio) && folio_ref_count(folio) == (1 + folio_nr_pages(folio)); @@ -5405,7 +5405,7 @@ vm_fault_t do_set_pmd(struct vm_fault *vmf, struct folio *folio, struct page *pa /** * set_pte_range - Set a range of PTEs to point to pages in a folio. - * @vmf: Fault decription. + * @vmf: Fault description. * @folio: The folio that contains @page. * @page: The first page to create a PTE for. * @nr: The number of PTEs to create. diff --git a/mm/secretmem.c b/mm/secretmem.c index b59350daffe318..9b0f5d9ec6f4b1 100644 --- a/mm/secretmem.c +++ b/mm/secretmem.c @@ -227,7 +227,7 @@ SYSCALL_DEFINE1(memfd_secret, unsigned int, flags) struct file *file; int fd, err; - /* make sure local flags do not confict with global fcntl.h */ + /* make sure local flags do not conflict with global fcntl.h */ BUILD_BUG_ON(SECRETMEM_FLAGS_MASK & O_CLOEXEC); if (!secretmem_enable || !can_set_direct_map()) diff --git a/mm/slab_common.c b/mm/slab_common.c index 29be54153fa915..87bde1d8916be6 100644 --- a/mm/slab_common.c +++ b/mm/slab_common.c @@ -259,7 +259,7 @@ static struct kmem_cache *create_cache(const char *name, * @object_size: The size of objects to be created in this cache. * @args: Additional arguments for the cache creation (see * &struct kmem_cache_args). - * @flags: See the desriptions of individual flags. The common ones are listed + * @flags: See the descriptions of individual flags. The common ones are listed * in the description below. * * Not to be called directly, use the kmem_cache_create() wrapper with the same diff --git a/mm/slub.c b/mm/slub.c index 559cb5f2be16cf..1e76c92fe37535 100644 --- a/mm/slub.c +++ b/mm/slub.c @@ -2533,7 +2533,7 @@ bool slab_free_hook(struct kmem_cache *s, void *x, bool init, memset((char *)kasan_reset_tag(x) + inuse, 0, s->size - inuse - rsize); /* - * Restore orig_size, otherwize kmalloc redzone overwritten + * Restore orig_size, otherwise kmalloc redzone overwritten * would be reported */ set_orig_size(s, x, orig_size); diff --git a/mm/swapfile.c b/mm/swapfile.c index 82524f8595eda8..89746abc473737 100644 --- a/mm/swapfile.c +++ b/mm/swapfile.c @@ -1703,7 +1703,7 @@ static bool swap_entries_put_map_nr(struct swap_info_struct *si, /* * Check if it's the last ref of swap entry in the freeing path. - * Qualified vlaue includes 1, SWAP_HAS_CACHE or SWAP_MAP_SHMEM. + * Qualified value includes 1, SWAP_HAS_CACHE or SWAP_MAP_SHMEM. */ static inline bool __maybe_unused swap_is_last_ref(unsigned char count) { diff --git a/mm/userfaultfd.c b/mm/userfaultfd.c index af61b95c89e4ec..0630f188c847c3 100644 --- a/mm/userfaultfd.c +++ b/mm/userfaultfd.c @@ -1578,7 +1578,7 @@ static int validate_move_areas(struct userfaultfd_ctx *ctx, /* * For now, we keep it simple and only move between writable VMAs. - * Access flags are equal, therefore cheching only the source is enough. + * Access flags are equal, therefore checking only the source is enough. */ if (!(src_vma->vm_flags & VM_WRITE)) return -EINVAL; diff --git a/mm/vma.c b/mm/vma.c index abe0da33c84461..9127eaeea93ffb 100644 --- a/mm/vma.c +++ b/mm/vma.c @@ -109,7 +109,7 @@ static inline bool is_mergeable_vma(struct vma_merge_struct *vmg, bool merge_nex static bool is_mergeable_anon_vma(struct vma_merge_struct *vmg, bool merge_next) { struct vm_area_struct *tgt = merge_next ? vmg->next : vmg->prev; - struct vm_area_struct *src = vmg->middle; /* exisitng merge case. */ + struct vm_area_struct *src = vmg->middle; /* existing merge case. */ struct anon_vma *tgt_anon = tgt->anon_vma; struct anon_vma *src_anon = vmg->anon_vma; @@ -798,7 +798,7 @@ static bool can_merge_remove_vma(struct vm_area_struct *vma) * Returns: The merged VMA if merge succeeds, or NULL otherwise. * * ASSUMPTIONS: - * - The caller must assign the VMA to be modifed to @vmg->middle. + * - The caller must assign the VMA to be modified to @vmg->middle. * - The caller must have set @vmg->prev to the previous VMA, if there is one. * - The caller must not set @vmg->next, as we determine this. * - The caller must hold a WRITE lock on the mm_struct->mmap_lock. From c5dbad55ce02a84a778ce33a47f6c6e6372ba8a2 Mon Sep 17 00:00:00 2001 From: "David Hildenbrand (Red Hat)" Date: Mon, 26 Jan 2026 14:12:21 -0500 Subject: [PATCH 2843/3902] mm/hugetlb: fix two comments related to huge_pmd_unshare() [ Upstream commit 3937027caecb4f8251e82dd857ba1d749bb5a428 ] Ever since we stopped using the page count to detect shared PMD page tables, these comments are outdated. The only reason we have to flush the TLB early is because once we drop the i_mmap_rwsem, the previously shared page table could get freed (to then get reallocated and used for other purpose). So we really have to flush the TLB before that could happen. So let's simplify the comments a bit. The "If we unshared PMDs, the TLB flush was not recorded in mmu_gather." part introduced as in commit a4a118f2eead ("hugetlbfs: flush TLBs correctly after huge_pmd_unshare") was confusing: sure it is recorded in the mmu_gather, otherwise tlb_flush_mmu_tlbonly() wouldn't do anything. So let's drop that comment while at it as well. We'll centralize these comments in a single helper as we rework the code next. Link: https://lkml.kernel.org/r/20251223214037.580860-3-david@kernel.org Fixes: 59d9094df3d7 ("mm: hugetlb: independent PMD page table shared count") Signed-off-by: David Hildenbrand (Red Hat) Reviewed-by: Rik van Riel Tested-by: Laurence Oberman Reviewed-by: Lorenzo Stoakes Acked-by: Oscar Salvador Reviewed-by: Harry Yoo Cc: Liu Shixin Cc: Lance Yang Cc: "Uschakow, Stanislav" Cc: Signed-off-by: Andrew Morton Signed-off-by: Sasha Levin Signed-off-by: Greg Kroah-Hartman --- mm/hugetlb.c | 24 ++++++++---------------- 1 file changed, 8 insertions(+), 16 deletions(-) diff --git a/mm/hugetlb.c b/mm/hugetlb.c index 4e016433e32e5b..6a60af4798bee0 100644 --- a/mm/hugetlb.c +++ b/mm/hugetlb.c @@ -6005,17 +6005,10 @@ void __unmap_hugepage_range(struct mmu_gather *tlb, struct vm_area_struct *vma, tlb_end_vma(tlb, vma); /* - * If we unshared PMDs, the TLB flush was not recorded in mmu_gather. We - * could defer the flush until now, since by holding i_mmap_rwsem we - * guaranteed that the last reference would not be dropped. But we must - * do the flushing before we return, as otherwise i_mmap_rwsem will be - * dropped and the last reference to the shared PMDs page might be - * dropped as well. - * - * In theory we could defer the freeing of the PMD pages as well, but - * huge_pmd_unshare() relies on the exact page_count for the PMD page to - * detect sharing, so we cannot defer the release of the page either. - * Instead, do flush now. + * There is nothing protecting a previously-shared page table that we + * unshared through huge_pmd_unshare() from getting freed after we + * release i_mmap_rwsem, so flush the TLB now. If huge_pmd_unshare() + * succeeded, flush the range corresponding to the pud. */ if (force_flush) tlb_flush_mmu_tlbonly(tlb); @@ -7226,11 +7219,10 @@ long hugetlb_change_protection(struct vm_area_struct *vma, cond_resched(); } /* - * Must flush TLB before releasing i_mmap_rwsem: x86's huge_pmd_unshare - * may have cleared our pud entry and done put_page on the page table: - * once we release i_mmap_rwsem, another task can do the final put_page - * and that page table be reused and filled with junk. If we actually - * did unshare a page of pmds, flush the range corresponding to the pud. + * There is nothing protecting a previously-shared page table that we + * unshared through huge_pmd_unshare() from getting freed after we + * release i_mmap_rwsem, so flush the TLB now. If huge_pmd_unshare() + * succeeded, flush the range corresponding to the pud. */ if (shared_pmd) flush_hugetlb_tlb_range(vma, range.start, range.end); From 2dabc85131c8405c55c027ad8bac47e15258b158 Mon Sep 17 00:00:00 2001 From: Andy Shevchenko Date: Mon, 26 Jan 2026 11:53:02 -0500 Subject: [PATCH 2844/3902] iio: core: Replace lockdep_set_class() + mutex_init() by combined call MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [ Upstream commit c76ba4b2644424b8dbacee80bb40991eac29d39e ] Replace lockdep_set_class() + mutex_init() by combined call mutex_init_with_key(). Signed-off-by: Andy Shevchenko Reviewed-by: Nuno Sá Signed-off-by: Jonathan Cameron Stable-dep-of: 9910159f0659 ("iio: core: add separate lockdep class for info_exist_lock") Signed-off-by: Sasha Levin Signed-off-by: Greg Kroah-Hartman --- drivers/iio/industrialio-core.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/drivers/iio/industrialio-core.c b/drivers/iio/industrialio-core.c index 5d2f35cf18bc3f..f69deefcfb6fdb 100644 --- a/drivers/iio/industrialio-core.c +++ b/drivers/iio/industrialio-core.c @@ -1717,9 +1717,8 @@ struct iio_dev *iio_device_alloc(struct device *parent, int sizeof_priv) INIT_LIST_HEAD(&iio_dev_opaque->ioctl_handlers); lockdep_register_key(&iio_dev_opaque->mlock_key); - lockdep_set_class(&iio_dev_opaque->mlock, &iio_dev_opaque->mlock_key); - mutex_init(&iio_dev_opaque->mlock); + mutex_init_with_key(&iio_dev_opaque->mlock, &iio_dev_opaque->mlock_key); mutex_init(&iio_dev_opaque->info_exist_lock); indio_dev->dev.parent = parent; From 25be3c170ff8e18472047694f7c006bbd497ff4c Mon Sep 17 00:00:00 2001 From: Rasmus Villemoes Date: Mon, 26 Jan 2026 11:53:03 -0500 Subject: [PATCH 2845/3902] iio: core: add separate lockdep class for info_exist_lock [ Upstream commit 9910159f06590c17df4fbddedaabb4c0201cc4cb ] When one iio device is a consumer of another, it is possible that the ->info_exist_lock of both ends up being taken when reading the value of the consumer device. Since they currently belong to the same lockdep class (being initialized in a single location with mutex_init()), that results in a lockdep warning CPU0 ---- lock(&iio_dev_opaque->info_exist_lock); lock(&iio_dev_opaque->info_exist_lock); *** DEADLOCK *** May be due to missing lock nesting notation 4 locks held by sensors/414: #0: c31fd6dc (&p->lock){+.+.}-{3:3}, at: seq_read_iter+0x44/0x4e4 #1: c4f5a1c4 (&of->mutex){+.+.}-{3:3}, at: kernfs_seq_start+0x1c/0xac #2: c2827548 (kn->active#34){.+.+}-{0:0}, at: kernfs_seq_start+0x30/0xac #3: c1dd2b68 (&iio_dev_opaque->info_exist_lock){+.+.}-{3:3}, at: iio_read_channel_processed_scale+0x24/0xd8 stack backtrace: CPU: 0 UID: 0 PID: 414 Comm: sensors Not tainted 6.17.11 #5 NONE Hardware name: Generic AM33XX (Flattened Device Tree) Call trace: unwind_backtrace from show_stack+0x10/0x14 show_stack from dump_stack_lvl+0x44/0x60 dump_stack_lvl from print_deadlock_bug+0x2b8/0x334 print_deadlock_bug from __lock_acquire+0x13a4/0x2ab0 __lock_acquire from lock_acquire+0xd0/0x2c0 lock_acquire from __mutex_lock+0xa0/0xe8c __mutex_lock from mutex_lock_nested+0x1c/0x24 mutex_lock_nested from iio_read_channel_raw+0x20/0x6c iio_read_channel_raw from rescale_read_raw+0x128/0x1c4 rescale_read_raw from iio_channel_read+0xe4/0xf4 iio_channel_read from iio_read_channel_processed_scale+0x6c/0xd8 iio_read_channel_processed_scale from iio_hwmon_read_val+0x68/0xbc iio_hwmon_read_val from dev_attr_show+0x18/0x48 dev_attr_show from sysfs_kf_seq_show+0x80/0x110 sysfs_kf_seq_show from seq_read_iter+0xdc/0x4e4 seq_read_iter from vfs_read+0x238/0x2e4 vfs_read from ksys_read+0x6c/0xec ksys_read from ret_fast_syscall+0x0/0x1c Just as the mlock_key already has its own lockdep class, add a lock_class_key for the info_exist mutex. Note that this has in theory been a problem since before IIO first left staging, but it only occurs when a chain of consumers is in use and that is not often done. Fixes: ac917a81117c ("staging:iio:core set the iio_dev.info pointer to null on unregister under lock.") Signed-off-by: Rasmus Villemoes Reviewed-by: Peter Rosin Cc: Signed-off-by: Jonathan Cameron Signed-off-by: Sasha Levin Signed-off-by: Greg Kroah-Hartman --- drivers/iio/industrialio-core.c | 4 +++- include/linux/iio/iio-opaque.h | 2 ++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/drivers/iio/industrialio-core.c b/drivers/iio/industrialio-core.c index f69deefcfb6fdb..117ffad4f3769a 100644 --- a/drivers/iio/industrialio-core.c +++ b/drivers/iio/industrialio-core.c @@ -1657,6 +1657,7 @@ static void iio_dev_release(struct device *device) mutex_destroy(&iio_dev_opaque->info_exist_lock); mutex_destroy(&iio_dev_opaque->mlock); + lockdep_unregister_key(&iio_dev_opaque->info_exist_key); lockdep_unregister_key(&iio_dev_opaque->mlock_key); ida_free(&iio_ida, iio_dev_opaque->id); @@ -1717,9 +1718,10 @@ struct iio_dev *iio_device_alloc(struct device *parent, int sizeof_priv) INIT_LIST_HEAD(&iio_dev_opaque->ioctl_handlers); lockdep_register_key(&iio_dev_opaque->mlock_key); + lockdep_register_key(&iio_dev_opaque->info_exist_key); mutex_init_with_key(&iio_dev_opaque->mlock, &iio_dev_opaque->mlock_key); - mutex_init(&iio_dev_opaque->info_exist_lock); + mutex_init_with_key(&iio_dev_opaque->info_exist_lock, &iio_dev_opaque->info_exist_key); indio_dev->dev.parent = parent; indio_dev->dev.type = &iio_device_type; diff --git a/include/linux/iio/iio-opaque.h b/include/linux/iio/iio-opaque.h index 4247497f3f8bd4..b87841a355f802 100644 --- a/include/linux/iio/iio-opaque.h +++ b/include/linux/iio/iio-opaque.h @@ -14,6 +14,7 @@ * @mlock: lock used to prevent simultaneous device state changes * @mlock_key: lockdep class for iio_dev lock * @info_exist_lock: lock to prevent use during removal + * @info_exist_key: lockdep class for info_exist lock * @trig_readonly: mark the current trigger immutable * @event_interface: event chrdevs associated with interrupt lines * @attached_buffers: array of buffers statically attached by the driver @@ -47,6 +48,7 @@ struct iio_dev_opaque { struct mutex mlock; struct lock_class_key mlock_key; struct mutex info_exist_lock; + struct lock_class_key info_exist_key; bool trig_readonly; struct iio_event_interface *event_interface; struct iio_buffer **attached_buffers; From 79966baabcfe9aea00f2f16f8750aa659ea4b62b Mon Sep 17 00:00:00 2001 From: Pradeep P V K Date: Mon, 26 Jan 2026 10:43:34 -0500 Subject: [PATCH 2846/3902] arm64: dts: qcom: talos: Correct UFS clocks ordering [ Upstream commit 8bb3754909cde5df4f8c1012bde220b97d8ee3bc ] The current UFS clocks does not align with their respective names, causing the ref_clk to be set to an incorrect frequency as below, which results in command timeouts. ufshcd-qcom 1d84000.ufshc: invalid ref_clk setting = 300000000 This commit fixes the issue by properly reordering the UFS clocks to match their names. Fixes: ea172f61f4fd ("arm64: dts: qcom: qcs615: Fix up UFS clocks") Cc: stable@vger.kernel.org Signed-off-by: Pradeep P V K Link: https://lore.kernel.org/r/20251126131146.16146-1-pradeep.pragallapati@oss.qualcomm.com Signed-off-by: Bjorn Andersson Signed-off-by: Sasha Levin Signed-off-by: Greg Kroah-Hartman --- arch/arm64/boot/dts/qcom/sm6150.dtsi | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/arch/arm64/boot/dts/qcom/sm6150.dtsi b/arch/arm64/boot/dts/qcom/sm6150.dtsi index 3d2a1cb02b628a..64e7c9dbafc70b 100644 --- a/arch/arm64/boot/dts/qcom/sm6150.dtsi +++ b/arch/arm64/boot/dts/qcom/sm6150.dtsi @@ -1260,10 +1260,10 @@ <&gcc GCC_AGGRE_UFS_PHY_AXI_CLK>, <&gcc GCC_UFS_PHY_AHB_CLK>, <&gcc GCC_UFS_PHY_UNIPRO_CORE_CLK>, - <&gcc GCC_UFS_PHY_ICE_CORE_CLK>, <&rpmhcc RPMH_CXO_CLK>, <&gcc GCC_UFS_PHY_TX_SYMBOL_0_CLK>, - <&gcc GCC_UFS_PHY_RX_SYMBOL_0_CLK>; + <&gcc GCC_UFS_PHY_RX_SYMBOL_0_CLK>, + <&gcc GCC_UFS_PHY_ICE_CORE_CLK>; clock-names = "core_clk", "bus_aggr_clk", "iface_clk", From 9966c8cc987e307ecd439266db8b4fd4feaad482 Mon Sep 17 00:00:00 2001 From: Biju Das Date: Tue, 27 Jan 2026 17:48:15 +0100 Subject: [PATCH 2847/3902] irqchip/renesas-rzv2h: Prevent TINT spurious interrupt during resume [ Upstream commit cd4a3ced4d1cdb14ffe905657b98a91e9d239dfb ] A glitch in the edge detection circuit can cause a spurious interrupt. The hardware manual recommends clearing the status flag after setting the ICU_TSSRk register as a countermeasure. Currently, a spurious interrupt is generated on the resume path of s2idle for the PMIC RTC TINT interrupt due to a glitch related to unnecessary enabling/disabling of the TINT enable bit. Fix this issue by not setting TSSR(TINT Source) and TITSR(TINT Detection Method Selection) registers if the values are the same as those set in these registers. Fixes: 0d7605e75ac2 ("irqchip: Add RZ/V2H(P) Interrupt Control Unit (ICU) driver") Signed-off-by: Biju Das Signed-off-by: Thomas Gleixner Cc: stable@vger.kernel.org Link: https://patch.msgid.link/20260113125315.359967-2-biju.das.jz@bp.renesas.com [tm: Added field_get() to avoid build error] Signed-off-by: Tommaso Merciai Signed-off-by: Greg Kroah-Hartman --- drivers/irqchip/irq-renesas-rzv2h.c | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/drivers/irqchip/irq-renesas-rzv2h.c b/drivers/irqchip/irq-renesas-rzv2h.c index 899a423b5da8ff..3dab62ededec92 100644 --- a/drivers/irqchip/irq-renesas-rzv2h.c +++ b/drivers/irqchip/irq-renesas-rzv2h.c @@ -89,6 +89,8 @@ #define ICU_RZG3E_TSSEL_MAX_VAL 0x8c #define ICU_RZV2H_TSSEL_MAX_VAL 0x55 +#define field_get(_mask, _reg) (((_reg) & (_mask)) >> (ffs(_mask) - 1)) + /** * struct rzv2h_hw_info - Interrupt Control Unit controller hardware info structure. * @tssel_lut: TINT lookup table @@ -328,6 +330,7 @@ static int rzv2h_tint_set_type(struct irq_data *d, unsigned int type) u32 titsr, titsr_k, titsel_n, tien; struct rzv2h_icu_priv *priv; u32 tssr, tssr_k, tssel_n; + u32 titsr_cur, tssr_cur; unsigned int hwirq; u32 tint, sense; int tint_nr; @@ -376,12 +379,18 @@ static int rzv2h_tint_set_type(struct irq_data *d, unsigned int type) guard(raw_spinlock)(&priv->lock); tssr = readl_relaxed(priv->base + priv->info->t_offs + ICU_TSSR(tssr_k)); + titsr = readl_relaxed(priv->base + priv->info->t_offs + ICU_TITSR(titsr_k)); + + tssr_cur = field_get(ICU_TSSR_TSSEL_MASK(tssel_n, priv->info->field_width), tssr); + titsr_cur = field_get(ICU_TITSR_TITSEL_MASK(titsel_n), titsr); + if (tssr_cur == tint && titsr_cur == sense) + return 0; + tssr &= ~(ICU_TSSR_TSSEL_MASK(tssel_n, priv->info->field_width) | tien); tssr |= ICU_TSSR_TSSEL_PREP(tint, tssel_n, priv->info->field_width); writel_relaxed(tssr, priv->base + priv->info->t_offs + ICU_TSSR(tssr_k)); - titsr = readl_relaxed(priv->base + priv->info->t_offs + ICU_TITSR(titsr_k)); titsr &= ~ICU_TITSR_TITSEL_MASK(titsel_n); titsr |= ICU_TITSR_TITSEL_PREP(sense, titsel_n); From a4d9dbfc1bab16e25fefd34b5e537a46bed8fc96 Mon Sep 17 00:00:00 2001 From: Lorenzo Stoakes Date: Thu, 22 Jan 2026 19:00:21 +0000 Subject: [PATCH 2848/3902] mm/vma: fix anon_vma UAF on mremap() faulted, unfaulted merge [ upstream commit 61f67c230a5e7c741c352349ea80147fbe65bfae ] Patch series "mm/vma: fix anon_vma UAF on mremap() faulted, unfaulted merge", v2. Commit 879bca0a2c4f ("mm/vma: fix incorrectly disallowed anonymous VMA merges") introduced the ability to merge previously unavailable VMA merge scenarios. However, it is handling merges incorrectly when it comes to mremap() of a faulted VMA adjacent to an unfaulted VMA. The issues arise in three cases: 1. Previous VMA unfaulted: copied -----| v |-----------|.............| | unfaulted |(faulted VMA)| |-----------|.............| prev 2. Next VMA unfaulted: copied -----| v |.............|-----------| |(faulted VMA)| unfaulted | |.............|-----------| next 3. Both adjacent VMAs unfaulted: copied -----| v |-----------|.............|-----------| | unfaulted |(faulted VMA)| unfaulted | |-----------|.............|-----------| prev next This series fixes each of these cases, and introduces self tests to assert that the issues are corrected. I also test a further case which was already handled, to assert that my changes continues to correctly handle it: 4. prev unfaulted, next faulted: copied -----| v |-----------|.............|-----------| | unfaulted |(faulted VMA)| faulted | |-----------|.............|-----------| prev next This bug was discovered via a syzbot report, linked to in the first patch in the series, I confirmed that this series fixes the bug. I also discovered that we are failing to check that the faulted VMA was not forked when merging a copied VMA in cases 1-3 above, an issue this series also addresses. I also added self tests to assert that this is resolved (and confirmed that the tests failed prior to this). I also cleaned up vma_expand() as part of this work, renamed vma_had_uncowed_parents() to vma_is_fork_child() as the previous name was unduly confusing, and simplified the comments around this function. This patch (of 4): Commit 879bca0a2c4f ("mm/vma: fix incorrectly disallowed anonymous VMA merges") introduced the ability to merge previously unavailable VMA merge scenarios. The key piece of logic introduced was the ability to merge a faulted VMA immediately next to an unfaulted VMA, which relies upon dup_anon_vma() to correctly handle anon_vma state. In the case of the merge of an existing VMA (that is changing properties of a VMA and then merging if those properties are shared by adjacent VMAs), dup_anon_vma() is invoked correctly. However in the case of the merge of a new VMA, a corner case peculiar to mremap() was missed. The issue is that vma_expand() only performs dup_anon_vma() if the target (the VMA that will ultimately become the merged VMA): is not the next VMA, i.e. the one that appears after the range in which the new VMA is to be established. A key insight here is that in all other cases other than mremap(), a new VMA merge either expands an existing VMA, meaning that the target VMA will be that VMA, or would have anon_vma be NULL. Specifically: * __mmap_region() - no anon_vma in place, initial mapping. * do_brk_flags() - expanding an existing VMA. * vma_merge_extend() - expanding an existing VMA. * relocate_vma_down() - no anon_vma in place, initial mapping. In addition, we are in the unique situation of needing to duplicate anon_vma state from a VMA that is neither the previous or next VMA being merged with. dup_anon_vma() deals exclusively with the target=unfaulted, src=faulted case. This leaves four possibilities, in each case where the copied VMA is faulted: 1. Previous VMA unfaulted: copied -----| v |-----------|.............| | unfaulted |(faulted VMA)| |-----------|.............| prev target = prev, expand prev to cover. 2. Next VMA unfaulted: copied -----| v |.............|-----------| |(faulted VMA)| unfaulted | |.............|-----------| next target = next, expand next to cover. 3. Both adjacent VMAs unfaulted: copied -----| v |-----------|.............|-----------| | unfaulted |(faulted VMA)| unfaulted | |-----------|.............|-----------| prev next target = prev, expand prev to cover. 4. prev unfaulted, next faulted: copied -----| v |-----------|.............|-----------| | unfaulted |(faulted VMA)| faulted | |-----------|.............|-----------| prev next target = prev, expand prev to cover. Essentially equivalent to 3, but with additional requirement that next's anon_vma is the same as the copied VMA's. This is covered by the existing logic. To account for this very explicitly, we introduce vma_merge_copied_range(), which sets a newly introduced vmg->copied_from field, then invokes vma_merge_new_range() which handles the rest of the logic. We then update the key vma_expand() function to clean up the logic and make what's going on clearer, making the 'remove next' case less special, before invoking dup_anon_vma() unconditionally should we be copying from a VMA. Note that in case 3, the if (remove_next) ... branch will be a no-op, as next=src in this instance and src is unfaulted. In case 4, it won't be, but since in this instance next=src and it is faulted, this will have required tgt=faulted, src=faulted to be compatible, meaning that next->anon_vma == vmg->copied_from->anon_vma, and thus a single dup_anon_vma() of next suffices to copy anon_vma state for the copied-from VMA also. If we are copying from a VMA in a successful merge we must _always_ propagate anon_vma state. This issue can be observed most directly by invoked mremap() to move around a VMA and cause this kind of merge with the MREMAP_DONTUNMAP flag specified. This will result in unlink_anon_vmas() being called after failing to duplicate anon_vma state to the target VMA, which results in the anon_vma itself being freed with folios still possessing dangling pointers to the anon_vma and thus a use-after-free bug. This bug was discovered via a syzbot report, which this patch resolves. We further make a change to update the mergeable anon_vma check to assert the copied-from anon_vma did not have CoW parents, as otherwise dup_anon_vma() might incorrectly propagate CoW ancestors from the next VMA in case 4 despite the anon_vma's being identical for both VMAs. Link: https://lkml.kernel.org/r/cover.1767638272.git.lorenzo.stoakes@oracle.com Link: https://lkml.kernel.org/r/b7930ad2b1503a657e29fe928eb33061d7eadf5b.1767638272.git.lorenzo.stoakes@oracle.com Signed-off-by: Lorenzo Stoakes Fixes: 879bca0a2c4f ("mm/vma: fix incorrectly disallowed anonymous VMA merges") Reported-by: syzbot+b165fc2e11771c66d8ba@syzkaller.appspotmail.com Closes: https://lore.kernel.org/all/694a2745.050a0220.19928e.0017.GAE@google.com/ Reported-by: syzbot+5272541ccbbb14e2ec30@syzkaller.appspotmail.com Closes: https://lore.kernel.org/all/694e3dc6.050a0220.35954c.0066.GAE@google.com/ Reviewed-by: Harry Yoo Reviewed-by: Jeongjun Park Acked-by: Vlastimil Babka Cc: David Hildenbrand (Red Hat) Cc: Jann Horn Cc: Yeoreum Yun Cc: Liam Howlett Cc: Liam R. Howlett Cc: Pedro Falcato Cc: Rik van Riel Cc: stable@vger.kernel.org Signed-off-by: Andrew Morton [ updated to account for lack of sticky VMA flags + built, tested confirmed working ] Signed-off-by: Lorenzo Stoakes Signed-off-by: Greg Kroah-Hartman --- mm/vma.c | 71 ++++++++++++++++++++++++++++++++++++++++++-------------- mm/vma.h | 3 +++ 2 files changed, 56 insertions(+), 18 deletions(-) diff --git a/mm/vma.c b/mm/vma.c index 9127eaeea93ffb..982ba32af0d222 100644 --- a/mm/vma.c +++ b/mm/vma.c @@ -835,6 +835,8 @@ static __must_check struct vm_area_struct *vma_merge_existing_range( VM_WARN_ON_VMG(middle && !(vma_iter_addr(vmg->vmi) >= middle->vm_start && vma_iter_addr(vmg->vmi) < middle->vm_end), vmg); + /* An existing merge can never be used by the mremap() logic. */ + VM_WARN_ON_VMG(vmg->copied_from, vmg); vmg->state = VMA_MERGE_NOMERGE; @@ -1101,6 +1103,33 @@ struct vm_area_struct *vma_merge_new_range(struct vma_merge_struct *vmg) return NULL; } +/* + * vma_merge_copied_range - Attempt to merge a VMA that is being copied by + * mremap() + * + * @vmg: Describes the VMA we are adding, in the copied-to range @vmg->start to + * @vmg->end (exclusive), which we try to merge with any adjacent VMAs if + * possible. + * + * vmg->prev, next, start, end, pgoff should all be relative to the COPIED TO + * range, i.e. the target range for the VMA. + * + * Returns: In instances where no merge was possible, NULL. Otherwise, a pointer + * to the VMA we expanded. + * + * ASSUMPTIONS: Same as vma_merge_new_range(), except vmg->middle must contain + * the copied-from VMA. + */ +static struct vm_area_struct *vma_merge_copied_range(struct vma_merge_struct *vmg) +{ + /* We must have a copied-from VMA. */ + VM_WARN_ON_VMG(!vmg->middle, vmg); + + vmg->copied_from = vmg->middle; + vmg->middle = NULL; + return vma_merge_new_range(vmg); +} + /* * vma_expand - Expand an existing VMA * @@ -1123,38 +1152,45 @@ int vma_expand(struct vma_merge_struct *vmg) bool remove_next = false; struct vm_area_struct *target = vmg->target; struct vm_area_struct *next = vmg->next; + int ret = 0; VM_WARN_ON_VMG(!target, vmg); mmap_assert_write_locked(vmg->mm); - vma_start_write(target); - if (next && (target != next) && (vmg->end == next->vm_end)) { - int ret; + if (next && target != next && vmg->end == next->vm_end) remove_next = true; - /* This should already have been checked by this point. */ - VM_WARN_ON_VMG(!can_merge_remove_vma(next), vmg); - vma_start_write(next); - /* - * In this case we don't report OOM, so vmg->give_up_on_mm is - * safe. - */ - ret = dup_anon_vma(target, next, &anon_dup); - if (ret) - return ret; - } + /* We must have a target. */ + VM_WARN_ON_VMG(!target, vmg); + /* This should have already been checked by this point. */ + VM_WARN_ON_VMG(remove_next && !can_merge_remove_vma(next), vmg); /* Not merging but overwriting any part of next is not handled. */ VM_WARN_ON_VMG(next && !remove_next && next != target && vmg->end > next->vm_start, vmg); - /* Only handles expanding */ + /* Only handles expanding. */ VM_WARN_ON_VMG(target->vm_start < vmg->start || target->vm_end > vmg->end, vmg); + /* + * If we are removing the next VMA or copying from a VMA + * (e.g. mremap()'ing), we must propagate anon_vma state. + * + * Note that, by convention, callers ignore OOM for this case, so + * we don't need to account for vmg->give_up_on_mm here. + */ if (remove_next) - vmg->__remove_next = true; + ret = dup_anon_vma(target, next, &anon_dup); + if (!ret && vmg->copied_from) + ret = dup_anon_vma(target, vmg->copied_from, &anon_dup); + if (ret) + return ret; + if (remove_next) { + vma_start_write(next); + vmg->__remove_next = true; + } if (commit_merge(vmg)) goto nomem; @@ -1837,10 +1873,9 @@ struct vm_area_struct *copy_vma(struct vm_area_struct **vmap, if (new_vma && new_vma->vm_start < addr + len) return NULL; /* should never get here */ - vmg.middle = NULL; /* New VMA range. */ vmg.pgoff = pgoff; vmg.next = vma_iter_next_rewind(&vmi, NULL); - new_vma = vma_merge_new_range(&vmg); + new_vma = vma_merge_copied_range(&vmg); if (new_vma) { /* diff --git a/mm/vma.h b/mm/vma.h index 9183fe54900907..d73e1b324bfd1a 100644 --- a/mm/vma.h +++ b/mm/vma.h @@ -106,6 +106,9 @@ struct vma_merge_struct { struct anon_vma_name *anon_name; enum vma_merge_state state; + /* If copied from (i.e. mremap()'d) the VMA from which we are copying. */ + struct vm_area_struct *copied_from; + /* Flags which callers can use to modify merge behaviour: */ /* From 5b0caf3913e725c76a6e99f749f1957a64934f77 Mon Sep 17 00:00:00 2001 From: Lorenzo Stoakes Date: Thu, 22 Jan 2026 19:00:22 +0000 Subject: [PATCH 2849/3902] mm/vma: enforce VMA fork limit on unfaulted,faulted mremap merge too [ Upstream commit 3b617fd3d317bf9dd7e2c233e56eafef05734c9d ] The is_mergeable_anon_vma() function uses vmg->middle as the source VMA. However when merging a new VMA, this field is NULL. In all cases except mremap(), the new VMA will either be newly established and thus lack an anon_vma, or will be an expansion of an existing VMA thus we do not care about whether VMA is CoW'd or not. In the case of an mremap(), we can end up in a situation where we can accidentally allow an unfaulted/faulted merge with a VMA that has been forked, violating the general rule that we do not permit this for reasons of anon_vma lock scalability. Now we have the ability to be aware of the fact we are copying a VMA and also know which VMA that is, we can explicitly check for this, so do so. This is pertinent since commit 879bca0a2c4f ("mm/vma: fix incorrectly disallowed anonymous VMA merges"), as this patch permits unfaulted/faulted merges that were previously disallowed running afoul of this issue. While we are here, vma_had_uncowed_parents() is a confusing name, so make it simple and rename it to vma_is_fork_child(). Link: https://lkml.kernel.org/r/6e2b9b3024ae1220961c8b81d74296d4720eaf2b.1767638272.git.lorenzo.stoakes@oracle.com Fixes: 879bca0a2c4f ("mm/vma: fix incorrectly disallowed anonymous VMA merges") Signed-off-by: Lorenzo Stoakes Reviewed-by: Harry Yoo Reviewed-by: Jeongjun Park Acked-by: Vlastimil Babka Cc: David Hildenbrand (Red Hat) Cc: Jann Horn Cc: Liam Howlett Cc: Pedro Falcato Cc: Rik van Riel Cc: Yeoreum Yun Cc: Signed-off-by: Andrew Morton [ with upstream commit 61f67c230a5e backported, this simply applied correctly. Built + tested ] Signed-off-by: Lorenzo Stoakes Signed-off-by: Greg Kroah-Hartman --- mm/vma.c | 27 +++++++++++++++------------ 1 file changed, 15 insertions(+), 12 deletions(-) diff --git a/mm/vma.c b/mm/vma.c index 982ba32af0d222..5815ae9e57703b 100644 --- a/mm/vma.c +++ b/mm/vma.c @@ -65,18 +65,13 @@ struct mmap_state { .state = VMA_MERGE_START, \ } -/* - * If, at any point, the VMA had unCoW'd mappings from parents, it will maintain - * more than one anon_vma_chain connecting it to more than one anon_vma. A merge - * would mean a wider range of folios sharing the root anon_vma lock, and thus - * potential lock contention, we do not wish to encourage merging such that this - * scales to a problem. - */ -static bool vma_had_uncowed_parents(struct vm_area_struct *vma) +/* Was this VMA ever forked from a parent, i.e. maybe contains CoW mappings? */ +static bool vma_is_fork_child(struct vm_area_struct *vma) { /* * The list_is_singular() test is to avoid merging VMA cloned from - * parents. This can improve scalability caused by anon_vma lock. + * parents. This can improve scalability caused by the anon_vma root + * lock. */ return vma && vma->anon_vma && !list_is_singular(&vma->anon_vma_chain); } @@ -121,11 +116,19 @@ static bool is_mergeable_anon_vma(struct vma_merge_struct *vmg, bool merge_next) VM_WARN_ON(src && src_anon != src->anon_vma); /* Case 1 - we will dup_anon_vma() from src into tgt. */ - if (!tgt_anon && src_anon) - return !vma_had_uncowed_parents(src); + if (!tgt_anon && src_anon) { + struct vm_area_struct *copied_from = vmg->copied_from; + + if (vma_is_fork_child(src)) + return false; + if (vma_is_fork_child(copied_from)) + return false; + + return true; + } /* Case 2 - we will simply use tgt's anon_vma. */ if (tgt_anon && !src_anon) - return !vma_had_uncowed_parents(tgt); + return !vma_is_fork_child(tgt); /* Case 3 - the anon_vma's are already shared. */ return src_anon == tgt_anon; } From d905362ff5b86f6b619953ada8e0af84158db2e9 Mon Sep 17 00:00:00 2001 From: Greg Kroah-Hartman Date: Fri, 30 Jan 2026 10:32:28 +0100 Subject: [PATCH 2850/3902] Linux 6.18.8 Link: https://lore.kernel.org/r/20260128145344.331957407@linuxfoundation.org Tested-by: Brett A C Sheffield Tested-by: Salvatore Bonaccorso Tested-by: Florian Fainelli Tested-by: Shung-Hsi Yu Tested-by: Takeshi Ogasawara Tested-by: Peter Schneider Tested-by: Slade Watkins Tested-by: Jon Hunter Tested-by: Ron Economos Tested-by: Mark Brown Tested-by: Brett Mastbergen Tested-by: Hardik Garg Tested-by: Miguel Ojeda Signed-off-by: Greg Kroah-Hartman --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index e3e3b82d94c013..6b6539f2f11df4 100644 --- a/Makefile +++ b/Makefile @@ -1,7 +1,7 @@ # SPDX-License-Identifier: GPL-2.0 VERSION = 6 PATCHLEVEL = 18 -SUBLEVEL = 7 +SUBLEVEL = 8 EXTRAVERSION = NAME = Baby Opossum Posse From 4f255524a5a5e97dcd6430e268fe549a8324e58d Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Fri, 30 Jan 2026 10:31:03 +0100 Subject: [PATCH 2851/3902] fixup! arm64: dts: apple: Initial t8122 (M3) device tree --- arch/arm64/boot/dts/apple/Makefile | 2 ++ arch/arm64/boot/dts/apple/t8122-j433.dts | 21 ++++++++++++ arch/arm64/boot/dts/apple/t8122-j434.dts | 21 ++++++++++++ arch/arm64/boot/dts/apple/t8122-j504.dts | 28 +--------------- arch/arm64/boot/dts/apple/t8122-jxxx.dtsi | 2 +- .../arm64/boot/dts/apple/t8122-usbpd-i2c.dtsi | 32 +++++++++++++++++++ 6 files changed, 78 insertions(+), 28 deletions(-) create mode 100644 arch/arm64/boot/dts/apple/t8122-j433.dts create mode 100644 arch/arm64/boot/dts/apple/t8122-j434.dts create mode 100644 arch/arm64/boot/dts/apple/t8122-usbpd-i2c.dtsi diff --git a/arch/arm64/boot/dts/apple/Makefile b/arch/arm64/boot/dts/apple/Makefile index a17f75bd5a2750..d253f08c98329e 100644 --- a/arch/arm64/boot/dts/apple/Makefile +++ b/arch/arm64/boot/dts/apple/Makefile @@ -91,4 +91,6 @@ dtb-$(CONFIG_ARCH_APPLE) += t8112-j413.dtb dtb-$(CONFIG_ARCH_APPLE) += t8112-j415.dtb dtb-$(CONFIG_ARCH_APPLE) += t8112-j473.dtb dtb-$(CONFIG_ARCH_APPLE) += t8112-j493.dtb +dtb-$(CONFIG_ARCH_APPLE) += t8122-j433.dtb +dtb-$(CONFIG_ARCH_APPLE) += t8122-j434.dtb dtb-$(CONFIG_ARCH_APPLE) += t8122-j504.dtb diff --git a/arch/arm64/boot/dts/apple/t8122-j433.dts b/arch/arm64/boot/dts/apple/t8122-j433.dts new file mode 100644 index 00000000000000..61fff420571574 --- /dev/null +++ b/arch/arm64/boot/dts/apple/t8122-j433.dts @@ -0,0 +1,21 @@ +// SPDX-License-Identifier: GPL-2.0+ OR MIT +/* + * Apple iMac (24-inch, 2x USB-C, M3, 2023) + * + * target-type: J433 + * + * Copyright The Asahi Linux Contributors + */ + +/dts-v1/; + +#include "t8122.dtsi" +#include "t8122-usbpd-i2c.dtsi" +#include "t8122-jxxx.dtsi" + +/ { + compatible = "apple,j433", "apple,t8122", "apple,arm-platform"; + model = "Apple iMac (24-inch, 2x USB-C, M3, 2023)"; +}; + + diff --git a/arch/arm64/boot/dts/apple/t8122-j434.dts b/arch/arm64/boot/dts/apple/t8122-j434.dts new file mode 100644 index 00000000000000..16ef556a07037e --- /dev/null +++ b/arch/arm64/boot/dts/apple/t8122-j434.dts @@ -0,0 +1,21 @@ +// SPDX-License-Identifier: GPL-2.0+ OR MIT +/* + * Apple iMac (24-inch, 4x USB-C, M3, 2023) + * + * target-type: J434 + * + * Copyright The Asahi Linux Contributors + */ + +/dts-v1/; + +#include "t8122.dtsi" +#include "t8122-usbpd-i2c.dtsi" +#include "t8122-jxxx.dtsi" + +/ { + compatible = "apple,j434", "apple,t8122", "apple,arm-platform"; + model = "Apple iMac (24-inch, 4x USB-C, M3, 2023)"; +}; + + diff --git a/arch/arm64/boot/dts/apple/t8122-j504.dts b/arch/arm64/boot/dts/apple/t8122-j504.dts index c876a447d4f9c5..464491b55b019c 100644 --- a/arch/arm64/boot/dts/apple/t8122-j504.dts +++ b/arch/arm64/boot/dts/apple/t8122-j504.dts @@ -10,6 +10,7 @@ /dts-v1/; #include "t8122.dtsi" +#include "t8122-usbpd-i2c.dtsi" #include "t8122-jxxx.dtsi" #include @@ -30,33 +31,6 @@ }; }; -&framebuffer0 { - // panel = &panel; - power-domains = <&ps_disp_cpu>, <&ps_dptx_ext_phy>; -}; - -&i2c0 { - status = "okay"; - - hpm0: usb-pd@38 { - compatible = "apple,cd321x"; - reg = <0x38>; - interrupt-parent = <&pinctrl_ap>; - interrupts = <8 IRQ_TYPE_LEVEL_LOW>; - interrupt-names = "irq"; - }; - - hpm1: usb-pd@3f { - compatible = "apple,cd321x"; - reg = <0x3f>; - interrupt-parent = <&pinctrl_ap>; - interrupts = <8 IRQ_TYPE_LEVEL_LOW>; - interrupt-names = "irq"; - }; -}; - - - &fpwm1 { status = "okay"; }; diff --git a/arch/arm64/boot/dts/apple/t8122-jxxx.dtsi b/arch/arm64/boot/dts/apple/t8122-jxxx.dtsi index fedf09bf22c194..813448cc8662ab 100644 --- a/arch/arm64/boot/dts/apple/t8122-jxxx.dtsi +++ b/arch/arm64/boot/dts/apple/t8122-jxxx.dtsi @@ -24,7 +24,7 @@ framebuffer0: framebuffer@0 { compatible = "apple,simple-framebuffer", "simple-framebuffer"; reg = <0 0 0 0>; /* To be filled by loader */ - power-domains = <&ps_disp_cpu>; + power-domains = <&ps_disp_cpu>, <&ps_dptx_ext_phy>; /* Format properties will be added by loader */ status = "disabled"; }; diff --git a/arch/arm64/boot/dts/apple/t8122-usbpd-i2c.dtsi b/arch/arm64/boot/dts/apple/t8122-usbpd-i2c.dtsi new file mode 100644 index 00000000000000..112c5199cabdd4 --- /dev/null +++ b/arch/arm64/boot/dts/apple/t8122-usbpd-i2c.dtsi @@ -0,0 +1,32 @@ +// SPDX-License-Identifier: GPL-2.0+ OR MIT +/* + * Apple M3 MacBook Pro and iMac (M3, 2023) I2C based USB PD controller nodes + * + * This file contains nodes for t8122 devices using I2C based cd321x USB Type-C + * port controllers. The are used in the M3 MacBook Pro and iMacs but not in the + * M3 Macbook Airs. + * + * target-type: J433, J434, J504 + * + * Copyright The Asahi Linux Contributors + */ + +&i2c0 { + status = "okay"; + + hpm0: usb-pd@38 { + compatible = "apple,cd321x"; + reg = <0x38>; + interrupt-parent = <&pinctrl_ap>; + interrupts = <8 IRQ_TYPE_LEVEL_LOW>; + interrupt-names = "irq"; + }; + + hpm1: usb-pd@3f { + compatible = "apple,cd321x"; + reg = <0x3f>; + interrupt-parent = <&pinctrl_ap>; + interrupts = <8 IRQ_TYPE_LEVEL_LOW>; + interrupt-names = "irq"; + }; +}; From d87c70d9339a9bd85940e1e52cb61184746c400c Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Sat, 31 Jan 2026 11:59:40 +0100 Subject: [PATCH 2852/3902] fixup! arm64: dts: apple: Initial t8122 (M3) device tree Signed-off-by: Janne Grunau --- arch/arm64/boot/dts/apple/t8122-jxxx.dtsi | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/arch/arm64/boot/dts/apple/t8122-jxxx.dtsi b/arch/arm64/boot/dts/apple/t8122-jxxx.dtsi index 813448cc8662ab..dd85f0c9fb1e11 100644 --- a/arch/arm64/boot/dts/apple/t8122-jxxx.dtsi +++ b/arch/arm64/boot/dts/apple/t8122-jxxx.dtsi @@ -4,7 +4,7 @@ * * This file contains parts common to all Apple M3 devices using the t8122. * - * target-type: J433, J434, J504, J513, J515 + * target-type: J433, J434, J504, J613, J615 * * Copyright The Asahi Linux Contributors */ From 2f49748b5700c8881b2e902ac4770b10e47ef45f Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Tue, 3 Feb 2026 23:03:46 +0100 Subject: [PATCH 2853/3902] fixup! dt-bindings: arm: apple: Add M3 devices (t8112 and t603x) Signed-off-by: Janne Grunau --- .../devicetree/bindings/arm/apple.yaml | 24 +++++++++---------- 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/Documentation/devicetree/bindings/arm/apple.yaml b/Documentation/devicetree/bindings/arm/apple.yaml index 3d0338f6f9cb22..13e0a79ca5b3b2 100644 --- a/Documentation/devicetree/bindings/arm/apple.yaml +++ b/Documentation/devicetree/bindings/arm/apple.yaml @@ -315,11 +315,11 @@ properties: - description: Apple M2 SoC based platforms items: - enum: - - apple,j504 # MacBook Pro (13-inch, M3, 2023) - - apple,j613 # MacBook Air (13-inch, M3, 2024) - - apple,j615 # MacBook Air (15-inch, M3, 2024) - - apple,j433 # iMac (24-inch, 2x USB-C, M3, 2023) - - apple,j434 # iMac (24-inch, 4x USB-C, M3, 2023) + - apple,j433 # iMac (24-inch, 2x USB-C, M3, 2023) + - apple,j434 # iMac (24-inch, 4x USB-C, M3, 2023) + - apple,j504 # MacBook Pro (13-inch, M3, 2023) + - apple,j613 # MacBook Air (13-inch, M3, 2024) + - apple,j615 # MacBook Air (15-inch, M3, 2024) - const: apple,t8122 - const: apple,arm-platform @@ -376,8 +376,8 @@ properties: - description: Apple M3 Pro SoC based platforms items: - enum: - - apple,j514s # MacBook Pro (14-inch, M3 Pro, 2023) - - apple,j516s # MacBook Pro (16-inch, M3 Pro, 2023) + - apple,j514s # MacBook Pro (14-inch, M3 Pro, 2023) + - apple,j516s # MacBook Pro (16-inch, M3 Pro, 2023) - const: apple,t6030 - const: apple,arm-platform @@ -385,21 +385,21 @@ properties: oneOf: - items: - enum: - - apple,j414c # MacBook Pro (14-inch, M3 Max, 16 cores, 2023) - - apple,j416c # MacBook Pro (16-inch, M3 Max, 16 cores, 2023) + - apple,j514c # MacBook Pro (14-inch, M3 Max, 16 cores, 2023) + - apple,j516c # MacBook Pro (16-inch, M3 Max, 16 cores, 2023) - const: apple,t6031 - const: apple,arm-platform - items: - enum: - - apple,j414m # MacBook Pro (14-inch, M3 Max, 14 cores, 2023) - - apple,j416m # MacBook Pro (16-inch, M3 Max, 14 cores, 2023) + - apple,j514m # MacBook Pro (14-inch, M3 Max, 14 cores, 2023) + - apple,j516m # MacBook Pro (16-inch, M3 Max, 14 cores, 2023) - const: apple,t6034 - const: apple,arm-platform - description: Apple M3 Ultra SoC based platforms items: - enum: - - apple,j575d # Mac Studio (M3 Ultra, 2025) + - apple,j575d # Mac Studio (M3 Ultra, 2025) - const: apple,t6032 - const: apple,arm-platform From 474f851ec5e2e9568a0938f340d7e6399f0e35e9 Mon Sep 17 00:00:00 2001 From: Amir Goldstein Date: Thu, 8 Jan 2026 08:45:22 +0100 Subject: [PATCH 2854/3902] readdir: require opt-in for d_type flags [ Upstream commit c644bce62b9c6b441143a03c910f986109c47001 ] Commit c31f91c6af96 ("fuse: don't allow signals to interrupt getdents copying") introduced the use of high bits in d_type as flags. However, overlayfs was not adapted to handle this change. In ovl_cache_entry_new(), the code checks if d_type == DT_CHR to determine if an entry might be a whiteout. When fuse is used as the lower layer and sets high bits in d_type, this comparison fails, causing whiteout files to not be recognized properly and resulting in incorrect overlayfs behavior. Fix this by requiring callers of iterate_dir() to opt-in for getting flag bits in d_type outside of S_DT_MASK. Fixes: c31f91c6af96 ("fuse: don't allow signals to interrupt getdents copying") Link: https://lore.kernel.org/all/20260107034551.439-1-luochunsheng@ustc.edu/ Link: https://github.com/containerd/stargz-snapshotter/issues/2214 Reported-by: Chunsheng Luo Reviewed-by: Chunsheng Luo Tested-by: Chunsheng Luo Signed-off-by: Amir Goldstein Link: https://patch.msgid.link/20260108074522.3400998-1-amir73il@gmail.com Signed-off-by: Christian Brauner Signed-off-by: Sasha Levin --- fs/readdir.c | 3 +++ include/linux/fs.h | 6 +++++- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/fs/readdir.c b/fs/readdir.c index 7764b863897888..73707b6816e9aa 100644 --- a/fs/readdir.c +++ b/fs/readdir.c @@ -316,6 +316,7 @@ SYSCALL_DEFINE3(getdents, unsigned int, fd, struct getdents_callback buf = { .ctx.actor = filldir, .ctx.count = count, + .ctx.dt_flags_mask = FILLDIR_FLAG_NOINTR, .current_dir = dirent }; int error; @@ -400,6 +401,7 @@ SYSCALL_DEFINE3(getdents64, unsigned int, fd, struct getdents_callback64 buf = { .ctx.actor = filldir64, .ctx.count = count, + .ctx.dt_flags_mask = FILLDIR_FLAG_NOINTR, .current_dir = dirent }; int error; @@ -569,6 +571,7 @@ COMPAT_SYSCALL_DEFINE3(getdents, unsigned int, fd, struct compat_getdents_callback buf = { .ctx.actor = compat_filldir, .ctx.count = count, + .ctx.dt_flags_mask = FILLDIR_FLAG_NOINTR, .current_dir = dirent, }; int error; diff --git a/include/linux/fs.h b/include/linux/fs.h index 9b2230fb2332ff..3e965c77fa1b16 100644 --- a/include/linux/fs.h +++ b/include/linux/fs.h @@ -2207,6 +2207,8 @@ struct dir_context { * INT_MAX unlimited */ int count; + /* @actor supports these flags in d_type high bits */ + unsigned int dt_flags_mask; }; /* If OR-ed with d_type, pending signals are not checked */ @@ -3985,7 +3987,9 @@ static inline bool dir_emit(struct dir_context *ctx, const char *name, int namelen, u64 ino, unsigned type) { - return ctx->actor(ctx, name, namelen, ctx->pos, ino, type); + unsigned int dt_mask = S_DT_MASK | ctx->dt_flags_mask; + + return ctx->actor(ctx, name, namelen, ctx->pos, ino, type & dt_mask); } static inline bool dir_emit_dot(struct file *file, struct dir_context *ctx) { From e80617a5e1c246da2f112a1a072cdd535046adfe Mon Sep 17 00:00:00 2001 From: Qu Wenruo Date: Mon, 19 Jan 2026 16:24:04 +1030 Subject: [PATCH 2855/3902] btrfs: zlib: fix the folio leak on S390 hardware acceleration [ Upstream commit 0d0f1314e8f86f5205f71f9e31e272a1d008e40b ] [BUG] After commit aa60fe12b4f4 ("btrfs: zlib: refactor S390x HW acceleration buffer preparation"), we no longer release the folio of the page cache of folio returned by btrfs_compress_filemap_get_folio() for S390 hardware acceleration path. [CAUSE] Before that commit, we call kumap_local() and folio_put() after handling each folio. Although the timing is not ideal (it release previous folio at the beginning of the loop, and rely on some extra cleanup out of the loop), it at least handles the folio release correctly. Meanwhile the refactored code is easier to read, it lacks the call to release the filemap folio. [FIX] Add the missing folio_put() for copy_data_into_buffer(). CC: linux-s390@vger.kernel.org # 6.18+ Fixes: aa60fe12b4f4 ("btrfs: zlib: refactor S390x HW acceleration buffer preparation") Reviewed-by: Boris Burkov Signed-off-by: Qu Wenruo Reviewed-by: David Sterba Signed-off-by: David Sterba Signed-off-by: Sasha Levin --- fs/btrfs/zlib.c | 1 + 1 file changed, 1 insertion(+) diff --git a/fs/btrfs/zlib.c b/fs/btrfs/zlib.c index 6caba8be7c845c..10ed48d4a84665 100644 --- a/fs/btrfs/zlib.c +++ b/fs/btrfs/zlib.c @@ -139,6 +139,7 @@ static int copy_data_into_buffer(struct address_space *mapping, data_in = kmap_local_folio(folio, offset); memcpy(workspace->buf + cur - filepos, data_in, copy_length); kunmap_local(data_in); + folio_put(folio); cur += copy_length; } return 0; From 4081d53864dae81ec796fc09c8539cf5bdbafea5 Mon Sep 17 00:00:00 2001 From: Zilin Guan Date: Thu, 22 Jan 2026 11:41:28 +0000 Subject: [PATCH 2856/3902] can: at91_can: Fix memory leak in at91_can_probe() [ Upstream commit 0baa4d3170d72a2a8dc93bf729d6d04ad113dc72 ] In at91_can_probe(), the dev structure is allocated via alloc_candev(). However, if the subsequent call to devm_phy_optional_get() fails, the code jumps directly to exit_iounmap, missing the call to free_candev(). This results in a memory leak of the allocated net_device structure. Fix this by jumping to the exit_free label instead, which ensures that free_candev() is called to properly release the memory. Compile tested only. Issue found using a prototype static analysis tool and code review. Fixes: 3ecc09856afb ("can: at91_can: add CAN transceiver support") Signed-off-by: Zilin Guan Link: https://patch.msgid.link/20260122114128.643752-1-zilin@seu.edu.cn Signed-off-by: Marc Kleine-Budde Signed-off-by: Sasha Levin --- drivers/net/can/at91_can.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/net/can/at91_can.c b/drivers/net/can/at91_can.c index 191707d7e3dac2..d6dcb2be563427 100644 --- a/drivers/net/can/at91_can.c +++ b/drivers/net/can/at91_can.c @@ -1100,7 +1100,7 @@ static int at91_can_probe(struct platform_device *pdev) if (IS_ERR(transceiver)) { err = PTR_ERR(transceiver); dev_err_probe(&pdev->dev, err, "failed to get phy\n"); - goto exit_iounmap; + goto exit_free; } dev->netdev_ops = &at91_netdev_ops; From 03e8c90c62233382042b7bd0fa8b8900552fdb62 Mon Sep 17 00:00:00 2001 From: Jia-Hong Su Date: Sun, 18 Jan 2026 20:08:59 +0800 Subject: [PATCH 2857/3902] Bluetooth: hci_uart: fix null-ptr-deref in hci_uart_write_work [ Upstream commit 0c3cd7a0b862c37acbee6d9502107146cc944398 ] hci_uart_set_proto() sets HCI_UART_PROTO_INIT before calling hci_uart_register_dev(), which calls proto->open() to initialize hu->priv. However, if a TTY write wakeup occurs during this window, hci_uart_tx_wakeup() may schedule write_work before hu->priv is initialized, leading to a NULL pointer dereference in hci_uart_write_work() when proto->dequeue() accesses hu->priv. The race condition is: CPU0 CPU1 ---- ---- hci_uart_set_proto() set_bit(HCI_UART_PROTO_INIT) hci_uart_register_dev() tty write wakeup hci_uart_tty_wakeup() hci_uart_tx_wakeup() schedule_work(&hu->write_work) proto->open(hu) // initializes hu->priv hci_uart_write_work() hci_uart_dequeue() proto->dequeue(hu) // accesses hu->priv (NULL!) Fix this by moving set_bit(HCI_UART_PROTO_INIT) after proto->open() succeeds, ensuring hu->priv is initialized before any work can be scheduled. Fixes: 5df5dafc171b ("Bluetooth: hci_uart: Fix another race during initialization") Link: https://lore.kernel.org/linux-bluetooth/6969764f.170a0220.2b9fc4.35a7@mx.google.com/ Signed-off-by: Jia-Hong Su Signed-off-by: Luiz Augusto von Dentz Signed-off-by: Sasha Levin --- drivers/bluetooth/hci_ldisc.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/bluetooth/hci_ldisc.c b/drivers/bluetooth/hci_ldisc.c index d0adae3267b41e..2b28515de92c40 100644 --- a/drivers/bluetooth/hci_ldisc.c +++ b/drivers/bluetooth/hci_ldisc.c @@ -685,6 +685,8 @@ static int hci_uart_register_dev(struct hci_uart *hu) return err; } + set_bit(HCI_UART_PROTO_INIT, &hu->flags); + if (test_bit(HCI_UART_INIT_PENDING, &hu->hdev_flags)) return 0; @@ -712,8 +714,6 @@ static int hci_uart_set_proto(struct hci_uart *hu, int id) hu->proto = p; - set_bit(HCI_UART_PROTO_INIT, &hu->flags); - err = hci_uart_register_dev(hu); if (err) { return err; From 3b6318505378828ee415d6ef678db6a74c077504 Mon Sep 17 00:00:00 2001 From: Jianpeng Chang Date: Wed, 21 Jan 2026 13:29:26 +0800 Subject: [PATCH 2858/3902] Bluetooth: MGMT: Fix memory leak in set_ssp_complete [ Upstream commit 1b9c17fd0a7fdcbe69ec5d6fe8e50bc5ed7f01f2 ] Fix memory leak in set_ssp_complete() where mgmt_pending_cmd structures are not freed after being removed from the pending list. Commit 302a1f674c00 ("Bluetooth: MGMT: Fix possible UAFs") replaced mgmt_pending_foreach() calls with individual command handling but missed adding mgmt_pending_free() calls in both error and success paths of set_ssp_complete(). Other completion functions like set_le_complete() were fixed correctly in the same commit. This causes a memory leak of the mgmt_pending_cmd structure and its associated parameter data for each SSP command that completes. Add the missing mgmt_pending_free(cmd) calls in both code paths to fix the memory leak. Also fix the same issue in set_advertising_complete(). Fixes: 302a1f674c00 ("Bluetooth: MGMT: Fix possible UAFs") Signed-off-by: Jianpeng Chang Signed-off-by: Luiz Augusto von Dentz Signed-off-by: Sasha Levin --- net/bluetooth/mgmt.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/net/bluetooth/mgmt.c b/net/bluetooth/mgmt.c index 211951eb832af7..ee2dd26b1b82b1 100644 --- a/net/bluetooth/mgmt.c +++ b/net/bluetooth/mgmt.c @@ -1954,6 +1954,7 @@ static void set_ssp_complete(struct hci_dev *hdev, void *data, int err) } mgmt_cmd_status(cmd->sk, cmd->hdev->id, cmd->opcode, mgmt_err); + mgmt_pending_free(cmd); return; } @@ -1972,6 +1973,7 @@ static void set_ssp_complete(struct hci_dev *hdev, void *data, int err) sock_put(match.sk); hci_update_eir_sync(hdev); + mgmt_pending_free(cmd); } static int set_ssp_sync(struct hci_dev *hdev, void *data) @@ -6356,6 +6358,7 @@ static void set_advertising_complete(struct hci_dev *hdev, void *data, int err) hci_dev_clear_flag(hdev, HCI_ADVERTISING); settings_rsp(cmd, &match); + mgmt_pending_free(cmd); new_settings(hdev, match.sk); From 23f40dbda938eb4738c91d27239e71e3fd722446 Mon Sep 17 00:00:00 2001 From: Zilin Guan Date: Tue, 20 Jan 2026 13:46:40 +0000 Subject: [PATCH 2859/3902] net/mlx5: Fix memory leak in esw_acl_ingress_lgcy_setup() [ Upstream commit 108948f723b13874b7ebf6b3f1cc598a7de38622 ] In esw_acl_ingress_lgcy_setup(), if esw_acl_table_create() fails, the function returns directly without releasing the previously created counter, leading to a memory leak. Fix this by jumping to the out label instead of returning directly, which aligns with the error handling logic of other paths in this function. Compile tested only. Issue found using a prototype static analysis tool and code review. Fixes: 07bab9502641 ("net/mlx5: E-Switch, Refactor eswitch ingress acl codes") Signed-off-by: Zilin Guan Reviewed-by: Tariq Toukan Link: https://patch.msgid.link/20260120134640.2717808-1-zilin@seu.edu.cn Signed-off-by: Jakub Kicinski Signed-off-by: Sasha Levin --- drivers/net/ethernet/mellanox/mlx5/core/esw/acl/ingress_lgcy.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/net/ethernet/mellanox/mlx5/core/esw/acl/ingress_lgcy.c b/drivers/net/ethernet/mellanox/mlx5/core/esw/acl/ingress_lgcy.c index 1c37098e09ea51..49a637829c5941 100644 --- a/drivers/net/ethernet/mellanox/mlx5/core/esw/acl/ingress_lgcy.c +++ b/drivers/net/ethernet/mellanox/mlx5/core/esw/acl/ingress_lgcy.c @@ -188,7 +188,7 @@ int esw_acl_ingress_lgcy_setup(struct mlx5_eswitch *esw, if (IS_ERR(vport->ingress.acl)) { err = PTR_ERR(vport->ingress.acl); vport->ingress.acl = NULL; - return err; + goto out; } err = esw_acl_ingress_lgcy_groups_create(esw, vport); From 713ba826ae114ab339c9a1b31e209bebdadb0ac9 Mon Sep 17 00:00:00 2001 From: Marc Kleine-Budde Date: Tue, 20 Jan 2026 10:40:22 +0100 Subject: [PATCH 2860/3902] can: gs_usb: gs_usb_receive_bulk_callback(): fix error message [ Upstream commit 494fc029f662c331e06b7c2031deff3c64200eed ] Sinc commit 79a6d1bfe114 ("can: gs_usb: gs_usb_receive_bulk_callback(): unanchor URL on usb_submit_urb() error") a failing resubmit URB will print an info message. In the case of a short read where netdev has not yet been assigned, initialize as NULL to avoid dereferencing an undefined value. Also report the error value of the failed resubmit. Fixes: 79a6d1bfe114 ("can: gs_usb: gs_usb_receive_bulk_callback(): unanchor URL on usb_submit_urb() error") Reported-by: Jakub Kicinski Closes: https://lore.kernel.org/all/20260119181904.1209979-1-kuba@kernel.org/ Link: https://patch.msgid.link/20260120-gs_usb-fix-error-message-v1-1-6be04de572bc@pengutronix.de Signed-off-by: Marc Kleine-Budde Signed-off-by: Sasha Levin --- drivers/net/can/usb/gs_usb.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/net/can/usb/gs_usb.c b/drivers/net/can/usb/gs_usb.c index fd7fb21b109893..861b583935225c 100644 --- a/drivers/net/can/usb/gs_usb.c +++ b/drivers/net/can/usb/gs_usb.c @@ -610,7 +610,7 @@ static void gs_usb_receive_bulk_callback(struct urb *urb) { struct gs_usb *parent = urb->context; struct gs_can *dev; - struct net_device *netdev; + struct net_device *netdev = NULL; int rc; struct net_device_stats *stats; struct gs_host_frame *hf = urb->transfer_buffer; @@ -768,7 +768,7 @@ static void gs_usb_receive_bulk_callback(struct urb *urb) } } else if (rc != -ESHUTDOWN && net_ratelimit()) { netdev_info(netdev, "failed to re-submit IN URB: %pe\n", - ERR_PTR(urb->status)); + ERR_PTR(rc)); } } From f14d881f0a5aa57b4956402f36e5b381905b5743 Mon Sep 17 00:00:00 2001 From: Justin Chen Date: Thu, 22 Jan 2026 11:40:01 -0800 Subject: [PATCH 2861/3902] net: bcmasp: fix early exit leak with fixed phy [ Upstream commit 6de4436bf369e1444606445e4cd5df5bcfc74b48 ] We are not deregistering the fixed phy link when hitting the early exit condition. Add the correct early exit sequence. Fixes: 490cb412007d ("net: bcmasp: Add support for ASP2.0 Ethernet controller") Signed-off-by: Justin Chen Reviewed-by: Florian Fainelli Link: https://patch.msgid.link/20260122194001.1098859-1-justin.chen@broadcom.com Signed-off-by: Jakub Kicinski Signed-off-by: Sasha Levin --- drivers/net/ethernet/broadcom/asp2/bcmasp_intf.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/drivers/net/ethernet/broadcom/asp2/bcmasp_intf.c b/drivers/net/ethernet/broadcom/asp2/bcmasp_intf.c index b9973956c48095..ceb6c11431dd9c 100644 --- a/drivers/net/ethernet/broadcom/asp2/bcmasp_intf.c +++ b/drivers/net/ethernet/broadcom/asp2/bcmasp_intf.c @@ -1261,7 +1261,7 @@ struct bcmasp_intf *bcmasp_interface_create(struct bcmasp_priv *priv, netdev_err(intf->ndev, "invalid PHY mode: %s for port %d\n", phy_modes(intf->phy_interface), intf->port); ret = -EINVAL; - goto err_free_netdev; + goto err_deregister_fixed_link; } ret = of_get_ethdev_address(ndev_dn, ndev); @@ -1286,6 +1286,9 @@ struct bcmasp_intf *bcmasp_interface_create(struct bcmasp_priv *priv, return intf; +err_deregister_fixed_link: + if (of_phy_is_fixed_link(ndev_dn)) + of_phy_deregister_fixed_link(ndev_dn); err_free_netdev: free_netdev(ndev); err: From d753f3c3f9d7a6e6dbb4d3a97b73007d71624551 Mon Sep 17 00:00:00 2001 From: Zilin Guan Date: Wed, 21 Jan 2026 13:05:51 +0000 Subject: [PATCH 2862/3902] octeon_ep: Fix memory leak in octep_device_setup() [ Upstream commit 8016dc5ee19a77678c264f8ba368b1e873fa705b ] In octep_device_setup(), if octep_ctrl_net_init() fails, the function returns directly without unmapping the mapped resources and freeing the allocated configuration memory. Fix this by jumping to the unsupported_dev label, which performs the necessary cleanup. This aligns with the error handling logic of other paths in this function. Compile tested only. Issue found using a prototype static analysis tool and code review. Fixes: 577f0d1b1c5f ("octeon_ep: add separate mailbox command and response queues") Signed-off-by: Zilin Guan Reviewed-by: Vadim Fedorenko Link: https://patch.msgid.link/20260121130551.3717090-1-zilin@seu.edu.cn Signed-off-by: Jakub Kicinski Signed-off-by: Sasha Levin --- drivers/net/ethernet/marvell/octeon_ep/octep_main.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/net/ethernet/marvell/octeon_ep/octep_main.c b/drivers/net/ethernet/marvell/octeon_ep/octep_main.c index bcea3fc26a8c7d..57db7ea2f5be9c 100644 --- a/drivers/net/ethernet/marvell/octeon_ep/octep_main.c +++ b/drivers/net/ethernet/marvell/octeon_ep/octep_main.c @@ -1338,7 +1338,7 @@ int octep_device_setup(struct octep_device *oct) ret = octep_ctrl_net_init(oct); if (ret) - return ret; + goto unsupported_dev; INIT_WORK(&oct->tx_timeout_task, octep_tx_timeout_task); INIT_WORK(&oct->ctrl_mbox_task, octep_ctrl_mbox_task); From bd98324e327e41de04b13e372cc16f73150df254 Mon Sep 17 00:00:00 2001 From: Eric Dumazet Date: Thu, 22 Jan 2026 16:29:14 +0000 Subject: [PATCH 2863/3902] bonding: annotate data-races around slave->last_rx [ Upstream commit f6c3665b6dc53c3ab7d31b585446a953a74340ef ] slave->last_rx and slave->target_last_arp_rx[...] can be read and written locklessly. Add READ_ONCE() and WRITE_ONCE() annotations. syzbot reported: BUG: KCSAN: data-race in bond_rcv_validate / bond_rcv_validate write to 0xffff888149f0d428 of 8 bytes by interrupt on cpu 1: bond_rcv_validate+0x202/0x7a0 drivers/net/bonding/bond_main.c:3335 bond_handle_frame+0xde/0x5e0 drivers/net/bonding/bond_main.c:1533 __netif_receive_skb_core+0x5b1/0x1950 net/core/dev.c:6039 __netif_receive_skb_one_core net/core/dev.c:6150 [inline] __netif_receive_skb+0x59/0x270 net/core/dev.c:6265 netif_receive_skb_internal net/core/dev.c:6351 [inline] netif_receive_skb+0x4b/0x2d0 net/core/dev.c:6410 ... write to 0xffff888149f0d428 of 8 bytes by interrupt on cpu 0: bond_rcv_validate+0x202/0x7a0 drivers/net/bonding/bond_main.c:3335 bond_handle_frame+0xde/0x5e0 drivers/net/bonding/bond_main.c:1533 __netif_receive_skb_core+0x5b1/0x1950 net/core/dev.c:6039 __netif_receive_skb_one_core net/core/dev.c:6150 [inline] __netif_receive_skb+0x59/0x270 net/core/dev.c:6265 netif_receive_skb_internal net/core/dev.c:6351 [inline] netif_receive_skb+0x4b/0x2d0 net/core/dev.c:6410 br_netif_receive_skb net/bridge/br_input.c:30 [inline] NF_HOOK include/linux/netfilter.h:318 [inline] ... value changed: 0x0000000100005365 -> 0x0000000100005366 Fixes: f5b2b966f032 ("[PATCH] bonding: Validate probe replies in ARP monitor") Signed-off-by: Eric Dumazet Reported-by: syzbot Link: https://patch.msgid.link/20260122162914.2299312-1-edumazet@google.com Signed-off-by: Jakub Kicinski Signed-off-by: Sasha Levin --- drivers/net/bonding/bond_main.c | 18 ++++++++++-------- drivers/net/bonding/bond_options.c | 8 ++++---- include/net/bonding.h | 13 +++++++------ 3 files changed, 21 insertions(+), 18 deletions(-) diff --git a/drivers/net/bonding/bond_main.c b/drivers/net/bonding/bond_main.c index 595fda2444b1fc..99adfffcca0441 100644 --- a/drivers/net/bonding/bond_main.c +++ b/drivers/net/bonding/bond_main.c @@ -3138,8 +3138,8 @@ static void bond_validate_arp(struct bonding *bond, struct slave *slave, __be32 __func__, &sip); return; } - slave->last_rx = jiffies; - slave->target_last_arp_rx[i] = jiffies; + WRITE_ONCE(slave->last_rx, jiffies); + WRITE_ONCE(slave->target_last_arp_rx[i], jiffies); } static int bond_arp_rcv(const struct sk_buff *skb, struct bonding *bond, @@ -3358,8 +3358,8 @@ static void bond_validate_na(struct bonding *bond, struct slave *slave, __func__, saddr); return; } - slave->last_rx = jiffies; - slave->target_last_arp_rx[i] = jiffies; + WRITE_ONCE(slave->last_rx, jiffies); + WRITE_ONCE(slave->target_last_arp_rx[i], jiffies); } static int bond_na_rcv(const struct sk_buff *skb, struct bonding *bond, @@ -3429,7 +3429,7 @@ int bond_rcv_validate(const struct sk_buff *skb, struct bonding *bond, (slave_do_arp_validate_only(bond) && is_ipv6) || #endif !slave_do_arp_validate_only(bond)) - slave->last_rx = jiffies; + WRITE_ONCE(slave->last_rx, jiffies); return RX_HANDLER_ANOTHER; } else if (is_arp) { return bond_arp_rcv(skb, bond, slave); @@ -3497,7 +3497,7 @@ static void bond_loadbalance_arp_mon(struct bonding *bond) if (slave->link != BOND_LINK_UP) { if (bond_time_in_interval(bond, last_tx, 1) && - bond_time_in_interval(bond, slave->last_rx, 1)) { + bond_time_in_interval(bond, READ_ONCE(slave->last_rx), 1)) { bond_propose_link_state(slave, BOND_LINK_UP); slave_state_changed = 1; @@ -3521,8 +3521,10 @@ static void bond_loadbalance_arp_mon(struct bonding *bond) * when the source ip is 0, so don't take the link down * if we don't know our ip yet */ - if (!bond_time_in_interval(bond, last_tx, bond->params.missed_max) || - !bond_time_in_interval(bond, slave->last_rx, bond->params.missed_max)) { + if (!bond_time_in_interval(bond, last_tx, + bond->params.missed_max) || + !bond_time_in_interval(bond, READ_ONCE(slave->last_rx), + bond->params.missed_max)) { bond_propose_link_state(slave, BOND_LINK_DOWN); slave_state_changed = 1; diff --git a/drivers/net/bonding/bond_options.c b/drivers/net/bonding/bond_options.c index 384499c869b8da..f1c6e9d8f61671 100644 --- a/drivers/net/bonding/bond_options.c +++ b/drivers/net/bonding/bond_options.c @@ -1152,7 +1152,7 @@ static void _bond_options_arp_ip_target_set(struct bonding *bond, int slot, if (slot >= 0 && slot < BOND_MAX_ARP_TARGETS) { bond_for_each_slave(bond, slave, iter) - slave->target_last_arp_rx[slot] = last_rx; + WRITE_ONCE(slave->target_last_arp_rx[slot], last_rx); targets[slot] = target; } } @@ -1221,8 +1221,8 @@ static int bond_option_arp_ip_target_rem(struct bonding *bond, __be32 target) bond_for_each_slave(bond, slave, iter) { targets_rx = slave->target_last_arp_rx; for (i = ind; (i < BOND_MAX_ARP_TARGETS-1) && targets[i+1]; i++) - targets_rx[i] = targets_rx[i+1]; - targets_rx[i] = 0; + WRITE_ONCE(targets_rx[i], READ_ONCE(targets_rx[i+1])); + WRITE_ONCE(targets_rx[i], 0); } for (i = ind; (i < BOND_MAX_ARP_TARGETS-1) && targets[i+1]; i++) targets[i] = targets[i+1]; @@ -1377,7 +1377,7 @@ static void _bond_options_ns_ip6_target_set(struct bonding *bond, int slot, if (slot >= 0 && slot < BOND_MAX_NS_TARGETS) { bond_for_each_slave(bond, slave, iter) { - slave->target_last_arp_rx[slot] = last_rx; + WRITE_ONCE(slave->target_last_arp_rx[slot], last_rx); slave_set_ns_maddr(bond, slave, target, &targets[slot]); } targets[slot] = *target; diff --git a/include/net/bonding.h b/include/net/bonding.h index 49edc7da05867f..46207840355709 100644 --- a/include/net/bonding.h +++ b/include/net/bonding.h @@ -521,13 +521,14 @@ static inline int bond_is_ip6_target_ok(struct in6_addr *addr) static inline unsigned long slave_oldest_target_arp_rx(struct bonding *bond, struct slave *slave) { + unsigned long tmp, ret = READ_ONCE(slave->target_last_arp_rx[0]); int i = 1; - unsigned long ret = slave->target_last_arp_rx[0]; - - for (; (i < BOND_MAX_ARP_TARGETS) && bond->params.arp_targets[i]; i++) - if (time_before(slave->target_last_arp_rx[i], ret)) - ret = slave->target_last_arp_rx[i]; + for (; (i < BOND_MAX_ARP_TARGETS) && bond->params.arp_targets[i]; i++) { + tmp = READ_ONCE(slave->target_last_arp_rx[i]); + if (time_before(tmp, ret)) + ret = tmp; + } return ret; } @@ -537,7 +538,7 @@ static inline unsigned long slave_last_rx(struct bonding *bond, if (bond->params.arp_all_targets == BOND_ARP_TARGETS_ALL) return slave_oldest_target_arp_rx(bond, slave); - return slave->last_rx; + return READ_ONCE(slave->last_rx); } static inline void slave_update_last_tx(struct slave *slave) From 590c8179ffb01c17644181408821b55b8704c50c Mon Sep 17 00:00:00 2001 From: Edward Cree Date: Fri, 23 Jan 2026 16:16:34 +0000 Subject: [PATCH 2864/3902] sfc: fix deadlock in RSS config read [ Upstream commit 944c614b0a7afa5b87612c3fb557b95a50ad654c ] Since cited commit, core locks the net_device's rss_lock when handling ethtool -x command, so driver's implementation should not lock it again. Remove the latter. Fixes: 040cef30b5e6 ("net: ethtool: move get_rxfh callback under the rss_lock") Reported-by: Damir Mansurov Closes: https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=1126015 Suggested-by: Ben Hutchings Signed-off-by: Edward Cree Link: https://patch.msgid.link/20260123161634.1215006-1-edward.cree@amd.com Signed-off-by: Jakub Kicinski Signed-off-by: Sasha Levin --- drivers/net/ethernet/sfc/mcdi_filters.c | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/drivers/net/ethernet/sfc/mcdi_filters.c b/drivers/net/ethernet/sfc/mcdi_filters.c index 6ef96292909a2f..3db589b90b68ad 100644 --- a/drivers/net/ethernet/sfc/mcdi_filters.c +++ b/drivers/net/ethernet/sfc/mcdi_filters.c @@ -2182,12 +2182,7 @@ int efx_mcdi_rx_pull_rss_context_config(struct efx_nic *efx, int efx_mcdi_rx_pull_rss_config(struct efx_nic *efx) { - int rc; - - mutex_lock(&efx->net_dev->ethtool->rss_lock); - rc = efx_mcdi_rx_pull_rss_context_config(efx, &efx->rss_context); - mutex_unlock(&efx->net_dev->ethtool->rss_lock); - return rc; + return efx_mcdi_rx_pull_rss_context_config(efx, &efx->rss_context); } void efx_mcdi_rx_restore_rss_contexts(struct efx_nic *efx) From 0b74c6e1327371b67236a86cbf8d4227ac9f95fa Mon Sep 17 00:00:00 2001 From: Zilin Guan Date: Fri, 23 Jan 2026 06:57:16 +0000 Subject: [PATCH 2865/3902] net: mvpp2: cls: Fix memory leak in mvpp2_ethtool_cls_rule_ins() [ Upstream commit 09f979d1f312627b31d2ee1e46f9692e442610cd ] In mvpp2_ethtool_cls_rule_ins(), the ethtool_rule is allocated by ethtool_rx_flow_rule_create(). If the subsequent conversion to flow type fails, the function jumps to the clean_rule label. However, the clean_rule label only frees efs, skipping the cleanup of ethtool_rule, which leads to a memory leak. Fix this by jumping to the clean_eth_rule label, which properly calls ethtool_rx_flow_rule_destroy() before freeing efs. Compile tested only. Issue found using a prototype static analysis tool and code review. Fixes: f4f1ba18195d ("net: mvpp2: cls: Report an error for unsupported flow types") Signed-off-by: Zilin Guan Reviewed-by: Maxime Chevallier Link: https://patch.msgid.link/20260123065716.2248324-1-zilin@seu.edu.cn Signed-off-by: Jakub Kicinski Signed-off-by: Sasha Levin --- drivers/net/ethernet/marvell/mvpp2/mvpp2_cls.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/net/ethernet/marvell/mvpp2/mvpp2_cls.c b/drivers/net/ethernet/marvell/mvpp2/mvpp2_cls.c index 44b201817d94c6..c116da7d7f18c8 100644 --- a/drivers/net/ethernet/marvell/mvpp2/mvpp2_cls.c +++ b/drivers/net/ethernet/marvell/mvpp2/mvpp2_cls.c @@ -1389,7 +1389,7 @@ int mvpp2_ethtool_cls_rule_ins(struct mvpp2_port *port, efs->rule.flow_type = mvpp2_cls_ethtool_flow_to_type(info->fs.flow_type); if (efs->rule.flow_type < 0) { ret = efs->rule.flow_type; - goto clean_rule; + goto clean_eth_rule; } ret = mvpp2_cls_rfs_parse_rule(&efs->rule); From 500c1237c9a13cc3d684c5f35df561f570265f56 Mon Sep 17 00:00:00 2001 From: Fernando Fernandez Mancera Date: Wed, 21 Jan 2026 20:44:08 +0100 Subject: [PATCH 2866/3902] ipv6: use the right ifindex when replying to icmpv6 from localhost [ Upstream commit 03cbcdf93866e61beb0063392e6dbb701f03aea2 ] When replying to a ICMPv6 echo request that comes from localhost address the right output ifindex is 1 (lo) and not rt6i_idev dev index. Use the skb device ifindex instead. This fixes pinging to a local address from localhost source address. $ ping6 -I ::1 2001:1:1::2 -c 3 PING 2001:1:1::2 (2001:1:1::2) from ::1 : 56 data bytes 64 bytes from 2001:1:1::2: icmp_seq=1 ttl=64 time=0.037 ms 64 bytes from 2001:1:1::2: icmp_seq=2 ttl=64 time=0.069 ms 64 bytes from 2001:1:1::2: icmp_seq=3 ttl=64 time=0.122 ms 2001:1:1::2 ping statistics 3 packets transmitted, 3 received, 0% packet loss, time 2032ms rtt min/avg/max/mdev = 0.037/0.076/0.122/0.035 ms Fixes: 1b70d792cf67 ("ipv6: Use rt6i_idev index for echo replies to a local address") Signed-off-by: Fernando Fernandez Mancera Reviewed-by: David Ahern Link: https://patch.msgid.link/20260121194409.6749-1-fmancera@suse.de Signed-off-by: Jakub Kicinski Signed-off-by: Sasha Levin --- net/ipv6/icmp.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/net/ipv6/icmp.c b/net/ipv6/icmp.c index 56c974cf75d151..cf6455cbe2cc91 100644 --- a/net/ipv6/icmp.c +++ b/net/ipv6/icmp.c @@ -769,7 +769,9 @@ static enum skb_drop_reason icmpv6_echo_reply(struct sk_buff *skb) fl6.daddr = ipv6_hdr(skb)->saddr; if (saddr) fl6.saddr = *saddr; - fl6.flowi6_oif = icmp6_iif(skb); + fl6.flowi6_oif = ipv6_addr_loopback(&fl6.daddr) ? + skb->dev->ifindex : + icmp6_iif(skb); fl6.fl6_icmp_type = type; fl6.flowi6_mark = mark; fl6.flowi6_uid = sock_net_uid(net, NULL); From 2c0fb0f60bc1545c52da61bc6bd4855c1e7814ba Mon Sep 17 00:00:00 2001 From: Kery Qi Date: Fri, 23 Jan 2026 01:04:01 +0800 Subject: [PATCH 2867/3902] net: wwan: t7xx: fix potential skb->frags overflow in RX path [ Upstream commit f0813bcd2d9d97fdbdf2efb9532ab03ae92e99e6 ] When receiving data in the DPMAIF RX path, the t7xx_dpmaif_set_frag_to_skb() function adds page fragments to an skb without checking if the number of fragments has exceeded MAX_SKB_FRAGS. This could lead to a buffer overflow in skb_shinfo(skb)->frags[] array, corrupting adjacent memory and potentially causing kernel crashes or other undefined behavior. This issue was identified through static code analysis by comparing with a similar vulnerability fixed in the mt76 driver commit b102f0c522cf ("mt76: fix array overflow on receiving too many fragments for a packet"). The vulnerability could be triggered if the modem firmware sends packets with excessive fragments. While under normal protocol conditions (MTU 3080 bytes, BAT buffer 3584 bytes), a single packet should not require additional fragments, the kernel should not blindly trust firmware behavior. Malicious, buggy, or compromised firmware could potentially craft packets with more fragments than the kernel expects. Fix this by adding a bounds check before calling skb_add_rx_frag() to ensure nr_frags does not exceed MAX_SKB_FRAGS. The check must be performed before unmapping to avoid a page leak and double DMA unmap during device teardown. Fixes: d642b012df70a ("net: wwan: t7xx: Add data path interface") Signed-off-by: Kery Qi Link: https://patch.msgid.link/20260122170401.1986-2-qikeyu2017@gmail.com Signed-off-by: Jakub Kicinski Signed-off-by: Sasha Levin --- drivers/net/wwan/t7xx/t7xx_hif_dpmaif_rx.c | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/drivers/net/wwan/t7xx/t7xx_hif_dpmaif_rx.c b/drivers/net/wwan/t7xx/t7xx_hif_dpmaif_rx.c index 2310493203d3c7..d9f10df03a5dba 100644 --- a/drivers/net/wwan/t7xx/t7xx_hif_dpmaif_rx.c +++ b/drivers/net/wwan/t7xx/t7xx_hif_dpmaif_rx.c @@ -395,6 +395,7 @@ static int t7xx_dpmaif_set_frag_to_skb(const struct dpmaif_rx_queue *rxq, struct sk_buff *skb) { unsigned long long data_bus_addr, data_base_addr; + struct skb_shared_info *shinfo = skb_shinfo(skb); struct device *dev = rxq->dpmaif_ctrl->dev; struct dpmaif_bat_page *page_info; unsigned int data_len; @@ -402,18 +403,22 @@ static int t7xx_dpmaif_set_frag_to_skb(const struct dpmaif_rx_queue *rxq, page_info = rxq->bat_frag->bat_skb; page_info += t7xx_normal_pit_bid(pkt_info); - dma_unmap_page(dev, page_info->data_bus_addr, page_info->data_len, DMA_FROM_DEVICE); if (!page_info->page) return -EINVAL; + if (shinfo->nr_frags >= MAX_SKB_FRAGS) + return -EINVAL; + + dma_unmap_page(dev, page_info->data_bus_addr, page_info->data_len, DMA_FROM_DEVICE); + data_bus_addr = le32_to_cpu(pkt_info->pd.data_addr_h); data_bus_addr = (data_bus_addr << 32) + le32_to_cpu(pkt_info->pd.data_addr_l); data_base_addr = page_info->data_bus_addr; data_offset = data_bus_addr - data_base_addr; data_offset += page_info->offset; data_len = FIELD_GET(PD_PIT_DATA_LEN, le32_to_cpu(pkt_info->header)); - skb_add_rx_frag(skb, skb_shinfo(skb)->nr_frags, page_info->page, + skb_add_rx_frag(skb, shinfo->nr_frags, page_info->page, data_offset, data_len, page_info->data_len); page_info->page = NULL; From cdc4deb9e7be2063aa1fcd6b3efe5a0a68f8dde1 Mon Sep 17 00:00:00 2001 From: Zeng Chi Date: Fri, 23 Jan 2026 16:57:49 +0800 Subject: [PATCH 2868/3902] net/mlx5: Fix return type mismatch in mlx5_esw_vport_vhca_id() [ Upstream commit ca12c4a155ebf84e9ef29b05ce979bc89364290f ] The function mlx5_esw_vport_vhca_id() is declared to return bool, but returns -EOPNOTSUPP (-45), which is an int error code. This causes a signedness bug as reported by smatch. This patch fixes this smatch report: drivers/net/ethernet/mellanox/mlx5/core/eswitch.h:981 mlx5_esw_vport_vhca_id() warn: signedness bug returning '(-45)' Fixes: 1baf30426553 ("net/mlx5: E-Switch, Set/Query hca cap via vhca id") Reviewed-by: Parav Pandit Signed-off-by: Zeng Chi Reviewed-by: Tariq Toukan Link: https://patch.msgid.link/20260123085749.1401969-1-zeng_chi911@163.com Signed-off-by: Jakub Kicinski Signed-off-by: Sasha Levin --- drivers/net/ethernet/mellanox/mlx5/core/eswitch.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/net/ethernet/mellanox/mlx5/core/eswitch.h b/drivers/net/ethernet/mellanox/mlx5/core/eswitch.h index 16eb99aba2a7e0..2d91f77b016012 100644 --- a/drivers/net/ethernet/mellanox/mlx5/core/eswitch.h +++ b/drivers/net/ethernet/mellanox/mlx5/core/eswitch.h @@ -1002,7 +1002,7 @@ mlx5_esw_host_functions_enabled(const struct mlx5_core_dev *dev) static inline bool mlx5_esw_vport_vhca_id(struct mlx5_eswitch *esw, u16 vportn, u16 *vhca_id) { - return -EOPNOTSUPP; + return false; } #endif /* CONFIG_MLX5_ESWITCH */ From dce375f4afc348c310d171abcde7ec1499a4c26a Mon Sep 17 00:00:00 2001 From: Kery Qi Date: Sat, 24 Jan 2026 05:10:31 +0800 Subject: [PATCH 2869/3902] rocker: fix memory leak in rocker_world_port_post_fini() [ Upstream commit 8d7ba71e46216b8657a82ca2ec118bc93812a4d0 ] In rocker_world_port_pre_init(), rocker_port->wpriv is allocated with kzalloc(wops->port_priv_size, GFP_KERNEL). However, in rocker_world_port_post_fini(), the memory is only freed when wops->port_post_fini callback is set: if (!wops->port_post_fini) return; wops->port_post_fini(rocker_port); kfree(rocker_port->wpriv); Since rocker_ofdpa_ops does not implement port_post_fini callback (it is NULL), the wpriv memory allocated for each port is never freed when ports are removed. This leads to a memory leak of sizeof(struct ofdpa_port) bytes per port on every device removal. Fix this by always calling kfree(rocker_port->wpriv) regardless of whether the port_post_fini callback exists. Fixes: e420114eef4a ("rocker: introduce worlds infrastructure") Signed-off-by: Kery Qi Reviewed-by: Simon Horman Link: https://patch.msgid.link/20260123211030.2109-2-qikeyu2017@gmail.com Signed-off-by: Jakub Kicinski Signed-off-by: Sasha Levin --- drivers/net/ethernet/rocker/rocker_main.c | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/drivers/net/ethernet/rocker/rocker_main.c b/drivers/net/ethernet/rocker/rocker_main.c index 36af94a2e062aa..2794f75df8fcba 100644 --- a/drivers/net/ethernet/rocker/rocker_main.c +++ b/drivers/net/ethernet/rocker/rocker_main.c @@ -1524,9 +1524,8 @@ static void rocker_world_port_post_fini(struct rocker_port *rocker_port) { struct rocker_world_ops *wops = rocker_port->rocker->wops; - if (!wops->port_post_fini) - return; - wops->port_post_fini(rocker_port); + if (wops->port_post_fini) + wops->port_post_fini(rocker_port); kfree(rocker_port->wpriv); } From 1f1b9523527df02685dde603f20ff6e603d8e4a1 Mon Sep 17 00:00:00 2001 From: Eric Dumazet Date: Sat, 24 Jan 2026 11:59:18 +0100 Subject: [PATCH 2870/3902] mptcp: fix race in mptcp_pm_nl_flush_addrs_doit() [ Upstream commit e2a9eeb69f7d4ca4cf4c70463af77664fdb6ab1d ] syzbot and Eulgyu Kim reported crashes in mptcp_pm_nl_get_local_id() and/or mptcp_pm_nl_is_backup() Root cause is list_splice_init() in mptcp_pm_nl_flush_addrs_doit() which is not RCU ready. list_splice_init_rcu() can not be called here while holding pernet->lock spinlock. Many thanks to Eulgyu Kim for providing a repro and testing our patches. Fixes: 141694df6573 ("mptcp: remove address when netlink flushes addrs") Signed-off-by: Eric Dumazet Reported-by: syzbot+5498a510ff9de39d37da@syzkaller.appspotmail.com Closes: https://lore.kernel.org/all/6970a46d.a00a0220.3ad28e.5cf0.GAE@google.com/T/ Reported-by: Eulgyu Kim Closes: https://github.com/multipath-tcp/mptcp_net-next/issues/611 Reviewed-by: Mat Martineau Signed-off-by: Matthieu Baerts (NGI0) Link: https://patch.msgid.link/20260124-net-mptcp-race_nl_flush_addrs-v3-1-b2dc1b613e9d@kernel.org Signed-off-by: Jakub Kicinski Signed-off-by: Sasha Levin --- net/mptcp/pm_kernel.c | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/net/mptcp/pm_kernel.c b/net/mptcp/pm_kernel.c index 0a50fd5edc06db..1b517a81e02997 100644 --- a/net/mptcp/pm_kernel.c +++ b/net/mptcp/pm_kernel.c @@ -1276,16 +1276,26 @@ static void __reset_counters(struct pm_nl_pernet *pernet) int mptcp_pm_nl_flush_addrs_doit(struct sk_buff *skb, struct genl_info *info) { struct pm_nl_pernet *pernet = genl_info_pm_nl(info); - LIST_HEAD(free_list); + struct list_head free_list; spin_lock_bh(&pernet->lock); - list_splice_init(&pernet->endp_list, &free_list); + free_list = pernet->endp_list; + INIT_LIST_HEAD_RCU(&pernet->endp_list); __reset_counters(pernet); pernet->next_id = 1; bitmap_zero(pernet->id_bitmap, MPTCP_PM_MAX_ADDR_ID + 1); spin_unlock_bh(&pernet->lock); - mptcp_nl_flush_addrs_list(sock_net(skb->sk), &free_list); + + if (free_list.next == &pernet->endp_list) + return 0; + synchronize_rcu(); + + /* Adjust the pointers to free_list instead of pernet->endp_list */ + free_list.prev->next = &free_list; + free_list.next->prev = &free_list; + + mptcp_nl_flush_addrs_list(sock_net(skb->sk), &free_list); __flush_addrs(&free_list); return 0; } From fdb99161cbef29dffed56a1cbec13fce301167db Mon Sep 17 00:00:00 2001 From: Vivian Wang Date: Fri, 23 Jan 2026 11:52:23 +0800 Subject: [PATCH 2871/3902] net: spacemit: Check for netif_carrier_ok() in emac_stats_update() [ Upstream commit 2c84959167d6493dbdac88965c7389b8ab88bf4e ] Some PHYs stop the refclk for power saving, usually while link down. This causes reading stats to time out. Therefore, in emac_stats_update(), also don't update and reschedule if !netif_carrier_ok(). But that means we could be missing later updates if the link comes back up, so also reschedule when link up is detected in emac_adjust_link(). While we're at it, improve the comments and error message prints around this to reflect the better understanding of how this could happen. Hopefully if this happens again on new hardware, these comments will direct towards a solution. Closes: https://lore.kernel.org/r/20260119141620.1318102-1-amadeus@jmu.edu.cn/ Fixes: bfec6d7f2001 ("net: spacemit: Add K1 Ethernet MAC") Co-developed-by: Chukun Pan Signed-off-by: Chukun Pan Signed-off-by: Vivian Wang Link: https://patch.msgid.link/20260123-k1-ethernet-clarify-stat-timeout-v3-1-93b9df627e87@iscas.ac.cn Signed-off-by: Jakub Kicinski Signed-off-by: Sasha Levin --- drivers/net/ethernet/spacemit/k1_emac.c | 34 ++++++++++++++++++++----- 1 file changed, 27 insertions(+), 7 deletions(-) diff --git a/drivers/net/ethernet/spacemit/k1_emac.c b/drivers/net/ethernet/spacemit/k1_emac.c index 220eb5ce75833d..88e9424d2d51aa 100644 --- a/drivers/net/ethernet/spacemit/k1_emac.c +++ b/drivers/net/ethernet/spacemit/k1_emac.c @@ -1099,7 +1099,13 @@ static int emac_read_stat_cnt(struct emac_priv *priv, u8 cnt, u32 *res, 100, 10000); if (ret) { - netdev_err(priv->ndev, "Read stat timeout\n"); + /* + * This could be caused by the PHY stopping its refclk even when + * the link is up, for power saving. See also comments in + * emac_stats_update(). + */ + dev_err_ratelimited(&priv->ndev->dev, + "Read stat timeout. PHY clock stopped?\n"); return ret; } @@ -1147,17 +1153,25 @@ static void emac_stats_update(struct emac_priv *priv) assert_spin_locked(&priv->stats_lock); - if (!netif_running(priv->ndev) || !netif_device_present(priv->ndev)) { - /* Not up, don't try to update */ + /* + * We can't read statistics if the interface is not up. Also, some PHYs + * stop their reference clocks for link down power saving, which also + * causes reading statistics to time out. Don't update and don't + * reschedule in these cases. + */ + if (!netif_running(priv->ndev) || + !netif_carrier_ok(priv->ndev) || + !netif_device_present(priv->ndev)) { return; } for (i = 0; i < sizeof(priv->tx_stats) / sizeof(*tx_stats); i++) { /* - * If reading stats times out, everything is broken and there's - * nothing we can do. Reading statistics also can't return an - * error, so just return without updating and without - * rescheduling. + * If reading stats times out anyway, the stat registers will be + * stuck, and we can't really recover from that. + * + * Reading statistics also can't return an error, so just return + * without updating and without rescheduling. */ if (emac_tx_read_stat_cnt(priv, i, &res)) return; @@ -1636,6 +1650,12 @@ static void emac_adjust_link(struct net_device *dev) emac_wr(priv, MAC_GLOBAL_CONTROL, ctrl); emac_set_fc_autoneg(priv); + + /* + * Reschedule stats updates now that link is up. See comments in + * emac_stats_update(). + */ + mod_timer(&priv->stats_timer, jiffies); } phy_print_status(phydev); From 61858cbce6ca4bef9ed116c689a4be9520841339 Mon Sep 17 00:00:00 2001 From: Kuniyuki Iwashima Date: Sun, 25 Jan 2026 00:59:28 +0000 Subject: [PATCH 2872/3902] nfc: llcp: Fix memleak in nfc_llcp_send_ui_frame(). [ Upstream commit 165c34fb6068ff153e3fc99a932a80a9d5755709 ] syzbot reported various memory leaks related to NFC, struct nfc_llcp_sock, sk_buff, nfc_dev, etc. [0] The leading log hinted that nfc_llcp_send_ui_frame() failed to allocate skb due to sock_error(sk) being -ENXIO. ENXIO is set by nfc_llcp_socket_release() when struct nfc_llcp_local is destroyed by local_cleanup(). The problem is that there is no synchronisation between nfc_llcp_send_ui_frame() and local_cleanup(), and skb could be put into local->tx_queue after it was purged in local_cleanup(): CPU1 CPU2 ---- ---- nfc_llcp_send_ui_frame() local_cleanup() |- do { ' |- pdu = nfc_alloc_send_skb(..., &err) | . | |- nfc_llcp_socket_release(local, false, ENXIO); | |- skb_queue_purge(&local->tx_queue); | | ' | |- skb_queue_tail(&local->tx_queue, pdu); | ... | |- pdu = nfc_alloc_send_skb(..., &err) | ^._________________________________.' local_cleanup() is called for struct nfc_llcp_local only after nfc_llcp_remove_local() unlinks it from llcp_devices. If we hold local->tx_queue.lock then, we can synchronise the thread and nfc_llcp_send_ui_frame(). Let's do that and check list_empty(&local->list) before queuing skb to local->tx_queue in nfc_llcp_send_ui_frame(). [0]: [ 56.074943][ T6096] llcp: nfc_llcp_send_ui_frame: Could not allocate PDU (error=-6) [ 64.318868][ T5813] kmemleak: 6 new suspected memory leaks (see /sys/kernel/debug/kmemleak) BUG: memory leak unreferenced object 0xffff8881272f6800 (size 1024): comm "syz.0.17", pid 6096, jiffies 4294942766 hex dump (first 32 bytes): 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 27 00 03 40 00 00 00 00 00 00 00 00 00 00 00 00 '..@............ backtrace (crc da58d84d): kmemleak_alloc_recursive include/linux/kmemleak.h:44 [inline] slab_post_alloc_hook mm/slub.c:4979 [inline] slab_alloc_node mm/slub.c:5284 [inline] __do_kmalloc_node mm/slub.c:5645 [inline] __kmalloc_noprof+0x3e3/0x6b0 mm/slub.c:5658 kmalloc_noprof include/linux/slab.h:961 [inline] sk_prot_alloc+0x11a/0x1b0 net/core/sock.c:2239 sk_alloc+0x36/0x360 net/core/sock.c:2295 nfc_llcp_sock_alloc+0x37/0x130 net/nfc/llcp_sock.c:979 llcp_sock_create+0x71/0xd0 net/nfc/llcp_sock.c:1044 nfc_sock_create+0xc9/0xf0 net/nfc/af_nfc.c:31 __sock_create+0x1a9/0x340 net/socket.c:1605 sock_create net/socket.c:1663 [inline] __sys_socket_create net/socket.c:1700 [inline] __sys_socket+0xb9/0x1a0 net/socket.c:1747 __do_sys_socket net/socket.c:1761 [inline] __se_sys_socket net/socket.c:1759 [inline] __x64_sys_socket+0x1b/0x30 net/socket.c:1759 do_syscall_x64 arch/x86/entry/syscall_64.c:63 [inline] do_syscall_64+0xa4/0xfa0 arch/x86/entry/syscall_64.c:94 entry_SYSCALL_64_after_hwframe+0x77/0x7f BUG: memory leak unreferenced object 0xffff88810fbd9800 (size 240): comm "syz.0.17", pid 6096, jiffies 4294942850 hex dump (first 32 bytes): 68 f0 ff 08 81 88 ff ff 68 f0 ff 08 81 88 ff ff h.......h....... 00 00 00 00 00 00 00 00 00 68 2f 27 81 88 ff ff .........h/'.... backtrace (crc 6cc652b1): kmemleak_alloc_recursive include/linux/kmemleak.h:44 [inline] slab_post_alloc_hook mm/slub.c:4979 [inline] slab_alloc_node mm/slub.c:5284 [inline] kmem_cache_alloc_node_noprof+0x36f/0x5e0 mm/slub.c:5336 __alloc_skb+0x203/0x240 net/core/skbuff.c:660 alloc_skb include/linux/skbuff.h:1383 [inline] alloc_skb_with_frags+0x69/0x3f0 net/core/skbuff.c:6671 sock_alloc_send_pskb+0x379/0x3e0 net/core/sock.c:2965 sock_alloc_send_skb include/net/sock.h:1859 [inline] nfc_alloc_send_skb+0x45/0x80 net/nfc/core.c:724 nfc_llcp_send_ui_frame+0x162/0x360 net/nfc/llcp_commands.c:766 llcp_sock_sendmsg+0x14c/0x1d0 net/nfc/llcp_sock.c:814 sock_sendmsg_nosec net/socket.c:727 [inline] __sock_sendmsg net/socket.c:742 [inline] __sys_sendto+0x2d8/0x2f0 net/socket.c:2244 __do_sys_sendto net/socket.c:2251 [inline] __se_sys_sendto net/socket.c:2247 [inline] __x64_sys_sendto+0x28/0x30 net/socket.c:2247 do_syscall_x64 arch/x86/entry/syscall_64.c:63 [inline] do_syscall_64+0xa4/0xfa0 arch/x86/entry/syscall_64.c:94 entry_SYSCALL_64_after_hwframe+0x77/0x7f Fixes: 94f418a20664 ("NFC: UI frame sending routine implementation") Reported-by: syzbot+f2d245f1d76bbfa50e4c@syzkaller.appspotmail.com Closes: https://lore.kernel.org/netdev/697569c7.a00a0220.33ccc7.0014.GAE@google.com/T/#u Signed-off-by: Kuniyuki Iwashima Reviewed-by: Simon Horman Link: https://patch.msgid.link/20260125010214.1572439-1-kuniyu@google.com Signed-off-by: Jakub Kicinski Signed-off-by: Sasha Levin --- net/nfc/llcp_commands.c | 17 ++++++++++++++++- net/nfc/llcp_core.c | 4 +++- 2 files changed, 19 insertions(+), 2 deletions(-) diff --git a/net/nfc/llcp_commands.c b/net/nfc/llcp_commands.c index e2680a3bef7995..b652323bc2c12b 100644 --- a/net/nfc/llcp_commands.c +++ b/net/nfc/llcp_commands.c @@ -778,8 +778,23 @@ int nfc_llcp_send_ui_frame(struct nfc_llcp_sock *sock, u8 ssap, u8 dsap, if (likely(frag_len > 0)) skb_put_data(pdu, msg_ptr, frag_len); + spin_lock(&local->tx_queue.lock); + + if (list_empty(&local->list)) { + spin_unlock(&local->tx_queue.lock); + + kfree_skb(pdu); + + len -= remaining_len; + if (len == 0) + len = -ENXIO; + break; + } + /* No need to check for the peer RW for UI frames */ - skb_queue_tail(&local->tx_queue, pdu); + __skb_queue_tail(&local->tx_queue, pdu); + + spin_unlock(&local->tx_queue.lock); remaining_len -= frag_len; msg_ptr += frag_len; diff --git a/net/nfc/llcp_core.c b/net/nfc/llcp_core.c index beeb3b4d28caba..444a3774c8e806 100644 --- a/net/nfc/llcp_core.c +++ b/net/nfc/llcp_core.c @@ -316,7 +316,9 @@ static struct nfc_llcp_local *nfc_llcp_remove_local(struct nfc_dev *dev) spin_lock(&llcp_devices_lock); list_for_each_entry_safe(local, tmp, &llcp_devices, list) if (local->dev == dev) { - list_del(&local->list); + spin_lock(&local->tx_queue.lock); + list_del_init(&local->list); + spin_unlock(&local->tx_queue.lock); spin_unlock(&llcp_devices_lock); return local; } From bd25b092a06a3e05f7e8bd6da6fa7318777d8c3d Mon Sep 17 00:00:00 2001 From: Nikolay Aleksandrov Date: Fri, 23 Jan 2026 14:06:59 +0200 Subject: [PATCH 2873/3902] bonding: fix use-after-free due to enslave fail after slave array update [ Upstream commit e9acda52fd2ee0cdca332f996da7a95c5fd25294 ] Fix a use-after-free which happens due to enslave failure after the new slave has been added to the array. Since the new slave can be used for Tx immediately, we can use it after it has been freed by the enslave error cleanup path which frees the allocated slave memory. Slave update array is supposed to be called last when further enslave failures are not expected. Move it after xdp setup to avoid any problems. It is very easy to reproduce the problem with a simple xdp_pass prog: ip l add bond1 type bond mode balance-xor ip l set bond1 up ip l set dev bond1 xdp object xdp_pass.o sec xdp_pass ip l add dumdum type dummy Then run in parallel: while :; do ip l set dumdum master bond1 1>/dev/null 2>&1; done; mausezahn bond1 -a own -b rand -A rand -B 1.1.1.1 -c 0 -t tcp "dp=1-1023, flags=syn" The crash happens almost immediately: [ 605.602850] Oops: general protection fault, probably for non-canonical address 0xe0e6fc2460000137: 0000 [#1] SMP KASAN NOPTI [ 605.602916] KASAN: maybe wild-memory-access in range [0x07380123000009b8-0x07380123000009bf] [ 605.602946] CPU: 0 UID: 0 PID: 2445 Comm: mausezahn Kdump: loaded Tainted: G B 6.19.0-rc6+ #21 PREEMPT(voluntary) [ 605.602979] Tainted: [B]=BAD_PAGE [ 605.602998] Hardware name: QEMU Standard PC (Q35 + ICH9, 2009), BIOS 1.16.3-debian-1.16.3-2 04/01/2014 [ 605.603032] RIP: 0010:netdev_core_pick_tx+0xcd/0x210 [ 605.603063] Code: 48 89 fa 48 c1 ea 03 80 3c 02 00 0f 85 3e 01 00 00 48 b8 00 00 00 00 00 fc ff df 4c 8b 6b 08 49 8d 7d 30 48 89 fa 48 c1 ea 03 <80> 3c 02 00 0f 85 25 01 00 00 49 8b 45 30 4c 89 e2 48 89 ee 48 89 [ 605.603111] RSP: 0018:ffff88817b9af348 EFLAGS: 00010213 [ 605.603145] RAX: dffffc0000000000 RBX: ffff88817d28b420 RCX: 0000000000000000 [ 605.603172] RDX: 00e7002460000137 RSI: 0000000000000008 RDI: 07380123000009be [ 605.603199] RBP: ffff88817b541a00 R08: 0000000000000001 R09: fffffbfff3ed8c0c [ 605.603226] R10: ffffffff9f6c6067 R11: 0000000000000001 R12: 0000000000000000 [ 605.603253] R13: 073801230000098e R14: ffff88817d28b448 R15: ffff88817b541a84 [ 605.603286] FS: 00007f6570ef67c0(0000) GS:ffff888221dfa000(0000) knlGS:0000000000000000 [ 605.603319] CS: 0010 DS: 0000 ES: 0000 CR0: 0000000080050033 [ 605.603343] CR2: 00007f65712fae40 CR3: 000000011371b000 CR4: 0000000000350ef0 [ 605.603373] Call Trace: [ 605.603392] [ 605.603410] __dev_queue_xmit+0x448/0x32a0 [ 605.603434] ? __pfx_vprintk_emit+0x10/0x10 [ 605.603461] ? __pfx_vprintk_emit+0x10/0x10 [ 605.603484] ? __pfx___dev_queue_xmit+0x10/0x10 [ 605.603507] ? bond_start_xmit+0xbfb/0xc20 [bonding] [ 605.603546] ? _printk+0xcb/0x100 [ 605.603566] ? __pfx__printk+0x10/0x10 [ 605.603589] ? bond_start_xmit+0xbfb/0xc20 [bonding] [ 605.603627] ? add_taint+0x5e/0x70 [ 605.603648] ? add_taint+0x2a/0x70 [ 605.603670] ? end_report.cold+0x51/0x75 [ 605.603693] ? bond_start_xmit+0xbfb/0xc20 [bonding] [ 605.603731] bond_start_xmit+0x623/0xc20 [bonding] Fixes: 9e2ee5c7e7c3 ("net, bonding: Add XDP support to the bonding driver") Signed-off-by: Nikolay Aleksandrov Reported-by: Chen Zhen Closes: https://lore.kernel.org/netdev/fae17c21-4940-5605-85b2-1d5e17342358@huawei.com/ CC: Jussi Maki CC: Daniel Borkmann Acked-by: Daniel Borkmann Link: https://patch.msgid.link/20260123120659.571187-1-razor@blackwall.org Signed-off-by: Paolo Abeni Signed-off-by: Sasha Levin --- drivers/net/bonding/bond_main.c | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/drivers/net/bonding/bond_main.c b/drivers/net/bonding/bond_main.c index 99adfffcca0441..51733fb29bd77a 100644 --- a/drivers/net/bonding/bond_main.c +++ b/drivers/net/bonding/bond_main.c @@ -2293,11 +2293,6 @@ int bond_enslave(struct net_device *bond_dev, struct net_device *slave_dev, unblock_netpoll_tx(); } - /* broadcast mode uses the all_slaves to loop through slaves. */ - if (bond_mode_can_use_xmit_hash(bond) || - BOND_MODE(bond) == BOND_MODE_BROADCAST) - bond_update_slave_arr(bond, NULL); - if (!slave_dev->netdev_ops->ndo_bpf || !slave_dev->netdev_ops->ndo_xdp_xmit) { if (bond->xdp_prog) { @@ -2331,6 +2326,11 @@ int bond_enslave(struct net_device *bond_dev, struct net_device *slave_dev, bpf_prog_inc(bond->xdp_prog); } + /* broadcast mode uses the all_slaves to loop through slaves. */ + if (bond_mode_can_use_xmit_hash(bond) || + BOND_MODE(bond) == BOND_MODE_BROADCAST) + bond_update_slave_arr(bond, NULL); + bond_xdp_set_features(bond_dev); slave_info(bond_dev, slave_dev, "Enslaving as %s interface with %s link\n", From c721ea2ff56726412dafb6e4eaf7a04bd99d4df2 Mon Sep 17 00:00:00 2001 From: Kohei Enju Date: Thu, 11 Dec 2025 18:15:31 +0900 Subject: [PATCH 2874/3902] ixgbe: fix memory leaks in the ixgbe_recovery_probe() path [ Upstream commit 638344712aefeba97b6e0d90f560815fd88abd0f ] When ixgbe_recovery_probe() is invoked and this function fails, allocated resources in advance are not completely freed, because ixgbe_probe() returns ixgbe_recovery_probe() directly and ixgbe_recovery_probe() only frees partial resources, resulting in memory leaks including: - adapter->io_addr - adapter->jump_tables[0] - adapter->mac_table - adapter->rss_key - adapter->af_xdp_zc_qps The leaked MMIO region can be observed in /proc/vmallocinfo, and the remaining leaks are reported by kmemleak. Don't return ixgbe_recovery_probe() directly, and instead let ixgbe_probe() to clean up resources on failures. Fixes: 29cb3b8d95c7 ("ixgbe: add E610 implementation of FW recovery mode") Signed-off-by: Kohei Enju Signed-off-by: Tony Nguyen Signed-off-by: Sasha Levin --- drivers/net/ethernet/intel/ixgbe/ixgbe_main.c | 20 ++++++++----------- 1 file changed, 8 insertions(+), 12 deletions(-) diff --git a/drivers/net/ethernet/intel/ixgbe/ixgbe_main.c b/drivers/net/ethernet/intel/ixgbe/ixgbe_main.c index 3190ce7e44c742..ee1007e9b63556 100644 --- a/drivers/net/ethernet/intel/ixgbe/ixgbe_main.c +++ b/drivers/net/ethernet/intel/ixgbe/ixgbe_main.c @@ -11468,14 +11468,12 @@ static void ixgbe_set_fw_version(struct ixgbe_adapter *adapter) */ static int ixgbe_recovery_probe(struct ixgbe_adapter *adapter) { - struct net_device *netdev = adapter->netdev; struct pci_dev *pdev = adapter->pdev; struct ixgbe_hw *hw = &adapter->hw; - bool disable_dev; int err = -EIO; if (hw->mac.type != ixgbe_mac_e610) - goto clean_up_probe; + return err; ixgbe_get_hw_control(adapter); mutex_init(&hw->aci.lock); @@ -11507,13 +11505,6 @@ static int ixgbe_recovery_probe(struct ixgbe_adapter *adapter) shutdown_aci: mutex_destroy(&adapter->hw.aci.lock); ixgbe_release_hw_control(adapter); -clean_up_probe: - disable_dev = !test_and_set_bit(__IXGBE_DISABLED, &adapter->state); - free_netdev(netdev); - devlink_free(adapter->devlink); - pci_release_mem_regions(pdev); - if (disable_dev) - pci_disable_device(pdev); return err; } @@ -11655,8 +11646,13 @@ static int ixgbe_probe(struct pci_dev *pdev, const struct pci_device_id *ent) if (err) goto err_sw_init; - if (ixgbe_check_fw_error(adapter)) - return ixgbe_recovery_probe(adapter); + if (ixgbe_check_fw_error(adapter)) { + err = ixgbe_recovery_probe(adapter); + if (err) + goto err_sw_init; + + return 0; + } if (adapter->hw.mac.type == ixgbe_mac_e610) { err = ixgbe_get_caps(&adapter->hw); From 214aed313f7a59be4fe34acabd73d957a7851f12 Mon Sep 17 00:00:00 2001 From: Kohei Enju Date: Thu, 11 Dec 2025 18:15:32 +0900 Subject: [PATCH 2875/3902] ixgbe: don't initialize aci lock in ixgbe_recovery_probe() [ Upstream commit 100cf7b4ca6ed770ec4287f3789b1da2e340a05a ] hw->aci.lock is already initialized in ixgbe_sw_init(), so ixgbe_recovery_probe() doesn't need to initialize the lock. This function is also not responsible for destroying the lock on failures. Additionally, change the name of label in accordance with this change. Fixes: 29cb3b8d95c7 ("ixgbe: add E610 implementation of FW recovery mode") Reported-by: Simon Horman Closes: https://lore.kernel.org/intel-wired-lan/aTcFhoH-z2btEKT-@horms.kernel.org/ Signed-off-by: Kohei Enju Signed-off-by: Tony Nguyen Signed-off-by: Sasha Levin --- drivers/net/ethernet/intel/ixgbe/ixgbe_main.c | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/drivers/net/ethernet/intel/ixgbe/ixgbe_main.c b/drivers/net/ethernet/intel/ixgbe/ixgbe_main.c index ee1007e9b63556..3edebca9583076 100644 --- a/drivers/net/ethernet/intel/ixgbe/ixgbe_main.c +++ b/drivers/net/ethernet/intel/ixgbe/ixgbe_main.c @@ -11476,10 +11476,9 @@ static int ixgbe_recovery_probe(struct ixgbe_adapter *adapter) return err; ixgbe_get_hw_control(adapter); - mutex_init(&hw->aci.lock); err = ixgbe_get_flash_data(&adapter->hw); if (err) - goto shutdown_aci; + goto err_release_hw_control; timer_setup(&adapter->service_timer, ixgbe_service_timer, 0); INIT_WORK(&adapter->service_task, ixgbe_recovery_service_task); @@ -11502,8 +11501,7 @@ static int ixgbe_recovery_probe(struct ixgbe_adapter *adapter) devl_unlock(adapter->devlink); return 0; -shutdown_aci: - mutex_destroy(&adapter->hw.aci.lock); +err_release_hw_control: ixgbe_release_hw_control(adapter); return err; } From 36126ddbe924727add05a594dedf230d3b575e4d Mon Sep 17 00:00:00 2001 From: Aaron Ma Date: Thu, 25 Dec 2025 14:21:21 +0800 Subject: [PATCH 2876/3902] ice: Fix NULL pointer dereference in ice_vsi_set_napi_queues [ Upstream commit 9bb30be4d89ff9a8d7ab1aa0eb2edaca83431f85 ] Add NULL pointer checks in ice_vsi_set_napi_queues() to prevent crashes during resume from suspend when rings[q_idx]->q_vector is NULL. Tested adaptor: 60:00.0 Ethernet controller [0200]: Intel Corporation Ethernet Controller E810-XXV for SFP [8086:159b] (rev 02) Subsystem: Intel Corporation Ethernet Network Adapter E810-XXV-2 [8086:4003] SR-IOV state: both disabled and enabled can reproduce this issue. kernel version: v6.18 Reproduce steps: Boot up and execute suspend like systemctl suspend or rtcwake. Log: <1>[ 231.443607] BUG: kernel NULL pointer dereference, address: 0000000000000040 <1>[ 231.444052] #PF: supervisor read access in kernel mode <1>[ 231.444484] #PF: error_code(0x0000) - not-present page <6>[ 231.444913] PGD 0 P4D 0 <4>[ 231.445342] Oops: Oops: 0000 [#1] SMP NOPTI <4>[ 231.446635] RIP: 0010:netif_queue_set_napi+0xa/0x170 <4>[ 231.447067] Code: 31 f6 31 ff c3 cc cc cc cc 0f 1f 80 00 00 00 00 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 0f 1f 44 00 00 48 85 c9 74 0b <48> 83 79 30 00 0f 84 39 01 00 00 55 41 89 d1 49 89 f8 89 f2 48 89 <4>[ 231.447513] RSP: 0018:ffffcc780fc078c0 EFLAGS: 00010202 <4>[ 231.447961] RAX: ffff8b848ca30400 RBX: ffff8b848caf2028 RCX: 0000000000000010 <4>[ 231.448443] RDX: 0000000000000000 RSI: 0000000000000000 RDI: ffff8b848dbd4000 <4>[ 231.448896] RBP: ffffcc780fc078e8 R08: 0000000000000000 R09: 0000000000000000 <4>[ 231.449345] R10: 0000000000000000 R11: 0000000000000000 R12: 0000000000000001 <4>[ 231.449817] R13: ffff8b848dbd4000 R14: ffff8b84833390c8 R15: 0000000000000000 <4>[ 231.450265] FS: 00007c7b29e9d740(0000) GS:ffff8b8c068e2000(0000) knlGS:0000000000000000 <4>[ 231.450715] CS: 0010 DS: 0000 ES: 0000 CR0: 0000000080050033 <4>[ 231.451179] CR2: 0000000000000040 CR3: 000000030626f004 CR4: 0000000000f72ef0 <4>[ 231.451629] PKRU: 55555554 <4>[ 231.452076] Call Trace: <4>[ 231.452549] <4>[ 231.452996] ? ice_vsi_set_napi_queues+0x4d/0x110 [ice] <4>[ 231.453482] ice_resume+0xfd/0x220 [ice] <4>[ 231.453977] ? __pfx_pci_pm_resume+0x10/0x10 <4>[ 231.454425] pci_pm_resume+0x8c/0x140 <4>[ 231.454872] ? __pfx_pci_pm_resume+0x10/0x10 <4>[ 231.455347] dpm_run_callback+0x5f/0x160 <4>[ 231.455796] ? dpm_wait_for_superior+0x107/0x170 <4>[ 231.456244] device_resume+0x177/0x270 <4>[ 231.456708] dpm_resume+0x209/0x2f0 <4>[ 231.457151] dpm_resume_end+0x15/0x30 <4>[ 231.457596] suspend_devices_and_enter+0x1da/0x2b0 <4>[ 231.458054] enter_state+0x10e/0x570 Add defensive checks for both the ring pointer and its q_vector before dereferencing, allowing the system to resume successfully even when q_vectors are unmapped. Fixes: 2a5dc090b92cf ("ice: move netif_queue_set_napi to rtnl-protected sections") Reviewed-by: Aleksandr Loktionov Signed-off-by: Aaron Ma Reviewed-by: Paul Menzel Signed-off-by: Tony Nguyen Signed-off-by: Sasha Levin --- drivers/net/ethernet/intel/ice/ice_lib.c | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/drivers/net/ethernet/intel/ice/ice_lib.c b/drivers/net/ethernet/intel/ice/ice_lib.c index 5a3e7d6697325f..3d14932871c58b 100644 --- a/drivers/net/ethernet/intel/ice/ice_lib.c +++ b/drivers/net/ethernet/intel/ice/ice_lib.c @@ -2784,12 +2784,14 @@ void ice_vsi_set_napi_queues(struct ice_vsi *vsi) return; ice_for_each_rxq(vsi, q_idx) - netif_queue_set_napi(netdev, q_idx, NETDEV_QUEUE_TYPE_RX, - &vsi->rx_rings[q_idx]->q_vector->napi); + if (vsi->rx_rings[q_idx] && vsi->rx_rings[q_idx]->q_vector) + netif_queue_set_napi(netdev, q_idx, NETDEV_QUEUE_TYPE_RX, + &vsi->rx_rings[q_idx]->q_vector->napi); ice_for_each_txq(vsi, q_idx) - netif_queue_set_napi(netdev, q_idx, NETDEV_QUEUE_TYPE_TX, - &vsi->tx_rings[q_idx]->q_vector->napi); + if (vsi->tx_rings[q_idx] && vsi->tx_rings[q_idx]->q_vector) + netif_queue_set_napi(netdev, q_idx, NETDEV_QUEUE_TYPE_TX, + &vsi->tx_rings[q_idx]->q_vector->napi); /* Also set the interrupt number for the NAPI */ ice_for_each_q_vector(vsi, v_idx) { struct ice_q_vector *q_vector = vsi->q_vectors[v_idx]; From 2df2aad9cf2f478545c7d19283488e1450b86f25 Mon Sep 17 00:00:00 2001 From: Jesse Brandeburg Date: Mon, 1 Dec 2025 15:38:52 -0800 Subject: [PATCH 2877/3902] ice: stop counting UDP csum mismatch as rx_errors [ Upstream commit 05faf2c0a76581d0a7fdbb8ec46477ba183df95b ] Since the beginning, the Intel ice driver has counted receive checksum offload mismatches into the rx_errors member of the rtnl_link_stats64 struct. In ethtool -S these show up as rx_csum_bad.nic. I believe counting these in rx_errors is fundamentally wrong, as it's pretty clear from the comments in if_link.h and from every other statistic the driver is summing into rx_errors, that all of them would cause a "hardware drop" except for the UDP checksum mismatch, as well as the fact that all the other causes for rx_errors are L2 reasons, and this L4 UDP "mismatch" is an outlier. A last nail in the coffin is that rx_errors is monitored in production and can indicate a bad NIC/cable/Switch port, but instead some random series of UDP packets with bad checksums will now trigger this alert. This false positive makes the alert useless and affects us as well as other companies. This packet with presumably a bad UDP checksum is *already* passed to the stack, just not marked as offloaded by the hardware/driver. If it is dropped by the stack it will show up as UDP_MIB_CSUMERRORS. And one more thing, none of the other Intel drivers, and at least bnxt_en and mlx5 both don't appear to count UDP offload mismatches as rx_errors. Here is a related customer complaint: https://community.intel.com/t5/Ethernet-Products/ice-rx-errros-is-too-sensitive-to-IP-TCP-attack-packets-Intel/td-p/1662125 Fixes: 4f1fe43c920b ("ice: Add more Rx errors to netdev's rx_error counter") Cc: Tony Nguyen Cc: Jake Keller Cc: IWL Signed-off-by: Jesse Brandeburg Acked-by: Jacob Keller Signed-off-by: Tony Nguyen Signed-off-by: Sasha Levin --- drivers/net/ethernet/intel/ice/ice_main.c | 1 - 1 file changed, 1 deletion(-) diff --git a/drivers/net/ethernet/intel/ice/ice_main.c b/drivers/net/ethernet/intel/ice/ice_main.c index fc284802e2bcd7..b5ebfcdc9d434e 100644 --- a/drivers/net/ethernet/intel/ice/ice_main.c +++ b/drivers/net/ethernet/intel/ice/ice_main.c @@ -6993,7 +6993,6 @@ void ice_update_vsi_stats(struct ice_vsi *vsi) cur_ns->rx_errors = pf->stats.crc_errors + pf->stats.illegal_bytes + pf->stats.rx_undersize + - pf->hw_csum_rx_error + pf->stats.rx_jabber + pf->stats.rx_fragments + pf->stats.rx_oversize; From fdf8437016f578f18b160c6e14f13ab96bfbc3ba Mon Sep 17 00:00:00 2001 From: Mark Bloch Date: Mon, 26 Jan 2026 09:14:54 +0200 Subject: [PATCH 2878/3902] net/mlx5e: TC, delete flows only for existing peers [ Upstream commit f67666938ae626cbda63fbf5176b3583c07e7124 ] When deleting TC steering flows, iterate only over actual devcom peers instead of assuming all possible ports exist. This avoids touching non-existent peers and ensures cleanup is limited to devices the driver is currently connected to. BUG: kernel NULL pointer dereference, address: 0000000000000008 #PF: supervisor write access in kernel mode #PF: error_code(0x0002) - not-present page PGD 133c8a067 P4D 0 Oops: Oops: 0002 [#1] SMP CPU: 19 UID: 0 PID: 2169 Comm: tc Not tainted 6.18.0+ #156 NONE Hardware name: QEMU Standard PC (Q35 + ICH9, 2009), BIOS rel-1.16.0-0-gd239552ce722-prebuilt.qemu.org 04/01/2014 RIP: 0010:mlx5e_tc_del_fdb_peers_flow+0xbe/0x200 [mlx5_core] Code: 00 00 a8 08 74 a8 49 8b 46 18 f6 c4 02 74 9f 4c 8d bf a0 12 00 00 4c 89 ff e8 0e e7 96 e1 49 8b 44 24 08 49 8b 0c 24 4c 89 ff <48> 89 41 08 48 89 08 49 89 2c 24 49 89 5c 24 08 e8 7d ce 96 e1 49 RSP: 0018:ff11000143867528 EFLAGS: 00010246 RAX: 0000000000000000 RBX: dead000000000122 RCX: 0000000000000000 RDX: ff11000143691580 RSI: ff110001026e5000 RDI: ff11000106f3d2a0 RBP: dead000000000100 R08: 00000000000003fd R09: 0000000000000002 R10: ff11000101c75690 R11: ff1100085faea178 R12: ff11000115f0ae78 R13: 0000000000000000 R14: ff11000115f0a800 R15: ff11000106f3d2a0 FS: 00007f35236bf740(0000) GS:ff110008dc809000(0000) knlGS:0000000000000000 CS: 0010 DS: 0000 ES: 0000 CR0: 0000000080050033 CR2: 0000000000000008 CR3: 0000000157a01001 CR4: 0000000000373eb0 Call Trace: mlx5e_tc_del_flow+0x46/0x270 [mlx5_core] mlx5e_flow_put+0x25/0x50 [mlx5_core] mlx5e_delete_flower+0x2a6/0x3e0 [mlx5_core] tc_setup_cb_reoffload+0x20/0x80 fl_reoffload+0x26f/0x2f0 [cls_flower] ? mlx5e_tc_reoffload_flows_work+0xc0/0xc0 [mlx5_core] ? mlx5e_tc_reoffload_flows_work+0xc0/0xc0 [mlx5_core] tcf_block_playback_offloads+0x9e/0x1c0 tcf_block_unbind+0x7b/0xd0 tcf_block_setup+0x186/0x1d0 tcf_block_offload_cmd.isra.0+0xef/0x130 tcf_block_offload_unbind+0x43/0x70 __tcf_block_put+0x85/0x160 ingress_destroy+0x32/0x110 [sch_ingress] __qdisc_destroy+0x44/0x100 qdisc_graft+0x22b/0x610 tc_get_qdisc+0x183/0x4d0 rtnetlink_rcv_msg+0x2d7/0x3d0 ? rtnl_calcit.isra.0+0x100/0x100 netlink_rcv_skb+0x53/0x100 netlink_unicast+0x249/0x320 ? __alloc_skb+0x102/0x1f0 netlink_sendmsg+0x1e3/0x420 __sock_sendmsg+0x38/0x60 ____sys_sendmsg+0x1ef/0x230 ? copy_msghdr_from_user+0x6c/0xa0 ___sys_sendmsg+0x7f/0xc0 ? ___sys_recvmsg+0x8a/0xc0 ? __sys_sendto+0x119/0x180 __sys_sendmsg+0x61/0xb0 do_syscall_64+0x55/0x640 entry_SYSCALL_64_after_hwframe+0x4b/0x53 RIP: 0033:0x7f35238bb764 Code: 15 b9 86 0c 00 f7 d8 64 89 02 b8 ff ff ff ff eb bf 0f 1f 44 00 00 f3 0f 1e fa 80 3d e5 08 0d 00 00 74 13 b8 2e 00 00 00 0f 05 <48> 3d 00 f0 ff ff 77 4c c3 0f 1f 00 55 48 89 e5 48 83 ec 20 89 55 RSP: 002b:00007ffed4c35638 EFLAGS: 00000202 ORIG_RAX: 000000000000002e RAX: ffffffffffffffda RBX: 000055a2efcc75e0 RCX: 00007f35238bb764 RDX: 0000000000000000 RSI: 00007ffed4c356a0 RDI: 0000000000000003 RBP: 00007ffed4c35710 R08: 0000000000000010 R09: 00007f3523984b20 R10: 0000000000000004 R11: 0000000000000202 R12: 00007ffed4c35790 R13: 000000006947df8f R14: 000055a2efcc75e0 R15: 00007ffed4c35780 Fixes: 9be6c21fdcf8 ("net/mlx5e: Handle offloads flows per peer") Signed-off-by: Mark Bloch Reviewed-by: Shay Drori Signed-off-by: Tariq Toukan Reviewed-by: Simon Horman Link: https://patch.msgid.link/1769411695-18820-3-git-send-email-tariqt@nvidia.com Signed-off-by: Jakub Kicinski Signed-off-by: Sasha Levin --- .../net/ethernet/mellanox/mlx5/core/en_tc.c | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/drivers/net/ethernet/mellanox/mlx5/core/en_tc.c b/drivers/net/ethernet/mellanox/mlx5/core/en_tc.c index 00c2763e57ca17..ebea43c235cc3f 100644 --- a/drivers/net/ethernet/mellanox/mlx5/core/en_tc.c +++ b/drivers/net/ethernet/mellanox/mlx5/core/en_tc.c @@ -2147,11 +2147,14 @@ static void mlx5e_tc_del_fdb_peer_flow(struct mlx5e_tc_flow *flow, static void mlx5e_tc_del_fdb_peers_flow(struct mlx5e_tc_flow *flow) { + struct mlx5_devcom_comp_dev *devcom; + struct mlx5_devcom_comp_dev *pos; + struct mlx5_eswitch *peer_esw; int i; - for (i = 0; i < MLX5_MAX_PORTS; i++) { - if (i == mlx5_get_dev_index(flow->priv->mdev)) - continue; + devcom = flow->priv->mdev->priv.eswitch->devcom; + mlx5_devcom_for_each_peer_entry(devcom, peer_esw, pos) { + i = mlx5_get_dev_index(peer_esw->dev); mlx5e_tc_del_fdb_peer_flow(flow, i); } } @@ -5511,12 +5514,16 @@ int mlx5e_tc_num_filters(struct mlx5e_priv *priv, unsigned long flags) void mlx5e_tc_clean_fdb_peer_flows(struct mlx5_eswitch *esw) { + struct mlx5_devcom_comp_dev *devcom; + struct mlx5_devcom_comp_dev *pos; struct mlx5e_tc_flow *flow, *tmp; + struct mlx5_eswitch *peer_esw; int i; - for (i = 0; i < MLX5_MAX_PORTS; i++) { - if (i == mlx5_get_dev_index(esw->dev)) - continue; + devcom = esw->devcom; + + mlx5_devcom_for_each_peer_entry(devcom, peer_esw, pos) { + i = mlx5_get_dev_index(peer_esw->dev); list_for_each_entry_safe(flow, tmp, &esw->offloads.peer_flows[i], peer[i]) mlx5e_tc_del_fdb_peers_flow(flow); } From 92e0483402afcbabedcc64013c93d8fca4d07105 Mon Sep 17 00:00:00 2001 From: Gal Pressman Date: Mon, 26 Jan 2026 09:14:55 +0200 Subject: [PATCH 2879/3902] net/mlx5e: Account for netdev stats in ndo_get_stats64 [ Upstream commit 476681f10cc1e0e56e26856684e75d4678b072b2 ] The driver's ndo_get_stats64 callback is only reporting mlx5 counters, without accounting for the netdev stats, causing errors from the network stack to be invisible in statistics. Add netdev_stats_to_stats64() call to first populate the counters, then add mlx5 counters on top, ensuring both are accounted for (where appropriate). Fixes: f62b8bb8f2d3 ("net/mlx5: Extend mlx5_core to support ConnectX-4 Ethernet functionality") Signed-off-by: Gal Pressman Signed-off-by: Tariq Toukan Reviewed-by: Simon Horman Link: https://patch.msgid.link/1769411695-18820-4-git-send-email-tariqt@nvidia.com Signed-off-by: Jakub Kicinski Signed-off-by: Sasha Levin --- .../net/ethernet/mellanox/mlx5/core/en_main.c | 20 ++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/drivers/net/ethernet/mellanox/mlx5/core/en_main.c b/drivers/net/ethernet/mellanox/mlx5/core/en_main.c index f8d9968542d9c1..59e17b41c3a677 100644 --- a/drivers/net/ethernet/mellanox/mlx5/core/en_main.c +++ b/drivers/net/ethernet/mellanox/mlx5/core/en_main.c @@ -4033,6 +4033,8 @@ mlx5e_get_stats(struct net_device *dev, struct rtnl_link_stats64 *stats) mlx5e_queue_update_stats(priv); } + netdev_stats_to_stats64(stats, &dev->stats); + if (mlx5e_is_uplink_rep(priv)) { struct mlx5e_vport_stats *vstats = &priv->stats.vport; @@ -4049,21 +4051,21 @@ mlx5e_get_stats(struct net_device *dev, struct rtnl_link_stats64 *stats) mlx5e_fold_sw_stats64(priv, stats); } - stats->rx_missed_errors = priv->stats.qcnt.rx_out_of_buffer; - stats->rx_dropped = PPORT_2863_GET(pstats, if_in_discards); + stats->rx_missed_errors += priv->stats.qcnt.rx_out_of_buffer; + stats->rx_dropped += PPORT_2863_GET(pstats, if_in_discards); - stats->rx_length_errors = + stats->rx_length_errors += PPORT_802_3_GET(pstats, a_in_range_length_errors) + PPORT_802_3_GET(pstats, a_out_of_range_length_field) + PPORT_802_3_GET(pstats, a_frame_too_long_errors) + VNIC_ENV_GET(&priv->stats.vnic, eth_wqe_too_small); - stats->rx_crc_errors = + stats->rx_crc_errors += PPORT_802_3_GET(pstats, a_frame_check_sequence_errors); - stats->rx_frame_errors = PPORT_802_3_GET(pstats, a_alignment_errors); - stats->tx_aborted_errors = PPORT_2863_GET(pstats, if_out_discards); - stats->rx_errors = stats->rx_length_errors + stats->rx_crc_errors + - stats->rx_frame_errors; - stats->tx_errors = stats->tx_aborted_errors + stats->tx_carrier_errors; + stats->rx_frame_errors += PPORT_802_3_GET(pstats, a_alignment_errors); + stats->tx_aborted_errors += PPORT_2863_GET(pstats, if_out_discards); + stats->rx_errors += stats->rx_length_errors + stats->rx_crc_errors + + stats->rx_frame_errors; + stats->tx_errors += stats->tx_aborted_errors + stats->tx_carrier_errors; } static void mlx5e_nic_set_rx_mode(struct mlx5e_priv *priv) From c3369fc5e6120a72169e71acd72e987907a682af Mon Sep 17 00:00:00 2001 From: Kuniyuki Iwashima Date: Tue, 27 Jan 2026 04:03:59 +0000 Subject: [PATCH 2880/3902] nfc: nci: Fix race between rfkill and nci_unregister_device(). [ Upstream commit d2492688bb9fed6ab6e313682c387ae71a66ebae ] syzbot reported the splat below [0] without a repro. It indicates that struct nci_dev.cmd_wq had been destroyed before nci_close_device() was called via rfkill. nci_dev.cmd_wq is only destroyed in nci_unregister_device(), which (I think) was called from virtual_ncidev_close() when syzbot close()d an fd of virtual_ncidev. The problem is that nci_unregister_device() destroys nci_dev.cmd_wq first and then calls nfc_unregister_device(), which removes the device from rfkill by rfkill_unregister(). So, the device is still visible via rfkill even after nci_dev.cmd_wq is destroyed. Let's unregister the device from rfkill first in nci_unregister_device(). Note that we cannot call nfc_unregister_device() before nci_close_device() because 1) nfc_unregister_device() calls device_del() which frees all memory allocated by devm_kzalloc() and linked to ndev->conn_info_list 2) nci_rx_work() could try to queue nci_conn_info to ndev->conn_info_list which could be leaked Thus, nfc_unregister_device() is split into two functions so we can remove rfkill interfaces only before nci_close_device(). [0]: DEBUG_LOCKS_WARN_ON(1) WARNING: kernel/locking/lockdep.c:238 at hlock_class kernel/locking/lockdep.c:238 [inline], CPU#0: syz.0.8675/6349 WARNING: kernel/locking/lockdep.c:238 at check_wait_context kernel/locking/lockdep.c:4854 [inline], CPU#0: syz.0.8675/6349 WARNING: kernel/locking/lockdep.c:238 at __lock_acquire+0x39d/0x2cf0 kernel/locking/lockdep.c:5187, CPU#0: syz.0.8675/6349 Modules linked in: CPU: 0 UID: 0 PID: 6349 Comm: syz.0.8675 Not tainted syzkaller #0 PREEMPT(full) Hardware name: Google Google Compute Engine/Google Compute Engine, BIOS Google 01/13/2026 RIP: 0010:hlock_class kernel/locking/lockdep.c:238 [inline] RIP: 0010:check_wait_context kernel/locking/lockdep.c:4854 [inline] RIP: 0010:__lock_acquire+0x3a4/0x2cf0 kernel/locking/lockdep.c:5187 Code: 18 00 4c 8b 74 24 08 75 27 90 e8 17 f2 fc 02 85 c0 74 1c 83 3d 50 e0 4e 0e 00 75 13 48 8d 3d 43 f7 51 0e 48 c7 c6 8b 3a de 8d <67> 48 0f b9 3a 90 31 c0 0f b6 98 c4 00 00 00 41 8b 45 20 25 ff 1f RSP: 0018:ffffc9000c767680 EFLAGS: 00010046 RAX: 0000000000000001 RBX: 0000000000040000 RCX: 0000000000080000 RDX: ffffc90013080000 RSI: ffffffff8dde3a8b RDI: ffffffff8ff24ca0 RBP: 0000000000000003 R08: ffffffff8fef35a3 R09: 1ffffffff1fde6b4 R10: dffffc0000000000 R11: fffffbfff1fde6b5 R12: 00000000000012a2 R13: ffff888030338ba8 R14: ffff888030338000 R15: ffff888030338b30 FS: 00007fa5995f66c0(0000) GS:ffff8881256f8000(0000) knlGS:0000000000000000 CS: 0010 DS: 0000 ES: 0000 CR0: 0000000080050033 CR2: 00007f7e72f842d0 CR3: 00000000485a0000 CR4: 00000000003526f0 Call Trace: lock_acquire+0x106/0x330 kernel/locking/lockdep.c:5868 touch_wq_lockdep_map+0xcb/0x180 kernel/workqueue.c:3940 __flush_workqueue+0x14b/0x14f0 kernel/workqueue.c:3982 nci_close_device+0x302/0x630 net/nfc/nci/core.c:567 nci_dev_down+0x3b/0x50 net/nfc/nci/core.c:639 nfc_dev_down+0x152/0x290 net/nfc/core.c:161 nfc_rfkill_set_block+0x2d/0x100 net/nfc/core.c:179 rfkill_set_block+0x1d2/0x440 net/rfkill/core.c:346 rfkill_fop_write+0x461/0x5a0 net/rfkill/core.c:1301 vfs_write+0x29a/0xb90 fs/read_write.c:684 ksys_write+0x150/0x270 fs/read_write.c:738 do_syscall_x64 arch/x86/entry/syscall_64.c:63 [inline] do_syscall_64+0xe2/0xf80 arch/x86/entry/syscall_64.c:94 entry_SYSCALL_64_after_hwframe+0x77/0x7f RIP: 0033:0x7fa59b39acb9 Code: ff c3 66 2e 0f 1f 84 00 00 00 00 00 0f 1f 44 00 00 48 89 f8 48 89 f7 48 89 d6 48 89 ca 4d 89 c2 4d 89 c8 4c 8b 4c 24 08 0f 05 <48> 3d 01 f0 ff ff 73 01 c3 48 c7 c1 e8 ff ff ff f7 d8 64 89 01 48 RSP: 002b:00007fa5995f6028 EFLAGS: 00000246 ORIG_RAX: 0000000000000001 RAX: ffffffffffffffda RBX: 00007fa59b615fa0 RCX: 00007fa59b39acb9 RDX: 0000000000000008 RSI: 0000200000000080 RDI: 0000000000000007 RBP: 00007fa59b408bf7 R08: 0000000000000000 R09: 0000000000000000 R10: 0000000000000000 R11: 0000000000000246 R12: 0000000000000000 R13: 00007fa59b616038 R14: 00007fa59b615fa0 R15: 00007ffc82218788 Fixes: 6a2968aaf50c ("NFC: basic NCI protocol implementation") Reported-by: syzbot+f9c5fd1a0874f9069dce@syzkaller.appspotmail.com Closes: https://lore.kernel.org/all/695e7f56.050a0220.1c677c.036c.GAE@google.com/ Signed-off-by: Kuniyuki Iwashima Reviewed-by: Simon Horman Link: https://patch.msgid.link/20260127040411.494931-1-kuniyu@google.com Signed-off-by: Jakub Kicinski Signed-off-by: Sasha Levin --- include/net/nfc/nfc.h | 2 ++ net/nfc/core.c | 27 ++++++++++++++++++++++++--- net/nfc/nci/core.c | 4 +++- 3 files changed, 29 insertions(+), 4 deletions(-) diff --git a/include/net/nfc/nfc.h b/include/net/nfc/nfc.h index 127e6c7d910dcb..c54df042db6be2 100644 --- a/include/net/nfc/nfc.h +++ b/include/net/nfc/nfc.h @@ -219,6 +219,8 @@ static inline void nfc_free_device(struct nfc_dev *dev) int nfc_register_device(struct nfc_dev *dev); +void nfc_unregister_rfkill(struct nfc_dev *dev); +void nfc_remove_device(struct nfc_dev *dev); void nfc_unregister_device(struct nfc_dev *dev); /** diff --git a/net/nfc/core.c b/net/nfc/core.c index 82f023f377541b..f50e5bab35d8ec 100644 --- a/net/nfc/core.c +++ b/net/nfc/core.c @@ -1147,14 +1147,14 @@ int nfc_register_device(struct nfc_dev *dev) EXPORT_SYMBOL(nfc_register_device); /** - * nfc_unregister_device - unregister a nfc device in the nfc subsystem + * nfc_unregister_rfkill - unregister a nfc device in the rfkill subsystem * * @dev: The nfc device to unregister */ -void nfc_unregister_device(struct nfc_dev *dev) +void nfc_unregister_rfkill(struct nfc_dev *dev) { - int rc; struct rfkill *rfk = NULL; + int rc; pr_debug("dev_name=%s\n", dev_name(&dev->dev)); @@ -1175,7 +1175,16 @@ void nfc_unregister_device(struct nfc_dev *dev) rfkill_unregister(rfk); rfkill_destroy(rfk); } +} +EXPORT_SYMBOL(nfc_unregister_rfkill); +/** + * nfc_remove_device - remove a nfc device in the nfc subsystem + * + * @dev: The nfc device to remove + */ +void nfc_remove_device(struct nfc_dev *dev) +{ if (dev->ops->check_presence) { timer_delete_sync(&dev->check_pres_timer); cancel_work_sync(&dev->check_pres_work); @@ -1188,6 +1197,18 @@ void nfc_unregister_device(struct nfc_dev *dev) device_del(&dev->dev); mutex_unlock(&nfc_devlist_mutex); } +EXPORT_SYMBOL(nfc_remove_device); + +/** + * nfc_unregister_device - unregister a nfc device in the nfc subsystem + * + * @dev: The nfc device to unregister + */ +void nfc_unregister_device(struct nfc_dev *dev) +{ + nfc_unregister_rfkill(dev); + nfc_remove_device(dev); +} EXPORT_SYMBOL(nfc_unregister_device); static int __init nfc_init(void) diff --git a/net/nfc/nci/core.c b/net/nfc/nci/core.c index fc921cd2cdffa5..e419e020a70a33 100644 --- a/net/nfc/nci/core.c +++ b/net/nfc/nci/core.c @@ -1303,6 +1303,8 @@ void nci_unregister_device(struct nci_dev *ndev) { struct nci_conn_info *conn_info, *n; + nfc_unregister_rfkill(ndev->nfc_dev); + /* This set_bit is not protected with specialized barrier, * However, it is fine because the mutex_lock(&ndev->req_lock); * in nci_close_device() will help to emit one. @@ -1320,7 +1322,7 @@ void nci_unregister_device(struct nci_dev *ndev) /* conn_info is allocated with devm_kzalloc */ } - nfc_unregister_device(ndev->nfc_dev); + nfc_remove_device(ndev->nfc_dev); } EXPORT_SYMBOL(nci_unregister_device); From f4bb58e14f042c046e68c69f0dbdc25036460fa9 Mon Sep 17 00:00:00 2001 From: Martin Kaiser Date: Tue, 27 Jan 2026 11:19:23 +0100 Subject: [PATCH 2881/3902] net: bridge: fix static key check [ Upstream commit cc0cf10fdaeadf5542d64a55b5b4120d3df90b7d ] Fix the check if netfilter's static keys are available. netfilter defines and exports static keys if CONFIG_JUMP_LABEL is enabled. (HAVE_JUMP_LABEL is never defined.) Fixes: 971502d77faa ("bridge: netfilter: unroll NF_HOOK helper in bridge input path") Signed-off-by: Martin Kaiser Reviewed-by: Florian Westphal Reviewed-by: Nikolay Aleksandrov Link: https://patch.msgid.link/20260127101925.1754425-1-martin@kaiser.cx Signed-off-by: Jakub Kicinski Signed-off-by: Sasha Levin --- net/bridge/br_input.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/net/bridge/br_input.c b/net/bridge/br_input.c index e355a15bf5ab13..1405f1061a5493 100644 --- a/net/bridge/br_input.c +++ b/net/bridge/br_input.c @@ -274,7 +274,7 @@ static int nf_hook_bridge_pre(struct sk_buff *skb, struct sk_buff **pskb) int ret; net = dev_net(skb->dev); -#ifdef HAVE_JUMP_LABEL +#ifdef CONFIG_JUMP_LABEL if (!static_key_false(&nf_hooks_needed[NFPROTO_BRIDGE][NF_BR_PRE_ROUTING])) goto frame_finish; #endif From 92db64d3546f95326a00a4103e087e1751843e1a Mon Sep 17 00:00:00 2001 From: Daniel Zahka Date: Mon, 26 Jan 2026 11:38:17 -0800 Subject: [PATCH 2882/3902] net/mlx5e: don't assume psp tx skbs are ipv6 csum handling [ Upstream commit a62f7d62d2b115e67c7224e36ace4ef12a9650b4 ] mlx5e_psp_handle_tx_skb() assumes skbs are ipv6 when doing a partial TCP checksum with tso. Make correctly mlx5e_psp_handle_tx_skb() handle ipv4 packets. Fixes: e5a1861a298e ("net/mlx5e: Implement PSP Tx data path") Signed-off-by: Daniel Zahka Reviewed-by: Eric Dumazet Reviewed-by: Cosmin Ratiu Link: https://patch.msgid.link/20260126-dzahka-fix-tx-csum-partial-v2-1-0a905590ea5f@gmail.com Signed-off-by: Jakub Kicinski Signed-off-by: Sasha Levin --- .../mellanox/mlx5/core/en_accel/psp_rxtx.c | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/drivers/net/ethernet/mellanox/mlx5/core/en_accel/psp_rxtx.c b/drivers/net/ethernet/mellanox/mlx5/core/en_accel/psp_rxtx.c index 828bff1137aff9..fa98d0074531b7 100644 --- a/drivers/net/ethernet/mellanox/mlx5/core/en_accel/psp_rxtx.c +++ b/drivers/net/ethernet/mellanox/mlx5/core/en_accel/psp_rxtx.c @@ -177,8 +177,6 @@ bool mlx5e_psp_handle_tx_skb(struct net_device *netdev, { struct mlx5e_priv *priv = netdev_priv(netdev); struct net *net = sock_net(skb->sk); - const struct ipv6hdr *ip6; - struct tcphdr *th; if (!mlx5e_psp_set_state(priv, skb, psp_st)) return true; @@ -189,11 +187,18 @@ bool mlx5e_psp_handle_tx_skb(struct net_device *netdev, return false; } if (skb_is_gso(skb)) { - ip6 = ipv6_hdr(skb); - th = inner_tcp_hdr(skb); + int len = skb_shinfo(skb)->gso_size + inner_tcp_hdrlen(skb); + struct tcphdr *th = inner_tcp_hdr(skb); - th->check = ~tcp_v6_check(skb_shinfo(skb)->gso_size + inner_tcp_hdrlen(skb), &ip6->saddr, - &ip6->daddr, 0); + if (skb->protocol == htons(ETH_P_IP)) { + const struct iphdr *ip = ip_hdr(skb); + + th->check = ~tcp_v4_check(len, ip->saddr, ip->daddr, 0); + } else { + const struct ipv6hdr *ip6 = ipv6_hdr(skb); + + th->check = ~tcp_v6_check(len, &ip6->saddr, &ip6->daddr, 0); + } } return true; From 335031cacd7e8d2221607a8c0aff69a15faf3a08 Mon Sep 17 00:00:00 2001 From: Wei Fang Date: Mon, 26 Jan 2026 16:15:44 +0800 Subject: [PATCH 2883/3902] net: phy: micrel: fix clk warning when removing the driver [ Upstream commit 2aa1545ba8d4801fba5be83a404e28014b80196a ] Since the commit 25c6a5ab151f ("net: phy: micrel: Dynamically control external clock of KSZ PHY"), the clock of Micrel PHY has been enabled by phy_driver::resume() and disabled by phy_driver::suspend(). However, devm_clk_get_optional_enabled() is used in kszphy_probe(), so the clock will automatically be disabled when the device is unbound from the bus. Therefore, this could cause the clock to be disabled twice, resulting in clk driver warnings. For example, this issue can be reproduced on i.MX6ULL platform, and we can see the following logs when removing the FEC MAC drivers. $ echo 2188000.ethernet > /sys/bus/platform/drivers/fec/unbind $ echo 20b4000.ethernet > /sys/bus/platform/drivers/fec/unbind [ 109.758207] ------------[ cut here ]------------ [ 109.758240] WARNING: drivers/clk/clk.c:1188 at clk_core_disable+0xb4/0xd0, CPU#0: sh/639 [ 109.771011] enet2_ref already disabled [ 109.793359] Call trace: [ 109.822006] clk_core_disable from clk_disable+0x28/0x34 [ 109.827340] clk_disable from clk_disable_unprepare+0xc/0x18 [ 109.833029] clk_disable_unprepare from devm_clk_release+0x1c/0x28 [ 109.839241] devm_clk_release from devres_release_all+0x98/0x100 [ 109.845278] devres_release_all from device_unbind_cleanup+0xc/0x70 [ 109.851571] device_unbind_cleanup from device_release_driver_internal+0x1a4/0x1f4 [ 109.859170] device_release_driver_internal from bus_remove_device+0xbc/0xe4 [ 109.866243] bus_remove_device from device_del+0x140/0x458 [ 109.871757] device_del from phy_mdio_device_remove+0xc/0x24 [ 109.877452] phy_mdio_device_remove from mdiobus_unregister+0x40/0xac [ 109.883918] mdiobus_unregister from fec_enet_mii_remove+0x40/0x78 [ 109.890125] fec_enet_mii_remove from fec_drv_remove+0x4c/0x158 [ 109.896076] fec_drv_remove from device_release_driver_internal+0x17c/0x1f4 [ 109.962748] WARNING: drivers/clk/clk.c:1047 at clk_core_unprepare+0xfc/0x13c, CPU#0: sh/639 [ 109.975805] enet2_ref already unprepared [ 110.002866] Call trace: [ 110.031758] clk_core_unprepare from clk_unprepare+0x24/0x2c [ 110.037440] clk_unprepare from devm_clk_release+0x1c/0x28 [ 110.042957] devm_clk_release from devres_release_all+0x98/0x100 [ 110.048989] devres_release_all from device_unbind_cleanup+0xc/0x70 [ 110.055280] device_unbind_cleanup from device_release_driver_internal+0x1a4/0x1f4 [ 110.062877] device_release_driver_internal from bus_remove_device+0xbc/0xe4 [ 110.069950] bus_remove_device from device_del+0x140/0x458 [ 110.075469] device_del from phy_mdio_device_remove+0xc/0x24 [ 110.081165] phy_mdio_device_remove from mdiobus_unregister+0x40/0xac [ 110.087632] mdiobus_unregister from fec_enet_mii_remove+0x40/0x78 [ 110.093836] fec_enet_mii_remove from fec_drv_remove+0x4c/0x158 [ 110.099782] fec_drv_remove from device_release_driver_internal+0x17c/0x1f4 After analyzing the process of removing the FEC driver, as shown below, it can be seen that the clock was disabled twice by the PHY driver. fec_drv_remove() --> fec_enet_close() --> phy_stop() --> phy_suspend() --> kszphy_suspend() #1 The clock is disabled --> fec_enet_mii_remove() --> mdiobus_unregister() --> phy_mdio_device_remove() --> device_del() --> devm_clk_release() #2 The clock is disabled again Therefore, devm_clk_get_optional() is used to fix the above issue. And to avoid the issue mentioned by the commit 985329462723 ("net: phy: micrel: use devm_clk_get_optional_enabled for the rmii-ref clock"), the clock is enabled by clk_prepare_enable() to get the correct clock rate. Fixes: 25c6a5ab151f ("net: phy: micrel: Dynamically control external clock of KSZ PHY") Signed-off-by: Wei Fang Reviewed-by: Maxime Chevallier Link: https://patch.msgid.link/20260126081544.983517-1-wei.fang@nxp.com Signed-off-by: Jakub Kicinski Signed-off-by: Sasha Levin --- drivers/net/phy/micrel.c | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/drivers/net/phy/micrel.c b/drivers/net/phy/micrel.c index 01c87c9b77020e..bc19880107ae42 100644 --- a/drivers/net/phy/micrel.c +++ b/drivers/net/phy/micrel.c @@ -2541,11 +2541,21 @@ static int kszphy_probe(struct phy_device *phydev) kszphy_parse_led_mode(phydev); - clk = devm_clk_get_optional_enabled(&phydev->mdio.dev, "rmii-ref"); + clk = devm_clk_get_optional(&phydev->mdio.dev, "rmii-ref"); /* NOTE: clk may be NULL if building without CONFIG_HAVE_CLK */ if (!IS_ERR_OR_NULL(clk)) { - unsigned long rate = clk_get_rate(clk); bool rmii_ref_clk_sel_25_mhz; + unsigned long rate; + int err; + + err = clk_prepare_enable(clk); + if (err) { + phydev_err(phydev, "Failed to enable rmii-ref clock\n"); + return err; + } + + rate = clk_get_rate(clk); + clk_disable_unprepare(clk); if (type) priv->rmii_ref_clk_sel = type->has_rmii_ref_clk_sel; @@ -2563,13 +2573,12 @@ static int kszphy_probe(struct phy_device *phydev) } } else if (!clk) { /* unnamed clock from the generic ethernet-phy binding */ - clk = devm_clk_get_optional_enabled(&phydev->mdio.dev, NULL); + clk = devm_clk_get_optional(&phydev->mdio.dev, NULL); } if (IS_ERR(clk)) return PTR_ERR(clk); - clk_disable_unprepare(clk); priv->clk = clk; if (ksz8041_fiber_mode(phydev)) From 2614734c4bc67f551ee2a0191c3af3178120459e Mon Sep 17 00:00:00 2001 From: Shay Drory Date: Tue, 27 Jan 2026 10:52:38 +0200 Subject: [PATCH 2884/3902] net/mlx5: fs, Fix inverted cap check in tx flow table root disconnect [ Upstream commit 2610a3d65691a1301ab10c92ff6ebab0bedf9199 ] The capability check for reset_root_to_default was inverted, causing the function to return -EOPNOTSUPP when the capability IS supported, rather than when it is NOT supported. Fix the capability check condition. Fixes: 3c9c34c32bc6 ("net/mlx5: fs, Command to control TX flow table root") Signed-off-by: Shay Drory Reviewed-by: Mark Bloch Reviewed-by: Simon Horman Signed-off-by: Tariq Toukan Link: https://patch.msgid.link/1769503961-124173-2-git-send-email-tariqt@nvidia.com Signed-off-by: Jakub Kicinski Signed-off-by: Sasha Levin --- drivers/net/ethernet/mellanox/mlx5/core/fs_cmd.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/drivers/net/ethernet/mellanox/mlx5/core/fs_cmd.c b/drivers/net/ethernet/mellanox/mlx5/core/fs_cmd.c index 1af76da8b1320e..b79544134e2a25 100644 --- a/drivers/net/ethernet/mellanox/mlx5/core/fs_cmd.c +++ b/drivers/net/ethernet/mellanox/mlx5/core/fs_cmd.c @@ -1167,7 +1167,8 @@ int mlx5_fs_cmd_set_tx_flow_table_root(struct mlx5_core_dev *dev, u32 ft_id, boo u32 out[MLX5_ST_SZ_DW(set_flow_table_root_out)] = {}; u32 in[MLX5_ST_SZ_DW(set_flow_table_root_in)] = {}; - if (disconnect && MLX5_CAP_FLOWTABLE_NIC_TX(dev, reset_root_to_default)) + if (disconnect && + !MLX5_CAP_FLOWTABLE_NIC_TX(dev, reset_root_to_default)) return -EOPNOTSUPP; MLX5_SET(set_flow_table_root_in, in, opcode, From fc3da1466af5f651c3eb06d5929e84b50e313ebb Mon Sep 17 00:00:00 2001 From: Cosmin Ratiu Date: Sun, 16 Nov 2025 22:45:35 +0200 Subject: [PATCH 2885/3902] net/mlx5: Initialize events outside devlink lock [ Upstream commit b6b03097f9826db72aeb3f751774c5e9edd9a5b3 ] Move event init/cleanup outside of mlx5_init_one() / mlx5_uninit_one() and into the mlx5_mdev_init() / mlx5_mdev_uninit() functions. By doing this, we avoid the events being reinitialized on devlink reload and, more importantly, the events->sw_nh notifier chain becomes available earlier in the init procedure, which will be used in subsequent patches. This makes sense because the events struct is pure software, independent of any HW details. Signed-off-by: Cosmin Ratiu Reviewed-by: Carolina Jubran Signed-off-by: Tariq Toukan Link: https://patch.msgid.link/1763325940-1231508-2-git-send-email-tariqt@nvidia.com Signed-off-by: Jakub Kicinski Stable-dep-of: a8f930b7be7b ("net/mlx5: Fix vhca_id access call trace use before alloc") Signed-off-by: Sasha Levin --- .../net/ethernet/mellanox/mlx5/core/main.c | 34 +++++++++++++------ 1 file changed, 24 insertions(+), 10 deletions(-) diff --git a/drivers/net/ethernet/mellanox/mlx5/core/main.c b/drivers/net/ethernet/mellanox/mlx5/core/main.c index 9e0c9e6266a47d..236cb1eb98c827 100644 --- a/drivers/net/ethernet/mellanox/mlx5/core/main.c +++ b/drivers/net/ethernet/mellanox/mlx5/core/main.c @@ -999,16 +999,10 @@ static int mlx5_init_once(struct mlx5_core_dev *dev) goto err_irq_cleanup; } - err = mlx5_events_init(dev); - if (err) { - mlx5_core_err(dev, "failed to initialize events\n"); - goto err_eq_cleanup; - } - err = mlx5_fw_reset_init(dev); if (err) { mlx5_core_err(dev, "failed to initialize fw reset events\n"); - goto err_events_cleanup; + goto err_eq_cleanup; } mlx5_cq_debugfs_init(dev); @@ -1110,8 +1104,6 @@ static int mlx5_init_once(struct mlx5_core_dev *dev) mlx5_cleanup_reserved_gids(dev); mlx5_cq_debugfs_cleanup(dev); mlx5_fw_reset_cleanup(dev); -err_events_cleanup: - mlx5_events_cleanup(dev); err_eq_cleanup: mlx5_eq_table_cleanup(dev); err_irq_cleanup: @@ -1144,7 +1136,6 @@ static void mlx5_cleanup_once(struct mlx5_core_dev *dev) mlx5_cleanup_reserved_gids(dev); mlx5_cq_debugfs_cleanup(dev); mlx5_fw_reset_cleanup(dev); - mlx5_events_cleanup(dev); mlx5_eq_table_cleanup(dev); mlx5_irq_table_cleanup(dev); mlx5_devcom_unregister_device(dev->priv.devc); @@ -1822,6 +1813,24 @@ static int vhca_id_show(struct seq_file *file, void *priv) DEFINE_SHOW_ATTRIBUTE(vhca_id); +static int mlx5_notifiers_init(struct mlx5_core_dev *dev) +{ + int err; + + err = mlx5_events_init(dev); + if (err) { + mlx5_core_err(dev, "failed to initialize events\n"); + return err; + } + + return 0; +} + +static void mlx5_notifiers_cleanup(struct mlx5_core_dev *dev) +{ + mlx5_events_cleanup(dev); +} + int mlx5_mdev_init(struct mlx5_core_dev *dev, int profile_idx) { struct mlx5_priv *priv = &dev->priv; @@ -1877,6 +1886,10 @@ int mlx5_mdev_init(struct mlx5_core_dev *dev, int profile_idx) if (err) goto err_hca_caps; + err = mlx5_notifiers_init(dev); + if (err) + goto err_hca_caps; + /* The conjunction of sw_vhca_id with sw_owner_id will be a global * unique id per function which uses mlx5_core. * Those values are supplied to FW as part of the init HCA command to @@ -1919,6 +1932,7 @@ void mlx5_mdev_uninit(struct mlx5_core_dev *dev) if (priv->sw_vhca_id > 0) ida_free(&sw_vhca_ida, dev->priv.sw_vhca_id); + mlx5_notifiers_cleanup(dev); mlx5_hca_caps_free(dev); mlx5_adev_cleanup(dev); mlx5_pagealloc_cleanup(dev); From 19bb3c68e18cf549ceafcdf0ade6721de1441ae8 Mon Sep 17 00:00:00 2001 From: Parav Pandit Date: Tue, 27 Jan 2026 10:52:40 +0200 Subject: [PATCH 2886/3902] net/mlx5: Fix vhca_id access call trace use before alloc [ Upstream commit a8f930b7be7be3f18f14446df461e17137400407 ] HCA CAP structure is allocated in mlx5_hca_caps_alloc(). mlx5_mdev_init() mlx5_hca_caps_alloc() And HCA CAP is read from the device in mlx5_init_one(). The vhca_id's debugfs file is published even before above two operations are done. Due to this when user reads the vhca id before the initialization, following call trace is observed. Fix this by deferring debugfs publication until the HCA CAP is allocated and read from the device. BUG: kernel NULL pointer dereference, address: 0000000000000004 PGD 0 P4D 0 Oops: Oops: 0000 [#1] SMP PTI CPU: 23 UID: 0 PID: 6605 Comm: cat Kdump: loaded Not tainted 6.18.0-rc7-sf+ #110 PREEMPT(full) Hardware name: Supermicro SYS-6028U-TR4+/X10DRU-i+, BIOS 2.0b 08/09/2016 RIP: 0010:vhca_id_show+0x17/0x30 [mlx5_core] Code: cb 66 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 0f 1f 44 00 00 48 8b 47 70 48 c7 c6 45 f0 12 c1 48 8b 80 70 03 00 00 <8b> 50 04 0f ca 0f b7 d2 e8 8c 82 47 cb 31 c0 c3 cc cc cc cc 0f 1f RSP: 0018:ffffd37f4f337d40 EFLAGS: 00010203 RAX: 0000000000000000 RBX: ffff8f18445c9b40 RCX: 0000000000000001 RDX: ffff8f1109825180 RSI: ffffffffc112f045 RDI: ffff8f18445c9b40 RBP: 0000000000000000 R08: 0000645eac0d2928 R09: 0000000000000006 R10: ffffd37f4f337d48 R11: 0000000000000000 R12: ffffd37f4f337dd8 R13: ffffd37f4f337db0 R14: ffff8f18445c9b68 R15: 0000000000000001 FS: 00007f3eea099580(0000) GS:ffff8f2090f1f000(0000) knlGS:0000000000000000 CS: 0010 DS: 0000 ES: 0000 CR0: 0000000080050033 CR2: 0000000000000004 CR3: 00000008b64e4006 CR4: 00000000003726f0 Call Trace: seq_read_iter+0x11f/0x4f0 ? _raw_spin_unlock+0x15/0x30 ? do_anonymous_page+0x104/0x810 seq_read+0xf6/0x120 ? srso_alias_untrain_ret+0x1/0x10 full_proxy_read+0x5c/0x90 vfs_read+0xad/0x320 ? handle_mm_fault+0x1ab/0x290 ksys_read+0x52/0xd0 do_syscall_64+0x61/0x11e0 entry_SYSCALL_64_after_hwframe+0x76/0x7e Fixes: dd3dd7263cde ("net/mlx5: Expose vhca_id to debugfs") Signed-off-by: Parav Pandit Reviewed-by: Shay Drori Reviewed-by: Simon Horman Signed-off-by: Tariq Toukan Link: https://patch.msgid.link/1769503961-124173-4-git-send-email-tariqt@nvidia.com Signed-off-by: Jakub Kicinski Signed-off-by: Sasha Levin --- .../net/ethernet/mellanox/mlx5/core/debugfs.c | 16 ++++++++++++++++ drivers/net/ethernet/mellanox/mlx5/core/main.c | 14 +++----------- .../net/ethernet/mellanox/mlx5/core/mlx5_core.h | 1 + .../ethernet/mellanox/mlx5/core/sf/dev/driver.c | 1 + 4 files changed, 21 insertions(+), 11 deletions(-) diff --git a/drivers/net/ethernet/mellanox/mlx5/core/debugfs.c b/drivers/net/ethernet/mellanox/mlx5/core/debugfs.c index 36806e813c33cc..1301c56e20d653 100644 --- a/drivers/net/ethernet/mellanox/mlx5/core/debugfs.c +++ b/drivers/net/ethernet/mellanox/mlx5/core/debugfs.c @@ -613,3 +613,19 @@ void mlx5_debug_cq_remove(struct mlx5_core_dev *dev, struct mlx5_core_cq *cq) cq->dbg = NULL; } } + +static int vhca_id_show(struct seq_file *file, void *priv) +{ + struct mlx5_core_dev *dev = file->private; + + seq_printf(file, "0x%x\n", MLX5_CAP_GEN(dev, vhca_id)); + return 0; +} + +DEFINE_SHOW_ATTRIBUTE(vhca_id); + +void mlx5_vhca_debugfs_init(struct mlx5_core_dev *dev) +{ + debugfs_create_file("vhca_id", 0400, dev->priv.dbg.dbg_root, dev, + &vhca_id_fops); +} diff --git a/drivers/net/ethernet/mellanox/mlx5/core/main.c b/drivers/net/ethernet/mellanox/mlx5/core/main.c index 236cb1eb98c827..14c57d43728028 100644 --- a/drivers/net/ethernet/mellanox/mlx5/core/main.c +++ b/drivers/net/ethernet/mellanox/mlx5/core/main.c @@ -1803,16 +1803,6 @@ static int mlx5_hca_caps_alloc(struct mlx5_core_dev *dev) return -ENOMEM; } -static int vhca_id_show(struct seq_file *file, void *priv) -{ - struct mlx5_core_dev *dev = file->private; - - seq_printf(file, "0x%x\n", MLX5_CAP_GEN(dev, vhca_id)); - return 0; -} - -DEFINE_SHOW_ATTRIBUTE(vhca_id); - static int mlx5_notifiers_init(struct mlx5_core_dev *dev) { int err; @@ -1855,7 +1845,7 @@ int mlx5_mdev_init(struct mlx5_core_dev *dev, int profile_idx) priv->numa_node = dev_to_node(mlx5_core_dma_dev(dev)); priv->dbg.dbg_root = debugfs_create_dir(dev_name(dev->device), mlx5_debugfs_root); - debugfs_create_file("vhca_id", 0400, priv->dbg.dbg_root, dev, &vhca_id_fops); + INIT_LIST_HEAD(&priv->traps); err = mlx5_cmd_init(dev); @@ -1993,6 +1983,8 @@ static int probe_one(struct pci_dev *pdev, const struct pci_device_id *id) goto err_init_one; } + mlx5_vhca_debugfs_init(dev); + pci_save_state(pdev); return 0; diff --git a/drivers/net/ethernet/mellanox/mlx5/core/mlx5_core.h b/drivers/net/ethernet/mellanox/mlx5/core/mlx5_core.h index 082259b56816ce..da5345e19082d6 100644 --- a/drivers/net/ethernet/mellanox/mlx5/core/mlx5_core.h +++ b/drivers/net/ethernet/mellanox/mlx5/core/mlx5_core.h @@ -258,6 +258,7 @@ int mlx5_wait_for_pages(struct mlx5_core_dev *dev, int *pages); void mlx5_cmd_flush(struct mlx5_core_dev *dev); void mlx5_cq_debugfs_init(struct mlx5_core_dev *dev); void mlx5_cq_debugfs_cleanup(struct mlx5_core_dev *dev); +void mlx5_vhca_debugfs_init(struct mlx5_core_dev *dev); int mlx5_query_pcam_reg(struct mlx5_core_dev *dev, u32 *pcam, u8 feature_group, u8 access_reg_group); diff --git a/drivers/net/ethernet/mellanox/mlx5/core/sf/dev/driver.c b/drivers/net/ethernet/mellanox/mlx5/core/sf/dev/driver.c index b706f1486504a7..c45540fe7d9d95 100644 --- a/drivers/net/ethernet/mellanox/mlx5/core/sf/dev/driver.c +++ b/drivers/net/ethernet/mellanox/mlx5/core/sf/dev/driver.c @@ -76,6 +76,7 @@ static int mlx5_sf_dev_probe(struct auxiliary_device *adev, const struct auxilia goto init_one_err; } + mlx5_vhca_debugfs_init(mdev); return 0; init_one_err: From cc3f137d36fda328bdd7b1888b8616cfd8abc546 Mon Sep 17 00:00:00 2001 From: Jianbo Liu Date: Tue, 27 Jan 2026 10:52:41 +0200 Subject: [PATCH 2887/3902] net/mlx5e: Skip ESN replay window setup for IPsec crypto offload [ Upstream commit 011be342dd24b5168a5dcf408b14c3babe503341 ] Commit a5e400a985df ("net/mlx5e: Honor user choice of IPsec replay window size") introduced logic to setup the ESN replay window size. This logic is only valid for packet offload. However, the check to skip this block only covered outbound offloads. It was not skipped for crypto offload, causing it to fall through to the new switch statement and trigger its WARN_ON default case (for instance, if a window larger than 256 bits was configured). Fix this by amending the condition to also skip the replay window setup if the offload type is not XFRM_DEV_OFFLOAD_PACKET. Fixes: a5e400a985df ("net/mlx5e: Honor user choice of IPsec replay window size") Signed-off-by: Jianbo Liu Reviewed-by: Leon Romanovsky Reviewed-by: Simon Horman Signed-off-by: Tariq Toukan Link: https://patch.msgid.link/1769503961-124173-5-git-send-email-tariqt@nvidia.com Signed-off-by: Jakub Kicinski Signed-off-by: Sasha Levin --- drivers/net/ethernet/mellanox/mlx5/core/en_accel/ipsec.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/drivers/net/ethernet/mellanox/mlx5/core/en_accel/ipsec.c b/drivers/net/ethernet/mellanox/mlx5/core/en_accel/ipsec.c index a8fb4bec369cf4..9c7064187ed0ff 100644 --- a/drivers/net/ethernet/mellanox/mlx5/core/en_accel/ipsec.c +++ b/drivers/net/ethernet/mellanox/mlx5/core/en_accel/ipsec.c @@ -430,7 +430,8 @@ void mlx5e_ipsec_build_accel_xfrm_attrs(struct mlx5e_ipsec_sa_entry *sa_entry, attrs->replay_esn.esn = sa_entry->esn_state.esn; attrs->replay_esn.esn_msb = sa_entry->esn_state.esn_msb; attrs->replay_esn.overlap = sa_entry->esn_state.overlap; - if (attrs->dir == XFRM_DEV_OFFLOAD_OUT) + if (attrs->dir == XFRM_DEV_OFFLOAD_OUT || + x->xso.type != XFRM_DEV_OFFLOAD_PACKET) goto skip_replay_window; switch (x->replay_esn->replay_window) { From b8d890f8c3223c338485128e7a5949673e90a21e Mon Sep 17 00:00:00 2001 From: Benjamin Berg Date: Sun, 18 Jan 2026 09:51:13 +0200 Subject: [PATCH 2888/3902] wifi: mac80211: parse all TTLM entries [ Upstream commit 3fa2886d11d4545dc0dcfd0759ffbd03f88b5410 ] For the follow up patch, we need to properly parse TTLM entries that do not have a switch time. Change the logic so that ieee80211_parse_adv_t2l returns usable values in all non-error cases. Before the values filled in were technically incorrect but enough for ieee80211_process_adv_ttlm. Signed-off-by: Benjamin Berg Reviewed-by: Johannes Berg Reviewed-by: Ilan Peer Signed-off-by: Miri Korenblit Link: https://patch.msgid.link/20260118093904.ccd324e2dd59.I69f0bee0a22e9b11bb95beef313e305dab17c051@changeid Signed-off-by: Johannes Berg Stable-dep-of: 1eab33aa63c9 ("wifi: mac80211: correctly decode TTLM with default link map") Signed-off-by: Sasha Levin --- net/mac80211/mlme.c | 25 ++++++++++++++----------- 1 file changed, 14 insertions(+), 11 deletions(-) diff --git a/net/mac80211/mlme.c b/net/mac80211/mlme.c index f3138d15853533..d70163c0b9e32c 100644 --- a/net/mac80211/mlme.c +++ b/net/mac80211/mlme.c @@ -7014,10 +7014,6 @@ ieee80211_parse_adv_t2l(struct ieee80211_sub_if_data *sdata, pos = (void *)ttlm->optional; control = ttlm->control; - if ((control & IEEE80211_TTLM_CONTROL_DEF_LINK_MAP) || - !(control & IEEE80211_TTLM_CONTROL_SWITCH_TIME_PRESENT)) - return 0; - if ((control & IEEE80211_TTLM_CONTROL_DIRECTION) != IEEE80211_TTLM_DIRECTION_BOTH) { sdata_info(sdata, "Invalid advertised T2L map direction\n"); @@ -7027,21 +7023,28 @@ ieee80211_parse_adv_t2l(struct ieee80211_sub_if_data *sdata, link_map_presence = *pos; pos++; - ttlm_info->switch_time = get_unaligned_le16(pos); + if (control & IEEE80211_TTLM_CONTROL_SWITCH_TIME_PRESENT) { + ttlm_info->switch_time = get_unaligned_le16(pos); - /* Since ttlm_info->switch_time == 0 means no switch time, bump it - * by 1. - */ - if (!ttlm_info->switch_time) - ttlm_info->switch_time = 1; + /* Since ttlm_info->switch_time == 0 means no switch time, bump + * it by 1. + */ + if (!ttlm_info->switch_time) + ttlm_info->switch_time = 1; - pos += 2; + pos += 2; + } if (control & IEEE80211_TTLM_CONTROL_EXPECTED_DUR_PRESENT) { ttlm_info->duration = pos[0] | pos[1] << 8 | pos[2] << 16; pos += 3; } + if (control & IEEE80211_TTLM_CONTROL_DEF_LINK_MAP) { + ttlm_info->map = 0xffff; + return 0; + } + if (control & IEEE80211_TTLM_CONTROL_LINK_MAP_SIZE) map_size = 1; else From a4f9a19a266e62627c9803a9b973a9377ce9f580 Mon Sep 17 00:00:00 2001 From: Benjamin Berg Date: Sun, 18 Jan 2026 09:51:14 +0200 Subject: [PATCH 2889/3902] wifi: mac80211: apply advertised TTLM from association response [ Upstream commit aebc29dec67aa998a9ea6d34aacba7b5c6a74d33 ] When the AP has a disabled link that the station can include in the association, the fact that the link is dormant needs to be advertised in the TID to Link Mapping (TTLM). Section 35.3.7.2.3 ("Negotiation of TTLM") of Draft P802.11REVmf_D1.0 also states that the mapping needs to be included in the association response frame. As such, we can simply rely on the TTLM from the association response. Before this change mac80211 would not properly track that an advertised TTLM was effectively active, resulting in it not enabling the link once it became available again. For the link reconfiguration case, the data was not used at all. This behaviour is actually correct because Draft P802.11REVmf_D1.0 states in section 35.3.6.4 that we "shall operate with all the TIDs mapped to the newly added links ..." Fixes: 6d543b34dbcf ("wifi: mac80211: Support disabled links during association") Signed-off-by: Benjamin Berg Signed-off-by: Miri Korenblit Link: https://patch.msgid.link/20260118093904.43c861424543.I067f702ac46b84ac3f8b4ea16fb0db9cbbfae7e2@changeid Signed-off-by: Johannes Berg Stable-dep-of: 1eab33aa63c9 ("wifi: mac80211: correctly decode TTLM with default link map") Signed-off-by: Sasha Levin --- net/mac80211/ieee80211_i.h | 2 - net/mac80211/mlme.c | 216 ++++++++++++++++++++----------------- 2 files changed, 119 insertions(+), 99 deletions(-) diff --git a/net/mac80211/ieee80211_i.h b/net/mac80211/ieee80211_i.h index 878c3b14aeb806..5c0c833fcf7a93 100644 --- a/net/mac80211/ieee80211_i.h +++ b/net/mac80211/ieee80211_i.h @@ -451,8 +451,6 @@ struct ieee80211_mgd_assoc_data { struct ieee80211_conn_settings conn; u16 status; - - bool disabled; } link[IEEE80211_MLD_MAX_NUM_LINKS]; u8 ap_addr[ETH_ALEN] __aligned(2); diff --git a/net/mac80211/mlme.c b/net/mac80211/mlme.c index d70163c0b9e32c..21c73a65f73f94 100644 --- a/net/mac80211/mlme.c +++ b/net/mac80211/mlme.c @@ -6161,6 +6161,98 @@ static bool ieee80211_get_dtim(const struct cfg80211_bss_ies *ies, return true; } +static u16 ieee80211_get_ttlm(u8 bm_size, u8 *data) +{ + if (bm_size == 1) + return *data; + + return get_unaligned_le16(data); +} + +static int +ieee80211_parse_adv_t2l(struct ieee80211_sub_if_data *sdata, + const struct ieee80211_ttlm_elem *ttlm, + struct ieee80211_adv_ttlm_info *ttlm_info) +{ + /* The element size was already validated in + * ieee80211_tid_to_link_map_size_ok() + */ + u8 control, link_map_presence, map_size, tid; + u8 *pos; + + memset(ttlm_info, 0, sizeof(*ttlm_info)); + pos = (void *)ttlm->optional; + control = ttlm->control; + + if ((control & IEEE80211_TTLM_CONTROL_DIRECTION) != + IEEE80211_TTLM_DIRECTION_BOTH) { + sdata_info(sdata, "Invalid advertised T2L map direction\n"); + return -EINVAL; + } + + link_map_presence = *pos; + pos++; + + if (control & IEEE80211_TTLM_CONTROL_SWITCH_TIME_PRESENT) { + ttlm_info->switch_time = get_unaligned_le16(pos); + + /* Since ttlm_info->switch_time == 0 means no switch time, bump + * it by 1. + */ + if (!ttlm_info->switch_time) + ttlm_info->switch_time = 1; + + pos += 2; + } + + if (control & IEEE80211_TTLM_CONTROL_EXPECTED_DUR_PRESENT) { + ttlm_info->duration = pos[0] | pos[1] << 8 | pos[2] << 16; + pos += 3; + } + + if (control & IEEE80211_TTLM_CONTROL_DEF_LINK_MAP) { + ttlm_info->map = 0xffff; + return 0; + } + + if (control & IEEE80211_TTLM_CONTROL_LINK_MAP_SIZE) + map_size = 1; + else + map_size = 2; + + /* According to Draft P802.11be_D3.0 clause 35.3.7.1.7, an AP MLD shall + * not advertise a TID-to-link mapping that does not map all TIDs to the + * same link set, reject frame if not all links have mapping + */ + if (link_map_presence != 0xff) { + sdata_info(sdata, + "Invalid advertised T2L mapping presence indicator\n"); + return -EINVAL; + } + + ttlm_info->map = ieee80211_get_ttlm(map_size, pos); + if (!ttlm_info->map) { + sdata_info(sdata, + "Invalid advertised T2L map for TID 0\n"); + return -EINVAL; + } + + pos += map_size; + + for (tid = 1; tid < 8; tid++) { + u16 map = ieee80211_get_ttlm(map_size, pos); + + if (map != ttlm_info->map) { + sdata_info(sdata, "Invalid advertised T2L map for tid %d\n", + tid); + return -EINVAL; + } + + pos += map_size; + } + return 0; +} + static bool ieee80211_assoc_success(struct ieee80211_sub_if_data *sdata, struct ieee80211_mgmt *mgmt, struct ieee802_11_elems *elems, @@ -6192,8 +6284,6 @@ static bool ieee80211_assoc_success(struct ieee80211_sub_if_data *sdata, continue; valid_links |= BIT(link_id); - if (assoc_data->link[link_id].disabled) - dormant_links |= BIT(link_id); if (link_id != assoc_data->assoc_link_id) { err = ieee80211_sta_allocate_link(sta, link_id); @@ -6202,6 +6292,33 @@ static bool ieee80211_assoc_success(struct ieee80211_sub_if_data *sdata, } } + /* + * We do not support setting a negotiated TTLM during + * association. As such, we can assume that if there is a TTLM, + * then it is the currently active advertised TTLM. + * In that case, there must be exactly one TTLM that does not + * have a switch time set. This mapping should also leave us + * with at least one usable link. + */ + if (elems->ttlm_num > 1) { + sdata_info(sdata, + "More than one advertised TTLM in association response\n"); + goto out_err; + } else if (elems->ttlm_num == 1) { + if (ieee80211_parse_adv_t2l(sdata, elems->ttlm[0], + &sdata->u.mgd.ttlm_info) || + sdata->u.mgd.ttlm_info.switch_time != 0 || + !(valid_links & sdata->u.mgd.ttlm_info.map)) { + sdata_info(sdata, + "Invalid advertised TTLM in association response\n"); + goto out_err; + } + + sdata->u.mgd.ttlm_info.active = true; + dormant_links = + valid_links & ~sdata->u.mgd.ttlm_info.map; + } + ieee80211_vif_set_links(sdata, valid_links, dormant_links); } @@ -6991,98 +7108,6 @@ static void ieee80211_tid_to_link_map_work(struct wiphy *wiphy, sdata->u.mgd.ttlm_info.switch_time = 0; } -static u16 ieee80211_get_ttlm(u8 bm_size, u8 *data) -{ - if (bm_size == 1) - return *data; - else - return get_unaligned_le16(data); -} - -static int -ieee80211_parse_adv_t2l(struct ieee80211_sub_if_data *sdata, - const struct ieee80211_ttlm_elem *ttlm, - struct ieee80211_adv_ttlm_info *ttlm_info) -{ - /* The element size was already validated in - * ieee80211_tid_to_link_map_size_ok() - */ - u8 control, link_map_presence, map_size, tid; - u8 *pos; - - memset(ttlm_info, 0, sizeof(*ttlm_info)); - pos = (void *)ttlm->optional; - control = ttlm->control; - - if ((control & IEEE80211_TTLM_CONTROL_DIRECTION) != - IEEE80211_TTLM_DIRECTION_BOTH) { - sdata_info(sdata, "Invalid advertised T2L map direction\n"); - return -EINVAL; - } - - link_map_presence = *pos; - pos++; - - if (control & IEEE80211_TTLM_CONTROL_SWITCH_TIME_PRESENT) { - ttlm_info->switch_time = get_unaligned_le16(pos); - - /* Since ttlm_info->switch_time == 0 means no switch time, bump - * it by 1. - */ - if (!ttlm_info->switch_time) - ttlm_info->switch_time = 1; - - pos += 2; - } - - if (control & IEEE80211_TTLM_CONTROL_EXPECTED_DUR_PRESENT) { - ttlm_info->duration = pos[0] | pos[1] << 8 | pos[2] << 16; - pos += 3; - } - - if (control & IEEE80211_TTLM_CONTROL_DEF_LINK_MAP) { - ttlm_info->map = 0xffff; - return 0; - } - - if (control & IEEE80211_TTLM_CONTROL_LINK_MAP_SIZE) - map_size = 1; - else - map_size = 2; - - /* According to Draft P802.11be_D3.0 clause 35.3.7.1.7, an AP MLD shall - * not advertise a TID-to-link mapping that does not map all TIDs to the - * same link set, reject frame if not all links have mapping - */ - if (link_map_presence != 0xff) { - sdata_info(sdata, - "Invalid advertised T2L mapping presence indicator\n"); - return -EINVAL; - } - - ttlm_info->map = ieee80211_get_ttlm(map_size, pos); - if (!ttlm_info->map) { - sdata_info(sdata, - "Invalid advertised T2L map for TID 0\n"); - return -EINVAL; - } - - pos += map_size; - - for (tid = 1; tid < 8; tid++) { - u16 map = ieee80211_get_ttlm(map_size, pos); - - if (map != ttlm_info->map) { - sdata_info(sdata, "Invalid advertised T2L map for tid %d\n", - tid); - return -EINVAL; - } - - pos += map_size; - } - return 0; -} - static void ieee80211_process_adv_ttlm(struct ieee80211_sub_if_data *sdata, struct ieee802_11_elems *elems, u64 beacon_ts) @@ -9729,7 +9754,6 @@ int ieee80211_mgd_assoc(struct ieee80211_sub_if_data *sdata, req, true, i, &assoc_data->link[i].conn); assoc_data->link[i].bss = link_cbss; - assoc_data->link[i].disabled = req->links[i].disabled; if (!bss->uapsd_supported) uapsd_supported = false; @@ -10711,8 +10735,6 @@ int ieee80211_mgd_assoc_ml_reconf(struct ieee80211_sub_if_data *sdata, &data->link[link_id].conn); data->link[link_id].bss = link_cbss; - data->link[link_id].disabled = - req->add_links[link_id].disabled; data->link[link_id].elems = (u8 *)req->add_links[link_id].elems; data->link[link_id].elems_len = From aabc36857bd39da65fe2d047bfaf63a0a09917d4 Mon Sep 17 00:00:00 2001 From: Benjamin Berg Date: Thu, 29 Jan 2026 11:33:50 +0100 Subject: [PATCH 2890/3902] wifi: mac80211: correctly decode TTLM with default link map [ Upstream commit 1eab33aa63c993685dd341e03bd5b267dd7403fa ] TID-To-Link Mapping (TTLM) elements do not contain any link mapping presence indicator if a default mapping is used and parsing needs to be skipped. Note that access points should not explicitly report an advertised TTLM with a default mapping as that is the implied mapping if the element is not included, this is even the case when switching back to the default mapping. However, mac80211 would incorrectly parse the frame and would also read one byte beyond the end of the element. Reported-by: Ruikai Peng Closes: https://lore.kernel.org/linux-wireless/CAFD3drMqc9YWvTCSHLyP89AOpBZsHdZ+pak6zVftYoZcUyF7gw@mail.gmail.com Fixes: 702e80470a33 ("wifi: mac80211: support handling of advertised TID-to-link mapping") Signed-off-by: Benjamin Berg Link: https://patch.msgid.link/20260129113349.d6b96f12c732.I69212a50f0f70db185edd3abefb6f04d3cb3e5ff@changeid Signed-off-by: Johannes Berg Signed-off-by: Sasha Levin --- net/mac80211/mlme.c | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/net/mac80211/mlme.c b/net/mac80211/mlme.c index 21c73a65f73f94..dca47a533392ac 100644 --- a/net/mac80211/mlme.c +++ b/net/mac80211/mlme.c @@ -8,7 +8,7 @@ * Copyright 2007, Michael Wu * Copyright 2013-2014 Intel Mobile Communications GmbH * Copyright (C) 2015 - 2017 Intel Deutschland GmbH - * Copyright (C) 2018 - 2025 Intel Corporation + * Copyright (C) 2018 - 2026 Intel Corporation */ #include @@ -6190,8 +6190,10 @@ ieee80211_parse_adv_t2l(struct ieee80211_sub_if_data *sdata, return -EINVAL; } - link_map_presence = *pos; - pos++; + if (!(control & IEEE80211_TTLM_CONTROL_DEF_LINK_MAP)) { + link_map_presence = *pos; + pos++; + } if (control & IEEE80211_TTLM_CONTROL_SWITCH_TIME_PRESENT) { ttlm_info->switch_time = get_unaligned_le16(pos); From a42bdbcada187edb3b75c8db99180972995856b6 Mon Sep 17 00:00:00 2001 From: Kery Qi Date: Wed, 21 Jan 2026 19:45:15 +0800 Subject: [PATCH 2891/3902] scsi: firewire: sbp-target: Fix overflow in sbp_make_tpg() [ Upstream commit b2d6b1d443009ed4da2d69f5423ab38e5780505a ] The code in sbp_make_tpg() limits "tpgt" to UINT_MAX but the data type of "tpg->tport_tpgt" is u16. This causes a type truncation issue. When a user creates a TPG via configfs mkdir, for example: mkdir /sys/kernel/config/target/sbp//tpgt_70000 The value 70000 passes the "tpgt > UINT_MAX" check since 70000 is far less than 4294967295. However, when assigned to the u16 field tpg->tport_tpgt, the value is silently truncated to 4464 (70000 & 0xFFFF). This causes the value the user specified to differ from what is actually stored, leading to confusion and potential unexpected behavior. Fix this by changing the type of "tpgt" to u16 and using kstrtou16() which will properly reject values outside the u16 range. Fixes: a511ce339780 ("sbp-target: Initial merge of firewire/ieee-1394 target mode support") Signed-off-by: Kery Qi Link: https://patch.msgid.link/20260121114515.1829-2-qikeyu2017@gmail.com Signed-off-by: Martin K. Petersen Signed-off-by: Sasha Levin --- drivers/target/sbp/sbp_target.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/target/sbp/sbp_target.c b/drivers/target/sbp/sbp_target.c index 3b89b5a70331f6..ad03bf7929f8bb 100644 --- a/drivers/target/sbp/sbp_target.c +++ b/drivers/target/sbp/sbp_target.c @@ -1961,12 +1961,12 @@ static struct se_portal_group *sbp_make_tpg(struct se_wwn *wwn, container_of(wwn, struct sbp_tport, tport_wwn); struct sbp_tpg *tpg; - unsigned long tpgt; + u16 tpgt; int ret; if (strstr(name, "tpgt_") != name) return ERR_PTR(-EINVAL); - if (kstrtoul(name + 5, 10, &tpgt) || tpgt > UINT_MAX) + if (kstrtou16(name + 5, 10, &tpgt)) return ERR_PTR(-EINVAL); if (tport->tpg) { From 4d7b7abb525b443f737b9e43c3867264f9a2608d Mon Sep 17 00:00:00 2001 From: Bard Liao Date: Fri, 23 Jan 2026 19:38:09 +0800 Subject: [PATCH 2892/3902] ASoC: soc-acpi-intel-ptl-match: fix name_prefix of rt1320-2 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [ Upstream commit 403a0591be681eebc0c4825f8b42afe7fd13ee7f ] rt1320_2_group2_adr works with rt1320_1_group2_adr and the name_prefix should be rt1320-2. Fixes: ffe450cb6bce ("ASoC: Intel: soc-acpi-intel-ptl-match: add rt713_vb_l3_rt1320_l12 support") Signed-off-by: Bard Liao Reviewed-by: Péter Ujfalusi Link: https://patch.msgid.link/20260123113809.2238766-1-yung-chuan.liao@linux.intel.com Signed-off-by: Mark Brown Signed-off-by: Sasha Levin --- sound/soc/intel/common/soc-acpi-intel-ptl-match.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sound/soc/intel/common/soc-acpi-intel-ptl-match.c b/sound/soc/intel/common/soc-acpi-intel-ptl-match.c index 4853f4f31786f8..55505625b36095 100644 --- a/sound/soc/intel/common/soc-acpi-intel-ptl-match.c +++ b/sound/soc/intel/common/soc-acpi-intel-ptl-match.c @@ -418,7 +418,7 @@ static const struct snd_soc_acpi_adr_device rt1320_2_group2_adr[] = { .adr = 0x000230025D132001ull, .num_endpoints = 1, .endpoints = &spk_r_endpoint, - .name_prefix = "rt1320-1" + .name_prefix = "rt1320-2" } }; From e67828aeb58d930449a400de409f23efc24232a6 Mon Sep 17 00:00:00 2001 From: Shuicheng Lin Date: Thu, 22 Jan 2026 21:40:54 +0000 Subject: [PATCH 2893/3902] drm/xe: Skip address copy for sync-only execs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [ Upstream commit c73a8917b31e8ddbd53cc248e17410cec27f8f58 ] For parallel exec queues, xe_exec_ioctl() copied the batch buffer address array from userspace without checking num_batch_buffer. If user creates a sync-only exec that doesn't use the address field, the exec will fail with -EFAULT. Add num_batch_buffer check to skip the copy, and the exec could be executed successfully. Here is the sync-only exec: struct drm_xe_exec exec = { .extensions = 0, .exec_queue_id = qid, .num_syncs = 1, .syncs = (uintptr_t)&sync, .address = 0, /* ignored for sync-only */ .num_batch_buffer = 0, /* sync-only */ }; Fixes: dd08ebf6c352 ("drm/xe: Introduce a new DRM driver for Intel GPUs") Cc: Matthew Brost Signed-off-by: Shuicheng Lin Reviewed-by: Matthew Brost Signed-off-by: Matthew Brost Link: https://patch.msgid.link/20260122214053.3189366-2-shuicheng.lin@intel.com (cherry picked from commit 4761791c1e736273d612ff564f318bfbbb04fa4e) Signed-off-by: Thomas Hellström Signed-off-by: Sasha Levin --- drivers/gpu/drm/xe/xe_exec.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/drivers/gpu/drm/xe/xe_exec.c b/drivers/gpu/drm/xe/xe_exec.c index ca85f7c15fabe8..87a32b61bece66 100644 --- a/drivers/gpu/drm/xe/xe_exec.c +++ b/drivers/gpu/drm/xe/xe_exec.c @@ -182,9 +182,9 @@ int xe_exec_ioctl(struct drm_device *dev, void *data, struct drm_file *file) goto err_syncs; } - if (xe_exec_queue_is_parallel(q)) { - err = copy_from_user(addresses, addresses_user, sizeof(u64) * - q->width); + if (args->num_batch_buffer && xe_exec_queue_is_parallel(q)) { + err = copy_from_user(addresses, addresses_user, + sizeof(u64) * q->width); if (err) { err = -EFAULT; goto err_syncs; From ecb638fb9a5cc68b43e237c1b76c89d139f10bcf Mon Sep 17 00:00:00 2001 From: Tagir Garaev Date: Wed, 21 Jan 2026 18:24:35 +0300 Subject: [PATCH 2894/3902] ASoC: Intel: sof_es8336: fix headphone GPIO logic inversion [ Upstream commit 213c4e51267fd825cd21a08a055450cac7e0b7fb ] The headphone GPIO should be set to the inverse of speaker_en. When speakers are enabled, headphones should be disabled and vice versa. Currently both GPIOs are set to the same value (speaker_en), causing audio to play through both speakers and headphones simultaneously when headphones are plugged in. Tested on Huawei Matebook (BOD-WXX9) with ES8336 codec. Fixes: 6e1ff1459e00 ("ASoC: Intel: sof_es8336: support a separate gpio to control headphone") Signed-off-by: Tagir Garaev Link: https://patch.msgid.link/20260121152435.101698-1-tgaraev653@gmail.com Signed-off-by: Mark Brown Signed-off-by: Sasha Levin --- sound/soc/intel/boards/sof_es8336.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sound/soc/intel/boards/sof_es8336.c b/sound/soc/intel/boards/sof_es8336.c index 10b189ea88dba8..09acd80d23e0fc 100644 --- a/sound/soc/intel/boards/sof_es8336.c +++ b/sound/soc/intel/boards/sof_es8336.c @@ -120,7 +120,7 @@ static void pcm_pop_work_events(struct work_struct *work) gpiod_set_value_cansleep(priv->gpio_speakers, priv->speaker_en); if (quirk & SOF_ES8336_HEADPHONE_GPIO) - gpiod_set_value_cansleep(priv->gpio_headphone, priv->speaker_en); + gpiod_set_value_cansleep(priv->gpio_headphone, !priv->speaker_en); } From c0ce86d92565bd0ae069b2ae601e83d2720852cf Mon Sep 17 00:00:00 2001 From: Denis Sergeev Date: Mon, 26 Jan 2026 06:59:14 +0300 Subject: [PATCH 2895/3902] gpiolib: acpi: use BIT_ULL() for u64 mask in address space handler [ Upstream commit c0ae43d303e45764918fa8c1dc13d6a5db59c479 ] The BIT() macro uses unsigned long, which is 32 bits on 32-bit architectures. When iterating over GPIO pins with index >= 32, the expression (*value & BIT(i)) causes undefined behavior due to shifting by a value >= type width. Since 'value' is a pointer to u64, use BIT_ULL() to ensure correct 64-bit mask on all architectures. Found by Linux Verification Center (linuxtesting.org) with Svace. Fixes: 2c4d00cb8fc5 ("gpiolib: acpi: Use BIT() macro to increase readability") Signed-off-by: Denis Sergeev Reviewed-by: Mika Westerberg Link: https://lore.kernel.org/r/20260126035914.16586-1-denserg.edu@gmail.com Signed-off-by: Bartosz Golaszewski Signed-off-by: Sasha Levin --- drivers/gpio/gpiolib-acpi-core.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/gpio/gpiolib-acpi-core.c b/drivers/gpio/gpiolib-acpi-core.c index d441c1236d8ca1..2ac6c708d927a5 100644 --- a/drivers/gpio/gpiolib-acpi-core.c +++ b/drivers/gpio/gpiolib-acpi-core.c @@ -1159,7 +1159,7 @@ acpi_gpio_adr_space_handler(u32 function, acpi_physical_address address, mutex_unlock(&achip->conn_lock); if (function == ACPI_WRITE) - gpiod_set_raw_value_cansleep(desc, !!(*value & BIT(i))); + gpiod_set_raw_value_cansleep(desc, !!(*value & BIT_ULL(i))); else *value |= (u64)gpiod_get_raw_value_cansleep(desc) << i; } From 7bec90f605cfb138006f5ba575f2310593347110 Mon Sep 17 00:00:00 2001 From: Yuhao Huang Date: Mon, 26 Jan 2026 12:03:48 +0800 Subject: [PATCH 2896/3902] gpio: virtuser: fix UAF in configfs release path [ Upstream commit 53ad4a948a4586359b841d607c08fb16c5503230 ] The gpio-virtuser configfs release path uses guard(mutex) to protect the device structure. However, the device is freed before the guard cleanup runs, causing mutex_unlock() to operate on freed memory. Specifically, gpio_virtuser_device_config_group_release() destroys the mutex and frees the device while still inside the guard(mutex) scope. When the function returns, the guard cleanup invokes mutex_unlock(&dev->lock), resulting in a slab use-after-free. Limit the mutex lifetime by using a scoped_guard() only around the activation check, so that the lock is released before mutex_destroy() and kfree() are called. Fixes: 91581c4b3f29 ("gpio: virtuser: new virtual testing driver for the GPIO API") Signed-off-by: Yuhao Huang Link: https://lore.kernel.org/r/20260126040348.11167-1-yuhaohuang@YuhaodeMacBook-Pro.local Signed-off-by: Bartosz Golaszewski Signed-off-by: Sasha Levin --- drivers/gpio/gpio-virtuser.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/drivers/gpio/gpio-virtuser.c b/drivers/gpio/gpio-virtuser.c index a10eab7d2617e4..252fec5ea38354 100644 --- a/drivers/gpio/gpio-virtuser.c +++ b/drivers/gpio/gpio-virtuser.c @@ -1684,10 +1684,10 @@ static void gpio_virtuser_device_config_group_release(struct config_item *item) { struct gpio_virtuser_device *dev = to_gpio_virtuser_device(item); - guard(mutex)(&dev->lock); - - if (gpio_virtuser_device_is_live(dev)) - gpio_virtuser_device_deactivate(dev); + scoped_guard(mutex, &dev->lock) { + if (gpio_virtuser_device_is_live(dev)) + gpio_virtuser_device_deactivate(dev); + } mutex_destroy(&dev->lock); ida_free(&gpio_virtuser_ida, dev->id); From 546e62c3fb29ad56dc9320b1c7325fa80ac9c0fc Mon Sep 17 00:00:00 2001 From: Yang Wang Date: Tue, 27 Jan 2026 11:07:07 +0800 Subject: [PATCH 2897/3902] drm/amd/pm: fix race in power state check before mutex lock [ Upstream commit ee8d07cd5730038e33bf5e551448190bbd480eb8 ] The power state check in amdgpu_dpm_set_powergating_by_smu() is done before acquiring the pm mutex, leading to a race condition where: 1. Thread A checks state and thinks no change is needed 2. Thread B acquires mutex and modifies the state 3. Thread A returns without updating state, causing inconsistency Fix this by moving the mutex lock before the power state check, ensuring atomicity of the state check and modification. Fixes: 6ee27ee27ba8 ("drm/amd/pm: avoid duplicate powergate/ungate setting") Signed-off-by: Yang Wang Reviewed-by: Kenneth Feng Signed-off-by: Alex Deucher (cherry picked from commit 7a3fbdfd19ec5992c0fc2d0bd83888644f5f2f38) Signed-off-by: Sasha Levin --- drivers/gpu/drm/amd/pm/amdgpu_dpm.c | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/drivers/gpu/drm/amd/pm/amdgpu_dpm.c b/drivers/gpu/drm/amd/pm/amdgpu_dpm.c index bc29a923fa6e53..8253d2977408db 100644 --- a/drivers/gpu/drm/amd/pm/amdgpu_dpm.c +++ b/drivers/gpu/drm/amd/pm/amdgpu_dpm.c @@ -80,15 +80,15 @@ int amdgpu_dpm_set_powergating_by_smu(struct amdgpu_device *adev, enum ip_power_state pwr_state = gate ? POWER_STATE_OFF : POWER_STATE_ON; bool is_vcn = block_type == AMD_IP_BLOCK_TYPE_VCN; + mutex_lock(&adev->pm.mutex); + if (atomic_read(&adev->pm.pwr_state[block_type]) == pwr_state && (!is_vcn || adev->vcn.num_vcn_inst == 1)) { dev_dbg(adev->dev, "IP block%d already in the target %s state!", block_type, gate ? "gate" : "ungate"); - return 0; + goto out_unlock; } - mutex_lock(&adev->pm.mutex); - switch (block_type) { case AMD_IP_BLOCK_TYPE_UVD: case AMD_IP_BLOCK_TYPE_VCE: @@ -115,6 +115,7 @@ int amdgpu_dpm_set_powergating_by_smu(struct amdgpu_device *adev, if (!ret) atomic_set(&adev->pm.pwr_state[block_type], pwr_state); +out_unlock: mutex_unlock(&adev->pm.mutex); return ret; From d4dbada7ac6f307107a062095fed178ebaaea0b7 Mon Sep 17 00:00:00 2001 From: Doug Berger Date: Tue, 27 Jan 2026 13:46:54 -0800 Subject: [PATCH 2898/3902] gpio: brcmstb: correct hwirq to bank map [ Upstream commit b2cf569ed81e7574d4287eaf3b2c38690a934d34 ] The brcmstb_gpio_hwirq_to_bank() function was designed to accommodate the downward numbering of dynamic GPIOs by traversing the bank list in the reverse order. However, the dynamic numbering has changed to increment upward which can produce an incorrect mapping. The function is modified to no longer assume an ordering of the list to accommodate either option. Fixes: 7b61212f2a07 ("gpiolib: Get rid of ARCH_NR_GPIOS") Signed-off-by: Doug Berger Signed-off-by: Florian Fainelli Reviewed-by: Linus Walleij Link: https://patch.msgid.link/20260127214656.447333-2-florian.fainelli@broadcom.com Signed-off-by: Bartosz Golaszewski Signed-off-by: Sasha Levin --- drivers/gpio/gpio-brcmstb.c | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/drivers/gpio/gpio-brcmstb.c b/drivers/gpio/gpio-brcmstb.c index f40c9472588bc7..f0cb1991b326cd 100644 --- a/drivers/gpio/gpio-brcmstb.c +++ b/drivers/gpio/gpio-brcmstb.c @@ -301,12 +301,10 @@ static struct brcmstb_gpio_bank *brcmstb_gpio_hwirq_to_bank( struct brcmstb_gpio_priv *priv, irq_hw_number_t hwirq) { struct brcmstb_gpio_bank *bank; - int i = 0; - /* banks are in descending order */ - list_for_each_entry_reverse(bank, &priv->bank_list, node) { - i += bank->chip.gc.ngpio; - if (hwirq < i) + list_for_each_entry(bank, &priv->bank_list, node) { + if (hwirq >= bank->chip.gc.offset && + hwirq < (bank->chip.gc.offset + bank->chip.gc.ngpio)) return bank; } return NULL; From 32d8f998bb8d4758f1747fdc53c783b33b78b9aa Mon Sep 17 00:00:00 2001 From: Nathan Chancellor Date: Wed, 21 Jan 2026 15:29:15 -0700 Subject: [PATCH 2899/3902] kbuild: rpm-pkg: Generate debuginfo package manually [ Upstream commit 62089b804895e845f82e132ea9d46a1fc53ed5a7 ] Commit a7c699d090a1 ("kbuild: rpm-pkg: build a debuginfo RPM") adjusted the __spec_install_post macro to include __os_install_post, which runs brp-strip. This ends up stripping module signatures, breaking loading modules with lockdown enabled. Undo most of the changes of the aforementioned debuginfo patch and mirror commit 16c36f8864e3 ("kbuild: deb-pkg: use build ID instead of debug link for dbg package") in kernel.spec to generate a functionally equivalent debuginfo package while avoiding touching the modules after they have already been signed during modules_install. Fixes: a7c699d090a1 ("kbuild: rpm-pkg: build a debuginfo RPM") Reported-by: Holger Kiehl Closes: https://lore.kernel.org/68c375f6-e07e-fec-434d-6a45a4f1390@praktifix.dwd.de/ Tested-by: Holger Kiehl Signed-off-by: Nathan Chancellor Link: https://patch.msgid.link/20260121-fix-module-signing-binrpm-pkg-v1-1-8fc5832b6cbc@kernel.org Signed-off-by: Nicolas Schier Signed-off-by: Sasha Levin --- scripts/package/kernel.spec | 65 +++++++++++++++++-------------------- 1 file changed, 30 insertions(+), 35 deletions(-) diff --git a/scripts/package/kernel.spec b/scripts/package/kernel.spec index 98f206cb7c6079..0f1c8de1bd95f8 100644 --- a/scripts/package/kernel.spec +++ b/scripts/package/kernel.spec @@ -2,6 +2,8 @@ %{!?_arch: %define _arch dummy} %{!?make: %define make make} %define makeflags %{?_smp_mflags} ARCH=%{ARCH} +%define __spec_install_post /usr/lib/rpm/brp-compress || : +%define debug_package %{nil} Name: kernel Summary: The Linux Kernel @@ -46,34 +48,12 @@ against the %{version} kernel package. %endif %if %{with_debuginfo} -# list of debuginfo-related options taken from distribution kernel.spec -# files -%undefine _include_minidebuginfo -%undefine _find_debuginfo_dwz_opts -%undefine _unique_build_ids -%undefine _unique_debug_names -%undefine _unique_debug_srcs -%undefine _debugsource_packages -%undefine _debuginfo_subpackages -%global _find_debuginfo_opts -r -%global _missing_build_ids_terminate_build 1 -%global _no_recompute_build_ids 1 -%{debug_package} +%package debuginfo +Summary: Debug information package for the Linux kernel +%description debuginfo +This package provides debug information for the kernel image and modules from the +%{version} package. %endif -# some (but not all) versions of rpmbuild emit %%debug_package with -# %%install. since we've already emitted it manually, that would cause -# a package redefinition error. ensure that doesn't happen -%define debug_package %{nil} - -# later, we make all modules executable so that find-debuginfo.sh strips -# them up. but they don't actually need to be executable, so remove the -# executable bit, taking care to do it _after_ find-debuginfo.sh has run -%define __spec_install_post \ - %{?__debug_package:%{__debug_install_post}} \ - %{__arch_install_post} \ - %{__os_install_post} \ - find %{buildroot}/lib/modules/%{KERNELRELEASE} -name "*.ko" -type f \\\ - | xargs --no-run-if-empty chmod u-x %prep %setup -q -n linux @@ -87,7 +67,7 @@ patch -p1 < %{SOURCE2} mkdir -p %{buildroot}/lib/modules/%{KERNELRELEASE} cp $(%{make} %{makeflags} -s image_name) %{buildroot}/lib/modules/%{KERNELRELEASE}/vmlinuz # DEPMOD=true makes depmod no-op. We do not package depmod-generated files. -%{make} %{makeflags} INSTALL_MOD_PATH=%{buildroot} DEPMOD=true modules_install +%{make} %{makeflags} INSTALL_MOD_PATH=%{buildroot} INSTALL_MOD_STRIP=1 DEPMOD=true modules_install %{make} %{makeflags} INSTALL_HDR_PATH=%{buildroot}/usr headers_install cp System.map %{buildroot}/lib/modules/%{KERNELRELEASE} cp .config %{buildroot}/lib/modules/%{KERNELRELEASE}/config @@ -118,22 +98,31 @@ ln -fns /usr/src/kernels/%{KERNELRELEASE} %{buildroot}/lib/modules/%{KERNELRELEA echo "%exclude /lib/modules/%{KERNELRELEASE}/build" } > %{buildroot}/kernel.list -# make modules executable so that find-debuginfo.sh strips them. this -# will be undone later in %%__spec_install_post -find %{buildroot}/lib/modules/%{KERNELRELEASE} -name "*.ko" -type f \ - | xargs --no-run-if-empty chmod u+x - %if %{with_debuginfo} # copying vmlinux directly to the debug directory means it will not get # stripped (but its source paths will still be collected + fixed up) mkdir -p %{buildroot}/usr/lib/debug/lib/modules/%{KERNELRELEASE} cp vmlinux %{buildroot}/usr/lib/debug/lib/modules/%{KERNELRELEASE} + +echo /usr/lib/debug/lib/modules/%{KERNELRELEASE}/vmlinux > %{buildroot}/debuginfo.list + +while read -r mod; do + mod="${mod%.o}.ko" + dbg="%{buildroot}/usr/lib/debug/lib/modules/%{KERNELRELEASE}/kernel/${mod}" + buildid=$("${READELF}" -n "${mod}" | sed -n 's@^.*Build ID: \(..\)\(.*\)@\1/\2@p') + link="%{buildroot}/usr/lib/debug/.build-id/${buildid}.debug" + + mkdir -p "${dbg%/*}" "${link%/*}" + "${OBJCOPY}" --only-keep-debug "${mod}" "${dbg}" + ln -sf --relative "${dbg}" "${link}" + + echo "${dbg#%{buildroot}}" >> %{buildroot}/debuginfo.list + echo "${link#%{buildroot}}" >> %{buildroot}/debuginfo.list +done < modules.order %endif %clean rm -rf %{buildroot} -rm -f debugfiles.list debuglinks.list debugsourcefiles.list debugsources.list \ - elfbins.list %post if [ -x /usr/bin/kernel-install ]; then @@ -172,3 +161,9 @@ fi /usr/src/kernels/%{KERNELRELEASE} /lib/modules/%{KERNELRELEASE}/build %endif + +%if %{with_debuginfo} +%files -f %{buildroot}/debuginfo.list debuginfo +%defattr (-, root, root) +%exclude /debuginfo.list +%endif From 8d9c5ceff4b536256db541931f71a6490da7919b Mon Sep 17 00:00:00 2001 From: Ethan Zuo Date: Wed, 28 Jan 2026 14:37:51 +0800 Subject: [PATCH 2900/3902] kbuild: Fix permissions of modules.builtin.modinfo [ Upstream commit 6d60354ea2f90352b22039ed8371c4f4321df90e ] Currently, modules.builtin.modinfo is created with executable permissions (0755). This is because after commit 39cfd5b12160 ("kbuild: extract modules.builtin.modinfo from vmlinux.unstripped"), modules.builtin.modinfo is extracted from vmlinux.unstripped using objcopy. When extracting sections, objcopy inherits attributes from the source ELF file. Since modules.builtin.modinfo is a data file and not an executable, it should have regular file permissions (0644). The executable bit can trigger warnings in Debian's Lintian tool. Explicitly remove the executable bit after generation. Fixes: 39cfd5b12160 ("kbuild: extract modules.builtin.modinfo from vmlinux.unstripped") Signed-off-by: Ethan Zuo Link: https://patch.msgid.link/SY0P300MB0609F6916B24ADF65502940B9C91A@SY0P300MB0609.AUSP300.PROD.OUTLOOK.COM Signed-off-by: Nicolas Schier Signed-off-by: Sasha Levin --- scripts/Makefile.vmlinux | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/scripts/Makefile.vmlinux b/scripts/Makefile.vmlinux index cd788cac9d91da..276c3134a563ad 100644 --- a/scripts/Makefile.vmlinux +++ b/scripts/Makefile.vmlinux @@ -113,7 +113,8 @@ vmlinux: vmlinux.unstripped FORCE # what kmod expects to parse. quiet_cmd_modules_builtin_modinfo = GEN $@ cmd_modules_builtin_modinfo = $(cmd_objcopy); \ - sed -i 's/\x00\+$$/\x00/g' $@ + sed -i 's/\x00\+$$/\x00/g' $@; \ + chmod -x $@ OBJCOPYFLAGS_modules.builtin.modinfo := -j .modinfo -O binary From 14e32032c42fe3bab36b00278a4cc5dd66660524 Mon Sep 17 00:00:00 2001 From: Yuntao Wang Date: Sat, 15 Nov 2025 21:47:52 +0800 Subject: [PATCH 2901/3902] of/reserved_mem: Simplify the logic of fdt_scan_reserved_mem_reg_nodes() [ Upstream commit 85a8a30c5b8e0ffaaf9f4dc51550dc71a1100df4 ] Use the existing helper functions to simplify the logic of fdt_scan_reserved_mem_reg_nodes() Signed-off-by: Yuntao Wang Link: https://patch.msgid.link/20251115134753.179931-8-yuntao.wang@linux.dev Signed-off-by: Rob Herring (Arm) Stable-dep-of: 0fd17e598333 ("of: reserved_mem: Allow reserved_mem framework detect "cma=" kernel param") Signed-off-by: Sasha Levin --- drivers/of/of_reserved_mem.c | 25 +++++++------------------ 1 file changed, 7 insertions(+), 18 deletions(-) diff --git a/drivers/of/of_reserved_mem.c b/drivers/of/of_reserved_mem.c index 2e9ea751ed2df0..e5ea4f1e5eff78 100644 --- a/drivers/of/of_reserved_mem.c +++ b/drivers/of/of_reserved_mem.c @@ -230,12 +230,9 @@ static void __init __rmem_check_for_overlap(void); */ void __init fdt_scan_reserved_mem_reg_nodes(void) { - int t_len = (dt_root_addr_cells + dt_root_size_cells) * sizeof(__be32); const void *fdt = initial_boot_params; phys_addr_t base, size; - const __be32 *prop; int node, child; - int len; if (!fdt) return; @@ -256,29 +253,21 @@ void __init fdt_scan_reserved_mem_reg_nodes(void) fdt_for_each_subnode(child, fdt, node) { const char *uname; + u64 b, s; - prop = of_get_flat_dt_prop(child, "reg", &len); - if (!prop) - continue; if (!of_fdt_device_is_available(fdt, child)) continue; - uname = fdt_get_name(fdt, child, NULL); - if (len && len % t_len != 0) { - pr_err("Reserved memory: invalid reg property in '%s', skipping node.\n", - uname); + if (!of_flat_dt_get_addr_size(child, "reg", &b, &s)) continue; - } - if (len > t_len) - pr_warn("%s() ignores %d regions in node '%s'\n", - __func__, len / t_len - 1, uname); + base = b; + size = s; - base = dt_mem_next_cell(dt_root_addr_cells, &prop); - size = dt_mem_next_cell(dt_root_size_cells, &prop); - - if (size) + if (size) { + uname = fdt_get_name(fdt, child, NULL); fdt_reserved_mem_save_node(child, uname, base, size); + } } /* check for overlapping reserved regions */ From 23f9f9c46edd97bd0d1b236bc186f635df32dac8 Mon Sep 17 00:00:00 2001 From: Oreoluwa Babatunde Date: Mon, 26 Jan 2026 18:13:27 +0100 Subject: [PATCH 2902/3902] of: reserved_mem: Allow reserved_mem framework detect "cma=" kernel param [ Upstream commit 0fd17e5983337231dc655e9ca0095d2ca3f47405 ] When initializing the default cma region, the "cma=" kernel parameter takes priority over a DT defined linux,cma-default region. Hence, give the reserved_mem framework the ability to detect this so that the DT defined cma region can skip initialization accordingly. Signed-off-by: Oreoluwa Babatunde Tested-by: Joy Zou Acked-by: Rob Herring (Arm) Fixes: 8a6e02d0c00e ("of: reserved_mem: Restructure how the reserved memory regions are processed") Fixes: 2c223f7239f3 ("of: reserved_mem: Restructure call site for dma_contiguous_early_fixup()") Link: https://lore.kernel.org/r/20251210002027.1171519-1-oreoluwa.babatunde@oss.qualcomm.com [mszyprow: rebased onto v6.19-rc1, added fixes tags, added a stub for cma_skip_dt_default_reserved_mem() if no CONFIG_DMA_CMA is set] Signed-off-by: Marek Szyprowski Signed-off-by: Sasha Levin --- drivers/of/of_reserved_mem.c | 19 +++++++++++++++++-- include/linux/cma.h | 9 +++++++++ kernel/dma/contiguous.c | 16 ++++++++++------ 3 files changed, 36 insertions(+), 8 deletions(-) diff --git a/drivers/of/of_reserved_mem.c b/drivers/of/of_reserved_mem.c index e5ea4f1e5eff78..fe111d1ea73971 100644 --- a/drivers/of/of_reserved_mem.c +++ b/drivers/of/of_reserved_mem.c @@ -158,7 +158,7 @@ static int __init __reserved_mem_reserve_reg(unsigned long node, phys_addr_t base, size; int len; const __be32 *prop; - bool nomap; + bool nomap, default_cma; prop = of_get_flat_dt_prop(node, "reg", &len); if (!prop) @@ -171,6 +171,12 @@ static int __init __reserved_mem_reserve_reg(unsigned long node, } nomap = of_get_flat_dt_prop(node, "no-map", NULL) != NULL; + default_cma = of_get_flat_dt_prop(node, "linux,cma-default", NULL); + + if (default_cma && cma_skip_dt_default_reserved_mem()) { + pr_err("Skipping dt linux,cma-default for \"cma=\" kernel param.\n"); + return -EINVAL; + } while (len >= t_len) { base = dt_mem_next_cell(dt_root_addr_cells, &prop); @@ -253,10 +259,13 @@ void __init fdt_scan_reserved_mem_reg_nodes(void) fdt_for_each_subnode(child, fdt, node) { const char *uname; + bool default_cma = of_get_flat_dt_prop(child, "linux,cma-default", NULL); u64 b, s; if (!of_fdt_device_is_available(fdt, child)) continue; + if (default_cma && cma_skip_dt_default_reserved_mem()) + continue; if (!of_flat_dt_get_addr_size(child, "reg", &b, &s)) continue; @@ -395,7 +404,7 @@ static int __init __reserved_mem_alloc_size(unsigned long node, const char *unam phys_addr_t base = 0, align = 0, size; int len; const __be32 *prop; - bool nomap; + bool nomap, default_cma; int ret; prop = of_get_flat_dt_prop(node, "size", &len); @@ -419,6 +428,12 @@ static int __init __reserved_mem_alloc_size(unsigned long node, const char *unam } nomap = of_get_flat_dt_prop(node, "no-map", NULL) != NULL; + default_cma = of_get_flat_dt_prop(node, "linux,cma-default", NULL); + + if (default_cma && cma_skip_dt_default_reserved_mem()) { + pr_err("Skipping dt linux,cma-default for \"cma=\" kernel param.\n"); + return -EINVAL; + } /* Need adjust the alignment to satisfy the CMA requirement */ if (IS_ENABLED(CONFIG_CMA) diff --git a/include/linux/cma.h b/include/linux/cma.h index 62d9c1cf632652..2e6931735880bd 100644 --- a/include/linux/cma.h +++ b/include/linux/cma.h @@ -57,6 +57,15 @@ extern bool cma_intersects(struct cma *cma, unsigned long start, unsigned long e extern void cma_reserve_pages_on_error(struct cma *cma); +#ifdef CONFIG_DMA_CMA +extern bool cma_skip_dt_default_reserved_mem(void); +#else +static inline bool cma_skip_dt_default_reserved_mem(void) +{ + return false; +} +#endif + #ifdef CONFIG_CMA struct folio *cma_alloc_folio(struct cma *cma, int order, gfp_t gfp); bool cma_free_folio(struct cma *cma, const struct folio *folio); diff --git a/kernel/dma/contiguous.c b/kernel/dma/contiguous.c index d9b9dcba6ff7cf..9071c08650e3ad 100644 --- a/kernel/dma/contiguous.c +++ b/kernel/dma/contiguous.c @@ -90,6 +90,16 @@ static int __init early_cma(char *p) } early_param("cma", early_cma); +/* + * cma_skip_dt_default_reserved_mem - This is called from the + * reserved_mem framework to detect if the default cma region is being + * set by the "cma=" kernel parameter. + */ +bool __init cma_skip_dt_default_reserved_mem(void) +{ + return size_cmdline != -1; +} + #ifdef CONFIG_DMA_NUMA_CMA static struct cma *dma_contiguous_numa_area[MAX_NUMNODES]; @@ -463,12 +473,6 @@ static int __init rmem_cma_setup(struct reserved_mem *rmem) struct cma *cma; int err; - if (size_cmdline != -1 && default_cma) { - pr_info("Reserved memory: bypass %s node, using cmdline CMA params instead\n", - rmem->name); - return -EBUSY; - } - if (!of_get_flat_dt_prop(node, "reusable", NULL) || of_get_flat_dt_prop(node, "no-map", NULL)) return -EINVAL; From db6e287bd6e80d58367baf05bf05af45856b9c2d Mon Sep 17 00:00:00 2001 From: Shida Zhang Date: Tue, 9 Dec 2025 17:01:56 +0800 Subject: [PATCH 2903/3902] bcache: fix improper use of bi_end_io [ Upstream commit 53280e398471f0bddbb17b798a63d41264651325 ] Don't call bio->bi_end_io() directly. Use the bio_endio() helper function instead, which handles completion more safely and uniformly. Suggested-by: Christoph Hellwig Reviewed-by: Christoph Hellwig Signed-off-by: Shida Zhang Signed-off-by: Jens Axboe Stable-dep-of: 4da7c5c3ec34 ("bcache: fix I/O accounting leak in detached_dev_do_request") Signed-off-by: Sasha Levin --- drivers/md/bcache/request.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/drivers/md/bcache/request.c b/drivers/md/bcache/request.c index af345dc6fde14f..82fdea7dea7aac 100644 --- a/drivers/md/bcache/request.c +++ b/drivers/md/bcache/request.c @@ -1104,7 +1104,7 @@ static void detached_dev_end_io(struct bio *bio) } kfree(ddip); - bio->bi_end_io(bio); + bio_endio(bio); } static void detached_dev_do_request(struct bcache_device *d, struct bio *bio, @@ -1121,7 +1121,7 @@ static void detached_dev_do_request(struct bcache_device *d, struct bio *bio, ddip = kzalloc(sizeof(struct detached_dev_io_private), GFP_NOIO); if (!ddip) { bio->bi_status = BLK_STS_RESOURCE; - bio->bi_end_io(bio); + bio_endio(bio); return; } @@ -1136,7 +1136,7 @@ static void detached_dev_do_request(struct bcache_device *d, struct bio *bio, if ((bio_op(bio) == REQ_OP_DISCARD) && !bdev_max_discard_sectors(dc->bdev)) - bio->bi_end_io(bio); + detached_dev_end_io(bio); else submit_bio_noacct(bio); } From 7b72d76a58e6b7e3835fd681babef49a6b30daba Mon Sep 17 00:00:00 2001 From: Shida Zhang Date: Thu, 22 Jan 2026 14:13:21 +0800 Subject: [PATCH 2904/3902] bcache: use bio cloning for detached device requests [ Upstream commit 3ef825dfd4e487d6f92b23ee2df2455814583ef4 ] Previously, bcache hijacked the bi_end_io and bi_private fields of the incoming bio when the backing device was in a detached state. This is fragile and breaks if the bio is needed to be processed by other layers. This patch transitions to using a cloned bio embedded within a private structure. This ensures the original bio's metadata remains untouched. Fixes: 53280e398471 ("bcache: fix improper use of bi_end_io") Co-developed-by: Christoph Hellwig Signed-off-by: Christoph Hellwig Signed-off-by: Shida Zhang Acked-by: Coly Li Signed-off-by: Jens Axboe Stable-dep-of: 4da7c5c3ec34 ("bcache: fix I/O accounting leak in detached_dev_do_request") Signed-off-by: Sasha Levin --- drivers/md/bcache/bcache.h | 9 +++++ drivers/md/bcache/request.c | 79 ++++++++++++++++--------------------- drivers/md/bcache/super.c | 12 +++++- 3 files changed, 54 insertions(+), 46 deletions(-) diff --git a/drivers/md/bcache/bcache.h b/drivers/md/bcache/bcache.h index 1d33e40d26ea51..cca5756030d71e 100644 --- a/drivers/md/bcache/bcache.h +++ b/drivers/md/bcache/bcache.h @@ -273,6 +273,8 @@ struct bcache_device { struct bio_set bio_split; + struct bio_set bio_detached; + unsigned int data_csum:1; int (*cache_miss)(struct btree *b, struct search *s, @@ -755,6 +757,13 @@ struct bbio { struct bio bio; }; +struct detached_dev_io_private { + struct bcache_device *d; + unsigned long start_time; + struct bio *orig_bio; + struct bio bio; +}; + #define BTREE_PRIO USHRT_MAX #define INITIAL_PRIO 32768U diff --git a/drivers/md/bcache/request.c b/drivers/md/bcache/request.c index 82fdea7dea7aac..a02aecac05cdf9 100644 --- a/drivers/md/bcache/request.c +++ b/drivers/md/bcache/request.c @@ -1077,68 +1077,58 @@ static CLOSURE_CALLBACK(cached_dev_nodata) continue_at(cl, cached_dev_bio_complete, NULL); } -struct detached_dev_io_private { - struct bcache_device *d; - unsigned long start_time; - bio_end_io_t *bi_end_io; - void *bi_private; - struct block_device *orig_bdev; -}; - static void detached_dev_end_io(struct bio *bio) { - struct detached_dev_io_private *ddip; - - ddip = bio->bi_private; - bio->bi_end_io = ddip->bi_end_io; - bio->bi_private = ddip->bi_private; + struct detached_dev_io_private *ddip = + container_of(bio, struct detached_dev_io_private, bio); + struct bio *orig_bio = ddip->orig_bio; /* Count on the bcache device */ - bio_end_io_acct_remapped(bio, ddip->start_time, ddip->orig_bdev); + bio_end_io_acct(orig_bio, ddip->start_time); if (bio->bi_status) { - struct cached_dev *dc = container_of(ddip->d, - struct cached_dev, disk); + struct cached_dev *dc = bio->bi_private; + /* should count I/O error for backing device here */ bch_count_backing_io_errors(dc, bio); + orig_bio->bi_status = bio->bi_status; } - kfree(ddip); - bio_endio(bio); + bio_put(bio); + bio_endio(orig_bio); } -static void detached_dev_do_request(struct bcache_device *d, struct bio *bio, - struct block_device *orig_bdev, unsigned long start_time) +static void detached_dev_do_request(struct bcache_device *d, + struct bio *orig_bio, unsigned long start_time) { struct detached_dev_io_private *ddip; struct cached_dev *dc = container_of(d, struct cached_dev, disk); + struct bio *clone_bio; - /* - * no need to call closure_get(&dc->disk.cl), - * because upper layer had already opened bcache device, - * which would call closure_get(&dc->disk.cl) - */ - ddip = kzalloc(sizeof(struct detached_dev_io_private), GFP_NOIO); - if (!ddip) { - bio->bi_status = BLK_STS_RESOURCE; - bio_endio(bio); + if (bio_op(orig_bio) == REQ_OP_DISCARD && + !bdev_max_discard_sectors(dc->bdev)) { + bio_endio(orig_bio); return; } - ddip->d = d; + clone_bio = bio_alloc_clone(dc->bdev, orig_bio, GFP_NOIO, + &d->bio_detached); + if (!clone_bio) { + orig_bio->bi_status = BLK_STS_RESOURCE; + bio_endio(orig_bio); + return; + } + + ddip = container_of(clone_bio, struct detached_dev_io_private, bio); /* Count on the bcache device */ - ddip->orig_bdev = orig_bdev; + ddip->d = d; ddip->start_time = start_time; - ddip->bi_end_io = bio->bi_end_io; - ddip->bi_private = bio->bi_private; - bio->bi_end_io = detached_dev_end_io; - bio->bi_private = ddip; - - if ((bio_op(bio) == REQ_OP_DISCARD) && - !bdev_max_discard_sectors(dc->bdev)) - detached_dev_end_io(bio); - else - submit_bio_noacct(bio); + ddip->orig_bio = orig_bio; + + clone_bio->bi_end_io = detached_dev_end_io; + clone_bio->bi_private = dc; + + submit_bio_noacct(clone_bio); } static void quit_max_writeback_rate(struct cache_set *c, @@ -1214,10 +1204,10 @@ void cached_dev_submit_bio(struct bio *bio) start_time = bio_start_io_acct(bio); - bio_set_dev(bio, dc->bdev); bio->bi_iter.bi_sector += dc->sb.data_offset; if (cached_dev_get(dc)) { + bio_set_dev(bio, dc->bdev); s = search_alloc(bio, d, orig_bdev, start_time); trace_bcache_request_start(s->d, bio); @@ -1237,9 +1227,10 @@ void cached_dev_submit_bio(struct bio *bio) else cached_dev_read(dc, s); } - } else + } else { /* I/O request sent to backing device */ - detached_dev_do_request(d, bio, orig_bdev, start_time); + detached_dev_do_request(d, bio, start_time); + } } static int cached_dev_ioctl(struct bcache_device *d, blk_mode_t mode, diff --git a/drivers/md/bcache/super.c b/drivers/md/bcache/super.c index 6d250e366412c7..9218b9dbd4af2f 100644 --- a/drivers/md/bcache/super.c +++ b/drivers/md/bcache/super.c @@ -887,6 +887,7 @@ static void bcache_device_free(struct bcache_device *d) } bioset_exit(&d->bio_split); + bioset_exit(&d->bio_detached); kvfree(d->full_dirty_stripes); kvfree(d->stripe_sectors_dirty); @@ -949,6 +950,11 @@ static int bcache_device_init(struct bcache_device *d, unsigned int block_size, BIOSET_NEED_BVECS|BIOSET_NEED_RESCUER)) goto out_ida_remove; + if (bioset_init(&d->bio_detached, 4, + offsetof(struct detached_dev_io_private, bio), + BIOSET_NEED_BVECS|BIOSET_NEED_RESCUER)) + goto out_bioset_split_exit; + if (lim.logical_block_size > PAGE_SIZE && cached_bdev) { /* * This should only happen with BCACHE_SB_VERSION_BDEV. @@ -964,7 +970,7 @@ static int bcache_device_init(struct bcache_device *d, unsigned int block_size, d->disk = blk_alloc_disk(&lim, NUMA_NO_NODE); if (IS_ERR(d->disk)) - goto out_bioset_exit; + goto out_bioset_detach_exit; set_capacity(d->disk, sectors); snprintf(d->disk->disk_name, DISK_NAME_LEN, "bcache%i", idx); @@ -976,7 +982,9 @@ static int bcache_device_init(struct bcache_device *d, unsigned int block_size, d->disk->private_data = d; return 0; -out_bioset_exit: +out_bioset_detach_exit: + bioset_exit(&d->bio_detached); +out_bioset_split_exit: bioset_exit(&d->bio_split); out_ida_remove: ida_free(&bcache_device_idx, idx); From d36c4149ea2240ffe568068823c846fb1966cd9b Mon Sep 17 00:00:00 2001 From: Shida Zhang Date: Tue, 27 Jan 2026 16:21:12 +0800 Subject: [PATCH 2905/3902] bcache: fix I/O accounting leak in detached_dev_do_request [ Upstream commit 4da7c5c3ec34d839bba6e035c3d05c447a2f9d4f ] When a bcache device is detached, discard requests are completed immediately. However, the I/O accounting started in cached_dev_make_request() is not ended, leading to 100% disk utilization reports in iostat. Add the missing bio_end_io_acct() call. Fixes: cafe56359144 ("bcache: A block layer cache") Signed-off-by: Shida Zhang Acked-by: Coly Li Reviewed-by: Christoph Hellwig Signed-off-by: Jens Axboe Signed-off-by: Sasha Levin --- drivers/md/bcache/request.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/md/bcache/request.c b/drivers/md/bcache/request.c index a02aecac05cdf9..6cba1180be8aab 100644 --- a/drivers/md/bcache/request.c +++ b/drivers/md/bcache/request.c @@ -1107,6 +1107,7 @@ static void detached_dev_do_request(struct bcache_device *d, if (bio_op(orig_bio) == REQ_OP_DISCARD && !bdev_max_discard_sectors(dc->bdev)) { + bio_end_io_acct(orig_bio, start_time); bio_endio(orig_bio); return; } From 64364ccf491c708becc3b1841bec2c90ef8dd62f Mon Sep 17 00:00:00 2001 From: Sai Sree Kartheek Adivi Date: Wed, 28 Jan 2026 19:05:54 +0530 Subject: [PATCH 2906/3902] dma/pool: distinguish between missing and exhausted atomic pools [ Upstream commit 56c430c7f06d838fe3b2077dbbc4cc0bf992312b ] Currently, dma_alloc_from_pool() unconditionally warns and dumps a stack trace when an allocation fails, with the message "Failed to get suitable pool". This conflates two distinct failure modes: 1. Configuration error: No atomic pool is available for the requested DMA mask (a fundamental system setup issue) 2. Resource Exhaustion: A suitable pool exists but is currently full (a recoverable runtime state) This lack of distinction prevents drivers from using __GFP_NOWARN to suppress error messages during temporary pressure spikes, such as when awaiting synchronous reclaim of descriptors. Refactor the error handling to distinguish these cases: - If no suitable pool is found, keep the unconditional WARN regarding the missing pool. - If a pool was found but is exhausted, respect __GFP_NOWARN and update the warning message to explicitly state "DMA pool exhausted". Fixes: 9420139f516d ("dma-pool: fix coherent pool allocations for IOMMU mappings") Signed-off-by: Sai Sree Kartheek Adivi Reviewed-by: Robin Murphy Signed-off-by: Marek Szyprowski Link: https://lore.kernel.org/r/20260128133554.3056582-1-s-adivi@ti.com Signed-off-by: Sasha Levin --- kernel/dma/pool.c | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/kernel/dma/pool.c b/kernel/dma/pool.c index 26392badc36b0f..985d6aa102b675 100644 --- a/kernel/dma/pool.c +++ b/kernel/dma/pool.c @@ -268,15 +268,20 @@ struct page *dma_alloc_from_pool(struct device *dev, size_t size, { struct gen_pool *pool = NULL; struct page *page; + bool pool_found = false; while ((pool = dma_guess_pool(pool, gfp))) { + pool_found = true; page = __dma_alloc_from_pool(dev, size, pool, cpu_addr, phys_addr_ok); if (page) return page; } - WARN(1, "Failed to get suitable pool for %s\n", dev_name(dev)); + if (pool_found) + WARN(!(gfp & __GFP_NOWARN), "DMA pool exhausted for %s\n", dev_name(dev)); + else + WARN(1, "Failed to get suitable pool for %s\n", dev_name(dev)); return NULL; } From 2859fa957a936fd1382f2e00a6e2a117d135b4dd Mon Sep 17 00:00:00 2001 From: Shuicheng Lin Date: Wed, 21 Jan 2026 17:37:51 +0000 Subject: [PATCH 2907/3902] drm/xe/configfs: Fix is_bound() pci_dev lifetime MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [ Upstream commit c1ed856c09d0d730c2f63bbb757cb6011db148f9 ] Move pci_dev_put() after pci_dbg() to avoid using pdev after dropping its reference. Fixes: 2674f1ef29f46 ("drm/xe/configfs: Block runtime attribute changes") Signed-off-by: Shuicheng Lin Reviewed-by: Ashutosh Dixit Signed-off-by: Ashutosh Dixit Link: https://patch.msgid.link/20260121173750.3090907-2-shuicheng.lin@intel.com (cherry picked from commit 63b33604365bdca43dee41bab809da2230491036) Signed-off-by: Thomas Hellström Signed-off-by: Sasha Levin --- drivers/gpu/drm/xe/xe_configfs.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/drivers/gpu/drm/xe/xe_configfs.c b/drivers/gpu/drm/xe/xe_configfs.c index 1396634231857e..6688b2954d20b5 100644 --- a/drivers/gpu/drm/xe/xe_configfs.c +++ b/drivers/gpu/drm/xe/xe_configfs.c @@ -258,11 +258,10 @@ static bool is_bound(struct xe_config_group_device *dev) return false; ret = pci_get_drvdata(pdev); - pci_dev_put(pdev); - if (ret) pci_dbg(pdev, "Already bound to driver\n"); + pci_dev_put(pdev); return ret; } From 02dc6cf6409e5f8bdf529a51725fe3e93c21c3d5 Mon Sep 17 00:00:00 2001 From: Shuicheng Lin Date: Tue, 20 Jan 2026 18:32:41 +0000 Subject: [PATCH 2908/3902] drm/xe/nvm: Manage nvm aux cleanup with devres MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [ Upstream commit 2da8fbb8f1c17129a08c1e0e42c71eabdca76062 ] Move nvm teardown to a devm-managed action registered from xe_nvm_init(). This ensures the auxiliary NVM device is deleted on probe failure and device detach without requiring explicit calls from remove paths. As part of this, drop xe_nvm_fini() from xe_device_remove() and from the survivability sysfs teardown, and remove the public xe_nvm_fini() API from the header. This is to fix below warn message when there is probe failure after xe_nvm_init(), then xe_device_probe() is called again: " [ 207.318152] sysfs: cannot create duplicate filename '/devices/pci0000:00/0000:00:01.0/0000:01:00.0/0000:02:01.0/0000:03:00.0/xe.nvm.768' [ 207.318157] CPU: 5 UID: 0 PID: 10261 Comm: modprobe Tainted: G B W 6.19.0-rc2-lgci-xe-kernel+ #223 PREEMPT(voluntary) [ 207.318160] Tainted: [B]=BAD_PAGE, [W]=WARN [ 207.318161] Hardware name: ASUS System Product Name/PRIME Z790-P WIFI, BIOS 0812 02/24/2023 [ 207.318163] Call Trace: [ 207.318163] [ 207.318165] dump_stack_lvl+0xa0/0xc0 [ 207.318170] dump_stack+0x10/0x20 [ 207.318171] sysfs_warn_dup+0xd5/0x110 [ 207.318175] sysfs_create_dir_ns+0x1f6/0x280 [ 207.318177] ? __pfx_sysfs_create_dir_ns+0x10/0x10 [ 207.318179] ? lock_acquire+0x1a4/0x2e0 [ 207.318182] ? __kasan_check_read+0x11/0x20 [ 207.318185] ? do_raw_spin_unlock+0x5c/0x240 [ 207.318187] kobject_add_internal+0x28d/0x8e0 [ 207.318189] kobject_add+0x11f/0x1f0 [ 207.318191] ? __pfx_kobject_add+0x10/0x10 [ 207.318193] ? lockdep_init_map_type+0x4b/0x230 [ 207.318195] ? get_device_parent.isra.0+0x43/0x4c0 [ 207.318197] ? kobject_get+0x55/0xf0 [ 207.318199] device_add+0x2d7/0x1500 [ 207.318201] ? __pfx_device_add+0x10/0x10 [ 207.318203] ? lockdep_init_map_type+0x4b/0x230 [ 207.318205] __auxiliary_device_add+0x99/0x140 [ 207.318208] xe_nvm_init+0x7a2/0xef0 [xe] [ 207.318333] ? xe_devcoredump_init+0x80/0x110 [xe] [ 207.318452] ? __devm_add_action+0x82/0xc0 [ 207.318454] ? fs_reclaim_release+0xc0/0x110 [ 207.318457] xe_device_probe+0x17dd/0x2c40 [xe] [ 207.318574] ? __pfx___drm_dev_dbg+0x10/0x10 [ 207.318576] ? add_dr+0x180/0x220 [ 207.318579] ? __pfx___drmm_mutex_release+0x10/0x10 [ 207.318582] ? __pfx_xe_device_probe+0x10/0x10 [xe] [ 207.318697] ? xe_pm_init_early+0x33a/0x410 [xe] [ 207.318850] xe_pci_probe+0x936/0x1250 [xe] [ 207.318999] ? lock_acquire+0x1a4/0x2e0 [ 207.319003] ? __pfx_xe_pci_probe+0x10/0x10 [xe] [ 207.319151] local_pci_probe+0xe6/0x1a0 [ 207.319154] pci_device_probe+0x523/0x840 [ 207.319157] ? __pfx_pci_device_probe+0x10/0x10 [ 207.319159] ? sysfs_do_create_link_sd.isra.0+0x8c/0x110 [ 207.319162] ? sysfs_create_link+0x48/0xc0 ... " Fixes: c28bfb107dac ("drm/xe/nvm: add on-die non-volatile memory device") Reviewed-by: Alexander Usyskin Reviewed-by: Brian Nguyen Cc: Rodrigo Vivi Cc: Riana Tauro Signed-off-by: Shuicheng Lin Signed-off-by: Ashutosh Dixit Link: https://patch.msgid.link/20260120183239.2966782-6-shuicheng.lin@intel.com (cherry picked from commit 11035eab1b7d88daa7904440046e64d3810b1ca1) Signed-off-by: Thomas Hellström Signed-off-by: Sasha Levin --- drivers/gpu/drm/xe/xe_device.c | 2 -- drivers/gpu/drm/xe/xe_nvm.c | 43 +++++++++++++++++----------------- drivers/gpu/drm/xe/xe_nvm.h | 2 -- 3 files changed, 22 insertions(+), 25 deletions(-) diff --git a/drivers/gpu/drm/xe/xe_device.c b/drivers/gpu/drm/xe/xe_device.c index 5f757790d6f53f..fe5aadb27b779f 100644 --- a/drivers/gpu/drm/xe/xe_device.c +++ b/drivers/gpu/drm/xe/xe_device.c @@ -974,8 +974,6 @@ void xe_device_remove(struct xe_device *xe) { xe_display_unregister(xe); - xe_nvm_fini(xe); - drm_dev_unplug(&xe->drm); xe_bo_pci_dev_remove_all(xe); diff --git a/drivers/gpu/drm/xe/xe_nvm.c b/drivers/gpu/drm/xe/xe_nvm.c index 33f4ac82fc80ab..1fff24dbc7cd49 100644 --- a/drivers/gpu/drm/xe/xe_nvm.c +++ b/drivers/gpu/drm/xe/xe_nvm.c @@ -83,6 +83,27 @@ static bool xe_nvm_writable_override(struct xe_device *xe) return writable_override; } +static void xe_nvm_fini(void *arg) +{ + struct xe_device *xe = arg; + struct intel_dg_nvm_dev *nvm = xe->nvm; + + if (!xe->info.has_gsc_nvm) + return; + + /* No access to internal NVM from VFs */ + if (IS_SRIOV_VF(xe)) + return; + + /* Nvm pointer should not be NULL here */ + if (WARN_ON(!nvm)) + return; + + auxiliary_device_delete(&nvm->aux_dev); + auxiliary_device_uninit(&nvm->aux_dev); + xe->nvm = NULL; +} + int xe_nvm_init(struct xe_device *xe) { struct pci_dev *pdev = to_pci_dev(xe->drm.dev); @@ -141,30 +162,10 @@ int xe_nvm_init(struct xe_device *xe) auxiliary_device_uninit(aux_dev); goto err; } - return 0; + return devm_add_action_or_reset(xe->drm.dev, xe_nvm_fini, xe); err: kfree(nvm); xe->nvm = NULL; return ret; } - -void xe_nvm_fini(struct xe_device *xe) -{ - struct intel_dg_nvm_dev *nvm = xe->nvm; - - if (!xe->info.has_gsc_nvm) - return; - - /* No access to internal NVM from VFs */ - if (IS_SRIOV_VF(xe)) - return; - - /* Nvm pointer should not be NULL here */ - if (WARN_ON(!nvm)) - return; - - auxiliary_device_delete(&nvm->aux_dev); - auxiliary_device_uninit(&nvm->aux_dev); - xe->nvm = NULL; -} diff --git a/drivers/gpu/drm/xe/xe_nvm.h b/drivers/gpu/drm/xe/xe_nvm.h index 7f3d5f57bed088..fd3467ad35a4d9 100644 --- a/drivers/gpu/drm/xe/xe_nvm.h +++ b/drivers/gpu/drm/xe/xe_nvm.h @@ -10,6 +10,4 @@ struct xe_device; int xe_nvm_init(struct xe_device *xe); -void xe_nvm_fini(struct xe_device *xe); - #endif From 32887d8e4bc0696b3cb6c5915a42b39cfd3434f4 Mon Sep 17 00:00:00 2001 From: Shuicheng Lin Date: Tue, 20 Jan 2026 18:32:42 +0000 Subject: [PATCH 2909/3902] drm/xe/nvm: Fix double-free on aux add failure MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [ Upstream commit 8a44241b0b83a6047c5448da1fff03fcc29496b5 ] After a successful auxiliary_device_init(), aux_dev->dev.release (xe_nvm_release_dev()) is responsible for the kfree(nvm). When there is failure with auxiliary_device_add(), driver will call auxiliary_device_uninit(), which call put_device(). So that the .release callback will be triggered to free the memory associated with the auxiliary_device. Move the kfree(nvm) into the auxiliary_device_init() failure path and remove the err goto path to fix below error. " [ 13.232905] ================================================================== [ 13.232911] BUG: KASAN: double-free in xe_nvm_init+0x751/0xf10 [xe] [ 13.233112] Free of addr ffff888120635000 by task systemd-udevd/273 [ 13.233120] CPU: 8 UID: 0 PID: 273 Comm: systemd-udevd Not tainted 6.19.0-rc2-lgci-xe-kernel+ #225 PREEMPT(voluntary) ... [ 13.233125] Call Trace: [ 13.233126] [ 13.233127] dump_stack_lvl+0x7f/0xc0 [ 13.233132] print_report+0xce/0x610 [ 13.233136] ? kasan_complete_mode_report_info+0x5d/0x1e0 [ 13.233139] ? xe_nvm_init+0x751/0xf10 [xe] ... " v2: drop err goto path. (Alexander) Fixes: 7926ba2143d8 ("drm/xe: defer free of NVM auxiliary container to device release callback") Reviewed-by: Nitin Gote Reviewed-by: Brian Nguyen Cc: Alexander Usyskin Cc: Rodrigo Vivi Suggested-by: Brian Nguyen Signed-off-by: Shuicheng Lin Signed-off-by: Ashutosh Dixit Link: https://patch.msgid.link/20260120183239.2966782-7-shuicheng.lin@intel.com (cherry picked from commit a3187c0c2bbd947ffff97f90d077ac88f9c2a215) Signed-off-by: Thomas Hellström Signed-off-by: Sasha Levin --- drivers/gpu/drm/xe/xe_nvm.c | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/drivers/gpu/drm/xe/xe_nvm.c b/drivers/gpu/drm/xe/xe_nvm.c index 1fff24dbc7cd49..6da42b2b5e4673 100644 --- a/drivers/gpu/drm/xe/xe_nvm.c +++ b/drivers/gpu/drm/xe/xe_nvm.c @@ -153,19 +153,17 @@ int xe_nvm_init(struct xe_device *xe) ret = auxiliary_device_init(aux_dev); if (ret) { drm_err(&xe->drm, "xe-nvm aux init failed %d\n", ret); - goto err; + kfree(nvm); + xe->nvm = NULL; + return ret; } ret = auxiliary_device_add(aux_dev); if (ret) { drm_err(&xe->drm, "xe-nvm aux add failed %d\n", ret); auxiliary_device_uninit(aux_dev); - goto err; + xe->nvm = NULL; + return ret; } return devm_add_action_or_reset(xe->drm.dev, xe_nvm_fini, xe); - -err: - kfree(nvm); - xe->nvm = NULL; - return ret; } From 0bbcb7586bc8132d9e968b2fbbb781ce57d04e9c Mon Sep 17 00:00:00 2001 From: Peter Zijlstra Date: Mon, 3 Nov 2025 11:25:52 +0100 Subject: [PATCH 2910/3902] sched/deadline: Document dl_server [ Upstream commit 2614069c5912e9d6f1f57c262face1b368fb8c93 ] Place the notes that resulted from going through the dl_server code in a comment. Signed-off-by: Peter Zijlstra (Intel) Stable-dep-of: 115135422562 ("sched/deadline: Fix 'stuck' dl_server") Signed-off-by: Sasha Levin --- kernel/sched/deadline.c | 194 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 194 insertions(+) diff --git a/kernel/sched/deadline.c b/kernel/sched/deadline.c index 465592fa530ef7..6bfffb244162ff 100644 --- a/kernel/sched/deadline.c +++ b/kernel/sched/deadline.c @@ -1573,6 +1573,200 @@ void dl_server_update(struct sched_dl_entity *dl_se, s64 delta_exec) update_curr_dl_se(dl_se->rq, dl_se, delta_exec); } +/* + * dl_server && dl_defer: + * + * 6 + * +--------------------+ + * v | + * +-------------+ 4 +-----------+ 5 +------------------+ + * +-> | A:init | <--- | D:running | -----> | E:replenish-wait | + * | +-------------+ +-----------+ +------------------+ + * | | | 1 ^ ^ | + * | | 1 +----------+ | 3 | + * | v | | + * | +--------------------------------+ 2 | + * | | | ----+ | + * | 8 | B:zero_laxity-wait | | | + * | | | <---+ | + * | +--------------------------------+ | + * | | ^ ^ 2 | + * | | 7 | 2 +--------------------+ + * | v | + * | +-------------+ | + * +-- | C:idle-wait | -+ + * +-------------+ + * ^ 7 | + * +---------+ + * + * + * [A] - init + * dl_server_active = 0 + * dl_throttled = 0 + * dl_defer_armed = 0 + * dl_defer_running = 0/1 + * dl_defer_idle = 0 + * + * [B] - zero_laxity-wait + * dl_server_active = 1 + * dl_throttled = 1 + * dl_defer_armed = 1 + * dl_defer_running = 0 + * dl_defer_idle = 0 + * + * [C] - idle-wait + * dl_server_active = 1 + * dl_throttled = 1 + * dl_defer_armed = 1 + * dl_defer_running = 0 + * dl_defer_idle = 1 + * + * [D] - running + * dl_server_active = 1 + * dl_throttled = 0 + * dl_defer_armed = 0 + * dl_defer_running = 1 + * dl_defer_idle = 0 + * + * [E] - replenish-wait + * dl_server_active = 1 + * dl_throttled = 1 + * dl_defer_armed = 0 + * dl_defer_running = 1 + * dl_defer_idle = 0 + * + * + * [1] A->B, A->D + * dl_server_start() + * dl_server_active = 1; + * enqueue_dl_entity() + * update_dl_entity(WAKEUP) + * if (!dl_defer_running) + * dl_defer_armed = 1; + * dl_throttled = 1; + * if (dl_throttled && start_dl_timer()) + * return; // [B] + * __enqueue_dl_entity(); + * // [D] + * + * // deplete server runtime from client-class + * [2] B->B, C->B, E->B + * dl_server_update() + * update_curr_dl_se() // idle = false + * if (dl_defer_idle) + * dl_defer_idle = 0; + * if (dl_defer && dl_throttled && dl_runtime_exceeded()) + * dl_defer_running = 0; + * hrtimer_try_to_cancel(); // stop timer + * replenish_dl_new_period() + * // fwd period + * dl_throttled = 1; + * dl_defer_armed = 1; + * start_dl_timer(); // restart timer + * // [B] + * + * // timer actually fires means we have runtime + * [3] B->D + * dl_server_timer() + * if (dl_defer_armed) + * dl_defer_running = 1; + * enqueue_dl_entity(REPLENISH) + * replenish_dl_entity() + * // fwd period + * if (dl_throttled) + * dl_throttled = 0; + * if (dl_defer_armed) + * dl_defer_armed = 0; + * __enqueue_dl_entity(); + * // [D] + * + * // schedule server + * [4] D->A + * pick_task_dl() + * p = server_pick_task(); + * if (!p) + * dl_server_stop() + * dequeue_dl_entity(); + * hrtimer_try_to_cancel(); + * dl_defer_armed = 0; + * dl_throttled = 0; + * dl_server_active = 0; + * // [A] + * return p; + * + * // server running + * [5] D->E + * update_curr_dl_se() + * if (dl_runtime_exceeded()) + * dl_throttled = 1; + * dequeue_dl_entity(); + * start_dl_timer(); + * // [E] + * + * // server replenished + * [6] E->D + * dl_server_timer() + * enqueue_dl_entity(REPLENISH) + * replenish_dl_entity() + * fwd-period + * if (dl_throttled) + * dl_throttled = 0; + * __enqueue_dl_entity(); + * // [D] + * + * // deplete server runtime from idle + * [7] B->C, C->C + * dl_server_update_idle() + * update_curr_dl_se() // idle = true + * if (dl_defer && dl_throttled && dl_runtime_exceeded()) + * if (dl_defer_idle) + * return; + * dl_defer_running = 0; + * hrtimer_try_to_cancel(); + * replenish_dl_new_period() + * // fwd period + * dl_throttled = 1; + * dl_defer_armed = 1; + * dl_defer_idle = 1; + * start_dl_timer(); // restart timer + * // [C] + * + * // stop idle server + * [8] C->A + * dl_server_timer() + * if (dl_defer_idle) + * dl_server_stop(); + * // [A] + * + * + * digraph dl_server { + * "A:init" -> "B:zero_laxity-wait" [label="1:dl_server_start"] + * "A:init" -> "D:running" [label="1:dl_server_start"] + * "B:zero_laxity-wait" -> "B:zero_laxity-wait" [label="2:dl_server_update"] + * "B:zero_laxity-wait" -> "C:idle-wait" [label="7:dl_server_update_idle"] + * "B:zero_laxity-wait" -> "D:running" [label="3:dl_server_timer"] + * "C:idle-wait" -> "A:init" [label="8:dl_server_timer"] + * "C:idle-wait" -> "B:zero_laxity-wait" [label="2:dl_server_update"] + * "C:idle-wait" -> "C:idle-wait" [label="7:dl_server_update_idle"] + * "D:running" -> "A:init" [label="4:pick_task_dl"] + * "D:running" -> "E:replenish-wait" [label="5:update_curr_dl_se"] + * "E:replenish-wait" -> "B:zero_laxity-wait" [label="2:dl_server_update"] + * "E:replenish-wait" -> "D:running" [label="6:dl_server_timer"] + * } + * + * + * Notes: + * + * - When there are fair tasks running the most likely loop is [2]->[2]. + * the dl_server never actually runs, the timer never fires. + * + * - When there is actual fair starvation; the timer fires and starts the + * dl_server. This will then throttle and replenish like a normal DL + * task. Notably it will not 'defer' again. + * + * - When idle it will push the actication forward once, and then wait + * for the timer to hit or a non-idle update to restart things. + */ void dl_server_start(struct sched_dl_entity *dl_se) { struct rq *rq = dl_se->rq; From 36370892e3111ac55042f122bb44e2c9dab3fedf Mon Sep 17 00:00:00 2001 From: Peter Zijlstra Date: Fri, 30 Jan 2026 13:41:00 +0100 Subject: [PATCH 2911/3902] sched/deadline: Fix 'stuck' dl_server [ Upstream commit 115135422562e2f791e98a6f55ec57b2da3b3a95 ] Andrea reported the dl_server getting stuck for him. He tracked it down to a state where dl_server_start() saw dl_defer_running==1, but the dl_server's job is no longer valid at the time of dl_server_start(). In the state diagram this corresponds to [4] D->A (or dl_server_stop() due to no more runnable tasks) followed by [1], which in case of a lapsed deadline must then be A->B. Now our A has dl_defer_running==1, while B demands dl_defer_running==0, therefore it must get cleared when the CBS wakeup rules demand a replenish. Fixes: a110a81c52a9 ("sched/deadline: Deferrable dl server") Reported-by: Andrea Righi arighi@nvidia.com Signed-off-by: Peter Zijlstra (Intel) Acked-by: Juri Lelli Tested-by: Andrea Righi arighi@nvidia.com Link: https://lkml.kernel.org/r/20260123161645.2181752-1-arighi@nvidia.com Link: https://patch.msgid.link/20260130124100.GC1079264@noisy.programming.kicks-ass.net Signed-off-by: Sasha Levin --- kernel/sched/deadline.c | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/kernel/sched/deadline.c b/kernel/sched/deadline.c index 6bfffb244162ff..c7a8717e837dd1 100644 --- a/kernel/sched/deadline.c +++ b/kernel/sched/deadline.c @@ -1043,6 +1043,12 @@ static void update_dl_entity(struct sched_dl_entity *dl_se) return; } + /* + * When [4] D->A is followed by [1] A->B, dl_defer_running + * needs to be cleared, otherwise it will fail to properly + * start the zero-laxity timer. + */ + dl_se->dl_defer_running = 0; replenish_dl_new_period(dl_se, rq); } else if (dl_server(dl_se) && dl_se->dl_defer) { /* @@ -1641,6 +1647,12 @@ void dl_server_update(struct sched_dl_entity *dl_se, s64 delta_exec) * dl_server_active = 1; * enqueue_dl_entity() * update_dl_entity(WAKEUP) + * if (dl_time_before() || dl_entity_overflow()) + * dl_defer_running = 0; + * replenish_dl_new_period(); + * // fwd period + * dl_throttled = 1; + * dl_defer_armed = 1; * if (!dl_defer_running) * dl_defer_armed = 1; * dl_throttled = 1; From b1f41c1f0bbbe07dbc6f0e28bbffe0042db5609b Mon Sep 17 00:00:00 2001 From: Laveesh Bansal Date: Tue, 6 Jan 2026 14:50:58 +0000 Subject: [PATCH 2912/3902] writeback: fix 100% CPU usage when dirtytime_expire_interval is 0 commit 543467d6fe97e27e22a26e367fda972dbefebbff upstream. When vm.dirtytime_expire_seconds is set to 0, wakeup_dirtytime_writeback() schedules delayed work with a delay of 0, causing immediate execution. The function then reschedules itself with 0 delay again, creating an infinite busy loop that causes 100% kworker CPU usage. Fix by: - Only scheduling delayed work in wakeup_dirtytime_writeback() when dirtytime_expire_interval is non-zero - Cancelling the delayed work in dirtytime_interval_handler() when the interval is set to 0 - Adding a guard in start_dirtytime_writeback() for defensive coding Tested by booting kernel in QEMU with virtme-ng: - Before fix: kworker CPU spikes to ~73% - After fix: CPU remains at normal levels - Setting interval back to non-zero correctly resumes writeback Fixes: a2f4870697a5 ("fs: make sure the timestamps for lazytime inodes eventually get written") Cc: stable@vger.kernel.org Closes: https://bugzilla.kernel.org/show_bug.cgi?id=220227 Signed-off-by: Laveesh Bansal Link: https://patch.msgid.link/20260106145059.543282-2-laveeshb@laveeshbansal.com Reviewed-by: Jan Kara Signed-off-by: Christian Brauner Signed-off-by: Greg Kroah-Hartman --- fs/fs-writeback.c | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/fs/fs-writeback.c b/fs/fs-writeback.c index fedccb991674db..21b6adee03dfb8 100644 --- a/fs/fs-writeback.c +++ b/fs/fs-writeback.c @@ -2471,7 +2471,8 @@ static void wakeup_dirtytime_writeback(struct work_struct *w) wb_wakeup(wb); } rcu_read_unlock(); - schedule_delayed_work(&dirtytime_work, dirtytime_expire_interval * HZ); + if (dirtytime_expire_interval) + schedule_delayed_work(&dirtytime_work, dirtytime_expire_interval * HZ); } static int dirtytime_interval_handler(const struct ctl_table *table, int write, @@ -2480,8 +2481,12 @@ static int dirtytime_interval_handler(const struct ctl_table *table, int write, int ret; ret = proc_dointvec_minmax(table, write, buffer, lenp, ppos); - if (ret == 0 && write) - mod_delayed_work(system_percpu_wq, &dirtytime_work, 0); + if (ret == 0 && write) { + if (dirtytime_expire_interval) + mod_delayed_work(system_percpu_wq, &dirtytime_work, 0); + else + cancel_delayed_work_sync(&dirtytime_work); + } return ret; } @@ -2498,7 +2503,8 @@ static const struct ctl_table vm_fs_writeback_table[] = { static int __init start_dirtytime_writeback(void) { - schedule_delayed_work(&dirtytime_work, dirtytime_expire_interval * HZ); + if (dirtytime_expire_interval) + schedule_delayed_work(&dirtytime_work, dirtytime_expire_interval * HZ); register_sysctl_init("vm", vm_fs_writeback_table); return 0; } From e0468c4527a22f0c485a8ece92afda227b702ad5 Mon Sep 17 00:00:00 2001 From: Bartosz Golaszewski Date: Mon, 26 Jan 2026 14:56:27 +0100 Subject: [PATCH 2913/3902] pinctrl: lpass-lpi: implement .get_direction() for the GPIO driver commit 4f0d22ec60cee420125f4055af76caa0f373a3fe upstream. GPIO controller driver should typically implement the .get_direction() callback as GPIOLIB internals may try to use it to determine the state of a pin. Add it for the LPASS LPI driver. Reported-by: Abel Vesa Cc: stable@vger.kernel.org Fixes: 6e261d1090d6 ("pinctrl: qcom: Add sm8250 lpass lpi pinctrl driver") Signed-off-by: Bartosz Golaszewski Reviewed-by: Konrad Dybcio Tested-by: Konrad Dybcio # X1E CRD Tested-by: Abel Vesa Signed-off-by: Linus Walleij Signed-off-by: Greg Kroah-Hartman --- drivers/pinctrl/qcom/pinctrl-lpass-lpi.c | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/drivers/pinctrl/qcom/pinctrl-lpass-lpi.c b/drivers/pinctrl/qcom/pinctrl-lpass-lpi.c index 78212f9928430c..76aed32962794e 100644 --- a/drivers/pinctrl/qcom/pinctrl-lpass-lpi.c +++ b/drivers/pinctrl/qcom/pinctrl-lpass-lpi.c @@ -312,6 +312,22 @@ static const struct pinconf_ops lpi_gpio_pinconf_ops = { .pin_config_group_set = lpi_config_set, }; +static int lpi_gpio_get_direction(struct gpio_chip *chip, unsigned int pin) +{ + unsigned long config = pinconf_to_config_packed(PIN_CONFIG_LEVEL, 0); + struct lpi_pinctrl *state = gpiochip_get_data(chip); + unsigned long arg; + int ret; + + ret = lpi_config_get(state->ctrl, pin, &config); + if (ret) + return ret; + + arg = pinconf_to_config_argument(config); + + return arg ? GPIO_LINE_DIRECTION_OUT : GPIO_LINE_DIRECTION_IN; +} + static int lpi_gpio_direction_input(struct gpio_chip *chip, unsigned int pin) { struct lpi_pinctrl *state = gpiochip_get_data(chip); @@ -409,6 +425,7 @@ static void lpi_gpio_dbg_show(struct seq_file *s, struct gpio_chip *chip) #endif static const struct gpio_chip lpi_gpio_template = { + .get_direction = lpi_gpio_get_direction, .direction_input = lpi_gpio_direction_input, .direction_output = lpi_gpio_direction_output, .get = lpi_gpio_get, From e81d1bc4ea7916f84ace664bc0c1fdc58d816583 Mon Sep 17 00:00:00 2001 From: Bartosz Golaszewski Date: Mon, 5 Jan 2026 16:05:08 +0100 Subject: [PATCH 2914/3902] pinctrl: meson: mark the GPIO controller as sleeping commit 28f24068387169722b508bba6b5257cb68b86e74 upstream. The GPIO controller is configured as non-sleeping but it uses generic pinctrl helpers which use a mutex for synchronization. This can cause the following lockdep splat with shared GPIOs enabled on boards which have multiple devices using the same GPIO: BUG: sleeping function called from invalid context at kernel/locking/mutex.c:591 in_atomic(): 1, irqs_disabled(): 1, non_block: 0, pid: 142, name: kworker/u25:3 preempt_count: 1, expected: 0 RCU nest depth: 0, expected: 0 INFO: lockdep is turned off. irq event stamp: 46379 hardirqs last enabled at (46379): [] _raw_spin_unlock_irqrestore+0x74/0x78 hardirqs last disabled at (46378): [] _raw_spin_lock_irqsave+0x84/0x88 softirqs last enabled at (46330): [] handle_softirqs+0x4c4/0x4dc softirqs last disabled at (46295): [] __do_softirq+0x14/0x20 CPU: 1 UID: 0 PID: 142 Comm: kworker/u25:3 Tainted: G C 6.19.0-rc4-next-20260105+ #11963 PREEMPT Tainted: [C]=CRAP Hardware name: Khadas VIM3 (DT) Workqueue: events_unbound deferred_probe_work_func Call trace: show_stack+0x18/0x24 (C) dump_stack_lvl+0x90/0xd0 dump_stack+0x18/0x24 __might_resched+0x144/0x248 __might_sleep+0x48/0x98 __mutex_lock+0x5c/0x894 mutex_lock_nested+0x24/0x30 pinctrl_get_device_gpio_range+0x44/0x128 pinctrl_gpio_set_config+0x40/0xdc gpiochip_generic_config+0x28/0x3c gpio_do_set_config+0xa8/0x194 gpiod_set_config+0x34/0xfc gpio_shared_proxy_set_config+0x6c/0xfc [gpio_shared_proxy] gpio_do_set_config+0xa8/0x194 gpiod_set_transitory+0x4c/0xf0 gpiod_configure_flags+0xa4/0x480 gpiod_find_and_request+0x1a0/0x574 gpiod_get_index+0x58/0x84 devm_gpiod_get_index+0x20/0xb4 devm_gpiod_get+0x18/0x24 mmc_pwrseq_emmc_probe+0x40/0xb8 platform_probe+0x5c/0xac really_probe+0xbc/0x298 __driver_probe_device+0x78/0x12c driver_probe_device+0xdc/0x164 __device_attach_driver+0xb8/0x138 bus_for_each_drv+0x80/0xdc __device_attach+0xa8/0x1b0 Fixes: 6ac730951104 ("pinctrl: add driver for Amlogic Meson SoCs") Cc: stable@vger.kernel.org Reported-by: Marek Szyprowski Closes: https://lore.kernel.org/all/00107523-7737-4b92-a785-14ce4e93b8cb@samsung.com/ Signed-off-by: Bartosz Golaszewski Reviewed-by: Martin Blumenstingl Reviewed-by: Neil Armstrong Signed-off-by: Linus Walleij Signed-off-by: Greg Kroah-Hartman --- drivers/pinctrl/meson/pinctrl-meson.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/pinctrl/meson/pinctrl-meson.c b/drivers/pinctrl/meson/pinctrl-meson.c index 18295b15ecd9dd..4507dc8b5563c0 100644 --- a/drivers/pinctrl/meson/pinctrl-meson.c +++ b/drivers/pinctrl/meson/pinctrl-meson.c @@ -619,7 +619,7 @@ static int meson_gpiolib_register(struct meson_pinctrl *pc) pc->chip.set = meson_gpio_set; pc->chip.base = -1; pc->chip.ngpio = pc->data->num_pins; - pc->chip.can_sleep = false; + pc->chip.can_sleep = true; ret = gpiochip_add_data(&pc->chip, pc); if (ret) { From 112d4978099700bcf5e3a4a7e571cf672188f3bc Mon Sep 17 00:00:00 2001 From: Krzysztof Kozlowski Date: Thu, 8 Jan 2026 11:07:22 +0100 Subject: [PATCH 2915/3902] pinctrl: qcom: sm8350-lpass-lpi: Merge with SC7280 to fix I2S2 and SWR TX pins commit 1fbe3abb449c5ef2178e1c3e3e8b9a43a7a410ac upstream. Qualcomm SC7280 and SM8350 SoCs have slightly different LPASS audio blocks (v9.4.5 and v9.2), however the LPASS LPI pin controllers are exactly the same. The driver for SM8350 has two issues, which can be fixed by simply moving over to SC7280 driver which has them correct: 1. "i2s2_data_groups" listed twice GPIO12, but should have both GPIO12 and GPIO13, 2. "swr_tx_data_groups" contained GPIO5 for "swr_tx_data2" function, but that function is also available on GPIO14, thus listing it twice is not necessary. OTOH, GPIO5 has also "swr_rx_data1", so selecting swr_rx_data function should not block the TX one. Fixes: be9f6d56381d ("pinctrl: qcom: sm8350-lpass-lpi: add SM8350 LPASS TLMM") Cc: stable@vger.kernel.org Signed-off-by: Krzysztof Kozlowski Reviewed-by: Bartosz Golaszewski Reviewed-by: Konrad Dybcio Signed-off-by: Linus Walleij Signed-off-by: Greg Kroah-Hartman --- arch/arm64/configs/defconfig | 1 - drivers/pinctrl/qcom/Kconfig | 15 +- drivers/pinctrl/qcom/Makefile | 1 - .../pinctrl/qcom/pinctrl-sc7280-lpass-lpi.c | 3 + .../pinctrl/qcom/pinctrl-sm8350-lpass-lpi.c | 151 ------------------ 5 files changed, 6 insertions(+), 165 deletions(-) delete mode 100644 drivers/pinctrl/qcom/pinctrl-sm8350-lpass-lpi.c diff --git a/arch/arm64/configs/defconfig b/arch/arm64/configs/defconfig index 1a48faad2473a9..32861d08a3b827 100644 --- a/arch/arm64/configs/defconfig +++ b/arch/arm64/configs/defconfig @@ -662,7 +662,6 @@ CONFIG_PINCTRL_LPASS_LPI=m CONFIG_PINCTRL_SC7280_LPASS_LPI=m CONFIG_PINCTRL_SM6115_LPASS_LPI=m CONFIG_PINCTRL_SM8250_LPASS_LPI=m -CONFIG_PINCTRL_SM8350_LPASS_LPI=m CONFIG_PINCTRL_SM8450_LPASS_LPI=m CONFIG_PINCTRL_SC8280XP_LPASS_LPI=m CONFIG_PINCTRL_SM8550_LPASS_LPI=m diff --git a/drivers/pinctrl/qcom/Kconfig b/drivers/pinctrl/qcom/Kconfig index c480e8b7850329..f56592411cf6d9 100644 --- a/drivers/pinctrl/qcom/Kconfig +++ b/drivers/pinctrl/qcom/Kconfig @@ -61,13 +61,14 @@ config PINCTRL_LPASS_LPI (Low Power Island) found on the Qualcomm Technologies Inc SoCs. config PINCTRL_SC7280_LPASS_LPI - tristate "Qualcomm Technologies Inc SC7280 LPASS LPI pin controller driver" + tristate "Qualcomm Technologies Inc SC7280 and SM8350 LPASS LPI pin controller driver" depends on ARM64 || COMPILE_TEST depends on PINCTRL_LPASS_LPI help This is the pinctrl, pinmux, pinconf and gpiolib driver for the Qualcomm Technologies Inc LPASS (Low Power Audio SubSystem) LPI - (Low Power Island) found on the Qualcomm Technologies Inc SC7280 platform. + (Low Power Island) found on the Qualcomm Technologies Inc SC7280 + and SM8350 platforms. config PINCTRL_SDM660_LPASS_LPI tristate "Qualcomm Technologies Inc SDM660 LPASS LPI pin controller driver" @@ -106,16 +107,6 @@ config PINCTRL_SM8250_LPASS_LPI Qualcomm Technologies Inc LPASS (Low Power Audio SubSystem) LPI (Low Power Island) found on the Qualcomm Technologies Inc SM8250 platform. -config PINCTRL_SM8350_LPASS_LPI - tristate "Qualcomm Technologies Inc SM8350 LPASS LPI pin controller driver" - depends on ARM64 || COMPILE_TEST - depends on PINCTRL_LPASS_LPI - help - This is the pinctrl, pinmux, pinconf and gpiolib driver for the - Qualcomm Technologies Inc LPASS (Low Power Audio SubSystem) LPI - (Low Power Island) found on the Qualcomm Technologies Inc SM8350 - platform. - config PINCTRL_SM8450_LPASS_LPI tristate "Qualcomm Technologies Inc SM8450 LPASS LPI pin controller driver" depends on ARM64 || COMPILE_TEST diff --git a/drivers/pinctrl/qcom/Makefile b/drivers/pinctrl/qcom/Makefile index 567d3051e760dd..9d7ada203d4cbf 100644 --- a/drivers/pinctrl/qcom/Makefile +++ b/drivers/pinctrl/qcom/Makefile @@ -63,7 +63,6 @@ obj-$(CONFIG_PINCTRL_SM8150) += pinctrl-sm8150.o obj-$(CONFIG_PINCTRL_SM8250) += pinctrl-sm8250.o obj-$(CONFIG_PINCTRL_SM8250_LPASS_LPI) += pinctrl-sm8250-lpass-lpi.o obj-$(CONFIG_PINCTRL_SM8350) += pinctrl-sm8350.o -obj-$(CONFIG_PINCTRL_SM8350_LPASS_LPI) += pinctrl-sm8350-lpass-lpi.o obj-$(CONFIG_PINCTRL_SM8450) += pinctrl-sm8450.o obj-$(CONFIG_PINCTRL_SM8450_LPASS_LPI) += pinctrl-sm8450-lpass-lpi.o obj-$(CONFIG_PINCTRL_SM8550) += pinctrl-sm8550.o diff --git a/drivers/pinctrl/qcom/pinctrl-sc7280-lpass-lpi.c b/drivers/pinctrl/qcom/pinctrl-sc7280-lpass-lpi.c index 1161f0a91a002a..750f410311a8fb 100644 --- a/drivers/pinctrl/qcom/pinctrl-sc7280-lpass-lpi.c +++ b/drivers/pinctrl/qcom/pinctrl-sc7280-lpass-lpi.c @@ -131,6 +131,9 @@ static const struct of_device_id lpi_pinctrl_of_match[] = { { .compatible = "qcom,sc7280-lpass-lpi-pinctrl", .data = &sc7280_lpi_data, + }, { + .compatible = "qcom,sm8350-lpass-lpi-pinctrl", + .data = &sc7280_lpi_data, }, { } }; diff --git a/drivers/pinctrl/qcom/pinctrl-sm8350-lpass-lpi.c b/drivers/pinctrl/qcom/pinctrl-sm8350-lpass-lpi.c deleted file mode 100644 index 7b146b4acfdf42..00000000000000 --- a/drivers/pinctrl/qcom/pinctrl-sm8350-lpass-lpi.c +++ /dev/null @@ -1,151 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-only -/* - * Copyright (c) 2016-2019, The Linux Foundation. All rights reserved. - * Copyright (c) 2020-2023 Linaro Ltd. - */ - -#include -#include -#include - -#include "pinctrl-lpass-lpi.h" - -enum lpass_lpi_functions { - LPI_MUX_dmic1_clk, - LPI_MUX_dmic1_data, - LPI_MUX_dmic2_clk, - LPI_MUX_dmic2_data, - LPI_MUX_dmic3_clk, - LPI_MUX_dmic3_data, - LPI_MUX_i2s1_clk, - LPI_MUX_i2s1_data, - LPI_MUX_i2s1_ws, - LPI_MUX_i2s2_clk, - LPI_MUX_i2s2_data, - LPI_MUX_i2s2_ws, - LPI_MUX_qua_mi2s_data, - LPI_MUX_qua_mi2s_sclk, - LPI_MUX_qua_mi2s_ws, - LPI_MUX_swr_rx_clk, - LPI_MUX_swr_rx_data, - LPI_MUX_swr_tx_clk, - LPI_MUX_swr_tx_data, - LPI_MUX_wsa_swr_clk, - LPI_MUX_wsa_swr_data, - LPI_MUX_gpio, - LPI_MUX__, -}; - -static const struct pinctrl_pin_desc sm8350_lpi_pins[] = { - PINCTRL_PIN(0, "gpio0"), - PINCTRL_PIN(1, "gpio1"), - PINCTRL_PIN(2, "gpio2"), - PINCTRL_PIN(3, "gpio3"), - PINCTRL_PIN(4, "gpio4"), - PINCTRL_PIN(5, "gpio5"), - PINCTRL_PIN(6, "gpio6"), - PINCTRL_PIN(7, "gpio7"), - PINCTRL_PIN(8, "gpio8"), - PINCTRL_PIN(9, "gpio9"), - PINCTRL_PIN(10, "gpio10"), - PINCTRL_PIN(11, "gpio11"), - PINCTRL_PIN(12, "gpio12"), - PINCTRL_PIN(13, "gpio13"), - PINCTRL_PIN(14, "gpio14"), -}; - -static const char * const swr_tx_clk_groups[] = { "gpio0" }; -static const char * const swr_tx_data_groups[] = { "gpio1", "gpio2", "gpio5", "gpio14" }; -static const char * const swr_rx_clk_groups[] = { "gpio3" }; -static const char * const swr_rx_data_groups[] = { "gpio4", "gpio5" }; -static const char * const dmic1_clk_groups[] = { "gpio6" }; -static const char * const dmic1_data_groups[] = { "gpio7" }; -static const char * const dmic2_clk_groups[] = { "gpio8" }; -static const char * const dmic2_data_groups[] = { "gpio9" }; -static const char * const i2s2_clk_groups[] = { "gpio10" }; -static const char * const i2s2_ws_groups[] = { "gpio11" }; -static const char * const dmic3_clk_groups[] = { "gpio12" }; -static const char * const dmic3_data_groups[] = { "gpio13" }; -static const char * const qua_mi2s_sclk_groups[] = { "gpio0" }; -static const char * const qua_mi2s_ws_groups[] = { "gpio1" }; -static const char * const qua_mi2s_data_groups[] = { "gpio2", "gpio3", "gpio4" }; -static const char * const i2s1_clk_groups[] = { "gpio6" }; -static const char * const i2s1_ws_groups[] = { "gpio7" }; -static const char * const i2s1_data_groups[] = { "gpio8", "gpio9" }; -static const char * const wsa_swr_clk_groups[] = { "gpio10" }; -static const char * const wsa_swr_data_groups[] = { "gpio11" }; -static const char * const i2s2_data_groups[] = { "gpio12", "gpio12" }; - -static const struct lpi_pingroup sm8350_groups[] = { - LPI_PINGROUP(0, 0, swr_tx_clk, qua_mi2s_sclk, _, _), - LPI_PINGROUP(1, 2, swr_tx_data, qua_mi2s_ws, _, _), - LPI_PINGROUP(2, 4, swr_tx_data, qua_mi2s_data, _, _), - LPI_PINGROUP(3, 8, swr_rx_clk, qua_mi2s_data, _, _), - LPI_PINGROUP(4, 10, swr_rx_data, qua_mi2s_data, _, _), - LPI_PINGROUP(5, 12, swr_tx_data, swr_rx_data, _, _), - LPI_PINGROUP(6, LPI_NO_SLEW, dmic1_clk, i2s1_clk, _, _), - LPI_PINGROUP(7, LPI_NO_SLEW, dmic1_data, i2s1_ws, _, _), - LPI_PINGROUP(8, LPI_NO_SLEW, dmic2_clk, i2s1_data, _, _), - LPI_PINGROUP(9, LPI_NO_SLEW, dmic2_data, i2s1_data, _, _), - LPI_PINGROUP(10, 16, i2s2_clk, wsa_swr_clk, _, _), - LPI_PINGROUP(11, 18, i2s2_ws, wsa_swr_data, _, _), - LPI_PINGROUP(12, LPI_NO_SLEW, dmic3_clk, i2s2_data, _, _), - LPI_PINGROUP(13, LPI_NO_SLEW, dmic3_data, i2s2_data, _, _), - LPI_PINGROUP(14, 6, swr_tx_data, _, _, _), -}; - -static const struct lpi_function sm8350_functions[] = { - LPI_FUNCTION(dmic1_clk), - LPI_FUNCTION(dmic1_data), - LPI_FUNCTION(dmic2_clk), - LPI_FUNCTION(dmic2_data), - LPI_FUNCTION(dmic3_clk), - LPI_FUNCTION(dmic3_data), - LPI_FUNCTION(i2s1_clk), - LPI_FUNCTION(i2s1_data), - LPI_FUNCTION(i2s1_ws), - LPI_FUNCTION(i2s2_clk), - LPI_FUNCTION(i2s2_data), - LPI_FUNCTION(i2s2_ws), - LPI_FUNCTION(qua_mi2s_data), - LPI_FUNCTION(qua_mi2s_sclk), - LPI_FUNCTION(qua_mi2s_ws), - LPI_FUNCTION(swr_rx_clk), - LPI_FUNCTION(swr_rx_data), - LPI_FUNCTION(swr_tx_clk), - LPI_FUNCTION(swr_tx_data), - LPI_FUNCTION(wsa_swr_clk), - LPI_FUNCTION(wsa_swr_data), -}; - -static const struct lpi_pinctrl_variant_data sm8350_lpi_data = { - .pins = sm8350_lpi_pins, - .npins = ARRAY_SIZE(sm8350_lpi_pins), - .groups = sm8350_groups, - .ngroups = ARRAY_SIZE(sm8350_groups), - .functions = sm8350_functions, - .nfunctions = ARRAY_SIZE(sm8350_functions), -}; - -static const struct of_device_id lpi_pinctrl_of_match[] = { - { - .compatible = "qcom,sm8350-lpass-lpi-pinctrl", - .data = &sm8350_lpi_data, - }, - { } -}; -MODULE_DEVICE_TABLE(of, lpi_pinctrl_of_match); - -static struct platform_driver lpi_pinctrl_driver = { - .driver = { - .name = "qcom-sm8350-lpass-lpi-pinctrl", - .of_match_table = lpi_pinctrl_of_match, - }, - .probe = lpi_pinctrl_probe, - .remove = lpi_pinctrl_remove, -}; -module_platform_driver(lpi_pinctrl_driver); - -MODULE_AUTHOR("Krzysztof Kozlowski "); -MODULE_DESCRIPTION("QTI SM8350 LPI GPIO pin control driver"); -MODULE_LICENSE("GPL"); From a28fce0365e1cb9cb8c04c893b9334e5ca9d9f1c Mon Sep 17 00:00:00 2001 From: Steven Rostedt Date: Thu, 29 Jan 2026 10:28:21 -0500 Subject: [PATCH 2916/3902] perf: sched: Fix perf crash with new is_user_task() helper commit 76ed27608f7dd235b727ebbb12163438c2fbb617 upstream. In order to do a user space stacktrace the current task needs to be a user task that has executed in user space. It use to be possible to test if a task is a user task or not by simply checking the task_struct mm field. If it was non NULL, it was a user task and if not it was a kernel task. But things have changed over time, and some kernel tasks now have their own mm field. An idea was made to instead test PF_KTHREAD and two functions were used to wrap this check in case it became more complex to test if a task was a user task or not[1]. But this was rejected and the C code simply checked the PF_KTHREAD directly. It was later found that not all kernel threads set PF_KTHREAD. The io-uring helpers instead set PF_USER_WORKER and this needed to be added as well. But checking the flags is still not enough. There's a very small window when a task exits that it frees its mm field and it is set back to NULL. If perf were to trigger at this moment, the flags test would say its a user space task but when perf would read the mm field it would crash with at NULL pointer dereference. Now there are flags that can be used to test if a task is exiting, but they are set in areas that perf may still want to profile the user space task (to see where it exited). The only real test is to check both the flags and the mm field. Instead of making this modification in every location, create a new is_user_task() helper function that does all the tests needed to know if it is safe to read the user space memory or not. [1] https://lore.kernel.org/all/20250425204120.639530125@goodmis.org/ Fixes: 90942f9fac05 ("perf: Use current->flags & PF_KTHREAD|PF_USER_WORKER instead of current->mm == NULL") Closes: https://lore.kernel.org/all/0d877e6f-41a7-4724-875d-0b0a27b8a545@roeck-us.net/ Reported-by: Guenter Roeck Signed-off-by: Steven Rostedt (Google) Signed-off-by: Peter Zijlstra (Intel) Tested-by: Guenter Roeck Cc: stable@vger.kernel.org Link: https://patch.msgid.link/20260129102821.46484722@gandalf.local.home Signed-off-by: Greg Kroah-Hartman --- include/linux/sched.h | 5 +++++ kernel/events/callchain.c | 2 +- kernel/events/core.c | 6 +++--- 3 files changed, 9 insertions(+), 4 deletions(-) diff --git a/include/linux/sched.h b/include/linux/sched.h index b469878de25c8a..6ad294330c0b6e 100644 --- a/include/linux/sched.h +++ b/include/linux/sched.h @@ -1798,6 +1798,11 @@ static __always_inline bool is_percpu_thread(void) (current->nr_cpus_allowed == 1); } +static __always_inline bool is_user_task(struct task_struct *task) +{ + return task->mm && !(task->flags & (PF_KTHREAD | PF_USER_WORKER)); +} + /* Per-process atomic flags. */ #define PFA_NO_NEW_PRIVS 0 /* May not gain new privileges. */ #define PFA_SPREAD_PAGE 1 /* Spread page cache over cpuset */ diff --git a/kernel/events/callchain.c b/kernel/events/callchain.c index 808c0d7a31faf0..575154dbd23194 100644 --- a/kernel/events/callchain.c +++ b/kernel/events/callchain.c @@ -246,7 +246,7 @@ get_perf_callchain(struct pt_regs *regs, bool kernel, bool user, if (user && !crosstask) { if (!user_mode(regs)) { - if (current->flags & (PF_KTHREAD | PF_USER_WORKER)) + if (!is_user_task(current)) goto exit_put; regs = task_pt_regs(current); } diff --git a/kernel/events/core.c b/kernel/events/core.c index df0717f4592a9b..1d8ca8e34f5c4e 100644 --- a/kernel/events/core.c +++ b/kernel/events/core.c @@ -7459,7 +7459,7 @@ static void perf_sample_regs_user(struct perf_regs *regs_user, if (user_mode(regs)) { regs_user->abi = perf_reg_abi(current); regs_user->regs = regs; - } else if (!(current->flags & (PF_KTHREAD | PF_USER_WORKER))) { + } else if (is_user_task(current)) { perf_get_regs_user(regs_user, regs); } else { regs_user->abi = PERF_SAMPLE_REGS_ABI_NONE; @@ -8099,7 +8099,7 @@ static u64 perf_virt_to_phys(u64 virt) * Try IRQ-safe get_user_page_fast_only first. * If failed, leave phys_addr as 0. */ - if (!(current->flags & (PF_KTHREAD | PF_USER_WORKER))) { + if (is_user_task(current)) { struct page *p; pagefault_disable(); @@ -8212,7 +8212,7 @@ perf_callchain(struct perf_event *event, struct pt_regs *regs) { bool kernel = !event->attr.exclude_callchain_kernel; bool user = !event->attr.exclude_callchain_user && - !(current->flags & (PF_KTHREAD | PF_USER_WORKER)); + is_user_task(current); /* Disallow cross-task user callchains. */ bool crosstask = event->ctx->task && event->ctx->task != current; const u32 max_stack = event->attr.sample_max_stack; From b038874e31fc3caa0b0d5abd259dd54b918ad4a1 Mon Sep 17 00:00:00 2001 From: Takashi Sakamoto Date: Wed, 28 Jan 2026 07:34:13 +0900 Subject: [PATCH 2917/3902] firewire: core: fix race condition against transaction list commit 20e01bba2ae4898ce65cdcacd1bd6bec5111abd9 upstream. The list of transaction is enumerated without acquiring card lock when processing AR response event. This causes a race condition bug when processing AT request completion event concurrently. This commit fixes the bug by put timer start for split transaction expiration into the scope of lock. The value of jiffies in card structure is referred before acquiring the lock. Cc: stable@vger.kernel.org # v6.18 Fixes: b5725cfa4120 ("firewire: core: use spin lock specific to timer for split transaction") Reported-by: Andreas Persson Closes: https://github.com/alsa-project/snd-firewire-ctl-services/issues/209 Tested-by: Andreas Persson Link: https://lore.kernel.org/r/20260127223413.22265-1-o-takashi@sakamocchi.jp Signed-off-by: Takashi Sakamoto Signed-off-by: Greg Kroah-Hartman --- drivers/firewire/core-transaction.c | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/drivers/firewire/core-transaction.c b/drivers/firewire/core-transaction.c index c65f491c54d063..2e797430332c07 100644 --- a/drivers/firewire/core-transaction.c +++ b/drivers/firewire/core-transaction.c @@ -134,20 +134,14 @@ static void split_transaction_timeout_callback(struct timer_list *timer) } } -static void start_split_transaction_timeout(struct fw_transaction *t, - struct fw_card *card) +// card->transactions.lock should be acquired in advance for the linked list. +static void start_split_transaction_timeout(struct fw_transaction *t, unsigned int delta) { - unsigned long delta; - if (list_empty(&t->link) || WARN_ON(t->is_split_transaction)) return; t->is_split_transaction = true; - // NOTE: This can be without irqsave when we can guarantee that __fw_send_request() for - // local destination never runs in any type of IRQ context. - scoped_guard(spinlock_irqsave, &card->split_timeout.lock) - delta = card->split_timeout.jiffies; mod_timer(&t->split_timeout_timer, jiffies + delta); } @@ -168,13 +162,20 @@ static void transmit_complete_callback(struct fw_packet *packet, break; case ACK_PENDING: { + unsigned int delta; + // NOTE: This can be without irqsave when we can guarantee that __fw_send_request() for // local destination never runs in any type of IRQ context. scoped_guard(spinlock_irqsave, &card->split_timeout.lock) { t->split_timeout_cycle = compute_split_timeout_timestamp(card, packet->timestamp) & 0xffff; + delta = card->split_timeout.jiffies; } - start_split_transaction_timeout(t, card); + + // NOTE: This can be without irqsave when we can guarantee that __fw_send_request() for + // local destination never runs in any type of IRQ context. + scoped_guard(spinlock_irqsave, &card->transactions.lock) + start_split_transaction_timeout(t, delta); break; } case ACK_BUSY_X: From fe9a1a825b4aef5f877b78a69989b084f08881e9 Mon Sep 17 00:00:00 2001 From: Han Gao Date: Wed, 28 Jan 2026 03:07:11 +0800 Subject: [PATCH 2918/3902] riscv: compat: fix COMPAT_UTS_MACHINE definition commit 0ea05c4f7527a98f5946f96c829733788934311d upstream. The COMPAT_UTS_MACHINE for riscv was incorrectly defined as "riscv". Change it to "riscv32" to reflect the correct 32-bit compat name. Fixes: 06d0e3723647 ("riscv: compat: Add basic compat data type implementation") Cc: stable@vger.kernel.org Signed-off-by: Han Gao Reviewed-by: Guo Ren (Alibaba Damo Academy) Link: https://patch.msgid.link/20260127190711.2264664-1-gaohan@iscas.ac.cn Signed-off-by: Paul Walmsley Signed-off-by: Greg Kroah-Hartman --- arch/riscv/include/asm/compat.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/arch/riscv/include/asm/compat.h b/arch/riscv/include/asm/compat.h index 6081327e55f5b6..28e115eed21805 100644 --- a/arch/riscv/include/asm/compat.h +++ b/arch/riscv/include/asm/compat.h @@ -2,7 +2,7 @@ #ifndef __ASM_COMPAT_H #define __ASM_COMPAT_H -#define COMPAT_UTS_MACHINE "riscv\0\0" +#define COMPAT_UTS_MACHINE "riscv32\0\0" /* * Architecture specific compatibility types From 50c66cb08070cb5ed8716c19a754b42a2900095f Mon Sep 17 00:00:00 2001 From: Hang Shu Date: Fri, 7 Nov 2025 09:39:17 +0000 Subject: [PATCH 2919/3902] rust: rbtree: fix documentation typo in CursorMut peek_next method commit 45f6aed8a835ee2bdd0a5d5ee626a91fe285014f upstream. The peek_next method's doc comment incorrectly stated it accesses the "previous" node when it actually accesses the next node. Fix the documentation to accurately reflect the method's behavior. Fixes: 98c14e40e07a ("rust: rbtree: add cursor") Reviewed-by: Alice Ryhl Signed-off-by: Hang Shu Reported-by: Miguel Ojeda Closes: https://github.com/Rust-for-Linux/linux/issues/1205 Cc: stable@vger.kernel.org Reviewed-by: Gary Guo Link: https://patch.msgid.link/20251107093921.3379954-1-m18080292938@163.com [ Reworded slightly. - Miguel ] Signed-off-by: Miguel Ojeda Signed-off-by: Greg Kroah-Hartman --- rust/kernel/rbtree.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rust/kernel/rbtree.rs b/rust/kernel/rbtree.rs index b8fe6be6fcc41a..9e178dacddf1d0 100644 --- a/rust/kernel/rbtree.rs +++ b/rust/kernel/rbtree.rs @@ -835,7 +835,7 @@ impl<'a, K, V> Cursor<'a, K, V> { self.peek(Direction::Prev) } - /// Access the previous node without moving the cursor. + /// Access the next node without moving the cursor. pub fn peek_next(&self) -> Option<(&K, &V)> { self.peek(Direction::Next) } From 8b581837295fcfb775685aa776d5f2ca70f9004f Mon Sep 17 00:00:00 2001 From: Miguel Ojeda Date: Thu, 15 Jan 2026 19:38:32 +0100 Subject: [PATCH 2920/3902] rust: kbuild: give `--config-path` to `rustfmt` in `.rsi` target commit af20ae33e7dd949f2e770198e74ac8f058cb299d upstream. `rustfmt` is configured via the `.rustfmt.toml` file in the source tree, and we apply `rustfmt` to the macro expanded sources generated by the `.rsi` target. However, under an `O=` pointing to an external folder (i.e. not just a subdir), `rustfmt` will not find the file when checking the parent folders. Since the edition is configured in this file, this can lead to errors when it encounters newer syntax, e.g. error: expected one of `!`, `.`, `::`, `;`, `?`, `where`, `{`, or an operator, found `"rust_minimal"` --> samples/rust/rust_minimal.rsi:29:49 | 28 | impl ::kernel::ModuleMetadata for RustMinimal { | - while parsing this item list starting here 29 | const NAME: &'static ::kernel::str::CStr = c"rust_minimal"; | ^^^^^^^^^^^^^^ expected one of 8 possible tokens 30 | } | - the item list ends here | = note: you may be trying to write a c-string literal = note: c-string literals require Rust 2021 or later = help: pass `--edition 2024` to `rustc` = note: for more on editions, read https://doc.rust-lang.org/edition-guide A workaround is to use `RUSTFMT=n`, which is documented in the `Makefile` help for cases where macro expanded source may happen to break `rustfmt` for other reasons, but this is not one of those cases. One solution would be to pass `--edition`, but we want `rustfmt` to use the entire configuration, even if currently we essentially use the default configuration. Thus explicitly give the path to the config file to `rustfmt` instead. Reported-by: Alice Ryhl Fixes: 2f7ab1267dc9 ("Kbuild: add Rust support") Cc: stable@vger.kernel.org Reviewed-by: Nathan Chancellor Reviewed-by: Gary Guo Link: https://patch.msgid.link/20260115183832.46595-1-ojeda@kernel.org Signed-off-by: Miguel Ojeda Signed-off-by: Greg Kroah-Hartman --- scripts/Makefile.build | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/Makefile.build b/scripts/Makefile.build index 5037f4715d7491..0c838c467c764e 100644 --- a/scripts/Makefile.build +++ b/scripts/Makefile.build @@ -356,7 +356,7 @@ $(obj)/%.o: $(obj)/%.rs FORCE quiet_cmd_rustc_rsi_rs = $(RUSTC_OR_CLIPPY_QUIET) $(quiet_modtag) $@ cmd_rustc_rsi_rs = \ $(rust_common_cmd) -Zunpretty=expanded $< >$@; \ - command -v $(RUSTFMT) >/dev/null && $(RUSTFMT) $@ + command -v $(RUSTFMT) >/dev/null && $(RUSTFMT) --config-path $(srctree)/.rustfmt.toml $@ $(obj)/%.rsi: $(obj)/%.rs FORCE +$(call if_changed_dep,rustc_rsi_rs) From 91539cf522ed0b905113c5d1dbc8f50d84a40d96 Mon Sep 17 00:00:00 2001 From: Fabio Estevam Date: Sun, 18 Jan 2026 17:50:30 -0300 Subject: [PATCH 2921/3902] ASoC: fsl: imx-card: Do not force slot width to sample width commit 9210f5ff6318163835d9e42ee68006be4da0f531 upstream. imx-card currently sets the slot width to the physical sample width for I2S links. This breaks controllers that use fixed-width slots (e.g. 32-bit FIFO words), causing the unused bits in the slot to contain undefined data when playing 16-bit streams. Do not override the slot width in the machine driver and let the CPU DAI select an appropriate default instead. This matches the behavior of simple-audio-card and avoids embedding controller-specific policy in the machine driver. On an i.MX8MP-based board using SAI as the I2S master with 32-bit slots, playing 16-bit audio resulted in spurious frequencies and an incorrect SAI data waveform, as the slot width was forced to 16 bits. After this change, audio artifacts are eliminated and the 16-bit samples correctly occupy the first half of the 32-bit slot, with the remaining bits padded with zeroes. Cc: stable@vger.kernel.org Fixes: aa736700f42f ("ASoC: imx-card: Add imx-card machine driver") Signed-off-by: Fabio Estevam Acked-by: Shengjiu Wang Link: https://patch.msgid.link/20260118205030.1532696-1-festevam@gmail.com Signed-off-by: Mark Brown Signed-off-by: Greg Kroah-Hartman --- sound/soc/fsl/imx-card.c | 1 - 1 file changed, 1 deletion(-) diff --git a/sound/soc/fsl/imx-card.c b/sound/soc/fsl/imx-card.c index 28699d7b75ca04..05b4e971a36618 100644 --- a/sound/soc/fsl/imx-card.c +++ b/sound/soc/fsl/imx-card.c @@ -346,7 +346,6 @@ static int imx_aif_hw_params(struct snd_pcm_substream *substream, SND_SOC_DAIFMT_PDM; } else { slots = 2; - slot_width = params_physical_width(params); fmt = (rtd->dai_link->dai_fmt & ~SND_SOC_DAIFMT_FORMAT_MASK) | SND_SOC_DAIFMT_I2S; } From 98e0bc206a32180e01913c33b3d5c74c7127756b Mon Sep 17 00:00:00 2001 From: Haoxiang Li Date: Sat, 13 Dec 2025 16:36:43 +0800 Subject: [PATCH 2922/3902] scsi: be2iscsi: Fix a memory leak in beiscsi_boot_get_sinfo() commit 4747bafaa50115d9667ece446b1d2d4aba83dc7f upstream. If nonemb_cmd->va fails to be allocated, free the allocation previously made by alloc_mcc_wrb(). Fixes: 50a4b824be9e ("scsi: be2iscsi: Fix to make boot discovery non-blocking") Cc: stable@vger.kernel.org Signed-off-by: Haoxiang Li Link: https://patch.msgid.link/20251213083643.301240-1-lihaoxiang@isrc.iscas.ac.cn Signed-off-by: Martin K. Petersen Signed-off-by: Greg Kroah-Hartman --- drivers/scsi/be2iscsi/be_mgmt.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/scsi/be2iscsi/be_mgmt.c b/drivers/scsi/be2iscsi/be_mgmt.c index 4e899ec1477d46..b1cba986f0fbd3 100644 --- a/drivers/scsi/be2iscsi/be_mgmt.c +++ b/drivers/scsi/be2iscsi/be_mgmt.c @@ -1025,6 +1025,7 @@ unsigned int beiscsi_boot_get_sinfo(struct beiscsi_hba *phba) &nonemb_cmd->dma, GFP_KERNEL); if (!nonemb_cmd->va) { + free_mcc_wrb(ctrl, tag); mutex_unlock(&ctrl->mbox_lock); return 0; } From cb69b0a0c0aaf25442ca1f41ccfd6bc36b919187 Mon Sep 17 00:00:00 2001 From: Zhang Heng Date: Mon, 26 Jan 2026 09:49:52 +0800 Subject: [PATCH 2923/3902] ASoC: amd: yc: Add DMI quirk for Acer TravelMate P216-41-TCO commit 9502b7df5a3c7e174f74f20324ac1fe781fc5c2d upstream. Add a DMI quirk for the Acer TravelMate P216-41-TCO fixing the issue where the internal microphone was not detected. Link: https://bugzilla.kernel.org/show_bug.cgi?id=220983 Cc: stable@vger.kernel.org Signed-off-by: Zhang Heng Link: https://patch.msgid.link/20260126014952.3674450-1-zhangheng@kylinos.cn Signed-off-by: Mark Brown Signed-off-by: Greg Kroah-Hartman --- sound/soc/amd/yc/acp6x-mach.c | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/sound/soc/amd/yc/acp6x-mach.c b/sound/soc/amd/yc/acp6x-mach.c index bf4d9d33656173..c0a8afb42e1654 100644 --- a/sound/soc/amd/yc/acp6x-mach.c +++ b/sound/soc/amd/yc/acp6x-mach.c @@ -668,6 +668,14 @@ static const struct dmi_system_id yc_acp_quirk_table[] = { DMI_MATCH(DMI_PRODUCT_NAME, "GOH-X"), } }, + { + .driver_data = &acp6x_card, + .matches = { + DMI_MATCH(DMI_BOARD_VENDOR, "RB"), + DMI_MATCH(DMI_BOARD_NAME, "XyloD5_RBU"), + } + }, + {} }; From e4c0a92146ddf8d0f283929f122716b33bd1c5b2 Mon Sep 17 00:00:00 2001 From: Martin Larsson Date: Wed, 21 Jan 2026 12:57:22 +0000 Subject: [PATCH 2924/3902] gpio: pca953x: mask interrupts in irq shutdown commit d02f20a4de0c498fbba2b0e3c1496e72c630a91e upstream. In the existing implementation irq_shutdown does not mask the interrupts in hardware. This can cause spurious interrupts from the IO expander. Add masking to irq_shutdown to prevent spurious interrupts. Cc: stable@vger.kernel.org Signed-off-by: Martin Larsson Reviewed-by: Linus Walleij Link: https://lore.kernel.org/r/20260121125631.2758346-1-martin.larsson@actia.se Signed-off-by: Bartosz Golaszewski Signed-off-by: Greg Kroah-Hartman --- drivers/gpio/gpio-pca953x.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/drivers/gpio/gpio-pca953x.c b/drivers/gpio/gpio-pca953x.c index b2de916107f425..34cadce9b31017 100644 --- a/drivers/gpio/gpio-pca953x.c +++ b/drivers/gpio/gpio-pca953x.c @@ -911,6 +911,8 @@ static void pca953x_irq_shutdown(struct irq_data *d) clear_bit(hwirq, chip->irq_trig_fall); clear_bit(hwirq, chip->irq_trig_level_low); clear_bit(hwirq, chip->irq_trig_level_high); + + pca953x_irq_mask(d); } static void pca953x_irq_print_chip(struct irq_data *data, struct seq_file *p) From 5ba425a7bab31fdc7669f06b3b0f87f03d97696b Mon Sep 17 00:00:00 2001 From: Chen Miao Date: Fri, 31 Oct 2025 02:32:39 +0000 Subject: [PATCH 2925/3902] kbuild: rust: clean libpin_init_internal in mrproper commit a44bfed9df8a514962e2cb076d9c0b594caeff36 upstream. When I enabled Rust compilation, I wanted to clean up its output, so I used make mrproper. However, I was still able to find that libpin_init_internal.so in the rust directory was not deleted, while all other corresponding outputs were cleared. Thus add it to the `MRPROPER_FILES` list. Reviewed-by: Dongliang Mu Signed-off-by: Chen Miao Fixes: d7659acca7a3 ("rust: add pin-init crate build infrastructure") Cc: stable@vger.kernel.org Acked-by: Nicolas Schier Acked-by: Benno Lossin Link: https://patch.msgid.link/71ff222b8731e63e06059c5d8566434e508baf2b.1761876365.git.chenmiao@openatom.club [ Fixed tags and Git author as discussed. Reworded slightly. - Miguel ] Signed-off-by: Miguel Ojeda Signed-off-by: Greg Kroah-Hartman --- Makefile | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 6b6539f2f11df4..5767f0a7d07ae1 100644 --- a/Makefile +++ b/Makefile @@ -1590,7 +1590,8 @@ MRPROPER_FILES += include/config include/generated \ certs/x509.genkey \ vmlinux-gdb.py \ rpmbuild \ - rust/libmacros.so rust/libmacros.dylib + rust/libmacros.so rust/libmacros.dylib \ + rust/libpin_init_internal.so rust/libpin_init_internal.dylib # clean - Delete most, but leave enough to build external modules # From 14ca9fa020829cfa7d43846c241058961f5ac79b Mon Sep 17 00:00:00 2001 From: Thomas Fourier Date: Mon, 12 Jan 2026 14:43:24 +0100 Subject: [PATCH 2926/3902] scsi: qla2xxx: edif: Fix dma_free_coherent() size commit 56bd3c0f749f45793d1eae1d0ddde4255c749bf6 upstream. Earlier in the function, the ha->flt buffer is allocated with size sizeof(struct qla_flt_header) + FLT_REGIONS_SIZE but freed in the error path with size SFP_DEV_SIZE. Fixes: 84318a9f01ce ("scsi: qla2xxx: edif: Add send, receive, and accept for auth_els") Cc: stable@vger.kernel.org Signed-off-by: Thomas Fourier Link: https://patch.msgid.link/20260112134326.55466-2-fourier.thomas@gmail.com Signed-off-by: Martin K. Petersen Signed-off-by: Greg Kroah-Hartman --- drivers/scsi/qla2xxx/qla_os.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/scsi/qla2xxx/qla_os.c b/drivers/scsi/qla2xxx/qla_os.c index fd32b30a5b38b2..3d814262040ac8 100644 --- a/drivers/scsi/qla2xxx/qla_os.c +++ b/drivers/scsi/qla2xxx/qla_os.c @@ -4488,7 +4488,7 @@ qla2x00_mem_alloc(struct qla_hw_data *ha, uint16_t req_len, uint16_t rsp_len, fail_elsrej: dma_pool_destroy(ha->purex_dma_pool); fail_flt: - dma_free_coherent(&ha->pdev->dev, SFP_DEV_SIZE, + dma_free_coherent(&ha->pdev->dev, sizeof(struct qla_flt_header) + FLT_REGIONS_SIZE, ha->flt, ha->flt_dma); fail_flt_buffer: From e4e15a0a4403c96d9898d8398f0640421df9cb16 Mon Sep 17 00:00:00 2001 From: Kohei Enju Date: Sat, 17 Jan 2026 16:00:45 +0000 Subject: [PATCH 2927/3902] efivarfs: fix error propagation in efivar_entry_get() commit 4b22ec1685ce1fc0d862dcda3225d852fb107995 upstream. efivar_entry_get() always returns success even if the underlying __efivar_entry_get() fails, masking errors. This may result in uninitialized heap memory being copied to userspace in the efivarfs_file_read() path. Fix it by returning the error from __efivar_entry_get(). Fixes: 2d82e6227ea1 ("efi: vars: Move efivar caching layer into efivarfs") Cc: # v6.1+ Signed-off-by: Kohei Enju Signed-off-by: Ard Biesheuvel Signed-off-by: Greg Kroah-Hartman --- fs/efivarfs/vars.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fs/efivarfs/vars.c b/fs/efivarfs/vars.c index 6edc10958ecf55..70e13db260dba0 100644 --- a/fs/efivarfs/vars.c +++ b/fs/efivarfs/vars.c @@ -552,7 +552,7 @@ int efivar_entry_get(struct efivar_entry *entry, u32 *attributes, err = __efivar_entry_get(entry, attributes, size, data); efivar_unlock(); - return 0; + return err; } /** From 68207ceefd71cc74ce4e983fa9bd10c3122e349b Mon Sep 17 00:00:00 2001 From: Ming Lei Date: Wed, 21 Jan 2026 17:38:54 +0800 Subject: [PATCH 2928/3902] nvmet: fix race in nvmet_bio_done() leading to NULL pointer dereference commit 0fcee2cfc4b2e16e62ff8e0cc2cd8dd24efad65e upstream. There is a race condition in nvmet_bio_done() that can cause a NULL pointer dereference in blk_cgroup_bio_start(): 1. nvmet_bio_done() is called when a bio completes 2. nvmet_req_complete() is called, which invokes req->ops->queue_response(req) 3. The queue_response callback can re-queue and re-submit the same request 4. The re-submission reuses the same inline_bio from nvmet_req 5. Meanwhile, nvmet_req_bio_put() (called after nvmet_req_complete) invokes bio_uninit() for inline_bio, which sets bio->bi_blkg to NULL 6. The re-submitted bio enters submit_bio_noacct_nocheck() 7. blk_cgroup_bio_start() dereferences bio->bi_blkg, causing a crash: BUG: kernel NULL pointer dereference, address: 0000000000000028 #PF: supervisor read access in kernel mode RIP: 0010:blk_cgroup_bio_start+0x10/0xd0 Call Trace: submit_bio_noacct_nocheck+0x44/0x250 nvmet_bdev_execute_rw+0x254/0x370 [nvmet] process_one_work+0x193/0x3c0 worker_thread+0x281/0x3a0 Fix this by reordering nvmet_bio_done() to call nvmet_req_bio_put() BEFORE nvmet_req_complete(). This ensures the bio is cleaned up before the request can be re-submitted, preventing the race condition. Fixes: 190f4c2c863a ("nvmet: fix memory leak of bio integrity") Cc: Dmitry Bogdanov Cc: stable@vger.kernel.org Cc: Guangwu Zhang Link: http://www.mail-archive.com/debian-kernel@lists.debian.org/msg146238.html Reviewed-by: Christoph Hellwig Signed-off-by: Ming Lei Signed-off-by: Keith Busch Signed-off-by: Greg Kroah-Hartman --- drivers/nvme/target/io-cmd-bdev.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/drivers/nvme/target/io-cmd-bdev.c b/drivers/nvme/target/io-cmd-bdev.c index 8d246b8ca604c5..0103815542d49f 100644 --- a/drivers/nvme/target/io-cmd-bdev.c +++ b/drivers/nvme/target/io-cmd-bdev.c @@ -180,9 +180,10 @@ u16 blk_to_nvme_status(struct nvmet_req *req, blk_status_t blk_sts) static void nvmet_bio_done(struct bio *bio) { struct nvmet_req *req = bio->bi_private; + blk_status_t blk_status = bio->bi_status; - nvmet_req_complete(req, blk_to_nvme_status(req, bio->bi_status)); nvmet_req_bio_put(req, bio); + nvmet_req_complete(req, blk_to_nvme_status(req, blk_status)); } #ifdef CONFIG_BLK_DEV_INTEGRITY From f69cae1bf44ca97168207e13ab05b33f7ae815a0 Mon Sep 17 00:00:00 2001 From: Zhang Heng Date: Mon, 26 Jan 2026 15:35:07 +0800 Subject: [PATCH 2929/3902] ALSA: hda/realtek: fix right sounds and mute/micmute LEDs for HP machine commit 891b77d459d0ce993c68365d899134bc9fd47ac0 upstream. The HP EliteBook 630 G11 (103c:8c8f) is using ALC236 codec which used 0x02 to control mute LED and 0x01 to control micmute LED. Therefore, add a quirk to make it works. Link: https://bugzilla.kernel.org/show_bug.cgi?id=220828 Cc: Signed-off-by: Zhang Heng Link: https://patch.msgid.link/20260126073508.3897461-1-zhangheng@kylinos.cn Signed-off-by: Takashi Iwai Signed-off-by: Greg Kroah-Hartman --- sound/hda/codecs/realtek/alc269.c | 1 + 1 file changed, 1 insertion(+) diff --git a/sound/hda/codecs/realtek/alc269.c b/sound/hda/codecs/realtek/alc269.c index fedbc5afc40674..9097de7d2e3d77 100644 --- a/sound/hda/codecs/realtek/alc269.c +++ b/sound/hda/codecs/realtek/alc269.c @@ -6631,6 +6631,7 @@ static const struct hda_quirk alc269_fixup_tbl[] = { SND_PCI_QUIRK(0x103c, 0x8c8c, "HP EliteBook 660", ALC236_FIXUP_HP_GPIO_LED), SND_PCI_QUIRK(0x103c, 0x8c8d, "HP ProBook 440 G11", ALC236_FIXUP_HP_GPIO_LED), SND_PCI_QUIRK(0x103c, 0x8c8e, "HP ProBook 460 G11", ALC236_FIXUP_HP_GPIO_LED), + SND_PCI_QUIRK(0x103c, 0x8c8f, "HP EliteBook 630 G11", ALC236_FIXUP_HP_GPIO_LED), SND_PCI_QUIRK(0x103c, 0x8c90, "HP EliteBook 640", ALC236_FIXUP_HP_GPIO_LED), SND_PCI_QUIRK(0x103c, 0x8c91, "HP EliteBook 660", ALC236_FIXUP_HP_GPIO_LED), SND_PCI_QUIRK(0x103c, 0x8c96, "HP", ALC236_FIXUP_HP_MUTE_LED_MICMUTE_VREF), From 0ac0e2e64b06890a230c277c4caa91873da58b7e Mon Sep 17 00:00:00 2001 From: Robin Murphy Date: Mon, 26 Jan 2026 12:12:26 +0000 Subject: [PATCH 2930/3902] gpio: rockchip: Stop calling pinctrl for set_direction commit 7ca497be00163610afb663867db24ac408752f13 upstream. Marking the whole controller as sleeping due to the pinctrl calls in the .direction_{input,output} callbacks has the unfortunate side effect that legitimate invocations of .get and .set, which cannot themselves sleep, in atomic context now spew WARN()s from gpiolib. However, as Heiko points out, the driver doing this is a bit silly to begin with, as the pinctrl .gpio_set_direction hook doesn't even care about the direction, the hook is only used to claim the mux. And sure enough, the .gpio_request_enable hook exists to serve this very purpose, so switch to that and remove the problematic business entirely. Cc: stable@vger.kernel.org Fixes: 20cf2aed89ac ("gpio: rockchip: mark the GPIO controller as sleeping") Suggested-by: Heiko Stuebner Signed-off-by: Robin Murphy Reviewed-by: Heiko Stuebner Link: https://lore.kernel.org/r/bddc0469f25843ca5ae0cf578ab3671435ae98a7.1769429546.git.robin.murphy@arm.com Signed-off-by: Bartosz Golaszewski Signed-off-by: Greg Kroah-Hartman --- drivers/gpio/gpio-rockchip.c | 8 -------- drivers/pinctrl/pinctrl-rockchip.c | 9 ++++----- 2 files changed, 4 insertions(+), 13 deletions(-) diff --git a/drivers/gpio/gpio-rockchip.c b/drivers/gpio/gpio-rockchip.c index bae2061f15fc47..0fff4a699f12d1 100644 --- a/drivers/gpio/gpio-rockchip.c +++ b/drivers/gpio/gpio-rockchip.c @@ -18,7 +18,6 @@ #include #include #include -#include #include #include #include @@ -164,12 +163,6 @@ static int rockchip_gpio_set_direction(struct gpio_chip *chip, unsigned long flags; u32 data = input ? 0 : 1; - - if (input) - pinctrl_gpio_direction_input(chip, offset); - else - pinctrl_gpio_direction_output(chip, offset); - raw_spin_lock_irqsave(&bank->slock, flags); rockchip_gpio_writel_bit(bank, offset, data, bank->gpio_regs->port_ddr); raw_spin_unlock_irqrestore(&bank->slock, flags); @@ -593,7 +586,6 @@ static int rockchip_gpiolib_register(struct rockchip_pin_bank *bank) gc->ngpio = bank->nr_pins; gc->label = bank->name; gc->parent = bank->dev; - gc->can_sleep = true; ret = gpiochip_add_data(gc, bank); if (ret) { diff --git a/drivers/pinctrl/pinctrl-rockchip.c b/drivers/pinctrl/pinctrl-rockchip.c index 7a68a6237649c1..f1cba3d2367a36 100644 --- a/drivers/pinctrl/pinctrl-rockchip.c +++ b/drivers/pinctrl/pinctrl-rockchip.c @@ -3184,10 +3184,9 @@ static int rockchip_pmx_set(struct pinctrl_dev *pctldev, unsigned selector, return 0; } -static int rockchip_pmx_gpio_set_direction(struct pinctrl_dev *pctldev, - struct pinctrl_gpio_range *range, - unsigned offset, - bool input) +static int rockchip_pmx_gpio_request_enable(struct pinctrl_dev *pctldev, + struct pinctrl_gpio_range *range, + unsigned int offset) { struct rockchip_pinctrl *info = pinctrl_dev_get_drvdata(pctldev); struct rockchip_pin_bank *bank; @@ -3201,7 +3200,7 @@ static const struct pinmux_ops rockchip_pmx_ops = { .get_function_name = rockchip_pmx_get_func_name, .get_function_groups = rockchip_pmx_get_groups, .set_mux = rockchip_pmx_set, - .gpio_set_direction = rockchip_pmx_gpio_set_direction, + .gpio_request_enable = rockchip_pmx_gpio_request_enable, }; /* From 33f971476ffc1074c3081d1ad4b1cdc92a3e1a3f Mon Sep 17 00:00:00 2001 From: Andrey Ryabinin Date: Tue, 13 Jan 2026 20:15:15 +0100 Subject: [PATCH 2931/3902] mm/kasan: fix KASAN poisoning in vrealloc() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit commit 9b47d4eea3f7c1f620e95bda1d6221660bde7d7b upstream. A KASAN warning can be triggered when vrealloc() changes the requested size to a value that is not aligned to KASAN_GRANULE_SIZE. ------------[ cut here ]------------ WARNING: CPU: 2 PID: 1 at mm/kasan/shadow.c:174 kasan_unpoison+0x40/0x48 ... pc : kasan_unpoison+0x40/0x48 lr : __kasan_unpoison_vmalloc+0x40/0x68 Call trace: kasan_unpoison+0x40/0x48 (P) vrealloc_node_align_noprof+0x200/0x320 bpf_patch_insn_data+0x90/0x2f0 convert_ctx_accesses+0x8c0/0x1158 bpf_check+0x1488/0x1900 bpf_prog_load+0xd20/0x1258 __sys_bpf+0x96c/0xdf0 __arm64_sys_bpf+0x50/0xa0 invoke_syscall+0x90/0x160 Introduce a dedicated kasan_vrealloc() helper that centralizes KASAN handling for vmalloc reallocations. The helper accounts for KASAN granule alignment when growing or shrinking an allocation and ensures that partial granules are handled correctly. Use this helper from vrealloc_node_align_noprof() to fix poisoning logic. [ryabinin.a.a@gmail.com: move kasan_enabled() check, fix build] Link: https://lkml.kernel.org/r/20260119144509.32767-1-ryabinin.a.a@gmail.com Link: https://lkml.kernel.org/r/20260113191516.31015-1-ryabinin.a.a@gmail.com Fixes: d699440f58ce ("mm: fix vrealloc()'s KASAN poisoning logic") Signed-off-by: Andrey Ryabinin Reported-by: Maciej Żenczykowski Reported-by: Closes: https://lkml.kernel.org/r/CANP3RGeuRW53vukDy7WDO3FiVgu34-xVJYkfpm08oLO3odYFrA@mail.gmail.com Reviewed-by: Andrey Konovalov Tested-by: Maciej Wieczor-Retman Cc: Alexander Potapenko Cc: Dmitriy Vyukov Cc: Dmitry Vyukov Cc: Uladzislau Rezki Cc: Vincenzo Frascino Cc: Signed-off-by: Andrew Morton Signed-off-by: Greg Kroah-Hartman --- include/linux/kasan.h | 14 ++++++++++++++ mm/kasan/common.c | 21 +++++++++++++++++++++ mm/vmalloc.c | 7 ++----- 3 files changed, 37 insertions(+), 5 deletions(-) diff --git a/include/linux/kasan.h b/include/linux/kasan.h index cde493cb7702d2..864472c9bfc971 100644 --- a/include/linux/kasan.h +++ b/include/linux/kasan.h @@ -625,6 +625,17 @@ kasan_unpoison_vmap_areas(struct vm_struct **vms, int nr_vms, __kasan_unpoison_vmap_areas(vms, nr_vms, flags); } +void __kasan_vrealloc(const void *start, unsigned long old_size, + unsigned long new_size); + +static __always_inline void kasan_vrealloc(const void *start, + unsigned long old_size, + unsigned long new_size) +{ + if (kasan_enabled()) + __kasan_vrealloc(start, old_size, new_size); +} + #else /* CONFIG_KASAN_VMALLOC */ static inline void kasan_populate_early_vm_area_shadow(void *start, @@ -654,6 +665,9 @@ kasan_unpoison_vmap_areas(struct vm_struct **vms, int nr_vms, kasan_vmalloc_flags_t flags) { } +static inline void kasan_vrealloc(const void *start, unsigned long old_size, + unsigned long new_size) { } + #endif /* CONFIG_KASAN_VMALLOC */ #if (defined(CONFIG_KASAN_GENERIC) || defined(CONFIG_KASAN_SW_TAGS)) && \ diff --git a/mm/kasan/common.c b/mm/kasan/common.c index 589be3d8673574..3341474507f954 100644 --- a/mm/kasan/common.c +++ b/mm/kasan/common.c @@ -613,4 +613,25 @@ void __kasan_unpoison_vmap_areas(struct vm_struct **vms, int nr_vms, __kasan_unpoison_vmalloc(addr, size, flags | KASAN_VMALLOC_KEEP_TAG); } } + +void __kasan_vrealloc(const void *addr, unsigned long old_size, + unsigned long new_size) +{ + if (new_size < old_size) { + kasan_poison_last_granule(addr, new_size); + + new_size = round_up(new_size, KASAN_GRANULE_SIZE); + old_size = round_up(old_size, KASAN_GRANULE_SIZE); + if (new_size < old_size) + __kasan_poison_vmalloc(addr + new_size, + old_size - new_size); + } else if (new_size > old_size) { + old_size = round_down(old_size, KASAN_GRANULE_SIZE); + __kasan_unpoison_vmalloc(addr + old_size, + new_size - old_size, + KASAN_VMALLOC_PROT_NORMAL | + KASAN_VMALLOC_VM_ALLOC | + KASAN_VMALLOC_KEEP_TAG); + } +} #endif diff --git a/mm/vmalloc.c b/mm/vmalloc.c index 33e705ccafbaa4..4fbd6e7dc479af 100644 --- a/mm/vmalloc.c +++ b/mm/vmalloc.c @@ -4167,7 +4167,7 @@ void *vrealloc_node_align_noprof(const void *p, size_t size, unsigned long align if (want_init_on_free() || want_init_on_alloc(flags)) memset((void *)p + size, 0, old_size - size); vm->requested_size = size; - kasan_poison_vmalloc(p + size, old_size - size); + kasan_vrealloc(p, old_size, size); return (void *)p; } @@ -4175,16 +4175,13 @@ void *vrealloc_node_align_noprof(const void *p, size_t size, unsigned long align * We already have the bytes available in the allocation; use them. */ if (size <= alloced_size) { - kasan_unpoison_vmalloc(p + old_size, size - old_size, - KASAN_VMALLOC_PROT_NORMAL | - KASAN_VMALLOC_VM_ALLOC | - KASAN_VMALLOC_KEEP_TAG); /* * No need to zero memory here, as unused memory will have * already been zeroed at initial allocation time or during * realloc shrink time. */ vm->requested_size = size; + kasan_vrealloc(p, old_size, size); return (void *)p; } From 35bb480000cd28f18aa12a67a9485957d6680480 Mon Sep 17 00:00:00 2001 From: "Matthieu Baerts (NGI0)" Date: Tue, 27 Jan 2026 20:27:25 +0100 Subject: [PATCH 2932/3902] mptcp: only reset subflow errors when propagated commit dccf46179ddd6c04c14be8ed584dc54665f53f0e upstream. Some subflow socket errors need to be reported to the MPTCP socket: the initial subflow connect (MP_CAPABLE), and the ones from the fallback sockets. The others are not propagated. The issue is that sock_error() was used to retrieve the error, which was also resetting the sk_err field. Because of that, when notifying the userspace about subflow close events later on from the MPTCP worker, the ssk->sk_err field was always 0. Now, the error (sk_err) is only reset when propagating it to the msk. Fixes: 15cc10453398 ("mptcp: deliver ssk errors to msk") Cc: stable@vger.kernel.org Reviewed-by: Geliang Tang Signed-off-by: Matthieu Baerts (NGI0) Link: https://patch.msgid.link/20260127-net-mptcp-dup-nl-events-v1-3-7f71e1bc4feb@kernel.org Signed-off-by: Jakub Kicinski Signed-off-by: Greg Kroah-Hartman --- net/mptcp/protocol.c | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/net/mptcp/protocol.c b/net/mptcp/protocol.c index d4e3111ba643f6..6ca9a37eabd117 100644 --- a/net/mptcp/protocol.c +++ b/net/mptcp/protocol.c @@ -784,11 +784,8 @@ static bool __mptcp_ofo_queue(struct mptcp_sock *msk) static bool __mptcp_subflow_error_report(struct sock *sk, struct sock *ssk) { - int err = sock_error(ssk); int ssk_state; - - if (!err) - return false; + int err; /* only propagate errors on fallen-back sockets or * on MPC connect @@ -796,6 +793,10 @@ static bool __mptcp_subflow_error_report(struct sock *sk, struct sock *ssk) if (sk->sk_state != TCP_SYN_SENT && !__mptcp_check_fallback(mptcp_sk(sk))) return false; + err = sock_error(ssk); + if (!err) + return false; + /* We need to propagate only transition to CLOSE state. * Orphaned socket will see such state change via * subflow_sched_work_if_closed() and that path will properly From 8d4ccc10a77f00c4f856dd24688a84ca1a22d46d Mon Sep 17 00:00:00 2001 From: "Matthieu Baerts (NGI0)" Date: Tue, 27 Jan 2026 20:27:24 +0100 Subject: [PATCH 2933/3902] selftests: mptcp: check no dup close events after error commit 8467458dfa61b37e259e3485a5d3e415d08193c1 upstream. This validates the previous commit: subflow closed events are re-sent with less info when the initial subflow is disconnected after an error and each time a subflow is closed after that. In this new test, the userspace PM is involved because that's how it was discovered, but it is not specific to it. The initial subflow is terminated with a RESET, and that will cause the subflow disconnect. Then, a new subflow is initiated, but also got rejected, which cause a second subflow closed event, but not a third one. While at it, in case of failure to get the expected amount of events, the events are printed. The 'Fixes' tag here below is the same as the one from the previous commit: this patch here is not fixing anything wrong in the selftests, but it validates the previous fix for an issue introduced by this commit ID. Fixes: d82809b6c5f2 ("mptcp: avoid duplicated SUB_CLOSED events") Cc: stable@vger.kernel.org Reviewed-by: Geliang Tang Signed-off-by: Matthieu Baerts (NGI0) Link: https://patch.msgid.link/20260127-net-mptcp-dup-nl-events-v1-2-7f71e1bc4feb@kernel.org Signed-off-by: Jakub Kicinski Signed-off-by: Greg Kroah-Hartman --- .../testing/selftests/net/mptcp/mptcp_join.sh | 51 +++++++++++++++++++ 1 file changed, 51 insertions(+) diff --git a/tools/testing/selftests/net/mptcp/mptcp_join.sh b/tools/testing/selftests/net/mptcp/mptcp_join.sh index 43f31f8d587fe1..b6910c585c0e62 100755 --- a/tools/testing/selftests/net/mptcp/mptcp_join.sh +++ b/tools/testing/selftests/net/mptcp/mptcp_join.sh @@ -3717,11 +3717,32 @@ chk_evt_nr() count=$(grep -cw "type:${evt}" "${evts}") if [ "${count}" != "${exp}" ]; then fail_test "got ${count} events, expected ${exp}" + cat "${evts}" else print_ok fi } +# $1: ns ; $2: event type ; $3: expected count +wait_event() +{ + local ns="${1}" + local evt_name="${2}" + local exp="${3}" + + local evt="${!evt_name}" + local evts="${evts_ns1}" + local count + + [ "${ns}" == "ns2" ] && evts="${evts_ns2}" + + for _ in $(seq 100); do + count=$(grep -cw "type:${evt}" "${evts}") + [ "${count}" -ge "${exp}" ] && break + sleep 0.1 + done +} + userspace_tests() { # userspace pm type prevents add_addr @@ -3930,6 +3951,36 @@ userspace_tests() kill_events_pids mptcp_lib_kill_group_wait $tests_pid fi + + # userspace pm no duplicated spurious close events after an error + if reset_with_events "userspace pm no dup close events after error" && + continue_if mptcp_lib_has_file '/proc/sys/net/mptcp/pm_type'; then + set_userspace_pm $ns2 + pm_nl_set_limits $ns1 0 2 + { timeout_test=120 test_linkfail=128 speed=slow \ + run_tests $ns1 $ns2 10.0.1.1 & } 2>/dev/null + local tests_pid=$! + wait_event ns2 MPTCP_LIB_EVENT_ESTABLISHED 1 + userspace_pm_add_sf $ns2 10.0.3.2 20 + chk_mptcp_info subflows 1 subflows 1 + chk_subflows_total 2 2 + + # force quick loss + ip netns exec $ns2 sysctl -q net.ipv4.tcp_syn_retries=1 + if ip netns exec "${ns1}" ${iptables} -A INPUT -s "10.0.1.2" \ + -p tcp --tcp-option 30 -j REJECT --reject-with tcp-reset && + ip netns exec "${ns2}" ${iptables} -A INPUT -d "10.0.1.2" \ + -p tcp --tcp-option 30 -j REJECT --reject-with tcp-reset; then + wait_event ns2 MPTCP_LIB_EVENT_SUB_CLOSED 1 + wait_event ns1 MPTCP_LIB_EVENT_SUB_CLOSED 1 + chk_subflows_total 1 1 + userspace_pm_add_sf $ns2 10.0.1.2 0 + wait_event ns2 MPTCP_LIB_EVENT_SUB_CLOSED 2 + chk_evt_nr ns2 MPTCP_LIB_EVENT_SUB_CLOSED 2 + fi + kill_events_pids + mptcp_lib_kill_group_wait $tests_pid + fi } endpoint_tests() From e73aab3337833c71d347f53ca80ae1ee879f6e18 Mon Sep 17 00:00:00 2001 From: "Matthieu Baerts (NGI0)" Date: Tue, 27 Jan 2026 20:27:26 +0100 Subject: [PATCH 2934/3902] selftests: mptcp: check subflow errors in close events commit 2ef9e3a3845d0a20b62b01f5b731debd0364688d upstream. This validates the previous commit: subflow closed events should contain an error field when a subflow got closed with an error, e.g. reset or timeout. For this test, the chk_evt_nr helper has been extended to check attributes in the matched events. In this test, the 2 subflow closed events should have an error. The 'Fixes' tag here below is the same as the one from the previous commit: this patch here is not fixing anything wrong in the selftests, but it validates the previous fix for an issue introduced by this commit ID. Fixes: 15cc10453398 ("mptcp: deliver ssk errors to msk") Cc: stable@vger.kernel.org Reviewed-by: Geliang Tang Signed-off-by: Matthieu Baerts (NGI0) Link: https://patch.msgid.link/20260127-net-mptcp-dup-nl-events-v1-4-7f71e1bc4feb@kernel.org Signed-off-by: Jakub Kicinski Signed-off-by: Greg Kroah-Hartman --- .../testing/selftests/net/mptcp/mptcp_join.sh | 23 ++++++++++++++++--- 1 file changed, 20 insertions(+), 3 deletions(-) diff --git a/tools/testing/selftests/net/mptcp/mptcp_join.sh b/tools/testing/selftests/net/mptcp/mptcp_join.sh index b6910c585c0e62..dc8353a3a40dda 100755 --- a/tools/testing/selftests/net/mptcp/mptcp_join.sh +++ b/tools/testing/selftests/net/mptcp/mptcp_join.sh @@ -3692,21 +3692,28 @@ userspace_pm_chk_get_addr() fi } -# $1: ns ; $2: event type ; $3: count +# $1: ns ; $2: event type ; $3: count ; [ $4: attr ; $5: attr count ] chk_evt_nr() { local ns=${1} local evt_name="${2}" local exp="${3}" + local attr="${4}" + local attr_exp="${5}" local evts="${evts_ns1}" local evt="${!evt_name}" + local attr_name local count + if [ -n "${attr}" ]; then + attr_name=", ${attr}: ${attr_exp}" + fi + evt_name="${evt_name:16}" # without MPTCP_LIB_EVENT_ [ "${ns}" == "ns2" ] && evts="${evts_ns2}" - print_check "event ${ns} ${evt_name} (${exp})" + print_check "event ${ns} ${evt_name} (${exp}${attr_name})" if [[ "${evt_name}" = "LISTENER_"* ]] && ! mptcp_lib_kallsyms_has "mptcp_event_pm_listener$"; then @@ -3718,6 +3725,16 @@ chk_evt_nr() if [ "${count}" != "${exp}" ]; then fail_test "got ${count} events, expected ${exp}" cat "${evts}" + return + elif [ -z "${attr}" ]; then + print_ok + return + fi + + count=$(grep -w "type:${evt}" "${evts}" | grep -c ",${attr}:") + if [ "${count}" != "${attr_exp}" ]; then + fail_test "got ${count} event attributes, expected ${attr_exp}" + grep -w "type:${evt}" "${evts}" else print_ok fi @@ -3976,7 +3993,7 @@ userspace_tests() chk_subflows_total 1 1 userspace_pm_add_sf $ns2 10.0.1.2 0 wait_event ns2 MPTCP_LIB_EVENT_SUB_CLOSED 2 - chk_evt_nr ns2 MPTCP_LIB_EVENT_SUB_CLOSED 2 + chk_evt_nr ns2 MPTCP_LIB_EVENT_SUB_CLOSED 2 error 2 fi kill_events_pids mptcp_lib_kill_group_wait $tests_pid From 131af3df59b24d8a8bafc0f3b89d9df8040d1748 Mon Sep 17 00:00:00 2001 From: "Matthieu Baerts (NGI0)" Date: Tue, 27 Jan 2026 20:27:27 +0100 Subject: [PATCH 2935/3902] selftests: mptcp: join: fix local endp not being tracked commit c5d5ecf21fdd9ce91e6116feb3aa83cee73352cc upstream. When running this mptcp_join.sh selftest on older kernel versions not supporting local endpoints tracking, this test fails because 3 MP_JOIN ACKs have been received, while only 2 were expected. It is not clear why only 2 MP_JOIN ACKs were expected on old kernel versions, while 3 MP_JOIN SYN and SYN+ACK were expected. When testing on the v5.15.197 kernel, 3 MP_JOIN ACKs are seen, which is also what is expected in the selftests included in this kernel version, see commit f4480eaad489 ("selftests: mptcp: add missing join check"). Switch the expected MP_JOIN ACKs to 3. While at it, move this chk_join_nr helper out of the special condition for older kernel versions as it is now the same as with more recent ones. Also, invert the condition to be more logical: what's expected on newer kernel versions having such helper first. Fixes: d4c81bbb8600 ("selftests: mptcp: join: support local endpoint being tracked or not") Cc: stable@vger.kernel.org Reviewed-by: Mat Martineau Signed-off-by: Matthieu Baerts (NGI0) Link: https://patch.msgid.link/20260127-net-mptcp-dup-nl-events-v1-5-7f71e1bc4feb@kernel.org Signed-off-by: Jakub Kicinski Signed-off-by: Greg Kroah-Hartman --- tools/testing/selftests/net/mptcp/mptcp_join.sh | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/tools/testing/selftests/net/mptcp/mptcp_join.sh b/tools/testing/selftests/net/mptcp/mptcp_join.sh index dc8353a3a40dda..9a95830005061d 100755 --- a/tools/testing/selftests/net/mptcp/mptcp_join.sh +++ b/tools/testing/selftests/net/mptcp/mptcp_join.sh @@ -2305,17 +2305,16 @@ signal_address_tests() ip netns exec $ns1 sysctl -q net.mptcp.add_addr_timeout=1 speed=slow \ run_tests $ns1 $ns2 10.0.1.1 + chk_join_nr 3 3 3 # It is not directly linked to the commit introducing this # symbol but for the parent one which is linked anyway. - if ! mptcp_lib_kallsyms_has "mptcp_pm_subflow_check_next$"; then - chk_join_nr 3 3 2 - chk_add_nr 4 4 - else - chk_join_nr 3 3 3 + if mptcp_lib_kallsyms_has "mptcp_pm_subflow_check_next$"; then # the server will not signal the address terminating # the MPC subflow chk_add_nr 3 3 + else + chk_add_nr 4 4 fi fi } From b91a84299d72ae0e05551e851e47cd3008bd025b Mon Sep 17 00:00:00 2001 From: Jan Kara Date: Wed, 21 Jan 2026 12:27:30 +0100 Subject: [PATCH 2936/3902] flex_proportions: make fprop_new_period() hardirq safe commit dd9e2f5b38f1fdd49b1ab6d3a85f81c14369eacc upstream. Bernd has reported a lockdep splat from flexible proportions code that is essentially complaining about the following race: run_timer_softirq - we are in softirq context call_timer_fn writeout_period fprop_new_period write_seqcount_begin(&p->sequence); ... blk_mq_end_request() blk_update_request() ext4_end_bio() folio_end_writeback() __wb_writeout_add() __fprop_add_percpu_max() if (unlikely(max_frac < FPROP_FRAC_BASE)) { fprop_fraction_percpu() seq = read_seqcount_begin(&p->sequence); - sees odd sequence so loops indefinitely Note that a deadlock like this is only possible if the bdi has configured maximum fraction of writeout throughput which is very rare in general but frequent for example for FUSE bdis. To fix this problem we have to make sure write section of the sequence counter is irqsafe. Link: https://lkml.kernel.org/r/20260121112729.24463-2-jack@suse.cz Fixes: a91befde3503 ("lib/flex_proportions.c: remove local_irq_ops in fprop_new_period()") Signed-off-by: Jan Kara Reported-by: Bernd Schubert Link: https://lore.kernel.org/all/9b845a47-9aee-43dd-99bc-1a82bea00442@bsbernd.com/ Reviewed-by: Matthew Wilcox (Oracle) Cc: Joanne Koong Cc: Miklos Szeredi Cc: Signed-off-by: Andrew Morton Signed-off-by: Greg Kroah-Hartman --- lib/flex_proportions.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/lib/flex_proportions.c b/lib/flex_proportions.c index 84ecccddc77182..012d5614efb9ab 100644 --- a/lib/flex_proportions.c +++ b/lib/flex_proportions.c @@ -64,13 +64,14 @@ void fprop_global_destroy(struct fprop_global *p) bool fprop_new_period(struct fprop_global *p, int periods) { s64 events = percpu_counter_sum(&p->events); + unsigned long flags; /* * Don't do anything if there are no events. */ if (events <= 1) return false; - preempt_disable_nested(); + local_irq_save(flags); write_seqcount_begin(&p->sequence); if (periods < 64) events -= events >> periods; @@ -78,7 +79,7 @@ bool fprop_new_period(struct fprop_global *p, int periods) percpu_counter_add(&p->events, -events); p->period += periods; write_seqcount_end(&p->sequence); - preempt_enable_nested(); + local_irq_restore(flags); return true; } From 629666d20c7dcd740e193ec0631fdff035b1f7d6 Mon Sep 17 00:00:00 2001 From: Qu Wenruo Date: Mon, 19 Jan 2026 17:31:19 +1030 Subject: [PATCH 2937/3902] btrfs: do not strictly require dirty metadata threshold for metadata writepages commit 4e159150a9a56d66d247f4b5510bed46fe58aa1c upstream. [BUG] There is an internal report that over 1000 processes are waiting at the io_schedule_timeout() of balance_dirty_pages(), causing a system hang and trigger a kernel coredump. The kernel is v6.4 kernel based, but the root problem still applies to any upstream kernel before v6.18. [CAUSE] From Jan Kara for his wisdom on the dirty page balance behavior first. This cgroup dirty limit was what was actually playing the role here because the cgroup had only a small amount of memory and so the dirty limit for it was something like 16MB. Dirty throttling is responsible for enforcing that nobody can dirty (significantly) more dirty memory than there's dirty limit. Thus when a task is dirtying pages it periodically enters into balance_dirty_pages() and we let it sleep there to slow down the dirtying. When the system is over dirty limit already (either globally or within a cgroup of the running task), we will not let the task exit from balance_dirty_pages() until the number of dirty pages drops below the limit. So in this particular case, as I already mentioned, there was a cgroup with relatively small amount of memory and as a result with dirty limit set at 16MB. A task from that cgroup has dirtied about 28MB worth of pages in btrfs btree inode and these were practically the only dirty pages in that cgroup. So that means the only way to reduce the dirty pages of that cgroup is to writeback the dirty pages of btrfs btree inode, and only after that those processes can exit balance_dirty_pages(). Now back to the btrfs part, btree_writepages() is responsible for writing back dirty btree inode pages. The problem here is, there is a btrfs internal threshold that if the btree inode's dirty bytes are below the 32M threshold, it will not do any writeback. This behavior is to batch as much metadata as possible so we won't write back those tree blocks and then later re-COW them again for another modification. This internal 32MiB is higher than the existing dirty page size (28MiB), meaning no writeback will happen, causing a deadlock between btrfs and cgroup: - Btrfs doesn't want to write back btree inode until more dirty pages - Cgroup/MM doesn't want more dirty pages for btrfs btree inode Thus any process touching that btree inode is put into sleep until the number of dirty pages is reduced. Thanks Jan Kara a lot for the analysis of the root cause. [ENHANCEMENT] Since kernel commit b55102826d7d ("btrfs: set AS_KERNEL_FILE on the btree_inode"), btrfs btree inode pages will only be charged to the root cgroup which should have a much larger limit than btrfs' 32MiB threshold. So it should not affect newer kernels. But for all current LTS kernels, they are all affected by this problem, and backporting the whole AS_KERNEL_FILE may not be a good idea. Even for newer kernels I still think it's a good idea to get rid of the internal threshold at btree_writepages(), since for most cases cgroup/MM has a better view of full system memory usage than btrfs' fixed threshold. For internal callers using btrfs_btree_balance_dirty() since that function is already doing internal threshold check, we don't need to bother them. But for external callers of btree_writepages(), just respect their requests and write back whatever they want, ignoring the internal btrfs threshold to avoid such deadlock on btree inode dirty page balancing. CC: stable@vger.kernel.org CC: Jan Kara Reviewed-by: Boris Burkov Signed-off-by: Qu Wenruo Signed-off-by: David Sterba Signed-off-by: Greg Kroah-Hartman --- fs/btrfs/disk-io.c | 22 ---------------------- fs/btrfs/extent_io.c | 3 +-- fs/btrfs/extent_io.h | 3 +-- 3 files changed, 2 insertions(+), 26 deletions(-) diff --git a/fs/btrfs/disk-io.c b/fs/btrfs/disk-io.c index a5336f530c8ed9..19d8c8fc459512 100644 --- a/fs/btrfs/disk-io.c +++ b/fs/btrfs/disk-io.c @@ -490,28 +490,6 @@ static int btree_migrate_folio(struct address_space *mapping, #define btree_migrate_folio NULL #endif -static int btree_writepages(struct address_space *mapping, - struct writeback_control *wbc) -{ - int ret; - - if (wbc->sync_mode == WB_SYNC_NONE) { - struct btrfs_fs_info *fs_info; - - if (wbc->for_kupdate) - return 0; - - fs_info = inode_to_fs_info(mapping->host); - /* this is a bit racy, but that's ok */ - ret = __percpu_counter_compare(&fs_info->dirty_metadata_bytes, - BTRFS_DIRTY_METADATA_THRESH, - fs_info->dirty_metadata_batch); - if (ret < 0) - return 0; - } - return btree_write_cache_pages(mapping, wbc); -} - static bool btree_release_folio(struct folio *folio, gfp_t gfp_flags) { if (folio_test_writeback(folio) || folio_test_dirty(folio)) diff --git a/fs/btrfs/extent_io.c b/fs/btrfs/extent_io.c index 1a07edaefaa0f0..e6ffa12f575352 100644 --- a/fs/btrfs/extent_io.c +++ b/fs/btrfs/extent_io.c @@ -2289,8 +2289,7 @@ void btrfs_btree_wait_writeback_range(struct btrfs_fs_info *fs_info, u64 start, } } -int btree_write_cache_pages(struct address_space *mapping, - struct writeback_control *wbc) +int btree_writepages(struct address_space *mapping, struct writeback_control *wbc) { struct btrfs_eb_write_context ctx = { .wbc = wbc }; struct btrfs_fs_info *fs_info = inode_to_fs_info(mapping->host); diff --git a/fs/btrfs/extent_io.h b/fs/btrfs/extent_io.h index 5fcbfe44218c44..559bec44a7a8e8 100644 --- a/fs/btrfs/extent_io.h +++ b/fs/btrfs/extent_io.h @@ -238,8 +238,7 @@ void extent_write_locked_range(struct inode *inode, const struct folio *locked_f u64 start, u64 end, struct writeback_control *wbc, bool pages_dirty); int btrfs_writepages(struct address_space *mapping, struct writeback_control *wbc); -int btree_write_cache_pages(struct address_space *mapping, - struct writeback_control *wbc); +int btree_writepages(struct address_space *mapping, struct writeback_control *wbc); void btrfs_btree_wait_writeback_range(struct btrfs_fs_info *fs_info, u64 start, u64 end); void btrfs_readahead(struct readahead_control *rac); int set_folio_extent_mapped(struct folio *folio); From c71fae335dfe760aa074fb53f45cf7950a8778fa Mon Sep 17 00:00:00 2001 From: Pimyn Girgis Date: Tue, 20 Jan 2026 17:15:10 +0100 Subject: [PATCH 2938/3902] mm/kfence: randomize the freelist on initialization commit 870ff19251bf3910dda7a7245da826924045fedd upstream. Randomize the KFENCE freelist during pool initialization to make allocation patterns less predictable. This is achieved by shuffling the order in which metadata objects are added to the freelist using get_random_u32_below(). Additionally, ensure the error path correctly calculates the address range to be reset if initialization fails, as the address increment logic has been moved to a separate loop. Link: https://lkml.kernel.org/r/20260120161510.3289089-1-pimyn@google.com Fixes: 0ce20dd84089 ("mm: add Kernel Electric-Fence infrastructure") Signed-off-by: Pimyn Girgis Reviewed-by: Alexander Potapenko Cc: Dmitry Vyukov Cc: Marco Elver Cc: Ernesto Martnez Garca Cc: Greg KH Cc: Kees Cook Cc: Signed-off-by: Andrew Morton Signed-off-by: Greg Kroah-Hartman --- mm/kfence/core.c | 23 +++++++++++++++++++---- 1 file changed, 19 insertions(+), 4 deletions(-) diff --git a/mm/kfence/core.c b/mm/kfence/core.c index 727c20c94ac596..6da35d477269f7 100644 --- a/mm/kfence/core.c +++ b/mm/kfence/core.c @@ -595,7 +595,7 @@ static void rcu_guarded_free(struct rcu_head *h) static unsigned long kfence_init_pool(void) { unsigned long addr, start_pfn; - int i; + int i, rand; if (!arch_kfence_init_pool()) return (unsigned long)__kfence_pool; @@ -645,13 +645,27 @@ static unsigned long kfence_init_pool(void) INIT_LIST_HEAD(&meta->list); raw_spin_lock_init(&meta->lock); meta->state = KFENCE_OBJECT_UNUSED; - meta->addr = addr; /* Initialize for validation in metadata_to_pageaddr(). */ - list_add_tail(&meta->list, &kfence_freelist); + /* Use addr to randomize the freelist. */ + meta->addr = i; /* Protect the right redzone. */ - if (unlikely(!kfence_protect(addr + PAGE_SIZE))) + if (unlikely(!kfence_protect(addr + 2 * i * PAGE_SIZE + PAGE_SIZE))) goto reset_slab; + } + + for (i = CONFIG_KFENCE_NUM_OBJECTS; i > 0; i--) { + rand = get_random_u32_below(i); + swap(kfence_metadata_init[i - 1].addr, kfence_metadata_init[rand].addr); + } + for (i = 0; i < CONFIG_KFENCE_NUM_OBJECTS; i++) { + struct kfence_metadata *meta_1 = &kfence_metadata_init[i]; + struct kfence_metadata *meta_2 = &kfence_metadata_init[meta_1->addr]; + + list_add_tail(&meta_2->list, &kfence_freelist); + } + for (i = 0; i < CONFIG_KFENCE_NUM_OBJECTS; i++) { + kfence_metadata_init[i].addr = addr; addr += 2 * PAGE_SIZE; } @@ -664,6 +678,7 @@ static unsigned long kfence_init_pool(void) return 0; reset_slab: + addr += 2 * i * PAGE_SIZE; for (i = 0; i < KFENCE_POOL_SIZE / PAGE_SIZE; i++) { struct slab *slab; From 4f57516293fdae01ffcb38b84b258dc8071bafaf Mon Sep 17 00:00:00 2001 From: Jane Chu Date: Tue, 20 Jan 2026 16:22:33 -0700 Subject: [PATCH 2939/3902] mm/memory-failure: fix missing ->mf_stats count in hugetlb poison commit a148a2040191b12b45b82cb29c281cb3036baf90 upstream. When a newly poisoned subpage ends up in an already poisoned hugetlb folio, 'num_poisoned_pages' is incremented, but the per node ->mf_stats is not. Fix the inconsistency by designating action_result() to update them both. While at it, define __get_huge_page_for_hwpoison() return values in terms of symbol names for better readibility. Also rename folio_set_hugetlb_hwpoison() to hugetlb_update_hwpoison() since the function does more than the conventional bit setting and the fact three possible return values are expected. Link: https://lkml.kernel.org/r/20260120232234.3462258-1-jane.chu@oracle.com Fixes: 18f41fa616ee ("mm: memory-failure: bump memory failure stats to pglist_data") Signed-off-by: Jane Chu Acked-by: Miaohe Lin Cc: Chris Mason Cc: David Hildenbrand Cc: David Rientjes Cc: Jiaqi Yan Cc: Liam R. Howlett Cc: Lorenzo Stoakes Cc: Matthew Wilcox (Oracle) Cc: Michal Hocko Cc: Mike Rapoport Cc: Muchun Song Cc: Naoya Horiguchi Cc: Oscar Salvador Cc: Suren Baghdasaryan Cc: William Roche Cc: Signed-off-by: Andrew Morton Signed-off-by: Greg Kroah-Hartman --- mm/memory-failure.c | 93 +++++++++++++++++++++++++++------------------ 1 file changed, 56 insertions(+), 37 deletions(-) diff --git a/mm/memory-failure.c b/mm/memory-failure.c index 3edebb0cda30b3..fce85d176eaf85 100644 --- a/mm/memory-failure.c +++ b/mm/memory-failure.c @@ -1873,12 +1873,22 @@ static unsigned long __folio_free_raw_hwp(struct folio *folio, bool move_flag) return count; } -static int folio_set_hugetlb_hwpoison(struct folio *folio, struct page *page) +#define MF_HUGETLB_FREED 0 /* freed hugepage */ +#define MF_HUGETLB_IN_USED 1 /* in-use hugepage */ +#define MF_HUGETLB_NON_HUGEPAGE 2 /* not a hugepage */ +#define MF_HUGETLB_FOLIO_PRE_POISONED 3 /* folio already poisoned */ +#define MF_HUGETLB_PAGE_PRE_POISONED 4 /* exact page already poisoned */ +#define MF_HUGETLB_RETRY 5 /* hugepage is busy, retry */ +/* + * Set hugetlb folio as hwpoisoned, update folio private raw hwpoison list + * to keep track of the poisoned pages. + */ +static int hugetlb_update_hwpoison(struct folio *folio, struct page *page) { struct llist_head *head; struct raw_hwp_page *raw_hwp; struct raw_hwp_page *p; - int ret = folio_test_set_hwpoison(folio) ? -EHWPOISON : 0; + int ret = folio_test_set_hwpoison(folio) ? MF_HUGETLB_FOLIO_PRE_POISONED : 0; /* * Once the hwpoison hugepage has lost reliable raw error info, @@ -1886,20 +1896,17 @@ static int folio_set_hugetlb_hwpoison(struct folio *folio, struct page *page) * so skip to add additional raw error info. */ if (folio_test_hugetlb_raw_hwp_unreliable(folio)) - return -EHWPOISON; + return MF_HUGETLB_FOLIO_PRE_POISONED; head = raw_hwp_list_head(folio); llist_for_each_entry(p, head->first, node) { if (p->page == page) - return -EHWPOISON; + return MF_HUGETLB_PAGE_PRE_POISONED; } raw_hwp = kmalloc(sizeof(struct raw_hwp_page), GFP_ATOMIC); if (raw_hwp) { raw_hwp->page = page; llist_add(&raw_hwp->node, head); - /* the first error event will be counted in action_result(). */ - if (ret) - num_poisoned_pages_inc(page_to_pfn(page)); } else { /* * Failed to save raw error info. We no longer trace all @@ -1947,42 +1954,39 @@ void folio_clear_hugetlb_hwpoison(struct folio *folio) /* * Called from hugetlb code with hugetlb_lock held. - * - * Return values: - * 0 - free hugepage - * 1 - in-use hugepage - * 2 - not a hugepage - * -EBUSY - the hugepage is busy (try to retry) - * -EHWPOISON - the hugepage is already hwpoisoned */ int __get_huge_page_for_hwpoison(unsigned long pfn, int flags, bool *migratable_cleared) { struct page *page = pfn_to_page(pfn); struct folio *folio = page_folio(page); - int ret = 2; /* fallback to normal page handling */ bool count_increased = false; + int ret, rc; - if (!folio_test_hugetlb(folio)) + if (!folio_test_hugetlb(folio)) { + ret = MF_HUGETLB_NON_HUGEPAGE; goto out; - - if (flags & MF_COUNT_INCREASED) { - ret = 1; + } else if (flags & MF_COUNT_INCREASED) { + ret = MF_HUGETLB_IN_USED; count_increased = true; } else if (folio_test_hugetlb_freed(folio)) { - ret = 0; + ret = MF_HUGETLB_FREED; } else if (folio_test_hugetlb_migratable(folio)) { - ret = folio_try_get(folio); - if (ret) + if (folio_try_get(folio)) { + ret = MF_HUGETLB_IN_USED; count_increased = true; + } else { + ret = MF_HUGETLB_FREED; + } } else { - ret = -EBUSY; + ret = MF_HUGETLB_RETRY; if (!(flags & MF_NO_RETRY)) goto out; } - if (folio_set_hugetlb_hwpoison(folio, page)) { - ret = -EHWPOISON; + rc = hugetlb_update_hwpoison(folio, page); + if (rc >= MF_HUGETLB_FOLIO_PRE_POISONED) { + ret = rc; goto out; } @@ -2007,10 +2011,16 @@ int __get_huge_page_for_hwpoison(unsigned long pfn, int flags, * with basic operations like hugepage allocation/free/demotion. * So some of prechecks for hwpoison (pinning, and testing/setting * PageHWPoison) should be done in single hugetlb_lock range. + * Returns: + * 0 - not hugetlb, or recovered + * -EBUSY - not recovered + * -EOPNOTSUPP - hwpoison_filter'ed + * -EHWPOISON - folio or exact page already poisoned + * -EFAULT - kill_accessing_process finds current->mm null */ static int try_memory_failure_hugetlb(unsigned long pfn, int flags, int *hugetlb) { - int res; + int res, rv; struct page *p = pfn_to_page(pfn); struct folio *folio; unsigned long page_flags; @@ -2019,22 +2029,31 @@ static int try_memory_failure_hugetlb(unsigned long pfn, int flags, int *hugetlb *hugetlb = 1; retry: res = get_huge_page_for_hwpoison(pfn, flags, &migratable_cleared); - if (res == 2) { /* fallback to normal page handling */ + switch (res) { + case MF_HUGETLB_NON_HUGEPAGE: /* fallback to normal page handling */ *hugetlb = 0; return 0; - } else if (res == -EHWPOISON) { - if (flags & MF_ACTION_REQUIRED) { - folio = page_folio(p); - res = kill_accessing_process(current, folio_pfn(folio), flags); - } - action_result(pfn, MF_MSG_ALREADY_POISONED, MF_FAILED); - return res; - } else if (res == -EBUSY) { + case MF_HUGETLB_RETRY: if (!(flags & MF_NO_RETRY)) { flags |= MF_NO_RETRY; goto retry; } return action_result(pfn, MF_MSG_GET_HWPOISON, MF_IGNORED); + case MF_HUGETLB_FOLIO_PRE_POISONED: + case MF_HUGETLB_PAGE_PRE_POISONED: + rv = -EHWPOISON; + if (flags & MF_ACTION_REQUIRED) { + folio = page_folio(p); + rv = kill_accessing_process(current, folio_pfn(folio), flags); + } + if (res == MF_HUGETLB_PAGE_PRE_POISONED) + action_result(pfn, MF_MSG_ALREADY_POISONED, MF_FAILED); + else + action_result(pfn, MF_MSG_HUGE, MF_FAILED); + return rv; + default: + WARN_ON((res != MF_HUGETLB_FREED) && (res != MF_HUGETLB_IN_USED)); + break; } folio = page_folio(p); @@ -2045,7 +2064,7 @@ static int try_memory_failure_hugetlb(unsigned long pfn, int flags, int *hugetlb if (migratable_cleared) folio_set_hugetlb_migratable(folio); folio_unlock(folio); - if (res == 1) + if (res == MF_HUGETLB_IN_USED) folio_put(folio); return -EOPNOTSUPP; } @@ -2054,7 +2073,7 @@ static int try_memory_failure_hugetlb(unsigned long pfn, int flags, int *hugetlb * Handling free hugepage. The possible race with hugepage allocation * or demotion can be prevented by PageHWPoison flag. */ - if (res == 0) { + if (res == MF_HUGETLB_FREED) { folio_unlock(folio); if (__page_handle_poison(p) > 0) { page_ref_inc(p); From b0020cbd26380177b9fb8b7e75a8f7bdba79db20 Mon Sep 17 00:00:00 2001 From: "robin.kuo" Date: Fri, 16 Jan 2026 14:25:00 +0800 Subject: [PATCH 2940/3902] mm, swap: restore swap_space attr aviod kernel panic commit a0f3c0845a4ff68d403c568266d17e9cc553e561 upstream. commit 8b47299a411a ("mm, swap: mark swap address space ro and add context debug check") made the swap address space read-only. It may lead to kernel panic if arch_prepare_to_swap returns a failure under heavy memory pressure as follows, el1_abort+0x40/0x64 el1h_64_sync_handler+0x48/0xcc el1h_64_sync+0x84/0x88 errseq_set+0x4c/0xb8 (P) __filemap_set_wb_err+0x20/0xd0 shrink_folio_list+0xc20/0x11cc evict_folios+0x1520/0x1be4 try_to_shrink_lruvec+0x27c/0x3dc shrink_one+0x9c/0x228 shrink_node+0xb3c/0xeac do_try_to_free_pages+0x170/0x4f0 try_to_free_pages+0x334/0x534 __alloc_pages_direct_reclaim+0x90/0x158 __alloc_pages_slowpath+0x334/0x588 __alloc_frozen_pages_noprof+0x224/0x2fc __folio_alloc_noprof+0x14/0x64 vma_alloc_zeroed_movable_folio+0x34/0x44 do_pte_missing+0xad4/0x1040 handle_mm_fault+0x4a4/0x790 do_page_fault+0x288/0x5f8 do_translation_fault+0x38/0x54 do_mem_abort+0x54/0xa8 Restore swap address space as not ro to avoid the panic. Link: https://lkml.kernel.org/r/20260116062535.306453-2-robin.kuo@mediatek.com Fixes: 8b47299a411a ("mm, swap: mark swap address space ro and add context debug check") Signed-off-by: robin.kuo Reviewed-by: Andrew Morton Cc: andrew.yang Cc: AngeloGiaocchino Del Regno Cc: Baoquan He Cc: Barry Song Cc: Chinwen Chang Cc: Chris Li Cc: Kairui Song Cc: Kairui Song Cc: Kemeng Shi Cc: Mathias Brugger Cc: Nhat Pham Cc: Qun-wei Lin Cc: Signed-off-by: Andrew Morton Signed-off-by: Greg Kroah-Hartman --- mm/swap.h | 2 +- mm/swap_state.c | 3 +-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/mm/swap.h b/mm/swap.h index 8d8efdf1297a7f..c6bdc5d215934f 100644 --- a/mm/swap.h +++ b/mm/swap.h @@ -198,7 +198,7 @@ int swap_writeout(struct folio *folio, struct swap_iocb **swap_plug); void __swap_writepage(struct folio *folio, struct swap_iocb **swap_plug); /* linux/mm/swap_state.c */ -extern struct address_space swap_space __ro_after_init; +extern struct address_space swap_space __read_mostly; static inline struct address_space *swap_address_space(swp_entry_t entry) { return &swap_space; diff --git a/mm/swap_state.c b/mm/swap_state.c index f4980dde5394b5..b8d7744b4436a0 100644 --- a/mm/swap_state.c +++ b/mm/swap_state.c @@ -37,8 +37,7 @@ static const struct address_space_operations swap_aops = { #endif }; -/* Set swap_space as read only as swap cache is handled by swap table */ -struct address_space swap_space __ro_after_init = { +struct address_space swap_space __read_mostly = { .a_ops = &swap_aops, }; From 40aed8b8565801e9ebd104345198912b973626ab Mon Sep 17 00:00:00 2001 From: Jane Chu Date: Tue, 20 Jan 2026 16:22:34 -0700 Subject: [PATCH 2941/3902] mm/memory-failure: teach kill_accessing_process to accept hugetlb tail page pfn commit 057a6f2632c956483e2b2628477f0fcd1cd8a844 upstream. When a hugetlb folio is being poisoned again, try_memory_failure_hugetlb() passed head pfn to kill_accessing_process(), that is not right. The precise pfn of the poisoned page should be used in order to determine the precise vaddr as the SIGBUS payload. This issue has already been taken care of in the normal path, that is, hwpoison_user_mappings(), see [1][2]. Further more, for [3] to work correctly in the hugetlb repoisoning case, it's essential to inform VM the precise poisoned page, not the head page. [1] https://lkml.kernel.org/r/20231218135837.3310403-1-willy@infradead.org [2] https://lkml.kernel.org/r/20250224211445.2663312-1-jane.chu@oracle.com [3] https://lore.kernel.org/lkml/20251116013223.1557158-1-jiaqiyan@google.com/ Link: https://lkml.kernel.org/r/20260120232234.3462258-2-jane.chu@oracle.com Signed-off-by: Jane Chu Reviewed-by: Liam R. Howlett Acked-by: Miaohe Lin Cc: Chris Mason Cc: David Hildenbrand Cc: David Rientjes Cc: Jiaqi Yan Cc: Lorenzo Stoakes Cc: Matthew Wilcox (Oracle) Cc: Michal Hocko Cc: Mike Rapoport Cc: Muchun Song Cc: Naoya Horiguchi Cc: Oscar Salvador Cc: Suren Baghdasaryan Cc: William Roche Cc: Signed-off-by: Andrew Morton Signed-off-by: Greg Kroah-Hartman --- mm/memory-failure.c | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/mm/memory-failure.c b/mm/memory-failure.c index fce85d176eaf85..6e770bad79ce3b 100644 --- a/mm/memory-failure.c +++ b/mm/memory-failure.c @@ -684,6 +684,8 @@ static int check_hwpoisoned_entry(pte_t pte, unsigned long addr, short shift, unsigned long poisoned_pfn, struct to_kill *tk) { unsigned long pfn = 0; + unsigned long hwpoison_vaddr; + unsigned long mask; if (pte_present(pte)) { pfn = pte_pfn(pte); @@ -694,10 +696,12 @@ static int check_hwpoisoned_entry(pte_t pte, unsigned long addr, short shift, pfn = swp_offset_pfn(swp); } - if (!pfn || pfn != poisoned_pfn) + mask = ~((1UL << (shift - PAGE_SHIFT)) - 1); + if (!pfn || pfn != (poisoned_pfn & mask)) return 0; - set_to_kill(tk, addr, shift); + hwpoison_vaddr = addr + ((poisoned_pfn - pfn) << PAGE_SHIFT); + set_to_kill(tk, hwpoison_vaddr, shift); return 1; } @@ -2042,10 +2046,8 @@ static int try_memory_failure_hugetlb(unsigned long pfn, int flags, int *hugetlb case MF_HUGETLB_FOLIO_PRE_POISONED: case MF_HUGETLB_PAGE_PRE_POISONED: rv = -EHWPOISON; - if (flags & MF_ACTION_REQUIRED) { - folio = page_folio(p); - rv = kill_accessing_process(current, folio_pfn(folio), flags); - } + if (flags & MF_ACTION_REQUIRED) + rv = kill_accessing_process(current, pfn, flags); if (res == MF_HUGETLB_PAGE_PRE_POISONED) action_result(pfn, MF_MSG_ALREADY_POISONED, MF_FAILED); else From b23bee8cdb7aabce5701a7f57414db5a354ae8ed Mon Sep 17 00:00:00 2001 From: Kairui Song Date: Tue, 20 Jan 2026 00:11:21 +0800 Subject: [PATCH 2942/3902] mm/shmem, swap: fix race of truncate and swap entry split commit 8a1968bd997f45a9b11aefeabdd1232e1b6c7184 upstream. The helper for shmem swap freeing is not handling the order of swap entries correctly. It uses xa_cmpxchg_irq to erase the swap entry, but it gets the entry order before that using xa_get_order without lock protection, and it may get an outdated order value if the entry is split or changed in other ways after the xa_get_order and before the xa_cmpxchg_irq. And besides, the order could grow and be larger than expected, and cause truncation to erase data beyond the end border. For example, if the target entry and following entries are swapped in or freed, then a large folio was added in place and swapped out, using the same entry, the xa_cmpxchg_irq will still succeed, it's very unlikely to happen though. To fix that, open code the Xarray cmpxchg and put the order retrieval and value checking in the same critical section. Also, ensure the order won't exceed the end border, skip it if the entry goes across the border. Skipping large swap entries crosses the end border is safe here. Shmem truncate iterates the range twice, in the first iteration, find_lock_entries already filtered such entries, and shmem will swapin the entries that cross the end border and partially truncate the folio (split the folio or at least zero part of it). So in the second loop here, if we see a swap entry that crosses the end order, it must at least have its content erased already. I observed random swapoff hangs and kernel panics when stress testing ZSWAP with shmem. After applying this patch, all problems are gone. Link: https://lkml.kernel.org/r/20260120-shmem-swap-fix-v3-1-3d33ebfbc057@tencent.com Fixes: 809bc86517cc ("mm: shmem: support large folio swap out") Signed-off-by: Kairui Song Reviewed-by: Nhat Pham Acked-by: Chris Li Cc: Baolin Wang Cc: Baoquan He Cc: Barry Song Cc: Hugh Dickins Cc: Kemeng Shi Cc: Signed-off-by: Andrew Morton Signed-off-by: Greg Kroah-Hartman --- mm/shmem.c | 45 ++++++++++++++++++++++++++++++++++----------- 1 file changed, 34 insertions(+), 11 deletions(-) diff --git a/mm/shmem.c b/mm/shmem.c index d09ccb2ba21e93..d131148323067f 100644 --- a/mm/shmem.c +++ b/mm/shmem.c @@ -944,17 +944,29 @@ static void shmem_delete_from_page_cache(struct folio *folio, void *radswap) * being freed). */ static long shmem_free_swap(struct address_space *mapping, - pgoff_t index, void *radswap) + pgoff_t index, pgoff_t end, void *radswap) { - int order = xa_get_order(&mapping->i_pages, index); - void *old; + XA_STATE(xas, &mapping->i_pages, index); + unsigned int nr_pages = 0; + pgoff_t base; + void *entry; - old = xa_cmpxchg_irq(&mapping->i_pages, index, radswap, NULL, 0); - if (old != radswap) - return 0; - free_swap_and_cache_nr(radix_to_swp_entry(radswap), 1 << order); + xas_lock_irq(&xas); + entry = xas_load(&xas); + if (entry == radswap) { + nr_pages = 1 << xas_get_order(&xas); + base = round_down(xas.xa_index, nr_pages); + if (base < index || base + nr_pages - 1 > end) + nr_pages = 0; + else + xas_store(&xas, NULL); + } + xas_unlock_irq(&xas); + + if (nr_pages) + free_swap_and_cache_nr(radix_to_swp_entry(radswap), nr_pages); - return 1 << order; + return nr_pages; } /* @@ -1106,8 +1118,8 @@ static void shmem_undo_range(struct inode *inode, loff_t lstart, loff_t lend, if (xa_is_value(folio)) { if (unfalloc) continue; - nr_swaps_freed += shmem_free_swap(mapping, - indices[i], folio); + nr_swaps_freed += shmem_free_swap(mapping, indices[i], + end - 1, folio); continue; } @@ -1173,12 +1185,23 @@ static void shmem_undo_range(struct inode *inode, loff_t lstart, loff_t lend, folio = fbatch.folios[i]; if (xa_is_value(folio)) { + int order; long swaps_freed; if (unfalloc) continue; - swaps_freed = shmem_free_swap(mapping, indices[i], folio); + swaps_freed = shmem_free_swap(mapping, indices[i], + end - 1, folio); if (!swaps_freed) { + /* + * If found a large swap entry cross the end border, + * skip it as the truncate_inode_partial_folio above + * should have at least zerod its content once. + */ + order = shmem_confirm_swap(mapping, indices[i], + radix_to_swp_entry(folio)); + if (order > 0 && indices[i] + (1 << order) > end) + continue; /* Swap was replaced by page: retry */ index = indices[i]; break; From 3d48d59235c494d34e32052f768393111c0806ef Mon Sep 17 00:00:00 2001 From: Jibin Zhang Date: Mon, 26 Jan 2026 23:21:11 +0800 Subject: [PATCH 2943/3902] net: fix segmentation of forwarding fraglist GRO commit 426ca15c7f6cb6562a081341ca88893a50c59fa2 upstream. This patch enhances GSO segment handling by properly checking the SKB_GSO_DODGY flag for frag_list GSO packets, addressing low throughput issues observed when a station accesses IPv4 servers via hotspots with an IPv6-only upstream interface. Specifically, it fixes a bug in GSO segmentation when forwarding GRO packets containing a frag_list. The function skb_segment_list cannot correctly process GRO skbs that have been converted by XLAT, since XLAT only translates the header of the head skb. Consequently, skbs in the frag_list may remain untranslated, resulting in protocol inconsistencies and reduced throughput. To address this, the patch explicitly sets the SKB_GSO_DODGY flag for GSO packets in XLAT's IPv4/IPv6 protocol translation helpers (bpf_skb_proto_4_to_6 and bpf_skb_proto_6_to_4). This marks GSO packets as potentially modified after protocol translation. As a result, GSO segmentation will avoid using skb_segment_list and instead falls back to skb_segment for packets with the SKB_GSO_DODGY flag. This ensures that only safe and fully translated frag_list packets are processed by skb_segment_list, resolving protocol inconsistencies and improving throughput when forwarding GRO packets converted by XLAT. Signed-off-by: Jibin Zhang Fixes: 9fd1ff5d2ac7 ("udp: Support UDP fraglist GRO/GSO.") Cc: stable@vger.kernel.org Link: https://patch.msgid.link/20260126152114.1211-1-jibin.zhang@mediatek.com Signed-off-by: Paolo Abeni Signed-off-by: Greg Kroah-Hartman --- net/core/filter.c | 2 ++ net/ipv4/tcp_offload.c | 3 ++- net/ipv4/udp_offload.c | 3 ++- net/ipv6/tcpv6_offload.c | 3 ++- 4 files changed, 8 insertions(+), 3 deletions(-) diff --git a/net/core/filter.c b/net/core/filter.c index df6ce85e48dcd6..6431ef3e9f7ddf 100644 --- a/net/core/filter.c +++ b/net/core/filter.c @@ -3352,6 +3352,7 @@ static int bpf_skb_proto_4_to_6(struct sk_buff *skb) shinfo->gso_type &= ~SKB_GSO_TCPV4; shinfo->gso_type |= SKB_GSO_TCPV6; } + shinfo->gso_type |= SKB_GSO_DODGY; } bpf_skb_change_protocol(skb, ETH_P_IPV6); @@ -3382,6 +3383,7 @@ static int bpf_skb_proto_6_to_4(struct sk_buff *skb) shinfo->gso_type &= ~SKB_GSO_TCPV6; shinfo->gso_type |= SKB_GSO_TCPV4; } + shinfo->gso_type |= SKB_GSO_DODGY; } bpf_skb_change_protocol(skb, ETH_P_IP); diff --git a/net/ipv4/tcp_offload.c b/net/ipv4/tcp_offload.c index 2cb93da93abc29..ed4bc3209c9730 100644 --- a/net/ipv4/tcp_offload.c +++ b/net/ipv4/tcp_offload.c @@ -107,7 +107,8 @@ static struct sk_buff *tcp4_gso_segment(struct sk_buff *skb, if (skb_shinfo(skb)->gso_type & SKB_GSO_FRAGLIST) { struct tcphdr *th = tcp_hdr(skb); - if (skb_pagelen(skb) - th->doff * 4 == skb_shinfo(skb)->gso_size) + if ((skb_pagelen(skb) - th->doff * 4 == skb_shinfo(skb)->gso_size) && + !(skb_shinfo(skb)->gso_type & SKB_GSO_DODGY)) return __tcp4_gso_segment_list(skb, features); skb->ip_summed = CHECKSUM_NONE; diff --git a/net/ipv4/udp_offload.c b/net/ipv4/udp_offload.c index 19d0b5b09ffae6..589456bd8b5f1b 100644 --- a/net/ipv4/udp_offload.c +++ b/net/ipv4/udp_offload.c @@ -514,7 +514,8 @@ struct sk_buff *__udp_gso_segment(struct sk_buff *gso_skb, if (skb_shinfo(gso_skb)->gso_type & SKB_GSO_FRAGLIST) { /* Detect modified geometry and pass those to skb_segment. */ - if (skb_pagelen(gso_skb) - sizeof(*uh) == skb_shinfo(gso_skb)->gso_size) + if ((skb_pagelen(gso_skb) - sizeof(*uh) == skb_shinfo(gso_skb)->gso_size) && + !(skb_shinfo(gso_skb)->gso_type & SKB_GSO_DODGY)) return __udp_gso_segment_list(gso_skb, features, is_ipv6); ret = __skb_linearize(gso_skb); diff --git a/net/ipv6/tcpv6_offload.c b/net/ipv6/tcpv6_offload.c index effeba58630b5a..5670d32c27f80e 100644 --- a/net/ipv6/tcpv6_offload.c +++ b/net/ipv6/tcpv6_offload.c @@ -170,7 +170,8 @@ static struct sk_buff *tcp6_gso_segment(struct sk_buff *skb, if (skb_shinfo(skb)->gso_type & SKB_GSO_FRAGLIST) { struct tcphdr *th = tcp_hdr(skb); - if (skb_pagelen(skb) - th->doff * 4 == skb_shinfo(skb)->gso_size) + if ((skb_pagelen(skb) - th->doff * 4 == skb_shinfo(skb)->gso_size) && + !(skb_shinfo(skb)->gso_type & SKB_GSO_DODGY)) return __tcp6_gso_segment_list(skb, features); skb->ip_summed = CHECKSUM_NONE; From 625605ac8be501a667abe53a9c4ea854bc3e0288 Mon Sep 17 00:00:00 2001 From: Alexandre Courbot Date: Mon, 8 Dec 2025 11:47:02 +0900 Subject: [PATCH 2944/3902] rust: bits: always inline functions using build_assert with arguments commit 09c3c9112d71c44146419c87c55c710e68335741 upstream. `build_assert` relies on the compiler to optimize out its error path. Functions using it with its arguments must thus always be inlined, otherwise the error path of `build_assert` might not be optimized out, triggering a build error. Cc: stable@vger.kernel.org Fixes: cc84ef3b88f4 ("rust: bits: add support for bits/genmask macros") Reviewed-by: Daniel Almeida Signed-off-by: Alexandre Courbot Link: https://patch.msgid.link/20251208-io-build-assert-v3-4-98aded02c1ea@nvidia.com Signed-off-by: Miguel Ojeda Signed-off-by: Greg Kroah-Hartman --- rust/kernel/bits.rs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/rust/kernel/bits.rs b/rust/kernel/bits.rs index 553d5026588329..2daead125626ae 100644 --- a/rust/kernel/bits.rs +++ b/rust/kernel/bits.rs @@ -27,7 +27,8 @@ macro_rules! impl_bit_fn { /// /// This version is the default and should be used if `n` is known at /// compile time. - #[inline] + // Always inline to optimize out error path of `build_assert`. + #[inline(always)] pub const fn [](n: u32) -> $ty { build_assert!(n < <$ty>::BITS); (1 as $ty) << n @@ -75,7 +76,8 @@ macro_rules! impl_genmask_fn { /// This version is the default and should be used if the range is known /// at compile time. $(#[$genmask_ex])* - #[inline] + // Always inline to optimize out error path of `build_assert`. + #[inline(always)] pub const fn [](range: RangeInclusive) -> $ty { let start = *range.start(); let end = *range.end(); From 4bb727f4b0614fcbc31494b1714e3ced9bf78f13 Mon Sep 17 00:00:00 2001 From: Miguel Ojeda Date: Sat, 24 Jan 2026 00:34:32 +0100 Subject: [PATCH 2945/3902] rust: sync: atomic: Provide stub for `rusttest` 32-bit hosts MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit commit bd36f6e2abf7f85644f7ea8deb1de4040b03bbc1 upstream. For arm32, on a x86_64 builder, running the `rusttest` target yields: error[E0080]: evaluation of constant value failed --> rust/kernel/static_assert.rs:37:23 | 37 | const _: () = ::core::assert!($condition $(,$arg)?); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ the evaluated program panicked at 'assertion failed: size_of::() == size_of::()', rust/kernel/sync/atomic/predefine.rs:68:1 | ::: rust/kernel/sync/atomic/predefine.rs:68:1 | 68 | static_assert!(size_of::() == size_of::()); | -------------------------------------------------------------------- in this macro invocation | = note: this error originates in the macro `::core::assert` which comes from the expansion of the macro `static_assert` (in Nightly builds, run with -Z macro-backtrace for more info) The reason is that `rusttest` runs on the host, so for e.g. a x86_64 builder `isize` is 64 bits but it is not a `CONFIG_64BIT` build. Fix it by providing a stub for `rusttest` as usual. Fixes: 84c6d36bcaf9 ("rust: sync: atomic: Add Atomic<{usize,isize}>") Cc: stable@vger.kernel.org Reviewed-by: Onur Özkan Acked-by: Boqun Feng Link: https://patch.msgid.link/20260123233432.22703-1-ojeda@kernel.org Signed-off-by: Miguel Ojeda Signed-off-by: Greg Kroah-Hartman --- rust/kernel/sync/atomic/predefine.rs | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/rust/kernel/sync/atomic/predefine.rs b/rust/kernel/sync/atomic/predefine.rs index 45a17985cda45e..0fca1ba3c2dbd1 100644 --- a/rust/kernel/sync/atomic/predefine.rs +++ b/rust/kernel/sync/atomic/predefine.rs @@ -35,12 +35,23 @@ unsafe impl super::AtomicAdd for i64 { // as `isize` and `usize`, and `isize` and `usize` are always bi-directional transmutable to // `isize_atomic_repr`, which also always implements `AtomicImpl`. #[allow(non_camel_case_types)] +#[cfg(not(testlib))] #[cfg(not(CONFIG_64BIT))] type isize_atomic_repr = i32; #[allow(non_camel_case_types)] +#[cfg(not(testlib))] #[cfg(CONFIG_64BIT)] type isize_atomic_repr = i64; +#[allow(non_camel_case_types)] +#[cfg(testlib)] +#[cfg(target_pointer_width = "32")] +type isize_atomic_repr = i32; +#[allow(non_camel_case_types)] +#[cfg(testlib)] +#[cfg(target_pointer_width = "64")] +type isize_atomic_repr = i64; + // Ensure size and alignment requirements are checked. static_assert!(size_of::() == size_of::()); static_assert!(align_of::() == align_of::()); From e82f822ed14723d536b23d446568d8098c3e1609 Mon Sep 17 00:00:00 2001 From: Alexandre Courbot Date: Mon, 8 Dec 2025 11:47:03 +0900 Subject: [PATCH 2946/3902] rust: sync: refcount: always inline functions using build_assert with arguments commit d6ff6e870077ae0f01a6f860ca1e4a5a825dc032 upstream. `build_assert` relies on the compiler to optimize out its error path. Functions using it with its arguments must thus always be inlined, otherwise the error path of `build_assert` might not be optimized out, triggering a build error. Cc: stable@vger.kernel.org Fixes: bb38f35b35f9 ("rust: implement `kernel::sync::Refcount`") Reviewed-by: Daniel Almeida Signed-off-by: Alexandre Courbot Acked-by: Boqun Feng Link: https://patch.msgid.link/20251208-io-build-assert-v3-5-98aded02c1ea@nvidia.com Signed-off-by: Miguel Ojeda Signed-off-by: Greg Kroah-Hartman --- rust/kernel/sync/refcount.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/rust/kernel/sync/refcount.rs b/rust/kernel/sync/refcount.rs index 19236a5bccdeb0..6c7ae8b05a0b85 100644 --- a/rust/kernel/sync/refcount.rs +++ b/rust/kernel/sync/refcount.rs @@ -23,7 +23,8 @@ impl Refcount { /// Construct a new [`Refcount`] from an initial value. /// /// The initial value should be non-saturated. - #[inline] + // Always inline to optimize out error path of `build_assert`. + #[inline(always)] pub fn new(value: i32) -> Self { build_assert!(value >= 0, "initial value saturated"); // SAFETY: There are no safety requirements for this FFI call. From 5bfa32ff75b5025bb2e93150548d6117fe79936d Mon Sep 17 00:00:00 2001 From: Tamir Duberstein Date: Wed, 23 Jul 2025 11:39:41 -0400 Subject: [PATCH 2947/3902] scripts: generate_rust_analyzer: Add pin_init -> compiler_builtins dep commit 98dcca855343512a99432224447f07c5988753ad upstream. Add a dependency edge from `pin_init` to `compiler_builtins` to `scripts/generate_rust_analyzer.py` to match `rust/Makefile`. This has been incorrect since commit d7659acca7a3 ("rust: add pin-init crate build infrastructure"). Signed-off-by: Tamir Duberstein Reviewed-by: Jesung Yang Acked-by: Benno Lossin Fixes: d7659acca7a3 ("rust: add pin-init crate build infrastructure") Cc: stable@vger.kernel.org Link: https://patch.msgid.link/20250723-rust-analyzer-pin-init-v1-2-3c6956173c78@kernel.org Signed-off-by: Miguel Ojeda Signed-off-by: Greg Kroah-Hartman --- scripts/generate_rust_analyzer.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/generate_rust_analyzer.py b/scripts/generate_rust_analyzer.py index fc27f0cca752d3..b056a52b348a5d 100755 --- a/scripts/generate_rust_analyzer.py +++ b/scripts/generate_rust_analyzer.py @@ -110,7 +110,7 @@ def append_sysroot_crate( append_crate( "pin_init", srctree / "rust" / "pin-init" / "src" / "lib.rs", - ["core", "pin_init_internal", "macros"], + ["core", "compiler_builtins", "pin_init_internal", "macros"], cfg=["kernel"], ) From 595718c74f85b54cd39dc9d6e9e1e6665a8b14fb Mon Sep 17 00:00:00 2001 From: Tamir Duberstein Date: Wed, 23 Jul 2025 11:39:42 -0400 Subject: [PATCH 2948/3902] scripts: generate_rust_analyzer: Add pin_init_internal deps commit 74e15ac34b098934895fd27655d098971d2b43d9 upstream. Commit d7659acca7a3 ("rust: add pin-init crate build infrastructure") did not add dependencies to `pin_init_internal`, resulting in broken navigation. Thus add them now. [ Tamir elaborates: "before this series, go-to-symbol from pin_init_internal to e.g. proc_macro::TokenStream doesn't work." - Miguel ] Signed-off-by: Tamir Duberstein Reviewed-by: Jesung Yang Acked-by: Benno Lossin Fixes: d7659acca7a3 ("rust: add pin-init crate build infrastructure") Cc: stable@vger.kernel.org Link: https://patch.msgid.link/20250723-rust-analyzer-pin-init-v1-3-3c6956173c78@kernel.org Signed-off-by: Miguel Ojeda Signed-off-by: Greg Kroah-Hartman --- scripts/generate_rust_analyzer.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/generate_rust_analyzer.py b/scripts/generate_rust_analyzer.py index b056a52b348a5d..b0cab469af0af9 100755 --- a/scripts/generate_rust_analyzer.py +++ b/scripts/generate_rust_analyzer.py @@ -102,7 +102,7 @@ def append_sysroot_crate( append_crate( "pin_init_internal", srctree / "rust" / "pin-init" / "internal" / "src" / "lib.rs", - [], + ["std", "proc_macro"], cfg=["kernel"], is_proc_macro=True, ) From ce798a0fe6b79560bed5363f1082da6fef2ea3df Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Onur=20=C3=96zkan?= Date: Wed, 24 Dec 2025 16:53:43 +0300 Subject: [PATCH 2949/3902] scripts: generate_rust_analyzer: remove sysroot assertion MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit commit 1b83ef9f7ad4635c913b80ef5e718f95f48e85af upstream. With nixpkgs's rustc, rust-src component is not bundled with the compiler by default and is instead provided from a separate store path, so this assumption does not hold. The assertion assumes these paths are in the same location which causes `make LLVM=1 rust-analyzer` to fail on NixOS. Link: https://rust-for-linux.zulipchat.com/#narrow/stream/x/topic/x/near/565284250 Signed-off-by: Onur Özkan Reviewed-by: Gary Guo Fixes: fe992163575b ("rust: Support latest version of `rust-analyzer`") Cc: stable@vger.kernel.org Link: https://patch.msgid.link/20251224135343.32476-1-work@onurozkan.dev [ Reworded title. - Miguel ] Signed-off-by: Miguel Ojeda Signed-off-by: Greg Kroah-Hartman --- scripts/generate_rust_analyzer.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/scripts/generate_rust_analyzer.py b/scripts/generate_rust_analyzer.py index b0cab469af0af9..be3933265c4dc9 100755 --- a/scripts/generate_rust_analyzer.py +++ b/scripts/generate_rust_analyzer.py @@ -192,9 +192,6 @@ def main(): level=logging.INFO if args.verbose else logging.WARNING ) - # Making sure that the `sysroot` and `sysroot_src` belong to the same toolchain. - assert args.sysroot in args.sysroot_src.parents - rust_project = { "crates": generate_crates(args.srctree, args.objtree, args.sysroot_src, args.exttree, args.cfgs, args.core_edition), "sysroot": str(args.sysroot), From 2426867644cc722e108439bf3d4e046ac45c6583 Mon Sep 17 00:00:00 2001 From: Tamir Duberstein Date: Fri, 16 Jan 2026 15:46:04 -0500 Subject: [PATCH 2950/3902] scripts: generate_rust_analyzer: compile sysroot with correct edition commit ac3c50b9a24e9ebeb585679078d6c47922034bb6 upstream. Use `core_edition` for all sysroot crates rather than just core as all were updated to edition 2024 in Rust 1.87. Fixes: f4daa80d6be7 ("rust: compile libcore with edition 2024 for 1.87+") Signed-off-by: Tamir Duberstein Cc: stable@vger.kernel.org Link: https://patch.msgid.link/20260116-rust-analyzer-sysroot-v2-1-094aedc33208@kernel.org [ Added `>`s to make the quote a single block. - Miguel ] Signed-off-by: Miguel Ojeda Signed-off-by: Greg Kroah-Hartman --- scripts/generate_rust_analyzer.py | 29 ++++++++++++++++++++++++++--- 1 file changed, 26 insertions(+), 3 deletions(-) diff --git a/scripts/generate_rust_analyzer.py b/scripts/generate_rust_analyzer.py index be3933265c4dc9..d1e0d78cc46ce6 100755 --- a/scripts/generate_rust_analyzer.py +++ b/scripts/generate_rust_analyzer.py @@ -61,7 +61,6 @@ def append_sysroot_crate( display_name, deps, cfg=[], - edition="2021", ): append_crate( display_name, @@ -69,13 +68,37 @@ def append_sysroot_crate( deps, cfg, is_workspace_member=False, - edition=edition, + # Miguel Ojeda writes: + # + # > ... in principle even the sysroot crates may have different + # > editions. + # > + # > For instance, in the move to 2024, it seems all happened at once + # > in 1.87.0 in these upstream commits: + # > + # > 0e071c2c6a58 ("Migrate core to Rust 2024") + # > f505d4e8e380 ("Migrate alloc to Rust 2024") + # > 0b2489c226c3 ("Migrate proc_macro to Rust 2024") + # > 993359e70112 ("Migrate std to Rust 2024") + # > + # > But in the previous move to 2021, `std` moved in 1.59.0, while + # > the others in 1.60.0: + # > + # > b656384d8398 ("Update stdlib to the 2021 edition") + # > 06a1c14d52a8 ("Switch all libraries to the 2021 edition") + # + # Link: https://lore.kernel.org/all/CANiq72kd9bHdKaAm=8xCUhSHMy2csyVed69bOc4dXyFAW4sfuw@mail.gmail.com/ + # + # At the time of writing all rust versions we support build the + # sysroot crates with the same edition. We may need to relax this + # assumption if future edition moves span multiple rust versions. + edition=core_edition, ) # NB: sysroot crates reexport items from one another so setting up our transitive dependencies # here is important for ensuring that rust-analyzer can resolve symbols. The sources of truth # for this dependency graph are `(sysroot_src / crate / "Cargo.toml" for crate in crates)`. - append_sysroot_crate("core", [], cfg=crates_cfgs.get("core", []), edition=core_edition) + append_sysroot_crate("core", [], cfg=crates_cfgs.get("core", [])) append_sysroot_crate("alloc", ["core"]) append_sysroot_crate("std", ["alloc", "core"]) append_sysroot_crate("proc_macro", ["core", "std"]) From 8afa6c4d7abfb694a9cd24ab16b74a100ae37c6c Mon Sep 17 00:00:00 2001 From: SeungJong Ha Date: Fri, 23 Jan 2026 13:18:44 +0000 Subject: [PATCH 2951/3902] scripts: generate_rust_analyzer: fix resolution of #[pin_data] macros commit e440bc5c190cd0e5f148b2892aeb1f4bbbf54507 upstream. Currently, rust-analyzer fails to properly resolve structs annotated with `#[pin_data]`. This prevents IDE features like "Go to Definition" from working correctly for those structs. Add the missing configuration to `generate_rust_analyzer.py` to ensure the `pin-init` crate macros are handled correctly. Signed-off-by: SeungJong Ha Fixes: d7659acca7a3 ("rust: add pin-init crate build infrastructure") Cc: stable@vger.kernel.org Tested-by: Tamir Duberstein Acked-by: Tamir Duberstein Acked-by: Gary Guo Reviewed-by: Jesung Yang Link: https://patch.msgid.link/20260123-fix-pin-init-crate-dependecies-v2-1-bb1c2500e54c@gmail.com Signed-off-by: Miguel Ojeda Signed-off-by: Greg Kroah-Hartman --- scripts/generate_rust_analyzer.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/generate_rust_analyzer.py b/scripts/generate_rust_analyzer.py index d1e0d78cc46ce6..40517b35c1ddf1 100755 --- a/scripts/generate_rust_analyzer.py +++ b/scripts/generate_rust_analyzer.py @@ -192,7 +192,7 @@ def is_root_crate(build_file, target): append_crate( name, path, - ["core", "kernel"], + ["core", "kernel", "pin_init"], cfg=cfg, ) From dd222df5b356e8d27119cade16f0675c30c3032d Mon Sep 17 00:00:00 2001 From: Tamir Duberstein Date: Wed, 23 Jul 2025 11:39:40 -0400 Subject: [PATCH 2952/3902] scripts: generate_rust_analyzer: Add compiler_builtins -> core dep commit 5157c328edb35bac05ce77da473c3209d20e0bbb upstream. Add a dependency edge from `compiler_builtins` to `core` to `scripts/generate_rust_analyzer.py` to match `rust/Makefile`. This has been incorrect since commit 8c4555ccc55c ("scripts: add `generate_rust_analyzer.py`") Signed-off-by: Tamir Duberstein Reviewed-by: Jesung Yang Acked-by: Benno Lossin Fixes: 8c4555ccc55c ("scripts: add `generate_rust_analyzer.py`") Cc: stable@vger.kernel.org Link: https://patch.msgid.link/20250723-rust-analyzer-pin-init-v1-1-3c6956173c78@kernel.org Signed-off-by: Miguel Ojeda Signed-off-by: Greg Kroah-Hartman --- scripts/generate_rust_analyzer.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/generate_rust_analyzer.py b/scripts/generate_rust_analyzer.py index 40517b35c1ddf1..852444352657ed 100755 --- a/scripts/generate_rust_analyzer.py +++ b/scripts/generate_rust_analyzer.py @@ -106,7 +106,7 @@ def append_sysroot_crate( append_crate( "compiler_builtins", srctree / "rust" / "compiler_builtins.rs", - [], + ["core"], ) append_crate( From ae8831ee0fb2f5f41f39722e7b3749d65bb78d08 Mon Sep 17 00:00:00 2001 From: Tvrtko Ursulin Date: Fri, 23 Jan 2026 14:15:40 +0000 Subject: [PATCH 2953/3902] drm: Do not allow userspace to trigger kernel warnings in drm_gem_change_handle_ioctl() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit commit 12f15d52d38ac53f7c70ea3d4b3d76afed04e064 upstream. Since GEM bo handles are u32 in the uapi and the internal implementation uses idr_alloc() which uses int ranges, passing a new handle larger than INT_MAX trivially triggers a kernel warning: idr_alloc(): ... if (WARN_ON_ONCE(start < 0)) return -EINVAL; ... Fix it by rejecting new handles above INT_MAX and at the same time make the end limit calculation more obvious by moving into int domain. Signed-off-by: Tvrtko Ursulin Reported-by: Zhi Wang Fixes: 53096728b891 ("drm: Add DRM prime interface to reassign GEM handle") Cc: David Francis Cc: Felix Kuehling Cc: Christian König Cc: # v6.18+ Tested-by: Harshit Mogalapalli Reviewed-by: Christian König Signed-off-by: Tvrtko Ursulin Link: https://lore.kernel.org/r/20260123141540.76540-1-tvrtko.ursulin@igalia.com Signed-off-by: Greg Kroah-Hartman --- drivers/gpu/drm/drm_gem.c | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/drivers/gpu/drm/drm_gem.c b/drivers/gpu/drm/drm_gem.c index 3b9df655e83772..11e7141c1524b8 100644 --- a/drivers/gpu/drm/drm_gem.c +++ b/drivers/gpu/drm/drm_gem.c @@ -970,16 +970,21 @@ int drm_gem_change_handle_ioctl(struct drm_device *dev, void *data, { struct drm_gem_change_handle *args = data; struct drm_gem_object *obj; - int ret; + int handle, ret; if (!drm_core_check_feature(dev, DRIVER_GEM)) return -EOPNOTSUPP; + /* idr_alloc() limitation. */ + if (args->new_handle > INT_MAX) + return -EINVAL; + handle = args->new_handle; + obj = drm_gem_object_lookup(file_priv, args->handle); if (!obj) return -ENOENT; - if (args->handle == args->new_handle) { + if (args->handle == handle) { ret = 0; goto out; } @@ -987,18 +992,19 @@ int drm_gem_change_handle_ioctl(struct drm_device *dev, void *data, mutex_lock(&file_priv->prime.lock); spin_lock(&file_priv->table_lock); - ret = idr_alloc(&file_priv->object_idr, obj, - args->new_handle, args->new_handle + 1, GFP_NOWAIT); + ret = idr_alloc(&file_priv->object_idr, obj, handle, handle + 1, + GFP_NOWAIT); spin_unlock(&file_priv->table_lock); if (ret < 0) goto out_unlock; if (obj->dma_buf) { - ret = drm_prime_add_buf_handle(&file_priv->prime, obj->dma_buf, args->new_handle); + ret = drm_prime_add_buf_handle(&file_priv->prime, obj->dma_buf, + handle); if (ret < 0) { spin_lock(&file_priv->table_lock); - idr_remove(&file_priv->object_idr, args->new_handle); + idr_remove(&file_priv->object_idr, handle); spin_unlock(&file_priv->table_lock); goto out_unlock; } From 3e845c46dfe5a1987052ef9496559ceba2417f55 Mon Sep 17 00:00:00 2001 From: Tvrtko Ursulin Date: Fri, 16 Jan 2026 09:50:40 +0000 Subject: [PATCH 2954/3902] drm/xe/xelp: Fix Wa_18022495364 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit commit 051be49133971076717846e2a04c746ab3476282 upstream. It looks I mistyped CS_DEBUG_MODE2 as CS_DEBUG_MODE1 when adding the workaround. Fix it. Signed-off-by: Tvrtko Ursulin Fixes: ca33cd271ef9 ("drm/xe/xelp: Add Wa_18022495364") Cc: Matt Roper Cc: "Thomas Hellström" Cc: Rodrigo Vivi Cc: # v6.18+ Reviewed-by: Matt Roper Signed-off-by: Thomas Hellström Link: https://patch.msgid.link/20260116095040.49335-1-tvrtko.ursulin@igalia.com (cherry picked from commit 7fe6cae2f7fad2b5166b0fc096618629f9e2ebcb) Signed-off-by: Thomas Hellström Signed-off-by: Greg Kroah-Hartman --- drivers/gpu/drm/xe/xe_lrc.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/gpu/drm/xe/xe_lrc.c b/drivers/gpu/drm/xe/xe_lrc.c index d77ef556e994ea..9f29dbe890f667 100644 --- a/drivers/gpu/drm/xe/xe_lrc.c +++ b/drivers/gpu/drm/xe/xe_lrc.c @@ -1185,7 +1185,7 @@ static ssize_t setup_invalidate_state_cache_wa(struct xe_lrc *lrc, return -ENOSPC; *cmd++ = MI_LOAD_REGISTER_IMM | MI_LRI_NUM_REGS(1); - *cmd++ = CS_DEBUG_MODE1(0).addr; + *cmd++ = CS_DEBUG_MODE2(0).addr; *cmd++ = _MASKED_BIT_ENABLE(INSTRUCTION_STATE_CACHE_INVALIDATE); return cmd - batch; From a4ea228271cd45fa56d96eb2dc0546c0ba6da9f2 Mon Sep 17 00:00:00 2001 From: Miguel Ojeda Date: Sat, 24 Jan 2026 17:09:48 +0100 Subject: [PATCH 2955/3902] drm/tyr: depend on `COMMON_CLK` to fix build error commit b0581f6ab952ffd135ca4402d2ee3da641538d6b upstream. Tyr needs `CONFIG_COMMON_CLK` to build: error[E0432]: unresolved import `kernel::clk::Clk` --> drivers/gpu/drm/tyr/driver.rs:3:5 | 3 | use kernel::clk::Clk; | ^^^^^^^^^^^^^^^^ no `Clk` in `clk` error[E0432]: unresolved import `kernel::clk::OptionalClk` --> drivers/gpu/drm/tyr/driver.rs:4:5 | 4 | use kernel::clk::OptionalClk; | ^^^^^^^^^^^^^^^^^^^^^^^^ no `OptionalClk` in `clk` Thus add the dependency to fix it. Fixes: cf4fd52e3236 ("rust: drm: Introduce the Tyr driver for Arm Mali GPUs") Cc: stable@vger.kernel.org Acked-by: Alice Ryhl Link: https://patch.msgid.link/20260124160948.67508-1-ojeda@kernel.org Signed-off-by: Miguel Ojeda Signed-off-by: Greg Kroah-Hartman --- drivers/gpu/drm/tyr/Kconfig | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/gpu/drm/tyr/Kconfig b/drivers/gpu/drm/tyr/Kconfig index 4b55308fd2eb4a..e933e647802722 100644 --- a/drivers/gpu/drm/tyr/Kconfig +++ b/drivers/gpu/drm/tyr/Kconfig @@ -6,6 +6,7 @@ config DRM_TYR depends on RUST depends on ARM || ARM64 || COMPILE_TEST depends on !GENERIC_ATOMIC64 # for IOMMU_IO_PGTABLE_LPAE + depends on COMMON_CLK default n help Rust DRM driver for ARM Mali CSF-based GPUs. From 31203f5680c3d1e8adb764d9efbbc8a6ecfb18ea Mon Sep 17 00:00:00 2001 From: Johan Hovold Date: Sun, 21 Dec 2025 17:45:52 +0100 Subject: [PATCH 2956/3902] drm/msm/a6xx: fix bogus hwcg register updates commit dedb897f11c5d7e32c0e0a0eff7cec23a8047167 upstream. The hw clock gating register sequence consists of register value pairs that are written to the GPU during initialisation. The a690 hwcg sequence has two GMU registers in it that used to amount to random writes in the GPU mapping, but since commit 188db3d7fe66 ("drm/msm/a6xx: Rebase GMU register offsets") they trigger a fault as the updated offsets now lie outside the mapping. This in turn breaks boot of machines like the Lenovo ThinkPad X13s. Note that the updates of these GMU registers is already taken care of properly since commit 40c297eb245b ("drm/msm/a6xx: Set GMU CGC properties on a6xx too"), but for some reason these two entries were left in the table. Fixes: 5e7665b5e484 ("drm/msm/adreno: Add Adreno A690 support") Cc: stable@vger.kernel.org # 6.5 Cc: Bjorn Andersson Cc: Konrad Dybcio Signed-off-by: Johan Hovold Reviewed-by: Konrad Dybcio Reviewed-by: Akhil P Oommen Fixes: 188db3d7fe66 ("drm/msm/a6xx: Rebase GMU register offsets") Patchwork: https://patchwork.freedesktop.org/patch/695778/ Message-ID: <20251221164552.19990-1-johan@kernel.org> Signed-off-by: Rob Clark (cherry picked from commit dcbd2f8280eea2c965453ed8c3c69d6f121e950b) Signed-off-by: Greg Kroah-Hartman --- drivers/gpu/drm/msm/adreno/a6xx_catalog.c | 2 -- 1 file changed, 2 deletions(-) diff --git a/drivers/gpu/drm/msm/adreno/a6xx_catalog.c b/drivers/gpu/drm/msm/adreno/a6xx_catalog.c index 2adbc198ecf26d..31974a4d7e14b9 100644 --- a/drivers/gpu/drm/msm/adreno/a6xx_catalog.c +++ b/drivers/gpu/drm/msm/adreno/a6xx_catalog.c @@ -501,8 +501,6 @@ static const struct adreno_reglist a690_hwcg[] = { {REG_A6XX_RBBM_CLOCK_CNTL_GMU_GX, 0x00000222}, {REG_A6XX_RBBM_CLOCK_DELAY_GMU_GX, 0x00000111}, {REG_A6XX_RBBM_CLOCK_HYST_GMU_GX, 0x00000555}, - {REG_A6XX_GPU_GMU_AO_GMU_CGC_DELAY_CNTL, 0x10111}, - {REG_A6XX_GPU_GMU_AO_GMU_CGC_HYST_CNTL, 0x5555}, {} }; From ca68745e820ecd210e3ab018497c9e6b69025c4b Mon Sep 17 00:00:00 2001 From: Johan Hovold Date: Thu, 30 Oct 2025 17:34:56 +0100 Subject: [PATCH 2957/3902] drm/imx/tve: fix probe device leak commit e535c23513c63f02f67e3e09e0787907029efeaf upstream. Make sure to drop the reference taken to the DDC device during probe on probe failure (e.g. probe deferral) and on driver unbind. Fixes: fcbc51e54d2a ("staging: drm/imx: Add support for Television Encoder (TVEv2)") Cc: stable@vger.kernel.org # 3.10 Cc: Philipp Zabel Reviewed-by: Frank Li Signed-off-by: Johan Hovold Link: https://patch.msgid.link/20251030163456.15807-1-johan@kernel.org Signed-off-by: Maxime Ripard Signed-off-by: Greg Kroah-Hartman --- drivers/gpu/drm/imx/ipuv3/imx-tve.c | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/drivers/gpu/drm/imx/ipuv3/imx-tve.c b/drivers/gpu/drm/imx/ipuv3/imx-tve.c index c5629e155d25ae..99c2e20fa3f762 100644 --- a/drivers/gpu/drm/imx/ipuv3/imx-tve.c +++ b/drivers/gpu/drm/imx/ipuv3/imx-tve.c @@ -525,6 +525,13 @@ static const struct component_ops imx_tve_ops = { .bind = imx_tve_bind, }; +static void imx_tve_put_device(void *_dev) +{ + struct device *dev = _dev; + + put_device(dev); +} + static int imx_tve_probe(struct platform_device *pdev) { struct device *dev = &pdev->dev; @@ -546,6 +553,12 @@ static int imx_tve_probe(struct platform_device *pdev) if (ddc_node) { tve->ddc = of_find_i2c_adapter_by_node(ddc_node); of_node_put(ddc_node); + if (tve->ddc) { + ret = devm_add_action_or_reset(dev, imx_tve_put_device, + &tve->ddc->dev); + if (ret) + return ret; + } } tve->mode = of_get_tve_mode(np); From 191e22e5d742e5d304db83e017b5becf32fb45e2 Mon Sep 17 00:00:00 2001 From: Yang Wang Date: Wed, 21 Jan 2026 11:04:06 +0800 Subject: [PATCH 2958/3902] drm/amd/pm: fix smu v13 soft clock frequency setting issue commit c764b7af15289051718b4859a67f9a3bc69d3fb2 upstream. v1: resolve the issue where some freq frequencies cannot be set correctly due to insufficient floating-point precision. v2: patch this convert on 'max' value only. Signed-off-by: Yang Wang Reviewed-by: Alex Deucher Signed-off-by: Alex Deucher (cherry picked from commit 6194f60c707e3878e120adeb36997075664d8429) Cc: stable@vger.kernel.org Signed-off-by: Greg Kroah-Hartman --- drivers/gpu/drm/amd/pm/swsmu/inc/smu_v13_0.h | 1 + drivers/gpu/drm/amd/pm/swsmu/smu13/smu_v13_0.c | 1 + 2 files changed, 2 insertions(+) diff --git a/drivers/gpu/drm/amd/pm/swsmu/inc/smu_v13_0.h b/drivers/gpu/drm/amd/pm/swsmu/inc/smu_v13_0.h index 4263798d716b76..8e592a477c33a1 100644 --- a/drivers/gpu/drm/amd/pm/swsmu/inc/smu_v13_0.h +++ b/drivers/gpu/drm/amd/pm/swsmu/inc/smu_v13_0.h @@ -56,6 +56,7 @@ #define SMUQ10_TO_UINT(x) ((x) >> 10) #define SMUQ10_FRAC(x) ((x) & 0x3ff) #define SMUQ10_ROUND(x) ((SMUQ10_TO_UINT(x)) + ((SMUQ10_FRAC(x)) >= 0x200)) +#define SMU_V13_SOFT_FREQ_ROUND(x) ((x) + 1) extern const int pmfw_decoded_link_speed[5]; extern const int pmfw_decoded_link_width[7]; diff --git a/drivers/gpu/drm/amd/pm/swsmu/smu13/smu_v13_0.c b/drivers/gpu/drm/amd/pm/swsmu/smu13/smu_v13_0.c index a89075e257174a..2efd914d81e5b8 100644 --- a/drivers/gpu/drm/amd/pm/swsmu/smu13/smu_v13_0.c +++ b/drivers/gpu/drm/amd/pm/swsmu/smu13/smu_v13_0.c @@ -1555,6 +1555,7 @@ int smu_v13_0_set_soft_freq_limited_range(struct smu_context *smu, return clk_id; if (max > 0) { + max = SMU_V13_SOFT_FREQ_ROUND(max); if (automatic) param = (uint32_t)((clk_id << 16) | 0xffff); else From 5f841c308c4531edd32f9780932ddfd46963c40b Mon Sep 17 00:00:00 2001 From: Yang Wang Date: Wed, 21 Jan 2026 11:06:29 +0800 Subject: [PATCH 2959/3902] drm/amd/pm: fix smu v14 soft clock frequency setting issue commit 239d0ccf567c3b09aed58eb88cd3376af37aaf14 upstream. v1: resolve the issue where some freq frequencies cannot be set correctly due to insufficient floating-point precision. v2: patch this convert on 'max' value only. Signed-off-by: Yang Wang Reviewed-by: Alex Deucher Signed-off-by: Alex Deucher (cherry picked from commit 53868dd8774344051999c880115740da92f97feb) Cc: stable@vger.kernel.org Signed-off-by: Greg Kroah-Hartman --- drivers/gpu/drm/amd/pm/swsmu/inc/smu_v14_0.h | 1 + drivers/gpu/drm/amd/pm/swsmu/smu14/smu_v14_0.c | 1 + 2 files changed, 2 insertions(+) diff --git a/drivers/gpu/drm/amd/pm/swsmu/inc/smu_v14_0.h b/drivers/gpu/drm/amd/pm/swsmu/inc/smu_v14_0.h index 29a4583db8734b..0b1e6f25e61129 100644 --- a/drivers/gpu/drm/amd/pm/swsmu/inc/smu_v14_0.h +++ b/drivers/gpu/drm/amd/pm/swsmu/inc/smu_v14_0.h @@ -57,6 +57,7 @@ extern const int decoded_link_width[8]; #define DECODE_GEN_SPEED(gen_speed_idx) (decoded_link_speed[gen_speed_idx]) #define DECODE_LANE_WIDTH(lane_width_idx) (decoded_link_width[lane_width_idx]) +#define SMU_V14_SOFT_FREQ_ROUND(x) ((x) + 1) struct smu_14_0_max_sustainable_clocks { uint32_t display_clock; diff --git a/drivers/gpu/drm/amd/pm/swsmu/smu14/smu_v14_0.c b/drivers/gpu/drm/amd/pm/swsmu/smu14/smu_v14_0.c index f9b0938c57ea71..e042f40c987f2c 100644 --- a/drivers/gpu/drm/amd/pm/swsmu/smu14/smu_v14_0.c +++ b/drivers/gpu/drm/amd/pm/swsmu/smu14/smu_v14_0.c @@ -1178,6 +1178,7 @@ int smu_v14_0_set_soft_freq_limited_range(struct smu_context *smu, return clk_id; if (max > 0) { + max = SMU_V14_SOFT_FREQ_ROUND(max); if (automatic) param = (uint32_t)((clk_id << 16) | 0xffff); else From 9a6d87fbe3f516bd980bae9a4b017ad4b2ccdf56 Mon Sep 17 00:00:00 2001 From: Alex Deucher Date: Fri, 16 Jan 2026 17:33:05 -0500 Subject: [PATCH 2960/3902] drm/amdgpu/soc21: fix xclk for APUs commit e7fbff9e7622a00c2b53cb14df481916f0019742 upstream. The reference clock is supposed to be 100Mhz, but it appears to actually be slightly lower (99.81Mhz). Closes: https://gitlab.freedesktop.org/mesa/mesa/-/issues/14451 Reviewed-by: Jesse Zhang Signed-off-by: Alex Deucher (cherry picked from commit 637fee3954d4bd509ea9d95ad1780fc174489860) Cc: stable@vger.kernel.org Signed-off-by: Greg Kroah-Hartman --- drivers/gpu/drm/amd/amdgpu/soc21.c | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/drivers/gpu/drm/amd/amdgpu/soc21.c b/drivers/gpu/drm/amd/amdgpu/soc21.c index ad36c96478a826..25536d89635d35 100644 --- a/drivers/gpu/drm/amd/amdgpu/soc21.c +++ b/drivers/gpu/drm/amd/amdgpu/soc21.c @@ -225,7 +225,13 @@ static u32 soc21_get_config_memsize(struct amdgpu_device *adev) static u32 soc21_get_xclk(struct amdgpu_device *adev) { - return adev->clock.spll.reference_freq; + u32 reference_clock = adev->clock.spll.reference_freq; + + /* reference clock is actually 99.81 Mhz rather than 100 Mhz */ + if ((adev->flags & AMD_IS_APU) && reference_clock == 10000) + return 9981; + + return reference_clock; } From 75ebd424417022eabc2990d1065eedfd1cc5895d Mon Sep 17 00:00:00 2001 From: Alex Deucher Date: Wed, 28 Jan 2026 20:51:08 -0500 Subject: [PATCH 2961/3902] drm/amdgpu/gfx10: fix wptr reset in KGQ init MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit commit cc4f433b14e05eaa4a98fd677b836e9229422387 upstream. wptr is a 64 bit value and we need to update the full value, not just 32 bits. Align with what we already do for KCQs. Reviewed-by: Timur Kristóf Reviewed-by: Jesse Zhang Signed-off-by: Alex Deucher (cherry picked from commit e80b1d1aa1073230b6c25a1a72e88f37e425ccda) Cc: stable@vger.kernel.org Signed-off-by: Greg Kroah-Hartman --- drivers/gpu/drm/amd/amdgpu/gfx_v10_0.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/gpu/drm/amd/amdgpu/gfx_v10_0.c b/drivers/gpu/drm/amd/amdgpu/gfx_v10_0.c index 8841d7213de456..726b2bdfbba332 100644 --- a/drivers/gpu/drm/amd/amdgpu/gfx_v10_0.c +++ b/drivers/gpu/drm/amd/amdgpu/gfx_v10_0.c @@ -6878,7 +6878,7 @@ static int gfx_v10_0_kgq_init_queue(struct amdgpu_ring *ring, bool reset) memcpy_toio(mqd, adev->gfx.me.mqd_backup[mqd_idx], sizeof(*mqd)); /* reset the ring */ ring->wptr = 0; - *ring->wptr_cpu_addr = 0; + atomic64_set((atomic64_t *)ring->wptr_cpu_addr, 0); amdgpu_ring_clear_ring(ring); } From 018892bd9a0c054f502c1226e24a95fdeeb928b3 Mon Sep 17 00:00:00 2001 From: Alex Deucher Date: Wed, 28 Jan 2026 18:09:03 -0500 Subject: [PATCH 2962/3902] drm/amdgpu/gfx11: fix wptr reset in KGQ init MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit commit b1f810471c6a6bd349f7f9f2f2fed96082056d46 upstream. wptr is a 64 bit value and we need to update the full value, not just 32 bits. Align with what we already do for KCQs. Reviewed-by: Timur Kristóf Reviewed-by: Jesse Zhang Signed-off-by: Alex Deucher (cherry picked from commit 1f16866bdb1daed7a80ca79ae2837a9832a74fbc) Cc: stable@vger.kernel.org Signed-off-by: Greg Kroah-Hartman --- drivers/gpu/drm/amd/amdgpu/gfx_v11_0.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/gpu/drm/amd/amdgpu/gfx_v11_0.c b/drivers/gpu/drm/amd/amdgpu/gfx_v11_0.c index f2be16e700c41a..18e5d4a76644ee 100644 --- a/drivers/gpu/drm/amd/amdgpu/gfx_v11_0.c +++ b/drivers/gpu/drm/amd/amdgpu/gfx_v11_0.c @@ -4199,7 +4199,7 @@ static int gfx_v11_0_kgq_init_queue(struct amdgpu_ring *ring, bool reset) memcpy_toio(mqd, adev->gfx.me.mqd_backup[mqd_idx], sizeof(*mqd)); /* reset the ring */ ring->wptr = 0; - *ring->wptr_cpu_addr = 0; + atomic64_set((atomic64_t *)ring->wptr_cpu_addr, 0); amdgpu_ring_clear_ring(ring); } From 4867b512bb7f5db2a848912d41124aa0335358c8 Mon Sep 17 00:00:00 2001 From: Alex Deucher Date: Wed, 28 Jan 2026 22:55:46 -0500 Subject: [PATCH 2963/3902] drm/amdgpu/gfx11: adjust KGQ reset sequence MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit commit 3eb46fbb601f9a0b4df8eba79252a0a85e983044 upstream. Kernel gfx queues do not need to be reinitialized or remapped after a reset. This fixes queue reset failures on APUs. v2: preserve init and remap for MMIO case. Fixes: b3e9bfd86658 ("drm/amdgpu/gfx11: add ring reset callbacks") Closes: https://gitlab.freedesktop.org/drm/amd/-/issues/4789 Reviewed-by: Timur Kristóf Signed-off-by: Alex Deucher (cherry picked from commit b340ff216fdabfe71ba0cdd47e9835a141d08e10) Cc: stable@vger.kernel.org Signed-off-by: Greg Kroah-Hartman --- drivers/gpu/drm/amd/amdgpu/gfx_v11_0.c | 23 +++++++++++++---------- 1 file changed, 13 insertions(+), 10 deletions(-) diff --git a/drivers/gpu/drm/amd/amdgpu/gfx_v11_0.c b/drivers/gpu/drm/amd/amdgpu/gfx_v11_0.c index 18e5d4a76644ee..c936772c037255 100644 --- a/drivers/gpu/drm/amd/amdgpu/gfx_v11_0.c +++ b/drivers/gpu/drm/amd/amdgpu/gfx_v11_0.c @@ -6821,11 +6821,12 @@ static int gfx_v11_0_reset_kgq(struct amdgpu_ring *ring, struct amdgpu_fence *timedout_fence) { struct amdgpu_device *adev = ring->adev; + bool use_mmio = false; int r; amdgpu_ring_reset_helper_begin(ring, timedout_fence); - r = amdgpu_mes_reset_legacy_queue(ring->adev, ring, vmid, false); + r = amdgpu_mes_reset_legacy_queue(ring->adev, ring, vmid, use_mmio); if (r) { dev_warn(adev->dev, "reset via MES failed and try pipe reset %d\n", r); @@ -6834,16 +6835,18 @@ static int gfx_v11_0_reset_kgq(struct amdgpu_ring *ring, return r; } - r = gfx_v11_0_kgq_init_queue(ring, true); - if (r) { - dev_err(adev->dev, "failed to init kgq\n"); - return r; - } + if (use_mmio) { + r = gfx_v11_0_kgq_init_queue(ring, true); + if (r) { + dev_err(adev->dev, "failed to init kgq\n"); + return r; + } - r = amdgpu_mes_map_legacy_queue(adev, ring); - if (r) { - dev_err(adev->dev, "failed to remap kgq\n"); - return r; + r = amdgpu_mes_map_legacy_queue(adev, ring); + if (r) { + dev_err(adev->dev, "failed to remap kgq\n"); + return r; + } } return amdgpu_ring_reset_helper_end(ring, timedout_fence); From d9790cf8bbe803de1fe95dc88c1eee8f5f8940b5 Mon Sep 17 00:00:00 2001 From: Alex Deucher Date: Wed, 28 Jan 2026 18:13:16 -0500 Subject: [PATCH 2964/3902] drm/amdgpu/gfx12: fix wptr reset in KGQ init MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit commit 9077d32a4b570fa20500aa26e149981c366c965d upstream. wptr is a 64 bit value and we need to update the full value, not just 32 bits. Align with what we already do for KCQs. Reviewed-by: Timur Kristóf Reviewed-by: Jesse Zhang Signed-off-by: Alex Deucher (cherry picked from commit a2918f958d3f677ea93c0ac257cb6ba69b7abb7c) Cc: stable@vger.kernel.org Signed-off-by: Greg Kroah-Hartman --- drivers/gpu/drm/amd/amdgpu/gfx_v12_0.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/gpu/drm/amd/amdgpu/gfx_v12_0.c b/drivers/gpu/drm/amd/amdgpu/gfx_v12_0.c index 0578f1a94b2470..ae9f2c9ee75692 100644 --- a/drivers/gpu/drm/amd/amdgpu/gfx_v12_0.c +++ b/drivers/gpu/drm/amd/amdgpu/gfx_v12_0.c @@ -3078,7 +3078,7 @@ static int gfx_v12_0_kgq_init_queue(struct amdgpu_ring *ring, bool reset) memcpy_toio(mqd, adev->gfx.me.mqd_backup[mqd_idx], sizeof(*mqd)); /* reset the ring */ ring->wptr = 0; - *ring->wptr_cpu_addr = 0; + atomic64_set((atomic64_t *)ring->wptr_cpu_addr, 0); amdgpu_ring_clear_ring(ring); } From 2d9bff2af0adb94fd5c60e5914dbd0c0be2d5204 Mon Sep 17 00:00:00 2001 From: Alex Deucher Date: Wed, 28 Jan 2026 23:05:50 -0500 Subject: [PATCH 2965/3902] drm/amdgpu/gfx12: adjust KGQ reset sequence MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit commit dfd64f6e8cd7b59238cdaf8af7a55711f13a89db upstream. Kernel gfx queues do not need to be reinitialized or remapped after a reset. Align with gfx11. v2: preserve init and remap for MMIO case. Reviewed-by: Timur Kristóf Signed-off-by: Alex Deucher (cherry picked from commit 0a6d6ed694d72b66b0ed7a483d5effa01acd3951) Cc: stable@vger.kernel.org Signed-off-by: Greg Kroah-Hartman --- drivers/gpu/drm/amd/amdgpu/gfx_v12_0.c | 23 +++++++++++++---------- 1 file changed, 13 insertions(+), 10 deletions(-) diff --git a/drivers/gpu/drm/amd/amdgpu/gfx_v12_0.c b/drivers/gpu/drm/amd/amdgpu/gfx_v12_0.c index ae9f2c9ee75692..f80e9e356e2527 100644 --- a/drivers/gpu/drm/amd/amdgpu/gfx_v12_0.c +++ b/drivers/gpu/drm/amd/amdgpu/gfx_v12_0.c @@ -5296,11 +5296,12 @@ static int gfx_v12_0_reset_kgq(struct amdgpu_ring *ring, struct amdgpu_fence *timedout_fence) { struct amdgpu_device *adev = ring->adev; + bool use_mmio = false; int r; amdgpu_ring_reset_helper_begin(ring, timedout_fence); - r = amdgpu_mes_reset_legacy_queue(ring->adev, ring, vmid, false); + r = amdgpu_mes_reset_legacy_queue(ring->adev, ring, vmid, use_mmio); if (r) { dev_warn(adev->dev, "reset via MES failed and try pipe reset %d\n", r); r = gfx_v12_reset_gfx_pipe(ring); @@ -5308,16 +5309,18 @@ static int gfx_v12_0_reset_kgq(struct amdgpu_ring *ring, return r; } - r = gfx_v12_0_kgq_init_queue(ring, true); - if (r) { - dev_err(adev->dev, "failed to init kgq\n"); - return r; - } + if (use_mmio) { + r = gfx_v12_0_kgq_init_queue(ring, true); + if (r) { + dev_err(adev->dev, "failed to init kgq\n"); + return r; + } - r = amdgpu_mes_map_legacy_queue(adev, ring); - if (r) { - dev_err(adev->dev, "failed to remap kgq\n"); - return r; + r = amdgpu_mes_map_legacy_queue(adev, ring); + if (r) { + dev_err(adev->dev, "failed to remap kgq\n"); + return r; + } } return amdgpu_ring_reset_helper_end(ring, timedout_fence); From ac251d17d8af58ddc3daba65eaf0a99e63dc4284 Mon Sep 17 00:00:00 2001 From: Jon Doron Date: Sat, 20 Dec 2025 15:04:40 +0200 Subject: [PATCH 2966/3902] drm/amdgpu: fix NULL pointer dereference in amdgpu_gmc_filter_faults_remove MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit commit 8b1ecc9377bc641533cd9e76dfa3aee3cd04a007 upstream. On APUs such as Raven and Renoir (GC 9.1.0, 9.2.2, 9.3.0), the ih1 and ih2 interrupt ring buffers are not initialized. This is by design, as these secondary IH rings are only available on discrete GPUs. See vega10_ih_sw_init() which explicitly skips ih1/ih2 initialization when AMD_IS_APU is set. However, amdgpu_gmc_filter_faults_remove() unconditionally uses ih1 to get the timestamp of the last interrupt entry. When retry faults are enabled on APUs (noretry=0), this function is called from the SVM page fault recovery path, resulting in a NULL pointer dereference when amdgpu_ih_decode_iv_ts_helper() attempts to access ih->ring[]. The crash manifests as: BUG: kernel NULL pointer dereference, address: 0000000000000004 RIP: 0010:amdgpu_ih_decode_iv_ts_helper+0x22/0x40 [amdgpu] Call Trace: amdgpu_gmc_filter_faults_remove+0x60/0x130 [amdgpu] svm_range_restore_pages+0xae5/0x11c0 [amdgpu] amdgpu_vm_handle_fault+0xc8/0x340 [amdgpu] gmc_v9_0_process_interrupt+0x191/0x220 [amdgpu] amdgpu_irq_dispatch+0xed/0x2c0 [amdgpu] amdgpu_ih_process+0x84/0x100 [amdgpu] This issue was exposed by commit 1446226d32a4 ("drm/amdgpu: Remove GC HW IP 9.3.0 from noretry=1") which changed the default for Renoir APU from noretry=1 to noretry=0, enabling retry fault handling and thus exercising the buggy code path. Fix this by adding a check for ih1.ring_size before attempting to use it. Also restore the soft_ih support from commit dd299441654f ("drm/amdgpu: Rework retry fault removal"). This is needed if the hardware doesn't support secondary HW IH rings. v2: additional updates (Alex) Closes: https://gitlab.freedesktop.org/drm/amd/-/issues/3814 Fixes: dd299441654f ("drm/amdgpu: Rework retry fault removal") Reviewed-by: Timur Kristóf Reviewed-by: Philip Yang Signed-off-by: Jon Doron Signed-off-by: Alex Deucher (cherry picked from commit 6ce8d536c80aa1f059e82184f0d1994436b1d526) Cc: stable@vger.kernel.org Signed-off-by: Greg Kroah-Hartman --- drivers/gpu/drm/amd/amdgpu/amdgpu_gmc.c | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_gmc.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_gmc.c index 869bceb0fe2c6d..aef1ba1bdca9e3 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_gmc.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_gmc.c @@ -498,8 +498,13 @@ void amdgpu_gmc_filter_faults_remove(struct amdgpu_device *adev, uint64_t addr, if (adev->irq.retry_cam_enabled) return; + else if (adev->irq.ih1.ring_size) + ih = &adev->irq.ih1; + else if (adev->irq.ih_soft.enabled) + ih = &adev->irq.ih_soft; + else + return; - ih = &adev->irq.ih1; /* Get the WPTR of the last entry in IH ring */ last_wptr = amdgpu_ih_get_wptr(adev, ih); /* Order wptr with ring data. */ From 078377b07f8e825e6798355d5a201a5c7367bcf9 Mon Sep 17 00:00:00 2001 From: Alex Deucher Date: Mon, 26 Jan 2026 23:44:45 -0500 Subject: [PATCH 2967/3902] drm/amdgpu: Fix cond_exec handling in amdgpu_ib_schedule() commit b1defcdc4457649db236415ee618a7151e28788c upstream. The EXEC_COUNT field must be > 0. In the gfx shadow handling we always emit a cond_exec packet after the gfx_shadow packet, but the EXEC_COUNT never gets patched. This leads to a hang when we try and reset queues on gfx11 APUs. Fixes: c68cbbfd54c6 ("drm/amdgpu: cleanup conditional execution") Closes: https://gitlab.freedesktop.org/drm/amd/-/issues/4789 Reviewed-by: Jesse Zhang Signed-off-by: Alex Deucher (cherry picked from commit ba205ac3d6e83f56c4f824f23f1b4522cb844ff3) Cc: stable@vger.kernel.org Signed-off-by: Greg Kroah-Hartman --- drivers/gpu/drm/amd/amdgpu/amdgpu_ib.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_ib.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_ib.c index 7d9bcb72e8dd3c..9581643dbb7262 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_ib.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_ib.c @@ -228,7 +228,7 @@ int amdgpu_ib_schedule(struct amdgpu_ring *ring, unsigned int num_ibs, amdgpu_ring_ib_begin(ring); - if (ring->funcs->emit_gfx_shadow) + if (ring->funcs->emit_gfx_shadow && adev->gfx.cp_gfx_shadow) amdgpu_ring_emit_gfx_shadow(ring, shadow_va, csa_va, gds_va, init_shadow, vmid); @@ -284,7 +284,8 @@ int amdgpu_ib_schedule(struct amdgpu_ring *ring, unsigned int num_ibs, fence_flags | AMDGPU_FENCE_FLAG_64BIT); } - if (ring->funcs->emit_gfx_shadow && ring->funcs->init_cond_exec) { + if (ring->funcs->emit_gfx_shadow && ring->funcs->init_cond_exec && + adev->gfx.cp_gfx_shadow) { amdgpu_ring_emit_gfx_shadow(ring, 0, 0, 0, false, 0); amdgpu_ring_init_cond_exec(ring, ring->cond_exe_gpu_addr); } From 84df65fcfbff150ba16e6f697f0cbbdbc297ba24 Mon Sep 17 00:00:00 2001 From: Nicolin Chen Date: Thu, 29 Jan 2026 14:43:41 -0800 Subject: [PATCH 2968/3902] iommu/tegra241-cmdqv: Reset VCMDQ in tegra241_vcmdq_hw_init_user() commit 80f1a2c2332fee0edccd006fe87fc8a6db94bab3 upstream. The Enable bits in CMDQV/VINTF/VCMDQ_CONFIG registers do not actually reset the HW registers. So, the driver explicitly clears all the registers when a VINTF or VCMDQ is being initialized calling its hw_deinit() function. However, a userspace VCMDQ is not properly reset, unlike an in-kernel VCMDQ getting reset in tegra241_vcmdq_hw_init(). Meanwhile, tegra241_vintf_hw_init() calling tegra241_vintf_hw_deinit() will not deinit any VCMDQ, since there is no userspace VCMDQ mapped to the VINTF at that stage. Then, this may result in dirty VCMDQ registers, which can fail the VM. Like tegra241_vcmdq_hw_init(), reset a VCMDQ in tegra241_vcmdq_hw_init() to fix this bug. This is required by a host kernel. Fixes: 6717f26ab1e7 ("iommu/tegra241-cmdqv: Add user-space use support") Cc: stable@vger.kernel.org Reported-by: Bao Nguyen Signed-off-by: Nicolin Chen Signed-off-by: Joerg Roedel Signed-off-by: Greg Kroah-Hartman --- drivers/iommu/arm/arm-smmu-v3/tegra241-cmdqv.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/drivers/iommu/arm/arm-smmu-v3/tegra241-cmdqv.c b/drivers/iommu/arm/arm-smmu-v3/tegra241-cmdqv.c index 378104cd395e59..04cc7a9036e431 100644 --- a/drivers/iommu/arm/arm-smmu-v3/tegra241-cmdqv.c +++ b/drivers/iommu/arm/arm-smmu-v3/tegra241-cmdqv.c @@ -1078,6 +1078,9 @@ static int tegra241_vcmdq_hw_init_user(struct tegra241_vcmdq *vcmdq) { char header[64]; + /* Reset VCMDQ */ + tegra241_vcmdq_hw_deinit(vcmdq); + /* Configure the vcmdq only; User space does the enabling */ writeq_relaxed(vcmdq->cmdq.q.q_base, REG_VCMDQ_PAGE1(vcmdq, BASE)); From f749b366b8e7934058f807688aa936686da0d196 Mon Sep 17 00:00:00 2001 From: Andy Shevchenko Date: Wed, 28 Jan 2026 10:58:54 +0100 Subject: [PATCH 2969/3902] gpiolib: acpi: Fix potential out-of-boundary left shift commit e64d1cb21a1c6ecd51bc1c94c83f6fc656f7c94d upstream. GPIO Address Space handler gets a pointer to the in or out value. This value is supposed to be at least 64-bit, but it's not limited to be exactly 64-bit. When ACPI tables are being parsed, for the bigger Connection():s ACPICA creates a Buffer instead of regular Integer object. The Buffer exists as long as Namespace holds the certain Connection(). Hence we can access the necessary bits without worrying. On the other hand, the left shift, used in the code, is limited by 31 (on 32-bit platforms) and otherwise considered to be Undefined Behaviour. Also the code uses only the first 64-bit word for the value, and anything bigger than 63 will be also subject to UB. Fix all this by modifying the code to correctly set or clear the respective bit in the bitmap constructed of 64-bit words. Fixes: 59084c564c41 ("gpiolib: acpi: use BIT_ULL() for u64 mask in address space handler") Fixes: 2c4d00cb8fc5 ("gpiolib: acpi: Use BIT() macro to increase readability") Cc: stable@vger.kernel.org Reviewed-by: Mika Westerberg Signed-off-by: Andy Shevchenko Link: https://patch.msgid.link/20260128095918.4157491-1-andriy.shevchenko@linux.intel.com Signed-off-by: Bartosz Golaszewski Signed-off-by: Greg Kroah-Hartman --- drivers/gpio/gpiolib-acpi-core.c | 21 +++++++++++++++++---- 1 file changed, 17 insertions(+), 4 deletions(-) diff --git a/drivers/gpio/gpiolib-acpi-core.c b/drivers/gpio/gpiolib-acpi-core.c index 2ac6c708d927a5..e64e21fd6bbaaa 100644 --- a/drivers/gpio/gpiolib-acpi-core.c +++ b/drivers/gpio/gpiolib-acpi-core.c @@ -1104,6 +1104,7 @@ acpi_gpio_adr_space_handler(u32 function, acpi_physical_address address, unsigned int pin = agpio->pin_table[i]; struct acpi_gpio_connection *conn; struct gpio_desc *desc; + u16 word, shift; bool found; mutex_lock(&achip->conn_lock); @@ -1158,10 +1159,22 @@ acpi_gpio_adr_space_handler(u32 function, acpi_physical_address address, mutex_unlock(&achip->conn_lock); - if (function == ACPI_WRITE) - gpiod_set_raw_value_cansleep(desc, !!(*value & BIT_ULL(i))); - else - *value |= (u64)gpiod_get_raw_value_cansleep(desc) << i; + /* + * For the cases when OperationRegion() consists of more than + * 64 bits calculate the word and bit shift to use that one to + * access the value. + */ + word = i / 64; + shift = i % 64; + + if (function == ACPI_WRITE) { + gpiod_set_raw_value_cansleep(desc, value[word] & BIT_ULL(shift)); + } else { + if (gpiod_get_raw_value_cansleep(desc)) + value[word] |= BIT_ULL(shift); + else + value[word] &= ~BIT_ULL(shift); + } } out: From ab21cf885fb2af179c44d8beeabd716133b9385d Mon Sep 17 00:00:00 2001 From: Mikhail Gavrilov Date: Sat, 6 Dec 2025 14:28:25 +0500 Subject: [PATCH 2970/3902] libbpf: Fix -Wdiscarded-qualifiers under C23 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit commit d70f79fef65810faf64dbae1f3a1b5623cdb2345 upstream. glibc ≥ 2.42 (GCC 15) defaults to -std=gnu23, which promotes -Wdiscarded-qualifiers to an error. In C23, strstr() and strchr() return "const char *". Change variable types to const char * where the pointers are never modified (res, sym_sfx, next_path). Suggested-by: Florian Weimer Suggested-by: Andrii Nakryiko Signed-off-by: Mikhail Gavrilov Link: https://lore.kernel.org/r/20251206092825.1471385-1-mikhail.v.gavrilov@gmail.com Signed-off-by: Alexei Starovoitov [ shung-hsi.yu: needed to fix kernel build failure due to libbpf since glibc 2.43+ (which adds 'const' qualifier to strstr) ] Signed-off-by: Shung-Hsi Yu Signed-off-by: Greg Kroah-Hartman --- tools/lib/bpf/libbpf.c | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/tools/lib/bpf/libbpf.c b/tools/lib/bpf/libbpf.c index dd3b2f57082d2d..9c98c6adb6d05e 100644 --- a/tools/lib/bpf/libbpf.c +++ b/tools/lib/bpf/libbpf.c @@ -8245,7 +8245,7 @@ static int kallsyms_cb(unsigned long long sym_addr, char sym_type, struct bpf_object *obj = ctx; const struct btf_type *t; struct extern_desc *ext; - char *res; + const char *res; res = strstr(sym_name, ".llvm."); if (sym_type == 'd' && res) @@ -11574,7 +11574,8 @@ static int avail_kallsyms_cb(unsigned long long sym_addr, char sym_type, * * [0] fb6a421fb615 ("kallsyms: Match symbols exactly with CONFIG_LTO_CLANG") */ - char sym_trim[256], *psym_trim = sym_trim, *sym_sfx; + char sym_trim[256], *psym_trim = sym_trim; + const char *sym_sfx; if (!(sym_sfx = strstr(sym_name, ".llvm."))) return 0; @@ -12159,7 +12160,7 @@ static int resolve_full_path(const char *file, char *result, size_t result_sz) if (!search_paths[i]) continue; for (s = search_paths[i]; s != NULL; s = strchr(s, ':')) { - char *next_path; + const char *next_path; int seg_len; if (s[0] == ':') From 5b22c6cbabc789d1ef3165f885bd018545d3b630 Mon Sep 17 00:00:00 2001 From: John Ogness Date: Fri, 30 Jan 2026 12:38:08 +0106 Subject: [PATCH 2971/3902] Revert "drm/nouveau/disp: Set drm_mode_config_funcs.atomic_(check|commit)" commit 6c65db809796717f0a96cf22f80405dbc1a31a4b upstream. This reverts commit 604826acb3f53c6648a7ee99a3914ead680ab7fb. Apparently there is more to supporting atomic modesetting than providing atomic_(check|commit) callbacks. Before this revert: WARNING: [] drivers/gpu/drm/drm_plane.c:389 at .__drm_universal_plane_init+0x13c/0x794 [drm], CPU#1: modprobe/1790 BUG: Kernel NULL pointer dereference on read at 0x00000000 .drm_atomic_get_plane_state+0xd4/0x210 [drm] (unreliable) .drm_client_modeset_commit_atomic+0xf8/0x338 [drm] .drm_client_modeset_commit_locked+0x80/0x260 [drm] .drm_client_modeset_commit+0x40/0x7c [drm] .__drm_fb_helper_restore_fbdev_mode_unlocked.part.0+0xfc/0x108 [drm_kms_helper] .drm_fb_helper_set_par+0x8c/0xb8 [drm_kms_helper] .fbcon_init+0x31c/0x618 [...] .__drm_fb_helper_initial_config_and_unlock+0x474/0x7f4 [drm_kms_helper] .drm_fbdev_client_hotplug+0xb0/0x120 [drm_client_lib] .drm_client_register+0x88/0xe4 [drm] .drm_fbdev_client_setup+0x12c/0x19b4 [drm_client_lib] .drm_client_setup+0x15c/0x18c [drm_client_lib] .nouveau_drm_probe+0x19c/0x268 [nouveau] Fixes: 604826acb3f5 ("drm/nouveau/disp: Set drm_mode_config_funcs.atomic_(check|commit)") Reported-by: John Ogness Closes: https://lore.kernel.org/lkml/87ldhf1prw.fsf@jogness.linutronix.de Signed-off-by: John Ogness Tested-by: Daniel Palmer Link: https://patch.msgid.link/20260130113230.2311221-1-john.ogness@linutronix.de Signed-off-by: Danilo Krummrich Signed-off-by: Greg Kroah-Hartman --- drivers/gpu/drm/nouveau/nouveau_display.c | 2 -- 1 file changed, 2 deletions(-) diff --git a/drivers/gpu/drm/nouveau/nouveau_display.c b/drivers/gpu/drm/nouveau/nouveau_display.c index e547be5906a055..805d0a87aa5461 100644 --- a/drivers/gpu/drm/nouveau/nouveau_display.c +++ b/drivers/gpu/drm/nouveau/nouveau_display.c @@ -351,8 +351,6 @@ nouveau_user_framebuffer_create(struct drm_device *dev, static const struct drm_mode_config_funcs nouveau_mode_config_funcs = { .fb_create = nouveau_user_framebuffer_create, - .atomic_commit = drm_atomic_helper_commit, - .atomic_check = drm_atomic_helper_check, }; From edb9fab1b78c67b43e21189cf12fccab121c881d Mon Sep 17 00:00:00 2001 From: Chen Ni Date: Wed, 12 Nov 2025 15:27:09 +0800 Subject: [PATCH 2972/3902] net/sched: act_ife: convert comma to semicolon commit 205305c028ad986d0649b8b100bab6032dcd1bb5 upstream. Replace comma between expressions with semicolons. Using a ',' in place of a ';' can have unintended side effects. Although that is not the case here, it is seems best to use ';' unless ',' is intended. Found by inspection. No functional change intended. Compile tested only. Signed-off-by: Chen Ni Reviewed-by: Jamal Hadi Salim Link: https://patch.msgid.link/20251112072709.73755-1-nichen@iscas.ac.cn Signed-off-by: Jakub Kicinski Cc: Ben Hutchings Signed-off-by: Greg Kroah-Hartman --- net/sched/act_ife.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/net/sched/act_ife.c b/net/sched/act_ife.c index c7ab25642d997d..8e8f6af731d51c 100644 --- a/net/sched/act_ife.c +++ b/net/sched/act_ife.c @@ -649,9 +649,9 @@ static int tcf_ife_dump(struct sk_buff *skb, struct tc_action *a, int bind, memset(&opt, 0, sizeof(opt)); - opt.index = ife->tcf_index, - opt.refcnt = refcount_read(&ife->tcf_refcnt) - ref, - opt.bindcnt = atomic_read(&ife->tcf_bindcnt) - bind, + opt.index = ife->tcf_index; + opt.refcnt = refcount_read(&ife->tcf_refcnt) - ref; + opt.bindcnt = atomic_read(&ife->tcf_bindcnt) - bind; spin_lock_bh(&ife->tcf_lock); opt.action = ife->tcf_action; From 664e78f2d4ec3ea09c96ec96c0992b2f910979bb Mon Sep 17 00:00:00 2001 From: Tejun Heo Date: Thu, 29 Jan 2026 09:25:45 +0000 Subject: [PATCH 2973/3902] sched_ext: Don't kick CPUs running higher classes commit a9c1fbbd6dadbaa38c157a07d5d11005460b86b9 upstream. When a sched_ext scheduler tries to kick a CPU, the CPU may be running a higher class task. sched_ext has no control over such CPUs. A sched_ext scheduler couldn't have expected to get access to the CPU after kicking it anyway. Skip kicking when the target CPU is running a higher class. Reviewed-by: Andrea Righi Signed-off-by: Tejun Heo Signed-off-by: Christian Loehle Signed-off-by: Greg Kroah-Hartman --- kernel/sched/ext.c | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/kernel/sched/ext.c b/kernel/sched/ext.c index 31eda2a56920dc..3d53b22329379e 100644 --- a/kernel/sched/ext.c +++ b/kernel/sched/ext.c @@ -5164,18 +5164,23 @@ static bool kick_one_cpu(s32 cpu, struct rq *this_rq, unsigned long *pseqs) { struct rq *rq = cpu_rq(cpu); struct scx_rq *this_scx = &this_rq->scx; + const struct sched_class *cur_class; bool should_wait = false; unsigned long flags; raw_spin_rq_lock_irqsave(rq, flags); + cur_class = rq->curr->sched_class; /* * During CPU hotplug, a CPU may depend on kicking itself to make - * forward progress. Allow kicking self regardless of online state. + * forward progress. Allow kicking self regardless of online state. If + * @cpu is running a higher class task, we have no control over @cpu. + * Skip kicking. */ - if (cpu_online(cpu) || cpu == cpu_of(this_rq)) { + if ((cpu_online(cpu) || cpu == cpu_of(this_rq)) && + !sched_class_above(cur_class, &ext_sched_class)) { if (cpumask_test_cpu(cpu, this_scx->cpus_to_preempt)) { - if (rq->curr->sched_class == &ext_sched_class) + if (cur_class == &ext_sched_class) rq->curr->scx.slice = 0; cpumask_clear_cpu(cpu, this_scx->cpus_to_preempt); } From 01b0831d71b6d701ca6a5668ebcec6817ce39aac Mon Sep 17 00:00:00 2001 From: Tejun Heo Date: Thu, 29 Jan 2026 09:25:46 +0000 Subject: [PATCH 2974/3902] sched_ext: Fix SCX_KICK_WAIT to work reliably commit a379fa1e2cae15d7422b4eead83a6366f2f445cb upstream. SCX_KICK_WAIT is used to synchronously wait for the target CPU to complete a reschedule and can be used to implement operations like core scheduling. This used to be implemented by scx_next_task_picked() incrementing pnt_seq, which was always called when a CPU picks the next task to run, allowing SCX_KICK_WAIT to reliably wait for the target CPU to enter the scheduler and pick the next task. However, commit b999e365c298 ("sched_ext: Replace scx_next_task_picked() with switch_class()") replaced scx_next_task_picked() with the switch_class() callback, which is only called when switching between sched classes. This broke SCX_KICK_WAIT because pnt_seq would no longer be reliably incremented unless the previous task was SCX and the next task was not. This fix leverages commit 4c95380701f5 ("sched/ext: Fold balance_scx() into pick_task_scx()") which refactored the pick path making put_prev_task_scx() the natural place to track task switches for SCX_KICK_WAIT. The fix moves pnt_seq increment to put_prev_task_scx() and also increments it in pick_task_scx() to handle cases where the same task is re-selected, whether by BPF scheduler decision or slice refill. The semantics: If the current task on the target CPU is SCX, SCX_KICK_WAIT waits until the CPU enters the scheduling path. This provides sufficient guarantee for use cases like core scheduling while keeping the operation self-contained within SCX. v2: - Also increment pnt_seq in pick_task_scx() to handle same-task re-selection (Andrea Righi). - Use smp_cond_load_acquire() for the busy-wait loop for better architecture optimization (Peter Zijlstra). Reported-by: Wen-Fang Liu Link: http://lkml.kernel.org/r/228ebd9e6ed3437996dffe15735a9caa@honor.com Cc: Peter Zijlstra Reviewed-by: Andrea Righi Signed-off-by: Tejun Heo Signed-off-by: Christian Loehle Signed-off-by: Greg Kroah-Hartman --- kernel/sched/ext.c | 46 +++++++++++++++++++++---------------- kernel/sched/ext_internal.h | 6 +++-- 2 files changed, 30 insertions(+), 22 deletions(-) diff --git a/kernel/sched/ext.c b/kernel/sched/ext.c index 3d53b22329379e..2ff7034841c7cb 100644 --- a/kernel/sched/ext.c +++ b/kernel/sched/ext.c @@ -2306,12 +2306,6 @@ static void switch_class(struct rq *rq, struct task_struct *next) struct scx_sched *sch = scx_root; const struct sched_class *next_class = next->sched_class; - /* - * Pairs with the smp_load_acquire() issued by a CPU in - * kick_cpus_irq_workfn() who is waiting for this CPU to perform a - * resched. - */ - smp_store_release(&rq->scx.pnt_seq, rq->scx.pnt_seq + 1); if (!(sch->ops.flags & SCX_OPS_HAS_CPU_PREEMPT)) return; @@ -2351,6 +2345,10 @@ static void put_prev_task_scx(struct rq *rq, struct task_struct *p, struct task_struct *next) { struct scx_sched *sch = scx_root; + + /* see kick_cpus_irq_workfn() */ + smp_store_release(&rq->scx.pnt_seq, rq->scx.pnt_seq + 1); + update_curr_scx(rq); /* see dequeue_task_scx() on why we skip when !QUEUED */ @@ -2404,6 +2402,9 @@ static struct task_struct *pick_task_scx(struct rq *rq) bool keep_prev = rq->scx.flags & SCX_RQ_BAL_KEEP; bool kick_idle = false; + /* see kick_cpus_irq_workfn() */ + smp_store_release(&rq->scx.pnt_seq, rq->scx.pnt_seq + 1); + /* * WORKAROUND: * @@ -5186,8 +5187,12 @@ static bool kick_one_cpu(s32 cpu, struct rq *this_rq, unsigned long *pseqs) } if (cpumask_test_cpu(cpu, this_scx->cpus_to_wait)) { - pseqs[cpu] = rq->scx.pnt_seq; - should_wait = true; + if (cur_class == &ext_sched_class) { + pseqs[cpu] = rq->scx.pnt_seq; + should_wait = true; + } else { + cpumask_clear_cpu(cpu, this_scx->cpus_to_wait); + } } resched_curr(rq); @@ -5248,18 +5253,19 @@ static void kick_cpus_irq_workfn(struct irq_work *irq_work) for_each_cpu(cpu, this_scx->cpus_to_wait) { unsigned long *wait_pnt_seq = &cpu_rq(cpu)->scx.pnt_seq; - if (cpu != cpu_of(this_rq)) { - /* - * Pairs with smp_store_release() issued by this CPU in - * switch_class() on the resched path. - * - * We busy-wait here to guarantee that no other task can - * be scheduled on our core before the target CPU has - * entered the resched path. - */ - while (smp_load_acquire(wait_pnt_seq) == pseqs[cpu]) - cpu_relax(); - } + /* + * Busy-wait until the task running at the time of kicking is no + * longer running. This can be used to implement e.g. core + * scheduling. + * + * smp_cond_load_acquire() pairs with store_releases in + * pick_task_scx() and put_prev_task_scx(). The former breaks + * the wait if SCX's scheduling path is entered even if the same + * task is picked subsequently. The latter is necessary to break + * the wait when $cpu is taken by a higher sched class. + */ + if (cpu != cpu_of(this_rq)) + smp_cond_load_acquire(wait_pnt_seq, VAL != pseqs[cpu]); cpumask_clear_cpu(cpu, this_scx->cpus_to_wait); } diff --git a/kernel/sched/ext_internal.h b/kernel/sched/ext_internal.h index b3617abed51081..601cfae8cc7656 100644 --- a/kernel/sched/ext_internal.h +++ b/kernel/sched/ext_internal.h @@ -986,8 +986,10 @@ enum scx_kick_flags { SCX_KICK_PREEMPT = 1LLU << 1, /* - * Wait for the CPU to be rescheduled. The scx_bpf_kick_cpu() call will - * return after the target CPU finishes picking the next task. + * The scx_bpf_kick_cpu() call will return after the current SCX task of + * the target CPU switches out. This can be used to implement e.g. core + * scheduling. This has no effect if the current task on the target CPU + * is not on SCX. */ SCX_KICK_WAIT = 1LLU << 2, }; From 0d26aa84ff0b790d7c29c28c791bdf2c0ecdb57a Mon Sep 17 00:00:00 2001 From: "Matthieu Baerts (NGI0)" Date: Tue, 3 Feb 2026 12:37:17 -0500 Subject: [PATCH 2975/3902] mptcp: avoid dup SUB_CLOSED events after disconnect [ Upstream commit 280d654324e33f8e6e3641f76764694c7b64c5db ] In case of subflow disconnect(), which can also happen with the first subflow in case of errors like timeout or reset, mptcp_subflow_ctx_reset will reset most fields from the mptcp_subflow_context structure, including close_event_done. Then, when another subflow is closed, yet another SUB_CLOSED event for the disconnected initial subflow is sent. Because of the previous reset, there are no source address and destination port. A solution is then to also check the subflow's local id: it shouldn't be negative anyway. Another solution would be not to reset subflow->close_event_done at disconnect time, but when reused. But then, probably the whole reset could be done when being reused. Let's not change this logic, similar to TCP with tcp_disconnect(). Fixes: d82809b6c5f2 ("mptcp: avoid duplicated SUB_CLOSED events") Cc: stable@vger.kernel.org Reported-by: Marco Angaroni Closes: https://github.com/multipath-tcp/mptcp_net-next/issues/603 Reviewed-by: Geliang Tang Signed-off-by: Matthieu Baerts (NGI0) Link: https://patch.msgid.link/20260127-net-mptcp-dup-nl-events-v1-1-7f71e1bc4feb@kernel.org Signed-off-by: Jakub Kicinski [ Adjust context ] Signed-off-by: Sasha Levin Signed-off-by: Greg Kroah-Hartman --- net/mptcp/protocol.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/net/mptcp/protocol.c b/net/mptcp/protocol.c index 6ca9a37eabd117..e4bb7e2d7b19a0 100644 --- a/net/mptcp/protocol.c +++ b/net/mptcp/protocol.c @@ -2534,8 +2534,8 @@ static void __mptcp_close_ssk(struct sock *sk, struct sock *ssk, void mptcp_close_ssk(struct sock *sk, struct sock *ssk, struct mptcp_subflow_context *subflow) { - /* The first subflow can already be closed and still in the list */ - if (subflow->close_event_done) + /* The first subflow can already be closed or disconnected */ + if (subflow->close_event_done || READ_ONCE(subflow->local_id) < 0) return; subflow->close_event_done = true; From adb851edb70783e3ded28044491f5a3ed065b7b2 Mon Sep 17 00:00:00 2001 From: Greg Kroah-Hartman Date: Fri, 6 Feb 2026 16:57:45 +0100 Subject: [PATCH 2976/3902] Linux 6.18.9 Link: https://lore.kernel.org/r/20260204143851.857060534@linuxfoundation.org Tested-by: Achill Gilgenast = Tested-by: Brett A C Sheffield Tested-by: Salvatore Bonaccorso Tested-by: Florian Fainelli Tested-by: Jon Hunter Tested-by: Justin M. Forbes Tested-by: Takeshi Ogasawara Tested-by: Peter Schneider Tested-by: Luna Jernberg Tested-by: Ron Economos Tested-by: Mark Brown Tested-by: Brett Mastbergen Tested-by: Hardik Garg Tested-by: Barry K. Nathan Tested-by: Shung-Hsi Yu Tested-by: Dileep Malepu Tested-by: Miguel Ojeda Tested-by: Jeffrin Jose T Signed-off-by: Greg Kroah-Hartman --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 5767f0a7d07ae1..64af72f16125b6 100644 --- a/Makefile +++ b/Makefile @@ -1,7 +1,7 @@ # SPDX-License-Identifier: GPL-2.0 VERSION = 6 PATCHLEVEL = 18 -SUBLEVEL = 8 +SUBLEVEL = 9 EXTRAVERSION = NAME = Baby Opossum Posse From 96070048c442c13c805f8987c5d0c81ded2391e4 Mon Sep 17 00:00:00 2001 From: Fiona Behrens Date: Thu, 5 Feb 2026 15:02:16 +0000 Subject: [PATCH 2977/3902] rust: iio: common: Change cfg guard of aop_sensors module Change the cfg guard of the `aop_sensors` module to the direct config symbols `CONFIG_IIO_AOP_SENSORS_LAS` and `CONFIG_IIO_AOP_SENSORS_ALS` without checking if they are set as either module or yes. This fixes a build error when `CONFIG_IIO_AOP_SENSORS_LAS` is set to module and `CONFIG_IIO_AOP_SENSORS_ALS` is disabled. Signed-off-by: Fiona Behrens --- rust/kernel/iio/common/mod.rs | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/rust/kernel/iio/common/mod.rs b/rust/kernel/iio/common/mod.rs index 570644ce0938a7..b789e9bf44c9bf 100644 --- a/rust/kernel/iio/common/mod.rs +++ b/rust/kernel/iio/common/mod.rs @@ -2,10 +2,5 @@ //! IIO common modules -#[cfg(any( - CONFIG_IIO_AOP_SENSOR_LAS = "y", - CONFIG_IIO_AOP_SENSOR_ALS = "m", - CONFIG_IIO_AOP_SENSOR_LAS = "y", - CONFIG_IIO_AOP_SENSOR_ALS = "m", -))] +#[cfg(any(CONFIG_IIO_AOP_SENSOR_LAS, CONFIG_IIO_AOP_SENSOR_ALS,))] pub mod aop_sensors; From ab200d71553bdcf4de554a5985b05b2dd606bc57 Mon Sep 17 00:00:00 2001 From: YunJe Shin Date: Wed, 28 Jan 2026 09:41:07 +0900 Subject: [PATCH 2978/3902] nvmet-tcp: add bounds checks in nvmet_tcp_build_pdu_iovec commit 52a0a98549344ca20ad81a4176d68d28e3c05a5c upstream. nvmet_tcp_build_pdu_iovec() could walk past cmd->req.sg when a PDU length or offset exceeds sg_cnt and then use bogus sg->length/offset values, leading to _copy_to_iter() GPF/KASAN. Guard sg_idx, remaining entries, and sg->length/offset before building the bvec. Fixes: 872d26a391da ("nvmet-tcp: add NVMe over TCP target driver") Signed-off-by: YunJe Shin Reviewed-by: Sagi Grimberg Reviewed-by: Joonkyo Jung Signed-off-by: Keith Busch Signed-off-by: Greg Kroah-Hartman --- drivers/nvme/target/tcp.c | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/drivers/nvme/target/tcp.c b/drivers/nvme/target/tcp.c index 41b6fd05519e4c..f0572fc0b65984 100644 --- a/drivers/nvme/target/tcp.c +++ b/drivers/nvme/target/tcp.c @@ -349,11 +349,14 @@ static void nvmet_tcp_free_cmd_buffers(struct nvmet_tcp_cmd *cmd) cmd->req.sg = NULL; } +static void nvmet_tcp_fatal_error(struct nvmet_tcp_queue *queue); + static void nvmet_tcp_build_pdu_iovec(struct nvmet_tcp_cmd *cmd) { struct bio_vec *iov = cmd->iov; struct scatterlist *sg; u32 length, offset, sg_offset; + unsigned int sg_remaining; int nr_pages; length = cmd->pdu_len; @@ -361,9 +364,22 @@ static void nvmet_tcp_build_pdu_iovec(struct nvmet_tcp_cmd *cmd) offset = cmd->rbytes_done; cmd->sg_idx = offset / PAGE_SIZE; sg_offset = offset % PAGE_SIZE; + if (!cmd->req.sg_cnt || cmd->sg_idx >= cmd->req.sg_cnt) { + nvmet_tcp_fatal_error(cmd->queue); + return; + } sg = &cmd->req.sg[cmd->sg_idx]; + sg_remaining = cmd->req.sg_cnt - cmd->sg_idx; while (length) { + if (!sg_remaining) { + nvmet_tcp_fatal_error(cmd->queue); + return; + } + if (!sg->length || sg->length <= sg_offset) { + nvmet_tcp_fatal_error(cmd->queue); + return; + } u32 iov_len = min_t(u32, length, sg->length - sg_offset); bvec_set_page(iov, sg_page(sg), iov_len, @@ -371,6 +387,7 @@ static void nvmet_tcp_build_pdu_iovec(struct nvmet_tcp_cmd *cmd) length -= iov_len; sg = sg_next(sg); + sg_remaining--; iov++; sg_offset = 0; } From feb603a69f830acb58f78d604f0c29e63cd38f87 Mon Sep 17 00:00:00 2001 From: Josh Poimboeuf Date: Fri, 6 Feb 2026 14:24:55 -0800 Subject: [PATCH 2979/3902] x86/vmware: Fix hypercall clobbers commit 2687c848e57820651b9f69d30c4710f4219f7dbf upstream. Fedora QA reported the following panic: BUG: unable to handle page fault for address: 0000000040003e54 #PF: supervisor write access in kernel mode #PF: error_code(0x0002) - not-present page Hardware name: QEMU Standard PC (Q35 + ICH9, 2009), BIOS edk2-20251119-3.fc43 11/19/2025 RIP: 0010:vmware_hypercall4.constprop.0+0x52/0x90 .. Call Trace: vmmouse_report_events+0x13e/0x1b0 psmouse_handle_byte+0x15/0x60 ps2_interrupt+0x8a/0xd0 ... because the QEMU VMware mouse emulation is buggy, and clears the top 32 bits of %rdi that the kernel kept a pointer in. The QEMU vmmouse driver saves and restores the register state in a "uint32_t data[6];" and as a result restores the state with the high bits all cleared. RDI originally contained the value of a valid kernel stack address (0xff5eeb3240003e54). After the vmware hypercall it now contains 0x40003e54, and we get a page fault as a result when it is dereferenced. The proper fix would be in QEMU, but this works around the issue in the kernel to keep old setups working, when old kernels had not happened to keep any state in %rdi over the hypercall. In theory this same issue exists for all the hypercalls in the vmmouse driver; in practice it has only been seen with vmware_hypercall3() and vmware_hypercall4(). For now, just mark RDI/RSI as clobbered for those two calls. This should have a minimal effect on code generation overall as it should be rare for the compiler to want to make RDI/RSI live across hypercalls. Reported-by: Justin Forbes Link: https://lore.kernel.org/all/99a9c69a-fc1a-43b7-8d1e-c42d6493b41f@broadcom.com/ Signed-off-by: Josh Poimboeuf Cc: stable@kernel.org Signed-off-by: Linus Torvalds Signed-off-by: Greg Kroah-Hartman --- arch/x86/include/asm/vmware.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/arch/x86/include/asm/vmware.h b/arch/x86/include/asm/vmware.h index c9cf43d5ef238c..4220dae14a2d9f 100644 --- a/arch/x86/include/asm/vmware.h +++ b/arch/x86/include/asm/vmware.h @@ -140,7 +140,7 @@ unsigned long vmware_hypercall3(unsigned long cmd, unsigned long in1, "b" (in1), "c" (cmd), "d" (0) - : "cc", "memory"); + : "di", "si", "cc", "memory"); return out0; } @@ -165,7 +165,7 @@ unsigned long vmware_hypercall4(unsigned long cmd, unsigned long in1, "b" (in1), "c" (cmd), "d" (0) - : "cc", "memory"); + : "di", "si", "cc", "memory"); return out0; } From a94b956bb7272ee8acdb65aae143c4d909c7d7fa Mon Sep 17 00:00:00 2001 From: Andrew Cooper Date: Mon, 26 Jan 2026 21:10:46 +0000 Subject: [PATCH 2980/3902] x86/kfence: fix booting on 32bit non-PAE systems commit 16459fe7e0ca6520a6e8f603de4ccd52b90fd765 upstream. The original patch inverted the PTE unconditionally to avoid L1TF-vulnerable PTEs, but Linux doesn't make this adjustment in 2-level paging. Adjust the logic to use the flip_protnone_guard() helper, which is a nop on 2-level paging but inverts the address bits in all other paging modes. This doesn't matter for the Xen aspect of the original change. Linux no longer supports running 32bit PV under Xen, and Xen doesn't support running any 32bit PV guests without using PAE paging. Link: https://lkml.kernel.org/r/20260126211046.2096622-1-andrew.cooper3@citrix.com Fixes: b505f1944535 ("x86/kfence: avoid writing L1TF-vulnerable PTEs") Reported-by: Ryusuke Konishi Closes: https://lore.kernel.org/lkml/CAKFNMokwjw68ubYQM9WkzOuH51wLznHpEOMSqtMoV1Rn9JV_gw@mail.gmail.com/ Signed-off-by: Andrew Cooper Tested-by: Ryusuke Konishi Tested-by: Borislav Petkov (AMD) Cc: Alexander Potapenko Cc: Marco Elver Cc: Dmitry Vyukov Cc: Thomas Gleixner Cc: Ingo Molnar Cc: Dave Hansen Cc: "H. Peter Anvin" Cc: Jann Horn Cc: Signed-off-by: Andrew Morton Signed-off-by: Greg Kroah-Hartman --- arch/x86/include/asm/kfence.h | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/arch/x86/include/asm/kfence.h b/arch/x86/include/asm/kfence.h index acf9ffa1a17183..dfd5c74ba41a2f 100644 --- a/arch/x86/include/asm/kfence.h +++ b/arch/x86/include/asm/kfence.h @@ -42,7 +42,7 @@ static inline bool kfence_protect_page(unsigned long addr, bool protect) { unsigned int level; pte_t *pte = lookup_address(addr, &level); - pteval_t val; + pteval_t val, new; if (WARN_ON(!pte || level != PG_LEVEL_4K)) return false; @@ -57,11 +57,12 @@ static inline bool kfence_protect_page(unsigned long addr, bool protect) return true; /* - * Otherwise, invert the entire PTE. This avoids writing out an + * Otherwise, flip the Present bit, taking care to avoid writing an * L1TF-vulnerable PTE (not present, without the high address bits * set). */ - set_pte(pte, __pte(~val)); + new = val ^ _PAGE_PRESENT; + set_pte(pte, __pte(flip_protnone_guard(val, new, PTE_PFN_MASK))); /* * If the page was protected (non-present) and we're making it From b5a02290ee3a44a62c9e855f7ef146e790219287 Mon Sep 17 00:00:00 2001 From: Sean Christopherson Date: Tue, 27 Jan 2026 17:43:08 -0800 Subject: [PATCH 2981/3902] KVM: x86: Explicitly configure supported XSS from {svm,vmx}_set_cpu_caps() commit f8ade833b733ae0b72e87ac6d2202a1afbe3eb4a upstream. Explicitly configure KVM's supported XSS as part of each vendor's setup flow to fix a bug where clearing SHSTK and IBT in kvm_cpu_caps, e.g. due to lack of CET XFEATURE support, makes kvm-intel.ko unloadable when nested VMX is enabled, i.e. when nested=1. The late clearing results in nested_vmx_setup_{entry,exit}_ctls() clearing VM_{ENTRY,EXIT}_LOAD_CET_STATE when nested_vmx_setup_ctls_msrs() runs during the CPU compatibility checks, ultimately leading to a mismatched VMCS config due to the reference config having the CET bits set, but every CPU's "local" config having the bits cleared. Note, kvm_caps.supported_{xcr0,xss} are unconditionally initialized by kvm_x86_vendor_init(), before calling into vendor code, and not referenced between ops->hardware_setup() and their current/old location. Fixes: 69cc3e886582 ("KVM: x86: Add XSS support for CET_KERNEL and CET_USER") Cc: stable@vger.kernel.org Cc: Mathias Krause Cc: John Allen Cc: Rick Edgecombe Cc: Chao Gao Cc: Binbin Wu Cc: Xiaoyao Li Reviewed-by: Xiaoyao Li Reviewed-by: Binbin Wu Link: https://patch.msgid.link/20260128014310.3255561-2-seanjc@google.com Signed-off-by: Sean Christopherson Signed-off-by: Greg Kroah-Hartman --- arch/x86/kvm/svm/svm.c | 2 ++ arch/x86/kvm/vmx/vmx.c | 2 ++ arch/x86/kvm/x86.c | 30 +++++++++++++++++------------- arch/x86/kvm/x86.h | 2 ++ 4 files changed, 23 insertions(+), 13 deletions(-) diff --git a/arch/x86/kvm/svm/svm.c b/arch/x86/kvm/svm/svm.c index 3b215c5b5b01d6..d758bff6e068bb 100644 --- a/arch/x86/kvm/svm/svm.c +++ b/arch/x86/kvm/svm/svm.c @@ -5285,6 +5285,8 @@ static __init void svm_set_cpu_caps(void) */ kvm_cpu_cap_clear(X86_FEATURE_BUS_LOCK_DETECT); kvm_cpu_cap_clear(X86_FEATURE_MSR_IMM); + + kvm_setup_xss_caps(); } static __init int svm_hardware_setup(void) diff --git a/arch/x86/kvm/vmx/vmx.c b/arch/x86/kvm/vmx/vmx.c index 91b6f2f3edc2ab..c084f48e2b0b98 100644 --- a/arch/x86/kvm/vmx/vmx.c +++ b/arch/x86/kvm/vmx/vmx.c @@ -8021,6 +8021,8 @@ static __init void vmx_set_cpu_caps(void) kvm_cpu_cap_clear(X86_FEATURE_SHSTK); kvm_cpu_cap_clear(X86_FEATURE_IBT); } + + kvm_setup_xss_caps(); } static bool vmx_is_io_intercepted(struct kvm_vcpu *vcpu, diff --git a/arch/x86/kvm/x86.c b/arch/x86/kvm/x86.c index 43fb2a05a91c96..c075ee23aeadef 100644 --- a/arch/x86/kvm/x86.c +++ b/arch/x86/kvm/x86.c @@ -9954,6 +9954,23 @@ static struct notifier_block pvclock_gtod_notifier = { }; #endif +void kvm_setup_xss_caps(void) +{ + if (!kvm_cpu_cap_has(X86_FEATURE_XSAVES)) + kvm_caps.supported_xss = 0; + + if (!kvm_cpu_cap_has(X86_FEATURE_SHSTK) && + !kvm_cpu_cap_has(X86_FEATURE_IBT)) + kvm_caps.supported_xss &= ~XFEATURE_MASK_CET_ALL; + + if ((kvm_caps.supported_xss & XFEATURE_MASK_CET_ALL) != XFEATURE_MASK_CET_ALL) { + kvm_cpu_cap_clear(X86_FEATURE_SHSTK); + kvm_cpu_cap_clear(X86_FEATURE_IBT); + kvm_caps.supported_xss &= ~XFEATURE_MASK_CET_ALL; + } +} +EXPORT_SYMBOL_FOR_KVM_INTERNAL(kvm_setup_xss_caps); + static inline void kvm_ops_update(struct kvm_x86_init_ops *ops) { memcpy(&kvm_x86_ops, ops->runtime_ops, sizeof(kvm_x86_ops)); @@ -10132,19 +10149,6 @@ int kvm_x86_vendor_init(struct kvm_x86_init_ops *ops) if (!tdp_enabled) kvm_caps.supported_quirks &= ~KVM_X86_QUIRK_IGNORE_GUEST_PAT; - if (!kvm_cpu_cap_has(X86_FEATURE_XSAVES)) - kvm_caps.supported_xss = 0; - - if (!kvm_cpu_cap_has(X86_FEATURE_SHSTK) && - !kvm_cpu_cap_has(X86_FEATURE_IBT)) - kvm_caps.supported_xss &= ~XFEATURE_MASK_CET_ALL; - - if ((kvm_caps.supported_xss & XFEATURE_MASK_CET_ALL) != XFEATURE_MASK_CET_ALL) { - kvm_cpu_cap_clear(X86_FEATURE_SHSTK); - kvm_cpu_cap_clear(X86_FEATURE_IBT); - kvm_caps.supported_xss &= ~XFEATURE_MASK_CET_ALL; - } - if (kvm_caps.has_tsc_control) { /* * Make sure the user can only configure tsc_khz values that diff --git a/arch/x86/kvm/x86.h b/arch/x86/kvm/x86.h index f3dc77f006f904..c8a561c17e9ad3 100644 --- a/arch/x86/kvm/x86.h +++ b/arch/x86/kvm/x86.h @@ -457,6 +457,8 @@ extern struct kvm_host_values kvm_host; extern bool enable_pmu; +void kvm_setup_xss_caps(void); + /* * Get a filtered version of KVM's supported XCR0 that strips out dynamic * features for which the current process doesn't (yet) have permission to use. From 21816bbc8492f8be5e2de3e06e7a0301d54078f2 Mon Sep 17 00:00:00 2001 From: Kaushlendra Kumar Date: Wed, 24 Dec 2025 08:50:53 +0530 Subject: [PATCH 2982/3902] platform/x86: intel_telemetry: Fix swapped arrays in PSS output MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit commit 25e9e322d2ab5c03602eff4fbf4f7c40019d8de2 upstream. The LTR blocking statistics and wakeup event counters are incorrectly cross-referenced during debugfs output rendering. The code populates pss_ltr_blkd[] with LTR blocking data and pss_s0ix_wakeup[] with wakeup data, but the display loops reference the wrong arrays. This causes the "LTR Blocking Status" section to print wakeup events and the "Wakes Status" section to print LTR blockers, misleading power management analysis and S0ix residency debugging. Fix by aligning array usage with the intended output section labels. Fixes: 87bee290998d ("platform:x86: Add Intel Telemetry Debugfs interfaces") Cc: stable@vger.kernel.org Signed-off-by: Kaushlendra Kumar Link: https://patch.msgid.link/20251224032053.3915900-1-kaushlendra.kumar@intel.com Reviewed-by: Ilpo Järvinen Signed-off-by: Ilpo Järvinen Signed-off-by: Greg Kroah-Hartman --- drivers/platform/x86/intel/telemetry/debugfs.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/platform/x86/intel/telemetry/debugfs.c b/drivers/platform/x86/intel/telemetry/debugfs.c index 70e5736c44c71a..189c61ff7ff0c0 100644 --- a/drivers/platform/x86/intel/telemetry/debugfs.c +++ b/drivers/platform/x86/intel/telemetry/debugfs.c @@ -449,7 +449,7 @@ static int telem_pss_states_show(struct seq_file *s, void *unused) for (index = 0; index < debugfs_conf->pss_ltr_evts; index++) { seq_printf(s, "%-32s\t%u\n", debugfs_conf->pss_ltr_data[index].name, - pss_s0ix_wakeup[index]); + pss_ltr_blkd[index]); } seq_puts(s, "\n--------------------------------------\n"); @@ -459,7 +459,7 @@ static int telem_pss_states_show(struct seq_file *s, void *unused) for (index = 0; index < debugfs_conf->pss_wakeup_evts; index++) { seq_printf(s, "%-32s\t%u\n", debugfs_conf->pss_wakeup[index].name, - pss_ltr_blkd[index]); + pss_s0ix_wakeup[index]); } return 0; From 5727ccf9d19ca414cb76d9b647883822e2789c2e Mon Sep 17 00:00:00 2001 From: Takashi Iwai Date: Tue, 3 Feb 2026 15:09:59 +0100 Subject: [PATCH 2983/3902] ALSA: aloop: Fix racy access at PCM trigger commit 826af7fa62e347464b1b4e0ba2fe19a92438084f upstream. The PCM trigger callback of aloop driver tries to check the PCM state and stop the stream of the tied substream in the corresponding cable. Since both check and stop operations are performed outside the cable lock, this may result in UAF when a program attempts to trigger frequently while opening/closing the tied stream, as spotted by fuzzers. For addressing the UAF, this patch changes two things: - It covers the most of code in loopback_check_format() with cable->lock spinlock, and add the proper NULL checks. This avoids already some racy accesses. - In addition, now we try to check the state of the capture PCM stream that may be stopped in this function, which was the major pain point leading to UAF. Reported-by: syzbot+5f8f3acdee1ec7a7ef7b@syzkaller.appspotmail.com Closes: https://lore.kernel.org/69783ba1.050a0220.c9109.0011.GAE@google.com Cc: Link: https://patch.msgid.link/20260203141003.116584-1-tiwai@suse.de Signed-off-by: Takashi Iwai Signed-off-by: Greg Kroah-Hartman --- sound/drivers/aloop.c | 62 +++++++++++++++++++++++++------------------ 1 file changed, 36 insertions(+), 26 deletions(-) diff --git a/sound/drivers/aloop.c b/sound/drivers/aloop.c index 64ef03b2d579c9..aa0d2fcb1a180c 100644 --- a/sound/drivers/aloop.c +++ b/sound/drivers/aloop.c @@ -336,37 +336,43 @@ static bool is_access_interleaved(snd_pcm_access_t access) static int loopback_check_format(struct loopback_cable *cable, int stream) { + struct loopback_pcm *dpcm_play, *dpcm_capt; struct snd_pcm_runtime *runtime, *cruntime; struct loopback_setup *setup; struct snd_card *card; + bool stop_capture = false; int check; - if (cable->valid != CABLE_VALID_BOTH) { - if (stream == SNDRV_PCM_STREAM_PLAYBACK) - goto __notify; - return 0; - } - runtime = cable->streams[SNDRV_PCM_STREAM_PLAYBACK]-> - substream->runtime; - cruntime = cable->streams[SNDRV_PCM_STREAM_CAPTURE]-> - substream->runtime; - check = runtime->format != cruntime->format || - runtime->rate != cruntime->rate || - runtime->channels != cruntime->channels || - is_access_interleaved(runtime->access) != - is_access_interleaved(cruntime->access); - if (!check) - return 0; - if (stream == SNDRV_PCM_STREAM_CAPTURE) { - return -EIO; - } else { - snd_pcm_stop(cable->streams[SNDRV_PCM_STREAM_CAPTURE]-> - substream, SNDRV_PCM_STATE_DRAINING); - __notify: - runtime = cable->streams[SNDRV_PCM_STREAM_PLAYBACK]-> - substream->runtime; - setup = get_setup(cable->streams[SNDRV_PCM_STREAM_PLAYBACK]); - card = cable->streams[SNDRV_PCM_STREAM_PLAYBACK]->loopback->card; + scoped_guard(spinlock_irqsave, &cable->lock) { + dpcm_play = cable->streams[SNDRV_PCM_STREAM_PLAYBACK]; + dpcm_capt = cable->streams[SNDRV_PCM_STREAM_CAPTURE]; + + if (cable->valid != CABLE_VALID_BOTH) { + if (stream == SNDRV_PCM_STREAM_CAPTURE || !dpcm_play) + return 0; + } else { + if (!dpcm_play || !dpcm_capt) + return -EIO; + runtime = dpcm_play->substream->runtime; + cruntime = dpcm_capt->substream->runtime; + if (!runtime || !cruntime) + return -EIO; + check = runtime->format != cruntime->format || + runtime->rate != cruntime->rate || + runtime->channels != cruntime->channels || + is_access_interleaved(runtime->access) != + is_access_interleaved(cruntime->access); + if (!check) + return 0; + if (stream == SNDRV_PCM_STREAM_CAPTURE) + return -EIO; + else if (cruntime->state == SNDRV_PCM_STATE_RUNNING) + stop_capture = true; + } + + setup = get_setup(dpcm_play); + card = dpcm_play->loopback->card; + runtime = dpcm_play->substream->runtime; if (setup->format != runtime->format) { snd_ctl_notify(card, SNDRV_CTL_EVENT_MASK_VALUE, &setup->format_id); @@ -389,6 +395,10 @@ static int loopback_check_format(struct loopback_cable *cable, int stream) setup->access = runtime->access; } } + + if (stop_capture) + snd_pcm_stop(dpcm_capt->substream, SNDRV_PCM_STATE_DRAINING); + return 0; } From d72563e402bab5c8599b728e2d000ffea63ee493 Mon Sep 17 00:00:00 2001 From: Gabor Juhos Date: Thu, 22 Jan 2026 18:20:12 +0100 Subject: [PATCH 2984/3902] pmdomain: qcom: rpmpd: fix off-by-one error in clamping to the highest state commit 8aa6f7697f5981d336cac7af6ddd182a03c6da01 upstream. As it is indicated by the comment, the rpmpd_aggregate_corner() function tries to clamp the state to the highest corner/level supported by the given power domain, however the calculation of the highest state contains an off-by-one error. The 'max_state' member of the 'rpmpd' structure indicates the highest corner/level, and as such it does not needs to be decremented. Change the code to use the 'max_state' value directly to avoid the error. Fixes: 98c8b3efacae ("soc: qcom: rpmpd: Add sync_state") Signed-off-by: Gabor Juhos Reviewed-by: Konrad Dybcio Cc: stable@vger.kernel.org Signed-off-by: Ulf Hansson Signed-off-by: Greg Kroah-Hartman --- drivers/pmdomain/qcom/rpmpd.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/pmdomain/qcom/rpmpd.c b/drivers/pmdomain/qcom/rpmpd.c index f8580ec0f73785..98ab4f9ea9bff4 100644 --- a/drivers/pmdomain/qcom/rpmpd.c +++ b/drivers/pmdomain/qcom/rpmpd.c @@ -1001,7 +1001,7 @@ static int rpmpd_aggregate_corner(struct rpmpd *pd) /* Clamp to the highest corner/level if sync_state isn't done yet */ if (!pd->state_synced) - this_active_corner = this_sleep_corner = pd->max_state - 1; + this_active_corner = this_sleep_corner = pd->max_state; else to_active_sleep(pd, pd->corner, &this_active_corner, &this_sleep_corner); From 72129d55be9ce87f674e65b7169d672e11a6f24c Mon Sep 17 00:00:00 2001 From: Xu Yang Date: Wed, 4 Feb 2026 19:11:41 +0800 Subject: [PATCH 2985/3902] pmdomain: imx8mp-blk-ctrl: Keep gpc power domain on for system wakeup commit e9ab2b83893dd03cf04d98faded81190e635233f upstream. Current design will power off all dependent GPC power domains in imx8mp_blk_ctrl_suspend(), even though the user device has enabled wakeup capability. The result is that wakeup function never works for such device. An example will be USB wakeup on i.MX8MP. PHY device '382f0040.usb-phy' is attached to power domain 'hsioblk-usb-phy2' which is spawned by hsio block control. A virtual power domain device 'genpd:3:32f10000.blk-ctrl' is created to build connection with 'hsioblk-usb-phy2' and it depends on GPC power domain 'usb-otg2'. If device '382f0040.usb-phy' enable wakeup, only power domain 'hsioblk-usb-phy2' keeps on during system suspend, power domain 'usb-otg2' is off all the time. So the wakeup event can't happen. In order to further establish a connection between the power domains related to GPC and block control during system suspend, register a genpd power on/off notifier for the power_dev. This allows us to prevent the GPC power domain from being powered off, in case the block control power domain is kept on to serve system wakeup. Suggested-by: Ulf Hansson Fixes: 556f5cf9568a ("soc: imx: add i.MX8MP HSIO blk-ctrl") Cc: stable@vger.kernel.org Signed-off-by: Xu Yang Signed-off-by: Ulf Hansson Signed-off-by: Greg Kroah-Hartman --- drivers/pmdomain/imx/imx8mp-blk-ctrl.c | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/drivers/pmdomain/imx/imx8mp-blk-ctrl.c b/drivers/pmdomain/imx/imx8mp-blk-ctrl.c index 34576be606e31a..56bbfee8668d34 100644 --- a/drivers/pmdomain/imx/imx8mp-blk-ctrl.c +++ b/drivers/pmdomain/imx/imx8mp-blk-ctrl.c @@ -65,6 +65,7 @@ struct imx8mp_blk_ctrl_domain { struct icc_bulk_data paths[DOMAIN_MAX_PATHS]; struct device *power_dev; struct imx8mp_blk_ctrl *bc; + struct notifier_block power_nb; int num_paths; int id; }; @@ -594,6 +595,20 @@ static int imx8mp_blk_ctrl_power_off(struct generic_pm_domain *genpd) return 0; } +static int imx8mp_blk_ctrl_gpc_notifier(struct notifier_block *nb, + unsigned long action, void *data) +{ + struct imx8mp_blk_ctrl_domain *domain = + container_of(nb, struct imx8mp_blk_ctrl_domain, power_nb); + + if (action == GENPD_NOTIFY_PRE_OFF) { + if (domain->genpd.status == GENPD_STATE_ON) + return NOTIFY_BAD; + } + + return NOTIFY_OK; +} + static struct lock_class_key blk_ctrl_genpd_lock_class; static int imx8mp_blk_ctrl_probe(struct platform_device *pdev) @@ -698,6 +713,14 @@ static int imx8mp_blk_ctrl_probe(struct platform_device *pdev) goto cleanup_pds; } + domain->power_nb.notifier_call = imx8mp_blk_ctrl_gpc_notifier; + ret = dev_pm_genpd_add_notifier(domain->power_dev, &domain->power_nb); + if (ret) { + dev_err_probe(dev, ret, "failed to add power notifier\n"); + dev_pm_domain_detach(domain->power_dev, true); + goto cleanup_pds; + } + domain->genpd.name = data->name; domain->genpd.power_on = imx8mp_blk_ctrl_power_on; domain->genpd.power_off = imx8mp_blk_ctrl_power_off; @@ -707,6 +730,7 @@ static int imx8mp_blk_ctrl_probe(struct platform_device *pdev) ret = pm_genpd_init(&domain->genpd, NULL, true); if (ret) { dev_err_probe(dev, ret, "failed to init power domain\n"); + dev_pm_genpd_remove_notifier(domain->power_dev); dev_pm_domain_detach(domain->power_dev, true); goto cleanup_pds; } @@ -755,6 +779,7 @@ static int imx8mp_blk_ctrl_probe(struct platform_device *pdev) cleanup_pds: for (i--; i >= 0; i--) { pm_genpd_remove(&bc->domains[i].genpd); + dev_pm_genpd_remove_notifier(bc->domains[i].power_dev); dev_pm_domain_detach(bc->domains[i].power_dev, true); } @@ -774,6 +799,7 @@ static void imx8mp_blk_ctrl_remove(struct platform_device *pdev) struct imx8mp_blk_ctrl_domain *domain = &bc->domains[i]; pm_genpd_remove(&domain->genpd); + dev_pm_genpd_remove_notifier(domain->power_dev); dev_pm_domain_detach(domain->power_dev, true); } From 5171a3dddf427e190cca75c31f49911cdd103af6 Mon Sep 17 00:00:00 2001 From: Jacky Bai Date: Fri, 23 Jan 2026 10:51:26 +0800 Subject: [PATCH 2986/3902] pmdomain: imx: gpcv2: Fix the imx8mm gpu hang due to wrong adb400 reset MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit commit ae0a24c5a8dcea20bf8e344eadf6593e6d1959c3 upstream. On i.MX8MM, the GPUMIX, GPU2D, and GPU3D blocks share a common reset domain. Due to this hardware limitation, powering off/on GPU2D or GPU3D also triggers a reset of the GPUMIX domain, including its ADB400 port. However, the ADB400 interface must always be placed into power‑down mode before being reset. Currently the GPUMIX and GPU2D/3D power domains rely on runtime PM to handle dependency ordering. In some corner cases, the GPUMIX power off sequence is skipped, leaving the ADB400 port active when GPU2D/3D reset. This causes the GPUMIX ADB400 port to be reset while still active, leading to unpredictable bus behavior and GPU hangs. To avoid this, refine the power‑domain control logic so that the GPUMIX ADB400 port is explicitly powered down and powered up as part of the GPU power domain on/off sequence. This ensures proper ordering and prevents incorrect ADB400 reset. Suggested-by: Lucas Stach Signed-off-by: Jacky Bai Reviewed-by: Lucas Stach Tested-by: Philipp Zabel Cc: stable@vger.kernel.org Signed-off-by: Ulf Hansson Signed-off-by: Greg Kroah-Hartman --- drivers/pmdomain/imx/gpcv2.c | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/drivers/pmdomain/imx/gpcv2.c b/drivers/pmdomain/imx/gpcv2.c index 105fcaf13a34c7..cff738e4d54622 100644 --- a/drivers/pmdomain/imx/gpcv2.c +++ b/drivers/pmdomain/imx/gpcv2.c @@ -165,13 +165,11 @@ #define IMX8M_VPU_HSK_PWRDNREQN BIT(5) #define IMX8M_DISP_HSK_PWRDNREQN BIT(4) -#define IMX8MM_GPUMIX_HSK_PWRDNACKN BIT(29) -#define IMX8MM_GPU_HSK_PWRDNACKN (BIT(27) | BIT(28)) +#define IMX8MM_GPU_HSK_PWRDNACKN GENMASK(29, 27) #define IMX8MM_VPUMIX_HSK_PWRDNACKN BIT(26) #define IMX8MM_DISPMIX_HSK_PWRDNACKN BIT(25) #define IMX8MM_HSIO_HSK_PWRDNACKN (BIT(23) | BIT(24)) -#define IMX8MM_GPUMIX_HSK_PWRDNREQN BIT(11) -#define IMX8MM_GPU_HSK_PWRDNREQN (BIT(9) | BIT(10)) +#define IMX8MM_GPU_HSK_PWRDNREQN GENMASK(11, 9) #define IMX8MM_VPUMIX_HSK_PWRDNREQN BIT(8) #define IMX8MM_DISPMIX_HSK_PWRDNREQN BIT(7) #define IMX8MM_HSIO_HSK_PWRDNREQN (BIT(5) | BIT(6)) @@ -794,8 +792,6 @@ static const struct imx_pgc_domain imx8mm_pgc_domains[] = { .bits = { .pxx = IMX8MM_GPUMIX_SW_Pxx_REQ, .map = IMX8MM_GPUMIX_A53_DOMAIN, - .hskreq = IMX8MM_GPUMIX_HSK_PWRDNREQN, - .hskack = IMX8MM_GPUMIX_HSK_PWRDNACKN, }, .pgc = BIT(IMX8MM_PGC_GPUMIX), .keep_clocks = true, From 11ca03ce17d7d23d92b09312361496fa7de0cb1e Mon Sep 17 00:00:00 2001 From: Xu Yang Date: Wed, 4 Feb 2026 19:11:42 +0800 Subject: [PATCH 2987/3902] pmdomain: imx8mp-blk-ctrl: Keep usb phy power domain on for system wakeup commit e2c4c5b2bbd4f688a0f9f6da26cdf6d723c53478 upstream. USB system wakeup need its PHY on, so add the GENPD_FLAG_ACTIVE_WAKEUP flags to USB PHY genpd configuration. Signed-off-by: Xu Yang Fixes: 556f5cf9568a ("soc: imx: add i.MX8MP HSIO blk-ctrl") Cc: stable@vger.kernel.org Signed-off-by: Ulf Hansson Signed-off-by: Greg Kroah-Hartman --- drivers/pmdomain/imx/imx8mp-blk-ctrl.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/drivers/pmdomain/imx/imx8mp-blk-ctrl.c b/drivers/pmdomain/imx/imx8mp-blk-ctrl.c index 56bbfee8668d34..8fc79f9723f07e 100644 --- a/drivers/pmdomain/imx/imx8mp-blk-ctrl.c +++ b/drivers/pmdomain/imx/imx8mp-blk-ctrl.c @@ -53,6 +53,7 @@ struct imx8mp_blk_ctrl_domain_data { const char * const *path_names; int num_paths; const char *gpc_name; + const unsigned int flags; }; #define DOMAIN_MAX_CLKS 3 @@ -265,10 +266,12 @@ static const struct imx8mp_blk_ctrl_domain_data imx8mp_hsio_domain_data[] = { [IMX8MP_HSIOBLK_PD_USB_PHY1] = { .name = "hsioblk-usb-phy1", .gpc_name = "usb-phy1", + .flags = GENPD_FLAG_ACTIVE_WAKEUP, }, [IMX8MP_HSIOBLK_PD_USB_PHY2] = { .name = "hsioblk-usb-phy2", .gpc_name = "usb-phy2", + .flags = GENPD_FLAG_ACTIVE_WAKEUP, }, [IMX8MP_HSIOBLK_PD_PCIE] = { .name = "hsioblk-pcie", @@ -724,6 +727,7 @@ static int imx8mp_blk_ctrl_probe(struct platform_device *pdev) domain->genpd.name = data->name; domain->genpd.power_on = imx8mp_blk_ctrl_power_on; domain->genpd.power_off = imx8mp_blk_ctrl_power_off; + domain->genpd.flags = data->flags; domain->bc = bc; domain->id = i; From eb54ce033b344b531b374496e68a2554b2b56b5a Mon Sep 17 00:00:00 2001 From: Xu Yang Date: Fri, 30 Jan 2026 13:11:07 +0800 Subject: [PATCH 2988/3902] pmdomain: imx8m-blk-ctrl: fix out-of-range access of bc->domains commit 6bd8b4a92a901fae1a422e6f914801063c345e8d upstream. Fix out-of-range access of bc->domains in imx8m_blk_ctrl_remove(). Fixes: 2684ac05a8c4 ("soc: imx: add i.MX8M blk-ctrl driver") Cc: stable@kernel.org Signed-off-by: Xu Yang Reviewed-by: Daniel Baluta Signed-off-by: Ulf Hansson Signed-off-by: Greg Kroah-Hartman --- drivers/pmdomain/imx/imx8m-blk-ctrl.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/pmdomain/imx/imx8m-blk-ctrl.c b/drivers/pmdomain/imx/imx8m-blk-ctrl.c index 74bf4936991d7c..19e992d2ee3b84 100644 --- a/drivers/pmdomain/imx/imx8m-blk-ctrl.c +++ b/drivers/pmdomain/imx/imx8m-blk-ctrl.c @@ -340,7 +340,7 @@ static void imx8m_blk_ctrl_remove(struct platform_device *pdev) of_genpd_del_provider(pdev->dev.of_node); - for (i = 0; bc->onecell_data.num_domains; i++) { + for (i = 0; i < bc->onecell_data.num_domains; i++) { struct imx8m_blk_ctrl_domain *domain = &bc->domains[i]; pm_genpd_remove(&domain->genpd); From cbc03ce3e6ce7e21214c3f02218213574c1a2d08 Mon Sep 17 00:00:00 2001 From: Andrii Nakryiko Date: Thu, 29 Jan 2026 13:53:40 -0800 Subject: [PATCH 2989/3902] procfs: avoid fetching build ID while holding VMA lock commit b5cbacd7f86f4f62b8813688c8e73be94e8e1951 upstream. Fix PROCMAP_QUERY to fetch optional build ID only after dropping mmap_lock or per-VMA lock, whichever was used to lock VMA under question, to avoid deadlock reported by syzbot: -> #1 (&mm->mmap_lock){++++}-{4:4}: __might_fault+0xed/0x170 _copy_to_iter+0x118/0x1720 copy_page_to_iter+0x12d/0x1e0 filemap_read+0x720/0x10a0 blkdev_read_iter+0x2b5/0x4e0 vfs_read+0x7f4/0xae0 ksys_read+0x12a/0x250 do_syscall_64+0xcb/0xf80 entry_SYSCALL_64_after_hwframe+0x77/0x7f -> #0 (&sb->s_type->i_mutex_key#8){++++}-{4:4}: __lock_acquire+0x1509/0x26d0 lock_acquire+0x185/0x340 down_read+0x98/0x490 blkdev_read_iter+0x2a7/0x4e0 __kernel_read+0x39a/0xa90 freader_fetch+0x1d5/0xa80 __build_id_parse.isra.0+0xea/0x6a0 do_procmap_query+0xd75/0x1050 procfs_procmap_ioctl+0x7a/0xb0 __x64_sys_ioctl+0x18e/0x210 do_syscall_64+0xcb/0xf80 entry_SYSCALL_64_after_hwframe+0x77/0x7f other info that might help us debug this: Possible unsafe locking scenario: CPU0 CPU1 ---- ---- rlock(&mm->mmap_lock); lock(&sb->s_type->i_mutex_key#8); lock(&mm->mmap_lock); rlock(&sb->s_type->i_mutex_key#8); *** DEADLOCK *** This seems to be exacerbated (as we haven't seen these syzbot reports before that) by the recent: 777a8560fd29 ("lib/buildid: use __kernel_read() for sleepable context") To make this safe, we need to grab file refcount while VMA is still locked, but other than that everything is pretty straightforward. Internal build_id_parse() API assumes VMA is passed, but it only needs the underlying file reference, so just add another variant build_id_parse_file() that expects file passed directly. [akpm@linux-foundation.org: fix up kerneldoc] Link: https://lkml.kernel.org/r/20260129215340.3742283-1-andrii@kernel.org Fixes: ed5d583a88a9 ("fs/procfs: implement efficient VMA querying API for /proc//maps") Signed-off-by: Andrii Nakryiko Reported-by: Reviewed-by: Suren Baghdasaryan Tested-by: Suren Baghdasaryan Reviewed-by: Shakeel Butt Cc: Alexei Starovoitov Cc: Daniel Borkmann Cc: Eduard Zingerman Cc: Hao Luo Cc: Jiri Olsa Cc: John Fastabend Cc: KP Singh Cc: Martin KaFai Lau Cc: Song Liu Cc: Stanislav Fomichev Cc: Yonghong Song Cc: Signed-off-by: Andrew Morton Signed-off-by: Greg Kroah-Hartman --- fs/proc/task_mmu.c | 42 ++++++++++++++++++++++++++--------------- include/linux/buildid.h | 3 +++ lib/buildid.c | 42 +++++++++++++++++++++++++++++------------ 3 files changed, 60 insertions(+), 27 deletions(-) diff --git a/fs/proc/task_mmu.c b/fs/proc/task_mmu.c index fc35a0543f0191..2ee152a318f50f 100644 --- a/fs/proc/task_mmu.c +++ b/fs/proc/task_mmu.c @@ -656,6 +656,7 @@ static int do_procmap_query(struct mm_struct *mm, void __user *uarg) struct proc_maps_locking_ctx lock_ctx = { .mm = mm }; struct procmap_query karg; struct vm_area_struct *vma; + struct file *vm_file = NULL; const char *name = NULL; char build_id_buf[BUILD_ID_SIZE_MAX], *name_buf = NULL; __u64 usize; @@ -727,21 +728,6 @@ static int do_procmap_query(struct mm_struct *mm, void __user *uarg) karg.inode = 0; } - if (karg.build_id_size) { - __u32 build_id_sz; - - err = build_id_parse(vma, build_id_buf, &build_id_sz); - if (err) { - karg.build_id_size = 0; - } else { - if (karg.build_id_size < build_id_sz) { - err = -ENAMETOOLONG; - goto out; - } - karg.build_id_size = build_id_sz; - } - } - if (karg.vma_name_size) { size_t name_buf_sz = min_t(size_t, PATH_MAX, karg.vma_name_size); const struct path *path; @@ -775,10 +761,34 @@ static int do_procmap_query(struct mm_struct *mm, void __user *uarg) karg.vma_name_size = name_sz; } + if (karg.build_id_size && vma->vm_file) + vm_file = get_file(vma->vm_file); + /* unlock vma or mmap_lock, and put mm_struct before copying data to user */ query_vma_teardown(&lock_ctx); mmput(mm); + if (karg.build_id_size) { + __u32 build_id_sz; + + if (vm_file) + err = build_id_parse_file(vm_file, build_id_buf, &build_id_sz); + else + err = -ENOENT; + if (err) { + karg.build_id_size = 0; + } else { + if (karg.build_id_size < build_id_sz) { + err = -ENAMETOOLONG; + goto out; + } + karg.build_id_size = build_id_sz; + } + } + + if (vm_file) + fput(vm_file); + if (karg.vma_name_size && copy_to_user(u64_to_user_ptr(karg.vma_name_addr), name, karg.vma_name_size)) { kfree(name_buf); @@ -798,6 +808,8 @@ static int do_procmap_query(struct mm_struct *mm, void __user *uarg) out: query_vma_teardown(&lock_ctx); mmput(mm); + if (vm_file) + fput(vm_file); kfree(name_buf); return err; } diff --git a/include/linux/buildid.h b/include/linux/buildid.h index 014a88c410739e..5e0a14866cc185 100644 --- a/include/linux/buildid.h +++ b/include/linux/buildid.h @@ -7,7 +7,10 @@ #define BUILD_ID_SIZE_MAX 20 struct vm_area_struct; +struct file; + int build_id_parse(struct vm_area_struct *vma, unsigned char *build_id, __u32 *size); +int build_id_parse_file(struct file *file, unsigned char *build_id, __u32 *size); int build_id_parse_nofault(struct vm_area_struct *vma, unsigned char *build_id, __u32 *size); int build_id_parse_buf(const void *buf, unsigned char *build_id, u32 buf_size); diff --git a/lib/buildid.c b/lib/buildid.c index a80592ddafd18a..ef112a7084ef17 100644 --- a/lib/buildid.c +++ b/lib/buildid.c @@ -295,7 +295,7 @@ static int get_build_id_64(struct freader *r, unsigned char *build_id, __u32 *si /* enough for Elf64_Ehdr, Elf64_Phdr, and all the smaller requests */ #define MAX_FREADER_BUF_SZ 64 -static int __build_id_parse(struct vm_area_struct *vma, unsigned char *build_id, +static int __build_id_parse(struct file *file, unsigned char *build_id, __u32 *size, bool may_fault) { const Elf32_Ehdr *ehdr; @@ -303,11 +303,7 @@ static int __build_id_parse(struct vm_area_struct *vma, unsigned char *build_id, char buf[MAX_FREADER_BUF_SZ]; int ret; - /* only works for page backed storage */ - if (!vma->vm_file) - return -EINVAL; - - freader_init_from_file(&r, buf, sizeof(buf), vma->vm_file, may_fault); + freader_init_from_file(&r, buf, sizeof(buf), file, may_fault); /* fetch first 18 bytes of ELF header for checks */ ehdr = freader_fetch(&r, 0, offsetofend(Elf32_Ehdr, e_type)); @@ -335,8 +331,8 @@ static int __build_id_parse(struct vm_area_struct *vma, unsigned char *build_id, return ret; } -/* - * Parse build ID of ELF file mapped to vma +/** + * build_id_parse_nofault() - Parse build ID of ELF file mapped to vma * @vma: vma object * @build_id: buffer to store build id, at least BUILD_ID_SIZE long * @size: returns actual build id size in case of success @@ -348,11 +344,14 @@ static int __build_id_parse(struct vm_area_struct *vma, unsigned char *build_id, */ int build_id_parse_nofault(struct vm_area_struct *vma, unsigned char *build_id, __u32 *size) { - return __build_id_parse(vma, build_id, size, false /* !may_fault */); + if (!vma->vm_file) + return -EINVAL; + + return __build_id_parse(vma->vm_file, build_id, size, false /* !may_fault */); } -/* - * Parse build ID of ELF file mapped to VMA +/** + * build_id_parse() - Parse build ID of ELF file mapped to VMA * @vma: vma object * @build_id: buffer to store build id, at least BUILD_ID_SIZE long * @size: returns actual build id size in case of success @@ -364,7 +363,26 @@ int build_id_parse_nofault(struct vm_area_struct *vma, unsigned char *build_id, */ int build_id_parse(struct vm_area_struct *vma, unsigned char *build_id, __u32 *size) { - return __build_id_parse(vma, build_id, size, true /* may_fault */); + if (!vma->vm_file) + return -EINVAL; + + return __build_id_parse(vma->vm_file, build_id, size, true /* may_fault */); +} + +/** + * build_id_parse_file() - Parse build ID of ELF file + * @file: file object + * @build_id: buffer to store build id, at least BUILD_ID_SIZE long + * @size: returns actual build id size in case of success + * + * Assumes faultable context and can cause page faults to bring in file data + * into page cache. + * + * Return: 0 on success; negative error, otherwise + */ +int build_id_parse_file(struct file *file, unsigned char *build_id, __u32 *size) +{ + return __build_id_parse(file, build_id, size, true /* may_fault */); } /** From e8af57e090790983591f6927b3d89ee6383f8c1e Mon Sep 17 00:00:00 2001 From: Hao Ge Date: Wed, 4 Feb 2026 18:14:01 +0800 Subject: [PATCH 2990/3902] mm/slab: Add alloc_tagging_slab_free_hook for memcg_alloc_abort_single commit e6c53ead2d8fa73206e0a63e9cd9aea6bc929837 upstream. When CONFIG_MEM_ALLOC_PROFILING_DEBUG is enabled, the following warning may be noticed: [ 3959.023862] ------------[ cut here ]------------ [ 3959.023891] alloc_tag was not cleared (got tag for lib/xarray.c:378) [ 3959.023947] WARNING: ./include/linux/alloc_tag.h:155 at alloc_tag_add+0x128/0x178, CPU#6: mkfs.ntfs/113998 [ 3959.023978] Modules linked in: dns_resolver tun brd overlay exfat btrfs blake2b libblake2b xor xor_neon raid6_pq loop sctp ip6_udp_tunnel udp_tunnel ext4 crc16 mbcache jbd2 rfkill sunrpc vfat fat sg fuse nfnetlink sr_mod virtio_gpu cdrom drm_client_lib virtio_dma_buf drm_shmem_helper drm_kms_helper ghash_ce drm sm4 backlight virtio_net net_failover virtio_scsi failover virtio_console virtio_blk virtio_mmio dm_mirror dm_region_hash dm_log dm_multipath dm_mod i2c_dev aes_neon_bs aes_ce_blk [last unloaded: hwpoison_inject] [ 3959.024170] CPU: 6 UID: 0 PID: 113998 Comm: mkfs.ntfs Kdump: loaded Tainted: G W 6.19.0-rc7+ #7 PREEMPT(voluntary) [ 3959.024182] Tainted: [W]=WARN [ 3959.024186] Hardware name: QEMU KVM Virtual Machine, BIOS unknown 2/2/2022 [ 3959.024192] pstate: 604000c5 (nZCv daIF +PAN -UAO -TCO -DIT -SSBS BTYPE=--) [ 3959.024199] pc : alloc_tag_add+0x128/0x178 [ 3959.024207] lr : alloc_tag_add+0x128/0x178 [ 3959.024214] sp : ffff80008b696d60 [ 3959.024219] x29: ffff80008b696d60 x28: 0000000000000000 x27: 0000000000000240 [ 3959.024232] x26: 0000000000000000 x25: 0000000000000240 x24: ffff800085d17860 [ 3959.024245] x23: 0000000000402800 x22: ffff0000c0012dc0 x21: 00000000000002d0 [ 3959.024257] x20: ffff0000e6ef3318 x19: ffff800085ae0410 x18: 0000000000000000 [ 3959.024269] x17: 0000000000000000 x16: 0000000000000000 x15: 0000000000000000 [ 3959.024281] x14: 0000000000000000 x13: 0000000000000001 x12: ffff600064101293 [ 3959.024292] x11: 1fffe00064101292 x10: ffff600064101292 x9 : dfff800000000000 [ 3959.024305] x8 : 00009fff9befed6e x7 : ffff000320809493 x6 : 0000000000000001 [ 3959.024316] x5 : ffff000320809490 x4 : ffff600064101293 x3 : ffff800080691838 [ 3959.024328] x2 : 0000000000000000 x1 : 0000000000000000 x0 : ffff0000d5bcd640 [ 3959.024340] Call trace: [ 3959.024346] alloc_tag_add+0x128/0x178 (P) [ 3959.024355] __alloc_tagging_slab_alloc_hook+0x11c/0x1a8 [ 3959.024362] kmem_cache_alloc_lru_noprof+0x1b8/0x5e8 [ 3959.024369] xas_alloc+0x304/0x4f0 [ 3959.024381] xas_create+0x1e0/0x4a0 [ 3959.024388] xas_store+0x68/0xda8 [ 3959.024395] __filemap_add_folio+0x5b0/0xbd8 [ 3959.024409] filemap_add_folio+0x16c/0x7e0 [ 3959.024416] __filemap_get_folio_mpol+0x2dc/0x9e8 [ 3959.024424] iomap_get_folio+0xfc/0x180 [ 3959.024435] __iomap_get_folio+0x2f8/0x4b8 [ 3959.024441] iomap_write_begin+0x198/0xc18 [ 3959.024448] iomap_write_iter+0x2ec/0x8f8 [ 3959.024454] iomap_file_buffered_write+0x19c/0x290 [ 3959.024461] blkdev_write_iter+0x38c/0x978 [ 3959.024470] vfs_write+0x4d4/0x928 [ 3959.024482] ksys_write+0xfc/0x1f8 [ 3959.024489] __arm64_sys_write+0x74/0xb0 [ 3959.024496] invoke_syscall+0xd4/0x258 [ 3959.024507] el0_svc_common.constprop.0+0xb4/0x240 [ 3959.024514] do_el0_svc+0x48/0x68 [ 3959.024520] el0_svc+0x40/0xf8 [ 3959.024526] el0t_64_sync_handler+0xa0/0xe8 [ 3959.024533] el0t_64_sync+0x1ac/0x1b0 [ 3959.024540] ---[ end trace 0000000000000000 ]--- When __memcg_slab_post_alloc_hook() fails, there are two different free paths depending on whether size == 1 or size != 1. In the kmem_cache_free_bulk() path, we do call alloc_tagging_slab_free_hook(). However, in memcg_alloc_abort_single() we don't, the above warning will be triggered on the next allocation. Therefore, add alloc_tagging_slab_free_hook() to the memcg_alloc_abort_single() path. Fixes: 9f9796b413d3 ("mm, slab: move memcg charging to post-alloc hook") Cc: stable@vger.kernel.org Suggested-by: Hao Li Signed-off-by: Hao Ge Reviewed-by: Hao Li Reviewed-by: Suren Baghdasaryan Reviewed-by: Harry Yoo Link: https://patch.msgid.link/20260204101401.202762-1-hao.ge@linux.dev Signed-off-by: Vlastimil Babka Signed-off-by: Greg Kroah-Hartman --- mm/slub.c | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/mm/slub.c b/mm/slub.c index 1e76c92fe37535..e01641cea143be 100644 --- a/mm/slub.c +++ b/mm/slub.c @@ -6667,8 +6667,12 @@ void slab_free(struct kmem_cache *s, struct slab *slab, void *object, static noinline void memcg_alloc_abort_single(struct kmem_cache *s, void *object) { + struct slab *slab = virt_to_slab(object); + + alloc_tagging_slab_free_hook(s, slab, &object, 1); + if (likely(slab_free_hook(s, object, slab_want_init_on_free(s), false))) - do_slab_free(s, virt_to_slab(object), object, object, 1, _RET_IP_); + do_slab_free(s, slab, object, object, 1, _RET_IP_); } #endif From 57b36ffc8881dd455d875f85c105901974af2130 Mon Sep 17 00:00:00 2001 From: Viacheslav Dubeyko Date: Tue, 3 Feb 2026 14:54:46 -0800 Subject: [PATCH 2991/3902] ceph: fix NULL pointer dereference in ceph_mds_auth_match() commit 7987cce375ac8ce98e170a77aa2399f2cf6eb99f upstream. The CephFS kernel client has regression starting from 6.18-rc1. We have issue in ceph_mds_auth_match() if fs_name == NULL: const char fs_name = mdsc->fsc->mount_options->mds_namespace; ... if (auth->match.fs_name && strcmp(auth->match.fs_name, fs_name)) { / fsname mismatch, try next one */ return 0; } Patrick Donnelly suggested that: In summary, we should definitely start decoding `fs_name` from the MDSMap and do strict authorizations checks against it. Note that the `-o mds_namespace=foo` should only be used for selecting the file system to mount and nothing else. It's possible no mds_namespace is specified but the kernel will mount the only file system that exists which may have name "foo". This patch reworks ceph_mdsmap_decode() and namespace_equals() with the goal of supporting the suggested concept. Now struct ceph_mdsmap contains m_fs_name field that receives copy of extracted FS name by ceph_extract_encoded_string(). For the case of "old" CephFS file systems, it is used "cephfs" name. [ idryomov: replace redundant %*pE with %s in ceph_mdsmap_decode(), get rid of a series of strlen() calls in ceph_namespace_match(), drop changes to namespace_equals() body to avoid treating empty mds_namespace as equal, drop changes to ceph_mdsc_handle_fsmap() as namespace_equals() isn't an equivalent substitution there ] Cc: stable@vger.kernel.org Fixes: 22c73d52a6d0 ("ceph: fix multifs mds auth caps issue") Link: https://tracker.ceph.com/issues/73886 Signed-off-by: Viacheslav Dubeyko Reviewed-by: Patrick Donnelly Tested-by: Patrick Donnelly Signed-off-by: Ilya Dryomov Signed-off-by: Greg Kroah-Hartman --- fs/ceph/mds_client.c | 5 +++-- fs/ceph/mdsmap.c | 26 +++++++++++++++++++------- fs/ceph/mdsmap.h | 1 + fs/ceph/super.h | 16 ++++++++++++++-- include/linux/ceph/ceph_fs.h | 6 ++++++ 5 files changed, 43 insertions(+), 11 deletions(-) diff --git a/fs/ceph/mds_client.c b/fs/ceph/mds_client.c index 1740047aef0f9a..f3d146b869436b 100644 --- a/fs/ceph/mds_client.c +++ b/fs/ceph/mds_client.c @@ -5655,7 +5655,7 @@ static int ceph_mds_auth_match(struct ceph_mds_client *mdsc, u32 caller_uid = from_kuid(&init_user_ns, cred->fsuid); u32 caller_gid = from_kgid(&init_user_ns, cred->fsgid); struct ceph_client *cl = mdsc->fsc->client; - const char *fs_name = mdsc->fsc->mount_options->mds_namespace; + const char *fs_name = mdsc->mdsmap->m_fs_name; const char *spath = mdsc->fsc->mount_options->server_path; bool gid_matched = false; u32 gid, tlen, len; @@ -5663,7 +5663,8 @@ static int ceph_mds_auth_match(struct ceph_mds_client *mdsc, doutc(cl, "fsname check fs_name=%s match.fs_name=%s\n", fs_name, auth->match.fs_name ? auth->match.fs_name : ""); - if (auth->match.fs_name && strcmp(auth->match.fs_name, fs_name)) { + + if (!ceph_namespace_match(auth->match.fs_name, fs_name)) { /* fsname mismatch, try next one */ return 0; } diff --git a/fs/ceph/mdsmap.c b/fs/ceph/mdsmap.c index 2c7b151a7c95cc..b228e5ecfb926d 100644 --- a/fs/ceph/mdsmap.c +++ b/fs/ceph/mdsmap.c @@ -353,22 +353,33 @@ struct ceph_mdsmap *ceph_mdsmap_decode(struct ceph_mds_client *mdsc, void **p, __decode_and_drop_type(p, end, u8, bad_ext); } if (mdsmap_ev >= 8) { - u32 fsname_len; + size_t fsname_len; + /* enabled */ ceph_decode_8_safe(p, end, m->m_enabled, bad_ext); + /* fs_name */ - ceph_decode_32_safe(p, end, fsname_len, bad_ext); + m->m_fs_name = ceph_extract_encoded_string(p, end, + &fsname_len, + GFP_NOFS); + if (IS_ERR(m->m_fs_name)) { + m->m_fs_name = NULL; + goto nomem; + } /* validate fsname against mds_namespace */ - if (!namespace_equals(mdsc->fsc->mount_options, *p, + if (!namespace_equals(mdsc->fsc->mount_options, m->m_fs_name, fsname_len)) { - pr_warn_client(cl, "fsname %*pE doesn't match mds_namespace %s\n", - (int)fsname_len, (char *)*p, + pr_warn_client(cl, "fsname %s doesn't match mds_namespace %s\n", + m->m_fs_name, mdsc->fsc->mount_options->mds_namespace); goto bad; } - /* skip fsname after validation */ - ceph_decode_skip_n(p, end, fsname_len, bad); + } else { + m->m_enabled = false; + m->m_fs_name = kstrdup(CEPH_OLD_FS_NAME, GFP_NOFS); + if (!m->m_fs_name) + goto nomem; } /* damaged */ if (mdsmap_ev >= 9) { @@ -430,6 +441,7 @@ void ceph_mdsmap_destroy(struct ceph_mdsmap *m) kfree(m->m_info); } kfree(m->m_data_pg_pools); + kfree(m->m_fs_name); kfree(m); } diff --git a/fs/ceph/mdsmap.h b/fs/ceph/mdsmap.h index 1f2171dd01bfa3..d48d07c3516d44 100644 --- a/fs/ceph/mdsmap.h +++ b/fs/ceph/mdsmap.h @@ -45,6 +45,7 @@ struct ceph_mdsmap { bool m_enabled; bool m_damaged; int m_num_laggy; + char *m_fs_name; }; static inline struct ceph_entity_addr * diff --git a/fs/ceph/super.h b/fs/ceph/super.h index a1f781c46b41d3..29a980e22dc26c 100644 --- a/fs/ceph/super.h +++ b/fs/ceph/super.h @@ -104,14 +104,26 @@ struct ceph_mount_options { struct fscrypt_dummy_policy dummy_enc_policy; }; +#define CEPH_NAMESPACE_WILDCARD "*" + +static inline bool ceph_namespace_match(const char *pattern, + const char *target) +{ + if (!pattern || !pattern[0] || + !strcmp(pattern, CEPH_NAMESPACE_WILDCARD)) + return true; + + return !strcmp(pattern, target); +} + /* * Check if the mds namespace in ceph_mount_options matches * the passed in namespace string. First time match (when * ->mds_namespace is NULL) is treated specially, since * ->mds_namespace needs to be initialized by the caller. */ -static inline int namespace_equals(struct ceph_mount_options *fsopt, - const char *namespace, size_t len) +static inline bool namespace_equals(struct ceph_mount_options *fsopt, + const char *namespace, size_t len) { return !(fsopt->mds_namespace && (strlen(fsopt->mds_namespace) != len || diff --git a/include/linux/ceph/ceph_fs.h b/include/linux/ceph/ceph_fs.h index c7f2c63b3bc3fb..08e5dbe15ca444 100644 --- a/include/linux/ceph/ceph_fs.h +++ b/include/linux/ceph/ceph_fs.h @@ -31,6 +31,12 @@ #define CEPH_INO_CEPH 2 /* hidden .ceph dir */ #define CEPH_INO_GLOBAL_SNAPREALM 3 /* global dummy snaprealm */ +/* + * name for "old" CephFS file systems, + * see ceph.git e2b151d009640114b2565c901d6f41f6cd5ec652 + */ +#define CEPH_OLD_FS_NAME "cephfs" + /* arbitrary limit on max # of monitors (cluster of 3 is typical) */ #define CEPH_MAX_MON 31 From 46dfdb6f7a79d74f14aa99ca90a1017677d15bbd Mon Sep 17 00:00:00 2001 From: Ilya Dryomov Date: Wed, 7 Jan 2026 22:37:55 +0100 Subject: [PATCH 2992/3902] rbd: check for EOD after exclusive lock is ensured to be held commit bd3884a204c3b507e6baa9a4091aa927f9af5404 upstream. Similar to commit 870611e4877e ("rbd: get snapshot context after exclusive lock is ensured to be held"), move the "beyond EOD" check into the image request state machine so that it's performed after exclusive lock is ensured to be held. This avoids various race conditions which can arise when the image is shrunk under I/O (in practice, mostly readahead). In one such scenario rbd_assert(objno < rbd_dev->object_map_size); can be triggered if a close-to-EOD read gets queued right before the shrink is initiated and the EOD check is performed against an outdated mapping_size. After the resize is done on the server side and exclusive lock is (re)acquired bringing along the new (now shrunk) object map, the read starts going through the state machine and rbd_obj_may_exist() gets invoked on an object that is out of bounds of rbd_dev->object_map array. Cc: stable@vger.kernel.org Signed-off-by: Ilya Dryomov Reviewed-by: Dongsheng Yang Signed-off-by: Greg Kroah-Hartman --- drivers/block/rbd.c | 33 +++++++++++++++++++++------------ 1 file changed, 21 insertions(+), 12 deletions(-) diff --git a/drivers/block/rbd.c b/drivers/block/rbd.c index af0e21149dbc9e..8f441eb8b19265 100644 --- a/drivers/block/rbd.c +++ b/drivers/block/rbd.c @@ -3495,11 +3495,29 @@ static void rbd_img_object_requests(struct rbd_img_request *img_req) rbd_assert(!need_exclusive_lock(img_req) || __rbd_is_lock_owner(rbd_dev)); - if (rbd_img_is_write(img_req)) { - rbd_assert(!img_req->snapc); + if (test_bit(IMG_REQ_CHILD, &img_req->flags)) { + rbd_assert(!rbd_img_is_write(img_req)); + } else { + struct request *rq = blk_mq_rq_from_pdu(img_req); + u64 off = (u64)blk_rq_pos(rq) << SECTOR_SHIFT; + u64 len = blk_rq_bytes(rq); + u64 mapping_size; + down_read(&rbd_dev->header_rwsem); - img_req->snapc = ceph_get_snap_context(rbd_dev->header.snapc); + mapping_size = rbd_dev->mapping.size; + if (rbd_img_is_write(img_req)) { + rbd_assert(!img_req->snapc); + img_req->snapc = + ceph_get_snap_context(rbd_dev->header.snapc); + } up_read(&rbd_dev->header_rwsem); + + if (unlikely(off + len > mapping_size)) { + rbd_warn(rbd_dev, "beyond EOD (%llu~%llu > %llu)", + off, len, mapping_size); + img_req->pending.result = -EIO; + return; + } } for_each_obj_request(img_req, obj_req) { @@ -4725,7 +4743,6 @@ static void rbd_queue_workfn(struct work_struct *work) struct request *rq = blk_mq_rq_from_pdu(img_request); u64 offset = (u64)blk_rq_pos(rq) << SECTOR_SHIFT; u64 length = blk_rq_bytes(rq); - u64 mapping_size; int result; /* Ignore/skip any zero-length requests */ @@ -4738,17 +4755,9 @@ static void rbd_queue_workfn(struct work_struct *work) blk_mq_start_request(rq); down_read(&rbd_dev->header_rwsem); - mapping_size = rbd_dev->mapping.size; rbd_img_capture_header(img_request); up_read(&rbd_dev->header_rwsem); - if (offset + length > mapping_size) { - rbd_warn(rbd_dev, "beyond EOD (%llu~%llu > %llu)", offset, - length, mapping_size); - result = -EIO; - goto err_img_request; - } - dout("%s rbd_dev %p img_req %p %s %llu~%llu\n", __func__, rbd_dev, img_request, obj_op_name(op_type), offset, length); From 35e6fd0d5bc304bc7489d519e6fe20987c3c6498 Mon Sep 17 00:00:00 2001 From: Thomas Weissschuh Date: Wed, 7 Jan 2026 11:01:49 +0100 Subject: [PATCH 2993/3902] ARM: 9468/1: fix memset64() on big-endian MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit commit 23ea2a4c72323feb6e3e025e8a6f18336513d5ad upstream. On big-endian systems the 32-bit low and high halves need to be swapped for the underlying assembly implementation to work correctly. Fixes: fd1d362600e2 ("ARM: implement memset32 & memset64") Cc: stable@vger.kernel.org Signed-off-by: Thomas Weißschuh Reviewed-by: Matthew Wilcox (Oracle) Reviewed-by: Arnd Bergmann Signed-off-by: Russell King (Oracle) Signed-off-by: Greg Kroah-Hartman --- arch/arm/include/asm/string.h | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/arch/arm/include/asm/string.h b/arch/arm/include/asm/string.h index 6c607c68f3ad75..c35250c4991bc7 100644 --- a/arch/arm/include/asm/string.h +++ b/arch/arm/include/asm/string.h @@ -42,7 +42,10 @@ static inline void *memset32(uint32_t *p, uint32_t v, __kernel_size_t n) extern void *__memset64(uint64_t *, uint32_t low, __kernel_size_t, uint32_t hi); static inline void *memset64(uint64_t *p, uint64_t v, __kernel_size_t n) { - return __memset64(p, v, n * 8, v >> 32); + if (IS_ENABLED(CONFIG_CPU_LITTLE_ENDIAN)) + return __memset64(p, v, n * 8, v >> 32); + else + return __memset64(p, v >> 32, n * 8, v); } /* From e258ed369c9e04caa7d2fd49785d753ae4034cb6 Mon Sep 17 00:00:00 2001 From: Daniel Vogelbacher Date: Sun, 1 Feb 2026 09:34:01 +0100 Subject: [PATCH 2994/3902] ceph: fix oops due to invalid pointer for kfree() in parse_longname() commit bc8dedae022ce3058659c3addef3ec4b41d15e00 upstream. This fixes a kernel oops when reading ceph snapshot directories (.snap), for example by simply running `ls /mnt/my_ceph/.snap`. The variable str is guarded by __free(kfree), but advanced by one for skipping the initial '_' in snapshot names. Thus, kfree() is called with an invalid pointer. This patch removes the need for advancing the pointer so kfree() is called with correct memory pointer. Steps to reproduce: 1. Create snapshots on a cephfs volume (I've 63 snaps in my testcase) 2. Add cephfs mount to fstab $ echo "samba-fileserver@.files=/volumes/datapool/stuff/3461082b-ecc9-4e82-8549-3fd2590d3fb6 /mnt/test/stuff ceph acl,noatime,_netdev 0 0" >> /etc/fstab 3. Reboot the system $ systemctl reboot 4. Check if it's really mounted $ mount | grep stuff 5. List snapshots (expected 63 snapshots on my system) $ ls /mnt/test/stuff/.snap Now ls hangs forever and the kernel log shows the oops. Cc: stable@vger.kernel.org Fixes: 101841c38346 ("[ceph] parse_longname(): strrchr() expects NUL-terminated string") Closes: https://bugzilla.kernel.org/show_bug.cgi?id=220807 Suggested-by: Helge Deller Signed-off-by: Daniel Vogelbacher Reviewed-by: Viacheslav Dubeyko Signed-off-by: Ilya Dryomov Signed-off-by: Greg Kroah-Hartman --- fs/ceph/crypto.c | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/fs/ceph/crypto.c b/fs/ceph/crypto.c index 7026e794813ca1..de823a50af9c5c 100644 --- a/fs/ceph/crypto.c +++ b/fs/ceph/crypto.c @@ -219,12 +219,13 @@ static struct inode *parse_longname(const struct inode *parent, struct ceph_vino vino = { .snap = CEPH_NOSNAP }; char *name_end, *inode_number; int ret = -EIO; - /* NUL-terminate */ - char *str __free(kfree) = kmemdup_nul(name, *name_len, GFP_KERNEL); + /* Snapshot name must start with an underscore */ + if (*name_len <= 0 || name[0] != '_') + return ERR_PTR(-EIO); + /* Skip initial '_' and NUL-terminate */ + char *str __free(kfree) = kmemdup_nul(name + 1, *name_len - 1, GFP_KERNEL); if (!str) return ERR_PTR(-ENOMEM); - /* Skip initial '_' */ - str++; name_end = strrchr(str, '_'); if (!name_end) { doutc(cl, "failed to parse long snapshot name: %s\n", str); From c13816e8fa23deec6a8d7465d9e637fd02683b5c Mon Sep 17 00:00:00 2001 From: Chen Ridong Date: Mon, 2 Feb 2026 12:27:16 +0000 Subject: [PATCH 2995/3902] cgroup/dmem: fix NULL pointer dereference when setting max commit 43151f812886be1855d2cba059f9c93e4729460b upstream. An issue was triggered: BUG: kernel NULL pointer dereference, address: 0000000000000000 #PF: supervisor read access in kernel mode #PF: error_code(0x0000) - not-present page PGD 0 P4D 0 Oops: Oops: 0000 [#1] SMP NOPTI CPU: 15 UID: 0 PID: 658 Comm: bash Tainted: 6.19.0-rc6-next-2026012 Tainted: [O]=OOT_MODULE Hardware name: QEMU Standard PC (i440FX + PIIX, 1996), RIP: 0010:strcmp+0x10/0x30 RSP: 0018:ffffc900017f7dc0 EFLAGS: 00000246 RAX: 0000000000000000 RBX: 0000000000000000 RCX: ffff888107cd4358 RDX: 0000000019f73907 RSI: ffffffff82cc381a RDI: 0000000000000000 RBP: ffff8881016bef0d R08: 000000006c0e7145 R09: 0000000056c0e714 R10: 0000000000000001 R11: ffff888107cd4358 R12: 0007ffffffffffff R13: ffff888101399200 R14: ffff888100fcb360 R15: 0007ffffffffffff CS: 0010 DS: 0000 ES: 0000 CR0: 0000000080050033 CR2: 0000000000000000 CR3: 0000000105c79000 CR4: 00000000000006f0 Call Trace: dmemcg_limit_write.constprop.0+0x16d/0x390 ? __pfx_set_resource_max+0x10/0x10 kernfs_fop_write_iter+0x14e/0x200 vfs_write+0x367/0x510 ksys_write+0x66/0xe0 do_syscall_64+0x6b/0x390 entry_SYSCALL_64_after_hwframe+0x76/0x7e RIP: 0033:0x7f42697e1887 It was trriggered setting max without limitation, the command is like: "echo test/region0 > dmem.max". To fix this issue, add check whether options is valid after parsing the region_name. Fixes: b168ed458dde ("kernel/cgroup: Add "dmem" memory accounting cgroup") Cc: stable@vger.kernel.org # v6.14+ Signed-off-by: Chen Ridong Signed-off-by: Tejun Heo Signed-off-by: Greg Kroah-Hartman --- kernel/cgroup/dmem.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/kernel/cgroup/dmem.c b/kernel/cgroup/dmem.c index e12b946278b6c6..1f0d6caaf2fbc0 100644 --- a/kernel/cgroup/dmem.c +++ b/kernel/cgroup/dmem.c @@ -700,6 +700,9 @@ static ssize_t dmemcg_limit_write(struct kernfs_open_file *of, if (!region_name[0]) continue; + if (!options || !*options) + return -EINVAL; + rcu_read_lock(); region = dmemcg_get_region_by_name(region_name); rcu_read_unlock(); From 5c38604abbfa5e973125082c83e65f77894d01e1 Mon Sep 17 00:00:00 2001 From: Chen Ridong Date: Mon, 2 Feb 2026 12:27:17 +0000 Subject: [PATCH 2996/3902] cgroup/dmem: avoid rcu warning when unregister region commit 592a68212c5664bcaa88f24ed80bf791282790fe upstream. A warnning was detected: WARNING: suspicious RCU usage 6.19.0-rc7-next-20260129+ #1101 Tainted: G O kernel/cgroup/dmem.c:456 suspicious rcu_dereference_check() usage! other info that might help us debug this: rcu_scheduler_active = 2, debug_locks = 1 1 lock held by insmod/532: #0: ffffffff85e78b38 (dmemcg_lock){+.+.}-dmem_cgroup_unregister_region+ stack backtrace: CPU: 2 UID: 0 PID: 532 Comm: insmod Tainted: 6.19.0-rc7-next- Tainted: [O]=OOT_MODULE Call Trace: dump_stack_lvl+0xb0/0xd0 lockdep_rcu_suspicious+0x151/0x1c0 dmem_cgroup_unregister_region+0x1e2/0x380 ? __pfx_dmem_test_init+0x10/0x10 [dmem_uaf] dmem_test_init+0x65/0xff0 [dmem_uaf] do_one_initcall+0xbb/0x3a0 The macro list_for_each_rcu() must be used within an RCU read-side critical section (between rcu_read_lock() and rcu_read_unlock()). Using it outside that context, as seen in dmem_cgroup_unregister_region(), triggers the lockdep warning because the RCU protection is not guaranteed. Replace list_for_each_rcu() with list_for_each_entry_safe(), which is appropriate for traversal under spinlock protection where nodes may be deleted. Fixes: b168ed458dde ("kernel/cgroup: Add "dmem" memory accounting cgroup") Cc: stable@vger.kernel.org # v6.14+ Signed-off-by: Chen Ridong Signed-off-by: Tejun Heo Signed-off-by: Greg Kroah-Hartman --- kernel/cgroup/dmem.c | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/kernel/cgroup/dmem.c b/kernel/cgroup/dmem.c index 1f0d6caaf2fbc0..787b334e0f5df6 100644 --- a/kernel/cgroup/dmem.c +++ b/kernel/cgroup/dmem.c @@ -423,7 +423,7 @@ static void dmemcg_free_region(struct kref *ref) */ void dmem_cgroup_unregister_region(struct dmem_cgroup_region *region) { - struct list_head *entry; + struct dmem_cgroup_pool_state *pool, *next; if (!region) return; @@ -433,10 +433,7 @@ void dmem_cgroup_unregister_region(struct dmem_cgroup_region *region) /* Remove from global region list */ list_del_rcu(®ion->region_node); - list_for_each_rcu(entry, ®ion->pools) { - struct dmem_cgroup_pool_state *pool = - container_of(entry, typeof(*pool), region_node); - + list_for_each_entry_safe(pool, next, ®ion->pools, region_node) { list_del_rcu(&pool->css_node); } From d3081353acaa6a638dcf75726066ea556a2de8d5 Mon Sep 17 00:00:00 2001 From: Chen Ridong Date: Mon, 2 Feb 2026 12:27:18 +0000 Subject: [PATCH 2997/3902] cgroup/dmem: avoid pool UAF commit 99a2ef500906138ba58093b9893972a5c303c734 upstream. An UAF issue was observed: BUG: KASAN: slab-use-after-free in page_counter_uncharge+0x65/0x150 Write of size 8 at addr ffff888106715440 by task insmod/527 CPU: 4 UID: 0 PID: 527 Comm: insmod 6.19.0-rc7-next-20260129+ #11 Tainted: [O]=OOT_MODULE Call Trace: dump_stack_lvl+0x82/0xd0 kasan_report+0xca/0x100 kasan_check_range+0x39/0x1c0 page_counter_uncharge+0x65/0x150 dmem_cgroup_uncharge+0x1f/0x260 Allocated by task 527: Freed by task 0: The buggy address belongs to the object at ffff888106715400 which belongs to the cache kmalloc-512 of size 512 The buggy address is located 64 bytes inside of freed 512-byte region [ffff888106715400, ffff888106715600) The buggy address belongs to the physical page: Memory state around the buggy address: ffff888106715300: fc fc fc fc fc fc fc fc fc fc fc fc fc fc fc fc ffff888106715380: fc fc fc fc fc fc fc fc fc fc fc fc fc fc fc fc >ffff888106715400: fa fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb ^ ffff888106715480: fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb ffff888106715500: fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb The issue occurs because a pool can still be held by a caller after its associated memory region is unregistered. The current implementation frees the pool even if users still hold references to it (e.g., before uncharge operations complete). This patch adds a reference counter to each pool, ensuring that a pool is only freed when its reference count drops to zero. Fixes: b168ed458dde ("kernel/cgroup: Add "dmem" memory accounting cgroup") Cc: stable@vger.kernel.org # v6.14+ Signed-off-by: Chen Ridong Signed-off-by: Tejun Heo Signed-off-by: Greg Kroah-Hartman --- kernel/cgroup/dmem.c | 60 ++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 58 insertions(+), 2 deletions(-) diff --git a/kernel/cgroup/dmem.c b/kernel/cgroup/dmem.c index 787b334e0f5df6..1ea6afffa985c4 100644 --- a/kernel/cgroup/dmem.c +++ b/kernel/cgroup/dmem.c @@ -14,6 +14,7 @@ #include #include #include +#include #include #include @@ -71,7 +72,9 @@ struct dmem_cgroup_pool_state { struct rcu_head rcu; struct page_counter cnt; + struct dmem_cgroup_pool_state *parent; + refcount_t ref; bool inited; }; @@ -88,6 +91,9 @@ struct dmem_cgroup_pool_state { static DEFINE_SPINLOCK(dmemcg_lock); static LIST_HEAD(dmem_cgroup_regions); +static void dmemcg_free_region(struct kref *ref); +static void dmemcg_pool_free_rcu(struct rcu_head *rcu); + static inline struct dmemcg_state * css_to_dmemcs(struct cgroup_subsys_state *css) { @@ -104,10 +110,38 @@ static struct dmemcg_state *parent_dmemcs(struct dmemcg_state *cg) return cg->css.parent ? css_to_dmemcs(cg->css.parent) : NULL; } +static void dmemcg_pool_get(struct dmem_cgroup_pool_state *pool) +{ + refcount_inc(&pool->ref); +} + +static bool dmemcg_pool_tryget(struct dmem_cgroup_pool_state *pool) +{ + return refcount_inc_not_zero(&pool->ref); +} + +static void dmemcg_pool_put(struct dmem_cgroup_pool_state *pool) +{ + if (!refcount_dec_and_test(&pool->ref)) + return; + + call_rcu(&pool->rcu, dmemcg_pool_free_rcu); +} + +static void dmemcg_pool_free_rcu(struct rcu_head *rcu) +{ + struct dmem_cgroup_pool_state *pool = container_of(rcu, typeof(*pool), rcu); + + if (pool->parent) + dmemcg_pool_put(pool->parent); + kref_put(&pool->region->ref, dmemcg_free_region); + kfree(pool); +} + static void free_cg_pool(struct dmem_cgroup_pool_state *pool) { list_del(&pool->region_node); - kfree(pool); + dmemcg_pool_put(pool); } static void @@ -342,6 +376,12 @@ alloc_pool_single(struct dmemcg_state *dmemcs, struct dmem_cgroup_region *region page_counter_init(&pool->cnt, ppool ? &ppool->cnt : NULL, true); reset_all_resource_limits(pool); + refcount_set(&pool->ref, 1); + kref_get(®ion->ref); + if (ppool && !pool->parent) { + pool->parent = ppool; + dmemcg_pool_get(ppool); + } list_add_tail_rcu(&pool->css_node, &dmemcs->pools); list_add_tail(&pool->region_node, ®ion->pools); @@ -389,6 +429,10 @@ get_cg_pool_locked(struct dmemcg_state *dmemcs, struct dmem_cgroup_region *regio /* Fix up parent links, mark as inited. */ pool->cnt.parent = &ppool->cnt; + if (ppool && !pool->parent) { + pool->parent = ppool; + dmemcg_pool_get(ppool); + } pool->inited = true; pool = ppool; @@ -435,6 +479,8 @@ void dmem_cgroup_unregister_region(struct dmem_cgroup_region *region) list_for_each_entry_safe(pool, next, ®ion->pools, region_node) { list_del_rcu(&pool->css_node); + list_del(&pool->region_node); + dmemcg_pool_put(pool); } /* @@ -515,8 +561,10 @@ static struct dmem_cgroup_region *dmemcg_get_region_by_name(const char *name) */ void dmem_cgroup_pool_state_put(struct dmem_cgroup_pool_state *pool) { - if (pool) + if (pool) { css_put(&pool->cs->css); + dmemcg_pool_put(pool); + } } EXPORT_SYMBOL_GPL(dmem_cgroup_pool_state_put); @@ -530,6 +578,8 @@ get_cg_pool_unlocked(struct dmemcg_state *cg, struct dmem_cgroup_region *region) pool = find_cg_pool_locked(cg, region); if (pool && !READ_ONCE(pool->inited)) pool = NULL; + if (pool && !dmemcg_pool_tryget(pool)) + pool = NULL; rcu_read_unlock(); while (!pool) { @@ -538,6 +588,8 @@ get_cg_pool_unlocked(struct dmemcg_state *cg, struct dmem_cgroup_region *region) pool = get_cg_pool_locked(cg, region, &allocpool); else pool = ERR_PTR(-ENODEV); + if (!IS_ERR(pool)) + dmemcg_pool_get(pool); spin_unlock(&dmemcg_lock); if (pool == ERR_PTR(-ENOMEM)) { @@ -573,6 +625,7 @@ void dmem_cgroup_uncharge(struct dmem_cgroup_pool_state *pool, u64 size) page_counter_uncharge(&pool->cnt, size); css_put(&pool->cs->css); + dmemcg_pool_put(pool); } EXPORT_SYMBOL_GPL(dmem_cgroup_uncharge); @@ -624,7 +677,9 @@ int dmem_cgroup_try_charge(struct dmem_cgroup_region *region, u64 size, if (ret_limit_pool) { *ret_limit_pool = container_of(fail, struct dmem_cgroup_pool_state, cnt); css_get(&(*ret_limit_pool)->cs->css); + dmemcg_pool_get(*ret_limit_pool); } + dmemcg_pool_put(pool); ret = -EAGAIN; goto err; } @@ -719,6 +774,7 @@ static ssize_t dmemcg_limit_write(struct kernfs_open_file *of, /* And commit */ apply(pool, new_limit); + dmemcg_pool_put(pool); out_put: kref_put(®ion->ref, dmemcg_free_region); From e9cdd54797dc2d1865309ad587f5d9ebfc87ed36 Mon Sep 17 00:00:00 2001 From: Mario Limonciello Date: Thu, 29 Jan 2026 13:47:22 -0600 Subject: [PATCH 2998/3902] drm/amd: Set minimum version for set_hw_resource_1 on gfx11 to 0x52 commit 1478a34470bf4755465d29b348b24a610bccc180 upstream. commit f81cd793119e ("drm/amd/amdgpu: Fix MES init sequence") caused a dependency on new enough MES firmware to use amdgpu. This was fixed on most gfx11 and gfx12 hardware with commit 0180e0a5dd5c ("drm/amdgpu/mes: add compatibility checks for set_hw_resource_1"), but this left out that GC 11.0.4 had breakage at MES 0x51. Bump the requirement to 0x52 instead. Reported-by: danijel@nausys.com Closes: https://gitlab.freedesktop.org/drm/amd/-/issues/4576 Fixes: f81cd793119e ("drm/amd/amdgpu: Fix MES init sequence") Reviewed-by: Alex Deucher Signed-off-by: Mario Limonciello Signed-off-by: Alex Deucher (cherry picked from commit c2d2ccc85faf8cc6934d50c18e43097eb453ade2) Cc: stable@vger.kernel.org Signed-off-by: Greg Kroah-Hartman --- drivers/gpu/drm/amd/amdgpu/mes_v11_0.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/gpu/drm/amd/amdgpu/mes_v11_0.c b/drivers/gpu/drm/amd/amdgpu/mes_v11_0.c index da575bb1377f14..05546a6e80aeca 100644 --- a/drivers/gpu/drm/amd/amdgpu/mes_v11_0.c +++ b/drivers/gpu/drm/amd/amdgpu/mes_v11_0.c @@ -1667,7 +1667,7 @@ static int mes_v11_0_hw_init(struct amdgpu_ip_block *ip_block) if (r) goto failure; - if ((adev->mes.sched_version & AMDGPU_MES_VERSION_MASK) >= 0x50) { + if ((adev->mes.sched_version & AMDGPU_MES_VERSION_MASK) >= 0x52) { r = mes_v11_0_set_hw_resources_1(&adev->mes); if (r) { DRM_ERROR("failed mes_v11_0_set_hw_resources_1, r=%d\n", r); From 11f8311f69e4c361717371b4901ff92daeb76e9c Mon Sep 17 00:00:00 2001 From: Debarghya Kundu Date: Mon, 2 Feb 2026 19:39:24 +0000 Subject: [PATCH 2999/3902] gve: Fix stats report corruption on queue count change commit 7b9ebcce0296e104a0d82a6b09d68564806158ff upstream. The driver and the NIC share a region in memory for stats reporting. The NIC calculates its offset into this region based on the total size of the stats region and the size of the NIC's stats. When the number of queues is changed, the driver's stats region is resized. If the queue count is increased, the NIC can write past the end of the allocated stats region, causing memory corruption. If the queue count is decreased, there is a gap between the driver and NIC stats, leading to incorrect stats reporting. This change fixes the issue by allocating stats region with maximum size, and the offset calculation for NIC stats is changed to match with the calculation of the NIC. Cc: stable@vger.kernel.org Fixes: 24aeb56f2d38 ("gve: Add Gvnic stats AQ command and ethtool show/set-priv-flags.") Signed-off-by: Debarghya Kundu Reviewed-by: Joshua Washington Signed-off-by: Harshitha Ramamurthy Reviewed-by: Jacob Keller Link: https://patch.msgid.link/20260202193925.3106272-2-hramamurthy@google.com Signed-off-by: Jakub Kicinski Signed-off-by: Greg Kroah-Hartman --- drivers/net/ethernet/google/gve/gve_ethtool.c | 54 ++++++++++++------- drivers/net/ethernet/google/gve/gve_main.c | 4 +- 2 files changed, 36 insertions(+), 22 deletions(-) diff --git a/drivers/net/ethernet/google/gve/gve_ethtool.c b/drivers/net/ethernet/google/gve/gve_ethtool.c index d0a223250845bc..8c07528fa6df07 100644 --- a/drivers/net/ethernet/google/gve/gve_ethtool.c +++ b/drivers/net/ethernet/google/gve/gve_ethtool.c @@ -156,7 +156,8 @@ gve_get_ethtool_stats(struct net_device *netdev, u64 rx_buf_alloc_fail, rx_desc_err_dropped_pkt, rx_hsplit_unsplit_pkt, rx_pkts, rx_hsplit_pkt, rx_skb_alloc_fail, rx_bytes, tx_pkts, tx_bytes, tx_dropped; - int stats_idx, base_stats_idx, max_stats_idx; + int rx_base_stats_idx, max_rx_stats_idx, max_tx_stats_idx; + int stats_idx, stats_region_len, nic_stats_len; struct stats *report_stats; int *rx_qid_to_stats_idx; int *tx_qid_to_stats_idx; @@ -265,20 +266,38 @@ gve_get_ethtool_stats(struct net_device *netdev, data[i++] = priv->stats_report_trigger_cnt; i = GVE_MAIN_STATS_LEN; - /* For rx cross-reporting stats, start from nic rx stats in report */ - base_stats_idx = GVE_TX_STATS_REPORT_NUM * num_tx_queues + - GVE_RX_STATS_REPORT_NUM * priv->rx_cfg.num_queues; - /* The boundary between driver stats and NIC stats shifts if there are - * stopped queues. - */ - base_stats_idx += NIC_RX_STATS_REPORT_NUM * num_stopped_rxqs + - NIC_TX_STATS_REPORT_NUM * num_stopped_txqs; - max_stats_idx = NIC_RX_STATS_REPORT_NUM * - (priv->rx_cfg.num_queues - num_stopped_rxqs) + - base_stats_idx; + rx_base_stats_idx = 0; + max_rx_stats_idx = 0; + max_tx_stats_idx = 0; + stats_region_len = priv->stats_report_len - + sizeof(struct gve_stats_report); + nic_stats_len = (NIC_RX_STATS_REPORT_NUM * priv->rx_cfg.num_queues + + NIC_TX_STATS_REPORT_NUM * num_tx_queues) * sizeof(struct stats); + if (unlikely((stats_region_len - + nic_stats_len) % sizeof(struct stats))) { + net_err_ratelimited("Starting index of NIC stats should be multiple of stats size"); + } else { + /* For rx cross-reporting stats, + * start from nic rx stats in report + */ + rx_base_stats_idx = (stats_region_len - nic_stats_len) / + sizeof(struct stats); + /* The boundary between driver stats and NIC stats + * shifts if there are stopped queues + */ + rx_base_stats_idx += NIC_RX_STATS_REPORT_NUM * + num_stopped_rxqs + NIC_TX_STATS_REPORT_NUM * + num_stopped_txqs; + max_rx_stats_idx = NIC_RX_STATS_REPORT_NUM * + (priv->rx_cfg.num_queues - num_stopped_rxqs) + + rx_base_stats_idx; + max_tx_stats_idx = NIC_TX_STATS_REPORT_NUM * + (num_tx_queues - num_stopped_txqs) + + max_rx_stats_idx; + } /* Preprocess the stats report for rx, map queue id to start index */ skip_nic_stats = false; - for (stats_idx = base_stats_idx; stats_idx < max_stats_idx; + for (stats_idx = rx_base_stats_idx; stats_idx < max_rx_stats_idx; stats_idx += NIC_RX_STATS_REPORT_NUM) { u32 stat_name = be32_to_cpu(report_stats[stats_idx].stat_name); u32 queue_id = be32_to_cpu(report_stats[stats_idx].queue_id); @@ -354,14 +373,9 @@ gve_get_ethtool_stats(struct net_device *netdev, i += priv->rx_cfg.num_queues * NUM_GVE_RX_CNTS; } - /* For tx cross-reporting stats, start from nic tx stats in report */ - base_stats_idx = max_stats_idx; - max_stats_idx = NIC_TX_STATS_REPORT_NUM * - (num_tx_queues - num_stopped_txqs) + - max_stats_idx; - /* Preprocess the stats report for tx, map queue id to start index */ skip_nic_stats = false; - for (stats_idx = base_stats_idx; stats_idx < max_stats_idx; + /* NIC TX stats start right after NIC RX stats */ + for (stats_idx = max_rx_stats_idx; stats_idx < max_tx_stats_idx; stats_idx += NIC_TX_STATS_REPORT_NUM) { u32 stat_name = be32_to_cpu(report_stats[stats_idx].stat_name); u32 queue_id = be32_to_cpu(report_stats[stats_idx].queue_id); diff --git a/drivers/net/ethernet/google/gve/gve_main.c b/drivers/net/ethernet/google/gve/gve_main.c index e240b7d22a3527..030800776ead17 100644 --- a/drivers/net/ethernet/google/gve/gve_main.c +++ b/drivers/net/ethernet/google/gve/gve_main.c @@ -283,9 +283,9 @@ static int gve_alloc_stats_report(struct gve_priv *priv) int tx_stats_num, rx_stats_num; tx_stats_num = (GVE_TX_STATS_REPORT_NUM + NIC_TX_STATS_REPORT_NUM) * - gve_num_tx_queues(priv); + priv->tx_cfg.max_queues; rx_stats_num = (GVE_RX_STATS_REPORT_NUM + NIC_RX_STATS_REPORT_NUM) * - priv->rx_cfg.num_queues; + priv->rx_cfg.max_queues; priv->stats_report_len = struct_size(priv->stats_report, stats, size_add(tx_stats_num, rx_stats_num)); priv->stats_report = From 41a7b9ab855c8ab66ff9e3e6af53bc7ef5df50b2 Mon Sep 17 00:00:00 2001 From: Max Yuan Date: Mon, 2 Feb 2026 19:39:25 +0000 Subject: [PATCH 3000/3902] gve: Correct ethtool rx_dropped calculation commit c7db85d579a1dccb624235534508c75fbf2dfe46 upstream. The gve driver's "rx_dropped" statistic, exposed via `ethtool -S`, incorrectly includes `rx_buf_alloc_fail` counts. These failures represent an inability to allocate receive buffers, not true packet drops where a received packet is discarded. This misrepresentation can lead to inaccurate diagnostics. This patch rectifies the ethtool "rx_dropped" calculation. It removes `rx_buf_alloc_fail` from the total and adds `xdp_tx_errors` and `xdp_redirect_errors`, which represent legitimate packet drops within the XDP path. Cc: stable@vger.kernel.org Fixes: 433e274b8f7b ("gve: Add stats for gve.") Signed-off-by: Max Yuan Reviewed-by: Jordan Rhee Reviewed-by: Joshua Washington Reviewed-by: Matt Olson Signed-off-by: Harshitha Ramamurthy Reviewed-by: Jacob Keller Link: https://patch.msgid.link/20260202193925.3106272-3-hramamurthy@google.com Signed-off-by: Jakub Kicinski Signed-off-by: Greg Kroah-Hartman --- drivers/net/ethernet/google/gve/gve_ethtool.c | 23 ++++++++++++++----- 1 file changed, 17 insertions(+), 6 deletions(-) diff --git a/drivers/net/ethernet/google/gve/gve_ethtool.c b/drivers/net/ethernet/google/gve/gve_ethtool.c index 8c07528fa6df07..edc0c718f71e9a 100644 --- a/drivers/net/ethernet/google/gve/gve_ethtool.c +++ b/drivers/net/ethernet/google/gve/gve_ethtool.c @@ -152,10 +152,11 @@ gve_get_ethtool_stats(struct net_device *netdev, u64 tmp_rx_pkts, tmp_rx_hsplit_pkt, tmp_rx_bytes, tmp_rx_hsplit_bytes, tmp_rx_skb_alloc_fail, tmp_rx_buf_alloc_fail, tmp_rx_desc_err_dropped_pkt, tmp_rx_hsplit_unsplit_pkt, - tmp_tx_pkts, tmp_tx_bytes; + tmp_tx_pkts, tmp_tx_bytes, + tmp_xdp_tx_errors, tmp_xdp_redirect_errors; u64 rx_buf_alloc_fail, rx_desc_err_dropped_pkt, rx_hsplit_unsplit_pkt, rx_pkts, rx_hsplit_pkt, rx_skb_alloc_fail, rx_bytes, tx_pkts, tx_bytes, - tx_dropped; + tx_dropped, xdp_tx_errors, xdp_redirect_errors; int rx_base_stats_idx, max_rx_stats_idx, max_tx_stats_idx; int stats_idx, stats_region_len, nic_stats_len; struct stats *report_stats; @@ -199,6 +200,7 @@ gve_get_ethtool_stats(struct net_device *netdev, for (rx_pkts = 0, rx_bytes = 0, rx_hsplit_pkt = 0, rx_skb_alloc_fail = 0, rx_buf_alloc_fail = 0, rx_desc_err_dropped_pkt = 0, rx_hsplit_unsplit_pkt = 0, + xdp_tx_errors = 0, xdp_redirect_errors = 0, ring = 0; ring < priv->rx_cfg.num_queues; ring++) { if (priv->rx) { @@ -216,6 +218,9 @@ gve_get_ethtool_stats(struct net_device *netdev, rx->rx_desc_err_dropped_pkt; tmp_rx_hsplit_unsplit_pkt = rx->rx_hsplit_unsplit_pkt; + tmp_xdp_tx_errors = rx->xdp_tx_errors; + tmp_xdp_redirect_errors = + rx->xdp_redirect_errors; } while (u64_stats_fetch_retry(&priv->rx[ring].statss, start)); rx_pkts += tmp_rx_pkts; @@ -225,6 +230,8 @@ gve_get_ethtool_stats(struct net_device *netdev, rx_buf_alloc_fail += tmp_rx_buf_alloc_fail; rx_desc_err_dropped_pkt += tmp_rx_desc_err_dropped_pkt; rx_hsplit_unsplit_pkt += tmp_rx_hsplit_unsplit_pkt; + xdp_tx_errors += tmp_xdp_tx_errors; + xdp_redirect_errors += tmp_xdp_redirect_errors; } } for (tx_pkts = 0, tx_bytes = 0, tx_dropped = 0, ring = 0; @@ -250,8 +257,8 @@ gve_get_ethtool_stats(struct net_device *netdev, data[i++] = rx_bytes; data[i++] = tx_bytes; /* total rx dropped packets */ - data[i++] = rx_skb_alloc_fail + rx_buf_alloc_fail + - rx_desc_err_dropped_pkt; + data[i++] = rx_skb_alloc_fail + rx_desc_err_dropped_pkt + + xdp_tx_errors + xdp_redirect_errors; data[i++] = tx_dropped; data[i++] = priv->tx_timeo_cnt; data[i++] = rx_skb_alloc_fail; @@ -330,6 +337,9 @@ gve_get_ethtool_stats(struct net_device *netdev, tmp_rx_buf_alloc_fail = rx->rx_buf_alloc_fail; tmp_rx_desc_err_dropped_pkt = rx->rx_desc_err_dropped_pkt; + tmp_xdp_tx_errors = rx->xdp_tx_errors; + tmp_xdp_redirect_errors = + rx->xdp_redirect_errors; } while (u64_stats_fetch_retry(&priv->rx[ring].statss, start)); data[i++] = tmp_rx_bytes; @@ -340,8 +350,9 @@ gve_get_ethtool_stats(struct net_device *netdev, data[i++] = rx->rx_frag_alloc_cnt; /* rx dropped packets */ data[i++] = tmp_rx_skb_alloc_fail + - tmp_rx_buf_alloc_fail + - tmp_rx_desc_err_dropped_pkt; + tmp_rx_desc_err_dropped_pkt + + tmp_xdp_tx_errors + + tmp_xdp_redirect_errors; data[i++] = rx->rx_copybreak_pkt; data[i++] = rx->rx_copied_pkt; /* stats from NIC */ From 7b6a0f121d50234aab3e7ab9a62ebe826d40a32a Mon Sep 17 00:00:00 2001 From: Kairui Song Date: Thu, 29 Jan 2026 00:19:23 +0800 Subject: [PATCH 3001/3902] mm, shmem: prevent infinite loop on truncate race commit 2030dddf95451b4e7a389f052091e7c4b7b274c6 upstream. When truncating a large swap entry, shmem_free_swap() returns 0 when the entry's index doesn't match the given index due to lookup alignment. The failure fallback path checks if the entry crosses the end border and aborts when it happens, so truncate won't erase an unexpected entry or range. But one scenario was ignored. When `index` points to the middle of a large swap entry, and the large swap entry doesn't go across the end border, find_get_entries() will return that large swap entry as the first item in the batch with `indices[0]` equal to `index`. The entry's base index will be smaller than `indices[0]`, so shmem_free_swap() will fail and return 0 due to the "base < index" check. The code will then call shmem_confirm_swap(), get the order, check if it crosses the END boundary (which it doesn't), and retry with the same index. The next iteration will find the same entry again at the same index with same indices, leading to an infinite loop. Fix this by retrying with a round-down index, and abort if the index is smaller than the truncate range. Link: https://lkml.kernel.org/r/aXo6ltB5iqAKJzY8@KASONG-MC4 Fixes: 809bc86517cc ("mm: shmem: support large folio swap out") Fixes: 8a1968bd997f ("mm/shmem, swap: fix race of truncate and swap entry split") Signed-off-by: Kairui Song Reported-by: Chris Mason Closes: https://lore.kernel.org/linux-mm/20260128130336.727049-1-clm@meta.com/ Reviewed-by: Baolin Wang Cc: Baoquan He Cc: Barry Song Cc: Chris Li Cc: Hugh Dickins Cc: Kemeng Shi Cc: Nhat Pham Cc: Signed-off-by: Andrew Morton Signed-off-by: Greg Kroah-Hartman --- mm/shmem.c | 23 ++++++++++++++--------- 1 file changed, 14 insertions(+), 9 deletions(-) diff --git a/mm/shmem.c b/mm/shmem.c index d131148323067f..94c5b0d78ac31e 100644 --- a/mm/shmem.c +++ b/mm/shmem.c @@ -1193,17 +1193,22 @@ static void shmem_undo_range(struct inode *inode, loff_t lstart, loff_t lend, swaps_freed = shmem_free_swap(mapping, indices[i], end - 1, folio); if (!swaps_freed) { - /* - * If found a large swap entry cross the end border, - * skip it as the truncate_inode_partial_folio above - * should have at least zerod its content once. - */ + pgoff_t base = indices[i]; + order = shmem_confirm_swap(mapping, indices[i], radix_to_swp_entry(folio)); - if (order > 0 && indices[i] + (1 << order) > end) - continue; - /* Swap was replaced by page: retry */ - index = indices[i]; + /* + * If found a large swap entry cross the end or start + * border, skip it as the truncate_inode_partial_folio + * above should have at least zerod its content once. + */ + if (order > 0) { + base = round_down(base, 1 << order); + if (base < start || base + (1 << order) > end) + continue; + } + /* Swap was replaced by page or extended, retry */ + index = base; break; } nr_swaps_freed += swaps_freed; From 5f645222eb30c91135119e12eccfd1b8ea88140e Mon Sep 17 00:00:00 2001 From: Bert Karwatzki Date: Sun, 1 Feb 2026 01:24:45 +0100 Subject: [PATCH 3002/3902] Revert "drm/amd: Check if ASPM is enabled from PCIe subsystem" MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit commit 243b467dea1735fed904c2e54d248a46fa417a2d upstream. This reverts commit 7294863a6f01248d72b61d38478978d638641bee. This commit was erroneously applied again after commit 0ab5d711ec74 ("drm/amd: Refactor `amdgpu_aspm` to be evaluated per device") removed it, leading to very hard to debug crashes, when used with a system with two AMD GPUs of which only one supports ASPM. Link: https://lore.kernel.org/linux-acpi/20251006120944.7880-1-spasswolf@web.de/ Link: https://github.com/acpica/acpica/issues/1060 Fixes: 0ab5d711ec74 ("drm/amd: Refactor `amdgpu_aspm` to be evaluated per device") Signed-off-by: Bert Karwatzki Reviewed-by: Christian König Reviewed-by: Mario Limonciello (AMD) Signed-off-by: Mario Limonciello Signed-off-by: Alex Deucher (cherry picked from commit 97a9689300eb2b393ba5efc17c8e5db835917080) Cc: stable@vger.kernel.org Signed-off-by: Greg Kroah-Hartman --- drivers/gpu/drm/amd/amdgpu/amdgpu_drv.c | 3 --- 1 file changed, 3 deletions(-) diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_drv.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_drv.c index 7333e19291cf93..ec9516d6ae976f 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_drv.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_drv.c @@ -2334,9 +2334,6 @@ static int amdgpu_pci_probe(struct pci_dev *pdev, return -ENODEV; } - if (amdgpu_aspm == -1 && !pcie_aspm_enabled(pdev)) - amdgpu_aspm = 0; - if (amdgpu_virtual_display || amdgpu_device_asic_has_dc_support(pdev, flags & AMD_ASIC_MASK)) supports_atomic = true; From 9cc8caba82c2bd918bcc40d1c0e9cdaa119bf283 Mon Sep 17 00:00:00 2001 From: Dave Airlie Date: Tue, 3 Feb 2026 15:21:12 +1000 Subject: [PATCH 3003/3902] nouveau: add a third state to the fini handler. commit 8f8a4dce64013737701d13565cf6107f42b725ea upstream. This is just refactoring to allow the lower layers to distinguish between suspend and runtime suspend. GSP 570 needs to set a flag with the GPU is going into GCOFF, this flag taken from the opengpu driver is set whenever runtime suspend is enterning GCOFF but not for normal suspend paths. This just refactors the code, a subsequent patch use the information. Fixes: 53dac0623853 ("drm/nouveau/gsp: add support for 570.144") Cc: Reviewed-by: Lyude Paul Tested-by: Lyude Paul Signed-off-by: Dave Airlie Link: https://patch.msgid.link/20260203052431.2219998-3-airlied@gmail.com Signed-off-by: Greg Kroah-Hartman --- drivers/gpu/drm/nouveau/include/nvif/client.h | 2 +- drivers/gpu/drm/nouveau/include/nvif/driver.h | 2 +- .../drm/nouveau/include/nvkm/core/device.h | 3 ++- .../drm/nouveau/include/nvkm/core/engine.h | 2 +- .../drm/nouveau/include/nvkm/core/object.h | 5 +++-- .../drm/nouveau/include/nvkm/core/oproxy.h | 2 +- .../drm/nouveau/include/nvkm/core/subdev.h | 4 ++-- .../nouveau/include/nvkm/core/suspend_state.h | 11 ++++++++++ drivers/gpu/drm/nouveau/nouveau_drm.c | 2 +- drivers/gpu/drm/nouveau/nouveau_nvif.c | 10 +++++++-- drivers/gpu/drm/nouveau/nvif/client.c | 4 ++-- drivers/gpu/drm/nouveau/nvkm/core/engine.c | 4 ++-- drivers/gpu/drm/nouveau/nvkm/core/ioctl.c | 4 ++-- drivers/gpu/drm/nouveau/nvkm/core/object.c | 20 +++++++++++++---- drivers/gpu/drm/nouveau/nvkm/core/oproxy.c | 2 +- drivers/gpu/drm/nouveau/nvkm/core/subdev.c | 18 ++++++++++++--- drivers/gpu/drm/nouveau/nvkm/core/uevent.c | 2 +- .../gpu/drm/nouveau/nvkm/engine/ce/ga100.c | 2 +- drivers/gpu/drm/nouveau/nvkm/engine/ce/priv.h | 2 +- .../gpu/drm/nouveau/nvkm/engine/device/base.c | 22 ++++++++++++++----- .../gpu/drm/nouveau/nvkm/engine/device/pci.c | 4 ++-- .../gpu/drm/nouveau/nvkm/engine/device/priv.h | 2 +- .../gpu/drm/nouveau/nvkm/engine/device/user.c | 2 +- .../gpu/drm/nouveau/nvkm/engine/disp/base.c | 4 ++-- .../gpu/drm/nouveau/nvkm/engine/disp/chan.c | 2 +- drivers/gpu/drm/nouveau/nvkm/engine/falcon.c | 4 ++-- .../gpu/drm/nouveau/nvkm/engine/fifo/base.c | 2 +- .../gpu/drm/nouveau/nvkm/engine/fifo/uchan.c | 6 ++--- drivers/gpu/drm/nouveau/nvkm/engine/gr/base.c | 4 ++-- .../gpu/drm/nouveau/nvkm/engine/gr/gf100.c | 2 +- drivers/gpu/drm/nouveau/nvkm/engine/gr/nv04.c | 2 +- drivers/gpu/drm/nouveau/nvkm/engine/gr/nv10.c | 2 +- drivers/gpu/drm/nouveau/nvkm/engine/gr/nv20.c | 2 +- drivers/gpu/drm/nouveau/nvkm/engine/gr/nv20.h | 2 +- drivers/gpu/drm/nouveau/nvkm/engine/gr/nv40.c | 4 ++-- .../gpu/drm/nouveau/nvkm/engine/mpeg/nv44.c | 2 +- .../gpu/drm/nouveau/nvkm/engine/sec2/base.c | 2 +- drivers/gpu/drm/nouveau/nvkm/engine/xtensa.c | 4 ++-- .../gpu/drm/nouveau/nvkm/subdev/acr/base.c | 2 +- .../gpu/drm/nouveau/nvkm/subdev/bar/base.c | 2 +- .../gpu/drm/nouveau/nvkm/subdev/clk/base.c | 2 +- .../drm/nouveau/nvkm/subdev/devinit/base.c | 4 ++-- .../gpu/drm/nouveau/nvkm/subdev/fault/base.c | 2 +- .../gpu/drm/nouveau/nvkm/subdev/fault/user.c | 2 +- .../gpu/drm/nouveau/nvkm/subdev/gpio/base.c | 2 +- .../gpu/drm/nouveau/nvkm/subdev/gsp/base.c | 2 +- .../gpu/drm/nouveau/nvkm/subdev/gsp/gh100.c | 2 +- .../gpu/drm/nouveau/nvkm/subdev/gsp/priv.h | 8 +++---- .../drm/nouveau/nvkm/subdev/gsp/rm/r535/gsp.c | 2 +- .../gpu/drm/nouveau/nvkm/subdev/gsp/tu102.c | 2 +- .../gpu/drm/nouveau/nvkm/subdev/i2c/base.c | 2 +- .../drm/nouveau/nvkm/subdev/instmem/base.c | 2 +- .../gpu/drm/nouveau/nvkm/subdev/pci/base.c | 2 +- .../gpu/drm/nouveau/nvkm/subdev/pmu/base.c | 2 +- .../gpu/drm/nouveau/nvkm/subdev/therm/base.c | 6 ++--- .../gpu/drm/nouveau/nvkm/subdev/timer/base.c | 2 +- 56 files changed, 139 insertions(+), 84 deletions(-) create mode 100644 drivers/gpu/drm/nouveau/include/nvkm/core/suspend_state.h diff --git a/drivers/gpu/drm/nouveau/include/nvif/client.h b/drivers/gpu/drm/nouveau/include/nvif/client.h index 03f1d564eb124d..b698c74306f8fe 100644 --- a/drivers/gpu/drm/nouveau/include/nvif/client.h +++ b/drivers/gpu/drm/nouveau/include/nvif/client.h @@ -11,7 +11,7 @@ struct nvif_client { int nvif_client_ctor(struct nvif_client *parent, const char *name, struct nvif_client *); void nvif_client_dtor(struct nvif_client *); -int nvif_client_suspend(struct nvif_client *); +int nvif_client_suspend(struct nvif_client *, bool); int nvif_client_resume(struct nvif_client *); /*XXX*/ diff --git a/drivers/gpu/drm/nouveau/include/nvif/driver.h b/drivers/gpu/drm/nouveau/include/nvif/driver.h index 7b08ff769039a3..61c8a177b28fdc 100644 --- a/drivers/gpu/drm/nouveau/include/nvif/driver.h +++ b/drivers/gpu/drm/nouveau/include/nvif/driver.h @@ -8,7 +8,7 @@ struct nvif_driver { const char *name; int (*init)(const char *name, u64 device, const char *cfg, const char *dbg, void **priv); - int (*suspend)(void *priv); + int (*suspend)(void *priv, bool runtime); int (*resume)(void *priv); int (*ioctl)(void *priv, void *data, u32 size, void **hack); void __iomem *(*map)(void *priv, u64 handle, u32 size); diff --git a/drivers/gpu/drm/nouveau/include/nvkm/core/device.h b/drivers/gpu/drm/nouveau/include/nvkm/core/device.h index 99579e7b937605..954a89d43bada1 100644 --- a/drivers/gpu/drm/nouveau/include/nvkm/core/device.h +++ b/drivers/gpu/drm/nouveau/include/nvkm/core/device.h @@ -2,6 +2,7 @@ #ifndef __NVKM_DEVICE_H__ #define __NVKM_DEVICE_H__ #include +#include #include enum nvkm_subdev_type; @@ -93,7 +94,7 @@ struct nvkm_device_func { void *(*dtor)(struct nvkm_device *); int (*preinit)(struct nvkm_device *); int (*init)(struct nvkm_device *); - void (*fini)(struct nvkm_device *, bool suspend); + void (*fini)(struct nvkm_device *, enum nvkm_suspend_state suspend); int (*irq)(struct nvkm_device *); resource_size_t (*resource_addr)(struct nvkm_device *, enum nvkm_bar_id); resource_size_t (*resource_size)(struct nvkm_device *, enum nvkm_bar_id); diff --git a/drivers/gpu/drm/nouveau/include/nvkm/core/engine.h b/drivers/gpu/drm/nouveau/include/nvkm/core/engine.h index 738899fcf30b6a..1e97be6c65644e 100644 --- a/drivers/gpu/drm/nouveau/include/nvkm/core/engine.h +++ b/drivers/gpu/drm/nouveau/include/nvkm/core/engine.h @@ -20,7 +20,7 @@ struct nvkm_engine_func { int (*oneinit)(struct nvkm_engine *); int (*info)(struct nvkm_engine *, u64 mthd, u64 *data); int (*init)(struct nvkm_engine *); - int (*fini)(struct nvkm_engine *, bool suspend); + int (*fini)(struct nvkm_engine *, enum nvkm_suspend_state suspend); int (*reset)(struct nvkm_engine *); int (*nonstall)(struct nvkm_engine *); void (*intr)(struct nvkm_engine *); diff --git a/drivers/gpu/drm/nouveau/include/nvkm/core/object.h b/drivers/gpu/drm/nouveau/include/nvkm/core/object.h index 10107ef3ca49ef..54d356154274f9 100644 --- a/drivers/gpu/drm/nouveau/include/nvkm/core/object.h +++ b/drivers/gpu/drm/nouveau/include/nvkm/core/object.h @@ -2,6 +2,7 @@ #ifndef __NVKM_OBJECT_H__ #define __NVKM_OBJECT_H__ #include +#include struct nvkm_event; struct nvkm_gpuobj; struct nvkm_uevent; @@ -27,7 +28,7 @@ enum nvkm_object_map { struct nvkm_object_func { void *(*dtor)(struct nvkm_object *); int (*init)(struct nvkm_object *); - int (*fini)(struct nvkm_object *, bool suspend); + int (*fini)(struct nvkm_object *, enum nvkm_suspend_state suspend); int (*mthd)(struct nvkm_object *, u32 mthd, void *data, u32 size); int (*ntfy)(struct nvkm_object *, u32 mthd, struct nvkm_event **); int (*map)(struct nvkm_object *, void *argv, u32 argc, @@ -49,7 +50,7 @@ int nvkm_object_new(const struct nvkm_oclass *, void *data, u32 size, void nvkm_object_del(struct nvkm_object **); void *nvkm_object_dtor(struct nvkm_object *); int nvkm_object_init(struct nvkm_object *); -int nvkm_object_fini(struct nvkm_object *, bool suspend); +int nvkm_object_fini(struct nvkm_object *, enum nvkm_suspend_state); int nvkm_object_mthd(struct nvkm_object *, u32 mthd, void *data, u32 size); int nvkm_object_ntfy(struct nvkm_object *, u32 mthd, struct nvkm_event **); int nvkm_object_map(struct nvkm_object *, void *argv, u32 argc, diff --git a/drivers/gpu/drm/nouveau/include/nvkm/core/oproxy.h b/drivers/gpu/drm/nouveau/include/nvkm/core/oproxy.h index 0e70a9afba33d4..cf66aee4d111d2 100644 --- a/drivers/gpu/drm/nouveau/include/nvkm/core/oproxy.h +++ b/drivers/gpu/drm/nouveau/include/nvkm/core/oproxy.h @@ -13,7 +13,7 @@ struct nvkm_oproxy { struct nvkm_oproxy_func { void (*dtor[2])(struct nvkm_oproxy *); int (*init[2])(struct nvkm_oproxy *); - int (*fini[2])(struct nvkm_oproxy *, bool suspend); + int (*fini[2])(struct nvkm_oproxy *, enum nvkm_suspend_state suspend); }; void nvkm_oproxy_ctor(const struct nvkm_oproxy_func *, diff --git a/drivers/gpu/drm/nouveau/include/nvkm/core/subdev.h b/drivers/gpu/drm/nouveau/include/nvkm/core/subdev.h index bce6e1ba09ea04..bd6b1b658e40f3 100644 --- a/drivers/gpu/drm/nouveau/include/nvkm/core/subdev.h +++ b/drivers/gpu/drm/nouveau/include/nvkm/core/subdev.h @@ -40,7 +40,7 @@ struct nvkm_subdev_func { int (*oneinit)(struct nvkm_subdev *); int (*info)(struct nvkm_subdev *, u64 mthd, u64 *data); int (*init)(struct nvkm_subdev *); - int (*fini)(struct nvkm_subdev *, bool suspend); + int (*fini)(struct nvkm_subdev *, enum nvkm_suspend_state suspend); void (*intr)(struct nvkm_subdev *); }; @@ -65,7 +65,7 @@ void nvkm_subdev_unref(struct nvkm_subdev *); int nvkm_subdev_preinit(struct nvkm_subdev *); int nvkm_subdev_oneinit(struct nvkm_subdev *); int nvkm_subdev_init(struct nvkm_subdev *); -int nvkm_subdev_fini(struct nvkm_subdev *, bool suspend); +int nvkm_subdev_fini(struct nvkm_subdev *, enum nvkm_suspend_state suspend); int nvkm_subdev_info(struct nvkm_subdev *, u64, u64 *); void nvkm_subdev_intr(struct nvkm_subdev *); diff --git a/drivers/gpu/drm/nouveau/include/nvkm/core/suspend_state.h b/drivers/gpu/drm/nouveau/include/nvkm/core/suspend_state.h new file mode 100644 index 00000000000000..134120fb71f4eb --- /dev/null +++ b/drivers/gpu/drm/nouveau/include/nvkm/core/suspend_state.h @@ -0,0 +1,11 @@ +/* SPDX-License-Identifier: MIT */ +#ifndef __NVKM_SUSPEND_STATE_H__ +#define __NVKM_SUSPEND_STATE_H__ + +enum nvkm_suspend_state { + NVKM_POWEROFF, + NVKM_SUSPEND, + NVKM_RUNTIME_SUSPEND, +}; + +#endif diff --git a/drivers/gpu/drm/nouveau/nouveau_drm.c b/drivers/gpu/drm/nouveau/nouveau_drm.c index 1527b801f013aa..dc469e571c0aa4 100644 --- a/drivers/gpu/drm/nouveau/nouveau_drm.c +++ b/drivers/gpu/drm/nouveau/nouveau_drm.c @@ -983,7 +983,7 @@ nouveau_do_suspend(struct nouveau_drm *drm, bool runtime) } NV_DEBUG(drm, "suspending object tree...\n"); - ret = nvif_client_suspend(&drm->_client); + ret = nvif_client_suspend(&drm->_client, runtime); if (ret) goto fail_client; diff --git a/drivers/gpu/drm/nouveau/nouveau_nvif.c b/drivers/gpu/drm/nouveau/nouveau_nvif.c index adb802421fda58..eeb4ebbc16bf6a 100644 --- a/drivers/gpu/drm/nouveau/nouveau_nvif.c +++ b/drivers/gpu/drm/nouveau/nouveau_nvif.c @@ -62,10 +62,16 @@ nvkm_client_resume(void *priv) } static int -nvkm_client_suspend(void *priv) +nvkm_client_suspend(void *priv, bool runtime) { struct nvkm_client *client = priv; - return nvkm_object_fini(&client->object, true); + enum nvkm_suspend_state state; + + if (runtime) + state = NVKM_RUNTIME_SUSPEND; + else + state = NVKM_SUSPEND; + return nvkm_object_fini(&client->object, state); } static int diff --git a/drivers/gpu/drm/nouveau/nvif/client.c b/drivers/gpu/drm/nouveau/nvif/client.c index fdf5054ed7d83c..36d3c99786bd5c 100644 --- a/drivers/gpu/drm/nouveau/nvif/client.c +++ b/drivers/gpu/drm/nouveau/nvif/client.c @@ -30,9 +30,9 @@ #include int -nvif_client_suspend(struct nvif_client *client) +nvif_client_suspend(struct nvif_client *client, bool runtime) { - return client->driver->suspend(client->object.priv); + return client->driver->suspend(client->object.priv, runtime); } int diff --git a/drivers/gpu/drm/nouveau/nvkm/core/engine.c b/drivers/gpu/drm/nouveau/nvkm/core/engine.c index 36a31e9eea228e..5bf62940d7be1e 100644 --- a/drivers/gpu/drm/nouveau/nvkm/core/engine.c +++ b/drivers/gpu/drm/nouveau/nvkm/core/engine.c @@ -41,7 +41,7 @@ nvkm_engine_reset(struct nvkm_engine *engine) if (engine->func->reset) return engine->func->reset(engine); - nvkm_subdev_fini(&engine->subdev, false); + nvkm_subdev_fini(&engine->subdev, NVKM_POWEROFF); return nvkm_subdev_init(&engine->subdev); } @@ -98,7 +98,7 @@ nvkm_engine_info(struct nvkm_subdev *subdev, u64 mthd, u64 *data) } static int -nvkm_engine_fini(struct nvkm_subdev *subdev, bool suspend) +nvkm_engine_fini(struct nvkm_subdev *subdev, enum nvkm_suspend_state suspend) { struct nvkm_engine *engine = nvkm_engine(subdev); if (engine->func->fini) diff --git a/drivers/gpu/drm/nouveau/nvkm/core/ioctl.c b/drivers/gpu/drm/nouveau/nvkm/core/ioctl.c index 45051a1249daca..b8fc9be678515f 100644 --- a/drivers/gpu/drm/nouveau/nvkm/core/ioctl.c +++ b/drivers/gpu/drm/nouveau/nvkm/core/ioctl.c @@ -141,7 +141,7 @@ nvkm_ioctl_new(struct nvkm_client *client, } ret = -EEXIST; } - nvkm_object_fini(object, false); + nvkm_object_fini(object, NVKM_POWEROFF); } nvkm_object_del(&object); @@ -160,7 +160,7 @@ nvkm_ioctl_del(struct nvkm_client *client, nvif_ioctl(object, "delete size %d\n", size); if (!(ret = nvif_unvers(ret, &data, &size, args->none))) { nvif_ioctl(object, "delete\n"); - nvkm_object_fini(object, false); + nvkm_object_fini(object, NVKM_POWEROFF); nvkm_object_del(&object); } diff --git a/drivers/gpu/drm/nouveau/nvkm/core/object.c b/drivers/gpu/drm/nouveau/nvkm/core/object.c index 390c265cf8af42..af9f00f74c2886 100644 --- a/drivers/gpu/drm/nouveau/nvkm/core/object.c +++ b/drivers/gpu/drm/nouveau/nvkm/core/object.c @@ -142,13 +142,25 @@ nvkm_object_bind(struct nvkm_object *object, struct nvkm_gpuobj *gpuobj, } int -nvkm_object_fini(struct nvkm_object *object, bool suspend) +nvkm_object_fini(struct nvkm_object *object, enum nvkm_suspend_state suspend) { - const char *action = suspend ? "suspend" : "fini"; + const char *action; struct nvkm_object *child; s64 time; int ret; + switch (suspend) { + case NVKM_POWEROFF: + default: + action = "fini"; + break; + case NVKM_SUSPEND: + action = "suspend"; + break; + case NVKM_RUNTIME_SUSPEND: + action = "runtime"; + break; + } nvif_debug(object, "%s children...\n", action); time = ktime_to_us(ktime_get()); list_for_each_entry_reverse(child, &object->tree, head) { @@ -212,11 +224,11 @@ nvkm_object_init(struct nvkm_object *object) fail_child: list_for_each_entry_continue_reverse(child, &object->tree, head) - nvkm_object_fini(child, false); + nvkm_object_fini(child, NVKM_POWEROFF); fail: nvif_error(object, "init failed with %d\n", ret); if (object->func->fini) - object->func->fini(object, false); + object->func->fini(object, NVKM_POWEROFF); return ret; } diff --git a/drivers/gpu/drm/nouveau/nvkm/core/oproxy.c b/drivers/gpu/drm/nouveau/nvkm/core/oproxy.c index 5db80d1780f089..7c9edf7527686f 100644 --- a/drivers/gpu/drm/nouveau/nvkm/core/oproxy.c +++ b/drivers/gpu/drm/nouveau/nvkm/core/oproxy.c @@ -87,7 +87,7 @@ nvkm_oproxy_uevent(struct nvkm_object *object, void *argv, u32 argc, } static int -nvkm_oproxy_fini(struct nvkm_object *object, bool suspend) +nvkm_oproxy_fini(struct nvkm_object *object, enum nvkm_suspend_state suspend) { struct nvkm_oproxy *oproxy = nvkm_oproxy(object); int ret; diff --git a/drivers/gpu/drm/nouveau/nvkm/core/subdev.c b/drivers/gpu/drm/nouveau/nvkm/core/subdev.c index 6c20e827a069a5..b7045d1c841586 100644 --- a/drivers/gpu/drm/nouveau/nvkm/core/subdev.c +++ b/drivers/gpu/drm/nouveau/nvkm/core/subdev.c @@ -51,12 +51,24 @@ nvkm_subdev_info(struct nvkm_subdev *subdev, u64 mthd, u64 *data) } int -nvkm_subdev_fini(struct nvkm_subdev *subdev, bool suspend) +nvkm_subdev_fini(struct nvkm_subdev *subdev, enum nvkm_suspend_state suspend) { struct nvkm_device *device = subdev->device; - const char *action = suspend ? "suspend" : subdev->use.enabled ? "fini" : "reset"; + const char *action; s64 time; + switch (suspend) { + case NVKM_POWEROFF: + default: + action = subdev->use.enabled ? "fini" : "reset"; + break; + case NVKM_SUSPEND: + action = "suspend"; + break; + case NVKM_RUNTIME_SUSPEND: + action = "runtime"; + break; + } nvkm_trace(subdev, "%s running...\n", action); time = ktime_to_us(ktime_get()); @@ -186,7 +198,7 @@ void nvkm_subdev_unref(struct nvkm_subdev *subdev) { if (refcount_dec_and_mutex_lock(&subdev->use.refcount, &subdev->use.mutex)) { - nvkm_subdev_fini(subdev, false); + nvkm_subdev_fini(subdev, NVKM_POWEROFF); mutex_unlock(&subdev->use.mutex); } } diff --git a/drivers/gpu/drm/nouveau/nvkm/core/uevent.c b/drivers/gpu/drm/nouveau/nvkm/core/uevent.c index cc254c390a5719..46beb6e470ee36 100644 --- a/drivers/gpu/drm/nouveau/nvkm/core/uevent.c +++ b/drivers/gpu/drm/nouveau/nvkm/core/uevent.c @@ -73,7 +73,7 @@ nvkm_uevent_mthd(struct nvkm_object *object, u32 mthd, void *argv, u32 argc) } static int -nvkm_uevent_fini(struct nvkm_object *object, bool suspend) +nvkm_uevent_fini(struct nvkm_object *object, enum nvkm_suspend_state suspend) { struct nvkm_uevent *uevent = nvkm_uevent(object); diff --git a/drivers/gpu/drm/nouveau/nvkm/engine/ce/ga100.c b/drivers/gpu/drm/nouveau/nvkm/engine/ce/ga100.c index 1c0c6013870602..1a3caf69760827 100644 --- a/drivers/gpu/drm/nouveau/nvkm/engine/ce/ga100.c +++ b/drivers/gpu/drm/nouveau/nvkm/engine/ce/ga100.c @@ -46,7 +46,7 @@ ga100_ce_nonstall(struct nvkm_engine *engine) } int -ga100_ce_fini(struct nvkm_engine *engine, bool suspend) +ga100_ce_fini(struct nvkm_engine *engine, enum nvkm_suspend_state suspend) { nvkm_inth_block(&engine->subdev.inth); return 0; diff --git a/drivers/gpu/drm/nouveau/nvkm/engine/ce/priv.h b/drivers/gpu/drm/nouveau/nvkm/engine/ce/priv.h index 34fd2657134b81..f07b4585331043 100644 --- a/drivers/gpu/drm/nouveau/nvkm/engine/ce/priv.h +++ b/drivers/gpu/drm/nouveau/nvkm/engine/ce/priv.h @@ -14,7 +14,7 @@ extern const struct nvkm_object_func gv100_ce_cclass; int ga100_ce_oneinit(struct nvkm_engine *); int ga100_ce_init(struct nvkm_engine *); -int ga100_ce_fini(struct nvkm_engine *, bool); +int ga100_ce_fini(struct nvkm_engine *, enum nvkm_suspend_state); int ga100_ce_nonstall(struct nvkm_engine *); u32 gb202_ce_grce_mask(struct nvkm_device *); diff --git a/drivers/gpu/drm/nouveau/nvkm/engine/device/base.c b/drivers/gpu/drm/nouveau/nvkm/engine/device/base.c index 3375a59ebf1a4a..a965914f1c2fbb 100644 --- a/drivers/gpu/drm/nouveau/nvkm/engine/device/base.c +++ b/drivers/gpu/drm/nouveau/nvkm/engine/device/base.c @@ -2935,13 +2935,25 @@ nvkm_device_engine(struct nvkm_device *device, int type, int inst) } int -nvkm_device_fini(struct nvkm_device *device, bool suspend) +nvkm_device_fini(struct nvkm_device *device, enum nvkm_suspend_state suspend) { - const char *action = suspend ? "suspend" : "fini"; + const char *action; struct nvkm_subdev *subdev; int ret; s64 time; + switch (suspend) { + case NVKM_POWEROFF: + default: + action = "fini"; + break; + case NVKM_SUSPEND: + action = "suspend"; + break; + case NVKM_RUNTIME_SUSPEND: + action = "runtime"; + break; + } nvdev_trace(device, "%s running...\n", action); time = ktime_to_us(ktime_get()); @@ -3031,7 +3043,7 @@ nvkm_device_init(struct nvkm_device *device) if (ret) return ret; - nvkm_device_fini(device, false); + nvkm_device_fini(device, NVKM_POWEROFF); nvdev_trace(device, "init running...\n"); time = ktime_to_us(ktime_get()); @@ -3059,9 +3071,9 @@ nvkm_device_init(struct nvkm_device *device) fail_subdev: list_for_each_entry_from(subdev, &device->subdev, head) - nvkm_subdev_fini(subdev, false); + nvkm_subdev_fini(subdev, NVKM_POWEROFF); fail: - nvkm_device_fini(device, false); + nvkm_device_fini(device, NVKM_POWEROFF); nvdev_error(device, "init failed with %d\n", ret); return ret; diff --git a/drivers/gpu/drm/nouveau/nvkm/engine/device/pci.c b/drivers/gpu/drm/nouveau/nvkm/engine/device/pci.c index 8f0261a0d618bf..4c29b60460d48d 100644 --- a/drivers/gpu/drm/nouveau/nvkm/engine/device/pci.c +++ b/drivers/gpu/drm/nouveau/nvkm/engine/device/pci.c @@ -1605,10 +1605,10 @@ nvkm_device_pci_irq(struct nvkm_device *device) } static void -nvkm_device_pci_fini(struct nvkm_device *device, bool suspend) +nvkm_device_pci_fini(struct nvkm_device *device, enum nvkm_suspend_state suspend) { struct nvkm_device_pci *pdev = nvkm_device_pci(device); - if (suspend) { + if (suspend != NVKM_POWEROFF) { pci_disable_device(pdev->pdev); pdev->suspend = true; } diff --git a/drivers/gpu/drm/nouveau/nvkm/engine/device/priv.h b/drivers/gpu/drm/nouveau/nvkm/engine/device/priv.h index 75ee7506d4432b..d0c40f0342442d 100644 --- a/drivers/gpu/drm/nouveau/nvkm/engine/device/priv.h +++ b/drivers/gpu/drm/nouveau/nvkm/engine/device/priv.h @@ -56,5 +56,5 @@ int nvkm_device_ctor(const struct nvkm_device_func *, const char *name, const char *cfg, const char *dbg, struct nvkm_device *); int nvkm_device_init(struct nvkm_device *); -int nvkm_device_fini(struct nvkm_device *, bool suspend); +int nvkm_device_fini(struct nvkm_device *, enum nvkm_suspend_state suspend); #endif diff --git a/drivers/gpu/drm/nouveau/nvkm/engine/device/user.c b/drivers/gpu/drm/nouveau/nvkm/engine/device/user.c index 58191b7a0494ef..32ff3181f47b67 100644 --- a/drivers/gpu/drm/nouveau/nvkm/engine/device/user.c +++ b/drivers/gpu/drm/nouveau/nvkm/engine/device/user.c @@ -218,7 +218,7 @@ nvkm_udevice_map(struct nvkm_object *object, void *argv, u32 argc, } static int -nvkm_udevice_fini(struct nvkm_object *object, bool suspend) +nvkm_udevice_fini(struct nvkm_object *object, enum nvkm_suspend_state suspend) { struct nvkm_udevice *udev = nvkm_udevice(object); struct nvkm_device *device = udev->device; diff --git a/drivers/gpu/drm/nouveau/nvkm/engine/disp/base.c b/drivers/gpu/drm/nouveau/nvkm/engine/disp/base.c index b24eb1e560bc7c..84745f60912e0b 100644 --- a/drivers/gpu/drm/nouveau/nvkm/engine/disp/base.c +++ b/drivers/gpu/drm/nouveau/nvkm/engine/disp/base.c @@ -99,13 +99,13 @@ nvkm_disp_intr(struct nvkm_engine *engine) } static int -nvkm_disp_fini(struct nvkm_engine *engine, bool suspend) +nvkm_disp_fini(struct nvkm_engine *engine, enum nvkm_suspend_state suspend) { struct nvkm_disp *disp = nvkm_disp(engine); struct nvkm_outp *outp; if (disp->func->fini) - disp->func->fini(disp, suspend); + disp->func->fini(disp, suspend != NVKM_POWEROFF); list_for_each_entry(outp, &disp->outps, head) { if (outp->func->fini) diff --git a/drivers/gpu/drm/nouveau/nvkm/engine/disp/chan.c b/drivers/gpu/drm/nouveau/nvkm/engine/disp/chan.c index 9b84e357d3544e..57a62a2de7c7da 100644 --- a/drivers/gpu/drm/nouveau/nvkm/engine/disp/chan.c +++ b/drivers/gpu/drm/nouveau/nvkm/engine/disp/chan.c @@ -128,7 +128,7 @@ nvkm_disp_chan_child_get(struct nvkm_object *object, int index, struct nvkm_ocla } static int -nvkm_disp_chan_fini(struct nvkm_object *object, bool suspend) +nvkm_disp_chan_fini(struct nvkm_object *object, enum nvkm_suspend_state suspend) { struct nvkm_disp_chan *chan = nvkm_disp_chan(object); diff --git a/drivers/gpu/drm/nouveau/nvkm/engine/falcon.c b/drivers/gpu/drm/nouveau/nvkm/engine/falcon.c index fd5ee9f0af360c..cf8e356867b4fc 100644 --- a/drivers/gpu/drm/nouveau/nvkm/engine/falcon.c +++ b/drivers/gpu/drm/nouveau/nvkm/engine/falcon.c @@ -93,13 +93,13 @@ nvkm_falcon_intr(struct nvkm_engine *engine) } static int -nvkm_falcon_fini(struct nvkm_engine *engine, bool suspend) +nvkm_falcon_fini(struct nvkm_engine *engine, enum nvkm_suspend_state suspend) { struct nvkm_falcon *falcon = nvkm_falcon(engine); struct nvkm_device *device = falcon->engine.subdev.device; const u32 base = falcon->addr; - if (!suspend) { + if (suspend == NVKM_POWEROFF) { nvkm_memory_unref(&falcon->core); if (falcon->external) { vfree(falcon->data.data); diff --git a/drivers/gpu/drm/nouveau/nvkm/engine/fifo/base.c b/drivers/gpu/drm/nouveau/nvkm/engine/fifo/base.c index 6fd4e60634fbe4..1561287a32f208 100644 --- a/drivers/gpu/drm/nouveau/nvkm/engine/fifo/base.c +++ b/drivers/gpu/drm/nouveau/nvkm/engine/fifo/base.c @@ -122,7 +122,7 @@ nvkm_fifo_class_get(struct nvkm_oclass *oclass, int index, const struct nvkm_dev } static int -nvkm_fifo_fini(struct nvkm_engine *engine, bool suspend) +nvkm_fifo_fini(struct nvkm_engine *engine, enum nvkm_suspend_state suspend) { struct nvkm_fifo *fifo = nvkm_fifo(engine); struct nvkm_runl *runl; diff --git a/drivers/gpu/drm/nouveau/nvkm/engine/fifo/uchan.c b/drivers/gpu/drm/nouveau/nvkm/engine/fifo/uchan.c index 52420a1edca52a..c978b97e10c64a 100644 --- a/drivers/gpu/drm/nouveau/nvkm/engine/fifo/uchan.c +++ b/drivers/gpu/drm/nouveau/nvkm/engine/fifo/uchan.c @@ -72,7 +72,7 @@ struct nvkm_uobj { }; static int -nvkm_uchan_object_fini_1(struct nvkm_oproxy *oproxy, bool suspend) +nvkm_uchan_object_fini_1(struct nvkm_oproxy *oproxy, enum nvkm_suspend_state suspend) { struct nvkm_uobj *uobj = container_of(oproxy, typeof(*uobj), oproxy); struct nvkm_chan *chan = uobj->chan; @@ -87,7 +87,7 @@ nvkm_uchan_object_fini_1(struct nvkm_oproxy *oproxy, bool suspend) nvkm_chan_cctx_bind(chan, ectx->engn, NULL); if (refcount_dec_and_test(&ectx->uses)) - nvkm_object_fini(ectx->object, false); + nvkm_object_fini(ectx->object, NVKM_POWEROFF); mutex_unlock(&chan->cgrp->mutex); } @@ -269,7 +269,7 @@ nvkm_uchan_map(struct nvkm_object *object, void *argv, u32 argc, } static int -nvkm_uchan_fini(struct nvkm_object *object, bool suspend) +nvkm_uchan_fini(struct nvkm_object *object, enum nvkm_suspend_state suspend) { struct nvkm_chan *chan = nvkm_uchan(object)->chan; diff --git a/drivers/gpu/drm/nouveau/nvkm/engine/gr/base.c b/drivers/gpu/drm/nouveau/nvkm/engine/gr/base.c index f5e68f09df768d..cd4908b1b4dfff 100644 --- a/drivers/gpu/drm/nouveau/nvkm/engine/gr/base.c +++ b/drivers/gpu/drm/nouveau/nvkm/engine/gr/base.c @@ -168,11 +168,11 @@ nvkm_gr_init(struct nvkm_engine *engine) } static int -nvkm_gr_fini(struct nvkm_engine *engine, bool suspend) +nvkm_gr_fini(struct nvkm_engine *engine, enum nvkm_suspend_state suspend) { struct nvkm_gr *gr = nvkm_gr(engine); if (gr->func->fini) - return gr->func->fini(gr, suspend); + return gr->func->fini(gr, suspend != NVKM_POWEROFF); return 0; } diff --git a/drivers/gpu/drm/nouveau/nvkm/engine/gr/gf100.c b/drivers/gpu/drm/nouveau/nvkm/engine/gr/gf100.c index 3ea447f6a45b51..3608215f0f11c8 100644 --- a/drivers/gpu/drm/nouveau/nvkm/engine/gr/gf100.c +++ b/drivers/gpu/drm/nouveau/nvkm/engine/gr/gf100.c @@ -2330,7 +2330,7 @@ gf100_gr_reset(struct nvkm_gr *base) WARN_ON(gf100_gr_fecs_halt_pipeline(gr)); - subdev->func->fini(subdev, false); + subdev->func->fini(subdev, NVKM_POWEROFF); nvkm_mc_disable(device, subdev->type, subdev->inst); if (gr->func->gpccs.reset) gr->func->gpccs.reset(gr); diff --git a/drivers/gpu/drm/nouveau/nvkm/engine/gr/nv04.c b/drivers/gpu/drm/nouveau/nvkm/engine/gr/nv04.c index ca822f07b63e9e..82937df8b8c0d2 100644 --- a/drivers/gpu/drm/nouveau/nvkm/engine/gr/nv04.c +++ b/drivers/gpu/drm/nouveau/nvkm/engine/gr/nv04.c @@ -1158,7 +1158,7 @@ nv04_gr_chan_dtor(struct nvkm_object *object) } static int -nv04_gr_chan_fini(struct nvkm_object *object, bool suspend) +nv04_gr_chan_fini(struct nvkm_object *object, enum nvkm_suspend_state suspend) { struct nv04_gr_chan *chan = nv04_gr_chan(object); struct nv04_gr *gr = chan->gr; diff --git a/drivers/gpu/drm/nouveau/nvkm/engine/gr/nv10.c b/drivers/gpu/drm/nouveau/nvkm/engine/gr/nv10.c index 92ef7c9b29101c..fcb4e4fce83f7a 100644 --- a/drivers/gpu/drm/nouveau/nvkm/engine/gr/nv10.c +++ b/drivers/gpu/drm/nouveau/nvkm/engine/gr/nv10.c @@ -951,7 +951,7 @@ nv10_gr_context_switch(struct nv10_gr *gr) } static int -nv10_gr_chan_fini(struct nvkm_object *object, bool suspend) +nv10_gr_chan_fini(struct nvkm_object *object, enum nvkm_suspend_state suspend) { struct nv10_gr_chan *chan = nv10_gr_chan(object); struct nv10_gr *gr = chan->gr; diff --git a/drivers/gpu/drm/nouveau/nvkm/engine/gr/nv20.c b/drivers/gpu/drm/nouveau/nvkm/engine/gr/nv20.c index 13407fafe94797..ab57b3b40228e8 100644 --- a/drivers/gpu/drm/nouveau/nvkm/engine/gr/nv20.c +++ b/drivers/gpu/drm/nouveau/nvkm/engine/gr/nv20.c @@ -27,7 +27,7 @@ nv20_gr_chan_init(struct nvkm_object *object) } int -nv20_gr_chan_fini(struct nvkm_object *object, bool suspend) +nv20_gr_chan_fini(struct nvkm_object *object, enum nvkm_suspend_state suspend) { struct nv20_gr_chan *chan = nv20_gr_chan(object); struct nv20_gr *gr = chan->gr; diff --git a/drivers/gpu/drm/nouveau/nvkm/engine/gr/nv20.h b/drivers/gpu/drm/nouveau/nvkm/engine/gr/nv20.h index c0d2be53413e0d..786c7832f7acdb 100644 --- a/drivers/gpu/drm/nouveau/nvkm/engine/gr/nv20.h +++ b/drivers/gpu/drm/nouveau/nvkm/engine/gr/nv20.h @@ -31,5 +31,5 @@ struct nv20_gr_chan { void *nv20_gr_chan_dtor(struct nvkm_object *); int nv20_gr_chan_init(struct nvkm_object *); -int nv20_gr_chan_fini(struct nvkm_object *, bool); +int nv20_gr_chan_fini(struct nvkm_object *, enum nvkm_suspend_state); #endif diff --git a/drivers/gpu/drm/nouveau/nvkm/engine/gr/nv40.c b/drivers/gpu/drm/nouveau/nvkm/engine/gr/nv40.c index b609b0150ba140..e3e797cf30349e 100644 --- a/drivers/gpu/drm/nouveau/nvkm/engine/gr/nv40.c +++ b/drivers/gpu/drm/nouveau/nvkm/engine/gr/nv40.c @@ -89,7 +89,7 @@ nv40_gr_chan_bind(struct nvkm_object *object, struct nvkm_gpuobj *parent, } static int -nv40_gr_chan_fini(struct nvkm_object *object, bool suspend) +nv40_gr_chan_fini(struct nvkm_object *object, enum nvkm_suspend_state suspend) { struct nv40_gr_chan *chan = nv40_gr_chan(object); struct nv40_gr *gr = chan->gr; @@ -101,7 +101,7 @@ nv40_gr_chan_fini(struct nvkm_object *object, bool suspend) nvkm_mask(device, 0x400720, 0x00000001, 0x00000000); if (nvkm_rd32(device, 0x40032c) == inst) { - if (suspend) { + if (suspend != NVKM_POWEROFF) { nvkm_wr32(device, 0x400720, 0x00000000); nvkm_wr32(device, 0x400784, inst); nvkm_mask(device, 0x400310, 0x00000020, 0x00000020); diff --git a/drivers/gpu/drm/nouveau/nvkm/engine/mpeg/nv44.c b/drivers/gpu/drm/nouveau/nvkm/engine/mpeg/nv44.c index 4b1374adbda3a1..38146f9cc81c4e 100644 --- a/drivers/gpu/drm/nouveau/nvkm/engine/mpeg/nv44.c +++ b/drivers/gpu/drm/nouveau/nvkm/engine/mpeg/nv44.c @@ -65,7 +65,7 @@ nv44_mpeg_chan_bind(struct nvkm_object *object, struct nvkm_gpuobj *parent, } static int -nv44_mpeg_chan_fini(struct nvkm_object *object, bool suspend) +nv44_mpeg_chan_fini(struct nvkm_object *object, enum nvkm_suspend_state suspend) { struct nv44_mpeg_chan *chan = nv44_mpeg_chan(object); diff --git a/drivers/gpu/drm/nouveau/nvkm/engine/sec2/base.c b/drivers/gpu/drm/nouveau/nvkm/engine/sec2/base.c index f2c60da5d1e86f..3e4d6a680ee954 100644 --- a/drivers/gpu/drm/nouveau/nvkm/engine/sec2/base.c +++ b/drivers/gpu/drm/nouveau/nvkm/engine/sec2/base.c @@ -37,7 +37,7 @@ nvkm_sec2_finimsg(void *priv, struct nvfw_falcon_msg *hdr) } static int -nvkm_sec2_fini(struct nvkm_engine *engine, bool suspend) +nvkm_sec2_fini(struct nvkm_engine *engine, enum nvkm_suspend_state suspend) { struct nvkm_sec2 *sec2 = nvkm_sec2(engine); struct nvkm_subdev *subdev = &sec2->engine.subdev; diff --git a/drivers/gpu/drm/nouveau/nvkm/engine/xtensa.c b/drivers/gpu/drm/nouveau/nvkm/engine/xtensa.c index f7d3ba0afb5564..910a5bb2d191db 100644 --- a/drivers/gpu/drm/nouveau/nvkm/engine/xtensa.c +++ b/drivers/gpu/drm/nouveau/nvkm/engine/xtensa.c @@ -76,7 +76,7 @@ nvkm_xtensa_intr(struct nvkm_engine *engine) } static int -nvkm_xtensa_fini(struct nvkm_engine *engine, bool suspend) +nvkm_xtensa_fini(struct nvkm_engine *engine, enum nvkm_suspend_state suspend) { struct nvkm_xtensa *xtensa = nvkm_xtensa(engine); struct nvkm_device *device = xtensa->engine.subdev.device; @@ -85,7 +85,7 @@ nvkm_xtensa_fini(struct nvkm_engine *engine, bool suspend) nvkm_wr32(device, base + 0xd84, 0); /* INTR_EN */ nvkm_wr32(device, base + 0xd94, 0); /* FIFO_CTRL */ - if (!suspend) + if (suspend == NVKM_POWEROFF) nvkm_memory_unref(&xtensa->gpu_fw); return 0; } diff --git a/drivers/gpu/drm/nouveau/nvkm/subdev/acr/base.c b/drivers/gpu/drm/nouveau/nvkm/subdev/acr/base.c index 9b8ca4e898f903..13d829593180c6 100644 --- a/drivers/gpu/drm/nouveau/nvkm/subdev/acr/base.c +++ b/drivers/gpu/drm/nouveau/nvkm/subdev/acr/base.c @@ -182,7 +182,7 @@ nvkm_acr_managed_falcon(struct nvkm_device *device, enum nvkm_acr_lsf_id id) } static int -nvkm_acr_fini(struct nvkm_subdev *subdev, bool suspend) +nvkm_acr_fini(struct nvkm_subdev *subdev, enum nvkm_suspend_state suspend) { if (!subdev->use.enabled) return 0; diff --git a/drivers/gpu/drm/nouveau/nvkm/subdev/bar/base.c b/drivers/gpu/drm/nouveau/nvkm/subdev/bar/base.c index 91bc53be97ffc2..7dee55bf9ada9e 100644 --- a/drivers/gpu/drm/nouveau/nvkm/subdev/bar/base.c +++ b/drivers/gpu/drm/nouveau/nvkm/subdev/bar/base.c @@ -90,7 +90,7 @@ nvkm_bar_bar2_init(struct nvkm_device *device) } static int -nvkm_bar_fini(struct nvkm_subdev *subdev, bool suspend) +nvkm_bar_fini(struct nvkm_subdev *subdev, enum nvkm_suspend_state suspend) { struct nvkm_bar *bar = nvkm_bar(subdev); diff --git a/drivers/gpu/drm/nouveau/nvkm/subdev/clk/base.c b/drivers/gpu/drm/nouveau/nvkm/subdev/clk/base.c index 178dc56909c274..71420f81714b35 100644 --- a/drivers/gpu/drm/nouveau/nvkm/subdev/clk/base.c +++ b/drivers/gpu/drm/nouveau/nvkm/subdev/clk/base.c @@ -577,7 +577,7 @@ nvkm_clk_read(struct nvkm_clk *clk, enum nv_clk_src src) } static int -nvkm_clk_fini(struct nvkm_subdev *subdev, bool suspend) +nvkm_clk_fini(struct nvkm_subdev *subdev, enum nvkm_suspend_state suspend) { struct nvkm_clk *clk = nvkm_clk(subdev); flush_work(&clk->work); diff --git a/drivers/gpu/drm/nouveau/nvkm/subdev/devinit/base.c b/drivers/gpu/drm/nouveau/nvkm/subdev/devinit/base.c index 3d9319c319c647..ad5ec9ee1294db 100644 --- a/drivers/gpu/drm/nouveau/nvkm/subdev/devinit/base.c +++ b/drivers/gpu/drm/nouveau/nvkm/subdev/devinit/base.c @@ -67,11 +67,11 @@ nvkm_devinit_post(struct nvkm_devinit *init) } static int -nvkm_devinit_fini(struct nvkm_subdev *subdev, bool suspend) +nvkm_devinit_fini(struct nvkm_subdev *subdev, enum nvkm_suspend_state suspend) { struct nvkm_devinit *init = nvkm_devinit(subdev); /* force full reinit on resume */ - if (suspend) + if (suspend != NVKM_POWEROFF) init->post = true; return 0; } diff --git a/drivers/gpu/drm/nouveau/nvkm/subdev/fault/base.c b/drivers/gpu/drm/nouveau/nvkm/subdev/fault/base.c index b53ac9a2552fa7..d8d32bb5bcd9e7 100644 --- a/drivers/gpu/drm/nouveau/nvkm/subdev/fault/base.c +++ b/drivers/gpu/drm/nouveau/nvkm/subdev/fault/base.c @@ -51,7 +51,7 @@ nvkm_fault_intr(struct nvkm_subdev *subdev) } static int -nvkm_fault_fini(struct nvkm_subdev *subdev, bool suspend) +nvkm_fault_fini(struct nvkm_subdev *subdev, enum nvkm_suspend_state suspend) { struct nvkm_fault *fault = nvkm_fault(subdev); if (fault->func->fini) diff --git a/drivers/gpu/drm/nouveau/nvkm/subdev/fault/user.c b/drivers/gpu/drm/nouveau/nvkm/subdev/fault/user.c index cd2fbc0472d8e7..8ab052d18e5d36 100644 --- a/drivers/gpu/drm/nouveau/nvkm/subdev/fault/user.c +++ b/drivers/gpu/drm/nouveau/nvkm/subdev/fault/user.c @@ -56,7 +56,7 @@ nvkm_ufault_map(struct nvkm_object *object, void *argv, u32 argc, } static int -nvkm_ufault_fini(struct nvkm_object *object, bool suspend) +nvkm_ufault_fini(struct nvkm_object *object, enum nvkm_suspend_state suspend) { struct nvkm_fault_buffer *buffer = nvkm_fault_buffer(object); buffer->fault->func->buffer.fini(buffer); diff --git a/drivers/gpu/drm/nouveau/nvkm/subdev/gpio/base.c b/drivers/gpu/drm/nouveau/nvkm/subdev/gpio/base.c index b196baa376dcdb..b2c34878a68f63 100644 --- a/drivers/gpu/drm/nouveau/nvkm/subdev/gpio/base.c +++ b/drivers/gpu/drm/nouveau/nvkm/subdev/gpio/base.c @@ -144,7 +144,7 @@ nvkm_gpio_intr(struct nvkm_subdev *subdev) } static int -nvkm_gpio_fini(struct nvkm_subdev *subdev, bool suspend) +nvkm_gpio_fini(struct nvkm_subdev *subdev, enum nvkm_suspend_state suspend) { struct nvkm_gpio *gpio = nvkm_gpio(subdev); u32 mask = (1ULL << gpio->func->lines) - 1; diff --git a/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/base.c b/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/base.c index 7ccb4176106653..30cb843ba35cdd 100644 --- a/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/base.c +++ b/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/base.c @@ -48,7 +48,7 @@ nvkm_gsp_intr_stall(struct nvkm_gsp *gsp, enum nvkm_subdev_type type, int inst) } static int -nvkm_gsp_fini(struct nvkm_subdev *subdev, bool suspend) +nvkm_gsp_fini(struct nvkm_subdev *subdev, enum nvkm_suspend_state suspend) { struct nvkm_gsp *gsp = nvkm_gsp(subdev); diff --git a/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/gh100.c b/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/gh100.c index b0dd5fce7bad36..88436a26417799 100644 --- a/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/gh100.c +++ b/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/gh100.c @@ -17,7 +17,7 @@ #include int -gh100_gsp_fini(struct nvkm_gsp *gsp, bool suspend) +gh100_gsp_fini(struct nvkm_gsp *gsp, enum nvkm_suspend_state suspend) { struct nvkm_falcon *falcon = &gsp->falcon; int ret, time = 4000; diff --git a/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/priv.h b/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/priv.h index 9dd66a2e38017b..71b7203bef5073 100644 --- a/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/priv.h +++ b/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/priv.h @@ -59,7 +59,7 @@ struct nvkm_gsp_func { void (*dtor)(struct nvkm_gsp *); int (*oneinit)(struct nvkm_gsp *); int (*init)(struct nvkm_gsp *); - int (*fini)(struct nvkm_gsp *, bool suspend); + int (*fini)(struct nvkm_gsp *, enum nvkm_suspend_state suspend); int (*reset)(struct nvkm_gsp *); struct { @@ -75,7 +75,7 @@ int tu102_gsp_fwsec_sb_ctor(struct nvkm_gsp *); void tu102_gsp_fwsec_sb_dtor(struct nvkm_gsp *); int tu102_gsp_oneinit(struct nvkm_gsp *); int tu102_gsp_init(struct nvkm_gsp *); -int tu102_gsp_fini(struct nvkm_gsp *, bool suspend); +int tu102_gsp_fini(struct nvkm_gsp *, enum nvkm_suspend_state suspend); int tu102_gsp_reset(struct nvkm_gsp *); u64 tu102_gsp_wpr_heap_size(struct nvkm_gsp *); @@ -87,12 +87,12 @@ int ga102_gsp_reset(struct nvkm_gsp *); int gh100_gsp_oneinit(struct nvkm_gsp *); int gh100_gsp_init(struct nvkm_gsp *); -int gh100_gsp_fini(struct nvkm_gsp *, bool suspend); +int gh100_gsp_fini(struct nvkm_gsp *, enum nvkm_suspend_state suspend); void r535_gsp_dtor(struct nvkm_gsp *); int r535_gsp_oneinit(struct nvkm_gsp *); int r535_gsp_init(struct nvkm_gsp *); -int r535_gsp_fini(struct nvkm_gsp *, bool suspend); +int r535_gsp_fini(struct nvkm_gsp *, enum nvkm_suspend_state suspend); int nvkm_gsp_new_(const struct nvkm_gsp_fwif *, struct nvkm_device *, enum nvkm_subdev_type, int, struct nvkm_gsp **); diff --git a/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/rm/r535/gsp.c b/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/rm/r535/gsp.c index 2a7e80c6d70f39..af77a4cc8a1629 100644 --- a/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/rm/r535/gsp.c +++ b/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/rm/r535/gsp.c @@ -1721,7 +1721,7 @@ r535_gsp_sr_data_size(struct nvkm_gsp *gsp) } int -r535_gsp_fini(struct nvkm_gsp *gsp, bool suspend) +r535_gsp_fini(struct nvkm_gsp *gsp, enum nvkm_suspend_state suspend) { struct nvkm_rm *rm = gsp->rm; int ret; diff --git a/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/tu102.c b/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/tu102.c index 04b642a1f73051..19cb269e7a2659 100644 --- a/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/tu102.c +++ b/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/tu102.c @@ -161,7 +161,7 @@ tu102_gsp_reset(struct nvkm_gsp *gsp) } int -tu102_gsp_fini(struct nvkm_gsp *gsp, bool suspend) +tu102_gsp_fini(struct nvkm_gsp *gsp, enum nvkm_suspend_state suspend) { u32 mbox0 = 0xff, mbox1 = 0xff; int ret; diff --git a/drivers/gpu/drm/nouveau/nvkm/subdev/i2c/base.c b/drivers/gpu/drm/nouveau/nvkm/subdev/i2c/base.c index 7ec17e8435a136..454bb21815a272 100644 --- a/drivers/gpu/drm/nouveau/nvkm/subdev/i2c/base.c +++ b/drivers/gpu/drm/nouveau/nvkm/subdev/i2c/base.c @@ -135,7 +135,7 @@ nvkm_i2c_intr(struct nvkm_subdev *subdev) } static int -nvkm_i2c_fini(struct nvkm_subdev *subdev, bool suspend) +nvkm_i2c_fini(struct nvkm_subdev *subdev, enum nvkm_suspend_state suspend) { struct nvkm_i2c *i2c = nvkm_i2c(subdev); struct nvkm_i2c_pad *pad; diff --git a/drivers/gpu/drm/nouveau/nvkm/subdev/instmem/base.c b/drivers/gpu/drm/nouveau/nvkm/subdev/instmem/base.c index 2f55bab8e132ff..6b9ed61684a027 100644 --- a/drivers/gpu/drm/nouveau/nvkm/subdev/instmem/base.c +++ b/drivers/gpu/drm/nouveau/nvkm/subdev/instmem/base.c @@ -176,7 +176,7 @@ nvkm_instmem_boot(struct nvkm_instmem *imem) } static int -nvkm_instmem_fini(struct nvkm_subdev *subdev, bool suspend) +nvkm_instmem_fini(struct nvkm_subdev *subdev, enum nvkm_suspend_state suspend) { struct nvkm_instmem *imem = nvkm_instmem(subdev); int ret; diff --git a/drivers/gpu/drm/nouveau/nvkm/subdev/pci/base.c b/drivers/gpu/drm/nouveau/nvkm/subdev/pci/base.c index 6867934256a768..0f3e0d324a529c 100644 --- a/drivers/gpu/drm/nouveau/nvkm/subdev/pci/base.c +++ b/drivers/gpu/drm/nouveau/nvkm/subdev/pci/base.c @@ -74,7 +74,7 @@ nvkm_pci_rom_shadow(struct nvkm_pci *pci, bool shadow) } static int -nvkm_pci_fini(struct nvkm_subdev *subdev, bool suspend) +nvkm_pci_fini(struct nvkm_subdev *subdev, enum nvkm_suspend_state suspend) { struct nvkm_pci *pci = nvkm_pci(subdev); diff --git a/drivers/gpu/drm/nouveau/nvkm/subdev/pmu/base.c b/drivers/gpu/drm/nouveau/nvkm/subdev/pmu/base.c index 8f2f50ad4ded1f..9e9004ec4588b8 100644 --- a/drivers/gpu/drm/nouveau/nvkm/subdev/pmu/base.c +++ b/drivers/gpu/drm/nouveau/nvkm/subdev/pmu/base.c @@ -77,7 +77,7 @@ nvkm_pmu_intr(struct nvkm_subdev *subdev) } static int -nvkm_pmu_fini(struct nvkm_subdev *subdev, bool suspend) +nvkm_pmu_fini(struct nvkm_subdev *subdev, enum nvkm_suspend_state suspend) { struct nvkm_pmu *pmu = nvkm_pmu(subdev); diff --git a/drivers/gpu/drm/nouveau/nvkm/subdev/therm/base.c b/drivers/gpu/drm/nouveau/nvkm/subdev/therm/base.c index fc5ee118e91067..1510aba339560b 100644 --- a/drivers/gpu/drm/nouveau/nvkm/subdev/therm/base.c +++ b/drivers/gpu/drm/nouveau/nvkm/subdev/therm/base.c @@ -341,15 +341,15 @@ nvkm_therm_intr(struct nvkm_subdev *subdev) } static int -nvkm_therm_fini(struct nvkm_subdev *subdev, bool suspend) +nvkm_therm_fini(struct nvkm_subdev *subdev, enum nvkm_suspend_state suspend) { struct nvkm_therm *therm = nvkm_therm(subdev); if (therm->func->fini) therm->func->fini(therm); - nvkm_therm_fan_fini(therm, suspend); - nvkm_therm_sensor_fini(therm, suspend); + nvkm_therm_fan_fini(therm, suspend != NVKM_POWEROFF); + nvkm_therm_sensor_fini(therm, suspend != NVKM_POWEROFF); if (suspend) { therm->suspend = therm->mode; diff --git a/drivers/gpu/drm/nouveau/nvkm/subdev/timer/base.c b/drivers/gpu/drm/nouveau/nvkm/subdev/timer/base.c index 8b0da0c062685d..a5c3c282b5d077 100644 --- a/drivers/gpu/drm/nouveau/nvkm/subdev/timer/base.c +++ b/drivers/gpu/drm/nouveau/nvkm/subdev/timer/base.c @@ -149,7 +149,7 @@ nvkm_timer_intr(struct nvkm_subdev *subdev) } static int -nvkm_timer_fini(struct nvkm_subdev *subdev, bool suspend) +nvkm_timer_fini(struct nvkm_subdev *subdev, enum nvkm_suspend_state suspend) { struct nvkm_timer *tmr = nvkm_timer(subdev); tmr->func->alarm_fini(tmr); From a20887d5239a32ad88dfbb64981040135d1c91ee Mon Sep 17 00:00:00 2001 From: Dave Airlie Date: Tue, 3 Feb 2026 15:21:11 +1000 Subject: [PATCH 3004/3902] nouveau/gsp: use rpc sequence numbers properly. commit 90caca3b7264cc3e92e347b2004fff4e386fc26e upstream. There are two layers of sequence numbers, one at the msg level and one at the rpc level. 570 firmware started asserting on the sequence numbers being in the right order, and we would see nocat records with asserts in them. Add the rpc level sequence number support. Fixes: 53dac0623853 ("drm/nouveau/gsp: add support for 570.144") Cc: Signed-off-by: Dave Airlie Reviewed-by: Lyude Paul Tested-by: Lyude Paul Link: https://patch.msgid.link/20260203052431.2219998-2-airlied@gmail.com Signed-off-by: Greg Kroah-Hartman --- drivers/gpu/drm/nouveau/include/nvkm/subdev/gsp.h | 6 ++++++ drivers/gpu/drm/nouveau/nvkm/subdev/gsp/rm/r535/gsp.c | 4 ++-- drivers/gpu/drm/nouveau/nvkm/subdev/gsp/rm/r535/rpc.c | 6 ++++++ drivers/gpu/drm/nouveau/nvkm/subdev/gsp/rm/r570/gsp.c | 2 +- 4 files changed, 15 insertions(+), 3 deletions(-) diff --git a/drivers/gpu/drm/nouveau/include/nvkm/subdev/gsp.h b/drivers/gpu/drm/nouveau/include/nvkm/subdev/gsp.h index b8b97e10ae83ed..64fed208e4cf85 100644 --- a/drivers/gpu/drm/nouveau/include/nvkm/subdev/gsp.h +++ b/drivers/gpu/drm/nouveau/include/nvkm/subdev/gsp.h @@ -44,6 +44,9 @@ typedef void (*nvkm_gsp_event_func)(struct nvkm_gsp_event *, void *repv, u32 rep * NVKM_GSP_RPC_REPLY_NOWAIT - If specified, immediately return to the * caller after the GSP RPC command is issued. * + * NVKM_GSP_RPC_REPLY_NOSEQ - If specified, exactly like NOWAIT + * but don't emit RPC sequence number. + * * NVKM_GSP_RPC_REPLY_RECV - If specified, wait and receive the entire GSP * RPC message after the GSP RPC command is issued. * @@ -53,6 +56,7 @@ typedef void (*nvkm_gsp_event_func)(struct nvkm_gsp_event *, void *repv, u32 rep */ enum nvkm_gsp_rpc_reply_policy { NVKM_GSP_RPC_REPLY_NOWAIT = 0, + NVKM_GSP_RPC_REPLY_NOSEQ, NVKM_GSP_RPC_REPLY_RECV, NVKM_GSP_RPC_REPLY_POLL, }; @@ -242,6 +246,8 @@ struct nvkm_gsp { /* The size of the registry RPC */ size_t registry_rpc_size; + u32 rpc_seq; + #ifdef CONFIG_DEBUG_FS /* * Logging buffers in debugfs. The wrapper objects need to remain diff --git a/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/rm/r535/gsp.c b/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/rm/r535/gsp.c index af77a4cc8a1629..2f028a30e07db2 100644 --- a/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/rm/r535/gsp.c +++ b/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/rm/r535/gsp.c @@ -704,7 +704,7 @@ r535_gsp_rpc_set_registry(struct nvkm_gsp *gsp) build_registry(gsp, rpc); - return nvkm_gsp_rpc_wr(gsp, rpc, NVKM_GSP_RPC_REPLY_NOWAIT); + return nvkm_gsp_rpc_wr(gsp, rpc, NVKM_GSP_RPC_REPLY_NOSEQ); fail: clean_registry(gsp); @@ -921,7 +921,7 @@ r535_gsp_set_system_info(struct nvkm_gsp *gsp) info->pciConfigMirrorSize = device->pci->func->cfg.size; r535_gsp_acpi_info(gsp, &info->acpiMethodData); - return nvkm_gsp_rpc_wr(gsp, info, NVKM_GSP_RPC_REPLY_NOWAIT); + return nvkm_gsp_rpc_wr(gsp, info, NVKM_GSP_RPC_REPLY_NOSEQ); } static int diff --git a/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/rm/r535/rpc.c b/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/rm/r535/rpc.c index 0dc4782df8c0c1..3ca3de8f434082 100644 --- a/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/rm/r535/rpc.c +++ b/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/rm/r535/rpc.c @@ -557,6 +557,7 @@ r535_gsp_rpc_handle_reply(struct nvkm_gsp *gsp, u32 fn, switch (policy) { case NVKM_GSP_RPC_REPLY_NOWAIT: + case NVKM_GSP_RPC_REPLY_NOSEQ: break; case NVKM_GSP_RPC_REPLY_RECV: reply = r535_gsp_msg_recv(gsp, fn, gsp_rpc_len); @@ -588,6 +589,11 @@ r535_gsp_rpc_send(struct nvkm_gsp *gsp, void *payload, rpc->data, rpc->length - sizeof(*rpc), true); } + if (policy == NVKM_GSP_RPC_REPLY_NOSEQ) + rpc->sequence = 0; + else + rpc->sequence = gsp->rpc_seq++; + ret = r535_gsp_cmdq_push(gsp, rpc); if (ret) return ERR_PTR(ret); diff --git a/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/rm/r570/gsp.c b/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/rm/r570/gsp.c index 9d2fa4e66d5982..996941c668ba9e 100644 --- a/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/rm/r570/gsp.c +++ b/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/rm/r570/gsp.c @@ -176,7 +176,7 @@ r570_gsp_set_system_info(struct nvkm_gsp *gsp) info->bIsPrimary = video_is_primary_device(device->dev); info->bPreserveVideoMemoryAllocations = false; - return nvkm_gsp_rpc_wr(gsp, info, NVKM_GSP_RPC_REPLY_NOWAIT); + return nvkm_gsp_rpc_wr(gsp, info, NVKM_GSP_RPC_REPLY_NOSEQ); } static void From ef763b480a3dea61b0080eb9603993312af70820 Mon Sep 17 00:00:00 2001 From: Dave Airlie Date: Tue, 3 Feb 2026 15:21:13 +1000 Subject: [PATCH 3005/3902] nouveau/gsp: fix suspend/resume regression on r570 firmware commit 8302d0afeaec0bc57d951dd085e0cffe997d4d18 upstream. The r570 firmware with certain GPUs (at least RTX6000) needs this flag to reflect the suspend vs runtime PM state of the driver. This uses that info to set the correct flags to the firmware. This fixes a regression on RTX6000 and other GPUs since r570 firmware was enabled. Fixes: 53dac0623853 ("drm/nouveau/gsp: add support for 570.144") Cc: Reviewed-by: Lyude Paul Tested-by: Lyude Paul Signed-off-by: Dave Airlie Link: https://patch.msgid.link/20260203052431.2219998-4-airlied@gmail.com Signed-off-by: Greg Kroah-Hartman --- drivers/gpu/drm/nouveau/nvkm/subdev/gsp/rm/r535/fbsr.c | 2 +- drivers/gpu/drm/nouveau/nvkm/subdev/gsp/rm/r535/gsp.c | 2 +- drivers/gpu/drm/nouveau/nvkm/subdev/gsp/rm/r570/fbsr.c | 8 ++++---- drivers/gpu/drm/nouveau/nvkm/subdev/gsp/rm/rm.h | 2 +- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/rm/r535/fbsr.c b/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/rm/r535/fbsr.c index 150e22fde2ac20..e962d0e8f83777 100644 --- a/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/rm/r535/fbsr.c +++ b/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/rm/r535/fbsr.c @@ -208,7 +208,7 @@ r535_fbsr_resume(struct nvkm_gsp *gsp) } static int -r535_fbsr_suspend(struct nvkm_gsp *gsp) +r535_fbsr_suspend(struct nvkm_gsp *gsp, bool runtime) { struct nvkm_subdev *subdev = &gsp->subdev; struct nvkm_device *device = subdev->device; diff --git a/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/rm/r535/gsp.c b/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/rm/r535/gsp.c index 2f028a30e07db2..7fb13434c051de 100644 --- a/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/rm/r535/gsp.c +++ b/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/rm/r535/gsp.c @@ -1748,7 +1748,7 @@ r535_gsp_fini(struct nvkm_gsp *gsp, enum nvkm_suspend_state suspend) sr->sysmemAddrOfSuspendResumeData = gsp->sr.radix3.lvl0.addr; sr->sizeOfSuspendResumeData = len; - ret = rm->api->fbsr->suspend(gsp); + ret = rm->api->fbsr->suspend(gsp, suspend == NVKM_RUNTIME_SUSPEND); if (ret) { nvkm_gsp_mem_dtor(&gsp->sr.meta); nvkm_gsp_radix3_dtor(gsp, &gsp->sr.radix3); diff --git a/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/rm/r570/fbsr.c b/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/rm/r570/fbsr.c index 2945d5b4e57070..8ef8b4f6558830 100644 --- a/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/rm/r570/fbsr.c +++ b/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/rm/r570/fbsr.c @@ -62,7 +62,7 @@ r570_fbsr_resume(struct nvkm_gsp *gsp) } static int -r570_fbsr_init(struct nvkm_gsp *gsp, struct sg_table *sgt, u64 size) +r570_fbsr_init(struct nvkm_gsp *gsp, struct sg_table *sgt, u64 size, bool runtime) { NV2080_CTRL_INTERNAL_FBSR_INIT_PARAMS *ctrl; struct nvkm_gsp_object memlist; @@ -81,7 +81,7 @@ r570_fbsr_init(struct nvkm_gsp *gsp, struct sg_table *sgt, u64 size) ctrl->hClient = gsp->internal.client.object.handle; ctrl->hSysMem = memlist.handle; ctrl->sysmemAddrOfSuspendResumeData = gsp->sr.meta.addr; - ctrl->bEnteringGcoffState = 1; + ctrl->bEnteringGcoffState = runtime ? 1 : 0; ret = nvkm_gsp_rm_ctrl_wr(&gsp->internal.device.subdevice, ctrl); if (ret) @@ -92,7 +92,7 @@ r570_fbsr_init(struct nvkm_gsp *gsp, struct sg_table *sgt, u64 size) } static int -r570_fbsr_suspend(struct nvkm_gsp *gsp) +r570_fbsr_suspend(struct nvkm_gsp *gsp, bool runtime) { struct nvkm_subdev *subdev = &gsp->subdev; struct nvkm_device *device = subdev->device; @@ -133,7 +133,7 @@ r570_fbsr_suspend(struct nvkm_gsp *gsp) return ret; /* Initialise FBSR on RM. */ - ret = r570_fbsr_init(gsp, &gsp->sr.fbsr, size); + ret = r570_fbsr_init(gsp, &gsp->sr.fbsr, size, runtime); if (ret) { nvkm_gsp_sg_free(device, &gsp->sr.fbsr); return ret; diff --git a/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/rm/rm.h b/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/rm/rm.h index 393ea775941fae..4f0ae6cc085c3d 100644 --- a/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/rm/rm.h +++ b/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/rm/rm.h @@ -78,7 +78,7 @@ struct nvkm_rm_api { } *device; const struct nvkm_rm_api_fbsr { - int (*suspend)(struct nvkm_gsp *); + int (*suspend)(struct nvkm_gsp *, bool runtime); void (*resume)(struct nvkm_gsp *); } *fbsr; From 488009aa62bb1217ea0624fd5108b79adef4e148 Mon Sep 17 00:00:00 2001 From: Kevin Hao Date: Tue, 3 Feb 2026 10:18:31 +0800 Subject: [PATCH 3006/3902] net: cpsw: Execute ndo_set_rx_mode callback in a work queue commit 0b8c878d117319f2be34c8391a77e0f4d5c94d79 upstream. Commit 1767bb2d47b7 ("ipv6: mcast: Don't hold RTNL for IPV6_ADD_MEMBERSHIP and MCAST_JOIN_GROUP.") removed the RTNL lock for IPV6_ADD_MEMBERSHIP and MCAST_JOIN_GROUP operations. However, this change triggered the following call trace on my BeagleBone Black board: WARNING: net/8021q/vlan_core.c:236 at vlan_for_each+0x120/0x124, CPU#0: rpcbind/481 RTNL: assertion failed at net/8021q/vlan_core.c (236) Modules linked in: CPU: 0 UID: 997 PID: 481 Comm: rpcbind Not tainted 6.19.0-rc7-next-20260130-yocto-standard+ #35 PREEMPT Hardware name: Generic AM33XX (Flattened Device Tree) Call trace: unwind_backtrace from show_stack+0x28/0x2c show_stack from dump_stack_lvl+0x30/0x38 dump_stack_lvl from __warn+0xb8/0x11c __warn from warn_slowpath_fmt+0x130/0x194 warn_slowpath_fmt from vlan_for_each+0x120/0x124 vlan_for_each from cpsw_add_mc_addr+0x54/0x98 cpsw_add_mc_addr from __hw_addr_ref_sync_dev+0xc4/0xec __hw_addr_ref_sync_dev from __dev_mc_add+0x78/0x88 __dev_mc_add from igmp6_group_added+0x84/0xec igmp6_group_added from __ipv6_dev_mc_inc+0x1fc/0x2f0 __ipv6_dev_mc_inc from __ipv6_sock_mc_join+0x124/0x1b4 __ipv6_sock_mc_join from do_ipv6_setsockopt+0x84c/0x1168 do_ipv6_setsockopt from ipv6_setsockopt+0x88/0xc8 ipv6_setsockopt from do_sock_setsockopt+0xe8/0x19c do_sock_setsockopt from __sys_setsockopt+0x84/0xac __sys_setsockopt from ret_fast_syscall+0x0/0x54 This trace occurs because vlan_for_each() is called within cpsw_ndo_set_rx_mode(), which expects the RTNL lock to be held. Since modifying vlan_for_each() to operate without the RTNL lock is not straightforward, and because ndo_set_rx_mode() is invoked both with and without the RTNL lock across different code paths, simply adding rtnl_lock() in cpsw_ndo_set_rx_mode() is not a viable solution. To resolve this issue, we opt to execute the actual processing within a work queue, following the approach used by the icssg-prueth driver. Please note: To reproduce this issue, I manually reverted the changes to am335x-bone-common.dtsi from commit c477358e66a3 ("ARM: dts: am335x-bone: switch to new cpsw switch drv") in order to revert to the legacy cpsw driver. Fixes: 1767bb2d47b7 ("ipv6: mcast: Don't hold RTNL for IPV6_ADD_MEMBERSHIP and MCAST_JOIN_GROUP.") Signed-off-by: Kevin Hao Cc: stable@vger.kernel.org Link: https://patch.msgid.link/20260203-bbb-v5-2-ea0ea217a85c@gmail.com Signed-off-by: Jakub Kicinski Signed-off-by: Greg Kroah-Hartman --- drivers/net/ethernet/ti/cpsw.c | 41 +++++++++++++++++++++++++++++----- 1 file changed, 35 insertions(+), 6 deletions(-) diff --git a/drivers/net/ethernet/ti/cpsw.c b/drivers/net/ethernet/ti/cpsw.c index 54c24cd3d3be63..b0e18bdc2c8510 100644 --- a/drivers/net/ethernet/ti/cpsw.c +++ b/drivers/net/ethernet/ti/cpsw.c @@ -305,12 +305,19 @@ static int cpsw_purge_all_mc(struct net_device *ndev, const u8 *addr, int num) return 0; } -static void cpsw_ndo_set_rx_mode(struct net_device *ndev) +static void cpsw_ndo_set_rx_mode_work(struct work_struct *work) { - struct cpsw_priv *priv = netdev_priv(ndev); + struct cpsw_priv *priv = container_of(work, struct cpsw_priv, rx_mode_work); struct cpsw_common *cpsw = priv->cpsw; + struct net_device *ndev = priv->ndev; int slave_port = -1; + rtnl_lock(); + if (!netif_running(ndev)) + goto unlock_rtnl; + + netif_addr_lock_bh(ndev); + if (cpsw->data.dual_emac) slave_port = priv->emac_port + 1; @@ -318,7 +325,7 @@ static void cpsw_ndo_set_rx_mode(struct net_device *ndev) /* Enable promiscuous mode */ cpsw_set_promiscious(ndev, true); cpsw_ale_set_allmulti(cpsw->ale, IFF_ALLMULTI, slave_port); - return; + goto unlock_addr; } else { /* Disable promiscuous mode */ cpsw_set_promiscious(ndev, false); @@ -331,6 +338,18 @@ static void cpsw_ndo_set_rx_mode(struct net_device *ndev) /* add/remove mcast address either for real netdev or for vlan */ __hw_addr_ref_sync_dev(&ndev->mc, ndev, cpsw_add_mc_addr, cpsw_del_mc_addr); + +unlock_addr: + netif_addr_unlock_bh(ndev); +unlock_rtnl: + rtnl_unlock(); +} + +static void cpsw_ndo_set_rx_mode(struct net_device *ndev) +{ + struct cpsw_priv *priv = netdev_priv(ndev); + + schedule_work(&priv->rx_mode_work); } static unsigned int cpsw_rxbuf_total_len(unsigned int len) @@ -1472,6 +1491,7 @@ static int cpsw_probe_dual_emac(struct cpsw_priv *priv) priv_sl2->ndev = ndev; priv_sl2->dev = &ndev->dev; priv_sl2->msg_enable = netif_msg_init(debug_level, CPSW_DEBUG); + INIT_WORK(&priv_sl2->rx_mode_work, cpsw_ndo_set_rx_mode_work); if (is_valid_ether_addr(data->slave_data[1].mac_addr)) { memcpy(priv_sl2->mac_addr, data->slave_data[1].mac_addr, @@ -1653,6 +1673,7 @@ static int cpsw_probe(struct platform_device *pdev) priv->dev = dev; priv->msg_enable = netif_msg_init(debug_level, CPSW_DEBUG); priv->emac_port = 0; + INIT_WORK(&priv->rx_mode_work, cpsw_ndo_set_rx_mode_work); if (is_valid_ether_addr(data->slave_data[0].mac_addr)) { memcpy(priv->mac_addr, data->slave_data[0].mac_addr, ETH_ALEN); @@ -1758,6 +1779,8 @@ static int cpsw_probe(struct platform_device *pdev) static void cpsw_remove(struct platform_device *pdev) { struct cpsw_common *cpsw = platform_get_drvdata(pdev); + struct net_device *ndev; + struct cpsw_priv *priv; int i, ret; ret = pm_runtime_resume_and_get(&pdev->dev); @@ -1770,9 +1793,15 @@ static void cpsw_remove(struct platform_device *pdev) return; } - for (i = 0; i < cpsw->data.slaves; i++) - if (cpsw->slaves[i].ndev) - unregister_netdev(cpsw->slaves[i].ndev); + for (i = 0; i < cpsw->data.slaves; i++) { + ndev = cpsw->slaves[i].ndev; + if (!ndev) + continue; + + priv = netdev_priv(ndev); + unregister_netdev(ndev); + disable_work_sync(&priv->rx_mode_work); + } cpts_release(cpsw->cpts); cpdma_ctlr_destroy(cpsw->dma); From d5b3a669866977dc87fd56fcf00a70df1536d258 Mon Sep 17 00:00:00 2001 From: Kevin Hao Date: Tue, 3 Feb 2026 10:18:30 +0800 Subject: [PATCH 3007/3902] net: cpsw_new: Execute ndo_set_rx_mode callback in a work queue commit c0b5dc73a38f954e780f93a549b8fe225235c07a upstream. Commit 1767bb2d47b7 ("ipv6: mcast: Don't hold RTNL for IPV6_ADD_MEMBERSHIP and MCAST_JOIN_GROUP.") removed the RTNL lock for IPV6_ADD_MEMBERSHIP and MCAST_JOIN_GROUP operations. However, this change triggered the following call trace on my BeagleBone Black board: WARNING: net/8021q/vlan_core.c:236 at vlan_for_each+0x120/0x124, CPU#0: rpcbind/496 RTNL: assertion failed at net/8021q/vlan_core.c (236) Modules linked in: CPU: 0 UID: 997 PID: 496 Comm: rpcbind Not tainted 6.19.0-rc6-next-20260122-yocto-standard+ #8 PREEMPT Hardware name: Generic AM33XX (Flattened Device Tree) Call trace: unwind_backtrace from show_stack+0x28/0x2c show_stack from dump_stack_lvl+0x30/0x38 dump_stack_lvl from __warn+0xb8/0x11c __warn from warn_slowpath_fmt+0x130/0x194 warn_slowpath_fmt from vlan_for_each+0x120/0x124 vlan_for_each from cpsw_add_mc_addr+0x54/0xd8 cpsw_add_mc_addr from __hw_addr_ref_sync_dev+0xc4/0xec __hw_addr_ref_sync_dev from __dev_mc_add+0x78/0x88 __dev_mc_add from igmp6_group_added+0x84/0xec igmp6_group_added from __ipv6_dev_mc_inc+0x1fc/0x2f0 __ipv6_dev_mc_inc from __ipv6_sock_mc_join+0x124/0x1b4 __ipv6_sock_mc_join from do_ipv6_setsockopt+0x84c/0x1168 do_ipv6_setsockopt from ipv6_setsockopt+0x88/0xc8 ipv6_setsockopt from do_sock_setsockopt+0xe8/0x19c do_sock_setsockopt from __sys_setsockopt+0x84/0xac __sys_setsockopt from ret_fast_syscall+0x0/0x5 This trace occurs because vlan_for_each() is called within cpsw_ndo_set_rx_mode(), which expects the RTNL lock to be held. Since modifying vlan_for_each() to operate without the RTNL lock is not straightforward, and because ndo_set_rx_mode() is invoked both with and without the RTNL lock across different code paths, simply adding rtnl_lock() in cpsw_ndo_set_rx_mode() is not a viable solution. To resolve this issue, we opt to execute the actual processing within a work queue, following the approach used by the icssg-prueth driver. Fixes: 1767bb2d47b7 ("ipv6: mcast: Don't hold RTNL for IPV6_ADD_MEMBERSHIP and MCAST_JOIN_GROUP.") Signed-off-by: Kevin Hao Cc: stable@vger.kernel.org Link: https://patch.msgid.link/20260203-bbb-v5-1-ea0ea217a85c@gmail.com Signed-off-by: Jakub Kicinski Signed-off-by: Greg Kroah-Hartman --- drivers/net/ethernet/ti/cpsw_new.c | 34 ++++++++++++++++++++++++----- drivers/net/ethernet/ti/cpsw_priv.h | 1 + 2 files changed, 30 insertions(+), 5 deletions(-) diff --git a/drivers/net/ethernet/ti/cpsw_new.c b/drivers/net/ethernet/ti/cpsw_new.c index 8b9e2078c6025a..371a099ac4e636 100644 --- a/drivers/net/ethernet/ti/cpsw_new.c +++ b/drivers/net/ethernet/ti/cpsw_new.c @@ -248,16 +248,22 @@ static int cpsw_purge_all_mc(struct net_device *ndev, const u8 *addr, int num) return 0; } -static void cpsw_ndo_set_rx_mode(struct net_device *ndev) +static void cpsw_ndo_set_rx_mode_work(struct work_struct *work) { - struct cpsw_priv *priv = netdev_priv(ndev); + struct cpsw_priv *priv = container_of(work, struct cpsw_priv, rx_mode_work); struct cpsw_common *cpsw = priv->cpsw; + struct net_device *ndev = priv->ndev; + rtnl_lock(); + if (!netif_running(ndev)) + goto unlock_rtnl; + + netif_addr_lock_bh(ndev); if (ndev->flags & IFF_PROMISC) { /* Enable promiscuous mode */ cpsw_set_promiscious(ndev, true); cpsw_ale_set_allmulti(cpsw->ale, IFF_ALLMULTI, priv->emac_port); - return; + goto unlock_addr; } /* Disable promiscuous mode */ @@ -270,6 +276,18 @@ static void cpsw_ndo_set_rx_mode(struct net_device *ndev) /* add/remove mcast address either for real netdev or for vlan */ __hw_addr_ref_sync_dev(&ndev->mc, ndev, cpsw_add_mc_addr, cpsw_del_mc_addr); + +unlock_addr: + netif_addr_unlock_bh(ndev); +unlock_rtnl: + rtnl_unlock(); +} + +static void cpsw_ndo_set_rx_mode(struct net_device *ndev) +{ + struct cpsw_priv *priv = netdev_priv(ndev); + + schedule_work(&priv->rx_mode_work); } static unsigned int cpsw_rxbuf_total_len(unsigned int len) @@ -1398,6 +1416,7 @@ static int cpsw_create_ports(struct cpsw_common *cpsw) priv->msg_enable = netif_msg_init(debug_level, CPSW_DEBUG); priv->emac_port = i + 1; priv->tx_packet_min = CPSW_MIN_PACKET_SIZE; + INIT_WORK(&priv->rx_mode_work, cpsw_ndo_set_rx_mode_work); if (is_valid_ether_addr(slave_data->mac_addr)) { ether_addr_copy(priv->mac_addr, slave_data->mac_addr); @@ -1447,13 +1466,18 @@ static int cpsw_create_ports(struct cpsw_common *cpsw) static void cpsw_unregister_ports(struct cpsw_common *cpsw) { + struct net_device *ndev; + struct cpsw_priv *priv; int i = 0; for (i = 0; i < cpsw->data.slaves; i++) { - if (!cpsw->slaves[i].ndev) + ndev = cpsw->slaves[i].ndev; + if (!ndev) continue; - unregister_netdev(cpsw->slaves[i].ndev); + priv = netdev_priv(ndev); + unregister_netdev(ndev); + disable_work_sync(&priv->rx_mode_work); } } diff --git a/drivers/net/ethernet/ti/cpsw_priv.h b/drivers/net/ethernet/ti/cpsw_priv.h index 91add8925e235c..acb6181c5c9e1b 100644 --- a/drivers/net/ethernet/ti/cpsw_priv.h +++ b/drivers/net/ethernet/ti/cpsw_priv.h @@ -391,6 +391,7 @@ struct cpsw_priv { u32 tx_packet_min; struct cpsw_ale_ratelimit ale_bc_ratelimit; struct cpsw_ale_ratelimit ale_mc_ratelimit; + struct work_struct rx_mode_work; }; #define ndev_to_cpsw(ndev) (((struct cpsw_priv *)netdev_priv(ndev))->cpsw) From a82647e1a9499a26fd2535edbf1700853c7e4159 Mon Sep 17 00:00:00 2001 From: Tomas Hlavacek Date: Fri, 30 Jan 2026 11:23:01 +0100 Subject: [PATCH 3008/3902] net: spacemit: k1-emac: fix jumbo frame support commit 3125fc17016945b11e9725c6aff30ff3326fd58f upstream. The driver never programs the MAC frame size and jabber registers, causing the hardware to reject frames larger than the default 1518 bytes even when larger DMA buffers are allocated. Program MAC_MAXIMUM_FRAME_SIZE, MAC_TRANSMIT_JABBER_SIZE, and MAC_RECEIVE_JABBER_SIZE based on the configured MTU. Also fix the maximum buffer size from 4096 to 4095, since the descriptor buffer size field is only 12 bits. Account for double VLAN tags in frame size calculations. Fixes: bfec6d7f2001 ("net: spacemit: Add K1 Ethernet MAC") Cc: stable@vger.kernel.org Signed-off-by: Tomas Hlavacek Link: https://patch.msgid.link/20260130102301.477514-1-tmshlvck@gmail.com Signed-off-by: Jakub Kicinski Signed-off-by: Greg Kroah-Hartman --- drivers/net/ethernet/spacemit/k1_emac.c | 21 +++++++++++++++------ 1 file changed, 15 insertions(+), 6 deletions(-) diff --git a/drivers/net/ethernet/spacemit/k1_emac.c b/drivers/net/ethernet/spacemit/k1_emac.c index 88e9424d2d51aa..b49c4708bf9eb1 100644 --- a/drivers/net/ethernet/spacemit/k1_emac.c +++ b/drivers/net/ethernet/spacemit/k1_emac.c @@ -12,6 +12,7 @@ #include #include #include +#include #include #include #include @@ -38,7 +39,7 @@ #define EMAC_DEFAULT_BUFSIZE 1536 #define EMAC_RX_BUF_2K 2048 -#define EMAC_RX_BUF_4K 4096 +#define EMAC_RX_BUF_MAX FIELD_MAX(RX_DESC_1_BUFFER_SIZE_1_MASK) /* Tuning parameters from SpacemiT */ #define EMAC_TX_FRAMES 64 @@ -202,8 +203,7 @@ static void emac_init_hw(struct emac_priv *priv) { /* Destination address for 802.3x Ethernet flow control */ u8 fc_dest_addr[ETH_ALEN] = { 0x01, 0x80, 0xc2, 0x00, 0x00, 0x01 }; - - u32 rxirq = 0, dma = 0; + u32 rxirq = 0, dma = 0, frame_sz; regmap_set_bits(priv->regmap_apmu, priv->regmap_apmu_offset + APMU_EMAC_CTRL_REG, @@ -228,6 +228,15 @@ static void emac_init_hw(struct emac_priv *priv) DEFAULT_TX_THRESHOLD); emac_wr(priv, MAC_RECEIVE_PACKET_START_THRESHOLD, DEFAULT_RX_THRESHOLD); + /* Set maximum frame size and jabber size based on configured MTU, + * accounting for Ethernet header, double VLAN tags, and FCS. + */ + frame_sz = priv->ndev->mtu + ETH_HLEN + 2 * VLAN_HLEN + ETH_FCS_LEN; + + emac_wr(priv, MAC_MAXIMUM_FRAME_SIZE, frame_sz); + emac_wr(priv, MAC_TRANSMIT_JABBER_SIZE, frame_sz); + emac_wr(priv, MAC_RECEIVE_JABBER_SIZE, frame_sz); + /* Configure flow control (enabled in emac_adjust_link() later) */ emac_set_mac_addr_reg(priv, fc_dest_addr, MAC_FC_SOURCE_ADDRESS_HIGH); emac_wr(priv, MAC_FC_PAUSE_HIGH_THRESHOLD, DEFAULT_FC_FIFO_HIGH); @@ -924,14 +933,14 @@ static int emac_change_mtu(struct net_device *ndev, int mtu) return -EBUSY; } - frame_len = mtu + ETH_HLEN + ETH_FCS_LEN; + frame_len = mtu + ETH_HLEN + 2 * VLAN_HLEN + ETH_FCS_LEN; if (frame_len <= EMAC_DEFAULT_BUFSIZE) priv->dma_buf_sz = EMAC_DEFAULT_BUFSIZE; else if (frame_len <= EMAC_RX_BUF_2K) priv->dma_buf_sz = EMAC_RX_BUF_2K; else - priv->dma_buf_sz = EMAC_RX_BUF_4K; + priv->dma_buf_sz = EMAC_RX_BUF_MAX; ndev->mtu = mtu; @@ -2025,7 +2034,7 @@ static int emac_probe(struct platform_device *pdev) ndev->hw_features = NETIF_F_SG; ndev->features |= ndev->hw_features; - ndev->max_mtu = EMAC_RX_BUF_4K - (ETH_HLEN + ETH_FCS_LEN); + ndev->max_mtu = EMAC_RX_BUF_MAX - (ETH_HLEN + 2 * VLAN_HLEN + ETH_FCS_LEN); ndev->pcpu_stat_type = NETDEV_PCPU_STAT_DSTATS; priv = netdev_priv(ndev); From 7a245ef476ffb4c27c4c7e31271ccd740ac7fcc7 Mon Sep 17 00:00:00 2001 From: Zhiquan Li Date: Thu, 22 Jan 2026 13:35:50 +0800 Subject: [PATCH 3009/3902] KVM: selftests: Add -U_FORTIFY_SOURCE to avoid some unpredictable test failures commit e396a74222654486d6ab45dca5d0c54c408b8b91 upstream. Some distributions (such as Ubuntu) configure GCC so that _FORTIFY_SOURCE is automatically enabled at -O1 or above. This results in some fortified version of definitions of standard library functions are included. While linker resolves the symbols, the fortified versions might override the definitions in lib/string_override.c and reference to those PLT entries in GLIBC. This is not a problem for the code in host, but it is a disaster for the guest code. E.g., if build and run x86/nested_emulation_test on Ubuntu 24.04 will encounter a L1 #PF due to memset() reference to __memset_chk@plt. The option -fno-builtin-memset is not helpful here, because those fortified versions are not built-in but some definitions which are included by header, they are for different intentions. In order to eliminate the unpredictable behaviors may vary depending on the linker and platform, add the "-U_FORTIFY_SOURCE" into CFLAGS to prevent from introducing the fortified definitions. Signed-off-by: Zhiquan Li Link: https://patch.msgid.link/20260122053551.548229-1-zhiquan_li@163.com Fixes: 6b6f71484bf4 ("KVM: selftests: Implement memcmp(), memcpy(), and memset() for guest use") Cc: stable@vger.kernel.org [sean: tag for stable] Signed-off-by: Sean Christopherson Signed-off-by: Greg Kroah-Hartman --- tools/testing/selftests/kvm/Makefile.kvm | 1 + 1 file changed, 1 insertion(+) diff --git a/tools/testing/selftests/kvm/Makefile.kvm b/tools/testing/selftests/kvm/Makefile.kvm index 148d427ff24bef..1715efe9d5b0d2 100644 --- a/tools/testing/selftests/kvm/Makefile.kvm +++ b/tools/testing/selftests/kvm/Makefile.kvm @@ -245,6 +245,7 @@ LINUX_TOOL_INCLUDE = $(top_srcdir)/tools/include LINUX_TOOL_ARCH_INCLUDE = $(top_srcdir)/tools/arch/$(ARCH)/include CFLAGS += -Wall -Wstrict-prototypes -Wuninitialized -O2 -g -std=gnu99 \ -Wno-gnu-variable-sized-type-not-at-end -MD -MP -DCONFIG_64BIT \ + -U_FORTIFY_SOURCE \ -fno-builtin-memcmp -fno-builtin-memcpy \ -fno-builtin-memset -fno-builtin-strnlen \ -fno-stack-protector -fno-PIE -fno-strict-aliasing \ From 4385b2f2843549bfb932e0dcf76bf4b065543a3c Mon Sep 17 00:00:00 2001 From: Sean Christopherson Date: Tue, 13 Jan 2026 09:46:05 -0800 Subject: [PATCH 3010/3902] KVM: Don't clobber irqfd routing type when deassigning irqfd commit b4d37cdb77a0015f51fee083598fa227cc07aaf1 upstream. When deassigning a KVM_IRQFD, don't clobber the irqfd's copy of the IRQ's routing entry as doing so breaks kvm_arch_irq_bypass_del_producer() on x86 and arm64, which explicitly look for KVM_IRQ_ROUTING_MSI. Instead, to handle a concurrent routing update, verify that the irqfd is still active before consuming the routing information. As evidenced by the x86 and arm64 bugs, and another bug in kvm_arch_update_irqfd_routing() (see below), clobbering the entry type without notifying arch code is surprising and error prone. As a bonus, checking that the irqfd is active provides a convenient location for documenting _why_ KVM must not consume the routing entry for an irqfd that is in the process of being deassigned: once the irqfd is deleted from the list (which happens *before* the eventfd is detached), it will no longer receive updates via kvm_irq_routing_update(), and so KVM could deliver an event using stale routing information (relative to KVM_SET_GSI_ROUTING returning to userspace). As an even better bonus, explicitly checking for the irqfd being active fixes a similar bug to the one the clobbering is trying to prevent: if an irqfd is deactivated, and then its routing is changed, kvm_irq_routing_update() won't invoke kvm_arch_update_irqfd_routing() (because the irqfd isn't in the list). And so if the irqfd is in bypass mode, IRQs will continue to be posted using the old routing information. As for kvm_arch_irq_bypass_del_producer(), clobbering the routing type results in KVM incorrectly keeping the IRQ in bypass mode, which is especially problematic on AMD as KVM tracks IRQs that are being posted to a vCPU in a list whose lifetime is tied to the irqfd. Without the help of KASAN to detect use-after-free, the most common sympton on AMD is a NULL pointer deref in amd_iommu_update_ga() due to the memory for irqfd structure being re-allocated and zeroed, resulting in irqfd->irq_bypass_data being NULL when read by avic_update_iommu_vcpu_affinity(): BUG: kernel NULL pointer dereference, address: 0000000000000018 #PF: supervisor read access in kernel mode #PF: error_code(0x0000) - not-present page PGD 40cf2b9067 P4D 40cf2b9067 PUD 408362a067 PMD 0 Oops: Oops: 0000 [#1] SMP CPU: 6 UID: 0 PID: 40383 Comm: vfio_irq_test Tainted: G U W O 6.19.0-smp--5dddc257e6b2-irqfd #31 NONE Tainted: [U]=USER, [W]=WARN, [O]=OOT_MODULE Hardware name: Google, Inc. Arcadia_IT_80/Arcadia_IT_80, BIOS 34.78.2-0 09/05/2025 RIP: 0010:amd_iommu_update_ga+0x19/0xe0 Call Trace: avic_update_iommu_vcpu_affinity+0x3d/0x90 [kvm_amd] __avic_vcpu_load+0xf4/0x130 [kvm_amd] kvm_arch_vcpu_load+0x89/0x210 [kvm] vcpu_load+0x30/0x40 [kvm] kvm_arch_vcpu_ioctl_run+0x45/0x620 [kvm] kvm_vcpu_ioctl+0x571/0x6a0 [kvm] __se_sys_ioctl+0x6d/0xb0 do_syscall_64+0x6f/0x9d0 entry_SYSCALL_64_after_hwframe+0x4b/0x53 RIP: 0033:0x46893b ---[ end trace 0000000000000000 ]--- If AVIC is inhibited when the irfd is deassigned, the bug will manifest as list corruption, e.g. on the next irqfd assignment. list_add corruption. next->prev should be prev (ffff8d474d5cd588), but was 0000000000000000. (next=ffff8d8658f86530). ------------[ cut here ]------------ kernel BUG at lib/list_debug.c:31! Oops: invalid opcode: 0000 [#1] SMP CPU: 128 UID: 0 PID: 80818 Comm: vfio_irq_test Tainted: G U W O 6.19.0-smp--f19dc4d680ba-irqfd #28 NONE Tainted: [U]=USER, [W]=WARN, [O]=OOT_MODULE Hardware name: Google, Inc. Arcadia_IT_80/Arcadia_IT_80, BIOS 34.78.2-0 09/05/2025 RIP: 0010:__list_add_valid_or_report+0x97/0xc0 Call Trace: avic_pi_update_irte+0x28e/0x2b0 [kvm_amd] kvm_pi_update_irte+0xbf/0x190 [kvm] kvm_arch_irq_bypass_add_producer+0x72/0x90 [kvm] irq_bypass_register_consumer+0xcd/0x170 [irqbypass] kvm_irqfd+0x4c6/0x540 [kvm] kvm_vm_ioctl+0x118/0x5d0 [kvm] __se_sys_ioctl+0x6d/0xb0 do_syscall_64+0x6f/0x9d0 entry_SYSCALL_64_after_hwframe+0x4b/0x53 ---[ end trace 0000000000000000 ]--- On Intel and arm64, the bug is less noisy, as the end result is that the device keeps posting IRQs to the vCPU even after it's been deassigned. Note, the worst of the breakage can be traced back to commit cb210737675e ("KVM: Pass new routing entries and irqfd when updating IRTEs"), as before that commit KVM would pull the routing information from the per-VM routing table. But as above, similar bugs have existed since support for IRQ bypass was added. E.g. if a routing change finished before irq_shutdown() invoked kvm_arch_irq_bypass_del_producer(), VMX and SVM would see stale routing information and potentially leave the irqfd in bypass mode. Alternatively, x86 could be fixed by explicitly checking irq_bypass_vcpu instead of irq_entry.type in kvm_arch_irq_bypass_del_producer(), and arm64 could be modified to utilize irq_bypass_vcpu in a similar manner. But (a) that wouldn't fix the routing updates bug, and (b) fixing core code doesn't preclude x86 (or arm64) from adding such code as a sanity check (spoiler alert). Fixes: f70c20aaf141 ("KVM: Add an arch specific hooks in 'struct kvm_kernel_irqfd'") Fixes: cb210737675e ("KVM: Pass new routing entries and irqfd when updating IRTEs") Fixes: a0d7e2fc61ab ("KVM: arm64: vgic-v4: Only attempt vLPI mapping for actual MSIs") Cc: stable@vger.kernel.org Cc: Marc Zyngier Cc: Oliver Upton Link: https://patch.msgid.link/20260113174606.104978-2-seanjc@google.com Signed-off-by: Sean Christopherson Signed-off-by: Greg Kroah-Hartman --- virt/kvm/eventfd.c | 44 ++++++++++++++++++++++++-------------------- 1 file changed, 24 insertions(+), 20 deletions(-) diff --git a/virt/kvm/eventfd.c b/virt/kvm/eventfd.c index a7794ffdb97619..1a64266341b193 100644 --- a/virt/kvm/eventfd.c +++ b/virt/kvm/eventfd.c @@ -157,21 +157,28 @@ irqfd_shutdown(struct work_struct *work) } -/* assumes kvm->irqfds.lock is held */ -static bool -irqfd_is_active(struct kvm_kernel_irqfd *irqfd) +static bool irqfd_is_active(struct kvm_kernel_irqfd *irqfd) { + /* + * Assert that either irqfds.lock or SRCU is held, as irqfds.lock must + * be held to prevent false positives (on the irqfd being active), and + * while false negatives are impossible as irqfds are never added back + * to the list once they're deactivated, the caller must at least hold + * SRCU to guard against routing changes if the irqfd is deactivated. + */ + lockdep_assert_once(lockdep_is_held(&irqfd->kvm->irqfds.lock) || + srcu_read_lock_held(&irqfd->kvm->irq_srcu)); + return list_empty(&irqfd->list) ? false : true; } /* * Mark the irqfd as inactive and schedule it for removal - * - * assumes kvm->irqfds.lock is held */ -static void -irqfd_deactivate(struct kvm_kernel_irqfd *irqfd) +static void irqfd_deactivate(struct kvm_kernel_irqfd *irqfd) { + lockdep_assert_held(&irqfd->kvm->irqfds.lock); + BUG_ON(!irqfd_is_active(irqfd)); list_del_init(&irqfd->list); @@ -217,8 +224,15 @@ irqfd_wakeup(wait_queue_entry_t *wait, unsigned mode, int sync, void *key) seq = read_seqcount_begin(&irqfd->irq_entry_sc); irq = irqfd->irq_entry; } while (read_seqcount_retry(&irqfd->irq_entry_sc, seq)); - /* An event has been signaled, inject an interrupt */ - if (kvm_arch_set_irq_inatomic(&irq, kvm, + + /* + * An event has been signaled, inject an interrupt unless the + * irqfd is being deassigned (isn't active), in which case the + * routing information may be stale (once the irqfd is removed + * from the list, it will stop receiving routing updates). + */ + if (unlikely(!irqfd_is_active(irqfd)) || + kvm_arch_set_irq_inatomic(&irq, kvm, KVM_USERSPACE_IRQ_SOURCE_ID, 1, false) == -EWOULDBLOCK) schedule_work(&irqfd->inject); @@ -585,18 +599,8 @@ kvm_irqfd_deassign(struct kvm *kvm, struct kvm_irqfd *args) spin_lock_irq(&kvm->irqfds.lock); list_for_each_entry_safe(irqfd, tmp, &kvm->irqfds.items, list) { - if (irqfd->eventfd == eventfd && irqfd->gsi == args->gsi) { - /* - * This clearing of irq_entry.type is needed for when - * another thread calls kvm_irq_routing_update before - * we flush workqueue below (we synchronize with - * kvm_irq_routing_update using irqfds.lock). - */ - write_seqcount_begin(&irqfd->irq_entry_sc); - irqfd->irq_entry.type = 0; - write_seqcount_end(&irqfd->irq_entry_sc); + if (irqfd->eventfd == eventfd && irqfd->gsi == args->gsi) irqfd_deactivate(irqfd); - } } spin_unlock_irq(&kvm->irqfds.lock); From 37751b6d0b6b5cd56bea83e17c7d2e5607f0c897 Mon Sep 17 00:00:00 2001 From: Gabor Juhos Date: Sun, 1 Feb 2026 21:35:06 +0100 Subject: [PATCH 3011/3902] hwmon: (gpio-fan) Fix set_rpm() return value commit f5c092787c48296633c2dd7240752f88fa9710fc upstream. The set_rpm function is used as a 'store' callback of a device attribute, and as such it should return with the number of bytes consumed. However since commit 0d01110e6356 ("hwmon: (gpio-fan) Add regulator support"), the function returns with zero on success. Due to this, the function gets called again and again whenever the user tries to change the FAN speed by writing the desired RPM value into the 'fan1_target' sysfs attribute. The broken behaviour can be reproduced easily. For example, the following command never returns unless it gets terminated: $ echo 500 > /sys/class/hwmon/hwmon1/fan1_target ^C $ Change the code to return with the same value as the 'count' parameter on success to indicate that all bytes from the input buffer are consumed. The function behaved the same way prior to the offending change. Cc: stable@vger.kernel.org Fixes: 0d01110e6356 ("hwmon: (gpio-fan) Add regulator support") Signed-off-by: Gabor Juhos Link: https://lore.kernel.org/r/20260201-gpio-fan-set_rpm-retval-fix-v1-1-dc39bc7693ca@gmail.com Signed-off-by: Guenter Roeck Signed-off-by: Greg Kroah-Hartman --- drivers/hwmon/gpio-fan.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/hwmon/gpio-fan.c b/drivers/hwmon/gpio-fan.c index 516c34bb61c9cf..d7fa021f376e39 100644 --- a/drivers/hwmon/gpio-fan.c +++ b/drivers/hwmon/gpio-fan.c @@ -291,7 +291,7 @@ static ssize_t set_rpm(struct device *dev, struct device_attribute *attr, { struct gpio_fan_data *fan_data = dev_get_drvdata(dev); unsigned long rpm; - int ret = count; + int ret; if (kstrtoul(buf, 10, &rpm)) return -EINVAL; @@ -308,7 +308,7 @@ static ssize_t set_rpm(struct device *dev, struct device_attribute *attr, exit_unlock: mutex_unlock(&fan_data->lock); - return ret; + return ret ? ret : count; } static DEVICE_ATTR_RW(pwm1); From 1a893bd7191217fb8cf346896d05fbb1cd933264 Mon Sep 17 00:00:00 2001 From: Gabor Juhos Date: Mon, 2 Feb 2026 16:58:57 +0100 Subject: [PATCH 3012/3902] hwmon: (gpio-fan) Allow to stop FANs when CONFIG_PM is disabled commit 52fb36a5f9c15285b7d67c0ff87dc17b3206b5df upstream. When CONFIG_PM is disabled, the GPIO controlled FANs can't be stopped by using the sysfs attributes since commit 0d01110e6356 ("hwmon: (gpio-fan) Add regulator support"). Using either the 'pwm1' or the 'fan1_target' attribute fails the same way: $ echo 0 > /sys/class/hwmon/hwmon1/pwm1 ash: write error: Function not implemented $ echo 0 > /sys/class/hwmon/hwmon1/fan1_target ash: write error: Function not implemented Both commands were working flawlessly before the mentioned commit. The issue happens because pm_runtime_put_sync() returns with -ENOSYS when CONFIG_PM is disabled, and the set_fan_speed() function handles this as an error. In order to restore the previous behaviour, change the error check in the set_fan_speed() function to ignore the -ENOSYS error code. Cc: stable@vger.kernel.org Fixes: 0d01110e6356 ("hwmon: (gpio-fan) Add regulator support") Signed-off-by: Gabor Juhos Link: https://lore.kernel.org/r/20260202-gpio-fan-stop-fix-v1-1-c7853183d93d@gmail.com Signed-off-by: Guenter Roeck Signed-off-by: Greg Kroah-Hartman --- drivers/hwmon/gpio-fan.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/hwmon/gpio-fan.c b/drivers/hwmon/gpio-fan.c index d7fa021f376e39..a8892ced1e549f 100644 --- a/drivers/hwmon/gpio-fan.c +++ b/drivers/hwmon/gpio-fan.c @@ -148,7 +148,7 @@ static int set_fan_speed(struct gpio_fan_data *fan_data, int speed_index) int ret; ret = pm_runtime_put_sync(fan_data->dev); - if (ret < 0) + if (ret < 0 && ret != -ENOSYS) return ret; } From 71c50e60421bbe5ec59566499b959de71dd215ed Mon Sep 17 00:00:00 2001 From: Lukas Wunner Date: Wed, 19 Nov 2025 09:50:03 +0100 Subject: [PATCH 3013/3902] PCI/ERR: Ensure error recoverability at all times commit a2f1e22390ac2ca7ac8d77aa0f78c068b6dd2208 upstream. When the PCI core gained power management support in 2002, it introduced pci_save_state() and pci_restore_state() helpers to restore Config Space after a D3hot or D3cold transition, which implies a Soft or Fundamental Reset (PCIe r7.0 sec 5.8): https://git.kernel.org/tglx/history/c/a5287abe398b In 2006, EEH and AER were introduced to recover from errors by performing a reset. Because errors can occur at any time, drivers began calling pci_save_state() on probe to ensure recoverability. In 2009, recoverability was foiled by commit c82f63e411f1 ("PCI: check saved state before restore"): It amended pci_restore_state() to bail out if the "state_saved" flag has been cleared. The flag is cleared by pci_restore_state() itself, hence a saved state is now allowed to be restored only once and is then invalidated. That doesn't seem to make sense because the saved state should be good enough to be reused. Soon after, drivers began to work around this behavior by calling pci_save_state() immediately after pci_restore_state(), see e.g. commit b94f2d775a71 ("igb: call pci_save_state after pci_restore_state"). Hilariously, two drivers even set the "saved_state" flag to true before invoking pci_restore_state(), see ipr_reset_restore_cfg_space() and e1000_io_slot_reset(). Despite these workarounds, recoverability at all times is not guaranteed: E.g. when a PCIe port goes through a runtime suspend and resume cycle, the "saved_state" flag is cleared by: pci_pm_runtime_resume() pci_pm_default_resume_early() pci_restore_state() ... and hence on a subsequent AER event, the port's Config Space cannot be restored. Riana reports a recovery failure of a GPU-integrated PCIe switch and has root-caused it to the behavior of pci_restore_state(). Another workaround would be necessary, namely calling pci_save_state() in pcie_port_device_runtime_resume(). The motivation of commit c82f63e411f1 was to prevent restoring state if pci_save_state() hasn't been called before. But that can be achieved by saving state already on device addition, after Config Space has been initialized. A desirable side effect is that devices become recoverable even if no driver gets bound. This renders the commit unnecessary, so revert it. Reported-by: Riana Tauro # off-list Signed-off-by: Lukas Wunner Signed-off-by: Bjorn Helgaas Tested-by: Riana Tauro Reviewed-by: Rafael J. Wysocki (Intel) Link: https://patch.msgid.link/9e34ce61c5404e99ffdd29205122c6fb334b38aa.1763483367.git.lukas@wunner.de Cc: Mario Limonciello Signed-off-by: Greg Kroah-Hartman --- drivers/pci/bus.c | 3 +++ drivers/pci/pci.c | 3 --- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/drivers/pci/bus.c b/drivers/pci/bus.c index f26aec6ff58899..9daf13ed3714e0 100644 --- a/drivers/pci/bus.c +++ b/drivers/pci/bus.c @@ -357,6 +357,9 @@ void pci_bus_add_device(struct pci_dev *dev) pci_proc_attach_device(dev); pci_bridge_d3_update(dev); + /* Save config space for error recoverability */ + pci_save_state(dev); + /* * If the PCI device is associated with a pwrctrl device with a * power supply, create a device link between the PCI device and diff --git a/drivers/pci/pci.c b/drivers/pci/pci.c index b14dd064006cca..2f0da5dbbba409 100644 --- a/drivers/pci/pci.c +++ b/drivers/pci/pci.c @@ -1855,9 +1855,6 @@ static void pci_restore_rebar_state(struct pci_dev *pdev) */ void pci_restore_state(struct pci_dev *dev) { - if (!dev->state_saved) - return; - pci_restore_pcie_state(dev); pci_restore_pasid_state(dev); pci_restore_pri_state(dev); From fa2274bb17a4a71bb9ca7288d871f6908464207a Mon Sep 17 00:00:00 2001 From: Lukas Wunner Date: Sun, 12 Oct 2025 15:25:02 +0200 Subject: [PATCH 3014/3902] treewide: Drop pci_save_state() after pci_restore_state() commit 383d89699c5028de510a6667f674ed38585f77fc upstream. In 2009, commit c82f63e411f1 ("PCI: check saved state before restore") changed the behavior of pci_restore_state() such that it became necessary to call pci_save_state() afterwards, lest recovery from subsequent PCI errors fails. The commit has just been reverted and so all the pci_save_state() after pci_restore_state() calls that have accumulated in the tree are now superfluous. Drop them. Two drivers chose a different approach to achieve the same result: drivers/scsi/ipr.c and drivers/net/ethernet/intel/e1000e/netdev.c set the pci_dev's "state_saved" flag to true before calling pci_restore_state(). Drop this as well. Signed-off-by: Lukas Wunner Signed-off-by: Bjorn Helgaas Acked-by: Dave Jiang Acked-by: Giovanni Cabiddu # qat Link: https://patch.msgid.link/c2b28cc4defa1b743cf1dedee23c455be98b397a.1760274044.git.lukas@wunner.de Cc: Mario Limonciello Signed-off-by: Greg Kroah-Hartman --- drivers/crypto/intel/qat/qat_common/adf_aer.c | 2 -- drivers/dma/ioat/init.c | 1 - drivers/net/ethernet/broadcom/bnx2.c | 2 -- drivers/net/ethernet/broadcom/bnx2x/bnx2x_main.c | 1 - drivers/net/ethernet/broadcom/tg3.c | 1 - drivers/net/ethernet/chelsio/cxgb3/cxgb3_main.c | 1 - drivers/net/ethernet/chelsio/cxgb4/cxgb4_main.c | 2 -- drivers/net/ethernet/hisilicon/hibmcge/hbg_err.c | 1 - drivers/net/ethernet/intel/e1000e/netdev.c | 1 - drivers/net/ethernet/intel/fm10k/fm10k_pci.c | 6 ------ drivers/net/ethernet/intel/i40e/i40e_main.c | 1 - drivers/net/ethernet/intel/ice/ice_main.c | 2 -- drivers/net/ethernet/intel/igb/igb_main.c | 2 -- drivers/net/ethernet/intel/igc/igc_main.c | 2 -- drivers/net/ethernet/intel/ixgbe/ixgbe_main.c | 1 - drivers/net/ethernet/mellanox/mlx4/main.c | 1 - drivers/net/ethernet/mellanox/mlx5/core/main.c | 1 - drivers/net/ethernet/meta/fbnic/fbnic_pci.c | 1 - drivers/net/ethernet/microchip/lan743x_main.c | 1 - drivers/net/ethernet/myricom/myri10ge/myri10ge.c | 4 ---- drivers/net/ethernet/neterion/s2io.c | 1 - drivers/pci/pcie/portdrv.c | 1 - drivers/scsi/bfa/bfad.c | 1 - drivers/scsi/csiostor/csio_init.c | 1 - drivers/scsi/ipr.c | 1 - drivers/scsi/lpfc/lpfc_init.c | 6 ------ drivers/scsi/qla2xxx/qla_os.c | 5 ----- drivers/scsi/qla4xxx/ql4_os.c | 5 ----- drivers/tty/serial/8250/8250_pci.c | 1 - drivers/tty/serial/jsm/jsm_driver.c | 1 - 30 files changed, 57 deletions(-) diff --git a/drivers/crypto/intel/qat/qat_common/adf_aer.c b/drivers/crypto/intel/qat/qat_common/adf_aer.c index a098689ab5b75d..f63e78724c7a73 100644 --- a/drivers/crypto/intel/qat/qat_common/adf_aer.c +++ b/drivers/crypto/intel/qat/qat_common/adf_aer.c @@ -103,7 +103,6 @@ void adf_dev_restore(struct adf_accel_dev *accel_dev) accel_dev->accel_id); hw_device->reset_device(accel_dev); pci_restore_state(pdev); - pci_save_state(pdev); } } @@ -202,7 +201,6 @@ static pci_ers_result_t adf_slot_reset(struct pci_dev *pdev) if (!pdev->is_busmaster) pci_set_master(pdev); pci_restore_state(pdev); - pci_save_state(pdev); res = adf_dev_up(accel_dev, false); if (res && res != -EALREADY) return PCI_ERS_RESULT_DISCONNECT; diff --git a/drivers/dma/ioat/init.c b/drivers/dma/ioat/init.c index 02f68b32851121..227398673b731d 100644 --- a/drivers/dma/ioat/init.c +++ b/drivers/dma/ioat/init.c @@ -1286,7 +1286,6 @@ static pci_ers_result_t ioat_pcie_error_slot_reset(struct pci_dev *pdev) } else { pci_set_master(pdev); pci_restore_state(pdev); - pci_save_state(pdev); pci_wake_from_d3(pdev, false); } diff --git a/drivers/net/ethernet/broadcom/bnx2.c b/drivers/net/ethernet/broadcom/bnx2.c index cb1011f6fd3072..805daae9dd3621 100644 --- a/drivers/net/ethernet/broadcom/bnx2.c +++ b/drivers/net/ethernet/broadcom/bnx2.c @@ -6444,7 +6444,6 @@ bnx2_reset_task(struct work_struct *work) if (!(pcicmd & PCI_COMMAND_MEMORY)) { /* in case PCI block has reset */ pci_restore_state(bp->pdev); - pci_save_state(bp->pdev); } rc = bnx2_init_nic(bp, 1); if (rc) { @@ -8718,7 +8717,6 @@ static pci_ers_result_t bnx2_io_slot_reset(struct pci_dev *pdev) } else { pci_set_master(pdev); pci_restore_state(pdev); - pci_save_state(pdev); if (netif_running(dev)) err = bnx2_init_nic(bp, 1); diff --git a/drivers/net/ethernet/broadcom/bnx2x/bnx2x_main.c b/drivers/net/ethernet/broadcom/bnx2x/bnx2x_main.c index f0f05d7315ac48..8e6eec828d4821 100644 --- a/drivers/net/ethernet/broadcom/bnx2x/bnx2x_main.c +++ b/drivers/net/ethernet/broadcom/bnx2x/bnx2x_main.c @@ -14216,7 +14216,6 @@ static pci_ers_result_t bnx2x_io_slot_reset(struct pci_dev *pdev) pci_set_master(pdev); pci_restore_state(pdev); - pci_save_state(pdev); if (netif_running(dev)) bnx2x_set_power_state(bp, PCI_D0); diff --git a/drivers/net/ethernet/broadcom/tg3.c b/drivers/net/ethernet/broadcom/tg3.c index d78cafdb20949c..0397a6ebf20f98 100644 --- a/drivers/net/ethernet/broadcom/tg3.c +++ b/drivers/net/ethernet/broadcom/tg3.c @@ -18349,7 +18349,6 @@ static pci_ers_result_t tg3_io_slot_reset(struct pci_dev *pdev) pci_set_master(pdev); pci_restore_state(pdev); - pci_save_state(pdev); if (!netdev || !netif_running(netdev)) { rc = PCI_ERS_RESULT_RECOVERED; diff --git a/drivers/net/ethernet/chelsio/cxgb3/cxgb3_main.c b/drivers/net/ethernet/chelsio/cxgb3/cxgb3_main.c index f92a3550e48000..3b1321c8ed143a 100644 --- a/drivers/net/ethernet/chelsio/cxgb3/cxgb3_main.c +++ b/drivers/net/ethernet/chelsio/cxgb3/cxgb3_main.c @@ -2933,7 +2933,6 @@ static int t3_reenable_adapter(struct adapter *adapter) } pci_set_master(adapter->pdev); pci_restore_state(adapter->pdev); - pci_save_state(adapter->pdev); /* Free sge resources */ t3_free_sge_resources(adapter); diff --git a/drivers/net/ethernet/chelsio/cxgb4/cxgb4_main.c b/drivers/net/ethernet/chelsio/cxgb4/cxgb4_main.c index 392723ef14e512..1ce2091cdc01a4 100644 --- a/drivers/net/ethernet/chelsio/cxgb4/cxgb4_main.c +++ b/drivers/net/ethernet/chelsio/cxgb4/cxgb4_main.c @@ -5456,7 +5456,6 @@ static pci_ers_result_t eeh_slot_reset(struct pci_dev *pdev) if (!adap) { pci_restore_state(pdev); - pci_save_state(pdev); return PCI_ERS_RESULT_RECOVERED; } @@ -5471,7 +5470,6 @@ static pci_ers_result_t eeh_slot_reset(struct pci_dev *pdev) pci_set_master(pdev); pci_restore_state(pdev); - pci_save_state(pdev); if (t4_wait_dev_ready(adap->regs) < 0) return PCI_ERS_RESULT_DISCONNECT; diff --git a/drivers/net/ethernet/hisilicon/hibmcge/hbg_err.c b/drivers/net/ethernet/hisilicon/hibmcge/hbg_err.c index e11495b7ee98ca..7234618e8e8187 100644 --- a/drivers/net/ethernet/hisilicon/hibmcge/hbg_err.c +++ b/drivers/net/ethernet/hisilicon/hibmcge/hbg_err.c @@ -160,7 +160,6 @@ static pci_ers_result_t hbg_pci_err_slot_reset(struct pci_dev *pdev) pci_set_master(pdev); pci_restore_state(pdev); - pci_save_state(pdev); hbg_err_reset(priv); return PCI_ERS_RESULT_RECOVERED; diff --git a/drivers/net/ethernet/intel/e1000e/netdev.c b/drivers/net/ethernet/intel/e1000e/netdev.c index 201322dac23305..75896602e732cd 100644 --- a/drivers/net/ethernet/intel/e1000e/netdev.c +++ b/drivers/net/ethernet/intel/e1000e/netdev.c @@ -7195,7 +7195,6 @@ static pci_ers_result_t e1000_io_slot_reset(struct pci_dev *pdev) "Cannot re-enable PCI device after reset.\n"); result = PCI_ERS_RESULT_DISCONNECT; } else { - pdev->state_saved = true; pci_restore_state(pdev); pci_set_master(pdev); diff --git a/drivers/net/ethernet/intel/fm10k/fm10k_pci.c b/drivers/net/ethernet/intel/fm10k/fm10k_pci.c index ae5fe34659cfb1..d75b8a50413d2a 100644 --- a/drivers/net/ethernet/intel/fm10k/fm10k_pci.c +++ b/drivers/net/ethernet/intel/fm10k/fm10k_pci.c @@ -2423,12 +2423,6 @@ static pci_ers_result_t fm10k_io_slot_reset(struct pci_dev *pdev) } else { pci_set_master(pdev); pci_restore_state(pdev); - - /* After second error pci->state_saved is false, this - * resets it so EEH doesn't break. - */ - pci_save_state(pdev); - pci_wake_from_d3(pdev, false); result = PCI_ERS_RESULT_RECOVERED; diff --git a/drivers/net/ethernet/intel/i40e/i40e_main.c b/drivers/net/ethernet/intel/i40e/i40e_main.c index 07d32f2586c80f..0b1cc0481027ab 100644 --- a/drivers/net/ethernet/intel/i40e/i40e_main.c +++ b/drivers/net/ethernet/intel/i40e/i40e_main.c @@ -16456,7 +16456,6 @@ static pci_ers_result_t i40e_pci_error_slot_reset(struct pci_dev *pdev) } else { pci_set_master(pdev); pci_restore_state(pdev); - pci_save_state(pdev); pci_wake_from_d3(pdev, false); reg = rd32(&pf->hw, I40E_GLGEN_RTRIG); diff --git a/drivers/net/ethernet/intel/ice/ice_main.c b/drivers/net/ethernet/intel/ice/ice_main.c index b5ebfcdc9d434e..c52324d999eb4d 100644 --- a/drivers/net/ethernet/intel/ice/ice_main.c +++ b/drivers/net/ethernet/intel/ice/ice_main.c @@ -5661,7 +5661,6 @@ static int ice_resume(struct device *dev) pci_set_power_state(pdev, PCI_D0); pci_restore_state(pdev); - pci_save_state(pdev); if (!pci_device_is_present(pdev)) return -ENODEV; @@ -5761,7 +5760,6 @@ static pci_ers_result_t ice_pci_err_slot_reset(struct pci_dev *pdev) } else { pci_set_master(pdev); pci_restore_state(pdev); - pci_save_state(pdev); pci_wake_from_d3(pdev, false); /* Check for life */ diff --git a/drivers/net/ethernet/intel/igb/igb_main.c b/drivers/net/ethernet/intel/igb/igb_main.c index 85f9589cc568cf..dbea37269d2cdb 100644 --- a/drivers/net/ethernet/intel/igb/igb_main.c +++ b/drivers/net/ethernet/intel/igb/igb_main.c @@ -9599,7 +9599,6 @@ static int __igb_resume(struct device *dev, bool rpm) pci_set_power_state(pdev, PCI_D0); pci_restore_state(pdev); - pci_save_state(pdev); if (!pci_device_is_present(pdev)) return -ENODEV; @@ -9754,7 +9753,6 @@ static pci_ers_result_t igb_io_slot_reset(struct pci_dev *pdev) } else { pci_set_master(pdev); pci_restore_state(pdev); - pci_save_state(pdev); pci_enable_wake(pdev, PCI_D3hot, 0); pci_enable_wake(pdev, PCI_D3cold, 0); diff --git a/drivers/net/ethernet/intel/igc/igc_main.c b/drivers/net/ethernet/intel/igc/igc_main.c index 21e67e7534562c..89a321a344d263 100644 --- a/drivers/net/ethernet/intel/igc/igc_main.c +++ b/drivers/net/ethernet/intel/igc/igc_main.c @@ -7530,7 +7530,6 @@ static int __igc_resume(struct device *dev, bool rpm) pci_set_power_state(pdev, PCI_D0); pci_restore_state(pdev); - pci_save_state(pdev); if (!pci_device_is_present(pdev)) return -ENODEV; @@ -7667,7 +7666,6 @@ static pci_ers_result_t igc_io_slot_reset(struct pci_dev *pdev) } else { pci_set_master(pdev); pci_restore_state(pdev); - pci_save_state(pdev); pci_enable_wake(pdev, PCI_D3hot, 0); pci_enable_wake(pdev, PCI_D3cold, 0); diff --git a/drivers/net/ethernet/intel/ixgbe/ixgbe_main.c b/drivers/net/ethernet/intel/ixgbe/ixgbe_main.c index 3edebca9583076..501216970e611f 100644 --- a/drivers/net/ethernet/intel/ixgbe/ixgbe_main.c +++ b/drivers/net/ethernet/intel/ixgbe/ixgbe_main.c @@ -12292,7 +12292,6 @@ static pci_ers_result_t ixgbe_io_slot_reset(struct pci_dev *pdev) adapter->hw.hw_addr = adapter->io_addr; pci_set_master(pdev); pci_restore_state(pdev); - pci_save_state(pdev); pci_wake_from_d3(pdev, false); diff --git a/drivers/net/ethernet/mellanox/mlx4/main.c b/drivers/net/ethernet/mellanox/mlx4/main.c index 03d2fc7d9b09f8..d1fbf37bdaf793 100644 --- a/drivers/net/ethernet/mellanox/mlx4/main.c +++ b/drivers/net/ethernet/mellanox/mlx4/main.c @@ -4366,7 +4366,6 @@ static pci_ers_result_t mlx4_pci_slot_reset(struct pci_dev *pdev) pci_set_master(pdev); pci_restore_state(pdev); - pci_save_state(pdev); return PCI_ERS_RESULT_RECOVERED; } diff --git a/drivers/net/ethernet/mellanox/mlx5/core/main.c b/drivers/net/ethernet/mellanox/mlx5/core/main.c index 14c57d43728028..acba430a94da27 100644 --- a/drivers/net/ethernet/mellanox/mlx5/core/main.c +++ b/drivers/net/ethernet/mellanox/mlx5/core/main.c @@ -2100,7 +2100,6 @@ static pci_ers_result_t mlx5_pci_slot_reset(struct pci_dev *pdev) pci_set_master(pdev); pci_restore_state(pdev); - pci_save_state(pdev); err = wait_vital(pdev); if (err) { diff --git a/drivers/net/ethernet/meta/fbnic/fbnic_pci.c b/drivers/net/ethernet/meta/fbnic/fbnic_pci.c index a7a6b4db8016f3..0fa90baad5f88e 100644 --- a/drivers/net/ethernet/meta/fbnic/fbnic_pci.c +++ b/drivers/net/ethernet/meta/fbnic/fbnic_pci.c @@ -574,7 +574,6 @@ static pci_ers_result_t fbnic_err_slot_reset(struct pci_dev *pdev) pci_set_power_state(pdev, PCI_D0); pci_restore_state(pdev); - pci_save_state(pdev); if (pci_enable_device_mem(pdev)) { dev_err(&pdev->dev, diff --git a/drivers/net/ethernet/microchip/lan743x_main.c b/drivers/net/ethernet/microchip/lan743x_main.c index 9d70b51ca91d62..e4c542fc6c2b87 100644 --- a/drivers/net/ethernet/microchip/lan743x_main.c +++ b/drivers/net/ethernet/microchip/lan743x_main.c @@ -3915,7 +3915,6 @@ static int lan743x_pm_resume(struct device *dev) pci_set_power_state(pdev, PCI_D0); pci_restore_state(pdev); - pci_save_state(pdev); /* Restore HW_CFG that was saved during pm suspend */ if (adapter->is_pci11x1x) diff --git a/drivers/net/ethernet/myricom/myri10ge/myri10ge.c b/drivers/net/ethernet/myricom/myri10ge/myri10ge.c index e611ff7fa3faba..7be30a8df26858 100644 --- a/drivers/net/ethernet/myricom/myri10ge/myri10ge.c +++ b/drivers/net/ethernet/myricom/myri10ge/myri10ge.c @@ -3416,10 +3416,6 @@ static void myri10ge_watchdog(struct work_struct *work) * nic was resumed from power saving mode. */ pci_restore_state(mgp->pdev); - - /* save state again for accounting reasons */ - pci_save_state(mgp->pdev); - } else { /* if we get back -1's from our slot, perhaps somebody * powered off our card. Don't try to reset it in diff --git a/drivers/net/ethernet/neterion/s2io.c b/drivers/net/ethernet/neterion/s2io.c index 5026b0263d430b..1e55ccb4822b0b 100644 --- a/drivers/net/ethernet/neterion/s2io.c +++ b/drivers/net/ethernet/neterion/s2io.c @@ -3425,7 +3425,6 @@ static void s2io_reset(struct s2io_nic *sp) /* Restore the PCI state saved during initialization. */ pci_restore_state(sp->pdev); - pci_save_state(sp->pdev); pci_read_config_word(sp->pdev, 0x2, &val16); if (check_pci_device_id(val16) != (u16)PCI_ANY_ID) break; diff --git a/drivers/pci/pcie/portdrv.c b/drivers/pci/pcie/portdrv.c index d1b68c18444f80..38a41ccf79b9a3 100644 --- a/drivers/pci/pcie/portdrv.c +++ b/drivers/pci/pcie/portdrv.c @@ -760,7 +760,6 @@ static pci_ers_result_t pcie_portdrv_slot_reset(struct pci_dev *dev) device_for_each_child(&dev->dev, &off, pcie_port_device_iter); pci_restore_state(dev); - pci_save_state(dev); return PCI_ERS_RESULT_RECOVERED; } diff --git a/drivers/scsi/bfa/bfad.c b/drivers/scsi/bfa/bfad.c index ff9adfc0b332e2..bdfd0651667187 100644 --- a/drivers/scsi/bfa/bfad.c +++ b/drivers/scsi/bfa/bfad.c @@ -1528,7 +1528,6 @@ bfad_pci_slot_reset(struct pci_dev *pdev) goto out_disable_device; } - pci_save_state(pdev); pci_set_master(pdev); rc = dma_set_mask_and_coherent(&bfad->pcidev->dev, DMA_BIT_MASK(64)); diff --git a/drivers/scsi/csiostor/csio_init.c b/drivers/scsi/csiostor/csio_init.c index 79c8dafdd49ed2..db0c2174430ac2 100644 --- a/drivers/scsi/csiostor/csio_init.c +++ b/drivers/scsi/csiostor/csio_init.c @@ -1093,7 +1093,6 @@ csio_pci_slot_reset(struct pci_dev *pdev) pci_set_master(pdev); pci_restore_state(pdev); - pci_save_state(pdev); /* Bring HW s/m to ready state. * but don't resume IOs. diff --git a/drivers/scsi/ipr.c b/drivers/scsi/ipr.c index d62bb7d0e4164e..dbd58a7e7bc1f3 100644 --- a/drivers/scsi/ipr.c +++ b/drivers/scsi/ipr.c @@ -7883,7 +7883,6 @@ static int ipr_reset_restore_cfg_space(struct ipr_cmnd *ipr_cmd) struct ipr_ioa_cfg *ioa_cfg = ipr_cmd->ioa_cfg; ENTER; - ioa_cfg->pdev->state_saved = true; pci_restore_state(ioa_cfg->pdev); if (ipr_set_pcix_cmd_reg(ioa_cfg)) { diff --git a/drivers/scsi/lpfc/lpfc_init.c b/drivers/scsi/lpfc/lpfc_init.c index f206267d9ecdc1..065eb91de9c0f6 100644 --- a/drivers/scsi/lpfc/lpfc_init.c +++ b/drivers/scsi/lpfc/lpfc_init.c @@ -14434,12 +14434,6 @@ lpfc_io_slot_reset_s3(struct pci_dev *pdev) pci_restore_state(pdev); - /* - * As the new kernel behavior of pci_restore_state() API call clears - * device saved_state flag, need to save the restored state again. - */ - pci_save_state(pdev); - if (pdev->is_busmaster) pci_set_master(pdev); diff --git a/drivers/scsi/qla2xxx/qla_os.c b/drivers/scsi/qla2xxx/qla_os.c index 3d814262040ac8..8ad0c19bdf4a9b 100644 --- a/drivers/scsi/qla2xxx/qla_os.c +++ b/drivers/scsi/qla2xxx/qla_os.c @@ -7890,11 +7890,6 @@ qla2xxx_pci_slot_reset(struct pci_dev *pdev) pci_restore_state(pdev); - /* pci_restore_state() clears the saved_state flag of the device - * save restored state which resets saved_state flag - */ - pci_save_state(pdev); - if (ha->mem_only) rc = pci_enable_device_mem(pdev); else diff --git a/drivers/scsi/qla4xxx/ql4_os.c b/drivers/scsi/qla4xxx/ql4_os.c index 83ff66f954e672..97329c97332f83 100644 --- a/drivers/scsi/qla4xxx/ql4_os.c +++ b/drivers/scsi/qla4xxx/ql4_os.c @@ -9796,11 +9796,6 @@ qla4xxx_pci_slot_reset(struct pci_dev *pdev) */ pci_restore_state(pdev); - /* pci_restore_state() clears the saved_state flag of the device - * save restored state which resets saved_state flag - */ - pci_save_state(pdev); - /* Initialize device or resume if in suspended state */ rc = pci_enable_device(pdev); if (rc) { diff --git a/drivers/tty/serial/8250/8250_pci.c b/drivers/tty/serial/8250/8250_pci.c index 93b3922bb5b6eb..79c3dca94b5600 100644 --- a/drivers/tty/serial/8250/8250_pci.c +++ b/drivers/tty/serial/8250/8250_pci.c @@ -6215,7 +6215,6 @@ static pci_ers_result_t serial8250_io_slot_reset(struct pci_dev *dev) return PCI_ERS_RESULT_DISCONNECT; pci_restore_state(dev); - pci_save_state(dev); return PCI_ERS_RESULT_RECOVERED; } diff --git a/drivers/tty/serial/jsm/jsm_driver.c b/drivers/tty/serial/jsm/jsm_driver.c index 417a5b6bffc34d..8d21373cae573a 100644 --- a/drivers/tty/serial/jsm/jsm_driver.c +++ b/drivers/tty/serial/jsm/jsm_driver.c @@ -355,7 +355,6 @@ static void jsm_io_resume(struct pci_dev *pdev) struct jsm_board *brd = pci_get_drvdata(pdev); pci_restore_state(pdev); - pci_save_state(pdev); jsm_uart_port_init(brd); } From 3a15c519d2b0f52b6949aff96dc768c07bd84d88 Mon Sep 17 00:00:00 2001 From: Daniele Palmas Date: Wed, 15 Oct 2025 12:20:59 +0200 Subject: [PATCH 3015/3902] bus: mhi: host: pci_generic: Add Telit FE990B40 modem support commit 6eaee77923ddf04beedb832c06f983679586361c upstream. Add SDX72 based modem Telit FE990B40, reusing FN920C04 configuration. 01:00.0 Unassigned class [ff00]: Qualcomm Device 0309 Subsystem: Device 1c5d:2025 Signed-off-by: Daniele Palmas Signed-off-by: Manivannan Sadhasivam Link: https://patch.msgid.link/20251015102059.1781001-1-dnlplm@gmail.com Signed-off-by: Fabio Porcedda Signed-off-by: Greg Kroah-Hartman --- drivers/bus/mhi/host/pci_generic.c | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/drivers/bus/mhi/host/pci_generic.c b/drivers/bus/mhi/host/pci_generic.c index b188bbf7de042d..3d8c9729fcfc57 100644 --- a/drivers/bus/mhi/host/pci_generic.c +++ b/drivers/bus/mhi/host/pci_generic.c @@ -877,6 +877,16 @@ static const struct mhi_pci_dev_info mhi_telit_fn990b40_info = { .edl_trigger = true, }; +static const struct mhi_pci_dev_info mhi_telit_fe990b40_info = { + .name = "telit-fe990b40", + .config = &modem_telit_fn920c04_config, + .bar_num = MHI_PCI_DEFAULT_BAR_NUM, + .dma_data_width = 32, + .sideband_wake = false, + .mru_default = 32768, + .edl_trigger = true, +}; + static const struct mhi_pci_dev_info mhi_netprisma_lcur57_info = { .name = "netprisma-lcur57", .edl = "qcom/prog_firehose_sdx24.mbn", @@ -933,6 +943,9 @@ static const struct pci_device_id mhi_pci_id_table[] = { /* Telit FN990B40 (sdx72) */ { PCI_DEVICE_SUB(PCI_VENDOR_ID_QCOM, 0x0309, 0x1c5d, 0x201a), .driver_data = (kernel_ulong_t) &mhi_telit_fn990b40_info }, + /* Telit FE990B40 (sdx72) */ + { PCI_DEVICE_SUB(PCI_VENDOR_ID_QCOM, 0x0309, 0x1c5d, 0x2025), + .driver_data = (kernel_ulong_t) &mhi_telit_fe990b40_info }, { PCI_DEVICE(PCI_VENDOR_ID_QCOM, 0x0309), .driver_data = (kernel_ulong_t) &mhi_qcom_sdx75_info }, /* QDU100, x100-DU */ From de7cb4282dafc1af7b3ef02338df54a6d7e3e5a8 Mon Sep 17 00:00:00 2001 From: Tim Chen Date: Mon, 10 Nov 2025 10:47:35 -0800 Subject: [PATCH 3016/3902] sched/fair: Skip sched_balance_running cmpxchg when balance is not due commit 3324b2180c17b21c31c16966cc85ca41a7c93703 upstream. The NUMA sched domain sets the SD_SERIALIZE flag by default, allowing only one NUMA load balancing operation to run system-wide at a time. Currently, each sched group leader directly under NUMA domain attempts to acquire the global sched_balance_running flag via cmpxchg() before checking whether load balancing is due or whether it is the designated load balancer for that NUMA domain. On systems with a large number of cores, this causes significant cache contention on the shared sched_balance_running flag. This patch reduces unnecessary cmpxchg() operations by first checking that the balancer is the designated leader for a NUMA domain from should_we_balance(), and the balance interval has expired before trying to acquire sched_balance_running to load balance a NUMA domain. On a 2-socket Granite Rapids system with sub-NUMA clustering enabled, running an OLTP workload, 7.8% of total CPU cycles were previously spent in sched_balance_domain() contending on sched_balance_running before this change. : 104 static __always_inline int arch_atomic_cmpxchg(atomic_t *v, int old, int new) : 105 { : 106 return arch_cmpxchg(&v->counter, old, new); 0.00 : ffffffff81326e6c: xor %eax,%eax 0.00 : ffffffff81326e6e: mov $0x1,%ecx 0.00 : ffffffff81326e73: lock cmpxchg %ecx,0x2394195(%rip) # ffffffff836bb010 : 110 sched_balance_domains(): : 12234 if (atomic_cmpxchg_acquire(&sched_balance_running, 0, 1)) 99.39 : ffffffff81326e7b: test %eax,%eax 0.00 : ffffffff81326e7d: jne ffffffff81326e99 : 12238 if (time_after_eq(jiffies, sd->last_balance + interval)) { 0.00 : ffffffff81326e7f: mov 0x14e2b3a(%rip),%rax # ffffffff828099c0 0.00 : ffffffff81326e86: sub 0x48(%r14),%rax 0.00 : ffffffff81326e8a: cmp %rdx,%rax After applying this fix, sched_balance_domain() is gone from the profile and there is a 5% throughput improvement. [peterz: made it so that redo retains the 'lock' and split out the CPU_NEWLY_IDLE change to a separate patch] Signed-off-by: Tim Chen Signed-off-by: Peter Zijlstra (Intel) Reviewed-by: Chen Yu Reviewed-by: Vincent Guittot Reviewed-by: Shrikanth Hegde Reviewed-by: K Prateek Nayak Reviewed-by: Srikar Dronamraju Tested-by: Mohini Narkhede Tested-by: Shrikanth Hegde Link: https://patch.msgid.link/6fed119b723c71552943bfe5798c93851b30a361.1762800251.git.tim.c.chen@linux.intel.com Signed-off-by: Greg Kroah-Hartman --- kernel/sched/fair.c | 54 +++++++++++++++++++++++---------------------- 1 file changed, 28 insertions(+), 26 deletions(-) diff --git a/kernel/sched/fair.c b/kernel/sched/fair.c index f0c7c94421beae..fc3ca2d5f7fb24 100644 --- a/kernel/sched/fair.c +++ b/kernel/sched/fair.c @@ -11692,6 +11692,21 @@ static void update_lb_imbalance_stat(struct lb_env *env, struct sched_domain *sd } } +/* + * This flag serializes load-balancing passes over large domains + * (above the NODE topology level) - only one load-balancing instance + * may run at a time, to reduce overhead on very large systems with + * lots of CPUs and large NUMA distances. + * + * - Note that load-balancing passes triggered while another one + * is executing are skipped and not re-tried. + * + * - Also note that this does not serialize rebalance_domains() + * execution, as non-SD_SERIALIZE domains will still be + * load-balanced in parallel. + */ +static atomic_t sched_balance_running = ATOMIC_INIT(0); + /* * Check this_cpu to ensure it is balanced within domain. Attempt to move * tasks if there is an imbalance. @@ -11717,6 +11732,7 @@ static int sched_balance_rq(int this_cpu, struct rq *this_rq, .fbq_type = all, .tasks = LIST_HEAD_INIT(env.tasks), }; + bool need_unlock = false; cpumask_and(cpus, sched_domain_span(sd), cpu_active_mask); @@ -11728,6 +11744,14 @@ static int sched_balance_rq(int this_cpu, struct rq *this_rq, goto out_balanced; } + if (!need_unlock && (sd->flags & SD_SERIALIZE) && idle != CPU_NEWLY_IDLE) { + int zero = 0; + if (!atomic_try_cmpxchg_acquire(&sched_balance_running, &zero, 1)) + goto out_balanced; + + need_unlock = true; + } + group = sched_balance_find_src_group(&env); if (!group) { schedstat_inc(sd->lb_nobusyg[idle]); @@ -11968,6 +11992,9 @@ static int sched_balance_rq(int this_cpu, struct rq *this_rq, sd->balance_interval < sd->max_interval) sd->balance_interval *= 2; out: + if (need_unlock) + atomic_set_release(&sched_balance_running, 0); + return ld_moved; } @@ -12092,21 +12119,6 @@ static int active_load_balance_cpu_stop(void *data) return 0; } -/* - * This flag serializes load-balancing passes over large domains - * (above the NODE topology level) - only one load-balancing instance - * may run at a time, to reduce overhead on very large systems with - * lots of CPUs and large NUMA distances. - * - * - Note that load-balancing passes triggered while another one - * is executing are skipped and not re-tried. - * - * - Also note that this does not serialize rebalance_domains() - * execution, as non-SD_SERIALIZE domains will still be - * load-balanced in parallel. - */ -static atomic_t sched_balance_running = ATOMIC_INIT(0); - /* * Scale the max sched_balance_rq interval with the number of CPUs in the system. * This trades load-balance latency on larger machines for less cross talk. @@ -12175,7 +12187,7 @@ static void sched_balance_domains(struct rq *rq, enum cpu_idle_type idle) /* Earliest time when we have to do rebalance again */ unsigned long next_balance = jiffies + 60*HZ; int update_next_balance = 0; - int need_serialize, need_decay = 0; + int need_decay = 0; u64 max_cost = 0; rcu_read_lock(); @@ -12199,13 +12211,6 @@ static void sched_balance_domains(struct rq *rq, enum cpu_idle_type idle) } interval = get_sd_balance_interval(sd, busy); - - need_serialize = sd->flags & SD_SERIALIZE; - if (need_serialize) { - if (atomic_cmpxchg_acquire(&sched_balance_running, 0, 1)) - goto out; - } - if (time_after_eq(jiffies, sd->last_balance + interval)) { if (sched_balance_rq(cpu, rq, sd, idle, &continue_balancing)) { /* @@ -12219,9 +12224,6 @@ static void sched_balance_domains(struct rq *rq, enum cpu_idle_type idle) sd->last_balance = jiffies; interval = get_sd_balance_interval(sd, busy); } - if (need_serialize) - atomic_set_release(&sched_balance_running, 0); -out: if (time_after(next_balance, sd->last_balance + interval)) { next_balance = sd->last_balance + interval; update_next_balance = 1; From 13de38aa3ea7aacad3c0bc1312d46582c866dc72 Mon Sep 17 00:00:00 2001 From: Peter Zijlstra Date: Mon, 17 Nov 2025 17:13:09 +0100 Subject: [PATCH 3017/3902] sched/fair: Have SD_SERIALIZE affect newidle balancing commit 522fb20fbdbe48ed98f587d628637ff38ececd2d upstream. Also serialize the possiblty much more frequent newidle balancing for the 'expensive' domains that have SD_BALANCE set. Initial benchmarking by K Prateek and Tim showed no negative effect. Split out from the larger patch moving sched_balance_running around for ease of bisect and such. Suggested-by: Shrikanth Hegde Seconded-by: K Prateek Nayak Signed-off-by: Peter Zijlstra (Intel) Link: https://lkml.kernel.org/r/df068896-82f9-458d-8fff-5a2f654e8ffd@amd.com Link: https://patch.msgid.link/6fed119b723c71552943bfe5798c93851b30a361.1762800251.git.tim.c.chen@linux.intel.com Signed-off-by: Tim Chen Signed-off-by: Greg Kroah-Hartman --- kernel/sched/fair.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/kernel/sched/fair.c b/kernel/sched/fair.c index fc3ca2d5f7fb24..82038166d7b0cf 100644 --- a/kernel/sched/fair.c +++ b/kernel/sched/fair.c @@ -11744,7 +11744,7 @@ static int sched_balance_rq(int this_cpu, struct rq *this_rq, goto out_balanced; } - if (!need_unlock && (sd->flags & SD_SERIALIZE) && idle != CPU_NEWLY_IDLE) { + if (!need_unlock && (sd->flags & SD_SERIALIZE)) { int zero = 0; if (!atomic_try_cmpxchg_acquire(&sched_balance_running, &zero, 1)) goto out_balanced; From 598fe3ff32e43918ed8a062f55432b3d23e6340c Mon Sep 17 00:00:00 2001 From: Alice Ryhl Date: Mon, 29 Dec 2025 15:38:14 +0000 Subject: [PATCH 3018/3902] rust_binder: correctly handle FDA objects of length zero commit 8f589c9c3be539d6c2b393c82940c3783831082f upstream. Fix a bug where an empty FDA (fd array) object with 0 fds would cause an out-of-bounds error. The previous implementation used `skip == 0` to mean "this is a pointer fixup", but 0 is also the correct skip length for an empty FDA. If the FDA is at the end of the buffer, then this results in an attempt to write 8-bytes out of bounds. This is caught and results in an EINVAL error being returned to userspace. The pattern of using `skip == 0` as a special value originates from the C-implementation of Binder. As part of fixing this bug, this pattern is replaced with a Rust enum. I considered the alternate option of not pushing a fixup when the length is zero, but I think it's cleaner to just get rid of the zero-is-special stuff. The root cause of this bug was diagnosed by Gemini CLI on first try. I used the following prompt: > There appears to be a bug in @drivers/android/binder/thread.rs where > the Fixups oob bug is triggered with 316 304 316 324. This implies > that we somehow ended up with a fixup where buffer A has a pointer to > buffer B, but the pointer is located at an index in buffer A that is > out of bounds. Please investigate the code to find the bug. You may > compare with @drivers/android/binder.c that implements this correctly. Cc: stable@vger.kernel.org Reported-by: DeepChirp Closes: https://github.com/waydroid/waydroid/issues/2157 Fixes: eafedbc7c050 ("rust_binder: add Rust Binder driver") Tested-by: DeepChirp Signed-off-by: Alice Ryhl Acked-by: Carlos Llamas Link: https://patch.msgid.link/20251229-fda-zero-v1-1-58a41cb0e7ec@google.com Signed-off-by: Greg Kroah-Hartman --- drivers/android/binder/thread.rs | 59 ++++++++++++++++++-------------- 1 file changed, 34 insertions(+), 25 deletions(-) diff --git a/drivers/android/binder/thread.rs b/drivers/android/binder/thread.rs index 7e34ccd394f804..33a9053b6adf06 100644 --- a/drivers/android/binder/thread.rs +++ b/drivers/android/binder/thread.rs @@ -69,17 +69,24 @@ struct ScatterGatherEntry { } /// This entry specifies that a fixup should happen at `target_offset` of the -/// buffer. If `skip` is nonzero, then the fixup is a `binder_fd_array_object` -/// and is applied later. Otherwise if `skip` is zero, then the size of the -/// fixup is `sizeof::()` and `pointer_value` is written to the buffer. -struct PointerFixupEntry { - /// The number of bytes to skip, or zero for a `binder_buffer_object` fixup. - skip: usize, - /// The translated pointer to write when `skip` is zero. - pointer_value: u64, - /// The offset at which the value should be written. The offset is relative - /// to the original buffer. - target_offset: usize, +/// buffer. +enum PointerFixupEntry { + /// A fixup for a `binder_buffer_object`. + Fixup { + /// The translated pointer to write. + pointer_value: u64, + /// The offset at which the value should be written. The offset is relative + /// to the original buffer. + target_offset: usize, + }, + /// A skip for a `binder_fd_array_object`. + Skip { + /// The number of bytes to skip. + skip: usize, + /// The offset at which the skip should happen. The offset is relative + /// to the original buffer. + target_offset: usize, + }, } /// Return type of `apply_and_validate_fixup_in_parent`. @@ -762,8 +769,7 @@ impl Thread { parent_entry.fixup_min_offset = info.new_min_offset; parent_entry.pointer_fixups.push( - PointerFixupEntry { - skip: 0, + PointerFixupEntry::Fixup { pointer_value: buffer_ptr_in_user_space, target_offset: info.target_offset, }, @@ -807,9 +813,8 @@ impl Thread { parent_entry .pointer_fixups .push( - PointerFixupEntry { + PointerFixupEntry::Skip { skip: fds_len, - pointer_value: 0, target_offset: info.target_offset, }, GFP_KERNEL, @@ -871,17 +876,21 @@ impl Thread { let mut reader = UserSlice::new(UserPtr::from_addr(sg_entry.sender_uaddr), sg_entry.length).reader(); for fixup in &mut sg_entry.pointer_fixups { - let fixup_len = if fixup.skip == 0 { - size_of::() - } else { - fixup.skip + let (fixup_len, fixup_offset) = match fixup { + PointerFixupEntry::Fixup { target_offset, .. } => { + (size_of::(), *target_offset) + } + PointerFixupEntry::Skip { + skip, + target_offset, + } => (*skip, *target_offset), }; - let target_offset_end = fixup.target_offset.checked_add(fixup_len).ok_or(EINVAL)?; - if fixup.target_offset < end_of_previous_fixup || offset_end < target_offset_end { + 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.target_offset, + fixup_offset, end_of_previous_fixup, offset_end, target_offset_end @@ -890,13 +899,13 @@ impl Thread { } let copy_off = end_of_previous_fixup; - let copy_len = fixup.target_offset - 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); return Err(err.into()); } - if fixup.skip == 0 { - let res = alloc.write::(fixup.target_offset, &fixup.pointer_value); + 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); return Err(err.into()); From 685bb05d307ac49187534fb1b00b021b6b72e0e7 Mon Sep 17 00:00:00 2001 From: Alice Ryhl Date: Fri, 23 Jan 2026 16:23:56 +0000 Subject: [PATCH 3019/3902] rust_binder: add additional alignment checks commit d047248190d86a52164656d47bec9bfba61dc71e upstream. This adds some alignment checks to match C Binder more closely. This causes the driver to reject more transactions. I don't think any of the transactions in question are harmful, but it's still a bug because it's the wrong uapi to accept them. The cases where usize is changed for u64, it will affect only 32-bit kernels. Cc: stable@vger.kernel.org Fixes: eafedbc7c050 ("rust_binder: add Rust Binder driver") Signed-off-by: Alice Ryhl Acked-by: Carlos Llamas Link: https://patch.msgid.link/20260123-binder-alignment-more-checks-v1-1-7e1cea77411d@google.com Signed-off-by: Greg Kroah-Hartman --- drivers/android/binder/thread.rs | 50 +++++++++++++++++++++++--------- 1 file changed, 36 insertions(+), 14 deletions(-) diff --git a/drivers/android/binder/thread.rs b/drivers/android/binder/thread.rs index 33a9053b6adf06..67af5ff2816607 100644 --- a/drivers/android/binder/thread.rs +++ b/drivers/android/binder/thread.rs @@ -39,6 +39,10 @@ use core::{ sync::atomic::{AtomicU32, Ordering}, }; +fn is_aligned(value: usize, to: usize) -> bool { + value % to == 0 +} + /// Stores the layout of the scatter-gather entries. This is used during the `translate_objects` /// call and is discarded when it returns. struct ScatterGatherState { @@ -795,6 +799,10 @@ impl Thread { let num_fds = usize::try_from(obj.num_fds).map_err(|_| EINVAL)?; let fds_len = num_fds.checked_mul(size_of::()).ok_or(EINVAL)?; + if !is_aligned(parent_offset, size_of::()) { + return Err(EINVAL.into()); + } + let info = sg_state.validate_parent_fixup(parent_index, parent_offset, fds_len)?; view.alloc.info_add_fd_reserve(num_fds)?; @@ -809,6 +817,10 @@ impl Thread { } }; + if !is_aligned(parent_entry.sender_uaddr, size_of::()) { + return Err(EINVAL.into()); + } + parent_entry.fixup_min_offset = info.new_min_offset; parent_entry .pointer_fixups @@ -825,6 +837,7 @@ impl Thread { .sender_uaddr .checked_add(parent_offset) .ok_or(EINVAL)?; + let mut fda_bytes = KVec::new(); UserSlice::new(UserPtr::from_addr(fda_uaddr as _), fds_len) .read_all(&mut fda_bytes, GFP_KERNEL)?; @@ -958,25 +971,30 @@ impl Thread { let data_size = trd.data_size.try_into().map_err(|_| EINVAL)?; let aligned_data_size = ptr_align(data_size).ok_or(EINVAL)?; - let offsets_size = trd.offsets_size.try_into().map_err(|_| EINVAL)?; - let aligned_offsets_size = ptr_align(offsets_size).ok_or(EINVAL)?; - let buffers_size = tr.buffers_size.try_into().map_err(|_| EINVAL)?; - let aligned_buffers_size = ptr_align(buffers_size).ok_or(EINVAL)?; + let offsets_size: usize = trd.offsets_size.try_into().map_err(|_| EINVAL)?; + let buffers_size: usize = tr.buffers_size.try_into().map_err(|_| EINVAL)?; let aligned_secctx_size = match secctx.as_ref() { Some((_offset, ctx)) => ptr_align(ctx.len()).ok_or(EINVAL)?, None => 0, }; + if !is_aligned(offsets_size, size_of::()) { + return Err(EINVAL.into()); + } + if !is_aligned(buffers_size, size_of::()) { + return Err(EINVAL.into()); + } + // This guarantees that at least `sizeof(usize)` bytes will be allocated. let len = usize::max( aligned_data_size - .checked_add(aligned_offsets_size) - .and_then(|sum| sum.checked_add(aligned_buffers_size)) + .checked_add(offsets_size) + .and_then(|sum| sum.checked_add(buffers_size)) .and_then(|sum| sum.checked_add(aligned_secctx_size)) .ok_or(ENOMEM)?, - size_of::(), + size_of::(), ); - let secctx_off = aligned_data_size + aligned_offsets_size + aligned_buffers_size; + let secctx_off = aligned_data_size + offsets_size + buffers_size; let mut alloc = match to_process.buffer_alloc(debug_id, len, is_oneway, self.process.task.pid()) { Ok(alloc) => alloc, @@ -1008,13 +1026,13 @@ impl Thread { } let offsets_start = aligned_data_size; - let offsets_end = aligned_data_size + aligned_offsets_size; + let offsets_end = aligned_data_size + offsets_size; // This state is used for BINDER_TYPE_PTR objects. let sg_state = sg_state.insert(ScatterGatherState { unused_buffer_space: UnusedBufferSpace { offset: offsets_end, - limit: len, + limit: offsets_end + buffers_size, }, sg_entries: KVec::new(), ancestors: KVec::new(), @@ -1023,12 +1041,16 @@ impl Thread { // Traverse the objects specified. let mut view = AllocationView::new(&mut alloc, data_size); for (index, index_offset) in (offsets_start..offsets_end) - .step_by(size_of::()) + .step_by(size_of::()) .enumerate() { - let offset = view.alloc.read(index_offset)?; + let offset: usize = view + .alloc + .read::(index_offset)? + .try_into() + .map_err(|_| EINVAL)?; - if offset < end_of_previous_object { + if offset < end_of_previous_object || !is_aligned(offset, size_of::()) { pr_warn!("Got transaction with invalid offset."); return Err(EINVAL.into()); } @@ -1060,7 +1082,7 @@ impl Thread { } // Update the indexes containing objects to clean up. - let offset_after_object = index_offset + size_of::(); + let offset_after_object = index_offset + size_of::(); view.alloc .set_info_offsets(offsets_start..offset_after_object); } From 287221c5e070761d62d16cfad58ace3f008e0b94 Mon Sep 17 00:00:00 2001 From: Carlos Llamas Date: Tue, 27 Jan 2026 23:55:10 +0000 Subject: [PATCH 3020/3902] rust_binderfs: fix ida_alloc_max() upper bound commit d6ba734814266bbf7ee01f9030436597116805f3 upstream. The 'max' argument of ida_alloc_max() takes the maximum valid ID and not the "count". Using an ID of BINDERFS_MAX_MINOR (1 << 20) for dev->minor would exceed the limits of minor numbers (20-bits). Fix this off-by-one error by subtracting 1 from the 'max'. Cc: stable@vger.kernel.org Fixes: eafedbc7c050 ("rust_binder: add Rust Binder driver") Reported-by: kernel test robot Closes: https://lore.kernel.org/r/202512181203.IOv6IChH-lkp@intel.com/ Signed-off-by: Carlos Llamas Reviewed-by: Alice Ryhl Link: https://patch.msgid.link/20260127235545.2307876-1-cmllamas@google.com Signed-off-by: Greg Kroah-Hartman --- drivers/android/binder/rust_binderfs.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/drivers/android/binder/rust_binderfs.c b/drivers/android/binder/rust_binderfs.c index 6b497146b698b3..c7e26753f1df59 100644 --- a/drivers/android/binder/rust_binderfs.c +++ b/drivers/android/binder/rust_binderfs.c @@ -132,8 +132,8 @@ static int binderfs_binder_device_create(struct inode *ref_inode, mutex_lock(&binderfs_minors_mutex); if (++info->device_count <= info->mount_opts.max) minor = ida_alloc_max(&binderfs_minors, - use_reserve ? BINDERFS_MAX_MINOR : - BINDERFS_MAX_MINOR_CAPPED, + use_reserve ? BINDERFS_MAX_MINOR - 1 : + BINDERFS_MAX_MINOR_CAPPED - 1, GFP_KERNEL); else minor = -ENOSPC; @@ -416,8 +416,8 @@ static int binderfs_binder_ctl_create(struct super_block *sb) /* Reserve a new minor number for the new device. */ mutex_lock(&binderfs_minors_mutex); minor = ida_alloc_max(&binderfs_minors, - use_reserve ? BINDERFS_MAX_MINOR : - BINDERFS_MAX_MINOR_CAPPED, + use_reserve ? BINDERFS_MAX_MINOR - 1 : + BINDERFS_MAX_MINOR_CAPPED - 1, GFP_KERNEL); mutex_unlock(&binderfs_minors_mutex); if (minor < 0) { From a6050dedb6f1cc23e518e3a132ab74a0aad6df90 Mon Sep 17 00:00:00 2001 From: Carlos Llamas Date: Thu, 22 Jan 2026 18:02:02 +0000 Subject: [PATCH 3021/3902] binder: fix UAF in binder_netlink_report() commit 5e8a3d01544282e50d887d76f30d1496a0a53562 upstream. Oneway transactions sent to frozen targets via binder_proc_transaction() return a BR_TRANSACTION_PENDING_FROZEN error but they are still treated as successful since the target is expected to thaw at some point. It is then not safe to access 't' after BR_TRANSACTION_PENDING_FROZEN errors as the transaction could have been consumed by the now thawed target. This is the case for binder_netlink_report() which derreferences 't' after a pending frozen error, as pointed out by the following KASAN report: ================================================================== BUG: KASAN: slab-use-after-free in binder_netlink_report.isra.0+0x694/0x6c8 Read of size 8 at addr ffff00000f98ba38 by task binder-util/522 CPU: 4 UID: 0 PID: 522 Comm: binder-util Not tainted 6.19.0-rc6-00015-gc03e9c42ae8f #1 PREEMPT Hardware name: linux,dummy-virt (DT) Call trace: binder_netlink_report.isra.0+0x694/0x6c8 binder_transaction+0x66e4/0x79b8 binder_thread_write+0xab4/0x4440 binder_ioctl+0x1fd4/0x2940 [...] Allocated by task 522: __kmalloc_cache_noprof+0x17c/0x50c binder_transaction+0x584/0x79b8 binder_thread_write+0xab4/0x4440 binder_ioctl+0x1fd4/0x2940 [...] Freed by task 488: kfree+0x1d0/0x420 binder_free_transaction+0x150/0x234 binder_thread_read+0x2d08/0x3ce4 binder_ioctl+0x488/0x2940 [...] ================================================================== Instead, make a transaction copy so the data can be safely accessed by binder_netlink_report() after a pending frozen error. While here, add a comment about not using t->buffer in binder_netlink_report(). Cc: stable@vger.kernel.org Fixes: 63740349eba7 ("binder: introduce transaction reports via netlink") Signed-off-by: Carlos Llamas Reviewed-by: Alice Ryhl Link: https://patch.msgid.link/20260122180203.1502637-1-cmllamas@google.com Signed-off-by: Greg Kroah-Hartman --- drivers/android/binder.c | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/drivers/android/binder.c b/drivers/android/binder.c index a3a1b5c33ba36a..65be6398481817 100644 --- a/drivers/android/binder.c +++ b/drivers/android/binder.c @@ -2991,6 +2991,10 @@ static void binder_set_txn_from_error(struct binder_transaction *t, int id, * @t: the binder transaction that failed * @data_size: the user provided data size for the transaction * @error: enum binder_driver_return_protocol returned to sender + * + * Note that t->buffer is not safe to access here, as it may have been + * released (or not yet allocated). Callers should guarantee all the + * transaction items used here are safe to access. */ static void binder_netlink_report(struct binder_proc *proc, struct binder_transaction *t, @@ -3780,6 +3784,14 @@ static void binder_transaction(struct binder_proc *proc, goto err_dead_proc_or_thread; } } else { + /* + * Make a transaction copy. It is not safe to access 't' after + * binder_proc_transaction() reported a pending frozen. The + * target could thaw and consume the transaction at any point. + * Instead, use a safe 't_copy' for binder_netlink_report(). + */ + struct binder_transaction t_copy = *t; + BUG_ON(target_node == NULL); BUG_ON(t->buffer->async_transaction != 1); return_error = binder_proc_transaction(t, target_proc, NULL); @@ -3790,7 +3802,7 @@ static void binder_transaction(struct binder_proc *proc, */ if (return_error == BR_TRANSACTION_PENDING_FROZEN) { tcomplete->type = BINDER_WORK_TRANSACTION_PENDING; - binder_netlink_report(proc, t, tr->data_size, + binder_netlink_report(proc, &t_copy, tr->data_size, return_error); } binder_enqueue_thread_work(thread, tcomplete); From e9bcfe865188a9009dcbdd20ed14c398f279257c Mon Sep 17 00:00:00 2001 From: Carlos Llamas Date: Fri, 23 Jan 2026 17:57:02 +0000 Subject: [PATCH 3022/3902] binder: fix BR_FROZEN_REPLY error log commit 1769f90e5ba2a6d24bb46b85da33fe861c68f005 upstream. The error logging for failed transactions is misleading as it always reports "dead process or thread" even when the target is actually frozen. Additionally, the pid and tid are reversed which can further confuse debugging efforts. Fix both issues. Cc: stable@kernel.org Cc: Steven Moreland Fixes: a15dac8b2286 ("binder: additional transaction error logs") Signed-off-by: Carlos Llamas Reviewed-by: Alice Ryhl Link: https://patch.msgid.link/20260123175702.2154348-1-cmllamas@google.com Signed-off-by: Greg Kroah-Hartman --- drivers/android/binder.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/drivers/android/binder.c b/drivers/android/binder.c index 65be6398481817..8e2989fb56a718 100644 --- a/drivers/android/binder.c +++ b/drivers/android/binder.c @@ -3824,8 +3824,9 @@ static void binder_transaction(struct binder_proc *proc, return; err_dead_proc_or_thread: - binder_txn_error("%d:%d dead process or thread\n", - thread->pid, proc->pid); + binder_txn_error("%d:%d %s process or thread\n", + proc->pid, thread->pid, + return_error == BR_FROZEN_REPLY ? "frozen" : "dead"); return_error_line = __LINE__; binder_dequeue_work(proc, tcomplete); err_translate_failed: From 116ffca92dc4d61c98d383e2ddd357abd8dddf24 Mon Sep 17 00:00:00 2001 From: Carlos Llamas Date: Tue, 27 Jan 2026 23:55:11 +0000 Subject: [PATCH 3023/3902] binderfs: fix ida_alloc_max() upper bound commit ec4ddc90d201d09ef4e4bef8a2c6d9624525ad68 upstream. The 'max' argument of ida_alloc_max() takes the maximum valid ID and not the "count". Using an ID of BINDERFS_MAX_MINOR (1 << 20) for dev->minor would exceed the limits of minor numbers (20-bits). Fix this off-by-one error by subtracting 1 from the 'max'. Cc: stable@vger.kernel.org Fixes: 3ad20fe393b3 ("binder: implement binderfs") Signed-off-by: Carlos Llamas Link: https://patch.msgid.link/20260127235545.2307876-2-cmllamas@google.com Signed-off-by: Greg Kroah-Hartman --- drivers/android/binderfs.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/drivers/android/binderfs.c b/drivers/android/binderfs.c index be8e64eb39ec51..6c6f52e1c032e9 100644 --- a/drivers/android/binderfs.c +++ b/drivers/android/binderfs.c @@ -132,8 +132,8 @@ static int binderfs_binder_device_create(struct inode *ref_inode, mutex_lock(&binderfs_minors_mutex); if (++info->device_count <= info->mount_opts.max) minor = ida_alloc_max(&binderfs_minors, - use_reserve ? BINDERFS_MAX_MINOR : - BINDERFS_MAX_MINOR_CAPPED, + use_reserve ? BINDERFS_MAX_MINOR - 1 : + BINDERFS_MAX_MINOR_CAPPED - 1, GFP_KERNEL); else minor = -ENOSPC; @@ -424,8 +424,8 @@ static int binderfs_binder_ctl_create(struct super_block *sb) /* Reserve a new minor number for the new device. */ mutex_lock(&binderfs_minors_mutex); minor = ida_alloc_max(&binderfs_minors, - use_reserve ? BINDERFS_MAX_MINOR : - BINDERFS_MAX_MINOR_CAPPED, + use_reserve ? BINDERFS_MAX_MINOR - 1 : + BINDERFS_MAX_MINOR_CAPPED - 1, GFP_KERNEL); mutex_unlock(&binderfs_minors_mutex); if (minor < 0) { From abd219fd48b11f1f199b627a8fddb77b9393e5d8 Mon Sep 17 00:00:00 2001 From: Steven Rostedt Date: Sat, 7 Feb 2026 10:56:15 -0500 Subject: [PATCH 3024/3902] tracing: Fix ftrace event field alignments [ Upstream commit 033c55fe2e326bea022c3cc5178ecf3e0e459b82 ] The fields of ftrace specific events (events used to save ftrace internal events like function traces and trace_printk) are generated similarly to how normal trace event fields are generated. That is, the fields are added to a trace_events_fields array that saves the name, offset, size, alignment and signness of the field. It is used to produce the output in the format file in tracefs so that tooling knows how to parse the binary data of the trace events. The issue is that some of the ftrace event structures are packed. The function graph exit event structures are one of them. The 64 bit calltime and rettime fields end up 4 byte aligned, but the algorithm to show to userspace shows them as 8 byte aligned. The macros that create the ftrace events has one for embedded structure fields. There's two macros for theses fields: __field_desc() and __field_packed() The difference of the latter macro is that it treats the field as packed. Rename that field to __field_desc_packed() and create replace the __field_packed() to be a normal field that is packed and have the calltime and rettime use those. This showed up on 32bit architectures for function graph time fields. It had: ~# cat /sys/kernel/tracing/events/ftrace/funcgraph_exit/format [..] field:unsigned long func; offset:8; size:4; signed:0; field:unsigned int depth; offset:12; size:4; signed:0; field:unsigned int overrun; offset:16; size:4; signed:0; field:unsigned long long calltime; offset:24; size:8; signed:0; field:unsigned long long rettime; offset:32; size:8; signed:0; Notice that overrun is at offset 16 with size 4, where in the structure calltime is at offset 20 (16 + 4), but it shows the offset at 24. That's because it used the alignment of unsigned long long when used as a declaration and not as a member of a structure where it would be aligned by word size (in this case 4). By using the proper structure alignment, the format has it at the correct offset: ~# cat /sys/kernel/tracing/events/ftrace/funcgraph_exit/format [..] field:unsigned long func; offset:8; size:4; signed:0; field:unsigned int depth; offset:12; size:4; signed:0; field:unsigned int overrun; offset:16; size:4; signed:0; field:unsigned long long calltime; offset:20; size:8; signed:0; field:unsigned long long rettime; offset:28; size:8; signed:0; Cc: stable@vger.kernel.org Cc: Mathieu Desnoyers Cc: Mark Rutland Acked-by: Masami Hiramatsu (Google) Reported-by: "jempty.liang" Link: https://patch.msgid.link/20260204113628.53faec78@gandalf.local.home Fixes: 04ae87a52074e ("ftrace: Rework event_create_dir()") Closes: https://lore.kernel.org/all/20260130015740.212343-1-imntjempty@163.com/ Closes: https://lore.kernel.org/all/20260202123342.2544795-1-imntjempty@163.com/ Signed-off-by: Steven Rostedt (Google) [ Different variable types and some renames ] Signed-off-by: Sasha Levin Signed-off-by: Greg Kroah-Hartman --- kernel/trace/trace.h | 7 +++++-- kernel/trace/trace_entries.h | 32 ++++++++++++++++---------------- kernel/trace/trace_export.c | 21 +++++++++++++++------ 3 files changed, 36 insertions(+), 24 deletions(-) diff --git a/kernel/trace/trace.h b/kernel/trace/trace.h index 85eabb454bee9f..ec372e0f2e716e 100644 --- a/kernel/trace/trace.h +++ b/kernel/trace/trace.h @@ -67,14 +67,17 @@ enum trace_type { #undef __field_fn #define __field_fn(type, item) type item; +#undef __field_packed +#define __field_packed(type, item) type item; + #undef __field_struct #define __field_struct(type, item) __field(type, item) #undef __field_desc #define __field_desc(type, container, item) -#undef __field_packed -#define __field_packed(type, container, item) +#undef __field_desc_packed +#define __field_desc_packed(type, container, item) #undef __array #define __array(type, item, size) type item[size]; diff --git a/kernel/trace/trace_entries.h b/kernel/trace/trace_entries.h index de294ae2c5c5a9..a649dfcf9b7cdc 100644 --- a/kernel/trace/trace_entries.h +++ b/kernel/trace/trace_entries.h @@ -79,8 +79,8 @@ FTRACE_ENTRY(funcgraph_entry, ftrace_graph_ent_entry, F_STRUCT( __field_struct( struct ftrace_graph_ent, graph_ent ) - __field_packed( unsigned long, graph_ent, func ) - __field_packed( unsigned int, graph_ent, depth ) + __field_desc_packed(unsigned long, graph_ent, func ) + __field_desc_packed(unsigned int, graph_ent, depth ) __dynamic_array(unsigned long, args ) ), @@ -96,9 +96,9 @@ FTRACE_ENTRY_PACKED(fgraph_retaddr_entry, fgraph_retaddr_ent_entry, F_STRUCT( __field_struct( struct fgraph_retaddr_ent, graph_ent ) - __field_packed( unsigned long, graph_ent, func ) - __field_packed( unsigned int, graph_ent, depth ) - __field_packed( unsigned long, graph_ent, retaddr ) + __field_desc_packed( unsigned long, graph_ent, func ) + __field_desc_packed( unsigned int, graph_ent, depth ) + __field_desc_packed( unsigned long, graph_ent, retaddr ) ), F_printk("--> %ps (%u) <- %ps", (void *)__entry->func, __entry->depth, @@ -122,12 +122,12 @@ FTRACE_ENTRY_PACKED(funcgraph_exit, ftrace_graph_ret_entry, F_STRUCT( __field_struct( struct ftrace_graph_ret, ret ) - __field_packed( unsigned long, ret, func ) - __field_packed( unsigned long, ret, retval ) - __field_packed( unsigned int, ret, depth ) - __field_packed( unsigned int, ret, overrun ) - __field(unsigned long long, calltime ) - __field(unsigned long long, rettime ) + __field_desc_packed( unsigned long, ret, func ) + __field_desc_packed( unsigned long, ret, retval ) + __field_desc_packed( unsigned int, ret, depth ) + __field_desc_packed( unsigned int, ret, overrun ) + __field_packed(unsigned long long, calltime) + __field_packed(unsigned long long, rettime ) ), F_printk("<-- %ps (%u) (start: %llx end: %llx) over: %u retval: %lx", @@ -145,11 +145,11 @@ FTRACE_ENTRY_PACKED(funcgraph_exit, ftrace_graph_ret_entry, F_STRUCT( __field_struct( struct ftrace_graph_ret, ret ) - __field_packed( unsigned long, ret, func ) - __field_packed( unsigned int, ret, depth ) - __field_packed( unsigned int, ret, overrun ) - __field(unsigned long long, calltime ) - __field(unsigned long long, rettime ) + __field_desc_packed( unsigned long, ret, func ) + __field_desc_packed( unsigned int, ret, depth ) + __field_desc_packed( unsigned int, ret, overrun ) + __field_packed(unsigned long long, calltime ) + __field_packed(unsigned long long, rettime ) ), F_printk("<-- %ps (%u) (start: %llx end: %llx) over: %u", diff --git a/kernel/trace/trace_export.c b/kernel/trace/trace_export.c index 1698fc22afa0a5..32a42ef31855d8 100644 --- a/kernel/trace/trace_export.c +++ b/kernel/trace/trace_export.c @@ -42,11 +42,14 @@ static int ftrace_event_register(struct trace_event_call *call, #undef __field_fn #define __field_fn(type, item) type item; +#undef __field_packed +#define __field_packed(type, item) type item; + #undef __field_desc #define __field_desc(type, container, item) type item; -#undef __field_packed -#define __field_packed(type, container, item) type item; +#undef __field_desc_packed +#define __field_desc_packed(type, container, item) type item; #undef __array #define __array(type, item, size) type item[size]; @@ -104,11 +107,14 @@ static void __always_unused ____ftrace_check_##name(void) \ #undef __field_fn #define __field_fn(_type, _item) __field_ext(_type, _item, FILTER_TRACE_FN) +#undef __field_packed +#define __field_packed(_type, _item) __field_ext_packed(_type, _item, FILTER_OTHER) + #undef __field_desc #define __field_desc(_type, _container, _item) __field_ext(_type, _item, FILTER_OTHER) -#undef __field_packed -#define __field_packed(_type, _container, _item) __field_ext_packed(_type, _item, FILTER_OTHER) +#undef __field_desc_packed +#define __field_desc_packed(_type, _container, _item) __field_ext_packed(_type, _item, FILTER_OTHER) #undef __array #define __array(_type, _item, _len) { \ @@ -146,11 +152,14 @@ static struct trace_event_fields ftrace_event_fields_##name[] = { \ #undef __field_fn #define __field_fn(type, item) +#undef __field_packed +#define __field_packed(type, item) + #undef __field_desc #define __field_desc(type, container, item) -#undef __field_packed -#define __field_packed(type, container, item) +#undef __field_desc_packed +#define __field_desc_packed(type, container, item) #undef __array #define __array(type, item, len) From e0bd226804f8e0098711042c93d64f3b720b36c0 Mon Sep 17 00:00:00 2001 From: Moon Hee Lee Date: Mon, 15 Dec 2025 19:59:32 -0800 Subject: [PATCH 3025/3902] wifi: mac80211: ocb: skip rx_no_sta when interface is not joined [ Upstream commit ff4071c60018a668249dc6a2df7d16330543540e ] ieee80211_ocb_rx_no_sta() assumes a valid channel context, which is only present after JOIN_OCB. RX may run before JOIN_OCB is executed, in which case the OCB interface is not operational. Skip RX peer handling when the interface is not joined to avoid warnings in the RX path. Reported-by: syzbot+b364457b2d1d4e4a3054@syzkaller.appspotmail.com Closes: https://syzkaller.appspot.com/bug?extid=b364457b2d1d4e4a3054 Tested-by: syzbot+b364457b2d1d4e4a3054@syzkaller.appspotmail.com Signed-off-by: Moon Hee Lee Link: https://patch.msgid.link/20251216035932.18332-1-moonhee.lee.ca@gmail.com Signed-off-by: Johannes Berg Signed-off-by: Sasha Levin --- net/mac80211/ocb.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/net/mac80211/ocb.c b/net/mac80211/ocb.c index a5d4358f122ae4..ebb4f4d88c237f 100644 --- a/net/mac80211/ocb.c +++ b/net/mac80211/ocb.c @@ -47,6 +47,9 @@ void ieee80211_ocb_rx_no_sta(struct ieee80211_sub_if_data *sdata, struct sta_info *sta; int band; + if (!ifocb->joined) + return; + /* XXX: Consider removing the least recently used entry and * allow new one to be added. */ From 689a7980e4788e13e766763d53569fb78dea2513 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Peter=20=C3=85strand?= Date: Wed, 3 Dec 2025 08:57:08 +0100 Subject: [PATCH 3026/3902] wifi: wlcore: ensure skb headroom before skb_push [ Upstream commit e75665dd096819b1184087ba5718bd93beafff51 ] This avoids occasional skb_under_panic Oops from wl1271_tx_work. In this case, headroom is less than needed (typically 110 - 94 = 16 bytes). Signed-off-by: Peter Astrand Link: https://patch.msgid.link/097bd417-e1d7-acd4-be05-47b199075013@lysator.liu.se Signed-off-by: Johannes Berg Signed-off-by: Sasha Levin --- drivers/net/wireless/ti/wlcore/tx.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/drivers/net/wireless/ti/wlcore/tx.c b/drivers/net/wireless/ti/wlcore/tx.c index 464587d16ab20d..f251627c24c6ea 100644 --- a/drivers/net/wireless/ti/wlcore/tx.c +++ b/drivers/net/wireless/ti/wlcore/tx.c @@ -207,6 +207,11 @@ static int wl1271_tx_allocate(struct wl1271 *wl, struct wl12xx_vif *wlvif, total_blocks = wlcore_hw_calc_tx_blocks(wl, total_len, spare_blocks); if (total_blocks <= wl->tx_blocks_available) { + if (skb_headroom(skb) < (total_len - skb->len) && + pskb_expand_head(skb, (total_len - skb->len), 0, GFP_ATOMIC)) { + wl1271_free_tx_id(wl, id); + return -EAGAIN; + } desc = skb_push(skb, total_len - skb->len); wlcore_hw_set_tx_desc_blocks(wl, desc, total_blocks, From 10d3ff7e5812c8d70300f6fa8f524009a06aa7e1 Mon Sep 17 00:00:00 2001 From: Johannes Berg Date: Tue, 2 Dec 2025 10:25:11 +0100 Subject: [PATCH 3027/3902] wifi: mac80211: don't WARN for connections on invalid channels [ Upstream commit 99067b58a408a384d2a45c105eb3dce980a862ce ] It's not clear (to me) how exactly syzbot managed to hit this, but it seems conceivable that e.g. regulatory changed and has disabled a channel between scanning (channel is checked to be usable by cfg80211_get_ies_channel_number) and connecting on the channel later. With one scenario that isn't covered elsewhere described above, the warning isn't good, replace it with a (more informative) error message. Reported-by: syzbot+639af5aa411f2581ad38@syzkaller.appspotmail.com Link: https://patch.msgid.link/20251202102511.5a8fb5184fa3.I961ee41b8f10538a54b8565dbf03ec1696e80e03@changeid Signed-off-by: Johannes Berg Signed-off-by: Sasha Levin --- net/mac80211/mlme.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/net/mac80211/mlme.c b/net/mac80211/mlme.c index dca47a533392ac..8ba199cd38c0fe 100644 --- a/net/mac80211/mlme.c +++ b/net/mac80211/mlme.c @@ -1126,7 +1126,10 @@ ieee80211_determine_chan_mode(struct ieee80211_sub_if_data *sdata, while (!ieee80211_chandef_usable(sdata, &chanreq->oper, IEEE80211_CHAN_DISABLED)) { - if (WARN_ON(chanreq->oper.width == NL80211_CHAN_WIDTH_20_NOHT)) { + if (chanreq->oper.width == NL80211_CHAN_WIDTH_20_NOHT) { + link_id_info(sdata, link_id, + "unusable channel (%d MHz) for connection\n", + chanreq->oper.chan->center_freq); ret = -EINVAL; goto free; } From 6b7c60feab3c6ff6ef9224d258c543fdd942c235 Mon Sep 17 00:00:00 2001 From: Ethan Nelson-Moore Date: Wed, 10 Dec 2025 22:24:51 -0800 Subject: [PATCH 3028/3902] net: usb: sr9700: support devices with virtual driver CD [ Upstream commit bf4172bd870c3a34d3065cbb39192c22cbd7b18d ] Some SR9700 devices have an SPI flash chip containing a virtual driver CD, in which case they appear as a device with two interfaces and product ID 0x9702. Interface 0 is the driver CD and interface 1 is the Ethernet device. Link: https://github.com/name-kurniawan/usb-lan Link: https://www.draisberghof.de/usb_modeswitch/bb/viewtopic.php?t=2185 Signed-off-by: Ethan Nelson-Moore Link: https://patch.msgid.link/20251211062451.139036-1-enelsonmoore@gmail.com [pabeni@redhat.com: fixes link tags] Signed-off-by: Paolo Abeni Signed-off-by: Sasha Levin --- drivers/net/usb/sr9700.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/drivers/net/usb/sr9700.c b/drivers/net/usb/sr9700.c index 5d97e95a17b0df..820c4c50697921 100644 --- a/drivers/net/usb/sr9700.c +++ b/drivers/net/usb/sr9700.c @@ -539,6 +539,11 @@ static const struct usb_device_id products[] = { USB_DEVICE(0x0fe6, 0x9700), /* SR9700 device */ .driver_info = (unsigned long)&sr9700_driver_info, }, + { + /* SR9700 with virtual driver CD-ROM - interface 0 is the CD-ROM device */ + USB_DEVICE_INTERFACE_NUMBER(0x0fe6, 0x9702, 1), + .driver_info = (unsigned long)&sr9700_driver_info, + }, {}, /* END */ }; From ff6892ea544c4052dd5799f675ebc20419953801 Mon Sep 17 00:00:00 2001 From: Yao Zi Date: Thu, 4 Dec 2025 12:32:04 +0000 Subject: [PATCH 3029/3902] wifi: iwlwifi: Implement settime64 as stub for MVM/MLD PTP [ Upstream commit 81d90d93d22ca4f61833cba921dce9a0bd82218f ] Since commit dfb073d32cac ("ptp: Return -EINVAL on ptp_clock_register if required ops are NULL"), PTP clock registered through ptp_clock_register is required to have ptp_clock_info.settime64 set, however, neither MVM nor MLD's PTP clock implementation sets it, resulting in warnings when the interface starts up, like WARNING: drivers/ptp/ptp_clock.c:325 at ptp_clock_register+0x2c8/0x6b8, CPU#1: wpa_supplicant/469 CPU: 1 UID: 0 PID: 469 Comm: wpa_supplicant Not tainted 6.18.0+ #101 PREEMPT(full) ra: ffff800002732cd4 iwl_mvm_ptp_init+0x114/0x188 [iwlmvm] ERA: 9000000002fdc468 ptp_clock_register+0x2c8/0x6b8 iwlwifi 0000:01:00.0: Failed to register PHC clock (-22) I don't find an appropriate firmware interface to implement settime64() for iwlwifi MLD/MVM, thus instead create a stub that returns -EOPTNOTSUPP only, suppressing the warning and allowing the PTP clock to be registered. Reported-by: Nathan Chancellor Closes: https://lore.kernel.org/all/20251108044822.GA3262936@ax162/ Signed-off-by: Yao Zi Tested-by: Nathan Chancellor Reviewed-by: Simon Horman tested-by: damian Tometzki damian@riscv-rocks.de Tested-by: Oliver Hartkopp Acked-by: Miri Korenblit Link: https://patch.msgid.link/20251204123204.9316-1-ziyao@disroot.org Signed-off-by: Johannes Berg Signed-off-by: Sasha Levin --- drivers/net/wireless/intel/iwlwifi/mld/ptp.c | 7 +++++++ drivers/net/wireless/intel/iwlwifi/mvm/ptp.c | 7 +++++++ 2 files changed, 14 insertions(+) diff --git a/drivers/net/wireless/intel/iwlwifi/mld/ptp.c b/drivers/net/wireless/intel/iwlwifi/mld/ptp.c index ffeb37a7f830eb..231920425c066e 100644 --- a/drivers/net/wireless/intel/iwlwifi/mld/ptp.c +++ b/drivers/net/wireless/intel/iwlwifi/mld/ptp.c @@ -121,6 +121,12 @@ static int iwl_mld_ptp_gettime(struct ptp_clock_info *ptp, return 0; } +static int iwl_mld_ptp_settime(struct ptp_clock_info *ptp, + const struct timespec64 *ts) +{ + return -EOPNOTSUPP; +} + static int iwl_mld_ptp_adjtime(struct ptp_clock_info *ptp, s64 delta) { struct iwl_mld *mld = container_of(ptp, struct iwl_mld, @@ -279,6 +285,7 @@ void iwl_mld_ptp_init(struct iwl_mld *mld) mld->ptp_data.ptp_clock_info.owner = THIS_MODULE; mld->ptp_data.ptp_clock_info.gettime64 = iwl_mld_ptp_gettime; + mld->ptp_data.ptp_clock_info.settime64 = iwl_mld_ptp_settime; mld->ptp_data.ptp_clock_info.max_adj = 0x7fffffff; mld->ptp_data.ptp_clock_info.adjtime = iwl_mld_ptp_adjtime; mld->ptp_data.ptp_clock_info.adjfine = iwl_mld_ptp_adjfine; diff --git a/drivers/net/wireless/intel/iwlwifi/mvm/ptp.c b/drivers/net/wireless/intel/iwlwifi/mvm/ptp.c index 06a4c9f74797a4..ad156b82eaa943 100644 --- a/drivers/net/wireless/intel/iwlwifi/mvm/ptp.c +++ b/drivers/net/wireless/intel/iwlwifi/mvm/ptp.c @@ -220,6 +220,12 @@ static int iwl_mvm_ptp_gettime(struct ptp_clock_info *ptp, return 0; } +static int iwl_mvm_ptp_settime(struct ptp_clock_info *ptp, + const struct timespec64 *ts) +{ + return -EOPNOTSUPP; +} + static int iwl_mvm_ptp_adjtime(struct ptp_clock_info *ptp, s64 delta) { struct iwl_mvm *mvm = container_of(ptp, struct iwl_mvm, @@ -281,6 +287,7 @@ void iwl_mvm_ptp_init(struct iwl_mvm *mvm) mvm->ptp_data.ptp_clock_info.adjfine = iwl_mvm_ptp_adjfine; mvm->ptp_data.ptp_clock_info.adjtime = iwl_mvm_ptp_adjtime; mvm->ptp_data.ptp_clock_info.gettime64 = iwl_mvm_ptp_gettime; + mvm->ptp_data.ptp_clock_info.settime64 = iwl_mvm_ptp_settime; mvm->ptp_data.scaled_freq = SCALE_FACTOR; /* Give a short 'friendly name' to identify the PHC clock */ From 86acdc17919444319504c5fbf07eb6fbbe52641e Mon Sep 17 00:00:00 2001 From: Dmytro Bagrii Date: Fri, 28 Nov 2025 18:15:23 +0200 Subject: [PATCH 3030/3902] platform/x86: dell-lis3lv02d: Add Latitude 5400 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [ Upstream commit a5b9fdd33c59a964a26d12c39b636ef85a25b074 ] Add accelerometer address 0x29 for Dell Latitude 5400. The address is verified as below: $ cat /sys/class/dmi/id/product_name Latitude 5400 $ grep -H '' /sys/bus/pci/drivers/i801_smbus/0000\:00*/i2c-*/name /sys/bus/pci/drivers/i801_smbus/0000:00:1f.4/i2c-10/name:SMBus I801 adapter at 0000:00:1f.4 $ i2cdetect 10 WARNING! This program can confuse your I2C bus, cause data loss and worse! I will probe file /dev/i2c-10. I will probe address range 0x08-0x77. Continue? [Y/n] Y 0 1 2 3 4 5 6 7 8 9 a b c d e f 00: 08 -- -- -- -- -- -- -- 10: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 20: -- -- -- -- -- -- -- -- -- UU -- -- -- -- -- -- 30: 30 -- -- -- -- 35 UU UU -- -- -- -- -- -- -- -- 40: -- -- -- -- 44 -- -- -- -- -- -- -- -- -- -- -- 50: UU -- 52 -- -- -- -- -- -- -- -- -- -- -- -- -- 60: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 70: -- -- -- -- -- -- -- -- $ xargs -n1 -a /proc/cmdline | grep ^dell_lis3lv02d dell_lis3lv02d.probe_i2c_addr=1 $ dmesg | grep lis3lv02d ... [ 206.012411] i2c i2c-10: Probing for lis3lv02d on address 0x29 [ 206.013727] i2c i2c-10: Detected lis3lv02d on address 0x29, please report this upstream to platform-driver-x86@vger.kernel.org so that a quirk can be added [ 206.240841] lis3lv02d_i2c 10-0029: supply Vdd not found, using dummy regulator [ 206.240868] lis3lv02d_i2c 10-0029: supply Vdd_IO not found, using dummy regulator [ 206.261258] lis3lv02d: 8 bits 3DC sensor found [ 206.346722] input: ST LIS3LV02DL Accelerometer as /devices/faux/lis3lv02d/input/input17 $ cat /sys/class/input/input17/name ST LIS3LV02DL Accelerometer Signed-off-by: Dmytro Bagrii Reviewed-by: Hans de Goede Link: https://patch.msgid.link/20251128161523.6224-1-dimich.dmb@gmail.com Reviewed-by: Ilpo Järvinen Signed-off-by: Ilpo Järvinen Signed-off-by: Sasha Levin --- drivers/platform/x86/dell/dell-lis3lv02d.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/platform/x86/dell/dell-lis3lv02d.c b/drivers/platform/x86/dell/dell-lis3lv02d.c index 77905a9ddde9dd..fe52bcd896f78f 100644 --- a/drivers/platform/x86/dell/dell-lis3lv02d.c +++ b/drivers/platform/x86/dell/dell-lis3lv02d.c @@ -44,6 +44,7 @@ static const struct dmi_system_id lis3lv02d_devices[] __initconst = { /* * Additional individual entries were added after verification. */ + DELL_LIS3LV02D_DMI_ENTRY("Latitude 5400", 0x29), DELL_LIS3LV02D_DMI_ENTRY("Latitude 5480", 0x29), DELL_LIS3LV02D_DMI_ENTRY("Latitude 5500", 0x29), DELL_LIS3LV02D_DMI_ENTRY("Latitude E6330", 0x29), From f309b2c7df659ecd3e6861304352ae405abaa307 Mon Sep 17 00:00:00 2001 From: shechenglong Date: Sun, 28 Dec 2025 21:04:26 +0800 Subject: [PATCH 3031/3902] block,bfq: fix aux stat accumulation destination [ Upstream commit 04bdb1a04d8a2a89df504c1e34250cd3c6e31a1c ] Route bfqg_stats_add_aux() time accumulation into the destination stats object instead of the source, aligning with other stat fields. Reviewed-by: Yu Kuai Signed-off-by: shechenglong Signed-off-by: Jens Axboe Signed-off-by: Sasha Levin --- block/bfq-cgroup.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/block/bfq-cgroup.c b/block/bfq-cgroup.c index 9fb9f353315025..6a75fe1c7a5c0e 100644 --- a/block/bfq-cgroup.c +++ b/block/bfq-cgroup.c @@ -380,7 +380,7 @@ static void bfqg_stats_add_aux(struct bfqg_stats *to, struct bfqg_stats *from) blkg_rwstat_add_aux(&to->merged, &from->merged); blkg_rwstat_add_aux(&to->service_time, &from->service_time); blkg_rwstat_add_aux(&to->wait_time, &from->wait_time); - bfq_stat_add_aux(&from->time, &from->time); + bfq_stat_add_aux(&to->time, &from->time); bfq_stat_add_aux(&to->avg_queue_size_sum, &from->avg_queue_size_sum); bfq_stat_add_aux(&to->avg_queue_size_samples, &from->avg_queue_size_samples); From fdda836fcee6fdbcccc24e3679097efb583f581f Mon Sep 17 00:00:00 2001 From: ZhangGuoDong Date: Sun, 28 Dec 2025 22:51:01 +0800 Subject: [PATCH 3032/3902] smb/server: call ksmbd_session_rpc_close() on error path in create_smb2_pipe() [ Upstream commit 7c28f8eef5ac5312794d8a52918076dcd787e53b ] When ksmbd_iov_pin_rsp() fails, we should call ksmbd_session_rpc_close(). Signed-off-by: ZhangGuoDong Signed-off-by: ChenXiaoSong Acked-by: Namjae Jeon Signed-off-by: Steve French Signed-off-by: Sasha Levin --- fs/smb/server/smb2pdu.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/fs/smb/server/smb2pdu.c b/fs/smb/server/smb2pdu.c index 2b59c282cda595..10d58e3094423b 100644 --- a/fs/smb/server/smb2pdu.c +++ b/fs/smb/server/smb2pdu.c @@ -2291,7 +2291,7 @@ static noinline int create_smb2_pipe(struct ksmbd_work *work) { struct smb2_create_rsp *rsp; struct smb2_create_req *req; - int id; + int id = -1; int err; char *name; @@ -2348,6 +2348,9 @@ static noinline int create_smb2_pipe(struct ksmbd_work *work) break; } + if (id >= 0) + ksmbd_session_rpc_close(work->sess, id); + if (!IS_ERR(name)) kfree(name); From 9531210f348aa78e260a9e5b0d1a6f7e7aa329e6 Mon Sep 17 00:00:00 2001 From: Huacai Chen Date: Wed, 31 Dec 2025 15:19:10 +0800 Subject: [PATCH 3033/3902] LoongArch: Set correct protection_map[] for VM_NONE/VM_SHARED [ Upstream commit d5be446948b379f1d1a8e7bc6656d13f44c5c7b1 ] For 32BIT platform _PAGE_PROTNONE is 0, so set a VMA to be VM_NONE or VM_SHARED will make pages non-present, then cause Oops with kernel page fault. Fix it by set correct protection_map[] for VM_NONE/VM_SHARED, replacing _PAGE_PROTNONE with _PAGE_PRESENT. Signed-off-by: Huacai Chen Signed-off-by: Sasha Levin --- arch/loongarch/mm/cache.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/arch/loongarch/mm/cache.c b/arch/loongarch/mm/cache.c index 6be04d36ca0769..496916845ff764 100644 --- a/arch/loongarch/mm/cache.c +++ b/arch/loongarch/mm/cache.c @@ -160,8 +160,8 @@ void cpu_cache_init(void) static const pgprot_t protection_map[16] = { [VM_NONE] = __pgprot(_CACHE_CC | _PAGE_USER | - _PAGE_PROTNONE | _PAGE_NO_EXEC | - _PAGE_NO_READ), + _PAGE_NO_EXEC | _PAGE_NO_READ | + (_PAGE_PROTNONE ? : _PAGE_PRESENT)), [VM_READ] = __pgprot(_CACHE_CC | _PAGE_VALID | _PAGE_USER | _PAGE_PRESENT | _PAGE_NO_EXEC), @@ -180,8 +180,8 @@ static const pgprot_t protection_map[16] = { [VM_EXEC | VM_WRITE | VM_READ] = __pgprot(_CACHE_CC | _PAGE_VALID | _PAGE_USER | _PAGE_PRESENT), [VM_SHARED] = __pgprot(_CACHE_CC | _PAGE_USER | - _PAGE_PROTNONE | _PAGE_NO_EXEC | - _PAGE_NO_READ), + _PAGE_NO_EXEC | _PAGE_NO_READ | + (_PAGE_PROTNONE ? : _PAGE_PRESENT)), [VM_SHARED | VM_READ] = __pgprot(_CACHE_CC | _PAGE_VALID | _PAGE_USER | _PAGE_PRESENT | _PAGE_NO_EXEC), From 0107b18cd8ac17eb3e54786adc05a85cdbb6ef22 Mon Sep 17 00:00:00 2001 From: FengWei Shih Date: Fri, 26 Dec 2025 18:18:16 +0800 Subject: [PATCH 3034/3902] md: suspend array while updating raid_disks via sysfs [ Upstream commit 2cc583653bbe050bacd1cadcc9776d39bf449740 ] In raid1_reshape(), freeze_array() is called before modifying the r1bio memory pool (conf->r1bio_pool) and conf->raid_disks, and unfreeze_array() is called after the update is completed. However, freeze_array() only waits until nr_sync_pending and (nr_pending - nr_queued) of all buckets reaches zero. When an I/O error occurs, nr_queued is increased and the corresponding r1bio is queued to either retry_list or bio_end_io_list. As a result, freeze_array() may unblock before these r1bios are released. This can lead to a situation where conf->raid_disks and the mempool have already been updated while queued r1bios, allocated with the old raid_disks value, are later released. Consequently, free_r1bio() may access memory out of bounds in put_all_bios() and release r1bios of the wrong size to the new mempool, potentially causing issues with the mempool as well. Since only normal I/O might increase nr_queued while an I/O error occurs, suspending the array avoids this issue. Note: Updating raid_disks via ioctl SET_ARRAY_INFO already suspends the array. Therefore, we suspend the array when updating raid_disks via sysfs to avoid this issue too. Signed-off-by: FengWei Shih Link: https://lore.kernel.org/linux-raid/20251226101816.4506-1-dannyshih@synology.com Signed-off-by: Yu Kuai Signed-off-by: Sasha Levin --- drivers/md/md.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/md/md.c b/drivers/md/md.c index 7b1365143f58d3..e04ddcb03981c9 100644 --- a/drivers/md/md.c +++ b/drivers/md/md.c @@ -4396,7 +4396,7 @@ raid_disks_store(struct mddev *mddev, const char *buf, size_t len) if (err < 0) return err; - err = mddev_lock(mddev); + err = mddev_suspend_and_lock(mddev); if (err) return err; if (mddev->pers) @@ -4421,7 +4421,7 @@ raid_disks_store(struct mddev *mddev, const char *buf, size_t len) } else mddev->raid_disks = n; out_unlock: - mddev_unlock(mddev); + mddev_unlock_and_resume(mddev); return err ? err : len; } static struct md_sysfs_entry md_raid_disks = From 4665e52bde3b1f8f442895ce7d88fa62a43e48c4 Mon Sep 17 00:00:00 2001 From: ZhangGuoDong Date: Mon, 29 Dec 2025 11:15:18 +0800 Subject: [PATCH 3035/3902] smb/server: fix refcount leak in smb2_open() [ Upstream commit f416c556997aa56ec4384c6b6efd6a0e6ac70aa7 ] When ksmbd_vfs_getattr() fails, the reference count of ksmbd_file must be released. Suggested-by: Namjae Jeon Signed-off-by: ZhangGuoDong Signed-off-by: ChenXiaoSong Acked-by: Namjae Jeon Signed-off-by: Steve French Signed-off-by: Sasha Levin --- fs/smb/server/smb2pdu.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fs/smb/server/smb2pdu.c b/fs/smb/server/smb2pdu.c index 10d58e3094423b..244a5665f26df4 100644 --- a/fs/smb/server/smb2pdu.c +++ b/fs/smb/server/smb2pdu.c @@ -3019,10 +3019,10 @@ int smb2_open(struct ksmbd_work *work) file_info = FILE_OPENED; rc = ksmbd_vfs_getattr(&fp->filp->f_path, &stat); + ksmbd_put_durable_fd(fp); if (rc) goto err_out2; - ksmbd_put_durable_fd(fp); goto reconnected_fp; } } else if (req_op_level == SMB2_OPLOCK_LEVEL_LEASE) From 2bb9c8a77df5074fe19f3774f5d0a56dbda824cc Mon Sep 17 00:00:00 2001 From: Alexandre Negrel Date: Tue, 30 Dec 2025 19:57:28 +0100 Subject: [PATCH 3036/3902] io_uring: use GFP_NOWAIT for overflow CQEs on legacy rings [ Upstream commit fc5ff2500976cd2710a7acecffd12d95ee4f98fc ] Allocate the overflowing CQE with GFP_NOWAIT instead of GFP_ATOMIC. This changes causes allocations to fail earlier in out-of-memory situations, rather than being deferred. Using GFP_ATOMIC allows a process to exceed memory limits. Closes: https://bugzilla.kernel.org/show_bug.cgi?id=220794 Signed-off-by: Alexandre Negrel Link: https://lore.kernel.org/io-uring/20251229201933.515797-1-alexandre@negrel.dev/ Signed-off-by: Jens Axboe Signed-off-by: Sasha Levin --- io_uring/io_uring.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/io_uring/io_uring.c b/io_uring/io_uring.c index e97c495c180656..104192bcc8e4bf 100644 --- a/io_uring/io_uring.c +++ b/io_uring/io_uring.c @@ -897,7 +897,7 @@ static __cold bool io_cqe_overflow_locked(struct io_ring_ctx *ctx, { struct io_overflow_cqe *ocqe; - ocqe = io_alloc_ocqe(ctx, cqe, big_cqe, GFP_ATOMIC); + ocqe = io_alloc_ocqe(ctx, cqe, big_cqe, GFP_NOWAIT); return io_cqring_add_overflow(ctx, ocqe); } From c2ed4f71e9288f21d5c53ff790270758e60fa5f9 Mon Sep 17 00:00:00 2001 From: Chenghao Duan Date: Wed, 31 Dec 2025 15:19:20 +0800 Subject: [PATCH 3037/3902] LoongArch: Enable exception fixup for specific ADE subcode MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [ Upstream commit 9bdc1ab5e4ce6f066119018d8f69631a46f9c5a0 ] This patch allows the LoongArch BPF JIT to handle recoverable memory access errors generated by BPF_PROBE_MEM* instructions. When a BPF program performs memory access operations, the instructions it executes may trigger ADEM exceptions. The kernel’s built-in BPF exception table mechanism (EX_TYPE_BPF) will generate corresponding exception fixup entries in the JIT compilation phase; however, the architecture-specific trap handling function needs to proactively call the common fixup routine to achieve exception recovery. do_ade(): fix EX_TYPE_BPF memory access exceptions for BPF programs, ensure safe execution. Relevant test cases: illegal address access tests in module_attach and subprogs_extable of selftests/bpf. Signed-off-by: Chenghao Duan Signed-off-by: Huacai Chen Signed-off-by: Sasha Levin --- arch/loongarch/kernel/traps.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/arch/loongarch/kernel/traps.c b/arch/loongarch/kernel/traps.c index da5926fead4af9..8e51ce004572ca 100644 --- a/arch/loongarch/kernel/traps.c +++ b/arch/loongarch/kernel/traps.c @@ -535,10 +535,15 @@ asmlinkage void noinstr do_fpe(struct pt_regs *regs, unsigned long fcsr) asmlinkage void noinstr do_ade(struct pt_regs *regs) { irqentry_state_t state = irqentry_enter(regs); + unsigned int esubcode = FIELD_GET(CSR_ESTAT_ESUBCODE, regs->csr_estat); + + if ((esubcode == EXSUBCODE_ADEM) && fixup_exception(regs)) + goto out; die_if_kernel("Kernel ade access", regs); force_sig_fault(SIGBUS, BUS_ADRERR, (void __user *)regs->csr_badvaddr); +out: irqentry_exit(regs, state); } From 70dd3513ed6ac8c6cab23f72c5b19f44ca89de9d Mon Sep 17 00:00:00 2001 From: ZhangGuoDong Date: Mon, 29 Dec 2025 10:13:29 +0800 Subject: [PATCH 3038/3902] smb/server: fix refcount leak in parse_durable_handle_context() [ Upstream commit 3296c3012a9d9a27e81e34910384e55a6ff3cff0 ] When the command is a replay operation and -ENOEXEC is returned, the refcount of ksmbd_file must be released. Signed-off-by: ZhangGuoDong Signed-off-by: ChenXiaoSong Acked-by: Namjae Jeon Signed-off-by: Steve French Signed-off-by: Sasha Levin --- fs/smb/server/smb2pdu.c | 1 + 1 file changed, 1 insertion(+) diff --git a/fs/smb/server/smb2pdu.c b/fs/smb/server/smb2pdu.c index 244a5665f26df4..470b274f4cc98a 100644 --- a/fs/smb/server/smb2pdu.c +++ b/fs/smb/server/smb2pdu.c @@ -2822,6 +2822,7 @@ static int parse_durable_handle_context(struct ksmbd_work *work, SMB2_CLIENT_GUID_SIZE)) { if (!(req->hdr.Flags & SMB2_FLAGS_REPLAY_OPERATION)) { err = -ENOEXEC; + ksmbd_put_durable_fd(dh_info->fp); goto out; } From 6de3a371a8b9fd095198b1aa68c22cc10a4c6961 Mon Sep 17 00:00:00 2001 From: Filipe Manana Date: Fri, 12 Dec 2025 17:10:10 +0000 Subject: [PATCH 3039/3902] btrfs: do not free data reservation in fallback from inline due to -ENOSPC [ Upstream commit f8da41de0bff9eb1d774a7253da0c9f637c4470a ] If we fail to create an inline extent due to -ENOSPC, we will attempt to go through the normal COW path, reserve an extent, create an ordered extent, etc. However we were always freeing the reserved qgroup data, which is wrong since we will use data. Fix this by freeing the reserved qgroup data in __cow_file_range_inline() only if we are not doing the fallback (ret is <= 0). Reviewed-by: Qu Wenruo Signed-off-by: Filipe Manana Reviewed-by: David Sterba Signed-off-by: David Sterba Signed-off-by: Sasha Levin --- fs/btrfs/inode.c | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/fs/btrfs/inode.c b/fs/btrfs/inode.c index 1af9b05328ce86..e72c69f77ce4b5 100644 --- a/fs/btrfs/inode.c +++ b/fs/btrfs/inode.c @@ -670,8 +670,12 @@ static noinline int __cow_file_range_inline(struct btrfs_inode *inode, * it won't count as data extent, free them directly here. * And at reserve time, it's always aligned to page size, so * just free one page here. + * + * If we fallback to non-inline (ret == 1) due to -ENOSPC, then we need + * to keep the data reservation. */ - btrfs_qgroup_free_data(inode, NULL, 0, fs_info->sectorsize, NULL); + if (ret <= 0) + btrfs_qgroup_free_data(inode, NULL, 0, fs_info->sectorsize, NULL); btrfs_free_path(path); btrfs_end_transaction(trans); return ret; From ea5ef771f3c26cba0875cefcf7a4d8b5d8aee9f4 Mon Sep 17 00:00:00 2001 From: Zhang Lixu Date: Wed, 10 Dec 2025 10:53:28 +0800 Subject: [PATCH 3040/3902] HID: intel-ish-hid: Update ishtp bus match to support device ID table [ Upstream commit daeed86b686855adda79f13729e0c9b0530990be ] The ishtp_cl_bus_match() function previously only checked the first entry in the driver's device ID table. Update it to iterate over the entire table, allowing proper matching for drivers with multiple supported protocol GUIDs. Signed-off-by: Zhang Lixu Acked-by: Srinivas Pandruvada Signed-off-by: Benjamin Tissoires Signed-off-by: Sasha Levin --- drivers/hid/intel-ish-hid/ishtp/bus.c | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/drivers/hid/intel-ish-hid/ishtp/bus.c b/drivers/hid/intel-ish-hid/ishtp/bus.c index c6ce37244e4975..c3915f3a060ead 100644 --- a/drivers/hid/intel-ish-hid/ishtp/bus.c +++ b/drivers/hid/intel-ish-hid/ishtp/bus.c @@ -240,9 +240,17 @@ static int ishtp_cl_bus_match(struct device *dev, const struct device_driver *dr { struct ishtp_cl_device *device = to_ishtp_cl_device(dev); struct ishtp_cl_driver *driver = to_ishtp_cl_driver(drv); + struct ishtp_fw_client *client = device->fw_client; + const struct ishtp_device_id *id; - return(device->fw_client ? guid_equal(&driver->id[0].guid, - &device->fw_client->props.protocol_name) : 0); + if (client) { + for (id = driver->id; !guid_is_null(&id->guid); id++) { + if (guid_equal(&id->guid, &client->props.protocol_name)) + return 1; + } + } + + return 0; } /** From a206870513cda25e05ef473d3b73b6c1e3fbd4ed Mon Sep 17 00:00:00 2001 From: DaytonCL Date: Sun, 14 Dec 2025 14:34:36 +0100 Subject: [PATCH 3041/3902] HID: multitouch: add MT_QUIRK_STICKY_FINGERS to MT_CLS_VTL [ Upstream commit ff3f234ff1dcd6d626a989151db067a1b7f0f215 ] Some VTL-class touchpads (e.g. TOPS0102:00 35CC:0104) intermittently fail to release a finger contact. A previous slot remains logically active, accompanied by stale BTN_TOOL_DOUBLETAP state, causing gestures to stay latched and resulting in stuck two-finger scrolling and false right-clicks. Apply MT_QUIRK_STICKY_FINGERS to handle the unreleased contact correctly. Link: https://gitlab.freedesktop.org/libinput/libinput/-/issues/1225 Suggested-by: Benjamin Tissoires Tested-by: DaytonCL Signed-off-by: DaytonCL Signed-off-by: Benjamin Tissoires Signed-off-by: Sasha Levin --- drivers/hid/hid-multitouch.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/hid/hid-multitouch.c b/drivers/hid/hid-multitouch.c index 179dc316b4b518..a0c1ad5acb6706 100644 --- a/drivers/hid/hid-multitouch.c +++ b/drivers/hid/hid-multitouch.c @@ -393,6 +393,7 @@ static const struct mt_class mt_classes[] = { { .name = MT_CLS_VTL, .quirks = MT_QUIRK_ALWAYS_VALID | MT_QUIRK_CONTACT_CNT_ACCURATE | + MT_QUIRK_STICKY_FINGERS | MT_QUIRK_FORCE_GET_FEATURE, }, { .name = MT_CLS_GOOGLE, From 28768bd3abf9995a93f6e01bfce01c60622964dd Mon Sep 17 00:00:00 2001 From: Filipe Manana Date: Fri, 12 Dec 2025 17:18:25 +0000 Subject: [PATCH 3042/3902] btrfs: fix reservation leak in some error paths when inserting inline extent [ Upstream commit c1c050f92d8f6aac4e17f7f2230160794fceef0c ] If we fail to allocate a path or join a transaction, we return from __cow_file_range_inline() without freeing the reserved qgroup data, resulting in a leak. Fix this by ensuring we call btrfs_qgroup_free_data() in such cases. Signed-off-by: Filipe Manana Reviewed-by: David Sterba Signed-off-by: David Sterba Signed-off-by: Sasha Levin --- fs/btrfs/inode.c | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/fs/btrfs/inode.c b/fs/btrfs/inode.c index e72c69f77ce4b5..76a66c74249a22 100644 --- a/fs/btrfs/inode.c +++ b/fs/btrfs/inode.c @@ -614,19 +614,22 @@ static noinline int __cow_file_range_inline(struct btrfs_inode *inode, struct btrfs_drop_extents_args drop_args = { 0 }; struct btrfs_root *root = inode->root; struct btrfs_fs_info *fs_info = root->fs_info; - struct btrfs_trans_handle *trans; + struct btrfs_trans_handle *trans = NULL; u64 data_len = (compressed_size ?: size); int ret; struct btrfs_path *path; path = btrfs_alloc_path(); - if (!path) - return -ENOMEM; + if (!path) { + ret = -ENOMEM; + goto out; + } trans = btrfs_join_transaction(root); if (IS_ERR(trans)) { - btrfs_free_path(path); - return PTR_ERR(trans); + ret = PTR_ERR(trans); + trans = NULL; + goto out; } trans->block_rsv = &inode->block_rsv; @@ -677,7 +680,8 @@ static noinline int __cow_file_range_inline(struct btrfs_inode *inode, if (ret <= 0) btrfs_qgroup_free_data(inode, NULL, 0, fs_info->sectorsize, NULL); btrfs_free_path(path); - btrfs_end_transaction(trans); + if (trans) + btrfs_end_transaction(trans); return ret; } From 8b44e753795107a22ba31495686e83f4aca48f36 Mon Sep 17 00:00:00 2001 From: Lukas Gerlach Date: Thu, 18 Dec 2025 20:13:32 +0100 Subject: [PATCH 3043/3902] riscv: Sanitize syscall table indexing under speculation [ Upstream commit 25fd7ee7bf58ac3ec7be3c9f82ceff153451946c ] The syscall number is a user-controlled value used to index into the syscall table. Use array_index_nospec() to clamp this value after the bounds check to prevent speculative out-of-bounds access and subsequent data leakage via cache side channels. Signed-off-by: Lukas Gerlach Link: https://patch.msgid.link/20251218191332.35849-3-lukas.gerlach@cispa.de Signed-off-by: Paul Walmsley Signed-off-by: Sasha Levin --- arch/riscv/kernel/traps.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/arch/riscv/kernel/traps.c b/arch/riscv/kernel/traps.c index 80230de167def3..47afea4ff1a8d2 100644 --- a/arch/riscv/kernel/traps.c +++ b/arch/riscv/kernel/traps.c @@ -339,8 +339,10 @@ void do_trap_ecall_u(struct pt_regs *regs) add_random_kstack_offset(); - if (syscall >= 0 && syscall < NR_syscalls) + if (syscall >= 0 && syscall < NR_syscalls) { + syscall = array_index_nospec(syscall, NR_syscalls); syscall_handler(regs, syscall); + } /* * Ultimately, this value will get limited by KSTACK_OFFSET_MAX(), From 7ae5b35148119fdc462f75eb77f019f5a496923a Mon Sep 17 00:00:00 2001 From: Zhang Lixu Date: Fri, 12 Dec 2025 10:51:50 +0800 Subject: [PATCH 3044/3902] HID: intel-ish-hid: Reset enum_devices_done before enumeration [ Upstream commit 56e230723e3a818373bd62331bccb1c6d2b3881b ] Some systems have enabled ISH without any sensors. In this case sending HOSTIF_DM_ENUM_DEVICES results in 0 sensors. This triggers ISH hardware reset on subsequent enumeration after S3/S4 resume. The enum_devices_done flag was not reset before sending the HOSTIF_DM_ENUM_DEVICES command. On subsequent enumeration calls (such as after S3/S4 resume), this flag retains its previous true value, causing the wait loop to be skipped and returning prematurely to hid_ishtp_cl_init(). If 0 HID devices are found, hid_ishtp_cl_init() skips getting HID device descriptors and sets init_done to true. When the delayed enumeration response arrives with init_done already true, the driver treats it as a bad packet and triggers an ISH hardware reset. Set enum_devices_done to false before sending the enumeration command, consistent with similar functions like ishtp_get_hid_descriptor() and ishtp_get_report_descriptor() which reset their respective flags. Signed-off-by: Zhang Lixu Acked-by: Srinivas Pandruvada Signed-off-by: Benjamin Tissoires Signed-off-by: Sasha Levin --- drivers/hid/intel-ish-hid/ishtp-hid-client.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/hid/intel-ish-hid/ishtp-hid-client.c b/drivers/hid/intel-ish-hid/ishtp-hid-client.c index f61add862b6b3f..12a43c64e81560 100644 --- a/drivers/hid/intel-ish-hid/ishtp-hid-client.c +++ b/drivers/hid/intel-ish-hid/ishtp-hid-client.c @@ -495,6 +495,7 @@ static int ishtp_enum_enum_devices(struct ishtp_cl *hid_ishtp_cl) int rv; /* Send HOSTIF_DM_ENUM_DEVICES */ + client_data->enum_devices_done = false; memset(&msg, 0, sizeof(struct hostif_msg)); msg.hdr.command = HOSTIF_DM_ENUM_DEVICES; rv = ishtp_cl_send(hid_ishtp_cl, (unsigned char *)&msg, From 747b9a7d4c712fd84dcf958471d7dd0caed78736 Mon Sep 17 00:00:00 2001 From: Siarhei Vishniakou Date: Tue, 11 Nov 2025 15:45:19 -0800 Subject: [PATCH 3045/3902] HID: playstation: Center initial joystick axes to prevent spurious events [ Upstream commit e9143268d259d98e111a649affa061acb8e13c5b ] When a new PlayStation gamepad (DualShock 4 or DualSense) is initialized, the input subsystem sets the default value for its absolute axes (e.g., ABS_X, ABS_Y) to 0. However, the hardware's actual neutral/resting state for these joysticks is 128 (0x80). This creates a mismatch. When the first HID report arrives from the device, the driver sees the resting value of 128. The kernel compares this to its initial state of 0 and incorrectly interprets this as a delta (0 -> 128). Consequently, it generates EV_ABS events for this initial, non-existent movement. This behavior can fail userspace 'sanity check' tests (e.g., in Android CTS) that correctly assert no motion events should be generated from a device that is already at rest. This patch fixes the issue by explicitly setting the initial value of the main joystick axes (e.g., ABS_X, ABS_Y, ABS_RX, ABS_RY) to 128 (0x80) in the common ps_gamepad_create() function. This aligns the kernel's initial state with the hardware's expected neutral state, ensuring that the first report (at 128) produces no delta and thus, no spurious event. Signed-off-by: Siarhei Vishniakou Reviewed-by: Benjamin Tissoires Signed-off-by: Benjamin Tissoires Signed-off-by: Sasha Levin --- drivers/hid/hid-playstation.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/drivers/hid/hid-playstation.c b/drivers/hid/hid-playstation.c index 128aa6abd10bed..e4dfcf26b04e74 100644 --- a/drivers/hid/hid-playstation.c +++ b/drivers/hid/hid-playstation.c @@ -753,11 +753,16 @@ ps_gamepad_create(struct hid_device *hdev, if (IS_ERR(gamepad)) return ERR_CAST(gamepad); + /* Set initial resting state for joysticks to 128 (center) */ input_set_abs_params(gamepad, ABS_X, 0, 255, 0, 0); + gamepad->absinfo[ABS_X].value = 128; input_set_abs_params(gamepad, ABS_Y, 0, 255, 0, 0); + gamepad->absinfo[ABS_Y].value = 128; input_set_abs_params(gamepad, ABS_Z, 0, 255, 0, 0); input_set_abs_params(gamepad, ABS_RX, 0, 255, 0, 0); + gamepad->absinfo[ABS_RX].value = 128; input_set_abs_params(gamepad, ABS_RY, 0, 255, 0, 0); + gamepad->absinfo[ABS_RY].value = 128; input_set_abs_params(gamepad, ABS_RZ, 0, 255, 0, 0); input_set_abs_params(gamepad, ABS_HAT0X, -1, 1, 0, 0); From 75f1f512b1567f714c465f7d7a7d8373c0044cba Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matou=C5=A1=20L=C3=A1nsk=C3=BD?= Date: Wed, 31 Dec 2025 18:12:07 +0100 Subject: [PATCH 3046/3902] ALSA: hda/realtek: Add quirk for Acer Nitro AN517-55 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [ Upstream commit 9be25402d8522e16e5ebe84f2b1b6c5de082a388 ] Add headset mic quirk for Acer Nitro AN517-55. This laptop uses the same audio configuration as the AN515-58 model. Signed-off-by: Matouš Lánský Link: https://patch.msgid.link/20251231171207.76943-1-matouslansky@post.cz Signed-off-by: Takashi Iwai Signed-off-by: Sasha Levin --- sound/hda/codecs/realtek/alc269.c | 1 + 1 file changed, 1 insertion(+) diff --git a/sound/hda/codecs/realtek/alc269.c b/sound/hda/codecs/realtek/alc269.c index 9097de7d2e3d77..28a390f8636dc2 100644 --- a/sound/hda/codecs/realtek/alc269.c +++ b/sound/hda/codecs/realtek/alc269.c @@ -6239,6 +6239,7 @@ static const struct hda_quirk alc269_fixup_tbl[] = { SND_PCI_QUIRK(0x1025, 0x1466, "Acer Aspire A515-56", ALC255_FIXUP_ACER_HEADPHONE_AND_MIC), SND_PCI_QUIRK(0x1025, 0x1534, "Acer Predator PH315-54", ALC255_FIXUP_ACER_MIC_NO_PRESENCE), SND_PCI_QUIRK(0x1025, 0x159c, "Acer Nitro 5 AN515-58", ALC2XX_FIXUP_HEADSET_MIC), + SND_PCI_QUIRK(0x1025, 0x1597, "Acer Nitro 5 AN517-55", ALC2XX_FIXUP_HEADSET_MIC), SND_PCI_QUIRK(0x1025, 0x169a, "Acer Swift SFG16", ALC256_FIXUP_ACER_SFG16_MICMUTE_LED), SND_PCI_QUIRK(0x1025, 0x1826, "Acer Helios ZPC", ALC287_FIXUP_PREDATOR_SPK_CS35L41_I2C_2), SND_PCI_QUIRK(0x1025, 0x182c, "Acer Helios ZPD", ALC287_FIXUP_PREDATOR_SPK_CS35L41_I2C_2), From 5b9bbe3d7bb9014ca58d1727f8605d9bbb222c80 Mon Sep 17 00:00:00 2001 From: Brendan Jackman Date: Tue, 16 Dec 2025 10:16:36 +0000 Subject: [PATCH 3047/3902] x86/sev: Disable GCOV on noinstr object [ Upstream commit 9efb74f84ba82a9de81fc921baf3c5e2decf8256 ] With Debian clang version 19.1.7 (3+build5) there are calls to kasan_check_write() from __sev_es_nmi_complete(), which violates noinstr. Fix it by disabling GCOV for the noinstr object, as has been done for previous such instrumentation issues. Note that this file already disables __SANITIZE_ADDRESS__ and __SANITIZE_THREAD__, thus calls like kasan_check_write() ought to be nops regardless of GCOV. This has been fixed in other patches. However, to avoid any other accidental instrumentation showing up, (and since, in principle GCOV is instrumentation and hence should be disabled for noinstr code anyway), disable GCOV overall as well. Signed-off-by: Brendan Jackman Signed-off-by: Borislav Petkov (AMD) Acked-by: Marco Elver Link: https://patch.msgid.link/20251216-gcov-inline-noinstr-v3-3-10244d154451@google.com Signed-off-by: Sasha Levin --- arch/x86/coco/sev/Makefile | 2 ++ 1 file changed, 2 insertions(+) diff --git a/arch/x86/coco/sev/Makefile b/arch/x86/coco/sev/Makefile index 3b8ae214a6a64d..b2e9ec2f69014f 100644 --- a/arch/x86/coco/sev/Makefile +++ b/arch/x86/coco/sev/Makefile @@ -8,3 +8,5 @@ UBSAN_SANITIZE_noinstr.o := n # GCC may fail to respect __no_sanitize_address or __no_kcsan when inlining KASAN_SANITIZE_noinstr.o := n KCSAN_SANITIZE_noinstr.o := n + +GCOV_PROFILE_noinstr.o := n From 3210077ed2648aa1443cde885e3d429186c5e8d8 Mon Sep 17 00:00:00 2001 From: Ruslan Krupitsa Date: Fri, 2 Jan 2026 02:53:36 +0300 Subject: [PATCH 3048/3902] ALSA: hda/realtek: add HP Laptop 15s-eq1xxx mute LED quirk [ Upstream commit 9ed7a28225af02b74f61e7880d460db49db83758 ] HP Laptop 15s-eq1xxx with ALC236 codec does not enable the mute LED automatically. This patch adds a quirk entry for subsystem ID 0x8706 using the ALC236_FIXUP_HP_MUTE_LED_COEFBIT2 fixup, enabling correct mute LED behavior. Signed-off-by: Ruslan Krupitsa Link: https://patch.msgid.link/AS8P194MB112895B8EC2D87D53A876085BBBAA@AS8P194MB1128.EURP194.PROD.OUTLOOK.COM Signed-off-by: Takashi Iwai Signed-off-by: Sasha Levin --- sound/hda/codecs/realtek/alc269.c | 1 + 1 file changed, 1 insertion(+) diff --git a/sound/hda/codecs/realtek/alc269.c b/sound/hda/codecs/realtek/alc269.c index 28a390f8636dc2..dc2e3ede7a23bd 100644 --- a/sound/hda/codecs/realtek/alc269.c +++ b/sound/hda/codecs/realtek/alc269.c @@ -6427,6 +6427,7 @@ static const struct hda_quirk alc269_fixup_tbl[] = { SND_PCI_QUIRK(0x103c, 0x863e, "HP Spectre x360 15-df1xxx", ALC285_FIXUP_HP_SPECTRE_X360_DF1), SND_PCI_QUIRK(0x103c, 0x86e8, "HP Spectre x360 15-eb0xxx", ALC285_FIXUP_HP_SPECTRE_X360_EB1), SND_PCI_QUIRK(0x103c, 0x86f9, "HP Spectre x360 13-aw0xxx", ALC285_FIXUP_HP_SPECTRE_X360_MUTE_LED), + SND_PCI_QUIRK(0x103c, 0x8706, "HP Laptop 15s-eq1xxx", ALC236_FIXUP_HP_MUTE_LED_COEFBIT2), SND_PCI_QUIRK(0x103c, 0x8716, "HP Elite Dragonfly G2 Notebook PC", ALC285_FIXUP_HP_GPIO_AMP_INIT), SND_PCI_QUIRK(0x103c, 0x8720, "HP EliteBook x360 1040 G8 Notebook PC", ALC285_FIXUP_HP_GPIO_AMP_INIT), SND_PCI_QUIRK(0x103c, 0x8724, "HP EliteBook 850 G7", ALC285_FIXUP_HP_GPIO_LED), From ddab2d0f93200931299a47b7e300d257ffda56f2 Mon Sep 17 00:00:00 2001 From: Manivannan Sadhasivam Date: Wed, 26 Nov 2025 13:47:18 +0530 Subject: [PATCH 3049/3902] PCI: qcom: Remove ASPM L0s support for MSM8996 SoC [ Upstream commit 0cc13256b60510936c34098ee7b929098eed823b ] Though I couldn't confirm ASPM L0s support with the Qcom hardware team, a bug report from Dmitry suggests that L0s is broken on this legacy SoC. Hence, remove L0s support from the Root Port Link Capabilities in this SoC. Since qcom_pcie_clear_aspm_l0s() is now used by more than one SoC config, call it from qcom_pcie_host_init() instead. Reported-by: Dmitry Baryshkov Closes: https://lore.kernel.org/linux-pci/4cp5pzmlkkht2ni7us6p3edidnk25l45xrp6w3fxguqcvhq2id@wjqqrdpkypkf Signed-off-by: Manivannan Sadhasivam Signed-off-by: Manivannan Sadhasivam Signed-off-by: Bjorn Helgaas Tested-by: Dmitry Baryshkov Reviewed-by: Konrad Dybcio Link: https://patch.msgid.link/20251126081718.8239-1-mani@kernel.org Signed-off-by: Sasha Levin --- drivers/pci/controller/dwc/pcie-qcom.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/drivers/pci/controller/dwc/pcie-qcom.c b/drivers/pci/controller/dwc/pcie-qcom.c index c48a20602d7fa4..6e820595ba32a9 100644 --- a/drivers/pci/controller/dwc/pcie-qcom.c +++ b/drivers/pci/controller/dwc/pcie-qcom.c @@ -1033,7 +1033,6 @@ static int qcom_pcie_post_init_2_7_0(struct qcom_pcie *pcie) writel(WR_NO_SNOOP_OVERRIDE_EN | RD_NO_SNOOP_OVERRIDE_EN, pcie->parf + PARF_NO_SNOOP_OVERRIDE); - qcom_pcie_clear_aspm_l0s(pcie->pci); qcom_pcie_clear_hpc(pcie->pci); return 0; @@ -1302,6 +1301,8 @@ static int qcom_pcie_host_init(struct dw_pcie_rp *pp) goto err_disable_phy; } + qcom_pcie_clear_aspm_l0s(pcie->pci); + qcom_ep_reset_deassert(pcie); if (pcie->cfg->ops->config_sid) { @@ -1450,6 +1451,7 @@ static const struct qcom_pcie_cfg cfg_2_1_0 = { static const struct qcom_pcie_cfg cfg_2_3_2 = { .ops = &ops_2_3_2, + .no_l0s = true, }; static const struct qcom_pcie_cfg cfg_2_3_3 = { From eb5d6dedadd66ca2aa100f47c004018624cd1b3e Mon Sep 17 00:00:00 2001 From: Daniel Gomez Date: Fri, 19 Dec 2025 06:13:20 +0100 Subject: [PATCH 3050/3902] netfilter: replace -EEXIST with -EBUSY [ Upstream commit 2bafeb8d2f380c3a81d98bd7b78b854b564f9cd4 ] The -EEXIST error code is reserved by the module loading infrastructure to indicate that a module is already loaded. When a module's init function returns -EEXIST, userspace tools like kmod interpret this as "module already loaded" and treat the operation as successful, returning 0 to the user even though the module initialization actually failed. Replace -EEXIST with -EBUSY to ensure correct error reporting in the module initialization path. Affected modules: * ebtable_broute ebtable_filter ebtable_nat arptable_filter * ip6table_filter ip6table_mangle ip6table_nat ip6table_raw * ip6table_security iptable_filter iptable_mangle iptable_nat * iptable_raw iptable_security Signed-off-by: Daniel Gomez Signed-off-by: Florian Westphal Signed-off-by: Sasha Levin --- net/bridge/netfilter/ebtables.c | 2 +- net/netfilter/nf_log.c | 4 ++-- net/netfilter/x_tables.c | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/net/bridge/netfilter/ebtables.c b/net/bridge/netfilter/ebtables.c index 5697e3949a3652..a04fc175752897 100644 --- a/net/bridge/netfilter/ebtables.c +++ b/net/bridge/netfilter/ebtables.c @@ -1299,7 +1299,7 @@ int ebt_register_template(const struct ebt_table *t, int (*table_init)(struct ne list_for_each_entry(tmpl, &template_tables, list) { if (WARN_ON_ONCE(strcmp(t->name, tmpl->name) == 0)) { mutex_unlock(&ebt_mutex); - return -EEXIST; + return -EBUSY; } } diff --git a/net/netfilter/nf_log.c b/net/netfilter/nf_log.c index 74cef8bf554c55..62cf6a30875e39 100644 --- a/net/netfilter/nf_log.c +++ b/net/netfilter/nf_log.c @@ -89,7 +89,7 @@ int nf_log_register(u_int8_t pf, struct nf_logger *logger) if (pf == NFPROTO_UNSPEC) { for (i = NFPROTO_UNSPEC; i < NFPROTO_NUMPROTO; i++) { if (rcu_access_pointer(loggers[i][logger->type])) { - ret = -EEXIST; + ret = -EBUSY; goto unlock; } } @@ -97,7 +97,7 @@ int nf_log_register(u_int8_t pf, struct nf_logger *logger) rcu_assign_pointer(loggers[i][logger->type], logger); } else { if (rcu_access_pointer(loggers[pf][logger->type])) { - ret = -EEXIST; + ret = -EBUSY; goto unlock; } rcu_assign_pointer(loggers[pf][logger->type], logger); diff --git a/net/netfilter/x_tables.c b/net/netfilter/x_tables.c index 90b7630421c442..48105ea3df1522 100644 --- a/net/netfilter/x_tables.c +++ b/net/netfilter/x_tables.c @@ -1764,7 +1764,7 @@ EXPORT_SYMBOL_GPL(xt_hook_ops_alloc); int xt_register_template(const struct xt_table *table, int (*table_init)(struct net *net)) { - int ret = -EEXIST, af = table->af; + int ret = -EBUSY, af = table->af; struct xt_template *t; mutex_lock(&xt[af].mutex); From 645671377158fc068b4e13b9af62ed33e066dafd Mon Sep 17 00:00:00 2001 From: Nathan Chancellor Date: Sat, 13 Dec 2025 19:58:10 +0900 Subject: [PATCH 3051/3902] drm/amd/display: Reduce number of arguments of dcn30's CalculatePrefetchSchedule() [ Upstream commit f54a91f5337cd918eb86cf600320d25b6cfd8209 ] After an innocuous optimization change in clang-22, dml30_ModeSupportAndSystemConfigurationFull() is over the 2048 byte stack limit for display_mode_vba_30.c. drivers/gpu/drm/amd/amdgpu/../display/dc/dml/dcn30/display_mode_vba_30.c:3529:6: warning: stack frame size (2096) exceeds limit (2048) in 'dml30_ModeSupportAndSystemConfigurationFull' [-Wframe-larger-than] 3529 | void dml30_ModeSupportAndSystemConfigurationFull(struct display_mode_lib *mode_lib) | ^ With clang-21, this function was already close to the limit: drivers/gpu/drm/amd/amdgpu/../display/dc/dml/dcn30/display_mode_vba_30.c:3529:6: warning: stack frame size (1912) exceeds limit (1586) in 'dml30_ModeSupportAndSystemConfigurationFull' [-Wframe-larger-than] 3529 | void dml30_ModeSupportAndSystemConfigurationFull(struct display_mode_lib *mode_lib) | ^ CalculatePrefetchSchedule() has a large number of parameters, which must be passed on the stack. Most of the parameters between the two callsites are the same, so they can be accessed through the existing mode_lib pointer, instead of being passed as explicit arguments. Doing this reduces the stack size of dml30_ModeSupportAndSystemConfigurationFull() from 2096 bytes to 1912 bytes with clang-22. Closes: https://github.com/ClangBuiltLinux/linux/issues/2117 Signed-off-by: Nathan Chancellor Signed-off-by: Alex Deucher (cherry picked from commit b20b3fc4210f83089f835cdb91deec4b0778761a) Signed-off-by: Sasha Levin --- .../dc/dml/dcn30/display_mode_vba_30.c | 258 +++++------------- 1 file changed, 73 insertions(+), 185 deletions(-) diff --git a/drivers/gpu/drm/amd/display/dc/dml/dcn30/display_mode_vba_30.c b/drivers/gpu/drm/amd/display/dc/dml/dcn30/display_mode_vba_30.c index 8d24763938ea6d..2d19bb8de59c84 100644 --- a/drivers/gpu/drm/amd/display/dc/dml/dcn30/display_mode_vba_30.c +++ b/drivers/gpu/drm/amd/display/dc/dml/dcn30/display_mode_vba_30.c @@ -77,32 +77,14 @@ static unsigned int dscceComputeDelay( static unsigned int dscComputeDelay( enum output_format_class pixelFormat, enum output_encoder_class Output); -// Super monster function with some 45 argument static bool CalculatePrefetchSchedule( struct display_mode_lib *mode_lib, - double PercentOfIdealDRAMFabricAndSDPPortBWReceivedAfterUrgLatencyPixelMixedWithVMData, - double PercentOfIdealDRAMFabricAndSDPPortBWReceivedAfterUrgLatencyVMDataOnly, + unsigned int k, Pipe *myPipe, unsigned int DSCDelay, - double DPPCLKDelaySubtotalPlusCNVCFormater, - double DPPCLKDelaySCL, - double DPPCLKDelaySCLLBOnly, - double DPPCLKDelayCNVCCursor, - double DISPCLKDelaySubtotal, unsigned int DPP_RECOUT_WIDTH, - enum output_format_class OutputFormat, - unsigned int MaxInterDCNTileRepeaters, unsigned int VStartup, unsigned int MaxVStartup, - unsigned int GPUVMPageTableLevels, - bool GPUVMEnable, - bool HostVMEnable, - unsigned int HostVMMaxNonCachedPageTableLevels, - double HostVMMinPageSize, - bool DynamicMetadataEnable, - bool DynamicMetadataVMEnabled, - int DynamicMetadataLinesBeforeActiveRequired, - unsigned int DynamicMetadataTransmittedBytes, double UrgentLatency, double UrgentExtraLatency, double TCalc, @@ -116,7 +98,6 @@ static bool CalculatePrefetchSchedule( unsigned int MaxNumSwathY, double PrefetchSourceLinesC, unsigned int SwathWidthC, - int BytePerPixelC, double VInitPreFillC, unsigned int MaxNumSwathC, long swath_width_luma_ub, @@ -124,9 +105,6 @@ static bool CalculatePrefetchSchedule( unsigned int SwathHeightY, unsigned int SwathHeightC, double TWait, - bool ProgressiveToInterlaceUnitInOPP, - double *DSTXAfterScaler, - double *DSTYAfterScaler, double *DestinationLinesForPrefetch, double *PrefetchBandwidth, double *DestinationLinesToRequestVMInVBlank, @@ -135,14 +113,7 @@ static bool CalculatePrefetchSchedule( double *VRatioPrefetchC, double *RequiredPrefetchPixDataBWLuma, double *RequiredPrefetchPixDataBWChroma, - bool *NotEnoughTimeForDynamicMetadata, - double *Tno_bw, - double *prefetch_vmrow_bw, - double *Tdmdl_vm, - double *Tdmdl, - unsigned int *VUpdateOffsetPix, - double *VUpdateWidthPix, - double *VReadyOffsetPix); + bool *NotEnoughTimeForDynamicMetadata); static double RoundToDFSGranularityUp(double Clock, double VCOSpeed); static double RoundToDFSGranularityDown(double Clock, double VCOSpeed); static void CalculateDCCConfiguration( @@ -810,29 +781,12 @@ static unsigned int dscComputeDelay(enum output_format_class pixelFormat, enum o static bool CalculatePrefetchSchedule( struct display_mode_lib *mode_lib, - double PercentOfIdealDRAMFabricAndSDPPortBWReceivedAfterUrgLatencyPixelMixedWithVMData, - double PercentOfIdealDRAMFabricAndSDPPortBWReceivedAfterUrgLatencyVMDataOnly, + unsigned int k, Pipe *myPipe, unsigned int DSCDelay, - double DPPCLKDelaySubtotalPlusCNVCFormater, - double DPPCLKDelaySCL, - double DPPCLKDelaySCLLBOnly, - double DPPCLKDelayCNVCCursor, - double DISPCLKDelaySubtotal, unsigned int DPP_RECOUT_WIDTH, - enum output_format_class OutputFormat, - unsigned int MaxInterDCNTileRepeaters, unsigned int VStartup, unsigned int MaxVStartup, - unsigned int GPUVMPageTableLevels, - bool GPUVMEnable, - bool HostVMEnable, - unsigned int HostVMMaxNonCachedPageTableLevels, - double HostVMMinPageSize, - bool DynamicMetadataEnable, - bool DynamicMetadataVMEnabled, - int DynamicMetadataLinesBeforeActiveRequired, - unsigned int DynamicMetadataTransmittedBytes, double UrgentLatency, double UrgentExtraLatency, double TCalc, @@ -846,7 +800,6 @@ static bool CalculatePrefetchSchedule( unsigned int MaxNumSwathY, double PrefetchSourceLinesC, unsigned int SwathWidthC, - int BytePerPixelC, double VInitPreFillC, unsigned int MaxNumSwathC, long swath_width_luma_ub, @@ -854,9 +807,6 @@ static bool CalculatePrefetchSchedule( unsigned int SwathHeightY, unsigned int SwathHeightC, double TWait, - bool ProgressiveToInterlaceUnitInOPP, - double *DSTXAfterScaler, - double *DSTYAfterScaler, double *DestinationLinesForPrefetch, double *PrefetchBandwidth, double *DestinationLinesToRequestVMInVBlank, @@ -865,15 +815,10 @@ static bool CalculatePrefetchSchedule( double *VRatioPrefetchC, double *RequiredPrefetchPixDataBWLuma, double *RequiredPrefetchPixDataBWChroma, - bool *NotEnoughTimeForDynamicMetadata, - double *Tno_bw, - double *prefetch_vmrow_bw, - double *Tdmdl_vm, - double *Tdmdl, - unsigned int *VUpdateOffsetPix, - double *VUpdateWidthPix, - double *VReadyOffsetPix) + bool *NotEnoughTimeForDynamicMetadata) { + struct vba_vars_st *v = &mode_lib->vba; + double DPPCLKDelaySubtotalPlusCNVCFormater = v->DPPCLKDelaySubtotal + v->DPPCLKDelayCNVCFormater; bool MyError = false; unsigned int DPPCycles = 0, DISPCLKCycles = 0; double DSTTotalPixelsAfterScaler = 0; @@ -905,26 +850,26 @@ static bool CalculatePrefetchSchedule( double Tdmec = 0; double Tdmsks = 0; - if (GPUVMEnable == true && HostVMEnable == true) { - HostVMInefficiencyFactor = PercentOfIdealDRAMFabricAndSDPPortBWReceivedAfterUrgLatencyPixelMixedWithVMData / PercentOfIdealDRAMFabricAndSDPPortBWReceivedAfterUrgLatencyVMDataOnly; - HostVMDynamicLevelsTrips = HostVMMaxNonCachedPageTableLevels; + if (v->GPUVMEnable == true && v->HostVMEnable == true) { + HostVMInefficiencyFactor = v->PercentOfIdealDRAMFabricAndSDPPortBWReceivedAfterUrgLatencyPixelMixedWithVMData / v->PercentOfIdealDRAMFabricAndSDPPortBWReceivedAfterUrgLatencyVMDataOnly; + HostVMDynamicLevelsTrips = v->HostVMMaxNonCachedPageTableLevels; } else { HostVMInefficiencyFactor = 1; HostVMDynamicLevelsTrips = 0; } CalculateDynamicMetadataParameters( - MaxInterDCNTileRepeaters, + v->MaxInterDCNTileRepeaters, myPipe->DPPCLK, myPipe->DISPCLK, myPipe->DCFCLKDeepSleep, myPipe->PixelClock, myPipe->HTotal, myPipe->VBlank, - DynamicMetadataTransmittedBytes, - DynamicMetadataLinesBeforeActiveRequired, + v->DynamicMetadataTransmittedBytes[k], + v->DynamicMetadataLinesBeforeActiveRequired[k], myPipe->InterlaceEnable, - ProgressiveToInterlaceUnitInOPP, + v->ProgressiveToInterlaceUnitInOPP, &Tsetup, &Tdmbf, &Tdmec, @@ -932,16 +877,16 @@ static bool CalculatePrefetchSchedule( LineTime = myPipe->HTotal / myPipe->PixelClock; trip_to_mem = UrgentLatency; - Tvm_trips = UrgentExtraLatency + trip_to_mem * (GPUVMPageTableLevels * (HostVMDynamicLevelsTrips + 1) - 1); + Tvm_trips = UrgentExtraLatency + trip_to_mem * (v->GPUVMMaxPageTableLevels * (HostVMDynamicLevelsTrips + 1) - 1); - if (DynamicMetadataVMEnabled == true && GPUVMEnable == true) { - *Tdmdl = TWait + Tvm_trips + trip_to_mem; + if (v->DynamicMetadataVMEnabled == true && v->GPUVMEnable == true) { + v->Tdmdl[k] = TWait + Tvm_trips + trip_to_mem; } else { - *Tdmdl = TWait + UrgentExtraLatency; + v->Tdmdl[k] = TWait + UrgentExtraLatency; } - if (DynamicMetadataEnable == true) { - if (VStartup * LineTime < Tsetup + *Tdmdl + Tdmbf + Tdmec + Tdmsks) { + if (v->DynamicMetadataEnable[k] == true) { + if (VStartup * LineTime < Tsetup + v->Tdmdl[k] + Tdmbf + Tdmec + Tdmsks) { *NotEnoughTimeForDynamicMetadata = true; } else { *NotEnoughTimeForDynamicMetadata = false; @@ -949,39 +894,39 @@ static bool CalculatePrefetchSchedule( dml_print("DML: Tdmbf: %fus - time for dmd transfer from dchub to dio output buffer\n", Tdmbf); dml_print("DML: Tdmec: %fus - time dio takes to transfer dmd\n", Tdmec); dml_print("DML: Tdmsks: %fus - time before active dmd must complete transmission at dio\n", Tdmsks); - dml_print("DML: Tdmdl: %fus - time for fabric to become ready and fetch dmd \n", *Tdmdl); + dml_print("DML: Tdmdl: %fus - time for fabric to become ready and fetch dmd \n", v->Tdmdl[k]); } } else { *NotEnoughTimeForDynamicMetadata = false; } - *Tdmdl_vm = (DynamicMetadataEnable == true && DynamicMetadataVMEnabled == true && GPUVMEnable == true ? TWait + Tvm_trips : 0); + v->Tdmdl_vm[k] = (v->DynamicMetadataEnable[k] == true && v->DynamicMetadataVMEnabled == true && v->GPUVMEnable == true ? TWait + Tvm_trips : 0); if (myPipe->ScalerEnabled) - DPPCycles = DPPCLKDelaySubtotalPlusCNVCFormater + DPPCLKDelaySCL; + DPPCycles = DPPCLKDelaySubtotalPlusCNVCFormater + v->DPPCLKDelaySCL; else - DPPCycles = DPPCLKDelaySubtotalPlusCNVCFormater + DPPCLKDelaySCLLBOnly; + DPPCycles = DPPCLKDelaySubtotalPlusCNVCFormater + v->DPPCLKDelaySCLLBOnly; - DPPCycles = DPPCycles + myPipe->NumberOfCursors * DPPCLKDelayCNVCCursor; + DPPCycles = DPPCycles + myPipe->NumberOfCursors * v->DPPCLKDelayCNVCCursor; - DISPCLKCycles = DISPCLKDelaySubtotal; + DISPCLKCycles = v->DISPCLKDelaySubtotal; if (myPipe->DPPCLK == 0.0 || myPipe->DISPCLK == 0.0) return true; - *DSTXAfterScaler = DPPCycles * myPipe->PixelClock / myPipe->DPPCLK + DISPCLKCycles * myPipe->PixelClock / myPipe->DISPCLK + v->DSTXAfterScaler[k] = DPPCycles * myPipe->PixelClock / myPipe->DPPCLK + DISPCLKCycles * myPipe->PixelClock / myPipe->DISPCLK + DSCDelay; - *DSTXAfterScaler = *DSTXAfterScaler + ((myPipe->ODMCombineEnabled)?18:0) + (myPipe->DPPPerPlane - 1) * DPP_RECOUT_WIDTH; + v->DSTXAfterScaler[k] = v->DSTXAfterScaler[k] + ((myPipe->ODMCombineEnabled)?18:0) + (myPipe->DPPPerPlane - 1) * DPP_RECOUT_WIDTH; - if (OutputFormat == dm_420 || (myPipe->InterlaceEnable && ProgressiveToInterlaceUnitInOPP)) - *DSTYAfterScaler = 1; + if (v->OutputFormat[k] == dm_420 || (myPipe->InterlaceEnable && v->ProgressiveToInterlaceUnitInOPP)) + v->DSTYAfterScaler[k] = 1; else - *DSTYAfterScaler = 0; + v->DSTYAfterScaler[k] = 0; - DSTTotalPixelsAfterScaler = *DSTYAfterScaler * myPipe->HTotal + *DSTXAfterScaler; - *DSTYAfterScaler = dml_floor(DSTTotalPixelsAfterScaler / myPipe->HTotal, 1); - *DSTXAfterScaler = DSTTotalPixelsAfterScaler - ((double) (*DSTYAfterScaler * myPipe->HTotal)); + DSTTotalPixelsAfterScaler = v->DSTYAfterScaler[k] * myPipe->HTotal + v->DSTXAfterScaler[k]; + v->DSTYAfterScaler[k] = dml_floor(DSTTotalPixelsAfterScaler / myPipe->HTotal, 1); + v->DSTXAfterScaler[k] = DSTTotalPixelsAfterScaler - ((double) (v->DSTYAfterScaler[k] * myPipe->HTotal)); MyError = false; @@ -990,33 +935,33 @@ static bool CalculatePrefetchSchedule( Tvm_trips_rounded = dml_ceil(4.0 * Tvm_trips / LineTime, 1) / 4 * LineTime; Tr0_trips_rounded = dml_ceil(4.0 * Tr0_trips / LineTime, 1) / 4 * LineTime; - if (GPUVMEnable) { - if (GPUVMPageTableLevels >= 3) { - *Tno_bw = UrgentExtraLatency + trip_to_mem * ((GPUVMPageTableLevels - 2) - 1); + if (v->GPUVMEnable) { + if (v->GPUVMMaxPageTableLevels >= 3) { + v->Tno_bw[k] = UrgentExtraLatency + trip_to_mem * ((v->GPUVMMaxPageTableLevels - 2) - 1); } else - *Tno_bw = 0; + v->Tno_bw[k] = 0; } else if (!myPipe->DCCEnable) - *Tno_bw = LineTime; + v->Tno_bw[k] = LineTime; else - *Tno_bw = LineTime / 4; + v->Tno_bw[k] = LineTime / 4; - dst_y_prefetch_equ = VStartup - (Tsetup + dml_max(TWait + TCalc, *Tdmdl)) / LineTime - - (*DSTYAfterScaler + *DSTXAfterScaler / myPipe->HTotal); + dst_y_prefetch_equ = VStartup - (Tsetup + dml_max(TWait + TCalc, v->Tdmdl[k])) / LineTime + - (v->DSTYAfterScaler[k] + v->DSTXAfterScaler[k] / myPipe->HTotal); dst_y_prefetch_equ = dml_min(dst_y_prefetch_equ, 63.75); // limit to the reg limit of U6.2 for DST_Y_PREFETCH Lsw_oto = dml_max(PrefetchSourceLinesY, PrefetchSourceLinesC); Tsw_oto = Lsw_oto * LineTime; - prefetch_bw_oto = (PrefetchSourceLinesY * swath_width_luma_ub * BytePerPixelY + PrefetchSourceLinesC * swath_width_chroma_ub * BytePerPixelC) / Tsw_oto; + prefetch_bw_oto = (PrefetchSourceLinesY * swath_width_luma_ub * BytePerPixelY + PrefetchSourceLinesC * swath_width_chroma_ub * v->BytePerPixelC[k]) / Tsw_oto; - if (GPUVMEnable == true) { - Tvm_oto = dml_max3(*Tno_bw + PDEAndMetaPTEBytesFrame * HostVMInefficiencyFactor / prefetch_bw_oto, + if (v->GPUVMEnable == true) { + Tvm_oto = dml_max3(v->Tno_bw[k] + PDEAndMetaPTEBytesFrame * HostVMInefficiencyFactor / prefetch_bw_oto, Tvm_trips, LineTime / 4.0); } else Tvm_oto = LineTime / 4.0; - if ((GPUVMEnable == true || myPipe->DCCEnable == true)) { + if ((v->GPUVMEnable == true || myPipe->DCCEnable == true)) { Tr0_oto = dml_max3( (MetaRowByte + PixelPTEBytesPerRow * HostVMInefficiencyFactor) / prefetch_bw_oto, LineTime - Tvm_oto, LineTime / 4); @@ -1042,10 +987,10 @@ static bool CalculatePrefetchSchedule( dml_print("DML: Tdmbf: %fus - time for dmd transfer from dchub to dio output buffer\n", Tdmbf); dml_print("DML: Tdmec: %fus - time dio takes to transfer dmd\n", Tdmec); dml_print("DML: Tdmsks: %fus - time before active dmd must complete transmission at dio\n", Tdmsks); - dml_print("DML: Tdmdl_vm: %fus - time for vm stages of dmd \n", *Tdmdl_vm); - dml_print("DML: Tdmdl: %fus - time for fabric to become ready and fetch dmd \n", *Tdmdl); - dml_print("DML: dst_x_after_scl: %f pixels - number of pixel clocks pipeline and buffer delay after scaler \n", *DSTXAfterScaler); - dml_print("DML: dst_y_after_scl: %d lines - number of lines of pipeline and buffer delay after scaler \n", (int)*DSTYAfterScaler); + dml_print("DML: Tdmdl_vm: %fus - time for vm stages of dmd \n", v->Tdmdl_vm[k]); + dml_print("DML: Tdmdl: %fus - time for fabric to become ready and fetch dmd \n", v->Tdmdl[k]); + dml_print("DML: dst_x_after_scl: %f pixels - number of pixel clocks pipeline and buffer delay after scaler \n", v->DSTXAfterScaler[k]); + dml_print("DML: dst_y_after_scl: %d lines - number of lines of pipeline and buffer delay after scaler \n", (int)v->DSTYAfterScaler[k]); *PrefetchBandwidth = 0; *DestinationLinesToRequestVMInVBlank = 0; @@ -1059,26 +1004,26 @@ static bool CalculatePrefetchSchedule( double PrefetchBandwidth3 = 0; double PrefetchBandwidth4 = 0; - if (Tpre_rounded - *Tno_bw > 0) + if (Tpre_rounded - v->Tno_bw[k] > 0) PrefetchBandwidth1 = (PDEAndMetaPTEBytesFrame * HostVMInefficiencyFactor + 2 * MetaRowByte + 2 * PixelPTEBytesPerRow * HostVMInefficiencyFactor + PrefetchSourceLinesY * swath_width_luma_ub * BytePerPixelY - + PrefetchSourceLinesC * swath_width_chroma_ub * BytePerPixelC) - / (Tpre_rounded - *Tno_bw); + + PrefetchSourceLinesC * swath_width_chroma_ub * v->BytePerPixelC[k]) + / (Tpre_rounded - v->Tno_bw[k]); else PrefetchBandwidth1 = 0; - if (VStartup == MaxVStartup && (PrefetchBandwidth1 > 4 * prefetch_bw_oto) && (Tpre_rounded - Tsw_oto / 4 - 0.75 * LineTime - *Tno_bw) > 0) { - PrefetchBandwidth1 = (PDEAndMetaPTEBytesFrame * HostVMInefficiencyFactor + 2 * MetaRowByte + 2 * PixelPTEBytesPerRow * HostVMInefficiencyFactor) / (Tpre_rounded - Tsw_oto / 4 - 0.75 * LineTime - *Tno_bw); + if (VStartup == MaxVStartup && (PrefetchBandwidth1 > 4 * prefetch_bw_oto) && (Tpre_rounded - Tsw_oto / 4 - 0.75 * LineTime - v->Tno_bw[k]) > 0) { + PrefetchBandwidth1 = (PDEAndMetaPTEBytesFrame * HostVMInefficiencyFactor + 2 * MetaRowByte + 2 * PixelPTEBytesPerRow * HostVMInefficiencyFactor) / (Tpre_rounded - Tsw_oto / 4 - 0.75 * LineTime - v->Tno_bw[k]); } - if (Tpre_rounded - *Tno_bw - 2 * Tr0_trips_rounded > 0) + if (Tpre_rounded - v->Tno_bw[k] - 2 * Tr0_trips_rounded > 0) PrefetchBandwidth2 = (PDEAndMetaPTEBytesFrame * HostVMInefficiencyFactor + PrefetchSourceLinesY * swath_width_luma_ub * BytePerPixelY + PrefetchSourceLinesC * swath_width_chroma_ub * - BytePerPixelC) / - (Tpre_rounded - *Tno_bw - 2 * Tr0_trips_rounded); + v->BytePerPixelC[k]) / + (Tpre_rounded - v->Tno_bw[k] - 2 * Tr0_trips_rounded); else PrefetchBandwidth2 = 0; @@ -1086,7 +1031,7 @@ static bool CalculatePrefetchSchedule( PrefetchBandwidth3 = (2 * MetaRowByte + 2 * PixelPTEBytesPerRow * HostVMInefficiencyFactor + PrefetchSourceLinesY * swath_width_luma_ub * BytePerPixelY + PrefetchSourceLinesC * - swath_width_chroma_ub * BytePerPixelC) / (Tpre_rounded - + swath_width_chroma_ub * v->BytePerPixelC[k]) / (Tpre_rounded - Tvm_trips_rounded); else PrefetchBandwidth3 = 0; @@ -1096,7 +1041,7 @@ static bool CalculatePrefetchSchedule( } if (Tpre_rounded - Tvm_trips_rounded - 2 * Tr0_trips_rounded > 0) - PrefetchBandwidth4 = (PrefetchSourceLinesY * swath_width_luma_ub * BytePerPixelY + PrefetchSourceLinesC * swath_width_chroma_ub * BytePerPixelC) + PrefetchBandwidth4 = (PrefetchSourceLinesY * swath_width_luma_ub * BytePerPixelY + PrefetchSourceLinesC * swath_width_chroma_ub * v->BytePerPixelC[k]) / (Tpre_rounded - Tvm_trips_rounded - 2 * Tr0_trips_rounded); else PrefetchBandwidth4 = 0; @@ -1107,7 +1052,7 @@ static bool CalculatePrefetchSchedule( bool Case3OK; if (PrefetchBandwidth1 > 0) { - if (*Tno_bw + PDEAndMetaPTEBytesFrame * HostVMInefficiencyFactor / PrefetchBandwidth1 + if (v->Tno_bw[k] + PDEAndMetaPTEBytesFrame * HostVMInefficiencyFactor / PrefetchBandwidth1 >= Tvm_trips_rounded && (MetaRowByte + PixelPTEBytesPerRow * HostVMInefficiencyFactor) / PrefetchBandwidth1 >= Tr0_trips_rounded) { Case1OK = true; } else { @@ -1118,7 +1063,7 @@ static bool CalculatePrefetchSchedule( } if (PrefetchBandwidth2 > 0) { - if (*Tno_bw + PDEAndMetaPTEBytesFrame * HostVMInefficiencyFactor / PrefetchBandwidth2 + if (v->Tno_bw[k] + PDEAndMetaPTEBytesFrame * HostVMInefficiencyFactor / PrefetchBandwidth2 >= Tvm_trips_rounded && (MetaRowByte + PixelPTEBytesPerRow * HostVMInefficiencyFactor) / PrefetchBandwidth2 < Tr0_trips_rounded) { Case2OK = true; } else { @@ -1129,7 +1074,7 @@ static bool CalculatePrefetchSchedule( } if (PrefetchBandwidth3 > 0) { - if (*Tno_bw + PDEAndMetaPTEBytesFrame * HostVMInefficiencyFactor / PrefetchBandwidth3 + if (v->Tno_bw[k] + PDEAndMetaPTEBytesFrame * HostVMInefficiencyFactor / PrefetchBandwidth3 < Tvm_trips_rounded && (MetaRowByte + PixelPTEBytesPerRow * HostVMInefficiencyFactor) / PrefetchBandwidth3 >= Tr0_trips_rounded) { Case3OK = true; } else { @@ -1152,13 +1097,13 @@ static bool CalculatePrefetchSchedule( dml_print("DML: prefetch_bw_equ: %f\n", prefetch_bw_equ); if (prefetch_bw_equ > 0) { - if (GPUVMEnable) { - Tvm_equ = dml_max3(*Tno_bw + PDEAndMetaPTEBytesFrame * HostVMInefficiencyFactor / prefetch_bw_equ, Tvm_trips, LineTime / 4); + if (v->GPUVMEnable) { + Tvm_equ = dml_max3(v->Tno_bw[k] + PDEAndMetaPTEBytesFrame * HostVMInefficiencyFactor / prefetch_bw_equ, Tvm_trips, LineTime / 4); } else { Tvm_equ = LineTime / 4; } - if ((GPUVMEnable || myPipe->DCCEnable)) { + if ((v->GPUVMEnable || myPipe->DCCEnable)) { Tr0_equ = dml_max4( (MetaRowByte + PixelPTEBytesPerRow * HostVMInefficiencyFactor) / prefetch_bw_equ, Tr0_trips, @@ -1227,7 +1172,7 @@ static bool CalculatePrefetchSchedule( } *RequiredPrefetchPixDataBWLuma = (double) PrefetchSourceLinesY / LinesToRequestPrefetchPixelData * BytePerPixelY * swath_width_luma_ub / LineTime; - *RequiredPrefetchPixDataBWChroma = (double) PrefetchSourceLinesC / LinesToRequestPrefetchPixelData * BytePerPixelC * swath_width_chroma_ub / LineTime; + *RequiredPrefetchPixDataBWChroma = (double) PrefetchSourceLinesC / LinesToRequestPrefetchPixelData * v->BytePerPixelC[k] * swath_width_chroma_ub / LineTime; } else { MyError = true; dml_print("DML: MyErr set %s:%d\n", __FILE__, __LINE__); @@ -1243,9 +1188,9 @@ static bool CalculatePrefetchSchedule( dml_print("DML: Tr0: %fus - time to fetch first row of data pagetables and first row of meta data (done in parallel)\n", TimeForFetchingRowInVBlank); dml_print("DML: Tr1: %fus - time to fetch second row of data pagetables and second row of meta data (done in parallel)\n", TimeForFetchingRowInVBlank); dml_print("DML: Tsw: %fus = time to fetch enough pixel data and cursor data to feed the scalers init position and detile\n", (double)LinesToRequestPrefetchPixelData * LineTime); - dml_print("DML: To: %fus - time for propagation from scaler to optc\n", (*DSTYAfterScaler + ((*DSTXAfterScaler) / (double) myPipe->HTotal)) * LineTime); + dml_print("DML: To: %fus - time for propagation from scaler to optc\n", (v->DSTYAfterScaler[k] + ((v->DSTXAfterScaler[k]) / (double) myPipe->HTotal)) * LineTime); dml_print("DML: Tvstartup - Tsetup - Tcalc - Twait - Tpre - To > 0\n"); - dml_print("DML: Tslack(pre): %fus - time left over in schedule\n", VStartup * LineTime - TimeForFetchingMetaPTE - 2 * TimeForFetchingRowInVBlank - (*DSTYAfterScaler + ((*DSTXAfterScaler) / (double) myPipe->HTotal)) * LineTime - TWait - TCalc - Tsetup); + dml_print("DML: Tslack(pre): %fus - time left over in schedule\n", VStartup * LineTime - TimeForFetchingMetaPTE - 2 * TimeForFetchingRowInVBlank - (v->DSTYAfterScaler[k] + ((v->DSTXAfterScaler[k]) / (double) myPipe->HTotal)) * LineTime - TWait - TCalc - Tsetup); dml_print("DML: row_bytes = dpte_row_bytes (per_pipe) = PixelPTEBytesPerRow = : %d\n", PixelPTEBytesPerRow); } else { @@ -1276,7 +1221,7 @@ static bool CalculatePrefetchSchedule( dml_print("DML: MyErr set %s:%d\n", __FILE__, __LINE__); } - *prefetch_vmrow_bw = dml_max(prefetch_vm_bw, prefetch_row_bw); + v->prefetch_vmrow_bw[k] = dml_max(prefetch_vm_bw, prefetch_row_bw); } if (MyError) { @@ -2437,30 +2382,12 @@ static void DISPCLKDPPCLKDCFCLKDeepSleepPrefetchParametersWatermarksAndPerforman v->ErrorResult[k] = CalculatePrefetchSchedule( mode_lib, - v->PercentOfIdealDRAMFabricAndSDPPortBWReceivedAfterUrgLatencyPixelMixedWithVMData, - v->PercentOfIdealDRAMFabricAndSDPPortBWReceivedAfterUrgLatencyVMDataOnly, + k, &myPipe, v->DSCDelay[k], - v->DPPCLKDelaySubtotal - + v->DPPCLKDelayCNVCFormater, - v->DPPCLKDelaySCL, - v->DPPCLKDelaySCLLBOnly, - v->DPPCLKDelayCNVCCursor, - v->DISPCLKDelaySubtotal, (unsigned int) (v->SwathWidthY[k] / v->HRatio[k]), - v->OutputFormat[k], - v->MaxInterDCNTileRepeaters, dml_min(v->VStartupLines, v->MaxVStartupLines[k]), v->MaxVStartupLines[k], - v->GPUVMMaxPageTableLevels, - v->GPUVMEnable, - v->HostVMEnable, - v->HostVMMaxNonCachedPageTableLevels, - v->HostVMMinPageSize, - v->DynamicMetadataEnable[k], - v->DynamicMetadataVMEnabled, - v->DynamicMetadataLinesBeforeActiveRequired[k], - v->DynamicMetadataTransmittedBytes[k], v->UrgentLatency, v->UrgentExtraLatency, v->TCalc, @@ -2474,7 +2401,6 @@ static void DISPCLKDPPCLKDCFCLKDeepSleepPrefetchParametersWatermarksAndPerforman v->MaxNumSwathY[k], v->PrefetchSourceLinesC[k], v->SwathWidthC[k], - v->BytePerPixelC[k], v->VInitPreFillC[k], v->MaxNumSwathC[k], v->swath_width_luma_ub[k], @@ -2482,9 +2408,6 @@ static void DISPCLKDPPCLKDCFCLKDeepSleepPrefetchParametersWatermarksAndPerforman v->SwathHeightY[k], v->SwathHeightC[k], TWait, - v->ProgressiveToInterlaceUnitInOPP, - &v->DSTXAfterScaler[k], - &v->DSTYAfterScaler[k], &v->DestinationLinesForPrefetch[k], &v->PrefetchBandwidth[k], &v->DestinationLinesToRequestVMInVBlank[k], @@ -2493,14 +2416,7 @@ static void DISPCLKDPPCLKDCFCLKDeepSleepPrefetchParametersWatermarksAndPerforman &v->VRatioPrefetchC[k], &v->RequiredPrefetchPixDataBWLuma[k], &v->RequiredPrefetchPixDataBWChroma[k], - &v->NotEnoughTimeForDynamicMetadata[k], - &v->Tno_bw[k], - &v->prefetch_vmrow_bw[k], - &v->Tdmdl_vm[k], - &v->Tdmdl[k], - &v->VUpdateOffsetPix[k], - &v->VUpdateWidthPix[k], - &v->VReadyOffsetPix[k]); + &v->NotEnoughTimeForDynamicMetadata[k]); if (v->BlendingAndTiming[k] == k) { double TotalRepeaterDelayTime = v->MaxInterDCNTileRepeaters * (2 / v->DPPCLK[k] + 3 / v->DISPCLK); v->VUpdateWidthPix[k] = (14 / v->DCFCLKDeepSleep + 12 / v->DPPCLK[k] + TotalRepeaterDelayTime) * v->PixelClock[k]; @@ -4770,29 +4686,12 @@ void dml30_ModeSupportAndSystemConfigurationFull(struct display_mode_lib *mode_l v->NoTimeForPrefetch[i][j][k] = CalculatePrefetchSchedule( mode_lib, - v->PercentOfIdealDRAMFabricAndSDPPortBWReceivedAfterUrgLatencyPixelMixedWithVMData, - v->PercentOfIdealDRAMFabricAndSDPPortBWReceivedAfterUrgLatencyVMDataOnly, + k, &myPipe, v->DSCDelayPerState[i][k], - v->DPPCLKDelaySubtotal + v->DPPCLKDelayCNVCFormater, - v->DPPCLKDelaySCL, - v->DPPCLKDelaySCLLBOnly, - v->DPPCLKDelayCNVCCursor, - v->DISPCLKDelaySubtotal, v->SwathWidthYThisState[k] / v->HRatio[k], - v->OutputFormat[k], - v->MaxInterDCNTileRepeaters, dml_min(v->MaxVStartup, v->MaximumVStartup[i][j][k]), v->MaximumVStartup[i][j][k], - v->GPUVMMaxPageTableLevels, - v->GPUVMEnable, - v->HostVMEnable, - v->HostVMMaxNonCachedPageTableLevels, - v->HostVMMinPageSize, - v->DynamicMetadataEnable[k], - v->DynamicMetadataVMEnabled, - v->DynamicMetadataLinesBeforeActiveRequired[k], - v->DynamicMetadataTransmittedBytes[k], v->UrgLatency[i], v->ExtraLatency, v->TimeCalc, @@ -4806,7 +4705,6 @@ void dml30_ModeSupportAndSystemConfigurationFull(struct display_mode_lib *mode_l v->MaxNumSwY[k], v->PrefetchLinesC[i][j][k], v->SwathWidthCThisState[k], - v->BytePerPixelC[k], v->PrefillC[k], v->MaxNumSwC[k], v->swath_width_luma_ub_this_state[k], @@ -4814,9 +4712,6 @@ void dml30_ModeSupportAndSystemConfigurationFull(struct display_mode_lib *mode_l v->SwathHeightYThisState[k], v->SwathHeightCThisState[k], v->TWait, - v->ProgressiveToInterlaceUnitInOPP, - &v->DSTXAfterScaler[k], - &v->DSTYAfterScaler[k], &v->LineTimesForPrefetch[k], &v->PrefetchBW[k], &v->LinesForMetaPTE[k], @@ -4825,14 +4720,7 @@ void dml30_ModeSupportAndSystemConfigurationFull(struct display_mode_lib *mode_l &v->VRatioPreC[i][j][k], &v->RequiredPrefetchPixelDataBWLuma[i][j][k], &v->RequiredPrefetchPixelDataBWChroma[i][j][k], - &v->NoTimeForDynamicMetadata[i][j][k], - &v->Tno_bw[k], - &v->prefetch_vmrow_bw[k], - &v->Tdmdl_vm[k], - &v->Tdmdl[k], - &v->VUpdateOffsetPix[k], - &v->VUpdateWidthPix[k], - &v->VReadyOffsetPix[k]); + &v->NoTimeForDynamicMetadata[i][j][k]); } for (k = 0; k <= v->NumberOfActivePlanes - 1; k++) { From 6e2108daed94ef8e3294bc6055aa15cf403696e0 Mon Sep 17 00:00:00 2001 From: Chris Chiu Date: Fri, 2 Jan 2026 06:56:43 +0000 Subject: [PATCH 3052/3902] HID: quirks: Add another Chicony HP 5MP Cameras to hid_ignore_list [ Upstream commit c06bc3557542307b9658fbd43cc946a14250347b ] Another Chicony Electronics HP 5MP Camera with USB ID 04F2:B882 reports a HID sensor interface that is not actually implemented. Add the device to the HID ignore list so the bogus sensor is never exposed to userspace. Then the system won't hang when runtime PM tries to wake the unresponsive device. Signed-off-by: Chris Chiu Signed-off-by: Benjamin Tissoires Signed-off-by: Sasha Levin --- drivers/hid/hid-ids.h | 1 + drivers/hid/hid-quirks.c | 1 + 2 files changed, 2 insertions(+) diff --git a/drivers/hid/hid-ids.h b/drivers/hid/hid-ids.h index c4589075a5ed68..3a22129fb70756 100644 --- a/drivers/hid/hid-ids.h +++ b/drivers/hid/hid-ids.h @@ -317,6 +317,7 @@ #define USB_DEVICE_ID_CHICONY_ACER_SWITCH12 0x1421 #define USB_DEVICE_ID_CHICONY_HP_5MP_CAMERA 0xb824 #define USB_DEVICE_ID_CHICONY_HP_5MP_CAMERA2 0xb82c +#define USB_DEVICE_ID_CHICONY_HP_5MP_CAMERA3 0xb882 #define USB_VENDOR_ID_CHUNGHWAT 0x2247 #define USB_DEVICE_ID_CHUNGHWAT_MULTITOUCH 0x0001 diff --git a/drivers/hid/hid-quirks.c b/drivers/hid/hid-quirks.c index 6a8a7ca3d80474..1d5537c0f40d88 100644 --- a/drivers/hid/hid-quirks.c +++ b/drivers/hid/hid-quirks.c @@ -778,6 +778,7 @@ static const struct hid_device_id hid_ignore_list[] = { { HID_USB_DEVICE(USB_VENDOR_ID_BERKSHIRE, USB_DEVICE_ID_BERKSHIRE_PCWD) }, { HID_USB_DEVICE(USB_VENDOR_ID_CHICONY, USB_DEVICE_ID_CHICONY_HP_5MP_CAMERA) }, { HID_USB_DEVICE(USB_VENDOR_ID_CHICONY, USB_DEVICE_ID_CHICONY_HP_5MP_CAMERA2) }, + { HID_USB_DEVICE(USB_VENDOR_ID_CHICONY, USB_DEVICE_ID_CHICONY_HP_5MP_CAMERA3) }, { HID_USB_DEVICE(USB_VENDOR_ID_CIDC, 0x0103) }, { HID_USB_DEVICE(USB_VENDOR_ID_CYGNAL, USB_DEVICE_ID_CYGNAL_RADIO_SI470X) }, { HID_USB_DEVICE(USB_VENDOR_ID_CYGNAL, USB_DEVICE_ID_CYGNAL_RADIO_SI4713) }, From 2124279f1f8c32c1646ce98e75a1a39b23b7db76 Mon Sep 17 00:00:00 2001 From: Kwok Kin Ming Date: Thu, 1 Jan 2026 02:18:26 +0800 Subject: [PATCH 3053/3902] HID: i2c-hid: fix potential buffer overflow in i2c_hid_get_report() [ Upstream commit 2497ff38c530b1af0df5130ca9f5ab22c5e92f29 ] `i2c_hid_xfer` is used to read `recv_len + sizeof(__le16)` bytes of data into `ihid->rawbuf`. The former can come from the userspace in the hidraw driver and is only bounded by HID_MAX_BUFFER_SIZE(16384) by default (unless we also set `max_buffer_size` field of `struct hid_ll_driver` which we do not). The latter has size determined at runtime by the maximum size of different report types you could receive on any particular device and can be a much smaller value. Fix this by truncating `recv_len` to `ihid->bufsize - sizeof(__le16)`. The impact is low since access to hidraw devices requires root. Signed-off-by: Kwok Kin Ming Signed-off-by: Benjamin Tissoires Signed-off-by: Sasha Levin --- drivers/hid/i2c-hid/i2c-hid-core.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/hid/i2c-hid/i2c-hid-core.c b/drivers/hid/i2c-hid/i2c-hid-core.c index 63f46a2e57882f..5a183af3d5c6a6 100644 --- a/drivers/hid/i2c-hid/i2c-hid-core.c +++ b/drivers/hid/i2c-hid/i2c-hid-core.c @@ -286,6 +286,7 @@ static int i2c_hid_get_report(struct i2c_hid *ihid, * In addition to report data device will supply data length * in the first 2 bytes of the response, so adjust . */ + recv_len = min(recv_len, ihid->bufsize - sizeof(__le16)); error = i2c_hid_xfer(ihid, ihid->cmdbuf, length, ihid->rawbuf, recv_len + sizeof(__le16)); if (error) { From 1e84a807c98a71f767fd1f609637bc5944f916cb Mon Sep 17 00:00:00 2001 From: Even Xu Date: Fri, 26 Dec 2025 11:39:53 +0800 Subject: [PATCH 3054/3902] HID: Intel-thc-hid: Intel-thc: Add safety check for reading DMA buffer [ Upstream commit a9a917998d172ec117f9e9de1919174153c0ace4 ] Add DMA buffer readiness check before reading DMA buffer to avoid unexpected NULL pointer accessing. Signed-off-by: Even Xu Tested-by: Rui Zhang Signed-off-by: Benjamin Tissoires Signed-off-by: Sasha Levin --- drivers/hid/intel-thc-hid/intel-thc/intel-thc-dma.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/drivers/hid/intel-thc-hid/intel-thc/intel-thc-dma.c b/drivers/hid/intel-thc-hid/intel-thc/intel-thc-dma.c index a0c368aa7979c4..6ee675e0a73845 100644 --- a/drivers/hid/intel-thc-hid/intel-thc/intel-thc-dma.c +++ b/drivers/hid/intel-thc-hid/intel-thc/intel-thc-dma.c @@ -575,6 +575,11 @@ static int read_dma_buffer(struct thc_device *dev, return -EINVAL; } + if (!read_config->prd_tbls || !read_config->sgls[prd_table_index]) { + dev_err_once(dev->dev, "PRD tables are not ready yet\n"); + return -EINVAL; + } + prd_tbl = &read_config->prd_tbls[prd_table_index]; mes_len = calc_message_len(prd_tbl, &nent); if (mes_len > read_config->max_packet_size) { From ecb8653a8fe9d2d3b55920ed362c80609576e7c8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rodrigo=20Lugathe=20da=20Concei=C3=A7=C3=A3o=20Alves?= Date: Thu, 27 Nov 2025 19:03:57 -0300 Subject: [PATCH 3055/3902] HID: Apply quirk HID_QUIRK_ALWAYS_POLL to Edifier QR30 (2d99:a101) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [ Upstream commit 85a866809333cd2bf8ddac93d9a3e3ba8e4f807d ] The USB speaker has a bug that causes it to reboot when changing the brightness using the physical knob. Add a new vendor and product ID entry in hid-ids.h, and register the corresponding device in hid-quirks.c with the required quirk. Signed-off-by: Rodrigo Lugathe da Conceição Alves Reviewed-by: Terry Junge Signed-off-by: Jiri Kosina Signed-off-by: Sasha Levin --- drivers/hid/hid-ids.h | 3 +++ drivers/hid/hid-quirks.c | 1 + 2 files changed, 4 insertions(+) diff --git a/drivers/hid/hid-ids.h b/drivers/hid/hid-ids.h index 3a22129fb70756..bec913a005a5d6 100644 --- a/drivers/hid/hid-ids.h +++ b/drivers/hid/hid-ids.h @@ -439,6 +439,9 @@ #define USB_DEVICE_ID_DWAV_EGALAX_MULTITOUCH_A001 0xa001 #define USB_DEVICE_ID_DWAV_EGALAX_MULTITOUCH_C002 0xc002 +#define USB_VENDOR_ID_EDIFIER 0x2d99 +#define USB_DEVICE_ID_EDIFIER_QR30 0xa101 /* EDIFIER Hal0 2.0 SE */ + #define USB_VENDOR_ID_ELAN 0x04f3 #define USB_DEVICE_ID_TOSHIBA_CLICK_L9W 0x0401 #define USB_DEVICE_ID_HP_X2 0x074d diff --git a/drivers/hid/hid-quirks.c b/drivers/hid/hid-quirks.c index 1d5537c0f40d88..31b2a5d1cd98f6 100644 --- a/drivers/hid/hid-quirks.c +++ b/drivers/hid/hid-quirks.c @@ -81,6 +81,7 @@ static const struct hid_device_id hid_quirks[] = { { HID_USB_DEVICE(USB_VENDOR_ID_DRAGONRISE, USB_DEVICE_ID_DRAGONRISE_PS3), HID_QUIRK_MULTI_INPUT }, { HID_USB_DEVICE(USB_VENDOR_ID_DRAGONRISE, USB_DEVICE_ID_DRAGONRISE_WIIU), HID_QUIRK_MULTI_INPUT }, { HID_USB_DEVICE(USB_VENDOR_ID_DWAV, USB_DEVICE_ID_EGALAX_TOUCHCONTROLLER), HID_QUIRK_MULTI_INPUT | HID_QUIRK_NOGET }, + { HID_USB_DEVICE(USB_VENDOR_ID_EDIFIER, USB_DEVICE_ID_EDIFIER_QR30), HID_QUIRK_ALWAYS_POLL }, { HID_USB_DEVICE(USB_VENDOR_ID_ELAN, HID_ANY_ID), HID_QUIRK_ALWAYS_POLL }, { HID_USB_DEVICE(USB_VENDOR_ID_ELO, USB_DEVICE_ID_ELO_TS2700), HID_QUIRK_NOGET }, { HID_USB_DEVICE(USB_VENDOR_ID_EMS, USB_DEVICE_ID_EMS_TRIO_LINKER_PLUS_II), HID_QUIRK_MULTI_INPUT }, From cd7ff7fd3e4b77f0b5a292e0926532eaa07c5162 Mon Sep 17 00:00:00 2001 From: Perry Yuan Date: Thu, 25 Dec 2025 16:43:49 +0800 Subject: [PATCH 3056/3902] drm/amd/pm: Disable MMIO access during SMU Mode 1 reset [ Upstream commit 0de604d0357d0d22cbf03af1077d174b641707b6 ] During Mode 1 reset, the ASIC undergoes a reset cycle and becomes temporarily inaccessible via PCIe. Any attempt to access MMIO registers during this window (e.g., from interrupt handlers or other driver threads) can result in uncompleted PCIe transactions, leading to NMI panics or system hangs. To prevent this, set the `no_hw_access` flag to true immediately after triggering the reset. This signals other driver components to skip register accesses while the device is offline. A memory barrier `smp_mb()` is added to ensure the flag update is globally visible to all cores before the driver enters the sleep/wait state. Signed-off-by: Perry Yuan Reviewed-by: Yifan Zhang Signed-off-by: Alex Deucher (cherry picked from commit 7edb503fe4b6d67f47d8bb0dfafb8e699bb0f8a4) Signed-off-by: Sasha Levin --- drivers/gpu/drm/amd/amdgpu/amdgpu_device.c | 3 +++ drivers/gpu/drm/amd/pm/swsmu/smu13/smu_v13_0_0_ppt.c | 7 ++++++- drivers/gpu/drm/amd/pm/swsmu/smu14/smu_v14_0_2_ppt.c | 9 +++++++-- 3 files changed, 16 insertions(+), 3 deletions(-) diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_device.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_device.c index 49107475af619e..53b33a636971a1 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_device.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_device.c @@ -5739,6 +5739,9 @@ int amdgpu_device_mode1_reset(struct amdgpu_device *adev) if (ret) goto mode1_reset_failed; + /* enable mmio access after mode 1 reset completed */ + adev->no_hw_access = false; + amdgpu_device_load_pci_state(adev->pdev); ret = amdgpu_psp_wait_for_bootloader(adev); if (ret) diff --git a/drivers/gpu/drm/amd/pm/swsmu/smu13/smu_v13_0_0_ppt.c b/drivers/gpu/drm/amd/pm/swsmu/smu13/smu_v13_0_0_ppt.c index c1062e5f039367..8d070a9ea2c101 100644 --- a/drivers/gpu/drm/amd/pm/swsmu/smu13/smu_v13_0_0_ppt.c +++ b/drivers/gpu/drm/amd/pm/swsmu/smu13/smu_v13_0_0_ppt.c @@ -2922,8 +2922,13 @@ static int smu_v13_0_0_mode1_reset(struct smu_context *smu) break; } - if (!ret) + if (!ret) { + /* disable mmio access while doing mode 1 reset*/ + smu->adev->no_hw_access = true; + /* ensure no_hw_access is globally visible before any MMIO */ + smp_mb(); msleep(SMU13_MODE1_RESET_WAIT_TIME_IN_MS); + } return ret; } diff --git a/drivers/gpu/drm/amd/pm/swsmu/smu14/smu_v14_0_2_ppt.c b/drivers/gpu/drm/amd/pm/swsmu/smu14/smu_v14_0_2_ppt.c index e735da7ab61265..bad8dd786bff2f 100644 --- a/drivers/gpu/drm/amd/pm/swsmu/smu14/smu_v14_0_2_ppt.c +++ b/drivers/gpu/drm/amd/pm/swsmu/smu14/smu_v14_0_2_ppt.c @@ -2143,10 +2143,15 @@ static int smu_v14_0_2_mode1_reset(struct smu_context *smu) ret = smu_cmn_send_debug_smc_msg(smu, DEBUGSMC_MSG_Mode1Reset); if (!ret) { - if (amdgpu_emu_mode == 1) + if (amdgpu_emu_mode == 1) { msleep(50000); - else + } else { + /* disable mmio access while doing mode 1 reset*/ + smu->adev->no_hw_access = true; + /* ensure no_hw_access is globally visible before any MMIO */ + smp_mb(); msleep(1000); + } } return ret; From 151589d15ee87b1aed1eea0d3db6dd22dd9d70c1 Mon Sep 17 00:00:00 2001 From: Wupeng Ma Date: Sun, 28 Dec 2025 14:50:07 +0800 Subject: [PATCH 3057/3902] ring-buffer: Avoid softlockup in ring_buffer_resize() during memory free [ Upstream commit 6435ffd6c7fcba330dfa91c58dc30aed2df3d0bf ] When user resize all trace ring buffer through file 'buffer_size_kb', then in ring_buffer_resize(), kernel allocates buffer pages for each cpu in a loop. If the kernel preemption model is PREEMPT_NONE and there are many cpus and there are many buffer pages to be freed, it may not give up cpu for a long time and finally cause a softlockup. To avoid it, call cond_resched() after each cpu buffer free as Commit f6bd2c92488c ("ring-buffer: Avoid softlockup in ring_buffer_resize()") does. Detailed call trace as follow: rcu: INFO: rcu_sched self-detected stall on CPU rcu: 24-....: (14837 ticks this GP) idle=521c/1/0x4000000000000000 softirq=230597/230597 fqs=5329 rcu: (t=15004 jiffies g=26003221 q=211022 ncpus=96) CPU: 24 UID: 0 PID: 11253 Comm: bash Kdump: loaded Tainted: G EL 6.18.2+ #278 NONE pc : arch_local_irq_restore+0x8/0x20 arch_local_irq_restore+0x8/0x20 (P) free_frozen_page_commit+0x28c/0x3b0 __free_frozen_pages+0x1c0/0x678 ___free_pages+0xc0/0xe0 free_pages+0x3c/0x50 ring_buffer_resize.part.0+0x6a8/0x880 ring_buffer_resize+0x3c/0x58 __tracing_resize_ring_buffer.part.0+0x34/0xd8 tracing_resize_ring_buffer+0x8c/0xd0 tracing_entries_write+0x74/0xd8 vfs_write+0xcc/0x288 ksys_write+0x74/0x118 __arm64_sys_write+0x24/0x38 Cc: Link: https://patch.msgid.link/20251228065008.2396573-1-mawupeng1@huawei.com Signed-off-by: Wupeng Ma Acked-by: Masami Hiramatsu (Google) Signed-off-by: Steven Rostedt (Google) Signed-off-by: Sasha Levin --- kernel/trace/ring_buffer.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/kernel/trace/ring_buffer.c b/kernel/trace/ring_buffer.c index afcd3747264d2e..3ba08fc1b7d058 100644 --- a/kernel/trace/ring_buffer.c +++ b/kernel/trace/ring_buffer.c @@ -3121,6 +3121,8 @@ int ring_buffer_resize(struct trace_buffer *buffer, unsigned long size, list) { list_del_init(&bpage->list); free_buffer_page(bpage); + + cond_resched(); } } out_err_unlock: From b1f8285bc8e3508c1fde23b5205f1270215d4984 Mon Sep 17 00:00:00 2001 From: Martin Kaiser Date: Tue, 23 Dec 2025 14:50:06 +0100 Subject: [PATCH 3058/3902] riscv: trace: fix snapshot deadlock with sbi ecall [ Upstream commit b0d7f5f0c9f05f1b6d4ee7110f15bef9c11f9df0 ] If sbi_ecall.c's functions are traceable, echo "__sbi_ecall:snapshot" > /sys/kernel/tracing/set_ftrace_filter may get the kernel into a deadlock. (Functions in sbi_ecall.c are excluded from tracing if CONFIG_RISCV_ALTERNATIVE_EARLY is set.) __sbi_ecall triggers a snapshot of the ringbuffer. The snapshot code raises an IPI interrupt, which results in another call to __sbi_ecall and another snapshot... All it takes to get into this endless loop is one initial __sbi_ecall. On RISC-V systems without SSTC extension, the clock events in timer-riscv.c issue periodic sbi ecalls, making the problem easy to trigger. Always exclude the sbi_ecall.c functions from tracing to fix the potential deadlock. sbi ecalls can easiliy be logged via trace events, excluding ecall functions from function tracing is not a big limitation. Signed-off-by: Martin Kaiser Link: https://patch.msgid.link/20251223135043.1336524-1-martin@kaiser.cx Signed-off-by: Paul Walmsley Signed-off-by: Sasha Levin --- arch/riscv/kernel/Makefile | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/arch/riscv/kernel/Makefile b/arch/riscv/kernel/Makefile index f60fce69b7259f..a01f6439d62b1a 100644 --- a/arch/riscv/kernel/Makefile +++ b/arch/riscv/kernel/Makefile @@ -3,12 +3,6 @@ # Makefile for the RISC-V Linux kernel # -ifdef CONFIG_FTRACE -CFLAGS_REMOVE_ftrace.o = $(CC_FLAGS_FTRACE) -CFLAGS_REMOVE_patch.o = $(CC_FLAGS_FTRACE) -CFLAGS_REMOVE_sbi.o = $(CC_FLAGS_FTRACE) -CFLAGS_REMOVE_return_address.o = $(CC_FLAGS_FTRACE) -endif CFLAGS_syscall_table.o += $(call cc-disable-warning, override-init) CFLAGS_compat_syscall_table.o += $(call cc-disable-warning, override-init) @@ -24,7 +18,6 @@ CFLAGS_sbi_ecall.o := -mcmodel=medany ifdef CONFIG_FTRACE CFLAGS_REMOVE_alternative.o = $(CC_FLAGS_FTRACE) CFLAGS_REMOVE_cpufeature.o = $(CC_FLAGS_FTRACE) -CFLAGS_REMOVE_sbi_ecall.o = $(CC_FLAGS_FTRACE) endif ifdef CONFIG_RELOCATABLE CFLAGS_alternative.o += -fno-pie @@ -43,6 +36,14 @@ CFLAGS_sbi_ecall.o += -D__NO_FORTIFY endif endif +ifdef CONFIG_FTRACE +CFLAGS_REMOVE_ftrace.o = $(CC_FLAGS_FTRACE) +CFLAGS_REMOVE_patch.o = $(CC_FLAGS_FTRACE) +CFLAGS_REMOVE_sbi.o = $(CC_FLAGS_FTRACE) +CFLAGS_REMOVE_return_address.o = $(CC_FLAGS_FTRACE) +CFLAGS_REMOVE_sbi_ecall.o = $(CC_FLAGS_FTRACE) +endif + always-$(KBUILD_BUILTIN) += vmlinux.lds obj-y += head.o From 71434e45bf1243ec735d5f7bb98ce614646aa9ba Mon Sep 17 00:00:00 2001 From: Dennis Marttinen Date: Sun, 4 Jan 2026 13:00:51 +0000 Subject: [PATCH 3059/3902] HID: logitech: add HID++ support for Logitech MX Anywhere 3S [ Upstream commit d7f6629bffdcb962d383ef8c9a30afef81e997fe ] I've acquired a Logitech MX Anywhere 3S mouse, which supports HID++ over Bluetooth. Adding its PID 0xb037 to the allowlist enables the additional features, such as high-resolution scrolling. Tested working across multiple machines, with a mix of Intel and Mediatek Bluetooth chips. [jkosina@suse.com: standardize shortlog] Signed-off-by: Dennis Marttinen Signed-off-by: Jiri Kosina Signed-off-by: Sasha Levin --- drivers/hid/hid-logitech-hidpp.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/drivers/hid/hid-logitech-hidpp.c b/drivers/hid/hid-logitech-hidpp.c index a88f2e5f791c6f..9b612f62d0fba3 100644 --- a/drivers/hid/hid-logitech-hidpp.c +++ b/drivers/hid/hid-logitech-hidpp.c @@ -4661,6 +4661,8 @@ static const struct hid_device_id hidpp_devices[] = { HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_LOGITECH, 0xb025) }, { /* MX Master 3S mouse over Bluetooth */ HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_LOGITECH, 0xb034) }, + { /* MX Anywhere 3S mouse over Bluetooth */ + HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_LOGITECH, 0xb037) }, { /* MX Anywhere 3SB mouse over Bluetooth */ HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_LOGITECH, 0xb038) }, {} From da1880c7b6b831a7df498864b5ccb1d9604792ac Mon Sep 17 00:00:00 2001 From: Arnoud Willemsen Date: Sun, 7 Dec 2025 03:43:19 +0100 Subject: [PATCH 3060/3902] HID: Elecom: Add support for ELECOM M-XT3DRBK (018C) [ Upstream commit 12adb969658ec39265eb8c7ea9e1856867fb9ceb ] Wireless/new version of the Elecom trackball mouse M-XT3DRBK has a product id that differs from the existing M-XT3DRBK. The report descriptor format also seems to have changed and matches other (newer?) models instead (except for six buttons instead of eight). This patch follows the same format as the patch for the M-XT3URBK (018F) by Naoki Ueki (Nov 3rd 2025) to enable the sixth mouse button. dmesg output: [ 292.074664] usb 1-2: new full-speed USB device number 7 using xhci_hcd [ 292.218667] usb 1-2: New USB device found, idVendor=056e, idProduct=018c, bcdDevice= 1.00 [ 292.218676] usb 1-2: New USB device strings: Mfr=1, Product=2, SerialNumber=0 [ 292.218679] usb 1-2: Product: ELECOM TrackBall Mouse [ 292.218681] usb 1-2: Manufacturer: ELECOM usbhid-dump output: 001:006:000:DESCRIPTOR 1765072638.050578 05 01 09 02 A1 01 09 01 A1 00 85 01 05 09 19 01 29 05 15 00 25 01 95 08 75 01 81 02 95 01 75 00 81 01 05 01 09 30 09 31 16 00 80 26 FF 7F 75 10 95 02 81 06 C0 A1 00 05 01 09 38 15 81 25 7F 75 08 95 01 81 06 C0 A1 00 05 0C 0A 38 02 95 01 75 08 15 81 25 7F 81 06 C0 C0 06 01 FF 09 00 A1 01 85 02 09 00 15 00 26 FF 00 75 08 95 07 81 02 C0 05 0C 09 01 A1 01 85 05 15 00 26 3C 02 19 00 2A 3C 02 75 10 95 01 81 00 C0 05 01 09 80 A1 01 85 03 19 81 29 83 15 00 25 01 95 03 75 01 81 02 95 01 75 05 81 01 C0 06 BC FF 09 88 A1 01 85 04 95 01 75 08 15 00 26 FF 00 19 00 2A FF 00 81 00 C0 06 02 FF 09 02 A1 01 85 06 09 02 15 00 26 FF 00 75 08 95 07 B1 02 C0 Signed-off-by: Arnoud Willemsen Signed-off-by: Jiri Kosina Signed-off-by: Sasha Levin --- drivers/hid/hid-elecom.c | 15 +++++++++++++-- drivers/hid/hid-ids.h | 3 ++- drivers/hid/hid-quirks.c | 3 ++- 3 files changed, 17 insertions(+), 4 deletions(-) diff --git a/drivers/hid/hid-elecom.c b/drivers/hid/hid-elecom.c index 981d1b6e96589c..2003d2dcda7cc7 100644 --- a/drivers/hid/hid-elecom.c +++ b/drivers/hid/hid-elecom.c @@ -77,7 +77,7 @@ static const __u8 *elecom_report_fixup(struct hid_device *hdev, __u8 *rdesc, break; case USB_DEVICE_ID_ELECOM_M_XT3URBK_00FB: case USB_DEVICE_ID_ELECOM_M_XT3URBK_018F: - case USB_DEVICE_ID_ELECOM_M_XT3DRBK: + case USB_DEVICE_ID_ELECOM_M_XT3DRBK_00FC: case USB_DEVICE_ID_ELECOM_M_XT4DRBK: /* * Report descriptor format: @@ -102,6 +102,16 @@ static const __u8 *elecom_report_fixup(struct hid_device *hdev, __u8 *rdesc, */ mouse_button_fixup(hdev, rdesc, *rsize, 12, 30, 14, 20, 8); break; + case USB_DEVICE_ID_ELECOM_M_XT3DRBK_018C: + /* + * Report descriptor format: + * 22: button bit count + * 30: padding bit count + * 24: button report size + * 16: button usage maximum + */ + mouse_button_fixup(hdev, rdesc, *rsize, 22, 30, 24, 16, 6); + break; case USB_DEVICE_ID_ELECOM_M_DT2DRBK: case USB_DEVICE_ID_ELECOM_M_HT1DRBK_011C: /* @@ -122,7 +132,8 @@ static const struct hid_device_id elecom_devices[] = { { HID_USB_DEVICE(USB_VENDOR_ID_ELECOM, USB_DEVICE_ID_ELECOM_M_XGL20DLBK) }, { HID_USB_DEVICE(USB_VENDOR_ID_ELECOM, USB_DEVICE_ID_ELECOM_M_XT3URBK_00FB) }, { HID_USB_DEVICE(USB_VENDOR_ID_ELECOM, USB_DEVICE_ID_ELECOM_M_XT3URBK_018F) }, - { HID_USB_DEVICE(USB_VENDOR_ID_ELECOM, USB_DEVICE_ID_ELECOM_M_XT3DRBK) }, + { HID_USB_DEVICE(USB_VENDOR_ID_ELECOM, USB_DEVICE_ID_ELECOM_M_XT3DRBK_00FC) }, + { HID_USB_DEVICE(USB_VENDOR_ID_ELECOM, USB_DEVICE_ID_ELECOM_M_XT3DRBK_018C) }, { HID_USB_DEVICE(USB_VENDOR_ID_ELECOM, USB_DEVICE_ID_ELECOM_M_XT4DRBK) }, { HID_USB_DEVICE(USB_VENDOR_ID_ELECOM, USB_DEVICE_ID_ELECOM_M_DT1URBK) }, { HID_USB_DEVICE(USB_VENDOR_ID_ELECOM, USB_DEVICE_ID_ELECOM_M_DT1DRBK) }, diff --git a/drivers/hid/hid-ids.h b/drivers/hid/hid-ids.h index bec913a005a5d6..b75d9d2f4dc731 100644 --- a/drivers/hid/hid-ids.h +++ b/drivers/hid/hid-ids.h @@ -455,7 +455,8 @@ #define USB_DEVICE_ID_ELECOM_M_XGL20DLBK 0x00e6 #define USB_DEVICE_ID_ELECOM_M_XT3URBK_00FB 0x00fb #define USB_DEVICE_ID_ELECOM_M_XT3URBK_018F 0x018f -#define USB_DEVICE_ID_ELECOM_M_XT3DRBK 0x00fc +#define USB_DEVICE_ID_ELECOM_M_XT3DRBK_00FC 0x00fc +#define USB_DEVICE_ID_ELECOM_M_XT3DRBK_018C 0x018c #define USB_DEVICE_ID_ELECOM_M_XT4DRBK 0x00fd #define USB_DEVICE_ID_ELECOM_M_DT1URBK 0x00fe #define USB_DEVICE_ID_ELECOM_M_DT1DRBK 0x00ff diff --git a/drivers/hid/hid-quirks.c b/drivers/hid/hid-quirks.c index 31b2a5d1cd98f6..11438039cdb7f7 100644 --- a/drivers/hid/hid-quirks.c +++ b/drivers/hid/hid-quirks.c @@ -422,7 +422,8 @@ static const struct hid_device_id hid_have_special_driver[] = { { HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_ELECOM, USB_DEVICE_ID_ELECOM_M_XGL20DLBK) }, { HID_USB_DEVICE(USB_VENDOR_ID_ELECOM, USB_DEVICE_ID_ELECOM_M_XT3URBK_00FB) }, { HID_USB_DEVICE(USB_VENDOR_ID_ELECOM, USB_DEVICE_ID_ELECOM_M_XT3URBK_018F) }, - { HID_USB_DEVICE(USB_VENDOR_ID_ELECOM, USB_DEVICE_ID_ELECOM_M_XT3DRBK) }, + { HID_USB_DEVICE(USB_VENDOR_ID_ELECOM, USB_DEVICE_ID_ELECOM_M_XT3DRBK_00FC) }, + { HID_USB_DEVICE(USB_VENDOR_ID_ELECOM, USB_DEVICE_ID_ELECOM_M_XT3DRBK_018C) }, { HID_USB_DEVICE(USB_VENDOR_ID_ELECOM, USB_DEVICE_ID_ELECOM_M_XT4DRBK) }, { HID_USB_DEVICE(USB_VENDOR_ID_ELECOM, USB_DEVICE_ID_ELECOM_M_DT1URBK) }, { HID_USB_DEVICE(USB_VENDOR_ID_ELECOM, USB_DEVICE_ID_ELECOM_M_DT1DRBK) }, From 74309a4b0ffc75af61a4fd440c8be0e6e3f0d8a2 Mon Sep 17 00:00:00 2001 From: Baochen Qiang Date: Mon, 22 Dec 2025 10:29:07 +0800 Subject: [PATCH 3061/3902] wifi: mac80211: collect station statistics earlier when disconnect [ Upstream commit a203dbeeca15a9b924f0d51f510921f4bae96801 ] In __sta_info_destroy_part2(), station statistics are requested after the IEEE80211_STA_NONE -> IEEE80211_STA_NOTEXIST transition. This is problematic because the driver may be unable to handle the request due to the STA being in the NOTEXIST state (i.e. if the driver destroys the underlying data when transitioning to NOTEXIST). Move the statistics collection to before the state transition to avoid this issue. Signed-off-by: Baochen Qiang Link: https://patch.msgid.link/20251222-mac80211-move-station-stats-collection-earlier-v1-1-12cd4e42c633@oss.qualcomm.com Signed-off-by: Johannes Berg Signed-off-by: Sasha Levin --- net/mac80211/sta_info.c | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/net/mac80211/sta_info.c b/net/mac80211/sta_info.c index f4d3b67fda062d..1a995bc301b19a 100644 --- a/net/mac80211/sta_info.c +++ b/net/mac80211/sta_info.c @@ -1533,6 +1533,10 @@ static void __sta_info_destroy_part2(struct sta_info *sta, bool recalc) } } + sinfo = kzalloc(sizeof(*sinfo), GFP_KERNEL); + if (sinfo) + sta_set_sinfo(sta, sinfo, true); + if (sta->uploaded) { ret = drv_sta_state(local, sdata, sta, IEEE80211_STA_NONE, IEEE80211_STA_NOTEXIST); @@ -1541,9 +1545,6 @@ static void __sta_info_destroy_part2(struct sta_info *sta, bool recalc) sta_dbg(sdata, "Removed STA %pM\n", sta->sta.addr); - sinfo = kzalloc(sizeof(*sinfo), GFP_KERNEL); - if (sinfo) - sta_set_sinfo(sta, sinfo, true); cfg80211_del_sta_sinfo(sdata->dev, sta->sta.addr, sinfo, GFP_KERNEL); kfree(sinfo); From 921903d73967f5081db67a998cc1bca7a486d446 Mon Sep 17 00:00:00 2001 From: Deep Harsora Date: Fri, 2 Jan 2026 15:21:24 +0000 Subject: [PATCH 3062/3902] ASoC: Intel: sof_sdw: Add new quirks for PTL on Dell with CS42L43 [ Upstream commit 12cacdfb023d1b2f6c4e5af471f2d5b6f0cbf909 ] Add missing quirks for some new Dell laptops using cs42l43's speaker outputs. Signed-off-by: Deep Harsora Signed-off-by: Maciej Strozek Link: https://patch.msgid.link/20260102152132.3053106-1-mstrozek@opensource.cirrus.com Signed-off-by: Mark Brown Signed-off-by: Sasha Levin --- sound/soc/intel/boards/sof_sdw.c | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/sound/soc/intel/boards/sof_sdw.c b/sound/soc/intel/boards/sof_sdw.c index c013e31d098e71..92fac7ed782f73 100644 --- a/sound/soc/intel/boards/sof_sdw.c +++ b/sound/soc/intel/boards/sof_sdw.c @@ -750,6 +750,14 @@ static const struct dmi_system_id sof_sdw_quirk_table[] = { .driver_data = (void *)(SOC_SDW_CODEC_SPKR), }, /* Pantherlake devices*/ + { + .callback = sof_sdw_quirk_cb, + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc"), + DMI_EXACT_MATCH(DMI_PRODUCT_SKU, "0DD6") + }, + .driver_data = (void *)(SOC_SDW_SIDECAR_AMPS), + }, { .callback = sof_sdw_quirk_cb, .matches = { From 9f665b3c3d9a168410251f27a5d019b7bf93185c Mon Sep 17 00:00:00 2001 From: Guodong Xu Date: Tue, 16 Dec 2025 22:10:06 +0800 Subject: [PATCH 3063/3902] dmaengine: mmp_pdma: Fix race condition in mmp_pdma_residue() [ Upstream commit a143545855bc2c6e1330f6f57ae375ac44af00a7 ] Add proper locking in mmp_pdma_residue() to prevent use-after-free when accessing descriptor list and descriptor contents. The race occurs when multiple threads call tx_status() while the tasklet on another CPU is freeing completed descriptors: CPU 0 CPU 1 ----- ----- mmp_pdma_tx_status() mmp_pdma_residue() -> NO LOCK held list_for_each_entry(sw, ..) DMA interrupt dma_do_tasklet() -> spin_lock(&desc_lock) list_move(sw->node, ...) spin_unlock(&desc_lock) | dma_pool_free(sw) <- FREED! -> access sw->desc <- UAF! This issue can be reproduced when running dmatest on the same channel with multiple threads (threads_per_chan > 1). Fix by protecting the chain_running list iteration and descriptor access with the chan->desc_lock spinlock. Signed-off-by: Juan Li Signed-off-by: Guodong Xu Link: https://patch.msgid.link/20251216-mmp-pdma-race-v1-1-976a224bb622@riscstar.com Signed-off-by: Vinod Koul Signed-off-by: Sasha Levin --- drivers/dma/mmp_pdma.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/drivers/dma/mmp_pdma.c b/drivers/dma/mmp_pdma.c index 86661eb3cde1ff..d12e729ee12c56 100644 --- a/drivers/dma/mmp_pdma.c +++ b/drivers/dma/mmp_pdma.c @@ -928,6 +928,7 @@ static unsigned int mmp_pdma_residue(struct mmp_pdma_chan *chan, { struct mmp_pdma_desc_sw *sw; struct mmp_pdma_device *pdev = to_mmp_pdma_dev(chan->chan.device); + unsigned long flags; u64 curr; u32 residue = 0; bool passed = false; @@ -945,6 +946,8 @@ static unsigned int mmp_pdma_residue(struct mmp_pdma_chan *chan, else curr = pdev->ops->read_src_addr(chan->phy); + spin_lock_irqsave(&chan->desc_lock, flags); + list_for_each_entry(sw, &chan->chain_running, node) { u64 start, end; u32 len; @@ -989,6 +992,7 @@ static unsigned int mmp_pdma_residue(struct mmp_pdma_chan *chan, continue; if (sw->async_tx.cookie == cookie) { + spin_unlock_irqrestore(&chan->desc_lock, flags); return residue; } else { residue = 0; @@ -996,6 +1000,8 @@ static unsigned int mmp_pdma_residue(struct mmp_pdma_chan *chan, } } + spin_unlock_irqrestore(&chan->desc_lock, flags); + /* We should only get here in case of cyclic transactions */ return residue; } From 61fa85497c7b7657464ba94beea749c1351781d2 Mon Sep 17 00:00:00 2001 From: Kery Qi Date: Wed, 7 Jan 2026 23:48:37 +0800 Subject: [PATCH 3064/3902] ASoC: davinci-evm: Fix reference leak in davinci_evm_probe [ Upstream commit 5b577d214fcc109707bcb77b4ae72a31cfd86798 ] The davinci_evm_probe() function calls of_parse_phandle() to acquire device nodes for "ti,audio-codec" and "ti,mcasp-controller". These functions return device nodes with incremented reference counts. However, in several error paths (e.g., when the second of_parse_phandle(), snd_soc_of_parse_card_name(), or devm_snd_soc_register_card() fails), the function returns directly without releasing the acquired nodes, leading to reference leaks. This patch adds an error handling path 'err_put' to properly release the device nodes using of_node_put() and clean up the pointers when an error occurs. Signed-off-by: Kery Qi Link: https://patch.msgid.link/20260107154836.1521-2-qikeyu2017@gmail.com Signed-off-by: Mark Brown Signed-off-by: Sasha Levin --- sound/soc/ti/davinci-evm.c | 39 ++++++++++++++++++++++++++++++-------- 1 file changed, 31 insertions(+), 8 deletions(-) diff --git a/sound/soc/ti/davinci-evm.c b/sound/soc/ti/davinci-evm.c index 2a2f5bc95576ea..a55a369ce71c27 100644 --- a/sound/soc/ti/davinci-evm.c +++ b/sound/soc/ti/davinci-evm.c @@ -193,27 +193,32 @@ static int davinci_evm_probe(struct platform_device *pdev) return -EINVAL; dai->cpus->of_node = of_parse_phandle(np, "ti,mcasp-controller", 0); - if (!dai->cpus->of_node) - return -EINVAL; + if (!dai->cpus->of_node) { + ret = -EINVAL; + goto err_put; + } dai->platforms->of_node = dai->cpus->of_node; evm_soc_card.dev = &pdev->dev; ret = snd_soc_of_parse_card_name(&evm_soc_card, "ti,model"); if (ret) - return ret; + goto err_put; mclk = devm_clk_get(&pdev->dev, "mclk"); if (PTR_ERR(mclk) == -EPROBE_DEFER) { - return -EPROBE_DEFER; + ret = -EPROBE_DEFER; + goto err_put; } else if (IS_ERR(mclk)) { dev_dbg(&pdev->dev, "mclk not found.\n"); mclk = NULL; } drvdata = devm_kzalloc(&pdev->dev, sizeof(*drvdata), GFP_KERNEL); - if (!drvdata) - return -ENOMEM; + if (!drvdata) { + ret = -ENOMEM; + goto err_put; + } drvdata->mclk = mclk; @@ -223,7 +228,8 @@ static int davinci_evm_probe(struct platform_device *pdev) if (!drvdata->mclk) { dev_err(&pdev->dev, "No clock or clock rate defined.\n"); - return -EINVAL; + ret = -EINVAL; + goto err_put; } drvdata->sysclk = clk_get_rate(drvdata->mclk); } else if (drvdata->mclk) { @@ -239,8 +245,25 @@ static int davinci_evm_probe(struct platform_device *pdev) snd_soc_card_set_drvdata(&evm_soc_card, drvdata); ret = devm_snd_soc_register_card(&pdev->dev, &evm_soc_card); - if (ret) + if (ret) { dev_err(&pdev->dev, "snd_soc_register_card failed (%d)\n", ret); + goto err_put; + } + + return ret; + +err_put: + dai->platforms->of_node = NULL; + + if (dai->cpus->of_node) { + of_node_put(dai->cpus->of_node); + dai->cpus->of_node = NULL; + } + + if (dai->codecs->of_node) { + of_node_put(dai->codecs->of_node); + dai->codecs->of_node = NULL; + } return ret; } From a883080063f9785a42ad3ed8d7bae5719b75ea09 Mon Sep 17 00:00:00 2001 From: Shengjiu Wang Date: Mon, 29 Dec 2025 17:04:32 +0800 Subject: [PATCH 3065/3902] ASoC: simple-card-utils: Check device node before overwrite direction [ Upstream commit 22a507d7680f2c3499c133f6384349f62f916176 ] Even the device node don't exist, the graph_util_parse_link_direction() will overwrite the playback_only and capture_only to be zero. Which cause the playback_only and capture_only are not correct, so check device node exist or not before update the value. Signed-off-by: Shengjiu Wang Acked-by: Kuninori Morimoto Link: https://patch.msgid.link/20251229090432.3964848-1-shengjiu.wang@nxp.com Signed-off-by: Mark Brown Signed-off-by: Sasha Levin --- sound/soc/generic/simple-card-utils.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/sound/soc/generic/simple-card-utils.c b/sound/soc/generic/simple-card-utils.c index 355f7ec8943c24..bdc02e85b089fe 100644 --- a/sound/soc/generic/simple-card-utils.c +++ b/sound/soc/generic/simple-card-utils.c @@ -1179,9 +1179,9 @@ void graph_util_parse_link_direction(struct device_node *np, bool is_playback_only = of_property_read_bool(np, "playback-only"); bool is_capture_only = of_property_read_bool(np, "capture-only"); - if (playback_only) + if (np && playback_only) *playback_only = is_playback_only; - if (capture_only) + if (np && capture_only) *capture_only = is_capture_only; } EXPORT_SYMBOL_GPL(graph_util_parse_link_direction); From e810b290922c535feb34bc90ab549446fe94d2a3 Mon Sep 17 00:00:00 2001 From: Chaitanya Kulkarni Date: Fri, 19 Dec 2025 16:18:42 -0800 Subject: [PATCH 3066/3902] nvme-fc: release admin tagset if init fails [ Upstream commit d1877cc7270302081a315a81a0ee8331f19f95c8 ] nvme_fabrics creates an NVMe/FC controller in following path: nvmf_dev_write() -> nvmf_create_ctrl() -> nvme_fc_create_ctrl() -> nvme_fc_init_ctrl() nvme_fc_init_ctrl() allocates the admin blk-mq resources right after nvme_add_ctrl() succeeds. If any of the subsequent steps fail (changing the controller state, scheduling connect work, etc.), we jump to the fail_ctrl path, which tears down the controller references but never frees the admin queue/tag set. The leaked blk-mq allocations match the kmemleak report seen during blktests nvme/fc. Check ctrl->ctrl.admin_tagset in the fail_ctrl path and call nvme_remove_admin_tag_set() when it is set so that all admin queue allocations are reclaimed whenever controller setup aborts. Reported-by: Yi Zhang Reviewed-by: Justin Tee Signed-off-by: Chaitanya Kulkarni Signed-off-by: Keith Busch Signed-off-by: Sasha Levin --- drivers/nvme/host/fc.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/drivers/nvme/host/fc.c b/drivers/nvme/host/fc.c index 8324230c53719e..bf78faf1a4ffa6 100644 --- a/drivers/nvme/host/fc.c +++ b/drivers/nvme/host/fc.c @@ -3584,6 +3584,8 @@ nvme_fc_init_ctrl(struct device *dev, struct nvmf_ctrl_options *opts, ctrl->ctrl.opts = NULL; + if (ctrl->ctrl.admin_tagset) + nvme_remove_admin_tag_set(&ctrl->ctrl); /* initiate nvme ctrl ref counting teardown */ nvme_uninit_ctrl(&ctrl->ctrl); From 62932d9ed639a9fa71b4ac1a56766a4b43abb7e4 Mon Sep 17 00:00:00 2001 From: Edward Adam Davis Date: Tue, 13 Jan 2026 16:29:23 +0800 Subject: [PATCH 3067/3902] ALSA: usb-audio: Prevent excessive number of frames [ Upstream commit ef5749ef8b307bf8717945701b1b79d036af0a15 ] In this case, the user constructed the parameters with maxpacksize 40 for rate 22050 / pps 1000, and packsize[0] 22 packsize[1] 23. The buffer size for each data URB is maxpacksize * packets, which in this example is 40 * 6 = 240; When the user performs a write operation to send audio data into the ALSA PCM playback stream, the calculated number of frames is packsize[0] * packets = 264, which exceeds the allocated URB buffer size, triggering the out-of-bounds (OOB) issue reported by syzbot [1]. Added a check for the number of single data URB frames when calculating the number of frames to prevent [1]. [1] BUG: KASAN: slab-out-of-bounds in copy_to_urb+0x261/0x460 sound/usb/pcm.c:1487 Write of size 264 at addr ffff88804337e800 by task syz.0.17/5506 Call Trace: copy_to_urb+0x261/0x460 sound/usb/pcm.c:1487 prepare_playback_urb+0x953/0x13d0 sound/usb/pcm.c:1611 prepare_outbound_urb+0x377/0xc50 sound/usb/endpoint.c:333 Reported-by: syzbot+6db0415d6d5c635f72cb@syzkaller.appspotmail.com Closes: https://syzkaller.appspot.com/bug?extid=6db0415d6d5c635f72cb Tested-by: syzbot+6db0415d6d5c635f72cb@syzkaller.appspotmail.com Signed-off-by: Edward Adam Davis Link: https://patch.msgid.link/tencent_9AECE6CD2C7A826D902D696C289724E8120A@qq.com Signed-off-by: Takashi Iwai Signed-off-by: Sasha Levin --- sound/usb/pcm.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sound/usb/pcm.c b/sound/usb/pcm.c index 54d01dfd820fa7..263abb36bb2d1c 100644 --- a/sound/usb/pcm.c +++ b/sound/usb/pcm.c @@ -1553,7 +1553,7 @@ static int prepare_playback_urb(struct snd_usb_substream *subs, for (i = 0; i < ctx->packets; i++) { counts = snd_usb_endpoint_next_packet_size(ep, ctx, i, avail); - if (counts < 0) + if (counts < 0 || frames + counts >= ep->max_urb_frames) break; /* set up descriptor */ urb->iso_frame_desc[i].offset = frames * stride; From 1c90f930e7b410dd2d75a2a19a85e19c64e98ad5 Mon Sep 17 00:00:00 2001 From: Hannes Reinecke Date: Mon, 18 Aug 2025 11:32:45 +0200 Subject: [PATCH 3068/3902] nvmet-tcp: fixup hang in nvmet_tcp_listen_data_ready() [ Upstream commit 2fa8961d3a6a1c2395d8d560ffed2c782681bade ] When the socket is closed while in TCP_LISTEN a callback is run to flush all outstanding packets, which in turns calls nvmet_tcp_listen_data_ready() with the sk_callback_lock held. So we need to check if we are in TCP_LISTEN before attempting to get the sk_callback_lock() to avoid a deadlock. Link: https://lore.kernel.org/linux-nvme/CAHj4cs-zu7eVB78yUpFjVe2UqMWFkLk8p+DaS3qj+uiGCXBAoA@mail.gmail.com/ Tested-by: Yi Zhang Reviewed-by: Sagi Grimberg Signed-off-by: Hannes Reinecke Signed-off-by: Keith Busch Signed-off-by: Sasha Levin --- drivers/nvme/target/tcp.c | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/drivers/nvme/target/tcp.c b/drivers/nvme/target/tcp.c index f0572fc0b65984..5c8d17bcc49bd5 100644 --- a/drivers/nvme/target/tcp.c +++ b/drivers/nvme/target/tcp.c @@ -2021,14 +2021,13 @@ static void nvmet_tcp_listen_data_ready(struct sock *sk) trace_sk_data_ready(sk); + if (sk->sk_state != TCP_LISTEN) + return; + read_lock_bh(&sk->sk_callback_lock); port = sk->sk_user_data; - if (!port) - goto out; - - if (sk->sk_state == TCP_LISTEN) + if (port) queue_work(nvmet_wq, &port->accept_work); -out: read_unlock_bh(&sk->sk_callback_lock); } From 3ba3d959c17aa02480879278419896bb2269bada Mon Sep 17 00:00:00 2001 From: Radhi Bajahaw Date: Mon, 12 Jan 2026 21:38:14 +0100 Subject: [PATCH 3069/3902] ASoC: amd: yc: Fix microphone on ASUS M6500RE [ Upstream commit 8e29db1b08808f709231e6fd4c79dcdee5b17a17 ] Add DMI match for ASUSTeK COMPUTER INC. M6500RE to enable the internal microphone. Signed-off-by: Radhi Bajahaw Link: https://patch.msgid.link/20260112203814.155-1-bajahawradhi@gmail.com Signed-off-by: Mark Brown Signed-off-by: Sasha Levin --- sound/soc/amd/yc/acp6x-mach.c | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/sound/soc/amd/yc/acp6x-mach.c b/sound/soc/amd/yc/acp6x-mach.c index c0a8afb42e1654..c4a4a06528b45d 100644 --- a/sound/soc/amd/yc/acp6x-mach.c +++ b/sound/soc/amd/yc/acp6x-mach.c @@ -416,6 +416,13 @@ static const struct dmi_system_id yc_acp_quirk_table[] = { DMI_MATCH(DMI_PRODUCT_NAME, "M6500RC"), } }, + { + .driver_data = &acp6x_card, + .matches = { + DMI_MATCH(DMI_BOARD_VENDOR, "ASUSTeK COMPUTER INC."), + DMI_MATCH(DMI_PRODUCT_NAME, "M6500RE"), + } + }, { .driver_data = &acp6x_card, .matches = { From e6ce61e01c9a22a281c023f44fb21f4902891699 Mon Sep 17 00:00:00 2001 From: Dimitrios Katsaros Date: Tue, 13 Jan 2026 11:58:46 +0100 Subject: [PATCH 3070/3902] ASoC: tlv320adcx140: Propagate error codes during probe [ Upstream commit d89aad92cfd15edbd704746f44c98fe687f9366f ] When scanning for the reset pin, we could get an -EPROBE_DEFER. The driver would assume that no reset pin had been defined, which would mean that the chip would never be powered. Now we both respect any error we get from devm_gpiod_get_optional. We also now properly report the missing GPIO definition when 'gpio_reset' is NULL. Signed-off-by: Dimitrios Katsaros Signed-off-by: Sascha Hauer Link: https://patch.msgid.link/20260113-sound-soc-codecs-tvl320adcx140-v4-3-8f7ecec525c8@pengutronix.de Signed-off-by: Mark Brown Signed-off-by: Sasha Levin --- sound/soc/codecs/tlv320adcx140.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/sound/soc/codecs/tlv320adcx140.c b/sound/soc/codecs/tlv320adcx140.c index 62d936c2838c96..1565727ca2f3d2 100644 --- a/sound/soc/codecs/tlv320adcx140.c +++ b/sound/soc/codecs/tlv320adcx140.c @@ -1156,6 +1156,9 @@ static int adcx140_i2c_probe(struct i2c_client *i2c) adcx140->gpio_reset = devm_gpiod_get_optional(adcx140->dev, "reset", GPIOD_OUT_LOW); if (IS_ERR(adcx140->gpio_reset)) + return dev_err_probe(&i2c->dev, PTR_ERR(adcx140->gpio_reset), + "Failed to get Reset GPIO\n"); + if (!adcx140->gpio_reset) dev_info(&i2c->dev, "Reset GPIO not defined\n"); adcx140->supply_areg = devm_regulator_get_optional(adcx140->dev, From 66b73d3f2cfc7c850221abc7bd7fd3cb88347164 Mon Sep 17 00:00:00 2001 From: Shenghao Ding Date: Thu, 15 Jan 2026 20:49:06 +0800 Subject: [PATCH 3071/3902] ALSA: hda/tas2781: Add newly-released HP laptop [ Upstream commit 46b8d0888f01f250fbd24d00ff80b755c3c42cd4 ] HP released the new laptop with the subid 0x103C. Signed-off-by: Shenghao Ding Link: https://patch.msgid.link/20260115124907.629-1-shenghao-ding@ti.com Signed-off-by: Takashi Iwai Signed-off-by: Sasha Levin --- sound/hda/codecs/side-codecs/tas2781_hda_i2c.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/sound/hda/codecs/side-codecs/tas2781_hda_i2c.c b/sound/hda/codecs/side-codecs/tas2781_hda_i2c.c index 0e4bda3a544ea4..624a822341bb71 100644 --- a/sound/hda/codecs/side-codecs/tas2781_hda_i2c.c +++ b/sound/hda/codecs/side-codecs/tas2781_hda_i2c.c @@ -2,7 +2,7 @@ // // TAS2781 HDA I2C driver // -// Copyright 2023 - 2025 Texas Instruments, Inc. +// Copyright 2023 - 2026 Texas Instruments, Inc. // // Author: Shenghao Ding // Current maintainer: Baojun Xu @@ -571,6 +571,9 @@ static int tas2781_hda_bind(struct device *dev, struct device *master, case 0x1028: tas_hda->catlog_id = DELL; break; + case 0x103C: + tas_hda->catlog_id = HP; + break; default: tas_hda->catlog_id = LENOVO; break; From 7d4c9c448c2b3da71515e615a0e80e40e5949160 Mon Sep 17 00:00:00 2001 From: Devyn Liu Date: Thu, 8 Jan 2026 15:53:23 +0800 Subject: [PATCH 3072/3902] spi: hisi-kunpeng: Fixed the wrong debugfs node name in hisi_spi debugfs initialization [ Upstream commit b062a899c997df7b9ce29c62164888baa7a85833 ] In hisi_spi_debugfs_init, spi controller pointer is calculated by container_of macro, and the member is hs->dev. But the host cannot be calculated offset directly by this. (hs->dev) points to (pdev->dev), and it is the (host->dev.parent) rather than (host->dev) points to the (pdev->dev), which is set in __spi_alloc_controller. In this patch, this issues is fixed by getting the spi_controller data from pdev->dev by dev_get_drvdata() directly. (dev->driver_data) points to the spi controller data in the probe stage. Signed-off-by: Devyn Liu Reviewed-by: Yang Shen Link: https://patch.msgid.link/20260108075323.3831574-1-liudingyuan@h-partners.com Signed-off-by: Mark Brown Signed-off-by: Sasha Levin --- drivers/spi/spi-hisi-kunpeng.c | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/drivers/spi/spi-hisi-kunpeng.c b/drivers/spi/spi-hisi-kunpeng.c index dadf558dd9c0c6..80a1a15de0bc33 100644 --- a/drivers/spi/spi-hisi-kunpeng.c +++ b/drivers/spi/spi-hisi-kunpeng.c @@ -161,10 +161,8 @@ static const struct debugfs_reg32 hisi_spi_regs[] = { static int hisi_spi_debugfs_init(struct hisi_spi *hs) { char name[32]; + struct spi_controller *host = dev_get_drvdata(hs->dev); - struct spi_controller *host; - - host = container_of(hs->dev, struct spi_controller, dev); snprintf(name, 32, "hisi_spi%d", host->bus_num); hs->debugfs = debugfs_create_dir(name, NULL); if (IS_ERR(hs->debugfs)) From f08f2d2907675926ac5657b25f86d921f269602a Mon Sep 17 00:00:00 2001 From: Kaushlendra Kumar Date: Mon, 5 Jan 2026 08:48:20 +0530 Subject: [PATCH 3073/3902] regmap: maple: free entry on mas_store_gfp() failure [ Upstream commit f3f380ce6b3d5c9805c7e0b3d5bc28d9ec41e2e8 ] regcache_maple_write() allocates a new block ('entry') to merge adjacent ranges and then stores it with mas_store_gfp(). When mas_store_gfp() fails, the new 'entry' remains allocated and is never freed, leaking memory. Free 'entry' on the failure path; on success continue freeing the replaced neighbor blocks ('lower', 'upper'). Signed-off-by: Kaushlendra Kumar Link: https://patch.msgid.link/20260105031820.260119-1-kaushlendra.kumar@intel.com Signed-off-by: Mark Brown Signed-off-by: Sasha Levin --- drivers/base/regmap/regcache-maple.c | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/drivers/base/regmap/regcache-maple.c b/drivers/base/regmap/regcache-maple.c index 2319c30283a6d9..9cf0384ce7b95b 100644 --- a/drivers/base/regmap/regcache-maple.c +++ b/drivers/base/regmap/regcache-maple.c @@ -95,12 +95,13 @@ static int regcache_maple_write(struct regmap *map, unsigned int reg, mas_unlock(&mas); - if (ret == 0) { - kfree(lower); - kfree(upper); + if (ret) { + kfree(entry); + return ret; } - - return ret; + kfree(lower); + kfree(upper); + return 0; } static int regcache_maple_drop(struct regmap *map, unsigned int min, From 7178b36de185029d09f33c4fbd3f7e9ae59ae913 Mon Sep 17 00:00:00 2001 From: Lianqin Hu Date: Fri, 16 Jan 2026 06:33:03 +0000 Subject: [PATCH 3074/3902] ALSA: usb-audio: Add delay quirk for MOONDROP Moonriver2 Ti [ Upstream commit 49985bc466b51af88d534485631c8cd8c9c65f43 ] Audio control requests that sets sampling frequency sometimes fail on this card. Adding delay between control messages eliminates that problem. usb 1-1: New USB device found, idVendor=2fc6, idProduct=f06b usb 1-1: New USB device strings: Mfr=1, Product=2, SerialNumber=3 usb 1-1: Product: MOONDROP Moonriver2 Ti usb 1-1: Manufacturer: MOONDROP usb 1-1: SerialNumber: MOONDROP Moonriver2 Ti Signed-off-by: Lianqin Hu Reviewed-by: Cryolitia PukNgae Signed-off-by: Takashi Iwai Link: https://patch.msgid.link/TYUPR06MB6217911EFC7E9224935FA507D28DA@TYUPR06MB6217.apcprd06.prod.outlook.com Signed-off-by: Sasha Levin --- sound/usb/quirks.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/sound/usb/quirks.c b/sound/usb/quirks.c index 94a8fdc9c6d3ce..8a646891ebb44f 100644 --- a/sound/usb/quirks.c +++ b/sound/usb/quirks.c @@ -2390,6 +2390,8 @@ static const struct usb_audio_quirk_flags_table quirk_flags_table[] = { QUIRK_FLAG_CTL_MSG_DELAY_1M), DEVICE_FLG(0x2d99, 0x0026, /* HECATE G2 GAMING HEADSET */ QUIRK_FLAG_MIXER_PLAYBACK_MIN_MUTE), + DEVICE_FLG(0x2fc6, 0xf06b, /* MOONDROP Moonriver2 Ti */ + QUIRK_FLAG_CTL_MSG_DELAY), DEVICE_FLG(0x2fc6, 0xf0b7, /* iBasso DC07 Pro */ QUIRK_FLAG_CTL_MSG_DELAY_1M), DEVICE_FLG(0x30be, 0x0101, /* Schiit Hel */ From 36e88bd40a8c0b74a6dd77b0eb46e28391ecb5b2 Mon Sep 17 00:00:00 2001 From: Alan Borzeszkowski Date: Thu, 15 Jan 2026 13:03:05 +0100 Subject: [PATCH 3075/3902] spi: intel-pci: Add support for Nova Lake SPI serial flash [ Upstream commit caa329649259d0f90c0056c9860ca659d4ba3211 ] Add Intel Nova Lake PCH-S SPI serial flash PCI ID to the list of supported devices. This is the same controller found in previous generations. Signed-off-by: Alan Borzeszkowski Acked-by: Mika Westerberg Link: https://patch.msgid.link/20260115120305.10080-1-alan.borzeszkowski@linux.intel.com Signed-off-by: Mark Brown Signed-off-by: Sasha Levin --- drivers/spi/spi-intel-pci.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/spi/spi-intel-pci.c b/drivers/spi/spi-intel-pci.c index b8c572394aac20..bce3d149bea180 100644 --- a/drivers/spi/spi-intel-pci.c +++ b/drivers/spi/spi-intel-pci.c @@ -81,6 +81,7 @@ static const struct pci_device_id intel_spi_pci_ids[] = { { PCI_VDEVICE(INTEL, 0x54a4), (unsigned long)&cnl_info }, { PCI_VDEVICE(INTEL, 0x5794), (unsigned long)&cnl_info }, { PCI_VDEVICE(INTEL, 0x5825), (unsigned long)&cnl_info }, + { PCI_VDEVICE(INTEL, 0x6e24), (unsigned long)&cnl_info }, { PCI_VDEVICE(INTEL, 0x7723), (unsigned long)&cnl_info }, { PCI_VDEVICE(INTEL, 0x7a24), (unsigned long)&cnl_info }, { PCI_VDEVICE(INTEL, 0x7aa4), (unsigned long)&cnl_info }, From 8a7ef96e6af91f0fdd3aa9ec25a45ec2431f718d Mon Sep 17 00:00:00 2001 From: Veerendranath Jakkam Date: Fri, 9 Jan 2026 20:30:04 +0530 Subject: [PATCH 3076/3902] wifi: cfg80211: Fix bitrate calculation overflow for HE rates [ Upstream commit a3034bf0746d88a00cceda9541534a5721445a24 ] An integer overflow occurs in cfg80211_calculate_bitrate_he() when calculating bitrates for high throughput HE configurations. For example, with 160 MHz bandwidth, HE-MCS 13, HE-NSS 4, and HE-GI 0, the multiplication (result * rate->nss) overflows the 32-bit 'result' variable before division by 8, leading to significantly underestimated bitrate values. The overflow occurs because the NSS multiplication operates on a 32-bit integer that cannot accommodate intermediate values exceeding 4,294,967,295. When overflow happens, the value wraps around, producing incorrect bitrates for high MCS and NSS combinations. Fix this by utilizing the 64-bit 'tmp' variable for the NSS multiplication and subsequent divisions via do_div(). This approach preserves full precision throughout the entire calculation, with the final value assigned to 'result' only after completing all operations. Signed-off-by: Veerendranath Jakkam Link: https://patch.msgid.link/20260109-he_bitrate_overflow-v1-1-95575e466b6e@oss.qualcomm.com Signed-off-by: Johannes Berg Signed-off-by: Sasha Levin --- net/wireless/util.c | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/net/wireless/util.c b/net/wireless/util.c index 4eb028ad168366..81d6d27d273cc8 100644 --- a/net/wireless/util.c +++ b/net/wireless/util.c @@ -1561,12 +1561,14 @@ static u32 cfg80211_calculate_bitrate_he(struct rate_info *rate) tmp = result; tmp *= SCALE; do_div(tmp, mcs_divisors[rate->mcs]); - result = tmp; /* and take NSS, DCM into account */ - result = (result * rate->nss) / 8; + tmp *= rate->nss; + do_div(tmp, 8); if (rate->he_dcm) - result /= 2; + do_div(tmp, 2); + + result = tmp; return result / 10000; } From 4530f4e4d0e6a207110b0ffed0c911bca43531a4 Mon Sep 17 00:00:00 2001 From: Maurizio Lombardi Date: Mon, 12 Jan 2026 17:53:52 +0100 Subject: [PATCH 3077/3902] scsi: target: iscsi: Fix use-after-free in iscsit_dec_session_usage_count() [ Upstream commit 84dc6037390b8607c5551047d3970336cb51ba9a ] In iscsit_dec_session_usage_count(), the function calls complete() while holding the sess->session_usage_lock. Similar to the connection usage count logic, the waiter signaled by complete() (e.g., in the session release path) may wake up and free the iscsit_session structure immediately. This creates a race condition where the current thread may attempt to execute spin_unlock_bh() on a session structure that has already been deallocated, resulting in a KASAN slab-use-after-free. To resolve this, release the session_usage_lock before calling complete() to ensure all dereferences of the sess pointer are finished before the waiter is allowed to proceed with deallocation. Signed-off-by: Maurizio Lombardi Reported-by: Zhaojuan Guo Reviewed-by: Mike Christie Link: https://patch.msgid.link/20260112165352.138606-3-mlombard@redhat.com Signed-off-by: Martin K. Petersen Signed-off-by: Sasha Levin --- drivers/target/iscsi/iscsi_target_util.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/drivers/target/iscsi/iscsi_target_util.c b/drivers/target/iscsi/iscsi_target_util.c index 5e6cf34929b55b..262a3e76b4b1c5 100644 --- a/drivers/target/iscsi/iscsi_target_util.c +++ b/drivers/target/iscsi/iscsi_target_util.c @@ -741,8 +741,11 @@ void iscsit_dec_session_usage_count(struct iscsit_session *sess) spin_lock_bh(&sess->session_usage_lock); sess->session_usage_count--; - if (!sess->session_usage_count && sess->session_waiting_on_uc) + if (!sess->session_usage_count && sess->session_waiting_on_uc) { + spin_unlock_bh(&sess->session_usage_lock); complete(&sess->session_waiting_on_uc_comp); + return; + } spin_unlock_bh(&sess->session_usage_lock); } From c65a1a72a41e4a74f481a61815a03fde9ce4f7d0 Mon Sep 17 00:00:00 2001 From: Nathan Chancellor Date: Fri, 16 Jan 2026 15:44:34 -0700 Subject: [PATCH 3078/3902] riscv: Use 64-bit variable for output in __get_user_asm [ Upstream commit bdce162f2e57a969803e5e9375999a3e0546905f ] After commit f6bff7827a48 ("riscv: uaccess: use 'asm_goto_output' for get_user()"), which was the first commit that started using asm goto with outputs on RISC-V, builds of clang built with assertions enabled start crashing in certain files that use get_user() with: clang: llvm/lib/CodeGen/SelectionDAG/SelectionDAGBuilder.cpp:12743: Register FollowCopyChain(MachineRegisterInfo &, Register): Assertion `MI->getOpcode() == TargetOpcode::COPY && "start of copy chain MUST be COPY"' failed. Internally, LLVM generates an addiw instruction when the output of the inline asm (which may be any scalar type) needs to be sign extended for ABI reasons, such as a later function call, so that basic block does not have to do it. Use a temporary 64-bit variable as the output of the inline assembly in __get_user_asm() and explicitly cast it to truncate it if necessary, avoiding the addiw that triggers the assertion. Link: https://github.com/ClangBuiltLinux/linux/issues/2092 Signed-off-by: Nathan Chancellor Link: https://patch.msgid.link/20260116-riscv-wa-llvm-asm-goto-outputs-assertion-failure-v3-1-55b5775f989b@kernel.org Signed-off-by: Paul Walmsley Signed-off-by: Sasha Levin --- arch/riscv/include/asm/uaccess.h | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/arch/riscv/include/asm/uaccess.h b/arch/riscv/include/asm/uaccess.h index f5f4f7f85543f2..1029c31026dcfb 100644 --- a/arch/riscv/include/asm/uaccess.h +++ b/arch/riscv/include/asm/uaccess.h @@ -97,13 +97,23 @@ static inline unsigned long __untagged_addr_remote(struct mm_struct *mm, unsigne */ #ifdef CONFIG_CC_HAS_ASM_GOTO_OUTPUT +/* + * Use a temporary variable for the output of the asm goto to avoid a + * triggering an LLVM assertion due to sign extending the output when + * it is used in later function calls: + * https://github.com/llvm/llvm-project/issues/143795 + */ #define __get_user_asm(insn, x, ptr, label) \ +do { \ + u64 __tmp; \ asm_goto_output( \ "1:\n" \ " " insn " %0, %1\n" \ _ASM_EXTABLE_UACCESS_ERR(1b, %l2, %0) \ - : "=&r" (x) \ - : "m" (*(ptr)) : : label) + : "=&r" (__tmp) \ + : "m" (*(ptr)) : : label); \ + (x) = (__typeof__(x))__tmp; \ +} while (0) #else /* !CONFIG_CC_HAS_ASM_GOTO_OUTPUT */ #define __get_user_asm(insn, x, ptr, label) \ do { \ From 1d5f2329ab4df65c2ee011b986d8a6e05ad0f67c Mon Sep 17 00:00:00 2001 From: Jens Axboe Date: Sun, 18 Jan 2026 19:48:01 -0700 Subject: [PATCH 3079/3902] io_uring/rw: free potentially allocated iovec on cache put failure [ Upstream commit 4b9748055457ac3a0710bf210c229d01ea1b01b9 ] If a read/write request goes through io_req_rw_cleanup() and has an allocated iovec attached and fails to put to the rw_cache, then it may end up with an unaccounted iovec pointer. Have io_rw_recycle() return whether it recycled the request or not, and use that to gauge whether to free a potential iovec or not. Reviewed-by: Nitesh Shetty Signed-off-by: Jens Axboe Signed-off-by: Sasha Levin --- io_uring/rw.c | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/io_uring/rw.c b/io_uring/rw.c index abe68ba9c9dc85..d7388a4a3ea5e0 100644 --- a/io_uring/rw.c +++ b/io_uring/rw.c @@ -144,19 +144,22 @@ static inline int io_import_rw_buffer(int rw, struct io_kiocb *req, return 0; } -static void io_rw_recycle(struct io_kiocb *req, unsigned int issue_flags) +static bool io_rw_recycle(struct io_kiocb *req, unsigned int issue_flags) { struct io_async_rw *rw = req->async_data; if (unlikely(issue_flags & IO_URING_F_UNLOCKED)) - return; + return false; io_alloc_cache_vec_kasan(&rw->vec); if (rw->vec.nr > IO_VEC_CACHE_SOFT_CAP) io_vec_free(&rw->vec); - if (io_alloc_cache_put(&req->ctx->rw_cache, rw)) + if (io_alloc_cache_put(&req->ctx->rw_cache, rw)) { io_req_async_data_clear(req, 0); + return true; + } + return false; } static void io_req_rw_cleanup(struct io_kiocb *req, unsigned int issue_flags) @@ -190,7 +193,11 @@ static void io_req_rw_cleanup(struct io_kiocb *req, unsigned int issue_flags) */ if (!(req->flags & (REQ_F_REISSUE | REQ_F_REFCOUNT))) { req->flags &= ~REQ_F_NEED_CLEANUP; - io_rw_recycle(req, issue_flags); + if (!io_rw_recycle(req, issue_flags)) { + struct io_async_rw *rw = req->async_data; + + io_vec_free(&rw->vec); + } } } From 108cbf2b7d2954eb2346dc58038b5a58679461e7 Mon Sep 17 00:00:00 2001 From: Tim Guttzeit Date: Mon, 19 Jan 2026 16:15:55 +0100 Subject: [PATCH 3080/3902] ALSA: hda/realtek: Fix headset mic for TongFang X6AR55xU [ Upstream commit b48fe9af1e60360baf09ca6b7a3cd6541f16e611 ] Add a PCI quirk to enable microphone detection on the headphone jack of TongFang X6AR55xU devices. Signed-off-by: Tim Guttzeit Signed-off-by: Werner Sembach Link: https://patch.msgid.link/20260119151626.35481-1-wse@tuxedocomputers.com Signed-off-by: Takashi Iwai Signed-off-by: Sasha Levin --- sound/hda/codecs/realtek/alc269.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/sound/hda/codecs/realtek/alc269.c b/sound/hda/codecs/realtek/alc269.c index dc2e3ede7a23bd..e9022f751c959c 100644 --- a/sound/hda/codecs/realtek/alc269.c +++ b/sound/hda/codecs/realtek/alc269.c @@ -7679,6 +7679,10 @@ static const struct snd_hda_pin_quirk alc269_pin_fixup_tbl[] = { {0x12, 0x90a60140}, {0x19, 0x04a11030}, {0x21, 0x04211020}), + SND_HDA_PIN_QUIRK(0x10ec0274, 0x1d05, "TongFang", ALC274_FIXUP_HP_HEADSET_MIC, + {0x17, 0x90170110}, + {0x19, 0x03a11030}, + {0x21, 0x03211020}), SND_HDA_PIN_QUIRK(0x10ec0282, 0x1025, "Acer", ALC282_FIXUP_ACER_DISABLE_LINEOUT, ALC282_STANDARD_PINS, {0x12, 0x90a609c0}, From 3835e49e146a4e6e7787b29465f1a23379b6ec44 Mon Sep 17 00:00:00 2001 From: Maurizio Lombardi Date: Mon, 12 Jan 2026 17:53:51 +0100 Subject: [PATCH 3081/3902] scsi: target: iscsi: Fix use-after-free in iscsit_dec_conn_usage_count() [ Upstream commit 9411a89e9e7135cc459178fa77a3f1d6191ae903 ] In iscsit_dec_conn_usage_count(), the function calls complete() while holding the conn->conn_usage_lock. As soon as complete() is invoked, the waiter (such as iscsit_close_connection()) may wake up and proceed to free the iscsit_conn structure. If the waiter frees the memory before the current thread reaches spin_unlock_bh(), it results in a KASAN slab-use-after-free as the function attempts to release a lock within the already-freed connection structure. Fix this by releasing the spinlock before calling complete(). Signed-off-by: Maurizio Lombardi Reported-by: Zhaojuan Guo Reviewed-by: Mike Christie Link: https://patch.msgid.link/20260112165352.138606-2-mlombard@redhat.com Signed-off-by: Martin K. Petersen Signed-off-by: Sasha Levin --- drivers/target/iscsi/iscsi_target_util.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/drivers/target/iscsi/iscsi_target_util.c b/drivers/target/iscsi/iscsi_target_util.c index 262a3e76b4b1c5..c1888c42afdd5e 100644 --- a/drivers/target/iscsi/iscsi_target_util.c +++ b/drivers/target/iscsi/iscsi_target_util.c @@ -813,8 +813,11 @@ void iscsit_dec_conn_usage_count(struct iscsit_conn *conn) spin_lock_bh(&conn->conn_usage_lock); conn->conn_usage_count--; - if (!conn->conn_usage_count && conn->conn_waiting_on_uc) + if (!conn->conn_usage_count && conn->conn_waiting_on_uc) { + spin_unlock_bh(&conn->conn_usage_lock); complete(&conn->conn_waiting_on_uc_comp); + return; + } spin_unlock_bh(&conn->conn_usage_lock); } From 990e40fb1d111041e840c91dc336ec87a69f43fc Mon Sep 17 00:00:00 2001 From: Qiang Ma Date: Thu, 18 Dec 2025 16:16:18 +0800 Subject: [PATCH 3082/3902] btrfs: fix Wmaybe-uninitialized warning in replay_one_buffer() [ Upstream commit 9c7e71c97c8cd086b148d0d3d1cd84a1deab023c ] Warning was found when compiling using loongarch64-gcc 12.3.1: $ make CFLAGS_tree-log.o=-Wmaybe-uninitialized In file included from fs/btrfs/ctree.h:21, from fs/btrfs/tree-log.c:12: fs/btrfs/accessors.h: In function 'replay_one_buffer': fs/btrfs/accessors.h:66:16: warning: 'inode_item' may be used uninitialized [-Wmaybe-uninitialized] 66 | return btrfs_get_##bits(eb, s, offsetof(type, member)); \ | ^~~~~~~~~~ fs/btrfs/tree-log.c:2803:42: note: 'inode_item' declared here 2803 | struct btrfs_inode_item *inode_item; | ^~~~~~~~~~ Initialize the inode_item to NULL, the compiler does not seem to see the relation between the first 'wc->log_key.type == BTRFS_INODE_ITEM_KEY' check and the other one that also checks the replay phase. Signed-off-by: Qiang Ma Reviewed-by: David Sterba Signed-off-by: David Sterba Signed-off-by: Sasha Levin --- fs/btrfs/tree-log.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fs/btrfs/tree-log.c b/fs/btrfs/tree-log.c index 1444857de9fe85..ae2e035d013e2e 100644 --- a/fs/btrfs/tree-log.c +++ b/fs/btrfs/tree-log.c @@ -2800,7 +2800,7 @@ static int replay_one_buffer(struct extent_buffer *eb, nritems = btrfs_header_nritems(eb); for (wc->log_slot = 0; wc->log_slot < nritems; wc->log_slot++) { - struct btrfs_inode_item *inode_item; + struct btrfs_inode_item *inode_item = NULL; btrfs_item_key_to_cpu(eb, &wc->log_key, wc->log_slot); From 7a1bec39c014e6f022f57b42782e9baeb4b8d2a4 Mon Sep 17 00:00:00 2001 From: Miri Korenblit Date: Sun, 11 Jan 2026 19:19:30 +0200 Subject: [PATCH 3083/3902] wifi: mac80211: correctly check if CSA is active [ Upstream commit db1d0b6ab11f612ea8a327663a578c8946efeee9 ] We are not adding an interface if an existing one is doing CSA. But the check won't work for MLO station interfaces, since for those, vif->bss_conf is zeroed out. Fix this by checking if any link of the vif has an active CSA. Reviewed-by: Johannes Berg Signed-off-by: Miri Korenblit Link: https://patch.msgid.link/20260111191912.7ceff62fc561.Ia38d27f42684d1cfd82d930d232bd5dea6ab9282@changeid Signed-off-by: Johannes Berg Signed-off-by: Sasha Levin --- net/mac80211/iface.c | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/net/mac80211/iface.c b/net/mac80211/iface.c index 0ca55b9655a7fc..72c129478da088 100644 --- a/net/mac80211/iface.c +++ b/net/mac80211/iface.c @@ -350,6 +350,8 @@ static int ieee80211_check_concurrent_iface(struct ieee80211_sub_if_data *sdata, /* we hold the RTNL here so can safely walk the list */ list_for_each_entry(nsdata, &local->interfaces, list) { if (nsdata != sdata && ieee80211_sdata_running(nsdata)) { + struct ieee80211_link_data *link; + /* * Only OCB and monitor mode may coexist */ @@ -376,8 +378,10 @@ static int ieee80211_check_concurrent_iface(struct ieee80211_sub_if_data *sdata, * will not add another interface while any channel * switch is active. */ - if (nsdata->vif.bss_conf.csa_active) - return -EBUSY; + for_each_link_data(nsdata, link) { + if (link->conf->csa_active) + return -EBUSY; + } /* * The remaining checks are only performed for interfaces From ccb3c75d57039adb3170ae54a0d470e359705984 Mon Sep 17 00:00:00 2001 From: Edward Adam Davis Date: Fri, 9 Jan 2026 21:02:02 +0800 Subject: [PATCH 3084/3902] btrfs: sync read disk super and set block size [ Upstream commit 3f29d661e5686f3aa14e6f11537ff5c49846f2e2 ] When the user performs a btrfs mount, the block device is not set correctly. The user sets the block size of the block device to 0x4000 by executing the BLKBSZSET command. Since the block size change also changes the mapping->flags value, this further affects the result of the mapping_min_folio_order() calculation. Let's analyze the following two scenarios: Scenario 1: Without executing the BLKBSZSET command, the block size is 0x1000, and mapping_min_folio_order() returns 0; Scenario 2: After executing the BLKBSZSET command, the block size is 0x4000, and mapping_min_folio_order() returns 2. do_read_cache_folio() allocates a folio before the BLKBSZSET command is executed. This results in the allocated folio having an order value of 0. Later, after BLKBSZSET is executed, the block size increases to 0x4000, and the mapping_min_folio_order() calculation result becomes 2. This leads to two undesirable consequences: 1. filemap_add_folio() triggers a VM_BUG_ON_FOLIO(folio_order(folio) < mapping_min_folio_order(mapping)) assertion. 2. The syzbot report [1] shows a null pointer dereference in create_empty_buffers() due to a buffer head allocation failure. Synchronization should be established based on the inode between the BLKBSZSET command and read cache page to prevent inconsistencies in block size or mapping flags before and after folio allocation. [1] KASAN: null-ptr-deref in range [0x0000000000000000-0x0000000000000007] RIP: 0010:create_empty_buffers+0x4d/0x480 fs/buffer.c:1694 Call Trace: folio_create_buffers+0x109/0x150 fs/buffer.c:1802 block_read_full_folio+0x14c/0x850 fs/buffer.c:2403 filemap_read_folio+0xc8/0x2a0 mm/filemap.c:2496 do_read_cache_folio+0x266/0x5c0 mm/filemap.c:4096 do_read_cache_page mm/filemap.c:4162 [inline] read_cache_page_gfp+0x29/0x120 mm/filemap.c:4195 btrfs_read_disk_super+0x192/0x500 fs/btrfs/volumes.c:1367 Reported-by: syzbot+b4a2af3000eaa84d95d5@syzkaller.appspotmail.com Closes: https://syzkaller.appspot.com/bug?extid=b4a2af3000eaa84d95d5 Signed-off-by: Edward Adam Davis Reviewed-by: Filipe Manana Signed-off-by: Filipe Manana Signed-off-by: David Sterba Signed-off-by: Sasha Levin --- fs/btrfs/volumes.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/fs/btrfs/volumes.c b/fs/btrfs/volumes.c index 48e717c105c356..8e7dcb12af4c42 100644 --- a/fs/btrfs/volumes.c +++ b/fs/btrfs/volumes.c @@ -1365,7 +1365,9 @@ struct btrfs_super_block *btrfs_read_disk_super(struct block_device *bdev, (bytenr + BTRFS_SUPER_INFO_SIZE) >> PAGE_SHIFT); } + filemap_invalidate_lock(mapping); page = read_cache_page_gfp(mapping, bytenr >> PAGE_SHIFT, GFP_NOFS); + filemap_invalidate_unlock(mapping); if (IS_ERR(page)) return ERR_CAST(page); From b4b065a8809976a17b52a16bb9f0ac3dc070ca02 Mon Sep 17 00:00:00 2001 From: Miri Korenblit Date: Sun, 18 Jan 2026 09:28:29 +0200 Subject: [PATCH 3085/3902] wifi: mac80211: don't increment crypto_tx_tailroom_needed_cnt twice [ Upstream commit 3f3d8ff31496874a69b131866f62474eb24ed20a ] In reconfig, in case the driver asks to disconnect during the reconfig, all the keys of the interface are marked as tainted. Then ieee80211_reenable_keys will loop over all the interface keys, and for each one it will a) increment crypto_tx_tailroom_needed_cnt b) call ieee80211_key_enable_hw_accel, which in turn will detect that this key is tainted, so it will mark it as "not in hardware", which is paired with crypto_tx_tailroom_needed_cnt incrementation, so we get two incrementations for each tainted key. Then we get a warning in ieee80211_free_keys. To fix it, don't increment the count in ieee80211_reenable_keys for tainted keys Reviewed-by: Johannes Berg Signed-off-by: Miri Korenblit Link: https://patch.msgid.link/20260118092821.4ca111fddcda.Id6e554f4b1c83760aa02d5a9e4e3080edb197aa2@changeid Signed-off-by: Johannes Berg Signed-off-by: Sasha Levin --- net/mac80211/key.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/net/mac80211/key.c b/net/mac80211/key.c index d5da7ccea66e0f..04c8809173d7f5 100644 --- a/net/mac80211/key.c +++ b/net/mac80211/key.c @@ -987,7 +987,8 @@ void ieee80211_reenable_keys(struct ieee80211_sub_if_data *sdata) if (ieee80211_sdata_running(sdata)) { list_for_each_entry(key, &sdata->key_list, list) { - increment_tailroom_need_count(sdata); + if (!(key->flags & KEY_FLAG_TAINTED)) + increment_tailroom_need_count(sdata); ieee80211_key_enable_hw_accel(key); } } From 3228b2eceb6c3d7e237f8a5330113dbd164fb90d Mon Sep 17 00:00:00 2001 From: Qu Wenruo Date: Wed, 14 Jan 2026 07:28:28 +1030 Subject: [PATCH 3086/3902] btrfs: reject new transactions if the fs is fully read-only [ Upstream commit 1972f44c189c8aacde308fa9284e474c1a5cbd9f ] [BUG] There is a bug report where a heavily fuzzed fs is mounted with all rescue mount options, which leads to the following warnings during unmount: BTRFS: Transaction aborted (error -22) Modules linked in: CPU: 0 UID: 0 PID: 9758 Comm: repro.out Not tainted 6.19.0-rc5-00002-gb71e635feefc #7 PREEMPT(full) Hardware name: QEMU Standard PC (i440FX + PIIX, 1996), BIOS 1.15.0-1 04/01/2014 RIP: 0010:find_free_extent_update_loop fs/btrfs/extent-tree.c:4208 [inline] RIP: 0010:find_free_extent+0x52f0/0x5d20 fs/btrfs/extent-tree.c:4611 Call Trace: btrfs_reserve_extent+0x2cd/0x790 fs/btrfs/extent-tree.c:4705 btrfs_alloc_tree_block+0x1e1/0x10e0 fs/btrfs/extent-tree.c:5157 btrfs_force_cow_block+0x578/0x2410 fs/btrfs/ctree.c:517 btrfs_cow_block+0x3c4/0xa80 fs/btrfs/ctree.c:708 btrfs_search_slot+0xcad/0x2b50 fs/btrfs/ctree.c:2130 btrfs_truncate_inode_items+0x45d/0x2350 fs/btrfs/inode-item.c:499 btrfs_evict_inode+0x923/0xe70 fs/btrfs/inode.c:5628 evict+0x5f4/0xae0 fs/inode.c:837 __dentry_kill+0x209/0x660 fs/dcache.c:670 finish_dput+0xc9/0x480 fs/dcache.c:879 shrink_dcache_for_umount+0xa0/0x170 fs/dcache.c:1661 generic_shutdown_super+0x67/0x2c0 fs/super.c:621 kill_anon_super+0x3b/0x70 fs/super.c:1289 btrfs_kill_super+0x41/0x50 fs/btrfs/super.c:2127 deactivate_locked_super+0xbc/0x130 fs/super.c:474 cleanup_mnt+0x425/0x4c0 fs/namespace.c:1318 task_work_run+0x1d4/0x260 kernel/task_work.c:233 exit_task_work include/linux/task_work.h:40 [inline] do_exit+0x694/0x22f0 kernel/exit.c:971 do_group_exit+0x21c/0x2d0 kernel/exit.c:1112 __do_sys_exit_group kernel/exit.c:1123 [inline] __se_sys_exit_group kernel/exit.c:1121 [inline] __x64_sys_exit_group+0x3f/0x40 kernel/exit.c:1121 x64_sys_call+0x2210/0x2210 arch/x86/include/generated/asm/syscalls_64.h:232 do_syscall_x64 arch/x86/entry/syscall_64.c:63 [inline] do_syscall_64+0xe8/0xf80 arch/x86/entry/syscall_64.c:94 entry_SYSCALL_64_after_hwframe+0x77/0x7f RIP: 0033:0x44f639 Code: Unable to access opcode bytes at 0x44f60f. RSP: 002b:00007ffc15c4e088 EFLAGS: 00000246 ORIG_RAX: 00000000000000e7 RAX: ffffffffffffffda RBX: 00000000004c32f0 RCX: 000000000044f639 RDX: 000000000000003c RSI: 00000000000000e7 RDI: 0000000000000001 RBP: 0000000000000001 R08: ffffffffffffffc0 R09: 0000000000000000 R10: 0000000000000000 R11: 0000000000000246 R12: 00000000004c32f0 R13: 0000000000000001 R14: 0000000000000000 R15: 0000000000000001 Since rescue mount options will mark the full fs read-only, there should be no new transaction triggered. But during unmount we will evict all inodes, which can trigger a new transaction, and triggers warnings on a heavily corrupted fs. [CAUSE] Btrfs allows new transaction even on a read-only fs, this is to allow log replay happen even on read-only mounts, just like what ext4/xfs do. However with rescue mount options, the fs is fully read-only and cannot be remounted read-write, thus in that case we should also reject any new transactions. [FIX] If we find the fs has rescue mount options, we should treat the fs as error, so that no new transaction can be started. Reported-by: Jiaming Zhang Link: https://lore.kernel.org/linux-btrfs/CANypQFYw8Nt8stgbhoycFojOoUmt+BoZ-z8WJOZVxcogDdwm=Q@mail.gmail.com/ Reviewed-by: Boris Burkov Reviewed-by: Johannes Thumshirn Signed-off-by: Qu Wenruo Reviewed-by: David Sterba Signed-off-by: David Sterba Signed-off-by: Sasha Levin --- fs/btrfs/disk-io.c | 13 +++++++++++++ fs/btrfs/fs.h | 8 ++++++++ 2 files changed, 21 insertions(+) diff --git a/fs/btrfs/disk-io.c b/fs/btrfs/disk-io.c index 19d8c8fc459512..745ae698bbc8ad 100644 --- a/fs/btrfs/disk-io.c +++ b/fs/btrfs/disk-io.c @@ -3248,6 +3248,15 @@ int btrfs_check_features(struct btrfs_fs_info *fs_info, bool is_rw_mount) return 0; } +static bool fs_is_full_ro(const struct btrfs_fs_info *fs_info) +{ + if (!sb_rdonly(fs_info->sb)) + return false; + if (unlikely(fs_info->mount_opt & BTRFS_MOUNT_FULL_RO_MASK)) + return true; + return false; +} + int __cold open_ctree(struct super_block *sb, struct btrfs_fs_devices *fs_devices) { u32 sectorsize; @@ -3356,6 +3365,10 @@ int __cold open_ctree(struct super_block *sb, struct btrfs_fs_devices *fs_device if (btrfs_super_flags(disk_super) & BTRFS_SUPER_FLAG_ERROR) WRITE_ONCE(fs_info->fs_error, -EUCLEAN); + /* If the fs has any rescue options, no transaction is allowed. */ + if (fs_is_full_ro(fs_info)) + WRITE_ONCE(fs_info->fs_error, -EROFS); + /* Set up fs_info before parsing mount options */ nodesize = btrfs_super_nodesize(disk_super); sectorsize = btrfs_super_sectorsize(disk_super); diff --git a/fs/btrfs/fs.h b/fs/btrfs/fs.h index 814bbc9417d2a2..37aa8d141a83d8 100644 --- a/fs/btrfs/fs.h +++ b/fs/btrfs/fs.h @@ -250,6 +250,14 @@ enum { BTRFS_MOUNT_REF_TRACKER = (1ULL << 33), }; +/* These mount options require a full read-only fs, no new transaction is allowed. */ +#define BTRFS_MOUNT_FULL_RO_MASK \ + (BTRFS_MOUNT_NOLOGREPLAY | \ + BTRFS_MOUNT_IGNOREBADROOTS | \ + BTRFS_MOUNT_IGNOREDATACSUMS | \ + BTRFS_MOUNT_IGNOREMETACSUMS | \ + BTRFS_MOUNT_IGNORESUPERFLAGS) + /* * Compat flags that we support. If any incompat flags are set other than the * ones specified below then we will fail to mount From 6dd87f6afe9e9a2839429192360c1026e2689e4f Mon Sep 17 00:00:00 2001 From: Martin Hamilton Date: Thu, 22 Jan 2026 02:51:18 +0000 Subject: [PATCH 3087/3902] ALSA: hda/realtek: ALC269 fixup for Lenovo Yoga Book 9i 13IRU8 audio [ Upstream commit 64e0924ed3b446fdd758dfab582e0e961863a116 ] The amp/speakers on the Lenovo Yoga Book 9i 13IRU8 laptop aren't fully powered up, resulting in horrible tinny sound by default. The kernel has an existing quirk for PCI SSID 0x17aa3843 which matches this machine and several others. The quirk applies the ALC287_FIXUP_IDEAPAD_BASS_SPK_AMP fixup, however the fixup does not work on this machine. This patch modifies the existing quirk by adding a check for the subsystem ID 0x17aa3881. If present, ALC287_FIXUP_TAS2781_I2C will be applied instead of ALC287_FIXUP_IDEAPAD_BASS_SPK_AMP. With this change the TAS2781 amp is powered up, firmware is downloaded and recognised by HDA/SOF - i.e. all is good, and we can boogie. Code is re-used from alc298_fixup_lenovo_c940_duet7(), which fixes a similar problem with two other Lenovo laptops. Cross checked against ALSA cardinfo database for potential clashes. Tested against 6.18.5 kernel built with Arch Linux default options. Tested in HDA mode and SOF mode. Note: Possible further work required to address quality of life issues caused by the firmware's agressive power saving, and to improve ALSA control mappings. Signed-off-by: Martin Hamilton Link: https://patch.msgid.link/20260122-alc269-yogabook9i-fixup-v1-1-a6883429400f@martinh.net Signed-off-by: Takashi Iwai Signed-off-by: Sasha Levin --- sound/hda/codecs/realtek/alc269.c | 24 +++++++++++++++++++++++- 1 file changed, 23 insertions(+), 1 deletion(-) diff --git a/sound/hda/codecs/realtek/alc269.c b/sound/hda/codecs/realtek/alc269.c index e9022f751c959c..ddfad56b30af53 100644 --- a/sound/hda/codecs/realtek/alc269.c +++ b/sound/hda/codecs/realtek/alc269.c @@ -3674,6 +3674,7 @@ enum { ALC287_FIXUP_LEGION_15IMHG05_AUTOMUTE, ALC287_FIXUP_YOGA7_14ITL_SPEAKERS, ALC298_FIXUP_LENOVO_C940_DUET7, + ALC287_FIXUP_LENOVO_YOGA_BOOK_9I, ALC287_FIXUP_13S_GEN2_SPEAKERS, ALC256_FIXUP_SET_COEF_DEFAULTS, ALC256_FIXUP_SYSTEM76_MIC_NO_PRESENCE, @@ -3757,6 +3758,23 @@ static void alc298_fixup_lenovo_c940_duet7(struct hda_codec *codec, __snd_hda_apply_fixup(codec, id, action, 0); } +/* A special fixup for Lenovo Yoga 9i and Yoga Book 9i 13IRU8 + * both have the very same PCI SSID and vendor ID, so we need + * to apply different fixups depending on the subsystem ID + */ +static void alc287_fixup_lenovo_yoga_book_9i(struct hda_codec *codec, + const struct hda_fixup *fix, + int action) +{ + int id; + + if (codec->core.subsystem_id == 0x17aa3881) + id = ALC287_FIXUP_TAS2781_I2C; /* Yoga Book 9i 13IRU8 */ + else + id = ALC287_FIXUP_IDEAPAD_BASS_SPK_AMP; /* Yoga 9i */ + __snd_hda_apply_fixup(codec, id, action, 0); +} + static const struct hda_fixup alc269_fixups[] = { [ALC269_FIXUP_GPIO2] = { .type = HDA_FIXUP_FUNC, @@ -5764,6 +5782,10 @@ static const struct hda_fixup alc269_fixups[] = { .type = HDA_FIXUP_FUNC, .v.func = alc298_fixup_lenovo_c940_duet7, }, + [ALC287_FIXUP_LENOVO_YOGA_BOOK_9I] = { + .type = HDA_FIXUP_FUNC, + .v.func = alc287_fixup_lenovo_yoga_book_9i, + }, [ALC287_FIXUP_13S_GEN2_SPEAKERS] = { .type = HDA_FIXUP_VERBS, .v.verbs = (const struct hda_verb[]) { @@ -7088,7 +7110,7 @@ static const struct hda_quirk alc269_fixup_tbl[] = { SND_PCI_QUIRK(0x17aa, 0x3827, "Ideapad S740", ALC285_FIXUP_IDEAPAD_S740_COEF), SND_PCI_QUIRK(0x17aa, 0x3834, "Lenovo IdeaPad Slim 9i 14ITL5", ALC287_FIXUP_YOGA7_14ITL_SPEAKERS), SND_PCI_QUIRK(0x17aa, 0x383d, "Legion Y9000X 2019", ALC285_FIXUP_LEGION_Y9000X_SPEAKERS), - SND_PCI_QUIRK(0x17aa, 0x3843, "Yoga 9i", ALC287_FIXUP_IDEAPAD_BASS_SPK_AMP), + SND_PCI_QUIRK(0x17aa, 0x3843, "Lenovo Yoga 9i / Yoga Book 9i", ALC287_FIXUP_LENOVO_YOGA_BOOK_9I), SND_PCI_QUIRK(0x17aa, 0x3847, "Legion 7 16ACHG6", ALC287_FIXUP_LEGION_16ACHG6), SND_PCI_QUIRK(0x17aa, 0x384a, "Lenovo Yoga 7 15ITL5", ALC287_FIXUP_YOGA7_14ITL_SPEAKERS), SND_PCI_QUIRK(0x17aa, 0x3852, "Lenovo Yoga 7 14ITL5", ALC287_FIXUP_YOGA7_14ITL_SPEAKERS), From 98bf5bc8cb8cbc4c33b3da38e529e6fe591868cb Mon Sep 17 00:00:00 2001 From: Ian Rogers Date: Wed, 7 Jan 2026 16:26:25 -0800 Subject: [PATCH 3088/3902] tracing: Avoid possible signed 64-bit truncation [ Upstream commit 00f13e28a9c3acd40f0551cde7e9d2d1a41585bf ] 64-bit truncation to 32-bit can result in the sign of the truncated value changing. The cmp_mod_entry is used in bsearch and so the truncation could result in an invalid search order. This would only happen were the addresses more than 2GB apart and so unlikely, but let's fix the potentially broken compare anyway. Cc: Mathieu Desnoyers Link: https://patch.msgid.link/20260108002625.333331-1-irogers@google.com Signed-off-by: Ian Rogers Acked-by: Masami Hiramatsu (Google) Signed-off-by: Steven Rostedt (Google) Signed-off-by: Sasha Levin --- kernel/trace/trace.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/kernel/trace/trace.c b/kernel/trace/trace.c index 142e3b737f0bcf..907923d5f8bbb9 100644 --- a/kernel/trace/trace.c +++ b/kernel/trace/trace.c @@ -6061,10 +6061,10 @@ static int cmp_mod_entry(const void *key, const void *pivot) unsigned long addr = (unsigned long)key; const struct trace_mod_entry *ent = pivot; - if (addr >= ent[0].mod_addr && addr < ent[1].mod_addr) - return 0; - else - return addr - ent->mod_addr; + if (addr < ent[0].mod_addr) + return -1; + + return addr >= ent[1].mod_addr; } /** From 245ff08e261ce3cb9158fc5bf75927a02d598a3b Mon Sep 17 00:00:00 2001 From: Alex Deucher Date: Wed, 21 Jan 2026 18:10:04 -0500 Subject: [PATCH 3089/3902] Revert "drm/amd/display: pause the workload setting in dm" [ Upstream commit f377ea0561c9576cdb7e3890bcf6b8168d455464 ] This reverts commit bc6d54ac7e7436721a19443265f971f890c13cc5. The workload profile needs to be in the default state when the dc idle optimizaion state is entered. However, when jobs come in for video or GFX or compute, the profile may be set to a non-default profile resulting in the dc idle optimizations not taking affect and resulting in higher power usage. As such we need to pause the workload profile changes during this transition. When this patch was originally committed, it caused a regression with a Dell U3224KB display, but no other problems were reported at the time. When it was reapplied (this patch) to address increased power usage, it seems to have caused additional regressions. This change seems to have a number of side affects (audio issues, stuttering, etc.). I suspect the pause should only happen when all displays are off or in static screen mode, but I think this call site gets called more often than that which results in idle state entry more often than intended. For now revert. Closes: https://gitlab.freedesktop.org/drm/amd/-/issues/4894 Closes: https://gitlab.freedesktop.org/drm/amd/-/issues/4717 Closes: https://gitlab.freedesktop.org/drm/amd/-/issues/4725 Closes: https://gitlab.freedesktop.org/drm/amd/-/issues/4517 Closes: https://gitlab.freedesktop.org/drm/amd/-/issues/4806 Cc: Yang Wang Cc: Kenneth Feng Cc: Roman Li Reviewed-by: Yang Wang Signed-off-by: Alex Deucher (cherry picked from commit 1412482b714358ffa30d38fd3dd0b05795163648) Signed-off-by: Sasha Levin --- .../gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_crtc.c | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_crtc.c b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_crtc.c index 38f9ea313dcbb5..2e7ee77c010e18 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_crtc.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_crtc.c @@ -248,8 +248,6 @@ static void amdgpu_dm_crtc_vblank_control_worker(struct work_struct *work) struct vblank_control_work *vblank_work = container_of(work, struct vblank_control_work, work); struct amdgpu_display_manager *dm = vblank_work->dm; - struct amdgpu_device *adev = drm_to_adev(dm->ddev); - int r; mutex_lock(&dm->dc_lock); @@ -279,16 +277,7 @@ static void amdgpu_dm_crtc_vblank_control_worker(struct work_struct *work) if (dm->active_vblank_irq_count == 0) { dc_post_update_surfaces_to_stream(dm->dc); - - r = amdgpu_dpm_pause_power_profile(adev, true); - if (r) - dev_warn(adev->dev, "failed to set default power profile mode\n"); - dc_allow_idle_optimizations(dm->dc, true); - - r = amdgpu_dpm_pause_power_profile(adev, false); - if (r) - dev_warn(adev->dev, "failed to restore the power profile mode\n"); } mutex_unlock(&dm->dc_lock); From f93ae43780b759a70734be9bc82c1adcf7f33208 Mon Sep 17 00:00:00 2001 From: "Rafael J. Wysocki" Date: Mon, 26 Jan 2026 16:38:45 +0200 Subject: [PATCH 3090/3902] platform/x86: toshiba_haps: Fix memory leaks in add/remove routines [ Upstream commit 128497456756e1b952bd5a912cd073836465109d ] toshiba_haps_add() leaks the haps object allocated by it if it returns an error after allocating that object successfully. toshiba_haps_remove() does not free the object pointed to by toshiba_haps before clearing that pointer, so it becomes unreachable allocated memory. Address these memory leaks by using devm_kzalloc() for allocating the memory in question. Fixes: 23d0ba0c908a ("platform/x86: Toshiba HDD Active Protection Sensor") Signed-off-by: Rafael J. Wysocki Signed-off-by: Sasha Levin --- drivers/platform/x86/toshiba_haps.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/platform/x86/toshiba_haps.c b/drivers/platform/x86/toshiba_haps.c index 03dfddeee0c0af..e9324bf16aea4b 100644 --- a/drivers/platform/x86/toshiba_haps.c +++ b/drivers/platform/x86/toshiba_haps.c @@ -183,7 +183,7 @@ static int toshiba_haps_add(struct acpi_device *acpi_dev) pr_info("Toshiba HDD Active Protection Sensor device\n"); - haps = kzalloc(sizeof(struct toshiba_haps_dev), GFP_KERNEL); + haps = devm_kzalloc(&acpi_dev->dev, sizeof(*haps), GFP_KERNEL); if (!haps) return -ENOMEM; From 6c45a5a7e1e3b0b147aeea7e0381690b9859bd1b Mon Sep 17 00:00:00 2001 From: Kaushlendra Kumar Date: Wed, 24 Dec 2025 11:41:44 +0530 Subject: [PATCH 3091/3902] platform/x86: intel_telemetry: Fix PSS event register mask MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [ Upstream commit 39e9c376ac42705af4ed4ae39eec028e8bced9b4 ] The PSS telemetry info parsing incorrectly applies TELEM_INFO_SRAMEVTS_MASK when extracting event register count from firmware response. This reads bits 15-8 instead of the correct bits 7-0, causing misdetection of hardware capabilities. The IOSS path correctly uses TELEM_INFO_NENABLES_MASK for register count. Apply the same mask to PSS parsing for consistency. Fixes: 9d16b482b059 ("platform:x86: Add Intel telemetry platform driver") Signed-off-by: Kaushlendra Kumar Link: https://patch.msgid.link/20251224061144.3925519-1-kaushlendra.kumar@intel.com Reviewed-by: Ilpo Järvinen Signed-off-by: Ilpo Järvinen Signed-off-by: Sasha Levin --- drivers/platform/x86/intel/telemetry/pltdrv.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/platform/x86/intel/telemetry/pltdrv.c b/drivers/platform/x86/intel/telemetry/pltdrv.c index f23c170a55dc67..d9aa349f81e412 100644 --- a/drivers/platform/x86/intel/telemetry/pltdrv.c +++ b/drivers/platform/x86/intel/telemetry/pltdrv.c @@ -610,7 +610,7 @@ static int telemetry_setup(struct platform_device *pdev) /* Get telemetry Info */ events = (read_buf & TELEM_INFO_SRAMEVTS_MASK) >> TELEM_INFO_SRAMEVTS_SHIFT; - event_regs = read_buf & TELEM_INFO_SRAMEVTS_MASK; + event_regs = read_buf & TELEM_INFO_NENABLES_MASK; if ((events < TELEM_MAX_EVENTS_SRAM) || (event_regs < TELEM_MAX_EVENTS_SRAM)) { dev_err(&pdev->dev, "PSS:Insufficient Space for SRAM Trace\n"); From 9029ccfab2ca99b829627d8104f8bcc9ec778543 Mon Sep 17 00:00:00 2001 From: Mario Limonciello Date: Wed, 28 Jan 2026 13:04:45 -0600 Subject: [PATCH 3092/3902] platform/x86: hp-bioscfg: Skip empty attribute names MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [ Upstream commit 6222883af286e2feb3c9ff2bf9fd8fdf4220c55a ] Avoid registering kobjects with empty names when a BIOS attribute name decodes to an empty string. Fixes: a34fc329b1895 ("platform/x86: hp-bioscfg: bioscfg") Reported-by: Alain Cousinie Closes: https://lore.kernel.org/platform-driver-x86/22ed5f78-c8bf-4ab4-8c38-420cc0201e7e@laposte.net/ Signed-off-by: Mario Limonciello Link: https://patch.msgid.link/20260128190501.2170068-1-mario.limonciello@amd.com Reviewed-by: Ilpo Järvinen Signed-off-by: Ilpo Järvinen Signed-off-by: Sasha Levin --- drivers/platform/x86/hp/hp-bioscfg/bioscfg.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/drivers/platform/x86/hp/hp-bioscfg/bioscfg.c b/drivers/platform/x86/hp/hp-bioscfg/bioscfg.c index dbe096eefa7582..51e8977d3eb4a8 100644 --- a/drivers/platform/x86/hp/hp-bioscfg/bioscfg.c +++ b/drivers/platform/x86/hp/hp-bioscfg/bioscfg.c @@ -696,6 +696,11 @@ static int hp_init_bios_package_attribute(enum hp_wmi_data_type attr_type, return ret; } + if (!str_value || !str_value[0]) { + pr_debug("Ignoring attribute with empty name\n"); + goto pack_attr_exit; + } + /* All duplicate attributes found are ignored */ duplicate = kset_find_obj(temp_kset, str_value); if (duplicate) { From faff38ebbfe631960a1f5840e60248ab5e3ae0e1 Mon Sep 17 00:00:00 2001 From: Ricardo Neri Date: Tue, 27 Jan 2026 15:45:40 -0800 Subject: [PATCH 3093/3902] platform/x86/intel/tpmi/plr: Make the file domain/status writeable MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [ Upstream commit 008bec8ffe6e7746588d1e12c5b3865fa478fc91 ] The file sys/kernel/debug/tpmi-/plr/domain/status has store and show callbacks. Make it writeable. Fixes: 811f67c51636d ("platform/x86/intel/tpmi: Add new auxiliary driver for performance limits") Signed-off-by: Ricardo Neri Link: https://patch.msgid.link/20260127-plr-debugfs-write-v1-1-1fffbc370b1e@linux.intel.com Reviewed-by: Ilpo Järvinen Signed-off-by: Ilpo Järvinen Signed-off-by: Sasha Levin --- drivers/platform/x86/intel/plr_tpmi.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/platform/x86/intel/plr_tpmi.c b/drivers/platform/x86/intel/plr_tpmi.c index 58132da4774570..05727169f49c14 100644 --- a/drivers/platform/x86/intel/plr_tpmi.c +++ b/drivers/platform/x86/intel/plr_tpmi.c @@ -316,7 +316,7 @@ static int intel_plr_probe(struct auxiliary_device *auxdev, const struct auxilia snprintf(name, sizeof(name), "domain%d", i); dentry = debugfs_create_dir(name, plr->dbgfs_dir); - debugfs_create_file("status", 0444, dentry, &plr->die_info[i], + debugfs_create_file("status", 0644, dentry, &plr->die_info[i], &plr_status_fops); } From 9ee608a64e37cea5b4b13e436c559dd0fb2ad1b5 Mon Sep 17 00:00:00 2001 From: ChenXiaoSong Date: Mon, 2 Feb 2026 08:24:07 +0000 Subject: [PATCH 3094/3902] smb/client: fix memory leak in smb2_open_file() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [ Upstream commit e3a43633023e3cacaca60d4b8972d084a2b06236 ] Reproducer: 1. server: directories are exported read-only 2. client: mount -t cifs //${server_ip}/export /mnt 3. client: dd if=/dev/zero of=/mnt/file bs=512 count=1000 oflag=direct 4. client: umount /mnt 5. client: sleep 1 6. client: modprobe -r cifs The error message is as follows: ============================================================================= BUG cifs_small_rq (Not tainted): Objects remaining on __kmem_cache_shutdown() ----------------------------------------------------------------------------- Object 0x00000000d47521be @offset=14336 ... WARNING: mm/slub.c:1251 at __kmem_cache_shutdown+0x34e/0x440, CPU#0: modprobe/1577 ... Call Trace: kmem_cache_destroy+0x94/0x190 cifs_destroy_request_bufs+0x3e/0x50 [cifs] cleanup_module+0x4e/0x540 [cifs] __se_sys_delete_module+0x278/0x400 __x64_sys_delete_module+0x5f/0x70 x64_sys_call+0x2299/0x2ff0 do_syscall_64+0x89/0x350 entry_SYSCALL_64_after_hwframe+0x76/0x7e ... kmem_cache_destroy cifs_small_rq: Slab cache still has objects when called from cifs_destroy_request_bufs+0x3e/0x50 [cifs] WARNING: mm/slab_common.c:532 at kmem_cache_destroy+0x16b/0x190, CPU#0: modprobe/1577 Link: https://lore.kernel.org/linux-cifs/9751f02d-d1df-4265-a7d6-b19761b21834@linux.dev/T/#mf14808c144448b715f711ce5f0477a071f08eaf6 Fixes: e255612b5ed9 ("cifs: Add fallback for SMB2 CREATE without FILE_READ_ATTRIBUTES") Reported-by: Paulo Alcantara Reviewed-by: Paulo Alcantara (Red Hat) Signed-off-by: ChenXiaoSong Reviewed-by: Pali Rohár Signed-off-by: Steve French Signed-off-by: Sasha Levin --- fs/smb/client/smb2file.c | 1 + 1 file changed, 1 insertion(+) diff --git a/fs/smb/client/smb2file.c b/fs/smb/client/smb2file.c index a7f6292388306c..03f90553d8319e 100644 --- a/fs/smb/client/smb2file.c +++ b/fs/smb/client/smb2file.c @@ -177,6 +177,7 @@ int smb2_open_file(const unsigned int xid, struct cifs_open_parms *oparms, __u32 rc = SMB2_open(xid, oparms, smb2_path, &smb2_oplock, smb2_data, NULL, &err_iov, &err_buftype); if (rc == -EACCES && retry_without_read_attributes) { + free_rsp_buf(err_buftype, err_iov.iov_base); oparms->desired_access &= ~FILE_READ_ATTRIBUTES; rc = SMB2_open(xid, oparms, smb2_path, &smb2_oplock, smb2_data, NULL, &err_iov, &err_buftype); From f8611a7981cd0b42c7ce4b0ab93e3b9c646a01ec Mon Sep 17 00:00:00 2001 From: "leobannocloutier@gmail.com" Date: Fri, 16 Jan 2026 20:53:15 -0500 Subject: [PATCH 3095/3902] hwmon: (dell-smm) Add Dell G15 5510 to fan control whitelist [ Upstream commit 830e0bef79aaaea8b1ef426b8032e70c63a58653 ] On the Dell G15 5510, fans spin at maximum speed when AC power is connected. This behavior has been observed as a regression in recent kernels (v6.18+). Add the Dell G15 5510 to the fan control whitelist to enable manual fan control and resolve the issue. This model requires the same fan control configuration as the Dell G15 5511. Fixes: 1c1658058c99 ("hwmon: (dell-smm) Add support for automatic fan mode") Signed-off-by: Leo Banno-Cloutier Link: https://lore.kernel.org/r/20260117015315.214569-2-leobannocloutier@gmail.com [groeck: Updated patch description to follow guidance] Signed-off-by: Guenter Roeck Signed-off-by: Sasha Levin --- drivers/hwmon/dell-smm-hwmon.c | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/drivers/hwmon/dell-smm-hwmon.c b/drivers/hwmon/dell-smm-hwmon.c index 8cf12b9bae2a74..f3d484a9f708b7 100644 --- a/drivers/hwmon/dell-smm-hwmon.c +++ b/drivers/hwmon/dell-smm-hwmon.c @@ -1630,6 +1630,14 @@ static const struct dmi_system_id i8k_whitelist_fan_control[] __initconst = { }, .driver_data = (void *)&i8k_fan_control_data[I8K_FAN_30A3_31A3], }, + { + .ident = "Dell G15 5510", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."), + DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Dell G15 5510"), + }, + .driver_data = (void *)&i8k_fan_control_data[I8K_FAN_30A3_31A3], + }, { .ident = "Dell G15 5511", .matches = { From 9b186feb752674f98abc77520a8d63f7063779c6 Mon Sep 17 00:00:00 2001 From: Eric Dumazet Date: Wed, 28 Jan 2026 14:15:38 +0000 Subject: [PATCH 3096/3902] net: add skb_header_pointer_careful() helper [ Upstream commit 13e00fdc9236bd4d0bff4109d2983171fbcb74c4 ] This variant of skb_header_pointer() should be used in contexts where @offset argument is user-controlled and could be negative. Negative offsets are supported, as long as the zone starts between skb->head and skb->data. Signed-off-by: Eric Dumazet Link: https://patch.msgid.link/20260128141539.3404400-2-edumazet@google.com Signed-off-by: Jakub Kicinski Stable-dep-of: cabd1a976375 ("net/sched: cls_u32: use skb_header_pointer_careful()") Signed-off-by: Sasha Levin --- include/linux/skbuff.h | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/include/linux/skbuff.h b/include/linux/skbuff.h index a7cc3d1f4fd111..50f127451dc65e 100644 --- a/include/linux/skbuff.h +++ b/include/linux/skbuff.h @@ -4301,6 +4301,18 @@ skb_header_pointer(const struct sk_buff *skb, int offset, int len, void *buffer) skb_headlen(skb), buffer); } +/* Variant of skb_header_pointer() where @offset is user-controlled + * and potentially negative. + */ +static inline void * __must_check +skb_header_pointer_careful(const struct sk_buff *skb, int offset, + int len, void *buffer) +{ + if (unlikely(offset < 0 && -offset > skb_headroom(skb))) + return NULL; + return skb_header_pointer(skb, offset, len, buffer); +} + static inline void * __must_check skb_pointer_if_linear(const struct sk_buff *skb, int offset, int len) { From 8a672f177ebe19c93d795fbe967846084fbc7943 Mon Sep 17 00:00:00 2001 From: Eric Dumazet Date: Wed, 28 Jan 2026 14:15:39 +0000 Subject: [PATCH 3097/3902] net/sched: cls_u32: use skb_header_pointer_careful() [ Upstream commit cabd1a976375780dabab888784e356f574bbaed8 ] skb_header_pointer() does not fully validate negative @offset values. Use skb_header_pointer_careful() instead. GangMin Kim provided a report and a repro fooling u32_classify(): BUG: KASAN: slab-out-of-bounds in u32_classify+0x1180/0x11b0 net/sched/cls_u32.c:221 Fixes: fbc2e7d9cf49 ("cls_u32: use skb_header_pointer() to dereference data safely") Reported-by: GangMin Kim Closes: https://lore.kernel.org/netdev/CANn89iJkyUZ=mAzLzC4GdcAgLuPnUoivdLaOs6B9rq5_erj76w@mail.gmail.com/T/ Signed-off-by: Eric Dumazet Link: https://patch.msgid.link/20260128141539.3404400-3-edumazet@google.com Signed-off-by: Jakub Kicinski Signed-off-by: Sasha Levin --- net/sched/cls_u32.c | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/net/sched/cls_u32.c b/net/sched/cls_u32.c index 2a1c00048fd6f4..58e849c0acf412 100644 --- a/net/sched/cls_u32.c +++ b/net/sched/cls_u32.c @@ -161,10 +161,8 @@ TC_INDIRECT_SCOPE int u32_classify(struct sk_buff *skb, int toff = off + key->off + (off2 & key->offmask); __be32 *data, hdata; - if (skb_headroom(skb) + toff > INT_MAX) - goto out; - - data = skb_header_pointer(skb, toff, 4, &hdata); + data = skb_header_pointer_careful(skb, toff, 4, + &hdata); if (!data) goto out; if ((*data ^ key->val) & key->mask) { @@ -214,8 +212,9 @@ TC_INDIRECT_SCOPE int u32_classify(struct sk_buff *skb, if (ht->divisor) { __be32 *data, hdata; - data = skb_header_pointer(skb, off + n->sel.hoff, 4, - &hdata); + data = skb_header_pointer_careful(skb, + off + n->sel.hoff, + 4, &hdata); if (!data) goto out; sel = ht->divisor & u32_hash_fold(*data, &n->sel, @@ -229,7 +228,7 @@ TC_INDIRECT_SCOPE int u32_classify(struct sk_buff *skb, if (n->sel.flags & TC_U32_VAROFFSET) { __be16 *data, hdata; - data = skb_header_pointer(skb, + data = skb_header_pointer_careful(skb, off + n->sel.offoff, 2, &hdata); if (!data) From 155eb99aff2920153bf21217ae29565fff81e6af Mon Sep 17 00:00:00 2001 From: Junrui Luo Date: Wed, 28 Jan 2026 16:07:34 +0800 Subject: [PATCH 3098/3902] dpaa2-switch: prevent ZERO_SIZE_PTR dereference when num_ifs is zero [ Upstream commit ed48a84a72fefb20a82dd90a7caa7807e90c6f66 ] The driver allocates arrays for ports, FDBs, and filter blocks using kcalloc() with ethsw->sw_attr.num_ifs as the element count. When the device reports zero interfaces (either due to hardware configuration or firmware issues), kcalloc(0, ...) returns ZERO_SIZE_PTR (0x10) instead of NULL. Later in dpaa2_switch_probe(), the NAPI initialization unconditionally accesses ethsw->ports[0]->netdev, which attempts to dereference ZERO_SIZE_PTR (address 0x10), resulting in a kernel panic. Add a check to ensure num_ifs is greater than zero after retrieving device attributes. This prevents the zero-sized allocations and subsequent invalid pointer dereference. Reported-by: Yuhao Jiang Reported-by: Junrui Luo Fixes: 0b1b71370458 ("staging: dpaa2-switch: handle Rx path on control interface") Signed-off-by: Junrui Luo Reviewed-by: Andrew Lunn Link: https://patch.msgid.link/SYBPR01MB7881BEABA8DA896947962470AF91A@SYBPR01MB7881.ausprd01.prod.outlook.com Signed-off-by: Jakub Kicinski Signed-off-by: Sasha Levin --- drivers/net/ethernet/freescale/dpaa2/dpaa2-switch.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/drivers/net/ethernet/freescale/dpaa2/dpaa2-switch.c b/drivers/net/ethernet/freescale/dpaa2/dpaa2-switch.c index b1e1ad9e4b48e6..0ff234f6a3ed97 100644 --- a/drivers/net/ethernet/freescale/dpaa2/dpaa2-switch.c +++ b/drivers/net/ethernet/freescale/dpaa2/dpaa2-switch.c @@ -3024,6 +3024,12 @@ static int dpaa2_switch_init(struct fsl_mc_device *sw_dev) goto err_close; } + if (!ethsw->sw_attr.num_ifs) { + dev_err(dev, "DPSW device has no interfaces\n"); + err = -ENODEV; + goto err_close; + } + err = dpsw_get_api_version(ethsw->mc_io, 0, ðsw->major, ðsw->minor); From d028147ae06407cb355245db1774793600670169 Mon Sep 17 00:00:00 2001 From: Zilin Guan Date: Wed, 28 Jan 2026 15:44:38 +0000 Subject: [PATCH 3099/3902] net: liquidio: Initialize netdev pointer before queue setup [ Upstream commit 926ede0c85e1e57c97d64d9612455267d597bb2c ] In setup_nic_devices(), the netdev is allocated using alloc_etherdev_mq(). However, the pointer to this structure is stored in oct->props[i].netdev only after the calls to netif_set_real_num_rx_queues() and netif_set_real_num_tx_queues(). If either of these functions fails, setup_nic_devices() returns an error without freeing the allocated netdev. Since oct->props[i].netdev is still NULL at this point, the cleanup function liquidio_destroy_nic_device() will fail to find and free the netdev, resulting in a memory leak. Fix this by initializing oct->props[i].netdev before calling the queue setup functions. This ensures that the netdev is properly accessible for cleanup in case of errors. Compile tested only. Issue found using a prototype static analysis tool and code review. Fixes: c33c997346c3 ("liquidio: enhanced ethtool --set-channels feature") Signed-off-by: Zilin Guan Reviewed-by: Kory Maincent Link: https://patch.msgid.link/20260128154440.278369-2-zilin@seu.edu.cn Signed-off-by: Jakub Kicinski Signed-off-by: Sasha Levin --- .../net/ethernet/cavium/liquidio/lio_main.c | 34 +++++++++---------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/drivers/net/ethernet/cavium/liquidio/lio_main.c b/drivers/net/ethernet/cavium/liquidio/lio_main.c index 8e2fcec26ea13c..925512c077a0c3 100644 --- a/drivers/net/ethernet/cavium/liquidio/lio_main.c +++ b/drivers/net/ethernet/cavium/liquidio/lio_main.c @@ -3515,6 +3515,23 @@ static int setup_nic_devices(struct octeon_device *octeon_dev) */ netdev->netdev_ops = &lionetdevops; + lio = GET_LIO(netdev); + + memset(lio, 0, sizeof(struct lio)); + + lio->ifidx = ifidx_or_pfnum; + + props = &octeon_dev->props[i]; + props->gmxport = resp->cfg_info.linfo.gmxport; + props->netdev = netdev; + + /* Point to the properties for octeon device to which this + * interface belongs. + */ + lio->oct_dev = octeon_dev; + lio->octprops = props; + lio->netdev = netdev; + retval = netif_set_real_num_rx_queues(netdev, num_oqueues); if (retval) { dev_err(&octeon_dev->pci_dev->dev, @@ -3531,16 +3548,6 @@ static int setup_nic_devices(struct octeon_device *octeon_dev) goto setup_nic_dev_free; } - lio = GET_LIO(netdev); - - memset(lio, 0, sizeof(struct lio)); - - lio->ifidx = ifidx_or_pfnum; - - props = &octeon_dev->props[i]; - props->gmxport = resp->cfg_info.linfo.gmxport; - props->netdev = netdev; - lio->linfo.num_rxpciq = num_oqueues; lio->linfo.num_txpciq = num_iqueues; for (j = 0; j < num_oqueues; j++) { @@ -3606,13 +3613,6 @@ static int setup_nic_devices(struct octeon_device *octeon_dev) netdev->min_mtu = LIO_MIN_MTU_SIZE; netdev->max_mtu = LIO_MAX_MTU_SIZE; - /* Point to the properties for octeon device to which this - * interface belongs. - */ - lio->oct_dev = octeon_dev; - lio->octprops = props; - lio->netdev = netdev; - dev_dbg(&octeon_dev->pci_dev->dev, "if%d gmx: %d hw_addr: 0x%llx\n", i, lio->linfo.gmxport, CVM_CAST64(lio->linfo.hw_addr)); From 293eaad0d6d6b2a37a458c7deb7be345349cd963 Mon Sep 17 00:00:00 2001 From: Zilin Guan Date: Wed, 28 Jan 2026 15:44:39 +0000 Subject: [PATCH 3100/3902] net: liquidio: Fix off-by-one error in PF setup_nic_devices() cleanup [ Upstream commit 8558aef4e8a1a83049ab906d21d391093cfa7e7f ] In setup_nic_devices(), the initialization loop jumps to the label setup_nic_dev_free on failure. The current cleanup loop while(i--) skip the failing index i, causing a memory leak. Fix this by changing the loop to iterate from the current index i down to 0. Also, decrement i in the devlink_alloc failure path to point to the last successfully allocated index. Compile tested only. Issue found using code review. Fixes: f21fb3ed364b ("Add support of Cavium Liquidio ethernet adapters") Suggested-by: Simon Horman Signed-off-by: Zilin Guan Reviewed-by: Kory Maincent Link: https://patch.msgid.link/20260128154440.278369-3-zilin@seu.edu.cn Signed-off-by: Jakub Kicinski Signed-off-by: Sasha Levin --- drivers/net/ethernet/cavium/liquidio/lio_main.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/drivers/net/ethernet/cavium/liquidio/lio_main.c b/drivers/net/ethernet/cavium/liquidio/lio_main.c index 925512c077a0c3..eb620e8544cf19 100644 --- a/drivers/net/ethernet/cavium/liquidio/lio_main.c +++ b/drivers/net/ethernet/cavium/liquidio/lio_main.c @@ -3760,6 +3760,7 @@ static int setup_nic_devices(struct octeon_device *octeon_dev) if (!devlink) { device_unlock(&octeon_dev->pci_dev->dev); dev_err(&octeon_dev->pci_dev->dev, "devlink alloc failed\n"); + i--; goto setup_nic_dev_free; } @@ -3775,11 +3776,11 @@ static int setup_nic_devices(struct octeon_device *octeon_dev) setup_nic_dev_free: - while (i--) { + do { dev_err(&octeon_dev->pci_dev->dev, "NIC ifidx:%d Setup failed\n", i); liquidio_destroy_nic_device(octeon_dev, i); - } + } while (i--); setup_nic_dev_done: From 4640fa5ad5e1a0dbd1c2d22323b7d70a8107dcfd Mon Sep 17 00:00:00 2001 From: Zilin Guan Date: Wed, 28 Jan 2026 15:44:40 +0000 Subject: [PATCH 3101/3902] net: liquidio: Fix off-by-one error in VF setup_nic_devices() cleanup [ Upstream commit 6cbba46934aefdfb5d171e0a95aec06c24f7ca30 ] In setup_nic_devices(), the initialization loop jumps to the label setup_nic_dev_free on failure. The current cleanup loop while(i--) skip the failing index i, causing a memory leak. Fix this by changing the loop to iterate from the current index i down to 0. Compile tested only. Issue found using code review. Fixes: 846b46873eeb ("liquidio CN23XX: VF offload features") Suggested-by: Simon Horman Signed-off-by: Zilin Guan Reviewed-by: Kory Maincent Link: https://patch.msgid.link/20260128154440.278369-4-zilin@seu.edu.cn Signed-off-by: Jakub Kicinski Signed-off-by: Sasha Levin --- drivers/net/ethernet/cavium/liquidio/lio_vf_main.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/net/ethernet/cavium/liquidio/lio_vf_main.c b/drivers/net/ethernet/cavium/liquidio/lio_vf_main.c index 3230dff5ba056c..5c177146b35b11 100644 --- a/drivers/net/ethernet/cavium/liquidio/lio_vf_main.c +++ b/drivers/net/ethernet/cavium/liquidio/lio_vf_main.c @@ -2222,11 +2222,11 @@ static int setup_nic_devices(struct octeon_device *octeon_dev) setup_nic_dev_free: - while (i--) { + do { dev_err(&octeon_dev->pci_dev->dev, "NIC ifidx:%d Setup failed\n", i); liquidio_destroy_nic_device(octeon_dev, i); - } + } while (i--); setup_nic_dev_done: From 1b381a638e1851d8cfdfe08ed9cdbec5295b18c9 Mon Sep 17 00:00:00 2001 From: Junrui Luo Date: Thu, 29 Jan 2026 00:55:13 +0800 Subject: [PATCH 3102/3902] dpaa2-switch: add bounds check for if_id in IRQ handler [ Upstream commit 31a7a0bbeb006bac2d9c81a2874825025214b6d8 ] The IRQ handler extracts if_id from the upper 16 bits of the hardware status register and uses it to index into ethsw->ports[] without validation. Since if_id can be any 16-bit value (0-65535) but the ports array is only allocated with sw_attr.num_ifs elements, this can lead to an out-of-bounds read potentially. Add a bounds check before accessing the array, consistent with the existing validation in dpaa2_switch_rx(). Reported-by: Yuhao Jiang Reported-by: Junrui Luo Fixes: 24ab724f8a46 ("dpaa2-switch: use the port index in the IRQ handler") Signed-off-by: Junrui Luo Link: https://patch.msgid.link/SYBPR01MB7881D420AB43FF1A227B84AFAF91A@SYBPR01MB7881.ausprd01.prod.outlook.com Signed-off-by: Jakub Kicinski Signed-off-by: Sasha Levin --- drivers/net/ethernet/freescale/dpaa2/dpaa2-switch.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/drivers/net/ethernet/freescale/dpaa2/dpaa2-switch.c b/drivers/net/ethernet/freescale/dpaa2/dpaa2-switch.c index 0ff234f6a3ed97..66240c340492ce 100644 --- a/drivers/net/ethernet/freescale/dpaa2/dpaa2-switch.c +++ b/drivers/net/ethernet/freescale/dpaa2/dpaa2-switch.c @@ -1531,6 +1531,10 @@ static irqreturn_t dpaa2_switch_irq0_handler_thread(int irq_num, void *arg) } if_id = (status & 0xFFFF0000) >> 16; + if (if_id >= ethsw->sw_attr.num_ifs) { + dev_err(dev, "Invalid if_id %d in IRQ status\n", if_id); + goto out; + } port_priv = ethsw->ports[if_id]; if (status & DPSW_IRQ_EVENT_LINK_CHANGED) From 6801ef140fc332b50d6a61411bc9d4dfa5d46ef2 Mon Sep 17 00:00:00 2001 From: Grzegorz Nitka Date: Thu, 27 Nov 2025 10:25:58 +0100 Subject: [PATCH 3103/3902] ice: fix missing TX timestamps interrupts on E825 devices [ Upstream commit 99854c167cfc113ad863832b1601c4ca1a639cfe ] Modify PTP (Precision Time Protocol) configuration on link down flow. Previously, PHY_REG_TX_OFFSET_READY register was cleared in such case. This register is used to determine if the timestamp is valid or not on the hardware side. However, there is a possibility that there is still the packet in the HW queue which originally was supposed to be timestamped but the link is already down and given register is cleared. This potentially might lead to the situation in which that 'delayed' packet's timestamp is treated as invalid one when the link is up again. This in turn leads to the situation in which the driver is not able to effectively clean timestamp memory and interrupt configuration. From the hardware perspective, that 'old' interrupt was not handled properly and even if new timestamp packets are processed, no new interrupts is generated. As a result, providing timestamps to the user applications (like ptp4l) is not possible. The solution for this problem is implemented at the driver level rather than the firmware, and maintains the tx_ready bit high, even during link down events. This avoids entering a potential inconsistent state between the driver and the timestamp hardware. Testing hints: - run PTP traffic at higher rate (like 16 PTP messages per second) - observe ptp4l behaviour at the client side in the following conditions: a) trigger link toggle events. It needs to be physiscal link down/up events b) link speed change In all above cases, PTP processing at ptp4l application should resume always. In failure case, the following permanent error message in ptp4l log was observed: controller-0 ptp4l: err [6175.116] ptp4l-legacy timed out while polling for tx timestamp Fixes: 7cab44f1c35f ("ice: Introduce ETH56G PHY model for E825C products") Reviewed-by: Aleksandr Loktionov Signed-off-by: Grzegorz Nitka Tested-by: Sunitha Mekala (A Contingent worker at Intel) Signed-off-by: Tony Nguyen Signed-off-by: Sasha Levin --- drivers/net/ethernet/intel/ice/ice_ptp.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/drivers/net/ethernet/intel/ice/ice_ptp.c b/drivers/net/ethernet/intel/ice/ice_ptp.c index 8ec0f7d0fcebdd..4aa88bac759f8c 100644 --- a/drivers/net/ethernet/intel/ice/ice_ptp.c +++ b/drivers/net/ethernet/intel/ice/ice_ptp.c @@ -1338,9 +1338,12 @@ void ice_ptp_link_change(struct ice_pf *pf, bool linkup) /* Do not reconfigure E810 or E830 PHY */ return; case ICE_MAC_GENERIC: - case ICE_MAC_GENERIC_3K_E825: ice_ptp_port_phy_restart(ptp_port); return; + case ICE_MAC_GENERIC_3K_E825: + if (linkup) + ice_ptp_port_phy_restart(ptp_port); + return; default: dev_warn(ice_pf_to_dev(pf), "%s: Unknown PHY type\n", __func__); } From ef72678c9df0ec3dde80d446fcf5a06cea996c4b Mon Sep 17 00:00:00 2001 From: Jacob Keller Date: Wed, 21 Jan 2026 10:44:19 -0800 Subject: [PATCH 3104/3902] ice: PTP: fix missing timestamps on E825 hardware MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [ Upstream commit 88b68f35eb43ad5ac77ac1107059040b04e6f477 ] The E825 hardware currently has each PF handle the PFINT_TSYN_TX cause of the miscellaneous OICR interrupt vector. The actual interrupt cause underlying this is shared by all ports on the same quad: ┌─────────────────────────────────┐ │ │ │ ┌────┐ ┌────┐ ┌────┐ ┌────┐ │ │ │PF 0│ │PF 1│ │PF 2│ │PF 3│ │ │ └────┘ └────┘ └────┘ └────┘ │ │ │ └────────────────▲────────────────┘ │ │ ┌────────────────┼────────────────┐ │ PHY QUAD │ └───▲────────▲────────▲────────▲──┘ │ │ │ │ ┌───┼──┐ ┌───┴──┐ ┌───┼──┐ ┌───┼──┐ │Port 0│ │Port 1│ │Port 2│ │Port 3│ └──────┘ └──────┘ └──────┘ └──────┘ If multiple PFs issue Tx timestamp requests near simultaneously, it is possible that the correct PF will not be interrupted and will miss its timestamp. Understanding why is somewhat complex. Consider the following sequence of events: CPU 0: Send Tx packet on PF 0 ... PF 0 enqueues packet with Tx request CPU 1, PF1: ... Send Tx packet on PF1 ... PF 1 enqueues packet with Tx request HW: PHY Port 0 sends packet PHY raises Tx timestamp event interrupt MAC raises each PF interrupt CPU 0, PF0: CPU 1, PF1: ice_misc_intr() checks for Tx timestamps ice_misc_intr() checks for Tx timestamp Sees packet ready bit set Sees nothing available ... Exits ... ... HW: PHY port 1 sends packet PHY interrupt ignored because not all packet timestamps read yet. ... Read timestamp, report to stack Because the interrupt event is shared for all ports on the same quad, the PHY will not raise a new interrupt for any PF until all timestamps are read. In the example above, the second timestamp comes in for port 1 before the timestamp from port 0 is read. At this point, there is no longer an interrupt thread running that will read the timestamps, because each PF has checked and found that there was no work to do. Applications such as ptp4l will timeout after waiting a few milliseconds. Eventually, the watchdog service task will re-check for all quads and notice that there are outstanding timestamps, and issue a software interrupt to recover. However, by this point it is far too late, and applications have already failed. All of this occurs because of the underlying hardware behavior. The PHY cannot raise a new interrupt signal until all outstanding timestamps have been read. As a first step to fix this, switch the E825C hardware to the ICE_PTP_TX_INTERRUPT_ALL mode. In this mode, only the clock owner PF will respond to the PFINT_TSYN_TX cause. Other PFs disable this cause and will not wake. In this mode, the clock owner will iterate over all ports and handle timestamps for each connected port. This matches the E822 behavior, and is a necessary but insufficient step to resolve the missing timestamps. Even with use of the ICE_PTP_TX_INTERRUPT_ALL mode, we still sometimes miss a timestamp event. The ice_ptp_tx_tstamp_owner() does re-check the ready bitmap, but does so before re-enabling the OICR interrupt vector. It also only checks the ready bitmap, but not the software Tx timestamp tracker. To avoid risk of losing a timestamp, refactor the logic to check both the software Tx timestamp tracker bitmap *and* the hardware ready bitmap. Additionally, do this outside of ice_ptp_process_ts() after we have already re-enabled the OICR interrupt. Remove the checks from the ice_ptp_tx_tstamp(), ice_ptp_tx_tstamp_owner(), and the ice_ptp_process_ts() functions. This results in ice_ptp_tx_tstamp() being nothing more than a wrapper around ice_ptp_process_tx_tstamp() so we can remove it. Add the ice_ptp_tx_tstamps_pending() function which returns a boolean indicating if there are any pending Tx timestamps. First, check the software timestamp tracker bitmap. In ICE_PTP_TX_INTERRUPT_ALL mode, check *all* ports software trackers. If a tracker has outstanding timestamp requests, return true. Additionally, check the PHY ready bitmap to confirm if the PHY indicates any outstanding timestamps. In the ice_misc_thread_fn(), call ice_ptp_tx_tstamps_pending() just before returning from the IRQ thread handler. If it returns true, write to PFINT_OICR to trigger a PFINT_OICR_TSYN_TX_M software interrupt. This will force the handler to interrupt again and complete the work even if the PHY hardware did not interrupt for any reason. This results in the following new flow for handling Tx timestamps: 1) send Tx packet 2) PHY captures timestamp 3) PHY triggers MAC interrupt 4) clock owner executes ice_misc_intr() with PFINT_OICR_TSYN_TX flag set 5) ice_ptp_ts_irq() returns IRQ_WAKE_THREAD 7) The interrupt thread wakes up and kernel calls ice_misc_intr_thread_fn() 8) ice_ptp_process_ts() is called to handle any outstanding timestamps 9) ice_irq_dynamic_ena() is called to re-enable the OICR hardware interrupt cause 10) ice_ptp_tx_tstamps_pending() is called to check if we missed any more outstanding timestamps, checking both software and hardware indicators. With this change, it should no longer be possible for new timestamps to come in such a way that we lose an interrupt. If a timestamp comes in before the ice_ptp_tx_tstamps_pending() call, it will be noticed by at least one of the software bitmap check or the hardware bitmap check. If the timestamp comes in *after* this check, it should cause a timestamp interrupt as we have already read all timestamps from the PHY and the OICR vector has been re-enabled. Fixes: 7cab44f1c35f ("ice: Introduce ETH56G PHY model for E825C products") Signed-off-by: Jacob Keller Reviewed-by: Aleksandr Loktionov Reviewed-by: Przemyslaw Korba Tested-by: Vitaly Grinberg Tested-by: Sunitha Mekala (A Contingent worker at Intel) Signed-off-by: Tony Nguyen Signed-off-by: Sasha Levin --- drivers/net/ethernet/intel/ice/ice_main.c | 20 +-- drivers/net/ethernet/intel/ice/ice_ptp.c | 148 ++++++++++++---------- drivers/net/ethernet/intel/ice/ice_ptp.h | 13 +- 3 files changed, 103 insertions(+), 78 deletions(-) diff --git a/drivers/net/ethernet/intel/ice/ice_main.c b/drivers/net/ethernet/intel/ice/ice_main.c index c52324d999eb4d..7a59c9dd07cb1f 100644 --- a/drivers/net/ethernet/intel/ice/ice_main.c +++ b/drivers/net/ethernet/intel/ice/ice_main.c @@ -3323,18 +3323,20 @@ static irqreturn_t ice_misc_intr_thread_fn(int __always_unused irq, void *data) if (ice_is_reset_in_progress(pf->state)) goto skip_irq; - if (test_and_clear_bit(ICE_MISC_THREAD_TX_TSTAMP, pf->misc_thread)) { - /* Process outstanding Tx timestamps. If there is more work, - * re-arm the interrupt to trigger again. - */ - if (ice_ptp_process_ts(pf) == ICE_TX_TSTAMP_WORK_PENDING) { - wr32(hw, PFINT_OICR, PFINT_OICR_TSYN_TX_M); - ice_flush(hw); - } - } + if (test_and_clear_bit(ICE_MISC_THREAD_TX_TSTAMP, pf->misc_thread)) + ice_ptp_process_ts(pf); skip_irq: ice_irq_dynamic_ena(hw, NULL, NULL); + ice_flush(hw); + + if (ice_ptp_tx_tstamps_pending(pf)) { + /* If any new Tx timestamps happened while in interrupt, + * re-arm the interrupt to trigger it again. + */ + wr32(hw, PFINT_OICR, PFINT_OICR_TSYN_TX_M); + ice_flush(hw); + } return IRQ_HANDLED; } diff --git a/drivers/net/ethernet/intel/ice/ice_ptp.c b/drivers/net/ethernet/intel/ice/ice_ptp.c index 4aa88bac759f8c..44c1ca58b8806d 100644 --- a/drivers/net/ethernet/intel/ice/ice_ptp.c +++ b/drivers/net/ethernet/intel/ice/ice_ptp.c @@ -569,6 +569,9 @@ static void ice_ptp_process_tx_tstamp(struct ice_ptp_tx *tx) pf = ptp_port_to_pf(ptp_port); hw = &pf->hw; + if (!tx->init) + return; + /* Read the Tx ready status first */ if (tx->has_ready_bitmap) { err = ice_get_phy_tx_tstamp_ready(hw, tx->block, &tstamp_ready); @@ -665,14 +668,9 @@ static void ice_ptp_process_tx_tstamp(struct ice_ptp_tx *tx) } } -/** - * ice_ptp_tx_tstamp_owner - Process Tx timestamps for all ports on the device - * @pf: Board private structure - */ -static enum ice_tx_tstamp_work ice_ptp_tx_tstamp_owner(struct ice_pf *pf) +static void ice_ptp_tx_tstamp_owner(struct ice_pf *pf) { struct ice_ptp_port *port; - unsigned int i; mutex_lock(&pf->adapter->ports.lock); list_for_each_entry(port, &pf->adapter->ports.ports, list_node) { @@ -684,49 +682,6 @@ static enum ice_tx_tstamp_work ice_ptp_tx_tstamp_owner(struct ice_pf *pf) ice_ptp_process_tx_tstamp(tx); } mutex_unlock(&pf->adapter->ports.lock); - - for (i = 0; i < ICE_GET_QUAD_NUM(pf->hw.ptp.num_lports); i++) { - u64 tstamp_ready; - int err; - - /* Read the Tx ready status first */ - err = ice_get_phy_tx_tstamp_ready(&pf->hw, i, &tstamp_ready); - if (err) - break; - else if (tstamp_ready) - return ICE_TX_TSTAMP_WORK_PENDING; - } - - return ICE_TX_TSTAMP_WORK_DONE; -} - -/** - * ice_ptp_tx_tstamp - Process Tx timestamps for this function. - * @tx: Tx tracking structure to initialize - * - * Returns: ICE_TX_TSTAMP_WORK_PENDING if there are any outstanding incomplete - * Tx timestamps, or ICE_TX_TSTAMP_WORK_DONE otherwise. - */ -static enum ice_tx_tstamp_work ice_ptp_tx_tstamp(struct ice_ptp_tx *tx) -{ - bool more_timestamps; - unsigned long flags; - - if (!tx->init) - return ICE_TX_TSTAMP_WORK_DONE; - - /* Process the Tx timestamp tracker */ - ice_ptp_process_tx_tstamp(tx); - - /* Check if there are outstanding Tx timestamps */ - spin_lock_irqsave(&tx->lock, flags); - more_timestamps = tx->init && !bitmap_empty(tx->in_use, tx->len); - spin_unlock_irqrestore(&tx->lock, flags); - - if (more_timestamps) - return ICE_TX_TSTAMP_WORK_PENDING; - - return ICE_TX_TSTAMP_WORK_DONE; } /** @@ -2659,30 +2614,92 @@ s8 ice_ptp_request_ts(struct ice_ptp_tx *tx, struct sk_buff *skb) return idx + tx->offset; } -/** - * ice_ptp_process_ts - Process the PTP Tx timestamps - * @pf: Board private structure - * - * Returns: ICE_TX_TSTAMP_WORK_PENDING if there are any outstanding Tx - * timestamps that need processing, and ICE_TX_TSTAMP_WORK_DONE otherwise. - */ -enum ice_tx_tstamp_work ice_ptp_process_ts(struct ice_pf *pf) +void ice_ptp_process_ts(struct ice_pf *pf) { switch (pf->ptp.tx_interrupt_mode) { case ICE_PTP_TX_INTERRUPT_NONE: /* This device has the clock owner handle timestamps for it */ - return ICE_TX_TSTAMP_WORK_DONE; + return; case ICE_PTP_TX_INTERRUPT_SELF: /* This device handles its own timestamps */ - return ice_ptp_tx_tstamp(&pf->ptp.port.tx); + ice_ptp_process_tx_tstamp(&pf->ptp.port.tx); + return; case ICE_PTP_TX_INTERRUPT_ALL: /* This device handles timestamps for all ports */ - return ice_ptp_tx_tstamp_owner(pf); + ice_ptp_tx_tstamp_owner(pf); + return; + default: + WARN_ONCE(1, "Unexpected Tx timestamp interrupt mode %u\n", + pf->ptp.tx_interrupt_mode); + return; + } +} + +static bool ice_port_has_timestamps(struct ice_ptp_tx *tx) +{ + bool more_timestamps; + + scoped_guard(spinlock_irqsave, &tx->lock) { + if (!tx->init) + return false; + + more_timestamps = !bitmap_empty(tx->in_use, tx->len); + } + + return more_timestamps; +} + +static bool ice_any_port_has_timestamps(struct ice_pf *pf) +{ + struct ice_ptp_port *port; + + scoped_guard(mutex, &pf->adapter->ports.lock) { + list_for_each_entry(port, &pf->adapter->ports.ports, + list_node) { + struct ice_ptp_tx *tx = &port->tx; + + if (ice_port_has_timestamps(tx)) + return true; + } + } + + return false; +} + +bool ice_ptp_tx_tstamps_pending(struct ice_pf *pf) +{ + struct ice_hw *hw = &pf->hw; + unsigned int i; + + /* Check software indicator */ + switch (pf->ptp.tx_interrupt_mode) { + case ICE_PTP_TX_INTERRUPT_NONE: + return false; + case ICE_PTP_TX_INTERRUPT_SELF: + if (ice_port_has_timestamps(&pf->ptp.port.tx)) + return true; + break; + case ICE_PTP_TX_INTERRUPT_ALL: + if (ice_any_port_has_timestamps(pf)) + return true; + break; default: WARN_ONCE(1, "Unexpected Tx timestamp interrupt mode %u\n", pf->ptp.tx_interrupt_mode); - return ICE_TX_TSTAMP_WORK_DONE; + break; + } + + /* Check hardware indicator */ + for (i = 0; i < ICE_GET_QUAD_NUM(hw->ptp.num_lports); i++) { + u64 tstamp_ready = 0; + int err; + + err = ice_get_phy_tx_tstamp_ready(&pf->hw, i, &tstamp_ready); + if (err || tstamp_ready) + return true; } + + return false; } /** @@ -2734,7 +2751,9 @@ irqreturn_t ice_ptp_ts_irq(struct ice_pf *pf) return IRQ_WAKE_THREAD; case ICE_MAC_E830: /* E830 can read timestamps in the top half using rd32() */ - if (ice_ptp_process_ts(pf) == ICE_TX_TSTAMP_WORK_PENDING) { + ice_ptp_process_ts(pf); + + if (ice_ptp_tx_tstamps_pending(pf)) { /* Process outstanding Tx timestamps. If there * is more work, re-arm the interrupt to trigger again. */ @@ -3187,8 +3206,9 @@ static void ice_ptp_init_tx_interrupt_mode(struct ice_pf *pf) { switch (pf->hw.mac_type) { case ICE_MAC_GENERIC: - /* E822 based PHY has the clock owner process the interrupt - * for all ports. + case ICE_MAC_GENERIC_3K_E825: + /* E82x hardware has the clock owner process timestamps for + * all ports. */ if (ice_pf_src_tmr_owned(pf)) pf->ptp.tx_interrupt_mode = ICE_PTP_TX_INTERRUPT_ALL; diff --git a/drivers/net/ethernet/intel/ice/ice_ptp.h b/drivers/net/ethernet/intel/ice/ice_ptp.h index 137f2070a2d991..46005642ef4197 100644 --- a/drivers/net/ethernet/intel/ice/ice_ptp.h +++ b/drivers/net/ethernet/intel/ice/ice_ptp.h @@ -302,8 +302,9 @@ void ice_ptp_extts_event(struct ice_pf *pf); s8 ice_ptp_request_ts(struct ice_ptp_tx *tx, struct sk_buff *skb); void ice_ptp_req_tx_single_tstamp(struct ice_ptp_tx *tx, u8 idx); void ice_ptp_complete_tx_single_tstamp(struct ice_ptp_tx *tx); -enum ice_tx_tstamp_work ice_ptp_process_ts(struct ice_pf *pf); +void ice_ptp_process_ts(struct ice_pf *pf); irqreturn_t ice_ptp_ts_irq(struct ice_pf *pf); +bool ice_ptp_tx_tstamps_pending(struct ice_pf *pf); u64 ice_ptp_read_src_clk_reg(struct ice_pf *pf, struct ptp_system_timestamp *sts); @@ -343,16 +344,18 @@ static inline void ice_ptp_req_tx_single_tstamp(struct ice_ptp_tx *tx, u8 idx) static inline void ice_ptp_complete_tx_single_tstamp(struct ice_ptp_tx *tx) { } -static inline bool ice_ptp_process_ts(struct ice_pf *pf) -{ - return true; -} +static inline void ice_ptp_process_ts(struct ice_pf *pf) { } static inline irqreturn_t ice_ptp_ts_irq(struct ice_pf *pf) { return IRQ_HANDLED; } +static inline bool ice_ptp_tx_tstamps_pending(struct ice_pf *pf) +{ + return false; +} + static inline u64 ice_ptp_read_src_clk_reg(struct ice_pf *pf, struct ptp_system_timestamp *sts) { From 7565d4df66b6619b50dc36618d8b8f1787d77e19 Mon Sep 17 00:00:00 2001 From: Aaron Ma Date: Wed, 21 Jan 2026 15:51:06 +0800 Subject: [PATCH 3105/3902] ice: Fix PTP NULL pointer dereference during VSI rebuild [ Upstream commit fc6f36eaaedcf4b81af6fe1a568f018ffd530660 ] Fix race condition where PTP periodic work runs while VSI is being rebuilt, accessing NULL vsi->rx_rings. The sequence was: 1. ice_ptp_prepare_for_reset() cancels PTP work 2. ice_ptp_rebuild() immediately queues PTP work 3. VSI rebuild happens AFTER ice_ptp_rebuild() 4. PTP work runs and accesses NULL vsi->rx_rings Fix: Keep PTP work cancelled during rebuild, only queue it after VSI rebuild completes in ice_rebuild(). Added ice_ptp_queue_work() helper function to encapsulate the logic for queuing PTP work, ensuring it's only queued when PTP is supported and the state is ICE_PTP_READY. Error log: [ 121.392544] ice 0000:60:00.1: PTP reset successful [ 121.392692] BUG: kernel NULL pointer dereference, address: 0000000000000000 [ 121.392712] #PF: supervisor read access in kernel mode [ 121.392720] #PF: error_code(0x0000) - not-present page [ 121.392727] PGD 0 [ 121.392734] Oops: Oops: 0000 [#1] SMP NOPTI [ 121.392746] CPU: 8 UID: 0 PID: 1005 Comm: ice-ptp-0000:60 Tainted: G S 6.19.0-rc6+ #4 PREEMPT(voluntary) [ 121.392761] Tainted: [S]=CPU_OUT_OF_SPEC [ 121.392773] RIP: 0010:ice_ptp_update_cached_phctime+0xbf/0x150 [ice] [ 121.393042] Call Trace: [ 121.393047] [ 121.393055] ice_ptp_periodic_work+0x69/0x180 [ice] [ 121.393202] kthread_worker_fn+0xa2/0x260 [ 121.393216] ? __pfx_ice_ptp_periodic_work+0x10/0x10 [ice] [ 121.393359] ? __pfx_kthread_worker_fn+0x10/0x10 [ 121.393371] kthread+0x10d/0x230 [ 121.393382] ? __pfx_kthread+0x10/0x10 [ 121.393393] ret_from_fork+0x273/0x2b0 [ 121.393407] ? __pfx_kthread+0x10/0x10 [ 121.393417] ret_from_fork_asm+0x1a/0x30 [ 121.393432] Fixes: 803bef817807d ("ice: factor out ice_ptp_rebuild_owner()") Signed-off-by: Aaron Ma Tested-by: Sunitha Mekala (A Contingent worker at Intel) Signed-off-by: Tony Nguyen Signed-off-by: Sasha Levin --- drivers/net/ethernet/intel/ice/ice_main.c | 3 +++ drivers/net/ethernet/intel/ice/ice_ptp.c | 26 ++++++++++++++++++----- drivers/net/ethernet/intel/ice/ice_ptp.h | 5 +++++ 3 files changed, 29 insertions(+), 5 deletions(-) diff --git a/drivers/net/ethernet/intel/ice/ice_main.c b/drivers/net/ethernet/intel/ice/ice_main.c index 7a59c9dd07cb1f..d34a32a09bf871 100644 --- a/drivers/net/ethernet/intel/ice/ice_main.c +++ b/drivers/net/ethernet/intel/ice/ice_main.c @@ -7815,6 +7815,9 @@ static void ice_rebuild(struct ice_pf *pf, enum ice_reset_req reset_type) /* Restore timestamp mode settings after VSI rebuild */ ice_ptp_restore_timestamp_mode(pf); + + /* Start PTP periodic work after VSI is fully rebuilt */ + ice_ptp_queue_work(pf); return; err_vsi_rebuild: diff --git a/drivers/net/ethernet/intel/ice/ice_ptp.c b/drivers/net/ethernet/intel/ice/ice_ptp.c index 44c1ca58b8806d..df38345b12d728 100644 --- a/drivers/net/ethernet/intel/ice/ice_ptp.c +++ b/drivers/net/ethernet/intel/ice/ice_ptp.c @@ -2832,6 +2832,20 @@ static void ice_ptp_periodic_work(struct kthread_work *work) msecs_to_jiffies(err ? 10 : 500)); } +/** + * ice_ptp_queue_work - Queue PTP periodic work for a PF + * @pf: Board private structure + * + * Helper function to queue PTP periodic work after VSI rebuild completes. + * This ensures that PTP work only runs when VSI structures are ready. + */ +void ice_ptp_queue_work(struct ice_pf *pf) +{ + if (test_bit(ICE_FLAG_PTP_SUPPORTED, pf->flags) && + pf->ptp.state == ICE_PTP_READY) + kthread_queue_delayed_work(pf->ptp.kworker, &pf->ptp.work, 0); +} + /** * ice_ptp_prepare_rebuild_sec - Prepare second NAC for PTP reset or rebuild * @pf: Board private structure @@ -2850,10 +2864,15 @@ static void ice_ptp_prepare_rebuild_sec(struct ice_pf *pf, bool rebuild, struct ice_pf *peer_pf = ptp_port_to_pf(port); if (!ice_is_primary(&peer_pf->hw)) { - if (rebuild) + if (rebuild) { + /* TODO: When implementing rebuild=true: + * 1. Ensure secondary PFs' VSIs are rebuilt + * 2. Call ice_ptp_queue_work(peer_pf) after VSI rebuild + */ ice_ptp_rebuild(peer_pf, reset_type); - else + } else { ice_ptp_prepare_for_reset(peer_pf, reset_type); + } } } } @@ -2999,9 +3018,6 @@ void ice_ptp_rebuild(struct ice_pf *pf, enum ice_reset_req reset_type) ptp->state = ICE_PTP_READY; - /* Start periodic work going */ - kthread_queue_delayed_work(ptp->kworker, &ptp->work, 0); - dev_info(ice_pf_to_dev(pf), "PTP reset successful\n"); return; diff --git a/drivers/net/ethernet/intel/ice/ice_ptp.h b/drivers/net/ethernet/intel/ice/ice_ptp.h index 46005642ef4197..4e02f922c1ff8a 100644 --- a/drivers/net/ethernet/intel/ice/ice_ptp.h +++ b/drivers/net/ethernet/intel/ice/ice_ptp.h @@ -316,6 +316,7 @@ void ice_ptp_prepare_for_reset(struct ice_pf *pf, void ice_ptp_init(struct ice_pf *pf); void ice_ptp_release(struct ice_pf *pf); void ice_ptp_link_change(struct ice_pf *pf, bool linkup); +void ice_ptp_queue_work(struct ice_pf *pf); #else /* IS_ENABLED(CONFIG_PTP_1588_CLOCK) */ static inline int ice_ptp_hwtstamp_get(struct net_device *netdev, @@ -384,6 +385,10 @@ static inline void ice_ptp_link_change(struct ice_pf *pf, bool linkup) { } +static inline void ice_ptp_queue_work(struct ice_pf *pf) +{ +} + static inline int ice_ptp_clock_index(struct ice_pf *pf) { return -1; From 07bb882485f892d8b669d588bebc2eebdcc847c5 Mon Sep 17 00:00:00 2001 From: Mohammad Heib Date: Sun, 28 Dec 2025 21:40:21 +0200 Subject: [PATCH 3106/3902] ice: drop udp_tunnel_get_rx_info() call from ndo_open() [ Upstream commit 234e615bfece9e3e91c50fe49ab9e68ee37c791a ] The ice driver calls udp_tunnel_get_rx_info() during ice_open_internal(). This is redundant because UDP tunnel RX offload state is preserved across device down/up cycles. The udp_tunnel core handles synchronization automatically when required. Furthermore, recent changes in the udp_tunnel infrastructure require querying RX info while holding the udp_tunnel lock. Calling it directly from the ndo_open path violates this requirement, triggering the following lockdep warning: Call Trace: ice_open_internal+0x253/0x350 [ice] __udp_tunnel_nic_assert_locked+0x86/0xb0 [udp_tunnel] __dev_open+0x2f5/0x880 __dev_change_flags+0x44c/0x660 netif_change_flags+0x80/0x160 devinet_ioctl+0xd21/0x15f0 inet_ioctl+0x311/0x350 sock_ioctl+0x114/0x220 __x64_sys_ioctl+0x131/0x1a0 ... Remove the redundant and unsafe call to udp_tunnel_get_rx_info() from ice_open_internal() to resolve the locking violation Fixes: 1ead7501094c ("udp_tunnel: remove rtnl_lock dependency") Signed-off-by: Mohammad Heib Reviewed-by: Aleksandr Loktionov Tested-by: Rinitha S (A Contingent worker at Intel) Signed-off-by: Tony Nguyen Signed-off-by: Sasha Levin --- drivers/net/ethernet/intel/ice/ice_main.c | 3 --- 1 file changed, 3 deletions(-) diff --git a/drivers/net/ethernet/intel/ice/ice_main.c b/drivers/net/ethernet/intel/ice/ice_main.c index d34a32a09bf871..f2b91f7f878618 100644 --- a/drivers/net/ethernet/intel/ice/ice_main.c +++ b/drivers/net/ethernet/intel/ice/ice_main.c @@ -9678,9 +9678,6 @@ int ice_open_internal(struct net_device *netdev) netdev_err(netdev, "Failed to open VSI 0x%04X on switch 0x%04X\n", vsi->vsi_num, vsi->vsw->sw_id); - /* Update existing tunnels information */ - udp_tunnel_get_rx_info(netdev); - return err; } From 8553bf2e09828bf3f6f96be2b25fca0f310ea957 Mon Sep 17 00:00:00 2001 From: Mohammad Heib Date: Sun, 28 Dec 2025 21:40:20 +0200 Subject: [PATCH 3107/3902] i40e: drop udp_tunnel_get_rx_info() call from i40e_open() [ Upstream commit 40857194956dcaf3d2b66d6bd113d844c93bef54 ] The i40e driver calls udp_tunnel_get_rx_info() during i40e_open(). This is redundant because UDP tunnel RX offload state is preserved across device down/up cycles. The udp_tunnel core handles synchronization automatically when required. Furthermore, recent changes in the udp_tunnel infrastructure require querying RX info while holding the udp_tunnel lock. Calling it directly from the ndo_open path violates this requirement, triggering the following lockdep warning: Call Trace: ? __udp_tunnel_nic_assert_locked+0x39/0x40 [udp_tunnel] i40e_open+0x135/0x14f [i40e] __dev_open+0x121/0x2e0 __dev_change_flags+0x227/0x270 dev_change_flags+0x3d/0xb0 devinet_ioctl+0x56f/0x860 sock_do_ioctl+0x7b/0x130 __x64_sys_ioctl+0x91/0xd0 do_syscall_64+0x90/0x170 ... Remove the redundant and unsafe call to udp_tunnel_get_rx_info() from i40e_open() resolve the locking violation. Fixes: 1ead7501094c ("udp_tunnel: remove rtnl_lock dependency") Signed-off-by: Mohammad Heib Reviewed-by: Aleksandr Loktionov Reviewed-by: Paul Menzel Tested-by: Rinitha S (A Contingent worker at Intel) Signed-off-by: Tony Nguyen Signed-off-by: Sasha Levin --- drivers/net/ethernet/intel/i40e/i40e_main.c | 1 - 1 file changed, 1 deletion(-) diff --git a/drivers/net/ethernet/intel/i40e/i40e_main.c b/drivers/net/ethernet/intel/i40e/i40e_main.c index 0b1cc0481027ab..d3bc3207054f90 100644 --- a/drivers/net/ethernet/intel/i40e/i40e_main.c +++ b/drivers/net/ethernet/intel/i40e/i40e_main.c @@ -9030,7 +9030,6 @@ int i40e_open(struct net_device *netdev) TCP_FLAG_FIN | TCP_FLAG_CWR) >> 16); wr32(&pf->hw, I40E_GLLAN_TSOMSK_L, be32_to_cpu(TCP_FLAG_CWR) >> 16); - udp_tunnel_get_rx_info(netdev); return 0; } From c9e4daf62ca07eb23fd6f4a90890d4654035d200 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marek=20Beh=C3=BAn?= Date: Thu, 29 Jan 2026 09:22:27 +0100 Subject: [PATCH 3108/3902] net: sfp: Fix quirk for Ubiquiti U-Fiber Instant SFP module MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [ Upstream commit adcbadfd8e05d3558c9cfaa783f17c645181165f ] Commit fd580c9830316eda ("net: sfp: augment SFP parsing with phy_interface_t bitmap") did not add augumentation for the interface bitmap in the quirk for Ubiquiti U-Fiber Instant. The subsequent commit f81fa96d8a6c7a77 ("net: phylink: use phy_interface_t bitmaps for optical modules") then changed phylink code for selection of SFP interface: instead of using link mode bitmap, the interface bitmap is used, and the fastest interface mode supported by both SFP module and MAC is chosen. Since the interface bitmap contains also modes faster than 1000base-x, this caused a regression wherein this module stopped working out-of-the-box. Fix this. Fixes: fd580c9830316eda ("net: sfp: augment SFP parsing with phy_interface_t bitmap") Signed-off-by: Marek Behún Reviewed-by: Maxime Chevallier Reviewed-by: Russell King (Oracle) Link: https://patch.msgid.link/20260129082227.17443-1-kabel@kernel.org Signed-off-by: Jakub Kicinski Signed-off-by: Sasha Levin --- drivers/net/phy/sfp.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/drivers/net/phy/sfp.c b/drivers/net/phy/sfp.c index 47f095bd91ceab..3e023723887c4b 100644 --- a/drivers/net/phy/sfp.c +++ b/drivers/net/phy/sfp.c @@ -479,6 +479,8 @@ static void sfp_quirk_ubnt_uf_instant(const struct sfp_eeprom_id *id, linkmode_zero(caps->link_modes); linkmode_set_bit(ETHTOOL_LINK_MODE_1000baseX_Full_BIT, caps->link_modes); + phy_interface_zero(caps->interfaces); + __set_bit(PHY_INTERFACE_MODE_1000BASEX, caps->interfaces); } #define SFP_QUIRK(_v, _p, _s, _f) \ From cdedcd5aa3f3cb8b7ae0f87ab3a936d0bd583d66 Mon Sep 17 00:00:00 2001 From: Eric Dumazet Date: Thu, 29 Jan 2026 20:43:59 +0000 Subject: [PATCH 3109/3902] macvlan: fix error recovery in macvlan_common_newlink() [ Upstream commit f8db6475a83649689c087a8f52486fcc53e627e9 ] valis provided a nice repro to crash the kernel: ip link add p1 type veth peer p2 ip link set address 00:00:00:00:00:20 dev p1 ip link set up dev p1 ip link set up dev p2 ip link add mv0 link p2 type macvlan mode source ip link add invalid% link p2 type macvlan mode source macaddr add 00:00:00:00:00:20 ping -c1 -I p1 1.2.3.4 He also gave a very detailed analysis: The issue is triggered when a new macvlan link is created with MACVLAN_MODE_SOURCE mode and MACVLAN_MACADDR_ADD (or MACVLAN_MACADDR_SET) parameter, lower device already has a macvlan port and register_netdevice() called from macvlan_common_newlink() fails (e.g. because of the invalid link name). In this case macvlan_hash_add_source is called from macvlan_change_sources() / macvlan_common_newlink(): This adds a reference to vlan to the port's vlan_source_hash using macvlan_source_entry. vlan is a pointer to the priv data of the link that is being created. When register_netdevice() fails, the error is returned from macvlan_newlink() to rtnl_newlink_create(): if (ops->newlink) err = ops->newlink(dev, ¶ms, extack); else err = register_netdevice(dev); if (err < 0) { free_netdev(dev); goto out; } and free_netdev() is called, causing a kvfree() on the struct net_device that is still referenced in the source entry attached to the lower device's macvlan port. Now all packets sent on the macvlan port with a matching source mac address will trigger a use-after-free in macvlan_forward_source(). With all that, my fix is to make sure we call macvlan_flush_sources() regardless of @create value whenever "goto destroy_macvlan_port;" path is taken. Many thanks to valis for following up on this issue. Fixes: aa5fd0fb7748 ("driver: macvlan: Destroy new macvlan port if macvlan_common_newlink failed.") Signed-off-by: Eric Dumazet Reported-by: valis Reported-by: syzbot+7182fbe91e58602ec1fe@syzkaller.appspotmail.com Closes: https: //lore.kernel.org/netdev/695fb1e8.050a0220.1c677c.039f.GAE@google.com/T/#u Cc: Boudewijn van der Heide Link: https://patch.msgid.link/20260129204359.632556-1-edumazet@google.com Signed-off-by: Jakub Kicinski Signed-off-by: Sasha Levin --- drivers/net/macvlan.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/drivers/net/macvlan.c b/drivers/net/macvlan.c index b4df7e184791d0..c509228be84d1b 100644 --- a/drivers/net/macvlan.c +++ b/drivers/net/macvlan.c @@ -1567,9 +1567,10 @@ int macvlan_common_newlink(struct net_device *dev, /* the macvlan port may be freed by macvlan_uninit when fail to register. * so we destroy the macvlan port only when it's valid. */ - if (create && macvlan_port_get_rtnl(lowerdev)) { + if (macvlan_port_get_rtnl(lowerdev)) { macvlan_flush_sources(port, vlan); - macvlan_port_destroy(port->dev); + if (create) + macvlan_port_destroy(port->dev); } return err; } From 1b2efc593dca99d8e8e6f6d6c7ccd9a972679702 Mon Sep 17 00:00:00 2001 From: Sergey Senozhatsky Date: Thu, 29 Jan 2026 12:10:30 +0900 Subject: [PATCH 3110/3902] net: usb: r8152: fix resume reset deadlock [ Upstream commit 6d06bc83a5ae8777a5f7a81c32dd75b8d9b2fe04 ] rtl8152 can trigger device reset during reset which potentially can result in a deadlock: **** DPM device timeout after 10 seconds; 15 seconds until panic **** Call Trace: schedule+0x483/0x1370 schedule_preempt_disabled+0x15/0x30 __mutex_lock_common+0x1fd/0x470 __rtl8152_set_mac_address+0x80/0x1f0 dev_set_mac_address+0x7f/0x150 rtl8152_post_reset+0x72/0x150 usb_reset_device+0x1d0/0x220 rtl8152_resume+0x99/0xc0 usb_resume_interface+0x3e/0xc0 usb_resume_both+0x104/0x150 usb_resume+0x22/0x110 The problem is that rtl8152 resume calls reset under tp->control mutex while reset basically re-enters rtl8152 and attempts to acquire the same tp->control lock once again. Reset INACCESSIBLE device outside of tp->control mutex scope to avoid recursive mutex_lock() deadlock. Fixes: 4933b066fefb ("r8152: If inaccessible at resume time, issue a reset") Reviewed-by: Douglas Anderson Signed-off-by: Sergey Senozhatsky Link: https://patch.msgid.link/20260129031106.3805887-1-senozhatsky@chromium.org Signed-off-by: Jakub Kicinski Signed-off-by: Sasha Levin --- drivers/net/usb/r8152.c | 29 +++++++++++++++-------------- 1 file changed, 15 insertions(+), 14 deletions(-) diff --git a/drivers/net/usb/r8152.c b/drivers/net/usb/r8152.c index a22d4bb2cf3b58..6a43054d5171fe 100644 --- a/drivers/net/usb/r8152.c +++ b/drivers/net/usb/r8152.c @@ -8535,19 +8535,6 @@ static int rtl8152_system_resume(struct r8152 *tp) usb_submit_urb(tp->intr_urb, GFP_NOIO); } - /* If the device is RTL8152_INACCESSIBLE here then we should do a - * reset. This is important because the usb_lock_device_for_reset() - * that happens as a result of usb_queue_reset_device() will silently - * fail if the device was suspended or if too much time passed. - * - * NOTE: The device is locked here so we can directly do the reset. - * We don't need usb_lock_device_for_reset() because that's just a - * wrapper over device_lock() and device_resume() (which calls us) - * does that for us. - */ - if (test_bit(RTL8152_INACCESSIBLE, &tp->flags)) - usb_reset_device(tp->udev); - return 0; } @@ -8658,19 +8645,33 @@ static int rtl8152_suspend(struct usb_interface *intf, pm_message_t message) static int rtl8152_resume(struct usb_interface *intf) { struct r8152 *tp = usb_get_intfdata(intf); + bool runtime_resume = test_bit(SELECTIVE_SUSPEND, &tp->flags); int ret; mutex_lock(&tp->control); rtl_reset_ocp_base(tp); - if (test_bit(SELECTIVE_SUSPEND, &tp->flags)) + if (runtime_resume) ret = rtl8152_runtime_resume(tp); else ret = rtl8152_system_resume(tp); mutex_unlock(&tp->control); + /* If the device is RTL8152_INACCESSIBLE here then we should do a + * reset. This is important because the usb_lock_device_for_reset() + * that happens as a result of usb_queue_reset_device() will silently + * fail if the device was suspended or if too much time passed. + * + * NOTE: The device is locked here so we can directly do the reset. + * We don't need usb_lock_device_for_reset() because that's just a + * wrapper over device_lock() and device_resume() (which calls us) + * does that for us. + */ + if (!runtime_resume && test_bit(RTL8152_INACCESSIBLE, &tp->flags)) + usb_reset_device(tp->udev); + return ret; } From 8860ddf0e07be37169d4ef9f2618e39fca934a66 Mon Sep 17 00:00:00 2001 From: "Rafael J. Wysocki" Date: Sat, 31 Jan 2026 07:23:28 -0800 Subject: [PATCH 3111/3902] hwmon: (acpi_power_meter) Fix deadlocks related to acpi_power_meter_notify() [ Upstream commit 615901b57b7ef8eb655f71358f7e956e42bcd16b ] The acpi_power_meter driver's .notify() callback function, acpi_power_meter_notify(), calls hwmon_device_unregister() under a lock that is also acquired by callbacks in sysfs attributes of the device being unregistered which is prone to deadlocks between sysfs access and device removal. Address this by moving the hwmon device removal in acpi_power_meter_notify() outside the lock in question, but notice that doing it alone is not sufficient because two concurrent METER_NOTIFY_CONFIG notifications may be attempting to remove the same device at the same time. To prevent that from happening, add a new lock serializing the execution of the switch () statement in acpi_power_meter_notify(). For simplicity, it is a static mutex which should not be a problem from the performance perspective. The new lock also allows the hwmon_device_register_with_info() in acpi_power_meter_notify() to be called outside the inner lock because it prevents the other notifications handled by that function from manipulating the "resource" object while the hwmon device based on it is being registered. The sending of ACPI netlink messages from acpi_power_meter_notify() is serialized by the new lock too which generally helps to ensure that the order of handling firmware notifications is the same as the order of sending netlink messages related to them. In addition, notice that hwmon_device_register_with_info() may fail in which case resource->hwmon_dev will become an error pointer, so add checks to avoid attempting to unregister the hwmon device pointer to by it in that case to acpi_power_meter_notify() and acpi_power_meter_remove(). Fixes: 16746ce8adfe ("hwmon: (acpi_power_meter) Replace the deprecated hwmon_device_register") Closes: https://lore.kernel.org/linux-hwmon/CAK8fFZ58fidGUCHi5WFX0uoTPzveUUDzT=k=AAm4yWo3bAuCFg@mail.gmail.com/ Reported-by: Jaroslav Pulchart Signed-off-by: Rafael J. Wysocki Signed-off-by: Guenter Roeck Signed-off-by: Sasha Levin --- drivers/hwmon/acpi_power_meter.c | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/drivers/hwmon/acpi_power_meter.c b/drivers/hwmon/acpi_power_meter.c index 29ccdc2fb7ff85..de408df0c4d786 100644 --- a/drivers/hwmon/acpi_power_meter.c +++ b/drivers/hwmon/acpi_power_meter.c @@ -47,6 +47,8 @@ static int cap_in_hardware; static bool force_cap_on; +static DEFINE_MUTEX(acpi_notify_lock); + static int can_cap_in_hardware(void) { return force_cap_on || cap_in_hardware; @@ -823,18 +825,26 @@ static void acpi_power_meter_notify(struct acpi_device *device, u32 event) resource = acpi_driver_data(device); + guard(mutex)(&acpi_notify_lock); + switch (event) { case METER_NOTIFY_CONFIG: + if (!IS_ERR(resource->hwmon_dev)) + hwmon_device_unregister(resource->hwmon_dev); + mutex_lock(&resource->lock); + free_capabilities(resource); remove_domain_devices(resource); - hwmon_device_unregister(resource->hwmon_dev); res = read_capabilities(resource); if (res) dev_err_once(&device->dev, "read capabilities failed.\n"); res = read_domain_devices(resource); if (res && res != -ENODEV) dev_err_once(&device->dev, "read domain devices failed.\n"); + + mutex_unlock(&resource->lock); + resource->hwmon_dev = hwmon_device_register_with_info(&device->dev, ACPI_POWER_METER_NAME, @@ -843,7 +853,7 @@ static void acpi_power_meter_notify(struct acpi_device *device, u32 event) power_extra_groups); if (IS_ERR(resource->hwmon_dev)) dev_err_once(&device->dev, "register hwmon device failed.\n"); - mutex_unlock(&resource->lock); + break; case METER_NOTIFY_TRIP: sysfs_notify(&device->dev.kobj, NULL, POWER_AVERAGE_NAME); @@ -953,7 +963,8 @@ static void acpi_power_meter_remove(struct acpi_device *device) return; resource = acpi_driver_data(device); - hwmon_device_unregister(resource->hwmon_dev); + if (!IS_ERR(resource->hwmon_dev)) + hwmon_device_unregister(resource->hwmon_dev); remove_domain_devices(resource); free_capabilities(resource); From fad7334082cd1244e621345cfdf89e1f6cd697a5 Mon Sep 17 00:00:00 2001 From: Jakub Kicinski Date: Thu, 29 Jan 2026 19:38:27 -0800 Subject: [PATCH 3112/3902] net: don't touch dev->stats in BPF redirect paths [ Upstream commit fdf3f6800be36377e045e2448087f12132b88d2f ] Gal reports that BPF redirect increments dev->stats.tx_errors on failure. This is not correct, most modern drivers completely ignore dev->stats so these drops will be invisible to the user. Core code should use the dedicated core stats which are folded into device stats in dev_get_stats(). Note that we're switching from tx_errors to tx_dropped. Core only has tx_dropped, hence presumably users already expect that counter to increment for "stack" Tx issues. Reported-by: Gal Pressman Link: https://lore.kernel.org/c5df3b60-246a-4030-9c9a-0a35cd1ca924@nvidia.com Fixes: b4ab31414970 ("bpf: Add redirect_neigh helper as redirect drop-in") Acked-by: Martin KaFai Lau Acked-by: Daniel Borkmann Reviewed-by: Eric Dumazet Link: https://patch.msgid.link/20260130033827.698841-1-kuba@kernel.org Signed-off-by: Jakub Kicinski Signed-off-by: Sasha Levin --- net/core/filter.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/net/core/filter.c b/net/core/filter.c index 6431ef3e9f7ddf..88b265f6ccf897 100644 --- a/net/core/filter.c +++ b/net/core/filter.c @@ -2289,12 +2289,12 @@ static int __bpf_redirect_neigh_v6(struct sk_buff *skb, struct net_device *dev, err = bpf_out_neigh_v6(net, skb, dev, nh); if (unlikely(net_xmit_eval(err))) - DEV_STATS_INC(dev, tx_errors); + dev_core_stats_tx_dropped_inc(dev); else ret = NET_XMIT_SUCCESS; goto out_xmit; out_drop: - DEV_STATS_INC(dev, tx_errors); + dev_core_stats_tx_dropped_inc(dev); kfree_skb(skb); out_xmit: return ret; @@ -2396,12 +2396,12 @@ static int __bpf_redirect_neigh_v4(struct sk_buff *skb, struct net_device *dev, err = bpf_out_neigh_v4(net, skb, dev, nh); if (unlikely(net_xmit_eval(err))) - DEV_STATS_INC(dev, tx_errors); + dev_core_stats_tx_dropped_inc(dev); else ret = NET_XMIT_SUCCESS; goto out_xmit; out_drop: - DEV_STATS_INC(dev, tx_errors); + dev_core_stats_tx_dropped_inc(dev); kfree_skb(skb); out_xmit: return ret; From 64cf3016234ce8a6e4195ed1b2d9e2a1ae41b57d Mon Sep 17 00:00:00 2001 From: Pavel Begunkov Date: Sun, 1 Feb 2026 21:18:53 +0000 Subject: [PATCH 3113/3902] io_uring/zcrx: fix page array leak [ Upstream commit 0ae91d8ab70922fb74c22c20bedcb69459579b1c ] d9f595b9a65e ("io_uring/zcrx: fix leaking pages on sg init fail") fixed a page leakage but didn't free the page array, release it as well. Fixes: b84621d96ee02 ("io_uring/zcrx: allocate sgtable for umem areas") Signed-off-by: Pavel Begunkov Signed-off-by: Jens Axboe Signed-off-by: Sasha Levin --- io_uring/zcrx.c | 1 + 1 file changed, 1 insertion(+) diff --git a/io_uring/zcrx.c b/io_uring/zcrx.c index 875ad40cf65911..03396769c775dc 100644 --- a/io_uring/zcrx.c +++ b/io_uring/zcrx.c @@ -196,6 +196,7 @@ static int io_import_umem(struct io_zcrx_ifq *ifq, GFP_KERNEL_ACCOUNT); if (ret) { unpin_user_pages(pages, nr_pages); + kvfree(pages); return ret; } From 2718ae6af7445ba2ee0abf6365ca43a9a3b16aeb Mon Sep 17 00:00:00 2001 From: Jiayuan Chen Date: Sun, 1 Feb 2026 21:59:10 +0800 Subject: [PATCH 3114/3902] linkwatch: use __dev_put() in callers to prevent UAF [ Upstream commit 83b67cc9be9223183caf91826d9c194d7fb128fa ] After linkwatch_do_dev() calls __dev_put() to release the linkwatch reference, the device refcount may drop to 1. At this point, netdev_run_todo() can proceed (since linkwatch_sync_dev() sees an empty list and returns without blocking), wait for the refcount to become 1 via netdev_wait_allrefs_any(), and then free the device via kobject_put(). This creates a use-after-free when __linkwatch_run_queue() tries to call netdev_unlock_ops() on the already-freed device. Note that adding netdev_lock_ops()/netdev_unlock_ops() pair in netdev_run_todo() before kobject_put() would not work, because netdev_lock_ops() is conditional - it only locks when netdev_need_ops_lock() returns true. If the device doesn't require ops_lock, linkwatch won't hold any lock, and netdev_run_todo() acquiring the lock won't provide synchronization. Fix this by moving __dev_put() from linkwatch_do_dev() to its callers. The device reference logically pairs with de-listing the device, so it's reasonable for the caller that did the de-listing to release it. This allows placing __dev_put() after all device accesses are complete, preventing UAF. The bug can be reproduced by adding mdelay(2000) after linkwatch_do_dev() in __linkwatch_run_queue(), then running: ip tuntap add mode tun name tun_test ip link set tun_test up ip link set tun_test carrier off ip link set tun_test carrier on sleep 0.5 ip tuntap del mode tun name tun_test KASAN report: ================================================================== BUG: KASAN: use-after-free in netdev_need_ops_lock include/net/netdev_lock.h:33 [inline] BUG: KASAN: use-after-free in netdev_unlock_ops include/net/netdev_lock.h:47 [inline] BUG: KASAN: use-after-free in __linkwatch_run_queue+0x865/0x8a0 net/core/link_watch.c:245 Read of size 8 at addr ffff88804de5c008 by task kworker/u32:10/8123 CPU: 0 UID: 0 PID: 8123 Comm: kworker/u32:10 Not tainted syzkaller #0 PREEMPT(full) Hardware name: QEMU Standard PC (Q35 + ICH9, 2009), BIOS 1.16.3-debian-1.16.3-2 04/01/2014 Workqueue: events_unbound linkwatch_event Call Trace: __dump_stack lib/dump_stack.c:94 [inline] dump_stack_lvl+0x100/0x190 lib/dump_stack.c:120 print_address_description mm/kasan/report.c:378 [inline] print_report+0x156/0x4c9 mm/kasan/report.c:482 kasan_report+0xdf/0x1a0 mm/kasan/report.c:595 netdev_need_ops_lock include/net/netdev_lock.h:33 [inline] netdev_unlock_ops include/net/netdev_lock.h:47 [inline] __linkwatch_run_queue+0x865/0x8a0 net/core/link_watch.c:245 linkwatch_event+0x8f/0xc0 net/core/link_watch.c:304 process_one_work+0x9c2/0x1840 kernel/workqueue.c:3257 process_scheduled_works kernel/workqueue.c:3340 [inline] worker_thread+0x5da/0xe40 kernel/workqueue.c:3421 kthread+0x3b3/0x730 kernel/kthread.c:463 ret_from_fork+0x754/0xaf0 arch/x86/kernel/process.c:158 ret_from_fork_asm+0x1a/0x30 arch/x86/entry/entry_64.S:246 ================================================================== Fixes: 04efcee6ef8d ("net: hold instance lock during NETDEV_CHANGE") Reported-by: syzbot+1ec2f6a450f0b54af8c8@syzkaller.appspotmail.com Closes: https://lore.kernel.org/all/6824d064.a70a0220.3e9d8.001a.GAE@google.com/T/ Signed-off-by: Jiayuan Chen Signed-off-by: Jiayuan Chen Reviewed-by: Eric Dumazet Link: https://patch.msgid.link/20260201135915.393451-1-jiayuan.chen@linux.dev Signed-off-by: Jakub Kicinski Signed-off-by: Sasha Levin --- net/core/link_watch.c | 20 +++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/net/core/link_watch.c b/net/core/link_watch.c index 212cde35affa7b..25c455c10a01cf 100644 --- a/net/core/link_watch.c +++ b/net/core/link_watch.c @@ -185,10 +185,6 @@ static void linkwatch_do_dev(struct net_device *dev) netif_state_change(dev); } - /* Note: our callers are responsible for calling netdev_tracker_free(). - * This is the reason we use __dev_put() instead of dev_put(). - */ - __dev_put(dev); } static void __linkwatch_run_queue(int urgent_only) @@ -243,6 +239,11 @@ static void __linkwatch_run_queue(int urgent_only) netdev_lock_ops(dev); linkwatch_do_dev(dev); netdev_unlock_ops(dev); + /* Use __dev_put() because netdev_tracker_free() was already + * called above. Must be after netdev_unlock_ops() to prevent + * netdev_run_todo() from freeing the device while still in use. + */ + __dev_put(dev); do_dev--; spin_lock_irq(&lweventlist_lock); } @@ -278,8 +279,13 @@ void __linkwatch_sync_dev(struct net_device *dev) { netdev_ops_assert_locked(dev); - if (linkwatch_clean_dev(dev)) + if (linkwatch_clean_dev(dev)) { linkwatch_do_dev(dev); + /* Use __dev_put() because netdev_tracker_free() was already + * called inside linkwatch_clean_dev(). + */ + __dev_put(dev); + } } void linkwatch_sync_dev(struct net_device *dev) @@ -288,6 +294,10 @@ void linkwatch_sync_dev(struct net_device *dev) netdev_lock_ops(dev); linkwatch_do_dev(dev); netdev_unlock_ops(dev); + /* Use __dev_put() because netdev_tracker_free() was already + * called inside linkwatch_clean_dev(). + */ + __dev_put(dev); } } From f3931416cbdd043e83d50198e7e740f205211672 Mon Sep 17 00:00:00 2001 From: Jakub Kicinski Date: Fri, 30 Jan 2026 11:03:11 -0800 Subject: [PATCH 3115/3902] net: rss: fix reporting RXH_XFRM_NO_CHANGE as input_xfrm for contexts [ Upstream commit 1c172febdf065375359b2b95156e476bfee30b60 ] Initializing input_xfrm to RXH_XFRM_NO_CHANGE in RSS contexts is problematic. I think I did this to make it clear that the context does not have its own settings applied. But unlike ETH_RSS_HASH_NO_CHANGE which is zero, RXH_XFRM_NO_CHANGE is 0xff. We need to be careful when reading the value back, and remember to treat 0xff as 0. Remove the initialization and switch to storing 0. This lets us also remove the workaround in ethnl_rss_set(). Get side does not need any adjustments and context get no longer reports: RSS input transformation: symmetric-xor: on symmetric-or-xor: on Unknown bits in RSS input transformation: 0xfc for NICs which don't support input_xfrm. Remove the init of hfunc to ETH_RSS_HASH_NO_CHANGE while at it. As already mentioned this is a noop since ETH_RSS_HASH_NO_CHANGE is 0 and struct is zalloc'd. But as this fix exemplifies storing NO_CHANGE as state is fragile. This issue is implicitly caught by running our selftests because YNL in selftests errors out on unknown bits. Fixes: d3e2c7bab124 ("ethtool: rss: support setting input-xfrm via Netlink") Link: https://patch.msgid.link/20260130190311.811129-1-kuba@kernel.org Signed-off-by: Jakub Kicinski Signed-off-by: Sasha Levin --- net/ethtool/common.c | 3 --- net/ethtool/rss.c | 9 ++------- 2 files changed, 2 insertions(+), 10 deletions(-) diff --git a/net/ethtool/common.c b/net/ethtool/common.c index 55223ebc2a7e62..146c7eaedc5acf 100644 --- a/net/ethtool/common.c +++ b/net/ethtool/common.c @@ -854,9 +854,6 @@ ethtool_rxfh_ctx_alloc(const struct ethtool_ops *ops, ctx->key_off = key_off; ctx->priv_size = ops->rxfh_priv_size; - ctx->hfunc = ETH_RSS_HASH_NO_CHANGE; - ctx->input_xfrm = RXH_XFRM_NO_CHANGE; - return ctx; } diff --git a/net/ethtool/rss.c b/net/ethtool/rss.c index 4dced53be4b3bb..da5934cceb0757 100644 --- a/net/ethtool/rss.c +++ b/net/ethtool/rss.c @@ -824,8 +824,8 @@ rss_set_ctx_update(struct ethtool_rxfh_context *ctx, struct nlattr **tb, static int ethnl_rss_set(struct ethnl_req_info *req_info, struct genl_info *info) { - bool indir_reset = false, indir_mod, xfrm_sym = false; struct rss_req_info *request = RSS_REQINFO(req_info); + bool indir_reset = false, indir_mod, xfrm_sym; struct ethtool_rxfh_context *ctx = NULL; struct net_device *dev = req_info->dev; bool mod = false, fields_mod = false; @@ -860,12 +860,7 @@ ethnl_rss_set(struct ethnl_req_info *req_info, struct genl_info *info) rxfh.input_xfrm = data.input_xfrm; ethnl_update_u8(&rxfh.input_xfrm, tb[ETHTOOL_A_RSS_INPUT_XFRM], &mod); - /* For drivers which don't support input_xfrm it will be set to 0xff - * in the RSS context info. In all other case input_xfrm != 0 means - * symmetric hashing is requested. - */ - if (!request->rss_context || ops->rxfh_per_ctx_key) - xfrm_sym = rxfh.input_xfrm || data.input_xfrm; + xfrm_sym = rxfh.input_xfrm || data.input_xfrm; if (rxfh.input_xfrm == data.input_xfrm) rxfh.input_xfrm = RXH_XFRM_NO_CHANGE; From c175b1eaf729eb9f5694cdfc7814fa8ad90e63e7 Mon Sep 17 00:00:00 2001 From: Daniel Hodges Date: Sat, 31 Jan 2026 10:01:14 -0800 Subject: [PATCH 3116/3902] tipc: use kfree_sensitive() for session key material [ Upstream commit 74d9391e8849e70ded5309222d09b0ed0edbd039 ] The rx->skey field contains a struct tipc_aead_key with GCM-AES encryption keys used for TIPC cluster communication. Using plain kfree() leaves this sensitive key material in freed memory pages where it could potentially be recovered. Switch to kfree_sensitive() to ensure the key material is zeroed before the memory is freed. Fixes: 1ef6f7c9390f ("tipc: add automatic session key exchange") Signed-off-by: Daniel Hodges Link: https://patch.msgid.link/20260131180114.2121438-1-hodgesd@meta.com Signed-off-by: Jakub Kicinski Signed-off-by: Sasha Levin --- net/tipc/crypto.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/net/tipc/crypto.c b/net/tipc/crypto.c index 751904f10aab00..970db62bd029b2 100644 --- a/net/tipc/crypto.c +++ b/net/tipc/crypto.c @@ -1219,7 +1219,7 @@ void tipc_crypto_key_flush(struct tipc_crypto *c) rx = c; tx = tipc_net(rx->net)->crypto_tx; if (cancel_delayed_work(&rx->work)) { - kfree(rx->skey); + kfree_sensitive(rx->skey); rx->skey = NULL; atomic_xchg(&rx->key_distr, 0); tipc_node_put(rx->node); @@ -2394,7 +2394,7 @@ static void tipc_crypto_work_rx(struct work_struct *work) break; default: synchronize_rcu(); - kfree(rx->skey); + kfree_sensitive(rx->skey); rx->skey = NULL; break; } From d98745c68023f8a45a4f5d7299b83994ee96213c Mon Sep 17 00:00:00 2001 From: Claudiu Manoil Date: Fri, 30 Jan 2026 16:10:32 +0200 Subject: [PATCH 3117/3902] net: enetc: Remove SI/BDR cacheability AXI settings for ENETC v4 [ Upstream commit a69c17230cab07bd156f894fdc82bd78b43ea72f ] For ENETC v4 these settings are controlled by the global ENETC message and buffer cache attribute registers (EnBCAR and EnMCAR), from the IERB register block. The hardcoded cacheability settings were inherited from LS1028A, and should be removed from the ENETC v4 driver as they conflict with the global IERB settings. Fixes: 99100d0d9922 ("net: enetc: add preliminary support for i.MX95 ENETC PF") Signed-off-by: Claudiu Manoil Reviewed-by: Wei Fang Link: https://patch.msgid.link/20260130141035.272471-2-claudiu.manoil@nxp.com Signed-off-by: Jakub Kicinski Signed-off-by: Sasha Levin --- drivers/net/ethernet/freescale/enetc/enetc.c | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/drivers/net/ethernet/freescale/enetc/enetc.c b/drivers/net/ethernet/freescale/enetc/enetc.c index f410c245ea918b..b6e3fb0401619c 100644 --- a/drivers/net/ethernet/freescale/enetc/enetc.c +++ b/drivers/net/ethernet/freescale/enetc/enetc.c @@ -2503,10 +2503,13 @@ int enetc_configure_si(struct enetc_ndev_priv *priv) struct enetc_hw *hw = &si->hw; int err; - /* set SI cache attributes */ - enetc_wr(hw, ENETC_SICAR0, - ENETC_SICAR_RD_COHERENT | ENETC_SICAR_WR_COHERENT); - enetc_wr(hw, ENETC_SICAR1, ENETC_SICAR_MSI); + if (is_enetc_rev1(si)) { + /* set SI cache attributes */ + enetc_wr(hw, ENETC_SICAR0, + ENETC_SICAR_RD_COHERENT | ENETC_SICAR_WR_COHERENT); + enetc_wr(hw, ENETC_SICAR1, ENETC_SICAR_MSI); + } + /* enable SI */ enetc_wr(hw, ENETC_SIMR, ENETC_SIMR_EN); From f346253e5fd95bda6a94486bb84ae8a4c0b4c150 Mon Sep 17 00:00:00 2001 From: Claudiu Manoil Date: Fri, 30 Jan 2026 16:10:33 +0200 Subject: [PATCH 3118/3902] net: enetc: Remove CBDR cacheability AXI settings for ENETC v4 [ Upstream commit 9ae13b2e64fcd2ca00a76b7d60fc4641a6b9209d ] For ENETC v4 these settings are controlled by the global ENETC command cache attribute registers (EnCAR), from the IERB register block. The hardcoded CDBR cacheability settings were inherited from LS1028A, and should be removed from the ENETC v4 driver as they conflict with the global IERB settings. Fixes: e3f4a0a8ddb4 ("net: enetc: add command BD ring support for i.MX95 ENETC") Signed-off-by: Claudiu Manoil Reviewed-by: Wei Fang Link: https://patch.msgid.link/20260130141035.272471-3-claudiu.manoil@nxp.com Signed-off-by: Jakub Kicinski Signed-off-by: Sasha Levin --- drivers/net/ethernet/freescale/enetc/enetc_cbdr.c | 4 ---- 1 file changed, 4 deletions(-) diff --git a/drivers/net/ethernet/freescale/enetc/enetc_cbdr.c b/drivers/net/ethernet/freescale/enetc/enetc_cbdr.c index 3d5f31879d5c64..a635bfdc30afc2 100644 --- a/drivers/net/ethernet/freescale/enetc/enetc_cbdr.c +++ b/drivers/net/ethernet/freescale/enetc/enetc_cbdr.c @@ -74,10 +74,6 @@ int enetc4_setup_cbdr(struct enetc_si *si) if (!user->ring) return -ENOMEM; - /* set CBDR cache attributes */ - enetc_wr(hw, ENETC_SICAR2, - ENETC_SICAR_RD_COHERENT | ENETC_SICAR_WR_COHERENT); - regs.pir = hw->reg + ENETC_SICBDRPIR; regs.cir = hw->reg + ENETC_SICBDRCIR; regs.mr = hw->reg + ENETC_SICBDRMR; From 566ea5769ec276e96462722e17ffc4467a61b34e Mon Sep 17 00:00:00 2001 From: Claudiu Manoil Date: Fri, 30 Jan 2026 16:10:34 +0200 Subject: [PATCH 3119/3902] net: enetc: Convert 16-bit register writes to 32-bit for ENETC v4 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [ Upstream commit 21d0fc95b5920ae8e69a2c0394bef82b8392bcc9 ] For ENETC v4, which is integrated into more complex SoCs (compared to v1), 16‑bit register writes are blocked in the SoC interconnect on some chips. To be fair, it is not recommended to access 32‑bit registers of this IP using lower‑width accessors (i.e. 16‑bit), and the only exception to this rule was introduced by me in the initial ENETC v1 driver for the PMAR1 register, which holds the lower 16 bits of the primary MAC address of an SI. Meanwhile, this exception has been replicated for v4 as well. Since LS1028 (the only SoC with ENETC v1) is not affected by this issue, the current patch fixes the 16‑bit writes to PMAR1 starting with ENETC v4. Fixes: 99100d0d9922 ("net: enetc: add preliminary support for i.MX95 ENETC PF") Signed-off-by: Claudiu Manoil Reviewed-by: Wei Fang Link: https://patch.msgid.link/20260130141035.272471-4-claudiu.manoil@nxp.com Signed-off-by: Jakub Kicinski Signed-off-by: Sasha Levin --- drivers/net/ethernet/freescale/enetc/enetc4_pf.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/net/ethernet/freescale/enetc/enetc4_pf.c b/drivers/net/ethernet/freescale/enetc/enetc4_pf.c index 82c443b28b154d..b270a01f5b7187 100644 --- a/drivers/net/ethernet/freescale/enetc/enetc4_pf.c +++ b/drivers/net/ethernet/freescale/enetc/enetc4_pf.c @@ -49,10 +49,10 @@ static void enetc4_pf_set_si_primary_mac(struct enetc_hw *hw, int si, if (si != 0) { __raw_writel(upper, hw->port + ENETC4_PSIPMAR0(si)); - __raw_writew(lower, hw->port + ENETC4_PSIPMAR1(si)); + __raw_writel(lower, hw->port + ENETC4_PSIPMAR1(si)); } else { __raw_writel(upper, hw->port + ENETC4_PMAR0); - __raw_writew(lower, hw->port + ENETC4_PMAR1); + __raw_writel(lower, hw->port + ENETC4_PMAR1); } } From 5a2b4b0e9c00388d49e200620f1f1614a6dceb49 Mon Sep 17 00:00:00 2001 From: Claudiu Manoil Date: Fri, 30 Jan 2026 16:10:35 +0200 Subject: [PATCH 3120/3902] net: enetc: Convert 16-bit register reads to 32-bit for ENETC v4 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [ Upstream commit c28d765ec5da160d3a48d0928528084cef97bf19 ] It is not recommended to access the 32‑bit registers of this hardware IP using lower‑width accessors (i.e. 16‑bit), and the only exception to this rule was introduced in the initial ENETC v1 driver for the PMAR1 register, which holds the lower 16 bits of the primary MAC address of an SI. Meanwhile, this exception has been replicated in the v4 driver code as well. Since LS1028 (the only SoC with ENETC v1) is not affected by this issue, the current patch converts the 16‑bit reads from PMAR1 starting with ENETC v4. Fixes: 99100d0d9922 ("net: enetc: add preliminary support for i.MX95 ENETC PF") Signed-off-by: Claudiu Manoil Reviewed-by: Wei Fang Link: https://patch.msgid.link/20260130141035.272471-5-claudiu.manoil@nxp.com Signed-off-by: Jakub Kicinski Signed-off-by: Sasha Levin --- .../net/ethernet/freescale/enetc/enetc4_pf.c | 2 +- drivers/net/ethernet/freescale/enetc/enetc_hw.h | 17 ++++++++++++++--- 2 files changed, 15 insertions(+), 4 deletions(-) diff --git a/drivers/net/ethernet/freescale/enetc/enetc4_pf.c b/drivers/net/ethernet/freescale/enetc/enetc4_pf.c index b270a01f5b7187..7dbfbc6fbdcb03 100644 --- a/drivers/net/ethernet/freescale/enetc/enetc4_pf.c +++ b/drivers/net/ethernet/freescale/enetc/enetc4_pf.c @@ -63,7 +63,7 @@ static void enetc4_pf_get_si_primary_mac(struct enetc_hw *hw, int si, u16 lower; upper = __raw_readl(hw->port + ENETC4_PSIPMAR0(si)); - lower = __raw_readw(hw->port + ENETC4_PSIPMAR1(si)); + lower = __raw_readl(hw->port + ENETC4_PSIPMAR1(si)); put_unaligned_le32(upper, addr); put_unaligned_le16(lower, addr + 4); diff --git a/drivers/net/ethernet/freescale/enetc/enetc_hw.h b/drivers/net/ethernet/freescale/enetc/enetc_hw.h index 377c963258147a..d382220ef2f0d0 100644 --- a/drivers/net/ethernet/freescale/enetc/enetc_hw.h +++ b/drivers/net/ethernet/freescale/enetc/enetc_hw.h @@ -707,13 +707,24 @@ struct enetc_cmd_rfse { #define ENETC_RFSE_EN BIT(15) #define ENETC_RFSE_MODE_BD 2 +static inline void enetc_get_primary_mac_addr(struct enetc_hw *hw, u8 *addr) +{ + u32 upper; + u16 lower; + + upper = __raw_readl(hw->reg + ENETC_SIPMAR0); + lower = __raw_readl(hw->reg + ENETC_SIPMAR1); + + put_unaligned_le32(upper, addr); + put_unaligned_le16(lower, addr + 4); +} + static inline void enetc_load_primary_mac_addr(struct enetc_hw *hw, struct net_device *ndev) { - u8 addr[ETH_ALEN] __aligned(4); + u8 addr[ETH_ALEN]; - *(u32 *)addr = __raw_readl(hw->reg + ENETC_SIPMAR0); - *(u16 *)(addr + 4) = __raw_readw(hw->reg + ENETC_SIPMAR1); + enetc_get_primary_mac_addr(hw, addr); eth_hw_addr_set(ndev, addr); } From 9b9f52f052f4953fecd2190ae2dde3aa76d10962 Mon Sep 17 00:00:00 2001 From: Miri Korenblit Date: Thu, 29 Jan 2026 21:27:09 +0200 Subject: [PATCH 3121/3902] wifi: iwlwifi: mld: cancel mlo_scan_start_wk [ Upstream commit 5ff641011ab7fb63ea101251087745d9826e8ef5 ] mlo_scan_start_wk is not canceled on disconnection. In fact, it is not canceled anywhere except in the restart cleanup, where we don't really have to. This can cause an init-after-queue issue: if, for example, the work was queued and then drv_change_interface got executed. This can also cause use-after-free: if the work is executed after the vif is freed. Fixes: 9748ad82a9d9 ("wifi: iwlwifi: defer MLO scan after link activation") Reviewed-by: Johannes Berg Signed-off-by: Miri Korenblit Link: https://patch.msgid.link/20260129212650.a36482a60719.I5bf64a108ca39dacb5ca0dcd8b7258a3ce8db74c@changeid Signed-off-by: Sasha Levin --- drivers/net/wireless/intel/iwlwifi/mld/iface.c | 2 -- drivers/net/wireless/intel/iwlwifi/mld/mac80211.c | 2 ++ 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/net/wireless/intel/iwlwifi/mld/iface.c b/drivers/net/wireless/intel/iwlwifi/mld/iface.c index ed379825a92361..240ce19996b347 100644 --- a/drivers/net/wireless/intel/iwlwifi/mld/iface.c +++ b/drivers/net/wireless/intel/iwlwifi/mld/iface.c @@ -55,8 +55,6 @@ void iwl_mld_cleanup_vif(void *data, u8 *mac, struct ieee80211_vif *vif) ieee80211_iter_keys(mld->hw, vif, iwl_mld_cleanup_keys_iter, NULL); - wiphy_delayed_work_cancel(mld->wiphy, &mld_vif->mlo_scan_start_wk); - CLEANUP_STRUCT(mld_vif); } diff --git a/drivers/net/wireless/intel/iwlwifi/mld/mac80211.c b/drivers/net/wireless/intel/iwlwifi/mld/mac80211.c index 5725104a53bf03..2a7e7417d7d84f 100644 --- a/drivers/net/wireless/intel/iwlwifi/mld/mac80211.c +++ b/drivers/net/wireless/intel/iwlwifi/mld/mac80211.c @@ -1755,6 +1755,8 @@ static int iwl_mld_move_sta_state_down(struct iwl_mld *mld, wiphy_work_cancel(mld->wiphy, &mld_vif->emlsr.unblock_tpt_wk); wiphy_delayed_work_cancel(mld->wiphy, &mld_vif->emlsr.check_tpt_wk); + wiphy_delayed_work_cancel(mld->wiphy, + &mld_vif->mlo_scan_start_wk); iwl_mld_reset_cca_40mhz_workaround(mld, vif); iwl_mld_smps_workaround(mld, vif, true); From 0031f8829c7fb03b2df74452797b201bfce7477d Mon Sep 17 00:00:00 2001 From: Miri Korenblit Date: Thu, 29 Jan 2026 21:27:10 +0200 Subject: [PATCH 3122/3902] wifi: iwlwifi: mvm: pause TCM on fast resume [ Upstream commit fb7f54aa2a99b07945911152c5d3d4a6eb39f797 ] Not pausing it means that we can have the TCM work queued into a non-freezable workqueue, which, in resume, is re-activated before the driver's resume is called. The TCM work might send commands to the FW before we resumed the device, leading to an assert. Closes: https://lore.kernel.org/linux-wireless/aTDoDiD55qlUZ0pn@debian.local/ Tested-by: Chris Bainbridge Fixes: e8bb19c1d590 ("wifi: iwlwifi: support fast resume") Reviewed-by: Johannes Berg Signed-off-by: Miri Korenblit Link: https://patch.msgid.link/20260129212650.05621f3faedb.I44df9cf9183b5143df8078131e0d87c0fd7e1763@changeid Signed-off-by: Sasha Levin --- drivers/net/wireless/intel/iwlwifi/mvm/d3.c | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/drivers/net/wireless/intel/iwlwifi/mvm/d3.c b/drivers/net/wireless/intel/iwlwifi/mvm/d3.c index 07f1a84c274efe..af1a45845999bb 100644 --- a/drivers/net/wireless/intel/iwlwifi/mvm/d3.c +++ b/drivers/net/wireless/intel/iwlwifi/mvm/d3.c @@ -1,6 +1,6 @@ // SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause /* - * Copyright (C) 2012-2014, 2018-2025 Intel Corporation + * Copyright (C) 2012-2014, 2018-2026 Intel Corporation * Copyright (C) 2013-2015 Intel Mobile Communications GmbH * Copyright (C) 2016-2017 Intel Deutschland GmbH */ @@ -3239,6 +3239,8 @@ void iwl_mvm_fast_suspend(struct iwl_mvm *mvm) IWL_DEBUG_WOWLAN(mvm, "Starting fast suspend flow\n"); + iwl_mvm_pause_tcm(mvm, true); + mvm->fast_resume = true; set_bit(IWL_MVM_STATUS_IN_D3, &mvm->status); @@ -3295,6 +3297,8 @@ int iwl_mvm_fast_resume(struct iwl_mvm *mvm) mvm->trans->state = IWL_TRANS_NO_FW; } + iwl_mvm_resume_tcm(mvm); + out: clear_bit(IWL_MVM_STATUS_IN_D3, &mvm->status); mvm->fast_resume = false; From 100f3bf91461285f2c155f5e9e883668c85e6627 Mon Sep 17 00:00:00 2001 From: Melissa Wen Date: Thu, 22 Jan 2026 12:20:29 -0300 Subject: [PATCH 3123/3902] drm/amd/display: fix wrong color value mapping on MCM shaper LUT [ Upstream commit 8f959d37c1f2efec6dac55915ee82302e98101fb ] Some shimmer/colorful points appears when using the steamOS color pipeline for HDR on gaming with DCN32. These points look like black values being wrongly mapped to red/blue/green values. It was caused because the number of hw points in regular LUTs and in a shaper LUT was treated as the same. DCN3+ regular LUTs have 257 bases and implicit deltas (i.e. HW calculates them), but shaper LUT is a special case: it has 256 bases and 256 deltas, as in DCN1-2 regular LUTs, and outputs 14-bit values. Fix that by setting by decreasing in 1 the number of HW points computed in the LUT segmentation so that shaper LUT (i.e. fixpoint == true) keeps the same DCN10 CM logic and regular LUTs go with `hw_points + 1`. CC: Krunoslav Kovac Fixes: 4d5fd3d08ea9 ("drm/amd/display: PQ tail accuracy") Signed-off-by: Melissa Wen Reviewed-by: Alex Hung Signed-off-by: Alex Deucher (cherry picked from commit 5006505b19a2119e71c008044d59f6d753c858b9) Signed-off-by: Sasha Levin --- drivers/gpu/drm/amd/display/dc/dcn30/dcn30_cm_common.c | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/drivers/gpu/drm/amd/display/dc/dcn30/dcn30_cm_common.c b/drivers/gpu/drm/amd/display/dc/dcn30/dcn30_cm_common.c index 0690c346f2c521..a4f14b16564c2f 100644 --- a/drivers/gpu/drm/amd/display/dc/dcn30/dcn30_cm_common.c +++ b/drivers/gpu/drm/amd/display/dc/dcn30/dcn30_cm_common.c @@ -163,6 +163,11 @@ bool cm3_helper_translate_curve_to_hw_format( hw_points += (1 << seg_distr[k]); } + // DCN3+ have 257 pts in lieu of no separate slope registers + // Prior HW had 256 base+slope pairs + // Shaper LUT (i.e. fixpoint == true) is still 256 bases and 256 deltas + hw_points = fixpoint ? (hw_points - 1) : hw_points; + j = 0; for (k = 0; k < (region_end - region_start); k++) { increment = NUMBER_SW_SEGMENTS / (1 << seg_distr[k]); @@ -223,8 +228,6 @@ bool cm3_helper_translate_curve_to_hw_format( corner_points[1].green.slope = dc_fixpt_zero; corner_points[1].blue.slope = dc_fixpt_zero; - // DCN3+ have 257 pts in lieu of no separate slope registers - // Prior HW had 256 base+slope pairs lut_params->hw_points_num = hw_points + 1; k = 0; From 9f42cb8fafd6d2ec312e55def88117b555d330e2 Mon Sep 17 00:00:00 2001 From: Chen Ni Date: Mon, 2 Feb 2026 12:02:28 +0800 Subject: [PATCH 3124/3902] net: ethernet: adi: adin1110: Check return value of devm_gpiod_get_optional() in adin1110_check_spi() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [ Upstream commit 78211543d2e44f84093049b4ef5f5bfa535f4645 ] The devm_gpiod_get_optional() function may return an ERR_PTR in case of genuine GPIO acquisition errors, not just NULL which indicates the legitimate absence of an optional GPIO. Add an IS_ERR() check after the call in adin1110_check_spi(). On error, return the error code to ensure proper failure handling rather than proceeding with invalid pointers. Fixes: 36934cac7aaf ("net: ethernet: adi: adin1110: add reset GPIO") Signed-off-by: Chen Ni Reviewed-by: Nuno Sá Link: https://patch.msgid.link/20260202040228.4129097-1-nichen@iscas.ac.cn Signed-off-by: Jakub Kicinski Signed-off-by: Sasha Levin --- drivers/net/ethernet/adi/adin1110.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/drivers/net/ethernet/adi/adin1110.c b/drivers/net/ethernet/adi/adin1110.c index 30f9d271e59531..71a2397edf2bbf 100644 --- a/drivers/net/ethernet/adi/adin1110.c +++ b/drivers/net/ethernet/adi/adin1110.c @@ -1089,6 +1089,9 @@ static int adin1110_check_spi(struct adin1110_priv *priv) reset_gpio = devm_gpiod_get_optional(&priv->spidev->dev, "reset", GPIOD_OUT_LOW); + if (IS_ERR(reset_gpio)) + return dev_err_probe(&priv->spidev->dev, PTR_ERR(reset_gpio), + "failed to get reset gpio\n"); if (reset_gpio) { /* MISO pin is used for internal configuration, can't have * anyone else disturbing the SDO line. From 589a530ae44d0c80f523fcfd1a15af8087f27d35 Mon Sep 17 00:00:00 2001 From: Eric Dumazet Date: Mon, 2 Feb 2026 20:52:17 +0000 Subject: [PATCH 3125/3902] net: add proper RCU protection to /proc/net/ptype [ Upstream commit f613e8b4afea0cd17c7168e8b00e25bc8d33175d ] Yin Fengwei reported an RCU stall in ptype_seq_show() and provided a patch. Real issue is that ptype_seq_next() and ptype_seq_show() violate RCU rules. ptype_seq_show() runs under rcu_read_lock(), and reads pt->dev to get device name without any barrier. At the same time, concurrent writers can remove a packet_type structure (which is correctly freed after an RCU grace period) and clear pt->dev without an RCU grace period. Define ptype_iter_state to carry a dev pointer along seq_net_private: struct ptype_iter_state { struct seq_net_private p; struct net_device *dev; // added in this patch }; We need to record the device pointer in ptype_get_idx() and ptype_seq_next() so that ptype_seq_show() is safe against concurrent pt->dev changes. We also need to add full RCU protection in ptype_seq_next(). (Missing READ_ONCE() when reading list.next values) Many thanks to Dong Chenchen for providing a repro. Fixes: 1da177e4c3f4 ("Linux-2.6.12-rc2") Fixes: 1d10f8a1f40b ("net-procfs: show net devices bound packet types") Fixes: c353e8983e0d ("net: introduce per netns packet chains") Reported-by: Yin Fengwei Reported-by: Dong Chenchen Closes: https://lore.kernel.org/netdev/CANn89iKRRKPnWjJmb-_3a=sq+9h6DvTQM4DBZHT5ZRGPMzQaiA@mail.gmail.com/T/#m7b80b9fc9b9267f90e0b7aad557595f686f9c50d Signed-off-by: Eric Dumazet Reviewed-by: Willem de Bruijn Tested-by: Yin Fengwei Link: https://patch.msgid.link/20260202205217.2881198-1-edumazet@google.com Signed-off-by: Jakub Kicinski Signed-off-by: Sasha Levin --- net/core/net-procfs.c | 50 +++++++++++++++++++++++++++++-------------- 1 file changed, 34 insertions(+), 16 deletions(-) diff --git a/net/core/net-procfs.c b/net/core/net-procfs.c index 70e0e9a3b650c0..7dbfa6109f0b87 100644 --- a/net/core/net-procfs.c +++ b/net/core/net-procfs.c @@ -170,8 +170,14 @@ static const struct seq_operations softnet_seq_ops = { .show = softnet_seq_show, }; +struct ptype_iter_state { + struct seq_net_private p; + struct net_device *dev; +}; + static void *ptype_get_idx(struct seq_file *seq, loff_t pos) { + struct ptype_iter_state *iter = seq->private; struct list_head *ptype_list = NULL; struct packet_type *pt = NULL; struct net_device *dev; @@ -181,12 +187,16 @@ static void *ptype_get_idx(struct seq_file *seq, loff_t pos) for_each_netdev_rcu(seq_file_net(seq), dev) { ptype_list = &dev->ptype_all; list_for_each_entry_rcu(pt, ptype_list, list) { - if (i == pos) + if (i == pos) { + iter->dev = dev; return pt; + } ++i; } } + iter->dev = NULL; + list_for_each_entry_rcu(pt, &seq_file_net(seq)->ptype_all, list) { if (i == pos) return pt; @@ -218,6 +228,7 @@ static void *ptype_seq_start(struct seq_file *seq, loff_t *pos) static void *ptype_seq_next(struct seq_file *seq, void *v, loff_t *pos) { + struct ptype_iter_state *iter = seq->private; struct net *net = seq_file_net(seq); struct net_device *dev; struct packet_type *pt; @@ -229,19 +240,21 @@ static void *ptype_seq_next(struct seq_file *seq, void *v, loff_t *pos) return ptype_get_idx(seq, 0); pt = v; - nxt = pt->list.next; - if (pt->dev) { - if (nxt != &pt->dev->ptype_all) + nxt = READ_ONCE(pt->list.next); + dev = iter->dev; + if (dev) { + if (nxt != &dev->ptype_all) goto found; - dev = pt->dev; for_each_netdev_continue_rcu(seq_file_net(seq), dev) { - if (!list_empty(&dev->ptype_all)) { - nxt = dev->ptype_all.next; + nxt = READ_ONCE(dev->ptype_all.next); + if (nxt != &dev->ptype_all) { + iter->dev = dev; goto found; } } - nxt = net->ptype_all.next; + iter->dev = NULL; + nxt = READ_ONCE(net->ptype_all.next); goto net_ptype_all; } @@ -252,20 +265,20 @@ static void *ptype_seq_next(struct seq_file *seq, void *v, loff_t *pos) if (nxt == &net->ptype_all) { /* continue with ->ptype_specific if it's not empty */ - nxt = net->ptype_specific.next; + nxt = READ_ONCE(net->ptype_specific.next); if (nxt != &net->ptype_specific) goto found; } hash = 0; - nxt = ptype_base[0].next; + nxt = READ_ONCE(ptype_base[0].next); } else hash = ntohs(pt->type) & PTYPE_HASH_MASK; while (nxt == &ptype_base[hash]) { if (++hash >= PTYPE_HASH_SIZE) return NULL; - nxt = ptype_base[hash].next; + nxt = READ_ONCE(ptype_base[hash].next); } found: return list_entry(nxt, struct packet_type, list); @@ -279,19 +292,24 @@ static void ptype_seq_stop(struct seq_file *seq, void *v) static int ptype_seq_show(struct seq_file *seq, void *v) { + struct ptype_iter_state *iter = seq->private; struct packet_type *pt = v; + struct net_device *dev; - if (v == SEQ_START_TOKEN) + if (v == SEQ_START_TOKEN) { seq_puts(seq, "Type Device Function\n"); - else if ((!pt->af_packet_net || net_eq(pt->af_packet_net, seq_file_net(seq))) && - (!pt->dev || net_eq(dev_net(pt->dev), seq_file_net(seq)))) { + return 0; + } + dev = iter->dev; + if ((!pt->af_packet_net || net_eq(pt->af_packet_net, seq_file_net(seq))) && + (!dev || net_eq(dev_net(dev), seq_file_net(seq)))) { if (pt->type == htons(ETH_P_ALL)) seq_puts(seq, "ALL "); else seq_printf(seq, "%04x", ntohs(pt->type)); seq_printf(seq, " %-8s %ps\n", - pt->dev ? pt->dev->name : "", pt->func); + dev ? dev->name : "", pt->func); } return 0; @@ -315,7 +333,7 @@ static int __net_init dev_proc_net_init(struct net *net) &softnet_seq_ops)) goto out_dev; if (!proc_create_net("ptype", 0444, net->proc_net, &ptype_seq_ops, - sizeof(struct seq_net_private))) + sizeof(struct ptype_iter_state))) goto out_softnet; if (wext_proc_init(net)) From 2e5edb69e5d0e23ef248c56fc977039268c77a7b Mon Sep 17 00:00:00 2001 From: Paolo Abeni Date: Mon, 2 Feb 2026 12:43:14 +0100 Subject: [PATCH 3126/3902] net: gro: fix outer network offset [ Upstream commit 5c2c3c38be396257a6a2e55bd601a12bb9781507 ] The udp GRO complete stage assumes that all the packets inserted the RX have the `encapsulation` flag zeroed. Such assumption is not true, as a few H/W NICs can set such flag when H/W offloading the checksum for an UDP encapsulated traffic, the tun driver can inject GSO packets with UDP encapsulation and the problematic layout can also be created via a veth based setup. Due to the above, in the problematic scenarios, udp4_gro_complete() uses the wrong network offset (inner instead of outer) to compute the outer UDP header pseudo checksum, leading to csum validation errors later on in packet processing. Address the issue always clearing the encapsulation flag at GRO completion time. Such flag will be set again as needed for encapsulated packets by udp_gro_complete(). Fixes: 5ef31ea5d053 ("net: gro: fix udp bad offset in socket lookup by adding {inner_}network_offset to napi_gro_cb") Reviewed-by: Willem de Bruijn Signed-off-by: Paolo Abeni Reviewed-by: Eric Dumazet Link: https://patch.msgid.link/562638dbebb3b15424220e26a180274b387e2a88.1770032084.git.pabeni@redhat.com Signed-off-by: Jakub Kicinski Signed-off-by: Sasha Levin --- net/core/gro.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/net/core/gro.c b/net/core/gro.c index 76f9c371242210..482fa7d7f5981a 100644 --- a/net/core/gro.c +++ b/net/core/gro.c @@ -265,6 +265,8 @@ static void gro_complete(struct gro_node *gro, struct sk_buff *skb) goto out; } + /* NICs can feed encapsulated packets into GRO */ + skb->encapsulation = 0; rcu_read_lock(); list_for_each_entry_rcu(ptype, head, list) { if (ptype->type != type || !ptype->callbacks.gro_complete) From 184a84f03b5e61acc2700790924836a011740f75 Mon Sep 17 00:00:00 2001 From: Jacob Keller Date: Mon, 2 Feb 2026 16:16:39 -0800 Subject: [PATCH 3127/3902] drm/mgag200: fix mgag200_bmc_stop_scanout() [ Upstream commit 0e0c8f4d16de92520623aa1ea485cadbf64e6929 ] The mgag200_bmc_stop_scanout() function is called by the .atomic_disable() handler for the MGA G200 VGA BMC encoder. This function performs a few register writes to inform the BMC of an upcoming mode change, and then polls to wait until the BMC actually stops. The polling is implemented using a busy loop with udelay() and an iteration timeout of 300, resulting in the function blocking for 300 milliseconds. The function gets called ultimately by the output_poll_execute work thread for the DRM output change polling thread of the mgag200 driver: kworker/0:0-mm_ 3528 [000] 4555.315364: ffffffffaa0e25b3 delay_halt.part.0+0x33 ffffffffc03f6188 mgag200_bmc_stop_scanout+0x178 ffffffffc087ae7a disable_outputs+0x12a ffffffffc087c12a drm_atomic_helper_commit_tail+0x1a ffffffffc03fa7b6 mgag200_mode_config_helper_atomic_commit_tail+0x26 ffffffffc087c9c1 commit_tail+0x91 ffffffffc087d51b drm_atomic_helper_commit+0x11b ffffffffc0509694 drm_atomic_commit+0xa4 ffffffffc05105e8 drm_client_modeset_commit_atomic+0x1e8 ffffffffc0510ce6 drm_client_modeset_commit_locked+0x56 ffffffffc0510e24 drm_client_modeset_commit+0x24 ffffffffc088a743 __drm_fb_helper_restore_fbdev_mode_unlocked+0x93 ffffffffc088a683 drm_fb_helper_hotplug_event+0xe3 ffffffffc050f8aa drm_client_dev_hotplug+0x9a ffffffffc088555a output_poll_execute+0x29a ffffffffa9b35924 process_one_work+0x194 ffffffffa9b364ee worker_thread+0x2fe ffffffffa9b3ecad kthread+0xdd ffffffffa9a08549 ret_from_fork+0x29 On a server running ptp4l with the mgag200 driver loaded, we found that ptp4l would sometimes get blocked from execution because of this busy waiting loop. Every so often, approximately once every 20 minutes -- though with large variance -- the output_poll_execute() thread would detect some sort of change that required performing a hotplug event which results in attempting to stop the BMC scanout, resulting in a 300msec delay on one CPU. On this system, ptp4l was pinned to a single CPU. When the output_poll_execute() thread ran on that CPU, it blocked ptp4l from executing for its 300 millisecond duration. This resulted in PTP service disruptions such as failure to send a SYNC message on time, failure to handle ANNOUNCE messages on time, and clock check warnings from the application. All of this despite the application being configured with FIFO_RT and a higher priority than the background workqueue tasks. (However, note that the kernel did not use CONFIG_PREEMPT...) It is unclear if the event is due to a faulty VGA connection, another bug, or actual events causing a change in the connection. At least on the system under test it is not a one-time event and consistently causes disruption to the time sensitive applications. The function has some helpful comments explaining what steps it is attempting to take. In particular, step 3a and 3b are explained as such: 3a - The third step is to verify if there is an active scan. We are waiting on a 0 on remhsyncsts (. 3b - This step occurs only if the remove is actually scanning. We are waiting for the end of the frame which is a 1 on remvsyncsts (). The actual steps 3a and 3b are implemented as while loops with a non-sleeping udelay(). The first step iterates while the tmp value at position 0 is *not* set. That is, it keeps iterating as long as the bit is zero. If the bit is already 0 (because there is no active scan), it will iterate the entire 300 attempts which wastes 300 milliseconds in total. This is opposite of what the description claims. The step 3b logic only executes if we do not iterate over the entire 300 attempts in the first loop. If it does trigger, it is trying to check and wait for a 1 on the remvsyncsts. However, again the condition is actually inverted and it will loop as long as the bit is 1, stopping once it hits zero (rather than the explained attempt to wait until we see a 1). Worse, both loops are implemented using non-sleeping waits which spin instead of allowing the scheduler to run other processes. If the kernel is not configured to allow arbitrary preemption, it will waste valuable CPU time doing nothing. There does not appear to be any documentation for the BMC register interface, beyond what is in the comments here. It seems more probable that the comment here is correct and the implementation accidentally got inverted from the intended logic. Reading through other DRM driver implementations, it does not appear that the .atomic_enable or .atomic_disable handlers need to delay instead of sleep. For example, the ast_astdp_encoder_helper_atomic_disable() function calls ast_dp_set_phy_sleep() which uses msleep(). The "atomic" in the name is referring to the atomic modesetting support, which is the support to enable atomic configuration from userspace, and not to the "atomic context" of the kernel. There is no reason to use udelay() here if a sleep would be sufficient. Replace the while loops with a read_poll_timeout() based implementation that will sleep between iterations, and which stops polling once the condition is met (instead of looping as long as the condition is met). This aligns with the commented behavior and avoids blocking on the CPU while doing nothing. Note the RREG_DAC is implemented using a statement expression to allow working properly with the read_poll_timeout family of functions. The other RREG_ macros ought to be cleaned up to have better semantics, and several places in the mgag200 driver could make use of RREG_DAC or similar RREG_* macros should likely be cleaned up for better semantics as well, but that task has been left as a future cleanup for a non-bugfix. Fixes: 414c45310625 ("mgag200: initial g200se driver (v2)") Suggested-by: Thomas Zimmermann Signed-off-by: Jacob Keller Reviewed-by: Thomas Zimmermann Reviewed-by: Jocelyn Falempe Signed-off-by: Thomas Zimmermann Link: https://patch.msgid.link/20260202-jk-mgag200-fix-bad-udelay-v2-1-ce1e9665987d@intel.com Signed-off-by: Sasha Levin --- drivers/gpu/drm/mgag200/mgag200_bmc.c | 31 +++++++++++---------------- drivers/gpu/drm/mgag200/mgag200_drv.h | 6 ++++++ 2 files changed, 18 insertions(+), 19 deletions(-) diff --git a/drivers/gpu/drm/mgag200/mgag200_bmc.c b/drivers/gpu/drm/mgag200/mgag200_bmc.c index a689c71ff1653d..bbdeb791c5b38b 100644 --- a/drivers/gpu/drm/mgag200/mgag200_bmc.c +++ b/drivers/gpu/drm/mgag200/mgag200_bmc.c @@ -1,6 +1,7 @@ // SPDX-License-Identifier: GPL-2.0-only #include +#include #include #include @@ -12,7 +13,7 @@ void mgag200_bmc_stop_scanout(struct mga_device *mdev) { u8 tmp; - int iter_max; + int ret; /* * 1 - The first step is to inform the BMC of an upcoming mode @@ -42,30 +43,22 @@ void mgag200_bmc_stop_scanout(struct mga_device *mdev) /* * 3a- The third step is to verify if there is an active scan. - * We are waiting for a 0 on remhsyncsts ). + * We are waiting for a 0 on remhsyncsts (). */ - iter_max = 300; - while (!(tmp & 0x1) && iter_max) { - WREG8(DAC_INDEX, MGA1064_SPAREREG); - tmp = RREG8(DAC_DATA); - udelay(1000); - iter_max--; - } + ret = read_poll_timeout(RREG_DAC, tmp, !(tmp & 0x1), + 1000, 300000, false, + MGA1064_SPAREREG); + if (ret == -ETIMEDOUT) + return; /* - * 3b- This step occurs only if the remove is actually + * 3b- This step occurs only if the remote BMC is actually * scanning. We are waiting for the end of the frame which is * a 1 on remvsyncsts (XSPAREREG<1>) */ - if (iter_max) { - iter_max = 300; - while ((tmp & 0x2) && iter_max) { - WREG8(DAC_INDEX, MGA1064_SPAREREG); - tmp = RREG8(DAC_DATA); - udelay(1000); - iter_max--; - } - } + (void)read_poll_timeout(RREG_DAC, tmp, (tmp & 0x2), + 1000, 300000, false, + MGA1064_SPAREREG); } void mgag200_bmc_start_scanout(struct mga_device *mdev) diff --git a/drivers/gpu/drm/mgag200/mgag200_drv.h b/drivers/gpu/drm/mgag200/mgag200_drv.h index f4bf40cd7c88a8..a875c4bf8cbe4c 100644 --- a/drivers/gpu/drm/mgag200/mgag200_drv.h +++ b/drivers/gpu/drm/mgag200/mgag200_drv.h @@ -111,6 +111,12 @@ #define DAC_INDEX 0x3c00 #define DAC_DATA 0x3c0a +#define RREG_DAC(reg) \ + ({ \ + WREG8(DAC_INDEX, reg); \ + RREG8(DAC_DATA); \ + }) \ + #define WREG_DAC(reg, v) \ do { \ WREG8(DAC_INDEX, reg); \ From 7c5db0957a23092e11a145542b37f61984dbacf5 Mon Sep 17 00:00:00 2001 From: Shuicheng Lin Date: Fri, 30 Jan 2026 04:39:08 +0000 Subject: [PATCH 3128/3902] drm/xe/query: Fix topology query pointer advance MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [ Upstream commit 7ee9b3e091c63da71e15c72003f1f07e467f5158 ] The topology query helper advanced the user pointer by the size of the pointer, not the size of the structure. This can misalign the output blob and corrupt the following mask. Fix the increment to use sizeof(*topo). There is no issue currently, as sizeof(*topo) happens to be equal to sizeof(topo) on 64-bit systems (both evaluate to 8 bytes). Fixes: dd08ebf6c352 ("drm/xe: Introduce a new DRM driver for Intel GPUs") Signed-off-by: Shuicheng Lin Reviewed-by: Matt Roper Link: https://patch.msgid.link/20260130043907.465128-2-shuicheng.lin@intel.com Signed-off-by: Matt Roper (cherry picked from commit c2a6859138e7f73ad904be17dd7d1da6cc7f06b3) Signed-off-by: Thomas Hellström Signed-off-by: Sasha Levin --- drivers/gpu/drm/xe/xe_query.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/gpu/drm/xe/xe_query.c b/drivers/gpu/drm/xe/xe_query.c index 2e9ff33ed2fe2a..856089f64c341d 100644 --- a/drivers/gpu/drm/xe/xe_query.c +++ b/drivers/gpu/drm/xe/xe_query.c @@ -491,7 +491,7 @@ static int copy_mask(void __user **ptr, if (copy_to_user(*ptr, topo, sizeof(*topo))) return -EFAULT; - *ptr += sizeof(topo); + *ptr += sizeof(*topo); if (copy_to_user(*ptr, mask, mask_size)) return -EFAULT; From 8abb71d4a1dce81032fb784189ed544e9366dc17 Mon Sep 17 00:00:00 2001 From: Karthik Poosa Date: Fri, 23 Jan 2026 23:02:38 +0530 Subject: [PATCH 3129/3902] drm/xe/pm: Disable D3Cold for BMG only on specific platforms MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [ Upstream commit bb36170d959fad7f663f91eb9c32a84dd86bef2b ] Restrict D3Cold disablement for BMG to unsupported NUC platforms, instead of disabling it on all platforms. Signed-off-by: Karthik Poosa Fixes: 3e331a6715ee ("drm/xe/pm: Temporarily disable D3Cold on BMG") Link: https://patch.msgid.link/20260123173238.1642383-1-karthik.poosa@intel.com Reviewed-by: Rodrigo Vivi Signed-off-by: Rodrigo Vivi (cherry picked from commit 39125eaf8863ab09d70c4b493f58639b08d5a897) Signed-off-by: Thomas Hellström Signed-off-by: Sasha Levin --- drivers/gpu/drm/xe/xe_pm.c | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/drivers/gpu/drm/xe/xe_pm.c b/drivers/gpu/drm/xe/xe_pm.c index a58bf004aee734..a74e800846ffac 100644 --- a/drivers/gpu/drm/xe/xe_pm.c +++ b/drivers/gpu/drm/xe/xe_pm.c @@ -8,6 +8,7 @@ #include #include #include +#include #include #include @@ -300,9 +301,15 @@ ALLOW_ERROR_INJECTION(xe_pm_init_early, ERRNO); /* See xe_pci_probe() */ static u32 vram_threshold_value(struct xe_device *xe) { - /* FIXME: D3Cold temporarily disabled by default on BMG */ - if (xe->info.platform == XE_BATTLEMAGE) - return 0; + if (xe->info.platform == XE_BATTLEMAGE) { + const char *product_name; + + product_name = dmi_get_system_info(DMI_PRODUCT_NAME); + if (product_name && strstr(product_name, "NUC13RNG")) { + drm_warn(&xe->drm, "BMG + D3Cold not supported on this platform\n"); + return 0; + } + } return DEFAULT_VRAM_THRESHOLD; } From 32ddd09d1103e7bc03d464c604a2e31dc47b9c30 Mon Sep 17 00:00:00 2001 From: Arnd Bergmann Date: Tue, 3 Feb 2026 17:34:36 +0100 Subject: [PATCH 3130/3902] hwmon: (occ) Mark occ_init_attribute() as __printf [ Upstream commit 831a2b27914cc880130ffe8fb8d1e65a5324d07f ] This is a printf-style function, which gcc -Werror=suggest-attribute=format correctly points out: drivers/hwmon/occ/common.c: In function 'occ_init_attribute': drivers/hwmon/occ/common.c:761:9: error: function 'occ_init_attribute' might be a candidate for 'gnu_printf' format attribute [-Werror=suggest-attribute=format] Add the attribute to avoid this warning and ensure any incorrect format strings are detected here. Fixes: 744c2fe950e9 ("hwmon: (occ) Rework attribute registration for stack usage") Signed-off-by: Arnd Bergmann Link: https://lore.kernel.org/r/20260203163440.2674340-1-arnd@kernel.org Signed-off-by: Guenter Roeck Signed-off-by: Sasha Levin --- drivers/hwmon/occ/common.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/hwmon/occ/common.c b/drivers/hwmon/occ/common.c index b3694a4209b975..89928d38831b61 100644 --- a/drivers/hwmon/occ/common.c +++ b/drivers/hwmon/occ/common.c @@ -749,6 +749,7 @@ static ssize_t occ_show_extended(struct device *dev, * are dynamically allocated, we cannot use the existing kernel macros which * stringify the name argument. */ +__printf(7, 8) static void occ_init_attribute(struct occ_attribute *attr, int mode, ssize_t (*show)(struct device *dev, struct device_attribute *attr, char *buf), ssize_t (*store)(struct device *dev, struct device_attribute *attr, From 8b68a45f9722f2babe9e7bad00aa74638addf081 Mon Sep 17 00:00:00 2001 From: Andrew Fasano Date: Wed, 4 Feb 2026 17:46:58 +0100 Subject: [PATCH 3131/3902] netfilter: nf_tables: fix inverted genmask check in nft_map_catchall_activate() [ Upstream commit f41c5d151078c5348271ffaf8e7410d96f2d82f8 ] nft_map_catchall_activate() has an inverted element activity check compared to its non-catchall counterpart nft_mapelem_activate() and compared to what is logically required. nft_map_catchall_activate() is called from the abort path to re-activate catchall map elements that were deactivated during a failed transaction. It should skip elements that are already active (they don't need re-activation) and process elements that are inactive (they need to be restored). Instead, the current code does the opposite: it skips inactive elements and processes active ones. Compare the non-catchall activate callback, which is correct: nft_mapelem_activate(): if (nft_set_elem_active(ext, iter->genmask)) return 0; /* skip active, process inactive */ With the buggy catchall version: nft_map_catchall_activate(): if (!nft_set_elem_active(ext, genmask)) continue; /* skip inactive, process active */ The consequence is that when a DELSET operation is aborted, nft_setelem_data_activate() is never called for the catchall element. For NFT_GOTO verdict elements, this means nft_data_hold() is never called to restore the chain->use reference count. Each abort cycle permanently decrements chain->use. Once chain->use reaches zero, DELCHAIN succeeds and frees the chain while catchall verdict elements still reference it, resulting in a use-after-free. This is exploitable for local privilege escalation from an unprivileged user via user namespaces + nftables on distributions that enable CONFIG_USER_NS and CONFIG_NF_TABLES. Fix by removing the negation so the check matches nft_mapelem_activate(): skip active elements, process inactive ones. Fixes: 628bd3e49cba ("netfilter: nf_tables: drop map element references from preparation phase") Signed-off-by: Andrew Fasano Signed-off-by: Florian Westphal Signed-off-by: Sasha Levin --- net/netfilter/nf_tables_api.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/net/netfilter/nf_tables_api.c b/net/netfilter/nf_tables_api.c index 3cbf2573b9e908..6059a299004d46 100644 --- a/net/netfilter/nf_tables_api.c +++ b/net/netfilter/nf_tables_api.c @@ -5917,7 +5917,7 @@ static void nft_map_catchall_activate(const struct nft_ctx *ctx, list_for_each_entry(catchall, &set->catchall_list, list) { ext = nft_set_elem_ext(set, catchall->elem); - if (!nft_set_elem_active(ext, genmask)) + if (nft_set_elem_active(ext, genmask)) continue; nft_clear(ctx->net, ext); From 3c58f6121863c1e622f1bb6b7b77cedd6f820eee Mon Sep 17 00:00:00 2001 From: Daniele Ceraolo Spurio Date: Thu, 29 Jan 2026 10:25:48 -0800 Subject: [PATCH 3132/3902] drm/xe/guc: Fix CFI violation in debugfs access. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [ Upstream commit 4cb1b327135dddf3d0ec2544ea36ed05ba2252bc ] xe_guc_print_info is void-returning, but the function pointer it is assigned to expects an int-returning function, leading to the following CFI error: [ 206.873690] CFI failure at guc_debugfs_show+0xa1/0xf0 [xe] (target: xe_guc_print_info+0x0/0x370 [xe]; expected type: 0xbe3bc66a) Fix this by updating xe_guc_print_info to return an integer. Fixes: e15826bb3c2c ("drm/xe/guc: Refactor GuC debugfs initialization") Signed-off-by: Daniele Ceraolo Spurio Cc: Michal Wajdeczko Cc: George D Sworo Reviewed-by: Michal Wajdeczko Link: https://patch.msgid.link/20260129182547.32899-2-daniele.ceraolospurio@intel.com (cherry picked from commit dd8ea2f2ab71b98887fdc426b0651dbb1d1ea760) Signed-off-by: Thomas Hellström Signed-off-by: Sasha Levin --- drivers/gpu/drm/xe/xe_guc.c | 6 ++++-- drivers/gpu/drm/xe/xe_guc.h | 2 +- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/drivers/gpu/drm/xe/xe_guc.c b/drivers/gpu/drm/xe/xe_guc.c index 00789844ea4d03..ae0c88da422b2b 100644 --- a/drivers/gpu/drm/xe/xe_guc.c +++ b/drivers/gpu/drm/xe/xe_guc.c @@ -1632,7 +1632,7 @@ int xe_guc_start(struct xe_guc *guc) return xe_guc_submit_start(guc); } -void xe_guc_print_info(struct xe_guc *guc, struct drm_printer *p) +int xe_guc_print_info(struct xe_guc *guc, struct drm_printer *p) { struct xe_gt *gt = guc_to_gt(guc); unsigned int fw_ref; @@ -1644,7 +1644,7 @@ void xe_guc_print_info(struct xe_guc *guc, struct drm_printer *p) if (!IS_SRIOV_VF(gt_to_xe(gt))) { fw_ref = xe_force_wake_get(gt_to_fw(gt), XE_FW_GT); if (!fw_ref) - return; + return -EIO; status = xe_mmio_read32(>->mmio, GUC_STATUS); @@ -1672,6 +1672,8 @@ void xe_guc_print_info(struct xe_guc *guc, struct drm_printer *p) drm_puts(p, "\n"); xe_guc_submit_print(guc, p); + + return 0; } /** diff --git a/drivers/gpu/drm/xe/xe_guc.h b/drivers/gpu/drm/xe/xe_guc.h index 1cca05967e621c..3b858933749bd2 100644 --- a/drivers/gpu/drm/xe/xe_guc.h +++ b/drivers/gpu/drm/xe/xe_guc.h @@ -45,7 +45,7 @@ int xe_guc_self_cfg32(struct xe_guc *guc, u16 key, u32 val); int xe_guc_self_cfg64(struct xe_guc *guc, u16 key, u64 val); void xe_guc_irq_handler(struct xe_guc *guc, const u16 iir); void xe_guc_sanitize(struct xe_guc *guc); -void xe_guc_print_info(struct xe_guc *guc, struct drm_printer *p); +int xe_guc_print_info(struct xe_guc *guc, struct drm_printer *p); int xe_guc_reset_prepare(struct xe_guc *guc); void xe_guc_reset_wait(struct xe_guc *guc); void xe_guc_stop_prepare(struct xe_guc *guc); From f3ed399e9aa6f36e92d2d0fe88b387915e9705fe Mon Sep 17 00:00:00 2001 From: Keith Busch Date: Wed, 4 Feb 2026 06:29:11 -0800 Subject: [PATCH 3133/3902] nvme-pci: handle changing device dma map requirements [ Upstream commit 071be3b0b6575d45be9df9c5b612f5882bfc5e88 ] The initial state of dma_needs_unmap may be false, but change to true while mapping the data iterator. Enabling swiotlb is one such case that can change the result. The nvme driver needs to save the mapped dma vectors to be unmapped later, so allocate as needed during iteration rather than assume it was always allocated at the beginning. This fixes a NULL dereference from accessing an uninitialized dma_vecs when the device dma unmapping requirements change mid-iteration. Fixes: b8b7570a7ec8 ("nvme-pci: fix dma unmapping when using PRPs and not using the IOVA mapping") Link: https://lore.kernel.org/linux-nvme/20260202125738.1194899-1-pradeep.pragallapati@oss.qualcomm.com/ Reported-by: Pradeep P V K Reviewed-by: Christoph Hellwig Signed-off-by: Keith Busch Signed-off-by: Sasha Levin --- drivers/nvme/host/pci.c | 45 +++++++++++++++++++++++++++-------------- 1 file changed, 30 insertions(+), 15 deletions(-) diff --git a/drivers/nvme/host/pci.c b/drivers/nvme/host/pci.c index 28f638413e122f..391c854428d3e9 100644 --- a/drivers/nvme/host/pci.c +++ b/drivers/nvme/host/pci.c @@ -771,6 +771,32 @@ static void nvme_unmap_data(struct request *req) nvme_free_descriptors(req); } +static bool nvme_pci_prp_save_mapping(struct request *req, + struct device *dma_dev, + struct blk_dma_iter *iter) +{ + struct nvme_iod *iod = blk_mq_rq_to_pdu(req); + + if (dma_use_iova(&iod->dma_state) || !dma_need_unmap(dma_dev)) + return true; + + if (!iod->nr_dma_vecs) { + struct nvme_queue *nvmeq = req->mq_hctx->driver_data; + + iod->dma_vecs = mempool_alloc(nvmeq->dev->dmavec_mempool, + GFP_ATOMIC); + if (!iod->dma_vecs) { + iter->status = BLK_STS_RESOURCE; + return false; + } + } + + iod->dma_vecs[iod->nr_dma_vecs].addr = iter->addr; + iod->dma_vecs[iod->nr_dma_vecs].len = iter->len; + iod->nr_dma_vecs++; + return true; +} + static bool nvme_pci_prp_iter_next(struct request *req, struct device *dma_dev, struct blk_dma_iter *iter) { @@ -780,12 +806,7 @@ static bool nvme_pci_prp_iter_next(struct request *req, struct device *dma_dev, return true; if (!blk_rq_dma_map_iter_next(req, dma_dev, &iod->dma_state, iter)) return false; - if (!dma_use_iova(&iod->dma_state) && dma_need_unmap(dma_dev)) { - iod->dma_vecs[iod->nr_dma_vecs].addr = iter->addr; - iod->dma_vecs[iod->nr_dma_vecs].len = iter->len; - iod->nr_dma_vecs++; - } - return true; + return nvme_pci_prp_save_mapping(req, dma_dev, iter); } static blk_status_t nvme_pci_setup_data_prp(struct request *req, @@ -798,15 +819,8 @@ static blk_status_t nvme_pci_setup_data_prp(struct request *req, unsigned int prp_len, i; __le64 *prp_list; - if (!dma_use_iova(&iod->dma_state) && dma_need_unmap(nvmeq->dev->dev)) { - iod->dma_vecs = mempool_alloc(nvmeq->dev->dmavec_mempool, - GFP_ATOMIC); - if (!iod->dma_vecs) - return BLK_STS_RESOURCE; - iod->dma_vecs[0].addr = iter->addr; - iod->dma_vecs[0].len = iter->len; - iod->nr_dma_vecs = 1; - } + if (!nvme_pci_prp_save_mapping(req, nvmeq->dev->dev, iter)) + return iter->status; /* * PRP1 always points to the start of the DMA transfers. @@ -1148,6 +1162,7 @@ static blk_status_t nvme_prep_rq(struct request *req) iod->nr_descriptors = 0; iod->total_len = 0; iod->meta_total_len = 0; + iod->nr_dma_vecs = 0; ret = nvme_setup_cmd(req->q->queuedata, req); if (ret) From b8ad2d53f706aeea833d23d45c0758398fede580 Mon Sep 17 00:00:00 2001 From: Shigeru Yoshida Date: Wed, 4 Feb 2026 18:58:37 +0900 Subject: [PATCH 3134/3902] ipv6: Fix ECMP sibling count mismatch when clearing RTF_ADDRCONF [ Upstream commit bbf4a17ad9ffc4e3d7ec13d73ecd59dea149ed25 ] syzbot reported a kernel BUG in fib6_add_rt2node() when adding an IPv6 route. [0] Commit f72514b3c569 ("ipv6: clear RA flags when adding a static route") introduced logic to clear RTF_ADDRCONF from existing routes when a static route with the same nexthop is added. However, this causes a problem when the existing route has a gateway. When RTF_ADDRCONF is cleared from a route that has a gateway, that route becomes eligible for ECMP, i.e. rt6_qualify_for_ecmp() returns true. The issue is that this route was never added to the fib6_siblings list. This leads to a mismatch between the following counts: - The sibling count computed by iterating fib6_next chain, which includes the newly ECMP-eligible route - The actual siblings in fib6_siblings list, which does not include that route When a subsequent ECMP route is added, fib6_add_rt2node() hits BUG_ON(sibling->fib6_nsiblings != rt->fib6_nsiblings) because the counts don't match. Fix this by only clearing RTF_ADDRCONF when the existing route does not have a gateway. Routes without a gateway cannot qualify for ECMP anyway (rt6_qualify_for_ecmp() requires fib_nh_gw_family), so clearing RTF_ADDRCONF on them is safe and matches the original intent of the commit. [0]: kernel BUG at net/ipv6/ip6_fib.c:1217! Oops: invalid opcode: 0000 [#1] SMP KASAN PTI CPU: 0 UID: 0 PID: 6010 Comm: syz.0.17 Not tainted syzkaller #0 PREEMPT(full) Hardware name: Google Google Compute Engine/Google Compute Engine, BIOS Google 10/25/2025 RIP: 0010:fib6_add_rt2node+0x3433/0x3470 net/ipv6/ip6_fib.c:1217 [...] Call Trace: fib6_add+0x8da/0x18a0 net/ipv6/ip6_fib.c:1532 __ip6_ins_rt net/ipv6/route.c:1351 [inline] ip6_route_add+0xde/0x1b0 net/ipv6/route.c:3946 ipv6_route_ioctl+0x35c/0x480 net/ipv6/route.c:4571 inet6_ioctl+0x219/0x280 net/ipv6/af_inet6.c:577 sock_do_ioctl+0xdc/0x300 net/socket.c:1245 sock_ioctl+0x576/0x790 net/socket.c:1366 vfs_ioctl fs/ioctl.c:51 [inline] __do_sys_ioctl fs/ioctl.c:597 [inline] __se_sys_ioctl+0xfc/0x170 fs/ioctl.c:583 do_syscall_x64 arch/x86/entry/syscall_64.c:63 [inline] do_syscall_64+0xfa/0xf80 arch/x86/entry/syscall_64.c:94 entry_SYSCALL_64_after_hwframe+0x77/0x7f Fixes: f72514b3c569 ("ipv6: clear RA flags when adding a static route") Reported-by: syzbot+cb809def1baaac68ab92@syzkaller.appspotmail.com Closes: https://syzkaller.appspot.com/bug?extid=cb809def1baaac68ab92 Tested-by: syzbot+cb809def1baaac68ab92@syzkaller.appspotmail.com Signed-off-by: Shigeru Yoshida Reviewed-by: Fernando Fernandez Mancera Link: https://patch.msgid.link/20260204095837.1285552-1-syoshida@redhat.com Signed-off-by: Jakub Kicinski Signed-off-by: Sasha Levin --- net/ipv6/ip6_fib.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/net/ipv6/ip6_fib.c b/net/ipv6/ip6_fib.c index 2111af022d946d..c6439e30e892af 100644 --- a/net/ipv6/ip6_fib.c +++ b/net/ipv6/ip6_fib.c @@ -1138,7 +1138,8 @@ static int fib6_add_rt2node(struct fib6_node *fn, struct fib6_info *rt, fib6_set_expires(iter, rt->expires); fib6_add_gc_list(iter); } - if (!(rt->fib6_flags & (RTF_ADDRCONF | RTF_PREFIX_RT))) { + if (!(rt->fib6_flags & (RTF_ADDRCONF | RTF_PREFIX_RT)) && + !iter->fib6_nh->fib_nh_gw_family) { iter->fib6_flags &= ~RTF_ADDRCONF; iter->fib6_flags &= ~RTF_PREFIX_RT; } From abd66845227c5f50a0eefbb21a02b6c49feae27d Mon Sep 17 00:00:00 2001 From: Richard Fitzgerald Date: Thu, 20 Nov 2025 13:06:39 +0000 Subject: [PATCH 3135/3902] firmware: cs_dsp: Factor out common debugfs string read [ Upstream commit 78cfd833bc04c0398ca4cfc64704350aebe4d4c2 ] cs_dsp_debugfs_wmfw_read() and cs_dsp_debugfs_bin_read() were identical except for which struct member they printed. Move all this duplicated code into a common function cs_dsp_debugfs_string_read(). The check for dsp->booted has been removed because this is redundant. The two strings are set when the DSP is booted and cleared when the DSP is powered-down. Access to the string char * must be protected by the pwr_lock mutex. The string is passed into cs_dsp_debugfs_string_read() as a pointer to the char * so that the mutex lock can also be factored out into cs_dsp_debugfs_string_read(). wmfw_file_name and bin_file_name members of struct cs_dsp have been changed to const char *. It makes for a better API to pass a const pointer into cs_dsp_debugfs_string_read(). Signed-off-by: Richard Fitzgerald Link: https://patch.msgid.link/20251120130640.1169780-2-rf@opensource.cirrus.com Signed-off-by: Mark Brown Stable-dep-of: 10db9f6899dd ("firmware: cs_dsp: rate-limit log messages in KUnit builds") Signed-off-by: Sasha Levin --- drivers/firmware/cirrus/cs_dsp.c | 45 ++++++++++++-------------- include/linux/firmware/cirrus/cs_dsp.h | 4 +-- 2 files changed, 23 insertions(+), 26 deletions(-) diff --git a/drivers/firmware/cirrus/cs_dsp.c b/drivers/firmware/cirrus/cs_dsp.c index f51047d8ea64f5..58e41751dbc19b 100644 --- a/drivers/firmware/cirrus/cs_dsp.c +++ b/drivers/firmware/cirrus/cs_dsp.c @@ -9,6 +9,7 @@ * Cirrus Logic International Semiconductor Ltd. */ +#include #include #include #include @@ -410,24 +411,30 @@ static void cs_dsp_debugfs_clear(struct cs_dsp *dsp) dsp->bin_file_name = NULL; } +static ssize_t cs_dsp_debugfs_string_read(struct cs_dsp *dsp, + char __user *user_buf, + size_t count, loff_t *ppos, + const char **pstr) +{ + const char *str; + + scoped_guard(mutex, &dsp->pwr_lock) { + str = *pstr; + if (!str) + return 0; + + return simple_read_from_buffer(user_buf, count, ppos, str, strlen(str)); + } +} + static ssize_t cs_dsp_debugfs_wmfw_read(struct file *file, char __user *user_buf, size_t count, loff_t *ppos) { struct cs_dsp *dsp = file->private_data; - ssize_t ret; - mutex_lock(&dsp->pwr_lock); - - if (!dsp->wmfw_file_name || !dsp->booted) - ret = 0; - else - ret = simple_read_from_buffer(user_buf, count, ppos, - dsp->wmfw_file_name, - strlen(dsp->wmfw_file_name)); - - mutex_unlock(&dsp->pwr_lock); - return ret; + return cs_dsp_debugfs_string_read(dsp, user_buf, count, ppos, + &dsp->wmfw_file_name); } static ssize_t cs_dsp_debugfs_bin_read(struct file *file, @@ -435,19 +442,9 @@ static ssize_t cs_dsp_debugfs_bin_read(struct file *file, size_t count, loff_t *ppos) { struct cs_dsp *dsp = file->private_data; - ssize_t ret; - - mutex_lock(&dsp->pwr_lock); - if (!dsp->bin_file_name || !dsp->booted) - ret = 0; - else - ret = simple_read_from_buffer(user_buf, count, ppos, - dsp->bin_file_name, - strlen(dsp->bin_file_name)); - - mutex_unlock(&dsp->pwr_lock); - return ret; + return cs_dsp_debugfs_string_read(dsp, user_buf, count, ppos, + &dsp->bin_file_name); } static const struct { diff --git a/include/linux/firmware/cirrus/cs_dsp.h b/include/linux/firmware/cirrus/cs_dsp.h index a66eb7624730c4..69959032f8f51e 100644 --- a/include/linux/firmware/cirrus/cs_dsp.h +++ b/include/linux/firmware/cirrus/cs_dsp.h @@ -188,8 +188,8 @@ struct cs_dsp { #ifdef CONFIG_DEBUG_FS struct dentry *debugfs_root; - char *wmfw_file_name; - char *bin_file_name; + const char *wmfw_file_name; + const char *bin_file_name; #endif }; From 01d2fb15f15c153f6a9935ab8e6926d9588a9ac6 Mon Sep 17 00:00:00 2001 From: Richard Fitzgerald Date: Fri, 30 Jan 2026 17:12:56 +0000 Subject: [PATCH 3136/3902] firmware: cs_dsp: rate-limit log messages in KUnit builds [ Upstream commit 10db9f6899dd3a2dfd26efd40afd308891dc44a8 ] Use the dev_*_ratelimit() macros if the cs_dsp KUnit tests are enabled in the build, and allow the KUnit tests to disable message output. Some of the KUnit tests cause a very large number of log messages from cs_dsp, because the tests perform many different test cases. This could cause some lines to be dropped from the kernel log. Dropped lines can prevent the KUnit wrappers from parsing the ktap output in the dmesg log. The KUnit builds of cs_dsp export three bools that the KUnit tests can use to entirely disable log output of err, warn and info messages. Some tests have been updated to use this, replacing the previous fudge of a usleep() in the exit handler of each test. We don't necessarily want to disable all log messages if they aren't expected to be excessive, so the rate-limiting allows leaving some logging enabled. The rate-limited macros are not used in normal builds because it is not appropriate to rate-limit every message. That could cause important messages to be dropped, and there wouldn't be such a high rate of messages in normal operation. Signed-off-by: Richard Fitzgerald Reported-by: Mark Brown Closes: https://lore.kernel.org/linux-sound/af393f08-facb-4c44-a054-1f61254803ec@opensource.cirrus.com/T/#t Fixes: cd8c058499b6 ("firmware: cs_dsp: Add KUnit testing of bin error cases") Link: https://patch.msgid.link/20260130171256.863152-1-rf@opensource.cirrus.com Signed-off-by: Mark Brown Signed-off-by: Sasha Levin --- drivers/firmware/cirrus/cs_dsp.c | 37 +++++++++++++++++++ drivers/firmware/cirrus/cs_dsp.h | 18 +++++++++ .../firmware/cirrus/test/cs_dsp_test_bin.c | 22 ++++++++++- .../cirrus/test/cs_dsp_test_bin_error.c | 24 +++++++++--- .../firmware/cirrus/test/cs_dsp_test_wmfw.c | 26 ++++++++++++- .../cirrus/test/cs_dsp_test_wmfw_error.c | 24 +++++++++--- drivers/firmware/cirrus/test/cs_dsp_tests.c | 1 + 7 files changed, 138 insertions(+), 14 deletions(-) create mode 100644 drivers/firmware/cirrus/cs_dsp.h diff --git a/drivers/firmware/cirrus/cs_dsp.c b/drivers/firmware/cirrus/cs_dsp.c index 58e41751dbc19b..7ca56777a1da5b 100644 --- a/drivers/firmware/cirrus/cs_dsp.c +++ b/drivers/firmware/cirrus/cs_dsp.c @@ -9,6 +9,7 @@ * Cirrus Logic International Semiconductor Ltd. */ +#include #include #include #include @@ -23,6 +24,41 @@ #include #include +#include "cs_dsp.h" + +/* + * When the KUnit test is running the error-case tests will cause a lot + * of messages. Rate-limit to prevent overflowing the kernel log buffer + * during KUnit test runs. + */ +#if IS_ENABLED(CONFIG_FW_CS_DSP_KUNIT_TEST) +bool cs_dsp_suppress_err_messages; +EXPORT_SYMBOL_IF_KUNIT(cs_dsp_suppress_err_messages); + +bool cs_dsp_suppress_warn_messages; +EXPORT_SYMBOL_IF_KUNIT(cs_dsp_suppress_warn_messages); + +bool cs_dsp_suppress_info_messages; +EXPORT_SYMBOL_IF_KUNIT(cs_dsp_suppress_info_messages); + +#define cs_dsp_err(_dsp, fmt, ...) \ + do { \ + if (!cs_dsp_suppress_err_messages) \ + dev_err_ratelimited(_dsp->dev, "%s: " fmt, _dsp->name, ##__VA_ARGS__); \ + } while (false) +#define cs_dsp_warn(_dsp, fmt, ...) \ + do { \ + if (!cs_dsp_suppress_warn_messages) \ + dev_warn_ratelimited(_dsp->dev, "%s: " fmt, _dsp->name, ##__VA_ARGS__); \ + } while (false) +#define cs_dsp_info(_dsp, fmt, ...) \ + do { \ + if (!cs_dsp_suppress_info_messages) \ + dev_info_ratelimited(_dsp->dev, "%s: " fmt, _dsp->name, ##__VA_ARGS__); \ + } while (false) +#define cs_dsp_dbg(_dsp, fmt, ...) \ + dev_dbg_ratelimited(_dsp->dev, "%s: " fmt, _dsp->name, ##__VA_ARGS__) +#else #define cs_dsp_err(_dsp, fmt, ...) \ dev_err(_dsp->dev, "%s: " fmt, _dsp->name, ##__VA_ARGS__) #define cs_dsp_warn(_dsp, fmt, ...) \ @@ -31,6 +67,7 @@ dev_info(_dsp->dev, "%s: " fmt, _dsp->name, ##__VA_ARGS__) #define cs_dsp_dbg(_dsp, fmt, ...) \ dev_dbg(_dsp->dev, "%s: " fmt, _dsp->name, ##__VA_ARGS__) +#endif #define ADSP1_CONTROL_1 0x00 #define ADSP1_CONTROL_2 0x02 diff --git a/drivers/firmware/cirrus/cs_dsp.h b/drivers/firmware/cirrus/cs_dsp.h new file mode 100644 index 00000000000000..adf543004aea31 --- /dev/null +++ b/drivers/firmware/cirrus/cs_dsp.h @@ -0,0 +1,18 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * cs_dsp.h -- Private header for cs_dsp driver. + * + * Copyright (C) 2026 Cirrus Logic, Inc. and + * Cirrus Logic International Semiconductor Ltd. + */ + +#ifndef FW_CS_DSP_H +#define FW_CS_DSP_H + +#if IS_ENABLED(CONFIG_KUNIT) +extern bool cs_dsp_suppress_err_messages; +extern bool cs_dsp_suppress_warn_messages; +extern bool cs_dsp_suppress_info_messages; +#endif + +#endif /* ifndef FW_CS_DSP_H */ diff --git a/drivers/firmware/cirrus/test/cs_dsp_test_bin.c b/drivers/firmware/cirrus/test/cs_dsp_test_bin.c index 163b7faecff466..2c6486fa95758d 100644 --- a/drivers/firmware/cirrus/test/cs_dsp_test_bin.c +++ b/drivers/firmware/cirrus/test/cs_dsp_test_bin.c @@ -17,6 +17,8 @@ #include #include +#include "../cs_dsp.h" + /* * Test method is: * @@ -2224,7 +2226,22 @@ static int cs_dsp_bin_test_common_init(struct kunit *test, struct cs_dsp *dsp) return ret; /* Automatically call cs_dsp_remove() when test case ends */ - return kunit_add_action_or_reset(priv->test, _cs_dsp_remove_wrapper, dsp); + ret = kunit_add_action_or_reset(priv->test, _cs_dsp_remove_wrapper, dsp); + if (ret) + return ret; + + /* + * The large number of test cases will cause an unusually large amount + * of dev_info() messages from cs_dsp, so suppress these. + */ + cs_dsp_suppress_info_messages = true; + + return 0; +} + +static void cs_dsp_bin_test_exit(struct kunit *test) +{ + cs_dsp_suppress_info_messages = false; } static int cs_dsp_bin_test_halo_init(struct kunit *test) @@ -2536,18 +2553,21 @@ static struct kunit_case cs_dsp_bin_test_cases_adsp2[] = { static struct kunit_suite cs_dsp_bin_test_halo = { .name = "cs_dsp_bin_halo", .init = cs_dsp_bin_test_halo_init, + .exit = cs_dsp_bin_test_exit, .test_cases = cs_dsp_bin_test_cases_halo, }; static struct kunit_suite cs_dsp_bin_test_adsp2_32bit = { .name = "cs_dsp_bin_adsp2_32bit", .init = cs_dsp_bin_test_adsp2_32bit_init, + .exit = cs_dsp_bin_test_exit, .test_cases = cs_dsp_bin_test_cases_adsp2, }; static struct kunit_suite cs_dsp_bin_test_adsp2_16bit = { .name = "cs_dsp_bin_adsp2_16bit", .init = cs_dsp_bin_test_adsp2_16bit_init, + .exit = cs_dsp_bin_test_exit, .test_cases = cs_dsp_bin_test_cases_adsp2, }; diff --git a/drivers/firmware/cirrus/test/cs_dsp_test_bin_error.c b/drivers/firmware/cirrus/test/cs_dsp_test_bin_error.c index a7ec956d27249e..631b9cb9eb2508 100644 --- a/drivers/firmware/cirrus/test/cs_dsp_test_bin_error.c +++ b/drivers/firmware/cirrus/test/cs_dsp_test_bin_error.c @@ -18,6 +18,8 @@ #include #include +#include "../cs_dsp.h" + KUNIT_DEFINE_ACTION_WRAPPER(_put_device_wrapper, put_device, struct device *); KUNIT_DEFINE_ACTION_WRAPPER(_cs_dsp_remove_wrapper, cs_dsp_remove, struct cs_dsp *); @@ -380,11 +382,9 @@ static void bin_block_payload_len_garbage(struct kunit *test) static void cs_dsp_bin_err_test_exit(struct kunit *test) { - /* - * Testing error conditions can produce a lot of log output - * from cs_dsp error messages, so rate limit the test cases. - */ - usleep_range(200, 500); + cs_dsp_suppress_err_messages = false; + cs_dsp_suppress_warn_messages = false; + cs_dsp_suppress_info_messages = false; } static int cs_dsp_bin_err_test_common_init(struct kunit *test, struct cs_dsp *dsp, @@ -474,7 +474,19 @@ static int cs_dsp_bin_err_test_common_init(struct kunit *test, struct cs_dsp *ds return ret; /* Automatically call cs_dsp_remove() when test case ends */ - return kunit_add_action_or_reset(priv->test, _cs_dsp_remove_wrapper, dsp); + ret = kunit_add_action_or_reset(priv->test, _cs_dsp_remove_wrapper, dsp); + if (ret) + return ret; + + /* + * Testing error conditions can produce a lot of log output + * from cs_dsp error messages, so suppress messages. + */ + cs_dsp_suppress_err_messages = true; + cs_dsp_suppress_warn_messages = true; + cs_dsp_suppress_info_messages = true; + + return 0; } static int cs_dsp_bin_err_test_halo_init(struct kunit *test) diff --git a/drivers/firmware/cirrus/test/cs_dsp_test_wmfw.c b/drivers/firmware/cirrus/test/cs_dsp_test_wmfw.c index 9e997c4ee2d67c..f02cb6cf763868 100644 --- a/drivers/firmware/cirrus/test/cs_dsp_test_wmfw.c +++ b/drivers/firmware/cirrus/test/cs_dsp_test_wmfw.c @@ -18,6 +18,8 @@ #include #include +#include "../cs_dsp.h" + /* * Test method is: * @@ -1853,7 +1855,22 @@ static int cs_dsp_wmfw_test_common_init(struct kunit *test, struct cs_dsp *dsp, return ret; /* Automatically call cs_dsp_remove() when test case ends */ - return kunit_add_action_or_reset(priv->test, _cs_dsp_remove_wrapper, dsp); + ret = kunit_add_action_or_reset(priv->test, _cs_dsp_remove_wrapper, dsp); + if (ret) + return ret; + + /* + * The large number of test cases will cause an unusually large amount + * of dev_info() messages from cs_dsp, so suppress these. + */ + cs_dsp_suppress_info_messages = true; + + return 0; +} + +static void cs_dsp_wmfw_test_exit(struct kunit *test) +{ + cs_dsp_suppress_info_messages = false; } static int cs_dsp_wmfw_test_halo_init(struct kunit *test) @@ -2163,42 +2180,49 @@ static struct kunit_case cs_dsp_wmfw_test_cases_adsp2[] = { static struct kunit_suite cs_dsp_wmfw_test_halo = { .name = "cs_dsp_wmfwV3_halo", .init = cs_dsp_wmfw_test_halo_init, + .exit = cs_dsp_wmfw_test_exit, .test_cases = cs_dsp_wmfw_test_cases_halo, }; static struct kunit_suite cs_dsp_wmfw_test_adsp2_32bit_wmfw0 = { .name = "cs_dsp_wmfwV0_adsp2_32bit", .init = cs_dsp_wmfw_test_adsp2_32bit_wmfw0_init, + .exit = cs_dsp_wmfw_test_exit, .test_cases = cs_dsp_wmfw_test_cases_adsp2, }; static struct kunit_suite cs_dsp_wmfw_test_adsp2_32bit_wmfw1 = { .name = "cs_dsp_wmfwV1_adsp2_32bit", .init = cs_dsp_wmfw_test_adsp2_32bit_wmfw1_init, + .exit = cs_dsp_wmfw_test_exit, .test_cases = cs_dsp_wmfw_test_cases_adsp2, }; static struct kunit_suite cs_dsp_wmfw_test_adsp2_32bit_wmfw2 = { .name = "cs_dsp_wmfwV2_adsp2_32bit", .init = cs_dsp_wmfw_test_adsp2_32bit_wmfw2_init, + .exit = cs_dsp_wmfw_test_exit, .test_cases = cs_dsp_wmfw_test_cases_adsp2, }; static struct kunit_suite cs_dsp_wmfw_test_adsp2_16bit_wmfw0 = { .name = "cs_dsp_wmfwV0_adsp2_16bit", .init = cs_dsp_wmfw_test_adsp2_16bit_wmfw0_init, + .exit = cs_dsp_wmfw_test_exit, .test_cases = cs_dsp_wmfw_test_cases_adsp2, }; static struct kunit_suite cs_dsp_wmfw_test_adsp2_16bit_wmfw1 = { .name = "cs_dsp_wmfwV1_adsp2_16bit", .init = cs_dsp_wmfw_test_adsp2_16bit_wmfw1_init, + .exit = cs_dsp_wmfw_test_exit, .test_cases = cs_dsp_wmfw_test_cases_adsp2, }; static struct kunit_suite cs_dsp_wmfw_test_adsp2_16bit_wmfw2 = { .name = "cs_dsp_wmfwV2_adsp2_16bit", .init = cs_dsp_wmfw_test_adsp2_16bit_wmfw2_init, + .exit = cs_dsp_wmfw_test_exit, .test_cases = cs_dsp_wmfw_test_cases_adsp2, }; diff --git a/drivers/firmware/cirrus/test/cs_dsp_test_wmfw_error.c b/drivers/firmware/cirrus/test/cs_dsp_test_wmfw_error.c index c309843261d724..37162d12e2fa78 100644 --- a/drivers/firmware/cirrus/test/cs_dsp_test_wmfw_error.c +++ b/drivers/firmware/cirrus/test/cs_dsp_test_wmfw_error.c @@ -18,6 +18,8 @@ #include #include +#include "../cs_dsp.h" + KUNIT_DEFINE_ACTION_WRAPPER(_put_device_wrapper, put_device, struct device *); KUNIT_DEFINE_ACTION_WRAPPER(_cs_dsp_remove_wrapper, cs_dsp_remove, struct cs_dsp *); @@ -989,11 +991,9 @@ static void wmfw_v2_coeff_description_exceeds_block(struct kunit *test) static void cs_dsp_wmfw_err_test_exit(struct kunit *test) { - /* - * Testing error conditions can produce a lot of log output - * from cs_dsp error messages, so rate limit the test cases. - */ - usleep_range(200, 500); + cs_dsp_suppress_err_messages = false; + cs_dsp_suppress_warn_messages = false; + cs_dsp_suppress_info_messages = false; } static int cs_dsp_wmfw_err_test_common_init(struct kunit *test, struct cs_dsp *dsp, @@ -1072,7 +1072,19 @@ static int cs_dsp_wmfw_err_test_common_init(struct kunit *test, struct cs_dsp *d return ret; /* Automatically call cs_dsp_remove() when test case ends */ - return kunit_add_action_or_reset(priv->test, _cs_dsp_remove_wrapper, dsp); + ret = kunit_add_action_or_reset(priv->test, _cs_dsp_remove_wrapper, dsp); + if (ret) + return ret; + + /* + * Testing error conditions can produce a lot of log output + * from cs_dsp error messages, so suppress messages. + */ + cs_dsp_suppress_err_messages = true; + cs_dsp_suppress_warn_messages = true; + cs_dsp_suppress_info_messages = true; + + return 0; } static int cs_dsp_wmfw_err_test_halo_init(struct kunit *test) diff --git a/drivers/firmware/cirrus/test/cs_dsp_tests.c b/drivers/firmware/cirrus/test/cs_dsp_tests.c index 7b829a03ca5291..288675fdbdc534 100644 --- a/drivers/firmware/cirrus/test/cs_dsp_tests.c +++ b/drivers/firmware/cirrus/test/cs_dsp_tests.c @@ -12,3 +12,4 @@ MODULE_AUTHOR("Richard Fitzgerald "); MODULE_LICENSE("GPL"); MODULE_IMPORT_NS("FW_CS_DSP"); MODULE_IMPORT_NS("FW_CS_DSP_KUNIT_TEST_UTILS"); +MODULE_IMPORT_NS("EXPORTED_FOR_KUNIT_TESTING"); From 8434b351cd4ea16c8e1857c6f3faa94079b32849 Mon Sep 17 00:00:00 2001 From: Sergey Shtylyov Date: Tue, 3 Feb 2026 19:15:57 +0300 Subject: [PATCH 3137/3902] ALSA: usb-audio: fix broken logic in snd_audigy2nx_led_update() [ Upstream commit 124bdc6eccc8c5cba68fee00e01c084c116c4360 ] When the support for the Sound Blaster X-Fi Surround 5.1 Pro was added, the existing logic for the X-Fi Surround 5.1 in snd_audigy2nx_led_put() was broken due to missing *else* before the added *if*: snd_usb_ctl_msg() became incorrectly called twice and an error from first snd_usb_ctl_msg() call ignored. As the added snd_usb_ctl_msg() call was totally identical to the existing one for the "plain" X-Fi Surround 5.1, just merge those two *if* statements while fixing the broken logic... Found by Linux Verification Center (linuxtesting.org) with the Svace static analysis tool. Fixes: 7cdd8d73139e ("ALSA: usb-audio - Add support for USB X-Fi S51 Pro") Signed-off-by: Sergey Shtylyov Link: https://patch.msgid.link/20260203161558.18680-1-s.shtylyov@auroraos.dev Signed-off-by: Takashi Iwai Signed-off-by: Sasha Levin --- sound/usb/mixer_quirks.c | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/sound/usb/mixer_quirks.c b/sound/usb/mixer_quirks.c index 828af3095b86ee..4873b5e7480161 100644 --- a/sound/usb/mixer_quirks.c +++ b/sound/usb/mixer_quirks.c @@ -311,13 +311,8 @@ static int snd_audigy2nx_led_update(struct usb_mixer_interface *mixer, if (pm.err < 0) return pm.err; - if (chip->usb_id == USB_ID(0x041e, 0x3042)) - err = snd_usb_ctl_msg(chip->dev, - usb_sndctrlpipe(chip->dev, 0), 0x24, - USB_DIR_OUT | USB_TYPE_VENDOR | USB_RECIP_OTHER, - !value, 0, NULL, 0); - /* USB X-Fi S51 Pro */ - if (chip->usb_id == USB_ID(0x041e, 0x30df)) + if (chip->usb_id == USB_ID(0x041e, 0x3042) || /* USB X-Fi S51 */ + chip->usb_id == USB_ID(0x041e, 0x30df)) /* USB X-Fi S51 Pro */ err = snd_usb_ctl_msg(chip->dev, usb_sndctrlpipe(chip->dev, 0), 0x24, USB_DIR_OUT | USB_TYPE_VENDOR | USB_RECIP_OTHER, From 279cb9180510f7e13c3a4dfde8c16a8fbc7c5709 Mon Sep 17 00:00:00 2001 From: Chris Bainbridge Date: Mon, 2 Feb 2026 20:50:33 +0000 Subject: [PATCH 3138/3902] ASoC: amd: fix memory leak in acp3x pdm dma ops [ Upstream commit 7f67ba5413f98d93116a756e7f17cd2c1d6c2bd6 ] Fixes: 4a767b1d039a8 ("ASoC: amd: add acp3x pdm driver dma ops") Signed-off-by: Chris Bainbridge Link: https://patch.msgid.link/20260202205034.7697-1-chris.bainbridge@gmail.com Signed-off-by: Mark Brown Signed-off-by: Sasha Levin --- sound/soc/amd/renoir/acp3x-pdm-dma.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/sound/soc/amd/renoir/acp3x-pdm-dma.c b/sound/soc/amd/renoir/acp3x-pdm-dma.c index 95ac8c68003750..a560d06097d5e3 100644 --- a/sound/soc/amd/renoir/acp3x-pdm-dma.c +++ b/sound/soc/amd/renoir/acp3x-pdm-dma.c @@ -301,9 +301,11 @@ static int acp_pdm_dma_close(struct snd_soc_component *component, struct snd_pcm_substream *substream) { struct pdm_dev_data *adata = dev_get_drvdata(component->dev); + struct pdm_stream_instance *rtd = substream->runtime->private_data; disable_pdm_interrupts(adata->acp_base); adata->capture_stream = NULL; + kfree(rtd); return 0; } From e71e3fa90a15134113f61343392e887cd1f4bf7c Mon Sep 17 00:00:00 2001 From: Chen Ni Date: Thu, 5 Feb 2026 15:26:49 +0800 Subject: [PATCH 3139/3902] gpio: loongson-64bit: Fix incorrect NULL check after devm_kcalloc() [ Upstream commit e34f77b09080c86c929153e2a72da26b4f8947ff ] Fix incorrect NULL check in loongson_gpio_init_irqchip(). The function checks chip->parent instead of chip->irq.parents. Fixes: 03c146cb6cd1 ("gpio: loongson-64bit: Add support for Loongson-2K0300 SoC") Signed-off-by: Chen Ni Link: https://patch.msgid.link/20260205072649.3271158-1-nichen@iscas.ac.cn Signed-off-by: Bartosz Golaszewski Signed-off-by: Sasha Levin --- drivers/gpio/gpio-loongson-64bit.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/gpio/gpio-loongson-64bit.c b/drivers/gpio/gpio-loongson-64bit.c index 82d4c3aa4d2fc7..d5573fb0616ce6 100644 --- a/drivers/gpio/gpio-loongson-64bit.c +++ b/drivers/gpio/gpio-loongson-64bit.c @@ -263,7 +263,7 @@ static int loongson_gpio_init_irqchip(struct platform_device *pdev, chip->irq.num_parents = data->intr_num; chip->irq.parents = devm_kcalloc(&pdev->dev, data->intr_num, sizeof(*chip->irq.parents), GFP_KERNEL); - if (!chip->parent) + if (!chip->irq.parents) return -ENOMEM; for (i = 0; i < data->intr_num; i++) { From 3f9b508b3eecc00a243edf320bd83834d6a9b482 Mon Sep 17 00:00:00 2001 From: LI Qingwu Date: Fri, 16 Jan 2026 11:19:05 +0000 Subject: [PATCH 3140/3902] i2c: imx: preserve error state in block data length handler commit b126097b0327437048bd045a0e4d273dea2910dd upstream. When a block read returns an invalid length, zero or >I2C_SMBUS_BLOCK_MAX, the length handler sets the state to IMX_I2C_STATE_FAILED. However, i2c_imx_master_isr() unconditionally overwrites this with IMX_I2C_STATE_READ_CONTINUE, causing an endless read loop that overruns buffers and crashes the system. Guard the state transition to preserve error states set by the length handler. Fixes: 5f5c2d4579ca ("i2c: imx: prevent rescheduling in non dma mode") Signed-off-by: LI Qingwu Cc: # v6.13+ Reviewed-by: Stefan Eichenberger Signed-off-by: Andi Shyti Link: https://lore.kernel.org/r/20260116111906.3413346-2-Qing-wu.Li@leica-geosystems.com.cn Signed-off-by: Wolfram Sang Signed-off-by: Greg Kroah-Hartman --- drivers/i2c/busses/i2c-imx.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/drivers/i2c/busses/i2c-imx.c b/drivers/i2c/busses/i2c-imx.c index dcce882f3ebaaf..85f554044cf1ee 100644 --- a/drivers/i2c/busses/i2c-imx.c +++ b/drivers/i2c/busses/i2c-imx.c @@ -1103,7 +1103,8 @@ static irqreturn_t i2c_imx_master_isr(struct imx_i2c_struct *i2c_imx, unsigned i case IMX_I2C_STATE_READ_BLOCK_DATA_LEN: i2c_imx_isr_read_block_data_len(i2c_imx); - i2c_imx->state = IMX_I2C_STATE_READ_CONTINUE; + if (i2c_imx->state == IMX_I2C_STATE_READ_BLOCK_DATA_LEN) + i2c_imx->state = IMX_I2C_STATE_READ_CONTINUE; break; case IMX_I2C_STATE_WRITE: From b767cf2d4efc814d639e31f2413082b345fe3841 Mon Sep 17 00:00:00 2001 From: Guodong Xu Date: Thu, 22 Jan 2026 17:43:42 +0800 Subject: [PATCH 3141/3902] regulator: spacemit-p1: Fix n_voltages for BUCK and LDO regulators [ Upstream commit 41399c5d476156635c9a58de870d39318e22fa09 ] Higher voltage settings were unusable due to incorrect n_voltages values causing registration failures. For example, setting aldo4 to 3.3V failed with -EINVAL because the required selector (123) exceeded the allowed range (n_voltages=117). Fix by aligning n_voltages with the hardware register widths per the P1 datasheet [1]: - BUCK: 255 (was 254), allows selectors 0-254, selector 255 is reserved - LDO: 128 (was 117), allows selectors 0-127, selectors 0-10 are for suspend mode, valid operational range is 11-127 This enables the full voltage range supported by the hardware. Fixes: 8b84d712ad84 ("regulator: spacemit: support SpacemiT P1 regulators") Link: https://developer.spacemit.com/documentation [1] Signed-off-by: Guodong Xu Link: https://patch.msgid.link/20260122-spacemit-p1-v1-1-309be27fbff9@riscstar.com Signed-off-by: Mark Brown Signed-off-by: Sasha Levin --- drivers/regulator/spacemit-p1.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/drivers/regulator/spacemit-p1.c b/drivers/regulator/spacemit-p1.c index 2bf9137e12b1d0..2b585ba01a93d0 100644 --- a/drivers/regulator/spacemit-p1.c +++ b/drivers/regulator/spacemit-p1.c @@ -87,13 +87,13 @@ static const struct linear_range p1_ldo_ranges[] = { } #define P1_BUCK_DESC(_n) \ - P1_REG_DESC(BUCK, buck, _n, "vin", 0x47, BUCK_MASK, 254, p1_buck_ranges) + P1_REG_DESC(BUCK, buck, _n, "vin", 0x47, BUCK_MASK, 255, p1_buck_ranges) #define P1_ALDO_DESC(_n) \ - P1_REG_DESC(ALDO, aldo, _n, "vin", 0x5b, LDO_MASK, 117, p1_ldo_ranges) + P1_REG_DESC(ALDO, aldo, _n, "vin", 0x5b, LDO_MASK, 128, p1_ldo_ranges) #define P1_DLDO_DESC(_n) \ - P1_REG_DESC(DLDO, dldo, _n, "buck5", 0x67, LDO_MASK, 117, p1_ldo_ranges) + P1_REG_DESC(DLDO, dldo, _n, "buck5", 0x67, LDO_MASK, 128, p1_ldo_ranges) static const struct regulator_desc p1_regulator_desc[] = { P1_BUCK_DESC(1), From e1777c400b7a93a5d40d18153f24d58b3470c27c Mon Sep 17 00:00:00 2001 From: Breno Leitao Date: Mon, 26 Jan 2026 09:50:26 -0800 Subject: [PATCH 3142/3902] spi: tegra210-quad: Return IRQ_HANDLED when timeout already processed transfer [ Upstream commit aabd8ea0aa253d40cf5f20a609fc3d6f61e38299 ] When the ISR thread wakes up late and finds that the timeout handler has already processed the transfer (curr_xfer is NULL), return IRQ_HANDLED instead of IRQ_NONE. Use a similar approach to tegra_qspi_handle_timeout() by reading QSPI_TRANS_STATUS and checking the QSPI_RDY bit to determine if the hardware actually completed the transfer. If QSPI_RDY is set, the interrupt was legitimate and triggered by real hardware activity. The fact that the timeout path handled it first doesn't make it spurious. Returning IRQ_NONE incorrectly suggests the interrupt wasn't for this device, which can cause issues with shared interrupt lines and interrupt accounting. Fixes: b4e002d8a7ce ("spi: tegra210-quad: Fix timeout handling") Signed-off-by: Breno Leitao Signed-off-by: Usama Arif Tested-by: Jon Hunter Acked-by: Jon Hunter Acked-by: Thierry Reding Link: https://patch.msgid.link/20260126-tegra_xfer-v2-1-6d2115e4f387@debian.org Signed-off-by: Mark Brown Signed-off-by: Sasha Levin --- drivers/spi/spi-tegra210-quad.c | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/drivers/spi/spi-tegra210-quad.c b/drivers/spi/spi-tegra210-quad.c index d9ca3d7b082f29..c6c05e6f48994e 100644 --- a/drivers/spi/spi-tegra210-quad.c +++ b/drivers/spi/spi-tegra210-quad.c @@ -1488,15 +1488,30 @@ static irqreturn_t handle_dma_based_xfer(struct tegra_qspi *tqspi) static irqreturn_t tegra_qspi_isr_thread(int irq, void *context_data) { struct tegra_qspi *tqspi = context_data; + u32 status; + + /* + * Read transfer status to check if interrupt was triggered by transfer + * completion + */ + status = tegra_qspi_readl(tqspi, QSPI_TRANS_STATUS); /* * Occasionally the IRQ thread takes a long time to wake up (usually * when the CPU that it's running on is excessively busy) and we have * already reached the timeout before and cleaned up the timed out * transfer. Avoid any processing in that case and bail out early. + * + * If no transfer is in progress, check if this was a real interrupt + * that the timeout handler already processed, or a spurious one. */ - if (!tqspi->curr_xfer) - return IRQ_NONE; + if (!tqspi->curr_xfer) { + /* Spurious interrupt - transfer not ready */ + if (!(status & QSPI_RDY)) + return IRQ_NONE; + /* Real interrupt, already handled by timeout path */ + return IRQ_HANDLED; + } tqspi->status_reg = tegra_qspi_readl(tqspi, QSPI_FIFO_STATUS); From 51013068438acbc1fed8e08dc34e32e63665d7d1 Mon Sep 17 00:00:00 2001 From: Breno Leitao Date: Mon, 26 Jan 2026 09:50:27 -0800 Subject: [PATCH 3143/3902] spi: tegra210-quad: Move curr_xfer read inside spinlock [ Upstream commit ef13ba357656451d6371940d8414e3e271df97e3 ] Move the assignment of the transfer pointer from curr_xfer inside the spinlock critical section in both handle_cpu_based_xfer() and handle_dma_based_xfer(). Previously, curr_xfer was read before acquiring the lock, creating a window where the timeout path could clear curr_xfer between reading it and using it. By moving the read inside the lock, the handlers are guaranteed to see a consistent value that cannot be modified by the timeout path. Fixes: 921fc1838fb0 ("spi: tegra210-quad: Add support for Tegra210 QSPI controller") Signed-off-by: Breno Leitao Acked-by: Thierry Reding Tested-by: Jon Hunter Acked-by: Jon Hunter Link: https://patch.msgid.link/20260126-tegra_xfer-v2-2-6d2115e4f387@debian.org Signed-off-by: Mark Brown Signed-off-by: Sasha Levin --- drivers/spi/spi-tegra210-quad.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/drivers/spi/spi-tegra210-quad.c b/drivers/spi/spi-tegra210-quad.c index c6c05e6f48994e..a599bad02b4d39 100644 --- a/drivers/spi/spi-tegra210-quad.c +++ b/drivers/spi/spi-tegra210-quad.c @@ -1376,10 +1376,11 @@ static int tegra_qspi_transfer_one_message(struct spi_controller *host, static irqreturn_t handle_cpu_based_xfer(struct tegra_qspi *tqspi) { - struct spi_transfer *t = tqspi->curr_xfer; + struct spi_transfer *t; unsigned long flags; spin_lock_irqsave(&tqspi->lock, flags); + t = tqspi->curr_xfer; if (tqspi->tx_status || tqspi->rx_status) { tegra_qspi_handle_error(tqspi); @@ -1410,7 +1411,7 @@ static irqreturn_t handle_cpu_based_xfer(struct tegra_qspi *tqspi) static irqreturn_t handle_dma_based_xfer(struct tegra_qspi *tqspi) { - struct spi_transfer *t = tqspi->curr_xfer; + struct spi_transfer *t; unsigned int total_fifo_words; unsigned long flags; long wait_status; @@ -1449,6 +1450,7 @@ static irqreturn_t handle_dma_based_xfer(struct tegra_qspi *tqspi) } spin_lock_irqsave(&tqspi->lock, flags); + t = tqspi->curr_xfer; if (num_errors) { tegra_qspi_dma_unmap_xfer(tqspi, t); From 2d3c0122e96111d38382f7a1ac00848865c969f6 Mon Sep 17 00:00:00 2001 From: Breno Leitao Date: Mon, 26 Jan 2026 09:50:28 -0800 Subject: [PATCH 3144/3902] spi: tegra210-quad: Protect curr_xfer assignment in tegra_qspi_setup_transfer_one [ Upstream commit f5a4d7f5e32ba163cff893493ec1cbb0fd2fb0d5 ] When the timeout handler processes a completed transfer and signals completion, the transfer thread can immediately set up the next transfer and assign curr_xfer to point to it. If a delayed ISR from the previous transfer then runs, it checks if (!tqspi->curr_xfer) (currently without the lock also -- to be fixed soon) to detect stale interrupts, but this check passes because curr_xfer now points to the new transfer. The ISR then incorrectly processes the new transfer's context. Protect the curr_xfer assignment with the spinlock to ensure the ISR either sees NULL (and bails out) or sees the new value only after the assignment is complete. Fixes: 921fc1838fb0 ("spi: tegra210-quad: Add support for Tegra210 QSPI controller") Signed-off-by: Breno Leitao Tested-by: Jon Hunter Acked-by: Jon Hunter Acked-by: Thierry Reding Link: https://patch.msgid.link/20260126-tegra_xfer-v2-3-6d2115e4f387@debian.org Signed-off-by: Mark Brown Signed-off-by: Sasha Levin --- drivers/spi/spi-tegra210-quad.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/drivers/spi/spi-tegra210-quad.c b/drivers/spi/spi-tegra210-quad.c index a599bad02b4d39..6d89a9309d85e8 100644 --- a/drivers/spi/spi-tegra210-quad.c +++ b/drivers/spi/spi-tegra210-quad.c @@ -839,6 +839,7 @@ static u32 tegra_qspi_setup_transfer_one(struct spi_device *spi, struct spi_tran u32 command1, command2, speed = t->speed_hz; u8 bits_per_word = t->bits_per_word; u32 tx_tap = 0, rx_tap = 0; + unsigned long flags; int req_mode; if (!has_acpi_companion(tqspi->dev) && speed != tqspi->cur_speed) { @@ -846,10 +847,12 @@ static u32 tegra_qspi_setup_transfer_one(struct spi_device *spi, struct spi_tran tqspi->cur_speed = speed; } + spin_lock_irqsave(&tqspi->lock, flags); tqspi->cur_pos = 0; tqspi->cur_rx_pos = 0; tqspi->cur_tx_pos = 0; tqspi->curr_xfer = t; + spin_unlock_irqrestore(&tqspi->lock, flags); if (is_first_of_msg) { tegra_qspi_mask_clear_irq(tqspi); From 3bc293d5b56502068481478842f57b3d96e432c7 Mon Sep 17 00:00:00 2001 From: Breno Leitao Date: Mon, 26 Jan 2026 09:50:29 -0800 Subject: [PATCH 3145/3902] spi: tegra210-quad: Protect curr_xfer in tegra_qspi_combined_seq_xfer [ Upstream commit bf4528ab28e2bf112c3a2cdef44fd13f007781cd ] The curr_xfer field is read by the IRQ handler without holding the lock to check if a transfer is in progress. When clearing curr_xfer in the combined sequence transfer loop, protect it with the spinlock to prevent a race with the interrupt handler. Protect the curr_xfer clearing at the exit path of tegra_qspi_combined_seq_xfer() with the spinlock to prevent a race with the interrupt handler that reads this field. Without this protection, the IRQ handler could read a partially updated curr_xfer value, leading to NULL pointer dereference or use-after-free. Fixes: b4e002d8a7ce ("spi: tegra210-quad: Fix timeout handling") Signed-off-by: Breno Leitao Tested-by: Jon Hunter Acked-by: Jon Hunter Acked-by: Thierry Reding Link: https://patch.msgid.link/20260126-tegra_xfer-v2-4-6d2115e4f387@debian.org Signed-off-by: Mark Brown Signed-off-by: Sasha Levin --- drivers/spi/spi-tegra210-quad.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/drivers/spi/spi-tegra210-quad.c b/drivers/spi/spi-tegra210-quad.c index 6d89a9309d85e8..78e26c25a7b355 100644 --- a/drivers/spi/spi-tegra210-quad.c +++ b/drivers/spi/spi-tegra210-quad.c @@ -1089,6 +1089,7 @@ static int tegra_qspi_combined_seq_xfer(struct tegra_qspi *tqspi, u32 address_value = 0; u32 cmd_config = 0, addr_config = 0; u8 cmd_value = 0, val = 0; + unsigned long flags; /* Enable Combined sequence mode */ val = tegra_qspi_readl(tqspi, QSPI_GLOBAL_CONFIG); @@ -1207,13 +1208,17 @@ static int tegra_qspi_combined_seq_xfer(struct tegra_qspi *tqspi, tegra_qspi_transfer_end(spi); spi_transfer_delay_exec(xfer); } + spin_lock_irqsave(&tqspi->lock, flags); tqspi->curr_xfer = NULL; + spin_unlock_irqrestore(&tqspi->lock, flags); transfer_phase++; } ret = 0; exit: + spin_lock_irqsave(&tqspi->lock, flags); tqspi->curr_xfer = NULL; + spin_unlock_irqrestore(&tqspi->lock, flags); msg->status = ret; return ret; From d51554dc05695cbdd8a4cd3050a5d59e222cb950 Mon Sep 17 00:00:00 2001 From: Breno Leitao Date: Mon, 26 Jan 2026 09:50:30 -0800 Subject: [PATCH 3146/3902] spi: tegra210-quad: Protect curr_xfer clearing in tegra_qspi_non_combined_seq_xfer [ Upstream commit 6d7723e8161f3c3f14125557e19dd080e9d882be ] Protect the curr_xfer clearing in tegra_qspi_non_combined_seq_xfer() with the spinlock to prevent a race with the interrupt handler that reads this field to check if a transfer is in progress. Fixes: b4e002d8a7ce ("spi: tegra210-quad: Fix timeout handling") Signed-off-by: Breno Leitao Tested-by: Jon Hunter Acked-by: Jon Hunter Acked-by: Thierry Reding Link: https://patch.msgid.link/20260126-tegra_xfer-v2-5-6d2115e4f387@debian.org Signed-off-by: Mark Brown Signed-off-by: Sasha Levin --- drivers/spi/spi-tegra210-quad.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/drivers/spi/spi-tegra210-quad.c b/drivers/spi/spi-tegra210-quad.c index 78e26c25a7b355..7fe16ed7e84bdc 100644 --- a/drivers/spi/spi-tegra210-quad.c +++ b/drivers/spi/spi-tegra210-quad.c @@ -1231,6 +1231,7 @@ static int tegra_qspi_non_combined_seq_xfer(struct tegra_qspi *tqspi, struct spi_transfer *transfer; bool is_first_msg = true; int ret = 0, val = 0; + unsigned long flags; msg->status = 0; msg->actual_length = 0; @@ -1304,7 +1305,9 @@ static int tegra_qspi_non_combined_seq_xfer(struct tegra_qspi *tqspi, msg->actual_length += xfer->len + dummy_bytes; complete_xfer: + spin_lock_irqsave(&tqspi->lock, flags); tqspi->curr_xfer = NULL; + spin_unlock_irqrestore(&tqspi->lock, flags); if (ret < 0) { tegra_qspi_transfer_end(spi); From 2ac3a105e51496147c0e44e49466eecfcc532d57 Mon Sep 17 00:00:00 2001 From: Breno Leitao Date: Mon, 26 Jan 2026 09:50:31 -0800 Subject: [PATCH 3147/3902] spi: tegra210-quad: Protect curr_xfer check in IRQ handler [ Upstream commit edf9088b6e1d6d88982db7eb5e736a0e4fbcc09e ] Now that all other accesses to curr_xfer are done under the lock, protect the curr_xfer NULL check in tegra_qspi_isr_thread() with the spinlock. Without this protection, the following race can occur: CPU0 (ISR thread) CPU1 (timeout path) ---------------- ------------------- if (!tqspi->curr_xfer) // sees non-NULL spin_lock() tqspi->curr_xfer = NULL spin_unlock() handle_*_xfer() spin_lock() t = tqspi->curr_xfer // NULL! ... t->len ... // NULL dereference! With this patch, all curr_xfer accesses are now properly synchronized. Although all accesses to curr_xfer are done under the lock, in tegra_qspi_isr_thread() it checks for NULL, releases the lock and reacquires it later in handle_cpu_based_xfer()/handle_dma_based_xfer(). There is a potential for an update in between, which could cause a NULL pointer dereference. To handle this, add a NULL check inside the handlers after acquiring the lock. This ensures that if the timeout path has already cleared curr_xfer, the handler will safely return without dereferencing the NULL pointer. Fixes: b4e002d8a7ce ("spi: tegra210-quad: Fix timeout handling") Signed-off-by: Breno Leitao Tested-by: Jon Hunter Acked-by: Jon Hunter Acked-by: Thierry Reding Link: https://patch.msgid.link/20260126-tegra_xfer-v2-6-6d2115e4f387@debian.org Signed-off-by: Mark Brown Signed-off-by: Sasha Levin --- drivers/spi/spi-tegra210-quad.c | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/drivers/spi/spi-tegra210-quad.c b/drivers/spi/spi-tegra210-quad.c index 7fe16ed7e84bdc..83def82fe48c14 100644 --- a/drivers/spi/spi-tegra210-quad.c +++ b/drivers/spi/spi-tegra210-quad.c @@ -1393,6 +1393,11 @@ static irqreturn_t handle_cpu_based_xfer(struct tegra_qspi *tqspi) spin_lock_irqsave(&tqspi->lock, flags); t = tqspi->curr_xfer; + if (!t) { + spin_unlock_irqrestore(&tqspi->lock, flags); + return IRQ_HANDLED; + } + if (tqspi->tx_status || tqspi->rx_status) { tegra_qspi_handle_error(tqspi); complete(&tqspi->xfer_completion); @@ -1463,6 +1468,11 @@ static irqreturn_t handle_dma_based_xfer(struct tegra_qspi *tqspi) spin_lock_irqsave(&tqspi->lock, flags); t = tqspi->curr_xfer; + if (!t) { + spin_unlock_irqrestore(&tqspi->lock, flags); + return IRQ_HANDLED; + } + if (num_errors) { tegra_qspi_dma_unmap_xfer(tqspi, t); tegra_qspi_handle_error(tqspi); @@ -1501,6 +1511,7 @@ static irqreturn_t handle_dma_based_xfer(struct tegra_qspi *tqspi) static irqreturn_t tegra_qspi_isr_thread(int irq, void *context_data) { struct tegra_qspi *tqspi = context_data; + unsigned long flags; u32 status; /* @@ -1518,7 +1529,9 @@ static irqreturn_t tegra_qspi_isr_thread(int irq, void *context_data) * If no transfer is in progress, check if this was a real interrupt * that the timeout handler already processed, or a spurious one. */ + spin_lock_irqsave(&tqspi->lock, flags); if (!tqspi->curr_xfer) { + spin_unlock_irqrestore(&tqspi->lock, flags); /* Spurious interrupt - transfer not ready */ if (!(status & QSPI_RDY)) return IRQ_NONE; @@ -1535,7 +1548,14 @@ static irqreturn_t tegra_qspi_isr_thread(int irq, void *context_data) tqspi->rx_status = tqspi->status_reg & (QSPI_RX_FIFO_OVF | QSPI_RX_FIFO_UNF); tegra_qspi_mask_clear_irq(tqspi); + spin_unlock_irqrestore(&tqspi->lock, flags); + /* + * Lock is released here but handlers safely re-check curr_xfer under + * lock before dereferencing. + * DMA handler also needs to sleep in wait_for_completion_*(), which + * cannot be done while holding spinlock. + */ if (!tqspi->is_curr_dma_xfer) return handle_cpu_based_xfer(tqspi); From b8eec12aa666c11f8a6ad1488c568f85c58875fa Mon Sep 17 00:00:00 2001 From: Felix Gu Date: Mon, 2 Feb 2026 23:15:09 +0800 Subject: [PATCH 3148/3902] spi: tegra: Fix a memory leak in tegra_slink_probe() [ Upstream commit 41d9a6795b95d6ea28439ac1e9ce8c95bbca20fc ] In tegra_slink_probe(), when platform_get_irq() fails, it directly returns from the function with an error code, which causes a memory leak. Replace it with a goto label to ensure proper cleanup. Fixes: eb9913b511f1 ("spi: tegra: Fix missing IRQ check in tegra_slink_probe()") Signed-off-by: Felix Gu Reviewed-by: Jon Hunter Link: https://patch.msgid.link/20260202-slink-v1-1-eac50433a6f9@gmail.com Signed-off-by: Mark Brown Signed-off-by: Sasha Levin --- drivers/spi/spi-tegra20-slink.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/drivers/spi/spi-tegra20-slink.c b/drivers/spi/spi-tegra20-slink.c index fe452d03c1ee43..709669610840bc 100644 --- a/drivers/spi/spi-tegra20-slink.c +++ b/drivers/spi/spi-tegra20-slink.c @@ -1086,8 +1086,10 @@ static int tegra_slink_probe(struct platform_device *pdev) reset_control_deassert(tspi->rst); spi_irq = platform_get_irq(pdev, 0); - if (spi_irq < 0) - return spi_irq; + if (spi_irq < 0) { + ret = spi_irq; + goto exit_pm_put; + } tspi->irq = spi_irq; ret = request_threaded_irq(tspi->irq, tegra_slink_isr, tegra_slink_isr_thread, IRQF_ONESHOT, From 57bac08056787dcde69cc4347fc0282795257f1c Mon Sep 17 00:00:00 2001 From: Vishwaroop A Date: Wed, 4 Feb 2026 14:12:12 +0000 Subject: [PATCH 3149/3902] spi: tegra114: Preserve SPI mode bits in def_command1_reg [ Upstream commit a0a75b40c919b9f6d3a0b6c978e6ccf344c1be5a ] The COMMAND1 register bits [29:28] set the SPI mode, which controls the clock idle level. When a transfer ends, tegra_spi_transfer_end() writes def_command1_reg back to restore the default state, but this register value currently lacks the mode bits. This results in the clock always being configured as idle low, breaking devices that need it high. Fix this by storing the mode bits in def_command1_reg during setup, to prevent this field from always being cleared. Fixes: f333a331adfa ("spi/tegra114: add spi driver") Signed-off-by: Vishwaroop A Link: https://patch.msgid.link/20260204141212.1540382-1-va@nvidia.com Signed-off-by: Mark Brown Signed-off-by: Sasha Levin --- drivers/spi/spi-tegra114.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/drivers/spi/spi-tegra114.c b/drivers/spi/spi-tegra114.c index 795a8482c2c700..48fb11fea55f2d 100644 --- a/drivers/spi/spi-tegra114.c +++ b/drivers/spi/spi-tegra114.c @@ -978,11 +978,14 @@ static int tegra_spi_setup(struct spi_device *spi) if (spi_get_csgpiod(spi, 0)) gpiod_set_value(spi_get_csgpiod(spi, 0), 0); + /* Update default register to include CS polarity and SPI mode */ val = tspi->def_command1_reg; if (spi->mode & SPI_CS_HIGH) val &= ~SPI_CS_POL_INACTIVE(spi_get_chipselect(spi, 0)); else val |= SPI_CS_POL_INACTIVE(spi_get_chipselect(spi, 0)); + val &= ~SPI_CONTROL_MODE_MASK; + val |= SPI_MODE_SEL(spi->mode & 0x3); tspi->def_command1_reg = val; tegra_spi_writel(tspi, tspi->def_command1_reg, SPI_COMMAND1); spin_unlock_irqrestore(&tspi->lock, flags); From 24ad4cfac0b8e7864cd5cd09c61b990aec683667 Mon Sep 17 00:00:00 2001 From: Werner Sembach Date: Fri, 23 Jan 2026 23:12:24 +0100 Subject: [PATCH 3150/3902] ALSA: hda/realtek: Really fix headset mic for TongFang X6AR55xU. commit 1aaedafb21f38cb872d44f7608b4828a1e14e795 upstream. Add a PCI quirk to enable microphone detection on the headphone jack of TongFang X6AR55xU devices. The former quirk entry did not acomplish this and is removed. Fixes: b48fe9af1e60 ("ALSA: hda/realtek: Fix headset mic for TongFang X6AR55xU") Signed-off-by: Tim Guttzeit Signed-off-by: Werner Sembach Link: https://patch.msgid.link/20260123221233.28273-1-wse@tuxedocomputers.com Signed-off-by: Takashi Iwai Signed-off-by: Greg Kroah-Hartman --- sound/hda/codecs/realtek/alc269.c | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/sound/hda/codecs/realtek/alc269.c b/sound/hda/codecs/realtek/alc269.c index ddfad56b30af53..2e9efafa732fe3 100644 --- a/sound/hda/codecs/realtek/alc269.c +++ b/sound/hda/codecs/realtek/alc269.c @@ -7232,6 +7232,7 @@ static const struct hda_quirk alc269_fixup_tbl[] = { SND_PCI_QUIRK(0x1d05, 0x1409, "TongFang GMxIXxx", ALC2XX_FIXUP_HEADSET_MIC), SND_PCI_QUIRK(0x1d05, 0x300f, "TongFang X6AR5xxY", ALC2XX_FIXUP_HEADSET_MIC), SND_PCI_QUIRK(0x1d05, 0x3019, "TongFang X6FR5xxY", ALC2XX_FIXUP_HEADSET_MIC), + SND_PCI_QUIRK(0x1d05, 0x3031, "TongFang X6AR55xU", ALC2XX_FIXUP_HEADSET_MIC), SND_PCI_QUIRK(0x1d17, 0x3288, "Haier Boyue G42", ALC269VC_FIXUP_ACER_VCOPPERBOX_PINS), SND_PCI_QUIRK(0x1d72, 0x1602, "RedmiBook", ALC255_FIXUP_XIAOMI_HEADSET_MIC), SND_PCI_QUIRK(0x1d72, 0x1701, "XiaomiNotebook Pro", ALC298_FIXUP_DELL1_MIC_NO_PRESENCE), @@ -7701,10 +7702,6 @@ static const struct snd_hda_pin_quirk alc269_pin_fixup_tbl[] = { {0x12, 0x90a60140}, {0x19, 0x04a11030}, {0x21, 0x04211020}), - SND_HDA_PIN_QUIRK(0x10ec0274, 0x1d05, "TongFang", ALC274_FIXUP_HP_HEADSET_MIC, - {0x17, 0x90170110}, - {0x19, 0x03a11030}, - {0x21, 0x03211020}), SND_HDA_PIN_QUIRK(0x10ec0282, 0x1025, "Acer", ALC282_FIXUP_ACER_DISABLE_LINEOUT, ALC282_STANDARD_PINS, {0x12, 0x90a609c0}, From ecd164120c248c2d1db3ebc54f35443b796efe29 Mon Sep 17 00:00:00 2001 From: Takashi Iwai Date: Wed, 21 Jan 2026 09:20:20 +0100 Subject: [PATCH 3151/3902] ALSA: usb-audio: Use the right limit for PCM OOB check commit 70b4db7d258118a7464f039112a74ddb49a95b06 upstream. The recent fix commit for addressing the OOB access of PCM URB data buffer caused a regression on Behringer UMC2020HD device, resulting in choppy sound. The fix used ep->max_urb_frames for the upper limit check, and this is no right value to be referred. Use the actual buffer size (ctx->buffer_size) as the upper limit instead, which also avoids the regression on the device above. Fixes: ef5749ef8b30 ("ALSA: usb-audio: Prevent excessive number of frames") Link: https://bugzilla.kernel.org/show_bug.cgi?id=220997 Link: https://patch.msgid.link/20260121082025.718748-1-tiwai@suse.de Signed-off-by: Takashi Iwai Signed-off-by: Greg Kroah-Hartman --- sound/usb/pcm.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/sound/usb/pcm.c b/sound/usb/pcm.c index 263abb36bb2d1c..682b6c1fe76bac 100644 --- a/sound/usb/pcm.c +++ b/sound/usb/pcm.c @@ -1553,7 +1553,8 @@ static int prepare_playback_urb(struct snd_usb_substream *subs, for (i = 0; i < ctx->packets; i++) { counts = snd_usb_endpoint_next_packet_size(ep, ctx, i, avail); - if (counts < 0 || frames + counts >= ep->max_urb_frames) + if (counts < 0 || + (frames + counts) * stride > ctx->buffer_size) break; /* set up descriptor */ urb->iso_frame_desc[i].offset = frames * stride; From 31b593fbece630d4af125536e13f54223a16065f Mon Sep 17 00:00:00 2001 From: Nathan Chancellor Date: Wed, 21 Jan 2026 14:36:00 -0700 Subject: [PATCH 3152/3902] riscv: Add intermediate cast to 'unsigned long' in __get_user_asm commit 841e47d56cef9b96fd2314220e3d0f1d92c719f4 upstream. After commit bdce162f2e57 ("riscv: Use 64-bit variable for output in __get_user_asm"), there is a warning when building for 32-bit RISC-V: In file included from include/linux/uaccess.h:13, from include/linux/sched/task.h:13, from include/linux/sched/signal.h:9, from include/linux/rcuwait.h:6, from include/linux/mm.h:36, from include/linux/migrate.h:5, from mm/migrate.c:16: mm/migrate.c: In function 'do_pages_move': arch/riscv/include/asm/uaccess.h:115:15: warning: cast to pointer from integer of different size [-Wint-to-pointer-cast] 115 | (x) = (__typeof__(x))__tmp; \ | ^ arch/riscv/include/asm/uaccess.h:198:17: note: in expansion of macro '__get_user_asm' 198 | __get_user_asm("lb", (x), __gu_ptr, label); \ | ^~~~~~~~~~~~~~ arch/riscv/include/asm/uaccess.h:218:9: note: in expansion of macro '__get_user_nocheck' 218 | __get_user_nocheck(x, ptr, __gu_failed); \ | ^~~~~~~~~~~~~~~~~~ arch/riscv/include/asm/uaccess.h:255:9: note: in expansion of macro '__get_user_error' 255 | __get_user_error(__gu_val, __gu_ptr, __gu_err); \ | ^~~~~~~~~~~~~~~~ arch/riscv/include/asm/uaccess.h:285:17: note: in expansion of macro '__get_user' 285 | __get_user((x), __p) : \ | ^~~~~~~~~~ mm/migrate.c:2358:29: note: in expansion of macro 'get_user' 2358 | if (get_user(p, pages + i)) | ^~~~~~~~ Add an intermediate cast to 'unsigned long', which is guaranteed to be the same width as a pointer, before the cast to the type of the output variable to clear up the warning. Fixes: bdce162f2e57 ("riscv: Use 64-bit variable for output in __get_user_asm") Reported-by: kernel test robot Closes: https://lore.kernel.org/oe-kbuild-all/202601210526.OT45dlOZ-lkp@intel.com/ Signed-off-by: Nathan Chancellor Link: https://patch.msgid.link/20260121-riscv-fix-int-to-pointer-cast-v1-1-b83eebe57c76@kernel.org Signed-off-by: Paul Walmsley Signed-off-by: Greg Kroah-Hartman --- arch/riscv/include/asm/uaccess.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/arch/riscv/include/asm/uaccess.h b/arch/riscv/include/asm/uaccess.h index 1029c31026dcfb..6aef591a6bfcb5 100644 --- a/arch/riscv/include/asm/uaccess.h +++ b/arch/riscv/include/asm/uaccess.h @@ -112,7 +112,7 @@ do { \ _ASM_EXTABLE_UACCESS_ERR(1b, %l2, %0) \ : "=&r" (__tmp) \ : "m" (*(ptr)) : : label); \ - (x) = (__typeof__(x))__tmp; \ + (x) = (__typeof__(x))(unsigned long)__tmp; \ } while (0) #else /* !CONFIG_CC_HAS_ASM_GOTO_OUTPUT */ #define __get_user_asm(insn, x, ptr, label) \ From 41cec610f690603820c80c4871dbb55bec77b9a2 Mon Sep 17 00:00:00 2001 From: Greg Kroah-Hartman Date: Wed, 11 Feb 2026 13:42:01 +0100 Subject: [PATCH 3153/3902] Linux 6.18.10 Link: https://lore.kernel.org/r/20260209142320.474120190@linuxfoundation.org Tested-by: Ronald Warsow Tested-by: Brett A C Sheffield Tested-by: Luna Jernberg Tested-by: Peter Schneider Tested-by: Hardik Garg Tested-by: Jon Hunter Tested-by: Takeshi Ogasawara Tested-by: Justin M. Forbes Tested-by: Ron Economos Tested-by: Mark Brown Tested-by: Jeffrin Jose T Tested-by: Florian Fainelli Tested-by: Dileep Malepu Tested-by: Shung-Hsi Yu Tested-by: Salvatore Bonaccorso Tested-by: Barry K. Nathan Signed-off-by: Greg Kroah-Hartman --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 64af72f16125b6..6d2269cbb0b25c 100644 --- a/Makefile +++ b/Makefile @@ -1,7 +1,7 @@ # SPDX-License-Identifier: GPL-2.0 VERSION = 6 PATCHLEVEL = 18 -SUBLEVEL = 9 +SUBLEVEL = 10 EXTRAVERSION = NAME = Baby Opossum Posse From 658743a32f72886d99b11670a2dcf9736f544e92 Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Sat, 14 Feb 2026 18:59:44 +0100 Subject: [PATCH 3154/3902] drm/asahi: workqueue: Drop completed work items This reverts commit 513cd1502fb9 ("drm/asahi: workqueue: Defer freeing the last completed work item") in asahi-6.17.9-1. Keeping the last completed work item limits the number of user space queues to at most 127 (the number of slots available for the BufferManager) since each work item keeps a slot alive. Signed-off-by: Janne Grunau --- drivers/gpu/drm/asahi/workqueue.rs | 19 +------------------ 1 file changed, 1 insertion(+), 18 deletions(-) diff --git a/drivers/gpu/drm/asahi/workqueue.rs b/drivers/gpu/drm/asahi/workqueue.rs index e3b9009fff0b79..55b6058d50224f 100644 --- a/drivers/gpu/drm/asahi/workqueue.rs +++ b/drivers/gpu/drm/asahi/workqueue.rs @@ -233,7 +233,6 @@ struct WorkQueueInner { size: u32, wptr: u32, pending: KVec>>, - last_completed_work: Option>>, last_token: Option, pending_jobs: usize, last_submitted: Option, @@ -715,7 +714,6 @@ impl WorkQueue::ver { size, wptr: 0, pending: KVec::new(), - last_completed_work: None, last_token: None, event: None, priority, @@ -909,8 +907,6 @@ impl WorkQueue for WorkQueue::ver { let last_wptr = inner.pending[completed_commands - 1].inner.wptr(); let pipe_type = inner.pipe_type; - let mut last_cmd = inner.last_completed_work.take(); - for mut cmd in inner.pending.drain(..completed_commands) { mod_pr_debug!( "WorkQueue({:?}): Queueing command @ {:?} for cleanup\n", @@ -918,13 +914,9 @@ impl WorkQueue for WorkQueue::ver { cmd.inner.gpu_va() ); cmd.as_mut().inner_mut().complete(); - if let Some(last_cmd) = last_cmd.replace(cmd) { - workqueue::system().enqueue(last_cmd); - } + workqueue::system().enqueue(cmd); } - inner.last_completed_work = last_cmd; - mod_pr_debug!( "WorkQueue({:?}): Completed {} commands, left pending {}, ls {:#x?}, lc {:#x?}\n", inner.pipe_type, @@ -1021,12 +1013,3 @@ impl WorkQueue for WorkQueue::ver { } } } - -#[versions(AGX)] -impl Drop for WorkQueueInner::ver { - fn drop(&mut self) { - if let Some(last_cmd) = self.last_completed_work.take() { - workqueue::system().enqueue(last_cmd); - } - } -} From 90a2d40d2ce14e2334a9efcd40f7b033874195af Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Sat, 14 Feb 2026 20:24:24 +0100 Subject: [PATCH 3155/3902] fixup! drm/asahi: Add the Asahi driver for Apple AGX GPUs Signed-off-by: Janne Grunau --- drivers/gpu/drm/asahi/buffer.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/gpu/drm/asahi/buffer.rs b/drivers/gpu/drm/asahi/buffer.rs index 62b12c2f2da167..309482441062d9 100644 --- a/drivers/gpu/drm/asahi/buffer.rs +++ b/drivers/gpu/drm/asahi/buffer.rs @@ -746,12 +746,12 @@ impl slotalloc::SlotItem for BufferSlotInner::ver { type Data = BufferManagerInner::ver; fn release(&mut self, data: &mut Self::Data, slot: u32) { - mod_pr_debug!("EventManager: Released slot {}\n", slot); + mod_pr_debug!("BufferManager: Released slot {}\n", slot); data.owners[slot as usize] = None; } } -/// Inner data for the event manager, to be protected by the SlotAllocator lock. +/// Inner data for the buffer manager, to be protected by the SlotAllocator lock. #[versions(AGX)] pub(crate) struct BufferManagerInner { owners: KVec>, From f02693a40e40702cb6201145379e21634dfbe20d Mon Sep 17 00:00:00 2001 From: Li Chen Date: Mon, 2 Feb 2026 22:37:53 +0800 Subject: [PATCH 3156/3902] io_uring/io-wq: add exit-on-idle state commit 38aa434ab9335ce2d178b7538cdf01d60b2014c3 upstream. io-wq uses an idle timeout to shrink the pool, but keeps the last worker around indefinitely to avoid churn. For tasks that used io_uring for file I/O and then stop using io_uring, this can leave an iou-wrk-* thread behind even after all io_uring instances are gone. This is unnecessary overhead and also gets in the way of process checkpoint/restore. Add an exit-on-idle state that makes all io-wq workers exit as soon as they become idle, and provide io_wq_set_exit_on_idle() to toggle it. Signed-off-by: Li Chen Signed-off-by: Jens Axboe Signed-off-by: Greg Kroah-Hartman --- io_uring/io-wq.c | 27 +++++++++++++++++++++++++-- io_uring/io-wq.h | 1 + 2 files changed, 26 insertions(+), 2 deletions(-) diff --git a/io_uring/io-wq.c b/io_uring/io-wq.c index 56b6a82579597a..49a9c914b4e97d 100644 --- a/io_uring/io-wq.c +++ b/io_uring/io-wq.c @@ -34,6 +34,7 @@ enum { enum { IO_WQ_BIT_EXIT = 0, /* wq exiting */ + IO_WQ_BIT_EXIT_ON_IDLE = 1, /* allow all workers to exit on idle */ }; enum { @@ -706,9 +707,13 @@ static int io_wq_worker(void *data) raw_spin_lock(&acct->workers_lock); /* * Last sleep timed out. Exit if we're not the last worker, - * or if someone modified our affinity. + * or if someone modified our affinity. If wq is marked + * idle-exit, drop the worker as well. This is used to avoid + * keeping io-wq workers around for tasks that no longer have + * any active io_uring instances. */ - if (last_timeout && (exit_mask || acct->nr_workers > 1)) { + if ((last_timeout && (exit_mask || acct->nr_workers > 1)) || + test_bit(IO_WQ_BIT_EXIT_ON_IDLE, &wq->state)) { acct->nr_workers--; raw_spin_unlock(&acct->workers_lock); __set_current_state(TASK_RUNNING); @@ -965,6 +970,24 @@ static bool io_wq_worker_wake(struct io_worker *worker, void *data) return false; } +void io_wq_set_exit_on_idle(struct io_wq *wq, bool enable) +{ + if (!wq->task) + return; + + if (!enable) { + clear_bit(IO_WQ_BIT_EXIT_ON_IDLE, &wq->state); + return; + } + + if (test_and_set_bit(IO_WQ_BIT_EXIT_ON_IDLE, &wq->state)) + return; + + rcu_read_lock(); + io_wq_for_each_worker(wq, io_wq_worker_wake, NULL); + rcu_read_unlock(); +} + static void io_run_cancel(struct io_wq_work *work, struct io_wq *wq) { do { diff --git a/io_uring/io-wq.h b/io_uring/io-wq.h index 774abab54732ef..94b14742b70328 100644 --- a/io_uring/io-wq.h +++ b/io_uring/io-wq.h @@ -41,6 +41,7 @@ struct io_wq_data { struct io_wq *io_wq_create(unsigned bounded, struct io_wq_data *data); void io_wq_exit_start(struct io_wq *wq); void io_wq_put_and_exit(struct io_wq *wq); +void io_wq_set_exit_on_idle(struct io_wq *wq, bool enable); void io_wq_enqueue(struct io_wq *wq, struct io_wq_work *work); void io_wq_hash_work(struct io_wq_work *work, void *val); From 1658b66fed206b5417cc301d67e888fd67dd130a Mon Sep 17 00:00:00 2001 From: Li Chen Date: Mon, 2 Feb 2026 22:37:54 +0800 Subject: [PATCH 3157/3902] io_uring: allow io-wq workers to exit when unused commit 91214661489467f8452d34edbf257488d85176e4 upstream. io_uring keeps a per-task io-wq around, even when the task no longer has any io_uring instances. If the task previously used io_uring for file I/O, this can leave an unrelated iou-wrk-* worker thread behind after the last io_uring instance is gone. When the last io_uring ctx is removed from the task context, mark the io-wq exit-on-idle so workers can go away. Clear the flag on subsequent io_uring usage. Signed-off-by: Li Chen Signed-off-by: Jens Axboe Signed-off-by: Greg Kroah-Hartman --- io_uring/tctx.c | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/io_uring/tctx.c b/io_uring/tctx.c index 5b66755579c08f..03c278aa58120f 100644 --- a/io_uring/tctx.c +++ b/io_uring/tctx.c @@ -122,6 +122,14 @@ int __io_uring_add_tctx_node(struct io_ring_ctx *ctx) return ret; } } + + /* + * Re-activate io-wq keepalive on any new io_uring usage. The wq may have + * been marked for idle-exit when the task temporarily had no active + * io_uring instances. + */ + if (tctx->io_wq) + io_wq_set_exit_on_idle(tctx->io_wq, false); if (!xa_load(&tctx->xa, (unsigned long)ctx)) { node = kmalloc(sizeof(*node), GFP_KERNEL); if (!node) @@ -183,6 +191,9 @@ __cold void io_uring_del_tctx_node(unsigned long index) if (tctx->last == node->ctx) tctx->last = NULL; kfree(node); + + if (xa_empty(&tctx->xa) && tctx->io_wq) + io_wq_set_exit_on_idle(tctx->io_wq, true); } __cold void io_uring_clean_tctx(struct io_uring_task *tctx) From c4b9edd55987384a1f201d3d07ff71e448d79c1b Mon Sep 17 00:00:00 2001 From: Henrique Carvalho Date: Tue, 27 Jan 2026 13:01:28 -0300 Subject: [PATCH 3158/3902] smb: client: split cached_fid bitfields to avoid shared-byte RMW races MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit commit ec306600d5ba7148c9dbf8f5a8f1f5c1a044a241 upstream. is_open, has_lease and on_list are stored in the same bitfield byte in struct cached_fid but are updated in different code paths that may run concurrently. Bitfield assignments generate byte read–modify–write operations (e.g. `orb $mask, addr` on x86_64), so updating one flag can restore stale values of the others. A possible interleaving is: CPU1: load old byte (has_lease=1, on_list=1) CPU2: clear both flags (store 0) CPU1: RMW store (old | IS_OPEN) -> reintroduces cleared bits To avoid this class of races, convert these flags to separate bool fields. Cc: stable@vger.kernel.org Fixes: ebe98f1447bbc ("cifs: enable caching of directories for which a lease is held") Signed-off-by: Henrique Carvalho Signed-off-by: Steve French Signed-off-by: Greg Kroah-Hartman --- fs/smb/client/cached_dir.h | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/fs/smb/client/cached_dir.h b/fs/smb/client/cached_dir.h index 1e383db7c33743..5091bf45345e81 100644 --- a/fs/smb/client/cached_dir.h +++ b/fs/smb/client/cached_dir.h @@ -36,10 +36,10 @@ struct cached_fid { struct list_head entry; struct cached_fids *cfids; const char *path; - bool has_lease:1; - bool is_open:1; - bool on_list:1; - bool file_all_info_is_valid:1; + bool has_lease; + bool is_open; + bool on_list; + bool file_all_info_is_valid; unsigned long time; /* jiffies of when lease was taken */ unsigned long last_access_time; /* jiffies of when last accessed */ struct kref refcount; From 71b5e7c528315ca360a1825a4ad2f8ae48c5dc16 Mon Sep 17 00:00:00 2001 From: Namjae Jeon Date: Sat, 24 Jan 2026 10:55:46 +0900 Subject: [PATCH 3159/3902] ksmbd: fix infinite loop caused by next_smb2_rcv_hdr_off reset in error paths commit 010eb01ce23b34b50531448b0da391c7f05a72af upstream. The problem occurs when a signed request fails smb2 signature verification check. In __process_request(), if check_sign_req() returns an error, set_smb2_rsp_status(work, STATUS_ACCESS_DENIED) is called. set_smb2_rsp_status() set work->next_smb2_rcv_hdr_off as zero. By resetting next_smb2_rcv_hdr_off to zero, the pointer to the next command in the chain is lost. Consequently, is_chained_smb2_message() continues to point to the same request header instead of advancing. If the header's NextCommand field is non-zero, the function returns true, causing __handle_ksmbd_work() to repeatedly process the same failed request in an infinite loop. This results in the kernel log being flooded with "bad smb2 signature" messages and high CPU usage. This patch fixes the issue by changing the return value from SERVER_HANDLER_CONTINUE to SERVER_HANDLER_ABORT. This ensures that the processing loop terminates immediately rather than attempting to continue from an invalidated offset. Reported-by: tianshuo han Cc: stable@vger.kernel.org Signed-off-by: Namjae Jeon Signed-off-by: Steve French Signed-off-by: Greg Kroah-Hartman --- fs/smb/server/server.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/fs/smb/server/server.c b/fs/smb/server/server.c index 40420544cc25a2..ef7a24e31f506f 100644 --- a/fs/smb/server/server.c +++ b/fs/smb/server/server.c @@ -126,21 +126,21 @@ static int __process_request(struct ksmbd_work *work, struct ksmbd_conn *conn, andx_again: if (command >= conn->max_cmds) { conn->ops->set_rsp_status(work, STATUS_INVALID_PARAMETER); - return SERVER_HANDLER_CONTINUE; + return SERVER_HANDLER_ABORT; } cmds = &conn->cmds[command]; if (!cmds->proc) { ksmbd_debug(SMB, "*** not implemented yet cmd = %x\n", command); conn->ops->set_rsp_status(work, STATUS_NOT_IMPLEMENTED); - return SERVER_HANDLER_CONTINUE; + return SERVER_HANDLER_ABORT; } if (work->sess && conn->ops->is_sign_req(work, command)) { ret = conn->ops->check_sign_req(work); if (!ret) { conn->ops->set_rsp_status(work, STATUS_ACCESS_DENIED); - return SERVER_HANDLER_CONTINUE; + return SERVER_HANDLER_ABORT; } } From e4a8a96a93d08570e0405cfd989a8a07e5b6ff33 Mon Sep 17 00:00:00 2001 From: Namjae Jeon Date: Mon, 9 Feb 2026 10:43:19 +0900 Subject: [PATCH 3160/3902] ksmbd: add chann_lock to protect ksmbd_chann_list xarray commit 4f3a06cc57976cafa8c6f716646be6c79a99e485 upstream. ksmbd_chann_list xarray lacks synchronization, allowing use-after-free in multi-channel sessions (between lookup_chann_list() and ksmbd_chann_del). Adds rw_semaphore chann_lock to struct ksmbd_session and protects all xa_load/xa_store/xa_erase accesses. Cc: stable@vger.kernel.org Reported-by: Igor Stepansky Signed-off-by: Namjae Jeon Signed-off-by: Steve French Signed-off-by: Greg Kroah-Hartman --- fs/smb/server/mgmt/user_session.c | 5 +++++ fs/smb/server/mgmt/user_session.h | 1 + fs/smb/server/smb2pdu.c | 12 +++++++++++- 3 files changed, 17 insertions(+), 1 deletion(-) diff --git a/fs/smb/server/mgmt/user_session.c b/fs/smb/server/mgmt/user_session.c index 7d880ff34402e0..26cb87625f1c6b 100644 --- a/fs/smb/server/mgmt/user_session.c +++ b/fs/smb/server/mgmt/user_session.c @@ -32,12 +32,14 @@ static void free_channel_list(struct ksmbd_session *sess) struct channel *chann; unsigned long index; + down_write(&sess->chann_lock); xa_for_each(&sess->ksmbd_chann_list, index, chann) { xa_erase(&sess->ksmbd_chann_list, index); kfree(chann); } xa_destroy(&sess->ksmbd_chann_list); + up_write(&sess->chann_lock); } static void __session_rpc_close(struct ksmbd_session *sess, @@ -220,7 +222,9 @@ static int ksmbd_chann_del(struct ksmbd_conn *conn, struct ksmbd_session *sess) { struct channel *chann; + down_write(&sess->chann_lock); chann = xa_erase(&sess->ksmbd_chann_list, (long)conn); + up_write(&sess->chann_lock); if (!chann) return -ENOENT; @@ -454,6 +458,7 @@ static struct ksmbd_session *__session_create(int protocol) rwlock_init(&sess->tree_conns_lock); atomic_set(&sess->refcnt, 2); init_rwsem(&sess->rpc_lock); + init_rwsem(&sess->chann_lock); ret = __init_smb2_session(sess); if (ret) diff --git a/fs/smb/server/mgmt/user_session.h b/fs/smb/server/mgmt/user_session.h index c5749d6ec7151c..cba7f688f6b577 100644 --- a/fs/smb/server/mgmt/user_session.h +++ b/fs/smb/server/mgmt/user_session.h @@ -49,6 +49,7 @@ struct ksmbd_session { char sess_key[CIFS_KEY_SIZE]; struct hlist_node hlist; + struct rw_semaphore chann_lock; struct xarray ksmbd_chann_list; struct xarray tree_conns; struct ida tree_conn_ida; diff --git a/fs/smb/server/smb2pdu.c b/fs/smb/server/smb2pdu.c index 470b274f4cc98a..bf8c4805943672 100644 --- a/fs/smb/server/smb2pdu.c +++ b/fs/smb/server/smb2pdu.c @@ -79,7 +79,13 @@ static inline bool check_session_id(struct ksmbd_conn *conn, u64 id) struct channel *lookup_chann_list(struct ksmbd_session *sess, struct ksmbd_conn *conn) { - return xa_load(&sess->ksmbd_chann_list, (long)conn); + struct channel *chann; + + down_read(&sess->chann_lock); + chann = xa_load(&sess->ksmbd_chann_list, (long)conn); + up_read(&sess->chann_lock); + + return chann; } /** @@ -1563,8 +1569,10 @@ static int ntlm_authenticate(struct ksmbd_work *work, return -ENOMEM; chann->conn = conn; + down_write(&sess->chann_lock); old = xa_store(&sess->ksmbd_chann_list, (long)conn, chann, KSMBD_DEFAULT_GFP); + up_write(&sess->chann_lock); if (xa_is_err(old)) { kfree(chann); return xa_err(old); @@ -1661,8 +1669,10 @@ static int krb5_authenticate(struct ksmbd_work *work, return -ENOMEM; chann->conn = conn; + down_write(&sess->chann_lock); old = xa_store(&sess->ksmbd_chann_list, (long)conn, chann, KSMBD_DEFAULT_GFP); + up_write(&sess->chann_lock); if (xa_is_err(old)) { kfree(chann); return xa_err(old); From cd25e0d809531a67e9dd53b19012d27d2b13425f Mon Sep 17 00:00:00 2001 From: Henrique Carvalho Date: Wed, 4 Feb 2026 20:06:43 -0300 Subject: [PATCH 3161/3902] smb: server: fix leak of active_num_conn in ksmbd_tcp_new_connection() commit 77ffbcac4e569566d0092d5f22627dfc0896b553 upstream. On kthread_run() failure in ksmbd_tcp_new_connection(), the transport is freed via free_transport(), which does not decrement active_num_conn, leaking this counter. Replace free_transport() with ksmbd_tcp_disconnect(). Fixes: 0d0d4680db22e ("ksmbd: add max connections parameter") Cc: stable@vger.kernel.org Signed-off-by: Henrique Carvalho Acked-by: Namjae Jeon Signed-off-by: Steve French Signed-off-by: Greg Kroah-Hartman --- fs/smb/server/transport_tcp.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/fs/smb/server/transport_tcp.c b/fs/smb/server/transport_tcp.c index d2e391c29464e7..48cbb04ad41a2f 100644 --- a/fs/smb/server/transport_tcp.c +++ b/fs/smb/server/transport_tcp.c @@ -41,6 +41,7 @@ static const struct ksmbd_transport_ops ksmbd_tcp_transport_ops; static void tcp_stop_kthread(struct task_struct *kthread); static struct interface *alloc_iface(char *ifname); +static void ksmbd_tcp_disconnect(struct ksmbd_transport *t); #define KSMBD_TRANS(t) (&(t)->transport) #define TCP_TRANS(t) ((struct tcp_transport *)container_of(t, \ @@ -216,7 +217,7 @@ static int ksmbd_tcp_new_connection(struct socket *client_sk) if (IS_ERR(handler)) { pr_err("cannot start conn thread\n"); rc = PTR_ERR(handler); - free_transport(t); + ksmbd_tcp_disconnect(KSMBD_TRANS(t)); } return rc; } From e811e60e1cc79923c4388146eb1fa26a7482731e Mon Sep 17 00:00:00 2001 From: Stefan Metzmacher Date: Thu, 22 Jan 2026 18:16:41 +0100 Subject: [PATCH 3162/3902] smb: smbdirect: introduce smbdirect_socket.recv_io.credits.available commit 6e3c5052f9686192e178806e017b7377155f4bab upstream. The logic off managing recv credits by counting posted recv_io and granted credits is racy. That's because the peer might already consumed a credit, but between receiving the incoming recv at the hardware and processing the completion in the 'recv_done' functions we likely have a window where we grant credits, which don't really exist. So we better have a decicated counter for the available credits, which will be incremented when we posted new recv buffers and drained when we grant the credits to the peer. Fixes: 5fb9b459b368 ("smb: client: count the number of posted recv_io messages in order to calculated credits") Fixes: 89b021a72663 ("smb: server: manage recv credits by counting posted recv_io and granted credits") Cc: # 6.18.x Cc: Steve French Cc: Tom Talpey Cc: Long Li Cc: Namjae Jeon Cc: linux-cifs@vger.kernel.org Cc: samba-technical@lists.samba.org Signed-off-by: Stefan Metzmacher Acked-by: Namjae Jeon Signed-off-by: Steve French Signed-off-by: Greg Kroah-Hartman --- fs/smb/common/smbdirect/smbdirect_socket.h | 2 ++ 1 file changed, 2 insertions(+) diff --git a/fs/smb/common/smbdirect/smbdirect_socket.h b/fs/smb/common/smbdirect/smbdirect_socket.h index 384b19177e1c37..e505dc77b70e29 100644 --- a/fs/smb/common/smbdirect/smbdirect_socket.h +++ b/fs/smb/common/smbdirect/smbdirect_socket.h @@ -231,6 +231,7 @@ struct smbdirect_socket { */ struct { u16 target; + atomic_t available; atomic_t count; } credits; @@ -375,6 +376,7 @@ static __always_inline void smbdirect_socket_init(struct smbdirect_socket *sc) INIT_WORK(&sc->recv_io.posted.refill_work, __smbdirect_socket_disabled_work); disable_work_sync(&sc->recv_io.posted.refill_work); + atomic_set(&sc->recv_io.credits.available, 0); atomic_set(&sc->recv_io.credits.count, 0); INIT_LIST_HEAD(&sc->recv_io.reassembly.list); From 88cf40f7b5fb45463d444f76028b7250f129b15e Mon Sep 17 00:00:00 2001 From: Stefan Metzmacher Date: Thu, 22 Jan 2026 18:16:42 +0100 Subject: [PATCH 3163/3902] smb: smbdirect: introduce smbdirect_socket.send_io.bcredits.* commit 8e94268b21c8235d430ce1aa6dc0b15952744b9b upstream. It turns out that our code will corrupt the stream of reassabled data transfer messages when we trigger an immendiate (empty) send. In order to fix this we'll have a single 'batch' credit per connection. And code getting that credit is free to use as much messages until remaining_length reaches 0, then the batch credit it given back and the next logical send can happen. Cc: # 6.18.x Cc: Steve French Cc: Tom Talpey Cc: Long Li Cc: Namjae Jeon Cc: linux-cifs@vger.kernel.org Cc: samba-technical@lists.samba.org Signed-off-by: Stefan Metzmacher Acked-by: Namjae Jeon Signed-off-by: Steve French Signed-off-by: Greg Kroah-Hartman --- fs/smb/common/smbdirect/smbdirect_socket.h | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/fs/smb/common/smbdirect/smbdirect_socket.h b/fs/smb/common/smbdirect/smbdirect_socket.h index e505dc77b70e29..ca22b7c2d3a7fe 100644 --- a/fs/smb/common/smbdirect/smbdirect_socket.h +++ b/fs/smb/common/smbdirect/smbdirect_socket.h @@ -154,6 +154,17 @@ struct smbdirect_socket { mempool_t *pool; } mem; + /* + * This is a coordination for smbdirect_send_batch. + * + * There's only one possible credit, which means + * only one instance is running at a time. + */ + struct { + atomic_t count; + wait_queue_head_t wait_queue; + } bcredits; + /* * The local credit state for ib_post_send() */ @@ -359,6 +370,9 @@ static __always_inline void smbdirect_socket_init(struct smbdirect_socket *sc) INIT_DELAYED_WORK(&sc->idle.timer_work, __smbdirect_socket_disabled_work); disable_delayed_work_sync(&sc->idle.timer_work); + atomic_set(&sc->send_io.bcredits.count, 0); + init_waitqueue_head(&sc->send_io.bcredits.wait_queue); + atomic_set(&sc->send_io.lcredits.count, 0); init_waitqueue_head(&sc->send_io.lcredits.wait_queue); @@ -473,6 +487,8 @@ struct smbdirect_send_batch { */ bool need_invalidate_rkey; u32 remote_key; + + int credit; }; struct smbdirect_recv_io { From 66c082e3d4651e8629a393a9e182b01eb50fb0a3 Mon Sep 17 00:00:00 2001 From: Stefan Metzmacher Date: Thu, 22 Jan 2026 18:16:43 +0100 Subject: [PATCH 3164/3902] smb: server: make use of smbdirect_socket.recv_io.credits.available commit 26ad87a2cfb8c1384620d1693a166ed87303046e upstream. The logic off managing recv credits by counting posted recv_io and granted credits is racy. That's because the peer might already consumed a credit, but between receiving the incoming recv at the hardware and processing the completion in the 'recv_done' functions we likely have a window where we grant credits, which don't really exist. So we better have a decicated counter for the available credits, which will be incremented when we posted new recv buffers and drained when we grant the credits to the peer. This fixes regression Namjae reported with the 6.18 release. Fixes: 89b021a72663 ("smb: server: manage recv credits by counting posted recv_io and granted credits") Cc: # 6.18.x Cc: Namjae Jeon Cc: Steve French Cc: Tom Talpey Cc: linux-cifs@vger.kernel.org Cc: samba-technical@lists.samba.org Signed-off-by: Stefan Metzmacher Acked-by: Namjae Jeon Signed-off-by: Steve French Signed-off-by: Greg Kroah-Hartman --- fs/smb/server/transport_rdma.c | 30 +++++++++++++++++++++++++----- 1 file changed, 25 insertions(+), 5 deletions(-) diff --git a/fs/smb/server/transport_rdma.c b/fs/smb/server/transport_rdma.c index 7d2ad73839e860..de959ca91f3ef0 100644 --- a/fs/smb/server/transport_rdma.c +++ b/fs/smb/server/transport_rdma.c @@ -926,6 +926,8 @@ static void smb_direct_post_recv_credits(struct work_struct *work) } } + atomic_add(credits, &sc->recv_io.credits.available); + if (credits) queue_work(sc->workqueue, &sc->idle.immediate_work); } @@ -972,19 +974,37 @@ static void send_done(struct ib_cq *cq, struct ib_wc *wc) static int manage_credits_prior_sending(struct smbdirect_socket *sc) { + int missing; + int available; int new_credits; if (atomic_read(&sc->recv_io.credits.count) >= sc->recv_io.credits.target) return 0; - new_credits = atomic_read(&sc->recv_io.posted.count); - if (new_credits == 0) + missing = (int)sc->recv_io.credits.target - atomic_read(&sc->recv_io.credits.count); + available = atomic_xchg(&sc->recv_io.credits.available, 0); + new_credits = (u16)min3(U16_MAX, missing, available); + if (new_credits <= 0) { + /* + * If credits are available, but not granted + * we need to re-add them again. + */ + if (available) + atomic_add(available, &sc->recv_io.credits.available); return 0; + } - new_credits -= atomic_read(&sc->recv_io.credits.count); - if (new_credits <= 0) - return 0; + if (new_credits < available) { + /* + * Readd the remaining available again. + */ + available -= new_credits; + atomic_add(available, &sc->recv_io.credits.available); + } + /* + * Remember we granted the credits + */ atomic_add(new_credits, &sc->recv_io.credits.count); return new_credits; } From cea7afb097b00fe1fb610b7d5f0538a4a1e5b1c0 Mon Sep 17 00:00:00 2001 From: Stefan Metzmacher Date: Thu, 22 Jan 2026 18:16:44 +0100 Subject: [PATCH 3165/3902] smb: server: let recv_done() queue a refill when the peer is low on credits commit 8106978d400cc88a99fb94927afe8fec7391ca3e upstream. In captures I saw that Windows was granting 191 credits in a batch when its peer posted a lot of messages. We are asking for a credit target of 255 and 191 is 252*3/4. So we also use that logic in order to fill the recv buffers available to the peer. Fixes: a7eef6144c97 ("smb: server: queue post_recv_credits_work in put_recvmsg() and avoid count_avail_recvmsg") Cc: # 6.18.x Cc: Namjae Jeon Cc: Steve French Cc: Tom Talpey Cc: linux-cifs@vger.kernel.org Cc: samba-technical@lists.samba.org Signed-off-by: Stefan Metzmacher Acked-by: Namjae Jeon Signed-off-by: Steve French Signed-off-by: Greg Kroah-Hartman --- fs/smb/server/transport_rdma.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/fs/smb/server/transport_rdma.c b/fs/smb/server/transport_rdma.c index de959ca91f3ef0..d563b1139ae8c2 100644 --- a/fs/smb/server/transport_rdma.c +++ b/fs/smb/server/transport_rdma.c @@ -661,6 +661,7 @@ static void recv_done(struct ib_cq *cq, struct ib_wc *wc) struct smbdirect_data_transfer *data_transfer = (struct smbdirect_data_transfer *)recvmsg->packet; u32 remaining_data_length, data_offset, data_length; + int current_recv_credits; u16 old_recv_credit_target; if (wc->byte_len < @@ -699,7 +700,7 @@ static void recv_done(struct ib_cq *cq, struct ib_wc *wc) } atomic_dec(&sc->recv_io.posted.count); - atomic_dec(&sc->recv_io.credits.count); + current_recv_credits = atomic_dec_return(&sc->recv_io.credits.count); old_recv_credit_target = sc->recv_io.credits.target; sc->recv_io.credits.target = @@ -719,7 +720,8 @@ static void recv_done(struct ib_cq *cq, struct ib_wc *wc) wake_up(&sc->send_io.credits.wait_queue); if (data_length) { - if (sc->recv_io.credits.target > old_recv_credit_target) + if (current_recv_credits <= (sc->recv_io.credits.target / 4) || + sc->recv_io.credits.target > old_recv_credit_target) queue_work(sc->workqueue, &sc->recv_io.posted.refill_work); enqueue_reassembly(sc, recvmsg, (int)data_length); From 5ef18a2e66f2f33fdac64437bddfb9fe6389fdc7 Mon Sep 17 00:00:00 2001 From: Stefan Metzmacher Date: Thu, 22 Jan 2026 18:16:45 +0100 Subject: [PATCH 3166/3902] smb: server: make use of smbdirect_socket.send_io.bcredits commit 34abd408c8ba24d7c97bd02ba874d8c714f49db1 upstream. It turns out that our code will corrupt the stream of reassabled data transfer messages when we trigger an immendiate (empty) send. In order to fix this we'll have a single 'batch' credit per connection. And code getting that credit is free to use as much messages until remaining_length reaches 0, then the batch credit it given back and the next logical send can happen. Cc: # 6.18.x Cc: Namjae Jeon Cc: Steve French Cc: Tom Talpey Cc: linux-cifs@vger.kernel.org Cc: samba-technical@lists.samba.org Signed-off-by: Stefan Metzmacher Acked-by: Namjae Jeon Signed-off-by: Steve French Signed-off-by: Greg Kroah-Hartman --- fs/smb/server/transport_rdma.c | 53 ++++++++++++++++++++++++++++++++-- 1 file changed, 51 insertions(+), 2 deletions(-) diff --git a/fs/smb/server/transport_rdma.c b/fs/smb/server/transport_rdma.c index d563b1139ae8c2..8ab9c2093b0f20 100644 --- a/fs/smb/server/transport_rdma.c +++ b/fs/smb/server/transport_rdma.c @@ -221,6 +221,7 @@ static void smb_direct_disconnect_wake_up_all(struct smbdirect_socket *sc) * in order to notice the broken connection. */ wake_up_all(&sc->status_wait); + wake_up_all(&sc->send_io.bcredits.wait_queue); wake_up_all(&sc->send_io.lcredits.wait_queue); wake_up_all(&sc->send_io.credits.wait_queue); wake_up_all(&sc->send_io.pending.zero_wait_queue); @@ -1050,6 +1051,7 @@ static void smb_direct_send_ctx_init(struct smbdirect_send_batch *send_ctx, send_ctx->wr_cnt = 0; send_ctx->need_invalidate_rkey = need_invalidate_rkey; send_ctx->remote_key = remote_key; + send_ctx->credit = 0; } static int smb_direct_flush_send_list(struct smbdirect_socket *sc, @@ -1057,10 +1059,10 @@ static int smb_direct_flush_send_list(struct smbdirect_socket *sc, bool is_last) { struct smbdirect_send_io *first, *last; - int ret; + int ret = 0; if (list_empty(&send_ctx->msg_list)) - return 0; + goto release_credit; first = list_first_entry(&send_ctx->msg_list, struct smbdirect_send_io, @@ -1102,6 +1104,13 @@ static int smb_direct_flush_send_list(struct smbdirect_socket *sc, smb_direct_free_sendmsg(sc, last); } +release_credit: + if (is_last && !ret && send_ctx->credit) { + atomic_add(send_ctx->credit, &sc->send_io.bcredits.count); + send_ctx->credit = 0; + wake_up(&sc->send_io.bcredits.wait_queue); + } + return ret; } @@ -1127,6 +1136,25 @@ static int wait_for_credits(struct smbdirect_socket *sc, } while (true); } +static int wait_for_send_bcredit(struct smbdirect_socket *sc, + struct smbdirect_send_batch *send_ctx) +{ + int ret; + + if (send_ctx->credit) + return 0; + + ret = wait_for_credits(sc, + &sc->send_io.bcredits.wait_queue, + &sc->send_io.bcredits.count, + 1); + if (ret) + return ret; + + send_ctx->credit = 1; + return 0; +} + static int wait_for_send_lcredit(struct smbdirect_socket *sc, struct smbdirect_send_batch *send_ctx) { @@ -1328,6 +1356,16 @@ static int smb_direct_post_send_data(struct smbdirect_socket *sc, struct smbdirect_send_io *msg; int data_length; struct scatterlist sg[SMBDIRECT_SEND_IO_MAX_SGE - 1]; + struct smbdirect_send_batch _send_ctx; + + if (!send_ctx) { + smb_direct_send_ctx_init(&_send_ctx, false, 0); + send_ctx = &_send_ctx; + } + + ret = wait_for_send_bcredit(sc, send_ctx); + if (ret) + goto bcredit_failed; ret = wait_for_send_lcredit(sc, send_ctx); if (ret) @@ -1380,6 +1418,13 @@ static int smb_direct_post_send_data(struct smbdirect_socket *sc, ret = post_sendmsg(sc, send_ctx, msg); if (ret) goto err; + + if (send_ctx == &_send_ctx) { + ret = smb_direct_flush_send_list(sc, send_ctx, true); + if (ret) + goto err; + } + return 0; err: smb_direct_free_sendmsg(sc, msg); @@ -1388,6 +1433,9 @@ static int smb_direct_post_send_data(struct smbdirect_socket *sc, credit_failed: atomic_inc(&sc->send_io.lcredits.count); lcredit_failed: + atomic_add(send_ctx->credit, &sc->send_io.bcredits.count); + send_ctx->credit = 0; +bcredit_failed: return ret; } @@ -1849,6 +1897,7 @@ static int smb_direct_send_negotiate_response(struct smbdirect_socket *sc, resp->max_fragmented_size = cpu_to_le32(sp->max_fragmented_recv_size); + atomic_set(&sc->send_io.bcredits.count, 1); sc->recv_io.expected = SMBDIRECT_EXPECT_DATA_TRANSFER; sc->status = SMBDIRECT_SOCKET_CONNECTED; } From 85bf0a73831ccca4960f3f315e9c68c72b292342 Mon Sep 17 00:00:00 2001 From: Stefan Metzmacher Date: Thu, 22 Jan 2026 18:16:46 +0100 Subject: [PATCH 3167/3902] smb: server: fix last send credit problem causing disconnects commit 8cf2bbac6281434065f5f3aeab19c9c08ff755a2 upstream. When we are about to use the last send credit that was granted to us by the peer, we need to wait until we are ourself able to grant at least one credit to the peer. Otherwise it might not be possible for the peer to grant more credits. The following sections in MS-SMBD are related to this: 3.1.5.1 Sending Upper Layer Messages ... If Connection.SendCredits is 1 and the CreditsGranted field of the message is 0, stop processing. ... 3.1.5.9 Managing Credits Prior to Sending ... If Connection.ReceiveCredits is zero, or if Connection.SendCredits is one and the Connection.SendQueue is not empty, the sender MUST allocate and post at least one receive of size Connection.MaxReceiveSize and MUST increment Connection.ReceiveCredits by the number allocated and posted. If no receives are posted, the processing MUST return a value of zero to indicate to the caller that no Send message can be currently performed. ... This problem was found by running this on Windows 2025 against ksmbd with required smb signing: 'frametest.exe -r 4k -t 20 -n 2000' after 'frametest.exe -w 4k -t 20 -n 2000'. Link: https://lore.kernel.org/linux-cifs/b58fa352-2386-4145-b42e-9b4b1d484e17@samba.org/ Cc: # 6.18.x Cc: Namjae Jeon Cc: Steve French Cc: Tom Talpey Cc: linux-cifs@vger.kernel.org Cc: samba-technical@lists.samba.org Signed-off-by: Stefan Metzmacher Acked-by: Namjae Jeon Signed-off-by: Steve French Signed-off-by: Greg Kroah-Hartman --- fs/smb/server/transport_rdma.c | 32 ++++++++++++++++++++++++++++++-- 1 file changed, 30 insertions(+), 2 deletions(-) diff --git a/fs/smb/server/transport_rdma.c b/fs/smb/server/transport_rdma.c index 8ab9c2093b0f20..21baabb1988fe9 100644 --- a/fs/smb/server/transport_rdma.c +++ b/fs/smb/server/transport_rdma.c @@ -931,6 +931,15 @@ static void smb_direct_post_recv_credits(struct work_struct *work) atomic_add(credits, &sc->recv_io.credits.available); + /* + * If the last send credit is waiting for credits + * it can grant we need to wake it up + */ + if (credits && + atomic_read(&sc->send_io.bcredits.count) == 0 && + atomic_read(&sc->send_io.credits.count) == 0) + wake_up(&sc->send_io.credits.wait_queue); + if (credits) queue_work(sc->workqueue, &sc->idle.immediate_work); } @@ -1204,6 +1213,7 @@ static int calc_rw_credits(struct smbdirect_socket *sc, static int smb_direct_create_header(struct smbdirect_socket *sc, int size, int remaining_data_length, + int new_credits, struct smbdirect_send_io **sendmsg_out) { struct smbdirect_socket_parameters *sp = &sc->parameters; @@ -1219,7 +1229,7 @@ static int smb_direct_create_header(struct smbdirect_socket *sc, /* Fill in the packet header */ packet = (struct smbdirect_data_transfer *)sendmsg->packet; packet->credits_requested = cpu_to_le16(sp->send_credit_target); - packet->credits_granted = cpu_to_le16(manage_credits_prior_sending(sc)); + packet->credits_granted = cpu_to_le16(new_credits); packet->flags = 0; if (manage_keep_alive_before_sending(sc)) @@ -1357,6 +1367,7 @@ static int smb_direct_post_send_data(struct smbdirect_socket *sc, int data_length; struct scatterlist sg[SMBDIRECT_SEND_IO_MAX_SGE - 1]; struct smbdirect_send_batch _send_ctx; + int new_credits; if (!send_ctx) { smb_direct_send_ctx_init(&_send_ctx, false, 0); @@ -1375,12 +1386,29 @@ static int smb_direct_post_send_data(struct smbdirect_socket *sc, if (ret) goto credit_failed; + new_credits = manage_credits_prior_sending(sc); + if (new_credits == 0 && + atomic_read(&sc->send_io.credits.count) == 0 && + atomic_read(&sc->recv_io.credits.count) == 0) { + queue_work(sc->workqueue, &sc->recv_io.posted.refill_work); + ret = wait_event_interruptible(sc->send_io.credits.wait_queue, + atomic_read(&sc->send_io.credits.count) >= 1 || + atomic_read(&sc->recv_io.credits.available) >= 1 || + sc->status != SMBDIRECT_SOCKET_CONNECTED); + if (sc->status != SMBDIRECT_SOCKET_CONNECTED) + ret = -ENOTCONN; + if (ret < 0) + goto credit_failed; + + new_credits = manage_credits_prior_sending(sc); + } + data_length = 0; for (i = 0; i < niov; i++) data_length += iov[i].iov_len; ret = smb_direct_create_header(sc, data_length, remaining_data_length, - &msg); + new_credits, &msg); if (ret) goto header_failed; From 24082642654f3e5149913946e89c00a297a8868f Mon Sep 17 00:00:00 2001 From: Stefan Metzmacher Date: Thu, 22 Jan 2026 18:16:47 +0100 Subject: [PATCH 3168/3902] smb: server: let send_done handle a completion without IB_SEND_SIGNALED commit 9da82dc73cb03e85d716a2609364572367a5ff47 upstream. With smbdirect_send_batch processing we likely have requests without IB_SEND_SIGNALED, which will be destroyed in the final request that has IB_SEND_SIGNALED set. If the connection is broken all requests are signaled even without explicit IB_SEND_SIGNALED. Cc: # 6.18.x Cc: Namjae Jeon Cc: Steve French Cc: Tom Talpey Cc: linux-cifs@vger.kernel.org Cc: samba-technical@lists.samba.org Signed-off-by: Stefan Metzmacher Acked-by: Namjae Jeon Signed-off-by: Steve French Signed-off-by: Greg Kroah-Hartman --- fs/smb/server/transport_rdma.c | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/fs/smb/server/transport_rdma.c b/fs/smb/server/transport_rdma.c index 21baabb1988fe9..4e74934e1f2778 100644 --- a/fs/smb/server/transport_rdma.c +++ b/fs/smb/server/transport_rdma.c @@ -957,6 +957,31 @@ static void send_done(struct ib_cq *cq, struct ib_wc *wc) ib_wc_status_msg(wc->status), wc->status, wc->opcode); + if (unlikely(!(sendmsg->wr.send_flags & IB_SEND_SIGNALED))) { + /* + * This happens when smbdirect_send_io is a sibling + * before the final message, it is signaled on + * error anyway, so we need to skip + * smbdirect_connection_free_send_io here, + * otherwise is will destroy the memory + * of the siblings too, which will cause + * use after free problems for the others + * triggered from ib_drain_qp(). + */ + if (wc->status != IB_WC_SUCCESS) + goto skip_free; + + /* + * This should not happen! + * But we better just close the + * connection... + */ + pr_err("unexpected send completion wc->status=%s (%d) wc->opcode=%d\n", + ib_wc_status_msg(wc->status), wc->status, wc->opcode); + smb_direct_disconnect_rdma_connection(sc); + return; + } + /* * Free possible siblings and then the main send_io */ @@ -970,6 +995,7 @@ static void send_done(struct ib_cq *cq, struct ib_wc *wc) lcredits += 1; if (wc->status != IB_WC_SUCCESS || wc->opcode != IB_WC_SEND) { +skip_free: pr_err("Send error. status='%s (%d)', opcode=%d\n", ib_wc_status_msg(wc->status), wc->status, wc->opcode); From f664e6e8a81103cb45c8802a9bc7499e0902c458 Mon Sep 17 00:00:00 2001 From: Stefan Metzmacher Date: Thu, 22 Jan 2026 18:16:48 +0100 Subject: [PATCH 3169/3902] smb: client: make use of smbdirect_socket.recv_io.credits.available commit 9911b1ed187a770a43950bf51f340ad4b7beecba upstream. The logic off managing recv credits by counting posted recv_io and granted credits is racy. That's because the peer might already consumed a credit, but between receiving the incoming recv at the hardware and processing the completion in the 'recv_done' functions we likely have a window where we grant credits, which don't really exist. So we better have a decicated counter for the available credits, which will be incremented when we posted new recv buffers and drained when we grant the credits to the peer. Fixes: 5fb9b459b368 ("smb: client: count the number of posted recv_io messages in order to calculated credits") Cc: # 6.18.x Cc: Steve French Cc: Tom Talpey Cc: Long Li Cc: Namjae Jeon Cc: linux-cifs@vger.kernel.org Cc: samba-technical@lists.samba.org Signed-off-by: Stefan Metzmacher Signed-off-by: Steve French Signed-off-by: Greg Kroah-Hartman --- fs/smb/client/smbdirect.c | 34 ++++++++++++++++++++++++++++------ 1 file changed, 28 insertions(+), 6 deletions(-) diff --git a/fs/smb/client/smbdirect.c b/fs/smb/client/smbdirect.c index 788a0670c4a8da..6679abbb9797c0 100644 --- a/fs/smb/client/smbdirect.c +++ b/fs/smb/client/smbdirect.c @@ -618,6 +618,7 @@ static void smbd_post_send_credits(struct work_struct *work) struct smbdirect_recv_io *response; struct smbdirect_socket *sc = container_of(work, struct smbdirect_socket, recv_io.posted.refill_work); + int posted = 0; if (sc->status != SMBDIRECT_SOCKET_CONNECTED) { return; @@ -640,9 +641,12 @@ static void smbd_post_send_credits(struct work_struct *work) } atomic_inc(&sc->recv_io.posted.count); + posted += 1; } } + atomic_add(posted, &sc->recv_io.credits.available); + /* Promptly send an immediate packet as defined in [MS-SMBD] 3.1.1.1 */ if (atomic_read(&sc->recv_io.credits.count) < sc->recv_io.credits.target - 1) { @@ -1033,19 +1037,38 @@ static int smbd_post_send_negotiate_req(struct smbdirect_socket *sc) */ static int manage_credits_prior_sending(struct smbdirect_socket *sc) { + int missing; + int available; int new_credits; if (atomic_read(&sc->recv_io.credits.count) >= sc->recv_io.credits.target) return 0; - new_credits = atomic_read(&sc->recv_io.posted.count); - if (new_credits == 0) + missing = (int)sc->recv_io.credits.target - atomic_read(&sc->recv_io.credits.count); + available = atomic_xchg(&sc->recv_io.credits.available, 0); + new_credits = (u16)min3(U16_MAX, missing, available); + if (new_credits <= 0) { + /* + * If credits are available, but not granted + * we need to re-add them again. + */ + if (available) + atomic_add(available, &sc->recv_io.credits.available); return 0; + } - new_credits -= atomic_read(&sc->recv_io.credits.count); - if (new_credits <= 0) - return 0; + if (new_credits < available) { + /* + * Readd the remaining available again. + */ + available -= new_credits; + atomic_add(available, &sc->recv_io.credits.available); + } + /* + * Remember we granted the credits + */ + atomic_add(new_credits, &sc->recv_io.credits.count); return new_credits; } @@ -1217,7 +1240,6 @@ static int smbd_post_send_iter(struct smbdirect_socket *sc, packet->credits_requested = cpu_to_le16(sp->send_credit_target); new_credits = manage_credits_prior_sending(sc); - atomic_add(new_credits, &sc->recv_io.credits.count); packet->credits_granted = cpu_to_le16(new_credits); packet->flags = 0; From 5b69ba9978dd084d8c9f397a8ecc7522d387b68e Mon Sep 17 00:00:00 2001 From: Stefan Metzmacher Date: Thu, 22 Jan 2026 18:16:49 +0100 Subject: [PATCH 3170/3902] smb: client: let recv_done() queue a refill when the peer is low on credits commit defb3c05fee94b296eebe05aaea16d2664b00252 upstream. In captures I saw that Windows was granting 191 credits in a batch when its peer posted a lot of messages. We are asking for a credit target of 255 and 191 is 252*3/4. So we also use that logic in order to fill the recv buffers available to the peer. Fixes: 02548c477a90 ("smb: client: queue post_recv_credits_work also if the peer raises the credit target") Cc: # 6.18.x Cc: Steve French Cc: Tom Talpey Cc: Long Li Cc: Namjae Jeon Cc: linux-cifs@vger.kernel.org Cc: samba-technical@lists.samba.org Signed-off-by: Stefan Metzmacher Signed-off-by: Steve French Signed-off-by: Greg Kroah-Hartman --- fs/smb/client/smbdirect.c | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/fs/smb/client/smbdirect.c b/fs/smb/client/smbdirect.c index 6679abbb9797c0..61693b4a83fcee 100644 --- a/fs/smb/client/smbdirect.c +++ b/fs/smb/client/smbdirect.c @@ -663,6 +663,7 @@ static void recv_done(struct ib_cq *cq, struct ib_wc *wc) container_of(wc->wr_cqe, struct smbdirect_recv_io, cqe); struct smbdirect_socket *sc = response->socket; struct smbdirect_socket_parameters *sp = &sc->parameters; + int current_recv_credits; u16 old_recv_credit_target; u32 data_offset = 0; u32 data_length = 0; @@ -747,7 +748,8 @@ static void recv_done(struct ib_cq *cq, struct ib_wc *wc) } atomic_dec(&sc->recv_io.posted.count); - atomic_dec(&sc->recv_io.credits.count); + current_recv_credits = atomic_dec_return(&sc->recv_io.credits.count); + old_recv_credit_target = sc->recv_io.credits.target; sc->recv_io.credits.target = le16_to_cpu(data_transfer->credits_requested); @@ -783,7 +785,8 @@ static void recv_done(struct ib_cq *cq, struct ib_wc *wc) * reassembly queue and wake up the reading thread */ if (data_length) { - if (sc->recv_io.credits.target > old_recv_credit_target) + if (current_recv_credits <= (sc->recv_io.credits.target / 4) || + sc->recv_io.credits.target > old_recv_credit_target) queue_work(sc->workqueue, &sc->recv_io.posted.refill_work); enqueue_reassembly(sc, response, data_length); From b9ec75aba3c8f5177b651bd0b171c51ba01e260b Mon Sep 17 00:00:00 2001 From: Stefan Metzmacher Date: Thu, 22 Jan 2026 18:16:50 +0100 Subject: [PATCH 3171/3902] smb: client: let smbd_post_send() make use of request->wr commit bf1656e12a9db2add716c7fb57b56967f69599fa upstream. We don't need a stack variable in addition. Cc: # 6.18.x Cc: Steve French Cc: Tom Talpey Cc: Long Li Cc: Namjae Jeon Cc: linux-cifs@vger.kernel.org Cc: samba-technical@lists.samba.org Signed-off-by: Stefan Metzmacher Signed-off-by: Steve French Signed-off-by: Greg Kroah-Hartman --- fs/smb/client/smbdirect.c | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/fs/smb/client/smbdirect.c b/fs/smb/client/smbdirect.c index 61693b4a83fcee..f2ae35a9f047f5 100644 --- a/fs/smb/client/smbdirect.c +++ b/fs/smb/client/smbdirect.c @@ -1105,7 +1105,6 @@ static int manage_keep_alive_before_sending(struct smbdirect_socket *sc) static int smbd_post_send(struct smbdirect_socket *sc, struct smbdirect_send_io *request) { - struct ib_send_wr send_wr; int rc, i; for (i = 0; i < request->num_sge; i++) { @@ -1121,14 +1120,14 @@ static int smbd_post_send(struct smbdirect_socket *sc, request->cqe.done = send_done; - send_wr.next = NULL; - send_wr.wr_cqe = &request->cqe; - send_wr.sg_list = request->sge; - send_wr.num_sge = request->num_sge; - send_wr.opcode = IB_WR_SEND; - send_wr.send_flags = IB_SEND_SIGNALED; + request->wr.next = NULL; + request->wr.wr_cqe = &request->cqe; + request->wr.sg_list = request->sge; + request->wr.num_sge = request->num_sge; + request->wr.opcode = IB_WR_SEND; + request->wr.send_flags = IB_SEND_SIGNALED; - rc = ib_post_send(sc->ib.qp, &send_wr, NULL); + rc = ib_post_send(sc->ib.qp, &request->wr, NULL); if (rc) { log_rdma_send(ERR, "ib_post_send failed rc=%d\n", rc); smbd_disconnect_rdma_connection(sc); From 2b08ca3ab6cc510baec1d30594ec7051b8a43c17 Mon Sep 17 00:00:00 2001 From: Stefan Metzmacher Date: Thu, 22 Jan 2026 18:16:51 +0100 Subject: [PATCH 3172/3902] smb: client: remove pointless sc->recv_io.credits.count rollback commit 6858531e5e8d68828eec349989cefce3f45a487f upstream. We either reach this code path before we call new_credits = manage_credits_prior_sending(sc), which means new_credits is still 0 or the connection is already broken as smbd_post_send() already called smbd_disconnect_rdma_connection(). This will also simplify further changes. Cc: # 6.18.x Cc: Steve French Cc: Tom Talpey Cc: Long Li Cc: Namjae Jeon Cc: linux-cifs@vger.kernel.org Cc: samba-technical@lists.samba.org Signed-off-by: Stefan Metzmacher Signed-off-by: Steve French Signed-off-by: Greg Kroah-Hartman --- fs/smb/client/smbdirect.c | 3 --- 1 file changed, 3 deletions(-) diff --git a/fs/smb/client/smbdirect.c b/fs/smb/client/smbdirect.c index f2ae35a9f047f5..c9fcd35e0c77aa 100644 --- a/fs/smb/client/smbdirect.c +++ b/fs/smb/client/smbdirect.c @@ -1288,9 +1288,6 @@ static int smbd_post_send_iter(struct smbdirect_socket *sc, DMA_TO_DEVICE); mempool_free(request, sc->send_io.mem.pool); - /* roll back the granted receive credits */ - atomic_sub(new_credits, &sc->recv_io.credits.count); - err_alloc: atomic_inc(&sc->send_io.credits.count); wake_up(&sc->send_io.credits.wait_queue); From 1fe0f989beb8b73b51721dc28aaf40e02f8bba55 Mon Sep 17 00:00:00 2001 From: Stefan Metzmacher Date: Thu, 22 Jan 2026 18:16:52 +0100 Subject: [PATCH 3173/3902] smb: client: remove pointless sc->send_io.pending handling in smbd_post_send_iter() commit 8bfe3fd33f36b987c8200b112646732b5f5cd8b3 upstream. If we reach this the connection is already broken as smbd_post_send() already called smbd_disconnect_rdma_connection(). This will also simplify further changes. Cc: # 6.18.x Cc: Steve French Cc: Tom Talpey Cc: Long Li Cc: Namjae Jeon Cc: linux-cifs@vger.kernel.org Cc: samba-technical@lists.samba.org Signed-off-by: Stefan Metzmacher Signed-off-by: Steve French Signed-off-by: Greg Kroah-Hartman --- fs/smb/client/smbdirect.c | 5 ----- 1 file changed, 5 deletions(-) diff --git a/fs/smb/client/smbdirect.c b/fs/smb/client/smbdirect.c index c9fcd35e0c77aa..cfbe8ce0db4220 100644 --- a/fs/smb/client/smbdirect.c +++ b/fs/smb/client/smbdirect.c @@ -1274,11 +1274,6 @@ static int smbd_post_send_iter(struct smbdirect_socket *sc, if (!rc) return 0; - if (atomic_dec_and_test(&sc->send_io.pending.count)) - wake_up(&sc->send_io.pending.zero_wait_queue); - - wake_up(&sc->send_io.pending.dec_wait_queue); - err_dma: for (i = 0; i < request->num_sge; i++) if (request->sge[i].addr) From 8786127068d511de683e57a4f3cfc95a0b75b19c Mon Sep 17 00:00:00 2001 From: Stefan Metzmacher Date: Thu, 22 Jan 2026 18:16:53 +0100 Subject: [PATCH 3174/3902] smb: client: port and use the wait_for_credits logic used by server commit bb848d205f7ac0141af52a5acb6dd116d9b71177 upstream. This simplifies the logic and prepares the use of smbdirect_send_batch in order to make sure all messages in a multi fragment send are grouped together. We'll add the smbdirect_send_batch processin in a later patch. Cc: # 6.18.x Cc: Steve French Cc: Tom Talpey Cc: Long Li Cc: Namjae Jeon Cc: linux-cifs@vger.kernel.org Cc: samba-technical@lists.samba.org Signed-off-by: Stefan Metzmacher Signed-off-by: Steve French Signed-off-by: Greg Kroah-Hartman --- fs/smb/client/smbdirect.c | 70 ++++++++++++++++++++++++--------------- 1 file changed, 43 insertions(+), 27 deletions(-) diff --git a/fs/smb/client/smbdirect.c b/fs/smb/client/smbdirect.c index cfbe8ce0db4220..405931ce3978f4 100644 --- a/fs/smb/client/smbdirect.c +++ b/fs/smb/client/smbdirect.c @@ -1137,6 +1137,44 @@ static int smbd_post_send(struct smbdirect_socket *sc, return rc; } +static int wait_for_credits(struct smbdirect_socket *sc, + wait_queue_head_t *waitq, atomic_t *total_credits, + int needed) +{ + int ret; + + do { + if (atomic_sub_return(needed, total_credits) >= 0) + return 0; + + atomic_add(needed, total_credits); + ret = wait_event_interruptible(*waitq, + atomic_read(total_credits) >= needed || + sc->status != SMBDIRECT_SOCKET_CONNECTED); + + if (sc->status != SMBDIRECT_SOCKET_CONNECTED) + return -ENOTCONN; + else if (ret < 0) + return ret; + } while (true); +} + +static int wait_for_send_lcredit(struct smbdirect_socket *sc) +{ + return wait_for_credits(sc, + &sc->send_io.lcredits.wait_queue, + &sc->send_io.lcredits.count, + 1); +} + +static int wait_for_send_credits(struct smbdirect_socket *sc) +{ + return wait_for_credits(sc, + &sc->send_io.credits.wait_queue, + &sc->send_io.credits.count, + 1); +} + static int smbd_post_send_iter(struct smbdirect_socket *sc, struct iov_iter *iter, int *_remaining_data_length) @@ -1149,41 +1187,19 @@ static int smbd_post_send_iter(struct smbdirect_socket *sc, struct smbdirect_data_transfer *packet; int new_credits = 0; -wait_lcredit: - /* Wait for local send credits */ - rc = wait_event_interruptible(sc->send_io.lcredits.wait_queue, - atomic_read(&sc->send_io.lcredits.count) > 0 || - sc->status != SMBDIRECT_SOCKET_CONNECTED); - if (rc) - goto err_wait_lcredit; - - if (sc->status != SMBDIRECT_SOCKET_CONNECTED) { - log_outgoing(ERR, "disconnected not sending on wait_credit\n"); + rc = wait_for_send_lcredit(sc); + if (rc) { + log_outgoing(ERR, "disconnected not sending on wait_lcredit\n"); rc = -EAGAIN; goto err_wait_lcredit; } - if (unlikely(atomic_dec_return(&sc->send_io.lcredits.count) < 0)) { - atomic_inc(&sc->send_io.lcredits.count); - goto wait_lcredit; - } -wait_credit: - /* Wait for send credits. A SMBD packet needs one credit */ - rc = wait_event_interruptible(sc->send_io.credits.wait_queue, - atomic_read(&sc->send_io.credits.count) > 0 || - sc->status != SMBDIRECT_SOCKET_CONNECTED); - if (rc) - goto err_wait_credit; - - if (sc->status != SMBDIRECT_SOCKET_CONNECTED) { + rc = wait_for_send_credits(sc); + if (rc) { log_outgoing(ERR, "disconnected not sending on wait_credit\n"); rc = -EAGAIN; goto err_wait_credit; } - if (unlikely(atomic_dec_return(&sc->send_io.credits.count) < 0)) { - atomic_inc(&sc->send_io.credits.count); - goto wait_credit; - } request = mempool_alloc(sc->send_io.mem.pool, GFP_KERNEL); if (!request) { From 1f3e8e2c67cbc1cea7621e74f3290b1731269de6 Mon Sep 17 00:00:00 2001 From: Stefan Metzmacher Date: Thu, 22 Jan 2026 18:16:54 +0100 Subject: [PATCH 3175/3902] smb: client: split out smbd_ib_post_send() commit bf30515caec590316e0d08208e4252eed4c160df upstream. This is like smb_direct_post_send() in the server and will simplify porting the smbdirect_send_batch and credit related logic from the server. Cc: # 6.18.x Cc: Steve French Cc: Tom Talpey Cc: Long Li Cc: Namjae Jeon Cc: linux-cifs@vger.kernel.org Cc: samba-technical@lists.samba.org Signed-off-by: Stefan Metzmacher Signed-off-by: Steve French Signed-off-by: Greg Kroah-Hartman --- fs/smb/client/smbdirect.c | 33 +++++++++++++++++---------------- 1 file changed, 17 insertions(+), 16 deletions(-) diff --git a/fs/smb/client/smbdirect.c b/fs/smb/client/smbdirect.c index 405931ce3978f4..75c0ac9cc65c73 100644 --- a/fs/smb/client/smbdirect.c +++ b/fs/smb/client/smbdirect.c @@ -1101,11 +1101,26 @@ static int manage_keep_alive_before_sending(struct smbdirect_socket *sc) return 0; } +static int smbd_ib_post_send(struct smbdirect_socket *sc, + struct ib_send_wr *wr) +{ + int ret; + + atomic_inc(&sc->send_io.pending.count); + ret = ib_post_send(sc->ib.qp, wr, NULL); + if (ret) { + pr_err("failed to post send: %d\n", ret); + smbd_disconnect_rdma_connection(sc); + ret = -EAGAIN; + } + return ret; +} + /* Post the send request */ static int smbd_post_send(struct smbdirect_socket *sc, struct smbdirect_send_io *request) { - int rc, i; + int i; for (i = 0; i < request->num_sge; i++) { log_rdma_send(INFO, @@ -1126,15 +1141,7 @@ static int smbd_post_send(struct smbdirect_socket *sc, request->wr.num_sge = request->num_sge; request->wr.opcode = IB_WR_SEND; request->wr.send_flags = IB_SEND_SIGNALED; - - rc = ib_post_send(sc->ib.qp, &request->wr, NULL); - if (rc) { - log_rdma_send(ERR, "ib_post_send failed rc=%d\n", rc); - smbd_disconnect_rdma_connection(sc); - rc = -EAGAIN; - } - - return rc; + return smbd_ib_post_send(sc, &request->wr); } static int wait_for_credits(struct smbdirect_socket *sc, @@ -1280,12 +1287,6 @@ static int smbd_post_send_iter(struct smbdirect_socket *sc, le32_to_cpu(packet->data_length), le32_to_cpu(packet->remaining_data_length)); - /* - * Now that we got a local and a remote credit - * we add us as pending - */ - atomic_inc(&sc->send_io.pending.count); - rc = smbd_post_send(sc, request); if (!rc) return 0; From d059e5fc4975598bd827ba2447db60141112bed7 Mon Sep 17 00:00:00 2001 From: Stefan Metzmacher Date: Thu, 22 Jan 2026 18:16:55 +0100 Subject: [PATCH 3176/3902] smb: client: introduce and use smbd_{alloc, free}_send_io() commit dc77da0373529d43175984b390106be2d8f03609 upstream. This is basically a copy of smb_direct_{alloc,free}_sendmsg() in the server, with just using ib_dma_unmap_page() in all cases, which is the same as ib_dma_unmap_single(). We'll use this logic in common code in future. (I basically backported it from my branch that as already has everything in common). Cc: # 6.18.x Cc: Steve French Cc: Tom Talpey Cc: Long Li Cc: Namjae Jeon Cc: linux-cifs@vger.kernel.org Cc: samba-technical@lists.samba.org Signed-off-by: Stefan Metzmacher Signed-off-by: Steve French Signed-off-by: Greg Kroah-Hartman --- fs/smb/client/smbdirect.c | 87 ++++++++++++++++++++++++++------------- 1 file changed, 58 insertions(+), 29 deletions(-) diff --git a/fs/smb/client/smbdirect.c b/fs/smb/client/smbdirect.c index 75c0ac9cc65c73..6cb40da7e5897c 100644 --- a/fs/smb/client/smbdirect.c +++ b/fs/smb/client/smbdirect.c @@ -493,10 +493,54 @@ static inline void *smbdirect_recv_io_payload(struct smbdirect_recv_io *response return (void *)response->packet; } +static struct smbdirect_send_io *smbd_alloc_send_io(struct smbdirect_socket *sc) +{ + struct smbdirect_send_io *msg; + + msg = mempool_alloc(sc->send_io.mem.pool, GFP_KERNEL); + if (!msg) + return ERR_PTR(-ENOMEM); + msg->socket = sc; + INIT_LIST_HEAD(&msg->sibling_list); + msg->num_sge = 0; + + return msg; +} + +static void smbd_free_send_io(struct smbdirect_send_io *msg) +{ + struct smbdirect_socket *sc = msg->socket; + size_t i; + + /* + * The list needs to be empty! + * The caller should take care of it. + */ + WARN_ON_ONCE(!list_empty(&msg->sibling_list)); + + /* + * Note we call ib_dma_unmap_page(), even if some sges are mapped using + * ib_dma_map_single(). + * + * The difference between _single() and _page() only matters for the + * ib_dma_map_*() case. + * + * For the ib_dma_unmap_*() case it does not matter as both take the + * dma_addr_t and dma_unmap_single_attrs() is just an alias to + * dma_unmap_page_attrs(). + */ + for (i = 0; i < msg->num_sge; i++) + ib_dma_unmap_page(sc->ib.dev, + msg->sge[i].addr, + msg->sge[i].length, + DMA_TO_DEVICE); + + mempool_free(msg, sc->send_io.mem.pool); +} + /* Called when a RDMA send is done */ static void send_done(struct ib_cq *cq, struct ib_wc *wc) { - int i; struct smbdirect_send_io *request = container_of(wc->wr_cqe, struct smbdirect_send_io, cqe); struct smbdirect_socket *sc = request->socket; @@ -505,12 +549,8 @@ static void send_done(struct ib_cq *cq, struct ib_wc *wc) log_rdma_send(INFO, "smbdirect_send_io 0x%p completed wc->status=%s\n", request, ib_wc_status_msg(wc->status)); - for (i = 0; i < request->num_sge; i++) - ib_dma_unmap_single(sc->ib.dev, - request->sge[i].addr, - request->sge[i].length, - DMA_TO_DEVICE); - mempool_free(request, sc->send_io.mem.pool); + /* Note this frees wc->wr_cqe, but not wc */ + smbd_free_send_io(request); lcredits += 1; if (wc->status != IB_WC_SUCCESS || wc->opcode != IB_WC_SEND) { @@ -963,15 +1003,13 @@ static int smbd_post_send_negotiate_req(struct smbdirect_socket *sc) { struct smbdirect_socket_parameters *sp = &sc->parameters; struct ib_send_wr send_wr; - int rc = -ENOMEM; + int rc; struct smbdirect_send_io *request; struct smbdirect_negotiate_req *packet; - request = mempool_alloc(sc->send_io.mem.pool, GFP_KERNEL); - if (!request) - return rc; - - request->socket = sc; + request = smbd_alloc_send_io(sc); + if (IS_ERR(request)) + return PTR_ERR(request); packet = smbdirect_send_io_payload(request); packet->min_version = cpu_to_le16(SMBDIRECT_V1); @@ -983,7 +1021,6 @@ static int smbd_post_send_negotiate_req(struct smbdirect_socket *sc) packet->max_fragmented_size = cpu_to_le32(sp->max_fragmented_recv_size); - request->num_sge = 1; request->sge[0].addr = ib_dma_map_single( sc->ib.dev, (void *)packet, sizeof(*packet), DMA_TO_DEVICE); @@ -991,6 +1028,7 @@ static int smbd_post_send_negotiate_req(struct smbdirect_socket *sc) rc = -EIO; goto dma_mapping_failed; } + request->num_sge = 1; request->sge[0].length = sizeof(*packet); request->sge[0].lkey = sc->ib.pd->local_dma_lkey; @@ -1020,13 +1058,11 @@ static int smbd_post_send_negotiate_req(struct smbdirect_socket *sc) /* if we reach here, post send failed */ log_rdma_send(ERR, "ib_post_send failed rc=%d\n", rc); atomic_dec(&sc->send_io.pending.count); - ib_dma_unmap_single(sc->ib.dev, request->sge[0].addr, - request->sge[0].length, DMA_TO_DEVICE); smbd_disconnect_rdma_connection(sc); dma_mapping_failed: - mempool_free(request, sc->send_io.mem.pool); + smbd_free_send_io(request); return rc; } @@ -1187,7 +1223,7 @@ static int smbd_post_send_iter(struct smbdirect_socket *sc, int *_remaining_data_length) { struct smbdirect_socket_parameters *sp = &sc->parameters; - int i, rc; + int rc; int header_length; int data_length; struct smbdirect_send_io *request; @@ -1208,13 +1244,12 @@ static int smbd_post_send_iter(struct smbdirect_socket *sc, goto err_wait_credit; } - request = mempool_alloc(sc->send_io.mem.pool, GFP_KERNEL); - if (!request) { - rc = -ENOMEM; + request = smbd_alloc_send_io(sc); + if (IS_ERR(request)) { + rc = PTR_ERR(request); goto err_alloc; } - request->socket = sc; memset(request->sge, 0, sizeof(request->sge)); /* Map the packet to DMA */ @@ -1292,13 +1327,7 @@ static int smbd_post_send_iter(struct smbdirect_socket *sc, return 0; err_dma: - for (i = 0; i < request->num_sge; i++) - if (request->sge[i].addr) - ib_dma_unmap_single(sc->ib.dev, - request->sge[i].addr, - request->sge[i].length, - DMA_TO_DEVICE); - mempool_free(request, sc->send_io.mem.pool); + smbd_free_send_io(request); err_alloc: atomic_inc(&sc->send_io.credits.count); From 9eff83600edf6957302816d6932389c8d1b6aa76 Mon Sep 17 00:00:00 2001 From: Stefan Metzmacher Date: Thu, 22 Jan 2026 18:16:56 +0100 Subject: [PATCH 3177/3902] smb: client: use smbdirect_send_batch processing commit 2c1ac39ce9cd4112f406775c626eef7f3eb4c481 upstream. This will allow us to use similar logic as we have in the server soon, so that we can share common code later. Cc: # 6.18.x Cc: Steve French Cc: Tom Talpey Cc: Long Li Cc: Namjae Jeon Cc: linux-cifs@vger.kernel.org Cc: samba-technical@lists.samba.org Signed-off-by: Stefan Metzmacher Signed-off-by: Steve French Signed-off-by: Greg Kroah-Hartman --- fs/smb/client/smbdirect.c | 149 ++++++++++++++++++++++++++++++++++---- 1 file changed, 135 insertions(+), 14 deletions(-) diff --git a/fs/smb/client/smbdirect.c b/fs/smb/client/smbdirect.c index 6cb40da7e5897c..ef3b237bccc132 100644 --- a/fs/smb/client/smbdirect.c +++ b/fs/smb/client/smbdirect.c @@ -544,11 +544,20 @@ static void send_done(struct ib_cq *cq, struct ib_wc *wc) struct smbdirect_send_io *request = container_of(wc->wr_cqe, struct smbdirect_send_io, cqe); struct smbdirect_socket *sc = request->socket; + struct smbdirect_send_io *sibling, *next; int lcredits = 0; log_rdma_send(INFO, "smbdirect_send_io 0x%p completed wc->status=%s\n", request, ib_wc_status_msg(wc->status)); + /* + * Free possible siblings and then the main send_io + */ + list_for_each_entry_safe(sibling, next, &request->sibling_list, sibling_list) { + list_del_init(&sibling->sibling_list); + smbd_free_send_io(sibling); + lcredits += 1; + } /* Note this frees wc->wr_cqe, but not wc */ smbd_free_send_io(request); lcredits += 1; @@ -1154,7 +1163,8 @@ static int smbd_ib_post_send(struct smbdirect_socket *sc, /* Post the send request */ static int smbd_post_send(struct smbdirect_socket *sc, - struct smbdirect_send_io *request) + struct smbdirect_send_batch *batch, + struct smbdirect_send_io *request) { int i; @@ -1170,16 +1180,95 @@ static int smbd_post_send(struct smbdirect_socket *sc, } request->cqe.done = send_done; - request->wr.next = NULL; - request->wr.wr_cqe = &request->cqe; request->wr.sg_list = request->sge; request->wr.num_sge = request->num_sge; request->wr.opcode = IB_WR_SEND; + + if (batch) { + request->wr.wr_cqe = NULL; + request->wr.send_flags = 0; + if (!list_empty(&batch->msg_list)) { + struct smbdirect_send_io *last; + + last = list_last_entry(&batch->msg_list, + struct smbdirect_send_io, + sibling_list); + last->wr.next = &request->wr; + } + list_add_tail(&request->sibling_list, &batch->msg_list); + batch->wr_cnt++; + return 0; + } + + request->wr.wr_cqe = &request->cqe; request->wr.send_flags = IB_SEND_SIGNALED; return smbd_ib_post_send(sc, &request->wr); } +static void smbd_send_batch_init(struct smbdirect_send_batch *batch, + bool need_invalidate_rkey, + unsigned int remote_key) +{ + INIT_LIST_HEAD(&batch->msg_list); + batch->wr_cnt = 0; + batch->need_invalidate_rkey = need_invalidate_rkey; + batch->remote_key = remote_key; +} + +static int smbd_send_batch_flush(struct smbdirect_socket *sc, + struct smbdirect_send_batch *batch, + bool is_last) +{ + struct smbdirect_send_io *first, *last; + int ret = 0; + + if (list_empty(&batch->msg_list)) + return 0; + + first = list_first_entry(&batch->msg_list, + struct smbdirect_send_io, + sibling_list); + last = list_last_entry(&batch->msg_list, + struct smbdirect_send_io, + sibling_list); + + if (batch->need_invalidate_rkey) { + first->wr.opcode = IB_WR_SEND_WITH_INV; + first->wr.ex.invalidate_rkey = batch->remote_key; + batch->need_invalidate_rkey = false; + batch->remote_key = 0; + } + + last->wr.send_flags = IB_SEND_SIGNALED; + last->wr.wr_cqe = &last->cqe; + + /* + * Remove last from batch->msg_list + * and splice the rest of batch->msg_list + * to last->sibling_list. + * + * batch->msg_list is a valid empty list + * at the end. + */ + list_del_init(&last->sibling_list); + list_splice_tail_init(&batch->msg_list, &last->sibling_list); + batch->wr_cnt = 0; + + ret = smbd_ib_post_send(sc, &first->wr); + if (ret) { + struct smbdirect_send_io *sibling, *next; + + list_for_each_entry_safe(sibling, next, &last->sibling_list, sibling_list) { + list_del_init(&sibling->sibling_list); + smbd_free_send_io(sibling); + } + smbd_free_send_io(last); + } + + return ret; +} + static int wait_for_credits(struct smbdirect_socket *sc, wait_queue_head_t *waitq, atomic_t *total_credits, int needed) @@ -1202,16 +1291,35 @@ static int wait_for_credits(struct smbdirect_socket *sc, } while (true); } -static int wait_for_send_lcredit(struct smbdirect_socket *sc) +static int wait_for_send_lcredit(struct smbdirect_socket *sc, + struct smbdirect_send_batch *batch) { + if (batch && (atomic_read(&sc->send_io.lcredits.count) <= 1)) { + int ret; + + ret = smbd_send_batch_flush(sc, batch, false); + if (ret) + return ret; + } + return wait_for_credits(sc, &sc->send_io.lcredits.wait_queue, &sc->send_io.lcredits.count, 1); } -static int wait_for_send_credits(struct smbdirect_socket *sc) +static int wait_for_send_credits(struct smbdirect_socket *sc, + struct smbdirect_send_batch *batch) { + if (batch && + (batch->wr_cnt >= 16 || atomic_read(&sc->send_io.credits.count) <= 1)) { + int ret; + + ret = smbd_send_batch_flush(sc, batch, false); + if (ret) + return ret; + } + return wait_for_credits(sc, &sc->send_io.credits.wait_queue, &sc->send_io.credits.count, @@ -1219,6 +1327,7 @@ static int wait_for_send_credits(struct smbdirect_socket *sc) } static int smbd_post_send_iter(struct smbdirect_socket *sc, + struct smbdirect_send_batch *batch, struct iov_iter *iter, int *_remaining_data_length) { @@ -1230,14 +1339,14 @@ static int smbd_post_send_iter(struct smbdirect_socket *sc, struct smbdirect_data_transfer *packet; int new_credits = 0; - rc = wait_for_send_lcredit(sc); + rc = wait_for_send_lcredit(sc, batch); if (rc) { log_outgoing(ERR, "disconnected not sending on wait_lcredit\n"); rc = -EAGAIN; goto err_wait_lcredit; } - rc = wait_for_send_credits(sc); + rc = wait_for_send_credits(sc, batch); if (rc) { log_outgoing(ERR, "disconnected not sending on wait_credit\n"); rc = -EAGAIN; @@ -1322,7 +1431,7 @@ static int smbd_post_send_iter(struct smbdirect_socket *sc, le32_to_cpu(packet->data_length), le32_to_cpu(packet->remaining_data_length)); - rc = smbd_post_send(sc, request); + rc = smbd_post_send(sc, batch, request); if (!rc) return 0; @@ -1351,10 +1460,11 @@ static int smbd_post_send_empty(struct smbdirect_socket *sc) int remaining_data_length = 0; sc->statistics.send_empty++; - return smbd_post_send_iter(sc, NULL, &remaining_data_length); + return smbd_post_send_iter(sc, NULL, NULL, &remaining_data_length); } static int smbd_post_send_full_iter(struct smbdirect_socket *sc, + struct smbdirect_send_batch *batch, struct iov_iter *iter, int *_remaining_data_length) { @@ -1367,7 +1477,7 @@ static int smbd_post_send_full_iter(struct smbdirect_socket *sc, */ while (iov_iter_count(iter) > 0) { - rc = smbd_post_send_iter(sc, iter, _remaining_data_length); + rc = smbd_post_send_iter(sc, batch, iter, _remaining_data_length); if (rc < 0) break; } @@ -2289,8 +2399,10 @@ int smbd_send(struct TCP_Server_Info *server, struct smbdirect_socket_parameters *sp = &sc->parameters; struct smb_rqst *rqst; struct iov_iter iter; + struct smbdirect_send_batch batch; unsigned int remaining_data_length, klen; int rc, i, rqst_idx; + int error = 0; if (sc->status != SMBDIRECT_SOCKET_CONNECTED) return -EAGAIN; @@ -2315,6 +2427,7 @@ int smbd_send(struct TCP_Server_Info *server, num_rqst, remaining_data_length); rqst_idx = 0; + smbd_send_batch_init(&batch, false, 0); do { rqst = &rqst_array[rqst_idx]; @@ -2333,20 +2446,28 @@ int smbd_send(struct TCP_Server_Info *server, klen += rqst->rq_iov[i].iov_len; iov_iter_kvec(&iter, ITER_SOURCE, rqst->rq_iov, rqst->rq_nvec, klen); - rc = smbd_post_send_full_iter(sc, &iter, &remaining_data_length); - if (rc < 0) + rc = smbd_post_send_full_iter(sc, &batch, &iter, &remaining_data_length); + if (rc < 0) { + error = rc; break; + } if (iov_iter_count(&rqst->rq_iter) > 0) { /* And then the data pages if there are any */ - rc = smbd_post_send_full_iter(sc, &rqst->rq_iter, + rc = smbd_post_send_full_iter(sc, &batch, &rqst->rq_iter, &remaining_data_length); - if (rc < 0) + if (rc < 0) { + error = rc; break; + } } } while (++rqst_idx < num_rqst); + rc = smbd_send_batch_flush(sc, &batch, true); + if (unlikely(!rc && error)) + rc = error; + /* * As an optimization, we don't wait for individual I/O to finish * before sending the next one. From cca0526ef2344cab6944d7f441fc24e152da031b Mon Sep 17 00:00:00 2001 From: Stefan Metzmacher Date: Thu, 22 Jan 2026 18:16:57 +0100 Subject: [PATCH 3178/3902] smb: client: make use of smbdirect_socket.send_io.bcredits commit 21538121efe6c8c5b51c742fa02cbe820bc48714 upstream. It turns out that our code will corrupt the stream of reassabled data transfer messages when we trigger an immendiate (empty) send. In order to fix this we'll have a single 'batch' credit per connection. And code getting that credit is free to use as much messages until remaining_length reaches 0, then the batch credit it given back and the next logical send can happen. Cc: # 6.18.x Cc: Steve French Cc: Tom Talpey Cc: Long Li Cc: Namjae Jeon Cc: linux-cifs@vger.kernel.org Cc: samba-technical@lists.samba.org Signed-off-by: Stefan Metzmacher Signed-off-by: Steve French Signed-off-by: Greg Kroah-Hartman --- fs/smb/client/smbdirect.c | 58 +++++++++++++++++++++++++++++++++++++-- 1 file changed, 55 insertions(+), 3 deletions(-) diff --git a/fs/smb/client/smbdirect.c b/fs/smb/client/smbdirect.c index ef3b237bccc132..dbb2d939bc44d7 100644 --- a/fs/smb/client/smbdirect.c +++ b/fs/smb/client/smbdirect.c @@ -657,6 +657,7 @@ static bool process_negotiation_response( sp->max_frmr_depth * PAGE_SIZE); sp->max_frmr_depth = sp->max_read_write_size / PAGE_SIZE; + atomic_set(&sc->send_io.bcredits.count, 1); sc->recv_io.expected = SMBDIRECT_EXPECT_DATA_TRANSFER; return true; } @@ -1214,6 +1215,7 @@ static void smbd_send_batch_init(struct smbdirect_send_batch *batch, batch->wr_cnt = 0; batch->need_invalidate_rkey = need_invalidate_rkey; batch->remote_key = remote_key; + batch->credit = 0; } static int smbd_send_batch_flush(struct smbdirect_socket *sc, @@ -1224,7 +1226,7 @@ static int smbd_send_batch_flush(struct smbdirect_socket *sc, int ret = 0; if (list_empty(&batch->msg_list)) - return 0; + goto release_credit; first = list_first_entry(&batch->msg_list, struct smbdirect_send_io, @@ -1266,6 +1268,13 @@ static int smbd_send_batch_flush(struct smbdirect_socket *sc, smbd_free_send_io(last); } +release_credit: + if (is_last && !ret && batch->credit) { + atomic_add(batch->credit, &sc->send_io.bcredits.count); + batch->credit = 0; + wake_up(&sc->send_io.bcredits.wait_queue); + } + return ret; } @@ -1291,6 +1300,25 @@ static int wait_for_credits(struct smbdirect_socket *sc, } while (true); } +static int wait_for_send_bcredit(struct smbdirect_socket *sc, + struct smbdirect_send_batch *batch) +{ + int ret; + + if (batch->credit) + return 0; + + ret = wait_for_credits(sc, + &sc->send_io.bcredits.wait_queue, + &sc->send_io.bcredits.count, + 1); + if (ret) + return ret; + + batch->credit = 1; + return 0; +} + static int wait_for_send_lcredit(struct smbdirect_socket *sc, struct smbdirect_send_batch *batch) { @@ -1338,6 +1366,19 @@ static int smbd_post_send_iter(struct smbdirect_socket *sc, struct smbdirect_send_io *request; struct smbdirect_data_transfer *packet; int new_credits = 0; + struct smbdirect_send_batch _batch; + + if (!batch) { + smbd_send_batch_init(&_batch, false, 0); + batch = &_batch; + } + + rc = wait_for_send_bcredit(sc, batch); + if (rc) { + log_outgoing(ERR, "disconnected not sending on wait_bcredit\n"); + rc = -EAGAIN; + goto err_wait_bcredit; + } rc = wait_for_send_lcredit(sc, batch); if (rc) { @@ -1432,8 +1473,14 @@ static int smbd_post_send_iter(struct smbdirect_socket *sc, le32_to_cpu(packet->remaining_data_length)); rc = smbd_post_send(sc, batch, request); - if (!rc) - return 0; + if (!rc) { + if (batch != &_batch) + return 0; + + rc = smbd_send_batch_flush(sc, batch, true); + if (!rc) + return 0; + } err_dma: smbd_free_send_io(request); @@ -1447,6 +1494,11 @@ static int smbd_post_send_iter(struct smbdirect_socket *sc, wake_up(&sc->send_io.lcredits.wait_queue); err_wait_lcredit: + atomic_add(batch->credit, &sc->send_io.bcredits.count); + batch->credit = 0; + wake_up(&sc->send_io.bcredits.wait_queue); + +err_wait_bcredit: return rc; } From 69ce4ae2ab65c01df74355afb5e332ac4223b816 Mon Sep 17 00:00:00 2001 From: Stefan Metzmacher Date: Thu, 22 Jan 2026 18:16:58 +0100 Subject: [PATCH 3179/3902] smb: client: fix last send credit problem causing disconnects commit 93ac432274e1361b4f6cd69e7c5d9b3ac21e13f5 upstream. When we are about to use the last send credit that was granted to us by the peer, we need to wait until we are ourself able to grant at least one credit to the peer. Otherwise it might not be possible for the peer to grant more credits. The following sections in MS-SMBD are related to this: 3.1.5.1 Sending Upper Layer Messages ... If Connection.SendCredits is 1 and the CreditsGranted field of the message is 0, stop processing. ... 3.1.5.9 Managing Credits Prior to Sending ... If Connection.ReceiveCredits is zero, or if Connection.SendCredits is one and the Connection.SendQueue is not empty, the sender MUST allocate and post at least one receive of size Connection.MaxReceiveSize and MUST increment Connection.ReceiveCredits by the number allocated and posted. If no receives are posted, the processing MUST return a value of zero to indicate to the caller that no Send message can be currently performed. ... This is a similar logic as we have in the server. Cc: # 6.18.x Cc: Steve French Cc: Tom Talpey Cc: Long Li Cc: Namjae Jeon Cc: linux-cifs@vger.kernel.org Cc: samba-technical@lists.samba.org Signed-off-by: Stefan Metzmacher Signed-off-by: Steve French Signed-off-by: Greg Kroah-Hartman --- fs/smb/client/smbdirect.c | 31 +++++++++++++++++++++++++++++-- 1 file changed, 29 insertions(+), 2 deletions(-) diff --git a/fs/smb/client/smbdirect.c b/fs/smb/client/smbdirect.c index dbb2d939bc44d7..20faa6d7f514de 100644 --- a/fs/smb/client/smbdirect.c +++ b/fs/smb/client/smbdirect.c @@ -697,6 +697,15 @@ static void smbd_post_send_credits(struct work_struct *work) atomic_add(posted, &sc->recv_io.credits.available); + /* + * If the last send credit is waiting for credits + * it can grant we need to wake it up + */ + if (posted && + atomic_read(&sc->send_io.bcredits.count) == 0 && + atomic_read(&sc->send_io.credits.count) == 0) + wake_up(&sc->send_io.credits.wait_queue); + /* Promptly send an immediate packet as defined in [MS-SMBD] 3.1.1.1 */ if (atomic_read(&sc->recv_io.credits.count) < sc->recv_io.credits.target - 1) { @@ -1394,6 +1403,26 @@ static int smbd_post_send_iter(struct smbdirect_socket *sc, goto err_wait_credit; } + new_credits = manage_credits_prior_sending(sc); + if (new_credits == 0 && + atomic_read(&sc->send_io.credits.count) == 0 && + atomic_read(&sc->recv_io.credits.count) == 0) { + queue_work(sc->workqueue, &sc->recv_io.posted.refill_work); + rc = wait_event_interruptible(sc->send_io.credits.wait_queue, + atomic_read(&sc->send_io.credits.count) >= 1 || + atomic_read(&sc->recv_io.credits.available) >= 1 || + sc->status != SMBDIRECT_SOCKET_CONNECTED); + if (sc->status != SMBDIRECT_SOCKET_CONNECTED) + rc = -ENOTCONN; + if (rc < 0) { + log_outgoing(ERR, "disconnected not sending on last credit\n"); + rc = -EAGAIN; + goto err_wait_credit; + } + + new_credits = manage_credits_prior_sending(sc); + } + request = smbd_alloc_send_io(sc); if (IS_ERR(request)) { rc = PTR_ERR(request); @@ -1448,8 +1477,6 @@ static int smbd_post_send_iter(struct smbdirect_socket *sc, /* Fill in the packet header */ packet->credits_requested = cpu_to_le16(sp->send_credit_target); - - new_credits = manage_credits_prior_sending(sc); packet->credits_granted = cpu_to_le16(new_credits); packet->flags = 0; From 6bf260ace7301a4557a8155f562e13b9e0c808c8 Mon Sep 17 00:00:00 2001 From: Stefan Metzmacher Date: Thu, 22 Jan 2026 18:16:59 +0100 Subject: [PATCH 3180/3902] smb: client: let smbd_post_send_negotiate_req() use smbd_post_send() commit 5b1c6149657af840a02885135c700ab42e6aa322 upstream. The server has similar logic and it makes sure that request->wr is used instead of a stack struct ib_send_wr send_wr. This makes sure send_done can see request->wr.send_flags as the next commit will check for IB_SEND_SIGNALED Cc: # 6.18.x Cc: Steve French Cc: Tom Talpey Cc: Long Li Cc: Namjae Jeon Cc: linux-cifs@vger.kernel.org Cc: samba-technical@lists.samba.org Signed-off-by: Stefan Metzmacher Signed-off-by: Steve French Signed-off-by: Greg Kroah-Hartman --- fs/smb/client/smbdirect.c | 32 +++++++------------------------- 1 file changed, 7 insertions(+), 25 deletions(-) diff --git a/fs/smb/client/smbdirect.c b/fs/smb/client/smbdirect.c index 20faa6d7f514de..88fefb901c27f6 100644 --- a/fs/smb/client/smbdirect.c +++ b/fs/smb/client/smbdirect.c @@ -35,6 +35,10 @@ static void enqueue_reassembly( static struct smbdirect_recv_io *_get_first_reassembly( struct smbdirect_socket *sc); +static int smbd_post_send(struct smbdirect_socket *sc, + struct smbdirect_send_batch *batch, + struct smbdirect_send_io *request); + static int smbd_post_recv( struct smbdirect_socket *sc, struct smbdirect_recv_io *response); @@ -1021,7 +1025,6 @@ static int smbd_ia_open( static int smbd_post_send_negotiate_req(struct smbdirect_socket *sc) { struct smbdirect_socket_parameters *sp = &sc->parameters; - struct ib_send_wr send_wr; int rc; struct smbdirect_send_io *request; struct smbdirect_negotiate_req *packet; @@ -1052,33 +1055,12 @@ static int smbd_post_send_negotiate_req(struct smbdirect_socket *sc) request->sge[0].length = sizeof(*packet); request->sge[0].lkey = sc->ib.pd->local_dma_lkey; - ib_dma_sync_single_for_device( - sc->ib.dev, request->sge[0].addr, - request->sge[0].length, DMA_TO_DEVICE); - - request->cqe.done = send_done; - - send_wr.next = NULL; - send_wr.wr_cqe = &request->cqe; - send_wr.sg_list = request->sge; - send_wr.num_sge = request->num_sge; - send_wr.opcode = IB_WR_SEND; - send_wr.send_flags = IB_SEND_SIGNALED; - - log_rdma_send(INFO, "sge addr=0x%llx length=%u lkey=0x%x\n", - request->sge[0].addr, - request->sge[0].length, request->sge[0].lkey); - - atomic_inc(&sc->send_io.pending.count); - rc = ib_post_send(sc->ib.qp, &send_wr, NULL); + rc = smbd_post_send(sc, NULL, request); if (!rc) return 0; - /* if we reach here, post send failed */ - log_rdma_send(ERR, "ib_post_send failed rc=%d\n", rc); - atomic_dec(&sc->send_io.pending.count); - - smbd_disconnect_rdma_connection(sc); + if (rc == -EAGAIN) + rc = -EIO; dma_mapping_failed: smbd_free_send_io(request); From 16c8be3d55441287ddd334e25df4cc376450dec9 Mon Sep 17 00:00:00 2001 From: Stefan Metzmacher Date: Thu, 22 Jan 2026 18:17:00 +0100 Subject: [PATCH 3181/3902] smb: client: let send_done handle a completion without IB_SEND_SIGNALED commit cf74fcdc43b322b6188a0750b5ee79e38be6d078 upstream. With smbdirect_send_batch processing we likely have requests without IB_SEND_SIGNALED, which will be destroyed in the final request that has IB_SEND_SIGNALED set. If the connection is broken all requests are signaled even without explicit IB_SEND_SIGNALED. Cc: # 6.18.x Cc: Steve French Cc: Tom Talpey Cc: Long Li Cc: Namjae Jeon Cc: linux-cifs@vger.kernel.org Cc: samba-technical@lists.samba.org Signed-off-by: Stefan Metzmacher Signed-off-by: Steve French Signed-off-by: Greg Kroah-Hartman --- fs/smb/client/smbdirect.c | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/fs/smb/client/smbdirect.c b/fs/smb/client/smbdirect.c index 88fefb901c27f6..01d55bcc6d0f9c 100644 --- a/fs/smb/client/smbdirect.c +++ b/fs/smb/client/smbdirect.c @@ -554,6 +554,32 @@ static void send_done(struct ib_cq *cq, struct ib_wc *wc) log_rdma_send(INFO, "smbdirect_send_io 0x%p completed wc->status=%s\n", request, ib_wc_status_msg(wc->status)); + if (unlikely(!(request->wr.send_flags & IB_SEND_SIGNALED))) { + /* + * This happens when smbdirect_send_io is a sibling + * before the final message, it is signaled on + * error anyway, so we need to skip + * smbdirect_connection_free_send_io here, + * otherwise is will destroy the memory + * of the siblings too, which will cause + * use after free problems for the others + * triggered from ib_drain_qp(). + */ + if (wc->status != IB_WC_SUCCESS) + goto skip_free; + + /* + * This should not happen! + * But we better just close the + * connection... + */ + log_rdma_send(ERR, + "unexpected send completion wc->status=%s (%d) wc->opcode=%d\n", + ib_wc_status_msg(wc->status), wc->status, wc->opcode); + smbd_disconnect_rdma_connection(sc); + return; + } + /* * Free possible siblings and then the main send_io */ @@ -567,6 +593,7 @@ static void send_done(struct ib_cq *cq, struct ib_wc *wc) lcredits += 1; if (wc->status != IB_WC_SUCCESS || wc->opcode != IB_WC_SEND) { +skip_free: if (wc->status != IB_WC_WR_FLUSH_ERR) log_rdma_send(ERR, "wc->status=%s wc->opcode=%d\n", ib_wc_status_msg(wc->status), wc->opcode); From 8d76b2488eb3cc0717ab81b60622cff4a5f90f79 Mon Sep 17 00:00:00 2001 From: Gui-Dong Han Date: Wed, 14 Jan 2026 00:28:43 +0800 Subject: [PATCH 3182/3902] driver core: enforce device_lock for driver_match_device() commit dc23806a7c47ec5f1293aba407fb69519f976ee0 upstream. Currently, driver_match_device() is called from three sites. One site (__device_attach_driver) holds device_lock(dev), but the other two (bind_store and __driver_attach) do not. This inconsistency means that bus match() callbacks are not guaranteed to be called with the lock held. Fix this by introducing driver_match_device_locked(), which guarantees holding the device lock using a scoped guard. Replace the unlocked calls in bind_store() and __driver_attach() with this new helper. Also add a lock assertion to driver_match_device() to enforce this guarantee. This consistency also fixes a known race condition. The driver_override implementation relies on the device_lock, so the missing lock led to the use-after-free (UAF) reported in Bugzilla for buses using this field. Stress testing the two newly locked paths for 24 hours with CONFIG_PROVE_LOCKING and CONFIG_LOCKDEP enabled showed no UAF recurrence and no lockdep warnings. Cc: stable@vger.kernel.org Closes: https://bugzilla.kernel.org/show_bug.cgi?id=220789 Suggested-by: Qiu-ji Chen Signed-off-by: Gui-Dong Han Fixes: 49b420a13ff9 ("driver core: check bus->match without holding device lock") Reviewed-by: Danilo Krummrich Reviewed-by: Greg Kroah-Hartman Reviewed-by: Rafael J. Wysocki (Intel) Link: https://patch.msgid.link/20260113162843.12712-1-hanguidong02@gmail.com Signed-off-by: Danilo Krummrich Signed-off-by: Greg Kroah-Hartman --- drivers/base/base.h | 9 +++++++++ drivers/base/bus.c | 2 +- drivers/base/dd.c | 2 +- 3 files changed, 11 insertions(+), 2 deletions(-) diff --git a/drivers/base/base.h b/drivers/base/base.h index 86fa7fbb354891..30459906987e00 100644 --- a/drivers/base/base.h +++ b/drivers/base/base.h @@ -166,9 +166,18 @@ void device_set_deferred_probe_reason(const struct device *dev, struct va_format static inline int driver_match_device(const struct device_driver *drv, struct device *dev) { + device_lock_assert(dev); + return drv->bus->match ? drv->bus->match(dev, drv) : 1; } +static inline int driver_match_device_locked(const struct device_driver *drv, + struct device *dev) +{ + guard(device)(dev); + return driver_match_device(drv, dev); +} + static inline void dev_sync_state(struct device *dev) { if (dev->bus->sync_state) diff --git a/drivers/base/bus.c b/drivers/base/bus.c index 5e75e1bce5516d..999d371bbf3501 100644 --- a/drivers/base/bus.c +++ b/drivers/base/bus.c @@ -263,7 +263,7 @@ static ssize_t bind_store(struct device_driver *drv, const char *buf, int err = -ENODEV; dev = bus_find_device_by_name(bus, NULL, buf); - if (dev && driver_match_device(drv, dev)) { + if (dev && driver_match_device_locked(drv, dev)) { err = device_driver_attach(drv, dev); if (!err) { /* success */ diff --git a/drivers/base/dd.c b/drivers/base/dd.c index 13ab98e033eaa1..b6b9132e1f94f0 100644 --- a/drivers/base/dd.c +++ b/drivers/base/dd.c @@ -1170,7 +1170,7 @@ static int __driver_attach(struct device *dev, void *data) * is an error. */ - ret = driver_match_device(drv, dev); + ret = driver_match_device_locked(drv, dev); if (ret == 0) { /* no match */ return 0; From be7a9bcee0ca66adb111c37583347e28eb42e506 Mon Sep 17 00:00:00 2001 From: Zenm Chen Date: Thu, 29 Jan 2026 10:28:19 +0800 Subject: [PATCH 3183/3902] Bluetooth: btusb: Add USB ID 7392:e611 for Edimax EW-7611UXB commit 6c0568b7741a346088fd6dfced2d871f7d481d06 upstream. Add USB ID 7392:e611 for Edimax EW-7611UXB which is RTL8851BU-based Wi-Fi + Bluetooth adapter. The information in /sys/kernel/debug/usb/devices about the Bluetooth device is listed as the below: T: Bus=03 Lev=01 Prnt=01 Port=01 Cnt=01 Dev#= 6 Spd=480 MxCh= 0 D: Ver= 2.00 Cls=ef(misc ) Sub=02 Prot=01 MxPS=64 #Cfgs= 1 P: Vendor=7392 ProdID=e611 Rev= 0.00 S: Manufacturer=Realtek S: Product=802.11ax WLAN Adapter S: SerialNumber=00e04c000001 C:* #Ifs= 3 Cfg#= 1 Atr=e0 MxPwr=500mA A: FirstIf#= 0 IfCount= 2 Cls=e0(wlcon) Sub=01 Prot=01 I:* If#= 0 Alt= 0 #EPs= 3 Cls=e0(wlcon) Sub=01 Prot=01 Driver=btusb E: Ad=81(I) Atr=03(Int.) MxPS= 16 Ivl=1ms E: Ad=02(O) Atr=02(Bulk) MxPS= 512 Ivl=0ms E: Ad=82(I) Atr=02(Bulk) MxPS= 512 Ivl=0ms I:* If#= 1 Alt= 0 #EPs= 2 Cls=e0(wlcon) Sub=01 Prot=01 Driver=btusb E: Ad=03(O) Atr=01(Isoc) MxPS= 0 Ivl=1ms E: Ad=83(I) Atr=01(Isoc) MxPS= 0 Ivl=1ms I: If#= 1 Alt= 1 #EPs= 2 Cls=e0(wlcon) Sub=01 Prot=01 Driver=btusb E: Ad=03(O) Atr=01(Isoc) MxPS= 9 Ivl=1ms E: Ad=83(I) Atr=01(Isoc) MxPS= 9 Ivl=1ms I: If#= 1 Alt= 2 #EPs= 2 Cls=e0(wlcon) Sub=01 Prot=01 Driver=btusb E: Ad=03(O) Atr=01(Isoc) MxPS= 17 Ivl=1ms E: Ad=83(I) Atr=01(Isoc) MxPS= 17 Ivl=1ms I: If#= 1 Alt= 3 #EPs= 2 Cls=e0(wlcon) Sub=01 Prot=01 Driver=btusb E: Ad=03(O) Atr=01(Isoc) MxPS= 25 Ivl=1ms E: Ad=83(I) Atr=01(Isoc) MxPS= 25 Ivl=1ms I: If#= 1 Alt= 4 #EPs= 2 Cls=e0(wlcon) Sub=01 Prot=01 Driver=btusb E: Ad=03(O) Atr=01(Isoc) MxPS= 33 Ivl=1ms E: Ad=83(I) Atr=01(Isoc) MxPS= 33 Ivl=1ms I: If#= 1 Alt= 5 #EPs= 2 Cls=e0(wlcon) Sub=01 Prot=01 Driver=btusb E: Ad=03(O) Atr=01(Isoc) MxPS= 49 Ivl=1ms E: Ad=83(I) Atr=01(Isoc) MxPS= 49 Ivl=1ms I: If#= 1 Alt= 6 #EPs= 2 Cls=e0(wlcon) Sub=01 Prot=01 Driver=btusb E: Ad=03(O) Atr=01(Isoc) MxPS= 63 Ivl=1ms E: Ad=83(I) Atr=01(Isoc) MxPS= 63 Ivl=1ms I:* If#= 2 Alt= 0 #EPs= 8 Cls=ff(vend.) Sub=ff Prot=ff Driver=rtw89_8851bu_git E: Ad=84(I) Atr=02(Bulk) MxPS= 512 Ivl=0ms E: Ad=05(O) Atr=02(Bulk) MxPS= 512 Ivl=0ms E: Ad=06(O) Atr=02(Bulk) MxPS= 512 Ivl=0ms E: Ad=07(O) Atr=02(Bulk) MxPS= 512 Ivl=0ms E: Ad=09(O) Atr=02(Bulk) MxPS= 512 Ivl=0ms E: Ad=0a(O) Atr=02(Bulk) MxPS= 512 Ivl=0ms E: Ad=0b(O) Atr=02(Bulk) MxPS= 512 Ivl=0ms E: Ad=0c(O) Atr=02(Bulk) MxPS= 512 Ivl=0ms Cc: stable@vger.kernel.org # 6.6.x Signed-off-by: Zenm Chen Reviewed-by: Paul Menzel Signed-off-by: Luiz Augusto von Dentz Signed-off-by: Greg Kroah-Hartman --- drivers/bluetooth/btusb.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/drivers/bluetooth/btusb.c b/drivers/bluetooth/btusb.c index 3420f711f0f089..a953fa9af85c64 100644 --- a/drivers/bluetooth/btusb.c +++ b/drivers/bluetooth/btusb.c @@ -527,6 +527,8 @@ static const struct usb_device_id quirks_table[] = { BTUSB_WIDEBAND_SPEECH }, { USB_DEVICE(0x2001, 0x332a), .driver_info = BTUSB_REALTEK | BTUSB_WIDEBAND_SPEECH }, + { USB_DEVICE(0x7392, 0xe611), .driver_info = BTUSB_REALTEK | + BTUSB_WIDEBAND_SPEECH }, /* Realtek 8852AE Bluetooth devices */ { USB_DEVICE(0x0bda, 0x2852), .driver_info = BTUSB_REALTEK | From 65a0016016e8b115270d97ca44dce635cbd49375 Mon Sep 17 00:00:00 2001 From: Takashi Iwai Date: Sat, 7 Feb 2026 14:13:17 +0100 Subject: [PATCH 3184/3902] ALSA: hda/conexant: Add quirk for HP ZBook Studio G4 commit 1585cf83e98db32463e5d54161b06a5f01fe9976 upstream. It was reported that we need the same quirk for HP ZBook Studio G4 (SSID 103c:826b) as other HP models to make the mute-LED working. Cc: Link: https://lore.kernel.org/64d78753-b9ff-4c64-8920-64d8d31cd20c@gmail.com Link: https://bugzilla.kernel.org/show_bug.cgi?id=221002 Link: https://patch.msgid.link/20260207131324.2428030-1-tiwai@suse.de Signed-off-by: Takashi Iwai Signed-off-by: Greg Kroah-Hartman --- sound/hda/codecs/conexant.c | 1 + 1 file changed, 1 insertion(+) diff --git a/sound/hda/codecs/conexant.c b/sound/hda/codecs/conexant.c index 5fcbc1312c6971..d6fba746030195 100644 --- a/sound/hda/codecs/conexant.c +++ b/sound/hda/codecs/conexant.c @@ -1081,6 +1081,7 @@ static const struct hda_quirk cxt5066_fixups[] = { SND_PCI_QUIRK(0x103c, 0x8174, "HP Spectre x360", CXT_FIXUP_HP_SPECTRE), SND_PCI_QUIRK(0x103c, 0x822e, "HP ProBook 440 G4", CXT_FIXUP_MUTE_LED_GPIO), SND_PCI_QUIRK(0x103c, 0x8231, "HP ProBook 450 G4", CXT_FIXUP_MUTE_LED_GPIO), + SND_PCI_QUIRK(0x103c, 0x826b, "HP ZBook Studio G4", CXT_FIXUP_MUTE_LED_GPIO), SND_PCI_QUIRK(0x103c, 0x828c, "HP EliteBook 840 G4", CXT_FIXUP_HP_DOCK), SND_PCI_QUIRK(0x103c, 0x8299, "HP 800 G3 SFF", CXT_FIXUP_HP_MIC_NO_PRESENCE), SND_PCI_QUIRK(0x103c, 0x829a, "HP 800 G3 DM", CXT_FIXUP_HP_MIC_NO_PRESENCE), From d75207465eed20bc9b0daa4a0927de9568996067 Mon Sep 17 00:00:00 2001 From: Thorsten Blum Date: Thu, 27 Nov 2025 15:01:57 +0100 Subject: [PATCH 3185/3902] crypto: iaa - Fix out-of-bounds index in find_empty_iaa_compression_mode commit 48329301969f6d21b2ef35f678e40f72b59eac94 upstream. The local variable 'i' is initialized with -EINVAL, but the for loop immediately overwrites it and -EINVAL is never returned. If no empty compression mode can be found, the function would return the out-of-bounds index IAA_COMP_MODES_MAX, which would cause an invalid array access in add_iaa_compression_mode(). Fix both issues by returning either a valid index or -EINVAL. Cc: stable@vger.kernel.org Fixes: b190447e0fa3 ("crypto: iaa - Add compression mode management along with fixed mode") Signed-off-by: Thorsten Blum Acked-by: Kanchana P Sridhar Signed-off-by: Herbert Xu Signed-off-by: Greg Kroah-Hartman --- drivers/crypto/intel/iaa/iaa_crypto_main.c | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/drivers/crypto/intel/iaa/iaa_crypto_main.c b/drivers/crypto/intel/iaa/iaa_crypto_main.c index d0058757b0000d..da9b2bc515194f 100644 --- a/drivers/crypto/intel/iaa/iaa_crypto_main.c +++ b/drivers/crypto/intel/iaa/iaa_crypto_main.c @@ -221,15 +221,13 @@ static struct iaa_compression_mode *iaa_compression_modes[IAA_COMP_MODES_MAX]; static int find_empty_iaa_compression_mode(void) { - int i = -EINVAL; + int i; - for (i = 0; i < IAA_COMP_MODES_MAX; i++) { - if (iaa_compression_modes[i]) - continue; - break; - } + for (i = 0; i < IAA_COMP_MODES_MAX; i++) + if (!iaa_compression_modes[i]) + return i; - return i; + return -EINVAL; } static struct iaa_compression_mode *find_iaa_compression_mode(const char *name, int *idx) From 62c89e1992c867a3e701a6bd60a168c59a62b159 Mon Sep 17 00:00:00 2001 From: Thorsten Blum Date: Wed, 26 Nov 2025 10:46:13 +0100 Subject: [PATCH 3186/3902] crypto: octeontx - Fix length check to avoid truncation in ucode_load_store commit 5565a72b24fa7935a9f30af386e92c8c9dfb23b9 upstream. OTX_CPT_UCODE_NAME_LENGTH limits the microcode name to 64 bytes. If a user writes a string of exactly 64 characters, the original code used 'strlen(buf) > 64' to check the length, but then strscpy() copies only 63 characters before adding a NUL terminator, silently truncating the copied string. Fix this off-by-one error by using 'count' directly for the length check to ensure long names are rejected early and copied without truncation. Cc: stable@vger.kernel.org Fixes: d9110b0b01ff ("crypto: marvell - add support for OCTEON TX CPT engine") Signed-off-by: Thorsten Blum Signed-off-by: Herbert Xu Signed-off-by: Greg Kroah-Hartman --- drivers/crypto/marvell/octeontx/otx_cptpf_ucode.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/crypto/marvell/octeontx/otx_cptpf_ucode.c b/drivers/crypto/marvell/octeontx/otx_cptpf_ucode.c index 9f5601c0280bf1..417a48f4135053 100644 --- a/drivers/crypto/marvell/octeontx/otx_cptpf_ucode.c +++ b/drivers/crypto/marvell/octeontx/otx_cptpf_ucode.c @@ -1326,7 +1326,7 @@ static ssize_t ucode_load_store(struct device *dev, int del_grp_idx = -1; int ucode_idx = 0; - if (strlen(buf) > OTX_CPT_UCODE_NAME_LENGTH) + if (count >= OTX_CPT_UCODE_NAME_LENGTH) return -EINVAL; eng_grps = container_of(attr, struct otx_cpt_eng_grps, ucode_load_attr); From 2ed27b5a1174351148c3adbfc0cd86d54072ba2e Mon Sep 17 00:00:00 2001 From: Kees Cook Date: Fri, 6 Feb 2026 19:49:54 -0800 Subject: [PATCH 3187/3902] crypto: omap - Allocate OMAP_CRYPTO_FORCE_COPY scatterlists correctly commit 1562b1fb7e17c1b3addb15e125c718b2be7f5512 upstream. The existing allocation of scatterlists in omap_crypto_copy_sg_lists() was allocating an array of scatterlist pointers, not scatterlist objects, resulting in a 4x too small allocation. Use sizeof(*new_sg) to get the correct object size. Fixes: 74ed87e7e7f7 ("crypto: omap - add base support library for common routines") Signed-off-by: Kees Cook Acked-by: Herbert Xu Signed-off-by: Linus Torvalds Signed-off-by: Greg Kroah-Hartman --- drivers/crypto/omap-crypto.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/crypto/omap-crypto.c b/drivers/crypto/omap-crypto.c index a4cc6bf146ec09..0345c9383d5097 100644 --- a/drivers/crypto/omap-crypto.c +++ b/drivers/crypto/omap-crypto.c @@ -21,7 +21,7 @@ static int omap_crypto_copy_sg_lists(int total, int bs, struct scatterlist *tmp; if (!(flags & OMAP_CRYPTO_FORCE_SINGLE_ENTRY)) { - new_sg = kmalloc_array(n, sizeof(*sg), GFP_KERNEL); + new_sg = kmalloc_array(n, sizeof(*new_sg), GFP_KERNEL); if (!new_sg) return -ENOMEM; From e69a7b0a71b6561b3b6459f1fded8d589f2e8ac2 Mon Sep 17 00:00:00 2001 From: Bibo Mao Date: Tue, 13 Jan 2026 11:05:54 +0800 Subject: [PATCH 3188/3902] crypto: virtio - Add spinlock protection with virtqueue notification commit b505047ffc8057555900d2d3a005d033e6967382 upstream. When VM boots with one virtio-crypto PCI device and builtin backend, run openssl benchmark command with multiple processes, such as openssl speed -evp aes-128-cbc -engine afalg -seconds 10 -multi 32 openssl processes will hangup and there is error reported like this: virtio_crypto virtio0: dataq.0:id 3 is not a head! It seems that the data virtqueue need protection when it is handled for virtio done notification. If the spinlock protection is added in virtcrypto_done_task(), openssl benchmark with multiple processes works well. Fixes: fed93fb62e05 ("crypto: virtio - Handle dataq logic with tasklet") Cc: stable@vger.kernel.org Signed-off-by: Bibo Mao Acked-by: Jason Wang Acked-by: Michael S. Tsirkin Signed-off-by: Herbert Xu Signed-off-by: Greg Kroah-Hartman --- drivers/crypto/virtio/virtio_crypto_core.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/drivers/crypto/virtio/virtio_crypto_core.c b/drivers/crypto/virtio/virtio_crypto_core.c index 3d241446099cc9..ccc6b5c1b24b37 100644 --- a/drivers/crypto/virtio/virtio_crypto_core.c +++ b/drivers/crypto/virtio/virtio_crypto_core.c @@ -75,15 +75,20 @@ static void virtcrypto_done_task(unsigned long data) struct data_queue *data_vq = (struct data_queue *)data; struct virtqueue *vq = data_vq->vq; struct virtio_crypto_request *vc_req; + unsigned long flags; unsigned int len; + spin_lock_irqsave(&data_vq->lock, flags); do { virtqueue_disable_cb(vq); while ((vc_req = virtqueue_get_buf(vq, &len)) != NULL) { + spin_unlock_irqrestore(&data_vq->lock, flags); if (vc_req->alg_cb) vc_req->alg_cb(vc_req, len); + spin_lock_irqsave(&data_vq->lock, flags); } } while (!virtqueue_enable_cb(vq)); + spin_unlock_irqrestore(&data_vq->lock, flags); } static void virtcrypto_dataq_callback(struct virtqueue *vq) From 52505d7f713bff9b3e2e04a63adcacf1ded62a45 Mon Sep 17 00:00:00 2001 From: Bibo Mao Date: Tue, 13 Jan 2026 11:05:55 +0800 Subject: [PATCH 3189/3902] crypto: virtio - Remove duplicated virtqueue_kick in virtio_crypto_skcipher_crypt_req commit 14f86a1155cca1176abf55987b2fce7f7fcb2455 upstream. With function virtio_crypto_skcipher_crypt_req(), there is already virtqueue_kick() call with spinlock held in function __virtio_crypto_skcipher_do_req(). Remove duplicated virtqueue_kick() function call here. Fixes: d79b5d0bbf2e ("crypto: virtio - support crypto engine framework") Cc: stable@vger.kernel.org Signed-off-by: Bibo Mao Acked-by: Jason Wang Acked-by: Michael S. Tsirkin Signed-off-by: Herbert Xu Signed-off-by: Greg Kroah-Hartman --- drivers/crypto/virtio/virtio_crypto_skcipher_algs.c | 2 -- 1 file changed, 2 deletions(-) diff --git a/drivers/crypto/virtio/virtio_crypto_skcipher_algs.c b/drivers/crypto/virtio/virtio_crypto_skcipher_algs.c index 1b3fb21a2a7de2..11053d1786d4d2 100644 --- a/drivers/crypto/virtio/virtio_crypto_skcipher_algs.c +++ b/drivers/crypto/virtio/virtio_crypto_skcipher_algs.c @@ -541,8 +541,6 @@ int virtio_crypto_skcipher_crypt_req( if (ret < 0) return ret; - virtqueue_kick(data_vq->vq); - return 0; } From 4aa45f841413cca81882602b4042c53502f34cad Mon Sep 17 00:00:00 2001 From: Edward Adam Davis Date: Sat, 20 Dec 2025 03:04:25 +0900 Subject: [PATCH 3190/3902] nilfs2: Fix potential block overflow that cause system hang commit ed527ef0c264e4bed6c7b2a158ddf516b17f5f66 upstream. When a user executes the FITRIM command, an underflow can occur when calculating nblocks if end_block is too small. Since nblocks is of type sector_t, which is u64, a negative nblocks value will become a very large positive integer. This ultimately leads to the block layer function __blkdev_issue_discard() taking an excessively long time to process the bio chain, and the ns_segctor_sem lock remains held for a long period. This prevents other tasks from acquiring the ns_segctor_sem lock, resulting in the hang reported by syzbot in [1]. If the ending block is too small, typically if it is smaller than 4KiB range, depending on the usage of the segment 0, it may be possible to attempt a discard request beyond the device size causing the hang. Exiting successfully and assign the discarded size (0 in this case) to range->len. Although the start and len values in the user input range are too small, a conservative strategy is adopted here to safely ignore them, which is equivalent to a no-op; it will not perform any trimming and will not throw an error. [1] task:segctord state:D stack:28968 pid:6093 tgid:6093 ppid:2 task_flags:0x200040 flags:0x00080000 Call Trace: rwbase_write_lock+0x3dd/0x750 kernel/locking/rwbase_rt.c:272 nilfs_transaction_lock+0x253/0x4c0 fs/nilfs2/segment.c:357 nilfs_segctor_thread_construct fs/nilfs2/segment.c:2569 [inline] nilfs_segctor_thread+0x6ec/0xe00 fs/nilfs2/segment.c:2684 [ryusuke: corrected part of the commit message about the consequences] Fixes: 82e11e857be3 ("nilfs2: add nilfs_sufile_trim_fs to trim clean segs") Reported-by: syzbot+7eedce5eb281acd832f0@syzkaller.appspotmail.com Closes: https://syzkaller.appspot.com/bug?extid=7eedce5eb281acd832f0 Signed-off-by: Edward Adam Davis Signed-off-by: Ryusuke Konishi Cc: stable@vger.kernel.org Signed-off-by: Viacheslav Dubeyko Signed-off-by: Greg Kroah-Hartman --- fs/nilfs2/sufile.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/fs/nilfs2/sufile.c b/fs/nilfs2/sufile.c index 330f269abedf5d..71ee217eb072cf 100644 --- a/fs/nilfs2/sufile.c +++ b/fs/nilfs2/sufile.c @@ -1093,6 +1093,9 @@ int nilfs_sufile_trim_fs(struct inode *sufile, struct fstrim_range *range) else end_block = start_block + len - 1; + if (end_block < nilfs->ns_first_data_block) + goto out; + segnum = nilfs_get_segnum_of_block(nilfs, start_block); segnum_end = nilfs_get_segnum_of_block(nilfs, end_block); @@ -1191,6 +1194,7 @@ int nilfs_sufile_trim_fs(struct inode *sufile, struct fstrim_range *range) out_sem: up_read(&NILFS_MDT(sufile)->mi_sem); +out: range->len = ndiscarded << nilfs->ns_blocksize_bits; return ret; } From 46c1d56ad321fb024761abd9af61a0cb616cf2f6 Mon Sep 17 00:00:00 2001 From: Mehdi Ben Hadj Khelifa Date: Mon, 1 Dec 2025 23:23:06 +0100 Subject: [PATCH 3191/3902] hfs: ensure sb->s_fs_info is always cleaned up commit 05ce49a902be15dc93854cbfc20161205a9ee446 upstream. When hfs was converted to the new mount api a bug was introduced by changing the allocation pattern of sb->s_fs_info. If setup_bdev_super() fails after a new superblock has been allocated by sget_fc(), but before hfs_fill_super() takes ownership of the filesystem-specific s_fs_info data it was leaked. Fix this by freeing sb->s_fs_info in hfs_kill_super(). Cc: stable@vger.kernel.org Fixes: ffcd06b6d13b ("hfs: convert hfs to use the new mount api") Reported-by: syzbot+ad45f827c88778ff7df6@syzkaller.appspotmail.com Closes: https://syzkaller.appspot.com/bug?extid=ad45f827c88778ff7df6 Tested-by: Viacheslav Dubeyko Signed-off-by: Christian Brauner Signed-off-by: Mehdi Ben Hadj Khelifa Reviewed-by: Viacheslav Dubeyko Signed-off-by: Viacheslav Dubeyko Link: https://lore.kernel.org/r/20251201222843.82310-2-mehdi.benhadjkhelifa@gmail.com Signed-off-by: Viacheslav Dubeyko Signed-off-by: Greg Kroah-Hartman --- fs/hfs/mdb.c | 35 ++++++++++++++--------------------- fs/hfs/super.c | 10 +++++++++- 2 files changed, 23 insertions(+), 22 deletions(-) diff --git a/fs/hfs/mdb.c b/fs/hfs/mdb.c index 53f3fae6021797..f28cd24dee8425 100644 --- a/fs/hfs/mdb.c +++ b/fs/hfs/mdb.c @@ -92,7 +92,7 @@ int hfs_mdb_get(struct super_block *sb) /* See if this is an HFS filesystem */ bh = sb_bread512(sb, part_start + HFS_MDB_BLK, mdb); if (!bh) - goto out; + return -EIO; if (mdb->drSigWord == cpu_to_be16(HFS_SUPER_MAGIC)) break; @@ -102,13 +102,14 @@ int hfs_mdb_get(struct super_block *sb) * (should do this only for cdrom/loop though) */ if (hfs_part_find(sb, &part_start, &part_size)) - goto out; + return -EIO; } HFS_SB(sb)->alloc_blksz = size = be32_to_cpu(mdb->drAlBlkSiz); if (!size || (size & (HFS_SECTOR_SIZE - 1))) { pr_err("bad allocation block size %d\n", size); - goto out_bh; + brelse(bh); + return -EIO; } size = min(HFS_SB(sb)->alloc_blksz, (u32)PAGE_SIZE); @@ -125,14 +126,16 @@ int hfs_mdb_get(struct super_block *sb) brelse(bh); if (!sb_set_blocksize(sb, size)) { pr_err("unable to set blocksize to %u\n", size); - goto out; + return -EIO; } bh = sb_bread512(sb, part_start + HFS_MDB_BLK, mdb); if (!bh) - goto out; - if (mdb->drSigWord != cpu_to_be16(HFS_SUPER_MAGIC)) - goto out_bh; + return -EIO; + if (mdb->drSigWord != cpu_to_be16(HFS_SUPER_MAGIC)) { + brelse(bh); + return -EIO; + } HFS_SB(sb)->mdb_bh = bh; HFS_SB(sb)->mdb = mdb; @@ -174,7 +177,7 @@ int hfs_mdb_get(struct super_block *sb) HFS_SB(sb)->bitmap = kzalloc(8192, GFP_KERNEL); if (!HFS_SB(sb)->bitmap) - goto out; + return -EIO; /* read in the bitmap */ block = be16_to_cpu(mdb->drVBMSt) + part_start; @@ -185,7 +188,7 @@ int hfs_mdb_get(struct super_block *sb) bh = sb_bread(sb, off >> sb->s_blocksize_bits); if (!bh) { pr_err("unable to read volume bitmap\n"); - goto out; + return -EIO; } off2 = off & (sb->s_blocksize - 1); len = min((int)sb->s_blocksize - off2, size); @@ -199,12 +202,12 @@ int hfs_mdb_get(struct super_block *sb) HFS_SB(sb)->ext_tree = hfs_btree_open(sb, HFS_EXT_CNID, hfs_ext_keycmp); if (!HFS_SB(sb)->ext_tree) { pr_err("unable to open extent tree\n"); - goto out; + return -EIO; } HFS_SB(sb)->cat_tree = hfs_btree_open(sb, HFS_CAT_CNID, hfs_cat_keycmp); if (!HFS_SB(sb)->cat_tree) { pr_err("unable to open catalog tree\n"); - goto out; + return -EIO; } attrib = mdb->drAtrb; @@ -229,12 +232,6 @@ int hfs_mdb_get(struct super_block *sb) } return 0; - -out_bh: - brelse(bh); -out: - hfs_mdb_put(sb); - return -EIO; } /* @@ -359,8 +356,6 @@ void hfs_mdb_close(struct super_block *sb) * Release the resources associated with the in-core MDB. */ void hfs_mdb_put(struct super_block *sb) { - if (!HFS_SB(sb)) - return; /* free the B-trees */ hfs_btree_close(HFS_SB(sb)->ext_tree); hfs_btree_close(HFS_SB(sb)->cat_tree); @@ -373,6 +368,4 @@ void hfs_mdb_put(struct super_block *sb) unload_nls(HFS_SB(sb)->nls_disk); kfree(HFS_SB(sb)->bitmap); - kfree(HFS_SB(sb)); - sb->s_fs_info = NULL; } diff --git a/fs/hfs/super.c b/fs/hfs/super.c index 47f50fa555a457..df289cbdd4e85b 100644 --- a/fs/hfs/super.c +++ b/fs/hfs/super.c @@ -431,10 +431,18 @@ static int hfs_init_fs_context(struct fs_context *fc) return 0; } +static void hfs_kill_super(struct super_block *sb) +{ + struct hfs_sb_info *hsb = HFS_SB(sb); + + kill_block_super(sb); + kfree(hsb); +} + static struct file_system_type hfs_fs_type = { .owner = THIS_MODULE, .name = "hfs", - .kill_sb = kill_block_super, + .kill_sb = hfs_kill_super, .fs_flags = FS_REQUIRES_DEV, .init_fs_context = hfs_init_fs_context, }; From 13394550441557115bb74f6de9778c165755a7ab Mon Sep 17 00:00:00 2001 From: Bitterblue Smith Date: Wed, 24 Dec 2025 01:24:21 +0200 Subject: [PATCH 3192/3902] wifi: rtw88: Fix alignment fault in rtw_core_enable_beacon() commit 0177aa828d966117ea30a44f2e1890fdb356118e upstream. rtw_core_enable_beacon() reads 4 bytes from an address that is not a multiple of 4. This results in a crash on some systems. Do 1 byte reads/writes instead. Unable to handle kernel paging request at virtual address ffff8000827e0522 Mem abort info: ESR = 0x0000000096000021 EC = 0x25: DABT (current EL), IL = 32 bits SET = 0, FnV = 0 EA = 0, S1PTW = 0 FSC = 0x21: alignment fault Data abort info: ISV = 0, ISS = 0x00000021, ISS2 = 0x00000000 CM = 0, WnR = 0, TnD = 0, TagAccess = 0 GCS = 0, Overlay = 0, DirtyBit = 0, Xs = 0 swapper pgtable: 4k pages, 48-bit VAs, pgdp=0000000005492000 [ffff8000827e0522] pgd=0000000000000000, p4d=10000001021d9403, pud=10000001021da403, pmd=100000011061c403, pte=00780000f3200f13 Internal error: Oops: 0000000096000021 [#1] SMP Modules linked in: [...] rtw88_8822ce rtw88_8822c rtw88_pci rtw88_core [...] CPU: 0 UID: 0 PID: 73 Comm: kworker/u32:2 Tainted: G W 6.17.9 #1-NixOS VOLUNTARY Tainted: [W]=WARN Hardware name: FriendlyElec NanoPC-T6 LTS (DT) Workqueue: phy0 rtw_c2h_work [rtw88_core] pstate: 60400009 (nZCv daif +PAN -UAO -TCO -DIT -SSBS BTYPE=--) pc : rtw_pci_read32+0x18/0x40 [rtw88_pci] lr : rtw_core_enable_beacon+0xe0/0x148 [rtw88_core] sp : ffff800080cc3ca0 x29: ffff800080cc3ca0 x28: ffff0001031fc240 x27: ffff000102100828 x26: ffffd2cb7c9b4088 x25: ffff0001031fc2c0 x24: ffff000112fdef00 x23: ffff000112fdef18 x22: ffff000111c29970 x21: 0000000000000001 x20: 0000000000000001 x19: ffff000111c22040 x18: 0000000000000000 x17: 0000000000000000 x16: 0000000000000000 x15: 0000000000000000 x14: 0000000000000000 x13: 0000000000000000 x12: 0000000000000000 x11: 0000000000000000 x10: 0000000000000000 x9 : ffffd2cb6507c090 x8 : 0000000000000000 x7 : 0000000000000000 x6 : 0000000000000000 x5 : 0000000000000000 x4 : 0000000000000000 x3 : 0000000000000000 x2 : 0000000000007f10 x1 : 0000000000000522 x0 : ffff8000827e0522 Call trace: rtw_pci_read32+0x18/0x40 [rtw88_pci] (P) rtw_hw_scan_chan_switch+0x124/0x1a8 [rtw88_core] rtw_fw_c2h_cmd_handle+0x254/0x290 [rtw88_core] rtw_c2h_work+0x50/0x98 [rtw88_core] process_one_work+0x178/0x3f8 worker_thread+0x208/0x418 kthread+0x120/0x220 ret_from_fork+0x10/0x20 Code: d28fe202 8b020000 f9524400 8b214000 (b9400000) ---[ end trace 0000000000000000 ]--- Fixes: ad6741b1e044 ("wifi: rtw88: Stop high queue during scan") Cc: stable@vger.kernel.org Closes: https://github.com/lwfinger/rtw88/issues/418 Signed-off-by: Bitterblue Smith Acked-by: Ping-Ke Shih Signed-off-by: Ping-Ke Shih Link: https://patch.msgid.link/6345300d-8c93-464c-9b05-d0d9af3c97ad@gmail.com Signed-off-by: Greg Kroah-Hartman --- drivers/net/wireless/realtek/rtw88/main.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/net/wireless/realtek/rtw88/main.c b/drivers/net/wireless/realtek/rtw88/main.c index fa0ed39cb1992a..d93d21656f26c8 100644 --- a/drivers/net/wireless/realtek/rtw88/main.c +++ b/drivers/net/wireless/realtek/rtw88/main.c @@ -2444,10 +2444,10 @@ void rtw_core_enable_beacon(struct rtw_dev *rtwdev, bool enable) if (enable) { rtw_write32_set(rtwdev, REG_BCN_CTRL, BIT_EN_BCN_FUNCTION); - rtw_write32_clr(rtwdev, REG_TXPAUSE, BIT_HIGH_QUEUE); + rtw_write8_clr(rtwdev, REG_TXPAUSE, BIT_HIGH_QUEUE); } else { rtw_write32_clr(rtwdev, REG_BCN_CTRL, BIT_EN_BCN_FUNCTION); - rtw_write32_set(rtwdev, REG_TXPAUSE, BIT_HIGH_QUEUE); + rtw_write8_set(rtwdev, REG_TXPAUSE, BIT_HIGH_QUEUE); } } From 1a9585e4c58d1f1662b3ca46110ed4f583082ce5 Mon Sep 17 00:00:00 2001 From: Anil Gurumurthy Date: Wed, 10 Dec 2025 15:46:01 +0530 Subject: [PATCH 3193/3902] scsi: qla2xxx: Validate sp before freeing associated memory MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit commit b6df15aec8c3441357d4da0eaf4339eb20f5999f upstream. System crash with the following signature [154563.214890] nvme nvme2: NVME-FC{1}: controller connect complete [154564.169363] qla2xxx [0000:b0:00.1]-3002:2: nvme: Sched: Set ZIO exchange threshold to 3. [154564.169405] qla2xxx [0000:b0:00.1]-ffffff:2: SET ZIO Activity exchange threshold to 5. [154565.539974] qla2xxx [0000:b0:00.1]-5013:2: RSCN database changed – 0078 0080 0000. [154565.545744] qla2xxx [0000:b0:00.1]-5013:2: RSCN database changed – 0078 00a0 0000. [154565.545857] qla2xxx [0000:b0:00.1]-11a2:2: FEC=enabled (data rate). [154565.552760] qla2xxx [0000:b0:00.1]-11a2:2: FEC=enabled (data rate). [154565.553079] BUG: kernel NULL pointer dereference, address: 00000000000000f8 [154565.553080] #PF: supervisor read access in kernel mode [154565.553082] #PF: error_code(0x0000) - not-present page [154565.553084] PGD 80000010488ab067 P4D 80000010488ab067 PUD 104978a067 PMD 0 [154565.553089] Oops: 0000 1 PREEMPT SMP PTI [154565.553092] CPU: 10 PID: 858 Comm: qla2xxx_2_dpc Kdump: loaded Tainted: G OE ------- --- 5.14.0-503.11.1.el9_5.x86_64 #1 [154565.553096] Hardware name: HPE Synergy 660 Gen10/Synergy 660 Gen10 Compute Module, BIOS I43 09/30/2024 [154565.553097] RIP: 0010:qla_fab_async_scan.part.0+0x40b/0x870 [qla2xxx] [154565.553141] Code: 00 00 e8 58 a3 ec d4 49 89 e9 ba 12 20 00 00 4c 89 e6 49 c7 c0 00 ee a8 c0 48 c7 c1 66 c0 a9 c0 bf 00 80 00 10 e8 15 69 00 00 <4c> 8b 8d f8 00 00 00 4d 85 c9 74 35 49 8b 84 24 00 19 00 00 48 8b [154565.553143] RSP: 0018:ffffb4dbc8aebdd0 EFLAGS: 00010286 [154565.553145] RAX: 0000000000000000 RBX: ffff8ec2cf0908d0 RCX: 0000000000000002 [154565.553147] RDX: 0000000000000000 RSI: ffffffffc0a9c896 RDI: ffffb4dbc8aebd47 [154565.553148] RBP: 0000000000000000 R08: ffffb4dbc8aebd45 R09: 0000000000ffff0a [154565.553150] R10: 0000000000000000 R11: 000000000000000f R12: ffff8ec2cf0908d0 [154565.553151] R13: ffff8ec2cf090900 R14: 0000000000000102 R15: ffff8ec2cf084000 [154565.553152] FS: 0000000000000000(0000) GS:ffff8ed27f800000(0000) knlGS:0000000000000000 [154565.553154] CS: 0010 DS: 0000 ES: 0000 CR0: 0000000080050033 [154565.553155] CR2: 00000000000000f8 CR3: 000000113ae0a005 CR4: 00000000007706f0 [154565.553157] DR0: 0000000000000000 DR1: 0000000000000000 DR2: 0000000000000000 [154565.553158] DR3: 0000000000000000 DR6: 00000000fffe0ff0 DR7: 0000000000000400 [154565.553159] PKRU: 55555554 [154565.553160] Call Trace: [154565.553162] [154565.553165] ? show_trace_log_lvl+0x1c4/0x2df [154565.553172] ? show_trace_log_lvl+0x1c4/0x2df [154565.553177] ? qla_fab_async_scan.part.0+0x40b/0x870 [qla2xxx] [154565.553215] ? __die_body.cold+0x8/0xd [154565.553218] ? page_fault_oops+0x134/0x170 [154565.553223] ? snprintf+0x49/0x70 [154565.553229] ? exc_page_fault+0x62/0x150 [154565.553238] ? asm_exc_page_fault+0x22/0x30 Check for sp being non NULL before freeing any associated memory Fixes: a4239945b8ad ("scsi: qla2xxx: Add switch command to simplify fabric discovery") Cc: stable@vger.kernel.org Signed-off-by: Anil Gurumurthy Signed-off-by: Nilesh Javali Reviewed-by: Himanshu Madhani Link: https://patch.msgid.link/20251210101604.431868-10-njavali@marvell.com Signed-off-by: Martin K. Petersen Signed-off-by: Greg Kroah-Hartman --- drivers/scsi/qla2xxx/qla_gs.c | 34 ++++++++++++++++++---------------- 1 file changed, 18 insertions(+), 16 deletions(-) diff --git a/drivers/scsi/qla2xxx/qla_gs.c b/drivers/scsi/qla2xxx/qla_gs.c index 51c7cea71f9022..f704682a0970cd 100644 --- a/drivers/scsi/qla2xxx/qla_gs.c +++ b/drivers/scsi/qla2xxx/qla_gs.c @@ -3701,23 +3701,25 @@ int qla_fab_async_scan(scsi_qla_host_t *vha, srb_t *sp) return rval; done_free_sp: - if (sp->u.iocb_cmd.u.ctarg.req) { - dma_free_coherent(&vha->hw->pdev->dev, - sp->u.iocb_cmd.u.ctarg.req_allocated_size, - sp->u.iocb_cmd.u.ctarg.req, - sp->u.iocb_cmd.u.ctarg.req_dma); - sp->u.iocb_cmd.u.ctarg.req = NULL; - } - if (sp->u.iocb_cmd.u.ctarg.rsp) { - dma_free_coherent(&vha->hw->pdev->dev, - sp->u.iocb_cmd.u.ctarg.rsp_allocated_size, - sp->u.iocb_cmd.u.ctarg.rsp, - sp->u.iocb_cmd.u.ctarg.rsp_dma); - sp->u.iocb_cmd.u.ctarg.rsp = NULL; - } + if (sp) { + if (sp->u.iocb_cmd.u.ctarg.req) { + dma_free_coherent(&vha->hw->pdev->dev, + sp->u.iocb_cmd.u.ctarg.req_allocated_size, + sp->u.iocb_cmd.u.ctarg.req, + sp->u.iocb_cmd.u.ctarg.req_dma); + sp->u.iocb_cmd.u.ctarg.req = NULL; + } + if (sp->u.iocb_cmd.u.ctarg.rsp) { + dma_free_coherent(&vha->hw->pdev->dev, + sp->u.iocb_cmd.u.ctarg.rsp_allocated_size, + sp->u.iocb_cmd.u.ctarg.rsp, + sp->u.iocb_cmd.u.ctarg.rsp_dma); + sp->u.iocb_cmd.u.ctarg.rsp = NULL; + } - /* ref: INIT */ - kref_put(&sp->cmd_kref, qla2x00_sp_release); + /* ref: INIT */ + kref_put(&sp->cmd_kref, qla2x00_sp_release); + } spin_lock_irqsave(&vha->work_lock, flags); vha->scan.scan_flags &= ~SF_SCANNING; From ae49d33bfc08bfc108b155795c14700d754cf5d2 Mon Sep 17 00:00:00 2001 From: Shreyas Deodhar Date: Wed, 10 Dec 2025 15:45:58 +0530 Subject: [PATCH 3194/3902] scsi: qla2xxx: Allow recovery for tape devices commit b0335ee4fb94832a4ef68774ca7e7b33b473c7a6 upstream. Tape device doesn't show up after RSCNs. To fix this, remove tape device specific checks which allows recovery of tape devices. Fixes: 44c57f205876 ("scsi: qla2xxx: Changes to support FCP2 Target") Cc: stable@vger.kernel.org Signed-off-by: Shreyas Deodhar Signed-off-by: Nilesh Javali Reviewed-by: Himanshu Madhani Link: https://patch.msgid.link/20251210101604.431868-7-njavali@marvell.com Signed-off-by: Martin K. Petersen Signed-off-by: Greg Kroah-Hartman --- drivers/scsi/qla2xxx/qla_gs.c | 3 --- drivers/scsi/qla2xxx/qla_init.c | 9 --------- 2 files changed, 12 deletions(-) diff --git a/drivers/scsi/qla2xxx/qla_gs.c b/drivers/scsi/qla2xxx/qla_gs.c index f704682a0970cd..297c6b3e87fede 100644 --- a/drivers/scsi/qla2xxx/qla_gs.c +++ b/drivers/scsi/qla2xxx/qla_gs.c @@ -3266,9 +3266,6 @@ void qla_fab_scan_finish(scsi_qla_host_t *vha, srb_t *sp) atomic_read(&fcport->state) == FCS_ONLINE) || do_delete) { if (fcport->loop_id != FC_NO_LOOP_ID) { - if (fcport->flags & FCF_FCP2_DEVICE) - continue; - ql_log(ql_log_warn, vha, 0x20f0, "%s %d %8phC post del sess\n", __func__, __LINE__, diff --git a/drivers/scsi/qla2xxx/qla_init.c b/drivers/scsi/qla2xxx/qla_init.c index 6a2e1c7fd1251a..4186da332484fd 100644 --- a/drivers/scsi/qla2xxx/qla_init.c +++ b/drivers/scsi/qla2xxx/qla_init.c @@ -1859,15 +1859,6 @@ void qla2x00_handle_rscn(scsi_qla_host_t *vha, struct event_arg *ea) case RSCN_PORT_ADDR: fcport = qla2x00_find_fcport_by_nportid(vha, &ea->id, 1); if (fcport) { - if (ql2xfc2target && - fcport->flags & FCF_FCP2_DEVICE && - atomic_read(&fcport->state) == FCS_ONLINE) { - ql_dbg(ql_dbg_disc, vha, 0x2115, - "Delaying session delete for FCP2 portid=%06x %8phC ", - fcport->d_id.b24, fcport->port_name); - return; - } - if (vha->hw->flags.edif_enabled && DBELL_ACTIVE(vha)) { /* * On ipsec start by remote port, Target port From c068ebbaf52820d6bdefb9b405a1e426663c635a Mon Sep 17 00:00:00 2001 From: Anil Gurumurthy Date: Wed, 10 Dec 2025 15:45:59 +0530 Subject: [PATCH 3195/3902] scsi: qla2xxx: Delay module unload while fabric scan in progress commit 8890bf450e0b6b283f48ac619fca5ac2f14ddd62 upstream. System crash seen during load/unload test in a loop. [105954.384919] RBP: ffff914589838dc0 R08: 0000000000000000 R09: 0000000000000086 [105954.384920] R10: 000000000000000f R11: ffffa31240904be5 R12: ffff914605f868e0 [105954.384921] R13: ffff914605f86910 R14: 0000000000008010 R15: 00000000ddb7c000 [105954.384923] FS: 0000000000000000(0000) GS:ffff9163fec40000(0000) knlGS:0000000000000000 [105954.384925] CS: 0010 DS: 0000 ES: 0000 CR0: 0000000080050033 [105954.384926] CR2: 000055d31ce1d6a0 CR3: 0000000119f5e001 CR4: 0000000000770ee0 [105954.384928] PKRU: 55555554 [105954.384929] Call Trace: [105954.384931] [105954.384934] qla24xx_sp_unmap+0x1f3/0x2a0 [qla2xxx] [105954.384962] ? qla_async_scan_sp_done+0x114/0x1f0 [qla2xxx] [105954.384980] ? qla24xx_els_ct_entry+0x4de/0x760 [qla2xxx] [105954.384999] ? __wake_up_common+0x80/0x190 [105954.385004] ? qla24xx_process_response_queue+0xc2/0xaa0 [qla2xxx] [105954.385023] ? qla24xx_msix_rsp_q+0x44/0xb0 [qla2xxx] [105954.385040] ? __handle_irq_event_percpu+0x3d/0x190 [105954.385044] ? handle_irq_event+0x58/0xb0 [105954.385046] ? handle_edge_irq+0x93/0x240 [105954.385050] ? __common_interrupt+0x41/0xa0 [105954.385055] ? common_interrupt+0x3e/0xa0 [105954.385060] ? asm_common_interrupt+0x22/0x40 The root cause of this was that there was a free (dma_free_attrs) in the interrupt context. There was a device discovery/fabric scan in progress. A module unload was issued which set the UNLOADING flag. As part of the discovery, after receiving an interrupt a work queue was scheduled (which involved a work to be queued). Since the UNLOADING flag is set, the work item was not allocated and the mapped memory had to be freed. The free occurred in interrupt context leading to system crash. Delay the driver unload until the fabric scan is complete to avoid the crash. Reported-by: kernel test robot Reported-by: Dan Carpenter Closes: https://lore.kernel.org/all/202512090414.07Waorz0-lkp@intel.com/ Fixes: 783e0dc4f66a ("qla2xxx: Check for device state before unloading the driver.") Cc: stable@vger.kernel.org Signed-off-by: Anil Gurumurthy Signed-off-by: Nilesh Javali Reviewed-by: Himanshu Madhani Link: https://patch.msgid.link/20251210101604.431868-8-njavali@marvell.com Signed-off-by: Martin K. Petersen Signed-off-by: Greg Kroah-Hartman --- drivers/scsi/qla2xxx/qla_os.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/drivers/scsi/qla2xxx/qla_os.c b/drivers/scsi/qla2xxx/qla_os.c index 8ad0c19bdf4a9b..59ca4adcb43154 100644 --- a/drivers/scsi/qla2xxx/qla_os.c +++ b/drivers/scsi/qla2xxx/qla_os.c @@ -1182,7 +1182,8 @@ qla2x00_wait_for_hba_ready(scsi_qla_host_t *vha) while ((qla2x00_reset_active(vha) || ha->dpc_active || ha->flags.mbox_busy) || test_bit(FX00_RESET_RECOVERY, &vha->dpc_flags) || - test_bit(FX00_TARGET_SCAN, &vha->dpc_flags)) { + test_bit(FX00_TARGET_SCAN, &vha->dpc_flags) || + (vha->scan.scan_flags & SF_SCANNING)) { if (test_bit(UNLOADING, &base_vha->dpc_flags)) break; msleep(1000); From f04840512438ac025dea6e357d80a986b28bbe4c Mon Sep 17 00:00:00 2001 From: Anil Gurumurthy Date: Wed, 10 Dec 2025 15:46:00 +0530 Subject: [PATCH 3196/3902] scsi: qla2xxx: Free sp in error path to fix system crash commit 7adbd2b7809066c75f0433e5e2a8e114b429f30f upstream. System crash seen during load/unload test in a loop, [61110.449331] qla2xxx [0000:27:00.0]-0042:0: Disabled MSI-X. [61110.467494] ============================================================================= [61110.467498] BUG qla2xxx_srbs (Tainted: G OE -------- --- ): Objects remaining in qla2xxx_srbs on __kmem_cache_shutdown() [61110.467501] ----------------------------------------------------------------------------- [61110.467502] Slab 0x000000000ffc8162 objects=51 used=1 fp=0x00000000e25d3d85 flags=0x57ffffc0010200(slab|head|node=1|zone=2|lastcpupid=0x1fffff) [61110.467509] CPU: 53 PID: 455206 Comm: rmmod Kdump: loaded Tainted: G OE -------- --- 5.14.0-284.11.1.el9_2.x86_64 #1 [61110.467513] Hardware name: HPE ProLiant DL385 Gen10 Plus v2/ProLiant DL385 Gen10 Plus v2, BIOS A42 08/17/2023 [61110.467515] Call Trace: [61110.467516] [61110.467519] dump_stack_lvl+0x34/0x48 [61110.467526] slab_err.cold+0x53/0x67 [61110.467534] __kmem_cache_shutdown+0x16e/0x320 [61110.467540] kmem_cache_destroy+0x51/0x160 [61110.467544] qla2x00_module_exit+0x93/0x99 [qla2xxx] [61110.467607] ? __do_sys_delete_module.constprop.0+0x178/0x280 [61110.467613] ? syscall_trace_enter.constprop.0+0x145/0x1d0 [61110.467616] ? do_syscall_64+0x5c/0x90 [61110.467619] ? exc_page_fault+0x62/0x150 [61110.467622] ? entry_SYSCALL_64_after_hwframe+0x63/0xcd [61110.467626] [61110.467627] Disabling lock debugging due to kernel taint [61110.467635] Object 0x0000000026f7e6e6 @offset=16000 [61110.467639] ------------[ cut here ]------------ [61110.467639] kmem_cache_destroy qla2xxx_srbs: Slab cache still has objects when called from qla2x00_module_exit+0x93/0x99 [qla2xxx] [61110.467659] WARNING: CPU: 53 PID: 455206 at mm/slab_common.c:520 kmem_cache_destroy+0x14d/0x160 [61110.467718] CPU: 53 PID: 455206 Comm: rmmod Kdump: loaded Tainted: G B OE -------- --- 5.14.0-284.11.1.el9_2.x86_64 #1 [61110.467720] Hardware name: HPE ProLiant DL385 Gen10 Plus v2/ProLiant DL385 Gen10 Plus v2, BIOS A42 08/17/2023 [61110.467721] RIP: 0010:kmem_cache_destroy+0x14d/0x160 [61110.467724] Code: 99 7d 07 00 48 89 ef e8 e1 6a 07 00 eb b3 48 8b 55 60 48 8b 4c 24 20 48 c7 c6 70 fc 66 90 48 c7 c7 f8 ef a1 90 e8 e1 ed 7c 00 <0f> 0b eb 93 c3 cc cc cc cc 66 2e 0f 1f 84 00 00 00 00 00 55 48 89 [61110.467725] RSP: 0018:ffffa304e489fe80 EFLAGS: 00010282 [61110.467727] RAX: 0000000000000000 RBX: ffffffffc0d9a860 RCX: 0000000000000027 [61110.467729] RDX: ffff8fd5ff9598a8 RSI: 0000000000000001 RDI: ffff8fd5ff9598a0 [61110.467730] RBP: ffff8fb6aaf78700 R08: 0000000000000000 R09: 0000000100d863b7 [61110.467731] R10: ffffa304e489fd20 R11: ffffffff913bef48 R12: 0000000040002000 [61110.467731] R13: 0000000000000000 R14: 0000000000000000 R15: 0000000000000000 [61110.467733] FS: 00007f64c89fb740(0000) GS:ffff8fd5ff940000(0000) knlGS:0000000000000000 [61110.467734] CS: 0010 DS: 0000 ES: 0000 CR0: 0000000080050033 [61110.467735] CR2: 00007f0f02bfe000 CR3: 00000020ad6dc005 CR4: 0000000000770ee0 [61110.467736] PKRU: 55555554 [61110.467737] Call Trace: [61110.467738] [61110.467739] qla2x00_module_exit+0x93/0x99 [qla2xxx] [61110.467755] ? __do_sys_delete_module.constprop.0+0x178/0x280 Free sp in the error path to fix the crash. Fixes: f352eeb75419 ("scsi: qla2xxx: Add ability to use GPNFT/GNNFT for RSCN handling") Cc: stable@vger.kernel.org Signed-off-by: Anil Gurumurthy Signed-off-by: Nilesh Javali Reviewed-by: Himanshu Madhani Link: https://patch.msgid.link/20251210101604.431868-9-njavali@marvell.com Signed-off-by: Martin K. Petersen Signed-off-by: Greg Kroah-Hartman --- drivers/scsi/qla2xxx/qla_gs.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/scsi/qla2xxx/qla_gs.c b/drivers/scsi/qla2xxx/qla_gs.c index 297c6b3e87fede..880cd73feaca45 100644 --- a/drivers/scsi/qla2xxx/qla_gs.c +++ b/drivers/scsi/qla2xxx/qla_gs.c @@ -3532,8 +3532,8 @@ int qla_fab_async_scan(scsi_qla_host_t *vha, srb_t *sp) if (vha->scan.scan_flags & SF_SCANNING) { spin_unlock_irqrestore(&vha->work_lock, flags); ql_dbg(ql_dbg_disc + ql_dbg_verbose, vha, 0x2012, - "%s: scan active\n", __func__); - return rval; + "%s: scan active for sp:%p\n", __func__, sp); + goto done_free_sp; } vha->scan.scan_flags |= SF_SCANNING; if (!sp) From d14e991279831673729b0f2002905bf3741df55b Mon Sep 17 00:00:00 2001 From: Anil Gurumurthy Date: Wed, 10 Dec 2025 15:46:02 +0530 Subject: [PATCH 3197/3902] scsi: qla2xxx: Query FW again before proceeding with login commit 42b2dab4340d39b71334151e10c6d7d9b0040ffa upstream. Issue occurred during a continuous reboot test of several thousand iterations specific to a fabric topo with dual mode target where it sends a PLOGI/PRLI and then sends a LOGO. The initiator was also in the process of discovery and sent a PLOGI to the switch. It then queried a list of ports logged in via mbx 75h and the GPDB response indicated that the target was logged in. This caused a mismatch in the states between the driver and FW. Requery the FW for the state and proceed with the rest of discovery process. Fixes: a4239945b8ad ("scsi: qla2xxx: Add switch command to simplify fabric discovery") Cc: stable@vger.kernel.org Signed-off-by: Anil Gurumurthy Signed-off-by: Nilesh Javali Reviewed-by: Himanshu Madhani Link: https://patch.msgid.link/20251210101604.431868-11-njavali@marvell.com Signed-off-by: Martin K. Petersen Signed-off-by: Greg Kroah-Hartman --- drivers/scsi/qla2xxx/qla_init.c | 19 +++++++++++++++++-- drivers/scsi/qla2xxx/qla_isr.c | 19 +++++++++++++++++-- 2 files changed, 34 insertions(+), 4 deletions(-) diff --git a/drivers/scsi/qla2xxx/qla_init.c b/drivers/scsi/qla2xxx/qla_init.c index 4186da332484fd..84f89445c74705 100644 --- a/drivers/scsi/qla2xxx/qla_init.c +++ b/drivers/scsi/qla2xxx/qla_init.c @@ -2462,8 +2462,23 @@ qla24xx_handle_plogi_done_event(struct scsi_qla_host *vha, struct event_arg *ea) ea->sp->gen1, fcport->rscn_gen, ea->data[0], ea->data[1], ea->iop[0], ea->iop[1]); - if ((fcport->fw_login_state == DSC_LS_PLOGI_PEND) || - (fcport->fw_login_state == DSC_LS_PRLI_PEND)) { + if (fcport->fw_login_state == DSC_LS_PLOGI_PEND) { + ql_dbg(ql_dbg_disc, vha, 0x20ea, + "%s %d %8phC Remote is trying to login\n", + __func__, __LINE__, fcport->port_name); + /* + * If we get here, there is port thats already logged in, + * but it's state has not moved ahead. Recheck with FW on + * what state it is in and proceed ahead + */ + if (!N2N_TOPO(vha->hw)) { + fcport->fw_login_state = DSC_LS_PRLI_COMP; + qla24xx_post_gpdb_work(vha, fcport, 0); + } + return; + } + + if (fcport->fw_login_state == DSC_LS_PRLI_PEND) { ql_dbg(ql_dbg_disc, vha, 0x20ea, "%s %d %8phC Remote is trying to login\n", __func__, __LINE__, fcport->port_name); diff --git a/drivers/scsi/qla2xxx/qla_isr.c b/drivers/scsi/qla2xxx/qla_isr.c index a04a5aa0d00572..608d2f36e7b4ff 100644 --- a/drivers/scsi/qla2xxx/qla_isr.c +++ b/drivers/scsi/qla2xxx/qla_isr.c @@ -1676,13 +1676,28 @@ qla2x00_async_event(scsi_qla_host_t *vha, struct rsp_que *rsp, uint16_t *mb) /* Port logout */ fcport = qla2x00_find_fcport_by_loopid(vha, mb[1]); - if (!fcport) + if (!fcport) { + ql_dbg(ql_dbg_async, vha, 0x5011, + "Could not find fcport:%04x %04x %04x\n", + mb[1], mb[2], mb[3]); break; - if (atomic_read(&fcport->state) != FCS_ONLINE) + } + + if (atomic_read(&fcport->state) != FCS_ONLINE) { + ql_dbg(ql_dbg_async, vha, 0x5012, + "Port state is not online State:0x%x \n", + atomic_read(&fcport->state)); + ql_dbg(ql_dbg_async, vha, 0x5012, + "Scheduling session for deletion \n"); + fcport->logout_on_delete = 0; + qlt_schedule_sess_for_deletion(fcport); break; + } + ql_dbg(ql_dbg_async, vha, 0x508a, "Marking port lost loopid=%04x portid=%06x.\n", fcport->loop_id, fcport->d_id.b24); + if (qla_ini_mode_enabled(vha)) { fcport->logout_on_delete = 0; qlt_schedule_sess_for_deletion(fcport); From 1d6bd6183e723a7b256ff34bbb5b498b5f4f2ec0 Mon Sep 17 00:00:00 2001 From: Gui-Dong Han Date: Wed, 3 Dec 2025 01:44:38 +0800 Subject: [PATCH 3198/3902] bus: fsl-mc: fix use-after-free in driver_override_show() commit 148891e95014b5dc5878acefa57f1940c281c431 upstream. The driver_override_show() function reads the driver_override string without holding the device_lock. However, driver_override_store() uses driver_set_override(), which modifies and frees the string while holding the device_lock. This can result in a concurrent use-after-free if the string is freed by the store function while being read by the show function. Fix this by holding the device_lock around the read operation. Fixes: 1f86a00c1159 ("bus/fsl-mc: add support for 'driver_override' in the mc-bus") Cc: stable@vger.kernel.org Signed-off-by: Gui-Dong Han Reviewed-by: Ioana Ciornei Link: https://lore.kernel.org/r/20251202174438.12658-1-hanguidong02@gmail.com Signed-off-by: Christophe Leroy (CS GROUP) Signed-off-by: Greg Kroah-Hartman --- drivers/bus/fsl-mc/fsl-mc-bus.c | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/drivers/bus/fsl-mc/fsl-mc-bus.c b/drivers/bus/fsl-mc/fsl-mc-bus.c index 25845c04e5620a..a97baf2cbcdd51 100644 --- a/drivers/bus/fsl-mc/fsl-mc-bus.c +++ b/drivers/bus/fsl-mc/fsl-mc-bus.c @@ -202,8 +202,12 @@ static ssize_t driver_override_show(struct device *dev, struct device_attribute *attr, char *buf) { struct fsl_mc_device *mc_dev = to_fsl_mc_device(dev); + ssize_t len; - return sysfs_emit(buf, "%s\n", mc_dev->driver_override); + device_lock(dev); + len = sysfs_emit(buf, "%s\n", mc_dev->driver_override); + device_unlock(dev); + return len; } static DEVICE_ATTR_RW(driver_override); From d741534302f71c511eb0bb670b92eaa7df4a0aec Mon Sep 17 00:00:00 2001 From: Chao Yu Date: Fri, 6 Feb 2026 06:30:05 +0800 Subject: [PATCH 3199/3902] erofs: fix UAF issue for file-backed mounts w/ directio option commit 1caf50ce4af096d0280d59a31abdd85703cd995c upstream. [ 9.269940][ T3222] Call trace: [ 9.269948][ T3222] ext4_file_read_iter+0xac/0x108 [ 9.269979][ T3222] vfs_iocb_iter_read+0xac/0x198 [ 9.269993][ T3222] erofs_fileio_rq_submit+0x12c/0x180 [ 9.270008][ T3222] erofs_fileio_submit_bio+0x14/0x24 [ 9.270030][ T3222] z_erofs_runqueue+0x834/0x8ac [ 9.270054][ T3222] z_erofs_read_folio+0x120/0x220 [ 9.270083][ T3222] filemap_read_folio+0x60/0x120 [ 9.270102][ T3222] filemap_fault+0xcac/0x1060 [ 9.270119][ T3222] do_pte_missing+0x2d8/0x1554 [ 9.270131][ T3222] handle_mm_fault+0x5ec/0x70c [ 9.270142][ T3222] do_page_fault+0x178/0x88c [ 9.270167][ T3222] do_translation_fault+0x38/0x54 [ 9.270183][ T3222] do_mem_abort+0x54/0xac [ 9.270208][ T3222] el0_da+0x44/0x7c [ 9.270227][ T3222] el0t_64_sync_handler+0x5c/0xf4 [ 9.270253][ T3222] el0t_64_sync+0x1bc/0x1c0 EROFS may encounter above panic when enabling file-backed mount w/ directio mount option, the root cause is it may suffer UAF in below race condition: - z_erofs_read_folio wq s_dio_done_wq - z_erofs_runqueue - erofs_fileio_submit_bio - erofs_fileio_rq_submit - vfs_iocb_iter_read - ext4_file_read_iter - ext4_dio_read_iter - iomap_dio_rw : bio was submitted and return -EIOCBQUEUED - dio_aio_complete_work - dio_complete - dio->iocb->ki_complete (erofs_fileio_ki_complete()) - kfree(rq) : it frees iocb, iocb.ki_filp can be UAF in file_accessed(). - file_accessed : access NULL file point Introduce a reference count in struct erofs_fileio_rq, and initialize it as two, both erofs_fileio_ki_complete() and erofs_fileio_rq_submit() will decrease reference count, the last one decreasing the reference count to zero will free rq. Cc: stable@kernel.org Fixes: fb176750266a ("erofs: add file-backed mount support") Fixes: 6422cde1b0d5 ("erofs: use buffered I/O for file-backed mounts by default") Signed-off-by: Chao Yu Reviewed-by: Gao Xiang Signed-off-by: Gao Xiang Signed-off-by: Greg Kroah-Hartman --- fs/erofs/fileio.c | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/fs/erofs/fileio.c b/fs/erofs/fileio.c index b7b3432a9882e0..a47c6bab98ff99 100644 --- a/fs/erofs/fileio.c +++ b/fs/erofs/fileio.c @@ -10,6 +10,7 @@ struct erofs_fileio_rq { struct bio bio; struct kiocb iocb; struct super_block *sb; + refcount_t ref; }; struct erofs_fileio { @@ -42,7 +43,8 @@ static void erofs_fileio_ki_complete(struct kiocb *iocb, long ret) } } bio_uninit(&rq->bio); - kfree(rq); + if (refcount_dec_and_test(&rq->ref)) + kfree(rq); } static void erofs_fileio_rq_submit(struct erofs_fileio_rq *rq) @@ -66,6 +68,8 @@ static void erofs_fileio_rq_submit(struct erofs_fileio_rq *rq) revert_creds(old_cred); if (ret != -EIOCBQUEUED) erofs_fileio_ki_complete(&rq->iocb, ret); + if (refcount_dec_and_test(&rq->ref)) + kfree(rq); } static struct erofs_fileio_rq *erofs_fileio_rq_alloc(struct erofs_map_dev *mdev) @@ -76,6 +80,7 @@ static struct erofs_fileio_rq *erofs_fileio_rq_alloc(struct erofs_map_dev *mdev) bio_init(&rq->bio, NULL, rq->bvecs, ARRAY_SIZE(rq->bvecs), REQ_OP_READ); rq->iocb.ki_filp = mdev->m_dif->file; rq->sb = mdev->m_sb; + refcount_set(&rq->ref, 2); return rq; } From ed82e7949f5cac3058f4100f3cd670531d41a266 Mon Sep 17 00:00:00 2001 From: "Darrick J. Wong" Date: Fri, 23 Jan 2026 09:27:39 -0800 Subject: [PATCH 3200/3902] xfs: fix UAF in xchk_btree_check_block_owner commit 1c253e11225bc5167217897885b85093e17c2217 upstream. We cannot dereference bs->cur when trying to determine if bs->cur aliases bs->sc->sa.{bno,rmap}_cur after the latter has been freed. Fix this by sampling before type before any freeing could happen. The correct temporal ordering was broken when we removed xfs_btnum_t. Cc: r772577952@gmail.com Cc: # v6.9 Fixes: ec793e690f801d ("xfs: remove xfs_btnum_t") Signed-off-by: "Darrick J. Wong" Reviewed-by: Christoph Hellwig Tested-by: Jiaming Zhang Signed-off-by: Greg Kroah-Hartman --- fs/xfs/scrub/btree.c | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/fs/xfs/scrub/btree.c b/fs/xfs/scrub/btree.c index cd6f0ff382a7c8..acade92c5fce1a 100644 --- a/fs/xfs/scrub/btree.c +++ b/fs/xfs/scrub/btree.c @@ -370,12 +370,15 @@ xchk_btree_check_block_owner( { xfs_agnumber_t agno; xfs_agblock_t agbno; + bool is_bnobt, is_rmapbt; bool init_sa; int error = 0; if (!bs->cur) return 0; + is_bnobt = xfs_btree_is_bno(bs->cur->bc_ops); + is_rmapbt = xfs_btree_is_rmap(bs->cur->bc_ops); agno = xfs_daddr_to_agno(bs->cur->bc_mp, daddr); agbno = xfs_daddr_to_agbno(bs->cur->bc_mp, daddr); @@ -398,11 +401,11 @@ xchk_btree_check_block_owner( * have to nullify it (to shut down further block owner checks) if * self-xref encounters problems. */ - if (!bs->sc->sa.bno_cur && xfs_btree_is_bno(bs->cur->bc_ops)) + if (!bs->sc->sa.bno_cur && is_bnobt) bs->cur = NULL; xchk_xref_is_only_owned_by(bs->sc, agbno, 1, bs->oinfo); - if (!bs->sc->sa.rmap_cur && xfs_btree_is_rmap(bs->cur->bc_ops)) + if (!bs->sc->sa.rmap_cur && is_rmapbt) bs->cur = NULL; out_free: From 60b75407c172e1f341a8a5097c5cbc97dbbdd893 Mon Sep 17 00:00:00 2001 From: Jeongjun Park Date: Mon, 19 Jan 2026 17:25:53 +0900 Subject: [PATCH 3201/3902] drm/exynos: vidi: use ctx->lock to protect struct vidi_context member variables related to memory alloc/free commit 52b330799e2d6f825ae2bb74662ec1b10eb954bb upstream. Exynos Virtual Display driver performs memory alloc/free operations without lock protection, which easily causes concurrency problem. For example, use-after-free can occur in race scenario like this: ``` CPU0 CPU1 CPU2 ---- ---- ---- vidi_connection_ioctl() if (vidi->connection) // true drm_edid = drm_edid_alloc(); // alloc drm_edid ... ctx->raw_edid = drm_edid; ... drm_mode_getconnector() drm_helper_probe_single_connector_modes() vidi_get_modes() if (ctx->raw_edid) // true drm_edid_dup(ctx->raw_edid); if (!drm_edid) // false ... vidi_connection_ioctl() if (vidi->connection) // false drm_edid_free(ctx->raw_edid); // free drm_edid ... drm_edid_alloc(drm_edid->edid) kmemdup(edid); // UAF!! ... ``` To prevent these vulns, at least in vidi_context, member variables related to memory alloc/free should be protected with ctx->lock. Cc: Signed-off-by: Jeongjun Park Signed-off-by: Inki Dae Signed-off-by: Greg Kroah-Hartman --- drivers/gpu/drm/exynos/exynos_drm_vidi.c | 38 ++++++++++++++++++++---- 1 file changed, 32 insertions(+), 6 deletions(-) diff --git a/drivers/gpu/drm/exynos/exynos_drm_vidi.c b/drivers/gpu/drm/exynos/exynos_drm_vidi.c index e094b8bbc0f124..b80410a3e4aadb 100644 --- a/drivers/gpu/drm/exynos/exynos_drm_vidi.c +++ b/drivers/gpu/drm/exynos/exynos_drm_vidi.c @@ -186,29 +186,37 @@ static ssize_t vidi_store_connection(struct device *dev, const char *buf, size_t len) { struct vidi_context *ctx = dev_get_drvdata(dev); - int ret; + int ret, new_connected; - ret = kstrtoint(buf, 0, &ctx->connected); + ret = kstrtoint(buf, 0, &new_connected); if (ret) return ret; - - if (ctx->connected > 1) + if (new_connected > 1) return -EINVAL; + mutex_lock(&ctx->lock); + /* * Use fake edid data for test. If raw_edid is set then it can't be * tested. */ if (ctx->raw_edid) { DRM_DEV_DEBUG_KMS(dev, "edid data is not fake data.\n"); - return -EINVAL; + ret = -EINVAL; + goto fail; } + ctx->connected = new_connected; + mutex_unlock(&ctx->lock); + DRM_DEV_DEBUG_KMS(dev, "requested connection.\n"); drm_helper_hpd_irq_event(ctx->drm_dev); return len; +fail: + mutex_unlock(&ctx->lock); + return ret; } static DEVICE_ATTR(connection, 0644, vidi_show_connection, @@ -238,11 +246,14 @@ int vidi_connection_ioctl(struct drm_device *drm_dev, void *data, return -EINVAL; } + mutex_lock(&ctx->lock); if (ctx->connected == vidi->connection) { + mutex_unlock(&ctx->lock); DRM_DEV_DEBUG_KMS(ctx->dev, "same connection request.\n"); return -EINVAL; } + mutex_unlock(&ctx->lock); if (vidi->connection) { const struct drm_edid *drm_edid; @@ -262,14 +273,21 @@ int vidi_connection_ioctl(struct drm_device *drm_dev, void *data, "edid data is invalid.\n"); return -EINVAL; } + mutex_lock(&ctx->lock); ctx->raw_edid = drm_edid; + mutex_unlock(&ctx->lock); } else { /* with connection = 0, free raw_edid */ + mutex_lock(&ctx->lock); drm_edid_free(ctx->raw_edid); ctx->raw_edid = NULL; + mutex_unlock(&ctx->lock); } + mutex_lock(&ctx->lock); ctx->connected = vidi->connection; + mutex_unlock(&ctx->lock); + drm_helper_hpd_irq_event(ctx->drm_dev); return 0; @@ -284,7 +302,7 @@ static enum drm_connector_status vidi_detect(struct drm_connector *connector, * connection request would come from user side * to do hotplug through specific ioctl. */ - return ctx->connected ? connector_status_connected : + return READ_ONCE(ctx->connected) ? connector_status_connected : connector_status_disconnected; } @@ -307,11 +325,15 @@ static int vidi_get_modes(struct drm_connector *connector) const struct drm_edid *drm_edid; int count; + mutex_lock(&ctx->lock); + if (ctx->raw_edid) drm_edid = drm_edid_dup(ctx->raw_edid); else drm_edid = drm_edid_alloc(fake_edid_info, sizeof(fake_edid_info)); + mutex_unlock(&ctx->lock); + drm_edid_connector_update(connector, drm_edid); count = drm_edid_connector_add_modes(connector); @@ -456,9 +478,13 @@ static void vidi_remove(struct platform_device *pdev) { struct vidi_context *ctx = platform_get_drvdata(pdev); + mutex_lock(&ctx->lock); + drm_edid_free(ctx->raw_edid); ctx->raw_edid = NULL; + mutex_unlock(&ctx->lock); + component_del(&pdev->dev, &vidi_component_ops); } From 24a253c3aa6d9a2cde46158ce9782e023bfbf32d Mon Sep 17 00:00:00 2001 From: Liu Song Date: Thu, 10 Jul 2025 14:38:45 +0800 Subject: [PATCH 3202/3902] PCI: endpoint: Avoid creating sub-groups asynchronously commit 7c5c7d06bd1f86d2c3ebe62be903a4ba42db4d2c upstream. The asynchronous creation of sub-groups by a delayed work could lead to a NULL pointer dereference when the driver directory is removed before the work completes. The crash can be easily reproduced with the following commands: # cd /sys/kernel/config/pci_ep/functions/pci_epf_test # for i in {1..20}; do mkdir test && rmdir test; done BUG: kernel NULL pointer dereference, address: 0000000000000088 ... Call Trace: configfs_register_group+0x3d/0x190 pci_epf_cfs_work+0x41/0x110 process_one_work+0x18f/0x350 worker_thread+0x25a/0x3a0 Fix this issue by using configfs_add_default_group() API which does not have the deadlock problem as configfs_register_group() and does not require the delayed work handler. Fixes: e85a2d783762 ("PCI: endpoint: Add support in configfs to associate two EPCs with EPF") Signed-off-by: Liu Song [mani: slightly reworded the description and added stable list] Signed-off-by: Manivannan Sadhasivam Signed-off-by: Bjorn Helgaas Cc: stable@kernel.org Link: https://patch.msgid.link/20250710143845409gLM6JdlwPhlHG9iX3F6jK@zte.com.cn Signed-off-by: Greg Kroah-Hartman --- drivers/pci/endpoint/pci-ep-cfs.c | 15 +++++---------- 1 file changed, 5 insertions(+), 10 deletions(-) diff --git a/drivers/pci/endpoint/pci-ep-cfs.c b/drivers/pci/endpoint/pci-ep-cfs.c index ef50c82e647f4d..43feb6139fa36c 100644 --- a/drivers/pci/endpoint/pci-ep-cfs.c +++ b/drivers/pci/endpoint/pci-ep-cfs.c @@ -23,7 +23,6 @@ struct pci_epf_group { struct config_group group; struct config_group primary_epc_group; struct config_group secondary_epc_group; - struct delayed_work cfs_work; struct pci_epf *epf; int index; }; @@ -103,7 +102,7 @@ static struct config_group secondary_epc_group = &epf_group->secondary_epc_group; config_group_init_type_name(secondary_epc_group, "secondary", &pci_secondary_epc_type); - configfs_register_group(&epf_group->group, secondary_epc_group); + configfs_add_default_group(secondary_epc_group, &epf_group->group); return secondary_epc_group; } @@ -166,7 +165,7 @@ static struct config_group config_group_init_type_name(primary_epc_group, "primary", &pci_primary_epc_type); - configfs_register_group(&epf_group->group, primary_epc_group); + configfs_add_default_group(primary_epc_group, &epf_group->group); return primary_epc_group; } @@ -570,15 +569,13 @@ static void pci_ep_cfs_add_type_group(struct pci_epf_group *epf_group) return; } - configfs_register_group(&epf_group->group, group); + configfs_add_default_group(group, &epf_group->group); } -static void pci_epf_cfs_work(struct work_struct *work) +static void pci_epf_cfs_add_sub_groups(struct pci_epf_group *epf_group) { - struct pci_epf_group *epf_group; struct config_group *group; - epf_group = container_of(work, struct pci_epf_group, cfs_work.work); group = pci_ep_cfs_add_primary_group(epf_group); if (IS_ERR(group)) { pr_err("failed to create 'primary' EPC interface\n"); @@ -637,9 +634,7 @@ static struct config_group *pci_epf_make(struct config_group *group, kfree(epf_name); - INIT_DELAYED_WORK(&epf_group->cfs_work, pci_epf_cfs_work); - queue_delayed_work(system_wq, &epf_group->cfs_work, - msecs_to_jiffies(1)); + pci_epf_cfs_add_sub_groups(epf_group); return &epf_group->group; From 116f7bd8160c6b37d1c6939385abf90f6f6ed2f5 Mon Sep 17 00:00:00 2001 From: Ali Tariq Date: Thu, 25 Dec 2025 11:54:29 +0000 Subject: [PATCH 3203/3902] wifi: rtl8xxxu: fix slab-out-of-bounds in rtl8xxxu_sta_add commit 86c946bcc00f6390ef65e9614ae60a9377e454f8 upstream. The driver does not set hw->sta_data_size, which causes mac80211 to allocate insufficient space for driver private station data in __sta_info_alloc(). When rtl8xxxu_sta_add() accesses members of struct rtl8xxxu_sta_info through sta->drv_priv, this results in a slab-out-of-bounds write. KASAN report on RISC-V (VisionFive 2) with RTL8192EU adapter: BUG: KASAN: slab-out-of-bounds in rtl8xxxu_sta_add+0x31c/0x346 Write of size 8 at addr ffffffd6d3e9ae88 by task kworker/u16:0/12 Set hw->sta_data_size to sizeof(struct rtl8xxxu_sta_info) during probe, similar to how hw->vif_data_size is configured. This ensures mac80211 allocates sufficient space for the driver's per-station private data. Tested on StarFive VisionFive 2 v1.2A board. Fixes: eef55f1545c9 ("wifi: rtl8xxxu: support multiple interfaces in {add,remove}_interface()") Cc: stable@vger.kernel.org Signed-off-by: Ali Tariq Reviewed-by: Ping-Ke Shih Signed-off-by: Ping-Ke Shih Link: https://patch.msgid.link/20251225115430.13011-1-alitariq45892@gmail.com Signed-off-by: Greg Kroah-Hartman --- drivers/net/wireless/realtek/rtl8xxxu/core.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/net/wireless/realtek/rtl8xxxu/core.c b/drivers/net/wireless/realtek/rtl8xxxu/core.c index 3e87c571e24195..a17c1084931b0e 100644 --- a/drivers/net/wireless/realtek/rtl8xxxu/core.c +++ b/drivers/net/wireless/realtek/rtl8xxxu/core.c @@ -7927,6 +7927,7 @@ static int rtl8xxxu_probe(struct usb_interface *interface, goto err_set_intfdata; hw->vif_data_size = sizeof(struct rtl8xxxu_vif); + hw->sta_data_size = sizeof(struct rtl8xxxu_sta_info); hw->wiphy->max_scan_ssids = 1; hw->wiphy->max_scan_ie_len = IEEE80211_MAX_DATA_LEN; From 32f08c3ddd6dda6cbb6c9d715de10f21dccde50f Mon Sep 17 00:00:00 2001 From: Danilo Krummrich Date: Tue, 27 Jan 2026 21:17:12 +0100 Subject: [PATCH 3204/3902] gpio: omap: do not register driver in probe() commit 730e5ebff40c852e3ea57b71bf02a4b89c69435f upstream. Commit 11a78b794496 ("ARM: OMAP: MPUIO wake updates") registers the omap_mpuio_driver from omap_mpuio_init(), which is called from omap_gpio_probe(). However, it neither makes sense to register drivers from probe() callbacks of other drivers, nor does the driver core allow registering drivers with a device lock already being held. The latter was revealed by commit dc23806a7c47 ("driver core: enforce device_lock for driver_match_device()") leading to a potential deadlock condition described in [1]. Additionally, the omap_mpuio_driver is never unregistered from the driver core, even if the module is unloaded. Hence, register the omap_mpuio_driver from the module initcall and unregister it in module_exit(). Link: https://lore.kernel.org/lkml/DFU7CEPUSG9A.1KKGVW4HIPMSH@kernel.org/ [1] Fixes: dc23806a7c47 ("driver core: enforce device_lock for driver_match_device()") Fixes: 11a78b794496 ("ARM: OMAP: MPUIO wake updates") Reviewed-by: Greg Kroah-Hartman Signed-off-by: Danilo Krummrich Reviewed-by: Rafael J. Wysocki (Intel) Link: https://patch.msgid.link/20260127201725.35883-1-dakr@kernel.org Signed-off-by: Bartosz Golaszewski Signed-off-by: Greg Kroah-Hartman --- drivers/gpio/gpio-omap.c | 22 ++++++++++++++++++---- 1 file changed, 18 insertions(+), 4 deletions(-) diff --git a/drivers/gpio/gpio-omap.c b/drivers/gpio/gpio-omap.c index a268c76bdca62d..8693544304fa8f 100644 --- a/drivers/gpio/gpio-omap.c +++ b/drivers/gpio/gpio-omap.c @@ -799,10 +799,13 @@ static struct platform_device omap_mpuio_device = { static inline void omap_mpuio_init(struct gpio_bank *bank) { - platform_set_drvdata(&omap_mpuio_device, bank); + static bool registered; - if (platform_driver_register(&omap_mpuio_driver) == 0) - (void) platform_device_register(&omap_mpuio_device); + platform_set_drvdata(&omap_mpuio_device, bank); + if (!registered) { + (void)platform_device_register(&omap_mpuio_device); + registered = true; + } } /*---------------------------------------------------------------------*/ @@ -1576,13 +1579,24 @@ static struct platform_driver omap_gpio_driver = { */ static int __init omap_gpio_drv_reg(void) { - return platform_driver_register(&omap_gpio_driver); + int ret; + + ret = platform_driver_register(&omap_mpuio_driver); + if (ret) + return ret; + + ret = platform_driver_register(&omap_gpio_driver); + if (ret) + platform_driver_unregister(&omap_mpuio_driver); + + return ret; } postcore_initcall(omap_gpio_drv_reg); static void __exit omap_gpio_exit(void) { platform_driver_unregister(&omap_gpio_driver); + platform_driver_unregister(&omap_mpuio_driver); } module_exit(omap_gpio_exit); From ee4fb138af107455c9d6e38cf7087d932796ba8a Mon Sep 17 00:00:00 2001 From: Greg Kroah-Hartman Date: Mon, 16 Feb 2026 10:19:46 +0100 Subject: [PATCH 3205/3902] Linux 6.18.11 Link: https://lore.kernel.org/r/20260213134708.885500854@linuxfoundation.org Tested-by: Peter Schneider Tested-by: Justin M. Forbes Tested-by: Ronald Warsow Tested-by: Jon Hunter Tested-by: Florian Fainelli Tested-by: Salvatore Bonaccorso Tested-by: Brett Mastbergen Tested-by: Luna Jernberg Tested-by: Ron Economos Tested-by: Brett A C Sheffield Tested-by: Barry K. Nathan Tested-by: Miguel Ojeda Tested-by: Dileep Malepu Signed-off-by: Greg Kroah-Hartman --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 6d2269cbb0b25c..1d8d4b2c1da72b 100644 --- a/Makefile +++ b/Makefile @@ -1,7 +1,7 @@ # SPDX-License-Identifier: GPL-2.0 VERSION = 6 PATCHLEVEL = 18 -SUBLEVEL = 10 +SUBLEVEL = 11 EXTRAVERSION = NAME = Baby Opossum Posse From 53a76425e0764421ba93bb9045d2e454667d5687 Mon Sep 17 00:00:00 2001 From: Greg Kroah-Hartman Date: Mon, 16 Feb 2026 16:41:21 +0100 Subject: [PATCH 3206/3902] Revert "driver core: enforce device_lock for driver_match_device()" This reverts commit 8d76b2488eb3cc0717ab81b60622cff4a5f90f79 which is commit dc23806a7c47ec5f1293aba407fb69519f976ee0 upstream. It causes boot regressions on some systems as all of the "fixes" for drivers are not properly backported yet. Once that is completed, only then can this be applied, if really necessary given the potential for explosions, perhaps we might want to wait a few -rc releases first... Cc: Danilo Krummrich Cc: Rafael J. Wysocki (Intel) Cc: Danilo Krummrich Cc: Gui-Dong Han Cc: Qiu-ji Chen Reported-by: Mark Brown Link: https://lore.kernel.org/r/7dfd0e63-a725-4fac-b2a0-f2e621d99d1b@sirena.org.uk Signed-off-by: Greg Kroah-Hartman --- drivers/base/base.h | 9 --------- drivers/base/bus.c | 2 +- drivers/base/dd.c | 2 +- 3 files changed, 2 insertions(+), 11 deletions(-) diff --git a/drivers/base/base.h b/drivers/base/base.h index 30459906987e00..86fa7fbb354891 100644 --- a/drivers/base/base.h +++ b/drivers/base/base.h @@ -166,18 +166,9 @@ void device_set_deferred_probe_reason(const struct device *dev, struct va_format static inline int driver_match_device(const struct device_driver *drv, struct device *dev) { - device_lock_assert(dev); - return drv->bus->match ? drv->bus->match(dev, drv) : 1; } -static inline int driver_match_device_locked(const struct device_driver *drv, - struct device *dev) -{ - guard(device)(dev); - return driver_match_device(drv, dev); -} - static inline void dev_sync_state(struct device *dev) { if (dev->bus->sync_state) diff --git a/drivers/base/bus.c b/drivers/base/bus.c index 999d371bbf3501..5e75e1bce5516d 100644 --- a/drivers/base/bus.c +++ b/drivers/base/bus.c @@ -263,7 +263,7 @@ static ssize_t bind_store(struct device_driver *drv, const char *buf, int err = -ENODEV; dev = bus_find_device_by_name(bus, NULL, buf); - if (dev && driver_match_device_locked(drv, dev)) { + if (dev && driver_match_device(drv, dev)) { err = device_driver_attach(drv, dev); if (!err) { /* success */ diff --git a/drivers/base/dd.c b/drivers/base/dd.c index b6b9132e1f94f0..13ab98e033eaa1 100644 --- a/drivers/base/dd.c +++ b/drivers/base/dd.c @@ -1170,7 +1170,7 @@ static int __driver_attach(struct device *dev, void *data) * is an error. */ - ret = driver_match_device_locked(drv, dev); + ret = driver_match_device(drv, dev); if (ret == 0) { /* no match */ return 0; From 2784b1b43af3711a6268a616eb8f41f9788c6e3a Mon Sep 17 00:00:00 2001 From: Greg Kroah-Hartman Date: Mon, 16 Feb 2026 17:10:28 +0100 Subject: [PATCH 3207/3902] Linux 6.18.12 Signed-off-by: Greg Kroah-Hartman --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 1d8d4b2c1da72b..09153bd3bc5d5e 100644 --- a/Makefile +++ b/Makefile @@ -1,7 +1,7 @@ # SPDX-License-Identifier: GPL-2.0 VERSION = 6 PATCHLEVEL = 18 -SUBLEVEL = 11 +SUBLEVEL = 12 EXTRAVERSION = NAME = Baby Opossum Posse From 31f33b856d2324d86bcaef295f4d210477a1c018 Mon Sep 17 00:00:00 2001 From: Anil Gurumurthy Date: Wed, 10 Dec 2025 15:46:03 +0530 Subject: [PATCH 3208/3902] scsi: qla2xxx: Fix bsg_done() causing double free commit c2c68225b1456f4d0d393b5a8778d51bb0d5b1d0 upstream. Kernel panic observed on system, [5353358.825191] BUG: unable to handle page fault for address: ff5f5e897b024000 [5353358.825194] #PF: supervisor write access in kernel mode [5353358.825195] #PF: error_code(0x0002) - not-present page [5353358.825196] PGD 100006067 P4D 0 [5353358.825198] Oops: 0002 [#1] PREEMPT SMP NOPTI [5353358.825200] CPU: 5 PID: 2132085 Comm: qlafwupdate.sub Kdump: loaded Tainted: G W L ------- --- 5.14.0-503.34.1.el9_5.x86_64 #1 [5353358.825203] Hardware name: HPE ProLiant DL360 Gen11/ProLiant DL360 Gen11, BIOS 2.44 01/17/2025 [5353358.825204] RIP: 0010:memcpy_erms+0x6/0x10 [5353358.825211] RSP: 0018:ff591da8f4f6b710 EFLAGS: 00010246 [5353358.825212] RAX: ff5f5e897b024000 RBX: 0000000000007090 RCX: 0000000000001000 [5353358.825213] RDX: 0000000000001000 RSI: ff591da8f4fed090 RDI: ff5f5e897b024000 [5353358.825214] RBP: 0000000000010000 R08: ff5f5e897b024000 R09: 0000000000000000 [5353358.825215] R10: ff46cf8c40517000 R11: 0000000000000001 R12: 0000000000008090 [5353358.825216] R13: ff591da8f4f6b720 R14: 0000000000001000 R15: 0000000000000000 [5353358.825218] FS: 00007f1e88d47740(0000) GS:ff46cf935f940000(0000) knlGS:0000000000000000 [5353358.825219] CS: 0010 DS: 0000 ES: 0000 CR0: 0000000080050033 [5353358.825220] CR2: ff5f5e897b024000 CR3: 0000000231532004 CR4: 0000000000771ef0 [5353358.825221] PKRU: 55555554 [5353358.825222] Call Trace: [5353358.825223] [5353358.825224] ? show_trace_log_lvl+0x1c4/0x2df [5353358.825229] ? show_trace_log_lvl+0x1c4/0x2df [5353358.825232] ? sg_copy_buffer+0xc8/0x110 [5353358.825236] ? __die_body.cold+0x8/0xd [5353358.825238] ? page_fault_oops+0x134/0x170 [5353358.825242] ? kernelmode_fixup_or_oops+0x84/0x110 [5353358.825244] ? exc_page_fault+0xa8/0x150 [5353358.825247] ? asm_exc_page_fault+0x22/0x30 [5353358.825252] ? memcpy_erms+0x6/0x10 [5353358.825253] sg_copy_buffer+0xc8/0x110 [5353358.825259] qla2x00_process_vendor_specific+0x652/0x1320 [qla2xxx] [5353358.825317] qla24xx_bsg_request+0x1b2/0x2d0 [qla2xxx] Most routines in qla_bsg.c call bsg_done() only for success cases. However a few invoke it for failure case as well leading to a double free. Validate before calling bsg_done(). Cc: stable@vger.kernel.org Signed-off-by: Anil Gurumurthy Signed-off-by: Nilesh Javali Reviewed-by: Himanshu Madhani Link: https://patch.msgid.link/20251210101604.431868-12-njavali@marvell.com Signed-off-by: Martin K. Petersen Signed-off-by: Sasha Levin Signed-off-by: Greg Kroah-Hartman --- drivers/scsi/qla2xxx/qla_bsg.c | 28 +++++++++++++++++----------- 1 file changed, 17 insertions(+), 11 deletions(-) diff --git a/drivers/scsi/qla2xxx/qla_bsg.c b/drivers/scsi/qla2xxx/qla_bsg.c index ccfc2d26dd3725..0798bfd0372e47 100644 --- a/drivers/scsi/qla2xxx/qla_bsg.c +++ b/drivers/scsi/qla2xxx/qla_bsg.c @@ -1546,8 +1546,9 @@ qla2x00_update_optrom(struct bsg_job *bsg_job) ha->optrom_buffer = NULL; ha->optrom_state = QLA_SWAITING; mutex_unlock(&ha->optrom_mutex); - bsg_job_done(bsg_job, bsg_reply->result, - bsg_reply->reply_payload_rcv_len); + if (!rval) + bsg_job_done(bsg_job, bsg_reply->result, + bsg_reply->reply_payload_rcv_len); return rval; } @@ -2612,8 +2613,9 @@ qla2x00_manage_host_stats(struct bsg_job *bsg_job) sizeof(struct ql_vnd_mng_host_stats_resp)); bsg_reply->result = DID_OK; - bsg_job_done(bsg_job, bsg_reply->result, - bsg_reply->reply_payload_rcv_len); + if (!ret) + bsg_job_done(bsg_job, bsg_reply->result, + bsg_reply->reply_payload_rcv_len); return ret; } @@ -2702,8 +2704,9 @@ qla2x00_get_host_stats(struct bsg_job *bsg_job) bsg_job->reply_payload.sg_cnt, data, response_len); bsg_reply->result = DID_OK; - bsg_job_done(bsg_job, bsg_reply->result, - bsg_reply->reply_payload_rcv_len); + if (!ret) + bsg_job_done(bsg_job, bsg_reply->result, + bsg_reply->reply_payload_rcv_len); kfree(data); host_stat_out: @@ -2802,8 +2805,9 @@ qla2x00_get_tgt_stats(struct bsg_job *bsg_job) bsg_job->reply_payload.sg_cnt, data, response_len); bsg_reply->result = DID_OK; - bsg_job_done(bsg_job, bsg_reply->result, - bsg_reply->reply_payload_rcv_len); + if (!ret) + bsg_job_done(bsg_job, bsg_reply->result, + bsg_reply->reply_payload_rcv_len); tgt_stat_out: kfree(data); @@ -2864,8 +2868,9 @@ qla2x00_manage_host_port(struct bsg_job *bsg_job) bsg_job->reply_payload.sg_cnt, &rsp_data, sizeof(struct ql_vnd_mng_host_port_resp)); bsg_reply->result = DID_OK; - bsg_job_done(bsg_job, bsg_reply->result, - bsg_reply->reply_payload_rcv_len); + if (!ret) + bsg_job_done(bsg_job, bsg_reply->result, + bsg_reply->reply_payload_rcv_len); return ret; } @@ -3240,7 +3245,8 @@ int qla2x00_mailbox_passthru(struct bsg_job *bsg_job) bsg_job->reply_len = sizeof(*bsg_job->reply); bsg_reply->result = DID_OK << 16; - bsg_job_done(bsg_job, bsg_reply->result, bsg_reply->reply_payload_rcv_len); + if (!ret) + bsg_job_done(bsg_job, bsg_reply->result, bsg_reply->reply_payload_rcv_len); kfree(req_data); From 68dd6c5ebe7991aa8dee221ca78bc8b93958e92b Mon Sep 17 00:00:00 2001 From: FUJITA Tomonori Date: Wed, 31 Dec 2025 13:57:28 +0900 Subject: [PATCH 3209/3902] rust: device: fix broken intra-doc links commit a9a42f0754b6c69525612d678b73da790e28b9fd upstream. The `pci` module is conditional on CONFIG_PCI. When it's disabled, the intra-doc link to `pci::Device` causes rustdoc warnings: warning: unresolved link to `kernel::pci::Device` --> rust/kernel/device.rs:163:22 | 163 | /// [`pci::Device`]: kernel::pci::Device | ^^^^^^^^^^^^^^^^^^^ no item named `pci` in module `kernel` | = note: `#[warn(rustdoc::broken_intra_doc_links)]` on by default Fix this by making the documentation conditional on CONFIG_PCI. Fixes: d6e26c1ae4a6 ("device: rust: expand documentation for Device") Signed-off-by: FUJITA Tomonori Reviewed-by: Dirk Behme Link: https://patch.msgid.link/20251231045728.1912024-2-fujita.tomonori@gmail.com [ Keep the "such as" part indicating a list of examples; fix typos in commit message. - Danilo ] Signed-off-by: Danilo Krummrich Signed-off-by: Greg Kroah-Hartman --- rust/kernel/device.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/rust/kernel/device.rs b/rust/kernel/device.rs index a849b7dde2fd91..176531f54ed30f 100644 --- a/rust/kernel/device.rs +++ b/rust/kernel/device.rs @@ -62,8 +62,9 @@ pub mod property; /// /// # Implementing Bus Devices /// -/// This section provides a guideline to implement bus specific devices, such as [`pci::Device`] or -/// [`platform::Device`]. +/// This section provides a guideline to implement bus specific devices, such as: +#[cfg_attr(CONFIG_PCI, doc = "* [`pci::Device`](kernel::pci::Device)")] +/// * [`platform::Device`] /// /// A bus specific device should be defined as follows. /// @@ -155,7 +156,6 @@ pub mod property; /// /// [`AlwaysRefCounted`]: kernel::types::AlwaysRefCounted /// [`impl_device_context_deref`]: kernel::impl_device_context_deref -/// [`pci::Device`]: kernel::pci::Device /// [`platform::Device`]: kernel::platform::Device #[repr(transparent)] pub struct Device(Opaque, PhantomData); From 7d82e965fe0e3aa6776d4965af1f42dd132281f4 Mon Sep 17 00:00:00 2001 From: FUJITA Tomonori Date: Wed, 31 Dec 2025 13:57:27 +0900 Subject: [PATCH 3210/3902] rust: dma: fix broken intra-doc links commit 32cb3840386fd3684fbe8294cfc0a6684417139e upstream. The `pci` module is conditional on CONFIG_PCI. When it's disabled, the intra-doc link to `pci::Device` causes rustdoc warnings: warning: unresolved link to `::kernel::pci::Device` --> rust/kernel/dma.rs:30:70 | 30 | /// where the underlying bus is DMA capable, such as [`pci::Device`](::kernel::pci::Device) or | ^^^^^^^^^^^^^^^^^^^^^ no item named `pci` in module `kernel` Fix this by making the documentation conditional on CONFIG_PCI. Fixes: d06d5f66f549 ("rust: dma: implement `dma::Device` trait") Signed-off-by: FUJITA Tomonori Reviewed-by: Dirk Behme Link: https://patch.msgid.link/20251231045728.1912024-1-fujita.tomonori@gmail.com [ Keep the "such as" part indicating a list of examples; fix typos in commit message. - Danilo ] Signed-off-by: Danilo Krummrich Signed-off-by: Greg Kroah-Hartman --- rust/kernel/dma.rs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/rust/kernel/dma.rs b/rust/kernel/dma.rs index 4e0af3e1a3b9ad..777ea5c2431c7e 100644 --- a/rust/kernel/dma.rs +++ b/rust/kernel/dma.rs @@ -26,8 +26,9 @@ pub type DmaAddress = bindings::dma_addr_t; /// Trait to be implemented by DMA capable bus devices. /// /// The [`dma::Device`](Device) trait should be implemented by bus specific device representations, -/// where the underlying bus is DMA capable, such as [`pci::Device`](::kernel::pci::Device) or -/// [`platform::Device`](::kernel::platform::Device). +/// where the underlying bus is DMA capable, such as: +#[cfg_attr(CONFIG_PCI, doc = "* [`pci::Device`](kernel::pci::Device)")] +/// * [`platform::Device`](::kernel::platform::Device) pub trait Device: AsRef> { /// Set up the device's DMA streaming addressing capabilities. /// From 464a50c6b23aab9bd0041725e84f386c3cf9af9f Mon Sep 17 00:00:00 2001 From: Alice Ryhl Date: Sat, 27 Dec 2025 15:47:21 +0000 Subject: [PATCH 3211/3902] rust: driver: fix broken intra-doc links to example driver types commit 4c9f6a782f6078dc94450fcb22e65d520bfa0775 upstream. The `auxiliary` and `pci` modules are conditional on `CONFIG_AUXILIARY_BUS` and `CONFIG_PCI` respectively. When these are disabled, the intra-doc links to `auxiliary::Driver` and `pci::Driver` break, causing rustdoc warnings (or errors with `-D warnings`). error: unresolved link to `kernel::auxiliary::Driver` --> rust/kernel/driver.rs:82:28 | 82 | //! [`auxiliary::Driver`]: kernel::auxiliary::Driver | ^^^^^^^^^^^^^^^^^^^^^^^^^ no item named `auxiliary` in module `kernel` Fix this by making the documentation for these examples conditional on the corresponding configuration options. Fixes: 970a7c68788e ("driver: rust: expand documentation for driver infrastructure") Signed-off-by: Alice Ryhl Reported-by: FUJITA Tomonori Closes: https://lore.kernel.org/rust-for-linux/20251209.151817.744108529426448097.fujita.tomonori@gmail.com/ Link: https://patch.msgid.link/20251227-driver-types-v1-1-1916154fbe5e@google.com Signed-off-by: Danilo Krummrich Signed-off-by: Greg Kroah-Hartman --- rust/kernel/driver.rs | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/rust/kernel/driver.rs b/rust/kernel/driver.rs index 279e3af206822c..16931b94d0d48c 100644 --- a/rust/kernel/driver.rs +++ b/rust/kernel/driver.rs @@ -33,7 +33,14 @@ //! } //! ``` //! -//! For specific examples see [`auxiliary::Driver`], [`pci::Driver`] and [`platform::Driver`]. +//! For specific examples see: +//! +//! * [`platform::Driver`](kernel::platform::Driver) +#![cfg_attr( + CONFIG_AUXILIARY_BUS, + doc = "* [`auxiliary::Driver`](kernel::auxiliary::Driver)" +)] +#![cfg_attr(CONFIG_PCI, doc = "* [`pci::Driver`](kernel::pci::Driver)")] //! //! The `probe()` callback should return a `Result>>`, i.e. the driver's private //! data. The bus abstraction should store the pointer in the corresponding bus device. The generic @@ -79,7 +86,6 @@ //! //! For this purpose the generic infrastructure in [`device_id`] should be used. //! -//! [`auxiliary::Driver`]: kernel::auxiliary::Driver //! [`Core`]: device::Core //! [`Device`]: device::Device //! [`Device`]: device::Device @@ -87,8 +93,6 @@ //! [`DeviceContext`]: device::DeviceContext //! [`device_id`]: kernel::device_id //! [`module_driver`]: kernel::module_driver -//! [`pci::Driver`]: kernel::pci::Driver -//! [`platform::Driver`]: kernel::platform::Driver use crate::error::{Error, Result}; use crate::{acpi, device, of, str::CStr, try_pin_init, types::Opaque, ThisModule}; From 652667ac1369eaf1eb03ef7db01bd94ac12dcc53 Mon Sep 17 00:00:00 2001 From: Anatolii Shirykalov Date: Mon, 19 Jan 2026 15:56:18 +0100 Subject: [PATCH 3212/3902] ASoC: amd: yc: Add ASUS ExpertBook PM1503CDA to quirks list [ Upstream commit 018b211b1d321a52ed8d8de74ce83ce52a2e1224 ] Add ASUS ExpertBook PM1503CDA to the DMI quirks table to enable internal DMIC support via the ACP6x machine driver. Signed-off-by: Anatolii Shirykalov Link: https://patch.msgid.link/20260119145618.3171435-1-pipocavsobake@gmail.com Signed-off-by: Mark Brown Signed-off-by: Sasha Levin --- sound/soc/amd/yc/acp6x-mach.c | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/sound/soc/amd/yc/acp6x-mach.c b/sound/soc/amd/yc/acp6x-mach.c index c4a4a06528b45d..c18da0915baad8 100644 --- a/sound/soc/amd/yc/acp6x-mach.c +++ b/sound/soc/amd/yc/acp6x-mach.c @@ -542,6 +542,13 @@ static const struct dmi_system_id yc_acp_quirk_table[] = { DMI_MATCH(DMI_PRODUCT_NAME, "15NBC1011"), } }, + { + .driver_data = &acp6x_card, + .matches = { + DMI_MATCH(DMI_BOARD_VENDOR, "ASUSTeK COMPUTER INC."), + DMI_MATCH(DMI_PRODUCT_NAME, "ASUS EXPERTBOOK PM1503CDA"), + } + }, { .driver_data = &acp6x_card, .matches = { From b0b2cb84b64a9e380a27ab7e006cb1dd2474a4a7 Mon Sep 17 00:00:00 2001 From: Xuewen Yan Date: Mon, 26 Jan 2026 17:42:09 +0800 Subject: [PATCH 3213/3902] gpio: sprd: Change sprd_gpio lock to raw_spin_lock [ Upstream commit 96313fcc1f062ba239f4832c9eff685da6c51c99 ] There was a lockdep warning in sprd_gpio: [ 6.258269][T329@C6] [ BUG: Invalid wait context ] [ 6.258270][T329@C6] 6.18.0-android17-0-g30527ad7aaae-ab00009-4k #1 Tainted: G W OE [ 6.258272][T329@C6] ----------------------------- [ 6.258273][T329@C6] modprobe/329 is trying to lock: [ 6.258275][T329@C6] ffffff8081c91690 (&sprd_gpio->lock){....}-{3:3}, at: sprd_gpio_irq_unmask+0x4c/0xa4 [gpio_sprd] [ 6.258282][T329@C6] other info that might help us debug this: [ 6.258283][T329@C6] context-{5:5} [ 6.258285][T329@C6] 3 locks held by modprobe/329: [ 6.258286][T329@C6] #0: ffffff808baca108 (&dev->mutex){....}-{4:4}, at: __driver_attach+0xc4/0x204 [ 6.258295][T329@C6] #1: ffffff80965e7240 (request_class#4){+.+.}-{4:4}, at: __setup_irq+0x1cc/0x82c [ 6.258304][T329@C6] #2: ffffff80965e70c8 (lock_class#4){....}-{2:2}, at: __setup_irq+0x21c/0x82c [ 6.258313][T329@C6] stack backtrace: [ 6.258314][T329@C6] CPU: 6 UID: 0 PID: 329 Comm: modprobe Tainted: G W OE 6.18.0-android17-0-g30527ad7aaae-ab00009-4k #1 PREEMPT 3ad5b0f45741a16e5838da790706e16ceb6717df [ 6.258316][T329@C6] Tainted: [W]=WARN, [O]=OOT_MODULE, [E]=UNSIGNED_MODULE [ 6.258317][T329@C6] Hardware name: Unisoc UMS9632-base Board (DT) [ 6.258318][T329@C6] Call trace: [ 6.258318][T329@C6] show_stack+0x20/0x30 (C) [ 6.258321][T329@C6] __dump_stack+0x28/0x3c [ 6.258324][T329@C6] dump_stack_lvl+0xac/0xf0 [ 6.258326][T329@C6] dump_stack+0x18/0x3c [ 6.258329][T329@C6] __lock_acquire+0x824/0x2c28 [ 6.258331][T329@C6] lock_acquire+0x148/0x2cc [ 6.258333][T329@C6] _raw_spin_lock_irqsave+0x6c/0xb4 [ 6.258334][T329@C6] sprd_gpio_irq_unmask+0x4c/0xa4 [gpio_sprd 814535e93c6d8e0853c45c02eab0fa88a9da6487] [ 6.258337][T329@C6] irq_startup+0x238/0x350 [ 6.258340][T329@C6] __setup_irq+0x504/0x82c [ 6.258342][T329@C6] request_threaded_irq+0x118/0x184 [ 6.258344][T329@C6] devm_request_threaded_irq+0x94/0x120 [ 6.258347][T329@C6] sc8546_init_irq+0x114/0x170 [sc8546_charger 223586ccafc27439f7db4f95b0c8e6e882349a99] [ 6.258352][T329@C6] sc8546_charger_probe+0x53c/0x5a0 [sc8546_charger 223586ccafc27439f7db4f95b0c8e6e882349a99] [ 6.258358][T329@C6] i2c_device_probe+0x2c8/0x350 [ 6.258361][T329@C6] really_probe+0x1a8/0x46c [ 6.258363][T329@C6] __driver_probe_device+0xa4/0x10c [ 6.258366][T329@C6] driver_probe_device+0x44/0x1b4 [ 6.258369][T329@C6] __driver_attach+0xd0/0x204 [ 6.258371][T329@C6] bus_for_each_dev+0x10c/0x168 [ 6.258373][T329@C6] driver_attach+0x2c/0x3c [ 6.258376][T329@C6] bus_add_driver+0x154/0x29c [ 6.258378][T329@C6] driver_register+0x70/0x10c [ 6.258381][T329@C6] i2c_register_driver+0x48/0xc8 [ 6.258384][T329@C6] init_module+0x28/0xfd8 [sc8546_charger 223586ccafc27439f7db4f95b0c8e6e882349a99] [ 6.258389][T329@C6] do_one_initcall+0x128/0x42c [ 6.258392][T329@C6] do_init_module+0x60/0x254 [ 6.258395][T329@C6] load_module+0x1054/0x1220 [ 6.258397][T329@C6] __arm64_sys_finit_module+0x240/0x35c [ 6.258400][T329@C6] invoke_syscall+0x60/0xec [ 6.258402][T329@C6] el0_svc_common+0xb0/0xe4 [ 6.258405][T329@C6] do_el0_svc+0x24/0x30 [ 6.258407][T329@C6] el0_svc+0x54/0x1c4 [ 6.258409][T329@C6] el0t_64_sync_handler+0x68/0xdc [ 6.258411][T329@C6] el0t_64_sync+0x1c4/0x1c8 This is because the spin_lock would change to rt_mutex in PREEMPT_RT, however the sprd_gpio->lock would use in hard-irq, this is unsafe. So change the spin_lock_t to raw_spin_lock_t to use the spinlock in hard-irq. Signed-off-by: Xuewen Yan Reviewed-by: Baolin Wang Reviewed-by: Sebastian Andrzej Siewior Link: https://lore.kernel.org/r/20260126094209.9855-1-xuewen.yan@unisoc.com [Bartosz: tweaked the commit message] Signed-off-by: Bartosz Golaszewski Signed-off-by: Sasha Levin --- drivers/gpio/gpio-sprd.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/drivers/gpio/gpio-sprd.c b/drivers/gpio/gpio-sprd.c index 413bcd0a424050..2cc8abe705cdb7 100644 --- a/drivers/gpio/gpio-sprd.c +++ b/drivers/gpio/gpio-sprd.c @@ -35,7 +35,7 @@ struct sprd_gpio { struct gpio_chip chip; void __iomem *base; - spinlock_t lock; + raw_spinlock_t lock; int irq; }; @@ -54,7 +54,7 @@ static void sprd_gpio_update(struct gpio_chip *chip, unsigned int offset, unsigned long flags; u32 tmp; - spin_lock_irqsave(&sprd_gpio->lock, flags); + raw_spin_lock_irqsave(&sprd_gpio->lock, flags); tmp = readl_relaxed(base + reg); if (val) @@ -63,7 +63,7 @@ static void sprd_gpio_update(struct gpio_chip *chip, unsigned int offset, tmp &= ~BIT(SPRD_GPIO_BIT(offset)); writel_relaxed(tmp, base + reg); - spin_unlock_irqrestore(&sprd_gpio->lock, flags); + raw_spin_unlock_irqrestore(&sprd_gpio->lock, flags); } static int sprd_gpio_read(struct gpio_chip *chip, unsigned int offset, u16 reg) @@ -236,7 +236,7 @@ static int sprd_gpio_probe(struct platform_device *pdev) if (IS_ERR(sprd_gpio->base)) return PTR_ERR(sprd_gpio->base); - spin_lock_init(&sprd_gpio->lock); + raw_spin_lock_init(&sprd_gpio->lock); sprd_gpio->chip.label = dev_name(&pdev->dev); sprd_gpio->chip.ngpio = SPRD_GPIO_NR; From 1bdbcf326474cd962a2388df52525e14b42a44d5 Mon Sep 17 00:00:00 2001 From: Zhang Heng Date: Mon, 26 Jan 2026 15:35:08 +0800 Subject: [PATCH 3214/3902] ALSA: hda/realtek: Add quirk for Inspur S14-G1 [ Upstream commit 9e18920e783d0bcd4c127a7adc66565243ab9655 ] Inspur S14-G1 is equipped with ALC256. Enable "power saving mode" and Enable "headset jack mode". Signed-off-by: Zhang Heng Link: https://patch.msgid.link/20260126073508.3897461-2-zhangheng@kylinos.cn Signed-off-by: Takashi Iwai Signed-off-by: Sasha Levin --- sound/hda/codecs/realtek/alc269.c | 1 + 1 file changed, 1 insertion(+) diff --git a/sound/hda/codecs/realtek/alc269.c b/sound/hda/codecs/realtek/alc269.c index 2e9efafa732fe3..a77f16abc6df05 100644 --- a/sound/hda/codecs/realtek/alc269.c +++ b/sound/hda/codecs/realtek/alc269.c @@ -7243,6 +7243,7 @@ static const struct hda_quirk alc269_fixup_tbl[] = { SND_PCI_QUIRK(0x1ee7, 0x2078, "HONOR BRB-X M1010", ALC2XX_FIXUP_HEADSET_MIC), SND_PCI_QUIRK(0x1f66, 0x0105, "Ayaneo Portable Game Player", ALC287_FIXUP_CS35L41_I2C_2), SND_PCI_QUIRK(0x2014, 0x800a, "Positivo ARN50", ALC269_FIXUP_LIMIT_INT_MIC_BOOST), + SND_PCI_QUIRK(0x2039, 0x0001, "Inspur S14-G1", ALC295_FIXUP_CHROME_BOOK), SND_PCI_QUIRK(0x2782, 0x0214, "VAIO VJFE-CL", ALC269_FIXUP_LIMIT_INT_MIC_BOOST), SND_PCI_QUIRK(0x2782, 0x0228, "Infinix ZERO BOOK 13", ALC269VB_FIXUP_INFINIX_ZERO_BOOK_13), SND_PCI_QUIRK(0x2782, 0x0232, "CHUWI CoreBook XPro", ALC269VB_FIXUP_CHUWI_COREBOOK_XPRO), From 20bc7062c352fcb252dce1901930c3948d4585c7 Mon Sep 17 00:00:00 2001 From: Ricardo Rivera-Matos Date: Thu, 15 Jan 2026 19:25:10 +0000 Subject: [PATCH 3215/3902] ASoC: cs35l45: Corrects ASP_TX5 DAPM widget channel [ Upstream commit 6dd0fdc908c02318c28ec2c0979661846ee0a9f7 ] ASP_TX5 was incorrectly mapped to a channel value of 3 corrects, the channel value of 4. Reviewed-by: Charles Keepax Signed-off-by: Ricardo Rivera-Matos Link: https://patch.msgid.link/20260115192523.1335742-2-rriveram@opensource.cirrus.com Signed-off-by: Mark Brown Signed-off-by: Sasha Levin --- sound/soc/codecs/cs35l45.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sound/soc/codecs/cs35l45.c b/sound/soc/codecs/cs35l45.c index d4dcdf37bb709a..9b1eff4e9bb717 100644 --- a/sound/soc/codecs/cs35l45.c +++ b/sound/soc/codecs/cs35l45.c @@ -455,7 +455,7 @@ static const struct snd_soc_dapm_widget cs35l45_dapm_widgets[] = { SND_SOC_DAPM_AIF_OUT("ASP_TX2", NULL, 1, CS35L45_ASP_ENABLES1, CS35L45_ASP_TX2_EN_SHIFT, 0), SND_SOC_DAPM_AIF_OUT("ASP_TX3", NULL, 2, CS35L45_ASP_ENABLES1, CS35L45_ASP_TX3_EN_SHIFT, 0), SND_SOC_DAPM_AIF_OUT("ASP_TX4", NULL, 3, CS35L45_ASP_ENABLES1, CS35L45_ASP_TX4_EN_SHIFT, 0), - SND_SOC_DAPM_AIF_OUT("ASP_TX5", NULL, 3, CS35L45_ASP_ENABLES1, CS35L45_ASP_TX5_EN_SHIFT, 0), + SND_SOC_DAPM_AIF_OUT("ASP_TX5", NULL, 4, CS35L45_ASP_ENABLES1, CS35L45_ASP_TX5_EN_SHIFT, 0), SND_SOC_DAPM_MUX("ASP_TX1 Source", SND_SOC_NOPM, 0, 0, &cs35l45_asp_muxes[0]), SND_SOC_DAPM_MUX("ASP_TX2 Source", SND_SOC_NOPM, 0, 0, &cs35l45_asp_muxes[1]), From d2e01e0c5e947da0b5005ca89efa82baeb232adc Mon Sep 17 00:00:00 2001 From: Kailang Yang Date: Fri, 23 Jan 2026 15:21:36 +0800 Subject: [PATCH 3216/3902] ALSA: hda/realtek - fixed speaker no sound [ Upstream commit 630fbc6e870eb06c5126cc97a3abecbe012272c8 ] If it play a 5s above silence media stream, it will cause silence detection trigger. Speaker will make no sound when you use another app to play a stream. Add this patch will solve this issue. GPIO2: Mute Hotkey GPIO3: Mic Mute LED Enable this will turn on hotkey and LED support. Signed-off-by: Kailang Yang Link: https://lore.kernel.org/f4929e137a7949238cc043d861a4d9f8@realtek.com Signed-off-by: Takashi Iwai Signed-off-by: Sasha Levin --- sound/hda/codecs/realtek/alc269.c | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/sound/hda/codecs/realtek/alc269.c b/sound/hda/codecs/realtek/alc269.c index a77f16abc6df05..55ef52fefaef4e 100644 --- a/sound/hda/codecs/realtek/alc269.c +++ b/sound/hda/codecs/realtek/alc269.c @@ -3371,11 +3371,22 @@ static void alc287_alc1318_playback_pcm_hook(struct hda_pcm_stream *hinfo, struct snd_pcm_substream *substream, int action) { + static const struct coef_fw dis_coefs[] = { + WRITE_COEF(0x24, 0x0013), WRITE_COEF(0x25, 0x0000), WRITE_COEF(0x26, 0xC203), + WRITE_COEF(0x28, 0x0004), WRITE_COEF(0x29, 0xb023), + }; /* Disable AMP silence detection */ + static const struct coef_fw en_coefs[] = { + WRITE_COEF(0x24, 0x0013), WRITE_COEF(0x25, 0x0000), WRITE_COEF(0x26, 0xC203), + WRITE_COEF(0x28, 0x0084), WRITE_COEF(0x29, 0xb023), + }; /* Enable AMP silence detection */ + switch (action) { case HDA_GEN_PCM_ACT_OPEN: + alc_process_coef_fw(codec, dis_coefs); alc_write_coefex_idx(codec, 0x5a, 0x00, 0x954f); /* write gpio3 to high */ break; case HDA_GEN_PCM_ACT_CLOSE: + alc_process_coef_fw(codec, en_coefs); alc_write_coefex_idx(codec, 0x5a, 0x00, 0x554f); /* write gpio3 as default value */ break; } From 4b71ad7676564a94ec5f7d18298f51e8ae53db73 Mon Sep 17 00:00:00 2001 From: Deepanshu Kartikey Date: Tue, 13 Jan 2026 14:10:37 +0530 Subject: [PATCH 3217/3902] romfs: check sb_set_blocksize() return value [ Upstream commit ab7ad7abb3660c58ffffdf07ff3bb976e7e0afa0 ] romfs_fill_super() ignores the return value of sb_set_blocksize(), which can fail if the requested block size is incompatible with the block device's configuration. This can be triggered by setting a loop device's block size larger than PAGE_SIZE using ioctl(LOOP_SET_BLOCK_SIZE, 32768), then mounting a romfs filesystem on that device. When sb_set_blocksize(sb, ROMBSIZE) is called with ROMBSIZE=4096 but the device has logical_block_size=32768, bdev_validate_blocksize() fails because the requested size is smaller than the device's logical block size. sb_set_blocksize() returns 0 (failure), but romfs ignores this and continues mounting. The superblock's block size remains at the device's logical block size (32768). Later, when sb_bread() attempts I/O with this oversized block size, it triggers a kernel BUG in folio_set_bh(): kernel BUG at fs/buffer.c:1582! BUG_ON(size > PAGE_SIZE); Fix by checking the return value of sb_set_blocksize() and failing the mount with -EINVAL if it returns 0. Reported-by: syzbot+9c4e33e12283d9437c25@syzkaller.appspotmail.com Closes: https://syzkaller.appspot.com/bug?extid=9c4e33e12283d9437c25 Signed-off-by: Deepanshu Kartikey Link: https://patch.msgid.link/20260113084037.1167887-1-kartikey406@gmail.com Signed-off-by: Christian Brauner Signed-off-by: Sasha Levin --- fs/romfs/super.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/fs/romfs/super.c b/fs/romfs/super.c index 0addcc849ff2ca..e83f9b78d7a16e 100644 --- a/fs/romfs/super.c +++ b/fs/romfs/super.c @@ -458,7 +458,10 @@ static int romfs_fill_super(struct super_block *sb, struct fs_context *fc) #ifdef CONFIG_BLOCK if (!sb->s_mtd) { - sb_set_blocksize(sb, ROMBSIZE); + if (!sb_set_blocksize(sb, ROMBSIZE)) { + errorf(fc, "romfs: unable to set blocksize\n"); + return -EINVAL; + } } else { sb->s_blocksize = ROMBSIZE; sb->s_blocksize_bits = blksize_bits(ROMBSIZE); From efdb9c8ca3cfb132b29d68d506e3bb5dee3b9c83 Mon Sep 17 00:00:00 2001 From: Brahmajit Das Date: Tue, 2 Sep 2025 02:50:20 +0530 Subject: [PATCH 3218/3902] =?UTF-8?q?drm/tegra:=20hdmi:=20sor:=20Fix=20err?= =?UTF-8?q?or:=20variable=20=E2=80=98j=E2=80=99=20set=20but=20not=20used?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [ Upstream commit 1beee8d0c263b3e239c8d6616e4f8bb700bed658 ] The variable j is set, however never used in or outside the loop, thus resulting in dead code. Building with GCC 16 results in a build error due to -Werror=unused-but-set-variable= enabled by default. This patch clean up the dead code and fixes the build error. Example build log: drivers/gpu/drm/tegra/sor.c:1867:19: error: variable ‘j’ set but not used [-Werror=unused-but-set-variable=] 1867 | size_t i, j; | ^ Signed-off-by: Brahmajit Das Signed-off-by: Thierry Reding Link: https://lore.kernel.org/r/20250901212020.3757519-1-listout@listout.xyz Signed-off-by: Sasha Levin --- drivers/gpu/drm/tegra/hdmi.c | 4 ++-- drivers/gpu/drm/tegra/sor.c | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/drivers/gpu/drm/tegra/hdmi.c b/drivers/gpu/drm/tegra/hdmi.c index 8cd2969e7d4bfe..c4820f5e765813 100644 --- a/drivers/gpu/drm/tegra/hdmi.c +++ b/drivers/gpu/drm/tegra/hdmi.c @@ -658,7 +658,7 @@ static void tegra_hdmi_write_infopack(struct tegra_hdmi *hdmi, const void *data, { const u8 *ptr = data; unsigned long offset; - size_t i, j; + size_t i; u32 value; switch (ptr[0]) { @@ -691,7 +691,7 @@ static void tegra_hdmi_write_infopack(struct tegra_hdmi *hdmi, const void *data, * - subpack_low: bytes 0 - 3 * - subpack_high: bytes 4 - 6 (with byte 7 padded to 0x00) */ - for (i = 3, j = 0; i < size; i += 7, j += 8) { + for (i = 3; i < size; i += 7) { size_t rem = size - i, num = min_t(size_t, rem, 4); value = tegra_hdmi_subpack(&ptr[i], num); diff --git a/drivers/gpu/drm/tegra/sor.c b/drivers/gpu/drm/tegra/sor.c index 21f3dfdcc5c957..bc7dd562cf6b6b 100644 --- a/drivers/gpu/drm/tegra/sor.c +++ b/drivers/gpu/drm/tegra/sor.c @@ -1864,7 +1864,7 @@ static void tegra_sor_hdmi_write_infopack(struct tegra_sor *sor, { const u8 *ptr = data; unsigned long offset; - size_t i, j; + size_t i; u32 value; switch (ptr[0]) { @@ -1897,7 +1897,7 @@ static void tegra_sor_hdmi_write_infopack(struct tegra_sor *sor, * - subpack_low: bytes 0 - 3 * - subpack_high: bytes 4 - 6 (with byte 7 padded to 0x00) */ - for (i = 3, j = 0; i < size; i += 7, j += 8) { + for (i = 3; i < size; i += 7) { size_t rem = size - i, num = min_t(size_t, rem, 4); value = tegra_sor_hdmi_subpack(&ptr[i], num); From 97528b1622b8f129574d29a571c32a3c85eafa3c Mon Sep 17 00:00:00 2001 From: "Rafael J. Wysocki" Date: Mon, 26 Jan 2026 21:02:40 +0100 Subject: [PATCH 3219/3902] platform/x86: classmate-laptop: Add missing NULL pointer checks MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [ Upstream commit fe747d7112283f47169e9c16e751179a9b38611e ] In a few places in the Classmate laptop driver, code using the accel object may run before that object's address is stored in the driver data of the input device using it. For example, cmpc_accel_sensitivity_store_v4() is the "show" method of cmpc_accel_sensitivity_attr_v4 which is added in cmpc_accel_add_v4(), before calling dev_set_drvdata() for inputdev->dev. If the sysfs attribute is accessed prematurely, the dev_get_drvdata(&inputdev->dev) call in in cmpc_accel_sensitivity_store_v4() returns NULL which leads to a NULL pointer dereference going forward. Moreover, sysfs attributes using the input device are added before initializing that device by cmpc_add_acpi_notify_device() and if one of them is accessed before running that function, a NULL pointer dereference will occur. For example, cmpc_accel_sensitivity_attr_v4 is added before calling cmpc_add_acpi_notify_device() and if it is read prematurely, the dev_get_drvdata(&acpi->dev) call in cmpc_accel_sensitivity_show_v4() returns NULL which leads to a NULL pointer dereference going forward. Fix this by adding NULL pointer checks in all of the relevant places. Signed-off-by: Rafael J. Wysocki Link: https://patch.msgid.link/12825381.O9o76ZdvQC@rafael.j.wysocki Reviewed-by: Ilpo Järvinen Signed-off-by: Ilpo Järvinen Signed-off-by: Sasha Levin --- drivers/platform/x86/classmate-laptop.c | 32 +++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/drivers/platform/x86/classmate-laptop.c b/drivers/platform/x86/classmate-laptop.c index 6b1b8e444e241c..74d3eb83f56a63 100644 --- a/drivers/platform/x86/classmate-laptop.c +++ b/drivers/platform/x86/classmate-laptop.c @@ -207,7 +207,12 @@ static ssize_t cmpc_accel_sensitivity_show_v4(struct device *dev, acpi = to_acpi_device(dev); inputdev = dev_get_drvdata(&acpi->dev); + if (!inputdev) + return -ENXIO; + accel = dev_get_drvdata(&inputdev->dev); + if (!accel) + return -ENXIO; return sysfs_emit(buf, "%d\n", accel->sensitivity); } @@ -224,7 +229,12 @@ static ssize_t cmpc_accel_sensitivity_store_v4(struct device *dev, acpi = to_acpi_device(dev); inputdev = dev_get_drvdata(&acpi->dev); + if (!inputdev) + return -ENXIO; + accel = dev_get_drvdata(&inputdev->dev); + if (!accel) + return -ENXIO; r = kstrtoul(buf, 0, &sensitivity); if (r) @@ -256,7 +266,12 @@ static ssize_t cmpc_accel_g_select_show_v4(struct device *dev, acpi = to_acpi_device(dev); inputdev = dev_get_drvdata(&acpi->dev); + if (!inputdev) + return -ENXIO; + accel = dev_get_drvdata(&inputdev->dev); + if (!accel) + return -ENXIO; return sysfs_emit(buf, "%d\n", accel->g_select); } @@ -273,7 +288,12 @@ static ssize_t cmpc_accel_g_select_store_v4(struct device *dev, acpi = to_acpi_device(dev); inputdev = dev_get_drvdata(&acpi->dev); + if (!inputdev) + return -ENXIO; + accel = dev_get_drvdata(&inputdev->dev); + if (!accel) + return -ENXIO; r = kstrtoul(buf, 0, &g_select); if (r) @@ -302,6 +322,8 @@ static int cmpc_accel_open_v4(struct input_dev *input) acpi = to_acpi_device(input->dev.parent); accel = dev_get_drvdata(&input->dev); + if (!accel) + return -ENXIO; cmpc_accel_set_sensitivity_v4(acpi->handle, accel->sensitivity); cmpc_accel_set_g_select_v4(acpi->handle, accel->g_select); @@ -549,7 +571,12 @@ static ssize_t cmpc_accel_sensitivity_show(struct device *dev, acpi = to_acpi_device(dev); inputdev = dev_get_drvdata(&acpi->dev); + if (!inputdev) + return -ENXIO; + accel = dev_get_drvdata(&inputdev->dev); + if (!accel) + return -ENXIO; return sysfs_emit(buf, "%d\n", accel->sensitivity); } @@ -566,7 +593,12 @@ static ssize_t cmpc_accel_sensitivity_store(struct device *dev, acpi = to_acpi_device(dev); inputdev = dev_get_drvdata(&acpi->dev); + if (!inputdev) + return -ENXIO; + accel = dev_get_drvdata(&inputdev->dev); + if (!accel) + return -ENXIO; r = kstrtoul(buf, 0, &sensitivity); if (r) From c3b2e922924bfbb9a2f4541bec2c7d2249399631 Mon Sep 17 00:00:00 2001 From: Tagir Garaev Date: Sun, 1 Feb 2026 15:17:28 +0300 Subject: [PATCH 3220/3902] ASoC: Intel: sof_es8336: Add DMI quirk for Huawei BOD-WXX9 [ Upstream commit 6b641122d31f9d33e7d60047ee0586d1659f3f54 ] Add DMI entry for Huawei Matebook D (BOD-WXX9) with HEADPHONE_GPIO and DMIC quirks. This device has ES8336 codec with: - GPIO 16 (headphone-enable) for headphone amplifier control - GPIO 17 (speakers-enable) for speaker amplifier control - GPIO 269 for jack detection IRQ - 2-channel DMIC Hardware investigation shows that both GPIO 16 and 17 are required for proper audio routing, as headphones and speakers share the same physical output (HPOL/HPOR) and are separated only via amplifier enable signals. RFC: Seeking advice on GPIO control issue: GPIO values change in driver (gpiod_get_value() shows logical value changes) but not physically (debugfs gpio shows no change). The same gpiod_set_value_cansleep() calls work correctly in probe context with msleep(), but fail when called from DAPM event callbacks. Context information from diagnostics: - in_atomic=0, in_interrupt=0, irqs_disabled=0 - Process context: pipewire - GPIO 17 (speakers): changes in driver, no physical change - GPIO 16 (headphone): changes in driver, no physical change In Windows, audio switching works without visible GPIO changes, suggesting possible ACPI/firmware involvement. Any suggestions on how to properly control these GPIOs from DAPM events would be appreciated. Signed-off-by: Tagir Garaev Link: https://patch.msgid.link/20260201121728.16597-1-tgaraev653@gmail.com Signed-off-by: Mark Brown Signed-off-by: Sasha Levin --- sound/soc/intel/boards/sof_es8336.c | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/sound/soc/intel/boards/sof_es8336.c b/sound/soc/intel/boards/sof_es8336.c index 09acd80d23e0fc..cf50de5c2edd8d 100644 --- a/sound/soc/intel/boards/sof_es8336.c +++ b/sound/soc/intel/boards/sof_es8336.c @@ -332,6 +332,15 @@ static int sof_es8336_quirk_cb(const struct dmi_system_id *id) * if the topology file is modified as well. */ static const struct dmi_system_id sof_es8336_quirk_table[] = { + { + .callback = sof_es8336_quirk_cb, + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "HUAWEI"), + DMI_MATCH(DMI_PRODUCT_NAME, "BOD-WXX9"), + }, + .driver_data = (void *)(SOF_ES8336_HEADPHONE_GPIO | + SOF_ES8336_ENABLE_DMIC) + }, { .callback = sof_es8336_quirk_cb, .matches = { From 78783e8d588cf79abcbe487b1c4da80621c39070 Mon Sep 17 00:00:00 2001 From: Dirk Su Date: Thu, 29 Jan 2026 14:50:19 +0800 Subject: [PATCH 3221/3902] ASoC: amd: yc: Add quirk for HP 200 G2a 16 [ Upstream commit 611c7d2262d5645118e0b3a9a88475d35a8366f2 ] Fix the missing mic on HP 200 G2a 16 by adding quirk with the board ID 8EE4 Signed-off-by: Dirk Su Link: https://patch.msgid.link/20260129065038.39349-1-dirk.su@canonical.com Signed-off-by: Mark Brown Signed-off-by: Sasha Levin --- sound/soc/amd/yc/acp6x-mach.c | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/sound/soc/amd/yc/acp6x-mach.c b/sound/soc/amd/yc/acp6x-mach.c index c18da0915baad8..67f2fee1939804 100644 --- a/sound/soc/amd/yc/acp6x-mach.c +++ b/sound/soc/amd/yc/acp6x-mach.c @@ -640,6 +640,13 @@ static const struct dmi_system_id yc_acp_quirk_table[] = { DMI_MATCH(DMI_BOARD_NAME, "8BD6"), } }, + { + .driver_data = &acp6x_card, + .matches = { + DMI_MATCH(DMI_BOARD_VENDOR, "HP"), + DMI_MATCH(DMI_BOARD_NAME, "8EE4"), + } + }, { .driver_data = &acp6x_card, .matches = { From a9584e9e464480042a5fc43bf097fa0837da8856 Mon Sep 17 00:00:00 2001 From: Breno Baptista Date: Wed, 4 Feb 2026 23:43:41 -0300 Subject: [PATCH 3222/3902] ALSA: hda/realtek: Enable headset mic for Acer Nitro 5 [ Upstream commit 51db05283f7c9c95a3e6853a3044cd04226551bf ] Add quirk to support microphone input through headphone jack on Acer Nitro 5 AN515-57 (ALC295). Signed-off-by: Breno Baptista Link: https://patch.msgid.link/20260205024341.26694-1-brenomb07@gmail.com Signed-off-by: Takashi Iwai Signed-off-by: Sasha Levin --- sound/hda/codecs/realtek/alc269.c | 1 + 1 file changed, 1 insertion(+) diff --git a/sound/hda/codecs/realtek/alc269.c b/sound/hda/codecs/realtek/alc269.c index 55ef52fefaef4e..a16cb45ac59e34 100644 --- a/sound/hda/codecs/realtek/alc269.c +++ b/sound/hda/codecs/realtek/alc269.c @@ -6271,6 +6271,7 @@ static const struct hda_quirk alc269_fixup_tbl[] = { SND_PCI_QUIRK(0x1025, 0x1430, "Acer TravelMate B311R-31", ALC256_FIXUP_ACER_MIC_NO_PRESENCE), SND_PCI_QUIRK(0x1025, 0x1466, "Acer Aspire A515-56", ALC255_FIXUP_ACER_HEADPHONE_AND_MIC), SND_PCI_QUIRK(0x1025, 0x1534, "Acer Predator PH315-54", ALC255_FIXUP_ACER_MIC_NO_PRESENCE), + SND_PCI_QUIRK(0x1025, 0x1539, "Acer Nitro 5 AN515-57", ALC2XX_FIXUP_HEADSET_MIC), SND_PCI_QUIRK(0x1025, 0x159c, "Acer Nitro 5 AN515-58", ALC2XX_FIXUP_HEADSET_MIC), SND_PCI_QUIRK(0x1025, 0x1597, "Acer Nitro 5 AN517-55", ALC2XX_FIXUP_HEADSET_MIC), SND_PCI_QUIRK(0x1025, 0x169a, "Acer Swift SFG16", ALC256_FIXUP_ACER_SFG16_MICMUTE_LED), From bb6ff25035da56aa915e4974f68649fa74659e66 Mon Sep 17 00:00:00 2001 From: gongqi <550230171hxy@gmail.com> Date: Thu, 22 Jan 2026 23:55:00 +0800 Subject: [PATCH 3223/3902] platform/x86/amd/pmc: Add quirk for MECHREVO Wujie 15X Pro MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [ Upstream commit 2b4e00d8e70ca8736fda82447be6a4e323c6d1f5 ] The MECHREVO Wujie 15X Pro suffers from spurious IRQ issues related to the AMD PMC. Add it to the quirk list to use the spurious_8042 fix. Signed-off-by: gongqi <550230171hxy@gmail.com> Link: https://patch.msgid.link/20260122155501.376199-4-550230171hxy@gmail.com Signed-off-by: Ilpo Järvinen Signed-off-by: Sasha Levin --- drivers/platform/x86/amd/pmc/pmc-quirks.c | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/drivers/platform/x86/amd/pmc/pmc-quirks.c b/drivers/platform/x86/amd/pmc/pmc-quirks.c index 404e62ad293a98..ed285afaf9b0d7 100644 --- a/drivers/platform/x86/amd/pmc/pmc-quirks.c +++ b/drivers/platform/x86/amd/pmc/pmc-quirks.c @@ -302,6 +302,13 @@ static const struct dmi_system_id fwbug_list[] = { DMI_MATCH(DMI_BOARD_NAME, "XxKK4NAx_XxSP4NAx"), } }, + { + .ident = "MECHREVO Wujie 15X Pro", + .driver_data = &quirk_spurious_8042, + .matches = { + DMI_MATCH(DMI_BOARD_NAME, "WUJIE Series-X5SP4NAG"), + } + }, {} }; From 2ca80dd4bb0e2050586bb8c549bcc72d16bd8bcd Mon Sep 17 00:00:00 2001 From: Maciej Strozek Date: Wed, 28 Jan 2026 09:24:05 +0000 Subject: [PATCH 3224/3902] ASoC: sof_sdw: Add a quirk for Lenovo laptop using sidecar amps with cs42l43 [ Upstream commit 1425900231372acf870dd89e8d3bb4935f7f0c81 ] Add a quirk for a Lenovo laptop (SSID: 0x17aa3821) to allow using sidecar CS35L57 amps with CS42L43 codec. Signed-off-by: Maciej Strozek Reviewed-by: Cezary Rojewski Link: https://patch.msgid.link/20260128092410.1540583-1-mstrozek@opensource.cirrus.com Signed-off-by: Mark Brown Signed-off-by: Sasha Levin --- sound/soc/intel/boards/sof_sdw.c | 1 + 1 file changed, 1 insertion(+) diff --git a/sound/soc/intel/boards/sof_sdw.c b/sound/soc/intel/boards/sof_sdw.c index 92fac7ed782f73..6c95b1f8fc1a59 100644 --- a/sound/soc/intel/boards/sof_sdw.c +++ b/sound/soc/intel/boards/sof_sdw.c @@ -802,6 +802,7 @@ static const struct snd_pci_quirk sof_sdw_ssid_quirk_table[] = { SND_PCI_QUIRK(0x17aa, 0x2347, "Lenovo P16", SOC_SDW_CODEC_MIC), SND_PCI_QUIRK(0x17aa, 0x2348, "Lenovo P16", SOC_SDW_CODEC_MIC), SND_PCI_QUIRK(0x17aa, 0x2349, "Lenovo P1", SOC_SDW_CODEC_MIC), + SND_PCI_QUIRK(0x17aa, 0x3821, "Lenovo 0x3821", SOC_SDW_SIDECAR_AMPS), {} }; From a8ed5b974a24418d43697c2ee828df10c450b987 Mon Sep 17 00:00:00 2001 From: "Rafael J. Wysocki" Date: Tue, 20 Jan 2026 16:43:44 +0100 Subject: [PATCH 3225/3902] platform/x86: panasonic-laptop: Fix sysfs group leak in error path MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [ Upstream commit 43b0b7eff4b3fb684f257d5a24376782e9663465 ] The acpi_pcc_hotkey_add() error path leaks sysfs group pcc_attr_group if platform_device_register_simple() fails for the "panasonic" platform device. Address this by making it call sysfs_remove_group() in that case for the group in question. Signed-off-by: Rafael J. Wysocki Link: https://patch.msgid.link/3398370.44csPzL39Z@rafael.j.wysocki Reviewed-by: Ilpo Järvinen Signed-off-by: Ilpo Järvinen Signed-off-by: Sasha Levin --- drivers/platform/x86/panasonic-laptop.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/drivers/platform/x86/panasonic-laptop.c b/drivers/platform/x86/panasonic-laptop.c index 255317e6fec881..937f1a5b78edfb 100644 --- a/drivers/platform/x86/panasonic-laptop.c +++ b/drivers/platform/x86/panasonic-laptop.c @@ -1089,7 +1089,7 @@ static int acpi_pcc_hotkey_add(struct acpi_device *device) PLATFORM_DEVID_NONE, NULL, 0); if (IS_ERR(pcc->platform)) { result = PTR_ERR(pcc->platform); - goto out_backlight; + goto out_sysfs; } result = device_create_file(&pcc->platform->dev, &dev_attr_cdpower); @@ -1105,6 +1105,8 @@ static int acpi_pcc_hotkey_add(struct acpi_device *device) out_platform: platform_device_unregister(pcc->platform); +out_sysfs: + sysfs_remove_group(&device->dev.kobj, &pcc_attr_group); out_backlight: backlight_device_unregister(pcc->backlight); out_input: From 7bc40c6f36575291ed04470e0e15bddb19f5b6b6 Mon Sep 17 00:00:00 2001 From: Charles Keepax Date: Fri, 30 Jan 2026 15:09:27 +0000 Subject: [PATCH 3226/3902] ASoC: cs42l43: Correct handling of 3-pole jack load detection [ Upstream commit e77a4081d7e324dfa876a9560b2a78969446ba82 ] The load detection process for 3-pole jacks requires slightly updated reference values to ensure an accurate result. Update the code to apply different tunings for the 3-pole and 4-pole cases. This also updates the thresholds overall so update the relevant comments to match. Signed-off-by: Charles Keepax Link: https://patch.msgid.link/20260130150927.2964664-1-ckeepax@opensource.cirrus.com Signed-off-by: Mark Brown Signed-off-by: Sasha Levin --- sound/soc/codecs/cs42l43-jack.c | 37 +++++++++++++++++++++++++++------ 1 file changed, 31 insertions(+), 6 deletions(-) diff --git a/sound/soc/codecs/cs42l43-jack.c b/sound/soc/codecs/cs42l43-jack.c index 867e23d4fb8d8b..744488f371ea43 100644 --- a/sound/soc/codecs/cs42l43-jack.c +++ b/sound/soc/codecs/cs42l43-jack.c @@ -496,7 +496,23 @@ void cs42l43_bias_sense_timeout(struct work_struct *work) pm_runtime_put_autosuspend(priv->dev); } -static void cs42l43_start_load_detect(struct cs42l43_codec *priv) +static const struct reg_sequence cs42l43_3pole_patch[] = { + { 0x4000, 0x00000055 }, + { 0x4000, 0x000000AA }, + { 0x17420, 0x8500F300 }, + { 0x17424, 0x36003E00 }, + { 0x4000, 0x00000000 }, +}; + +static const struct reg_sequence cs42l43_4pole_patch[] = { + { 0x4000, 0x00000055 }, + { 0x4000, 0x000000AA }, + { 0x17420, 0x7800E600 }, + { 0x17424, 0x36003800 }, + { 0x4000, 0x00000000 }, +}; + +static void cs42l43_start_load_detect(struct cs42l43_codec *priv, bool mic) { struct cs42l43 *cs42l43 = priv->core; @@ -520,6 +536,15 @@ static void cs42l43_start_load_detect(struct cs42l43_codec *priv) dev_err(priv->dev, "Load detect HP power down timed out\n"); } + if (mic) + regmap_multi_reg_write_bypassed(cs42l43->regmap, + cs42l43_4pole_patch, + ARRAY_SIZE(cs42l43_4pole_patch)); + else + regmap_multi_reg_write_bypassed(cs42l43->regmap, + cs42l43_3pole_patch, + ARRAY_SIZE(cs42l43_3pole_patch)); + regmap_update_bits(cs42l43->regmap, CS42L43_BLOCK_EN3, CS42L43_ADC1_EN_MASK | CS42L43_ADC2_EN_MASK, 0); regmap_update_bits(cs42l43->regmap, CS42L43_DACCNFG2, CS42L43_HP_HPF_EN_MASK, 0); @@ -598,7 +623,7 @@ static int cs42l43_run_load_detect(struct cs42l43_codec *priv, bool mic) reinit_completion(&priv->load_detect); - cs42l43_start_load_detect(priv); + cs42l43_start_load_detect(priv, mic); time_left = wait_for_completion_timeout(&priv->load_detect, msecs_to_jiffies(CS42L43_LOAD_TIMEOUT_MS)); cs42l43_stop_load_detect(priv); @@ -622,11 +647,11 @@ static int cs42l43_run_load_detect(struct cs42l43_codec *priv, bool mic) } switch (val & CS42L43_AMP3_RES_DET_MASK) { - case 0x0: // low impedance - case 0x1: // high impedance + case 0x0: // < 22 Ohm impedance + case 0x1: // < 150 Ohm impedance + case 0x2: // < 1000 Ohm impedance return CS42L43_JACK_HEADPHONE; - case 0x2: // lineout - case 0x3: // Open circuit + case 0x3: // > 1000 Ohm impedance return CS42L43_JACK_LINEOUT; default: return -EINVAL; From f2584f791a10343bdc995ff6ff402db45b95de69 Mon Sep 17 00:00:00 2001 From: Deepanshu Kartikey Date: Fri, 30 Jan 2026 21:22:15 +0530 Subject: [PATCH 3227/3902] tracing/dma: Cap dma_map_sg tracepoint arrays to prevent buffer overflow [ Upstream commit daafcc0ef0b358d9d622b6e3b7c43767aa3814ee ] The dma_map_sg tracepoint can trigger a perf buffer overflow when tracing large scatter-gather lists. With devices like virtio-gpu creating large DRM buffers, nents can exceed 1000 entries, resulting in: phys_addrs: 1000 * 8 bytes = 8,000 bytes dma_addrs: 1000 * 8 bytes = 8,000 bytes lengths: 1000 * 4 bytes = 4,000 bytes Total: ~20,000 bytes This exceeds PERF_MAX_TRACE_SIZE (8192 bytes), causing: WARNING: CPU: 0 PID: 5497 at kernel/trace/trace_event_perf.c:405 perf buffer not large enough, wanted 24620, have 8192 Cap all three dynamic arrays at 128 entries using min() in the array size calculation. This ensures arrays are only as large as needed (up to the cap), avoiding unnecessary memory allocation for small operations while preventing overflow for large ones. The tracepoint now records the full nents/ents counts and a truncated flag so users can see when data has been capped. Changes in v2: - Use min(nents, DMA_TRACE_MAX_ENTRIES) for dynamic array sizing instead of fixed DMA_TRACE_MAX_ENTRIES allocation (feedback from Steven Rostedt) - This allocates only what's needed up to the cap, avoiding waste for small operations Reported-by: syzbot+28cea38c382fd15e751a@syzkaller.appspotmail.com Closes: https://syzkaller.appspot.com/bug?extid=28cea38c382fd15e751a Tested-by: syzbot+28cea38c382fd15e751a@syzkaller.appspotmail.com Signed-off-by: Deepanshu Kartikey Reviwed-by: Sean Anderson Signed-off-by: Marek Szyprowski Link: https://lore.kernel.org/r/20260130155215.69737-1-kartikey406@gmail.com Signed-off-by: Sasha Levin --- include/trace/events/dma.h | 25 +++++++++++++++++++------ 1 file changed, 19 insertions(+), 6 deletions(-) diff --git a/include/trace/events/dma.h b/include/trace/events/dma.h index b3fef140ae155b..33e99e792f1aa2 100644 --- a/include/trace/events/dma.h +++ b/include/trace/events/dma.h @@ -275,6 +275,8 @@ TRACE_EVENT(dma_free_sgt, sizeof(u64), sizeof(u64))) ); +#define DMA_TRACE_MAX_ENTRIES 128 + TRACE_EVENT(dma_map_sg, TP_PROTO(struct device *dev, struct scatterlist *sgl, int nents, int ents, enum dma_data_direction dir, unsigned long attrs), @@ -282,9 +284,12 @@ TRACE_EVENT(dma_map_sg, TP_STRUCT__entry( __string(device, dev_name(dev)) - __dynamic_array(u64, phys_addrs, nents) - __dynamic_array(u64, dma_addrs, ents) - __dynamic_array(unsigned int, lengths, ents) + __field(int, full_nents) + __field(int, full_ents) + __field(bool, truncated) + __dynamic_array(u64, phys_addrs, min(nents, DMA_TRACE_MAX_ENTRIES)) + __dynamic_array(u64, dma_addrs, min(ents, DMA_TRACE_MAX_ENTRIES)) + __dynamic_array(unsigned int, lengths, min(ents, DMA_TRACE_MAX_ENTRIES)) __field(enum dma_data_direction, dir) __field(unsigned long, attrs) ), @@ -292,11 +297,16 @@ TRACE_EVENT(dma_map_sg, TP_fast_assign( struct scatterlist *sg; int i; + int traced_nents = min_t(int, nents, DMA_TRACE_MAX_ENTRIES); + int traced_ents = min_t(int, ents, DMA_TRACE_MAX_ENTRIES); __assign_str(device); - for_each_sg(sgl, sg, nents, i) + __entry->full_nents = nents; + __entry->full_ents = ents; + __entry->truncated = (nents > DMA_TRACE_MAX_ENTRIES) || (ents > DMA_TRACE_MAX_ENTRIES); + for_each_sg(sgl, sg, traced_nents, i) ((u64 *)__get_dynamic_array(phys_addrs))[i] = sg_phys(sg); - for_each_sg(sgl, sg, ents, i) { + for_each_sg(sgl, sg, traced_ents, i) { ((u64 *)__get_dynamic_array(dma_addrs))[i] = sg_dma_address(sg); ((unsigned int *)__get_dynamic_array(lengths))[i] = @@ -306,9 +316,12 @@ TRACE_EVENT(dma_map_sg, __entry->attrs = attrs; ), - TP_printk("%s dir=%s dma_addrs=%s sizes=%s phys_addrs=%s attrs=%s", + TP_printk("%s dir=%s nents=%d/%d ents=%d/%d%s dma_addrs=%s sizes=%s phys_addrs=%s attrs=%s", __get_str(device), decode_dma_data_direction(__entry->dir), + min_t(int, __entry->full_nents, DMA_TRACE_MAX_ENTRIES), __entry->full_nents, + min_t(int, __entry->full_ents, DMA_TRACE_MAX_ENTRIES), __entry->full_ents, + __entry->truncated ? " [TRUNCATED]" : "", __print_array(__get_dynamic_array(dma_addrs), __get_dynamic_array_len(dma_addrs) / sizeof(u64), sizeof(u64)), From 8e6f50396170939e4444ae18cd488f24b41bd5fd Mon Sep 17 00:00:00 2001 From: Melissa Wen Date: Mon, 8 Dec 2025 22:44:15 -0100 Subject: [PATCH 3228/3902] drm/amd/display: extend delta clamping logic to CM3 LUT helper [ Upstream commit d25b32aa829a3ed5570138e541a71fb7805faec3 ] Commit 27fc10d1095f ("drm/amd/display: Fix the delta clamping for shaper LUT") fixed banding when using plane shaper LUT in DCN10 CM helper. The problem is also present in DCN30 CM helper, fix banding by extending the same bug delta clamping fix to CM3. Signed-off-by: Melissa Wen Reviewed-by: Harry Wentland Signed-off-by: Alex Deucher (cherry picked from commit 0274a54897f356f9c78767c4a2a5863f7dde90c6) Signed-off-by: Sasha Levin --- .../amd/display/dc/dcn30/dcn30_cm_common.c | 30 +++++++++++++++---- .../display/dc/dwb/dcn30/dcn30_cm_common.h | 2 +- .../amd/display/dc/hwss/dcn30/dcn30_hwseq.c | 9 +++--- .../amd/display/dc/hwss/dcn32/dcn32_hwseq.c | 17 ++++++----- .../amd/display/dc/hwss/dcn401/dcn401_hwseq.c | 16 +++++----- 5 files changed, 49 insertions(+), 25 deletions(-) diff --git a/drivers/gpu/drm/amd/display/dc/dcn30/dcn30_cm_common.c b/drivers/gpu/drm/amd/display/dc/dcn30/dcn30_cm_common.c index a4f14b16564c2f..227aa8672d17ba 100644 --- a/drivers/gpu/drm/amd/display/dc/dcn30/dcn30_cm_common.c +++ b/drivers/gpu/drm/amd/display/dc/dcn30/dcn30_cm_common.c @@ -105,9 +105,12 @@ void cm_helper_program_gamcor_xfer_func( #define NUMBER_REGIONS 32 #define NUMBER_SW_SEGMENTS 16 -bool cm3_helper_translate_curve_to_hw_format( - const struct dc_transfer_func *output_tf, - struct pwl_params *lut_params, bool fixpoint) +#define DC_LOGGER \ + ctx->logger + +bool cm3_helper_translate_curve_to_hw_format(struct dc_context *ctx, + const struct dc_transfer_func *output_tf, + struct pwl_params *lut_params, bool fixpoint) { struct curve_points3 *corner_points; struct pwl_result_data *rgb_resulted; @@ -251,6 +254,10 @@ bool cm3_helper_translate_curve_to_hw_format( if (fixpoint == true) { i = 1; while (i != hw_points + 2) { + uint32_t red_clamp; + uint32_t green_clamp; + uint32_t blue_clamp; + if (i >= hw_points) { if (dc_fixpt_lt(rgb_plus_1->red, rgb->red)) rgb_plus_1->red = dc_fixpt_add(rgb->red, @@ -263,9 +270,20 @@ bool cm3_helper_translate_curve_to_hw_format( rgb_minus_1->delta_blue); } - rgb->delta_red_reg = dc_fixpt_clamp_u0d10(rgb->delta_red); - rgb->delta_green_reg = dc_fixpt_clamp_u0d10(rgb->delta_green); - rgb->delta_blue_reg = dc_fixpt_clamp_u0d10(rgb->delta_blue); + rgb->delta_red = dc_fixpt_sub(rgb_plus_1->red, rgb->red); + rgb->delta_green = dc_fixpt_sub(rgb_plus_1->green, rgb->green); + rgb->delta_blue = dc_fixpt_sub(rgb_plus_1->blue, rgb->blue); + + red_clamp = dc_fixpt_clamp_u0d14(rgb->delta_red); + green_clamp = dc_fixpt_clamp_u0d14(rgb->delta_green); + blue_clamp = dc_fixpt_clamp_u0d14(rgb->delta_blue); + + if (red_clamp >> 10 || green_clamp >> 10 || blue_clamp >> 10) + DC_LOG_ERROR("Losing delta precision while programming shaper LUT."); + + rgb->delta_red_reg = red_clamp & 0x3ff; + rgb->delta_green_reg = green_clamp & 0x3ff; + rgb->delta_blue_reg = blue_clamp & 0x3ff; rgb->red_reg = dc_fixpt_clamp_u0d14(rgb->red); rgb->green_reg = dc_fixpt_clamp_u0d14(rgb->green); rgb->blue_reg = dc_fixpt_clamp_u0d14(rgb->blue); diff --git a/drivers/gpu/drm/amd/display/dc/dwb/dcn30/dcn30_cm_common.h b/drivers/gpu/drm/amd/display/dc/dwb/dcn30/dcn30_cm_common.h index b86347c9b0389d..95f9318a54efce 100644 --- a/drivers/gpu/drm/amd/display/dc/dwb/dcn30/dcn30_cm_common.h +++ b/drivers/gpu/drm/amd/display/dc/dwb/dcn30/dcn30_cm_common.h @@ -59,7 +59,7 @@ void cm_helper_program_gamcor_xfer_func( const struct pwl_params *params, const struct dcn3_xfer_func_reg *reg); -bool cm3_helper_translate_curve_to_hw_format( +bool cm3_helper_translate_curve_to_hw_format(struct dc_context *ctx, const struct dc_transfer_func *output_tf, struct pwl_params *lut_params, bool fixpoint); diff --git a/drivers/gpu/drm/amd/display/dc/hwss/dcn30/dcn30_hwseq.c b/drivers/gpu/drm/amd/display/dc/hwss/dcn30/dcn30_hwseq.c index e47ed5571dfdd8..731645a2ab9aa7 100644 --- a/drivers/gpu/drm/amd/display/dc/hwss/dcn30/dcn30_hwseq.c +++ b/drivers/gpu/drm/amd/display/dc/hwss/dcn30/dcn30_hwseq.c @@ -238,7 +238,7 @@ bool dcn30_set_blend_lut( if (plane_state->blend_tf.type == TF_TYPE_HWPWL) blend_lut = &plane_state->blend_tf.pwl; else if (plane_state->blend_tf.type == TF_TYPE_DISTRIBUTED_POINTS) { - result = cm3_helper_translate_curve_to_hw_format( + result = cm3_helper_translate_curve_to_hw_format(plane_state->ctx, &plane_state->blend_tf, &dpp_base->regamma_params, false); if (!result) return result; @@ -333,8 +333,9 @@ bool dcn30_set_input_transfer_func(struct dc *dc, if (plane_state->in_transfer_func.type == TF_TYPE_HWPWL) params = &plane_state->in_transfer_func.pwl; else if (plane_state->in_transfer_func.type == TF_TYPE_DISTRIBUTED_POINTS && - cm3_helper_translate_curve_to_hw_format(&plane_state->in_transfer_func, - &dpp_base->degamma_params, false)) + cm3_helper_translate_curve_to_hw_format(plane_state->ctx, + &plane_state->in_transfer_func, + &dpp_base->degamma_params, false)) params = &dpp_base->degamma_params; result = dpp_base->funcs->dpp_program_gamcor_lut(dpp_base, params); @@ -405,7 +406,7 @@ bool dcn30_set_output_transfer_func(struct dc *dc, params = &stream->out_transfer_func.pwl; else if (pipe_ctx->stream->out_transfer_func.type == TF_TYPE_DISTRIBUTED_POINTS && - cm3_helper_translate_curve_to_hw_format( + cm3_helper_translate_curve_to_hw_format(stream->ctx, &stream->out_transfer_func, &mpc->blender_params, false)) params = &mpc->blender_params; diff --git a/drivers/gpu/drm/amd/display/dc/hwss/dcn32/dcn32_hwseq.c b/drivers/gpu/drm/amd/display/dc/hwss/dcn32/dcn32_hwseq.c index f39292952702fa..30bb5d8d85dc27 100644 --- a/drivers/gpu/drm/amd/display/dc/hwss/dcn32/dcn32_hwseq.c +++ b/drivers/gpu/drm/amd/display/dc/hwss/dcn32/dcn32_hwseq.c @@ -486,8 +486,9 @@ bool dcn32_set_mcm_luts( if (plane_state->blend_tf.type == TF_TYPE_HWPWL) lut_params = &plane_state->blend_tf.pwl; else if (plane_state->blend_tf.type == TF_TYPE_DISTRIBUTED_POINTS) { - result = cm3_helper_translate_curve_to_hw_format(&plane_state->blend_tf, - &dpp_base->regamma_params, false); + result = cm3_helper_translate_curve_to_hw_format(plane_state->ctx, + &plane_state->blend_tf, + &dpp_base->regamma_params, false); if (!result) return result; @@ -502,8 +503,9 @@ bool dcn32_set_mcm_luts( else if (plane_state->in_shaper_func.type == TF_TYPE_DISTRIBUTED_POINTS) { // TODO: dpp_base replace ASSERT(false); - cm3_helper_translate_curve_to_hw_format(&plane_state->in_shaper_func, - &dpp_base->shaper_params, true); + cm3_helper_translate_curve_to_hw_format(plane_state->ctx, + &plane_state->in_shaper_func, + &dpp_base->shaper_params, true); lut_params = &dpp_base->shaper_params; } @@ -543,8 +545,9 @@ bool dcn32_set_input_transfer_func(struct dc *dc, if (plane_state->in_transfer_func.type == TF_TYPE_HWPWL) params = &plane_state->in_transfer_func.pwl; else if (plane_state->in_transfer_func.type == TF_TYPE_DISTRIBUTED_POINTS && - cm3_helper_translate_curve_to_hw_format(&plane_state->in_transfer_func, - &dpp_base->degamma_params, false)) + cm3_helper_translate_curve_to_hw_format(plane_state->ctx, + &plane_state->in_transfer_func, + &dpp_base->degamma_params, false)) params = &dpp_base->degamma_params; dpp_base->funcs->dpp_program_gamcor_lut(dpp_base, params); @@ -575,7 +578,7 @@ bool dcn32_set_output_transfer_func(struct dc *dc, params = &stream->out_transfer_func.pwl; else if (pipe_ctx->stream->out_transfer_func.type == TF_TYPE_DISTRIBUTED_POINTS && - cm3_helper_translate_curve_to_hw_format( + cm3_helper_translate_curve_to_hw_format(stream->ctx, &stream->out_transfer_func, &mpc->blender_params, false)) params = &mpc->blender_params; diff --git a/drivers/gpu/drm/amd/display/dc/hwss/dcn401/dcn401_hwseq.c b/drivers/gpu/drm/amd/display/dc/hwss/dcn401/dcn401_hwseq.c index 68e48a2492c9ee..77cdd02a41bdd0 100644 --- a/drivers/gpu/drm/amd/display/dc/hwss/dcn401/dcn401_hwseq.c +++ b/drivers/gpu/drm/amd/display/dc/hwss/dcn401/dcn401_hwseq.c @@ -427,7 +427,7 @@ void dcn401_populate_mcm_luts(struct dc *dc, if (mcm_luts.lut1d_func->type == TF_TYPE_HWPWL) m_lut_params.pwl = &mcm_luts.lut1d_func->pwl; else if (mcm_luts.lut1d_func->type == TF_TYPE_DISTRIBUTED_POINTS) { - rval = cm3_helper_translate_curve_to_hw_format( + rval = cm3_helper_translate_curve_to_hw_format(mpc->ctx, mcm_luts.lut1d_func, &dpp_base->regamma_params, false); m_lut_params.pwl = rval ? &dpp_base->regamma_params : NULL; @@ -447,7 +447,7 @@ void dcn401_populate_mcm_luts(struct dc *dc, m_lut_params.pwl = &mcm_luts.shaper->pwl; else if (mcm_luts.shaper->type == TF_TYPE_DISTRIBUTED_POINTS) { ASSERT(false); - rval = cm3_helper_translate_curve_to_hw_format( + rval = cm3_helper_translate_curve_to_hw_format(mpc->ctx, mcm_luts.shaper, &dpp_base->regamma_params, true); m_lut_params.pwl = rval ? &dpp_base->regamma_params : NULL; @@ -624,8 +624,9 @@ bool dcn401_set_mcm_luts(struct pipe_ctx *pipe_ctx, if (plane_state->blend_tf.type == TF_TYPE_HWPWL) lut_params = &plane_state->blend_tf.pwl; else if (plane_state->blend_tf.type == TF_TYPE_DISTRIBUTED_POINTS) { - rval = cm3_helper_translate_curve_to_hw_format(&plane_state->blend_tf, - &dpp_base->regamma_params, false); + rval = cm3_helper_translate_curve_to_hw_format(plane_state->ctx, + &plane_state->blend_tf, + &dpp_base->regamma_params, false); lut_params = rval ? &dpp_base->regamma_params : NULL; } result = mpc->funcs->program_1dlut(mpc, lut_params, mpcc_id); @@ -636,8 +637,9 @@ bool dcn401_set_mcm_luts(struct pipe_ctx *pipe_ctx, lut_params = &plane_state->in_shaper_func.pwl; else if (plane_state->in_shaper_func.type == TF_TYPE_DISTRIBUTED_POINTS) { // TODO: dpp_base replace - rval = cm3_helper_translate_curve_to_hw_format(&plane_state->in_shaper_func, - &dpp_base->shaper_params, true); + rval = cm3_helper_translate_curve_to_hw_format(plane_state->ctx, + &plane_state->in_shaper_func, + &dpp_base->shaper_params, true); lut_params = rval ? &dpp_base->shaper_params : NULL; } result &= mpc->funcs->program_shaper(mpc, lut_params, mpcc_id); @@ -671,7 +673,7 @@ bool dcn401_set_output_transfer_func(struct dc *dc, params = &stream->out_transfer_func.pwl; else if (pipe_ctx->stream->out_transfer_func.type == TF_TYPE_DISTRIBUTED_POINTS && - cm3_helper_translate_curve_to_hw_format( + cm3_helper_translate_curve_to_hw_format(stream->ctx, &stream->out_transfer_func, &mpc->blender_params, false)) params = &mpc->blender_params; From 00c57e2369386e244de92db0d58548cea574fedd Mon Sep 17 00:00:00 2001 From: Melissa Wen Date: Fri, 16 Jan 2026 12:50:49 -0300 Subject: [PATCH 3229/3902] drm/amd/display: remove assert around dpp_base replacement [ Upstream commit 84962445cd8a83dc5bed4c8ad5bbb2c1cdb249a0 ] There is nothing wrong if in_shaper_func type is DISTRIBUTED POINTS. Remove the assert placed for a TODO to avoid misinterpretations. Signed-off-by: Melissa Wen Reviewed-by: Alex Hung Signed-off-by: Alex Deucher (cherry picked from commit 1714dcc4c2c53e41190896eba263ed6328bcf415) Signed-off-by: Sasha Levin --- drivers/gpu/drm/amd/display/dc/hwss/dcn32/dcn32_hwseq.c | 1 - 1 file changed, 1 deletion(-) diff --git a/drivers/gpu/drm/amd/display/dc/hwss/dcn32/dcn32_hwseq.c b/drivers/gpu/drm/amd/display/dc/hwss/dcn32/dcn32_hwseq.c index 30bb5d8d85dc27..c6fde355ac8235 100644 --- a/drivers/gpu/drm/amd/display/dc/hwss/dcn32/dcn32_hwseq.c +++ b/drivers/gpu/drm/amd/display/dc/hwss/dcn32/dcn32_hwseq.c @@ -502,7 +502,6 @@ bool dcn32_set_mcm_luts( lut_params = &plane_state->in_shaper_func.pwl; else if (plane_state->in_shaper_func.type == TF_TYPE_DISTRIBUTED_POINTS) { // TODO: dpp_base replace - ASSERT(false); cm3_helper_translate_curve_to_hw_format(plane_state->ctx, &plane_state->in_shaper_func, &dpp_base->shaper_params, true); From cab928242853a832ffa7efda270ecfb9efeebb6e Mon Sep 17 00:00:00 2001 From: Ziyi Guo Date: Mon, 2 Feb 2026 17:41:12 +0000 Subject: [PATCH 3230/3902] ASoC: fsl_xcvr: fix missing lock in fsl_xcvr_mode_put() [ Upstream commit f514248727606b9087bc38a284ff686e0093abf1 ] fsl_xcvr_activate_ctl() has lockdep_assert_held(&card->snd_card->controls_rwsem), but fsl_xcvr_mode_put() calls it without acquiring this lock. Other callers of fsl_xcvr_activate_ctl() in fsl_xcvr_startup() and fsl_xcvr_shutdown() properly acquire the lock with down_read()/up_read(). Add the missing down_read()/up_read() calls around fsl_xcvr_activate_ctl() in fsl_xcvr_mode_put() to fix the lockdep assertion and prevent potential race conditions when multiple userspace threads access the control. Signed-off-by: Ziyi Guo Link: https://patch.msgid.link/20260202174112.2018402-1-n7l8m4@u.northwestern.edu Signed-off-by: Mark Brown Signed-off-by: Sasha Levin --- sound/soc/fsl/fsl_xcvr.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/sound/soc/fsl/fsl_xcvr.c b/sound/soc/fsl/fsl_xcvr.c index 58db4906a01d50..51669e5fe8888e 100644 --- a/sound/soc/fsl/fsl_xcvr.c +++ b/sound/soc/fsl/fsl_xcvr.c @@ -223,10 +223,13 @@ static int fsl_xcvr_mode_put(struct snd_kcontrol *kcontrol, xcvr->mode = snd_soc_enum_item_to_val(e, item[0]); + down_read(&card->snd_card->controls_rwsem); fsl_xcvr_activate_ctl(dai, fsl_xcvr_arc_mode_kctl.name, (xcvr->mode == FSL_XCVR_MODE_ARC)); fsl_xcvr_activate_ctl(dai, fsl_xcvr_earc_capds_kctl.name, (xcvr->mode == FSL_XCVR_MODE_EARC)); + up_read(&card->snd_card->controls_rwsem); + /* Allow playback for SPDIF only */ rtd = snd_soc_get_pcm_runtime(card, card->dai_link); rtd->pcm->streams[SNDRV_PCM_STREAM_PLAYBACK].substream_count = From fa4938e0d0d64a5623629470eefb5547495e7aa6 Mon Sep 17 00:00:00 2001 From: Jens Axboe Date: Tue, 3 Feb 2026 09:56:55 -0700 Subject: [PATCH 3231/3902] io_uring/fdinfo: be a bit nicer when looping a lot of SQEs/CQEs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [ Upstream commit 38cfdd9dd279473a73814df9fd7e6e716951d361 ] Add cond_resched() in those dump loops, just in case a lot of entries are being dumped. And detect invalid CQ ring head/tail entries, to avoid iterating more than what is necessary. Generally not an issue, but can be if things like KASAN or other debugging metrics are enabled. Reported-by: 是参差 Link: https://lore.kernel.org/all/PS1PPF7E1D7501FE5631002D242DD89403FAB9BA@PS1PPF7E1D7501F.apcprd02.prod.outlook.com/ Reviewed-by: Keith Busch Signed-off-by: Jens Axboe Signed-off-by: Sasha Levin --- io_uring/fdinfo.c | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/io_uring/fdinfo.c b/io_uring/fdinfo.c index 294c75a8a3bdbc..3585ad8308504a 100644 --- a/io_uring/fdinfo.c +++ b/io_uring/fdinfo.c @@ -65,7 +65,7 @@ static void __io_uring_show_fdinfo(struct io_ring_ctx *ctx, struct seq_file *m) unsigned int cq_head = READ_ONCE(r->cq.head); unsigned int cq_tail = READ_ONCE(r->cq.tail); unsigned int sq_shift = 0; - unsigned int sq_entries; + unsigned int cq_entries, sq_entries; int sq_pid = -1, sq_cpu = -1; u64 sq_total_time = 0, sq_work_time = 0; unsigned int i; @@ -119,9 +119,11 @@ static void __io_uring_show_fdinfo(struct io_ring_ctx *ctx, struct seq_file *m) } } seq_printf(m, "\n"); + cond_resched(); } seq_printf(m, "CQEs:\t%u\n", cq_tail - cq_head); - while (cq_head < cq_tail) { + cq_entries = min(cq_tail - cq_head, ctx->cq_entries); + for (i = 0; i < cq_entries; i++) { struct io_uring_cqe *cqe; bool cqe32 = false; @@ -136,8 +138,11 @@ static void __io_uring_show_fdinfo(struct io_ring_ctx *ctx, struct seq_file *m) cqe->big_cqe[0], cqe->big_cqe[1]); seq_printf(m, "\n"); cq_head++; - if (cqe32) + if (cqe32) { cq_head++; + i++; + } + cond_resched(); } if (ctx->flags & IORING_SETUP_SQPOLL) { From 6d66464d1a059d92df8a5454d5bc9414f6ec0b03 Mon Sep 17 00:00:00 2001 From: Alban Bedel Date: Thu, 29 Jan 2026 15:59:44 +0100 Subject: [PATCH 3232/3902] gpiolib: acpi: Fix gpio count with string references [ Upstream commit c62e0658d458d8f100445445c3ddb106f3824a45 ] Since commit 9880702d123f2 ("ACPI: property: Support using strings in reference properties") it is possible to use strings instead of local references. This work fine with single GPIO but not with arrays as acpi_gpio_package_count() didn't handle this case. Update it to handle strings like local references to cover this case as well. Signed-off-by: Alban Bedel Reviewed-by: Mika Westerberg Link: https://patch.msgid.link/20260129145944.3372777-1-alban.bedel@lht.dlh.de Signed-off-by: Bartosz Golaszewski Signed-off-by: Sasha Levin --- drivers/gpio/gpiolib-acpi-core.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/gpio/gpiolib-acpi-core.c b/drivers/gpio/gpiolib-acpi-core.c index e64e21fd6bbaaa..8110690ea69d0b 100644 --- a/drivers/gpio/gpiolib-acpi-core.c +++ b/drivers/gpio/gpiolib-acpi-core.c @@ -1359,6 +1359,7 @@ static int acpi_gpio_package_count(const union acpi_object *obj) while (element < end) { switch (element->type) { case ACPI_TYPE_LOCAL_REFERENCE: + case ACPI_TYPE_STRING: element += 3; fallthrough; case ACPI_TYPE_INTEGER: From 0c74d37343b93ac6503fceb762459d1fb9dea486 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Otto=20Pfl=C3=BCger?= Date: Tue, 6 Jan 2026 11:11:12 +0100 Subject: [PATCH 3233/3902] arm64: dts: mediatek: mt8183: Add missing endpoint IDs to display graph MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit commit be0b304eeb8c5f77e4f98f64e58729d879195f2f upstream. The endpoint IDs in the display graph are expected to match the associated display path number, i.e. all endpoints connected to mmsys_ep_main must have reg = <0> and all endpoints connected to mmsys_ep_ext must have reg = <1>. Add the missing ID to all endpoints in the display graph, based on mt8365.dtsi as an existing example that does this correctly. Fixes: e72d63fa0563 ("arm64: dts: mediatek: mt8183: Migrate to display controller OF graph") Reported-by: Evans Jahja Closes: https://lore.kernel.org/linux-mediatek/CAAq5pW9o3itC0G16LnJO7KMAQ_XoqXUpB=cuJ_7e3-H11zKd5Q@mail.gmail.com/ Tested-by: Chen-Yu Tsai Signed-off-by: Otto Pflüger [Angelo: Fixed dtbs_check issues] Signed-off-by: AngeloGioacchino Del Regno Cc: Thorsten Leemhuis Signed-off-by: Greg Kroah-Hartman --- arch/arm64/boot/dts/mediatek/mt8183.dtsi | 37 ++++++++++++++++++++---- 1 file changed, 31 insertions(+), 6 deletions(-) diff --git a/arch/arm64/boot/dts/mediatek/mt8183.dtsi b/arch/arm64/boot/dts/mediatek/mt8183.dtsi index 960d8955d018c1..27ce4b31cc99de 100644 --- a/arch/arm64/boot/dts/mediatek/mt8183.dtsi +++ b/arch/arm64/boot/dts/mediatek/mt8183.dtsi @@ -1812,15 +1812,23 @@ #size-cells = <0>; port@0 { + #address-cells = <1>; + #size-cells = <0>; reg = <0>; - ovl_2l1_in: endpoint { + + ovl_2l1_in: endpoint@1 { + reg = <1>; remote-endpoint = <&mmsys_ep_ext>; }; }; port@1 { + #address-cells = <1>; + #size-cells = <0>; reg = <1>; - ovl_2l1_out: endpoint { + + ovl_2l1_out: endpoint@1 { + reg = <1>; remote-endpoint = <&rdma1_in>; }; }; @@ -1872,15 +1880,23 @@ #size-cells = <0>; port@0 { + #address-cells = <1>; + #size-cells = <0>; reg = <0>; - rdma1_in: endpoint { + + rdma1_in: endpoint@1 { + reg = <1>; remote-endpoint = <&ovl_2l1_out>; }; }; port@1 { + #address-cells = <1>; + #size-cells = <0>; reg = <1>; - rdma1_out: endpoint { + + rdma1_out: endpoint@1 { + reg = <1>; remote-endpoint = <&dpi_in>; }; }; @@ -2076,15 +2092,24 @@ #size-cells = <0>; port@0 { + #address-cells = <1>; + #size-cells = <0>; reg = <0>; - dpi_in: endpoint { + + dpi_in: endpoint@1 { + reg = <1>; remote-endpoint = <&rdma1_out>; }; }; port@1 { + #address-cells = <1>; + #size-cells = <0>; reg = <1>; - dpi_out: endpoint { }; + + dpi_out: endpoint@1 { + reg = <1>; + }; }; }; }; From 9b671f6f432be07c0ddd66e437d6d0e0db684f83 Mon Sep 17 00:00:00 2001 From: "David Hildenbrand (Red Hat)" Date: Tue, 23 Dec 2025 22:40:37 +0100 Subject: [PATCH 3234/3902] mm/hugetlb: fix excessive IPI broadcasts when unsharing PMD tables using mmu_gather commit 8ce720d5bd91e9dc16db3604aa4b1bf76770a9a1 upstream. As reported, ever since commit 1013af4f585f ("mm/hugetlb: fix huge_pmd_unshare() vs GUP-fast race") we can end up in some situations where we perform so many IPI broadcasts when unsharing hugetlb PMD page tables that it severely regresses some workloads. In particular, when we fork()+exit(), or when we munmap() a large area backed by many shared PMD tables, we perform one IPI broadcast per unshared PMD table. There are two optimizations to be had: (1) When we process (unshare) multiple such PMD tables, such as during exit(), it is sufficient to send a single IPI broadcast (as long as we respect locking rules) instead of one per PMD table. Locking prevents that any of these PMD tables could get reused before we drop the lock. (2) When we are not the last sharer (> 2 users including us), there is no need to send the IPI broadcast. The shared PMD tables cannot become exclusive (fully unshared) before an IPI will be broadcasted by the last sharer. Concurrent GUP-fast could walk into a PMD table just before we unshared it. It could then succeed in grabbing a page from the shared page table even after munmap() etc succeeded (and supressed an IPI). But there is not difference compared to GUP-fast just sleeping for a while after grabbing the page and re-enabling IRQs. Most importantly, GUP-fast will never walk into page tables that are no-longer shared, because the last sharer will issue an IPI broadcast. (if ever required, checking whether the PUD changed in GUP-fast after grabbing the page like we do in the PTE case could handle this) So let's rework PMD sharing TLB flushing + IPI sync to use the mmu_gather infrastructure so we can implement these optimizations and demystify the code at least a bit. Extend the mmu_gather infrastructure to be able to deal with our special hugetlb PMD table sharing implementation. To make initialization of the mmu_gather easier when working on a single VMA (in particular, when dealing with hugetlb), provide tlb_gather_mmu_vma(). We'll consolidate the handling for (full) unsharing of PMD tables in tlb_unshare_pmd_ptdesc() and tlb_flush_unshared_tables(), and track in "struct mmu_gather" whether we had (full) unsharing of PMD tables. Because locking is very special (concurrent unsharing+reuse must be prevented), we disallow deferring flushing to tlb_finish_mmu() and instead require an explicit earlier call to tlb_flush_unshared_tables(). From hugetlb code, we call huge_pmd_unshare_flush() where we make sure that the expected lock protecting us from concurrent unsharing+reuse is still held. Check with a VM_WARN_ON_ONCE() in tlb_finish_mmu() that tlb_flush_unshared_tables() was properly called earlier. Document it all properly. Notes about tlb_remove_table_sync_one() interaction with unsharing: There are two fairly tricky things: (1) tlb_remove_table_sync_one() is a NOP on architectures without CONFIG_MMU_GATHER_RCU_TABLE_FREE. Here, the assumption is that the previous TLB flush would send an IPI to all relevant CPUs. Careful: some architectures like x86 only send IPIs to all relevant CPUs when tlb->freed_tables is set. The relevant architectures should be selecting MMU_GATHER_RCU_TABLE_FREE, but x86 might not do that in stable kernels and it might have been problematic before this patch. Also, the arch flushing behavior (independent of IPIs) is different when tlb->freed_tables is set. Do we have to enlighten them to also take care of tlb->unshared_tables? So far we didn't care, so hopefully we are fine. Of course, we could be setting tlb->freed_tables as well, but that might then unnecessarily flush too much, because the semantics of tlb->freed_tables are a bit fuzzy. This patch changes nothing in this regard. (2) tlb_remove_table_sync_one() is not a NOP on architectures with CONFIG_MMU_GATHER_RCU_TABLE_FREE that actually don't need a sync. Take x86 as an example: in the common case (!pv, !X86_FEATURE_INVLPGB) we still issue IPIs during TLB flushes and don't actually need the second tlb_remove_table_sync_one(). This optimized can be implemented on top of this, by checking e.g., in tlb_remove_table_sync_one() whether we really need IPIs. But as described in (1), it really must honor tlb->freed_tables then to send IPIs to all relevant CPUs. Notes on TLB flushing changes: (1) Flushing for non-shared PMD tables We're converting from flush_hugetlb_tlb_range() to tlb_remove_huge_tlb_entry(). Given that we properly initialize the MMU gather in tlb_gather_mmu_vma() to be hugetlb aware, similar to __unmap_hugepage_range(), that should be fine. (2) Flushing for shared PMD tables We're converting from various things (flush_hugetlb_tlb_range(), tlb_flush_pmd_range(), flush_tlb_range()) to tlb_flush_pmd_range(). tlb_flush_pmd_range() achieves the same that tlb_remove_huge_tlb_entry() would achieve in these scenarios. Note that tlb_remove_huge_tlb_entry() also calls __tlb_remove_tlb_entry(), however that is only implemented on powerpc, which does not support PMD table sharing. Similar to (1), tlb_gather_mmu_vma() should make sure that TLB flushing keeps on working as expected. Further, note that the ptdesc_pmd_pts_dec() in huge_pmd_share() is not a concern, as we are holding the i_mmap_lock the whole time, preventing concurrent unsharing. That ptdesc_pmd_pts_dec() usage will be removed separately as a cleanup later. There are plenty more cleanups to be had, but they have to wait until this is fixed. [david@kernel.org: fix kerneldoc] Link: https://lkml.kernel.org/r/f223dd74-331c-412d-93fc-69e360a5006c@kernel.org Link: https://lkml.kernel.org/r/20251223214037.580860-5-david@kernel.org Fixes: 1013af4f585f ("mm/hugetlb: fix huge_pmd_unshare() vs GUP-fast race") Signed-off-by: David Hildenbrand (Red Hat) Reported-by: "Uschakow, Stanislav" Closes: https://lore.kernel.org/all/4d3878531c76479d9f8ca9789dc6485d@amazon.de/ Tested-by: Laurence Oberman Acked-by: Harry Yoo Reviewed-by: Lorenzo Stoakes Cc: Lance Yang Cc: Liu Shixin Cc: Oscar Salvador Cc: Rik van Riel Cc: Signed-off-by: Andrew Morton Signed-off-by: David Hildenbrand (Arm) Signed-off-by: Greg Kroah-Hartman --- include/asm-generic/tlb.h | 77 +++++++++++++++++++++++- include/linux/hugetlb.h | 15 +++-- include/linux/mm_types.h | 1 + mm/hugetlb.c | 123 ++++++++++++++++++++++---------------- mm/mmu_gather.c | 33 ++++++++++ mm/rmap.c | 25 +++++--- 6 files changed, 208 insertions(+), 66 deletions(-) diff --git a/include/asm-generic/tlb.h b/include/asm-generic/tlb.h index 1fff717cae510d..4d679d2a206b46 100644 --- a/include/asm-generic/tlb.h +++ b/include/asm-generic/tlb.h @@ -46,7 +46,8 @@ * * The mmu_gather API consists of: * - * - tlb_gather_mmu() / tlb_gather_mmu_fullmm() / tlb_finish_mmu() + * - tlb_gather_mmu() / tlb_gather_mmu_fullmm() / tlb_gather_mmu_vma() / + * tlb_finish_mmu() * * start and finish a mmu_gather * @@ -364,6 +365,20 @@ struct mmu_gather { unsigned int vma_huge : 1; unsigned int vma_pfn : 1; + /* + * Did we unshare (unmap) any shared page tables? For now only + * used for hugetlb PMD table sharing. + */ + unsigned int unshared_tables : 1; + + /* + * Did we unshare any page tables such that they are now exclusive + * and could get reused+modified by the new owner? When setting this + * flag, "unshared_tables" will be set as well. For now only used + * for hugetlb PMD table sharing. + */ + unsigned int fully_unshared_tables : 1; + unsigned int batch_count; #ifndef CONFIG_MMU_GATHER_NO_GATHER @@ -400,6 +415,7 @@ static inline void __tlb_reset_range(struct mmu_gather *tlb) tlb->cleared_pmds = 0; tlb->cleared_puds = 0; tlb->cleared_p4ds = 0; + tlb->unshared_tables = 0; /* * Do not reset mmu_gather::vma_* fields here, we do not * call into tlb_start_vma() again to set them if there is an @@ -484,7 +500,7 @@ static inline void tlb_flush_mmu_tlbonly(struct mmu_gather *tlb) * these bits. */ if (!(tlb->freed_tables || tlb->cleared_ptes || tlb->cleared_pmds || - tlb->cleared_puds || tlb->cleared_p4ds)) + tlb->cleared_puds || tlb->cleared_p4ds || tlb->unshared_tables)) return; tlb_flush(tlb); @@ -773,6 +789,63 @@ static inline bool huge_pmd_needs_flush(pmd_t oldpmd, pmd_t newpmd) } #endif +#ifdef CONFIG_HUGETLB_PMD_PAGE_TABLE_SHARING +static inline void tlb_unshare_pmd_ptdesc(struct mmu_gather *tlb, struct ptdesc *pt, + unsigned long addr) +{ + /* + * The caller must make sure that concurrent unsharing + exclusive + * reuse is impossible until tlb_flush_unshared_tables() was called. + */ + VM_WARN_ON_ONCE(!ptdesc_pmd_is_shared(pt)); + ptdesc_pmd_pts_dec(pt); + + /* Clearing a PUD pointing at a PMD table with PMD leaves. */ + tlb_flush_pmd_range(tlb, addr & PUD_MASK, PUD_SIZE); + + /* + * If the page table is now exclusively owned, we fully unshared + * a page table. + */ + if (!ptdesc_pmd_is_shared(pt)) + tlb->fully_unshared_tables = true; + tlb->unshared_tables = true; +} + +static inline void tlb_flush_unshared_tables(struct mmu_gather *tlb) +{ + /* + * As soon as the caller drops locks to allow for reuse of + * previously-shared tables, these tables could get modified and + * even reused outside of hugetlb context, so we have to make sure that + * any page table walkers (incl. TLB, GUP-fast) are aware of that + * change. + * + * Even if we are not fully unsharing a PMD table, we must + * flush the TLB for the unsharer now. + */ + if (tlb->unshared_tables) + tlb_flush_mmu_tlbonly(tlb); + + /* + * Similarly, we must make sure that concurrent GUP-fast will not + * walk previously-shared page tables that are getting modified+reused + * elsewhere. So broadcast an IPI to wait for any concurrent GUP-fast. + * + * We only perform this when we are the last sharer of a page table, + * as the IPI will reach all CPUs: any GUP-fast. + * + * Note that on configs where tlb_remove_table_sync_one() is a NOP, + * the expectation is that the tlb_flush_mmu_tlbonly() would have issued + * required IPIs already for us. + */ + if (tlb->fully_unshared_tables) { + tlb_remove_table_sync_one(); + tlb->fully_unshared_tables = false; + } +} +#endif /* CONFIG_HUGETLB_PMD_PAGE_TABLE_SHARING */ + #endif /* CONFIG_MMU */ #endif /* _ASM_GENERIC__TLB_H */ diff --git a/include/linux/hugetlb.h b/include/linux/hugetlb.h index 89054f714992fb..6fc7934eafa116 100644 --- a/include/linux/hugetlb.h +++ b/include/linux/hugetlb.h @@ -241,8 +241,9 @@ pte_t *huge_pte_alloc(struct mm_struct *mm, struct vm_area_struct *vma, pte_t *huge_pte_offset(struct mm_struct *mm, unsigned long addr, unsigned long sz); unsigned long hugetlb_mask_last_page(struct hstate *h); -int huge_pmd_unshare(struct mm_struct *mm, struct vm_area_struct *vma, - unsigned long addr, pte_t *ptep); +int huge_pmd_unshare(struct mmu_gather *tlb, struct vm_area_struct *vma, + unsigned long addr, pte_t *ptep); +void huge_pmd_unshare_flush(struct mmu_gather *tlb, struct vm_area_struct *vma); void adjust_range_if_pmd_sharing_possible(struct vm_area_struct *vma, unsigned long *start, unsigned long *end); @@ -302,13 +303,17 @@ static inline struct address_space *hugetlb_folio_mapping_lock_write( return NULL; } -static inline int huge_pmd_unshare(struct mm_struct *mm, - struct vm_area_struct *vma, - unsigned long addr, pte_t *ptep) +static inline int huge_pmd_unshare(struct mmu_gather *tlb, + struct vm_area_struct *vma, unsigned long addr, pte_t *ptep) { return 0; } +static inline void huge_pmd_unshare_flush(struct mmu_gather *tlb, + struct vm_area_struct *vma) +{ +} + static inline void adjust_range_if_pmd_sharing_possible( struct vm_area_struct *vma, unsigned long *start, unsigned long *end) diff --git a/include/linux/mm_types.h b/include/linux/mm_types.h index 90e5790c318f02..8b1045c51e0a68 100644 --- a/include/linux/mm_types.h +++ b/include/linux/mm_types.h @@ -1490,6 +1490,7 @@ static inline void mm_set_cpus_allowed(struct mm_struct *mm, const struct cpumas struct mmu_gather; extern void tlb_gather_mmu(struct mmu_gather *tlb, struct mm_struct *mm); extern void tlb_gather_mmu_fullmm(struct mmu_gather *tlb, struct mm_struct *mm); +void tlb_gather_mmu_vma(struct mmu_gather *tlb, struct vm_area_struct *vma); extern void tlb_finish_mmu(struct mmu_gather *tlb); struct vm_fault; diff --git a/mm/hugetlb.c b/mm/hugetlb.c index 6a60af4798bee0..be0f935a8b1212 100644 --- a/mm/hugetlb.c +++ b/mm/hugetlb.c @@ -5797,7 +5797,7 @@ int move_hugetlb_page_tables(struct vm_area_struct *vma, unsigned long last_addr_mask; pte_t *src_pte, *dst_pte; struct mmu_notifier_range range; - bool shared_pmd = false; + struct mmu_gather tlb; mmu_notifier_range_init(&range, MMU_NOTIFY_CLEAR, 0, mm, old_addr, old_end); @@ -5807,6 +5807,7 @@ int move_hugetlb_page_tables(struct vm_area_struct *vma, * range. */ flush_cache_range(vma, range.start, range.end); + tlb_gather_mmu_vma(&tlb, vma); mmu_notifier_invalidate_range_start(&range); last_addr_mask = hugetlb_mask_last_page(h); @@ -5823,8 +5824,7 @@ int move_hugetlb_page_tables(struct vm_area_struct *vma, if (huge_pte_none(huge_ptep_get(mm, old_addr, src_pte))) continue; - if (huge_pmd_unshare(mm, vma, old_addr, src_pte)) { - shared_pmd = true; + if (huge_pmd_unshare(&tlb, vma, old_addr, src_pte)) { old_addr |= last_addr_mask; new_addr |= last_addr_mask; continue; @@ -5835,15 +5835,16 @@ int move_hugetlb_page_tables(struct vm_area_struct *vma, break; move_huge_pte(vma, old_addr, new_addr, src_pte, dst_pte, sz); + tlb_remove_huge_tlb_entry(h, &tlb, src_pte, old_addr); } - if (shared_pmd) - flush_hugetlb_tlb_range(vma, range.start, range.end); - else - flush_hugetlb_tlb_range(vma, old_end - len, old_end); + tlb_flush_mmu_tlbonly(&tlb); + huge_pmd_unshare_flush(&tlb, vma); + mmu_notifier_invalidate_range_end(&range); i_mmap_unlock_write(mapping); hugetlb_vma_unlock_write(vma); + tlb_finish_mmu(&tlb); return len + old_addr - old_end; } @@ -5862,7 +5863,6 @@ void __unmap_hugepage_range(struct mmu_gather *tlb, struct vm_area_struct *vma, unsigned long sz = huge_page_size(h); bool adjust_reservation; unsigned long last_addr_mask; - bool force_flush = false; WARN_ON(!is_vm_hugetlb_page(vma)); BUG_ON(start & ~huge_page_mask(h)); @@ -5885,10 +5885,8 @@ void __unmap_hugepage_range(struct mmu_gather *tlb, struct vm_area_struct *vma, } ptl = huge_pte_lock(h, mm, ptep); - if (huge_pmd_unshare(mm, vma, address, ptep)) { + if (huge_pmd_unshare(tlb, vma, address, ptep)) { spin_unlock(ptl); - tlb_flush_pmd_range(tlb, address & PUD_MASK, PUD_SIZE); - force_flush = true; address |= last_addr_mask; continue; } @@ -6004,14 +6002,7 @@ void __unmap_hugepage_range(struct mmu_gather *tlb, struct vm_area_struct *vma, } tlb_end_vma(tlb, vma); - /* - * There is nothing protecting a previously-shared page table that we - * unshared through huge_pmd_unshare() from getting freed after we - * release i_mmap_rwsem, so flush the TLB now. If huge_pmd_unshare() - * succeeded, flush the range corresponding to the pud. - */ - if (force_flush) - tlb_flush_mmu_tlbonly(tlb); + huge_pmd_unshare_flush(tlb, vma); } void __hugetlb_zap_begin(struct vm_area_struct *vma, @@ -7104,11 +7095,11 @@ long hugetlb_change_protection(struct vm_area_struct *vma, pte_t pte; struct hstate *h = hstate_vma(vma); long pages = 0, psize = huge_page_size(h); - bool shared_pmd = false; struct mmu_notifier_range range; unsigned long last_addr_mask; bool uffd_wp = cp_flags & MM_CP_UFFD_WP; bool uffd_wp_resolve = cp_flags & MM_CP_UFFD_WP_RESOLVE; + struct mmu_gather tlb; /* * In the case of shared PMDs, the area to flush could be beyond @@ -7121,6 +7112,7 @@ long hugetlb_change_protection(struct vm_area_struct *vma, BUG_ON(address >= end); flush_cache_range(vma, range.start, range.end); + tlb_gather_mmu_vma(&tlb, vma); mmu_notifier_invalidate_range_start(&range); hugetlb_vma_lock_write(vma); @@ -7145,7 +7137,7 @@ long hugetlb_change_protection(struct vm_area_struct *vma, } } ptl = huge_pte_lock(h, mm, ptep); - if (huge_pmd_unshare(mm, vma, address, ptep)) { + if (huge_pmd_unshare(&tlb, vma, address, ptep)) { /* * When uffd-wp is enabled on the vma, unshare * shouldn't happen at all. Warn about it if it @@ -7154,7 +7146,6 @@ long hugetlb_change_protection(struct vm_area_struct *vma, WARN_ON_ONCE(uffd_wp || uffd_wp_resolve); pages++; spin_unlock(ptl); - shared_pmd = true; address |= last_addr_mask; continue; } @@ -7206,6 +7197,7 @@ long hugetlb_change_protection(struct vm_area_struct *vma, pte = huge_pte_clear_uffd_wp(pte); huge_ptep_modify_prot_commit(vma, address, ptep, old_pte, pte); pages++; + tlb_remove_huge_tlb_entry(h, &tlb, ptep, address); } else { /* None pte */ if (unlikely(uffd_wp)) @@ -7218,16 +7210,9 @@ long hugetlb_change_protection(struct vm_area_struct *vma, cond_resched(); } - /* - * There is nothing protecting a previously-shared page table that we - * unshared through huge_pmd_unshare() from getting freed after we - * release i_mmap_rwsem, so flush the TLB now. If huge_pmd_unshare() - * succeeded, flush the range corresponding to the pud. - */ - if (shared_pmd) - flush_hugetlb_tlb_range(vma, range.start, range.end); - else - flush_hugetlb_tlb_range(vma, start, end); + + tlb_flush_mmu_tlbonly(&tlb); + huge_pmd_unshare_flush(&tlb, vma); /* * No need to call mmu_notifier_arch_invalidate_secondary_tlbs() we are * downgrading page table protection not changing it to point to a new @@ -7238,6 +7223,7 @@ long hugetlb_change_protection(struct vm_area_struct *vma, i_mmap_unlock_write(vma->vm_file->f_mapping); hugetlb_vma_unlock_write(vma); mmu_notifier_invalidate_range_end(&range); + tlb_finish_mmu(&tlb); return pages > 0 ? (pages << h->order) : pages; } @@ -7590,18 +7576,27 @@ pte_t *huge_pmd_share(struct mm_struct *mm, struct vm_area_struct *vma, return pte; } -/* - * unmap huge page backed by shared pte. +/** + * huge_pmd_unshare - Unmap a pmd table if it is shared by multiple users + * @tlb: the current mmu_gather. + * @vma: the vma covering the pmd table. + * @addr: the address we are trying to unshare. + * @ptep: pointer into the (pmd) page table. + * + * Called with the page table lock held, the i_mmap_rwsem held in write mode + * and the hugetlb vma lock held in write mode. * - * Called with page table lock held. + * Note: The caller must call huge_pmd_unshare_flush() before dropping the + * i_mmap_rwsem. * - * returns: 1 successfully unmapped a shared pte page - * 0 the underlying pte page is not shared, or it is the last user + * Returns: 1 if it was a shared PMD table and it got unmapped, or 0 if it + * was not a shared PMD table. */ -int huge_pmd_unshare(struct mm_struct *mm, struct vm_area_struct *vma, - unsigned long addr, pte_t *ptep) +int huge_pmd_unshare(struct mmu_gather *tlb, struct vm_area_struct *vma, + unsigned long addr, pte_t *ptep) { unsigned long sz = huge_page_size(hstate_vma(vma)); + struct mm_struct *mm = vma->vm_mm; pgd_t *pgd = pgd_offset(mm, addr); p4d_t *p4d = p4d_offset(pgd, addr); pud_t *pud = pud_offset(p4d, addr); @@ -7613,18 +7608,36 @@ int huge_pmd_unshare(struct mm_struct *mm, struct vm_area_struct *vma, i_mmap_assert_write_locked(vma->vm_file->f_mapping); hugetlb_vma_assert_locked(vma); pud_clear(pud); - /* - * Once our caller drops the rmap lock, some other process might be - * using this page table as a normal, non-hugetlb page table. - * Wait for pending gup_fast() in other threads to finish before letting - * that happen. - */ - tlb_remove_table_sync_one(); - ptdesc_pmd_pts_dec(virt_to_ptdesc(ptep)); + + tlb_unshare_pmd_ptdesc(tlb, virt_to_ptdesc(ptep), addr); + mm_dec_nr_pmds(mm); return 1; } +/* + * huge_pmd_unshare_flush - Complete a sequence of huge_pmd_unshare() calls + * @tlb: the current mmu_gather. + * @vma: the vma covering the pmd table. + * + * Perform necessary TLB flushes or IPI broadcasts to synchronize PMD table + * unsharing with concurrent page table walkers. + * + * This function must be called after a sequence of huge_pmd_unshare() + * calls while still holding the i_mmap_rwsem. + */ +void huge_pmd_unshare_flush(struct mmu_gather *tlb, struct vm_area_struct *vma) +{ + /* + * We must synchronize page table unsharing such that nobody will + * try reusing a previously-shared page table while it might still + * be in use by previous sharers (TLB, GUP_fast). + */ + i_mmap_assert_write_locked(vma->vm_file->f_mapping); + + tlb_flush_unshared_tables(tlb); +} + #else /* !CONFIG_HUGETLB_PMD_PAGE_TABLE_SHARING */ pte_t *huge_pmd_share(struct mm_struct *mm, struct vm_area_struct *vma, @@ -7633,12 +7646,16 @@ pte_t *huge_pmd_share(struct mm_struct *mm, struct vm_area_struct *vma, return NULL; } -int huge_pmd_unshare(struct mm_struct *mm, struct vm_area_struct *vma, - unsigned long addr, pte_t *ptep) +int huge_pmd_unshare(struct mmu_gather *tlb, struct vm_area_struct *vma, + unsigned long addr, pte_t *ptep) { return 0; } +void huge_pmd_unshare_flush(struct mmu_gather *tlb, struct vm_area_struct *vma) +{ +} + void adjust_range_if_pmd_sharing_possible(struct vm_area_struct *vma, unsigned long *start, unsigned long *end) { @@ -7905,6 +7922,7 @@ static void hugetlb_unshare_pmds(struct vm_area_struct *vma, unsigned long sz = huge_page_size(h); struct mm_struct *mm = vma->vm_mm; struct mmu_notifier_range range; + struct mmu_gather tlb; unsigned long address; spinlock_t *ptl; pte_t *ptep; @@ -7916,6 +7934,8 @@ static void hugetlb_unshare_pmds(struct vm_area_struct *vma, return; flush_cache_range(vma, start, end); + tlb_gather_mmu_vma(&tlb, vma); + /* * No need to call adjust_range_if_pmd_sharing_possible(), because * we have already done the PUD_SIZE alignment. @@ -7934,10 +7954,10 @@ static void hugetlb_unshare_pmds(struct vm_area_struct *vma, if (!ptep) continue; ptl = huge_pte_lock(h, mm, ptep); - huge_pmd_unshare(mm, vma, address, ptep); + huge_pmd_unshare(&tlb, vma, address, ptep); spin_unlock(ptl); } - flush_hugetlb_tlb_range(vma, start, end); + huge_pmd_unshare_flush(&tlb, vma); if (take_locks) { i_mmap_unlock_write(vma->vm_file->f_mapping); hugetlb_vma_unlock_write(vma); @@ -7947,6 +7967,7 @@ static void hugetlb_unshare_pmds(struct vm_area_struct *vma, * Documentation/mm/mmu_notifier.rst. */ mmu_notifier_invalidate_range_end(&range); + tlb_finish_mmu(&tlb); } /* diff --git a/mm/mmu_gather.c b/mm/mmu_gather.c index 374aa6f021c6b0..5619ee967f3c7d 100644 --- a/mm/mmu_gather.c +++ b/mm/mmu_gather.c @@ -9,6 +9,7 @@ #include #include #include +#include #include #include @@ -426,6 +427,7 @@ static void __tlb_gather_mmu(struct mmu_gather *tlb, struct mm_struct *mm, #endif tlb->vma_pfn = 0; + tlb->fully_unshared_tables = 0; __tlb_reset_range(tlb); inc_tlb_flush_pending(tlb->mm); } @@ -459,6 +461,31 @@ void tlb_gather_mmu_fullmm(struct mmu_gather *tlb, struct mm_struct *mm) __tlb_gather_mmu(tlb, mm, true); } +/** + * tlb_gather_mmu_vma - initialize an mmu_gather structure for operating on a + * single VMA + * @tlb: the mmu_gather structure to initialize + * @vma: the vm_area_struct + * + * Called to initialize an (on-stack) mmu_gather structure for operating on + * a single VMA. In contrast to tlb_gather_mmu(), calling this function will + * not require another call to tlb_start_vma(). In contrast to tlb_start_vma(), + * this function will *not* call flush_cache_range(). + * + * For hugetlb VMAs, this function will also initialize the mmu_gather + * page_size accordingly, not requiring a separate call to + * tlb_change_page_size(). + * + */ +void tlb_gather_mmu_vma(struct mmu_gather *tlb, struct vm_area_struct *vma) +{ + tlb_gather_mmu(tlb, vma->vm_mm); + tlb_update_vma_flags(tlb, vma); + if (is_vm_hugetlb_page(vma)) + /* All entries have the same size. */ + tlb_change_page_size(tlb, huge_page_size(hstate_vma(vma))); +} + /** * tlb_finish_mmu - finish an mmu_gather structure * @tlb: the mmu_gather structure to finish @@ -468,6 +495,12 @@ void tlb_gather_mmu_fullmm(struct mmu_gather *tlb, struct mm_struct *mm) */ void tlb_finish_mmu(struct mmu_gather *tlb) { + /* + * We expect an earlier huge_pmd_unshare_flush() call to sort this out, + * due to complicated locking requirements with page table unsharing. + */ + VM_WARN_ON_ONCE(tlb->fully_unshared_tables); + /* * If there are parallel threads are doing PTE changes on same range * under non-exclusive lock (e.g., mmap_lock read-side) but defer TLB diff --git a/mm/rmap.c b/mm/rmap.c index d52055a026a058..f1e6a97cf46043 100644 --- a/mm/rmap.c +++ b/mm/rmap.c @@ -76,7 +76,7 @@ #include #include -#include +#include #define CREATE_TRACE_POINTS #include @@ -2019,13 +2019,17 @@ static bool try_to_unmap_one(struct folio *folio, struct vm_area_struct *vma, * if unsuccessful. */ if (!anon) { + struct mmu_gather tlb; + VM_BUG_ON(!(flags & TTU_RMAP_LOCKED)); if (!hugetlb_vma_trylock_write(vma)) goto walk_abort; - if (huge_pmd_unshare(mm, vma, address, pvmw.pte)) { + + tlb_gather_mmu_vma(&tlb, vma); + if (huge_pmd_unshare(&tlb, vma, address, pvmw.pte)) { hugetlb_vma_unlock_write(vma); - flush_tlb_range(vma, - range.start, range.end); + huge_pmd_unshare_flush(&tlb, vma); + tlb_finish_mmu(&tlb); /* * The PMD table was unmapped, * consequently unmapping the folio. @@ -2033,6 +2037,7 @@ static bool try_to_unmap_one(struct folio *folio, struct vm_area_struct *vma, goto walk_done; } hugetlb_vma_unlock_write(vma); + tlb_finish_mmu(&tlb); } pteval = huge_ptep_clear_flush(vma, address, pvmw.pte); if (pte_dirty(pteval)) @@ -2398,17 +2403,20 @@ static bool try_to_migrate_one(struct folio *folio, struct vm_area_struct *vma, * fail if unsuccessful. */ if (!anon) { + struct mmu_gather tlb; + VM_BUG_ON(!(flags & TTU_RMAP_LOCKED)); if (!hugetlb_vma_trylock_write(vma)) { page_vma_mapped_walk_done(&pvmw); ret = false; break; } - if (huge_pmd_unshare(mm, vma, address, pvmw.pte)) { - hugetlb_vma_unlock_write(vma); - flush_tlb_range(vma, - range.start, range.end); + tlb_gather_mmu_vma(&tlb, vma); + if (huge_pmd_unshare(&tlb, vma, address, pvmw.pte)) { + hugetlb_vma_unlock_write(vma); + huge_pmd_unshare_flush(&tlb, vma); + tlb_finish_mmu(&tlb); /* * The PMD table was unmapped, * consequently unmapping the folio. @@ -2417,6 +2425,7 @@ static bool try_to_migrate_one(struct folio *folio, struct vm_area_struct *vma, break; } hugetlb_vma_unlock_write(vma); + tlb_finish_mmu(&tlb); } /* Nuke the hugetlb page table entry */ pteval = huge_ptep_clear_flush(vma, address, pvmw.pte); From eaa9fcf57934cda01e9f34b232ca78187b344ad1 Mon Sep 17 00:00:00 2001 From: Tiezhu Yang Date: Tue, 10 Feb 2026 19:31:17 +0800 Subject: [PATCH 3235/3902] LoongArch: Rework KASAN initialization for PTW-enabled systems commit 5ec5ac4ca27e4daa234540ac32f9fc5219377d53 upstream. kasan_init_generic() indicates that kasan is fully initialized, so it should be put at end of kasan_init(). Otherwise bringing up the primary CPU failed when CONFIG_KASAN is set on PTW-enabled systems, here are the call chains: kernel_entry() start_kernel() setup_arch() kasan_init() kasan_init_generic() The reason is PTW-enabled systems have speculative accesses which means memory accesses to the shadow memory after kasan_init() may be executed by hardware before. However, accessing shadow memory is safe only after kasan fully initialized because kasan_init() uses a temporary PGD table until we have populated all levels of shadow page tables and writen the PGD register. Moving kasan_init_generic() later can defer the occasion of kasan_enabled(), so as to avoid speculative accesses on shadow pages. After moving kasan_init_generic() to the end, kasan_init() can no longer call kasan_mem_to_shadow() for shadow address conversion because it will always return kasan_early_shadow_page. On the other hand, we should keep the current logic of kasan_mem_to_shadow() for both the early and final stage because there may be instrumentation before kasan_init(). To solve this, we factor out a new mem_to_shadow() function from current kasan_mem_to_shadow() for the shadow address conversion in kasan_init(). Cc: stable@vger.kernel.org Signed-off-by: Tiezhu Yang Signed-off-by: Huacai Chen Signed-off-by: Greg Kroah-Hartman --- arch/loongarch/mm/kasan_init.c | 78 +++++++++++++++++----------------- 1 file changed, 40 insertions(+), 38 deletions(-) diff --git a/arch/loongarch/mm/kasan_init.c b/arch/loongarch/mm/kasan_init.c index 170da98ad4f551..0fc02ca0645738 100644 --- a/arch/loongarch/mm/kasan_init.c +++ b/arch/loongarch/mm/kasan_init.c @@ -40,39 +40,43 @@ static pgd_t kasan_pg_dir[PTRS_PER_PGD] __initdata __aligned(PAGE_SIZE); #define __pte_none(early, pte) (early ? pte_none(pte) : \ ((pte_val(pte) & _PFN_MASK) == (unsigned long)__pa(kasan_early_shadow_page))) -void *kasan_mem_to_shadow(const void *addr) +static void *mem_to_shadow(const void *addr) { - if (!kasan_enabled()) { + unsigned long offset = 0; + unsigned long maddr = (unsigned long)addr; + unsigned long xrange = (maddr >> XRANGE_SHIFT) & 0xffff; + + if (maddr >= FIXADDR_START) return (void *)(kasan_early_shadow_page); - } else { - unsigned long maddr = (unsigned long)addr; - unsigned long xrange = (maddr >> XRANGE_SHIFT) & 0xffff; - unsigned long offset = 0; - - if (maddr >= FIXADDR_START) - return (void *)(kasan_early_shadow_page); - - maddr &= XRANGE_SHADOW_MASK; - switch (xrange) { - case XKPRANGE_CC_SEG: - offset = XKPRANGE_CC_SHADOW_OFFSET; - break; - case XKPRANGE_UC_SEG: - offset = XKPRANGE_UC_SHADOW_OFFSET; - break; - case XKPRANGE_WC_SEG: - offset = XKPRANGE_WC_SHADOW_OFFSET; - break; - case XKVRANGE_VC_SEG: - offset = XKVRANGE_VC_SHADOW_OFFSET; - break; - default: - WARN_ON(1); - return NULL; - } - return (void *)((maddr >> KASAN_SHADOW_SCALE_SHIFT) + offset); + maddr &= XRANGE_SHADOW_MASK; + switch (xrange) { + case XKPRANGE_CC_SEG: + offset = XKPRANGE_CC_SHADOW_OFFSET; + break; + case XKPRANGE_UC_SEG: + offset = XKPRANGE_UC_SHADOW_OFFSET; + break; + case XKPRANGE_WC_SEG: + offset = XKPRANGE_WC_SHADOW_OFFSET; + break; + case XKVRANGE_VC_SEG: + offset = XKVRANGE_VC_SHADOW_OFFSET; + break; + default: + WARN_ON(1); + return NULL; } + + return (void *)((maddr >> KASAN_SHADOW_SCALE_SHIFT) + offset); +} + +void *kasan_mem_to_shadow(const void *addr) +{ + if (kasan_enabled()) + return mem_to_shadow(addr); + else + return (void *)(kasan_early_shadow_page); } const void *kasan_shadow_to_mem(const void *shadow_addr) @@ -293,11 +297,8 @@ void __init kasan_init(void) /* Maps everything to a single page of zeroes */ kasan_pgd_populate(KASAN_SHADOW_START, KASAN_SHADOW_END, NUMA_NO_NODE, true); - kasan_populate_early_shadow(kasan_mem_to_shadow((void *)VMALLOC_START), - kasan_mem_to_shadow((void *)KFENCE_AREA_END)); - - /* Enable KASAN here before kasan_mem_to_shadow(). */ - kasan_init_generic(); + kasan_populate_early_shadow(mem_to_shadow((void *)VMALLOC_START), + mem_to_shadow((void *)KFENCE_AREA_END)); /* Populate the linear mapping */ for_each_mem_range(i, &pa_start, &pa_end) { @@ -307,13 +308,13 @@ void __init kasan_init(void) if (start >= end) break; - kasan_map_populate((unsigned long)kasan_mem_to_shadow(start), - (unsigned long)kasan_mem_to_shadow(end), NUMA_NO_NODE); + kasan_map_populate((unsigned long)mem_to_shadow(start), + (unsigned long)mem_to_shadow(end), NUMA_NO_NODE); } /* Populate modules mapping */ - kasan_map_populate((unsigned long)kasan_mem_to_shadow((void *)MODULES_VADDR), - (unsigned long)kasan_mem_to_shadow((void *)MODULES_END), NUMA_NO_NODE); + kasan_map_populate((unsigned long)mem_to_shadow((void *)MODULES_VADDR), + (unsigned long)mem_to_shadow((void *)MODULES_END), NUMA_NO_NODE); /* * KAsan may reuse the contents of kasan_early_shadow_pte directly, so we * should make sure that it maps the zero page read-only. @@ -328,4 +329,5 @@ void __init kasan_init(void) /* At this point kasan is fully initialized. Enable error messages */ init_task.kasan_depth = 0; + kasan_init_generic(); } From 98d91080517d4c642820b5d8219cb759ddaf79b7 Mon Sep 17 00:00:00 2001 From: Chen Ridong Date: Wed, 14 Jan 2026 01:51:29 +0000 Subject: [PATCH 3236/3902] cpuset: Fix missing adaptation for cpuset_is_populated MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Commit b1bcaed1e39a ("cpuset: Treat cpusets in attaching as populated") was backported to the long‑term support (LTS) branches. However, because commit d5cf4d34a333 ("cgroup/cpuset: Don't track # of local child partitions") was not backported, a corresponding adaptation to the backported code is still required. To ensure correct behavior, replace cgroup_is_populated with cpuset_is_populated in the partition_is_populated function. Cc: stable@vger.kernel.org # 6.1+ Fixes: b1bcaed1e39a ("cpuset: Treat cpusets in attaching as populated") Cc: Waiman Long Cc: Tejun Heo Signed-off-by: Chen Ridong Signed-off-by: Greg Kroah-Hartman --- kernel/cgroup/cpuset.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/kernel/cgroup/cpuset.c b/kernel/cgroup/cpuset.c index 61b56b6ca66a76..1245418cc8b3bd 100644 --- a/kernel/cgroup/cpuset.c +++ b/kernel/cgroup/cpuset.c @@ -385,7 +385,7 @@ static inline bool partition_is_populated(struct cpuset *cs, cs->attach_in_progress) return true; if (!excluded_child && !cs->nr_subparts) - return cgroup_is_populated(cs->css.cgroup); + return cpuset_is_populated(cs); rcu_read_lock(); cpuset_for_each_descendant_pre(cp, pos_css, cs) { From 3e4cbd1d46c246dfa684c8e9d8c20ae0b960c50a Mon Sep 17 00:00:00 2001 From: Guangshuo Li Date: Sun, 7 Dec 2025 15:25:32 +0800 Subject: [PATCH 3237/3902] fbdev: rivafb: fix divide error in nv3_arb() commit 0209e21e3c372fa2da04c39214bec0b64e4eb5f4 upstream. A userspace program can trigger the RIVA NV3 arbitration code by calling the FBIOPUT_VSCREENINFO ioctl on /dev/fb*. When doing so, the driver recomputes FIFO arbitration parameters in nv3_arb(), using state->mclk_khz (derived from the PRAMDAC MCLK PLL) as a divisor without validating it first. In a normal setup, state->mclk_khz is provided by the real hardware and is non-zero. However, an attacker can construct a malicious or misconfigured device (e.g. a crafted/emulated PCI device) that exposes a bogus PLL configuration, causing state->mclk_khz to become zero. Once nv3_get_param() calls nv3_arb(), the division by state->mclk_khz in the gns calculation causes a divide error and crashes the kernel. Fix this by checking whether state->mclk_khz is zero and bailing out before doing the division. The following log reveals it: rivafb: setting virtual Y resolution to 2184 divide error: 0000 [#1] PREEMPT SMP KASAN PTI CPU: 0 PID: 2187 Comm: syz-executor.0 Not tainted 5.18.0-rc1+ #1 Hardware name: QEMU Standard PC (i440FX + PIIX, 1996), BIOS rel-1.12.0-59-gc9ba5276e321-prebuilt.qemu.org 04/01/2014 RIP: 0010:nv3_arb drivers/video/fbdev/riva/riva_hw.c:439 [inline] RIP: 0010:nv3_get_param+0x3ab/0x13b0 drivers/video/fbdev/riva/riva_hw.c:546 Call Trace: nv3CalcArbitration.constprop.0+0x255/0x460 drivers/video/fbdev/riva/riva_hw.c:603 nv3UpdateArbitrationSettings drivers/video/fbdev/riva/riva_hw.c:637 [inline] CalcStateExt+0x447/0x1b90 drivers/video/fbdev/riva/riva_hw.c:1246 riva_load_video_mode+0x8a9/0xea0 drivers/video/fbdev/riva/fbdev.c:779 rivafb_set_par+0xc0/0x5f0 drivers/video/fbdev/riva/fbdev.c:1196 fb_set_var+0x604/0xeb0 drivers/video/fbdev/core/fbmem.c:1033 do_fb_ioctl+0x234/0x670 drivers/video/fbdev/core/fbmem.c:1109 fb_ioctl+0xdd/0x130 drivers/video/fbdev/core/fbmem.c:1188 __x64_sys_ioctl+0x122/0x190 fs/ioctl.c:856 Fixes: 1da177e4c3f4 ("Linux-2.6.12-rc2") Cc: stable@vger.kernel.org Signed-off-by: Guangshuo Li Signed-off-by: Helge Deller Signed-off-by: Greg Kroah-Hartman --- drivers/video/fbdev/riva/riva_hw.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/drivers/video/fbdev/riva/riva_hw.c b/drivers/video/fbdev/riva/riva_hw.c index 8b829b7200642f..f292079566cfca 100644 --- a/drivers/video/fbdev/riva/riva_hw.c +++ b/drivers/video/fbdev/riva/riva_hw.c @@ -436,6 +436,9 @@ static char nv3_arb(nv3_fifo_info * res_info, nv3_sim_state * state, nv3_arb_in vmisses = 2; eburst_size = state->memory_width * 1; mburst_size = 32; + if (!state->mclk_khz) + return (0); + gns = 1000000 * (gmisses*state->mem_page_miss + state->mem_latency)/state->mclk_khz; ainfo->by_gfacc = gns*ainfo->gdrain_rate/1000000; ainfo->wcmocc = 0; From 1c008ad0f0d1c1523902b9cdb08e404129677bfc Mon Sep 17 00:00:00 2001 From: Greg Kroah-Hartman Date: Sun, 28 Dec 2025 14:17:03 +0100 Subject: [PATCH 3238/3902] fbdev: smscufx: properly copy ioctl memory to kernelspace commit 120adae7b42faa641179270c067864544a50ab69 upstream. The UFX_IOCTL_REPORT_DAMAGE ioctl does not properly copy data from userspace to kernelspace, and instead directly references the memory, which can cause problems if invalid data is passed from userspace. Fix this all up by correctly copying the memory before accessing it within the kernel. Reported-by: Tianchu Chen Cc: stable Cc: Steve Glendinning Cc: Helge Deller Signed-off-by: Greg Kroah-Hartman Signed-off-by: Helge Deller Signed-off-by: Greg Kroah-Hartman --- drivers/video/fbdev/smscufx.c | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/drivers/video/fbdev/smscufx.c b/drivers/video/fbdev/smscufx.c index 5f0dd01fd83495..891ce7b76d637e 100644 --- a/drivers/video/fbdev/smscufx.c +++ b/drivers/video/fbdev/smscufx.c @@ -932,7 +932,6 @@ static int ufx_ops_ioctl(struct fb_info *info, unsigned int cmd, unsigned long arg) { struct ufx_data *dev = info->par; - struct dloarea *area = NULL; if (!atomic_read(&dev->usb_active)) return 0; @@ -947,6 +946,10 @@ static int ufx_ops_ioctl(struct fb_info *info, unsigned int cmd, /* TODO: Help propose a standard fb.h ioctl to report mmap damage */ if (cmd == UFX_IOCTL_REPORT_DAMAGE) { + struct dloarea *area __free(kfree) = kmalloc(sizeof(*area), GFP_KERNEL); + if (!area) + return -ENOMEM; + /* If we have a damage-aware client, turn fb_defio "off" * To avoid perf imact of unnecessary page fault handling. * Done by resetting the delay for this fb_info to a very @@ -956,7 +959,8 @@ static int ufx_ops_ioctl(struct fb_info *info, unsigned int cmd, if (info->fbdefio) info->fbdefio->delay = UFX_DEFIO_WRITE_DISABLE; - area = (struct dloarea *)arg; + if (copy_from_user(area, (u8 __user *)arg, sizeof(*area))) + return -EFAULT; if (area->x < 0) area->x = 0; From fb9f7e52273db15dd8c0e2fc107a986b87240bb4 Mon Sep 17 00:00:00 2001 From: Zhiguo Niu Date: Fri, 26 Dec 2025 10:56:04 +0800 Subject: [PATCH 3239/3902] f2fs: fix to add gc count stat in f2fs_gc_range commit 761dac9073cd67d4705a94cd1af674945a117f4c upstream. It missed the stat count in f2fs_gc_range. Cc: stable@kernel.org Fixes: 9bf1dcbdfdc8 ("f2fs: fix to account gc stats correctly") Signed-off-by: Zhiguo Niu Reviewed-by: Chao Yu Signed-off-by: Jaegeuk Kim Signed-off-by: Greg Kroah-Hartman --- fs/f2fs/gc.c | 1 + 1 file changed, 1 insertion(+) diff --git a/fs/f2fs/gc.c b/fs/f2fs/gc.c index 5f90cca64c7aa2..6886486d266145 100644 --- a/fs/f2fs/gc.c +++ b/fs/f2fs/gc.c @@ -2093,6 +2093,7 @@ int f2fs_gc_range(struct f2fs_sb_info *sbi, if (unlikely(f2fs_cp_error(sbi))) return -EIO; + stat_inc_gc_call_count(sbi, FOREGROUND); for (segno = start_seg; segno <= end_seg; segno += SEGS_PER_SEC(sbi)) { struct gc_inode_list gc_list = { .ilist = LIST_HEAD_INIT(gc_list.ilist), From 70cf1e96bde07a1a75ce3a479132d06b4950c801 Mon Sep 17 00:00:00 2001 From: Chao Yu Date: Tue, 6 Jan 2026 14:31:17 +0800 Subject: [PATCH 3240/3902] f2fs: fix to check sysfs filename w/ gc_pin_file_thresh correctly commit 0eda086de85e140f53c6123a4c00662f4e614ee4 upstream. Sysfs entry name is gc_pin_file_thresh instead of gc_pin_file_threshold, fix it. Cc: stable@kernel.org Fixes: c521a6ab4ad7 ("f2fs: fix to limit gc_pin_file_threshold") Signed-off-by: Chao Yu Signed-off-by: Jaegeuk Kim Signed-off-by: Greg Kroah-Hartman --- fs/f2fs/sysfs.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fs/f2fs/sysfs.c b/fs/f2fs/sysfs.c index 5685b454bfd12d..42f506a0141aa7 100644 --- a/fs/f2fs/sysfs.c +++ b/fs/f2fs/sysfs.c @@ -749,7 +749,7 @@ static ssize_t __sbi_store(struct f2fs_attr *a, return count; } - if (!strcmp(a->attr.name, "gc_pin_file_threshold")) { + if (!strcmp(a->attr.name, "gc_pin_file_thresh")) { if (t > MAX_GC_FAILED_PINNED_FILES) return -EINVAL; sbi->gc_pin_file_threshold = t; From bd66b4c487d5091d2a65d6089e0de36f0c26a4c7 Mon Sep 17 00:00:00 2001 From: Yongpeng Yang Date: Tue, 6 Jan 2026 20:12:11 +0800 Subject: [PATCH 3241/3902] f2fs: fix IS_CHECKPOINTED flag inconsistency issue caused by concurrent atomic commit and checkpoint writes commit 7633a7387eb4d0259d6bea945e1d3469cd135bbc upstream. During SPO tests, when mounting F2FS, an -EINVAL error was returned from f2fs_recover_inode_page. The issue occurred under the following scenario Thread A Thread B f2fs_ioc_commit_atomic_write - f2fs_do_sync_file // atomic = true - f2fs_fsync_node_pages : last_folio = inode folio : schedule before folio_lock(last_folio) f2fs_write_checkpoint - block_operations// writeback last_folio - schedule before f2fs_flush_nat_entries : set_fsync_mark(last_folio, 1) : set_dentry_mark(last_folio, 1) : folio_mark_dirty(last_folio) - __write_node_folio(last_folio) : f2fs_down_read(&sbi->node_write)//block - f2fs_flush_nat_entries : {struct nat_entry}->flag |= BIT(IS_CHECKPOINTED) - unblock_operations : f2fs_up_write(&sbi->node_write) f2fs_write_checkpoint//return : f2fs_do_write_node_page() f2fs_ioc_commit_atomic_write//return SPO Thread A calls f2fs_need_dentry_mark(sbi, ino), and the last_folio has already been written once. However, the {struct nat_entry}->flag did not have the IS_CHECKPOINTED set, causing set_dentry_mark(last_folio, 1) and write last_folio again after Thread B finishes f2fs_write_checkpoint. After SPO and reboot, it was detected that {struct node_info}->blk_addr was not NULL_ADDR because Thread B successfully write the checkpoint. This issue only occurs in atomic write scenarios. For regular file fsync operations, the folio must be dirty. If block_operations->f2fs_sync_node_pages successfully submit the folio write, this path will not be executed. Otherwise, the f2fs_write_checkpoint will need to wait for the folio write submission to complete, as sbi->nr_pages[F2FS_DIRTY_NODES] > 0. Therefore, the situation where f2fs_need_dentry_mark checks that the {struct nat_entry}->flag /wo the IS_CHECKPOINTED flag, but the folio write has already been submitted, will not occur. Therefore, for atomic file fsync, sbi->node_write should be acquired through __write_node_folio to ensure that the IS_CHECKPOINTED flag correctly indicates that the checkpoint write has been completed. Fixes: 608514deba38 ("f2fs: set fsync mark only for the last dnode") Cc: stable@kernel.org Signed-off-by: Sheng Yong Signed-off-by: Jinbao Liu Signed-off-by: Yongpeng Yang Reviewed-by: Chao Yu Signed-off-by: Jaegeuk Kim Signed-off-by: Greg Kroah-Hartman --- fs/f2fs/node.c | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/fs/f2fs/node.c b/fs/f2fs/node.c index 482a362f262543..2c6102bee34957 100644 --- a/fs/f2fs/node.c +++ b/fs/f2fs/node.c @@ -1774,8 +1774,13 @@ static bool __write_node_folio(struct folio *folio, bool atomic, bool *submitted goto redirty_out; } - if (atomic && !test_opt(sbi, NOBARRIER)) - fio.op_flags |= REQ_PREFLUSH | REQ_FUA; + if (atomic) { + if (!test_opt(sbi, NOBARRIER)) + fio.op_flags |= REQ_PREFLUSH | REQ_FUA; + if (IS_INODE(folio)) + set_dentry_mark(folio, + f2fs_need_dentry_mark(sbi, ino_of_node(folio))); + } /* should add to global list before clearing PAGECACHE status */ if (f2fs_in_warm_node_list(sbi, folio)) { @@ -1916,8 +1921,9 @@ int f2fs_fsync_node_pages(struct f2fs_sb_info *sbi, struct inode *inode, if (is_inode_flag_set(inode, FI_DIRTY_INODE)) f2fs_update_inode(inode, folio); - set_dentry_mark(folio, - f2fs_need_dentry_mark(sbi, ino)); + if (!atomic) + set_dentry_mark(folio, + f2fs_need_dentry_mark(sbi, ino)); } /* may be written by other thread */ if (!folio_test_dirty(folio)) From d4a594dd952df123cbdcdee9b9640d9d55e4a954 Mon Sep 17 00:00:00 2001 From: Yongpeng Yang Date: Wed, 7 Jan 2026 10:33:46 +0800 Subject: [PATCH 3242/3902] f2fs: fix out-of-bounds access in sysfs attribute read/write commit 98ea0039dbfdd00e5cc1b9a8afa40434476c0955 upstream. Some f2fs sysfs attributes suffer from out-of-bounds memory access and incorrect handling of integer values whose size is not 4 bytes. For example: vm:~# echo 65537 > /sys/fs/f2fs/vde/carve_out vm:~# cat /sys/fs/f2fs/vde/carve_out 65537 vm:~# echo 4294967297 > /sys/fs/f2fs/vde/atgc_age_threshold vm:~# cat /sys/fs/f2fs/vde/atgc_age_threshold 1 carve_out maps to {struct f2fs_sb_info}->carve_out, which is a 8-bit integer. However, the sysfs interface allows setting it to a value larger than 255, resulting in an out-of-range update. atgc_age_threshold maps to {struct atgc_management}->age_threshold, which is a 64-bit integer, but its sysfs interface cannot correctly set values larger than UINT_MAX. The root causes are: 1. __sbi_store() treats all default values as unsigned int, which prevents updating integers larger than 4 bytes and causes out-of-bounds writes for integers smaller than 4 bytes. 2. f2fs_sbi_show() also assumes all default values are unsigned int, leading to out-of-bounds reads and incorrect access to integers larger than 4 bytes. This patch introduces {struct f2fs_attr}->size to record the actual size of the integer associated with each sysfs attribute. With this information, sysfs read and write operations can correctly access and update values according to their real data size, avoiding memory corruption and truncation. Fixes: b59d0bae6ca3 ("f2fs: add sysfs support for controlling the gc_thread") Cc: stable@kernel.org Signed-off-by: Jinbao Liu Signed-off-by: Yongpeng Yang Reviewed-by: Chao Yu Signed-off-by: Jaegeuk Kim Signed-off-by: Greg Kroah-Hartman --- fs/f2fs/sysfs.c | 60 ++++++++++++++++++++++++++++++++++++++++++------- 1 file changed, 52 insertions(+), 8 deletions(-) diff --git a/fs/f2fs/sysfs.c b/fs/f2fs/sysfs.c index 42f506a0141aa7..c6fa8f74b91c2c 100644 --- a/fs/f2fs/sysfs.c +++ b/fs/f2fs/sysfs.c @@ -58,6 +58,7 @@ struct f2fs_attr { const char *buf, size_t len); int struct_type; int offset; + int size; int id; }; @@ -344,11 +345,30 @@ static ssize_t main_blkaddr_show(struct f2fs_attr *a, (unsigned long long)MAIN_BLKADDR(sbi)); } +static ssize_t __sbi_show_value(struct f2fs_attr *a, + struct f2fs_sb_info *sbi, char *buf, + unsigned char *value) +{ + switch (a->size) { + case 1: + return sysfs_emit(buf, "%u\n", *(u8 *)value); + case 2: + return sysfs_emit(buf, "%u\n", *(u16 *)value); + case 4: + return sysfs_emit(buf, "%u\n", *(u32 *)value); + case 8: + return sysfs_emit(buf, "%llu\n", *(u64 *)value); + default: + f2fs_bug_on(sbi, 1); + return sysfs_emit(buf, + "show sysfs node value with wrong type\n"); + } +} + static ssize_t f2fs_sbi_show(struct f2fs_attr *a, struct f2fs_sb_info *sbi, char *buf) { unsigned char *ptr = NULL; - unsigned int *ui; ptr = __struct_ptr(sbi, a->struct_type); if (!ptr) @@ -428,9 +448,30 @@ static ssize_t f2fs_sbi_show(struct f2fs_attr *a, atomic_read(&sbi->cp_call_count[BACKGROUND])); #endif - ui = (unsigned int *)(ptr + a->offset); + return __sbi_show_value(a, sbi, buf, ptr + a->offset); +} - return sysfs_emit(buf, "%u\n", *ui); +static void __sbi_store_value(struct f2fs_attr *a, + struct f2fs_sb_info *sbi, + unsigned char *ui, unsigned long value) +{ + switch (a->size) { + case 1: + *(u8 *)ui = value; + break; + case 2: + *(u16 *)ui = value; + break; + case 4: + *(u32 *)ui = value; + break; + case 8: + *(u64 *)ui = value; + break; + default: + f2fs_bug_on(sbi, 1); + f2fs_err(sbi, "store sysfs node value with wrong type"); + } } static ssize_t __sbi_store(struct f2fs_attr *a, @@ -906,7 +947,7 @@ static ssize_t __sbi_store(struct f2fs_attr *a, return count; } - *ui = (unsigned int)t; + __sbi_store_value(a, sbi, ptr + a->offset, t); return count; } @@ -1053,24 +1094,27 @@ static struct f2fs_attr f2fs_attr_sb_##_name = { \ .id = F2FS_FEATURE_##_feat, \ } -#define F2FS_ATTR_OFFSET(_struct_type, _name, _mode, _show, _store, _offset) \ +#define F2FS_ATTR_OFFSET(_struct_type, _name, _mode, _show, _store, _offset, _size) \ static struct f2fs_attr f2fs_attr_##_name = { \ .attr = {.name = __stringify(_name), .mode = _mode }, \ .show = _show, \ .store = _store, \ .struct_type = _struct_type, \ - .offset = _offset \ + .offset = _offset, \ + .size = _size \ } #define F2FS_RO_ATTR(struct_type, struct_name, name, elname) \ F2FS_ATTR_OFFSET(struct_type, name, 0444, \ f2fs_sbi_show, NULL, \ - offsetof(struct struct_name, elname)) + offsetof(struct struct_name, elname), \ + sizeof_field(struct struct_name, elname)) #define F2FS_RW_ATTR(struct_type, struct_name, name, elname) \ F2FS_ATTR_OFFSET(struct_type, name, 0644, \ f2fs_sbi_show, f2fs_sbi_store, \ - offsetof(struct struct_name, elname)) + offsetof(struct struct_name, elname), \ + sizeof_field(struct struct_name, elname)) #define F2FS_GENERAL_RO_ATTR(name) \ static struct f2fs_attr f2fs_attr_##name = __ATTR(name, 0444, name##_show, NULL) From 995030be4ce6338c6ff814583c14166446a64008 Mon Sep 17 00:00:00 2001 From: Chao Yu Date: Wed, 7 Jan 2026 19:22:18 +0800 Subject: [PATCH 3243/3902] f2fs: fix to avoid UAF in f2fs_write_end_io() commit ce2739e482bce8d2c014d76c4531c877f382aa54 upstream. As syzbot reported an use-after-free issue in f2fs_write_end_io(). It is caused by below race condition: loop device umount - worker_thread - loop_process_work - do_req_filebacked - lo_rw_aio - lo_rw_aio_complete - blk_mq_end_request - blk_update_request - f2fs_write_end_io - dec_page_count - folio_end_writeback - kill_f2fs_super - kill_block_super - f2fs_put_super : free(sbi) : get_pages(, F2FS_WB_CP_DATA) accessed sbi which is freed In kill_f2fs_super(), we will drop all page caches of f2fs inodes before call free(sbi), it guarantee that all folios should end its writeback, so it should be safe to access sbi before last folio_end_writeback(). Let's relocate ckpt thread wakeup flow before folio_end_writeback() to resolve this issue. Cc: stable@kernel.org Fixes: e234088758fc ("f2fs: avoid wait if IO end up when do_checkpoint for better performance") Reported-by: syzbot+b4444e3c972a7a124187@syzkaller.appspotmail.com Closes: https://syzkaller.appspot.com/bug?extid=b4444e3c972a7a124187 Signed-off-by: Chao Yu Signed-off-by: Jaegeuk Kim Signed-off-by: Greg Kroah-Hartman --- fs/f2fs/data.c | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/fs/f2fs/data.c b/fs/f2fs/data.c index 0f9446143c8e17..986b410fd2d039 100644 --- a/fs/f2fs/data.c +++ b/fs/f2fs/data.c @@ -356,14 +356,20 @@ static void f2fs_write_end_io(struct bio *bio) folio->index != nid_of_node(folio)); dec_page_count(sbi, type); + + /* + * we should access sbi before folio_end_writeback() to + * avoid racing w/ kill_f2fs_super() + */ + if (type == F2FS_WB_CP_DATA && !get_pages(sbi, type) && + wq_has_sleeper(&sbi->cp_wait)) + wake_up(&sbi->cp_wait); + if (f2fs_in_warm_node_list(sbi, folio)) f2fs_del_fsync_node_entry(sbi, folio); folio_clear_f2fs_gcing(folio); folio_end_writeback(folio); } - if (!get_pages(sbi, F2FS_WB_CP_DATA) && - wq_has_sleeper(&sbi->cp_wait)) - wake_up(&sbi->cp_wait); bio_put(bio); } From 97e4f479939e5ac6272e2e2473297942c6549ed3 Mon Sep 17 00:00:00 2001 From: Daeho Jeong Date: Sat, 10 Jan 2026 15:54:05 -0800 Subject: [PATCH 3244/3902] f2fs: support non-4KB block size without packed_ssa feature commit e48e16f3e37fac76e2f0c14c58df2b0398a323b0 upstream. Currently, F2FS requires the packed_ssa feature to be enabled when utilizing non-4KB block sizes (e.g., 16KB). This restriction limits the flexibility of filesystem formatting options. This patch allows F2FS to support non-4KB block sizes even when the packed_ssa feature is disabled. It adjusts the SSA calculation logic to correctly handle summary entries in larger blocks without the packed layout. Cc: stable@kernel.org Fixes: 7ee8bc3942f2 ("f2fs: revert summary entry count from 2048 to 512 in 16kb block support") Signed-off-by: Daeho Jeong Reviewed-by: Chao Yu Signed-off-by: Jaegeuk Kim Signed-off-by: Greg Kroah-Hartman --- fs/f2fs/f2fs.h | 54 ++++++++++++++++++-------- fs/f2fs/gc.c | 23 +++++------ fs/f2fs/node.c | 12 +++--- fs/f2fs/recovery.c | 6 +-- fs/f2fs/segment.c | 86 ++++++++++++++++++++++------------------- fs/f2fs/segment.h | 9 ++--- fs/f2fs/super.c | 26 ++++++------- include/linux/f2fs_fs.h | 73 ++++++++++++++++++++-------------- 8 files changed, 166 insertions(+), 123 deletions(-) diff --git a/fs/f2fs/f2fs.h b/fs/f2fs/f2fs.h index 37ad0c27c5b480..fc55d6dde3e23b 100644 --- a/fs/f2fs/f2fs.h +++ b/fs/f2fs/f2fs.h @@ -508,13 +508,25 @@ struct fsync_inode_entry { #define nats_in_cursum(jnl) (le16_to_cpu((jnl)->n_nats)) #define sits_in_cursum(jnl) (le16_to_cpu((jnl)->n_sits)) -#define nat_in_journal(jnl, i) ((jnl)->nat_j.entries[i].ne) -#define nid_in_journal(jnl, i) ((jnl)->nat_j.entries[i].nid) -#define sit_in_journal(jnl, i) ((jnl)->sit_j.entries[i].se) -#define segno_in_journal(jnl, i) ((jnl)->sit_j.entries[i].segno) - -#define MAX_NAT_JENTRIES(jnl) (NAT_JOURNAL_ENTRIES - nats_in_cursum(jnl)) -#define MAX_SIT_JENTRIES(jnl) (SIT_JOURNAL_ENTRIES - sits_in_cursum(jnl)) +#define nat_in_journal(jnl, i) \ + (((struct nat_journal_entry *)(jnl)->nat_j.entries)[i].ne) +#define nid_in_journal(jnl, i) \ + (((struct nat_journal_entry *)(jnl)->nat_j.entries)[i].nid) +#define sit_in_journal(jnl, i) \ + (((struct sit_journal_entry *)(jnl)->sit_j.entries)[i].se) +#define segno_in_journal(jnl, i) \ + (((struct sit_journal_entry *)(jnl)->sit_j.entries)[i].segno) + +#define sum_entries(sum) ((struct f2fs_summary *)(sum)) +#define sum_journal(sbi, sum) \ + ((struct f2fs_journal *)((char *)(sum) + \ + ((sbi)->entries_in_sum * sizeof(struct f2fs_summary)))) +#define sum_footer(sbi, sum) \ + ((struct summary_footer *)((char *)(sum) + (sbi)->sum_blocksize - \ + sizeof(struct summary_footer))) + +#define MAX_NAT_JENTRIES(sbi, jnl) ((sbi)->nat_journal_entries - nats_in_cursum(jnl)) +#define MAX_SIT_JENTRIES(sbi, jnl) ((sbi)->sit_journal_entries - sits_in_cursum(jnl)) static inline int update_nats_in_cursum(struct f2fs_journal *journal, int i) { @@ -532,14 +544,6 @@ static inline int update_sits_in_cursum(struct f2fs_journal *journal, int i) return before; } -static inline bool __has_cursum_space(struct f2fs_journal *journal, - int size, int type) -{ - if (type == NAT_JOURNAL) - return size <= MAX_NAT_JENTRIES(journal); - return size <= MAX_SIT_JENTRIES(journal); -} - /* for inline stuff */ #define DEF_INLINE_RESERVED_SIZE 1 static inline int get_extra_isize(struct inode *inode); @@ -1750,6 +1754,15 @@ struct f2fs_sb_info { bool readdir_ra; /* readahead inode in readdir */ u64 max_io_bytes; /* max io bytes to merge IOs */ + /* variable summary block units */ + unsigned int sum_blocksize; /* sum block size */ + unsigned int sums_per_block; /* sum block count per block */ + unsigned int entries_in_sum; /* entry count in sum block */ + unsigned int sum_entry_size; /* total entry size in sum block */ + unsigned int sum_journal_size; /* journal size in sum block */ + unsigned int nat_journal_entries; /* nat journal entry count in the journal */ + unsigned int sit_journal_entries; /* sit journal entry count in the journal */ + block_t user_block_count; /* # of user blocks */ block_t total_valid_block_count; /* # of valid blocks */ block_t discard_blks; /* discard command candidats */ @@ -2799,6 +2812,14 @@ static inline block_t __start_sum_addr(struct f2fs_sb_info *sbi) return le32_to_cpu(F2FS_CKPT(sbi)->cp_pack_start_sum); } +static inline bool __has_cursum_space(struct f2fs_sb_info *sbi, + struct f2fs_journal *journal, int size, int type) +{ + if (type == NAT_JOURNAL) + return size <= MAX_NAT_JENTRIES(sbi, journal); + return size <= MAX_SIT_JENTRIES(sbi, journal); +} + extern void f2fs_mark_inode_dirty_sync(struct inode *inode, bool sync); static inline int inc_valid_node_count(struct f2fs_sb_info *sbi, struct inode *inode, bool is_inode) @@ -3952,7 +3973,8 @@ void f2fs_wait_on_block_writeback_range(struct inode *inode, block_t blkaddr, block_t len); void f2fs_write_data_summaries(struct f2fs_sb_info *sbi, block_t start_blk); void f2fs_write_node_summaries(struct f2fs_sb_info *sbi, block_t start_blk); -int f2fs_lookup_journal_in_cursum(struct f2fs_journal *journal, int type, +int f2fs_lookup_journal_in_cursum(struct f2fs_sb_info *sbi, + struct f2fs_journal *journal, int type, unsigned int val, int alloc); void f2fs_flush_sit_entries(struct f2fs_sb_info *sbi, struct cp_control *cpc); int f2fs_check_and_fix_write_pointer(struct f2fs_sb_info *sbi); diff --git a/fs/f2fs/gc.c b/fs/f2fs/gc.c index 6886486d266145..9d2f4f22fd3963 100644 --- a/fs/f2fs/gc.c +++ b/fs/f2fs/gc.c @@ -1766,8 +1766,8 @@ static int do_garbage_collect(struct f2fs_sb_info *sbi, sanity_check_seg_type(sbi, get_seg_entry(sbi, segno)->type); - segno = rounddown(segno, SUMS_PER_BLOCK); - sum_blk_cnt = DIV_ROUND_UP(end_segno - segno, SUMS_PER_BLOCK); + segno = rounddown(segno, sbi->sums_per_block); + sum_blk_cnt = DIV_ROUND_UP(end_segno - segno, sbi->sums_per_block); /* readahead multi ssa blocks those have contiguous address */ if (__is_large_section(sbi)) f2fs_ra_meta_pages(sbi, GET_SUM_BLOCK(sbi, segno), @@ -1777,17 +1777,17 @@ static int do_garbage_collect(struct f2fs_sb_info *sbi, while (segno < end_segno) { struct folio *sum_folio = f2fs_get_sum_folio(sbi, segno); - segno += SUMS_PER_BLOCK; + segno += sbi->sums_per_block; if (IS_ERR(sum_folio)) { int err = PTR_ERR(sum_folio); - end_segno = segno - SUMS_PER_BLOCK; - segno = rounddown(start_segno, SUMS_PER_BLOCK); + end_segno = segno - sbi->sums_per_block; + segno = rounddown(start_segno, sbi->sums_per_block); while (segno < end_segno) { sum_folio = filemap_get_folio(META_MAPPING(sbi), GET_SUM_BLOCK(sbi, segno)); folio_put_refs(sum_folio, 2); - segno += SUMS_PER_BLOCK; + segno += sbi->sums_per_block; } return err; } @@ -1803,8 +1803,8 @@ static int do_garbage_collect(struct f2fs_sb_info *sbi, /* find segment summary of victim */ struct folio *sum_folio = filemap_get_folio(META_MAPPING(sbi), GET_SUM_BLOCK(sbi, segno)); - unsigned int block_end_segno = rounddown(segno, SUMS_PER_BLOCK) - + SUMS_PER_BLOCK; + unsigned int block_end_segno = rounddown(segno, sbi->sums_per_block) + + sbi->sums_per_block; if (block_end_segno > end_segno) block_end_segno = end_segno; @@ -1830,12 +1830,13 @@ static int do_garbage_collect(struct f2fs_sb_info *sbi, migrated >= sbi->migration_granularity) continue; - sum = SUM_BLK_PAGE_ADDR(sum_folio, cur_segno); - if (type != GET_SUM_TYPE((&sum->footer))) { + sum = SUM_BLK_PAGE_ADDR(sbi, sum_folio, cur_segno); + if (type != GET_SUM_TYPE(sum_footer(sbi, sum))) { f2fs_err(sbi, "Inconsistent segment (%u) type " "[%d, %d] in SSA and SIT", cur_segno, type, - GET_SUM_TYPE((&sum->footer))); + GET_SUM_TYPE( + sum_footer(sbi, sum))); f2fs_stop_checkpoint(sbi, false, STOP_CP_REASON_CORRUPTED_SUMMARY); continue; diff --git a/fs/f2fs/node.c b/fs/f2fs/node.c index 2c6102bee34957..3dacc90ee9a6cf 100644 --- a/fs/f2fs/node.c +++ b/fs/f2fs/node.c @@ -606,7 +606,7 @@ int f2fs_get_node_info(struct f2fs_sb_info *sbi, nid_t nid, goto retry; } - i = f2fs_lookup_journal_in_cursum(journal, NAT_JOURNAL, nid, 0); + i = f2fs_lookup_journal_in_cursum(sbi, journal, NAT_JOURNAL, nid, 0); if (i >= 0) { ne = nat_in_journal(journal, i); node_info_from_raw_nat(ni, &ne); @@ -2943,7 +2943,7 @@ int f2fs_restore_node_summary(struct f2fs_sb_info *sbi, /* scan the node segment */ last_offset = BLKS_PER_SEG(sbi); addr = START_BLOCK(sbi, segno); - sum_entry = &sum->entries[0]; + sum_entry = sum_entries(sum); for (i = 0; i < last_offset; i += nrpages, addr += nrpages) { nrpages = bio_max_segs(last_offset - i); @@ -3084,7 +3084,7 @@ static int __flush_nat_entry_set(struct f2fs_sb_info *sbi, * #2, flush nat entries to nat page. */ if (enabled_nat_bits(sbi, cpc) || - !__has_cursum_space(journal, set->entry_cnt, NAT_JOURNAL)) + !__has_cursum_space(sbi, journal, set->entry_cnt, NAT_JOURNAL)) to_journal = false; if (to_journal) { @@ -3107,7 +3107,7 @@ static int __flush_nat_entry_set(struct f2fs_sb_info *sbi, f2fs_bug_on(sbi, nat_get_blkaddr(ne) == NEW_ADDR); if (to_journal) { - offset = f2fs_lookup_journal_in_cursum(journal, + offset = f2fs_lookup_journal_in_cursum(sbi, journal, NAT_JOURNAL, nid, 1); f2fs_bug_on(sbi, offset < 0); raw_ne = &nat_in_journal(journal, offset); @@ -3178,7 +3178,7 @@ int f2fs_flush_nat_entries(struct f2fs_sb_info *sbi, struct cp_control *cpc) * into nat entry set. */ if (enabled_nat_bits(sbi, cpc) || - !__has_cursum_space(journal, + !__has_cursum_space(sbi, journal, nm_i->nat_cnt[DIRTY_NAT], NAT_JOURNAL)) remove_nats_in_journal(sbi); @@ -3189,7 +3189,7 @@ int f2fs_flush_nat_entries(struct f2fs_sb_info *sbi, struct cp_control *cpc) set_idx = setvec[found - 1]->set + 1; for (idx = 0; idx < found; idx++) __adjust_nat_entry_set(setvec[idx], &sets, - MAX_NAT_JENTRIES(journal)); + MAX_NAT_JENTRIES(sbi, journal)); } /* flush dirty nats in nat entry set */ diff --git a/fs/f2fs/recovery.c b/fs/f2fs/recovery.c index 62a0c71b5b75d6..06674e694b271e 100644 --- a/fs/f2fs/recovery.c +++ b/fs/f2fs/recovery.c @@ -514,7 +514,7 @@ static int check_index_in_prev_nodes(struct f2fs_sb_info *sbi, struct curseg_info *curseg = CURSEG_I(sbi, i); if (curseg->segno == segno) { - sum = curseg->sum_blk->entries[blkoff]; + sum = sum_entries(curseg->sum_blk)[blkoff]; goto got_it; } } @@ -522,8 +522,8 @@ static int check_index_in_prev_nodes(struct f2fs_sb_info *sbi, sum_folio = f2fs_get_sum_folio(sbi, segno); if (IS_ERR(sum_folio)) return PTR_ERR(sum_folio); - sum_node = SUM_BLK_PAGE_ADDR(sum_folio, segno); - sum = sum_node->entries[blkoff]; + sum_node = SUM_BLK_PAGE_ADDR(sbi, sum_folio, segno); + sum = sum_entries(sum_node)[blkoff]; f2fs_folio_put(sum_folio, true); got_it: /* Use the locked dnode page and inode */ diff --git a/fs/f2fs/segment.c b/fs/f2fs/segment.c index 10d873d1b328cd..f1a6720160633d 100644 --- a/fs/f2fs/segment.c +++ b/fs/f2fs/segment.c @@ -2689,12 +2689,12 @@ int f2fs_npages_for_summary_flush(struct f2fs_sb_info *sbi, bool for_ra) valid_sum_count += f2fs_curseg_valid_blocks(sbi, i); } - sum_in_page = (PAGE_SIZE - 2 * SUM_JOURNAL_SIZE - + sum_in_page = (sbi->sum_blocksize - 2 * sbi->sum_journal_size - SUM_FOOTER_SIZE) / SUMMARY_SIZE; if (valid_sum_count <= sum_in_page) return 1; else if ((valid_sum_count - sum_in_page) <= - (PAGE_SIZE - SUM_FOOTER_SIZE) / SUMMARY_SIZE) + (sbi->sum_blocksize - SUM_FOOTER_SIZE) / SUMMARY_SIZE) return 2; return 3; } @@ -2714,7 +2714,7 @@ void f2fs_update_meta_page(struct f2fs_sb_info *sbi, { struct folio *folio; - if (SUMS_PER_BLOCK == 1) + if (!f2fs_sb_has_packed_ssa(sbi)) folio = f2fs_grab_meta_folio(sbi, blk_addr); else folio = f2fs_get_meta_folio_retry(sbi, blk_addr); @@ -2732,7 +2732,7 @@ static void write_sum_page(struct f2fs_sb_info *sbi, { struct folio *folio; - if (SUMS_PER_BLOCK == 1) + if (!f2fs_sb_has_packed_ssa(sbi)) return f2fs_update_meta_page(sbi, (void *)sum_blk, GET_SUM_BLOCK(sbi, segno)); @@ -2740,7 +2740,8 @@ static void write_sum_page(struct f2fs_sb_info *sbi, if (IS_ERR(folio)) return; - memcpy(SUM_BLK_PAGE_ADDR(folio, segno), sum_blk, sizeof(*sum_blk)); + memcpy(SUM_BLK_PAGE_ADDR(sbi, folio, segno), sum_blk, + sbi->sum_blocksize); folio_mark_dirty(folio); f2fs_folio_put(folio, true); } @@ -2759,11 +2760,11 @@ static void write_current_sum_page(struct f2fs_sb_info *sbi, mutex_lock(&curseg->curseg_mutex); down_read(&curseg->journal_rwsem); - memcpy(&dst->journal, curseg->journal, SUM_JOURNAL_SIZE); + memcpy(sum_journal(sbi, dst), curseg->journal, sbi->sum_journal_size); up_read(&curseg->journal_rwsem); - memcpy(dst->entries, src->entries, SUM_ENTRY_SIZE); - memcpy(&dst->footer, &src->footer, SUM_FOOTER_SIZE); + memcpy(sum_entries(dst), sum_entries(src), sbi->sum_entry_size); + memcpy(sum_footer(sbi, dst), sum_footer(sbi, src), SUM_FOOTER_SIZE); mutex_unlock(&curseg->curseg_mutex); @@ -2936,7 +2937,7 @@ static void reset_curseg(struct f2fs_sb_info *sbi, int type, int modified) curseg->next_blkoff = 0; curseg->next_segno = NULL_SEGNO; - sum_footer = &(curseg->sum_blk->footer); + sum_footer = sum_footer(sbi, curseg->sum_blk); memset(sum_footer, 0, sizeof(struct summary_footer)); sanity_check_seg_type(sbi, seg_type); @@ -3082,11 +3083,11 @@ static int change_curseg(struct f2fs_sb_info *sbi, int type) sum_folio = f2fs_get_sum_folio(sbi, new_segno); if (IS_ERR(sum_folio)) { /* GC won't be able to use stale summary pages by cp_error */ - memset(curseg->sum_blk, 0, SUM_ENTRY_SIZE); + memset(curseg->sum_blk, 0, sbi->sum_entry_size); return PTR_ERR(sum_folio); } - sum_node = SUM_BLK_PAGE_ADDR(sum_folio, new_segno); - memcpy(curseg->sum_blk, sum_node, SUM_ENTRY_SIZE); + sum_node = SUM_BLK_PAGE_ADDR(sbi, sum_folio, new_segno); + memcpy(curseg->sum_blk, sum_node, sbi->sum_entry_size); f2fs_folio_put(sum_folio, true); return 0; } @@ -3818,7 +3819,7 @@ int f2fs_allocate_data_block(struct f2fs_sb_info *sbi, struct folio *folio, f2fs_wait_discard_bio(sbi, *new_blkaddr); - curseg->sum_blk->entries[curseg->next_blkoff] = *sum; + sum_entries(curseg->sum_blk)[curseg->next_blkoff] = *sum; if (curseg->alloc_type == SSR) { curseg->next_blkoff = f2fs_find_next_ssr_block(sbi, curseg); } else { @@ -4187,7 +4188,7 @@ void f2fs_do_replace_block(struct f2fs_sb_info *sbi, struct f2fs_summary *sum, } curseg->next_blkoff = GET_BLKOFF_FROM_SEG0(sbi, new_blkaddr); - curseg->sum_blk->entries[curseg->next_blkoff] = *sum; + sum_entries(curseg->sum_blk)[curseg->next_blkoff] = *sum; if (!recover_curseg || recover_newaddr) { if (!from_gc) @@ -4307,12 +4308,12 @@ static int read_compacted_summaries(struct f2fs_sb_info *sbi) /* Step 1: restore nat cache */ seg_i = CURSEG_I(sbi, CURSEG_HOT_DATA); - memcpy(seg_i->journal, kaddr, SUM_JOURNAL_SIZE); + memcpy(seg_i->journal, kaddr, sbi->sum_journal_size); /* Step 2: restore sit cache */ seg_i = CURSEG_I(sbi, CURSEG_COLD_DATA); - memcpy(seg_i->journal, kaddr + SUM_JOURNAL_SIZE, SUM_JOURNAL_SIZE); - offset = 2 * SUM_JOURNAL_SIZE; + memcpy(seg_i->journal, kaddr + sbi->sum_journal_size, sbi->sum_journal_size); + offset = 2 * sbi->sum_journal_size; /* Step 3: restore summary entries */ for (i = CURSEG_HOT_DATA; i <= CURSEG_COLD_DATA; i++) { @@ -4334,9 +4335,9 @@ static int read_compacted_summaries(struct f2fs_sb_info *sbi) struct f2fs_summary *s; s = (struct f2fs_summary *)(kaddr + offset); - seg_i->sum_blk->entries[j] = *s; + sum_entries(seg_i->sum_blk)[j] = *s; offset += SUMMARY_SIZE; - if (offset + SUMMARY_SIZE <= PAGE_SIZE - + if (offset + SUMMARY_SIZE <= sbi->sum_blocksize - SUM_FOOTER_SIZE) continue; @@ -4392,7 +4393,7 @@ static int read_normal_summaries(struct f2fs_sb_info *sbi, int type) if (IS_NODESEG(type)) { if (__exist_node_summaries(sbi)) { - struct f2fs_summary *ns = &sum->entries[0]; + struct f2fs_summary *ns = sum_entries(sum); int i; for (i = 0; i < BLKS_PER_SEG(sbi); i++, ns++) { @@ -4412,11 +4413,13 @@ static int read_normal_summaries(struct f2fs_sb_info *sbi, int type) /* update journal info */ down_write(&curseg->journal_rwsem); - memcpy(curseg->journal, &sum->journal, SUM_JOURNAL_SIZE); + memcpy(curseg->journal, sum_journal(sbi, sum), sbi->sum_journal_size); up_write(&curseg->journal_rwsem); - memcpy(curseg->sum_blk->entries, sum->entries, SUM_ENTRY_SIZE); - memcpy(&curseg->sum_blk->footer, &sum->footer, SUM_FOOTER_SIZE); + memcpy(sum_entries(curseg->sum_blk), sum_entries(sum), + sbi->sum_entry_size); + memcpy(sum_footer(sbi, curseg->sum_blk), sum_footer(sbi, sum), + SUM_FOOTER_SIZE); curseg->next_segno = segno; reset_curseg(sbi, type, 0); curseg->alloc_type = ckpt->alloc_type[type]; @@ -4460,8 +4463,8 @@ static int restore_curseg_summaries(struct f2fs_sb_info *sbi) } /* sanity check for summary blocks */ - if (nats_in_cursum(nat_j) > NAT_JOURNAL_ENTRIES || - sits_in_cursum(sit_j) > SIT_JOURNAL_ENTRIES) { + if (nats_in_cursum(nat_j) > sbi->nat_journal_entries || + sits_in_cursum(sit_j) > sbi->sit_journal_entries) { f2fs_err(sbi, "invalid journal entries nats %u sits %u", nats_in_cursum(nat_j), sits_in_cursum(sit_j)); return -EINVAL; @@ -4485,13 +4488,13 @@ static void write_compacted_summaries(struct f2fs_sb_info *sbi, block_t blkaddr) /* Step 1: write nat cache */ seg_i = CURSEG_I(sbi, CURSEG_HOT_DATA); - memcpy(kaddr, seg_i->journal, SUM_JOURNAL_SIZE); - written_size += SUM_JOURNAL_SIZE; + memcpy(kaddr, seg_i->journal, sbi->sum_journal_size); + written_size += sbi->sum_journal_size; /* Step 2: write sit cache */ seg_i = CURSEG_I(sbi, CURSEG_COLD_DATA); - memcpy(kaddr + written_size, seg_i->journal, SUM_JOURNAL_SIZE); - written_size += SUM_JOURNAL_SIZE; + memcpy(kaddr + written_size, seg_i->journal, sbi->sum_journal_size); + written_size += sbi->sum_journal_size; /* Step 3: write summary entries */ for (i = CURSEG_HOT_DATA; i <= CURSEG_COLD_DATA; i++) { @@ -4504,7 +4507,7 @@ static void write_compacted_summaries(struct f2fs_sb_info *sbi, block_t blkaddr) written_size = 0; } summary = (struct f2fs_summary *)(kaddr + written_size); - *summary = seg_i->sum_blk->entries[j]; + *summary = sum_entries(seg_i->sum_blk)[j]; written_size += SUMMARY_SIZE; if (written_size + SUMMARY_SIZE <= PAGE_SIZE - @@ -4549,8 +4552,9 @@ void f2fs_write_node_summaries(struct f2fs_sb_info *sbi, block_t start_blk) write_normal_summaries(sbi, start_blk, CURSEG_HOT_NODE); } -int f2fs_lookup_journal_in_cursum(struct f2fs_journal *journal, int type, - unsigned int val, int alloc) +int f2fs_lookup_journal_in_cursum(struct f2fs_sb_info *sbi, + struct f2fs_journal *journal, int type, + unsigned int val, int alloc) { int i; @@ -4559,13 +4563,13 @@ int f2fs_lookup_journal_in_cursum(struct f2fs_journal *journal, int type, if (le32_to_cpu(nid_in_journal(journal, i)) == val) return i; } - if (alloc && __has_cursum_space(journal, 1, NAT_JOURNAL)) + if (alloc && __has_cursum_space(sbi, journal, 1, NAT_JOURNAL)) return update_nats_in_cursum(journal, 1); } else if (type == SIT_JOURNAL) { for (i = 0; i < sits_in_cursum(journal); i++) if (le32_to_cpu(segno_in_journal(journal, i)) == val) return i; - if (alloc && __has_cursum_space(journal, 1, SIT_JOURNAL)) + if (alloc && __has_cursum_space(sbi, journal, 1, SIT_JOURNAL)) return update_sits_in_cursum(journal, 1); } return -1; @@ -4713,8 +4717,8 @@ void f2fs_flush_sit_entries(struct f2fs_sb_info *sbi, struct cp_control *cpc) * entries, remove all entries from journal and add and account * them in sit entry set. */ - if (!__has_cursum_space(journal, sit_i->dirty_sentries, SIT_JOURNAL) || - !to_journal) + if (!__has_cursum_space(sbi, journal, + sit_i->dirty_sentries, SIT_JOURNAL) || !to_journal) remove_sits_in_journal(sbi); /* @@ -4731,7 +4735,8 @@ void f2fs_flush_sit_entries(struct f2fs_sb_info *sbi, struct cp_control *cpc) unsigned int segno = start_segno; if (to_journal && - !__has_cursum_space(journal, ses->entry_cnt, SIT_JOURNAL)) + !__has_cursum_space(sbi, journal, ses->entry_cnt, + SIT_JOURNAL)) to_journal = false; if (to_journal) { @@ -4759,7 +4764,7 @@ void f2fs_flush_sit_entries(struct f2fs_sb_info *sbi, struct cp_control *cpc) } if (to_journal) { - offset = f2fs_lookup_journal_in_cursum(journal, + offset = f2fs_lookup_journal_in_cursum(sbi, journal, SIT_JOURNAL, segno, 1); f2fs_bug_on(sbi, offset < 0); segno_in_journal(journal, offset) = @@ -4966,12 +4971,13 @@ static int build_curseg(struct f2fs_sb_info *sbi) for (i = 0; i < NO_CHECK_TYPE; i++) { mutex_init(&array[i].curseg_mutex); - array[i].sum_blk = f2fs_kzalloc(sbi, PAGE_SIZE, GFP_KERNEL); + array[i].sum_blk = f2fs_kzalloc(sbi, sbi->sum_blocksize, + GFP_KERNEL); if (!array[i].sum_blk) return -ENOMEM; init_rwsem(&array[i].journal_rwsem); array[i].journal = f2fs_kzalloc(sbi, - sizeof(struct f2fs_journal), GFP_KERNEL); + sbi->sum_journal_size, GFP_KERNEL); if (!array[i].journal) return -ENOMEM; array[i].seg_type = log_type_to_seg_type(i); diff --git a/fs/f2fs/segment.h b/fs/f2fs/segment.h index f3e2fff45cf532..d05d133c89aff8 100644 --- a/fs/f2fs/segment.h +++ b/fs/f2fs/segment.h @@ -85,12 +85,11 @@ static inline void sanity_check_seg_type(struct f2fs_sb_info *sbi, #define GET_ZONE_FROM_SEG(sbi, segno) \ GET_ZONE_FROM_SEC(sbi, GET_SEC_FROM_SEG(sbi, segno)) -#define SUMS_PER_BLOCK (F2FS_BLKSIZE / F2FS_SUM_BLKSIZE) #define GET_SUM_BLOCK(sbi, segno) \ - (SM_I(sbi)->ssa_blkaddr + (segno / SUMS_PER_BLOCK)) -#define GET_SUM_BLKOFF(segno) (segno % SUMS_PER_BLOCK) -#define SUM_BLK_PAGE_ADDR(folio, segno) \ - (folio_address(folio) + GET_SUM_BLKOFF(segno) * F2FS_SUM_BLKSIZE) + (SM_I(sbi)->ssa_blkaddr + (segno / (sbi)->sums_per_block)) +#define GET_SUM_BLKOFF(sbi, segno) (segno % (sbi)->sums_per_block) +#define SUM_BLK_PAGE_ADDR(sbi, folio, segno) \ + (folio_address(folio) + GET_SUM_BLKOFF(sbi, segno) * (sbi)->sum_blocksize) #define GET_SUM_TYPE(footer) ((footer)->entry_type) #define SET_SUM_TYPE(footer, type) ((footer)->entry_type = (type)) diff --git a/fs/f2fs/super.c b/fs/f2fs/super.c index eb466a11d9d7f6..907f632193abf0 100644 --- a/fs/f2fs/super.c +++ b/fs/f2fs/super.c @@ -4059,20 +4059,6 @@ static int sanity_check_raw_super(struct f2fs_sb_info *sbi, if (sanity_check_area_boundary(sbi, folio, index)) return -EFSCORRUPTED; - /* - * Check for legacy summary layout on 16KB+ block devices. - * Modern f2fs-tools packs multiple 4KB summary areas into one block, - * whereas legacy versions used one block per summary, leading - * to a much larger SSA. - */ - if (SUMS_PER_BLOCK > 1 && - !(__F2FS_HAS_FEATURE(raw_super, F2FS_FEATURE_PACKED_SSA))) { - f2fs_info(sbi, "Error: Device formatted with a legacy version. " - "Please reformat with a tool supporting the packed ssa " - "feature for block sizes larger than 4kb."); - return -EOPNOTSUPP; - } - return 0; } @@ -4283,6 +4269,18 @@ static void init_sb_info(struct f2fs_sb_info *sbi) spin_lock_init(&sbi->gc_remaining_trials_lock); atomic64_set(&sbi->current_atomic_write, 0); + sbi->sum_blocksize = f2fs_sb_has_packed_ssa(sbi) ? + 4096 : sbi->blocksize; + sbi->sums_per_block = sbi->blocksize / sbi->sum_blocksize; + sbi->entries_in_sum = sbi->sum_blocksize / 8; + sbi->sum_entry_size = SUMMARY_SIZE * sbi->entries_in_sum; + sbi->sum_journal_size = sbi->sum_blocksize - SUM_FOOTER_SIZE - + sbi->sum_entry_size; + sbi->nat_journal_entries = (sbi->sum_journal_size - 2) / + sizeof(struct nat_journal_entry); + sbi->sit_journal_entries = (sbi->sum_journal_size - 2) / + sizeof(struct sit_journal_entry); + sbi->dir_level = DEF_DIR_LEVEL; sbi->interval_time[CP_TIME] = DEF_CP_INTERVAL; sbi->interval_time[REQ_TIME] = DEF_IDLE_INTERVAL; diff --git a/include/linux/f2fs_fs.h b/include/linux/f2fs_fs.h index a7880787cad366..dc41722fcc9dde 100644 --- a/include/linux/f2fs_fs.h +++ b/include/linux/f2fs_fs.h @@ -17,7 +17,6 @@ #define F2FS_LOG_SECTORS_PER_BLOCK (PAGE_SHIFT - 9) /* log number for sector/blk */ #define F2FS_BLKSIZE PAGE_SIZE /* support only block == page */ #define F2FS_BLKSIZE_BITS PAGE_SHIFT /* bits for F2FS_BLKSIZE */ -#define F2FS_SUM_BLKSIZE 4096 /* only support 4096 byte sum block */ #define F2FS_MAX_EXTENSION 64 /* # of extension entries */ #define F2FS_EXTENSION_LEN 8 /* max size of extension */ @@ -442,10 +441,8 @@ struct f2fs_sit_block { * from node's page's beginning to get a data block address. * ex) data_blkaddr = (block_t)(nodepage_start_address + ofs_in_node) */ -#define ENTRIES_IN_SUM (F2FS_SUM_BLKSIZE / 8) #define SUMMARY_SIZE (7) /* sizeof(struct f2fs_summary) */ #define SUM_FOOTER_SIZE (5) /* sizeof(struct summary_footer) */ -#define SUM_ENTRY_SIZE (SUMMARY_SIZE * ENTRIES_IN_SUM) /* a summary entry for a block in a segment */ struct f2fs_summary { @@ -468,22 +465,6 @@ struct summary_footer { __le32 check_sum; /* summary checksum */ } __packed; -#define SUM_JOURNAL_SIZE (F2FS_SUM_BLKSIZE - SUM_FOOTER_SIZE -\ - SUM_ENTRY_SIZE) -#define NAT_JOURNAL_ENTRIES ((SUM_JOURNAL_SIZE - 2) /\ - sizeof(struct nat_journal_entry)) -#define NAT_JOURNAL_RESERVED ((SUM_JOURNAL_SIZE - 2) %\ - sizeof(struct nat_journal_entry)) -#define SIT_JOURNAL_ENTRIES ((SUM_JOURNAL_SIZE - 2) /\ - sizeof(struct sit_journal_entry)) -#define SIT_JOURNAL_RESERVED ((SUM_JOURNAL_SIZE - 2) %\ - sizeof(struct sit_journal_entry)) - -/* Reserved area should make size of f2fs_extra_info equals to - * that of nat_journal and sit_journal. - */ -#define EXTRA_INFO_RESERVED (SUM_JOURNAL_SIZE - 2 - 8) - /* * frequently updated NAT/SIT entries can be stored in the spare area in * summary blocks @@ -498,9 +479,16 @@ struct nat_journal_entry { struct f2fs_nat_entry ne; } __packed; +/* + * The nat_journal structure is a placeholder whose actual size varies depending + * on the use of packed_ssa. Therefore, it must always be accessed only through + * specific sets of macros and fields, and size calculations should use + * size-related macros instead of sizeof(). + * Relevant macros: sbi->nat_journal_entries, nat_in_journal(), + * nid_in_journal(), MAX_NAT_JENTRIES(). + */ struct nat_journal { - struct nat_journal_entry entries[NAT_JOURNAL_ENTRIES]; - __u8 reserved[NAT_JOURNAL_RESERVED]; + struct nat_journal_entry entries[0]; } __packed; struct sit_journal_entry { @@ -508,14 +496,21 @@ struct sit_journal_entry { struct f2fs_sit_entry se; } __packed; +/* + * The sit_journal structure is a placeholder whose actual size varies depending + * on the use of packed_ssa. Therefore, it must always be accessed only through + * specific sets of macros and fields, and size calculations should use + * size-related macros instead of sizeof(). + * Relevant macros: sbi->sit_journal_entries, sit_in_journal(), + * segno_in_journal(), MAX_SIT_JENTRIES(). + */ struct sit_journal { - struct sit_journal_entry entries[SIT_JOURNAL_ENTRIES]; - __u8 reserved[SIT_JOURNAL_RESERVED]; + struct sit_journal_entry entries[0]; } __packed; struct f2fs_extra_info { __le64 kbytes_written; - __u8 reserved[EXTRA_INFO_RESERVED]; + __u8 reserved[]; } __packed; struct f2fs_journal { @@ -531,11 +526,33 @@ struct f2fs_journal { }; } __packed; -/* Block-sized summary block structure */ +/* + * Block-sized summary block structure + * + * The f2fs_summary_block structure is a placeholder whose actual size varies + * depending on the use of packed_ssa. Therefore, it must always be accessed + * only through specific sets of macros and fields, and size calculations should + * use size-related macros instead of sizeof(). + * Relevant macros: sbi->sum_blocksize, sbi->entries_in_sum, + * sbi->sum_entry_size, sum_entries(), sum_journal(), sum_footer(). + * + * Summary Block Layout + * + * +-----------------------+ <--- Block Start + * | struct f2fs_summary | + * | entries[0] | + * | ... | + * | entries[N-1] | + * +-----------------------+ + * | struct f2fs_journal | + * +-----------------------+ + * | struct summary_footer | + * +-----------------------+ <--- Block End + */ struct f2fs_summary_block { - struct f2fs_summary entries[ENTRIES_IN_SUM]; - struct f2fs_journal journal; - struct summary_footer footer; + struct f2fs_summary entries[0]; + // struct f2fs_journal journal; + // struct summary_footer footer; } __packed; /* From fee27b69dde1a05908b350eea42937af2387c4fe Mon Sep 17 00:00:00 2001 From: Chao Yu Date: Tue, 13 Jan 2026 14:22:29 +0800 Subject: [PATCH 3245/3902] f2fs: fix to avoid mapping wrong physical block for swapfile commit 5c145c03188bc9ba1c29e0bc4d527a5978fc47f9 upstream. Xiaolong Guo reported a f2fs bug in bugzilla [1] [1] https://bugzilla.kernel.org/show_bug.cgi?id=220951 Quoted: "When using stress-ng's swap stress test on F2FS filesystem with kernel 6.6+, the system experiences data corruption leading to either: 1 dm-verity corruption errors and device reboot 2 F2FS node corruption errors and boot hangs The issue occurs specifically when: 1 Using F2FS filesystem (ext4 is unaffected) 2 Swapfile size is less than F2FS section size (2MB) 3 Swapfile has fragmented physical layout (multiple non-contiguous extents) 4 Kernel version is 6.6+ (6.1 is unaffected) The root cause is in check_swap_activate() function in fs/f2fs/data.c. When the first extent of a small swapfile (< 2MB) is not aligned to section boundaries, the function incorrectly treats it as the last extent, failing to map subsequent extents. This results in incorrect swap_extent creation where only the first extent is mapped, causing subsequent swap writes to overwrite wrong physical locations (other files' data). Steps to Reproduce 1 Setup a device with F2FS-formatted userdata partition 2 Compile stress-ng from https://github.com/ColinIanKing/stress-ng 3 Run swap stress test: (Android devices) adb shell "cd /data/stressng; ./stress-ng-64 --metrics-brief --timeout 60 --swap 0" Log: 1 Ftrace shows in kernel 6.6, only first extent is mapped during second f2fs_map_blocks call in check_swap_activate(): stress-ng-swap-8990: f2fs_map_blocks: ino=11002, file offset=0, start blkaddr=0x43143, len=0x1 (Only 4KB mapped, not the full swapfile) 2 in kernel 6.1, both extents are correctly mapped: stress-ng-swap-5966: f2fs_map_blocks: ino=28011, file offset=0, start blkaddr=0x13cd4, len=0x1 stress-ng-swap-5966: f2fs_map_blocks: ino=28011, file offset=1, start blkaddr=0x60c84b, len=0xff The problematic code is in check_swap_activate(): if ((pblock - SM_I(sbi)->main_blkaddr) % blks_per_sec || nr_pblocks % blks_per_sec || !f2fs_valid_pinned_area(sbi, pblock)) { bool last_extent = false; not_aligned++; nr_pblocks = roundup(nr_pblocks, blks_per_sec); if (cur_lblock + nr_pblocks > sis->max) nr_pblocks -= blks_per_sec; /* this extent is last one */ if (!nr_pblocks) { nr_pblocks = last_lblock - cur_lblock; last_extent = true; } ret = f2fs_migrate_blocks(inode, cur_lblock, nr_pblocks); if (ret) { if (ret == -ENOENT) ret = -EINVAL; goto out; } if (!last_extent) goto retry; } When the first extent is unaligned and roundup(nr_pblocks, blks_per_sec) exceeds sis->max, we subtract blks_per_sec resulting in nr_pblocks = 0. The code then incorrectly assumes this is the last extent, sets nr_pblocks = last_lblock - cur_lblock (entire swapfile), and performs migration. After migration, it doesn't retry mapping, so subsequent extents are never processed. " In order to fix this issue, we need to lookup block mapping info after we migrate all blocks in the tail of swapfile. Cc: stable@kernel.org Fixes: 9703d69d9d15 ("f2fs: support file pinning for zoned devices") Cc: Daeho Jeong Reported-and-tested-by: Xiaolong Guo Closes: https://bugzilla.kernel.org/show_bug.cgi?id=220951 Signed-off-by: Chao Yu Signed-off-by: Jaegeuk Kim Signed-off-by: Greg Kroah-Hartman --- fs/f2fs/data.c | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/fs/f2fs/data.c b/fs/f2fs/data.c index 986b410fd2d039..f14ee3d0aaa2d1 100644 --- a/fs/f2fs/data.c +++ b/fs/f2fs/data.c @@ -3939,6 +3939,7 @@ static int check_swap_activate(struct swap_info_struct *sis, while (cur_lblock < last_lblock && cur_lblock < sis->max) { struct f2fs_map_blocks map; + bool last_extent = false; retry: cond_resched(); @@ -3964,11 +3965,10 @@ static int check_swap_activate(struct swap_info_struct *sis, pblock = map.m_pblk; nr_pblocks = map.m_len; - if ((pblock - SM_I(sbi)->main_blkaddr) % blks_per_sec || - nr_pblocks % blks_per_sec || - f2fs_is_sequential_zone_area(sbi, pblock)) { - bool last_extent = false; - + if (!last_extent && + ((pblock - SM_I(sbi)->main_blkaddr) % blks_per_sec || + nr_pblocks % blks_per_sec || + f2fs_is_sequential_zone_area(sbi, pblock))) { not_aligned++; nr_pblocks = roundup(nr_pblocks, blks_per_sec); @@ -3989,8 +3989,8 @@ static int check_swap_activate(struct swap_info_struct *sis, goto out; } - if (!last_extent) - goto retry; + /* lookup block mapping info after block migration */ + goto retry; } if (cur_lblock + nr_pblocks >= sis->max) From 0415ae51a40e346ca233dcc1b4b7c486d70e69d2 Mon Sep 17 00:00:00 2001 From: Yeongjin Gil Date: Thu, 22 Jan 2026 19:45:27 +0900 Subject: [PATCH 3246/3902] f2fs: optimize f2fs_overwrite_io() for f2fs_iomap_begin commit d860974a7e38d35e9e2c4dc8a9f4223b38b6ad99 upstream. When overwriting already allocated blocks, f2fs_iomap_begin() calls f2fs_overwrite_io() to check block mappings. However, f2fs_overwrite_io() iterates through all mapped blocks in the range, which can be inefficient for fragmented files with large I/O requests. This patch optimizes f2fs_overwrite_io() by adding a 'check_first' parameter and introducing __f2fs_overwrite_io() helper. When called from f2fs_iomap_begin(), we only check the first mapping to determine if the range is already allocated, which is sufficient for setting map.m_may_create. This optimization significantly reduces the number of f2fs_map_blocks() calls in f2fs_overwrite_io() when called from f2fs_iomap_begin(), especially for fragmented files with large I/O requests. Cc: stable@kernel.org Fixes: 351bc761338d ("f2fs: optimize f2fs DIO overwrites") Reviewed-by: Sungjong Seo Reviewed-by: Sunmin Jeong Signed-off-by: Yeongjin Gil Reviewed-by: Chao Yu Signed-off-by: Jaegeuk Kim Signed-off-by: Greg Kroah-Hartman --- fs/f2fs/data.c | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/fs/f2fs/data.c b/fs/f2fs/data.c index f14ee3d0aaa2d1..debdc6ae6eb9d8 100644 --- a/fs/f2fs/data.c +++ b/fs/f2fs/data.c @@ -1799,7 +1799,8 @@ int f2fs_map_blocks(struct inode *inode, struct f2fs_map_blocks *map, int flag) return err; } -bool f2fs_overwrite_io(struct inode *inode, loff_t pos, size_t len) +static bool __f2fs_overwrite_io(struct inode *inode, loff_t pos, size_t len, + bool check_first) { struct f2fs_map_blocks map; block_t last_lblk; @@ -1821,10 +1822,17 @@ bool f2fs_overwrite_io(struct inode *inode, loff_t pos, size_t len) if (err || map.m_len == 0) return false; map.m_lblk += map.m_len; + if (check_first) + break; } return true; } +bool f2fs_overwrite_io(struct inode *inode, loff_t pos, size_t len) +{ + return __f2fs_overwrite_io(inode, pos, len, false); +} + static int f2fs_xattr_fiemap(struct inode *inode, struct fiemap_extent_info *fieinfo) { @@ -4191,7 +4199,7 @@ static int f2fs_iomap_begin(struct inode *inode, loff_t offset, loff_t length, * f2fs_map_lock and f2fs_balance_fs are not necessary. */ if ((flags & IOMAP_WRITE) && - !f2fs_overwrite_io(inode, offset, length)) + !__f2fs_overwrite_io(inode, offset, length, true)) map.m_may_create = true; err = f2fs_map_blocks(inode, &map, F2FS_GET_BLOCK_DIO); From f611dafe0ffd33bc3fe522506af38903f34ffcc1 Mon Sep 17 00:00:00 2001 From: Danilo Krummrich Date: Wed, 21 Jan 2026 15:12:01 +0100 Subject: [PATCH 3247/3902] iommu/arm-smmu-qcom: do not register driver in probe() commit ed1ac3c977dd6b119405fa36dd41f7151bd5b4de upstream. Commit 0b4eeee2876f ("iommu/arm-smmu-qcom: Register the TBU driver in qcom_smmu_impl_init") intended to also probe the TBU driver when CONFIG_ARM_SMMU_QCOM_DEBUG is disabled, but also moved the corresponding platform_driver_register() call into qcom_smmu_impl_init() which is called from arm_smmu_device_probe(). However, it neither makes sense to register drivers from probe() callbacks of other drivers, nor does the driver core allow registering drivers with a device lock already being held. The latter was revealed by commit dc23806a7c47 ("driver core: enforce device_lock for driver_match_device()") leading to a deadlock condition described in [1]. Additionally, it was noted by Robin that the current approach is potentially racy with async probe [2]. Hence, fix this by registering the qcom_smmu_tbu_driver from module_init(). Unfortunately, due to the vendoring of the driver, this requires an indirection through arm-smmu-impl.c. Reported-by: Mark Brown Closes: https://lore.kernel.org/lkml/7ae38e31-ef31-43ad-9106-7c76ea0e8596@sirena.org.uk/ Link: https://lore.kernel.org/lkml/DFU7CEPUSG9A.1KKGVW4HIPMSH@kernel.org/ [1] Link: https://lore.kernel.org/lkml/0c0d3707-9ea5-44f9-88a1-a65c62e3df8d@arm.com/ [2] Fixes: dc23806a7c47 ("driver core: enforce device_lock for driver_match_device()") Fixes: 0b4eeee2876f ("iommu/arm-smmu-qcom: Register the TBU driver in qcom_smmu_impl_init") Acked-by: Robin Murphy Tested-by: Bjorn Andersson Reviewed-by: Bjorn Andersson Acked-by: Konrad Dybcio Reviewed-by: Greg Kroah-Hartman Tested-by: Ioana Ciornei #LX2160ARDB Tested-by: Wang Jiayue Reviewed-by: Wang Jiayue Tested-by: Mark Brown Acked-by: Joerg Roedel Link: https://patch.msgid.link/20260121141215.29658-1-dakr@kernel.org Signed-off-by: Danilo Krummrich Signed-off-by: Greg Kroah-Hartman --- drivers/iommu/arm/arm-smmu/arm-smmu-impl.c | 14 +++++++++++++ drivers/iommu/arm/arm-smmu/arm-smmu-qcom.c | 14 +++++++++---- drivers/iommu/arm/arm-smmu/arm-smmu.c | 24 +++++++++++++++++++++- drivers/iommu/arm/arm-smmu/arm-smmu.h | 5 +++++ 4 files changed, 52 insertions(+), 5 deletions(-) diff --git a/drivers/iommu/arm/arm-smmu/arm-smmu-impl.c b/drivers/iommu/arm/arm-smmu/arm-smmu-impl.c index db9b9a8e139c87..4565a58bb213f4 100644 --- a/drivers/iommu/arm/arm-smmu/arm-smmu-impl.c +++ b/drivers/iommu/arm/arm-smmu/arm-smmu-impl.c @@ -228,3 +228,17 @@ struct arm_smmu_device *arm_smmu_impl_init(struct arm_smmu_device *smmu) return smmu; } + +int __init arm_smmu_impl_module_init(void) +{ + if (IS_ENABLED(CONFIG_ARM_SMMU_QCOM)) + return qcom_smmu_module_init(); + + return 0; +} + +void __exit arm_smmu_impl_module_exit(void) +{ + if (IS_ENABLED(CONFIG_ARM_SMMU_QCOM)) + qcom_smmu_module_exit(); +} diff --git a/drivers/iommu/arm/arm-smmu/arm-smmu-qcom.c b/drivers/iommu/arm/arm-smmu/arm-smmu-qcom.c index c939d0856b719c..2c442aa2181571 100644 --- a/drivers/iommu/arm/arm-smmu/arm-smmu-qcom.c +++ b/drivers/iommu/arm/arm-smmu/arm-smmu-qcom.c @@ -773,10 +773,6 @@ struct arm_smmu_device *qcom_smmu_impl_init(struct arm_smmu_device *smmu) { const struct device_node *np = smmu->dev->of_node; const struct of_device_id *match; - static u8 tbu_registered; - - if (!tbu_registered++) - platform_driver_register(&qcom_smmu_tbu_driver); #ifdef CONFIG_ACPI if (np == NULL) { @@ -801,3 +797,13 @@ struct arm_smmu_device *qcom_smmu_impl_init(struct arm_smmu_device *smmu) return smmu; } + +int __init qcom_smmu_module_init(void) +{ + return platform_driver_register(&qcom_smmu_tbu_driver); +} + +void __exit qcom_smmu_module_exit(void) +{ + platform_driver_unregister(&qcom_smmu_tbu_driver); +} diff --git a/drivers/iommu/arm/arm-smmu/arm-smmu.c b/drivers/iommu/arm/arm-smmu/arm-smmu.c index 4ced4b5bee4df3..488632b8eeab61 100644 --- a/drivers/iommu/arm/arm-smmu/arm-smmu.c +++ b/drivers/iommu/arm/arm-smmu/arm-smmu.c @@ -2362,7 +2362,29 @@ static struct platform_driver arm_smmu_driver = { .remove = arm_smmu_device_remove, .shutdown = arm_smmu_device_shutdown, }; -module_platform_driver(arm_smmu_driver); + +static int __init arm_smmu_init(void) +{ + int ret; + + ret = platform_driver_register(&arm_smmu_driver); + if (ret) + return ret; + + ret = arm_smmu_impl_module_init(); + if (ret) + platform_driver_unregister(&arm_smmu_driver); + + return ret; +} +module_init(arm_smmu_init); + +static void __exit arm_smmu_exit(void) +{ + arm_smmu_impl_module_exit(); + platform_driver_unregister(&arm_smmu_driver); +} +module_exit(arm_smmu_exit); MODULE_DESCRIPTION("IOMMU API for ARM architected SMMU implementations"); MODULE_AUTHOR("Will Deacon "); diff --git a/drivers/iommu/arm/arm-smmu/arm-smmu.h b/drivers/iommu/arm/arm-smmu/arm-smmu.h index 2dbf3243b5ad2d..26d2e33cd328b8 100644 --- a/drivers/iommu/arm/arm-smmu/arm-smmu.h +++ b/drivers/iommu/arm/arm-smmu/arm-smmu.h @@ -540,6 +540,11 @@ struct arm_smmu_device *arm_smmu_impl_init(struct arm_smmu_device *smmu); struct arm_smmu_device *nvidia_smmu_impl_init(struct arm_smmu_device *smmu); struct arm_smmu_device *qcom_smmu_impl_init(struct arm_smmu_device *smmu); +int __init arm_smmu_impl_module_init(void); +void __exit arm_smmu_impl_module_exit(void); +int __init qcom_smmu_module_init(void); +void __exit qcom_smmu_module_exit(void); + void arm_smmu_write_context_bank(struct arm_smmu_device *smmu, int idx); int arm_mmu500_reset(struct arm_smmu_device *smmu); From 715a7a72e47361548a377996effb8f4ed869e84e Mon Sep 17 00:00:00 2001 From: Fabio Porcedda Date: Fri, 23 Jan 2026 16:19:16 +0100 Subject: [PATCH 3248/3902] USB: serial: option: add Telit FN920C04 RNDIS compositions commit 509f403f3ccec14188036212118651bf23599396 upstream. Add the following compositions: 0x10a1: RNDIS + tty (AT/NMEA) + tty (AT) + tty (diag) T: Bus=01 Lev=01 Prnt=01 Port=00 Cnt=01 Dev#= 9 Spd=480 MxCh= 0 D: Ver= 2.00 Cls=00(>ifc ) Sub=00 Prot=00 MxPS=64 #Cfgs= 1 P: Vendor=1bc7 ProdID=10a1 Rev=05.15 S: Manufacturer=Telit Cinterion S: Product=FN920 S: SerialNumber=d128dba9 C: #Ifs= 5 Cfg#= 1 Atr=e0 MxPwr=500mA I: If#= 0 Alt= 0 #EPs= 1 Cls=ef(misc ) Sub=04 Prot=01 Driver=rndis_host E: Ad=82(I) Atr=03(Int.) MxPS= 8 Ivl=32ms I: If#= 1 Alt= 0 #EPs= 2 Cls=0a(data ) Sub=00 Prot=00 Driver=rndis_host E: Ad=01(O) Atr=02(Bulk) MxPS= 512 Ivl=0ms E: Ad=81(I) Atr=02(Bulk) MxPS= 512 Ivl=0ms I: If#= 2 Alt= 0 #EPs= 3 Cls=ff(vend.) Sub=ff Prot=60 Driver=option E: Ad=02(O) Atr=02(Bulk) MxPS= 512 Ivl=0ms E: Ad=83(I) Atr=02(Bulk) MxPS= 512 Ivl=0ms E: Ad=84(I) Atr=03(Int.) MxPS= 10 Ivl=32ms I: If#= 3 Alt= 0 #EPs= 3 Cls=ff(vend.) Sub=ff Prot=40 Driver=option E: Ad=03(O) Atr=02(Bulk) MxPS= 512 Ivl=0ms E: Ad=85(I) Atr=02(Bulk) MxPS= 512 Ivl=0ms E: Ad=86(I) Atr=03(Int.) MxPS= 10 Ivl=32ms I: If#= 4 Alt= 0 #EPs= 2 Cls=ff(vend.) Sub=ff Prot=30 Driver=option E: Ad=04(O) Atr=02(Bulk) MxPS= 512 Ivl=0ms E: Ad=87(I) Atr=02(Bulk) MxPS= 512 Ivl=0ms 0x10a6: RNDIS + tty (AT/NMEA) + tty (AT) + tty (diag) T: Bus=01 Lev=01 Prnt=01 Port=00 Cnt=01 Dev#= 10 Spd=480 MxCh= 0 D: Ver= 2.00 Cls=00(>ifc ) Sub=00 Prot=00 MxPS=64 #Cfgs= 1 P: Vendor=1bc7 ProdID=10a6 Rev=05.15 S: Manufacturer=Telit Cinterion S: Product=FN920 S: SerialNumber=d128dba9 C: #Ifs= 5 Cfg#= 1 Atr=e0 MxPwr=500mA I: If#= 0 Alt= 0 #EPs= 1 Cls=ef(misc ) Sub=04 Prot=01 Driver=rndis_host E: Ad=82(I) Atr=03(Int.) MxPS= 8 Ivl=32ms I: If#= 1 Alt= 0 #EPs= 2 Cls=0a(data ) Sub=00 Prot=00 Driver=rndis_host E: Ad=01(O) Atr=02(Bulk) MxPS= 512 Ivl=0ms E: Ad=81(I) Atr=02(Bulk) MxPS= 512 Ivl=0ms I: If#= 2 Alt= 0 #EPs= 3 Cls=ff(vend.) Sub=ff Prot=40 Driver=option E: Ad=02(O) Atr=02(Bulk) MxPS= 512 Ivl=0ms E: Ad=83(I) Atr=02(Bulk) MxPS= 512 Ivl=0ms E: Ad=84(I) Atr=03(Int.) MxPS= 10 Ivl=32ms I: If#= 3 Alt= 0 #EPs= 3 Cls=ff(vend.) Sub=ff Prot=40 Driver=option E: Ad=03(O) Atr=02(Bulk) MxPS= 512 Ivl=0ms E: Ad=85(I) Atr=02(Bulk) MxPS= 512 Ivl=0ms E: Ad=86(I) Atr=03(Int.) MxPS= 10 Ivl=32ms I: If#= 4 Alt= 0 #EPs= 2 Cls=ff(vend.) Sub=ff Prot=30 Driver=option E: Ad=04(O) Atr=02(Bulk) MxPS= 512 Ivl=0ms E: Ad=87(I) Atr=02(Bulk) MxPS= 512 Ivl=0ms 0x10ab: RNDIS + tty (AT) + tty (diag) + DPL (Data Packet Logging) + adb T: Bus=01 Lev=01 Prnt=01 Port=00 Cnt=01 Dev#= 11 Spd=480 MxCh= 0 D: Ver= 2.00 Cls=00(>ifc ) Sub=00 Prot=00 MxPS=64 #Cfgs= 1 P: Vendor=1bc7 ProdID=10ab Rev=05.15 S: Manufacturer=Telit Cinterion S: Product=FN920 S: SerialNumber=d128dba9 C: #Ifs= 6 Cfg#= 1 Atr=e0 MxPwr=500mA I: If#= 0 Alt= 0 #EPs= 1 Cls=ef(misc ) Sub=04 Prot=01 Driver=rndis_host E: Ad=82(I) Atr=03(Int.) MxPS= 8 Ivl=32ms I: If#= 1 Alt= 0 #EPs= 2 Cls=0a(data ) Sub=00 Prot=00 Driver=rndis_host E: Ad=01(O) Atr=02(Bulk) MxPS= 512 Ivl=0ms E: Ad=81(I) Atr=02(Bulk) MxPS= 512 Ivl=0ms I: If#= 2 Alt= 0 #EPs= 3 Cls=ff(vend.) Sub=ff Prot=40 Driver=option E: Ad=02(O) Atr=02(Bulk) MxPS= 512 Ivl=0ms E: Ad=83(I) Atr=02(Bulk) MxPS= 512 Ivl=0ms E: Ad=84(I) Atr=03(Int.) MxPS= 10 Ivl=32ms I: If#= 3 Alt= 0 #EPs= 2 Cls=ff(vend.) Sub=ff Prot=30 Driver=option E: Ad=03(O) Atr=02(Bulk) MxPS= 512 Ivl=0ms E: Ad=85(I) Atr=02(Bulk) MxPS= 512 Ivl=0ms I: If#= 4 Alt= 0 #EPs= 1 Cls=ff(vend.) Sub=ff Prot=80 Driver=(none) E: Ad=86(I) Atr=02(Bulk) MxPS= 512 Ivl=0ms I: If#= 5 Alt= 0 #EPs= 2 Cls=ff(vend.) Sub=42 Prot=01 Driver=(none) E: Ad=04(O) Atr=02(Bulk) MxPS= 512 Ivl=0ms E: Ad=87(I) Atr=02(Bulk) MxPS= 512 Ivl=0ms Cc: stable@vger.kernel.org Signed-off-by: Fabio Porcedda Signed-off-by: Johan Hovold Signed-off-by: Greg Kroah-Hartman --- drivers/usb/serial/option.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/drivers/usb/serial/option.c b/drivers/usb/serial/option.c index 9f2cc5fb9f4562..d4505a4264460b 100644 --- a/drivers/usb/serial/option.c +++ b/drivers/usb/serial/option.c @@ -1401,12 +1401,16 @@ static const struct usb_device_id option_ids[] = { .driver_info = NCTRL(0) | RSVD(1) }, { USB_DEVICE_INTERFACE_CLASS(TELIT_VENDOR_ID, 0x10a0, 0xff), /* Telit FN20C04 (rmnet) */ .driver_info = RSVD(0) | NCTRL(3) }, + { USB_DEVICE_INTERFACE_CLASS(TELIT_VENDOR_ID, 0x10a1, 0xff), /* Telit FN20C04 (RNDIS) */ + .driver_info = NCTRL(4) }, { USB_DEVICE_INTERFACE_CLASS(TELIT_VENDOR_ID, 0x10a2, 0xff), /* Telit FN920C04 (MBIM) */ .driver_info = NCTRL(4) }, { USB_DEVICE_INTERFACE_CLASS(TELIT_VENDOR_ID, 0x10a3, 0xff), /* Telit FN920C04 (ECM) */ .driver_info = NCTRL(4) }, { USB_DEVICE_INTERFACE_CLASS(TELIT_VENDOR_ID, 0x10a4, 0xff), /* Telit FN20C04 (rmnet) */ .driver_info = RSVD(0) | NCTRL(3) }, + { USB_DEVICE_INTERFACE_CLASS(TELIT_VENDOR_ID, 0x10a6, 0xff), /* Telit FN920C04 (RNDIS) */ + .driver_info = NCTRL(4) }, { USB_DEVICE_INTERFACE_CLASS(TELIT_VENDOR_ID, 0x10a7, 0xff), /* Telit FN920C04 (MBIM) */ .driver_info = NCTRL(4) }, { USB_DEVICE_INTERFACE_CLASS(TELIT_VENDOR_ID, 0x10a8, 0xff), /* Telit FN920C04 (ECM) */ @@ -1415,6 +1419,8 @@ static const struct usb_device_id option_ids[] = { .driver_info = RSVD(0) | NCTRL(2) | RSVD(3) | RSVD(4) }, { USB_DEVICE_INTERFACE_CLASS(TELIT_VENDOR_ID, 0x10aa, 0xff), /* Telit FN920C04 (MBIM) */ .driver_info = NCTRL(3) | RSVD(4) | RSVD(5) }, + { USB_DEVICE_INTERFACE_CLASS(TELIT_VENDOR_ID, 0x10ab, 0xff), /* Telit FN920C04 (RNDIS) */ + .driver_info = NCTRL(3) | RSVD(4) | RSVD(5) }, { USB_DEVICE_AND_INTERFACE_INFO(TELIT_VENDOR_ID, 0x10b0, 0xff, 0xff, 0x30), /* Telit FE990B (rmnet) */ .driver_info = NCTRL(5) }, { USB_DEVICE_AND_INTERFACE_INFO(TELIT_VENDOR_ID, 0x10b0, 0xff, 0xff, 0x40) }, From cd2fec912a0f04390d446ed692f73f2da842855f Mon Sep 17 00:00:00 2001 From: Chao Yu Date: Tue, 17 Feb 2026 12:42:37 -0500 Subject: [PATCH 3249/3902] f2fs: fix to do sanity check on node footer in __write_node_folio() [ Upstream commit 0a736109c9d29de0c26567e42cb99b27861aa8ba ] Add node footer sanity check during node folio's writeback, if sanity check fails, let's shutdown filesystem to avoid looping to redirty and writeback in .writepages. Signed-off-by: Chao Yu Signed-off-by: Jaegeuk Kim Signed-off-by: Sasha Levin Signed-off-by: Greg Kroah-Hartman --- fs/f2fs/node.c | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/fs/f2fs/node.c b/fs/f2fs/node.c index 3dacc90ee9a6cf..fc3110efb49848 100644 --- a/fs/f2fs/node.c +++ b/fs/f2fs/node.c @@ -1751,7 +1751,11 @@ static bool __write_node_folio(struct folio *folio, bool atomic, bool *submitted /* get old block addr of this node page */ nid = nid_of_node(folio); - f2fs_bug_on(sbi, folio->index != nid); + + if (sanity_check_node_footer(sbi, folio, nid, NODE_TYPE_REGULAR)) { + f2fs_handle_critical_error(sbi, STOP_CP_REASON_CORRUPTED_NID); + goto redirty_out; + } if (f2fs_get_node_info(sbi, nid, &ni, !do_balance)) goto redirty_out; From 855c54f1803e3ebc613677b4f389c7f92656a1fc Mon Sep 17 00:00:00 2001 From: Chao Yu Date: Tue, 17 Feb 2026 12:42:38 -0500 Subject: [PATCH 3250/3902] f2fs: fix to do sanity check on node footer in {read,write}_end_io [ Upstream commit 50ac3ecd8e05b6bcc350c71a4307d40c030ec7e4 ] -----------[ cut here ]------------ kernel BUG at fs/f2fs/data.c:358! Call Trace: blk_update_request+0x5eb/0xe70 block/blk-mq.c:987 blk_mq_end_request+0x3e/0x70 block/blk-mq.c:1149 blk_complete_reqs block/blk-mq.c:1224 [inline] blk_done_softirq+0x107/0x160 block/blk-mq.c:1229 handle_softirqs+0x283/0x870 kernel/softirq.c:579 __do_softirq kernel/softirq.c:613 [inline] invoke_softirq kernel/softirq.c:453 [inline] __irq_exit_rcu+0xca/0x1f0 kernel/softirq.c:680 irq_exit_rcu+0x9/0x30 kernel/softirq.c:696 instr_sysvec_apic_timer_interrupt arch/x86/kernel/apic/apic.c:1050 [inline] sysvec_apic_timer_interrupt+0xa6/0xc0 arch/x86/kernel/apic/apic.c:1050 In f2fs_write_end_io(), it detects there is inconsistency in between node page index (nid) and footer.nid of node page. If footer of node page is corrupted in fuzzed image, then we load corrupted node page w/ async method, e.g. f2fs_ra_node_pages() or f2fs_ra_node_page(), in where we won't do sanity check on node footer, once node page becomes dirty, we will encounter this bug after node page writeback. Cc: stable@kernel.org Reported-by: syzbot+803dd716c4310d16ff3a@syzkaller.appspotmail.com Closes: https://syzkaller.appspot.com/bug?extid=803dd716c4310d16ff3a Signed-off-by: Chao Yu Signed-off-by: Jaegeuk Kim [ Context ] Signed-off-by: Sasha Levin Signed-off-by: Greg Kroah-Hartman --- fs/f2fs/data.c | 13 +++++++++++-- fs/f2fs/f2fs.h | 12 ++++++++++++ fs/f2fs/node.c | 20 +++++++++++--------- fs/f2fs/node.h | 8 -------- 4 files changed, 34 insertions(+), 19 deletions(-) diff --git a/fs/f2fs/data.c b/fs/f2fs/data.c index debdc6ae6eb9d8..3ac0ecbf3ced38 100644 --- a/fs/f2fs/data.c +++ b/fs/f2fs/data.c @@ -151,6 +151,12 @@ static void f2fs_finish_read_bio(struct bio *bio, bool in_task) } dec_page_count(F2FS_F_SB(folio), __read_io_type(folio)); + + if (F2FS_F_SB(folio)->node_inode && is_node_folio(folio) && + f2fs_sanity_check_node_footer(F2FS_F_SB(folio), + folio, folio->index, NODE_TYPE_REGULAR, true)) + bio->bi_status = BLK_STS_IOERR; + folio_end_read(folio, bio->bi_status == BLK_STS_OK); } @@ -352,8 +358,11 @@ static void f2fs_write_end_io(struct bio *bio) STOP_CP_REASON_WRITE_FAIL); } - f2fs_bug_on(sbi, is_node_folio(folio) && - folio->index != nid_of_node(folio)); + if (is_node_folio(folio)) { + f2fs_sanity_check_node_footer(sbi, folio, + folio->index, NODE_TYPE_REGULAR, true); + f2fs_bug_on(sbi, folio->index != nid_of_node(folio)); + } dec_page_count(sbi, type); diff --git a/fs/f2fs/f2fs.h b/fs/f2fs/f2fs.h index fc55d6dde3e23b..123c50f6619ae4 100644 --- a/fs/f2fs/f2fs.h +++ b/fs/f2fs/f2fs.h @@ -1516,6 +1516,15 @@ enum f2fs_lookup_mode { LOOKUP_AUTO, }; +/* For node type in __get_node_folio() */ +enum node_type { + NODE_TYPE_REGULAR, + NODE_TYPE_INODE, + NODE_TYPE_XATTR, + NODE_TYPE_NON_INODE, +}; + + static inline int f2fs_test_bit(unsigned int nr, char *addr); static inline void f2fs_set_bit(unsigned int nr, char *addr); static inline void f2fs_clear_bit(unsigned int nr, char *addr); @@ -3874,6 +3883,9 @@ struct folio *f2fs_new_node_folio(struct dnode_of_data *dn, unsigned int ofs); void f2fs_ra_node_page(struct f2fs_sb_info *sbi, nid_t nid); struct folio *f2fs_get_node_folio(struct f2fs_sb_info *sbi, pgoff_t nid, enum node_type node_type); +int f2fs_sanity_check_node_footer(struct f2fs_sb_info *sbi, + struct folio *folio, pgoff_t nid, + enum node_type ntype, bool in_irq); struct folio *f2fs_get_inode_folio(struct f2fs_sb_info *sbi, pgoff_t ino); struct folio *f2fs_get_xnode_folio(struct f2fs_sb_info *sbi, pgoff_t xnid); int f2fs_move_node_folio(struct folio *node_folio, int gc_type); diff --git a/fs/f2fs/node.c b/fs/f2fs/node.c index fc3110efb49848..591fcdf3ba77b9 100644 --- a/fs/f2fs/node.c +++ b/fs/f2fs/node.c @@ -1500,9 +1500,9 @@ void f2fs_ra_node_page(struct f2fs_sb_info *sbi, nid_t nid) f2fs_folio_put(afolio, err ? true : false); } -static int sanity_check_node_footer(struct f2fs_sb_info *sbi, +int f2fs_sanity_check_node_footer(struct f2fs_sb_info *sbi, struct folio *folio, pgoff_t nid, - enum node_type ntype) + enum node_type ntype, bool in_irq) { if (unlikely(nid != nid_of_node(folio))) goto out_err; @@ -1527,12 +1527,13 @@ static int sanity_check_node_footer(struct f2fs_sb_info *sbi, goto out_err; return 0; out_err: - f2fs_warn(sbi, "inconsistent node block, node_type:%d, nid:%lu, " - "node_footer[nid:%u,ino:%u,ofs:%u,cpver:%llu,blkaddr:%u]", - ntype, nid, nid_of_node(folio), ino_of_node(folio), - ofs_of_node(folio), cpver_of_node(folio), - next_blkaddr_of_node(folio)); set_sbi_flag(sbi, SBI_NEED_FSCK); + f2fs_warn_ratelimited(sbi, "inconsistent node block, node_type:%d, nid:%lu, " + "node_footer[nid:%u,ino:%u,ofs:%u,cpver:%llu,blkaddr:%u]", + ntype, nid, nid_of_node(folio), ino_of_node(folio), + ofs_of_node(folio), cpver_of_node(folio), + next_blkaddr_of_node(folio)); + f2fs_handle_error(sbi, ERROR_INCONSISTENT_FOOTER); return -EFSCORRUPTED; } @@ -1578,7 +1579,7 @@ static struct folio *__get_node_folio(struct f2fs_sb_info *sbi, pgoff_t nid, goto out_err; } page_hit: - err = sanity_check_node_footer(sbi, folio, nid, ntype); + err = f2fs_sanity_check_node_footer(sbi, folio, nid, ntype, false); if (!err) return folio; out_err: @@ -1752,7 +1753,8 @@ static bool __write_node_folio(struct folio *folio, bool atomic, bool *submitted /* get old block addr of this node page */ nid = nid_of_node(folio); - if (sanity_check_node_footer(sbi, folio, nid, NODE_TYPE_REGULAR)) { + if (f2fs_sanity_check_node_footer(sbi, folio, nid, + NODE_TYPE_REGULAR, false)) { f2fs_handle_critical_error(sbi, STOP_CP_REASON_CORRUPTED_NID); goto redirty_out; } diff --git a/fs/f2fs/node.h b/fs/f2fs/node.h index 9cb8dcf8d41760..824ac9f0e6e42a 100644 --- a/fs/f2fs/node.h +++ b/fs/f2fs/node.h @@ -52,14 +52,6 @@ enum { IS_PREALLOC, /* nat entry is preallocated */ }; -/* For node type in __get_node_folio() */ -enum node_type { - NODE_TYPE_REGULAR, - NODE_TYPE_INODE, - NODE_TYPE_XATTR, - NODE_TYPE_NON_INODE, -}; - /* * For node information */ From df337ba02ca53ca9cfcefc1ed7ca38424daa52a0 Mon Sep 17 00:00:00 2001 From: Daeho Jeong Date: Mon, 26 Jan 2026 14:28:01 -0800 Subject: [PATCH 3251/3902] f2fs: fix incomplete block usage in compact SSA summaries commit 91b76f1059b60f453b51877f29f0e35693737383 upstream. In a previous commit, a bug was introduced where compact SSA summaries failed to utilize the entire block space in non-4KB block size configurations, leading to inefficient space management. This patch fixes the calculation logic to ensure that compact SSA summaries can fully occupy the block regardless of the block size. Reported-by: Chris Mason Fixes: e48e16f3e37f ("f2fs: support non-4KB block size without packed_ssa feature") Signed-off-by: Daeho Jeong Reviewed-by: Chao Yu Signed-off-by: Jaegeuk Kim Cc: Jiri Slaby Signed-off-by: Greg Kroah-Hartman --- fs/f2fs/segment.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/fs/f2fs/segment.c b/fs/f2fs/segment.c index f1a6720160633d..23b94a8fd843de 100644 --- a/fs/f2fs/segment.c +++ b/fs/f2fs/segment.c @@ -2689,12 +2689,12 @@ int f2fs_npages_for_summary_flush(struct f2fs_sb_info *sbi, bool for_ra) valid_sum_count += f2fs_curseg_valid_blocks(sbi, i); } - sum_in_page = (sbi->sum_blocksize - 2 * sbi->sum_journal_size - + sum_in_page = (sbi->blocksize - 2 * sbi->sum_journal_size - SUM_FOOTER_SIZE) / SUMMARY_SIZE; if (valid_sum_count <= sum_in_page) return 1; else if ((valid_sum_count - sum_in_page) <= - (sbi->sum_blocksize - SUM_FOOTER_SIZE) / SUMMARY_SIZE) + (sbi->blocksize - SUM_FOOTER_SIZE) / SUMMARY_SIZE) return 2; return 3; } @@ -4337,7 +4337,7 @@ static int read_compacted_summaries(struct f2fs_sb_info *sbi) s = (struct f2fs_summary *)(kaddr + offset); sum_entries(seg_i->sum_blk)[j] = *s; offset += SUMMARY_SIZE; - if (offset + SUMMARY_SIZE <= sbi->sum_blocksize - + if (offset + SUMMARY_SIZE <= sbi->blocksize - SUM_FOOTER_SIZE) continue; @@ -4510,7 +4510,7 @@ static void write_compacted_summaries(struct f2fs_sb_info *sbi, block_t blkaddr) *summary = sum_entries(seg_i->sum_blk)[j]; written_size += SUMMARY_SIZE; - if (written_size + SUMMARY_SIZE <= PAGE_SIZE - + if (written_size + SUMMARY_SIZE <= sbi->blocksize - SUM_FOOTER_SIZE) continue; From 25e0b1c206e3def1bd3bf9dcba980c5138c637a9 Mon Sep 17 00:00:00 2001 From: Greg Kroah-Hartman Date: Thu, 19 Feb 2026 16:31:37 +0100 Subject: [PATCH 3252/3902] Linux 6.18.13 Link: https://lore.kernel.org/r/20260217200006.470920131@linuxfoundation.org Tested-by: Florian Fainelli Tested-by: Peter Schneider Tested-by: Jon Hunter Tested-by: Salvatore Bonaccorso Tested-by: Brett A C Sheffield Tested-by: Luna Jernberg Tested-by: Jeffrin Jose T Tested-by: Mark Brown Tested-by: Justin M. Forbes Tested-by: Ron Economos Tested-by: Miguel Ojeda Signed-off-by: Greg Kroah-Hartman --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 09153bd3bc5d5e..c4b22ec262784b 100644 --- a/Makefile +++ b/Makefile @@ -1,7 +1,7 @@ # SPDX-License-Identifier: GPL-2.0 VERSION = 6 PATCHLEVEL = 18 -SUBLEVEL = 12 +SUBLEVEL = 13 EXTRAVERSION = NAME = Baby Opossum Posse From 714c99e1dc8f85f446e05be02ba83972e981a817 Mon Sep 17 00:00:00 2001 From: YunJe Shin Date: Wed, 4 Feb 2026 18:24:57 +0900 Subject: [PATCH 3253/3902] RDMA/siw: Fix potential NULL pointer dereference in header processing commit 14ab3da122bd18920ad57428f6cf4fade8385142 upstream. If siw_get_hdr() returns -EINVAL before set_rx_fpdu_context(), qp->rx_fpdu can be NULL. The error path in siw_tcp_rx_data() dereferences qp->rx_fpdu->more_ddp_segs without checking, which may lead to a NULL pointer deref. Only check more_ddp_segs when rx_fpdu is present. KASAN splat: [ 101.384271] KASAN: null-ptr-deref in range [0x00000000000000c0-0x00000000000000c7] [ 101.385869] RIP: 0010:siw_tcp_rx_data+0x13ad/0x1e50 Fixes: 8b6a361b8c48 ("rdma/siw: receive path") Signed-off-by: YunJe Shin Link: https://patch.msgid.link/20260204092546.489842-1-ioerts@kookmin.ac.kr Acked-by: Bernard Metzler Signed-off-by: Leon Romanovsky Signed-off-by: Greg Kroah-Hartman --- drivers/infiniband/sw/siw/siw_qp_rx.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/drivers/infiniband/sw/siw/siw_qp_rx.c b/drivers/infiniband/sw/siw/siw_qp_rx.c index a10820e3388782..e8a88b378d51d4 100644 --- a/drivers/infiniband/sw/siw/siw_qp_rx.c +++ b/drivers/infiniband/sw/siw/siw_qp_rx.c @@ -1435,7 +1435,8 @@ int siw_tcp_rx_data(read_descriptor_t *rd_desc, struct sk_buff *skb, } if (unlikely(rv != 0 && rv != -EAGAIN)) { if ((srx->state > SIW_GET_HDR || - qp->rx_fpdu->more_ddp_segs) && run_completion) + (qp->rx_fpdu && qp->rx_fpdu->more_ddp_segs)) && + run_completion) siw_rdmap_complete(qp, rv); siw_dbg_qp(qp, "rx error %d, rx state %d\n", rv, From 205955f29c26330b1dc7fdeadd5bb97c38e26f56 Mon Sep 17 00:00:00 2001 From: YunJe Shin Date: Tue, 3 Feb 2026 19:06:21 +0900 Subject: [PATCH 3254/3902] RDMA/umad: Reject negative data_len in ib_umad_write commit 5551b02fdbfd85a325bb857f3a8f9c9f33397ed2 upstream. ib_umad_write computes data_len from user-controlled count and the MAD header sizes. With a mismatched user MAD header size and RMPP header length, data_len can become negative and reach ib_create_send_mad(). This can make the padding calculation exceed the segment size and trigger an out-of-bounds memset in alloc_send_rmpp_list(). Add an explicit check to reject negative data_len before creating the send buffer. KASAN splat: [ 211.363464] BUG: KASAN: slab-out-of-bounds in ib_create_send_mad+0xa01/0x11b0 [ 211.364077] Write of size 220 at addr ffff88800c3fa1f8 by task spray_thread/102 [ 211.365867] ib_create_send_mad+0xa01/0x11b0 [ 211.365887] ib_umad_write+0x853/0x1c80 Fixes: 2be8e3ee8efd ("IB/umad: Add P_Key index support") Signed-off-by: YunJe Shin Link: https://patch.msgid.link/20260203100628.1215408-1-ioerts@kookmin.ac.kr Signed-off-by: Leon Romanovsky Signed-off-by: Greg Kroah-Hartman --- drivers/infiniband/core/user_mad.c | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/drivers/infiniband/core/user_mad.c b/drivers/infiniband/core/user_mad.c index fd67fc9fe85a46..2f7e3c4483fc57 100644 --- a/drivers/infiniband/core/user_mad.c +++ b/drivers/infiniband/core/user_mad.c @@ -514,7 +514,8 @@ static ssize_t ib_umad_write(struct file *filp, const char __user *buf, struct rdma_ah_attr ah_attr; struct ib_ah *ah; __be64 *tid; - int ret, data_len, hdr_len, copy_offset, rmpp_active; + int ret, hdr_len, copy_offset, rmpp_active; + size_t data_len; u8 base_version; if (count < hdr_size(file) + IB_MGMT_RMPP_HDR) @@ -588,7 +589,10 @@ static ssize_t ib_umad_write(struct file *filp, const char __user *buf, } base_version = ((struct ib_mad_hdr *)&packet->mad.data)->base_version; - data_len = count - hdr_size(file) - hdr_len; + if (check_sub_overflow(count, hdr_size(file) + hdr_len, &data_len)) { + ret = -EINVAL; + goto err_ah; + } packet->msg = ib_create_send_mad(agent, be32_to_cpu(packet->mad.hdr.qpn), packet->mad.hdr.pkey_index, rmpp_active, From 91f08ec6a97b28694a679be7489d2c96a2034b1b Mon Sep 17 00:00:00 2001 From: Thomas Fourier Date: Tue, 16 Dec 2025 18:47:13 +0100 Subject: [PATCH 3255/3902] auxdisplay: arm-charlcd: fix release_mem_region() size [ Upstream commit b5c23a4d291d2ac1dfdd574a68a3a68c8da3069e ] It seems like, after the request_mem_region(), the corresponding release_mem_region() must take the same size. This was done in (now removed due to previous refactoring) charlcd_remove() but not in the error path in charlcd_probe(). Fixes: ce8962455e90 ("ARM: 6214/2: driver for the character LCD found in ARM refdesigns") Signed-off-by: Thomas Fourier Reviewed-by: Geert Uytterhoeven Signed-off-by: Andy Shevchenko Signed-off-by: Sasha Levin --- drivers/auxdisplay/arm-charlcd.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/auxdisplay/arm-charlcd.c b/drivers/auxdisplay/arm-charlcd.c index a7eae99a48f77d..4e22882f57c9c2 100644 --- a/drivers/auxdisplay/arm-charlcd.c +++ b/drivers/auxdisplay/arm-charlcd.c @@ -323,7 +323,7 @@ static int __init charlcd_probe(struct platform_device *pdev) out_no_irq: iounmap(lcd->virtbase); out_no_memregion: - release_mem_region(lcd->phybase, SZ_4K); + release_mem_region(lcd->phybase, lcd->physize); out_no_resource: kfree(lcd); return ret; From 2e6ff6a6fc69cc17ed10c9cb6242935d52acd52d Mon Sep 17 00:00:00 2001 From: Shardul Bankar Date: Tue, 30 Dec 2025 02:19:38 +0530 Subject: [PATCH 3256/3902] hfsplus: return error when node already exists in hfs_bnode_create [ Upstream commit d8a73cc46c8462a969a7516131feb3096f4c49d3 ] When hfs_bnode_create() finds that a node is already hashed (which should not happen in normal operation), it currently returns the existing node without incrementing its reference count. This causes a reference count inconsistency that leads to a kernel panic when the node is later freed in hfs_bnode_put(): kernel BUG at fs/hfsplus/bnode.c:676! BUG_ON(!atomic_read(&node->refcnt)) This scenario can occur when hfs_bmap_alloc() attempts to allocate a node that is already in use (e.g., when node 0's bitmap bit is incorrectly unset), or due to filesystem corruption. Returning an existing node from a create path is not normal operation. Fix this by returning ERR_PTR(-EEXIST) instead of the node when it's already hashed. This properly signals the error condition to callers, which already check for IS_ERR() return values. Reported-by: syzbot+1c8ff72d0cd8a50dfeaa@syzkaller.appspotmail.com Link: https://syzkaller.appspot.com/bug?extid=1c8ff72d0cd8a50dfeaa Link: https://lore.kernel.org/all/784415834694f39902088fa8946850fc1779a318.camel@ibm.com/ Fixes: 634725a92938 ("[PATCH] hfs: cleanup HFS+ prints") Signed-off-by: Shardul Bankar Reviewed-by: Viacheslav Dubeyko Signed-off-by: Viacheslav Dubeyko Link: https://lore.kernel.org/r/20251229204938.1907089-1-shardul.b@mpiricsoftware.com Signed-off-by: Viacheslav Dubeyko Signed-off-by: Sasha Levin --- fs/hfsplus/bnode.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fs/hfsplus/bnode.c b/fs/hfsplus/bnode.c index 482a6c5faa1975..8e60e04c427bd2 100644 --- a/fs/hfsplus/bnode.c +++ b/fs/hfsplus/bnode.c @@ -629,7 +629,7 @@ struct hfs_bnode *hfs_bnode_create(struct hfs_btree *tree, u32 num) if (node) { pr_crit("new node %u already hashed?\n", num); WARN_ON(1); - return node; + return ERR_PTR(-EEXIST); } node = __hfs_bnode_create(tree, num); if (!node) From 4a4a6e12c9c829be3f74b7206fa8640fc4e1c566 Mon Sep 17 00:00:00 2001 From: Yao Kai Date: Thu, 1 Jan 2026 11:34:10 -0500 Subject: [PATCH 3257/3902] rcu: Fix rcu_read_unlock() deadloop due to softirq [ Upstream commit d41e37f26b3157b3f1d10223863519a943aa239b ] Commit 5f5fa7ea89dc ("rcu: Don't use negative nesting depth in __rcu_read_unlock()") removes the recursion-protection code from __rcu_read_unlock(). Therefore, we could invoke the deadloop in raise_softirq_irqoff() with ftrace enabled as follows: WARNING: CPU: 0 PID: 0 at kernel/trace/trace.c:3021 __ftrace_trace_stack.constprop.0+0x172/0x180 Modules linked in: my_irq_work(O) CPU: 0 UID: 0 PID: 0 Comm: swapper/0 Tainted: G O 6.18.0-rc7-dirty #23 PREEMPT(full) Tainted: [O]=OOT_MODULE Hardware name: QEMU Standard PC (i440FX + PIIX, 1996), BIOS 1.15.0-1 04/01/2014 RIP: 0010:__ftrace_trace_stack.constprop.0+0x172/0x180 RSP: 0018:ffffc900000034a8 EFLAGS: 00010002 RAX: 0000000000000000 RBX: 0000000000000004 RCX: 0000000000000000 RDX: 0000000000000003 RSI: ffffffff826d7b87 RDI: ffffffff826e9329 RBP: 0000000000090009 R08: 0000000000000005 R09: ffffffff82afbc4c R10: 0000000000000008 R11: 0000000000011d7a R12: 0000000000000000 R13: ffff888003874100 R14: 0000000000000003 R15: ffff8880038c1054 FS: 0000000000000000(0000) GS:ffff8880fa8ea000(0000) knlGS:0000000000000000 CS: 0010 DS: 0000 ES: 0000 CR0: 0000000080050033 CR2: 000055b31fa7f540 CR3: 00000000078f4005 CR4: 0000000000770ef0 PKRU: 55555554 Call Trace: trace_buffer_unlock_commit_regs+0x6d/0x220 trace_event_buffer_commit+0x5c/0x260 trace_event_raw_event_softirq+0x47/0x80 raise_softirq_irqoff+0x6e/0xa0 rcu_read_unlock_special+0xb1/0x160 unwind_next_frame+0x203/0x9b0 __unwind_start+0x15d/0x1c0 arch_stack_walk+0x62/0xf0 stack_trace_save+0x48/0x70 __ftrace_trace_stack.constprop.0+0x144/0x180 trace_buffer_unlock_commit_regs+0x6d/0x220 trace_event_buffer_commit+0x5c/0x260 trace_event_raw_event_softirq+0x47/0x80 raise_softirq_irqoff+0x6e/0xa0 rcu_read_unlock_special+0xb1/0x160 unwind_next_frame+0x203/0x9b0 __unwind_start+0x15d/0x1c0 arch_stack_walk+0x62/0xf0 stack_trace_save+0x48/0x70 __ftrace_trace_stack.constprop.0+0x144/0x180 trace_buffer_unlock_commit_regs+0x6d/0x220 trace_event_buffer_commit+0x5c/0x260 trace_event_raw_event_softirq+0x47/0x80 raise_softirq_irqoff+0x6e/0xa0 rcu_read_unlock_special+0xb1/0x160 unwind_next_frame+0x203/0x9b0 __unwind_start+0x15d/0x1c0 arch_stack_walk+0x62/0xf0 stack_trace_save+0x48/0x70 __ftrace_trace_stack.constprop.0+0x144/0x180 trace_buffer_unlock_commit_regs+0x6d/0x220 trace_event_buffer_commit+0x5c/0x260 trace_event_raw_event_softirq+0x47/0x80 raise_softirq_irqoff+0x6e/0xa0 rcu_read_unlock_special+0xb1/0x160 __is_insn_slot_addr+0x54/0x70 kernel_text_address+0x48/0xc0 __kernel_text_address+0xd/0x40 unwind_get_return_address+0x1e/0x40 arch_stack_walk+0x9c/0xf0 stack_trace_save+0x48/0x70 __ftrace_trace_stack.constprop.0+0x144/0x180 trace_buffer_unlock_commit_regs+0x6d/0x220 trace_event_buffer_commit+0x5c/0x260 trace_event_raw_event_softirq+0x47/0x80 __raise_softirq_irqoff+0x61/0x80 __flush_smp_call_function_queue+0x115/0x420 __sysvec_call_function_single+0x17/0xb0 sysvec_call_function_single+0x8c/0xc0 Commit b41642c87716 ("rcu: Fix rcu_read_unlock() deadloop due to IRQ work") fixed the infinite loop in rcu_read_unlock_special() for IRQ work by setting a flag before calling irq_work_queue_on(). We fix this issue by setting the same flag before calling raise_softirq_irqoff() and rename the flag to defer_qs_pending for more common. Fixes: 5f5fa7ea89dc ("rcu: Don't use negative nesting depth in __rcu_read_unlock()") Reported-by: Tengda Wu Signed-off-by: Yao Kai Reviewed-by: Joel Fernandes Tested-by: Paul E. McKenney Signed-off-by: Joel Fernandes Signed-off-by: Boqun Feng Signed-off-by: Sasha Levin --- kernel/rcu/tree.h | 2 +- kernel/rcu/tree_plugin.h | 15 +++++++++------ 2 files changed, 10 insertions(+), 7 deletions(-) diff --git a/kernel/rcu/tree.h b/kernel/rcu/tree.h index b8bbe7960cda7a..2265b9c2906e1e 100644 --- a/kernel/rcu/tree.h +++ b/kernel/rcu/tree.h @@ -203,7 +203,7 @@ struct rcu_data { /* during and after the last grace */ /* period it is aware of. */ struct irq_work defer_qs_iw; /* Obtain later scheduler attention. */ - int defer_qs_iw_pending; /* Scheduler attention pending? */ + int defer_qs_pending; /* irqwork or softirq pending? */ struct work_struct strict_work; /* Schedule readers for strict GPs. */ /* 2) batch handling */ diff --git a/kernel/rcu/tree_plugin.h b/kernel/rcu/tree_plugin.h index d85763336b3c0f..cafb1cc8eff846 100644 --- a/kernel/rcu/tree_plugin.h +++ b/kernel/rcu/tree_plugin.h @@ -487,8 +487,8 @@ rcu_preempt_deferred_qs_irqrestore(struct task_struct *t, unsigned long flags) union rcu_special special; rdp = this_cpu_ptr(&rcu_data); - if (rdp->defer_qs_iw_pending == DEFER_QS_PENDING) - rdp->defer_qs_iw_pending = DEFER_QS_IDLE; + if (rdp->defer_qs_pending == DEFER_QS_PENDING) + rdp->defer_qs_pending = DEFER_QS_IDLE; /* * If RCU core is waiting for this CPU to exit its critical section, @@ -645,7 +645,7 @@ static void rcu_preempt_deferred_qs_handler(struct irq_work *iwp) * 5. Deferred QS reporting does not happen. */ if (rcu_preempt_depth() > 0) - WRITE_ONCE(rdp->defer_qs_iw_pending, DEFER_QS_IDLE); + WRITE_ONCE(rdp->defer_qs_pending, DEFER_QS_IDLE); } /* @@ -747,7 +747,10 @@ static void rcu_read_unlock_special(struct task_struct *t) // Using softirq, safe to awaken, and either the // wakeup is free or there is either an expedited // GP in flight or a potential need to deboost. - raise_softirq_irqoff(RCU_SOFTIRQ); + if (rdp->defer_qs_pending != DEFER_QS_PENDING) { + rdp->defer_qs_pending = DEFER_QS_PENDING; + raise_softirq_irqoff(RCU_SOFTIRQ); + } } else { // Enabling BH or preempt does reschedule, so... // Also if no expediting and no possible deboosting, @@ -756,11 +759,11 @@ static void rcu_read_unlock_special(struct task_struct *t) set_tsk_need_resched(current); set_preempt_need_resched(); if (IS_ENABLED(CONFIG_IRQ_WORK) && irqs_were_disabled && - needs_exp && rdp->defer_qs_iw_pending != DEFER_QS_PENDING && + needs_exp && rdp->defer_qs_pending != DEFER_QS_PENDING && cpu_online(rdp->cpu)) { // Get scheduler to re-evaluate and call hooks. // If !IRQ_WORK, FQS scan will eventually IPI. - rdp->defer_qs_iw_pending = DEFER_QS_PENDING; + rdp->defer_qs_pending = DEFER_QS_PENDING; irq_work_queue_on(&rdp->defer_qs_iw, rdp->cpu); } } From 28397957c75b913214d0d1a681f432b749696569 Mon Sep 17 00:00:00 2001 From: Ben Dooks Date: Fri, 9 Jan 2026 13:39:38 +0000 Subject: [PATCH 3258/3902] audit: move the compat_xxx_class[] extern declarations to audit_arch.h [ Upstream commit 76489955c6d4a065ca69dc88faf7a50a59b66f35 ] The comapt_xxx_class symbols aren't declared in anything that lib/comapt_audit.c is including (arm64 build) which is causing the following sparse warnings: lib/compat_audit.c:7:10: warning: symbol 'compat_dir_class' was not declared. Should it be static? lib/compat_audit.c:12:10: warning: symbol 'compat_read_class' was not declared. Should it be static? lib/compat_audit.c:17:10: warning: symbol 'compat_write_class' was not declared. Should it be static? lib/compat_audit.c:22:10: warning: symbol 'compat_chattr_class' was not declared. Should it be static? lib/compat_audit.c:27:10: warning: symbol 'compat_signal_class' was not declared. Should it be static? Trying to fix this by chaning compat_audit.c to inclde does not work on arm64 due to compile errors with the extra includes that changing this header makes. The simpler thing would be just to move the definitons of these symbols out of into which is included. Fixes: 4b58841149dca ("audit: Add generic compat syscall support") Signed-off-by: Ben Dooks [PM: rewrite subject line, fixed line length in description] Signed-off-by: Paul Moore Signed-off-by: Sasha Levin --- include/linux/audit.h | 6 ------ include/linux/audit_arch.h | 7 +++++++ 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/include/linux/audit.h b/include/linux/audit.h index 536f8ee8da818c..b8d8029c6c480c 100644 --- a/include/linux/audit.h +++ b/include/linux/audit.h @@ -128,12 +128,6 @@ enum audit_nfcfgop { extern int __init audit_register_class(int class, unsigned *list); extern int audit_classify_syscall(int abi, unsigned syscall); extern int audit_classify_arch(int arch); -/* only for compat system calls */ -extern unsigned compat_write_class[]; -extern unsigned compat_read_class[]; -extern unsigned compat_dir_class[]; -extern unsigned compat_chattr_class[]; -extern unsigned compat_signal_class[]; /* audit_names->type values */ #define AUDIT_TYPE_UNKNOWN 0 /* we don't know yet */ diff --git a/include/linux/audit_arch.h b/include/linux/audit_arch.h index 0e34d673ef1712..2b8153791e6a5d 100644 --- a/include/linux/audit_arch.h +++ b/include/linux/audit_arch.h @@ -23,4 +23,11 @@ enum auditsc_class_t { extern int audit_classify_compat_syscall(int abi, unsigned syscall); +/* only for compat system calls */ +extern unsigned compat_write_class[]; +extern unsigned compat_read_class[]; +extern unsigned compat_dir_class[]; +extern unsigned compat_chattr_class[]; +extern unsigned compat_signal_class[]; + #endif From d0200c70e3175522fba2f2e8b725bc197a764964 Mon Sep 17 00:00:00 2001 From: Xiaochen Shen Date: Wed, 17 Dec 2025 11:04:53 +0800 Subject: [PATCH 3259/3902] selftests/resctrl: Fix a division by zero error on Hygon [ Upstream commit 671ef08d9455f5754d1fc96f5a14e357d6b80936 ] Change to adjust effective L3 cache size with SNC enabled change introduced the snc_nodes_per_l3_cache() function to detect the Intel Sub-NUMA Clustering (SNC) feature by comparing #CPUs in node0 with #CPUs sharing LLC with CPU0. The function was designed to return: (1) >1: SNC mode is enabled. (2) 1: SNC mode is not enabled or not supported. However, on certain Hygon CPUs, #CPUs sharing LLC with CPU0 is actually less than #CPUs in node0. This results in snc_nodes_per_l3_cache() returning 0 (calculated as cache_cpus / node_cpus). This leads to a division by zero error in get_cache_size(): *cache_size /= snc_nodes_per_l3_cache(); Causing the resctrl selftest to fail with: "Floating point exception (core dumped)" Fix the issue by ensuring snc_nodes_per_l3_cache() returns 1 when SNC mode is not supported on the platform. Updated commit log to fix commit has issues: Shuah Khan Link: https://lore.kernel.org/r/20251217030456.3834956-2-shenxiaochen@open-hieco.net Fixes: a1cd99e700ec ("selftests/resctrl: Adjust effective L3 cache size with SNC enabled") Signed-off-by: Xiaochen Shen Reviewed-by: Reinette Chatre Reviewed-by: Fenghua Yu Signed-off-by: Shuah Khan Signed-off-by: Sasha Levin --- tools/testing/selftests/resctrl/resctrlfs.c | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/tools/testing/selftests/resctrl/resctrlfs.c b/tools/testing/selftests/resctrl/resctrlfs.c index 195f04c4d15868..b9c1bfb6cc029a 100644 --- a/tools/testing/selftests/resctrl/resctrlfs.c +++ b/tools/testing/selftests/resctrl/resctrlfs.c @@ -243,6 +243,16 @@ int snc_nodes_per_l3_cache(void) } snc_mode = cache_cpus / node_cpus; + /* + * On some platforms (e.g. Hygon), + * cache_cpus < node_cpus, the calculated snc_mode is 0. + * + * Set snc_mode = 1 to indicate that SNC mode is not + * supported on the platform. + */ + if (!snc_mode) + snc_mode = 1; + if (snc_mode > 1) ksft_print_msg("SNC-%d mode discovered.\n", snc_mode); } From 49c201c09b473f6c126be2836569ed3ce899bea8 Mon Sep 17 00:00:00 2001 From: Billy Tsai Date: Mon, 12 Jan 2026 14:07:22 +0800 Subject: [PATCH 3260/3902] i3c: Move device name assignment after i3c_bus_init [ Upstream commit 3502cea99c7ceb331458cbd34ef6792c83144687 ] Move device name initialization to occur after i3c_bus_init() so that i3cbus->id is guaranteed to be assigned before it is used. Fixes: 9d4f219807d5 ("i3c: fix refcount inconsistency in i3c_master_register") Signed-off-by: Billy Tsai Reviewed-by: Frank Li Link: https://patch.msgid.link/20260112-upstream_i3c_fix-v1-1-cbbf2cb71809@aspeedtech.com Signed-off-by: Alexandre Belloni Signed-off-by: Sasha Levin --- drivers/i3c/master.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/drivers/i3c/master.c b/drivers/i3c/master.c index 66513a27e6e776..bc68a5e7455bec 100644 --- a/drivers/i3c/master.c +++ b/drivers/i3c/master.c @@ -2884,7 +2884,6 @@ int i3c_master_register(struct i3c_master_controller *master, INIT_LIST_HEAD(&master->boardinfo.i3c); device_initialize(&master->dev); - dev_set_name(&master->dev, "i3c-%d", i3cbus->id); master->dev.dma_mask = parent->dma_mask; master->dev.coherent_dma_mask = parent->coherent_dma_mask; @@ -2894,6 +2893,8 @@ int i3c_master_register(struct i3c_master_controller *master, if (ret) goto err_put_dev; + dev_set_name(&master->dev, "i3c-%d", i3cbus->id); + ret = of_populate_i3c_bus(master); if (ret) goto err_put_dev; From c2ed725668f23fb1ad1f0f883c45b42c641321bf Mon Sep 17 00:00:00 2001 From: Breno Leitao Date: Wed, 7 Jan 2026 06:06:36 -0800 Subject: [PATCH 3261/3902] device_cgroup: remove branch hint after code refactor [ Upstream commit 6784f274722559c0cdaaa418bc8b7b1d61c314f9 ] commit 4ef4ac360101 ("device_cgroup: avoid access to ->i_rdev in the common case in devcgroup_inode_permission()") reordered the checks in devcgroup_inode_permission() to check the inode mode before checking i_rdev, for better cache behavior. However, the likely() annotation on the i_rdev check was not updated to reflect the new code flow. Originally, when i_rdev was checked first, likely(!inode->i_rdev) made sense because most inodes were(?) regular files/directories, thus i_rdev == 0. After the reorder, by the time we reach the i_rdev check, we have already confirmed the inode IS a block or character device. Block and character special files are precisely defined by having a device number (i_rdev), so !inode->i_rdev is now the rare edge case, not the common case. Branch profiling confirmed this is 100% mispredicted: correct incorrect % Function File Line ------- --------- - -------- ---- ---- 0 2631904 100 devcgroup_inode_permission device_cgroup.h 24 Remove likely() to avoid giving the wrong hint to the CPU. Fixes: 4ef4ac360101 ("device_cgroup: avoid access to ->i_rdev in the common case in devcgroup_inode_permission()") Signed-off-by: Breno Leitao Link: https://patch.msgid.link/20260107-likely_device-v1-1-0c55f83a7e47@debian.org Reviewed-by: Mateusz Guzik Signed-off-by: Christian Brauner Signed-off-by: Sasha Levin --- include/linux/device_cgroup.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/linux/device_cgroup.h b/include/linux/device_cgroup.h index 0864773a57e8fe..822085bc2d202c 100644 --- a/include/linux/device_cgroup.h +++ b/include/linux/device_cgroup.h @@ -21,7 +21,7 @@ static inline int devcgroup_inode_permission(struct inode *inode, int mask) if (likely(!S_ISBLK(inode->i_mode) && !S_ISCHR(inode->i_mode))) return 0; - if (likely(!inode->i_rdev)) + if (!inode->i_rdev) return 0; if (S_ISBLK(inode->i_mode)) From 44e9f5aedfb4cafd09b356e10d4617c1a1860225 Mon Sep 17 00:00:00 2001 From: Amir Goldstein Date: Fri, 9 Jan 2026 22:15:36 +0100 Subject: [PATCH 3262/3902] fs: move initializing f_mode before file_ref_init() [ Upstream commit 1219e0feaefc9697f738b223540e8e8906291cb3 ] The comment above file_ref_init() says: "We're SLAB_TYPESAFE_BY_RCU so initialize f_ref last." but file_set_fsnotify_mode() was added after file_ref_init(). Move it right after setting f_mode, where it makes more sense. Fixes: 711f9b8fbe4f4 ("fsnotify: disable pre-content and permission events by default") Signed-off-by: Amir Goldstein Link: https://patch.msgid.link/20260109211536.3565697-1-amir73il@gmail.com Reviewed-by: Jan Kara Signed-off-by: Christian Brauner Signed-off-by: Sasha Levin --- fs/file_table.c | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/fs/file_table.c b/fs/file_table.c index cd4a3db4659ac4..34244fccf2edf0 100644 --- a/fs/file_table.c +++ b/fs/file_table.c @@ -176,6 +176,11 @@ static int init_file(struct file *f, int flags, const struct cred *cred) f->f_flags = flags; f->f_mode = OPEN_FMODE(flags); + /* + * Disable permission and pre-content events for all files by default. + * They may be enabled later by fsnotify_open_perm_and_set_mode(). + */ + file_set_fsnotify_mode(f, FMODE_NONOTIFY_PERM); f->f_op = NULL; f->f_mapping = NULL; @@ -197,11 +202,6 @@ static int init_file(struct file *f, int flags, const struct cred *cred) * refcount bumps we should reinitialize the reused file first. */ file_ref_init(&f->f_ref, 1); - /* - * Disable permission and pre-content events for all files by default. - * They may be enabled later by fsnotify_open_perm_and_set_mode(). - */ - file_set_fsnotify_mode(f, FMODE_NONOTIFY_PERM); return 0; } From 5c328675d2a8adc8f9e69fbe0c3989ea96edb92c Mon Sep 17 00:00:00 2001 From: Ben Dooks Date: Thu, 8 Jan 2026 11:58:56 +0000 Subject: [PATCH 3263/3902] fs: add for 'init_fs' [ Upstream commit 589cff4975afe1a4eaaa1d961652f50b1628d78d ] The init_fs symbol is defined in but was not included in fs/fs_struct.c so fix by adding the include. Fixes the following sparse warning: fs/fs_struct.c:150:18: warning: symbol 'init_fs' was not declared. Should it be static? Fixes: 3e93cd671813e ("Take fs_struct handling to new file") Signed-off-by: Ben Dooks Link: https://patch.msgid.link/20260108115856.238027-1-ben.dooks@codethink.co.uk Reviewed-by: Jan Kara Signed-off-by: Christian Brauner Signed-off-by: Sasha Levin --- fs/fs_struct.c | 1 + 1 file changed, 1 insertion(+) diff --git a/fs/fs_struct.c b/fs/fs_struct.c index 28be762ac1c637..a0b40ad5e7423e 100644 --- a/fs/fs_struct.c +++ b/fs/fs_struct.c @@ -6,6 +6,7 @@ #include #include #include +#include #include "internal.h" /* From 7378340f2e220b428472ef6b82943fbdc966bedb Mon Sep 17 00:00:00 2001 From: Adrian Hunter Date: Tue, 13 Jan 2026 09:26:44 +0200 Subject: [PATCH 3264/3902] i3c: master: Update hot-join flag only on success [ Upstream commit f0775157b9f9a28ae3eabc8d05b0bc52e8056c80 ] To prevent inconsistent state when an error occurs, ensure the hot-join flag is updated only when enabling or disabling hot-join succeeds. Fixes: 317bacf960a48 ("i3c: master: add enable(disable) hot join in sys entry") Signed-off-by: Adrian Hunter Reviewed-by: Frank Li Link: https://patch.msgid.link/20260113072702.16268-4-adrian.hunter@intel.com Signed-off-by: Alexandre Belloni Signed-off-by: Sasha Levin --- drivers/i3c/master.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/drivers/i3c/master.c b/drivers/i3c/master.c index bc68a5e7455bec..425e36b36009bf 100644 --- a/drivers/i3c/master.c +++ b/drivers/i3c/master.c @@ -620,7 +620,8 @@ static int i3c_set_hotjoin(struct i3c_master_controller *master, bool enable) else ret = master->ops->disable_hotjoin(master); - master->hotjoin = enable; + if (!ret) + master->hotjoin = enable; i3c_bus_normaluse_unlock(&master->bus); From 5480341dc884f163b854451e8f1670b2eb7012ae Mon Sep 17 00:00:00 2001 From: Andreas Gruenbacher Date: Tue, 9 Dec 2025 22:59:12 +0000 Subject: [PATCH 3265/3902] gfs2: Retries missing in gfs2_{rename,exchange} [ Upstream commit 11d763f0b0afc2cf5f92f4adae5dbbbbef712f8f ] Fix a bug in gfs2's asynchronous glock handling for rename and exchange operations. The original async implementation from commit ad26967b9afa ("gfs2: Use async glocks for rename") mentioned that retries were needed but never implemented them, causing operations to fail with -ESTALE instead of retrying on timeout. Also makes the waiting interruptible. In addition, the timeouts used were too high for situations in which timing out is a rare but expected scenario. Switch to shorter timeouts with randomization and exponentional backoff. Fixes: ad26967b9afa ("gfs2: Use async glocks for rename") Signed-off-by: Andreas Gruenbacher Signed-off-by: Sasha Levin --- fs/gfs2/glock.c | 36 +++++++++++++++++++++++++++--------- fs/gfs2/glock.h | 3 ++- fs/gfs2/inode.c | 18 ++++++++++++++---- 3 files changed, 43 insertions(+), 14 deletions(-) diff --git a/fs/gfs2/glock.c b/fs/gfs2/glock.c index 9f2eb7e385695d..7aec6cfdfd91de 100644 --- a/fs/gfs2/glock.c +++ b/fs/gfs2/glock.c @@ -1354,31 +1354,45 @@ static int glocks_pending(unsigned int num_gh, struct gfs2_holder *ghs) * gfs2_glock_async_wait - wait on multiple asynchronous glock acquisitions * @num_gh: the number of holders in the array * @ghs: the glock holder array + * @retries: number of retries attempted so far * * Returns: 0 on success, meaning all glocks have been granted and are held. * -ESTALE if the request timed out, meaning all glocks were released, * and the caller should retry the operation. */ -int gfs2_glock_async_wait(unsigned int num_gh, struct gfs2_holder *ghs) +int gfs2_glock_async_wait(unsigned int num_gh, struct gfs2_holder *ghs, + unsigned int retries) { struct gfs2_sbd *sdp = ghs[0].gh_gl->gl_name.ln_sbd; - int i, ret = 0, timeout = 0; unsigned long start_time = jiffies; + int i, ret = 0; + long timeout; might_sleep(); - /* - * Total up the (minimum hold time * 2) of all glocks and use that to - * determine the max amount of time we should wait. - */ - for (i = 0; i < num_gh; i++) - timeout += ghs[i].gh_gl->gl_hold_time << 1; - if (!wait_event_timeout(sdp->sd_async_glock_wait, + timeout = GL_GLOCK_MIN_HOLD; + if (retries) { + unsigned int max_shift; + long incr; + + /* Add a random delay and increase the timeout exponentially. */ + max_shift = BITS_PER_LONG - 2 - __fls(GL_GLOCK_HOLD_INCR); + incr = min(GL_GLOCK_HOLD_INCR << min(retries - 1, max_shift), + 10 * HZ - GL_GLOCK_MIN_HOLD); + schedule_timeout_interruptible(get_random_long() % (incr / 3)); + if (signal_pending(current)) + goto interrupted; + timeout += (incr / 3) + get_random_long() % (incr / 3); + } + + if (!wait_event_interruptible_timeout(sdp->sd_async_glock_wait, !glocks_pending(num_gh, ghs), timeout)) { ret = -ESTALE; /* request timed out. */ goto out; } + if (signal_pending(current)) + goto interrupted; for (i = 0; i < num_gh; i++) { struct gfs2_holder *gh = &ghs[i]; @@ -1402,6 +1416,10 @@ int gfs2_glock_async_wait(unsigned int num_gh, struct gfs2_holder *ghs) } } return ret; + +interrupted: + ret = -EINTR; + goto out; } /** diff --git a/fs/gfs2/glock.h b/fs/gfs2/glock.h index d041b922b45e3b..2d4fd1a2bbbb87 100644 --- a/fs/gfs2/glock.h +++ b/fs/gfs2/glock.h @@ -204,7 +204,8 @@ int gfs2_glock_poll(struct gfs2_holder *gh); int gfs2_instantiate(struct gfs2_holder *gh); int gfs2_glock_holder_ready(struct gfs2_holder *gh); int gfs2_glock_wait(struct gfs2_holder *gh); -int gfs2_glock_async_wait(unsigned int num_gh, struct gfs2_holder *ghs); +int gfs2_glock_async_wait(unsigned int num_gh, struct gfs2_holder *ghs, + unsigned int retries); void gfs2_glock_dq(struct gfs2_holder *gh); void gfs2_glock_dq_wait(struct gfs2_holder *gh); void gfs2_glock_dq_uninit(struct gfs2_holder *gh); diff --git a/fs/gfs2/inode.c b/fs/gfs2/inode.c index d7e35a05c1610c..63d9fe74643441 100644 --- a/fs/gfs2/inode.c +++ b/fs/gfs2/inode.c @@ -1495,7 +1495,7 @@ static int gfs2_rename(struct inode *odir, struct dentry *odentry, unsigned int num_gh; int dir_rename = 0; struct gfs2_diradd da = { .nr_blocks = 0, .save_loc = 0, }; - unsigned int x; + unsigned int retries = 0, x; int error; gfs2_holder_mark_uninitialized(&r_gh); @@ -1545,12 +1545,17 @@ static int gfs2_rename(struct inode *odir, struct dentry *odentry, num_gh++; } +again: for (x = 0; x < num_gh; x++) { error = gfs2_glock_nq(ghs + x); if (error) goto out_gunlock; } - error = gfs2_glock_async_wait(num_gh, ghs); + error = gfs2_glock_async_wait(num_gh, ghs, retries); + if (error == -ESTALE) { + retries++; + goto again; + } if (error) goto out_gunlock; @@ -1739,7 +1744,7 @@ static int gfs2_exchange(struct inode *odir, struct dentry *odentry, struct gfs2_sbd *sdp = GFS2_SB(odir); struct gfs2_holder ghs[4], r_gh; unsigned int num_gh; - unsigned int x; + unsigned int retries = 0, x; umode_t old_mode = oip->i_inode.i_mode; umode_t new_mode = nip->i_inode.i_mode; int error; @@ -1783,13 +1788,18 @@ static int gfs2_exchange(struct inode *odir, struct dentry *odentry, gfs2_holder_init(nip->i_gl, LM_ST_EXCLUSIVE, GL_ASYNC, ghs + num_gh); num_gh++; +again: for (x = 0; x < num_gh; x++) { error = gfs2_glock_nq(ghs + x); if (error) goto out_gunlock; } - error = gfs2_glock_async_wait(num_gh, ghs); + error = gfs2_glock_async_wait(num_gh, ghs, retries); + if (error == -ESTALE) { + retries++; + goto again; + } if (error) goto out_gunlock; From 1d47922b98046b8070a77347fb883a6523792803 Mon Sep 17 00:00:00 2001 From: Andreas Gruenbacher Date: Sun, 14 Dec 2025 16:47:34 +0000 Subject: [PATCH 3266/3902] gfs2: Fix slab-use-after-free in qd_put [ Upstream commit 22150a7d401d9e9169b9b68e05bed95f7f49bf69 ] Commit a475c5dd16e5 ("gfs2: Free quota data objects synchronously") started freeing quota data objects during filesystem shutdown instead of putting them back onto the LRU list, but it failed to remove these objects from the LRU list, causing LRU list corruption. This caused use-after-free when the shrinker (gfs2_qd_shrink_scan) tried to access already-freed objects on the LRU list. Fix this by removing qd objects from the LRU list before freeing them in qd_put(). Initial fix from Deepanshu Kartikey . Fixes: a475c5dd16e5 ("gfs2: Free quota data objects synchronously") Reported-by: syzbot+046b605f01802054bff0@syzkaller.appspotmail.com Closes: https://syzkaller.appspot.com/bug?extid=046b605f01802054bff0 Signed-off-by: Andreas Gruenbacher Signed-off-by: Sasha Levin --- fs/gfs2/quota.c | 1 + 1 file changed, 1 insertion(+) diff --git a/fs/gfs2/quota.c b/fs/gfs2/quota.c index f2df01f801b811..898fc3937b4497 100644 --- a/fs/gfs2/quota.c +++ b/fs/gfs2/quota.c @@ -334,6 +334,7 @@ static void qd_put(struct gfs2_quota_data *qd) lockref_mark_dead(&qd->qd_lockref); spin_unlock(&qd->qd_lockref.lock); + list_lru_del_obj(&gfs2_qd_lru, &qd->qd_lru); gfs2_qd_dispose(qd); return; } From 6d76febba07c40bcf358f63216d36ea68cf1c215 Mon Sep 17 00:00:00 2001 From: Deepanshu Kartikey Date: Fri, 30 Jan 2026 14:51:34 +0530 Subject: [PATCH 3267/3902] gfs2: Fix use-after-free in iomap inline data write path [ Upstream commit faddeb848305e79db89ee0479bb0e33380656321 ] The inline data buffer head (dibh) is being released prematurely in gfs2_iomap_begin() via release_metapath() while iomap->inline_data still points to dibh->b_data. This causes a use-after-free when iomap_write_end_inline() later attempts to write to the inline data area. The bug sequence: 1. gfs2_iomap_begin() calls gfs2_meta_inode_buffer() to read inode metadata into dibh 2. Sets iomap->inline_data = dibh->b_data + sizeof(struct gfs2_dinode) 3. Calls release_metapath() which calls brelse(dibh), dropping refcount to 0 4. kswapd reclaims the page (~39ms later in the syzbot report) 5. iomap_write_end_inline() tries to memcpy() to iomap->inline_data 6. KASAN detects use-after-free write to freed memory Fix by storing dibh in iomap->private and incrementing its refcount with get_bh() in gfs2_iomap_begin(). The buffer is then properly released in gfs2_iomap_end() after the inline write completes, ensuring the page stays alive for the entire iomap operation. Note: A C reproducer is not available for this issue. The fix is based on analysis of the KASAN report and code review showing the buffer head is freed before use. [agruenba: Take buffer head reference in gfs2_iomap_begin() to avoid leaks in gfs2_iomap_get() and gfs2_iomap_alloc().] Reported-by: syzbot+ea1cd4aa4d1e98458a55@syzkaller.appspotmail.com Closes: https://syzkaller.appspot.com/bug?extid=ea1cd4aa4d1e98458a55 Fixes: d0a22a4b03b8 ("gfs2: Fix iomap write page reclaim deadlock") Signed-off-by: Deepanshu Kartikey Signed-off-by: Andreas Gruenbacher Signed-off-by: Sasha Levin --- fs/gfs2/bmap.c | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/fs/gfs2/bmap.c b/fs/gfs2/bmap.c index 131091520de6be..fdcac8e3f2ba26 100644 --- a/fs/gfs2/bmap.c +++ b/fs/gfs2/bmap.c @@ -1127,10 +1127,18 @@ static int gfs2_iomap_begin(struct inode *inode, loff_t pos, loff_t length, goto out_unlock; break; default: - goto out_unlock; + goto out; } ret = gfs2_iomap_begin_write(inode, pos, length, flags, iomap, &mp); + if (ret) + goto out_unlock; + +out: + if (iomap->type == IOMAP_INLINE) { + iomap->private = metapath_dibh(&mp); + get_bh(iomap->private); + } out_unlock: release_metapath(&mp); @@ -1144,6 +1152,9 @@ static int gfs2_iomap_end(struct inode *inode, loff_t pos, loff_t length, struct gfs2_inode *ip = GFS2_I(inode); struct gfs2_sbd *sdp = GFS2_SB(inode); + if (iomap->private) + brelse(iomap->private); + switch (flags & (IOMAP_WRITE | IOMAP_ZERO)) { case IOMAP_WRITE: if (flags & IOMAP_DIRECT) From 53f4761ab032de49136805c934a1bf9fa4ec0fd8 Mon Sep 17 00:00:00 2001 From: Fredrik Markstrom Date: Fri, 16 Jan 2026 15:29:42 +0100 Subject: [PATCH 3268/3902] i3c: dw: Initialize spinlock to avoid upsetting lockdep [ Upstream commit b58eaa4761ab02fc38c39d674a6bcdd55e00f388 ] The devs_lock spinlock introduced when adding support for ibi:s was never initialized. Fixes: e389b1d72a624 ("i3c: dw: Add support for in-band interrupts") Suggested-by: Jani Nurminen Signed-off-by: Fredrik Markstrom Reviewed-by: Ivar Holmqvist Link: https://patch.msgid.link/20260116-i3c_dw_initialize_spinlock-v3-1-cf707b6ed75f@est.tech Signed-off-by: Alexandre Belloni Signed-off-by: Sasha Levin --- drivers/i3c/master/dw-i3c-master.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/drivers/i3c/master/dw-i3c-master.c b/drivers/i3c/master/dw-i3c-master.c index 9ceedf09c3b6a6..75f813d72f851e 100644 --- a/drivers/i3c/master/dw-i3c-master.c +++ b/drivers/i3c/master/dw-i3c-master.c @@ -1563,6 +1563,8 @@ int dw_i3c_common_probe(struct dw_i3c_master *master, spin_lock_init(&master->xferqueue.lock); INIT_LIST_HEAD(&master->xferqueue.list); + spin_lock_init(&master->devs_lock); + writel(INTR_ALL, master->regs + INTR_STATUS); irq = platform_get_irq(pdev, 0); ret = devm_request_irq(&pdev->dev, irq, From 8e71414e252c1cb235911008a98fd47927d3a55c Mon Sep 17 00:00:00 2001 From: Zilin Guan Date: Mon, 26 Jan 2026 08:11:21 +0000 Subject: [PATCH 3269/3902] i3c: dw: Fix memory leak in dw_i3c_master_i2c_xfers() [ Upstream commit 2537089413514caaa9a5fdeeac3a34d45100f747 ] The dw_i3c_master_i2c_xfers() function allocates memory for the xfer structure using dw_i3c_master_alloc_xfer(). If pm_runtime_resume_and_get() fails, the function returns without freeing the allocated xfer, resulting in a memory leak. Add a dw_i3c_master_free_xfer() call to the error path to ensure the allocated memory is properly freed. Compile tested only. Issue found using a prototype static analysis tool and code review. Fixes: 62fe9d06f570 ("i3c: dw: Add power management support") Signed-off-by: Zilin Guan Reviewed-by: Frank Li Link: https://patch.msgid.link/20260126081121.644099-1-zilin@seu.edu.cn Signed-off-by: Alexandre Belloni Signed-off-by: Sasha Levin --- drivers/i3c/master/dw-i3c-master.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/i3c/master/dw-i3c-master.c b/drivers/i3c/master/dw-i3c-master.c index 75f813d72f851e..c06595cb740103 100644 --- a/drivers/i3c/master/dw-i3c-master.c +++ b/drivers/i3c/master/dw-i3c-master.c @@ -1094,6 +1094,7 @@ static int dw_i3c_master_i2c_xfers(struct i2c_dev_desc *dev, dev_err(master->dev, "<%s> cannot resume i3c bus master, err: %d\n", __func__, ret); + dw_i3c_master_free_xfer(xfer); return ret; } From 1a22048c1117cdfac185ba450aba67ed6b65dc87 Mon Sep 17 00:00:00 2001 From: Alper Ak Date: Fri, 26 Dec 2025 13:23:38 +0300 Subject: [PATCH 3270/3902] tpm: tpm_i2c_infineon: Fix locality leak on get_burstcount() failure [ Upstream commit bbd6e97c836cbeb9606d7b7e5dcf8a1d89525713 ] get_burstcount() can return -EBUSY on timeout. When this happens, the function returns directly without releasing the locality that was acquired at the beginning of tpm_tis_i2c_send(). Use goto out_err to ensure proper cleanup when get_burstcount() fails. Fixes: aad628c1d91a ("char/tpm: Add new driver for Infineon I2C TIS TPM") Signed-off-by: Alper Ak Reviewed-by: Jarkko Sakkinen Signed-off-by: Jarkko Sakkinen Signed-off-by: Sasha Levin --- drivers/char/tpm/tpm_i2c_infineon.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/drivers/char/tpm/tpm_i2c_infineon.c b/drivers/char/tpm/tpm_i2c_infineon.c index bdf1f329a67946..8b7d32de0b2ef9 100644 --- a/drivers/char/tpm/tpm_i2c_infineon.c +++ b/drivers/char/tpm/tpm_i2c_infineon.c @@ -544,8 +544,10 @@ static int tpm_tis_i2c_send(struct tpm_chip *chip, u8 *buf, size_t bufsiz, burstcnt = get_burstcount(chip); /* burstcnt < 0 = TPM is busy */ - if (burstcnt < 0) - return burstcnt; + if (burstcnt < 0) { + rc = burstcnt; + goto out_err; + } if (burstcnt > (len - 1 - count)) burstcnt = len - 1 - count; From ec15eb67fe9df87981b4829b901ec254273ca483 Mon Sep 17 00:00:00 2001 From: Alper Ak Date: Fri, 26 Dec 2025 15:09:27 +0300 Subject: [PATCH 3271/3902] tpm: st33zp24: Fix missing cleanup on get_burstcount() error [ Upstream commit 3e91b44c93ad2871f89fc2a98c5e4fe6ca5db3d9 ] get_burstcount() can return -EBUSY on timeout. When this happens, st33zp24_send() returns directly without releasing the locality acquired earlier. Use goto out_err to ensure proper cleanup when get_burstcount() fails. Fixes: bf38b8710892 ("tpm/tpm_i2c_stm_st33: Split tpm_i2c_tpm_st33 in 2 layers (core + phy)") Signed-off-by: Alper Ak Signed-off-by: Jarkko Sakkinen Signed-off-by: Sasha Levin --- drivers/char/tpm/st33zp24/st33zp24.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/drivers/char/tpm/st33zp24/st33zp24.c b/drivers/char/tpm/st33zp24/st33zp24.c index 2ed7815e4899b7..e2b7451ea7ccd3 100644 --- a/drivers/char/tpm/st33zp24/st33zp24.c +++ b/drivers/char/tpm/st33zp24/st33zp24.c @@ -328,8 +328,10 @@ static int st33zp24_send(struct tpm_chip *chip, unsigned char *buf, for (i = 0; i < len - 1;) { burstcnt = get_burstcount(chip); - if (burstcnt < 0) - return burstcnt; + if (burstcnt < 0) { + ret = burstcnt; + goto out_err; + } size = min_t(int, len - i - 1, burstcnt); ret = tpm_dev->ops->send(tpm_dev->phy_id, TPM_DATA_FIFO, buf + i, size); From 728ba4346177481426c03a70316db0195fc68696 Mon Sep 17 00:00:00 2001 From: Gao Xiang Date: Thu, 27 Nov 2025 16:07:56 +0800 Subject: [PATCH 3272/3902] erofs: get rid of raw bi_end_io() usage [ Upstream commit 80d0c27a0a4af8e0678d7412781482e6f73c22c7 ] These BIOs are actually harmless in practice, as they are all pseudo BIOs and do not use advanced features like chaining. Using the BIO interface is a more friendly and unified approach for both bdev and and file-backed I/Os (compared to awkward bvec interfaces). Let's use bio_endio() instead. Reviewed-by: Christoph Hellwig Reviewed-by: Ming Lei Reviewed-by: Chao Yu Signed-off-by: Gao Xiang Stable-dep-of: bc804a8d7e86 ("erofs: handle end of filesystem properly for file-backed mounts") Signed-off-by: Sasha Levin --- fs/erofs/fileio.c | 2 +- fs/erofs/fscache.c | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/fs/erofs/fileio.c b/fs/erofs/fileio.c index a47c6bab98ff99..e2eaa7119bd4f5 100644 --- a/fs/erofs/fileio.c +++ b/fs/erofs/fileio.c @@ -35,13 +35,13 @@ static void erofs_fileio_ki_complete(struct kiocb *iocb, long ret) if (rq->bio.bi_end_io) { if (ret < 0 && !rq->bio.bi_status) rq->bio.bi_status = errno_to_blk_status(ret); - rq->bio.bi_end_io(&rq->bio); } else { bio_for_each_folio_all(fi, &rq->bio) { DBG_BUGON(folio_test_uptodate(fi.folio)); erofs_onlinefolio_end(fi.folio, ret, false); } } + bio_endio(&rq->bio); bio_uninit(&rq->bio); if (refcount_dec_and_test(&rq->ref)) kfree(rq); diff --git a/fs/erofs/fscache.c b/fs/erofs/fscache.c index 362acf828279fc..7a346e20f7b7aa 100644 --- a/fs/erofs/fscache.c +++ b/fs/erofs/fscache.c @@ -185,7 +185,7 @@ static void erofs_fscache_bio_endio(void *priv, ssize_t transferred_or_error) if (IS_ERR_VALUE(transferred_or_error)) io->bio.bi_status = errno_to_blk_status(transferred_or_error); - io->bio.bi_end_io(&io->bio); + bio_endio(&io->bio); BUILD_BUG_ON(offsetof(struct erofs_fscache_bio, io) != 0); erofs_fscache_io_put(&io->io); } @@ -216,7 +216,7 @@ void erofs_fscache_submit_bio(struct bio *bio) if (!ret) return; bio->bi_status = errno_to_blk_status(ret); - bio->bi_end_io(bio); + bio_endio(bio); } static int erofs_fscache_meta_read_folio(struct file *data, struct folio *folio) From e49abde0ffc382a967b24f326d1614ac3bb06a94 Mon Sep 17 00:00:00 2001 From: Gao Xiang Date: Fri, 30 Jan 2026 15:54:22 +0800 Subject: [PATCH 3273/3902] erofs: handle end of filesystem properly for file-backed mounts [ Upstream commit bc804a8d7e865ef47fb7edcaf5e77d18bf444ebc ] I/O requests beyond the end of the filesystem should be zeroed out, similar to loopback devices and that is what we expect. Fixes: ce63cb62d794 ("erofs: support unencoded inodes for fileio") Signed-off-by: Gao Xiang Signed-off-by: Sasha Levin --- fs/erofs/fileio.c | 20 ++++++++------------ 1 file changed, 8 insertions(+), 12 deletions(-) diff --git a/fs/erofs/fileio.c b/fs/erofs/fileio.c index e2eaa7119bd4f5..5b77ee8cc99f4d 100644 --- a/fs/erofs/fileio.c +++ b/fs/erofs/fileio.c @@ -25,21 +25,17 @@ static void erofs_fileio_ki_complete(struct kiocb *iocb, long ret) container_of(iocb, struct erofs_fileio_rq, iocb); struct folio_iter fi; - if (ret > 0) { - if (ret != rq->bio.bi_iter.bi_size) { - bio_advance(&rq->bio, ret); - zero_fill_bio(&rq->bio); - } - ret = 0; + if (ret >= 0 && ret != rq->bio.bi_iter.bi_size) { + bio_advance(&rq->bio, ret); + zero_fill_bio(&rq->bio); } - if (rq->bio.bi_end_io) { - if (ret < 0 && !rq->bio.bi_status) - rq->bio.bi_status = errno_to_blk_status(ret); - } else { + if (!rq->bio.bi_end_io) { bio_for_each_folio_all(fi, &rq->bio) { DBG_BUGON(folio_test_uptodate(fi.folio)); - erofs_onlinefolio_end(fi.folio, ret, false); + erofs_onlinefolio_end(fi.folio, ret < 0, false); } + } else if (ret < 0 && !rq->bio.bi_status) { + rq->bio.bi_status = errno_to_blk_status(ret); } bio_endio(&rq->bio); bio_uninit(&rq->bio); @@ -51,7 +47,7 @@ static void erofs_fileio_rq_submit(struct erofs_fileio_rq *rq) { const struct cred *old_cred; struct iov_iter iter; - int ret; + ssize_t ret; if (!rq) return; From 8f7f0e1728685416b0ef7a8872399a843f06fcf0 Mon Sep 17 00:00:00 2001 From: Qu Wenruo Date: Tue, 28 Oct 2025 10:06:36 +1030 Subject: [PATCH 3274/3902] btrfs: headers cleanup to remove unnecessary local includes [ Upstream commit c5667f9c8eb90293dfa4e52c65eb89fe39f5652d ] [BUG] When I tried to remove btrfs_bio::fs_info and use btrfs_bio::inode to grab the fs_info, the header "btrfs_inode.h" is needed to access the full btrfs_inode structure. Then btrfs will fail to compile. [CAUSE] There is a recursive including chain: "bio.h" -> "btrfs_inode.h" -> "extent_map.h" -> "compression.h" -> "bio.h" That recursive including is causing problems for btrfs. [ENHANCEMENT] To reduce the risk of recursive including: - Remove unnecessary local includes from btrfs headers Either the included header is pulled in by other headers, or is completely unnecessary. - Remove btrfs local includes if the header only requires a pointer In that case let the implementing C file to pull the required header. This is especially important for headers like "btrfs_inode.h" which pulls in a lot of other btrfs headers, thus it's a mine field of recursive including. - Remove unnecessary temporary structure definition Either if we have included the header defining the structure, or completely unused. Now including "btrfs_inode.h" inside "bio.h" is completely fine, although "btrfs_inode.h" still includes "extent_map.h", but that header only includes "fs.h", no more chain back to "bio.h". Signed-off-by: Qu Wenruo Reviewed-by: David Sterba Signed-off-by: David Sterba Stable-dep-of: b39b26e017c7 ("btrfs: zoned: don't zone append to conventional zone") Signed-off-by: Sasha Levin --- fs/btrfs/accessors.h | 1 + fs/btrfs/btrfs_inode.h | 8 ++++---- fs/btrfs/compression.h | 3 --- fs/btrfs/ctree.h | 2 -- fs/btrfs/defrag.c | 1 + fs/btrfs/dir-item.c | 1 + fs/btrfs/direct-io.c | 2 ++ fs/btrfs/disk-io.c | 1 + fs/btrfs/disk-io.h | 3 ++- fs/btrfs/extent-tree.c | 1 + fs/btrfs/extent_io.h | 1 - fs/btrfs/extent_map.h | 3 +-- fs/btrfs/file-item.h | 2 +- fs/btrfs/inode.c | 1 + fs/btrfs/space-info.c | 1 + fs/btrfs/subpage.h | 1 - fs/btrfs/transaction.c | 2 ++ fs/btrfs/transaction.h | 4 ---- fs/btrfs/tree-log.c | 1 + fs/btrfs/tree-log.h | 3 +-- fs/btrfs/zoned.h | 1 - 21 files changed, 21 insertions(+), 22 deletions(-) diff --git a/fs/btrfs/accessors.h b/fs/btrfs/accessors.h index 99b3ced12805bb..78721412951c5a 100644 --- a/fs/btrfs/accessors.h +++ b/fs/btrfs/accessors.h @@ -12,6 +12,7 @@ #include #include #include +#include "fs.h" #include "extent_io.h" struct extent_buffer; diff --git a/fs/btrfs/btrfs_inode.h b/fs/btrfs/btrfs_inode.h index af373d50a901f2..a66ca5531b5c57 100644 --- a/fs/btrfs/btrfs_inode.h +++ b/fs/btrfs/btrfs_inode.h @@ -18,20 +18,20 @@ #include #include #include +#include "ctree.h" #include "block-rsv.h" #include "extent_map.h" -#include "extent_io.h" #include "extent-io-tree.h" -#include "ordered-data.h" -#include "delayed-inode.h" -struct extent_state; struct posix_acl; struct iov_iter; struct writeback_control; struct btrfs_root; struct btrfs_fs_info; struct btrfs_trans_handle; +struct btrfs_bio; +struct btrfs_file_extent; +struct btrfs_delayed_node; /* * Since we search a directory based on f_pos (struct dir_context::pos) we have diff --git a/fs/btrfs/compression.h b/fs/btrfs/compression.h index eba188a9e3bb58..c6812d5fcab791 100644 --- a/fs/btrfs/compression.h +++ b/fs/btrfs/compression.h @@ -14,14 +14,11 @@ #include #include "bio.h" #include "fs.h" -#include "messages.h" struct address_space; -struct page; struct inode; struct btrfs_inode; struct btrfs_ordered_extent; -struct btrfs_bio; /* * We want to make sure that amount of RAM required to uncompress an extent is diff --git a/fs/btrfs/ctree.h b/fs/btrfs/ctree.h index fe70b593c7cd9d..16dd11c4853130 100644 --- a/fs/btrfs/ctree.h +++ b/fs/btrfs/ctree.h @@ -17,9 +17,7 @@ #include #include #include "locking.h" -#include "fs.h" #include "accessors.h" -#include "extent-io-tree.h" struct extent_buffer; struct btrfs_block_rsv; diff --git a/fs/btrfs/defrag.c b/fs/btrfs/defrag.c index 7b277934f66f92..a4cc1bc6356227 100644 --- a/fs/btrfs/defrag.c +++ b/fs/btrfs/defrag.c @@ -15,6 +15,7 @@ #include "defrag.h" #include "file-item.h" #include "super.h" +#include "compression.h" static struct kmem_cache *btrfs_inode_defrag_cachep; diff --git a/fs/btrfs/dir-item.c b/fs/btrfs/dir-item.c index 69863e398e223f..77e1bcb2a74bf1 100644 --- a/fs/btrfs/dir-item.c +++ b/fs/btrfs/dir-item.c @@ -9,6 +9,7 @@ #include "transaction.h" #include "accessors.h" #include "dir-item.h" +#include "delayed-inode.h" /* * insert a name into a directory, doing overflow properly if there is a hash diff --git a/fs/btrfs/direct-io.c b/fs/btrfs/direct-io.c index 802d4dbe5b3817..8888ef4ae6064b 100644 --- a/fs/btrfs/direct-io.c +++ b/fs/btrfs/direct-io.c @@ -10,6 +10,8 @@ #include "fs.h" #include "transaction.h" #include "volumes.h" +#include "bio.h" +#include "ordered-data.h" struct btrfs_dio_data { ssize_t submitted; diff --git a/fs/btrfs/disk-io.c b/fs/btrfs/disk-io.c index 745ae698bbc8ad..3fd5d6a27d4c0e 100644 --- a/fs/btrfs/disk-io.c +++ b/fs/btrfs/disk-io.c @@ -50,6 +50,7 @@ #include "relocation.h" #include "scrub.h" #include "super.h" +#include "delayed-inode.h" #define BTRFS_SUPER_FLAG_SUPP (BTRFS_HEADER_FLAG_WRITTEN |\ BTRFS_HEADER_FLAG_RELOC |\ diff --git a/fs/btrfs/disk-io.h b/fs/btrfs/disk-io.h index 57920f2c6fe4ef..5320da83d0cf8e 100644 --- a/fs/btrfs/disk-io.h +++ b/fs/btrfs/disk-io.h @@ -9,7 +9,8 @@ #include #include #include "ctree.h" -#include "fs.h" +#include "bio.h" +#include "ordered-data.h" struct block_device; struct super_block; diff --git a/fs/btrfs/extent-tree.c b/fs/btrfs/extent-tree.c index dc4ca98c37800a..01337e3f2879c7 100644 --- a/fs/btrfs/extent-tree.c +++ b/fs/btrfs/extent-tree.c @@ -40,6 +40,7 @@ #include "orphan.h" #include "tree-checker.h" #include "raid-stripe-tree.h" +#include "delayed-inode.h" #undef SCRAMBLE_DELAYED_REFS diff --git a/fs/btrfs/extent_io.h b/fs/btrfs/extent_io.h index 559bec44a7a8e8..73571d5d3d5ad9 100644 --- a/fs/btrfs/extent_io.h +++ b/fs/btrfs/extent_io.h @@ -12,7 +12,6 @@ #include #include #include -#include "compression.h" #include "messages.h" #include "ulist.h" #include "misc.h" diff --git a/fs/btrfs/extent_map.h b/fs/btrfs/extent_map.h index d4b81ee4d97bdb..6f685f3c932721 100644 --- a/fs/btrfs/extent_map.h +++ b/fs/btrfs/extent_map.h @@ -8,8 +8,7 @@ #include #include #include -#include "misc.h" -#include "compression.h" +#include "fs.h" struct btrfs_inode; struct btrfs_fs_info; diff --git a/fs/btrfs/file-item.h b/fs/btrfs/file-item.h index 63216c43676def..0d59e830018a63 100644 --- a/fs/btrfs/file-item.h +++ b/fs/btrfs/file-item.h @@ -7,7 +7,7 @@ #include #include #include "ctree.h" -#include "accessors.h" +#include "ordered-data.h" struct extent_map; struct btrfs_file_extent_item; diff --git a/fs/btrfs/inode.c b/fs/btrfs/inode.c index 76a66c74249a22..b261dbeb290401 100644 --- a/fs/btrfs/inode.c +++ b/fs/btrfs/inode.c @@ -71,6 +71,7 @@ #include "backref.h" #include "raid-stripe-tree.h" #include "fiemap.h" +#include "delayed-inode.h" #define COW_FILE_RANGE_KEEP_LOCKED (1UL << 0) #define COW_FILE_RANGE_NO_INLINE (1UL << 1) diff --git a/fs/btrfs/space-info.c b/fs/btrfs/space-info.c index a6f94e9f559158..e5c18a29eb7e6e 100644 --- a/fs/btrfs/space-info.c +++ b/fs/btrfs/space-info.c @@ -15,6 +15,7 @@ #include "accessors.h" #include "extent-tree.h" #include "zoned.h" +#include "delayed-inode.h" /* * HOW DOES SPACE RESERVATION WORK diff --git a/fs/btrfs/subpage.h b/fs/btrfs/subpage.h index ad0552db7c7dcb..d81a0ade559fd1 100644 --- a/fs/btrfs/subpage.h +++ b/fs/btrfs/subpage.h @@ -7,7 +7,6 @@ #include #include #include "btrfs_inode.h" -#include "fs.h" struct address_space; struct folio; diff --git a/fs/btrfs/transaction.c b/fs/btrfs/transaction.c index c457316c2788be..041f4781956cf2 100644 --- a/fs/btrfs/transaction.c +++ b/fs/btrfs/transaction.c @@ -32,6 +32,8 @@ #include "ioctl.h" #include "relocation.h" #include "scrub.h" +#include "ordered-data.h" +#include "delayed-inode.h" static struct kmem_cache *btrfs_trans_handle_cachep; diff --git a/fs/btrfs/transaction.h b/fs/btrfs/transaction.h index 9f7c777af6356c..18ef069197e5ba 100644 --- a/fs/btrfs/transaction.h +++ b/fs/btrfs/transaction.h @@ -14,10 +14,6 @@ #include #include "btrfs_inode.h" #include "delayed-ref.h" -#include "extent-io-tree.h" -#include "block-rsv.h" -#include "messages.h" -#include "misc.h" struct dentry; struct inode; diff --git a/fs/btrfs/tree-log.c b/fs/btrfs/tree-log.c index ae2e035d013e2e..6c5db73c3e85f1 100644 --- a/fs/btrfs/tree-log.c +++ b/fs/btrfs/tree-log.c @@ -29,6 +29,7 @@ #include "orphan.h" #include "print-tree.h" #include "tree-checker.h" +#include "delayed-inode.h" #define MAX_CONFLICT_INODES 10 diff --git a/fs/btrfs/tree-log.h b/fs/btrfs/tree-log.h index dc313e6bb2faa1..4f149d7d4fdee1 100644 --- a/fs/btrfs/tree-log.h +++ b/fs/btrfs/tree-log.h @@ -8,8 +8,7 @@ #include #include -#include "messages.h" -#include "ctree.h" +#include #include "transaction.h" struct inode; diff --git a/fs/btrfs/zoned.h b/fs/btrfs/zoned.h index 17c5656580dd97..2b807a02d1a8a8 100644 --- a/fs/btrfs/zoned.h +++ b/fs/btrfs/zoned.h @@ -15,7 +15,6 @@ #include "disk-io.h" #include "block-group.h" #include "btrfs_inode.h" -#include "fs.h" struct block_device; struct extent_buffer; From 4663160aa2d8472e9194367d02f75e26573809e9 Mon Sep 17 00:00:00 2001 From: Qu Wenruo Date: Wed, 29 Oct 2025 08:35:33 +1030 Subject: [PATCH 3275/3902] btrfs: remove btrfs_bio::fs_info by extracting it from btrfs_bio::inode [ Upstream commit 81cea6cd7041ebd42281e0517f856d88527d3326 ] Currently there is only one caller which doesn't populate btrfs_bio::inode, and that's scrub. The idea is scrub doesn't want any automatic csum verification nor read-repair, as everything will be handled by scrub itself. However that behavior is really no different than metadata inode, thus we can reuse btree_inode as btrfs_bio::inode for scrub. The only exception is in btrfs_submit_chunk() where if a bbio is from scrub or data reloc inode, we set rst_search_commit_root to true. This means we still need a way to distinguish scrub from metadata, but that can be done by a new flag inside btrfs_bio. Now btrfs_bio::inode is a mandatory parameter, we can extract fs_info from that inode thus can remove btrfs_bio::fs_info to save 8 bytes from btrfs_bio structure. Signed-off-by: Qu Wenruo Reviewed-by: David Sterba Signed-off-by: David Sterba Stable-dep-of: b39b26e017c7 ("btrfs: zoned: don't zone append to conventional zone") Signed-off-by: Sasha Levin --- fs/btrfs/bio.c | 53 ++++++++++++++++++++++-------------------- fs/btrfs/bio.h | 18 +++++++++----- fs/btrfs/compression.c | 6 ++--- fs/btrfs/compression.h | 3 ++- fs/btrfs/direct-io.c | 4 +--- fs/btrfs/extent_io.c | 22 +++++++----------- fs/btrfs/inode.c | 7 ++---- fs/btrfs/scrub.c | 51 ++++++++++++++++++++++------------------ fs/btrfs/zoned.c | 4 ++-- 9 files changed, 87 insertions(+), 81 deletions(-) diff --git a/fs/btrfs/bio.c b/fs/btrfs/bio.c index 21df48e6c4fa20..b85b6b21b5450b 100644 --- a/fs/btrfs/bio.c +++ b/fs/btrfs/bio.c @@ -41,13 +41,17 @@ static bool bbio_has_ordered_extent(const struct btrfs_bio *bbio) * Initialize a btrfs_bio structure. This skips the embedded bio itself as it * is already initialized by the block layer. */ -void btrfs_bio_init(struct btrfs_bio *bbio, struct btrfs_fs_info *fs_info, +void btrfs_bio_init(struct btrfs_bio *bbio, struct btrfs_inode *inode, u64 file_offset, btrfs_bio_end_io_t end_io, void *private) { + /* @inode parameter is mandatory. */ + ASSERT(inode); + memset(bbio, 0, offsetof(struct btrfs_bio, bio)); - bbio->fs_info = fs_info; + bbio->inode = inode; bbio->end_io = end_io; bbio->private = private; + bbio->file_offset = file_offset; atomic_set(&bbio->pending_ios, 1); WRITE_ONCE(bbio->status, BLK_STS_OK); } @@ -60,7 +64,7 @@ void btrfs_bio_init(struct btrfs_bio *bbio, struct btrfs_fs_info *fs_info, * a mempool. */ struct btrfs_bio *btrfs_bio_alloc(unsigned int nr_vecs, blk_opf_t opf, - struct btrfs_fs_info *fs_info, + struct btrfs_inode *inode, u64 file_offset, btrfs_bio_end_io_t end_io, void *private) { struct btrfs_bio *bbio; @@ -68,7 +72,7 @@ struct btrfs_bio *btrfs_bio_alloc(unsigned int nr_vecs, blk_opf_t opf, bio = bio_alloc_bioset(NULL, nr_vecs, opf, GFP_NOFS, &btrfs_bioset); bbio = btrfs_bio(bio); - btrfs_bio_init(bbio, fs_info, end_io, private); + btrfs_bio_init(bbio, inode, file_offset, end_io, private); return bbio; } @@ -85,9 +89,7 @@ static struct btrfs_bio *btrfs_split_bio(struct btrfs_fs_info *fs_info, return ERR_CAST(bio); bbio = btrfs_bio(bio); - btrfs_bio_init(bbio, fs_info, NULL, orig_bbio); - bbio->inode = orig_bbio->inode; - bbio->file_offset = orig_bbio->file_offset; + btrfs_bio_init(bbio, orig_bbio->inode, orig_bbio->file_offset, NULL, orig_bbio); orig_bbio->file_offset += map_length; if (bbio_has_ordered_extent(bbio)) { refcount_inc(&orig_bbio->ordered->refs); @@ -244,9 +246,8 @@ static struct btrfs_failed_bio *repair_one_sector(struct btrfs_bio *failed_bbio, bio_add_folio_nofail(repair_bio, folio, sectorsize, foff); repair_bbio = btrfs_bio(repair_bio); - btrfs_bio_init(repair_bbio, fs_info, NULL, fbio); - repair_bbio->inode = failed_bbio->inode; - repair_bbio->file_offset = failed_bbio->file_offset + bio_offset; + btrfs_bio_init(repair_bbio, failed_bbio->inode, failed_bbio->file_offset + bio_offset, + NULL, fbio); mirror = next_repair_mirror(fbio, failed_bbio->mirror_num); btrfs_debug(fs_info, "submitting repair read to mirror %d", mirror); @@ -332,7 +333,7 @@ static void btrfs_simple_end_io(struct bio *bio) { struct btrfs_bio *bbio = btrfs_bio(bio); struct btrfs_device *dev = bio->bi_private; - struct btrfs_fs_info *fs_info = bbio->fs_info; + struct btrfs_fs_info *fs_info = bbio->inode->root->fs_info; btrfs_bio_counter_dec(fs_info); @@ -581,10 +582,11 @@ static void run_one_async_done(struct btrfs_work *work, bool do_free) static bool should_async_write(struct btrfs_bio *bbio) { + struct btrfs_fs_info *fs_info = bbio->inode->root->fs_info; bool auto_csum_mode = true; #ifdef CONFIG_BTRFS_EXPERIMENTAL - struct btrfs_fs_devices *fs_devices = bbio->fs_info->fs_devices; + struct btrfs_fs_devices *fs_devices = fs_info->fs_devices; enum btrfs_offload_csum_mode csum_mode = READ_ONCE(fs_devices->offload_csum_mode); if (csum_mode == BTRFS_OFFLOAD_CSUM_FORCE_OFF) @@ -594,7 +596,7 @@ static bool should_async_write(struct btrfs_bio *bbio) #endif /* Submit synchronously if the checksum implementation is fast. */ - if (auto_csum_mode && test_bit(BTRFS_FS_CSUM_IMPL_FAST, &bbio->fs_info->flags)) + if (auto_csum_mode && test_bit(BTRFS_FS_CSUM_IMPL_FAST, &fs_info->flags)) return false; /* @@ -605,7 +607,7 @@ static bool should_async_write(struct btrfs_bio *bbio) return false; /* Zoned devices require I/O to be submitted in order. */ - if ((bbio->bio.bi_opf & REQ_META) && btrfs_is_zoned(bbio->fs_info)) + if ((bbio->bio.bi_opf & REQ_META) && btrfs_is_zoned(fs_info)) return false; return true; @@ -620,7 +622,7 @@ static bool btrfs_wq_submit_bio(struct btrfs_bio *bbio, struct btrfs_io_context *bioc, struct btrfs_io_stripe *smap, int mirror_num) { - struct btrfs_fs_info *fs_info = bbio->fs_info; + struct btrfs_fs_info *fs_info = bbio->inode->root->fs_info; struct async_submit_bio *async; async = kmalloc(sizeof(*async), GFP_NOFS); @@ -639,11 +641,12 @@ static bool btrfs_wq_submit_bio(struct btrfs_bio *bbio, static u64 btrfs_append_map_length(struct btrfs_bio *bbio, u64 map_length) { + struct btrfs_fs_info *fs_info = bbio->inode->root->fs_info; unsigned int nr_segs; int sector_offset; - map_length = min(map_length, bbio->fs_info->max_zone_append_size); - sector_offset = bio_split_rw_at(&bbio->bio, &bbio->fs_info->limits, + map_length = min(map_length, fs_info->max_zone_append_size); + sector_offset = bio_split_rw_at(&bbio->bio, &fs_info->limits, &nr_segs, map_length); if (sector_offset) { /* @@ -651,7 +654,7 @@ static u64 btrfs_append_map_length(struct btrfs_bio *bbio, u64 map_length) * sectorsize and thus cause unaligned I/Os. Fix that by * always rounding down to the nearest boundary. */ - return ALIGN_DOWN(sector_offset << SECTOR_SHIFT, bbio->fs_info->sectorsize); + return ALIGN_DOWN(sector_offset << SECTOR_SHIFT, fs_info->sectorsize); } return map_length; } @@ -659,7 +662,7 @@ static u64 btrfs_append_map_length(struct btrfs_bio *bbio, u64 map_length) static bool btrfs_submit_chunk(struct btrfs_bio *bbio, int mirror_num) { struct btrfs_inode *inode = bbio->inode; - struct btrfs_fs_info *fs_info = bbio->fs_info; + struct btrfs_fs_info *fs_info = inode->root->fs_info; struct bio *bio = &bbio->bio; u64 logical = bio->bi_iter.bi_sector << SECTOR_SHIFT; u64 length = bio->bi_iter.bi_size; @@ -670,7 +673,7 @@ static bool btrfs_submit_chunk(struct btrfs_bio *bbio, int mirror_num) blk_status_t status; int ret; - if (!bbio->inode || btrfs_is_data_reloc_root(inode->root)) + if (bbio->is_scrub || btrfs_is_data_reloc_root(inode->root)) smap.rst_search_commit_root = true; else smap.rst_search_commit_root = false; @@ -734,7 +737,7 @@ static bool btrfs_submit_chunk(struct btrfs_bio *bbio, int mirror_num) * Csum items for reloc roots have already been cloned at this * point, so they are handled as part of the no-checksum case. */ - if (inode && !(inode->flags & BTRFS_INODE_NODATASUM) && + if (!(inode->flags & BTRFS_INODE_NODATASUM) && !test_bit(BTRFS_FS_STATE_NO_DATA_CSUMS, &fs_info->fs_state) && !btrfs_is_data_reloc_root(inode->root)) { if (should_async_write(bbio) && @@ -782,7 +785,7 @@ static bool btrfs_submit_chunk(struct btrfs_bio *bbio, int mirror_num) static void assert_bbio_alignment(struct btrfs_bio *bbio) { #ifdef CONFIG_BTRFS_ASSERT - struct btrfs_fs_info *fs_info = bbio->fs_info; + struct btrfs_fs_info *fs_info = bbio->inode->root->fs_info; struct bio_vec bvec; struct bvec_iter iter; const u32 blocksize = fs_info->sectorsize; @@ -885,16 +888,16 @@ int btrfs_repair_io_failure(struct btrfs_fs_info *fs_info, u64 ino, u64 start, */ void btrfs_submit_repair_write(struct btrfs_bio *bbio, int mirror_num, bool dev_replace) { - struct btrfs_fs_info *fs_info = bbio->fs_info; + struct btrfs_fs_info *fs_info = bbio->inode->root->fs_info; u64 logical = bbio->bio.bi_iter.bi_sector << SECTOR_SHIFT; u64 length = bbio->bio.bi_iter.bi_size; struct btrfs_io_stripe smap = { 0 }; int ret; - ASSERT(fs_info); ASSERT(mirror_num > 0); ASSERT(btrfs_op(&bbio->bio) == BTRFS_MAP_WRITE); - ASSERT(!bbio->inode); + ASSERT(!is_data_inode(bbio->inode)); + ASSERT(bbio->is_scrub); btrfs_bio_counter_inc_blocked(fs_info); ret = btrfs_map_repair_block(fs_info, &smap, logical, length, mirror_num); diff --git a/fs/btrfs/bio.h b/fs/btrfs/bio.h index 00883aea55d70f..b7a0de6f978406 100644 --- a/fs/btrfs/bio.h +++ b/fs/btrfs/bio.h @@ -34,7 +34,10 @@ typedef void (*btrfs_bio_end_io_t)(struct btrfs_bio *bbio); struct btrfs_bio { /* * Inode and offset into it that this I/O operates on. - * Only set for data I/O. + * + * If the inode is a data one, csum verification and read-repair + * will be done automatically. + * If the inode is a metadata one, everything is handled by the caller. */ struct btrfs_inode *inode; u64 file_offset; @@ -76,14 +79,17 @@ struct btrfs_bio { atomic_t pending_ios; struct work_struct end_io_work; - /* File system that this I/O operates on. */ - struct btrfs_fs_info *fs_info; - /* Save the first error status of split bio. */ blk_status_t status; /* Use the commit root to look up csums (data read bio only). */ bool csum_search_commit_root; + + /* + * Since scrub will reuse btree inode, we need this flag to distinguish + * scrub bios. + */ + bool is_scrub; /* * This member must come last, bio_alloc_bioset will allocate enough * bytes for entire btrfs_bio but relies on bio being last. @@ -99,10 +105,10 @@ static inline struct btrfs_bio *btrfs_bio(struct bio *bio) int __init btrfs_bioset_init(void); void __cold btrfs_bioset_exit(void); -void btrfs_bio_init(struct btrfs_bio *bbio, struct btrfs_fs_info *fs_info, +void btrfs_bio_init(struct btrfs_bio *bbio, struct btrfs_inode *inode, u64 file_offset, btrfs_bio_end_io_t end_io, void *private); struct btrfs_bio *btrfs_bio_alloc(unsigned int nr_vecs, blk_opf_t opf, - struct btrfs_fs_info *fs_info, + struct btrfs_inode *inode, u64 file_offset, btrfs_bio_end_io_t end_io, void *private); void btrfs_bio_end_io(struct btrfs_bio *bbio, blk_status_t status); diff --git a/fs/btrfs/compression.c b/fs/btrfs/compression.c index bacad18357b338..8c3899832a1aa1 100644 --- a/fs/btrfs/compression.c +++ b/fs/btrfs/compression.c @@ -67,9 +67,7 @@ static struct compressed_bio *alloc_compressed_bio(struct btrfs_inode *inode, bbio = btrfs_bio(bio_alloc_bioset(NULL, BTRFS_MAX_COMPRESSED_PAGES, op, GFP_NOFS, &btrfs_compressed_bioset)); - btrfs_bio_init(bbio, inode->root->fs_info, end_io, NULL); - bbio->inode = inode; - bbio->file_offset = start; + btrfs_bio_init(bbio, inode, start, end_io, NULL); return to_compressed_bio(bbio); } @@ -354,7 +352,7 @@ static void end_bbio_compressed_write(struct btrfs_bio *bbio) static void btrfs_add_compressed_bio_folios(struct compressed_bio *cb) { - struct btrfs_fs_info *fs_info = cb->bbio.fs_info; + struct btrfs_fs_info *fs_info = cb->bbio.inode->root->fs_info; struct bio *bio = &cb->bbio.bio; u32 offset = 0; diff --git a/fs/btrfs/compression.h b/fs/btrfs/compression.h index c6812d5fcab791..062ebd9c2d32d1 100644 --- a/fs/btrfs/compression.h +++ b/fs/btrfs/compression.h @@ -14,6 +14,7 @@ #include #include "bio.h" #include "fs.h" +#include "btrfs_inode.h" struct address_space; struct inode; @@ -74,7 +75,7 @@ struct compressed_bio { static inline struct btrfs_fs_info *cb_to_fs_info(const struct compressed_bio *cb) { - return cb->bbio.fs_info; + return cb->bbio.inode->root->fs_info; } /* @range_end must be exclusive. */ diff --git a/fs/btrfs/direct-io.c b/fs/btrfs/direct-io.c index 8888ef4ae6064b..e29ea28ce90b9a 100644 --- a/fs/btrfs/direct-io.c +++ b/fs/btrfs/direct-io.c @@ -715,10 +715,8 @@ static void btrfs_dio_submit_io(const struct iomap_iter *iter, struct bio *bio, container_of(bbio, struct btrfs_dio_private, bbio); struct btrfs_dio_data *dio_data = iter->private; - btrfs_bio_init(bbio, BTRFS_I(iter->inode)->root->fs_info, + btrfs_bio_init(bbio, BTRFS_I(iter->inode), file_offset, btrfs_dio_end_io, bio->bi_private); - bbio->inode = BTRFS_I(iter->inode); - bbio->file_offset = file_offset; dip->file_offset = file_offset; dip->bytes = bio->bi_iter.bi_size; diff --git a/fs/btrfs/extent_io.c b/fs/btrfs/extent_io.c index e6ffa12f575352..c3524401ff03eb 100644 --- a/fs/btrfs/extent_io.c +++ b/fs/btrfs/extent_io.c @@ -518,7 +518,7 @@ static void end_folio_read(struct folio *folio, bool uptodate, u64 start, u32 le */ static void end_bbio_data_write(struct btrfs_bio *bbio) { - struct btrfs_fs_info *fs_info = bbio->fs_info; + struct btrfs_fs_info *fs_info = bbio->inode->root->fs_info; struct bio *bio = &bbio->bio; int error = blk_status_to_errno(bio->bi_status); struct folio_iter fi; @@ -574,7 +574,7 @@ static void begin_folio_read(struct btrfs_fs_info *fs_info, struct folio *folio) */ static void end_bbio_data_read(struct btrfs_bio *bbio) { - struct btrfs_fs_info *fs_info = bbio->fs_info; + struct btrfs_fs_info *fs_info = bbio->inode->root->fs_info; struct bio *bio = &bbio->bio; struct folio_iter fi; @@ -739,12 +739,10 @@ static void alloc_new_bio(struct btrfs_inode *inode, struct btrfs_fs_info *fs_info = inode->root->fs_info; struct btrfs_bio *bbio; - bbio = btrfs_bio_alloc(BIO_MAX_VECS, bio_ctrl->opf, fs_info, - bio_ctrl->end_io_func, NULL); + bbio = btrfs_bio_alloc(BIO_MAX_VECS, bio_ctrl->opf, inode, + file_offset, bio_ctrl->end_io_func, NULL); bbio->bio.bi_iter.bi_sector = disk_bytenr >> SECTOR_SHIFT; bbio->bio.bi_write_hint = inode->vfs_inode.i_write_hint; - bbio->inode = inode; - bbio->file_offset = file_offset; bio_ctrl->bbio = bbio; bio_ctrl->len_to_oe_boundary = U32_MAX; bio_ctrl->next_file_offset = file_offset; @@ -2223,12 +2221,11 @@ static noinline_for_stack void write_one_eb(struct extent_buffer *eb, bbio = btrfs_bio_alloc(INLINE_EXTENT_BUFFER_PAGES, REQ_OP_WRITE | REQ_META | wbc_to_write_flags(wbc), - eb->fs_info, end_bbio_meta_write, eb); + BTRFS_I(fs_info->btree_inode), eb->start, + end_bbio_meta_write, eb); bbio->bio.bi_iter.bi_sector = eb->start >> SECTOR_SHIFT; bio_set_dev(&bbio->bio, fs_info->fs_devices->latest_dev->bdev); wbc_init_bio(wbc, &bbio->bio); - bbio->inode = BTRFS_I(eb->fs_info->btree_inode); - bbio->file_offset = eb->start; for (int i = 0; i < num_extent_folios(eb); i++) { struct folio *folio = eb->folios[i]; u64 range_start = max_t(u64, eb->start, folio_pos(folio)); @@ -3842,6 +3839,7 @@ static void end_bbio_meta_read(struct btrfs_bio *bbio) int read_extent_buffer_pages_nowait(struct extent_buffer *eb, int mirror_num, const struct btrfs_tree_parent_check *check) { + struct btrfs_fs_info *fs_info = eb->fs_info; struct btrfs_bio *bbio; if (test_bit(EXTENT_BUFFER_UPTODATE, &eb->bflags)) @@ -3875,11 +3873,9 @@ int read_extent_buffer_pages_nowait(struct extent_buffer *eb, int mirror_num, refcount_inc(&eb->refs); bbio = btrfs_bio_alloc(INLINE_EXTENT_BUFFER_PAGES, - REQ_OP_READ | REQ_META, eb->fs_info, - end_bbio_meta_read, eb); + REQ_OP_READ | REQ_META, BTRFS_I(fs_info->btree_inode), + eb->start, end_bbio_meta_read, eb); bbio->bio.bi_iter.bi_sector = eb->start >> SECTOR_SHIFT; - bbio->inode = BTRFS_I(eb->fs_info->btree_inode); - bbio->file_offset = eb->start; memcpy(&bbio->parent_check, check, sizeof(*check)); for (int i = 0; i < num_extent_folios(eb); i++) { struct folio *folio = eb->folios[i]; diff --git a/fs/btrfs/inode.c b/fs/btrfs/inode.c index b261dbeb290401..47e762856521d3 100644 --- a/fs/btrfs/inode.c +++ b/fs/btrfs/inode.c @@ -9423,7 +9423,6 @@ int btrfs_encoded_read_regular_fill_pages(struct btrfs_inode *inode, u64 disk_bytenr, u64 disk_io_size, struct page **pages, void *uring_ctx) { - struct btrfs_fs_info *fs_info = inode->root->fs_info; struct btrfs_encoded_read_private *priv, sync_priv; struct completion sync_reads; unsigned long i = 0; @@ -9448,10 +9447,9 @@ int btrfs_encoded_read_regular_fill_pages(struct btrfs_inode *inode, priv->status = 0; priv->uring_ctx = uring_ctx; - bbio = btrfs_bio_alloc(BIO_MAX_VECS, REQ_OP_READ, fs_info, + bbio = btrfs_bio_alloc(BIO_MAX_VECS, REQ_OP_READ, inode, 0, btrfs_encoded_read_endio, priv); bbio->bio.bi_iter.bi_sector = disk_bytenr >> SECTOR_SHIFT; - bbio->inode = inode; do { size_t bytes = min_t(u64, disk_io_size, PAGE_SIZE); @@ -9460,10 +9458,9 @@ int btrfs_encoded_read_regular_fill_pages(struct btrfs_inode *inode, refcount_inc(&priv->pending_refs); btrfs_submit_bbio(bbio, 0); - bbio = btrfs_bio_alloc(BIO_MAX_VECS, REQ_OP_READ, fs_info, + bbio = btrfs_bio_alloc(BIO_MAX_VECS, REQ_OP_READ, inode, 0, btrfs_encoded_read_endio, priv); bbio->bio.bi_iter.bi_sector = disk_bytenr >> SECTOR_SHIFT; - bbio->inode = inode; continue; } diff --git a/fs/btrfs/scrub.c b/fs/btrfs/scrub.c index b6a7ea105eb134..747e2c748376a3 100644 --- a/fs/btrfs/scrub.c +++ b/fs/btrfs/scrub.c @@ -929,10 +929,11 @@ static int calc_next_mirror(int mirror, int num_copies) static void scrub_bio_add_sector(struct btrfs_bio *bbio, struct scrub_stripe *stripe, int sector_nr) { + struct btrfs_fs_info *fs_info = bbio->inode->root->fs_info; void *kaddr = scrub_stripe_get_kaddr(stripe, sector_nr); int ret; - ret = bio_add_page(&bbio->bio, virt_to_page(kaddr), bbio->fs_info->sectorsize, + ret = bio_add_page(&bbio->bio, virt_to_page(kaddr), fs_info->sectorsize, offset_in_page(kaddr)); /* * Caller should ensure the bbio has enough size. @@ -942,7 +943,21 @@ static void scrub_bio_add_sector(struct btrfs_bio *bbio, struct scrub_stripe *st * to create the minimal amount of bio vectors, for fs block size < page * size cases. */ - ASSERT(ret == bbio->fs_info->sectorsize); + ASSERT(ret == fs_info->sectorsize); +} + +static struct btrfs_bio *alloc_scrub_bbio(struct btrfs_fs_info *fs_info, + unsigned int nr_vecs, blk_opf_t opf, + u64 logical, + btrfs_bio_end_io_t end_io, void *private) +{ + struct btrfs_bio *bbio; + + bbio = btrfs_bio_alloc(nr_vecs, opf, BTRFS_I(fs_info->btree_inode), + logical, end_io, private); + bbio->is_scrub = true; + bbio->bio.bi_iter.bi_sector = logical >> SECTOR_SHIFT; + return bbio; } static void scrub_stripe_submit_repair_read(struct scrub_stripe *stripe, @@ -968,12 +983,10 @@ static void scrub_stripe_submit_repair_read(struct scrub_stripe *stripe, bbio = NULL; } - if (!bbio) { - bbio = btrfs_bio_alloc(stripe->nr_sectors, REQ_OP_READ, - fs_info, scrub_repair_read_endio, stripe); - bbio->bio.bi_iter.bi_sector = (stripe->logical + - (i << fs_info->sectorsize_bits)) >> SECTOR_SHIFT; - } + if (!bbio) + bbio = alloc_scrub_bbio(fs_info, stripe->nr_sectors, REQ_OP_READ, + stripe->logical + (i << fs_info->sectorsize_bits), + scrub_repair_read_endio, stripe); scrub_bio_add_sector(bbio, stripe, i); } @@ -1352,13 +1365,10 @@ static void scrub_write_sectors(struct scrub_ctx *sctx, struct scrub_stripe *str scrub_submit_write_bio(sctx, stripe, bbio, dev_replace); bbio = NULL; } - if (!bbio) { - bbio = btrfs_bio_alloc(stripe->nr_sectors, REQ_OP_WRITE, - fs_info, scrub_write_endio, stripe); - bbio->bio.bi_iter.bi_sector = (stripe->logical + - (sector_nr << fs_info->sectorsize_bits)) >> - SECTOR_SHIFT; - } + if (!bbio) + bbio = alloc_scrub_bbio(fs_info, stripe->nr_sectors, REQ_OP_WRITE, + stripe->logical + (sector_nr << fs_info->sectorsize_bits), + scrub_write_endio, stripe); scrub_bio_add_sector(bbio, stripe, sector_nr); } if (bbio) @@ -1849,9 +1859,8 @@ static void scrub_submit_extent_sector_read(struct scrub_stripe *stripe) continue; } - bbio = btrfs_bio_alloc(stripe->nr_sectors, REQ_OP_READ, - fs_info, scrub_read_endio, stripe); - bbio->bio.bi_iter.bi_sector = logical >> SECTOR_SHIFT; + bbio = alloc_scrub_bbio(fs_info, stripe->nr_sectors, REQ_OP_READ, + logical, scrub_read_endio, stripe); } scrub_bio_add_sector(bbio, stripe, i); @@ -1888,10 +1897,8 @@ static void scrub_submit_initial_read(struct scrub_ctx *sctx, return; } - bbio = btrfs_bio_alloc(BTRFS_STRIPE_LEN >> min_folio_shift, REQ_OP_READ, fs_info, - scrub_read_endio, stripe); - - bbio->bio.bi_iter.bi_sector = stripe->logical >> SECTOR_SHIFT; + bbio = alloc_scrub_bbio(fs_info, BTRFS_STRIPE_LEN >> min_folio_shift, REQ_OP_READ, + stripe->logical, scrub_read_endio, stripe); /* Read the whole range inside the chunk boundary. */ for (unsigned int cur = 0; cur < nr_sectors; cur++) scrub_bio_add_sector(bbio, stripe, cur); diff --git a/fs/btrfs/zoned.c b/fs/btrfs/zoned.c index d1db7fa1fe583a..3afc9c0c222878 100644 --- a/fs/btrfs/zoned.c +++ b/fs/btrfs/zoned.c @@ -1809,14 +1809,14 @@ bool btrfs_use_zone_append(struct btrfs_bio *bbio) { u64 start = (bbio->bio.bi_iter.bi_sector << SECTOR_SHIFT); struct btrfs_inode *inode = bbio->inode; - struct btrfs_fs_info *fs_info = bbio->fs_info; + struct btrfs_fs_info *fs_info = inode->root->fs_info; struct btrfs_block_group *cache; bool ret = false; if (!btrfs_is_zoned(fs_info)) return false; - if (!inode || !is_data_inode(inode)) + if (!is_data_inode(inode)) return false; if (btrfs_op(&bbio->bio) != BTRFS_MAP_WRITE) From 0981c6b984a163f2232acfbf8e26b75146032511 Mon Sep 17 00:00:00 2001 From: Qu Wenruo Date: Thu, 23 Oct 2025 15:19:16 +1030 Subject: [PATCH 3276/3902] btrfs: make sure all btrfs_bio::end_io are called in task context [ Upstream commit 4591c3ef751d861d7dd95ff4d2aadb1b5e95854e ] [BACKGROUND] Btrfs has a lot of different bi_end_io functions, to handle different raid profiles. But they introduced a lot of different contexts for btrfs_bio::end_io() calls: - Simple read bios Run in task context, backed by either endio_meta_workers or endio_workers. - Simple write bios Run in IRQ context. - RAID56 write or rebuild bios Run in task context, backed by rmw_workers. - Mirrored write bios Run in irq context. This is inconsistent, and contributes to the number of workqueues used in btrfs. [ENHANCEMENT] Make all the above bios call their btrfs_bio::end_io() in task context, backed by either endio_meta_workers for metadata, or endio_workers for data. For simple write bios, merge the handling into simple_end_io_work(). For mirrored write bios, it will be a little more complex, since both the original or the cloned bios can run the final btrfs_bio::end_io(). Here we make sure the cloned bios are using btrfs_bioset, to reuse the end_io_work, and run both original and cloned work inside the workqueue. Add extra ASSERT()s to make sure btrfs_bio_end_io() is running in task context. This not only unifies the context for btrfs_bio::end_io() functions, but also opens a new door for further btrfs_bio::end_io() related cleanups. Signed-off-by: Qu Wenruo Reviewed-by: David Sterba Signed-off-by: David Sterba Stable-dep-of: b39b26e017c7 ("btrfs: zoned: don't zone append to conventional zone") Signed-off-by: Sasha Levin --- fs/btrfs/bio.c | 64 ++++++++++++++++++++++++++++++++++++-------------- 1 file changed, 46 insertions(+), 18 deletions(-) diff --git a/fs/btrfs/bio.c b/fs/btrfs/bio.c index b85b6b21b5450b..52b8893f26f164 100644 --- a/fs/btrfs/bio.c +++ b/fs/btrfs/bio.c @@ -102,6 +102,9 @@ static struct btrfs_bio *btrfs_split_bio(struct btrfs_fs_info *fs_info, void btrfs_bio_end_io(struct btrfs_bio *bbio, blk_status_t status) { + /* Make sure we're already in task context. */ + ASSERT(in_task()); + bbio->bio.bi_status = status; if (bbio->bio.bi_pool == &btrfs_clone_bioset) { struct btrfs_bio *orig_bbio = bbio->private; @@ -318,15 +321,20 @@ static struct workqueue_struct *btrfs_end_io_wq(const struct btrfs_fs_info *fs_i return fs_info->endio_workers; } -static void btrfs_end_bio_work(struct work_struct *work) +static void simple_end_io_work(struct work_struct *work) { struct btrfs_bio *bbio = container_of(work, struct btrfs_bio, end_io_work); + struct bio *bio = &bbio->bio; - /* Metadata reads are checked and repaired by the submitter. */ - if (is_data_bbio(bbio)) - btrfs_check_read_bio(bbio, bbio->bio.bi_private); - else - btrfs_bio_end_io(bbio, bbio->bio.bi_status); + if (bio_op(bio) == REQ_OP_READ) { + /* Metadata reads are checked and repaired by the submitter. */ + if (is_data_bbio(bbio)) + return btrfs_check_read_bio(bbio, bbio->bio.bi_private); + return btrfs_bio_end_io(bbio, bbio->bio.bi_status); + } + if (bio_is_zone_append(bio) && !bio->bi_status) + btrfs_record_physical_zoned(bbio); + btrfs_bio_end_io(bbio, bbio->bio.bi_status); } static void btrfs_simple_end_io(struct bio *bio) @@ -340,14 +348,8 @@ static void btrfs_simple_end_io(struct bio *bio) if (bio->bi_status) btrfs_log_dev_io_error(bio, dev); - if (bio_op(bio) == REQ_OP_READ) { - INIT_WORK(&bbio->end_io_work, btrfs_end_bio_work); - queue_work(btrfs_end_io_wq(fs_info, bio), &bbio->end_io_work); - } else { - if (bio_is_zone_append(bio) && !bio->bi_status) - btrfs_record_physical_zoned(bbio); - btrfs_bio_end_io(bbio, bbio->bio.bi_status); - } + INIT_WORK(&bbio->end_io_work, simple_end_io_work); + queue_work(btrfs_end_io_wq(fs_info, bio), &bbio->end_io_work); } static void btrfs_raid56_end_io(struct bio *bio) @@ -355,6 +357,9 @@ static void btrfs_raid56_end_io(struct bio *bio) struct btrfs_io_context *bioc = bio->bi_private; struct btrfs_bio *bbio = btrfs_bio(bio); + /* RAID56 endio is always handled in workqueue. */ + ASSERT(in_task()); + btrfs_bio_counter_dec(bioc->fs_info); bbio->mirror_num = bioc->mirror_num; if (bio_op(bio) == REQ_OP_READ && is_data_bbio(bbio)) @@ -365,11 +370,12 @@ static void btrfs_raid56_end_io(struct bio *bio) btrfs_put_bioc(bioc); } -static void btrfs_orig_write_end_io(struct bio *bio) +static void orig_write_end_io_work(struct work_struct *work) { + struct btrfs_bio *bbio = container_of(work, struct btrfs_bio, end_io_work); + struct bio *bio = &bbio->bio; struct btrfs_io_stripe *stripe = bio->bi_private; struct btrfs_io_context *bioc = stripe->bioc; - struct btrfs_bio *bbio = btrfs_bio(bio); btrfs_bio_counter_dec(bioc->fs_info); @@ -394,8 +400,18 @@ static void btrfs_orig_write_end_io(struct bio *bio) btrfs_put_bioc(bioc); } -static void btrfs_clone_write_end_io(struct bio *bio) +static void btrfs_orig_write_end_io(struct bio *bio) +{ + struct btrfs_bio *bbio = btrfs_bio(bio); + + INIT_WORK(&bbio->end_io_work, orig_write_end_io_work); + queue_work(btrfs_end_io_wq(bbio->inode->root->fs_info, bio), &bbio->end_io_work); +} + +static void clone_write_end_io_work(struct work_struct *work) { + struct btrfs_bio *bbio = container_of(work, struct btrfs_bio, end_io_work); + struct bio *bio = &bbio->bio; struct btrfs_io_stripe *stripe = bio->bi_private; if (bio->bi_status) { @@ -410,6 +426,14 @@ static void btrfs_clone_write_end_io(struct bio *bio) bio_put(bio); } +static void btrfs_clone_write_end_io(struct bio *bio) +{ + struct btrfs_bio *bbio = btrfs_bio(bio); + + INIT_WORK(&bbio->end_io_work, clone_write_end_io_work); + queue_work(btrfs_end_io_wq(bbio->inode->root->fs_info, bio), &bbio->end_io_work); +} + static void btrfs_submit_dev_bio(struct btrfs_device *dev, struct bio *bio) { if (!dev || !dev->bdev || @@ -456,6 +480,7 @@ static void btrfs_submit_dev_bio(struct btrfs_device *dev, struct bio *bio) static void btrfs_submit_mirrored_bio(struct btrfs_io_context *bioc, int dev_nr) { struct bio *orig_bio = bioc->orig_bio, *bio; + struct btrfs_bio *orig_bbio = btrfs_bio(orig_bio); ASSERT(bio_op(orig_bio) != REQ_OP_READ); @@ -464,8 +489,11 @@ static void btrfs_submit_mirrored_bio(struct btrfs_io_context *bioc, int dev_nr) bio = orig_bio; bio->bi_end_io = btrfs_orig_write_end_io; } else { - bio = bio_alloc_clone(NULL, orig_bio, GFP_NOFS, &fs_bio_set); + /* We need to use endio_work to run end_io in task context. */ + bio = bio_alloc_clone(NULL, orig_bio, GFP_NOFS, &btrfs_bioset); bio_inc_remaining(orig_bio); + btrfs_bio_init(btrfs_bio(bio), orig_bbio->inode, + orig_bbio->file_offset, NULL, NULL); bio->bi_end_io = btrfs_clone_write_end_io; } From 9643b3baa46b6731efe7e851143a97df54135778 Mon Sep 17 00:00:00 2001 From: Qu Wenruo Date: Fri, 24 Oct 2025 15:08:34 +1030 Subject: [PATCH 3277/3902] btrfs: introduce btrfs_bio::async_csum [ Upstream commit dd57c78aec398717a2fa6488d87b1a6cd43c7d0d ] [ENHANCEMENT] Btrfs currently calculates data checksums then submits the bio. But after commit 968f19c5b1b7 ("btrfs: always fallback to buffered write if the inode requires checksum"), any writes with data checksum will fallback to buffered IO, meaning the content will not change during writeback. This means we're safe to calculate the data checksum and submit the bio in parallel, and only need the following new behavior: - Wait the csum generation to finish before calling btrfs_bio::end_io() Or this can lead to use-after-free for the csum generation worker. - Save the current bi_iter for csum_one_bio() As the submission part can advance btrfs_bio::bio.bi_iter, if not saved csum_one_bio() may got an empty bi_iter and do not generate any checksum. Unfortunately this means we have to increase the size of btrfs_bio for 16 bytes, but this is still acceptable. As usual, such new feature is hidden behind the experimental flag. [THEORETIC ANALYZE] Consider the following theoretic hardware performance, which should be more or less close to modern mainstream hardware: Memory bandwidth: 50GiB/s CRC32C bandwidth: 45GiB/s SSD bandwidth: 8GiB/s Then write bandwidth with data checksum before the patch is: 1 / ( 1 / 50 + 1 / 45 + 1 / 8) = 5.98 GiB/s After the patch, the bandwidth is: 1 / ( 1 / 50 + max( 1 / 45 + 1 / 8)) = 6.90 GiB/s The difference is 15.32% improvement. [REAL WORLD BENCHMARK] I'm using a Zen5 (HX 370) as the host, the VM has 4GiB memory, 10 vCPUs, the storage is backed by a PCIe gen3 x4 NVMe. The test is a direct IO write, with 1MiB block size, write 7GiB data into a btrfs mount with data checksum. Thus the direct write will fallback to buffered one: Vanilla Datasum: 1619.97 GiB/s Patched Datasum: 1792.26 GiB/s Diff +10.6 % In my case, the bottleneck is the storage, thus the improvement is not reaching the theoretic one, but still some observable improvement. Signed-off-by: Qu Wenruo Signed-off-by: David Sterba Stable-dep-of: b39b26e017c7 ("btrfs: zoned: don't zone append to conventional zone") Signed-off-by: Sasha Levin --- fs/btrfs/bio.c | 21 ++++++++++++---- fs/btrfs/bio.h | 7 ++++++ fs/btrfs/file-item.c | 60 +++++++++++++++++++++++++++++++------------- fs/btrfs/file-item.h | 2 +- 4 files changed, 67 insertions(+), 23 deletions(-) diff --git a/fs/btrfs/bio.c b/fs/btrfs/bio.c index 52b8893f26f164..1286c1ac19404e 100644 --- a/fs/btrfs/bio.c +++ b/fs/btrfs/bio.c @@ -105,6 +105,9 @@ void btrfs_bio_end_io(struct btrfs_bio *bbio, blk_status_t status) /* Make sure we're already in task context. */ ASSERT(in_task()); + if (bbio->async_csum) + wait_for_completion(&bbio->csum_done); + bbio->bio.bi_status = status; if (bbio->bio.bi_pool == &btrfs_clone_bioset) { struct btrfs_bio *orig_bbio = bbio->private; @@ -538,7 +541,11 @@ static int btrfs_bio_csum(struct btrfs_bio *bbio) { if (bbio->bio.bi_opf & REQ_META) return btree_csum_one_bio(bbio); - return btrfs_csum_one_bio(bbio); +#ifdef CONFIG_BTRFS_EXPERIMENTAL + return btrfs_csum_one_bio(bbio, true); +#else + return btrfs_csum_one_bio(bbio, false); +#endif } /* @@ -617,10 +624,14 @@ static bool should_async_write(struct btrfs_bio *bbio) struct btrfs_fs_devices *fs_devices = fs_info->fs_devices; enum btrfs_offload_csum_mode csum_mode = READ_ONCE(fs_devices->offload_csum_mode); - if (csum_mode == BTRFS_OFFLOAD_CSUM_FORCE_OFF) - return false; - - auto_csum_mode = (csum_mode == BTRFS_OFFLOAD_CSUM_AUTO); + if (csum_mode == BTRFS_OFFLOAD_CSUM_FORCE_ON) + return true; + /* + * Write bios will calculate checksum and submit bio at the same time. + * Unless explicitly required don't offload serial csum calculate and bio + * submit into a workqueue. + */ + return false; #endif /* Submit synchronously if the checksum implementation is fast. */ diff --git a/fs/btrfs/bio.h b/fs/btrfs/bio.h index b7a0de6f978406..9a44b86d561b17 100644 --- a/fs/btrfs/bio.h +++ b/fs/btrfs/bio.h @@ -63,6 +63,9 @@ struct btrfs_bio { struct { struct btrfs_ordered_extent *ordered; struct btrfs_ordered_sum *sums; + struct work_struct csum_work; + struct completion csum_done; + struct bvec_iter csum_saved_iter; u64 orig_physical; }; @@ -90,6 +93,10 @@ struct btrfs_bio { * scrub bios. */ bool is_scrub; + + /* Whether the csum generation for data write is async. */ + bool async_csum; + /* * This member must come last, bio_alloc_bioset will allocate enough * bytes for entire btrfs_bio but relies on bio being last. diff --git a/fs/btrfs/file-item.c b/fs/btrfs/file-item.c index a42e6d54e7cd74..4b7c40f05e8f9e 100644 --- a/fs/btrfs/file-item.c +++ b/fs/btrfs/file-item.c @@ -18,6 +18,7 @@ #include "fs.h" #include "accessors.h" #include "file-item.h" +#include "volumes.h" #define __MAX_CSUM_ITEMS(r, size) ((unsigned long)(((BTRFS_LEAF_DATA_SIZE(r) - \ sizeof(struct btrfs_item) * 2) / \ @@ -764,21 +765,46 @@ int btrfs_lookup_csums_bitmap(struct btrfs_root *root, struct btrfs_path *path, return ret; } +static void csum_one_bio(struct btrfs_bio *bbio, struct bvec_iter *src) +{ + struct btrfs_inode *inode = bbio->inode; + struct btrfs_fs_info *fs_info = inode->root->fs_info; + SHASH_DESC_ON_STACK(shash, fs_info->csum_shash); + struct bio *bio = &bbio->bio; + struct btrfs_ordered_sum *sums = bbio->sums; + struct bvec_iter iter = *src; + phys_addr_t paddr; + const u32 blocksize = fs_info->sectorsize; + int index = 0; + + shash->tfm = fs_info->csum_shash; + + btrfs_bio_for_each_block(paddr, bio, &iter, blocksize) { + btrfs_calculate_block_csum(fs_info, paddr, sums->sums + index); + index += fs_info->csum_size; + } +} + +static void csum_one_bio_work(struct work_struct *work) +{ + struct btrfs_bio *bbio = container_of(work, struct btrfs_bio, csum_work); + + ASSERT(btrfs_op(&bbio->bio) == BTRFS_MAP_WRITE); + ASSERT(bbio->async_csum == true); + csum_one_bio(bbio, &bbio->csum_saved_iter); + complete(&bbio->csum_done); +} + /* * Calculate checksums of the data contained inside a bio. */ -int btrfs_csum_one_bio(struct btrfs_bio *bbio) +int btrfs_csum_one_bio(struct btrfs_bio *bbio, bool async) { struct btrfs_ordered_extent *ordered = bbio->ordered; struct btrfs_inode *inode = bbio->inode; struct btrfs_fs_info *fs_info = inode->root->fs_info; - SHASH_DESC_ON_STACK(shash, fs_info->csum_shash); struct bio *bio = &bbio->bio; struct btrfs_ordered_sum *sums; - struct bvec_iter iter = bio->bi_iter; - phys_addr_t paddr; - const u32 blocksize = fs_info->sectorsize; - int index; unsigned nofs_flag; nofs_flag = memalloc_nofs_save(); @@ -789,21 +815,21 @@ int btrfs_csum_one_bio(struct btrfs_bio *bbio) if (!sums) return -ENOMEM; + sums->logical = bio->bi_iter.bi_sector << SECTOR_SHIFT; sums->len = bio->bi_iter.bi_size; INIT_LIST_HEAD(&sums->list); - - sums->logical = bio->bi_iter.bi_sector << SECTOR_SHIFT; - index = 0; - - shash->tfm = fs_info->csum_shash; - - btrfs_bio_for_each_block(paddr, bio, &iter, blocksize) { - btrfs_calculate_block_csum(fs_info, paddr, sums->sums + index); - index += fs_info->csum_size; - } - bbio->sums = sums; btrfs_add_ordered_sum(ordered, sums); + + if (!async) { + csum_one_bio(bbio, &bbio->bio.bi_iter); + return 0; + } + init_completion(&bbio->csum_done); + bbio->async_csum = true; + bbio->csum_saved_iter = bbio->bio.bi_iter; + INIT_WORK(&bbio->csum_work, csum_one_bio_work); + schedule_work(&bbio->csum_work); return 0; } diff --git a/fs/btrfs/file-item.h b/fs/btrfs/file-item.h index 0d59e830018a63..5645c5e3abdb79 100644 --- a/fs/btrfs/file-item.h +++ b/fs/btrfs/file-item.h @@ -64,7 +64,7 @@ int btrfs_lookup_file_extent(struct btrfs_trans_handle *trans, int btrfs_csum_file_blocks(struct btrfs_trans_handle *trans, struct btrfs_root *root, struct btrfs_ordered_sum *sums); -int btrfs_csum_one_bio(struct btrfs_bio *bbio); +int btrfs_csum_one_bio(struct btrfs_bio *bbio, bool async); int btrfs_alloc_dummy_sum(struct btrfs_bio *bbio); int btrfs_lookup_csums_range(struct btrfs_root *root, u64 start, u64 end, struct list_head *list, int search_commit, From f957ddc4d8eb951ca03acee1c5bcdea1a73cb449 Mon Sep 17 00:00:00 2001 From: Johannes Thumshirn Date: Thu, 4 Dec 2025 13:42:23 +0100 Subject: [PATCH 3278/3902] btrfs: zoned: don't zone append to conventional zone [ Upstream commit b39b26e017c7889181cb84032e22bef72e81cf29 ] In case of a zoned RAID, it can happen that a data write is targeting a sequential write required zone and a conventional zone. In this case the bio will be marked as REQ_OP_ZONE_APPEND but for the conventional zone, this needs to be REQ_OP_WRITE. The setting of REQ_OP_ZONE_APPEND is deferred to the last possible time in btrfs_submit_dev_bio(), but the decision if we can use zone append is cached in btrfs_bio. CC: Naohiro Aota Fixes: e9b9b911e03c ("btrfs: add raid stripe tree to features enabled with debug config") Reviewed-by: Christoph Hellwig Reviewed-by: Naohiro Aota Signed-off-by: Johannes Thumshirn Signed-off-by: David Sterba Signed-off-by: Sasha Levin --- fs/btrfs/bio.c | 19 +++++++++---------- fs/btrfs/bio.h | 3 +++ 2 files changed, 12 insertions(+), 10 deletions(-) diff --git a/fs/btrfs/bio.c b/fs/btrfs/bio.c index 1286c1ac19404e..7d8aaa019c8ce6 100644 --- a/fs/btrfs/bio.c +++ b/fs/btrfs/bio.c @@ -439,6 +439,8 @@ static void btrfs_clone_write_end_io(struct bio *bio) static void btrfs_submit_dev_bio(struct btrfs_device *dev, struct bio *bio) { + u64 physical = bio->bi_iter.bi_sector << SECTOR_SHIFT; + if (!dev || !dev->bdev || test_bit(BTRFS_DEV_STATE_MISSING, &dev->dev_state) || (btrfs_op(bio) == BTRFS_MAP_WRITE && @@ -453,12 +455,13 @@ static void btrfs_submit_dev_bio(struct btrfs_device *dev, struct bio *bio) * For zone append writing, bi_sector must point the beginning of the * zone */ - if (bio_op(bio) == REQ_OP_ZONE_APPEND) { - u64 physical = bio->bi_iter.bi_sector << SECTOR_SHIFT; + if (btrfs_bio(bio)->can_use_append && btrfs_dev_is_sequential(dev, physical)) { u64 zone_start = round_down(physical, dev->fs_info->zone_size); ASSERT(btrfs_dev_is_sequential(dev, physical)); bio->bi_iter.bi_sector = zone_start >> SECTOR_SHIFT; + bio->bi_opf &= ~REQ_OP_WRITE; + bio->bi_opf |= REQ_OP_ZONE_APPEND; } btrfs_debug(dev->fs_info, "%s: rw %d 0x%x, sector=%llu, dev=%lu (%s id %llu), size=%u", @@ -706,7 +709,6 @@ static bool btrfs_submit_chunk(struct btrfs_bio *bbio, int mirror_num) u64 logical = bio->bi_iter.bi_sector << SECTOR_SHIFT; u64 length = bio->bi_iter.bi_size; u64 map_length = length; - bool use_append = btrfs_use_zone_append(bbio); struct btrfs_io_context *bioc = NULL; struct btrfs_io_stripe smap; blk_status_t status; @@ -726,8 +728,10 @@ static bool btrfs_submit_chunk(struct btrfs_bio *bbio, int mirror_num) goto end_bbio; } + bbio->can_use_append = btrfs_use_zone_append(bbio); + map_length = min(map_length, length); - if (use_append) + if (bbio->can_use_append) map_length = btrfs_append_map_length(bbio, map_length); if (map_length < length) { @@ -756,11 +760,6 @@ static bool btrfs_submit_chunk(struct btrfs_bio *bbio, int mirror_num) } if (btrfs_op(bio) == BTRFS_MAP_WRITE) { - if (use_append) { - bio->bi_opf &= ~REQ_OP_WRITE; - bio->bi_opf |= REQ_OP_ZONE_APPEND; - } - if (is_data_bbio(bbio) && bioc && bioc->use_rst) { /* * No locking for the list update, as we only add to @@ -787,7 +786,7 @@ static bool btrfs_submit_chunk(struct btrfs_bio *bbio, int mirror_num) status = errno_to_blk_status(ret); if (status) goto fail; - } else if (use_append || + } else if (bbio->can_use_append || (btrfs_is_zoned(fs_info) && inode && inode->flags & BTRFS_INODE_NODATASUM)) { ret = btrfs_alloc_dummy_sum(bbio); diff --git a/fs/btrfs/bio.h b/fs/btrfs/bio.h index 9a44b86d561b17..69fe54f564fcb1 100644 --- a/fs/btrfs/bio.h +++ b/fs/btrfs/bio.h @@ -97,6 +97,9 @@ struct btrfs_bio { /* Whether the csum generation for data write is async. */ bool async_csum; + /* Whether the bio is written using zone append. */ + bool can_use_append; + /* * This member must come last, bio_alloc_bioset will allocate enough * bytes for entire btrfs_bio but relies on bio being last. From 00ea0370ba7bc669f03ae498246768cafa1ec800 Mon Sep 17 00:00:00 2001 From: Filipe Manana Date: Tue, 20 Jan 2026 19:35:23 +0000 Subject: [PATCH 3279/3902] btrfs: qgroup: return correct error when deleting qgroup relation item [ Upstream commit 51b1fcf71c88c3c89e7dcf07869c5de837b1f428 ] If we fail to delete the second qgroup relation item, we end up returning success or -ENOENT in case the first item does not exist, instead of returning the error from the second item deletion. Fixes: 73798c465b66 ("btrfs: qgroup: Try our best to delete qgroup relations") Reviewed-by: Johannes Thumshirn Signed-off-by: Filipe Manana Reviewed-by: David Sterba Signed-off-by: David Sterba Signed-off-by: Sasha Levin --- fs/btrfs/qgroup.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/fs/btrfs/qgroup.c b/fs/btrfs/qgroup.c index febc22d1b64878..7a1dd250e92c09 100644 --- a/fs/btrfs/qgroup.c +++ b/fs/btrfs/qgroup.c @@ -1627,8 +1627,10 @@ static int __del_qgroup_relation(struct btrfs_trans_handle *trans, u64 src, if (ret < 0 && ret != -ENOENT) goto out; ret2 = del_qgroup_relation_item(trans, dst, src); - if (ret2 < 0 && ret2 != -ENOENT) + if (ret2 < 0 && ret2 != -ENOENT) { + ret = ret2; goto out; + } /* At least one deletion succeeded, return 0 */ if (!ret || !ret2) From 80e1fda9c084dcf54819a12bc7682ec0afd2d8f4 Mon Sep 17 00:00:00 2001 From: Boris Burkov Date: Mon, 22 Dec 2025 16:15:44 -0800 Subject: [PATCH 3280/3902] btrfs: fix block_group_tree dirty_list corruption [ Upstream commit 3a1f4264daed4b419c325a7fe35e756cada3cf82 ] When the incompat flag EXTENT_TREE_V2 is set, we unconditionally add the block group tree to the switch_commits list before calling switch_commit_roots, as we do for the tree root and the chunk root. However, the block group tree uses normal root dirty tracking and in any transaction that does an allocation and dirties a block group, the block group root will already be linked to a list by the dirty_list field and this use of list_add_tail() is invalid and corrupts the prev/next members of block_group_root->dirty_list. This is apparent on a subsequent list_del on the prev if we enable CONFIG_DEBUG_LIST: [32.1571] ------------[ cut here ]------------ [32.1572] list_del corruption. next->prev should beffff958890202538, but was ffff9588992bd538. (next=ffff958890201538) [32.1575] WARNING: lib/list_debug.c:65 at 0x0, CPU#3: sync/607 [32.1583] CPU: 3 UID: 0 PID: 607 Comm: sync Not tainted 6.18.0 #24PREEMPT(none) [32.1585] Hardware name: QEMU Standard PC (Q35 + ICH9, 2009), BIOS1.17.0-4.fc41 04/01/2014 [32.1587] RIP: 0010:__list_del_entry_valid_or_report+0x108/0x120 [32.1593] RSP: 0018:ffffaa288287fdd0 EFLAGS: 00010202 [32.1594] RAX: 0000000000000001 RBX: ffff95889326e800 RCX:ffff958890201538 [32.1596] RDX: ffff9588992bd538 RSI: ffff958890202538 RDI:ffffffff82a41e00 [32.1597] RBP: ffff958890202538 R08: ffffffff828fc1e8 R09:00000000ffffefff [32.1599] R10: ffffffff8288c200 R11: ffffffff828e4200 R12:ffff958890201538 [32.1601] R13: ffff95889326e958 R14: ffff958895c24000 R15:ffff958890202538 [32.1603] FS: 00007f0c28eb5740(0000) GS:ffff958af2bd2000(0000)knlGS:0000000000000000 [32.1605] CS: 0010 DS: 0000 ES: 0000 CR0: 0000000080050033 [32.1607] CR2: 00007f0c28e8a3cc CR3: 0000000109942005 CR4:0000000000370ef0 [32.1609] Call Trace: [32.1610] [32.1611] switch_commit_roots+0x82/0x1d0 [btrfs] [32.1615] btrfs_commit_transaction+0x968/0x1550 [btrfs] [32.1618] ? btrfs_attach_transaction_barrier+0x23/0x60 [btrfs] [32.1621] __iterate_supers+0xe8/0x190 [32.1622] ? __pfx_sync_fs_one_sb+0x10/0x10 [32.1623] ksys_sync+0x63/0xb0 [32.1624] __do_sys_sync+0xe/0x20 [32.1625] do_syscall_64+0x73/0x450 [32.1626] entry_SYSCALL_64_after_hwframe+0x76/0x7e [32.1627] RIP: 0033:0x7f0c28d05d2b [32.1632] RSP: 002b:00007ffc9d988048 EFLAGS: 00000246 ORIG_RAX:00000000000000a2 [32.1634] RAX: ffffffffffffffda RBX: 00007ffc9d988228 RCX:00007f0c28d05d2b [32.1636] RDX: 00007f0c28e02301 RSI: 00007ffc9d989b21 RDI:00007f0c28dba90d [32.1637] RBP: 0000000000000001 R08: 0000000000000001 R09:0000000000000000 [32.1639] R10: 0000000000000000 R11: 0000000000000246 R12:000055b96572cb80 [32.1641] R13: 000055b96572b19f R14: 00007f0c28dfa434 R15:000055b96572b034 [32.1643] [32.1644] irq event stamp: 0 [32.1644] hardirqs last enabled at (0): [<0000000000000000>] 0x0 [32.1646] hardirqs last disabled at (0): []copy_process+0xb37/0x2260 [32.1648] softirqs last enabled at (0): []copy_process+0xb37/0x2260 [32.1650] softirqs last disabled at (0): [<0000000000000000>] 0x0 [32.1652] ---[ end trace 0000000000000000 ]--- Furthermore, this list corruption eventually (when we happen to add a new block group) results in getting the switch_commits and dirty_cowonly_roots lists mixed up and attempting to call update_root on the tree root which can't be found in the tree root, resulting in a transaction abort: [87.8269] BTRFS critical (device nvme1n1): unable to find root key (1 0 0) in tree 1 [87.8272] ------------[ cut here ]------------ [87.8274] BTRFS: Transaction aborted (error -117) [87.8275] WARNING: fs/btrfs/root-tree.c:153 at 0x0, CPU#4: sync/703 [87.8285] CPU: 4 UID: 0 PID: 703 Comm: sync Not tainted 6.18.0 #25 PREEMPT(none) [87.8287] Hardware name: QEMU Standard PC (Q35 + ICH9, 2009), BIOS 1.17.0-4.fc41 04/01/2014 [87.8289] RIP: 0010:btrfs_update_root+0x296/0x790 [btrfs] [87.8295] RSP: 0018:ffffa58d035dfd60 EFLAGS: 00010282 [87.8297] RAX: ffff9a59126ddb68 RBX: ffff9a59126dc000 RCX: 0000000000000000 [87.8299] RDX: 0000000000000000 RSI: 00000000ffffff8b RDI: ffffffffc0b28270 [87.8301] RBP: ffff9a5904aec000 R08: 0000000000000000 R09: 00000000ffffefff [87.8303] R10: ffffffff9ac8c200 R11: ffffffff9ace4200 R12: 0000000000000001 [87.8305] R13: ffff9a59041740e8 R14: ffff9a5904aec1f7 R15: ffff9a590fdefaf0 [87.8307] FS: 00007f54cde6b740(0000) GS:ffff9a5b5a81c000(0000) knlGS:0000000000000000 [87.8309] CS: 0010 DS: 0000 ES: 0000 CR0: 0000000080050033 [87.8310] CR2: 00007f54cde403cc CR3: 0000000112902004 CR4: 0000000000370ef0 [87.8312] Call Trace: [87.8313] [87.8314] ? _raw_spin_unlock+0x23/0x40 [87.8315] commit_cowonly_roots+0x1ad/0x250 [btrfs] [87.8317] ? btrfs_commit_transaction+0x79b/0x1560 [btrfs] [87.8320] btrfs_commit_transaction+0x8aa/0x1560 [btrfs] [87.8322] ? btrfs_attach_transaction_barrier+0x23/0x60 [btrfs] [87.8325] __iterate_supers+0xf1/0x170 [87.8326] ? __pfx_sync_fs_one_sb+0x10/0x10 [87.8327] ksys_sync+0x63/0xb0 [87.8328] __do_sys_sync+0xe/0x20 [87.8329] do_syscall_64+0x73/0x450 [87.8330] entry_SYSCALL_64_after_hwframe+0x76/0x7e [87.8331] RIP: 0033:0x7f54cdd05d2b [87.8336] RSP: 002b:00007fff1b58ff78 EFLAGS: 00000246 ORIG_RAX: 00000000000000a2 [87.8338] RAX: ffffffffffffffda RBX: 00007fff1b590158 RCX: 00007f54cdd05d2b [87.8340] RDX: 00007f54cde02301 RSI: 00007fff1b592b66 RDI: 00007f54cddba90d [87.8342] RBP: 0000000000000001 R08: 0000000000000001 R09: 0000000000000000 [87.8344] R10: 0000000000000000 R11: 0000000000000246 R12: 000055e07ca96b80 [87.8346] R13: 000055e07ca9519f R14: 00007f54cddfa434 R15: 000055e07ca95034 [87.8348] [87.8348] irq event stamp: 0 [87.8349] hardirqs last enabled at (0): [<0000000000000000>] 0x0 [87.8351] hardirqs last disabled at (0): [] copy_process+0xb37/0x21e0 [87.8353] softirqs last enabled at (0): [] copy_process+0xb37/0x21e0 [87.8355] softirqs last disabled at (0): [<0000000000000000>] 0x0 [87.8357] ---[ end trace 0000000000000000 ]--- [87.8358] BTRFS: error (device nvme1n1 state A) in btrfs_update_root:153: errno=-117 Filesystem corrupted [87.8360] BTRFS info (device nvme1n1 state EA): forced readonly [87.8362] BTRFS warning (device nvme1n1 state EA): Skipping commit of aborted transaction. [87.8364] BTRFS: error (device nvme1n1 state EA) in cleanup_transaction:2037: errno=-117 Filesystem corrupted Since the block group tree was pulled out of the extent tree and uses normal root dirty tracking, remove the offending extra list_add. This fixes the list corruption and the resulting fs corruption. Fixes: 14033b08a029 ("btrfs: don't save block group root into super block") Reviewed-by: Filipe Manana Signed-off-by: Boris Burkov Signed-off-by: David Sterba Signed-off-by: Sasha Levin --- fs/btrfs/transaction.c | 7 ------- 1 file changed, 7 deletions(-) diff --git a/fs/btrfs/transaction.c b/fs/btrfs/transaction.c index 041f4781956cf2..b537bba7678061 100644 --- a/fs/btrfs/transaction.c +++ b/fs/btrfs/transaction.c @@ -2484,13 +2484,6 @@ int btrfs_commit_transaction(struct btrfs_trans_handle *trans) list_add_tail(&fs_info->chunk_root->dirty_list, &cur_trans->switch_commits); - if (btrfs_fs_incompat(fs_info, EXTENT_TREE_V2)) { - btrfs_set_root_node(&fs_info->block_group_root->root_item, - fs_info->block_group_root->node); - list_add_tail(&fs_info->block_group_root->dirty_list, - &cur_trans->switch_commits); - } - switch_commit_roots(trans); ASSERT(list_empty(&cur_trans->dirty_bgs)); From 7d4eadee7042d27fcea659fcdd738f463a7d2e70 Mon Sep 17 00:00:00 2001 From: Boris Burkov Date: Thu, 29 Jan 2026 16:11:21 -0800 Subject: [PATCH 3281/3902] btrfs: fix EEXIST abort due to non-consecutive gaps in chunk allocation [ Upstream commit b14c5e04bd0f722ed631845599d52d03fcae1bc1 ] I have been observing a number of systems aborting at insert_dev_extents() in btrfs_create_pending_block_groups(). The following is a sample stack trace of such an abort coming from forced chunk allocation (typically behind CONFIG_BTRFS_EXPERIMENTAL) but this can theoretically happen to any DUP chunk allocation. [81.801] ------------[ cut here ]------------ [81.801] BTRFS: Transaction aborted (error -17) [81.801] WARNING: fs/btrfs/block-group.c:2876 at btrfs_create_pending_block_groups+0x721/0x770 [btrfs], CPU#1: bash/319 [81.802] Modules linked in: virtio_net btrfs xor zstd_compress raid6_pq null_blk [81.803] CPU: 1 UID: 0 PID: 319 Comm: bash Kdump: loaded Not tainted 6.19.0-rc6+ #319 NONE [81.803] Hardware name: QEMU Standard PC (i440FX + PIIX, 1996), BIOS Arch Linux 1.17.0-2-2 04/01/2014 [81.804] RIP: 0010:btrfs_create_pending_block_groups+0x723/0x770 [btrfs] [81.806] RSP: 0018:ffffa36241a6bce8 EFLAGS: 00010282 [81.806] RAX: 000000000000000d RBX: ffff8e699921e400 RCX: 0000000000000000 [81.807] RDX: 0000000002040001 RSI: 00000000ffffffef RDI: ffffffffc0608bf0 [81.807] RBP: 00000000ffffffef R08: ffff8e69830f6000 R09: 0000000000000007 [81.808] R10: ffff8e699921e5e8 R11: 0000000000000000 R12: ffff8e6999228000 [81.808] R13: ffff8e6984d82000 R14: ffff8e69966a69c0 R15: ffff8e69aa47b000 [81.809] FS: 00007fec6bdd9740(0000) GS:ffff8e6b1b379000(0000) knlGS:0000000000000000 [81.809] CS: 0010 DS: 0000 ES: 0000 CR0: 0000000080050033 [81.810] CR2: 00005604833670f0 CR3: 0000000116679000 CR4: 00000000000006f0 [81.810] Call Trace: [81.810] [81.810] __btrfs_end_transaction+0x3e/0x2b0 [btrfs] [81.811] btrfs_force_chunk_alloc_store+0xcd/0x140 [btrfs] [81.811] kernfs_fop_write_iter+0x15f/0x240 [81.812] vfs_write+0x264/0x500 [81.812] ksys_write+0x6c/0xe0 [81.812] do_syscall_64+0x66/0x770 [81.812] entry_SYSCALL_64_after_hwframe+0x76/0x7e [81.813] RIP: 0033:0x7fec6be66197 [81.814] RSP: 002b:00007fffb159dd30 EFLAGS: 00000202 ORIG_RAX: 0000000000000001 [81.815] RAX: ffffffffffffffda RBX: 00007fec6bdd9740 RCX: 00007fec6be66197 [81.815] RDX: 0000000000000002 RSI: 0000560483374f80 RDI: 0000000000000001 [81.816] RBP: 0000560483374f80 R08: 0000000000000000 R09: 0000000000000000 [81.816] R10: 0000000000000000 R11: 0000000000000202 R12: 0000000000000002 [81.817] R13: 00007fec6bfb85c0 R14: 00007fec6bfb5ee0 R15: 00005604833729c0 [81.817] [81.817] irq event stamp: 20039 [81.818] hardirqs last enabled at (20047): [] __up_console_sem+0x52/0x60 [81.818] hardirqs last disabled at (20056): [] __up_console_sem+0x37/0x60 [81.819] softirqs last enabled at (19470): [] __irq_exit_rcu+0x96/0xc0 [81.819] softirqs last disabled at (19463): [] __irq_exit_rcu+0x96/0xc0 [81.820] ---[ end trace 0000000000000000 ]--- [81.820] BTRFS: error (device dm-7 state A) in btrfs_create_pending_block_groups:2876: errno=-17 Object already exists Inspecting these aborts with drgn, I observed a pattern of overlapping chunk_maps. Note how stripe 1 of the first chunk overlaps in physical address with stripe 0 of the second chunk. Physical Start Physical End Length Logical Type Stripe ---------------------------------------------------------------------------------------------------- 0x0000000102500000 0x0000000142500000 1.0G 0x0000000641d00000 META|DUP 0/2 0x0000000142500000 0x0000000182500000 1.0G 0x0000000641d00000 META|DUP 1/2 0x0000000142500000 0x0000000182500000 1.0G 0x0000000601d00000 META|DUP 0/2 0x0000000182500000 0x00000001c2500000 1.0G 0x0000000601d00000 META|DUP 1/2 Now how could this possibly happen? All chunk allocation is protected by the chunk_mutex so racing allocations should see a consistent view of the CHUNK_ALLOCATED bit in the chunk allocation extent-io-tree (device->alloc_state as set by chunk_map_device_set_bits()) The tree itself is protected by a spin lock, and clearing/setting the bits is always protected by fs_info->mapping_tree_lock, so no race is apparent. It turns out that there is a subtle bug in the logic regarding chunk allocations that have happened in the current transaction, known as "pending extents". The chunk allocation as defined in find_free_dev_extent() is a loop which searches the commit root of the dev_root and looks for gaps between DEV_EXTENT items. For those gaps, it then checks alloc_state bitmap for any pending extents and adjusts the hole that it finds accordingly. However, the logic in that adjustment assumes that the first pending extent is the only one in that range. e.g., given a layout with two non-consecutive pending extents in a hole passed to dev_extent_hole_check() via *hole_start and *hole_size: |----pending A----| real hole |----pending B----| | candidate hole | *hole_start *hole_start + *hole_size the code incorrectly returns a "hole" from the end of pending extent A until the passed in hole end, failing to account for pending B. However, it is not entirely obvious that it is actually possible to produce such a layout. I was able to reproduce it, but with some contortions: I continued to use the force chunk allocation sysfs file and I introduced a long delay (10 seconds) into the start of the cleaner thread. I also prevented the unused bgs cleaning logic from ever deleting metadata bgs. These help make it easier to deterministically produce the condition but shouldn't really matter if you imagine the conditions happening by race/luck. Allocations/frees can happen concurrently with the cleaner thread preparing to process an unused extent and both create some used chunks with an unused chunk interleaved, all during one transaction. Then btrfs_delete_unused_bgs() sees the unused one and clears it, leaving a range with several pending chunk allocations and a gap in the middle. The basic idea is that the unused_bgs cleanup work happens on a worker so if we allocate 3 block groups in one transaction, then the cleaner work kicked off by the previous transaction comes through and deletes the middle one of the 3, then the commit root shows no dev extents and we have the bad pattern in the extent-io-tree. One final consideration is that the code happens to loop to the next hole if there are no more extents at all, so we need one more dev extent way past the area we are working in. Something like the following demonstrates the technique: # push the BG frontier out to 20G fallocate -l 20G $mnt/foo # allocate one more that will prevent the "no more dev extents" luck fallocate -l 1G $mnt/sticky # sync sync # clear out the allocation area rm $mnt/foo sync _cleaner # let everything quiesce sleep 20 sync # dev tree should have one bg 20G out and the rest at the beginning.. # sort of like an empty FS but with a random sticky chunk. # kick off the cleaner in the background, remember it will sleep 10s # before doing interesting work _cleaner & sleep 3 # create 3 trivial block groups, all empty, all immediately marked as unused. echo 1 > "$(_btrfs_sysfs_space_info $dev metadata)/force_chunk_alloc" echo 1 > "$(_btrfs_sysfs_space_info $dev data)/force_chunk_alloc" echo 1 > "$(_btrfs_sysfs_space_info $dev metadata)/force_chunk_alloc" # let the cleaner thread definitely finish, it will remove the data bg sleep 10 # this allocation sees the non-consecutive pending metadata chunks with # data chunk gap of 1G and allocates a 2G extent in that hole. ENOSPC! echo 1 > "$(_btrfs_sysfs_space_info $dev metadata)/force_chunk_alloc" As for the fix, it is not that obvious. I could not see a trivial way to do it even by adding backup loops into find_free_dev_extent(), so I opted to change the semantics of dev_extent_hole_check() to not stop looping until it finds a sufficiently big hole. For clarity, this also required changing the helper function contains_pending_extent() into two new helpers which find the first pending extent and the first suitable hole in a range. I attempted to clean up the documentation and range calculations to be as consistent and clear as possible for the future. I also looked at the zoned case and concluded that the loop there is different and not to be unified with this one. As far as I can tell, the zoned check will only further constrain the hole so looping back to find more holes is acceptable. Though given that zoned really only appends, I find it highly unlikely that it is susceptible to this bug. Fixes: 1b9845081633 ("Btrfs: fix find_free_dev_extent() malfunction in case device tree has hole") Reported-by: Dimitrios Apostolou Closes: https://lore.kernel.org/linux-btrfs/q7760374-q1p4-029o-5149-26p28421s468@tzk.arg/ Reviewed-by: Qu Wenruo Signed-off-by: Boris Burkov Signed-off-by: David Sterba Signed-off-by: Sasha Levin --- fs/btrfs/volumes.c | 243 ++++++++++++++++++++++++++++++++++----------- 1 file changed, 183 insertions(+), 60 deletions(-) diff --git a/fs/btrfs/volumes.c b/fs/btrfs/volumes.c index 8e7dcb12af4c42..645bf98a9571b5 100644 --- a/fs/btrfs/volumes.c +++ b/fs/btrfs/volumes.c @@ -1506,30 +1506,158 @@ struct btrfs_device *btrfs_scan_one_device(const char *path, } /* - * Try to find a chunk that intersects [start, start + len] range and when one - * such is found, record the end of it in *start + * Find the first pending extent intersecting a range. + * + * @device: the device to search + * @start: start of the range to check + * @len: length of the range to check + * @pending_start: output pointer for the start of the found pending extent + * @pending_end: output pointer for the end of the found pending extent (inclusive) + * + * Search for a pending chunk allocation that intersects the half-open range + * [start, start + len). + * + * Return: true if a pending extent was found, false otherwise. + * If the return value is true, store the first pending extent in + * [*pending_start, *pending_end]. Otherwise, the two output variables + * may still be modified, to something outside the range and should not + * be used. */ -static bool contains_pending_extent(struct btrfs_device *device, u64 *start, - u64 len) +static bool first_pending_extent(struct btrfs_device *device, u64 start, u64 len, + u64 *pending_start, u64 *pending_end) { - u64 physical_start, physical_end; - lockdep_assert_held(&device->fs_info->chunk_mutex); - if (btrfs_find_first_extent_bit(&device->alloc_state, *start, - &physical_start, &physical_end, + if (btrfs_find_first_extent_bit(&device->alloc_state, start, + pending_start, pending_end, CHUNK_ALLOCATED, NULL)) { - if (in_range(physical_start, *start, len) || - in_range(*start, physical_start, - physical_end + 1 - physical_start)) { - *start = physical_end + 1; + if (in_range(*pending_start, start, len) || + in_range(start, *pending_start, *pending_end + 1 - *pending_start)) { return true; } } return false; } +/* + * Find the first real hole accounting for pending extents. + * + * @device: the device containing the candidate hole + * @start: input/output pointer for the hole start position + * @len: input/output pointer for the hole length + * @min_hole_size: the size of hole we are looking for + * + * Given a potential hole specified by [*start, *start + *len), check for pending + * chunk allocations within that range. If pending extents are found, the hole is + * adjusted to represent the first true free space that is large enough when + * accounting for pending chunks. + * + * Note that this function must handle various cases involving non consecutive + * pending extents. + * + * Returns: true if a suitable hole was found and false otherwise. + * If the return value is true, then *start and *len are set to represent the hole. + * If the return value is false, then *start is set to the largest hole we + * found and *len is set to its length. + * If there are no holes at all, then *start is set to the end of the range and + * *len is set to 0. + */ +static bool find_hole_in_pending_extents(struct btrfs_device *device, u64 *start, + u64 *len, u64 min_hole_size) +{ + u64 pending_start, pending_end; + u64 end; + u64 max_hole_start = 0; + u64 max_hole_len = 0; + + lockdep_assert_held(&device->fs_info->chunk_mutex); + + if (*len == 0) + return false; + + end = *start + *len - 1; + + /* + * Loop until we either see a large enough hole or check every pending + * extent overlapping the candidate hole. + * At every hole that we observe, record it if it is the new max. + * At the end of the iteration, set the output variables to the max hole. + */ + while (true) { + if (first_pending_extent(device, *start, *len, &pending_start, &pending_end)) { + /* + * Case 1: the pending extent overlaps the start of + * candidate hole. That means the true hole is after the + * pending extent, but we need to find the next pending + * extent to properly size the hole. In the next loop, + * we will reduce to case 2 or 3. + * e.g., + * + * |----pending A----| real hole |----pending B----| + * | candidate hole | + * *start end + */ + if (pending_start <= *start) { + *start = pending_end + 1; + goto next; + } + /* + * Case 2: The pending extent starts after *start (and overlaps + * [*start, end), so the first hole just goes up to the start + * of the pending extent. + * e.g., + * + * | real hole |----pending A----| + * | candidate hole | + * *start end + */ + *len = pending_start - *start; + if (*len > max_hole_len) { + max_hole_start = *start; + max_hole_len = *len; + } + if (*len >= min_hole_size) + break; + /* + * If the hole wasn't big enough, then we advance past + * the pending extent and keep looking. + */ + *start = pending_end + 1; + goto next; + } else { + /* + * Case 3: There is no pending extent overlapping the + * range [*start, *start + *len - 1], so the only remaining + * hole is the remaining range. + * e.g., + * + * | candidate hole | + * | real hole | + * *start end + */ + + if (*len > max_hole_len) { + max_hole_start = *start; + max_hole_len = *len; + } + break; + } +next: + if (*start > end) + break; + *len = end - *start + 1; + } + if (max_hole_len) { + *start = max_hole_start; + *len = max_hole_len; + } else { + *start = end + 1; + *len = 0; + } + return max_hole_len >= min_hole_size; +} + static u64 dev_extent_search_start(struct btrfs_device *device) { switch (device->fs_devices->chunk_alloc_policy) { @@ -1594,59 +1722,57 @@ static bool dev_extent_hole_check_zoned(struct btrfs_device *device, } /* - * Check if specified hole is suitable for allocation. + * Validate and adjust a hole for chunk allocation + * + * @device: the device containing the candidate hole + * @hole_start: input/output pointer for the hole start position + * @hole_size: input/output pointer for the hole size + * @num_bytes: minimum allocation size required * - * @device: the device which we have the hole - * @hole_start: starting position of the hole - * @hole_size: the size of the hole - * @num_bytes: the size of the free space that we need + * Check if the specified hole is suitable for allocation and adjust it if + * necessary. The hole may be modified to skip over pending chunk allocations + * and to satisfy stricter zoned requirements on zoned filesystems. * - * This function may modify @hole_start and @hole_size to reflect the suitable - * position for allocation. Returns 1 if hole position is updated, 0 otherwise. + * For regular (non-zoned) allocation, if the hole after adjustment is smaller + * than @num_bytes, the search continues past additional pending extents until + * either a sufficiently large hole is found or no more pending extents exist. + * + * Return: true if a suitable hole was found and false otherwise. + * If the return value is true, then *hole_start and *hole_size are set to + * represent the hole we found. + * If the return value is false, then *hole_start is set to the largest + * hole we found and *hole_size is set to its length. + * If there are no holes at all, then *hole_start is set to the end of the range + * and *hole_size is set to 0. */ static bool dev_extent_hole_check(struct btrfs_device *device, u64 *hole_start, u64 *hole_size, u64 num_bytes) { - bool changed = false; - u64 hole_end = *hole_start + *hole_size; + bool found = false; + const u64 hole_end = *hole_start + *hole_size - 1; - for (;;) { - /* - * Check before we set max_hole_start, otherwise we could end up - * sending back this offset anyway. - */ - if (contains_pending_extent(device, hole_start, *hole_size)) { - if (hole_end >= *hole_start) - *hole_size = hole_end - *hole_start; - else - *hole_size = 0; - changed = true; - } + ASSERT(*hole_size > 0); - switch (device->fs_devices->chunk_alloc_policy) { - default: - btrfs_warn_unknown_chunk_allocation(device->fs_devices->chunk_alloc_policy); - fallthrough; - case BTRFS_CHUNK_ALLOC_REGULAR: - /* No extra check */ - break; - case BTRFS_CHUNK_ALLOC_ZONED: - if (dev_extent_hole_check_zoned(device, hole_start, - hole_size, num_bytes)) { - changed = true; - /* - * The changed hole can contain pending extent. - * Loop again to check that. - */ - continue; - } - break; - } +again: + *hole_size = hole_end - *hole_start + 1; + found = find_hole_in_pending_extents(device, hole_start, hole_size, num_bytes); + if (!found) + return found; + ASSERT(*hole_size >= num_bytes); + switch (device->fs_devices->chunk_alloc_policy) { + default: + btrfs_warn_unknown_chunk_allocation(device->fs_devices->chunk_alloc_policy); + fallthrough; + case BTRFS_CHUNK_ALLOC_REGULAR: + return found; + case BTRFS_CHUNK_ALLOC_ZONED: + if (dev_extent_hole_check_zoned(device, hole_start, hole_size, num_bytes)) + goto again; break; } - return changed; + return found; } /* @@ -1705,7 +1831,7 @@ static int find_free_dev_extent(struct btrfs_device *device, u64 num_bytes, ret = -ENOMEM; goto out; } -again: + if (search_start >= search_end || test_bit(BTRFS_DEV_STATE_REPLACE_TGT, &device->dev_state)) { ret = -ENOSPC; @@ -1792,11 +1918,7 @@ static int find_free_dev_extent(struct btrfs_device *device, u64 num_bytes, */ if (search_end > search_start) { hole_size = search_end - search_start; - if (dev_extent_hole_check(device, &search_start, &hole_size, - num_bytes)) { - btrfs_release_path(path); - goto again; - } + dev_extent_hole_check(device, &search_start, &hole_size, num_bytes); if (hole_size > max_hole_size) { max_hole_start = search_start; @@ -4882,6 +5004,7 @@ int btrfs_shrink_device(struct btrfs_device *device, u64 new_size) u64 diff; u64 start; u64 free_diff = 0; + u64 pending_start, pending_end; new_size = round_down(new_size, fs_info->sectorsize); start = new_size; @@ -4927,7 +5050,7 @@ int btrfs_shrink_device(struct btrfs_device *device, u64 new_size) * in-memory chunks are synced to disk so that the loop below sees them * and relocates them accordingly. */ - if (contains_pending_extent(device, &start, diff)) { + if (first_pending_extent(device, start, diff, &pending_start, &pending_end)) { mutex_unlock(&fs_info->chunk_mutex); ret = btrfs_commit_transaction(trans); if (ret) From 5de1aa0bf3a5db0b3cbf61959da5ac61250833ed Mon Sep 17 00:00:00 2001 From: Gao Xiang Date: Tue, 3 Feb 2026 16:25:36 +0800 Subject: [PATCH 3282/3902] erofs: fix inline data read failure for ztailpacking pclusters [ Upstream commit c134a40f86efb8d6b5a949ef70e06d5752209be5 ] Compressed folios for ztailpacking pclusters must be valid before adding these pclusters to I/O chains. Otherwise, z_erofs_decompress_pcluster() may assume they are already valid and then trigger a NULL pointer dereference. It is somewhat hard to reproduce because the inline data is in the same block as the tail of the compressed indexes, which are usually read just before. However, it may still happen if a fatal signal arrives while read_mapping_folio() is running, as shown below: erofs: (device dm-1): z_erofs_pcluster_begin: failed to get inline data -4 Unable to handle kernel NULL pointer dereference at virtual address 0000000000000008 ... pc : z_erofs_decompress_queue+0x4c8/0xa14 lr : z_erofs_decompress_queue+0x160/0xa14 sp : ffffffc08b3eb3a0 x29: ffffffc08b3eb570 x28: ffffffc08b3eb418 x27: 0000000000001000 x26: ffffff8086ebdbb8 x25: ffffff8086ebdbb8 x24: 0000000000000001 x23: 0000000000000008 x22: 00000000fffffffb x21: dead000000000700 x20: 00000000000015e7 x19: ffffff808babb400 x18: ffffffc089edc098 x17: 00000000c006287d x16: 00000000c006287d x15: 0000000000000004 x14: ffffff80ba8f8000 x13: 0000000000000004 x12: 00000006589a77c9 x11: 0000000000000015 x10: 0000000000000000 x9 : 0000000000000000 x8 : 0000000000000000 x7 : 0000000000000000 x6 : 000000000000003f x5 : 0000000000000040 x4 : ffffffffffffffe0 x3 : 0000000000000020 x2 : 0000000000000008 x1 : 0000000000000000 x0 : 0000000000000000 Call trace: z_erofs_decompress_queue+0x4c8/0xa14 z_erofs_runqueue+0x908/0x97c z_erofs_read_folio+0x128/0x228 filemap_read_folio+0x68/0x128 filemap_get_pages+0x44c/0x8b4 filemap_read+0x12c/0x5b8 generic_file_read_iter+0x4c/0x15c do_iter_readv_writev+0x188/0x1e0 vfs_iter_read+0xac/0x1a4 backing_file_read_iter+0x170/0x34c ovl_read_iter+0xf0/0x140 vfs_read+0x28c/0x344 ksys_read+0x80/0xf0 __arm64_sys_read+0x24/0x34 invoke_syscall+0x60/0x114 el0_svc_common+0x88/0xe4 do_el0_svc+0x24/0x30 el0_svc+0x40/0xa8 el0t_64_sync_handler+0x70/0xbc el0t_64_sync+0x1bc/0x1c0 Fix this by reading the inline data before allocating and adding the pclusters to the I/O chains. Fixes: cecf864d3d76 ("erofs: support inline data decompression") Reported-by: Zhiguo Niu Reviewed-and-tested-by: Zhiguo Niu Signed-off-by: Gao Xiang Signed-off-by: Sasha Levin --- fs/erofs/zdata.c | 30 ++++++++++++++++-------------- 1 file changed, 16 insertions(+), 14 deletions(-) diff --git a/fs/erofs/zdata.c b/fs/erofs/zdata.c index 683703aee5ef29..98e44570841a3d 100644 --- a/fs/erofs/zdata.c +++ b/fs/erofs/zdata.c @@ -805,14 +805,26 @@ static int z_erofs_pcluster_begin(struct z_erofs_frontend *fe) struct erofs_map_blocks *map = &fe->map; struct super_block *sb = fe->inode->i_sb; struct z_erofs_pcluster *pcl = NULL; - void *ptr; + void *ptr = NULL; int ret; DBG_BUGON(fe->pcl); /* must be Z_EROFS_PCLUSTER_TAIL or pointed to previous pcluster */ DBG_BUGON(!fe->head); - if (!(map->m_flags & EROFS_MAP_META)) { + if (map->m_flags & EROFS_MAP_META) { + ret = erofs_init_metabuf(&map->buf, sb, + erofs_inode_in_metabox(fe->inode)); + if (ret) + return ret; + ptr = erofs_bread(&map->buf, map->m_pa, false); + if (IS_ERR(ptr)) { + erofs_err(sb, "failed to read inline data %pe @ pa %llu of nid %llu", + ptr, map->m_pa, EROFS_I(fe->inode)->nid); + return PTR_ERR(ptr); + } + ptr = map->buf.page; + } else { while (1) { rcu_read_lock(); pcl = xa_load(&EROFS_SB(sb)->managed_pslots, map->m_pa); @@ -852,18 +864,8 @@ static int z_erofs_pcluster_begin(struct z_erofs_frontend *fe) /* bind cache first when cached decompression is preferred */ z_erofs_bind_cache(fe); } else { - ret = erofs_init_metabuf(&map->buf, sb, - erofs_inode_in_metabox(fe->inode)); - if (ret) - return ret; - ptr = erofs_bread(&map->buf, map->m_pa, false); - if (IS_ERR(ptr)) { - ret = PTR_ERR(ptr); - erofs_err(sb, "failed to get inline folio %d", ret); - return ret; - } - folio_get(page_folio(map->buf.page)); - WRITE_ONCE(fe->pcl->compressed_bvecs[0].page, map->buf.page); + folio_get(page_folio((struct page *)ptr)); + WRITE_ONCE(fe->pcl->compressed_bvecs[0].page, ptr); fe->pcl->pageofs_in = map->m_pa & ~PAGE_MASK; fe->mode = Z_EROFS_PCLUSTER_FOLLOWED_NOINPLACE; } From e66dcf7bb9c4df5582c82bc3582725abcbfbea73 Mon Sep 17 00:00:00 2001 From: Paulo Alcantara Date: Thu, 5 Feb 2026 13:19:52 -0300 Subject: [PATCH 3283/3902] smb: client: fix potential UAF and double free in smb2_open_file() [ Upstream commit ebbbc4bfad4cb355d17c671223d0814ee3ef4eda ] Zero out @err_iov and @err_buftype before retrying SMB2_open() to prevent an UAF bug if @data != NULL, otherwise a double free. Fixes: e3a43633023e ("smb/client: fix memory leak in smb2_open_file()") Reported-by: David Howells Closes: https://lore.kernel.org/r/2892312.1770306653@warthog.procyon.org.uk Signed-off-by: Paulo Alcantara (Red Hat) Reviewed-by: David Howells Reviewed-by: ChenXiaoSong Cc: linux-cifs@vger.kernel.org Signed-off-by: Steve French Signed-off-by: Sasha Levin --- fs/smb/client/smb2file.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/fs/smb/client/smb2file.c b/fs/smb/client/smb2file.c index 03f90553d8319e..e6cdf2efc7f4f4 100644 --- a/fs/smb/client/smb2file.c +++ b/fs/smb/client/smb2file.c @@ -178,6 +178,8 @@ int smb2_open_file(const unsigned int xid, struct cifs_open_parms *oparms, __u32 &err_buftype); if (rc == -EACCES && retry_without_read_attributes) { free_rsp_buf(err_buftype, err_iov.iov_base); + memset(&err_iov, 0, sizeof(err_iov)); + err_buftype = CIFS_NO_BUFFER; oparms->desired_access &= ~FILE_READ_ATTRIBUTES; rc = SMB2_open(xid, oparms, smb2_path, &smb2_oplock, smb2_data, NULL, &err_iov, &err_buftype); From cbdb9f3adf5428ff702193122973288cc80a66be Mon Sep 17 00:00:00 2001 From: Shyam Prasad N Date: Sat, 31 Jan 2026 14:03:05 +0530 Subject: [PATCH 3284/3902] netfs: avoid double increment of retry_count in subreq [ Upstream commit a5ca32d031bbba5160e1f555aabb75a3f40f918d ] This change fixes the instance of double incrementing of retry_count. The increment of this count already happens when netfs_reissue_write gets called. Incrementing this value before is not necessary. Fixes: 4acb665cf4f3 ("netfs: Work around recursion by abandoning retry if nothing read") Acked-by: David Howells Signed-off-by: Shyam Prasad N Signed-off-by: Steve French Signed-off-by: Sasha Levin --- fs/netfs/write_retry.c | 1 - 1 file changed, 1 deletion(-) diff --git a/fs/netfs/write_retry.c b/fs/netfs/write_retry.c index fc9c3e0d34d813..29489a23a22093 100644 --- a/fs/netfs/write_retry.c +++ b/fs/netfs/write_retry.c @@ -98,7 +98,6 @@ static void netfs_retry_write_stream(struct netfs_io_request *wreq, subreq->start = start; subreq->len = len; __clear_bit(NETFS_SREQ_NEED_RETRY, &subreq->flags); - subreq->retry_count++; trace_netfs_sreq(subreq, netfs_sreq_trace_retry); /* Renegotiate max_len (wsize) */ From 7cce69596cecc64d0548c3bbf11ef5b67a135100 Mon Sep 17 00:00:00 2001 From: Florian-Ewald Mueller Date: Fri, 5 Dec 2025 13:47:32 +0100 Subject: [PATCH 3285/3902] rnbd-srv: Fix server side setting of bi_size for special IOs [ Upstream commit 4ac9690d4b9456ca1d5276d86547fa2e7cd47684 ] On rnbd-srv, the bi_size of the bio is set during the bio_add_page function, to which datalen is passed. But for special IOs like DISCARD and WRITE_ZEROES, datalen is 0, since there is no data to write. For these special IOs, use the bi_size of the rnbd_msg_io. Fixes: f6f84be089c9 ("block/rnbd-srv: Add sanity check and remove redundant assignment") Signed-off-by: Florian-Ewald Mueller Signed-off-by: Md Haris Iqbal Signed-off-by: Grzegorz Prajsner Signed-off-by: Jens Axboe Signed-off-by: Sasha Levin --- drivers/block/rnbd/rnbd-srv.c | 33 +++++++++++++++++++++++---------- 1 file changed, 23 insertions(+), 10 deletions(-) diff --git a/drivers/block/rnbd/rnbd-srv.c b/drivers/block/rnbd/rnbd-srv.c index 2df8941a6b1463..9b3fdc202e1521 100644 --- a/drivers/block/rnbd/rnbd-srv.c +++ b/drivers/block/rnbd/rnbd-srv.c @@ -145,18 +145,30 @@ static int process_rdma(struct rnbd_srv_session *srv_sess, priv->sess_dev = sess_dev; priv->id = id; - bio = bio_alloc(file_bdev(sess_dev->bdev_file), 1, + bio = bio_alloc(file_bdev(sess_dev->bdev_file), !!datalen, rnbd_to_bio_flags(le32_to_cpu(msg->rw)), GFP_KERNEL); - bio_add_virt_nofail(bio, data, datalen); - - bio->bi_opf = rnbd_to_bio_flags(le32_to_cpu(msg->rw)); - if (bio_has_data(bio) && - bio->bi_iter.bi_size != le32_to_cpu(msg->bi_size)) { - rnbd_srv_err_rl(sess_dev, "Datalen mismatch: bio bi_size (%u), bi_size (%u)\n", - bio->bi_iter.bi_size, msg->bi_size); - err = -EINVAL; - goto bio_put; + if (unlikely(!bio)) { + err = -ENOMEM; + goto put_sess_dev; } + + if (!datalen) { + /* + * For special requests like DISCARD and WRITE_ZEROES, the datalen is zero. + */ + bio->bi_iter.bi_size = le32_to_cpu(msg->bi_size); + } else { + bio_add_virt_nofail(bio, data, datalen); + bio->bi_opf = rnbd_to_bio_flags(le32_to_cpu(msg->rw)); + if (bio->bi_iter.bi_size != le32_to_cpu(msg->bi_size)) { + rnbd_srv_err_rl(sess_dev, + "Datalen mismatch: bio bi_size (%u), bi_size (%u)\n", + bio->bi_iter.bi_size, msg->bi_size); + err = -EINVAL; + goto bio_put; + } + } + bio->bi_end_io = rnbd_dev_bi_end_io; bio->bi_private = priv; bio->bi_iter.bi_sector = le64_to_cpu(msg->sector); @@ -170,6 +182,7 @@ static int process_rdma(struct rnbd_srv_session *srv_sess, bio_put: bio_put(bio); +put_sess_dev: rnbd_put_sess_dev(sess_dev); err: kfree(priv); From 68f38f648e4b5bed2aeadd2f711e25302e6490f8 Mon Sep 17 00:00:00 2001 From: "Rafael J. Wysocki" Date: Tue, 23 Dec 2025 18:09:11 +0800 Subject: [PATCH 3286/3902] ACPI: processor: Update cpuidle driver check in __acpi_processor_start() [ Upstream commit 0089ce1c056aee547115bdc25c223f8f88c08498 ] Commit 7a8c994cbb2d ("ACPI: processor: idle: Optimize ACPI idle driver registration") moved the ACPI idle driver registration to acpi_processor_driver_init() and acpi_processor_power_init() does not register an idle driver any more. Accordingly, the cpuidle driver check in __acpi_processor_start() needs to be updated to avoid calling acpi_processor_power_init() without a cpuidle driver, in which case the registration of the cpuidle device in that function would lead to a NULL pointer dereference in __cpuidle_register_device(). Fixes: 7a8c994cbb2d ("ACPI: processor: idle: Optimize ACPI idle driver registration") Signed-off-by: Rafael J. Wysocki Reviewed-by: Mario Limonciello (AMD) Tested-by: Borislav Petkov (AMD) Link: https://patch.msgid.link/20251223100914.2407069-4-lihuisong@huawei.com Signed-off-by: Rafael J. Wysocki Signed-off-by: Sasha Levin --- drivers/acpi/processor_driver.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/acpi/processor_driver.c b/drivers/acpi/processor_driver.c index 65e779be64ffcc..7644de24d2faa4 100644 --- a/drivers/acpi/processor_driver.c +++ b/drivers/acpi/processor_driver.c @@ -166,7 +166,7 @@ static int __acpi_processor_start(struct acpi_device *device) if (result && !IS_ENABLED(CONFIG_ACPI_CPU_FREQ_PSS)) dev_dbg(&device->dev, "CPPC data invalid or not present\n"); - if (!cpuidle_get_driver() || cpuidle_get_driver() == &acpi_idle_driver) + if (cpuidle_get_driver() == &acpi_idle_driver) acpi_processor_power_init(pr); acpi_pss_perf_init(pr); From cb45bcc9709306b0cc2e0184233ca0578fdfbc7e Mon Sep 17 00:00:00 2001 From: Teddy Astie Date: Tue, 6 Jan 2026 17:36:50 +0000 Subject: [PATCH 3287/3902] xen/virtio: Don't use grant-dma-ops when running as Dom0 [ Upstream commit dc8ea8714311e549ee93a2b0bdd5487d20bfadbf ] Dom0 inherit devices from the machine and is usually in PV mode. If we are running in a virtual that has virtio devices, these devices would be considered as using grants with Dom0 as backend, while being the said Dom0 itself, while we want to use these devices like regular PCI devices. Fix this by preventing grant-dma-ops from being used when running as Dom0 (initial domain). We still keep the device-tree logic as-is. Signed-off-by: Teddy Astie Fixes: 61367688f1fb0 ("xen/virtio: enable grant based virtio on x86") Reviewed-by: Juergen Gross Signed-off-by: Juergen Gross Message-ID: <6698564dd2270a9f7377b78ebfb20cb425cabbe8.1767720955.git.teddy.astie@vates.tech> Signed-off-by: Sasha Levin --- drivers/xen/grant-dma-ops.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/drivers/xen/grant-dma-ops.c b/drivers/xen/grant-dma-ops.c index 29257d2639dbf0..43a918c498c6c7 100644 --- a/drivers/xen/grant-dma-ops.c +++ b/drivers/xen/grant-dma-ops.c @@ -362,7 +362,8 @@ static int xen_grant_init_backend_domid(struct device *dev, if (np) { ret = xen_dt_grant_init_backend_domid(dev, np, backend_domid); of_node_put(np); - } else if (IS_ENABLED(CONFIG_XEN_VIRTIO_FORCE_GRANT) || xen_pv_domain()) { + } else if (!xen_initial_domain() && + (IS_ENABLED(CONFIG_XEN_VIRTIO_FORCE_GRANT) || xen_pv_domain())) { dev_info(dev, "Using dom0 as backend\n"); *backend_domid = 0; ret = 0; From c1c266e2c1f10a115adaf898652e56b9f007a915 Mon Sep 17 00:00:00 2001 From: Caleb Sander Mateos Date: Mon, 5 Jan 2026 14:05:40 -0700 Subject: [PATCH 3288/3902] io_uring: use release-acquire ordering for IORING_SETUP_R_DISABLED [ Upstream commit 7a8737e1132ff07ca225aa7a4008f87319b5b1ca ] io_uring_enter(), __io_msg_ring_data(), and io_msg_send_fd() read ctx->flags and ctx->submitter_task without holding the ctx's uring_lock. This means they may race with the assignment to ctx->submitter_task and the clearing of IORING_SETUP_R_DISABLED from ctx->flags in io_register_enable_rings(). Ensure the correct ordering of the ctx->flags and ctx->submitter_task memory accesses by storing to ctx->flags using release ordering and loading it using acquire ordering. Signed-off-by: Caleb Sander Mateos Fixes: 4add705e4eeb ("io_uring: remove io_register_submitter") Reviewed-by: Joanne Koong Reviewed-by: Gabriel Krisman Bertazi Signed-off-by: Jens Axboe Signed-off-by: Sasha Levin --- io_uring/io_uring.c | 6 +++++- io_uring/msg_ring.c | 12 ++++++++++-- io_uring/register.c | 3 ++- 3 files changed, 17 insertions(+), 4 deletions(-) diff --git a/io_uring/io_uring.c b/io_uring/io_uring.c index 104192bcc8e4bf..d8a35a49dd1acf 100644 --- a/io_uring/io_uring.c +++ b/io_uring/io_uring.c @@ -3490,7 +3490,11 @@ SYSCALL_DEFINE6(io_uring_enter, unsigned int, fd, u32, to_submit, ctx = file->private_data; ret = -EBADFD; - if (unlikely(ctx->flags & IORING_SETUP_R_DISABLED)) + /* + * Keep IORING_SETUP_R_DISABLED check before submitter_task load + * in io_uring_add_tctx_node() -> __io_uring_add_tctx_node_from_submit() + */ + if (unlikely(smp_load_acquire(&ctx->flags) & IORING_SETUP_R_DISABLED)) goto out; /* diff --git a/io_uring/msg_ring.c b/io_uring/msg_ring.c index 5e5b94236d7204..bce74a8b64c6eb 100644 --- a/io_uring/msg_ring.c +++ b/io_uring/msg_ring.c @@ -124,7 +124,11 @@ static int __io_msg_ring_data(struct io_ring_ctx *target_ctx, return -EINVAL; if (!(msg->flags & IORING_MSG_RING_FLAGS_PASS) && msg->dst_fd) return -EINVAL; - if (target_ctx->flags & IORING_SETUP_R_DISABLED) + /* + * Keep IORING_SETUP_R_DISABLED check before submitter_task load + * in io_msg_data_remote() -> io_msg_remote_post() + */ + if (smp_load_acquire(&target_ctx->flags) & IORING_SETUP_R_DISABLED) return -EBADFD; if (io_msg_need_remote(target_ctx)) @@ -244,7 +248,11 @@ static int io_msg_send_fd(struct io_kiocb *req, unsigned int issue_flags) return -EINVAL; if (target_ctx == ctx) return -EINVAL; - if (target_ctx->flags & IORING_SETUP_R_DISABLED) + /* + * Keep IORING_SETUP_R_DISABLED check before submitter_task load + * in io_msg_fd_remote() + */ + if (smp_load_acquire(&target_ctx->flags) & IORING_SETUP_R_DISABLED) return -EBADFD; if (!msg->src_file) { int ret = io_msg_grab_file(req, issue_flags); diff --git a/io_uring/register.c b/io_uring/register.c index d189b266b8ccec..db53e664348d74 100644 --- a/io_uring/register.c +++ b/io_uring/register.c @@ -193,7 +193,8 @@ static int io_register_enable_rings(struct io_ring_ctx *ctx) if (ctx->restrictions.registered) ctx->restricted = 1; - ctx->flags &= ~IORING_SETUP_R_DISABLED; + /* Keep submitter_task store before clearing IORING_SETUP_R_DISABLED */ + smp_store_release(&ctx->flags, ctx->flags & ~IORING_SETUP_R_DISABLED); if (ctx->sq_data && wq_has_sleeper(&ctx->sq_data->wait)) wake_up(&ctx->sq_data->wait); return 0; From b24595b86920911d2b04f862422b896a0620e9ad Mon Sep 17 00:00:00 2001 From: Alexey Simakov Date: Wed, 14 Jan 2026 13:20:17 +0100 Subject: [PATCH 3289/3902] ACPICA: Fix NULL pointer dereference in acpi_ev_address_space_dispatch() [ Upstream commit f851e03bce968ff9b3faad1b616062e1244fd38d ] Cover a missed execution path with a new check. Fixes: 0acf24ad7e10 ("ACPICA: Add support for PCC Opregion special context data") Link: https://github.com/acpica/acpica/commit/f421dd9dd897 Signed-off-by: Alexey Simakov Signed-off-by: Rafael J. Wysocki Link: https://patch.msgid.link/3030574.e9J7NaK4W3@rafael.j.wysocki Signed-off-by: Sasha Levin --- drivers/acpi/acpica/evregion.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/drivers/acpi/acpica/evregion.c b/drivers/acpi/acpica/evregion.c index fa3475da7ea9b6..b6198f73c81dfa 100644 --- a/drivers/acpi/acpica/evregion.c +++ b/drivers/acpi/acpica/evregion.c @@ -163,7 +163,9 @@ acpi_ev_address_space_dispatch(union acpi_operand_object *region_obj, return_ACPI_STATUS(AE_NOT_EXIST); } - if (region_obj->region.space_id == ACPI_ADR_SPACE_PLATFORM_COMM) { + if (field_obj + && region_obj->region.space_id == + ACPI_ADR_SPACE_PLATFORM_COMM) { struct acpi_pcc_info *ctx = handler_desc->address_space.context; From 1d126db2f263f2ce3968203ecb06faf1ea8a5007 Mon Sep 17 00:00:00 2001 From: Jens Axboe Date: Wed, 21 Jan 2026 11:21:32 -0700 Subject: [PATCH 3290/3902] io_uring/eventfd: remove unused ctx->evfd_last_cq_tail member [ Upstream commit 07f3c3a1cd56c2048a92dad0c11f15e4ac3888c1 ] A previous commit got rid of any use of this member, but forgot to remove it. Kill it. Fixes: f4bb2f65bb81 ("io_uring/eventfd: move ctx->evfd_last_cq_tail into io_ev_fd") Signed-off-by: Jens Axboe Signed-off-by: Sasha Levin --- include/linux/io_uring_types.h | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/include/linux/io_uring_types.h b/include/linux/io_uring_types.h index c2ea6280901dca..b4d8aca3e78604 100644 --- a/include/linux/io_uring_types.h +++ b/include/linux/io_uring_types.h @@ -439,6 +439,9 @@ struct io_ring_ctx { struct list_head defer_list; unsigned nr_drained; + /* protected by ->completion_lock */ + unsigned nr_req_allocated; + #ifdef CONFIG_NET_RX_BUSY_POLL struct list_head napi_list; /* track busy poll napi_id */ spinlock_t napi_lock; /* napi_list lock */ @@ -451,10 +454,6 @@ struct io_ring_ctx { DECLARE_HASHTABLE(napi_ht, 4); #endif - /* protected by ->completion_lock */ - unsigned evfd_last_cq_tail; - unsigned nr_req_allocated; - /* * Protection for resize vs mmap races - both the mmap and resize * side will need to grab this lock, to prevent either side from From d733106e7c652d5fc3d497e9ee74456476b905d3 Mon Sep 17 00:00:00 2001 From: Jens Axboe Date: Wed, 21 Jan 2026 11:48:56 -0700 Subject: [PATCH 3291/3902] io_uring/sync: validate passed in offset [ Upstream commit 649dd18f559891bdafc5532d737c7dfb56060a6d ] Check if the passed in offset is negative once cast to sync->off. This ensures that -EINVAL is returned for that case, like it would be for sync_file_range(2). Fixes: c992fe2925d7 ("io_uring: add fsync support") Signed-off-by: Jens Axboe Signed-off-by: Sasha Levin --- io_uring/sync.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/io_uring/sync.c b/io_uring/sync.c index cea2d381ffd2a4..ab7fa1cd7dd639 100644 --- a/io_uring/sync.c +++ b/io_uring/sync.c @@ -62,6 +62,8 @@ int io_fsync_prep(struct io_kiocb *req, const struct io_uring_sqe *sqe) return -EINVAL; sync->off = READ_ONCE(sqe->off); + if (sync->off < 0) + return -EINVAL; sync->len = READ_ONCE(sqe->len); req->flags |= REQ_F_FORCE_ASYNC; return 0; From 9d589661f21fa841cb1c08110f41d9e104c5f7cd Mon Sep 17 00:00:00 2001 From: "Rafael J. Wysocki" Date: Tue, 20 Jan 2026 16:26:14 +0100 Subject: [PATCH 3292/3902] cpuidle: governors: menu: Always check timers with tick stopped [ Upstream commit 80606f4eb8d7484ab7f7d6f0fd30d71e6fbcf328 ] After commit 5484e31bbbff ("cpuidle: menu: Skip tick_nohz_get_sleep_length() call in some cases"), if the return value of get_typical_interval() multiplied by NSEC_PER_USEC is not greater than RESIDENCY_THRESHOLD_NS, the menu governor will skip computing the time till the closest timer. If that happens when the tick has been stopped already, the selected idle state may be too deep due to the subsequent check comparing predicted_ns with TICK_NSEC and causing its value to be replaced with the expected time till the closest timer, which is KTIME_MAX in that case. That will cause the deepest enabled idle state to be selected, but the time till the closest timer very well may be shorter than the target residency of that state, in which case a shallower state should be used. Address this by making menu_select() always compute the time till the closest timer when the tick has been stopped. Also move the predicted_ns check mentioned above into the branch in which the time till the closest timer is determined because it only needs to be done in that case. Fixes: 5484e31bbbff ("cpuidle: menu: Skip tick_nohz_get_sleep_length() call in some cases") Signed-off-by: Rafael J. Wysocki Reviewed-by: Christian Loehle Link: https://patch.msgid.link/5959091.DvuYhMxLoT@rafael.j.wysocki Signed-off-by: Sasha Levin --- drivers/cpuidle/governors/menu.c | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/drivers/cpuidle/governors/menu.c b/drivers/cpuidle/governors/menu.c index 64d6f7a1c77663..ca863ba03d4544 100644 --- a/drivers/cpuidle/governors/menu.c +++ b/drivers/cpuidle/governors/menu.c @@ -239,7 +239,7 @@ static int menu_select(struct cpuidle_driver *drv, struct cpuidle_device *dev, /* Find the shortest expected idle interval. */ predicted_ns = get_typical_interval(data) * NSEC_PER_USEC; - if (predicted_ns > RESIDENCY_THRESHOLD_NS) { + if (predicted_ns > RESIDENCY_THRESHOLD_NS || tick_nohz_tick_stopped()) { unsigned int timer_us; /* Determine the time till the closest timer. */ @@ -259,6 +259,16 @@ static int menu_select(struct cpuidle_driver *drv, struct cpuidle_device *dev, RESOLUTION * DECAY * NSEC_PER_USEC); /* Use the lowest expected idle interval to pick the idle state. */ predicted_ns = min((u64)timer_us * NSEC_PER_USEC, predicted_ns); + /* + * If the tick is already stopped, the cost of possible short + * idle duration misprediction is much higher, because the CPU + * may be stuck in a shallow idle state for a long time as a + * result of it. In that case, say we might mispredict and use + * the known time till the closest timer event for the idle + * state selection. + */ + if (tick_nohz_tick_stopped() && predicted_ns < TICK_NSEC) + predicted_ns = data->next_timer_ns; } else { /* * Because the next timer event is not going to be determined @@ -284,16 +294,6 @@ static int menu_select(struct cpuidle_driver *drv, struct cpuidle_device *dev, return 0; } - /* - * If the tick is already stopped, the cost of possible short idle - * duration misprediction is much higher, because the CPU may be stuck - * in a shallow idle state for a long time as a result of it. In that - * case, say we might mispredict and use the known time till the closest - * timer event for the idle state selection. - */ - if (tick_nohz_tick_stopped() && predicted_ns < TICK_NSEC) - predicted_ns = data->next_timer_ns; - /* * Find the idle state with the lowest power while satisfying * our constraints. From 649ddf7e5aa64ef53e039fb9daea51f0b6460b02 Mon Sep 17 00:00:00 2001 From: "Rafael J. Wysocki" Date: Tue, 20 Jan 2026 16:23:41 +0100 Subject: [PATCH 3293/3902] thermal: intel: x86_pkg_temp_thermal: Handle invalid temperature [ Upstream commit 9635c586a559ba0e45b2bfbff79c937ddbaf1a62 ] After commit be0a3600aa1e ("thermal: sysfs: Rework the handling of trip point updates"), THERMAL_TEMP_INVALID can be passed to sys_set_trip_temp() and it is treated as a regular temperature value there, so the sysfs write fails even though it is expected to succeed and disable the given trip point. Address this by making sys_set_trip_temp() clear its temp variable when it is equal to THERMAL_TEMP_INVALID. Fixes: be0a3600aa1e ("thermal: sysfs: Rework the handling of trip point updates") Signed-off-by: Rafael J. Wysocki Link: https://patch.msgid.link/2815400.mvXUDI8C0e@rafael.j.wysocki Signed-off-by: Sasha Levin --- drivers/thermal/intel/x86_pkg_temp_thermal.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/drivers/thermal/intel/x86_pkg_temp_thermal.c b/drivers/thermal/intel/x86_pkg_temp_thermal.c index 3fc679b6f11b17..aab5f9fca9c33c 100644 --- a/drivers/thermal/intel/x86_pkg_temp_thermal.c +++ b/drivers/thermal/intel/x86_pkg_temp_thermal.c @@ -128,6 +128,9 @@ sys_set_trip_temp(struct thermal_zone_device *tzd, u32 l, h, mask, shift, intr; int tj_max, val, ret; + if (temp == THERMAL_TEMP_INVALID) + temp = 0; + tj_max = intel_tcc_get_tjmax(zonedev->cpu); if (tj_max < 0) return tj_max; From 32c046a506ea9270c06d09195c0b400cf1285261 Mon Sep 17 00:00:00 2001 From: Yu Kuai Date: Thu, 15 Jan 2026 01:12:29 +0800 Subject: [PATCH 3294/3902] md/raid5: fix raid5_run() to return error when log_init() fails [ Upstream commit 2d9f7150ac197ce79c9c917a004d4cf0b26ad7e0 ] Since commit f63f17350e53 ("md/raid5: use the atomic queue limit update APIs"), the abort path in raid5_run() returns 'ret' instead of -EIO. However, if log_init() fails, 'ret' is still 0 from the previous successful call, causing raid5_run() to return success despite the failure. Fix this by capturing the return value from log_init(). Link: https://lore.kernel.org/linux-raid/20260114171241.3043364-2-yukuai@fnnas.com Fixes: f63f17350e53 ("md/raid5: use the atomic queue limit update APIs") Reported-by: Dan Carpenter Closes: https://lore.kernel.org/r/202601130531.LGfcZsa4-lkp@intel.com/ Signed-off-by: Yu Kuai Reviewed-by: Li Nan Reviewed-by: Xiao Ni Reviewed-by: Christoph Hellwig Signed-off-by: Sasha Levin --- drivers/md/raid5.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/drivers/md/raid5.c b/drivers/md/raid5.c index 41de29206402ad..1041788a54c20c 100644 --- a/drivers/md/raid5.c +++ b/drivers/md/raid5.c @@ -8056,7 +8056,8 @@ static int raid5_run(struct mddev *mddev) goto abort; } - if (log_init(conf, journal_dev, raid5_has_ppl(conf))) + ret = log_init(conf, journal_dev, raid5_has_ppl(conf)); + if (ret) goto abort; return 0; From 5bf494167291e762fe9ffce50c9dc5095fa84089 Mon Sep 17 00:00:00 2001 From: Li Nan Date: Mon, 5 Jan 2026 19:02:58 +0800 Subject: [PATCH 3295/3902] md/raid10: fix any_working flag handling in raid10_sync_request [ Upstream commit 99582edb3f62e8ee6c34512021368f53f9b091f2 ] In raid10_sync_request(), 'any_working' indicates if any IO will be submitted. When there's only one In_sync disk with badblocks, 'any_working' might be set to 1 but no IO is submitted. Fix it by setting 'any_working' after badblock checks. Link: https://lore.kernel.org/linux-raid/20260105110300.1442509-11-linan666@huaweicloud.com Fixes: e875ecea266a ("md/raid10 record bad blocks as needed during recovery.") Signed-off-by: Li Nan Reviewed-by: Yu Kuai Signed-off-by: Yu Kuai Signed-off-by: Sasha Levin --- drivers/md/raid10.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/md/raid10.c b/drivers/md/raid10.c index 14dcd5142eb477..d58ae150b45022 100644 --- a/drivers/md/raid10.c +++ b/drivers/md/raid10.c @@ -3402,7 +3402,6 @@ static sector_t raid10_sync_request(struct mddev *mddev, sector_t sector_nr, !test_bit(In_sync, &rdev->flags)) continue; /* This is where we read from */ - any_working = 1; sector = r10_bio->devs[j].addr; if (is_badblock(rdev, sector, max_sync, @@ -3417,6 +3416,7 @@ static sector_t raid10_sync_request(struct mddev *mddev, sector_t sector_nr, continue; } } + any_working = 1; bio = r10_bio->devs[0].bio; bio->bi_next = biolist; biolist = bio; From 870b9f15867b0e70f3459ef3974b043e8b229690 Mon Sep 17 00:00:00 2001 From: Yu Kuai Date: Sat, 24 Jan 2026 02:26:22 +0800 Subject: [PATCH 3296/3902] md/raid5: fix IO hang with degraded array with llbitmap [ Upstream commit cd1635d844d26471c56c0a432abdee12fc9ad735 ] When llbitmap bit state is still unwritten, any new write should force rcw, as bitmap_ops->blocks_synced() is checked in handle_stripe_dirtying(). However, later the same check is missing in need_this_block(), causing stripe to deadloop during handling because handle_stripe() will decide to go to handle_stripe_fill(), meanwhile need_this_block() always return 0 and nothing is handled. Link: https://lore.kernel.org/linux-raid/20260123182623.3718551-2-yukuai@fnnas.com Fixes: 5ab829f1971d ("md/md-llbitmap: introduce new lockless bitmap") Signed-off-by: Yu Kuai Reviewed-by: Li Nan Signed-off-by: Sasha Levin --- drivers/md/raid5.c | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/drivers/md/raid5.c b/drivers/md/raid5.c index 1041788a54c20c..3b711a1198adbe 100644 --- a/drivers/md/raid5.c +++ b/drivers/md/raid5.c @@ -3751,9 +3751,14 @@ static int need_this_block(struct stripe_head *sh, struct stripe_head_state *s, struct r5dev *dev = &sh->dev[disk_idx]; struct r5dev *fdev[2] = { &sh->dev[s->failed_num[0]], &sh->dev[s->failed_num[1]] }; + struct mddev *mddev = sh->raid_conf->mddev; + bool force_rcw = false; int i; - bool force_rcw = (sh->raid_conf->rmw_level == PARITY_DISABLE_RMW); + if (sh->raid_conf->rmw_level == PARITY_DISABLE_RMW || + (mddev->bitmap_ops && mddev->bitmap_ops->blocks_synced && + !mddev->bitmap_ops->blocks_synced(mddev, sh->sector))) + force_rcw = true; if (test_bit(R5_LOCKED, &dev->flags) || test_bit(R5_UPTODATE, &dev->flags)) From 095417d6b669c2dec39a5842ccb94df915f97f54 Mon Sep 17 00:00:00 2001 From: Yu Kuai Date: Sat, 24 Jan 2026 02:26:23 +0800 Subject: [PATCH 3297/3902] md/md-llbitmap: fix percpu_ref not resurrected on suspend timeout [ Upstream commit d119bd2e1643cc023210ff3c6f0657e4f914e71d ] When llbitmap_suspend_timeout() times out waiting for percpu_ref to become zero, it returns -ETIMEDOUT without resurrecting the percpu_ref. The caller (md_llbitmap_daemon_fn) then continues to the next page without calling llbitmap_resume(), leaving the percpu_ref in a killed state permanently. Fix this by resurrecting the percpu_ref before returning the error, ensuring the page control structure remains usable for subsequent operations. Link: https://lore.kernel.org/linux-raid/20260123182623.3718551-3-yukuai@fnnas.com Fixes: 5ab829f1971d ("md/md-llbitmap: introduce new lockless bitmap") Signed-off-by: Yu Kuai Reviewed-by: Li Nan Signed-off-by: Sasha Levin --- drivers/md/md-llbitmap.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/drivers/md/md-llbitmap.c b/drivers/md/md-llbitmap.c index 1eb434306162a5..bcb6eae1c711f6 100644 --- a/drivers/md/md-llbitmap.c +++ b/drivers/md/md-llbitmap.c @@ -712,8 +712,10 @@ static int llbitmap_suspend_timeout(struct llbitmap *llbitmap, int page_idx) percpu_ref_kill(&pctl->active); if (!wait_event_timeout(pctl->wait, percpu_ref_is_zero(&pctl->active), - llbitmap->mddev->bitmap_info.daemon_sleep * HZ)) + llbitmap->mddev->bitmap_info.daemon_sleep * HZ)) { + percpu_ref_resurrect(&pctl->active); return -ETIMEDOUT; + } return 0; } From 27feb209c2d7bf86d652ff9f5a66c078f30ad71a Mon Sep 17 00:00:00 2001 From: Aleks Todorov Date: Fri, 23 Jan 2026 14:03:44 +0000 Subject: [PATCH 3298/3902] OPP: Return correct value in dev_pm_opp_get_level [ Upstream commit 0b7277e02dabba2a9921a7f4761ae6e627e7297a ] Commit 073d3d2ca7d4 ("OPP: Level zero is valid") modified the documentation for this function to indicate that errors should return a non-zero value to avoid colliding with the OPP level zero, however forgot to actually update the return. No in-tree kernel code depends on the error value being 0. Fixes: 073d3d2ca7d4 ("OPP: Level zero is valid") Signed-off-by: Aleks Todorov Signed-off-by: Viresh Kumar Signed-off-by: Sasha Levin --- drivers/opp/core.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/opp/core.c b/drivers/opp/core.c index bba4f7daff8cb8..775d4a36f2f544 100644 --- a/drivers/opp/core.c +++ b/drivers/opp/core.c @@ -241,7 +241,7 @@ unsigned int dev_pm_opp_get_level(struct dev_pm_opp *opp) { if (IS_ERR_OR_NULL(opp) || !opp->available) { pr_err("%s: Invalid parameters\n", __func__); - return 0; + return U32_MAX; } return opp->level; From 67bbf2527c1d0e48d0b92b6851c4e7c0810dd2db Mon Sep 17 00:00:00 2001 From: Felix Gu Date: Wed, 21 Jan 2026 23:32:06 +0800 Subject: [PATCH 3299/3902] cpufreq: scmi: Fix device_node reference leak in scmi_cpu_domain_id() [ Upstream commit 0b7fbf9333fa4699a53145bad8ce74ea986caa13 ] When calling of_parse_phandle_with_args(), the caller is responsible to call of_node_put() to release the reference of device node. In scmi_cpu_domain_id(), it does not release the reference. Fixes: e336baa4193e ("cpufreq: scmi: Prepare to move OF parsing of domain-id to cpufreq") Signed-off-by: Felix Gu Signed-off-by: Viresh Kumar Signed-off-by: Sasha Levin --- drivers/cpufreq/scmi-cpufreq.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/cpufreq/scmi-cpufreq.c b/drivers/cpufreq/scmi-cpufreq.c index d2a110079f5fd5..c450cf9c881d49 100644 --- a/drivers/cpufreq/scmi-cpufreq.c +++ b/drivers/cpufreq/scmi-cpufreq.c @@ -101,6 +101,7 @@ static int scmi_cpu_domain_id(struct device *cpu_dev) return -EINVAL; } + of_node_put(domain_id.np); return domain_id.args[0]; } From 769a8a732e825a8e2ce7270cb211bdbf7f87eb4f Mon Sep 17 00:00:00 2001 From: Christoph Hellwig Date: Mon, 26 Jan 2026 06:53:38 +0100 Subject: [PATCH 3300/3902] iomap: fix submission side handling of completion side errors [ Upstream commit 4ad357e39b2ecd5da7bcc7e840ee24d179593cd5 ] The "if (dio->error)" in iomap_dio_bio_iter exists to stop submitting more bios when a completion already return an error. Commit cfe057f7db1f ("iomap_dio_actor(): fix iov_iter bugs") made it revert the iov by "copied", which is very wrong given that we've already consumed that range and submitted a bio for it. Fixes: cfe057f7db1f ("iomap_dio_actor(): fix iov_iter bugs") Signed-off-by: Christoph Hellwig Reviewed-by: Damien Le Moal Reviewed-by: Darrick J. Wong Signed-off-by: Jens Axboe Signed-off-by: Sasha Levin --- fs/iomap/direct-io.c | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/fs/iomap/direct-io.c b/fs/iomap/direct-io.c index 6317e4cd42517d..e73c71f39bd45f 100644 --- a/fs/iomap/direct-io.c +++ b/fs/iomap/direct-io.c @@ -429,9 +429,13 @@ static int iomap_dio_bio_iter(struct iomap_iter *iter, struct iomap_dio *dio) nr_pages = bio_iov_vecs_to_alloc(dio->submit.iter, BIO_MAX_VECS); do { size_t n; - if (dio->error) { - iov_iter_revert(dio->submit.iter, copied); - copied = ret = 0; + + /* + * If completions already occurred and reported errors, give up now and + * don't bother submitting more bios. + */ + if (unlikely(data_race(dio->error))) { + ret = 0; goto out; } From 8344d5da9df74fdbef676214d0c482fc822a01ca Mon Sep 17 00:00:00 2001 From: Felix Gu Date: Sat, 24 Jan 2026 03:06:40 +0800 Subject: [PATCH 3301/3902] thermal/of: Fix reference leak in thermal_of_cm_lookup() [ Upstream commit a1fe789a96fe47733c133134fd264cb7ca832395 ] In thermal_of_cm_lookup(), tr_np is obtained via of_parse_phandle(), but never released. Use the __free(device_node) cleanup attribute to automatically release the node and fix the leak. Fixes: 423de5b5bc5b ("thermal/of: Fix cdev lookup in thermal_of_should_bind()") Signed-off-by: Felix Gu Reviewed-by: Lukasz Luba [ rjw: Changelog edits ] Link: https://patch.msgid.link/20260124-thermal_of-v1-1-54d3416948cf@gmail.com Signed-off-by: Rafael J. Wysocki Signed-off-by: Sasha Levin --- drivers/thermal/thermal_of.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/thermal/thermal_of.c b/drivers/thermal/thermal_of.c index 1a51a4d240ff60..b6d0c92f5522bd 100644 --- a/drivers/thermal/thermal_of.c +++ b/drivers/thermal/thermal_of.c @@ -280,10 +280,10 @@ static bool thermal_of_cm_lookup(struct device_node *cm_np, struct cooling_spec *c) { for_each_child_of_node_scoped(cm_np, child) { - struct device_node *tr_np; int count, i; - tr_np = of_parse_phandle(child, "trip", 0); + struct device_node *tr_np __free(device_node) = + of_parse_phandle(child, "trip", 0); if (tr_np != trip->priv) continue; From 9b03dda1453e8e8c580e10d90de0179d489deb6c Mon Sep 17 00:00:00 2001 From: Caleb Sander Mateos Date: Wed, 28 Jan 2026 13:56:34 -0700 Subject: [PATCH 3302/3902] ublk: restore auto buf unregister refcount optimization [ Upstream commit ad5f2e2908c9b79a86529281a48e94d644d43dc7 ] Commit 1ceeedb59749 ("ublk: optimize UBLK_IO_UNREGISTER_IO_BUF on daemon task") optimized ublk request buffer unregistration to use a non-atomic reference count decrement when performed on the ublk_io's daemon task. The optimization applied to auto buffer unregistration, which happens as part of handling UBLK_IO_COMMIT_AND_FETCH_REQ on the daemon task. However, commit b749965edda8 ("ublk: remove ublk_commit_and_fetch()") reordered the ublk_sub_req_ref() for the completed request before the io_buffer_unregister_bvec() call. As a result, task_registered_buffers is already 0 when io_buffer_unregister_bvec() calls ublk_io_release() and the non-atomic refcount optimization doesn't apply. Move the io_buffer_unregister_bvec() call back to before ublk_need_complete_req() to restore the reference counting optimization. Signed-off-by: Caleb Sander Mateos Fixes: b749965edda8 ("ublk: remove ublk_commit_and_fetch()") Reviewed-by: Ming Lei Signed-off-by: Jens Axboe Signed-off-by: Sasha Levin --- drivers/block/ublk_drv.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/block/ublk_drv.c b/drivers/block/ublk_drv.c index 4b6d7b785d7b36..56058090d223e7 100644 --- a/drivers/block/ublk_drv.c +++ b/drivers/block/ublk_drv.c @@ -2526,11 +2526,11 @@ static int ublk_ch_uring_cmd_local(struct io_uring_cmd *cmd, io->res = result; req = ublk_fill_io_cmd(io, cmd); ret = ublk_config_io_buf(ub, io, cmd, addr, &buf_idx); + if (buf_idx != UBLK_INVALID_BUF_IDX) + io_buffer_unregister_bvec(cmd, buf_idx, issue_flags); compl = ublk_need_complete_req(ub, io); /* can't touch 'ublk_io' any more */ - if (buf_idx != UBLK_INVALID_BUF_IDX) - io_buffer_unregister_bvec(cmd, buf_idx, issue_flags); if (req_op(req) == REQ_OP_ZONE_APPEND) req->__sector = addr; if (compl) From f75a5555e0049e7857eae25b60aee98b80e287ec Mon Sep 17 00:00:00 2001 From: Govindarajulu Varadarajan Date: Fri, 30 Jan 2026 10:14:12 -0700 Subject: [PATCH 3303/3902] ublk: Validate SQE128 flag before accessing the cmd [ Upstream commit da7e4b75e50c087d2031a92f6646eb90f7045a67 ] ublk_ctrl_cmd_dump() accesses (header *)sqe->cmd before IO_URING_F_SQE128 flag check. This could cause out of boundary memory access. Move the SQE128 flag check earlier in ublk_ctrl_uring_cmd() to return -EINVAL immediately if the flag is not set. Fixes: 71f28f3136af ("ublk_drv: add io_uring based userspace block driver") Signed-off-by: Govindarajulu Varadarajan Reviewed-by: Caleb Sander Mateos Reviewed-by: Ming Lei Signed-off-by: Jens Axboe Signed-off-by: Sasha Levin --- drivers/block/ublk_drv.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/drivers/block/ublk_drv.c b/drivers/block/ublk_drv.c index 56058090d223e7..965460d4fc76ea 100644 --- a/drivers/block/ublk_drv.c +++ b/drivers/block/ublk_drv.c @@ -3841,10 +3841,10 @@ static int ublk_ctrl_uring_cmd(struct io_uring_cmd *cmd, if (issue_flags & IO_URING_F_NONBLOCK) return -EAGAIN; - ublk_ctrl_cmd_dump(cmd); - if (!(issue_flags & IO_URING_F_SQE128)) - goto out; + return -EINVAL; + + ublk_ctrl_cmd_dump(cmd); ret = ublk_check_cmd_op(cmd_op); if (ret) From 02455f658db8e32cc4ae9adb9c2ff6d1b80bb0b0 Mon Sep 17 00:00:00 2001 From: Roger Pau Monne Date: Wed, 28 Jan 2026 12:05:08 +0100 Subject: [PATCH 3304/3902] Partial revert "x86/xen: fix balloon target initialization for PVH dom0" MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [ Upstream commit 0949c646d64697428ff6257d52efa5093566868d ] This partially reverts commit 87af633689ce16ddb166c80f32b120e50b1295de so the current memory target for PV guests is still fetched from start_info->nr_pages, which matches exactly what the toolstack sets the initial memory target to. Using get_num_physpages() is possible on PV also, but needs adjusting to take into account the ISA hole and the PFN at 0 not considered usable memory despite being populated, and hence would need extra adjustments. Instead of carrying those extra adjustments switch back to the previous code. That leaves Linux with a difference in how current memory target is obtained for HVM vs PV, but that's better than adding extra logic just for PV. However if switching to start_info->nr_pages for PV domains we need to differentiate between released pages (freed back to the hypervisor) as opposed to pages in the physmap which are not populated to start with. Introduce a new xen_unpopulated_pages to account for papges that have never been populated, and hence in the PV case don't need subtracting. Fixes: 87af633689ce ("x86/xen: fix balloon target initialization for PVH dom0") Reported-by: James Dingwall Signed-off-by: Roger Pau Monné Reviewed-by: Juergen Gross Signed-off-by: Juergen Gross Message-ID: <20260128110510.46425-2-roger.pau@citrix.com> Signed-off-by: Sasha Levin --- arch/x86/xen/enlighten.c | 2 +- drivers/xen/balloon.c | 19 +++++++++++++++---- drivers/xen/unpopulated-alloc.c | 3 +++ include/xen/xen.h | 2 ++ 4 files changed, 21 insertions(+), 5 deletions(-) diff --git a/arch/x86/xen/enlighten.c b/arch/x86/xen/enlighten.c index 53282dc7d5ac5b..23b91bf9b66303 100644 --- a/arch/x86/xen/enlighten.c +++ b/arch/x86/xen/enlighten.c @@ -470,7 +470,7 @@ int __init arch_xen_unpopulated_init(struct resource **res) * driver to know how much of the physmap is unpopulated and * set an accurate initial memory target. */ - xen_released_pages += xen_extra_mem[i].n_pfns; + xen_unpopulated_pages += xen_extra_mem[i].n_pfns; /* Zero so region is not also added to the balloon driver. */ xen_extra_mem[i].n_pfns = 0; } diff --git a/drivers/xen/balloon.c b/drivers/xen/balloon.c index 49c3f992639435..8c44a25a7d2b95 100644 --- a/drivers/xen/balloon.c +++ b/drivers/xen/balloon.c @@ -724,6 +724,7 @@ static int __init balloon_add_regions(void) static int __init balloon_init(void) { struct task_struct *task; + unsigned long current_pages; int rc; if (!xen_domain()) @@ -731,12 +732,18 @@ static int __init balloon_init(void) pr_info("Initialising balloon driver\n"); - if (xen_released_pages >= get_num_physpages()) { - WARN(1, "Released pages underflow current target"); - return -ERANGE; + if (xen_pv_domain()) { + if (xen_released_pages >= xen_start_info->nr_pages) + goto underflow; + current_pages = min(xen_start_info->nr_pages - + xen_released_pages, max_pfn); + } else { + if (xen_unpopulated_pages >= get_num_physpages()) + goto underflow; + current_pages = get_num_physpages() - xen_unpopulated_pages; } - balloon_stats.current_pages = get_num_physpages() - xen_released_pages; + balloon_stats.current_pages = current_pages; balloon_stats.target_pages = balloon_stats.current_pages; balloon_stats.balloon_low = 0; balloon_stats.balloon_high = 0; @@ -767,6 +774,10 @@ static int __init balloon_init(void) xen_balloon_init(); return 0; + + underflow: + WARN(1, "Released pages underflow current target"); + return -ERANGE; } subsys_initcall(balloon_init); diff --git a/drivers/xen/unpopulated-alloc.c b/drivers/xen/unpopulated-alloc.c index d6fc2aefe2646b..1dc0b495c8e597 100644 --- a/drivers/xen/unpopulated-alloc.c +++ b/drivers/xen/unpopulated-alloc.c @@ -18,6 +18,9 @@ static unsigned int list_count; static struct resource *target_resource; +/* Pages to subtract from the memory count when setting balloon target. */ +unsigned long xen_unpopulated_pages __initdata; + /* * If arch is not happy with system "iomem_resource" being used for * the region allocation it can provide it's own view by creating specific diff --git a/include/xen/xen.h b/include/xen/xen.h index 61854e3f283776..f280c5dcf92369 100644 --- a/include/xen/xen.h +++ b/include/xen/xen.h @@ -69,11 +69,13 @@ extern u64 xen_saved_max_mem_size; #endif #ifdef CONFIG_XEN_UNPOPULATED_ALLOC +extern unsigned long xen_unpopulated_pages; int xen_alloc_unpopulated_pages(unsigned int nr_pages, struct page **pages); void xen_free_unpopulated_pages(unsigned int nr_pages, struct page **pages); #include int arch_xen_unpopulated_init(struct resource **res); #else +#define xen_unpopulated_pages 0UL #include static inline int xen_alloc_unpopulated_pages(unsigned int nr_pages, struct page **pages) From ec10e3dc93994b87adf7c759a4639fe34013989a Mon Sep 17 00:00:00 2001 From: Zilin Guan Date: Mon, 26 Jan 2026 07:15:33 +0000 Subject: [PATCH 3305/3902] md/raid1: fix memory leak in raid1_run() [ Upstream commit 6abc7d5dcf0ee0f85e16e41c87fbd06231f28753 ] raid1_run() calls setup_conf() which registers a thread via md_register_thread(). If raid1_set_limits() fails, the previously registered thread is not unregistered, resulting in a memory leak of the md_thread structure and the thread resource itself. Add md_unregister_thread() to the error path to properly cleanup the thread, which aligns with the error handling logic of other paths in this function. Compile tested only. Issue found using a prototype static analysis tool and code review. Link: https://lore.kernel.org/linux-raid/20260126071533.606263-1-zilin@seu.edu.cn Fixes: 97894f7d3c29 ("md/raid1: use the atomic queue limit update APIs") Signed-off-by: Zilin Guan Reviewed-by: Li Nan Signed-off-by: Yu Kuai Signed-off-by: Sasha Levin --- drivers/md/raid1.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/md/raid1.c b/drivers/md/raid1.c index 592a4023300474..ce7fd688695666 100644 --- a/drivers/md/raid1.c +++ b/drivers/md/raid1.c @@ -3253,6 +3253,7 @@ static int raid1_run(struct mddev *mddev) if (!mddev_is_dm(mddev)) { ret = raid1_set_limits(mddev); if (ret) { + md_unregister_thread(mddev, &conf->thread); if (!mddev->private) raid1_free(mddev, conf); return ret; From 80d58a8915d7d4fcf4343c02522602c85ef916fa Mon Sep 17 00:00:00 2001 From: Xiao Ni Date: Tue, 27 Jan 2026 15:39:27 +0800 Subject: [PATCH 3306/3902] md: fix return value of mddev_trylock [ Upstream commit 05c8de4f09b08e97c6ecb190dcec0e68b167cb03 ] A return value of 0 is treaded as successful lock acquisition. In fact, a return value of 1 means getting the lock successfully. Link: https://lore.kernel.org/linux-raid/20260127073951.17248-1-xni@redhat.com Fixes: 9e59d609763f ("md: call del_gendisk in control path") Reported-by: Bart Van Assche Closes: https://lore.kernel.org/linux-raid/20250611073108.25463-1-xni@redhat.com/T/#mfa369ef5faa4aa58e13e6d9fdb88aecd862b8f2f Signed-off-by: Xiao Ni Reviewed-by: Bart Van Assche Reviewed-by: Li Nan Signed-off-by: Yu Kuai Signed-off-by: Sasha Levin --- drivers/md/md.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/md/md.h b/drivers/md/md.h index fd6e001c1d38f1..9d66afb8cc6e6c 100644 --- a/drivers/md/md.h +++ b/drivers/md/md.h @@ -736,8 +736,8 @@ static inline int mddev_trylock(struct mddev *mddev) int ret; ret = mutex_trylock(&mddev->reconfig_mutex); - if (!ret && test_bit(MD_DELETED, &mddev->flags)) { - ret = -ENODEV; + if (ret && test_bit(MD_DELETED, &mddev->flags)) { + ret = 0; mutex_unlock(&mddev->reconfig_mutex); } return ret; From 1141301a7195416d6947bc557b0cc46dec95899a Mon Sep 17 00:00:00 2001 From: Samuel Wu Date: Fri, 23 Jan 2026 17:21:29 -0800 Subject: [PATCH 3307/3902] PM: wakeup: Handle empty list in wakeup_sources_walk_start() [ Upstream commit 75ce02f4bc9a8b8350b6b1b01872467b0cc960cc ] In the case of an empty wakeup_sources list, wakeup_sources_walk_start() will return an invalid but non-NULL address. This also affects wrappers of the aforementioned function, like for_each_wakeup_source(). Update wakeup_sources_walk_start() to return NULL in case of an empty list. Fixes: b4941adb24c0 ("PM: wakeup: Add routine to help fetch wakeup source object.") Signed-off-by: Samuel Wu [ rjw: Subject and changelog edits ] Link: https://patch.msgid.link/20260124012133.2451708-2-wusamuel@google.com Signed-off-by: Rafael J. Wysocki Signed-off-by: Sasha Levin --- drivers/base/power/wakeup.c | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/drivers/base/power/wakeup.c b/drivers/base/power/wakeup.c index d1283ff1080bbd..2f630df16bfe10 100644 --- a/drivers/base/power/wakeup.c +++ b/drivers/base/power/wakeup.c @@ -276,9 +276,7 @@ EXPORT_SYMBOL_GPL(wakeup_sources_read_unlock); */ struct wakeup_source *wakeup_sources_walk_start(void) { - struct list_head *ws_head = &wakeup_sources; - - return list_entry_rcu(ws_head->next, struct wakeup_source, entry); + return list_first_or_null_rcu(&wakeup_sources, struct wakeup_source, entry); } EXPORT_SYMBOL_GPL(wakeup_sources_walk_start); From c787a235deb33be6eda40beee8f561da5fd8cb8c Mon Sep 17 00:00:00 2001 From: Breno Leitao Date: Mon, 2 Feb 2026 10:32:51 -0800 Subject: [PATCH 3308/3902] arm64/gcs: Fix error handling in arch_set_shadow_stack_status() [ Upstream commit 53c998527ffa60f9deda8974a11ad39790684159 ] alloc_gcs() returns an error-encoded pointer on failure, which comes from do_mmap(), not NULL. The current NULL check fails to detect errors, which could lead to using an invalid GCS address. Use IS_ERR_VALUE() to properly detect errors, consistent with the check in gcs_alloc_thread_stack(). Fixes: b57180c75c7e ("arm64/gcs: Implement shadow stack prctl() interface") Reviewed-by: Mark Brown Signed-off-by: Breno Leitao Signed-off-by: Will Deacon Signed-off-by: Sasha Levin --- arch/arm64/mm/gcs.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/arch/arm64/mm/gcs.c b/arch/arm64/mm/gcs.c index 6e93f78de79b17..04a23a497f2051 100644 --- a/arch/arm64/mm/gcs.c +++ b/arch/arm64/mm/gcs.c @@ -199,8 +199,8 @@ int arch_set_shadow_stack_status(struct task_struct *task, unsigned long arg) size = gcs_size(0); gcs = alloc_gcs(0, size); - if (!gcs) - return -ENOMEM; + if (IS_ERR_VALUE(gcs)) + return gcs; task->thread.gcspr_el0 = gcs + size - sizeof(u64); task->thread.gcs_base = gcs; From 5cbf75e0f07f6597e524924665fb4bc727e7aa1a Mon Sep 17 00:00:00 2001 From: Leo Yan Date: Tue, 3 Feb 2026 14:40:43 +0000 Subject: [PATCH 3309/3902] perf: arm_spe: Properly set hw.state on failures [ Upstream commit 283182c1c239f6873d1a50e9e710c1a699f2256b ] When arm_spe_pmu_next_off() fails to calculate a valid limit, it returns zero to indicate that tracing should not start. However, the caller arm_spe_perf_aux_output_begin() does not propagate this failure by updating hwc->state, cause the error to be silently ignored by upper layers. Because hwc->state remains zero after a failure, arm_spe_pmu_start() continues to programs filter registers unnecessarily. The driver still reports success to the perf core, so the core assumes the SPE event was enabled and proceeds to enable other events. This breaks event group semantics: SPE is already stopped while other events in the same group are enabled. Fix this by updating arm_spe_perf_aux_output_begin() to return a status code indicating success (0) or failure (-EIO). Both the interrupt handler and arm_spe_pmu_start() check the return value and call arm_spe_pmu_stop() to set PERF_HES_STOPPED in hwc->state. In the interrupt handler, the period (e.g., period_left) needs to be updated, so PERF_EF_UPDATE is passed to arm_spe_pmu_stop(). When the error occurs during event start, the trace unit is not yet enabled, so a flag '0' is used to drain buffer and update state only. Fixes: d5d9696b0380 ("drivers/perf: Add support for ARMv8.2 Statistical Profiling Extension") Signed-off-by: Leo Yan Signed-off-by: Will Deacon Signed-off-by: Sasha Levin --- drivers/perf/arm_spe_pmu.c | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/drivers/perf/arm_spe_pmu.c b/drivers/perf/arm_spe_pmu.c index fa50645feddadb..e4e4e63c64c42a 100644 --- a/drivers/perf/arm_spe_pmu.c +++ b/drivers/perf/arm_spe_pmu.c @@ -105,6 +105,8 @@ struct arm_spe_pmu { /* Keep track of our dynamic hotplug state */ static enum cpuhp_state arm_spe_pmu_online; +static void arm_spe_pmu_stop(struct perf_event *event, int flags); + enum arm_spe_pmu_buf_fault_action { SPE_PMU_BUF_FAULT_ACT_SPURIOUS, SPE_PMU_BUF_FAULT_ACT_FATAL, @@ -582,8 +584,8 @@ static u64 arm_spe_pmu_next_off(struct perf_output_handle *handle) return limit; } -static void arm_spe_perf_aux_output_begin(struct perf_output_handle *handle, - struct perf_event *event) +static int arm_spe_perf_aux_output_begin(struct perf_output_handle *handle, + struct perf_event *event) { u64 base, limit; struct arm_spe_pmu_buf *buf; @@ -597,7 +599,6 @@ static void arm_spe_perf_aux_output_begin(struct perf_output_handle *handle, /* Start a new aux session */ buf = perf_aux_output_begin(handle, event); if (!buf) { - event->hw.state |= PERF_HES_STOPPED; /* * We still need to clear the limit pointer, since the * profiler might only be disabled by virtue of a fault. @@ -617,6 +618,7 @@ static void arm_spe_perf_aux_output_begin(struct perf_output_handle *handle, out_write_limit: write_sysreg_s(limit, SYS_PMBLIMITR_EL1); + return (limit & PMBLIMITR_EL1_E) ? 0 : -EIO; } static void arm_spe_perf_aux_output_end(struct perf_output_handle *handle) @@ -756,7 +758,10 @@ static irqreturn_t arm_spe_pmu_irq_handler(int irq, void *dev) * when we get to it. */ if (!(handle->aux_flags & PERF_AUX_FLAG_TRUNCATED)) { - arm_spe_perf_aux_output_begin(handle, event); + if (arm_spe_perf_aux_output_begin(handle, event)) { + arm_spe_pmu_stop(event, PERF_EF_UPDATE); + break; + } isb(); } break; @@ -851,9 +856,10 @@ static void arm_spe_pmu_start(struct perf_event *event, int flags) struct perf_output_handle *handle = this_cpu_ptr(spe_pmu->handle); hwc->state = 0; - arm_spe_perf_aux_output_begin(handle, event); - if (hwc->state) + if (arm_spe_perf_aux_output_begin(handle, event)) { + arm_spe_pmu_stop(event, 0); return; + } reg = arm_spe_event_to_pmsfcr(event); write_sysreg_s(reg, SYS_PMSFCR_EL1); From 630f3eca39624670ed020909192efc06977423ec Mon Sep 17 00:00:00 2001 From: Yaxiong Tian Date: Tue, 3 Feb 2026 10:48:52 +0800 Subject: [PATCH 3310/3902] cpufreq: intel_pstate: Enable asym capacity only when CPU SMT is not possible [ Upstream commit 1fedbb589448bee9f20bb2ed9c850d1d2cf9963c ] According to the description in the intel_pstate.rst documentation, Capacity-Aware Scheduling and Energy-Aware Scheduling are only supported on a hybrid processor without SMT. Previously, the system used sched_smt_active() for judgment, which is not a strict condition because users can switch it on or off via /sys at any time. This could lead to incorrect driver settings in certain scenarios. For example, on a CPU that supports SMT, a user can disable SMT via the nosmt parameter to enable asym capacity, and then re-enable SMT via /sys. In such cases, some settings in the driver would no longer be correct. To address this issue, replace sched_smt_active() with cpu_smt_possible(), and only enable asym capacity when CPU SMT is not possible. Fixes: 929ebc93ccaa ("cpufreq: intel_pstate: Set asymmetric CPU capacity on hybrid systems") Signed-off-by: Yaxiong Tian [ rjw: Subject and changelog edits ] Link: https://patch.msgid.link/20260203024852.301066-1-tianyaxiong@kylinos.cn Signed-off-by: Rafael J. Wysocki Signed-off-by: Sasha Levin --- drivers/cpufreq/intel_pstate.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/cpufreq/intel_pstate.c b/drivers/cpufreq/intel_pstate.c index 492a10f1bdbfa3..38333f7da40db4 100644 --- a/drivers/cpufreq/intel_pstate.c +++ b/drivers/cpufreq/intel_pstate.c @@ -1152,7 +1152,7 @@ static void hybrid_init_cpu_capacity_scaling(bool refresh) * the capacity of SMT threads is not deterministic even approximately, * do not do that when SMT is in use. */ - if (hwp_is_hybrid && !sched_smt_active() && arch_enable_hybrid_capacity_scale()) { + if (hwp_is_hybrid && !cpu_smt_possible() && arch_enable_hybrid_capacity_scale()) { hybrid_refresh_cpu_capacity_scaling(); /* * Disabling ITMT causes sched domains to be rebuilt to disable asym From af5b0854fba03ce47c09ada29d535a9f702bd2ab Mon Sep 17 00:00:00 2001 From: Gui-Dong Han Date: Tue, 3 Feb 2026 11:19:43 +0800 Subject: [PATCH 3311/3902] PM: sleep: wakeirq: harden dev_pm_clear_wake_irq() against races [ Upstream commit 5c9ecd8e6437cd55a38ea4f1e1d19cee8e226cb8 ] dev_pm_clear_wake_irq() currently uses a dangerous pattern where dev->power.wakeirq is read and checked for NULL outside the lock. If two callers invoke this function concurrently, both might see a valid pointer and proceed. This could result in a double-free when the second caller acquires the lock and tries to release the same object. Address this by removing the lockless check of dev->power.wakeirq. Instead, acquire dev->power.lock immediately to ensure the check and the subsequent operations are atomic. If dev->power.wakeirq is NULL under the lock, simply unlock and return. This guarantees that concurrent calls cannot race to free the same object. Based on a quick scan of current users, I did not find an actual bug as drivers seem to rely on their own synchronization. However, since asynchronous usage patterns exist (e.g., in drivers/net/wireless/ti/wlcore), I believe a race is theoretically possible if the API is used less carefully in the future. This change hardens the API to be robust against such cases. Fixes: 4990d4fe327b ("PM / Wakeirq: Add automated device wake IRQ handling") Signed-off-by: Gui-Dong Han Link: https://patch.msgid.link/20260203031943.1924-1-hanguidong02@gmail.com Signed-off-by: Rafael J. Wysocki Signed-off-by: Sasha Levin --- drivers/base/power/wakeirq.c | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/drivers/base/power/wakeirq.c b/drivers/base/power/wakeirq.c index 8aa28c08b2891f..c0809d18fc5406 100644 --- a/drivers/base/power/wakeirq.c +++ b/drivers/base/power/wakeirq.c @@ -83,13 +83,16 @@ EXPORT_SYMBOL_GPL(dev_pm_set_wake_irq); */ void dev_pm_clear_wake_irq(struct device *dev) { - struct wake_irq *wirq = dev->power.wakeirq; + struct wake_irq *wirq; unsigned long flags; - if (!wirq) + spin_lock_irqsave(&dev->power.lock, flags); + wirq = dev->power.wakeirq; + if (!wirq) { + spin_unlock_irqrestore(&dev->power.lock, flags); return; + } - spin_lock_irqsave(&dev->power.lock, flags); device_wakeup_detach_irq(dev); dev->power.wakeirq = NULL; spin_unlock_irqrestore(&dev->power.lock, flags); From 6715560527e343a387e4a0d2e6c401748e89fa55 Mon Sep 17 00:00:00 2001 From: Salah Triki Date: Fri, 30 Jan 2026 21:47:59 +0100 Subject: [PATCH 3312/3902] s390/cio: Fix device lifecycle handling in css_alloc_subchannel() [ Upstream commit f65c75b0b9b5a390bc3beadcde0a6fbc3ad118f7 ] `css_alloc_subchannel()` calls `device_initialize()` before setting up the DMA masks. If `dma_set_coherent_mask()` or `dma_set_mask()` fails, the error path frees the subchannel structure directly, bypassing the device model reference counting. Once `device_initialize()` has been called, the embedded struct device must be released via `put_device()`, allowing the release callback to free the container structure. Fix the error path by dropping the initial device reference with `put_device()` instead of calling `kfree()` directly. This ensures correct device lifetime handling and avoids potential use-after-free or double-free issues. Fixes: e5dcf0025d7af ("s390/css: move subchannel lock allocation") Signed-off-by: Salah Triki Reviewed-by: Vineeth Vijayan Signed-off-by: Heiko Carstens Signed-off-by: Sasha Levin --- drivers/s390/cio/css.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/s390/cio/css.c b/drivers/s390/cio/css.c index be78a57f9bfdef..8a70596a55447c 100644 --- a/drivers/s390/cio/css.c +++ b/drivers/s390/cio/css.c @@ -236,7 +236,7 @@ struct subchannel *css_alloc_subchannel(struct subchannel_id schid, return sch; err: - kfree(sch); + put_device(&sch->dev); return ERR_PTR(ret); } From 445d35742d8636225d01e26f90fb49fa0327d8c1 Mon Sep 17 00:00:00 2001 From: Jens Axboe Date: Thu, 5 Feb 2026 08:38:20 -0700 Subject: [PATCH 3313/3902] io_uring/kbuf: fix memory leak if io_buffer_add_list fails [ Upstream commit 442ae406603a94f1a263654494f425302ceb0445 ] io_register_pbuf_ring() ignores the return value of io_buffer_add_list(), which can fail if xa_store() returns an error (e.g., -ENOMEM). When this happens, the function returns 0 (success) to the caller, but the io_buffer_list structure is neither added to the xarray nor freed. In practice this requires failure injection to hit, hence not a real issue. But it should get fixed up none the less. Fixes: c7fb19428d67 ("io_uring: add support for ring mapped supplied buffers") Signed-off-by: Jens Axboe Signed-off-by: Sasha Levin --- io_uring/kbuf.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/io_uring/kbuf.c b/io_uring/kbuf.c index d974381d93ff75..308ef71bcb286e 100644 --- a/io_uring/kbuf.c +++ b/io_uring/kbuf.c @@ -669,8 +669,9 @@ int io_register_pbuf_ring(struct io_ring_ctx *ctx, void __user *arg) bl->buf_ring = br; if (reg.flags & IOU_PBUF_RING_INC) bl->flags |= IOBL_INC; - io_buffer_add_list(ctx, bl, reg.bgid); - return 0; + ret = io_buffer_add_list(ctx, bl, reg.bgid); + if (!ret) + return 0; fail: io_free_region(ctx, &bl->region); kfree(bl); From 7e4c643aa3b22ec685cd7889f5506d2b015ed80f Mon Sep 17 00:00:00 2001 From: Andrew Cooper Date: Wed, 26 Nov 2025 13:03:52 +0000 Subject: [PATCH 3314/3902] x86/cpu/amd: Correct the microcode table for Zenbleed [ Upstream commit fb7bfa31b8e8569f154f2fe0ea6c2f03c0f087aa ] The good revisions are tied to exact steppings, meaning it's not valid to match on model number alone, let alone a range. This is probably only a latent issue. From public microcode archives, the following CPUs exist 17-30-00, 17-60-00, 17-70-00 and would be captured by the model ranges. They're likely pre-production steppings, and likely didn't get Zenbleed microcode, but it's still incorrect to compare them to a different steppings revision. Either way, convert the logic to use x86_match_min_microcode_rev(), which is the preferred mechanism. Fixes: 522b1d69219d ("x86/cpu/amd: Add a Zenbleed fix") Signed-off-by: Andrew Cooper Signed-off-by: Ingo Molnar Cc: Borislav Petkov Cc: Mario Limonciello Cc: x86@kernel.org Link: https://patch.msgid.link/20251126130352.880424-1-andrew.cooper3@citrix.com Signed-off-by: Sasha Levin --- arch/x86/kernel/cpu/amd.c | 30 +++++++++--------------------- 1 file changed, 9 insertions(+), 21 deletions(-) diff --git a/arch/x86/kernel/cpu/amd.c b/arch/x86/kernel/cpu/amd.c index 5d46709c58d0b8..a92750f3079af0 100644 --- a/arch/x86/kernel/cpu/amd.c +++ b/arch/x86/kernel/cpu/amd.c @@ -951,26 +951,14 @@ static void init_amd_zen1(struct cpuinfo_x86 *c) } } -static bool cpu_has_zenbleed_microcode(void) -{ - u32 good_rev = 0; - - switch (boot_cpu_data.x86_model) { - case 0x30 ... 0x3f: good_rev = 0x0830107b; break; - case 0x60 ... 0x67: good_rev = 0x0860010c; break; - case 0x68 ... 0x6f: good_rev = 0x08608107; break; - case 0x70 ... 0x7f: good_rev = 0x08701033; break; - case 0xa0 ... 0xaf: good_rev = 0x08a00009; break; - - default: - return false; - } - - if (boot_cpu_data.microcode < good_rev) - return false; - - return true; -} +static const struct x86_cpu_id amd_zenbleed_microcode[] = { + ZEN_MODEL_STEP_UCODE(0x17, 0x31, 0x0, 0x0830107b), + ZEN_MODEL_STEP_UCODE(0x17, 0x60, 0x1, 0x0860010c), + ZEN_MODEL_STEP_UCODE(0x17, 0x68, 0x1, 0x08608107), + ZEN_MODEL_STEP_UCODE(0x17, 0x71, 0x0, 0x08701033), + ZEN_MODEL_STEP_UCODE(0x17, 0xa0, 0x0, 0x08a00009), + {} +}; static void zen2_zenbleed_check(struct cpuinfo_x86 *c) { @@ -980,7 +968,7 @@ static void zen2_zenbleed_check(struct cpuinfo_x86 *c) if (!cpu_has(c, X86_FEATURE_AVX)) return; - if (!cpu_has_zenbleed_microcode()) { + if (!x86_match_min_microcode_rev(amd_zenbleed_microcode)) { pr_notice_once("Zenbleed: please update your microcode for the most optimal fix\n"); msr_set_bit(MSR_AMD64_DE_CFG, MSR_AMD64_DE_CFG_ZEN2_FP_BACKUP_FIX_BIT); } else { From 1838a7507e1339cf10b356791d75260151492259 Mon Sep 17 00:00:00 2001 From: Sandipan Das Date: Fri, 5 Dec 2025 16:16:46 -0800 Subject: [PATCH 3315/3902] perf/x86/core: Do not set bit width for unavailable counters [ Upstream commit b456a6ba5756b6fb7e651775343e713bd08418e7 ] Not all x86 processors have fixed counters. It may also be the case that a processor has only fixed counters and no general-purpose counters. Set the bit widths corresponding to each counter type only if such counters are available. Fixes: b3d9468a8bd2 ("perf, x86: Expose perf capability to other modules") Signed-off-by: Sandipan Das Co-developed-by: Dapeng Mi Signed-off-by: Dapeng Mi Signed-off-by: Mingwei Zhang Signed-off-by: Sean Christopherson Signed-off-by: Peter Zijlstra (Intel) Tested-by: Xudong Hao Link: https://patch.msgid.link/20251206001720.468579-11-seanjc@google.com Signed-off-by: Sasha Levin --- arch/x86/events/core.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/arch/x86/events/core.c b/arch/x86/events/core.c index dd9ff120ad437a..56df4855f38e93 100644 --- a/arch/x86/events/core.c +++ b/arch/x86/events/core.c @@ -3101,8 +3101,8 @@ void perf_get_x86_pmu_capability(struct x86_pmu_capability *cap) cap->version = x86_pmu.version; cap->num_counters_gp = x86_pmu_num_counters(NULL); cap->num_counters_fixed = x86_pmu_num_counters_fixed(NULL); - cap->bit_width_gp = x86_pmu.cntval_bits; - cap->bit_width_fixed = x86_pmu.cntval_bits; + cap->bit_width_gp = cap->num_counters_gp ? x86_pmu.cntval_bits : 0; + cap->bit_width_fixed = cap->num_counters_fixed ? x86_pmu.cntval_bits : 0; cap->events_mask = (unsigned int)x86_pmu.events_maskl; cap->events_mask_len = x86_pmu.events_mask_len; cap->pebs_ept = x86_pmu.pebs_ept; From 36abc0b2df605f51e19490d759f823faf8ce5852 Mon Sep 17 00:00:00 2001 From: Giovanni Cabiddu Date: Thu, 20 Nov 2025 16:30:46 +0000 Subject: [PATCH 3316/3902] crypto: qat - fix warning on adf_pfvf_pf_proto.c MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [ Upstream commit 994689b8f91b02fdb5f64cba2412cde5ef3084b5 ] Building the QAT driver with -Wmaybe-uninitialized triggers warnings in qat_common/adf_pfvf_pf_proto.c. Specifically, the variables blk_type, blk_byte, and byte_max may be used uninitialized in handle_blkmsg_req(): make M=drivers/crypto/intel/qat W=1 C=2 "KCFLAGS=-Werror" \ KBUILD_CFLAGS_KERNEL=-Wmaybe-uninitialized \ CFLAGS_MODULE=-Wmaybe-uninitialized ... warning: ‘byte_max’ may be used uninitialized [-Wmaybe-uninitialized] warning: ‘blk_type’ may be used uninitialized [-Wmaybe-uninitialized] warning: ‘blk_byte’ may be used uninitialized [-Wmaybe-uninitialized] Although the caller of handle_blkmsg_req() always provides a req.type that is handled by the switch, the compiler cannot guarantee this. Add a default case to the switch statement to handle an invalid req.type. Fixes: 673184a2a58f ("crypto: qat - introduce support for PFVF block messages") Signed-off-by: Giovanni Cabiddu Reviewed-by: Ahsan Atta Signed-off-by: Herbert Xu Signed-off-by: Sasha Levin --- .../crypto/intel/qat/qat_common/adf_pfvf_pf_proto.c | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/drivers/crypto/intel/qat/qat_common/adf_pfvf_pf_proto.c b/drivers/crypto/intel/qat/qat_common/adf_pfvf_pf_proto.c index b9b5e744a3f163..af8dbc7517cf82 100644 --- a/drivers/crypto/intel/qat/qat_common/adf_pfvf_pf_proto.c +++ b/drivers/crypto/intel/qat/qat_common/adf_pfvf_pf_proto.c @@ -148,6 +148,16 @@ static struct pfvf_message handle_blkmsg_req(struct adf_accel_vf_info *vf_info, blk_byte = FIELD_GET(ADF_VF2PF_SMALL_BLOCK_BYTE_MASK, req.data); byte_max = ADF_VF2PF_SMALL_BLOCK_BYTE_MAX; break; + default: + dev_err(&GET_DEV(vf_info->accel_dev), + "Invalid BlockMsg type 0x%.4x received from VF%u\n", + req.type, vf_info->vf_nr); + resp.type = ADF_PF2VF_MSGTYPE_BLKMSG_RESP; + resp.data = FIELD_PREP(ADF_PF2VF_BLKMSG_RESP_TYPE_MASK, + ADF_PF2VF_BLKMSG_RESP_TYPE_ERROR) | + FIELD_PREP(ADF_PF2VF_BLKMSG_RESP_DATA_MASK, + ADF_PF2VF_UNSPECIFIED_ERROR); + return resp; } /* Is this a request for CRC or data? */ From c2a90d855bea5a3666d29846d9b0da869a91f9bb Mon Sep 17 00:00:00 2001 From: Puranjay Mohan Date: Wed, 31 Dec 2025 14:10:50 -0800 Subject: [PATCH 3317/3902] selftests/bpf: veristat: fix printing order in output_stats() [ Upstream commit c286e7e9d1f1f3d90ad11c37e896f582b02d19c4 ] The order of the variables in the printf() doesn't match the text and therefore veristat prints something like this: Done. Processed 24 files, 0 programs. Skipped 62 files, 0 programs. When it should print: Done. Processed 24 files, 62 programs. Skipped 0 files, 0 programs. Fix the order of variables in the printf() call. Fixes: 518fee8bfaf2 ("selftests/bpf: make veristat skip non-BPF and failing-to-open BPF objects") Tested-by: Eduard Zingerman Signed-off-by: Puranjay Mohan Link: https://lore.kernel.org/r/20251231221052.759396-1-puranjay@kernel.org Signed-off-by: Alexei Starovoitov Signed-off-by: Sasha Levin --- tools/testing/selftests/bpf/veristat.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/testing/selftests/bpf/veristat.c b/tools/testing/selftests/bpf/veristat.c index e962f133250c7d..1be1e353d40a7b 100644 --- a/tools/testing/selftests/bpf/veristat.c +++ b/tools/testing/selftests/bpf/veristat.c @@ -2580,7 +2580,7 @@ static void output_stats(const struct verif_stats *s, enum resfmt fmt, bool last if (last && fmt == RESFMT_TABLE) { output_header_underlines(); printf("Done. Processed %d files, %d programs. Skipped %d files, %d programs.\n", - env.files_processed, env.files_skipped, env.progs_processed, env.progs_skipped); + env.files_processed, env.progs_processed, env.files_skipped, env.progs_skipped); } } From c3cfe342d1209562f4e2d1bab6414035fe337f91 Mon Sep 17 00:00:00 2001 From: Varun R Mallya Date: Wed, 7 Jan 2026 05:05:27 +0530 Subject: [PATCH 3318/3902] libbpf: Fix OOB read in btf_dump_get_bitfield_value [ Upstream commit 5714ca8cba5ed736f3733663c446cbee63a10a64 ] When dumping bitfield data, btf_dump_get_bitfield_value() reads data based on the underlying type's size (t->size). However, it does not verify that the provided data buffer (data_sz) is large enough to contain these bytes. If btf_dump__dump_type_data() is called with a buffer smaller than the type's size, this leads to an out-of-bounds read. This was confirmed by AddressSanitizer in the linked issue. Fix this by ensuring we do not read past the provided data_sz limit. Fixes: a1d3cc3c5eca ("libbpf: Avoid use of __int128 in typed dump display") Reported-by: Harrison Green Suggested-by: Alan Maguire Signed-off-by: Varun R Mallya Signed-off-by: Andrii Nakryiko Link: https://lore.kernel.org/bpf/20260106233527.163487-1-varunrmallya@gmail.com Closes: https://github.com/libbpf/libbpf/issues/928 Signed-off-by: Sasha Levin --- tools/lib/bpf/btf_dump.c | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/tools/lib/bpf/btf_dump.c b/tools/lib/bpf/btf_dump.c index 6388392f49a0b5..53c6624161d797 100644 --- a/tools/lib/bpf/btf_dump.c +++ b/tools/lib/bpf/btf_dump.c @@ -1762,9 +1762,18 @@ static int btf_dump_get_bitfield_value(struct btf_dump *d, __u16 left_shift_bits, right_shift_bits; const __u8 *bytes = data; __u8 nr_copy_bits; + __u8 start_bit, nr_bytes; __u64 num = 0; int i; + /* Calculate how many bytes cover the bitfield */ + start_bit = bits_offset % 8; + nr_bytes = (start_bit + bit_sz + 7) / 8; + + /* Bound check */ + if (data + nr_bytes > d->typed_dump->data_end) + return -E2BIG; + /* Maximum supported bitfield size is 64 bits */ if (t->size > 8) { pr_warn("unexpected bitfield size %d\n", t->size); From 26cbfd67fdb5e11f74f1e44fecc11505717e6a93 Mon Sep 17 00:00:00 2001 From: Gabriele Monaco Date: Fri, 5 Dec 2025 14:16:16 +0100 Subject: [PATCH 3319/3902] sched: Export hidden tracepoints to modules [ Upstream commit 6c125b85f3c87b4bf7dba91af6f27d9600b9dba0 ] The tracepoints sched_entry, sched_exit and sched_set_need_resched are not exported to tracefs as trace events, this allows only kernel code to access them. Helper modules like [1] can be used to still have the tracepoints available to ftrace for debugging purposes, but they do rely on the tracepoints being exported. Export the 3 not exported tracepoints. Note that sched_set_state is already exported as the macro is called from modules. [1] - https://github.com/qais-yousef/sched_tp.git Fixes: adcc3bfa8806 ("sched: Adapt sched tracepoints for RV task model") Signed-off-by: Gabriele Monaco Signed-off-by: Peter Zijlstra (Intel) Reviewed-by: Phil Auld Link: https://patch.msgid.link/20251205131621.135513-9-gmonaco@redhat.com Signed-off-by: Sasha Levin --- kernel/sched/core.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/kernel/sched/core.c b/kernel/sched/core.c index e460c22de8ad49..c1e4d8a5947cf9 100644 --- a/kernel/sched/core.c +++ b/kernel/sched/core.c @@ -119,6 +119,9 @@ EXPORT_TRACEPOINT_SYMBOL_GPL(sched_util_est_cfs_tp); EXPORT_TRACEPOINT_SYMBOL_GPL(sched_util_est_se_tp); EXPORT_TRACEPOINT_SYMBOL_GPL(sched_update_nr_running_tp); EXPORT_TRACEPOINT_SYMBOL_GPL(sched_compute_energy_tp); +EXPORT_TRACEPOINT_SYMBOL_GPL(sched_entry_tp); +EXPORT_TRACEPOINT_SYMBOL_GPL(sched_exit_tp); +EXPORT_TRACEPOINT_SYMBOL_GPL(sched_set_need_resched_tp); DEFINE_PER_CPU_SHARED_ALIGNED(struct rq, runqueues); DEFINE_PER_CPU(struct rnd_state, sched_rnd_state); From 9b94124cb9c239ab369b85a3a9a660a5f331557a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20Wei=C3=9Fschuh?= Date: Tue, 23 Dec 2025 07:59:17 +0100 Subject: [PATCH 3320/3902] ARM: VDSO: Patch out __vdso_clock_getres() if unavailable MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [ Upstream commit b9fecf0dddfc55cd7d02b0011494da3c613f7cde ] The vDSO code hides symbols which are non-functional. __vdso_clock_getres() was not added to this list when it got introduced. Fixes: 052e76a31b4a ("ARM: 8931/1: Add clock_getres entry point") Signed-off-by: Thomas Weißschuh Signed-off-by: Thomas Gleixner Link: https://patch.msgid.link/20251223-vdso-compat-time32-v1-6-97ea7a06a543@linutronix.de Signed-off-by: Sasha Levin --- arch/arm/kernel/vdso.c | 1 + 1 file changed, 1 insertion(+) diff --git a/arch/arm/kernel/vdso.c b/arch/arm/kernel/vdso.c index e38a30477f3d70..566c40f0f7c77b 100644 --- a/arch/arm/kernel/vdso.c +++ b/arch/arm/kernel/vdso.c @@ -161,6 +161,7 @@ static void __init patch_vdso(void *ehdr) vdso_nullpatch_one(&einfo, "__vdso_gettimeofday"); vdso_nullpatch_one(&einfo, "__vdso_clock_gettime"); vdso_nullpatch_one(&einfo, "__vdso_clock_gettime64"); + vdso_nullpatch_one(&einfo, "__vdso_clock_getres"); } } From 8ed7f3c03d0426b786e7941df36d1b5b2b7b9d24 Mon Sep 17 00:00:00 2001 From: Thomas Gleixner Date: Tue, 13 Jan 2026 17:47:37 +0100 Subject: [PATCH 3321/3902] time/sched_clock: Use ACCESS_PRIVATE() to evaluate hrtimer::function [ Upstream commit 3db5306b0bd562ac0fe7eddad26c60ebb6f5fdd4 ] This dereference of sched_clock_timer::function was missed when the hrtimer callback function pointer was marked private. Fixes: 04257da0c99c ("hrtimers: Make callback function pointer private") Reported-by: kernel test robot Signed-off-by: Thomas Gleixner Link: https://patch.msgid.link/875x95jw7q.ffs@tglx Closes: https://lore.kernel.org/oe-kbuild-all/202601131713.KsxhXQ0M-lkp@intel.com/ Signed-off-by: Sasha Levin --- kernel/time/sched_clock.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/kernel/time/sched_clock.c b/kernel/time/sched_clock.c index cc1afec306b3f0..425d429906d0d1 100644 --- a/kernel/time/sched_clock.c +++ b/kernel/time/sched_clock.c @@ -215,7 +215,7 @@ void sched_clock_register(u64 (*read)(void), int bits, unsigned long rate) update_clock_read_data(&rd); - if (sched_clock_timer.function != NULL) { + if (ACCESS_PRIVATE(&sched_clock_timer, function) != NULL) { /* update timeout for clock wrap */ hrtimer_start(&sched_clock_timer, cd.wrap_kt, HRTIMER_MODE_REL_HARD); From 9029e8db69fb30f7d19b5d0917547c7872389520 Mon Sep 17 00:00:00 2001 From: Gabriele Monaco Date: Mon, 12 Jan 2026 15:04:13 +0100 Subject: [PATCH 3322/3902] sched: Fix build for modules using set_tsk_need_resched() [ Upstream commit 8d737320166bd145af70a3133a9964b00ca81cba ] Commit adcc3bfa8806 ("sched: Adapt sched tracepoints for RV task model") added a tracepoint to the need_resched action that can be triggered also by set_tsk_need_resched. This function was previously accessible from out-of-tree modules but it's no longer available because the __trace_set_need_resched() symbol is not exported (together with the tracepoint itself, which was exported in a separate patch) and building such modules fails. Export __trace_set_need_resched to modules to fix those build issues. Fixes: adcc3bfa8806 ("sched: Adapt sched tracepoints for RV task model") Signed-off-by: Gabriele Monaco Signed-off-by: Peter Zijlstra (Intel) Reviewed-by: Phil Auld Link: https://patch.msgid.link/20260112140413.362202-1-gmonaco@redhat.com Signed-off-by: Sasha Levin --- kernel/sched/core.c | 1 + 1 file changed, 1 insertion(+) diff --git a/kernel/sched/core.c b/kernel/sched/core.c index c1e4d8a5947cf9..582c3847f483a5 100644 --- a/kernel/sched/core.c +++ b/kernel/sched/core.c @@ -1139,6 +1139,7 @@ void __trace_set_need_resched(struct task_struct *curr, int tif) { trace_sched_set_need_resched_tp(curr, smp_processor_id(), tif); } +EXPORT_SYMBOL_GPL(__trace_set_need_resched); void resched_curr(struct rq *rq) { From 2bdeabe2b6478660ae15d8701a9311d9a1850a03 Mon Sep 17 00:00:00 2001 From: Thomas Fourier Date: Thu, 18 Dec 2025 10:56:45 +0100 Subject: [PATCH 3323/3902] crypto: cavium - fix dma_free_coherent() size [ Upstream commit 941676c30ba5b40a01bed92448f457ce62fd1f07 ] The size of the buffer in alloc_command_queues() is curr->size + CPT_NEXT_CHUNK_PTR_SIZE, so used that length for dma_free_coherent(). Fixes: c694b233295b ("crypto: cavium - Add the Virtual Function driver for CPT") Signed-off-by: Thomas Fourier Signed-off-by: Herbert Xu Signed-off-by: Sasha Levin --- drivers/crypto/cavium/cpt/cptvf_main.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/drivers/crypto/cavium/cpt/cptvf_main.c b/drivers/crypto/cavium/cpt/cptvf_main.c index c246920e6f540c..bccd680c7f7ee4 100644 --- a/drivers/crypto/cavium/cpt/cptvf_main.c +++ b/drivers/crypto/cavium/cpt/cptvf_main.c @@ -180,7 +180,8 @@ static void free_command_queues(struct cpt_vf *cptvf, hlist_for_each_entry_safe(chunk, node, &cqinfo->queue[i].chead, nextchunk) { - dma_free_coherent(&pdev->dev, chunk->size, + dma_free_coherent(&pdev->dev, + chunk->size + CPT_NEXT_CHUNK_PTR_SIZE, chunk->head, chunk->dma_addr); chunk->head = NULL; From c6eb4991a17762a0ffa09511ec9a761b15407f2f Mon Sep 17 00:00:00 2001 From: Thomas Fourier Date: Thu, 18 Dec 2025 11:12:57 +0100 Subject: [PATCH 3324/3902] crypto: octeontx - fix dma_free_coherent() size [ Upstream commit 624a6760bf8464965c17c8df10b40b557eaa3002 ] The size of the buffer in alloc_command_queues() is curr->size + OTX_CPT_NEXT_CHUNK_PTR_SIZE, so used that length for dma_free_coherent(). Fixes: 10b4f09491bf ("crypto: marvell - add the Virtual Function driver for CPT") Signed-off-by: Thomas Fourier Signed-off-by: Herbert Xu Signed-off-by: Sasha Levin --- drivers/crypto/marvell/octeontx/otx_cptvf_main.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/drivers/crypto/marvell/octeontx/otx_cptvf_main.c b/drivers/crypto/marvell/octeontx/otx_cptvf_main.c index 88a41d1ca5f644..6c0bfb3ea1c9f2 100644 --- a/drivers/crypto/marvell/octeontx/otx_cptvf_main.c +++ b/drivers/crypto/marvell/octeontx/otx_cptvf_main.c @@ -168,7 +168,8 @@ static void free_command_queues(struct otx_cptvf *cptvf, chunk = list_first_entry(&cqinfo->queue[i].chead, struct otx_cpt_cmd_chunk, nextchunk); - dma_free_coherent(&pdev->dev, chunk->size, + dma_free_coherent(&pdev->dev, + chunk->size + OTX_CPT_NEXT_CHUNK_PTR_SIZE, chunk->head, chunk->dma_addr); chunk->head = NULL; From 1f269645bb4c8d9b0911b75fc6972f8b61d6b75e Mon Sep 17 00:00:00 2001 From: Chenghai Huang Date: Thu, 18 Dec 2025 21:44:42 +0800 Subject: [PATCH 3325/3902] crypto: hisilicon/zip - adjust the way to obtain the req in the callback function [ Upstream commit 19c2475ce1984cf675ebfbbeaa5509b2fb1887d6 ] In the shared queue design, multiple tfms use same qp, and one qp need to corresponds to multiple qp_ctx. So use tag to obtain the req virtual address. Build a one-to-one relationship between tfm and qp_ctx. finaly remove the old get_tag operation. Fixes: 2bcf36348ce5 ("crypto: hisilicon/zip - initialize operations about 'sqe' in 'acomp_alg.init'") Signed-off-by: Chenghai Huang Signed-off-by: Weili Qian Signed-off-by: Herbert Xu Signed-off-by: Sasha Levin --- drivers/crypto/hisilicon/zip/zip_crypto.c | 24 +++++++++-------------- 1 file changed, 9 insertions(+), 15 deletions(-) diff --git a/drivers/crypto/hisilicon/zip/zip_crypto.c b/drivers/crypto/hisilicon/zip/zip_crypto.c index b97513981a3b79..b4a656e0177d2d 100644 --- a/drivers/crypto/hisilicon/zip/zip_crypto.c +++ b/drivers/crypto/hisilicon/zip/zip_crypto.c @@ -39,6 +39,7 @@ enum { HZIP_CTX_Q_NUM }; +#define GET_REQ_FROM_SQE(sqe) ((u64)(sqe)->dw26 | (u64)(sqe)->dw27 << 32) #define COMP_NAME_TO_TYPE(alg_name) \ (!strcmp((alg_name), "deflate") ? HZIP_ALG_TYPE_DEFLATE : 0) @@ -48,6 +49,7 @@ struct hisi_zip_req { struct hisi_acc_hw_sgl *hw_dst; dma_addr_t dma_src; dma_addr_t dma_dst; + struct hisi_zip_qp_ctx *qp_ctx; u16 req_id; }; @@ -74,7 +76,6 @@ struct hisi_zip_sqe_ops { void (*fill_req_type)(struct hisi_zip_sqe *sqe, u8 req_type); void (*fill_tag)(struct hisi_zip_sqe *sqe, struct hisi_zip_req *req); void (*fill_sqe_type)(struct hisi_zip_sqe *sqe, u8 sqe_type); - u32 (*get_tag)(struct hisi_zip_sqe *sqe); u32 (*get_status)(struct hisi_zip_sqe *sqe); u32 (*get_dstlen)(struct hisi_zip_sqe *sqe); }; @@ -131,6 +132,7 @@ static struct hisi_zip_req *hisi_zip_create_req(struct hisi_zip_qp_ctx *qp_ctx, req_cache = q + req_id; req_cache->req_id = req_id; req_cache->req = req; + req_cache->qp_ctx = qp_ctx; return req_cache; } @@ -181,7 +183,8 @@ static void hisi_zip_fill_req_type(struct hisi_zip_sqe *sqe, u8 req_type) static void hisi_zip_fill_tag(struct hisi_zip_sqe *sqe, struct hisi_zip_req *req) { - sqe->dw26 = req->req_id; + sqe->dw26 = lower_32_bits((u64)req); + sqe->dw27 = upper_32_bits((u64)req); } static void hisi_zip_fill_sqe_type(struct hisi_zip_sqe *sqe, u8 sqe_type) @@ -237,7 +240,7 @@ static int hisi_zip_do_work(struct hisi_zip_qp_ctx *qp_ctx, &req->dma_dst, DMA_FROM_DEVICE); if (IS_ERR(req->hw_dst)) { ret = PTR_ERR(req->hw_dst); - dev_err(dev, "failed to map the dst buffer to hw slg (%d)!\n", + dev_err(dev, "failed to map the dst buffer to hw sgl (%d)!\n", ret); goto err_unmap_input; } @@ -265,11 +268,6 @@ static int hisi_zip_do_work(struct hisi_zip_qp_ctx *qp_ctx, return ret; } -static u32 hisi_zip_get_tag(struct hisi_zip_sqe *sqe) -{ - return sqe->dw26; -} - static u32 hisi_zip_get_status(struct hisi_zip_sqe *sqe) { return sqe->dw3 & HZIP_BD_STATUS_M; @@ -282,14 +280,12 @@ static u32 hisi_zip_get_dstlen(struct hisi_zip_sqe *sqe) static void hisi_zip_acomp_cb(struct hisi_qp *qp, void *data) { - struct hisi_zip_qp_ctx *qp_ctx = qp->qp_ctx; + struct hisi_zip_sqe *sqe = data; + struct hisi_zip_req *req = (struct hisi_zip_req *)GET_REQ_FROM_SQE(sqe); + struct hisi_zip_qp_ctx *qp_ctx = req->qp_ctx; const struct hisi_zip_sqe_ops *ops = qp_ctx->ctx->ops; struct hisi_zip_dfx *dfx = &qp_ctx->zip_dev->dfx; - struct hisi_zip_req_q *req_q = &qp_ctx->req_q; struct device *dev = &qp->qm->pdev->dev; - struct hisi_zip_sqe *sqe = data; - u32 tag = ops->get_tag(sqe); - struct hisi_zip_req *req = req_q->q + tag; struct acomp_req *acomp_req = req->req; int err = 0; u32 status; @@ -393,7 +389,6 @@ static const struct hisi_zip_sqe_ops hisi_zip_ops = { .fill_req_type = hisi_zip_fill_req_type, .fill_tag = hisi_zip_fill_tag, .fill_sqe_type = hisi_zip_fill_sqe_type, - .get_tag = hisi_zip_get_tag, .get_status = hisi_zip_get_status, .get_dstlen = hisi_zip_get_dstlen, }; @@ -581,7 +576,6 @@ static void hisi_zip_acomp_exit(struct crypto_acomp *tfm) { struct hisi_zip_ctx *ctx = crypto_tfm_ctx(&tfm->base); - hisi_zip_set_acomp_cb(ctx, NULL); hisi_zip_release_sgl_pool(ctx); hisi_zip_release_req_q(ctx); hisi_zip_ctx_exit(ctx); From 139e24de706eddc3e59888183a3cd601576c1621 Mon Sep 17 00:00:00 2001 From: Chenghai Huang Date: Thu, 18 Dec 2025 21:44:43 +0800 Subject: [PATCH 3326/3902] crypto: hisilicon/sec - move backlog management to qp and store sqe in qp for callback [ Upstream commit 08eb67d23e5172a5d1e60f1f0acccee569fe10ba ] When multiple tfm use a same qp, the backlog data should be managed centrally by the qp, rather than in the qp_ctx of each req. Additionally, since SEC_BD_TYPE1 and SEC_BD_TYPE2 cannot use the tag of the sqe to carry the virtual address of the req, the sent sqe is stored in the qp. This allows the callback function to get the req address. To handle the differences between hardware types, the callback functions are split into two separate implementations. Fixes: f0ae287c5045 ("crypto: hisilicon/sec2 - implement full backlog mode for sec") Signed-off-by: Chenghai Huang Signed-off-by: Weili Qian Signed-off-by: Herbert Xu Signed-off-by: Sasha Levin --- drivers/crypto/hisilicon/qm.c | 20 ++++- drivers/crypto/hisilicon/sec2/sec.h | 7 -- drivers/crypto/hisilicon/sec2/sec_crypto.c | 88 +++++++++++----------- include/linux/hisi_acc_qm.h | 8 ++ 4 files changed, 69 insertions(+), 54 deletions(-) diff --git a/drivers/crypto/hisilicon/qm.c b/drivers/crypto/hisilicon/qm.c index 0968304c0cb510..6bf91735dfcd84 100644 --- a/drivers/crypto/hisilicon/qm.c +++ b/drivers/crypto/hisilicon/qm.c @@ -2210,6 +2210,7 @@ static void qp_stop_fail_cb(struct hisi_qp *qp) for (i = 0; i < qp_used; i++) { pos = (i + cur_head) % sq_depth; qp->req_cb(qp, qp->sqe + (u32)(qm->sqe_size * pos)); + qm_cq_head_update(qp); atomic_dec(&qp->qp_status.used); } } @@ -2374,6 +2375,7 @@ int hisi_qp_send(struct hisi_qp *qp, const void *msg) return -EBUSY; memcpy(sqe, msg, qp->qm->sqe_size); + qp->msg[sq_tail] = msg; qm_db(qp->qm, qp->qp_id, QM_DOORBELL_CMD_SQ, sq_tail_next, 0); atomic_inc(&qp->qp_status.used); @@ -2907,12 +2909,13 @@ EXPORT_SYMBOL_GPL(hisi_qm_wait_task_finish); static void hisi_qp_memory_uninit(struct hisi_qm *qm, int num) { struct device *dev = &qm->pdev->dev; - struct qm_dma *qdma; + struct hisi_qp *qp; int i; for (i = num - 1; i >= 0; i--) { - qdma = &qm->qp_array[i].qdma; - dma_free_coherent(dev, qdma->size, qdma->va, qdma->dma); + qp = &qm->qp_array[i]; + dma_free_coherent(dev, qp->qdma.size, qp->qdma.va, qp->qdma.dma); + kfree(qp->msg); kfree(qm->poll_data[i].qp_finish_id); } @@ -2934,10 +2937,14 @@ static int hisi_qp_memory_init(struct hisi_qm *qm, size_t dma_size, int id, return -ENOMEM; qp = &qm->qp_array[id]; + qp->msg = kmalloc_array(sq_depth, sizeof(void *), GFP_KERNEL); + if (!qp->msg) + goto err_free_qp_finish_id; + qp->qdma.va = dma_alloc_coherent(dev, dma_size, &qp->qdma.dma, GFP_KERNEL); if (!qp->qdma.va) - goto err_free_qp_finish_id; + goto err_free_qp_msg; qp->sqe = qp->qdma.va; qp->sqe_dma = qp->qdma.dma; @@ -2949,8 +2956,13 @@ static int hisi_qp_memory_init(struct hisi_qm *qm, size_t dma_size, int id, qp->qm = qm; qp->qp_id = id; + spin_lock_init(&qp->backlog.lock); + INIT_LIST_HEAD(&qp->backlog.list); + return 0; +err_free_qp_msg: + kfree(qp->msg); err_free_qp_finish_id: kfree(qm->poll_data[id].qp_finish_id); return ret; diff --git a/drivers/crypto/hisilicon/sec2/sec.h b/drivers/crypto/hisilicon/sec2/sec.h index 81d0beda93b2bb..0710977861f323 100644 --- a/drivers/crypto/hisilicon/sec2/sec.h +++ b/drivers/crypto/hisilicon/sec2/sec.h @@ -82,11 +82,6 @@ struct sec_aead_req { __u8 out_mac_buf[SEC_MAX_MAC_LEN]; }; -struct sec_instance_backlog { - struct list_head list; - spinlock_t lock; -}; - /* SEC request of Crypto */ struct sec_req { union { @@ -112,7 +107,6 @@ struct sec_req { bool use_pbuf; struct list_head list; - struct sec_instance_backlog *backlog; struct sec_request_buf buf; }; @@ -172,7 +166,6 @@ struct sec_qp_ctx { spinlock_t id_lock; struct hisi_acc_sgl_pool *c_in_pool; struct hisi_acc_sgl_pool *c_out_pool; - struct sec_instance_backlog backlog; u16 send_head; }; diff --git a/drivers/crypto/hisilicon/sec2/sec_crypto.c b/drivers/crypto/hisilicon/sec2/sec_crypto.c index 31590d01139a37..4e41235116e15d 100644 --- a/drivers/crypto/hisilicon/sec2/sec_crypto.c +++ b/drivers/crypto/hisilicon/sec2/sec_crypto.c @@ -54,7 +54,6 @@ #define SEC_AUTH_CIPHER_V3 0x40 #define SEC_FLAG_OFFSET 7 #define SEC_FLAG_MASK 0x0780 -#define SEC_TYPE_MASK 0x0F #define SEC_DONE_MASK 0x0001 #define SEC_ICV_MASK 0x000E @@ -148,7 +147,7 @@ static void sec_free_req_id(struct sec_req *req) spin_unlock_bh(&qp_ctx->id_lock); } -static u8 pre_parse_finished_bd(struct bd_status *status, void *resp) +static void pre_parse_finished_bd(struct bd_status *status, void *resp) { struct sec_sqe *bd = resp; @@ -158,11 +157,9 @@ static u8 pre_parse_finished_bd(struct bd_status *status, void *resp) SEC_FLAG_MASK) >> SEC_FLAG_OFFSET; status->tag = le16_to_cpu(bd->type2.tag); status->err_type = bd->type2.error_type; - - return bd->type_cipher_auth & SEC_TYPE_MASK; } -static u8 pre_parse_finished_bd3(struct bd_status *status, void *resp) +static void pre_parse_finished_bd3(struct bd_status *status, void *resp) { struct sec_sqe3 *bd3 = resp; @@ -172,8 +169,6 @@ static u8 pre_parse_finished_bd3(struct bd_status *status, void *resp) SEC_FLAG_MASK) >> SEC_FLAG_OFFSET; status->tag = le64_to_cpu(bd3->tag); status->err_type = bd3->error_type; - - return le32_to_cpu(bd3->bd_param) & SEC_TYPE_MASK; } static int sec_cb_status_check(struct sec_req *req, @@ -244,7 +239,7 @@ static void sec_alg_send_backlog_soft(struct sec_ctx *ctx, struct sec_qp_ctx *qp struct sec_req *req, *tmp; int ret; - list_for_each_entry_safe(req, tmp, &qp_ctx->backlog.list, list) { + list_for_each_entry_safe(req, tmp, &qp_ctx->qp->backlog.list, list) { list_del(&req->list); ctx->req_op->buf_unmap(ctx, req); if (req->req_id >= 0) @@ -265,11 +260,12 @@ static void sec_alg_send_backlog_soft(struct sec_ctx *ctx, struct sec_qp_ctx *qp static void sec_alg_send_backlog(struct sec_ctx *ctx, struct sec_qp_ctx *qp_ctx) { + struct hisi_qp *qp = qp_ctx->qp; struct sec_req *req, *tmp; int ret; - spin_lock_bh(&qp_ctx->backlog.lock); - list_for_each_entry_safe(req, tmp, &qp_ctx->backlog.list, list) { + spin_lock_bh(&qp->backlog.lock); + list_for_each_entry_safe(req, tmp, &qp->backlog.list, list) { ret = qp_send_message(req); switch (ret) { case -EINPROGRESS: @@ -287,42 +283,46 @@ static void sec_alg_send_backlog(struct sec_ctx *ctx, struct sec_qp_ctx *qp_ctx) } unlock: - spin_unlock_bh(&qp_ctx->backlog.lock); + spin_unlock_bh(&qp->backlog.lock); } static void sec_req_cb(struct hisi_qp *qp, void *resp) { - struct sec_qp_ctx *qp_ctx = qp->qp_ctx; - struct sec_dfx *dfx = &qp_ctx->ctx->sec->debug.dfx; - u8 type_supported = qp_ctx->ctx->type_supported; + const struct sec_sqe *sqe = qp->msg[qp->qp_status.cq_head]; + struct sec_req *req = container_of(sqe, struct sec_req, sec_sqe); + struct sec_ctx *ctx = req->ctx; + struct sec_dfx *dfx = &ctx->sec->debug.dfx; struct bd_status status; - struct sec_ctx *ctx; - struct sec_req *req; int err; - u8 type; - if (type_supported == SEC_BD_TYPE2) { - type = pre_parse_finished_bd(&status, resp); - req = qp_ctx->req_list[status.tag]; - } else { - type = pre_parse_finished_bd3(&status, resp); - req = (void *)(uintptr_t)status.tag; - } + pre_parse_finished_bd(&status, resp); - if (unlikely(type != type_supported)) { - atomic64_inc(&dfx->err_bd_cnt); - pr_err("err bd type [%u]\n", type); - return; - } + req->err_type = status.err_type; + err = sec_cb_status_check(req, &status); + if (err) + atomic64_inc(&dfx->done_flag_cnt); - if (unlikely(!req)) { - atomic64_inc(&dfx->invalid_req_cnt); - atomic_inc(&qp->qp_status.used); - return; - } + atomic64_inc(&dfx->recv_cnt); + ctx->req_op->buf_unmap(ctx, req); + ctx->req_op->callback(ctx, req, err); +} + +static void sec_req_cb3(struct hisi_qp *qp, void *resp) +{ + struct bd_status status; + struct sec_ctx *ctx; + struct sec_dfx *dfx; + struct sec_req *req; + int err; + + pre_parse_finished_bd3(&status, resp); + + req = (void *)(uintptr_t)status.tag; req->err_type = status.err_type; ctx = req->ctx; + dfx = &ctx->sec->debug.dfx; + err = sec_cb_status_check(req, &status); if (err) atomic64_inc(&dfx->done_flag_cnt); @@ -330,7 +330,6 @@ static void sec_req_cb(struct hisi_qp *qp, void *resp) atomic64_inc(&dfx->recv_cnt); ctx->req_op->buf_unmap(ctx, req); - ctx->req_op->callback(ctx, req, err); } @@ -348,8 +347,10 @@ static int sec_alg_send_message_retry(struct sec_req *req) static int sec_alg_try_enqueue(struct sec_req *req) { + struct hisi_qp *qp = req->qp_ctx->qp; + /* Check if any request is already backlogged */ - if (!list_empty(&req->backlog->list)) + if (!list_empty(&qp->backlog.list)) return -EBUSY; /* Try to enqueue to HW ring */ @@ -359,17 +360,18 @@ static int sec_alg_try_enqueue(struct sec_req *req) static int sec_alg_send_message_maybacklog(struct sec_req *req) { + struct hisi_qp *qp = req->qp_ctx->qp; int ret; ret = sec_alg_try_enqueue(req); if (ret != -EBUSY) return ret; - spin_lock_bh(&req->backlog->lock); + spin_lock_bh(&qp->backlog.lock); ret = sec_alg_try_enqueue(req); if (ret == -EBUSY) - list_add_tail(&req->list, &req->backlog->list); - spin_unlock_bh(&req->backlog->lock); + list_add_tail(&req->list, &qp->backlog.list); + spin_unlock_bh(&qp->backlog.lock); return ret; } @@ -629,13 +631,14 @@ static int sec_create_qp_ctx(struct sec_ctx *ctx, int qp_ctx_id) qp_ctx->qp = qp; qp_ctx->ctx = ctx; - qp->req_cb = sec_req_cb; + if (ctx->type_supported == SEC_BD_TYPE3) + qp->req_cb = sec_req_cb3; + else + qp->req_cb = sec_req_cb; spin_lock_init(&qp_ctx->req_lock); idr_init(&qp_ctx->req_idr); - spin_lock_init(&qp_ctx->backlog.lock); spin_lock_init(&qp_ctx->id_lock); - INIT_LIST_HEAD(&qp_ctx->backlog.list); qp_ctx->send_head = 0; ret = sec_alloc_qp_ctx_resource(ctx, qp_ctx); @@ -1952,7 +1955,6 @@ static int sec_request_init(struct sec_ctx *ctx, struct sec_req *req) } while (req->req_id < 0 && ++i < ctx->sec->ctx_q_num); req->qp_ctx = qp_ctx; - req->backlog = &qp_ctx->backlog; return 0; } diff --git a/include/linux/hisi_acc_qm.h b/include/linux/hisi_acc_qm.h index c4690e365ade90..2d0cc61ed88693 100644 --- a/include/linux/hisi_acc_qm.h +++ b/include/linux/hisi_acc_qm.h @@ -444,6 +444,11 @@ struct hisi_qp_ops { int (*fill_sqe)(void *sqe, void *q_parm, void *d_parm); }; +struct instance_backlog { + struct list_head list; + spinlock_t lock; +}; + struct hisi_qp { u32 qp_id; u16 sq_depth; @@ -468,6 +473,9 @@ struct hisi_qp { bool is_in_kernel; u16 pasid; struct uacce_queue *uacce_q; + + struct instance_backlog backlog; + const void **msg; }; static inline int vfs_num_set(const char *val, const struct kernel_param *kp) From 2b8b12e3f869d1a8cd4ecde537e2d26deb9df04a Mon Sep 17 00:00:00 2001 From: lizhi Date: Thu, 18 Dec 2025 21:44:44 +0800 Subject: [PATCH 3327/3902] crypto: hisilicon/hpre: extend tag field to 64 bits for better performance [ Upstream commit 3a1984758197f7fd4c557dd98090e8e0cf9f498e ] This commit expands the tag field in hpre_sqe structure from 16-bit to 64-bit. The change enables storing request addresses directly in the tag field, allowing callback functions to access request messages without the previous indirection mechanism. By eliminating the need for lookup tables, this modification reduces lock contention and associated overhead, leading to improved efficiency and simplified code. Fixes: c8b4b477079d ("crypto: hisilicon - add HiSilicon HPRE accelerator") Signed-off-by: lizhi Signed-off-by: Weili Qian Signed-off-by: Chenghai Huang Signed-off-by: Herbert Xu Signed-off-by: Sasha Levin --- drivers/crypto/hisilicon/hpre/hpre.h | 5 +- drivers/crypto/hisilicon/hpre/hpre_crypto.c | 142 ++++---------------- 2 files changed, 25 insertions(+), 122 deletions(-) diff --git a/drivers/crypto/hisilicon/hpre/hpre.h b/drivers/crypto/hisilicon/hpre/hpre.h index 0f3ddbadbcf991..021dbd9a1d48fb 100644 --- a/drivers/crypto/hisilicon/hpre/hpre.h +++ b/drivers/crypto/hisilicon/hpre/hpre.h @@ -94,9 +94,8 @@ struct hpre_sqe { __le64 key; __le64 in; __le64 out; - __le16 tag; - __le16 resv2; -#define _HPRE_SQE_ALIGN_EXT 7 + __le64 tag; +#define _HPRE_SQE_ALIGN_EXT 6 __le32 rsvd1[_HPRE_SQE_ALIGN_EXT]; }; diff --git a/drivers/crypto/hisilicon/hpre/hpre_crypto.c b/drivers/crypto/hisilicon/hpre/hpre_crypto.c index 21ccf879f70c55..4197281c8dff51 100644 --- a/drivers/crypto/hisilicon/hpre/hpre_crypto.c +++ b/drivers/crypto/hisilicon/hpre/hpre_crypto.c @@ -108,12 +108,10 @@ struct hpre_ecdh_ctx { struct hpre_ctx { struct hisi_qp *qp; struct device *dev; - struct hpre_asym_request **req_list; struct hpre *hpre; spinlock_t req_lock; unsigned int key_sz; bool crt_g2_mode; - struct idr req_idr; union { struct hpre_rsa_ctx rsa; struct hpre_dh_ctx dh; @@ -136,7 +134,6 @@ struct hpre_asym_request { struct kpp_request *ecdh; } areq; int err; - int req_id; hpre_cb cb; struct timespec64 req_time; }; @@ -151,58 +148,13 @@ static inline unsigned int hpre_align_pd(void) return (hpre_align_sz() - 1) & ~(crypto_tfm_ctx_alignment() - 1); } -static int hpre_alloc_req_id(struct hpre_ctx *ctx) +static void hpre_dfx_add_req_time(struct hpre_asym_request *hpre_req) { - unsigned long flags; - int id; - - spin_lock_irqsave(&ctx->req_lock, flags); - id = idr_alloc(&ctx->req_idr, NULL, 0, ctx->qp->sq_depth, GFP_ATOMIC); - spin_unlock_irqrestore(&ctx->req_lock, flags); - - return id; -} - -static void hpre_free_req_id(struct hpre_ctx *ctx, int req_id) -{ - unsigned long flags; - - spin_lock_irqsave(&ctx->req_lock, flags); - idr_remove(&ctx->req_idr, req_id); - spin_unlock_irqrestore(&ctx->req_lock, flags); -} - -static int hpre_add_req_to_ctx(struct hpre_asym_request *hpre_req) -{ - struct hpre_ctx *ctx; - struct hpre_dfx *dfx; - int id; - - ctx = hpre_req->ctx; - id = hpre_alloc_req_id(ctx); - if (unlikely(id < 0)) - return -EINVAL; - - ctx->req_list[id] = hpre_req; - hpre_req->req_id = id; + struct hpre_ctx *ctx = hpre_req->ctx; + struct hpre_dfx *dfx = ctx->hpre->debug.dfx; - dfx = ctx->hpre->debug.dfx; if (atomic64_read(&dfx[HPRE_OVERTIME_THRHLD].value)) ktime_get_ts64(&hpre_req->req_time); - - return id; -} - -static void hpre_rm_req_from_ctx(struct hpre_asym_request *hpre_req) -{ - struct hpre_ctx *ctx = hpre_req->ctx; - int id = hpre_req->req_id; - - if (hpre_req->req_id >= 0) { - hpre_req->req_id = HPRE_INVLD_REQ_ID; - ctx->req_list[id] = NULL; - hpre_free_req_id(ctx, id); - } } static struct hisi_qp *hpre_get_qp_and_start(u8 type) @@ -340,26 +292,19 @@ static void hpre_hw_data_clr_all(struct hpre_ctx *ctx, static int hpre_alg_res_post_hf(struct hpre_ctx *ctx, struct hpre_sqe *sqe, void **kreq) { - struct hpre_asym_request *req; unsigned int err, done, alg; - int id; #define HPRE_NO_HW_ERR 0 #define HPRE_HW_TASK_DONE 3 #define HREE_HW_ERR_MASK GENMASK(10, 0) #define HREE_SQE_DONE_MASK GENMASK(1, 0) #define HREE_ALG_TYPE_MASK GENMASK(4, 0) - id = (int)le16_to_cpu(sqe->tag); - req = ctx->req_list[id]; - hpre_rm_req_from_ctx(req); - *kreq = req; + *kreq = (void *)le64_to_cpu(sqe->tag); err = (le32_to_cpu(sqe->dw0) >> HPRE_SQE_ALG_BITS) & HREE_HW_ERR_MASK; - done = (le32_to_cpu(sqe->dw0) >> HPRE_SQE_DONE_SHIFT) & HREE_SQE_DONE_MASK; - if (likely(err == HPRE_NO_HW_ERR && done == HPRE_HW_TASK_DONE)) return 0; @@ -370,34 +315,9 @@ static int hpre_alg_res_post_hf(struct hpre_ctx *ctx, struct hpre_sqe *sqe, return -EINVAL; } -static int hpre_ctx_set(struct hpre_ctx *ctx, struct hisi_qp *qp, int qlen) -{ - struct hpre *hpre; - - if (!ctx || !qp || qlen < 0) - return -EINVAL; - - spin_lock_init(&ctx->req_lock); - ctx->qp = qp; - ctx->dev = &qp->qm->pdev->dev; - - hpre = container_of(ctx->qp->qm, struct hpre, qm); - ctx->hpre = hpre; - ctx->req_list = kcalloc(qlen, sizeof(void *), GFP_KERNEL); - if (!ctx->req_list) - return -ENOMEM; - ctx->key_sz = 0; - ctx->crt_g2_mode = false; - idr_init(&ctx->req_idr); - - return 0; -} - static void hpre_ctx_clear(struct hpre_ctx *ctx, bool is_clear_all) { if (is_clear_all) { - idr_destroy(&ctx->req_idr); - kfree(ctx->req_list); hisi_qm_free_qps(&ctx->qp, 1); } @@ -467,29 +387,22 @@ static void hpre_rsa_cb(struct hpre_ctx *ctx, void *resp) static void hpre_alg_cb(struct hisi_qp *qp, void *resp) { - struct hpre_ctx *ctx = qp->qp_ctx; - struct hpre_dfx *dfx = ctx->hpre->debug.dfx; + struct hpre_asym_request *h_req; struct hpre_sqe *sqe = resp; - struct hpre_asym_request *req = ctx->req_list[le16_to_cpu(sqe->tag)]; - if (unlikely(!req)) { - atomic64_inc(&dfx[HPRE_INVALID_REQ_CNT].value); + h_req = (struct hpre_asym_request *)le64_to_cpu(sqe->tag); + if (unlikely(!h_req)) { + pr_err("Failed to get request, and qp_id is %u\n", qp->qp_id); return; } - req->cb(ctx, resp); -} - -static void hpre_stop_qp_and_put(struct hisi_qp *qp) -{ - hisi_qm_stop_qp(qp); - hisi_qm_free_qps(&qp, 1); + h_req->cb(h_req->ctx, resp); } static int hpre_ctx_init(struct hpre_ctx *ctx, u8 type) { struct hisi_qp *qp; - int ret; + struct hpre *hpre; qp = hpre_get_qp_and_start(type); if (IS_ERR(qp)) @@ -497,19 +410,21 @@ static int hpre_ctx_init(struct hpre_ctx *ctx, u8 type) qp->qp_ctx = ctx; qp->req_cb = hpre_alg_cb; + spin_lock_init(&ctx->req_lock); + ctx->qp = qp; + ctx->dev = &qp->qm->pdev->dev; + hpre = container_of(ctx->qp->qm, struct hpre, qm); + ctx->hpre = hpre; + ctx->key_sz = 0; + ctx->crt_g2_mode = false; - ret = hpre_ctx_set(ctx, qp, qp->sq_depth); - if (ret) - hpre_stop_qp_and_put(qp); - - return ret; + return 0; } static int hpre_msg_request_set(struct hpre_ctx *ctx, void *req, bool is_rsa) { struct hpre_asym_request *h_req; struct hpre_sqe *msg; - int req_id; void *tmp; if (is_rsa) { @@ -549,11 +464,8 @@ static int hpre_msg_request_set(struct hpre_ctx *ctx, void *req, bool is_rsa) msg->task_len1 = (ctx->key_sz >> HPRE_BITS_2_BYTES_SHIFT) - 1; h_req->ctx = ctx; - req_id = hpre_add_req_to_ctx(h_req); - if (req_id < 0) - return -EBUSY; - - msg->tag = cpu_to_le16((u16)req_id); + hpre_dfx_add_req_time(h_req); + msg->tag = cpu_to_le64((uintptr_t)h_req); return 0; } @@ -619,7 +531,6 @@ static int hpre_dh_compute_value(struct kpp_request *req) return -EINPROGRESS; clear_all: - hpre_rm_req_from_ctx(hpre_req); hpre_hw_data_clr_all(ctx, hpre_req, req->dst, req->src); return ret; @@ -828,7 +739,6 @@ static int hpre_rsa_enc(struct akcipher_request *req) return -EINPROGRESS; clear_all: - hpre_rm_req_from_ctx(hpre_req); hpre_hw_data_clr_all(ctx, hpre_req, req->dst, req->src); return ret; @@ -883,7 +793,6 @@ static int hpre_rsa_dec(struct akcipher_request *req) return -EINPROGRESS; clear_all: - hpre_rm_req_from_ctx(hpre_req); hpre_hw_data_clr_all(ctx, hpre_req, req->dst, req->src); return ret; @@ -1346,7 +1255,7 @@ static int hpre_ecdh_set_param(struct hpre_ctx *ctx, struct ecdh *params) return 0; } -static bool hpre_key_is_zero(char *key, unsigned short key_sz) +static bool hpre_key_is_zero(const char *key, unsigned short key_sz) { int i; @@ -1488,7 +1397,6 @@ static int hpre_ecdh_msg_request_set(struct hpre_ctx *ctx, { struct hpre_asym_request *h_req; struct hpre_sqe *msg; - int req_id; void *tmp; if (req->dst_len < ctx->key_sz << 1) { @@ -1510,11 +1418,8 @@ static int hpre_ecdh_msg_request_set(struct hpre_ctx *ctx, msg->task_len1 = (ctx->key_sz >> HPRE_BITS_2_BYTES_SHIFT) - 1; h_req->ctx = ctx; - req_id = hpre_add_req_to_ctx(h_req); - if (req_id < 0) - return -EBUSY; - - msg->tag = cpu_to_le16((u16)req_id); + hpre_dfx_add_req_time(h_req); + msg->tag = cpu_to_le64((uintptr_t)h_req); return 0; } @@ -1612,7 +1517,6 @@ static int hpre_ecdh_compute_value(struct kpp_request *req) return -EINPROGRESS; clear_all: - hpre_rm_req_from_ctx(hpre_req); hpre_ecdh_hw_data_clr_all(ctx, hpre_req, req->dst, req->src); return ret; } From 23fcfd8c0d1fdd59bd724447f3950ff3defa7da6 Mon Sep 17 00:00:00 2001 From: Chenghai Huang Date: Thu, 18 Dec 2025 21:44:45 +0800 Subject: [PATCH 3328/3902] crypto: hisilicon/qm - enhance the configuration of req_type in queue attributes [ Upstream commit 21452eaa06edb5f6038720e643aed0bbfffad9c3 ] Originally, when a queue was requested, it could only be configured with the default algorithm type of 0. Now, when multiple tfms use the same queue, the queue must be selected based on its attributes to meet the requirements of tfm tasks. So the algorithm type attribute of queue need to be distinguished. Just like a queue used for compression in ZIP cannot be used for decompression tasks. Fixes: 3f1ec97aacf1 ("crypto: hisilicon/qm - Put device finding logic into QM") Signed-off-by: Chenghai Huang Signed-off-by: Weili Qian Signed-off-by: Herbert Xu Signed-off-by: Sasha Levin --- drivers/crypto/hisilicon/hpre/hpre_main.c | 2 +- drivers/crypto/hisilicon/qm.c | 8 ++++---- drivers/crypto/hisilicon/sec2/sec_crypto.c | 1 - drivers/crypto/hisilicon/sec2/sec_main.c | 21 ++++++++++++++++----- drivers/crypto/hisilicon/zip/zip.h | 2 +- drivers/crypto/hisilicon/zip/zip_crypto.c | 13 +++++++++---- drivers/crypto/hisilicon/zip/zip_main.c | 4 ++-- include/linux/hisi_acc_qm.h | 3 +-- 8 files changed, 34 insertions(+), 20 deletions(-) diff --git a/drivers/crypto/hisilicon/hpre/hpre_main.c b/drivers/crypto/hisilicon/hpre/hpre_main.c index b94fecd765eebd..884d5d0afaf41f 100644 --- a/drivers/crypto/hisilicon/hpre/hpre_main.c +++ b/drivers/crypto/hisilicon/hpre/hpre_main.c @@ -465,7 +465,7 @@ struct hisi_qp *hpre_create_qp(u8 type) * type: 0 - RSA/DH. algorithm supported in V2, * 1 - ECC algorithm in V3. */ - ret = hisi_qm_alloc_qps_node(&hpre_devices, 1, type, node, &qp); + ret = hisi_qm_alloc_qps_node(&hpre_devices, 1, &type, node, &qp); if (!ret) return qp; diff --git a/drivers/crypto/hisilicon/qm.c b/drivers/crypto/hisilicon/qm.c index 6bf91735dfcd84..41b7ffa0fb1ae3 100644 --- a/drivers/crypto/hisilicon/qm.c +++ b/drivers/crypto/hisilicon/qm.c @@ -3583,7 +3583,7 @@ static int hisi_qm_sort_devices(int node, struct list_head *head, * not meet the requirements will return error. */ int hisi_qm_alloc_qps_node(struct hisi_qm_list *qm_list, int qp_num, - u8 alg_type, int node, struct hisi_qp **qps) + u8 *alg_type, int node, struct hisi_qp **qps) { struct hisi_qm_resource *tmp; int ret = -ENODEV; @@ -3601,7 +3601,7 @@ int hisi_qm_alloc_qps_node(struct hisi_qm_list *qm_list, int qp_num, list_for_each_entry(tmp, &head, list) { for (i = 0; i < qp_num; i++) { - qps[i] = hisi_qm_create_qp(tmp->qm, alg_type); + qps[i] = hisi_qm_create_qp(tmp->qm, alg_type[i]); if (IS_ERR(qps[i])) { hisi_qm_free_qps(qps, i); break; @@ -3616,8 +3616,8 @@ int hisi_qm_alloc_qps_node(struct hisi_qm_list *qm_list, int qp_num, mutex_unlock(&qm_list->lock); if (ret) - pr_info("Failed to create qps, node[%d], alg[%u], qp[%d]!\n", - node, alg_type, qp_num); + pr_info("Failed to create qps, node[%d], qp[%d]!\n", + node, qp_num); err: free_list(&head); diff --git a/drivers/crypto/hisilicon/sec2/sec_crypto.c b/drivers/crypto/hisilicon/sec2/sec_crypto.c index 4e41235116e15d..364bd69c608833 100644 --- a/drivers/crypto/hisilicon/sec2/sec_crypto.c +++ b/drivers/crypto/hisilicon/sec2/sec_crypto.c @@ -626,7 +626,6 @@ static int sec_create_qp_ctx(struct sec_ctx *ctx, int qp_ctx_id) qp_ctx = &ctx->qp_ctx[qp_ctx_id]; qp = ctx->qps[qp_ctx_id]; - qp->req_type = 0; qp->qp_ctx = qp_ctx; qp_ctx->qp = qp; qp_ctx->ctx = ctx; diff --git a/drivers/crypto/hisilicon/sec2/sec_main.c b/drivers/crypto/hisilicon/sec2/sec_main.c index 5eb2d68207426e..7dd125f5f511fa 100644 --- a/drivers/crypto/hisilicon/sec2/sec_main.c +++ b/drivers/crypto/hisilicon/sec2/sec_main.c @@ -417,18 +417,29 @@ struct hisi_qp **sec_create_qps(void) int node = cpu_to_node(raw_smp_processor_id()); u32 ctx_num = ctx_q_num; struct hisi_qp **qps; + u8 *type; int ret; qps = kcalloc(ctx_num, sizeof(struct hisi_qp *), GFP_KERNEL); if (!qps) return NULL; - ret = hisi_qm_alloc_qps_node(&sec_devices, ctx_num, 0, node, qps); - if (!ret) - return qps; + /* The type of SEC is all 0, so just allocated by kcalloc */ + type = kcalloc(ctx_num, sizeof(u8), GFP_KERNEL); + if (!type) { + kfree(qps); + return NULL; + } - kfree(qps); - return NULL; + ret = hisi_qm_alloc_qps_node(&sec_devices, ctx_num, type, node, qps); + if (ret) { + kfree(type); + kfree(qps); + return NULL; + } + + kfree(type); + return qps; } u64 sec_get_alg_bitmap(struct hisi_qm *qm, u32 high, u32 low) diff --git a/drivers/crypto/hisilicon/zip/zip.h b/drivers/crypto/hisilicon/zip/zip.h index 9fb2a9c01132b4..b83f228281ab1e 100644 --- a/drivers/crypto/hisilicon/zip/zip.h +++ b/drivers/crypto/hisilicon/zip/zip.h @@ -99,7 +99,7 @@ enum zip_cap_table_type { ZIP_CORE5_BITMAP, }; -int zip_create_qps(struct hisi_qp **qps, int qp_num, int node); +int zip_create_qps(struct hisi_qp **qps, int qp_num, int node, u8 *alg_type); int hisi_zip_register_to_crypto(struct hisi_qm *qm); void hisi_zip_unregister_from_crypto(struct hisi_qm *qm); bool hisi_zip_alg_support(struct hisi_qm *qm, u32 alg); diff --git a/drivers/crypto/hisilicon/zip/zip_crypto.c b/drivers/crypto/hisilicon/zip/zip_crypto.c index b4a656e0177d2d..8250a33ba58622 100644 --- a/drivers/crypto/hisilicon/zip/zip_crypto.c +++ b/drivers/crypto/hisilicon/zip/zip_crypto.c @@ -66,6 +66,7 @@ struct hisi_zip_qp_ctx { struct hisi_acc_sgl_pool *sgl_pool; struct hisi_zip *zip_dev; struct hisi_zip_ctx *ctx; + u8 req_type; }; struct hisi_zip_sqe_ops { @@ -245,7 +246,7 @@ static int hisi_zip_do_work(struct hisi_zip_qp_ctx *qp_ctx, goto err_unmap_input; } - hisi_zip_fill_sqe(qp_ctx->ctx, &zip_sqe, qp->req_type, req); + hisi_zip_fill_sqe(qp_ctx->ctx, &zip_sqe, qp_ctx->req_type, req); /* send command to start a task */ atomic64_inc(&dfx->send_cnt); @@ -360,7 +361,6 @@ static int hisi_zip_start_qp(struct hisi_qp *qp, struct hisi_zip_qp_ctx *qp_ctx, struct device *dev = &qp->qm->pdev->dev; int ret; - qp->req_type = req_type; qp->alg_type = alg_type; qp->qp_ctx = qp_ctx; @@ -397,10 +397,15 @@ static int hisi_zip_ctx_init(struct hisi_zip_ctx *hisi_zip_ctx, u8 req_type, int { struct hisi_qp *qps[HZIP_CTX_Q_NUM] = { NULL }; struct hisi_zip_qp_ctx *qp_ctx; + u8 alg_type[HZIP_CTX_Q_NUM]; struct hisi_zip *hisi_zip; int ret, i, j; - ret = zip_create_qps(qps, HZIP_CTX_Q_NUM, node); + /* alg_type = 0 for compress, 1 for decompress in hw sqe */ + for (i = 0; i < HZIP_CTX_Q_NUM; i++) + alg_type[i] = i; + + ret = zip_create_qps(qps, HZIP_CTX_Q_NUM, node, alg_type); if (ret) { pr_err("failed to create zip qps (%d)!\n", ret); return -ENODEV; @@ -409,7 +414,6 @@ static int hisi_zip_ctx_init(struct hisi_zip_ctx *hisi_zip_ctx, u8 req_type, int hisi_zip = container_of(qps[0]->qm, struct hisi_zip, qm); for (i = 0; i < HZIP_CTX_Q_NUM; i++) { - /* alg_type = 0 for compress, 1 for decompress in hw sqe */ qp_ctx = &hisi_zip_ctx->qp_ctx[i]; qp_ctx->ctx = hisi_zip_ctx; ret = hisi_zip_start_qp(qps[i], qp_ctx, i, req_type); @@ -422,6 +426,7 @@ static int hisi_zip_ctx_init(struct hisi_zip_ctx *hisi_zip_ctx, u8 req_type, int } qp_ctx->zip_dev = hisi_zip; + qp_ctx->req_type = req_type; } hisi_zip_ctx->ops = &hisi_zip_ops; diff --git a/drivers/crypto/hisilicon/zip/zip_main.c b/drivers/crypto/hisilicon/zip/zip_main.c index 4fcbe6bada0663..85b26ef1754855 100644 --- a/drivers/crypto/hisilicon/zip/zip_main.c +++ b/drivers/crypto/hisilicon/zip/zip_main.c @@ -446,12 +446,12 @@ static const struct pci_device_id hisi_zip_dev_ids[] = { }; MODULE_DEVICE_TABLE(pci, hisi_zip_dev_ids); -int zip_create_qps(struct hisi_qp **qps, int qp_num, int node) +int zip_create_qps(struct hisi_qp **qps, int qp_num, int node, u8 *alg_type) { if (node == NUMA_NO_NODE) node = cpu_to_node(raw_smp_processor_id()); - return hisi_qm_alloc_qps_node(&zip_devices, qp_num, 0, node, qps); + return hisi_qm_alloc_qps_node(&zip_devices, qp_num, alg_type, node, qps); } bool hisi_zip_alg_support(struct hisi_qm *qm, u32 alg) diff --git a/include/linux/hisi_acc_qm.h b/include/linux/hisi_acc_qm.h index 2d0cc61ed88693..4f83f07009907f 100644 --- a/include/linux/hisi_acc_qm.h +++ b/include/linux/hisi_acc_qm.h @@ -454,7 +454,6 @@ struct hisi_qp { u16 sq_depth; u16 cq_depth; u8 alg_type; - u8 req_type; struct qm_dma qdma; void *sqe; @@ -580,7 +579,7 @@ struct hisi_acc_sgl_pool *hisi_acc_create_sgl_pool(struct device *dev, void hisi_acc_free_sgl_pool(struct device *dev, struct hisi_acc_sgl_pool *pool); int hisi_qm_alloc_qps_node(struct hisi_qm_list *qm_list, int qp_num, - u8 alg_type, int node, struct hisi_qp **qps); + u8 *alg_type, int node, struct hisi_qp **qps); void hisi_qm_free_qps(struct hisi_qp **qps, int qp_num); void hisi_qm_dev_shutdown(struct pci_dev *pdev); void hisi_qm_wait_task_finish(struct hisi_qm *qm, struct hisi_qm_list *qm_list); From 3acd2479c770e89b1d9f56161171d64169433b5c Mon Sep 17 00:00:00 2001 From: Chenghai Huang Date: Thu, 18 Dec 2025 21:44:46 +0800 Subject: [PATCH 3329/3902] crypto: hisilicon/qm - centralize the sending locks of each module into qm [ Upstream commit 8cd9b608ee8dea78cac3f373bd5e3b3de2755d46 ] When a single queue used by multiple tfms, the protection of shared resources by individual module driver programs is no longer sufficient. The hisi_qp_send needs to be ensured by the lock in qp. Fixes: 5fdb4b345cfb ("crypto: hisilicon - add a lock for the qp send operation") Signed-off-by: Chenghai Huang Signed-off-by: Weili Qian Signed-off-by: Herbert Xu Signed-off-by: Sasha Levin --- drivers/crypto/hisilicon/hpre/hpre_crypto.c | 4 ---- drivers/crypto/hisilicon/qm.c | 16 ++++++++++++---- drivers/crypto/hisilicon/zip/zip_crypto.c | 3 --- include/linux/hisi_acc_qm.h | 1 + 4 files changed, 13 insertions(+), 11 deletions(-) diff --git a/drivers/crypto/hisilicon/hpre/hpre_crypto.c b/drivers/crypto/hisilicon/hpre/hpre_crypto.c index 4197281c8dff51..220022ae7afb61 100644 --- a/drivers/crypto/hisilicon/hpre/hpre_crypto.c +++ b/drivers/crypto/hisilicon/hpre/hpre_crypto.c @@ -109,7 +109,6 @@ struct hpre_ctx { struct hisi_qp *qp; struct device *dev; struct hpre *hpre; - spinlock_t req_lock; unsigned int key_sz; bool crt_g2_mode; union { @@ -410,7 +409,6 @@ static int hpre_ctx_init(struct hpre_ctx *ctx, u8 type) qp->qp_ctx = ctx; qp->req_cb = hpre_alg_cb; - spin_lock_init(&ctx->req_lock); ctx->qp = qp; ctx->dev = &qp->qm->pdev->dev; hpre = container_of(ctx->qp->qm, struct hpre, qm); @@ -478,9 +476,7 @@ static int hpre_send(struct hpre_ctx *ctx, struct hpre_sqe *msg) do { atomic64_inc(&dfx[HPRE_SEND_CNT].value); - spin_lock_bh(&ctx->req_lock); ret = hisi_qp_send(ctx->qp, msg); - spin_unlock_bh(&ctx->req_lock); if (ret != -EBUSY) break; atomic64_inc(&dfx[HPRE_SEND_BUSY_CNT].value); diff --git a/drivers/crypto/hisilicon/qm.c b/drivers/crypto/hisilicon/qm.c index 41b7ffa0fb1ae3..c2c24c3a2be86d 100644 --- a/drivers/crypto/hisilicon/qm.c +++ b/drivers/crypto/hisilicon/qm.c @@ -2360,26 +2360,33 @@ EXPORT_SYMBOL_GPL(hisi_qm_stop_qp); int hisi_qp_send(struct hisi_qp *qp, const void *msg) { struct hisi_qp_status *qp_status = &qp->qp_status; - u16 sq_tail = qp_status->sq_tail; - u16 sq_tail_next = (sq_tail + 1) % qp->sq_depth; - void *sqe = qm_get_avail_sqe(qp); + u16 sq_tail, sq_tail_next; + void *sqe; + spin_lock_bh(&qp->qp_lock); if (unlikely(atomic_read(&qp->qp_status.flags) == QP_STOP || atomic_read(&qp->qm->status.flags) == QM_STOP || qp->is_resetting)) { + spin_unlock_bh(&qp->qp_lock); dev_info_ratelimited(&qp->qm->pdev->dev, "QP is stopped or resetting\n"); return -EAGAIN; } - if (!sqe) + sqe = qm_get_avail_sqe(qp); + if (!sqe) { + spin_unlock_bh(&qp->qp_lock); return -EBUSY; + } + sq_tail = qp_status->sq_tail; + sq_tail_next = (sq_tail + 1) % qp->sq_depth; memcpy(sqe, msg, qp->qm->sqe_size); qp->msg[sq_tail] = msg; qm_db(qp->qm, qp->qp_id, QM_DOORBELL_CMD_SQ, sq_tail_next, 0); atomic_inc(&qp->qp_status.used); qp_status->sq_tail = sq_tail_next; + spin_unlock_bh(&qp->qp_lock); return 0; } @@ -2956,6 +2963,7 @@ static int hisi_qp_memory_init(struct hisi_qm *qm, size_t dma_size, int id, qp->qm = qm; qp->qp_id = id; + spin_lock_init(&qp->qp_lock); spin_lock_init(&qp->backlog.lock); INIT_LIST_HEAD(&qp->backlog.list); diff --git a/drivers/crypto/hisilicon/zip/zip_crypto.c b/drivers/crypto/hisilicon/zip/zip_crypto.c index 8250a33ba58622..2f9035c016f3ff 100644 --- a/drivers/crypto/hisilicon/zip/zip_crypto.c +++ b/drivers/crypto/hisilicon/zip/zip_crypto.c @@ -217,7 +217,6 @@ static int hisi_zip_do_work(struct hisi_zip_qp_ctx *qp_ctx, { struct hisi_acc_sgl_pool *pool = qp_ctx->sgl_pool; struct hisi_zip_dfx *dfx = &qp_ctx->zip_dev->dfx; - struct hisi_zip_req_q *req_q = &qp_ctx->req_q; struct acomp_req *a_req = req->req; struct hisi_qp *qp = qp_ctx->qp; struct device *dev = &qp->qm->pdev->dev; @@ -250,9 +249,7 @@ static int hisi_zip_do_work(struct hisi_zip_qp_ctx *qp_ctx, /* send command to start a task */ atomic64_inc(&dfx->send_cnt); - spin_lock_bh(&req_q->req_lock); ret = hisi_qp_send(qp, &zip_sqe); - spin_unlock_bh(&req_q->req_lock); if (unlikely(ret < 0)) { atomic64_inc(&dfx->send_busy_cnt); ret = -EAGAIN; diff --git a/include/linux/hisi_acc_qm.h b/include/linux/hisi_acc_qm.h index 4f83f07009907f..75ae01ddaa1a80 100644 --- a/include/linux/hisi_acc_qm.h +++ b/include/linux/hisi_acc_qm.h @@ -473,6 +473,7 @@ struct hisi_qp { u16 pasid; struct uacce_queue *uacce_q; + spinlock_t qp_lock; struct instance_backlog backlog; const void **msg; }; From cc7a36b6ac8f4403c85319a7990868a76335494f Mon Sep 17 00:00:00 2001 From: Chenghai Huang Date: Thu, 18 Dec 2025 21:44:50 +0800 Subject: [PATCH 3330/3902] crypto: hisilicon/zip - support fallback for zip [ Upstream commit 73398f85a430cfebc2ff06ab836d6d9eb1484c79 ] When the hardware queue resource busy(no shareable queue) or memery alloc fail in initialization of acomp_alg, use soft algorithm to complete the work. Fixes: 1a9e6f59caee ("crypto: hisilicon/zip - remove zlib and gzip") Signed-off-by: Chenghai Huang Signed-off-by: Herbert Xu Signed-off-by: Sasha Levin --- drivers/crypto/hisilicon/Kconfig | 1 + drivers/crypto/hisilicon/zip/zip_crypto.c | 50 +++++++++++++++++++---- 2 files changed, 43 insertions(+), 8 deletions(-) diff --git a/drivers/crypto/hisilicon/Kconfig b/drivers/crypto/hisilicon/Kconfig index 4835bdebdbb381..a0cb1a8186ac9d 100644 --- a/drivers/crypto/hisilicon/Kconfig +++ b/drivers/crypto/hisilicon/Kconfig @@ -57,6 +57,7 @@ config CRYPTO_DEV_HISI_ZIP depends on UACCE || UACCE=n depends on ACPI select CRYPTO_DEV_HISI_QM + select CRYPTO_DEFLATE help Support for HiSilicon ZIP Driver diff --git a/drivers/crypto/hisilicon/zip/zip_crypto.c b/drivers/crypto/hisilicon/zip/zip_crypto.c index 2f9035c016f3ff..5fc2ed9d5eef32 100644 --- a/drivers/crypto/hisilicon/zip/zip_crypto.c +++ b/drivers/crypto/hisilicon/zip/zip_crypto.c @@ -84,6 +84,7 @@ struct hisi_zip_sqe_ops { struct hisi_zip_ctx { struct hisi_zip_qp_ctx qp_ctx[HZIP_CTX_Q_NUM]; const struct hisi_zip_sqe_ops *ops; + bool fallback; }; static int sgl_sge_nr_set(const char *val, const struct kernel_param *kp) @@ -110,6 +111,24 @@ static u16 sgl_sge_nr = HZIP_SGL_SGE_NR; module_param_cb(sgl_sge_nr, &sgl_sge_nr_ops, &sgl_sge_nr, 0444); MODULE_PARM_DESC(sgl_sge_nr, "Number of sge in sgl(1-255)"); +static int hisi_zip_fallback_do_work(struct acomp_req *acomp_req, bool is_decompress) +{ + ACOMP_FBREQ_ON_STACK(fbreq, acomp_req); + int ret; + + if (!is_decompress) + ret = crypto_acomp_compress(fbreq); + else + ret = crypto_acomp_decompress(fbreq); + if (ret) { + pr_err("failed to do fallback work, ret=%d\n", ret); + return ret; + } + + acomp_req->dlen = fbreq->dlen; + return ret; +} + static struct hisi_zip_req *hisi_zip_create_req(struct hisi_zip_qp_ctx *qp_ctx, struct acomp_req *req) { @@ -313,10 +332,15 @@ static int hisi_zip_acompress(struct acomp_req *acomp_req) { struct hisi_zip_ctx *ctx = crypto_tfm_ctx(acomp_req->base.tfm); struct hisi_zip_qp_ctx *qp_ctx = &ctx->qp_ctx[HZIP_QPC_COMP]; - struct device *dev = &qp_ctx->qp->qm->pdev->dev; struct hisi_zip_req *req; + struct device *dev; int ret; + if (ctx->fallback) + return hisi_zip_fallback_do_work(acomp_req, 0); + + dev = &qp_ctx->qp->qm->pdev->dev; + req = hisi_zip_create_req(qp_ctx, acomp_req); if (IS_ERR(req)) return PTR_ERR(req); @@ -334,10 +358,15 @@ static int hisi_zip_adecompress(struct acomp_req *acomp_req) { struct hisi_zip_ctx *ctx = crypto_tfm_ctx(acomp_req->base.tfm); struct hisi_zip_qp_ctx *qp_ctx = &ctx->qp_ctx[HZIP_QPC_DECOMP]; - struct device *dev = &qp_ctx->qp->qm->pdev->dev; struct hisi_zip_req *req; + struct device *dev; int ret; + if (ctx->fallback) + return hisi_zip_fallback_do_work(acomp_req, 1); + + dev = &qp_ctx->qp->qm->pdev->dev; + req = hisi_zip_create_req(qp_ctx, acomp_req); if (IS_ERR(req)) return PTR_ERR(req); @@ -546,7 +575,7 @@ static int hisi_zip_acomp_init(struct crypto_acomp *tfm) ret = hisi_zip_ctx_init(ctx, COMP_NAME_TO_TYPE(alg_name), tfm->base.node); if (ret) { pr_err("failed to init ctx (%d)!\n", ret); - return ret; + goto switch_to_soft; } dev = &ctx->qp_ctx[0].qp->qm->pdev->dev; @@ -571,16 +600,20 @@ static int hisi_zip_acomp_init(struct crypto_acomp *tfm) hisi_zip_release_req_q(ctx); err_ctx_exit: hisi_zip_ctx_exit(ctx); - return ret; +switch_to_soft: + ctx->fallback = true; + return 0; } static void hisi_zip_acomp_exit(struct crypto_acomp *tfm) { struct hisi_zip_ctx *ctx = crypto_tfm_ctx(&tfm->base); - hisi_zip_release_sgl_pool(ctx); - hisi_zip_release_req_q(ctx); - hisi_zip_ctx_exit(ctx); + if (!ctx->fallback) { + hisi_zip_release_sgl_pool(ctx); + hisi_zip_release_req_q(ctx); + hisi_zip_ctx_exit(ctx); + } } static struct acomp_alg hisi_zip_acomp_deflate = { @@ -591,7 +624,8 @@ static struct acomp_alg hisi_zip_acomp_deflate = { .base = { .cra_name = "deflate", .cra_driver_name = "hisi-deflate-acomp", - .cra_flags = CRYPTO_ALG_ASYNC, + .cra_flags = CRYPTO_ALG_ASYNC | + CRYPTO_ALG_NEED_FALLBACK, .cra_module = THIS_MODULE, .cra_priority = HZIP_ALG_PRIORITY, .cra_ctxsize = sizeof(struct hisi_zip_ctx), From c990abb136b74547c222bfb78f8978179c009aa2 Mon Sep 17 00:00:00 2001 From: Chenghai Huang Date: Thu, 18 Dec 2025 21:44:47 +0800 Subject: [PATCH 3331/3902] crypto: hisilicon - consolidate qp creation and start in hisi_qm_alloc_qps_node [ Upstream commit 72f3bbebff15e87171271d643ee2672fb8e92031 ] Consolidate the creation and start of qp into the function hisi_qm_alloc_qps_node. This change eliminates the need for each module to perform these steps in two separate phases (creation and start). Signed-off-by: Chenghai Huang Signed-off-by: Weili Qian Signed-off-by: Herbert Xu Stable-dep-of: 6aff4d977e2d ("crypto: hisilicon/hpre - support the hpre algorithm fallback") Signed-off-by: Sasha Levin --- drivers/crypto/hisilicon/hpre/hpre_crypto.c | 40 ++---------- drivers/crypto/hisilicon/qm.c | 70 ++++++++++++++++----- drivers/crypto/hisilicon/sec2/sec_crypto.c | 8 --- drivers/crypto/hisilicon/zip/zip_crypto.c | 43 ++----------- include/linux/hisi_acc_qm.h | 1 - 5 files changed, 66 insertions(+), 96 deletions(-) diff --git a/drivers/crypto/hisilicon/hpre/hpre_crypto.c b/drivers/crypto/hisilicon/hpre/hpre_crypto.c index 220022ae7afb61..f410e610eabaa0 100644 --- a/drivers/crypto/hisilicon/hpre/hpre_crypto.c +++ b/drivers/crypto/hisilicon/hpre/hpre_crypto.c @@ -156,27 +156,6 @@ static void hpre_dfx_add_req_time(struct hpre_asym_request *hpre_req) ktime_get_ts64(&hpre_req->req_time); } -static struct hisi_qp *hpre_get_qp_and_start(u8 type) -{ - struct hisi_qp *qp; - int ret; - - qp = hpre_create_qp(type); - if (!qp) { - pr_err("Can not create hpre qp!\n"); - return ERR_PTR(-ENODEV); - } - - ret = hisi_qm_start_qp(qp, 0); - if (ret < 0) { - hisi_qm_free_qps(&qp, 1); - pci_err(qp->qm->pdev, "Can not start qp!\n"); - return ERR_PTR(-EINVAL); - } - - return qp; -} - static int hpre_get_data_dma_addr(struct hpre_asym_request *hpre_req, struct scatterlist *data, unsigned int len, int is_src, dma_addr_t *tmp) @@ -316,9 +295,8 @@ static int hpre_alg_res_post_hf(struct hpre_ctx *ctx, struct hpre_sqe *sqe, static void hpre_ctx_clear(struct hpre_ctx *ctx, bool is_clear_all) { - if (is_clear_all) { + if (is_clear_all) hisi_qm_free_qps(&ctx->qp, 1); - } ctx->crt_g2_mode = false; ctx->key_sz = 0; @@ -403,11 +381,10 @@ static int hpre_ctx_init(struct hpre_ctx *ctx, u8 type) struct hisi_qp *qp; struct hpre *hpre; - qp = hpre_get_qp_and_start(type); - if (IS_ERR(qp)) - return PTR_ERR(qp); + qp = hpre_create_qp(type); + if (!qp) + return -ENODEV; - qp->qp_ctx = ctx; qp->req_cb = hpre_alg_cb; ctx->qp = qp; ctx->dev = &qp->qm->pdev->dev; @@ -597,9 +574,6 @@ static void hpre_dh_clear_ctx(struct hpre_ctx *ctx, bool is_clear_all) struct device *dev = ctx->dev; unsigned int sz = ctx->key_sz; - if (is_clear_all) - hisi_qm_stop_qp(ctx->qp); - if (ctx->dh.g) { dma_free_coherent(dev, sz, ctx->dh.g, ctx->dh.dma_g); ctx->dh.g = NULL; @@ -940,9 +914,6 @@ static void hpre_rsa_clear_ctx(struct hpre_ctx *ctx, bool is_clear_all) unsigned int half_key_sz = ctx->key_sz >> 1; struct device *dev = ctx->dev; - if (is_clear_all) - hisi_qm_stop_qp(ctx->qp); - if (ctx->rsa.pubkey) { dma_free_coherent(dev, ctx->key_sz << 1, ctx->rsa.pubkey, ctx->rsa.dma_pubkey); @@ -1112,9 +1083,6 @@ static void hpre_ecc_clear_ctx(struct hpre_ctx *ctx, bool is_clear_all) unsigned int sz = ctx->key_sz; unsigned int shift = sz << 1; - if (is_clear_all) - hisi_qm_stop_qp(ctx->qp); - if (ctx->ecdh.p) { /* ecdh: p->a->k->b */ memzero_explicit(ctx->ecdh.p + shift, sz); diff --git a/drivers/crypto/hisilicon/qm.c b/drivers/crypto/hisilicon/qm.c index c2c24c3a2be86d..a7c8839180ee72 100644 --- a/drivers/crypto/hisilicon/qm.c +++ b/drivers/crypto/hisilicon/qm.c @@ -3516,6 +3516,14 @@ void hisi_qm_dev_err_uninit(struct hisi_qm *qm) } EXPORT_SYMBOL_GPL(hisi_qm_dev_err_uninit); +static void qm_release_qp_nolock(struct hisi_qp *qp) +{ + struct hisi_qm *qm = qp->qm; + + qm->qp_in_used--; + idr_remove(&qm->qp_idr, qp->qp_id); +} + /** * hisi_qm_free_qps() - free multiple queue pairs. * @qps: The queue pairs need to be freed. @@ -3528,8 +3536,15 @@ void hisi_qm_free_qps(struct hisi_qp **qps, int qp_num) if (!qps || qp_num <= 0) return; - for (i = qp_num - 1; i >= 0; i--) - hisi_qm_release_qp(qps[i]); + down_write(&qps[0]->qm->qps_lock); + + for (i = qp_num - 1; i >= 0; i--) { + qm_stop_qp_nolock(qps[i]); + qm_release_qp_nolock(qps[i]); + } + + up_write(&qps[0]->qm->qps_lock); + qm_pm_put_sync(qps[0]->qm); } EXPORT_SYMBOL_GPL(hisi_qm_free_qps); @@ -3543,6 +3558,43 @@ static void free_list(struct list_head *head) } } +static int qm_get_and_start_qp(struct hisi_qm *qm, int qp_num, struct hisi_qp **qps, u8 *alg_type) +{ + int i, ret; + + ret = qm_pm_get_sync(qm); + if (ret) + return ret; + + down_write(&qm->qps_lock); + for (i = 0; i < qp_num; i++) { + qps[i] = qm_create_qp_nolock(qm, alg_type[i]); + if (IS_ERR(qps[i])) { + ret = -ENODEV; + goto stop_and_free; + } + + ret = qm_start_qp_nolock(qps[i], 0); + if (ret) { + qm_release_qp_nolock(qps[i]); + goto stop_and_free; + } + } + up_write(&qm->qps_lock); + + return 0; + +stop_and_free: + for (i--; i >= 0; i--) { + qm_stop_qp_nolock(qps[i]); + qm_release_qp_nolock(qps[i]); + } + up_write(&qm->qps_lock); + qm_pm_put_sync(qm); + + return ret; +} + static int hisi_qm_sort_devices(int node, struct list_head *head, struct hisi_qm_list *qm_list) { @@ -3596,7 +3648,6 @@ int hisi_qm_alloc_qps_node(struct hisi_qm_list *qm_list, int qp_num, struct hisi_qm_resource *tmp; int ret = -ENODEV; LIST_HEAD(head); - int i; if (!qps || !qm_list || qp_num <= 0) return -EINVAL; @@ -3608,18 +3659,9 @@ int hisi_qm_alloc_qps_node(struct hisi_qm_list *qm_list, int qp_num, } list_for_each_entry(tmp, &head, list) { - for (i = 0; i < qp_num; i++) { - qps[i] = hisi_qm_create_qp(tmp->qm, alg_type[i]); - if (IS_ERR(qps[i])) { - hisi_qm_free_qps(qps, i); - break; - } - } - - if (i == qp_num) { - ret = 0; + ret = qm_get_and_start_qp(tmp->qm, qp_num, qps, alg_type); + if (!ret) break; - } } mutex_unlock(&qm_list->lock); diff --git a/drivers/crypto/hisilicon/sec2/sec_crypto.c b/drivers/crypto/hisilicon/sec2/sec_crypto.c index 364bd69c608833..d09d081f42dc78 100644 --- a/drivers/crypto/hisilicon/sec2/sec_crypto.c +++ b/drivers/crypto/hisilicon/sec2/sec_crypto.c @@ -626,7 +626,6 @@ static int sec_create_qp_ctx(struct sec_ctx *ctx, int qp_ctx_id) qp_ctx = &ctx->qp_ctx[qp_ctx_id]; qp = ctx->qps[qp_ctx_id]; - qp->qp_ctx = qp_ctx; qp_ctx->qp = qp; qp_ctx->ctx = ctx; @@ -644,14 +643,8 @@ static int sec_create_qp_ctx(struct sec_ctx *ctx, int qp_ctx_id) if (ret) goto err_destroy_idr; - ret = hisi_qm_start_qp(qp, 0); - if (ret < 0) - goto err_resource_free; - return 0; -err_resource_free: - sec_free_qp_ctx_resource(ctx, qp_ctx); err_destroy_idr: idr_destroy(&qp_ctx->req_idr); return ret; @@ -660,7 +653,6 @@ static int sec_create_qp_ctx(struct sec_ctx *ctx, int qp_ctx_id) static void sec_release_qp_ctx(struct sec_ctx *ctx, struct sec_qp_ctx *qp_ctx) { - hisi_qm_stop_qp(qp_ctx->qp); sec_free_qp_ctx_resource(ctx, qp_ctx); idr_destroy(&qp_ctx->req_idr); } diff --git a/drivers/crypto/hisilicon/zip/zip_crypto.c b/drivers/crypto/hisilicon/zip/zip_crypto.c index 5fc2ed9d5eef32..e140d4f8afe0e8 100644 --- a/drivers/crypto/hisilicon/zip/zip_crypto.c +++ b/drivers/crypto/hisilicon/zip/zip_crypto.c @@ -381,32 +381,6 @@ static int hisi_zip_adecompress(struct acomp_req *acomp_req) return ret; } -static int hisi_zip_start_qp(struct hisi_qp *qp, struct hisi_zip_qp_ctx *qp_ctx, - int alg_type, int req_type) -{ - struct device *dev = &qp->qm->pdev->dev; - int ret; - - qp->alg_type = alg_type; - qp->qp_ctx = qp_ctx; - - ret = hisi_qm_start_qp(qp, 0); - if (ret < 0) { - dev_err(dev, "failed to start qp (%d)!\n", ret); - return ret; - } - - qp_ctx->qp = qp; - - return 0; -} - -static void hisi_zip_release_qp(struct hisi_zip_qp_ctx *qp_ctx) -{ - hisi_qm_stop_qp(qp_ctx->qp); - hisi_qm_free_qps(&qp_ctx->qp, 1); -} - static const struct hisi_zip_sqe_ops hisi_zip_ops = { .sqe_type = 0x3, .fill_addr = hisi_zip_fill_addr, @@ -425,7 +399,7 @@ static int hisi_zip_ctx_init(struct hisi_zip_ctx *hisi_zip_ctx, u8 req_type, int struct hisi_zip_qp_ctx *qp_ctx; u8 alg_type[HZIP_CTX_Q_NUM]; struct hisi_zip *hisi_zip; - int ret, i, j; + int ret, i; /* alg_type = 0 for compress, 1 for decompress in hw sqe */ for (i = 0; i < HZIP_CTX_Q_NUM; i++) @@ -442,17 +416,9 @@ static int hisi_zip_ctx_init(struct hisi_zip_ctx *hisi_zip_ctx, u8 req_type, int for (i = 0; i < HZIP_CTX_Q_NUM; i++) { qp_ctx = &hisi_zip_ctx->qp_ctx[i]; qp_ctx->ctx = hisi_zip_ctx; - ret = hisi_zip_start_qp(qps[i], qp_ctx, i, req_type); - if (ret) { - for (j = i - 1; j >= 0; j--) - hisi_qm_stop_qp(hisi_zip_ctx->qp_ctx[j].qp); - - hisi_qm_free_qps(qps, HZIP_CTX_Q_NUM); - return ret; - } - qp_ctx->zip_dev = hisi_zip; qp_ctx->req_type = req_type; + qp_ctx->qp = qps[i]; } hisi_zip_ctx->ops = &hisi_zip_ops; @@ -462,10 +428,13 @@ static int hisi_zip_ctx_init(struct hisi_zip_ctx *hisi_zip_ctx, u8 req_type, int static void hisi_zip_ctx_exit(struct hisi_zip_ctx *hisi_zip_ctx) { + struct hisi_qp *qps[HZIP_CTX_Q_NUM] = { NULL }; int i; for (i = 0; i < HZIP_CTX_Q_NUM; i++) - hisi_zip_release_qp(&hisi_zip_ctx->qp_ctx[i]); + qps[i] = hisi_zip_ctx->qp_ctx[i].qp; + + hisi_qm_free_qps(qps, HZIP_CTX_Q_NUM); } static int hisi_zip_create_req_q(struct hisi_zip_ctx *ctx) diff --git a/include/linux/hisi_acc_qm.h b/include/linux/hisi_acc_qm.h index 75ae01ddaa1a80..4cf418a41fe438 100644 --- a/include/linux/hisi_acc_qm.h +++ b/include/linux/hisi_acc_qm.h @@ -463,7 +463,6 @@ struct hisi_qp { struct hisi_qp_status qp_status; struct hisi_qp_ops *hw_ops; - void *qp_ctx; void (*req_cb)(struct hisi_qp *qp, void *data); void (*event_cb)(struct hisi_qp *qp); From 9f35acf698017824ff391bd20fc09ab1c821bdab Mon Sep 17 00:00:00 2001 From: Weili Qian Date: Thu, 18 Dec 2025 21:44:51 +0800 Subject: [PATCH 3332/3902] crypto: hisilicon/hpre - support the hpre algorithm fallback [ Upstream commit 6aff4d977e2d582c5d6ff6afd5646c1a459490fa ] When all hardware queues are busy and no shareable queue, new processes fail to apply for queues. To avoid affecting tasks, support fallback mechanism when hardware queues are unavailable. HPRE driver supports DH algorithm, limited to prime numbers up to 4K. It supports prime numbers larger than 4K via fallback mechanism. Fixes: 05e7b906aa7c ("crypto: hisilicon/hpre - add 'ECDH' algorithm") Signed-off-by: Weili Qian Signed-off-by: Chenghai Huang Signed-off-by: Herbert Xu Signed-off-by: Sasha Levin --- drivers/crypto/hisilicon/hpre/hpre_crypto.c | 238 ++++++++++++++++---- 1 file changed, 199 insertions(+), 39 deletions(-) diff --git a/drivers/crypto/hisilicon/hpre/hpre_crypto.c b/drivers/crypto/hisilicon/hpre/hpre_crypto.c index f410e610eabaa0..839c1f67714369 100644 --- a/drivers/crypto/hisilicon/hpre/hpre_crypto.c +++ b/drivers/crypto/hisilicon/hpre/hpre_crypto.c @@ -93,6 +93,7 @@ struct hpre_dh_ctx { char *g; /* m */ dma_addr_t dma_g; + struct crypto_kpp *soft_tfm; }; struct hpre_ecdh_ctx { @@ -103,6 +104,7 @@ struct hpre_ecdh_ctx { /* low address: x->y */ unsigned char *g; dma_addr_t dma_g; + struct crypto_kpp *soft_tfm; }; struct hpre_ctx { @@ -120,6 +122,7 @@ struct hpre_ctx { unsigned int curve_id; /* for high performance core */ u8 enable_hpcore; + bool fallback; }; struct hpre_asym_request { @@ -382,8 +385,10 @@ static int hpre_ctx_init(struct hpre_ctx *ctx, u8 type) struct hpre *hpre; qp = hpre_create_qp(type); - if (!qp) + if (!qp) { + ctx->qp = NULL; return -ENODEV; + } qp->req_cb = hpre_alg_cb; ctx->qp = qp; @@ -509,6 +514,48 @@ static int hpre_dh_compute_value(struct kpp_request *req) return ret; } +static struct kpp_request *hpre_dh_prepare_fb_req(struct kpp_request *req) +{ + struct kpp_request *fb_req = kpp_request_ctx(req); + struct crypto_kpp *tfm = crypto_kpp_reqtfm(req); + struct hpre_ctx *ctx = kpp_tfm_ctx(tfm); + + kpp_request_set_tfm(fb_req, ctx->dh.soft_tfm); + kpp_request_set_callback(fb_req, req->base.flags, req->base.complete, req->base.data); + kpp_request_set_input(fb_req, req->src, req->src_len); + kpp_request_set_output(fb_req, req->dst, req->dst_len); + + return fb_req; +} + +static int hpre_dh_generate_public_key(struct kpp_request *req) +{ + struct crypto_kpp *tfm = crypto_kpp_reqtfm(req); + struct hpre_ctx *ctx = kpp_tfm_ctx(tfm); + struct kpp_request *fb_req; + + if (ctx->fallback) { + fb_req = hpre_dh_prepare_fb_req(req); + return crypto_kpp_generate_public_key(fb_req); + } + + return hpre_dh_compute_value(req); +} + +static int hpre_dh_compute_shared_secret(struct kpp_request *req) +{ + struct crypto_kpp *tfm = crypto_kpp_reqtfm(req); + struct hpre_ctx *ctx = kpp_tfm_ctx(tfm); + struct kpp_request *fb_req; + + if (ctx->fallback) { + fb_req = hpre_dh_prepare_fb_req(req); + return crypto_kpp_compute_shared_secret(fb_req); + } + + return hpre_dh_compute_value(req); +} + static int hpre_is_dh_params_length_valid(unsigned int key_sz) { #define _HPRE_DH_GRP1 768 @@ -535,13 +582,6 @@ static int hpre_dh_set_params(struct hpre_ctx *ctx, struct dh *params) struct device *dev = ctx->dev; unsigned int sz; - if (params->p_size > HPRE_DH_MAX_P_SZ) - return -EINVAL; - - if (hpre_is_dh_params_length_valid(params->p_size << - HPRE_BITS_2_BYTES_SHIFT)) - return -EINVAL; - sz = ctx->key_sz = params->p_size; ctx->dh.xa_p = dma_alloc_coherent(dev, sz << 1, &ctx->dh.dma_xa_p, GFP_KERNEL); @@ -574,6 +614,9 @@ static void hpre_dh_clear_ctx(struct hpre_ctx *ctx, bool is_clear_all) struct device *dev = ctx->dev; unsigned int sz = ctx->key_sz; + if (!ctx->qp) + return; + if (ctx->dh.g) { dma_free_coherent(dev, sz, ctx->dh.g, ctx->dh.dma_g); ctx->dh.g = NULL; @@ -599,6 +642,13 @@ static int hpre_dh_set_secret(struct crypto_kpp *tfm, const void *buf, if (crypto_dh_decode_key(buf, len, ¶ms) < 0) return -EINVAL; + if (!ctx->qp) + goto set_soft_secret; + + if (hpre_is_dh_params_length_valid(params.p_size << + HPRE_BITS_2_BYTES_SHIFT)) + goto set_soft_secret; + /* Free old secret if any */ hpre_dh_clear_ctx(ctx, false); @@ -609,27 +659,55 @@ static int hpre_dh_set_secret(struct crypto_kpp *tfm, const void *buf, memcpy(ctx->dh.xa_p + (ctx->key_sz - params.key_size), params.key, params.key_size); + ctx->fallback = false; return 0; err_clear_ctx: hpre_dh_clear_ctx(ctx, false); return ret; +set_soft_secret: + ctx->fallback = true; + return crypto_kpp_set_secret(ctx->dh.soft_tfm, buf, len); } static unsigned int hpre_dh_max_size(struct crypto_kpp *tfm) { struct hpre_ctx *ctx = kpp_tfm_ctx(tfm); + if (ctx->fallback) + return crypto_kpp_maxsize(ctx->dh.soft_tfm); + return ctx->key_sz; } static int hpre_dh_init_tfm(struct crypto_kpp *tfm) { struct hpre_ctx *ctx = kpp_tfm_ctx(tfm); + const char *alg = kpp_alg_name(tfm); + unsigned int reqsize; + int ret; + + ctx->dh.soft_tfm = crypto_alloc_kpp(alg, 0, CRYPTO_ALG_NEED_FALLBACK); + if (IS_ERR(ctx->dh.soft_tfm)) { + pr_err("Failed to alloc dh tfm!\n"); + return PTR_ERR(ctx->dh.soft_tfm); + } + + crypto_kpp_set_flags(ctx->dh.soft_tfm, crypto_kpp_get_flags(tfm)); + + reqsize = max(sizeof(struct hpre_asym_request) + hpre_align_pd(), + sizeof(struct kpp_request) + crypto_kpp_reqsize(ctx->dh.soft_tfm)); + kpp_set_reqsize(tfm, reqsize); - kpp_set_reqsize(tfm, sizeof(struct hpre_asym_request) + hpre_align_pd()); + ret = hpre_ctx_init(ctx, HPRE_V2_ALG_TYPE); + if (ret && ret != -ENODEV) { + crypto_free_kpp(ctx->dh.soft_tfm); + return ret; + } else if (ret == -ENODEV) { + ctx->fallback = true; + } - return hpre_ctx_init(ctx, HPRE_V2_ALG_TYPE); + return 0; } static void hpre_dh_exit_tfm(struct crypto_kpp *tfm) @@ -637,6 +715,7 @@ static void hpre_dh_exit_tfm(struct crypto_kpp *tfm) struct hpre_ctx *ctx = kpp_tfm_ctx(tfm); hpre_dh_clear_ctx(ctx, true); + crypto_free_kpp(ctx->dh.soft_tfm); } static void hpre_rsa_drop_leading_zeros(const char **ptr, size_t *len) @@ -676,9 +755,8 @@ static int hpre_rsa_enc(struct akcipher_request *req) struct hpre_sqe *msg = &hpre_req->req; int ret; - /* For 512 and 1536 bits key size, use soft tfm instead */ - if (ctx->key_sz == HPRE_RSA_512BITS_KSZ || - ctx->key_sz == HPRE_RSA_1536BITS_KSZ) { + /* For unsupported key size and unavailable devices, use soft tfm instead */ + if (ctx->fallback) { akcipher_request_set_tfm(req, ctx->rsa.soft_tfm); ret = crypto_akcipher_encrypt(req); akcipher_request_set_tfm(req, tfm); @@ -723,9 +801,8 @@ static int hpre_rsa_dec(struct akcipher_request *req) struct hpre_sqe *msg = &hpre_req->req; int ret; - /* For 512 and 1536 bits key size, use soft tfm instead */ - if (ctx->key_sz == HPRE_RSA_512BITS_KSZ || - ctx->key_sz == HPRE_RSA_1536BITS_KSZ) { + /* For unsupported key size and unavailable devices, use soft tfm instead */ + if (ctx->fallback) { akcipher_request_set_tfm(req, ctx->rsa.soft_tfm); ret = crypto_akcipher_decrypt(req); akcipher_request_set_tfm(req, tfm); @@ -778,8 +855,10 @@ static int hpre_rsa_set_n(struct hpre_ctx *ctx, const char *value, ctx->key_sz = vlen; /* if invalid key size provided, we use software tfm */ - if (!hpre_rsa_key_size_is_support(ctx->key_sz)) + if (!hpre_rsa_key_size_is_support(ctx->key_sz)) { + ctx->fallback = true; return 0; + } ctx->rsa.pubkey = dma_alloc_coherent(ctx->dev, vlen << 1, &ctx->rsa.dma_pubkey, @@ -914,6 +993,9 @@ static void hpre_rsa_clear_ctx(struct hpre_ctx *ctx, bool is_clear_all) unsigned int half_key_sz = ctx->key_sz >> 1; struct device *dev = ctx->dev; + if (!ctx->qp) + return; + if (ctx->rsa.pubkey) { dma_free_coherent(dev, ctx->key_sz << 1, ctx->rsa.pubkey, ctx->rsa.dma_pubkey); @@ -993,6 +1075,7 @@ static int hpre_rsa_setkey(struct hpre_ctx *ctx, const void *key, goto free; } + ctx->fallback = false; return 0; free: @@ -1010,6 +1093,9 @@ static int hpre_rsa_setpubkey(struct crypto_akcipher *tfm, const void *key, if (ret) return ret; + if (!ctx->qp) + return 0; + return hpre_rsa_setkey(ctx, key, keylen, false); } @@ -1023,6 +1109,9 @@ static int hpre_rsa_setprivkey(struct crypto_akcipher *tfm, const void *key, if (ret) return ret; + if (!ctx->qp) + return 0; + return hpre_rsa_setkey(ctx, key, keylen, true); } @@ -1030,9 +1119,8 @@ static unsigned int hpre_rsa_max_size(struct crypto_akcipher *tfm) { struct hpre_ctx *ctx = akcipher_tfm_ctx(tfm); - /* For 512 and 1536 bits key size, use soft tfm instead */ - if (ctx->key_sz == HPRE_RSA_512BITS_KSZ || - ctx->key_sz == HPRE_RSA_1536BITS_KSZ) + /* For unsupported key size and unavailable devices, use soft tfm instead */ + if (ctx->fallback) return crypto_akcipher_maxsize(ctx->rsa.soft_tfm); return ctx->key_sz; @@ -1053,10 +1141,14 @@ static int hpre_rsa_init_tfm(struct crypto_akcipher *tfm) hpre_align_pd()); ret = hpre_ctx_init(ctx, HPRE_V2_ALG_TYPE); - if (ret) + if (ret && ret != -ENODEV) { crypto_free_akcipher(ctx->rsa.soft_tfm); + return ret; + } else if (ret == -ENODEV) { + ctx->fallback = true; + } - return ret; + return 0; } static void hpre_rsa_exit_tfm(struct crypto_akcipher *tfm) @@ -1260,6 +1352,9 @@ static int hpre_ecdh_set_secret(struct crypto_kpp *tfm, const void *buf, struct ecdh params; int ret; + if (ctx->fallback) + return crypto_kpp_set_secret(ctx->ecdh.soft_tfm, buf, len); + if (crypto_ecdh_decode_key(buf, len, ¶ms) < 0) { dev_err(dev, "failed to decode ecdh key!\n"); return -EINVAL; @@ -1485,23 +1580,82 @@ static int hpre_ecdh_compute_value(struct kpp_request *req) return ret; } +static int hpre_ecdh_generate_public_key(struct kpp_request *req) +{ + struct crypto_kpp *tfm = crypto_kpp_reqtfm(req); + struct hpre_ctx *ctx = kpp_tfm_ctx(tfm); + int ret; + + if (ctx->fallback) { + kpp_request_set_tfm(req, ctx->ecdh.soft_tfm); + ret = crypto_kpp_generate_public_key(req); + kpp_request_set_tfm(req, tfm); + return ret; + } + + return hpre_ecdh_compute_value(req); +} + +static int hpre_ecdh_compute_shared_secret(struct kpp_request *req) +{ + struct crypto_kpp *tfm = crypto_kpp_reqtfm(req); + struct hpre_ctx *ctx = kpp_tfm_ctx(tfm); + int ret; + + if (ctx->fallback) { + kpp_request_set_tfm(req, ctx->ecdh.soft_tfm); + ret = crypto_kpp_compute_shared_secret(req); + kpp_request_set_tfm(req, tfm); + return ret; + } + + return hpre_ecdh_compute_value(req); +} + static unsigned int hpre_ecdh_max_size(struct crypto_kpp *tfm) { struct hpre_ctx *ctx = kpp_tfm_ctx(tfm); + if (ctx->fallback) + return crypto_kpp_maxsize(ctx->ecdh.soft_tfm); + /* max size is the pub_key_size, include x and y */ return ctx->key_sz << 1; } +static int hpre_ecdh_init_tfm(struct crypto_kpp *tfm) +{ + struct hpre_ctx *ctx = kpp_tfm_ctx(tfm); + const char *alg = kpp_alg_name(tfm); + int ret; + + ret = hpre_ctx_init(ctx, HPRE_V3_ECC_ALG_TYPE); + if (!ret) { + kpp_set_reqsize(tfm, sizeof(struct hpre_asym_request) + hpre_align_pd()); + return 0; + } else if (ret && ret != -ENODEV) { + return ret; + } + + ctx->ecdh.soft_tfm = crypto_alloc_kpp(alg, 0, CRYPTO_ALG_NEED_FALLBACK); + if (IS_ERR(ctx->ecdh.soft_tfm)) { + pr_err("Failed to alloc %s tfm!\n", alg); + return PTR_ERR(ctx->ecdh.soft_tfm); + } + + crypto_kpp_set_flags(ctx->ecdh.soft_tfm, crypto_kpp_get_flags(tfm)); + ctx->fallback = true; + + return 0; +} + static int hpre_ecdh_nist_p192_init_tfm(struct crypto_kpp *tfm) { struct hpre_ctx *ctx = kpp_tfm_ctx(tfm); ctx->curve_id = ECC_CURVE_NIST_P192; - kpp_set_reqsize(tfm, sizeof(struct hpre_asym_request) + hpre_align_pd()); - - return hpre_ctx_init(ctx, HPRE_V3_ECC_ALG_TYPE); + return hpre_ecdh_init_tfm(tfm); } static int hpre_ecdh_nist_p256_init_tfm(struct crypto_kpp *tfm) @@ -1511,9 +1665,7 @@ static int hpre_ecdh_nist_p256_init_tfm(struct crypto_kpp *tfm) ctx->curve_id = ECC_CURVE_NIST_P256; ctx->enable_hpcore = 1; - kpp_set_reqsize(tfm, sizeof(struct hpre_asym_request) + hpre_align_pd()); - - return hpre_ctx_init(ctx, HPRE_V3_ECC_ALG_TYPE); + return hpre_ecdh_init_tfm(tfm); } static int hpre_ecdh_nist_p384_init_tfm(struct crypto_kpp *tfm) @@ -1522,15 +1674,18 @@ static int hpre_ecdh_nist_p384_init_tfm(struct crypto_kpp *tfm) ctx->curve_id = ECC_CURVE_NIST_P384; - kpp_set_reqsize(tfm, sizeof(struct hpre_asym_request) + hpre_align_pd()); - - return hpre_ctx_init(ctx, HPRE_V3_ECC_ALG_TYPE); + return hpre_ecdh_init_tfm(tfm); } static void hpre_ecdh_exit_tfm(struct crypto_kpp *tfm) { struct hpre_ctx *ctx = kpp_tfm_ctx(tfm); + if (ctx->fallback) { + crypto_free_kpp(ctx->ecdh.soft_tfm); + return; + } + hpre_ecc_clear_ctx(ctx, true); } @@ -1548,13 +1703,14 @@ static struct akcipher_alg rsa = { .cra_name = "rsa", .cra_driver_name = "hpre-rsa", .cra_module = THIS_MODULE, + .cra_flags = CRYPTO_ALG_NEED_FALLBACK, }, }; static struct kpp_alg dh = { .set_secret = hpre_dh_set_secret, - .generate_public_key = hpre_dh_compute_value, - .compute_shared_secret = hpre_dh_compute_value, + .generate_public_key = hpre_dh_generate_public_key, + .compute_shared_secret = hpre_dh_compute_shared_secret, .max_size = hpre_dh_max_size, .init = hpre_dh_init_tfm, .exit = hpre_dh_exit_tfm, @@ -1564,14 +1720,15 @@ static struct kpp_alg dh = { .cra_name = "dh", .cra_driver_name = "hpre-dh", .cra_module = THIS_MODULE, + .cra_flags = CRYPTO_ALG_NEED_FALLBACK, }, }; static struct kpp_alg ecdh_curves[] = { { .set_secret = hpre_ecdh_set_secret, - .generate_public_key = hpre_ecdh_compute_value, - .compute_shared_secret = hpre_ecdh_compute_value, + .generate_public_key = hpre_ecdh_generate_public_key, + .compute_shared_secret = hpre_ecdh_compute_shared_secret, .max_size = hpre_ecdh_max_size, .init = hpre_ecdh_nist_p192_init_tfm, .exit = hpre_ecdh_exit_tfm, @@ -1581,11 +1738,12 @@ static struct kpp_alg ecdh_curves[] = { .cra_name = "ecdh-nist-p192", .cra_driver_name = "hpre-ecdh-nist-p192", .cra_module = THIS_MODULE, + .cra_flags = CRYPTO_ALG_NEED_FALLBACK, }, }, { .set_secret = hpre_ecdh_set_secret, - .generate_public_key = hpre_ecdh_compute_value, - .compute_shared_secret = hpre_ecdh_compute_value, + .generate_public_key = hpre_ecdh_generate_public_key, + .compute_shared_secret = hpre_ecdh_compute_shared_secret, .max_size = hpre_ecdh_max_size, .init = hpre_ecdh_nist_p256_init_tfm, .exit = hpre_ecdh_exit_tfm, @@ -1595,11 +1753,12 @@ static struct kpp_alg ecdh_curves[] = { .cra_name = "ecdh-nist-p256", .cra_driver_name = "hpre-ecdh-nist-p256", .cra_module = THIS_MODULE, + .cra_flags = CRYPTO_ALG_NEED_FALLBACK, }, }, { .set_secret = hpre_ecdh_set_secret, - .generate_public_key = hpre_ecdh_compute_value, - .compute_shared_secret = hpre_ecdh_compute_value, + .generate_public_key = hpre_ecdh_generate_public_key, + .compute_shared_secret = hpre_ecdh_compute_shared_secret, .max_size = hpre_ecdh_max_size, .init = hpre_ecdh_nist_p384_init_tfm, .exit = hpre_ecdh_exit_tfm, @@ -1609,6 +1768,7 @@ static struct kpp_alg ecdh_curves[] = { .cra_name = "ecdh-nist-p384", .cra_driver_name = "hpre-ecdh-nist-p384", .cra_module = THIS_MODULE, + .cra_flags = CRYPTO_ALG_NEED_FALLBACK, }, } }; From 740e710a6f2724a906442dbfec25abba64cfe9bf Mon Sep 17 00:00:00 2001 From: Qi Tao Date: Thu, 18 Dec 2025 21:44:52 +0800 Subject: [PATCH 3333/3902] crypto: hisilicon/sec2 - support skcipher/aead fallback for hardware queue unavailable [ Upstream commit e7507439628052363500d717caffb5c2241854dc ] When all hardware queues are busy and no shareable queue, new processes fail to apply for queues. To avoid affecting tasks, support fallback mechanism when hardware queues are unavailable. Fixes: c16a70c1f253 ("crypto: hisilicon/sec - add new algorithm mode for AEAD") Signed-off-by: Qi Tao Signed-off-by: Chenghai Huang Signed-off-by: Herbert Xu Signed-off-by: Sasha Levin --- drivers/crypto/hisilicon/sec2/sec_crypto.c | 62 ++++++++++++++++------ 1 file changed, 47 insertions(+), 15 deletions(-) diff --git a/drivers/crypto/hisilicon/sec2/sec_crypto.c b/drivers/crypto/hisilicon/sec2/sec_crypto.c index d09d081f42dc78..c462b58d303435 100644 --- a/drivers/crypto/hisilicon/sec2/sec_crypto.c +++ b/drivers/crypto/hisilicon/sec2/sec_crypto.c @@ -663,10 +663,8 @@ static int sec_ctx_base_init(struct sec_ctx *ctx) int i, ret; ctx->qps = sec_create_qps(); - if (!ctx->qps) { - pr_err("Can not create sec qps!\n"); + if (!ctx->qps) return -ENODEV; - } sec = container_of(ctx->qps[0]->qm, struct sec_dev, qm); ctx->sec = sec; @@ -702,6 +700,9 @@ static void sec_ctx_base_uninit(struct sec_ctx *ctx) { int i; + if (!ctx->qps) + return; + for (i = 0; i < ctx->sec->ctx_q_num; i++) sec_release_qp_ctx(ctx, &ctx->qp_ctx[i]); @@ -713,6 +714,9 @@ static int sec_cipher_init(struct sec_ctx *ctx) { struct sec_cipher_ctx *c_ctx = &ctx->c_ctx; + if (!ctx->qps) + return 0; + c_ctx->c_key = dma_alloc_coherent(ctx->dev, SEC_MAX_KEY_SIZE, &c_ctx->c_key_dma, GFP_KERNEL); if (!c_ctx->c_key) @@ -725,6 +729,9 @@ static void sec_cipher_uninit(struct sec_ctx *ctx) { struct sec_cipher_ctx *c_ctx = &ctx->c_ctx; + if (!ctx->qps) + return; + memzero_explicit(c_ctx->c_key, SEC_MAX_KEY_SIZE); dma_free_coherent(ctx->dev, SEC_MAX_KEY_SIZE, c_ctx->c_key, c_ctx->c_key_dma); @@ -746,6 +753,9 @@ static void sec_auth_uninit(struct sec_ctx *ctx) { struct sec_auth_ctx *a_ctx = &ctx->a_ctx; + if (!ctx->qps) + return; + memzero_explicit(a_ctx->a_key, SEC_MAX_AKEY_SIZE); dma_free_coherent(ctx->dev, SEC_MAX_AKEY_SIZE, a_ctx->a_key, a_ctx->a_key_dma); @@ -783,7 +793,7 @@ static int sec_skcipher_init(struct crypto_skcipher *tfm) } ret = sec_ctx_base_init(ctx); - if (ret) + if (ret && ret != -ENODEV) return ret; ret = sec_cipher_init(ctx); @@ -892,6 +902,9 @@ static int sec_skcipher_setkey(struct crypto_skcipher *tfm, const u8 *key, struct device *dev = ctx->dev; int ret; + if (!ctx->qps) + goto set_soft_key; + if (c_mode == SEC_CMODE_XTS) { ret = xts_verify_key(tfm, key, keylen); if (ret) { @@ -922,13 +935,14 @@ static int sec_skcipher_setkey(struct crypto_skcipher *tfm, const u8 *key, } memcpy(c_ctx->c_key, key, keylen); - if (c_ctx->fbtfm) { - ret = crypto_sync_skcipher_setkey(c_ctx->fbtfm, key, keylen); - if (ret) { - dev_err(dev, "failed to set fallback skcipher key!\n"); - return ret; - } + +set_soft_key: + ret = crypto_sync_skcipher_setkey(c_ctx->fbtfm, key, keylen); + if (ret) { + dev_err(dev, "failed to set fallback skcipher key!\n"); + return ret; } + return 0; } @@ -1392,6 +1406,9 @@ static int sec_aead_setkey(struct crypto_aead *tfm, const u8 *key, struct crypto_authenc_keys keys; int ret; + if (!ctx->qps) + return sec_aead_fallback_setkey(a_ctx, tfm, key, keylen); + ctx->a_ctx.a_alg = a_alg; ctx->c_ctx.c_alg = c_alg; c_ctx->c_mode = c_mode; @@ -2048,6 +2065,9 @@ static int sec_skcipher_ctx_init(struct crypto_skcipher *tfm) if (ret) return ret; + if (!ctx->qps) + return 0; + if (ctx->sec->qm.ver < QM_HW_V3) { ctx->type_supported = SEC_BD_TYPE2; ctx->req_op = &sec_skcipher_req_ops; @@ -2056,7 +2076,7 @@ static int sec_skcipher_ctx_init(struct crypto_skcipher *tfm) ctx->req_op = &sec_skcipher_req_ops_v3; } - return ret; + return 0; } static void sec_skcipher_ctx_exit(struct crypto_skcipher *tfm) @@ -2124,7 +2144,7 @@ static int sec_aead_ctx_init(struct crypto_aead *tfm, const char *hash_name) int ret; ret = sec_aead_init(tfm); - if (ret) { + if (ret && ret != -ENODEV) { pr_err("hisi_sec2: aead init error!\n"); return ret; } @@ -2166,7 +2186,7 @@ static int sec_aead_xcm_ctx_init(struct crypto_aead *tfm) int ret; ret = sec_aead_init(tfm); - if (ret) { + if (ret && ret != -ENODEV) { dev_err(ctx->dev, "hisi_sec2: aead xcm init error!\n"); return ret; } @@ -2311,6 +2331,9 @@ static int sec_skcipher_crypto(struct skcipher_request *sk_req, bool encrypt) bool need_fallback = false; int ret; + if (!ctx->qps) + goto soft_crypto; + if (!sk_req->cryptlen) { if (ctx->c_ctx.c_mode == SEC_CMODE_XTS) return -EINVAL; @@ -2328,9 +2351,12 @@ static int sec_skcipher_crypto(struct skcipher_request *sk_req, bool encrypt) return -EINVAL; if (unlikely(ctx->c_ctx.fallback || need_fallback)) - return sec_skcipher_soft_crypto(ctx, sk_req, encrypt); + goto soft_crypto; return ctx->req_op->process(ctx, req); + +soft_crypto: + return sec_skcipher_soft_crypto(ctx, sk_req, encrypt); } static int sec_skcipher_encrypt(struct skcipher_request *sk_req) @@ -2538,6 +2564,9 @@ static int sec_aead_crypto(struct aead_request *a_req, bool encrypt) bool need_fallback = false; int ret; + if (!ctx->qps) + goto soft_crypto; + req->flag = a_req->base.flags; req->aead_req.aead_req = a_req; req->c_req.encrypt = encrypt; @@ -2548,11 +2577,14 @@ static int sec_aead_crypto(struct aead_request *a_req, bool encrypt) ret = sec_aead_param_check(ctx, req, &need_fallback); if (unlikely(ret)) { if (need_fallback) - return sec_aead_soft_crypto(ctx, a_req, encrypt); + goto soft_crypto; return -EINVAL; } return ctx->req_op->process(ctx, req); + +soft_crypto: + return sec_aead_soft_crypto(ctx, a_req, encrypt); } static int sec_aead_encrypt(struct aead_request *a_req) From 33d3290333e8eeb09b49bb14852b83c0aa84618c Mon Sep 17 00:00:00 2001 From: Chenghai Huang Date: Fri, 19 Dec 2025 11:36:19 +0800 Subject: [PATCH 3334/3902] crypto: hisilicon/sgl - fix inconsistent map/unmap direction issue [ Upstream commit 4154f7d3b1c133b909d20c44ecb8277e8482aa6b ] Ensure that the direction for dma_map_sg and dma_unmap_sg is consistent. Fixes: 2566de3e06a3 ("crypto: hisilicon - Use fine grained DMA mapping direction") Signed-off-by: Chenghai Huang Reviewed-by: Zenghui Yu Signed-off-by: Herbert Xu Signed-off-by: Sasha Levin --- drivers/crypto/hisilicon/sgl.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/crypto/hisilicon/sgl.c b/drivers/crypto/hisilicon/sgl.c index 7a9ef2a9972a22..848ad7b101d939 100644 --- a/drivers/crypto/hisilicon/sgl.c +++ b/drivers/crypto/hisilicon/sgl.c @@ -265,7 +265,7 @@ hisi_acc_sg_buf_map_to_hw_sgl(struct device *dev, struct scatterlist *sgl, return curr_hw_sgl; err_unmap: - dma_unmap_sg(dev, sgl, sg_n, DMA_BIDIRECTIONAL); + dma_unmap_sg(dev, sgl, sg_n, dir); return ERR_PTR(ret); } From 92a8cb1806adefb263cf096eab6705705cf7eee1 Mon Sep 17 00:00:00 2001 From: Puranjay Mohan Date: Thu, 15 Jan 2026 07:11:40 -0800 Subject: [PATCH 3335/3902] bpf: Preserve id of register in sync_linked_regs() [ Upstream commit af9e89d8dd39530c8bd14c33ddf6b502df1071b6 ] sync_linked_regs() copies the id of known_reg to reg when propagating bounds of known_reg to reg using the off of known_reg, but when known_reg was linked to reg like: known_reg = reg ; both known_reg and reg get same id known_reg += 4 ; known_reg gets off = 4, and its id gets BPF_ADD_CONST now when a call to sync_linked_regs() happens, let's say with the following: if known_reg >= 10 goto pc+2 known_reg's new bounds are propagated to reg but now reg gets BPF_ADD_CONST from the copy. This means if another link to reg is created like: another_reg = reg ; another_reg should get the id of reg but assign_scalar_id_before_mov() sees BPF_ADD_CONST on reg and assigns a new id to it. As reg has a new id now, known_reg's link to reg is broken. If we find new bounds for known_reg, they will not be propagated to reg. This can be seen in the selftest added in the next commit: 0: (85) call bpf_get_prandom_u32#7 ; R0=scalar() 1: (57) r0 &= 255 ; R0=scalar(smin=smin32=0,smax=umax=smax32=umax32=255,var_off=(0x0; 0xff)) 2: (bf) r1 = r0 ; R0=scalar(id=1,smin=smin32=0,smax=umax=smax32=umax32=255,var_off=(0x0; 0xff)) R1=scalar(id=1,smin=smin32=0,smax=umax=smax32=umax32=255,var_off=(0x0; 0xff)) 3: (07) r1 += 4 ; R1=scalar(id=1+4,smin=umin=smin32=umin32=4,smax=umax=smax32=umax32=259,var_off=(0x0; 0x1ff)) 4: (a5) if r1 < 0xa goto pc+4 ; R1=scalar(id=1+4,smin=umin=smin32=umin32=10,smax=umax=smax32=umax32=259,var_off=(0x0; 0x1ff)) 5: (bf) r2 = r0 ; R0=scalar(id=2,smin=umin=smin32=umin32=6,smax=umax=smax32=umax32=255) R2=scalar(id=2,smin=umin=smin32=umin32=6,smax=umax=smax32=umax32=255) 6: (a5) if r1 < 0xe goto pc+2 ; R1=scalar(id=1+4,smin=umin=smin32=umin32=14,smax=umax=smax32=umax32=259,var_off=(0x0; 0x1ff)) 7: (35) if r0 >= 0xa goto pc+1 ; R0=scalar(id=2,smin=umin=smin32=umin32=6,smax=umax=smax32=umax32=9,var_off=(0x0; 0xf)) 8: (37) r0 /= 0 div by zero When 4 is verified, r1's bounds are propagated to r0 but r0 also gets BPF_ADD_CONST (bug). When 5 is verified, r0 gets a new id (2) and its link with r1 is broken. After 6 we know r1 has bounds [14, 259] and therefore r0 should have bounds [10, 255], therefore the branch at 7 is always taken. But because r0's id was changed to 2, r1's new bounds are not propagated to r0. The verifier still thinks r0 has bounds [6, 255] before 7 and execution can reach div by zero. Fix this by preserving id in sync_linked_regs() like off and subreg_def. Fixes: 98d7ca374ba4 ("bpf: Track delta between "linked" registers.") Signed-off-by: Puranjay Mohan Acked-by: Eduard Zingerman Link: https://lore.kernel.org/r/20260115151143.1344724-2-puranjay@kernel.org Signed-off-by: Alexei Starovoitov Signed-off-by: Sasha Levin --- kernel/bpf/verifier.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/kernel/bpf/verifier.c b/kernel/bpf/verifier.c index 89560e455ce7b2..14546d1bdb52c6 100644 --- a/kernel/bpf/verifier.c +++ b/kernel/bpf/verifier.c @@ -16715,6 +16715,7 @@ static void sync_linked_regs(struct bpf_verifier_state *vstate, struct bpf_reg_s } else { s32 saved_subreg_def = reg->subreg_def; s32 saved_off = reg->off; + u32 saved_id = reg->id; fake_reg.type = SCALAR_VALUE; __mark_reg_known(&fake_reg, (s32)reg->off - (s32)known_reg->off); @@ -16722,10 +16723,11 @@ static void sync_linked_regs(struct bpf_verifier_state *vstate, struct bpf_reg_s /* reg = known_reg; reg += delta */ copy_register_state(reg, known_reg); /* - * Must preserve off, id and add_const flag, + * Must preserve off, id and subreg_def flag, * otherwise another sync_linked_regs() will be incorrect. */ reg->off = saved_off; + reg->id = saved_id; reg->subreg_def = saved_subreg_def; scalar32_min_max_add(reg, &fake_reg); From fdfe75161f6e8c41a7d3023fbb815b537107b806 Mon Sep 17 00:00:00 2001 From: Zesen Liu Date: Tue, 20 Jan 2026 16:28:46 +0800 Subject: [PATCH 3336/3902] bpf: Fix memory access flags in helper prototypes [ Upstream commit 802eef5afb1865bc5536a5302c068ba2215a1f72 ] After commit 37cce22dbd51 ("bpf: verifier: Refactor helper access type tracking"), the verifier started relying on the access type flags in helper function prototypes to perform memory access optimizations. Currently, several helper functions utilizing ARG_PTR_TO_MEM lack the corresponding MEM_RDONLY or MEM_WRITE flags. This omission causes the verifier to incorrectly assume that the buffer contents are unchanged across the helper call. Consequently, the verifier may optimize away subsequent reads based on this wrong assumption, leading to correctness issues. For bpf_get_stack_proto_raw_tp, the original MEM_RDONLY was incorrect since the helper writes to the buffer. Change it to ARG_PTR_TO_UNINIT_MEM which correctly indicates write access to potentially uninitialized memory. Similar issues were recently addressed for specific helpers in commit ac44dcc788b9 ("bpf: Fix verifier assumptions of bpf_d_path's output buffer") and commit 2eb7648558a7 ("bpf: Specify access type of bpf_sysctl_get_name args"). Fix these prototypes by adding the correct memory access flags. Fixes: 37cce22dbd51 ("bpf: verifier: Refactor helper access type tracking") Co-developed-by: Shuran Liu Signed-off-by: Shuran Liu Co-developed-by: Peili Gao Signed-off-by: Peili Gao Co-developed-by: Haoran Ni Signed-off-by: Haoran Ni Signed-off-by: Zesen Liu Acked-by: Eduard Zingerman Link: https://lore.kernel.org/r/20260120-helper_proto-v3-1-27b0180b4e77@gmail.com Signed-off-by: Alexei Starovoitov Signed-off-by: Sasha Levin --- kernel/bpf/helpers.c | 2 +- kernel/bpf/syscall.c | 2 +- kernel/trace/bpf_trace.c | 6 +++--- net/core/filter.c | 20 ++++++++++---------- 4 files changed, 15 insertions(+), 15 deletions(-) diff --git a/kernel/bpf/helpers.c b/kernel/bpf/helpers.c index 81ef159ef89bd3..68da6dcfb4bb7c 100644 --- a/kernel/bpf/helpers.c +++ b/kernel/bpf/helpers.c @@ -1080,7 +1080,7 @@ const struct bpf_func_proto bpf_snprintf_proto = { .func = bpf_snprintf, .gpl_only = true, .ret_type = RET_INTEGER, - .arg1_type = ARG_PTR_TO_MEM_OR_NULL, + .arg1_type = ARG_PTR_TO_MEM_OR_NULL | MEM_WRITE, .arg2_type = ARG_CONST_SIZE_OR_ZERO, .arg3_type = ARG_PTR_TO_CONST_STR, .arg4_type = ARG_PTR_TO_MEM | PTR_MAYBE_NULL | MEM_RDONLY, diff --git a/kernel/bpf/syscall.c b/kernel/bpf/syscall.c index df219e72590997..e9cf69594824c2 100644 --- a/kernel/bpf/syscall.c +++ b/kernel/bpf/syscall.c @@ -6396,7 +6396,7 @@ static const struct bpf_func_proto bpf_kallsyms_lookup_name_proto = { .func = bpf_kallsyms_lookup_name, .gpl_only = false, .ret_type = RET_INTEGER, - .arg1_type = ARG_PTR_TO_MEM, + .arg1_type = ARG_PTR_TO_MEM | MEM_RDONLY, .arg2_type = ARG_CONST_SIZE_OR_ZERO, .arg3_type = ARG_ANYTHING, .arg4_type = ARG_PTR_TO_FIXED_SIZE_MEM | MEM_UNINIT | MEM_WRITE | MEM_ALIGNED, diff --git a/kernel/trace/bpf_trace.c b/kernel/trace/bpf_trace.c index 49e0bdaa7a1bf1..e7f1fe44352afe 100644 --- a/kernel/trace/bpf_trace.c +++ b/kernel/trace/bpf_trace.c @@ -1022,7 +1022,7 @@ const struct bpf_func_proto bpf_snprintf_btf_proto = { .func = bpf_snprintf_btf, .gpl_only = false, .ret_type = RET_INTEGER, - .arg1_type = ARG_PTR_TO_MEM, + .arg1_type = ARG_PTR_TO_MEM | MEM_WRITE, .arg2_type = ARG_CONST_SIZE, .arg3_type = ARG_PTR_TO_MEM | MEM_RDONLY, .arg4_type = ARG_CONST_SIZE, @@ -1526,7 +1526,7 @@ static const struct bpf_func_proto bpf_read_branch_records_proto = { .gpl_only = true, .ret_type = RET_INTEGER, .arg1_type = ARG_PTR_TO_CTX, - .arg2_type = ARG_PTR_TO_MEM_OR_NULL, + .arg2_type = ARG_PTR_TO_MEM_OR_NULL | MEM_WRITE, .arg3_type = ARG_CONST_SIZE_OR_ZERO, .arg4_type = ARG_ANYTHING, }; @@ -1661,7 +1661,7 @@ static const struct bpf_func_proto bpf_get_stack_proto_raw_tp = { .gpl_only = true, .ret_type = RET_INTEGER, .arg1_type = ARG_PTR_TO_CTX, - .arg2_type = ARG_PTR_TO_MEM | MEM_RDONLY, + .arg2_type = ARG_PTR_TO_UNINIT_MEM, .arg3_type = ARG_CONST_SIZE_OR_ZERO, .arg4_type = ARG_ANYTHING, }; diff --git a/net/core/filter.c b/net/core/filter.c index 88b265f6ccf897..b9a51f322b655d 100644 --- a/net/core/filter.c +++ b/net/core/filter.c @@ -6325,7 +6325,7 @@ static const struct bpf_func_proto bpf_xdp_fib_lookup_proto = { .gpl_only = true, .ret_type = RET_INTEGER, .arg1_type = ARG_PTR_TO_CTX, - .arg2_type = ARG_PTR_TO_MEM, + .arg2_type = ARG_PTR_TO_MEM | MEM_WRITE, .arg3_type = ARG_CONST_SIZE, .arg4_type = ARG_ANYTHING, }; @@ -6380,7 +6380,7 @@ static const struct bpf_func_proto bpf_skb_fib_lookup_proto = { .gpl_only = true, .ret_type = RET_INTEGER, .arg1_type = ARG_PTR_TO_CTX, - .arg2_type = ARG_PTR_TO_MEM, + .arg2_type = ARG_PTR_TO_MEM | MEM_WRITE, .arg3_type = ARG_CONST_SIZE, .arg4_type = ARG_ANYTHING, }; @@ -7934,9 +7934,9 @@ static const struct bpf_func_proto bpf_tcp_raw_gen_syncookie_ipv4_proto = { .gpl_only = true, /* __cookie_v4_init_sequence() is GPL */ .pkt_access = true, .ret_type = RET_INTEGER, - .arg1_type = ARG_PTR_TO_FIXED_SIZE_MEM, + .arg1_type = ARG_PTR_TO_FIXED_SIZE_MEM | MEM_RDONLY, .arg1_size = sizeof(struct iphdr), - .arg2_type = ARG_PTR_TO_MEM, + .arg2_type = ARG_PTR_TO_MEM | MEM_RDONLY, .arg3_type = ARG_CONST_SIZE_OR_ZERO, }; @@ -7966,9 +7966,9 @@ static const struct bpf_func_proto bpf_tcp_raw_gen_syncookie_ipv6_proto = { .gpl_only = true, /* __cookie_v6_init_sequence() is GPL */ .pkt_access = true, .ret_type = RET_INTEGER, - .arg1_type = ARG_PTR_TO_FIXED_SIZE_MEM, + .arg1_type = ARG_PTR_TO_FIXED_SIZE_MEM | MEM_RDONLY, .arg1_size = sizeof(struct ipv6hdr), - .arg2_type = ARG_PTR_TO_MEM, + .arg2_type = ARG_PTR_TO_MEM | MEM_RDONLY, .arg3_type = ARG_CONST_SIZE_OR_ZERO, }; @@ -7986,9 +7986,9 @@ static const struct bpf_func_proto bpf_tcp_raw_check_syncookie_ipv4_proto = { .gpl_only = true, /* __cookie_v4_check is GPL */ .pkt_access = true, .ret_type = RET_INTEGER, - .arg1_type = ARG_PTR_TO_FIXED_SIZE_MEM, + .arg1_type = ARG_PTR_TO_FIXED_SIZE_MEM | MEM_RDONLY, .arg1_size = sizeof(struct iphdr), - .arg2_type = ARG_PTR_TO_FIXED_SIZE_MEM, + .arg2_type = ARG_PTR_TO_FIXED_SIZE_MEM | MEM_RDONLY, .arg2_size = sizeof(struct tcphdr), }; @@ -8010,9 +8010,9 @@ static const struct bpf_func_proto bpf_tcp_raw_check_syncookie_ipv6_proto = { .gpl_only = true, /* __cookie_v6_check is GPL */ .pkt_access = true, .ret_type = RET_INTEGER, - .arg1_type = ARG_PTR_TO_FIXED_SIZE_MEM, + .arg1_type = ARG_PTR_TO_FIXED_SIZE_MEM | MEM_RDONLY, .arg1_size = sizeof(struct ipv6hdr), - .arg2_type = ARG_PTR_TO_FIXED_SIZE_MEM, + .arg2_type = ARG_PTR_TO_FIXED_SIZE_MEM | MEM_RDONLY, .arg2_size = sizeof(struct tcphdr), }; #endif /* CONFIG_SYN_COOKIES */ From c77581cdebada8a6eec9723274b16c93abbb8b34 Mon Sep 17 00:00:00 2001 From: Kery Qi Date: Wed, 21 Jan 2026 17:41:16 +0800 Subject: [PATCH 3337/3902] selftests/bpf: Fix resource leak in serial_test_wq on attach failure [ Upstream commit a32ae2658471dd87a2f7a438388ed7d9a5767212 ] When wq__attach() fails, serial_test_wq() returns early without calling wq__destroy(), leaking the skeleton resources allocated by wq__open_and_load(). This causes ASAN leak reports in selftests runs. Fix this by jumping to a common clean_up label that calls wq__destroy() on all exit paths after successful open_and_load. Note that the early return after wq__open_and_load() failure is correct and doesn't need fixing, since that function returns NULL on failure (after internally cleaning up any partial allocations). Fixes: 8290dba51910 ("selftests/bpf: wq: add bpf_wq_start() checks") Signed-off-by: Kery Qi Signed-off-by: Andrii Nakryiko Acked-by: Yonghong Song Link: https://lore.kernel.org/bpf/20260121094114.1801-3-qikeyu2017@gmail.com Signed-off-by: Sasha Levin --- tools/testing/selftests/bpf/prog_tests/wq.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/tools/testing/selftests/bpf/prog_tests/wq.c b/tools/testing/selftests/bpf/prog_tests/wq.c index 99e438fe12acd5..15ac8e6d17450d 100644 --- a/tools/testing/selftests/bpf/prog_tests/wq.c +++ b/tools/testing/selftests/bpf/prog_tests/wq.c @@ -16,12 +16,12 @@ void serial_test_wq(void) /* re-run the success test to check if the timer was actually executed */ wq_skel = wq__open_and_load(); - if (!ASSERT_OK_PTR(wq_skel, "wq_skel_load")) + if (!ASSERT_OK_PTR(wq_skel, "wq__open_and_load")) return; err = wq__attach(wq_skel); if (!ASSERT_OK(err, "wq_attach")) - return; + goto clean_up; prog_fd = bpf_program__fd(wq_skel->progs.test_syscall_array_sleepable); err = bpf_prog_test_run_opts(prog_fd, &topts); @@ -31,6 +31,7 @@ void serial_test_wq(void) usleep(50); /* 10 usecs should be enough, but give it extra */ ASSERT_EQ(wq_skel->bss->ok_sleepable, (1 << 1), "ok_sleepable"); +clean_up: wq__destroy(wq_skel); } From 1bbb1356f6be500f9cda5380be26e7b1370a146b Mon Sep 17 00:00:00 2001 From: Thomas Gleixner Date: Mon, 19 Jan 2026 11:38:34 +0100 Subject: [PATCH 3338/3902] hrtimer: Fix trace oddity [ Upstream commit 5d6446f409da00e5a389125ddb5ce09f5bc404c9 ] It turns out that __run_hrtimer() will trace like: -0 [032] d.h2. 20705.474563: hrtimer_cancel: hrtimer=0xff2db8f77f8226e8 -0 [032] d.h1. 20705.474563: hrtimer_expire_entry: hrtimer=0xff2db8f77f8226e8 now=20699452001850 function=tick_nohz_handler/0x0 Which is a bit nonsensical, the timer doesn't get canceled on expiration. The cause is the use of the incorrect debug helper. Fixes: c6a2a1770245 ("hrtimer: Add tracepoint for hrtimers") Reported-by: Peter Zijlstra (Intel) Signed-off-by: Thomas Gleixner Signed-off-by: Peter Zijlstra (Intel) Link: https://patch.msgid.link/20260121143208.219595606@infradead.org Signed-off-by: Sasha Levin --- kernel/time/hrtimer.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/kernel/time/hrtimer.c b/kernel/time/hrtimer.c index e618addb58641c..21b6d934014803 100644 --- a/kernel/time/hrtimer.c +++ b/kernel/time/hrtimer.c @@ -1742,7 +1742,7 @@ static void __run_hrtimer(struct hrtimer_cpu_base *cpu_base, lockdep_assert_held(&cpu_base->lock); - debug_deactivate(timer); + debug_hrtimer_deactivate(timer); base->running = timer; /* From 7530c3595d1e23bc5938cbd44b7e8f33457fc71f Mon Sep 17 00:00:00 2001 From: Aleksander Jan Bajkowski Date: Tue, 30 Dec 2025 22:17:17 +0100 Subject: [PATCH 3339/3902] crypto: inside-secure/eip93 - fix kernel panic in driver detach [ Upstream commit b6e32ba6d32503440a3e3e16c8d0521cbb7e0c5d ] During driver detach, the same hash algorithm is unregistered multiple times due to a wrong iterator. Fixes: 9739f5f93b78 ("crypto: eip93 - Add Inside Secure SafeXcel EIP-93 crypto engine support") Signed-off-by: Aleksander Jan Bajkowski Reviewed-by: Antoine Tenart Signed-off-by: Herbert Xu Signed-off-by: Sasha Levin --- drivers/crypto/inside-secure/eip93/eip93-main.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/crypto/inside-secure/eip93/eip93-main.c b/drivers/crypto/inside-secure/eip93/eip93-main.c index 0b38a567da0e01..3cdc3308dcac86 100644 --- a/drivers/crypto/inside-secure/eip93/eip93-main.c +++ b/drivers/crypto/inside-secure/eip93/eip93-main.c @@ -90,7 +90,7 @@ static void eip93_unregister_algs(unsigned int i) crypto_unregister_aead(&eip93_algs[j]->alg.aead); break; case EIP93_ALG_TYPE_HASH: - crypto_unregister_ahash(&eip93_algs[i]->alg.ahash); + crypto_unregister_ahash(&eip93_algs[j]->alg.ahash); break; } } From e6a4b3d22221183a173e88e3df07c756e83140e4 Mon Sep 17 00:00:00 2001 From: Tom Lendacky Date: Mon, 5 Jan 2026 10:22:17 -0700 Subject: [PATCH 3340/3902] crypto: ccp - Fix a case where SNP_SHUTDOWN is missed [ Upstream commit 551120148b67e04527b405c5ec33a31593846ba4 ] If page reclaim fails in sev_ioctl_do_snp_platform_status() and SNP was moved from UNINIT to INIT for the function, SNP is not moved back to UNINIT state. Additionally, SNP is not required to be initialized in order to execute the SNP_PLATFORM_STATUS command, so don't attempt to move to INIT state and let SNP_PLATFORM_STATUS report the status as is. Fixes: ceac7fb89e8d ("crypto: ccp - Ensure implicit SEV/SNP init and shutdown in ioctls") Signed-off-by: Tom Lendacky Reviewed-by: Tycho Andersen (AMD) Reviewed-by: Alexey Kardashevskiy Signed-off-by: Tycho Andersen (AMD) Signed-off-by: Herbert Xu Signed-off-by: Sasha Levin --- drivers/crypto/ccp/sev-dev.c | 46 ++++++++++++++++++------------------ 1 file changed, 23 insertions(+), 23 deletions(-) diff --git a/drivers/crypto/ccp/sev-dev.c b/drivers/crypto/ccp/sev-dev.c index 0d13d47c164bb7..3c6ee8b4e4487b 100644 --- a/drivers/crypto/ccp/sev-dev.c +++ b/drivers/crypto/ccp/sev-dev.c @@ -2351,11 +2351,10 @@ static int sev_ioctl_do_pdh_export(struct sev_issue_cmd *argp, bool writable) static int sev_ioctl_do_snp_platform_status(struct sev_issue_cmd *argp) { struct sev_device *sev = psp_master->sev_data; - bool shutdown_required = false; struct sev_data_snp_addr buf; struct page *status_page; - int ret, error; void *data; + int ret; if (!argp->data) return -EINVAL; @@ -2366,31 +2365,35 @@ static int sev_ioctl_do_snp_platform_status(struct sev_issue_cmd *argp) data = page_address(status_page); - if (!sev->snp_initialized) { - ret = snp_move_to_init_state(argp, &shutdown_required); - if (ret) - goto cleanup; - } - /* - * Firmware expects status page to be in firmware-owned state, otherwise - * it will report firmware error code INVALID_PAGE_STATE (0x1A). + * SNP_PLATFORM_STATUS can be executed in any SNP state. But if executed + * when SNP has been initialized, the status page must be firmware-owned. */ - if (rmp_mark_pages_firmware(__pa(data), 1, true)) { - ret = -EFAULT; - goto cleanup; + if (sev->snp_initialized) { + /* + * Firmware expects the status page to be in Firmware state, + * otherwise it will report an error INVALID_PAGE_STATE. + */ + if (rmp_mark_pages_firmware(__pa(data), 1, true)) { + ret = -EFAULT; + goto cleanup; + } } buf.address = __psp_pa(data); ret = __sev_do_cmd_locked(SEV_CMD_SNP_PLATFORM_STATUS, &buf, &argp->error); - /* - * Status page will be transitioned to Reclaim state upon success, or - * left in Firmware state in failure. Use snp_reclaim_pages() to - * transition either case back to Hypervisor-owned state. - */ - if (snp_reclaim_pages(__pa(data), 1, true)) - return -EFAULT; + if (sev->snp_initialized) { + /* + * The status page will be in Reclaim state on success, or left + * in Firmware state on failure. Use snp_reclaim_pages() to + * transition either case back to Hypervisor-owned state. + */ + if (snp_reclaim_pages(__pa(data), 1, true)) { + snp_leak_pages(__page_to_pfn(status_page), 1); + return -EFAULT; + } + } if (ret) goto cleanup; @@ -2400,9 +2403,6 @@ static int sev_ioctl_do_snp_platform_status(struct sev_issue_cmd *argp) ret = -EFAULT; cleanup: - if (shutdown_required) - __sev_snp_shutdown_locked(&error, false); - __free_pages(status_page, 0); return ret; } From 46825599fad19684091ea630080b259eede510ee Mon Sep 17 00:00:00 2001 From: "Tycho Andersen (AMD)" Date: Mon, 5 Jan 2026 10:22:18 -0700 Subject: [PATCH 3341/3902] crypto: ccp - narrow scope of snp_range_list [ Upstream commit dc8ccab15081efc4f2c5a9fc7b209cd641d29177 ] snp_range_list is only used in __sev_snp_init_locked() in the SNP_INIT_EX case, move the declaration there and add a __free() cleanup helper for it instead of waiting until shutdown. Fixes: 1ca5614b84ee ("crypto: ccp: Add support to initialize the AMD-SP for SEV-SNP") Reviewed-by: Alexey Kardashevskiy Signed-off-by: Tycho Andersen (AMD) Reviewed-by: Tom Lendacky Signed-off-by: Herbert Xu Signed-off-by: Sasha Levin --- drivers/crypto/ccp/sev-dev.c | 13 +------------ 1 file changed, 1 insertion(+), 12 deletions(-) diff --git a/drivers/crypto/ccp/sev-dev.c b/drivers/crypto/ccp/sev-dev.c index 3c6ee8b4e4487b..5fdba0fe4accea 100644 --- a/drivers/crypto/ccp/sev-dev.c +++ b/drivers/crypto/ccp/sev-dev.c @@ -119,13 +119,6 @@ static size_t sev_es_tmr_size = SEV_TMR_SIZE; #define NV_LENGTH (32 * 1024) static void *sev_init_ex_buffer; -/* - * SEV_DATA_RANGE_LIST: - * Array containing range of pages that firmware transitions to HV-fixed - * page state. - */ -static struct sev_data_range_list *snp_range_list; - static void __sev_firmware_shutdown(struct sev_device *sev, bool panic); static int snp_shutdown_on_panic(struct notifier_block *nb, @@ -1365,6 +1358,7 @@ static int snp_filter_reserved_mem_regions(struct resource *rs, void *arg) static int __sev_snp_init_locked(int *error, unsigned int max_snp_asid) { + struct sev_data_range_list *snp_range_list __free(kfree) = NULL; struct psp_device *psp = psp_master; struct sev_data_snp_init_ex data; struct sev_device *sev; @@ -2753,11 +2747,6 @@ static void __sev_firmware_shutdown(struct sev_device *sev, bool panic) sev_init_ex_buffer = NULL; } - if (snp_range_list) { - kfree(snp_range_list); - snp_range_list = NULL; - } - __sev_snp_shutdown_locked(&error, panic); } From 6abdd33a088db9bca9fd79ab6ebe5e5c17fb68b1 Mon Sep 17 00:00:00 2001 From: Aleksander Jan Bajkowski Date: Mon, 5 Jan 2026 21:41:49 +0100 Subject: [PATCH 3342/3902] hwrng: airoha - set rng quality to 900 [ Upstream commit c0008a29a006091d7f9d288620c2456afa23ff27 ] Airoha uses RAW mode to collect noise from the TRNG. These appear to be unprocessed oscillations from the tero loop. For this reason, they do not have a perfect distribution and entropy. Simple noise compression reduces its size by 9%, so setting the quality to 900 seems reasonable. The same value is used by the downstream driver. Compare the size before and after compression: $ ls -l random_airoha* -rw-r--r-- 1 aleksander aleksander 76546048 Jan 3 23:43 random_airoha -rw-rw-r-- 1 aleksander aleksander 69783562 Jan 5 20:23 random_airoha.zip FIPS test results: $ cat random_airoha | rngtest -c 10000 rngtest 2.6 Copyright (c) 2004 by Henrique de Moraes Holschuh This is free software; see the source for copying conditions. There is NO warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. rngtest: starting FIPS tests... rngtest: bits received from input: 200000032 rngtest: FIPS 140-2 successes: 0 rngtest: FIPS 140-2 failures: 10000 rngtest: FIPS 140-2(2001-10-10) Monobit: 9957 rngtest: FIPS 140-2(2001-10-10) Poker: 10000 rngtest: FIPS 140-2(2001-10-10) Runs: 10000 rngtest: FIPS 140-2(2001-10-10) Long run: 4249 rngtest: FIPS 140-2(2001-10-10) Continuous run: 0 rngtest: input channel speed: (min=953.674; avg=27698.935; max=19073.486)Mibits/s rngtest: FIPS tests speed: (min=59.791; avg=298.028; max=328.853)Mibits/s rngtest: Program run time: 647638 microseconds In general, these data look like real noise, but with lower entropy than expected. Fixes: e53ca8efcc5e ("hwrng: airoha - add support for Airoha EN7581 TRNG") Suggested-by: Benjamin Larsson Signed-off-by: Aleksander Jan Bajkowski Signed-off-by: Herbert Xu Signed-off-by: Sasha Levin --- drivers/char/hw_random/airoha-trng.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/char/hw_random/airoha-trng.c b/drivers/char/hw_random/airoha-trng.c index 1dbfa9505c214f..9a648f6d9fd409 100644 --- a/drivers/char/hw_random/airoha-trng.c +++ b/drivers/char/hw_random/airoha-trng.c @@ -212,6 +212,7 @@ static int airoha_trng_probe(struct platform_device *pdev) trng->rng.init = airoha_trng_init; trng->rng.cleanup = airoha_trng_cleanup; trng->rng.read = airoha_trng_read; + trng->rng.quality = 900; ret = devm_hwrng_register(dev, &trng->rng); if (ret) { From 3ed835f6aa1888fbeee3811bc7e75c970e578eb1 Mon Sep 17 00:00:00 2001 From: Kumar Kartikeya Dwivedi Date: Thu, 22 Jan 2026 03:59:11 -0800 Subject: [PATCH 3343/3902] rqspinlock: Fix TAS fallback lock entry creation [ Upstream commit 82f3b142c99cf44c7b1e70b7720169c646b9760f ] The TAS fallback can be invoked directly when queued spin locks are disabled, and through the slow path when paravirt is enabled for queued spin locks. In the latter case, the res_spin_lock macro will attempt the fast path and already hold the entry when entering the slow path. This will lead to creation of extraneous entries that are not released, which may cause false positives for deadlock detection. Fix this by always preceding invocation of the TAS fallback in every case with the grabbing of the held lock entry, and add a comment to make note of this. Fixes: c9102a68c070 ("rqspinlock: Add a test-and-set fallback") Reported-by: Amery Hung Signed-off-by: Kumar Kartikeya Dwivedi Tested-by: Amery Hung Link: https://lore.kernel.org/r/20260122115911.3668985-1-memxor@gmail.com Signed-off-by: Alexei Starovoitov Signed-off-by: Sasha Levin --- include/asm-generic/rqspinlock.h | 2 +- kernel/bpf/rqspinlock.c | 7 ++++--- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/include/asm-generic/rqspinlock.h b/include/asm-generic/rqspinlock.h index 0f2dcbbfee2f0a..5c5cf2f7fc395a 100644 --- a/include/asm-generic/rqspinlock.h +++ b/include/asm-generic/rqspinlock.h @@ -191,7 +191,7 @@ static __always_inline int res_spin_lock(rqspinlock_t *lock) #else -#define res_spin_lock(lock) resilient_tas_spin_lock(lock) +#define res_spin_lock(lock) ({ grab_held_lock_entry(lock); resilient_tas_spin_lock(lock); }) #endif /* CONFIG_QUEUED_SPINLOCKS */ diff --git a/kernel/bpf/rqspinlock.c b/kernel/bpf/rqspinlock.c index 3faf9cbd6c753b..c0c93a0f5af636 100644 --- a/kernel/bpf/rqspinlock.c +++ b/kernel/bpf/rqspinlock.c @@ -276,10 +276,11 @@ int __lockfunc resilient_tas_spin_lock(rqspinlock_t *lock) RES_INIT_TIMEOUT(ts); /* - * The fast path is not invoked for the TAS fallback, so we must grab - * the deadlock detection entry here. + * We are either called directly from res_spin_lock after grabbing the + * deadlock detection entry when queued spinlocks are disabled, or from + * resilient_queued_spin_lock_slowpath after grabbing the deadlock + * detection entry. No need to obtain it here. */ - grab_held_lock_entry(lock); /* * Since the waiting loop's time is dependent on the amount of From f274a4540226e1d5d7664d32263f04d742d643ae Mon Sep 17 00:00:00 2001 From: Jiayuan Chen Date: Sat, 24 Jan 2026 19:32:43 +0800 Subject: [PATCH 3344/3902] bpf, sockmap: Fix incorrect copied_seq calculation [ Upstream commit b40cc5adaa80e1471095a62d78233b611d7a558c ] A socket using sockmap has its own independent receive queue: ingress_msg. This queue may contain data from its own protocol stack or from other sockets. The issue is that when reading from ingress_msg, we update tp->copied_seq by default. However, if the data is not from its own protocol stack, tcp->rcv_nxt is not increased. Later, if we convert this socket to a native socket, reading from this socket may fail because copied_seq might be significantly larger than rcv_nxt. This fix also addresses the syzkaller-reported bug referenced in the Closes tag. This patch marks the skmsg objects in ingress_msg. When reading, we update copied_seq only if the data is from its own protocol stack. FD1:read() -- FD1->copied_seq++ | [read data] | [enqueue data] v [sockmap] -> ingress to self -> ingress_msg queue FD1 native stack ------> ^ -- FD1->rcv_nxt++ -> redirect to other | [enqueue data] | | | ingress to FD1 v ^ ... | [sockmap] FD2 native stack Closes: https://syzkaller.appspot.com/bug?extid=06dbd397158ec0ea4983 Fixes: 04919bed948dc ("tcp: Introduce tcp_read_skb()") Reviewed-by: Jakub Sitnicki Reviewed-by: John Fastabend Signed-off-by: Jiayuan Chen Link: https://lore.kernel.org/r/20260124113314.113584-2-jiayuan.chen@linux.dev Signed-off-by: Alexei Starovoitov Signed-off-by: Sasha Levin --- include/linux/skmsg.h | 2 ++ net/core/skmsg.c | 27 ++++++++++++++++++++++++--- net/ipv4/tcp_bpf.c | 5 +++-- 3 files changed, 29 insertions(+), 5 deletions(-) diff --git a/include/linux/skmsg.h b/include/linux/skmsg.h index 49847888c287ab..dfdc158ab88c8e 100644 --- a/include/linux/skmsg.h +++ b/include/linux/skmsg.h @@ -141,6 +141,8 @@ int sk_msg_memcopy_from_iter(struct sock *sk, struct iov_iter *from, struct sk_msg *msg, u32 bytes); int sk_msg_recvmsg(struct sock *sk, struct sk_psock *psock, struct msghdr *msg, int len, int flags); +int __sk_msg_recvmsg(struct sock *sk, struct sk_psock *psock, struct msghdr *msg, + int len, int flags, int *copied_from_self); bool sk_msg_is_readable(struct sock *sk); static inline void sk_msg_check_to_free(struct sk_msg *msg, u32 i, u32 bytes) diff --git a/net/core/skmsg.c b/net/core/skmsg.c index 2ac7731e1e0a74..d402da5caadd65 100644 --- a/net/core/skmsg.c +++ b/net/core/skmsg.c @@ -409,22 +409,26 @@ int sk_msg_memcopy_from_iter(struct sock *sk, struct iov_iter *from, } EXPORT_SYMBOL_GPL(sk_msg_memcopy_from_iter); -/* Receive sk_msg from psock->ingress_msg to @msg. */ -int sk_msg_recvmsg(struct sock *sk, struct sk_psock *psock, struct msghdr *msg, - int len, int flags) +int __sk_msg_recvmsg(struct sock *sk, struct sk_psock *psock, struct msghdr *msg, + int len, int flags, int *copied_from_self) { struct iov_iter *iter = &msg->msg_iter; int peek = flags & MSG_PEEK; struct sk_msg *msg_rx; int i, copied = 0; + bool from_self; msg_rx = sk_psock_peek_msg(psock); + if (copied_from_self) + *copied_from_self = 0; + while (copied != len) { struct scatterlist *sge; if (unlikely(!msg_rx)) break; + from_self = msg_rx->sk == sk; i = msg_rx->sg.start; do { struct page *page; @@ -443,6 +447,9 @@ int sk_msg_recvmsg(struct sock *sk, struct sk_psock *psock, struct msghdr *msg, } copied += copy; + if (from_self && copied_from_self) + *copied_from_self += copy; + if (likely(!peek)) { sge->offset += copy; sge->length -= copy; @@ -487,6 +494,13 @@ int sk_msg_recvmsg(struct sock *sk, struct sk_psock *psock, struct msghdr *msg, out: return copied; } + +/* Receive sk_msg from psock->ingress_msg to @msg. */ +int sk_msg_recvmsg(struct sock *sk, struct sk_psock *psock, struct msghdr *msg, + int len, int flags) +{ + return __sk_msg_recvmsg(sk, psock, msg, len, flags, NULL); +} EXPORT_SYMBOL_GPL(sk_msg_recvmsg); bool sk_msg_is_readable(struct sock *sk) @@ -616,6 +630,12 @@ static int sk_psock_skb_ingress_self(struct sk_psock *psock, struct sk_buff *skb if (unlikely(!msg)) return -EAGAIN; skb_set_owner_r(skb, sk); + + /* This is used in tcp_bpf_recvmsg_parser() to determine whether the + * data originates from the socket's own protocol stack. No need to + * refcount sk because msg's lifetime is bound to sk via the ingress_msg. + */ + msg->sk = sk; err = sk_psock_skb_ingress_enqueue(skb, off, len, psock, sk, msg, take_ref); if (err < 0) kfree(msg); @@ -909,6 +929,7 @@ int sk_psock_msg_verdict(struct sock *sk, struct sk_psock *psock, sk_msg_compute_data_pointers(msg); msg->sk = sk; ret = bpf_prog_run_pin_on_cpu(prog, msg); + msg->sk = NULL; ret = sk_psock_map_verd(ret, msg->sk_redir); psock->apply_bytes = msg->apply_bytes; if (ret == __SK_REDIRECT) { diff --git a/net/ipv4/tcp_bpf.c b/net/ipv4/tcp_bpf.c index a268e1595b22aa..5c698fd7fbf815 100644 --- a/net/ipv4/tcp_bpf.c +++ b/net/ipv4/tcp_bpf.c @@ -226,6 +226,7 @@ static int tcp_bpf_recvmsg_parser(struct sock *sk, int peek = flags & MSG_PEEK; struct sk_psock *psock; struct tcp_sock *tcp; + int copied_from_self = 0; int copied = 0; u32 seq; @@ -262,7 +263,7 @@ static int tcp_bpf_recvmsg_parser(struct sock *sk, } msg_bytes_ready: - copied = sk_msg_recvmsg(sk, psock, msg, len, flags); + copied = __sk_msg_recvmsg(sk, psock, msg, len, flags, &copied_from_self); /* The typical case for EFAULT is the socket was gracefully * shutdown with a FIN pkt. So check here the other case is * some error on copy_page_to_iter which would be unexpected. @@ -277,7 +278,7 @@ static int tcp_bpf_recvmsg_parser(struct sock *sk, goto out; } } - seq += copied; + seq += copied_from_self; if (!copied) { long timeo; int data; From 4b8d1424b32c4e01b8d6f48d748f550dc230a254 Mon Sep 17 00:00:00 2001 From: Jiayuan Chen Date: Sat, 24 Jan 2026 19:32:44 +0800 Subject: [PATCH 3345/3902] bpf, sockmap: Fix FIONREAD for sockmap [ Upstream commit 929e30f9312514902133c45e51c79088421ab084 ] A socket using sockmap has its own independent receive queue: ingress_msg. This queue may contain data from its own protocol stack or from other sockets. Therefore, for sockmap, relying solely on copied_seq and rcv_nxt to calculate FIONREAD is not enough. This patch adds a new msg_tot_len field in the psock structure to record the data length in ingress_msg. Additionally, we implement new ioctl interfaces for TCP and UDP to intercept FIONREAD operations. Note that we intentionally do not include sk_receive_queue data in the FIONREAD result. Data in sk_receive_queue has not yet been processed by the BPF verdict program, and may be redirected to other sockets or dropped. Including it would create semantic ambiguity since this data may never be readable by the user. Unix and VSOCK sockets have similar issues, but fixing them is outside the scope of this patch as it would require more intrusive changes. Previous work by John Fastabend made some efforts towards FIONREAD support: commit e5c6de5fa025 ("bpf, sockmap: Incorrectly handling copied_seq") Although the current patch is based on the previous work by John Fastabend, it is acceptable for our Fixes tag to point to the same commit. FD1:read() -- FD1->copied_seq++ | [read data] | [enqueue data] v [sockmap] -> ingress to self -> ingress_msg queue FD1 native stack ------> ^ -- FD1->rcv_nxt++ -> redirect to other | [enqueue data] | | | ingress to FD1 v ^ ... | [sockmap] FD2 native stack Fixes: 04919bed948dc ("tcp: Introduce tcp_read_skb()") Signed-off-by: Jiayuan Chen Reviewed-by: Jakub Sitnicki Link: https://lore.kernel.org/r/20260124113314.113584-3-jiayuan.chen@linux.dev Signed-off-by: Alexei Starovoitov Signed-off-by: Sasha Levin --- include/linux/skmsg.h | 68 +++++++++++++++++++++++++++++++++++++++++-- net/core/skmsg.c | 3 ++ net/ipv4/tcp_bpf.c | 20 +++++++++++++ net/ipv4/udp_bpf.c | 23 ++++++++++++--- 4 files changed, 108 insertions(+), 6 deletions(-) diff --git a/include/linux/skmsg.h b/include/linux/skmsg.h index dfdc158ab88c8e..829b281d6c9c27 100644 --- a/include/linux/skmsg.h +++ b/include/linux/skmsg.h @@ -97,6 +97,8 @@ struct sk_psock { struct sk_buff_head ingress_skb; struct list_head ingress_msg; spinlock_t ingress_lock; + /** @msg_tot_len: Total bytes queued in ingress_msg list. */ + u32 msg_tot_len; unsigned long state; struct list_head link; spinlock_t link_lock; @@ -321,6 +323,27 @@ static inline void sock_drop(struct sock *sk, struct sk_buff *skb) kfree_skb(skb); } +static inline u32 sk_psock_get_msg_len_nolock(struct sk_psock *psock) +{ + /* Used by ioctl to read msg_tot_len only; lock-free for performance */ + return READ_ONCE(psock->msg_tot_len); +} + +static inline void sk_psock_msg_len_add_locked(struct sk_psock *psock, int diff) +{ + /* Use WRITE_ONCE to ensure correct read in sk_psock_get_msg_len_nolock(). + * ingress_lock should be held to prevent concurrent updates to msg_tot_len + */ + WRITE_ONCE(psock->msg_tot_len, psock->msg_tot_len + diff); +} + +static inline void sk_psock_msg_len_add(struct sk_psock *psock, int diff) +{ + spin_lock_bh(&psock->ingress_lock); + sk_psock_msg_len_add_locked(psock, diff); + spin_unlock_bh(&psock->ingress_lock); +} + static inline bool sk_psock_queue_msg(struct sk_psock *psock, struct sk_msg *msg) { @@ -329,6 +352,7 @@ static inline bool sk_psock_queue_msg(struct sk_psock *psock, spin_lock_bh(&psock->ingress_lock); if (sk_psock_test_state(psock, SK_PSOCK_TX_ENABLED)) { list_add_tail(&msg->list, &psock->ingress_msg); + sk_psock_msg_len_add_locked(psock, msg->sg.size); ret = true; } else { sk_msg_free(psock->sk, msg); @@ -345,18 +369,25 @@ static inline struct sk_msg *sk_psock_dequeue_msg(struct sk_psock *psock) spin_lock_bh(&psock->ingress_lock); msg = list_first_entry_or_null(&psock->ingress_msg, struct sk_msg, list); - if (msg) + if (msg) { list_del(&msg->list); + sk_psock_msg_len_add_locked(psock, -msg->sg.size); + } spin_unlock_bh(&psock->ingress_lock); return msg; } +static inline struct sk_msg *sk_psock_peek_msg_locked(struct sk_psock *psock) +{ + return list_first_entry_or_null(&psock->ingress_msg, struct sk_msg, list); +} + static inline struct sk_msg *sk_psock_peek_msg(struct sk_psock *psock) { struct sk_msg *msg; spin_lock_bh(&psock->ingress_lock); - msg = list_first_entry_or_null(&psock->ingress_msg, struct sk_msg, list); + msg = sk_psock_peek_msg_locked(psock); spin_unlock_bh(&psock->ingress_lock); return msg; } @@ -523,6 +554,39 @@ static inline bool sk_psock_strp_enabled(struct sk_psock *psock) return !!psock->saved_data_ready; } +/* for tcp only, sk is locked */ +static inline ssize_t sk_psock_msg_inq(struct sock *sk) +{ + struct sk_psock *psock; + ssize_t inq = 0; + + psock = sk_psock_get(sk); + if (likely(psock)) { + inq = sk_psock_get_msg_len_nolock(psock); + sk_psock_put(sk, psock); + } + return inq; +} + +/* for udp only, sk is not locked */ +static inline ssize_t sk_msg_first_len(struct sock *sk) +{ + struct sk_psock *psock; + struct sk_msg *msg; + ssize_t inq = 0; + + psock = sk_psock_get(sk); + if (likely(psock)) { + spin_lock_bh(&psock->ingress_lock); + msg = sk_psock_peek_msg_locked(psock); + if (msg) + inq = msg->sg.size; + spin_unlock_bh(&psock->ingress_lock); + sk_psock_put(sk, psock); + } + return inq; +} + #if IS_ENABLED(CONFIG_NET_SOCK_MSG) #define BPF_F_STRPARSER (1UL << 1) diff --git a/net/core/skmsg.c b/net/core/skmsg.c index d402da5caadd65..ddde93dd8bc6d4 100644 --- a/net/core/skmsg.c +++ b/net/core/skmsg.c @@ -458,6 +458,7 @@ int __sk_msg_recvmsg(struct sock *sk, struct sk_psock *psock, struct msghdr *msg atomic_sub(copy, &sk->sk_rmem_alloc); } msg_rx->sg.size -= copy; + sk_psock_msg_len_add(psock, -copy); if (!sge->length) { sk_msg_iter_var_next(i); @@ -821,9 +822,11 @@ static void __sk_psock_purge_ingress_msg(struct sk_psock *psock) list_del(&msg->list); if (!msg->skb) atomic_sub(msg->sg.size, &psock->sk->sk_rmem_alloc); + sk_psock_msg_len_add(psock, -msg->sg.size); sk_msg_free(psock->sk, msg); kfree(msg); } + WARN_ON_ONCE(psock->msg_tot_len); } static void __sk_psock_zap_ingress(struct sk_psock *psock) diff --git a/net/ipv4/tcp_bpf.c b/net/ipv4/tcp_bpf.c index 5c698fd7fbf815..ca8a5cb8e569d7 100644 --- a/net/ipv4/tcp_bpf.c +++ b/net/ipv4/tcp_bpf.c @@ -10,6 +10,7 @@ #include #include +#include void tcp_eat_skb(struct sock *sk, struct sk_buff *skb) { @@ -332,6 +333,24 @@ static int tcp_bpf_recvmsg_parser(struct sock *sk, return copied; } +static int tcp_bpf_ioctl(struct sock *sk, int cmd, int *karg) +{ + bool slow; + + if (cmd != SIOCINQ) + return tcp_ioctl(sk, cmd, karg); + + /* works similar as tcp_ioctl */ + if (sk->sk_state == TCP_LISTEN) + return -EINVAL; + + slow = lock_sock_fast(sk); + *karg = sk_psock_msg_inq(sk); + unlock_sock_fast(sk, slow); + + return 0; +} + static int tcp_bpf_recvmsg(struct sock *sk, struct msghdr *msg, size_t len, int flags, int *addr_len) { @@ -610,6 +629,7 @@ static void tcp_bpf_rebuild_protos(struct proto prot[TCP_BPF_NUM_CFGS], prot[TCP_BPF_BASE].close = sock_map_close; prot[TCP_BPF_BASE].recvmsg = tcp_bpf_recvmsg; prot[TCP_BPF_BASE].sock_is_readable = sk_msg_is_readable; + prot[TCP_BPF_BASE].ioctl = tcp_bpf_ioctl; prot[TCP_BPF_TX] = prot[TCP_BPF_BASE]; prot[TCP_BPF_TX].sendmsg = tcp_bpf_sendmsg; diff --git a/net/ipv4/udp_bpf.c b/net/ipv4/udp_bpf.c index 0735d820e413f3..91233e37cd97a2 100644 --- a/net/ipv4/udp_bpf.c +++ b/net/ipv4/udp_bpf.c @@ -5,6 +5,7 @@ #include #include #include +#include #include "udp_impl.h" @@ -111,12 +112,26 @@ enum { static DEFINE_SPINLOCK(udpv6_prot_lock); static struct proto udp_bpf_prots[UDP_BPF_NUM_PROTS]; +static int udp_bpf_ioctl(struct sock *sk, int cmd, int *karg) +{ + if (cmd != SIOCINQ) + return udp_ioctl(sk, cmd, karg); + + /* Since we don't hold a lock, sk_receive_queue may contain data. + * BPF might only be processing this data at the moment. We only + * care about the data in the ingress_msg here. + */ + *karg = sk_msg_first_len(sk); + return 0; +} + static void udp_bpf_rebuild_protos(struct proto *prot, const struct proto *base) { - *prot = *base; - prot->close = sock_map_close; - prot->recvmsg = udp_bpf_recvmsg; - prot->sock_is_readable = sk_msg_is_readable; + *prot = *base; + prot->close = sock_map_close; + prot->recvmsg = udp_bpf_recvmsg; + prot->sock_is_readable = sk_msg_is_readable; + prot->ioctl = udp_bpf_ioctl; } static void udp_bpf_check_v6_needs_rebuild(struct proto *ops) From 4e0772cded109c238411f2fac36ac39302758b81 Mon Sep 17 00:00:00 2001 From: Guillaume Gonnet Date: Tue, 27 Jan 2026 17:02:00 +0100 Subject: [PATCH 3346/3902] bpf: Fix tcx/netkit detach permissions when prog fd isn't given [ Upstream commit ae23bc81ddf7c17b663c4ed1b21e35527b0a7131 ] This commit fixes a security issue where BPF_PROG_DETACH on tcx or netkit devices could be executed by any user when no program fd was provided, bypassing permission checks. The fix adds a capability check for CAP_NET_ADMIN or CAP_SYS_ADMIN in this case. Fixes: e420bed02507 ("bpf: Add fd-based tcx multi-prog infra with link support") Signed-off-by: Guillaume Gonnet Link: https://lore.kernel.org/r/20260127160200.10395-1-ggonnet.linux@gmail.com Signed-off-by: Alexei Starovoitov Signed-off-by: Sasha Levin --- include/linux/bpf.h | 5 +++++ include/linux/bpf_mprog.h | 10 ++++++++++ kernel/bpf/syscall.c | 7 ++----- 3 files changed, 17 insertions(+), 5 deletions(-) diff --git a/include/linux/bpf.h b/include/linux/bpf.h index d808253f2e945d..e2dd3a6d495afd 100644 --- a/include/linux/bpf.h +++ b/include/linux/bpf.h @@ -3200,6 +3200,11 @@ static inline void bpf_prog_report_arena_violation(bool write, unsigned long add } #endif /* CONFIG_BPF_SYSCALL */ +static inline bool bpf_net_capable(void) +{ + return capable(CAP_NET_ADMIN) || capable(CAP_SYS_ADMIN); +} + static __always_inline int bpf_probe_read_kernel_common(void *dst, u32 size, const void *unsafe_ptr) { diff --git a/include/linux/bpf_mprog.h b/include/linux/bpf_mprog.h index 929225f7b09594..0b9f4caeeb0a32 100644 --- a/include/linux/bpf_mprog.h +++ b/include/linux/bpf_mprog.h @@ -340,4 +340,14 @@ static inline bool bpf_mprog_supported(enum bpf_prog_type type) return false; } } + +static inline bool bpf_mprog_detach_empty(enum bpf_prog_type type) +{ + switch (type) { + case BPF_PROG_TYPE_SCHED_CLS: + return bpf_net_capable(); + default: + return false; + } +} #endif /* __BPF_MPROG_H */ diff --git a/kernel/bpf/syscall.c b/kernel/bpf/syscall.c index e9cf69594824c2..f39367765f0c4f 100644 --- a/kernel/bpf/syscall.c +++ b/kernel/bpf/syscall.c @@ -1365,11 +1365,6 @@ static int map_check_btf(struct bpf_map *map, struct bpf_token *token, return ret; } -static bool bpf_net_capable(void) -{ - return capable(CAP_NET_ADMIN) || capable(CAP_SYS_ADMIN); -} - #define BPF_MAP_CREATE_LAST_FIELD excl_prog_hash_size /* called via syscall */ static int map_create(union bpf_attr *attr, bpfptr_t uattr) @@ -4554,6 +4549,8 @@ static int bpf_prog_detach(const union bpf_attr *attr) prog = bpf_prog_get_type(attr->attach_bpf_fd, ptype); if (IS_ERR(prog)) return PTR_ERR(prog); + } else if (!bpf_mprog_detach_empty(ptype)) { + return -EPERM; } } else if (is_cgroup_prog_type(ptype, 0, false)) { if (attr->attach_flags || attr->relative_fd) From 4ecbcfb71a50c401c5eeaa41d72c9edf9af08518 Mon Sep 17 00:00:00 2001 From: Luis Gerhorst Date: Tue, 27 Jan 2026 12:59:11 +0100 Subject: [PATCH 3347/3902] bpf: Fix verifier_bug_if to account for BPF_CALL MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [ Upstream commit cd3b6a3d49f8061d0c4c7e4226783051fe592ae7 ] The BPF verifier assumes `insn_aux->nospec_result` is only set for direct memory writes (e.g., `*(u32*)(r1+off) = r2`). However, the assertion fails to account for helper calls (e.g., `bpf_skb_load_bytes_relative`) that perform writes to stack memory. Make the check more precise to resolve this. The problem is that `BPF_CALL` instructions have `BPF_CLASS(insn->code) == BPF_JMP`, which triggers the warning check: - Helpers like `bpf_skb_load_bytes_relative` write to stack memory - `check_helper_call()` loops through `meta.access_size`, calling `check_mem_access(..., BPF_WRITE)` - `check_stack_write()` sets `insn_aux->nospec_result = 1` - Since `BPF_CALL` is encoded as `BPF_JMP | BPF_CALL`, the warning fires Execution flow: ``` 1. Drop capabilities → Enable Spectre mitigation 2. Load BPF program └─> do_check() ├─> check_cond_jmp_op() → Marks dead branch as speculative │ └─> push_stack(..., speculative=true) ├─> pop_stack() → state->speculative = 1 ├─> check_helper_call() → Processes helper in dead branch │ └─> check_mem_access(..., BPF_WRITE) │ └─> insn_aux->nospec_result = 1 └─> Checks: state->speculative && insn_aux->nospec_result └─> BPF_CLASS(insn->code) == BPF_JMP → WARNING ``` To fix the assert, it would be nice to be able to reuse bpf_insn_successors() here, but bpf_insn_successors()->cnt is not exactly what we want as it may also be 1 for BPF_JA. Instead, we could check opcode_info.can_jump, but then we would have to share the table between the functions. This would mean moving the table out of the function and adding bpf_opcode_info(). As the verifier_bug_if() only runs for insns with nospec_result set, the impact on verification time would likely still be negligible. However, I assume sharing bpf_opcode_info() between liveness.c and verifier.c will not be worth it. It seems as only adjust_jmp_off() could also be simplified using it, and there imm/off is touched. Thus it is maybe better to rely on exact opcode/class matching there. Therefore, to avoid this sharing only for a verifier_bug_if(), just check the opcode. This should now cover all opcodes for which can_jump in bpf_insn_successors() is true. Parts of the description and example are taken from the bug report. Fixes: dadb59104c64 ("bpf: Fix aux usage after do_check_insn()") Signed-off-by: Luis Gerhorst Reported-by: Yinhao Hu Reported-by: Kaiyan Mei Reported-by: Dongliang Mu Closes: https://lore.kernel.org/bpf/7678017d-b760-4053-a2d8-a6879b0dbeeb@hust.edu.cn/ Link: https://lore.kernel.org/r/20260127115912.3026761-2-luis.gerhorst@fau.de Signed-off-by: Alexei Starovoitov Signed-off-by: Sasha Levin --- kernel/bpf/verifier.c | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/kernel/bpf/verifier.c b/kernel/bpf/verifier.c index 14546d1bdb52c6..4338d233beecfa 100644 --- a/kernel/bpf/verifier.c +++ b/kernel/bpf/verifier.c @@ -20149,17 +20149,19 @@ static int do_check(struct bpf_verifier_env *env) * may skip a nospec patched-in after the jump. This can * currently never happen because nospec_result is only * used for the write-ops - * `*(size*)(dst_reg+off)=src_reg|imm32` which must - * never skip the following insn. Still, add a warning - * to document this in case nospec_result is used - * elsewhere in the future. + * `*(size*)(dst_reg+off)=src_reg|imm32` and helper + * calls. These must never skip the following insn + * (i.e., bpf_insn_successors()'s opcode_info.can_jump + * is false). Still, add a warning to document this in + * case nospec_result is used elsewhere in the future. * * All non-branch instructions have a single * fall-through edge. For these, nospec_result should * already work. */ - if (verifier_bug_if(BPF_CLASS(insn->code) == BPF_JMP || - BPF_CLASS(insn->code) == BPF_JMP32, env, + if (verifier_bug_if((BPF_CLASS(insn->code) == BPF_JMP || + BPF_CLASS(insn->code) == BPF_JMP32) && + BPF_OP(insn->code) != BPF_CALL, env, "speculation barrier after jump instruction may not have the desired effect")) return -EFAULT; process_bpf_exit: From 9a3ace9b010ffd8c422c97844ae152f7c53d6b18 Mon Sep 17 00:00:00 2001 From: Ella Ma Date: Fri, 9 Jan 2026 16:17:24 +0100 Subject: [PATCH 3348/3902] crypto: ccp - Fix a crash due to incorrect cleanup usage of kfree [ Upstream commit d5abcc33ee76bc26d58b39dc1a097e43a99dd438 ] Annotating a local pointer variable, which will be assigned with the kmalloc-family functions, with the `__cleanup(kfree)` attribute will make the address of the local variable, rather than the address returned by kmalloc, passed to kfree directly and lead to a crash due to invalid deallocation of stack address. According to other places in the repo, the correct usage should be `__free(kfree)`. The code coincidentally compiled because the parameter type `void *` of kfree is compatible with the desired type `struct { ... } **`. Fixes: a71475582ada ("crypto: ccp - reduce stack usage in ccp_run_aes_gcm_cmd") Signed-off-by: Ella Ma Acked-by: Tom Lendacky Signed-off-by: Herbert Xu Signed-off-by: Sasha Levin --- drivers/crypto/ccp/ccp-ops.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/crypto/ccp/ccp-ops.c b/drivers/crypto/ccp/ccp-ops.c index d78865d9d5f09c..d0412e58476253 100644 --- a/drivers/crypto/ccp/ccp-ops.c +++ b/drivers/crypto/ccp/ccp-ops.c @@ -642,7 +642,7 @@ ccp_run_aes_gcm_cmd(struct ccp_cmd_queue *cmd_q, struct ccp_cmd *cmd) struct ccp_data dst; struct ccp_data aad; struct ccp_op op; - } *wa __cleanup(kfree) = kzalloc(sizeof *wa, GFP_KERNEL); + } *wa __free(kfree) = kzalloc(sizeof(*wa), GFP_KERNEL); unsigned int dm_offset; unsigned int authsize; unsigned int jobid; From 243d642ff5809811208fa1707b7ab8a6ab4b1d68 Mon Sep 17 00:00:00 2001 From: Aleksander Jan Bajkowski Date: Sun, 11 Jan 2026 14:20:32 +0100 Subject: [PATCH 3349/3902] crypto: inside-secure/eip93 - unregister only available algorithm [ Upstream commit 0ceeadc7b53a041d89d5843f6bf0ccb7c98b0b4f ] EIP93 has an options register. This register indicates which crypto algorithms are implemented in silicon. Supported algorithms are registered on this basis. Unregister algorithms on the same basis. Currently, all algorithms are unregistered, even those not supported by HW. This results in panic on platforms that don't have all options implemented in silicon. Fixes: 9739f5f93b78 ("crypto: eip93 - Add Inside Secure SafeXcel EIP-93 crypto engine support") Signed-off-by: Aleksander Jan Bajkowski Acked-by: Antoine Tenart Signed-off-by: Herbert Xu Signed-off-by: Sasha Levin --- .../crypto/inside-secure/eip93/eip93-main.c | 92 +++++++++++-------- 1 file changed, 53 insertions(+), 39 deletions(-) diff --git a/drivers/crypto/inside-secure/eip93/eip93-main.c b/drivers/crypto/inside-secure/eip93/eip93-main.c index 3cdc3308dcac86..b7fd9795062d4c 100644 --- a/drivers/crypto/inside-secure/eip93/eip93-main.c +++ b/drivers/crypto/inside-secure/eip93/eip93-main.c @@ -77,11 +77,44 @@ inline void eip93_irq_clear(struct eip93_device *eip93, u32 mask) __raw_writel(mask, eip93->base + EIP93_REG_INT_CLR); } -static void eip93_unregister_algs(unsigned int i) +static int eip93_algo_is_supported(u32 alg_flags, u32 supported_algo_flags) +{ + if ((IS_DES(alg_flags) || IS_3DES(alg_flags)) && + !(supported_algo_flags & EIP93_PE_OPTION_TDES)) + return 0; + + if (IS_AES(alg_flags) && + !(supported_algo_flags & EIP93_PE_OPTION_AES)) + return 0; + + if (IS_HASH_MD5(alg_flags) && + !(supported_algo_flags & EIP93_PE_OPTION_MD5)) + return 0; + + if (IS_HASH_SHA1(alg_flags) && + !(supported_algo_flags & EIP93_PE_OPTION_SHA_1)) + return 0; + + if (IS_HASH_SHA224(alg_flags) && + !(supported_algo_flags & EIP93_PE_OPTION_SHA_224)) + return 0; + + if (IS_HASH_SHA256(alg_flags) && + !(supported_algo_flags & EIP93_PE_OPTION_SHA_256)) + return 0; + + return 1; +} + +static void eip93_unregister_algs(u32 supported_algo_flags, unsigned int i) { unsigned int j; for (j = 0; j < i; j++) { + if (!eip93_algo_is_supported(eip93_algs[j]->flags, + supported_algo_flags)) + continue; + switch (eip93_algs[j]->type) { case EIP93_ALG_TYPE_SKCIPHER: crypto_unregister_skcipher(&eip93_algs[j]->alg.skcipher); @@ -106,49 +139,27 @@ static int eip93_register_algs(struct eip93_device *eip93, u32 supported_algo_fl eip93_algs[i]->eip93 = eip93; - if ((IS_DES(alg_flags) || IS_3DES(alg_flags)) && - !(supported_algo_flags & EIP93_PE_OPTION_TDES)) + if (!eip93_algo_is_supported(alg_flags, supported_algo_flags)) continue; - if (IS_AES(alg_flags)) { - if (!(supported_algo_flags & EIP93_PE_OPTION_AES)) - continue; + if (IS_AES(alg_flags) && !IS_HMAC(alg_flags)) { + if (supported_algo_flags & EIP93_PE_OPTION_AES_KEY128) + eip93_algs[i]->alg.skcipher.max_keysize = + AES_KEYSIZE_128; - if (!IS_HMAC(alg_flags)) { - if (supported_algo_flags & EIP93_PE_OPTION_AES_KEY128) - eip93_algs[i]->alg.skcipher.max_keysize = - AES_KEYSIZE_128; + if (supported_algo_flags & EIP93_PE_OPTION_AES_KEY192) + eip93_algs[i]->alg.skcipher.max_keysize = + AES_KEYSIZE_192; - if (supported_algo_flags & EIP93_PE_OPTION_AES_KEY192) - eip93_algs[i]->alg.skcipher.max_keysize = - AES_KEYSIZE_192; + if (supported_algo_flags & EIP93_PE_OPTION_AES_KEY256) + eip93_algs[i]->alg.skcipher.max_keysize = + AES_KEYSIZE_256; - if (supported_algo_flags & EIP93_PE_OPTION_AES_KEY256) - eip93_algs[i]->alg.skcipher.max_keysize = - AES_KEYSIZE_256; - - if (IS_RFC3686(alg_flags)) - eip93_algs[i]->alg.skcipher.max_keysize += - CTR_RFC3686_NONCE_SIZE; - } + if (IS_RFC3686(alg_flags)) + eip93_algs[i]->alg.skcipher.max_keysize += + CTR_RFC3686_NONCE_SIZE; } - if (IS_HASH_MD5(alg_flags) && - !(supported_algo_flags & EIP93_PE_OPTION_MD5)) - continue; - - if (IS_HASH_SHA1(alg_flags) && - !(supported_algo_flags & EIP93_PE_OPTION_SHA_1)) - continue; - - if (IS_HASH_SHA224(alg_flags) && - !(supported_algo_flags & EIP93_PE_OPTION_SHA_224)) - continue; - - if (IS_HASH_SHA256(alg_flags) && - !(supported_algo_flags & EIP93_PE_OPTION_SHA_256)) - continue; - switch (eip93_algs[i]->type) { case EIP93_ALG_TYPE_SKCIPHER: ret = crypto_register_skcipher(&eip93_algs[i]->alg.skcipher); @@ -167,7 +178,7 @@ static int eip93_register_algs(struct eip93_device *eip93, u32 supported_algo_fl return 0; fail: - eip93_unregister_algs(i); + eip93_unregister_algs(supported_algo_flags, i); return ret; } @@ -469,8 +480,11 @@ static int eip93_crypto_probe(struct platform_device *pdev) static void eip93_crypto_remove(struct platform_device *pdev) { struct eip93_device *eip93 = platform_get_drvdata(pdev); + u32 algo_flags; + + algo_flags = readl(eip93->base + EIP93_REG_PE_OPTION_1); - eip93_unregister_algs(ARRAY_SIZE(eip93_algs)); + eip93_unregister_algs(algo_flags, ARRAY_SIZE(eip93_algs)); eip93_cleanup(eip93); } From 9e10486f79936ff8f16b79e48c52e506d62b68d2 Mon Sep 17 00:00:00 2001 From: Jiri Olsa Date: Mon, 26 Jan 2026 22:18:32 +0100 Subject: [PATCH 3350/3902] x86/fgraph: Fix return_to_handler regs.rsp value [ Upstream commit 8bc11700e0d23d4fdb7d8d5a73b2e95de427cabc ] The previous change (Fixes commit) messed up the rsp register value, which is wrong because it's already adjusted with FRAME_SIZE, we need the original rsp value. This change does not affect fprobe current kernel unwind, the !perf_hw_regs path perf_callchain_kernel: if (perf_hw_regs(regs)) { if (perf_callchain_store(entry, regs->ip)) return; unwind_start(&state, current, regs, NULL); } else { unwind_start(&state, current, NULL, (void *)regs->sp); } which uses pt_regs.sp as first_frame boundary (FRAME_SIZE shift makes no difference, unwind stil stops at the right frame). This change fixes the other path when we want to unwind directly from pt_regs sp/fp/ip state, which is coming in following change. Fixes: 20a0bc10272f ("x86/fgraph,bpf: Fix stack ORC unwind from kprobe_multi return probe") Signed-off-by: Jiri Olsa Signed-off-by: Andrii Nakryiko Reviewed-by: Steven Rostedt (Google) Link: https://lore.kernel.org/bpf/20260126211837.472802-2-jolsa@kernel.org Signed-off-by: Sasha Levin --- arch/x86/kernel/ftrace_64.S | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/arch/x86/kernel/ftrace_64.S b/arch/x86/kernel/ftrace_64.S index 823dbdd0eb4109..ae94f7b0fdf16f 100644 --- a/arch/x86/kernel/ftrace_64.S +++ b/arch/x86/kernel/ftrace_64.S @@ -354,6 +354,9 @@ SYM_CODE_START(return_to_handler) UNWIND_HINT_UNDEFINED ANNOTATE_NOENDBR + /* Store original rsp for pt_regs.sp value. */ + movq %rsp, %rdi + /* Restore return_to_handler value that got eaten by previous ret instruction. */ subq $8, %rsp UNWIND_HINT_FUNC @@ -364,7 +367,7 @@ SYM_CODE_START(return_to_handler) movq %rax, RAX(%rsp) movq %rdx, RDX(%rsp) movq %rbp, RBP(%rsp) - movq %rsp, RSP(%rsp) + movq %rdi, RSP(%rsp) movq %rsp, %rdi call ftrace_return_to_handler From 42a155c738b9a18c9b2fec23f29236fefd5009e1 Mon Sep 17 00:00:00 2001 From: Jiri Olsa Date: Mon, 26 Jan 2026 22:18:33 +0100 Subject: [PATCH 3351/3902] x86/fgraph,bpf: Switch kprobe_multi program stack unwind to hw_regs path MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [ Upstream commit aea251799998aa1b78eacdfb308f18ea114ea5b3 ] Mahe reported missing function from stack trace on top of kprobe multi program. The missing function is the very first one in the stacktrace, the one that the bpf program is attached to. # bpftrace -e 'kprobe:__x64_sys_newuname* { print(kstack)}' Attaching 1 probe... do_syscall_64+134 entry_SYSCALL_64_after_hwframe+118 ('*' is used for kprobe_multi attachment) The reason is that the previous change (the Fixes commit) fixed stack unwind for tracepoint, but removed attached function address from the stack trace on top of kprobe multi programs, which I also overlooked in the related test (check following patch). The tracepoint and kprobe_multi have different stack setup, but use same unwind path. I think it's better to keep the previous change, which fixed tracepoint unwind and instead change the kprobe multi unwind as explained below. The bpf program stack unwind calls perf_callchain_kernel for kernel portion and it follows two unwind paths based on X86_EFLAGS_FIXED bit in pt_regs.flags. When the bit set we unwind from stack represented by pt_regs argument, otherwise we unwind currently executed stack up to 'first_frame' boundary. The 'first_frame' value is taken from regs.rsp value, but ftrace_caller and ftrace_regs_caller (ftrace trampoline) functions set the regs.rsp to the previous stack frame, so we skip the attached function entry. If we switch kprobe_multi unwind to use the X86_EFLAGS_FIXED bit, we set the start of the unwind to the attached function address. As another benefit we also cut extra unwind cycles needed to reach the 'first_frame' boundary. The speedup can be measured with trigger bench for kprobe_multi program and stacktrace support. - trigger bench with stacktrace on current code: kprobe-multi : 0.810 ± 0.001M/s kretprobe-multi: 0.808 ± 0.001M/s - and with the fix: kprobe-multi : 1.264 ± 0.001M/s kretprobe-multi: 1.401 ± 0.002M/s With the fix, the entry probe stacktrace: # bpftrace -e 'kprobe:__x64_sys_newuname* { print(kstack)}' Attaching 1 probe... __x64_sys_newuname+9 do_syscall_64+134 entry_SYSCALL_64_after_hwframe+118 The return probe skips the attached function, because it's no longer on the stack at the point of the unwind and this way is the same how standard kretprobe works. # bpftrace -e 'kretprobe:__x64_sys_newuname* { print(kstack)}' Attaching 1 probe... do_syscall_64+134 entry_SYSCALL_64_after_hwframe+118 Fixes: 6d08340d1e35 ("Revert "perf/x86: Always store regs->ip in perf_callchain_kernel()"") Reported-by: Mahe Tardy Signed-off-by: Jiri Olsa Signed-off-by: Andrii Nakryiko Acked-by: Steven Rostedt (Google) Link: https://lore.kernel.org/bpf/20260126211837.472802-3-jolsa@kernel.org Signed-off-by: Sasha Levin --- arch/x86/include/asm/ftrace.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/arch/x86/include/asm/ftrace.h b/arch/x86/include/asm/ftrace.h index b08c95872eed9f..c56e1e63b89324 100644 --- a/arch/x86/include/asm/ftrace.h +++ b/arch/x86/include/asm/ftrace.h @@ -57,7 +57,7 @@ arch_ftrace_get_regs(struct ftrace_regs *fregs) } #define arch_ftrace_partial_regs(regs) do { \ - regs->flags &= ~X86_EFLAGS_FIXED; \ + regs->flags |= X86_EFLAGS_FIXED; \ regs->cs = __KERNEL_CS; \ } while (0) From 94fdeeaeb7cef9cf99b46b10f148ca9ae195b9e7 Mon Sep 17 00:00:00 2001 From: Jiri Olsa Date: Mon, 26 Jan 2026 22:18:34 +0100 Subject: [PATCH 3352/3902] selftests/bpf: Fix kprobe multi stacktrace_ips test [ Upstream commit 0207f94971e72a13380e28022c86da147e8e090f ] We now include the attached function in the stack trace, fixing the test accordingly. Fixes: c9e208fa93cd ("selftests/bpf: Add stacktrace ips test for kprobe_multi/kretprobe_multi") Signed-off-by: Jiri Olsa Signed-off-by: Andrii Nakryiko Link: https://lore.kernel.org/bpf/20260126211837.472802-4-jolsa@kernel.org Signed-off-by: Sasha Levin --- .../selftests/bpf/prog_tests/stacktrace_ips.c | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/tools/testing/selftests/bpf/prog_tests/stacktrace_ips.c b/tools/testing/selftests/bpf/prog_tests/stacktrace_ips.c index c9efdd2a5b18a5..c93718dafd9b63 100644 --- a/tools/testing/selftests/bpf/prog_tests/stacktrace_ips.c +++ b/tools/testing/selftests/bpf/prog_tests/stacktrace_ips.c @@ -74,11 +74,20 @@ static void test_stacktrace_ips_kprobe_multi(bool retprobe) load_kallsyms(); - check_stacktrace_ips(bpf_map__fd(skel->maps.stackmap), skel->bss->stack_key, 4, - ksym_get_addr("bpf_testmod_stacktrace_test_3"), - ksym_get_addr("bpf_testmod_stacktrace_test_2"), - ksym_get_addr("bpf_testmod_stacktrace_test_1"), - ksym_get_addr("bpf_testmod_test_read")); + if (retprobe) { + check_stacktrace_ips(bpf_map__fd(skel->maps.stackmap), skel->bss->stack_key, 4, + ksym_get_addr("bpf_testmod_stacktrace_test_3"), + ksym_get_addr("bpf_testmod_stacktrace_test_2"), + ksym_get_addr("bpf_testmod_stacktrace_test_1"), + ksym_get_addr("bpf_testmod_test_read")); + } else { + check_stacktrace_ips(bpf_map__fd(skel->maps.stackmap), skel->bss->stack_key, 5, + ksym_get_addr("bpf_testmod_stacktrace_test"), + ksym_get_addr("bpf_testmod_stacktrace_test_3"), + ksym_get_addr("bpf_testmod_stacktrace_test_2"), + ksym_get_addr("bpf_testmod_stacktrace_test_1"), + ksym_get_addr("bpf_testmod_test_read")); + } cleanup: stacktrace_ips__destroy(skel); From d0bac6618e810c02def90966aadcf9eb78b18116 Mon Sep 17 00:00:00 2001 From: Weili Qian Date: Sat, 17 Jan 2026 15:18:21 +0800 Subject: [PATCH 3353/3902] crypto: hisilicon/trng - support tfms sharing the device [ Upstream commit 3d3135057ff567d5c09fff4c9ef6391a684e8042 ] Since the number of devices is limited, and the number of tfms may exceed the number of devices, to ensure that tfms can be successfully allocated, support tfms sharing the same device. Fixes: e4d9d10ef4be ("crypto: hisilicon/trng - add support for PRNG") Signed-off-by: Weili Qian Signed-off-by: Chenghai Huang Signed-off-by: Herbert Xu Signed-off-by: Sasha Levin --- drivers/crypto/hisilicon/trng/trng.c | 121 +++++++++++++++++++-------- 1 file changed, 86 insertions(+), 35 deletions(-) diff --git a/drivers/crypto/hisilicon/trng/trng.c b/drivers/crypto/hisilicon/trng/trng.c index ac74df4a947126..5ca0b90859a81e 100644 --- a/drivers/crypto/hisilicon/trng/trng.c +++ b/drivers/crypto/hisilicon/trng/trng.c @@ -40,6 +40,7 @@ #define SEED_SHIFT_24 24 #define SEED_SHIFT_16 16 #define SEED_SHIFT_8 8 +#define SW_MAX_RANDOM_BYTES 65520 struct hisi_trng_list { struct mutex lock; @@ -53,8 +54,10 @@ struct hisi_trng { struct list_head list; struct hwrng rng; u32 ver; - bool is_used; - struct mutex mutex; + u32 ctx_num; + /* The bytes of the random number generated since the last seeding. */ + u32 random_bytes; + struct mutex lock; }; struct hisi_trng_ctx { @@ -63,10 +66,14 @@ struct hisi_trng_ctx { static atomic_t trng_active_devs; static struct hisi_trng_list trng_devices; +static int hisi_trng_read(struct hwrng *rng, void *buf, size_t max, bool wait); -static void hisi_trng_set_seed(struct hisi_trng *trng, const u8 *seed) +static int hisi_trng_set_seed(struct hisi_trng *trng, const u8 *seed) { u32 val, seed_reg, i; + int ret; + + writel(0x0, trng->base + SW_DRBG_BLOCKS); for (i = 0; i < SW_DRBG_SEED_SIZE; i += SW_DRBG_SEED_SIZE / SW_DRBG_SEED_REGS_NUM) { @@ -78,6 +85,20 @@ static void hisi_trng_set_seed(struct hisi_trng *trng, const u8 *seed) seed_reg = (i >> SW_DRBG_NUM_SHIFT) % SW_DRBG_SEED_REGS_NUM; writel(val, trng->base + SW_DRBG_SEED(seed_reg)); } + + writel(SW_DRBG_BLOCKS_NUM | (0x1 << SW_DRBG_ENABLE_SHIFT), + trng->base + SW_DRBG_BLOCKS); + writel(0x1, trng->base + SW_DRBG_INIT); + ret = readl_relaxed_poll_timeout(trng->base + SW_DRBG_STATUS, + val, val & BIT(0), SLEEP_US, TIMEOUT_US); + if (ret) { + pr_err("failed to init trng(%d)\n", ret); + return -EIO; + } + + trng->random_bytes = 0; + + return 0; } static int hisi_trng_seed(struct crypto_rng *tfm, const u8 *seed, @@ -85,8 +106,7 @@ static int hisi_trng_seed(struct crypto_rng *tfm, const u8 *seed, { struct hisi_trng_ctx *ctx = crypto_rng_ctx(tfm); struct hisi_trng *trng = ctx->trng; - u32 val = 0; - int ret = 0; + int ret; if (slen < SW_DRBG_SEED_SIZE) { pr_err("slen(%u) is not matched with trng(%d)\n", slen, @@ -94,43 +114,45 @@ static int hisi_trng_seed(struct crypto_rng *tfm, const u8 *seed, return -EINVAL; } - writel(0x0, trng->base + SW_DRBG_BLOCKS); - hisi_trng_set_seed(trng, seed); + mutex_lock(&trng->lock); + ret = hisi_trng_set_seed(trng, seed); + mutex_unlock(&trng->lock); - writel(SW_DRBG_BLOCKS_NUM | (0x1 << SW_DRBG_ENABLE_SHIFT), - trng->base + SW_DRBG_BLOCKS); - writel(0x1, trng->base + SW_DRBG_INIT); + return ret; +} - ret = readl_relaxed_poll_timeout(trng->base + SW_DRBG_STATUS, - val, val & BIT(0), SLEEP_US, TIMEOUT_US); - if (ret) - pr_err("fail to init trng(%d)\n", ret); +static int hisi_trng_reseed(struct hisi_trng *trng) +{ + u8 seed[SW_DRBG_SEED_SIZE]; + int size; - return ret; + if (!trng->random_bytes) + return 0; + + size = hisi_trng_read(&trng->rng, seed, SW_DRBG_SEED_SIZE, false); + if (size != SW_DRBG_SEED_SIZE) + return -EIO; + + return hisi_trng_set_seed(trng, seed); } -static int hisi_trng_generate(struct crypto_rng *tfm, const u8 *src, - unsigned int slen, u8 *dstn, unsigned int dlen) +static int hisi_trng_get_bytes(struct hisi_trng *trng, u8 *dstn, unsigned int dlen) { - struct hisi_trng_ctx *ctx = crypto_rng_ctx(tfm); - struct hisi_trng *trng = ctx->trng; u32 data[SW_DRBG_DATA_NUM]; u32 currsize = 0; u32 val = 0; int ret; u32 i; - if (dlen > SW_DRBG_BLOCKS_NUM * SW_DRBG_BYTES || dlen == 0) { - pr_err("dlen(%u) exceeds limit(%d)!\n", dlen, - SW_DRBG_BLOCKS_NUM * SW_DRBG_BYTES); - return -EINVAL; - } + ret = hisi_trng_reseed(trng); + if (ret) + return ret; do { ret = readl_relaxed_poll_timeout(trng->base + SW_DRBG_STATUS, - val, val & BIT(1), SLEEP_US, TIMEOUT_US); + val, val & BIT(1), SLEEP_US, TIMEOUT_US); if (ret) { - pr_err("fail to generate random number(%d)!\n", ret); + pr_err("failed to generate random number(%d)!\n", ret); break; } @@ -145,30 +167,57 @@ static int hisi_trng_generate(struct crypto_rng *tfm, const u8 *src, currsize = dlen; } + trng->random_bytes += SW_DRBG_BYTES; writel(0x1, trng->base + SW_DRBG_GEN); } while (currsize < dlen); return ret; } +static int hisi_trng_generate(struct crypto_rng *tfm, const u8 *src, + unsigned int slen, u8 *dstn, unsigned int dlen) +{ + struct hisi_trng_ctx *ctx = crypto_rng_ctx(tfm); + struct hisi_trng *trng = ctx->trng; + unsigned int currsize = 0; + unsigned int block_size; + int ret; + + if (!dstn || !dlen) { + pr_err("output is error, dlen %u!\n", dlen); + return -EINVAL; + } + + do { + block_size = min_t(unsigned int, dlen - currsize, SW_MAX_RANDOM_BYTES); + mutex_lock(&trng->lock); + ret = hisi_trng_get_bytes(trng, dstn + currsize, block_size); + mutex_unlock(&trng->lock); + if (ret) + return ret; + currsize += block_size; + } while (currsize < dlen); + + return 0; +} + static int hisi_trng_init(struct crypto_tfm *tfm) { struct hisi_trng_ctx *ctx = crypto_tfm_ctx(tfm); struct hisi_trng *trng; - int ret = -EBUSY; + u32 ctx_num = ~0; mutex_lock(&trng_devices.lock); list_for_each_entry(trng, &trng_devices.list, list) { - if (!trng->is_used) { - trng->is_used = true; + if (trng->ctx_num < ctx_num) { + ctx_num = trng->ctx_num; ctx->trng = trng; - ret = 0; - break; } } + ctx->trng->ctx_num++; mutex_unlock(&trng_devices.lock); - return ret; + return 0; } static void hisi_trng_exit(struct crypto_tfm *tfm) @@ -176,7 +225,7 @@ static void hisi_trng_exit(struct crypto_tfm *tfm) struct hisi_trng_ctx *ctx = crypto_tfm_ctx(tfm); mutex_lock(&trng_devices.lock); - ctx->trng->is_used = false; + ctx->trng->ctx_num--; mutex_unlock(&trng_devices.lock); } @@ -238,7 +287,7 @@ static int hisi_trng_del_from_list(struct hisi_trng *trng) int ret = -EBUSY; mutex_lock(&trng_devices.lock); - if (!trng->is_used) { + if (!trng->ctx_num) { list_del(&trng->list); ret = 0; } @@ -262,7 +311,9 @@ static int hisi_trng_probe(struct platform_device *pdev) if (IS_ERR(trng->base)) return PTR_ERR(trng->base); - trng->is_used = false; + trng->ctx_num = 0; + trng->random_bytes = SW_MAX_RANDOM_BYTES; + mutex_init(&trng->lock); trng->ver = readl(trng->base + HISI_TRNG_VERSION); if (!trng_devices.is_init) { INIT_LIST_HEAD(&trng_devices.list); From d7decb572b55d2af33e59e9858fcee5d9ae69175 Mon Sep 17 00:00:00 2001 From: Jianpeng Chang Date: Tue, 20 Jan 2026 09:55:24 +0800 Subject: [PATCH 3354/3902] crypto: caam - fix netdev memory leak in dpaa2_caam_probe [ Upstream commit 7d43252b3060b0ba4a192dce5dba85a3f39ffe39 ] When commit 0e1a4d427f58 ("crypto: caam: Unembed net_dev structure in dpaa2") converted embedded net_device to dynamically allocated pointers, it added cleanup in dpaa2_dpseci_disable() but missed adding cleanup in dpaa2_dpseci_free() for error paths. This causes memory leaks when dpaa2_dpseci_dpio_setup() fails during probe due to DPIO devices not being ready yet. The kernel's deferred probe mechanism handles the retry successfully, but the netdevs allocated during the failed probe attempt are never freed, resulting in kmemleak reports showing multiple leaked netdev-related allocations all traced back to dpaa2_caam_probe(). Fix this by preserving the CPU mask of allocated netdevs during setup and using it for cleanup in dpaa2_dpseci_free(). This approach ensures that only the CPUs that actually had netdevs allocated will be cleaned up, avoiding potential issues with CPU hotplug scenarios. Fixes: 0e1a4d427f58 ("crypto: caam: Unembed net_dev structure in dpaa2") Signed-off-by: Jianpeng Chang Reviewed-by: Breno Leitao Signed-off-by: Herbert Xu Signed-off-by: Sasha Levin --- drivers/crypto/caam/caamalg_qi2.c | 27 +++++++++++++++------------ drivers/crypto/caam/caamalg_qi2.h | 2 ++ 2 files changed, 17 insertions(+), 12 deletions(-) diff --git a/drivers/crypto/caam/caamalg_qi2.c b/drivers/crypto/caam/caamalg_qi2.c index 107ccb2ade4205..c6117c23eb25b0 100644 --- a/drivers/crypto/caam/caamalg_qi2.c +++ b/drivers/crypto/caam/caamalg_qi2.c @@ -4814,7 +4814,8 @@ static void dpaa2_dpseci_free(struct dpaa2_caam_priv *priv) { struct device *dev = priv->dev; struct fsl_mc_device *ls_dev = to_fsl_mc_device(dev); - int err; + struct dpaa2_caam_priv_per_cpu *ppriv; + int i, err; if (DPSECI_VER(priv->major_ver, priv->minor_ver) > DPSECI_VER(5, 3)) { err = dpseci_reset(priv->mc_io, 0, ls_dev->mc_handle); @@ -4822,6 +4823,12 @@ static void dpaa2_dpseci_free(struct dpaa2_caam_priv *priv) dev_err(dev, "dpseci_reset() failed\n"); } + for_each_cpu(i, priv->clean_mask) { + ppriv = per_cpu_ptr(priv->ppriv, i); + free_netdev(ppriv->net_dev); + } + free_cpumask_var(priv->clean_mask); + dpaa2_dpseci_congestion_free(priv); dpseci_close(priv->mc_io, 0, ls_dev->mc_handle); } @@ -5007,16 +5014,15 @@ static int __cold dpaa2_dpseci_setup(struct fsl_mc_device *ls_dev) struct device *dev = &ls_dev->dev; struct dpaa2_caam_priv *priv; struct dpaa2_caam_priv_per_cpu *ppriv; - cpumask_var_t clean_mask; int err, cpu; u8 i; err = -ENOMEM; - if (!zalloc_cpumask_var(&clean_mask, GFP_KERNEL)) - goto err_cpumask; - priv = dev_get_drvdata(dev); + if (!zalloc_cpumask_var(&priv->clean_mask, GFP_KERNEL)) + goto err_cpumask; + priv->dev = dev; priv->dpsec_id = ls_dev->obj_desc.id; @@ -5118,7 +5124,7 @@ static int __cold dpaa2_dpseci_setup(struct fsl_mc_device *ls_dev) err = -ENOMEM; goto err_alloc_netdev; } - cpumask_set_cpu(cpu, clean_mask); + cpumask_set_cpu(cpu, priv->clean_mask); ppriv->net_dev->dev = *dev; netif_napi_add_tx_weight(ppriv->net_dev, &ppriv->napi, @@ -5126,18 +5132,16 @@ static int __cold dpaa2_dpseci_setup(struct fsl_mc_device *ls_dev) DPAA2_CAAM_NAPI_WEIGHT); } - err = 0; - goto free_cpumask; + return 0; err_alloc_netdev: - free_dpaa2_pcpu_netdev(priv, clean_mask); + free_dpaa2_pcpu_netdev(priv, priv->clean_mask); err_get_rx_queue: dpaa2_dpseci_congestion_free(priv); err_get_vers: dpseci_close(priv->mc_io, 0, ls_dev->mc_handle); err_open: -free_cpumask: - free_cpumask_var(clean_mask); + free_cpumask_var(priv->clean_mask); err_cpumask: return err; } @@ -5182,7 +5186,6 @@ static int __cold dpaa2_dpseci_disable(struct dpaa2_caam_priv *priv) ppriv = per_cpu_ptr(priv->ppriv, i); napi_disable(&ppriv->napi); netif_napi_del(&ppriv->napi); - free_netdev(ppriv->net_dev); } return 0; diff --git a/drivers/crypto/caam/caamalg_qi2.h b/drivers/crypto/caam/caamalg_qi2.h index 61d1219a202fcb..8e65b4b28c7bab 100644 --- a/drivers/crypto/caam/caamalg_qi2.h +++ b/drivers/crypto/caam/caamalg_qi2.h @@ -42,6 +42,7 @@ * @mc_io: pointer to MC portal's I/O object * @domain: IOMMU domain * @ppriv: per CPU pointers to privata data + * @clean_mask: CPU mask of CPUs that have allocated netdevs */ struct dpaa2_caam_priv { int dpsec_id; @@ -65,6 +66,7 @@ struct dpaa2_caam_priv { struct dpaa2_caam_priv_per_cpu __percpu *ppriv; struct dentry *dfs_root; + cpumask_var_t clean_mask; }; /** From d7b87adeb0eb539b9b824b101bb14fb01e41240b Mon Sep 17 00:00:00 2001 From: Paul Chaignon Date: Sat, 31 Jan 2026 17:08:37 +0100 Subject: [PATCH 3355/3902] bpf: Fix bpf_xdp_store_bytes proto for read-only arg [ Upstream commit 6557f1565d779851c4db9c488c49c05a47a6e72f ] While making some maps in Cilium read-only from the BPF side, we noticed that the bpf_xdp_store_bytes proto is incorrect. In particular, the verifier was throwing the following error: ; ret = ctx_store_bytes(ctx, l3_off + offsetof(struct iphdr, saddr), &nat->address, 4, 0); 635: (79) r1 = *(u64 *)(r10 -144) ; R1=ctx() R10=fp0 fp-144=ctx() 636: (b4) w2 = 26 ; R2=26 637: (b4) w4 = 4 ; R4=4 638: (b4) w5 = 0 ; R5=0 639: (85) call bpf_xdp_store_bytes#190 write into map forbidden, value_size=6 off=0 size=4 nat comes from a BPF_F_RDONLY_PROG map, so R3 is a PTR_TO_MAP_VALUE. The verifier checks the helper's memory access to R3 in check_mem_size_reg, as it reaches ARG_CONST_SIZE argument. The third argument has expected type ARG_PTR_TO_UNINIT_MEM, which includes the MEM_WRITE flag. The verifier thus checks for a BPF_WRITE access on R3. Given R3 points to a read-only map, the check fails. Conversely, ARG_PTR_TO_UNINIT_MEM can also lead to the helper reading from uninitialized memory. This patch simply fixes the expected argument type to match that of bpf_skb_store_bytes. Fixes: 3f364222d032 ("net: xdp: introduce bpf_xdp_pointer utility routine") Signed-off-by: Paul Chaignon Link: https://lore.kernel.org/r/9fa3c9f72d806e82541071c4df88b8cba28ad6a9.1769875479.git.paul.chaignon@gmail.com Signed-off-by: Alexei Starovoitov Signed-off-by: Sasha Levin --- net/core/filter.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/net/core/filter.c b/net/core/filter.c index b9a51f322b655d..d93f7dea828e57 100644 --- a/net/core/filter.c +++ b/net/core/filter.c @@ -4133,7 +4133,7 @@ static const struct bpf_func_proto bpf_xdp_store_bytes_proto = { .ret_type = RET_INTEGER, .arg1_type = ARG_PTR_TO_CTX, .arg2_type = ARG_ANYTHING, - .arg3_type = ARG_PTR_TO_UNINIT_MEM, + .arg3_type = ARG_PTR_TO_MEM | MEM_RDONLY, .arg4_type = ARG_CONST_SIZE, }; From 0c1876a365447d84c03c4acec605440b30b7944b Mon Sep 17 00:00:00 2001 From: Sebastian Andrzej Siewior Date: Wed, 28 Jan 2026 10:55:21 +0100 Subject: [PATCH 3356/3902] genirq: Set IRQF_COND_ONESHOT in devm_request_irq(). [ Upstream commit 943b052ded21feb84f293d40b06af3181cd0d0d7 ] The flag IRQF_COND_ONESHOT was already force-added to request_irq() because the ACPI SCI interrupt handler is using the IRQF_ONESHOT flag which breaks all shared handlers. devm_request_irq() needs the same change since some users, such as int0002_vgpio, are using this function instead. Add IRQF_COND_ONESHOT to the flags passed to devm_request_irq(). Fixes: c37927a203fa2 ("genirq: Set IRQF_COND_ONESHOT in request_irq()") Signed-off-by: Sebastian Andrzej Siewior Signed-off-by: Thomas Gleixner Link: https://patch.msgid.link/20260128095540.863589-2-bigeasy@linutronix.de Signed-off-by: Sasha Levin --- include/linux/interrupt.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/linux/interrupt.h b/include/linux/interrupt.h index 51b6484c049345..8f1166bc3b1cef 100644 --- a/include/linux/interrupt.h +++ b/include/linux/interrupt.h @@ -215,7 +215,7 @@ static inline int __must_check devm_request_irq(struct device *dev, unsigned int irq, irq_handler_t handler, unsigned long irqflags, const char *devname, void *dev_id) { - return devm_request_threaded_irq(dev, irq, handler, NULL, irqflags, + return devm_request_threaded_irq(dev, irq, handler, NULL, irqflags | IRQF_COND_ONESHOT, devname, dev_id); } From 91b6a550472a976223310538adee5fd7193ff87f Mon Sep 17 00:00:00 2001 From: Sebastian Andrzej Siewior Date: Wed, 28 Jan 2026 10:55:22 +0100 Subject: [PATCH 3357/3902] platform/x86: int0002: Remove IRQF_ONESHOT from request_irq() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [ Upstream commit f6bc712877f24dc89bdfd7bdbf1a32f3b9960b34 ] Passing IRQF_ONESHOT ensures that the interrupt source is masked until the secondary (threaded) handler is done. If only a primary handler is used then the flag makes no sense because the interrupt cannot fire (again) while its handler is running. The flag also prevents force-threading of the primary handler and the irq-core will warn about this. The flag was added to match the flag on the shared handler which uses a threaded handler and therefore IRQF_ONESHOT. This is no longer needed because devm_request_irq() now passes IRQF_COND_ONESHOT for this case. Revert adding IRQF_ONESHOT to irqflags. Fixes: 8f812373d1958 ("platform/x86: intel: int0002_vgpio: Pass IRQF_ONESHOT to request_irq()") Reported-by: Borah, Chaitanya Kumar Signed-off-by: Sebastian Andrzej Siewior Signed-off-by: Thomas Gleixner Reviewed-by: Hans de Goede Acked-by: Ilpo Järvinen Link: https://patch.msgid.link/20260128095540.863589-3-bigeasy@linutronix.de Closes: https://lore.kernel.org/all/555f1c56-0f74-41bf-8bd2-6217e0aab0c6@intel.com Signed-off-by: Sasha Levin --- drivers/platform/x86/intel/int0002_vgpio.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/platform/x86/intel/int0002_vgpio.c b/drivers/platform/x86/intel/int0002_vgpio.c index 6f5629dc3f8dbc..562e8802564367 100644 --- a/drivers/platform/x86/intel/int0002_vgpio.c +++ b/drivers/platform/x86/intel/int0002_vgpio.c @@ -206,8 +206,8 @@ static int int0002_probe(struct platform_device *pdev) * FIXME: augment this if we managed to pull handling of shared * IRQs into gpiolib. */ - ret = devm_request_irq(dev, irq, int0002_irq, - IRQF_ONESHOT | IRQF_SHARED, "INT0002", chip); + ret = devm_request_irq(dev, irq, int0002_irq, IRQF_SHARED, "INT0002", + chip); if (ret) { dev_err(dev, "Error requesting IRQ %d: %d\n", irq, ret); return ret; From 680b652b2d08779965aa5d27b575bee112b3a3a8 Mon Sep 17 00:00:00 2001 From: Sebastian Andrzej Siewior Date: Wed, 28 Jan 2026 10:55:23 +0100 Subject: [PATCH 3358/3902] iommu/amd: Use core's primary handler and set IRQF_ONESHOT [ Upstream commit 5bfcdccb4d18d3909b7f87942be67fd6bdc00c1d ] request_threaded_irq() is invoked with a primary and a secondary handler and no flags are passed. The primary handler is the same as irq_default_primary_handler() so there is no need to have an identical copy. The lack of the IRQF_ONESHOT can be dangerous because the interrupt source is not masked while the threaded handler is active. This means, especially on LEVEL typed interrupt lines, the interrupt can fire again before the threaded handler had a chance to run. Use the default primary interrupt handler by specifying NULL and set IRQF_ONESHOT so the interrupt source is masked until the secondary handler is done. Fixes: 72fe00f01f9a3 ("x86/amd-iommu: Use threaded interupt handler") Signed-off-by: Sebastian Andrzej Siewior Signed-off-by: Thomas Gleixner Link: https://patch.msgid.link/20260128095540.863589-4-bigeasy@linutronix.de Signed-off-by: Sasha Levin --- drivers/iommu/amd/amd_iommu.h | 1 - drivers/iommu/amd/init.c | 12 ++++-------- drivers/iommu/amd/iommu.c | 5 ----- 3 files changed, 4 insertions(+), 14 deletions(-) diff --git a/drivers/iommu/amd/amd_iommu.h b/drivers/iommu/amd/amd_iommu.h index 9b4b589a54b57e..bf77fdf5529f48 100644 --- a/drivers/iommu/amd/amd_iommu.h +++ b/drivers/iommu/amd/amd_iommu.h @@ -15,7 +15,6 @@ irqreturn_t amd_iommu_int_thread(int irq, void *data); irqreturn_t amd_iommu_int_thread_evtlog(int irq, void *data); irqreturn_t amd_iommu_int_thread_pprlog(int irq, void *data); irqreturn_t amd_iommu_int_thread_galog(int irq, void *data); -irqreturn_t amd_iommu_int_handler(int irq, void *data); void amd_iommu_restart_log(struct amd_iommu *iommu, const char *evt_type, u8 cntrl_intr, u8 cntrl_log, u32 status_run_mask, u32 status_overflow_mask); diff --git a/drivers/iommu/amd/init.c b/drivers/iommu/amd/init.c index 034edce816d01b..53afb1cb0a6fcc 100644 --- a/drivers/iommu/amd/init.c +++ b/drivers/iommu/amd/init.c @@ -2355,12 +2355,8 @@ static int iommu_setup_msi(struct amd_iommu *iommu) if (r) return r; - r = request_threaded_irq(iommu->dev->irq, - amd_iommu_int_handler, - amd_iommu_int_thread, - 0, "AMD-Vi", - iommu); - + r = request_threaded_irq(iommu->dev->irq, NULL, amd_iommu_int_thread, + IRQF_ONESHOT, "AMD-Vi", iommu); if (r) { pci_disable_msi(iommu->dev); return r; @@ -2534,8 +2530,8 @@ static int __iommu_setup_intcapxt(struct amd_iommu *iommu, const char *devname, return irq; } - ret = request_threaded_irq(irq, amd_iommu_int_handler, - thread_fn, 0, devname, iommu); + ret = request_threaded_irq(irq, NULL, thread_fn, IRQF_ONESHOT, devname, + iommu); if (ret) { irq_domain_free_irqs(irq, 1); irq_domain_remove(domain); diff --git a/drivers/iommu/amd/iommu.c b/drivers/iommu/amd/iommu.c index 5914bef0c8c19a..30dd482fe0953d 100644 --- a/drivers/iommu/amd/iommu.c +++ b/drivers/iommu/amd/iommu.c @@ -1146,11 +1146,6 @@ irqreturn_t amd_iommu_int_thread(int irq, void *data) return IRQ_HANDLED; } -irqreturn_t amd_iommu_int_handler(int irq, void *data) -{ - return IRQ_WAKE_THREAD; -} - /**************************************************************************** * * IOMMU command queuing functions From f5e5bbf56ecf6434ae065219754f70fc21d2d169 Mon Sep 17 00:00:00 2001 From: Sebastian Andrzej Siewior Date: Wed, 28 Jan 2026 10:55:26 +0100 Subject: [PATCH 3359/3902] Bluetooth: btintel_pcie: Use IRQF_ONESHOT and default primary handler [ Upstream commit 28abed6569c87eab9071ab56c64433c2f0d9ce51 ] There is no added value in btintel_pcie_msix_isr() compared to irq_default_primary_handler(). Using a threaded interrupt without a dedicated primary handler mandates the IRQF_ONESHOT flag to mask the interrupt source while the threaded handler is active. Otherwise the interrupt can fire again before the threaded handler had a chance to run. Use the default primary interrupt handler by specifying NULL and set IRQF_ONESHOT so the interrupt source is masked until the secondary handler is done. Fixes: c2b636b3f788d ("Bluetooth: btintel_pcie: Add support for PCIe transport") Signed-off-by: Sebastian Andrzej Siewior Signed-off-by: Thomas Gleixner Link: https://patch.msgid.link/20260128095540.863589-7-bigeasy@linutronix.de Signed-off-by: Sasha Levin --- drivers/bluetooth/btintel_pcie.c | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/drivers/bluetooth/btintel_pcie.c b/drivers/bluetooth/btintel_pcie.c index f280bcc61bbfb7..c68a8de3025b05 100644 --- a/drivers/bluetooth/btintel_pcie.c +++ b/drivers/bluetooth/btintel_pcie.c @@ -1430,11 +1430,6 @@ static void btintel_pcie_msix_rx_handle(struct btintel_pcie_data *data) } } -static irqreturn_t btintel_pcie_msix_isr(int irq, void *data) -{ - return IRQ_WAKE_THREAD; -} - static inline bool btintel_pcie_is_rxq_empty(struct btintel_pcie_data *data) { return data->ia.cr_hia[BTINTEL_PCIE_RXQ_NUM] == data->ia.cr_tia[BTINTEL_PCIE_RXQ_NUM]; @@ -1536,9 +1531,9 @@ static int btintel_pcie_setup_irq(struct btintel_pcie_data *data) err = devm_request_threaded_irq(&data->pdev->dev, msix_entry->vector, - btintel_pcie_msix_isr, + NULL, btintel_pcie_irq_msix_handler, - IRQF_SHARED, + IRQF_ONESHOT | IRQF_SHARED, KBUILD_MODNAME, msix_entry); if (err) { From 909dfc310ee53df6c8f3f09a6a3e532073cbe53b Mon Sep 17 00:00:00 2001 From: Sebastian Andrzej Siewior Date: Wed, 28 Jan 2026 10:55:27 +0100 Subject: [PATCH 3360/3902] scsi: efct: Use IRQF_ONESHOT and default primary handler [ Upstream commit bd81f07e9a27c341cd7e72be95eb0b7cf3910926 ] There is no added value in efct_intr_msix() compared to irq_default_primary_handler(). Using a threaded interrupt without a dedicated primary handler mandates the IRQF_ONESHOT flag to mask the interrupt source while the threaded handler is active. Otherwise the interrupt can fire again before the threaded handler had a chance to run. Use the default primary interrupt handler by specifying NULL and set IRQF_ONESHOT so the interrupt source is masked until the secondary handler is done. Fixes: 4df84e8466242 ("scsi: elx: efct: Driver initialization routines") Signed-off-by: Sebastian Andrzej Siewior Signed-off-by: Thomas Gleixner Link: https://patch.msgid.link/20260128095540.863589-8-bigeasy@linutronix.de Signed-off-by: Sasha Levin --- drivers/scsi/elx/efct/efct_driver.c | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/drivers/scsi/elx/efct/efct_driver.c b/drivers/scsi/elx/efct/efct_driver.c index 1bd42f7db1773d..528399f725d428 100644 --- a/drivers/scsi/elx/efct/efct_driver.c +++ b/drivers/scsi/elx/efct/efct_driver.c @@ -415,12 +415,6 @@ efct_intr_thread(int irq, void *handle) return IRQ_HANDLED; } -static irqreturn_t -efct_intr_msix(int irq, void *handle) -{ - return IRQ_WAKE_THREAD; -} - static int efct_setup_msix(struct efct *efct, u32 num_intrs) { @@ -450,7 +444,7 @@ efct_setup_msix(struct efct *efct, u32 num_intrs) intr_ctx->index = i; rc = request_threaded_irq(pci_irq_vector(efct->pci, i), - efct_intr_msix, efct_intr_thread, 0, + NULL, efct_intr_thread, IRQF_ONESHOT, EFCT_DRIVER_NAME, intr_ctx); if (rc) { dev_err(&efct->pci->dev, From 66839f7c2bd66689ad508e13b0c73a4aa3340e48 Mon Sep 17 00:00:00 2001 From: Sebastian Andrzej Siewior Date: Wed, 28 Jan 2026 10:55:30 +0100 Subject: [PATCH 3361/3902] EDAC/altera: Remove IRQF_ONESHOT [ Upstream commit 5c858d6c66304b4c7579582ec5235f02d43578ea ] Passing IRQF_ONESHOT ensures that the interrupt source is masked until the secondary (threaded) handler is done. If only a primary handler is used then the flag makes no sense because the interrupt can not fire (again) while its handler is running. The flag also prevents force-threading of the primary handler and the irq-core will warn about this. Remove IRQF_ONESHOT from irqflags. Fixes: a29d64a45eed1 ("EDAC, altera: Add IRQ Flags to disable IRQ while handling") Signed-off-by: Sebastian Andrzej Siewior Signed-off-by: Thomas Gleixner Link: https://patch.msgid.link/20260128095540.863589-11-bigeasy@linutronix.de Signed-off-by: Sasha Levin --- drivers/edac/altera_edac.c | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/drivers/edac/altera_edac.c b/drivers/edac/altera_edac.c index 0c5b94e64ea157..4edd2088c2db6f 100644 --- a/drivers/edac/altera_edac.c +++ b/drivers/edac/altera_edac.c @@ -1563,8 +1563,7 @@ static int altr_portb_setup(struct altr_edac_device_dev *device) goto err_release_group_1; } rc = devm_request_irq(&altdev->ddev, altdev->sb_irq, - prv->ecc_irq_handler, - IRQF_ONESHOT | IRQF_TRIGGER_HIGH, + prv->ecc_irq_handler, IRQF_TRIGGER_HIGH, ecc_name, altdev); if (rc) { edac_printk(KERN_ERR, EDAC_DEVICE, "PortB SBERR IRQ error\n"); @@ -1587,8 +1586,7 @@ static int altr_portb_setup(struct altr_edac_device_dev *device) goto err_release_group_1; } rc = devm_request_irq(&altdev->ddev, altdev->db_irq, - prv->ecc_irq_handler, - IRQF_ONESHOT | IRQF_TRIGGER_HIGH, + prv->ecc_irq_handler, IRQF_TRIGGER_HIGH, ecc_name, altdev); if (rc) { edac_printk(KERN_ERR, EDAC_DEVICE, "PortB DBERR IRQ error\n"); @@ -1970,8 +1968,7 @@ static int altr_edac_a10_device_add(struct altr_arria10_edac *edac, goto err_release_group1; } rc = devm_request_irq(edac->dev, altdev->sb_irq, prv->ecc_irq_handler, - IRQF_ONESHOT | IRQF_TRIGGER_HIGH, - ecc_name, altdev); + IRQF_TRIGGER_HIGH, ecc_name, altdev); if (rc) { edac_printk(KERN_ERR, EDAC_DEVICE, "No SBERR IRQ resource\n"); goto err_release_group1; @@ -1993,7 +1990,7 @@ static int altr_edac_a10_device_add(struct altr_arria10_edac *edac, goto err_release_group1; } rc = devm_request_irq(edac->dev, altdev->db_irq, prv->ecc_irq_handler, - IRQF_ONESHOT | IRQF_TRIGGER_HIGH, + IRQF_TRIGGER_HIGH, ecc_name, altdev); if (rc) { edac_printk(KERN_ERR, EDAC_DEVICE, "No DBERR IRQ resource\n"); From 5248f7f5b3407e0d93462cfb63810d44a1b12562 Mon Sep 17 00:00:00 2001 From: Sebastian Andrzej Siewior Date: Wed, 28 Jan 2026 10:55:31 +0100 Subject: [PATCH 3362/3902] usb: typec: fusb302: Remove IRQF_ONESHOT [ Upstream commit a7fb84ea70aae9a92a842932206e70ed1b3c7007 ] Passing IRQF_ONESHOT ensures that the interrupt source is masked until the secondary (threaded) handler is done. If only a primary handler is used then the flag makes no sense because the interrupt can not fire (again) while its handler is running. The flag also prevents force-threading of the primary handler and the irq-core will warn about this. Remove IRQF_ONESHOT from irqflags. Fixes: 309b6341d5570 ("usb: typec: fusb302: Revert incorrect threaded irq fix") Signed-off-by: Sebastian Andrzej Siewior Signed-off-by: Thomas Gleixner Acked-by: Greg Kroah-Hartman Acked-by: Heikki Krogerus Link: https://patch.msgid.link/20260128095540.863589-12-bigeasy@linutronix.de Signed-off-by: Sasha Levin --- drivers/usb/typec/tcpm/fusb302.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/drivers/usb/typec/tcpm/fusb302.c b/drivers/usb/typec/tcpm/fusb302.c index 870a71f953f6cd..19ff8217818e7e 100644 --- a/drivers/usb/typec/tcpm/fusb302.c +++ b/drivers/usb/typec/tcpm/fusb302.c @@ -1756,8 +1756,7 @@ static int fusb302_probe(struct i2c_client *client) } ret = request_irq(chip->gpio_int_n_irq, fusb302_irq_intn, - IRQF_ONESHOT | IRQF_TRIGGER_LOW, - "fsc_interrupt_int_n", chip); + IRQF_TRIGGER_LOW, "fsc_interrupt_int_n", chip); if (ret < 0) { dev_err(dev, "cannot request IRQ for GPIO Int_N, ret=%d", ret); goto tcpm_unregister_port; From 7b0c5dec6a01ab9dc053a499f8b16ed976413a43 Mon Sep 17 00:00:00 2001 From: Sebastian Andrzej Siewior Date: Wed, 28 Jan 2026 10:55:32 +0100 Subject: [PATCH 3363/3902] rtc: amlogic-a4: Remove IRQF_ONESHOT [ Upstream commit 18d28446231390e4ea3634fb16200865df2c6506 ] Passing IRQF_ONESHOT ensures that the interrupt source is masked until the secondary (threaded) handler is done. If only a primary handler is used then the flag makes no sense because the interrupt can not fire (again) while its handler is running. The flag also prevents force-threading of the primary handler and the irq-core will warn about this. Remove IRQF_ONESHOT from irqflags. Fixes: c89ac9182ee29 ("rtc: support for the Amlogic on-chip RTC") Signed-off-by: Sebastian Andrzej Siewior Signed-off-by: Thomas Gleixner Reviewed-by: Xianwei Zhao Link: https://patch.msgid.link/20260128095540.863589-13-bigeasy@linutronix.de Signed-off-by: Sasha Levin --- drivers/rtc/rtc-amlogic-a4.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/rtc/rtc-amlogic-a4.c b/drivers/rtc/rtc-amlogic-a4.c index a993d35e1d6b09..d766055d958488 100644 --- a/drivers/rtc/rtc-amlogic-a4.c +++ b/drivers/rtc/rtc-amlogic-a4.c @@ -371,7 +371,7 @@ static int aml_rtc_probe(struct platform_device *pdev) } ret = devm_request_irq(dev, rtc->irq, aml_rtc_handler, - IRQF_ONESHOT, "aml-rtc alarm", rtc); + 0, "aml-rtc alarm", rtc); if (ret) { dev_err_probe(dev, ret, "IRQ%d request failed, ret = %d\n", rtc->irq, ret); From 352f1bbbd711a766439cfc170a6d9e11f504c813 Mon Sep 17 00:00:00 2001 From: Sebastian Andrzej Siewior Date: Wed, 28 Jan 2026 10:55:35 +0100 Subject: [PATCH 3364/3902] mfd: wm8350-core: Use IRQF_ONESHOT [ Upstream commit 553b4999cbe231b5011cb8db05a3092dec168aca ] Using a threaded interrupt without a dedicated primary handler mandates the IRQF_ONESHOT flag to mask the interrupt source while the threaded handler is active. Otherwise the interrupt can fire again before the threaded handler had a chance to run. Mark explained that this should not happen with this hardware since it is a slow irqchip which is behind an I2C/ SPI bus but the IRQ-core will refuse to accept such a handler. Set IRQF_ONESHOT so the interrupt source is masked until the secondary handler is done. Fixes: 1c6c69525b40e ("genirq: Reject bogus threaded irq requests") Signed-off-by: Sebastian Andrzej Siewior Signed-off-by: Thomas Gleixner Reviewed-by: Charles Keepax Reviewed-by: Andy Shevchenko Link: https://patch.msgid.link/20260128095540.863589-16-bigeasy@linutronix.de Signed-off-by: Sasha Levin --- include/linux/mfd/wm8350/core.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/linux/mfd/wm8350/core.h b/include/linux/mfd/wm8350/core.h index 5f70d3b5d1b1a4..097ef4dfcdac8c 100644 --- a/include/linux/mfd/wm8350/core.h +++ b/include/linux/mfd/wm8350/core.h @@ -667,7 +667,7 @@ static inline int wm8350_register_irq(struct wm8350 *wm8350, int irq, return -ENODEV; return request_threaded_irq(irq + wm8350->irq_base, NULL, - handler, flags, name, data); + handler, flags | IRQF_ONESHOT, name, data); } static inline void wm8350_free_irq(struct wm8350 *wm8350, int irq, void *data) From 83550d2311559beb40bb3ceb8c13826209ad8bab Mon Sep 17 00:00:00 2001 From: Sebastian Andrzej Siewior Date: Wed, 28 Jan 2026 10:55:40 +0100 Subject: [PATCH 3365/3902] media: pci: mg4b: Use IRQF_NO_THREAD [ Upstream commit ef92b98f5f6758a049898b53aa30476010db04fa ] The interrupt handler iio_trigger_generic_data_rdy_poll() will invoke other interrupt handlers and this supposed to happen from hard interrupt context. Use IRQF_NO_THREAD to forbid forced-threading. Fixes: 0ab13674a9bd1 ("media: pci: mgb4: Added Digiteq Automotive MGB4 driver") Signed-off-by: Sebastian Andrzej Siewior Signed-off-by: Thomas Gleixner Link: https://patch.msgid.link/20260128095540.863589-21-bigeasy@linutronix.de Signed-off-by: Sasha Levin --- drivers/media/pci/mgb4/mgb4_trigger.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/media/pci/mgb4/mgb4_trigger.c b/drivers/media/pci/mgb4/mgb4_trigger.c index d7dddc5c8728e8..10c23f0c833d59 100644 --- a/drivers/media/pci/mgb4/mgb4_trigger.c +++ b/drivers/media/pci/mgb4/mgb4_trigger.c @@ -114,7 +114,7 @@ static int probe_trigger(struct iio_dev *indio_dev, int irq) if (!st->trig) return -ENOMEM; - ret = request_irq(irq, &iio_trigger_generic_data_rdy_poll, 0, + ret = request_irq(irq, &iio_trigger_generic_data_rdy_poll, IRQF_NO_THREAD, "mgb4-trigger", st->trig); if (ret) goto error_free_trig; From 909131ad0a0065ecf457e544e035ea0086099fab Mon Sep 17 00:00:00 2001 From: Joel Fernandes Date: Mon, 26 Jan 2026 10:58:59 +0100 Subject: [PATCH 3366/3902] sched/deadline: Clear the defer params [ Upstream commit 3cb3b27693bf30defb16aa096158a3b24583b8d2 ] The defer params were not cleared in __dl_clear_params. Clear them. Without this is some of my test cases are flaking and the DL timer is not starting correctly AFAICS. Fixes: a110a81c52a9 ("sched/deadline: Deferrable dl server") Signed-off-by: Joel Fernandes Signed-off-by: Peter Zijlstra (Intel) Reviewed-by: Andrea Righi Acked-by: Juri Lelli Tested-by: Christian Loehle Link: https://patch.msgid.link/20260126100050.3854740-2-arighi@nvidia.com Signed-off-by: Sasha Levin --- kernel/sched/deadline.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/kernel/sched/deadline.c b/kernel/sched/deadline.c index c7a8717e837dd1..72499cf2a1db5d 100644 --- a/kernel/sched/deadline.c +++ b/kernel/sched/deadline.c @@ -3591,6 +3591,9 @@ static void __dl_clear_params(struct sched_dl_entity *dl_se) dl_se->dl_non_contending = 0; dl_se->dl_overrun = 0; dl_se->dl_server = 0; + dl_se->dl_defer = 0; + dl_se->dl_defer_running = 0; + dl_se->dl_defer_armed = 0; #ifdef CONFIG_RT_MUTEXES dl_se->pi_se = dl_se; From 52aeb1e07ec223caf212f036817976c98d2aa250 Mon Sep 17 00:00:00 2001 From: Chen Jinghuang Date: Thu, 22 Jan 2026 01:25:33 +0000 Subject: [PATCH 3367/3902] sched/rt: Skip currently executing CPU in rto_next_cpu() [ Upstream commit 94894c9c477e53bcea052e075c53f89df3d2a33e ] CPU0 becomes overloaded when hosting a CPU-bound RT task, a non-CPU-bound RT task, and a CFS task stuck in kernel space. When other CPUs switch from RT to non-RT tasks, RT load balancing (LB) is triggered; with HAVE_RT_PUSH_IPI enabled, they send IPIs to CPU0 to drive the execution of rto_push_irq_work_func. During push_rt_task on CPU0, if next_task->prio < rq->donor->prio, resched_curr() sets NEED_RESCHED and after the push operation completes, CPU0 calls rto_next_cpu(). Since only CPU0 is overloaded in this scenario, rto_next_cpu() should ideally return -1 (no further IPI needed). However, multiple CPUs invoking tell_cpu_to_push() during LB increments rd->rto_loop_next. Even when rd->rto_cpu is set to -1, the mismatch between rd->rto_loop and rd->rto_loop_next forces rto_next_cpu() to restart its search from -1. With CPU0 remaining overloaded (satisfying rt_nr_migratory && rt_nr_total > 1), it gets reselected, causing CPU0 to queue irq_work to itself and send self-IPIs repeatedly. As long as CPU0 stays overloaded and other CPUs run pull_rt_tasks(), it falls into an infinite self-IPI loop, which triggers a CPU hardlockup due to continuous self-interrupts. The trigging scenario is as follows: cpu0 cpu1 cpu2 pull_rt_task tell_cpu_to_push <------------irq_work_queue_on rto_push_irq_work_func push_rt_task resched_curr(rq) pull_rt_task rto_next_cpu tell_cpu_to_push <-------------------------- atomic_inc(rto_loop_next) rd->rto_loop != next rto_next_cpu irq_work_queue_on rto_push_irq_work_func Fix redundant self-IPI by filtering the initiating CPU in rto_next_cpu(). This solution has been verified to effectively eliminate spurious self-IPIs and prevent CPU hardlockup scenarios. Fixes: 4bdced5c9a29 ("sched/rt: Simplify the IPI based RT balancing logic") Suggested-by: Steven Rostedt (Google) Suggested-by: K Prateek Nayak Signed-off-by: Chen Jinghuang Signed-off-by: Peter Zijlstra (Intel) Reviewed-by: Steven Rostedt (Google) Reviewed-by: Valentin Schneider Link: https://patch.msgid.link/20260122012533.673768-1-chenjinghuang2@huawei.com Signed-off-by: Sasha Levin --- kernel/sched/rt.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/kernel/sched/rt.c b/kernel/sched/rt.c index fb07dcfc60a244..d4d994fb8999a4 100644 --- a/kernel/sched/rt.c +++ b/kernel/sched/rt.c @@ -2100,6 +2100,7 @@ static void push_rt_tasks(struct rq *rq) */ static int rto_next_cpu(struct root_domain *rd) { + int this_cpu = smp_processor_id(); int next; int cpu; @@ -2123,6 +2124,10 @@ static int rto_next_cpu(struct root_domain *rd) rd->rto_cpu = cpu; + /* Do not send IPI to self */ + if (cpu == this_cpu) + continue; + if (cpu < nr_cpu_ids) return cpu; From 45acb60fe59abdec804758fe2dceb18dc4378a74 Mon Sep 17 00:00:00 2001 From: Titouan Ameline de Cadeville Date: Tue, 3 Feb 2026 18:59:50 +0100 Subject: [PATCH 3368/3902] fs/tests: exec: drop duplicate bprm_stack_limits test vectors [ Upstream commit 46a03ea50b5f380bdb99178b8f90b39c6ba1f528 ] Remove duplicate entries from the bprm_stack_limits KUnit test vector table. The duplicates do not add coverage and only increase test size. Signed-off-by: Titouan Ameline de Cadeville Fixes: 60371f43e56b ("exec: Add KUnit test for bprm_stack_limits()") Link: https://patch.msgid.link/20260203175950.43710-1-titouan.ameline@gmail.com Signed-off-by: Kees Cook Signed-off-by: Sasha Levin --- fs/tests/exec_kunit.c | 6 ------ 1 file changed, 6 deletions(-) diff --git a/fs/tests/exec_kunit.c b/fs/tests/exec_kunit.c index 7c77d039680bbe..f412d1a0f6bba9 100644 --- a/fs/tests/exec_kunit.c +++ b/fs/tests/exec_kunit.c @@ -87,9 +87,6 @@ static const struct bprm_stack_limits_result bprm_stack_limits_results[] = { .argc = 0, .envc = ARG_MAX / sizeof(void *) - 1 }, .expected_argmin = ULONG_MAX - sizeof(void *) }, /* Raising rlim_stack / 4 to _STK_LIM / 4 * 3 will see more space. */ - { { .p = ULONG_MAX, .rlim_stack.rlim_cur = 4 * (_STK_LIM / 4 * 3), - .argc = 0, .envc = 0 }, - .expected_argmin = ULONG_MAX - (_STK_LIM / 4 * 3) + sizeof(void *) }, { { .p = ULONG_MAX, .rlim_stack.rlim_cur = 4 * (_STK_LIM / 4 * 3), .argc = 0, .envc = 0 }, .expected_argmin = ULONG_MAX - (_STK_LIM / 4 * 3) + sizeof(void *) }, @@ -103,9 +100,6 @@ static const struct bprm_stack_limits_result bprm_stack_limits_results[] = { { { .p = ULONG_MAX, .rlim_stack.rlim_cur = 4 * _STK_LIM, .argc = 0, .envc = 0 }, .expected_argmin = ULONG_MAX - (_STK_LIM / 4 * 3) + sizeof(void *) }, - { { .p = ULONG_MAX, .rlim_stack.rlim_cur = 4 * _STK_LIM, - .argc = 0, .envc = 0 }, - .expected_argmin = ULONG_MAX - (_STK_LIM / 4 * 3) + sizeof(void *) }, }; static void exec_test_bprm_stack_limits(struct kunit *test) From 5835a077c6f5c565d525eaca9fac01572b97a9b9 Mon Sep 17 00:00:00 2001 From: KP Singh Date: Thu, 5 Feb 2026 07:38:07 +0100 Subject: [PATCH 3369/3902] bpf: Limit bpf program signature size [ Upstream commit ea1535e28bb3773fc0b3cbd1f3842b808016990c ] Practical BPF signatures are significantly smaller than KMALLOC_MAX_CACHE_SIZE Allowing larger sizes opens the door for abuse by passing excessive size values and forcing the kernel into expensive allocation paths (via kmalloc_large or vmalloc). Fixes: 349271568303 ("bpf: Implement signature verification for BPF programs") Reported-by: Chris Mason Signed-off-by: KP Singh Acked-by: Daniel Borkmann Link: https://lore.kernel.org/r/20260205063807.690823-1-kpsingh@kernel.org Signed-off-by: Alexei Starovoitov Signed-off-by: Sasha Levin --- kernel/bpf/syscall.c | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/kernel/bpf/syscall.c b/kernel/bpf/syscall.c index f39367765f0c4f..2649e0472dfe04 100644 --- a/kernel/bpf/syscall.c +++ b/kernel/bpf/syscall.c @@ -2825,6 +2825,13 @@ static int bpf_prog_verify_signature(struct bpf_prog *prog, union bpf_attr *attr void *sig; int err = 0; + /* + * Don't attempt to use kmalloc_large or vmalloc for signatures. + * Practical signature for BPF program should be below this limit. + */ + if (attr->signature_size > KMALLOC_MAX_CACHE_SIZE) + return -EINVAL; + if (system_keyring_id_check(attr->keyring_id) == 0) key = bpf_lookup_system_key(attr->keyring_id); else From 7752d36343862323bbeea4ce3adf0ec2ed86e122 Mon Sep 17 00:00:00 2001 From: KP Singh Date: Thu, 5 Feb 2026 08:07:55 +0100 Subject: [PATCH 3370/3902] bpf: Require frozen map for calculating map hash [ Upstream commit a2c86aa621c22f2a7e26c654f936d65cfff0aa91 ] Currently, bpf_map_get_info_by_fd calculates and caches the hash of the map regardless of the map's frozen state. This leads to a TOCTOU bug where userspace can call BPF_OBJ_GET_INFO_BY_FD to cache the hash and then modify the map contents before freezing. Therefore, a trusted loader can be tricked into verifying the stale hash while loading the modified contents. Fix this by returning -EPERM if the map is not frozen when the hash is requested. This ensures the hash is only generated for the final, immutable state of the map. Fixes: ea2e6467ac36 ("bpf: Return hashes of maps in BPF_OBJ_GET_INFO_BY_FD") Reported-by: Toshi Piazza Signed-off-by: KP Singh Acked-by: Daniel Borkmann Link: https://lore.kernel.org/r/20260205070755.695776-1-kpsingh@kernel.org Signed-off-by: Alexei Starovoitov Signed-off-by: Sasha Levin --- kernel/bpf/syscall.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/kernel/bpf/syscall.c b/kernel/bpf/syscall.c index 2649e0472dfe04..586ece78f783ad 100644 --- a/kernel/bpf/syscall.c +++ b/kernel/bpf/syscall.c @@ -5303,6 +5303,9 @@ static int bpf_map_get_info_by_fd(struct file *file, if (info.hash_size != SHA256_DIGEST_SIZE) return -EINVAL; + if (!READ_ONCE(map->frozen)) + return -EPERM; + err = map->ops->map_get_hash(map, SHA256_DIGEST_SIZE, map->sha); if (err != 0) return err; From 4869d0e4e48a5301b267d359b2561c4080791a55 Mon Sep 17 00:00:00 2001 From: Zilin Guan Date: Thu, 29 Jan 2026 15:10:16 +0000 Subject: [PATCH 3371/3902] crypto: starfive - Fix memory leak in starfive_aes_aead_do_one_req() [ Upstream commit ccb679fdae2e62ed92fd9acb25ed809c0226fcc6 ] The starfive_aes_aead_do_one_req() function allocates rctx->adata with kzalloc() but fails to free it if sg_copy_to_buffer() or starfive_aes_hw_init() fails, which lead to memory leaks. Since rctx->adata is unconditionally freed after the write_adata operations, ensure consistent cleanup by freeing the allocation in these earlier error paths as well. Compile tested only. Issue found using a prototype static analysis tool and code review. Fixes: 7467147ef9bf ("crypto: starfive - Use dma for aes requests") Signed-off-by: Zilin Guan Signed-off-by: Herbert Xu Signed-off-by: Sasha Levin --- drivers/crypto/starfive/jh7110-aes.c | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/drivers/crypto/starfive/jh7110-aes.c b/drivers/crypto/starfive/jh7110-aes.c index 426b24889af853..01195664cc7cd9 100644 --- a/drivers/crypto/starfive/jh7110-aes.c +++ b/drivers/crypto/starfive/jh7110-aes.c @@ -669,8 +669,10 @@ static int starfive_aes_aead_do_one_req(struct crypto_engine *engine, void *areq return -ENOMEM; if (sg_copy_to_buffer(req->src, sg_nents_for_len(req->src, cryp->assoclen), - rctx->adata, cryp->assoclen) != cryp->assoclen) + rctx->adata, cryp->assoclen) != cryp->assoclen) { + kfree(rctx->adata); return -EINVAL; + } } if (cryp->total_in) @@ -681,8 +683,11 @@ static int starfive_aes_aead_do_one_req(struct crypto_engine *engine, void *areq ctx->rctx = rctx; ret = starfive_aes_hw_init(ctx); - if (ret) + if (ret) { + if (cryp->assoclen) + kfree(rctx->adata); return ret; + } if (!cryp->assoclen) goto write_text; From 9095671fa24044500d21f9f108b942894cc0cc42 Mon Sep 17 00:00:00 2001 From: Jonathan McDowell Date: Tue, 23 Sep 2025 14:33:05 +0100 Subject: [PATCH 3372/3902] hwrng: core - Allow runtime disabling of the HW RNG [ Upstream commit e74b96d77da9eb5ee1b603c937c2adab5134a04b ] The HW RNG core allows for manual selection of which RNG device to use, but does not allow for no device to be enabled. It may be desirable to do this on systems with only a single suitable hardware RNG, where we need exclusive access to other functionality on this device. In particular when performing TPM firmware upgrades this lets us ensure the kernel does not try to access the device. Before: root@debian-qemu-efi:~# grep "" /sys/devices/virtual/misc/hw_random/rng_* /sys/devices/virtual/misc/hw_random/rng_available:tpm-rng-0 /sys/devices/virtual/misc/hw_random/rng_current:tpm-rng-0 /sys/devices/virtual/misc/hw_random/rng_quality:1024 /sys/devices/virtual/misc/hw_random/rng_selected:0 After: root@debian-qemu-efi:~# grep "" /sys/devices/virtual/misc/hw_random/rng_* /sys/devices/virtual/misc/hw_random/rng_available:tpm-rng-0 none /sys/devices/virtual/misc/hw_random/rng_current:tpm-rng-0 /sys/devices/virtual/misc/hw_random/rng_quality:1024 /sys/devices/virtual/misc/hw_random/rng_selected:0 root@debian-qemu-efi:~# echo none > /sys/devices/virtual/misc/hw_random/rng_current root@debian-qemu-efi:~# grep "" /sys/devices/virtual/misc/hw_random/rng_* /sys/devices/virtual/misc/hw_random/rng_available:tpm-rng-0 none /sys/devices/virtual/misc/hw_random/rng_current:none grep: /sys/devices/virtual/misc/hw_random/rng_quality: No such device /sys/devices/virtual/misc/hw_random/rng_selected:1 (Observe using bpftrace no calls to TPM being made) root@debian-qemu-efi:~# echo "" > /sys/devices/virtual/misc/hw_random/rng_current root@debian-qemu-efi:~# grep "" /sys/devices/virtual/misc/hw_random/rng_* /sys/devices/virtual/misc/hw_random/rng_available:tpm-rng-0 none /sys/devices/virtual/misc/hw_random/rng_current:tpm-rng-0 /sys/devices/virtual/misc/hw_random/rng_quality:1024 /sys/devices/virtual/misc/hw_random/rng_selected:0 (Observe using bpftrace that calls to the TPM resume) Signed-off-by: Jonathan McDowell Signed-off-by: Herbert Xu Stable-dep-of: cc2f39d6ac48 ("hwrng: core - use RCU and work_struct to fix race condition") Signed-off-by: Sasha Levin --- drivers/char/hw_random/core.c | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/drivers/char/hw_random/core.c b/drivers/char/hw_random/core.c index 018316f546215a..56d888bebe0c08 100644 --- a/drivers/char/hw_random/core.c +++ b/drivers/char/hw_random/core.c @@ -341,6 +341,9 @@ static ssize_t rng_current_store(struct device *dev, if (sysfs_streq(buf, "")) { err = enable_best_rng(); + } else if (sysfs_streq(buf, "none")) { + cur_rng_set_by_user = 1; + drop_current_rng(); } else { list_for_each_entry(rng, &rng_list, list) { if (sysfs_streq(rng->name, buf)) { @@ -392,7 +395,7 @@ static ssize_t rng_available_show(struct device *dev, strlcat(buf, rng->name, PAGE_SIZE); strlcat(buf, " ", PAGE_SIZE); } - strlcat(buf, "\n", PAGE_SIZE); + strlcat(buf, "none\n", PAGE_SIZE); mutex_unlock(&rng_mutex); return strlen(buf); @@ -544,8 +547,8 @@ int hwrng_register(struct hwrng *rng) /* Adjust quality field to always have a proper value */ rng->quality = min_t(u16, min_t(u16, default_quality, 1024), rng->quality ?: 1024); - if (!current_rng || - (!cur_rng_set_by_user && rng->quality > current_rng->quality)) { + if (!cur_rng_set_by_user && + (!current_rng || rng->quality > current_rng->quality)) { /* * Set new rng as current as the new rng source * provides better entropy quality and was not From dcf416eb88eafe1e3c0f920a14bdffd10bc4d259 Mon Sep 17 00:00:00 2001 From: Lianjie Wang Date: Fri, 30 Jan 2026 06:50:16 +0900 Subject: [PATCH 3373/3902] hwrng: core - use RCU and work_struct to fix race condition [ Upstream commit cc2f39d6ac48e6e3cb2d6240bc0d6df839dd0828 ] Currently, hwrng_fill is not cleared until the hwrng_fillfn() thread exits. Since hwrng_unregister() reads hwrng_fill outside the rng_mutex lock, a concurrent hwrng_unregister() may call kthread_stop() again on the same task. Additionally, if hwrng_unregister() is called immediately after hwrng_register(), the stopped thread may have never been executed. Thus, hwrng_fill remains dirty even after hwrng_unregister() returns. In this case, subsequent calls to hwrng_register() will fail to start new threads, and hwrng_unregister() will call kthread_stop() on the same freed task. In both cases, a use-after-free occurs: refcount_t: addition on 0; use-after-free. WARNING: ... at lib/refcount.c:25 refcount_warn_saturate+0xec/0x1c0 Call Trace: kthread_stop+0x181/0x360 hwrng_unregister+0x288/0x380 virtrng_remove+0xe3/0x200 This patch fixes the race by protecting the global hwrng_fill pointer inside the rng_mutex lock, so that hwrng_fillfn() thread is stopped only once, and calls to kthread_run() and kthread_stop() are serialized with the lock held. To avoid deadlock in hwrng_fillfn() while being stopped with the lock held, we convert current_rng to RCU, so that get_current_rng() can read current_rng without holding the lock. To remove the lock from put_rng(), we also delay the actual cleanup into a work_struct. Since get_current_rng() no longer returns ERR_PTR values, the IS_ERR() checks are removed from its callers. With hwrng_fill protected by the rng_mutex lock, hwrng_fillfn() can no longer clear hwrng_fill itself. Therefore, if hwrng_fillfn() returns directly after current_rng is dropped, kthread_stop() would be called on a freed task_struct later. To fix this, hwrng_fillfn() calls schedule() now to keep the task alive until being stopped. The kthread_stop() call is also moved from hwrng_unregister() to drop_current_rng(), ensuring kthread_stop() is called on all possible paths where current_rng becomes NULL, so that the thread would not wait forever. Fixes: be4000bc4644 ("hwrng: create filler thread") Suggested-by: Herbert Xu Signed-off-by: Lianjie Wang Signed-off-by: Herbert Xu Signed-off-by: Sasha Levin --- drivers/char/hw_random/core.c | 168 +++++++++++++++++++++------------- include/linux/hw_random.h | 2 + 2 files changed, 107 insertions(+), 63 deletions(-) diff --git a/drivers/char/hw_random/core.c b/drivers/char/hw_random/core.c index 56d888bebe0c08..036de7294bbdab 100644 --- a/drivers/char/hw_random/core.c +++ b/drivers/char/hw_random/core.c @@ -20,23 +20,25 @@ #include #include #include +#include #include #include #include #include #include +#include #define RNG_MODULE_NAME "hw_random" #define RNG_BUFFER_SIZE (SMP_CACHE_BYTES < 32 ? 32 : SMP_CACHE_BYTES) -static struct hwrng *current_rng; +static struct hwrng __rcu *current_rng; /* the current rng has been explicitly chosen by user via sysfs */ static int cur_rng_set_by_user; static struct task_struct *hwrng_fill; /* list of registered rngs */ static LIST_HEAD(rng_list); -/* Protects rng_list and current_rng */ +/* Protects rng_list, hwrng_fill and updating on current_rng */ static DEFINE_MUTEX(rng_mutex); /* Protects rng read functions, data_avail, rng_buffer and rng_fillbuf */ static DEFINE_MUTEX(reading_mutex); @@ -64,18 +66,39 @@ static size_t rng_buffer_size(void) return RNG_BUFFER_SIZE; } -static inline void cleanup_rng(struct kref *kref) +static void cleanup_rng_work(struct work_struct *work) { - struct hwrng *rng = container_of(kref, struct hwrng, ref); + struct hwrng *rng = container_of(work, struct hwrng, cleanup_work); + + /* + * Hold rng_mutex here so we serialize in case they set_current_rng + * on rng again immediately. + */ + mutex_lock(&rng_mutex); + + /* Skip if rng has been reinitialized. */ + if (kref_read(&rng->ref)) { + mutex_unlock(&rng_mutex); + return; + } if (rng->cleanup) rng->cleanup(rng); complete(&rng->cleanup_done); + mutex_unlock(&rng_mutex); +} + +static inline void cleanup_rng(struct kref *kref) +{ + struct hwrng *rng = container_of(kref, struct hwrng, ref); + + schedule_work(&rng->cleanup_work); } static int set_current_rng(struct hwrng *rng) { + struct hwrng *old_rng; int err; BUG_ON(!mutex_is_locked(&rng_mutex)); @@ -84,8 +107,14 @@ static int set_current_rng(struct hwrng *rng) if (err) return err; - drop_current_rng(); - current_rng = rng; + old_rng = rcu_dereference_protected(current_rng, + lockdep_is_held(&rng_mutex)); + rcu_assign_pointer(current_rng, rng); + + if (old_rng) { + synchronize_rcu(); + kref_put(&old_rng->ref, cleanup_rng); + } /* if necessary, start hwrng thread */ if (!hwrng_fill) { @@ -101,47 +130,56 @@ static int set_current_rng(struct hwrng *rng) static void drop_current_rng(void) { - BUG_ON(!mutex_is_locked(&rng_mutex)); - if (!current_rng) + struct hwrng *rng; + + rng = rcu_dereference_protected(current_rng, + lockdep_is_held(&rng_mutex)); + if (!rng) return; + RCU_INIT_POINTER(current_rng, NULL); + synchronize_rcu(); + + if (hwrng_fill) { + kthread_stop(hwrng_fill); + hwrng_fill = NULL; + } + /* decrease last reference for triggering the cleanup */ - kref_put(¤t_rng->ref, cleanup_rng); - current_rng = NULL; + kref_put(&rng->ref, cleanup_rng); } -/* Returns ERR_PTR(), NULL or refcounted hwrng */ +/* Returns NULL or refcounted hwrng */ static struct hwrng *get_current_rng_nolock(void) { - if (current_rng) - kref_get(¤t_rng->ref); + struct hwrng *rng; + + rng = rcu_dereference_protected(current_rng, + lockdep_is_held(&rng_mutex)); + if (rng) + kref_get(&rng->ref); - return current_rng; + return rng; } static struct hwrng *get_current_rng(void) { struct hwrng *rng; - if (mutex_lock_interruptible(&rng_mutex)) - return ERR_PTR(-ERESTARTSYS); + rcu_read_lock(); + rng = rcu_dereference(current_rng); + if (rng) + kref_get(&rng->ref); - rng = get_current_rng_nolock(); + rcu_read_unlock(); - mutex_unlock(&rng_mutex); return rng; } static void put_rng(struct hwrng *rng) { - /* - * Hold rng_mutex here so we serialize in case they set_current_rng - * on rng again immediately. - */ - mutex_lock(&rng_mutex); if (rng) kref_put(&rng->ref, cleanup_rng); - mutex_unlock(&rng_mutex); } static int hwrng_init(struct hwrng *rng) @@ -213,10 +251,6 @@ static ssize_t rng_dev_read(struct file *filp, char __user *buf, while (size) { rng = get_current_rng(); - if (IS_ERR(rng)) { - err = PTR_ERR(rng); - goto out; - } if (!rng) { err = -ENODEV; goto out; @@ -303,7 +337,7 @@ static struct miscdevice rng_miscdev = { static int enable_best_rng(void) { - struct hwrng *rng, *new_rng = NULL; + struct hwrng *rng, *cur_rng, *new_rng = NULL; int ret = -ENODEV; BUG_ON(!mutex_is_locked(&rng_mutex)); @@ -321,7 +355,9 @@ static int enable_best_rng(void) new_rng = rng; } - ret = ((new_rng == current_rng) ? 0 : set_current_rng(new_rng)); + cur_rng = rcu_dereference_protected(current_rng, + lockdep_is_held(&rng_mutex)); + ret = ((new_rng == cur_rng) ? 0 : set_current_rng(new_rng)); if (!ret) cur_rng_set_by_user = 0; @@ -371,8 +407,6 @@ static ssize_t rng_current_show(struct device *dev, struct hwrng *rng; rng = get_current_rng(); - if (IS_ERR(rng)) - return PTR_ERR(rng); ret = sysfs_emit(buf, "%s\n", rng ? rng->name : "none"); put_rng(rng); @@ -416,8 +450,6 @@ static ssize_t rng_quality_show(struct device *dev, struct hwrng *rng; rng = get_current_rng(); - if (IS_ERR(rng)) - return PTR_ERR(rng); if (!rng) /* no need to put_rng */ return -ENODEV; @@ -432,6 +464,7 @@ static ssize_t rng_quality_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t len) { + struct hwrng *rng; u16 quality; int ret = -EINVAL; @@ -448,12 +481,13 @@ static ssize_t rng_quality_store(struct device *dev, goto out; } - if (!current_rng) { + rng = rcu_dereference_protected(current_rng, lockdep_is_held(&rng_mutex)); + if (!rng) { ret = -ENODEV; goto out; } - current_rng->quality = quality; + rng->quality = quality; current_quality = quality; /* obsolete */ /* the best available RNG may have changed */ @@ -489,8 +523,20 @@ static int hwrng_fillfn(void *unused) struct hwrng *rng; rng = get_current_rng(); - if (IS_ERR(rng) || !rng) + if (!rng) { + /* + * Keep the task_struct alive until kthread_stop() + * is called to avoid UAF in drop_current_rng(). + */ + while (!kthread_should_stop()) { + set_current_state(TASK_INTERRUPTIBLE); + if (!kthread_should_stop()) + schedule(); + } + set_current_state(TASK_RUNNING); break; + } + mutex_lock(&reading_mutex); rc = rng_get_data(rng, rng_fillbuf, rng_buffer_size(), 1); @@ -518,14 +564,13 @@ static int hwrng_fillfn(void *unused) add_hwgenerator_randomness((void *)rng_fillbuf, rc, entropy >> 10, true); } - hwrng_fill = NULL; return 0; } int hwrng_register(struct hwrng *rng) { int err = -EINVAL; - struct hwrng *tmp; + struct hwrng *cur_rng, *tmp; if (!rng->name || (!rng->data_read && !rng->read)) goto out; @@ -540,6 +585,7 @@ int hwrng_register(struct hwrng *rng) } list_add_tail(&rng->list, &rng_list); + INIT_WORK(&rng->cleanup_work, cleanup_rng_work); init_completion(&rng->cleanup_done); complete(&rng->cleanup_done); init_completion(&rng->dying); @@ -547,16 +593,19 @@ int hwrng_register(struct hwrng *rng) /* Adjust quality field to always have a proper value */ rng->quality = min_t(u16, min_t(u16, default_quality, 1024), rng->quality ?: 1024); - if (!cur_rng_set_by_user && - (!current_rng || rng->quality > current_rng->quality)) { - /* - * Set new rng as current as the new rng source - * provides better entropy quality and was not - * chosen by userspace. - */ - err = set_current_rng(rng); - if (err) - goto out_unlock; + if (!cur_rng_set_by_user) { + cur_rng = rcu_dereference_protected(current_rng, + lockdep_is_held(&rng_mutex)); + if (!cur_rng || rng->quality > cur_rng->quality) { + /* + * Set new rng as current as the new rng source + * provides better entropy quality and was not + * chosen by userspace. + */ + err = set_current_rng(rng); + if (err) + goto out_unlock; + } } mutex_unlock(&rng_mutex); return 0; @@ -569,14 +618,17 @@ EXPORT_SYMBOL_GPL(hwrng_register); void hwrng_unregister(struct hwrng *rng) { - struct hwrng *new_rng; + struct hwrng *cur_rng; int err; mutex_lock(&rng_mutex); list_del(&rng->list); complete_all(&rng->dying); - if (current_rng == rng) { + + cur_rng = rcu_dereference_protected(current_rng, + lockdep_is_held(&rng_mutex)); + if (cur_rng == rng) { err = enable_best_rng(); if (err) { drop_current_rng(); @@ -584,17 +636,7 @@ void hwrng_unregister(struct hwrng *rng) } } - new_rng = get_current_rng_nolock(); - if (list_empty(&rng_list)) { - mutex_unlock(&rng_mutex); - if (hwrng_fill) - kthread_stop(hwrng_fill); - } else - mutex_unlock(&rng_mutex); - - if (new_rng) - put_rng(new_rng); - + mutex_unlock(&rng_mutex); wait_for_completion(&rng->cleanup_done); } EXPORT_SYMBOL_GPL(hwrng_unregister); @@ -682,7 +724,7 @@ static int __init hwrng_modinit(void) static void __exit hwrng_modexit(void) { mutex_lock(&rng_mutex); - BUG_ON(current_rng); + WARN_ON(rcu_access_pointer(current_rng)); kfree(rng_buffer); kfree(rng_fillbuf); mutex_unlock(&rng_mutex); diff --git a/include/linux/hw_random.h b/include/linux/hw_random.h index b424555753b11f..b77bc55a4cf356 100644 --- a/include/linux/hw_random.h +++ b/include/linux/hw_random.h @@ -15,6 +15,7 @@ #include #include #include +#include /** * struct hwrng - Hardware Random Number Generator driver @@ -48,6 +49,7 @@ struct hwrng { /* internal. */ struct list_head list; struct kref ref; + struct work_struct cleanup_work; struct completion cleanup_done; struct completion dying; }; From 8b1a095541c138003effc7905bea323fce9e5bd4 Mon Sep 17 00:00:00 2001 From: "Bastien Curutchet (eBPF Foundation)" Date: Fri, 31 Oct 2025 09:04:37 +0100 Subject: [PATCH 3374/3902] selftests/bpf: test_xsk: Split xskxceiver [ Upstream commit 3ab77f35a75eb236956c1e8ba8494ef18a75eae0 ] AF_XDP features are tested by the test_xsk.sh script but not by the test_progs framework. The tests used by the script are defined in xksxceiver.c which can't be integrated in the test_progs framework as is. Extract these test definitions from xskxceiver{.c/.h} to put them in new test_xsk{.c/.h} files. Keep the main() function and its unshared dependencies in xksxceiver to avoid impacting the test_xsk.sh script which is often used to test real hardware. Move ksft_test_result_*() calls to xskxceiver.c to keep the kselftest's report valid Reviewed-by: Maciej Fijalkowski Signed-off-by: Bastien Curutchet (eBPF Foundation) Link: https://lore.kernel.org/r/20251031-xsk-v7-1-39fe486593a3@bootlin.com Signed-off-by: Alexei Starovoitov Stable-dep-of: 42e41b2a0afa ("selftests/xsk: properly handle batch ending in the middle of a packet") Signed-off-by: Sasha Levin --- tools/testing/selftests/bpf/Makefile | 2 +- tools/testing/selftests/bpf/test_xsk.c | 2420 ++++++++++++++++++++ tools/testing/selftests/bpf/test_xsk.h | 297 +++ tools/testing/selftests/bpf/xskxceiver.c | 2545 +--------------------- tools/testing/selftests/bpf/xskxceiver.h | 156 -- 5 files changed, 2762 insertions(+), 2658 deletions(-) create mode 100644 tools/testing/selftests/bpf/test_xsk.c create mode 100644 tools/testing/selftests/bpf/test_xsk.h diff --git a/tools/testing/selftests/bpf/Makefile b/tools/testing/selftests/bpf/Makefile index e59b2bbf8d9204..591e7e77f89ba5 100644 --- a/tools/testing/selftests/bpf/Makefile +++ b/tools/testing/selftests/bpf/Makefile @@ -805,7 +805,7 @@ $(OUTPUT)/test_verifier: test_verifier.c verifier/tests.h $(BPFOBJ) | $(OUTPUT) # Include find_bit.c to compile xskxceiver. EXTRA_SRC := $(TOOLSDIR)/lib/find_bit.c -$(OUTPUT)/xskxceiver: $(EXTRA_SRC) xskxceiver.c xskxceiver.h $(OUTPUT)/network_helpers.o $(OUTPUT)/xsk.o $(OUTPUT)/xsk_xdp_progs.skel.h $(BPFOBJ) | $(OUTPUT) +$(OUTPUT)/xskxceiver: $(EXTRA_SRC) test_xsk.c test_xsk.h xskxceiver.c xskxceiver.h $(OUTPUT)/network_helpers.o $(OUTPUT)/xsk.o $(OUTPUT)/xsk_xdp_progs.skel.h $(BPFOBJ) | $(OUTPUT) $(call msg,BINARY,,$@) $(Q)$(CC) $(CFLAGS) $(filter %.a %.o %.c,$^) $(LDLIBS) -o $@ diff --git a/tools/testing/selftests/bpf/test_xsk.c b/tools/testing/selftests/bpf/test_xsk.c new file mode 100644 index 00000000000000..02250f29f9946d --- /dev/null +++ b/tools/testing/selftests/bpf/test_xsk.c @@ -0,0 +1,2420 @@ +// SPDX-License-Identifier: GPL-2.0 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "network_helpers.h" +#include "test_xsk.h" +#include "xsk_xdp_common.h" +#include "xsk_xdp_progs.skel.h" + +#define DEFAULT_BATCH_SIZE 64 +#define MIN_PKT_SIZE 64 +#define MAX_ETH_JUMBO_SIZE 9000 +#define MAX_INTERFACES 2 +#define MAX_TEARDOWN_ITER 10 +#define MAX_TX_BUDGET_DEFAULT 32 +#define PKT_DUMP_NB_TO_PRINT 16 +/* Just to align the data in the packet */ +#define PKT_HDR_SIZE (sizeof(struct ethhdr) + 2) +#define POLL_TMOUT 1000 +#define THREAD_TMOUT 3 +#define UMEM_HEADROOM_TEST_SIZE 128 +#define XSK_DESC__INVALID_OPTION (0xffff) +#define XSK_UMEM__INVALID_FRAME_SIZE (MAX_ETH_JUMBO_SIZE + 1) +#define XSK_UMEM__LARGE_FRAME_SIZE (3 * 1024) +#define XSK_UMEM__MAX_FRAME_SIZE (4 * 1024) + +static const u8 g_mac[ETH_ALEN] = {0x55, 0x44, 0x33, 0x22, 0x11, 0x00}; + +bool opt_verbose; +pthread_barrier_t barr; +pthread_mutex_t pacing_mutex = PTHREAD_MUTEX_INITIALIZER; + +int pkts_in_flight; + +/* The payload is a word consisting of a packet sequence number in the upper + * 16-bits and a intra packet data sequence number in the lower 16 bits. So the 3rd packet's + * 5th word of data will contain the number (2<<16) | 4 as they are numbered from 0. + */ +static void write_payload(void *dest, u32 pkt_nb, u32 start, u32 size) +{ + u32 *ptr = (u32 *)dest, i; + + start /= sizeof(*ptr); + size /= sizeof(*ptr); + for (i = 0; i < size; i++) + ptr[i] = htonl(pkt_nb << 16 | (i + start)); +} + +static void gen_eth_hdr(struct xsk_socket_info *xsk, struct ethhdr *eth_hdr) +{ + memcpy(eth_hdr->h_dest, xsk->dst_mac, ETH_ALEN); + memcpy(eth_hdr->h_source, xsk->src_mac, ETH_ALEN); + eth_hdr->h_proto = htons(ETH_P_LOOPBACK); +} + +static bool is_umem_valid(struct ifobject *ifobj) +{ + return !!ifobj->umem->umem; +} + +static u32 mode_to_xdp_flags(enum test_mode mode) +{ + return (mode == TEST_MODE_SKB) ? XDP_FLAGS_SKB_MODE : XDP_FLAGS_DRV_MODE; +} + +static u64 umem_size(struct xsk_umem_info *umem) +{ + return umem->num_frames * umem->frame_size; +} + +int xsk_configure_umem(struct ifobject *ifobj, struct xsk_umem_info *umem, void *buffer, + u64 size) +{ + struct xsk_umem_config cfg = { + .fill_size = XSK_RING_PROD__DEFAULT_NUM_DESCS, + .comp_size = XSK_RING_CONS__DEFAULT_NUM_DESCS, + .frame_size = umem->frame_size, + .frame_headroom = umem->frame_headroom, + .flags = XSK_UMEM__DEFAULT_FLAGS + }; + int ret; + + if (umem->fill_size) + cfg.fill_size = umem->fill_size; + + if (umem->comp_size) + cfg.comp_size = umem->comp_size; + + if (umem->unaligned_mode) + cfg.flags |= XDP_UMEM_UNALIGNED_CHUNK_FLAG; + + ret = xsk_umem__create(&umem->umem, buffer, size, + &umem->fq, &umem->cq, &cfg); + if (ret) + return ret; + + umem->buffer = buffer; + if (ifobj->shared_umem && ifobj->rx_on) { + umem->base_addr = umem_size(umem); + umem->next_buffer = umem_size(umem); + } + + return 0; +} + +static u64 umem_alloc_buffer(struct xsk_umem_info *umem) +{ + u64 addr; + + addr = umem->next_buffer; + umem->next_buffer += umem->frame_size; + if (umem->next_buffer >= umem->base_addr + umem_size(umem)) + umem->next_buffer = umem->base_addr; + + return addr; +} + +static void umem_reset_alloc(struct xsk_umem_info *umem) +{ + umem->next_buffer = 0; +} + +static void enable_busy_poll(struct xsk_socket_info *xsk) +{ + int sock_opt; + + sock_opt = 1; + if (setsockopt(xsk_socket__fd(xsk->xsk), SOL_SOCKET, SO_PREFER_BUSY_POLL, + (void *)&sock_opt, sizeof(sock_opt)) < 0) + exit_with_error(errno); + + sock_opt = 20; + if (setsockopt(xsk_socket__fd(xsk->xsk), SOL_SOCKET, SO_BUSY_POLL, + (void *)&sock_opt, sizeof(sock_opt)) < 0) + exit_with_error(errno); + + sock_opt = xsk->batch_size; + if (setsockopt(xsk_socket__fd(xsk->xsk), SOL_SOCKET, SO_BUSY_POLL_BUDGET, + (void *)&sock_opt, sizeof(sock_opt)) < 0) + exit_with_error(errno); +} + +int xsk_configure_socket(struct xsk_socket_info *xsk, struct xsk_umem_info *umem, + struct ifobject *ifobject, bool shared) +{ + struct xsk_socket_config cfg = {}; + struct xsk_ring_cons *rxr; + struct xsk_ring_prod *txr; + + xsk->umem = umem; + cfg.rx_size = xsk->rxqsize; + cfg.tx_size = XSK_RING_PROD__DEFAULT_NUM_DESCS; + cfg.bind_flags = ifobject->bind_flags; + if (shared) + cfg.bind_flags |= XDP_SHARED_UMEM; + if (ifobject->mtu > MAX_ETH_PKT_SIZE) + cfg.bind_flags |= XDP_USE_SG; + if (umem->comp_size) + cfg.tx_size = umem->comp_size; + if (umem->fill_size) + cfg.rx_size = umem->fill_size; + + txr = ifobject->tx_on ? &xsk->tx : NULL; + rxr = ifobject->rx_on ? &xsk->rx : NULL; + return xsk_socket__create(&xsk->xsk, ifobject->ifindex, 0, umem->umem, rxr, txr, &cfg); +} + +#define MAX_SKB_FRAGS_PATH "/proc/sys/net/core/max_skb_frags" +static unsigned int get_max_skb_frags(void) +{ + unsigned int max_skb_frags = 0; + FILE *file; + + file = fopen(MAX_SKB_FRAGS_PATH, "r"); + if (!file) { + ksft_print_msg("Error opening %s\n", MAX_SKB_FRAGS_PATH); + return 0; + } + + if (fscanf(file, "%u", &max_skb_frags) != 1) + ksft_print_msg("Error reading %s\n", MAX_SKB_FRAGS_PATH); + + fclose(file); + return max_skb_frags; +} + +static int set_ring_size(struct ifobject *ifobj) +{ + int ret; + u32 ctr = 0; + + while (ctr++ < SOCK_RECONF_CTR) { + ret = set_hw_ring_size(ifobj->ifname, &ifobj->ring); + if (!ret) + break; + + /* Retry if it fails */ + if (ctr >= SOCK_RECONF_CTR || errno != EBUSY) + return -errno; + + usleep(USLEEP_MAX); + } + + return ret; +} + +int hw_ring_size_reset(struct ifobject *ifobj) +{ + ifobj->ring.tx_pending = ifobj->set_ring.default_tx; + ifobj->ring.rx_pending = ifobj->set_ring.default_rx; + return set_ring_size(ifobj); +} + +static void __test_spec_init(struct test_spec *test, struct ifobject *ifobj_tx, + struct ifobject *ifobj_rx) +{ + u32 i, j; + + for (i = 0; i < MAX_INTERFACES; i++) { + struct ifobject *ifobj = i ? ifobj_rx : ifobj_tx; + + ifobj->xsk = &ifobj->xsk_arr[0]; + ifobj->use_poll = false; + ifobj->use_fill_ring = true; + ifobj->release_rx = true; + ifobj->validation_func = NULL; + ifobj->use_metadata = false; + + if (i == 0) { + ifobj->rx_on = false; + ifobj->tx_on = true; + } else { + ifobj->rx_on = true; + ifobj->tx_on = false; + } + + memset(ifobj->umem, 0, sizeof(*ifobj->umem)); + ifobj->umem->num_frames = DEFAULT_UMEM_BUFFERS; + ifobj->umem->frame_size = XSK_UMEM__DEFAULT_FRAME_SIZE; + + for (j = 0; j < MAX_SOCKETS; j++) { + memset(&ifobj->xsk_arr[j], 0, sizeof(ifobj->xsk_arr[j])); + ifobj->xsk_arr[j].rxqsize = XSK_RING_CONS__DEFAULT_NUM_DESCS; + ifobj->xsk_arr[j].batch_size = DEFAULT_BATCH_SIZE; + if (i == 0) + ifobj->xsk_arr[j].pkt_stream = test->tx_pkt_stream_default; + else + ifobj->xsk_arr[j].pkt_stream = test->rx_pkt_stream_default; + + memcpy(ifobj->xsk_arr[j].src_mac, g_mac, ETH_ALEN); + memcpy(ifobj->xsk_arr[j].dst_mac, g_mac, ETH_ALEN); + ifobj->xsk_arr[j].src_mac[5] += ((j * 2) + 0); + ifobj->xsk_arr[j].dst_mac[5] += ((j * 2) + 1); + } + } + + if (ifobj_tx->hw_ring_size_supp) + hw_ring_size_reset(ifobj_tx); + + test->ifobj_tx = ifobj_tx; + test->ifobj_rx = ifobj_rx; + test->current_step = 0; + test->total_steps = 1; + test->nb_sockets = 1; + test->fail = false; + test->set_ring = false; + test->adjust_tail = false; + test->adjust_tail_support = false; + test->mtu = MAX_ETH_PKT_SIZE; + test->xdp_prog_rx = ifobj_rx->xdp_progs->progs.xsk_def_prog; + test->xskmap_rx = ifobj_rx->xdp_progs->maps.xsk; + test->xdp_prog_tx = ifobj_tx->xdp_progs->progs.xsk_def_prog; + test->xskmap_tx = ifobj_tx->xdp_progs->maps.xsk; +} + +void test_init(struct test_spec *test, struct ifobject *ifobj_tx, + struct ifobject *ifobj_rx, enum test_mode mode, + const struct test_spec *test_to_run) +{ + struct pkt_stream *tx_pkt_stream; + struct pkt_stream *rx_pkt_stream; + u32 i; + + tx_pkt_stream = test->tx_pkt_stream_default; + rx_pkt_stream = test->rx_pkt_stream_default; + memset(test, 0, sizeof(*test)); + test->tx_pkt_stream_default = tx_pkt_stream; + test->rx_pkt_stream_default = rx_pkt_stream; + + for (i = 0; i < MAX_INTERFACES; i++) { + struct ifobject *ifobj = i ? ifobj_rx : ifobj_tx; + + ifobj->bind_flags = XDP_USE_NEED_WAKEUP; + if (mode == TEST_MODE_ZC) + ifobj->bind_flags |= XDP_ZEROCOPY; + else + ifobj->bind_flags |= XDP_COPY; + } + + memcpy(test->name, test_to_run->name, MAX_TEST_NAME_SIZE); + test->test_func = test_to_run->test_func; + test->mode = mode; + __test_spec_init(test, ifobj_tx, ifobj_rx); +} + +static void test_spec_reset(struct test_spec *test) +{ + __test_spec_init(test, test->ifobj_tx, test->ifobj_rx); +} + +static void test_spec_set_xdp_prog(struct test_spec *test, struct bpf_program *xdp_prog_rx, + struct bpf_program *xdp_prog_tx, struct bpf_map *xskmap_rx, + struct bpf_map *xskmap_tx) +{ + test->xdp_prog_rx = xdp_prog_rx; + test->xdp_prog_tx = xdp_prog_tx; + test->xskmap_rx = xskmap_rx; + test->xskmap_tx = xskmap_tx; +} + +static int test_spec_set_mtu(struct test_spec *test, int mtu) +{ + int err; + + if (test->ifobj_rx->mtu != mtu) { + err = xsk_set_mtu(test->ifobj_rx->ifindex, mtu); + if (err) + return err; + test->ifobj_rx->mtu = mtu; + } + if (test->ifobj_tx->mtu != mtu) { + err = xsk_set_mtu(test->ifobj_tx->ifindex, mtu); + if (err) + return err; + test->ifobj_tx->mtu = mtu; + } + + return 0; +} + +void pkt_stream_reset(struct pkt_stream *pkt_stream) +{ + if (pkt_stream) { + pkt_stream->current_pkt_nb = 0; + pkt_stream->nb_rx_pkts = 0; + } +} + +static struct pkt *pkt_stream_get_next_tx_pkt(struct pkt_stream *pkt_stream) +{ + if (pkt_stream->current_pkt_nb >= pkt_stream->nb_pkts) + return NULL; + + return &pkt_stream->pkts[pkt_stream->current_pkt_nb++]; +} + +static struct pkt *pkt_stream_get_next_rx_pkt(struct pkt_stream *pkt_stream, u32 *pkts_sent) +{ + while (pkt_stream->current_pkt_nb < pkt_stream->nb_pkts) { + (*pkts_sent)++; + if (pkt_stream->pkts[pkt_stream->current_pkt_nb].valid) + return &pkt_stream->pkts[pkt_stream->current_pkt_nb++]; + pkt_stream->current_pkt_nb++; + } + return NULL; +} + +void pkt_stream_delete(struct pkt_stream *pkt_stream) +{ + free(pkt_stream->pkts); + free(pkt_stream); +} + +void pkt_stream_restore_default(struct test_spec *test) +{ + struct pkt_stream *tx_pkt_stream = test->ifobj_tx->xsk->pkt_stream; + struct pkt_stream *rx_pkt_stream = test->ifobj_rx->xsk->pkt_stream; + + if (tx_pkt_stream != test->tx_pkt_stream_default) { + pkt_stream_delete(test->ifobj_tx->xsk->pkt_stream); + test->ifobj_tx->xsk->pkt_stream = test->tx_pkt_stream_default; + } + + if (rx_pkt_stream != test->rx_pkt_stream_default) { + pkt_stream_delete(test->ifobj_rx->xsk->pkt_stream); + test->ifobj_rx->xsk->pkt_stream = test->rx_pkt_stream_default; + } +} + +static struct pkt_stream *__pkt_stream_alloc(u32 nb_pkts) +{ + struct pkt_stream *pkt_stream; + + pkt_stream = calloc(1, sizeof(*pkt_stream)); + if (!pkt_stream) + return NULL; + + pkt_stream->pkts = calloc(nb_pkts, sizeof(*pkt_stream->pkts)); + if (!pkt_stream->pkts) { + free(pkt_stream); + return NULL; + } + + pkt_stream->nb_pkts = nb_pkts; + return pkt_stream; +} + +static u32 pkt_nb_frags(u32 frame_size, struct pkt_stream *pkt_stream, struct pkt *pkt) +{ + u32 nb_frags = 1, next_frag; + + if (!pkt) + return 1; + + if (!pkt_stream->verbatim) { + if (!pkt->valid || !pkt->len) + return 1; + return ceil_u32(pkt->len, frame_size); + } + + /* Search for the end of the packet in verbatim mode */ + if (!pkt_continues(pkt->options)) + return nb_frags; + + next_frag = pkt_stream->current_pkt_nb; + pkt++; + while (next_frag++ < pkt_stream->nb_pkts) { + nb_frags++; + if (!pkt_continues(pkt->options) || !pkt->valid) + break; + pkt++; + } + return nb_frags; +} + +static bool set_pkt_valid(int offset, u32 len) +{ + return len <= MAX_ETH_JUMBO_SIZE; +} + +static void pkt_set(struct pkt_stream *pkt_stream, struct pkt *pkt, int offset, u32 len) +{ + pkt->offset = offset; + pkt->len = len; + pkt->valid = set_pkt_valid(offset, len); +} + +static void pkt_stream_pkt_set(struct pkt_stream *pkt_stream, struct pkt *pkt, int offset, u32 len) +{ + bool prev_pkt_valid = pkt->valid; + + pkt_set(pkt_stream, pkt, offset, len); + pkt_stream->nb_valid_entries += pkt->valid - prev_pkt_valid; +} + +static u32 pkt_get_buffer_len(struct xsk_umem_info *umem, u32 len) +{ + return ceil_u32(len, umem->frame_size) * umem->frame_size; +} + +static struct pkt_stream *__pkt_stream_generate(u32 nb_pkts, u32 pkt_len, u32 nb_start, u32 nb_off) +{ + struct pkt_stream *pkt_stream; + u32 i; + + pkt_stream = __pkt_stream_alloc(nb_pkts); + if (!pkt_stream) + exit_with_error(ENOMEM); + + pkt_stream->nb_pkts = nb_pkts; + pkt_stream->max_pkt_len = pkt_len; + for (i = 0; i < nb_pkts; i++) { + struct pkt *pkt = &pkt_stream->pkts[i]; + + pkt_stream_pkt_set(pkt_stream, pkt, 0, pkt_len); + pkt->pkt_nb = nb_start + i * nb_off; + } + + return pkt_stream; +} + +struct pkt_stream *pkt_stream_generate(u32 nb_pkts, u32 pkt_len) +{ + return __pkt_stream_generate(nb_pkts, pkt_len, 0, 1); +} + +static struct pkt_stream *pkt_stream_clone(struct pkt_stream *pkt_stream) +{ + return pkt_stream_generate(pkt_stream->nb_pkts, pkt_stream->pkts[0].len); +} + +static void pkt_stream_replace_ifobject(struct ifobject *ifobj, u32 nb_pkts, u32 pkt_len) +{ + ifobj->xsk->pkt_stream = pkt_stream_generate(nb_pkts, pkt_len); +} + +static void pkt_stream_replace(struct test_spec *test, u32 nb_pkts, u32 pkt_len) +{ + pkt_stream_replace_ifobject(test->ifobj_tx, nb_pkts, pkt_len); + pkt_stream_replace_ifobject(test->ifobj_rx, nb_pkts, pkt_len); +} + +static void __pkt_stream_replace_half(struct ifobject *ifobj, u32 pkt_len, + int offset) +{ + struct pkt_stream *pkt_stream; + u32 i; + + pkt_stream = pkt_stream_clone(ifobj->xsk->pkt_stream); + for (i = 1; i < ifobj->xsk->pkt_stream->nb_pkts; i += 2) + pkt_stream_pkt_set(pkt_stream, &pkt_stream->pkts[i], offset, pkt_len); + + ifobj->xsk->pkt_stream = pkt_stream; +} + +static void pkt_stream_replace_half(struct test_spec *test, u32 pkt_len, int offset) +{ + __pkt_stream_replace_half(test->ifobj_tx, pkt_len, offset); + __pkt_stream_replace_half(test->ifobj_rx, pkt_len, offset); +} + +static void pkt_stream_receive_half(struct test_spec *test) +{ + struct pkt_stream *pkt_stream = test->ifobj_tx->xsk->pkt_stream; + u32 i; + + test->ifobj_rx->xsk->pkt_stream = pkt_stream_generate(pkt_stream->nb_pkts, + pkt_stream->pkts[0].len); + pkt_stream = test->ifobj_rx->xsk->pkt_stream; + for (i = 1; i < pkt_stream->nb_pkts; i += 2) + pkt_stream->pkts[i].valid = false; + + pkt_stream->nb_valid_entries /= 2; +} + +static void pkt_stream_even_odd_sequence(struct test_spec *test) +{ + struct pkt_stream *pkt_stream; + u32 i; + + for (i = 0; i < test->nb_sockets; i++) { + pkt_stream = test->ifobj_tx->xsk_arr[i].pkt_stream; + pkt_stream = __pkt_stream_generate(pkt_stream->nb_pkts / 2, + pkt_stream->pkts[0].len, i, 2); + test->ifobj_tx->xsk_arr[i].pkt_stream = pkt_stream; + + pkt_stream = test->ifobj_rx->xsk_arr[i].pkt_stream; + pkt_stream = __pkt_stream_generate(pkt_stream->nb_pkts / 2, + pkt_stream->pkts[0].len, i, 2); + test->ifobj_rx->xsk_arr[i].pkt_stream = pkt_stream; + } +} + +static u64 pkt_get_addr(struct pkt *pkt, struct xsk_umem_info *umem) +{ + if (!pkt->valid) + return pkt->offset; + return pkt->offset + umem_alloc_buffer(umem); +} + +static void pkt_stream_cancel(struct pkt_stream *pkt_stream) +{ + pkt_stream->current_pkt_nb--; +} + +static void pkt_generate(struct xsk_socket_info *xsk, struct xsk_umem_info *umem, u64 addr, u32 len, + u32 pkt_nb, u32 bytes_written) +{ + void *data = xsk_umem__get_data(umem->buffer, addr); + + if (len < MIN_PKT_SIZE) + return; + + if (!bytes_written) { + gen_eth_hdr(xsk, data); + + len -= PKT_HDR_SIZE; + data += PKT_HDR_SIZE; + } else { + bytes_written -= PKT_HDR_SIZE; + } + + write_payload(data, pkt_nb, bytes_written, len); +} + +static struct pkt_stream *__pkt_stream_generate_custom(struct ifobject *ifobj, struct pkt *frames, + u32 nb_frames, bool verbatim) +{ + u32 i, len = 0, pkt_nb = 0, payload = 0; + struct pkt_stream *pkt_stream; + + pkt_stream = __pkt_stream_alloc(nb_frames); + if (!pkt_stream) + exit_with_error(ENOMEM); + + for (i = 0; i < nb_frames; i++) { + struct pkt *pkt = &pkt_stream->pkts[pkt_nb]; + struct pkt *frame = &frames[i]; + + pkt->offset = frame->offset; + if (verbatim) { + *pkt = *frame; + pkt->pkt_nb = payload; + if (!frame->valid || !pkt_continues(frame->options)) + payload++; + } else { + if (frame->valid) + len += frame->len; + if (frame->valid && pkt_continues(frame->options)) + continue; + + pkt->pkt_nb = pkt_nb; + pkt->len = len; + pkt->valid = frame->valid; + pkt->options = 0; + + len = 0; + } + + print_verbose("offset: %d len: %u valid: %u options: %u pkt_nb: %u\n", + pkt->offset, pkt->len, pkt->valid, pkt->options, pkt->pkt_nb); + + if (pkt->valid && pkt->len > pkt_stream->max_pkt_len) + pkt_stream->max_pkt_len = pkt->len; + + if (pkt->valid) + pkt_stream->nb_valid_entries++; + + pkt_nb++; + } + + pkt_stream->nb_pkts = pkt_nb; + pkt_stream->verbatim = verbatim; + return pkt_stream; +} + +static void pkt_stream_generate_custom(struct test_spec *test, struct pkt *pkts, u32 nb_pkts) +{ + struct pkt_stream *pkt_stream; + + pkt_stream = __pkt_stream_generate_custom(test->ifobj_tx, pkts, nb_pkts, true); + test->ifobj_tx->xsk->pkt_stream = pkt_stream; + + pkt_stream = __pkt_stream_generate_custom(test->ifobj_rx, pkts, nb_pkts, false); + test->ifobj_rx->xsk->pkt_stream = pkt_stream; +} + +static void pkt_print_data(u32 *data, u32 cnt) +{ + u32 i; + + for (i = 0; i < cnt; i++) { + u32 seqnum, pkt_nb; + + seqnum = ntohl(*data) & 0xffff; + pkt_nb = ntohl(*data) >> 16; + ksft_print_msg("%u:%u ", pkt_nb, seqnum); + data++; + } +} + +static void pkt_dump(void *pkt, u32 len, bool eth_header) +{ + struct ethhdr *ethhdr = pkt; + u32 i, *data; + + if (eth_header) { + /*extract L2 frame */ + ksft_print_msg("DEBUG>> L2: dst mac: "); + for (i = 0; i < ETH_ALEN; i++) + ksft_print_msg("%02X", ethhdr->h_dest[i]); + + ksft_print_msg("\nDEBUG>> L2: src mac: "); + for (i = 0; i < ETH_ALEN; i++) + ksft_print_msg("%02X", ethhdr->h_source[i]); + + data = pkt + PKT_HDR_SIZE; + } else { + data = pkt; + } + + /*extract L5 frame */ + ksft_print_msg("\nDEBUG>> L5: seqnum: "); + pkt_print_data(data, PKT_DUMP_NB_TO_PRINT); + ksft_print_msg("...."); + if (len > PKT_DUMP_NB_TO_PRINT * sizeof(u32)) { + ksft_print_msg("\n.... "); + pkt_print_data(data + len / sizeof(u32) - PKT_DUMP_NB_TO_PRINT, + PKT_DUMP_NB_TO_PRINT); + } + ksft_print_msg("\n---------------------------------------\n"); +} + +static bool is_offset_correct(struct xsk_umem_info *umem, struct pkt *pkt, u64 addr) +{ + u32 headroom = umem->unaligned_mode ? 0 : umem->frame_headroom; + u32 offset = addr % umem->frame_size, expected_offset; + int pkt_offset = pkt->valid ? pkt->offset : 0; + + if (!umem->unaligned_mode) + pkt_offset = 0; + + expected_offset = (pkt_offset + headroom + XDP_PACKET_HEADROOM) % umem->frame_size; + + if (offset == expected_offset) + return true; + + ksft_print_msg("[%s] expected [%u], got [%u]\n", __func__, expected_offset, offset); + return false; +} + +static bool is_metadata_correct(struct pkt *pkt, void *buffer, u64 addr) +{ + void *data = xsk_umem__get_data(buffer, addr); + struct xdp_info *meta = data - sizeof(struct xdp_info); + + if (meta->count != pkt->pkt_nb) { + ksft_print_msg("[%s] expected meta_count [%d], got meta_count [%llu]\n", + __func__, pkt->pkt_nb, + (unsigned long long)meta->count); + return false; + } + + return true; +} + +static bool is_adjust_tail_supported(struct xsk_xdp_progs *skel_rx) +{ + struct bpf_map *data_map; + int adjust_value = 0; + int key = 0; + int ret; + + data_map = bpf_object__find_map_by_name(skel_rx->obj, "xsk_xdp_.bss"); + if (!data_map || !bpf_map__is_internal(data_map)) { + ksft_print_msg("Error: could not find bss section of XDP program\n"); + exit_with_error(errno); + } + + ret = bpf_map_lookup_elem(bpf_map__fd(data_map), &key, &adjust_value); + if (ret) { + ksft_print_msg("Error: bpf_map_lookup_elem failed with error %d\n", ret); + exit_with_error(errno); + } + + /* Set the 'adjust_value' variable to -EOPNOTSUPP in the XDP program if the adjust_tail + * helper is not supported. Skip the adjust_tail test case in this scenario. + */ + return adjust_value != -EOPNOTSUPP; +} + +static bool is_frag_valid(struct xsk_umem_info *umem, u64 addr, u32 len, u32 expected_pkt_nb, + u32 bytes_processed) +{ + u32 seqnum, pkt_nb, *pkt_data, words_to_end, expected_seqnum; + void *data = xsk_umem__get_data(umem->buffer, addr); + + addr -= umem->base_addr; + + if (addr >= umem->num_frames * umem->frame_size || + addr + len > umem->num_frames * umem->frame_size) { + ksft_print_msg("Frag invalid addr: %llx len: %u\n", + (unsigned long long)addr, len); + return false; + } + if (!umem->unaligned_mode && addr % umem->frame_size + len > umem->frame_size) { + ksft_print_msg("Frag crosses frame boundary addr: %llx len: %u\n", + (unsigned long long)addr, len); + return false; + } + + pkt_data = data; + if (!bytes_processed) { + pkt_data += PKT_HDR_SIZE / sizeof(*pkt_data); + len -= PKT_HDR_SIZE; + } else { + bytes_processed -= PKT_HDR_SIZE; + } + + expected_seqnum = bytes_processed / sizeof(*pkt_data); + seqnum = ntohl(*pkt_data) & 0xffff; + pkt_nb = ntohl(*pkt_data) >> 16; + + if (expected_pkt_nb != pkt_nb) { + ksft_print_msg("[%s] expected pkt_nb [%u], got pkt_nb [%u]\n", + __func__, expected_pkt_nb, pkt_nb); + goto error; + } + if (expected_seqnum != seqnum) { + ksft_print_msg("[%s] expected seqnum at start [%u], got seqnum [%u]\n", + __func__, expected_seqnum, seqnum); + goto error; + } + + words_to_end = len / sizeof(*pkt_data) - 1; + pkt_data += words_to_end; + seqnum = ntohl(*pkt_data) & 0xffff; + expected_seqnum += words_to_end; + if (expected_seqnum != seqnum) { + ksft_print_msg("[%s] expected seqnum at end [%u], got seqnum [%u]\n", + __func__, expected_seqnum, seqnum); + goto error; + } + + return true; + +error: + pkt_dump(data, len, !bytes_processed); + return false; +} + +static bool is_pkt_valid(struct pkt *pkt, void *buffer, u64 addr, u32 len) +{ + if (pkt->len != len) { + ksft_print_msg("[%s] expected packet length [%d], got length [%d]\n", + __func__, pkt->len, len); + pkt_dump(xsk_umem__get_data(buffer, addr), len, true); + return false; + } + + return true; +} + +static u32 load_value(u32 *counter) +{ + return __atomic_load_n(counter, __ATOMIC_ACQUIRE); +} + +static bool kick_tx_with_check(struct xsk_socket_info *xsk, int *ret) +{ + u32 max_budget = MAX_TX_BUDGET_DEFAULT; + u32 cons, ready_to_send; + int delta; + + cons = load_value(xsk->tx.consumer); + ready_to_send = load_value(xsk->tx.producer) - cons; + *ret = sendto(xsk_socket__fd(xsk->xsk), NULL, 0, MSG_DONTWAIT, NULL, 0); + + delta = load_value(xsk->tx.consumer) - cons; + /* By default, xsk should consume exact @max_budget descs at one + * send in this case where hitting the max budget limit in while + * loop is triggered in __xsk_generic_xmit(). Please make sure that + * the number of descs to be sent is larger than @max_budget, or + * else the tx.consumer will be updated in xskq_cons_peek_desc() + * in time which hides the issue we try to verify. + */ + if (ready_to_send > max_budget && delta != max_budget) + return false; + + return true; +} + +int kick_tx(struct xsk_socket_info *xsk) +{ + int ret; + + if (xsk->check_consumer) { + if (!kick_tx_with_check(xsk, &ret)) + return TEST_FAILURE; + } else { + ret = sendto(xsk_socket__fd(xsk->xsk), NULL, 0, MSG_DONTWAIT, NULL, 0); + } + if (ret >= 0) + return TEST_PASS; + if (errno == ENOBUFS || errno == EAGAIN || errno == EBUSY || errno == ENETDOWN) { + usleep(100); + return TEST_PASS; + } + return TEST_FAILURE; +} + +int kick_rx(struct xsk_socket_info *xsk) +{ + int ret; + + ret = recvfrom(xsk_socket__fd(xsk->xsk), NULL, 0, MSG_DONTWAIT, NULL, NULL); + if (ret < 0) + return TEST_FAILURE; + + return TEST_PASS; +} + +static int complete_pkts(struct xsk_socket_info *xsk, int batch_size) +{ + unsigned int rcvd; + u32 idx; + int ret; + + if (xsk_ring_prod__needs_wakeup(&xsk->tx)) { + ret = kick_tx(xsk); + if (ret) + return TEST_FAILURE; + } + + rcvd = xsk_ring_cons__peek(&xsk->umem->cq, batch_size, &idx); + if (rcvd) { + if (rcvd > xsk->outstanding_tx) { + u64 addr = *xsk_ring_cons__comp_addr(&xsk->umem->cq, idx + rcvd - 1); + + ksft_print_msg("[%s] Too many packets completed\n", __func__); + ksft_print_msg("Last completion address: %llx\n", + (unsigned long long)addr); + return TEST_FAILURE; + } + + xsk_ring_cons__release(&xsk->umem->cq, rcvd); + xsk->outstanding_tx -= rcvd; + } + + return TEST_PASS; +} + +static int __receive_pkts(struct test_spec *test, struct xsk_socket_info *xsk) +{ + u32 frags_processed = 0, nb_frags = 0, pkt_len = 0; + u32 idx_rx = 0, idx_fq = 0, rcvd, pkts_sent = 0; + struct pkt_stream *pkt_stream = xsk->pkt_stream; + struct ifobject *ifobj = test->ifobj_rx; + struct xsk_umem_info *umem = xsk->umem; + struct pollfd fds = { }; + struct pkt *pkt; + u64 first_addr = 0; + int ret; + + fds.fd = xsk_socket__fd(xsk->xsk); + fds.events = POLLIN; + + ret = kick_rx(xsk); + if (ret) + return TEST_FAILURE; + + if (ifobj->use_poll) { + ret = poll(&fds, 1, POLL_TMOUT); + if (ret < 0) + return TEST_FAILURE; + + if (!ret) { + if (!is_umem_valid(test->ifobj_tx)) + return TEST_PASS; + + ksft_print_msg("ERROR: [%s] Poll timed out\n", __func__); + return TEST_CONTINUE; + } + + if (!(fds.revents & POLLIN)) + return TEST_CONTINUE; + } + + rcvd = xsk_ring_cons__peek(&xsk->rx, xsk->batch_size, &idx_rx); + if (!rcvd) + return TEST_CONTINUE; + + if (ifobj->use_fill_ring) { + ret = xsk_ring_prod__reserve(&umem->fq, rcvd, &idx_fq); + while (ret != rcvd) { + if (xsk_ring_prod__needs_wakeup(&umem->fq)) { + ret = poll(&fds, 1, POLL_TMOUT); + if (ret < 0) + return TEST_FAILURE; + } + ret = xsk_ring_prod__reserve(&umem->fq, rcvd, &idx_fq); + } + } + + while (frags_processed < rcvd) { + const struct xdp_desc *desc = xsk_ring_cons__rx_desc(&xsk->rx, idx_rx++); + u64 addr = desc->addr, orig; + + orig = xsk_umem__extract_addr(addr); + addr = xsk_umem__add_offset_to_addr(addr); + + if (!nb_frags) { + pkt = pkt_stream_get_next_rx_pkt(pkt_stream, &pkts_sent); + if (!pkt) { + ksft_print_msg("[%s] received too many packets addr: %lx len %u\n", + __func__, addr, desc->len); + return TEST_FAILURE; + } + } + + print_verbose("Rx: addr: %lx len: %u options: %u pkt_nb: %u valid: %u\n", + addr, desc->len, desc->options, pkt->pkt_nb, pkt->valid); + + if (!is_frag_valid(umem, addr, desc->len, pkt->pkt_nb, pkt_len) || + !is_offset_correct(umem, pkt, addr) || (ifobj->use_metadata && + !is_metadata_correct(pkt, umem->buffer, addr))) + return TEST_FAILURE; + + if (!nb_frags++) + first_addr = addr; + frags_processed++; + pkt_len += desc->len; + if (ifobj->use_fill_ring) + *xsk_ring_prod__fill_addr(&umem->fq, idx_fq++) = orig; + + if (pkt_continues(desc->options)) + continue; + + /* The complete packet has been received */ + if (!is_pkt_valid(pkt, umem->buffer, first_addr, pkt_len) || + !is_offset_correct(umem, pkt, addr)) + return TEST_FAILURE; + + pkt_stream->nb_rx_pkts++; + nb_frags = 0; + pkt_len = 0; + } + + if (nb_frags) { + /* In the middle of a packet. Start over from beginning of packet. */ + idx_rx -= nb_frags; + xsk_ring_cons__cancel(&xsk->rx, nb_frags); + if (ifobj->use_fill_ring) { + idx_fq -= nb_frags; + xsk_ring_prod__cancel(&umem->fq, nb_frags); + } + frags_processed -= nb_frags; + } + + if (ifobj->use_fill_ring) + xsk_ring_prod__submit(&umem->fq, frags_processed); + if (ifobj->release_rx) + xsk_ring_cons__release(&xsk->rx, frags_processed); + + pthread_mutex_lock(&pacing_mutex); + pkts_in_flight -= pkts_sent; + pthread_mutex_unlock(&pacing_mutex); + pkts_sent = 0; + + return TEST_CONTINUE; +} + +bool all_packets_received(struct test_spec *test, struct xsk_socket_info *xsk, u32 sock_num, + unsigned long *bitmap) +{ + struct pkt_stream *pkt_stream = xsk->pkt_stream; + + if (!pkt_stream) { + __set_bit(sock_num, bitmap); + return false; + } + + if (pkt_stream->nb_rx_pkts == pkt_stream->nb_valid_entries) { + __set_bit(sock_num, bitmap); + if (bitmap_full(bitmap, test->nb_sockets)) + return true; + } + + return false; +} + +static int receive_pkts(struct test_spec *test) +{ + struct timeval tv_end, tv_now, tv_timeout = {THREAD_TMOUT, 0}; + DECLARE_BITMAP(bitmap, test->nb_sockets); + struct xsk_socket_info *xsk; + u32 sock_num = 0; + int res, ret; + + ret = gettimeofday(&tv_now, NULL); + if (ret) + exit_with_error(errno); + + timeradd(&tv_now, &tv_timeout, &tv_end); + + while (1) { + xsk = &test->ifobj_rx->xsk_arr[sock_num]; + + if ((all_packets_received(test, xsk, sock_num, bitmap))) + break; + + res = __receive_pkts(test, xsk); + if (!(res == TEST_PASS || res == TEST_CONTINUE)) + return res; + + ret = gettimeofday(&tv_now, NULL); + if (ret) + exit_with_error(errno); + + if (timercmp(&tv_now, &tv_end, >)) { + ksft_print_msg("ERROR: [%s] Receive loop timed out\n", __func__); + return TEST_FAILURE; + } + sock_num = (sock_num + 1) % test->nb_sockets; + } + + return TEST_PASS; +} + +static int __send_pkts(struct ifobject *ifobject, struct xsk_socket_info *xsk, bool timeout) +{ + u32 i, idx = 0, valid_pkts = 0, valid_frags = 0, buffer_len; + struct pkt_stream *pkt_stream = xsk->pkt_stream; + struct xsk_umem_info *umem = ifobject->umem; + bool use_poll = ifobject->use_poll; + struct pollfd fds = { }; + int ret; + + buffer_len = pkt_get_buffer_len(umem, pkt_stream->max_pkt_len); + /* pkts_in_flight might be negative if many invalid packets are sent */ + if (pkts_in_flight >= (int)((umem_size(umem) - xsk->batch_size * buffer_len) / + buffer_len)) { + ret = kick_tx(xsk); + if (ret) + return TEST_FAILURE; + return TEST_CONTINUE; + } + + fds.fd = xsk_socket__fd(xsk->xsk); + fds.events = POLLOUT; + + while (xsk_ring_prod__reserve(&xsk->tx, xsk->batch_size, &idx) < xsk->batch_size) { + if (use_poll) { + ret = poll(&fds, 1, POLL_TMOUT); + if (timeout) { + if (ret < 0) { + ksft_print_msg("ERROR: [%s] Poll error %d\n", + __func__, errno); + return TEST_FAILURE; + } + if (ret == 0) + return TEST_PASS; + break; + } + if (ret <= 0) { + ksft_print_msg("ERROR: [%s] Poll error %d\n", + __func__, errno); + return TEST_FAILURE; + } + } + + complete_pkts(xsk, xsk->batch_size); + } + + for (i = 0; i < xsk->batch_size; i++) { + struct pkt *pkt = pkt_stream_get_next_tx_pkt(pkt_stream); + u32 nb_frags_left, nb_frags, bytes_written = 0; + + if (!pkt) + break; + + nb_frags = pkt_nb_frags(umem->frame_size, pkt_stream, pkt); + if (nb_frags > xsk->batch_size - i) { + pkt_stream_cancel(pkt_stream); + xsk_ring_prod__cancel(&xsk->tx, xsk->batch_size - i); + break; + } + nb_frags_left = nb_frags; + + while (nb_frags_left--) { + struct xdp_desc *tx_desc = xsk_ring_prod__tx_desc(&xsk->tx, idx + i); + + tx_desc->addr = pkt_get_addr(pkt, ifobject->umem); + if (pkt_stream->verbatim) { + tx_desc->len = pkt->len; + tx_desc->options = pkt->options; + } else if (nb_frags_left) { + tx_desc->len = umem->frame_size; + tx_desc->options = XDP_PKT_CONTD; + } else { + tx_desc->len = pkt->len - bytes_written; + tx_desc->options = 0; + } + if (pkt->valid) + pkt_generate(xsk, umem, tx_desc->addr, tx_desc->len, pkt->pkt_nb, + bytes_written); + bytes_written += tx_desc->len; + + print_verbose("Tx addr: %llx len: %u options: %u pkt_nb: %u\n", + tx_desc->addr, tx_desc->len, tx_desc->options, pkt->pkt_nb); + + if (nb_frags_left) { + i++; + if (pkt_stream->verbatim) + pkt = pkt_stream_get_next_tx_pkt(pkt_stream); + } + } + + if (pkt && pkt->valid) { + valid_pkts++; + valid_frags += nb_frags; + } + } + + pthread_mutex_lock(&pacing_mutex); + pkts_in_flight += valid_pkts; + pthread_mutex_unlock(&pacing_mutex); + + xsk_ring_prod__submit(&xsk->tx, i); + xsk->outstanding_tx += valid_frags; + + if (use_poll) { + ret = poll(&fds, 1, POLL_TMOUT); + if (ret <= 0) { + if (ret == 0 && timeout) + return TEST_PASS; + + ksft_print_msg("ERROR: [%s] Poll error %d\n", __func__, ret); + return TEST_FAILURE; + } + } + + if (!timeout) { + if (complete_pkts(xsk, i)) + return TEST_FAILURE; + + usleep(10); + return TEST_PASS; + } + + return TEST_CONTINUE; +} + +static int wait_for_tx_completion(struct xsk_socket_info *xsk) +{ + struct timeval tv_end, tv_now, tv_timeout = {THREAD_TMOUT, 0}; + int ret; + + ret = gettimeofday(&tv_now, NULL); + if (ret) + exit_with_error(errno); + timeradd(&tv_now, &tv_timeout, &tv_end); + + while (xsk->outstanding_tx) { + ret = gettimeofday(&tv_now, NULL); + if (ret) + exit_with_error(errno); + if (timercmp(&tv_now, &tv_end, >)) { + ksft_print_msg("ERROR: [%s] Transmission loop timed out\n", __func__); + return TEST_FAILURE; + } + + complete_pkts(xsk, xsk->batch_size); + } + + return TEST_PASS; +} + +bool all_packets_sent(struct test_spec *test, unsigned long *bitmap) +{ + return bitmap_full(bitmap, test->nb_sockets); +} + +static int send_pkts(struct test_spec *test, struct ifobject *ifobject) +{ + bool timeout = !is_umem_valid(test->ifobj_rx); + DECLARE_BITMAP(bitmap, test->nb_sockets); + u32 i, ret; + + while (!(all_packets_sent(test, bitmap))) { + for (i = 0; i < test->nb_sockets; i++) { + struct pkt_stream *pkt_stream; + + pkt_stream = ifobject->xsk_arr[i].pkt_stream; + if (!pkt_stream || pkt_stream->current_pkt_nb >= pkt_stream->nb_pkts) { + __set_bit(i, bitmap); + continue; + } + ret = __send_pkts(ifobject, &ifobject->xsk_arr[i], timeout); + if (ret == TEST_CONTINUE && !test->fail) + continue; + + if ((ret || test->fail) && !timeout) + return TEST_FAILURE; + + if (ret == TEST_PASS && timeout) + return ret; + + ret = wait_for_tx_completion(&ifobject->xsk_arr[i]); + if (ret) + return TEST_FAILURE; + } + } + + return TEST_PASS; +} + +static int get_xsk_stats(struct xsk_socket *xsk, struct xdp_statistics *stats) +{ + int fd = xsk_socket__fd(xsk), err; + socklen_t optlen, expected_len; + + optlen = sizeof(*stats); + err = getsockopt(fd, SOL_XDP, XDP_STATISTICS, stats, &optlen); + if (err) { + ksft_print_msg("[%s] getsockopt(XDP_STATISTICS) error %u %s\n", + __func__, -err, strerror(-err)); + return TEST_FAILURE; + } + + expected_len = sizeof(struct xdp_statistics); + if (optlen != expected_len) { + ksft_print_msg("[%s] getsockopt optlen error. Expected: %u got: %u\n", + __func__, expected_len, optlen); + return TEST_FAILURE; + } + + return TEST_PASS; +} + +static int validate_rx_dropped(struct ifobject *ifobject) +{ + struct xsk_socket *xsk = ifobject->xsk->xsk; + struct xdp_statistics stats; + int err; + + err = kick_rx(ifobject->xsk); + if (err) + return TEST_FAILURE; + + err = get_xsk_stats(xsk, &stats); + if (err) + return TEST_FAILURE; + + /* The receiver calls getsockopt after receiving the last (valid) + * packet which is not the final packet sent in this test (valid and + * invalid packets are sent in alternating fashion with the final + * packet being invalid). Since the last packet may or may not have + * been dropped already, both outcomes must be allowed. + */ + if (stats.rx_dropped == ifobject->xsk->pkt_stream->nb_pkts / 2 || + stats.rx_dropped == ifobject->xsk->pkt_stream->nb_pkts / 2 - 1) + return TEST_PASS; + + return TEST_FAILURE; +} + +static int validate_rx_full(struct ifobject *ifobject) +{ + struct xsk_socket *xsk = ifobject->xsk->xsk; + struct xdp_statistics stats; + int err; + + usleep(1000); + err = kick_rx(ifobject->xsk); + if (err) + return TEST_FAILURE; + + err = get_xsk_stats(xsk, &stats); + if (err) + return TEST_FAILURE; + + if (stats.rx_ring_full) + return TEST_PASS; + + return TEST_FAILURE; +} + +static int validate_fill_empty(struct ifobject *ifobject) +{ + struct xsk_socket *xsk = ifobject->xsk->xsk; + struct xdp_statistics stats; + int err; + + usleep(1000); + err = kick_rx(ifobject->xsk); + if (err) + return TEST_FAILURE; + + err = get_xsk_stats(xsk, &stats); + if (err) + return TEST_FAILURE; + + if (stats.rx_fill_ring_empty_descs) + return TEST_PASS; + + return TEST_FAILURE; +} + +static int validate_tx_invalid_descs(struct ifobject *ifobject) +{ + struct xsk_socket *xsk = ifobject->xsk->xsk; + int fd = xsk_socket__fd(xsk); + struct xdp_statistics stats; + socklen_t optlen; + int err; + + optlen = sizeof(stats); + err = getsockopt(fd, SOL_XDP, XDP_STATISTICS, &stats, &optlen); + if (err) { + ksft_print_msg("[%s] getsockopt(XDP_STATISTICS) error %u %s\n", + __func__, -err, strerror(-err)); + return TEST_FAILURE; + } + + if (stats.tx_invalid_descs != ifobject->xsk->pkt_stream->nb_pkts / 2) { + ksft_print_msg("[%s] tx_invalid_descs incorrect. Got [%llu] expected [%u]\n", + __func__, + (unsigned long long)stats.tx_invalid_descs, + ifobject->xsk->pkt_stream->nb_pkts); + return TEST_FAILURE; + } + + return TEST_PASS; +} + +static void xsk_configure(struct test_spec *test, struct ifobject *ifobject, + struct xsk_umem_info *umem, bool tx) +{ + int i, ret; + + for (i = 0; i < test->nb_sockets; i++) { + bool shared = (ifobject->shared_umem && tx) ? true : !!i; + u32 ctr = 0; + + while (ctr++ < SOCK_RECONF_CTR) { + ret = xsk_configure_socket(&ifobject->xsk_arr[i], umem, + ifobject, shared); + if (!ret) + break; + + /* Retry if it fails as xsk_socket__create() is asynchronous */ + if (ctr >= SOCK_RECONF_CTR) + exit_with_error(-ret); + usleep(USLEEP_MAX); + } + if (ifobject->busy_poll) + enable_busy_poll(&ifobject->xsk_arr[i]); + } +} + +static void thread_common_ops_tx(struct test_spec *test, struct ifobject *ifobject) +{ + xsk_configure(test, ifobject, test->ifobj_rx->umem, true); + ifobject->xsk = &ifobject->xsk_arr[0]; + ifobject->xskmap = test->ifobj_rx->xskmap; + memcpy(ifobject->umem, test->ifobj_rx->umem, sizeof(struct xsk_umem_info)); + ifobject->umem->base_addr = 0; +} + +static void xsk_populate_fill_ring(struct xsk_umem_info *umem, struct pkt_stream *pkt_stream, + bool fill_up) +{ + u32 rx_frame_size = umem->frame_size - XDP_PACKET_HEADROOM; + u32 idx = 0, filled = 0, buffers_to_fill, nb_pkts; + int ret; + + if (umem->num_frames < XSK_RING_PROD__DEFAULT_NUM_DESCS) + buffers_to_fill = umem->num_frames; + else + buffers_to_fill = umem->fill_size; + + ret = xsk_ring_prod__reserve(&umem->fq, buffers_to_fill, &idx); + if (ret != buffers_to_fill) + exit_with_error(ENOSPC); + + while (filled < buffers_to_fill) { + struct pkt *pkt = pkt_stream_get_next_rx_pkt(pkt_stream, &nb_pkts); + u64 addr; + u32 i; + + for (i = 0; i < pkt_nb_frags(rx_frame_size, pkt_stream, pkt); i++) { + if (!pkt) { + if (!fill_up) + break; + addr = filled * umem->frame_size + umem->base_addr; + } else if (pkt->offset >= 0) { + addr = pkt->offset % umem->frame_size + umem_alloc_buffer(umem); + } else { + addr = pkt->offset + umem_alloc_buffer(umem); + } + + *xsk_ring_prod__fill_addr(&umem->fq, idx++) = addr; + if (++filled >= buffers_to_fill) + break; + } + } + xsk_ring_prod__submit(&umem->fq, filled); + xsk_ring_prod__cancel(&umem->fq, buffers_to_fill - filled); + + pkt_stream_reset(pkt_stream); + umem_reset_alloc(umem); +} + +static void thread_common_ops(struct test_spec *test, struct ifobject *ifobject) +{ + LIBBPF_OPTS(bpf_xdp_query_opts, opts); + int mmap_flags; + u64 umem_sz; + void *bufs; + int ret; + u32 i; + + umem_sz = ifobject->umem->num_frames * ifobject->umem->frame_size; + mmap_flags = MAP_PRIVATE | MAP_ANONYMOUS | MAP_NORESERVE; + + if (ifobject->umem->unaligned_mode) + mmap_flags |= MAP_HUGETLB | MAP_HUGE_2MB; + + if (ifobject->shared_umem) + umem_sz *= 2; + + bufs = mmap(NULL, umem_sz, PROT_READ | PROT_WRITE, mmap_flags, -1, 0); + if (bufs == MAP_FAILED) + exit_with_error(errno); + + ret = xsk_configure_umem(ifobject, ifobject->umem, bufs, umem_sz); + if (ret) + exit_with_error(-ret); + + xsk_configure(test, ifobject, ifobject->umem, false); + + ifobject->xsk = &ifobject->xsk_arr[0]; + + if (!ifobject->rx_on) + return; + + xsk_populate_fill_ring(ifobject->umem, ifobject->xsk->pkt_stream, ifobject->use_fill_ring); + + for (i = 0; i < test->nb_sockets; i++) { + ifobject->xsk = &ifobject->xsk_arr[i]; + ret = xsk_update_xskmap(ifobject->xskmap, ifobject->xsk->xsk, i); + if (ret) + exit_with_error(errno); + } +} + +void *worker_testapp_validate_tx(void *arg) +{ + struct test_spec *test = (struct test_spec *)arg; + struct ifobject *ifobject = test->ifobj_tx; + int err; + + if (test->current_step == 1) { + if (!ifobject->shared_umem) + thread_common_ops(test, ifobject); + else + thread_common_ops_tx(test, ifobject); + } + + err = send_pkts(test, ifobject); + + if (!err && ifobject->validation_func) + err = ifobject->validation_func(ifobject); + if (err) + test->fail = true; + + pthread_exit(NULL); +} + +void *worker_testapp_validate_rx(void *arg) +{ + struct test_spec *test = (struct test_spec *)arg; + struct ifobject *ifobject = test->ifobj_rx; + int err; + + if (test->current_step == 1) { + thread_common_ops(test, ifobject); + } else { + xsk_clear_xskmap(ifobject->xskmap); + err = xsk_update_xskmap(ifobject->xskmap, ifobject->xsk->xsk, 0); + if (err) { + ksft_print_msg("Error: Failed to update xskmap, error %s\n", + strerror(-err)); + exit_with_error(-err); + } + } + + pthread_barrier_wait(&barr); + + err = receive_pkts(test); + + if (!err && ifobject->validation_func) + err = ifobject->validation_func(ifobject); + + if (err) { + if (test->adjust_tail && !is_adjust_tail_supported(ifobject->xdp_progs)) + test->adjust_tail_support = false; + else + test->fail = true; + } + + pthread_exit(NULL); +} + +static void testapp_clean_xsk_umem(struct ifobject *ifobj) +{ + u64 umem_sz = ifobj->umem->num_frames * ifobj->umem->frame_size; + + if (ifobj->shared_umem) + umem_sz *= 2; + + umem_sz = ceil_u64(umem_sz, HUGEPAGE_SIZE) * HUGEPAGE_SIZE; + xsk_umem__delete(ifobj->umem->umem); + munmap(ifobj->umem->buffer, umem_sz); +} + +static void handler(int signum) +{ + pthread_exit(NULL); +} + +static bool xdp_prog_changed_rx(struct test_spec *test) +{ + struct ifobject *ifobj = test->ifobj_rx; + + return ifobj->xdp_prog != test->xdp_prog_rx || ifobj->mode != test->mode; +} + +static bool xdp_prog_changed_tx(struct test_spec *test) +{ + struct ifobject *ifobj = test->ifobj_tx; + + return ifobj->xdp_prog != test->xdp_prog_tx || ifobj->mode != test->mode; +} + +static void xsk_reattach_xdp(struct ifobject *ifobj, struct bpf_program *xdp_prog, + struct bpf_map *xskmap, enum test_mode mode) +{ + int err; + + xsk_detach_xdp_program(ifobj->ifindex, mode_to_xdp_flags(ifobj->mode)); + err = xsk_attach_xdp_program(xdp_prog, ifobj->ifindex, mode_to_xdp_flags(mode)); + if (err) { + ksft_print_msg("Error attaching XDP program\n"); + exit_with_error(-err); + } + + if (ifobj->mode != mode && (mode == TEST_MODE_DRV || mode == TEST_MODE_ZC)) + if (!xsk_is_in_mode(ifobj->ifindex, XDP_FLAGS_DRV_MODE)) { + ksft_print_msg("ERROR: XDP prog not in DRV mode\n"); + exit_with_error(EINVAL); + } + + ifobj->xdp_prog = xdp_prog; + ifobj->xskmap = xskmap; + ifobj->mode = mode; +} + +static void xsk_attach_xdp_progs(struct test_spec *test, struct ifobject *ifobj_rx, + struct ifobject *ifobj_tx) +{ + if (xdp_prog_changed_rx(test)) + xsk_reattach_xdp(ifobj_rx, test->xdp_prog_rx, test->xskmap_rx, test->mode); + + if (!ifobj_tx || ifobj_tx->shared_umem) + return; + + if (xdp_prog_changed_tx(test)) + xsk_reattach_xdp(ifobj_tx, test->xdp_prog_tx, test->xskmap_tx, test->mode); +} + +static int __testapp_validate_traffic(struct test_spec *test, struct ifobject *ifobj1, + struct ifobject *ifobj2) +{ + pthread_t t0, t1; + int err; + + if (test->mtu > MAX_ETH_PKT_SIZE) { + if (test->mode == TEST_MODE_ZC && (!ifobj1->multi_buff_zc_supp || + (ifobj2 && !ifobj2->multi_buff_zc_supp))) { + ksft_print_msg("Multi buffer for zero-copy not supported.\n"); + return TEST_SKIP; + } + if (test->mode != TEST_MODE_ZC && (!ifobj1->multi_buff_supp || + (ifobj2 && !ifobj2->multi_buff_supp))) { + ksft_print_msg("Multi buffer not supported.\n"); + return TEST_SKIP; + } + } + err = test_spec_set_mtu(test, test->mtu); + if (err) { + ksft_print_msg("Error, could not set mtu.\n"); + exit_with_error(err); + } + + if (ifobj2) { + if (pthread_barrier_init(&barr, NULL, 2)) + exit_with_error(errno); + pkt_stream_reset(ifobj2->xsk->pkt_stream); + } + + test->current_step++; + pkt_stream_reset(ifobj1->xsk->pkt_stream); + pkts_in_flight = 0; + + signal(SIGUSR1, handler); + /*Spawn RX thread */ + pthread_create(&t0, NULL, ifobj1->func_ptr, test); + + if (ifobj2) { + pthread_barrier_wait(&barr); + if (pthread_barrier_destroy(&barr)) + exit_with_error(errno); + + /*Spawn TX thread */ + pthread_create(&t1, NULL, ifobj2->func_ptr, test); + + pthread_join(t1, NULL); + } + + if (!ifobj2) + pthread_kill(t0, SIGUSR1); + else + pthread_join(t0, NULL); + + if (test->total_steps == test->current_step || test->fail) { + u32 i; + + if (ifobj2) + for (i = 0; i < test->nb_sockets; i++) + xsk_socket__delete(ifobj2->xsk_arr[i].xsk); + + for (i = 0; i < test->nb_sockets; i++) + xsk_socket__delete(ifobj1->xsk_arr[i].xsk); + + testapp_clean_xsk_umem(ifobj1); + if (ifobj2 && !ifobj2->shared_umem) + testapp_clean_xsk_umem(ifobj2); + } + + return !!test->fail; +} + +static int testapp_validate_traffic(struct test_spec *test) +{ + struct ifobject *ifobj_rx = test->ifobj_rx; + struct ifobject *ifobj_tx = test->ifobj_tx; + + if ((ifobj_rx->umem->unaligned_mode && !ifobj_rx->unaligned_supp) || + (ifobj_tx->umem->unaligned_mode && !ifobj_tx->unaligned_supp)) { + ksft_print_msg("No huge pages present.\n"); + return TEST_SKIP; + } + + if (test->set_ring) { + if (ifobj_tx->hw_ring_size_supp) { + if (set_ring_size(ifobj_tx)) { + ksft_print_msg("Failed to change HW ring size.\n"); + return TEST_FAILURE; + } + } else { + ksft_print_msg("Changing HW ring size not supported.\n"); + return TEST_SKIP; + } + } + + xsk_attach_xdp_progs(test, ifobj_rx, ifobj_tx); + return __testapp_validate_traffic(test, ifobj_rx, ifobj_tx); +} + +static int testapp_validate_traffic_single_thread(struct test_spec *test, struct ifobject *ifobj) +{ + return __testapp_validate_traffic(test, ifobj, NULL); +} + +int testapp_teardown(struct test_spec *test) +{ + int i; + + for (i = 0; i < MAX_TEARDOWN_ITER; i++) { + if (testapp_validate_traffic(test)) + return TEST_FAILURE; + test_spec_reset(test); + } + + return TEST_PASS; +} + +static void swap_directions(struct ifobject **ifobj1, struct ifobject **ifobj2) +{ + thread_func_t tmp_func_ptr = (*ifobj1)->func_ptr; + struct ifobject *tmp_ifobj = (*ifobj1); + + (*ifobj1)->func_ptr = (*ifobj2)->func_ptr; + (*ifobj2)->func_ptr = tmp_func_ptr; + + *ifobj1 = *ifobj2; + *ifobj2 = tmp_ifobj; +} + +int testapp_bidirectional(struct test_spec *test) +{ + int res; + + test->ifobj_tx->rx_on = true; + test->ifobj_rx->tx_on = true; + test->total_steps = 2; + if (testapp_validate_traffic(test)) + return TEST_FAILURE; + + print_verbose("Switching Tx/Rx direction\n"); + swap_directions(&test->ifobj_rx, &test->ifobj_tx); + res = __testapp_validate_traffic(test, test->ifobj_rx, test->ifobj_tx); + + swap_directions(&test->ifobj_rx, &test->ifobj_tx); + return res; +} + +static int swap_xsk_resources(struct test_spec *test) +{ + int ret; + + test->ifobj_tx->xsk_arr[0].pkt_stream = NULL; + test->ifobj_rx->xsk_arr[0].pkt_stream = NULL; + test->ifobj_tx->xsk_arr[1].pkt_stream = test->tx_pkt_stream_default; + test->ifobj_rx->xsk_arr[1].pkt_stream = test->rx_pkt_stream_default; + test->ifobj_tx->xsk = &test->ifobj_tx->xsk_arr[1]; + test->ifobj_rx->xsk = &test->ifobj_rx->xsk_arr[1]; + + ret = xsk_update_xskmap(test->ifobj_rx->xskmap, test->ifobj_rx->xsk->xsk, 0); + if (ret) + return TEST_FAILURE; + + return TEST_PASS; +} + +int testapp_xdp_prog_cleanup(struct test_spec *test) +{ + test->total_steps = 2; + test->nb_sockets = 2; + if (testapp_validate_traffic(test)) + return TEST_FAILURE; + + if (swap_xsk_resources(test)) + return TEST_FAILURE; + return testapp_validate_traffic(test); +} + +int testapp_headroom(struct test_spec *test) +{ + test->ifobj_rx->umem->frame_headroom = UMEM_HEADROOM_TEST_SIZE; + return testapp_validate_traffic(test); +} + +int testapp_stats_rx_dropped(struct test_spec *test) +{ + if (test->mode == TEST_MODE_ZC) { + ksft_print_msg("Can not run RX_DROPPED test for ZC mode\n"); + return TEST_SKIP; + } + + pkt_stream_replace_half(test, MIN_PKT_SIZE * 4, 0); + test->ifobj_rx->umem->frame_headroom = test->ifobj_rx->umem->frame_size - + XDP_PACKET_HEADROOM - MIN_PKT_SIZE * 3; + pkt_stream_receive_half(test); + test->ifobj_rx->validation_func = validate_rx_dropped; + return testapp_validate_traffic(test); +} + +int testapp_stats_tx_invalid_descs(struct test_spec *test) +{ + pkt_stream_replace_half(test, XSK_UMEM__INVALID_FRAME_SIZE, 0); + test->ifobj_tx->validation_func = validate_tx_invalid_descs; + return testapp_validate_traffic(test); +} + +int testapp_stats_rx_full(struct test_spec *test) +{ + pkt_stream_replace(test, DEFAULT_UMEM_BUFFERS + DEFAULT_UMEM_BUFFERS / 2, MIN_PKT_SIZE); + test->ifobj_rx->xsk->pkt_stream = pkt_stream_generate(DEFAULT_UMEM_BUFFERS, MIN_PKT_SIZE); + + test->ifobj_rx->xsk->rxqsize = DEFAULT_UMEM_BUFFERS; + test->ifobj_rx->release_rx = false; + test->ifobj_rx->validation_func = validate_rx_full; + return testapp_validate_traffic(test); +} + +int testapp_stats_fill_empty(struct test_spec *test) +{ + pkt_stream_replace(test, DEFAULT_UMEM_BUFFERS + DEFAULT_UMEM_BUFFERS / 2, MIN_PKT_SIZE); + test->ifobj_rx->xsk->pkt_stream = pkt_stream_generate(DEFAULT_UMEM_BUFFERS, MIN_PKT_SIZE); + + test->ifobj_rx->use_fill_ring = false; + test->ifobj_rx->validation_func = validate_fill_empty; + return testapp_validate_traffic(test); +} + +int testapp_send_receive_unaligned(struct test_spec *test) +{ + test->ifobj_tx->umem->unaligned_mode = true; + test->ifobj_rx->umem->unaligned_mode = true; + /* Let half of the packets straddle a 4K buffer boundary */ + pkt_stream_replace_half(test, MIN_PKT_SIZE, -MIN_PKT_SIZE / 2); + + return testapp_validate_traffic(test); +} + +int testapp_send_receive_unaligned_mb(struct test_spec *test) +{ + test->mtu = MAX_ETH_JUMBO_SIZE; + test->ifobj_tx->umem->unaligned_mode = true; + test->ifobj_rx->umem->unaligned_mode = true; + pkt_stream_replace(test, DEFAULT_PKT_CNT, MAX_ETH_JUMBO_SIZE); + return testapp_validate_traffic(test); +} + +int testapp_single_pkt(struct test_spec *test) +{ + struct pkt pkts[] = {{0, MIN_PKT_SIZE, 0, true}}; + + pkt_stream_generate_custom(test, pkts, ARRAY_SIZE(pkts)); + return testapp_validate_traffic(test); +} + +int testapp_send_receive_mb(struct test_spec *test) +{ + test->mtu = MAX_ETH_JUMBO_SIZE; + pkt_stream_replace(test, DEFAULT_PKT_CNT, MAX_ETH_JUMBO_SIZE); + + return testapp_validate_traffic(test); +} + +int testapp_invalid_desc_mb(struct test_spec *test) +{ + struct xsk_umem_info *umem = test->ifobj_tx->umem; + u64 umem_size = umem->num_frames * umem->frame_size; + struct pkt pkts[] = { + /* Valid packet for synch to start with */ + {0, MIN_PKT_SIZE, 0, true, 0}, + /* Zero frame len is not legal */ + {0, XSK_UMEM__LARGE_FRAME_SIZE, 0, false, XDP_PKT_CONTD}, + {0, XSK_UMEM__LARGE_FRAME_SIZE, 0, false, XDP_PKT_CONTD}, + {0, 0, 0, false, 0}, + /* Invalid address in the second frame */ + {0, XSK_UMEM__LARGE_FRAME_SIZE, 0, false, XDP_PKT_CONTD}, + {umem_size, XSK_UMEM__LARGE_FRAME_SIZE, 0, false, XDP_PKT_CONTD}, + /* Invalid len in the middle */ + {0, XSK_UMEM__LARGE_FRAME_SIZE, 0, false, XDP_PKT_CONTD}, + {0, XSK_UMEM__INVALID_FRAME_SIZE, 0, false, XDP_PKT_CONTD}, + /* Invalid options in the middle */ + {0, XSK_UMEM__LARGE_FRAME_SIZE, 0, false, XDP_PKT_CONTD}, + {0, XSK_UMEM__LARGE_FRAME_SIZE, 0, false, XSK_DESC__INVALID_OPTION}, + /* Transmit 2 frags, receive 3 */ + {0, XSK_UMEM__MAX_FRAME_SIZE, 0, true, XDP_PKT_CONTD}, + {0, XSK_UMEM__MAX_FRAME_SIZE, 0, true, 0}, + /* Middle frame crosses chunk boundary with small length */ + {0, XSK_UMEM__LARGE_FRAME_SIZE, 0, false, XDP_PKT_CONTD}, + {-MIN_PKT_SIZE / 2, MIN_PKT_SIZE, 0, false, 0}, + /* Valid packet for synch so that something is received */ + {0, MIN_PKT_SIZE, 0, true, 0}}; + + if (umem->unaligned_mode) { + /* Crossing a chunk boundary allowed */ + pkts[12].valid = true; + pkts[13].valid = true; + } + + test->mtu = MAX_ETH_JUMBO_SIZE; + pkt_stream_generate_custom(test, pkts, ARRAY_SIZE(pkts)); + return testapp_validate_traffic(test); +} + +int testapp_invalid_desc(struct test_spec *test) +{ + struct xsk_umem_info *umem = test->ifobj_tx->umem; + u64 umem_size = umem->num_frames * umem->frame_size; + struct pkt pkts[] = { + /* Zero packet address allowed */ + {0, MIN_PKT_SIZE, 0, true}, + /* Allowed packet */ + {0, MIN_PKT_SIZE, 0, true}, + /* Straddling the start of umem */ + {-2, MIN_PKT_SIZE, 0, false}, + /* Packet too large */ + {0, XSK_UMEM__INVALID_FRAME_SIZE, 0, false}, + /* Up to end of umem allowed */ + {umem_size - MIN_PKT_SIZE - 2 * umem->frame_size, MIN_PKT_SIZE, 0, true}, + /* After umem ends */ + {umem_size, MIN_PKT_SIZE, 0, false}, + /* Straddle the end of umem */ + {umem_size - MIN_PKT_SIZE / 2, MIN_PKT_SIZE, 0, false}, + /* Straddle a 4K boundary */ + {0x1000 - MIN_PKT_SIZE / 2, MIN_PKT_SIZE, 0, false}, + /* Straddle a 2K boundary */ + {0x800 - MIN_PKT_SIZE / 2, MIN_PKT_SIZE, 0, true}, + /* Valid packet for synch so that something is received */ + {0, MIN_PKT_SIZE, 0, true}}; + + if (umem->unaligned_mode) { + /* Crossing a page boundary allowed */ + pkts[7].valid = true; + } + if (umem->frame_size == XSK_UMEM__DEFAULT_FRAME_SIZE / 2) { + /* Crossing a 2K frame size boundary not allowed */ + pkts[8].valid = false; + } + + if (test->ifobj_tx->shared_umem) { + pkts[4].offset += umem_size; + pkts[5].offset += umem_size; + pkts[6].offset += umem_size; + } + + pkt_stream_generate_custom(test, pkts, ARRAY_SIZE(pkts)); + return testapp_validate_traffic(test); +} + +int testapp_xdp_drop(struct test_spec *test) +{ + struct xsk_xdp_progs *skel_rx = test->ifobj_rx->xdp_progs; + struct xsk_xdp_progs *skel_tx = test->ifobj_tx->xdp_progs; + + test_spec_set_xdp_prog(test, skel_rx->progs.xsk_xdp_drop, skel_tx->progs.xsk_xdp_drop, + skel_rx->maps.xsk, skel_tx->maps.xsk); + + pkt_stream_receive_half(test); + return testapp_validate_traffic(test); +} + +int testapp_xdp_metadata_copy(struct test_spec *test) +{ + struct xsk_xdp_progs *skel_rx = test->ifobj_rx->xdp_progs; + struct xsk_xdp_progs *skel_tx = test->ifobj_tx->xdp_progs; + + test_spec_set_xdp_prog(test, skel_rx->progs.xsk_xdp_populate_metadata, + skel_tx->progs.xsk_xdp_populate_metadata, + skel_rx->maps.xsk, skel_tx->maps.xsk); + test->ifobj_rx->use_metadata = true; + + skel_rx->bss->count = 0; + + return testapp_validate_traffic(test); +} + +int testapp_xdp_shared_umem(struct test_spec *test) +{ + struct xsk_xdp_progs *skel_rx = test->ifobj_rx->xdp_progs; + struct xsk_xdp_progs *skel_tx = test->ifobj_tx->xdp_progs; + + test->total_steps = 1; + test->nb_sockets = 2; + + test_spec_set_xdp_prog(test, skel_rx->progs.xsk_xdp_shared_umem, + skel_tx->progs.xsk_xdp_shared_umem, + skel_rx->maps.xsk, skel_tx->maps.xsk); + + pkt_stream_even_odd_sequence(test); + + return testapp_validate_traffic(test); +} + +int testapp_poll_txq_tmout(struct test_spec *test) +{ + test->ifobj_tx->use_poll = true; + /* create invalid frame by set umem frame_size and pkt length equal to 2048 */ + test->ifobj_tx->umem->frame_size = 2048; + pkt_stream_replace(test, 2 * DEFAULT_PKT_CNT, 2048); + return testapp_validate_traffic_single_thread(test, test->ifobj_tx); +} + +int testapp_poll_rxq_tmout(struct test_spec *test) +{ + test->ifobj_rx->use_poll = true; + return testapp_validate_traffic_single_thread(test, test->ifobj_rx); +} + +int testapp_too_many_frags(struct test_spec *test) +{ + struct pkt *pkts; + u32 max_frags, i; + int ret; + + if (test->mode == TEST_MODE_ZC) { + max_frags = test->ifobj_tx->xdp_zc_max_segs; + } else { + max_frags = get_max_skb_frags(); + if (!max_frags) { + ksft_print_msg("Can't get MAX_SKB_FRAGS from system, using default (17)\n"); + max_frags = 17; + } + max_frags += 1; + } + + pkts = calloc(2 * max_frags + 2, sizeof(struct pkt)); + if (!pkts) + return TEST_FAILURE; + + test->mtu = MAX_ETH_JUMBO_SIZE; + + /* Valid packet for synch */ + pkts[0].len = MIN_PKT_SIZE; + pkts[0].valid = true; + + /* One valid packet with the max amount of frags */ + for (i = 1; i < max_frags + 1; i++) { + pkts[i].len = MIN_PKT_SIZE; + pkts[i].options = XDP_PKT_CONTD; + pkts[i].valid = true; + } + pkts[max_frags].options = 0; + + /* An invalid packet with the max amount of frags but signals packet + * continues on the last frag + */ + for (i = max_frags + 1; i < 2 * max_frags + 1; i++) { + pkts[i].len = MIN_PKT_SIZE; + pkts[i].options = XDP_PKT_CONTD; + pkts[i].valid = false; + } + + /* Valid packet for synch */ + pkts[2 * max_frags + 1].len = MIN_PKT_SIZE; + pkts[2 * max_frags + 1].valid = true; + + pkt_stream_generate_custom(test, pkts, 2 * max_frags + 2); + ret = testapp_validate_traffic(test); + + free(pkts); + return ret; +} + +static int xsk_load_xdp_programs(struct ifobject *ifobj) +{ + ifobj->xdp_progs = xsk_xdp_progs__open_and_load(); + if (libbpf_get_error(ifobj->xdp_progs)) + return libbpf_get_error(ifobj->xdp_progs); + + return 0; +} + +/* Simple test */ +static bool hugepages_present(void) +{ + size_t mmap_sz = 2 * DEFAULT_UMEM_BUFFERS * XSK_UMEM__DEFAULT_FRAME_SIZE; + void *bufs; + + bufs = mmap(NULL, mmap_sz, PROT_READ | PROT_WRITE, + MAP_PRIVATE | MAP_ANONYMOUS | MAP_HUGETLB, -1, MAP_HUGE_2MB); + if (bufs == MAP_FAILED) + return false; + + mmap_sz = ceil_u64(mmap_sz, HUGEPAGE_SIZE) * HUGEPAGE_SIZE; + munmap(bufs, mmap_sz); + return true; +} + +void init_iface(struct ifobject *ifobj, thread_func_t func_ptr) +{ + LIBBPF_OPTS(bpf_xdp_query_opts, query_opts); + int err; + + ifobj->func_ptr = func_ptr; + + err = xsk_load_xdp_programs(ifobj); + if (err) { + ksft_print_msg("Error loading XDP program\n"); + exit_with_error(err); + } + + if (hugepages_present()) + ifobj->unaligned_supp = true; + + err = bpf_xdp_query(ifobj->ifindex, XDP_FLAGS_DRV_MODE, &query_opts); + if (err) { + ksft_print_msg("Error querying XDP capabilities\n"); + exit_with_error(-err); + } + if (query_opts.feature_flags & NETDEV_XDP_ACT_RX_SG) + ifobj->multi_buff_supp = true; + if (query_opts.feature_flags & NETDEV_XDP_ACT_XSK_ZEROCOPY) { + if (query_opts.xdp_zc_max_segs > 1) { + ifobj->multi_buff_zc_supp = true; + ifobj->xdp_zc_max_segs = query_opts.xdp_zc_max_segs; + } else { + ifobj->xdp_zc_max_segs = 0; + } + } +} + +int testapp_send_receive(struct test_spec *test) +{ + return testapp_validate_traffic(test); +} + +int testapp_send_receive_2k_frame(struct test_spec *test) +{ + test->ifobj_tx->umem->frame_size = 2048; + test->ifobj_rx->umem->frame_size = 2048; + pkt_stream_replace(test, DEFAULT_PKT_CNT, MIN_PKT_SIZE); + return testapp_validate_traffic(test); +} + +int testapp_poll_rx(struct test_spec *test) +{ + test->ifobj_rx->use_poll = true; + return testapp_validate_traffic(test); +} + +int testapp_poll_tx(struct test_spec *test) +{ + test->ifobj_tx->use_poll = true; + return testapp_validate_traffic(test); +} + +int testapp_aligned_inv_desc(struct test_spec *test) +{ + return testapp_invalid_desc(test); +} + +int testapp_aligned_inv_desc_2k_frame(struct test_spec *test) +{ + test->ifobj_tx->umem->frame_size = 2048; + test->ifobj_rx->umem->frame_size = 2048; + return testapp_invalid_desc(test); +} + +int testapp_unaligned_inv_desc(struct test_spec *test) +{ + test->ifobj_tx->umem->unaligned_mode = true; + test->ifobj_rx->umem->unaligned_mode = true; + return testapp_invalid_desc(test); +} + +int testapp_unaligned_inv_desc_4001_frame(struct test_spec *test) +{ + u64 page_size, umem_size; + + /* Odd frame size so the UMEM doesn't end near a page boundary. */ + test->ifobj_tx->umem->frame_size = 4001; + test->ifobj_rx->umem->frame_size = 4001; + test->ifobj_tx->umem->unaligned_mode = true; + test->ifobj_rx->umem->unaligned_mode = true; + /* This test exists to test descriptors that staddle the end of + * the UMEM but not a page. + */ + page_size = sysconf(_SC_PAGESIZE); + umem_size = test->ifobj_tx->umem->num_frames * test->ifobj_tx->umem->frame_size; + assert(umem_size % page_size > MIN_PKT_SIZE); + assert(umem_size % page_size < page_size - MIN_PKT_SIZE); + + return testapp_invalid_desc(test); +} + +int testapp_aligned_inv_desc_mb(struct test_spec *test) +{ + return testapp_invalid_desc_mb(test); +} + +int testapp_unaligned_inv_desc_mb(struct test_spec *test) +{ + test->ifobj_tx->umem->unaligned_mode = true; + test->ifobj_rx->umem->unaligned_mode = true; + return testapp_invalid_desc_mb(test); +} + +int testapp_xdp_metadata(struct test_spec *test) +{ + return testapp_xdp_metadata_copy(test); +} + +int testapp_xdp_metadata_mb(struct test_spec *test) +{ + test->mtu = MAX_ETH_JUMBO_SIZE; + return testapp_xdp_metadata_copy(test); +} + +int testapp_hw_sw_min_ring_size(struct test_spec *test) +{ + int ret; + + test->set_ring = true; + test->total_steps = 2; + test->ifobj_tx->ring.tx_pending = DEFAULT_BATCH_SIZE; + test->ifobj_tx->ring.rx_pending = DEFAULT_BATCH_SIZE * 2; + test->ifobj_tx->xsk->batch_size = 1; + test->ifobj_rx->xsk->batch_size = 1; + ret = testapp_validate_traffic(test); + if (ret) + return ret; + + /* Set batch size to hw_ring_size - 1 */ + test->ifobj_tx->xsk->batch_size = DEFAULT_BATCH_SIZE - 1; + test->ifobj_rx->xsk->batch_size = DEFAULT_BATCH_SIZE - 1; + return testapp_validate_traffic(test); +} + +int testapp_hw_sw_max_ring_size(struct test_spec *test) +{ + u32 max_descs = XSK_RING_PROD__DEFAULT_NUM_DESCS * 4; + int ret; + + test->set_ring = true; + test->total_steps = 2; + test->ifobj_tx->ring.tx_pending = test->ifobj_tx->ring.tx_max_pending; + test->ifobj_tx->ring.rx_pending = test->ifobj_tx->ring.rx_max_pending; + test->ifobj_rx->umem->num_frames = max_descs; + test->ifobj_rx->umem->fill_size = max_descs; + test->ifobj_rx->umem->comp_size = max_descs; + test->ifobj_tx->xsk->batch_size = XSK_RING_PROD__DEFAULT_NUM_DESCS; + test->ifobj_rx->xsk->batch_size = XSK_RING_PROD__DEFAULT_NUM_DESCS; + + ret = testapp_validate_traffic(test); + if (ret) + return ret; + + /* Set batch_size to 8152 for testing, as the ice HW ignores the 3 lowest bits when + * updating the Rx HW tail register. + */ + test->ifobj_tx->xsk->batch_size = test->ifobj_tx->ring.tx_max_pending - 8; + test->ifobj_rx->xsk->batch_size = test->ifobj_tx->ring.tx_max_pending - 8; + pkt_stream_replace(test, max_descs, MIN_PKT_SIZE); + return testapp_validate_traffic(test); +} + +static int testapp_xdp_adjust_tail(struct test_spec *test, int adjust_value) +{ + struct xsk_xdp_progs *skel_rx = test->ifobj_rx->xdp_progs; + struct xsk_xdp_progs *skel_tx = test->ifobj_tx->xdp_progs; + + test_spec_set_xdp_prog(test, skel_rx->progs.xsk_xdp_adjust_tail, + skel_tx->progs.xsk_xdp_adjust_tail, + skel_rx->maps.xsk, skel_tx->maps.xsk); + + skel_rx->bss->adjust_value = adjust_value; + + return testapp_validate_traffic(test); +} + +static int testapp_adjust_tail(struct test_spec *test, u32 value, u32 pkt_len) +{ + int ret; + + test->adjust_tail_support = true; + test->adjust_tail = true; + test->total_steps = 1; + + pkt_stream_replace_ifobject(test->ifobj_tx, DEFAULT_BATCH_SIZE, pkt_len); + pkt_stream_replace_ifobject(test->ifobj_rx, DEFAULT_BATCH_SIZE, pkt_len + value); + + ret = testapp_xdp_adjust_tail(test, value); + if (ret) + return ret; + + if (!test->adjust_tail_support) { + ksft_print_msg("%s %sResize pkt with bpf_xdp_adjust_tail() not supported\n", + mode_string(test), busy_poll_string(test)); + return TEST_SKIP; + } + + return 0; +} + +int testapp_adjust_tail_shrink(struct test_spec *test) +{ + /* Shrink by 4 bytes for testing purpose */ + return testapp_adjust_tail(test, -4, MIN_PKT_SIZE * 2); +} + +int testapp_adjust_tail_shrink_mb(struct test_spec *test) +{ + test->mtu = MAX_ETH_JUMBO_SIZE; + /* Shrink by the frag size */ + return testapp_adjust_tail(test, -XSK_UMEM__MAX_FRAME_SIZE, XSK_UMEM__LARGE_FRAME_SIZE * 2); +} + +int testapp_adjust_tail_grow(struct test_spec *test) +{ + /* Grow by 4 bytes for testing purpose */ + return testapp_adjust_tail(test, 4, MIN_PKT_SIZE * 2); +} + +int testapp_adjust_tail_grow_mb(struct test_spec *test) +{ + test->mtu = MAX_ETH_JUMBO_SIZE; + /* Grow by (frag_size - last_frag_Size) - 1 to stay inside the last fragment */ + return testapp_adjust_tail(test, (XSK_UMEM__MAX_FRAME_SIZE / 2) - 1, + XSK_UMEM__LARGE_FRAME_SIZE * 2); +} + +int testapp_tx_queue_consumer(struct test_spec *test) +{ + int nr_packets; + + if (test->mode == TEST_MODE_ZC) { + ksft_print_msg("Can not run TX_QUEUE_CONSUMER test for ZC mode\n"); + return TEST_SKIP; + } + + nr_packets = MAX_TX_BUDGET_DEFAULT + 1; + pkt_stream_replace(test, nr_packets, MIN_PKT_SIZE); + test->ifobj_tx->xsk->batch_size = nr_packets; + test->ifobj_tx->xsk->check_consumer = true; + + return testapp_validate_traffic(test); +} + +struct ifobject *ifobject_create(void) +{ + struct ifobject *ifobj; + + ifobj = calloc(1, sizeof(struct ifobject)); + if (!ifobj) + return NULL; + + ifobj->xsk_arr = calloc(MAX_SOCKETS, sizeof(*ifobj->xsk_arr)); + if (!ifobj->xsk_arr) + goto out_xsk_arr; + + ifobj->umem = calloc(1, sizeof(*ifobj->umem)); + if (!ifobj->umem) + goto out_umem; + + return ifobj; + +out_umem: + free(ifobj->xsk_arr); +out_xsk_arr: + free(ifobj); + return NULL; +} + +void ifobject_delete(struct ifobject *ifobj) +{ + free(ifobj->umem); + free(ifobj->xsk_arr); + free(ifobj); +} diff --git a/tools/testing/selftests/bpf/test_xsk.h b/tools/testing/selftests/bpf/test_xsk.h new file mode 100644 index 00000000000000..fb546cab39fdfb --- /dev/null +++ b/tools/testing/selftests/bpf/test_xsk.h @@ -0,0 +1,297 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +#ifndef TEST_XSK_H_ +#define TEST_XSK_H_ + +#include +#include + +#include "../kselftest.h" +#include "xsk.h" + +#ifndef SO_PREFER_BUSY_POLL +#define SO_PREFER_BUSY_POLL 69 +#endif + +#ifndef SO_BUSY_POLL_BUDGET +#define SO_BUSY_POLL_BUDGET 70 +#endif + +#define TEST_PASS 0 +#define TEST_FAILURE -1 +#define TEST_CONTINUE 1 +#define TEST_SKIP 2 + +#define DEFAULT_PKT_CNT (4 * 1024) +#define DEFAULT_UMEM_BUFFERS (DEFAULT_PKT_CNT / 4) +#define HUGEPAGE_SIZE (2 * 1024 * 1024) +#define MIN_PKT_SIZE 64 +#define MAX_ETH_PKT_SIZE 1518 +#define MAX_INTERFACE_NAME_CHARS 16 +#define MAX_TEST_NAME_SIZE 48 +#define SOCK_RECONF_CTR 10 +#define USLEEP_MAX 10000 + +extern bool opt_verbose; +#define print_verbose(x...) do { if (opt_verbose) ksft_print_msg(x); } while (0) + +static void __exit_with_error(int error, const char *file, const char *func, int line) +{ + ksft_test_result_fail("[%s:%s:%i]: ERROR: %d/\"%s\"\n", file, func, line, error, + strerror(error)); + ksft_exit_xfail(); +} +#define exit_with_error(error) __exit_with_error(error, __FILE__, __func__, __LINE__) + +static inline u32 ceil_u32(u32 a, u32 b) +{ + return (a + b - 1) / b; +} + +static inline u64 ceil_u64(u64 a, u64 b) +{ + return (a + b - 1) / b; +} + +/* Simple test */ +enum test_mode { + TEST_MODE_SKB, + TEST_MODE_DRV, + TEST_MODE_ZC, + TEST_MODE_ALL +}; + +struct ifobject; +struct test_spec; +typedef int (*validation_func_t)(struct ifobject *ifobj); +typedef void *(*thread_func_t)(void *arg); +typedef int (*test_func_t)(struct test_spec *test); + +struct xsk_socket_info { + struct xsk_ring_cons rx; + struct xsk_ring_prod tx; + struct xsk_umem_info *umem; + struct xsk_socket *xsk; + struct pkt_stream *pkt_stream; + u32 outstanding_tx; + u32 rxqsize; + u32 batch_size; + u8 dst_mac[ETH_ALEN]; + u8 src_mac[ETH_ALEN]; + bool check_consumer; +}; + +int kick_rx(struct xsk_socket_info *xsk); +int kick_tx(struct xsk_socket_info *xsk); + +struct xsk_umem_info { + struct xsk_ring_prod fq; + struct xsk_ring_cons cq; + struct xsk_umem *umem; + u64 next_buffer; + u32 num_frames; + u32 frame_headroom; + void *buffer; + u32 frame_size; + u32 base_addr; + u32 fill_size; + u32 comp_size; + bool unaligned_mode; +}; + +struct set_hw_ring { + u32 default_tx; + u32 default_rx; +}; + +int hw_ring_size_reset(struct ifobject *ifobj); + +struct ifobject { + char ifname[MAX_INTERFACE_NAME_CHARS]; + struct xsk_socket_info *xsk; + struct xsk_socket_info *xsk_arr; + struct xsk_umem_info *umem; + thread_func_t func_ptr; + validation_func_t validation_func; + struct xsk_xdp_progs *xdp_progs; + struct bpf_map *xskmap; + struct bpf_program *xdp_prog; + struct ethtool_ringparam ring; + struct set_hw_ring set_ring; + enum test_mode mode; + int ifindex; + int mtu; + u32 bind_flags; + u32 xdp_zc_max_segs; + bool tx_on; + bool rx_on; + bool use_poll; + bool busy_poll; + bool use_fill_ring; + bool release_rx; + bool shared_umem; + bool use_metadata; + bool unaligned_supp; + bool multi_buff_supp; + bool multi_buff_zc_supp; + bool hw_ring_size_supp; +}; +struct ifobject *ifobject_create(void); +void ifobject_delete(struct ifobject *ifobj); +void init_iface(struct ifobject *ifobj, thread_func_t func_ptr); + +int xsk_configure_umem(struct ifobject *ifobj, struct xsk_umem_info *umem, void *buffer, u64 size); +int xsk_configure_socket(struct xsk_socket_info *xsk, struct xsk_umem_info *umem, + struct ifobject *ifobject, bool shared); + + +struct pkt { + int offset; + u32 len; + u32 pkt_nb; + bool valid; + u16 options; +}; + +struct pkt_stream { + u32 nb_pkts; + u32 current_pkt_nb; + struct pkt *pkts; + u32 max_pkt_len; + u32 nb_rx_pkts; + u32 nb_valid_entries; + bool verbatim; +}; + +static inline bool pkt_continues(u32 options) +{ + return options & XDP_PKT_CONTD; +} + +struct pkt_stream *pkt_stream_generate(u32 nb_pkts, u32 pkt_len); +void pkt_stream_delete(struct pkt_stream *pkt_stream); +void pkt_stream_reset(struct pkt_stream *pkt_stream); +void pkt_stream_restore_default(struct test_spec *test); + +struct test_spec { + struct ifobject *ifobj_tx; + struct ifobject *ifobj_rx; + struct pkt_stream *tx_pkt_stream_default; + struct pkt_stream *rx_pkt_stream_default; + struct bpf_program *xdp_prog_rx; + struct bpf_program *xdp_prog_tx; + struct bpf_map *xskmap_rx; + struct bpf_map *xskmap_tx; + test_func_t test_func; + int mtu; + u16 total_steps; + u16 current_step; + u16 nb_sockets; + bool fail; + bool set_ring; + bool adjust_tail; + bool adjust_tail_support; + enum test_mode mode; + char name[MAX_TEST_NAME_SIZE]; +}; + +#define busy_poll_string(test) (test)->ifobj_tx->busy_poll ? "BUSY-POLL " : "" +static inline char *mode_string(struct test_spec *test) +{ + switch (test->mode) { + case TEST_MODE_SKB: + return "SKB"; + case TEST_MODE_DRV: + return "DRV"; + case TEST_MODE_ZC: + return "ZC"; + default: + return "BOGUS"; + } +} + +void test_init(struct test_spec *test, struct ifobject *ifobj_tx, + struct ifobject *ifobj_rx, enum test_mode mode, + const struct test_spec *test_to_run); + +int testapp_adjust_tail_grow(struct test_spec *test); +int testapp_adjust_tail_grow_mb(struct test_spec *test); +int testapp_adjust_tail_shrink(struct test_spec *test); +int testapp_adjust_tail_shrink_mb(struct test_spec *test); +int testapp_aligned_inv_desc(struct test_spec *test); +int testapp_aligned_inv_desc_2k_frame(struct test_spec *test); +int testapp_aligned_inv_desc_mb(struct test_spec *test); +int testapp_bidirectional(struct test_spec *test); +int testapp_headroom(struct test_spec *test); +int testapp_hw_sw_max_ring_size(struct test_spec *test); +int testapp_hw_sw_min_ring_size(struct test_spec *test); +int testapp_poll_rx(struct test_spec *test); +int testapp_poll_rxq_tmout(struct test_spec *test); +int testapp_poll_tx(struct test_spec *test); +int testapp_poll_txq_tmout(struct test_spec *test); +int testapp_send_receive(struct test_spec *test); +int testapp_send_receive_2k_frame(struct test_spec *test); +int testapp_send_receive_mb(struct test_spec *test); +int testapp_send_receive_unaligned(struct test_spec *test); +int testapp_send_receive_unaligned_mb(struct test_spec *test); +int testapp_single_pkt(struct test_spec *test); +int testapp_stats_fill_empty(struct test_spec *test); +int testapp_stats_rx_dropped(struct test_spec *test); +int testapp_stats_tx_invalid_descs(struct test_spec *test); +int testapp_stats_rx_full(struct test_spec *test); +int testapp_teardown(struct test_spec *test); +int testapp_too_many_frags(struct test_spec *test); +int testapp_tx_queue_consumer(struct test_spec *test); +int testapp_unaligned_inv_desc(struct test_spec *test); +int testapp_unaligned_inv_desc_4001_frame(struct test_spec *test); +int testapp_unaligned_inv_desc_mb(struct test_spec *test); +int testapp_xdp_drop(struct test_spec *test); +int testapp_xdp_metadata(struct test_spec *test); +int testapp_xdp_metadata_mb(struct test_spec *test); +int testapp_xdp_prog_cleanup(struct test_spec *test); +int testapp_xdp_shared_umem(struct test_spec *test); + +void *worker_testapp_validate_rx(void *arg); +void *worker_testapp_validate_tx(void *arg); + +static const struct test_spec tests[] = { + {.name = "SEND_RECEIVE", .test_func = testapp_send_receive}, + {.name = "SEND_RECEIVE_2K_FRAME", .test_func = testapp_send_receive_2k_frame}, + {.name = "SEND_RECEIVE_SINGLE_PKT", .test_func = testapp_single_pkt}, + {.name = "POLL_RX", .test_func = testapp_poll_rx}, + {.name = "POLL_TX", .test_func = testapp_poll_tx}, + {.name = "POLL_RXQ_FULL", .test_func = testapp_poll_rxq_tmout}, + {.name = "POLL_TXQ_FULL", .test_func = testapp_poll_txq_tmout}, + {.name = "SEND_RECEIVE_UNALIGNED", .test_func = testapp_send_receive_unaligned}, + {.name = "ALIGNED_INV_DESC", .test_func = testapp_aligned_inv_desc}, + {.name = "ALIGNED_INV_DESC_2K_FRAME_SIZE", .test_func = testapp_aligned_inv_desc_2k_frame}, + {.name = "UNALIGNED_INV_DESC", .test_func = testapp_unaligned_inv_desc}, + {.name = "UNALIGNED_INV_DESC_4001_FRAME_SIZE", + .test_func = testapp_unaligned_inv_desc_4001_frame}, + {.name = "UMEM_HEADROOM", .test_func = testapp_headroom}, + {.name = "TEARDOWN", .test_func = testapp_teardown}, + {.name = "BIDIRECTIONAL", .test_func = testapp_bidirectional}, + {.name = "STAT_RX_DROPPED", .test_func = testapp_stats_rx_dropped}, + {.name = "STAT_TX_INVALID", .test_func = testapp_stats_tx_invalid_descs}, + {.name = "STAT_RX_FULL", .test_func = testapp_stats_rx_full}, + {.name = "STAT_FILL_EMPTY", .test_func = testapp_stats_fill_empty}, + {.name = "XDP_PROG_CLEANUP", .test_func = testapp_xdp_prog_cleanup}, + {.name = "XDP_DROP_HALF", .test_func = testapp_xdp_drop}, + {.name = "XDP_SHARED_UMEM", .test_func = testapp_xdp_shared_umem}, + {.name = "XDP_METADATA_COPY", .test_func = testapp_xdp_metadata}, + {.name = "XDP_METADATA_COPY_MULTI_BUFF", .test_func = testapp_xdp_metadata_mb}, + {.name = "SEND_RECEIVE_9K_PACKETS", .test_func = testapp_send_receive_mb}, + {.name = "SEND_RECEIVE_UNALIGNED_9K_PACKETS", + .test_func = testapp_send_receive_unaligned_mb}, + {.name = "ALIGNED_INV_DESC_MULTI_BUFF", .test_func = testapp_aligned_inv_desc_mb}, + {.name = "UNALIGNED_INV_DESC_MULTI_BUFF", .test_func = testapp_unaligned_inv_desc_mb}, + {.name = "TOO_MANY_FRAGS", .test_func = testapp_too_many_frags}, + {.name = "HW_SW_MIN_RING_SIZE", .test_func = testapp_hw_sw_min_ring_size}, + {.name = "HW_SW_MAX_RING_SIZE", .test_func = testapp_hw_sw_max_ring_size}, + {.name = "XDP_ADJUST_TAIL_SHRINK", .test_func = testapp_adjust_tail_shrink}, + {.name = "XDP_ADJUST_TAIL_SHRINK_MULTI_BUFF", .test_func = testapp_adjust_tail_shrink_mb}, + {.name = "XDP_ADJUST_TAIL_GROW", .test_func = testapp_adjust_tail_grow}, + {.name = "XDP_ADJUST_TAIL_GROW_MULTI_BUFF", .test_func = testapp_adjust_tail_grow_mb}, + {.name = "TX_QUEUE_CONSUMER", .test_func = testapp_tx_queue_consumer}, + }; + +#endif /* TEST_XSK_H_ */ diff --git a/tools/testing/selftests/bpf/xskxceiver.c b/tools/testing/selftests/bpf/xskxceiver.c index 352adc8df2d1cd..8e108e3162695d 100644 --- a/tools/testing/selftests/bpf/xskxceiver.c +++ b/tools/testing/selftests/bpf/xskxceiver.c @@ -74,31 +74,23 @@ #define _GNU_SOURCE #include #include -#include #include #include #include #include #include -#include #include #include #include #include -#include -#include -#include #include #include #include -#include #include #include -#include -#include #include -#include +#include "test_xsk.h" #include "xsk_xdp_progs.skel.h" #include "xsk.h" #include "xskxceiver.h" @@ -109,181 +101,12 @@ #include -#define MAX_TX_BUDGET_DEFAULT 32 - -static bool opt_verbose; static bool opt_print_tests; static enum test_mode opt_mode = TEST_MODE_ALL; static u32 opt_run_test = RUN_ALL_TESTS; void test__fail(void) { /* for network_helpers.c */ } -static void __exit_with_error(int error, const char *file, const char *func, int line) -{ - ksft_test_result_fail("[%s:%s:%i]: ERROR: %d/\"%s\"\n", file, func, line, error, - strerror(error)); - ksft_exit_xfail(); -} - -#define exit_with_error(error) __exit_with_error(error, __FILE__, __func__, __LINE__) -#define busy_poll_string(test) (test)->ifobj_tx->busy_poll ? "BUSY-POLL " : "" -static char *mode_string(struct test_spec *test) -{ - switch (test->mode) { - case TEST_MODE_SKB: - return "SKB"; - case TEST_MODE_DRV: - return "DRV"; - case TEST_MODE_ZC: - return "ZC"; - default: - return "BOGUS"; - } -} - -static void report_failure(struct test_spec *test) -{ - if (test->fail) - return; - - ksft_test_result_fail("FAIL: %s %s%s\n", mode_string(test), busy_poll_string(test), - test->name); - test->fail = true; -} - -/* The payload is a word consisting of a packet sequence number in the upper - * 16-bits and a intra packet data sequence number in the lower 16 bits. So the 3rd packet's - * 5th word of data will contain the number (2<<16) | 4 as they are numbered from 0. - */ -static void write_payload(void *dest, u32 pkt_nb, u32 start, u32 size) -{ - u32 *ptr = (u32 *)dest, i; - - start /= sizeof(*ptr); - size /= sizeof(*ptr); - for (i = 0; i < size; i++) - ptr[i] = htonl(pkt_nb << 16 | (i + start)); -} - -static void gen_eth_hdr(struct xsk_socket_info *xsk, struct ethhdr *eth_hdr) -{ - memcpy(eth_hdr->h_dest, xsk->dst_mac, ETH_ALEN); - memcpy(eth_hdr->h_source, xsk->src_mac, ETH_ALEN); - eth_hdr->h_proto = htons(ETH_P_LOOPBACK); -} - -static bool is_umem_valid(struct ifobject *ifobj) -{ - return !!ifobj->umem->umem; -} - -static u32 mode_to_xdp_flags(enum test_mode mode) -{ - return (mode == TEST_MODE_SKB) ? XDP_FLAGS_SKB_MODE : XDP_FLAGS_DRV_MODE; -} - -static u64 umem_size(struct xsk_umem_info *umem) -{ - return umem->num_frames * umem->frame_size; -} - -static int xsk_configure_umem(struct ifobject *ifobj, struct xsk_umem_info *umem, void *buffer, - u64 size) -{ - struct xsk_umem_config cfg = { - .fill_size = XSK_RING_PROD__DEFAULT_NUM_DESCS, - .comp_size = XSK_RING_CONS__DEFAULT_NUM_DESCS, - .frame_size = umem->frame_size, - .frame_headroom = umem->frame_headroom, - .flags = XSK_UMEM__DEFAULT_FLAGS - }; - int ret; - - if (umem->fill_size) - cfg.fill_size = umem->fill_size; - - if (umem->comp_size) - cfg.comp_size = umem->comp_size; - - if (umem->unaligned_mode) - cfg.flags |= XDP_UMEM_UNALIGNED_CHUNK_FLAG; - - ret = xsk_umem__create(&umem->umem, buffer, size, - &umem->fq, &umem->cq, &cfg); - if (ret) - return ret; - - umem->buffer = buffer; - if (ifobj->shared_umem && ifobj->rx_on) { - umem->base_addr = umem_size(umem); - umem->next_buffer = umem_size(umem); - } - - return 0; -} - -static u64 umem_alloc_buffer(struct xsk_umem_info *umem) -{ - u64 addr; - - addr = umem->next_buffer; - umem->next_buffer += umem->frame_size; - if (umem->next_buffer >= umem->base_addr + umem_size(umem)) - umem->next_buffer = umem->base_addr; - - return addr; -} - -static void umem_reset_alloc(struct xsk_umem_info *umem) -{ - umem->next_buffer = 0; -} - -static void enable_busy_poll(struct xsk_socket_info *xsk) -{ - int sock_opt; - - sock_opt = 1; - if (setsockopt(xsk_socket__fd(xsk->xsk), SOL_SOCKET, SO_PREFER_BUSY_POLL, - (void *)&sock_opt, sizeof(sock_opt)) < 0) - exit_with_error(errno); - - sock_opt = 20; - if (setsockopt(xsk_socket__fd(xsk->xsk), SOL_SOCKET, SO_BUSY_POLL, - (void *)&sock_opt, sizeof(sock_opt)) < 0) - exit_with_error(errno); - - sock_opt = xsk->batch_size; - if (setsockopt(xsk_socket__fd(xsk->xsk), SOL_SOCKET, SO_BUSY_POLL_BUDGET, - (void *)&sock_opt, sizeof(sock_opt)) < 0) - exit_with_error(errno); -} - -static int __xsk_configure_socket(struct xsk_socket_info *xsk, struct xsk_umem_info *umem, - struct ifobject *ifobject, bool shared) -{ - struct xsk_socket_config cfg = {}; - struct xsk_ring_cons *rxr; - struct xsk_ring_prod *txr; - - xsk->umem = umem; - cfg.rx_size = xsk->rxqsize; - cfg.tx_size = XSK_RING_PROD__DEFAULT_NUM_DESCS; - cfg.bind_flags = ifobject->bind_flags; - if (shared) - cfg.bind_flags |= XDP_SHARED_UMEM; - if (ifobject->mtu > MAX_ETH_PKT_SIZE) - cfg.bind_flags |= XDP_USE_SG; - if (umem->comp_size) - cfg.tx_size = umem->comp_size; - if (umem->fill_size) - cfg.rx_size = umem->fill_size; - - txr = ifobject->tx_on ? &xsk->tx : NULL; - rxr = ifobject->rx_on ? &xsk->rx : NULL; - return xsk_socket__create(&xsk->xsk, ifobject->ifindex, 0, umem->umem, rxr, txr, &cfg); -} - static bool ifobj_zc_avail(struct ifobject *ifobject) { size_t umem_sz = DEFAULT_UMEM_BUFFERS * XSK_UMEM__DEFAULT_FRAME_SIZE; @@ -314,7 +137,7 @@ static bool ifobj_zc_avail(struct ifobject *ifobject) ifobject->bind_flags = XDP_USE_NEED_WAKEUP | XDP_ZEROCOPY; ifobject->rx_on = true; xsk->rxqsize = XSK_RING_CONS__DEFAULT_NUM_DESCS; - ret = __xsk_configure_socket(xsk, umem, ifobject, false); + ret = xsk_configure_socket(xsk, umem, ifobject, false); if (!ret) zc_avail = true; @@ -327,25 +150,6 @@ static bool ifobj_zc_avail(struct ifobject *ifobject) return zc_avail; } -#define MAX_SKB_FRAGS_PATH "/proc/sys/net/core/max_skb_frags" -static unsigned int get_max_skb_frags(void) -{ - unsigned int max_skb_frags = 0; - FILE *file; - - file = fopen(MAX_SKB_FRAGS_PATH, "r"); - if (!file) { - ksft_print_msg("Error opening %s\n", MAX_SKB_FRAGS_PATH); - return 0; - } - - if (fscanf(file, "%u", &max_skb_frags) != 1) - ksft_print_msg("Error reading %s\n", MAX_SKB_FRAGS_PATH); - - fclose(file); - return max_skb_frags; -} - static struct option long_options[] = { {"interface", required_argument, 0, 'i'}, {"busy-poll", no_argument, 0, 'b'}, @@ -446,2327 +250,66 @@ static void parse_command_line(struct ifobject *ifobj_tx, struct ifobject *ifobj } } -static int set_ring_size(struct ifobject *ifobj) -{ - int ret; - u32 ctr = 0; - - while (ctr++ < SOCK_RECONF_CTR) { - ret = set_hw_ring_size(ifobj->ifname, &ifobj->ring); - if (!ret) - break; - - /* Retry if it fails */ - if (ctr >= SOCK_RECONF_CTR || errno != EBUSY) - return -errno; - - usleep(USLEEP_MAX); - } - - return ret; -} - -static int hw_ring_size_reset(struct ifobject *ifobj) -{ - ifobj->ring.tx_pending = ifobj->set_ring.default_tx; - ifobj->ring.rx_pending = ifobj->set_ring.default_rx; - return set_ring_size(ifobj); -} - -static void __test_spec_init(struct test_spec *test, struct ifobject *ifobj_tx, - struct ifobject *ifobj_rx) +static void xsk_unload_xdp_programs(struct ifobject *ifobj) { - u32 i, j; - - for (i = 0; i < MAX_INTERFACES; i++) { - struct ifobject *ifobj = i ? ifobj_rx : ifobj_tx; - - ifobj->xsk = &ifobj->xsk_arr[0]; - ifobj->use_poll = false; - ifobj->use_fill_ring = true; - ifobj->release_rx = true; - ifobj->validation_func = NULL; - ifobj->use_metadata = false; - - if (i == 0) { - ifobj->rx_on = false; - ifobj->tx_on = true; - } else { - ifobj->rx_on = true; - ifobj->tx_on = false; - } - - memset(ifobj->umem, 0, sizeof(*ifobj->umem)); - ifobj->umem->num_frames = DEFAULT_UMEM_BUFFERS; - ifobj->umem->frame_size = XSK_UMEM__DEFAULT_FRAME_SIZE; - - for (j = 0; j < MAX_SOCKETS; j++) { - memset(&ifobj->xsk_arr[j], 0, sizeof(ifobj->xsk_arr[j])); - ifobj->xsk_arr[j].rxqsize = XSK_RING_CONS__DEFAULT_NUM_DESCS; - ifobj->xsk_arr[j].batch_size = DEFAULT_BATCH_SIZE; - if (i == 0) - ifobj->xsk_arr[j].pkt_stream = test->tx_pkt_stream_default; - else - ifobj->xsk_arr[j].pkt_stream = test->rx_pkt_stream_default; - - memcpy(ifobj->xsk_arr[j].src_mac, g_mac, ETH_ALEN); - memcpy(ifobj->xsk_arr[j].dst_mac, g_mac, ETH_ALEN); - ifobj->xsk_arr[j].src_mac[5] += ((j * 2) + 0); - ifobj->xsk_arr[j].dst_mac[5] += ((j * 2) + 1); - } - } - - if (ifobj_tx->hw_ring_size_supp) - hw_ring_size_reset(ifobj_tx); - - test->ifobj_tx = ifobj_tx; - test->ifobj_rx = ifobj_rx; - test->current_step = 0; - test->total_steps = 1; - test->nb_sockets = 1; - test->fail = false; - test->set_ring = false; - test->adjust_tail = false; - test->adjust_tail_support = false; - test->mtu = MAX_ETH_PKT_SIZE; - test->xdp_prog_rx = ifobj_rx->xdp_progs->progs.xsk_def_prog; - test->xskmap_rx = ifobj_rx->xdp_progs->maps.xsk; - test->xdp_prog_tx = ifobj_tx->xdp_progs->progs.xsk_def_prog; - test->xskmap_tx = ifobj_tx->xdp_progs->maps.xsk; + xsk_xdp_progs__destroy(ifobj->xdp_progs); } -static void test_spec_init(struct test_spec *test, struct ifobject *ifobj_tx, - struct ifobject *ifobj_rx, enum test_mode mode, - const struct test_spec *test_to_run) +static void run_pkt_test(struct test_spec *test) { - struct pkt_stream *tx_pkt_stream; - struct pkt_stream *rx_pkt_stream; - u32 i; - - tx_pkt_stream = test->tx_pkt_stream_default; - rx_pkt_stream = test->rx_pkt_stream_default; - memset(test, 0, sizeof(*test)); - test->tx_pkt_stream_default = tx_pkt_stream; - test->rx_pkt_stream_default = rx_pkt_stream; + int ret; - for (i = 0; i < MAX_INTERFACES; i++) { - struct ifobject *ifobj = i ? ifobj_rx : ifobj_tx; + ret = test->test_func(test); - ifobj->bind_flags = XDP_USE_NEED_WAKEUP; - if (mode == TEST_MODE_ZC) - ifobj->bind_flags |= XDP_ZEROCOPY; - else - ifobj->bind_flags |= XDP_COPY; + switch (ret) { + case TEST_PASS: + ksft_test_result_pass("PASS: %s %s%s\n", mode_string(test), busy_poll_string(test), + test->name); + break; + case TEST_SKIP: + ksft_test_result_skip("SKIP: %s %s%s\n", mode_string(test), busy_poll_string(test), + test->name); + break; + case TEST_FAILURE: + ksft_test_result_fail("FAIL: %s %s%s\n", mode_string(test), busy_poll_string(test), + test->name); + break; + default: + ksft_test_result_fail("FAIL: %s %s%s -- Unexpected returned value (%d)\n", + mode_string(test), busy_poll_string(test), test->name, ret); } - strncpy(test->name, test_to_run->name, MAX_TEST_NAME_SIZE); - test->test_func = test_to_run->test_func; - test->mode = mode; - __test_spec_init(test, ifobj_tx, ifobj_rx); -} - -static void test_spec_reset(struct test_spec *test) -{ - __test_spec_init(test, test->ifobj_tx, test->ifobj_rx); + pkt_stream_restore_default(test); } -static void test_spec_set_xdp_prog(struct test_spec *test, struct bpf_program *xdp_prog_rx, - struct bpf_program *xdp_prog_tx, struct bpf_map *xskmap_rx, - struct bpf_map *xskmap_tx) +static bool is_xdp_supported(int ifindex) { - test->xdp_prog_rx = xdp_prog_rx; - test->xdp_prog_tx = xdp_prog_tx; - test->xskmap_rx = xskmap_rx; - test->xskmap_tx = xskmap_tx; -} + int flags = XDP_FLAGS_DRV_MODE; -static int test_spec_set_mtu(struct test_spec *test, int mtu) -{ + LIBBPF_OPTS(bpf_link_create_opts, opts, .flags = flags); + struct bpf_insn insns[2] = { + BPF_MOV64_IMM(BPF_REG_0, XDP_PASS), + BPF_EXIT_INSN() + }; + int prog_fd, insn_cnt = ARRAY_SIZE(insns); int err; - if (test->ifobj_rx->mtu != mtu) { - err = xsk_set_mtu(test->ifobj_rx->ifindex, mtu); - if (err) - return err; - test->ifobj_rx->mtu = mtu; - } - if (test->ifobj_tx->mtu != mtu) { - err = xsk_set_mtu(test->ifobj_tx->ifindex, mtu); - if (err) - return err; - test->ifobj_tx->mtu = mtu; - } - - return 0; -} - -static void pkt_stream_reset(struct pkt_stream *pkt_stream) -{ - if (pkt_stream) { - pkt_stream->current_pkt_nb = 0; - pkt_stream->nb_rx_pkts = 0; - } -} - -static struct pkt *pkt_stream_get_next_tx_pkt(struct pkt_stream *pkt_stream) -{ - if (pkt_stream->current_pkt_nb >= pkt_stream->nb_pkts) - return NULL; - - return &pkt_stream->pkts[pkt_stream->current_pkt_nb++]; -} - -static struct pkt *pkt_stream_get_next_rx_pkt(struct pkt_stream *pkt_stream, u32 *pkts_sent) -{ - while (pkt_stream->current_pkt_nb < pkt_stream->nb_pkts) { - (*pkts_sent)++; - if (pkt_stream->pkts[pkt_stream->current_pkt_nb].valid) - return &pkt_stream->pkts[pkt_stream->current_pkt_nb++]; - pkt_stream->current_pkt_nb++; - } - return NULL; -} - -static void pkt_stream_delete(struct pkt_stream *pkt_stream) -{ - free(pkt_stream->pkts); - free(pkt_stream); -} - -static void pkt_stream_restore_default(struct test_spec *test) -{ - struct pkt_stream *tx_pkt_stream = test->ifobj_tx->xsk->pkt_stream; - struct pkt_stream *rx_pkt_stream = test->ifobj_rx->xsk->pkt_stream; - - if (tx_pkt_stream != test->tx_pkt_stream_default) { - pkt_stream_delete(test->ifobj_tx->xsk->pkt_stream); - test->ifobj_tx->xsk->pkt_stream = test->tx_pkt_stream_default; - } - - if (rx_pkt_stream != test->rx_pkt_stream_default) { - pkt_stream_delete(test->ifobj_rx->xsk->pkt_stream); - test->ifobj_rx->xsk->pkt_stream = test->rx_pkt_stream_default; - } -} - -static struct pkt_stream *__pkt_stream_alloc(u32 nb_pkts) -{ - struct pkt_stream *pkt_stream; - - pkt_stream = calloc(1, sizeof(*pkt_stream)); - if (!pkt_stream) - return NULL; - - pkt_stream->pkts = calloc(nb_pkts, sizeof(*pkt_stream->pkts)); - if (!pkt_stream->pkts) { - free(pkt_stream); - return NULL; - } - - pkt_stream->nb_pkts = nb_pkts; - return pkt_stream; -} - -static bool pkt_continues(u32 options) -{ - return options & XDP_PKT_CONTD; -} - -static u32 ceil_u32(u32 a, u32 b) -{ - return (a + b - 1) / b; -} - -static u32 pkt_nb_frags(u32 frame_size, struct pkt_stream *pkt_stream, struct pkt *pkt) -{ - u32 nb_frags = 1, next_frag; - - if (!pkt) - return 1; - - if (!pkt_stream->verbatim) { - if (!pkt->valid || !pkt->len) - return 1; - return ceil_u32(pkt->len, frame_size); - } - - /* Search for the end of the packet in verbatim mode */ - if (!pkt_continues(pkt->options)) - return nb_frags; - - next_frag = pkt_stream->current_pkt_nb; - pkt++; - while (next_frag++ < pkt_stream->nb_pkts) { - nb_frags++; - if (!pkt_continues(pkt->options) || !pkt->valid) - break; - pkt++; - } - return nb_frags; -} - -static bool set_pkt_valid(int offset, u32 len) -{ - return len <= MAX_ETH_JUMBO_SIZE; -} - -static void pkt_set(struct pkt_stream *pkt_stream, struct pkt *pkt, int offset, u32 len) -{ - pkt->offset = offset; - pkt->len = len; - pkt->valid = set_pkt_valid(offset, len); -} - -static void pkt_stream_pkt_set(struct pkt_stream *pkt_stream, struct pkt *pkt, int offset, u32 len) -{ - bool prev_pkt_valid = pkt->valid; - - pkt_set(pkt_stream, pkt, offset, len); - pkt_stream->nb_valid_entries += pkt->valid - prev_pkt_valid; -} - -static u32 pkt_get_buffer_len(struct xsk_umem_info *umem, u32 len) -{ - return ceil_u32(len, umem->frame_size) * umem->frame_size; -} - -static struct pkt_stream *__pkt_stream_generate(u32 nb_pkts, u32 pkt_len, u32 nb_start, u32 nb_off) -{ - struct pkt_stream *pkt_stream; - u32 i; - - pkt_stream = __pkt_stream_alloc(nb_pkts); - if (!pkt_stream) - exit_with_error(ENOMEM); - - pkt_stream->nb_pkts = nb_pkts; - pkt_stream->max_pkt_len = pkt_len; - for (i = 0; i < nb_pkts; i++) { - struct pkt *pkt = &pkt_stream->pkts[i]; - - pkt_stream_pkt_set(pkt_stream, pkt, 0, pkt_len); - pkt->pkt_nb = nb_start + i * nb_off; - } - - return pkt_stream; -} - -static struct pkt_stream *pkt_stream_generate(u32 nb_pkts, u32 pkt_len) -{ - return __pkt_stream_generate(nb_pkts, pkt_len, 0, 1); -} - -static struct pkt_stream *pkt_stream_clone(struct pkt_stream *pkt_stream) -{ - return pkt_stream_generate(pkt_stream->nb_pkts, pkt_stream->pkts[0].len); -} - -static void pkt_stream_replace_ifobject(struct ifobject *ifobj, u32 nb_pkts, u32 pkt_len) -{ - ifobj->xsk->pkt_stream = pkt_stream_generate(nb_pkts, pkt_len); -} - -static void pkt_stream_replace(struct test_spec *test, u32 nb_pkts, u32 pkt_len) -{ - pkt_stream_replace_ifobject(test->ifobj_tx, nb_pkts, pkt_len); - pkt_stream_replace_ifobject(test->ifobj_rx, nb_pkts, pkt_len); -} - -static void __pkt_stream_replace_half(struct ifobject *ifobj, u32 pkt_len, - int offset) -{ - struct pkt_stream *pkt_stream; - u32 i; - - pkt_stream = pkt_stream_clone(ifobj->xsk->pkt_stream); - for (i = 1; i < ifobj->xsk->pkt_stream->nb_pkts; i += 2) - pkt_stream_pkt_set(pkt_stream, &pkt_stream->pkts[i], offset, pkt_len); - - ifobj->xsk->pkt_stream = pkt_stream; -} - -static void pkt_stream_replace_half(struct test_spec *test, u32 pkt_len, int offset) -{ - __pkt_stream_replace_half(test->ifobj_tx, pkt_len, offset); - __pkt_stream_replace_half(test->ifobj_rx, pkt_len, offset); -} - -static void pkt_stream_receive_half(struct test_spec *test) -{ - struct pkt_stream *pkt_stream = test->ifobj_tx->xsk->pkt_stream; - u32 i; - - test->ifobj_rx->xsk->pkt_stream = pkt_stream_generate(pkt_stream->nb_pkts, - pkt_stream->pkts[0].len); - pkt_stream = test->ifobj_rx->xsk->pkt_stream; - for (i = 1; i < pkt_stream->nb_pkts; i += 2) - pkt_stream->pkts[i].valid = false; - - pkt_stream->nb_valid_entries /= 2; -} - -static void pkt_stream_even_odd_sequence(struct test_spec *test) -{ - struct pkt_stream *pkt_stream; - u32 i; - - for (i = 0; i < test->nb_sockets; i++) { - pkt_stream = test->ifobj_tx->xsk_arr[i].pkt_stream; - pkt_stream = __pkt_stream_generate(pkt_stream->nb_pkts / 2, - pkt_stream->pkts[0].len, i, 2); - test->ifobj_tx->xsk_arr[i].pkt_stream = pkt_stream; + prog_fd = bpf_prog_load(BPF_PROG_TYPE_XDP, NULL, "GPL", insns, insn_cnt, NULL); + if (prog_fd < 0) + return false; - pkt_stream = test->ifobj_rx->xsk_arr[i].pkt_stream; - pkt_stream = __pkt_stream_generate(pkt_stream->nb_pkts / 2, - pkt_stream->pkts[0].len, i, 2); - test->ifobj_rx->xsk_arr[i].pkt_stream = pkt_stream; + err = bpf_xdp_attach(ifindex, prog_fd, flags, NULL); + if (err) { + close(prog_fd); + return false; } -} - -static u64 pkt_get_addr(struct pkt *pkt, struct xsk_umem_info *umem) -{ - if (!pkt->valid) - return pkt->offset; - return pkt->offset + umem_alloc_buffer(umem); -} -static void pkt_stream_cancel(struct pkt_stream *pkt_stream) -{ - pkt_stream->current_pkt_nb--; -} - -static void pkt_generate(struct xsk_socket_info *xsk, struct xsk_umem_info *umem, u64 addr, u32 len, - u32 pkt_nb, u32 bytes_written) -{ - void *data = xsk_umem__get_data(umem->buffer, addr); - - if (len < MIN_PKT_SIZE) - return; - - if (!bytes_written) { - gen_eth_hdr(xsk, data); - - len -= PKT_HDR_SIZE; - data += PKT_HDR_SIZE; - } else { - bytes_written -= PKT_HDR_SIZE; - } + bpf_xdp_detach(ifindex, flags, NULL); + close(prog_fd); - write_payload(data, pkt_nb, bytes_written, len); + return true; } -static struct pkt_stream *__pkt_stream_generate_custom(struct ifobject *ifobj, struct pkt *frames, - u32 nb_frames, bool verbatim) -{ - u32 i, len = 0, pkt_nb = 0, payload = 0; - struct pkt_stream *pkt_stream; - - pkt_stream = __pkt_stream_alloc(nb_frames); - if (!pkt_stream) - exit_with_error(ENOMEM); - - for (i = 0; i < nb_frames; i++) { - struct pkt *pkt = &pkt_stream->pkts[pkt_nb]; - struct pkt *frame = &frames[i]; - - pkt->offset = frame->offset; - if (verbatim) { - *pkt = *frame; - pkt->pkt_nb = payload; - if (!frame->valid || !pkt_continues(frame->options)) - payload++; - } else { - if (frame->valid) - len += frame->len; - if (frame->valid && pkt_continues(frame->options)) - continue; - - pkt->pkt_nb = pkt_nb; - pkt->len = len; - pkt->valid = frame->valid; - pkt->options = 0; - - len = 0; - } - - print_verbose("offset: %d len: %u valid: %u options: %u pkt_nb: %u\n", - pkt->offset, pkt->len, pkt->valid, pkt->options, pkt->pkt_nb); - - if (pkt->valid && pkt->len > pkt_stream->max_pkt_len) - pkt_stream->max_pkt_len = pkt->len; - - if (pkt->valid) - pkt_stream->nb_valid_entries++; - - pkt_nb++; - } - - pkt_stream->nb_pkts = pkt_nb; - pkt_stream->verbatim = verbatim; - return pkt_stream; -} - -static void pkt_stream_generate_custom(struct test_spec *test, struct pkt *pkts, u32 nb_pkts) -{ - struct pkt_stream *pkt_stream; - - pkt_stream = __pkt_stream_generate_custom(test->ifobj_tx, pkts, nb_pkts, true); - test->ifobj_tx->xsk->pkt_stream = pkt_stream; - - pkt_stream = __pkt_stream_generate_custom(test->ifobj_rx, pkts, nb_pkts, false); - test->ifobj_rx->xsk->pkt_stream = pkt_stream; -} - -static void pkt_print_data(u32 *data, u32 cnt) -{ - u32 i; - - for (i = 0; i < cnt; i++) { - u32 seqnum, pkt_nb; - - seqnum = ntohl(*data) & 0xffff; - pkt_nb = ntohl(*data) >> 16; - ksft_print_msg("%u:%u ", pkt_nb, seqnum); - data++; - } -} - -static void pkt_dump(void *pkt, u32 len, bool eth_header) -{ - struct ethhdr *ethhdr = pkt; - u32 i, *data; - - if (eth_header) { - /*extract L2 frame */ - ksft_print_msg("DEBUG>> L2: dst mac: "); - for (i = 0; i < ETH_ALEN; i++) - ksft_print_msg("%02X", ethhdr->h_dest[i]); - - ksft_print_msg("\nDEBUG>> L2: src mac: "); - for (i = 0; i < ETH_ALEN; i++) - ksft_print_msg("%02X", ethhdr->h_source[i]); - - data = pkt + PKT_HDR_SIZE; - } else { - data = pkt; - } - - /*extract L5 frame */ - ksft_print_msg("\nDEBUG>> L5: seqnum: "); - pkt_print_data(data, PKT_DUMP_NB_TO_PRINT); - ksft_print_msg("...."); - if (len > PKT_DUMP_NB_TO_PRINT * sizeof(u32)) { - ksft_print_msg("\n.... "); - pkt_print_data(data + len / sizeof(u32) - PKT_DUMP_NB_TO_PRINT, - PKT_DUMP_NB_TO_PRINT); - } - ksft_print_msg("\n---------------------------------------\n"); -} - -static bool is_offset_correct(struct xsk_umem_info *umem, struct pkt *pkt, u64 addr) -{ - u32 headroom = umem->unaligned_mode ? 0 : umem->frame_headroom; - u32 offset = addr % umem->frame_size, expected_offset; - int pkt_offset = pkt->valid ? pkt->offset : 0; - - if (!umem->unaligned_mode) - pkt_offset = 0; - - expected_offset = (pkt_offset + headroom + XDP_PACKET_HEADROOM) % umem->frame_size; - - if (offset == expected_offset) - return true; - - ksft_print_msg("[%s] expected [%u], got [%u]\n", __func__, expected_offset, offset); - return false; -} - -static bool is_metadata_correct(struct pkt *pkt, void *buffer, u64 addr) -{ - void *data = xsk_umem__get_data(buffer, addr); - struct xdp_info *meta = data - sizeof(struct xdp_info); - - if (meta->count != pkt->pkt_nb) { - ksft_print_msg("[%s] expected meta_count [%d], got meta_count [%llu]\n", - __func__, pkt->pkt_nb, - (unsigned long long)meta->count); - return false; - } - - return true; -} - -static bool is_adjust_tail_supported(struct xsk_xdp_progs *skel_rx) -{ - struct bpf_map *data_map; - int adjust_value = 0; - int key = 0; - int ret; - - data_map = bpf_object__find_map_by_name(skel_rx->obj, "xsk_xdp_.bss"); - if (!data_map || !bpf_map__is_internal(data_map)) { - ksft_print_msg("Error: could not find bss section of XDP program\n"); - exit_with_error(errno); - } - - ret = bpf_map_lookup_elem(bpf_map__fd(data_map), &key, &adjust_value); - if (ret) { - ksft_print_msg("Error: bpf_map_lookup_elem failed with error %d\n", ret); - exit_with_error(errno); - } - - /* Set the 'adjust_value' variable to -EOPNOTSUPP in the XDP program if the adjust_tail - * helper is not supported. Skip the adjust_tail test case in this scenario. - */ - return adjust_value != -EOPNOTSUPP; -} - -static bool is_frag_valid(struct xsk_umem_info *umem, u64 addr, u32 len, u32 expected_pkt_nb, - u32 bytes_processed) -{ - u32 seqnum, pkt_nb, *pkt_data, words_to_end, expected_seqnum; - void *data = xsk_umem__get_data(umem->buffer, addr); - - addr -= umem->base_addr; - - if (addr >= umem->num_frames * umem->frame_size || - addr + len > umem->num_frames * umem->frame_size) { - ksft_print_msg("Frag invalid addr: %llx len: %u\n", - (unsigned long long)addr, len); - return false; - } - if (!umem->unaligned_mode && addr % umem->frame_size + len > umem->frame_size) { - ksft_print_msg("Frag crosses frame boundary addr: %llx len: %u\n", - (unsigned long long)addr, len); - return false; - } - - pkt_data = data; - if (!bytes_processed) { - pkt_data += PKT_HDR_SIZE / sizeof(*pkt_data); - len -= PKT_HDR_SIZE; - } else { - bytes_processed -= PKT_HDR_SIZE; - } - - expected_seqnum = bytes_processed / sizeof(*pkt_data); - seqnum = ntohl(*pkt_data) & 0xffff; - pkt_nb = ntohl(*pkt_data) >> 16; - - if (expected_pkt_nb != pkt_nb) { - ksft_print_msg("[%s] expected pkt_nb [%u], got pkt_nb [%u]\n", - __func__, expected_pkt_nb, pkt_nb); - goto error; - } - if (expected_seqnum != seqnum) { - ksft_print_msg("[%s] expected seqnum at start [%u], got seqnum [%u]\n", - __func__, expected_seqnum, seqnum); - goto error; - } - - words_to_end = len / sizeof(*pkt_data) - 1; - pkt_data += words_to_end; - seqnum = ntohl(*pkt_data) & 0xffff; - expected_seqnum += words_to_end; - if (expected_seqnum != seqnum) { - ksft_print_msg("[%s] expected seqnum at end [%u], got seqnum [%u]\n", - __func__, expected_seqnum, seqnum); - goto error; - } - - return true; - -error: - pkt_dump(data, len, !bytes_processed); - return false; -} - -static bool is_pkt_valid(struct pkt *pkt, void *buffer, u64 addr, u32 len) -{ - if (pkt->len != len) { - ksft_print_msg("[%s] expected packet length [%d], got length [%d]\n", - __func__, pkt->len, len); - pkt_dump(xsk_umem__get_data(buffer, addr), len, true); - return false; - } - - return true; -} - -static u32 load_value(u32 *counter) -{ - return __atomic_load_n(counter, __ATOMIC_ACQUIRE); -} - -static bool kick_tx_with_check(struct xsk_socket_info *xsk, int *ret) -{ - u32 max_budget = MAX_TX_BUDGET_DEFAULT; - u32 cons, ready_to_send; - int delta; - - cons = load_value(xsk->tx.consumer); - ready_to_send = load_value(xsk->tx.producer) - cons; - *ret = sendto(xsk_socket__fd(xsk->xsk), NULL, 0, MSG_DONTWAIT, NULL, 0); - - delta = load_value(xsk->tx.consumer) - cons; - /* By default, xsk should consume exact @max_budget descs at one - * send in this case where hitting the max budget limit in while - * loop is triggered in __xsk_generic_xmit(). Please make sure that - * the number of descs to be sent is larger than @max_budget, or - * else the tx.consumer will be updated in xskq_cons_peek_desc() - * in time which hides the issue we try to verify. - */ - if (ready_to_send > max_budget && delta != max_budget) - return false; - - return true; -} - -static int kick_tx(struct xsk_socket_info *xsk) -{ - int ret; - - if (xsk->check_consumer) { - if (!kick_tx_with_check(xsk, &ret)) - return TEST_FAILURE; - } else { - ret = sendto(xsk_socket__fd(xsk->xsk), NULL, 0, MSG_DONTWAIT, NULL, 0); - } - if (ret >= 0) - return TEST_PASS; - if (errno == ENOBUFS || errno == EAGAIN || errno == EBUSY || errno == ENETDOWN) { - usleep(100); - return TEST_PASS; - } - return TEST_FAILURE; -} - -static int kick_rx(struct xsk_socket_info *xsk) -{ - int ret; - - ret = recvfrom(xsk_socket__fd(xsk->xsk), NULL, 0, MSG_DONTWAIT, NULL, NULL); - if (ret < 0) - return TEST_FAILURE; - - return TEST_PASS; -} - -static int complete_pkts(struct xsk_socket_info *xsk, int batch_size) -{ - unsigned int rcvd; - u32 idx; - int ret; - - if (xsk_ring_prod__needs_wakeup(&xsk->tx)) { - ret = kick_tx(xsk); - if (ret) - return TEST_FAILURE; - } - - rcvd = xsk_ring_cons__peek(&xsk->umem->cq, batch_size, &idx); - if (rcvd) { - if (rcvd > xsk->outstanding_tx) { - u64 addr = *xsk_ring_cons__comp_addr(&xsk->umem->cq, idx + rcvd - 1); - - ksft_print_msg("[%s] Too many packets completed\n", __func__); - ksft_print_msg("Last completion address: %llx\n", - (unsigned long long)addr); - return TEST_FAILURE; - } - - xsk_ring_cons__release(&xsk->umem->cq, rcvd); - xsk->outstanding_tx -= rcvd; - } - - return TEST_PASS; -} - -static int __receive_pkts(struct test_spec *test, struct xsk_socket_info *xsk) -{ - u32 frags_processed = 0, nb_frags = 0, pkt_len = 0; - u32 idx_rx = 0, idx_fq = 0, rcvd, pkts_sent = 0; - struct pkt_stream *pkt_stream = xsk->pkt_stream; - struct ifobject *ifobj = test->ifobj_rx; - struct xsk_umem_info *umem = xsk->umem; - struct pollfd fds = { }; - struct pkt *pkt; - u64 first_addr = 0; - int ret; - - fds.fd = xsk_socket__fd(xsk->xsk); - fds.events = POLLIN; - - ret = kick_rx(xsk); - if (ret) - return TEST_FAILURE; - - if (ifobj->use_poll) { - ret = poll(&fds, 1, POLL_TMOUT); - if (ret < 0) - return TEST_FAILURE; - - if (!ret) { - if (!is_umem_valid(test->ifobj_tx)) - return TEST_PASS; - - ksft_print_msg("ERROR: [%s] Poll timed out\n", __func__); - return TEST_CONTINUE; - } - - if (!(fds.revents & POLLIN)) - return TEST_CONTINUE; - } - - rcvd = xsk_ring_cons__peek(&xsk->rx, xsk->batch_size, &idx_rx); - if (!rcvd) - return TEST_CONTINUE; - - if (ifobj->use_fill_ring) { - ret = xsk_ring_prod__reserve(&umem->fq, rcvd, &idx_fq); - while (ret != rcvd) { - if (xsk_ring_prod__needs_wakeup(&umem->fq)) { - ret = poll(&fds, 1, POLL_TMOUT); - if (ret < 0) - return TEST_FAILURE; - } - ret = xsk_ring_prod__reserve(&umem->fq, rcvd, &idx_fq); - } - } - - while (frags_processed < rcvd) { - const struct xdp_desc *desc = xsk_ring_cons__rx_desc(&xsk->rx, idx_rx++); - u64 addr = desc->addr, orig; - - orig = xsk_umem__extract_addr(addr); - addr = xsk_umem__add_offset_to_addr(addr); - - if (!nb_frags) { - pkt = pkt_stream_get_next_rx_pkt(pkt_stream, &pkts_sent); - if (!pkt) { - ksft_print_msg("[%s] received too many packets addr: %lx len %u\n", - __func__, addr, desc->len); - return TEST_FAILURE; - } - } - - print_verbose("Rx: addr: %lx len: %u options: %u pkt_nb: %u valid: %u\n", - addr, desc->len, desc->options, pkt->pkt_nb, pkt->valid); - - if (!is_frag_valid(umem, addr, desc->len, pkt->pkt_nb, pkt_len) || - !is_offset_correct(umem, pkt, addr) || (ifobj->use_metadata && - !is_metadata_correct(pkt, umem->buffer, addr))) - return TEST_FAILURE; - - if (!nb_frags++) - first_addr = addr; - frags_processed++; - pkt_len += desc->len; - if (ifobj->use_fill_ring) - *xsk_ring_prod__fill_addr(&umem->fq, idx_fq++) = orig; - - if (pkt_continues(desc->options)) - continue; - - /* The complete packet has been received */ - if (!is_pkt_valid(pkt, umem->buffer, first_addr, pkt_len) || - !is_offset_correct(umem, pkt, addr)) - return TEST_FAILURE; - - pkt_stream->nb_rx_pkts++; - nb_frags = 0; - pkt_len = 0; - } - - if (nb_frags) { - /* In the middle of a packet. Start over from beginning of packet. */ - idx_rx -= nb_frags; - xsk_ring_cons__cancel(&xsk->rx, nb_frags); - if (ifobj->use_fill_ring) { - idx_fq -= nb_frags; - xsk_ring_prod__cancel(&umem->fq, nb_frags); - } - frags_processed -= nb_frags; - } - - if (ifobj->use_fill_ring) - xsk_ring_prod__submit(&umem->fq, frags_processed); - if (ifobj->release_rx) - xsk_ring_cons__release(&xsk->rx, frags_processed); - - pthread_mutex_lock(&pacing_mutex); - pkts_in_flight -= pkts_sent; - pthread_mutex_unlock(&pacing_mutex); - pkts_sent = 0; - -return TEST_CONTINUE; -} - -bool all_packets_received(struct test_spec *test, struct xsk_socket_info *xsk, u32 sock_num, - unsigned long *bitmap) -{ - struct pkt_stream *pkt_stream = xsk->pkt_stream; - - if (!pkt_stream) { - __set_bit(sock_num, bitmap); - return false; - } - - if (pkt_stream->nb_rx_pkts == pkt_stream->nb_valid_entries) { - __set_bit(sock_num, bitmap); - if (bitmap_full(bitmap, test->nb_sockets)) - return true; - } - - return false; -} - -static int receive_pkts(struct test_spec *test) -{ - struct timeval tv_end, tv_now, tv_timeout = {THREAD_TMOUT, 0}; - DECLARE_BITMAP(bitmap, test->nb_sockets); - struct xsk_socket_info *xsk; - u32 sock_num = 0; - int res, ret; - - ret = gettimeofday(&tv_now, NULL); - if (ret) - exit_with_error(errno); - - timeradd(&tv_now, &tv_timeout, &tv_end); - - while (1) { - xsk = &test->ifobj_rx->xsk_arr[sock_num]; - - if ((all_packets_received(test, xsk, sock_num, bitmap))) - break; - - res = __receive_pkts(test, xsk); - if (!(res == TEST_PASS || res == TEST_CONTINUE)) - return res; - - ret = gettimeofday(&tv_now, NULL); - if (ret) - exit_with_error(errno); - - if (timercmp(&tv_now, &tv_end, >)) { - ksft_print_msg("ERROR: [%s] Receive loop timed out\n", __func__); - return TEST_FAILURE; - } - sock_num = (sock_num + 1) % test->nb_sockets; - } - - return TEST_PASS; -} - -static int __send_pkts(struct ifobject *ifobject, struct xsk_socket_info *xsk, bool timeout) -{ - u32 i, idx = 0, valid_pkts = 0, valid_frags = 0, buffer_len; - struct pkt_stream *pkt_stream = xsk->pkt_stream; - struct xsk_umem_info *umem = ifobject->umem; - bool use_poll = ifobject->use_poll; - struct pollfd fds = { }; - int ret; - - buffer_len = pkt_get_buffer_len(umem, pkt_stream->max_pkt_len); - /* pkts_in_flight might be negative if many invalid packets are sent */ - if (pkts_in_flight >= (int)((umem_size(umem) - xsk->batch_size * buffer_len) / - buffer_len)) { - ret = kick_tx(xsk); - if (ret) - return TEST_FAILURE; - return TEST_CONTINUE; - } - - fds.fd = xsk_socket__fd(xsk->xsk); - fds.events = POLLOUT; - - while (xsk_ring_prod__reserve(&xsk->tx, xsk->batch_size, &idx) < xsk->batch_size) { - if (use_poll) { - ret = poll(&fds, 1, POLL_TMOUT); - if (timeout) { - if (ret < 0) { - ksft_print_msg("ERROR: [%s] Poll error %d\n", - __func__, errno); - return TEST_FAILURE; - } - if (ret == 0) - return TEST_PASS; - break; - } - if (ret <= 0) { - ksft_print_msg("ERROR: [%s] Poll error %d\n", - __func__, errno); - return TEST_FAILURE; - } - } - - complete_pkts(xsk, xsk->batch_size); - } - - for (i = 0; i < xsk->batch_size; i++) { - struct pkt *pkt = pkt_stream_get_next_tx_pkt(pkt_stream); - u32 nb_frags_left, nb_frags, bytes_written = 0; - - if (!pkt) - break; - - nb_frags = pkt_nb_frags(umem->frame_size, pkt_stream, pkt); - if (nb_frags > xsk->batch_size - i) { - pkt_stream_cancel(pkt_stream); - xsk_ring_prod__cancel(&xsk->tx, xsk->batch_size - i); - break; - } - nb_frags_left = nb_frags; - - while (nb_frags_left--) { - struct xdp_desc *tx_desc = xsk_ring_prod__tx_desc(&xsk->tx, idx + i); - - tx_desc->addr = pkt_get_addr(pkt, ifobject->umem); - if (pkt_stream->verbatim) { - tx_desc->len = pkt->len; - tx_desc->options = pkt->options; - } else if (nb_frags_left) { - tx_desc->len = umem->frame_size; - tx_desc->options = XDP_PKT_CONTD; - } else { - tx_desc->len = pkt->len - bytes_written; - tx_desc->options = 0; - } - if (pkt->valid) - pkt_generate(xsk, umem, tx_desc->addr, tx_desc->len, pkt->pkt_nb, - bytes_written); - bytes_written += tx_desc->len; - - print_verbose("Tx addr: %llx len: %u options: %u pkt_nb: %u\n", - tx_desc->addr, tx_desc->len, tx_desc->options, pkt->pkt_nb); - - if (nb_frags_left) { - i++; - if (pkt_stream->verbatim) - pkt = pkt_stream_get_next_tx_pkt(pkt_stream); - } - } - - if (pkt && pkt->valid) { - valid_pkts++; - valid_frags += nb_frags; - } - } - - pthread_mutex_lock(&pacing_mutex); - pkts_in_flight += valid_pkts; - pthread_mutex_unlock(&pacing_mutex); - - xsk_ring_prod__submit(&xsk->tx, i); - xsk->outstanding_tx += valid_frags; - - if (use_poll) { - ret = poll(&fds, 1, POLL_TMOUT); - if (ret <= 0) { - if (ret == 0 && timeout) - return TEST_PASS; - - ksft_print_msg("ERROR: [%s] Poll error %d\n", __func__, ret); - return TEST_FAILURE; - } - } - - if (!timeout) { - if (complete_pkts(xsk, i)) - return TEST_FAILURE; - - usleep(10); - return TEST_PASS; - } - - return TEST_CONTINUE; -} - -static int wait_for_tx_completion(struct xsk_socket_info *xsk) -{ - struct timeval tv_end, tv_now, tv_timeout = {THREAD_TMOUT, 0}; - int ret; - - ret = gettimeofday(&tv_now, NULL); - if (ret) - exit_with_error(errno); - timeradd(&tv_now, &tv_timeout, &tv_end); - - while (xsk->outstanding_tx) { - ret = gettimeofday(&tv_now, NULL); - if (ret) - exit_with_error(errno); - if (timercmp(&tv_now, &tv_end, >)) { - ksft_print_msg("ERROR: [%s] Transmission loop timed out\n", __func__); - return TEST_FAILURE; - } - - complete_pkts(xsk, xsk->batch_size); - } - - return TEST_PASS; -} - -bool all_packets_sent(struct test_spec *test, unsigned long *bitmap) -{ - return bitmap_full(bitmap, test->nb_sockets); -} - -static int send_pkts(struct test_spec *test, struct ifobject *ifobject) -{ - bool timeout = !is_umem_valid(test->ifobj_rx); - DECLARE_BITMAP(bitmap, test->nb_sockets); - u32 i, ret; - - while (!(all_packets_sent(test, bitmap))) { - for (i = 0; i < test->nb_sockets; i++) { - struct pkt_stream *pkt_stream; - - pkt_stream = ifobject->xsk_arr[i].pkt_stream; - if (!pkt_stream || pkt_stream->current_pkt_nb >= pkt_stream->nb_pkts) { - __set_bit(i, bitmap); - continue; - } - ret = __send_pkts(ifobject, &ifobject->xsk_arr[i], timeout); - if (ret == TEST_CONTINUE && !test->fail) - continue; - - if ((ret || test->fail) && !timeout) - return TEST_FAILURE; - - if (ret == TEST_PASS && timeout) - return ret; - - ret = wait_for_tx_completion(&ifobject->xsk_arr[i]); - if (ret) - return TEST_FAILURE; - } - } - - return TEST_PASS; -} - -static int get_xsk_stats(struct xsk_socket *xsk, struct xdp_statistics *stats) -{ - int fd = xsk_socket__fd(xsk), err; - socklen_t optlen, expected_len; - - optlen = sizeof(*stats); - err = getsockopt(fd, SOL_XDP, XDP_STATISTICS, stats, &optlen); - if (err) { - ksft_print_msg("[%s] getsockopt(XDP_STATISTICS) error %u %s\n", - __func__, -err, strerror(-err)); - return TEST_FAILURE; - } - - expected_len = sizeof(struct xdp_statistics); - if (optlen != expected_len) { - ksft_print_msg("[%s] getsockopt optlen error. Expected: %u got: %u\n", - __func__, expected_len, optlen); - return TEST_FAILURE; - } - - return TEST_PASS; -} - -static int validate_rx_dropped(struct ifobject *ifobject) -{ - struct xsk_socket *xsk = ifobject->xsk->xsk; - struct xdp_statistics stats; - int err; - - err = kick_rx(ifobject->xsk); - if (err) - return TEST_FAILURE; - - err = get_xsk_stats(xsk, &stats); - if (err) - return TEST_FAILURE; - - /* The receiver calls getsockopt after receiving the last (valid) - * packet which is not the final packet sent in this test (valid and - * invalid packets are sent in alternating fashion with the final - * packet being invalid). Since the last packet may or may not have - * been dropped already, both outcomes must be allowed. - */ - if (stats.rx_dropped == ifobject->xsk->pkt_stream->nb_pkts / 2 || - stats.rx_dropped == ifobject->xsk->pkt_stream->nb_pkts / 2 - 1) - return TEST_PASS; - - return TEST_FAILURE; -} - -static int validate_rx_full(struct ifobject *ifobject) -{ - struct xsk_socket *xsk = ifobject->xsk->xsk; - struct xdp_statistics stats; - int err; - - usleep(1000); - err = kick_rx(ifobject->xsk); - if (err) - return TEST_FAILURE; - - err = get_xsk_stats(xsk, &stats); - if (err) - return TEST_FAILURE; - - if (stats.rx_ring_full) - return TEST_PASS; - - return TEST_FAILURE; -} - -static int validate_fill_empty(struct ifobject *ifobject) -{ - struct xsk_socket *xsk = ifobject->xsk->xsk; - struct xdp_statistics stats; - int err; - - usleep(1000); - err = kick_rx(ifobject->xsk); - if (err) - return TEST_FAILURE; - - err = get_xsk_stats(xsk, &stats); - if (err) - return TEST_FAILURE; - - if (stats.rx_fill_ring_empty_descs) - return TEST_PASS; - - return TEST_FAILURE; -} - -static int validate_tx_invalid_descs(struct ifobject *ifobject) -{ - struct xsk_socket *xsk = ifobject->xsk->xsk; - int fd = xsk_socket__fd(xsk); - struct xdp_statistics stats; - socklen_t optlen; - int err; - - optlen = sizeof(stats); - err = getsockopt(fd, SOL_XDP, XDP_STATISTICS, &stats, &optlen); - if (err) { - ksft_print_msg("[%s] getsockopt(XDP_STATISTICS) error %u %s\n", - __func__, -err, strerror(-err)); - return TEST_FAILURE; - } - - if (stats.tx_invalid_descs != ifobject->xsk->pkt_stream->nb_pkts / 2) { - ksft_print_msg("[%s] tx_invalid_descs incorrect. Got [%llu] expected [%u]\n", - __func__, - (unsigned long long)stats.tx_invalid_descs, - ifobject->xsk->pkt_stream->nb_pkts); - return TEST_FAILURE; - } - - return TEST_PASS; -} - -static void xsk_configure_socket(struct test_spec *test, struct ifobject *ifobject, - struct xsk_umem_info *umem, bool tx) -{ - int i, ret; - - for (i = 0; i < test->nb_sockets; i++) { - bool shared = (ifobject->shared_umem && tx) ? true : !!i; - u32 ctr = 0; - - while (ctr++ < SOCK_RECONF_CTR) { - ret = __xsk_configure_socket(&ifobject->xsk_arr[i], umem, - ifobject, shared); - if (!ret) - break; - - /* Retry if it fails as xsk_socket__create() is asynchronous */ - if (ctr >= SOCK_RECONF_CTR) - exit_with_error(-ret); - usleep(USLEEP_MAX); - } - if (ifobject->busy_poll) - enable_busy_poll(&ifobject->xsk_arr[i]); - } -} - -static void thread_common_ops_tx(struct test_spec *test, struct ifobject *ifobject) -{ - xsk_configure_socket(test, ifobject, test->ifobj_rx->umem, true); - ifobject->xsk = &ifobject->xsk_arr[0]; - ifobject->xskmap = test->ifobj_rx->xskmap; - memcpy(ifobject->umem, test->ifobj_rx->umem, sizeof(struct xsk_umem_info)); - ifobject->umem->base_addr = 0; -} - -static void xsk_populate_fill_ring(struct xsk_umem_info *umem, struct pkt_stream *pkt_stream, - bool fill_up) -{ - u32 rx_frame_size = umem->frame_size - XDP_PACKET_HEADROOM; - u32 idx = 0, filled = 0, buffers_to_fill, nb_pkts; - int ret; - - if (umem->num_frames < XSK_RING_PROD__DEFAULT_NUM_DESCS) - buffers_to_fill = umem->num_frames; - else - buffers_to_fill = umem->fill_size; - - ret = xsk_ring_prod__reserve(&umem->fq, buffers_to_fill, &idx); - if (ret != buffers_to_fill) - exit_with_error(ENOSPC); - - while (filled < buffers_to_fill) { - struct pkt *pkt = pkt_stream_get_next_rx_pkt(pkt_stream, &nb_pkts); - u64 addr; - u32 i; - - for (i = 0; i < pkt_nb_frags(rx_frame_size, pkt_stream, pkt); i++) { - if (!pkt) { - if (!fill_up) - break; - addr = filled * umem->frame_size + umem->base_addr; - } else if (pkt->offset >= 0) { - addr = pkt->offset % umem->frame_size + umem_alloc_buffer(umem); - } else { - addr = pkt->offset + umem_alloc_buffer(umem); - } - - *xsk_ring_prod__fill_addr(&umem->fq, idx++) = addr; - if (++filled >= buffers_to_fill) - break; - } - } - xsk_ring_prod__submit(&umem->fq, filled); - xsk_ring_prod__cancel(&umem->fq, buffers_to_fill - filled); - - pkt_stream_reset(pkt_stream); - umem_reset_alloc(umem); -} - -static void thread_common_ops(struct test_spec *test, struct ifobject *ifobject) -{ - u64 umem_sz = ifobject->umem->num_frames * ifobject->umem->frame_size; - int mmap_flags = MAP_PRIVATE | MAP_ANONYMOUS | MAP_NORESERVE; - LIBBPF_OPTS(bpf_xdp_query_opts, opts); - void *bufs; - int ret; - u32 i; - - if (ifobject->umem->unaligned_mode) - mmap_flags |= MAP_HUGETLB | MAP_HUGE_2MB; - - if (ifobject->shared_umem) - umem_sz *= 2; - - bufs = mmap(NULL, umem_sz, PROT_READ | PROT_WRITE, mmap_flags, -1, 0); - if (bufs == MAP_FAILED) - exit_with_error(errno); - - ret = xsk_configure_umem(ifobject, ifobject->umem, bufs, umem_sz); - if (ret) - exit_with_error(-ret); - - xsk_configure_socket(test, ifobject, ifobject->umem, false); - - ifobject->xsk = &ifobject->xsk_arr[0]; - - if (!ifobject->rx_on) - return; - - xsk_populate_fill_ring(ifobject->umem, ifobject->xsk->pkt_stream, ifobject->use_fill_ring); - - for (i = 0; i < test->nb_sockets; i++) { - ifobject->xsk = &ifobject->xsk_arr[i]; - ret = xsk_update_xskmap(ifobject->xskmap, ifobject->xsk->xsk, i); - if (ret) - exit_with_error(errno); - } -} - -static void *worker_testapp_validate_tx(void *arg) -{ - struct test_spec *test = (struct test_spec *)arg; - struct ifobject *ifobject = test->ifobj_tx; - int err; - - if (test->current_step == 1) { - if (!ifobject->shared_umem) - thread_common_ops(test, ifobject); - else - thread_common_ops_tx(test, ifobject); - } - - err = send_pkts(test, ifobject); - - if (!err && ifobject->validation_func) - err = ifobject->validation_func(ifobject); - if (err) - report_failure(test); - - pthread_exit(NULL); -} - -static void *worker_testapp_validate_rx(void *arg) -{ - struct test_spec *test = (struct test_spec *)arg; - struct ifobject *ifobject = test->ifobj_rx; - int err; - - if (test->current_step == 1) { - thread_common_ops(test, ifobject); - } else { - xsk_clear_xskmap(ifobject->xskmap); - err = xsk_update_xskmap(ifobject->xskmap, ifobject->xsk->xsk, 0); - if (err) { - ksft_print_msg("Error: Failed to update xskmap, error %s\n", - strerror(-err)); - exit_with_error(-err); - } - } - - pthread_barrier_wait(&barr); - - err = receive_pkts(test); - - if (!err && ifobject->validation_func) - err = ifobject->validation_func(ifobject); - - if (err) { - if (test->adjust_tail && !is_adjust_tail_supported(ifobject->xdp_progs)) - test->adjust_tail_support = false; - else - report_failure(test); - } - - pthread_exit(NULL); -} - -static u64 ceil_u64(u64 a, u64 b) -{ - return (a + b - 1) / b; -} - -static void testapp_clean_xsk_umem(struct ifobject *ifobj) -{ - u64 umem_sz = ifobj->umem->num_frames * ifobj->umem->frame_size; - - if (ifobj->shared_umem) - umem_sz *= 2; - - umem_sz = ceil_u64(umem_sz, HUGEPAGE_SIZE) * HUGEPAGE_SIZE; - xsk_umem__delete(ifobj->umem->umem); - munmap(ifobj->umem->buffer, umem_sz); -} - -static void handler(int signum) -{ - pthread_exit(NULL); -} - -static bool xdp_prog_changed_rx(struct test_spec *test) -{ - struct ifobject *ifobj = test->ifobj_rx; - - return ifobj->xdp_prog != test->xdp_prog_rx || ifobj->mode != test->mode; -} - -static bool xdp_prog_changed_tx(struct test_spec *test) -{ - struct ifobject *ifobj = test->ifobj_tx; - - return ifobj->xdp_prog != test->xdp_prog_tx || ifobj->mode != test->mode; -} - -static void xsk_reattach_xdp(struct ifobject *ifobj, struct bpf_program *xdp_prog, - struct bpf_map *xskmap, enum test_mode mode) -{ - int err; - - xsk_detach_xdp_program(ifobj->ifindex, mode_to_xdp_flags(ifobj->mode)); - err = xsk_attach_xdp_program(xdp_prog, ifobj->ifindex, mode_to_xdp_flags(mode)); - if (err) { - ksft_print_msg("Error attaching XDP program\n"); - exit_with_error(-err); - } - - if (ifobj->mode != mode && (mode == TEST_MODE_DRV || mode == TEST_MODE_ZC)) - if (!xsk_is_in_mode(ifobj->ifindex, XDP_FLAGS_DRV_MODE)) { - ksft_print_msg("ERROR: XDP prog not in DRV mode\n"); - exit_with_error(EINVAL); - } - - ifobj->xdp_prog = xdp_prog; - ifobj->xskmap = xskmap; - ifobj->mode = mode; -} - -static void xsk_attach_xdp_progs(struct test_spec *test, struct ifobject *ifobj_rx, - struct ifobject *ifobj_tx) -{ - if (xdp_prog_changed_rx(test)) - xsk_reattach_xdp(ifobj_rx, test->xdp_prog_rx, test->xskmap_rx, test->mode); - - if (!ifobj_tx || ifobj_tx->shared_umem) - return; - - if (xdp_prog_changed_tx(test)) - xsk_reattach_xdp(ifobj_tx, test->xdp_prog_tx, test->xskmap_tx, test->mode); -} - -static int __testapp_validate_traffic(struct test_spec *test, struct ifobject *ifobj1, - struct ifobject *ifobj2) -{ - pthread_t t0, t1; - int err; - - if (test->mtu > MAX_ETH_PKT_SIZE) { - if (test->mode == TEST_MODE_ZC && (!ifobj1->multi_buff_zc_supp || - (ifobj2 && !ifobj2->multi_buff_zc_supp))) { - ksft_test_result_skip("Multi buffer for zero-copy not supported.\n"); - return TEST_SKIP; - } - if (test->mode != TEST_MODE_ZC && (!ifobj1->multi_buff_supp || - (ifobj2 && !ifobj2->multi_buff_supp))) { - ksft_test_result_skip("Multi buffer not supported.\n"); - return TEST_SKIP; - } - } - err = test_spec_set_mtu(test, test->mtu); - if (err) { - ksft_print_msg("Error, could not set mtu.\n"); - exit_with_error(err); - } - - if (ifobj2) { - if (pthread_barrier_init(&barr, NULL, 2)) - exit_with_error(errno); - pkt_stream_reset(ifobj2->xsk->pkt_stream); - } - - test->current_step++; - pkt_stream_reset(ifobj1->xsk->pkt_stream); - pkts_in_flight = 0; - - signal(SIGUSR1, handler); - /*Spawn RX thread */ - pthread_create(&t0, NULL, ifobj1->func_ptr, test); - - if (ifobj2) { - pthread_barrier_wait(&barr); - if (pthread_barrier_destroy(&barr)) - exit_with_error(errno); - - /*Spawn TX thread */ - pthread_create(&t1, NULL, ifobj2->func_ptr, test); - - pthread_join(t1, NULL); - } - - if (!ifobj2) - pthread_kill(t0, SIGUSR1); - else - pthread_join(t0, NULL); - - if (test->total_steps == test->current_step || test->fail) { - u32 i; - - if (ifobj2) - for (i = 0; i < test->nb_sockets; i++) - xsk_socket__delete(ifobj2->xsk_arr[i].xsk); - - for (i = 0; i < test->nb_sockets; i++) - xsk_socket__delete(ifobj1->xsk_arr[i].xsk); - - testapp_clean_xsk_umem(ifobj1); - if (ifobj2 && !ifobj2->shared_umem) - testapp_clean_xsk_umem(ifobj2); - } - - return !!test->fail; -} - -static int testapp_validate_traffic(struct test_spec *test) -{ - struct ifobject *ifobj_rx = test->ifobj_rx; - struct ifobject *ifobj_tx = test->ifobj_tx; - - if ((ifobj_rx->umem->unaligned_mode && !ifobj_rx->unaligned_supp) || - (ifobj_tx->umem->unaligned_mode && !ifobj_tx->unaligned_supp)) { - ksft_test_result_skip("No huge pages present.\n"); - return TEST_SKIP; - } - - if (test->set_ring) { - if (ifobj_tx->hw_ring_size_supp) { - if (set_ring_size(ifobj_tx)) { - ksft_test_result_skip("Failed to change HW ring size.\n"); - return TEST_FAILURE; - } - } else { - ksft_test_result_skip("Changing HW ring size not supported.\n"); - return TEST_SKIP; - } - } - - xsk_attach_xdp_progs(test, ifobj_rx, ifobj_tx); - return __testapp_validate_traffic(test, ifobj_rx, ifobj_tx); -} - -static int testapp_validate_traffic_single_thread(struct test_spec *test, struct ifobject *ifobj) -{ - return __testapp_validate_traffic(test, ifobj, NULL); -} - -static int testapp_teardown(struct test_spec *test) -{ - int i; - - for (i = 0; i < MAX_TEARDOWN_ITER; i++) { - if (testapp_validate_traffic(test)) - return TEST_FAILURE; - test_spec_reset(test); - } - - return TEST_PASS; -} - -static void swap_directions(struct ifobject **ifobj1, struct ifobject **ifobj2) -{ - thread_func_t tmp_func_ptr = (*ifobj1)->func_ptr; - struct ifobject *tmp_ifobj = (*ifobj1); - - (*ifobj1)->func_ptr = (*ifobj2)->func_ptr; - (*ifobj2)->func_ptr = tmp_func_ptr; - - *ifobj1 = *ifobj2; - *ifobj2 = tmp_ifobj; -} - -static int testapp_bidirectional(struct test_spec *test) -{ - int res; - - test->ifobj_tx->rx_on = true; - test->ifobj_rx->tx_on = true; - test->total_steps = 2; - if (testapp_validate_traffic(test)) - return TEST_FAILURE; - - print_verbose("Switching Tx/Rx direction\n"); - swap_directions(&test->ifobj_rx, &test->ifobj_tx); - res = __testapp_validate_traffic(test, test->ifobj_rx, test->ifobj_tx); - - swap_directions(&test->ifobj_rx, &test->ifobj_tx); - return res; -} - -static int swap_xsk_resources(struct test_spec *test) -{ - int ret; - - test->ifobj_tx->xsk_arr[0].pkt_stream = NULL; - test->ifobj_rx->xsk_arr[0].pkt_stream = NULL; - test->ifobj_tx->xsk_arr[1].pkt_stream = test->tx_pkt_stream_default; - test->ifobj_rx->xsk_arr[1].pkt_stream = test->rx_pkt_stream_default; - test->ifobj_tx->xsk = &test->ifobj_tx->xsk_arr[1]; - test->ifobj_rx->xsk = &test->ifobj_rx->xsk_arr[1]; - - ret = xsk_update_xskmap(test->ifobj_rx->xskmap, test->ifobj_rx->xsk->xsk, 0); - if (ret) - return TEST_FAILURE; - - return TEST_PASS; -} - -static int testapp_xdp_prog_cleanup(struct test_spec *test) -{ - test->total_steps = 2; - test->nb_sockets = 2; - if (testapp_validate_traffic(test)) - return TEST_FAILURE; - - if (swap_xsk_resources(test)) - return TEST_FAILURE; - return testapp_validate_traffic(test); -} - -static int testapp_headroom(struct test_spec *test) -{ - test->ifobj_rx->umem->frame_headroom = UMEM_HEADROOM_TEST_SIZE; - return testapp_validate_traffic(test); -} - -static int testapp_stats_rx_dropped(struct test_spec *test) -{ - if (test->mode == TEST_MODE_ZC) { - ksft_test_result_skip("Can not run RX_DROPPED test for ZC mode\n"); - return TEST_SKIP; - } - - pkt_stream_replace_half(test, MIN_PKT_SIZE * 4, 0); - test->ifobj_rx->umem->frame_headroom = test->ifobj_rx->umem->frame_size - - XDP_PACKET_HEADROOM - MIN_PKT_SIZE * 3; - pkt_stream_receive_half(test); - test->ifobj_rx->validation_func = validate_rx_dropped; - return testapp_validate_traffic(test); -} - -static int testapp_stats_tx_invalid_descs(struct test_spec *test) -{ - pkt_stream_replace_half(test, XSK_UMEM__INVALID_FRAME_SIZE, 0); - test->ifobj_tx->validation_func = validate_tx_invalid_descs; - return testapp_validate_traffic(test); -} - -static int testapp_stats_rx_full(struct test_spec *test) -{ - pkt_stream_replace(test, DEFAULT_UMEM_BUFFERS + DEFAULT_UMEM_BUFFERS / 2, MIN_PKT_SIZE); - test->ifobj_rx->xsk->pkt_stream = pkt_stream_generate(DEFAULT_UMEM_BUFFERS, MIN_PKT_SIZE); - - test->ifobj_rx->xsk->rxqsize = DEFAULT_UMEM_BUFFERS; - test->ifobj_rx->release_rx = false; - test->ifobj_rx->validation_func = validate_rx_full; - return testapp_validate_traffic(test); -} - -static int testapp_stats_fill_empty(struct test_spec *test) -{ - pkt_stream_replace(test, DEFAULT_UMEM_BUFFERS + DEFAULT_UMEM_BUFFERS / 2, MIN_PKT_SIZE); - test->ifobj_rx->xsk->pkt_stream = pkt_stream_generate(DEFAULT_UMEM_BUFFERS, MIN_PKT_SIZE); - - test->ifobj_rx->use_fill_ring = false; - test->ifobj_rx->validation_func = validate_fill_empty; - return testapp_validate_traffic(test); -} - -static int testapp_send_receive_unaligned(struct test_spec *test) -{ - test->ifobj_tx->umem->unaligned_mode = true; - test->ifobj_rx->umem->unaligned_mode = true; - /* Let half of the packets straddle a 4K buffer boundary */ - pkt_stream_replace_half(test, MIN_PKT_SIZE, -MIN_PKT_SIZE / 2); - - return testapp_validate_traffic(test); -} - -static int testapp_send_receive_unaligned_mb(struct test_spec *test) -{ - test->mtu = MAX_ETH_JUMBO_SIZE; - test->ifobj_tx->umem->unaligned_mode = true; - test->ifobj_rx->umem->unaligned_mode = true; - pkt_stream_replace(test, DEFAULT_PKT_CNT, MAX_ETH_JUMBO_SIZE); - return testapp_validate_traffic(test); -} - -static int testapp_single_pkt(struct test_spec *test) -{ - struct pkt pkts[] = {{0, MIN_PKT_SIZE, 0, true}}; - - pkt_stream_generate_custom(test, pkts, ARRAY_SIZE(pkts)); - return testapp_validate_traffic(test); -} - -static int testapp_send_receive_mb(struct test_spec *test) -{ - test->mtu = MAX_ETH_JUMBO_SIZE; - pkt_stream_replace(test, DEFAULT_PKT_CNT, MAX_ETH_JUMBO_SIZE); - - return testapp_validate_traffic(test); -} - -static int testapp_invalid_desc_mb(struct test_spec *test) -{ - struct xsk_umem_info *umem = test->ifobj_tx->umem; - u64 umem_size = umem->num_frames * umem->frame_size; - struct pkt pkts[] = { - /* Valid packet for synch to start with */ - {0, MIN_PKT_SIZE, 0, true, 0}, - /* Zero frame len is not legal */ - {0, XSK_UMEM__LARGE_FRAME_SIZE, 0, false, XDP_PKT_CONTD}, - {0, XSK_UMEM__LARGE_FRAME_SIZE, 0, false, XDP_PKT_CONTD}, - {0, 0, 0, false, 0}, - /* Invalid address in the second frame */ - {0, XSK_UMEM__LARGE_FRAME_SIZE, 0, false, XDP_PKT_CONTD}, - {umem_size, XSK_UMEM__LARGE_FRAME_SIZE, 0, false, XDP_PKT_CONTD}, - /* Invalid len in the middle */ - {0, XSK_UMEM__LARGE_FRAME_SIZE, 0, false, XDP_PKT_CONTD}, - {0, XSK_UMEM__INVALID_FRAME_SIZE, 0, false, XDP_PKT_CONTD}, - /* Invalid options in the middle */ - {0, XSK_UMEM__LARGE_FRAME_SIZE, 0, false, XDP_PKT_CONTD}, - {0, XSK_UMEM__LARGE_FRAME_SIZE, 0, false, XSK_DESC__INVALID_OPTION}, - /* Transmit 2 frags, receive 3 */ - {0, XSK_UMEM__MAX_FRAME_SIZE, 0, true, XDP_PKT_CONTD}, - {0, XSK_UMEM__MAX_FRAME_SIZE, 0, true, 0}, - /* Middle frame crosses chunk boundary with small length */ - {0, XSK_UMEM__LARGE_FRAME_SIZE, 0, false, XDP_PKT_CONTD}, - {-MIN_PKT_SIZE / 2, MIN_PKT_SIZE, 0, false, 0}, - /* Valid packet for synch so that something is received */ - {0, MIN_PKT_SIZE, 0, true, 0}}; - - if (umem->unaligned_mode) { - /* Crossing a chunk boundary allowed */ - pkts[12].valid = true; - pkts[13].valid = true; - } - - test->mtu = MAX_ETH_JUMBO_SIZE; - pkt_stream_generate_custom(test, pkts, ARRAY_SIZE(pkts)); - return testapp_validate_traffic(test); -} - -static int testapp_invalid_desc(struct test_spec *test) -{ - struct xsk_umem_info *umem = test->ifobj_tx->umem; - u64 umem_size = umem->num_frames * umem->frame_size; - struct pkt pkts[] = { - /* Zero packet address allowed */ - {0, MIN_PKT_SIZE, 0, true}, - /* Allowed packet */ - {0, MIN_PKT_SIZE, 0, true}, - /* Straddling the start of umem */ - {-2, MIN_PKT_SIZE, 0, false}, - /* Packet too large */ - {0, XSK_UMEM__INVALID_FRAME_SIZE, 0, false}, - /* Up to end of umem allowed */ - {umem_size - MIN_PKT_SIZE - 2 * umem->frame_size, MIN_PKT_SIZE, 0, true}, - /* After umem ends */ - {umem_size, MIN_PKT_SIZE, 0, false}, - /* Straddle the end of umem */ - {umem_size - MIN_PKT_SIZE / 2, MIN_PKT_SIZE, 0, false}, - /* Straddle a 4K boundary */ - {0x1000 - MIN_PKT_SIZE / 2, MIN_PKT_SIZE, 0, false}, - /* Straddle a 2K boundary */ - {0x800 - MIN_PKT_SIZE / 2, MIN_PKT_SIZE, 0, true}, - /* Valid packet for synch so that something is received */ - {0, MIN_PKT_SIZE, 0, true}}; - - if (umem->unaligned_mode) { - /* Crossing a page boundary allowed */ - pkts[7].valid = true; - } - if (umem->frame_size == XSK_UMEM__DEFAULT_FRAME_SIZE / 2) { - /* Crossing a 2K frame size boundary not allowed */ - pkts[8].valid = false; - } - - if (test->ifobj_tx->shared_umem) { - pkts[4].offset += umem_size; - pkts[5].offset += umem_size; - pkts[6].offset += umem_size; - } - - pkt_stream_generate_custom(test, pkts, ARRAY_SIZE(pkts)); - return testapp_validate_traffic(test); -} - -static int testapp_xdp_drop(struct test_spec *test) -{ - struct xsk_xdp_progs *skel_rx = test->ifobj_rx->xdp_progs; - struct xsk_xdp_progs *skel_tx = test->ifobj_tx->xdp_progs; - - test_spec_set_xdp_prog(test, skel_rx->progs.xsk_xdp_drop, skel_tx->progs.xsk_xdp_drop, - skel_rx->maps.xsk, skel_tx->maps.xsk); - - pkt_stream_receive_half(test); - return testapp_validate_traffic(test); -} - -static int testapp_xdp_metadata_copy(struct test_spec *test) -{ - struct xsk_xdp_progs *skel_rx = test->ifobj_rx->xdp_progs; - struct xsk_xdp_progs *skel_tx = test->ifobj_tx->xdp_progs; - - test_spec_set_xdp_prog(test, skel_rx->progs.xsk_xdp_populate_metadata, - skel_tx->progs.xsk_xdp_populate_metadata, - skel_rx->maps.xsk, skel_tx->maps.xsk); - test->ifobj_rx->use_metadata = true; - - skel_rx->bss->count = 0; - - return testapp_validate_traffic(test); -} - -static int testapp_xdp_shared_umem(struct test_spec *test) -{ - struct xsk_xdp_progs *skel_rx = test->ifobj_rx->xdp_progs; - struct xsk_xdp_progs *skel_tx = test->ifobj_tx->xdp_progs; - - test->total_steps = 1; - test->nb_sockets = 2; - - test_spec_set_xdp_prog(test, skel_rx->progs.xsk_xdp_shared_umem, - skel_tx->progs.xsk_xdp_shared_umem, - skel_rx->maps.xsk, skel_tx->maps.xsk); - - pkt_stream_even_odd_sequence(test); - - return testapp_validate_traffic(test); -} - -static int testapp_poll_txq_tmout(struct test_spec *test) -{ - test->ifobj_tx->use_poll = true; - /* create invalid frame by set umem frame_size and pkt length equal to 2048 */ - test->ifobj_tx->umem->frame_size = 2048; - pkt_stream_replace(test, 2 * DEFAULT_PKT_CNT, 2048); - return testapp_validate_traffic_single_thread(test, test->ifobj_tx); -} - -static int testapp_poll_rxq_tmout(struct test_spec *test) -{ - test->ifobj_rx->use_poll = true; - return testapp_validate_traffic_single_thread(test, test->ifobj_rx); -} - -static int testapp_too_many_frags(struct test_spec *test) -{ - struct pkt *pkts; - u32 max_frags, i; - int ret; - - if (test->mode == TEST_MODE_ZC) { - max_frags = test->ifobj_tx->xdp_zc_max_segs; - } else { - max_frags = get_max_skb_frags(); - if (!max_frags) { - ksft_print_msg("Couldn't retrieve MAX_SKB_FRAGS from system, using default (17) value\n"); - max_frags = 17; - } - max_frags += 1; - } - - pkts = calloc(2 * max_frags + 2, sizeof(struct pkt)); - if (!pkts) - return TEST_FAILURE; - - test->mtu = MAX_ETH_JUMBO_SIZE; - - /* Valid packet for synch */ - pkts[0].len = MIN_PKT_SIZE; - pkts[0].valid = true; - - /* One valid packet with the max amount of frags */ - for (i = 1; i < max_frags + 1; i++) { - pkts[i].len = MIN_PKT_SIZE; - pkts[i].options = XDP_PKT_CONTD; - pkts[i].valid = true; - } - pkts[max_frags].options = 0; - - /* An invalid packet with the max amount of frags but signals packet - * continues on the last frag - */ - for (i = max_frags + 1; i < 2 * max_frags + 1; i++) { - pkts[i].len = MIN_PKT_SIZE; - pkts[i].options = XDP_PKT_CONTD; - pkts[i].valid = false; - } - - /* Valid packet for synch */ - pkts[2 * max_frags + 1].len = MIN_PKT_SIZE; - pkts[2 * max_frags + 1].valid = true; - - pkt_stream_generate_custom(test, pkts, 2 * max_frags + 2); - ret = testapp_validate_traffic(test); - - free(pkts); - return ret; -} - -static int xsk_load_xdp_programs(struct ifobject *ifobj) -{ - ifobj->xdp_progs = xsk_xdp_progs__open_and_load(); - if (libbpf_get_error(ifobj->xdp_progs)) - return libbpf_get_error(ifobj->xdp_progs); - - return 0; -} - -static void xsk_unload_xdp_programs(struct ifobject *ifobj) -{ - xsk_xdp_progs__destroy(ifobj->xdp_progs); -} - -/* Simple test */ -static bool hugepages_present(void) -{ - size_t mmap_sz = 2 * DEFAULT_UMEM_BUFFERS * XSK_UMEM__DEFAULT_FRAME_SIZE; - void *bufs; - - bufs = mmap(NULL, mmap_sz, PROT_READ | PROT_WRITE, - MAP_PRIVATE | MAP_ANONYMOUS | MAP_HUGETLB, -1, MAP_HUGE_2MB); - if (bufs == MAP_FAILED) - return false; - - mmap_sz = ceil_u64(mmap_sz, HUGEPAGE_SIZE) * HUGEPAGE_SIZE; - munmap(bufs, mmap_sz); - return true; -} - -static void init_iface(struct ifobject *ifobj, thread_func_t func_ptr) -{ - LIBBPF_OPTS(bpf_xdp_query_opts, query_opts); - int err; - - ifobj->func_ptr = func_ptr; - - err = xsk_load_xdp_programs(ifobj); - if (err) { - ksft_print_msg("Error loading XDP program\n"); - exit_with_error(err); - } - - if (hugepages_present()) - ifobj->unaligned_supp = true; - - err = bpf_xdp_query(ifobj->ifindex, XDP_FLAGS_DRV_MODE, &query_opts); - if (err) { - ksft_print_msg("Error querying XDP capabilities\n"); - exit_with_error(-err); - } - if (query_opts.feature_flags & NETDEV_XDP_ACT_RX_SG) - ifobj->multi_buff_supp = true; - if (query_opts.feature_flags & NETDEV_XDP_ACT_XSK_ZEROCOPY) { - if (query_opts.xdp_zc_max_segs > 1) { - ifobj->multi_buff_zc_supp = true; - ifobj->xdp_zc_max_segs = query_opts.xdp_zc_max_segs; - } else { - ifobj->xdp_zc_max_segs = 0; - } - } -} - -static int testapp_send_receive(struct test_spec *test) -{ - return testapp_validate_traffic(test); -} - -static int testapp_send_receive_2k_frame(struct test_spec *test) -{ - test->ifobj_tx->umem->frame_size = 2048; - test->ifobj_rx->umem->frame_size = 2048; - pkt_stream_replace(test, DEFAULT_PKT_CNT, MIN_PKT_SIZE); - return testapp_validate_traffic(test); -} - -static int testapp_poll_rx(struct test_spec *test) -{ - test->ifobj_rx->use_poll = true; - return testapp_validate_traffic(test); -} - -static int testapp_poll_tx(struct test_spec *test) -{ - test->ifobj_tx->use_poll = true; - return testapp_validate_traffic(test); -} - -static int testapp_aligned_inv_desc(struct test_spec *test) -{ - return testapp_invalid_desc(test); -} - -static int testapp_aligned_inv_desc_2k_frame(struct test_spec *test) -{ - test->ifobj_tx->umem->frame_size = 2048; - test->ifobj_rx->umem->frame_size = 2048; - return testapp_invalid_desc(test); -} - -static int testapp_unaligned_inv_desc(struct test_spec *test) -{ - test->ifobj_tx->umem->unaligned_mode = true; - test->ifobj_rx->umem->unaligned_mode = true; - return testapp_invalid_desc(test); -} - -static int testapp_unaligned_inv_desc_4001_frame(struct test_spec *test) -{ - u64 page_size, umem_size; - - /* Odd frame size so the UMEM doesn't end near a page boundary. */ - test->ifobj_tx->umem->frame_size = 4001; - test->ifobj_rx->umem->frame_size = 4001; - test->ifobj_tx->umem->unaligned_mode = true; - test->ifobj_rx->umem->unaligned_mode = true; - /* This test exists to test descriptors that staddle the end of - * the UMEM but not a page. - */ - page_size = sysconf(_SC_PAGESIZE); - umem_size = test->ifobj_tx->umem->num_frames * test->ifobj_tx->umem->frame_size; - assert(umem_size % page_size > MIN_PKT_SIZE); - assert(umem_size % page_size < page_size - MIN_PKT_SIZE); - - return testapp_invalid_desc(test); -} - -static int testapp_aligned_inv_desc_mb(struct test_spec *test) -{ - return testapp_invalid_desc_mb(test); -} - -static int testapp_unaligned_inv_desc_mb(struct test_spec *test) -{ - test->ifobj_tx->umem->unaligned_mode = true; - test->ifobj_rx->umem->unaligned_mode = true; - return testapp_invalid_desc_mb(test); -} - -static int testapp_xdp_metadata(struct test_spec *test) -{ - return testapp_xdp_metadata_copy(test); -} - -static int testapp_xdp_metadata_mb(struct test_spec *test) -{ - test->mtu = MAX_ETH_JUMBO_SIZE; - return testapp_xdp_metadata_copy(test); -} - -static int testapp_hw_sw_min_ring_size(struct test_spec *test) -{ - int ret; - - test->set_ring = true; - test->total_steps = 2; - test->ifobj_tx->ring.tx_pending = DEFAULT_BATCH_SIZE; - test->ifobj_tx->ring.rx_pending = DEFAULT_BATCH_SIZE * 2; - test->ifobj_tx->xsk->batch_size = 1; - test->ifobj_rx->xsk->batch_size = 1; - ret = testapp_validate_traffic(test); - if (ret) - return ret; - - /* Set batch size to hw_ring_size - 1 */ - test->ifobj_tx->xsk->batch_size = DEFAULT_BATCH_SIZE - 1; - test->ifobj_rx->xsk->batch_size = DEFAULT_BATCH_SIZE - 1; - return testapp_validate_traffic(test); -} - -static int testapp_hw_sw_max_ring_size(struct test_spec *test) -{ - u32 max_descs = XSK_RING_PROD__DEFAULT_NUM_DESCS * 4; - int ret; - - test->set_ring = true; - test->total_steps = 2; - test->ifobj_tx->ring.tx_pending = test->ifobj_tx->ring.tx_max_pending; - test->ifobj_tx->ring.rx_pending = test->ifobj_tx->ring.rx_max_pending; - test->ifobj_rx->umem->num_frames = max_descs; - test->ifobj_rx->umem->fill_size = max_descs; - test->ifobj_rx->umem->comp_size = max_descs; - test->ifobj_tx->xsk->batch_size = XSK_RING_PROD__DEFAULT_NUM_DESCS; - test->ifobj_rx->xsk->batch_size = XSK_RING_PROD__DEFAULT_NUM_DESCS; - - ret = testapp_validate_traffic(test); - if (ret) - return ret; - - /* Set batch_size to 8152 for testing, as the ice HW ignores the 3 lowest bits when - * updating the Rx HW tail register. - */ - test->ifobj_tx->xsk->batch_size = test->ifobj_tx->ring.tx_max_pending - 8; - test->ifobj_rx->xsk->batch_size = test->ifobj_tx->ring.tx_max_pending - 8; - pkt_stream_replace(test, max_descs, MIN_PKT_SIZE); - return testapp_validate_traffic(test); -} - -static int testapp_xdp_adjust_tail(struct test_spec *test, int adjust_value) -{ - struct xsk_xdp_progs *skel_rx = test->ifobj_rx->xdp_progs; - struct xsk_xdp_progs *skel_tx = test->ifobj_tx->xdp_progs; - - test_spec_set_xdp_prog(test, skel_rx->progs.xsk_xdp_adjust_tail, - skel_tx->progs.xsk_xdp_adjust_tail, - skel_rx->maps.xsk, skel_tx->maps.xsk); - - skel_rx->bss->adjust_value = adjust_value; - - return testapp_validate_traffic(test); -} - -static int testapp_adjust_tail(struct test_spec *test, u32 value, u32 pkt_len) -{ - int ret; - - test->adjust_tail_support = true; - test->adjust_tail = true; - test->total_steps = 1; - - pkt_stream_replace_ifobject(test->ifobj_tx, DEFAULT_BATCH_SIZE, pkt_len); - pkt_stream_replace_ifobject(test->ifobj_rx, DEFAULT_BATCH_SIZE, pkt_len + value); - - ret = testapp_xdp_adjust_tail(test, value); - if (ret) - return ret; - - if (!test->adjust_tail_support) { - ksft_test_result_skip("%s %sResize pkt with bpf_xdp_adjust_tail() not supported\n", - mode_string(test), busy_poll_string(test)); - return TEST_SKIP; - } - - return 0; -} - -static int testapp_adjust_tail_shrink(struct test_spec *test) -{ - /* Shrink by 4 bytes for testing purpose */ - return testapp_adjust_tail(test, -4, MIN_PKT_SIZE * 2); -} - -static int testapp_adjust_tail_shrink_mb(struct test_spec *test) -{ - test->mtu = MAX_ETH_JUMBO_SIZE; - /* Shrink by the frag size */ - return testapp_adjust_tail(test, -XSK_UMEM__MAX_FRAME_SIZE, XSK_UMEM__LARGE_FRAME_SIZE * 2); -} - -static int testapp_adjust_tail_grow(struct test_spec *test) -{ - /* Grow by 4 bytes for testing purpose */ - return testapp_adjust_tail(test, 4, MIN_PKT_SIZE * 2); -} - -static int testapp_adjust_tail_grow_mb(struct test_spec *test) -{ - test->mtu = MAX_ETH_JUMBO_SIZE; - /* Grow by (frag_size - last_frag_Size) - 1 to stay inside the last fragment */ - return testapp_adjust_tail(test, (XSK_UMEM__MAX_FRAME_SIZE / 2) - 1, - XSK_UMEM__LARGE_FRAME_SIZE * 2); -} - -static int testapp_tx_queue_consumer(struct test_spec *test) -{ - int nr_packets; - - if (test->mode == TEST_MODE_ZC) { - ksft_test_result_skip("Can not run TX_QUEUE_CONSUMER test for ZC mode\n"); - return TEST_SKIP; - } - - nr_packets = MAX_TX_BUDGET_DEFAULT + 1; - pkt_stream_replace(test, nr_packets, MIN_PKT_SIZE); - test->ifobj_tx->xsk->batch_size = nr_packets; - test->ifobj_tx->xsk->check_consumer = true; - - return testapp_validate_traffic(test); -} - -static void run_pkt_test(struct test_spec *test) -{ - int ret; - - ret = test->test_func(test); - - if (ret == TEST_PASS) - ksft_test_result_pass("PASS: %s %s%s\n", mode_string(test), busy_poll_string(test), - test->name); - pkt_stream_restore_default(test); -} - -static struct ifobject *ifobject_create(void) -{ - struct ifobject *ifobj; - - ifobj = calloc(1, sizeof(struct ifobject)); - if (!ifobj) - return NULL; - - ifobj->xsk_arr = calloc(MAX_SOCKETS, sizeof(*ifobj->xsk_arr)); - if (!ifobj->xsk_arr) - goto out_xsk_arr; - - ifobj->umem = calloc(1, sizeof(*ifobj->umem)); - if (!ifobj->umem) - goto out_umem; - - return ifobj; - -out_umem: - free(ifobj->xsk_arr); -out_xsk_arr: - free(ifobj); - return NULL; -} - -static void ifobject_delete(struct ifobject *ifobj) -{ - free(ifobj->umem); - free(ifobj->xsk_arr); - free(ifobj); -} - -static bool is_xdp_supported(int ifindex) -{ - int flags = XDP_FLAGS_DRV_MODE; - - LIBBPF_OPTS(bpf_link_create_opts, opts, .flags = flags); - struct bpf_insn insns[2] = { - BPF_MOV64_IMM(BPF_REG_0, XDP_PASS), - BPF_EXIT_INSN() - }; - int prog_fd, insn_cnt = ARRAY_SIZE(insns); - int err; - - prog_fd = bpf_prog_load(BPF_PROG_TYPE_XDP, NULL, "GPL", insns, insn_cnt, NULL); - if (prog_fd < 0) - return false; - - err = bpf_xdp_attach(ifindex, prog_fd, flags, NULL); - if (err) { - close(prog_fd); - return false; - } - - bpf_xdp_detach(ifindex, flags, NULL); - close(prog_fd); - - return true; -} - -static const struct test_spec tests[] = { - {.name = "SEND_RECEIVE", .test_func = testapp_send_receive}, - {.name = "SEND_RECEIVE_2K_FRAME", .test_func = testapp_send_receive_2k_frame}, - {.name = "SEND_RECEIVE_SINGLE_PKT", .test_func = testapp_single_pkt}, - {.name = "POLL_RX", .test_func = testapp_poll_rx}, - {.name = "POLL_TX", .test_func = testapp_poll_tx}, - {.name = "POLL_RXQ_FULL", .test_func = testapp_poll_rxq_tmout}, - {.name = "POLL_TXQ_FULL", .test_func = testapp_poll_txq_tmout}, - {.name = "SEND_RECEIVE_UNALIGNED", .test_func = testapp_send_receive_unaligned}, - {.name = "ALIGNED_INV_DESC", .test_func = testapp_aligned_inv_desc}, - {.name = "ALIGNED_INV_DESC_2K_FRAME_SIZE", .test_func = testapp_aligned_inv_desc_2k_frame}, - {.name = "UNALIGNED_INV_DESC", .test_func = testapp_unaligned_inv_desc}, - {.name = "UNALIGNED_INV_DESC_4001_FRAME_SIZE", - .test_func = testapp_unaligned_inv_desc_4001_frame}, - {.name = "UMEM_HEADROOM", .test_func = testapp_headroom}, - {.name = "TEARDOWN", .test_func = testapp_teardown}, - {.name = "BIDIRECTIONAL", .test_func = testapp_bidirectional}, - {.name = "STAT_RX_DROPPED", .test_func = testapp_stats_rx_dropped}, - {.name = "STAT_TX_INVALID", .test_func = testapp_stats_tx_invalid_descs}, - {.name = "STAT_RX_FULL", .test_func = testapp_stats_rx_full}, - {.name = "STAT_FILL_EMPTY", .test_func = testapp_stats_fill_empty}, - {.name = "XDP_PROG_CLEANUP", .test_func = testapp_xdp_prog_cleanup}, - {.name = "XDP_DROP_HALF", .test_func = testapp_xdp_drop}, - {.name = "XDP_SHARED_UMEM", .test_func = testapp_xdp_shared_umem}, - {.name = "XDP_METADATA_COPY", .test_func = testapp_xdp_metadata}, - {.name = "XDP_METADATA_COPY_MULTI_BUFF", .test_func = testapp_xdp_metadata_mb}, - {.name = "SEND_RECEIVE_9K_PACKETS", .test_func = testapp_send_receive_mb}, - {.name = "SEND_RECEIVE_UNALIGNED_9K_PACKETS", - .test_func = testapp_send_receive_unaligned_mb}, - {.name = "ALIGNED_INV_DESC_MULTI_BUFF", .test_func = testapp_aligned_inv_desc_mb}, - {.name = "UNALIGNED_INV_DESC_MULTI_BUFF", .test_func = testapp_unaligned_inv_desc_mb}, - {.name = "TOO_MANY_FRAGS", .test_func = testapp_too_many_frags}, - {.name = "HW_SW_MIN_RING_SIZE", .test_func = testapp_hw_sw_min_ring_size}, - {.name = "HW_SW_MAX_RING_SIZE", .test_func = testapp_hw_sw_max_ring_size}, - {.name = "XDP_ADJUST_TAIL_SHRINK", .test_func = testapp_adjust_tail_shrink}, - {.name = "XDP_ADJUST_TAIL_SHRINK_MULTI_BUFF", .test_func = testapp_adjust_tail_shrink_mb}, - {.name = "XDP_ADJUST_TAIL_GROW", .test_func = testapp_adjust_tail_grow}, - {.name = "XDP_ADJUST_TAIL_GROW_MULTI_BUFF", .test_func = testapp_adjust_tail_grow_mb}, - {.name = "TX_QUEUE_CONSUMER", .test_func = testapp_tx_queue_consumer}, - }; - static void print_tests(void) { u32 i; @@ -2833,7 +376,7 @@ int main(int argc, char **argv) init_iface(ifobj_rx, worker_testapp_validate_rx); init_iface(ifobj_tx, worker_testapp_validate_tx); - test_spec_init(&test, ifobj_tx, ifobj_rx, 0, &tests[0]); + test_init(&test, ifobj_tx, ifobj_rx, 0, &tests[0]); tx_pkt_stream_default = pkt_stream_generate(DEFAULT_PKT_CNT, MIN_PKT_SIZE); rx_pkt_stream_default = pkt_stream_generate(DEFAULT_PKT_CNT, MIN_PKT_SIZE); if (!tx_pkt_stream_default || !rx_pkt_stream_default) @@ -2868,7 +411,7 @@ int main(int argc, char **argv) if (opt_run_test != RUN_ALL_TESTS && j != opt_run_test) continue; - test_spec_init(&test, ifobj_tx, ifobj_rx, i, &tests[j]); + test_init(&test, ifobj_tx, ifobj_rx, i, &tests[j]); run_pkt_test(&test); usleep(USLEEP_MAX); diff --git a/tools/testing/selftests/bpf/xskxceiver.h b/tools/testing/selftests/bpf/xskxceiver.h index 4df3a5d329acf8..3ca518df23adaf 100644 --- a/tools/testing/selftests/bpf/xskxceiver.h +++ b/tools/testing/selftests/bpf/xskxceiver.h @@ -22,169 +22,13 @@ #define PF_XDP AF_XDP #endif -#ifndef SO_BUSY_POLL_BUDGET -#define SO_BUSY_POLL_BUDGET 70 -#endif - -#ifndef SO_PREFER_BUSY_POLL -#define SO_PREFER_BUSY_POLL 69 -#endif - -#define TEST_PASS 0 -#define TEST_FAILURE -1 -#define TEST_CONTINUE 1 -#define TEST_SKIP 2 -#define MAX_INTERFACES 2 -#define MAX_INTERFACE_NAME_CHARS 16 -#define MAX_TEST_NAME_SIZE 48 #define MAX_TEARDOWN_ITER 10 -#define PKT_HDR_SIZE (sizeof(struct ethhdr) + 2) /* Just to align the data in the packet */ -#define MIN_PKT_SIZE 64 -#define MAX_ETH_PKT_SIZE 1518 #define MAX_ETH_JUMBO_SIZE 9000 -#define USLEEP_MAX 10000 #define SOCK_RECONF_CTR 10 -#define DEFAULT_BATCH_SIZE 64 -#define POLL_TMOUT 1000 -#define THREAD_TMOUT 3 -#define DEFAULT_PKT_CNT (4 * 1024) -#define DEFAULT_UMEM_BUFFERS (DEFAULT_PKT_CNT / 4) #define RX_FULL_RXQSIZE 32 #define UMEM_HEADROOM_TEST_SIZE 128 #define XSK_UMEM__INVALID_FRAME_SIZE (MAX_ETH_JUMBO_SIZE + 1) -#define XSK_UMEM__LARGE_FRAME_SIZE (3 * 1024) -#define XSK_UMEM__MAX_FRAME_SIZE (4 * 1024) -#define XSK_DESC__INVALID_OPTION (0xffff) -#define HUGEPAGE_SIZE (2 * 1024 * 1024) -#define PKT_DUMP_NB_TO_PRINT 16 #define RUN_ALL_TESTS UINT_MAX #define NUM_MAC_ADDRESSES 4 -#define print_verbose(x...) do { if (opt_verbose) ksft_print_msg(x); } while (0) - -enum test_mode { - TEST_MODE_SKB, - TEST_MODE_DRV, - TEST_MODE_ZC, - TEST_MODE_ALL -}; - -struct xsk_umem_info { - struct xsk_ring_prod fq; - struct xsk_ring_cons cq; - struct xsk_umem *umem; - u64 next_buffer; - u32 num_frames; - u32 frame_headroom; - void *buffer; - u32 frame_size; - u32 base_addr; - u32 fill_size; - u32 comp_size; - bool unaligned_mode; -}; - -struct xsk_socket_info { - struct xsk_ring_cons rx; - struct xsk_ring_prod tx; - struct xsk_umem_info *umem; - struct xsk_socket *xsk; - struct pkt_stream *pkt_stream; - u32 outstanding_tx; - u32 rxqsize; - u32 batch_size; - u8 dst_mac[ETH_ALEN]; - u8 src_mac[ETH_ALEN]; - bool check_consumer; -}; - -struct pkt { - int offset; - u32 len; - u32 pkt_nb; - bool valid; - u16 options; -}; - -struct pkt_stream { - u32 nb_pkts; - u32 current_pkt_nb; - struct pkt *pkts; - u32 max_pkt_len; - u32 nb_rx_pkts; - u32 nb_valid_entries; - bool verbatim; -}; - -struct set_hw_ring { - u32 default_tx; - u32 default_rx; -}; - -struct ifobject; -struct test_spec; -typedef int (*validation_func_t)(struct ifobject *ifobj); -typedef void *(*thread_func_t)(void *arg); -typedef int (*test_func_t)(struct test_spec *test); - -struct ifobject { - char ifname[MAX_INTERFACE_NAME_CHARS]; - struct xsk_socket_info *xsk; - struct xsk_socket_info *xsk_arr; - struct xsk_umem_info *umem; - thread_func_t func_ptr; - validation_func_t validation_func; - struct xsk_xdp_progs *xdp_progs; - struct bpf_map *xskmap; - struct bpf_program *xdp_prog; - struct ethtool_ringparam ring; - struct set_hw_ring set_ring; - enum test_mode mode; - int ifindex; - int mtu; - u32 bind_flags; - u32 xdp_zc_max_segs; - bool tx_on; - bool rx_on; - bool use_poll; - bool busy_poll; - bool use_fill_ring; - bool release_rx; - bool shared_umem; - bool use_metadata; - bool unaligned_supp; - bool multi_buff_supp; - bool multi_buff_zc_supp; - bool hw_ring_size_supp; -}; - -struct test_spec { - struct ifobject *ifobj_tx; - struct ifobject *ifobj_rx; - struct pkt_stream *tx_pkt_stream_default; - struct pkt_stream *rx_pkt_stream_default; - struct bpf_program *xdp_prog_rx; - struct bpf_program *xdp_prog_tx; - struct bpf_map *xskmap_rx; - struct bpf_map *xskmap_tx; - test_func_t test_func; - int mtu; - u16 total_steps; - u16 current_step; - u16 nb_sockets; - bool fail; - bool set_ring; - bool adjust_tail; - bool adjust_tail_support; - enum test_mode mode; - char name[MAX_TEST_NAME_SIZE]; -}; - -pthread_barrier_t barr; -pthread_mutex_t pacing_mutex = PTHREAD_MUTEX_INITIALIZER; - -int pkts_in_flight; - -static const u8 g_mac[ETH_ALEN] = {0x55, 0x44, 0x33, 0x22, 0x11, 0x00}; - #endif /* XSKXCEIVER_H_ */ From b2b172a9871325fe9e26dbef5aeda757b02c1c06 Mon Sep 17 00:00:00 2001 From: Larysa Zaremba Date: Tue, 3 Feb 2026 16:50:57 +0100 Subject: [PATCH 3375/3902] selftests/xsk: properly handle batch ending in the middle of a packet [ Upstream commit 42e41b2a0afa04ca49ee2725aadf90ccb058ed28 ] Referenced commit reduced the scope of the variable pkt, so now it has to be reinitialized via pkt_stream_get_next_rx_pkt(), which also increments some counters. When the packet is interrupted by the batch ending, pkt stream therefore proceeds to the next packet, while xsk ring still contains the previous one, this results in a pkt_nb mismatch. Decrement the affected counters when packet is interrupted. Fixes: 8913e653e9b8 ("selftests/xsk: Iterate over all the sockets in the receive pkts function") Reviewed-by: Aleksandr Loktionov Signed-off-by: Larysa Zaremba Link: https://lore.kernel.org/r/20260203155103.2305816-2-larysa.zaremba@intel.com Signed-off-by: Alexei Starovoitov Signed-off-by: Sasha Levin --- tools/testing/selftests/bpf/test_xsk.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tools/testing/selftests/bpf/test_xsk.c b/tools/testing/selftests/bpf/test_xsk.c index 02250f29f9946d..b52f597ea9bd28 100644 --- a/tools/testing/selftests/bpf/test_xsk.c +++ b/tools/testing/selftests/bpf/test_xsk.c @@ -1027,6 +1027,8 @@ static int __receive_pkts(struct test_spec *test, struct xsk_socket_info *xsk) xsk_ring_prod__cancel(&umem->fq, nb_frags); } frags_processed -= nb_frags; + pkt_stream_cancel(pkt_stream); + pkts_sent--; } if (ifobj->use_fill_ring) From 119702b7167764c5f246dff7873fa748add73cb3 Mon Sep 17 00:00:00 2001 From: Larysa Zaremba Date: Tue, 3 Feb 2026 16:50:58 +0100 Subject: [PATCH 3376/3902] selftests/xsk: fix number of Tx frags in invalid packet [ Upstream commit 88af9fefed412e4bea9a1a771cbe6fe347fa3507 ] The issue occurs in TOO_MANY_FRAGS test case when xdp_zc_max_segs is set to an odd number. TOO_MANY_FRAGS test case contains an invalid packet consisting of (xdp_zc_max_segs) frags. Every frag, even the last one has XDP_PKT_CONTD flag set. This packet is expected to be dropped. After that, there is a valid linear packet, which is expected to be received back. Once (xdp_zc_max_segs) is an odd number, the last packet cannot be received, if packet forwarding between Rx and Tx interfaces relies on the ethernet header, e.g. checks for ETH_P_LOOPBACK. Packet is malformed, if all traffic is looped. Turns out, sending function processes multiple invalid frags as if they were in 2-frag packets. So once the invalid mbuf packet contains an odd number of those, the valid packet after gets paired with the previous invalid descriptor, and hence does not get an ethernet header generated, so it is either dropped or malformed. Make invalid packets in verbatim mode always have only a single frag. For such packets, number of frags is otherwise meaningless, as descriptor flags are pre-configured in verbatim mode and packet data is not generated for invalid descriptors. Fixes: 697604492b64 ("selftests/xsk: add invalid descriptor test for multi-buffer") Reviewed-by: Aleksandr Loktionov Signed-off-by: Larysa Zaremba Link: https://lore.kernel.org/r/20260203155103.2305816-3-larysa.zaremba@intel.com Signed-off-by: Alexei Starovoitov Signed-off-by: Sasha Levin --- tools/testing/selftests/bpf/test_xsk.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/testing/selftests/bpf/test_xsk.c b/tools/testing/selftests/bpf/test_xsk.c index b52f597ea9bd28..55d318c5c5e593 100644 --- a/tools/testing/selftests/bpf/test_xsk.c +++ b/tools/testing/selftests/bpf/test_xsk.c @@ -431,7 +431,7 @@ static u32 pkt_nb_frags(u32 frame_size, struct pkt_stream *pkt_stream, struct pk } /* Search for the end of the packet in verbatim mode */ - if (!pkt_continues(pkt->options)) + if (!pkt_continues(pkt->options) || !pkt->valid) return nb_frags; next_frag = pkt_stream->current_pkt_nb; From 4f73486ca822305c1cf5b8ebc0b53a6ab3801a81 Mon Sep 17 00:00:00 2001 From: Sai Ritvik Tanksalkar Date: Sun, 1 Feb 2026 13:22:40 +0000 Subject: [PATCH 3377/3902] pstore/ram: fix buffer overflow in persistent_ram_save_old() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [ Upstream commit 5669645c052f235726a85f443769b6fc02f66762 ] persistent_ram_save_old() can be called multiple times for the same persistent_ram_zone (e.g., via ramoops_pstore_read -> ramoops_get_next_prz for PSTORE_TYPE_DMESG records). Currently, the function only allocates prz->old_log when it is NULL, but it unconditionally updates prz->old_log_size to the current buffer size and then performs memcpy_fromio() using this new size. If the buffer size has grown since the first allocation (which can happen across different kernel boot cycles), this leads to: 1. A heap buffer overflow (OOB write) in the memcpy_fromio() calls 2. A subsequent OOB read when ramoops_pstore_read() accesses the buffer using the incorrect (larger) old_log_size The KASAN splat would look similar to: BUG: KASAN: slab-out-of-bounds in ramoops_pstore_read+0x... Read of size N at addr ... by task ... The conditions are likely extremely hard to hit: 0. Crash with a ramoops write of less-than-record-max-size bytes. 1. Reboot: ramoops registers, pstore_get_records(0) reads old crash, allocates old_log with size X 2. Crash handler registered, timer started (if pstore_update_ms >= 0) 3. Oops happens (non-fatal, system continues) 4. pstore_dump() writes oops via ramoops_pstore_write() size Y (>X) 5. pstore_new_entry = 1, pstore_timer_kick() called 6. System continues running (not a panic oops) 7. Timer fires after pstore_update_ms milliseconds 8. pstore_timefunc() → schedule_work() → pstore_dowork() → pstore_get_records(1) 9. ramoops_get_next_prz() → persistent_ram_save_old() 10. buffer_size() returns Y, but old_log is X bytes 11. Y > X: memcpy_fromio() overflows heap Requirements: - a prior crash record exists that did not fill the record size (almost impossible since the crash handler writes as much as it can possibly fit into the record, capped by max record size and the kmsg buffer almost always exceeds the max record size) - pstore_update_ms >= 0 (disabled by default) - Non-fatal oops (system survives) Free and reallocate the buffer when the new size differs from the previously allocated size. This ensures old_log always has sufficient space for the data being copied. Fixes: 201e4aca5aa1 ("pstore/ram: Should update old dmesg buffer before reading") Signed-off-by: Sai Ritvik Tanksalkar Link: https://patch.msgid.link/20260201132240.2948732-1-stanksal@purdue.edu Signed-off-by: Kees Cook Signed-off-by: Sasha Levin --- fs/pstore/ram_core.c | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/fs/pstore/ram_core.c b/fs/pstore/ram_core.c index f1848cdd6d3485..c9eaacdec37e48 100644 --- a/fs/pstore/ram_core.c +++ b/fs/pstore/ram_core.c @@ -298,6 +298,17 @@ void persistent_ram_save_old(struct persistent_ram_zone *prz) if (!size) return; + /* + * If the existing buffer is differently sized, free it so a new + * one is allocated. This can happen when persistent_ram_save_old() + * is called early in boot and later for a timer-triggered + * survivable crash when the crash dumps don't match in size + * (which would be extremely unlikely given kmsg buffers usually + * exceed prz buffer sizes). + */ + if (prz->old_log && prz->old_log_size != size) + persistent_ram_free_old(prz); + if (!prz->old_log) { persistent_ram_ecc_old(prz); prz->old_log = kvzalloc(size, GFP_KERNEL); From d32e8339e1fda98dec1a948b2c0025440dcf61ef Mon Sep 17 00:00:00 2001 From: Jorge Ramirez-Ortiz Date: Tue, 9 Dec 2025 08:45:37 +0100 Subject: [PATCH 3378/3902] soc: qcom: smem: handle ENOMEM error during probe [ Upstream commit 0fe01a7955f4fef97e7cc6d14bfc5931c660402b ] Fail the driver probe if the region can't be mapped Signed-off-by: Jorge Ramirez-Ortiz Fixes: 20bb6c9de1b7 ("soc: qcom: smem: map only partitions used by local HOST") Reviewed-by: Dmitry Baryshkov Link: https://lore.kernel.org/r/20251209074610.3751781-1-jorge.ramirez@oss.qualcomm.com Signed-off-by: Bjorn Andersson Signed-off-by: Sasha Levin --- drivers/soc/qcom/smem.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/drivers/soc/qcom/smem.c b/drivers/soc/qcom/smem.c index f1d1b5aa5e4db6..39177aa5793adb 100644 --- a/drivers/soc/qcom/smem.c +++ b/drivers/soc/qcom/smem.c @@ -1215,7 +1215,9 @@ static int qcom_smem_probe(struct platform_device *pdev) smem->item_count = qcom_smem_get_item_count(smem); break; case SMEM_GLOBAL_HEAP_VERSION: - qcom_smem_map_global(smem, size); + ret = qcom_smem_map_global(smem, size); + if (ret < 0) + return ret; smem->item_count = SMEM_ITEM_COUNT; break; default: From 3efb54e1201b86bbfb2c5333cfa02221d5e1e010 Mon Sep 17 00:00:00 2001 From: Dan Carpenter Date: Tue, 9 Dec 2025 17:36:59 +0300 Subject: [PATCH 3379/3902] EDAC/i5000: Fix snprintf() size calculation in calculate_dimm_size() [ Upstream commit 7b5c7e83ac405ff9ecbdd92b37a477f4288f8814 ] The snprintf() can't really overflow because we're writing a max of 42 bytes to a PAGE_SIZE buffer. But the limit calculation doesn't take the first 11 bytes that we wrote into consideration so the limit is not correct. Just fix it for correctness even though it doesn't affect runtime. Fixes: 64e1fdaf55d6 ("i5000_edac: Fix the logic that retrieves memory information") Signed-off-by: Dan Carpenter Signed-off-by: Tony Luck Reviewed-by: Qiuxu Zhuo Link: https://patch.msgid.link/07cd652c51e77aad5a8350e1a7cd9407e5bbe373.1765290801.git.dan.carpenter@linaro.org Signed-off-by: Sasha Levin --- drivers/edac/i5000_edac.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/edac/i5000_edac.c b/drivers/edac/i5000_edac.c index 4a1bebc1ff14d4..471b8540d18b0f 100644 --- a/drivers/edac/i5000_edac.c +++ b/drivers/edac/i5000_edac.c @@ -1111,6 +1111,7 @@ static void calculate_dimm_size(struct i5000_pvt *pvt) n = snprintf(p, space, " "); p += n; + space -= n; for (branch = 0; branch < MAX_BRANCHES; branch++) { n = snprintf(p, space, " branch %d | ", branch); p += n; From f64f4ec804b3e485174d9803d2c732453995eafb Mon Sep 17 00:00:00 2001 From: Dan Carpenter Date: Tue, 9 Dec 2025 17:37:04 +0300 Subject: [PATCH 3380/3902] EDAC/i5400: Fix snprintf() limit calculation in calculate_dimm_size() [ Upstream commit 72f12683611344853ab030fe7d19b23970ed2bd8 ] The snprintf() can't really overflow because we're writing a max of 42 bytes to a PAGE_SIZE buffer. But my static checker complains because the limit calculation doesn't take the first 11 space characters that we wrote into the buffer into consideration. Fix this for the sake of correctness even though it doesn't affect runtime. Also delete an earlier "space -= n;" which was not used. Fixes: 68d086f89b80 ("i5400_edac: improve debug messages to better represent the filled memory") Signed-off-by: Dan Carpenter Signed-off-by: Tony Luck Reviewed-by: Qiuxu Zhuo Link: https://patch.msgid.link/ccd06b91748e7ed8e33eeb2ff1e7b98700879304.1765290801.git.dan.carpenter@linaro.org Signed-off-by: Sasha Levin --- drivers/edac/i5400_edac.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/edac/i5400_edac.c b/drivers/edac/i5400_edac.c index b5cf25905b059d..fb49a1d1df1120 100644 --- a/drivers/edac/i5400_edac.c +++ b/drivers/edac/i5400_edac.c @@ -1026,13 +1026,13 @@ static void calculate_dimm_size(struct i5400_pvt *pvt) space -= n; } - space -= n; edac_dbg(2, "%s\n", mem_buffer); p = mem_buffer; space = PAGE_SIZE; n = snprintf(p, space, " "); p += n; + space -= n; for (branch = 0; branch < MAX_BRANCHES; branch++) { n = snprintf(p, space, " branch %d | ", branch); p += n; From 9fd645784128257fdd558225ddf3056b1a84ff47 Mon Sep 17 00:00:00 2001 From: Sudeep Holla Date: Thu, 18 Dec 2025 14:20:01 +0000 Subject: [PATCH 3381/3902] firmware: arm_ffa: Correct 32-bit response handling in NOTIFICATION_INFO_GET [ Upstream commit be4d4543f78074fbebd530ba5109d39a2a34e668 ] The FF-A specification allows NOTIFICATION_INFO_GET to return either a 64-bit (FFA_FN64_SUCCESS) or a 32-bit (FFA_SUCCESS) response, depending on whether the firmware chooses the SMC64 or SMC32 calling convention. The driver previously detected the response format by checking ret.a0, but still interpreted the returned ID lists (x3..x17 or w3..w7) as if they always followed the 64-bit SMC64 layout. In the SMC32 case, the upper 32 bits of each argument register are undefined by the calling convention, meaning the driver could read stale or garbage values when parsing notification IDs. This resulted in incorrectly decoded partition/VCPU IDs whenever the FF-A firmware used an SMC32 return path. Fix the issue by: - Introducing logic to map list indices to the correct u16 offsets, depending on whether the response width matches the kernel word size or is a 32-bit response on a 64-bit kernel. - Ensuring that the packed ID list is parsed using the proper layout, avoiding reads from undefined upper halves in the SMC32 case. With this change, NOTIFICATION_INFO_GET now correctly interprets ID list entries regardless of the response width, aligning the driver with the FF-A specification. Fixes: 3522be48d82b ("firmware: arm_ffa: Implement the NOTIFICATION_INFO_GET interface") Reported-by: Sourav Mohapatra Message-Id: <20251218142001.2457111-1-sudeep.holla@arm.com> Signed-off-by: Sudeep Holla Signed-off-by: Sasha Levin --- drivers/firmware/arm_ffa/driver.c | 33 +++++++++++++++++++++++++++---- 1 file changed, 29 insertions(+), 4 deletions(-) diff --git a/drivers/firmware/arm_ffa/driver.c b/drivers/firmware/arm_ffa/driver.c index c72ee475658569..c501c3104b3a4f 100644 --- a/drivers/firmware/arm_ffa/driver.c +++ b/drivers/firmware/arm_ffa/driver.c @@ -981,10 +981,27 @@ static void __do_sched_recv_cb(u16 part_id, u16 vcpu, bool is_per_vcpu) } } +/* + * Map logical ID index to the u16 index within the packed ID list. + * + * For native responses (FF-A width == kernel word size), IDs are + * tightly packed: idx -> idx. + * + * For 32-bit responses on a 64-bit kernel, each 64-bit register + * contributes 4 x u16 values but only the lower 2 are defined; the + * upper 2 are garbage. This mapping skips those upper halves: + * 0,1,2,3,4,5,... -> 0,1,4,5,8,9,... + */ +static int list_idx_to_u16_idx(int idx, bool is_native_resp) +{ + return is_native_resp ? idx : idx + 2 * (idx >> 1); +} + static void ffa_notification_info_get(void) { - int idx, list, max_ids, lists_cnt, ids_processed, ids_count[MAX_IDS_64]; - bool is_64b_resp; + int ids_processed, ids_count[MAX_IDS_64]; + int idx, list, max_ids, lists_cnt; + bool is_64b_resp, is_native_resp; ffa_value_t ret; u64 id_list; @@ -1001,6 +1018,7 @@ static void ffa_notification_info_get(void) } is_64b_resp = (ret.a0 == FFA_FN64_SUCCESS); + is_native_resp = (ret.a0 == FFA_FN_NATIVE(SUCCESS)); ids_processed = 0; lists_cnt = FIELD_GET(NOTIFICATION_INFO_GET_ID_COUNT, ret.a2); @@ -1017,12 +1035,16 @@ static void ffa_notification_info_get(void) /* Process IDs */ for (list = 0; list < lists_cnt; list++) { + int u16_idx; u16 vcpu_id, part_id, *packed_id_list = (u16 *)&ret.a3; if (ids_processed >= max_ids - 1) break; - part_id = packed_id_list[ids_processed++]; + u16_idx = list_idx_to_u16_idx(ids_processed, + is_native_resp); + part_id = packed_id_list[u16_idx]; + ids_processed++; if (ids_count[list] == 1) { /* Global Notification */ __do_sched_recv_cb(part_id, 0, false); @@ -1034,7 +1056,10 @@ static void ffa_notification_info_get(void) if (ids_processed >= max_ids - 1) break; - vcpu_id = packed_id_list[ids_processed++]; + u16_idx = list_idx_to_u16_idx(ids_processed, + is_native_resp); + vcpu_id = packed_id_list[u16_idx]; + ids_processed++; __do_sched_recv_cb(part_id, vcpu_id, true); } From 5a76398d0e03bc5287793320482ecdd7a3bfb686 Mon Sep 17 00:00:00 2001 From: Alexander Stein Date: Tue, 9 Dec 2025 11:53:09 +0100 Subject: [PATCH 3382/3902] arm64: dts: tqma8mpql-mba8mpxl: Fix HDMI CEC pad control settings [ Upstream commit 8401527abb5e3a00c867b6597b8e1b29c80c9824 ] As per datasheet of the HDMI protection IC the CEC_IC pin has been configured as open-drain. Fixes: 418d1d840e42 ("arm64: dts: freescale: add initial device tree for TQMa8MPQL with i.MX8MP") Signed-off-by: Alexander Stein Signed-off-by: Shawn Guo Signed-off-by: Sasha Levin --- arch/arm64/boot/dts/freescale/imx8mp-tqma8mpql-mba8mpxl.dts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/arch/arm64/boot/dts/freescale/imx8mp-tqma8mpql-mba8mpxl.dts b/arch/arm64/boot/dts/freescale/imx8mp-tqma8mpql-mba8mpxl.dts index 4eedd00d83b9fc..ac05c05193c5be 100644 --- a/arch/arm64/boot/dts/freescale/imx8mp-tqma8mpql-mba8mpxl.dts +++ b/arch/arm64/boot/dts/freescale/imx8mp-tqma8mpql-mba8mpxl.dts @@ -860,7 +860,7 @@ fsl,pins = , , , - ; + ; }; pinctrl_hoggpio2: hoggpio2grp { From 911a473e29c322e4894af1321bacf249dca207c6 Mon Sep 17 00:00:00 2001 From: Alexander Stein Date: Tue, 9 Dec 2025 11:53:13 +0100 Subject: [PATCH 3383/3902] arm64: dts: tqma8mpql-mba8mp-ras314: Fix HDMI CEC pad control settings [ Upstream commit 53a5c1d98d1155ece4c9446c0fea55e17d08774a ] As per datasheet of the HDMI protection IC the CEC_IC pin has been configured as open-drain. Fixes: ddabb3ce3f90 ("arm64: dts: freescale: add TQMa8MPQL on MBa8MP-RAS314") Signed-off-by: Alexander Stein Signed-off-by: Shawn Guo Signed-off-by: Sasha Levin --- .../arm64/boot/dts/freescale/imx8mp-tqma8mpql-mba8mp-ras314.dts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/arch/arm64/boot/dts/freescale/imx8mp-tqma8mpql-mba8mp-ras314.dts b/arch/arm64/boot/dts/freescale/imx8mp-tqma8mpql-mba8mp-ras314.dts index f7346b3d35fe53..a122f2ed5f5314 100644 --- a/arch/arm64/boot/dts/freescale/imx8mp-tqma8mpql-mba8mp-ras314.dts +++ b/arch/arm64/boot/dts/freescale/imx8mp-tqma8mpql-mba8mp-ras314.dts @@ -704,7 +704,7 @@ fsl,pins = , , , - ; + ; }; pinctrl_gpt1: gpt1grp { From 97ca54da16f401aec2bb82d6ac82d39b92d1178d Mon Sep 17 00:00:00 2001 From: Haotian Zhang Date: Wed, 17 Dec 2025 12:13:38 +0800 Subject: [PATCH 3384/3902] clk: qcom: Return correct error code in qcom_cc_probe_by_index() [ Upstream commit 1e07ebe744fb522983bd52a4a6148601675330c7 ] When devm_platform_ioremap_resource() fails, it returns various error codes. Returning a hardcoded -ENOMEM masks the actual failure reason. Use PTR_ERR() to propagate the actual error code returned by devm_platform_ioremap_resource() instead of -ENOMEM. Fixes: 75e0a1e30191 ("clk: qcom: define probe by index API as common API") Signed-off-by: Haotian Zhang Reviewed-by: Konrad Dybcio Link: https://lore.kernel.org/r/20251217041338.2432-1-vulab@iscas.ac.cn Signed-off-by: Bjorn Andersson Signed-off-by: Sasha Levin --- drivers/clk/qcom/common.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/clk/qcom/common.c b/drivers/clk/qcom/common.c index 12159188677418..eec369d2173b5c 100644 --- a/drivers/clk/qcom/common.c +++ b/drivers/clk/qcom/common.c @@ -454,7 +454,7 @@ int qcom_cc_probe_by_index(struct platform_device *pdev, int index, base = devm_platform_ioremap_resource(pdev, index); if (IS_ERR(base)) - return -ENOMEM; + return PTR_ERR(base); regmap = devm_regmap_init_mmio(&pdev->dev, base, desc->config); if (IS_ERR(regmap)) From 75a3a27e4f825888d3f3a6120518f2587a34eaeb Mon Sep 17 00:00:00 2001 From: Dmitry Baryshkov Date: Thu, 11 Dec 2025 03:27:45 +0200 Subject: [PATCH 3385/3902] arm64: dts: qcom: sdm630: fix gpu_speed_bin size [ Upstream commit e814796dfcae8905682ac3ac2dd57f512a9f6726 ] Historically sdm630.dtsi has used 1 byte length for the gpu_speed_bin cell, although it spans two bytes (offset 5, size 7 bits). It was being accepted by the kernel because before the commit 7a06ef751077 ("nvmem: core: fix bit offsets of more than one byte") the kernel didn't have length check. After this commit nvmem core rejects QFPROM on sdm630 / sdm660, making GPU and USB unusable on those platforms. Set the size of the gpu_speed_bin cell to 2 bytes, fixing the parsing error. While we are at it, update the length to 8 bits as pointed out by Alexey Minnekhanov. Fixes: b190fb010664 ("arm64: dts: qcom: sdm630: Add sdm630 dts file") Signed-off-by: Dmitry Baryshkov Reviewed-by: Konrad Dybcio Reviewed-by: Alexey Minnekhanov Link: https://lore.kernel.org/r/20251211-sdm630-fix-gpu-v2-1-92f0e736dba0@oss.qualcomm.com Signed-off-by: Bjorn Andersson Signed-off-by: Sasha Levin --- arch/arm64/boot/dts/qcom/sdm630.dtsi | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/arch/arm64/boot/dts/qcom/sdm630.dtsi b/arch/arm64/boot/dts/qcom/sdm630.dtsi index 8b1a45a4e56ed1..b383e480a394d4 100644 --- a/arch/arm64/boot/dts/qcom/sdm630.dtsi +++ b/arch/arm64/boot/dts/qcom/sdm630.dtsi @@ -598,8 +598,8 @@ }; gpu_speed_bin: gpu-speed-bin@41a0 { - reg = <0x41a2 0x1>; - bits = <5 7>; + reg = <0x41a2 0x2>; + bits = <5 8>; }; }; From f5b047a2327185a92111defb13e4328277def39e Mon Sep 17 00:00:00 2001 From: Casey Connolly Date: Tue, 18 Nov 2025 15:52:25 +0100 Subject: [PATCH 3386/3902] arm64: dts: qcom: sdm845-oneplus: Don't mark ts supply boot-on [ Upstream commit c9b98b9dad9749bf2eb7336a6fca31a6af1039d7 ] The touchscreen isn't enabled by bootloader and doesn't need to be enabled at boot, only when the driver probes, thus remove the regulator-boot-on property. Fixes: 288ef8a42612 ("arm64: dts: sdm845: add oneplus6/6t devices") Signed-off-by: Casey Connolly Signed-off-by: David Heidelberg Reviewed-by: Konrad Dybcio Link: https://lore.kernel.org/r/20251118-dts-oneplus-regulators-v2-1-3e67cea1e4e7@ixit.cz Signed-off-by: Bjorn Andersson Signed-off-by: Sasha Levin --- arch/arm64/boot/dts/qcom/sdm845-oneplus-common.dtsi | 1 - 1 file changed, 1 deletion(-) diff --git a/arch/arm64/boot/dts/qcom/sdm845-oneplus-common.dtsi b/arch/arm64/boot/dts/qcom/sdm845-oneplus-common.dtsi index 51a9a276399ac7..35d105ff689b94 100644 --- a/arch/arm64/boot/dts/qcom/sdm845-oneplus-common.dtsi +++ b/arch/arm64/boot/dts/qcom/sdm845-oneplus-common.dtsi @@ -160,7 +160,6 @@ gpio = <&tlmm 88 0>; enable-active-high; - regulator-boot-on; }; }; From 7f4d96e1221807774c3b9419864a293f6c7119eb Mon Sep 17 00:00:00 2001 From: Casey Connolly Date: Tue, 18 Nov 2025 15:52:26 +0100 Subject: [PATCH 3387/3902] arm64: dts: qcom: sdm845-oneplus: Don't keep panel regulator always on [ Upstream commit 45d1f42d3e84b5880cf9fab1eb24a7818320eeb7 ] The panel regulator doesn't need to be always on, so remove this property. Fixes: 288ef8a42612 ("arm64: dts: sdm845: add oneplus6/6t devices") Signed-off-by: Casey Connolly Signed-off-by: David Heidelberg Reviewed-by: Konrad Dybcio Link: https://lore.kernel.org/r/20251118-dts-oneplus-regulators-v2-2-3e67cea1e4e7@ixit.cz Signed-off-by: Bjorn Andersson Signed-off-by: Sasha Levin --- arch/arm64/boot/dts/qcom/sdm845-oneplus-common.dtsi | 1 - 1 file changed, 1 deletion(-) diff --git a/arch/arm64/boot/dts/qcom/sdm845-oneplus-common.dtsi b/arch/arm64/boot/dts/qcom/sdm845-oneplus-common.dtsi index 35d105ff689b94..7169da658dcd02 100644 --- a/arch/arm64/boot/dts/qcom/sdm845-oneplus-common.dtsi +++ b/arch/arm64/boot/dts/qcom/sdm845-oneplus-common.dtsi @@ -256,7 +256,6 @@ regulator-min-microvolt = <1800000>; regulator-max-microvolt = <1800000>; regulator-initial-mode = ; - regulator-always-on; }; vreg_l17a_1p3: ldo17 { From 02d580c9affa52d2f2e097b9b67ca778a4c34468 Mon Sep 17 00:00:00 2001 From: Casey Connolly Date: Tue, 18 Nov 2025 15:52:27 +0100 Subject: [PATCH 3388/3902] arm64: dts: qcom: sdm845-oneplus: Mark l14a regulator as boot-on [ Upstream commit ad33ee060be46794a03d033894c9db3a9d6c1a0f ] This regulator is used only for the display, which is enabled by the bootloader and left on for continuous splash. Mark it as such. Fixes: 288ef8a42612 ("arm64: dts: sdm845: add oneplus6/6t devices") Signed-off-by: Casey Connolly Signed-off-by: David Heidelberg Reviewed-by: Konrad Dybcio Link: https://lore.kernel.org/r/20251118-dts-oneplus-regulators-v2-3-3e67cea1e4e7@ixit.cz Signed-off-by: Bjorn Andersson Signed-off-by: Sasha Levin --- arch/arm64/boot/dts/qcom/sdm845-oneplus-common.dtsi | 1 + 1 file changed, 1 insertion(+) diff --git a/arch/arm64/boot/dts/qcom/sdm845-oneplus-common.dtsi b/arch/arm64/boot/dts/qcom/sdm845-oneplus-common.dtsi index 7169da658dcd02..1036305231b208 100644 --- a/arch/arm64/boot/dts/qcom/sdm845-oneplus-common.dtsi +++ b/arch/arm64/boot/dts/qcom/sdm845-oneplus-common.dtsi @@ -256,6 +256,7 @@ regulator-min-microvolt = <1800000>; regulator-max-microvolt = <1800000>; regulator-initial-mode = ; + regulator-boot-on; }; vreg_l17a_1p3: ldo17 { From 480374c75c675a10097fbd6d2102697de6e753d7 Mon Sep 17 00:00:00 2001 From: Abel Vesa Date: Mon, 3 Nov 2025 18:51:40 +0200 Subject: [PATCH 3389/3902] arm64: dts: qcom: x1e80100: Fix USB combo PHYs SS1 and SS2 ref clocks [ Upstream commit 3af51501e2b8c87564b5cda43b0e5c316cf54717 ] It seems the USB combo SS1 and SS2 ref clocks have another gate, unlike the SS0. These gates are part of the TCSR clock controller. At least on Dell XPS 13 (9345), if the ref clock provided by the TCSR clock controller for SS1 PHY is disabled on the clk_disable_unused late initcall, the PHY fails to initialize. It doesn't happen on the SS0 PHY and the SS2 is not used on this device. This doesn't seem to be a problem on CRD though. It might be that the RPMh has a vote for it from some other consumer and does not actually disable it when ther kernel drops its vote. Either way, these TCSR provided clocks seem to be the correct ones for the SS1 and SS2, so use them instead. Fixes: 4af46b7bd66f ("arm64: dts: qcom: x1e80100: Add USB nodes") Signed-off-by: Abel Vesa Reviewed-by: Neil Armstrong Reviewed-by: Taniya Das Reviewed-by: Konrad Dybcio Link: https://lore.kernel.org/r/20251103-dts-qcom-x1e80100-fix-combo-ref-clks-v1-1-f395ec3cb7e8@linaro.org Signed-off-by: Bjorn Andersson Signed-off-by: Sasha Levin --- arch/arm64/boot/dts/qcom/x1e80100.dtsi | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/arch/arm64/boot/dts/qcom/x1e80100.dtsi b/arch/arm64/boot/dts/qcom/x1e80100.dtsi index 662ad694cd914f..3290fd8c2d6e69 100644 --- a/arch/arm64/boot/dts/qcom/x1e80100.dtsi +++ b/arch/arm64/boot/dts/qcom/x1e80100.dtsi @@ -2910,7 +2910,7 @@ reg = <0 0x00fda000 0 0x4000>; clocks = <&gcc GCC_USB3_SEC_PHY_AUX_CLK>, - <&rpmhcc RPMH_CXO_CLK>, + <&tcsr TCSR_USB4_1_CLKREF_EN>, <&gcc GCC_USB3_SEC_PHY_COM_AUX_CLK>, <&gcc GCC_USB3_SEC_PHY_PIPE_CLK>; clock-names = "aux", @@ -2981,7 +2981,7 @@ reg = <0 0x00fdf000 0 0x4000>; clocks = <&gcc GCC_USB3_TERT_PHY_AUX_CLK>, - <&rpmhcc RPMH_CXO_CLK>, + <&tcsr TCSR_USB4_2_CLKREF_EN>, <&gcc GCC_USB3_TERT_PHY_COM_AUX_CLK>, <&gcc GCC_USB3_TERT_PHY_PIPE_CLK>; clock-names = "aux", From 9bbee82bacb9032af0d2826a7e97d4febe6789d4 Mon Sep 17 00:00:00 2001 From: Geert Uytterhoeven Date: Tue, 25 Nov 2025 17:52:06 +0100 Subject: [PATCH 3390/3902] arm64: dts: renesas: r9a09g047e57-smarc: Remove duplicate SW_LCD_EN [ Upstream commit 44cfd102ce28e749a07bb0f1668cf932077b1175 ] SW_LCD_EN is defined twice. Fixes: 9e95446b0cf93a91 ("arm64: dts: renesas: r9a09g047e57-smarc: Add gpio keys") Signed-off-by: Geert Uytterhoeven Link: https://patch.msgid.link/1f93558c62f4461f50935644ec831a7d2cb52630.1764089463.git.geert+renesas@glider.be Signed-off-by: Sasha Levin --- arch/arm64/boot/dts/renesas/r9a09g047e57-smarc.dts | 1 - 1 file changed, 1 deletion(-) diff --git a/arch/arm64/boot/dts/renesas/r9a09g047e57-smarc.dts b/arch/arm64/boot/dts/renesas/r9a09g047e57-smarc.dts index 08e814c03fa855..ed6fcdc337a0bd 100644 --- a/arch/arm64/boot/dts/renesas/r9a09g047e57-smarc.dts +++ b/arch/arm64/boot/dts/renesas/r9a09g047e57-smarc.dts @@ -8,7 +8,6 @@ /dts-v1/; /* Switch selection settings */ -#define SW_LCD_EN 0 #define SW_GPIO8_CAN0_STB 0 #define SW_GPIO9_CAN1_STB 0 #define SW_LCD_EN 0 From db09ba1f9e70405ed9be59c15303cde6213174ab Mon Sep 17 00:00:00 2001 From: Vladimir Zapolskiy Date: Fri, 26 Dec 2025 02:39:23 +0200 Subject: [PATCH 3391/3902] arm64: dts: qcom: msm8994-octagon: Fix Analog Devices vendor prefix of AD7147 [ Upstream commit 7db5fbe508deedec6c183d5056cf3c504c027f40 ] Trivial change, Analog Devices vendor prefix is "adi", but there is a valid "ad" vendor prefix of another company, this may explain why the issue hasn't been discovered by the automatic tests. A problem of not described compatible value is out of this change scope. Fixes: c636eeb751f6 ("arm64: dts: qcom: msm8994-octagon: Add AD7147 and APDS9930 sensors") Signed-off-by: Vladimir Zapolskiy Reviewed-by: Konrad Dybcio Reviewed-by: Dmitry Baryshkov Link: https://lore.kernel.org/r/20251226003923.3341904-1-vladimir.zapolskiy@linaro.org Signed-off-by: Bjorn Andersson Signed-off-by: Sasha Levin --- arch/arm64/boot/dts/qcom/msm8994-msft-lumia-octagon.dtsi | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/arch/arm64/boot/dts/qcom/msm8994-msft-lumia-octagon.dtsi b/arch/arm64/boot/dts/qcom/msm8994-msft-lumia-octagon.dtsi index 4c983b10dd9252..7ace3540ef0a0e 100644 --- a/arch/arm64/boot/dts/qcom/msm8994-msft-lumia-octagon.dtsi +++ b/arch/arm64/boot/dts/qcom/msm8994-msft-lumia-octagon.dtsi @@ -378,7 +378,7 @@ status = "okay"; sideinteraction: touch@2c { - compatible = "ad,ad7147_captouch"; + compatible = "adi,ad7147_captouch"; reg = <0x2c>; pinctrl-names = "default", "sleep"; From 3ce94bf05945c8896bdd28c02ad407afe1de4143 Mon Sep 17 00:00:00 2001 From: Chen-Yu Tsai Date: Thu, 25 Dec 2025 18:36:14 +0800 Subject: [PATCH 3392/3902] ARM: dts: allwinner: sun5i-a13-utoo-p66: delete "power-gpios" property [ Upstream commit 0b2761eb1287bd9f62367cccf6626eb3107cef6f ] The P66's device tree includes the reference design dtsi files, which defines a node and properties for the touchpanel in the common design. The P66 dts file then overrides all the properties to match its own design, but as the touchpanel model is different, a different schema is matched. This other schema uses a different name for the GPIO. The original submission added the correct GPIO property, but did not delete the one inherited from the reference design, causing validation errors. Explicitly delete the incorrect GPIO property. Fixes: 2a53aff27236 ("ARM: dts: sun5i: Enable touchscreen on Utoo P66") Reviewed-by: Jernej Skrabec Link: https://patch.msgid.link/20251225103616.3203473-4-wens@kernel.org Signed-off-by: Chen-Yu Tsai Signed-off-by: Sasha Levin --- arch/arm/boot/dts/allwinner/sun5i-a13-utoo-p66.dts | 1 + 1 file changed, 1 insertion(+) diff --git a/arch/arm/boot/dts/allwinner/sun5i-a13-utoo-p66.dts b/arch/arm/boot/dts/allwinner/sun5i-a13-utoo-p66.dts index be486d28d04fae..428cab5a0e906e 100644 --- a/arch/arm/boot/dts/allwinner/sun5i-a13-utoo-p66.dts +++ b/arch/arm/boot/dts/allwinner/sun5i-a13-utoo-p66.dts @@ -102,6 +102,7 @@ /* The P66 uses a different EINT then the reference design */ interrupts = <6 9 IRQ_TYPE_EDGE_FALLING>; /* EINT9 (PG9) */ /* The icn8318 binding expects wake-gpios instead of power-gpios */ + /delete-property/ power-gpios; wake-gpios = <&pio 1 3 GPIO_ACTIVE_HIGH>; /* PB3 */ touchscreen-size-x = <800>; touchscreen-size-y = <480>; From 1b39dbd7761d51f4f5d08c414583ee924bd8c29b Mon Sep 17 00:00:00 2001 From: Christophe Leroy Date: Wed, 24 Dec 2025 12:20:49 +0100 Subject: [PATCH 3393/3902] powerpc/uaccess: Move barrier_nospec() out of allow_read_{from/write}_user() [ Upstream commit 5fbc09eb0b4f4b1a4b33abebacbeee0d29f195e9 ] Commit 74e19ef0ff80 ("uaccess: Add speculation barrier to copy_from_user()") added a redundant barrier_nospec() in copy_from_user(), because powerpc is already calling barrier_nospec() in allow_read_from_user() and allow_read_write_user(). But on other architectures that call to barrier_nospec() was missing. So change powerpc instead of reverting the above commit and having to fix other architectures one by one. This is now possible because barrier_nospec() has also been added in copy_from_user_iter(). Move barrier_nospec() out of allow_read_from_user() and allow_read_write_user(). This will also allow reuse of those functions when implementing masked user access which doesn't require barrier_nospec(). Don't add it back in raw_copy_from_user() as it is already called by copy_from_user() and copy_from_user_iter(). Fixes: 74e19ef0ff80 ("uaccess: Add speculation barrier to copy_from_user()") Signed-off-by: Christophe Leroy Signed-off-by: Madhavan Srinivasan Link: https://patch.msgid.link/f29612105c5fcbc8ceb7303808ddc1a781f0f6b5.1766574657.git.chleroy@kernel.org Signed-off-by: Sasha Levin --- arch/powerpc/include/asm/kup.h | 2 -- arch/powerpc/include/asm/uaccess.h | 4 ++++ 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/arch/powerpc/include/asm/kup.h b/arch/powerpc/include/asm/kup.h index dab63b82a8d4f3..f2009d7c8cfa75 100644 --- a/arch/powerpc/include/asm/kup.h +++ b/arch/powerpc/include/asm/kup.h @@ -134,7 +134,6 @@ static __always_inline void kuap_assert_locked(void) static __always_inline void allow_read_from_user(const void __user *from, unsigned long size) { - barrier_nospec(); allow_user_access(NULL, from, size, KUAP_READ); } @@ -146,7 +145,6 @@ static __always_inline void allow_write_to_user(void __user *to, unsigned long s static __always_inline void allow_read_write_user(void __user *to, const void __user *from, unsigned long size) { - barrier_nospec(); allow_user_access(to, from, size, KUAP_READ_WRITE); } diff --git a/arch/powerpc/include/asm/uaccess.h b/arch/powerpc/include/asm/uaccess.h index 4f5a46a77fa2b6..3987a5c33558bd 100644 --- a/arch/powerpc/include/asm/uaccess.h +++ b/arch/powerpc/include/asm/uaccess.h @@ -301,6 +301,7 @@ do { \ __typeof__(sizeof(*(ptr))) __gu_size = sizeof(*(ptr)); \ \ might_fault(); \ + barrier_nospec(); \ allow_read_from_user(__gu_addr, __gu_size); \ __get_user_size_allowed(__gu_val, __gu_addr, __gu_size, __gu_err); \ prevent_read_from_user(__gu_addr, __gu_size); \ @@ -329,6 +330,7 @@ raw_copy_in_user(void __user *to, const void __user *from, unsigned long n) { unsigned long ret; + barrier_nospec(); allow_read_write_user(to, from, n); ret = __copy_tofrom_user(to, from, n); prevent_read_write_user(to, from, n); @@ -415,6 +417,7 @@ static __must_check __always_inline bool user_access_begin(const void __user *pt might_fault(); + barrier_nospec(); allow_read_write_user((void __user *)ptr, ptr, len); return true; } @@ -431,6 +434,7 @@ user_read_access_begin(const void __user *ptr, size_t len) might_fault(); + barrier_nospec(); allow_read_from_user(ptr, len); return true; } From 3eeda22d52ff9defd3018726d92c85e55bed4988 Mon Sep 17 00:00:00 2001 From: Haotian Zhang Date: Tue, 16 Dec 2025 09:39:32 +0800 Subject: [PATCH 3394/3902] soc: qcom: cmd-db: Use devm_memremap() to fix memory leak in cmd_db_dev_probe [ Upstream commit 0da7824734d8d83e6a844dd0207f071cb0c50cf4 ] If cmd_db_magic_matches() fails after memremap() succeeds, the function returns -EINVAL without unmapping the memory region, causing a potential resource leak. Switch to devm_memremap to automatically manage the map resource. Fixes: 312416d9171a ("drivers: qcom: add command DB driver") Suggested-by: Dmitry Baryshkov Signed-off-by: Haotian Zhang Link: https://lore.kernel.org/r/20251216013933.773-1-vulab@iscas.ac.cn Signed-off-by: Bjorn Andersson Signed-off-by: Sasha Levin --- drivers/soc/qcom/cmd-db.c | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/drivers/soc/qcom/cmd-db.c b/drivers/soc/qcom/cmd-db.c index ae66c2623d250d..84a75d8c4b7020 100644 --- a/drivers/soc/qcom/cmd-db.c +++ b/drivers/soc/qcom/cmd-db.c @@ -349,15 +349,16 @@ static int cmd_db_dev_probe(struct platform_device *pdev) return -EINVAL; } - cmd_db_header = memremap(rmem->base, rmem->size, MEMREMAP_WC); - if (!cmd_db_header) { - ret = -ENOMEM; + cmd_db_header = devm_memremap(&pdev->dev, rmem->base, rmem->size, MEMREMAP_WC); + if (IS_ERR(cmd_db_header)) { + ret = PTR_ERR(cmd_db_header); cmd_db_header = NULL; return ret; } if (!cmd_db_magic_matches(cmd_db_header)) { dev_err(&pdev->dev, "Invalid Command DB Magic\n"); + cmd_db_header = NULL; return -EINVAL; } From 0f6498077faa9cd89bb787bcc57063494a6f0601 Mon Sep 17 00:00:00 2001 From: Zilin Guan Date: Sun, 28 Dec 2025 16:26:36 +0000 Subject: [PATCH 3395/3902] soc: mediatek: svs: Fix memory leak in svs_enable_debug_write() [ Upstream commit 6259094ee806fb813ca95894c65fb80e2ec98bf1 ] In svs_enable_debug_write(), the buf allocated by memdup_user_nul() is leaked if kstrtoint() fails. Fix this by using __free(kfree) to automatically free buf, eliminating the need for explicit kfree() calls and preventing leaks. Fixes: 13f1bbcfb582 ("soc: mediatek: SVS: add debug commands") Co-developed-by: Jianhao Xu Signed-off-by: Jianhao Xu Signed-off-by: Zilin Guan [Angelo: Added missing cleanup.h inclusion] Signed-off-by: AngeloGioacchino Del Regno Signed-off-by: Sasha Levin --- drivers/soc/mediatek/mtk-svs.c | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/drivers/soc/mediatek/mtk-svs.c b/drivers/soc/mediatek/mtk-svs.c index f45537546553ec..99edecb204f254 100644 --- a/drivers/soc/mediatek/mtk-svs.c +++ b/drivers/soc/mediatek/mtk-svs.c @@ -9,6 +9,7 @@ #include #include #include +#include #include #include #include @@ -789,7 +790,7 @@ static ssize_t svs_enable_debug_write(struct file *filp, struct svs_bank *svsb = file_inode(filp)->i_private; struct svs_platform *svsp = dev_get_drvdata(svsb->dev); int enabled, ret; - char *buf = NULL; + char *buf __free(kfree) = NULL; if (count >= PAGE_SIZE) return -EINVAL; @@ -807,8 +808,6 @@ static ssize_t svs_enable_debug_write(struct file *filp, svsb->mode_support = SVSB_MODE_ALL_DISABLE; } - kfree(buf); - return count; } From 6e6561231c6cfc32c5631aeecc0928ff2b14265c Mon Sep 17 00:00:00 2001 From: Narayana Murty N Date: Wed, 10 Dec 2025 08:25:59 -0600 Subject: [PATCH 3396/3902] powerpc/eeh: fix recursive pci_lock_rescan_remove locking in EEH event handling MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [ Upstream commit 815a8d2feb5615ae7f0b5befd206af0b0160614c ] The recent commit 1010b4c012b0 ("powerpc/eeh: Make EEH driver device hotplug safe") restructured the EEH driver to improve synchronization with the PCI hotplug layer. However, it inadvertently moved pci_lock_rescan_remove() outside its intended scope in eeh_handle_normal_event(), leading to broken PCI error reporting and improper EEH event triggering. Specifically, eeh_handle_normal_event() acquired pci_lock_rescan_remove() before calling eeh_pe_bus_get(), but eeh_pe_bus_get() itself attempts to acquire the same lock internally, causing nested locking and disrupting normal EEH event handling paths. This patch adds a boolean parameter do_lock to _eeh_pe_bus_get(), with two public wrappers: eeh_pe_bus_get() with locking enabled. eeh_pe_bus_get_nolock() that skips locking. Callers that already hold pci_lock_rescan_remove() now use eeh_pe_bus_get_nolock() to avoid recursive lock acquisition. Additionally, pci_lock_rescan_remove() calls are restored to the correct position—after eeh_pe_bus_get() and immediately before iterating affected PEs and devices. This ensures EEH-triggered PCI removes occur under proper bus rescan locking without recursive lock contention. The eeh_pe_loc_get() function has been split into two functions: eeh_pe_loc_get(struct eeh_pe *pe) which retrieves the loc for given PE. eeh_pe_loc_get_bus(struct pci_bus *bus) which retrieves the location code for given bus. This resolves lockdep warnings such as: [ 84.964298] [ T928] ============================================ [ 84.964304] [ T928] WARNING: possible recursive locking detected [ 84.964311] [ T928] 6.18.0-rc3 #51 Not tainted [ 84.964315] [ T928] -------------------------------------------- [ 84.964320] [ T928] eehd/928 is trying to acquire lock: [ 84.964324] [ T928] c000000003b29d58 (pci_rescan_remove_lock){+.+.}-{3:3}, at: pci_lock_rescan_remove+0x28/0x40 [ 84.964342] [ T928] but task is already holding lock: [ 84.964347] [ T928] c000000003b29d58 (pci_rescan_remove_lock){+.+.}-{3:3}, at: pci_lock_rescan_remove+0x28/0x40 [ 84.964357] [ T928] other info that might help us debug this: [ 84.964363] [ T928] Possible unsafe locking scenario: [ 84.964367] [ T928] CPU0 [ 84.964370] [ T928] ---- [ 84.964373] [ T928] lock(pci_rescan_remove_lock); [ 84.964378] [ T928] lock(pci_rescan_remove_lock); [ 84.964383] [ T928] *** DEADLOCK *** [ 84.964388] [ T928] May be due to missing lock nesting notation [ 84.964393] [ T928] 1 lock held by eehd/928: [ 84.964397] [ T928] #0: c000000003b29d58 (pci_rescan_remove_lock){+.+.}-{3:3}, at: pci_lock_rescan_remove+0x28/0x40 [ 84.964408] [ T928] stack backtrace: [ 84.964414] [ T928] CPU: 2 UID: 0 PID: 928 Comm: eehd Not tainted 6.18.0-rc3 #51 VOLUNTARY [ 84.964417] [ T928] Hardware name: IBM,9080-HEX POWER10 (architected) 0x800200 0xf000006 of:IBM,FW1060.00 (NH1060_022) hv:phyp pSeries [ 84.964419] [ T928] Call Trace: [ 84.964420] [ T928] [c0000011a7157990] [c000000001705de4] dump_stack_lvl+0xc8/0x130 (unreliable) [ 84.964424] [ T928] [c0000011a71579d0] [c0000000002f66e0] print_deadlock_bug+0x430/0x440 [ 84.964428] [ T928] [c0000011a7157a70] [c0000000002fd0c0] __lock_acquire+0x1530/0x2d80 [ 84.964431] [ T928] [c0000011a7157ba0] [c0000000002fea54] lock_acquire+0x144/0x410 [ 84.964433] [ T928] [c0000011a7157cb0] [c0000011a7157cb0] __mutex_lock+0xf4/0x1050 [ 84.964436] [ T928] [c0000011a7157e00] [c000000000de21d8] pci_lock_rescan_remove+0x28/0x40 [ 84.964439] [ T928] [c0000011a7157e20] [c00000000004ed98] eeh_pe_bus_get+0x48/0xc0 [ 84.964442] [ T928] [c0000011a7157e50] [c000000000050434] eeh_handle_normal_event+0x64/0xa60 [ 84.964446] [ T928] [c0000011a7157f30] [c000000000051de8] eeh_event_handler+0xf8/0x190 [ 84.964450] [ T928] [c0000011a7157f90] [c0000000002747ac] kthread+0x16c/0x180 [ 84.964453] [ T928] [c0000011a7157fe0] [c00000000000ded8] start_kernel_thread+0x14/0x18 Fixes: 1010b4c012b0 ("powerpc/eeh: Make EEH driver device hotplug safe") Signed-off-by: Narayana Murty N Reviewed-by: Sourabh Jain Reviewed-by: Mahesh Salgaonkar Signed-off-by: Madhavan Srinivasan Link: https://patch.msgid.link/20251210142559.8874-1-nnmlinux@linux.ibm.com Signed-off-by: Sasha Levin --- arch/powerpc/include/asm/eeh.h | 2 + arch/powerpc/kernel/eeh_driver.c | 11 ++--- arch/powerpc/kernel/eeh_pe.c | 74 ++++++++++++++++++++++++++++++-- 3 files changed, 78 insertions(+), 9 deletions(-) diff --git a/arch/powerpc/include/asm/eeh.h b/arch/powerpc/include/asm/eeh.h index 5e34611de9ef40..b7ebb4ac2c7103 100644 --- a/arch/powerpc/include/asm/eeh.h +++ b/arch/powerpc/include/asm/eeh.h @@ -289,6 +289,8 @@ void eeh_pe_dev_traverse(struct eeh_pe *root, void eeh_pe_restore_bars(struct eeh_pe *pe); const char *eeh_pe_loc_get(struct eeh_pe *pe); struct pci_bus *eeh_pe_bus_get(struct eeh_pe *pe); +const char *eeh_pe_loc_get_bus(struct pci_bus *bus); +struct pci_bus *eeh_pe_bus_get_nolock(struct eeh_pe *pe); void eeh_show_enabled(void); int __init eeh_init(struct eeh_ops *ops); diff --git a/arch/powerpc/kernel/eeh_driver.c b/arch/powerpc/kernel/eeh_driver.c index ef78ff77cf8f21..028f6915853234 100644 --- a/arch/powerpc/kernel/eeh_driver.c +++ b/arch/powerpc/kernel/eeh_driver.c @@ -846,7 +846,7 @@ void eeh_handle_normal_event(struct eeh_pe *pe) pci_lock_rescan_remove(); - bus = eeh_pe_bus_get(pe); + bus = eeh_pe_bus_get_nolock(pe); if (!bus) { pr_err("%s: Cannot find PCI bus for PHB#%x-PE#%x\n", __func__, pe->phb->global_number, pe->addr); @@ -886,14 +886,15 @@ void eeh_handle_normal_event(struct eeh_pe *pe) /* Log the event */ if (pe->type & EEH_PE_PHB) { pr_err("EEH: Recovering PHB#%x, location: %s\n", - pe->phb->global_number, eeh_pe_loc_get(pe)); + pe->phb->global_number, eeh_pe_loc_get_bus(bus)); } else { struct eeh_pe *phb_pe = eeh_phb_pe_get(pe->phb); pr_err("EEH: Recovering PHB#%x-PE#%x\n", pe->phb->global_number, pe->addr); pr_err("EEH: PE location: %s, PHB location: %s\n", - eeh_pe_loc_get(pe), eeh_pe_loc_get(phb_pe)); + eeh_pe_loc_get_bus(bus), + eeh_pe_loc_get_bus(eeh_pe_bus_get_nolock(phb_pe))); } #ifdef CONFIG_STACKTRACE @@ -1098,7 +1099,7 @@ void eeh_handle_normal_event(struct eeh_pe *pe) eeh_pe_state_clear(pe, EEH_PE_PRI_BUS, true); eeh_pe_dev_mode_mark(pe, EEH_DEV_REMOVED); - bus = eeh_pe_bus_get(pe); + bus = eeh_pe_bus_get_nolock(pe); if (bus) pci_hp_remove_devices(bus); else @@ -1222,7 +1223,7 @@ void eeh_handle_special_event(void) (phb_pe->state & EEH_PE_RECOVERING)) continue; - bus = eeh_pe_bus_get(phb_pe); + bus = eeh_pe_bus_get_nolock(phb_pe); if (!bus) { pr_err("%s: Cannot find PCI bus for " "PHB#%x-PE#%x\n", diff --git a/arch/powerpc/kernel/eeh_pe.c b/arch/powerpc/kernel/eeh_pe.c index e740101fadf3b1..040e8f69a4aa86 100644 --- a/arch/powerpc/kernel/eeh_pe.c +++ b/arch/powerpc/kernel/eeh_pe.c @@ -812,6 +812,24 @@ void eeh_pe_restore_bars(struct eeh_pe *pe) const char *eeh_pe_loc_get(struct eeh_pe *pe) { struct pci_bus *bus = eeh_pe_bus_get(pe); + return eeh_pe_loc_get_bus(bus); +} + +/** + * eeh_pe_loc_get_bus - Retrieve location code binding to the given PCI bus + * @bus: PCI bus + * + * Retrieve the location code associated with the given PCI bus. If the bus + * is a root bus, the location code is fetched from the PHB device tree node + * or root port. Otherwise, the location code is obtained from the device + * tree node of the upstream bridge of the bus. The function walks up the + * bus hierarchy if necessary, checking each node for the appropriate + * location code property ("ibm,io-base-loc-code" for root buses, + * "ibm,slot-location-code" for others). If no location code is found, + * returns "N/A". + */ +const char *eeh_pe_loc_get_bus(struct pci_bus *bus) +{ struct device_node *dn; const char *loc = NULL; @@ -838,8 +856,9 @@ const char *eeh_pe_loc_get(struct eeh_pe *pe) } /** - * eeh_pe_bus_get - Retrieve PCI bus according to the given PE + * _eeh_pe_bus_get - Retrieve PCI bus according to the given PE * @pe: EEH PE + * @do_lock: Is the caller already held the pci_lock_rescan_remove? * * Retrieve the PCI bus according to the given PE. Basically, * there're 3 types of PEs: PHB/Bus/Device. For PHB PE, the @@ -847,7 +866,7 @@ const char *eeh_pe_loc_get(struct eeh_pe *pe) * returned for BUS PE. However, we don't have associated PCI * bus for DEVICE PE. */ -struct pci_bus *eeh_pe_bus_get(struct eeh_pe *pe) +static struct pci_bus *_eeh_pe_bus_get(struct eeh_pe *pe, bool do_lock) { struct eeh_dev *edev; struct pci_dev *pdev; @@ -862,11 +881,58 @@ struct pci_bus *eeh_pe_bus_get(struct eeh_pe *pe) /* Retrieve the parent PCI bus of first (top) PCI device */ edev = list_first_entry_or_null(&pe->edevs, struct eeh_dev, entry); - pci_lock_rescan_remove(); + if (do_lock) + pci_lock_rescan_remove(); pdev = eeh_dev_to_pci_dev(edev); if (pdev) bus = pdev->bus; - pci_unlock_rescan_remove(); + if (do_lock) + pci_unlock_rescan_remove(); return bus; } + +/** + * eeh_pe_bus_get - Retrieve PCI bus associated with the given EEH PE, locking + * if needed + * @pe: Pointer to the EEH PE + * + * This function is a wrapper around _eeh_pe_bus_get(), which retrieves the PCI + * bus associated with the provided EEH PE structure. It acquires the PCI + * rescans lock to ensure safe access to shared data during the retrieval + * process. This function should be used when the caller requires the PCI bus + * while holding the rescan/remove lock, typically during operations that modify + * or inspect PCIe device state in a safe manner. + * + * RETURNS: + * A pointer to the PCI bus associated with the EEH PE, or NULL if none found. + */ + +struct pci_bus *eeh_pe_bus_get(struct eeh_pe *pe) +{ + return _eeh_pe_bus_get(pe, true); +} + +/** + * eeh_pe_bus_get_nolock - Retrieve PCI bus associated with the given EEH PE + * without locking + * @pe: Pointer to the EEH PE + * + * This function is a variant of _eeh_pe_bus_get() that retrieves the PCI bus + * associated with the specified EEH PE without acquiring the + * pci_lock_rescan_remove lock. It should only be used when the caller can + * guarantee safe access to PE structures without the need for that lock, + * typically in contexts where the lock is already held locking is otherwise + * managed. + * + * RETURNS: + * pointer to the PCI bus associated with the EEH PE, or NULL if none is found. + * + * NOTE: + * Use this function carefully to avoid race conditions and data corruption. + */ + +struct pci_bus *eeh_pe_bus_get_nolock(struct eeh_pe *pe) +{ + return _eeh_pe_bus_get(pe, false); +} From bcab50ed6e73cf6307da13386a16cfa8723bb28e Mon Sep 17 00:00:00 2001 From: Lad Prabhakar Date: Tue, 6 Jan 2026 13:13:19 +0000 Subject: [PATCH 3397/3902] arm64: dts: renesas: rzt2h-n2h-evk-common: Use GPIO for SD0 write protect [ Upstream commit a1b1ee0348f889ec262482e16e9ff670617db7b0 ] Switch SD0 write-protect detection to a GPIO on the RZ/T2H and RZ/N2H EVKs. Both boards use a full-size SD card slot on the SD0 channel with a dedicated WP pin. The RZ/T2H and RZ/N2H SoCs use of_data_rcar_gen3, which sets MMC_CAP2_NO_WRITE_PROTECT and causes the core to ignore the WP signal unless a wp-gpios property is provided. Describe the WP pin as a GPIO to allow the MMC core to evaluate the write-protect status correctly. Fixes: d065453e5ee0 ("arm64: dts: renesas: rzt2h-rzn2h-evk: Enable SD card slot") Signed-off-by: Lad Prabhakar Reviewed-by: Geert Uytterhoeven Link: https://patch.msgid.link/20260106131319.643084-1-prabhakar.mahadev-lad.rj@bp.renesas.com Signed-off-by: Geert Uytterhoeven Signed-off-by: Sasha Levin --- arch/arm64/boot/dts/renesas/rzt2h-n2h-evk-common.dtsi | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/arch/arm64/boot/dts/renesas/rzt2h-n2h-evk-common.dtsi b/arch/arm64/boot/dts/renesas/rzt2h-n2h-evk-common.dtsi index 5c91002c99c483..5384a43837c1d4 100644 --- a/arch/arm64/boot/dts/renesas/rzt2h-n2h-evk-common.dtsi +++ b/arch/arm64/boot/dts/renesas/rzt2h-n2h-evk-common.dtsi @@ -154,8 +154,7 @@ ctrl-pins { pinmux = , /* SD0_CLK */ , /* SD0_CMD */ - , /* SD0_CD */ - ; /* SD0_WP */ + ; /* SD0_CD */ }; }; @@ -212,6 +211,7 @@ pinctrl-names = "default", "state_uhs"; vmmc-supply = <®_3p3v>; vqmmc-supply = <&vqmmc_sdhi0>; + wp-gpios = <&pinctrl RZT2H_GPIO(22, 6) GPIO_ACTIVE_HIGH>; bus-width = <4>; sd-uhs-sdr50; sd-uhs-sdr104; From 6666c9d544323a49f862f5de55c68c5ff40cd91b Mon Sep 17 00:00:00 2001 From: Vladimir Zapolskiy Date: Mon, 29 Dec 2025 00:49:07 +0200 Subject: [PATCH 3398/3902] arm: dts: lpc32xx: add clocks property to Motor Control PWM device tree node [ Upstream commit 71630e581a0e34c03757f5c1706f57c853b92555 ] Motor Control PWM depends on its own supply clock, the clock gate control is present in TIMCLK_CTRL1 register. Fixes: b7d41c937ed7 ("ARM: LPC32xx: Add the motor PWM to base dts file") Signed-off-by: Vladimir Zapolskiy Signed-off-by: Sasha Levin --- arch/arm/boot/dts/nxp/lpc/lpc32xx.dtsi | 1 + 1 file changed, 1 insertion(+) diff --git a/arch/arm/boot/dts/nxp/lpc/lpc32xx.dtsi b/arch/arm/boot/dts/nxp/lpc/lpc32xx.dtsi index 2236901a003130..8e9ed93da129e8 100644 --- a/arch/arm/boot/dts/nxp/lpc/lpc32xx.dtsi +++ b/arch/arm/boot/dts/nxp/lpc/lpc32xx.dtsi @@ -302,6 +302,7 @@ mpwm: pwm@400e8000 { compatible = "nxp,lpc3220-motor-pwm"; reg = <0x400e8000 0x78>; + clocks = <&clk LPC32XX_CLK_MCPWM>; #pwm-cells = <3>; status = "disabled"; }; From 85cf4f7c2cf22fed8763927e909be6bd25cd3fb6 Mon Sep 17 00:00:00 2001 From: AngeloGioacchino Del Regno Date: Fri, 9 Jan 2026 12:47:41 +0100 Subject: [PATCH 3399/3902] arm64: dts: mediatek: mt8183-jacuzzi-pico6: Fix typo in pinmux node [ Upstream commit b1fc81a986c9b8089db31e21a372cc8b6514e900 ] Rename "piins-bt-wakeup" to "pins-bt-wakeup" to fix a dtbs_check warning happening due to this typo. Fixes: 055ef10ccdd4 ("arm64: dts: mt8183: Add jacuzzi pico/pico6 board") Reviewed-by: Chen-Yu Tsai Signed-off-by: AngeloGioacchino Del Regno Signed-off-by: Sasha Levin --- arch/arm64/boot/dts/mediatek/mt8183-kukui-jacuzzi-pico6.dts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/arch/arm64/boot/dts/mediatek/mt8183-kukui-jacuzzi-pico6.dts b/arch/arm64/boot/dts/mediatek/mt8183-kukui-jacuzzi-pico6.dts index cce326aec1aa59..40af5656d6f15f 100644 --- a/arch/arm64/boot/dts/mediatek/mt8183-kukui-jacuzzi-pico6.dts +++ b/arch/arm64/boot/dts/mediatek/mt8183-kukui-jacuzzi-pico6.dts @@ -91,7 +91,7 @@ &pio { bt_pins_wakeup: bt-pins-wakeup { - piins-bt-wakeup { + pins-bt-wakeup { pinmux = ; input-enable; }; From 5a5ec520e74012637f141879fe21d4c371371063 Mon Sep 17 00:00:00 2001 From: Jerome Brunet Date: Wed, 14 Jan 2026 11:43:11 +0100 Subject: [PATCH 3400/3902] arm64: dts: amlogic: s4: assign mmc b clock to 24MHz [ Upstream commit 86124a8becb43eed3103f2459399daee8af2c99d ] The amlogic MMC driver operate with the assumption that MMC clock is configured to provide 24MHz. It uses this path for low rates such as 400kHz. This assumption did hold true until but it now, but it is apparently not the case with s4. The clock has been reported to provide 1GHz instead. This is most likely due to how the bootloader is using the MMC clock on this platform. Regardless of why the MMC clock rate is 1GHz, if the MMC driver expects 24MHz, the clock should be properly assigned, so assign it. Reported-by: Nick Xie Closes: https://lore.kernel.org/linux-amlogic/20260113011931.40424-1-nick@khadas.com/ Fixes: 3ab9d54b5d84 ("arm64: dts: amlogic: enable some device nodes for S4") Tested-by: Nick Xie Signed-off-by: Jerome Brunet Reviewed-by: Neil Armstrong Link: https://patch.msgid.link/20260114-amlogic-s4-mmc-fixup-v3-1-a4d3e136b3f2@baylibre.com Signed-off-by: Neil Armstrong Signed-off-by: Sasha Levin --- arch/arm64/boot/dts/amlogic/meson-s4.dtsi | 3 +++ 1 file changed, 3 insertions(+) diff --git a/arch/arm64/boot/dts/amlogic/meson-s4.dtsi b/arch/arm64/boot/dts/amlogic/meson-s4.dtsi index 9d99ed2994dfa2..f314f07062abe6 100644 --- a/arch/arm64/boot/dts/amlogic/meson-s4.dtsi +++ b/arch/arm64/boot/dts/amlogic/meson-s4.dtsi @@ -838,6 +838,9 @@ clock-names = "core", "clkin0", "clkin1"; resets = <&reset RESET_SD_EMMC_B>; status = "disabled"; + + assigned-clocks = <&clkc_periphs CLKID_SD_EMMC_B>; + assigned-clock-rates = <24000000>; }; emmc: mmc@fe08c000 { From 1f71dfc03471bf489f973fac134042c137964016 Mon Sep 17 00:00:00 2001 From: Jerome Brunet Date: Wed, 14 Jan 2026 11:43:12 +0100 Subject: [PATCH 3401/3902] arm64: dts: amlogic: s4: fix mmc clock assignment [ Upstream commit 3a115d42922cffc91b303992eadf220111d66c31 ] MMC A and C are mis-represented as having their "clkin0" input connected to xtal while it is actually connected to the MMC clock, probably in an attempt to provide 24MHz to the device on this input. Fix this and assign the clock to 24MHz to actually provide the required rate. Fixes: 3ab9d54b5d84 ("arm64: dts: amlogic: enable some device nodes for S4") Tested-by: Nick Xie Signed-off-by: Jerome Brunet Reviewed-by: Neil Armstrong Link: https://patch.msgid.link/20260114-amlogic-s4-mmc-fixup-v3-2-a4d3e136b3f2@baylibre.com Signed-off-by: Neil Armstrong Signed-off-by: Sasha Levin --- arch/arm64/boot/dts/amlogic/meson-s4.dtsi | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/arch/arm64/boot/dts/amlogic/meson-s4.dtsi b/arch/arm64/boot/dts/amlogic/meson-s4.dtsi index f314f07062abe6..dfc0a30a6e61be 100644 --- a/arch/arm64/boot/dts/amlogic/meson-s4.dtsi +++ b/arch/arm64/boot/dts/amlogic/meson-s4.dtsi @@ -819,13 +819,16 @@ reg = <0x0 0xfe088000 0x0 0x800>; interrupts = ; clocks = <&clkc_periphs CLKID_SDEMMC_A>, - <&xtal>, + <&clkc_periphs CLKID_SD_EMMC_A>, <&clkc_pll CLKID_FCLK_DIV2>; clock-names = "core", "clkin0", "clkin1"; resets = <&reset RESET_SD_EMMC_A>; cap-sdio-irq; keep-power-in-suspend; status = "disabled"; + + assigned-clocks = <&clkc_periphs CLKID_SD_EMMC_A>; + assigned-clock-rates = <24000000>; }; sd: mmc@fe08a000 { @@ -848,13 +851,16 @@ reg = <0x0 0xfe08c000 0x0 0x800>; interrupts = ; clocks = <&clkc_periphs CLKID_NAND>, - <&xtal>, + <&clkc_periphs CLKID_SD_EMMC_C>, <&clkc_pll CLKID_FCLK_DIV2>; clock-names = "core", "clkin0", "clkin1"; resets = <&reset RESET_NAND_EMMC>; no-sdio; no-sd; status = "disabled"; + + assigned-clocks = <&clkc_periphs CLKID_SD_EMMC_C>; + assigned-clock-rates = <24000000>; }; }; }; From 8273989e774abdf6504094b52b33d6852ad73c10 Mon Sep 17 00:00:00 2001 From: Abhash Kumar Jha Date: Mon, 12 Jan 2026 14:21:12 +0530 Subject: [PATCH 3402/3902] arm64: dts: ti: k3-j784s4-main.dtsi: Move c71_3 node to appropriate order [ Upstream commit 24c9d5fb8bbf5e8c9e6fc2beffeb80ac2da83de4 ] The device tree nodes should be ordered by unit addresses in ascending order. Correct the order by moving the c71_3 DSP node at the end as it has a higher unit address. Signed-off-by: Abhash Kumar Jha Reviewed-by: Udit Kumar Link: https://patch.msgid.link/20260112085113.3476193-2-a-kumar2@ti.com Signed-off-by: Nishanth Menon Stable-dep-of: 61acc4428a7f ("arm64: dts: ti: k3-j784s4-j742s2-main-common.dtsi: Refactor watchdog instances for j784s4") Signed-off-by: Sasha Levin --- arch/arm64/boot/dts/ti/k3-j784s4-main.dtsi | 26 +++++++++++----------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/arch/arm64/boot/dts/ti/k3-j784s4-main.dtsi b/arch/arm64/boot/dts/ti/k3-j784s4-main.dtsi index 0160fe0da98388..5b7830a3c0975b 100644 --- a/arch/arm64/boot/dts/ti/k3-j784s4-main.dtsi +++ b/arch/arm64/boot/dts/ti/k3-j784s4-main.dtsi @@ -6,19 +6,6 @@ */ &cbass_main { - c71_3: dsp@67800000 { - compatible = "ti,j721s2-c71-dsp"; - reg = <0x00 0x67800000 0x00 0x00080000>, - <0x00 0x67e00000 0x00 0x0000c000>; - reg-names = "l2sram", "l1dram"; - resets = <&k3_reset 40 1>; - firmware-name = "j784s4-c71_3-fw"; - ti,sci = <&sms>; - ti,sci-dev-id = <40>; - ti,sci-proc-ids = <0x33 0xff>; - status = "disabled"; - }; - pcie2_rc: pcie@2920000 { compatible = "ti,j784s4-pcie-host"; reg = <0x00 0x02920000 0x00 0x1000>, @@ -113,6 +100,19 @@ status = "disabled"; }; }; + + c71_3: dsp@67800000 { + compatible = "ti,j721s2-c71-dsp"; + reg = <0x00 0x67800000 0x00 0x00080000>, + <0x00 0x67e00000 0x00 0x0000c000>; + reg-names = "l2sram", "l1dram"; + resets = <&k3_reset 40 1>; + firmware-name = "j784s4-c71_3-fw"; + ti,sci = <&sms>; + ti,sci-dev-id = <40>; + ti,sci-proc-ids = <0x33 0xff>; + status = "disabled"; + }; }; &scm_conf { From 6906a9889898934770e3386190e631c847359879 Mon Sep 17 00:00:00 2001 From: Abhash Kumar Jha Date: Mon, 12 Jan 2026 14:21:13 +0530 Subject: [PATCH 3403/3902] arm64: dts: ti: k3-j784s4-j742s2-main-common.dtsi: Refactor watchdog instances for j784s4 [ Upstream commit 61acc4428a7f52e0a13e226ba76f2ce2ca66c065 ] Each A72 core has one watchdog instance associated with it. Since j742s2 has 4 A72 cores, the common file should not define 8 watchdog instances. Refactor the last 4 extra watchdogs from the common file to j784s4 specific file, as j784s4 has 8 A72 cores and thus hardware description requires 8 watchdog instances. Fixes: 9cc161a4509c ("arm64: dts: ti: Refactor J784s4 SoC files to a common file") Signed-off-by: Abhash Kumar Jha Reviewed-by: Udit Kumar Link: https://patch.msgid.link/20260112085113.3476193-3-a-kumar2@ti.com Signed-off-by: Nishanth Menon Signed-off-by: Sasha Levin --- .../dts/ti/k3-j784s4-j742s2-main-common.dtsi | 36 ------------------- arch/arm64/boot/dts/ti/k3-j784s4-main.dtsi | 36 +++++++++++++++++++ 2 files changed, 36 insertions(+), 36 deletions(-) diff --git a/arch/arm64/boot/dts/ti/k3-j784s4-j742s2-main-common.dtsi b/arch/arm64/boot/dts/ti/k3-j784s4-j742s2-main-common.dtsi index 9cc0901d58fbf9..c2636e624f18ba 100644 --- a/arch/arm64/boot/dts/ti/k3-j784s4-j742s2-main-common.dtsi +++ b/arch/arm64/boot/dts/ti/k3-j784s4-j742s2-main-common.dtsi @@ -2378,42 +2378,6 @@ assigned-clock-parents = <&k3_clks 351 4>; }; - watchdog4: watchdog@2240000 { - compatible = "ti,j7-rti-wdt"; - reg = <0x00 0x2240000 0x00 0x100>; - clocks = <&k3_clks 352 0>; - power-domains = <&k3_pds 352 TI_SCI_PD_EXCLUSIVE>; - assigned-clocks = <&k3_clks 352 0>; - assigned-clock-parents = <&k3_clks 352 4>; - }; - - watchdog5: watchdog@2250000 { - compatible = "ti,j7-rti-wdt"; - reg = <0x00 0x2250000 0x00 0x100>; - clocks = <&k3_clks 353 0>; - power-domains = <&k3_pds 353 TI_SCI_PD_EXCLUSIVE>; - assigned-clocks = <&k3_clks 353 0>; - assigned-clock-parents = <&k3_clks 353 4>; - }; - - watchdog6: watchdog@2260000 { - compatible = "ti,j7-rti-wdt"; - reg = <0x00 0x2260000 0x00 0x100>; - clocks = <&k3_clks 354 0>; - power-domains = <&k3_pds 354 TI_SCI_PD_EXCLUSIVE>; - assigned-clocks = <&k3_clks 354 0>; - assigned-clock-parents = <&k3_clks 354 4>; - }; - - watchdog7: watchdog@2270000 { - compatible = "ti,j7-rti-wdt"; - reg = <0x00 0x2270000 0x00 0x100>; - clocks = <&k3_clks 355 0>; - power-domains = <&k3_pds 355 TI_SCI_PD_EXCLUSIVE>; - assigned-clocks = <&k3_clks 355 0>; - assigned-clock-parents = <&k3_clks 355 4>; - }; - /* * The following RTI instances are coupled with MCU R5Fs, c7x and * GPU so keeping them reserved as these will be used by their diff --git a/arch/arm64/boot/dts/ti/k3-j784s4-main.dtsi b/arch/arm64/boot/dts/ti/k3-j784s4-main.dtsi index 5b7830a3c0975b..78fcd0c40abcfa 100644 --- a/arch/arm64/boot/dts/ti/k3-j784s4-main.dtsi +++ b/arch/arm64/boot/dts/ti/k3-j784s4-main.dtsi @@ -6,6 +6,42 @@ */ &cbass_main { + watchdog4: watchdog@2240000 { + compatible = "ti,j7-rti-wdt"; + reg = <0x00 0x2240000 0x00 0x100>; + clocks = <&k3_clks 352 0>; + power-domains = <&k3_pds 352 TI_SCI_PD_EXCLUSIVE>; + assigned-clocks = <&k3_clks 352 0>; + assigned-clock-parents = <&k3_clks 352 4>; + }; + + watchdog5: watchdog@2250000 { + compatible = "ti,j7-rti-wdt"; + reg = <0x00 0x2250000 0x00 0x100>; + clocks = <&k3_clks 353 0>; + power-domains = <&k3_pds 353 TI_SCI_PD_EXCLUSIVE>; + assigned-clocks = <&k3_clks 353 0>; + assigned-clock-parents = <&k3_clks 353 4>; + }; + + watchdog6: watchdog@2260000 { + compatible = "ti,j7-rti-wdt"; + reg = <0x00 0x2260000 0x00 0x100>; + clocks = <&k3_clks 354 0>; + power-domains = <&k3_pds 354 TI_SCI_PD_EXCLUSIVE>; + assigned-clocks = <&k3_clks 354 0>; + assigned-clock-parents = <&k3_clks 354 4>; + }; + + watchdog7: watchdog@2270000 { + compatible = "ti,j7-rti-wdt"; + reg = <0x00 0x2270000 0x00 0x100>; + clocks = <&k3_clks 355 0>; + power-domains = <&k3_pds 355 TI_SCI_PD_EXCLUSIVE>; + assigned-clocks = <&k3_clks 355 0>; + assigned-clock-parents = <&k3_clks 355 4>; + }; + pcie2_rc: pcie@2920000 { compatible = "ti,j784s4-pcie-host"; reg = <0x00 0x02920000 0x00 0x1000>, From eeb84c4f43372eeadc360a6bf7cacd13ff23037e Mon Sep 17 00:00:00 2001 From: Dmitry Baryshkov Date: Sat, 10 Jan 2026 21:37:53 +0200 Subject: [PATCH 3404/3902] soc: qcom: ubwc: add missing include [ Upstream commit ccef4b2703ff5b0de0b1bda30a0de3026d52eb19 ] The header has a function which calls pr_err(). Don't require users of the header to include and include it here. Fixes: 87cfc79dcd60 ("drm/msm/a6xx: Resolve the meaning of UBWC_MODE") Signed-off-by: Dmitry Baryshkov Reviewed-by: Bryan O'Donoghue Link: https://lore.kernel.org/r/20260110-iris-ubwc-v1-1-dd70494dcd7b@oss.qualcomm.com Signed-off-by: Bjorn Andersson Signed-off-by: Sasha Levin --- include/linux/soc/qcom/ubwc.h | 1 + 1 file changed, 1 insertion(+) diff --git a/include/linux/soc/qcom/ubwc.h b/include/linux/soc/qcom/ubwc.h index 1ed8b1b16bc90b..d9dfc9edc1b2f3 100644 --- a/include/linux/soc/qcom/ubwc.h +++ b/include/linux/soc/qcom/ubwc.h @@ -8,6 +8,7 @@ #define __QCOM_UBWC_H__ #include +#include #include struct qcom_ubwc_cfg_data { From 6cc29c7148550fdca590ae3b731734fe138307fc Mon Sep 17 00:00:00 2001 From: Haotian Zhang Date: Mon, 24 Nov 2025 18:48:05 +0800 Subject: [PATCH 3405/3902] hwspinlock: omap: Handle devm_pm_runtime_enable() errors [ Upstream commit 3bd4edd67b034f8e1f61c86e0eb098de6179e3f2 ] Although unlikely, devm_pm_runtime_enable() can fail due to memory allocations. Without proper error handling, the subsequent pm_runtime_resume_and_get() call may operate on incorrectly initialized runtime PM state. Add error handling to check the return value of devm_pm_runtime_enable() and return on failure. Fixes: 25f7d74d4514 ("hwspinlock: omap: Use devm_pm_runtime_enable() helper") Signed-off-by: Haotian Zhang Link: https://patch.msgid.link/20251124104805.135-1-vulab@iscas.ac.cn Signed-off-by: Kevin Hilman Signed-off-by: Sasha Levin --- drivers/hwspinlock/omap_hwspinlock.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/drivers/hwspinlock/omap_hwspinlock.c b/drivers/hwspinlock/omap_hwspinlock.c index 27b47b8623c09c..2d8de835bc2429 100644 --- a/drivers/hwspinlock/omap_hwspinlock.c +++ b/drivers/hwspinlock/omap_hwspinlock.c @@ -88,7 +88,9 @@ static int omap_hwspinlock_probe(struct platform_device *pdev) * make sure the module is enabled and clocked before reading * the module SYSSTATUS register */ - devm_pm_runtime_enable(&pdev->dev); + ret = devm_pm_runtime_enable(&pdev->dev); + if (ret) + return ret; ret = pm_runtime_resume_and_get(&pdev->dev); if (ret < 0) return ret; From f6e54c1ea7b84f8fcb32a4205bb421d32c769bcd Mon Sep 17 00:00:00 2001 From: Jerome Brunet Date: Wed, 14 Jan 2026 18:08:48 +0100 Subject: [PATCH 3406/3902] arm64: dts: amlogic: c3: assign the MMC signal clocks [ Upstream commit 69330fd2368371c4eb47d60ace6bca09763d24a0 ] The amlogic MMC driver operate with the assumption that MMC clock is configured to provide 24MHz. It uses this path for low rates such as 400kHz. Assign the clocks to make sure they are properly configured Fixes: 520b792e8317 ("arm64: dts: amlogic: add some device nodes for C3") Signed-off-by: Jerome Brunet Reviewed-by: Neil Armstrong Link: https://patch.msgid.link/20260114-amlogic-mmc-clocks-followup-v1-1-a999fafbe0aa@baylibre.com Signed-off-by: Neil Armstrong Signed-off-by: Sasha Levin --- arch/arm64/boot/dts/amlogic/amlogic-c3.dtsi | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/arch/arm64/boot/dts/amlogic/amlogic-c3.dtsi b/arch/arm64/boot/dts/amlogic/amlogic-c3.dtsi index 07aaaf71ea9aed..f226df3ce153ba 100644 --- a/arch/arm64/boot/dts/amlogic/amlogic-c3.dtsi +++ b/arch/arm64/boot/dts/amlogic/amlogic-c3.dtsi @@ -969,6 +969,10 @@ no-sd; resets = <&reset RESET_SD_EMMC_A>; status = "disabled"; + + assigned-clocks = <&clkc_periphs CLKID_SD_EMMC_A>; + assigned-clock-rates = <24000000>; + }; sd: mmc@8a000 { @@ -984,6 +988,9 @@ no-sdio; resets = <&reset RESET_SD_EMMC_B>; status = "disabled"; + + assigned-clocks = <&clkc_periphs CLKID_SD_EMMC_B>; + assigned-clock-rates = <24000000>; }; nand: nand-controller@8d000 { From c9bb634897e337b0d4502b2430624fa9e789b6ed Mon Sep 17 00:00:00 2001 From: Jerome Brunet Date: Wed, 14 Jan 2026 18:08:50 +0100 Subject: [PATCH 3407/3902] arm64: dts: amlogic: axg: assign the MMC signal clocks [ Upstream commit 13d3fe2318ef6e46d6fcfe13bc373827fdf2aeac ] The amlogic MMC driver operate with the assumption that MMC clock is configured to provide 24MHz. It uses this path for low rates such as 400kHz. Assign the clocks to make sure they are properly configured Fixes: 221cf34bac54 ("ARM64: dts: meson-axg: enable the eMMC controller") Signed-off-by: Jerome Brunet Reviewed-by: Neil Armstrong Link: https://patch.msgid.link/20260114-amlogic-mmc-clocks-followup-v1-3-a999fafbe0aa@baylibre.com Signed-off-by: Neil Armstrong Signed-off-by: Sasha Levin --- arch/arm64/boot/dts/amlogic/meson-axg.dtsi | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/arch/arm64/boot/dts/amlogic/meson-axg.dtsi b/arch/arm64/boot/dts/amlogic/meson-axg.dtsi index 04fb130ac7c6a4..bbf94a1f92a107 100644 --- a/arch/arm64/boot/dts/amlogic/meson-axg.dtsi +++ b/arch/arm64/boot/dts/amlogic/meson-axg.dtsi @@ -1960,6 +1960,9 @@ <&clkc CLKID_FCLK_DIV2>; clock-names = "core", "clkin0", "clkin1"; resets = <&reset RESET_SD_EMMC_B>; + + assigned-clocks = <&clkc CLKID_SD_EMMC_B_CLK0>; + assigned-clock-rates = <24000000>; }; sd_emmc_c: mmc@7000 { @@ -1972,6 +1975,9 @@ <&clkc CLKID_FCLK_DIV2>; clock-names = "core", "clkin0", "clkin1"; resets = <&reset RESET_SD_EMMC_C>; + + assigned-clocks = <&clkc CLKID_SD_EMMC_C_CLK0>; + assigned-clock-rates = <24000000>; }; nfc: nand-controller@7800 { From 0709a0901956c3465d63046f6a9cb80a105aa5ef Mon Sep 17 00:00:00 2001 From: Jerome Brunet Date: Wed, 14 Jan 2026 18:08:51 +0100 Subject: [PATCH 3408/3902] arm64: dts: amlogic: gx: assign the MMC signal clocks [ Upstream commit 406706559046eebc09a31e8ae5e78620bfd746fe ] The amlogic MMC driver operate with the assumption that MMC clock is configured to provide 24MHz. It uses this path for low rates such as 400kHz. Assign the clocks to make sure they are properly configured Fixes: 50662499f911 ("ARM64: dts: meson-gx: Use correct mmc clock source 0") Signed-off-by: Jerome Brunet Reviewed-by: Neil Armstrong Link: https://patch.msgid.link/20260114-amlogic-mmc-clocks-followup-v1-4-a999fafbe0aa@baylibre.com Signed-off-by: Neil Armstrong Signed-off-by: Sasha Levin --- arch/arm64/boot/dts/amlogic/meson-gxbb.dtsi | 9 +++++++++ arch/arm64/boot/dts/amlogic/meson-gxl.dtsi | 9 +++++++++ 2 files changed, 18 insertions(+) diff --git a/arch/arm64/boot/dts/amlogic/meson-gxbb.dtsi b/arch/arm64/boot/dts/amlogic/meson-gxbb.dtsi index f69923da07febd..a9c830a570cc6c 100644 --- a/arch/arm64/boot/dts/amlogic/meson-gxbb.dtsi +++ b/arch/arm64/boot/dts/amlogic/meson-gxbb.dtsi @@ -824,6 +824,9 @@ <&clkc CLKID_FCLK_DIV2>; clock-names = "core", "clkin0", "clkin1"; resets = <&reset RESET_SD_EMMC_A>; + + assigned-clocks = <&clkc CLKID_SD_EMMC_A_CLK0>; + assigned-clock-rates = <24000000>; }; &sd_emmc_b { @@ -832,6 +835,9 @@ <&clkc CLKID_FCLK_DIV2>; clock-names = "core", "clkin0", "clkin1"; resets = <&reset RESET_SD_EMMC_B>; + + assigned-clocks = <&clkc CLKID_SD_EMMC_B_CLK0>; + assigned-clock-rates = <24000000>; }; &sd_emmc_c { @@ -840,6 +846,9 @@ <&clkc CLKID_FCLK_DIV2>; clock-names = "core", "clkin0", "clkin1"; resets = <&reset RESET_SD_EMMC_C>; + + assigned-clocks = <&clkc CLKID_SD_EMMC_C_CLK0>; + assigned-clock-rates = <24000000>; }; &simplefb_hdmi { diff --git a/arch/arm64/boot/dts/amlogic/meson-gxl.dtsi b/arch/arm64/boot/dts/amlogic/meson-gxl.dtsi index ba535010a3c91d..e202d84f067205 100644 --- a/arch/arm64/boot/dts/amlogic/meson-gxl.dtsi +++ b/arch/arm64/boot/dts/amlogic/meson-gxl.dtsi @@ -894,6 +894,9 @@ <&clkc CLKID_FCLK_DIV2>; clock-names = "core", "clkin0", "clkin1"; resets = <&reset RESET_SD_EMMC_A>; + + assigned-clocks = <&clkc CLKID_SD_EMMC_A_CLK0>; + assigned-clock-rates = <24000000>; }; &sd_emmc_b { @@ -902,6 +905,9 @@ <&clkc CLKID_FCLK_DIV2>; clock-names = "core", "clkin0", "clkin1"; resets = <&reset RESET_SD_EMMC_B>; + + assigned-clocks = <&clkc CLKID_SD_EMMC_B_CLK0>; + assigned-clock-rates = <24000000>; }; &sd_emmc_c { @@ -910,6 +916,9 @@ <&clkc CLKID_FCLK_DIV2>; clock-names = "core", "clkin0", "clkin1"; resets = <&reset RESET_SD_EMMC_C>; + + assigned-clocks = <&clkc CLKID_SD_EMMC_C_CLK0>; + assigned-clock-rates = <24000000>; }; &simplefb_hdmi { From 844317f9b007587055b44c541a87ad9f49df55c4 Mon Sep 17 00:00:00 2001 From: Jerome Brunet Date: Wed, 14 Jan 2026 18:08:52 +0100 Subject: [PATCH 3409/3902] arm64: dts: amlogic: g12: assign the MMC B and C signal clocks [ Upstream commit be2ff5fdb0e83e32d4ec4e68a69875cec0d14621 ] The amlogic MMC driver operate with the assumption that MMC clock is configured to provide 24MHz. It uses this path for low rates such as 400kHz. Assign the clocks to make sure they are properly configured Fixes: 4759fd87b928 ("arm64: dts: meson: g12a: add mmc nodes") Signed-off-by: Jerome Brunet Reviewed-by: Neil Armstrong Link: https://patch.msgid.link/20260114-amlogic-mmc-clocks-followup-v1-5-a999fafbe0aa@baylibre.com Signed-off-by: Neil Armstrong Signed-off-by: Sasha Levin --- arch/arm64/boot/dts/amlogic/meson-g12-common.dtsi | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/arch/arm64/boot/dts/amlogic/meson-g12-common.dtsi b/arch/arm64/boot/dts/amlogic/meson-g12-common.dtsi index dcc927a9da8024..6724405eaa6f54 100644 --- a/arch/arm64/boot/dts/amlogic/meson-g12-common.dtsi +++ b/arch/arm64/boot/dts/amlogic/meson-g12-common.dtsi @@ -2443,6 +2443,9 @@ <&clkc CLKID_FCLK_DIV2>; clock-names = "core", "clkin0", "clkin1"; resets = <&reset RESET_SD_EMMC_B>; + + assigned-clocks = <&clkc CLKID_SD_EMMC_B_CLK0>; + assigned-clock-rates = <24000000>; }; sd_emmc_c: mmc@ffe07000 { @@ -2455,6 +2458,9 @@ <&clkc CLKID_FCLK_DIV2>; clock-names = "core", "clkin0", "clkin1"; resets = <&reset RESET_SD_EMMC_C>; + + assigned-clocks = <&clkc CLKID_SD_EMMC_C_CLK0>; + assigned-clock-rates = <24000000>; }; usb: usb@ffe09000 { From 33bc5e10165d5239e469bcfa6efee0f13c0f7a36 Mon Sep 17 00:00:00 2001 From: Jerome Brunet Date: Wed, 14 Jan 2026 18:08:53 +0100 Subject: [PATCH 3410/3902] arm64: dts: amlogic: g12: assign the MMC A signal clock [ Upstream commit 3c941feaa363f1573a501452391ddf513394c84b ] The amlogic MMC driver operate with the assumption that MMC clock is configured to provide 24MHz. It uses this path for low rates such as 400kHz. Assign the clock to make sure it is properly configured Fixes: 8a6b3ca2d361 ("arm64: dts: meson: g12a: add SDIO controller") Signed-off-by: Jerome Brunet Reviewed-by: Neil Armstrong Link: https://patch.msgid.link/20260114-amlogic-mmc-clocks-followup-v1-6-a999fafbe0aa@baylibre.com Signed-off-by: Neil Armstrong Signed-off-by: Sasha Levin --- arch/arm64/boot/dts/amlogic/meson-g12-common.dtsi | 3 +++ 1 file changed, 3 insertions(+) diff --git a/arch/arm64/boot/dts/amlogic/meson-g12-common.dtsi b/arch/arm64/boot/dts/amlogic/meson-g12-common.dtsi index 6724405eaa6f54..8d8ab775404d91 100644 --- a/arch/arm64/boot/dts/amlogic/meson-g12-common.dtsi +++ b/arch/arm64/boot/dts/amlogic/meson-g12-common.dtsi @@ -2431,6 +2431,9 @@ <&clkc CLKID_FCLK_DIV2>; clock-names = "core", "clkin0", "clkin1"; resets = <&reset RESET_SD_EMMC_A>; + + assigned-clocks = <&clkc CLKID_SD_EMMC_A_CLK0>; + assigned-clock-rates = <24000000>; }; sd_emmc_b: mmc@ffe05000 { From 2d7ea0516f2beced16bf51aefc8c8aa214294df9 Mon Sep 17 00:00:00 2001 From: Dmitry Baryshkov Date: Tue, 6 Jan 2026 03:01:16 +0200 Subject: [PATCH 3411/3902] arm64: dts: qcom: qrb4210-rb2: Fix UART3 wakeup IRQ storm [ Upstream commit c5dc4812f6bf397b82290c540085e9ec98b47b30 ] Follow commit 9c92d36b0b1e ("arm64: dts: qcom: qrb2210-rb1: Fix UART3 wakeup IRQ storm") and apply the similar fix to the RB2 platform. Having RX / TX pins as pull up and wakup interrupt as high-level triggered generates an interrupt storm when trying to suspend the device. Avoid the storm by using the falling edge trigger (as all other platforms do). Fixes: cab60b166575 ("arm64: dts: qcom: qrb4210-rb2: Enable bluetooth") Reviewed-by: Konrad Dybcio Signed-off-by: Dmitry Baryshkov Link: https://lore.kernel.org/r/20260106-wcn3990-pwrctl-v2-6-0386204328be@oss.qualcomm.com Signed-off-by: Bjorn Andersson Signed-off-by: Sasha Levin --- arch/arm64/boot/dts/qcom/qrb4210-rb2.dts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/arch/arm64/boot/dts/qcom/qrb4210-rb2.dts b/arch/arm64/boot/dts/qcom/qrb4210-rb2.dts index bdf2d66e40c625..44ca3e61c33d49 100644 --- a/arch/arm64/boot/dts/qcom/qrb4210-rb2.dts +++ b/arch/arm64/boot/dts/qcom/qrb4210-rb2.dts @@ -694,7 +694,7 @@ &uart3 { interrupts-extended = <&intc GIC_SPI 330 IRQ_TYPE_LEVEL_HIGH>, - <&tlmm 11 IRQ_TYPE_LEVEL_HIGH>; + <&tlmm 11 IRQ_TYPE_EDGE_FALLING>; pinctrl-0 = <&uart3_default>; pinctrl-1 = <&uart3_sleep>; pinctrl-names = "default", "sleep"; From 39974be66f8173cd7fe11b59081bdf3683cd4566 Mon Sep 17 00:00:00 2001 From: Dmitry Baryshkov Date: Tue, 6 Jan 2026 03:01:17 +0200 Subject: [PATCH 3412/3902] arm64: dts: qcom: sdm845-db845c: drop CS from SPIO0 [ Upstream commit 8bfb696ccdc5bcfad7a45b84c2c8a36757070e19 ] On SDM845 SPI uses hardware-provided chip select, while specifying cs-gpio makes the driver request GPIO pin, which on DB845c conflicts with the normal host controllers pinctrl entry. Drop the cs-gpios property to restore SPI functionality. Fixes: cb29e7106d4e ("arm64: dts: qcom: db845c: Add support for MCP2517FD") Reviewed-by: Konrad Dybcio Signed-off-by: Dmitry Baryshkov Link: https://lore.kernel.org/r/20260106-wcn3990-pwrctl-v2-7-0386204328be@oss.qualcomm.com Signed-off-by: Bjorn Andersson Signed-off-by: Sasha Levin --- arch/arm64/boot/dts/qcom/sdm845-db845c.dts | 1 - 1 file changed, 1 deletion(-) diff --git a/arch/arm64/boot/dts/qcom/sdm845-db845c.dts b/arch/arm64/boot/dts/qcom/sdm845-db845c.dts index 8abf3e909502f3..384be2f8b14117 100644 --- a/arch/arm64/boot/dts/qcom/sdm845-db845c.dts +++ b/arch/arm64/boot/dts/qcom/sdm845-db845c.dts @@ -850,7 +850,6 @@ status = "okay"; pinctrl-names = "default"; pinctrl-0 = <&qup_spi0_default>; - cs-gpios = <&tlmm 3 GPIO_ACTIVE_LOW>; can@0 { compatible = "microchip,mcp2517fd"; From 06557d72ea40ccc1e72257249e8412b257042b93 Mon Sep 17 00:00:00 2001 From: Dmitry Baryshkov Date: Tue, 6 Jan 2026 03:01:18 +0200 Subject: [PATCH 3413/3902] arm64: dts: qcom: sdm845-db845c: specify power for WiFi CH1 [ Upstream commit c303e89f7f17c29981d09f8beaaf60937ae8b1f2 ] Specify power supply for the second chain / antenna output of the onboard WiFi chip. Fixes: 3f72e2d3e682 ("arm64: dts: qcom: Add Dragonboard 845c") Reviewed-by: Konrad Dybcio Signed-off-by: Dmitry Baryshkov Link: https://lore.kernel.org/r/20260106-wcn3990-pwrctl-v2-8-0386204328be@oss.qualcomm.com Signed-off-by: Bjorn Andersson Signed-off-by: Sasha Levin --- arch/arm64/boot/dts/qcom/sdm845-db845c.dts | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/arch/arm64/boot/dts/qcom/sdm845-db845c.dts b/arch/arm64/boot/dts/qcom/sdm845-db845c.dts index 384be2f8b14117..5147d6d3cc26bf 100644 --- a/arch/arm64/boot/dts/qcom/sdm845-db845c.dts +++ b/arch/arm64/boot/dts/qcom/sdm845-db845c.dts @@ -379,6 +379,12 @@ regulator-initial-mode = ; }; + vreg_l23a_3p3: ldo23 { + regulator-min-microvolt = <3300000>; + regulator-max-microvolt = <3312000>; + regulator-initial-mode = ; + }; + vreg_l24a_3p075: ldo24 { regulator-min-microvolt = <3088000>; regulator-max-microvolt = <3088000>; @@ -1155,6 +1161,7 @@ vdd-1.8-xo-supply = <&vreg_l7a_1p8>; vdd-1.3-rfa-supply = <&vreg_l17a_1p3>; vdd-3.3-ch0-supply = <&vreg_l25a_3p3>; + vdd-3.3-ch1-supply = <&vreg_l23a_3p3>; qcom,snoc-host-cap-8bit-quirk; qcom,calibration-variant = "Thundercomm_DB845C"; From 37082c9b418d41c2fd01f2e88b3348046ad370fb Mon Sep 17 00:00:00 2001 From: Jonathan Marek Date: Thu, 27 Nov 2025 16:29:42 -0500 Subject: [PATCH 3414/3902] arm64: dts: qcom: x1e: bus is 40-bits (fix 64GB models) [ Upstream commit b38dd256e11a4c8bd5a893e11fc42d493939c907 ] Unlike the phone SoCs this was copied from, x1e has a 40-bit physical bus. The upper address space is used to support more than 32GB of memory. This fixes issues when DMA buffers are allocated outside the 36-bit range. Fixes: af16b00578a7 ("arm64: dts: qcom: Add base X1E80100 dtsi and the QCP dts") Signed-off-by: Jonathan Marek Reviewed-by: Konrad Dybcio Link: https://lore.kernel.org/r/20251127212943.24480-1-jonathan@marek.ca Signed-off-by: Bjorn Andersson Signed-off-by: Sasha Levin --- arch/arm64/boot/dts/qcom/x1e80100.dtsi | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/arch/arm64/boot/dts/qcom/x1e80100.dtsi b/arch/arm64/boot/dts/qcom/x1e80100.dtsi index 3290fd8c2d6e69..512a75da4f13f1 100644 --- a/arch/arm64/boot/dts/qcom/x1e80100.dtsi +++ b/arch/arm64/boot/dts/qcom/x1e80100.dtsi @@ -791,8 +791,8 @@ #address-cells = <2>; #size-cells = <2>; - dma-ranges = <0 0 0 0 0x10 0>; - ranges = <0 0 0 0 0x10 0>; + dma-ranges = <0 0 0 0 0x100 0>; + ranges = <0 0 0 0 0x100 0>; gcc: clock-controller@100000 { compatible = "qcom,x1e80100-gcc"; From 222e45e64bbca35147676a70b0ce750ab38d77e1 Mon Sep 17 00:00:00 2001 From: Viken Dadhaniya Date: Tue, 11 Nov 2025 22:33:50 +0530 Subject: [PATCH 3415/3902] arm64: dts: qcom: talos: Drop opp-shared from QUP OPP table [ Upstream commit dda4bdd325326dd67ae4401f4f3d35b9cf781e3f ] QUP devices are currently marked with opp-shared in their OPP table, causing the kernel to treat them as part of a shared OPP domain. This leads to the qcom_geni_serial driver failing to probe with error -EBUSY (-16). Remove the opp-shared property to ensure the OPP framework treats the QUP OPP table as device-specific, allowing the serial driver to probe successfully Fixes: f6746dc9e379 ("arm64: dts: qcom: qcs615: Add QUPv3 configuration") Signed-off-by: Viken Dadhaniya Reviewed-by: Konrad Dybcio Link: https://lore.kernel.org/r/20251111170350.525832-1-viken.dadhaniya@oss.qualcomm.com Signed-off-by: Bjorn Andersson Signed-off-by: Sasha Levin --- arch/arm64/boot/dts/qcom/sm6150.dtsi | 1 - 1 file changed, 1 deletion(-) diff --git a/arch/arm64/boot/dts/qcom/sm6150.dtsi b/arch/arm64/boot/dts/qcom/sm6150.dtsi index 64e7c9dbafc70b..363b9f436cd0a2 100644 --- a/arch/arm64/boot/dts/qcom/sm6150.dtsi +++ b/arch/arm64/boot/dts/qcom/sm6150.dtsi @@ -398,7 +398,6 @@ qup_opp_table: opp-table-qup { compatible = "operating-points-v2"; - opp-shared; opp-75000000 { opp-hz = /bits/ 64 <75000000>; From 58c0b917fdca3a6052d61bd1df140ea6e8a22763 Mon Sep 17 00:00:00 2001 From: Konrad Dybcio Date: Mon, 29 Dec 2025 21:47:40 +0100 Subject: [PATCH 3416/3902] arm64: dts: qcom: agatti: Add CX_MEM/DBGC GPU regions [ Upstream commit 0fdcc948929a6d673bd0f90631dd6e42090c3dbd ] Describe the GPU register regions, with the former existing but not being used much if at all on this silicon, and the latter containing various debugging levers generally related to dumping the state of the IP upon a crash. Fixes: 4faeef52c8e6 ("arm64: dts: qcom: qcm2290: Add GPU nodes") Reported-by: Krzysztof Kozlowski Closes: https://lore.kernel.org/linux-arm-msm/8a64f70b-8034-45e7-86a3-0015cf357132@oss.qualcomm.com/T/#m404f1425c36b61467760f058b696b8910340a063 Signed-off-by: Konrad Dybcio Reviewed-by: Dmitry Baryshkov Reviewed-by: Akhil P Oommen Link: https://lore.kernel.org/r/20251229-topic-6115_2290_gpu_dbgc-v1-2-4a24d196389c@oss.qualcomm.com Signed-off-by: Bjorn Andersson Signed-off-by: Sasha Levin --- arch/arm64/boot/dts/qcom/qcm2290.dtsi | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/arch/arm64/boot/dts/qcom/qcm2290.dtsi b/arch/arm64/boot/dts/qcom/qcm2290.dtsi index 3b0ba590ee825d..e0e400fdd2497e 100644 --- a/arch/arm64/boot/dts/qcom/qcm2290.dtsi +++ b/arch/arm64/boot/dts/qcom/qcm2290.dtsi @@ -1503,8 +1503,12 @@ gpu: gpu@5900000 { compatible = "qcom,adreno-07000200", "qcom,adreno"; - reg = <0x0 0x05900000 0x0 0x40000>; - reg-names = "kgsl_3d0_reg_memory"; + reg = <0x0 0x05900000 0x0 0x40000>, + <0x0 0x0599e000 0x0 0x1000>, + <0x0 0x05961000 0x0 0x800>; + reg-names = "kgsl_3d0_reg_memory", + "cx_mem", + "cx_dbgc"; interrupts = ; From 3c0dd95dd520822ef45c6d9235fe24ca3ffd6e6c Mon Sep 17 00:00:00 2001 From: Konrad Dybcio Date: Mon, 29 Dec 2025 21:47:41 +0100 Subject: [PATCH 3417/3902] arm64: dts: qcom: sm6115: Add CX_MEM/DBGC GPU regions [ Upstream commit 78c13dac18cf0e6f6cbc6ea85d4f967e6cca9562 ] Describe the GPU register regions, with the former existing but not being used much if at all on this silicon, and the latter containing various debugging levers generally related to dumping the state of the IP upon a crash. Fixes: 11750af256f8 ("arm64: dts: qcom: sm6115: Add GPU nodes") Reported-by: Krzysztof Kozlowski Closes: https://lore.kernel.org/linux-arm-msm/8a64f70b-8034-45e7-86a3-0015cf357132@oss.qualcomm.com/T/#m404f1425c36b61467760f058b696b8910340a063 Signed-off-by: Konrad Dybcio Reviewed-by: Dmitry Baryshkov Reviewed-by: Akhil P Oommen Link: https://lore.kernel.org/r/20251229-topic-6115_2290_gpu_dbgc-v1-3-4a24d196389c@oss.qualcomm.com Signed-off-by: Bjorn Andersson Signed-off-by: Sasha Levin --- arch/arm64/boot/dts/qcom/sm6115.dtsi | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/arch/arm64/boot/dts/qcom/sm6115.dtsi b/arch/arm64/boot/dts/qcom/sm6115.dtsi index 91fc36b59abf96..8b8395f6a2dfc2 100644 --- a/arch/arm64/boot/dts/qcom/sm6115.dtsi +++ b/arch/arm64/boot/dts/qcom/sm6115.dtsi @@ -1715,8 +1715,12 @@ gpu: gpu@5900000 { compatible = "qcom,adreno-610.0", "qcom,adreno"; - reg = <0x0 0x05900000 0x0 0x40000>; - reg-names = "kgsl_3d0_reg_memory"; + reg = <0x0 0x05900000 0x0 0x40000>, + <0x0 0x0599e000 0x0 0x1000>, + <0x0 0x05961000 0x0 0x800>; + reg-names = "kgsl_3d0_reg_memory", + "cx_mem", + "cx_dbgc"; /* There's no (real) GMU, so we have to handle quite a bunch of clocks! */ clocks = <&gpucc GPU_CC_GX_GFX3D_CLK>, From da9c1afd9991e591b987e3ecc5fe237357ce25a7 Mon Sep 17 00:00:00 2001 From: Junhui Liu Date: Sat, 17 Jan 2026 18:06:22 +0800 Subject: [PATCH 3418/3902] reset: canaan: k230: drop OF dependency and enable by default [ Upstream commit c7a5e01e229d21e0560d78bd645b4f7398667ce4 ] The driver doesn't use any symbols depending on CONFIG_OF, so drop the dependency. Also, enable it by default when ARCH_CANAAN is selected. Fixes: 360a7a647759 ("reset: canaan: add reset driver for Kendryte K230") Signed-off-by: Junhui Liu Signed-off-by: Philipp Zabel Signed-off-by: Sasha Levin --- drivers/reset/Kconfig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/reset/Kconfig b/drivers/reset/Kconfig index 78b7078478d46d..b3b9e0f9d8c485 100644 --- a/drivers/reset/Kconfig +++ b/drivers/reset/Kconfig @@ -150,7 +150,7 @@ config RESET_K210 config RESET_K230 tristate "Reset controller driver for Canaan Kendryte K230 SoC" depends on ARCH_CANAAN || COMPILE_TEST - depends on OF + default ARCH_CANAAN help Support for the Canaan Kendryte K230 RISC-V SoC reset controller. Say Y if you want to control reset signals provided by this From 57753f2c64c033a21a7400b3a2192db1cd6c890e Mon Sep 17 00:00:00 2001 From: Boris Brezillon Date: Fri, 28 Nov 2025 09:48:37 +0100 Subject: [PATCH 3419/3902] drm/panthor: Recover from panthor_gpu_flush_caches() failures [ Upstream commit 3c0a60195b37af83bbbaf223cd3a78945bace49e ] We have seen a few cases where the whole memory subsystem is blocked and flush operations never complete. When that happens, we want to: - schedule a reset, so we can recover from this situation - in the reset path, we need to reset the pending_reqs so we can send new commands after the reset - if more panthor_gpu_flush_caches() operations are queued after the timeout, we skip them and return -EIO directly to avoid needless waits (the memory block won't miraculously work again) Note that we drop the WARN_ON()s because these hangs can be triggered with buggy GPU jobs created by the UMD, and there's no way we can prevent it. We do keep the error messages though. v2: - New patch v3: - Collect R-b - Explicitly mention the fact we dropped the WARN_ON()s in the commit message v4: - No changes Fixes: 5cd894e258c4 ("drm/panthor: Add the GPU logical block") Reviewed-by: Steven Price Link: https://patch.msgid.link/20251128084841.3804658-4-boris.brezillon@collabora.com Signed-off-by: Boris Brezillon Signed-off-by: Sasha Levin --- drivers/gpu/drm/panthor/panthor_gpu.c | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/drivers/gpu/drm/panthor/panthor_gpu.c b/drivers/gpu/drm/panthor/panthor_gpu.c index db69449a5be099..b92c9e738dbfab 100644 --- a/drivers/gpu/drm/panthor/panthor_gpu.c +++ b/drivers/gpu/drm/panthor/panthor_gpu.c @@ -259,38 +259,42 @@ int panthor_gpu_l2_power_on(struct panthor_device *ptdev) int panthor_gpu_flush_caches(struct panthor_device *ptdev, u32 l2, u32 lsc, u32 other) { - bool timedout = false; unsigned long flags; + int ret = 0; /* Serialize cache flush operations. */ guard(mutex)(&ptdev->gpu->cache_flush_lock); spin_lock_irqsave(&ptdev->gpu->reqs_lock, flags); - if (!drm_WARN_ON(&ptdev->base, - ptdev->gpu->pending_reqs & GPU_IRQ_CLEAN_CACHES_COMPLETED)) { + if (!(ptdev->gpu->pending_reqs & GPU_IRQ_CLEAN_CACHES_COMPLETED)) { ptdev->gpu->pending_reqs |= GPU_IRQ_CLEAN_CACHES_COMPLETED; gpu_write(ptdev, GPU_CMD, GPU_FLUSH_CACHES(l2, lsc, other)); + } else { + ret = -EIO; } spin_unlock_irqrestore(&ptdev->gpu->reqs_lock, flags); + if (ret) + return ret; + if (!wait_event_timeout(ptdev->gpu->reqs_acked, !(ptdev->gpu->pending_reqs & GPU_IRQ_CLEAN_CACHES_COMPLETED), msecs_to_jiffies(100))) { spin_lock_irqsave(&ptdev->gpu->reqs_lock, flags); if ((ptdev->gpu->pending_reqs & GPU_IRQ_CLEAN_CACHES_COMPLETED) != 0 && !(gpu_read(ptdev, GPU_INT_RAWSTAT) & GPU_IRQ_CLEAN_CACHES_COMPLETED)) - timedout = true; + ret = -ETIMEDOUT; else ptdev->gpu->pending_reqs &= ~GPU_IRQ_CLEAN_CACHES_COMPLETED; spin_unlock_irqrestore(&ptdev->gpu->reqs_lock, flags); } - if (timedout) { + if (ret) { + panthor_device_schedule_reset(ptdev); drm_err(&ptdev->base, "Flush caches timeout"); - return -ETIMEDOUT; } - return 0; + return ret; } /** @@ -330,6 +334,7 @@ int panthor_gpu_soft_reset(struct panthor_device *ptdev) return -ETIMEDOUT; } + ptdev->gpu->pending_reqs = 0; return 0; } From 693a04f41a54b27cef92e775f9bff0499f595001 Mon Sep 17 00:00:00 2001 From: Boris Brezillon Date: Fri, 28 Nov 2025 10:48:34 +0100 Subject: [PATCH 3420/3902] drm/panthor: Fix the full_tick check [ Upstream commit a3c2d0b40b108bd45d44f6c1dfa33c39d577adcd ] We have a full tick when the remaining time to the next tick is zero, not the other way around. Declare a full_tick variable so we don't get that test wrong in other places. v2: - Add R-b v3: - Collect R-b Fixes: de8548813824 ("drm/panthor: Add the scheduler logical block") Reviewed-by: Steven Price Reviewed-by: Chia-I Wu Link: https://patch.msgid.link/20251128094839.3856402-4-boris.brezillon@collabora.com Signed-off-by: Boris Brezillon Signed-off-by: Sasha Levin --- drivers/gpu/drm/panthor/panthor_sched.c | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/drivers/gpu/drm/panthor/panthor_sched.c b/drivers/gpu/drm/panthor/panthor_sched.c index 881a07ffbabc8b..6e4667cbe1b821 100644 --- a/drivers/gpu/drm/panthor/panthor_sched.c +++ b/drivers/gpu/drm/panthor/panthor_sched.c @@ -2387,6 +2387,7 @@ static void tick_work(struct work_struct *work) u64 remaining_jiffies = 0, resched_delay; u64 now = get_jiffies_64(); int prio, ret, cookie; + bool full_tick; if (!drm_dev_enter(&ptdev->base, &cookie)) return; @@ -2398,15 +2399,17 @@ static void tick_work(struct work_struct *work) if (time_before64(now, sched->resched_target)) remaining_jiffies = sched->resched_target - now; + full_tick = remaining_jiffies == 0; + mutex_lock(&sched->lock); if (panthor_device_reset_is_pending(sched->ptdev)) goto out_unlock; - tick_ctx_init(sched, &ctx, remaining_jiffies != 0); + tick_ctx_init(sched, &ctx, full_tick); if (ctx.csg_upd_failed_mask) goto out_cleanup_ctx; - if (remaining_jiffies) { + if (!full_tick) { /* Scheduling forced in the middle of a tick. Only RT groups * can preempt non-RT ones. Currently running RT groups can't be * preempted. From 73f2614637f642f96d2c82dde83250a3986f396d Mon Sep 17 00:00:00 2001 From: Boris Brezillon Date: Fri, 28 Nov 2025 10:48:35 +0100 Subject: [PATCH 3421/3902] drm/panthor: Fix the group priority rotation logic [ Upstream commit 55429c51d5db3db24c2ad561944c6a0ca922d476 ] When rotating group priorities, we want the group with the highest priority to go back to the end of the queue, and all other active groups to get their priority bumped, otherwise some groups will never get a chance to run with the highest priority. This implies moving the rotation itself to tick_work(), and only dealing with old group ordering in tick_ctx_insert_old_group(). v2: - Add R-b - Fix the commit message v3: - Drop the full_tick argument in tick_ctx_init() - Collect R-b Fixes: de8548813824 ("drm/panthor: Add the scheduler logical block") Reviewed-by: Steven Price Reviewed-by: Chia-I Wu Link: https://patch.msgid.link/20251128094839.3856402-5-boris.brezillon@collabora.com Signed-off-by: Boris Brezillon Signed-off-by: Sasha Levin --- drivers/gpu/drm/panthor/panthor_sched.c | 52 +++++++++++++++---------- 1 file changed, 31 insertions(+), 21 deletions(-) diff --git a/drivers/gpu/drm/panthor/panthor_sched.c b/drivers/gpu/drm/panthor/panthor_sched.c index 6e4667cbe1b821..03062169b8d337 100644 --- a/drivers/gpu/drm/panthor/panthor_sched.c +++ b/drivers/gpu/drm/panthor/panthor_sched.c @@ -1989,31 +1989,22 @@ tick_ctx_pick_groups_from_list(const struct panthor_scheduler *sched, static void tick_ctx_insert_old_group(struct panthor_scheduler *sched, struct panthor_sched_tick_ctx *ctx, - struct panthor_group *group, - bool full_tick) + struct panthor_group *group) { struct panthor_csg_slot *csg_slot = &sched->csg_slots[group->csg_id]; struct panthor_group *other_group; - if (!full_tick) { - list_add_tail(&group->run_node, &ctx->old_groups[group->priority]); - return; - } - - /* Rotate to make sure groups with lower CSG slot - * priorities have a chance to get a higher CSG slot - * priority next time they get picked. This priority - * has an impact on resource request ordering, so it's - * important to make sure we don't let one group starve - * all other groups with the same group priority. - */ + /* Class groups in descending priority order so we can easily rotate. */ list_for_each_entry(other_group, &ctx->old_groups[csg_slot->group->priority], run_node) { struct panthor_csg_slot *other_csg_slot = &sched->csg_slots[other_group->csg_id]; - if (other_csg_slot->priority > csg_slot->priority) { - list_add_tail(&csg_slot->group->run_node, &other_group->run_node); + /* Our group has a higher prio than the one we're testing against, + * place it just before. + */ + if (csg_slot->priority > other_csg_slot->priority) { + list_add_tail(&group->run_node, &other_group->run_node); return; } } @@ -2023,8 +2014,7 @@ tick_ctx_insert_old_group(struct panthor_scheduler *sched, static void tick_ctx_init(struct panthor_scheduler *sched, - struct panthor_sched_tick_ctx *ctx, - bool full_tick) + struct panthor_sched_tick_ctx *ctx) { struct panthor_device *ptdev = sched->ptdev; struct panthor_csg_slots_upd_ctx upd_ctx; @@ -2062,7 +2052,7 @@ tick_ctx_init(struct panthor_scheduler *sched, group->fatal_queues |= GENMASK(group->queue_count - 1, 0); } - tick_ctx_insert_old_group(sched, ctx, group, full_tick); + tick_ctx_insert_old_group(sched, ctx, group); csgs_upd_ctx_queue_reqs(ptdev, &upd_ctx, i, csg_iface->output->ack ^ CSG_STATUS_UPDATE, CSG_STATUS_UPDATE); @@ -2405,7 +2395,7 @@ static void tick_work(struct work_struct *work) if (panthor_device_reset_is_pending(sched->ptdev)) goto out_unlock; - tick_ctx_init(sched, &ctx, full_tick); + tick_ctx_init(sched, &ctx); if (ctx.csg_upd_failed_mask) goto out_cleanup_ctx; @@ -2431,9 +2421,29 @@ static void tick_work(struct work_struct *work) for (prio = PANTHOR_CSG_PRIORITY_COUNT - 1; prio >= 0 && !tick_ctx_is_full(sched, &ctx); prio--) { + struct panthor_group *old_highest_prio_group = + list_first_entry_or_null(&ctx.old_groups[prio], + struct panthor_group, run_node); + + /* Pull out the group with the highest prio for rotation. */ + if (old_highest_prio_group) + list_del(&old_highest_prio_group->run_node); + + /* Re-insert old active groups so they get a chance to run with higher prio. */ + tick_ctx_pick_groups_from_list(sched, &ctx, &ctx.old_groups[prio], true, true); + + /* Fill the remaining slots with runnable groups. */ tick_ctx_pick_groups_from_list(sched, &ctx, &sched->groups.runnable[prio], true, false); - tick_ctx_pick_groups_from_list(sched, &ctx, &ctx.old_groups[prio], true, true); + + /* Re-insert the old group with the highest prio, and give it a chance to be + * scheduled again (but with a lower prio) if there's room left. + */ + if (old_highest_prio_group) { + list_add_tail(&old_highest_prio_group->run_node, &ctx.old_groups[prio]); + tick_ctx_pick_groups_from_list(sched, &ctx, &ctx.old_groups[prio], + true, true); + } } /* If we have free CSG slots left, pick idle groups */ From ef100b8e257b62905c9af20f1df83017613b1713 Mon Sep 17 00:00:00 2001 From: Boris Brezillon Date: Fri, 28 Nov 2025 10:48:36 +0100 Subject: [PATCH 3422/3902] drm/panthor: Fix immediate ticking on a disabled tick [ Upstream commit 4356d21994f4ff5c87305b874939b359f16f6677 ] We have a few paths where we schedule the tick work immediately without changing the resched_target. If the tick was stopped, this would lead to a remaining_jiffies that's always > 0, and it wouldn't force a full tick in that case. Add extra checks to cover that case properly. v2: - Fix typo - Simplify the code as suggested by Steve v3: - Collect R-b Fixes: de8548813824 ("drm/panthor: Add the scheduler logical block") Reviewed-by: Steven Price Reviewed-by: Chia-I Wu Link: https://patch.msgid.link/20251128094839.3856402-6-boris.brezillon@collabora.com Signed-off-by: Boris Brezillon Signed-off-by: Sasha Levin --- drivers/gpu/drm/panthor/panthor_sched.c | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/drivers/gpu/drm/panthor/panthor_sched.c b/drivers/gpu/drm/panthor/panthor_sched.c index 03062169b8d337..3d4ac739998253 100644 --- a/drivers/gpu/drm/panthor/panthor_sched.c +++ b/drivers/gpu/drm/panthor/panthor_sched.c @@ -2374,6 +2374,7 @@ static void tick_work(struct work_struct *work) tick_work.work); struct panthor_device *ptdev = sched->ptdev; struct panthor_sched_tick_ctx ctx; + u64 resched_target = sched->resched_target; u64 remaining_jiffies = 0, resched_delay; u64 now = get_jiffies_64(); int prio, ret, cookie; @@ -2386,8 +2387,12 @@ static void tick_work(struct work_struct *work) if (drm_WARN_ON(&ptdev->base, ret)) goto out_dev_exit; - if (time_before64(now, sched->resched_target)) - remaining_jiffies = sched->resched_target - now; + /* If the tick is stopped, calculate when the next tick would be */ + if (resched_target == U64_MAX) + resched_target = sched->last_tick + sched->tick_period; + + if (time_before64(now, resched_target)) + remaining_jiffies = resched_target - now; full_tick = remaining_jiffies == 0; From d2f485242e3116d3c2da7e02462df7a0eb4a62ae Mon Sep 17 00:00:00 2001 From: Boris Brezillon Date: Fri, 28 Nov 2025 10:48:37 +0100 Subject: [PATCH 3423/3902] drm/panthor: Fix the logic that decides when to stop ticking [ Upstream commit 61d9a43d70dc3e1709ecd14a34f6d5f01e21dfc9 ] When we have multiple active groups with the same priority, we need to keep ticking for the priority rotation to take place. If we don't do that, we might starve slots with lower priorities. It's annoying to deal with that in tick_ctx_update_resched_target(), so let's add a ::stop_tick field to the tick context which is initialized to true, and downgraded to false as soon as we detect something that requires to tick to happen. This way we can complement the current logic with extra conditions if needed. v2: - Add R-b v3: - Drop panthor_sched_tick_ctx::min_priority (no longer relevant) - Collect R-b Fixes: de8548813824 ("drm/panthor: Add the scheduler logical block") Reviewed-by: Steven Price Reviewed-by: Chia-I Wu Link: https://patch.msgid.link/20251128094839.3856402-7-boris.brezillon@collabora.com Signed-off-by: Boris Brezillon Signed-off-by: Sasha Levin --- drivers/gpu/drm/panthor/panthor_sched.c | 44 ++++++++++--------------- 1 file changed, 17 insertions(+), 27 deletions(-) diff --git a/drivers/gpu/drm/panthor/panthor_sched.c b/drivers/gpu/drm/panthor/panthor_sched.c index 3d4ac739998253..368e7f34491054 100644 --- a/drivers/gpu/drm/panthor/panthor_sched.c +++ b/drivers/gpu/drm/panthor/panthor_sched.c @@ -1902,10 +1902,10 @@ struct panthor_sched_tick_ctx { struct list_head groups[PANTHOR_CSG_PRIORITY_COUNT]; u32 idle_group_count; u32 group_count; - enum panthor_csg_priority min_priority; struct panthor_vm *vms[MAX_CS_PER_CSG]; u32 as_count; bool immediate_tick; + bool stop_tick; u32 csg_upd_failed_mask; }; @@ -1970,17 +1970,21 @@ tick_ctx_pick_groups_from_list(const struct panthor_scheduler *sched, if (!owned_by_tick_ctx) group_get(group); - list_move_tail(&group->run_node, &ctx->groups[group->priority]); ctx->group_count++; + + /* If we have more than one active group with the same priority, + * we need to keep ticking to rotate the CSG priority. + */ if (group_is_idle(group)) ctx->idle_group_count++; + else if (!list_empty(&ctx->groups[group->priority])) + ctx->stop_tick = false; + + list_move_tail(&group->run_node, &ctx->groups[group->priority]); if (i == ctx->as_count) ctx->vms[ctx->as_count++] = group->vm; - if (ctx->min_priority > group->priority) - ctx->min_priority = group->priority; - if (tick_ctx_is_full(sched, ctx)) return; } @@ -2024,7 +2028,7 @@ tick_ctx_init(struct panthor_scheduler *sched, memset(ctx, 0, sizeof(*ctx)); csgs_upd_ctx_init(&upd_ctx); - ctx->min_priority = PANTHOR_CSG_PRIORITY_COUNT; + ctx->stop_tick = true; for (i = 0; i < ARRAY_SIZE(ctx->groups); i++) { INIT_LIST_HEAD(&ctx->groups[i]); INIT_LIST_HEAD(&ctx->old_groups[i]); @@ -2336,32 +2340,18 @@ static u64 tick_ctx_update_resched_target(struct panthor_scheduler *sched, const struct panthor_sched_tick_ctx *ctx) { - /* We had space left, no need to reschedule until some external event happens. */ - if (!tick_ctx_is_full(sched, ctx)) - goto no_tick; - - /* If idle groups were scheduled, no need to wake up until some external - * event happens (group unblocked, new job submitted, ...). - */ - if (ctx->idle_group_count) - goto no_tick; + u64 resched_target; - if (drm_WARN_ON(&sched->ptdev->base, ctx->min_priority >= PANTHOR_CSG_PRIORITY_COUNT)) + if (ctx->stop_tick) goto no_tick; - /* If there are groups of the same priority waiting, we need to - * keep the scheduler ticking, otherwise, we'll just wait for - * new groups with higher priority to be queued. - */ - if (!list_empty(&sched->groups.runnable[ctx->min_priority])) { - u64 resched_target = sched->last_tick + sched->tick_period; + resched_target = sched->last_tick + sched->tick_period; - if (time_before64(sched->resched_target, sched->last_tick) || - time_before64(resched_target, sched->resched_target)) - sched->resched_target = resched_target; + if (time_before64(sched->resched_target, sched->last_tick) || + time_before64(resched_target, sched->resched_target)) + sched->resched_target = resched_target; - return sched->resched_target - sched->last_tick; - } + return sched->resched_target - sched->last_tick; no_tick: sched->resched_target = U64_MAX; From 8c66adcae91b3898be43fa0c830278543d7c7ba5 Mon Sep 17 00:00:00 2001 From: Boris Brezillon Date: Fri, 28 Nov 2025 10:48:38 +0100 Subject: [PATCH 3424/3902] drm/panthor: Make sure we resume the tick when new jobs are submitted [ Upstream commit 99820b4b7e50d9651f01d2d55b6b9ba92dcc5b99 ] If the group is already assigned a slot but was idle before this job submission, we need to make sure the priority rotation happens in the future. Extract the existing logic living in group_schedule_locked() and call this new sched_resume_tick() helper from the "group is assigned a slot" path. v2: - Add R-b v3: - Re-use queue_mask to clear the bit - Collect R-b Fixes: de8548813824 ("drm/panthor: Add the scheduler logical block") Reviewed-by: Steven Price Reviewed-by: Chia-I Wu Link: https://patch.msgid.link/20251128094839.3856402-8-boris.brezillon@collabora.com Signed-off-by: Boris Brezillon Signed-off-by: Sasha Levin --- drivers/gpu/drm/panthor/panthor_sched.c | 43 +++++++++++++++++++------ 1 file changed, 34 insertions(+), 9 deletions(-) diff --git a/drivers/gpu/drm/panthor/panthor_sched.c b/drivers/gpu/drm/panthor/panthor_sched.c index 368e7f34491054..8f11fe73bee234 100644 --- a/drivers/gpu/drm/panthor/panthor_sched.c +++ b/drivers/gpu/drm/panthor/panthor_sched.c @@ -2563,14 +2563,33 @@ static void sync_upd_work(struct work_struct *work) sched_queue_delayed_work(sched, tick, 0); } +static void sched_resume_tick(struct panthor_device *ptdev) +{ + struct panthor_scheduler *sched = ptdev->scheduler; + u64 delay_jiffies, now; + + drm_WARN_ON(&ptdev->base, sched->resched_target != U64_MAX); + + /* Scheduler tick was off, recalculate the resched_target based on the + * last tick event, and queue the scheduler work. + */ + now = get_jiffies_64(); + sched->resched_target = sched->last_tick + sched->tick_period; + if (sched->used_csg_slot_count == sched->csg_slot_count && + time_before64(now, sched->resched_target)) + delay_jiffies = min_t(unsigned long, sched->resched_target - now, ULONG_MAX); + else + delay_jiffies = 0; + + sched_queue_delayed_work(sched, tick, delay_jiffies); +} + static void group_schedule_locked(struct panthor_group *group, u32 queue_mask) { struct panthor_device *ptdev = group->ptdev; struct panthor_scheduler *sched = ptdev->scheduler; struct list_head *queue = &sched->groups.runnable[group->priority]; - u64 delay_jiffies = 0; bool was_idle; - u64 now; if (!group_can_run(group)) return; @@ -2615,13 +2634,7 @@ static void group_schedule_locked(struct panthor_group *group, u32 queue_mask) /* Scheduler tick was off, recalculate the resched_target based on the * last tick event, and queue the scheduler work. */ - now = get_jiffies_64(); - sched->resched_target = sched->last_tick + sched->tick_period; - if (sched->used_csg_slot_count == sched->csg_slot_count && - time_before64(now, sched->resched_target)) - delay_jiffies = min_t(unsigned long, sched->resched_target - now, ULONG_MAX); - - sched_queue_delayed_work(sched, tick, delay_jiffies); + sched_resume_tick(ptdev); } static void queue_stop(struct panthor_queue *queue, @@ -3222,6 +3235,18 @@ queue_run_job(struct drm_sched_job *sched_job) group_schedule_locked(group, BIT(job->queue_idx)); } else { + u32 queue_mask = BIT(job->queue_idx); + bool resume_tick = group_is_idle(group) && + (group->idle_queues & queue_mask) && + !(group->blocked_queues & queue_mask) && + sched->resched_target == U64_MAX; + + /* We just added something to the queue, so it's no longer idle. */ + group->idle_queues &= ~queue_mask; + + if (resume_tick) + sched_resume_tick(ptdev); + gpu_write(ptdev, CSF_DOORBELL(queue->doorbell_id), 1); if (!sched->pm.has_ref && !(group->blocked_queues & BIT(job->queue_idx))) { From 932c36b81325bcabbb90865697c64584202f6858 Mon Sep 17 00:00:00 2001 From: Lai Jiangshan Date: Fri, 21 Nov 2025 22:57:14 +0800 Subject: [PATCH 3425/3902] workqueue: Factor out assign_rescuer_work() [ Upstream commit 99ed6f62a46e91dc796b785618d646eeded1b230 ] Move the code to assign work to rescuer and assign_rescuer_work(). Signed-off-by: Lai Jiangshan Signed-off-by: Tejun Heo Stable-dep-of: e5a30c303b07 ("workqueue: Process rescuer work items one-by-one using a cursor") Signed-off-by: Sasha Levin --- kernel/workqueue.c | 29 ++++++++++++++++++----------- 1 file changed, 18 insertions(+), 11 deletions(-) diff --git a/kernel/workqueue.c b/kernel/workqueue.c index 45320e27a16c4a..2fa31871017250 100644 --- a/kernel/workqueue.c +++ b/kernel/workqueue.c @@ -3443,6 +3443,23 @@ static int worker_thread(void *__worker) goto woke_up; } +static bool assign_rescuer_work(struct pool_workqueue *pwq, struct worker *rescuer) +{ + struct worker_pool *pool = pwq->pool; + struct work_struct *work, *n; + + /* + * Slurp in all works issued via this workqueue and + * process'em. + */ + list_for_each_entry_safe(work, n, &pool->worklist, entry) { + if (get_work_pwq(work) == pwq && assign_work(work, rescuer, &n)) + pwq->stats[PWQ_STAT_RESCUED]++; + } + + return !list_empty(&rescuer->scheduled); +} + /** * rescuer_thread - the rescuer thread function * @__rescuer: self @@ -3497,7 +3514,6 @@ static int rescuer_thread(void *__rescuer) struct pool_workqueue *pwq = list_first_entry(&wq->maydays, struct pool_workqueue, mayday_node); struct worker_pool *pool = pwq->pool; - struct work_struct *work, *n; __set_current_state(TASK_RUNNING); list_del_init(&pwq->mayday_node); @@ -3508,18 +3524,9 @@ static int rescuer_thread(void *__rescuer) raw_spin_lock_irq(&pool->lock); - /* - * Slurp in all works issued via this workqueue and - * process'em. - */ WARN_ON_ONCE(!list_empty(&rescuer->scheduled)); - list_for_each_entry_safe(work, n, &pool->worklist, entry) { - if (get_work_pwq(work) == pwq && - assign_work(work, rescuer, &n)) - pwq->stats[PWQ_STAT_RESCUED]++; - } - if (!list_empty(&rescuer->scheduled)) { + if (assign_rescuer_work(pwq, rescuer)) { process_scheduled_works(rescuer); /* From 5ec7110f5ed405c437a8cc58538ae0322362f33f Mon Sep 17 00:00:00 2001 From: Lai Jiangshan Date: Fri, 21 Nov 2025 22:57:15 +0800 Subject: [PATCH 3426/3902] workqueue: Only assign rescuer work when really needed [ Upstream commit 7b05c90b3302cf3d830dfa6f8961376bcaf43b94 ] If the pwq does not need rescue (normal workers have been created or become available), the rescuer can immediately move on to other stalled pwqs. Signed-off-by: Lai Jiangshan Signed-off-by: Tejun Heo Stable-dep-of: e5a30c303b07 ("workqueue: Process rescuer work items one-by-one using a cursor") Signed-off-by: Sasha Levin --- kernel/workqueue.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/kernel/workqueue.c b/kernel/workqueue.c index 2fa31871017250..f678200ce86923 100644 --- a/kernel/workqueue.c +++ b/kernel/workqueue.c @@ -3448,6 +3448,10 @@ static bool assign_rescuer_work(struct pool_workqueue *pwq, struct worker *rescu struct worker_pool *pool = pwq->pool; struct work_struct *work, *n; + /* need rescue? */ + if (!pwq->nr_active || !need_to_create_worker(pool)) + return false; + /* * Slurp in all works issued via this workqueue and * process'em. From d8d97352bf5d5995e24670c176846795b93dbfe7 Mon Sep 17 00:00:00 2001 From: Lai Jiangshan Date: Mon, 8 Dec 2025 21:25:18 +0800 Subject: [PATCH 3427/3902] workqueue: Process rescuer work items one-by-one using a cursor [ Upstream commit e5a30c303b07a4d6083e0f7f051b53add6d93c5d ] Previously, the rescuer scanned for all matching work items at once and processed them within a single rescuer thread, which could cause one blocking work item to stall all others. Make the rescuer process work items one-by-one instead of slurping all matches in a single pass. Break the rescuer loop after finding and processing the first matching work item, then restart the search to pick up the next. This gives normal worker threads a chance to process other items which gives them the opportunity to be processed instead of waiting on the rescuer's queue and prevents a blocking work item from stalling the rest once memory pressure is relieved. Introduce a dummy cursor work item to avoid potentially O(N^2) rescans of the work list. The marker records the resume position for the next scan, eliminating redundant traversals. Also introduce RESCUER_BATCH to control the maximum number of work items the rescuer processes in each turn, and move on to other PWQs when the limit is reached. Cc: ying chen Reported-by: ying chen Fixes: e22bee782b3b ("workqueue: implement concurrency managed dynamic worker pool") Signed-off-by: Lai Jiangshan Signed-off-by: Tejun Heo Signed-off-by: Sasha Levin --- kernel/workqueue.c | 75 ++++++++++++++++++++++++++++++++++++---------- 1 file changed, 59 insertions(+), 16 deletions(-) diff --git a/kernel/workqueue.c b/kernel/workqueue.c index f678200ce86923..885a8b31f855ba 100644 --- a/kernel/workqueue.c +++ b/kernel/workqueue.c @@ -117,6 +117,8 @@ enum wq_internal_consts { MAYDAY_INTERVAL = HZ / 10, /* and then every 100ms */ CREATE_COOLDOWN = HZ, /* time to breath after fail */ + RESCUER_BATCH = 16, /* process items per turn */ + /* * Rescue workers are used only on emergencies and shared by * all cpus. Give MIN_NICE. @@ -286,6 +288,7 @@ struct pool_workqueue { struct list_head pending_node; /* LN: node on wq_node_nr_active->pending_pwqs */ struct list_head pwqs_node; /* WR: node on wq->pwqs */ struct list_head mayday_node; /* MD: node on wq->maydays */ + struct work_struct mayday_cursor; /* L: cursor on pool->worklist */ u64 stats[PWQ_NR_STATS]; @@ -1126,6 +1129,12 @@ static struct worker *find_worker_executing_work(struct worker_pool *pool, return NULL; } +static void mayday_cursor_func(struct work_struct *work) +{ + /* should not be processed, only for marking position */ + BUG(); +} + /** * move_linked_works - move linked works to a list * @work: start of series of works to be scheduled @@ -1188,6 +1197,16 @@ static bool assign_work(struct work_struct *work, struct worker *worker, lockdep_assert_held(&pool->lock); + /* The cursor work should not be processed */ + if (unlikely(work->func == mayday_cursor_func)) { + /* only worker_thread() can possibly take this branch */ + WARN_ON_ONCE(worker->rescue_wq); + if (nextp) + *nextp = list_next_entry(work, entry); + list_del_init(&work->entry); + return false; + } + /* * A single work shouldn't be executed concurrently by multiple workers. * __queue_work() ensures that @work doesn't jump to a different pool @@ -3446,22 +3465,30 @@ static int worker_thread(void *__worker) static bool assign_rescuer_work(struct pool_workqueue *pwq, struct worker *rescuer) { struct worker_pool *pool = pwq->pool; + struct work_struct *cursor = &pwq->mayday_cursor; struct work_struct *work, *n; /* need rescue? */ if (!pwq->nr_active || !need_to_create_worker(pool)) return false; - /* - * Slurp in all works issued via this workqueue and - * process'em. - */ - list_for_each_entry_safe(work, n, &pool->worklist, entry) { - if (get_work_pwq(work) == pwq && assign_work(work, rescuer, &n)) + /* search from the start or cursor if available */ + if (list_empty(&cursor->entry)) + work = list_first_entry(&pool->worklist, struct work_struct, entry); + else + work = list_next_entry(cursor, entry); + + /* find the next work item to rescue */ + list_for_each_entry_safe_from(work, n, &pool->worklist, entry) { + if (get_work_pwq(work) == pwq && assign_work(work, rescuer, &n)) { pwq->stats[PWQ_STAT_RESCUED]++; + /* put the cursor for next search */ + list_move_tail(&cursor->entry, &n->entry); + return true; + } } - return !list_empty(&rescuer->scheduled); + return false; } /** @@ -3518,6 +3545,7 @@ static int rescuer_thread(void *__rescuer) struct pool_workqueue *pwq = list_first_entry(&wq->maydays, struct pool_workqueue, mayday_node); struct worker_pool *pool = pwq->pool; + unsigned int count = 0; __set_current_state(TASK_RUNNING); list_del_init(&pwq->mayday_node); @@ -3530,19 +3558,16 @@ static int rescuer_thread(void *__rescuer) WARN_ON_ONCE(!list_empty(&rescuer->scheduled)); - if (assign_rescuer_work(pwq, rescuer)) { + while (assign_rescuer_work(pwq, rescuer)) { process_scheduled_works(rescuer); /* - * The above execution of rescued work items could - * have created more to rescue through - * pwq_activate_first_inactive() or chained - * queueing. Let's put @pwq back on mayday list so - * that such back-to-back work items, which may be - * being used to relieve memory pressure, don't - * incur MAYDAY_INTERVAL delay inbetween. + * If the per-turn work item limit is reached and other + * PWQs are in mayday, requeue mayday for this PWQ and + * let the rescuer handle the other PWQs first. */ - if (pwq->nr_active && need_to_create_worker(pool)) { + if (++count > RESCUER_BATCH && !list_empty(&pwq->wq->maydays) && + pwq->nr_active && need_to_create_worker(pool)) { raw_spin_lock(&wq_mayday_lock); /* * Queue iff we aren't racing destruction @@ -3553,9 +3578,14 @@ static int rescuer_thread(void *__rescuer) list_add_tail(&pwq->mayday_node, &wq->maydays); } raw_spin_unlock(&wq_mayday_lock); + break; } } + /* The cursor can not be left behind without the rescuer watching it. */ + if (!list_empty(&pwq->mayday_cursor.entry) && list_empty(&pwq->mayday_node)) + list_del_init(&pwq->mayday_cursor.entry); + /* * Leave this pool. Notify regular workers; otherwise, we end up * with 0 concurrency and stalling the execution. @@ -5174,6 +5204,19 @@ static void init_pwq(struct pool_workqueue *pwq, struct workqueue_struct *wq, INIT_LIST_HEAD(&pwq->pwqs_node); INIT_LIST_HEAD(&pwq->mayday_node); kthread_init_work(&pwq->release_work, pwq_release_workfn); + + /* + * Set the dummy cursor work with valid function and get_work_pwq(). + * + * The cursor work should only be in the pwq->pool->worklist, and + * should not be treated as a processable work item. + * + * WORK_STRUCT_PENDING and WORK_STRUCT_INACTIVE just make it less + * surprise for kernel debugging tools and reviewers. + */ + INIT_WORK(&pwq->mayday_cursor, mayday_cursor_func); + atomic_long_set(&pwq->mayday_cursor.data, (unsigned long)pwq | + WORK_STRUCT_PENDING | WORK_STRUCT_PWQ | WORK_STRUCT_INACTIVE); } /* sync @pwq with the current state of its associated wq and link it */ From 0b0d8ed13d292f7de564b71f83c17c327a07ee32 Mon Sep 17 00:00:00 2001 From: Boris Brezillon Date: Mon, 8 Dec 2025 11:08:29 +0100 Subject: [PATCH 3428/3902] drm/panthor: Fix panthor_gpu_coherency_set() [ Upstream commit 9beb8dca9e749e9983e70b22e9823e6fcd519f91 ] GPU_COHERENCY_PROTOCOL takes one of GPU_COHERENCY_xx not BIT(GPU_COHERENCY_xx). v3: - New commit v4: - Add Steve's R-b v5: - No changes v6: - No changes v7: - No changes v8: - No changes Cc: Akash Goel Fixes: dd7db8d911a1 ("drm/panthor: Explicitly set the coherency mode") Reported-by: Steven Price Reviewed-by: Steven Price Link: https://patch.msgid.link/20251208100841.730527-3-boris.brezillon@collabora.com Signed-off-by: Boris Brezillon Signed-off-by: Sasha Levin --- drivers/gpu/drm/panthor/panthor_gpu.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/gpu/drm/panthor/panthor_gpu.c b/drivers/gpu/drm/panthor/panthor_gpu.c index b92c9e738dbfab..77d0f4ced12064 100644 --- a/drivers/gpu/drm/panthor/panthor_gpu.c +++ b/drivers/gpu/drm/panthor/panthor_gpu.c @@ -49,7 +49,7 @@ struct panthor_gpu { static void panthor_gpu_coherency_set(struct panthor_device *ptdev) { gpu_write(ptdev, GPU_COHERENCY_PROTOCOL, - ptdev->coherent ? GPU_COHERENCY_PROT_BIT(ACE_LITE) : GPU_COHERENCY_NONE); + ptdev->coherent ? GPU_COHERENCY_ACE_LITE : GPU_COHERENCY_NONE); } static void panthor_gpu_irq_handler(struct panthor_device *ptdev, u32 status) From 5b295f0133935e1693ecb7b3afeb6e2d88e8c77a Mon Sep 17 00:00:00 2001 From: Lizhi Hou Date: Wed, 10 Dec 2025 20:51:25 -0800 Subject: [PATCH 3429/3902] accel/amdxdna: Fix race where send ring appears full due to delayed head update [ Upstream commit 343f5683cfa443000904c88ce2e23656375fc51c ] The firmware sends a response and interrupts the driver before advancing the mailbox send ring head pointer. As a result, the driver may observe the response and attempt to send a new request before the firmware has updated the head pointer. In this window, the send ring still appears full, causing the driver to incorrectly fail the send operation. This race can be triggered more easily in a multithreaded environment, leading to unexpected and spurious "send ring full" failures. To address this, poll the send ring head pointer for up to 100us before returning a full-ring condition. This allows the firmware time to update the head pointer. Fixes: b87f920b9344 ("accel/amdxdna: Support hardware mailbox") Reviewed-by: Mario Limonciello (AMD) Signed-off-by: Lizhi Hou Link: https://patch.msgid.link/20251211045125.1724604-1-lizhi.hou@amd.com Signed-off-by: Sasha Levin --- drivers/accel/amdxdna/amdxdna_mailbox.c | 27 +++++++++++++++---------- 1 file changed, 16 insertions(+), 11 deletions(-) diff --git a/drivers/accel/amdxdna/amdxdna_mailbox.c b/drivers/accel/amdxdna/amdxdna_mailbox.c index 6634a4d5717ffb..a80c77a478bff0 100644 --- a/drivers/accel/amdxdna/amdxdna_mailbox.c +++ b/drivers/accel/amdxdna/amdxdna_mailbox.c @@ -206,26 +206,34 @@ mailbox_send_msg(struct mailbox_channel *mb_chann, struct mailbox_msg *mb_msg) u32 head, tail; u32 start_addr; u32 tmp_tail; + int ret; head = mailbox_get_headptr(mb_chann, CHAN_RES_X2I); tail = mb_chann->x2i_tail; - ringbuf_size = mailbox_get_ringbuf_size(mb_chann, CHAN_RES_X2I); + ringbuf_size = mailbox_get_ringbuf_size(mb_chann, CHAN_RES_X2I) - sizeof(u32); start_addr = mb_chann->res[CHAN_RES_X2I].rb_start_addr; tmp_tail = tail + mb_msg->pkg_size; - if (tail < head && tmp_tail >= head) - goto no_space; - - if (tail >= head && (tmp_tail > ringbuf_size - sizeof(u32) && - mb_msg->pkg_size >= head)) - goto no_space; - if (tail >= head && tmp_tail > ringbuf_size - sizeof(u32)) { +check_again: + if (tail >= head && tmp_tail > ringbuf_size) { write_addr = mb_chann->mb->res.ringbuf_base + start_addr + tail; writel(TOMBSTONE, write_addr); /* tombstone is set. Write from the start of the ringbuf */ tail = 0; + tmp_tail = tail + mb_msg->pkg_size; + } + + if (tail < head && tmp_tail >= head) { + ret = read_poll_timeout(mailbox_get_headptr, head, + tmp_tail < head || tail >= head, + 1, 100, false, mb_chann, CHAN_RES_X2I); + if (ret) + return ret; + + if (tail >= head) + goto check_again; } write_addr = mb_chann->mb->res.ringbuf_base + start_addr + tail; @@ -237,9 +245,6 @@ mailbox_send_msg(struct mailbox_channel *mb_chann, struct mailbox_msg *mb_msg) mb_msg->pkg.header.id); return 0; - -no_space: - return -ENOSPC; } static int From 082aba8e3aab961fc4d26a14dbfa397a23eb3978 Mon Sep 17 00:00:00 2001 From: David Heidelberg Date: Sun, 14 Dec 2025 15:51:21 +0100 Subject: [PATCH 3430/3902] drm/panel: sw43408: Remove manual invocation of unprepare at remove [ Upstream commit cbc1e99a9e0a6c8b22ddcbb40ca37457066f9493 ] The drm_panel_remove should take care of disable/unprepare. Remove the manual call from the sw43408_remove function. Fixes: 069a6c0e94f9 ("drm: panel: Add LG sw43408 panel driver") Reviewed-by: Dmitry Baryshkov Signed-off-by: David Heidelberg Signed-off-by: Neil Armstrong Link: https://patch.msgid.link/20251214-pixel-3-v7-5-b1c0cf6f224d@ixit.cz Signed-off-by: Sasha Levin --- drivers/gpu/drm/panel/panel-lg-sw43408.c | 4 ---- 1 file changed, 4 deletions(-) diff --git a/drivers/gpu/drm/panel/panel-lg-sw43408.c b/drivers/gpu/drm/panel/panel-lg-sw43408.c index 46a56ea92ad9ff..6e307fba658f78 100644 --- a/drivers/gpu/drm/panel/panel-lg-sw43408.c +++ b/drivers/gpu/drm/panel/panel-lg-sw43408.c @@ -294,10 +294,6 @@ static void sw43408_remove(struct mipi_dsi_device *dsi) struct sw43408_panel *ctx = mipi_dsi_get_drvdata(dsi); int ret; - ret = sw43408_unprepare(&ctx->base); - if (ret < 0) - dev_err(&dsi->dev, "failed to unprepare panel: %d\n", ret); - ret = mipi_dsi_detach(dsi); if (ret < 0) dev_err(&dsi->dev, "failed to detach from DSI host: %d\n", ret); From ed9acd01e907fa0d6d5224b03be2a188c5949d92 Mon Sep 17 00:00:00 2001 From: Takashi Iwai Date: Tue, 16 Dec 2025 15:06:25 +0100 Subject: [PATCH 3431/3902] ALSA: pcm: Relax __free() variable declarations [ Upstream commit f3d233daf011abbad2f6ebd0e545b42d2f378a4f ] We used to have a variable declaration with __free() initialized with NULL. This was to keep the old coding style rule, but recently it's relaxed and rather recommends to follow the new rule to declare in place of use for __free() -- which avoids potential deadlocks or UAFs with nested cleanups. Although the current code has no bug, per se, let's follow the new standard and move the declaration to the place of assignment (or directly assign the allocated result) instead of NULL initializations. Fixes: ae9213984864 ("ALSA: pcm: Use automatic cleanup of kfree()") Signed-off-by: Takashi Iwai Link: https://patch.msgid.link/20251216140634.171890-4-tiwai@suse.de Signed-off-by: Sasha Levin --- sound/core/pcm.c | 4 ++-- sound/core/pcm_compat.c | 9 ++++---- sound/core/pcm_native.c | 50 +++++++++++++++++++++-------------------- 3 files changed, 33 insertions(+), 30 deletions(-) diff --git a/sound/core/pcm.c b/sound/core/pcm.c index 283aac441fa0a7..0b512085eb63f2 100644 --- a/sound/core/pcm.c +++ b/sound/core/pcm.c @@ -328,13 +328,13 @@ static const char *snd_pcm_oss_format_name(int format) static void snd_pcm_proc_info_read(struct snd_pcm_substream *substream, struct snd_info_buffer *buffer) { - struct snd_pcm_info *info __free(kfree) = NULL; int err; if (! substream) return; - info = kmalloc(sizeof(*info), GFP_KERNEL); + struct snd_pcm_info *info __free(kfree) = + kmalloc(sizeof(*info), GFP_KERNEL); if (!info) return; diff --git a/sound/core/pcm_compat.c b/sound/core/pcm_compat.c index 54eb9bd8eb2188..e86f68f1f23c1e 100644 --- a/sound/core/pcm_compat.c +++ b/sound/core/pcm_compat.c @@ -235,7 +235,6 @@ static int snd_pcm_ioctl_hw_params_compat(struct snd_pcm_substream *substream, int refine, struct snd_pcm_hw_params32 __user *data32) { - struct snd_pcm_hw_params *data __free(kfree) = NULL; struct snd_pcm_runtime *runtime; int err; @@ -243,7 +242,8 @@ static int snd_pcm_ioctl_hw_params_compat(struct snd_pcm_substream *substream, if (!runtime) return -ENOTTY; - data = kmalloc(sizeof(*data), GFP_KERNEL); + struct snd_pcm_hw_params *data __free(kfree) = + kmalloc(sizeof(*data), GFP_KERNEL); if (!data) return -ENOMEM; @@ -332,7 +332,6 @@ static int snd_pcm_ioctl_xfern_compat(struct snd_pcm_substream *substream, compat_caddr_t buf; compat_caddr_t __user *bufptr; u32 frames; - void __user **bufs __free(kfree) = NULL; int err, ch, i; if (! substream->runtime) @@ -349,7 +348,9 @@ static int snd_pcm_ioctl_xfern_compat(struct snd_pcm_substream *substream, get_user(frames, &data32->frames)) return -EFAULT; bufptr = compat_ptr(buf); - bufs = kmalloc_array(ch, sizeof(void __user *), GFP_KERNEL); + + void __user **bufs __free(kfree) = + kmalloc_array(ch, sizeof(void __user *), GFP_KERNEL); if (bufs == NULL) return -ENOMEM; for (i = 0; i < ch; i++) { diff --git a/sound/core/pcm_native.c b/sound/core/pcm_native.c index 932a9bf98cbc09..844ee1b4d286f8 100644 --- a/sound/core/pcm_native.c +++ b/sound/core/pcm_native.c @@ -242,10 +242,10 @@ int snd_pcm_info(struct snd_pcm_substream *substream, struct snd_pcm_info *info) int snd_pcm_info_user(struct snd_pcm_substream *substream, struct snd_pcm_info __user * _info) { - struct snd_pcm_info *info __free(kfree) = NULL; int err; + struct snd_pcm_info *info __free(kfree) = + kmalloc(sizeof(*info), GFP_KERNEL); - info = kmalloc(sizeof(*info), GFP_KERNEL); if (! info) return -ENOMEM; err = snd_pcm_info(substream, info); @@ -364,7 +364,6 @@ static int constrain_params_by_rules(struct snd_pcm_substream *substream, struct snd_pcm_hw_constraints *constrs = &substream->runtime->hw_constraints; unsigned int k; - unsigned int *rstamps __free(kfree) = NULL; unsigned int vstamps[SNDRV_PCM_HW_PARAM_LAST_INTERVAL + 1]; unsigned int stamp; struct snd_pcm_hw_rule *r; @@ -380,7 +379,8 @@ static int constrain_params_by_rules(struct snd_pcm_substream *substream, * Each member of 'rstamps' array represents the sequence number of * recent application of corresponding rule. */ - rstamps = kcalloc(constrs->rules_num, sizeof(unsigned int), GFP_KERNEL); + unsigned int *rstamps __free(kfree) = + kcalloc(constrs->rules_num, sizeof(unsigned int), GFP_KERNEL); if (!rstamps) return -ENOMEM; @@ -583,10 +583,10 @@ EXPORT_SYMBOL(snd_pcm_hw_refine); static int snd_pcm_hw_refine_user(struct snd_pcm_substream *substream, struct snd_pcm_hw_params __user * _params) { - struct snd_pcm_hw_params *params __free(kfree) = NULL; int err; + struct snd_pcm_hw_params *params __free(kfree) = + memdup_user(_params, sizeof(*params)); - params = memdup_user(_params, sizeof(*params)); if (IS_ERR(params)) return PTR_ERR(params); @@ -889,10 +889,10 @@ static int snd_pcm_hw_params(struct snd_pcm_substream *substream, static int snd_pcm_hw_params_user(struct snd_pcm_substream *substream, struct snd_pcm_hw_params __user * _params) { - struct snd_pcm_hw_params *params __free(kfree) = NULL; int err; + struct snd_pcm_hw_params *params __free(kfree) = + memdup_user(_params, sizeof(*params)); - params = memdup_user(_params, sizeof(*params)); if (IS_ERR(params)) return PTR_ERR(params); @@ -2267,7 +2267,6 @@ static int snd_pcm_link(struct snd_pcm_substream *substream, int fd) { struct snd_pcm_file *pcm_file; struct snd_pcm_substream *substream1; - struct snd_pcm_group *group __free(kfree) = NULL; struct snd_pcm_group *target_group; bool nonatomic = substream->pcm->nonatomic; CLASS(fd, f)(fd); @@ -2283,7 +2282,8 @@ static int snd_pcm_link(struct snd_pcm_substream *substream, int fd) if (substream == substream1) return -EINVAL; - group = kzalloc(sizeof(*group), GFP_KERNEL); + struct snd_pcm_group *group __free(kfree) = + kzalloc(sizeof(*group), GFP_KERNEL); if (!group) return -ENOMEM; snd_pcm_group_init(group); @@ -3291,7 +3291,6 @@ static int snd_pcm_xfern_frames_ioctl(struct snd_pcm_substream *substream, { struct snd_xfern xfern; struct snd_pcm_runtime *runtime = substream->runtime; - void *bufs __free(kfree) = NULL; snd_pcm_sframes_t result; if (runtime->state == SNDRV_PCM_STATE_OPEN) @@ -3303,7 +3302,8 @@ static int snd_pcm_xfern_frames_ioctl(struct snd_pcm_substream *substream, if (copy_from_user(&xfern, _xfern, sizeof(xfern))) return -EFAULT; - bufs = memdup_array_user(xfern.bufs, runtime->channels, sizeof(void *)); + void *bufs __free(kfree) = + memdup_array_user(xfern.bufs, runtime->channels, sizeof(void *)); if (IS_ERR(bufs)) return PTR_ERR(bufs); if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) @@ -3577,7 +3577,6 @@ static ssize_t snd_pcm_readv(struct kiocb *iocb, struct iov_iter *to) struct snd_pcm_runtime *runtime; snd_pcm_sframes_t result; unsigned long i; - void __user **bufs __free(kfree) = NULL; snd_pcm_uframes_t frames; const struct iovec *iov = iter_iov(to); @@ -3596,7 +3595,9 @@ static ssize_t snd_pcm_readv(struct kiocb *iocb, struct iov_iter *to) if (!frame_aligned(runtime, iov->iov_len)) return -EINVAL; frames = bytes_to_samples(runtime, iov->iov_len); - bufs = kmalloc_array(to->nr_segs, sizeof(void *), GFP_KERNEL); + + void __user **bufs __free(kfree) = + kmalloc_array(to->nr_segs, sizeof(void *), GFP_KERNEL); if (bufs == NULL) return -ENOMEM; for (i = 0; i < to->nr_segs; ++i) { @@ -3616,7 +3617,6 @@ static ssize_t snd_pcm_writev(struct kiocb *iocb, struct iov_iter *from) struct snd_pcm_runtime *runtime; snd_pcm_sframes_t result; unsigned long i; - void __user **bufs __free(kfree) = NULL; snd_pcm_uframes_t frames; const struct iovec *iov = iter_iov(from); @@ -3634,7 +3634,9 @@ static ssize_t snd_pcm_writev(struct kiocb *iocb, struct iov_iter *from) !frame_aligned(runtime, iov->iov_len)) return -EINVAL; frames = bytes_to_samples(runtime, iov->iov_len); - bufs = kmalloc_array(from->nr_segs, sizeof(void *), GFP_KERNEL); + + void __user **bufs __free(kfree) = + kmalloc_array(from->nr_segs, sizeof(void *), GFP_KERNEL); if (bufs == NULL) return -ENOMEM; for (i = 0; i < from->nr_segs; ++i) { @@ -4106,15 +4108,15 @@ static void snd_pcm_hw_convert_to_old_params(struct snd_pcm_hw_params_old *opara static int snd_pcm_hw_refine_old_user(struct snd_pcm_substream *substream, struct snd_pcm_hw_params_old __user * _oparams) { - struct snd_pcm_hw_params *params __free(kfree) = NULL; - struct snd_pcm_hw_params_old *oparams __free(kfree) = NULL; int err; - params = kmalloc(sizeof(*params), GFP_KERNEL); + struct snd_pcm_hw_params *params __free(kfree) = + kmalloc(sizeof(*params), GFP_KERNEL); if (!params) return -ENOMEM; - oparams = memdup_user(_oparams, sizeof(*oparams)); + struct snd_pcm_hw_params_old *oparams __free(kfree) = + memdup_user(_oparams, sizeof(*oparams)); if (IS_ERR(oparams)) return PTR_ERR(oparams); snd_pcm_hw_convert_from_old_params(params, oparams); @@ -4135,15 +4137,15 @@ static int snd_pcm_hw_refine_old_user(struct snd_pcm_substream *substream, static int snd_pcm_hw_params_old_user(struct snd_pcm_substream *substream, struct snd_pcm_hw_params_old __user * _oparams) { - struct snd_pcm_hw_params *params __free(kfree) = NULL; - struct snd_pcm_hw_params_old *oparams __free(kfree) = NULL; int err; - params = kmalloc(sizeof(*params), GFP_KERNEL); + struct snd_pcm_hw_params *params __free(kfree) = + kmalloc(sizeof(*params), GFP_KERNEL); if (!params) return -ENOMEM; - oparams = memdup_user(_oparams, sizeof(*oparams)); + struct snd_pcm_hw_params_old *oparams __free(kfree) = + memdup_user(_oparams, sizeof(*oparams)); if (IS_ERR(oparams)) return PTR_ERR(oparams); From fa3b263c4de3edb8d3fa34e88ec70d348de02c52 Mon Sep 17 00:00:00 2001 From: Takashi Iwai Date: Tue, 16 Dec 2025 15:06:30 +0100 Subject: [PATCH 3432/3902] ALSA: vmaster: Relax __free() variable declarations [ Upstream commit 3b7c7bda39e1e48f926fb3d280a5f5d20a939857 ] We used to have a variable declaration with __free() initialized with NULL. This was to keep the old coding style rule, but recently it's relaxed and rather recommends to follow the new rule to declare in place of use for __free() -- which avoids potential deadlocks or UAFs with nested cleanups. Although the current code has no bug, per se, let's follow the new standard and move the declaration to the place of assignment (or directly assign the allocated result) instead of NULL initializations. Fixes: fb9e197f3f27 ("ALSA: vmaster: Use automatic cleanup of kfree()") Signed-off-by: Takashi Iwai Link: https://patch.msgid.link/20251216140634.171890-9-tiwai@suse.de Signed-off-by: Sasha Levin --- sound/core/vmaster.c | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/sound/core/vmaster.c b/sound/core/vmaster.c index c657659b236c42..76cc64245f5df4 100644 --- a/sound/core/vmaster.c +++ b/sound/core/vmaster.c @@ -56,10 +56,10 @@ struct link_follower { static int follower_update(struct link_follower *follower) { - struct snd_ctl_elem_value *uctl __free(kfree) = NULL; int err, ch; + struct snd_ctl_elem_value *uctl __free(kfree) = + kzalloc(sizeof(*uctl), GFP_KERNEL); - uctl = kzalloc(sizeof(*uctl), GFP_KERNEL); if (!uctl) return -ENOMEM; uctl->id = follower->follower.id; @@ -74,7 +74,6 @@ static int follower_update(struct link_follower *follower) /* get the follower ctl info and save the initial values */ static int follower_init(struct link_follower *follower) { - struct snd_ctl_elem_info *uinfo __free(kfree) = NULL; int err; if (follower->info.count) { @@ -84,7 +83,8 @@ static int follower_init(struct link_follower *follower) return 0; } - uinfo = kmalloc(sizeof(*uinfo), GFP_KERNEL); + struct snd_ctl_elem_info *uinfo __free(kfree) = + kmalloc(sizeof(*uinfo), GFP_KERNEL); if (!uinfo) return -ENOMEM; uinfo->id = follower->follower.id; @@ -341,9 +341,9 @@ static int master_get(struct snd_kcontrol *kcontrol, static int sync_followers(struct link_master *master, int old_val, int new_val) { struct link_follower *follower; - struct snd_ctl_elem_value *uval __free(kfree) = NULL; + struct snd_ctl_elem_value *uval __free(kfree) = + kmalloc(sizeof(*uval), GFP_KERNEL); - uval = kmalloc(sizeof(*uval), GFP_KERNEL); if (!uval) return -ENOMEM; list_for_each_entry(follower, &master->followers, list) { From 956cc8a74f410264d6c8783092929d0fcfcedc1d Mon Sep 17 00:00:00 2001 From: Simon Trimmer Date: Tue, 16 Dec 2025 14:22:04 +0000 Subject: [PATCH 3433/3902] ASoC: SDCA: Allow sample width wild cards in set_usage() [ Upstream commit 87783532d34050e2bff6749a4fe9860e624a0540 ] The SDCA spec allows the sample rate and width to be wild cards, but the current implementation of set_usage() only checked for a wild card of the sample rate. Fixes: 4ed357f72a0e ("ASoC: SDCA: Add hw_params() helper function") Signed-off-by: Simon Trimmer Reviewed-by: Charles Keepax Link: https://patch.msgid.link/20251216142204.183958-1-simont@opensource.cirrus.com Signed-off-by: Mark Brown Signed-off-by: Sasha Levin --- sound/soc/sdca/sdca_asoc.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sound/soc/sdca/sdca_asoc.c b/sound/soc/sdca/sdca_asoc.c index 892b7c028faea8..7e986870d48c5f 100644 --- a/sound/soc/sdca/sdca_asoc.c +++ b/sound/soc/sdca/sdca_asoc.c @@ -1519,7 +1519,7 @@ static int set_usage(struct device *dev, struct regmap *regmap, unsigned int rate = sdca_range(range, SDCA_USAGE_SAMPLE_RATE, i); unsigned int width = sdca_range(range, SDCA_USAGE_SAMPLE_WIDTH, i); - if ((!rate || rate == target_rate) && width == target_width) { + if ((!rate || rate == target_rate) && (!width || width == target_width)) { unsigned int usage = sdca_range(range, SDCA_USAGE_NUMBER, i); unsigned int reg = SDW_SDCA_CTL(function->desc->adr, entity->id, sel, 0); From da3be3289ecfadc831db49aac9e2b07a25b146b5 Mon Sep 17 00:00:00 2001 From: Ketil Johnsen Date: Fri, 19 Dec 2025 10:35:44 +0100 Subject: [PATCH 3434/3902] drm/panthor: Evict groups before VM termination [ Upstream commit 565ed40b5fc1242f7538a016fce5a85f802d4fb5 ] Ensure all related groups are evicted and suspended before VM destruction takes place. This fixes an issue where panthor_vm_destroy() destroys and unmaps the heap context while there are still on slot groups using this. The FW will do a write out to the heap context when a CSG (group) is suspended, so a premature unmap of the heap context will cause a GPU page fault. This page fault is quite harmless, and do not affect the continued operation of the GPU. Fixes: 647810ec2476 ("drm/panthor: Add the MMU/VM logical block") Reviewed-by: Boris Brezillon Signed-off-by: Ketil Johnsen Reviewed-by: Liviu Dudau Reviewed-by: Steven Price Link: https://patch.msgid.link/20251219093546.1227697-1-ketil.johnsen@arm.com Co-developed-by: Boris Brezillon Signed-off-by: Boris Brezillon Signed-off-by: Sasha Levin --- drivers/gpu/drm/panthor/panthor_mmu.c | 4 ++++ drivers/gpu/drm/panthor/panthor_sched.c | 14 ++++++++++++++ drivers/gpu/drm/panthor/panthor_sched.h | 1 + 3 files changed, 19 insertions(+) diff --git a/drivers/gpu/drm/panthor/panthor_mmu.c b/drivers/gpu/drm/panthor/panthor_mmu.c index 15961629872e1d..0fd8ffec92dd04 100644 --- a/drivers/gpu/drm/panthor/panthor_mmu.c +++ b/drivers/gpu/drm/panthor/panthor_mmu.c @@ -1561,6 +1561,10 @@ static void panthor_vm_destroy(struct panthor_vm *vm) vm->destroyed = true; + /* Tell scheduler to stop all GPU work related to this VM */ + if (refcount_read(&vm->as.active_cnt) > 0) + panthor_sched_prepare_for_vm_destruction(vm->ptdev); + mutex_lock(&vm->heaps.lock); panthor_heap_pool_destroy(vm->heaps.pool); vm->heaps.pool = NULL; diff --git a/drivers/gpu/drm/panthor/panthor_sched.c b/drivers/gpu/drm/panthor/panthor_sched.c index 8f11fe73bee234..c7dd98936bd6bf 100644 --- a/drivers/gpu/drm/panthor/panthor_sched.c +++ b/drivers/gpu/drm/panthor/panthor_sched.c @@ -2707,6 +2707,20 @@ void panthor_sched_report_mmu_fault(struct panthor_device *ptdev) panthor_sched_immediate_tick(ptdev); } +void panthor_sched_prepare_for_vm_destruction(struct panthor_device *ptdev) +{ + /* FW can write out internal state, like the heap context, during CSG + * suspend. It is therefore important that the scheduler has fully + * evicted any pending and related groups before VM destruction can + * safely continue. Failure to do so can lead to GPU page faults. + * A controlled termination of a Panthor instance involves destroying + * the group(s) before the VM. This means any relevant group eviction + * has already been initiated by this point, and we just need to + * ensure that any pending tick_work() has been completed. + */ + flush_work(&ptdev->scheduler->tick_work.work); +} + void panthor_sched_resume(struct panthor_device *ptdev) { /* Force a tick to re-evaluate after a resume. */ diff --git a/drivers/gpu/drm/panthor/panthor_sched.h b/drivers/gpu/drm/panthor/panthor_sched.h index 742b0b4ff3a3c5..6a560ab0a5b317 100644 --- a/drivers/gpu/drm/panthor/panthor_sched.h +++ b/drivers/gpu/drm/panthor/panthor_sched.h @@ -49,6 +49,7 @@ void panthor_sched_suspend(struct panthor_device *ptdev); void panthor_sched_resume(struct panthor_device *ptdev); void panthor_sched_report_mmu_fault(struct panthor_device *ptdev); +void panthor_sched_prepare_for_vm_destruction(struct panthor_device *ptdev); void panthor_sched_report_fw_events(struct panthor_device *ptdev, u32 events); void panthor_fdinfo_gather_group_samples(struct panthor_file *pfile); From cb59ae56199526612643f119e3c880a78e202d4e Mon Sep 17 00:00:00 2001 From: Konstantin Andreev Date: Tue, 30 Sep 2025 15:16:02 +0300 Subject: [PATCH 3435/3902] smack: /smack/doi must be > 0 [ Upstream commit 19c013e1551bf51e1493da1270841d60e4fd3f15 ] /smack/doi allows writing and keeping negative doi values. Correct values are 0 < doi <= (max 32-bit positive integer) (2008-02-04, Casey Schaufler) Fixes: e114e473771c ("Smack: Simplified Mandatory Access Control Kernel") Signed-off-by: Konstantin Andreev Signed-off-by: Casey Schaufler Signed-off-by: Sasha Levin --- security/smack/smackfs.c | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/security/smack/smackfs.c b/security/smack/smackfs.c index b1e5e62f5cbd1f..316c2ea401e8ab 100644 --- a/security/smack/smackfs.c +++ b/security/smack/smackfs.c @@ -141,7 +141,7 @@ struct smack_parsed_rule { int smk_access2; }; -static int smk_cipso_doi_value = SMACK_CIPSO_DOI_DEFAULT; +static u32 smk_cipso_doi_value = SMACK_CIPSO_DOI_DEFAULT; /* * Values for parsing cipso rules @@ -1562,7 +1562,7 @@ static ssize_t smk_read_doi(struct file *filp, char __user *buf, if (*ppos != 0) return 0; - sprintf(temp, "%d", smk_cipso_doi_value); + sprintf(temp, "%lu", (unsigned long)smk_cipso_doi_value); rc = simple_read_from_buffer(buf, count, ppos, temp, strlen(temp)); return rc; @@ -1581,7 +1581,7 @@ static ssize_t smk_write_doi(struct file *file, const char __user *buf, size_t count, loff_t *ppos) { char temp[80]; - int i; + unsigned long u; if (!smack_privileged(CAP_MAC_ADMIN)) return -EPERM; @@ -1594,10 +1594,12 @@ static ssize_t smk_write_doi(struct file *file, const char __user *buf, temp[count] = '\0'; - if (sscanf(temp, "%d", &i) != 1) + if (kstrtoul(temp, 10, &u)) return -EINVAL; - smk_cipso_doi_value = i; + if (u == CIPSO_V4_DOI_UNKNOWN || u > U32_MAX) + return -EINVAL; + smk_cipso_doi_value = u; smk_cipso_doi(); From 5a247a84de0ba44edbbd6be851c8a6b2aa60ff85 Mon Sep 17 00:00:00 2001 From: Konstantin Andreev Date: Tue, 30 Sep 2025 15:31:53 +0300 Subject: [PATCH 3436/3902] smack: /smack/doi: accept previously used values MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [ Upstream commit 33d589ed60ae433b483761987b85e0d24e54584e ] Writing to /smack/doi a value that has ever been written there in the past disables networking for non-ambient labels. E.g. # cat /smack/doi 3 # netlabelctl -p cipso list Configured CIPSO mappings (1) DOI value : 3 mapping type : PASS_THROUGH # netlabelctl -p map list Configured NetLabel domain mappings (3) domain: "_" (IPv4) protocol: UNLABELED domain: DEFAULT (IPv4) protocol: CIPSO, DOI = 3 domain: DEFAULT (IPv6) protocol: UNLABELED # cat /smack/ambient _ # cat /proc/$$/attr/smack/current _ # ping -c1 10.1.95.12 64 bytes from 10.1.95.12: icmp_seq=1 ttl=64 time=0.964 ms # echo foo >/proc/$$/attr/smack/current # ping -c1 10.1.95.12 64 bytes from 10.1.95.12: icmp_seq=1 ttl=64 time=0.956 ms unknown option 86 # echo 4 >/smack/doi # echo 3 >/smack/doi !> [ 214.050395] smk_cipso_doi:691 cipso add rc = -17 # echo 3 >/smack/doi !> [ 249.402261] smk_cipso_doi:678 remove rc = -2 !> [ 249.402261] smk_cipso_doi:691 cipso add rc = -17 # ping -c1 10.1.95.12 !!> ping: 10.1.95.12: Address family for hostname not supported # echo _ >/proc/$$/attr/smack/current # ping -c1 10.1.95.12 64 bytes from 10.1.95.12: icmp_seq=1 ttl=64 time=0.617 ms This happens because Smack keeps decommissioned DOIs, fails to re-add them, and consequently refuses to add the “default” domain map: # netlabelctl -p cipso list Configured CIPSO mappings (2) DOI value : 3 mapping type : PASS_THROUGH DOI value : 4 mapping type : PASS_THROUGH # netlabelctl -p map list Configured NetLabel domain mappings (2) domain: "_" (IPv4) protocol: UNLABELED !> (no ipv4 map for default domain here) domain: DEFAULT (IPv6) protocol: UNLABELED Fix by clearing decommissioned DOI definitions and serializing concurrent DOI updates with a new lock. Also: - allow /smack/doi to live unconfigured, since adding a map (netlbl_cfg_cipsov4_map_add) may fail. CIPSO_V4_DOI_UNKNOWN(0) indicates the unconfigured DOI - add new DOI before removing the old default map, so the old map remains if the add fails (2008-02-04, Casey Schaufler) Fixes: e114e473771c ("Smack: Simplified Mandatory Access Control Kernel") Signed-off-by: Konstantin Andreev Signed-off-by: Casey Schaufler Signed-off-by: Sasha Levin --- security/smack/smackfs.c | 71 +++++++++++++++++++++++++--------------- 1 file changed, 45 insertions(+), 26 deletions(-) diff --git a/security/smack/smackfs.c b/security/smack/smackfs.c index 316c2ea401e8ab..d27d9140dda2f3 100644 --- a/security/smack/smackfs.c +++ b/security/smack/smackfs.c @@ -70,6 +70,7 @@ enum smk_inos { static DEFINE_MUTEX(smack_cipso_lock); static DEFINE_MUTEX(smack_ambient_lock); static DEFINE_MUTEX(smk_net4addr_lock); +static DEFINE_MUTEX(smk_cipso_doi_lock); #if IS_ENABLED(CONFIG_IPV6) static DEFINE_MUTEX(smk_net6addr_lock); #endif /* CONFIG_IPV6 */ @@ -141,7 +142,7 @@ struct smack_parsed_rule { int smk_access2; }; -static u32 smk_cipso_doi_value = SMACK_CIPSO_DOI_DEFAULT; +static u32 smk_cipso_doi_value = CIPSO_V4_DOI_UNKNOWN; /* * Values for parsing cipso rules @@ -663,43 +664,60 @@ static const struct file_operations smk_load_ops = { }; /** - * smk_cipso_doi - initialize the CIPSO domain + * smk_cipso_doi - set netlabel maps + * @ndoi: new value for our CIPSO DOI + * @gfp_flags: kmalloc allocation context */ -static void smk_cipso_doi(void) +static int +smk_cipso_doi(u32 ndoi, gfp_t gfp_flags) { - int rc; + int rc = 0; struct cipso_v4_doi *doip; struct netlbl_audit nai; - smk_netlabel_audit_set(&nai); + mutex_lock(&smk_cipso_doi_lock); - rc = netlbl_cfg_map_del(NULL, PF_INET, NULL, NULL, &nai); - if (rc != 0) - printk(KERN_WARNING "%s:%d remove rc = %d\n", - __func__, __LINE__, rc); + if (smk_cipso_doi_value == ndoi) + goto clr_doi_lock; + + smk_netlabel_audit_set(&nai); - doip = kmalloc(sizeof(struct cipso_v4_doi), GFP_KERNEL | __GFP_NOFAIL); + doip = kmalloc(sizeof(struct cipso_v4_doi), gfp_flags); + if (!doip) { + rc = -ENOMEM; + goto clr_doi_lock; + } doip->map.std = NULL; - doip->doi = smk_cipso_doi_value; + doip->doi = ndoi; doip->type = CIPSO_V4_MAP_PASS; doip->tags[0] = CIPSO_V4_TAG_RBITMAP; for (rc = 1; rc < CIPSO_V4_TAG_MAXCNT; rc++) doip->tags[rc] = CIPSO_V4_TAG_INVALID; rc = netlbl_cfg_cipsov4_add(doip, &nai); - if (rc != 0) { - printk(KERN_WARNING "%s:%d cipso add rc = %d\n", - __func__, __LINE__, rc); + if (rc) { kfree(doip); - return; + goto clr_doi_lock; } - rc = netlbl_cfg_cipsov4_map_add(doip->doi, NULL, NULL, NULL, &nai); - if (rc != 0) { - printk(KERN_WARNING "%s:%d map add rc = %d\n", - __func__, __LINE__, rc); - netlbl_cfg_cipsov4_del(doip->doi, &nai); - return; + + if (smk_cipso_doi_value != CIPSO_V4_DOI_UNKNOWN) { + rc = netlbl_cfg_map_del(NULL, PF_INET, NULL, NULL, &nai); + if (rc && rc != -ENOENT) + goto clr_ndoi_def; + + netlbl_cfg_cipsov4_del(smk_cipso_doi_value, &nai); } + + rc = netlbl_cfg_cipsov4_map_add(ndoi, NULL, NULL, NULL, &nai); + if (rc) { + smk_cipso_doi_value = CIPSO_V4_DOI_UNKNOWN; // no default map +clr_ndoi_def: netlbl_cfg_cipsov4_del(ndoi, &nai); + } else + smk_cipso_doi_value = ndoi; + +clr_doi_lock: + mutex_unlock(&smk_cipso_doi_lock); + return rc; } /** @@ -1599,11 +1617,8 @@ static ssize_t smk_write_doi(struct file *file, const char __user *buf, if (u == CIPSO_V4_DOI_UNKNOWN || u > U32_MAX) return -EINVAL; - smk_cipso_doi_value = u; - - smk_cipso_doi(); - return count; + return smk_cipso_doi(u, GFP_KERNEL) ? : count; } static const struct file_operations smk_doi_ops = { @@ -2984,6 +2999,7 @@ static int __init init_smk_fs(void) { int err; int rc; + struct netlbl_audit nai; if (smack_enabled == 0) return 0; @@ -3002,7 +3018,10 @@ static int __init init_smk_fs(void) } } - smk_cipso_doi(); + smk_netlabel_audit_set(&nai); + (void) netlbl_cfg_map_del(NULL, PF_INET, NULL, NULL, &nai); + (void) smk_cipso_doi(SMACK_CIPSO_DOI_DEFAULT, + GFP_KERNEL | __GFP_NOFAIL); smk_unlbl_ambient(NULL); rc = smack_populate_secattr(&smack_known_floor); From 77dc0140ea592b13003acd460c9d77df39d83d1c Mon Sep 17 00:00:00 2001 From: Cristian Ciocaltea Date: Wed, 31 Dec 2025 22:04:15 +0200 Subject: [PATCH 3437/3902] ASoC: nau8821: Fixup nau8821_enable_jack_detect() [ Upstream commit 70237853edf0a69773a7370eb74ea2a44dfe3050 ] The nau8821_enable_jack_detect() function was supposed to allow enabling or disabling jack events reporting. However, once enabled, any subsequent invocation would fail and the following splat is shown: [ 3136.996771] Hardware name: Valve Jupiter/Jupiter, BIOS F7A0131 01/30/2024 [ 3136.996773] Workqueue: events_unbound deferred_probe_work_func [ 3136.996780] Call Trace: [ 3136.996782] [ 3136.996787] dump_stack_lvl+0x6e/0xa0 [ 3136.996796] __setup_irq.cold+0x9c/0xce [ 3136.996803] ? __pfx_irq_default_primary_handler+0x10/0x10 [ 3136.996812] ? __pfx_nau8821_interrupt+0x10/0x10 [snd_soc_nau8821] [ 3136.996825] request_threaded_irq+0xd9/0x160 [ 3136.996853] devm_request_threaded_irq+0x71/0xd0 [ 3136.996859] ? __pfx_nau8821_interrupt+0x10/0x10 [snd_soc_nau8821] [ 3136.996882] nau8821_enable_jack_detect+0xa5/0xc0 [snd_soc_nau8821] [ 3136.996901] acp5x_8821_init+0x8d/0xa0 [snd_soc_acp5x_mach] [ 3136.996917] snd_soc_link_init+0x25/0x50 [snd_soc_core] [ 3136.996958] snd_soc_bind_card+0x615/0xd00 [snd_soc_core] [ 3136.997026] snd_soc_register_card+0x1b2/0x1c0 [snd_soc_core] [ 3136.997064] devm_snd_soc_register_card+0x47/0x90 [snd_soc_core] [ 3136.997108] acp5x_probe+0x72/0xb0 [snd_soc_acp5x_mach] [...] [ 3136.997508] nau8821 i2c-NVTN2020:00: Cannot request irq 58 (-16) Introduce jdet_active flag to driver data structure and use it to provide one-time initialization of the jack detection work queue and related interrupt line. Note this is also a prerequisite for additional fixes around module unloading and suspend handling. Fixes: aab1ad11d69f ("ASoC: nau8821: new driver") Signed-off-by: Cristian Ciocaltea Link: https://patch.msgid.link/20251231-nau8821-cleanup-v1-1-6b0b76cbbb64@collabora.com Signed-off-by: Mark Brown Signed-off-by: Sasha Levin --- sound/soc/codecs/nau8821.c | 5 +++++ sound/soc/codecs/nau8821.h | 1 + 2 files changed, 6 insertions(+) diff --git a/sound/soc/codecs/nau8821.c b/sound/soc/codecs/nau8821.c index 4fa9a785513e52..dfb9630bffe29c 100644 --- a/sound/soc/codecs/nau8821.c +++ b/sound/soc/codecs/nau8821.c @@ -1661,8 +1661,13 @@ int nau8821_enable_jack_detect(struct snd_soc_component *component, int ret; nau8821->jack = jack; + + if (nau8821->jdet_active) + return 0; + /* Initiate jack detection work queue */ INIT_DELAYED_WORK(&nau8821->jdet_work, nau8821_jdet_work); + nau8821->jdet_active = true; ret = devm_request_threaded_irq(nau8821->dev, nau8821->irq, NULL, nau8821_interrupt, IRQF_TRIGGER_LOW | IRQF_ONESHOT, diff --git a/sound/soc/codecs/nau8821.h b/sound/soc/codecs/nau8821.h index 88602923780d85..f9d7cd8cbd2116 100644 --- a/sound/soc/codecs/nau8821.h +++ b/sound/soc/codecs/nau8821.h @@ -562,6 +562,7 @@ struct nau8821 { struct snd_soc_dapm_context *dapm; struct snd_soc_jack *jack; struct delayed_work jdet_work; + bool jdet_active; int irq; int clk_id; int micbias_voltage; From 1de71556cbd6e1d0d26fb86b9b3bb8caa0df8495 Mon Sep 17 00:00:00 2001 From: Zilin Guan Date: Tue, 11 Nov 2025 14:57:06 +0000 Subject: [PATCH 3438/3902] media: chips-media: wave5: Fix memory leak on codec_info allocation failure [ Upstream commit a519e21e32398459ba357e67b541402f7295ee1b ] In wave5_vpu_open_enc() and wave5_vpu_open_dec(), a vpu instance is allocated via kzalloc(). If the subsequent allocation for inst->codec_info fails, the functions return -ENOMEM without freeing the previously allocated instance, causing a memory leak. Fix this by calling kfree() on the instance in this error path to ensure it is properly released. Fixes: 9707a6254a8a6 ("media: chips-media: wave5: Add the v4l2 layer") Signed-off-by: Zilin Guan Signed-off-by: Nicolas Dufresne Signed-off-by: Hans Verkuil Signed-off-by: Sasha Levin --- drivers/media/platform/chips-media/wave5/wave5-vpu-dec.c | 4 +++- drivers/media/platform/chips-media/wave5/wave5-vpu-enc.c | 4 +++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/drivers/media/platform/chips-media/wave5/wave5-vpu-dec.c b/drivers/media/platform/chips-media/wave5/wave5-vpu-dec.c index e3038c18ca3621..a4387ed58cac36 100644 --- a/drivers/media/platform/chips-media/wave5/wave5-vpu-dec.c +++ b/drivers/media/platform/chips-media/wave5/wave5-vpu-dec.c @@ -1753,8 +1753,10 @@ static int wave5_vpu_open_dec(struct file *filp) spin_lock_init(&inst->state_spinlock); inst->codec_info = kzalloc(sizeof(*inst->codec_info), GFP_KERNEL); - if (!inst->codec_info) + if (!inst->codec_info) { + kfree(inst); return -ENOMEM; + } v4l2_fh_init(&inst->v4l2_fh, vdev); v4l2_fh_add(&inst->v4l2_fh, filp); diff --git a/drivers/media/platform/chips-media/wave5/wave5-vpu-enc.c b/drivers/media/platform/chips-media/wave5/wave5-vpu-enc.c index 9bfaa9fb3ceb3e..94fb5d7c87021a 100644 --- a/drivers/media/platform/chips-media/wave5/wave5-vpu-enc.c +++ b/drivers/media/platform/chips-media/wave5/wave5-vpu-enc.c @@ -1578,8 +1578,10 @@ static int wave5_vpu_open_enc(struct file *filp) inst->ops = &wave5_vpu_enc_inst_ops; inst->codec_info = kzalloc(sizeof(*inst->codec_info), GFP_KERNEL); - if (!inst->codec_info) + if (!inst->codec_info) { + kfree(inst); return -ENOMEM; + } v4l2_fh_init(&inst->v4l2_fh, vdev); v4l2_fh_add(&inst->v4l2_fh, filp); From 083351c6886e320252af1c1bd8602ab7b10b17f3 Mon Sep 17 00:00:00 2001 From: "Mario Limonciello (AMD)" Date: Sun, 14 Dec 2025 19:12:19 -0600 Subject: [PATCH 3439/3902] drm/amd: Drop "amdgpu kernel modesetting enabled" message [ Upstream commit 8644084a74a4573278d6f454c6638ccd5965f4e2 ] The behavior for amdgpu was changed with commit e00e5c223878 ("drm/amdgpu: adjust drm_firmware_drivers_only() handling") to potentially allow loading even if nomodeset was set, so the message is no longer accurate. Just drop it to avoid confusion. Fixes: e00e5c223878 ("drm/amdgpu: adjust drm_firmware_drivers_only() handling") Signed-off-by: Mario Limonciello (AMD) Reviewed-by: Aurabindo Pillai Signed-off-by: Alex Deucher Signed-off-by: Sasha Levin --- drivers/gpu/drm/amd/amdgpu/amdgpu_drv.c | 1 - 1 file changed, 1 deletion(-) diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_drv.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_drv.c index ec9516d6ae976f..3aa33c1de29b55 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_drv.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_drv.c @@ -3155,7 +3155,6 @@ static int __init amdgpu_init(void) if (r) goto error_fence; - DRM_INFO("amdgpu kernel modesetting enabled.\n"); amdgpu_register_atpx_handler(); amdgpu_acpi_detect(); From 30c6faf9170595e75958421c7ef693139c73ae28 Mon Sep 17 00:00:00 2001 From: Srinivasan Shanmugam Date: Wed, 17 Dec 2025 15:21:57 +0530 Subject: [PATCH 3440/3902] drm/amdkfd: Fix signal_eviction_fence() bool return value MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [ Upstream commit 31dc58adda9874420ab8fa5a2f9c43377745753a ] signal_eviction_fence() is declared to return bool, but returns -EINVAL when no eviction fence is present. This makes the "no fence" or "the NULL-fence" path evaluate to true and triggers a Smatch warning. v2: Return true instead to explicitly indicate that there is no eviction fence to signal and that eviction is already complete. This matches the existing caller logic where a NULL fence means "nothing to do" and allows restore handling to proceed normally. (Christian) Fixes the below: drivers/gpu/drm/amd/amdgpu/../amdkfd/kfd_process.c:2099 signal_eviction_fence() warn: '(-22)' is not bool drivers/gpu/drm/amd/amdgpu/../amdkfd/kfd_process.c 2090 static bool signal_eviction_fence(struct kfd_process *p) ^^^^ 2091 { 2092 struct dma_fence *ef; 2093 bool ret; 2094 2095 rcu_read_lock(); 2096 ef = dma_fence_get_rcu_safe(&p->ef); 2097 rcu_read_unlock(); 2098 if (!ef) --> 2099 return -EINVAL; This should be either true or false. Probably true because presumably it has been tested? 2100 2101 ret = dma_fence_check_and_signal(ef); 2102 dma_fence_put(ef); 2103 2104 return ret; 2105 } Fixes: 37865e02e6cc ("drm/amdkfd: Fix eviction fence handling") Reported by: Dan Carpenter Cc: Philip Yang Cc: Gang BA Cc: Felix Kuehling Signed-off-by: Srinivasan Shanmugam Reviewed-by: Christian König Signed-off-by: Alex Deucher Signed-off-by: Sasha Levin --- drivers/gpu/drm/amd/amdkfd/kfd_process.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/gpu/drm/amd/amdkfd/kfd_process.c b/drivers/gpu/drm/amd/amdkfd/kfd_process.c index ddfe30c13e9d6e..8ed513a77d38f0 100644 --- a/drivers/gpu/drm/amd/amdkfd/kfd_process.c +++ b/drivers/gpu/drm/amd/amdkfd/kfd_process.c @@ -1992,7 +1992,7 @@ static int signal_eviction_fence(struct kfd_process *p) ef = dma_fence_get_rcu_safe(&p->ef); rcu_read_unlock(); if (!ef) - return -EINVAL; + return true; ret = dma_fence_signal(ef); dma_fence_put(ef); From 8db8fadf1364f582315054f01831e551b7416f85 Mon Sep 17 00:00:00 2001 From: Srinivasan Shanmugam Date: Thu, 18 Dec 2025 15:25:25 +0530 Subject: [PATCH 3441/3902] drm/amdgpu: Use explicit VCN instance 0 in SR-IOV init MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [ Upstream commit af26fa751c2eef66916acbf0d3c3e9159da56186 ] vcn_v2_0_start_sriov() declares a local variable "i" initialized to zero and uses it only as the instance index in SOC15_REG_OFFSET(UVD, i, ...). The value is never changed and all other fields are taken from adev->vcn.inst[0], so this path only ever programs VCN instance 0. This triggered a Smatch: warn: iterator 'i' not incremented Replace the dummy iterator with an explicit instance index of 0 in SOC15_REG_OFFSET() calls. Fixes: dd26858a9cd8 ("drm/amdgpu: implement initialization part on VCN2.0 for SRIOV") Reported by: Dan Carpenter Cc: darlington Opara Cc: Jinage Zhao Cc: Monk Liu Cc: Emily Deng Cc: Christian König Cc: Alex Deucher Signed-off-by: Srinivasan Shanmugam Reviewed-by: Emily Deng Signed-off-by: Alex Deucher Signed-off-by: Sasha Levin --- drivers/gpu/drm/amd/amdgpu/vcn_v2_0.c | 45 ++++++++++++++------------- 1 file changed, 23 insertions(+), 22 deletions(-) diff --git a/drivers/gpu/drm/amd/amdgpu/vcn_v2_0.c b/drivers/gpu/drm/amd/amdgpu/vcn_v2_0.c index 8897dcc9c1a0a9..e35fae9cdaf66e 100644 --- a/drivers/gpu/drm/amd/amdgpu/vcn_v2_0.c +++ b/drivers/gpu/drm/amd/amdgpu/vcn_v2_0.c @@ -1964,7 +1964,8 @@ static int vcn_v2_0_start_sriov(struct amdgpu_device *adev) struct mmsch_v2_0_cmd_end end = { {0} }; struct mmsch_v2_0_init_header *header; uint32_t *init_table = adev->virt.mm_table.cpu_addr; - uint8_t i = 0; + + /* This path only programs VCN instance 0. */ header = (struct mmsch_v2_0_init_header *)init_table; direct_wt.cmd_header.command_type = MMSCH_COMMAND__DIRECT_REG_WRITE; @@ -1983,93 +1984,93 @@ static int vcn_v2_0_start_sriov(struct amdgpu_device *adev) size = AMDGPU_GPU_PAGE_ALIGN(adev->vcn.inst[0].fw->size + 4); MMSCH_V2_0_INSERT_DIRECT_RD_MOD_WT( - SOC15_REG_OFFSET(UVD, i, mmUVD_STATUS), + SOC15_REG_OFFSET(UVD, 0, mmUVD_STATUS), 0xFFFFFFFF, 0x00000004); /* mc resume*/ if (adev->firmware.load_type == AMDGPU_FW_LOAD_PSP) { MMSCH_V2_0_INSERT_DIRECT_WT( - SOC15_REG_OFFSET(UVD, i, + SOC15_REG_OFFSET(UVD, 0, mmUVD_LMI_VCPU_CACHE_64BIT_BAR_LOW), adev->firmware.ucode[AMDGPU_UCODE_ID_VCN].tmr_mc_addr_lo); MMSCH_V2_0_INSERT_DIRECT_WT( - SOC15_REG_OFFSET(UVD, i, + SOC15_REG_OFFSET(UVD, 0, mmUVD_LMI_VCPU_CACHE_64BIT_BAR_HIGH), adev->firmware.ucode[AMDGPU_UCODE_ID_VCN].tmr_mc_addr_hi); offset = 0; } else { MMSCH_V2_0_INSERT_DIRECT_WT( - SOC15_REG_OFFSET(UVD, i, + SOC15_REG_OFFSET(UVD, 0, mmUVD_LMI_VCPU_CACHE_64BIT_BAR_LOW), lower_32_bits(adev->vcn.inst->gpu_addr)); MMSCH_V2_0_INSERT_DIRECT_WT( - SOC15_REG_OFFSET(UVD, i, + SOC15_REG_OFFSET(UVD, 0, mmUVD_LMI_VCPU_CACHE_64BIT_BAR_HIGH), upper_32_bits(adev->vcn.inst->gpu_addr)); offset = size; } MMSCH_V2_0_INSERT_DIRECT_WT( - SOC15_REG_OFFSET(UVD, i, mmUVD_VCPU_CACHE_OFFSET0), + SOC15_REG_OFFSET(UVD, 0, mmUVD_VCPU_CACHE_OFFSET0), 0); MMSCH_V2_0_INSERT_DIRECT_WT( - SOC15_REG_OFFSET(UVD, i, mmUVD_VCPU_CACHE_SIZE0), + SOC15_REG_OFFSET(UVD, 0, mmUVD_VCPU_CACHE_SIZE0), size); MMSCH_V2_0_INSERT_DIRECT_WT( - SOC15_REG_OFFSET(UVD, i, + SOC15_REG_OFFSET(UVD, 0, mmUVD_LMI_VCPU_CACHE1_64BIT_BAR_LOW), lower_32_bits(adev->vcn.inst->gpu_addr + offset)); MMSCH_V2_0_INSERT_DIRECT_WT( - SOC15_REG_OFFSET(UVD, i, + SOC15_REG_OFFSET(UVD, 0, mmUVD_LMI_VCPU_CACHE1_64BIT_BAR_HIGH), upper_32_bits(adev->vcn.inst->gpu_addr + offset)); MMSCH_V2_0_INSERT_DIRECT_WT( - SOC15_REG_OFFSET(UVD, i, mmUVD_VCPU_CACHE_OFFSET1), + SOC15_REG_OFFSET(UVD, 0, mmUVD_VCPU_CACHE_OFFSET1), 0); MMSCH_V2_0_INSERT_DIRECT_WT( - SOC15_REG_OFFSET(UVD, i, mmUVD_VCPU_CACHE_SIZE1), + SOC15_REG_OFFSET(UVD, 0, mmUVD_VCPU_CACHE_SIZE1), AMDGPU_VCN_STACK_SIZE); MMSCH_V2_0_INSERT_DIRECT_WT( - SOC15_REG_OFFSET(UVD, i, + SOC15_REG_OFFSET(UVD, 0, mmUVD_LMI_VCPU_CACHE2_64BIT_BAR_LOW), lower_32_bits(adev->vcn.inst->gpu_addr + offset + AMDGPU_VCN_STACK_SIZE)); MMSCH_V2_0_INSERT_DIRECT_WT( - SOC15_REG_OFFSET(UVD, i, + SOC15_REG_OFFSET(UVD, 0, mmUVD_LMI_VCPU_CACHE2_64BIT_BAR_HIGH), upper_32_bits(adev->vcn.inst->gpu_addr + offset + AMDGPU_VCN_STACK_SIZE)); MMSCH_V2_0_INSERT_DIRECT_WT( - SOC15_REG_OFFSET(UVD, i, mmUVD_VCPU_CACHE_OFFSET2), + SOC15_REG_OFFSET(UVD, 0, mmUVD_VCPU_CACHE_OFFSET2), 0); MMSCH_V2_0_INSERT_DIRECT_WT( - SOC15_REG_OFFSET(UVD, i, mmUVD_VCPU_CACHE_SIZE2), + SOC15_REG_OFFSET(UVD, 0, mmUVD_VCPU_CACHE_SIZE2), AMDGPU_VCN_CONTEXT_SIZE); for (r = 0; r < adev->vcn.inst[0].num_enc_rings; ++r) { ring = &adev->vcn.inst->ring_enc[r]; ring->wptr = 0; MMSCH_V2_0_INSERT_DIRECT_WT( - SOC15_REG_OFFSET(UVD, i, mmUVD_RB_BASE_LO), + SOC15_REG_OFFSET(UVD, 0, mmUVD_RB_BASE_LO), lower_32_bits(ring->gpu_addr)); MMSCH_V2_0_INSERT_DIRECT_WT( - SOC15_REG_OFFSET(UVD, i, mmUVD_RB_BASE_HI), + SOC15_REG_OFFSET(UVD, 0, mmUVD_RB_BASE_HI), upper_32_bits(ring->gpu_addr)); MMSCH_V2_0_INSERT_DIRECT_WT( - SOC15_REG_OFFSET(UVD, i, mmUVD_RB_SIZE), + SOC15_REG_OFFSET(UVD, 0, mmUVD_RB_SIZE), ring->ring_size / 4); } ring = &adev->vcn.inst->ring_dec; ring->wptr = 0; MMSCH_V2_0_INSERT_DIRECT_WT( - SOC15_REG_OFFSET(UVD, i, + SOC15_REG_OFFSET(UVD, 0, mmUVD_LMI_RBC_RB_64BIT_BAR_LOW), lower_32_bits(ring->gpu_addr)); MMSCH_V2_0_INSERT_DIRECT_WT( - SOC15_REG_OFFSET(UVD, i, + SOC15_REG_OFFSET(UVD, 0, mmUVD_LMI_RBC_RB_64BIT_BAR_HIGH), upper_32_bits(ring->gpu_addr)); /* force RBC into idle state */ @@ -2080,7 +2081,7 @@ static int vcn_v2_0_start_sriov(struct amdgpu_device *adev) tmp = REG_SET_FIELD(tmp, UVD_RBC_RB_CNTL, RB_NO_UPDATE, 1); tmp = REG_SET_FIELD(tmp, UVD_RBC_RB_CNTL, RB_RPTR_WR_EN, 1); MMSCH_V2_0_INSERT_DIRECT_WT( - SOC15_REG_OFFSET(UVD, i, mmUVD_RBC_RB_CNTL), tmp); + SOC15_REG_OFFSET(UVD, 0, mmUVD_RBC_RB_CNTL), tmp); /* add end packet */ tmp = sizeof(struct mmsch_v2_0_cmd_end); From dc567a611b1694a685c5d1c548c186c518154347 Mon Sep 17 00:00:00 2001 From: Mahadevan P Date: Thu, 1 Jan 2026 10:34:38 +0530 Subject: [PATCH 3442/3902] drm/msm/disp/dpu: add merge3d support for sc7280 [ Upstream commit 2892de3f4f985fa779c330468e2f341fdb762ccd ] On SC7280 targets, display modes with a width greater than the max_mixer_width (2400) are rejected during mode validation when merge3d is disabled. This limitation exists because, without a 3D merge block, two layer mixers cannot be combined(non-DSC interface), preventing large layers from being split across mixers. As a result, higher resolution modes cannot be supported. Enable merge3d support on SC7280 to allow combining streams from two layer mixers into a single non-DSC interface. This capability removes the width restriction and enables buffer sizes beyond the 2400-pixel limit. Fixes: 591e34a091d1 ("drm/msm/disp/dpu1: add support for display for SC7280 target") Signed-off-by: Mahadevan P Reviewed-by: Dmitry Baryshkov Patchwork: https://patchwork.freedesktop.org/patch/696713/ Link: https://lore.kernel.org/r/20260101-4k-v2-1-712ae3c1f816@oss.qualcomm.com Signed-off-by: Dmitry Baryshkov Signed-off-by: Sasha Levin --- .../gpu/drm/msm/disp/dpu1/catalog/dpu_7_2_sc7280.h | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/drivers/gpu/drm/msm/disp/dpu1/catalog/dpu_7_2_sc7280.h b/drivers/gpu/drm/msm/disp/dpu1/catalog/dpu_7_2_sc7280.h index 8f978b9c345202..2f8688224f3430 100644 --- a/drivers/gpu/drm/msm/disp/dpu1/catalog/dpu_7_2_sc7280.h +++ b/drivers/gpu/drm/msm/disp/dpu1/catalog/dpu_7_2_sc7280.h @@ -13,6 +13,7 @@ static const struct dpu_caps sc7280_dpu_caps = { .has_dim_layer = true, .has_idle_pc = true, .max_linewidth = 2400, + .has_3d_merge = true, .pixel_ram_size = DEFAULT_PIXEL_RAM_SIZE, }; @@ -134,17 +135,24 @@ static const struct dpu_pingpong_cfg sc7280_pp[] = { .name = "pingpong_2", .id = PINGPONG_2, .base = 0x6b000, .len = 0, .sblk = &sc7280_pp_sblk, - .merge_3d = 0, + .merge_3d = MERGE_3D_1, .intr_done = DPU_IRQ_IDX(MDP_SSPP_TOP0_INTR, 10), }, { .name = "pingpong_3", .id = PINGPONG_3, .base = 0x6c000, .len = 0, .sblk = &sc7280_pp_sblk, - .merge_3d = 0, + .merge_3d = MERGE_3D_1, .intr_done = DPU_IRQ_IDX(MDP_SSPP_TOP0_INTR, 11), }, }; +static const struct dpu_merge_3d_cfg sc7280_merge_3d[] = { + { + .name = "merge_3d_1", .id = MERGE_3D_1, + .base = 0x4f000, .len = 0x8, + }, +}; + /* NOTE: sc7280 only has one DSC hard slice encoder */ static const struct dpu_dsc_cfg sc7280_dsc[] = { { @@ -247,6 +255,8 @@ const struct dpu_mdss_cfg dpu_sc7280_cfg = { .mixer = sc7280_lm, .pingpong_count = ARRAY_SIZE(sc7280_pp), .pingpong = sc7280_pp, + .merge_3d_count = ARRAY_SIZE(sc7280_merge_3d), + .merge_3d = sc7280_merge_3d, .dsc_count = ARRAY_SIZE(sc7280_dsc), .dsc = sc7280_dsc, .wb_count = ARRAY_SIZE(sc7280_wb), From 822d9b24de402dd91b2bfccf3a3f658a60ba991e Mon Sep 17 00:00:00 2001 From: Teguh Sobirin Date: Tue, 30 Dec 2025 09:17:56 +0200 Subject: [PATCH 3443/3902] drm/msm/dpu: Set vsync source irrespective of mdp top support [ Upstream commit 1ad9880f059c9b0943e53714f9a59924cb035bbb ] Since DPU 5.x the vsync source TE setup is split between MDP TOP and INTF blocks. Currently all code to setup vsync_source is only executed if MDP TOP implements the setup_vsync_source() callback. However on DPU >= 8.x this callback is not implemented, making DPU driver skip all vsync setup. Move the INTF part out of this condition, letting DPU driver to setup TE vsync selection on all new DPU devices. Signed-off-by: Teguh Sobirin Fixes: 2f69e5458447 ("drm/msm/dpu: skip watchdog timer programming through TOP on >= SM8450") [DB: restored top->ops.setup_vsync_source call] Reviewed-by: Marijn Suijten Signed-off-by: Dmitry Baryshkov Patchwork: https://patchwork.freedesktop.org/patch/696584/ Link: https://lore.kernel.org/r/20251230-intf-fix-wd-v6-1-98203d150611@oss.qualcomm.com Signed-off-by: Sasha Levin --- drivers/gpu/drm/msm/disp/dpu1/dpu_encoder.c | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/drivers/gpu/drm/msm/disp/dpu1/dpu_encoder.c b/drivers/gpu/drm/msm/disp/dpu1/dpu_encoder.c index 258edaa18fc02f..7b90c59792f6b5 100644 --- a/drivers/gpu/drm/msm/disp/dpu1/dpu_encoder.c +++ b/drivers/gpu/drm/msm/disp/dpu1/dpu_encoder.c @@ -784,6 +784,8 @@ static void _dpu_encoder_update_vsync_source(struct dpu_encoder_virt *dpu_enc, return; } + vsync_cfg.vsync_source = disp_info->vsync_source; + if (hw_mdptop->ops.setup_vsync_source) { for (i = 0; i < dpu_enc->num_phys_encs; i++) vsync_cfg.ppnumber[i] = dpu_enc->hw_pp[i]->idx; @@ -791,17 +793,15 @@ static void _dpu_encoder_update_vsync_source(struct dpu_encoder_virt *dpu_enc, vsync_cfg.pp_count = dpu_enc->num_phys_encs; vsync_cfg.frame_rate = drm_mode_vrefresh(&dpu_enc->base.crtc->state->adjusted_mode); - vsync_cfg.vsync_source = disp_info->vsync_source; - hw_mdptop->ops.setup_vsync_source(hw_mdptop, &vsync_cfg); + } - for (i = 0; i < dpu_enc->num_phys_encs; i++) { - phys_enc = dpu_enc->phys_encs[i]; + for (i = 0; i < dpu_enc->num_phys_encs; i++) { + phys_enc = dpu_enc->phys_encs[i]; - if (phys_enc->has_intf_te && phys_enc->hw_intf->ops.vsync_sel) - phys_enc->hw_intf->ops.vsync_sel(phys_enc->hw_intf, - vsync_cfg.vsync_source); - } + if (phys_enc->has_intf_te && phys_enc->hw_intf->ops.vsync_sel) + phys_enc->hw_intf->ops.vsync_sel(phys_enc->hw_intf, + vsync_cfg.vsync_source); } } From acfd1dce074f8af3b334c8a446e558143047ea11 Mon Sep 17 00:00:00 2001 From: Dmitry Baryshkov Date: Tue, 30 Dec 2025 09:17:57 +0200 Subject: [PATCH 3444/3902] drm/msm/dpu: fix WD timer handling on DPU 8.x [ Upstream commit 794b0e68caba49b950b42ec32e364028c2facf57 ] Since DPU 8.x Watchdog timer settings were moved from the TOP to the INTF block. Support programming the timer in the INTF block. Fixes tag points to the commit which removed register access to those registers on DPU 8.x+ (and which also should have added proper support for WD timer on those devices). Fixes: 43e3293fc614 ("drm/msm/dpu: add support for MDP_TOP blackhole") Reviewed-by: Marijn Suijten Signed-off-by: Dmitry Baryshkov Patchwork: https://patchwork.freedesktop.org/patch/696586/ Link: https://lore.kernel.org/r/20251230-intf-fix-wd-v6-2-98203d150611@oss.qualcomm.com Signed-off-by: Sasha Levin --- drivers/gpu/drm/msm/disp/dpu1/dpu_encoder.c | 4 +- drivers/gpu/drm/msm/disp/dpu1/dpu_hw_intf.c | 49 +++++++++++++++++++-- drivers/gpu/drm/msm/disp/dpu1/dpu_hw_intf.h | 3 +- drivers/gpu/drm/msm/disp/dpu1/dpu_hw_top.c | 7 --- drivers/gpu/drm/msm/disp/dpu1/dpu_hw_util.h | 7 +++ 5 files changed, 57 insertions(+), 13 deletions(-) diff --git a/drivers/gpu/drm/msm/disp/dpu1/dpu_encoder.c b/drivers/gpu/drm/msm/disp/dpu1/dpu_encoder.c index 7b90c59792f6b5..777eab5ad844ec 100644 --- a/drivers/gpu/drm/msm/disp/dpu1/dpu_encoder.c +++ b/drivers/gpu/drm/msm/disp/dpu1/dpu_encoder.c @@ -785,13 +785,13 @@ static void _dpu_encoder_update_vsync_source(struct dpu_encoder_virt *dpu_enc, } vsync_cfg.vsync_source = disp_info->vsync_source; + vsync_cfg.frame_rate = drm_mode_vrefresh(&dpu_enc->base.crtc->state->adjusted_mode); if (hw_mdptop->ops.setup_vsync_source) { for (i = 0; i < dpu_enc->num_phys_encs; i++) vsync_cfg.ppnumber[i] = dpu_enc->hw_pp[i]->idx; vsync_cfg.pp_count = dpu_enc->num_phys_encs; - vsync_cfg.frame_rate = drm_mode_vrefresh(&dpu_enc->base.crtc->state->adjusted_mode); hw_mdptop->ops.setup_vsync_source(hw_mdptop, &vsync_cfg); } @@ -801,7 +801,7 @@ static void _dpu_encoder_update_vsync_source(struct dpu_encoder_virt *dpu_enc, if (phys_enc->has_intf_te && phys_enc->hw_intf->ops.vsync_sel) phys_enc->hw_intf->ops.vsync_sel(phys_enc->hw_intf, - vsync_cfg.vsync_source); + &vsync_cfg); } } diff --git a/drivers/gpu/drm/msm/disp/dpu1/dpu_hw_intf.c b/drivers/gpu/drm/msm/disp/dpu1/dpu_hw_intf.c index a80ac82a96255d..7e620f59098499 100644 --- a/drivers/gpu/drm/msm/disp/dpu1/dpu_hw_intf.c +++ b/drivers/gpu/drm/msm/disp/dpu1/dpu_hw_intf.c @@ -67,6 +67,10 @@ #define INTF_MISR_CTRL 0x180 #define INTF_MISR_SIGNATURE 0x184 +#define INTF_WD_TIMER_0_CTL 0x230 +#define INTF_WD_TIMER_0_CTL2 0x234 +#define INTF_WD_TIMER_0_LOAD_VALUE 0x238 + #define INTF_MUX 0x25C #define INTF_STATUS 0x26C #define INTF_AVR_CONTROL 0x270 @@ -475,7 +479,20 @@ static int dpu_hw_intf_get_vsync_info(struct dpu_hw_intf *intf, } static void dpu_hw_intf_vsync_sel(struct dpu_hw_intf *intf, - enum dpu_vsync_source vsync_source) + struct dpu_vsync_source_cfg *cfg) +{ + struct dpu_hw_blk_reg_map *c; + + if (!intf) + return; + + c = &intf->hw; + + DPU_REG_WRITE(c, INTF_TEAR_MDP_VSYNC_SEL, (cfg->vsync_source & 0xf)); +} + +static void dpu_hw_intf_vsync_sel_v8(struct dpu_hw_intf *intf, + struct dpu_vsync_source_cfg *cfg) { struct dpu_hw_blk_reg_map *c; @@ -484,7 +501,30 @@ static void dpu_hw_intf_vsync_sel(struct dpu_hw_intf *intf, c = &intf->hw; - DPU_REG_WRITE(c, INTF_TEAR_MDP_VSYNC_SEL, (vsync_source & 0xf)); + if (cfg->vsync_source >= DPU_VSYNC_SOURCE_WD_TIMER_4 && + cfg->vsync_source <= DPU_VSYNC_SOURCE_WD_TIMER_1) { + pr_warn_once("DPU 8.x supports only GPIOs and timer0 as TE sources\n"); + return; + } + + if (cfg->vsync_source == DPU_VSYNC_SOURCE_WD_TIMER_0) { + u32 reg; + + DPU_REG_WRITE(c, INTF_WD_TIMER_0_LOAD_VALUE, + CALCULATE_WD_LOAD_VALUE(cfg->frame_rate)); + + DPU_REG_WRITE(c, INTF_WD_TIMER_0_CTL, BIT(0)); /* clear timer */ + + reg = BIT(8); /* enable heartbeat timer */ + reg |= BIT(0); /* enable WD timer */ + reg |= BIT(1); /* select default 16 clock ticks */ + DPU_REG_WRITE(c, INTF_WD_TIMER_0_CTL2, reg); + + /* make sure that timers are enabled/disabled for vsync state */ + wmb(); + } + + dpu_hw_intf_vsync_sel(intf, cfg); } static void dpu_hw_intf_disable_autorefresh(struct dpu_hw_intf *intf, @@ -598,7 +638,10 @@ struct dpu_hw_intf *dpu_hw_intf_init(struct drm_device *dev, c->ops.enable_tearcheck = dpu_hw_intf_enable_te; c->ops.disable_tearcheck = dpu_hw_intf_disable_te; c->ops.connect_external_te = dpu_hw_intf_connect_external_te; - c->ops.vsync_sel = dpu_hw_intf_vsync_sel; + if (mdss_rev->core_major_ver >= 8) + c->ops.vsync_sel = dpu_hw_intf_vsync_sel_v8; + else + c->ops.vsync_sel = dpu_hw_intf_vsync_sel; c->ops.disable_autorefresh = dpu_hw_intf_disable_autorefresh; } diff --git a/drivers/gpu/drm/msm/disp/dpu1/dpu_hw_intf.h b/drivers/gpu/drm/msm/disp/dpu1/dpu_hw_intf.h index f31067a9aaf1d6..e84ab849d71a98 100644 --- a/drivers/gpu/drm/msm/disp/dpu1/dpu_hw_intf.h +++ b/drivers/gpu/drm/msm/disp/dpu1/dpu_hw_intf.h @@ -12,6 +12,7 @@ #include "dpu_hw_util.h" struct dpu_hw_intf; +struct dpu_vsync_source_cfg; /* intf timing settings */ struct dpu_hw_intf_timing_params { @@ -107,7 +108,7 @@ struct dpu_hw_intf_ops { int (*connect_external_te)(struct dpu_hw_intf *intf, bool enable_external_te); - void (*vsync_sel)(struct dpu_hw_intf *intf, enum dpu_vsync_source vsync_source); + void (*vsync_sel)(struct dpu_hw_intf *intf, struct dpu_vsync_source_cfg *cfg); /** * Disable autorefresh if enabled diff --git a/drivers/gpu/drm/msm/disp/dpu1/dpu_hw_top.c b/drivers/gpu/drm/msm/disp/dpu1/dpu_hw_top.c index 96dc10589bee6c..1ebd75d4f9be8d 100644 --- a/drivers/gpu/drm/msm/disp/dpu1/dpu_hw_top.c +++ b/drivers/gpu/drm/msm/disp/dpu1/dpu_hw_top.c @@ -22,13 +22,6 @@ #define TRAFFIC_SHAPER_WR_CLIENT(num) (0x060 + (num * 4)) #define TRAFFIC_SHAPER_FIXPOINT_FACTOR 4 -#define MDP_TICK_COUNT 16 -#define XO_CLK_RATE 19200 -#define MS_TICKS_IN_SEC 1000 - -#define CALCULATE_WD_LOAD_VALUE(fps) \ - ((uint32_t)((MS_TICKS_IN_SEC * XO_CLK_RATE)/(MDP_TICK_COUNT * fps))) - static void dpu_hw_setup_split_pipe(struct dpu_hw_mdp *mdp, struct split_pipe_cfg *cfg) { diff --git a/drivers/gpu/drm/msm/disp/dpu1/dpu_hw_util.h b/drivers/gpu/drm/msm/disp/dpu1/dpu_hw_util.h index 67b08e99335dcf..6fe65bc3bff4e8 100644 --- a/drivers/gpu/drm/msm/disp/dpu1/dpu_hw_util.h +++ b/drivers/gpu/drm/msm/disp/dpu1/dpu_hw_util.h @@ -21,6 +21,13 @@ #define TO_S15D16(_x_)((_x_) << 7) +#define MDP_TICK_COUNT 16 +#define XO_CLK_RATE 19200 +#define MS_TICKS_IN_SEC 1000 + +#define CALCULATE_WD_LOAD_VALUE(fps) \ + ((uint32_t)((MS_TICKS_IN_SEC * XO_CLK_RATE)/(MDP_TICK_COUNT * fps))) + extern const struct dpu_csc_cfg dpu_csc_YUV2RGB_601L; extern const struct dpu_csc_cfg dpu_csc10_YUV2RGB_601L; extern const struct dpu_csc_cfg dpu_csc10_rgb2yuv_601l; From 4ef745e4d0d6356394b97456bb4de3550830fc4e Mon Sep 17 00:00:00 2001 From: Mani Chandana Ballary Kuntumalla Date: Tue, 25 Nov 2025 16:26:20 +0530 Subject: [PATCH 3445/3902] drm/msm/dp: Update msm_dp_controller IDs for sa8775p [ Upstream commit 1338e8ae4084e55c0359a79e617b2ae183d01579 ] The Qualcomm SA8775P platform comes with 2 DisplayPort controllers for each mdss. Update controller id for DPTX0 and DPTX1 of mdss1. Fixes: dcb380d19e58 ("drm/msm/dp: Add DisplayPort controller for SA8775P") Signed-off-by: Mani Chandana Ballary Kuntumalla Reviewed-by: Dmitry Baryshkov Patchwork: https://patchwork.freedesktop.org/patch/690234/ Link: https://lore.kernel.org/r/20251125105622.1755651-2-quic_mkuntuma@quicinc.com Signed-off-by: Dmitry Baryshkov Signed-off-by: Sasha Levin --- drivers/gpu/drm/msm/dp/dp_display.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/gpu/drm/msm/dp/dp_display.c b/drivers/gpu/drm/msm/dp/dp_display.c index d87d47cc7ec3eb..f247aad5539758 100644 --- a/drivers/gpu/drm/msm/dp/dp_display.c +++ b/drivers/gpu/drm/msm/dp/dp_display.c @@ -133,8 +133,8 @@ struct msm_dp_desc { static const struct msm_dp_desc msm_dp_desc_sa8775p[] = { { .io_start = 0x0af54000, .id = MSM_DP_CONTROLLER_0, .wide_bus_supported = true }, { .io_start = 0x0af5c000, .id = MSM_DP_CONTROLLER_1, .wide_bus_supported = true }, - { .io_start = 0x22154000, .id = MSM_DP_CONTROLLER_2, .wide_bus_supported = true }, - { .io_start = 0x2215c000, .id = MSM_DP_CONTROLLER_3, .wide_bus_supported = true }, + { .io_start = 0x22154000, .id = MSM_DP_CONTROLLER_0, .wide_bus_supported = true }, + { .io_start = 0x2215c000, .id = MSM_DP_CONTROLLER_1, .wide_bus_supported = true }, {} }; From a6b7308f35cdf1d17a135303c17f8309f4b5d9e3 Mon Sep 17 00:00:00 2001 From: Jani Nikula Date: Wed, 7 Jan 2026 18:02:26 +0200 Subject: [PATCH 3446/3902] mei: late_bind: fix struct intel_lb_component_ops kernel-doc [ Upstream commit 936cae9254e55a39aeaa0c156a764d22f319338b ] Fix kernel-doc warnings on struct intel_lb_component_ops: Warning: include/drm/intel/intel_lb_mei_interface.h:55 Incorrect use of kernel-doc format: * push_payload - Sends a payload to the authentication firmware And a bunch more. There isn't really support for documenting function pointer struct members in kernel-doc, but at least reference the member properly. Fixes: 741eeabb7c78 ("mei: late_bind: add late binding component driver") Cc: Alexander Usyskin Reviewed-by: Nitin Gote Link: https://patch.msgid.link/20260107160226.2381388-1-jani.nikula@intel.com Signed-off-by: Jani Nikula Signed-off-by: Sasha Levin --- include/drm/intel/intel_lb_mei_interface.h | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/include/drm/intel/intel_lb_mei_interface.h b/include/drm/intel/intel_lb_mei_interface.h index d65be2cba2ab9f..0850738a30fc75 100644 --- a/include/drm/intel/intel_lb_mei_interface.h +++ b/include/drm/intel/intel_lb_mei_interface.h @@ -53,7 +53,8 @@ enum intel_lb_status { */ struct intel_lb_component_ops { /** - * push_payload - Sends a payload to the authentication firmware + * @push_payload: Sends a payload to the authentication firmware + * * @dev: Device struct corresponding to the mei device * @type: Payload type (see &enum intel_lb_type) * @flags: Payload flags bitmap (e.g. %INTEL_LB_FLAGS_IS_PERSISTENT) From b6a83ad13d253f9319920313a0fbad637fab9aef Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Draszik?= Date: Fri, 9 Jan 2026 08:38:39 +0000 Subject: [PATCH 3447/3902] regulator: core: move supply check earlier in set_machine_constraints() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [ Upstream commit 86a8eeb0e913f4b6a55dabba5122098d4e805e55 ] Since commit 98e48cd9283d ("regulator: core: resolve supply for boot-on/always-on regulators"), set_machine_constraints() can return -EPROBE_DEFER very late, after it has done a lot of work and configuration of the regulator. This means that configuration will happen multiple times for no benefit in that case. Furthermore, this can lead to timing-dependent voltage glitches as mentioned e.g. in commit 8a866d527ac0 ("regulator: core: Resolve supply name earlier to prevent double-init"). We can know that it's going to fail very early, in particular before going through the complete regulator configuration by moving some code around a little. Do so to avoid re-configuring the regulator multiple times, also avoiding the voltage glitches if we can. Fixes: 98e48cd9283d ("regulator: core: resolve supply for boot-on/always-on regulators") Signed-off-by: André Draszik Link: https://patch.msgid.link/20260109-regulators-defer-v2-3-1a25dc968e60@linaro.org Signed-off-by: Mark Brown Signed-off-by: Sasha Levin --- drivers/regulator/core.c | 55 ++++++++++++++++++++++------------------ 1 file changed, 30 insertions(+), 25 deletions(-) diff --git a/drivers/regulator/core.c b/drivers/regulator/core.c index b38b087eccfd77..17c60d9547dc50 100644 --- a/drivers/regulator/core.c +++ b/drivers/regulator/core.c @@ -1431,6 +1431,33 @@ static int set_machine_constraints(struct regulator_dev *rdev) int ret = 0; const struct regulator_ops *ops = rdev->desc->ops; + /* + * If there is no mechanism for controlling the regulator then + * flag it as always_on so we don't end up duplicating checks + * for this so much. Note that we could control the state of + * a supply to control the output on a regulator that has no + * direct control. + */ + if (!rdev->ena_pin && !ops->enable) { + if (rdev->supply_name && !rdev->supply) + return -EPROBE_DEFER; + + if (rdev->supply) + rdev->constraints->always_on = + rdev->supply->rdev->constraints->always_on; + else + rdev->constraints->always_on = true; + } + + /* + * If we want to enable this regulator, make sure that we know the + * supplying regulator. + */ + if (rdev->constraints->always_on || rdev->constraints->boot_on) { + if (rdev->supply_name && !rdev->supply) + return -EPROBE_DEFER; + } + ret = machine_constraints_voltage(rdev, rdev->constraints); if (ret != 0) return ret; @@ -1596,37 +1623,15 @@ static int set_machine_constraints(struct regulator_dev *rdev) } } - /* - * If there is no mechanism for controlling the regulator then - * flag it as always_on so we don't end up duplicating checks - * for this so much. Note that we could control the state of - * a supply to control the output on a regulator that has no - * direct control. - */ - if (!rdev->ena_pin && !ops->enable) { - if (rdev->supply_name && !rdev->supply) - return -EPROBE_DEFER; - - if (rdev->supply) - rdev->constraints->always_on = - rdev->supply->rdev->constraints->always_on; - else - rdev->constraints->always_on = true; - } - /* If the constraints say the regulator should be on at this point * and we have control then make sure it is enabled. */ if (rdev->constraints->always_on || rdev->constraints->boot_on) { bool supply_enabled = false; - /* If we want to enable this regulator, make sure that we know - * the supplying regulator. - */ - if (rdev->supply_name && !rdev->supply) - return -EPROBE_DEFER; - - /* If supplying regulator has already been enabled, + /* We have ensured a potential supply has been resolved above. + * + * If supplying regulator has already been enabled, * it's not intended to have use_count increment * when rdev is only boot-on. */ From 35301ca2a83d17aac2f3e8e35c696f0da2a13111 Mon Sep 17 00:00:00 2001 From: Haotian Zhang Date: Mon, 17 Nov 2025 16:28:08 +0800 Subject: [PATCH 3448/3902] HID: playstation: Add missing check for input_ff_create_memless [ Upstream commit e6807641ac94e832988655a1c0e60ccc806b76dc ] The ps_gamepad_create() function calls input_ff_create_memless() without verifying its return value, which can lead to incorrect behavior or potential crashes when FF effects are triggered. Add a check for the return value of input_ff_create_memless(). Fixes: 51151098d7ab ("HID: playstation: add DualSense classic rumble support.") Signed-off-by: Haotian Zhang Signed-off-by: Jiri Kosina Signed-off-by: Sasha Levin --- drivers/hid/hid-playstation.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/drivers/hid/hid-playstation.c b/drivers/hid/hid-playstation.c index e4dfcf26b04e74..2ec6d4445e84ba 100644 --- a/drivers/hid/hid-playstation.c +++ b/drivers/hid/hid-playstation.c @@ -774,7 +774,9 @@ ps_gamepad_create(struct hid_device *hdev, #if IS_ENABLED(CONFIG_PLAYSTATION_FF) if (play_effect) { input_set_capability(gamepad, EV_FF, FF_RUMBLE); - input_ff_create_memless(gamepad, NULL, play_effect); + ret = input_ff_create_memless(gamepad, NULL, play_effect); + if (ret) + return ERR_PTR(ret); } #endif From 2f803e86ba4f4b0177ade51a473785f4c94f734b Mon Sep 17 00:00:00 2001 From: Dmitry Baryshkov Date: Fri, 14 Nov 2025 05:43:28 +0200 Subject: [PATCH 3449/3902] drm/msm/disp: set num_planes to 1 for interleaved YUV formats [ Upstream commit 6421e1c5075b7e1536a8fcbe6b4086db07103048 ] Interleaved YUV formats use only one plane for all pixel data. Specify num_planes = 1 for those formats. This was left unnoticed since _dpu_format_populate_plane_sizes_linear() overrides layout->num_planes. Fixes: 25fdd5933e4c ("drm/msm: Add SDM845 DPU support") Reviewed-by: Jessica Zhang Patchwork: https://patchwork.freedesktop.org/patch/688162/ Link: https://lore.kernel.org/r/20251114-dpu-formats-v3-1-cae312379d49@oss.qualcomm.com Tested-by: Luca Weiss # qcm6490-fairphone-fp5 Signed-off-by: Dmitry Baryshkov Signed-off-by: Sasha Levin --- drivers/gpu/drm/msm/disp/mdp_format.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/drivers/gpu/drm/msm/disp/mdp_format.c b/drivers/gpu/drm/msm/disp/mdp_format.c index 426782d50cb49d..eebedb1a2636e7 100644 --- a/drivers/gpu/drm/msm/disp/mdp_format.c +++ b/drivers/gpu/drm/msm/disp/mdp_format.c @@ -479,25 +479,25 @@ static const struct msm_format mdp_formats[] = { 0, BPC8, BPC8, BPC8, C2_R_Cr, C0_G_Y, C1_B_Cb, C0_G_Y, false, CHROMA_H2V1, 4, 2, MSM_FORMAT_FLAG_YUV, - MDP_FETCH_LINEAR, 2), + MDP_FETCH_LINEAR, 1), INTERLEAVED_YUV_FMT(UYVY, 0, BPC8, BPC8, BPC8, C1_B_Cb, C0_G_Y, C2_R_Cr, C0_G_Y, false, CHROMA_H2V1, 4, 2, MSM_FORMAT_FLAG_YUV, - MDP_FETCH_LINEAR, 2), + MDP_FETCH_LINEAR, 1), INTERLEAVED_YUV_FMT(YUYV, 0, BPC8, BPC8, BPC8, C0_G_Y, C1_B_Cb, C0_G_Y, C2_R_Cr, false, CHROMA_H2V1, 4, 2, MSM_FORMAT_FLAG_YUV, - MDP_FETCH_LINEAR, 2), + MDP_FETCH_LINEAR, 1), INTERLEAVED_YUV_FMT(YVYU, 0, BPC8, BPC8, BPC8, C0_G_Y, C2_R_Cr, C0_G_Y, C1_B_Cb, false, CHROMA_H2V1, 4, 2, MSM_FORMAT_FLAG_YUV, - MDP_FETCH_LINEAR, 2), + MDP_FETCH_LINEAR, 1), /* 3 plane YUV */ PLANAR_YUV_FMT(YUV420, From 2b46bb9f88cd79039f66a696a3bac201ceebb87b Mon Sep 17 00:00:00 2001 From: Dmitry Baryshkov Date: Sun, 28 Dec 2025 06:02:28 +0200 Subject: [PATCH 3450/3902] drm/msm/dpu: fix CMD panels on DPU 1.x - 3.x [ Upstream commit 59ca3d11f5311d9167015fe4f431701614ae0048 ] DPU units before 4.x don't have a separate CTL_START IRQ to mark the begin of the data transfer. In such a case, wait for the frame transfer to complete rather than trying to wait for the CTL_START interrupt (and obviously hitting the timeout). Fixes: 050770cbbd26 ("drm/msm/dpu: Fix timeout issues on command mode panels") Reported-by: Alexey Minnekhanov Closes: https://lore.kernel.org/r/8e1d33ff-d902-4ae9-9162-e00d17a5e6d1@postmarketos.org Patchwork: https://patchwork.freedesktop.org/patch/696490/ Link: https://lore.kernel.org/r/20251228-mdp5-drop-dpu3-v4-2-7497c3d39179@oss.qualcomm.com Tested-by: Alexey Minnekhanov Signed-off-by: Dmitry Baryshkov Signed-off-by: Sasha Levin --- drivers/gpu/drm/msm/disp/dpu1/dpu_encoder_phys_cmd.c | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/drivers/gpu/drm/msm/disp/dpu1/dpu_encoder_phys_cmd.c b/drivers/gpu/drm/msm/disp/dpu1/dpu_encoder_phys_cmd.c index 0ec6d67c7c70b1..93db1484f60698 100644 --- a/drivers/gpu/drm/msm/disp/dpu1/dpu_encoder_phys_cmd.c +++ b/drivers/gpu/drm/msm/disp/dpu1/dpu_encoder_phys_cmd.c @@ -681,10 +681,11 @@ static int dpu_encoder_phys_cmd_wait_for_commit_done( if (!dpu_encoder_phys_cmd_is_master(phys_enc)) return 0; - if (phys_enc->hw_ctl->ops.is_started(phys_enc->hw_ctl)) - return dpu_encoder_phys_cmd_wait_for_tx_complete(phys_enc); + if (phys_enc->irq[INTR_IDX_CTL_START] && + !phys_enc->hw_ctl->ops.is_started(phys_enc->hw_ctl)) + return _dpu_encoder_phys_cmd_wait_for_ctl_start(phys_enc); - return _dpu_encoder_phys_cmd_wait_for_ctl_start(phys_enc); + return dpu_encoder_phys_cmd_wait_for_tx_complete(phys_enc); } static void dpu_encoder_phys_cmd_handle_post_kickoff( From c0727bcbf09b472afb8a7c924419428b477cce81 Mon Sep 17 00:00:00 2001 From: Brian Masney Date: Thu, 8 Jan 2026 16:16:42 -0500 Subject: [PATCH 3451/3902] drm/msm/dsi_phy_14nm: convert from divider_round_rate() to divider_determine_rate() [ Upstream commit 1d232f793d4dbffd329ad48b52954d4c8ca24db5 ] The divider_round_rate() function is now deprecated, so let's migrate to divider_determine_rate() instead so that this deprecated API can be removed. Note that when the main function itself was migrated to use determine_rate, this was mistakenly converted to: req->rate = divider_round_rate(...) This is invalid in the case when an error occurs since it can set the rate to a negative value. Fixes: cc41f29a6b04 ("drm/msm/dsi_phy_14nm: convert from round_rate() to determine_rate()") Signed-off-by: Brian Masney Reviewed-by: Konrad Dybcio Reviewed-by: Abel Vesa Patchwork: https://patchwork.freedesktop.org/patch/697613/ Link: https://lore.kernel.org/r/20260108-clk-divider-round-rate-v1-24-535a3ed73bf3@redhat.com Signed-off-by: Dmitry Baryshkov Signed-off-by: Sasha Levin --- drivers/gpu/drm/msm/dsi/phy/dsi_phy_14nm.c | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/drivers/gpu/drm/msm/dsi/phy/dsi_phy_14nm.c b/drivers/gpu/drm/msm/dsi/phy/dsi_phy_14nm.c index fdefcbd9c2848a..a156c7e7cea832 100644 --- a/drivers/gpu/drm/msm/dsi/phy/dsi_phy_14nm.c +++ b/drivers/gpu/drm/msm/dsi/phy/dsi_phy_14nm.c @@ -628,12 +628,7 @@ static int dsi_pll_14nm_postdiv_determine_rate(struct clk_hw *hw, DBG("DSI%d PLL parent rate=%lu", pll_14nm->phy->id, req->rate); - req->rate = divider_round_rate(hw, req->rate, &req->best_parent_rate, - NULL, - postdiv->width, - postdiv->flags); - - return 0; + return divider_determine_rate(hw, req, NULL, postdiv->width, postdiv->flags); } static int dsi_pll_14nm_postdiv_set_rate(struct clk_hw *hw, unsigned long rate, From 5fab6e1405e18ff48c56bfbd145c6daf12a7f5cd Mon Sep 17 00:00:00 2001 From: Lizhi Hou Date: Tue, 13 Jan 2026 09:36:24 -0800 Subject: [PATCH 3452/3902] accel/amdxdna: Fix notifier_wq flushing warning [ Upstream commit b36178488d479e9a53bbef2b01280378b5586e60 ] Create notifier_wq with WQ_MEM_RECLAIM flag to fix the possible warning. workqueue: WQ_MEM_RECLAIM amdxdna_js:drm_sched_free_job_work [gpu_sched] is flushing !WQ_MEM_RECLAIM notifier_wq:0x0 Fixes: e486147c912f ("accel/amdxdna: Add BO import and export") Reviewed-by: Mario Limonciello (AMD) Reviewed-by: Maciej Falkowski Signed-off-by: Lizhi Hou Link: https://patch.msgid.link/20260113173624.256053-1-lizhi.hou@amd.com Signed-off-by: Sasha Levin --- drivers/accel/amdxdna/amdxdna_pci_drv.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/accel/amdxdna/amdxdna_pci_drv.c b/drivers/accel/amdxdna/amdxdna_pci_drv.c index 569cd703729d3f..ccf2d1de558c0f 100644 --- a/drivers/accel/amdxdna/amdxdna_pci_drv.c +++ b/drivers/accel/amdxdna/amdxdna_pci_drv.c @@ -292,7 +292,7 @@ static int amdxdna_probe(struct pci_dev *pdev, const struct pci_device_id *id) fs_reclaim_release(GFP_KERNEL); } - xdna->notifier_wq = alloc_ordered_workqueue("notifier_wq", 0); + xdna->notifier_wq = alloc_ordered_workqueue("notifier_wq", WQ_MEM_RECLAIM); if (!xdna->notifier_wq) return -ENOMEM; From 21cf7527de1d174c22fdca8eedf5e8c0d7206a04 Mon Sep 17 00:00:00 2001 From: David Heidelberg Date: Fri, 9 Jan 2026 18:57:07 +0100 Subject: [PATCH 3453/3902] media: ccs: Accommodate C-PHY into the calculation [ Upstream commit 3085977e734dab74adebb1dda195befce25addff ] We need to set correct mode for PLL to calculate correct frequency. Signalling mode is known at this point, so use it for that. Fixes: 47b6eaf36eba ("media: ccs-pll: Differentiate between CSI-2 D-PHY and C-PHY") Reviewed-by: Mehdi Djait Signed-off-by: David Heidelberg [Sakari Ailus: Drop extra newline.] Signed-off-by: Sakari Ailus Signed-off-by: Hans Verkuil Signed-off-by: Sasha Levin --- drivers/media/i2c/ccs/ccs-core.c | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/drivers/media/i2c/ccs/ccs-core.c b/drivers/media/i2c/ccs/ccs-core.c index 1c889c878abd3a..08e78f0bf25284 100644 --- a/drivers/media/i2c/ccs/ccs-core.c +++ b/drivers/media/i2c/ccs/ccs-core.c @@ -3425,7 +3425,21 @@ static int ccs_probe(struct i2c_client *client) sensor->scale_m = CCS_LIM(sensor, SCALER_N_MIN); /* prepare PLL configuration input values */ - sensor->pll.bus_type = CCS_PLL_BUS_TYPE_CSI2_DPHY; + switch (sensor->hwcfg.csi_signalling_mode) { + case CCS_CSI_SIGNALING_MODE_CSI_2_CPHY: + sensor->pll.bus_type = CCS_PLL_BUS_TYPE_CSI2_CPHY; + break; + case CCS_CSI_SIGNALING_MODE_CSI_2_DPHY: + case SMIAPP_CSI_SIGNALLING_MODE_CCP2_DATA_CLOCK: + case SMIAPP_CSI_SIGNALLING_MODE_CCP2_DATA_STROBE: + sensor->pll.bus_type = CCS_PLL_BUS_TYPE_CSI2_DPHY; + break; + default: + dev_err(&client->dev, "unsupported signalling mode %u\n", + sensor->hwcfg.csi_signalling_mode); + rval = -EINVAL; + goto out_cleanup; + } sensor->pll.csi2.lanes = sensor->hwcfg.lanes; if (CCS_LIM(sensor, CLOCK_CALCULATION) & CCS_CLOCK_CALCULATION_LANE_SPEED) { From f83b92c5768647034b074abe798f07e0524d1dc0 Mon Sep 17 00:00:00 2001 From: Dmitry Baryshkov Date: Fri, 21 Nov 2025 18:13:03 +0200 Subject: [PATCH 3454/3902] drm/msm/a2xx: fix pixel shader start on A225 [ Upstream commit 6a7b0a670ba4d283285d76d45233cbecc5af5e40 ] A225 has a different PixelShader start address, write correct address while initializing GPU. Fixes: 21af872cd8c6 ("drm/msm/adreno: add a2xx") Signed-off-by: Dmitry Baryshkov Reviewed-by: Konrad Dybcio Patchwork: https://patchwork.freedesktop.org/patch/689906/ Message-ID: <20251121-a225-v1-1-a1bab651d186@oss.qualcomm.com> Signed-off-by: Rob Clark Signed-off-by: Sasha Levin --- drivers/gpu/drm/msm/adreno/a2xx_gpu.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/drivers/gpu/drm/msm/adreno/a2xx_gpu.c b/drivers/gpu/drm/msm/adreno/a2xx_gpu.c index 963c0f669ee50d..e67ed58aa3d8fd 100644 --- a/drivers/gpu/drm/msm/adreno/a2xx_gpu.c +++ b/drivers/gpu/drm/msm/adreno/a2xx_gpu.c @@ -77,7 +77,10 @@ static bool a2xx_me_init(struct msm_gpu *gpu) /* Vertex and Pixel Shader Start Addresses in instructions * (3 DWORDS per instruction) */ - OUT_RING(ring, 0x80000180); + if (adreno_is_a225(adreno_gpu)) + OUT_RING(ring, 0x80000300); + else + OUT_RING(ring, 0x80000180); /* Maximum Contexts */ OUT_RING(ring, 0x00000001); /* Write Confirm Interval and The CP will wait the From 840581b30cdeb21c04235999cde8763f8341460d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Grzelak?= Date: Mon, 8 Dec 2025 11:27:14 +0100 Subject: [PATCH 3455/3902] drm/buddy: release free_trees array on buddy mm teardown MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [ Upstream commit 7d0507772406e129329983b8b807e5b499bd74fd ] During initialization of DRM buddy memory manager at drm_buddy_init, mm->free_trees array is allocated for both clear and dirty RB trees. During cleanup happening at drm_buddy_fini it is never freed, leading to following memory leaks observed on xe module load & unload cycles: kmemleak_alloc+0x4a/0x90 __kmalloc_cache_noprof+0x488/0x800 drm_buddy_init+0xc2/0x330 [drm_buddy] __xe_ttm_vram_mgr_init+0xc3/0x190 [xe] xe_ttm_stolen_mgr_init+0xf5/0x9d0 [xe] xe_device_probe+0x326/0x9e0 [xe] xe_pci_probe+0x39a/0x610 [xe] local_pci_probe+0x47/0xb0 pci_device_probe+0xf3/0x260 really_probe+0xf1/0x3c0 __driver_probe_device+0x8c/0x180 driver_probe_device+0x24/0xd0 __driver_attach+0x10f/0x220 bus_for_each_dev+0x7f/0xe0 driver_attach+0x1e/0x30 bus_add_driver+0x151/0x290 Deallocate array for free trees when cleaning up buddy memory manager in the same way as if going through out_free_tree label. Fixes: d4cd665c98c1 ("drm/buddy: Separate clear and dirty free block trees") Signed-off-by: Michał Grzelak Reviewed-by: Lucas De Marchi Reviewed-by: Matthew Auld Signed-off-by: Arunpravin Paneer Selvam Link: https://patch.msgid.link/20251208102714.4008260-2-michal.grzelak@intel.com Signed-off-by: Sasha Levin --- drivers/gpu/drm/drm_buddy.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/gpu/drm/drm_buddy.c b/drivers/gpu/drm/drm_buddy.c index f2c92902e4a302..3f1a9892f2a392 100644 --- a/drivers/gpu/drm/drm_buddy.c +++ b/drivers/gpu/drm/drm_buddy.c @@ -419,6 +419,7 @@ void drm_buddy_fini(struct drm_buddy *mm) for_each_free_tree(i) kfree(mm->free_trees[i]); + kfree(mm->free_trees); kfree(mm->roots); } EXPORT_SYMBOL(drm_buddy_fini); From bc950899ef118979277300a7a3e2922f92aac80c Mon Sep 17 00:00:00 2001 From: Baihan Li Date: Wed, 10 Dec 2025 10:37:56 +0800 Subject: [PATCH 3456/3902] drm/hisilicon/hibmc: fix dp probabilistical detect errors after HPD irq [ Upstream commit 3906e7a3b26d683868704fe262db443207f392fe ] The issue is that drm_connector_helper_detect_from_ddc() returns wrong status when plugging or unplugging the monitor, which may cause the link failed err.[0] Use HPD pin status in DP's detect_ctx() for real physical monitor in/out, and implement a complete DP detection including read DPCD, check if it's a branch device and its sink count for different situations. [0]: hibme-drm 0000:83:00.0: [drm] *ERROR* channel equalization failed 5 times hibme-drm 0000:83:00.0: [drm] *ERROR* channel equalization failed 5 times hibme-drm 0000:83:00.0: [drm] *ERROR* dp link training failed, ret: -16 hibmc-drm 0000:83:00.0: [drm] *ERROR* hibme dp mode set failed: -16 Fixes: 3c7623fb5bb6 ("drm/hisilicon/hibmc: Enable this hot plug detect of irq feature") Signed-off-by: Baihan Li Signed-off-by: Yongbang Shi Reviewed-by: Tao Tian Link: https://patch.msgid.link/20251210023759.3944834-2-shiyongbang@huawei.com Signed-off-by: Dmitry Baryshkov Signed-off-by: Sasha Levin --- drivers/gpu/drm/hisilicon/hibmc/dp/dp_comm.h | 4 ++ drivers/gpu/drm/hisilicon/hibmc/dp/dp_hw.c | 19 +++++++ drivers/gpu/drm/hisilicon/hibmc/dp/dp_hw.h | 6 +++ drivers/gpu/drm/hisilicon/hibmc/dp/dp_reg.h | 3 ++ .../gpu/drm/hisilicon/hibmc/hibmc_drm_dp.c | 52 +++++++++++++++++-- 5 files changed, 80 insertions(+), 4 deletions(-) diff --git a/drivers/gpu/drm/hisilicon/hibmc/dp/dp_comm.h b/drivers/gpu/drm/hisilicon/hibmc/dp/dp_comm.h index 4add05c7f161a0..f9ee7ebfec55c3 100644 --- a/drivers/gpu/drm/hisilicon/hibmc/dp/dp_comm.h +++ b/drivers/gpu/drm/hisilicon/hibmc/dp/dp_comm.h @@ -40,6 +40,10 @@ struct hibmc_dp_dev { struct mutex lock; /* protects concurrent RW in hibmc_dp_reg_write_field() */ struct hibmc_dp_link link; u8 dpcd[DP_RECEIVER_CAP_SIZE]; + u8 downstream_ports[DP_MAX_DOWNSTREAM_PORTS]; + struct drm_dp_desc desc; + bool is_branch; + int hpd_status; void __iomem *serdes_base; }; diff --git a/drivers/gpu/drm/hisilicon/hibmc/dp/dp_hw.c b/drivers/gpu/drm/hisilicon/hibmc/dp/dp_hw.c index 8f0daec7d17490..0ec6ace2d08228 100644 --- a/drivers/gpu/drm/hisilicon/hibmc/dp/dp_hw.c +++ b/drivers/gpu/drm/hisilicon/hibmc/dp/dp_hw.c @@ -2,6 +2,7 @@ // Copyright (c) 2024 Hisilicon Limited. #include +#include #include #include "dp_config.h" #include "dp_comm.h" @@ -305,3 +306,21 @@ void hibmc_dp_set_cbar(struct hibmc_dp *dp, const struct hibmc_dp_cbar_cfg *cfg) hibmc_dp_reg_write_field(dp_dev, HIBMC_DP_COLOR_BAR_CTRL, BIT(0), cfg->enable); writel(HIBMC_DP_SYNC_EN_MASK, dp_dev->base + HIBMC_DP_TIMING_SYNC_CTRL); } + +bool hibmc_dp_check_hpd_status(struct hibmc_dp *dp, int exp_status) +{ + u32 status; + int ret; + + ret = readl_poll_timeout(dp->dp_dev->base + HIBMC_DP_HPD_STATUS, status, + FIELD_GET(HIBMC_DP_HPD_CUR_STATE, status) == exp_status, + 1000, 100000); /* DP spec says 100ms */ + if (ret) { + drm_dbg_dp(dp->drm_dev, "wait hpd status timeout"); + return false; + } + + dp->dp_dev->hpd_status = exp_status; + + return true; +} diff --git a/drivers/gpu/drm/hisilicon/hibmc/dp/dp_hw.h b/drivers/gpu/drm/hisilicon/hibmc/dp/dp_hw.h index 665f5b166dfb5c..59c1eae153c55d 100644 --- a/drivers/gpu/drm/hisilicon/hibmc/dp/dp_hw.h +++ b/drivers/gpu/drm/hisilicon/hibmc/dp/dp_hw.h @@ -14,6 +14,11 @@ struct hibmc_dp_dev; +enum hibmc_hpd_status { + HIBMC_HPD_OUT, + HIBMC_HPD_IN, +}; + enum hibmc_dp_cbar_pattern { CBAR_COLOR_BAR, CBAR_WHITE, @@ -60,5 +65,6 @@ void hibmc_dp_reset_link(struct hibmc_dp *dp); void hibmc_dp_hpd_cfg(struct hibmc_dp *dp); void hibmc_dp_enable_int(struct hibmc_dp *dp); void hibmc_dp_disable_int(struct hibmc_dp *dp); +bool hibmc_dp_check_hpd_status(struct hibmc_dp *dp, int exp_status); #endif diff --git a/drivers/gpu/drm/hisilicon/hibmc/dp/dp_reg.h b/drivers/gpu/drm/hisilicon/hibmc/dp/dp_reg.h index 394b1e933c3ae7..64306abcd98666 100644 --- a/drivers/gpu/drm/hisilicon/hibmc/dp/dp_reg.h +++ b/drivers/gpu/drm/hisilicon/hibmc/dp/dp_reg.h @@ -24,6 +24,9 @@ #define HIBMC_DP_CFG_AUX_READY_DATA_BYTE GENMASK(16, 12) #define HIBMC_DP_CFG_AUX GENMASK(24, 17) +#define HIBMC_DP_HPD_STATUS 0x98 +#define HIBMC_DP_HPD_CUR_STATE GENMASK(7, 4) + #define HIBMC_DP_PHYIF_CTRL0 0xa0 #define HIBMC_DP_CFG_SCRAMBLE_EN BIT(0) #define HIBMC_DP_CFG_PAT_SEL GENMASK(7, 4) diff --git a/drivers/gpu/drm/hisilicon/hibmc/hibmc_drm_dp.c b/drivers/gpu/drm/hisilicon/hibmc/hibmc_drm_dp.c index d06832e62e966e..4a66a107900a10 100644 --- a/drivers/gpu/drm/hisilicon/hibmc/hibmc_drm_dp.c +++ b/drivers/gpu/drm/hisilicon/hibmc/hibmc_drm_dp.c @@ -12,6 +12,7 @@ #include "hibmc_drm_drv.h" #include "dp/dp_hw.h" +#include "dp/dp_comm.h" #define DP_MASKED_SINK_HPD_PLUG_INT BIT(2) @@ -31,12 +32,53 @@ static int hibmc_dp_connector_get_modes(struct drm_connector *connector) return count; } +static bool hibmc_dp_get_dpcd(struct hibmc_dp_dev *dp_dev) +{ + int ret; + + ret = drm_dp_read_dpcd_caps(dp_dev->aux, dp_dev->dpcd); + if (ret) + return false; + + dp_dev->is_branch = drm_dp_is_branch(dp_dev->dpcd); + + ret = drm_dp_read_desc(dp_dev->aux, &dp_dev->desc, dp_dev->is_branch); + if (ret) + return false; + + ret = drm_dp_read_downstream_info(dp_dev->aux, dp_dev->dpcd, dp_dev->downstream_ports); + if (ret) + return false; + + return true; +} + static int hibmc_dp_detect(struct drm_connector *connector, struct drm_modeset_acquire_ctx *ctx, bool force) { - mdelay(200); + struct hibmc_dp *dp = to_hibmc_dp(connector); + struct hibmc_dp_dev *dp_dev = dp->dp_dev; + int ret; + + if (dp->irq_status) { + if (dp_dev->hpd_status != HIBMC_HPD_IN) + return connector_status_disconnected; + } + + if (!hibmc_dp_get_dpcd(dp_dev)) + return connector_status_disconnected; + + if (!dp_dev->is_branch) + return connector_status_connected; + + if (drm_dp_read_sink_count_cap(connector, dp_dev->dpcd, &dp_dev->desc) && + dp_dev->downstream_ports[0] & DP_DS_PORT_HPD) { + ret = drm_dp_read_sink_count(dp_dev->aux); + if (ret > 0) + return connector_status_connected; + } - return drm_connector_helper_detect_from_ddc(connector, ctx, force); + return connector_status_disconnected; } static const struct drm_connector_helper_funcs hibmc_dp_conn_helper_funcs = { @@ -115,7 +157,7 @@ irqreturn_t hibmc_dp_hpd_isr(int irq, void *arg) { struct drm_device *dev = (struct drm_device *)arg; struct hibmc_drm_private *priv = to_hibmc_drm_private(dev); - int idx; + int idx, exp_status; if (!drm_dev_enter(dev, &idx)) return -ENODEV; @@ -123,12 +165,14 @@ irqreturn_t hibmc_dp_hpd_isr(int irq, void *arg) if (priv->dp.irq_status & DP_MASKED_SINK_HPD_PLUG_INT) { drm_dbg_dp(&priv->dev, "HPD IN isr occur!\n"); hibmc_dp_hpd_cfg(&priv->dp); + exp_status = HIBMC_HPD_IN; } else { drm_dbg_dp(&priv->dev, "HPD OUT isr occur!\n"); hibmc_dp_reset_link(&priv->dp); + exp_status = HIBMC_HPD_OUT; } - if (dev->registered) + if (hibmc_dp_check_hpd_status(&priv->dp, exp_status)) drm_connector_helper_hpd_irq_event(&priv->dp.connector); drm_dev_exit(idx); From 84359ed3c53f0a44c56c71bb90498e8db72acf92 Mon Sep 17 00:00:00 2001 From: Baihan Li Date: Wed, 10 Dec 2025 10:37:57 +0800 Subject: [PATCH 3457/3902] drm/hisilicon/hibmc: add dp mode valid check [ Upstream commit 607805abfb747b98f43aa57d6d9ba4caed4d106f ] If DP is connected, check the DP BW in mode_valid_ctx() to ensure that DP's link rate supports high-resolution data transmission. Fixes: 0ab6ea261c1f ("drm/hisilicon/hibmc: add dp module in hibmc") Signed-off-by: Baihan Li Signed-off-by: Yongbang Shi Reviewed-by: Dmitry Baryshkov Reviewed-by: Tao Tian Link: https://patch.msgid.link/20251210023759.3944834-3-shiyongbang@huawei.com Signed-off-by: Dmitry Baryshkov Signed-off-by: Sasha Levin --- .../gpu/drm/hisilicon/hibmc/dp/dp_config.h | 2 ++ drivers/gpu/drm/hisilicon/hibmc/dp/dp_hw.c | 10 ++++++++++ drivers/gpu/drm/hisilicon/hibmc/dp/dp_hw.h | 2 ++ .../gpu/drm/hisilicon/hibmc/hibmc_drm_dp.c | 19 +++++++++++++++++++ 4 files changed, 33 insertions(+) diff --git a/drivers/gpu/drm/hisilicon/hibmc/dp/dp_config.h b/drivers/gpu/drm/hisilicon/hibmc/dp/dp_config.h index 08f9e1caf7fcbb..efb30a7584758c 100644 --- a/drivers/gpu/drm/hisilicon/hibmc/dp/dp_config.h +++ b/drivers/gpu/drm/hisilicon/hibmc/dp/dp_config.h @@ -17,5 +17,7 @@ #define HIBMC_DP_LINK_RATE_CAL 27 #define HIBMC_DP_SYNC_DELAY(lanes) ((lanes) == 0x2 ? 86 : 46) #define HIBMC_DP_INT_ENABLE 0xc +/* HIBMC_DP_LINK_RATE_CAL * 10000 * 80% = 216000 */ +#define DP_MODE_VALI_CAL 216000 #endif diff --git a/drivers/gpu/drm/hisilicon/hibmc/dp/dp_hw.c b/drivers/gpu/drm/hisilicon/hibmc/dp/dp_hw.c index 0ec6ace2d08228..37549dafa06caf 100644 --- a/drivers/gpu/drm/hisilicon/hibmc/dp/dp_hw.c +++ b/drivers/gpu/drm/hisilicon/hibmc/dp/dp_hw.c @@ -264,6 +264,16 @@ void hibmc_dp_reset_link(struct hibmc_dp *dp) dp->dp_dev->link.status.channel_equalized = false; } +u8 hibmc_dp_get_link_rate(struct hibmc_dp *dp) +{ + return dp->dp_dev->link.cap.link_rate; +} + +u8 hibmc_dp_get_lanes(struct hibmc_dp *dp) +{ + return dp->dp_dev->link.cap.lanes; +} + static const struct hibmc_dp_color_raw g_rgb_raw[] = { {CBAR_COLOR_BAR, 0x000, 0x000, 0x000}, {CBAR_WHITE, 0xfff, 0xfff, 0xfff}, diff --git a/drivers/gpu/drm/hisilicon/hibmc/dp/dp_hw.h b/drivers/gpu/drm/hisilicon/hibmc/dp/dp_hw.h index 59c1eae153c55d..31316fe1ea8dbf 100644 --- a/drivers/gpu/drm/hisilicon/hibmc/dp/dp_hw.h +++ b/drivers/gpu/drm/hisilicon/hibmc/dp/dp_hw.h @@ -66,5 +66,7 @@ void hibmc_dp_hpd_cfg(struct hibmc_dp *dp); void hibmc_dp_enable_int(struct hibmc_dp *dp); void hibmc_dp_disable_int(struct hibmc_dp *dp); bool hibmc_dp_check_hpd_status(struct hibmc_dp *dp, int exp_status); +u8 hibmc_dp_get_link_rate(struct hibmc_dp *dp); +u8 hibmc_dp_get_lanes(struct hibmc_dp *dp); #endif diff --git a/drivers/gpu/drm/hisilicon/hibmc/hibmc_drm_dp.c b/drivers/gpu/drm/hisilicon/hibmc/hibmc_drm_dp.c index 4a66a107900a10..616821e3c933bc 100644 --- a/drivers/gpu/drm/hisilicon/hibmc/hibmc_drm_dp.c +++ b/drivers/gpu/drm/hisilicon/hibmc/hibmc_drm_dp.c @@ -13,6 +13,7 @@ #include "hibmc_drm_drv.h" #include "dp/dp_hw.h" #include "dp/dp_comm.h" +#include "dp/dp_config.h" #define DP_MASKED_SINK_HPD_PLUG_INT BIT(2) @@ -81,9 +82,27 @@ static int hibmc_dp_detect(struct drm_connector *connector, return connector_status_disconnected; } +static int hibmc_dp_mode_valid(struct drm_connector *connector, + const struct drm_display_mode *mode, + struct drm_modeset_acquire_ctx *ctx, + enum drm_mode_status *status) +{ + struct hibmc_dp *dp = to_hibmc_dp(connector); + u64 cur_val, max_val; + + /* check DP link BW */ + cur_val = (u64)mode->clock * HIBMC_DP_BPP; + max_val = (u64)hibmc_dp_get_link_rate(dp) * DP_MODE_VALI_CAL * hibmc_dp_get_lanes(dp); + + *status = cur_val > max_val ? MODE_CLOCK_HIGH : MODE_OK; + + return 0; +} + static const struct drm_connector_helper_funcs hibmc_dp_conn_helper_funcs = { .get_modes = hibmc_dp_connector_get_modes, .detect_ctx = hibmc_dp_detect, + .mode_valid_ctx = hibmc_dp_mode_valid, }; static int hibmc_dp_late_register(struct drm_connector *connector) From 0922d282f7d305603938a69fa724523579a0c846 Mon Sep 17 00:00:00 2001 From: Baihan Li Date: Wed, 10 Dec 2025 10:37:58 +0800 Subject: [PATCH 3458/3902] drm/hisilicon/hibmc: fix no showing problem with loading hibmc manually [ Upstream commit 0607052a6aee1e3d218a99fae70ba9f14b3b47ed ] When using command rmmod and insmod, there is no showing in second time insmoding. Because DP controller won't send HPD signals, if connection doesn't change or controller isn't reset. So add reset before unreset in hibmc_dp_hw_init(). And also need to move the HDCP cfg after DP controller de-resets, so that HDCP configuration takes effect. Fixes: 3c7623fb5bb6 ("drm/hisilicon/hibmc: Enable this hot plug detect of irq feature") Signed-off-by: Baihan Li Signed-off-by: Yongbang Shi Reviewed-by: Dmitry Baryshkov Reviewed-by: Tao Tian Link: https://patch.msgid.link/20251210023759.3944834-4-shiyongbang@huawei.com Signed-off-by: Dmitry Baryshkov Signed-off-by: Sasha Levin --- drivers/gpu/drm/hisilicon/hibmc/dp/dp_hw.c | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/drivers/gpu/drm/hisilicon/hibmc/dp/dp_hw.c b/drivers/gpu/drm/hisilicon/hibmc/dp/dp_hw.c index 37549dafa06caf..8f8ca940b6b26e 100644 --- a/drivers/gpu/drm/hisilicon/hibmc/dp/dp_hw.c +++ b/drivers/gpu/drm/hisilicon/hibmc/dp/dp_hw.c @@ -177,13 +177,16 @@ int hibmc_dp_hw_init(struct hibmc_dp *dp) dp_dev->link.cap.lanes = 0x2; dp_dev->link.cap.link_rate = DP_LINK_BW_8_1; - /* hdcp data */ - writel(HIBMC_DP_HDCP, dp_dev->base + HIBMC_DP_HDCP_CFG); /* int init */ writel(0, dp_dev->base + HIBMC_DP_INTR_ENABLE); writel(HIBMC_DP_INT_RST, dp_dev->base + HIBMC_DP_INTR_ORIGINAL_STATUS); /* rst */ + writel(0, dp_dev->base + HIBMC_DP_DPTX_RST_CTRL); + usleep_range(30, 50); + /* de-rst */ writel(HIBMC_DP_DPTX_RST, dp_dev->base + HIBMC_DP_DPTX_RST_CTRL); + /* hdcp data */ + writel(HIBMC_DP_HDCP, dp_dev->base + HIBMC_DP_HDCP_CFG); /* clock enable */ writel(HIBMC_DP_CLK_EN, dp_dev->base + HIBMC_DP_DPTX_CLK_CTRL); From fb328aa95c34c14f5e358392afbae5a9dbe211ee Mon Sep 17 00:00:00 2001 From: Baihan Li Date: Wed, 10 Dec 2025 10:37:59 +0800 Subject: [PATCH 3459/3902] drm/hisilicon/hibmc: Adding reset colorbar cfg in dp init. [ Upstream commit 6dad7fa8581e96321ec8a6a4f8160762466f539a ] Add colorbar disable operation before reset chontroller, to make sure colorbar status is clear in the DP init, so if rmmod the driver and the previous colorbar configuration will not affect the next time insmod the driver. Fixes: 3c7623fb5bb6 ("drm/hisilicon/hibmc: Enable this hot plug detect of irq feature") Signed-off-by: Baihan Li Signed-off-by: Yongbang Shi Reviewed-by: Dmitry Baryshkov Reviewed-by: Tao Tian Link: https://patch.msgid.link/20251210023759.3944834-5-shiyongbang@huawei.com Signed-off-by: Dmitry Baryshkov Signed-off-by: Sasha Levin --- drivers/gpu/drm/hisilicon/hibmc/dp/dp_hw.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/drivers/gpu/drm/hisilicon/hibmc/dp/dp_hw.c b/drivers/gpu/drm/hisilicon/hibmc/dp/dp_hw.c index 8f8ca940b6b26e..d5bd3c45649b2f 100644 --- a/drivers/gpu/drm/hisilicon/hibmc/dp/dp_hw.c +++ b/drivers/gpu/drm/hisilicon/hibmc/dp/dp_hw.c @@ -180,6 +180,8 @@ int hibmc_dp_hw_init(struct hibmc_dp *dp) /* int init */ writel(0, dp_dev->base + HIBMC_DP_INTR_ENABLE); writel(HIBMC_DP_INT_RST, dp_dev->base + HIBMC_DP_INTR_ORIGINAL_STATUS); + /* clr colorbar */ + writel(0, dp_dev->base + HIBMC_DP_COLOR_BAR_CTRL); /* rst */ writel(0, dp_dev->base + HIBMC_DP_DPTX_RST_CTRL); usleep_range(30, 50); From 7672af88d30724222573f5f4bded99195650fc11 Mon Sep 17 00:00:00 2001 From: Cristian Ciocaltea Date: Thu, 15 Jan 2026 18:24:43 +0200 Subject: [PATCH 3460/3902] drm/rockchip: dw_hdmi_qp: Fix RK3576 HPD interrupt handling [ Upstream commit 5f7be8afc40c5ccf1be0410514703e50a49532c0 ] The threaded interrupt handler on RK3576 checks HPD IRQ status before deciding to continue with interrupt clearing and unmasking. This is not only redundant, since a similar verification has been already performed by the hard IRQ handler before masking the interrupt, but is also error prone, because it might happen that hardware clears the status register right after the masking operation completes, and before the threaded handler reads its value. The consequence is that HPD IRQ gets never unmasked, which breaks hotplug detection until reloading the driver or rebooting the system. Drop the unnecessary verification of the HPD interrupt status from the threaded interrupt handler. Fixes: 36439120efbd ("drm/rockchip: dw_hdmi_qp: Add basic RK3576 HDMI output support") Signed-off-by: Cristian Ciocaltea Signed-off-by: Heiko Stuebner Link: https://patch.msgid.link/20260115-dw-hdmi-qp-hpd-v1-1-e59c166eaa65@collabora.com Signed-off-by: Sasha Levin --- drivers/gpu/drm/rockchip/dw_hdmi_qp-rockchip.c | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/drivers/gpu/drm/rockchip/dw_hdmi_qp-rockchip.c b/drivers/gpu/drm/rockchip/dw_hdmi_qp-rockchip.c index 9ac45e7bc987a6..409f1a1e82a061 100644 --- a/drivers/gpu/drm/rockchip/dw_hdmi_qp-rockchip.c +++ b/drivers/gpu/drm/rockchip/dw_hdmi_qp-rockchip.c @@ -267,12 +267,7 @@ static irqreturn_t dw_hdmi_qp_rk3576_hardirq(int irq, void *dev_id) static irqreturn_t dw_hdmi_qp_rk3576_irq(int irq, void *dev_id) { struct rockchip_hdmi_qp *hdmi = dev_id; - u32 intr_stat, val; - - regmap_read(hdmi->regmap, RK3576_IOC_HDMI_HPD_STATUS, &intr_stat); - - if (!intr_stat) - return IRQ_NONE; + u32 val; val = FIELD_PREP_WM16(RK3576_HDMI_HPD_INT_CLR, 1); regmap_write(hdmi->regmap, RK3576_IOC_MISC_CON0, val); From b573177b314e08d6f3002f07a3a4403bf253f848 Mon Sep 17 00:00:00 2001 From: Dmitry Baryshkov Date: Mon, 19 Jan 2026 14:16:37 +0200 Subject: [PATCH 3461/3902] drm/msm/mdss: correct HBB programmed on UBWC 5.x and 6.x devices [ Upstream commit e6177c7a2401b87b016728b75992926971d871fc ] As in the previous generations, on UBWC 5.x and 6.x devices the Highest Bank Bit value should be programmed into the hardware with the offset of -13. Correct the value written into the register to prevent unpredictable results. Fixes: 227d4ce0b09e ("drm/msm: Offset MDSS HBB value by 13") Tested-by: Val Packett # x1e80100-dell-latitude-7455 Reviewed-by: Konrad Dybcio Signed-off-by: Dmitry Baryshkov Patchwork: https://patchwork.freedesktop.org/patch/699274/ Link: https://lore.kernel.org/r/20260119-msm-ubwc-fixes-v4-1-0987acc0427f@oss.qualcomm.com Signed-off-by: Sasha Levin --- drivers/gpu/drm/msm/msm_mdss.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/gpu/drm/msm/msm_mdss.c b/drivers/gpu/drm/msm/msm_mdss.c index 2d0e3e784c044d..4dbb1b1d879f1b 100644 --- a/drivers/gpu/drm/msm/msm_mdss.c +++ b/drivers/gpu/drm/msm/msm_mdss.c @@ -229,7 +229,7 @@ static void msm_mdss_setup_ubwc_dec_50(struct msm_mdss *msm_mdss) { const struct qcom_ubwc_cfg_data *data = msm_mdss->mdss_data; u32 value = MDSS_UBWC_STATIC_UBWC_SWIZZLE(data->ubwc_swizzle) | - MDSS_UBWC_STATIC_HIGHEST_BANK_BIT(data->highest_bank_bit); + MDSS_UBWC_STATIC_HIGHEST_BANK_BIT(data->highest_bank_bit - 13); if (data->ubwc_bank_spread) value |= MDSS_UBWC_STATIC_UBWC_BANK_SPREAD; From d791b31e7da1646ecf65df395df2f2ac61304f92 Mon Sep 17 00:00:00 2001 From: Dmitry Baryshkov Date: Mon, 19 Jan 2026 14:16:38 +0200 Subject: [PATCH 3462/3902] drm/msm/dpu: offset HBB values written to DPU by -13 [ Upstream commit 7ead14d4b9742b5ed244f35b999f0fe26dc23586 ] As in all other places, the Highest Bank Bit value should be programmed into the hardware with the offset of -13. Correct the value written into the register to prevent unpredictable results. Fixes: 227d4ce0b09e ("drm/msm: Offset MDSS HBB value by 13") Tested-by: Val Packett # x1e80100-dell-latitude-7455 Reviewed-by: Konrad Dybcio Signed-off-by: Dmitry Baryshkov Patchwork: https://patchwork.freedesktop.org/patch/699276/ Link: https://lore.kernel.org/r/20260119-msm-ubwc-fixes-v4-2-0987acc0427f@oss.qualcomm.com Signed-off-by: Sasha Levin --- drivers/gpu/drm/msm/disp/dpu1/dpu_hw_sspp.c | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/drivers/gpu/drm/msm/disp/dpu1/dpu_hw_sspp.c b/drivers/gpu/drm/msm/disp/dpu1/dpu_hw_sspp.c index 6f1fc790ad6d81..b66c4cb5760c9a 100644 --- a/drivers/gpu/drm/msm/disp/dpu1/dpu_hw_sspp.c +++ b/drivers/gpu/drm/msm/disp/dpu1/dpu_hw_sspp.c @@ -270,30 +270,32 @@ static void dpu_hw_sspp_setup_format(struct dpu_sw_pipe *pipe, ((fmt->bpp - 1) << 9); if (fmt->fetch_mode != MDP_FETCH_LINEAR) { + u32 hbb = ctx->ubwc->highest_bank_bit - 13; + if (MSM_FORMAT_IS_UBWC(fmt)) opmode |= MDSS_MDP_OP_BWC_EN; src_format |= (fmt->fetch_mode & 3) << 30; /*FRAME_FORMAT */ DPU_REG_WRITE(c, SSPP_FETCH_CONFIG, DPU_FETCH_CONFIG_RESET_VALUE | - ctx->ubwc->highest_bank_bit << 18); + hbb << 18); switch (ctx->ubwc->ubwc_enc_version) { case UBWC_1_0: fast_clear = fmt->alpha_enable ? BIT(31) : 0; DPU_REG_WRITE(c, SSPP_UBWC_STATIC_CTRL, fast_clear | (ctx->ubwc->ubwc_swizzle & 0x1) | BIT(8) | - (ctx->ubwc->highest_bank_bit << 4)); + (hbb << 4)); break; case UBWC_2_0: fast_clear = fmt->alpha_enable ? BIT(31) : 0; DPU_REG_WRITE(c, SSPP_UBWC_STATIC_CTRL, fast_clear | (ctx->ubwc->ubwc_swizzle) | - (ctx->ubwc->highest_bank_bit << 4)); + (hbb << 4)); break; case UBWC_3_0: DPU_REG_WRITE(c, SSPP_UBWC_STATIC_CTRL, BIT(30) | (ctx->ubwc->ubwc_swizzle) | - (ctx->ubwc->highest_bank_bit << 4)); + (hbb << 4)); break; case UBWC_4_0: DPU_REG_WRITE(c, SSPP_UBWC_STATIC_CTRL, From e60e16d406b2310a05353649078f9b3e4031d33d Mon Sep 17 00:00:00 2001 From: Dmitry Baryshkov Date: Mon, 19 Jan 2026 14:16:39 +0200 Subject: [PATCH 3463/3902] drm/msm/dpu: program correct register for UBWC config on DPU 8.x+ [ Upstream commit 5dcec3fc1311c277369a4bdf8b292781e5cc91fd ] Since DPU 8.0 there is a separate register for the second rectangle, which needs to be programmed with the UBWC config if multirect is being used. Write pipe's UBWC configuration to the correct register. Fixes: 100d7ef6995d ("drm/msm/dpu: add support for SM8450") Tested-by: Val Packett # x1e80100-dell-latitude-7455 Reviewed-by: Konrad Dybcio Signed-off-by: Dmitry Baryshkov Patchwork: https://patchwork.freedesktop.org/patch/699277/ Link: https://lore.kernel.org/r/20260119-msm-ubwc-fixes-v4-3-0987acc0427f@oss.qualcomm.com Signed-off-by: Sasha Levin --- drivers/gpu/drm/msm/disp/dpu1/dpu_hw_sspp.c | 25 ++++++++++++++++----- 1 file changed, 19 insertions(+), 6 deletions(-) diff --git a/drivers/gpu/drm/msm/disp/dpu1/dpu_hw_sspp.c b/drivers/gpu/drm/msm/disp/dpu1/dpu_hw_sspp.c index b66c4cb5760c9a..6ff4902fce08ee 100644 --- a/drivers/gpu/drm/msm/disp/dpu1/dpu_hw_sspp.c +++ b/drivers/gpu/drm/msm/disp/dpu1/dpu_hw_sspp.c @@ -72,6 +72,8 @@ #define SSPP_EXCL_REC_XY_REC1 0x188 #define SSPP_EXCL_REC_SIZE 0x1B4 #define SSPP_EXCL_REC_XY 0x1B8 +#define SSPP_UBWC_STATIC_CTRL_REC1 0x1c0 +#define SSPP_UBWC_ERROR_STATUS_REC1 0x1c8 #define SSPP_CLK_CTRL 0x330 /* SSPP_SRC_OP_MODE & OP_MODE_REC1 */ @@ -215,7 +217,7 @@ static void dpu_hw_sspp_setup_format(struct dpu_sw_pipe *pipe, u32 chroma_samp, unpack, src_format; u32 opmode = 0; u32 fast_clear = 0; - u32 op_mode_off, unpack_pat_off, format_off; + u32 op_mode_off, unpack_pat_off, format_off, ubwc_ctrl_off, ubwc_error_off; if (!ctx || !fmt) return; @@ -225,10 +227,21 @@ static void dpu_hw_sspp_setup_format(struct dpu_sw_pipe *pipe, op_mode_off = SSPP_SRC_OP_MODE; unpack_pat_off = SSPP_SRC_UNPACK_PATTERN; format_off = SSPP_SRC_FORMAT; + ubwc_ctrl_off = SSPP_UBWC_STATIC_CTRL; + ubwc_error_off = SSPP_UBWC_ERROR_STATUS; } else { op_mode_off = SSPP_SRC_OP_MODE_REC1; unpack_pat_off = SSPP_SRC_UNPACK_PATTERN_REC1; format_off = SSPP_SRC_FORMAT_REC1; + + /* reg wasn't present before DPU 8.0 */ + if (ctx->mdss_ver->core_major_ver >= 8) { + ubwc_ctrl_off = SSPP_UBWC_STATIC_CTRL_REC1; + ubwc_error_off = SSPP_UBWC_ERROR_STATUS_REC1; + } else { + ubwc_ctrl_off = SSPP_UBWC_STATIC_CTRL; + ubwc_error_off = SSPP_UBWC_ERROR_STATUS; + } } c = &ctx->hw; @@ -281,24 +294,24 @@ static void dpu_hw_sspp_setup_format(struct dpu_sw_pipe *pipe, switch (ctx->ubwc->ubwc_enc_version) { case UBWC_1_0: fast_clear = fmt->alpha_enable ? BIT(31) : 0; - DPU_REG_WRITE(c, SSPP_UBWC_STATIC_CTRL, + DPU_REG_WRITE(c, ubwc_ctrl_off, fast_clear | (ctx->ubwc->ubwc_swizzle & 0x1) | BIT(8) | (hbb << 4)); break; case UBWC_2_0: fast_clear = fmt->alpha_enable ? BIT(31) : 0; - DPU_REG_WRITE(c, SSPP_UBWC_STATIC_CTRL, + DPU_REG_WRITE(c, ubwc_ctrl_off, fast_clear | (ctx->ubwc->ubwc_swizzle) | (hbb << 4)); break; case UBWC_3_0: - DPU_REG_WRITE(c, SSPP_UBWC_STATIC_CTRL, + DPU_REG_WRITE(c, ubwc_ctrl_off, BIT(30) | (ctx->ubwc->ubwc_swizzle) | (hbb << 4)); break; case UBWC_4_0: - DPU_REG_WRITE(c, SSPP_UBWC_STATIC_CTRL, + DPU_REG_WRITE(c, ubwc_ctrl_off, MSM_FORMAT_IS_YUV(fmt) ? 0 : BIT(30)); break; } @@ -327,7 +340,7 @@ static void dpu_hw_sspp_setup_format(struct dpu_sw_pipe *pipe, DPU_REG_WRITE(c, op_mode_off, opmode); /* clear previous UBWC error */ - DPU_REG_WRITE(c, SSPP_UBWC_ERROR_STATUS, BIT(31)); + DPU_REG_WRITE(c, ubwc_error_off, BIT(31)); } static void dpu_hw_sspp_setup_pe_config(struct dpu_hw_sspp *ctx, From b7e2bd20e7c8b301210e984d7f387dac303eef42 Mon Sep 17 00:00:00 2001 From: Nathan Chancellor Date: Tue, 13 Jan 2026 17:00:31 -0700 Subject: [PATCH 3464/3902] drm/msm/dp: Avoid division by zero in msm_dp_ctrl_config_msa() [ Upstream commit f185076da44c774241a16a82a7773ece3c1c607b ] An (admittedly problematic) optimization change in LLVM 20 [1] turns known division by zero into the equivalent of __builtin_unreachable(), which invokes undefined behavior if it is encountered in a control flow graph, destroying code generation. When compile testing for x86_64, objtool flags an instance of this optimization triggering in msm_dp_ctrl_config_msa(), inlined into msm_dp_ctrl_on_stream(): drivers/gpu/drm/msm/msm.o: warning: objtool: msm_dp_ctrl_on_stream(): unexpected end of section .text.msm_dp_ctrl_on_stream The zero division happens if the else branch in the first if statement in msm_dp_ctrl_config_msa() is taken because pixel_div is initialized to zero and it is not possible for LLVM to eliminate the else branch since rate is still not known after inlining into msm_dp_ctrl_on_stream(). Transform the if statements into a switch statement with a default case with the existing error print and an early return to avoid the invalid division. Add a comment to note this helps the compiler, even though the case is known to be unreachable. With this, pixel_dev's default zero initialization can be dropped, as it is dead with this change. Fixes: c943b4948b58 ("drm/msm/dp: add displayPort driver support") Link: https://github.com/llvm/llvm-project/commit/37932643abab699e8bb1def08b7eb4eae7ff1448 [1] Reported-by: kernel test robot Closes: https://lore.kernel.org/oe-kbuild-all/202601081959.9UVJEOfP-lkp@intel.com/ Suggested-by: Konrad Dybcio Signed-off-by: Nathan Chancellor Reviewed-by: Dmitry Baryshkov Patchwork: https://patchwork.freedesktop.org/patch/698355/ Link: https://lore.kernel.org/r/20260113-drm-msm-dp_ctrl-avoid-zero-div-v2-1-f1aa67bf6e8e@kernel.org Signed-off-by: Dmitry Baryshkov Signed-off-by: Sasha Levin --- drivers/gpu/drm/msm/dp/dp_ctrl.c | 24 ++++++++++++++++++------ 1 file changed, 18 insertions(+), 6 deletions(-) diff --git a/drivers/gpu/drm/msm/dp/dp_ctrl.c b/drivers/gpu/drm/msm/dp/dp_ctrl.c index c42fd2c17a328f..38ed4de8313e31 100644 --- a/drivers/gpu/drm/msm/dp/dp_ctrl.c +++ b/drivers/gpu/drm/msm/dp/dp_ctrl.c @@ -2395,20 +2395,32 @@ static void msm_dp_ctrl_config_msa(struct msm_dp_ctrl_private *ctrl, bool is_ycbcr_420) { u32 pixel_m, pixel_n; - u32 mvid, nvid, pixel_div = 0, dispcc_input_rate; + u32 mvid, nvid, pixel_div, dispcc_input_rate; u32 const nvid_fixed = DP_LINK_CONSTANT_N_VALUE; u32 const link_rate_hbr2 = 540000; u32 const link_rate_hbr3 = 810000; unsigned long den, num; - if (rate == link_rate_hbr3) + switch (rate) { + case link_rate_hbr3: pixel_div = 6; - else if (rate == 162000 || rate == 270000) - pixel_div = 2; - else if (rate == link_rate_hbr2) + break; + case link_rate_hbr2: pixel_div = 4; - else + break; + case 162000: + case 270000: + pixel_div = 2; + break; + default: + /* + * This cannot be reached but the compiler is not able to know + * that statically so return early to avoid a possibly invalid + * division. + */ DRM_ERROR("Invalid pixel mux divider\n"); + return; + } dispcc_input_rate = (rate * 10) / pixel_div; From 39c24b19a61114f1a5118ad2290fb5056b264bf4 Mon Sep 17 00:00:00 2001 From: Andy Shevchenko Date: Tue, 20 Jan 2026 14:12:30 +0100 Subject: [PATCH 3465/3902] platform/chrome: cros_typec_switch: Don't touch struct fwnode_handle::dev [ Upstream commit e1adf48853bc715f4deea074932aa1c44eb7abea ] The 'dev' field in struct fwnode is special and related to device links, There no driver should use it for printing messages. Fix incorrect use of private field. Fixes: affc804c44c8 ("platform/chrome: cros_typec_switch: Add switch driver") Signed-off-by: Andy Shevchenko Link: https://lore.kernel.org/r/20260120131413.1697891-2-andriy.shevchenko@linux.intel.com Signed-off-by: Tzung-Bi Shih Signed-off-by: Sasha Levin --- drivers/platform/chrome/cros_typec_switch.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/drivers/platform/chrome/cros_typec_switch.c b/drivers/platform/chrome/cros_typec_switch.c index 8d7c34abb0a128..d8a28d4e51a85e 100644 --- a/drivers/platform/chrome/cros_typec_switch.c +++ b/drivers/platform/chrome/cros_typec_switch.c @@ -230,20 +230,20 @@ static int cros_typec_register_switches(struct cros_typec_switch_data *sdata) adev = to_acpi_device_node(fwnode); if (!adev) { - dev_err(fwnode->dev, "Couldn't get ACPI device handle\n"); + dev_err(dev, "Couldn't get ACPI device handle for %pfwP\n", fwnode); ret = -ENODEV; goto err_switch; } ret = acpi_evaluate_integer(adev->handle, "_ADR", NULL, &index); if (ACPI_FAILURE(ret)) { - dev_err(fwnode->dev, "_ADR wasn't evaluated\n"); + dev_err(dev, "_ADR wasn't evaluated for %pfwP\n", fwnode); ret = -ENODATA; goto err_switch; } if (index >= EC_USB_PD_MAX_PORTS) { - dev_err(fwnode->dev, "Invalid port index number: %llu\n", index); + dev_err(dev, "%pfwP: Invalid port index number: %llu\n", fwnode, index); ret = -EINVAL; goto err_switch; } From 16dd68310f5011db4f12a7fb94fb2990cf149ba4 Mon Sep 17 00:00:00 2001 From: Gokul Praveen Date: Wed, 21 Jan 2026 11:41:34 +0530 Subject: [PATCH 3466/3902] pwm: tiehrpwm: Enable pwmchip's parent device before setting configuration MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [ Upstream commit 75e7ed52ac7c1da90f304dcda2906636404df921 ] The period and duty cycle configurations on J7200 and J784S4 SoCs does not get reflected after setting them using sysfs nodes. This is because at the end of ehrpwm_pwm_config function, the put_sync function is called which resets the hardware. Hold the PWM controller out of low-power mode during .apply() to make sure it accepts the writes to its registers. This renders the calls to pm_runtime_get_sync() and pm_runtime_put_sync() in ehrpwm_pwm_config() into no-ops, so these can be dropped. Fixes: 5f027d9b83db ("pwm: tiehrpwm: Implement .apply() callback") Signed-off-by: Gokul Praveen Suggested-by: Uwe Kleine-König Link: https://patch.msgid.link/20260121061134.15466-1-g-praveen@ti.com Signed-off-by: Uwe Kleine-König Signed-off-by: Sasha Levin --- drivers/pwm/pwm-tiehrpwm.c | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/drivers/pwm/pwm-tiehrpwm.c b/drivers/pwm/pwm-tiehrpwm.c index 7a86cb090f76f1..2533c95b0ba9d3 100644 --- a/drivers/pwm/pwm-tiehrpwm.c +++ b/drivers/pwm/pwm-tiehrpwm.c @@ -237,8 +237,6 @@ static int ehrpwm_pwm_config(struct pwm_chip *chip, struct pwm_device *pwm, if (period_cycles < 1) period_cycles = 1; - pm_runtime_get_sync(pwmchip_parent(chip)); - /* Update clock prescaler values */ ehrpwm_modify(pc->mmio_base, TBCTL, TBCTL_CLKDIV_MASK, tb_divval); @@ -290,8 +288,6 @@ static int ehrpwm_pwm_config(struct pwm_chip *chip, struct pwm_device *pwm, if (!(duty_cycles > period_cycles)) ehrpwm_write(pc->mmio_base, cmp_reg, duty_cycles); - pm_runtime_put_sync(pwmchip_parent(chip)); - return 0; } @@ -378,6 +374,8 @@ static int ehrpwm_pwm_apply(struct pwm_chip *chip, struct pwm_device *pwm, int err; bool enabled = pwm->state.enabled; + guard(pm_runtime_active)(pwmchip_parent(chip)); + if (state->polarity != pwm->state.polarity) { if (enabled) { ehrpwm_pwm_disable(chip, pwm); From 5332491be2ca19b85e9f9c548c539f354f34fa32 Mon Sep 17 00:00:00 2001 From: Ricardo Ribalda Date: Wed, 14 Jan 2026 10:32:13 +0000 Subject: [PATCH 3467/3902] media: uvcvideo: Fix allocation for small frame sizes [ Upstream commit 40d3ac25c11310bfaa50ed7614846ef75cb69a1e ] If a frame has size of less or equal than one packet size uvc_alloc_urb_buffers() is unable to allocate memory for it due to a off-by-one error. Fix the off-by-one-error and now that we are at it, make sure that stream->urb_size has always a valid value when we return from the function, even when an error happens. Fixes: efdc8a9585ce ("V4L/DVB (10295): uvcvideo: Retry URB buffers allocation when the system is low on memory.") Reported-by: Itay Chamiel Closes: https://lore.kernel.org/linux-media/CANiDSCsSoZf2LsCCoWAUbCg6tJT-ypXR1B85aa6rAdMVYr2iBQ@mail.gmail.com/T/#t Co-developed-by: Itay Chamiel Signed-off-by: Itay Chamiel Signed-off-by: Ricardo Ribalda Reviewed-by: Laurent Pinchart Tested-by: Itay Chamiel Link: https://patch.msgid.link/20260114-uvc-alloc-urb-v1-1-cedf3fb66711@chromium.org Signed-off-by: Laurent Pinchart Signed-off-by: Hans Verkuil Signed-off-by: Sasha Levin --- drivers/media/usb/uvc/uvc_video.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/drivers/media/usb/uvc/uvc_video.c b/drivers/media/usb/uvc/uvc_video.c index 2094e059d7d39a..ec76595f3c4be0 100644 --- a/drivers/media/usb/uvc/uvc_video.c +++ b/drivers/media/usb/uvc/uvc_video.c @@ -1812,7 +1812,7 @@ static int uvc_alloc_urb_buffers(struct uvc_streaming *stream, npackets = UVC_MAX_PACKETS; /* Retry allocations until one succeed. */ - for (; npackets > 1; npackets /= 2) { + for (; npackets > 0; npackets /= 2) { stream->urb_size = psize * npackets; for (i = 0; i < UVC_URBS; ++i) { @@ -1837,6 +1837,7 @@ static int uvc_alloc_urb_buffers(struct uvc_streaming *stream, uvc_dbg(stream->dev, VIDEO, "Failed to allocate URB buffers (%u bytes per packet)\n", psize); + stream->urb_size = 0; return 0; } From ede7b0f33c5a67d34be1389784716338db760015 Mon Sep 17 00:00:00 2001 From: Roberto Sassu Date: Tue, 20 Jan 2026 15:53:41 +0100 Subject: [PATCH 3468/3902] evm: Use ordered xattrs list to calculate HMAC in evm_init_hmac() [ Upstream commit 0496fc9cdc384f67be4413b1c6156eb64fccd5c4 ] Commit 8e5d9f916a96 ("smack: deduplicate xattr setting in smack_inode_init_security()") introduced xattr_dupval() to simplify setting the xattrs to be provided by the SMACK LSM on inode creation, in the smack_inode_init_security(). Unfortunately, moving lsm_get_xattr_slot() caused the SMACK64TRANSMUTE xattr be added in the array of new xattrs before SMACK64. This causes the HMAC of xattrs calculated by evm_init_hmac() for new files to diverge from the one calculated by both evm_calc_hmac_or_hash() and evmctl. evm_init_hmac() calculates the HMAC of the xattrs of new files based on the order LSMs provide them, while evm_calc_hmac_or_hash() and evmctl calculate the HMAC based on an ordered xattrs list. Fix the issue by making evm_init_hmac() calculate the HMAC of new files based on the ordered xattrs list too. Fixes: 8e5d9f916a96 ("smack: deduplicate xattr setting in smack_inode_init_security()") Signed-off-by: Roberto Sassu Signed-off-by: Mimi Zohar Signed-off-by: Sasha Levin --- security/integrity/evm/evm_crypto.c | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/security/integrity/evm/evm_crypto.c b/security/integrity/evm/evm_crypto.c index a5e730ffda57fb..5a8cef45bacf01 100644 --- a/security/integrity/evm/evm_crypto.c +++ b/security/integrity/evm/evm_crypto.c @@ -401,6 +401,7 @@ int evm_init_hmac(struct inode *inode, const struct xattr *xattrs, { struct shash_desc *desc; const struct xattr *xattr; + struct xattr_list *xattr_entry; desc = init_desc(EVM_XATTR_HMAC, HASH_ALGO_SHA1); if (IS_ERR(desc)) { @@ -408,11 +409,16 @@ int evm_init_hmac(struct inode *inode, const struct xattr *xattrs, return PTR_ERR(desc); } - for (xattr = xattrs; xattr->name; xattr++) { - if (!evm_protected_xattr(xattr->name)) - continue; + list_for_each_entry_lockless(xattr_entry, &evm_config_xattrnames, + list) { + for (xattr = xattrs; xattr->name; xattr++) { + if (strcmp(xattr_entry->name + + XATTR_SECURITY_PREFIX_LEN, xattr->name) != 0) + continue; - crypto_shash_update(desc, xattr->value, xattr->value_len); + crypto_shash_update(desc, xattr->value, + xattr->value_len); + } } hmac_add_misc(desc, inode, EVM_XATTR_HMAC, hmac_val); From c287399b66c003012a41b7c374dd8b5a0ac14f56 Mon Sep 17 00:00:00 2001 From: Vinay Belgaumkar Date: Fri, 23 Jan 2026 16:59:17 -0800 Subject: [PATCH 3469/3902] drm/xe/ptl: Disable DCC on PTL [ Upstream commit 801a6e61f5fbab2c0dd76c8360f45b625b49e410 ] On PTL, the recommendation is to disable DCC(Duty Cycle Control) as it may cause some regressions due to added latencies. Upcoming GuC releases will disable DCC on PTL as well, but we need to force it in KMD so that this behavior is propagated to older kernels. v2: Update commit message (Rodrigo) v3: Rebase v4: Fix typo: s/propagted/propagated Fixes: 5cdb71d3b0db ("drm/xe/ptl: Add GuC FW definition for PTL") Cc: Daniele Ceraolo Spurio Cc: Rodrigo Vivi Signed-off-by: Vinay Belgaumkar Link: https://patch.msgid.link/20260124005917.398522-1-vinay.belgaumkar@intel.com Reviewed-by: Rodrigo Vivi Signed-off-by: Rodrigo Vivi (cherry picked from commit 40ee63f5df2d5c6471b583df800aac89dc0502a4) Signed-off-by: Rodrigo Vivi Signed-off-by: Sasha Levin --- drivers/gpu/drm/xe/xe_guc_pc.c | 34 ++++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/drivers/gpu/drm/xe/xe_guc_pc.c b/drivers/gpu/drm/xe/xe_guc_pc.c index 53fdf59524c41a..a3e9796e6430f7 100644 --- a/drivers/gpu/drm/xe/xe_guc_pc.c +++ b/drivers/gpu/drm/xe/xe_guc_pc.c @@ -1231,6 +1231,36 @@ int xe_guc_pc_set_power_profile(struct xe_guc_pc *pc, const char *buf) return ret; } +static int pc_action_set_dcc(struct xe_guc_pc *pc, bool enable) +{ + int ret; + + ret = pc_action_set_param(pc, + SLPC_PARAM_TASK_ENABLE_DCC, + enable); + if (!ret) + return pc_action_set_param(pc, + SLPC_PARAM_TASK_DISABLE_DCC, + !enable); + else + return ret; +} + +static int pc_modify_defaults(struct xe_guc_pc *pc) +{ + struct xe_device *xe = pc_to_xe(pc); + struct xe_gt *gt = pc_to_gt(pc); + int ret = 0; + + if (xe->info.platform == XE_PANTHERLAKE) { + ret = pc_action_set_dcc(pc, false); + if (unlikely(ret)) + xe_gt_err(gt, "Failed to modify DCC default: %pe\n", ERR_PTR(ret)); + } + + return ret; +} + /** * xe_guc_pc_start - Start GuC's Power Conservation component * @pc: Xe_GuC_PC instance @@ -1288,6 +1318,10 @@ int xe_guc_pc_start(struct xe_guc_pc *pc) ktime_ms_delta(ktime_get(), earlier)); } + ret = pc_modify_defaults(pc); + if (ret) + return ret; + ret = pc_init_freqs(pc); if (ret) goto out; From aa27ad4e20e0aa2e7c038fe303fbc99e156a4327 Mon Sep 17 00:00:00 2001 From: Shuicheng Lin Date: Fri, 9 Jan 2026 21:10:42 +0000 Subject: [PATCH 3470/3902] drm/xe: Unregister drm device on probe error [ Upstream commit 96c2c72b817d70e8d110e78b0162e044a0c41f9f ] Call drm_dev_unregister() when xe_device_probe() fails after successful drm_dev_register(). This ensures the DRM device is promptly unregistered before returning an error, avoiding leaving it registered on the failure path. Otherwise, there is warn message if xe_device_probe() is called again: " [ 207.322365] [drm:drm_minor_register] [ 207.322381] debugfs: '128' already exists in 'dri' [ 207.322432] sysfs: cannot create duplicate filename '/devices/pci0000:00/0000:00:01.0/0000:01:00.0/0000:02:01.0/0000:03:00.0/drm/renderD128' [ 207.322435] CPU: 5 UID: 0 PID: 10261 Comm: modprobe Tainted: G B W 6.19.0-rc2-lgci-xe-kernel+ #223 PREEMPT(voluntary) [ 207.322439] Tainted: [B]=BAD_PAGE, [W]=WARN [ 207.322440] Hardware name: ASUS System Product Name/PRIME Z790-P WIFI, BIOS 0812 02/24/2023 [ 207.322441] Call Trace: [ 207.322442] [ 207.322443] dump_stack_lvl+0xa0/0xc0 [ 207.322446] dump_stack+0x10/0x20 [ 207.322448] sysfs_warn_dup+0xd5/0x110 [ 207.322451] sysfs_create_dir_ns+0x1f6/0x280 [ 207.322453] ? __pfx_sysfs_create_dir_ns+0x10/0x10 [ 207.322455] ? lock_acquire+0x1a4/0x2e0 [ 207.322458] ? __kasan_check_read+0x11/0x20 [ 207.322461] kobject_add_internal+0x28d/0x8e0 [ 207.322464] kobject_add+0x11f/0x1f0 [ 207.322465] ? lock_acquire+0x1a4/0x2e0 [ 207.322467] ? __pfx_kobject_add+0x10/0x10 [ 207.322469] ? __kasan_check_write+0x14/0x20 [ 207.322471] ? kobject_put+0x62/0x4a0 [ 207.322473] ? get_device_parent.isra.0+0x1bb/0x4c0 [ 207.322475] ? kobject_put+0x62/0x4a0 [ 207.322477] device_add+0x2d7/0x1500 [ 207.322479] ? __pfx_device_add+0x10/0x10 [ 207.322481] ? drm_debugfs_add_file+0xfa/0x170 [ 207.322483] ? drm_debugfs_add_files+0x82/0xd0 [ 207.322485] ? drm_debugfs_add_files+0x82/0xd0 [ 207.322487] drm_minor_register+0x10a/0x2d0 [ 207.322489] drm_dev_register+0x143/0x860 [ 207.322491] ? xe_configfs_get_psmi_enabled+0x12/0x90 [xe] [ 207.322667] xe_device_probe+0x185b/0x2c40 [xe] [ 207.322812] ? __pfx___drm_dev_dbg+0x10/0x10 [ 207.322815] ? add_dr+0x180/0x220 [ 207.322818] ? __pfx___drmm_mutex_release+0x10/0x10 [ 207.322821] ? __pfx_xe_device_probe+0x10/0x10 [xe] [ 207.322966] ? xe_pm_init_early+0x33a/0x410 [xe] [ 207.323136] xe_pci_probe+0x936/0x1250 [xe] [ 207.323298] ? lock_acquire+0x1a4/0x2e0 [ 207.323302] ? __pfx_xe_pci_probe+0x10/0x10 [xe] [ 207.323464] local_pci_probe+0xe6/0x1a0 [ 207.323468] pci_device_probe+0x523/0x840 [ 207.323470] ? __pfx_pci_device_probe+0x10/0x10 [ 207.323473] ? sysfs_do_create_link_sd.isra.0+0x8c/0x110 [ 207.323476] ? sysfs_create_link+0x48/0xc0 [ 207.323479] really_probe+0x1fd/0x8a0 ... " Fixes: dd08ebf6c352 ("drm/xe: Introduce a new DRM driver for Intel GPUs") Signed-off-by: Shuicheng Lin Reviewed-by: Jonathan Cavitt Link: https://patch.msgid.link/20260109211041.2446012-2-shuicheng.lin@intel.com Signed-off-by: Matt Roper (cherry picked from commit 60bfb8baf8f0d5b0d521744dfd01c880ce1a23f3) Signed-off-by: Rodrigo Vivi Signed-off-by: Sasha Levin --- drivers/gpu/drm/xe/xe_device.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/gpu/drm/xe/xe_device.c b/drivers/gpu/drm/xe/xe_device.c index fe5aadb27b779f..0d69cd0e4e7985 100644 --- a/drivers/gpu/drm/xe/xe_device.c +++ b/drivers/gpu/drm/xe/xe_device.c @@ -966,6 +966,7 @@ int xe_device_probe(struct xe_device *xe) err_unregister_display: xe_display_unregister(xe); + drm_dev_unregister(&xe->drm); return err; } From a3ce2b030809f1f7fc7f63d6c9198282cf655738 Mon Sep 17 00:00:00 2001 From: Harry Yoo Date: Fri, 23 Jan 2026 07:52:40 +0100 Subject: [PATCH 3471/3902] mm/slab: fix false lockdep warning in __kfree_rcu_sheaf() [ Upstream commit f8b4cd2dad097e4ea5aed3511f42b9eb771e7b19 ] kvfree_call_rcu() can be called while holding a raw_spinlock_t. Since __kfree_rcu_sheaf() may acquire a spinlock_t (which becomes a sleeping lock on PREEMPT_RT) and violate lock nesting rules, kvfree_call_rcu() bypasses the sheaves layer entirely on PREEMPT_RT. However, lockdep still complains about acquiring spinlock_t while holding raw_spinlock_t, even on !PREEMPT_RT where spinlock_t is a spinning lock. This causes a false lockdep warning [1]: ============================= [ BUG: Invalid wait context ] 6.19.0-rc6-next-20260120 #21508 Not tainted ----------------------------- migration/1/23 is trying to lock: ffff8afd01054e98 (&barn->lock){..-.}-{3:3}, at: barn_get_empty_sheaf+0x1d/0xb0 other info that might help us debug this: context-{5:5} 3 locks held by migration/1/23: #0: ffff8afd01fd89a8 (&p->pi_lock){-.-.}-{2:2}, at: __balance_push_cpu_stop+0x3f/0x200 #1: ffffffff9f15c5c8 (rcu_read_lock){....}-{1:3}, at: cpuset_cpus_allowed_fallback+0x27/0x250 #2: ffff8afd1f470be0 ((local_lock_t *)&pcs->lock){+.+.}-{3:3}, at: __kfree_rcu_sheaf+0x52/0x3d0 stack backtrace: CPU: 1 UID: 0 PID: 23 Comm: migration/1 Not tainted 6.19.0-rc6-next-20260120 #21508 PREEMPTLAZY Hardware name: QEMU Standard PC (Q35 + ICH9, 2009), BIOS rel-1.14.0-0-g155821a1990b-prebuilt.qemu.org 04/01/2014 Stopper: __balance_push_cpu_stop+0x0/0x200 <- balance_push+0x118/0x170 Call Trace: __dump_stack+0x22/0x30 dump_stack_lvl+0x60/0x80 dump_stack+0x19/0x24 __lock_acquire+0xd3a/0x28e0 ? __lock_acquire+0x5a9/0x28e0 ? __lock_acquire+0x5a9/0x28e0 ? barn_get_empty_sheaf+0x1d/0xb0 lock_acquire+0xc3/0x270 ? barn_get_empty_sheaf+0x1d/0xb0 ? __kfree_rcu_sheaf+0x52/0x3d0 _raw_spin_lock_irqsave+0x47/0x70 ? barn_get_empty_sheaf+0x1d/0xb0 barn_get_empty_sheaf+0x1d/0xb0 ? __kfree_rcu_sheaf+0x52/0x3d0 __kfree_rcu_sheaf+0x19f/0x3d0 kvfree_call_rcu+0xaf/0x390 set_cpus_allowed_force+0xc8/0xf0 [...] This wasn't triggered until sheaves were enabled for all slab caches, since kfree_rcu() wasn't being called with a raw spinlock held for caches with sheaves (vma, maple node). As suggested by Vlastimil Babka, fix this by using a lockdep map with LD_WAIT_CONFIG wait type to tell lockdep that acquiring spinlock_t is valid in this case, as those spinlocks won't be used on PREEMPT_RT. Note that kfree_rcu_sheaf_map should be acquired using _try() variant, otherwise the acquisition of the lockdep map itself will trigger an invalid wait context warning. Reported-by: Paul E. McKenney Closes: https://lore.kernel.org/linux-mm/c858b9af-2510-448b-9ab3-058f7b80dd42@paulmck-laptop [1] Fixes: ec66e0d59952 ("slab: add sheaf support for batching kfree_rcu() operations") Suggested-by: Vlastimil Babka Signed-off-by: Harry Yoo Reviewed-by: Sebastian Andrzej Siewior Signed-off-by: Vlastimil Babka Signed-off-by: Sasha Levin --- mm/slub.c | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/mm/slub.c b/mm/slub.c index e01641cea143be..896421a5555730 100644 --- a/mm/slub.c +++ b/mm/slub.c @@ -6232,11 +6232,29 @@ static void rcu_free_sheaf(struct rcu_head *head) free_empty_sheaf(s, sheaf); } +/* + * kvfree_call_rcu() can be called while holding a raw_spinlock_t. Since + * __kfree_rcu_sheaf() may acquire a spinlock_t (sleeping lock on PREEMPT_RT), + * this would violate lock nesting rules. Therefore, kvfree_call_rcu() avoids + * this problem by bypassing the sheaves layer entirely on PREEMPT_RT. + * + * However, lockdep still complains that it is invalid to acquire spinlock_t + * while holding raw_spinlock_t, even on !PREEMPT_RT where spinlock_t is a + * spinning lock. Tell lockdep that acquiring spinlock_t is valid here + * by temporarily raising the wait-type to LD_WAIT_CONFIG. + */ +static DEFINE_WAIT_OVERRIDE_MAP(kfree_rcu_sheaf_map, LD_WAIT_CONFIG); + bool __kfree_rcu_sheaf(struct kmem_cache *s, void *obj) { struct slub_percpu_sheaves *pcs; struct slab_sheaf *rcu_sheaf; + if (WARN_ON_ONCE(IS_ENABLED(CONFIG_PREEMPT_RT))) + return false; + + lock_map_acquire_try(&kfree_rcu_sheaf_map); + if (!local_trylock(&s->cpu_sheaves->lock)) goto fail; @@ -6313,10 +6331,12 @@ bool __kfree_rcu_sheaf(struct kmem_cache *s, void *obj) local_unlock(&s->cpu_sheaves->lock); stat(s, FREE_RCU_SHEAF); + lock_map_release(&kfree_rcu_sheaf_map); return true; fail: stat(s, FREE_RCU_SHEAF_FAIL); + lock_map_release(&kfree_rcu_sheaf_map); return false; } From 52b71d02539783826a337244f019dc5f55cfaa46 Mon Sep 17 00:00:00 2001 From: Sheetal Date: Fri, 23 Jan 2026 15:23:43 +0530 Subject: [PATCH 3472/3902] ASoC: tegra: Add AHUB writeable_reg for RX holes [ Upstream commit 0ba6286a71581aaf8413a55b9bd90ea3463fd23b ] Add writeable_reg callbacks for Tegra210/186 AHUB RX registers so the flat cache only treats valid RX locations as writable, avoiding holes in the register map. Fixes: 16e1bcc2caf4 ("ASoC: tegra: Add Tegra210 based AHUB driver") Signed-off-by: Sheetal Reviewed-by: Jon Hunter Tested-by: Jon Hunter Link: https://patch.msgid.link/20260123095346.1258556-2-sheetal@nvidia.com Signed-off-by: Mark Brown Signed-off-by: Sasha Levin --- sound/soc/tegra/tegra210_ahub.c | 57 +++++++++++++++++++++++++++++++++ sound/soc/tegra/tegra210_ahub.h | 30 +++++++++++++++++ 2 files changed, 87 insertions(+) diff --git a/sound/soc/tegra/tegra210_ahub.c b/sound/soc/tegra/tegra210_ahub.c index 21aeaeba0b1079..01d60a74ad1c3b 100644 --- a/sound/soc/tegra/tegra210_ahub.c +++ b/sound/soc/tegra/tegra210_ahub.c @@ -2049,6 +2049,61 @@ static const struct snd_soc_component_driver tegra264_ahub_component = { .num_dapm_routes = ARRAY_SIZE(tegra264_ahub_routes), }; +static bool tegra210_ahub_wr_reg(struct device *dev, unsigned int reg) +{ + int part; + + if (reg % TEGRA210_XBAR_RX_STRIDE) + return false; + + for (part = 0; part < TEGRA210_XBAR_UPDATE_MAX_REG; part++) { + switch (reg & ~(part * TEGRA210_XBAR_PART1_RX)) { + case TEGRA210_AXBAR_PART_0_ADMAIF_RX1_0 ... TEGRA210_AXBAR_PART_0_ADMAIF_RX10_0: + case TEGRA210_AXBAR_PART_0_I2S1_RX1_0 ... TEGRA210_AXBAR_PART_0_I2S5_RX1_0: + case TEGRA210_AXBAR_PART_0_SFC1_RX1_0 ... TEGRA210_AXBAR_PART_0_SFC4_RX1_0: + case TEGRA210_AXBAR_PART_0_MIXER1_RX1_0 ... TEGRA210_AXBAR_PART_0_MIXER1_RX10_0: + case TEGRA210_AXBAR_PART_0_SPDIF1_RX1_0 ... TEGRA210_AXBAR_PART_0_SPDIF1_RX2_0: + case TEGRA210_AXBAR_PART_0_AFC1_RX1_0 ... TEGRA210_AXBAR_PART_0_AFC6_RX1_0: + case TEGRA210_AXBAR_PART_0_OPE1_RX1_0 ... TEGRA210_AXBAR_PART_0_OPE2_RX1_0: + case TEGRA210_AXBAR_PART_0_SPKPROT1_RX1_0: + case TEGRA210_AXBAR_PART_0_MVC1_RX1_0 ... TEGRA210_AXBAR_PART_0_MVC2_RX1_0: + case TEGRA210_AXBAR_PART_0_AMX1_RX1_0 ... TEGRA210_AXBAR_PART_0_ADX2_RX1_0: + return true; + default: + break; + } + } + + return false; +} + +static bool tegra186_ahub_wr_reg(struct device *dev, unsigned int reg) +{ + int part; + + if (reg % TEGRA210_XBAR_RX_STRIDE) + return false; + + for (part = 0; part < TEGRA186_XBAR_UPDATE_MAX_REG; part++) { + switch (reg & ~(part * TEGRA210_XBAR_PART1_RX)) { + case TEGRA210_AXBAR_PART_0_ADMAIF_RX1_0 ... TEGRA186_AXBAR_PART_0_I2S6_RX1_0: + case TEGRA210_AXBAR_PART_0_SFC1_RX1_0 ... TEGRA210_AXBAR_PART_0_SFC4_RX1_0: + case TEGRA210_AXBAR_PART_0_MIXER1_RX1_0 ... TEGRA210_AXBAR_PART_0_MIXER1_RX10_0: + case TEGRA186_AXBAR_PART_0_DSPK1_RX1_0 ... TEGRA186_AXBAR_PART_0_DSPK2_RX1_0: + case TEGRA210_AXBAR_PART_0_AFC1_RX1_0 ... TEGRA210_AXBAR_PART_0_AFC6_RX1_0: + case TEGRA210_AXBAR_PART_0_OPE1_RX1_0: + case TEGRA186_AXBAR_PART_0_MVC1_RX1_0 ... TEGRA186_AXBAR_PART_0_MVC2_RX1_0: + case TEGRA186_AXBAR_PART_0_AMX1_RX1_0 ... TEGRA186_AXBAR_PART_0_AMX3_RX4_0: + case TEGRA210_AXBAR_PART_0_ADX1_RX1_0 ... TEGRA186_AXBAR_PART_0_ASRC1_RX7_0: + return true; + default: + break; + } + } + + return false; +} + static bool tegra264_ahub_wr_reg(struct device *dev, unsigned int reg) { int part; @@ -2076,6 +2131,7 @@ static const struct regmap_config tegra210_ahub_regmap_config = { .reg_bits = 32, .val_bits = 32, .reg_stride = 4, + .writeable_reg = tegra210_ahub_wr_reg, .max_register = TEGRA210_MAX_REGISTER_ADDR, .cache_type = REGCACHE_FLAT, }; @@ -2084,6 +2140,7 @@ static const struct regmap_config tegra186_ahub_regmap_config = { .reg_bits = 32, .val_bits = 32, .reg_stride = 4, + .writeable_reg = tegra186_ahub_wr_reg, .max_register = TEGRA186_MAX_REGISTER_ADDR, .cache_type = REGCACHE_FLAT, }; diff --git a/sound/soc/tegra/tegra210_ahub.h b/sound/soc/tegra/tegra210_ahub.h index f355b2cfd19b22..acbe640dd3b572 100644 --- a/sound/soc/tegra/tegra210_ahub.h +++ b/sound/soc/tegra/tegra210_ahub.h @@ -68,6 +68,36 @@ #define TEGRA210_MAX_REGISTER_ADDR (TEGRA210_XBAR_PART2_RX + \ (TEGRA210_XBAR_RX_STRIDE * (TEGRA210_XBAR_AUDIO_RX_COUNT - 1))) +/* AXBAR register offsets */ +#define TEGRA186_AXBAR_PART_0_AMX1_RX1_0 0x120 +#define TEGRA186_AXBAR_PART_0_AMX3_RX4_0 0x14c +#define TEGRA186_AXBAR_PART_0_ASRC1_RX7_0 0x1a8 +#define TEGRA186_AXBAR_PART_0_DSPK1_RX1_0 0xc0 +#define TEGRA186_AXBAR_PART_0_DSPK2_RX1_0 0xc4 +#define TEGRA186_AXBAR_PART_0_I2S6_RX1_0 0x54 +#define TEGRA186_AXBAR_PART_0_MVC1_RX1_0 0x110 +#define TEGRA186_AXBAR_PART_0_MVC2_RX1_0 0x114 +#define TEGRA210_AXBAR_PART_0_ADMAIF_RX10_0 0x24 +#define TEGRA210_AXBAR_PART_0_ADMAIF_RX1_0 0x0 +#define TEGRA210_AXBAR_PART_0_ADX1_RX1_0 0x160 +#define TEGRA210_AXBAR_PART_0_ADX2_RX1_0 0x164 +#define TEGRA210_AXBAR_PART_0_AFC1_RX1_0 0xd0 +#define TEGRA210_AXBAR_PART_0_AFC6_RX1_0 0xe4 +#define TEGRA210_AXBAR_PART_0_AMX1_RX1_0 0x140 +#define TEGRA210_AXBAR_PART_0_I2S1_RX1_0 0x40 +#define TEGRA210_AXBAR_PART_0_I2S5_RX1_0 0x50 +#define TEGRA210_AXBAR_PART_0_MIXER1_RX10_0 0xa4 +#define TEGRA210_AXBAR_PART_0_MIXER1_RX1_0 0x80 +#define TEGRA210_AXBAR_PART_0_MVC1_RX1_0 0x120 +#define TEGRA210_AXBAR_PART_0_MVC2_RX1_0 0x124 +#define TEGRA210_AXBAR_PART_0_OPE1_RX1_0 0x100 +#define TEGRA210_AXBAR_PART_0_OPE2_RX1_0 0x104 +#define TEGRA210_AXBAR_PART_0_SFC1_RX1_0 0x60 +#define TEGRA210_AXBAR_PART_0_SFC4_RX1_0 0x6c +#define TEGRA210_AXBAR_PART_0_SPDIF1_RX1_0 0xc0 +#define TEGRA210_AXBAR_PART_0_SPDIF1_RX2_0 0xc4 +#define TEGRA210_AXBAR_PART_0_SPKPROT1_RX1_0 0x110 + #define MUX_REG(id) (TEGRA210_XBAR_RX_STRIDE * (id)) #define MUX_VALUE(npart, nbit) (1 + (nbit) + (npart) * 32) From e6ff5e9e390055d440e4505521bfd42ae4fc3ce3 Mon Sep 17 00:00:00 2001 From: Tzung-Bi Shih Date: Fri, 30 Jan 2026 04:03:35 +0000 Subject: [PATCH 3473/3902] platform/chrome: cros_ec_lightbar: Fix response size initialization [ Upstream commit ec0dd36dbf8b0b209e63d0cd795451fa2203c736 ] Commit 1e7913ff5f9f ("platform/chrome: cros_ec_lightbar: Reduce ligthbar get version command") meant to set smaller values for both request and response sizes. However, it incorrectly assigned the response size to the `result` field instead of `insize`. Fix it. Reported-by: Gwendal Grignou Closes: https://lore.kernel.org/chrome-platform/CAMHSBOVrrYaB=1nEqZk09VkczCrj=6B-P8Fe29TpPdSDgT2CCQ@mail.gmail.com Fixes: 1e7913ff5f9f ("platform/chrome: cros_ec_lightbar: Reduce ligthbar get version command") Link: https://lore.kernel.org/r/20260130040335.361997-1-tzungbi@kernel.org Reviewed-by: Gwendal Grignou Signed-off-by: Tzung-Bi Shih Signed-off-by: Sasha Levin --- drivers/platform/chrome/cros_ec_lightbar.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/platform/chrome/cros_ec_lightbar.c b/drivers/platform/chrome/cros_ec_lightbar.c index 87634f6921b78a..6b028615ee245b 100644 --- a/drivers/platform/chrome/cros_ec_lightbar.c +++ b/drivers/platform/chrome/cros_ec_lightbar.c @@ -119,7 +119,7 @@ static int get_lightbar_version(struct cros_ec_dev *ec, param = (struct ec_params_lightbar *)msg->data; param->cmd = LIGHTBAR_CMD_VERSION; msg->outsize = sizeof(param->cmd); - msg->result = sizeof(resp->version); + msg->insize = sizeof(resp->version); ret = cros_ec_cmd_xfer_status(ec->ec_dev, msg); if (ret < 0 && ret != -EINVAL) { ret = 0; From f6b4c1d98a7b8040d4d02e89425b3942016a2c2c Mon Sep 17 00:00:00 2001 From: Lizhi Hou Date: Tue, 27 Jan 2026 16:23:56 -0800 Subject: [PATCH 3474/3902] accel/amdxdna: Hold mm structure across iommu_sva_unbind_device() [ Upstream commit a9162439ad792afcddc04718408ec1380b7a5f63 ] Some tests trigger a crash in iommu_sva_unbind_device() due to accessing iommu_mm after the associated mm structure has been freed. Fix this by taking an explicit reference to the mm structure after successfully binding the device, and releasing it only after the device is unbound. This ensures the mm remains valid for the entire SVA bind/unbind lifetime. Fixes: be462c97b7df ("accel/amdxdna: Add hardware context") Reviewed-by: Mario Limonciello (AMD) Signed-off-by: Lizhi Hou Link: https://patch.msgid.link/20260128002356.1858122-1-lizhi.hou@amd.com Signed-off-by: Sasha Levin --- drivers/accel/amdxdna/amdxdna_pci_drv.c | 3 +++ drivers/accel/amdxdna/amdxdna_pci_drv.h | 1 + 2 files changed, 4 insertions(+) diff --git a/drivers/accel/amdxdna/amdxdna_pci_drv.c b/drivers/accel/amdxdna/amdxdna_pci_drv.c index ccf2d1de558c0f..88643e28af84b9 100644 --- a/drivers/accel/amdxdna/amdxdna_pci_drv.c +++ b/drivers/accel/amdxdna/amdxdna_pci_drv.c @@ -88,6 +88,8 @@ static int amdxdna_drm_open(struct drm_device *ddev, struct drm_file *filp) ret = -ENODEV; goto unbind_sva; } + client->mm = current->mm; + mmgrab(client->mm); init_srcu_struct(&client->hwctx_srcu); xa_init_flags(&client->hwctx_xa, XA_FLAGS_ALLOC); mutex_init(&client->mm_lock); @@ -127,6 +129,7 @@ static void amdxdna_drm_close(struct drm_device *ddev, struct drm_file *filp) drm_gem_object_put(to_gobj(client->dev_heap)); iommu_sva_unbind_device(client->sva); + mmdrop(client->mm); XDNA_DBG(xdna, "pid %d closed", client->pid); kfree(client); diff --git a/drivers/accel/amdxdna/amdxdna_pci_drv.h b/drivers/accel/amdxdna/amdxdna_pci_drv.h index 72d6696d49da81..64009ca2498271 100644 --- a/drivers/accel/amdxdna/amdxdna_pci_drv.h +++ b/drivers/accel/amdxdna/amdxdna_pci_drv.h @@ -128,6 +128,7 @@ struct amdxdna_client { struct iommu_sva *sva; int pasid; + struct mm_struct *mm; }; #define amdxdna_for_each_hwctx(client, hwctx_id, entry) \ From b79d31dce49b50c79620389b3639280802a86960 Mon Sep 17 00:00:00 2001 From: Lizhi Hou Date: Thu, 29 Jan 2026 16:32:55 -0800 Subject: [PATCH 3475/3902] accel/amdxdna: Stop job scheduling across aie2_release_resource() [ Upstream commit f1370241fe8045702bc9d0812b996791f0500f1b ] Running jobs on a hardware context while it is in the process of releasing resources can lead to use-after-free and crashes. Fix this by stopping job scheduling before calling aie2_release_resource() and restarting it after the release completes. Additionally, aie2_sched_job_run() now checks whether the hardware context is still active. Fixes: 4fd6ca90fc7f ("accel/amdxdna: Refactor hardware context destroy routine") Reviewed-by: Mario Limonciello (AMD) Signed-off-by: Lizhi Hou Link: https://patch.msgid.link/20260130003255.2083255-1-lizhi.hou@amd.com Signed-off-by: Sasha Levin --- drivers/accel/amdxdna/aie2_ctx.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/drivers/accel/amdxdna/aie2_ctx.c b/drivers/accel/amdxdna/aie2_ctx.c index 75246c481fa50e..c3cb24d96cee3b 100644 --- a/drivers/accel/amdxdna/aie2_ctx.c +++ b/drivers/accel/amdxdna/aie2_ctx.c @@ -317,6 +317,9 @@ aie2_sched_job_run(struct drm_sched_job *sched_job) struct dma_fence *fence; int ret; + if (hwctx->status != HWCTX_STAT_READY) + return NULL; + if (!mmget_not_zero(job->mm)) return ERR_PTR(-ESRCH); @@ -684,7 +687,10 @@ void aie2_hwctx_fini(struct amdxdna_hwctx *hwctx) aie2_hwctx_wait_for_idle(hwctx); /* Request fw to destroy hwctx and cancel the rest pending requests */ + drm_sched_stop(&hwctx->priv->sched, NULL); aie2_release_resource(hwctx); + hwctx->status = HWCTX_STAT_STOP; + drm_sched_start(&hwctx->priv->sched, 0); mutex_unlock(&xdna->dev_lock); drm_sched_entity_destroy(&hwctx->priv->entity); From 5a68d2c99c859e6e8e36fa4e32749abf6d1fb66a Mon Sep 17 00:00:00 2001 From: Zishun Yi Date: Fri, 30 Jan 2026 01:10:22 +0800 Subject: [PATCH 3476/3902] accel/amdxdna: Fix memory leak in amdxdna_ubuf_map [ Upstream commit 84dd57fb0359500092f1101409ca32091731490d ] The amdxdna_ubuf_map() function allocates memory for sg and internal sg table structures, but it fails to free them if subsequent operations (sg_alloc_table_from_pages or dma_map_sgtable) fail. Fixes: bd72d4acda10 ("accel/amdxdna: Support user space allocated buffer") Signed-off-by: Zishun Yi Reviewed-by: Lizhi Hou Reviewed-by: Min Ma Signed-off-by: Lizhi Hou Link: https://patch.msgid.link/20260129171022.68578-1-zishun.yi.dev@gmail.com Signed-off-by: Sasha Levin --- drivers/accel/amdxdna/amdxdna_ubuf.c | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/drivers/accel/amdxdna/amdxdna_ubuf.c b/drivers/accel/amdxdna/amdxdna_ubuf.c index 077b2261cf2a04..9e3b3b055caa86 100644 --- a/drivers/accel/amdxdna/amdxdna_ubuf.c +++ b/drivers/accel/amdxdna/amdxdna_ubuf.c @@ -34,15 +34,21 @@ static struct sg_table *amdxdna_ubuf_map(struct dma_buf_attachment *attach, ret = sg_alloc_table_from_pages(sg, ubuf->pages, ubuf->nr_pages, 0, ubuf->nr_pages << PAGE_SHIFT, GFP_KERNEL); if (ret) - return ERR_PTR(ret); + goto err_free_sg; if (ubuf->flags & AMDXDNA_UBUF_FLAG_MAP_DMA) { ret = dma_map_sgtable(attach->dev, sg, direction, 0); if (ret) - return ERR_PTR(ret); + goto err_free_table; } return sg; + +err_free_table: + sg_free_table(sg); +err_free_sg: + kfree(sg); + return ERR_PTR(ret); } static void amdxdna_ubuf_unmap(struct dma_buf_attachment *attach, From feb4bcfd405282de60aba321f13a1272b30c5af4 Mon Sep 17 00:00:00 2001 From: Ryan Lin Date: Fri, 30 Jan 2026 13:34:56 +0800 Subject: [PATCH 3477/3902] HID: intel-ish-hid: fix NULL-ptr-deref in ishtp_bus_remove_all_clients [ Upstream commit 56f7db581ee73af53cd512e00a6261a025bf1d58 ] During a warm reset flow, the cl->device pointer may be NULL if the reset occurs while clients are still being enumerated. Accessing cl->device->reference_count without a NULL check leads to a kernel panic. This issue was identified during multi-unit warm reboot stress clycles. Add a defensive NULL check for cl->device to ensure stability under such intensive testing conditions. KASAN: null-ptr-deref in range [0000000000000000-0000000000000007] Workqueue: ish_fw_update_wq fw_reset_work_fn Call Trace: ishtp_bus_remove_all_clients+0xbe/0x130 [intel_ishtp] ishtp_reset_handler+0x85/0x1a0 [intel_ishtp] fw_reset_work_fn+0x8a/0xc0 [intel_ish_ipc] Fixes: 3703f53b99e4a ("HID: intel_ish-hid: ISH Transport layer") Signed-off-by: Ryan Lin Signed-off-by: Jiri Kosina Signed-off-by: Sasha Levin --- drivers/hid/intel-ish-hid/ishtp/bus.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/hid/intel-ish-hid/ishtp/bus.c b/drivers/hid/intel-ish-hid/ishtp/bus.c index c3915f3a060ead..b890fbf97a75cd 100644 --- a/drivers/hid/intel-ish-hid/ishtp/bus.c +++ b/drivers/hid/intel-ish-hid/ishtp/bus.c @@ -730,7 +730,7 @@ void ishtp_bus_remove_all_clients(struct ishtp_device *ishtp_dev, spin_lock_irqsave(&ishtp_dev->cl_list_lock, flags); list_for_each_entry(cl, &ishtp_dev->cl_list, link) { cl->state = ISHTP_CL_DISCONNECTED; - if (warm_reset && cl->device->reference_count) + if (warm_reset && cl->device && cl->device->reference_count) continue; /* From 59be162822e56feaa100b122ba655fd9a550ecf9 Mon Sep 17 00:00:00 2001 From: Lizhi Hou Date: Tue, 3 Feb 2026 10:40:37 -0800 Subject: [PATCH 3478/3902] accel/amdxdna: Fix incorrect error code returned for failed chain command [ Upstream commit 750817a7c41de083ca5d73052e97bb7b67d7c394 ] The driver currently returns an incorrect error code when a chain command fails. In this case, ERT_CMD_STATE_ERROR is expected to be reported for failed chain commands. Fixes: aac243092b70 ("accel/amdxdna: Add command execution") Reviewed-by: Mario Limonciello (AMD) Reviewed-by: Maciej Falkowski Signed-off-by: Lizhi Hou Link: https://patch.msgid.link/20260203184037.2751889-1-lizhi.hou@amd.com Signed-off-by: Sasha Levin --- drivers/accel/amdxdna/aie2_ctx.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/accel/amdxdna/aie2_ctx.c b/drivers/accel/amdxdna/aie2_ctx.c index c3cb24d96cee3b..4610f491f0881c 100644 --- a/drivers/accel/amdxdna/aie2_ctx.c +++ b/drivers/accel/amdxdna/aie2_ctx.c @@ -294,7 +294,7 @@ aie2_sched_cmdlist_resp_handler(void *handle, void __iomem *data, size_t size) ret = -EINVAL; goto out; } - amdxdna_cmd_set_state(cmd_abo, fail_cmd_status); + amdxdna_cmd_set_state(cmd_abo, ERT_CMD_STATE_ERROR); if (amdxdna_cmd_get_op(cmd_abo) == ERT_CMD_CHAIN) { struct amdxdna_cmd_chain *cc = amdxdna_cmd_get_payload(cmd_abo, NULL); From 32606d55ffc1a1572dc65429cd0288aed067c896 Mon Sep 17 00:00:00 2001 From: Charles Keepax Date: Wed, 4 Feb 2026 12:59:38 +0000 Subject: [PATCH 3479/3902] ASoC: SDCA: Remove outdated todo comment [ Upstream commit b27b57f85fe3f0eca479556ac55bc9cbd1a5685a ] Support for -cn- properties has already been added, however the TODO comment noting this feature was required was not removed. Remove the now redundant comment. Fixes: 50a479527ef01 ("ASoC: SDCA: Add support for -cn- value properties") Signed-off-by: Charles Keepax Link: https://patch.msgid.link/20260204125944.1134011-2-ckeepax@opensource.cirrus.com Reviewed-by: Pierre-Louis Bossart Signed-off-by: Mark Brown Signed-off-by: Sasha Levin --- sound/soc/sdca/sdca_functions.c | 4 ---- 1 file changed, 4 deletions(-) diff --git a/sound/soc/sdca/sdca_functions.c b/sound/soc/sdca/sdca_functions.c index 19b12564f822ef..7adbf653bd8acc 100644 --- a/sound/soc/sdca/sdca_functions.c +++ b/sound/soc/sdca/sdca_functions.c @@ -854,10 +854,6 @@ static int find_sdca_control_value(struct device *dev, struct sdca_entity *entit return 0; } -/* - * TODO: Add support for -cn- properties, allowing different channels to have - * different defaults etc. - */ static int find_sdca_entity_control(struct device *dev, struct sdca_entity *entity, struct fwnode_handle *control_node, struct sdca_control *control) From 97b513d04e116123a085856022756b5ac8ba251a Mon Sep 17 00:00:00 2001 From: Charles Keepax Date: Mon, 20 Oct 2025 16:55:02 +0100 Subject: [PATCH 3480/3902] ASoC: SDCA: Force some SDCA Controls to be volatile [ Upstream commit c7b6c6b60594fd1efe35c61bc6a2176b25263ccc ] Whilst SDCA does specify an Access Mode for each Control, there is not a 1-to-1 mapping between that and ASoC's internal representation. Some registers require being treated as volatile from the hosts perspective even in their Access Mode is Read-Write. Add an explicit list of SDCA controls that should be forced volatile. Reviewed-by: Bard Liao Signed-off-by: Charles Keepax Reviewed-by: Pierre-Louis Bossart Link: https://patch.msgid.link/20251020155512.353774-10-ckeepax@opensource.cirrus.com Signed-off-by: Mark Brown Stable-dep-of: 9fad74b79e5f ("ASoC: SDCA: Handle volatile controls correctly") Signed-off-by: Sasha Levin --- include/sound/sdca_function.h | 1 + sound/soc/sdca/sdca_functions.c | 58 +++++++++++++++++++++++++++++++++ sound/soc/sdca/sdca_regmap.c | 9 +---- 3 files changed, 60 insertions(+), 8 deletions(-) diff --git a/include/sound/sdca_function.h b/include/sound/sdca_function.h index ea68856e4c8c44..86fd74146c33d8 100644 --- a/include/sound/sdca_function.h +++ b/include/sound/sdca_function.h @@ -771,6 +771,7 @@ struct sdca_control { u8 layers; bool deferrable; + bool is_volatile; bool has_default; bool has_fixed; }; diff --git a/sound/soc/sdca/sdca_functions.c b/sound/soc/sdca/sdca_functions.c index 7adbf653bd8acc..4417278e39bb1a 100644 --- a/sound/soc/sdca/sdca_functions.c +++ b/sound/soc/sdca/sdca_functions.c @@ -779,6 +779,62 @@ find_sdca_control_datatype(const struct sdca_entity *entity, } } +static bool find_sdca_control_volatile(const struct sdca_entity *entity, + const struct sdca_control *control) +{ + switch (control->mode) { + case SDCA_ACCESS_MODE_DC: + return false; + case SDCA_ACCESS_MODE_RO: + case SDCA_ACCESS_MODE_RW1S: + case SDCA_ACCESS_MODE_RW1C: + return true; + default: + break; + } + + switch (SDCA_CTL_TYPE(entity->type, control->sel)) { + case SDCA_CTL_TYPE_S(XU, FDL_CURRENTOWNER): + case SDCA_CTL_TYPE_S(XU, FDL_MESSAGEOFFSET): + case SDCA_CTL_TYPE_S(XU, FDL_MESSAGELENGTH): + case SDCA_CTL_TYPE_S(XU, FDL_STATUS): + case SDCA_CTL_TYPE_S(XU, FDL_HOST_REQUEST): + case SDCA_CTL_TYPE_S(SPE, AUTHTX_CURRENTOWNER): + case SDCA_CTL_TYPE_S(SPE, AUTHTX_MESSAGEOFFSET): + case SDCA_CTL_TYPE_S(SPE, AUTHTX_MESSAGELENGTH): + case SDCA_CTL_TYPE_S(SPE, AUTHRX_CURRENTOWNER): + case SDCA_CTL_TYPE_S(SPE, AUTHRX_MESSAGEOFFSET): + case SDCA_CTL_TYPE_S(SPE, AUTHRX_MESSAGELENGTH): + case SDCA_CTL_TYPE_S(MFPU, AE_CURRENTOWNER): + case SDCA_CTL_TYPE_S(MFPU, AE_MESSAGEOFFSET): + case SDCA_CTL_TYPE_S(MFPU, AE_MESSAGELENGTH): + case SDCA_CTL_TYPE_S(SMPU, HIST_CURRENTOWNER): + case SDCA_CTL_TYPE_S(SMPU, HIST_MESSAGEOFFSET): + case SDCA_CTL_TYPE_S(SMPU, HIST_MESSAGELENGTH): + case SDCA_CTL_TYPE_S(SMPU, DTODTX_CURRENTOWNER): + case SDCA_CTL_TYPE_S(SMPU, DTODTX_MESSAGEOFFSET): + case SDCA_CTL_TYPE_S(SMPU, DTODTX_MESSAGELENGTH): + case SDCA_CTL_TYPE_S(SMPU, DTODRX_CURRENTOWNER): + case SDCA_CTL_TYPE_S(SMPU, DTODRX_MESSAGEOFFSET): + case SDCA_CTL_TYPE_S(SMPU, DTODRX_MESSAGELENGTH): + case SDCA_CTL_TYPE_S(SAPU, DTODTX_CURRENTOWNER): + case SDCA_CTL_TYPE_S(SAPU, DTODTX_MESSAGEOFFSET): + case SDCA_CTL_TYPE_S(SAPU, DTODTX_MESSAGELENGTH): + case SDCA_CTL_TYPE_S(SAPU, DTODRX_CURRENTOWNER): + case SDCA_CTL_TYPE_S(SAPU, DTODRX_MESSAGEOFFSET): + case SDCA_CTL_TYPE_S(SAPU, DTODRX_MESSAGELENGTH): + case SDCA_CTL_TYPE_S(HIDE, HIDTX_CURRENTOWNER): + case SDCA_CTL_TYPE_S(HIDE, HIDTX_MESSAGEOFFSET): + case SDCA_CTL_TYPE_S(HIDE, HIDTX_MESSAGELENGTH): + case SDCA_CTL_TYPE_S(HIDE, HIDRX_CURRENTOWNER): + case SDCA_CTL_TYPE_S(HIDE, HIDRX_MESSAGEOFFSET): + case SDCA_CTL_TYPE_S(HIDE, HIDRX_MESSAGELENGTH): + return true; + default: + return false; + } +} + static int find_sdca_control_range(struct device *dev, struct fwnode_handle *control_node, struct sdca_control_range *range) @@ -927,6 +983,8 @@ static int find_sdca_entity_control(struct device *dev, struct sdca_entity *enti break; } + control->is_volatile = find_sdca_control_volatile(entity, control); + ret = find_sdca_control_range(dev, control_node, &control->range); if (ret) { dev_err(dev, "%s: control %#x: range missing: %d\n", diff --git a/sound/soc/sdca/sdca_regmap.c b/sound/soc/sdca/sdca_regmap.c index 72f893e00ff506..8fa138fca00ffb 100644 --- a/sound/soc/sdca/sdca_regmap.c +++ b/sound/soc/sdca/sdca_regmap.c @@ -147,14 +147,7 @@ bool sdca_regmap_volatile(struct sdca_function_data *function, unsigned int reg) if (!control) return false; - switch (control->mode) { - case SDCA_ACCESS_MODE_RO: - case SDCA_ACCESS_MODE_RW1S: - case SDCA_ACCESS_MODE_RW1C: - return true; - default: - return false; - } + return control->is_volatile; } EXPORT_SYMBOL_NS(sdca_regmap_volatile, "SND_SOC_SDCA"); From 81190b2e9d04bfa2cdade33c797aa228db251ec5 Mon Sep 17 00:00:00 2001 From: Charles Keepax Date: Wed, 4 Feb 2026 12:59:39 +0000 Subject: [PATCH 3481/3902] ASoC: SDCA: Handle volatile controls correctly [ Upstream commit 9fad74b79e5ff353fe156c4b685cceffa5afdb1d ] There are very few volatile controls in SDCA that are exported as ALSA controls, typically Detected Mode is the only common one. However, the current code does not resume the device when these ALSA controls are accessed, which will result in the read/write failing. Add a new wrapper specifically for volatile controls that will do the required pm_runtime operations before accessing the register. Fixes: c3ca24e3fcb6 ("ASoC: SDCA: Create ALSA controls from DisCo") Signed-off-by: Charles Keepax Link: https://patch.msgid.link/20260204125944.1134011-3-ckeepax@opensource.cirrus.com Reviewed-by: Pierre-Louis Bossart Signed-off-by: Mark Brown Signed-off-by: Sasha Levin --- sound/soc/sdca/sdca_asoc.c | 52 ++++++++++++++++++++++++++++++++++++-- 1 file changed, 50 insertions(+), 2 deletions(-) diff --git a/sound/soc/sdca/sdca_asoc.c b/sound/soc/sdca/sdca_asoc.c index 7e986870d48c5f..197a592ec2f166 100644 --- a/sound/soc/sdca/sdca_asoc.c +++ b/sound/soc/sdca/sdca_asoc.c @@ -16,6 +16,7 @@ #include #include #include +#include #include #include #include @@ -836,6 +837,48 @@ static int control_limit_kctl(struct device *dev, return 0; } +static int volatile_get_volsw(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *component = snd_kcontrol_chip(kcontrol); + struct device *dev = component->dev; + int ret; + + ret = pm_runtime_resume_and_get(dev); + if (ret < 0) { + dev_err(dev, "failed to resume reading %s: %d\n", + kcontrol->id.name, ret); + return ret; + } + + ret = snd_soc_get_volsw(kcontrol, ucontrol); + + pm_runtime_put(dev); + + return ret; +} + +static int volatile_put_volsw(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *component = snd_kcontrol_chip(kcontrol); + struct device *dev = component->dev; + int ret; + + ret = pm_runtime_resume_and_get(dev); + if (ret < 0) { + dev_err(dev, "failed to resume writing %s: %d\n", + kcontrol->id.name, ret); + return ret; + } + + ret = snd_soc_put_volsw(kcontrol, ucontrol); + + pm_runtime_put(dev); + + return ret; +} + static int populate_control(struct device *dev, struct sdca_function_data *function, struct sdca_entity *entity, @@ -890,8 +933,13 @@ static int populate_control(struct device *dev, (*kctl)->private_value = (unsigned long)mc; (*kctl)->iface = SNDRV_CTL_ELEM_IFACE_MIXER; (*kctl)->info = snd_soc_info_volsw; - (*kctl)->get = snd_soc_get_volsw; - (*kctl)->put = snd_soc_put_volsw; + if (control->is_volatile) { + (*kctl)->get = volatile_get_volsw; + (*kctl)->put = volatile_put_volsw; + } else { + (*kctl)->get = snd_soc_get_volsw; + (*kctl)->put = snd_soc_put_volsw; + } if (readonly_control(control)) (*kctl)->access = SNDRV_CTL_ELEM_ACCESS_READ; From 98c6a053b6d38db889a669c8da9ee629cdb8bd38 Mon Sep 17 00:00:00 2001 From: Francesco Lavra Date: Mon, 9 Feb 2026 10:50:01 +0100 Subject: [PATCH 3482/3902] spi: tools: Add include folder to .gitignore [ Upstream commit 5af56f30c4fcbade4a92f94dadfea517d1db9703 ] The Makefile for the SPI tools creates an include/linux/spi folder and some symlinks inside it. After running `make -C spi/tools`, this folder shows up as untracked in the git status. Add the above folder to the .gitignore file. Fixes: f325b73dc4db ("spi: tools: move to tools buildsystem") Signed-off-by: Francesco Lavra Link: https://patch.msgid.link/20260209095001.556495-1-flavra@baylibre.com Signed-off-by: Mark Brown Signed-off-by: Sasha Levin --- tools/spi/.gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/tools/spi/.gitignore b/tools/spi/.gitignore index 14ddba3d21957b..038261b34ed83c 100644 --- a/tools/spi/.gitignore +++ b/tools/spi/.gitignore @@ -1,3 +1,4 @@ # SPDX-License-Identifier: GPL-2.0-only spidev_fdx spidev_test +include/ From 14a38784e09aebc21207dc32fffa05247fc3dd64 Mon Sep 17 00:00:00 2001 From: Guenter Roeck Date: Sat, 7 Feb 2026 08:12:25 -0800 Subject: [PATCH 3483/3902] Revert "hwmon: (ibmpex) fix use-after-free in high/low store" [ Upstream commit 8bde3e395a85017f12af2b0ba5c3684f5af9c006 ] This reverts commit 6946c726c3f4c36f0f049e6f97e88c510b15f65d. Jean Delvare points out that the patch does not completely fix the reported problem, that it in fact introduces a (new) race condition, and that it may actually not be needed in the first place. Various AI reviews agree. Specific and relevant AI feedback: " This reordering sets the driver data to NULL before removing the sensor attributes in the loop below. ibmpex_show_sensor() retrieves this driver data via dev_get_drvdata() but does not check if it is NULL before dereferencing it to access data->sensors[]. If a userspace process reads a sensor file (like temp1_input) while this delete function is running, could it race with the dev_set_drvdata(..., NULL) call here and crash in ibmpex_show_sensor()? Would it be safer to keep the original order where device_remove_file() is called before clearing the driver data? device_remove_file() should wait for any active sysfs callbacks to complete, which might already prevent the use-after-free this patch intends to fix. " Revert the offending patch. If it can be shown that the originally reported alleged race condition does indeed exist, it can always be re-introduced with a complete fix. Reported-by: Jean Delvare Closes: https://lore.kernel.org/linux-hwmon/20260121095342.73e723cb@endymion/ Cc: Jean Delvare Cc: Junrui Luo Fixes: 6946c726c3f4 ("hwmon: (ibmpex) fix use-after-free in high/low store") Reviewed-by: Jean Delvare Signed-off-by: Guenter Roeck Signed-off-by: Sasha Levin --- drivers/hwmon/ibmpex.c | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/drivers/hwmon/ibmpex.c b/drivers/hwmon/ibmpex.c index 129f3a9e8fe965..228c5f6c6f3836 100644 --- a/drivers/hwmon/ibmpex.c +++ b/drivers/hwmon/ibmpex.c @@ -277,9 +277,6 @@ static ssize_t ibmpex_high_low_store(struct device *dev, { struct ibmpex_bmc_data *data = dev_get_drvdata(dev); - if (!data) - return -ENODEV; - ibmpex_reset_high_low_data(data); return count; @@ -511,9 +508,6 @@ static void ibmpex_bmc_delete(struct ibmpex_bmc_data *data) { int i, j; - hwmon_device_unregister(data->hwmon_dev); - dev_set_drvdata(data->bmc_device, NULL); - device_remove_file(data->bmc_device, &sensor_dev_attr_reset_high_low.dev_attr); device_remove_file(data->bmc_device, &dev_attr_name.attr); @@ -527,7 +521,8 @@ static void ibmpex_bmc_delete(struct ibmpex_bmc_data *data) } list_del(&data->list); - + dev_set_drvdata(data->bmc_device, NULL); + hwmon_device_unregister(data->hwmon_dev); ipmi_destroy_user(data->user); kfree(data->sensors); kfree(data); From d1878562a41fda13e486aaf95dd2dac5d43633d4 Mon Sep 17 00:00:00 2001 From: Carl Lee Date: Tue, 10 Feb 2026 15:26:34 +0800 Subject: [PATCH 3484/3902] hwmon: (pmbus/mpq8785) fix VOUT_MODE mismatch during identification [ Upstream commit 9e33c1dba22431bea9b2bf48adf56859e52fc7ec ] When MPQ8785 reports VOUT_MODE as VID mode, mpq8785_identify() configures the driver for direct mode. The subsequent pmbus_identify_common() check then fails due to a mismatch between the reported mode and the configured mode, causing device initialization to fail. Override the reported VOUT_MODE to direct mode to keep the driver configuration consistent with the reported mode and allow successful device initialization. This does not change how voltages are interpreted, but avoids a false identification failure caused by mismatched mode handling. Fixes: f20b4a931130c ("hwmon: Add driver for MPS MPQ8785 Synchronous Step-Down Converter") Signed-off-by: Carl Lee Link: https://lore.kernel.org/r/20260210-dt-bindings-hwmon-pmbus-mpq8785-add-mpq8786-support-v3-1-84636ccfe76f@amd.com Signed-off-by: Guenter Roeck Signed-off-by: Sasha Levin --- drivers/hwmon/pmbus/mpq8785.c | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/drivers/hwmon/pmbus/mpq8785.c b/drivers/hwmon/pmbus/mpq8785.c index 1f56aaf4dde807..87bd039c77b9b3 100644 --- a/drivers/hwmon/pmbus/mpq8785.c +++ b/drivers/hwmon/pmbus/mpq8785.c @@ -47,6 +47,33 @@ static int mpq8785_identify(struct i2c_client *client, return 0; }; +static int mpq8785_read_byte_data(struct i2c_client *client, int page, int reg) +{ + int ret; + + switch (reg) { + case PMBUS_VOUT_MODE: + ret = pmbus_read_byte_data(client, page, reg); + if (ret < 0) + return ret; + + if ((ret >> 5) == 1) { + /* + * The MPQ8785 chip reports VOUT_MODE as VID mode, but the driver + * treats VID as direct mode. Without this, identification would fail + * due to mode mismatch. + * This override ensures the reported mode matches the driver + * configuration, allowing successful initialization. + */ + return PB_VOUT_MODE_DIRECT; + } + + return ret; + default: + return -ENODATA; + } +} + static int mpm82504_read_word_data(struct i2c_client *client, int page, int phase, int reg) { @@ -129,6 +156,7 @@ static int mpq8785_probe(struct i2c_client *client) break; case mpq8785: info->identify = mpq8785_identify; + info->read_byte_data = mpq8785_read_byte_data; break; default: return -ENODEV; From 4e8e7080b5579ccfe779733cae5415a3aa6e3cf7 Mon Sep 17 00:00:00 2001 From: Haotian Zhang Date: Wed, 19 Nov 2025 10:33:08 +0800 Subject: [PATCH 3485/3902] PCI: mediatek: Fix IRQ domain leak when MSI allocation fails [ Upstream commit 7f0cdcddf8bef1c8c18f9be6708073fd3790a20f ] In mtk_pcie_init_irq_domain(), if mtk_pcie_allocate_msi_domains() fails after port->irq_domain has been successfully created via irq_domain_create_linear(), the function returns directly without cleaning up the allocated IRQ domain, resulting in a resource leak. Add irq_domain_remove() call in the error path to properly release the INTx IRQ domain before returning the error. Fixes: 43e6409db64d ("PCI: mediatek: Add MSI support for MT2712 and MT7622") Signed-off-by: Haotian Zhang Signed-off-by: Manivannan Sadhasivam Link: https://patch.msgid.link/20251119023308.476-1-vulab@iscas.ac.cn Signed-off-by: Sasha Levin --- drivers/pci/controller/pcie-mediatek.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/drivers/pci/controller/pcie-mediatek.c b/drivers/pci/controller/pcie-mediatek.c index 24cc30a2ab6c66..e0bf667c2b4c6c 100644 --- a/drivers/pci/controller/pcie-mediatek.c +++ b/drivers/pci/controller/pcie-mediatek.c @@ -575,8 +575,10 @@ static int mtk_pcie_init_irq_domain(struct mtk_pcie_port *port, if (IS_ENABLED(CONFIG_PCI_MSI)) { ret = mtk_pcie_allocate_msi_domains(port); - if (ret) + if (ret) { + irq_domain_remove(port->irq_domain); return ret; + } } return 0; From 004288f3b24ebb30cdfd8b379ff796829ac0bff1 Mon Sep 17 00:00:00 2001 From: Haotian Zhang Date: Fri, 19 Dec 2025 10:16:15 +0800 Subject: [PATCH 3486/3902] PCI: xilinx: Fix INTx IRQ domain leak in error paths [ Upstream commit f42b3c053b1554d66af6fe45bb1ef357464c0456 ] In xilinx_pcie_init_irq_domain(), if xilinx_allocate_msi_domains() fails after pcie->leg_domain has been successfully created via irq_domain_create_linear(), the function returns directly without cleaning up the allocated IRQ domain, resulting in a resource leak. In xilinx_free_msi_domains(), pcie->leg_domain is also neglected. Add irq_domain_remove() call in the error path to properly release the IRQ domain before returning the error. Also rename xilinx_free_msi_domains() to xilinx_free_irq_domains() and add the release of pcie->leg_domain to it. Fixes: 313b64c3ae52 ("PCI: xilinx: Convert to MSI domains") Suggested-by: Manivannan Sadhasivam Signed-off-by: Haotian Zhang Signed-off-by: Manivannan Sadhasivam Link: https://patch.msgid.link/20251219021615.965-1-vulab@iscas.ac.cn Signed-off-by: Sasha Levin --- drivers/pci/controller/pcie-xilinx.c | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/drivers/pci/controller/pcie-xilinx.c b/drivers/pci/controller/pcie-xilinx.c index 937ea6ae1ac486..4aa139abac16e8 100644 --- a/drivers/pci/controller/pcie-xilinx.c +++ b/drivers/pci/controller/pcie-xilinx.c @@ -302,9 +302,10 @@ static int xilinx_allocate_msi_domains(struct xilinx_pcie *pcie) return 0; } -static void xilinx_free_msi_domains(struct xilinx_pcie *pcie) +static void xilinx_free_irq_domains(struct xilinx_pcie *pcie) { irq_domain_remove(pcie->msi_domain); + irq_domain_remove(pcie->leg_domain); } /* INTx Functions */ @@ -480,8 +481,10 @@ static int xilinx_pcie_init_irq_domain(struct xilinx_pcie *pcie) phys_addr_t pa = ALIGN_DOWN(virt_to_phys(pcie), SZ_4K); ret = xilinx_allocate_msi_domains(pcie); - if (ret) + if (ret) { + irq_domain_remove(pcie->leg_domain); return ret; + } pcie_write(pcie, upper_32_bits(pa), XILINX_PCIE_REG_MSIBASE1); pcie_write(pcie, lower_32_bits(pa), XILINX_PCIE_REG_MSIBASE2); @@ -600,7 +603,7 @@ static int xilinx_pcie_probe(struct platform_device *pdev) err = pci_host_probe(bridge); if (err) - xilinx_free_msi_domains(pcie); + xilinx_free_irq_domains(pcie); return err; } From ed0ef3d0c92d8acf737f5b9f1f0d18235039ca08 Mon Sep 17 00:00:00 2001 From: Baruch Siach Date: Mon, 3 Nov 2025 09:28:30 +0200 Subject: [PATCH 3487/3902] Documentation: PCI: endpoint: Fix ntb/vntb copy & paste errors [ Upstream commit ad0c6da5be901f5c181490f683d22b416059bccb ] Fix copy & paste errors by changing the references from 'ntb' to 'vntb'. Fixes: 4ac8c8e52cd9 ("Documentation: PCI: Add specification for the PCI vNTB function device") Signed-off-by: Baruch Siach [mani: squashed the patches and fixed more errors] Signed-off-by: Manivannan Sadhasivam Signed-off-by: Bjorn Helgaas Reviewed-by: Frank Li Link: https://patch.msgid.link/b51c2a69ffdbfa2c359f5cf33f3ad2acc3db87e4.1762154911.git.baruch@tkos.co.il Signed-off-by: Sasha Levin --- Documentation/PCI/endpoint/pci-vntb-howto.rst | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/Documentation/PCI/endpoint/pci-vntb-howto.rst b/Documentation/PCI/endpoint/pci-vntb-howto.rst index 9a7a2f0a68498e..3679f5c3025494 100644 --- a/Documentation/PCI/endpoint/pci-vntb-howto.rst +++ b/Documentation/PCI/endpoint/pci-vntb-howto.rst @@ -52,14 +52,14 @@ pci-epf-vntb device, the following commands can be used:: # cd /sys/kernel/config/pci_ep/ # mkdir functions/pci_epf_vntb/func1 -The "mkdir func1" above creates the pci-epf-ntb function device that will +The "mkdir func1" above creates the pci-epf-vntb function device that will be probed by pci_epf_vntb driver. The PCI endpoint framework populates the directory with the following configurable fields:: - # ls functions/pci_epf_ntb/func1 - baseclass_code deviceid msi_interrupts pci-epf-ntb.0 + # ls functions/pci_epf_vntb/func1 + baseclass_code deviceid msi_interrupts pci-epf-vntb.0 progif_code secondary subsys_id vendorid cache_line_size interrupt_pin msix_interrupts primary revid subclass_code subsys_vendor_id @@ -111,13 +111,13 @@ A sample configuration for virtual NTB driver for virtual PCI bus:: # echo 0x080A > functions/pci_epf_vntb/func1/pci_epf_vntb.0/vntb_pid # echo 0x10 > functions/pci_epf_vntb/func1/pci_epf_vntb.0/vbus_number -Binding pci-epf-ntb Device to EP Controller +Binding pci-epf-vntb Device to EP Controller -------------------------------------------- NTB function device should be attached to PCI endpoint controllers connected to the host. - # ln -s controllers/5f010000.pcie_ep functions/pci-epf-ntb/func1/primary + # ln -s controllers/5f010000.pcie_ep functions/pci_epf_vntb/func1/primary Once the above step is completed, the PCI endpoint controllers are ready to establish a link with the host. @@ -139,7 +139,7 @@ lspci Output at Host side ------------------------- Note that the devices listed here correspond to the values populated in -"Creating pci-epf-ntb Device" section above:: +"Creating pci-epf-vntb Device" section above:: # lspci 00:00.0 PCI bridge: Freescale Semiconductor Inc Device 0000 (rev 01) @@ -152,7 +152,7 @@ lspci Output at EP Side / Virtual PCI bus ----------------------------------------- Note that the devices listed here correspond to the values populated in -"Creating pci-epf-ntb Device" section above:: +"Creating pci-epf-vntb Device" section above:: # lspci 10:00.0 Unassigned class [ffff]: Dawicontrol Computersysteme GmbH Device 1234 (rev ff) From d072c2c82322e9734f22aa8d2562ac321eec90f4 Mon Sep 17 00:00:00 2001 From: Brian Norris Date: Fri, 3 Oct 2025 15:40:09 -0700 Subject: [PATCH 3488/3902] PCI/PM: Avoid redundant delays on D3hot->D3cold [ Upstream commit 4d982084507d663df160546c4c48066a8887ed89 ] When transitioning to D3cold, __pci_set_power_state() first transitions to D3hot. If the device was already in D3hot, this adds excess work: (a) read/modify/write PMCSR; and (b) excess delay (pci_dev_d3_sleep()). For (b), we already performed the necessary delay on the previous D3hot entry; this was extra noticeable when evaluating runtime PM transition latency. Check whether we're already in the target state before continuing. Note that __pci_set_power_state() already does this same check for other state transitions, but D3cold is special because __pci_set_power_state() converts it to D3hot for the purposes of PMCSR. This seems to be an oversight in commit 0aacdc957401 ("PCI/PM: Clean up pci_set_low_power_state()"). Fixes: 0aacdc957401 ("PCI/PM: Clean up pci_set_low_power_state()") Signed-off-by: Brian Norris Signed-off-by: Brian Norris [bhelgaas: reverse test to match other "dev->current_state == state" cases] Signed-off-by: Bjorn Helgaas Link: https://patch.msgid.link/20251003154008.1.I7a21c240b30062c66471329567a96dceb6274358@changeid Signed-off-by: Sasha Levin --- drivers/pci/pci.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/drivers/pci/pci.c b/drivers/pci/pci.c index 2f0da5dbbba409..08a8c17ba4b124 100644 --- a/drivers/pci/pci.c +++ b/drivers/pci/pci.c @@ -1488,6 +1488,9 @@ static int pci_set_low_power_state(struct pci_dev *dev, pci_power_t state, bool || (state == PCI_D2 && !dev->d2_support)) return -EIO; + if (dev->current_state == state) + return 0; + pci_read_config_word(dev, dev->pm_cap + PCI_PM_CTRL, &pmcsr); if (PCI_POSSIBLE_ERROR(pmcsr)) { pci_err(dev, "Unable to change power state from %s to %s, device inaccessible\n", From 2108e783a7cf2410f2cb4399aaf0f8fd47baa11c Mon Sep 17 00:00:00 2001 From: Huang Chenming Date: Tue, 9 Dec 2025 08:27:33 +0530 Subject: [PATCH 3489/3902] wifi: cfg80211: Fix use_for flag update on BSS refresh MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [ Upstream commit 4073ea516106e5f98ed0476f89cdede8baa98d37 ] Userspace may fail to connect to certain BSS that were initially marked as unusable due to regulatory restrictions (use_for = 0, e.g., 6 GHz power type mismatch). Even after these restrictions are removed and the BSS becomes usable, connection attempts still fail. The issue occurs in cfg80211_update_known_bss() where the use_for flag is updated using bitwise AND (&=) instead of direct assignment. Once a BSS is marked with use_for = 0, the AND operation masks out any subsequent non-zero values, permanently keeping the flag at 0. This causes __cfg80211_get_bss(), invoked by nl80211_assoc_bss(), to fail the check "(bss->pub.use_for & use_for) != use_for", thereby blocking association. Replace the bitwise AND operation with direct assignment so the use_for flag accurately reflects the current BSS state. Fixes: d02a12b8e4bb ("wifi: cfg80211: add BSS usage reporting") Signed-off-by: Huang Chenming Link: https://patch.msgid.link/20251209025733.2098456-1-chenming.huang@oss.qualcomm.com Signed-off-by: Johannes Berg Signed-off-by: Sasha Levin --- net/wireless/scan.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/net/wireless/scan.c b/net/wireless/scan.c index 90a9187a6b135e..9a0c02c23dc56c 100644 --- a/net/wireless/scan.c +++ b/net/wireless/scan.c @@ -1959,7 +1959,7 @@ cfg80211_update_known_bss(struct cfg80211_registered_device *rdev, ether_addr_copy(known->parent_bssid, new->parent_bssid); known->pub.max_bssid_indicator = new->pub.max_bssid_indicator; known->pub.bssid_index = new->pub.bssid_index; - known->pub.use_for &= new->pub.use_for; + known->pub.use_for = new->pub.use_for; known->pub.cannot_use_reasons = new->pub.cannot_use_reasons; known->bss_source = new->bss_source; From e19cce88ec4c4877f4ff2469099b9cf23cc3e93e Mon Sep 17 00:00:00 2001 From: Hou Tao Date: Sat, 20 Dec 2025 12:04:34 +0800 Subject: [PATCH 3490/3902] PCI/P2PDMA: Release per-CPU pgmap ref when vm_insert_page() fails [ Upstream commit 6220694c52a5a04102b48109e4f24e958b559bd3 ] When vm_insert_page() fails in p2pmem_alloc_mmap(), p2pmem_alloc_mmap() doesn't invoke percpu_ref_put() to free the per-CPU ref of pgmap acquired after gen_pool_alloc_owner(), and memunmap_pages() will hang forever when trying to remove the PCI device. Fix it by adding the missed percpu_ref_put(). Fixes: 7e9c7ef83d78 ("PCI/P2PDMA: Allow userspace VMA allocations through sysfs") Signed-off-by: Hou Tao Signed-off-by: Bjorn Helgaas Reviewed-by: Logan Gunthorpe Reviewed-by: Alistair Popple Link: https://patch.msgid.link/20251220040446.274991-2-houtao@huaweicloud.com Signed-off-by: Sasha Levin --- drivers/pci/p2pdma.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/pci/p2pdma.c b/drivers/pci/p2pdma.c index 78e108e47254ab..5497ce0be7c5c8 100644 --- a/drivers/pci/p2pdma.c +++ b/drivers/pci/p2pdma.c @@ -152,6 +152,7 @@ static int p2pmem_alloc_mmap(struct file *filp, struct kobject *kobj, ret = vm_insert_page(vma, vaddr, page); if (ret) { gen_pool_free(p2pdma->pool, (uintptr_t)kaddr, len); + percpu_ref_put(ref); return ret; } percpu_ref_get(ref); From eb9aa9f8010465d927864f5a35bdc5604b0ff51a Mon Sep 17 00:00:00 2001 From: Hou Tao Date: Sat, 20 Dec 2025 12:04:35 +0800 Subject: [PATCH 3491/3902] PCI/P2PDMA: Fix p2pmem_alloc_mmap() warning condition [ Upstream commit cb500023a75246f60b79af9f7321d6e75330c5b5 ] Commit b7e282378773 has already changed the initial page refcount of p2pdma page from one to zero, however, in p2pmem_alloc_mmap() it uses "VM_WARN_ON_ONCE_PAGE(!page_ref_count(page))" to assert the initial page refcount should not be zero and the following will be reported when CONFIG_DEBUG_VM is enabled: page: refcount:0 mapcount:0 mapping:0000000000000000 index:0x0 pfn:0x380400000 flags: 0x20000000002000(reserved|node=0|zone=4) raw: 0020000000002000 ff1100015e3ab440 0000000000000000 0000000000000000 raw: 0000000000000000 0000000000000000 00000000ffffffff 0000000000000000 page dumped because: VM_WARN_ON_ONCE_PAGE(!page_ref_count(page)) ------------[ cut here ]------------ WARNING: CPU: 5 PID: 449 at drivers/pci/p2pdma.c:240 p2pmem_alloc_mmap+0x83a/0xa60 Fix by using "page_ref_count(page)" as the assertion condition. Fixes: b7e282378773 ("mm/mm_init: move p2pdma page refcount initialisation to p2pdma") Signed-off-by: Hou Tao Signed-off-by: Bjorn Helgaas Reviewed-by: Logan Gunthorpe Reviewed-by: Alistair Popple Link: https://patch.msgid.link/20251220040446.274991-3-houtao@huaweicloud.com Signed-off-by: Sasha Levin --- drivers/pci/p2pdma.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/pci/p2pdma.c b/drivers/pci/p2pdma.c index 5497ce0be7c5c8..12c69bb2b23265 100644 --- a/drivers/pci/p2pdma.c +++ b/drivers/pci/p2pdma.c @@ -147,7 +147,7 @@ static int p2pmem_alloc_mmap(struct file *filp, struct kobject *kobj, * we have just allocated the page no one else should be * using it. */ - VM_WARN_ON_ONCE_PAGE(!page_ref_count(page), page); + VM_WARN_ON_ONCE_PAGE(page_ref_count(page), page); set_page_count(page, 1); ret = vm_insert_page(vma, vaddr, page); if (ret) { From 1938bf90c1dd01b8a9979d4262ba0173b86a50bc Mon Sep 17 00:00:00 2001 From: Shuai Xue Date: Wed, 10 Dec 2025 21:29:07 +0800 Subject: [PATCH 3492/3902] Documentation: tracing: Add PCI tracepoint documentation MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [ Upstream commit 8236fc613d44e59f6736d6c3e9efffaf26ab7f00 ] The PCI tracing system provides tracepoints to monitor critical hardware events that can impact system performance and reliability. Add documentation about it. Signed-off-by: Shuai Xue [bhelgaas: squash fixes: https://lore.kernel.org/r/20260108013956.14351-2-bagasdotme@gmail.com https://lore.kernel.org/r/20260108013956.14351-3-bagasdotme@gmail.com] Signed-off-by: Bjorn Helgaas Reviewed-by: Ilpo Järvinen Link: https://patch.msgid.link/20251210132907.58799-4-xueshuai@linux.alibaba.com Signed-off-by: Sasha Levin --- Documentation/trace/events-pci.rst | 74 ++++++++++++++++++++++++++++++ Documentation/trace/index.rst | 1 + 2 files changed, 75 insertions(+) create mode 100644 Documentation/trace/events-pci.rst diff --git a/Documentation/trace/events-pci.rst b/Documentation/trace/events-pci.rst new file mode 100644 index 00000000000000..03ff4ad30ddfa1 --- /dev/null +++ b/Documentation/trace/events-pci.rst @@ -0,0 +1,74 @@ +.. SPDX-License-Identifier: GPL-2.0 + +=========================== +Subsystem Trace Points: PCI +=========================== + +Overview +======== +The PCI tracing system provides tracepoints to monitor critical hardware events +that can impact system performance and reliability. These events normally show +up here: + + /sys/kernel/tracing/events/pci + +Cf. include/trace/events/pci.h for the events definitions. + +Available Tracepoints +===================== + +pci_hp_event +------------ + +Monitors PCI hotplug events including card insertion/removal and link +state changes. +:: + + pci_hp_event "%s slot:%s, event:%s\n" + +**Event Types**: + +* ``LINK_UP`` - PCIe link established +* ``LINK_DOWN`` - PCIe link lost +* ``CARD_PRESENT`` - Card detected in slot +* ``CARD_NOT_PRESENT`` - Card removed from slot + +**Example Usage**:: + + # Enable the tracepoint + echo 1 > /sys/kernel/debug/tracing/events/pci/pci_hp_event/enable + + # Monitor events (the following output is generated when a device is hotplugged) + cat /sys/kernel/debug/tracing/trace_pipe + irq/51-pciehp-88 [001] ..... 1311.177459: pci_hp_event: 0000:00:02.0 slot:10, event:CARD_PRESENT + + irq/51-pciehp-88 [001] ..... 1311.177566: pci_hp_event: 0000:00:02.0 slot:10, event:LINK_UP + +pcie_link_event +--------------- + +Monitors PCIe link speed changes and provides detailed link status information. +:: + + pcie_link_event "%s type:%d, reason:%d, cur_bus_speed:%d, max_bus_speed:%d, width:%u, flit_mode:%u, status:%s\n" + +**Parameters**: + +* ``type`` - PCIe device type (4=Root Port, etc.) +* ``reason`` - Reason for link change: + + - ``0`` - Link retrain + - ``1`` - Bus enumeration + - ``2`` - Bandwidth notification enable + - ``3`` - Bandwidth notification IRQ + - ``4`` - Hotplug event + + +**Example Usage**:: + + # Enable the tracepoint + echo 1 > /sys/kernel/debug/tracing/events/pci/pcie_link_event/enable + + # Monitor events (the following output is generated when a device is hotplugged) + cat /sys/kernel/debug/tracing/trace_pipe + irq/51-pciehp-88 [001] ..... 381.545386: pcie_link_event: 0000:00:02.0 type:4, reason:4, cur_bus_speed:20, max_bus_speed:23, width:1, flit_mode:0, status:DLLLA diff --git a/Documentation/trace/index.rst b/Documentation/trace/index.rst index b4a429dc4f7ad6..0a40bfabcf19bf 100644 --- a/Documentation/trace/index.rst +++ b/Documentation/trace/index.rst @@ -54,6 +54,7 @@ applications. events-power events-nmi events-msr + events-pci boottime-trace histogram histogram-design From ef42c53b7a2848ca15e46b134a332a06ae00a511 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A5kon=20Bugge?= Date: Wed, 12 Nov 2025 10:54:40 +0100 Subject: [PATCH 3493/3902] PCI: Do not attempt to set ExtTag for VFs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [ Upstream commit 73711730a1128d91ebca1a6994ceeb18f36cb0cd ] The bit for enabling extended tags is Reserved and Preserved (RsvdP) for VFs, according to PCIe r7.0 section 7.5.3.4 table 7.21. Hence, bail out early from pci_configure_extended_tags() if the device is a VF. Otherwise, we may see incorrect log messages such as: kernel: pci 0000:af:00.2: enabling Extended Tags (af:00.2 is a VF) Fixes: 60db3a4d8cc9 ("PCI: Enable PCIe Extended Tags if supported") Signed-off-by: Håkon Bugge Signed-off-by: Bjorn Helgaas Reviewed-by: Zhu Yanjun Link: https://patch.msgid.link/20251112095442.1913258-1-haakon.bugge@oracle.com Signed-off-by: Sasha Levin --- drivers/pci/probe.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/drivers/pci/probe.c b/drivers/pci/probe.c index 9cd032dff31e57..8cf573fca3078b 100644 --- a/drivers/pci/probe.c +++ b/drivers/pci/probe.c @@ -2251,7 +2251,8 @@ int pci_configure_extended_tags(struct pci_dev *dev, void *ign) u16 ctl; int ret; - if (!pci_is_pcie(dev)) + /* PCI_EXP_DEVCTL_EXT_TAG is RsvdP in VFs */ + if (!pci_is_pcie(dev) || dev->is_virtfn) return 0; ret = pcie_capability_read_dword(dev, PCI_EXP_DEVCAP, &cap); From 1af76378577e030fe5fb26000da4442161d907f8 Mon Sep 17 00:00:00 2001 From: Inochi Amaoto Date: Fri, 9 Jan 2026 12:07:53 +0800 Subject: [PATCH 3494/3902] PCI: sophgo: Disable L0s and L1 on Sophgo 2044 PCIe Root Ports [ Upstream commit 613f3255a35a95f52575dd8c60b7ac9d711639ce ] Sophgo 2044 Root Ports advertise L0 and L1 capabilities without supporting them. Since commit f3ac2ff14834 ("PCI/ASPM: Enable all ClockPM and ASPM states for devicetree platforms") force enabled ASPM on all device tree platforms, the issue became evident and the SG2044 Root Port started breaking. Hence, disable the L0s and L1 capabilities in the LINKCAP register for the SG2044 Root Ports, so that these states won't get enabled. Fixes: 467d9c0348d6 ("PCI: dwc: Add Sophgo SG2044 PCIe controller driver in Root Complex mode") Signed-off-by: Inochi Amaoto [mani: reworded description and corrected fixes tag] Signed-off-by: Manivannan Sadhasivam Tested-by: Han Gao Link: https://patch.msgid.link/20260109040756.731169-1-inochiama@gmail.com Signed-off-by: Sasha Levin --- drivers/pci/controller/dwc/pcie-sophgo.c | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/drivers/pci/controller/dwc/pcie-sophgo.c b/drivers/pci/controller/dwc/pcie-sophgo.c index ad4baaa34ffa1d..044088898819e5 100644 --- a/drivers/pci/controller/dwc/pcie-sophgo.c +++ b/drivers/pci/controller/dwc/pcie-sophgo.c @@ -161,6 +161,22 @@ static void sophgo_pcie_msi_enable(struct dw_pcie_rp *pp) raw_spin_unlock_irqrestore(&pp->lock, flags); } +static void sophgo_pcie_disable_l0s_l1(struct dw_pcie_rp *pp) +{ + struct dw_pcie *pci = to_dw_pcie_from_pp(pp); + u32 offset, val; + + offset = dw_pcie_find_capability(pci, PCI_CAP_ID_EXP); + + dw_pcie_dbi_ro_wr_en(pci); + + val = dw_pcie_readl_dbi(pci, PCI_EXP_LNKCAP + offset); + val &= ~(PCI_EXP_LNKCAP_ASPM_L0S | PCI_EXP_LNKCAP_ASPM_L1); + dw_pcie_writel_dbi(pci, PCI_EXP_LNKCAP + offset, val); + + dw_pcie_dbi_ro_wr_dis(pci); +} + static int sophgo_pcie_host_init(struct dw_pcie_rp *pp) { int irq; @@ -171,6 +187,8 @@ static int sophgo_pcie_host_init(struct dw_pcie_rp *pp) irq_set_chained_handler_and_data(irq, sophgo_pcie_intx_handler, pp); + sophgo_pcie_disable_l0s_l1(pp); + sophgo_pcie_msi_enable(pp); return 0; From 5d0a2a2ce94b5af78e8b9bbb41a1ac67d04379c4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Uwe=20Kleine-K=C3=B6nig?= Date: Tue, 2 Dec 2025 16:13:49 +0100 Subject: [PATCH 3495/3902] PCI/portdrv: Fix potential resource leak MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [ Upstream commit 01464a3fdf91c041a381d93a1b6fefbdb819a46f ] pcie_port_probe_service() unconditionally calls get_device() (unless it fails). So drop that reference also unconditionally as it's fine for a PCIe driver to not have a remove callback. Fixes: 1da177e4c3f4 ("Linux-2.6.12-rc2") Signed-off-by: Uwe Kleine-König Signed-off-by: Bjorn Helgaas Reviewed-by: Ilpo Järvinen Reviewed-by: Jonathan Cameron Link: https://patch.msgid.link/e1c68c3b3f1af8427e98ca5e2c79f8bf0ebe2ce4.1764688034.git.u.kleine-koenig@baylibre.com Signed-off-by: Sasha Levin --- drivers/pci/pcie/portdrv.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/drivers/pci/pcie/portdrv.c b/drivers/pci/pcie/portdrv.c index 38a41ccf79b9a3..a0991da4821363 100644 --- a/drivers/pci/pcie/portdrv.c +++ b/drivers/pci/pcie/portdrv.c @@ -557,10 +557,10 @@ static int pcie_port_remove_service(struct device *dev) pciedev = to_pcie_device(dev); driver = to_service_driver(dev->driver); - if (driver && driver->remove) { + if (driver && driver->remove) driver->remove(pciedev); - put_device(dev); - } + + put_device(dev); return 0; } From 7a3385e97af2b6f485fef11e82d8c29adee4be93 Mon Sep 17 00:00:00 2001 From: Mikulas Patocka Date: Thu, 8 Jan 2026 20:55:08 +0100 Subject: [PATCH 3496/3902] dm: fix unlocked test for dm_suspended_md [ Upstream commit 24c405fdbe215c45e57bba672cc42859038491ee ] The function dm_blk_report_zones tests if the device is suspended with the "dm_suspended_md" call. However, this function is called without holding any locks, so the device may be suspended just after it. Move the call to dm_suspended_md after dm_get_live_table, so that the device can't be suspended after the suspended state was tested. Signed-off-by: Mikulas Patocka Fixes: 37f53a2c60d0 ("dm: fix dm_blk_report_zones") Reviewed-by: Benjamin Marzinski Signed-off-by: Sasha Levin --- drivers/md/dm-zone.c | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/drivers/md/dm-zone.c b/drivers/md/dm-zone.c index 78e17dd4d01b82..ba39c8313f32b4 100644 --- a/drivers/md/dm-zone.c +++ b/drivers/md/dm-zone.c @@ -66,11 +66,13 @@ int dm_blk_report_zones(struct gendisk *disk, sector_t sector, * Zone revalidation during __bind() is in progress, but this * call is from a different process */ - if (dm_suspended_md(md)) - return -EAGAIN; - map = dm_get_live_table(md, &srcu_idx); put_table = true; + + if (dm_suspended_md(md)) { + ret = -EAGAIN; + goto do_put_table; + } } else { /* Zone revalidation during __bind() */ map = zone_revalidate_map; @@ -80,6 +82,7 @@ int dm_blk_report_zones(struct gendisk *disk, sector_t sector, ret = dm_blk_do_report_zones(md, map, sector, nr_zones, cb, data); +do_put_table: if (put_table) dm_put_live_table(md, srcu_idx); From aeddfb00bb83c50a8bdfa5139f8c2c87e8d85049 Mon Sep 17 00:00:00 2001 From: Mikulas Patocka Date: Thu, 8 Jan 2026 20:56:20 +0100 Subject: [PATCH 3497/3902] dm: use READ_ONCE in dm_blk_report_zones [ Upstream commit e9f5a55b70ae6187ab64ef2d1232ae2738e31d1f ] The functon dm_blk_report_zones reads md->zone_revalidate_map, however it may change while the function is running. Use READ_ONCE. Signed-off-by: Mikulas Patocka Fixes: 37f53a2c60d0 ("dm: fix dm_blk_report_zones") Reviewed-by: Benjamin Marzinski Signed-off-by: Sasha Levin --- drivers/md/dm-zone.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/md/dm-zone.c b/drivers/md/dm-zone.c index ba39c8313f32b4..f4950b5f766d3b 100644 --- a/drivers/md/dm-zone.c +++ b/drivers/md/dm-zone.c @@ -56,7 +56,7 @@ int dm_blk_report_zones(struct gendisk *disk, sector_t sector, { struct mapped_device *md = disk->private_data; struct dm_table *map; - struct dm_table *zone_revalidate_map = md->zone_revalidate_map; + struct dm_table *zone_revalidate_map = READ_ONCE(md->zone_revalidate_map); int srcu_idx, ret = -EIO; bool put_table = false; From 5b1f6531d4a12ecdfae5f73fab3fef1b6dbda1c4 Mon Sep 17 00:00:00 2001 From: Aadityarangan Shridhar Iyengar Date: Sun, 11 Jan 2026 22:06:50 +0530 Subject: [PATCH 3498/3902] PCI/PTM: Fix pcie_ptm_create_debugfs() memory leak [ Upstream commit 62171369cf17794ddd88f602c2c84d008ecafcff ] In pcie_ptm_create_debugfs(), if devm_kasprintf() fails after successfully allocating ptm_debugfs with kzalloc(), the function returns without freeing the allocated memory, resulting in a memory leak. Free ptm_debugfs before returning in the devm_kasprintf() error path and in pcie_ptm_destroy_debugfs(). Fixes: 132833405e61 ("PCI: Add debugfs support for exposing PTM context") Signed-off-by: Aadityarangan Shridhar Iyengar [bhelgaas: squash additional fix from Mani: https://lore.kernel.org/r/pdp4xc4d5ee3e547mmdro5riui3mclduqdl7j6iclfbozo2a4c@7m3qdm6yrhuv] Signed-off-by: Bjorn Helgaas Reviewed-by: Manivannan Sadhasivam Link: https://patch.msgid.link/20260111163650.33168-1-adiyenga@cisco.com Signed-off-by: Sasha Levin --- drivers/pci/pcie/ptm.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/drivers/pci/pcie/ptm.c b/drivers/pci/pcie/ptm.c index 65e4b008be00df..41d370f082ee41 100644 --- a/drivers/pci/pcie/ptm.c +++ b/drivers/pci/pcie/ptm.c @@ -519,8 +519,10 @@ struct pci_ptm_debugfs *pcie_ptm_create_debugfs(struct device *dev, void *pdata, return NULL; dirname = devm_kasprintf(dev, GFP_KERNEL, "pcie_ptm_%s", dev_name(dev)); - if (!dirname) + if (!dirname) { + kfree(ptm_debugfs); return NULL; + } ptm_debugfs->debugfs = debugfs_create_dir(dirname, NULL); ptm_debugfs->pdata = pdata; @@ -551,6 +553,7 @@ void pcie_ptm_destroy_debugfs(struct pci_ptm_debugfs *ptm_debugfs) mutex_destroy(&ptm_debugfs->lock); debugfs_remove_recursive(ptm_debugfs->debugfs); + kfree(ptm_debugfs); } EXPORT_SYMBOL_GPL(pcie_ptm_destroy_debugfs); #endif From a44220e4eba357f62a3a95ac7c22eadd77568e1c Mon Sep 17 00:00:00 2001 From: Alistair Popple Date: Mon, 12 Jan 2026 11:54:40 +1100 Subject: [PATCH 3499/3902] PCI/P2PDMA: Reset page reference count when page mapping fails [ Upstream commit 83014d82a1100abc89f7712ad67c3e5accaddc43 ] When mapping a p2pdma page the page reference count is initialised to 1 prior to calling vm_insert_page(). This is to avoid vm_insert_page() warning if the page refcount is zero. Prior to setting the page count there is a check to ensure the page is currently free (ie. has a zero reference count). However vm_insert_page() can fail. In this case the pages are freed back to the genalloc pool, but that does not reset the page refcount. So a future allocation of the same page will see the elevated page refcount from the previous set_page_count() call triggering the VM_WARN_ON_ONCE_PAGE checking that the page is free. Fix this by resetting the page refcount to zero using set_page_count(). Note that put_page() is not used because that would result in freeing the page twice due to implicitly calling p2pdma_folio_free(). Fixes: b7e282378773 ("mm/mm_init: move p2pdma page refcount initialisation to p2pdma") Signed-off-by: Alistair Popple Signed-off-by: Bjorn Helgaas Reviewed-by: Logan Gunthorpe Acked-by: Balbir Singh Link: https://patch.msgid.link/20260112005440.998543-1-apopple@nvidia.com Signed-off-by: Sasha Levin --- drivers/pci/p2pdma.c | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/drivers/pci/p2pdma.c b/drivers/pci/p2pdma.c index 12c69bb2b23265..49e395eb013d38 100644 --- a/drivers/pci/p2pdma.c +++ b/drivers/pci/p2pdma.c @@ -152,6 +152,13 @@ static int p2pmem_alloc_mmap(struct file *filp, struct kobject *kobj, ret = vm_insert_page(vma, vaddr, page); if (ret) { gen_pool_free(p2pdma->pool, (uintptr_t)kaddr, len); + + /* + * Reset the page count. We don't use put_page() + * because we don't want to trigger the + * p2pdma_folio_free() path. + */ + set_page_count(page, 0); percpu_ref_put(ref); return ret; } From 5a0b892ac90d03038f0463d938992a47c7dfacd2 Mon Sep 17 00:00:00 2001 From: Rosen Penev Date: Fri, 1 Aug 2025 17:04:32 -0700 Subject: [PATCH 3500/3902] wifi: ath9k: add OF dependency to AHB MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [ Upstream commit 125e7b31f041cc0a4ede1e42bef69915f0a63a35 ] The conversion to OF missed adding a Kconfig dependency. Fixes: 2fa490c0d759 ("wifi: ath9k: ahb: replace id_table with of") Signed-off-by: Rosen Penev Acked-by: Toke Høiland-Jørgensen Link: https://patch.msgid.link/20250802000432.3079550-1-rosenp@gmail.com Signed-off-by: Jeff Johnson Signed-off-by: Sasha Levin --- drivers/net/wireless/ath/ath9k/Kconfig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/net/wireless/ath/ath9k/Kconfig b/drivers/net/wireless/ath/ath9k/Kconfig index 0c47be06c153be..47d570a5ca6a1c 100644 --- a/drivers/net/wireless/ath/ath9k/Kconfig +++ b/drivers/net/wireless/ath/ath9k/Kconfig @@ -47,7 +47,7 @@ config ATH9K_PCI config ATH9K_AHB bool "Atheros ath9k AHB bus support" - depends on ATH9K + depends on ATH9K && OF default n help This option enables the AHB bus support in ath9k. From 7379837c3f9efa576dc2d716ebfaa3a113b3112f Mon Sep 17 00:00:00 2001 From: Baochen Qiang Date: Mon, 3 Nov 2025 10:44:49 +0800 Subject: [PATCH 3501/3902] wifi: ath12k: do WoW offloads only on primary link [ Upstream commit e62102ac9b773bdb08475aa9ca24dea61ae98708 ] In case of multi-link connection, WCN7850 firmware crashes due to WoW offloads enabled on both primary and secondary links. Change to do it only on primary link to fix it. Tested-on: WCN7850 hw2.0 PCI WLAN.HMT.1.1.c5-00284-QCAHMTSWPL_V1.0_V2.0_SILICONZ-1 Fixes: 32f7b19668bd ("wifi: ath12k: support MLO as well if single_chip_mlo_support flag is set") Signed-off-by: Baochen Qiang Reviewed-by: Vasanthakumar Thiagarajan Link: https://patch.msgid.link/20251103-ath12-primary-link-wow-v1-1-3cf523dc09f0@oss.qualcomm.com Signed-off-by: Jeff Johnson Signed-off-by: Sasha Levin --- drivers/net/wireless/ath/ath12k/wow.c | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/drivers/net/wireless/ath/ath12k/wow.c b/drivers/net/wireless/ath/ath12k/wow.c index e8481626f19404..c78aa95d497911 100644 --- a/drivers/net/wireless/ath/ath12k/wow.c +++ b/drivers/net/wireless/ath/ath12k/wow.c @@ -135,6 +135,9 @@ static int ath12k_wow_cleanup(struct ath12k *ar) lockdep_assert_wiphy(ath12k_ar_to_hw(ar)->wiphy); list_for_each_entry(arvif, &ar->arvifs, list) { + if (arvif != &arvif->ahvif->deflink) + continue; + ret = ath12k_wow_vif_cleanup(arvif); if (ret) { ath12k_warn(ar->ab, "failed to clean wow wakeups on vdev %i: %d\n", @@ -479,8 +482,12 @@ static int ath12k_wow_set_wakeups(struct ath12k *ar, lockdep_assert_wiphy(ath12k_ar_to_hw(ar)->wiphy); list_for_each_entry(arvif, &ar->arvifs, list) { + if (arvif != &arvif->ahvif->deflink) + continue; + if (ath12k_wow_is_p2p_vdev(arvif->ahvif)) continue; + ret = ath12k_wow_vif_set_wakeups(arvif, wowlan); if (ret) { ath12k_warn(ar->ab, "failed to set wow wakeups on vdev %i: %d\n", @@ -538,6 +545,9 @@ static int ath12k_wow_nlo_cleanup(struct ath12k *ar) lockdep_assert_wiphy(ath12k_ar_to_hw(ar)->wiphy); list_for_each_entry(arvif, &ar->arvifs, list) { + if (arvif != &arvif->ahvif->deflink) + continue; + if (ath12k_wow_is_p2p_vdev(arvif->ahvif)) continue; @@ -745,6 +755,9 @@ static int ath12k_wow_arp_ns_offload(struct ath12k *ar, bool enable) list_for_each_entry(arvif, &ar->arvifs, list) { ahvif = arvif->ahvif; + if (arvif != &ahvif->deflink) + continue; + if (ahvif->vdev_type != WMI_VDEV_TYPE_STA) continue; @@ -776,6 +789,9 @@ static int ath12k_gtk_rekey_offload(struct ath12k *ar, bool enable) lockdep_assert_wiphy(ath12k_ar_to_hw(ar)->wiphy); list_for_each_entry(arvif, &ar->arvifs, list) { + if (arvif != &arvif->ahvif->deflink) + continue; + if (arvif->ahvif->vdev_type != WMI_VDEV_TYPE_STA || !arvif->is_up || !arvif->rekey_data.enable_offload) From 02bb1500f1479750e6557c8044f6a2d7e9d30c12 Mon Sep 17 00:00:00 2001 From: Abhishek Bapat Date: Thu, 15 Jan 2026 21:31:03 +0000 Subject: [PATCH 3502/3902] quota: fix livelock between quotactl and freeze_super [ Upstream commit 77449e453dfc006ad738dec55374c4cbc056fd39 ] When a filesystem is frozen, quotactl_block() enters a retry loop waiting for the filesystem to thaw. It acquires s_umount, checks the freeze state, drops s_umount and uses sb_start_write() - sb_end_write() pair to wait for the unfreeze. However, this retry loop can trigger a livelock issue, specifically on kernels with preemption disabled. The mechanism is as follows: 1. freeze_super() sets SB_FREEZE_WRITE and calls sb_wait_write(). 2. sb_wait_write() calls percpu_down_write(), which initiates synchronize_rcu(). 3. Simultaneously, quotactl_block() spins in its retry loop, immediately executing the sb_start_write() - sb_end_write() pair. 4. Because the kernel is non-preemptible and the loop contains no scheduling points, quotactl_block() never yields the CPU. This prevents that CPU from reaching an RCU quiescent state. 5. synchronize_rcu() in the freezer thread waits indefinitely for the quotactl_block() CPU to report a quiescent state. 6. quotactl_block() spins indefinitely waiting for the freezer to advance, which it cannot do as it is blocked on the RCU sync. This results in a hang of the freezer process and 100% CPU usage by the quota process. While this can occur intermittently on multi-core systems, it is reliably reproducing on a node with the following script, running both the freezer and the quota toggle on the same CPU: # mkfs.ext4 -O quota /dev/sda 2g && mkdir a_mount # mount /dev/sda -o quota,usrquota,grpquota a_mount # taskset -c 3 bash -c "while true; do xfs_freeze -f a_mount; \ xfs_freeze -u a_mount; done" & # taskset -c 3 bash -c "while true; do quotaon a_mount; \ quotaoff a_mount; done" & Adding cond_resched() to the retry loop fixes the issue. It acts as an RCU quiescent state, allowing synchronize_rcu() in percpu_down_write() to complete. Fixes: 576215cffdef ("fs: Drop wait_unfrozen wait queue") Signed-off-by: Abhishek Bapat Link: https://patch.msgid.link/20260115213103.1089129-1-abhishekbapat@google.com Signed-off-by: Jan Kara Signed-off-by: Sasha Levin --- fs/quota/quota.c | 1 + 1 file changed, 1 insertion(+) diff --git a/fs/quota/quota.c b/fs/quota/quota.c index 7c2b75a4448528..de4379a9c79208 100644 --- a/fs/quota/quota.c +++ b/fs/quota/quota.c @@ -899,6 +899,7 @@ static struct super_block *quotactl_block(const char __user *special, int cmd) sb_start_write(sb); sb_end_write(sb); put_super(sb); + cond_resched(); goto retry; } return sb; From c1116859a7f462233b500bb95e0c85fd6210b08a Mon Sep 17 00:00:00 2001 From: Jian Zhang Date: Thu, 8 Jan 2026 18:18:29 +0800 Subject: [PATCH 3503/3902] net: mctp-i2c: fix duplicate reception of old data [ Upstream commit ae4744e173fadd092c43eda4ca92dcb74645225a ] The MCTP I2C slave callback did not handle I2C_SLAVE_READ_REQUESTED events. As a result, i2c read event will trigger repeated reception of old data, reset rx_pos when a read request is received. Signed-off-by: Jian Zhang Link: https://patch.msgid.link/20260108101829.1140448-1-zhangjian.3032@bytedance.com Signed-off-by: Jakub Kicinski Stable-dep-of: 2a14e91b6d76 ("mctp i2c: initialise event handler read bytes") Signed-off-by: Sasha Levin --- drivers/net/mctp/mctp-i2c.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/drivers/net/mctp/mctp-i2c.c b/drivers/net/mctp/mctp-i2c.c index f782d93f826efc..ecda1cc36391ce 100644 --- a/drivers/net/mctp/mctp-i2c.c +++ b/drivers/net/mctp/mctp-i2c.c @@ -242,6 +242,9 @@ static int mctp_i2c_slave_cb(struct i2c_client *client, return 0; switch (event) { + case I2C_SLAVE_READ_REQUESTED: + midev->rx_pos = 0; + break; case I2C_SLAVE_WRITE_RECEIVED: if (midev->rx_pos < MCTP_I2C_BUFSZ) { midev->rx_buffer[midev->rx_pos] = *val; @@ -279,6 +282,9 @@ static int mctp_i2c_recv(struct mctp_i2c_dev *midev) size_t recvlen; int status; + if (midev->rx_pos == 0) + return 0; + /* + 1 for the PEC */ if (midev->rx_pos < MCTP_I2C_MINLEN + 1) { ndev->stats.rx_length_errors++; From 6ff2ebfef75fbc57d937d8fbe738b967edf2d331 Mon Sep 17 00:00:00 2001 From: Matt Johnston Date: Tue, 13 Jan 2026 17:01:16 +0800 Subject: [PATCH 3504/3902] mctp i2c: initialise event handler read bytes [ Upstream commit 2a14e91b6d76639dac70ea170f4384c1ee3cb48d ] Set a 0xff value for i2c reads of an mctp-i2c device. Otherwise reads will return "val" from the i2c bus driver. For i2c-aspeed and i2c-npcm7xx that is a stack uninitialised u8. Tested with "i2ctransfer -y 1 r10@0x34" where 0x34 is a mctp-i2c instance, now it returns all 0xff. Fixes: f5b8abf9fc3d ("mctp i2c: MCTP I2C binding driver") Signed-off-by: Matt Johnston Link: https://patch.msgid.link/20260113-mctp-read-fix-v1-1-70c4b59c741c@codeconstruct.com.au Signed-off-by: Jakub Kicinski Signed-off-by: Sasha Levin --- drivers/net/mctp/mctp-i2c.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/drivers/net/mctp/mctp-i2c.c b/drivers/net/mctp/mctp-i2c.c index ecda1cc36391ce..8043b57bdf2509 100644 --- a/drivers/net/mctp/mctp-i2c.c +++ b/drivers/net/mctp/mctp-i2c.c @@ -243,7 +243,10 @@ static int mctp_i2c_slave_cb(struct i2c_client *client, switch (event) { case I2C_SLAVE_READ_REQUESTED: + case I2C_SLAVE_READ_PROCESSED: + /* MCTP I2C transport only uses writes */ midev->rx_pos = 0; + *val = 0xff; break; case I2C_SLAVE_WRITE_RECEIVED: if (midev->rx_pos < MCTP_I2C_BUFSZ) { From 59f10fa502ee7859d3c9b3b31ddf40ccde99810f Mon Sep 17 00:00:00 2001 From: Miri Korenblit Date: Wed, 7 Jan 2026 14:04:51 +0200 Subject: [PATCH 3505/3902] wifi: cfg80211: stop NAN and P2P in cfg80211_leave [ Upstream commit e1696c8bd0056bc1a5f7766f58ac333adc203e8a ] Seems that there is an assumption that this function should be called only for netdev interfaces, but it can also be called in suspend, or from nl80211_netlink_notify (indirectly). Note that the documentation of NL80211_ATTR_SOCKET_OWNER explicitly says that NAN interfaces would be destroyed as well in the nl80211_netlink_notify case. Fix this by also stopping P2P and NAN. Fixes: cb3b7d87652a ("cfg80211: add start / stop NAN commands") Reviewed-by: Johannes Berg Signed-off-by: Miri Korenblit Link: https://patch.msgid.link/20260107140430.dab142cbef0b.I290cc47836d56dd7e35012ce06bec36c6da688cd@changeid Signed-off-by: Johannes Berg Signed-off-by: Sasha Levin --- net/wireless/core.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/net/wireless/core.c b/net/wireless/core.c index 5e5c1bc380a897..87f083d9247a43 100644 --- a/net/wireless/core.c +++ b/net/wireless/core.c @@ -1400,8 +1400,10 @@ void cfg80211_leave(struct cfg80211_registered_device *rdev, cfg80211_leave_ocb(rdev, dev); break; case NL80211_IFTYPE_P2P_DEVICE: + cfg80211_stop_p2p_device(rdev, wdev); + break; case NL80211_IFTYPE_NAN: - /* cannot happen, has no netdev */ + cfg80211_stop_nan(rdev, wdev); break; case NL80211_IFTYPE_AP_VLAN: case NL80211_IFTYPE_MONITOR: From f1f9aea3729765e456ebdede35170ab906e52d44 Mon Sep 17 00:00:00 2001 From: Li Chen Date: Tue, 6 Jan 2026 20:06:21 +0800 Subject: [PATCH 3506/3902] ext4: fast commit: make s_fc_lock reclaim-safe [ Upstream commit 491f2927ae097e2d405afe0b3fe841931ab8aad2 ] s_fc_lock can be acquired from inode eviction and thus is reclaim unsafe. Since the fast commit path holds s_fc_lock while writing the commit log, allocations under the lock can enter reclaim and invert the lock order with fs_reclaim. Add ext4_fc_lock()/ext4_fc_unlock() helpers which acquire s_fc_lock under memalloc_nofs_save()/restore() context and use them everywhere so allocations under the lock cannot recurse into filesystem reclaim. Fixes: 6593714d67ba ("ext4: hold s_fc_lock while during fast commit") Signed-off-by: Li Chen Reviewed-by: Baokun Li Reviewed-by: Zhang Yi Reviewed-by: Jan Kara Link: https://patch.msgid.link/20260106120621.440126-1-me@linux.beauty Signed-off-by: Theodore Ts'o Signed-off-by: Sasha Levin --- fs/ext4/ext4.h | 16 ++++++++++++++ fs/ext4/fast_commit.c | 51 ++++++++++++++++++++++++------------------- 2 files changed, 44 insertions(+), 23 deletions(-) diff --git a/fs/ext4/ext4.h b/fs/ext4/ext4.h index 57087da6c7bee8..933297251f66a4 100644 --- a/fs/ext4/ext4.h +++ b/fs/ext4/ext4.h @@ -1771,6 +1771,10 @@ struct ext4_sb_info { * Main fast commit lock. This lock protects accesses to the * following fields: * ei->i_fc_list, s_fc_dentry_q, s_fc_q, s_fc_bytes, s_fc_bh. + * + * s_fc_lock can be taken from reclaim context (inode eviction) and is + * thus reclaim unsafe. Use ext4_fc_lock()/ext4_fc_unlock() helpers + * when acquiring / releasing the lock. */ struct mutex s_fc_lock; struct buffer_head *s_fc_bh; @@ -1815,6 +1819,18 @@ static inline void ext4_writepages_up_write(struct super_block *sb, int ctx) percpu_up_write(&EXT4_SB(sb)->s_writepages_rwsem); } +static inline int ext4_fc_lock(struct super_block *sb) +{ + mutex_lock(&EXT4_SB(sb)->s_fc_lock); + return memalloc_nofs_save(); +} + +static inline void ext4_fc_unlock(struct super_block *sb, int ctx) +{ + memalloc_nofs_restore(ctx); + mutex_unlock(&EXT4_SB(sb)->s_fc_lock); +} + static inline int ext4_valid_inum(struct super_block *sb, unsigned long ino) { return ino == EXT4_ROOT_INO || diff --git a/fs/ext4/fast_commit.c b/fs/ext4/fast_commit.c index fa66b08de9994e..5bd57d7f921b9a 100644 --- a/fs/ext4/fast_commit.c +++ b/fs/ext4/fast_commit.c @@ -231,16 +231,16 @@ static bool ext4_fc_disabled(struct super_block *sb) void ext4_fc_del(struct inode *inode) { struct ext4_inode_info *ei = EXT4_I(inode); - struct ext4_sb_info *sbi = EXT4_SB(inode->i_sb); struct ext4_fc_dentry_update *fc_dentry; wait_queue_head_t *wq; + int alloc_ctx; if (ext4_fc_disabled(inode->i_sb)) return; - mutex_lock(&sbi->s_fc_lock); + alloc_ctx = ext4_fc_lock(inode->i_sb); if (list_empty(&ei->i_fc_list) && list_empty(&ei->i_fc_dilist)) { - mutex_unlock(&sbi->s_fc_lock); + ext4_fc_unlock(inode->i_sb, alloc_ctx); return; } @@ -275,9 +275,9 @@ void ext4_fc_del(struct inode *inode) #endif prepare_to_wait(wq, &wait.wq_entry, TASK_UNINTERRUPTIBLE); if (ext4_test_inode_state(inode, EXT4_STATE_FC_FLUSHING_DATA)) { - mutex_unlock(&sbi->s_fc_lock); + ext4_fc_unlock(inode->i_sb, alloc_ctx); schedule(); - mutex_lock(&sbi->s_fc_lock); + alloc_ctx = ext4_fc_lock(inode->i_sb); } finish_wait(wq, &wait.wq_entry); } @@ -288,7 +288,7 @@ void ext4_fc_del(struct inode *inode) * dentry create references, since it is not needed to log it anyways. */ if (list_empty(&ei->i_fc_dilist)) { - mutex_unlock(&sbi->s_fc_lock); + ext4_fc_unlock(inode->i_sb, alloc_ctx); return; } @@ -298,7 +298,7 @@ void ext4_fc_del(struct inode *inode) list_del_init(&fc_dentry->fcd_dilist); WARN_ON(!list_empty(&ei->i_fc_dilist)); - mutex_unlock(&sbi->s_fc_lock); + ext4_fc_unlock(inode->i_sb, alloc_ctx); release_dentry_name_snapshot(&fc_dentry->fcd_name); kmem_cache_free(ext4_fc_dentry_cachep, fc_dentry); @@ -315,6 +315,7 @@ void ext4_fc_mark_ineligible(struct super_block *sb, int reason, handle_t *handl tid_t tid; bool has_transaction = true; bool is_ineligible; + int alloc_ctx; if (ext4_fc_disabled(sb)) return; @@ -329,12 +330,12 @@ void ext4_fc_mark_ineligible(struct super_block *sb, int reason, handle_t *handl has_transaction = false; read_unlock(&sbi->s_journal->j_state_lock); } - mutex_lock(&sbi->s_fc_lock); + alloc_ctx = ext4_fc_lock(sb); is_ineligible = ext4_test_mount_flag(sb, EXT4_MF_FC_INELIGIBLE); if (has_transaction && (!is_ineligible || tid_gt(tid, sbi->s_fc_ineligible_tid))) sbi->s_fc_ineligible_tid = tid; ext4_set_mount_flag(sb, EXT4_MF_FC_INELIGIBLE); - mutex_unlock(&sbi->s_fc_lock); + ext4_fc_unlock(sb, alloc_ctx); WARN_ON(reason >= EXT4_FC_REASON_MAX); sbi->s_fc_stats.fc_ineligible_reason_count[reason]++; } @@ -358,6 +359,7 @@ static int ext4_fc_track_template( struct ext4_inode_info *ei = EXT4_I(inode); struct ext4_sb_info *sbi = EXT4_SB(inode->i_sb); tid_t tid = 0; + int alloc_ctx; int ret; tid = handle->h_transaction->t_tid; @@ -373,14 +375,14 @@ static int ext4_fc_track_template( if (!enqueue) return ret; - mutex_lock(&sbi->s_fc_lock); + alloc_ctx = ext4_fc_lock(inode->i_sb); if (list_empty(&EXT4_I(inode)->i_fc_list)) list_add_tail(&EXT4_I(inode)->i_fc_list, (sbi->s_journal->j_flags & JBD2_FULL_COMMIT_ONGOING || sbi->s_journal->j_flags & JBD2_FAST_COMMIT_ONGOING) ? &sbi->s_fc_q[FC_Q_STAGING] : &sbi->s_fc_q[FC_Q_MAIN]); - mutex_unlock(&sbi->s_fc_lock); + ext4_fc_unlock(inode->i_sb, alloc_ctx); return ret; } @@ -402,6 +404,7 @@ static int __track_dentry_update(handle_t *handle, struct inode *inode, struct inode *dir = dentry->d_parent->d_inode; struct super_block *sb = inode->i_sb; struct ext4_sb_info *sbi = EXT4_SB(sb); + int alloc_ctx; spin_unlock(&ei->i_fc_lock); @@ -425,7 +428,7 @@ static int __track_dentry_update(handle_t *handle, struct inode *inode, take_dentry_name_snapshot(&node->fcd_name, dentry); INIT_LIST_HEAD(&node->fcd_dilist); INIT_LIST_HEAD(&node->fcd_list); - mutex_lock(&sbi->s_fc_lock); + alloc_ctx = ext4_fc_lock(sb); if (sbi->s_journal->j_flags & JBD2_FULL_COMMIT_ONGOING || sbi->s_journal->j_flags & JBD2_FAST_COMMIT_ONGOING) list_add_tail(&node->fcd_list, @@ -446,7 +449,7 @@ static int __track_dentry_update(handle_t *handle, struct inode *inode, WARN_ON(!list_empty(&ei->i_fc_dilist)); list_add_tail(&node->fcd_dilist, &ei->i_fc_dilist); } - mutex_unlock(&sbi->s_fc_lock); + ext4_fc_unlock(sb, alloc_ctx); spin_lock(&ei->i_fc_lock); return 0; @@ -1046,18 +1049,19 @@ static int ext4_fc_perform_commit(journal_t *journal) struct blk_plug plug; int ret = 0; u32 crc = 0; + int alloc_ctx; /* * Step 1: Mark all inodes on s_fc_q[MAIN] with * EXT4_STATE_FC_FLUSHING_DATA. This prevents these inodes from being * freed until the data flush is over. */ - mutex_lock(&sbi->s_fc_lock); + alloc_ctx = ext4_fc_lock(sb); list_for_each_entry(iter, &sbi->s_fc_q[FC_Q_MAIN], i_fc_list) { ext4_set_inode_state(&iter->vfs_inode, EXT4_STATE_FC_FLUSHING_DATA); } - mutex_unlock(&sbi->s_fc_lock); + ext4_fc_unlock(sb, alloc_ctx); /* Step 2: Flush data for all the eligible inodes. */ ret = ext4_fc_flush_data(journal); @@ -1067,7 +1071,7 @@ static int ext4_fc_perform_commit(journal_t *journal) * any error from step 2. This ensures that waiters waiting on * EXT4_STATE_FC_FLUSHING_DATA can resume. */ - mutex_lock(&sbi->s_fc_lock); + alloc_ctx = ext4_fc_lock(sb); list_for_each_entry(iter, &sbi->s_fc_q[FC_Q_MAIN], i_fc_list) { ext4_clear_inode_state(&iter->vfs_inode, EXT4_STATE_FC_FLUSHING_DATA); @@ -1084,7 +1088,7 @@ static int ext4_fc_perform_commit(journal_t *journal) * prepare_to_wait() in ext4_fc_del(). */ smp_mb(); - mutex_unlock(&sbi->s_fc_lock); + ext4_fc_unlock(sb, alloc_ctx); /* * If we encountered error in Step 2, return it now after clearing @@ -1101,12 +1105,12 @@ static int ext4_fc_perform_commit(journal_t *journal) * previous handles are now drained. We now mark the inodes on the * commit queue as being committed. */ - mutex_lock(&sbi->s_fc_lock); + alloc_ctx = ext4_fc_lock(sb); list_for_each_entry(iter, &sbi->s_fc_q[FC_Q_MAIN], i_fc_list) { ext4_set_inode_state(&iter->vfs_inode, EXT4_STATE_FC_COMMITTING); } - mutex_unlock(&sbi->s_fc_lock); + ext4_fc_unlock(sb, alloc_ctx); jbd2_journal_unlock_updates(journal); /* @@ -1117,6 +1121,7 @@ static int ext4_fc_perform_commit(journal_t *journal) blkdev_issue_flush(journal->j_fs_dev); blk_start_plug(&plug); + alloc_ctx = ext4_fc_lock(sb); /* Step 6: Write fast commit blocks to disk. */ if (sbi->s_fc_bytes == 0) { /* @@ -1134,7 +1139,6 @@ static int ext4_fc_perform_commit(journal_t *journal) } /* Step 6.2: Now write all the dentry updates. */ - mutex_lock(&sbi->s_fc_lock); ret = ext4_fc_commit_dentry_updates(journal, &crc); if (ret) goto out; @@ -1156,7 +1160,7 @@ static int ext4_fc_perform_commit(journal_t *journal) ret = ext4_fc_write_tail(sb, crc); out: - mutex_unlock(&sbi->s_fc_lock); + ext4_fc_unlock(sb, alloc_ctx); blk_finish_plug(&plug); return ret; } @@ -1290,6 +1294,7 @@ static void ext4_fc_cleanup(journal_t *journal, int full, tid_t tid) struct ext4_sb_info *sbi = EXT4_SB(sb); struct ext4_inode_info *ei; struct ext4_fc_dentry_update *fc_dentry; + int alloc_ctx; if (full && sbi->s_fc_bh) sbi->s_fc_bh = NULL; @@ -1297,7 +1302,7 @@ static void ext4_fc_cleanup(journal_t *journal, int full, tid_t tid) trace_ext4_fc_cleanup(journal, full, tid); jbd2_fc_release_bufs(journal); - mutex_lock(&sbi->s_fc_lock); + alloc_ctx = ext4_fc_lock(sb); while (!list_empty(&sbi->s_fc_q[FC_Q_MAIN])) { ei = list_first_entry(&sbi->s_fc_q[FC_Q_MAIN], struct ext4_inode_info, @@ -1356,7 +1361,7 @@ static void ext4_fc_cleanup(journal_t *journal, int full, tid_t tid) if (full) sbi->s_fc_bytes = 0; - mutex_unlock(&sbi->s_fc_lock); + ext4_fc_unlock(sb, alloc_ctx); trace_ext4_fc_stats(sb); } From 0d7ccd3d16ad0f352b56b32d88167afceeeebb59 Mon Sep 17 00:00:00 2001 From: Florian Westphal Date: Fri, 28 Nov 2025 12:26:54 +0100 Subject: [PATCH 3507/3902] netfilter: nf_tables: reset table validation state on abort [ Upstream commit 6f93616a7323d646d18db9c09f147e453b40fdd7 ] If a transaction fails the final validation in the commit hook, the table validation state is changed to NFT_VALIDATE_DO and a replay of the batch is performed. Every rule insert will then do a graph validation. This is much slower, but provides better error reporting to the user because we can point at the rule that introduces the validation issue. Without this reset the affected table(s) remain in full validation mode, i.e. on next transaction we start with slow-mode. This makes the next transaction after a failed incremental update very slow: # time iptables-restore < /tmp/ruleset real 0m0.496s [..] # time iptables -A CALLEE -j CALLER iptables v1.8.11 (nf_tables): RULE_APPEND failed (Too many links): rule in chain CALLEE real 0m0.022s [..] # time iptables-restore < /tmp/ruleset real 1m22.355s [..] After this patch, 2nd iptables-restore is back to ~0.5s. Fixes: 9a32e9850686 ("netfilter: nf_tables: don't write table validation state without mutex") Signed-off-by: Florian Westphal Signed-off-by: Sasha Levin --- net/netfilter/nf_tables_api.c | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/net/netfilter/nf_tables_api.c b/net/netfilter/nf_tables_api.c index 6059a299004d46..df18dfd5a8271e 100644 --- a/net/netfilter/nf_tables_api.c +++ b/net/netfilter/nf_tables_api.c @@ -11538,6 +11538,13 @@ static int nf_tables_abort(struct net *net, struct sk_buff *skb, ret = __nf_tables_abort(net, action); nft_gc_seq_end(nft_net, gc_seq); + if (action == NFNL_ABORT_NONE) { + struct nft_table *table; + + list_for_each_entry(table, &nft_net->tables, list) + table->validate_state = NFT_VALIDATE_SKIP; + } + WARN_ON_ONCE(!list_empty(&nft_net->commit_list)); /* module autoload needs to happen after GC sequence update because it From ab455baa7420f8c416ec69b8d5ccda3fc9763994 Mon Sep 17 00:00:00 2001 From: Fernando Fernandez Mancera Date: Fri, 21 Nov 2025 01:14:31 +0100 Subject: [PATCH 3508/3902] netfilter: nf_conncount: make nf_conncount_gc_list() to disable BH [ Upstream commit c0362b5748282e22fa1592a8d3474f726ad964c2 ] For convenience when performing GC over the connection list, make nf_conncount_gc_list() to disable BH. This unifies the behavior with nf_conncount_add() and nf_conncount_count(). Signed-off-by: Fernando Fernandez Mancera Signed-off-by: Pablo Neira Ayuso Stable-dep-of: 21d033e47273 ("netfilter: nf_conncount: increase the connection clean up limit to 64") Signed-off-by: Sasha Levin --- net/netfilter/nf_conncount.c | 24 +++++++++++++++++------- net/netfilter/nft_connlimit.c | 7 +------ 2 files changed, 18 insertions(+), 13 deletions(-) diff --git a/net/netfilter/nf_conncount.c b/net/netfilter/nf_conncount.c index 828d5c64c68a3d..8487808c87614f 100644 --- a/net/netfilter/nf_conncount.c +++ b/net/netfilter/nf_conncount.c @@ -282,8 +282,8 @@ void nf_conncount_list_init(struct nf_conncount_list *list) EXPORT_SYMBOL_GPL(nf_conncount_list_init); /* Return true if the list is empty. Must be called with BH disabled. */ -bool nf_conncount_gc_list(struct net *net, - struct nf_conncount_list *list) +static bool __nf_conncount_gc_list(struct net *net, + struct nf_conncount_list *list) { const struct nf_conntrack_tuple_hash *found; struct nf_conncount_tuple *conn, *conn_n; @@ -295,10 +295,6 @@ bool nf_conncount_gc_list(struct net *net, if ((u32)jiffies == READ_ONCE(list->last_gc)) return false; - /* don't bother if other cpu is already doing GC */ - if (!spin_trylock(&list->list_lock)) - return false; - list_for_each_entry_safe(conn, conn_n, &list->head, node) { found = find_or_evict(net, list, conn); if (IS_ERR(found)) { @@ -327,7 +323,21 @@ bool nf_conncount_gc_list(struct net *net, if (!list->count) ret = true; list->last_gc = (u32)jiffies; - spin_unlock(&list->list_lock); + + return ret; +} + +bool nf_conncount_gc_list(struct net *net, + struct nf_conncount_list *list) +{ + bool ret; + + /* don't bother if other cpu is already doing GC */ + if (!spin_trylock_bh(&list->list_lock)) + return false; + + ret = __nf_conncount_gc_list(net, list); + spin_unlock_bh(&list->list_lock); return ret; } diff --git a/net/netfilter/nft_connlimit.c b/net/netfilter/nft_connlimit.c index d4964087bbc5e2..714a594859357f 100644 --- a/net/netfilter/nft_connlimit.c +++ b/net/netfilter/nft_connlimit.c @@ -232,13 +232,8 @@ static void nft_connlimit_destroy_clone(const struct nft_ctx *ctx, static bool nft_connlimit_gc(struct net *net, const struct nft_expr *expr) { struct nft_connlimit *priv = nft_expr_priv(expr); - bool ret; - local_bh_disable(); - ret = nf_conncount_gc_list(net, priv->list); - local_bh_enable(); - - return ret; + return nf_conncount_gc_list(net, priv->list); } static struct nft_expr_type nft_connlimit_type; From 6e5fa7add3e76da068a478d905be64be8fa4e80a Mon Sep 17 00:00:00 2001 From: Fernando Fernandez Mancera Date: Wed, 17 Dec 2025 15:46:41 +0100 Subject: [PATCH 3509/3902] netfilter: nf_conncount: increase the connection clean up limit to 64 [ Upstream commit 21d033e472735ecec677f1ae46d6740b5e47a4f3 ] After the optimization to only perform one GC per jiffy, a new problem was introduced. If more than 8 new connections are tracked per jiffy the list won't be cleaned up fast enough possibly reaching the limit wrongly. In order to prevent this issue, only skip the GC if it was already triggered during the same jiffy and the increment is lower than the clean up limit. In addition, increase the clean up limit to 64 connections to avoid triggering GC too often and do more effective GCs. This has been tested using a HTTP server and several performance tools while having nft_connlimit/xt_connlimit or OVS limit configured. Output of slowhttptest + OVS limit at 52000 connections: slow HTTP test status on 340th second: initializing: 0 pending: 432 connected: 51998 error: 0 closed: 0 service available: YES Fixes: d265929930e2 ("netfilter: nf_conncount: reduce unnecessary GC") Reported-by: Aleksandra Rukomoinikova Closes: https://lore.kernel.org/netfilter/b2064e7b-0776-4e14-adb6-c68080987471@k2.cloud/ Signed-off-by: Fernando Fernandez Mancera Signed-off-by: Florian Westphal Signed-off-by: Sasha Levin --- include/net/netfilter/nf_conntrack_count.h | 1 + net/netfilter/nf_conncount.c | 15 ++++++++++----- 2 files changed, 11 insertions(+), 5 deletions(-) diff --git a/include/net/netfilter/nf_conntrack_count.h b/include/net/netfilter/nf_conntrack_count.h index 52a06de41aa0f9..cf0166520cf338 100644 --- a/include/net/netfilter/nf_conntrack_count.h +++ b/include/net/netfilter/nf_conntrack_count.h @@ -13,6 +13,7 @@ struct nf_conncount_list { u32 last_gc; /* jiffies at most recent gc */ struct list_head head; /* connections with the same filtering key */ unsigned int count; /* length of list */ + unsigned int last_gc_count; /* length of list at most recent gc */ }; struct nf_conncount_data *nf_conncount_init(struct net *net, unsigned int keylen); diff --git a/net/netfilter/nf_conncount.c b/net/netfilter/nf_conncount.c index 8487808c87614f..288936f5c1bf92 100644 --- a/net/netfilter/nf_conncount.c +++ b/net/netfilter/nf_conncount.c @@ -34,8 +34,9 @@ #define CONNCOUNT_SLOTS 256U -#define CONNCOUNT_GC_MAX_NODES 8 -#define MAX_KEYLEN 5 +#define CONNCOUNT_GC_MAX_NODES 8 +#define CONNCOUNT_GC_MAX_COLLECT 64 +#define MAX_KEYLEN 5 /* we will save the tuples of all connections we care about */ struct nf_conncount_tuple { @@ -182,12 +183,13 @@ static int __nf_conncount_add(struct net *net, goto out_put; } - if ((u32)jiffies == list->last_gc) + if ((u32)jiffies == list->last_gc && + (list->count - list->last_gc_count) < CONNCOUNT_GC_MAX_COLLECT) goto add_new_node; /* check the saved connections */ list_for_each_entry_safe(conn, conn_n, &list->head, node) { - if (collect > CONNCOUNT_GC_MAX_NODES) + if (collect > CONNCOUNT_GC_MAX_COLLECT) break; found = find_or_evict(net, list, conn); @@ -230,6 +232,7 @@ static int __nf_conncount_add(struct net *net, nf_ct_put(found_ct); } list->last_gc = (u32)jiffies; + list->last_gc_count = list->count; add_new_node: if (WARN_ON_ONCE(list->count > INT_MAX)) { @@ -277,6 +280,7 @@ void nf_conncount_list_init(struct nf_conncount_list *list) spin_lock_init(&list->list_lock); INIT_LIST_HEAD(&list->head); list->count = 0; + list->last_gc_count = 0; list->last_gc = (u32)jiffies; } EXPORT_SYMBOL_GPL(nf_conncount_list_init); @@ -316,13 +320,14 @@ static bool __nf_conncount_gc_list(struct net *net, } nf_ct_put(found_ct); - if (collected > CONNCOUNT_GC_MAX_NODES) + if (collected > CONNCOUNT_GC_MAX_COLLECT) break; } if (!list->count) ret = true; list->last_gc = (u32)jiffies; + list->last_gc_count = list->count; return ret; } From a0cdcacc0d414c6cf536db389bf4aa33f8a36f6f Mon Sep 17 00:00:00 2001 From: Florian Westphal Date: Fri, 19 Aug 2022 16:16:07 +0200 Subject: [PATCH 3510/3902] netfilter: nft_compat: add more restrictions on netlink attributes [ Upstream commit cda26c645946b08f070f20c166d4736767e4a805 ] As far as I can see nothing bad can happen when NFTA_TARGET/MATCH_NAME are too large because this calls x_tables helpers which check for the length, but it seems better to already reject it during netlink parsing. Rest of the changes avoid silent u8/u16 truncations. For _TYPE, its expected to be only 1 or 0. In x_tables world, this variable is set by kernel, for IPT_SO_GET_REVISION_TARGET its 1, for all others its set to 0. As older versions of nf_tables permitted any value except 1 to mean 'match', keep this as-is but sanitize the value for consistency. Fixes: 0ca743a55991 ("netfilter: nf_tables: add compatibility layer for x_tables") Reviewed-by: Fernando Fernandez Mancera Signed-off-by: Florian Westphal Signed-off-by: Sasha Levin --- net/netfilter/nft_compat.c | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/net/netfilter/nft_compat.c b/net/netfilter/nft_compat.c index 72711d62fddfa4..08f620311b03f1 100644 --- a/net/netfilter/nft_compat.c +++ b/net/netfilter/nft_compat.c @@ -134,7 +134,8 @@ static void nft_target_eval_bridge(const struct nft_expr *expr, } static const struct nla_policy nft_target_policy[NFTA_TARGET_MAX + 1] = { - [NFTA_TARGET_NAME] = { .type = NLA_NUL_STRING }, + [NFTA_TARGET_NAME] = { .type = NLA_NUL_STRING, + .len = XT_EXTENSION_MAXNAMELEN, }, [NFTA_TARGET_REV] = NLA_POLICY_MAX(NLA_BE32, 255), [NFTA_TARGET_INFO] = { .type = NLA_BINARY }, }; @@ -434,7 +435,8 @@ static void nft_match_eval(const struct nft_expr *expr, } static const struct nla_policy nft_match_policy[NFTA_MATCH_MAX + 1] = { - [NFTA_MATCH_NAME] = { .type = NLA_NUL_STRING }, + [NFTA_MATCH_NAME] = { .type = NLA_NUL_STRING, + .len = XT_EXTENSION_MAXNAMELEN }, [NFTA_MATCH_REV] = NLA_POLICY_MAX(NLA_BE32, 255), [NFTA_MATCH_INFO] = { .type = NLA_BINARY }, }; @@ -693,7 +695,12 @@ static int nfnl_compat_get_rcu(struct sk_buff *skb, name = nla_data(tb[NFTA_COMPAT_NAME]); rev = ntohl(nla_get_be32(tb[NFTA_COMPAT_REV])); - target = ntohl(nla_get_be32(tb[NFTA_COMPAT_TYPE])); + /* x_tables api checks for 'target == 1' to mean target, + * everything else means 'match'. + * In x_tables world, the number is set by kernel, not + * userspace. + */ + target = nla_get_be32(tb[NFTA_COMPAT_TYPE]) == htonl(1); switch(family) { case AF_INET: From 536ffc371b52ecd00f78658895e35e0775df9b81 Mon Sep 17 00:00:00 2001 From: Fernando Fernandez Mancera Date: Mon, 19 Jan 2026 21:35:46 +0100 Subject: [PATCH 3511/3902] netfilter: nf_conncount: fix tracking of connections from localhost [ Upstream commit de8a70cefcb26cdceaafdc5ac144712681419c29 ] Since commit be102eb6a0e7 ("netfilter: nf_conncount: rework API to use sk_buff directly"), we skip the adding and trigger a GC when the ct is confirmed. For connections originated from local to local it doesn't work because the connection is confirmed on POSTROUTING, therefore tracking on the INPUT hook is always skipped. In order to fix this, we check whether skb input ifindex is set to loopback ifindex. If it is then we fallback on a GC plus track operation skipping the optimization. This fallback is necessary to avoid duplicated tracking of a packet train e.g 10 UDP datagrams sent on a burst when initiating the connection. Tested with xt_connlimit/nft_connlimit and OVS limit and with a HTTP server and iperf3 on UDP mode. Fixes: be102eb6a0e7 ("netfilter: nf_conncount: rework API to use sk_buff directly") Reported-by: Michal Slabihoudek Closes: https://lore.kernel.org/netfilter/6989BD9F-8C24-4397-9AD7-4613B28BF0DB@gooddata.com/ Signed-off-by: Fernando Fernandez Mancera Signed-off-by: Florian Westphal Signed-off-by: Sasha Levin --- net/netfilter/nf_conncount.c | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/net/netfilter/nf_conncount.c b/net/netfilter/nf_conncount.c index 288936f5c1bf92..14e62b3263cd94 100644 --- a/net/netfilter/nf_conncount.c +++ b/net/netfilter/nf_conncount.c @@ -179,14 +179,25 @@ static int __nf_conncount_add(struct net *net, return -ENOENT; if (ct && nf_ct_is_confirmed(ct)) { - err = -EEXIST; - goto out_put; + /* local connections are confirmed in postrouting so confirmation + * might have happened before hitting connlimit + */ + if (skb->skb_iif != LOOPBACK_IFINDEX) { + err = -EEXIST; + goto out_put; + } + + /* this is likely a local connection, skip optimization to avoid + * adding duplicates from a 'packet train' + */ + goto check_connections; } if ((u32)jiffies == list->last_gc && (list->count - list->last_gc_count) < CONNCOUNT_GC_MAX_COLLECT) goto add_new_node; +check_connections: /* check the saved connections */ list_for_each_entry_safe(conn, conn_n, &list->head, node) { if (collect > CONNCOUNT_GC_MAX_COLLECT) From 34de49d09775a4c8d5a6e14ed9005c850f4934fe Mon Sep 17 00:00:00 2001 From: Petr Mladek Date: Fri, 28 Nov 2025 14:59:18 +0100 Subject: [PATCH 3512/3902] kallsyms/bpf: rename __bpf_address_lookup() to bpf_address_lookup() [ Upstream commit cd6735896d0343942cf3dafb48ce32eb79341990 ] bpf_address_lookup() has been used only in kallsyms_lookup_buildid(). It was supposed to set @modname and @modbuildid when the symbol was in a module. But it always just cleared @modname because BPF symbols were never in a module. And it did not clear @modbuildid because the pointer was not passed. The wrapper is no longer needed. Both @modname and @modbuildid are now always initialized to NULL in kallsyms_lookup_buildid(). Remove the wrapper and rename __bpf_address_lookup() to bpf_address_lookup() because this variant is used everywhere. [akpm@linux-foundation.org: fix loongarch] Link: https://lkml.kernel.org/r/20251128135920.217303-6-pmladek@suse.com Fixes: 9294523e3768 ("module: add printk formats to add module build ID to stacktraces") Signed-off-by: Petr Mladek Acked-by: Alexei Starovoitov Cc: Aaron Tomlin Cc: Daniel Borkman Cc: Daniel Gomez Cc: John Fastabend Cc: Kees Cook Cc: Luis Chamberalin Cc: Marc Rutland Cc: "Masami Hiramatsu (Google)" Cc: Petr Pavlu Cc: Sami Tolvanen Cc: Steven Rostedt (Google) Signed-off-by: Andrew Morton Signed-off-by: Sasha Levin --- arch/arm64/net/bpf_jit_comp.c | 2 +- arch/loongarch/net/bpf_jit.c | 2 +- arch/powerpc/net/bpf_jit_comp.c | 2 +- include/linux/filter.h | 26 ++++---------------------- kernel/bpf/core.c | 4 ++-- kernel/kallsyms.c | 5 ++--- 6 files changed, 11 insertions(+), 30 deletions(-) diff --git a/arch/arm64/net/bpf_jit_comp.c b/arch/arm64/net/bpf_jit_comp.c index 0dfefeedfe56ce..83a6ca613f9c24 100644 --- a/arch/arm64/net/bpf_jit_comp.c +++ b/arch/arm64/net/bpf_jit_comp.c @@ -2939,7 +2939,7 @@ int bpf_arch_text_poke(void *ip, enum bpf_text_poke_type poke_type, u64 plt_target = 0ULL; bool poking_bpf_entry; - if (!__bpf_address_lookup((unsigned long)ip, &size, &offset, namebuf)) + if (!bpf_address_lookup((unsigned long)ip, &size, &offset, namebuf)) /* Only poking bpf text is supported. Since kernel function * entry is set up by ftrace, we reply on ftrace to poke kernel * functions. diff --git a/arch/loongarch/net/bpf_jit.c b/arch/loongarch/net/bpf_jit.c index 87ff025137873a..e9d666508ae28f 100644 --- a/arch/loongarch/net/bpf_jit.c +++ b/arch/loongarch/net/bpf_jit.c @@ -1318,7 +1318,7 @@ int bpf_arch_text_poke(void *ip, enum bpf_text_poke_type poke_type, /* Only poking bpf text is supported. Since kernel function entry * is set up by ftrace, we rely on ftrace to poke kernel functions. */ - if (!__bpf_address_lookup((unsigned long)ip, &size, &offset, namebuf)) + if (!bpf_address_lookup((unsigned long)ip, &size, &offset, namebuf)) return -ENOTSUPP; image = ip - offset; diff --git a/arch/powerpc/net/bpf_jit_comp.c b/arch/powerpc/net/bpf_jit_comp.c index 88ad5ba7b87fd0..21f7f26a5e2f35 100644 --- a/arch/powerpc/net/bpf_jit_comp.c +++ b/arch/powerpc/net/bpf_jit_comp.c @@ -1122,7 +1122,7 @@ int bpf_arch_text_poke(void *ip, enum bpf_text_poke_type poke_type, branch_flags = poke_type == BPF_MOD_CALL ? BRANCH_SET_LINK : 0; /* We currently only support poking bpf programs */ - if (!__bpf_address_lookup(bpf_func, &size, &offset, name)) { + if (!bpf_address_lookup(bpf_func, &size, &offset, name)) { pr_err("%s (0x%lx): kernel/modules are not supported\n", __func__, bpf_func); return -EOPNOTSUPP; } diff --git a/include/linux/filter.h b/include/linux/filter.h index 569de3b14279af..cf7a0bce1bb6d8 100644 --- a/include/linux/filter.h +++ b/include/linux/filter.h @@ -1375,24 +1375,13 @@ static inline bool bpf_jit_kallsyms_enabled(void) return false; } -int __bpf_address_lookup(unsigned long addr, unsigned long *size, - unsigned long *off, char *sym); +int bpf_address_lookup(unsigned long addr, unsigned long *size, + unsigned long *off, char *sym); bool is_bpf_text_address(unsigned long addr); int bpf_get_kallsym(unsigned int symnum, unsigned long *value, char *type, char *sym); struct bpf_prog *bpf_prog_ksym_find(unsigned long addr); -static inline int -bpf_address_lookup(unsigned long addr, unsigned long *size, - unsigned long *off, char **modname, char *sym) -{ - int ret = __bpf_address_lookup(addr, size, off, sym); - - if (ret && modname) - *modname = NULL; - return ret; -} - void bpf_prog_kallsyms_add(struct bpf_prog *fp); void bpf_prog_kallsyms_del(struct bpf_prog *fp); @@ -1431,8 +1420,8 @@ static inline bool bpf_jit_kallsyms_enabled(void) } static inline int -__bpf_address_lookup(unsigned long addr, unsigned long *size, - unsigned long *off, char *sym) +bpf_address_lookup(unsigned long addr, unsigned long *size, + unsigned long *off, char *sym) { return 0; } @@ -1453,13 +1442,6 @@ static inline struct bpf_prog *bpf_prog_ksym_find(unsigned long addr) return NULL; } -static inline int -bpf_address_lookup(unsigned long addr, unsigned long *size, - unsigned long *off, char **modname, char *sym) -{ - return 0; -} - static inline void bpf_prog_kallsyms_add(struct bpf_prog *fp) { } diff --git a/kernel/bpf/core.c b/kernel/bpf/core.c index d595fe512498cc..c2278f392e9320 100644 --- a/kernel/bpf/core.c +++ b/kernel/bpf/core.c @@ -713,8 +713,8 @@ static struct bpf_ksym *bpf_ksym_find(unsigned long addr) return n ? container_of(n, struct bpf_ksym, tnode) : NULL; } -int __bpf_address_lookup(unsigned long addr, unsigned long *size, - unsigned long *off, char *sym) +int bpf_address_lookup(unsigned long addr, unsigned long *size, + unsigned long *off, char *sym) { struct bpf_ksym *ksym; int ret = 0; diff --git a/kernel/kallsyms.c b/kernel/kallsyms.c index 049e296f586ccd..7417dd5f8a796e 100644 --- a/kernel/kallsyms.c +++ b/kernel/kallsyms.c @@ -345,7 +345,7 @@ int kallsyms_lookup_size_offset(unsigned long addr, unsigned long *symbolsize, return 1; } return !!module_address_lookup(addr, symbolsize, offset, NULL, NULL, namebuf) || - !!__bpf_address_lookup(addr, symbolsize, offset, namebuf); + !!bpf_address_lookup(addr, symbolsize, offset, namebuf); } static int kallsyms_lookup_buildid(unsigned long addr, @@ -377,8 +377,7 @@ static int kallsyms_lookup_buildid(unsigned long addr, ret = module_address_lookup(addr, symbolsize, offset, modname, modbuildid, namebuf); if (!ret) - ret = bpf_address_lookup(addr, symbolsize, - offset, modname, namebuf); + ret = bpf_address_lookup(addr, symbolsize, offset, namebuf); if (!ret) ret = ftrace_mod_address_lookup(addr, symbolsize, From 7f4827b247500f2d1e29c6e707651593f80d7bd8 Mon Sep 17 00:00:00 2001 From: Petr Mladek Date: Fri, 28 Nov 2025 14:59:16 +0100 Subject: [PATCH 3513/3902] module: add helper function for reading module_buildid() [ Upstream commit acfdbb4ab2910ff6f03becb569c23ac7b2223913 ] Add a helper function for reading the optional "build_id" member of struct module. It is going to be used also in ftrace_mod_address_lookup(). Use "#ifdef" instead of "#if IS_ENABLED()" to match the declaration of the optional field in struct module. Link: https://lkml.kernel.org/r/20251128135920.217303-4-pmladek@suse.com Signed-off-by: Petr Mladek Reviewed-by: Daniel Gomez Reviewed-by: Petr Pavlu Cc: Aaron Tomlin Cc: Alexei Starovoitov Cc: Daniel Borkman Cc: John Fastabend Cc: Kees Cook Cc: Luis Chamberalin Cc: Marc Rutland Cc: "Masami Hiramatsu (Google)" Cc: Sami Tolvanen Cc: Steven Rostedt (Google) Signed-off-by: Andrew Morton Stable-dep-of: e8a1e7eaa19d ("kallsyms/ftrace: set module buildid in ftrace_mod_address_lookup()") Signed-off-by: Sasha Levin --- include/linux/module.h | 9 +++++++++ kernel/module/kallsyms.c | 9 ++------- 2 files changed, 11 insertions(+), 7 deletions(-) diff --git a/include/linux/module.h b/include/linux/module.h index e135cc79aceeaf..4decae2b167551 100644 --- a/include/linux/module.h +++ b/include/linux/module.h @@ -747,6 +747,15 @@ static inline void __module_get(struct module *module) __mod ? __mod->name : "kernel"; \ }) +static inline const unsigned char *module_buildid(struct module *mod) +{ +#ifdef CONFIG_STACKTRACE_BUILD_ID + return mod->build_id; +#else + return NULL; +#endif +} + /* Dereference module function descriptor */ void *dereference_module_function_descriptor(struct module *mod, void *ptr); diff --git a/kernel/module/kallsyms.c b/kernel/module/kallsyms.c index 00a60796327c06..0fc11e45df9b91 100644 --- a/kernel/module/kallsyms.c +++ b/kernel/module/kallsyms.c @@ -334,13 +334,8 @@ int module_address_lookup(unsigned long addr, if (mod) { if (modname) *modname = mod->name; - if (modbuildid) { -#if IS_ENABLED(CONFIG_STACKTRACE_BUILD_ID) - *modbuildid = mod->build_id; -#else - *modbuildid = NULL; -#endif - } + if (modbuildid) + *modbuildid = module_buildid(mod); sym = find_kallsyms_symbol(mod, addr, size, offset); From feef0143ec5e98a48334316f0067a445199b310d Mon Sep 17 00:00:00 2001 From: Petr Mladek Date: Fri, 28 Nov 2025 14:59:19 +0100 Subject: [PATCH 3514/3902] kallsyms/ftrace: set module buildid in ftrace_mod_address_lookup() [ Upstream commit e8a1e7eaa19d0b757b06a2f913e3eeb4b1c002c6 ] __sprint_symbol() might access an invalid pointer when kallsyms_lookup_buildid() returns a symbol found by ftrace_mod_address_lookup(). The ftrace lookup function must set both @modname and @modbuildid the same way as module_address_lookup(). Link: https://lkml.kernel.org/r/20251128135920.217303-7-pmladek@suse.com Fixes: 9294523e3768 ("module: add printk formats to add module build ID to stacktraces") Signed-off-by: Petr Mladek Reviewed-by: Aaron Tomlin Acked-by: Steven Rostedt (Google) Cc: Alexei Starovoitov Cc: Daniel Borkman Cc: Daniel Gomez Cc: John Fastabend Cc: Kees Cook Cc: Luis Chamberalin Cc: Marc Rutland Cc: "Masami Hiramatsu (Google)" Cc: Petr Pavlu Cc: Sami Tolvanen Signed-off-by: Andrew Morton Signed-off-by: Sasha Levin --- include/linux/ftrace.h | 6 ++++-- kernel/kallsyms.c | 4 ++-- kernel/trace/ftrace.c | 5 ++++- 3 files changed, 10 insertions(+), 5 deletions(-) diff --git a/include/linux/ftrace.h b/include/linux/ftrace.h index 07f8c309e43273..9cc60e2506af16 100644 --- a/include/linux/ftrace.h +++ b/include/linux/ftrace.h @@ -87,11 +87,13 @@ struct ftrace_hash; defined(CONFIG_DYNAMIC_FTRACE) int ftrace_mod_address_lookup(unsigned long addr, unsigned long *size, - unsigned long *off, char **modname, char *sym); + unsigned long *off, char **modname, + const unsigned char **modbuildid, char *sym); #else static inline int ftrace_mod_address_lookup(unsigned long addr, unsigned long *size, - unsigned long *off, char **modname, char *sym) + unsigned long *off, char **modname, + const unsigned char **modbuildid, char *sym) { return 0; } diff --git a/kernel/kallsyms.c b/kernel/kallsyms.c index 7417dd5f8a796e..cdd6e025935d34 100644 --- a/kernel/kallsyms.c +++ b/kernel/kallsyms.c @@ -380,8 +380,8 @@ static int kallsyms_lookup_buildid(unsigned long addr, ret = bpf_address_lookup(addr, symbolsize, offset, namebuf); if (!ret) - ret = ftrace_mod_address_lookup(addr, symbolsize, - offset, modname, namebuf); + ret = ftrace_mod_address_lookup(addr, symbolsize, offset, + modname, modbuildid, namebuf); return ret; } diff --git a/kernel/trace/ftrace.c b/kernel/trace/ftrace.c index e95408a47c1d06..905f4d16795598 100644 --- a/kernel/trace/ftrace.c +++ b/kernel/trace/ftrace.c @@ -7709,7 +7709,8 @@ ftrace_func_address_lookup(struct ftrace_mod_map *mod_map, int ftrace_mod_address_lookup(unsigned long addr, unsigned long *size, - unsigned long *off, char **modname, char *sym) + unsigned long *off, char **modname, + const unsigned char **modbuildid, char *sym) { struct ftrace_mod_map *mod_map; int ret = 0; @@ -7721,6 +7722,8 @@ ftrace_mod_address_lookup(unsigned long addr, unsigned long *size, if (ret) { if (modname) *modname = mod_map->mod->name; + if (modbuildid) + *modbuildid = module_buildid(mod_map->mod); break; } } From e53224783442874d80852c478a6ac7a2f2b2ca7e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Wedekind?= Date: Mon, 19 Jan 2026 15:31:10 +0100 Subject: [PATCH 3515/3902] PCI: Mark 3ware-9650SA Root Port Extended Tags as broken MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [ Upstream commit 959ac08a2c2811305be8c2779779e8b0932e5a99 ] Per PCIe r7.0, sec 2.2.6.2.1 and 7.5.3.4, a Requester may not use 8-bit Tags unless its Extended Tag Field Enable is set, but all Receivers/Completers must handle 8-bit Tags correctly regardless of their Extended Tag Field Enable. Some devices do not handle 8-bit Tags as Completers, so add a quirk for them. If we find such a device, we disable Extended Tags for the entire hierarchy to make peer-to-peer DMA possible. The 3ware 9650SA seems to have issues with handling 8-bit tags. Mark it as broken. This fixes PCI Parity Errors like : 3w-9xxx: scsi0: ERROR: (0x06:0x000C): PCI Parity Error: clearing. 3w-9xxx: scsi0: ERROR: (0x06:0x000D): PCI Abort: clearing. 3w-9xxx: scsi0: ERROR: (0x06:0x000E): Controller Queue Error: clearing. 3w-9xxx: scsi0: ERROR: (0x06:0x0010): Microcontroller Error: clearing. Fixes: 60db3a4d8cc9 ("PCI: Enable PCIe Extended Tags if supported") Closes: https://bugzilla.kernel.org/show_bug.cgi?id=202425 Signed-off-by: Jörg Wedekind Signed-off-by: Bjorn Helgaas Link: https://patch.msgid.link/20260119143114.21948-1-joerg@wedekind.de Signed-off-by: Sasha Levin --- drivers/pci/quirks.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/pci/quirks.c b/drivers/pci/quirks.c index b9c252aa6fe08a..c7e733beaab038 100644 --- a/drivers/pci/quirks.c +++ b/drivers/pci/quirks.c @@ -5581,6 +5581,7 @@ static void quirk_no_ext_tags(struct pci_dev *pdev) pci_walk_bus(bridge->bus, pci_configure_extended_tags, NULL); } DECLARE_PCI_FIXUP_EARLY(PCI_VENDOR_ID_3WARE, 0x1004, quirk_no_ext_tags); +DECLARE_PCI_FIXUP_EARLY(PCI_VENDOR_ID_3WARE, 0x1005, quirk_no_ext_tags); DECLARE_PCI_FIXUP_EARLY(PCI_VENDOR_ID_SERVERWORKS, 0x0132, quirk_no_ext_tags); DECLARE_PCI_FIXUP_EARLY(PCI_VENDOR_ID_SERVERWORKS, 0x0140, quirk_no_ext_tags); DECLARE_PCI_FIXUP_EARLY(PCI_VENDOR_ID_SERVERWORKS, 0x0141, quirk_no_ext_tags); From 53336a811485ae3b5cf8b8610e00ed289a810624 Mon Sep 17 00:00:00 2001 From: Zilin Guan Date: Fri, 16 Jan 2026 13:08:34 +0000 Subject: [PATCH 3516/3902] wifi: rtw89: debug: Fix memory leak in __print_txpwr_map() [ Upstream commit 6070a44051b1c35714fa130de7726cfe91ca5559 ] In __print_txpwr_map(), memory is allocated to bufp via vzalloc(). If max_valid_addr is 0, the function returns -EOPNOTSUPP immediately without freeing bufp, leading to a memory leak. Since the validation of max_valid_addr does not depend on the allocated memory, fix this by moving the vzalloc() call after the check. Compile tested only. Issue found using a prototype static analysis tool and code review. Fixes: 036042e15770 ("wifi: rtw89: debug: txpwr table supports Wi-Fi 7 chips") Suggested-by: Zong-Zhe Yang Signed-off-by: Zilin Guan Reviewed-by: Zong-Zhe Yang Signed-off-by: Ping-Ke Shih Link: https://patch.msgid.link/20260116130834.1413924-1-zilin@seu.edu.cn Signed-off-by: Sasha Levin --- drivers/net/wireless/realtek/rtw89/debug.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/drivers/net/wireless/realtek/rtw89/debug.c b/drivers/net/wireless/realtek/rtw89/debug.c index 3dc7981c510fdb..a82df3814069cd 100644 --- a/drivers/net/wireless/realtek/rtw89/debug.c +++ b/drivers/net/wireless/realtek/rtw89/debug.c @@ -824,10 +824,6 @@ static ssize_t __print_txpwr_map(struct rtw89_dev *rtwdev, char *buf, size_t buf s8 *bufp, tmp; int ret; - bufp = vzalloc(map->addr_to - map->addr_from + 4); - if (!bufp) - return -ENOMEM; - if (path_num == 1) max_valid_addr = map->addr_to_1ss; else @@ -836,6 +832,10 @@ static ssize_t __print_txpwr_map(struct rtw89_dev *rtwdev, char *buf, size_t buf if (max_valid_addr == 0) return -EOPNOTSUPP; + bufp = vzalloc(map->addr_to - map->addr_from + 4); + if (!bufp) + return -ENOMEM; + for (addr = map->addr_from; addr <= max_valid_addr; addr += 4) { ret = rtw89_mac_txpwr_read32(rtwdev, RTW89_PHY_0, addr, &val); if (ret) From 36244dfd3853f7bf89d03b8e90d56b23ce7fbc16 Mon Sep 17 00:00:00 2001 From: Dmytro Maluka Date: Thu, 22 Jan 2026 09:48:52 +0800 Subject: [PATCH 3517/3902] iommu/vt-d: Flush cache for PASID table before using it [ Upstream commit 22d169bdd2849fe6bd18c2643742e1c02be6451c ] When writing the address of a freshly allocated zero-initialized PASID table to a PASID directory entry, do that after the CPU cache flush for this PASID table, not before it, to avoid the time window when this PASID table may be already used by non-coherent IOMMU hardware while its contents in RAM is still some random old data, not zero-initialized. Fixes: 194b3348bdbb ("iommu/vt-d: Fix PASID directory pointer coherency") Signed-off-by: Dmytro Maluka Reviewed-by: Kevin Tian Link: https://lore.kernel.org/r/20251221123508.37495-1-dmaluka@chromium.org Signed-off-by: Lu Baolu Signed-off-by: Joerg Roedel Signed-off-by: Sasha Levin --- drivers/iommu/intel/pasid.c | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/drivers/iommu/intel/pasid.c b/drivers/iommu/intel/pasid.c index 52f678975da745..67cbf53d18c8ff 100644 --- a/drivers/iommu/intel/pasid.c +++ b/drivers/iommu/intel/pasid.c @@ -153,6 +153,9 @@ static struct pasid_entry *intel_pasid_get_entry(struct device *dev, u32 pasid) if (!entries) return NULL; + if (!ecap_coherent(info->iommu->ecap)) + clflush_cache_range(entries, VTD_PAGE_SIZE); + /* * The pasid directory table entry won't be freed after * allocation. No worry about the race with free and @@ -165,10 +168,8 @@ static struct pasid_entry *intel_pasid_get_entry(struct device *dev, u32 pasid) iommu_free_pages(entries); goto retry; } - if (!ecap_coherent(info->iommu->ecap)) { - clflush_cache_range(entries, VTD_PAGE_SIZE); + if (!ecap_coherent(info->iommu->ecap)) clflush_cache_range(&dir[dir_index].val, sizeof(*dir)); - } } return &entries[index]; From 821807c167b7b48a41b95b6607c6b9f97600f7d9 Mon Sep 17 00:00:00 2001 From: Lu Baolu Date: Thu, 22 Jan 2026 09:48:54 +0800 Subject: [PATCH 3518/3902] iommu/vt-d: Clear Present bit before tearing down PASID entry [ Upstream commit 75ed00055c059dedc47b5daaaa2f8a7a019138ff ] The Intel VT-d Scalable Mode PASID table entry consists of 512 bits (64 bytes). When tearing down an entry, the current implementation zeros the entire 64-byte structure immediately using multiple 64-bit writes. Since the IOMMU hardware may fetch these 64 bytes using multiple internal transactions (e.g., four 128-bit bursts), updating or zeroing the entire entry while it is active (P=1) risks a "torn" read. If a hardware fetch occurs simultaneously with the CPU zeroing the entry, the hardware could observe an inconsistent state, leading to unpredictable behavior or spurious faults. Follow the "Guidance to Software for Invalidations" in the VT-d spec (Section 6.5.3.3) by implementing the recommended ownership handshake: 1. Clear only the 'Present' (P) bit of the PASID entry. 2. Use a dma_wmb() to ensure the cleared bit is visible to hardware before proceeding. 3. Execute the required invalidation sequence (PASID cache, IOTLB, and Device-TLB flush) to ensure the hardware has released all cached references. 4. Only after the flushes are complete, zero out the remaining fields of the PASID entry. Also, add a dma_wmb() in pasid_set_present() to ensure that all other fields of the PASID entry are visible to the hardware before the Present bit is set. Fixes: 0bbeb01a4faf ("iommu/vt-d: Manage scalalble mode PASID tables") Signed-off-by: Lu Baolu Reviewed-by: Dmytro Maluka Reviewed-by: Samiullah Khawaja Reviewed-by: Kevin Tian Link: https://lore.kernel.org/r/20260120061816.2132558-2-baolu.lu@linux.intel.com Signed-off-by: Joerg Roedel Signed-off-by: Sasha Levin --- drivers/iommu/intel/pasid.c | 6 +++++- drivers/iommu/intel/pasid.h | 14 ++++++++++++++ 2 files changed, 19 insertions(+), 1 deletion(-) diff --git a/drivers/iommu/intel/pasid.c b/drivers/iommu/intel/pasid.c index 67cbf53d18c8ff..f64b5ae306d0f2 100644 --- a/drivers/iommu/intel/pasid.c +++ b/drivers/iommu/intel/pasid.c @@ -273,7 +273,7 @@ void intel_pasid_tear_down_entry(struct intel_iommu *iommu, struct device *dev, did = pasid_get_domain_id(pte); pgtt = pasid_pte_get_pgtt(pte); - intel_pasid_clear_entry(dev, pasid, fault_ignore); + pasid_clear_present(pte); spin_unlock(&iommu->lock); if (!ecap_coherent(iommu->ecap)) @@ -287,6 +287,10 @@ void intel_pasid_tear_down_entry(struct intel_iommu *iommu, struct device *dev, iommu->flush.flush_iotlb(iommu, did, 0, 0, DMA_TLB_DSI_FLUSH); devtlb_invalidation_with_pasid(iommu, dev, pasid); + intel_pasid_clear_entry(dev, pasid, fault_ignore); + if (!ecap_coherent(iommu->ecap)) + clflush_cache_range(pte, sizeof(*pte)); + if (!fault_ignore) intel_iommu_drain_pasid_prq(dev, pasid); } diff --git a/drivers/iommu/intel/pasid.h b/drivers/iommu/intel/pasid.h index a771a77d4239c4..637373995be803 100644 --- a/drivers/iommu/intel/pasid.h +++ b/drivers/iommu/intel/pasid.h @@ -233,9 +233,23 @@ static inline void pasid_set_wpe(struct pasid_entry *pe) */ static inline void pasid_set_present(struct pasid_entry *pe) { + dma_wmb(); pasid_set_bits(&pe->val[0], 1 << 0, 1); } +/* + * Clear the Present (P) bit (bit 0) of a scalable-mode PASID table entry. + * This initiates the transition of the entry's ownership from hardware + * to software. The caller is responsible for fulfilling the invalidation + * handshake recommended by the VT-d spec, Section 6.5.3.3 (Guidance to + * Software for Invalidations). + */ +static inline void pasid_clear_present(struct pasid_entry *pe) +{ + pasid_set_bits(&pe->val[0], 1 << 0, 0); + dma_wmb(); +} + /* * Setup Page Walk Snoop bit (Bit 87) of a scalable mode PASID * entry. From d2138abc8f0a7fce4101b7229b43b06811ed083d Mon Sep 17 00:00:00 2001 From: Lu Baolu Date: Thu, 22 Jan 2026 09:48:55 +0800 Subject: [PATCH 3519/3902] iommu/vt-d: Clear Present bit before tearing down context entry MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [ Upstream commit c1e4f1dccbe9d7656d1c6872ebeadb5992d0aaa2 ] When tearing down a context entry, the current implementation zeros the entire 128-bit entry using multiple 64-bit writes. This creates a window where the hardware can fetch a "torn" entry — where some fields are already zeroed while the 'Present' bit is still set — leading to unpredictable behavior or spurious faults. While x86 provides strong write ordering, the compiler may reorder writes to the two 64-bit halves of the context entry. Even without compiler reordering, the hardware fetch is not guaranteed to be atomic with respect to multiple CPU writes. Align with the "Guidance to Software for Invalidations" in the VT-d spec (Section 6.5.3.3) by implementing the recommended ownership handshake: 1. Clear only the 'Present' (P) bit of the context entry first to signal the transition of ownership from hardware to software. 2. Use dma_wmb() to ensure the cleared bit is visible to the IOMMU. 3. Perform the required cache and context-cache invalidation to ensure hardware no longer has cached references to the entry. 4. Fully zero out the entry only after the invalidation is complete. Also, add a dma_wmb() to context_set_present() to ensure the entry is fully initialized before the 'Present' bit becomes visible. Fixes: ba39592764ed2 ("Intel IOMMU: Intel IOMMU driver") Reported-by: Dmytro Maluka Closes: https://lore.kernel.org/all/aTG7gc7I5wExai3S@google.com/ Signed-off-by: Lu Baolu Reviewed-by: Dmytro Maluka Reviewed-by: Samiullah Khawaja Reviewed-by: Kevin Tian Link: https://lore.kernel.org/r/20260120061816.2132558-3-baolu.lu@linux.intel.com Signed-off-by: Joerg Roedel Signed-off-by: Sasha Levin --- drivers/iommu/intel/iommu.c | 4 +++- drivers/iommu/intel/iommu.h | 21 ++++++++++++++++++++- drivers/iommu/intel/pasid.c | 5 ++++- 3 files changed, 27 insertions(+), 3 deletions(-) diff --git a/drivers/iommu/intel/iommu.c b/drivers/iommu/intel/iommu.c index e236c7ec221f4b..49e83c8566a32f 100644 --- a/drivers/iommu/intel/iommu.c +++ b/drivers/iommu/intel/iommu.c @@ -1722,10 +1722,12 @@ static void domain_context_clear_one(struct device_domain_info *info, u8 bus, u8 } did = context_domain_id(context); - context_clear_entry(context); + context_clear_present(context); __iommu_flush_cache(iommu, context, sizeof(*context)); spin_unlock(&iommu->lock); intel_context_flush_no_pasid(info, context, did); + context_clear_entry(context); + __iommu_flush_cache(iommu, context, sizeof(*context)); } int __domain_setup_first_level(struct intel_iommu *iommu, struct device *dev, diff --git a/drivers/iommu/intel/iommu.h b/drivers/iommu/intel/iommu.h index dcc5466d35f933..9198ac7f6bbab2 100644 --- a/drivers/iommu/intel/iommu.h +++ b/drivers/iommu/intel/iommu.h @@ -969,7 +969,26 @@ static inline unsigned long lvl_to_nr_pages(unsigned int lvl) static inline void context_set_present(struct context_entry *context) { - context->lo |= 1; + u64 val; + + dma_wmb(); + val = READ_ONCE(context->lo) | 1; + WRITE_ONCE(context->lo, val); +} + +/* + * Clear the Present (P) bit (bit 0) of a context table entry. This initiates + * the transition of the entry's ownership from hardware to software. The + * caller is responsible for fulfilling the invalidation handshake recommended + * by the VT-d spec, Section 6.5.3.3 (Guidance to Software for Invalidations). + */ +static inline void context_clear_present(struct context_entry *context) +{ + u64 val; + + val = READ_ONCE(context->lo) & GENMASK_ULL(63, 1); + WRITE_ONCE(context->lo, val); + dma_wmb(); } static inline void context_set_fault_enable(struct context_entry *context) diff --git a/drivers/iommu/intel/pasid.c b/drivers/iommu/intel/pasid.c index f64b5ae306d0f2..d13099a6cb9c3e 100644 --- a/drivers/iommu/intel/pasid.c +++ b/drivers/iommu/intel/pasid.c @@ -1028,7 +1028,7 @@ static int device_pasid_table_setup(struct device *dev, u8 bus, u8 devfn) } if (context_copied(iommu, bus, devfn)) { - context_clear_entry(context); + context_clear_present(context); __iommu_flush_cache(iommu, context, sizeof(*context)); /* @@ -1048,6 +1048,9 @@ static int device_pasid_table_setup(struct device *dev, u8 bus, u8 devfn) iommu->flush.flush_iotlb(iommu, 0, 0, 0, DMA_TLB_GLOBAL_FLUSH); devtlb_invalidation_with_pasid(iommu, dev, IOMMU_NO_PASID); + context_clear_entry(context); + __iommu_flush_cache(iommu, context, sizeof(*context)); + /* * At this point, the device is supposed to finish reset at * its driver probe stage, so no in-flight DMA will exist, From 1bd726fa8f5cf64568b4e49595e6c518eb64cd85 Mon Sep 17 00:00:00 2001 From: Mikulas Patocka Date: Mon, 26 Jan 2026 15:36:22 +0100 Subject: [PATCH 3520/3902] dm: use bio_clone_blkg_association [ Upstream commit 2df8b310bcfe76827fd71092f58a2493ee6590b0 ] The origin bio carries blk-cgroup information which could be set from foreground(task_css(css) - wbc->wb->blkcg_css), so the blkcg won't control buffer io since commit ca522482e3eaf ("dm: pass NULL bdev to bio_alloc_clone"). The synchronous io is still under control by blkcg, because 'bio->bi_blkg' is set by io submitting task which has been added into 'cgroup.procs'. Fix it by using bio_clone_blkg_association when submitting a cloned bio. Link: https://bugzilla.kernel.org/show_bug.cgi?id=220985 Fixes: ca522482e3eaf ("dm: pass NULL bdev to bio_alloc_clone") Reported-by: Zhihao Cheng Signed-off-by: Mikulas Patocka Tested-by: Zhihao Cheng Signed-off-by: Sasha Levin --- drivers/md/dm.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/drivers/md/dm.c b/drivers/md/dm.c index 6c83ab940af752..52f01c44e73a6c 100644 --- a/drivers/md/dm.c +++ b/drivers/md/dm.c @@ -1363,6 +1363,8 @@ void dm_submit_bio_remap(struct bio *clone, struct bio *tgt_clone) if (!tgt_clone) tgt_clone = clone; + bio_clone_blkg_association(tgt_clone, io->orig_bio); + /* * Account io->origin_bio to DM dev on behalf of target * that took ownership of IO with DM_MAPIO_SUBMITTED. From 59409c5958b20ae07f59d5d1e8c48e5a389b99a8 Mon Sep 17 00:00:00 2001 From: Chuck Lever Date: Mon, 8 Dec 2025 11:15:32 -0500 Subject: [PATCH 3521/3902] xdrgen: Fix struct prefix for typedef types in program wrappers [ Upstream commit bf0fe9ad3d597d8e1378dc9953ca96dfc3addb2b ] The program templates for decoder/argument.j2 and encoder/result.j2 unconditionally add 'struct' prefix to all types. This is incorrect when an RPC protocol specification lists a typedef'd basic type or an enum as a procedure argument or result (e.g., NFSv2's fhandle or stat), resulting in compiler errors when building generated C code. Fixes: 4b132aacb076 ("tools: Add xdrgen") Signed-off-by: Chuck Lever Signed-off-by: Sasha Levin --- tools/net/sunrpc/xdrgen/generators/__init__.py | 3 ++- .../sunrpc/xdrgen/templates/C/program/decoder/argument.j2 | 4 ++++ .../net/sunrpc/xdrgen/templates/C/program/encoder/result.j2 | 6 ++++++ 3 files changed, 12 insertions(+), 1 deletion(-) diff --git a/tools/net/sunrpc/xdrgen/generators/__init__.py b/tools/net/sunrpc/xdrgen/generators/__init__.py index b98574a36a4ac3..a2eb6652ac900e 100644 --- a/tools/net/sunrpc/xdrgen/generators/__init__.py +++ b/tools/net/sunrpc/xdrgen/generators/__init__.py @@ -6,7 +6,7 @@ from jinja2 import Environment, FileSystemLoader, Template from xdr_ast import _XdrAst, Specification, _RpcProgram, _XdrTypeSpecifier -from xdr_ast import public_apis, pass_by_reference, get_header_name +from xdr_ast import public_apis, pass_by_reference, structs, get_header_name from xdr_parse import get_xdr_annotate @@ -22,6 +22,7 @@ def create_jinja2_environment(language: str, xdr_type: str) -> Environment: environment.globals["annotate"] = get_xdr_annotate() environment.globals["public_apis"] = public_apis environment.globals["pass_by_reference"] = pass_by_reference + environment.globals["structs"] = structs return environment case _: raise NotImplementedError("Language not supported") diff --git a/tools/net/sunrpc/xdrgen/templates/C/program/decoder/argument.j2 b/tools/net/sunrpc/xdrgen/templates/C/program/decoder/argument.j2 index 0b1709cca0d4a4..19b219dd276d3e 100644 --- a/tools/net/sunrpc/xdrgen/templates/C/program/decoder/argument.j2 +++ b/tools/net/sunrpc/xdrgen/templates/C/program/decoder/argument.j2 @@ -14,7 +14,11 @@ bool {{ program }}_svc_decode_{{ argument }}(struct svc_rqst *rqstp, struct xdr_ {% if argument == 'void' %} return xdrgen_decode_void(xdr); {% else %} +{% if argument in structs %} struct {{ argument }} *argp = rqstp->rq_argp; +{% else %} + {{ argument }} *argp = rqstp->rq_argp; +{% endif %} return xdrgen_decode_{{ argument }}(xdr, argp); {% endif %} diff --git a/tools/net/sunrpc/xdrgen/templates/C/program/encoder/result.j2 b/tools/net/sunrpc/xdrgen/templates/C/program/encoder/result.j2 index 6fc61a5d47b7f5..746592cfda5628 100644 --- a/tools/net/sunrpc/xdrgen/templates/C/program/encoder/result.j2 +++ b/tools/net/sunrpc/xdrgen/templates/C/program/encoder/result.j2 @@ -14,8 +14,14 @@ bool {{ program }}_svc_encode_{{ result }}(struct svc_rqst *rqstp, struct xdr_st {% if result == 'void' %} return xdrgen_encode_void(xdr); {% else %} +{% if result in structs %} struct {{ result }} *resp = rqstp->rq_resp; return xdrgen_encode_{{ result }}(xdr, resp); +{% else %} + {{ result }} *resp = rqstp->rq_resp; + + return xdrgen_encode_{{ result }}(xdr, *resp); +{% endif %} {% endif %} } From 59d23891cdba5c8bd61b98309f1231caf7d2a41d Mon Sep 17 00:00:00 2001 From: Chuck Lever Date: Tue, 9 Dec 2025 19:28:50 -0500 Subject: [PATCH 3522/3902] NFS: NFSERR_INVAL is not defined by NFSv2 [ Upstream commit 0ac903d1bfdce8ff40657c2b7d996947b72b6645 ] A documenting comment in include/uapi/linux/nfs.h claims incorrectly that NFSv2 defines NFSERR_INVAL. There is no such definition in either RFC 1094 or https://pubs.opengroup.org/onlinepubs/9629799/chap7.htm NFS3ERR_INVAL is introduced in RFC 1813. NFSD returns NFSERR_INVAL for PROC_GETACL, which has no specification (yet). However, nfsd_map_status() maps nfserr_symlink and nfserr_wrong_type to nfserr_inval, which does not align with RFC 1094. This logic was introduced only recently by commit 438f81e0e92a ("nfsd: move error choice for incorrect object types to version-specific code."). Given that we have no INVAL or SERVERFAULT status in NFSv2, probably the only choice is NFSERR_IO. Fixes: 438f81e0e92a ("nfsd: move error choice for incorrect object types to version-specific code.") Reviewed-by: NeilBrown Reviewed-by: Jeff Layton Signed-off-by: Chuck Lever Signed-off-by: Sasha Levin --- fs/nfsd/nfs2acl.c | 2 +- fs/nfsd/nfsproc.c | 2 +- include/uapi/linux/nfs.h | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/fs/nfsd/nfs2acl.c b/fs/nfsd/nfs2acl.c index 5fb202acb0fd00..0ac538c7618009 100644 --- a/fs/nfsd/nfs2acl.c +++ b/fs/nfsd/nfs2acl.c @@ -45,7 +45,7 @@ static __be32 nfsacld_proc_getacl(struct svc_rqst *rqstp) inode = d_inode(fh->fh_dentry); if (argp->mask & ~NFS_ACL_MASK) { - resp->status = nfserr_inval; + resp->status = nfserr_io; goto out; } resp->mask = argp->mask; diff --git a/fs/nfsd/nfsproc.c b/fs/nfsd/nfsproc.c index 8f71f5748c75b6..906a6725789008 100644 --- a/fs/nfsd/nfsproc.c +++ b/fs/nfsd/nfsproc.c @@ -33,7 +33,7 @@ static __be32 nfsd_map_status(__be32 status) break; case nfserr_symlink: case nfserr_wrong_type: - status = nfserr_inval; + status = nfserr_io; break; } return status; diff --git a/include/uapi/linux/nfs.h b/include/uapi/linux/nfs.h index 71c7196d32817d..e629c495353456 100644 --- a/include/uapi/linux/nfs.h +++ b/include/uapi/linux/nfs.h @@ -55,7 +55,7 @@ NFSERR_NODEV = 19, /* v2 v3 v4 */ NFSERR_NOTDIR = 20, /* v2 v3 v4 */ NFSERR_ISDIR = 21, /* v2 v3 v4 */ - NFSERR_INVAL = 22, /* v2 v3 v4 */ + NFSERR_INVAL = 22, /* v3 v4 */ NFSERR_FBIG = 27, /* v2 v3 v4 */ NFSERR_NOSPC = 28, /* v2 v3 v4 */ NFSERR_ROFS = 30, /* v2 v3 v4 */ From b633683dc0d4c6b733108f017ffb81b8581f4035 Mon Sep 17 00:00:00 2001 From: Chuck Lever Date: Sat, 20 Dec 2025 10:41:09 -0500 Subject: [PATCH 3523/3902] xdrgen: Initialize data pointer for zero-length items [ Upstream commit 27b0fcae8f535fb882b1876227a935dcfdf576aa ] The xdrgen decoders for strings and opaque data had an optimization that skipped calling xdr_inline_decode() when the item length was zero. This left the data pointer uninitialized, which could lead to unpredictable behavior when callers access it. Remove the zero-length check and always call xdr_inline_decode(). When passed a length of zero, xdr_inline_decode() returns the current buffer position, which is valid and matches the behavior of hand-coded XDR decoders throughout the kernel. Fixes: 4b132aacb076 ("tools: Add xdrgen") Reviewed-by: Jeff Layton Reviewed-by: NeilBrown Signed-off-by: Chuck Lever Signed-off-by: Sasha Levin --- include/linux/sunrpc/xdrgen/_builtins.h | 20 ++++++++------------ 1 file changed, 8 insertions(+), 12 deletions(-) diff --git a/include/linux/sunrpc/xdrgen/_builtins.h b/include/linux/sunrpc/xdrgen/_builtins.h index 66ca3ece951ab9..a5ab75d2db0446 100644 --- a/include/linux/sunrpc/xdrgen/_builtins.h +++ b/include/linux/sunrpc/xdrgen/_builtins.h @@ -188,12 +188,10 @@ xdrgen_decode_string(struct xdr_stream *xdr, string *ptr, u32 maxlen) return false; if (unlikely(maxlen && len > maxlen)) return false; - if (len != 0) { - p = xdr_inline_decode(xdr, len); - if (unlikely(!p)) - return false; - ptr->data = (unsigned char *)p; - } + p = xdr_inline_decode(xdr, len); + if (unlikely(!p)) + return false; + ptr->data = (unsigned char *)p; ptr->len = len; return true; } @@ -219,12 +217,10 @@ xdrgen_decode_opaque(struct xdr_stream *xdr, opaque *ptr, u32 maxlen) return false; if (unlikely(maxlen && len > maxlen)) return false; - if (len != 0) { - p = xdr_inline_decode(xdr, len); - if (unlikely(!p)) - return false; - ptr->data = (u8 *)p; - } + p = xdr_inline_decode(xdr, len); + if (unlikely(!p)) + return false; + ptr->data = (u8 *)p; ptr->len = len; return true; } From b29094276ab222ba7397c90ba0cedece7233c333 Mon Sep 17 00:00:00 2001 From: Chuck Lever Date: Mon, 22 Dec 2025 09:44:29 -0500 Subject: [PATCH 3524/3902] xdrgen: Remove inclusion of nlm4.h header [ Upstream commit eb1f3b55ac6202a013daf14ed508066947cdafa8 ] The client-side source code template mistakenly includes the nlm4.h header file, which is specific to the NLM protocol and should not be present in the generic template that generates client stubs for all XDR-based protocols. Fixes: 903a7d37d9ea ("xdrgen: Update the files included in client-side source code") Signed-off-by: Chuck Lever Signed-off-by: Sasha Levin --- tools/net/sunrpc/xdrgen/templates/C/source_top/client.j2 | 1 - 1 file changed, 1 deletion(-) diff --git a/tools/net/sunrpc/xdrgen/templates/C/source_top/client.j2 b/tools/net/sunrpc/xdrgen/templates/C/source_top/client.j2 index c5518c519854a0..df3598c38b2c29 100644 --- a/tools/net/sunrpc/xdrgen/templates/C/source_top/client.j2 +++ b/tools/net/sunrpc/xdrgen/templates/C/source_top/client.j2 @@ -8,6 +8,5 @@ #include #include #include -#include #include From d75ec4504a4340b033b15cad0303988b3089dd93 Mon Sep 17 00:00:00 2001 From: Anthony Iliopoulos Date: Mon, 22 Dec 2025 14:30:04 -0500 Subject: [PATCH 3525/3902] nfsd: never defer requests during idmap lookup [ Upstream commit f9c206cdc4266caad6a9a7f46341420a10f03ccb ] During v4 request compound arg decoding, some ops (e.g. SETATTR) can trigger idmap lookup upcalls. When those upcall responses get delayed beyond the allowed time limit, cache_check() will mark the request for deferral and cause it to be dropped. This prevents nfs4svc_encode_compoundres from being executed, and thus the session slot flag NFSD4_SLOT_INUSE never gets cleared. Subsequent client requests will fail with NFSERR_JUKEBOX, given that the slot will be marked as in-use, making the SEQUENCE op fail. Fix this by making sure that the RQ_USEDEFERRAL flag is always clear during nfs4svc_decode_compoundargs(), since no v4 request should ever be deferred. Fixes: 2f425878b6a7 ("nfsd: don't use the deferral service, return NFS4ERR_DELAY") Signed-off-by: Anthony Iliopoulos Reviewed-by: NeilBrown Signed-off-by: Chuck Lever Signed-off-by: Sasha Levin --- fs/nfsd/nfs4idmap.c | 48 +++++++++++++++++++++++++++++++++++++++------ fs/nfsd/nfs4proc.c | 2 -- fs/nfsd/nfs4xdr.c | 16 +++++++++++++++ 3 files changed, 58 insertions(+), 8 deletions(-) diff --git a/fs/nfsd/nfs4idmap.c b/fs/nfsd/nfs4idmap.c index 8cca1329f3485c..b5b3d45979c9b3 100644 --- a/fs/nfsd/nfs4idmap.c +++ b/fs/nfsd/nfs4idmap.c @@ -643,13 +643,31 @@ static __be32 encode_name_from_id(struct xdr_stream *xdr, return idmap_id_to_name(xdr, rqstp, type, id); } -__be32 -nfsd_map_name_to_uid(struct svc_rqst *rqstp, const char *name, size_t namelen, - kuid_t *uid) +/** + * nfsd_map_name_to_uid - Map user@domain to local UID + * @rqstp: RPC execution context + * @name: user@domain name to be mapped + * @namelen: length of name, in bytes + * @uid: OUT: mapped local UID value + * + * Returns nfs_ok on success or an NFSv4 status code on failure. + */ +__be32 nfsd_map_name_to_uid(struct svc_rqst *rqstp, const char *name, + size_t namelen, kuid_t *uid) { __be32 status; u32 id = -1; + /* + * The idmap lookup below triggers an upcall that invokes + * cache_check(). RQ_USEDEFERRAL must be clear to prevent + * cache_check() from setting RQ_DROPME via svc_defer(). + * NFSv4 servers are not permitted to drop requests. Also + * RQ_DROPME will force NFSv4.1 session slot processing to + * be skipped. + */ + WARN_ON_ONCE(test_bit(RQ_USEDEFERRAL, &rqstp->rq_flags)); + if (name == NULL || namelen == 0) return nfserr_inval; @@ -660,13 +678,31 @@ nfsd_map_name_to_uid(struct svc_rqst *rqstp, const char *name, size_t namelen, return status; } -__be32 -nfsd_map_name_to_gid(struct svc_rqst *rqstp, const char *name, size_t namelen, - kgid_t *gid) +/** + * nfsd_map_name_to_gid - Map user@domain to local GID + * @rqstp: RPC execution context + * @name: user@domain name to be mapped + * @namelen: length of name, in bytes + * @gid: OUT: mapped local GID value + * + * Returns nfs_ok on success or an NFSv4 status code on failure. + */ +__be32 nfsd_map_name_to_gid(struct svc_rqst *rqstp, const char *name, + size_t namelen, kgid_t *gid) { __be32 status; u32 id = -1; + /* + * The idmap lookup below triggers an upcall that invokes + * cache_check(). RQ_USEDEFERRAL must be clear to prevent + * cache_check() from setting RQ_DROPME via svc_defer(). + * NFSv4 servers are not permitted to drop requests. Also + * RQ_DROPME will force NFSv4.1 session slot processing to + * be skipped. + */ + WARN_ON_ONCE(test_bit(RQ_USEDEFERRAL, &rqstp->rq_flags)); + if (name == NULL || namelen == 0) return nfserr_inval; diff --git a/fs/nfsd/nfs4proc.c b/fs/nfsd/nfs4proc.c index 42a6b914c0fe64..8dada7ef97cb17 100644 --- a/fs/nfsd/nfs4proc.c +++ b/fs/nfsd/nfs4proc.c @@ -2995,8 +2995,6 @@ nfsd4_proc_compound(struct svc_rqst *rqstp) BUG_ON(cstate->replay_owner); out: cstate->status = status; - /* Reset deferral mechanism for RPC deferrals */ - set_bit(RQ_USEDEFERRAL, &rqstp->rq_flags); return rpc_success; } diff --git a/fs/nfsd/nfs4xdr.c b/fs/nfsd/nfs4xdr.c index 4a403ce4fd468c..5f046d5be4a6e2 100644 --- a/fs/nfsd/nfs4xdr.c +++ b/fs/nfsd/nfs4xdr.c @@ -6001,6 +6001,22 @@ nfs4svc_decode_compoundargs(struct svc_rqst *rqstp, struct xdr_stream *xdr) args->ops = args->iops; args->rqstp = rqstp; + /* + * NFSv4 operation decoders can invoke svc cache lookups + * that trigger svc_defer() when RQ_USEDEFERRAL is set, + * setting RQ_DROPME. This creates two problems: + * + * 1. Non-idempotency: Compounds make it too hard to avoid + * problems if a request is deferred and replayed. + * + * 2. Session slot leakage (NFSv4.1+): If RQ_DROPME is set + * during decode but SEQUENCE executes successfully, the + * session slot will be marked INUSE. The request is then + * dropped before encoding, so the slot is never released, + * rendering it permanently unusable by the client. + */ + clear_bit(RQ_USEDEFERRAL, &rqstp->rq_flags); + return nfsd4_decode_compound(args); } From bd1d06eaad53d576bee297f361645b1f4ca7e92c Mon Sep 17 00:00:00 2001 From: Chaitanya Mishra Date: Sat, 27 Dec 2025 14:52:29 +0530 Subject: [PATCH 3526/3902] lib/kstrtox: fix kstrtobool() docstring to mention enabled/disabled [ Upstream commit 1921044eebf1d6861a6de1a76e3f63729a45e712 ] Commit ae5b3500856f ("kstrtox: add support for enabled and disabled in kstrtobool()") added support for 'e'/'E' (enabled) and 'd'/'D' (disabled) inputs, but did not update the docstring accordingly. Update the docstring to include 'Ee' (for true) and 'Dd' (for false) in the list of accepted first characters. Link: https://lkml.kernel.org/r/20251227092229.57330-1-chaitanyamishra.ai@gmail.com Fixes: ae5b3500856f ("kstrtox: add support for enabled and disabled in kstrtobool()") Signed-off-by: Chaitanya Mishra Cc: Mario Limonciello Signed-off-by: Andrew Morton Signed-off-by: Sasha Levin --- lib/kstrtox.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/kstrtox.c b/lib/kstrtox.c index bdde40cd69d789..97be2a39f53710 100644 --- a/lib/kstrtox.c +++ b/lib/kstrtox.c @@ -340,8 +340,8 @@ EXPORT_SYMBOL(kstrtos8); * @s: input string * @res: result * - * This routine returns 0 iff the first character is one of 'YyTt1NnFf0', or - * [oO][NnFf] for "on" and "off". Otherwise it will return -EINVAL. Value + * This routine returns 0 iff the first character is one of 'EeYyTt1DdNnFf0', + * or [oO][NnFf] for "on" and "off". Otherwise it will return -EINVAL. Value * pointed to by res is updated upon finding a match. */ noinline From a77e945c528f012ac593e25ef320c45502ee7316 Mon Sep 17 00:00:00 2001 From: Alice Ryhl Date: Wed, 7 Jan 2026 08:28:46 +0000 Subject: [PATCH 3527/3902] rust: task: restrict Task::group_leader() to current MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [ Upstream commit 105ddfb2d2b3acec7a7d9695463df48733d91e6c ] The Task::group_leader() method currently allows you to access the group_leader() of any task, for example one you hold a refcount to. But this is not safe in general since the group leader could change when a task exits. See for example commit a15f37a40145c ("kernel/sys.c: fix the racy usage of task_lock(tsk->group_leader) in sys_prlimit64() paths"). All existing users of Task::group_leader() call this method on current, which is guaranteed running, so there's not an actual issue in Rust code today. But to prevent code in the future from making this mistake, restrict Task::group_leader() so that it can only be called on current. There are some other cases where accessing task->group_leader is okay. For example it can be safe if you hold tasklist_lock or rcu_read_lock(). However, only supporting current->group_leader is sufficient for all in-tree Rust users of group_leader right now. Safe Rust functionality for accessing it under rcu or while holding tasklist_lock may be added in the future if required by any future Rust module. This patch is a bugfix in that it prevents users of this API from writing incorrect code. It doesn't change behavior of correct code. Link: https://lkml.kernel.org/r/20260107-task-group-leader-v2-1-8fbf816f2a2f@google.com Signed-off-by: Alice Ryhl Fixes: 313c4281bc9d ("rust: add basic `Task`") Reported-by: Oleg Nesterov Closes: https://lore.kernel.org/all/aTLnV-5jlgfk1aRK@redhat.com/ Reviewed-by: Boqun Feng Reviewed-by: Gary Guo Cc: Andreas Hindborg Cc: Benno Lossin Cc: "Björn Roy Baron" Cc: Björn Roy Baron Cc: Christian Brauner Cc: Danilo Krummrich Cc: FUJITA Tomonori Cc: Miguel Ojeda Cc: Panagiotis Foliadis Cc: Shankari Anand Cc: Trevor Gross Signed-off-by: Andrew Morton Signed-off-by: Sasha Levin --- rust/kernel/task.rs | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/rust/kernel/task.rs b/rust/kernel/task.rs index 49fad6de06740a..cc907fb531bcee 100644 --- a/rust/kernel/task.rs +++ b/rust/kernel/task.rs @@ -204,18 +204,6 @@ impl Task { self.0.get() } - /// Returns the group leader of the given task. - pub fn group_leader(&self) -> &Task { - // SAFETY: The group leader of a task never changes after initialization, so reading this - // field is not a data race. - let ptr = unsafe { *ptr::addr_of!((*self.as_ptr()).group_leader) }; - - // SAFETY: The lifetime of the returned task reference is tied to the lifetime of `self`, - // and given that a task has a reference to its group leader, we know it must be valid for - // the lifetime of the returned task reference. - unsafe { &*ptr.cast() } - } - /// Returns the PID of the given task. pub fn pid(&self) -> Pid { // SAFETY: The pid of a task never changes after initialization, so reading this field is @@ -345,6 +333,18 @@ impl CurrentTask { // `release_task()` call. Some(unsafe { PidNamespace::from_ptr(active_ns) }) } + + /// Returns the group leader of the current task. + pub fn group_leader(&self) -> &Task { + // SAFETY: The group leader of a task never changes while the task is running, and `self` + // is the current task, which is guaranteed running. + let ptr = unsafe { (*self.as_ptr()).group_leader }; + + // SAFETY: `current->group_leader` stays valid for at least the duration in which `current` + // is running, and the signature of this function ensures that the returned `&Task` can + // only be used while `current` is still valid, thus still running. + unsafe { &*ptr.cast() } + } } // SAFETY: The type invariants guarantee that `Task` is always refcounted. From 17866f8a0822d414cb02e621cf003a7d04396ef8 Mon Sep 17 00:00:00 2001 From: Zhiyu Zhang Date: Thu, 1 Jan 2026 19:11:48 +0800 Subject: [PATCH 3528/3902] fat: avoid parent link count underflow in rmdir [ Upstream commit 8cafcb881364af5ef3a8b9fed4db254054033d8a ] Corrupted FAT images can leave a directory inode with an incorrect i_nlink (e.g. 2 even though subdirectories exist). rmdir then unconditionally calls drop_nlink(dir) and can drive i_nlink to 0, triggering the WARN_ON in drop_nlink(). Add a sanity check in vfat_rmdir() and msdos_rmdir(): only drop the parent link count when it is at least 3, otherwise report a filesystem error. Link: https://lkml.kernel.org/r/20260101111148.1437-1-zhiyuzhang999@gmail.com Fixes: 9a53c3a783c2 ("[PATCH] r/o bind mounts: unlink: monitor i_nlink") Signed-off-by: Zhiyu Zhang Reported-by: Zhiyu Zhang Closes: https://lore.kernel.org/linux-fsdevel/aVN06OKsKxZe6-Kv@casper.infradead.org/T/#t Tested-by: Zhiyu Zhang Acked-by: OGAWA Hirofumi Cc: Al Viro Cc: Christian Brauner Cc: Jan Kara Signed-off-by: Andrew Morton Signed-off-by: Sasha Levin --- fs/fat/namei_msdos.c | 7 ++++++- fs/fat/namei_vfat.c | 7 ++++++- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/fs/fat/namei_msdos.c b/fs/fat/namei_msdos.c index 0b920ee40a7f9f..262ec1b790b560 100644 --- a/fs/fat/namei_msdos.c +++ b/fs/fat/namei_msdos.c @@ -325,7 +325,12 @@ static int msdos_rmdir(struct inode *dir, struct dentry *dentry) err = fat_remove_entries(dir, &sinfo); /* and releases bh */ if (err) goto out; - drop_nlink(dir); + if (dir->i_nlink >= 3) + drop_nlink(dir); + else { + fat_fs_error(sb, "parent dir link count too low (%u)", + dir->i_nlink); + } clear_nlink(inode); fat_truncate_time(inode, NULL, S_CTIME); diff --git a/fs/fat/namei_vfat.c b/fs/fat/namei_vfat.c index 5dbc4cbb8fce3d..47ff083cfc7e66 100644 --- a/fs/fat/namei_vfat.c +++ b/fs/fat/namei_vfat.c @@ -803,7 +803,12 @@ static int vfat_rmdir(struct inode *dir, struct dentry *dentry) err = fat_remove_entries(dir, &sinfo); /* and releases bh */ if (err) goto out; - drop_nlink(dir); + if (dir->i_nlink >= 3) + drop_nlink(dir); + else { + fat_fs_error(sb, "parent dir link count too low (%u)", + dir->i_nlink); + } clear_nlink(inode); fat_truncate_time(inode, NULL, S_ATIME|S_MTIME); From d5de3e34dcb5a2de5e8947b303be0a560cbdb770 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ilpo=20J=C3=A4rvinen?= Date: Fri, 19 Dec 2025 19:40:15 +0200 Subject: [PATCH 3529/3902] PCI: Rewrite bridge window head alignment function MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [ Upstream commit bc75c8e5071120e919beb39e69f0979cccfdf219 ] The calculation of bridge window head alignment is done by calculate_mem_align() [*]. With the default bridge window alignment, it is used for both head and tail alignment. The selected head alignment does not always result in tight-fitting resources (gap at d4f00000-d4ffffff): d4800000-dbffffff : PCI Bus 0000:06 d4800000-d48fffff : PCI Bus 0000:07 d4800000-d4803fff : 0000:07:00.0 d4800000-d4803fff : nvme d4900000-d49fffff : PCI Bus 0000:0a d4900000-d490ffff : 0000:0a:00.0 d4900000-d490ffff : r8169 d4910000-d4913fff : 0000:0a:00.0 d4a00000-d4cfffff : PCI Bus 0000:0b d4a00000-d4bfffff : 0000:0b:00.0 d4a00000-d4bfffff : 0000:0b:00.0 d4c00000-d4c07fff : 0000:0b:00.0 d4d00000-d4dfffff : PCI Bus 0000:15 d4d00000-d4d07fff : 0000:15:00.0 d4d00000-d4d07fff : xhci-hcd d4e00000-d4efffff : PCI Bus 0000:16 d4e00000-d4e7ffff : 0000:16:00.0 d4e80000-d4e803ff : 0000:16:00.0 d4e80000-d4e803ff : ahci d5000000-dbffffff : PCI Bus 0000:0c This has not caused problems (for years) with the default bridge window tail alignment that grossly over-estimates the required tail alignment leaving more tail room than necessary. With the introduction of relaxed tail alignment that leaves no extra tail room whatsoever, any gaps will immediately turn into assignment failures. Introduce head alignment calculation that ensures no gaps are left and apply the new approach when using relaxed alignment. We may want to consider using it for the normal alignment eventually, but as the first step, solve only the problem with the relaxed tail alignment. ([*] I don't understand the algorithm in calculate_mem_align().) Link: https://git.kernel.org/history/history/c/5d0a8965aea9 ("[PATCH] 2.5.14: New PCI allocation code (alpha, arm, parisc) [2/2]") Closes: https://bugzilla.kernel.org/show_bug.cgi?id=220775 Reported-by: Malte Schröder Signed-off-by: Ilpo Järvinen Signed-off-by: Bjorn Helgaas Tested-by: Malte Schröder Cc: stable@vger.kernel.org Link: https://patch.msgid.link/20251219174036.16738-3-ilpo.jarvinen@linux.intel.com Stable-dep-of: f909e3ee3ed1 ("PCI: Remove old_size limit from bridge window sizing") Signed-off-by: Sasha Levin --- drivers/pci/setup-bus.c | 53 ++++++++++++++++++++++++++++++++++------- 1 file changed, 44 insertions(+), 9 deletions(-) diff --git a/drivers/pci/setup-bus.c b/drivers/pci/setup-bus.c index 5ba878f15db353..cd12926a72af7e 100644 --- a/drivers/pci/setup-bus.c +++ b/drivers/pci/setup-bus.c @@ -1223,6 +1223,45 @@ static inline resource_size_t calculate_mem_align(resource_size_t *aligns, return min_align; } +/* + * Calculate bridge window head alignment that leaves no gaps in between + * resources. + */ +static resource_size_t calculate_head_align(resource_size_t *aligns, + int max_order) +{ + resource_size_t head_align = 1; + resource_size_t remainder = 0; + int order; + + /* Take the largest alignment as the starting point. */ + head_align <<= max_order + __ffs(SZ_1M); + + for (order = max_order - 1; order >= 0; order--) { + resource_size_t align1 = 1; + + align1 <<= order + __ffs(SZ_1M); + + /* + * Account smaller resources with alignment < max_order that + * could be used to fill head room if alignment less than + * max_order is used. + */ + remainder += aligns[order]; + + /* + * Test if head fill is enough to satisfy the alignment of + * the larger resources after reducing the alignment. + */ + while ((head_align > align1) && (remainder >= head_align / 2)) { + head_align /= 2; + remainder -= head_align; + } + } + + return head_align; +} + /** * pbus_upstream_space_available - Check no upstream resource limits allocation * @bus: The bus @@ -1310,13 +1349,13 @@ static void pbus_size_mem(struct pci_bus *bus, unsigned long type, { struct pci_dev *dev; resource_size_t min_align, win_align, align, size, size0, size1 = 0; - resource_size_t aligns[28]; /* Alignments from 1MB to 128TB */ + resource_size_t aligns[28] = {}; /* Alignments from 1MB to 128TB */ + resource_size_t aligns2[28] = {};/* Alignments from 1MB to 128TB */ int order, max_order; struct resource *b_res = pbus_select_window_for_type(bus, type); resource_size_t children_add_size = 0; resource_size_t children_add_align = 0; resource_size_t add_align = 0; - resource_size_t relaxed_align; resource_size_t old_size; if (!b_res) @@ -1326,7 +1365,6 @@ static void pbus_size_mem(struct pci_bus *bus, unsigned long type, if (b_res->parent) return; - memset(aligns, 0, sizeof(aligns)); max_order = 0; size = 0; @@ -1377,6 +1415,7 @@ static void pbus_size_mem(struct pci_bus *bus, unsigned long type, */ if (r_size <= align) aligns[order] += align; + aligns2[order] += align; if (order > max_order) max_order = order; @@ -1401,9 +1440,7 @@ static void pbus_size_mem(struct pci_bus *bus, unsigned long type, if (bus->self && size0 && !pbus_upstream_space_available(bus, b_res, size0, min_align)) { - relaxed_align = 1ULL << (max_order + __ffs(SZ_1M)); - relaxed_align = max(relaxed_align, win_align); - min_align = min(min_align, relaxed_align); + min_align = calculate_head_align(aligns2, max_order); size0 = calculate_memsize(size, min_size, 0, 0, old_size, win_align); resource_set_range(b_res, min_align, size0); pci_info(bus->self, "bridge window %pR to %pR requires relaxed alignment rules\n", @@ -1417,9 +1454,7 @@ static void pbus_size_mem(struct pci_bus *bus, unsigned long type, if (bus->self && size1 && !pbus_upstream_space_available(bus, b_res, size1, add_align)) { - relaxed_align = 1ULL << (max_order + __ffs(SZ_1M)); - relaxed_align = max(relaxed_align, win_align); - min_align = min(min_align, relaxed_align); + min_align = calculate_head_align(aligns2, max_order); size1 = calculate_memsize(size, min_size, add_size, children_add_size, old_size, win_align); pci_info(bus->self, From b855d994862190772dc09d47a1a12e82f3ad3a9e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ilpo=20J=C3=A4rvinen?= Date: Fri, 19 Dec 2025 19:40:16 +0200 Subject: [PATCH 3530/3902] PCI: Stop over-estimating bridge window size MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [ Upstream commit 3958bf16e2fe1b1c95467e58694102122c951a31 ] New way to calculate the bridge window head alignment produces tight-fit, that is, it does not leave any gaps between the resources. Similarly, relaxed tail alignment does not leave extra tail room. Start to use bridge window calculation that does not over-estimate the size of the required window. pbus_upstream_space_available() can be removed. Signed-off-by: Ilpo Järvinen Signed-off-by: Bjorn Helgaas Tested-by: Malte Schröder Link: https://patch.msgid.link/20251219174036.16738-4-ilpo.jarvinen@linux.intel.com Stable-dep-of: f909e3ee3ed1 ("PCI: Remove old_size limit from bridge window sizing") Signed-off-by: Sasha Levin --- drivers/pci/setup-bus.c | 97 +++-------------------------------------- 1 file changed, 5 insertions(+), 92 deletions(-) diff --git a/drivers/pci/setup-bus.c b/drivers/pci/setup-bus.c index cd12926a72af7e..25d6d4d3afc14c 100644 --- a/drivers/pci/setup-bus.c +++ b/drivers/pci/setup-bus.c @@ -1262,68 +1262,6 @@ static resource_size_t calculate_head_align(resource_size_t *aligns, return head_align; } -/** - * pbus_upstream_space_available - Check no upstream resource limits allocation - * @bus: The bus - * @res: The resource to help select the correct bridge window - * @size: The size required from the bridge window - * @align: Required alignment for the resource - * - * Check that @size can fit inside the upstream bridge resources that are - * already assigned. Select the upstream bridge window based on the type of - * @res. - * - * Return: %true if enough space is available on all assigned upstream - * resources. - */ -static bool pbus_upstream_space_available(struct pci_bus *bus, - struct resource *res, - resource_size_t size, - resource_size_t align) -{ - struct resource_constraint constraint = { - .max = RESOURCE_SIZE_MAX, - .align = align, - }; - struct pci_bus *downstream = bus; - - while ((bus = bus->parent)) { - if (pci_is_root_bus(bus)) - break; - - res = pbus_select_window(bus, res); - if (!res) - return false; - if (!res->parent) - continue; - - if (resource_size(res) >= size) { - struct resource gap = {}; - - if (find_resource_space(res, &gap, size, &constraint) == 0) { - gap.flags = res->flags; - pci_dbg(bus->self, - "Assigned bridge window %pR to %pR free space at %pR\n", - res, &bus->busn_res, &gap); - return true; - } - } - - if (bus->self) { - pci_info(bus->self, - "Assigned bridge window %pR to %pR cannot fit 0x%llx required for %s bridging to %pR\n", - res, &bus->busn_res, - (unsigned long long)size, - pci_name(downstream->self), - &downstream->busn_res); - } - - return false; - } - - return true; -} - /** * pbus_size_mem() - Size the memory window of a given bus * @@ -1350,7 +1288,6 @@ static void pbus_size_mem(struct pci_bus *bus, unsigned long type, struct pci_dev *dev; resource_size_t min_align, win_align, align, size, size0, size1 = 0; resource_size_t aligns[28] = {}; /* Alignments from 1MB to 128TB */ - resource_size_t aligns2[28] = {};/* Alignments from 1MB to 128TB */ int order, max_order; struct resource *b_res = pbus_select_window_for_type(bus, type); resource_size_t children_add_size = 0; @@ -1409,13 +1346,8 @@ static void pbus_size_mem(struct pci_bus *bus, unsigned long type, continue; } size += max(r_size, align); - /* - * Exclude ranges with size > align from calculation of - * the alignment. - */ - if (r_size <= align) - aligns[order] += align; - aligns2[order] += align; + + aligns[order] += align; if (order > max_order) max_order = order; @@ -1429,38 +1361,19 @@ static void pbus_size_mem(struct pci_bus *bus, unsigned long type, old_size = resource_size(b_res); win_align = window_alignment(bus, b_res->flags); - min_align = calculate_mem_align(aligns, max_order); + min_align = calculate_head_align(aligns, max_order); min_align = max(min_align, win_align); - size0 = calculate_memsize(size, min_size, 0, 0, old_size, min_align); + size0 = calculate_memsize(size, min_size, 0, 0, old_size, win_align); if (size0) { resource_set_range(b_res, min_align, size0); b_res->flags &= ~IORESOURCE_DISABLED; } - if (bus->self && size0 && - !pbus_upstream_space_available(bus, b_res, size0, min_align)) { - min_align = calculate_head_align(aligns2, max_order); - size0 = calculate_memsize(size, min_size, 0, 0, old_size, win_align); - resource_set_range(b_res, min_align, size0); - pci_info(bus->self, "bridge window %pR to %pR requires relaxed alignment rules\n", - b_res, &bus->busn_res); - } - if (realloc_head && (add_size > 0 || children_add_size > 0)) { add_align = max(min_align, add_align); size1 = calculate_memsize(size, min_size, add_size, children_add_size, - old_size, add_align); - - if (bus->self && size1 && - !pbus_upstream_space_available(bus, b_res, size1, add_align)) { - min_align = calculate_head_align(aligns2, max_order); - size1 = calculate_memsize(size, min_size, add_size, children_add_size, - old_size, win_align); - pci_info(bus->self, - "bridge window %pR to %pR requires relaxed alignment rules\n", - b_res, &bus->busn_res); - } + old_size, win_align); } if (!size0 && !size1) { From 2a9370693b1ceed745edd831a691d6faa7aeb2dd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ilpo=20J=C3=A4rvinen?= Date: Fri, 19 Dec 2025 19:40:18 +0200 Subject: [PATCH 3531/3902] PCI: Remove old_size limit from bridge window sizing MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [ Upstream commit f909e3ee3ed1a44202f09ac7e637a0f9ec372225 ] calculate_memsize() applies lower bound to the resource size before aligning the resource size making it impossible to shrink bridge window resources. I've not found any justification for this lower bound and nothing indicated it was to work around some HW issue. Prior to the commit 3baeae36039a ("PCI: Use pci_release_resource() instead of release_resource()"), releasing a bridge window during BAR resize resulted in clearing start and end address of the resource. Clearing addresses destroys the resource size as a side-effect, therefore nullifying the effect of the old size lower bound. After the commit 3baeae36039a ("PCI: Use pci_release_resource() instead of release_resource()"), BAR resize uses the aligned old size, which results in exceeding what fits into the parent window in some cases: xe 0030:03:00.0: [drm] Attempting to resize bar from 256MiB -> 16384MiB xe 0030:03:00.0: BAR 0 [mem 0x620c000000000-0x620c000ffffff 64bit]: releasing xe 0030:03:00.0: BAR 2 [mem 0x6200000000000-0x620000fffffff 64bit pref]: releasing pci 0030:02:01.0: bridge window [mem 0x6200000000000-0x620001fffffff 64bit pref]: releasing pci 0030:01:00.0: bridge window [mem 0x6200000000000-0x6203fbff0ffff 64bit pref]: releasing pci 0030:00:00.0: bridge window [mem 0x6200000000000-0x6203fbff0ffff 64bit pref]: was not released (still contains assigned resources) pci 0030:00:00.0: Assigned bridge window [mem 0x6200000000000-0x6203fbff0ffff 64bit pref] to [bus 01-04] free space at [mem 0x6200400000000-0x62007ffffffff 64bit pref] pci 0030:00:00.0: Assigned bridge window [mem 0x6200000000000-0x6203fbff0ffff 64bit pref] to [bus 01-04] cannot fit 0x4000000000 required for 0030:01:00.0 bridging to [bus 02-04] The old size of 0x6200000000000-0x6203fbff0ffff resource was used as the lower bound which results in 0x4000000000 size request due to alignment. That exceeds what can fit into the parent window. Since the lower bound never even was enforced fully because the resource addresses were cleared when the bridge window is released, remove the old_size lower bound entirely and trust the calculated bridge window size is enough. This same problem may occur on io window side but seems less likely to cause issues due to general difference in alignment. Removing the lower bound may have other unforeseen consequences in case of io window so it's better to leave it as -next material if no problem is reported related to io window sizing (BAR resize shouldn't touch io windows anyway). Fixes: 3baeae36039a ("PCI: Use pci_release_resource() instead of release_resource()") Reported-by: Simon Richter Link: https://lore.kernel.org/r/f9a8c975-f5d3-4dd2-988e-4371a1433a60@hogyros.de/ Signed-off-by: Ilpo Järvinen Signed-off-by: Bjorn Helgaas Link: https://patch.msgid.link/20251219174036.16738-6-ilpo.jarvinen@linux.intel.com Signed-off-by: Sasha Levin --- drivers/pci/setup-bus.c | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/drivers/pci/setup-bus.c b/drivers/pci/setup-bus.c index 25d6d4d3afc14c..4f4890196e63e4 100644 --- a/drivers/pci/setup-bus.c +++ b/drivers/pci/setup-bus.c @@ -1066,16 +1066,13 @@ static resource_size_t calculate_memsize(resource_size_t size, resource_size_t min_size, resource_size_t add_size, resource_size_t children_add_size, - resource_size_t old_size, resource_size_t align) { if (size < min_size) size = min_size; - if (old_size == 1) - old_size = 0; size = max(size, add_size) + children_add_size; - return ALIGN(max(size, old_size), align); + return ALIGN(size, align); } resource_size_t __weak pcibios_window_alignment(struct pci_bus *bus, @@ -1293,7 +1290,6 @@ static void pbus_size_mem(struct pci_bus *bus, unsigned long type, resource_size_t children_add_size = 0; resource_size_t children_add_align = 0; resource_size_t add_align = 0; - resource_size_t old_size; if (!b_res) return; @@ -1359,11 +1355,10 @@ static void pbus_size_mem(struct pci_bus *bus, unsigned long type, } } - old_size = resource_size(b_res); win_align = window_alignment(bus, b_res->flags); min_align = calculate_head_align(aligns, max_order); min_align = max(min_align, win_align); - size0 = calculate_memsize(size, min_size, 0, 0, old_size, win_align); + size0 = calculate_memsize(size, min_size, 0, 0, win_align); if (size0) { resource_set_range(b_res, min_align, size0); @@ -1373,7 +1368,7 @@ static void pbus_size_mem(struct pci_bus *bus, unsigned long type, if (realloc_head && (add_size > 0 || children_add_size > 0)) { add_align = max(min_align, add_align); size1 = calculate_memsize(size, min_size, add_size, children_add_size, - old_size, win_align); + win_align); } if (!size0 && !size1) { From adc2deacdd8198689c89c9b7f20a6e17806b3243 Mon Sep 17 00:00:00 2001 From: Eric Dumazet Date: Tue, 27 Jan 2026 12:38:27 +0000 Subject: [PATCH 3532/3902] tcp: tcp_tx_timestamp() must look at the rtx queue [ Upstream commit 838eb9687691d29915797a885b861fd09353386e ] tcp_tx_timestamp() is only called at the end of tcp_sendmsg_locked() before the final tcp_push(). By the time it is called, it is possible all the copied data has been sent already (transmit queue is empty). If this is the case, use the last skb in the rtx queue. Fixes: 75c119afe14f ("tcp: implement rb-tree based retransmit queue") Signed-off-by: Eric Dumazet Reviewed-by: Jason Xing Link: https://patch.msgid.link/20260127123828.4098577-2-edumazet@google.com Signed-off-by: Jakub Kicinski Signed-off-by: Sasha Levin --- net/ipv4/tcp.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/net/ipv4/tcp.c b/net/ipv4/tcp.c index 74079eab898040..e35825656e6ea9 100644 --- a/net/ipv4/tcp.c +++ b/net/ipv4/tcp.c @@ -503,6 +503,9 @@ static void tcp_tx_timestamp(struct sock *sk, struct sockcm_cookie *sockc) struct sk_buff *skb = tcp_write_queue_tail(sk); u32 tsflags = sockc->tsflags; + if (unlikely(!skb)) + skb = skb_rb_last(&sk->tcp_rtx_queue); + if (tsflags && skb) { struct skb_shared_info *shinfo = skb_shinfo(skb); struct tcp_skb_cb *tcb = TCP_SKB_CB(skb); From 8fb1dc29637806a4ff4daba69ba170ebb53e5f99 Mon Sep 17 00:00:00 2001 From: Sergey Shtylyov Date: Tue, 27 Jan 2026 23:39:42 +0300 Subject: [PATCH 3533/3902] PCI: Check parent for NULL in of_pci_bus_release_domain_nr() [ Upstream commit f7245901de8978d829f80b3d8e36ed9a8fd18049 ] of_pci_bus_find_domain_nr() allows its parent parameter to be NULL but of_pci_bus_release_domain_nr() (that undoes its effect) doesn't -- that means it's going to blow up while calling of_get_pci_domain_nr() if the parent parameter indeed happens to be NULL. Add the missing NULL check. Found by Linux Verification Center (linuxtesting.org) with the Svace static analysis tool. Fixes: c14f7ccc9f5d ("PCI: Assign PCI domain IDs by ida_alloc()") Signed-off-by: Sergey Shtylyov Signed-off-by: Bjorn Helgaas Link: https://patch.msgid.link/20260127203944.28588-1-s.shtylyov@auroraos.dev Signed-off-by: Sasha Levin --- drivers/pci/pci.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/pci/pci.c b/drivers/pci/pci.c index 08a8c17ba4b124..82e323b5aaa25c 100644 --- a/drivers/pci/pci.c +++ b/drivers/pci/pci.c @@ -6717,7 +6717,7 @@ static void of_pci_bus_release_domain_nr(struct device *parent, int domain_nr) return; /* Release domain from IDA where it was allocated. */ - if (of_get_pci_domain_nr(parent->of_node) == domain_nr) + if (parent && of_get_pci_domain_nr(parent->of_node) == domain_nr) ida_free(&pci_domain_nr_static_ida, domain_nr); else ida_free(&pci_domain_nr_dynamic_ida, domain_nr); From 134bd7f7e42f856b5cf99bc51d5391b14243b630 Mon Sep 17 00:00:00 2001 From: Ziyi Guo Date: Fri, 23 Jan 2026 04:58:22 +0000 Subject: [PATCH 3534/3902] wifi: ath10k: sdio: add missing lock protection in ath10k_sdio_fw_crashed_dump() [ Upstream commit e55ac348089e579fc224569c7bd90340bf2439f9 ] ath10k_sdio_fw_crashed_dump() calls ath10k_coredump_new() which requires ar->dump_mutex to be held, as indicated by lockdep_assert_held() in that function. However, the SDIO implementation does not acquire this lock, unlike the PCI and SNOC implementations which properly hold the mutex. Additionally, ar->stats.fw_crash_counter is documented as protected by ar->data_lock in core.h, but the SDIO implementation modifies it without holding this spinlock. Add the missing mutex_lock()/mutex_unlock() around the coredump operations, and add spin_lock_bh()/spin_unlock_bh() around the fw_crash_counter increment, following the pattern used in ath10k_pci_fw_dump_work() and ath10k_snoc_fw_crashed_dump(). Fixes: 3c45f21af84e ("ath10k: sdio: add firmware coredump support") Signed-off-by: Ziyi Guo Reviewed-by: Baochen Qiang Link: https://patch.msgid.link/20260123045822.2221549-1-n7l8m4@u.northwestern.edu Signed-off-by: Jeff Johnson Signed-off-by: Sasha Levin --- drivers/net/wireless/ath/ath10k/sdio.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/drivers/net/wireless/ath/ath10k/sdio.c b/drivers/net/wireless/ath/ath10k/sdio.c index c06d50db40b818..00d0556dafefdb 100644 --- a/drivers/net/wireless/ath/ath10k/sdio.c +++ b/drivers/net/wireless/ath/ath10k/sdio.c @@ -2487,7 +2487,11 @@ void ath10k_sdio_fw_crashed_dump(struct ath10k *ar) if (fast_dump) ath10k_bmi_start(ar); + mutex_lock(&ar->dump_mutex); + + spin_lock_bh(&ar->data_lock); ar->stats.fw_crash_counter++; + spin_unlock_bh(&ar->data_lock); ath10k_sdio_disable_intrs(ar); @@ -2505,6 +2509,8 @@ void ath10k_sdio_fw_crashed_dump(struct ath10k *ar) ath10k_sdio_enable_intrs(ar); + mutex_unlock(&ar->dump_mutex); + ath10k_core_start_recovery(ar); } From 0e0ca8dbfef901babb8c9c52ba114a9fda87e7e6 Mon Sep 17 00:00:00 2001 From: Miaoqing Pan Date: Wed, 21 Jan 2026 17:50:54 +0800 Subject: [PATCH 3535/3902] wifi: ath11k: add usecase firmware handling based on device compatible [ Upstream commit c386a2b1068910538e87ef1cf2fc938ebf7e218f ] For M.2 WLAN chips, there is no suitable DTS node to specify the firmware-name property. In addition, assigning firmware for the M.2 PCIe interface causes chips that do not use usecase specific firmware to fail. Therefore, abandoning the approach of specifying firmware in DTS. As an alternative, propose a static lookup table mapping device compatible to firmware names. Currently, only WCN6855 HW2.1 requires this. However, support for the firmware-name property is retained to keep the ABI backwards compatible. For details on usecase specific firmware, see: https://lore.kernel.org/all/20250522013444.1301330-3-miaoqing.pan@oss.qualcomm.com/. Tested-on: WCN6855 hw2.1 PCI WLAN.HSP.1.1-04685-QCAHSPSWPL_V1_V2_SILICONZ_IOE-1 Fixes: edbbc647c4f3 ("wifi: ath11k: support usercase-specific firmware overrides") Signed-off-by: Miaoqing Pan Reviewed-by: Vasanthakumar Thiagarajan Reviewed-by: Baochen Qiang Link: https://patch.msgid.link/20260121095055.3683957-2-miaoqing.pan@oss.qualcomm.com Signed-off-by: Jeff Johnson Signed-off-by: Sasha Levin --- drivers/net/wireless/ath/ath11k/core.c | 27 ++++++++++++++++++++++++++ drivers/net/wireless/ath/ath11k/core.h | 4 ++++ 2 files changed, 31 insertions(+) diff --git a/drivers/net/wireless/ath/ath11k/core.c b/drivers/net/wireless/ath/ath11k/core.c index 812686173ac8a9..06b4df2370e95d 100644 --- a/drivers/net/wireless/ath/ath11k/core.c +++ b/drivers/net/wireless/ath/ath11k/core.c @@ -997,6 +997,33 @@ static const struct dmi_system_id ath11k_pm_quirk_table[] = { {} }; +static const struct __ath11k_core_usecase_firmware_table { + u32 hw_rev; + const char *compatible; + const char *firmware_name; +} ath11k_core_usecase_firmware_table[] = { + { ATH11K_HW_WCN6855_HW21, "qcom,lemans-evk", "nfa765"}, + { ATH11K_HW_WCN6855_HW21, "qcom,monaco-evk", "nfa765"}, + { ATH11K_HW_WCN6855_HW21, "qcom,hamoa-iot-evk", "nfa765"}, + { /* Sentinel */ } +}; + +const char *ath11k_core_get_usecase_firmware(struct ath11k_base *ab) +{ + const struct __ath11k_core_usecase_firmware_table *entry = NULL; + + entry = ath11k_core_usecase_firmware_table; + while (entry->compatible) { + if (ab->hw_rev == entry->hw_rev && + of_machine_is_compatible(entry->compatible)) + return entry->firmware_name; + entry++; + } + + return NULL; +} +EXPORT_SYMBOL(ath11k_core_get_usecase_firmware); + void ath11k_fw_stats_pdevs_free(struct list_head *head) { struct ath11k_fw_stats_pdev *i, *tmp; diff --git a/drivers/net/wireless/ath/ath11k/core.h b/drivers/net/wireless/ath/ath11k/core.h index e8780b05ce11e3..834988dad591c7 100644 --- a/drivers/net/wireless/ath/ath11k/core.h +++ b/drivers/net/wireless/ath/ath11k/core.h @@ -1275,6 +1275,7 @@ bool ath11k_core_coldboot_cal_support(struct ath11k_base *ab); const struct firmware *ath11k_core_firmware_request(struct ath11k_base *ab, const char *filename); +const char *ath11k_core_get_usecase_firmware(struct ath11k_base *ab); static inline const char *ath11k_scan_state_str(enum ath11k_scan_state state) { @@ -1329,6 +1330,9 @@ static inline void ath11k_core_create_firmware_path(struct ath11k_base *ab, of_property_read_string(ab->dev->of_node, "firmware-name", &fw_name); + if (!fw_name) + fw_name = ath11k_core_get_usecase_firmware(ab); + if (fw_name && strncmp(filename, "board", 5)) snprintf(buf, buf_len, "%s/%s/%s/%s", ATH11K_FW_DIR, ab->hw_params.fw.dir, fw_name, filename); From 474e93f4a5aa5e990aeaeaf1cc2bcdd6a3997b88 Mon Sep 17 00:00:00 2001 From: Aaradhana Sahu Date: Fri, 23 Jan 2026 12:42:51 +0530 Subject: [PATCH 3536/3902] wifi: ath12k: Fix index decrement when array_len is zero [ Upstream commit e4763898bb1325dbb3792961b6d607b5c6452d64 ] Currently, print_array_to_buf_index() decrements index unconditionally. This may lead to invalid buffer access when array_len is zero. Fix this by decrementing index only when array_len is non-zero. Tested-on: QCN9274 hw2.0 PCI WLAN.WBE.1.5-01651-QCAHKSWPL_SILICONZ-1 Tested-on: WCN7850 hw2.0 PCI WLAN.HMT.1.1.c5-00302-QCAHMTSWPL_V1.0_V2.0_SILICONZ-1.115823.3 Fixes: adf6df963c03 ("wifi: ath12k: Add support to parse requested stats_type") Signed-off-by: Aaradhana Sahu Reviewed-by: Baochen Qiang Link: https://patch.msgid.link/20260123071253.2202644-2-aaradhana.sahu@oss.qualcomm.com Signed-off-by: Jeff Johnson Signed-off-by: Sasha Levin --- drivers/net/wireless/ath/ath12k/debugfs_htt_stats.c | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/drivers/net/wireless/ath/ath12k/debugfs_htt_stats.c b/drivers/net/wireless/ath/ath12k/debugfs_htt_stats.c index 48b010a1b7566d..4f749d473d0e1f 100644 --- a/drivers/net/wireless/ath/ath12k/debugfs_htt_stats.c +++ b/drivers/net/wireless/ath/ath12k/debugfs_htt_stats.c @@ -1,7 +1,7 @@ // SPDX-License-Identifier: BSD-3-Clause-Clear /* * Copyright (c) 2018-2021 The Linux Foundation. All rights reserved. - * Copyright (c) 2021-2025 Qualcomm Innovation Center, Inc. All rights reserved. + * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries. */ #include @@ -29,8 +29,10 @@ print_array_to_buf_index(u8 *buf, u32 offset, const char *header, u32 stats_inde " %u:%u,", stats_index++, le32_to_cpu(array[i])); } /* To overwrite the last trailing comma */ - index--; - *(buf + offset + index) = '\0'; + if (array_len > 0) { + index--; + *(buf + offset + index) = '\0'; + } if (footer) { index += scnprintf(buf + offset + index, From da289440f04c93048d82d293b180f1cacdfee2d9 Mon Sep 17 00:00:00 2001 From: Aaradhana Sahu Date: Tue, 27 Jan 2026 09:04:00 +0530 Subject: [PATCH 3537/3902] wifi: ath12k: clear stale link mapping of ahvif->links_map [ Upstream commit 2c1ba9c2adf0fda96eaaebd8799268a7506a8fc9 ] When an arvif is initialized in non-AP STA mode but MLO connection preparation fails before the arvif is created (arvif->is_created remains false), the error path attempts to delete all links. However, link deletion only executes when arvif->is_created is true. As a result, ahvif retains a stale entry of arvif that is initialized but not created. When a new arvif is initialized with the same link id, this stale mapping triggers the following WARN_ON. WARNING: drivers/net/wireless/ath/ath12k/mac.c:4271 at ath12k_mac_op_change_vif_links+0x140/0x180 [ath12k], CPU#3: wpa_supplicant/275 Call trace: ath12k_mac_op_change_vif_links+0x140/0x180 [ath12k] (P) drv_change_vif_links+0xbc/0x1a4 [mac80211] ieee80211_vif_update_links+0x54c/0x6a0 [mac80211] ieee80211_vif_set_links+0x40/0x70 [mac80211] ieee80211_prep_connection+0x84/0x450 [mac80211] ieee80211_mgd_auth+0x200/0x480 [mac80211] ieee80211_auth+0x14/0x20 [mac80211] cfg80211_mlme_auth+0x90/0xf0 [cfg80211] nl80211_authenticate+0x32c/0x380 [cfg80211] genl_family_rcv_msg_doit+0xc8/0x134 Fix this issue by unassigning the link vif and clearing ahvif->links_map if arvif is only initialized but not created. Tested-on: QCN9274 hw2.0 PCI WLAN.WBE.1.5-01651-QCAHKSWPL_SILICONZ-1 Fixes: 81e4be30544e ("wifi: ath12k: handle link removal in change_vif_links()") Signed-off-by: Aaradhana Sahu Reviewed-by: Vasanthakumar Thiagarajan Reviewed-by: Baochen Qiang Link: https://patch.msgid.link/20260127033400.1721220-1-aaradhana.sahu@oss.qualcomm.com Signed-off-by: Jeff Johnson Signed-off-by: Sasha Levin --- drivers/net/wireless/ath/ath12k/mac.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/drivers/net/wireless/ath/ath12k/mac.c b/drivers/net/wireless/ath/ath12k/mac.c index 256ffae4d7f7dc..b97469dca0467c 100644 --- a/drivers/net/wireless/ath/ath12k/mac.c +++ b/drivers/net/wireless/ath/ath12k/mac.c @@ -4004,8 +4004,10 @@ ath12k_mac_op_change_vif_links(struct ieee80211_hw *hw, if (WARN_ON(!arvif)) return -EINVAL; - if (!arvif->is_created) + if (!arvif->is_created) { + ath12k_mac_unassign_link_vif(arvif); continue; + } if (WARN_ON(!arvif->ar)) return -EINVAL; From dbe723b480e4731faf181ae5b650a2dcdcbc4179 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A5kon=20Bugge?= Date: Thu, 29 Jan 2026 18:52:32 +0100 Subject: [PATCH 3538/3902] PCI: Initialize RCB from pci_configure_device() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [ Upstream commit 1a6845aaa6de81f95959b380b45de8f10d6a8502 ] Commit e42010d8207f ("PCI: Set Read Completion Boundary to 128 iff Root Port supports it (_HPX)") worked around a bogus _HPX type 2 record, which caused program_hpx_type2() to set the RCB in an endpoint even though the Root Port did not have the RCB bit set. e42010d8207f fixed that by setting the RCB in the endpoint only when it was set in the Root Port. In retrospect, program_hpx_type2() is intended for AER-related settings, and the RCB should be configured elsewhere so it doesn't depend on the presence or contents of an _HPX record. Explicitly program the RCB from pci_configure_device() so it matches the Root Port's RCB. The Root Port may not be visible to virtualized guests; in that case, leave RCB alone. Fixes: e42010d8207f ("PCI: Set Read Completion Boundary to 128 iff Root Port supports it (_HPX)") Signed-off-by: Håkon Bugge Signed-off-by: Bjorn Helgaas Link: https://patch.msgid.link/20260129175237.727059-2-haakon.bugge@oracle.com Signed-off-by: Sasha Levin --- drivers/pci/probe.c | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/drivers/pci/probe.c b/drivers/pci/probe.c index 8cf573fca3078b..7d4f0db5ac265a 100644 --- a/drivers/pci/probe.c +++ b/drivers/pci/probe.c @@ -2381,6 +2381,37 @@ static void pci_configure_serr(struct pci_dev *dev) } } +static void pci_configure_rcb(struct pci_dev *dev) +{ + struct pci_dev *rp; + u16 rp_lnkctl; + + /* + * Per PCIe r7.0, sec 7.5.3.7, RCB is only meaningful in Root Ports + * (where it is read-only), Endpoints, and Bridges. It may only be + * set for Endpoints and Bridges if it is set in the Root Port. For + * Endpoints, it is 'RsvdP' for Virtual Functions. + */ + if (!pci_is_pcie(dev) || + pci_pcie_type(dev) == PCI_EXP_TYPE_ROOT_PORT || + pci_pcie_type(dev) == PCI_EXP_TYPE_UPSTREAM || + pci_pcie_type(dev) == PCI_EXP_TYPE_DOWNSTREAM || + pci_pcie_type(dev) == PCI_EXP_TYPE_RC_EC || + dev->is_virtfn) + return; + + /* Root Port often not visible to virtualized guests */ + rp = pcie_find_root_port(dev); + if (!rp) + return; + + pcie_capability_read_word(rp, PCI_EXP_LNKCTL, &rp_lnkctl); + pcie_capability_clear_and_set_word(dev, PCI_EXP_LNKCTL, + PCI_EXP_LNKCTL_RCB, + (rp_lnkctl & PCI_EXP_LNKCTL_RCB) ? + PCI_EXP_LNKCTL_RCB : 0); +} + static void pci_configure_device(struct pci_dev *dev) { pci_configure_mps(dev); @@ -2390,6 +2421,7 @@ static void pci_configure_device(struct pci_dev *dev) pci_configure_aspm_l1ss(dev); pci_configure_eetlp_prefix(dev); pci_configure_serr(dev); + pci_configure_rcb(dev); pci_acpi_program_hp_params(dev); } From 4a0d8d372915261c344fea63e4eb1e6b41650ac0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A5kon=20Bugge?= Date: Thu, 29 Jan 2026 18:52:33 +0100 Subject: [PATCH 3539/3902] PCI/ACPI: Restrict program_hpx_type2() to AER bits MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [ Upstream commit 9abf79c8d7b40db0e5a34aa8c744ea60ff9a3fcf ] Previously program_hpx_type2() applied PCIe settings unconditionally, which could incorrectly change bits like Extended Tag Field Enable and Enable Relaxed Ordering. When _HPX was added to ACPI r3.0, the intent of the PCIe Setting Record (Type 2) in sec 6.2.7.3 was to configure AER registers when the OS does not own the AER Capability: The PCI Express setting record contains ... [the AER] Uncorrectable Error Mask, Uncorrectable Error Severity, Correctable Error Mask ... to be used when configuring registers in the Advanced Error Reporting Extended Capability Structure ... OSPM [1] will only evaluate _HPX with Setting Record – Type 2 if OSPM is not controlling the PCI Express Advanced Error Reporting capability. ACPI r3.0b, sec 6.2.7.3, added more AER registers, including registers in the PCIe Capability with AER-related bits, and the restriction that the OS use this only when it owns PCIe native hotplug: ... when configuring PCI Express registers in the Advanced Error Reporting Extended Capability Structure *or PCI Express Capability Structure* ... An OS that has assumed ownership of native hot plug but does not ... have ownership of the AER register set must use ... the Type 2 record to program the AER registers ... However, since the Type 2 record also includes register bits that have functions other than AER, the OS must ignore values ... that are not applicable. Restrict program_hpx_type2() to only the intended purpose: - Apply settings only when OS owns PCIe native hotplug but not AER, - Only touch the AER-related bits (Error Reporting Enables) in Device Control - Don't touch Link Control at all, since nothing there seems AER-related, but log _HPX settings for debugging purposes Note that Read Completion Boundary is now configured elsewhere, since it is unrelated to _HPX. [1] Operating System-directed configuration and Power Management Fixes: 40abb96c51bb ("[PATCH] pciehp: Fix programming hotplug parameters") Signed-off-by: Håkon Bugge Signed-off-by: Bjorn Helgaas Link: https://patch.msgid.link/20260129175237.727059-3-haakon.bugge@oracle.com Signed-off-by: Sasha Levin --- drivers/pci/pci-acpi.c | 59 +++++++++++++++++------------------------- drivers/pci/pci.h | 3 +++ drivers/pci/pcie/aer.c | 3 --- 3 files changed, 27 insertions(+), 38 deletions(-) diff --git a/drivers/pci/pci-acpi.c b/drivers/pci/pci-acpi.c index 9369377725fa03..0162acfb57896b 100644 --- a/drivers/pci/pci-acpi.c +++ b/drivers/pci/pci-acpi.c @@ -271,21 +271,6 @@ static acpi_status decode_type1_hpx_record(union acpi_object *record, return AE_OK; } -static bool pcie_root_rcb_set(struct pci_dev *dev) -{ - struct pci_dev *rp = pcie_find_root_port(dev); - u16 lnkctl; - - if (!rp) - return false; - - pcie_capability_read_word(rp, PCI_EXP_LNKCTL, &lnkctl); - if (lnkctl & PCI_EXP_LNKCTL_RCB) - return true; - - return false; -} - /* _HPX PCI Express Setting Record (Type 2) */ struct hpx_type2 { u32 revision; @@ -311,6 +296,7 @@ static void program_hpx_type2(struct pci_dev *dev, struct hpx_type2 *hpx) { int pos; u32 reg32; + const struct pci_host_bridge *host; if (!hpx) return; @@ -318,6 +304,15 @@ static void program_hpx_type2(struct pci_dev *dev, struct hpx_type2 *hpx) if (!pci_is_pcie(dev)) return; + host = pci_find_host_bridge(dev->bus); + + /* + * Only do the _HPX Type 2 programming if OS owns PCIe native + * hotplug but not AER. + */ + if (!host->native_pcie_hotplug || host->native_aer) + return; + if (hpx->revision > 1) { pci_warn(dev, "PCIe settings rev %d not supported\n", hpx->revision); @@ -325,33 +320,27 @@ static void program_hpx_type2(struct pci_dev *dev, struct hpx_type2 *hpx) } /* - * Don't allow _HPX to change MPS or MRRS settings. We manage - * those to make sure they're consistent with the rest of the - * platform. + * We only allow _HPX to program DEVCTL bits related to AER, namely + * PCI_EXP_DEVCTL_CERE, PCI_EXP_DEVCTL_NFERE, PCI_EXP_DEVCTL_FERE, + * and PCI_EXP_DEVCTL_URRE. + * + * The rest of DEVCTL is managed by the OS to make sure it's + * consistent with the rest of the platform. */ - hpx->pci_exp_devctl_and |= PCI_EXP_DEVCTL_PAYLOAD | - PCI_EXP_DEVCTL_READRQ; - hpx->pci_exp_devctl_or &= ~(PCI_EXP_DEVCTL_PAYLOAD | - PCI_EXP_DEVCTL_READRQ); + hpx->pci_exp_devctl_and |= ~PCI_EXP_AER_FLAGS; + hpx->pci_exp_devctl_or &= PCI_EXP_AER_FLAGS; /* Initialize Device Control Register */ pcie_capability_clear_and_set_word(dev, PCI_EXP_DEVCTL, ~hpx->pci_exp_devctl_and, hpx->pci_exp_devctl_or); - /* Initialize Link Control Register */ + /* Log if _HPX attempts to modify Link Control Register */ if (pcie_cap_has_lnkctl(dev)) { - - /* - * If the Root Port supports Read Completion Boundary of - * 128, set RCB to 128. Otherwise, clear it. - */ - hpx->pci_exp_lnkctl_and |= PCI_EXP_LNKCTL_RCB; - hpx->pci_exp_lnkctl_or &= ~PCI_EXP_LNKCTL_RCB; - if (pcie_root_rcb_set(dev)) - hpx->pci_exp_lnkctl_or |= PCI_EXP_LNKCTL_RCB; - - pcie_capability_clear_and_set_word(dev, PCI_EXP_LNKCTL, - ~hpx->pci_exp_lnkctl_and, hpx->pci_exp_lnkctl_or); + if (hpx->pci_exp_lnkctl_and != 0xffff || + hpx->pci_exp_lnkctl_or != 0) + pci_info(dev, "_HPX attempts Link Control setting (AND %#06x OR %#06x)\n", + hpx->pci_exp_lnkctl_and, + hpx->pci_exp_lnkctl_or); } /* Find Advanced Error Reporting Enhanced Capability */ diff --git a/drivers/pci/pci.h b/drivers/pci/pci.h index 36f8c0985430a4..565acfcd7cdb14 100644 --- a/drivers/pci/pci.h +++ b/drivers/pci/pci.h @@ -88,6 +88,9 @@ struct pcie_tlp_log; #define PCI_BUS_BRIDGE_MEM_WINDOW 1 #define PCI_BUS_BRIDGE_PREF_MEM_WINDOW 2 +#define PCI_EXP_AER_FLAGS (PCI_EXP_DEVCTL_CERE | PCI_EXP_DEVCTL_NFERE | \ + PCI_EXP_DEVCTL_FERE | PCI_EXP_DEVCTL_URRE) + extern const unsigned char pcie_link_speed[]; extern bool pci_early_dump; diff --git a/drivers/pci/pcie/aer.c b/drivers/pci/pcie/aer.c index 0b5ed4722ac323..23bead9415fcd1 100644 --- a/drivers/pci/pcie/aer.c +++ b/drivers/pci/pcie/aer.c @@ -238,9 +238,6 @@ void pcie_ecrc_get_policy(char *str) } #endif /* CONFIG_PCIE_ECRC */ -#define PCI_EXP_AER_FLAGS (PCI_EXP_DEVCTL_CERE | PCI_EXP_DEVCTL_NFERE | \ - PCI_EXP_DEVCTL_FERE | PCI_EXP_DEVCTL_URRE) - int pcie_aer_is_native(struct pci_dev *dev) { struct pci_host_bridge *host = pci_find_host_bridge(dev->bus); From f5efb397c8f937873130f903569ec366d1e5d173 Mon Sep 17 00:00:00 2001 From: Kevin Brodsky Date: Thu, 22 Jan 2026 17:02:19 +0000 Subject: [PATCH 3540/3902] selftests/mm: fix usage of FORCE_READ() in cow tests [ Upstream commit bce1dabd310e87fefe0645fec9ba98b84d37e418 ] Commit 5bbc2b785e63 ("selftests/mm: fix FORCE_READ to read input value correctly") modified FORCE_READ() to take a value instead of a pointer. It also changed most of the call sites accordingly, but missed many of them in cow.c. In those cases, we ended up with the pointer itself being read, not the memory it points to. No failure occurred as a result, so it looks like the tests work just fine without faulting in. However, the huge_zeropage tests explicitly check that pages are populated, so those became skipped. Convert all the remaining FORCE_READ() to fault in the mapped page, as was originally intended. This allows the huge_zeropage tests to run again (3 tests in total). Link: https://lkml.kernel.org/r/20260122170224.4056513-5-kevin.brodsky@arm.com Fixes: 5bbc2b785e63 ("selftests/mm: fix FORCE_READ to read input value correctly") Signed-off-by: Kevin Brodsky Acked-by: SeongJae Park Reviewed-by: wang lian Acked-by: David Hildenbrand (Red Hat) Reviewed-by: Dev Jain Cc: Jason Gunthorpe Cc: John Hubbard Cc: Lorenzo Stoakes Cc: Mark Brown Cc: Paolo Abeni Cc: Ryan Roberts Cc: Shuah Khan Cc: Usama Anjum Cc: Yunsheng Lin Signed-off-by: Andrew Morton Signed-off-by: Sasha Levin --- tools/testing/selftests/mm/cow.c | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/tools/testing/selftests/mm/cow.c b/tools/testing/selftests/mm/cow.c index 6560c26f47d1fe..0df61422467da0 100644 --- a/tools/testing/selftests/mm/cow.c +++ b/tools/testing/selftests/mm/cow.c @@ -1612,8 +1612,8 @@ static void run_with_huge_zeropage(non_anon_test_fn fn, const char *desc) * the first sub-page and test if we get another sub-page populated * automatically. */ - FORCE_READ(mem); - FORCE_READ(smem); + FORCE_READ(*mem); + FORCE_READ(*smem); if (!pagemap_is_populated(pagemap_fd, mem + pagesize) || !pagemap_is_populated(pagemap_fd, smem + pagesize)) { ksft_test_result_skip("Did not get THPs populated\n"); @@ -1663,8 +1663,8 @@ static void run_with_memfd(non_anon_test_fn fn, const char *desc) } /* Fault the page in. */ - FORCE_READ(mem); - FORCE_READ(smem); + FORCE_READ(*mem); + FORCE_READ(*smem); fn(mem, smem, pagesize); munmap: @@ -1719,8 +1719,8 @@ static void run_with_tmpfile(non_anon_test_fn fn, const char *desc) } /* Fault the page in. */ - FORCE_READ(mem); - FORCE_READ(smem); + FORCE_READ(*mem); + FORCE_READ(*smem); fn(mem, smem, pagesize); munmap: @@ -1773,8 +1773,8 @@ static void run_with_memfd_hugetlb(non_anon_test_fn fn, const char *desc, } /* Fault the page in. */ - FORCE_READ(mem); - FORCE_READ(smem); + FORCE_READ(*mem); + FORCE_READ(*smem); fn(mem, smem, hugetlbsize); munmap: From 8fd58e0a0f04059b293a2673a6ffcda2b615160f Mon Sep 17 00:00:00 2001 From: Ondrej Mosnacek Date: Thu, 22 Jan 2026 15:13:03 +0100 Subject: [PATCH 3541/3902] ipc: don't audit capability check in ipc_permissions() [ Upstream commit 071588136007482d70fd2667b827036bc60b1f8f ] The IPC sysctls implement the ctl_table_root::permissions hook and they override the file access mode based on the CAP_CHECKPOINT_RESTORE capability, which is being checked regardless of whether any access is actually denied or not, so if an LSM denies the capability, an audit record may be logged even when access is in fact granted. It wouldn't be viable to restructure the sysctl permission logic to only check the capability when the access would be actually denied if it's not granted. Thus, do the same as in net_ctl_permissions() (net/sysctl_net.c) - switch from ns_capable() to ns_capable_noaudit(), so that the check never emits an audit record. Fixes: 0889f44e2810 ("ipc: Check permissions for checkpoint_restart sysctls at open time") Signed-off-by: Ondrej Mosnacek Acked-by: Alexey Gladkov Acked-by: Serge Hallyn Signed-off-by: Serge Hallyn Stable-dep-of: 8924336531e2 ("ipc: don't audit capability check in ipc_permissions()") Signed-off-by: Sasha Levin --- include/linux/capability.h | 6 ++++++ ipc/ipc_sysctl.c | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/include/linux/capability.h b/include/linux/capability.h index 1fb08922552c79..37db92b3d6f89e 100644 --- a/include/linux/capability.h +++ b/include/linux/capability.h @@ -203,6 +203,12 @@ static inline bool checkpoint_restore_ns_capable(struct user_namespace *ns) ns_capable(ns, CAP_SYS_ADMIN); } +static inline bool checkpoint_restore_ns_capable_noaudit(struct user_namespace *ns) +{ + return ns_capable_noaudit(ns, CAP_CHECKPOINT_RESTORE) || + ns_capable_noaudit(ns, CAP_SYS_ADMIN); +} + /* audit system wants to get cap info from files as well */ int get_vfs_caps_from_disk(struct mnt_idmap *idmap, const struct dentry *dentry, diff --git a/ipc/ipc_sysctl.c b/ipc/ipc_sysctl.c index 15b17e86e198cf..9b087ebeb643b7 100644 --- a/ipc/ipc_sysctl.c +++ b/ipc/ipc_sysctl.c @@ -214,7 +214,7 @@ static int ipc_permissions(struct ctl_table_header *head, const struct ctl_table if (((table->data == &ns->ids[IPC_SEM_IDS].next_id) || (table->data == &ns->ids[IPC_MSG_IDS].next_id) || (table->data == &ns->ids[IPC_SHM_IDS].next_id)) && - checkpoint_restore_ns_capable(ns->user_ns)) + checkpoint_restore_ns_capable_noaudit(ns->user_ns)) mode = 0666; else #endif From b4374b0bed103fc37be64a8f9a344f8c93eb5599 Mon Sep 17 00:00:00 2001 From: Ondrej Mosnacek Date: Thu, 22 Jan 2026 15:07:45 +0100 Subject: [PATCH 3542/3902] ucount: check for CAP_SYS_RESOURCE using ns_capable_noaudit() [ Upstream commit 0895a000e4fff9e950a7894210db45973e485c35 ] The user.* sysctls implement the ctl_table_root::permissions hook and they override the file access mode based on the CAP_SYS_RESOURCE capability (at most rwx if capable, at most r-- if not). The capability is being checked unconditionally, so if an LSM denies the capability, an audit record may be logged even when access is in fact granted. Given the logic in the set_permissions() function in kernel/ucount.c and the unfortunate way the permission checking is implemented, it doesn't seem viable to avoid false positive denials by deferring the capability check. Thus, do the same as in net_ctl_permissions() (net/sysctl_net.c) - switch from ns_capable() to ns_capable_noaudit(), so that the check never logs an audit record. Link: https://lkml.kernel.org/r/20260122140745.239428-1-omosnace@redhat.com Fixes: dbec28460a89 ("userns: Add per user namespace sysctls.") Signed-off-by: Ondrej Mosnacek Reviewed-by: Paul Moore Acked-by: Serge Hallyn Cc: Eric Biederman Cc: Alexey Gladkov Signed-off-by: Andrew Morton Signed-off-by: Sasha Levin --- kernel/ucount.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/kernel/ucount.c b/kernel/ucount.c index 586af49fc03e42..fc4a8f2d309651 100644 --- a/kernel/ucount.c +++ b/kernel/ucount.c @@ -47,7 +47,7 @@ static int set_permissions(struct ctl_table_header *head, int mode; /* Allow users with CAP_SYS_RESOURCE unrestrained access */ - if (ns_capable(user_ns, CAP_SYS_RESOURCE)) + if (ns_capable_noaudit(user_ns, CAP_SYS_RESOURCE)) mode = (table->mode & S_IRWXU) >> 6; else /* Allow all others at most read-only access */ From 817780cb3f555228e93d6fee2a776c717b751757 Mon Sep 17 00:00:00 2001 From: Arnd Bergmann Date: Mon, 2 Feb 2026 21:43:45 +0100 Subject: [PATCH 3543/3902] jfs: avoid -Wtautological-constant-out-of-range-compare warning [ Upstream commit 7833570dae833028337bb53b7f389825b910c100 ] A recent change for the range check started triggering a clang warning: fs/jfs/jfs_dtree.c:2906:31: error: result of comparison of constant 128 with expression of type 's8' (aka 'signed char') is always false [-Werror,-Wtautological-constant-out-of-range-compare] 2906 | if (stbl[i] < 0 || stbl[i] >= DTPAGEMAXSLOT) { | ~~~~~~~ ^ ~~~~~~~~~~~~~ fs/jfs/jfs_dtree.c:3111:30: error: result of comparison of constant 128 with expression of type 's8' (aka 'signed char') is always false [-Werror,-Wtautological-constant-out-of-range-compare] 3111 | if (stbl[0] < 0 || stbl[0] >= DTPAGEMAXSLOT) { | ~~~~~~~ ^ ~~~~~~~~~~~~~ Both the old and the new check were useless, but the previous version apparently did not lead to the warning. Remove the extraneous range check for simplicity. Fixes: cafc6679824a ("jfs: replace hardcoded magic number with DTPAGEMAXSLOT constant") Signed-off-by: Arnd Bergmann Signed-off-by: Dave Kleikamp Signed-off-by: Sasha Levin --- fs/jfs/jfs_dtree.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/fs/jfs/jfs_dtree.c b/fs/jfs/jfs_dtree.c index 0ab83bb7bbdf9a..9ab3f2fc61d176 100644 --- a/fs/jfs/jfs_dtree.c +++ b/fs/jfs/jfs_dtree.c @@ -2903,7 +2903,7 @@ int jfs_readdir(struct file *file, struct dir_context *ctx) stbl = DT_GETSTBL(p); for (i = index; i < p->header.nextindex; i++) { - if (stbl[i] < 0 || stbl[i] >= DTPAGEMAXSLOT) { + if (stbl[i] < 0) { jfs_err("JFS: Invalid stbl[%d] = %d for inode %ld, block = %lld", i, stbl[i], (long)ip->i_ino, (long long)bn); free_page(dirent_buf); @@ -3108,7 +3108,7 @@ static int dtReadFirst(struct inode *ip, struct btstack * btstack) /* get the leftmost entry */ stbl = DT_GETSTBL(p); - if (stbl[0] < 0 || stbl[0] >= DTPAGEMAXSLOT) { + if (stbl[0] < 0) { DT_PUTPAGE(mp); jfs_error(ip->i_sb, "stbl[0] out of bound\n"); return -EIO; From 69484fd350be4cd98708d42544e01149bca24f0b Mon Sep 17 00:00:00 2001 From: Chia-Yu Chang Date: Sat, 31 Jan 2026 23:25:04 +0100 Subject: [PATCH 3544/3902] tcp: ECT_1_NEGOTIATION and NEEDS_ACCECN identifiers MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [ Upstream commit 100f946b8d44b64bc0b8a8c30d283105031c0a77 ] Two flags for congestion control (CC) module are added in this patch related to AccECN negotiation. First, a new flag (TCP_CONG_NEEDS_ACCECN) defines that the CC expects to negotiate AccECN functionality using the ECE, CWR and AE flags in the TCP header. Second, during ECN negotiation, ECT(0) in the IP header is used. This patch enables CC to control whether ECT(0) or ECT(1) should be used on a per-segment basis. A new flag (TCP_CONG_ECT_1_NEGOTIATION) defines the expected ECT value in the IP header by the CA when not-yet initialized for the connection. The detailed AccECN negotiaotn can be found in IETF RFC9768. Co-developed-by: Olivier Tilmans Signed-off-by: Olivier Tilmans Signed-off-by: Ilpo Järvinen Signed-off-by: Chia-Yu Chang Acked-by: Paolo Abeni Reviewed-by: Eric Dumazet Link: https://patch.msgid.link/20260131222515.8485-5-chia-yu.chang@nokia-bell-labs.com Signed-off-by: Paolo Abeni Stable-dep-of: c5ff6b837159 ("tcp: accecn: handle unexpected AccECN negotiation feedback") Signed-off-by: Sasha Levin --- include/net/inet_ecn.h | 20 +++++++++++++++++--- include/net/tcp.h | 21 ++++++++++++++++++++- include/net/tcp_ecn.h | 13 ++++++++++--- net/ipv4/tcp_cong.c | 5 +++-- net/ipv4/tcp_input.c | 3 ++- 5 files changed, 52 insertions(+), 10 deletions(-) diff --git a/include/net/inet_ecn.h b/include/net/inet_ecn.h index ea32393464a291..827b87a95dab3f 100644 --- a/include/net/inet_ecn.h +++ b/include/net/inet_ecn.h @@ -51,11 +51,25 @@ static inline __u8 INET_ECN_encapsulate(__u8 outer, __u8 inner) return outer; } +/* Apply either ECT(0) or ECT(1) */ +static inline void __INET_ECN_xmit(struct sock *sk, bool use_ect_1) +{ + __u8 ect = use_ect_1 ? INET_ECN_ECT_1 : INET_ECN_ECT_0; + + /* Mask the complete byte in case the connection alternates between + * ECT(0) and ECT(1). + */ + inet_sk(sk)->tos &= ~INET_ECN_MASK; + inet_sk(sk)->tos |= ect; + if (inet6_sk(sk)) { + inet6_sk(sk)->tclass &= ~INET_ECN_MASK; + inet6_sk(sk)->tclass |= ect; + } +} + static inline void INET_ECN_xmit(struct sock *sk) { - inet_sk(sk)->tos |= INET_ECN_ECT_0; - if (inet6_sk(sk) != NULL) - inet6_sk(sk)->tclass |= INET_ECN_ECT_0; + __INET_ECN_xmit(sk, false); } static inline void INET_ECN_dontxmit(struct sock *sk) diff --git a/include/net/tcp.h b/include/net/tcp.h index ab20f549b8f914..c1db8a851243d5 100644 --- a/include/net/tcp.h +++ b/include/net/tcp.h @@ -1190,7 +1190,12 @@ enum tcp_ca_ack_event_flags { #define TCP_CONG_NON_RESTRICTED BIT(0) /* Requires ECN/ECT set on all packets */ #define TCP_CONG_NEEDS_ECN BIT(1) -#define TCP_CONG_MASK (TCP_CONG_NON_RESTRICTED | TCP_CONG_NEEDS_ECN) +/* Require successfully negotiated AccECN capability */ +#define TCP_CONG_NEEDS_ACCECN BIT(2) +/* Use ECT(1) instead of ECT(0) while the CA is uninitialized */ +#define TCP_CONG_ECT_1_NEGOTIATION BIT(3) +#define TCP_CONG_MASK (TCP_CONG_NON_RESTRICTED | TCP_CONG_NEEDS_ECN | \ + TCP_CONG_NEEDS_ACCECN | TCP_CONG_ECT_1_NEGOTIATION) union tcp_cc_info; @@ -1322,6 +1327,20 @@ static inline bool tcp_ca_needs_ecn(const struct sock *sk) return icsk->icsk_ca_ops->flags & TCP_CONG_NEEDS_ECN; } +static inline bool tcp_ca_needs_accecn(const struct sock *sk) +{ + const struct inet_connection_sock *icsk = inet_csk(sk); + + return icsk->icsk_ca_ops->flags & TCP_CONG_NEEDS_ACCECN; +} + +static inline bool tcp_ca_ect_1_negotiation(const struct sock *sk) +{ + const struct inet_connection_sock *icsk = inet_csk(sk); + + return icsk->icsk_ca_ops->flags & TCP_CONG_ECT_1_NEGOTIATION; +} + static inline void tcp_ca_event(struct sock *sk, const enum tcp_ca_event event) { const struct inet_connection_sock *icsk = inet_csk(sk); diff --git a/include/net/tcp_ecn.h b/include/net/tcp_ecn.h index f13e5cd2b1ac3c..fdde1c342b35cb 100644 --- a/include/net/tcp_ecn.h +++ b/include/net/tcp_ecn.h @@ -31,6 +31,12 @@ enum tcp_accecn_option { TCP_ACCECN_OPTION_FULL = 2, }; +/* Apply either ECT(0) or ECT(1) based on TCP_CONG_ECT_1_NEGOTIATION flag */ +static inline void INET_ECN_xmit_ect_1_negotiation(struct sock *sk) +{ + __INET_ECN_xmit(sk, tcp_ca_ect_1_negotiation(sk)); +} + static inline void tcp_ecn_queue_cwr(struct tcp_sock *tp) { /* Do not set CWR if in AccECN mode! */ @@ -561,7 +567,7 @@ static inline void tcp_ecn_send_synack(struct sock *sk, struct sk_buff *skb) TCP_SKB_CB(skb)->tcp_flags &= ~TCPHDR_ECE; else if (tcp_ca_needs_ecn(sk) || tcp_bpf_ca_needs_ecn(sk)) - INET_ECN_xmit(sk); + INET_ECN_xmit_ect_1_negotiation(sk); if (tp->ecn_flags & TCP_ECN_MODE_ACCECN) { TCP_SKB_CB(skb)->tcp_flags &= ~TCPHDR_ACE; @@ -579,7 +585,8 @@ static inline void tcp_ecn_send_syn(struct sock *sk, struct sk_buff *skb) bool use_ecn, use_accecn; u8 tcp_ecn = READ_ONCE(sock_net(sk)->ipv4.sysctl_tcp_ecn); - use_accecn = tcp_ecn == TCP_ECN_IN_ACCECN_OUT_ACCECN; + use_accecn = tcp_ecn == TCP_ECN_IN_ACCECN_OUT_ACCECN || + tcp_ca_needs_accecn(sk); use_ecn = tcp_ecn == TCP_ECN_IN_ECN_OUT_ECN || tcp_ecn == TCP_ECN_IN_ACCECN_OUT_ECN || tcp_ca_needs_ecn(sk) || bpf_needs_ecn || use_accecn; @@ -595,7 +602,7 @@ static inline void tcp_ecn_send_syn(struct sock *sk, struct sk_buff *skb) if (use_ecn) { if (tcp_ca_needs_ecn(sk) || bpf_needs_ecn) - INET_ECN_xmit(sk); + INET_ECN_xmit_ect_1_negotiation(sk); TCP_SKB_CB(skb)->tcp_flags |= TCPHDR_ECE | TCPHDR_CWR; if (use_accecn) { diff --git a/net/ipv4/tcp_cong.c b/net/ipv4/tcp_cong.c index df758adbb445f2..e9f6c77e063163 100644 --- a/net/ipv4/tcp_cong.c +++ b/net/ipv4/tcp_cong.c @@ -16,6 +16,7 @@ #include #include #include +#include #include static DEFINE_SPINLOCK(tcp_cong_list_lock); @@ -227,7 +228,7 @@ void tcp_assign_congestion_control(struct sock *sk) memset(icsk->icsk_ca_priv, 0, sizeof(icsk->icsk_ca_priv)); if (ca->flags & TCP_CONG_NEEDS_ECN) - INET_ECN_xmit(sk); + INET_ECN_xmit_ect_1_negotiation(sk); else INET_ECN_dontxmit(sk); } @@ -257,7 +258,7 @@ static void tcp_reinit_congestion_control(struct sock *sk, memset(icsk->icsk_ca_priv, 0, sizeof(icsk->icsk_ca_priv)); if (ca->flags & TCP_CONG_NEEDS_ECN) - INET_ECN_xmit(sk); + INET_ECN_xmit_ect_1_negotiation(sk); else INET_ECN_dontxmit(sk); diff --git a/net/ipv4/tcp_input.c b/net/ipv4/tcp_input.c index e4a979b75cc663..f920fa44c3d396 100644 --- a/net/ipv4/tcp_input.c +++ b/net/ipv4/tcp_input.c @@ -7222,7 +7222,8 @@ static void tcp_ecn_create_request(struct request_sock *req, u32 ecn_ok_dst; if (tcp_accecn_syn_requested(th) && - READ_ONCE(net->ipv4.sysctl_tcp_ecn) >= 3) { + (READ_ONCE(net->ipv4.sysctl_tcp_ecn) >= 3 || + tcp_ca_needs_accecn(listen_sk))) { inet_rsk(req)->ecn_ok = 1; tcp_rsk(req)->accecn_ok = 1; tcp_rsk(req)->syn_ect_rcv = TCP_SKB_CB(skb)->ip_dsfield & From 2ff45b29f37ed8e9d33cc4ac722a72ad0a9c6af4 Mon Sep 17 00:00:00 2001 From: Chia-Yu Chang Date: Sat, 31 Jan 2026 23:25:05 +0100 Subject: [PATCH 3545/3902] tcp: disable RFC3168 fallback identifier for CC modules [ Upstream commit e68c28f22f46ecfdec3656ae785dd8ccbb4d557d ] When AccECN is not successfully negociated for a TCP flow, it defaults fallback to classic ECN (RFC3168). However, L4S service will fallback to non-ECN. This patch enables congestion control module to control whether it should not fallback to classic ECN after unsuccessful AccECN negotiation. A new CA module flag (TCP_CONG_NO_FALLBACK_RFC3168) identifies this behavior expected by the CA. Signed-off-by: Chia-Yu Chang Acked-by: Paolo Abeni Reviewed-by: Eric Dumazet Link: https://patch.msgid.link/20260131222515.8485-6-chia-yu.chang@nokia-bell-labs.com Signed-off-by: Paolo Abeni Stable-dep-of: c5ff6b837159 ("tcp: accecn: handle unexpected AccECN negotiation feedback") Signed-off-by: Sasha Levin --- include/net/tcp.h | 12 +++++++++++- include/net/tcp_ecn.h | 11 ++++++++--- net/ipv4/tcp_input.c | 2 +- net/ipv4/tcp_minisocks.c | 7 ++++--- 4 files changed, 24 insertions(+), 8 deletions(-) diff --git a/include/net/tcp.h b/include/net/tcp.h index c1db8a851243d5..3c84d95cdba813 100644 --- a/include/net/tcp.h +++ b/include/net/tcp.h @@ -1194,8 +1194,11 @@ enum tcp_ca_ack_event_flags { #define TCP_CONG_NEEDS_ACCECN BIT(2) /* Use ECT(1) instead of ECT(0) while the CA is uninitialized */ #define TCP_CONG_ECT_1_NEGOTIATION BIT(3) +/* Cannot fallback to RFC3168 during AccECN negotiation */ +#define TCP_CONG_NO_FALLBACK_RFC3168 BIT(4) #define TCP_CONG_MASK (TCP_CONG_NON_RESTRICTED | TCP_CONG_NEEDS_ECN | \ - TCP_CONG_NEEDS_ACCECN | TCP_CONG_ECT_1_NEGOTIATION) + TCP_CONG_NEEDS_ACCECN | TCP_CONG_ECT_1_NEGOTIATION | \ + TCP_CONG_NO_FALLBACK_RFC3168) union tcp_cc_info; @@ -1341,6 +1344,13 @@ static inline bool tcp_ca_ect_1_negotiation(const struct sock *sk) return icsk->icsk_ca_ops->flags & TCP_CONG_ECT_1_NEGOTIATION; } +static inline bool tcp_ca_no_fallback_rfc3168(const struct sock *sk) +{ + const struct inet_connection_sock *icsk = inet_csk(sk); + + return icsk->icsk_ca_ops->flags & TCP_CONG_NO_FALLBACK_RFC3168; +} + static inline void tcp_ca_event(struct sock *sk, const enum tcp_ca_event event) { const struct inet_connection_sock *icsk = inet_csk(sk); diff --git a/include/net/tcp_ecn.h b/include/net/tcp_ecn.h index fdde1c342b35cb..2e1637edf1d3c7 100644 --- a/include/net/tcp_ecn.h +++ b/include/net/tcp_ecn.h @@ -507,7 +507,9 @@ static inline void tcp_ecn_rcv_synack(struct sock *sk, const struct sk_buff *skb * | ECN | AccECN | 0 0 1 | Classic ECN | * +========+========+============+=============+ */ - if (tcp_ecn_mode_pending(tp)) + if (tcp_ca_no_fallback_rfc3168(sk)) + tcp_ecn_mode_set(tp, TCP_ECN_DISABLED); + else if (tcp_ecn_mode_pending(tp)) /* Downgrade from AccECN, or requested initially */ tcp_ecn_mode_set(tp, TCP_ECN_MODE_RFC3168); break; @@ -531,9 +533,11 @@ static inline void tcp_ecn_rcv_synack(struct sock *sk, const struct sk_buff *skb } } -static inline void tcp_ecn_rcv_syn(struct tcp_sock *tp, const struct tcphdr *th, +static inline void tcp_ecn_rcv_syn(struct sock *sk, const struct tcphdr *th, const struct sk_buff *skb) { + struct tcp_sock *tp = tcp_sk(sk); + if (tcp_ecn_mode_pending(tp)) { if (!tcp_accecn_syn_requested(th)) { /* Downgrade to classic ECN feedback */ @@ -545,7 +549,8 @@ static inline void tcp_ecn_rcv_syn(struct tcp_sock *tp, const struct tcphdr *th, tcp_ecn_mode_set(tp, TCP_ECN_MODE_ACCECN); } } - if (tcp_ecn_mode_rfc3168(tp) && (!th->ece || !th->cwr)) + if (tcp_ecn_mode_rfc3168(tp) && + (!th->ece || !th->cwr || tcp_ca_no_fallback_rfc3168(sk))) tcp_ecn_mode_set(tp, TCP_ECN_DISABLED); } diff --git a/net/ipv4/tcp_input.c b/net/ipv4/tcp_input.c index f920fa44c3d396..ede266463d5d10 100644 --- a/net/ipv4/tcp_input.c +++ b/net/ipv4/tcp_input.c @@ -6817,7 +6817,7 @@ static int tcp_rcv_synsent_state_process(struct sock *sk, struct sk_buff *skb, tp->snd_wl1 = TCP_SKB_CB(skb)->seq; tp->max_window = tp->snd_wnd; - tcp_ecn_rcv_syn(tp, th, skb); + tcp_ecn_rcv_syn(sk, th, skb); tcp_mtup_init(sk); tcp_sync_mss(sk, icsk->icsk_pmtu_cookie); diff --git a/net/ipv4/tcp_minisocks.c b/net/ipv4/tcp_minisocks.c index 2ec8c6f1cdccc0..1fade94813c622 100644 --- a/net/ipv4/tcp_minisocks.c +++ b/net/ipv4/tcp_minisocks.c @@ -488,9 +488,10 @@ static void tcp_ecn_openreq_child(struct sock *sk, tp->accecn_opt_demand = 1; tcp_ecn_received_counters_payload(sk, skb); } else { - tcp_ecn_mode_set(tp, inet_rsk(req)->ecn_ok ? - TCP_ECN_MODE_RFC3168 : - TCP_ECN_DISABLED); + if (inet_rsk(req)->ecn_ok && !tcp_ca_no_fallback_rfc3168(sk)) + tcp_ecn_mode_set(tp, TCP_ECN_MODE_RFC3168); + else + tcp_ecn_mode_set(tp, TCP_ECN_DISABLED); } } From b8856aba0fb03cd5a1da7930054cc0b049de2bea Mon Sep 17 00:00:00 2001 From: Chia-Yu Chang Date: Sat, 31 Jan 2026 23:25:06 +0100 Subject: [PATCH 3546/3902] tcp: accecn: handle unexpected AccECN negotiation feedback [ Upstream commit c5ff6b83715919767f181f13e992b5055812a194 ] According to Sections 3.1.2 and 3.1.3 of AccECN spec (RFC9768). In Section 3.1.2, it says an AccECN implementation has no need to recognize or support the Server response labelled 'Nonce' or ECN-nonce feedback more generally, as RFC 3540 has been reclassified as Historic. AccECN is compatible with alternative ECN feedback integrity approaches to the nonce. The SYN/ACK labelled 'Nonce' with (AE,CWR,ECE) = (1,0,1) is reserved for future use. A TCP Client (A) that receives such a SYN/ACK follows the procedure for forward compatibility given in Section 3.1.3. Then in Section 3.1.3, it says if a TCP Client has sent a SYN requesting AccECN feedback with (AE,CWR,ECE) = (1,1,1) then receives a SYN/ACK with the currently reserved combination (AE,CWR,ECE) = (1,0,1) but it does not have logic specific to such a combination, the Client MUST enable AccECN mode as if the SYN/ACK onfirmed that the Server supported AccECN and as if it fed back that the IP-ECN field on the SYN had arrived unchanged. Fixes: 3cae34274c79 ("tcp: accecn: AccECN negotiation"). Signed-off-by: Chia-Yu Chang Acked-by: Paolo Abeni Reviewed-by: Eric Dumazet Link: https://patch.msgid.link/20260131222515.8485-7-chia-yu.chang@nokia-bell-labs.com Signed-off-by: Paolo Abeni Signed-off-by: Sasha Levin --- include/net/tcp_ecn.h | 44 ++++++++++++++++++++++++++++++------------- 1 file changed, 31 insertions(+), 13 deletions(-) diff --git a/include/net/tcp_ecn.h b/include/net/tcp_ecn.h index 2e1637edf1d3c7..a709fb1756eb75 100644 --- a/include/net/tcp_ecn.h +++ b/include/net/tcp_ecn.h @@ -473,6 +473,26 @@ static inline u8 tcp_accecn_option_init(const struct sk_buff *skb, return TCP_ACCECN_OPT_COUNTER_SEEN; } +static inline void tcp_ecn_rcv_synack_accecn(struct sock *sk, + const struct sk_buff *skb, u8 dsf) +{ + struct tcp_sock *tp = tcp_sk(sk); + + tcp_ecn_mode_set(tp, TCP_ECN_MODE_ACCECN); + tp->syn_ect_rcv = dsf & INET_ECN_MASK; + /* Demand Accurate ECN option in response to the SYN on the SYN/ACK + * and the TCP server will try to send one more packet with an AccECN + * Option at a later point during the connection. + */ + if (tp->rx_opt.accecn && + tp->saw_accecn_opt < TCP_ACCECN_OPT_COUNTER_SEEN) { + u8 saw_opt = tcp_accecn_option_init(skb, tp->rx_opt.accecn); + + tcp_accecn_saw_opt_fail_recv(tp, saw_opt); + tp->accecn_opt_demand = 2; + } +} + /* See Table 2 of the AccECN draft */ static inline void tcp_ecn_rcv_synack(struct sock *sk, const struct sk_buff *skb, const struct tcphdr *th, u8 ip_dsfield) @@ -495,13 +515,11 @@ static inline void tcp_ecn_rcv_synack(struct sock *sk, const struct sk_buff *skb tcp_ecn_mode_set(tp, TCP_ECN_DISABLED); break; case 0x1: - case 0x5: /* +========+========+============+=============+ * | A | B | SYN/ACK | Feedback | * | | | B->A | Mode of A | * | | | AE CWR ECE | | * +========+========+============+=============+ - * | AccECN | Nonce | 1 0 1 | (Reserved) | * | AccECN | ECN | 0 0 1 | Classic ECN | * | Nonce | AccECN | 0 0 1 | Classic ECN | * | ECN | AccECN | 0 0 1 | Classic ECN | @@ -509,20 +527,20 @@ static inline void tcp_ecn_rcv_synack(struct sock *sk, const struct sk_buff *skb */ if (tcp_ca_no_fallback_rfc3168(sk)) tcp_ecn_mode_set(tp, TCP_ECN_DISABLED); - else if (tcp_ecn_mode_pending(tp)) - /* Downgrade from AccECN, or requested initially */ + else tcp_ecn_mode_set(tp, TCP_ECN_MODE_RFC3168); break; - default: - tcp_ecn_mode_set(tp, TCP_ECN_MODE_ACCECN); - tp->syn_ect_rcv = ip_dsfield & INET_ECN_MASK; - if (tp->rx_opt.accecn && - tp->saw_accecn_opt < TCP_ACCECN_OPT_COUNTER_SEEN) { - u8 saw_opt = tcp_accecn_option_init(skb, tp->rx_opt.accecn); - - tcp_accecn_saw_opt_fail_recv(tp, saw_opt); - tp->accecn_opt_demand = 2; + case 0x5: + if (tcp_ecn_mode_pending(tp)) { + tcp_ecn_rcv_synack_accecn(sk, skb, ip_dsfield); + if (INET_ECN_is_ce(ip_dsfield)) { + tp->received_ce++; + tp->received_ce_pending++; + } } + break; + default: + tcp_ecn_rcv_synack_accecn(sk, skb, ip_dsfield); if (INET_ECN_is_ce(ip_dsfield) && tcp_accecn_validate_syn_feedback(sk, ace, tp->syn_ect_snt)) { From 10a66a44e3691c2c9257e272ac6f14db60168044 Mon Sep 17 00:00:00 2001 From: Tuo Li Date: Mon, 5 Jan 2026 15:14:38 +0800 Subject: [PATCH 3547/3902] of: unittest: fix possible null-pointer dereferences in of_unittest_property_copy() [ Upstream commit d289cb7fcefe41a54d8f9c6d0e0947f5f82b15c6 ] This function first duplicates p1 and p2 into new, and then checks whether the duplication succeeds. However, if the duplication fails (e.g., kzalloc() returns NULL in __of_prop_dup()), new will be NULL but is still dereferenced in __of_prop_free(). To ensure that the unit test continues to run even when duplication fails, add a NULL check before calling __of_prop_free(). Fixes: 1c5e3d9bf33b ("of: Add a helper to free property struct") Signed-off-by: Tuo Li Link: https://patch.msgid.link/20260105071438.156186-1-islituo@gmail.com Signed-off-by: Rob Herring (Arm) Signed-off-by: Sasha Levin --- drivers/of/unittest.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/drivers/of/unittest.c b/drivers/of/unittest.c index 3b773aaf9d0503..9c184e93f50c67 100644 --- a/drivers/of/unittest.c +++ b/drivers/of/unittest.c @@ -804,11 +804,13 @@ static void __init of_unittest_property_copy(void) new = __of_prop_dup(&p1, GFP_KERNEL); unittest(new && propcmp(&p1, new), "empty property didn't copy correctly\n"); - __of_prop_free(new); + if (new) + __of_prop_free(new); new = __of_prop_dup(&p2, GFP_KERNEL); unittest(new && propcmp(&p2, new), "non-empty property didn't copy correctly\n"); - __of_prop_free(new); + if (new) + __of_prop_free(new); #endif } From fb7bf00b04a6b48859f52035d4e745848c2b4c79 Mon Sep 17 00:00:00 2001 From: Paolo Abeni Date: Tue, 3 Feb 2026 19:41:17 +0100 Subject: [PATCH 3548/3902] mptcp: do not account for OoO in mptcp_rcvbuf_grow() [ Upstream commit 6b329393502e5857662b851a13f947209c588587 ] MPTCP-level OoOs are physiological when multiple subflows are active concurrently and will not cause retransmissions nor are caused by drops. Accounting for them in mptcp_rcvbuf_grow() causes the rcvbuf slowly drifting towards tcp_rmem[2]. Remove such accounting. Note that subflows will still account for TCP-level OoO when the MPTCP-level rcvbuf is propagated. This also closes a subtle and very unlikely race condition with rcvspace init; active sockets with user-space holding the msk-level socket lock, could complete such initialization in the receive callback, after that the first OoO data reaches the rcvbuf and potentially triggering a divide by zero Oops. Fixes: e118cdc34dd1 ("mptcp: rcvbuf auto-tuning improvement") Signed-off-by: Paolo Abeni Reviewed-by: Mat Martineau Signed-off-by: Matthieu Baerts (NGI0) Link: https://patch.msgid.link/20260203-net-next-mptcp-misc-feat-6-20-v1-1-31ec8bfc56d1@kernel.org Signed-off-by: Jakub Kicinski Signed-off-by: Sasha Levin --- net/mptcp/protocol.c | 6 ------ 1 file changed, 6 deletions(-) diff --git a/net/mptcp/protocol.c b/net/mptcp/protocol.c index e4bb7e2d7b19a0..15a70af7b776dc 100644 --- a/net/mptcp/protocol.c +++ b/net/mptcp/protocol.c @@ -224,9 +224,6 @@ static bool mptcp_rcvbuf_grow(struct sock *sk, u32 newval) do_div(grow, oldval); rcvwin += grow << 1; - if (!RB_EMPTY_ROOT(&msk->out_of_order_queue)) - rcvwin += MPTCP_SKB_CB(msk->ooo_last_skb)->end_seq - msk->ack_seq; - cap = READ_ONCE(net->ipv4.sysctl_tcp_rmem[2]); rcvbuf = min_t(u32, mptcp_space_from_win(sk, rcvwin), cap); @@ -350,9 +347,6 @@ static void mptcp_data_queue_ofo(struct mptcp_sock *msk, struct sk_buff *skb) end: skb_condense(skb); skb_set_owner_r(skb, sk); - /* do not grow rcvbuf for not-yet-accepted or orphaned sockets. */ - if (sk->sk_socket) - mptcp_rcvbuf_grow(sk, msk->rcvq_space.space); } static void mptcp_init_skb(struct sock *ssk, struct sk_buff *skb, int offset, From 1fff00261c7883baafe98541481a3732d652d9fc Mon Sep 17 00:00:00 2001 From: Paolo Abeni Date: Tue, 3 Feb 2026 19:41:18 +0100 Subject: [PATCH 3549/3902] mptcp: fix receive space timestamp initialization [ Upstream commit 70274765fef555af92a1532d5bd5450c691fca9d ] MPTCP initialize the receive buffer stamp in mptcp_rcv_space_init(), using the provided subflow stamp. Such helper is invoked in several places; for passive sockets, space init happened at clone time. In such scenario, MPTCP ends-up accesses the subflow stamp before its initialization, leading to quite randomic timing for the first receive buffer auto-tune event, as the timestamp for newly created subflow is not refreshed there. Fix the issue moving the stamp initialization out of the mentioned helper, at the data transfer start, and always using a fresh timestamp. Fixes: 013e3179dbd2 ("mptcp: fix rcv space initialization") Reviewed-by: Mat Martineau Signed-off-by: Paolo Abeni Signed-off-by: Matthieu Baerts (NGI0) Link: https://patch.msgid.link/20260203-net-next-mptcp-misc-feat-6-20-v1-2-31ec8bfc56d1@kernel.org Signed-off-by: Jakub Kicinski Signed-off-by: Sasha Levin --- net/mptcp/protocol.c | 8 ++++---- net/mptcp/protocol.h | 5 +++++ 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/net/mptcp/protocol.c b/net/mptcp/protocol.c index 15a70af7b776dc..8f18509204b675 100644 --- a/net/mptcp/protocol.c +++ b/net/mptcp/protocol.c @@ -2056,8 +2056,8 @@ static void mptcp_rcv_space_adjust(struct mptcp_sock *msk, int copied) msk->rcvq_space.copied += copied; - mstamp = div_u64(tcp_clock_ns(), NSEC_PER_USEC); - time = tcp_stamp_us_delta(mstamp, msk->rcvq_space.time); + mstamp = mptcp_stamp(); + time = tcp_stamp_us_delta(mstamp, READ_ONCE(msk->rcvq_space.time)); rtt_us = msk->rcvq_space.rtt_us; if (rtt_us && time < (rtt_us >> 3)) @@ -3421,6 +3421,7 @@ struct sock *mptcp_sk_clone_init(const struct sock *sk, __mptcp_propagate_sndbuf(nsk, ssk); mptcp_rcv_space_init(msk, ssk); + msk->rcvq_space.time = mptcp_stamp(); if (mp_opt->suboptions & OPTION_MPTCP_MPC_ACK) __mptcp_subflow_fully_established(msk, subflow, mp_opt); @@ -3438,8 +3439,6 @@ void mptcp_rcv_space_init(struct mptcp_sock *msk, const struct sock *ssk) msk->rcvq_space.copied = 0; msk->rcvq_space.rtt_us = 0; - msk->rcvq_space.time = tp->tcp_mstamp; - /* initial rcv_space offering made to peer */ msk->rcvq_space.space = min_t(u32, tp->rcv_wnd, TCP_INIT_CWND * tp->advmss); @@ -3652,6 +3651,7 @@ void mptcp_finish_connect(struct sock *ssk) * accessing the field below */ WRITE_ONCE(msk->local_key, subflow->local_key); + WRITE_ONCE(msk->rcvq_space.time, mptcp_stamp()); mptcp_pm_new_connection(msk, ssk, 0); } diff --git a/net/mptcp/protocol.h b/net/mptcp/protocol.h index 30d5e57197936e..27b1698c5aa2d0 100644 --- a/net/mptcp/protocol.h +++ b/net/mptcp/protocol.h @@ -870,6 +870,11 @@ static inline bool mptcp_is_fully_established(struct sock *sk) READ_ONCE(mptcp_sk(sk)->fully_established); } +static inline u64 mptcp_stamp(void) +{ + return div_u64(tcp_clock_ns(), NSEC_PER_USEC); +} + void mptcp_rcv_space_init(struct mptcp_sock *msk, const struct sock *ssk); void mptcp_data_ready(struct sock *sk, struct sock *ssk); bool mptcp_finish_join(struct sock *sk); From 9c3398e5b3a914b74276d44ab54c49123b89c61a Mon Sep 17 00:00:00 2001 From: Anshumali Gaur Date: Tue, 3 Feb 2026 10:37:01 +0530 Subject: [PATCH 3550/3902] octeontx2-af: Fix PF driver crash with kexec kernel booting [ Upstream commit 2d2d574309e3ae84ee794869a5da8b4c38753a94 ] During a kexec reboot the hardware is not power-cycled, so AF state from the old kernel can persist into the new kernel. When AF and PF drivers are built as modules, the PF driver may probe before AF reinitializes the hardware. The PF driver treats the RVUM block revision as an indication that AF initialization is complete. If this value is left uncleared at shutdown, PF may incorrectly assume AF is ready and access stale hardware state, leading to a crash. Clear the RVUM block revision during AF shutdown to avoid PF mis-detecting AF readiness after kexec. Fixes: 54494aa5d1e6 ("octeontx2-af: Add Marvell OcteonTX2 RVU AF driver") Signed-off-by: Anshumali Gaur Reviewed-by: Jacob Keller Link: https://patch.msgid.link/20260203050701.2616685-1-agaur@marvell.com Signed-off-by: Jakub Kicinski Signed-off-by: Sasha Levin --- drivers/net/ethernet/marvell/octeontx2/af/rvu.c | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/drivers/net/ethernet/marvell/octeontx2/af/rvu.c b/drivers/net/ethernet/marvell/octeontx2/af/rvu.c index 747fbdf2a908f1..8530df8b3fdaf7 100644 --- a/drivers/net/ethernet/marvell/octeontx2/af/rvu.c +++ b/drivers/net/ethernet/marvell/octeontx2/af/rvu.c @@ -3632,11 +3632,22 @@ static void rvu_remove(struct pci_dev *pdev) devm_kfree(&pdev->dev, rvu); } +static void rvu_shutdown(struct pci_dev *pdev) +{ + struct rvu *rvu = pci_get_drvdata(pdev); + + if (!rvu) + return; + + rvu_clear_rvum_blk_revid(rvu); +} + static struct pci_driver rvu_driver = { .name = DRV_NAME, .id_table = rvu_id_table, .probe = rvu_probe, .remove = rvu_remove, + .shutdown = rvu_shutdown, }; static int __init rvu_init_module(void) From 67b75bb0ec55a31e88dd28939406e311b5fbe7f3 Mon Sep 17 00:00:00 2001 From: Thomas Bogendoerfer Date: Tue, 3 Feb 2026 15:11:52 +0100 Subject: [PATCH 3551/3902] bonding: only set speed/duplex to unknown, if getting speed failed [ Upstream commit 48dec8d88af96039a4a17b8c2f148f2a4066e195 ] bond_update_speed_duplex() first set speed/duplex to unknown and then asks slave driver for current speed/duplex. Since getting speed/duplex might take longer there is a race, where this false state is visible by /proc/net/bonding. With commit 691b2bf14946 ("bonding: update port speed when getting bond speed") this race gets more visible, if user space is calling ethtool on a regular base. Fix this by only setting speed/duplex to unknown, if link speed is really unknown/unusable. Fixes: 98f41f694f46 ("bonding:update speed/duplex for NETDEV_CHANGE") Signed-off-by: Thomas Bogendoerfer Acked-by: Jay Vosburgh Reviewed-by: Nikolay Aleksandrov Reviewed-by: Hangbin Liu Link: https://patch.msgid.link/20260203141153.51581-1-tbogendoerfer@suse.de Signed-off-by: Jakub Kicinski Signed-off-by: Sasha Levin --- drivers/net/bonding/bond_main.c | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/drivers/net/bonding/bond_main.c b/drivers/net/bonding/bond_main.c index 51733fb29bd77a..166dff47a029ff 100644 --- a/drivers/net/bonding/bond_main.c +++ b/drivers/net/bonding/bond_main.c @@ -791,26 +791,29 @@ static int bond_update_speed_duplex(struct slave *slave) struct ethtool_link_ksettings ecmd; int res; - slave->speed = SPEED_UNKNOWN; - slave->duplex = DUPLEX_UNKNOWN; - res = __ethtool_get_link_ksettings(slave_dev, &ecmd); if (res < 0) - return 1; + goto speed_duplex_unknown; if (ecmd.base.speed == 0 || ecmd.base.speed == ((__u32)-1)) - return 1; + goto speed_duplex_unknown; switch (ecmd.base.duplex) { case DUPLEX_FULL: case DUPLEX_HALF: break; default: - return 1; + goto speed_duplex_unknown; } slave->speed = ecmd.base.speed; slave->duplex = ecmd.base.duplex; return 0; + +speed_duplex_unknown: + slave->speed = SPEED_UNKNOWN; + slave->duplex = DUPLEX_UNKNOWN; + + return 1; } const char *bond_slave_link_status(s8 link) From 531c1aec81bfe19d00af13da5531fbb8209e4bd2 Mon Sep 17 00:00:00 2001 From: Eric Dumazet Date: Tue, 3 Feb 2026 19:25:09 +0000 Subject: [PATCH 3552/3902] inet: RAW sockets using IPPROTO_RAW MUST drop incoming ICMP [ Upstream commit c89477ad79446867394360b29bb801010fc3ff22 ] Yizhou Zhao reported that simply having one RAW socket on protocol IPPROTO_RAW (255) was dangerous. socket(AF_INET, SOCK_RAW, 255); A malicious incoming ICMP packet can set the protocol field to 255 and match this socket, leading to FNHE cache changes. inner = IP(src="192.168.2.1", dst="8.8.8.8", proto=255)/Raw("TEST") pkt = IP(src="192.168.1.1", dst="192.168.2.1")/ICMP(type=3, code=4, nexthopmtu=576)/inner "man 7 raw" states: A protocol of IPPROTO_RAW implies enabled IP_HDRINCL and is able to send any IP protocol that is specified in the passed header. Receiving of all IP protocols via IPPROTO_RAW is not possible using raw sockets. Make sure we drop these malicious packets. Fixes: 1da177e4c3f4 ("Linux-2.6.12-rc2") Reported-by: Yizhou Zhao Link: https://lore.kernel.org/netdev/20251109134600.292125-1-zhaoyz24@mails.tsinghua.edu.cn/ Signed-off-by: Eric Dumazet Reviewed-by: David Ahern Reviewed-by: Ido Schimmel Link: https://patch.msgid.link/20260203192509.682208-1-edumazet@google.com Signed-off-by: Jakub Kicinski Signed-off-by: Sasha Levin --- net/ipv4/icmp.c | 14 ++++++++++---- net/ipv6/icmp.c | 6 ++++++ 2 files changed, 16 insertions(+), 4 deletions(-) diff --git a/net/ipv4/icmp.c b/net/ipv4/icmp.c index 1b7fb5d935edfd..8e10e9e7676c58 100644 --- a/net/ipv4/icmp.c +++ b/net/ipv4/icmp.c @@ -843,16 +843,22 @@ static void icmp_socket_deliver(struct sk_buff *skb, u32 info) /* Checkin full IP header plus 8 bytes of protocol to * avoid additional coding at protocol handlers. */ - if (!pskb_may_pull(skb, iph->ihl * 4 + 8)) { - __ICMP_INC_STATS(dev_net_rcu(skb->dev), ICMP_MIB_INERRORS); - return; - } + if (!pskb_may_pull(skb, iph->ihl * 4 + 8)) + goto out; + + /* IPPROTO_RAW sockets are not supposed to receive anything. */ + if (protocol == IPPROTO_RAW) + goto out; raw_icmp_error(skb, protocol, info); ipprot = rcu_dereference(inet_protos[protocol]); if (ipprot && ipprot->err_handler) ipprot->err_handler(skb, info); + return; + +out: + __ICMP_INC_STATS(dev_net_rcu(skb->dev), ICMP_MIB_INERRORS); } static bool icmp_tag_validation(int proto) diff --git a/net/ipv6/icmp.c b/net/ipv6/icmp.c index cf6455cbe2cc91..306eec18e82c17 100644 --- a/net/ipv6/icmp.c +++ b/net/ipv6/icmp.c @@ -870,6 +870,12 @@ enum skb_drop_reason icmpv6_notify(struct sk_buff *skb, u8 type, if (reason != SKB_NOT_DROPPED_YET) goto out; + if (nexthdr == IPPROTO_RAW) { + /* Add a more specific reason later ? */ + reason = SKB_DROP_REASON_NOT_SPECIFIED; + goto out; + } + /* BUGGG_FUTURE: we should try to parse exthdrs in this packet. Without this we will not able f.e. to make source routed pmtu discovery. From 276820278e9717cc7d4bb32381892dd3ddf418d4 Mon Sep 17 00:00:00 2001 From: Votokina Victoria Date: Tue, 3 Feb 2026 14:31:57 +0300 Subject: [PATCH 3553/3902] nfc: hci: shdlc: Stop timers and work before freeing context [ Upstream commit c9efde1e537baed7648a94022b43836a348a074f ] llc_shdlc_deinit() purges SHDLC skb queues and frees the llc_shdlc structure while its timers and state machine work may still be active. Timer callbacks can schedule sm_work, and sm_work accesses SHDLC state and the skb queues. If teardown happens in parallel with a queued/running work item, it can lead to UAF and other shutdown races. Stop all SHDLC timers and cancel sm_work synchronously before purging the queues and freeing the context. Found by Linux Verification Center (linuxtesting.org) with SVACE. Fixes: 4a61cd6687fc ("NFC: Add an shdlc llc module to llc core") Signed-off-by: Votokina Victoria Link: https://patch.msgid.link/20260203113158.2008723-1-Victoria.Votokina@kaspersky.com Signed-off-by: Jakub Kicinski Signed-off-by: Sasha Levin --- net/nfc/hci/llc_shdlc.c | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/net/nfc/hci/llc_shdlc.c b/net/nfc/hci/llc_shdlc.c index 4fc37894860c9e..08c8aa1530d8a0 100644 --- a/net/nfc/hci/llc_shdlc.c +++ b/net/nfc/hci/llc_shdlc.c @@ -762,6 +762,14 @@ static void llc_shdlc_deinit(struct nfc_llc *llc) { struct llc_shdlc *shdlc = nfc_llc_get_data(llc); + timer_shutdown_sync(&shdlc->connect_timer); + timer_shutdown_sync(&shdlc->t1_timer); + timer_shutdown_sync(&shdlc->t2_timer); + shdlc->t1_active = false; + shdlc->t2_active = false; + + cancel_work_sync(&shdlc->sm_work); + skb_queue_purge(&shdlc->rcv_q); skb_queue_purge(&shdlc->send_q); skb_queue_purge(&shdlc->ack_pending_q); From 870e3e63da8e88daffe9d692a025c711658018a8 Mon Sep 17 00:00:00 2001 From: Scott Mitchell Date: Fri, 23 Jan 2026 14:09:30 -0800 Subject: [PATCH 3554/3902] netfilter: nfnetlink_queue: optimize verdict lookup with hash table [ Upstream commit e19079adcd26a25d7d3e586b1837493361fdf8b6 ] The current implementation uses a linear list to find queued packets by ID when processing verdicts from userspace. With large queue depths and out-of-order verdicting, this O(n) lookup becomes a significant bottleneck, causing userspace verdict processing to dominate CPU time. Replace the linear search with a hash table for O(1) average-case packet lookup by ID. A global rhashtable spanning all network namespaces attributes hash bucket memory to kernel but is subject to fixed upper bound. Signed-off-by: Scott Mitchell Signed-off-by: Florian Westphal Stable-dep-of: 207b3ebacb61 ("netfilter: nfnetlink_queue: do shared-unconfirmed check before segmentation") Signed-off-by: Sasha Levin --- include/net/netfilter/nf_queue.h | 3 + net/netfilter/nfnetlink_queue.c | 146 ++++++++++++++++++++++++------- 2 files changed, 119 insertions(+), 30 deletions(-) diff --git a/include/net/netfilter/nf_queue.h b/include/net/netfilter/nf_queue.h index 4aeffddb75861e..e6803831d6af51 100644 --- a/include/net/netfilter/nf_queue.h +++ b/include/net/netfilter/nf_queue.h @@ -6,11 +6,13 @@ #include #include #include +#include #include /* Each queued (to userspace) skbuff has one of these. */ struct nf_queue_entry { struct list_head list; + struct rhash_head hash_node; struct sk_buff *skb; unsigned int id; unsigned int hook_index; /* index in hook_entries->hook[] */ @@ -20,6 +22,7 @@ struct nf_queue_entry { #endif struct nf_hook_state state; u16 size; /* sizeof(entry) + saved route keys */ + u16 queue_num; /* extra space to store route keys */ }; diff --git a/net/netfilter/nfnetlink_queue.c b/net/netfilter/nfnetlink_queue.c index 8b7b39d8a10913..336e3ad18e72dc 100644 --- a/net/netfilter/nfnetlink_queue.c +++ b/net/netfilter/nfnetlink_queue.c @@ -30,6 +30,8 @@ #include #include #include +#include +#include #include #include #include @@ -47,6 +49,8 @@ #endif #define NFQNL_QMAX_DEFAULT 1024 +#define NFQNL_HASH_MIN 1024 +#define NFQNL_HASH_MAX 1048576 /* We're using struct nlattr which has 16bit nla_len. Note that nla_len * includes the header length. Thus, the maximum packet length that we @@ -56,6 +60,26 @@ */ #define NFQNL_MAX_COPY_RANGE (0xffff - NLA_HDRLEN) +/* Composite key for packet lookup: (net, queue_num, packet_id) */ +struct nfqnl_packet_key { + possible_net_t net; + u32 packet_id; + u16 queue_num; +} __aligned(sizeof(u32)); /* jhash2 requires 32-bit alignment */ + +/* Global rhashtable - one for entire system, all netns */ +static struct rhashtable nfqnl_packet_map __read_mostly; + +/* Helper to initialize composite key */ +static inline void nfqnl_init_key(struct nfqnl_packet_key *key, + struct net *net, u32 packet_id, u16 queue_num) +{ + memset(key, 0, sizeof(*key)); + write_pnet(&key->net, net); + key->packet_id = packet_id; + key->queue_num = queue_num; +} + struct nfqnl_instance { struct hlist_node hlist; /* global list of queues */ struct rcu_head rcu; @@ -100,6 +124,39 @@ static inline u_int8_t instance_hashfn(u_int16_t queue_num) return ((queue_num >> 8) ^ queue_num) % INSTANCE_BUCKETS; } +/* Extract composite key from nf_queue_entry for hashing */ +static u32 nfqnl_packet_obj_hashfn(const void *data, u32 len, u32 seed) +{ + const struct nf_queue_entry *entry = data; + struct nfqnl_packet_key key; + + nfqnl_init_key(&key, entry->state.net, entry->id, entry->queue_num); + + return jhash2((u32 *)&key, sizeof(key) / sizeof(u32), seed); +} + +/* Compare stack-allocated key against entry */ +static int nfqnl_packet_obj_cmpfn(struct rhashtable_compare_arg *arg, + const void *obj) +{ + const struct nfqnl_packet_key *key = arg->key; + const struct nf_queue_entry *entry = obj; + + return !net_eq(entry->state.net, read_pnet(&key->net)) || + entry->queue_num != key->queue_num || + entry->id != key->packet_id; +} + +static const struct rhashtable_params nfqnl_rhashtable_params = { + .head_offset = offsetof(struct nf_queue_entry, hash_node), + .key_len = sizeof(struct nfqnl_packet_key), + .obj_hashfn = nfqnl_packet_obj_hashfn, + .obj_cmpfn = nfqnl_packet_obj_cmpfn, + .automatic_shrinking = true, + .min_size = NFQNL_HASH_MIN, + .max_size = NFQNL_HASH_MAX, +}; + static struct nfqnl_instance * instance_lookup(struct nfnl_queue_net *q, u_int16_t queue_num) { @@ -191,33 +248,45 @@ instance_destroy(struct nfnl_queue_net *q, struct nfqnl_instance *inst) spin_unlock(&q->instances_lock); } -static inline void +static int __enqueue_entry(struct nfqnl_instance *queue, struct nf_queue_entry *entry) { - list_add_tail(&entry->list, &queue->queue_list); - queue->queue_total++; + int err; + + entry->queue_num = queue->queue_num; + + err = rhashtable_insert_fast(&nfqnl_packet_map, &entry->hash_node, + nfqnl_rhashtable_params); + if (unlikely(err)) + return err; + + list_add_tail(&entry->list, &queue->queue_list); + queue->queue_total++; + + return 0; } static void __dequeue_entry(struct nfqnl_instance *queue, struct nf_queue_entry *entry) { + rhashtable_remove_fast(&nfqnl_packet_map, &entry->hash_node, + nfqnl_rhashtable_params); list_del(&entry->list); queue->queue_total--; } static struct nf_queue_entry * -find_dequeue_entry(struct nfqnl_instance *queue, unsigned int id) +find_dequeue_entry(struct nfqnl_instance *queue, unsigned int id, + struct net *net) { - struct nf_queue_entry *entry = NULL, *i; + struct nfqnl_packet_key key; + struct nf_queue_entry *entry; - spin_lock_bh(&queue->lock); + nfqnl_init_key(&key, net, id, queue->queue_num); - list_for_each_entry(i, &queue->queue_list, list) { - if (i->id == id) { - entry = i; - break; - } - } + spin_lock_bh(&queue->lock); + entry = rhashtable_lookup_fast(&nfqnl_packet_map, &key, + nfqnl_rhashtable_params); if (entry) __dequeue_entry(queue, entry); @@ -407,8 +476,7 @@ nfqnl_flush(struct nfqnl_instance *queue, nfqnl_cmpfn cmpfn, unsigned long data) spin_lock_bh(&queue->lock); list_for_each_entry_safe(entry, next, &queue->queue_list, list) { if (!cmpfn || cmpfn(entry, data)) { - list_del(&entry->list); - queue->queue_total--; + __dequeue_entry(queue, entry); nfqnl_reinject(entry, NF_DROP); } } @@ -888,23 +956,23 @@ __nfqnl_enqueue_packet(struct net *net, struct nfqnl_instance *queue, if (nf_ct_drop_unconfirmed(entry)) goto err_out_free_nskb; - if (queue->queue_total >= queue->queue_maxlen) { - if (queue->flags & NFQA_CFG_F_FAIL_OPEN) { - failopen = 1; - err = 0; - } else { - queue->queue_dropped++; - net_warn_ratelimited("nf_queue: full at %d entries, dropping packets(s)\n", - queue->queue_total); - } - goto err_out_free_nskb; - } + if (queue->queue_total >= queue->queue_maxlen) + goto err_out_queue_drop; + entry->id = ++queue->id_sequence; *packet_id_ptr = htonl(entry->id); + /* Insert into hash BEFORE unicast. If failure don't send to userspace. */ + err = __enqueue_entry(queue, entry); + if (unlikely(err)) + goto err_out_queue_drop; + /* nfnetlink_unicast will either free the nskb or add it to a socket */ err = nfnetlink_unicast(nskb, net, queue->peer_portid); if (err < 0) { + /* Unicast failed - remove entry we just inserted */ + __dequeue_entry(queue, entry); + if (queue->flags & NFQA_CFG_F_FAIL_OPEN) { failopen = 1; err = 0; @@ -914,11 +982,22 @@ __nfqnl_enqueue_packet(struct net *net, struct nfqnl_instance *queue, goto err_out_unlock; } - __enqueue_entry(queue, entry); - spin_unlock_bh(&queue->lock); return 0; +err_out_queue_drop: + if (queue->flags & NFQA_CFG_F_FAIL_OPEN) { + failopen = 1; + err = 0; + } else { + queue->queue_dropped++; + + if (queue->queue_total >= queue->queue_maxlen) + net_warn_ratelimited("nf_queue: full at %d entries, dropping packets(s)\n", + queue->queue_total); + else + net_warn_ratelimited("nf_queue: hash insert failed: %d\n", err); + } err_out_free_nskb: kfree_skb(nskb); err_out_unlock: @@ -1430,7 +1509,7 @@ static int nfqnl_recv_verdict(struct sk_buff *skb, const struct nfnl_info *info, verdict = ntohl(vhdr->verdict); - entry = find_dequeue_entry(queue, ntohl(vhdr->id)); + entry = find_dequeue_entry(queue, ntohl(vhdr->id), info->net); if (entry == NULL) return -ENOENT; @@ -1781,10 +1860,14 @@ static int __init nfnetlink_queue_init(void) { int status; + status = rhashtable_init(&nfqnl_packet_map, &nfqnl_rhashtable_params); + if (status < 0) + return status; + status = register_pernet_subsys(&nfnl_queue_net_ops); if (status < 0) { pr_err("failed to register pernet ops\n"); - goto out; + goto cleanup_rhashtable; } netlink_register_notifier(&nfqnl_rtnl_notifier); @@ -1809,7 +1892,8 @@ static int __init nfnetlink_queue_init(void) cleanup_netlink_notifier: netlink_unregister_notifier(&nfqnl_rtnl_notifier); unregister_pernet_subsys(&nfnl_queue_net_ops); -out: +cleanup_rhashtable: + rhashtable_destroy(&nfqnl_packet_map); return status; } @@ -1821,6 +1905,8 @@ static void __exit nfnetlink_queue_fini(void) netlink_unregister_notifier(&nfqnl_rtnl_notifier); unregister_pernet_subsys(&nfnl_queue_net_ops); + rhashtable_destroy(&nfqnl_packet_map); + rcu_barrier(); /* Wait for completion of call_rcu()'s */ } From 23901aa6b8a2f294c4b774436b4691f3ff863a8f Mon Sep 17 00:00:00 2001 From: Florian Westphal Date: Thu, 20 Nov 2025 17:17:06 +0100 Subject: [PATCH 3555/3902] netfilter: nfnetlink_queue: do shared-unconfirmed check before segmentation [ Upstream commit 207b3ebacb6113acaaec0d171d5307032c690004 ] Ulrich reports a regression with nfqueue: If an application did not set the 'F_GSO' capability flag and a gso packet with an unconfirmed nf_conn entry is received all packets are now dropped instead of queued, because the check happens after skb_gso_segment(). In that case, we did have exclusive ownership of the skb and its associated conntrack entry. The elevated use count is due to skb_clone happening via skb_gso_segment(). Move the check so that its peformed vs. the aggregated packet. Then, annotate the individual segments except the first one so we can do a 2nd check at reinject time. For the normal case, where userspace does in-order reinjects, this avoids packet drops: first reinjected segment continues traversal and confirms entry, remaining segments observe the confirmed entry. While at it, simplify nf_ct_drop_unconfirmed(): We only care about unconfirmed entries with a refcnt > 1, there is no need to special-case dying entries. This only happens with UDP. With TCP, the only unconfirmed packet will be the TCP SYN, those aren't aggregated by GRO. Next patch adds a udpgro test case to cover this scenario. Reported-by: Ulrich Weber Fixes: 7d8dc1c7be8d ("netfilter: nf_queue: drop packets with cloned unconfirmed conntracks") Signed-off-by: Florian Westphal Signed-off-by: Sasha Levin --- include/net/netfilter/nf_queue.h | 1 + net/netfilter/nfnetlink_queue.c | 123 +++++++++++++++++++------------ 2 files changed, 75 insertions(+), 49 deletions(-) diff --git a/include/net/netfilter/nf_queue.h b/include/net/netfilter/nf_queue.h index e6803831d6af51..45eb26b2e95b37 100644 --- a/include/net/netfilter/nf_queue.h +++ b/include/net/netfilter/nf_queue.h @@ -21,6 +21,7 @@ struct nf_queue_entry { struct net_device *physout; #endif struct nf_hook_state state; + bool nf_ct_is_unconfirmed; u16 size; /* sizeof(entry) + saved route keys */ u16 queue_num; diff --git a/net/netfilter/nfnetlink_queue.c b/net/netfilter/nfnetlink_queue.c index 336e3ad18e72dc..34548213f2f14f 100644 --- a/net/netfilter/nfnetlink_queue.c +++ b/net/netfilter/nfnetlink_queue.c @@ -438,6 +438,34 @@ static void nf_reinject(struct nf_queue_entry *entry, unsigned int verdict) nf_queue_entry_free(entry); } +/* return true if the entry has an unconfirmed conntrack attached that isn't owned by us + * exclusively. + */ +static bool nf_ct_drop_unconfirmed(const struct nf_queue_entry *entry, bool *is_unconfirmed) +{ +#if IS_ENABLED(CONFIG_NF_CONNTRACK) + struct nf_conn *ct = (void *)skb_nfct(entry->skb); + + if (!ct || nf_ct_is_confirmed(ct)) + return false; + + if (is_unconfirmed) + *is_unconfirmed = true; + + /* in some cases skb_clone() can occur after initial conntrack + * pickup, but conntrack assumes exclusive skb->_nfct ownership for + * unconfirmed entries. + * + * This happens for br_netfilter and with ip multicast routing. + * This can't be solved with serialization here because one clone + * could have been queued for local delivery or could be transmitted + * in parallel on another CPU. + */ + return refcount_read(&ct->ct_general.use) > 1; +#endif + return false; +} + static void nfqnl_reinject(struct nf_queue_entry *entry, unsigned int verdict) { const struct nf_ct_hook *ct_hook; @@ -465,6 +493,24 @@ static void nfqnl_reinject(struct nf_queue_entry *entry, unsigned int verdict) break; } } + + if (verdict != NF_DROP && entry->nf_ct_is_unconfirmed) { + /* If first queued segment was already reinjected then + * there is a good chance the ct entry is now confirmed. + * + * Handle the rare cases: + * - out-of-order verdict + * - threaded userspace reinjecting in parallel + * - first segment was dropped + * + * In all of those cases we can't handle this packet + * because we can't be sure that another CPU won't modify + * nf_conn->ext in parallel which isn't allowed. + */ + if (nf_ct_drop_unconfirmed(entry, NULL)) + verdict = NF_DROP; + } + nf_reinject(entry, verdict); } @@ -894,49 +940,6 @@ nfqnl_build_packet_message(struct net *net, struct nfqnl_instance *queue, return NULL; } -static bool nf_ct_drop_unconfirmed(const struct nf_queue_entry *entry) -{ -#if IS_ENABLED(CONFIG_NF_CONNTRACK) - static const unsigned long flags = IPS_CONFIRMED | IPS_DYING; - struct nf_conn *ct = (void *)skb_nfct(entry->skb); - unsigned long status; - unsigned int use; - - if (!ct) - return false; - - status = READ_ONCE(ct->status); - if ((status & flags) == IPS_DYING) - return true; - - if (status & IPS_CONFIRMED) - return false; - - /* in some cases skb_clone() can occur after initial conntrack - * pickup, but conntrack assumes exclusive skb->_nfct ownership for - * unconfirmed entries. - * - * This happens for br_netfilter and with ip multicast routing. - * We can't be solved with serialization here because one clone could - * have been queued for local delivery. - */ - use = refcount_read(&ct->ct_general.use); - if (likely(use == 1)) - return false; - - /* Can't decrement further? Exclusive ownership. */ - if (!refcount_dec_not_one(&ct->ct_general.use)) - return false; - - skb_set_nfct(entry->skb, 0); - /* No nf_ct_put(): we already decremented .use and it cannot - * drop down to 0. - */ - return true; -#endif - return false; -} - static int __nfqnl_enqueue_packet(struct net *net, struct nfqnl_instance *queue, struct nf_queue_entry *entry) @@ -953,9 +956,6 @@ __nfqnl_enqueue_packet(struct net *net, struct nfqnl_instance *queue, } spin_lock_bh(&queue->lock); - if (nf_ct_drop_unconfirmed(entry)) - goto err_out_free_nskb; - if (queue->queue_total >= queue->queue_maxlen) goto err_out_queue_drop; @@ -998,7 +998,6 @@ __nfqnl_enqueue_packet(struct net *net, struct nfqnl_instance *queue, else net_warn_ratelimited("nf_queue: hash insert failed: %d\n", err); } -err_out_free_nskb: kfree_skb(nskb); err_out_unlock: spin_unlock_bh(&queue->lock); @@ -1077,9 +1076,10 @@ __nfqnl_enqueue_packet_gso(struct net *net, struct nfqnl_instance *queue, static int nfqnl_enqueue_packet(struct nf_queue_entry *entry, unsigned int queuenum) { - unsigned int queued; - struct nfqnl_instance *queue; struct sk_buff *skb, *segs, *nskb; + bool ct_is_unconfirmed = false; + struct nfqnl_instance *queue; + unsigned int queued; int err = -ENOBUFS; struct net *net = entry->state.net; struct nfnl_queue_net *q = nfnl_queue_pernet(net); @@ -1103,6 +1103,15 @@ nfqnl_enqueue_packet(struct nf_queue_entry *entry, unsigned int queuenum) break; } + /* Check if someone already holds another reference to + * unconfirmed ct. If so, we cannot queue the skb: + * concurrent modifications of nf_conn->ext are not + * allowed and we can't know if another CPU isn't + * processing the same nf_conn entry in parallel. + */ + if (nf_ct_drop_unconfirmed(entry, &ct_is_unconfirmed)) + return -EINVAL; + if (!skb_is_gso(skb) || ((queue->flags & NFQA_CFG_F_GSO) && !skb_is_gso_sctp(skb))) return __nfqnl_enqueue_packet(net, queue, entry); @@ -1116,7 +1125,23 @@ nfqnl_enqueue_packet(struct nf_queue_entry *entry, unsigned int queuenum) goto out_err; queued = 0; err = 0; + skb_list_walk_safe(segs, segs, nskb) { + if (ct_is_unconfirmed && queued > 0) { + /* skb_gso_segment() increments the ct refcount. + * This is a problem for unconfirmed (not in hash) + * entries, those can race when reinjections happen + * in parallel. + * + * Annotate this for all queued entries except the + * first one. + * + * As long as the first one is reinjected first it + * will do the confirmation for us. + */ + entry->nf_ct_is_unconfirmed = ct_is_unconfirmed; + } + if (err == 0) err = __nfqnl_enqueue_packet_gso(net, queue, segs, entry); From 6f2d238faff01e3209032f983b0fef4bf648e13e Mon Sep 17 00:00:00 2001 From: Florian Westphal Date: Tue, 27 Jan 2026 20:13:45 +0100 Subject: [PATCH 3556/3902] netfilter: nft_set_hash: fix get operation on big endian [ Upstream commit 2f635adbe2642d398a0be3ab245accd2987be0c3 ] tests/shell/testcases/packetpath/set_match_nomatch_hash_fast fails on big endian with: Error: Could not process rule: No such file or directory reset element ip test s { 244.147.90.126 } ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Fatal: Cannot fetch element "244.147.90.126" ... because the wrong bucket is searched, jhash() and jhash1_word are not interchangeable on big endian. Fixes: 3b02b0adc242 ("netfilter: nft_set_hash: fix lookups with fixed size hash on big endian") Signed-off-by: Florian Westphal Signed-off-by: Sasha Levin --- net/netfilter/nft_set_hash.c | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/net/netfilter/nft_set_hash.c b/net/netfilter/nft_set_hash.c index ba01ce75d6dea9..739b992bde591e 100644 --- a/net/netfilter/nft_set_hash.c +++ b/net/netfilter/nft_set_hash.c @@ -619,15 +619,20 @@ static struct nft_elem_priv * nft_hash_get(const struct net *net, const struct nft_set *set, const struct nft_set_elem *elem, unsigned int flags) { + const u32 *key = (const u32 *)&elem->key.val; struct nft_hash *priv = nft_set_priv(set); u8 genmask = nft_genmask_cur(net); struct nft_hash_elem *he; u32 hash; - hash = jhash(elem->key.val.data, set->klen, priv->seed); + if (set->klen == 4) + hash = jhash_1word(*key, priv->seed); + else + hash = jhash(key, set->klen, priv->seed); + hash = reciprocal_scale(hash, priv->buckets); hlist_for_each_entry_rcu(he, &priv->table[hash], node) { - if (!memcmp(nft_set_ext_key(&he->ext), elem->key.val.data, set->klen) && + if (!memcmp(nft_set_ext_key(&he->ext), key, set->klen) && nft_set_elem_active(&he->ext, genmask)) return &he->priv; } From 735ad034f1dcbce15ac25c931898c0e093bd9dc9 Mon Sep 17 00:00:00 2001 From: Anders Grahn Date: Tue, 3 Feb 2026 14:48:30 +0100 Subject: [PATCH 3557/3902] netfilter: nft_counter: fix reset of counters on 32bit archs [ Upstream commit 1e13f27e0675552161ab1778be9a23a636dde8a7 ] nft_counter_reset() calls u64_stats_add() with a negative value to reset the counter. This will work on 64bit archs, hence the negative value added will wrap as a 64bit value which then can wrap the stat counter as well. On 32bit archs, the added negative value will wrap as a 32bit value and _not_ wrapping the stat counter properly. In most cases, this would just lead to a very large 32bit value being added to the stat counter. Fix by introducing u64_stats_sub(). Fixes: 4a1d3acd6ea8 ("netfilter: nft_counter: Use u64_stats_t for statistic.") Signed-off-by: Anders Grahn Signed-off-by: Florian Westphal Signed-off-by: Sasha Levin --- include/linux/u64_stats_sync.h | 10 ++++++++++ net/netfilter/nft_counter.c | 4 ++-- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/include/linux/u64_stats_sync.h b/include/linux/u64_stats_sync.h index 457879938fc198..3366090a86bd2f 100644 --- a/include/linux/u64_stats_sync.h +++ b/include/linux/u64_stats_sync.h @@ -89,6 +89,11 @@ static inline void u64_stats_add(u64_stats_t *p, unsigned long val) local64_add(val, &p->v); } +static inline void u64_stats_sub(u64_stats_t *p, s64 val) +{ + local64_sub(val, &p->v); +} + static inline void u64_stats_inc(u64_stats_t *p) { local64_inc(&p->v); @@ -130,6 +135,11 @@ static inline void u64_stats_add(u64_stats_t *p, unsigned long val) p->v += val; } +static inline void u64_stats_sub(u64_stats_t *p, s64 val) +{ + p->v -= val; +} + static inline void u64_stats_inc(u64_stats_t *p) { p->v++; diff --git a/net/netfilter/nft_counter.c b/net/netfilter/nft_counter.c index cc732532949630..0d70325280cc57 100644 --- a/net/netfilter/nft_counter.c +++ b/net/netfilter/nft_counter.c @@ -117,8 +117,8 @@ static void nft_counter_reset(struct nft_counter_percpu_priv *priv, nft_sync = this_cpu_ptr(&nft_counter_sync); u64_stats_update_begin(nft_sync); - u64_stats_add(&this_cpu->packets, -total->packets); - u64_stats_add(&this_cpu->bytes, -total->bytes); + u64_stats_sub(&this_cpu->packets, total->packets); + u64_stats_sub(&this_cpu->bytes, total->bytes); u64_stats_update_end(nft_sync); local_bh_enable(); From 40a7afde2f5369ad63fe801edbf4b44d51b17b8d Mon Sep 17 00:00:00 2001 From: Pablo Neira Ayuso Date: Fri, 6 Feb 2026 13:33:43 +0100 Subject: [PATCH 3558/3902] netfilter: nft_set_rbtree: fix bogus EEXIST with NLM_F_CREATE with null interval [ Upstream commit 7f9203f41aae8eea74fba6a3370da41332eabcda ] Userspace adds a non-matching null element to the kernel for historical reasons. This null element is added when the set is populated with elements. Inclusion of this element is conditional, therefore, userspace needs to dump the set content to check for its presence. If the NLM_F_CREATE flag is turned on, this becomes an issue because kernel bogusly reports EEXIST. Add special case to ignore NLM_F_CREATE in this case, therefore, re-adding the nul-element never fails. Fixes: c016c7e45ddf ("netfilter: nf_tables: honor NLM_F_EXCL flag in set element insertion") Signed-off-by: Pablo Neira Ayuso Signed-off-by: Florian Westphal Signed-off-by: Sasha Levin --- net/netfilter/nf_tables_api.c | 5 +++++ net/netfilter/nft_set_rbtree.c | 13 +++++++++++++ 2 files changed, 18 insertions(+) diff --git a/net/netfilter/nf_tables_api.c b/net/netfilter/nf_tables_api.c index df18dfd5a8271e..e3279179cd305b 100644 --- a/net/netfilter/nf_tables_api.c +++ b/net/netfilter/nf_tables_api.c @@ -7637,6 +7637,11 @@ static int nft_add_set_elem(struct nft_ctx *ctx, struct nft_set *set, * and an existing one. */ err = -EEXIST; + } else if (err == -ECANCELED) { + /* ECANCELED reports an existing nul-element in + * interval sets. + */ + err = 0; } goto err_element_clash; } diff --git a/net/netfilter/nft_set_rbtree.c b/net/netfilter/nft_set_rbtree.c index ca594161b8402e..eacb3acc2b9579 100644 --- a/net/netfilter/nft_set_rbtree.c +++ b/net/netfilter/nft_set_rbtree.c @@ -39,6 +39,13 @@ static bool nft_rbtree_interval_start(const struct nft_rbtree_elem *rbe) return !nft_rbtree_interval_end(rbe); } +static bool nft_rbtree_interval_null(const struct nft_set *set, + const struct nft_rbtree_elem *rbe) +{ + return (!memchr_inv(nft_set_ext_key(&rbe->ext), 0, set->klen) && + nft_rbtree_interval_end(rbe)); +} + static int nft_rbtree_cmp(const struct nft_set *set, const struct nft_rbtree_elem *e1, const struct nft_rbtree_elem *e2) @@ -431,6 +438,12 @@ static int __nft_rbtree_insert(const struct net *net, const struct nft_set *set, */ if (rbe_le && !nft_rbtree_cmp(set, new, rbe_le) && nft_rbtree_interval_end(rbe_le) == nft_rbtree_interval_end(new)) { + /* - ignore null interval, otherwise NLM_F_CREATE bogusly + * reports EEXIST. + */ + if (nft_rbtree_interval_null(set, new)) + return -ECANCELED; + *elem_priv = &rbe_le->priv; return -EEXIST; } From dad14d22dff1a191612acb98facceb303d0524a2 Mon Sep 17 00:00:00 2001 From: Pablo Neira Ayuso Date: Fri, 6 Feb 2026 13:33:44 +0100 Subject: [PATCH 3559/3902] netfilter: nft_set_rbtree: check for partial overlaps in anonymous sets [ Upstream commit 4780ec142cbb24b794129d3080eee5cac2943ffc ] Userspace provides an optimized representation in case intervals are adjacent, where the end element is omitted. The existing partial overlap detection logic skips anonymous set checks on start elements for this reason. However, it is possible to add intervals that overlap to this anonymous where two start elements with the same, eg. A-B, A-C where C < B. start end A B start end A C Restore the check on overlapping start elements to report an overlap. Fixes: c9e6978e2725 ("netfilter: nft_set_rbtree: Switch to node list walk for overlap detection") Signed-off-by: Pablo Neira Ayuso Signed-off-by: Florian Westphal Signed-off-by: Sasha Levin --- net/netfilter/nft_set_rbtree.c | 30 +++++++++++++++++++++++++----- 1 file changed, 25 insertions(+), 5 deletions(-) diff --git a/net/netfilter/nft_set_rbtree.c b/net/netfilter/nft_set_rbtree.c index eacb3acc2b9579..f2a1aa88601849 100644 --- a/net/netfilter/nft_set_rbtree.c +++ b/net/netfilter/nft_set_rbtree.c @@ -309,11 +309,23 @@ static bool nft_rbtree_update_first(const struct nft_set *set, return false; } +/* Only for anonymous sets which do not allow updates, all element are active. */ +static struct nft_rbtree_elem *nft_rbtree_prev_active(struct nft_rbtree_elem *rbe) +{ + struct rb_node *node; + + node = rb_prev(&rbe->node); + if (!node) + return NULL; + + return rb_entry(node, struct nft_rbtree_elem, node); +} + static int __nft_rbtree_insert(const struct net *net, const struct nft_set *set, struct nft_rbtree_elem *new, struct nft_elem_priv **elem_priv) { - struct nft_rbtree_elem *rbe, *rbe_le = NULL, *rbe_ge = NULL; + struct nft_rbtree_elem *rbe, *rbe_le = NULL, *rbe_ge = NULL, *rbe_prev; struct rb_node *node, *next, *parent, **p, *first = NULL; struct nft_rbtree *priv = nft_set_priv(set); u8 cur_genmask = nft_genmask_cur(net); @@ -451,11 +463,19 @@ static int __nft_rbtree_insert(const struct net *net, const struct nft_set *set, /* - new start element with existing closest, less or equal key value * being a start element: partial overlap, reported as -ENOTEMPTY. * Anonymous sets allow for two consecutive start element since they - * are constant, skip them to avoid bogus overlap reports. + * are constant, but validate that this new start element does not + * sit in between an existing start and end elements: partial overlap, + * reported as -ENOTEMPTY. */ - if (!nft_set_is_anonymous(set) && rbe_le && - nft_rbtree_interval_start(rbe_le) && nft_rbtree_interval_start(new)) - return -ENOTEMPTY; + if (rbe_le && + nft_rbtree_interval_start(rbe_le) && nft_rbtree_interval_start(new)) { + if (!nft_set_is_anonymous(set)) + return -ENOTEMPTY; + + rbe_prev = nft_rbtree_prev_active(rbe_le); + if (rbe_prev && nft_rbtree_interval_end(rbe_prev)) + return -ENOTEMPTY; + } /* - new end element with existing closest, less or equal key value * being a end element: partial overlap, reported as -ENOTEMPTY. From 36ed9b6e39611234ef85c16e3cfd1bb24b306c07 Mon Sep 17 00:00:00 2001 From: Pablo Neira Ayuso Date: Wed, 21 Jan 2026 01:08:45 +0100 Subject: [PATCH 3560/3902] netfilter: nft_set_rbtree: translate rbtree to array for binary search [ Upstream commit 7e43e0a1141deec651a60109dab3690854107298 ] The rbtree can temporarily store overlapping inactive elements during the transaction processing, leading to false negative lookups. To address this issue, this patch adds a .commit function that walks the the rbtree to build a array of intervals of ordered elements. This conversion compacts the two singleton elements that represent the start and the end of the interval into a single interval object for space efficient. Binary search is O(log n), similar to rbtree lookup time, therefore, performance number should be similar, and there is an implementation available under lib/bsearch.c and include/linux/bsearch.h that is used for this purpose. This slightly increases memory consumption for this new array that stores pointers to the start and the end of the interval. With this patch: # time nft -f 100k-intervals-set.nft real 0m4.218s user 0m3.544s sys 0m0.400s Without this patch: # time nft -f 100k-intervals-set.nft real 0m3.920s user 0m3.547s sys 0m0.276s With this patch, with IPv4 intervals: baseline rbtree (match on first field only): 15254954pps Without this patch: baseline rbtree (match on first field only): 10256119pps This provides a ~50% improvement in matching intervals from packet path. Signed-off-by: Pablo Neira Ayuso Signed-off-by: Florian Westphal Stable-dep-of: 782f2688128e ("netfilter: nft_set_rbtree: validate element belonging to interval") Signed-off-by: Sasha Levin --- net/netfilter/nft_set_rbtree.c | 341 +++++++++++++++++++++++++-------- 1 file changed, 257 insertions(+), 84 deletions(-) diff --git a/net/netfilter/nft_set_rbtree.c b/net/netfilter/nft_set_rbtree.c index f2a1aa88601849..04e696c87f4a0e 100644 --- a/net/netfilter/nft_set_rbtree.c +++ b/net/netfilter/nft_set_rbtree.c @@ -10,14 +10,29 @@ #include #include #include +#include #include #include #include #include +struct nft_array_interval { + struct nft_set_ext *from; + struct nft_set_ext *to; +}; + +struct nft_array { + u32 max_intervals; + u32 num_intervals; + struct nft_array_interval *intervals; + struct rcu_head rcu_head; +}; + struct nft_rbtree { struct rb_root root; rwlock_t lock; + struct nft_array __rcu *array; + struct nft_array *array_next; seqcount_rwlock_t count; unsigned long last_gc; }; @@ -54,90 +69,6 @@ static int nft_rbtree_cmp(const struct nft_set *set, set->klen); } -static bool nft_rbtree_elem_expired(const struct nft_rbtree_elem *rbe) -{ - return nft_set_elem_expired(&rbe->ext); -} - -static const struct nft_set_ext * -__nft_rbtree_lookup(const struct net *net, const struct nft_set *set, - const u32 *key, unsigned int seq) -{ - struct nft_rbtree *priv = nft_set_priv(set); - const struct nft_rbtree_elem *rbe, *interval = NULL; - u8 genmask = nft_genmask_cur(net); - const struct rb_node *parent; - int d; - - parent = rcu_dereference_raw(priv->root.rb_node); - while (parent != NULL) { - if (read_seqcount_retry(&priv->count, seq)) - return NULL; - - rbe = rb_entry(parent, struct nft_rbtree_elem, node); - - d = memcmp(nft_set_ext_key(&rbe->ext), key, set->klen); - if (d < 0) { - parent = rcu_dereference_raw(parent->rb_left); - if (interval && - !nft_rbtree_cmp(set, rbe, interval) && - nft_rbtree_interval_end(rbe) && - nft_rbtree_interval_start(interval)) - continue; - if (nft_set_elem_active(&rbe->ext, genmask) && - !nft_rbtree_elem_expired(rbe)) - interval = rbe; - } else if (d > 0) - parent = rcu_dereference_raw(parent->rb_right); - else { - if (!nft_set_elem_active(&rbe->ext, genmask)) { - parent = rcu_dereference_raw(parent->rb_left); - continue; - } - - if (nft_rbtree_elem_expired(rbe)) - return NULL; - - if (nft_rbtree_interval_end(rbe)) { - if (nft_set_is_anonymous(set)) - return NULL; - parent = rcu_dereference_raw(parent->rb_left); - interval = NULL; - continue; - } - - return &rbe->ext; - } - } - - if (set->flags & NFT_SET_INTERVAL && interval != NULL && - nft_rbtree_interval_start(interval)) - return &interval->ext; - - return NULL; -} - -INDIRECT_CALLABLE_SCOPE -const struct nft_set_ext * -nft_rbtree_lookup(const struct net *net, const struct nft_set *set, - const u32 *key) -{ - struct nft_rbtree *priv = nft_set_priv(set); - unsigned int seq = read_seqcount_begin(&priv->count); - const struct nft_set_ext *ext; - - ext = __nft_rbtree_lookup(net, set, key, seq); - if (ext || !read_seqcount_retry(&priv->count, seq)) - return ext; - - read_lock_bh(&priv->lock); - seq = read_seqcount_begin(&priv->count); - ext = __nft_rbtree_lookup(net, set, key, seq); - read_unlock_bh(&priv->lock); - - return ext; -} - static bool __nft_rbtree_get(const struct net *net, const struct nft_set *set, const u32 *key, struct nft_rbtree_elem **elem, unsigned int seq, unsigned int flags, u8 genmask) @@ -228,6 +159,60 @@ nft_rbtree_get(const struct net *net, const struct nft_set *set, return &rbe->priv; } +struct nft_array_lookup_ctx { + const u32 *key; + u32 klen; +}; + +static int nft_array_lookup_cmp(const void *pkey, const void *entry) +{ + const struct nft_array_interval *interval = entry; + const struct nft_array_lookup_ctx *ctx = pkey; + int a, b; + + if (!interval->from) + return 1; + + a = memcmp(ctx->key, nft_set_ext_key(interval->from), ctx->klen); + if (!interval->to) + b = -1; + else + b = memcmp(ctx->key, nft_set_ext_key(interval->to), ctx->klen); + + if (a >= 0 && b < 0) + return 0; + + if (a < 0) + return -1; + + return 1; +} + +INDIRECT_CALLABLE_SCOPE +const struct nft_set_ext * +nft_rbtree_lookup(const struct net *net, const struct nft_set *set, + const u32 *key) +{ + struct nft_rbtree *priv = nft_set_priv(set); + struct nft_array *array = rcu_dereference(priv->array); + const struct nft_array_interval *interval; + struct nft_array_lookup_ctx ctx = { + .key = key, + .klen = set->klen, + }; + + if (!array) + return NULL; + + interval = bsearch(&ctx, array->intervals, array->num_intervals, + sizeof(struct nft_array_interval), + nft_array_lookup_cmp); + if (!interval || nft_set_elem_expired(interval->from)) + return NULL; + + return interval->from; +} + static void nft_rbtree_gc_elem_remove(struct net *net, struct nft_set *set, struct nft_rbtree *priv, struct nft_rbtree_elem *rbe) @@ -514,6 +499,87 @@ static int __nft_rbtree_insert(const struct net *net, const struct nft_set *set, return 0; } +static int nft_array_intervals_alloc(struct nft_array *array, u32 max_intervals) +{ + struct nft_array_interval *intervals; + + intervals = kvcalloc(max_intervals, sizeof(struct nft_array_interval), + GFP_KERNEL_ACCOUNT); + if (!intervals) + return -ENOMEM; + + if (array->intervals) + kvfree(array->intervals); + + array->intervals = intervals; + array->max_intervals = max_intervals; + + return 0; +} + +static struct nft_array *nft_array_alloc(u32 max_intervals) +{ + struct nft_array *array; + + array = kzalloc(sizeof(*array), GFP_KERNEL_ACCOUNT); + if (!array) + return NULL; + + if (nft_array_intervals_alloc(array, max_intervals) < 0) { + kfree(array); + return NULL; + } + + return array; +} + +#define NFT_ARRAY_EXTRA_SIZE 10240 + +/* Similar to nft_rbtree_{u,k}size to hide details to userspace, but consider + * packed representation coming from userspace for anonymous sets too. + */ +static u32 nft_array_elems(const struct nft_set *set) +{ + u32 nelems = atomic_read(&set->nelems); + + /* Adjacent intervals are represented with a single start element in + * anonymous sets, use the current element counter as is. + */ + if (nft_set_is_anonymous(set)) + return nelems; + + /* Add extra room for never matching interval at the beginning and open + * interval at the end which only use a single element to represent it. + * The conversion to array will compact intervals, this allows reduce + * memory consumption. + */ + return (nelems / 2) + 2; +} + +static int nft_array_may_resize(const struct nft_set *set) +{ + u32 nelems = nft_array_elems(set), new_max_intervals; + struct nft_rbtree *priv = nft_set_priv(set); + struct nft_array *array; + + if (!priv->array_next) { + array = nft_array_alloc(nelems + NFT_ARRAY_EXTRA_SIZE); + if (!array) + return -ENOMEM; + + priv->array_next = array; + } + + if (nelems < priv->array_next->max_intervals) + return 0; + + new_max_intervals = priv->array_next->max_intervals + NFT_ARRAY_EXTRA_SIZE; + if (nft_array_intervals_alloc(priv->array_next, new_max_intervals) < 0) + return -ENOMEM; + + return 0; +} + static int nft_rbtree_insert(const struct net *net, const struct nft_set *set, const struct nft_set_elem *elem, struct nft_elem_priv **elem_priv) @@ -522,6 +588,9 @@ static int nft_rbtree_insert(const struct net *net, const struct nft_set *set, struct nft_rbtree *priv = nft_set_priv(set); int err; + if (nft_array_may_resize(set) < 0) + return -ENOMEM; + do { if (fatal_signal_pending(current)) return -EINTR; @@ -586,6 +655,9 @@ nft_rbtree_deactivate(const struct net *net, const struct nft_set *set, u64 tstamp = nft_net_tstamp(net); int d; + if (nft_array_may_resize(set) < 0) + return NULL; + while (parent != NULL) { rbe = rb_entry(parent, struct nft_rbtree_elem, node); @@ -648,6 +720,11 @@ static void nft_rbtree_walk(const struct nft_ctx *ctx, switch (iter->type) { case NFT_ITER_UPDATE: lockdep_assert_held(&nft_pernet(ctx->net)->commit_mutex); + + if (nft_array_may_resize(set) < 0) { + iter->err = -ENOMEM; + break; + } nft_rbtree_do_walk(ctx, set, iter); break; case NFT_ITER_READ: @@ -750,14 +827,24 @@ static int nft_rbtree_init(const struct nft_set *set, seqcount_rwlock_init(&priv->count, &priv->lock); priv->root = RB_ROOT; + priv->array = NULL; + priv->array_next = NULL; + return 0; } +static void __nft_array_free(struct nft_array *array) +{ + kvfree(array->intervals); + kfree(array); +} + static void nft_rbtree_destroy(const struct nft_ctx *ctx, const struct nft_set *set) { struct nft_rbtree *priv = nft_set_priv(set); struct nft_rbtree_elem *rbe; + struct nft_array *array; struct rb_node *node; while ((node = priv->root.rb_node) != NULL) { @@ -765,6 +852,12 @@ static void nft_rbtree_destroy(const struct nft_ctx *ctx, rbe = rb_entry(node, struct nft_rbtree_elem, node); nf_tables_set_elem_destroy(ctx, set, &rbe->priv); } + + array = rcu_dereference_protected(priv->array, true); + if (array) + __nft_array_free(array); + if (priv->array_next) + __nft_array_free(priv->array_next); } static bool nft_rbtree_estimate(const struct nft_set_desc *desc, u32 features, @@ -785,12 +878,91 @@ static bool nft_rbtree_estimate(const struct nft_set_desc *desc, u32 features, return true; } +static void nft_array_free_rcu(struct rcu_head *rcu_head) +{ + struct nft_array *array = container_of(rcu_head, struct nft_array, rcu_head); + + __nft_array_free(array); +} + static void nft_rbtree_commit(struct nft_set *set) { struct nft_rbtree *priv = nft_set_priv(set); + struct nft_rbtree_elem *rbe, *prev_rbe; + struct nft_array *old; + u32 num_intervals = 0; + struct rb_node *node; if (time_after_eq(jiffies, priv->last_gc + nft_set_gc_interval(set))) nft_rbtree_gc(set); + + /* No changes, skip, eg. elements updates only. */ + if (!priv->array_next) + return; + + /* Reverse walk to create an array from smaller to largest interval. */ + node = rb_last(&priv->root); + if (node) + prev_rbe = rb_entry(node, struct nft_rbtree_elem, node); + else + prev_rbe = NULL; + + while (prev_rbe) { + rbe = prev_rbe; + + if (nft_rbtree_interval_start(rbe)) + priv->array_next->intervals[num_intervals].from = &rbe->ext; + else if (nft_rbtree_interval_end(rbe)) + priv->array_next->intervals[num_intervals++].to = &rbe->ext; + + if (num_intervals >= priv->array_next->max_intervals) { + pr_warn_once("malformed interval set from userspace?"); + goto err_out; + } + + node = rb_prev(node); + if (!node) + break; + + prev_rbe = rb_entry(node, struct nft_rbtree_elem, node); + + /* For anonymous sets, when adjacent ranges are found, + * the end element is not added to the set to pack the set + * representation. Use next start element to complete this + * interval. + */ + if (nft_rbtree_interval_start(rbe) && + nft_rbtree_interval_start(prev_rbe) && + priv->array_next->intervals[num_intervals].from) + priv->array_next->intervals[num_intervals++].to = &prev_rbe->ext; + + if (num_intervals >= priv->array_next->max_intervals) { + pr_warn_once("malformed interval set from userspace?"); + goto err_out; + } + } + + if (priv->array_next->intervals[num_intervals].from) + num_intervals++; +err_out: + priv->array_next->num_intervals = num_intervals; + old = rcu_replace_pointer(priv->array, priv->array_next, true); + priv->array_next = NULL; + if (old) + call_rcu(&old->rcu_head, nft_array_free_rcu); +} + +static void nft_rbtree_abort(const struct nft_set *set) +{ + struct nft_rbtree *priv = nft_set_priv(set); + struct nft_array *array_next; + + if (!priv->array_next) + return; + + array_next = priv->array_next; + priv->array_next = NULL; + __nft_array_free(array_next); } static void nft_rbtree_gc_init(const struct nft_set *set) @@ -854,6 +1026,7 @@ const struct nft_set_type nft_set_rbtree_type = { .flush = nft_rbtree_flush, .activate = nft_rbtree_activate, .commit = nft_rbtree_commit, + .abort = nft_rbtree_abort, .gc_init = nft_rbtree_gc_init, .lookup = nft_rbtree_lookup, .walk = nft_rbtree_walk, From e4ea5be0b35fd2f772bb289e235e3836320957bd Mon Sep 17 00:00:00 2001 From: Pablo Neira Ayuso Date: Wed, 21 Jan 2026 01:08:46 +0100 Subject: [PATCH 3561/3902] netfilter: nft_set_rbtree: use binary search array in get command [ Upstream commit 2aa34191f06fc5af4f70241518a8554370d86054 ] Rework .get interface to use the binary search array, this needs a specific lookup function to match on end intervals (<=). Packet path lookup is slight different because match is on lesser value, not equal (ie. <). After this patch, seqcount can be removed in a follow up patch. Signed-off-by: Pablo Neira Ayuso Signed-off-by: Florian Westphal Stable-dep-of: 782f2688128e ("netfilter: nft_set_rbtree: validate element belonging to interval") Signed-off-by: Sasha Levin --- net/netfilter/nft_set_rbtree.c | 154 ++++++++++++++------------------- 1 file changed, 64 insertions(+), 90 deletions(-) diff --git a/net/netfilter/nft_set_rbtree.c b/net/netfilter/nft_set_rbtree.c index 04e696c87f4a0e..1b0502cc87301d 100644 --- a/net/netfilter/nft_set_rbtree.c +++ b/net/netfilter/nft_set_rbtree.c @@ -69,96 +69,6 @@ static int nft_rbtree_cmp(const struct nft_set *set, set->klen); } -static bool __nft_rbtree_get(const struct net *net, const struct nft_set *set, - const u32 *key, struct nft_rbtree_elem **elem, - unsigned int seq, unsigned int flags, u8 genmask) -{ - struct nft_rbtree_elem *rbe, *interval = NULL; - struct nft_rbtree *priv = nft_set_priv(set); - const struct rb_node *parent; - const void *this; - int d; - - parent = rcu_dereference_raw(priv->root.rb_node); - while (parent != NULL) { - if (read_seqcount_retry(&priv->count, seq)) - return false; - - rbe = rb_entry(parent, struct nft_rbtree_elem, node); - - this = nft_set_ext_key(&rbe->ext); - d = memcmp(this, key, set->klen); - if (d < 0) { - parent = rcu_dereference_raw(parent->rb_left); - if (!(flags & NFT_SET_ELEM_INTERVAL_END)) - interval = rbe; - } else if (d > 0) { - parent = rcu_dereference_raw(parent->rb_right); - if (flags & NFT_SET_ELEM_INTERVAL_END) - interval = rbe; - } else { - if (!nft_set_elem_active(&rbe->ext, genmask)) { - parent = rcu_dereference_raw(parent->rb_left); - continue; - } - - if (nft_set_elem_expired(&rbe->ext)) - return false; - - if (!nft_set_ext_exists(&rbe->ext, NFT_SET_EXT_FLAGS) || - (*nft_set_ext_flags(&rbe->ext) & NFT_SET_ELEM_INTERVAL_END) == - (flags & NFT_SET_ELEM_INTERVAL_END)) { - *elem = rbe; - return true; - } - - if (nft_rbtree_interval_end(rbe)) - interval = NULL; - - parent = rcu_dereference_raw(parent->rb_left); - } - } - - if (set->flags & NFT_SET_INTERVAL && interval != NULL && - nft_set_elem_active(&interval->ext, genmask) && - !nft_set_elem_expired(&interval->ext) && - ((!nft_rbtree_interval_end(interval) && - !(flags & NFT_SET_ELEM_INTERVAL_END)) || - (nft_rbtree_interval_end(interval) && - (flags & NFT_SET_ELEM_INTERVAL_END)))) { - *elem = interval; - return true; - } - - return false; -} - -static struct nft_elem_priv * -nft_rbtree_get(const struct net *net, const struct nft_set *set, - const struct nft_set_elem *elem, unsigned int flags) -{ - struct nft_rbtree *priv = nft_set_priv(set); - unsigned int seq = read_seqcount_begin(&priv->count); - struct nft_rbtree_elem *rbe = ERR_PTR(-ENOENT); - const u32 *key = (const u32 *)&elem->key.val; - u8 genmask = nft_genmask_cur(net); - bool ret; - - ret = __nft_rbtree_get(net, set, key, &rbe, seq, flags, genmask); - if (ret || !read_seqcount_retry(&priv->count, seq)) - return &rbe->priv; - - read_lock_bh(&priv->lock); - seq = read_seqcount_begin(&priv->count); - ret = __nft_rbtree_get(net, set, key, &rbe, seq, flags, genmask); - read_unlock_bh(&priv->lock); - - if (!ret) - return ERR_PTR(-ENOENT); - - return &rbe->priv; -} - struct nft_array_lookup_ctx { const u32 *key; u32 klen; @@ -213,6 +123,70 @@ nft_rbtree_lookup(const struct net *net, const struct nft_set *set, return interval->from; } +struct nft_array_get_ctx { + const u32 *key; + unsigned int flags; + u32 klen; +}; + +static int nft_array_get_cmp(const void *pkey, const void *entry) +{ + const struct nft_array_interval *interval = entry; + const struct nft_array_get_ctx *ctx = pkey; + int a, b; + + if (!interval->from) + return 1; + + a = memcmp(ctx->key, nft_set_ext_key(interval->from), ctx->klen); + if (!interval->to) + b = -1; + else + b = memcmp(ctx->key, nft_set_ext_key(interval->to), ctx->klen); + + if (a >= 0) { + if (ctx->flags & NFT_SET_ELEM_INTERVAL_END && b <= 0) + return 0; + else if (b < 0) + return 0; + } + + if (a < 0) + return -1; + + return 1; +} + +static struct nft_elem_priv * +nft_rbtree_get(const struct net *net, const struct nft_set *set, + const struct nft_set_elem *elem, unsigned int flags) +{ + struct nft_rbtree *priv = nft_set_priv(set); + struct nft_array *array = rcu_dereference(priv->array); + const struct nft_array_interval *interval; + struct nft_array_get_ctx ctx = { + .key = (const u32 *)&elem->key.val, + .flags = flags, + .klen = set->klen, + }; + struct nft_rbtree_elem *rbe; + + if (!array) + return ERR_PTR(-ENOENT); + + interval = bsearch(&ctx, array->intervals, array->num_intervals, + sizeof(struct nft_array_interval), nft_array_get_cmp); + if (!interval || nft_set_elem_expired(interval->from)) + return ERR_PTR(-ENOENT); + + if (flags & NFT_SET_ELEM_INTERVAL_END) + rbe = container_of(interval->to, struct nft_rbtree_elem, ext); + else + rbe = container_of(interval->from, struct nft_rbtree_elem, ext); + + return &rbe->priv; +} + static void nft_rbtree_gc_elem_remove(struct net *net, struct nft_set *set, struct nft_rbtree *priv, struct nft_rbtree_elem *rbe) From 89a3f393ab0d964d38a7c4d8d9cb018f33b20e42 Mon Sep 17 00:00:00 2001 From: Pablo Neira Ayuso Date: Wed, 21 Jan 2026 01:08:47 +0100 Subject: [PATCH 3562/3902] netfilter: nft_set_rbtree: remove seqcount_rwlock_t [ Upstream commit 5599fa810b503eafc2bd8cd15bd45f35fc8ff6b9 ] After the conversion to binary search array, this is not required anymore. Remove it. Signed-off-by: Pablo Neira Ayuso Signed-off-by: Florian Westphal Stable-dep-of: 782f2688128e ("netfilter: nft_set_rbtree: validate element belonging to interval") Signed-off-by: Sasha Levin --- net/netfilter/nft_set_rbtree.c | 6 ------ 1 file changed, 6 deletions(-) diff --git a/net/netfilter/nft_set_rbtree.c b/net/netfilter/nft_set_rbtree.c index 1b0502cc87301d..6470bc5d38749e 100644 --- a/net/netfilter/nft_set_rbtree.c +++ b/net/netfilter/nft_set_rbtree.c @@ -33,7 +33,6 @@ struct nft_rbtree { rwlock_t lock; struct nft_array __rcu *array; struct nft_array *array_next; - seqcount_rwlock_t count; unsigned long last_gc; }; @@ -572,9 +571,7 @@ static int nft_rbtree_insert(const struct net *net, const struct nft_set *set, cond_resched(); write_lock_bh(&priv->lock); - write_seqcount_begin(&priv->count); err = __nft_rbtree_insert(net, set, rbe, elem_priv); - write_seqcount_end(&priv->count); write_unlock_bh(&priv->lock); } while (err == -EAGAIN); @@ -584,9 +581,7 @@ static int nft_rbtree_insert(const struct net *net, const struct nft_set *set, static void nft_rbtree_erase(struct nft_rbtree *priv, struct nft_rbtree_elem *rbe) { write_lock_bh(&priv->lock); - write_seqcount_begin(&priv->count); rb_erase(&rbe->node, &priv->root); - write_seqcount_end(&priv->count); write_unlock_bh(&priv->lock); } @@ -798,7 +793,6 @@ static int nft_rbtree_init(const struct nft_set *set, BUILD_BUG_ON(offsetof(struct nft_rbtree_elem, priv) != 0); rwlock_init(&priv->lock); - seqcount_rwlock_init(&priv->count, &priv->lock); priv->root = RB_ROOT; priv->array = NULL; From 3c40cc71cec7a3a26826e87551151bafdfe14f31 Mon Sep 17 00:00:00 2001 From: Florian Westphal Date: Wed, 28 Jan 2026 15:06:21 +0100 Subject: [PATCH 3563/3902] netfilter: nft_set_rbtree: don't gc elements on insert [ Upstream commit 35f83a75529a829b0939708b003652f7b4f3df9a ] During insertion we can queue up expired elements for garbage collection. In case of later abort, the commit hook will never be called. Packet path and 'get' requests will find free'd elements in the binary search blob: nft_set_ext_key include/net/netfilter/nf_tables.h:800 [inline] nft_array_get_cmp+0x1f6/0x2a0 net/netfilter/nft_set_rbtree.c:133 __inline_bsearch include/linux/bsearch.h:15 [inline] bsearch+0x50/0xc0 lib/bsearch.c:33 nft_rbtree_get+0x16b/0x400 net/netfilter/nft_set_rbtree.c:169 nft_setelem_get net/netfilter/nf_tables_api.c:6495 [inline] nft_get_set_elem+0x420/0xaa0 net/netfilter/nf_tables_api.c:6543 nf_tables_getsetelem+0x448/0x5e0 net/netfilter/nf_tables_api.c:6632 nfnetlink_rcv_msg+0x8ae/0x12c0 net/netfilter/nfnetlink.c:290 Also, when we insert an element that triggers -EEXIST, and that insertion happens to also zap a timed-out entry, we end up with same issue: Neither commit nor abort hook is called. Fix this by removing gc api usage during insertion. The blamed commit also removes concurrency of the rbtree with the packet path, so we can now safely rb_erase() the element and move it to a new expired list that can be reaped in the commit hook before building the next blob iteration. This also avoids the need to rebuild the blob in the abort path: Expired elements seen during insertion attempts are kept around until a transaction passes. Reported-by: syzbot+d417922a3e7935517ef6@syzkaller.appspotmail.com Closes: https://syzkaller.appspot.com/bug?extid=d417922a3e7935517ef6 Fixes: 7e43e0a1141d ("netfilter: nft_set_rbtree: translate rbtree to array for binary search") Signed-off-by: Florian Westphal Stable-dep-of: 782f2688128e ("netfilter: nft_set_rbtree: validate element belonging to interval") Signed-off-by: Sasha Levin --- net/netfilter/nft_set_rbtree.c | 136 ++++++++++++++++----------------- 1 file changed, 68 insertions(+), 68 deletions(-) diff --git a/net/netfilter/nft_set_rbtree.c b/net/netfilter/nft_set_rbtree.c index 6470bc5d38749e..14b4256bb00d0e 100644 --- a/net/netfilter/nft_set_rbtree.c +++ b/net/netfilter/nft_set_rbtree.c @@ -34,11 +34,15 @@ struct nft_rbtree { struct nft_array __rcu *array; struct nft_array *array_next; unsigned long last_gc; + struct list_head expired; }; struct nft_rbtree_elem { struct nft_elem_priv priv; - struct rb_node node; + union { + struct rb_node node; + struct list_head list; + }; struct nft_set_ext ext; }; @@ -186,13 +190,16 @@ nft_rbtree_get(const struct net *net, const struct nft_set *set, return &rbe->priv; } -static void nft_rbtree_gc_elem_remove(struct net *net, struct nft_set *set, - struct nft_rbtree *priv, - struct nft_rbtree_elem *rbe) +static void nft_rbtree_gc_elem_move(struct net *net, struct nft_set *set, + struct nft_rbtree *priv, + struct nft_rbtree_elem *rbe) { lockdep_assert_held_write(&priv->lock); nft_setelem_data_deactivate(net, set, &rbe->priv); rb_erase(&rbe->node, &priv->root); + + /* collected later on in commit callback */ + list_add(&rbe->list, &priv->expired); } static const struct nft_rbtree_elem * @@ -203,11 +210,6 @@ nft_rbtree_gc_elem(const struct nft_set *__set, struct nft_rbtree *priv, struct rb_node *prev = rb_prev(&rbe->node); struct net *net = read_pnet(&set->net); struct nft_rbtree_elem *rbe_prev; - struct nft_trans_gc *gc; - - gc = nft_trans_gc_alloc(set, 0, GFP_ATOMIC); - if (!gc) - return ERR_PTR(-ENOMEM); /* search for end interval coming before this element. * end intervals don't carry a timeout extension, they @@ -225,28 +227,10 @@ nft_rbtree_gc_elem(const struct nft_set *__set, struct nft_rbtree *priv, rbe_prev = NULL; if (prev) { rbe_prev = rb_entry(prev, struct nft_rbtree_elem, node); - nft_rbtree_gc_elem_remove(net, set, priv, rbe_prev); - - /* There is always room in this trans gc for this element, - * memory allocation never actually happens, hence, the warning - * splat in such case. No need to set NFT_SET_ELEM_DEAD_BIT, - * this is synchronous gc which never fails. - */ - gc = nft_trans_gc_queue_sync(gc, GFP_ATOMIC); - if (WARN_ON_ONCE(!gc)) - return ERR_PTR(-ENOMEM); - - nft_trans_gc_elem_add(gc, rbe_prev); + nft_rbtree_gc_elem_move(net, set, priv, rbe_prev); } - nft_rbtree_gc_elem_remove(net, set, priv, rbe); - gc = nft_trans_gc_queue_sync(gc, GFP_ATOMIC); - if (WARN_ON_ONCE(!gc)) - return ERR_PTR(-ENOMEM); - - nft_trans_gc_elem_add(gc, rbe); - - nft_trans_gc_queue_sync_done(gc); + nft_rbtree_gc_elem_move(net, set, priv, rbe); return rbe_prev; } @@ -708,29 +692,13 @@ static void nft_rbtree_walk(const struct nft_ctx *ctx, } } -static void nft_rbtree_gc_remove(struct net *net, struct nft_set *set, - struct nft_rbtree *priv, - struct nft_rbtree_elem *rbe) -{ - nft_setelem_data_deactivate(net, set, &rbe->priv); - nft_rbtree_erase(priv, rbe); -} - -static void nft_rbtree_gc(struct nft_set *set) +static void nft_rbtree_gc_scan(struct nft_set *set) { struct nft_rbtree *priv = nft_set_priv(set); struct nft_rbtree_elem *rbe, *rbe_end = NULL; struct net *net = read_pnet(&set->net); u64 tstamp = nft_net_tstamp(net); struct rb_node *node, *next; - struct nft_trans_gc *gc; - - set = nft_set_container_of(priv); - net = read_pnet(&set->net); - - gc = nft_trans_gc_alloc(set, 0, GFP_KERNEL); - if (!gc) - return; for (node = rb_first(&priv->root); node ; node = next) { next = rb_next(node); @@ -748,34 +716,46 @@ static void nft_rbtree_gc(struct nft_set *set) if (!__nft_set_elem_expired(&rbe->ext, tstamp)) continue; - gc = nft_trans_gc_queue_sync(gc, GFP_KERNEL); - if (!gc) - goto try_later; - /* end element needs to be removed first, it has * no timeout extension. */ + write_lock_bh(&priv->lock); if (rbe_end) { - nft_rbtree_gc_remove(net, set, priv, rbe_end); - nft_trans_gc_elem_add(gc, rbe_end); + nft_rbtree_gc_elem_move(net, set, priv, rbe_end); rbe_end = NULL; } - gc = nft_trans_gc_queue_sync(gc, GFP_KERNEL); - if (!gc) - goto try_later; - - nft_rbtree_gc_remove(net, set, priv, rbe); - nft_trans_gc_elem_add(gc, rbe); + nft_rbtree_gc_elem_move(net, set, priv, rbe); + write_unlock_bh(&priv->lock); } -try_later: + priv->last_gc = jiffies; +} + +static void nft_rbtree_gc_queue(struct nft_set *set) +{ + struct nft_rbtree *priv = nft_set_priv(set); + struct nft_rbtree_elem *rbe, *rbe_end; + struct nft_trans_gc *gc; + + if (list_empty(&priv->expired)) + return; - if (gc) { - gc = nft_trans_gc_catchall_sync(gc); - nft_trans_gc_queue_sync_done(gc); - priv->last_gc = jiffies; + gc = nft_trans_gc_alloc(set, 0, GFP_KERNEL); + if (!gc) + return; + + list_for_each_entry_safe(rbe, rbe_end, &priv->expired, list) { + list_del(&rbe->list); + nft_trans_gc_elem_add(gc, rbe); + + gc = nft_trans_gc_queue_sync(gc, GFP_KERNEL); + if (!gc) + return; } + + gc = nft_trans_gc_catchall_sync(gc); + nft_trans_gc_queue_sync_done(gc); } static u64 nft_rbtree_privsize(const struct nlattr * const nla[], @@ -794,6 +774,7 @@ static int nft_rbtree_init(const struct nft_set *set, rwlock_init(&priv->lock); priv->root = RB_ROOT; + INIT_LIST_HEAD(&priv->expired); priv->array = NULL; priv->array_next = NULL; @@ -811,10 +792,15 @@ static void nft_rbtree_destroy(const struct nft_ctx *ctx, const struct nft_set *set) { struct nft_rbtree *priv = nft_set_priv(set); - struct nft_rbtree_elem *rbe; + struct nft_rbtree_elem *rbe, *next; struct nft_array *array; struct rb_node *node; + list_for_each_entry_safe(rbe, next, &priv->expired, list) { + list_del(&rbe->list); + nf_tables_set_elem_destroy(ctx, set, &rbe->priv); + } + while ((node = priv->root.rb_node) != NULL) { rb_erase(node, &priv->root); rbe = rb_entry(node, struct nft_rbtree_elem, node); @@ -861,13 +847,21 @@ static void nft_rbtree_commit(struct nft_set *set) u32 num_intervals = 0; struct rb_node *node; - if (time_after_eq(jiffies, priv->last_gc + nft_set_gc_interval(set))) - nft_rbtree_gc(set); - /* No changes, skip, eg. elements updates only. */ if (!priv->array_next) return; + /* GC can be performed if the binary search blob is going + * to be rebuilt. It has to be done in two phases: first + * scan tree and move all expired elements to the expired + * list. + * + * Then, after blob has been re-built and published to other + * CPUs, queue collected entries for freeing. + */ + if (time_after_eq(jiffies, priv->last_gc + nft_set_gc_interval(set))) + nft_rbtree_gc_scan(set); + /* Reverse walk to create an array from smaller to largest interval. */ node = rb_last(&priv->root); if (node) @@ -914,10 +908,16 @@ static void nft_rbtree_commit(struct nft_set *set) num_intervals++; err_out: priv->array_next->num_intervals = num_intervals; - old = rcu_replace_pointer(priv->array, priv->array_next, true); + old = rcu_replace_pointer(priv->array, priv->array_next, + lockdep_is_held(&nft_pernet(read_pnet(&set->net))->commit_mutex)); priv->array_next = NULL; if (old) call_rcu(&old->rcu_head, nft_array_free_rcu); + + /* New blob is public, queue collected entries for freeing. + * call_rcu ensures elements stay around until readers are done. + */ + nft_rbtree_gc_queue(set); } static void nft_rbtree_abort(const struct nft_set *set) From 90cbb8561aee03dbbe866ee3c6df7031b0299500 Mon Sep 17 00:00:00 2001 From: Pablo Neira Ayuso Date: Fri, 6 Feb 2026 13:33:45 +0100 Subject: [PATCH 3564/3902] netfilter: nft_set_rbtree: validate element belonging to interval [ Upstream commit 782f2688128eca6d05a48be1c247f68d86afc168 ] The existing partial overlap detection does not check if the elements belong to the interval, eg. add element inet x y { 1.1.1.1-2.2.2.2, 4.4.4.4-5.5.5.5 } add element inet x y { 1.1.1.1-5.5.5.5 } => this should fail: ENOENT Similar situation occurs with deletions: add element inet x y { 1.1.1.1-2.2.2.2, 4.4.4.4-5.5.5.5} delete element inet x y { 1.1.1.1-5.5.5.5 } => this should fail: ENOENT This currently works via mitigation by nft in userspace, which is performing the overlap detection before sending the elements to the kernel. This requires a previous netlink dump of the set content which slows down incremental updates on interval sets, because a netlink set content dump is needed. This patch extends the existing overlap detection to track the most recent start element that already exists. The pointer to the existing start element is stored as a cookie (no pointer dereference is ever possible). If the end element is added and it already exists, then check that the existing end element is adjacent to the already existing start element. Similar logic applies to element deactivation. This patch also annotates the timestamp to identify if start cookie comes from an older batch, in such case reset it. Otherwise, a failing create element command leaves the start cookie in place, resulting in bogus error reporting. There is still a few more corner cases of overlap detection related to the open interval that are addressed in follow up patches. This is address an early design mistake where an interval is expressed as two elements, using the NFT_SET_ELEM_INTERVAL_END flag, instead of the more recent NFTA_SET_ELEM_KEY_END attribute that pipapo already uses. Fixes: 7c84d41416d8 ("netfilter: nft_set_rbtree: Detect partial overlaps on insertion") Signed-off-by: Pablo Neira Ayuso Signed-off-by: Florian Westphal Signed-off-by: Sasha Levin --- net/netfilter/nft_set_rbtree.c | 147 ++++++++++++++++++++++++++++++++- 1 file changed, 143 insertions(+), 4 deletions(-) diff --git a/net/netfilter/nft_set_rbtree.c b/net/netfilter/nft_set_rbtree.c index 14b4256bb00d0e..a4fb5b517d9de5 100644 --- a/net/netfilter/nft_set_rbtree.c +++ b/net/netfilter/nft_set_rbtree.c @@ -33,8 +33,10 @@ struct nft_rbtree { rwlock_t lock; struct nft_array __rcu *array; struct nft_array *array_next; + unsigned long start_rbe_cookie; unsigned long last_gc; struct list_head expired; + u64 last_tstamp; }; struct nft_rbtree_elem { @@ -263,16 +265,85 @@ static struct nft_rbtree_elem *nft_rbtree_prev_active(struct nft_rbtree_elem *rb return rb_entry(node, struct nft_rbtree_elem, node); } +static struct nft_rbtree_elem * +__nft_rbtree_next_active(struct rb_node *node, u8 genmask) +{ + struct nft_rbtree_elem *next_rbe; + + while (node) { + next_rbe = rb_entry(node, struct nft_rbtree_elem, node); + if (!nft_set_elem_active(&next_rbe->ext, genmask)) { + node = rb_next(node); + continue; + } + + return next_rbe; + } + + return NULL; +} + +static struct nft_rbtree_elem * +nft_rbtree_next_active(struct nft_rbtree_elem *rbe, u8 genmask) +{ + return __nft_rbtree_next_active(rb_next(&rbe->node), genmask); +} + +static void nft_rbtree_maybe_reset_start_cookie(struct nft_rbtree *priv, + u64 tstamp) +{ + if (priv->last_tstamp != tstamp) { + priv->start_rbe_cookie = 0; + priv->last_tstamp = tstamp; + } +} + +static void nft_rbtree_set_start_cookie(struct nft_rbtree *priv, + const struct nft_rbtree_elem *rbe) +{ + priv->start_rbe_cookie = (unsigned long)rbe; +} + +static bool nft_rbtree_cmp_start_cookie(struct nft_rbtree *priv, + const struct nft_rbtree_elem *rbe) +{ + return priv->start_rbe_cookie == (unsigned long)rbe; +} + +static bool nft_rbtree_insert_same_interval(const struct net *net, + struct nft_rbtree *priv, + struct nft_rbtree_elem *rbe) +{ + u8 genmask = nft_genmask_next(net); + struct nft_rbtree_elem *next_rbe; + + if (!priv->start_rbe_cookie) + return true; + + next_rbe = nft_rbtree_next_active(rbe, genmask); + if (next_rbe) { + /* Closest start element differs from last element added. */ + if (nft_rbtree_interval_start(next_rbe) && + nft_rbtree_cmp_start_cookie(priv, next_rbe)) { + priv->start_rbe_cookie = 0; + return true; + } + } + + priv->start_rbe_cookie = 0; + + return false; +} + static int __nft_rbtree_insert(const struct net *net, const struct nft_set *set, struct nft_rbtree_elem *new, - struct nft_elem_priv **elem_priv) + struct nft_elem_priv **elem_priv, u64 tstamp) { struct nft_rbtree_elem *rbe, *rbe_le = NULL, *rbe_ge = NULL, *rbe_prev; struct rb_node *node, *next, *parent, **p, *first = NULL; struct nft_rbtree *priv = nft_set_priv(set); u8 cur_genmask = nft_genmask_cur(net); u8 genmask = nft_genmask_next(net); - u64 tstamp = nft_net_tstamp(net); int d; /* Descend the tree to search for an existing element greater than the @@ -378,12 +449,18 @@ static int __nft_rbtree_insert(const struct net *net, const struct nft_set *set, } } + if (nft_rbtree_interval_null(set, new)) + priv->start_rbe_cookie = 0; + else if (nft_rbtree_interval_start(new) && priv->start_rbe_cookie) + priv->start_rbe_cookie = 0; + /* - new start element matching existing start element: full overlap * reported as -EEXIST, cleared by caller if NLM_F_EXCL is not given. */ if (rbe_ge && !nft_rbtree_cmp(set, new, rbe_ge) && nft_rbtree_interval_start(rbe_ge) == nft_rbtree_interval_start(new)) { *elem_priv = &rbe_ge->priv; + nft_rbtree_set_start_cookie(priv, rbe_ge); return -EEXIST; } @@ -399,6 +476,11 @@ static int __nft_rbtree_insert(const struct net *net, const struct nft_set *set, return -ECANCELED; *elem_priv = &rbe_le->priv; + + /* - start and end element belong to the same interval. */ + if (!nft_rbtree_insert_same_interval(net, priv, rbe_le)) + return -ENOTEMPTY; + return -EEXIST; } @@ -543,8 +625,11 @@ static int nft_rbtree_insert(const struct net *net, const struct nft_set *set, { struct nft_rbtree_elem *rbe = nft_elem_priv_cast(elem->priv); struct nft_rbtree *priv = nft_set_priv(set); + u64 tstamp = nft_net_tstamp(net); int err; + nft_rbtree_maybe_reset_start_cookie(priv, tstamp); + if (nft_array_may_resize(set) < 0) return -ENOMEM; @@ -555,7 +640,7 @@ static int nft_rbtree_insert(const struct net *net, const struct nft_set *set, cond_resched(); write_lock_bh(&priv->lock); - err = __nft_rbtree_insert(net, set, rbe, elem_priv); + err = __nft_rbtree_insert(net, set, rbe, elem_priv, tstamp); write_unlock_bh(&priv->lock); } while (err == -EAGAIN); @@ -588,6 +673,48 @@ static void nft_rbtree_activate(const struct net *net, nft_clear(net, &rbe->ext); } +static struct nft_rbtree_elem * +nft_rbtree_next_inactive(struct nft_rbtree_elem *rbe, u8 genmask) +{ + struct nft_rbtree_elem *next_rbe; + struct rb_node *node; + + node = rb_next(&rbe->node); + if (node) { + next_rbe = rb_entry(node, struct nft_rbtree_elem, node); + if (nft_rbtree_interval_start(next_rbe) && + !nft_set_elem_active(&next_rbe->ext, genmask)) + return next_rbe; + } + + return NULL; +} + +static bool nft_rbtree_deactivate_same_interval(const struct net *net, + struct nft_rbtree *priv, + struct nft_rbtree_elem *rbe) +{ + u8 genmask = nft_genmask_next(net); + struct nft_rbtree_elem *next_rbe; + + if (!priv->start_rbe_cookie) + return true; + + next_rbe = nft_rbtree_next_inactive(rbe, genmask); + if (next_rbe) { + /* Closest start element differs from last element added. */ + if (nft_rbtree_interval_start(next_rbe) && + nft_rbtree_cmp_start_cookie(priv, next_rbe)) { + priv->start_rbe_cookie = 0; + return true; + } + } + + priv->start_rbe_cookie = 0; + + return false; +} + static void nft_rbtree_flush(const struct net *net, const struct nft_set *set, struct nft_elem_priv *elem_priv) @@ -602,12 +729,18 @@ nft_rbtree_deactivate(const struct net *net, const struct nft_set *set, const struct nft_set_elem *elem) { struct nft_rbtree_elem *rbe, *this = nft_elem_priv_cast(elem->priv); - const struct nft_rbtree *priv = nft_set_priv(set); + struct nft_rbtree *priv = nft_set_priv(set); const struct rb_node *parent = priv->root.rb_node; u8 genmask = nft_genmask_next(net); u64 tstamp = nft_net_tstamp(net); int d; + nft_rbtree_maybe_reset_start_cookie(priv, tstamp); + + if (nft_rbtree_interval_start(this) || + nft_rbtree_interval_null(set, this)) + priv->start_rbe_cookie = 0; + if (nft_array_may_resize(set) < 0) return NULL; @@ -635,6 +768,12 @@ nft_rbtree_deactivate(const struct net *net, const struct nft_set *set, parent = parent->rb_left; continue; } + + if (nft_rbtree_interval_start(rbe)) + nft_rbtree_set_start_cookie(priv, rbe); + else if (!nft_rbtree_deactivate_same_interval(net, priv, rbe)) + return NULL; + nft_rbtree_flush(net, set, &rbe->priv); return &rbe->priv; } From 12b1681793e9b7552495290785a3570c539f409d Mon Sep 17 00:00:00 2001 From: Pablo Neira Ayuso Date: Fri, 6 Feb 2026 13:33:46 +0100 Subject: [PATCH 3565/3902] netfilter: nft_set_rbtree: validate open interval overlap [ Upstream commit 648946966a08e4cb1a71619e3d1b12bd7642de7b ] Open intervals do not have an end element, in particular an open interval at the end of the set is hard to validate because of it is lacking the end element, and interval validation relies on such end element to perform the checks. This patch adds a new flag field to struct nft_set_elem, this is not an issue because this is a temporary object that is allocated in the stack from the insert/deactivate path. This flag field is used to specify that this is the last element in this add/delete command. The last flag is used, in combination with the start element cookie, to check if there is a partial overlap, eg. Already exists: 255.255.255.0-255.255.255.254 Add interval: 255.255.255.0-255.255.255.255 ~~~~~~~~~~~~~ start element overlap Basically, the idea is to check for an existing end element in the set if there is an overlap with an existing start element. However, the last open interval can come in any position in the add command, the corner case can get a bit more complicated: Already exists: 255.255.255.0-255.255.255.254 Add intervals: 255.255.255.0-255.255.255.255,255.255.255.0-255.255.255.254 ~~~~~~~~~~~~~ start element overlap To catch this overlap, annotate that the new start element is a possible overlap, then report the overlap if the next element is another start element that confirms that previous element in an open interval at the end of the set. For deletions, do not update the start cookie when deleting an open interval, otherwise this can trigger spurious EEXIST when adding new elements. Unfortunately, there is no NFT_SET_ELEM_INTERVAL_OPEN flag which would make easier to detect open interval overlaps. Fixes: 7c84d41416d8 ("netfilter: nft_set_rbtree: Detect partial overlaps on insertion") Signed-off-by: Pablo Neira Ayuso Signed-off-by: Florian Westphal Signed-off-by: Sasha Levin --- include/net/netfilter/nf_tables.h | 4 ++ net/netfilter/nf_tables_api.c | 21 +++++++-- net/netfilter/nft_set_rbtree.c | 71 ++++++++++++++++++++++++++----- 3 files changed, 82 insertions(+), 14 deletions(-) diff --git a/include/net/netfilter/nf_tables.h b/include/net/netfilter/nf_tables.h index 0e266c2d0e7f07..7eac73f9b4ce34 100644 --- a/include/net/netfilter/nf_tables.h +++ b/include/net/netfilter/nf_tables.h @@ -278,6 +278,8 @@ struct nft_userdata { unsigned char data[]; }; +#define NFT_SET_ELEM_INTERNAL_LAST 0x1 + /* placeholder structure for opaque set element backend representation. */ struct nft_elem_priv { }; @@ -287,6 +289,7 @@ struct nft_elem_priv { }; * @key: element key * @key_end: closing element key * @data: element data + * @flags: flags * @priv: element private data and extensions */ struct nft_set_elem { @@ -302,6 +305,7 @@ struct nft_set_elem { u32 buf[NFT_DATA_VALUE_MAXLEN / sizeof(u32)]; struct nft_data val; } data; + u32 flags; struct nft_elem_priv *priv; }; diff --git a/net/netfilter/nf_tables_api.c b/net/netfilter/nf_tables_api.c index e3279179cd305b..9051f2c3595a20 100644 --- a/net/netfilter/nf_tables_api.c +++ b/net/netfilter/nf_tables_api.c @@ -7271,7 +7271,8 @@ static u32 nft_set_maxsize(const struct nft_set *set) } static int nft_add_set_elem(struct nft_ctx *ctx, struct nft_set *set, - const struct nlattr *attr, u32 nlmsg_flags) + const struct nlattr *attr, u32 nlmsg_flags, + bool last) { struct nft_expr *expr_array[NFT_SET_EXPR_MAX] = {}; struct nlattr *nla[NFTA_SET_ELEM_MAX + 1]; @@ -7557,6 +7558,11 @@ static int nft_add_set_elem(struct nft_ctx *ctx, struct nft_set *set, if (flags) *nft_set_ext_flags(ext) = flags; + if (last) + elem.flags = NFT_SET_ELEM_INTERNAL_LAST; + else + elem.flags = 0; + if (obj) *nft_set_ext_obj(ext) = obj; @@ -7720,7 +7726,8 @@ static int nf_tables_newsetelem(struct sk_buff *skb, nft_ctx_init(&ctx, net, skb, info->nlh, family, table, NULL, nla); nla_for_each_nested(attr, nla[NFTA_SET_ELEM_LIST_ELEMENTS], rem) { - err = nft_add_set_elem(&ctx, set, attr, info->nlh->nlmsg_flags); + err = nft_add_set_elem(&ctx, set, attr, info->nlh->nlmsg_flags, + nla_is_last(attr, rem)); if (err < 0) { NL_SET_BAD_ATTR(extack, attr); return err; @@ -7843,7 +7850,7 @@ static void nft_trans_elems_destroy_abort(const struct nft_ctx *ctx, } static int nft_del_setelem(struct nft_ctx *ctx, struct nft_set *set, - const struct nlattr *attr) + const struct nlattr *attr, bool last) { struct nlattr *nla[NFTA_SET_ELEM_MAX + 1]; struct nft_set_ext_tmpl tmpl; @@ -7911,6 +7918,11 @@ static int nft_del_setelem(struct nft_ctx *ctx, struct nft_set *set, if (flags) *nft_set_ext_flags(ext) = flags; + if (last) + elem.flags = NFT_SET_ELEM_INTERNAL_LAST; + else + elem.flags = 0; + trans = nft_trans_elem_alloc(ctx, NFT_MSG_DELSETELEM, set); if (trans == NULL) goto fail_trans; @@ -8058,7 +8070,8 @@ static int nf_tables_delsetelem(struct sk_buff *skb, return nft_set_flush(&ctx, set, genmask); nla_for_each_nested(attr, nla[NFTA_SET_ELEM_LIST_ELEMENTS], rem) { - err = nft_del_setelem(&ctx, set, attr); + err = nft_del_setelem(&ctx, set, attr, + nla_is_last(attr, rem)); if (err == -ENOENT && NFNL_MSG_TYPE(info->nlh->nlmsg_type) == NFT_MSG_DESTROYSETELEM) continue; diff --git a/net/netfilter/nft_set_rbtree.c b/net/netfilter/nft_set_rbtree.c index a4fb5b517d9de5..644d4b9167057c 100644 --- a/net/netfilter/nft_set_rbtree.c +++ b/net/netfilter/nft_set_rbtree.c @@ -304,10 +304,19 @@ static void nft_rbtree_set_start_cookie(struct nft_rbtree *priv, priv->start_rbe_cookie = (unsigned long)rbe; } +static void nft_rbtree_set_start_cookie_open(struct nft_rbtree *priv, + const struct nft_rbtree_elem *rbe, + unsigned long open_interval) +{ + priv->start_rbe_cookie = (unsigned long)rbe | open_interval; +} + +#define NFT_RBTREE_OPEN_INTERVAL 1UL + static bool nft_rbtree_cmp_start_cookie(struct nft_rbtree *priv, const struct nft_rbtree_elem *rbe) { - return priv->start_rbe_cookie == (unsigned long)rbe; + return (priv->start_rbe_cookie & ~NFT_RBTREE_OPEN_INTERVAL) == (unsigned long)rbe; } static bool nft_rbtree_insert_same_interval(const struct net *net, @@ -337,13 +346,14 @@ static bool nft_rbtree_insert_same_interval(const struct net *net, static int __nft_rbtree_insert(const struct net *net, const struct nft_set *set, struct nft_rbtree_elem *new, - struct nft_elem_priv **elem_priv, u64 tstamp) + struct nft_elem_priv **elem_priv, u64 tstamp, bool last) { struct nft_rbtree_elem *rbe, *rbe_le = NULL, *rbe_ge = NULL, *rbe_prev; struct rb_node *node, *next, *parent, **p, *first = NULL; struct nft_rbtree *priv = nft_set_priv(set); u8 cur_genmask = nft_genmask_cur(net); u8 genmask = nft_genmask_next(net); + unsigned long open_interval = 0; int d; /* Descend the tree to search for an existing element greater than the @@ -449,10 +459,18 @@ static int __nft_rbtree_insert(const struct net *net, const struct nft_set *set, } } - if (nft_rbtree_interval_null(set, new)) - priv->start_rbe_cookie = 0; - else if (nft_rbtree_interval_start(new) && priv->start_rbe_cookie) + if (nft_rbtree_interval_null(set, new)) { priv->start_rbe_cookie = 0; + } else if (nft_rbtree_interval_start(new) && priv->start_rbe_cookie) { + if (nft_set_is_anonymous(set)) { + priv->start_rbe_cookie = 0; + } else if (priv->start_rbe_cookie & NFT_RBTREE_OPEN_INTERVAL) { + /* Previous element is an open interval that partially + * overlaps with an existing non-open interval. + */ + return -ENOTEMPTY; + } + } /* - new start element matching existing start element: full overlap * reported as -EEXIST, cleared by caller if NLM_F_EXCL is not given. @@ -460,7 +478,27 @@ static int __nft_rbtree_insert(const struct net *net, const struct nft_set *set, if (rbe_ge && !nft_rbtree_cmp(set, new, rbe_ge) && nft_rbtree_interval_start(rbe_ge) == nft_rbtree_interval_start(new)) { *elem_priv = &rbe_ge->priv; - nft_rbtree_set_start_cookie(priv, rbe_ge); + + /* - Corner case: new start element of open interval (which + * comes as last element in the batch) overlaps the start of + * an existing interval with an end element: partial overlap. + */ + node = rb_first(&priv->root); + rbe = __nft_rbtree_next_active(node, genmask); + if (rbe && nft_rbtree_interval_end(rbe)) { + rbe = nft_rbtree_next_active(rbe, genmask); + if (rbe && + nft_rbtree_interval_start(rbe) && + !nft_rbtree_cmp(set, new, rbe)) { + if (last) + return -ENOTEMPTY; + + /* Maybe open interval? */ + open_interval = NFT_RBTREE_OPEN_INTERVAL; + } + } + nft_rbtree_set_start_cookie_open(priv, rbe_ge, open_interval); + return -EEXIST; } @@ -515,6 +553,12 @@ static int __nft_rbtree_insert(const struct net *net, const struct nft_set *set, nft_rbtree_interval_end(rbe_ge) && nft_rbtree_interval_end(new)) return -ENOTEMPTY; + /* - start element overlaps an open interval but end element is new: + * partial overlap, reported as -ENOEMPTY. + */ + if (!rbe_ge && priv->start_rbe_cookie && nft_rbtree_interval_end(new)) + return -ENOTEMPTY; + /* Accepted element: pick insertion point depending on key value */ parent = NULL; p = &priv->root.rb_node; @@ -624,6 +668,7 @@ static int nft_rbtree_insert(const struct net *net, const struct nft_set *set, struct nft_elem_priv **elem_priv) { struct nft_rbtree_elem *rbe = nft_elem_priv_cast(elem->priv); + bool last = !!(elem->flags & NFT_SET_ELEM_INTERNAL_LAST); struct nft_rbtree *priv = nft_set_priv(set); u64 tstamp = nft_net_tstamp(net); int err; @@ -640,8 +685,12 @@ static int nft_rbtree_insert(const struct net *net, const struct nft_set *set, cond_resched(); write_lock_bh(&priv->lock); - err = __nft_rbtree_insert(net, set, rbe, elem_priv, tstamp); + err = __nft_rbtree_insert(net, set, rbe, elem_priv, tstamp, last); write_unlock_bh(&priv->lock); + + if (nft_rbtree_interval_end(rbe)) + priv->start_rbe_cookie = 0; + } while (err == -EAGAIN); return err; @@ -729,6 +778,7 @@ nft_rbtree_deactivate(const struct net *net, const struct nft_set *set, const struct nft_set_elem *elem) { struct nft_rbtree_elem *rbe, *this = nft_elem_priv_cast(elem->priv); + bool last = !!(elem->flags & NFT_SET_ELEM_INTERNAL_LAST); struct nft_rbtree *priv = nft_set_priv(set); const struct rb_node *parent = priv->root.rb_node; u8 genmask = nft_genmask_next(net); @@ -769,9 +819,10 @@ nft_rbtree_deactivate(const struct net *net, const struct nft_set *set, continue; } - if (nft_rbtree_interval_start(rbe)) - nft_rbtree_set_start_cookie(priv, rbe); - else if (!nft_rbtree_deactivate_same_interval(net, priv, rbe)) + if (nft_rbtree_interval_start(rbe)) { + if (!last) + nft_rbtree_set_start_cookie(priv, rbe); + } else if (!nft_rbtree_deactivate_same_interval(net, priv, rbe)) return NULL; nft_rbtree_flush(net, set, &rbe->priv); From 782368c13f5a331e4a7261261061b79cb41ffafe Mon Sep 17 00:00:00 2001 From: Nicolas Cavallari Date: Mon, 19 Jan 2026 17:08:33 +0100 Subject: [PATCH 3566/3902] PCI: Add ACS quirk for Pericom PI7C9X2G404 switches [12d8:b404] [ Upstream commit 5907a90551e9f7968781f3a6ab8684458959beb3 ] 12d8:b404 is apparently another PCI ID for Pericom PI7C9X2G404 (as identified by the chip silkscreen and lspci). It is also affected by the PI7C9X2G errata (e.g. a network card attached to it fails under load when P2P Redirect Request is enabled), so apply the same quirk to this PCI ID too. PCI bridge [0604]: Pericom Semiconductor PI7C9X2G404 EV/SV PCIe2 4-Port/4-Lane Packet Switch [12d8:b404] (rev 01) Fixes: acd61ffb2f16 ("PCI: Add ACS quirk for Pericom PI7C9X2G switches") Closes: https://lore.kernel.org/all/a1d926f0-4cb5-4877-a4df-617902648d80@green-communications.fr/ Signed-off-by: Nicolas Cavallari Signed-off-by: Bjorn Helgaas Link: https://patch.msgid.link/20260119160915.26456-1-nicolas.cavallari@green-communications.fr Signed-off-by: Sasha Levin --- drivers/pci/quirks.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/drivers/pci/quirks.c b/drivers/pci/quirks.c index c7e733beaab038..9e073321b2dd29 100644 --- a/drivers/pci/quirks.c +++ b/drivers/pci/quirks.c @@ -6189,6 +6189,10 @@ DECLARE_PCI_FIXUP_ENABLE(PCI_VENDOR_ID_PERICOM, 0x2303, pci_fixup_pericom_acs_store_forward); DECLARE_PCI_FIXUP_RESUME(PCI_VENDOR_ID_PERICOM, 0x2303, pci_fixup_pericom_acs_store_forward); +DECLARE_PCI_FIXUP_ENABLE(PCI_VENDOR_ID_PERICOM, 0xb404, + pci_fixup_pericom_acs_store_forward); +DECLARE_PCI_FIXUP_RESUME(PCI_VENDOR_ID_PERICOM, 0xb404, + pci_fixup_pericom_acs_store_forward); static void nvidia_ion_ahci_fixup(struct pci_dev *pdev) { From f399e8af187a86a7feb16de879474e854bb6bc89 Mon Sep 17 00:00:00 2001 From: Ivan Vecera Date: Wed, 29 Oct 2025 16:32:06 +0100 Subject: [PATCH 3567/3902] dpll: add phase-adjust-gran pin attribute [ Upstream commit 30176bf7c871681df506f3165ffe76ec462db991 ] Phase-adjust values are currently limited by a min-max range. Some hardware requires, for certain pin types, that values be multiples of a specific granularity, as in the zl3073x driver. Add a `phase-adjust-gran` pin attribute and an appropriate field in dpll_pin_properties. If set by the driver, use its value to validate user-provided phase-adjust values. Reviewed-by: Michal Schmidt Reviewed-by: Petr Oros Tested-by: Prathosh Satish Signed-off-by: Ivan Vecera Reviewed-by: Jiri Pirko Reviewed-by: Arkadiusz Kubalewski Link: https://patch.msgid.link/20251029153207.178448-2-ivecera@redhat.com Signed-off-by: Jakub Kicinski Stable-dep-of: 5d41f95f5d0b ("dpll: zl3073x: Fix output pin phase adjustment sign") Signed-off-by: Sasha Levin --- Documentation/driver-api/dpll.rst | 36 +++++++++++++++------------ Documentation/netlink/specs/dpll.yaml | 7 ++++++ drivers/dpll/dpll_netlink.c | 12 ++++++++- include/linux/dpll.h | 1 + include/uapi/linux/dpll.h | 1 + 5 files changed, 40 insertions(+), 17 deletions(-) diff --git a/Documentation/driver-api/dpll.rst b/Documentation/driver-api/dpll.rst index be1fc643b645e3..83118c728ed90c 100644 --- a/Documentation/driver-api/dpll.rst +++ b/Documentation/driver-api/dpll.rst @@ -198,26 +198,28 @@ be requested with the same attribute with ``DPLL_CMD_DEVICE_SET`` command. ================================== ====================================== Device may also provide ability to adjust a signal phase on a pin. -If pin phase adjustment is supported, minimal and maximal values that pin -handle shall be provide to the user on ``DPLL_CMD_PIN_GET`` respond -with ``DPLL_A_PIN_PHASE_ADJUST_MIN`` and ``DPLL_A_PIN_PHASE_ADJUST_MAX`` +If pin phase adjustment is supported, minimal and maximal values and +granularity that pin handle shall be provided to the user on +``DPLL_CMD_PIN_GET`` respond with ``DPLL_A_PIN_PHASE_ADJUST_MIN``, +``DPLL_A_PIN_PHASE_ADJUST_MAX`` and ``DPLL_A_PIN_PHASE_ADJUST_GRAN`` attributes. Configured phase adjust value is provided with ``DPLL_A_PIN_PHASE_ADJUST`` attribute of a pin, and value change can be requested with the same attribute with ``DPLL_CMD_PIN_SET`` command. - =============================== ====================================== - ``DPLL_A_PIN_ID`` configured pin id - ``DPLL_A_PIN_PHASE_ADJUST_MIN`` attr minimum value of phase adjustment - ``DPLL_A_PIN_PHASE_ADJUST_MAX`` attr maximum value of phase adjustment - ``DPLL_A_PIN_PHASE_ADJUST`` attr configured value of phase - adjustment on parent dpll device - ``DPLL_A_PIN_PARENT_DEVICE`` nested attribute for requesting - configuration on given parent dpll - device - ``DPLL_A_PIN_PARENT_ID`` parent dpll device id - ``DPLL_A_PIN_PHASE_OFFSET`` attr measured phase difference - between a pin and parent dpll device - =============================== ====================================== + ================================ ========================================== + ``DPLL_A_PIN_ID`` configured pin id + ``DPLL_A_PIN_PHASE_ADJUST_GRAN`` attr granularity of phase adjustment value + ``DPLL_A_PIN_PHASE_ADJUST_MIN`` attr minimum value of phase adjustment + ``DPLL_A_PIN_PHASE_ADJUST_MAX`` attr maximum value of phase adjustment + ``DPLL_A_PIN_PHASE_ADJUST`` attr configured value of phase + adjustment on parent dpll device + ``DPLL_A_PIN_PARENT_DEVICE`` nested attribute for requesting + configuration on given parent dpll + device + ``DPLL_A_PIN_PARENT_ID`` parent dpll device id + ``DPLL_A_PIN_PHASE_OFFSET`` attr measured phase difference + between a pin and parent dpll device + ================================ ========================================== All phase related values are provided in pico seconds, which represents time difference between signals phase. The negative value means that @@ -384,6 +386,8 @@ according to attribute purpose. frequencies ``DPLL_A_PIN_ANY_FREQUENCY_MIN`` attr minimum value of frequency ``DPLL_A_PIN_ANY_FREQUENCY_MAX`` attr maximum value of frequency + ``DPLL_A_PIN_PHASE_ADJUST_GRAN`` attr granularity of phase + adjustment value ``DPLL_A_PIN_PHASE_ADJUST_MIN`` attr minimum value of phase adjustment ``DPLL_A_PIN_PHASE_ADJUST_MAX`` attr maximum value of phase diff --git a/Documentation/netlink/specs/dpll.yaml b/Documentation/netlink/specs/dpll.yaml index 80728f6f9bc876..78d0724d7e12ce 100644 --- a/Documentation/netlink/specs/dpll.yaml +++ b/Documentation/netlink/specs/dpll.yaml @@ -440,6 +440,12 @@ attribute-sets: doc: | Capable pin provides list of pins that can be bound to create a reference-sync pin pair. + - + name: phase-adjust-gran + type: u32 + doc: | + Granularity of phase adjustment, in picoseconds. The value of + phase adjustment must be a multiple of this granularity. - name: pin-parent-device @@ -616,6 +622,7 @@ operations: - capabilities - parent-device - parent-pin + - phase-adjust-gran - phase-adjust-min - phase-adjust-max - phase-adjust diff --git a/drivers/dpll/dpll_netlink.c b/drivers/dpll/dpll_netlink.c index a4153bcb6dcfe1..64944f601ee5aa 100644 --- a/drivers/dpll/dpll_netlink.c +++ b/drivers/dpll/dpll_netlink.c @@ -637,6 +637,10 @@ dpll_cmd_pin_get_one(struct sk_buff *msg, struct dpll_pin *pin, ret = dpll_msg_add_pin_freq(msg, pin, ref, extack); if (ret) return ret; + if (prop->phase_gran && + nla_put_u32(msg, DPLL_A_PIN_PHASE_ADJUST_GRAN, + prop->phase_gran)) + return -EMSGSIZE; if (nla_put_s32(msg, DPLL_A_PIN_PHASE_ADJUST_MIN, prop->phase_range.min)) return -EMSGSIZE; @@ -1261,7 +1265,13 @@ dpll_pin_phase_adj_set(struct dpll_pin *pin, struct nlattr *phase_adj_attr, if (phase_adj > pin->prop.phase_range.max || phase_adj < pin->prop.phase_range.min) { NL_SET_ERR_MSG_ATTR(extack, phase_adj_attr, - "phase adjust value not supported"); + "phase adjust value of out range"); + return -EINVAL; + } + if (pin->prop.phase_gran && phase_adj % (s32)pin->prop.phase_gran) { + NL_SET_ERR_MSG_ATTR_FMT(extack, phase_adj_attr, + "phase adjust value not multiple of %u", + pin->prop.phase_gran); return -EINVAL; } diff --git a/include/linux/dpll.h b/include/linux/dpll.h index 25be745bf41f1d..562f520b23c277 100644 --- a/include/linux/dpll.h +++ b/include/linux/dpll.h @@ -163,6 +163,7 @@ struct dpll_pin_properties { u32 freq_supported_num; struct dpll_pin_frequency *freq_supported; struct dpll_pin_phase_adjust_range phase_range; + u32 phase_gran; }; #if IS_ENABLED(CONFIG_DPLL) diff --git a/include/uapi/linux/dpll.h b/include/uapi/linux/dpll.h index ab1725a954d749..69d35570ac4f12 100644 --- a/include/uapi/linux/dpll.h +++ b/include/uapi/linux/dpll.h @@ -251,6 +251,7 @@ enum dpll_a_pin { DPLL_A_PIN_ESYNC_FREQUENCY_SUPPORTED, DPLL_A_PIN_ESYNC_PULSE, DPLL_A_PIN_REFERENCE_SYNC, + DPLL_A_PIN_PHASE_ADJUST_GRAN, __DPLL_A_PIN_MAX, DPLL_A_PIN_MAX = (__DPLL_A_PIN_MAX - 1) From 40bcd1fa0e23c31e9c2ab910e54d93d8d0fabc1b Mon Sep 17 00:00:00 2001 From: Ivan Vecera Date: Wed, 29 Oct 2025 16:32:07 +0100 Subject: [PATCH 3568/3902] dpll: zl3073x: Specify phase adjustment granularity for pins [ Upstream commit 055a01b29fd643e33b9b1e88e24bbe1afe6fc6d9 ] Output pins phase adjustment values in the device are expressed in half synth clock cycles. Use this number of cycles as output pins' phase adjust granularity and simplify both get/set callbacks. Reviewed-by: Michal Schmidt Reviewed-by: Petr Oros Tested-by: Prathosh Satish Signed-off-by: Ivan Vecera Reviewed-by: Arkadiusz Kubalewski Link: https://patch.msgid.link/20251029153207.178448-3-ivecera@redhat.com Signed-off-by: Jakub Kicinski Stable-dep-of: 5d41f95f5d0b ("dpll: zl3073x: Fix output pin phase adjustment sign") Signed-off-by: Sasha Levin --- drivers/dpll/zl3073x/dpll.c | 58 +++++++++---------------------------- drivers/dpll/zl3073x/prop.c | 11 +++++++ 2 files changed, 25 insertions(+), 44 deletions(-) diff --git a/drivers/dpll/zl3073x/dpll.c b/drivers/dpll/zl3073x/dpll.c index f93f9a45832432..d90150671d3742 100644 --- a/drivers/dpll/zl3073x/dpll.c +++ b/drivers/dpll/zl3073x/dpll.c @@ -35,6 +35,7 @@ * @prio: pin priority <0, 14> * @selectable: pin is selectable in automatic mode * @esync_control: embedded sync is controllable + * @phase_gran: phase adjustment granularity * @pin_state: last saved pin state * @phase_offset: last saved pin phase offset * @freq_offset: last saved fractional frequency offset @@ -49,6 +50,7 @@ struct zl3073x_dpll_pin { u8 prio; bool selectable; bool esync_control; + s32 phase_gran; enum dpll_pin_state pin_state; s64 phase_offset; s64 freq_offset; @@ -1388,25 +1390,14 @@ zl3073x_dpll_output_pin_phase_adjust_get(const struct dpll_pin *dpll_pin, struct zl3073x_dpll *zldpll = dpll_priv; struct zl3073x_dev *zldev = zldpll->dev; struct zl3073x_dpll_pin *pin = pin_priv; - u32 synth_freq; s32 phase_comp; - u8 out, synth; + u8 out; int rc; - out = zl3073x_output_pin_out_get(pin->id); - synth = zl3073x_out_synth_get(zldev, out); - synth_freq = zl3073x_synth_freq_get(zldev, synth); - - /* Check synth freq for zero */ - if (!synth_freq) { - dev_err(zldev->dev, "Got zero synth frequency for output %u\n", - out); - return -EINVAL; - } - guard(mutex)(&zldev->multiop_lock); /* Read output configuration */ + out = zl3073x_output_pin_out_get(pin->id); rc = zl3073x_mb_op(zldev, ZL_REG_OUTPUT_MB_SEM, ZL_OUTPUT_MB_SEM_RD, ZL_REG_OUTPUT_MB_MASK, BIT(out)); if (rc) @@ -1417,11 +1408,10 @@ zl3073x_dpll_output_pin_phase_adjust_get(const struct dpll_pin *dpll_pin, if (rc) return rc; - /* Value in register is expressed in half synth clock cycles */ - phase_comp *= (int)div_u64(PSEC_PER_SEC, 2 * synth_freq); - - /* Reverse two's complement negation applied during 'set' */ - *phase_adjust = -phase_comp; + /* Convert value to ps and reverse two's complement negation applied + * during 'set' + */ + *phase_adjust = -phase_comp * pin->phase_gran; return rc; } @@ -1437,39 +1427,18 @@ zl3073x_dpll_output_pin_phase_adjust_set(const struct dpll_pin *dpll_pin, struct zl3073x_dpll *zldpll = dpll_priv; struct zl3073x_dev *zldev = zldpll->dev; struct zl3073x_dpll_pin *pin = pin_priv; - int half_synth_cycle; - u32 synth_freq; - u8 out, synth; + u8 out; int rc; - /* Get attached synth */ - out = zl3073x_output_pin_out_get(pin->id); - synth = zl3073x_out_synth_get(zldev, out); - - /* Get synth's frequency */ - synth_freq = zl3073x_synth_freq_get(zldev, synth); - - /* Value in register is expressed in half synth clock cycles so - * the given phase adjustment a multiple of half synth clock. - */ - half_synth_cycle = (int)div_u64(PSEC_PER_SEC, 2 * synth_freq); - - if ((phase_adjust % half_synth_cycle) != 0) { - NL_SET_ERR_MSG_FMT(extack, - "Phase adjustment value has to be multiple of %d", - half_synth_cycle); - return -EINVAL; - } - phase_adjust /= half_synth_cycle; - /* The value in the register is stored as two's complement negation - * of requested value. + * of requested value and expressed in half synth clock cycles. */ - phase_adjust = -phase_adjust; + phase_adjust = -phase_adjust / pin->phase_gran; guard(mutex)(&zldev->multiop_lock); /* Read output configuration */ + out = zl3073x_output_pin_out_get(pin->id); rc = zl3073x_mb_op(zldev, ZL_REG_OUTPUT_MB_SEM, ZL_OUTPUT_MB_SEM_RD, ZL_REG_OUTPUT_MB_MASK, BIT(out)); if (rc) @@ -1758,9 +1727,10 @@ zl3073x_dpll_pin_register(struct zl3073x_dpll_pin *pin, u32 index) if (IS_ERR(props)) return PTR_ERR(props); - /* Save package label & esync capability */ + /* Save package label, esync capability and phase adjust granularity */ strscpy(pin->label, props->package_label); pin->esync_control = props->esync_control; + pin->phase_gran = props->dpll_props.phase_gran; if (zl3073x_dpll_is_input_pin(pin)) { rc = zl3073x_dpll_ref_prio_get(pin, &pin->prio); diff --git a/drivers/dpll/zl3073x/prop.c b/drivers/dpll/zl3073x/prop.c index 4cf7e8aefcb373..9e1fca5cdaf1ea 100644 --- a/drivers/dpll/zl3073x/prop.c +++ b/drivers/dpll/zl3073x/prop.c @@ -208,7 +208,18 @@ struct zl3073x_pin_props *zl3073x_pin_props_get(struct zl3073x_dev *zldev, DPLL_PIN_CAPABILITIES_PRIORITY_CAN_CHANGE | DPLL_PIN_CAPABILITIES_STATE_CAN_CHANGE; } else { + u8 out, synth; + u32 f; + props->dpll_props.type = DPLL_PIN_TYPE_GNSS; + + /* The output pin phase adjustment granularity equals half of + * the synth frequency count. + */ + out = zl3073x_output_pin_out_get(index); + synth = zl3073x_out_synth_get(zldev, out); + f = 2 * zl3073x_synth_freq_get(zldev, synth); + props->dpll_props.phase_gran = f ? div_u64(PSEC_PER_SEC, f) : 1; } props->dpll_props.phase_range.min = S32_MIN; From 84b8990f5771d0591de7365ddbec17b1fafb97c7 Mon Sep 17 00:00:00 2001 From: Ivan Vecera Date: Thu, 13 Nov 2025 08:41:00 +0100 Subject: [PATCH 3569/3902] dpll: zl3073x: Store raw register values instead of parsed state [ Upstream commit 58fb88d30b0250f928e1afa0eaa4547770d86229 ] The zl3073x_ref, zl3073x_out and zl3073x_synth structures previously stored state that was parsed from register reads. This included values like boolean 'enabled' flags, synthesizer selections, and pre-calculated frequencies. This commit refactors the state management to store the raw register values directly in these structures. The various inline helper functions are updated to parse these raw values on-demand using FIELD_GET. Reviewed-by: Petr Oros Tested-by: Prathosh Satish Signed-off-by: Ivan Vecera Link: https://patch.msgid.link/20251113074105.141379-2-ivecera@redhat.com Signed-off-by: Jakub Kicinski Stable-dep-of: 5d41f95f5d0b ("dpll: zl3073x: Fix output pin phase adjustment sign") Signed-off-by: Sasha Levin --- drivers/dpll/zl3073x/core.c | 81 ++++++++++++------------------------- drivers/dpll/zl3073x/core.h | 61 ++++++++++++++++------------ 2 files changed, 60 insertions(+), 82 deletions(-) diff --git a/drivers/dpll/zl3073x/core.c b/drivers/dpll/zl3073x/core.c index e42e527813cf8b..50c1fe59bc7f0f 100644 --- a/drivers/dpll/zl3073x/core.c +++ b/drivers/dpll/zl3073x/core.c @@ -598,25 +598,22 @@ int zl3073x_write_hwreg_seq(struct zl3073x_dev *zldev, * @zldev: pointer to zl3073x_dev structure * @index: input reference index to fetch state for * - * Function fetches information for the given input reference that are - * invariant and stores them for later use. + * Function fetches state for the given input reference and stores it for + * later user. * * Return: 0 on success, <0 on error */ static int zl3073x_ref_state_fetch(struct zl3073x_dev *zldev, u8 index) { - struct zl3073x_ref *input = &zldev->ref[index]; - u8 ref_config; + struct zl3073x_ref *ref = &zldev->ref[index]; int rc; /* If the input is differential then the configuration for N-pin * reference is ignored and P-pin config is used for both. */ - if (zl3073x_is_n_pin(index) && - zl3073x_ref_is_diff(zldev, index - 1)) { - input->enabled = zl3073x_ref_is_enabled(zldev, index - 1); - input->diff = true; + if (zl3073x_is_n_pin(index) && zl3073x_ref_is_diff(zldev, index - 1)) { + memcpy(ref, &zldev->ref[index - 1], sizeof(*ref)); return 0; } @@ -630,16 +627,14 @@ zl3073x_ref_state_fetch(struct zl3073x_dev *zldev, u8 index) return rc; /* Read ref_config register */ - rc = zl3073x_read_u8(zldev, ZL_REG_REF_CONFIG, &ref_config); + rc = zl3073x_read_u8(zldev, ZL_REG_REF_CONFIG, &ref->config); if (rc) return rc; - input->enabled = FIELD_GET(ZL_REF_CONFIG_ENABLE, ref_config); - input->diff = FIELD_GET(ZL_REF_CONFIG_DIFF_EN, ref_config); - dev_dbg(zldev->dev, "REF%u is %s and configured as %s\n", index, - str_enabled_disabled(input->enabled), - input->diff ? "differential" : "single-ended"); + str_enabled_disabled(zl3073x_ref_is_enabled(zldev, index)), + zl3073x_ref_is_diff(zldev, index) + ? "differential" : "single-ended"); return rc; } @@ -649,8 +644,8 @@ zl3073x_ref_state_fetch(struct zl3073x_dev *zldev, u8 index) * @zldev: pointer to zl3073x_dev structure * @index: output index to fetch state for * - * Function fetches information for the given output (not output pin) - * that are invariant and stores them for later use. + * Function fetches state of the given output (not output pin) and stores it + * for later use. * * Return: 0 on success, <0 on error */ @@ -658,22 +653,16 @@ static int zl3073x_out_state_fetch(struct zl3073x_dev *zldev, u8 index) { struct zl3073x_out *out = &zldev->out[index]; - u8 output_ctrl, output_mode; int rc; /* Read output configuration */ - rc = zl3073x_read_u8(zldev, ZL_REG_OUTPUT_CTRL(index), &output_ctrl); + rc = zl3073x_read_u8(zldev, ZL_REG_OUTPUT_CTRL(index), &out->ctrl); if (rc) return rc; - /* Store info about output enablement and synthesizer the output - * is connected to. - */ - out->enabled = FIELD_GET(ZL_OUTPUT_CTRL_EN, output_ctrl); - out->synth = FIELD_GET(ZL_OUTPUT_CTRL_SYNTH_SEL, output_ctrl); - dev_dbg(zldev->dev, "OUT%u is %s and connected to SYNTH%u\n", index, - str_enabled_disabled(out->enabled), out->synth); + str_enabled_disabled(zl3073x_out_is_enabled(zldev, index)), + zl3073x_out_synth_get(zldev, index)); guard(mutex)(&zldev->multiop_lock); @@ -683,17 +672,13 @@ zl3073x_out_state_fetch(struct zl3073x_dev *zldev, u8 index) if (rc) return rc; - /* Read output_mode */ - rc = zl3073x_read_u8(zldev, ZL_REG_OUTPUT_MODE, &output_mode); + /* Read output mode */ + rc = zl3073x_read_u8(zldev, ZL_REG_OUTPUT_MODE, &out->mode); if (rc) return rc; - /* Extract and store output signal format */ - out->signal_format = FIELD_GET(ZL_OUTPUT_MODE_SIGNAL_FORMAT, - output_mode); - dev_dbg(zldev->dev, "OUT%u has signal format 0x%02x\n", index, - out->signal_format); + zl3073x_out_signal_format_get(zldev, index)); return rc; } @@ -703,8 +688,7 @@ zl3073x_out_state_fetch(struct zl3073x_dev *zldev, u8 index) * @zldev: pointer to zl3073x_dev structure * @index: synth index to fetch state for * - * Function fetches information for the given synthesizer that are - * invariant and stores them for later use. + * Function fetches state of the given synthesizer and stores it for later use. * * Return: 0 on success, <0 on error */ @@ -712,25 +696,13 @@ static int zl3073x_synth_state_fetch(struct zl3073x_dev *zldev, u8 index) { struct zl3073x_synth *synth = &zldev->synth[index]; - u16 base, m, n; - u8 synth_ctrl; - u32 mult; int rc; /* Read synth control register */ - rc = zl3073x_read_u8(zldev, ZL_REG_SYNTH_CTRL(index), &synth_ctrl); + rc = zl3073x_read_u8(zldev, ZL_REG_SYNTH_CTRL(index), &synth->ctrl); if (rc) return rc; - /* Store info about synth enablement and DPLL channel the synth is - * driven by. - */ - synth->enabled = FIELD_GET(ZL_SYNTH_CTRL_EN, synth_ctrl); - synth->dpll = FIELD_GET(ZL_SYNTH_CTRL_DPLL_SEL, synth_ctrl); - - dev_dbg(zldev->dev, "SYNTH%u is %s and driven by DPLL%u\n", index, - str_enabled_disabled(synth->enabled), synth->dpll); - guard(mutex)(&zldev->multiop_lock); /* Read synth configuration */ @@ -744,35 +716,32 @@ zl3073x_synth_state_fetch(struct zl3073x_dev *zldev, u8 index) * * Read registers with these values */ - rc = zl3073x_read_u16(zldev, ZL_REG_SYNTH_FREQ_BASE, &base); + rc = zl3073x_read_u16(zldev, ZL_REG_SYNTH_FREQ_BASE, &synth->freq_base); if (rc) return rc; - rc = zl3073x_read_u32(zldev, ZL_REG_SYNTH_FREQ_MULT, &mult); + rc = zl3073x_read_u32(zldev, ZL_REG_SYNTH_FREQ_MULT, &synth->freq_mult); if (rc) return rc; - rc = zl3073x_read_u16(zldev, ZL_REG_SYNTH_FREQ_M, &m); + rc = zl3073x_read_u16(zldev, ZL_REG_SYNTH_FREQ_M, &synth->freq_m); if (rc) return rc; - rc = zl3073x_read_u16(zldev, ZL_REG_SYNTH_FREQ_N, &n); + rc = zl3073x_read_u16(zldev, ZL_REG_SYNTH_FREQ_N, &synth->freq_n); if (rc) return rc; /* Check denominator for zero to avoid div by 0 */ - if (!n) { + if (!synth->freq_n) { dev_err(zldev->dev, "Zero divisor for SYNTH%u retrieved from device\n", index); return -EINVAL; } - /* Compute and store synth frequency */ - zldev->synth[index].freq = div_u64(mul_u32_u32(base * m, mult), n); - dev_dbg(zldev->dev, "SYNTH%u frequency: %u Hz\n", index, - zldev->synth[index].freq); + zl3073x_synth_freq_get(zldev, index)); return rc; } diff --git a/drivers/dpll/zl3073x/core.h b/drivers/dpll/zl3073x/core.h index 1dca4ddcf2350c..51d0fd6cfabfc3 100644 --- a/drivers/dpll/zl3073x/core.h +++ b/drivers/dpll/zl3073x/core.h @@ -29,38 +29,38 @@ struct zl3073x_dpll; /** * struct zl3073x_ref - input reference invariant info - * @enabled: input reference is enabled or disabled - * @diff: true if input reference is differential * @ffo: current fractional frequency offset + * @config: reference config */ struct zl3073x_ref { - bool enabled; - bool diff; s64 ffo; + u8 config; }; /** * struct zl3073x_out - output invariant info - * @enabled: out is enabled or disabled - * @synth: synthesizer the out is connected to - * @signal_format: out signal format + * @ctrl: output control + * @mode: output mode */ struct zl3073x_out { - bool enabled; - u8 synth; - u8 signal_format; + u8 ctrl; + u8 mode; }; /** * struct zl3073x_synth - synthesizer invariant info - * @freq: synthesizer frequency - * @dpll: ID of DPLL the synthesizer is driven by - * @enabled: synth is enabled or disabled + * @freq_mult: frequency multiplier + * @freq_base: frequency base + * @freq_m: frequency numerator + * @freq_n: frequency denominator + * @ctrl: synth control */ struct zl3073x_synth { - u32 freq; - u8 dpll; - bool enabled; + u32 freq_mult; + u16 freq_base; + u16 freq_m; + u16 freq_n; + u8 ctrl; }; /** @@ -239,7 +239,10 @@ zl3073x_ref_ffo_get(struct zl3073x_dev *zldev, u8 index) static inline bool zl3073x_ref_is_diff(struct zl3073x_dev *zldev, u8 index) { - return zldev->ref[index].diff; + if (FIELD_GET(ZL_REF_CONFIG_DIFF_EN, zldev->ref[index].config)) + return true; + + return false; } /** @@ -252,7 +255,10 @@ zl3073x_ref_is_diff(struct zl3073x_dev *zldev, u8 index) static inline bool zl3073x_ref_is_enabled(struct zl3073x_dev *zldev, u8 index) { - return zldev->ref[index].enabled; + if (FIELD_GET(ZL_REF_CONFIG_ENABLE, zldev->ref[index].config)) + return true; + + return false; } /** @@ -265,7 +271,7 @@ zl3073x_ref_is_enabled(struct zl3073x_dev *zldev, u8 index) static inline u8 zl3073x_synth_dpll_get(struct zl3073x_dev *zldev, u8 index) { - return zldev->synth[index].dpll; + return FIELD_GET(ZL_SYNTH_CTRL_DPLL_SEL, zldev->synth[index].ctrl); } /** @@ -278,7 +284,10 @@ zl3073x_synth_dpll_get(struct zl3073x_dev *zldev, u8 index) static inline u32 zl3073x_synth_freq_get(struct zl3073x_dev *zldev, u8 index) { - return zldev->synth[index].freq; + struct zl3073x_synth *synth = &zldev->synth[index]; + + return mul_u64_u32_div(synth->freq_base * synth->freq_m, + synth->freq_mult, synth->freq_n); } /** @@ -291,7 +300,7 @@ zl3073x_synth_freq_get(struct zl3073x_dev *zldev, u8 index) static inline bool zl3073x_synth_is_enabled(struct zl3073x_dev *zldev, u8 index) { - return zldev->synth[index].enabled; + return FIELD_GET(ZL_SYNTH_CTRL_EN, zldev->synth[index].ctrl); } /** @@ -304,7 +313,7 @@ zl3073x_synth_is_enabled(struct zl3073x_dev *zldev, u8 index) static inline u8 zl3073x_out_synth_get(struct zl3073x_dev *zldev, u8 index) { - return zldev->out[index].synth; + return FIELD_GET(ZL_OUTPUT_CTRL_SYNTH_SEL, zldev->out[index].ctrl); } /** @@ -321,10 +330,10 @@ zl3073x_out_is_enabled(struct zl3073x_dev *zldev, u8 index) /* Output is enabled only if associated synth is enabled */ synth = zl3073x_out_synth_get(zldev, index); - if (zl3073x_synth_is_enabled(zldev, synth)) - return zldev->out[index].enabled; + if (!zl3073x_synth_is_enabled(zldev, synth)) + return false; - return false; + return FIELD_GET(ZL_OUTPUT_CTRL_EN, zldev->out[index].ctrl); } /** @@ -337,7 +346,7 @@ zl3073x_out_is_enabled(struct zl3073x_dev *zldev, u8 index) static inline u8 zl3073x_out_signal_format_get(struct zl3073x_dev *zldev, u8 index) { - return zldev->out[index].signal_format; + return FIELD_GET(ZL_OUTPUT_MODE_SIGNAL_FORMAT, zldev->out[index].mode); } /** From 9116bb0e0c9f89e72fb6c22dc2fcec23b244fa86 Mon Sep 17 00:00:00 2001 From: Ivan Vecera Date: Thu, 13 Nov 2025 08:41:01 +0100 Subject: [PATCH 3570/3902] dpll: zl3073x: Split ref, out, and synth logic from core [ Upstream commit 607f2c00c61faa3b437dbb0d38287e7a9d398a52 ] Refactor the zl3073x driver by splitting the logic for input references, outputs and synthesizers out of the monolithic core.[ch] files. Move the logic for each functional block into its own dedicated files: ref.[ch], out.[ch] and synth.[ch]. Specifically: - Move state structures (zl3073x_ref, zl3073x_out, zl3073x_synth) from core.h into their respective new headers - Move state-fetching functions (..._state_fetch) from core.c to their new .c files - Move the zl3073x_ref_freq_factorize helper from core.c to ref.c - Introduce a new helper layer to decouple the core device logic from the state-parsing logic: 1. Move the original inline helpers (e.g., zl3073x_ref_is_enabled) to the new headers (ref.h, etc.) and make them operate on a const struct ... * pointer. 2. Create new zl3073x_dev_... prefixed functions in core.h (e.g., zl3073x_dev_ref_is_enabled) and Implement these _dev_ functions to fetch state using a new ..._state_get() helper and then call the non-prefixed helper. 3. Update all driver-internal callers (in dpll.c, prop.c, etc.) to use the new zl3073x_dev_... functions. Reviewed-by: Petr Oros Tested-by: Prathosh Satish Signed-off-by: Ivan Vecera Link: https://patch.msgid.link/20251113074105.141379-3-ivecera@redhat.com Signed-off-by: Jakub Kicinski Stable-dep-of: 5d41f95f5d0b ("dpll: zl3073x: Fix output pin phase adjustment sign") Signed-off-by: Sasha Levin --- drivers/dpll/zl3073x/Makefile | 3 +- drivers/dpll/zl3073x/core.c | 194 ---------------------------------- drivers/dpll/zl3073x/core.h | 170 +++++++++++++---------------- drivers/dpll/zl3073x/dpll.c | 36 +++---- drivers/dpll/zl3073x/out.c | 67 ++++++++++++ drivers/dpll/zl3073x/out.h | 80 ++++++++++++++ drivers/dpll/zl3073x/prop.c | 12 +-- drivers/dpll/zl3073x/ref.c | 112 ++++++++++++++++++++ drivers/dpll/zl3073x/ref.h | 66 ++++++++++++ drivers/dpll/zl3073x/synth.c | 87 +++++++++++++++ drivers/dpll/zl3073x/synth.h | 72 +++++++++++++ 11 files changed, 584 insertions(+), 315 deletions(-) create mode 100644 drivers/dpll/zl3073x/out.c create mode 100644 drivers/dpll/zl3073x/out.h create mode 100644 drivers/dpll/zl3073x/ref.c create mode 100644 drivers/dpll/zl3073x/ref.h create mode 100644 drivers/dpll/zl3073x/synth.c create mode 100644 drivers/dpll/zl3073x/synth.h diff --git a/drivers/dpll/zl3073x/Makefile b/drivers/dpll/zl3073x/Makefile index 84e22aae57e5f7..bd324c7fe7101d 100644 --- a/drivers/dpll/zl3073x/Makefile +++ b/drivers/dpll/zl3073x/Makefile @@ -1,7 +1,8 @@ # SPDX-License-Identifier: GPL-2.0 obj-$(CONFIG_ZL3073X) += zl3073x.o -zl3073x-objs := core.o devlink.o dpll.o flash.o fw.o prop.o +zl3073x-objs := core.o devlink.o dpll.o flash.o fw.o \ + out.o prop.o ref.o synth.o obj-$(CONFIG_ZL3073X_I2C) += zl3073x_i2c.o zl3073x_i2c-objs := i2c.o diff --git a/drivers/dpll/zl3073x/core.c b/drivers/dpll/zl3073x/core.c index 50c1fe59bc7f0f..2f340f7eb9ec31 100644 --- a/drivers/dpll/zl3073x/core.c +++ b/drivers/dpll/zl3073x/core.c @@ -129,47 +129,6 @@ const struct regmap_config zl3073x_regmap_config = { }; EXPORT_SYMBOL_NS_GPL(zl3073x_regmap_config, "ZL3073X"); -/** - * zl3073x_ref_freq_factorize - factorize given frequency - * @freq: input frequency - * @base: base frequency - * @mult: multiplier - * - * Checks if the given frequency can be factorized using one of the - * supported base frequencies. If so the base frequency and multiplier - * are stored into appropriate parameters if they are not NULL. - * - * Return: 0 on success, -EINVAL if the frequency cannot be factorized - */ -int -zl3073x_ref_freq_factorize(u32 freq, u16 *base, u16 *mult) -{ - static const u16 base_freqs[] = { - 1, 2, 4, 5, 8, 10, 16, 20, 25, 32, 40, 50, 64, 80, 100, 125, - 128, 160, 200, 250, 256, 320, 400, 500, 625, 640, 800, 1000, - 1250, 1280, 1600, 2000, 2500, 3125, 3200, 4000, 5000, 6250, - 6400, 8000, 10000, 12500, 15625, 16000, 20000, 25000, 31250, - 32000, 40000, 50000, 62500, - }; - u32 div; - int i; - - for (i = 0; i < ARRAY_SIZE(base_freqs); i++) { - div = freq / base_freqs[i]; - - if (div <= U16_MAX && (freq % base_freqs[i]) == 0) { - if (base) - *base = base_freqs[i]; - if (mult) - *mult = div; - - return 0; - } - } - - return -EINVAL; -} - static bool zl3073x_check_reg(struct zl3073x_dev *zldev, unsigned int reg, size_t size) { @@ -593,159 +552,6 @@ int zl3073x_write_hwreg_seq(struct zl3073x_dev *zldev, return rc; } -/** - * zl3073x_ref_state_fetch - get input reference state - * @zldev: pointer to zl3073x_dev structure - * @index: input reference index to fetch state for - * - * Function fetches state for the given input reference and stores it for - * later user. - * - * Return: 0 on success, <0 on error - */ -static int -zl3073x_ref_state_fetch(struct zl3073x_dev *zldev, u8 index) -{ - struct zl3073x_ref *ref = &zldev->ref[index]; - int rc; - - /* If the input is differential then the configuration for N-pin - * reference is ignored and P-pin config is used for both. - */ - if (zl3073x_is_n_pin(index) && zl3073x_ref_is_diff(zldev, index - 1)) { - memcpy(ref, &zldev->ref[index - 1], sizeof(*ref)); - - return 0; - } - - guard(mutex)(&zldev->multiop_lock); - - /* Read reference configuration */ - rc = zl3073x_mb_op(zldev, ZL_REG_REF_MB_SEM, ZL_REF_MB_SEM_RD, - ZL_REG_REF_MB_MASK, BIT(index)); - if (rc) - return rc; - - /* Read ref_config register */ - rc = zl3073x_read_u8(zldev, ZL_REG_REF_CONFIG, &ref->config); - if (rc) - return rc; - - dev_dbg(zldev->dev, "REF%u is %s and configured as %s\n", index, - str_enabled_disabled(zl3073x_ref_is_enabled(zldev, index)), - zl3073x_ref_is_diff(zldev, index) - ? "differential" : "single-ended"); - - return rc; -} - -/** - * zl3073x_out_state_fetch - get output state - * @zldev: pointer to zl3073x_dev structure - * @index: output index to fetch state for - * - * Function fetches state of the given output (not output pin) and stores it - * for later use. - * - * Return: 0 on success, <0 on error - */ -static int -zl3073x_out_state_fetch(struct zl3073x_dev *zldev, u8 index) -{ - struct zl3073x_out *out = &zldev->out[index]; - int rc; - - /* Read output configuration */ - rc = zl3073x_read_u8(zldev, ZL_REG_OUTPUT_CTRL(index), &out->ctrl); - if (rc) - return rc; - - dev_dbg(zldev->dev, "OUT%u is %s and connected to SYNTH%u\n", index, - str_enabled_disabled(zl3073x_out_is_enabled(zldev, index)), - zl3073x_out_synth_get(zldev, index)); - - guard(mutex)(&zldev->multiop_lock); - - /* Read output configuration */ - rc = zl3073x_mb_op(zldev, ZL_REG_OUTPUT_MB_SEM, ZL_OUTPUT_MB_SEM_RD, - ZL_REG_OUTPUT_MB_MASK, BIT(index)); - if (rc) - return rc; - - /* Read output mode */ - rc = zl3073x_read_u8(zldev, ZL_REG_OUTPUT_MODE, &out->mode); - if (rc) - return rc; - - dev_dbg(zldev->dev, "OUT%u has signal format 0x%02x\n", index, - zl3073x_out_signal_format_get(zldev, index)); - - return rc; -} - -/** - * zl3073x_synth_state_fetch - get synth state - * @zldev: pointer to zl3073x_dev structure - * @index: synth index to fetch state for - * - * Function fetches state of the given synthesizer and stores it for later use. - * - * Return: 0 on success, <0 on error - */ -static int -zl3073x_synth_state_fetch(struct zl3073x_dev *zldev, u8 index) -{ - struct zl3073x_synth *synth = &zldev->synth[index]; - int rc; - - /* Read synth control register */ - rc = zl3073x_read_u8(zldev, ZL_REG_SYNTH_CTRL(index), &synth->ctrl); - if (rc) - return rc; - - guard(mutex)(&zldev->multiop_lock); - - /* Read synth configuration */ - rc = zl3073x_mb_op(zldev, ZL_REG_SYNTH_MB_SEM, ZL_SYNTH_MB_SEM_RD, - ZL_REG_SYNTH_MB_MASK, BIT(index)); - if (rc) - return rc; - - /* The output frequency is determined by the following formula: - * base * multiplier * numerator / denominator - * - * Read registers with these values - */ - rc = zl3073x_read_u16(zldev, ZL_REG_SYNTH_FREQ_BASE, &synth->freq_base); - if (rc) - return rc; - - rc = zl3073x_read_u32(zldev, ZL_REG_SYNTH_FREQ_MULT, &synth->freq_mult); - if (rc) - return rc; - - rc = zl3073x_read_u16(zldev, ZL_REG_SYNTH_FREQ_M, &synth->freq_m); - if (rc) - return rc; - - rc = zl3073x_read_u16(zldev, ZL_REG_SYNTH_FREQ_N, &synth->freq_n); - if (rc) - return rc; - - /* Check denominator for zero to avoid div by 0 */ - if (!synth->freq_n) { - dev_err(zldev->dev, - "Zero divisor for SYNTH%u retrieved from device\n", - index); - return -EINVAL; - } - - dev_dbg(zldev->dev, "SYNTH%u frequency: %u Hz\n", index, - zl3073x_synth_freq_get(zldev, index)); - - return rc; -} - static int zl3073x_dev_state_fetch(struct zl3073x_dev *zldev) { diff --git a/drivers/dpll/zl3073x/core.h b/drivers/dpll/zl3073x/core.h index 51d0fd6cfabfc3..fe779fc77dd09d 100644 --- a/drivers/dpll/zl3073x/core.h +++ b/drivers/dpll/zl3073x/core.h @@ -9,7 +9,10 @@ #include #include +#include "out.h" +#include "ref.h" #include "regs.h" +#include "synth.h" struct device; struct regmap; @@ -27,42 +30,6 @@ struct zl3073x_dpll; #define ZL3073X_NUM_PINS (ZL3073X_NUM_INPUT_PINS + \ ZL3073X_NUM_OUTPUT_PINS) -/** - * struct zl3073x_ref - input reference invariant info - * @ffo: current fractional frequency offset - * @config: reference config - */ -struct zl3073x_ref { - s64 ffo; - u8 config; -}; - -/** - * struct zl3073x_out - output invariant info - * @ctrl: output control - * @mode: output mode - */ -struct zl3073x_out { - u8 ctrl; - u8 mode; -}; - -/** - * struct zl3073x_synth - synthesizer invariant info - * @freq_mult: frequency multiplier - * @freq_base: frequency base - * @freq_m: frequency numerator - * @freq_n: frequency denominator - * @ctrl: synth control - */ -struct zl3073x_synth { - u32 freq_mult; - u16 freq_base; - u16 freq_m; - u16 freq_n; - u8 ctrl; -}; - /** * struct zl3073x_dev - zl3073x device * @dev: pointer to device @@ -175,7 +142,6 @@ int zl3073x_write_hwreg_seq(struct zl3073x_dev *zldev, * Misc operations *****************/ -int zl3073x_ref_freq_factorize(u32 freq, u16 *base, u16 *mult); int zl3073x_ref_phase_offsets_update(struct zl3073x_dev *zldev, int channel); static inline bool @@ -217,181 +183,188 @@ zl3073x_output_pin_out_get(u8 id) } /** - * zl3073x_ref_ffo_get - get current fractional frequency offset + * zl3073x_dev_ref_ffo_get - get current fractional frequency offset * @zldev: pointer to zl3073x device * @index: input reference index * * Return: the latest measured fractional frequency offset */ static inline s64 -zl3073x_ref_ffo_get(struct zl3073x_dev *zldev, u8 index) +zl3073x_dev_ref_ffo_get(struct zl3073x_dev *zldev, u8 index) { - return zldev->ref[index].ffo; + const struct zl3073x_ref *ref = zl3073x_ref_state_get(zldev, index); + + return zl3073x_ref_ffo_get(ref); } /** - * zl3073x_ref_is_diff - check if the given input reference is differential + * zl3073x_dev_ref_is_diff - check if the given input reference is differential * @zldev: pointer to zl3073x device * @index: input reference index * * Return: true if reference is differential, false if reference is single-ended */ static inline bool -zl3073x_ref_is_diff(struct zl3073x_dev *zldev, u8 index) +zl3073x_dev_ref_is_diff(struct zl3073x_dev *zldev, u8 index) { - if (FIELD_GET(ZL_REF_CONFIG_DIFF_EN, zldev->ref[index].config)) - return true; + const struct zl3073x_ref *ref = zl3073x_ref_state_get(zldev, index); - return false; + return zl3073x_ref_is_diff(ref); } /** - * zl3073x_ref_is_enabled - check if the given input reference is enabled + * zl3073x_dev_ref_is_enabled - check if the given input reference is enabled * @zldev: pointer to zl3073x device * @index: input reference index * * Return: true if input refernce is enabled, false otherwise */ static inline bool -zl3073x_ref_is_enabled(struct zl3073x_dev *zldev, u8 index) +zl3073x_dev_ref_is_enabled(struct zl3073x_dev *zldev, u8 index) { - if (FIELD_GET(ZL_REF_CONFIG_ENABLE, zldev->ref[index].config)) - return true; + const struct zl3073x_ref *ref = zl3073x_ref_state_get(zldev, index); - return false; + return zl3073x_ref_is_enabled(ref); } /** - * zl3073x_synth_dpll_get - get DPLL ID the synth is driven by + * zl3073x_dev_synth_dpll_get - get DPLL ID the synth is driven by * @zldev: pointer to zl3073x device * @index: synth index * * Return: ID of DPLL the given synthetizer is driven by */ static inline u8 -zl3073x_synth_dpll_get(struct zl3073x_dev *zldev, u8 index) +zl3073x_dev_synth_dpll_get(struct zl3073x_dev *zldev, u8 index) { - return FIELD_GET(ZL_SYNTH_CTRL_DPLL_SEL, zldev->synth[index].ctrl); + const struct zl3073x_synth *synth; + + synth = zl3073x_synth_state_get(zldev, index); + return zl3073x_synth_dpll_get(synth); } /** - * zl3073x_synth_freq_get - get synth current freq + * zl3073x_dev_synth_freq_get - get synth current freq * @zldev: pointer to zl3073x device * @index: synth index * * Return: frequency of given synthetizer */ static inline u32 -zl3073x_synth_freq_get(struct zl3073x_dev *zldev, u8 index) +zl3073x_dev_synth_freq_get(struct zl3073x_dev *zldev, u8 index) { - struct zl3073x_synth *synth = &zldev->synth[index]; + const struct zl3073x_synth *synth; - return mul_u64_u32_div(synth->freq_base * synth->freq_m, - synth->freq_mult, synth->freq_n); + synth = zl3073x_synth_state_get(zldev, index); + return zl3073x_synth_freq_get(synth); } /** - * zl3073x_synth_is_enabled - check if the given synth is enabled + * zl3073x_dev_synth_is_enabled - check if the given synth is enabled * @zldev: pointer to zl3073x device * @index: synth index * * Return: true if synth is enabled, false otherwise */ static inline bool -zl3073x_synth_is_enabled(struct zl3073x_dev *zldev, u8 index) +zl3073x_dev_synth_is_enabled(struct zl3073x_dev *zldev, u8 index) { - return FIELD_GET(ZL_SYNTH_CTRL_EN, zldev->synth[index].ctrl); + const struct zl3073x_synth *synth; + + synth = zl3073x_synth_state_get(zldev, index); + return zl3073x_synth_is_enabled(synth); } /** - * zl3073x_out_synth_get - get synth connected to given output + * zl3073x_dev_out_synth_get - get synth connected to given output * @zldev: pointer to zl3073x device * @index: output index * * Return: index of synth connected to given output. */ static inline u8 -zl3073x_out_synth_get(struct zl3073x_dev *zldev, u8 index) +zl3073x_dev_out_synth_get(struct zl3073x_dev *zldev, u8 index) { - return FIELD_GET(ZL_OUTPUT_CTRL_SYNTH_SEL, zldev->out[index].ctrl); + const struct zl3073x_out *out = zl3073x_out_state_get(zldev, index); + + return zl3073x_out_synth_get(out); } /** - * zl3073x_out_is_enabled - check if the given output is enabled + * zl3073x_dev_out_is_enabled - check if the given output is enabled * @zldev: pointer to zl3073x device * @index: output index * * Return: true if the output is enabled, false otherwise */ static inline bool -zl3073x_out_is_enabled(struct zl3073x_dev *zldev, u8 index) +zl3073x_dev_out_is_enabled(struct zl3073x_dev *zldev, u8 index) { - u8 synth; + const struct zl3073x_out *out = zl3073x_out_state_get(zldev, index); + const struct zl3073x_synth *synth; + u8 synth_id; /* Output is enabled only if associated synth is enabled */ - synth = zl3073x_out_synth_get(zldev, index); - if (!zl3073x_synth_is_enabled(zldev, synth)) - return false; + synth_id = zl3073x_out_synth_get(out); + synth = zl3073x_synth_state_get(zldev, synth_id); - return FIELD_GET(ZL_OUTPUT_CTRL_EN, zldev->out[index].ctrl); + return zl3073x_synth_is_enabled(synth) && zl3073x_out_is_enabled(out); } /** - * zl3073x_out_signal_format_get - get output signal format + * zl3073x_dev_out_signal_format_get - get output signal format * @zldev: pointer to zl3073x device * @index: output index * * Return: signal format of given output */ static inline u8 -zl3073x_out_signal_format_get(struct zl3073x_dev *zldev, u8 index) +zl3073x_dev_out_signal_format_get(struct zl3073x_dev *zldev, u8 index) { - return FIELD_GET(ZL_OUTPUT_MODE_SIGNAL_FORMAT, zldev->out[index].mode); + const struct zl3073x_out *out = zl3073x_out_state_get(zldev, index); + + return zl3073x_out_signal_format_get(out); } /** - * zl3073x_out_dpll_get - get DPLL ID the output is driven by + * zl3073x_dev_out_dpll_get - get DPLL ID the output is driven by * @zldev: pointer to zl3073x device * @index: output index * * Return: ID of DPLL the given output is driven by */ static inline -u8 zl3073x_out_dpll_get(struct zl3073x_dev *zldev, u8 index) +u8 zl3073x_dev_out_dpll_get(struct zl3073x_dev *zldev, u8 index) { - u8 synth; + const struct zl3073x_out *out = zl3073x_out_state_get(zldev, index); + const struct zl3073x_synth *synth; + u8 synth_id; /* Get synthesizer connected to given output */ - synth = zl3073x_out_synth_get(zldev, index); + synth_id = zl3073x_out_synth_get(out); + synth = zl3073x_synth_state_get(zldev, synth_id); /* Return DPLL that drives the synth */ - return zl3073x_synth_dpll_get(zldev, synth); + return zl3073x_synth_dpll_get(synth); } /** - * zl3073x_out_is_diff - check if the given output is differential + * zl3073x_dev_out_is_diff - check if the given output is differential * @zldev: pointer to zl3073x device * @index: output index * * Return: true if output is differential, false if output is single-ended */ static inline bool -zl3073x_out_is_diff(struct zl3073x_dev *zldev, u8 index) +zl3073x_dev_out_is_diff(struct zl3073x_dev *zldev, u8 index) { - switch (zl3073x_out_signal_format_get(zldev, index)) { - case ZL_OUTPUT_MODE_SIGNAL_FORMAT_LVDS: - case ZL_OUTPUT_MODE_SIGNAL_FORMAT_DIFF: - case ZL_OUTPUT_MODE_SIGNAL_FORMAT_LOWVCM: - return true; - default: - break; - } + const struct zl3073x_out *out = zl3073x_out_state_get(zldev, index); - return false; + return zl3073x_out_is_diff(out); } /** - * zl3073x_output_pin_is_enabled - check if the given output pin is enabled + * zl3073x_dev_output_pin_is_enabled - check if the given output pin is enabled * @zldev: pointer to zl3073x device * @id: output pin id * @@ -401,16 +374,21 @@ zl3073x_out_is_diff(struct zl3073x_dev *zldev, u8 index) * Return: true if output pin is enabled, false if output pin is disabled */ static inline bool -zl3073x_output_pin_is_enabled(struct zl3073x_dev *zldev, u8 id) +zl3073x_dev_output_pin_is_enabled(struct zl3073x_dev *zldev, u8 id) { - u8 output = zl3073x_output_pin_out_get(id); + u8 out_id = zl3073x_output_pin_out_get(id); + const struct zl3073x_out *out; + + out = zl3073x_out_state_get(zldev, out_id); - /* Check if the whole output is enabled */ - if (!zl3073x_out_is_enabled(zldev, output)) + /* Check if the output is enabled - call _dev_ helper that + * additionally checks for attached synth enablement. + */ + if (!zl3073x_dev_out_is_enabled(zldev, out_id)) return false; /* Check signal format */ - switch (zl3073x_out_signal_format_get(zldev, output)) { + switch (zl3073x_out_signal_format_get(out)) { case ZL_OUTPUT_MODE_SIGNAL_FORMAT_DISABLED: /* Both output pins are disabled by signal format */ return false; diff --git a/drivers/dpll/zl3073x/dpll.c b/drivers/dpll/zl3073x/dpll.c index d90150671d3742..62996f26e065fe 100644 --- a/drivers/dpll/zl3073x/dpll.c +++ b/drivers/dpll/zl3073x/dpll.c @@ -967,7 +967,7 @@ zl3073x_dpll_output_pin_esync_get(const struct dpll_pin *dpll_pin, * for N-division is also used for the esync divider so both cannot * be used. */ - switch (zl3073x_out_signal_format_get(zldev, out)) { + switch (zl3073x_dev_out_signal_format_get(zldev, out)) { case ZL_OUTPUT_MODE_SIGNAL_FORMAT_2_NDIV: case ZL_OUTPUT_MODE_SIGNAL_FORMAT_2_NDIV_INV: return -EOPNOTSUPP; @@ -1001,10 +1001,10 @@ zl3073x_dpll_output_pin_esync_get(const struct dpll_pin *dpll_pin, } /* Get synth attached to output pin */ - synth = zl3073x_out_synth_get(zldev, out); + synth = zl3073x_dev_out_synth_get(zldev, out); /* Get synth frequency */ - synth_freq = zl3073x_synth_freq_get(zldev, synth); + synth_freq = zl3073x_dev_synth_freq_get(zldev, synth); clock_type = FIELD_GET(ZL_OUTPUT_MODE_CLOCK_TYPE, output_mode); if (clock_type != ZL_OUTPUT_MODE_CLOCK_TYPE_ESYNC) { @@ -1078,7 +1078,7 @@ zl3073x_dpll_output_pin_esync_set(const struct dpll_pin *dpll_pin, * for N-division is also used for the esync divider so both cannot * be used. */ - switch (zl3073x_out_signal_format_get(zldev, out)) { + switch (zl3073x_dev_out_signal_format_get(zldev, out)) { case ZL_OUTPUT_MODE_SIGNAL_FORMAT_2_NDIV: case ZL_OUTPUT_MODE_SIGNAL_FORMAT_2_NDIV_INV: return -EOPNOTSUPP; @@ -1117,10 +1117,10 @@ zl3073x_dpll_output_pin_esync_set(const struct dpll_pin *dpll_pin, goto write_mailbox; /* Get synth attached to output pin */ - synth = zl3073x_out_synth_get(zldev, out); + synth = zl3073x_dev_out_synth_get(zldev, out); /* Get synth frequency */ - synth_freq = zl3073x_synth_freq_get(zldev, synth); + synth_freq = zl3073x_dev_synth_freq_get(zldev, synth); rc = zl3073x_read_u32(zldev, ZL_REG_OUTPUT_DIV, &output_div); if (rc) @@ -1172,8 +1172,8 @@ zl3073x_dpll_output_pin_frequency_get(const struct dpll_pin *dpll_pin, int rc; out = zl3073x_output_pin_out_get(pin->id); - synth = zl3073x_out_synth_get(zldev, out); - synth_freq = zl3073x_synth_freq_get(zldev, synth); + synth = zl3073x_dev_out_synth_get(zldev, out); + synth_freq = zl3073x_dev_synth_freq_get(zldev, synth); guard(mutex)(&zldev->multiop_lock); @@ -1195,7 +1195,7 @@ zl3073x_dpll_output_pin_frequency_get(const struct dpll_pin *dpll_pin, } /* Read used signal format for the given output */ - signal_format = zl3073x_out_signal_format_get(zldev, out); + signal_format = zl3073x_dev_out_signal_format_get(zldev, out); switch (signal_format) { case ZL_OUTPUT_MODE_SIGNAL_FORMAT_2_NDIV: @@ -1263,12 +1263,12 @@ zl3073x_dpll_output_pin_frequency_set(const struct dpll_pin *dpll_pin, int rc; out = zl3073x_output_pin_out_get(pin->id); - synth = zl3073x_out_synth_get(zldev, out); - synth_freq = zl3073x_synth_freq_get(zldev, synth); + synth = zl3073x_dev_out_synth_get(zldev, out); + synth_freq = zl3073x_dev_synth_freq_get(zldev, synth); new_div = synth_freq / (u32)frequency; /* Get used signal format for the given output */ - signal_format = zl3073x_out_signal_format_get(zldev, out); + signal_format = zl3073x_dev_out_signal_format_get(zldev, out); guard(mutex)(&zldev->multiop_lock); @@ -1856,8 +1856,8 @@ zl3073x_dpll_pin_is_registrable(struct zl3073x_dpll *zldpll, if (zldpll->refsel_mode == ZL_DPLL_MODE_REFSEL_MODE_NCO) return false; - is_diff = zl3073x_ref_is_diff(zldev, ref); - is_enabled = zl3073x_ref_is_enabled(zldev, ref); + is_diff = zl3073x_dev_ref_is_diff(zldev, ref); + is_enabled = zl3073x_dev_ref_is_enabled(zldev, ref); } else { /* Output P&N pair shares single HW output */ u8 out = zl3073x_output_pin_out_get(index); @@ -1865,7 +1865,7 @@ zl3073x_dpll_pin_is_registrable(struct zl3073x_dpll *zldpll, name = "OUT"; /* Skip the pin if it is connected to different DPLL channel */ - if (zl3073x_out_dpll_get(zldev, out) != zldpll->id) { + if (zl3073x_dev_out_dpll_get(zldev, out) != zldpll->id) { dev_dbg(zldev->dev, "%s%u is driven by different DPLL\n", name, out); @@ -1873,8 +1873,8 @@ zl3073x_dpll_pin_is_registrable(struct zl3073x_dpll *zldpll, return false; } - is_diff = zl3073x_out_is_diff(zldev, out); - is_enabled = zl3073x_output_pin_is_enabled(zldev, index); + is_diff = zl3073x_dev_out_is_diff(zldev, out); + is_enabled = zl3073x_dev_output_pin_is_enabled(zldev, index); } /* Skip N-pin if the corresponding input/output is differential */ @@ -2124,7 +2124,7 @@ zl3073x_dpll_pin_ffo_check(struct zl3073x_dpll_pin *pin) return false; /* Get the latest measured ref's ffo */ - ffo = zl3073x_ref_ffo_get(zldev, ref); + ffo = zl3073x_dev_ref_ffo_get(zldev, ref); /* Compare with previous value */ if (pin->freq_offset != ffo) { diff --git a/drivers/dpll/zl3073x/out.c b/drivers/dpll/zl3073x/out.c new file mode 100644 index 00000000000000..a48f6917b39fb7 --- /dev/null +++ b/drivers/dpll/zl3073x/out.c @@ -0,0 +1,67 @@ +// SPDX-License-Identifier: GPL-2.0-only + +#include +#include +#include +#include +#include +#include + +#include "core.h" +#include "out.h" + +/** + * zl3073x_out_state_fetch - fetch output state from hardware + * @zldev: pointer to zl3073x_dev structure + * @index: output index to fetch state for + * + * Function fetches state of the given output from hardware and stores it + * for later use. + * + * Return: 0 on success, <0 on error + */ +int zl3073x_out_state_fetch(struct zl3073x_dev *zldev, u8 index) +{ + struct zl3073x_out *out = &zldev->out[index]; + int rc; + + /* Read output configuration */ + rc = zl3073x_read_u8(zldev, ZL_REG_OUTPUT_CTRL(index), &out->ctrl); + if (rc) + return rc; + + dev_dbg(zldev->dev, "OUT%u is %s and connected to SYNTH%u\n", index, + str_enabled_disabled(zl3073x_out_is_enabled(out)), + zl3073x_out_synth_get(out)); + + guard(mutex)(&zldev->multiop_lock); + + /* Read output configuration */ + rc = zl3073x_mb_op(zldev, ZL_REG_OUTPUT_MB_SEM, ZL_OUTPUT_MB_SEM_RD, + ZL_REG_OUTPUT_MB_MASK, BIT(index)); + if (rc) + return rc; + + /* Read output mode */ + rc = zl3073x_read_u8(zldev, ZL_REG_OUTPUT_MODE, &out->mode); + if (rc) + return rc; + + dev_dbg(zldev->dev, "OUT%u has signal format 0x%02x\n", index, + zl3073x_out_signal_format_get(out)); + + return rc; +} + +/** + * zl3073x_out_state_get - get current output state + * @zldev: pointer to zl3073x_dev structure + * @index: output index to get state for + * + * Return: pointer to given output state + */ +const struct zl3073x_out *zl3073x_out_state_get(struct zl3073x_dev *zldev, + u8 index) +{ + return &zldev->out[index]; +} diff --git a/drivers/dpll/zl3073x/out.h b/drivers/dpll/zl3073x/out.h new file mode 100644 index 00000000000000..986aa046221da4 --- /dev/null +++ b/drivers/dpll/zl3073x/out.h @@ -0,0 +1,80 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ + +#ifndef _ZL3073X_OUT_H +#define _ZL3073X_OUT_H + +#include +#include + +#include "regs.h" + +struct zl3073x_dev; + +/** + * struct zl3073x_out - output state + * @ctrl: output control + * @mode: output mode + */ +struct zl3073x_out { + u8 ctrl; + u8 mode; +}; + +int zl3073x_out_state_fetch(struct zl3073x_dev *zldev, u8 index); +const struct zl3073x_out *zl3073x_out_state_get(struct zl3073x_dev *zldev, + u8 index); + +/** + * zl3073x_out_signal_format_get - get output signal format + * @out: pointer to out state + * + * Return: signal format of given output + */ +static inline u8 zl3073x_out_signal_format_get(const struct zl3073x_out *out) +{ + return FIELD_GET(ZL_OUTPUT_MODE_SIGNAL_FORMAT, out->mode); +} + +/** + * zl3073x_out_is_diff - check if the given output is differential + * @out: pointer to out state + * + * Return: true if output is differential, false if output is single-ended + */ +static inline bool zl3073x_out_is_diff(const struct zl3073x_out *out) +{ + switch (zl3073x_out_signal_format_get(out)) { + case ZL_OUTPUT_MODE_SIGNAL_FORMAT_LVDS: + case ZL_OUTPUT_MODE_SIGNAL_FORMAT_DIFF: + case ZL_OUTPUT_MODE_SIGNAL_FORMAT_LOWVCM: + return true; + default: + break; + } + + return false; +} + +/** + * zl3073x_out_is_enabled - check if the given output is enabled + * @out: pointer to out state + * + * Return: true if output is enabled, false if output is disabled + */ +static inline bool zl3073x_out_is_enabled(const struct zl3073x_out *out) +{ + return !!FIELD_GET(ZL_OUTPUT_CTRL_EN, out->ctrl); +} + +/** + * zl3073x_out_synth_get - get synth connected to given output + * @out: pointer to out state + * + * Return: index of synth connected to given output. + */ +static inline u8 zl3073x_out_synth_get(const struct zl3073x_out *out) +{ + return FIELD_GET(ZL_OUTPUT_CTRL_SYNTH_SEL, out->ctrl); +} + +#endif /* _ZL3073X_OUT_H */ diff --git a/drivers/dpll/zl3073x/prop.c b/drivers/dpll/zl3073x/prop.c index 9e1fca5cdaf1ea..4ed153087570b4 100644 --- a/drivers/dpll/zl3073x/prop.c +++ b/drivers/dpll/zl3073x/prop.c @@ -46,10 +46,10 @@ zl3073x_pin_check_freq(struct zl3073x_dev *zldev, enum dpll_pin_direction dir, /* Get output pin synthesizer */ out = zl3073x_output_pin_out_get(id); - synth = zl3073x_out_synth_get(zldev, out); + synth = zl3073x_dev_out_synth_get(zldev, out); /* Get synth frequency */ - synth_freq = zl3073x_synth_freq_get(zldev, synth); + synth_freq = zl3073x_dev_synth_freq_get(zldev, synth); /* Check the frequency divides synth frequency */ if (synth_freq % (u32)freq) @@ -93,13 +93,13 @@ zl3073x_prop_pin_package_label_set(struct zl3073x_dev *zldev, prefix = "REF"; ref = zl3073x_input_pin_ref_get(id); - is_diff = zl3073x_ref_is_diff(zldev, ref); + is_diff = zl3073x_dev_ref_is_diff(zldev, ref); } else { u8 out; prefix = "OUT"; out = zl3073x_output_pin_out_get(id); - is_diff = zl3073x_out_is_diff(zldev, out); + is_diff = zl3073x_dev_out_is_diff(zldev, out); } if (!is_diff) @@ -217,8 +217,8 @@ struct zl3073x_pin_props *zl3073x_pin_props_get(struct zl3073x_dev *zldev, * the synth frequency count. */ out = zl3073x_output_pin_out_get(index); - synth = zl3073x_out_synth_get(zldev, out); - f = 2 * zl3073x_synth_freq_get(zldev, synth); + synth = zl3073x_dev_out_synth_get(zldev, out); + f = 2 * zl3073x_dev_synth_freq_get(zldev, synth); props->dpll_props.phase_gran = f ? div_u64(PSEC_PER_SEC, f) : 1; } diff --git a/drivers/dpll/zl3073x/ref.c b/drivers/dpll/zl3073x/ref.c new file mode 100644 index 00000000000000..6abd6288a02ad9 --- /dev/null +++ b/drivers/dpll/zl3073x/ref.c @@ -0,0 +1,112 @@ +// SPDX-License-Identifier: GPL-2.0-only + +#include +#include +#include +#include +#include +#include + +#include "core.h" +#include "ref.h" + +/** + * zl3073x_ref_freq_factorize - factorize given frequency + * @freq: input frequency + * @base: base frequency + * @mult: multiplier + * + * Checks if the given frequency can be factorized using one of the + * supported base frequencies. If so the base frequency and multiplier + * are stored into appropriate parameters if they are not NULL. + * + * Return: 0 on success, -EINVAL if the frequency cannot be factorized + */ +int +zl3073x_ref_freq_factorize(u32 freq, u16 *base, u16 *mult) +{ + static const u16 base_freqs[] = { + 1, 2, 4, 5, 8, 10, 16, 20, 25, 32, 40, 50, 64, 80, 100, 125, + 128, 160, 200, 250, 256, 320, 400, 500, 625, 640, 800, 1000, + 1250, 1280, 1600, 2000, 2500, 3125, 3200, 4000, 5000, 6250, + 6400, 8000, 10000, 12500, 15625, 16000, 20000, 25000, 31250, + 32000, 40000, 50000, 62500, + }; + u32 div; + int i; + + for (i = 0; i < ARRAY_SIZE(base_freqs); i++) { + div = freq / base_freqs[i]; + + if (div <= U16_MAX && (freq % base_freqs[i]) == 0) { + if (base) + *base = base_freqs[i]; + if (mult) + *mult = div; + + return 0; + } + } + + return -EINVAL; +} + +/** + * zl3073x_ref_state_fetch - fetch input reference state from hardware + * @zldev: pointer to zl3073x_dev structure + * @index: input reference index to fetch state for + * + * Function fetches state for the given input reference from hardware and + * stores it for later use. + * + * Return: 0 on success, <0 on error + */ +int zl3073x_ref_state_fetch(struct zl3073x_dev *zldev, u8 index) +{ + struct zl3073x_ref *ref = &zldev->ref[index]; + int rc; + + /* For differential type inputs the N-pin reference shares + * part of the configuration with the P-pin counterpart. + */ + if (zl3073x_is_n_pin(index) && zl3073x_ref_is_diff(ref - 1)) { + struct zl3073x_ref *p_ref = &zldev->ref[index - 1]; + + /* Copy the shared items from the P-pin */ + ref->config = p_ref->config; + + return 0; /* Finish - no non-shared items for now */ + } + + guard(mutex)(&zldev->multiop_lock); + + /* Read reference configuration */ + rc = zl3073x_mb_op(zldev, ZL_REG_REF_MB_SEM, ZL_REF_MB_SEM_RD, + ZL_REG_REF_MB_MASK, BIT(index)); + if (rc) + return rc; + + /* Read ref_config register */ + rc = zl3073x_read_u8(zldev, ZL_REG_REF_CONFIG, &ref->config); + if (rc) + return rc; + + dev_dbg(zldev->dev, "REF%u is %s and configured as %s\n", index, + str_enabled_disabled(zl3073x_ref_is_enabled(ref)), + zl3073x_ref_is_diff(ref) ? "differential" : "single-ended"); + + return rc; +} + +/** + * zl3073x_ref_state_get - get current input reference state + * @zldev: pointer to zl3073x_dev structure + * @index: input reference index to get state for + * + * Return: pointer to given input reference state + */ +const struct zl3073x_ref * +zl3073x_ref_state_get(struct zl3073x_dev *zldev, u8 index) +{ + return &zldev->ref[index]; +} diff --git a/drivers/dpll/zl3073x/ref.h b/drivers/dpll/zl3073x/ref.h new file mode 100644 index 00000000000000..e72f2c87508760 --- /dev/null +++ b/drivers/dpll/zl3073x/ref.h @@ -0,0 +1,66 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ + +#ifndef _ZL3073X_REF_H +#define _ZL3073X_REF_H + +#include +#include + +#include "regs.h" + +struct zl3073x_dev; + +/** + * struct zl3073x_ref - input reference state + * @ffo: current fractional frequency offset + * @config: reference config + */ +struct zl3073x_ref { + s64 ffo; + u8 config; +}; + +int zl3073x_ref_state_fetch(struct zl3073x_dev *zldev, u8 index); + +const struct zl3073x_ref *zl3073x_ref_state_get(struct zl3073x_dev *zldev, + u8 index); + +int zl3073x_ref_freq_factorize(u32 freq, u16 *base, u16 *mult); + +/** + * zl3073x_ref_ffo_get - get current fractional frequency offset + * @ref: pointer to ref state + * + * Return: the latest measured fractional frequency offset + */ +static inline s64 +zl3073x_ref_ffo_get(const struct zl3073x_ref *ref) +{ + return ref->ffo; +} + +/** + * zl3073x_ref_is_diff - check if the given input reference is differential + * @ref: pointer to ref state + * + * Return: true if reference is differential, false if reference is single-ended + */ +static inline bool +zl3073x_ref_is_diff(const struct zl3073x_ref *ref) +{ + return !!FIELD_GET(ZL_REF_CONFIG_DIFF_EN, ref->config); +} + +/** + * zl3073x_ref_is_enabled - check if the given input reference is enabled + * @ref: pointer to ref state + * + * Return: true if input refernce is enabled, false otherwise + */ +static inline bool +zl3073x_ref_is_enabled(const struct zl3073x_ref *ref) +{ + return !!FIELD_GET(ZL_REF_CONFIG_ENABLE, ref->config); +} + +#endif /* _ZL3073X_REF_H */ diff --git a/drivers/dpll/zl3073x/synth.c b/drivers/dpll/zl3073x/synth.c new file mode 100644 index 00000000000000..da839572dab26f --- /dev/null +++ b/drivers/dpll/zl3073x/synth.c @@ -0,0 +1,87 @@ +// SPDX-License-Identifier: GPL-2.0-only + +#include +#include +#include +#include +#include +#include + +#include "core.h" +#include "synth.h" + +/** + * zl3073x_synth_state_fetch - fetch synth state from hardware + * @zldev: pointer to zl3073x_dev structure + * @index: synth index to fetch state for + * + * Function fetches state of the given synthesizer from the hardware and + * stores it for later use. + * + * Return: 0 on success, <0 on error + */ +int zl3073x_synth_state_fetch(struct zl3073x_dev *zldev, u8 index) +{ + struct zl3073x_synth *synth = &zldev->synth[index]; + int rc; + + /* Read synth control register */ + rc = zl3073x_read_u8(zldev, ZL_REG_SYNTH_CTRL(index), &synth->ctrl); + if (rc) + return rc; + + guard(mutex)(&zldev->multiop_lock); + + /* Read synth configuration */ + rc = zl3073x_mb_op(zldev, ZL_REG_SYNTH_MB_SEM, ZL_SYNTH_MB_SEM_RD, + ZL_REG_SYNTH_MB_MASK, BIT(index)); + if (rc) + return rc; + + /* The output frequency is determined by the following formula: + * base * multiplier * numerator / denominator + * + * Read registers with these values + */ + rc = zl3073x_read_u16(zldev, ZL_REG_SYNTH_FREQ_BASE, &synth->freq_base); + if (rc) + return rc; + + rc = zl3073x_read_u32(zldev, ZL_REG_SYNTH_FREQ_MULT, &synth->freq_mult); + if (rc) + return rc; + + rc = zl3073x_read_u16(zldev, ZL_REG_SYNTH_FREQ_M, &synth->freq_m); + if (rc) + return rc; + + rc = zl3073x_read_u16(zldev, ZL_REG_SYNTH_FREQ_N, &synth->freq_n); + if (rc) + return rc; + + /* Check denominator for zero to avoid div by 0 */ + if (!synth->freq_n) { + dev_err(zldev->dev, + "Zero divisor for SYNTH%u retrieved from device\n", + index); + return -EINVAL; + } + + dev_dbg(zldev->dev, "SYNTH%u frequency: %u Hz\n", index, + zl3073x_synth_freq_get(synth)); + + return rc; +} + +/** + * zl3073x_synth_state_get - get current synth state + * @zldev: pointer to zl3073x_dev structure + * @index: synth index to get state for + * + * Return: pointer to given synth state + */ +const struct zl3073x_synth *zl3073x_synth_state_get(struct zl3073x_dev *zldev, + u8 index) +{ + return &zldev->synth[index]; +} diff --git a/drivers/dpll/zl3073x/synth.h b/drivers/dpll/zl3073x/synth.h new file mode 100644 index 00000000000000..6c55eb8a888c2c --- /dev/null +++ b/drivers/dpll/zl3073x/synth.h @@ -0,0 +1,72 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ + +#ifndef _ZL3073X_SYNTH_H +#define _ZL3073X_SYNTH_H + +#include +#include +#include + +#include "regs.h" + +struct zl3073x_dev; + +/** + * struct zl3073x_synth - synthesizer state + * @freq_mult: frequency multiplier + * @freq_base: frequency base + * @freq_m: frequency numerator + * @freq_n: frequency denominator + * @ctrl: synth control + */ +struct zl3073x_synth { + u32 freq_mult; + u16 freq_base; + u16 freq_m; + u16 freq_n; + u8 ctrl; +}; + +int zl3073x_synth_state_fetch(struct zl3073x_dev *zldev, u8 synth_id); + +const struct zl3073x_synth *zl3073x_synth_state_get(struct zl3073x_dev *zldev, + u8 synth_id); + +int zl3073x_synth_state_set(struct zl3073x_dev *zldev, u8 synth_id, + const struct zl3073x_synth *synth); + +/** + * zl3073x_synth_dpll_get - get DPLL ID the synth is driven by + * @synth: pointer to synth state + * + * Return: ID of DPLL the given synthetizer is driven by + */ +static inline u8 zl3073x_synth_dpll_get(const struct zl3073x_synth *synth) +{ + return FIELD_GET(ZL_SYNTH_CTRL_DPLL_SEL, synth->ctrl); +} + +/** + * zl3073x_synth_freq_get - get synth current freq + * @synth: pointer to synth state + * + * Return: frequency of given synthetizer + */ +static inline u32 zl3073x_synth_freq_get(const struct zl3073x_synth *synth) +{ + return mul_u64_u32_div(synth->freq_base * synth->freq_m, + synth->freq_mult, synth->freq_n); +} + +/** + * zl3073x_synth_is_enabled - check if the given synth is enabled + * @synth: pointer to synth state + * + * Return: true if synth is enabled, false otherwise + */ +static inline bool zl3073x_synth_is_enabled(const struct zl3073x_synth *synth) +{ + return FIELD_GET(ZL_SYNTH_CTRL_EN, synth->ctrl); +} + +#endif /* _ZL3073X_SYNTH_H */ From 072cec234a877e6292acda8a80425b13fee2c7d1 Mon Sep 17 00:00:00 2001 From: Ivan Vecera Date: Thu, 13 Nov 2025 08:41:04 +0100 Subject: [PATCH 3571/3902] dpll: zl3073x: Cache all output properties in zl3073x_out [ Upstream commit 5fb9b0d411f81ec46833ea8e43c0263515060c64 ] Expand the zl3073x_out structure to cache all output-related hardware registers, including divisors, widths, embedded-sync parameters and phase compensation. Modify zl3073x_out_state_fetch() to read and populate all these new fields at once, including zero-divisor checks. Refactor all dpll "getter" functions in dpll.c to read from this new cached state instead of performing direct register access. Introduce a new function, zl3073x_out_state_set(), to handle writing changes back to the hardware. This function compares the provided state with the current cached state and writes *only* the modified register values via a single mailbox sequence before updating the local cache. Refactor all dpll "setter" functions to modify a local copy of the output state and then call zl3073x_out_state_set() to commit the changes. This change centralizes all output-related register I/O into out.c, significantly reduces bus traffic, and simplifies the logic in dpll.c. Reviewed-by: Petr Oros Tested-by: Prathosh Satish Signed-off-by: Ivan Vecera Link: https://patch.msgid.link/20251113074105.141379-6-ivecera@redhat.com Signed-off-by: Jakub Kicinski Stable-dep-of: 5d41f95f5d0b ("dpll: zl3073x: Fix output pin phase adjustment sign") Signed-off-by: Sasha Levin --- drivers/dpll/zl3073x/dpll.c | 380 +++++++++--------------------------- drivers/dpll/zl3073x/out.c | 90 +++++++++ drivers/dpll/zl3073x/out.h | 13 ++ 3 files changed, 193 insertions(+), 290 deletions(-) diff --git a/drivers/dpll/zl3073x/dpll.c b/drivers/dpll/zl3073x/dpll.c index 62996f26e065fe..38551cf7884948 100644 --- a/drivers/dpll/zl3073x/dpll.c +++ b/drivers/dpll/zl3073x/dpll.c @@ -953,21 +953,19 @@ zl3073x_dpll_output_pin_esync_get(const struct dpll_pin *dpll_pin, struct zl3073x_dpll *zldpll = dpll_priv; struct zl3073x_dev *zldev = zldpll->dev; struct zl3073x_dpll_pin *pin = pin_priv; - struct device *dev = zldev->dev; - u32 esync_period, esync_width; - u8 clock_type, synth; - u8 out, output_mode; - u32 output_div; + const struct zl3073x_synth *synth; + const struct zl3073x_out *out; + u8 clock_type, out_id; u32 synth_freq; - int rc; - out = zl3073x_output_pin_out_get(pin->id); + out_id = zl3073x_output_pin_out_get(pin->id); + out = zl3073x_out_state_get(zldev, out_id); /* If N-division is enabled, esync is not supported. The register used * for N-division is also used for the esync divider so both cannot * be used. */ - switch (zl3073x_dev_out_signal_format_get(zldev, out)) { + switch (zl3073x_out_signal_format_get(out)) { case ZL_OUTPUT_MODE_SIGNAL_FORMAT_2_NDIV: case ZL_OUTPUT_MODE_SIGNAL_FORMAT_2_NDIV_INV: return -EOPNOTSUPP; @@ -975,38 +973,11 @@ zl3073x_dpll_output_pin_esync_get(const struct dpll_pin *dpll_pin, break; } - guard(mutex)(&zldev->multiop_lock); - - /* Read output configuration into mailbox */ - rc = zl3073x_mb_op(zldev, ZL_REG_OUTPUT_MB_SEM, ZL_OUTPUT_MB_SEM_RD, - ZL_REG_OUTPUT_MB_MASK, BIT(out)); - if (rc) - return rc; - - /* Read output mode */ - rc = zl3073x_read_u8(zldev, ZL_REG_OUTPUT_MODE, &output_mode); - if (rc) - return rc; - - /* Read output divisor */ - rc = zl3073x_read_u32(zldev, ZL_REG_OUTPUT_DIV, &output_div); - if (rc) - return rc; - - /* Check output divisor for zero */ - if (!output_div) { - dev_err(dev, "Zero divisor for OUTPUT%u got from device\n", - out); - return -EINVAL; - } - - /* Get synth attached to output pin */ - synth = zl3073x_dev_out_synth_get(zldev, out); - - /* Get synth frequency */ - synth_freq = zl3073x_dev_synth_freq_get(zldev, synth); + /* Get attached synth frequency */ + synth = zl3073x_synth_state_get(zldev, zl3073x_out_synth_get(out)); + synth_freq = zl3073x_synth_freq_get(synth); - clock_type = FIELD_GET(ZL_OUTPUT_MODE_CLOCK_TYPE, output_mode); + clock_type = FIELD_GET(ZL_OUTPUT_MODE_CLOCK_TYPE, out->mode); if (clock_type != ZL_OUTPUT_MODE_CLOCK_TYPE_ESYNC) { /* No need to read esync data if it is not enabled */ esync->freq = 0; @@ -1015,38 +986,21 @@ zl3073x_dpll_output_pin_esync_get(const struct dpll_pin *dpll_pin, goto finish; } - /* Read esync period */ - rc = zl3073x_read_u32(zldev, ZL_REG_OUTPUT_ESYNC_PERIOD, &esync_period); - if (rc) - return rc; - - /* Check esync divisor for zero */ - if (!esync_period) { - dev_err(dev, "Zero esync divisor for OUTPUT%u got from device\n", - out); - return -EINVAL; - } - - /* Get esync pulse width in units of half synth cycles */ - rc = zl3073x_read_u32(zldev, ZL_REG_OUTPUT_ESYNC_WIDTH, &esync_width); - if (rc) - return rc; - /* Compute esync frequency */ - esync->freq = synth_freq / output_div / esync_period; + esync->freq = synth_freq / out->div / out->esync_n_period; /* By comparing the esync_pulse_width to the half of the pulse width * the esync pulse percentage can be determined. * Note that half pulse width is in units of half synth cycles, which * is why it reduces down to be output_div. */ - esync->pulse = (50 * esync_width) / output_div; + esync->pulse = (50 * out->esync_n_width) / out->div; finish: /* Set supported esync ranges if the pin supports esync control and * if the output frequency is > 1 Hz. */ - if (pin->esync_control && (synth_freq / output_div) > 1) { + if (pin->esync_control && (synth_freq / out->div) > 1) { esync->range = esync_freq_ranges; esync->range_num = ARRAY_SIZE(esync_freq_ranges); } else { @@ -1064,21 +1018,22 @@ zl3073x_dpll_output_pin_esync_set(const struct dpll_pin *dpll_pin, void *dpll_priv, u64 freq, struct netlink_ext_ack *extack) { - u32 esync_period, esync_width, output_div; struct zl3073x_dpll *zldpll = dpll_priv; struct zl3073x_dev *zldev = zldpll->dev; struct zl3073x_dpll_pin *pin = pin_priv; - u8 clock_type, out, output_mode, synth; + const struct zl3073x_synth *synth; + struct zl3073x_out out; + u8 clock_type, out_id; u32 synth_freq; - int rc; - out = zl3073x_output_pin_out_get(pin->id); + out_id = zl3073x_output_pin_out_get(pin->id); + out = *zl3073x_out_state_get(zldev, out_id); /* If N-division is enabled, esync is not supported. The register used * for N-division is also used for the esync divider so both cannot * be used. */ - switch (zl3073x_dev_out_signal_format_get(zldev, out)) { + switch (zl3073x_out_signal_format_get(&out)) { case ZL_OUTPUT_MODE_SIGNAL_FORMAT_2_NDIV: case ZL_OUTPUT_MODE_SIGNAL_FORMAT_2_NDIV_INV: return -EOPNOTSUPP; @@ -1086,19 +1041,6 @@ zl3073x_dpll_output_pin_esync_set(const struct dpll_pin *dpll_pin, break; } - guard(mutex)(&zldev->multiop_lock); - - /* Read output configuration into mailbox */ - rc = zl3073x_mb_op(zldev, ZL_REG_OUTPUT_MB_SEM, ZL_OUTPUT_MB_SEM_RD, - ZL_REG_OUTPUT_MB_MASK, BIT(out)); - if (rc) - return rc; - - /* Read output mode */ - rc = zl3073x_read_u8(zldev, ZL_REG_OUTPUT_MODE, &output_mode); - if (rc) - return rc; - /* Select clock type */ if (freq) clock_type = ZL_OUTPUT_MODE_CLOCK_TYPE_ESYNC; @@ -1106,38 +1048,19 @@ zl3073x_dpll_output_pin_esync_set(const struct dpll_pin *dpll_pin, clock_type = ZL_OUTPUT_MODE_CLOCK_TYPE_NORMAL; /* Update clock type in output mode */ - output_mode &= ~ZL_OUTPUT_MODE_CLOCK_TYPE; - output_mode |= FIELD_PREP(ZL_OUTPUT_MODE_CLOCK_TYPE, clock_type); - rc = zl3073x_write_u8(zldev, ZL_REG_OUTPUT_MODE, output_mode); - if (rc) - return rc; + out.mode &= ~ZL_OUTPUT_MODE_CLOCK_TYPE; + out.mode |= FIELD_PREP(ZL_OUTPUT_MODE_CLOCK_TYPE, clock_type); /* If esync is being disabled just write mailbox and finish */ if (!freq) goto write_mailbox; - /* Get synth attached to output pin */ - synth = zl3073x_dev_out_synth_get(zldev, out); - - /* Get synth frequency */ - synth_freq = zl3073x_dev_synth_freq_get(zldev, synth); - - rc = zl3073x_read_u32(zldev, ZL_REG_OUTPUT_DIV, &output_div); - if (rc) - return rc; - - /* Check output divisor for zero */ - if (!output_div) { - dev_err(zldev->dev, - "Zero divisor for OUTPUT%u got from device\n", out); - return -EINVAL; - } + /* Get attached synth frequency */ + synth = zl3073x_synth_state_get(zldev, zl3073x_out_synth_get(&out)); + synth_freq = zl3073x_synth_freq_get(synth); /* Compute and update esync period */ - esync_period = synth_freq / (u32)freq / output_div; - rc = zl3073x_write_u32(zldev, ZL_REG_OUTPUT_ESYNC_PERIOD, esync_period); - if (rc) - return rc; + out.esync_n_period = synth_freq / (u32)freq / out.div; /* Half of the period in units of 1/2 synth cycle can be represented by * the output_div. To get the supported esync pulse width of 25% of the @@ -1145,15 +1068,11 @@ zl3073x_dpll_output_pin_esync_set(const struct dpll_pin *dpll_pin, * assumes that output_div is even, otherwise some resolution will be * lost. */ - esync_width = output_div / 2; - rc = zl3073x_write_u32(zldev, ZL_REG_OUTPUT_ESYNC_WIDTH, esync_width); - if (rc) - return rc; + out.esync_n_width = out.div / 2; write_mailbox: /* Commit output configuration */ - return zl3073x_mb_op(zldev, ZL_REG_OUTPUT_MB_SEM, ZL_OUTPUT_MB_SEM_WR, - ZL_REG_OUTPUT_MB_MASK, BIT(out)); + return zl3073x_out_state_set(zldev, out_id, &out); } static int @@ -1166,83 +1085,46 @@ zl3073x_dpll_output_pin_frequency_get(const struct dpll_pin *dpll_pin, struct zl3073x_dpll *zldpll = dpll_priv; struct zl3073x_dev *zldev = zldpll->dev; struct zl3073x_dpll_pin *pin = pin_priv; - struct device *dev = zldev->dev; - u8 out, signal_format, synth; - u32 output_div, synth_freq; - int rc; - - out = zl3073x_output_pin_out_get(pin->id); - synth = zl3073x_dev_out_synth_get(zldev, out); - synth_freq = zl3073x_dev_synth_freq_get(zldev, synth); - - guard(mutex)(&zldev->multiop_lock); - - /* Read output configuration into mailbox */ - rc = zl3073x_mb_op(zldev, ZL_REG_OUTPUT_MB_SEM, ZL_OUTPUT_MB_SEM_RD, - ZL_REG_OUTPUT_MB_MASK, BIT(out)); - if (rc) - return rc; - - rc = zl3073x_read_u32(zldev, ZL_REG_OUTPUT_DIV, &output_div); - if (rc) - return rc; + const struct zl3073x_synth *synth; + const struct zl3073x_out *out; + u32 synth_freq; + u8 out_id; - /* Check output divisor for zero */ - if (!output_div) { - dev_err(dev, "Zero divisor for output %u got from device\n", - out); - return -EINVAL; - } + out_id = zl3073x_output_pin_out_get(pin->id); + out = zl3073x_out_state_get(zldev, out_id); - /* Read used signal format for the given output */ - signal_format = zl3073x_dev_out_signal_format_get(zldev, out); + /* Get attached synth frequency */ + synth = zl3073x_synth_state_get(zldev, zl3073x_out_synth_get(out)); + synth_freq = zl3073x_synth_freq_get(synth); - switch (signal_format) { + switch (zl3073x_out_signal_format_get(out)) { case ZL_OUTPUT_MODE_SIGNAL_FORMAT_2_NDIV: case ZL_OUTPUT_MODE_SIGNAL_FORMAT_2_NDIV_INV: /* In case of divided format we have to distiguish between * given output pin type. + * + * For P-pin the resulting frequency is computed as simple + * division of synth frequency and output divisor. + * + * For N-pin we have to divide additionally by divisor stored + * in esync_n_period output mailbox register that is used as + * N-pin divisor for these modes. */ - if (zl3073x_dpll_is_p_pin(pin)) { - /* For P-pin the resulting frequency is computed as - * simple division of synth frequency and output - * divisor. - */ - *frequency = synth_freq / output_div; - } else { - /* For N-pin we have to divide additionally by - * divisor stored in esync_period output mailbox - * register that is used as N-pin divisor for these - * modes. - */ - u32 ndiv; - - rc = zl3073x_read_u32(zldev, ZL_REG_OUTPUT_ESYNC_PERIOD, - &ndiv); - if (rc) - return rc; + *frequency = synth_freq / out->div; - /* Check N-pin divisor for zero */ - if (!ndiv) { - dev_err(dev, - "Zero N-pin divisor for output %u got from device\n", - out); - return -EINVAL; - } + if (!zl3073x_dpll_is_p_pin(pin)) + *frequency = (u32)*frequency / out->esync_n_period; - /* Compute final divisor for N-pin */ - *frequency = synth_freq / output_div / ndiv; - } break; default: /* In other modes the resulting frequency is computed as * division of synth frequency and output divisor. */ - *frequency = synth_freq / output_div; + *frequency = synth_freq / out->div; break; } - return rc; + return 0; } static int @@ -1255,28 +1137,21 @@ zl3073x_dpll_output_pin_frequency_set(const struct dpll_pin *dpll_pin, struct zl3073x_dpll *zldpll = dpll_priv; struct zl3073x_dev *zldev = zldpll->dev; struct zl3073x_dpll_pin *pin = pin_priv; - struct device *dev = zldev->dev; - u32 output_n_freq, output_p_freq; - u8 out, signal_format, synth; - u32 cur_div, new_div, ndiv; - u32 synth_freq; - int rc; + const struct zl3073x_synth *synth; + u8 out_id, signal_format; + u32 new_div, synth_freq; + struct zl3073x_out out; - out = zl3073x_output_pin_out_get(pin->id); - synth = zl3073x_dev_out_synth_get(zldev, out); - synth_freq = zl3073x_dev_synth_freq_get(zldev, synth); + out_id = zl3073x_output_pin_out_get(pin->id); + out = *zl3073x_out_state_get(zldev, out_id); + + /* Get attached synth frequency and compute new divisor */ + synth = zl3073x_synth_state_get(zldev, zl3073x_out_synth_get(&out)); + synth_freq = zl3073x_synth_freq_get(synth); new_div = synth_freq / (u32)frequency; /* Get used signal format for the given output */ - signal_format = zl3073x_dev_out_signal_format_get(zldev, out); - - guard(mutex)(&zldev->multiop_lock); - - /* Load output configuration */ - rc = zl3073x_mb_op(zldev, ZL_REG_OUTPUT_MB_SEM, ZL_OUTPUT_MB_SEM_RD, - ZL_REG_OUTPUT_MB_MASK, BIT(out)); - if (rc) - return rc; + signal_format = zl3073x_out_signal_format_get(&out); /* Check signal format */ if (signal_format != ZL_OUTPUT_MODE_SIGNAL_FORMAT_2_NDIV && @@ -1284,99 +1159,50 @@ zl3073x_dpll_output_pin_frequency_set(const struct dpll_pin *dpll_pin, /* For non N-divided signal formats the frequency is computed * as division of synth frequency and output divisor. */ - rc = zl3073x_write_u32(zldev, ZL_REG_OUTPUT_DIV, new_div); - if (rc) - return rc; + out.div = new_div; /* For 50/50 duty cycle the divisor is equal to width */ - rc = zl3073x_write_u32(zldev, ZL_REG_OUTPUT_WIDTH, new_div); - if (rc) - return rc; + out.width = new_div; /* Commit output configuration */ - return zl3073x_mb_op(zldev, - ZL_REG_OUTPUT_MB_SEM, ZL_OUTPUT_MB_SEM_WR, - ZL_REG_OUTPUT_MB_MASK, BIT(out)); + return zl3073x_out_state_set(zldev, out_id, &out); } - /* For N-divided signal format get current divisor */ - rc = zl3073x_read_u32(zldev, ZL_REG_OUTPUT_DIV, &cur_div); - if (rc) - return rc; - - /* Check output divisor for zero */ - if (!cur_div) { - dev_err(dev, "Zero divisor for output %u got from device\n", - out); - return -EINVAL; - } - - /* Get N-pin divisor (shares the same register with esync */ - rc = zl3073x_read_u32(zldev, ZL_REG_OUTPUT_ESYNC_PERIOD, &ndiv); - if (rc) - return rc; - - /* Check N-pin divisor for zero */ - if (!ndiv) { - dev_err(dev, - "Zero N-pin divisor for output %u got from device\n", - out); - return -EINVAL; - } - - /* Compute current output frequency for P-pin */ - output_p_freq = synth_freq / cur_div; - - /* Compute current N-pin frequency */ - output_n_freq = output_p_freq / ndiv; - if (zl3073x_dpll_is_p_pin(pin)) { /* We are going to change output frequency for P-pin but * if the requested frequency is less than current N-pin * frequency then indicate a failure as we are not able * to compute N-pin divisor to keep its frequency unchanged. + * + * Update divisor for N-pin to keep N-pin frequency. */ - if (frequency <= output_n_freq) + out.esync_n_period = (out.esync_n_period * out.div) / new_div; + if (!out.esync_n_period) return -EINVAL; /* Update the output divisor */ - rc = zl3073x_write_u32(zldev, ZL_REG_OUTPUT_DIV, new_div); - if (rc) - return rc; + out.div = new_div; /* For 50/50 duty cycle the divisor is equal to width */ - rc = zl3073x_write_u32(zldev, ZL_REG_OUTPUT_WIDTH, new_div); - if (rc) - return rc; - - /* Compute new divisor for N-pin */ - ndiv = (u32)frequency / output_n_freq; + out.width = out.div; } else { /* We are going to change frequency of N-pin but if * the requested freq is greater or equal than freq of P-pin * in the output pair we cannot compute divisor for the N-pin. * In this case indicate a failure. + * + * Update divisor for N-pin */ - if (output_p_freq <= frequency) + out.esync_n_period = div64_u64(synth_freq, frequency * out.div); + if (!out.esync_n_period) return -EINVAL; - - /* Compute new divisor for N-pin */ - ndiv = output_p_freq / (u32)frequency; } - /* Update divisor for the N-pin */ - rc = zl3073x_write_u32(zldev, ZL_REG_OUTPUT_ESYNC_PERIOD, ndiv); - if (rc) - return rc; - /* For 50/50 duty cycle the divisor is equal to width */ - rc = zl3073x_write_u32(zldev, ZL_REG_OUTPUT_ESYNC_WIDTH, ndiv); - if (rc) - return rc; + out.esync_n_width = out.esync_n_period; /* Commit output configuration */ - return zl3073x_mb_op(zldev, ZL_REG_OUTPUT_MB_SEM, ZL_OUTPUT_MB_SEM_WR, - ZL_REG_OUTPUT_MB_MASK, BIT(out)); + return zl3073x_out_state_set(zldev, out_id, &out); } static int @@ -1390,30 +1216,18 @@ zl3073x_dpll_output_pin_phase_adjust_get(const struct dpll_pin *dpll_pin, struct zl3073x_dpll *zldpll = dpll_priv; struct zl3073x_dev *zldev = zldpll->dev; struct zl3073x_dpll_pin *pin = pin_priv; - s32 phase_comp; - u8 out; - int rc; - - guard(mutex)(&zldev->multiop_lock); - - /* Read output configuration */ - out = zl3073x_output_pin_out_get(pin->id); - rc = zl3073x_mb_op(zldev, ZL_REG_OUTPUT_MB_SEM, ZL_OUTPUT_MB_SEM_RD, - ZL_REG_OUTPUT_MB_MASK, BIT(out)); - if (rc) - return rc; + const struct zl3073x_out *out; + u8 out_id; - /* Read current output phase compensation */ - rc = zl3073x_read_u32(zldev, ZL_REG_OUTPUT_PHASE_COMP, &phase_comp); - if (rc) - return rc; + out_id = zl3073x_output_pin_out_get(pin->id); + out = zl3073x_out_state_get(zldev, out_id); /* Convert value to ps and reverse two's complement negation applied * during 'set' */ - *phase_adjust = -phase_comp * pin->phase_gran; + *phase_adjust = -out->phase_comp * pin->phase_gran; - return rc; + return 0; } static int @@ -1427,31 +1241,19 @@ zl3073x_dpll_output_pin_phase_adjust_set(const struct dpll_pin *dpll_pin, struct zl3073x_dpll *zldpll = dpll_priv; struct zl3073x_dev *zldev = zldpll->dev; struct zl3073x_dpll_pin *pin = pin_priv; - u8 out; - int rc; + struct zl3073x_out out; + u8 out_id; + + out_id = zl3073x_output_pin_out_get(pin->id); + out = *zl3073x_out_state_get(zldev, out_id); /* The value in the register is stored as two's complement negation * of requested value and expressed in half synth clock cycles. */ - phase_adjust = -phase_adjust / pin->phase_gran; - - guard(mutex)(&zldev->multiop_lock); - - /* Read output configuration */ - out = zl3073x_output_pin_out_get(pin->id); - rc = zl3073x_mb_op(zldev, ZL_REG_OUTPUT_MB_SEM, ZL_OUTPUT_MB_SEM_RD, - ZL_REG_OUTPUT_MB_MASK, BIT(out)); - if (rc) - return rc; - - /* Write the requested value into the compensation register */ - rc = zl3073x_write_u32(zldev, ZL_REG_OUTPUT_PHASE_COMP, phase_adjust); - if (rc) - return rc; + out.phase_comp = -phase_adjust / pin->phase_gran; /* Update output configuration from mailbox */ - return zl3073x_mb_op(zldev, ZL_REG_OUTPUT_MB_SEM, ZL_OUTPUT_MB_SEM_WR, - ZL_REG_OUTPUT_MB_MASK, BIT(out)); + return zl3073x_out_state_set(zldev, out_id, &out); } static int @@ -1862,17 +1664,15 @@ zl3073x_dpll_pin_is_registrable(struct zl3073x_dpll *zldpll, /* Output P&N pair shares single HW output */ u8 out = zl3073x_output_pin_out_get(index); - name = "OUT"; - /* Skip the pin if it is connected to different DPLL channel */ if (zl3073x_dev_out_dpll_get(zldev, out) != zldpll->id) { dev_dbg(zldev->dev, - "%s%u is driven by different DPLL\n", name, - out); + "OUT%u is driven by different DPLL\n", out); return false; } + name = "OUT"; is_diff = zl3073x_dev_out_is_diff(zldev, out); is_enabled = zl3073x_dev_output_pin_is_enabled(zldev, index); } diff --git a/drivers/dpll/zl3073x/out.c b/drivers/dpll/zl3073x/out.c index a48f6917b39fb7..86829a0c1c022f 100644 --- a/drivers/dpll/zl3073x/out.c +++ b/drivers/dpll/zl3073x/out.c @@ -50,6 +50,46 @@ int zl3073x_out_state_fetch(struct zl3073x_dev *zldev, u8 index) dev_dbg(zldev->dev, "OUT%u has signal format 0x%02x\n", index, zl3073x_out_signal_format_get(out)); + /* Read output divisor */ + rc = zl3073x_read_u32(zldev, ZL_REG_OUTPUT_DIV, &out->div); + if (rc) + return rc; + + if (!out->div) { + dev_err(zldev->dev, "Zero divisor for OUT%u got from device\n", + index); + return -EINVAL; + } + + dev_dbg(zldev->dev, "OUT%u divisor: %u\n", index, out->div); + + /* Read output width */ + rc = zl3073x_read_u32(zldev, ZL_REG_OUTPUT_WIDTH, &out->width); + if (rc) + return rc; + + rc = zl3073x_read_u32(zldev, ZL_REG_OUTPUT_ESYNC_PERIOD, + &out->esync_n_period); + if (rc) + return rc; + + if (!out->esync_n_period) { + dev_err(zldev->dev, + "Zero esync divisor for OUT%u got from device\n", + index); + return -EINVAL; + } + + rc = zl3073x_read_u32(zldev, ZL_REG_OUTPUT_ESYNC_WIDTH, + &out->esync_n_width); + if (rc) + return rc; + + rc = zl3073x_read_u32(zldev, ZL_REG_OUTPUT_PHASE_COMP, + &out->phase_comp); + if (rc) + return rc; + return rc; } @@ -65,3 +105,53 @@ const struct zl3073x_out *zl3073x_out_state_get(struct zl3073x_dev *zldev, { return &zldev->out[index]; } + +int zl3073x_out_state_set(struct zl3073x_dev *zldev, u8 index, + const struct zl3073x_out *out) +{ + struct zl3073x_out *dout = &zldev->out[index]; + int rc; + + guard(mutex)(&zldev->multiop_lock); + + /* Read output configuration into mailbox */ + rc = zl3073x_mb_op(zldev, ZL_REG_OUTPUT_MB_SEM, ZL_OUTPUT_MB_SEM_RD, + ZL_REG_OUTPUT_MB_MASK, BIT(index)); + if (rc) + return rc; + + /* Update mailbox with changed values */ + if (dout->div != out->div) + rc = zl3073x_write_u32(zldev, ZL_REG_OUTPUT_DIV, out->div); + if (!rc && dout->width != out->width) + rc = zl3073x_write_u32(zldev, ZL_REG_OUTPUT_WIDTH, out->width); + if (!rc && dout->esync_n_period != out->esync_n_period) + rc = zl3073x_write_u32(zldev, ZL_REG_OUTPUT_ESYNC_PERIOD, + out->esync_n_period); + if (!rc && dout->esync_n_width != out->esync_n_width) + rc = zl3073x_write_u32(zldev, ZL_REG_OUTPUT_ESYNC_WIDTH, + out->esync_n_width); + if (!rc && dout->mode != out->mode) + rc = zl3073x_write_u8(zldev, ZL_REG_OUTPUT_MODE, out->mode); + if (!rc && dout->phase_comp != out->phase_comp) + rc = zl3073x_write_u32(zldev, ZL_REG_OUTPUT_PHASE_COMP, + out->phase_comp); + if (rc) + return rc; + + /* Commit output configuration */ + rc = zl3073x_mb_op(zldev, ZL_REG_OUTPUT_MB_SEM, ZL_OUTPUT_MB_SEM_WR, + ZL_REG_OUTPUT_MB_MASK, BIT(index)); + if (rc) + return rc; + + /* After successful commit store new state */ + dout->div = out->div; + dout->width = out->width; + dout->esync_n_period = out->esync_n_period; + dout->esync_n_width = out->esync_n_width; + dout->mode = out->mode; + dout->phase_comp = out->phase_comp; + + return 0; +} diff --git a/drivers/dpll/zl3073x/out.h b/drivers/dpll/zl3073x/out.h index 986aa046221da4..e8ea7a0e0f071c 100644 --- a/drivers/dpll/zl3073x/out.h +++ b/drivers/dpll/zl3073x/out.h @@ -12,10 +12,20 @@ struct zl3073x_dev; /** * struct zl3073x_out - output state + * @div: output divisor + * @width: output pulse width + * @esync_n_period: embedded sync or n-pin period (for n-div formats) + * @esync_n_width: embedded sync or n-pin pulse width + * @phase_comp: phase compensation * @ctrl: output control * @mode: output mode */ struct zl3073x_out { + u32 div; + u32 width; + u32 esync_n_period; + u32 esync_n_width; + s32 phase_comp; u8 ctrl; u8 mode; }; @@ -24,6 +34,9 @@ int zl3073x_out_state_fetch(struct zl3073x_dev *zldev, u8 index); const struct zl3073x_out *zl3073x_out_state_get(struct zl3073x_dev *zldev, u8 index); +int zl3073x_out_state_set(struct zl3073x_dev *zldev, u8 index, + const struct zl3073x_out *out); + /** * zl3073x_out_signal_format_get - get output signal format * @out: pointer to out state From 9a4d6c37cb1280d96ebbb043ec7ef9ce7eecc0dd Mon Sep 17 00:00:00 2001 From: Ivan Vecera Date: Thu, 5 Feb 2026 19:10:55 +0100 Subject: [PATCH 3572/3902] dpll: zl3073x: Fix output pin phase adjustment sign [ Upstream commit 5d41f95f5d0bd9db02f3f16a649d0631f71e9fdb ] The output pin phase adjustment functions incorrectly negate the phase compensation value. Per the ZL3073x datasheet, the output phase compensation register is simply a signed two's complement integer where: - Positive values move the phase later in time - Negative values move the phase earlier in time No negation is required. The erroneous negation caused phase adjustments to be applied in the wrong direction. Note that input pin phase adjustment correctly uses negation because the hardware has an inverted convention for input references (positive moves phase earlier, negative moves phase later). Fixes: 6287262f761e ("dpll: zl3073x: Add support to adjust phase") Signed-off-by: Ivan Vecera Reviewed-by: Vadim Fedorenko Link: https://patch.msgid.link/20260205181055.129768-1-ivecera@redhat.com Signed-off-by: Jakub Kicinski Signed-off-by: Sasha Levin --- drivers/dpll/zl3073x/dpll.c | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/drivers/dpll/zl3073x/dpll.c b/drivers/dpll/zl3073x/dpll.c index 38551cf7884948..11ca32e1bb8292 100644 --- a/drivers/dpll/zl3073x/dpll.c +++ b/drivers/dpll/zl3073x/dpll.c @@ -1222,10 +1222,8 @@ zl3073x_dpll_output_pin_phase_adjust_get(const struct dpll_pin *dpll_pin, out_id = zl3073x_output_pin_out_get(pin->id); out = zl3073x_out_state_get(zldev, out_id); - /* Convert value to ps and reverse two's complement negation applied - * during 'set' - */ - *phase_adjust = -out->phase_comp * pin->phase_gran; + /* The value in the register is expressed in half synth clock cycles. */ + *phase_adjust = out->phase_comp * pin->phase_gran; return 0; } @@ -1247,10 +1245,8 @@ zl3073x_dpll_output_pin_phase_adjust_set(const struct dpll_pin *dpll_pin, out_id = zl3073x_output_pin_out_get(pin->id); out = *zl3073x_out_state_get(zldev, out_id); - /* The value in the register is stored as two's complement negation - * of requested value and expressed in half synth clock cycles. - */ - out.phase_comp = -phase_adjust / pin->phase_gran; + /* The value in the register is expressed in half synth clock cycles. */ + out.phase_comp = phase_adjust / pin->phase_gran; /* Update output configuration from mailbox */ return zl3073x_out_state_set(zldev, out_id, &out); From fdbccddb7e7822016601829f95de4008e193f7bc Mon Sep 17 00:00:00 2001 From: Jian Shen Date: Thu, 5 Feb 2026 20:17:19 +0800 Subject: [PATCH 3573/3902] net: hns3: fix double free issue for tx spare buffer [ Upstream commit 6d2f142b1e4b203387a92519d9d2e34752a79dbb ] In hns3_set_ringparam(), a temporary copy (tmp_rings) of the ring structure is created for rollback. However, the tx_spare pointer in the original ring handle is incorrectly left pointing to the old backup memory. Later, if memory allocation fails in hns3_init_all_ring() during the setup, the error path attempts to free all newly allocated rings. Since tx_spare contains a stale (non-NULL) pointer from the backup, it is mistaken for a newly allocated buffer and is erroneously freed, leading to a double-free of the backup memory. The root cause is that the tx_spare field was not cleared after its value was saved in tmp_rings, leaving a dangling pointer. Fix this by setting tx_spare to NULL in the original ring structure when the creation of the new `tx_spare` fails. This ensures the error cleanup path only frees genuinely newly allocated buffers. Fixes: 907676b130711 ("net: hns3: use tx bounce buffer for small packets") Signed-off-by: Jian Shen Signed-off-by: Jijie Shao Reviewed-by: Jacob Keller Link: https://patch.msgid.link/20260205121719.3285730-1-shaojijie@huawei.com Signed-off-by: Jakub Kicinski Signed-off-by: Sasha Levin --- drivers/net/ethernet/hisilicon/hns3/hns3_enet.c | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/drivers/net/ethernet/hisilicon/hns3/hns3_enet.c b/drivers/net/ethernet/hisilicon/hns3/hns3_enet.c index e976a88b952f01..c8eba180250e75 100644 --- a/drivers/net/ethernet/hisilicon/hns3/hns3_enet.c +++ b/drivers/net/ethernet/hisilicon/hns3/hns3_enet.c @@ -1048,13 +1048,13 @@ static void hns3_init_tx_spare_buffer(struct hns3_enet_ring *ring) int order; if (!alloc_size) - return; + goto not_init; order = get_order(alloc_size); if (order > MAX_PAGE_ORDER) { if (net_ratelimit()) dev_warn(ring_to_dev(ring), "failed to allocate tx spare buffer, exceed to max order\n"); - return; + goto not_init; } tx_spare = devm_kzalloc(ring_to_dev(ring), sizeof(*tx_spare), @@ -1092,6 +1092,13 @@ static void hns3_init_tx_spare_buffer(struct hns3_enet_ring *ring) devm_kfree(ring_to_dev(ring), tx_spare); devm_kzalloc_error: ring->tqp->handle->kinfo.tx_spare_buf_size = 0; +not_init: + /* When driver init or reset_init, the ring->tx_spare is always NULL; + * but when called from hns3_set_ringparam, it's usually not NULL, and + * will be restored if hns3_init_all_ring() failed. So it's safe to set + * ring->tx_spare to NULL here. + */ + ring->tx_spare = NULL; } /* Use hns3_tx_spare_space() to make sure there is enough buffer From 4f9ae386861e280b7631ca252f798d25575627ee Mon Sep 17 00:00:00 2001 From: Jinliang Zheng Date: Wed, 28 Jan 2026 16:30:07 +0800 Subject: [PATCH 3574/3902] procfs: fix missing RCU protection when reading real_parent in do_task_stat() [ Upstream commit 76149d53502cf17ef3ae454ff384551236fba867 ] When reading /proc/[pid]/stat, do_task_stat() accesses task->real_parent without proper RCU protection, which leads to: cpu 0 cpu 1 ----- ----- do_task_stat var = task->real_parent release_task call_rcu(delayed_put_task_struct) task_tgid_nr_ns(var) rcu_read_lock <--- Too late to protect task->real_parent! task_pid_ptr <--- UAF! rcu_read_unlock This patch uses task_ppid_nr_ns() instead of task_tgid_nr_ns() to add proper RCU protection for accessing task->real_parent. Link: https://lkml.kernel.org/r/20260128083007.3173016-1-alexjlzheng@tencent.com Fixes: 06fffb1267c9 ("do_task_stat: don't take rcu_read_lock()") Signed-off-by: Jinliang Zheng Acked-by: Oleg Nesterov Cc: David Hildenbrand Cc: Ingo Molnar Cc: Lorenzo Stoakes Cc: Mateusz Guzik Cc: ruippan Cc: Usama Arif Signed-off-by: Andrew Morton Signed-off-by: Sasha Levin --- fs/proc/array.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fs/proc/array.c b/fs/proc/array.c index 2ae63189091e02..038d4b57127fef 100644 --- a/fs/proc/array.c +++ b/fs/proc/array.c @@ -529,7 +529,7 @@ static int do_task_stat(struct seq_file *m, struct pid_namespace *ns, } sid = task_session_nr_ns(task, ns); - ppid = task_tgid_nr_ns(task->real_parent, ns); + ppid = task_ppid_nr_ns(task, ns); pgid = task_pgrp_nr_ns(task, ns); unlock_task_sighand(task, &flags); From 03462247e3b655501ff8ea2c5c298a0ae08032a0 Mon Sep 17 00:00:00 2001 From: Stefan Metzmacher Date: Thu, 5 Feb 2026 17:14:14 +0100 Subject: [PATCH 3575/3902] smb: client: correct value for smbd_max_fragmented_recv_size [ Upstream commit 4a93d1ee2d0206970b6eb13fbffe07938cd95948 ] When we download a file without rdma offload or get a large directly enumeration from the server, the server might want to send up to smbd_max_fragmented_recv_size bytes, but if it is too large all our recv buffers might already be moved to the recv_io.reassembly.list and we're no longer able to grant recv credits. The maximum fragmented upper-layer payload receive size supported Assume max_payload_per_credit is smbd_max_receive_size - 24 = 1340 The maximum number would be smbd_receive_credit_max * max_payload_per_credit 1340 * 255 = 341700 (0x536C4) The minimum value from the spec is 131072 (0x20000) For now we use the logic we used in ksmbd before: (1364 * 255) / 2 = 173910 (0x2A756) Fixes: 03bee01d6215 ("CIFS: SMBD: Add SMB Direct protocol initial values and constants") Cc: Steve French Cc: Tom Talpey Cc: Long Li Cc: Namjae Jeon Cc: linux-cifs@vger.kernel.org Cc: samba-technical@lists.samba.org Signed-off-by: Stefan Metzmacher Signed-off-by: Steve French Signed-off-by: Sasha Levin --- fs/smb/client/smbdirect.c | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/fs/smb/client/smbdirect.c b/fs/smb/client/smbdirect.c index 01d55bcc6d0f9c..c8cef098d48065 100644 --- a/fs/smb/client/smbdirect.c +++ b/fs/smb/client/smbdirect.c @@ -101,8 +101,23 @@ int smbd_send_credit_target = 255; /* The maximum single message size can be sent to remote peer */ int smbd_max_send_size = 1364; -/* The maximum fragmented upper-layer payload receive size supported */ -int smbd_max_fragmented_recv_size = 1024 * 1024; +/* + * The maximum fragmented upper-layer payload receive size supported + * + * Assume max_payload_per_credit is + * smbd_max_receive_size - 24 = 1340 + * + * The maximum number would be + * smbd_receive_credit_max * max_payload_per_credit + * + * 1340 * 255 = 341700 (0x536C4) + * + * The minimum value from the spec is 131072 (0x20000) + * + * For now we use the logic we used in ksmbd before: + * (1364 * 255) / 2 = 173910 (0x2A756) + */ +int smbd_max_fragmented_recv_size = (1364 * 255) / 2; /* The maximum single-message size which can be received */ int smbd_max_receive_size = 1364; From 69d3f9ee5489e6e8b66defcfa226e91d82393297 Mon Sep 17 00:00:00 2001 From: Jiayuan Chen Date: Thu, 5 Feb 2026 17:54:51 +0800 Subject: [PATCH 3576/3902] net: atm: fix crash due to unvalidated vcc pointer in sigd_send() [ Upstream commit ae88a5d2f29b69819dc7b04086734439d074a643 ] Reproducer available at [1]. The ATM send path (sendmsg -> vcc_sendmsg -> sigd_send) reads the vcc pointer from msg->vcc and uses it directly without any validation. This pointer comes from userspace via sendmsg() and can be arbitrarily forged: int fd = socket(AF_ATMSVC, SOCK_DGRAM, 0); ioctl(fd, ATMSIGD_CTRL); // become ATM signaling daemon struct msghdr msg = { .msg_iov = &iov, ... }; *(unsigned long *)(buf + 4) = 0xdeadbeef; // fake vcc pointer sendmsg(fd, &msg, 0); // kernel dereferences 0xdeadbeef In normal operation, the kernel sends the vcc pointer to the signaling daemon via sigd_enq() when processing operations like connect(), bind(), or listen(). The daemon is expected to return the same pointer when responding. However, a malicious daemon can send arbitrary pointer values. Fix this by introducing find_get_vcc() which validates the pointer by searching through vcc_hash (similar to how sigd_close() iterates over all VCCs), and acquires a reference via sock_hold() if found. Since struct atm_vcc embeds struct sock as its first member, they share the same lifetime. Therefore using sock_hold/sock_put is sufficient to keep the vcc alive while it is being used. Note that there may be a race with sigd_close() which could mark the vcc with various flags (e.g., ATM_VF_RELEASED) after find_get_vcc() returns. However, sock_hold() guarantees the memory remains valid, so this race only affects the logical state, not memory safety. [1]: https://gist.github.com/mrpre/1ba5949c45529c511152e2f4c755b0f3 Fixes: 1da177e4c3f4 ("Linux-2.6.12-rc2") Reported-by: syzbot+1f22cb1769f249df9fa0@syzkaller.appspotmail.com Closes: https://lore.kernel.org/all/69039850.a70a0220.5b2ed.005d.GAE@google.com/T/ Signed-off-by: Jiayuan Chen Link: https://patch.msgid.link/20260205095501.131890-1-jiayuan.chen@linux.dev Signed-off-by: Paolo Abeni Signed-off-by: Sasha Levin --- net/atm/signaling.c | 56 +++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 54 insertions(+), 2 deletions(-) diff --git a/net/atm/signaling.c b/net/atm/signaling.c index e70ae2c113f954..358fbe5e4d1d06 100644 --- a/net/atm/signaling.c +++ b/net/atm/signaling.c @@ -22,6 +22,36 @@ struct atm_vcc *sigd = NULL; +/* + * find_get_vcc - validate and get a reference to a vcc pointer + * @vcc: the vcc pointer to validate + * + * This function validates that @vcc points to a registered VCC in vcc_hash. + * If found, it increments the socket reference count and returns the vcc. + * The caller must call sock_put(sk_atm(vcc)) when done. + * + * Returns the vcc pointer if valid, NULL otherwise. + */ +static struct atm_vcc *find_get_vcc(struct atm_vcc *vcc) +{ + int i; + + read_lock(&vcc_sklist_lock); + for (i = 0; i < VCC_HTABLE_SIZE; i++) { + struct sock *s; + + sk_for_each(s, &vcc_hash[i]) { + if (atm_sk(s) == vcc) { + sock_hold(s); + read_unlock(&vcc_sklist_lock); + return vcc; + } + } + } + read_unlock(&vcc_sklist_lock); + return NULL; +} + static void sigd_put_skb(struct sk_buff *skb) { if (!sigd) { @@ -69,7 +99,14 @@ static int sigd_send(struct atm_vcc *vcc, struct sk_buff *skb) msg = (struct atmsvc_msg *) skb->data; WARN_ON(refcount_sub_and_test(skb->truesize, &sk_atm(vcc)->sk_wmem_alloc)); - vcc = *(struct atm_vcc **) &msg->vcc; + + vcc = find_get_vcc(*(struct atm_vcc **)&msg->vcc); + if (!vcc) { + pr_debug("invalid vcc pointer in msg\n"); + dev_kfree_skb(skb); + return -EINVAL; + } + pr_debug("%d (0x%lx)\n", (int)msg->type, (unsigned long)vcc); sk = sk_atm(vcc); @@ -100,7 +137,16 @@ static int sigd_send(struct atm_vcc *vcc, struct sk_buff *skb) clear_bit(ATM_VF_WAITING, &vcc->flags); break; case as_indicate: - vcc = *(struct atm_vcc **)&msg->listen_vcc; + /* Release the reference from msg->vcc, we'll use msg->listen_vcc instead */ + sock_put(sk); + + vcc = find_get_vcc(*(struct atm_vcc **)&msg->listen_vcc); + if (!vcc) { + pr_debug("invalid listen_vcc pointer in msg\n"); + dev_kfree_skb(skb); + return -EINVAL; + } + sk = sk_atm(vcc); pr_debug("as_indicate!!!\n"); lock_sock(sk); @@ -115,6 +161,8 @@ static int sigd_send(struct atm_vcc *vcc, struct sk_buff *skb) sk->sk_state_change(sk); as_indicate_complete: release_sock(sk); + /* Paired with find_get_vcc(msg->listen_vcc) above */ + sock_put(sk); return 0; case as_close: set_bit(ATM_VF_RELEASED, &vcc->flags); @@ -131,11 +179,15 @@ static int sigd_send(struct atm_vcc *vcc, struct sk_buff *skb) break; default: pr_alert("bad message type %d\n", (int)msg->type); + /* Paired with find_get_vcc(msg->vcc) above */ + sock_put(sk); return -EINVAL; } sk->sk_state_change(sk); out: dev_kfree_skb(skb); + /* Paired with find_get_vcc(msg->vcc) above */ + sock_put(sk); return 0; } From d8c851cd3245281779792c31117ba90db7527401 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20Rebe?= Date: Thu, 5 Feb 2026 17:09:59 +0100 Subject: [PATCH 3577/3902] net: sunhme: Fix sbus regression MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [ Upstream commit 8c5d17834ec104d0abd1bda52fbc04e647fab274 ] Commit cc216e4b44ce ("net: sunhme: Switch SBUS to devres") changed explicit sized of_ioremap with BMAC_REG_SIZEs to devm_platform_ioremap_resource mapping all the resource. However, this does not work on my Sun Ultra 2 with SBUS HMEs: hme f0072f38: error -EBUSY: can't request region for resource [mem 0x1ffe8c07000-0x1ffe8c0701f] hme f0072f38: Cannot map TCVR registers. hme f0072f38: probe with driver hme failed with error -16 hme f007ab44: error -EBUSY: can't request region for resource [mem 0x1ff28c07000-0x1ff28c0701f] hme f007ab44: Cannot map TCVR registers. hme f007ab44: probe with driver hme failed with error -16 Turns out the open-firmware resources overlap, at least on this machines and PROM version: hexdump /proc/device-tree/sbus@1f,0/SUNW,hme@2,8c00000/reg: 00 00 00 02 08 c0 00 00 00 00 01 08 00 00 00 02 08 c0 20 00 00 00 20 00 00 00 00 02 08 c0 40 00 00 00 20 00 00 00 00 02 08 c0 60 00 00 00 20 00 00 00 00 02 08 c0 70 00 00 00 00 20 And the driver previously explicitly mapped way smaller mmio regions: /proc/iomem: 1ff28c00000-1ff28c00107 : HME Global Regs 1ff28c02000-1ff28c02033 : HME TX Regs 1ff28c04000-1ff28c0401f : HME RX Regs 1ff28c06000-1ff28c0635f : HME BIGMAC Regs 1ff28c07000-1ff28c0701f : HME Tranceiver Regs Quirk this specific issue by truncating the previous resource to not overlap into the TCVR registers. Fixes: cc216e4b44ce ("net: sunhme: Switch SBUS to devres") Signed-off-by: René Rebe Reviewed-by: Sean Anderson Link: https://patch.msgid.link/20260205.170959.89574674688839340.rene@exactco.de Signed-off-by: Paolo Abeni Signed-off-by: Sasha Levin --- drivers/net/ethernet/sun/sunhme.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/drivers/net/ethernet/sun/sunhme.c b/drivers/net/ethernet/sun/sunhme.c index 48f0a96c0e9e3a..6669980829980d 100644 --- a/drivers/net/ethernet/sun/sunhme.c +++ b/drivers/net/ethernet/sun/sunhme.c @@ -2551,6 +2551,9 @@ static int happy_meal_sbus_probe_one(struct platform_device *op, int is_qfe) goto err_out_clear_quattro; } + /* BIGMAC may have bogus sizes */ + if ((op->resource[3].end - op->resource[3].start) >= BMAC_REG_SIZE) + op->resource[3].end = op->resource[3].start + BMAC_REG_SIZE - 1; hp->bigmacregs = devm_platform_ioremap_resource(op, 3); if (IS_ERR(hp->bigmacregs)) { dev_err(&op->dev, "Cannot map BIGMAC registers.\n"); From 1c9ef28f643cce34a6a6c36c8f4d6d60a60db7e1 Mon Sep 17 00:00:00 2001 From: Jiayuan Chen Date: Fri, 6 Feb 2026 13:02:19 +0800 Subject: [PATCH 3578/3902] xfrm: fix ip_rt_bug race in icmp_route_lookup reverse path [ Upstream commit 81b84de32bb27ae1ae2eb9acf0420e9d0d14bf00 ] icmp_route_lookup() performs multiple route lookups to find a suitable route for sending ICMP error messages, with special handling for XFRM (IPsec) policies. The lookup sequence is: 1. First, lookup output route for ICMP reply (dst = original src) 2. Pass through xfrm_lookup() for policy check 3. If blocked (-EPERM) or dst is not local, enter "reverse path" 4. In reverse path, call xfrm_decode_session_reverse() to get fl4_dec which reverses the original packet's flow (saddr<->daddr swapped) 5. If fl4_dec.saddr is local (we are the original destination), use __ip_route_output_key() for output route lookup 6. If fl4_dec.saddr is NOT local (we are a forwarding node), use ip_route_input() to simulate the reverse packet's input path 7. Finally, pass rt2 through xfrm_lookup() with XFRM_LOOKUP_ICMP flag The bug occurs in step 6: ip_route_input() is called with fl4_dec.daddr (original packet's source) as destination. If this address becomes local between the initial check and ip_route_input() call (e.g., due to concurrent "ip addr add"), ip_route_input() returns a LOCAL route with dst.output set to ip_rt_bug. This route is then used for ICMP output, causing dst_output() to call ip_rt_bug(), triggering a WARN_ON: ------------[ cut here ]------------ WARNING: net/ipv4/route.c:1275 at ip_rt_bug+0x21/0x30, CPU#1 Call Trace: ip_push_pending_frames+0x202/0x240 icmp_push_reply+0x30d/0x430 __icmp_send+0x1149/0x24f0 ip_options_compile+0xa2/0xd0 ip_rcv_finish_core+0x829/0x1950 ip_rcv+0x2d7/0x420 __netif_receive_skb_one_core+0x185/0x1f0 netif_receive_skb+0x90/0x450 tun_get_user+0x3413/0x3fb0 tun_chr_write_iter+0xe4/0x220 ... Fix this by checking rt2->rt_type after ip_route_input(). If it's RTN_LOCAL, the route cannot be used for output, so treat it as an error. The reproducer requires kernel modification to widen the race window, making it unsuitable as a selftest. It is available at: https://gist.github.com/mrpre/eae853b72ac6a750f5d45d64ddac1e81 Reported-by: syzbot+e738404dcd14b620923c@syzkaller.appspotmail.com Closes: https://lore.kernel.org/all/000000000000b1060905eada8881@google.com/T/ Closes: https://lore.kernel.org/r/20260128090523.356953-1-jiayuan.chen@linux.dev Fixes: 8b7817f3a959 ("[IPSEC]: Add ICMP host relookup support") Signed-off-by: Jiayuan Chen Signed-off-by: Jiayuan Chen Link: https://patch.msgid.link/20260206050220.59642-1-jiayuan.chen@linux.dev Signed-off-by: Paolo Abeni Signed-off-by: Sasha Levin --- net/ipv4/icmp.c | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/net/ipv4/icmp.c b/net/ipv4/icmp.c index 8e10e9e7676c58..9323ee0a6ac483 100644 --- a/net/ipv4/icmp.c +++ b/net/ipv4/icmp.c @@ -554,6 +554,21 @@ static struct rtable *icmp_route_lookup(struct net *net, struct flowi4 *fl4, /* steal dst entry from skb_in, don't drop refcnt */ skb_dstref_steal(skb_in); skb_dstref_restore(skb_in, orefdst); + + /* + * At this point, fl4_dec.daddr should NOT be local (we + * checked fl4_dec.saddr above). However, a race condition + * may occur if the address is added to the interface + * concurrently. In that case, ip_route_input() returns a + * LOCAL route with dst.output=ip_rt_bug, which must not + * be used for output. + */ + if (!err && rt2 && rt2->rt_type == RTN_LOCAL) { + net_warn_ratelimited("detected local route for %pI4 during ICMP sending, src %pI4\n", + &fl4_dec.daddr, &fl4_dec.saddr); + dst_release(&rt2->dst); + err = -EINVAL; + } } if (err) From c8c197aaa56b25a2d54f3aa07e27e228d6c08546 Mon Sep 17 00:00:00 2001 From: Jiayuan Chen Date: Fri, 6 Feb 2026 15:44:44 +0800 Subject: [PATCH 3579/3902] serial: caif: fix use-after-free in caif_serial ldisc_close() [ Upstream commit 308e7e4d0a846359685f40aade023aee7b27284c ] There is a use-after-free bug in caif_serial where handle_tx() may access ser->tty after the tty has been freed. The race condition occurs between ldisc_close() and packet transmission: CPU 0 (close) CPU 1 (xmit) ------------- ------------ ldisc_close() tty_kref_put(ser->tty) [tty may be freed here] <-- race window --> caif_xmit() handle_tx() tty = ser->tty // dangling ptr tty->ops->write() // UAF! schedule_work() ser_release() unregister_netdevice() The root cause is that tty_kref_put() is called in ldisc_close() while the network device is still active and can receive packets. Since ser and tty have a 1:1 binding relationship with consistent lifecycles (ser is allocated in ldisc_open and freed in ser_release via unregister_netdevice, and each ser binds exactly one tty), we can safely defer the tty reference release to ser_release() where the network device is unregistered. Fix this by moving tty_kref_put() from ldisc_close() to ser_release(), after unregister_netdevice(). This ensures the tty reference is held as long as the network device exists, preventing the UAF. Note: We save ser->tty before unregister_netdevice() because ser is embedded in netdev's private data and will be freed along with netdev (needs_free_netdev = true). How to reproduce: Add mdelay(500) at the beginning of ldisc_close() to widen the race window, then run the reproducer program [1]. Note: There is a separate deadloop issue in handle_tx() when using PORT_UNKNOWN serial ports (e.g., /dev/ttyS3 in QEMU without proper serial backend). This deadloop exists even without this patch, and is likely caused by inconsistency between uart_write_room() and uart_write() in serial core. It has been addressed in a separate patch [2]. KASAN report: ================================================================== BUG: KASAN: slab-use-after-free in handle_tx+0x5d1/0x620 Read of size 1 at addr ffff8881131e1490 by task caif_uaf_trigge/9929 Call Trace: dump_stack_lvl+0x10e/0x1f0 print_report+0xd0/0x630 kasan_report+0xe4/0x120 handle_tx+0x5d1/0x620 dev_hard_start_xmit+0x9d/0x6c0 __dev_queue_xmit+0x6e2/0x4410 packet_xmit+0x243/0x360 packet_sendmsg+0x26cf/0x5500 __sys_sendto+0x4a3/0x520 __x64_sys_sendto+0xe0/0x1c0 do_syscall_64+0xc9/0xf80 entry_SYSCALL_64_after_hwframe+0x77/0x7f RIP: 0033:0x7f615df2c0d7 Allocated by task 9930: Freed by task 64: Last potentially related work creation: The buggy address belongs to the object at ffff8881131e1000 which belongs to the cache kmalloc-cg-2k of size 2048 The buggy address is located 1168 bytes inside of freed 2048-byte region [ffff8881131e1000, ffff8881131e1800) The buggy address belongs to the physical page: page_owner tracks the page as allocated page last free pid 9778 tgid 9778 stack trace: Memory state around the buggy address: ffff8881131e1380: fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb ffff8881131e1400: fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb >ffff8881131e1480: fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb ^ ffff8881131e1500: fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb ffff8881131e1580: fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb ================================================================== [1]: https://gist.github.com/mrpre/f683f244544f7b11e7fa87df9e6c2eeb [2]: https://lore.kernel.org/linux-serial/20260204074327.226165-1-jiayuan.chen@linux.dev/T/#u Reported-by: syzbot+827272712bd6d12c79a4@syzkaller.appspotmail.com Closes: https://lore.kernel.org/all/000000000000a4a7550611e234f5@google.com/T/ Fixes: 56e0ef527b18 ("drivers/net: caif: fix wrong rtnl_is_locked() usage") Reviewed-by: Greg Kroah-Hartman Signed-off-by: Jiayuan Chen Reviewed-by: Jijie Shao Link: https://patch.msgid.link/20260206074450.154267-1-jiayuan.chen@linux.dev Signed-off-by: Paolo Abeni Signed-off-by: Sasha Levin --- drivers/net/caif/caif_serial.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/drivers/net/caif/caif_serial.c b/drivers/net/caif/caif_serial.c index c398ac42eae905..b90890030751f1 100644 --- a/drivers/net/caif/caif_serial.c +++ b/drivers/net/caif/caif_serial.c @@ -284,6 +284,7 @@ static void ser_release(struct work_struct *work) { struct list_head list; struct ser_device *ser, *tmp; + struct tty_struct *tty; spin_lock(&ser_lock); list_replace_init(&ser_release_list, &list); @@ -292,9 +293,11 @@ static void ser_release(struct work_struct *work) if (!list_empty(&list)) { rtnl_lock(); list_for_each_entry_safe(ser, tmp, &list, node) { + tty = ser->tty; dev_close(ser->dev); unregister_netdevice(ser->dev); debugfs_deinit(ser); + tty_kref_put(tty); } rtnl_unlock(); } @@ -355,8 +358,6 @@ static void ldisc_close(struct tty_struct *tty) { struct ser_device *ser = tty->disc_data; - tty_kref_put(ser->tty); - spin_lock(&ser_lock); list_move(&ser->node, &ser_release_list); spin_unlock(&ser_lock); From 1b8e3c8b74436bdb7503ec9a76ab16574433a031 Mon Sep 17 00:00:00 2001 From: Vimlesh Kumar Date: Fri, 6 Feb 2026 11:15:06 +0000 Subject: [PATCH 3580/3902] octeon_ep: disable per ring interrupts [ Upstream commit 73e6ffa37cebee152c07c5f2b8bc70fd2899ea6e ] Disable the MSI-X per ring interrupt for every PF ring when PF netdev goes down. Fixes: 1f2c2d0cee023 ("octeon_ep: add hardware configuration APIs") Signed-off-by: Sathesh Edara Signed-off-by: Shinas Rasheed Signed-off-by: Vimlesh Kumar Link: https://patch.msgid.link/20260206111510.1045092-2-vimleshk@marvell.com Signed-off-by: Paolo Abeni Signed-off-by: Sasha Levin --- .../ethernet/marvell/octeon_ep/octep_cn9k_pf.c | 18 +++++++++++++++--- .../ethernet/marvell/octeon_ep/octep_cnxk_pf.c | 18 +++++++++++++++--- .../marvell/octeon_ep/octep_regs_cn9k_pf.h | 1 + .../marvell/octeon_ep/octep_regs_cnxk_pf.h | 1 + 4 files changed, 32 insertions(+), 6 deletions(-) diff --git a/drivers/net/ethernet/marvell/octeon_ep/octep_cn9k_pf.c b/drivers/net/ethernet/marvell/octeon_ep/octep_cn9k_pf.c index b5805969404fa1..f0bcb5f3c14741 100644 --- a/drivers/net/ethernet/marvell/octeon_ep/octep_cn9k_pf.c +++ b/drivers/net/ethernet/marvell/octeon_ep/octep_cn9k_pf.c @@ -696,14 +696,26 @@ static void octep_enable_interrupts_cn93_pf(struct octep_device *oct) /* Disable all interrupts */ static void octep_disable_interrupts_cn93_pf(struct octep_device *oct) { - u64 intr_mask = 0ULL; + u64 reg_val, intr_mask = 0ULL; int srn, num_rings, i; srn = CFG_GET_PORTS_PF_SRN(oct->conf); num_rings = CFG_GET_PORTS_ACTIVE_IO_RINGS(oct->conf); - for (i = 0; i < num_rings; i++) - intr_mask |= (0x1ULL << (srn + i)); + for (i = 0; i < num_rings; i++) { + intr_mask |= BIT_ULL(srn + i); + reg_val = octep_read_csr64(oct, + CN93_SDP_R_IN_INT_LEVELS(srn + i)); + reg_val &= ~CN93_INT_ENA_BIT; + octep_write_csr64(oct, + CN93_SDP_R_IN_INT_LEVELS(srn + i), reg_val); + + reg_val = octep_read_csr64(oct, + CN93_SDP_R_OUT_INT_LEVELS(srn + i)); + reg_val &= ~CN93_INT_ENA_BIT; + octep_write_csr64(oct, + CN93_SDP_R_OUT_INT_LEVELS(srn + i), reg_val); + } octep_write_csr64(oct, CN93_SDP_EPF_IRERR_RINT_ENA_W1C, intr_mask); octep_write_csr64(oct, CN93_SDP_EPF_ORERR_RINT_ENA_W1C, intr_mask); diff --git a/drivers/net/ethernet/marvell/octeon_ep/octep_cnxk_pf.c b/drivers/net/ethernet/marvell/octeon_ep/octep_cnxk_pf.c index 5de0b5ecbc5fd1..07e00887c6940a 100644 --- a/drivers/net/ethernet/marvell/octeon_ep/octep_cnxk_pf.c +++ b/drivers/net/ethernet/marvell/octeon_ep/octep_cnxk_pf.c @@ -720,14 +720,26 @@ static void octep_enable_interrupts_cnxk_pf(struct octep_device *oct) /* Disable all interrupts */ static void octep_disable_interrupts_cnxk_pf(struct octep_device *oct) { - u64 intr_mask = 0ULL; + u64 reg_val, intr_mask = 0ULL; int srn, num_rings, i; srn = CFG_GET_PORTS_PF_SRN(oct->conf); num_rings = CFG_GET_PORTS_ACTIVE_IO_RINGS(oct->conf); - for (i = 0; i < num_rings; i++) - intr_mask |= (0x1ULL << (srn + i)); + for (i = 0; i < num_rings; i++) { + intr_mask |= BIT_ULL(srn + i); + reg_val = octep_read_csr64(oct, + CNXK_SDP_R_IN_INT_LEVELS(srn + i)); + reg_val &= ~CNXK_INT_ENA_BIT; + octep_write_csr64(oct, + CNXK_SDP_R_IN_INT_LEVELS(srn + i), reg_val); + + reg_val = octep_read_csr64(oct, + CNXK_SDP_R_OUT_INT_LEVELS(srn + i)); + reg_val &= ~CNXK_INT_ENA_BIT; + octep_write_csr64(oct, + CNXK_SDP_R_OUT_INT_LEVELS(srn + i), reg_val); + } octep_write_csr64(oct, CNXK_SDP_EPF_IRERR_RINT_ENA_W1C, intr_mask); octep_write_csr64(oct, CNXK_SDP_EPF_ORERR_RINT_ENA_W1C, intr_mask); diff --git a/drivers/net/ethernet/marvell/octeon_ep/octep_regs_cn9k_pf.h b/drivers/net/ethernet/marvell/octeon_ep/octep_regs_cn9k_pf.h index ca473502d7a02a..95f1dfff90cce4 100644 --- a/drivers/net/ethernet/marvell/octeon_ep/octep_regs_cn9k_pf.h +++ b/drivers/net/ethernet/marvell/octeon_ep/octep_regs_cn9k_pf.h @@ -386,5 +386,6 @@ #define CN93_PEM_BAR4_INDEX 7 #define CN93_PEM_BAR4_INDEX_SIZE 0x400000ULL #define CN93_PEM_BAR4_INDEX_OFFSET (CN93_PEM_BAR4_INDEX * CN93_PEM_BAR4_INDEX_SIZE) +#define CN93_INT_ENA_BIT BIT_ULL(62) #endif /* _OCTEP_REGS_CN9K_PF_H_ */ diff --git a/drivers/net/ethernet/marvell/octeon_ep/octep_regs_cnxk_pf.h b/drivers/net/ethernet/marvell/octeon_ep/octep_regs_cnxk_pf.h index e637d7c8224d49..4d172a552f80c5 100644 --- a/drivers/net/ethernet/marvell/octeon_ep/octep_regs_cnxk_pf.h +++ b/drivers/net/ethernet/marvell/octeon_ep/octep_regs_cnxk_pf.h @@ -412,5 +412,6 @@ #define CNXK_PEM_BAR4_INDEX 7 #define CNXK_PEM_BAR4_INDEX_SIZE 0x400000ULL #define CNXK_PEM_BAR4_INDEX_OFFSET (CNXK_PEM_BAR4_INDEX * CNXK_PEM_BAR4_INDEX_SIZE) +#define CNXK_INT_ENA_BIT BIT_ULL(62) #endif /* _OCTEP_REGS_CNXK_PF_H_ */ From 13a61502c1c83b11d1355de9bf39626d7fe74201 Mon Sep 17 00:00:00 2001 From: Vimlesh Kumar Date: Fri, 6 Feb 2026 11:15:07 +0000 Subject: [PATCH 3581/3902] octeon_ep: ensure dbell BADDR updation [ Upstream commit ce8fe3fc4f99efd872120301c0f72f2e90ab9769 ] Make sure the OUT DBELL base address reflects the latest values written to it. Fix: Add a wait until the OUT DBELL base address register is updated with the DMA ring descriptor address, and modify the setup_oq function to properly handle failures. Fixes: 0807dc76f3bf5 ("octeon_ep: support Octeon CN10K devices") Signed-off-by: Sathesh Edara Signed-off-by: Shinas Rasheed Signed-off-by: Vimlesh Kumar Link: https://patch.msgid.link/20260206111510.1045092-3-vimleshk@marvell.com Signed-off-by: Paolo Abeni Signed-off-by: Sasha Levin --- .../marvell/octeon_ep/octep_cn9k_pf.c | 3 +- .../marvell/octeon_ep/octep_cnxk_pf.c | 46 +++++++++++++++---- .../ethernet/marvell/octeon_ep/octep_main.h | 2 +- .../net/ethernet/marvell/octeon_ep/octep_rx.c | 8 +++- 4 files changed, 48 insertions(+), 11 deletions(-) diff --git a/drivers/net/ethernet/marvell/octeon_ep/octep_cn9k_pf.c b/drivers/net/ethernet/marvell/octeon_ep/octep_cn9k_pf.c index f0bcb5f3c14741..01e82d0b6b2cdd 100644 --- a/drivers/net/ethernet/marvell/octeon_ep/octep_cn9k_pf.c +++ b/drivers/net/ethernet/marvell/octeon_ep/octep_cn9k_pf.c @@ -307,7 +307,7 @@ static void octep_setup_iq_regs_cn93_pf(struct octep_device *oct, int iq_no) } /* Setup registers for a hardware Rx Queue */ -static void octep_setup_oq_regs_cn93_pf(struct octep_device *oct, int oq_no) +static int octep_setup_oq_regs_cn93_pf(struct octep_device *oct, int oq_no) { u64 reg_val; u64 oq_ctl = 0ULL; @@ -355,6 +355,7 @@ static void octep_setup_oq_regs_cn93_pf(struct octep_device *oct, int oq_no) reg_val = ((u64)time_threshold << 32) | CFG_GET_OQ_INTR_PKT(oct->conf); octep_write_csr64(oct, CN93_SDP_R_OUT_INT_LEVELS(oq_no), reg_val); + return 0; } /* Setup registers for a PF mailbox */ diff --git a/drivers/net/ethernet/marvell/octeon_ep/octep_cnxk_pf.c b/drivers/net/ethernet/marvell/octeon_ep/octep_cnxk_pf.c index 07e00887c6940a..09a3f1d0645b85 100644 --- a/drivers/net/ethernet/marvell/octeon_ep/octep_cnxk_pf.c +++ b/drivers/net/ethernet/marvell/octeon_ep/octep_cnxk_pf.c @@ -8,6 +8,7 @@ #include #include #include +#include #include "octep_config.h" #include "octep_main.h" @@ -327,12 +328,14 @@ static void octep_setup_iq_regs_cnxk_pf(struct octep_device *oct, int iq_no) } /* Setup registers for a hardware Rx Queue */ -static void octep_setup_oq_regs_cnxk_pf(struct octep_device *oct, int oq_no) +static int octep_setup_oq_regs_cnxk_pf(struct octep_device *oct, int oq_no) { - u64 reg_val; - u64 oq_ctl = 0ULL; - u32 time_threshold = 0; struct octep_oq *oq = oct->oq[oq_no]; + unsigned long t_out_jiffies; + u32 time_threshold = 0; + u64 oq_ctl = 0ULL; + u64 reg_ba_val; + u64 reg_val; oq_no += CFG_GET_PORTS_PF_SRN(oct->conf); reg_val = octep_read_csr64(oct, CNXK_SDP_R_OUT_CONTROL(oq_no)); @@ -343,6 +346,36 @@ static void octep_setup_oq_regs_cnxk_pf(struct octep_device *oct, int oq_no) reg_val = octep_read_csr64(oct, CNXK_SDP_R_OUT_CONTROL(oq_no)); } while (!(reg_val & CNXK_R_OUT_CTL_IDLE)); } + octep_write_csr64(oct, CNXK_SDP_R_OUT_WMARK(oq_no), oq->max_count); + /* Wait for WMARK to get applied */ + usleep_range(10, 15); + + octep_write_csr64(oct, CNXK_SDP_R_OUT_SLIST_BADDR(oq_no), + oq->desc_ring_dma); + octep_write_csr64(oct, CNXK_SDP_R_OUT_SLIST_RSIZE(oq_no), + oq->max_count); + reg_ba_val = octep_read_csr64(oct, CNXK_SDP_R_OUT_SLIST_BADDR(oq_no)); + + if (reg_ba_val != oq->desc_ring_dma) { + t_out_jiffies = jiffies + 10 * HZ; + do { + if (reg_ba_val == ULLONG_MAX) + return -EFAULT; + octep_write_csr64(oct, + CNXK_SDP_R_OUT_SLIST_BADDR(oq_no), + oq->desc_ring_dma); + octep_write_csr64(oct, + CNXK_SDP_R_OUT_SLIST_RSIZE(oq_no), + oq->max_count); + reg_ba_val = + octep_read_csr64(oct, + CNXK_SDP_R_OUT_SLIST_BADDR(oq_no)); + } while ((reg_ba_val != oq->desc_ring_dma) && + time_before(jiffies, t_out_jiffies)); + + if (reg_ba_val != oq->desc_ring_dma) + return -EAGAIN; + } reg_val &= ~(CNXK_R_OUT_CTL_IMODE); reg_val &= ~(CNXK_R_OUT_CTL_ROR_P); @@ -356,10 +389,6 @@ static void octep_setup_oq_regs_cnxk_pf(struct octep_device *oct, int oq_no) reg_val |= (CNXK_R_OUT_CTL_ES_P); octep_write_csr64(oct, CNXK_SDP_R_OUT_CONTROL(oq_no), reg_val); - octep_write_csr64(oct, CNXK_SDP_R_OUT_SLIST_BADDR(oq_no), - oq->desc_ring_dma); - octep_write_csr64(oct, CNXK_SDP_R_OUT_SLIST_RSIZE(oq_no), - oq->max_count); oq_ctl = octep_read_csr64(oct, CNXK_SDP_R_OUT_CONTROL(oq_no)); @@ -385,6 +414,7 @@ static void octep_setup_oq_regs_cnxk_pf(struct octep_device *oct, int oq_no) reg_val &= ~0xFFFFFFFFULL; reg_val |= CFG_GET_OQ_WMARK(oct->conf); octep_write_csr64(oct, CNXK_SDP_R_OUT_WMARK(oq_no), reg_val); + return 0; } /* Setup registers for a PF mailbox */ diff --git a/drivers/net/ethernet/marvell/octeon_ep/octep_main.h b/drivers/net/ethernet/marvell/octeon_ep/octep_main.h index 81ac4267811c81..35d0ff289a70df 100644 --- a/drivers/net/ethernet/marvell/octeon_ep/octep_main.h +++ b/drivers/net/ethernet/marvell/octeon_ep/octep_main.h @@ -77,7 +77,7 @@ struct octep_pci_win_regs { struct octep_hw_ops { void (*setup_iq_regs)(struct octep_device *oct, int q); - void (*setup_oq_regs)(struct octep_device *oct, int q); + int (*setup_oq_regs)(struct octep_device *oct, int q); void (*setup_mbox_regs)(struct octep_device *oct, int mbox); irqreturn_t (*mbox_intr_handler)(void *ioq_vector); diff --git a/drivers/net/ethernet/marvell/octeon_ep/octep_rx.c b/drivers/net/ethernet/marvell/octeon_ep/octep_rx.c index 82b6b19e76b47a..f2a7c6a76c742a 100644 --- a/drivers/net/ethernet/marvell/octeon_ep/octep_rx.c +++ b/drivers/net/ethernet/marvell/octeon_ep/octep_rx.c @@ -12,6 +12,8 @@ #include "octep_config.h" #include "octep_main.h" +static void octep_oq_free_ring_buffers(struct octep_oq *oq); + static void octep_oq_reset_indices(struct octep_oq *oq) { oq->host_read_idx = 0; @@ -170,11 +172,15 @@ static int octep_setup_oq(struct octep_device *oct, int q_no) goto oq_fill_buff_err; octep_oq_reset_indices(oq); - oct->hw_ops.setup_oq_regs(oct, q_no); + if (oct->hw_ops.setup_oq_regs(oct, q_no)) + goto oq_setup_err; + oct->num_oqs++; return 0; +oq_setup_err: + octep_oq_free_ring_buffers(oq); oq_fill_buff_err: vfree(oq->buff_info); oq->buff_info = NULL; From a16bea45d758e97002e8ccd3395458e846e18bd1 Mon Sep 17 00:00:00 2001 From: Vimlesh Kumar Date: Fri, 6 Feb 2026 11:15:08 +0000 Subject: [PATCH 3582/3902] octeon_ep_vf: ensure dbell BADDR updation [ Upstream commit 484e834d53cffa91c311631271f83130cf6e9e7c ] Make sure the OUT DBELL base address reflects the latest values written to it. Fix: Add a wait until the OUT DBELL base address register is updated with the DMA ring descriptor address, and modify the setup_oq function to properly handle failures. Fixes: 2c0c32c72be29 ("octeon_ep_vf: add hardware configuration APIs") Signed-off-by: Sathesh Edara Signed-off-by: Shinas Rasheed Signed-off-by: Vimlesh Kumar Link: https://patch.msgid.link/20260206111510.1045092-4-vimleshk@marvell.com Signed-off-by: Paolo Abeni Signed-off-by: Sasha Levin --- .../marvell/octeon_ep_vf/octep_vf_cn9k.c | 3 +- .../marvell/octeon_ep_vf/octep_vf_cnxk.c | 39 +++++++++++++++++-- .../marvell/octeon_ep_vf/octep_vf_main.h | 2 +- .../marvell/octeon_ep_vf/octep_vf_rx.c | 8 +++- 4 files changed, 46 insertions(+), 6 deletions(-) diff --git a/drivers/net/ethernet/marvell/octeon_ep_vf/octep_vf_cn9k.c b/drivers/net/ethernet/marvell/octeon_ep_vf/octep_vf_cn9k.c index 88937fce75f140..4c769b27c27892 100644 --- a/drivers/net/ethernet/marvell/octeon_ep_vf/octep_vf_cn9k.c +++ b/drivers/net/ethernet/marvell/octeon_ep_vf/octep_vf_cn9k.c @@ -196,7 +196,7 @@ static void octep_vf_setup_iq_regs_cn93(struct octep_vf_device *oct, int iq_no) } /* Setup registers for a hardware Rx Queue */ -static void octep_vf_setup_oq_regs_cn93(struct octep_vf_device *oct, int oq_no) +static int octep_vf_setup_oq_regs_cn93(struct octep_vf_device *oct, int oq_no) { struct octep_vf_oq *oq = oct->oq[oq_no]; u32 time_threshold = 0; @@ -239,6 +239,7 @@ static void octep_vf_setup_oq_regs_cn93(struct octep_vf_device *oct, int oq_no) time_threshold = CFG_GET_OQ_INTR_TIME(oct->conf); reg_val = ((u64)time_threshold << 32) | CFG_GET_OQ_INTR_PKT(oct->conf); octep_vf_write_csr64(oct, CN93_VF_SDP_R_OUT_INT_LEVELS(oq_no), reg_val); + return 0; } /* Setup registers for a VF mailbox */ diff --git a/drivers/net/ethernet/marvell/octeon_ep_vf/octep_vf_cnxk.c b/drivers/net/ethernet/marvell/octeon_ep_vf/octep_vf_cnxk.c index 1f79dfad42c626..a968b93a67943b 100644 --- a/drivers/net/ethernet/marvell/octeon_ep_vf/octep_vf_cnxk.c +++ b/drivers/net/ethernet/marvell/octeon_ep_vf/octep_vf_cnxk.c @@ -199,11 +199,13 @@ static void octep_vf_setup_iq_regs_cnxk(struct octep_vf_device *oct, int iq_no) } /* Setup registers for a hardware Rx Queue */ -static void octep_vf_setup_oq_regs_cnxk(struct octep_vf_device *oct, int oq_no) +static int octep_vf_setup_oq_regs_cnxk(struct octep_vf_device *oct, int oq_no) { struct octep_vf_oq *oq = oct->oq[oq_no]; + unsigned long t_out_jiffies; u32 time_threshold = 0; u64 oq_ctl = ULL(0); + u64 reg_ba_val; u64 reg_val; reg_val = octep_vf_read_csr64(oct, CNXK_VF_SDP_R_OUT_CONTROL(oq_no)); @@ -214,6 +216,38 @@ static void octep_vf_setup_oq_regs_cnxk(struct octep_vf_device *oct, int oq_no) reg_val = octep_vf_read_csr64(oct, CNXK_VF_SDP_R_OUT_CONTROL(oq_no)); } while (!(reg_val & CNXK_VF_R_OUT_CTL_IDLE)); } + octep_vf_write_csr64(oct, CNXK_VF_SDP_R_OUT_WMARK(oq_no), + oq->max_count); + /* Wait for WMARK to get applied */ + usleep_range(10, 15); + + octep_vf_write_csr64(oct, CNXK_VF_SDP_R_OUT_SLIST_BADDR(oq_no), + oq->desc_ring_dma); + octep_vf_write_csr64(oct, CNXK_VF_SDP_R_OUT_SLIST_RSIZE(oq_no), + oq->max_count); + reg_ba_val = octep_vf_read_csr64(oct, + CNXK_VF_SDP_R_OUT_SLIST_BADDR(oq_no)); + if (reg_ba_val != oq->desc_ring_dma) { + t_out_jiffies = jiffies + 10 * HZ; + do { + if (reg_ba_val == ULLONG_MAX) + return -EFAULT; + octep_vf_write_csr64(oct, + CNXK_VF_SDP_R_OUT_SLIST_BADDR + (oq_no), oq->desc_ring_dma); + octep_vf_write_csr64(oct, + CNXK_VF_SDP_R_OUT_SLIST_RSIZE + (oq_no), oq->max_count); + reg_ba_val = + octep_vf_read_csr64(oct, + CNXK_VF_SDP_R_OUT_SLIST_BADDR + (oq_no)); + } while ((reg_ba_val != oq->desc_ring_dma) && + time_before(jiffies, t_out_jiffies)); + + if (reg_ba_val != oq->desc_ring_dma) + return -EAGAIN; + } reg_val &= ~(CNXK_VF_R_OUT_CTL_IMODE); reg_val &= ~(CNXK_VF_R_OUT_CTL_ROR_P); @@ -227,8 +261,6 @@ static void octep_vf_setup_oq_regs_cnxk(struct octep_vf_device *oct, int oq_no) reg_val |= (CNXK_VF_R_OUT_CTL_ES_P); octep_vf_write_csr64(oct, CNXK_VF_SDP_R_OUT_CONTROL(oq_no), reg_val); - octep_vf_write_csr64(oct, CNXK_VF_SDP_R_OUT_SLIST_BADDR(oq_no), oq->desc_ring_dma); - octep_vf_write_csr64(oct, CNXK_VF_SDP_R_OUT_SLIST_RSIZE(oq_no), oq->max_count); oq_ctl = octep_vf_read_csr64(oct, CNXK_VF_SDP_R_OUT_CONTROL(oq_no)); /* Clear the ISIZE and BSIZE (22-0) */ @@ -250,6 +282,7 @@ static void octep_vf_setup_oq_regs_cnxk(struct octep_vf_device *oct, int oq_no) reg_val &= ~GENMASK_ULL(31, 0); reg_val |= CFG_GET_OQ_WMARK(oct->conf); octep_vf_write_csr64(oct, CNXK_VF_SDP_R_OUT_WMARK(oq_no), reg_val); + return 0; } /* Setup registers for a VF mailbox */ diff --git a/drivers/net/ethernet/marvell/octeon_ep_vf/octep_vf_main.h b/drivers/net/ethernet/marvell/octeon_ep_vf/octep_vf_main.h index b9f13506f46205..c74cd2369e90d4 100644 --- a/drivers/net/ethernet/marvell/octeon_ep_vf/octep_vf_main.h +++ b/drivers/net/ethernet/marvell/octeon_ep_vf/octep_vf_main.h @@ -55,7 +55,7 @@ struct octep_vf_mmio { struct octep_vf_hw_ops { void (*setup_iq_regs)(struct octep_vf_device *oct, int q); - void (*setup_oq_regs)(struct octep_vf_device *oct, int q); + int (*setup_oq_regs)(struct octep_vf_device *oct, int q); void (*setup_mbox_regs)(struct octep_vf_device *oct, int mbox); irqreturn_t (*non_ioq_intr_handler)(void *ioq_vector); diff --git a/drivers/net/ethernet/marvell/octeon_ep_vf/octep_vf_rx.c b/drivers/net/ethernet/marvell/octeon_ep_vf/octep_vf_rx.c index d70c8be3cfc40b..6f865dbbba6c67 100644 --- a/drivers/net/ethernet/marvell/octeon_ep_vf/octep_vf_rx.c +++ b/drivers/net/ethernet/marvell/octeon_ep_vf/octep_vf_rx.c @@ -12,6 +12,8 @@ #include "octep_vf_config.h" #include "octep_vf_main.h" +static void octep_vf_oq_free_ring_buffers(struct octep_vf_oq *oq); + static void octep_vf_oq_reset_indices(struct octep_vf_oq *oq) { oq->host_read_idx = 0; @@ -171,11 +173,15 @@ static int octep_vf_setup_oq(struct octep_vf_device *oct, int q_no) goto oq_fill_buff_err; octep_vf_oq_reset_indices(oq); - oct->hw_ops.setup_oq_regs(oct, q_no); + if (oct->hw_ops.setup_oq_regs(oct, q_no)) + goto oq_setup_err; + oct->num_oqs++; return 0; +oq_setup_err: + octep_vf_oq_free_ring_buffers(oq); oq_fill_buff_err: vfree(oq->buff_info); oq->buff_info = NULL; From 1d4f8092bd7851a12a769422113db532f70da1ab Mon Sep 17 00:00:00 2001 From: Eric Joyner Date: Fri, 6 Feb 2026 14:46:51 -0800 Subject: [PATCH 3583/3902] ionic: Rate limit unknown xcvr type messages [ Upstream commit cdb1634de3bf197c0d86487d1fb84c128a79cc7c ] Running ethtool repeatedly with a transceiver unknown to the driver or firmware will cause the driver to spam the kernel logs with "unknown xcvr type" messages which can distract from real issues; and this isn't interesting information outside of debugging. Fix this by rate limiting the output so that there are still notifications but not so many that they flood the log. Using dev_dbg_once() would reduce the number of messages further, but this would miss the case where a different unknown transceiver type is plugged in, and its status is requested. Fixes: 4d03e00a2140 ("ionic: Add initial ethtool support") Signed-off-by: Eric Joyner Reviewed-by: Brett Creeley Link: https://patch.msgid.link/20260206224651.1491-1-eric.joyner@amd.com Signed-off-by: Jakub Kicinski Signed-off-by: Sasha Levin --- drivers/net/ethernet/pensando/ionic/ionic_ethtool.c | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/drivers/net/ethernet/pensando/ionic/ionic_ethtool.c b/drivers/net/ethernet/pensando/ionic/ionic_ethtool.c index 2d9efadb5d2ae1..347b0aff100b9f 100644 --- a/drivers/net/ethernet/pensando/ionic/ionic_ethtool.c +++ b/drivers/net/ethernet/pensando/ionic/ionic_ethtool.c @@ -263,9 +263,10 @@ static int ionic_get_link_ksettings(struct net_device *netdev, /* This means there's no module plugged in */ break; default: - dev_info(lif->ionic->dev, "unknown xcvr type pid=%d / 0x%x\n", - idev->port_info->status.xcvr.pid, - idev->port_info->status.xcvr.pid); + dev_dbg_ratelimited(lif->ionic->dev, + "unknown xcvr type pid=%d / 0x%x\n", + idev->port_info->status.xcvr.pid, + idev->port_info->status.xcvr.pid); break; } From 8abc12ce2573efcefaf3ee39b195ccc4a5265b45 Mon Sep 17 00:00:00 2001 From: Michael Dege Date: Fri, 6 Feb 2026 14:41:53 +0100 Subject: [PATCH 3584/3902] net: renesas: rswitch: fix forwarding offload statemachine [ Upstream commit e9a5073a98d940837cbb95e71eed1f28f48e7b30 ] A change of the port state of one port, caused the state of another port to change. This behvior was unintended. Fixes: b7502b1043de ("net: renesas: rswitch: add offloading for L2 switching") Signed-off-by: Michael Dege Link: https://patch.msgid.link/20260206-fix-offloading-statemachine-v3-1-07bfba07d03e@renesas.com Signed-off-by: Paolo Abeni Signed-off-by: Sasha Levin --- drivers/net/ethernet/renesas/rswitch_l2.c | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/drivers/net/ethernet/renesas/rswitch_l2.c b/drivers/net/ethernet/renesas/rswitch_l2.c index 4a69ec77d69c60..9433cd8adced97 100644 --- a/drivers/net/ethernet/renesas/rswitch_l2.c +++ b/drivers/net/ethernet/renesas/rswitch_l2.c @@ -1,7 +1,7 @@ // SPDX-License-Identifier: GPL-2.0 /* Renesas Ethernet Switch device driver * - * Copyright (C) 2025 Renesas Electronics Corporation + * Copyright (C) 2025 - 2026 Renesas Electronics Corporation */ #include @@ -60,6 +60,7 @@ static void rswitch_update_l2_hw_learning(struct rswitch_private *priv) static void rswitch_update_l2_hw_forwarding(struct rswitch_private *priv) { struct rswitch_device *rdev; + bool new_forwarding_offload; unsigned int fwd_mask; /* calculate fwd_mask with zeroes in bits corresponding to ports that @@ -73,8 +74,9 @@ static void rswitch_update_l2_hw_forwarding(struct rswitch_private *priv) } rswitch_for_all_ports(priv, rdev) { - if ((rdev_for_l2_offload(rdev) && rdev->forwarding_requested) || - rdev->forwarding_offloaded) { + new_forwarding_offload = (rdev_for_l2_offload(rdev) && rdev->forwarding_requested); + + if (new_forwarding_offload || rdev->forwarding_offloaded) { /* Update allowed offload destinations even for ports * with L2 offload enabled earlier. * @@ -84,13 +86,10 @@ static void rswitch_update_l2_hw_forwarding(struct rswitch_private *priv) priv->addr + FWPC2(rdev->port)); } - if (rdev_for_l2_offload(rdev) && - rdev->forwarding_requested && - !rdev->forwarding_offloaded) { + if (new_forwarding_offload && !rdev->forwarding_offloaded) rswitch_change_l2_hw_offloading(rdev, true, false); - } else if (rdev->forwarding_offloaded) { + else if (!new_forwarding_offload && rdev->forwarding_offloaded) rswitch_change_l2_hw_offloading(rdev, false, false); - } } } From 08b10c468962cd46091c5ac825473ab7c9de5b3c Mon Sep 17 00:00:00 2001 From: Hariprasad Kelam Date: Fri, 6 Feb 2026 23:56:45 +0530 Subject: [PATCH 3585/3902] octeontx2-pf: Unregister devlink on probe failure [ Upstream commit 943f3b8bfbf297cf74392b50a7108ce1fe4cbd8c ] When probe fails after devlink registration, the missing devlink unregister call causing a memory leak. Fixes: 2da489432747 ("octeontx2-pf: devlink params support to set mcam entry count") Signed-off-by: Hariprasad Kelam Link: https://patch.msgid.link/20260206182645.4032737-1-hkelam@marvell.com Signed-off-by: Paolo Abeni Signed-off-by: Sasha Levin --- drivers/net/ethernet/marvell/octeontx2/nic/otx2_pf.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/net/ethernet/marvell/octeontx2/nic/otx2_pf.c b/drivers/net/ethernet/marvell/octeontx2/nic/otx2_pf.c index a7a7bc0e1b6757..bbf25769f4994a 100644 --- a/drivers/net/ethernet/marvell/octeontx2/nic/otx2_pf.c +++ b/drivers/net/ethernet/marvell/octeontx2/nic/otx2_pf.c @@ -3321,6 +3321,7 @@ static int otx2_probe(struct pci_dev *pdev, const struct pci_device_id *id) err_sriov_cleannup: otx2_sriov_vfcfg_cleanup(pf); err_pf_sriov_init: + otx2_unregister_dl(pf); otx2_shutdown_tc(pf); err_mcam_flow_del: otx2_mcam_flow_del(pf); From 365996a2b14d07caa9e33d367b67ea26c09d89b4 Mon Sep 17 00:00:00 2001 From: Kuniyuki Iwashima Date: Sat, 7 Feb 2026 23:22:34 +0000 Subject: [PATCH 3586/3902] af_unix: Fix memleak of newsk in unix_stream_connect(). [ Upstream commit 6884028cd7f275f8bcb854a347265cb1fb0e4bea ] When prepare_peercred() fails in unix_stream_connect(), unix_release_sock() is not called for newsk, and the memory is leaked. Let's move prepare_peercred() before unix_create1(). Fixes: fd0a109a0f6b ("net, pidfs: prepare for handing out pidfds for reaped sk->sk_peer_pid") Signed-off-by: Kuniyuki Iwashima Link: https://patch.msgid.link/20260207232236.2557549-1-kuniyu@google.com Signed-off-by: Paolo Abeni Signed-off-by: Sasha Levin --- net/unix/af_unix.c | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/net/unix/af_unix.c b/net/unix/af_unix.c index c634a7fc860906..9dad3af700af3e 100644 --- a/net/unix/af_unix.c +++ b/net/unix/af_unix.c @@ -1671,10 +1671,9 @@ static int unix_stream_connect(struct socket *sock, struct sockaddr *uaddr, timeo = sock_sndtimeo(sk, flags & O_NONBLOCK); - /* First of all allocate resources. - * If we will make it after state is locked, - * we will have to recheck all again in any case. - */ + err = prepare_peercred(&peercred); + if (err) + goto out; /* create new sock for complete connection */ newsk = unix_create1(net, NULL, 0, sock->type); @@ -1683,10 +1682,6 @@ static int unix_stream_connect(struct socket *sock, struct sockaddr *uaddr, goto out; } - err = prepare_peercred(&peercred); - if (err) - goto out; - /* Allocate skb for sending to listening sock */ skb = sock_wmalloc(newsk, 1, 0, GFP_KERNEL); if (!skb) { From d273d9cedd78db56905b852a06e746bb5ff1dc55 Mon Sep 17 00:00:00 2001 From: Honggang LI Date: Wed, 24 Dec 2025 10:38:19 +0800 Subject: [PATCH 3587/3902] RDMA/rtrs: server: remove dead code [ Upstream commit a3572bdc3a028ca47f77d7166ac95b719cf77d50 ] As rkey had been initialized to zero, the WARN_ON_ONCE should never been triggered. Remove it. Fixes: 9cb837480424 ("RDMA/rtrs: server: main functionality") Signed-off-by: Honggang LI Link: https://patch.msgid.link/20251224023819.138846-1-honggangli@163.com Signed-off-by: Leon Romanovsky Signed-off-by: Sasha Levin --- drivers/infiniband/ulp/rtrs/rtrs-srv.c | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/drivers/infiniband/ulp/rtrs/rtrs-srv.c b/drivers/infiniband/ulp/rtrs/rtrs-srv.c index 9ecc6343455d6c..7a402eb8e0bf0c 100644 --- a/drivers/infiniband/ulp/rtrs/rtrs-srv.c +++ b/drivers/infiniband/ulp/rtrs/rtrs-srv.c @@ -208,7 +208,6 @@ static int rdma_write_sg(struct rtrs_srv_op *id) size_t sg_cnt; int err, offset; bool need_inval; - u32 rkey = 0; struct ib_reg_wr rwr; struct ib_sge *plist; struct ib_sge list; @@ -240,11 +239,6 @@ static int rdma_write_sg(struct rtrs_srv_op *id) wr->wr.num_sge = 1; wr->remote_addr = le64_to_cpu(id->rd_msg->desc[0].addr); wr->rkey = le32_to_cpu(id->rd_msg->desc[0].key); - if (rkey == 0) - rkey = wr->rkey; - else - /* Only one key is actually used */ - WARN_ON_ONCE(rkey != wr->rkey); wr->wr.opcode = IB_WR_RDMA_WRITE; wr->wr.wr_cqe = &io_comp_cqe; @@ -277,7 +271,7 @@ static int rdma_write_sg(struct rtrs_srv_op *id) inv_wr.opcode = IB_WR_SEND_WITH_INV; inv_wr.wr_cqe = &io_comp_cqe; inv_wr.send_flags = 0; - inv_wr.ex.invalidate_rkey = rkey; + inv_wr.ex.invalidate_rkey = wr->rkey; } imm_wr.wr.next = NULL; From 38e2c5ad6f1170941e2cb19eddece4462ffb9e97 Mon Sep 17 00:00:00 2001 From: Etienne AUJAMES Date: Wed, 31 Dec 2025 14:07:45 +0100 Subject: [PATCH 3588/3902] IB/cache: update gid cache on client reregister event MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [ Upstream commit ddd6c8c873e912cb1ead79def54de5e24ff71c80 ] Some HCAs (e.g: ConnectX4) do not trigger a IB_EVENT_GID_CHANGE on subnet prefix update from SM (PortInfo). Since the commit d58c23c92548 ("IB/core: Only update PKEY and GID caches on respective events"), the GID cache is updated exclusively on IB_EVENT_GID_CHANGE. If this event is not emitted, the subnet prefix in the IPoIB interface’s hardware address remains set to its default value (0xfe80000000000000). Then rdma_bind_addr() failed because it relies on hardware address to find the port GID (subnet_prefix + port GUID). This patch fixes this issue by updating the GID cache on IB_EVENT_CLIENT_REREGISTER event (emitted on PortInfo::ClientReregister=1). Fixes: d58c23c92548 ("IB/core: Only update PKEY and GID caches on respective events") Signed-off-by: Etienne AUJAMES Link: https://patch.msgid.link/aVUfsO58QIDn5bGX@eaujamesFR0130 Reviewed-by: Parav Pandit Signed-off-by: Leon Romanovsky Signed-off-by: Sasha Levin --- drivers/infiniband/core/cache.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/drivers/infiniband/core/cache.c b/drivers/infiniband/core/cache.c index 81cf3c902e8195..0fc1c5bce2f0d3 100644 --- a/drivers/infiniband/core/cache.c +++ b/drivers/infiniband/core/cache.c @@ -1537,7 +1537,8 @@ static void ib_cache_event_task(struct work_struct *_work) * the cache. */ ret = ib_cache_update(work->event.device, work->event.element.port_num, - work->event.event == IB_EVENT_GID_CHANGE, + work->event.event == IB_EVENT_GID_CHANGE || + work->event.event == IB_EVENT_CLIENT_REREGISTER, work->event.event == IB_EVENT_PKEY_CHANGE, work->enforce_security); From 0cbec8b49270f3f0600b8e3ef5e8f0d233dcea27 Mon Sep 17 00:00:00 2001 From: Chengchang Tang Date: Sun, 4 Jan 2026 14:40:54 +0800 Subject: [PATCH 3589/3902] RDMA/hns: Fix WQ_MEM_RECLAIM warning [ Upstream commit c0a26bbd3f99b7b03f072e3409aff4e6ec8af6f6 ] When sunrpc is used, if a reset triggered, our wq may lead the following trace: workqueue: WQ_MEM_RECLAIM xprtiod:xprt_rdma_connect_worker [rpcrdma] is flushing !WQ_MEM_RECLAIM hns_roce_irq_workq:flush_work_handle [hns_roce_hw_v2] WARNING: CPU: 0 PID: 8250 at kernel/workqueue.c:2644 check_flush_dependency+0xe0/0x144 Call trace: check_flush_dependency+0xe0/0x144 start_flush_work.constprop.0+0x1d0/0x2f0 __flush_work.isra.0+0x40/0xb0 flush_work+0x14/0x30 hns_roce_v2_destroy_qp+0xac/0x1e0 [hns_roce_hw_v2] ib_destroy_qp_user+0x9c/0x2b4 rdma_destroy_qp+0x34/0xb0 rpcrdma_ep_destroy+0x28/0xcc [rpcrdma] rpcrdma_ep_put+0x74/0xb4 [rpcrdma] rpcrdma_xprt_disconnect+0x1d8/0x260 [rpcrdma] xprt_rdma_connect_worker+0xc0/0x120 [rpcrdma] process_one_work+0x1cc/0x4d0 worker_thread+0x154/0x414 kthread+0x104/0x144 ret_from_fork+0x10/0x18 Since QP destruction frees memory, this wq should have the WQ_MEM_RECLAIM. Fixes: ffd541d45726 ("RDMA/hns: Add the workqueue framework for flush cqe handler") Signed-off-by: Chengchang Tang Signed-off-by: Junxian Huang Link: https://patch.msgid.link/20260104064057.1582216-2-huangjunxian6@hisilicon.com Signed-off-by: Leon Romanovsky Signed-off-by: Sasha Levin --- drivers/infiniband/hw/hns/hns_roce_hw_v2.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/drivers/infiniband/hw/hns/hns_roce_hw_v2.c b/drivers/infiniband/hw/hns/hns_roce_hw_v2.c index 63052c0e76133b..cb0bbc4167b0da 100644 --- a/drivers/infiniband/hw/hns/hns_roce_hw_v2.c +++ b/drivers/infiniband/hw/hns/hns_roce_hw_v2.c @@ -6878,7 +6878,8 @@ static int hns_roce_v2_init_eq_table(struct hns_roce_dev *hr_dev) INIT_WORK(&hr_dev->ecc_work, fmea_ram_ecc_work); - hr_dev->irq_workq = alloc_ordered_workqueue("hns_roce_irq_workq", 0); + hr_dev->irq_workq = alloc_ordered_workqueue("hns_roce_irq_workq", + WQ_MEM_RECLAIM); if (!hr_dev->irq_workq) { dev_err(dev, "failed to create irq workqueue.\n"); ret = -ENOMEM; From 4994b9be17ff742113973f7966080f98c251e1a5 Mon Sep 17 00:00:00 2001 From: Junxian Huang Date: Sun, 4 Jan 2026 14:40:56 +0800 Subject: [PATCH 3590/3902] RDMA/hns: Fix RoCEv1 failure due to DSCP [ Upstream commit 84bd5d60f0a2b9c763c5e6d0b3d8f4f61f6c5470 ] DSCP is not supported in RoCEv1, but get_dscp() is still called. If get_dscp() returns an error, it'll eventually cause create_ah to fail even when using RoCEv1. Correct the return value and avoid calling get_dscp() when using RoCEv1. Fixes: ee20cc17e9d8 ("RDMA/hns: Support DSCP") Signed-off-by: Junxian Huang Link: https://patch.msgid.link/20260104064057.1582216-4-huangjunxian6@hisilicon.com Signed-off-by: Leon Romanovsky Signed-off-by: Sasha Levin --- drivers/infiniband/hw/hns/hns_roce_ah.c | 23 +++++++++--------- drivers/infiniband/hw/hns/hns_roce_hw_v2.c | 28 ++++++++++++---------- 2 files changed, 26 insertions(+), 25 deletions(-) diff --git a/drivers/infiniband/hw/hns/hns_roce_ah.c b/drivers/infiniband/hw/hns/hns_roce_ah.c index 307c35888b3003..3b6c6a6e9f9779 100644 --- a/drivers/infiniband/hw/hns/hns_roce_ah.c +++ b/drivers/infiniband/hw/hns/hns_roce_ah.c @@ -61,7 +61,7 @@ int hns_roce_create_ah(struct ib_ah *ibah, struct rdma_ah_init_attr *init_attr, u8 tclass = get_tclass(grh); u8 priority = 0; u8 tc_mode = 0; - int ret; + int ret = 0; if (hr_dev->pci_dev->revision == PCI_REVISION_ID_HIP08 && udata) { ret = -EOPNOTSUPP; @@ -78,19 +78,18 @@ int hns_roce_create_ah(struct ib_ah *ibah, struct rdma_ah_init_attr *init_attr, ah->av.flowlabel = grh->flow_label; ah->av.udp_sport = get_ah_udp_sport(ah_attr); ah->av.tclass = tclass; + ah->av.sl = rdma_ah_get_sl(ah_attr); - ret = hr_dev->hw->get_dscp(hr_dev, tclass, &tc_mode, &priority); - if (ret == -EOPNOTSUPP) - ret = 0; - - if (ret && grh->sgid_attr->gid_type == IB_GID_TYPE_ROCE_UDP_ENCAP) - goto err_out; + if (grh->sgid_attr->gid_type == IB_GID_TYPE_ROCE_UDP_ENCAP) { + ret = hr_dev->hw->get_dscp(hr_dev, tclass, &tc_mode, &priority); + if (ret == -EOPNOTSUPP) + ret = 0; + else if (ret) + goto err_out; - if (tc_mode == HNAE3_TC_MAP_MODE_DSCP && - grh->sgid_attr->gid_type == IB_GID_TYPE_ROCE_UDP_ENCAP) - ah->av.sl = priority; - else - ah->av.sl = rdma_ah_get_sl(ah_attr); + if (tc_mode == HNAE3_TC_MAP_MODE_DSCP) + ah->av.sl = priority; + } if (!check_sl_valid(hr_dev, ah->av.sl)) { ret = -EINVAL; diff --git a/drivers/infiniband/hw/hns/hns_roce_hw_v2.c b/drivers/infiniband/hw/hns/hns_roce_hw_v2.c index cb0bbc4167b0da..b0e7c5c6e2ff5f 100644 --- a/drivers/infiniband/hw/hns/hns_roce_hw_v2.c +++ b/drivers/infiniband/hw/hns/hns_roce_hw_v2.c @@ -4975,20 +4975,22 @@ static int hns_roce_set_sl(struct ib_qp *ibqp, struct ib_device *ibdev = &hr_dev->ib_dev; int ret; - ret = hns_roce_hw_v2_get_dscp(hr_dev, get_tclass(&attr->ah_attr.grh), - &hr_qp->tc_mode, &hr_qp->priority); - if (ret && ret != -EOPNOTSUPP && - grh->sgid_attr->gid_type == IB_GID_TYPE_ROCE_UDP_ENCAP) { - ibdev_err_ratelimited(ibdev, - "failed to get dscp, ret = %d.\n", ret); - return ret; - } + hr_qp->sl = rdma_ah_get_sl(&attr->ah_attr); - if (hr_qp->tc_mode == HNAE3_TC_MAP_MODE_DSCP && - grh->sgid_attr->gid_type == IB_GID_TYPE_ROCE_UDP_ENCAP) - hr_qp->sl = hr_qp->priority; - else - hr_qp->sl = rdma_ah_get_sl(&attr->ah_attr); + if (grh->sgid_attr->gid_type == IB_GID_TYPE_ROCE_UDP_ENCAP) { + ret = hns_roce_hw_v2_get_dscp(hr_dev, + get_tclass(&attr->ah_attr.grh), + &hr_qp->tc_mode, &hr_qp->priority); + if (ret && ret != -EOPNOTSUPP) { + ibdev_err_ratelimited(ibdev, + "failed to get dscp, ret = %d.\n", + ret); + return ret; + } + + if (hr_qp->tc_mode == HNAE3_TC_MAP_MODE_DSCP) + hr_qp->sl = hr_qp->priority; + } if (!check_sl_valid(hr_dev, hr_qp->sl)) return -EINVAL; From 8bf968d68ea6c43c78db708f92fa76ba4a15cadd Mon Sep 17 00:00:00 2001 From: Chengchang Tang Date: Sun, 4 Jan 2026 14:40:57 +0800 Subject: [PATCH 3591/3902] RDMA/hns: Notify ULP of remaining soft-WCs during reset [ Upstream commit 0789f929900d85b80b343c5f04f8b9444e991384 ] During a reset, software-generated WCs cannot be reported via interrupts. This may cause the ULP to miss some WCs. To avoid this, add check in the CQ arm process: if a hardware reset has occurred and there are still unreported soft-WCs, notify the ULP to handle the remaining WCs, thereby preventing any loss of completions. Fixes: 626903e9355b ("RDMA/hns: Add support for reporting wc as software mode") Signed-off-by: Chengchang Tang Signed-off-by: Junxian Huang Link: https://patch.msgid.link/20260104064057.1582216-5-huangjunxian6@hisilicon.com Signed-off-by: Leon Romanovsky Signed-off-by: Sasha Levin --- drivers/infiniband/hw/hns/hns_roce_hw_v2.c | 23 ++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/drivers/infiniband/hw/hns/hns_roce_hw_v2.c b/drivers/infiniband/hw/hns/hns_roce_hw_v2.c index b0e7c5c6e2ff5f..f895731ad74a0f 100644 --- a/drivers/infiniband/hw/hns/hns_roce_hw_v2.c +++ b/drivers/infiniband/hw/hns/hns_roce_hw_v2.c @@ -3661,6 +3661,23 @@ static void hns_roce_v2_write_cqc(struct hns_roce_dev *hr_dev, HNS_ROCE_V2_CQ_DEFAULT_INTERVAL); } +static bool left_sw_wc(struct hns_roce_dev *hr_dev, struct hns_roce_cq *hr_cq) +{ + struct hns_roce_qp *hr_qp; + + list_for_each_entry(hr_qp, &hr_cq->sq_list, sq_node) { + if (hr_qp->sq.head != hr_qp->sq.tail) + return true; + } + + list_for_each_entry(hr_qp, &hr_cq->rq_list, rq_node) { + if (hr_qp->rq.head != hr_qp->rq.tail) + return true; + } + + return false; +} + static int hns_roce_v2_req_notify_cq(struct ib_cq *ibcq, enum ib_cq_notify_flags flags) { @@ -3669,6 +3686,12 @@ static int hns_roce_v2_req_notify_cq(struct ib_cq *ibcq, struct hns_roce_v2_db cq_db = {}; u32 notify_flag; + if (hr_dev->state >= HNS_ROCE_DEVICE_STATE_RST_DOWN) { + if ((flags & IB_CQ_REPORT_MISSED_EVENTS) && + left_sw_wc(hr_dev, hr_cq)) + return 1; + return 0; + } /* * flags = 0, then notify_flag : next * flags = 1, then notify flag : solocited From 51c89a5247bf5a48505693c8d6cc6f5da357252b Mon Sep 17 00:00:00 2001 From: Maher Sanalla Date: Sun, 4 Jan 2026 15:51:35 +0200 Subject: [PATCH 3592/3902] RDMA/mlx5: Fix ucaps init error flow [ Upstream commit 6dc78c53de99e4ed9868d4f0fc6da6e46f52fe4d ] In mlx5_ib_stage_caps_init(), if mlx5_ib_init_ucaps() fails after mlx5_ib_init_var_table() succeeds, the VAR bitmap is leaked since the function returns without cleanup. Thus, cleanup the var table bitmap in case of error of initializing ucaps before exiting, preventing the leak above. Fixes: cf7174e8982f ("RDMA/mlx5: Create UCAP char devices for supported device capabilities") Signed-off-by: Maher Sanalla Reviewed-by: Yishai Hadas Link: https://patch.msgid.link/20260104-ib-core-misc-v1-3-00367f77f3a8@nvidia.com Reviewed-by: Kalesh AP Signed-off-by: Leon Romanovsky Signed-off-by: Sasha Levin --- drivers/infiniband/hw/mlx5/main.c | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/drivers/infiniband/hw/mlx5/main.c b/drivers/infiniband/hw/mlx5/main.c index fc1e86f6c40971..8f69c8c1ba54d6 100644 --- a/drivers/infiniband/hw/mlx5/main.c +++ b/drivers/infiniband/hw/mlx5/main.c @@ -4462,12 +4462,16 @@ static int mlx5_ib_stage_caps_init(struct mlx5_ib_dev *dev) MLX5_HCA_CAP_2_GENERAL_OBJECT_TYPES_RDMA_CTRL) { err = mlx5_ib_init_ucaps(dev); if (err) - return err; + goto err_ucaps; } dev->ib_dev.use_cq_dim = true; return 0; + +err_ucaps: + bitmap_free(dev->var_table.bitmap); + return err; } static const struct ib_device_ops mlx5_ib_dev_port_ops = { From 9b82bdb9e7c93e05dacb2d33a19a006d9b9f74c6 Mon Sep 17 00:00:00 2001 From: Dan Williams Date: Mon, 15 Dec 2025 16:56:11 -0800 Subject: [PATCH 3593/3902] cxl/mem: Fix devm_cxl_memdev_edac_release() confusion [ Upstream commit 10016118b6fade907143a32a7aeaa777063dc79c ] A device release method is only for undoing allocations on the path to preparing the device for device_add(). In contrast, devm allocations are post device_add(), are acquired during / after ->probe() and are released synchronous with ->remove(). So, a "devm" helper in a "release" method is a clear anti-pattern. Move this devm release action where it belongs, an action created at edac object creation time. Otherwise, this leaks resources until cxl_memdev_release() time which may be long after these xarray and error record caches have gone idle. Note, this also fixes up the type of @cxlmd->err_rec_array which needlessly dropped type-safety. Fixes: 0b5ccb0de1e2 ("cxl/edac: Support for finding memory operation attributes from the current boot") Cc: Dave Jiang Cc: Jonathan Cameron Cc: Shiju Jose Cc: Alison Schofield Reviewed-by: Alison Schofield Reviewed-by: Ben Cheatham Reviewed-by: Dave Jiang Reviewed-by: Jonathan Cameron Tested-by: Shiju Jose Reviewed-by: Shiju Jose Tested-by: Alejandro Lucero Link: https://patch.msgid.link/20251216005616.3090129-2-dan.j.williams@intel.com Signed-off-by: Dan Williams Signed-off-by: Dave Jiang Signed-off-by: Sasha Levin --- drivers/cxl/core/edac.c | 64 ++++++++++++++++++++++----------------- drivers/cxl/core/memdev.c | 1 - drivers/cxl/cxlmem.h | 5 +-- 3 files changed, 38 insertions(+), 32 deletions(-) diff --git a/drivers/cxl/core/edac.c b/drivers/cxl/core/edac.c index 79994ca9bc9f37..81160260e26b7e 100644 --- a/drivers/cxl/core/edac.c +++ b/drivers/cxl/core/edac.c @@ -1988,6 +1988,40 @@ static int cxl_memdev_soft_ppr_init(struct cxl_memdev *cxlmd, return 0; } +static void err_rec_free(void *_cxlmd) +{ + struct cxl_memdev *cxlmd = _cxlmd; + struct cxl_mem_err_rec *array_rec = cxlmd->err_rec_array; + struct cxl_event_gen_media *rec_gen_media; + struct cxl_event_dram *rec_dram; + unsigned long index; + + cxlmd->err_rec_array = NULL; + xa_for_each(&array_rec->rec_dram, index, rec_dram) + kfree(rec_dram); + xa_destroy(&array_rec->rec_dram); + + xa_for_each(&array_rec->rec_gen_media, index, rec_gen_media) + kfree(rec_gen_media); + xa_destroy(&array_rec->rec_gen_media); + kfree(array_rec); +} + +static int devm_cxl_memdev_setup_err_rec(struct cxl_memdev *cxlmd) +{ + struct cxl_mem_err_rec *array_rec = + kzalloc(sizeof(*array_rec), GFP_KERNEL); + + if (!array_rec) + return -ENOMEM; + + xa_init(&array_rec->rec_gen_media); + xa_init(&array_rec->rec_dram); + cxlmd->err_rec_array = array_rec; + + return devm_add_action_or_reset(&cxlmd->dev, err_rec_free, cxlmd); +} + int devm_cxl_memdev_edac_register(struct cxl_memdev *cxlmd) { struct edac_dev_feature ras_features[CXL_NR_EDAC_DEV_FEATURES]; @@ -2038,15 +2072,9 @@ int devm_cxl_memdev_edac_register(struct cxl_memdev *cxlmd) } if (repair_inst) { - struct cxl_mem_err_rec *array_rec = - devm_kzalloc(&cxlmd->dev, sizeof(*array_rec), - GFP_KERNEL); - if (!array_rec) - return -ENOMEM; - - xa_init(&array_rec->rec_gen_media); - xa_init(&array_rec->rec_dram); - cxlmd->err_rec_array = array_rec; + rc = devm_cxl_memdev_setup_err_rec(cxlmd); + if (rc) + return rc; } } @@ -2088,22 +2116,4 @@ int devm_cxl_region_edac_register(struct cxl_region *cxlr) } EXPORT_SYMBOL_NS_GPL(devm_cxl_region_edac_register, "CXL"); -void devm_cxl_memdev_edac_release(struct cxl_memdev *cxlmd) -{ - struct cxl_mem_err_rec *array_rec = cxlmd->err_rec_array; - struct cxl_event_gen_media *rec_gen_media; - struct cxl_event_dram *rec_dram; - unsigned long index; - - if (!IS_ENABLED(CONFIG_CXL_EDAC_MEM_REPAIR) || !array_rec) - return; - - xa_for_each(&array_rec->rec_dram, index, rec_dram) - kfree(rec_dram); - xa_destroy(&array_rec->rec_dram); - xa_for_each(&array_rec->rec_gen_media, index, rec_gen_media) - kfree(rec_gen_media); - xa_destroy(&array_rec->rec_gen_media); -} -EXPORT_SYMBOL_NS_GPL(devm_cxl_memdev_edac_release, "CXL"); diff --git a/drivers/cxl/core/memdev.c b/drivers/cxl/core/memdev.c index e370d733e44001..4dff7f44d908e0 100644 --- a/drivers/cxl/core/memdev.c +++ b/drivers/cxl/core/memdev.c @@ -27,7 +27,6 @@ static void cxl_memdev_release(struct device *dev) struct cxl_memdev *cxlmd = to_cxl_memdev(dev); ida_free(&cxl_memdev_ida, cxlmd->id); - devm_cxl_memdev_edac_release(cxlmd); kfree(cxlmd); } diff --git a/drivers/cxl/cxlmem.h b/drivers/cxl/cxlmem.h index 434031a0c1f74a..c12ab4fc951238 100644 --- a/drivers/cxl/cxlmem.h +++ b/drivers/cxl/cxlmem.h @@ -63,7 +63,7 @@ struct cxl_memdev { int depth; u8 scrub_cycle; int scrub_region_id; - void *err_rec_array; + struct cxl_mem_err_rec *err_rec_array; }; static inline struct cxl_memdev *to_cxl_memdev(struct device *dev) @@ -877,7 +877,6 @@ int devm_cxl_memdev_edac_register(struct cxl_memdev *cxlmd); int devm_cxl_region_edac_register(struct cxl_region *cxlr); int cxl_store_rec_gen_media(struct cxl_memdev *cxlmd, union cxl_event *evt); int cxl_store_rec_dram(struct cxl_memdev *cxlmd, union cxl_event *evt); -void devm_cxl_memdev_edac_release(struct cxl_memdev *cxlmd); #else static inline int devm_cxl_memdev_edac_register(struct cxl_memdev *cxlmd) { return 0; } @@ -889,8 +888,6 @@ static inline int cxl_store_rec_gen_media(struct cxl_memdev *cxlmd, static inline int cxl_store_rec_dram(struct cxl_memdev *cxlmd, union cxl_event *evt) { return 0; } -static inline void devm_cxl_memdev_edac_release(struct cxl_memdev *cxlmd) -{ return; } #endif #ifdef CONFIG_CXL_SUSPEND From 709db4b476e254579d9c48ec34d397a41ca0c407 Mon Sep 17 00:00:00 2001 From: Waqar Hameed Date: Sat, 20 Dec 2025 23:35:58 +0100 Subject: [PATCH 3594/3902] power: supply: ab8500: Fix use-after-free in power_supply_changed() [ Upstream commit c4af8a98bb52825a5331ae1d0604c0ea6956ba4b ] Using the `devm_` variant for requesting IRQ _before_ the `devm_` variant for allocating/registering the `power_supply` handle, means that the `power_supply` handle will be deallocated/unregistered _before_ the interrupt handler (since `devm_` naturally deallocates in reverse allocation order). This means that during removal, there is a race condition where an interrupt can fire just _after_ the `power_supply` handle has been freed, *but* just _before_ the corresponding unregistration of the IRQ handler has run. This will lead to the IRQ handler calling `power_supply_changed()` with a freed `power_supply` handle. Which usually crashes the system or otherwise silently corrupts the memory... Note that there is a similar situation which can also happen during `probe()`; the possibility of an interrupt firing _before_ registering the `power_supply` handle. This would then lead to the nasty situation of using the `power_supply` handle *uninitialized* in `power_supply_changed()`. Commit 1c1f13a006ed ("power: supply: ab8500: Move to componentized binding") introduced this issue during a refactorization. Fix this racy use-after-free by making sure the IRQ is requested _after_ the registration of the `power_supply` handle. Fixes: 1c1f13a006ed ("power: supply: ab8500: Move to componentized binding") Signed-off-by: Waqar Hameed Reviewed-by: Linus Walleij Link: https://patch.msgid.link/ccf83a09942cb8dda3dff70b2682f2c2e9cb97f2.1766268280.git.waqar.hameed@axis.com Signed-off-by: Sebastian Reichel Signed-off-by: Sasha Levin --- drivers/power/supply/ab8500_charger.c | 40 +++++++++++++-------------- 1 file changed, 20 insertions(+), 20 deletions(-) diff --git a/drivers/power/supply/ab8500_charger.c b/drivers/power/supply/ab8500_charger.c index 5f4537766e5b90..1813fbdfa1c1f6 100644 --- a/drivers/power/supply/ab8500_charger.c +++ b/drivers/power/supply/ab8500_charger.c @@ -3466,26 +3466,6 @@ static int ab8500_charger_probe(struct platform_device *pdev) return ret; } - /* Request interrupts */ - for (i = 0; i < ARRAY_SIZE(ab8500_charger_irq); i++) { - irq = platform_get_irq_byname(pdev, ab8500_charger_irq[i].name); - if (irq < 0) - return irq; - - ret = devm_request_threaded_irq(dev, - irq, NULL, ab8500_charger_irq[i].isr, - IRQF_SHARED | IRQF_NO_SUSPEND | IRQF_ONESHOT, - ab8500_charger_irq[i].name, di); - - if (ret != 0) { - dev_err(dev, "failed to request %s IRQ %d: %d\n" - , ab8500_charger_irq[i].name, irq, ret); - return ret; - } - dev_dbg(dev, "Requested %s IRQ %d: %d\n", - ab8500_charger_irq[i].name, irq, ret); - } - /* initialize lock */ spin_lock_init(&di->usb_state.usb_lock); mutex_init(&di->usb_ipt_crnt_lock); @@ -3614,6 +3594,26 @@ static int ab8500_charger_probe(struct platform_device *pdev) return PTR_ERR(di->usb_chg.psy); } + /* Request interrupts */ + for (i = 0; i < ARRAY_SIZE(ab8500_charger_irq); i++) { + irq = platform_get_irq_byname(pdev, ab8500_charger_irq[i].name); + if (irq < 0) + return irq; + + ret = devm_request_threaded_irq(dev, + irq, NULL, ab8500_charger_irq[i].isr, + IRQF_SHARED | IRQF_NO_SUSPEND | IRQF_ONESHOT, + ab8500_charger_irq[i].name, di); + + if (ret != 0) { + dev_err(dev, "failed to request %s IRQ %d: %d\n" + , ab8500_charger_irq[i].name, irq, ret); + return ret; + } + dev_dbg(dev, "Requested %s IRQ %d: %d\n", + ab8500_charger_irq[i].name, irq, ret); + } + /* * Check what battery we have, since we always have the USB * psy, use that as a handle. From f27eb76def5c07e4d7cc468b40741f19dafc83ce Mon Sep 17 00:00:00 2001 From: Waqar Hameed Date: Sat, 20 Dec 2025 23:35:59 +0100 Subject: [PATCH 3595/3902] power: supply: act8945a: Fix use-after-free in power_supply_changed() [ Upstream commit 3291c51d4684d048dd2eb91b5b65fcfdaf72141f ] Using the `devm_` variant for requesting IRQ _before_ the `devm_` variant for allocating/registering the `power_supply` handle, means that the `power_supply` handle will be deallocated/unregistered _before_ the interrupt handler (since `devm_` naturally deallocates in reverse allocation order). This means that during removal, there is a race condition where an interrupt can fire just _after_ the `power_supply` handle has been freed, *but* just _before_ the corresponding unregistration of the IRQ handler has run. This will lead to the IRQ handler calling `power_supply_changed()` with a freed `power_supply` handle. Which usually crashes the system or otherwise silently corrupts the memory... Note that there is a similar situation which can also happen during `probe()`; the possibility of an interrupt firing _before_ registering the `power_supply` handle. This would then lead to the nasty situation of using the `power_supply` handle *uninitialized* in `power_supply_changed()`. Fix this racy use-after-free by making sure the IRQ is requested _after_ the registration of the `power_supply` handle. Fixes: a09209acd6a8 ("power: supply: act8945a_charger: Add status change update support") Signed-off-by: Waqar Hameed Link: https://patch.msgid.link/bcf3a23b5187df0bba54a8c8fe09f8b8a0031dee.1766268280.git.waqar.hameed@axis.com Signed-off-by: Sebastian Reichel Signed-off-by: Sasha Levin --- drivers/power/supply/act8945a_charger.c | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/drivers/power/supply/act8945a_charger.c b/drivers/power/supply/act8945a_charger.c index 3901a02f326a55..9dec4486b1439a 100644 --- a/drivers/power/supply/act8945a_charger.c +++ b/drivers/power/supply/act8945a_charger.c @@ -597,14 +597,6 @@ static int act8945a_charger_probe(struct platform_device *pdev) return irq ?: -ENXIO; } - ret = devm_request_irq(&pdev->dev, irq, act8945a_status_changed, - IRQF_TRIGGER_FALLING, "act8945a_interrupt", - charger); - if (ret) { - dev_err(&pdev->dev, "failed to request nIRQ pin IRQ\n"); - return ret; - } - charger->desc.name = "act8945a-charger"; charger->desc.get_property = act8945a_charger_get_property; charger->desc.properties = act8945a_charger_props; @@ -625,6 +617,14 @@ static int act8945a_charger_probe(struct platform_device *pdev) return PTR_ERR(charger->psy); } + ret = devm_request_irq(&pdev->dev, irq, act8945a_status_changed, + IRQF_TRIGGER_FALLING, "act8945a_interrupt", + charger); + if (ret) { + dev_err(&pdev->dev, "failed to request nIRQ pin IRQ\n"); + return ret; + } + platform_set_drvdata(pdev, charger); INIT_WORK(&charger->work, act8945a_work); From 4b6fb0b6124f558131e502e3ffd03e6583b3ace6 Mon Sep 17 00:00:00 2001 From: Waqar Hameed Date: Sat, 20 Dec 2025 23:35:59 +0100 Subject: [PATCH 3596/3902] power: supply: bq256xx: Fix use-after-free in power_supply_changed() [ Upstream commit 8005843369723d9c8975b7c4202d1b85d6125302 ] Using the `devm_` variant for requesting IRQ _before_ the `devm_` variant for allocating/registering the `power_supply` handle, means that the `power_supply` handle will be deallocated/unregistered _before_ the interrupt handler (since `devm_` naturally deallocates in reverse allocation order). This means that during removal, there is a race condition where an interrupt can fire just _after_ the `power_supply` handle has been freed, *but* just _before_ the corresponding unregistration of the IRQ handler has run. This will lead to the IRQ handler calling `power_supply_changed()` with a freed `power_supply` handle. Which usually crashes the system or otherwise silently corrupts the memory... Note that there is a similar situation which can also happen during `probe()`; the possibility of an interrupt firing _before_ registering the `power_supply` handle. This would then lead to the nasty situation of using the `power_supply` handle *uninitialized* in `power_supply_changed()`. Fix this racy use-after-free by making sure the IRQ is requested _after_ the registration of the `power_supply` handle. Fixes: 32e4978bb920 ("power: supply: bq256xx: Introduce the BQ256XX charger driver") Signed-off-by: Waqar Hameed Link: https://patch.msgid.link/39da6da8cc060fa0382ca859f65071e791cb6119.1766268280.git.waqar.hameed@axis.com Signed-off-by: Sebastian Reichel Signed-off-by: Sasha Levin --- drivers/power/supply/bq256xx_charger.c | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/drivers/power/supply/bq256xx_charger.c b/drivers/power/supply/bq256xx_charger.c index ae14162f017a9e..d3de4f8b80db1e 100644 --- a/drivers/power/supply/bq256xx_charger.c +++ b/drivers/power/supply/bq256xx_charger.c @@ -1741,6 +1741,12 @@ static int bq256xx_probe(struct i2c_client *client) usb_register_notifier(bq->usb3_phy, &bq->usb_nb); } + ret = bq256xx_power_supply_init(bq, &psy_cfg, dev); + if (ret) { + dev_err(dev, "Failed to register power supply\n"); + return ret; + } + if (client->irq) { ret = devm_request_threaded_irq(dev, client->irq, NULL, bq256xx_irq_handler_thread, @@ -1753,12 +1759,6 @@ static int bq256xx_probe(struct i2c_client *client) } } - ret = bq256xx_power_supply_init(bq, &psy_cfg, dev); - if (ret) { - dev_err(dev, "Failed to register power supply\n"); - return ret; - } - ret = bq256xx_hw_init(bq); if (ret) { dev_err(dev, "Cannot initialize the chip.\n"); From 03d1e4ee4e6aa6d2966e883e4ca0e5be73bf1b7c Mon Sep 17 00:00:00 2001 From: Waqar Hameed Date: Sat, 20 Dec 2025 23:35:59 +0100 Subject: [PATCH 3597/3902] power: supply: bq25980: Fix use-after-free in power_supply_changed() [ Upstream commit 5f0b1cb41906e86b64bf69f5ededb83b0d757c27 ] Using the `devm_` variant for requesting IRQ _before_ the `devm_` variant for allocating/registering the `power_supply` handle, means that the `power_supply` handle will be deallocated/unregistered _before_ the interrupt handler (since `devm_` naturally deallocates in reverse allocation order). This means that during removal, there is a race condition where an interrupt can fire just _after_ the `power_supply` handle has been freed, *but* just _before_ the corresponding unregistration of the IRQ handler has run. This will lead to the IRQ handler calling `power_supply_changed()` with a freed `power_supply` handle. Which usually crashes the system or otherwise silently corrupts the memory... Note that there is a similar situation which can also happen during `probe()`; the possibility of an interrupt firing _before_ registering the `power_supply` handle. This would then lead to the nasty situation of using the `power_supply` handle *uninitialized* in `power_supply_changed()`. Fix this racy use-after-free by making sure the IRQ is requested _after_ the registration of the `power_supply` handle. Fixes: 5069185fc18e ("power: supply: bq25980: Add support for the BQ259xx family") Signed-off-by: Waqar Hameed Link: https://patch.msgid.link/8763035cadb959e14787b3837f2d3db61f6e1c34.1766268280.git.waqar.hameed@axis.com Signed-off-by: Sebastian Reichel Signed-off-by: Sasha Levin --- drivers/power/supply/bq25980_charger.c | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/drivers/power/supply/bq25980_charger.c b/drivers/power/supply/bq25980_charger.c index 723858d62d1414..73f06f09f134cd 100644 --- a/drivers/power/supply/bq25980_charger.c +++ b/drivers/power/supply/bq25980_charger.c @@ -1241,6 +1241,12 @@ static int bq25980_probe(struct i2c_client *client) return ret; } + ret = bq25980_power_supply_init(bq, dev); + if (ret) { + dev_err(dev, "Failed to register power supply\n"); + return ret; + } + if (client->irq) { ret = devm_request_threaded_irq(dev, client->irq, NULL, bq25980_irq_handler_thread, @@ -1251,12 +1257,6 @@ static int bq25980_probe(struct i2c_client *client) return ret; } - ret = bq25980_power_supply_init(bq, dev); - if (ret) { - dev_err(dev, "Failed to register power supply\n"); - return ret; - } - ret = bq25980_hw_init(bq); if (ret) { dev_err(dev, "Cannot initialize the chip.\n"); From 2841bbb5a35c4449c0a0458e8e476b2a62f95147 Mon Sep 17 00:00:00 2001 From: Waqar Hameed Date: Sat, 20 Dec 2025 23:36:00 +0100 Subject: [PATCH 3598/3902] power: supply: cpcap-battery: Fix use-after-free in power_supply_changed() [ Upstream commit 642f33e34b969eedec334738fd5df95d2dc42742 ] Using the `devm_` variant for requesting IRQ _before_ the `devm_` variant for allocating/registering the `power_supply` handle, means that the `power_supply` handle will be deallocated/unregistered _before_ the interrupt handler (since `devm_` naturally deallocates in reverse allocation order). This means that during removal, there is a race condition where an interrupt can fire just _after_ the `power_supply` handle has been freed, *but* just _before_ the corresponding unregistration of the IRQ handler has run. This will lead to the IRQ handler calling `power_supply_changed()` with a freed `power_supply` handle. Which usually crashes the system or otherwise silently corrupts the memory... Note that there is a similar situation which can also happen during `probe()`; the possibility of an interrupt firing _before_ registering the `power_supply` handle. This would then lead to the nasty situation of using the `power_supply` handle *uninitialized* in `power_supply_changed()`. Fix this racy use-after-free by making sure the IRQ is requested _after_ the registration of the `power_supply` handle. Fixes: 874b2adbed12 ("power: supply: cpcap-battery: Add a battery driver") Signed-off-by: Waqar Hameed Link: https://patch.msgid.link/81db58d610c9a51a68184f856cd431a934cccee2.1766268280.git.waqar.hameed@axis.com Signed-off-by: Sebastian Reichel Signed-off-by: Sasha Levin --- drivers/power/supply/cpcap-battery.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/drivers/power/supply/cpcap-battery.c b/drivers/power/supply/cpcap-battery.c index 8106d1edcbc26a..507fdc1c866d56 100644 --- a/drivers/power/supply/cpcap-battery.c +++ b/drivers/power/supply/cpcap-battery.c @@ -1122,10 +1122,6 @@ static int cpcap_battery_probe(struct platform_device *pdev) platform_set_drvdata(pdev, ddata); - error = cpcap_battery_init_interrupts(pdev, ddata); - if (error) - return error; - error = cpcap_battery_init_iio(ddata); if (error) return error; @@ -1142,6 +1138,10 @@ static int cpcap_battery_probe(struct platform_device *pdev) return error; } + error = cpcap_battery_init_interrupts(pdev, ddata); + if (error) + return error; + atomic_set(&ddata->active, 1); error = cpcap_battery_calibrate(ddata); From 8c89aade8335e26a6a7dcda18992d15f51943927 Mon Sep 17 00:00:00 2001 From: Waqar Hameed Date: Sat, 20 Dec 2025 23:36:00 +0100 Subject: [PATCH 3599/3902] power: supply: goldfish: Fix use-after-free in power_supply_changed() [ Upstream commit b2ce982e2e0c888dc55c888ad0e20ea04daf2e6b ] Using the `devm_` variant for requesting IRQ _before_ the `devm_` variant for allocating/registering the `power_supply` handle, means that the `power_supply` handle will be deallocated/unregistered _before_ the interrupt handler (since `devm_` naturally deallocates in reverse allocation order). This means that during removal, there is a race condition where an interrupt can fire just _after_ the `power_supply` handle has been freed, *but* just _before_ the corresponding unregistration of the IRQ handler has run. This will lead to the IRQ handler calling `power_supply_changed()` with a freed `power_supply` handle. Which usually crashes the system or otherwise silently corrupts the memory... Note that there is a similar situation which can also happen during `probe()`; the possibility of an interrupt firing _before_ registering the `power_supply` handle. This would then lead to the nasty situation of using the `power_supply` handle *uninitialized* in `power_supply_changed()`. Fix this racy use-after-free by making sure the IRQ is requested _after_ the registration of the `power_supply` handle. Fixes: 84d7b7687489 ("power: Add battery driver for goldfish emulator") Signed-off-by: Waqar Hameed Link: https://patch.msgid.link/500a606bb6fb6f2bb8d797e19a00cea9dd7b03c1.1766268280.git.waqar.hameed@axis.com Signed-off-by: Sebastian Reichel Signed-off-by: Sasha Levin --- drivers/power/supply/goldfish_battery.c | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/drivers/power/supply/goldfish_battery.c b/drivers/power/supply/goldfish_battery.c index 479195e35d734a..5aa24e4dc4455d 100644 --- a/drivers/power/supply/goldfish_battery.c +++ b/drivers/power/supply/goldfish_battery.c @@ -224,12 +224,6 @@ static int goldfish_battery_probe(struct platform_device *pdev) if (data->irq < 0) return -ENODEV; - ret = devm_request_irq(&pdev->dev, data->irq, - goldfish_battery_interrupt, - IRQF_SHARED, pdev->name, data); - if (ret) - return ret; - psy_cfg.drv_data = data; data->ac = devm_power_supply_register(&pdev->dev, @@ -244,6 +238,12 @@ static int goldfish_battery_probe(struct platform_device *pdev) if (IS_ERR(data->battery)) return PTR_ERR(data->battery); + ret = devm_request_irq(&pdev->dev, data->irq, + goldfish_battery_interrupt, + IRQF_SHARED, pdev->name, data); + if (ret) + return ret; + GOLDFISH_BATTERY_WRITE(data, BATTERY_INT_ENABLE, BATTERY_INT_MASK); return 0; } From a8b7117ae3a791c6a328674d05a06cd45d8241bd Mon Sep 17 00:00:00 2001 From: Waqar Hameed Date: Sat, 20 Dec 2025 23:36:01 +0100 Subject: [PATCH 3600/3902] power: supply: pm8916_bms_vm: Fix use-after-free in power_supply_changed() [ Upstream commit 62914959b35e9a1e29cc0f64cb8cfc5075a5366f ] Using the `devm_` variant for requesting IRQ _before_ the `devm_` variant for allocating/registering the `power_supply` handle, means that the `power_supply` handle will be deallocated/unregistered _before_ the interrupt handler (since `devm_` naturally deallocates in reverse allocation order). This means that during removal, there is a race condition where an interrupt can fire just _after_ the `power_supply` handle has been freed, *but* just _before_ the corresponding unregistration of the IRQ handler has run. This will lead to the IRQ handler calling `power_supply_changed()` with a freed `power_supply` handle. Which usually crashes the system or otherwise silently corrupts the memory... Note that there is a similar situation which can also happen during `probe()`; the possibility of an interrupt firing _before_ registering the `power_supply` handle. This would then lead to the nasty situation of using the `power_supply` handle *uninitialized* in `power_supply_changed()`. Fix this racy use-after-free by making sure the IRQ is requested _after_ the registration of the `power_supply` handle. Fixes: 098bce1838e0 ("power: supply: Add pm8916 VM-BMS support") Signed-off-by: Waqar Hameed Reviewed-by: Nikita Travkin Link: https://patch.msgid.link/2749c09ff81fcac87ae48147e216135450d8c067.1766268280.git.waqar.hameed@axis.com Signed-off-by: Sebastian Reichel Signed-off-by: Sasha Levin --- drivers/power/supply/pm8916_bms_vm.c | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/drivers/power/supply/pm8916_bms_vm.c b/drivers/power/supply/pm8916_bms_vm.c index 5120be086e6ffc..de5d571c03e212 100644 --- a/drivers/power/supply/pm8916_bms_vm.c +++ b/drivers/power/supply/pm8916_bms_vm.c @@ -167,15 +167,6 @@ static int pm8916_bms_vm_battery_probe(struct platform_device *pdev) if (ret < 0) return -EINVAL; - irq = platform_get_irq_byname(pdev, "fifo"); - if (irq < 0) - return irq; - - ret = devm_request_threaded_irq(dev, irq, NULL, pm8916_bms_vm_fifo_update_done_irq, - IRQF_ONESHOT, "pm8916_vm_bms", bat); - if (ret) - return ret; - ret = regmap_bulk_read(bat->regmap, bat->reg + PM8916_PERPH_TYPE, &tmp, 2); if (ret) goto comm_error; @@ -220,6 +211,15 @@ static int pm8916_bms_vm_battery_probe(struct platform_device *pdev) if (ret) return dev_err_probe(dev, ret, "Unable to get battery info\n"); + irq = platform_get_irq_byname(pdev, "fifo"); + if (irq < 0) + return irq; + + ret = devm_request_threaded_irq(dev, irq, NULL, pm8916_bms_vm_fifo_update_done_irq, + IRQF_ONESHOT, "pm8916_vm_bms", bat); + if (ret) + return ret; + platform_set_drvdata(pdev, bat); return 0; From 08e674e9862a2db46fb234eb7c5442455ece0131 Mon Sep 17 00:00:00 2001 From: Waqar Hameed Date: Sat, 20 Dec 2025 23:36:01 +0100 Subject: [PATCH 3601/3902] power: supply: pm8916_lbc: Fix use-after-free in power_supply_changed() [ Upstream commit b7508129978ae1e2ed9b0410396abc05def9c4eb ] Using the `devm_` variant for requesting IRQ _before_ the `devm_` variant for allocating/registering the `power_supply` handle, means that the `power_supply` handle will be deallocated/unregistered _before_ the interrupt handler (since `devm_` naturally deallocates in reverse allocation order). This means that during removal, there is a race condition where an interrupt can fire just _after_ the `power_supply` handle has been freed, *but* just _before_ the corresponding unregistration of the IRQ handler has run. This will lead to the IRQ handler calling `power_supply_changed()` with a freed `power_supply` handle. Which usually crashes the system or otherwise silently corrupts the memory... Note that there is a similar situation which can also happen during `probe()`; the possibility of an interrupt firing _before_ registering the `power_supply` handle. This would then lead to the nasty situation of using the `power_supply` handle *uninitialized* in `power_supply_changed()`. Fix this racy use-after-free by making sure the IRQ is requested _after_ the registration of the `power_supply` handle. Fixes: f8d7a3d21160 ("power: supply: Add driver for pm8916 lbc") Signed-off-by: Waqar Hameed Reviewed-by: Nikita Travkin Link: https://patch.msgid.link/64d8dd3675a4e59fa32c3e0ef451f12d1f7ed18f.1766268280.git.waqar.hameed@axis.com Signed-off-by: Sebastian Reichel Signed-off-by: Sasha Levin --- drivers/power/supply/pm8916_lbc.c | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/drivers/power/supply/pm8916_lbc.c b/drivers/power/supply/pm8916_lbc.c index c74b75b1b2676c..3ca717d84aade6 100644 --- a/drivers/power/supply/pm8916_lbc.c +++ b/drivers/power/supply/pm8916_lbc.c @@ -274,15 +274,6 @@ static int pm8916_lbc_charger_probe(struct platform_device *pdev) return dev_err_probe(dev, -EINVAL, "Wrong amount of reg values: %d (4 expected)\n", len); - irq = platform_get_irq_byname(pdev, "usb_vbus"); - if (irq < 0) - return irq; - - ret = devm_request_threaded_irq(dev, irq, NULL, pm8916_lbc_charger_state_changed_irq, - IRQF_ONESHOT, "pm8916_lbc", chg); - if (ret) - return ret; - ret = device_property_read_u32_array(dev, "reg", chg->reg, len); if (ret) return ret; @@ -332,6 +323,15 @@ static int pm8916_lbc_charger_probe(struct platform_device *pdev) if (ret) return dev_err_probe(dev, ret, "Unable to get battery info\n"); + irq = platform_get_irq_byname(pdev, "usb_vbus"); + if (irq < 0) + return irq; + + ret = devm_request_threaded_irq(dev, irq, NULL, pm8916_lbc_charger_state_changed_irq, + IRQF_ONESHOT, "pm8916_lbc", chg); + if (ret) + return ret; + chg->edev = devm_extcon_dev_allocate(dev, pm8916_lbc_charger_cable); if (IS_ERR(chg->edev)) return PTR_ERR(chg->edev); From 64e15155095f39f4dec9b4659da1238ef8fc54d4 Mon Sep 17 00:00:00 2001 From: Waqar Hameed Date: Sat, 20 Dec 2025 23:36:02 +0100 Subject: [PATCH 3602/3902] power: supply: rt9455: Fix use-after-free in power_supply_changed() [ Upstream commit e2febe375e5ea5afed92f4cd9711bde8f24ee6d2 ] Using the `devm_` variant for requesting IRQ _before_ the `devm_` variant for allocating/registering the `power_supply` handle, means that the `power_supply` handle will be deallocated/unregistered _before_ the interrupt handler (since `devm_` naturally deallocates in reverse allocation order). This means that during removal, there is a race condition where an interrupt can fire just _after_ the `power_supply` handle has been freed, *but* just _before_ the corresponding unregistration of the IRQ handler has run. This will lead to the IRQ handler calling `power_supply_changed()` with a freed `power_supply` handle. Which usually crashes the system or otherwise silently corrupts the memory... Note that there is a similar situation which can also happen during `probe()`; the possibility of an interrupt firing _before_ registering the `power_supply` handle. This would then lead to the nasty situation of using the `power_supply` handle *uninitialized* in `power_supply_changed()`. Fix this racy use-after-free by making sure the IRQ is requested _after_ the registration of the `power_supply` handle. Fixes: e86d69dd786e ("power_supply: Add support for Richtek RT9455 battery charger") Signed-off-by: Waqar Hameed Link: https://patch.msgid.link/1567d831e04c3e2fcb9e18dd36b7bcba4634581a.1766268280.git.waqar.hameed@axis.com Signed-off-by: Sebastian Reichel Signed-off-by: Sasha Levin --- drivers/power/supply/rt9455_charger.c | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/drivers/power/supply/rt9455_charger.c b/drivers/power/supply/rt9455_charger.c index 1ffe7f02932f6d..5130d2395e88fa 100644 --- a/drivers/power/supply/rt9455_charger.c +++ b/drivers/power/supply/rt9455_charger.c @@ -1663,6 +1663,15 @@ static int rt9455_probe(struct i2c_client *client) rt9455_charger_config.supplied_to = rt9455_charger_supplied_to; rt9455_charger_config.num_supplicants = ARRAY_SIZE(rt9455_charger_supplied_to); + + info->charger = devm_power_supply_register(dev, &rt9455_charger_desc, + &rt9455_charger_config); + if (IS_ERR(info->charger)) { + dev_err(dev, "Failed to register charger\n"); + ret = PTR_ERR(info->charger); + goto put_usb_notifier; + } + ret = devm_request_threaded_irq(dev, client->irq, NULL, rt9455_irq_handler_thread, IRQF_TRIGGER_LOW | IRQF_ONESHOT, @@ -1678,14 +1687,6 @@ static int rt9455_probe(struct i2c_client *client) goto put_usb_notifier; } - info->charger = devm_power_supply_register(dev, &rt9455_charger_desc, - &rt9455_charger_config); - if (IS_ERR(info->charger)) { - dev_err(dev, "Failed to register charger\n"); - ret = PTR_ERR(info->charger); - goto put_usb_notifier; - } - return 0; put_usb_notifier: From 861dda7a9074c0ff67788928165ae39d7f647491 Mon Sep 17 00:00:00 2001 From: Waqar Hameed Date: Sat, 20 Dec 2025 23:36:02 +0100 Subject: [PATCH 3603/3902] power: supply: sbs-battery: Fix use-after-free in power_supply_changed() [ Upstream commit 8d59cf3887fbabacef53bfba473e33e8a8d9d07b ] Using the `devm_` variant for requesting IRQ _before_ the `devm_` variant for allocating/registering the `power_supply` handle, means that the `power_supply` handle will be deallocated/unregistered _before_ the interrupt handler (since `devm_` naturally deallocates in reverse allocation order). This means that during removal, there is a race condition where an interrupt can fire just _after_ the `power_supply` handle has been freed, *but* just _before_ the corresponding unregistration of the IRQ handler has run. This will lead to the IRQ handler calling `power_supply_changed()` with a freed `power_supply` handle. Which usually crashes the system or otherwise silently corrupts the memory... Note that there is a similar situation which can also happen during `probe()`; the possibility of an interrupt firing _before_ registering the `power_supply` handle. This would then lead to the nasty situation of using the `power_supply` handle *uninitialized* in `power_supply_changed()`. Fix this racy use-after-free by making sure the IRQ is requested _after_ the registration of the `power_supply` handle. Keep the old behavior of just printing a warning in case of any failures during the IRQ request and finishing the probe successfully. Fixes: d2cec82c2880 ("power: sbs-battery: Request threaded irq and fix dev callback cookie") Signed-off-by: Waqar Hameed Reviewed-by: Phil Reid Link: https://patch.msgid.link/0ef896e002495e615157b482d18a437af19ddcd0.1766268280.git.waqar.hameed@axis.com Signed-off-by: Sebastian Reichel Signed-off-by: Sasha Levin --- drivers/power/supply/sbs-battery.c | 36 +++++++++++++++--------------- 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/drivers/power/supply/sbs-battery.c b/drivers/power/supply/sbs-battery.c index 943c82ee978f40..43c48196c16741 100644 --- a/drivers/power/supply/sbs-battery.c +++ b/drivers/power/supply/sbs-battery.c @@ -1174,24 +1174,6 @@ static int sbs_probe(struct i2c_client *client) i2c_set_clientdata(client, chip); - if (!chip->gpio_detect) - goto skip_gpio; - - irq = gpiod_to_irq(chip->gpio_detect); - if (irq <= 0) { - dev_warn(&client->dev, "Failed to get gpio as irq: %d\n", irq); - goto skip_gpio; - } - - rc = devm_request_threaded_irq(&client->dev, irq, NULL, sbs_irq, - IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING | IRQF_ONESHOT, - dev_name(&client->dev), chip); - if (rc) { - dev_warn(&client->dev, "Failed to request irq: %d\n", rc); - goto skip_gpio; - } - -skip_gpio: /* * Before we register, we might need to make sure we can actually talk * to the battery. @@ -1217,6 +1199,24 @@ static int sbs_probe(struct i2c_client *client) return dev_err_probe(&client->dev, PTR_ERR(chip->power_supply), "Failed to register power supply\n"); + if (!chip->gpio_detect) + goto out; + + irq = gpiod_to_irq(chip->gpio_detect); + if (irq <= 0) { + dev_warn(&client->dev, "Failed to get gpio as irq: %d\n", irq); + goto out; + } + + rc = devm_request_threaded_irq(&client->dev, irq, NULL, sbs_irq, + IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING | IRQF_ONESHOT, + dev_name(&client->dev), chip); + if (rc) { + dev_warn(&client->dev, "Failed to request irq: %d\n", rc); + goto out; + } + +out: dev_info(&client->dev, "%s: battery gas gauge device registered\n", client->name); From a465a149922283d095253155102d3e17037b5c4d Mon Sep 17 00:00:00 2001 From: Alexander Koskovich Date: Sun, 14 Dec 2025 19:16:18 +0000 Subject: [PATCH 3604/3902] power: reset: nvmem-reboot-mode: respect cell size for nvmem_cell_write [ Upstream commit 36b05629226413836cfbb3fbe6689cd188bca156 ] Some platforms expose reboot mode cells that are smaller than an unsigned int, in which cases lead to write failures. Read the cell first to determine actual size and only write the number of bytes the cell can hold. Fixes: 7a78a7f7695b ("power: reset: nvmem-reboot-mode: use NVMEM as reboot mode write interface") Signed-off-by: Alexander Koskovich Link: https://patch.msgid.link/20251214191529.2470580-1-akoskovich@pm.me Signed-off-by: Sebastian Reichel Signed-off-by: Sasha Levin --- drivers/power/reset/nvmem-reboot-mode.c | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/drivers/power/reset/nvmem-reboot-mode.c b/drivers/power/reset/nvmem-reboot-mode.c index 41530b70cfc48c..d260715fccf67f 100644 --- a/drivers/power/reset/nvmem-reboot-mode.c +++ b/drivers/power/reset/nvmem-reboot-mode.c @@ -10,6 +10,7 @@ #include #include #include +#include struct nvmem_reboot_mode { struct reboot_mode_driver reboot; @@ -19,12 +20,22 @@ struct nvmem_reboot_mode { static int nvmem_reboot_mode_write(struct reboot_mode_driver *reboot, unsigned int magic) { - int ret; struct nvmem_reboot_mode *nvmem_rbm; + size_t buf_len; + void *buf; + int ret; nvmem_rbm = container_of(reboot, struct nvmem_reboot_mode, reboot); - ret = nvmem_cell_write(nvmem_rbm->cell, &magic, sizeof(magic)); + buf = nvmem_cell_read(nvmem_rbm->cell, &buf_len); + if (IS_ERR(buf)) + return PTR_ERR(buf); + kfree(buf); + + if (buf_len > sizeof(magic)) + return -EINVAL; + + ret = nvmem_cell_write(nvmem_rbm->cell, &magic, buf_len); if (ret < 0) dev_err(reboot->dev, "update reboot mode bits failed\n"); From 64327c46f25513cfe63feb1b1e171ca73faded36 Mon Sep 17 00:00:00 2001 From: Haotian Zhang Date: Thu, 4 Dec 2025 16:34:36 +0800 Subject: [PATCH 3605/3902] power: supply: bq27xxx: fix wrong errno when bus ops are unsupported [ Upstream commit 688364a11647dc09ba1e4429313e0008066ec790 ] bq27xxx_write(), bq27xxx_read_block(), and bq27xxx_write_block() return -EPERM when the bus callback pointer is NULL. A NULL callback indicates the operation is not supported by the bus/driver, not that permission is denied. Return -EOPNOTSUPP instead of -EPERM when di->bus.write/ read_bulk/write_bulk is NULL. Fixes: 14073f6614f6 ("power: supply: bq27xxx: Add bulk transfer bus methods") Signed-off-by: Haotian Zhang Reviewed-by: Matt Ranostay Link: https://patch.msgid.link/20251204083436.1367-1-vulab@iscas.ac.cn Signed-off-by: Sebastian Reichel Signed-off-by: Sasha Levin --- drivers/power/supply/bq27xxx_battery.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/drivers/power/supply/bq27xxx_battery.c b/drivers/power/supply/bq27xxx_battery.c index 19445e39651c71..45f0e39b8c2dd4 100644 --- a/drivers/power/supply/bq27xxx_battery.c +++ b/drivers/power/supply/bq27xxx_battery.c @@ -1172,7 +1172,7 @@ static inline int bq27xxx_write(struct bq27xxx_device_info *di, int reg_index, return -EINVAL; if (!di->bus.write) - return -EPERM; + return -EOPNOTSUPP; ret = di->bus.write(di, di->regs[reg_index], value, single); if (ret < 0) @@ -1191,7 +1191,7 @@ static inline int bq27xxx_read_block(struct bq27xxx_device_info *di, int reg_ind return -EINVAL; if (!di->bus.read_bulk) - return -EPERM; + return -EOPNOTSUPP; ret = di->bus.read_bulk(di, di->regs[reg_index], data, len); if (ret < 0) @@ -1210,7 +1210,7 @@ static inline int bq27xxx_write_block(struct bq27xxx_device_info *di, int reg_in return -EINVAL; if (!di->bus.write_bulk) - return -EPERM; + return -EOPNOTSUPP; ret = di->bus.write_bulk(di, di->regs[reg_index], data, len); if (ret < 0) From c0def811ad8d642dca9b6d31a198cc39f5f90837 Mon Sep 17 00:00:00 2001 From: Waqar Hameed Date: Sat, 20 Dec 2025 23:46:24 +0100 Subject: [PATCH 3606/3902] power: supply: wm97xx: Fix NULL pointer dereference in power_supply_changed() [ Upstream commit 39fe0eac6d755ef215026518985fcf8de9360e9e ] In `probe()`, `request_irq()` is called before allocating/registering a `power_supply` handle. If an interrupt is fired between the call to `request_irq()` and `power_supply_register()`, the `power_supply` handle will be used uninitialized in `power_supply_changed()` in `wm97xx_bat_update()` (triggered from the interrupt handler). This will lead to a `NULL` pointer dereference since Fix this racy `NULL` pointer dereference by making sure the IRQ is requested _after_ the registration of the `power_supply` handle. Since the IRQ is the last thing requests in the `probe()` now, remove the error path for freeing it. Instead add one for unregistering the `power_supply` handle when IRQ request fails. Fixes: 7c87942aef52 ("wm97xx_battery: Use irq to detect charger state") Signed-off-by: Waqar Hameed Link: https://patch.msgid.link/97b55f0479a932eea7213844bf66f28a974e27a2.1766270196.git.waqar.hameed@axis.com Signed-off-by: Sebastian Reichel Signed-off-by: Sasha Levin --- drivers/power/supply/wm97xx_battery.c | 34 +++++++++++++++------------ 1 file changed, 19 insertions(+), 15 deletions(-) diff --git a/drivers/power/supply/wm97xx_battery.c b/drivers/power/supply/wm97xx_battery.c index b3b0c37a9dd2d5..f00722c88c6fea 100644 --- a/drivers/power/supply/wm97xx_battery.c +++ b/drivers/power/supply/wm97xx_battery.c @@ -178,12 +178,6 @@ static int wm97xx_bat_probe(struct platform_device *dev) "failed to get charge GPIO\n"); if (charge_gpiod) { gpiod_set_consumer_name(charge_gpiod, "BATT CHRG"); - ret = request_irq(gpiod_to_irq(charge_gpiod), - wm97xx_chrg_irq, 0, - "AC Detect", dev); - if (ret) - return dev_err_probe(&dev->dev, ret, - "failed to request GPIO irq\n"); props++; /* POWER_SUPPLY_PROP_STATUS */ } @@ -199,10 +193,8 @@ static int wm97xx_bat_probe(struct platform_device *dev) props++; /* POWER_SUPPLY_PROP_VOLTAGE_MIN */ prop = kcalloc(props, sizeof(*prop), GFP_KERNEL); - if (!prop) { - ret = -ENOMEM; - goto err3; - } + if (!prop) + return -ENOMEM; prop[i++] = POWER_SUPPLY_PROP_PRESENT; if (charge_gpiod) @@ -236,15 +228,27 @@ static int wm97xx_bat_probe(struct platform_device *dev) schedule_work(&bat_work); } else { ret = PTR_ERR(bat_psy); - goto err4; + goto free; + } + + if (charge_gpiod) { + ret = request_irq(gpiod_to_irq(charge_gpiod), wm97xx_chrg_irq, + 0, "AC Detect", dev); + if (ret) { + dev_err_probe(&dev->dev, ret, + "failed to request GPIO irq\n"); + goto unregister; + } } return 0; -err4: + +unregister: + power_supply_unregister(bat_psy); + +free: kfree(prop); -err3: - if (charge_gpiod) - free_irq(gpiod_to_irq(charge_gpiod), dev); + return ret; } From 33e396029002bc4f2d888e07555edb807c1fadfd Mon Sep 17 00:00:00 2001 From: Roman Penyaev Date: Wed, 7 Jan 2026 17:15:08 +0100 Subject: [PATCH 3607/3902] RDMA/rtrs-srv: fix SG mapping [ Upstream commit 83835f7c07b523c7ca2a5ad0a511670b5810539e ] This fixes the following error on the server side: RTRS server session allocation failed: -EINVAL caused by the caller of the `ib_dma_map_sg()`, which does not expect less mapped entries, than requested, which is in the order of things and can be easily reproduced on the machine with enabled IOMMU. The fix is to treat any positive number of mapped sg entries as a successful mapping and cache DMA addresses by traversing modified SG table. Fixes: 9cb837480424 ("RDMA/rtrs: server: main functionality") Signed-off-by: Roman Penyaev Signed-off-by: Jack Wang Signed-off-by: Grzegorz Prajsner Link: https://patch.msgid.link/20260107161517.56357-2-haris.iqbal@ionos.com Signed-off-by: Leon Romanovsky Signed-off-by: Sasha Levin --- drivers/infiniband/ulp/rtrs/rtrs-srv.c | 25 ++++++++++++++++++++----- 1 file changed, 20 insertions(+), 5 deletions(-) diff --git a/drivers/infiniband/ulp/rtrs/rtrs-srv.c b/drivers/infiniband/ulp/rtrs/rtrs-srv.c index 7a402eb8e0bf0c..adb798e2a54ae1 100644 --- a/drivers/infiniband/ulp/rtrs/rtrs-srv.c +++ b/drivers/infiniband/ulp/rtrs/rtrs-srv.c @@ -595,7 +595,7 @@ static int map_cont_bufs(struct rtrs_srv_path *srv_path) srv_path->mrs_num++) { struct rtrs_srv_mr *srv_mr = &srv_path->mrs[srv_path->mrs_num]; struct scatterlist *s; - int nr, nr_sgt, chunks; + int nr, nr_sgt, chunks, ind; sgt = &srv_mr->sgt; chunks = chunks_per_mr * srv_path->mrs_num; @@ -625,7 +625,7 @@ static int map_cont_bufs(struct rtrs_srv_path *srv_path) } nr = ib_map_mr_sg(mr, sgt->sgl, nr_sgt, NULL, max_chunk_size); - if (nr != nr_sgt) { + if (nr < nr_sgt) { err = nr < 0 ? nr : -EINVAL; goto dereg_mr; } @@ -641,9 +641,24 @@ static int map_cont_bufs(struct rtrs_srv_path *srv_path) goto dereg_mr; } } - /* Eventually dma addr for each chunk can be cached */ - for_each_sg(sgt->sgl, s, nr_sgt, i) - srv_path->dma_addr[chunks + i] = sg_dma_address(s); + + /* + * Cache DMA addresses by traversing sg entries. If + * regions were merged, an inner loop is required to + * populate the DMA address array by traversing larger + * regions. + */ + ind = chunks; + for_each_sg(sgt->sgl, s, nr_sgt, i) { + unsigned int dma_len = sg_dma_len(s); + u64 dma_addr = sg_dma_address(s); + u64 dma_addr_end = dma_addr + dma_len; + + do { + srv_path->dma_addr[ind++] = dma_addr; + dma_addr += max_chunk_size; + } while (dma_addr < dma_addr_end); + } ib_update_fast_reg_key(mr, ib_inc_rkey(mr->rkey)); srv_mr->mr = mr; From 5c07aef09a121a4cd622a71eb0753a9e135c84a8 Mon Sep 17 00:00:00 2001 From: Jiasheng Jiang Date: Mon, 12 Jan 2026 01:54:12 +0000 Subject: [PATCH 3608/3902] RDMA/rxe: Fix double free in rxe_srq_from_init [ Upstream commit 0beefd0e15d962f497aad750b2d5e9c3570b66d1 ] In rxe_srq_from_init(), the queue pointer 'q' is assigned to 'srq->rq.queue' before copying the SRQ number to user space. If copy_to_user() fails, the function calls rxe_queue_cleanup() to free the queue, but leaves the now-invalid pointer in 'srq->rq.queue'. The caller of rxe_srq_from_init() (rxe_create_srq) eventually calls rxe_srq_cleanup() upon receiving the error, which triggers a second rxe_queue_cleanup() on the same memory, leading to a double free. The call trace looks like this: kmem_cache_free+0x.../0x... rxe_queue_cleanup+0x1a/0x30 [rdma_rxe] rxe_srq_cleanup+0x42/0x60 [rdma_rxe] rxe_elem_release+0x31/0x70 [rdma_rxe] rxe_create_srq+0x12b/0x1a0 [rdma_rxe] ib_create_srq_user+0x9a/0x150 [ib_core] Fix this by moving 'srq->rq.queue = q' after copy_to_user. Fixes: aae0484e15f0 ("IB/rxe: avoid srq memory leak") Signed-off-by: Jiasheng Jiang Link: https://patch.msgid.link/20260112015412.29458-1-jiashengjiangcool@gmail.com Reviewed-by: Zhu Yanjun Signed-off-by: Leon Romanovsky Signed-off-by: Sasha Levin --- drivers/infiniband/sw/rxe/rxe_srq.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/drivers/infiniband/sw/rxe/rxe_srq.c b/drivers/infiniband/sw/rxe/rxe_srq.c index 2a234f26ac1044..c9a7cd38953d31 100644 --- a/drivers/infiniband/sw/rxe/rxe_srq.c +++ b/drivers/infiniband/sw/rxe/rxe_srq.c @@ -77,9 +77,6 @@ int rxe_srq_from_init(struct rxe_dev *rxe, struct rxe_srq *srq, goto err_free; } - srq->rq.queue = q; - init->attr.max_wr = srq->rq.max_wr; - if (uresp) { if (copy_to_user(&uresp->srq_num, &srq->srq_num, sizeof(uresp->srq_num))) { @@ -88,6 +85,9 @@ int rxe_srq_from_init(struct rxe_dev *rxe, struct rxe_srq *srq, } } + srq->rq.queue = q; + init->attr.max_wr = srq->rq.max_wr; + return 0; err_free: From eb715133e0ae12514bba4d2d5ce1dee774476056 Mon Sep 17 00:00:00 2001 From: Jacob Moroni Date: Mon, 12 Jan 2026 02:00:06 +0000 Subject: [PATCH 3609/3902] RDMA/iwcm: Fix workqueue list corruption by removing work_list [ Upstream commit 7874eeacfa42177565c01d5198726671acf7adf2 ] The commit e1168f0 ("RDMA/iwcm: Simplify cm_event_handler()") changed the work submission logic to unconditionally call queue_work() with the expectation that queue_work() would have no effect if work was already pending. The problem is that a free list of struct iwcm_work is used (for which struct work_struct is embedded), so each call to queue_work() is basically unique and therefore does indeed queue the work. This causes a problem in the work handler which walks the work_list until it's empty to process entries. This means that a single run of the work handler could process item N+1 and release it back to the free list while the actual workqueue entry is still queued. It could then get reused (INIT_WORK...) and lead to list corruption in the workqueue logic. Fix this by just removing the work_list. The workqueue already does this for us. This fixes the following error that was observed when stress testing with ucmatose on an Intel E830 in iWARP mode: [ 151.465780] list_del corruption. next->prev should be ffff9f0915c69c08, but was ffff9f0a1116be08. (next=ffff9f0a15b11c08) [ 151.466639] ------------[ cut here ]------------ [ 151.466986] kernel BUG at lib/list_debug.c:67! [ 151.467349] Oops: invalid opcode: 0000 [#1] SMP NOPTI [ 151.467753] CPU: 14 UID: 0 PID: 2306 Comm: kworker/u64:18 Not tainted 6.19.0-rc4+ #1 PREEMPT(voluntary) [ 151.468466] Hardware name: QEMU Ubuntu 24.04 PC (i440FX + PIIX, 1996), BIOS 1.16.3-debian-1.16.3-2 04/01/2014 [ 151.469192] Workqueue: 0x0 (iw_cm_wq) [ 151.469478] RIP: 0010:__list_del_entry_valid_or_report+0xf0/0x100 [ 151.469942] Code: c7 58 5f 4c b2 e8 10 50 aa ff 0f 0b 48 89 ef e8 36 57 cb ff 48 8b 55 08 48 89 e9 48 89 de 48 c7 c7 a8 5f 4c b2 e8 f0 4f aa ff <0f> 0b 66 2e 0f 1f 84 00 00 00 00 00 0f 1f 40 00 90 90 90 90 90 90 [ 151.471323] RSP: 0000:ffffb15644e7bd68 EFLAGS: 00010046 [ 151.471712] RAX: 000000000000006d RBX: ffff9f0915c69c08 RCX: 0000000000000027 [ 151.472243] RDX: 0000000000000000 RSI: 0000000000000000 RDI: ffff9f0a37d9c600 [ 151.472768] RBP: ffff9f0a15b11c08 R08: 0000000000000000 R09: c0000000ffff7fff [ 151.473294] R10: 0000000000000001 R11: ffffb15644e7bba8 R12: ffff9f092339ee68 [ 151.473817] R13: ffff9f0900059c28 R14: ffff9f092339ee78 R15: 0000000000000000 [ 151.474344] FS: 0000000000000000(0000) GS:ffff9f0a847b5000(0000) knlGS:0000000000000000 [ 151.474934] CS: 0010 DS: 0000 ES: 0000 CR0: 0000000080050033 [ 151.475362] CR2: 0000559e233a9088 CR3: 000000020296b004 CR4: 0000000000770ef0 [ 151.475895] PKRU: 55555554 [ 151.476118] Call Trace: [ 151.476331] [ 151.476497] move_linked_works+0x49/0xa0 [ 151.476792] __pwq_activate_work.isra.46+0x2f/0xa0 [ 151.477151] pwq_dec_nr_in_flight+0x1e0/0x2f0 [ 151.477479] process_scheduled_works+0x1c8/0x410 [ 151.477823] worker_thread+0x125/0x260 [ 151.478108] ? __pfx_worker_thread+0x10/0x10 [ 151.478430] kthread+0xfe/0x240 [ 151.478671] ? __pfx_kthread+0x10/0x10 [ 151.478955] ? __pfx_kthread+0x10/0x10 [ 151.479240] ret_from_fork+0x208/0x270 [ 151.479523] ? __pfx_kthread+0x10/0x10 [ 151.479806] ret_from_fork_asm+0x1a/0x30 [ 151.480103] Fixes: e1168f09b331 ("RDMA/iwcm: Simplify cm_event_handler()") Signed-off-by: Jacob Moroni Link: https://patch.msgid.link/20260112020006.1352438-1-jmoroni@google.com Reviewed-by: Bart Van Assche Signed-off-by: Leon Romanovsky Signed-off-by: Sasha Levin --- drivers/infiniband/core/iwcm.c | 56 +++++++++++++--------------------- drivers/infiniband/core/iwcm.h | 1 - 2 files changed, 21 insertions(+), 36 deletions(-) diff --git a/drivers/infiniband/core/iwcm.c b/drivers/infiniband/core/iwcm.c index 62410578dec37d..eb942ab9c40559 100644 --- a/drivers/infiniband/core/iwcm.c +++ b/drivers/infiniband/core/iwcm.c @@ -95,7 +95,6 @@ static struct workqueue_struct *iwcm_wq; struct iwcm_work { struct work_struct work; struct iwcm_id_private *cm_id; - struct list_head list; struct iw_cm_event event; struct list_head free_list; }; @@ -178,7 +177,6 @@ static int alloc_work_entries(struct iwcm_id_private *cm_id_priv, int count) return -ENOMEM; } work->cm_id = cm_id_priv; - INIT_LIST_HEAD(&work->list); put_work(work); } return 0; @@ -213,7 +211,6 @@ static void free_cm_id(struct iwcm_id_private *cm_id_priv) static bool iwcm_deref_id(struct iwcm_id_private *cm_id_priv) { if (refcount_dec_and_test(&cm_id_priv->refcount)) { - BUG_ON(!list_empty(&cm_id_priv->work_list)); free_cm_id(cm_id_priv); return true; } @@ -260,7 +257,6 @@ struct iw_cm_id *iw_create_cm_id(struct ib_device *device, refcount_set(&cm_id_priv->refcount, 1); init_waitqueue_head(&cm_id_priv->connect_wait); init_completion(&cm_id_priv->destroy_comp); - INIT_LIST_HEAD(&cm_id_priv->work_list); INIT_LIST_HEAD(&cm_id_priv->work_free_list); return &cm_id_priv->id; @@ -1007,13 +1003,13 @@ static int process_event(struct iwcm_id_private *cm_id_priv, } /* - * Process events on the work_list for the cm_id. If the callback - * function requests that the cm_id be deleted, a flag is set in the - * cm_id flags to indicate that when the last reference is - * removed, the cm_id is to be destroyed. This is necessary to - * distinguish between an object that will be destroyed by the app - * thread asleep on the destroy_comp list vs. an object destroyed - * here synchronously when the last reference is removed. + * Process events for the cm_id. If the callback function requests + * that the cm_id be deleted, a flag is set in the cm_id flags to + * indicate that when the last reference is removed, the cm_id is + * to be destroyed. This is necessary to distinguish between an + * object that will be destroyed by the app thread asleep on the + * destroy_comp list vs. an object destroyed here synchronously + * when the last reference is removed. */ static void cm_work_handler(struct work_struct *_work) { @@ -1024,35 +1020,26 @@ static void cm_work_handler(struct work_struct *_work) int ret = 0; spin_lock_irqsave(&cm_id_priv->lock, flags); - while (!list_empty(&cm_id_priv->work_list)) { - work = list_first_entry(&cm_id_priv->work_list, - struct iwcm_work, list); - list_del_init(&work->list); - levent = work->event; - put_work(work); - spin_unlock_irqrestore(&cm_id_priv->lock, flags); - - if (!test_bit(IWCM_F_DROP_EVENTS, &cm_id_priv->flags)) { - ret = process_event(cm_id_priv, &levent); - if (ret) { - destroy_cm_id(&cm_id_priv->id); - WARN_ON_ONCE(iwcm_deref_id(cm_id_priv)); - } - } else - pr_debug("dropping event %d\n", levent.event); - if (iwcm_deref_id(cm_id_priv)) - return; - spin_lock_irqsave(&cm_id_priv->lock, flags); - } + levent = work->event; + put_work(work); spin_unlock_irqrestore(&cm_id_priv->lock, flags); + + if (!test_bit(IWCM_F_DROP_EVENTS, &cm_id_priv->flags)) { + ret = process_event(cm_id_priv, &levent); + if (ret) { + destroy_cm_id(&cm_id_priv->id); + WARN_ON_ONCE(iwcm_deref_id(cm_id_priv)); + } + } else + pr_debug("dropping event %d\n", levent.event); + if (iwcm_deref_id(cm_id_priv)) + return; } /* * This function is called on interrupt context. Schedule events on * the iwcm_wq thread to allow callback functions to downcall into - * the CM and/or block. Events are queued to a per-CM_ID - * work_list. If this is the first event on the work_list, the work - * element is also queued on the iwcm_wq thread. + * the CM and/or block. * * Each event holds a reference on the cm_id. Until the last posted * event has been delivered and processed, the cm_id cannot be @@ -1094,7 +1081,6 @@ static int cm_event_handler(struct iw_cm_id *cm_id, } refcount_inc(&cm_id_priv->refcount); - list_add_tail(&work->list, &cm_id_priv->work_list); queue_work(iwcm_wq, &work->work); out: spin_unlock_irqrestore(&cm_id_priv->lock, flags); diff --git a/drivers/infiniband/core/iwcm.h b/drivers/infiniband/core/iwcm.h index bf74639be1287c..b56fb12edece40 100644 --- a/drivers/infiniband/core/iwcm.h +++ b/drivers/infiniband/core/iwcm.h @@ -50,7 +50,6 @@ struct iwcm_id_private { struct ib_qp *qp; struct completion destroy_comp; wait_queue_head_t connect_wait; - struct list_head work_list; spinlock_t lock; refcount_t refcount; struct list_head work_free_list; From d9aefb386fdc430a23a7d94d4f2fe2d52ffe75eb Mon Sep 17 00:00:00 2001 From: Krishna Chomal Date: Tue, 13 Jan 2026 23:56:03 +0530 Subject: [PATCH 3610/3902] platform/x86: hp-wmi: fix platform profile values for Omen 16-wf1xxx MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [ Upstream commit 8ca7515d3c76a8b629f703ff8301a75f503bcc50 ] HP Omen 16-wf1xxx (board ID 8C78) currently sends the incorrect Victus-specific thermal profile values via WMI, leading to a logical inconsistency when switching between platform profiles. The driver currently uses Victus S values: 0x00 => Balanced / Low-Power 0x01 => Performance However, Omen Gaming Hub logs / EC register inspection on Windows shows that this board is intended to use: 0x30 => Balanced / Low-Power 0x31 => Performance This patch corrects the thermal profile command values to match the values observed from Omen Gaming Hub logs. The performance benchmarks and peak power draw (from both CPU and GPU) show no observable change with this correction (suggesting that the firmware is currently tolerant of the incorrect values). However sending the correct values prevents potential regressions after future firmware updates. Refactor victus_s_thermal_profile_boards from a list of strings to a dmi_system_id table and move the lookup to module init. The new struct thermal_profile_params is used to store board-specific WMI parameters, allowing the driver to cache these values in a static pointer. This avoids repeated DMI string comparisons and allows marking of DMI table as __initconst. Testing on HP Omen 16-wf1xxx (board 8C78) confirmed WMI codes 0x30/0x31 are now sent, resolving the logical inconsistency and ensuring the value visible in EC registers match the Windows state for this profile. Fixes: fb146a38cb11 ("platform/x86: hp-wmi: Add Omen 16-wf1xxx fan support") Signed-off-by: Krishna Chomal Link: https://patch.msgid.link/20260113182604.115211-2-krishna.chomal108@gmail.com Reviewed-by: Ilpo Järvinen Signed-off-by: Ilpo Järvinen Signed-off-by: Sasha Levin --- drivers/platform/x86/hp/hp-wmi.c | 179 ++++++++++++++++++++++--------- 1 file changed, 127 insertions(+), 52 deletions(-) diff --git a/drivers/platform/x86/hp/hp-wmi.c b/drivers/platform/x86/hp/hp-wmi.c index ad9d9f97960f2b..dfe45692c956a2 100644 --- a/drivers/platform/x86/hp/hp-wmi.c +++ b/drivers/platform/x86/hp/hp-wmi.c @@ -53,6 +53,66 @@ MODULE_ALIAS("wmi:5FB7F034-2C63-45E9-BE91-3D44E2C707E4"); #define zero_if_sup(tmp) (zero_insize_support?0:sizeof(tmp)) // use when zero insize is required +enum hp_thermal_profile_omen_v0 { + HP_OMEN_V0_THERMAL_PROFILE_DEFAULT = 0x00, + HP_OMEN_V0_THERMAL_PROFILE_PERFORMANCE = 0x01, + HP_OMEN_V0_THERMAL_PROFILE_COOL = 0x02, +}; + +enum hp_thermal_profile_omen_v1 { + HP_OMEN_V1_THERMAL_PROFILE_DEFAULT = 0x30, + HP_OMEN_V1_THERMAL_PROFILE_PERFORMANCE = 0x31, + HP_OMEN_V1_THERMAL_PROFILE_COOL = 0x50, +}; + +enum hp_thermal_profile_omen_flags { + HP_OMEN_EC_FLAGS_TURBO = 0x04, + HP_OMEN_EC_FLAGS_NOTIMER = 0x02, + HP_OMEN_EC_FLAGS_JUSTSET = 0x01, +}; + +enum hp_thermal_profile_victus { + HP_VICTUS_THERMAL_PROFILE_DEFAULT = 0x00, + HP_VICTUS_THERMAL_PROFILE_PERFORMANCE = 0x01, + HP_VICTUS_THERMAL_PROFILE_QUIET = 0x03, +}; + +enum hp_thermal_profile_victus_s { + HP_VICTUS_S_THERMAL_PROFILE_DEFAULT = 0x00, + HP_VICTUS_S_THERMAL_PROFILE_PERFORMANCE = 0x01, +}; + +enum hp_thermal_profile { + HP_THERMAL_PROFILE_PERFORMANCE = 0x00, + HP_THERMAL_PROFILE_DEFAULT = 0x01, + HP_THERMAL_PROFILE_COOL = 0x02, + HP_THERMAL_PROFILE_QUIET = 0x03, +}; + +struct thermal_profile_params { + u8 performance; + u8 balanced; + u8 low_power; +}; + +static const struct thermal_profile_params victus_s_thermal_params = { + .performance = HP_VICTUS_S_THERMAL_PROFILE_PERFORMANCE, + .balanced = HP_VICTUS_S_THERMAL_PROFILE_DEFAULT, + .low_power = HP_VICTUS_S_THERMAL_PROFILE_DEFAULT, +}; + +static const struct thermal_profile_params omen_v1_thermal_params = { + .performance = HP_OMEN_V1_THERMAL_PROFILE_PERFORMANCE, + .balanced = HP_OMEN_V1_THERMAL_PROFILE_DEFAULT, + .low_power = HP_OMEN_V1_THERMAL_PROFILE_DEFAULT, +}; + +/* + * A generic pointer for the currently-active board's thermal profile + * parameters. + */ +static struct thermal_profile_params *active_thermal_profile_params; + /* DMI board names of devices that should use the omen specific path for * thermal profiles. * This was obtained by taking a look in the windows omen command center @@ -93,12 +153,40 @@ static const char * const victus_thermal_profile_boards[] = { }; /* DMI Board names of Victus 16-r and Victus 16-s laptops */ -static const char * const victus_s_thermal_profile_boards[] = { - "8BBE", "8BD4", "8BD5", - "8C78", "8C99", "8C9C", - "8D41", +static const struct dmi_system_id victus_s_thermal_profile_boards[] __initconst = { + { + .matches = { DMI_MATCH(DMI_BOARD_NAME, "8BBE") }, + .driver_data = (void *)&victus_s_thermal_params, + }, + { + .matches = { DMI_MATCH(DMI_BOARD_NAME, "8BD4") }, + .driver_data = (void *)&victus_s_thermal_params, + }, + { + .matches = { DMI_MATCH(DMI_BOARD_NAME, "8BD5") }, + .driver_data = (void *)&victus_s_thermal_params, + }, + { + .matches = { DMI_MATCH(DMI_BOARD_NAME, "8C78") }, + .driver_data = (void *)&omen_v1_thermal_params, + }, + { + .matches = { DMI_MATCH(DMI_BOARD_NAME, "8C99") }, + .driver_data = (void *)&victus_s_thermal_params, + }, + { + .matches = { DMI_MATCH(DMI_BOARD_NAME, "8C9C") }, + .driver_data = (void *)&victus_s_thermal_params, + }, + { + .matches = { DMI_MATCH(DMI_BOARD_NAME, "8D41") }, + .driver_data = (void *)&victus_s_thermal_params, + }, + {}, }; +static bool is_victus_s_board; + enum hp_wmi_radio { HPWMI_WIFI = 0x0, HPWMI_BLUETOOTH = 0x1, @@ -219,42 +307,6 @@ enum hp_wireless2_bits { HPWMI_POWER_FW_OR_HW = HPWMI_POWER_BIOS | HPWMI_POWER_HARD, }; -enum hp_thermal_profile_omen_v0 { - HP_OMEN_V0_THERMAL_PROFILE_DEFAULT = 0x00, - HP_OMEN_V0_THERMAL_PROFILE_PERFORMANCE = 0x01, - HP_OMEN_V0_THERMAL_PROFILE_COOL = 0x02, -}; - -enum hp_thermal_profile_omen_v1 { - HP_OMEN_V1_THERMAL_PROFILE_DEFAULT = 0x30, - HP_OMEN_V1_THERMAL_PROFILE_PERFORMANCE = 0x31, - HP_OMEN_V1_THERMAL_PROFILE_COOL = 0x50, -}; - -enum hp_thermal_profile_omen_flags { - HP_OMEN_EC_FLAGS_TURBO = 0x04, - HP_OMEN_EC_FLAGS_NOTIMER = 0x02, - HP_OMEN_EC_FLAGS_JUSTSET = 0x01, -}; - -enum hp_thermal_profile_victus { - HP_VICTUS_THERMAL_PROFILE_DEFAULT = 0x00, - HP_VICTUS_THERMAL_PROFILE_PERFORMANCE = 0x01, - HP_VICTUS_THERMAL_PROFILE_QUIET = 0x03, -}; - -enum hp_thermal_profile_victus_s { - HP_VICTUS_S_THERMAL_PROFILE_DEFAULT = 0x00, - HP_VICTUS_S_THERMAL_PROFILE_PERFORMANCE = 0x01, -}; - -enum hp_thermal_profile { - HP_THERMAL_PROFILE_PERFORMANCE = 0x00, - HP_THERMAL_PROFILE_DEFAULT = 0x01, - HP_THERMAL_PROFILE_COOL = 0x02, - HP_THERMAL_PROFILE_QUIET = 0x03, -}; - #define IS_HWBLOCKED(x) ((x & HPWMI_POWER_FW_OR_HW) != HPWMI_POWER_FW_OR_HW) #define IS_SWBLOCKED(x) !(x & HPWMI_POWER_SOFT) @@ -1575,15 +1627,8 @@ static int platform_profile_victus_set_ec(enum platform_profile_option profile) static bool is_victus_s_thermal_profile(void) { - const char *board_name; - - board_name = dmi_get_system_info(DMI_BOARD_NAME); - if (!board_name) - return false; - - return match_string(victus_s_thermal_profile_boards, - ARRAY_SIZE(victus_s_thermal_profile_boards), - board_name) >= 0; + /* Initialised in driver init, hence safe to use here */ + return is_victus_s_board; } static int victus_s_gpu_thermal_profile_get(bool *ctgp_enable, @@ -1666,25 +1711,30 @@ static int victus_s_set_cpu_pl1_pl2(u8 pl1, u8 pl2) static int platform_profile_victus_s_set_ec(enum platform_profile_option profile) { + struct thermal_profile_params *params; bool gpu_ctgp_enable, gpu_ppab_enable; u8 gpu_dstate; /* Test shows 1 = 100%, 2 = 50%, 3 = 25%, 4 = 12.5% */ int err, tp; + params = active_thermal_profile_params; + if (!params) + return -ENODEV; + switch (profile) { case PLATFORM_PROFILE_PERFORMANCE: - tp = HP_VICTUS_S_THERMAL_PROFILE_PERFORMANCE; + tp = params->performance; gpu_ctgp_enable = true; gpu_ppab_enable = true; gpu_dstate = 1; break; case PLATFORM_PROFILE_BALANCED: - tp = HP_VICTUS_S_THERMAL_PROFILE_DEFAULT; + tp = params->balanced; gpu_ctgp_enable = false; gpu_ppab_enable = true; gpu_dstate = 1; break; case PLATFORM_PROFILE_LOW_POWER: - tp = HP_VICTUS_S_THERMAL_PROFILE_DEFAULT; + tp = params->low_power; gpu_ctgp_enable = false; gpu_ppab_enable = false; gpu_dstate = 1; @@ -2221,6 +2271,26 @@ static int hp_wmi_hwmon_init(void) return 0; } +static void __init setup_active_thermal_profile_params(void) +{ + const struct dmi_system_id *id; + + /* + * Currently only victus_s devices use the + * active_thermal_profile_params + */ + id = dmi_first_match(victus_s_thermal_profile_boards); + if (id) { + /* + * Marking this boolean is required to ensure that + * is_victus_s_thermal_profile() behaves like a valid + * wrapper. + */ + is_victus_s_board = true; + active_thermal_profile_params = id->driver_data; + } +} + static int __init hp_wmi_init(void) { int event_capable = wmi_has_guid(HPWMI_EVENT_GUID); @@ -2248,6 +2318,11 @@ static int __init hp_wmi_init(void) goto err_destroy_input; } + /* + * Setup active board's thermal profile parameters before + * starting platform driver probe. + */ + setup_active_thermal_profile_params(); err = platform_driver_probe(&hp_wmi_driver, hp_wmi_bios_setup); if (err) goto err_unregister_device; From 71b23ada25b337135fd36089bca44748095428b9 Mon Sep 17 00:00:00 2001 From: Malaya Kumar Rout Date: Thu, 15 Jan 2026 15:33:33 +0530 Subject: [PATCH 3611/3902] tools/power/x86/intel-speed-select: Fix file descriptor leak in isolate_cpus() [ Upstream commit 56c17ee151c6e1a73d77e15b82a8e2130cd8dd16 ] The file descriptor opened in isolate_cpus() when (!level) is true was not being closed before returning, causing a file descriptor leak in both the error path and the success path. When write() fails at line 950, the function returns at line 953 without closing the file descriptor. Similarly, on success, the function returns at line 956 without closing the file descriptor. Add close(fd) calls before both return statements to fix the resource leak. This follows the same pattern used elsewhere in the same function where file descriptors are properly closed before returning (see lines 1005 and 1027). Fixes: 997074df658e ("tools/power/x86/intel-speed-select: Use cgroup v2 isolation") Signed-off-by: Malaya Kumar Rout Signed-off-by: Srinivas Pandruvada Signed-off-by: Sasha Levin --- tools/power/x86/intel-speed-select/isst-config.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tools/power/x86/intel-speed-select/isst-config.c b/tools/power/x86/intel-speed-select/isst-config.c index 0ce251b8d46652..a7d54dfd3c68b6 100644 --- a/tools/power/x86/intel-speed-select/isst-config.c +++ b/tools/power/x86/intel-speed-select/isst-config.c @@ -950,9 +950,11 @@ int isolate_cpus(struct isst_id *id, int mask_size, cpu_set_t *cpu_mask, int lev ret = write(fd, "member", strlen("member")); if (ret == -1) { printf("Can't update to member\n"); + close(fd); return ret; } + close(fd); return 0; } From 6d838873da9cb97551d42316967cc82bf8f8031b Mon Sep 17 00:00:00 2001 From: Chiara Meiohas Date: Tue, 13 Jan 2026 15:37:10 +0200 Subject: [PATCH 3612/3902] RDMA/mlx5: Fix UMR hang in LAG error state unload [ Upstream commit ebc2164a4cd4314503f1a0c8e7aaf76d7e5fa211 ] During firmware reset in LAG mode, a race condition causes the driver to hang indefinitely while waiting for UMR completion during device unload. See [1]. In LAG mode the bond device is only registered on the master, so it never sees sys_error events from the slave. During firmware reset this causes UMR waits to hang forever on unload as the slave is dead but the master hasn't entered error state yet, so UMR posts succeed but completions never arrive. Fix this by adding a sys_error notifier that gets registered before MLX5_IB_STAGE_IB_REG and stays alive until after ib_unregister_device(). This ensures error events reach the bond device throughout teardown. [1] Call Trace: __schedule+0x2bd/0x760 schedule+0x37/0xa0 schedule_preempt_disabled+0xa/0x10 __mutex_lock.isra.6+0x2b5/0x4a0 __mlx5_ib_dereg_mr+0x606/0x870 [mlx5_ib] ? __xa_erase+0x4a/0xa0 ? _cond_resched+0x15/0x30 ? wait_for_completion+0x31/0x100 ib_dereg_mr_user+0x48/0xc0 [ib_core] ? rdmacg_uncharge_hierarchy+0xa0/0x100 destroy_hw_idr_uobject+0x20/0x50 [ib_uverbs] uverbs_destroy_uobject+0x37/0x150 [ib_uverbs] __uverbs_cleanup_ufile+0xda/0x140 [ib_uverbs] uverbs_destroy_ufile_hw+0x3a/0xf0 [ib_uverbs] ib_uverbs_remove_one+0xc3/0x140 [ib_uverbs] remove_client_context+0x8b/0xd0 [ib_core] disable_device+0x8c/0x130 [ib_core] __ib_unregister_device+0x10d/0x180 [ib_core] ib_unregister_device+0x21/0x30 [ib_core] __mlx5_ib_remove+0x1e4/0x1f0 [mlx5_ib] auxiliary_bus_remove+0x1e/0x30 device_release_driver_internal+0x103/0x1f0 bus_remove_device+0xf7/0x170 device_del+0x181/0x410 mlx5_rescan_drivers_locked.part.10+0xa9/0x1d0 [mlx5_core] mlx5_disable_lag+0x253/0x260 [mlx5_core] mlx5_lag_disable_change+0x89/0xc0 [mlx5_core] mlx5_eswitch_disable+0x67/0xa0 [mlx5_core] mlx5_unload+0x15/0xd0 [mlx5_core] mlx5_unload_one+0x71/0xc0 [mlx5_core] mlx5_sync_reset_reload_work+0x83/0x100 [mlx5_core] process_one_work+0x1a7/0x360 worker_thread+0x30/0x390 ? create_worker+0x1a0/0x1a0 kthread+0x116/0x130 ? kthread_flush_work_fn+0x10/0x10 ret_from_fork+0x22/0x40 Fixes: ede132a5cf55 ("RDMA/mlx5: Move events notifier registration to be after device registration") Signed-off-by: Chiara Meiohas Signed-off-by: Maher Sanalla Reviewed-by: Mark Bloch Signed-off-by: Edward Srouji Link: https://patch.msgid.link/20260113-umr-hand-lag-fix-v1-1-3dc476e00cd9@nvidia.com Signed-off-by: Leon Romanovsky Signed-off-by: Sasha Levin --- drivers/infiniband/hw/mlx5/main.c | 75 ++++++++++++++++++++++++---- drivers/infiniband/hw/mlx5/mlx5_ib.h | 2 + 2 files changed, 68 insertions(+), 9 deletions(-) diff --git a/drivers/infiniband/hw/mlx5/main.c b/drivers/infiniband/hw/mlx5/main.c index 8f69c8c1ba54d6..b6096f9126858a 100644 --- a/drivers/infiniband/hw/mlx5/main.c +++ b/drivers/infiniband/hw/mlx5/main.c @@ -2874,7 +2874,6 @@ static void mlx5_ib_handle_event(struct work_struct *_work) container_of(_work, struct mlx5_ib_event_work, work); struct mlx5_ib_dev *ibdev; struct ib_event ibev; - bool fatal = false; if (work->is_slave) { ibdev = mlx5_ib_get_ibdev_from_mpi(work->mpi); @@ -2885,12 +2884,6 @@ static void mlx5_ib_handle_event(struct work_struct *_work) } switch (work->event) { - case MLX5_DEV_EVENT_SYS_ERROR: - ibev.event = IB_EVENT_DEVICE_FATAL; - mlx5_ib_handle_internal_error(ibdev); - ibev.element.port_num = (u8)(unsigned long)work->param; - fatal = true; - break; case MLX5_EVENT_TYPE_PORT_CHANGE: if (handle_port_change(ibdev, work->param, &ibev)) goto out; @@ -2912,8 +2905,6 @@ static void mlx5_ib_handle_event(struct work_struct *_work) if (ibdev->ib_active) ib_dispatch_event(&ibev); - if (fatal) - ibdev->ib_active = false; out: kfree(work); } @@ -2957,6 +2948,66 @@ static int mlx5_ib_event_slave_port(struct notifier_block *nb, return NOTIFY_OK; } +static void mlx5_ib_handle_sys_error_event(struct work_struct *_work) +{ + struct mlx5_ib_event_work *work = + container_of(_work, struct mlx5_ib_event_work, work); + struct mlx5_ib_dev *ibdev = work->dev; + struct ib_event ibev; + + ibev.event = IB_EVENT_DEVICE_FATAL; + mlx5_ib_handle_internal_error(ibdev); + ibev.element.port_num = (u8)(unsigned long)work->param; + ibev.device = &ibdev->ib_dev; + + if (!rdma_is_port_valid(&ibdev->ib_dev, ibev.element.port_num)) { + mlx5_ib_warn(ibdev, "warning: event on port %d\n", ibev.element.port_num); + goto out; + } + + if (ibdev->ib_active) + ib_dispatch_event(&ibev); + + ibdev->ib_active = false; +out: + kfree(work); +} + +static int mlx5_ib_sys_error_event(struct notifier_block *nb, + unsigned long event, void *param) +{ + struct mlx5_ib_event_work *work; + + if (event != MLX5_DEV_EVENT_SYS_ERROR) + return NOTIFY_DONE; + + work = kmalloc(sizeof(*work), GFP_ATOMIC); + if (!work) + return NOTIFY_DONE; + + INIT_WORK(&work->work, mlx5_ib_handle_sys_error_event); + work->dev = container_of(nb, struct mlx5_ib_dev, sys_error_events); + work->is_slave = false; + work->param = param; + work->event = event; + + queue_work(mlx5_ib_event_wq, &work->work); + + return NOTIFY_OK; +} + +static int mlx5_ib_stage_sys_error_notifier_init(struct mlx5_ib_dev *dev) +{ + dev->sys_error_events.notifier_call = mlx5_ib_sys_error_event; + mlx5_notifier_register(dev->mdev, &dev->sys_error_events); + return 0; +} + +static void mlx5_ib_stage_sys_error_notifier_cleanup(struct mlx5_ib_dev *dev) +{ + mlx5_notifier_unregister(dev->mdev, &dev->sys_error_events); +} + static int mlx5_ib_get_plane_num(struct mlx5_core_dev *mdev, u8 *num_plane) { struct mlx5_hca_vport_context vport_ctx; @@ -4807,6 +4858,9 @@ static const struct mlx5_ib_profile pf_profile = { STAGE_CREATE(MLX5_IB_STAGE_WHITELIST_UID, mlx5_ib_devx_init, mlx5_ib_devx_cleanup), + STAGE_CREATE(MLX5_IB_STAGE_SYS_ERROR_NOTIFIER, + mlx5_ib_stage_sys_error_notifier_init, + mlx5_ib_stage_sys_error_notifier_cleanup), STAGE_CREATE(MLX5_IB_STAGE_IB_REG, mlx5_ib_stage_ib_reg_init, mlx5_ib_stage_ib_reg_cleanup), @@ -4864,6 +4918,9 @@ const struct mlx5_ib_profile raw_eth_profile = { STAGE_CREATE(MLX5_IB_STAGE_WHITELIST_UID, mlx5_ib_devx_init, mlx5_ib_devx_cleanup), + STAGE_CREATE(MLX5_IB_STAGE_SYS_ERROR_NOTIFIER, + mlx5_ib_stage_sys_error_notifier_init, + mlx5_ib_stage_sys_error_notifier_cleanup), STAGE_CREATE(MLX5_IB_STAGE_IB_REG, mlx5_ib_stage_ib_reg_init, mlx5_ib_stage_ib_reg_cleanup), diff --git a/drivers/infiniband/hw/mlx5/mlx5_ib.h b/drivers/infiniband/hw/mlx5/mlx5_ib.h index 09d82d5f95e354..fbccb0362590bb 100644 --- a/drivers/infiniband/hw/mlx5/mlx5_ib.h +++ b/drivers/infiniband/hw/mlx5/mlx5_ib.h @@ -1007,6 +1007,7 @@ enum mlx5_ib_stages { MLX5_IB_STAGE_BFREG, MLX5_IB_STAGE_PRE_IB_REG_UMR, MLX5_IB_STAGE_WHITELIST_UID, + MLX5_IB_STAGE_SYS_ERROR_NOTIFIER, MLX5_IB_STAGE_IB_REG, MLX5_IB_STAGE_DEVICE_NOTIFIER, MLX5_IB_STAGE_POST_IB_REG_UMR, @@ -1165,6 +1166,7 @@ struct mlx5_ib_dev { /* protect accessing data_direct_dev */ struct mutex data_direct_lock; struct notifier_block mdev_events; + struct notifier_block sys_error_events; struct notifier_block lag_events; int num_ports; /* serialize update of capability mask From 9b0927c9b91b4aaa759596416232e1b48c3b7a98 Mon Sep 17 00:00:00 2001 From: Or Har-Toov Date: Thu, 15 Jan 2026 14:26:45 +0200 Subject: [PATCH 3613/3902] IB/mlx5: Fix port speed query for representors [ Upstream commit 18ea78e2ae83d1d86a72d21d9511927e57e2c0e1 ] When querying speed information for a representor in switchdev mode, the code previously used the first device in the eswitch, which may not match the device that actually owns the representor. In setups such as multi-port eswitch or LAG, this led to incorrect port attributes being reported. Fix this by retrieving the correct core device from the representor's eswitch before querying its port attributes. Fixes: 27f9e0ccb6da ("net/mlx5: Lag, Add single RDMA device in multiport mode") Signed-off-by: Or Har-Toov Reviewed-by: Mark Bloch Signed-off-by: Edward Srouji Link: https://patch.msgid.link/20260115-port-speed-query-fix-v2-1-3bde6a3c78e7@nvidia.com Signed-off-by: Leon Romanovsky Signed-off-by: Sasha Levin --- drivers/infiniband/hw/mlx5/main.c | 20 ++++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/drivers/infiniband/hw/mlx5/main.c b/drivers/infiniband/hw/mlx5/main.c index b6096f9126858a..5899bd5cb16230 100644 --- a/drivers/infiniband/hw/mlx5/main.c +++ b/drivers/infiniband/hw/mlx5/main.c @@ -557,12 +557,20 @@ static int mlx5_query_port_roce(struct ib_device *device, u32 port_num, * of an error it will still be zeroed out. * Use native port in case of reps */ - if (dev->is_rep) - err = mlx5_query_port_ptys(mdev, out, sizeof(out), MLX5_PTYS_EN, - 1, 0); - else - err = mlx5_query_port_ptys(mdev, out, sizeof(out), MLX5_PTYS_EN, - mdev_port_num, 0); + if (dev->is_rep) { + struct mlx5_eswitch_rep *rep; + + rep = dev->port[port_num - 1].rep; + if (rep) { + mdev = mlx5_eswitch_get_core_dev(rep->esw); + WARN_ON(!mdev); + } + mdev_port_num = 1; + } + + err = mlx5_query_port_ptys(mdev, out, sizeof(out), MLX5_PTYS_EN, + mdev_port_num, 0); + if (err) goto out; ext = !!MLX5_GET_ETH_PROTO(ptys_reg, out, true, eth_proto_capability); From 4d02e3870c88fa8a4fd10d5be2d1d3e765e65419 Mon Sep 17 00:00:00 2001 From: Alok Tiwari Date: Fri, 19 Dec 2025 03:09:30 -0800 Subject: [PATCH 3614/3902] mtd: rawnand: cadence: Fix return type of CDMA send-and-wait helper [ Upstream commit 6d8226cbbf124bb5613b532216b74c886a4361b7 ] cadence_nand_cdma_send_and_wait() propagates negative errno values from cadence_nand_cdma_send(), returns -ETIMEDOUT on failure and -EIO when the CDMA engine reports a command failure. However, it is declared as u32, causing error codes to wrap. Change the return type to int to correctly propagate errors. Fixes: ec4ba01e894d ("mtd: rawnand: Add new Cadence NAND driver to MTD subsystem") Signed-off-by: Alok Tiwari Signed-off-by: Miquel Raynal Signed-off-by: Sasha Levin --- drivers/mtd/nand/raw/cadence-nand-controller.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/mtd/nand/raw/cadence-nand-controller.c b/drivers/mtd/nand/raw/cadence-nand-controller.c index 32ed38b8939478..21eabedc2ed1c6 100644 --- a/drivers/mtd/nand/raw/cadence-nand-controller.c +++ b/drivers/mtd/nand/raw/cadence-nand-controller.c @@ -1015,7 +1015,7 @@ static int cadence_nand_cdma_send(struct cdns_nand_ctrl *cdns_ctrl, } /* Send SDMA command and wait for finish. */ -static u32 +static int cadence_nand_cdma_send_and_wait(struct cdns_nand_ctrl *cdns_ctrl, u8 thread) { From 721bd22bcf45a63ebd9bd0f478ef721b45cc5383 Mon Sep 17 00:00:00 2001 From: Alexander Usyskin Date: Thu, 15 Jan 2026 07:22:37 +0200 Subject: [PATCH 3615/3902] mtd: intel-dg: Fix accessing regions before setting nregions [ Upstream commit 779c59274d03cc5c07237a2c845dfb71cff77705 ] The regions array is counted by nregions, but it's set only after accessing it: [] UBSAN: array-index-out-of-bounds in drivers/mtd/devices/mtd_intel_dg.c:750:15 [] index 0 is out of range for type ' [*]' Fix it by also fixing an undesired behavior: the loop silently ignores ENOMEM and continues setting the other entries. CC: Gustavo A. R. Silva CC: Raag Jadav Reported-by: Jani Partanen Closes: https://lore.kernel.org/all/caca6c67-4f1d-49f1-948f-e63b6b937b29@sotapeli.fi Fixes: ceb5ab3cb646 ("mtd: add driver for intel graphics non-volatile memory device") Signed-off-by: Lucas De Marchi Signed-off-by: Alexander Usyskin Reviewed-by: Raag Jadav Signed-off-by: Miquel Raynal Signed-off-by: Sasha Levin --- drivers/mtd/devices/mtd_intel_dg.c | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/drivers/mtd/devices/mtd_intel_dg.c b/drivers/mtd/devices/mtd_intel_dg.c index b438ee5aacc34a..114e69135b8d93 100644 --- a/drivers/mtd/devices/mtd_intel_dg.c +++ b/drivers/mtd/devices/mtd_intel_dg.c @@ -738,6 +738,7 @@ static int intel_dg_mtd_probe(struct auxiliary_device *aux_dev, kref_init(&nvm->refcnt); mutex_init(&nvm->lock); + nvm->nregions = nregions; for (n = 0, i = 0; i < INTEL_DG_NVM_REGIONS; i++) { if (!invm->regions[i].name) @@ -745,13 +746,15 @@ static int intel_dg_mtd_probe(struct auxiliary_device *aux_dev, char *name = kasprintf(GFP_KERNEL, "%s.%s", dev_name(&aux_dev->dev), invm->regions[i].name); - if (!name) - continue; + if (!name) { + ret = -ENOMEM; + goto err; + } + nvm->regions[n].name = name; nvm->regions[n].id = i; n++; } - nvm->nregions = n; /* in case where kasprintf fail */ nvm->base = devm_ioremap_resource(device, &invm->bar); if (IS_ERR(nvm->base)) { From 8269e0ee89bd8ae61c3195ad912746edc0dc18cc Mon Sep 17 00:00:00 2001 From: "Anthony Pighin (Nokia)" Date: Fri, 16 Jan 2026 15:31:26 +0000 Subject: [PATCH 3616/3902] vfio/pci: Lock upstream bridge for vfio_pci_core_disable() [ Upstream commit 962ae6892d8bd208b2d1e2b358f07551ddc8d32f ] The commit 7e89efc6e9e4 ("Lock upstream bridge for pci_reset_function()") added locking of the upstream bridge to the reset function. To catch paths that are not properly locked, the commit 920f6468924f ("Warn on missing cfg_access_lock during secondary bus reset") added a warning if the PCI configuration space was not locked during a secondary bus reset request. When a VFIO PCI device is released from userspace ownership, an attempt to reset the PCI device function may be made. If so, and the upstream bridge is not locked, the release request results in a warning: pcieport 0000:00:00.0: unlocked secondary bus reset via: pci_reset_bus_function+0x188/0x1b8 Add missing upstream bridge locking to vfio_pci_core_disable(). Fixes: 7e89efc6e9e4 ("PCI: Lock upstream bridge for pci_reset_function()") Signed-off-by: Anthony Pighin Link: https://lore.kernel.org/r/BN0PR08MB695171D3AB759C65B6438B5D838DA@BN0PR08MB6951.namprd08.prod.outlook.com Signed-off-by: Alex Williamson Signed-off-by: Sasha Levin --- drivers/vfio/pci/vfio_pci_core.c | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/drivers/vfio/pci/vfio_pci_core.c b/drivers/vfio/pci/vfio_pci_core.c index 5efe7535f41ed8..085373d71e9c24 100644 --- a/drivers/vfio/pci/vfio_pci_core.c +++ b/drivers/vfio/pci/vfio_pci_core.c @@ -589,6 +589,7 @@ EXPORT_SYMBOL_GPL(vfio_pci_core_enable); void vfio_pci_core_disable(struct vfio_pci_core_device *vdev) { + struct pci_dev *bridge; struct pci_dev *pdev = vdev->pdev; struct vfio_pci_dummy_resource *dummy_res, *tmp; struct vfio_pci_ioeventfd *ioeventfd, *ioeventfd_tmp; @@ -695,12 +696,20 @@ void vfio_pci_core_disable(struct vfio_pci_core_device *vdev) * We can not use the "try" reset interface here, which will * overwrite the previously restored configuration information. */ - if (vdev->reset_works && pci_dev_trylock(pdev)) { - if (!__pci_reset_function_locked(pdev)) - vdev->needs_reset = false; - pci_dev_unlock(pdev); + if (vdev->reset_works) { + bridge = pci_upstream_bridge(pdev); + if (bridge && !pci_dev_trylock(bridge)) + goto out_restore_state; + if (pci_dev_trylock(pdev)) { + if (!__pci_reset_function_locked(pdev)) + vdev->needs_reset = false; + pci_dev_unlock(pdev); + } + if (bridge) + pci_dev_unlock(bridge); } +out_restore_state: pci_restore_state(pdev); out: pci_disable_device(pdev); From 7c28e2789697797d5cc6cd25a30d164b539b5e2f Mon Sep 17 00:00:00 2001 From: Shyam Sundar S K Date: Thu, 15 Jan 2026 22:11:28 -0600 Subject: [PATCH 3617/3902] platform/x86/amd/pmf: Prevent TEE errors after hibernate MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [ Upstream commit 48d229c7047128dd52eaf863881bb3e62b5896e5 ] After resuming from hibernate, TEE commands can time out and cause PSP disables. Fix this by reinitializing the Trusted Application (TA) and cancelling the pb workqueue in the hibernate callbacks to avoid these errors. ccp 0000:c4:00.2: tee: command 0x5 timed out, disabling PSP amd-pmf AMDI0107:00: TEE enact cmd failed. err: ffff000e, ret:0 amd-pmf AMDI0107:00: TEE enact cmd failed. err: ffff000e, ret:0 amd-pmf AMDI0107:00: TEE enact cmd failed. err: ffff000e, ret:0 Fixes: ae82cef7d9c5 ("platform/x86/amd/pmf: Add support for PMF-TA interaction") Reported-by: Lars Francke Closes: https://lore.kernel.org/platform-driver-x86/CAD-Ua_gfJnQSo8ucS_7ZwzuhoBRJ14zXP7s8b-zX3ZcxcyWePw@mail.gmail.com/ Tested-by: Yijun Shen Co-developed-by: Patil Rajesh Reddy Signed-off-by: Patil Rajesh Reddy Signed-off-by: Shyam Sundar S K [ML: Add more tags] Signed-off-by: Mario Limonciello (AMD) Link: https://patch.msgid.link/20260116041132.153674-2-superm1@kernel.org Signed-off-by: Ilpo Järvinen Signed-off-by: Sasha Levin --- drivers/platform/x86/amd/pmf/core.c | 62 ++++++++++++++++++++++++++- drivers/platform/x86/amd/pmf/pmf.h | 10 +++++ drivers/platform/x86/amd/pmf/tee-if.c | 12 ++---- 3 files changed, 74 insertions(+), 10 deletions(-) diff --git a/drivers/platform/x86/amd/pmf/core.c b/drivers/platform/x86/amd/pmf/core.c index bc544a4a5266ee..e787480f4df26b 100644 --- a/drivers/platform/x86/amd/pmf/core.c +++ b/drivers/platform/x86/amd/pmf/core.c @@ -314,6 +314,61 @@ int amd_pmf_init_metrics_table(struct amd_pmf_dev *dev) return 0; } +static int amd_pmf_reinit_ta(struct amd_pmf_dev *pdev) +{ + bool status; + int ret, i; + + for (i = 0; i < ARRAY_SIZE(amd_pmf_ta_uuid); i++) { + ret = amd_pmf_tee_init(pdev, &amd_pmf_ta_uuid[i]); + if (ret) { + dev_err(pdev->dev, "TEE init failed for UUID[%d] ret: %d\n", i, ret); + return ret; + } + + ret = amd_pmf_start_policy_engine(pdev); + dev_dbg(pdev->dev, "start policy engine ret: %d (UUID idx: %d)\n", ret, i); + status = ret == TA_PMF_TYPE_SUCCESS; + if (status) + break; + amd_pmf_tee_deinit(pdev); + } + + return 0; +} + +static int amd_pmf_restore_handler(struct device *dev) +{ + struct amd_pmf_dev *pdev = dev_get_drvdata(dev); + int ret; + + if (pdev->buf) { + ret = amd_pmf_set_dram_addr(pdev, false); + if (ret) + return ret; + } + + if (pdev->smart_pc_enabled) + amd_pmf_reinit_ta(pdev); + + return 0; +} + +static int amd_pmf_freeze_handler(struct device *dev) +{ + struct amd_pmf_dev *pdev = dev_get_drvdata(dev); + + if (!pdev->smart_pc_enabled) + return 0; + + cancel_delayed_work_sync(&pdev->pb_work); + /* Clear all TEE resources */ + amd_pmf_tee_deinit(pdev); + pdev->session_id = 0; + + return 0; +} + static int amd_pmf_suspend_handler(struct device *dev) { struct amd_pmf_dev *pdev = dev_get_drvdata(dev); @@ -347,7 +402,12 @@ static int amd_pmf_resume_handler(struct device *dev) return 0; } -static DEFINE_SIMPLE_DEV_PM_OPS(amd_pmf_pm, amd_pmf_suspend_handler, amd_pmf_resume_handler); +static const struct dev_pm_ops amd_pmf_pm = { + .suspend = amd_pmf_suspend_handler, + .resume = amd_pmf_resume_handler, + .freeze = amd_pmf_freeze_handler, + .restore = amd_pmf_restore_handler, +}; static void amd_pmf_init_features(struct amd_pmf_dev *dev) { diff --git a/drivers/platform/x86/amd/pmf/pmf.h b/drivers/platform/x86/amd/pmf/pmf.h index bd19f2a6bc786b..2da1885d879151 100644 --- a/drivers/platform/x86/amd/pmf/pmf.h +++ b/drivers/platform/x86/amd/pmf/pmf.h @@ -122,6 +122,12 @@ struct cookie_header { typedef void (*apmf_event_handler_t)(acpi_handle handle, u32 event, void *data); +static const uuid_t amd_pmf_ta_uuid[] __used = { UUID_INIT(0xd9b39bf2, 0x66bd, 0x4154, 0xaf, 0xb8, + 0x8a, 0xcc, 0x2b, 0x2b, 0x60, 0xd6), + UUID_INIT(0x6fd93b77, 0x3fb8, 0x524d, 0xb1, 0x2d, + 0xc5, 0x29, 0xb1, 0x3d, 0x85, 0x43), + }; + /* APTS PMF BIOS Interface */ struct amd_pmf_apts_output { u16 table_version; @@ -888,4 +894,8 @@ void amd_pmf_populate_ta_inputs(struct amd_pmf_dev *dev, struct ta_pmf_enact_tab void amd_pmf_dump_ta_inputs(struct amd_pmf_dev *dev, struct ta_pmf_enact_table *in); int amd_pmf_invoke_cmd_enact(struct amd_pmf_dev *dev); +int amd_pmf_tee_init(struct amd_pmf_dev *dev, const uuid_t *uuid); +void amd_pmf_tee_deinit(struct amd_pmf_dev *dev); +int amd_pmf_start_policy_engine(struct amd_pmf_dev *dev); + #endif /* PMF_H */ diff --git a/drivers/platform/x86/amd/pmf/tee-if.c b/drivers/platform/x86/amd/pmf/tee-if.c index 6e8116bef4f6ab..9030459352375a 100644 --- a/drivers/platform/x86/amd/pmf/tee-if.c +++ b/drivers/platform/x86/amd/pmf/tee-if.c @@ -27,12 +27,6 @@ module_param(pb_side_load, bool, 0444); MODULE_PARM_DESC(pb_side_load, "Sideload policy binaries debug policy failures"); #endif -static const uuid_t amd_pmf_ta_uuid[] = { UUID_INIT(0xd9b39bf2, 0x66bd, 0x4154, 0xaf, 0xb8, 0x8a, - 0xcc, 0x2b, 0x2b, 0x60, 0xd6), - UUID_INIT(0x6fd93b77, 0x3fb8, 0x524d, 0xb1, 0x2d, 0xc5, - 0x29, 0xb1, 0x3d, 0x85, 0x43), - }; - static const char *amd_pmf_uevent_as_str(unsigned int state) { switch (state) { @@ -312,7 +306,7 @@ static void amd_pmf_invoke_cmd(struct work_struct *work) schedule_delayed_work(&dev->pb_work, msecs_to_jiffies(pb_actions_ms)); } -static int amd_pmf_start_policy_engine(struct amd_pmf_dev *dev) +int amd_pmf_start_policy_engine(struct amd_pmf_dev *dev) { struct cookie_header *header; int res; @@ -468,7 +462,7 @@ static int amd_pmf_register_input_device(struct amd_pmf_dev *dev) return 0; } -static int amd_pmf_tee_init(struct amd_pmf_dev *dev, const uuid_t *uuid) +int amd_pmf_tee_init(struct amd_pmf_dev *dev, const uuid_t *uuid) { u32 size; int ret; @@ -516,7 +510,7 @@ static int amd_pmf_tee_init(struct amd_pmf_dev *dev, const uuid_t *uuid) return ret; } -static void amd_pmf_tee_deinit(struct amd_pmf_dev *dev) +void amd_pmf_tee_deinit(struct amd_pmf_dev *dev) { if (!dev->tee_ctx) return; From b3010ff068fadea7816615cd71f95ded4d86d463 Mon Sep 17 00:00:00 2001 From: "Mario Limonciello (AMD)" Date: Thu, 15 Jan 2026 22:11:29 -0600 Subject: [PATCH 3618/3902] crypto: ccp - Declare PSP dead if PSP_CMD_TEE_RING_INIT fails MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [ Upstream commit 5e599d7871bf852e94e8aa08b99724635f2cbf96 ] tee_init_ring() only declares PSP dead if the command times out. If there is any other failure it is still considered fatal though. Set psp_dead for other failures as well. Fixes: 949a0c8dd3c2 ("crypto: ccp - Move direct access to some PSP registers out of TEE") Tested-by: Yijun Shen Signed-off-by: Mario Limonciello (AMD) Acked-by: Tom Lendacky Reviewed-by: Shyam Sundar S K Link: https://patch.msgid.link/20260116041132.153674-3-superm1@kernel.org Signed-off-by: Ilpo Järvinen Signed-off-by: Sasha Levin --- drivers/crypto/ccp/tee-dev.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/crypto/ccp/tee-dev.c b/drivers/crypto/ccp/tee-dev.c index 5e1d80724678d0..af881daa5855b2 100644 --- a/drivers/crypto/ccp/tee-dev.c +++ b/drivers/crypto/ccp/tee-dev.c @@ -125,6 +125,7 @@ static int tee_init_ring(struct psp_tee_device *tee) dev_err(tee->dev, "tee: ring init command failed (%#010lx)\n", FIELD_GET(PSP_CMDRESP_STS, reg)); tee_free_ring(tee); + psp_dead = true; ret = -EIO; } From 10eef0f932387b8595eff9c32e2d5b6f83856f38 Mon Sep 17 00:00:00 2001 From: "Mario Limonciello (AMD)" Date: Thu, 15 Jan 2026 22:11:30 -0600 Subject: [PATCH 3619/3902] crypto: ccp - Add an S4 restore flow MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [ Upstream commit 0ba2035026d0ab6c7c7e65ad8b418dc73d5700d9 ] The system will have lost power during S4. The ring used for TEE communications needs to be initialized before use. Fixes: f892a21f51162 ("crypto: ccp - use generic power management") Reported-by: Lars Francke Closes: https://lore.kernel.org/platform-driver-x86/CAD-Ua_gfJnQSo8ucS_7ZwzuhoBRJ14zXP7s8b-zX3ZcxcyWePw@mail.gmail.com/ Tested-by: Yijun Shen Signed-off-by: Mario Limonciello (AMD) Reviewed-by: Shyam Sundar S K Reviewed-by: Tom Lendacky Link: https://patch.msgid.link/20260116041132.153674-4-superm1@kernel.org Signed-off-by: Ilpo Järvinen Signed-off-by: Sasha Levin --- drivers/crypto/ccp/psp-dev.c | 11 +++++++++++ drivers/crypto/ccp/sp-dev.c | 12 ++++++++++++ drivers/crypto/ccp/sp-dev.h | 3 +++ drivers/crypto/ccp/sp-pci.c | 16 +++++++++++++++- drivers/crypto/ccp/tee-dev.c | 5 +++++ drivers/crypto/ccp/tee-dev.h | 1 + 6 files changed, 47 insertions(+), 1 deletion(-) diff --git a/drivers/crypto/ccp/psp-dev.c b/drivers/crypto/ccp/psp-dev.c index 9e21da0e298ad7..5c7f7e02a7d8ab 100644 --- a/drivers/crypto/ccp/psp-dev.c +++ b/drivers/crypto/ccp/psp-dev.c @@ -351,6 +351,17 @@ struct psp_device *psp_get_master_device(void) return sp ? sp->psp_data : NULL; } +int psp_restore(struct sp_device *sp) +{ + struct psp_device *psp = sp->psp_data; + int ret = 0; + + if (psp->tee_data) + ret = tee_restore(psp); + + return ret; +} + void psp_pci_init(void) { psp_master = psp_get_master_device(); diff --git a/drivers/crypto/ccp/sp-dev.c b/drivers/crypto/ccp/sp-dev.c index 3467f6db4f505a..f204aa5df96e24 100644 --- a/drivers/crypto/ccp/sp-dev.c +++ b/drivers/crypto/ccp/sp-dev.c @@ -230,6 +230,18 @@ int sp_resume(struct sp_device *sp) return 0; } +int sp_restore(struct sp_device *sp) +{ + if (sp->psp_data) { + int ret = psp_restore(sp); + + if (ret) + return ret; + } + + return sp_resume(sp); +} + struct sp_device *sp_get_psp_master_device(void) { struct sp_device *i, *ret = NULL; diff --git a/drivers/crypto/ccp/sp-dev.h b/drivers/crypto/ccp/sp-dev.h index 6f9d7063257d70..c8a611ef275b5f 100644 --- a/drivers/crypto/ccp/sp-dev.h +++ b/drivers/crypto/ccp/sp-dev.h @@ -141,6 +141,7 @@ void sp_destroy(struct sp_device *sp); int sp_suspend(struct sp_device *sp); int sp_resume(struct sp_device *sp); +int sp_restore(struct sp_device *sp); int sp_request_ccp_irq(struct sp_device *sp, irq_handler_t handler, const char *name, void *data); void sp_free_ccp_irq(struct sp_device *sp, void *data); @@ -174,6 +175,7 @@ int psp_dev_init(struct sp_device *sp); void psp_pci_init(void); void psp_dev_destroy(struct sp_device *sp); void psp_pci_exit(void); +int psp_restore(struct sp_device *sp); #else /* !CONFIG_CRYPTO_DEV_SP_PSP */ @@ -181,6 +183,7 @@ static inline int psp_dev_init(struct sp_device *sp) { return 0; } static inline void psp_pci_init(void) { } static inline void psp_dev_destroy(struct sp_device *sp) { } static inline void psp_pci_exit(void) { } +static inline int psp_restore(struct sp_device *sp) { return 0; } #endif /* CONFIG_CRYPTO_DEV_SP_PSP */ diff --git a/drivers/crypto/ccp/sp-pci.c b/drivers/crypto/ccp/sp-pci.c index 8891ceee1d7d05..6ac805d99ccb30 100644 --- a/drivers/crypto/ccp/sp-pci.c +++ b/drivers/crypto/ccp/sp-pci.c @@ -353,6 +353,13 @@ static int __maybe_unused sp_pci_resume(struct device *dev) return sp_resume(sp); } +static int __maybe_unused sp_pci_restore(struct device *dev) +{ + struct sp_device *sp = dev_get_drvdata(dev); + + return sp_restore(sp); +} + #ifdef CONFIG_CRYPTO_DEV_SP_PSP static const struct sev_vdata sevv1 = { .cmdresp_reg = 0x10580, /* C2PMSG_32 */ @@ -563,7 +570,14 @@ static const struct pci_device_id sp_pci_table[] = { }; MODULE_DEVICE_TABLE(pci, sp_pci_table); -static SIMPLE_DEV_PM_OPS(sp_pci_pm_ops, sp_pci_suspend, sp_pci_resume); +static const struct dev_pm_ops sp_pci_pm_ops = { + .suspend = pm_sleep_ptr(sp_pci_suspend), + .resume = pm_sleep_ptr(sp_pci_resume), + .freeze = pm_sleep_ptr(sp_pci_suspend), + .thaw = pm_sleep_ptr(sp_pci_resume), + .poweroff = pm_sleep_ptr(sp_pci_suspend), + .restore_early = pm_sleep_ptr(sp_pci_restore), +}; static struct pci_driver sp_pci_driver = { .name = "ccp", diff --git a/drivers/crypto/ccp/tee-dev.c b/drivers/crypto/ccp/tee-dev.c index af881daa5855b2..11c4b05e2f3a28 100644 --- a/drivers/crypto/ccp/tee-dev.c +++ b/drivers/crypto/ccp/tee-dev.c @@ -366,3 +366,8 @@ int psp_check_tee_status(void) return 0; } EXPORT_SYMBOL(psp_check_tee_status); + +int tee_restore(struct psp_device *psp) +{ + return tee_init_ring(psp->tee_data); +} diff --git a/drivers/crypto/ccp/tee-dev.h b/drivers/crypto/ccp/tee-dev.h index ea9a2b7c05f577..c23416cb7bb37f 100644 --- a/drivers/crypto/ccp/tee-dev.h +++ b/drivers/crypto/ccp/tee-dev.h @@ -111,5 +111,6 @@ struct tee_ring_cmd { int tee_dev_init(struct psp_device *psp); void tee_dev_destroy(struct psp_device *psp); +int tee_restore(struct psp_device *psp); #endif /* __TEE_DEV_H__ */ From eb5263f3f8e483caa8956404607eec1c514cadef Mon Sep 17 00:00:00 2001 From: "Mario Limonciello (AMD)" Date: Thu, 15 Jan 2026 22:11:31 -0600 Subject: [PATCH 3620/3902] crypto: ccp - Factor out ring destroy handling to a helper MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [ Upstream commit d95f87a65bce5f2f2a02ca6094ca4841d4073df3 ] The ring destroy command needs to be used in multiple places. Split out the code to a helper. Tested-by: Yijun Shen Signed-off-by: Mario Limonciello (AMD) Acked-by: Tom Lendacky Reviewed-by: Shyam Sundar S K Link: https://patch.msgid.link/20260116041132.153674-5-superm1@kernel.org Signed-off-by: Ilpo Järvinen Stable-dep-of: 7b85137caf11 ("crypto: ccp - Send PSP_CMD_TEE_RING_DESTROY when PSP_CMD_TEE_RING_INIT fails") Signed-off-by: Sasha Levin --- drivers/crypto/ccp/tee-dev.c | 36 ++++++++++++++++++++++++------------ 1 file changed, 24 insertions(+), 12 deletions(-) diff --git a/drivers/crypto/ccp/tee-dev.c b/drivers/crypto/ccp/tee-dev.c index 11c4b05e2f3a28..ef1430f86ad62b 100644 --- a/drivers/crypto/ccp/tee-dev.c +++ b/drivers/crypto/ccp/tee-dev.c @@ -86,6 +86,29 @@ static inline void tee_free_cmd_buffer(struct tee_init_ring_cmd *cmd) kfree(cmd); } +static bool tee_send_destroy_cmd(struct psp_tee_device *tee) +{ + unsigned int reg; + int ret; + + ret = psp_mailbox_command(tee->psp, PSP_CMD_TEE_RING_DESTROY, NULL, + TEE_DEFAULT_CMD_TIMEOUT, ®); + if (ret) { + dev_err(tee->dev, "tee: ring destroy command timed out, disabling TEE support\n"); + psp_dead = true; + return false; + } + + if (FIELD_GET(PSP_CMDRESP_STS, reg)) { + dev_err(tee->dev, "tee: ring destroy command failed (%#010lx)\n", + FIELD_GET(PSP_CMDRESP_STS, reg)); + psp_dead = true; + return false; + } + + return true; +} + static int tee_init_ring(struct psp_tee_device *tee) { int ring_size = MAX_RING_BUFFER_ENTRIES * sizeof(struct tee_ring_cmd); @@ -137,24 +160,13 @@ static int tee_init_ring(struct psp_tee_device *tee) static void tee_destroy_ring(struct psp_tee_device *tee) { - unsigned int reg; - int ret; - if (!tee->rb_mgr.ring_start) return; if (psp_dead) goto free_ring; - ret = psp_mailbox_command(tee->psp, PSP_CMD_TEE_RING_DESTROY, NULL, - TEE_DEFAULT_CMD_TIMEOUT, ®); - if (ret) { - dev_err(tee->dev, "tee: ring destroy command timed out, disabling TEE support\n"); - psp_dead = true; - } else if (FIELD_GET(PSP_CMDRESP_STS, reg)) { - dev_err(tee->dev, "tee: ring destroy command failed (%#010lx)\n", - FIELD_GET(PSP_CMDRESP_STS, reg)); - } + tee_send_destroy_cmd(tee); free_ring: tee_free_ring(tee); From eb0e8003d9a036c498f1a84e22d061d5eaf75f6d Mon Sep 17 00:00:00 2001 From: "Mario Limonciello (AMD)" Date: Thu, 15 Jan 2026 22:11:32 -0600 Subject: [PATCH 3621/3902] crypto: ccp - Send PSP_CMD_TEE_RING_DESTROY when PSP_CMD_TEE_RING_INIT fails MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [ Upstream commit 7b85137caf110a09a4a18f00f730de4709f9afc8 ] The hibernate resume sequence involves loading a resume kernel that is just used for loading the hibernate image before shifting back to the existing kernel. During that hibernate resume sequence the resume kernel may have loaded the ccp driver. If this happens the resume kernel will also have called PSP_CMD_TEE_RING_INIT but it will never have called PSP_CMD_TEE_RING_DESTROY. This is problematic because the existing kernel needs to re-initialize the ring. One could argue that the existing kernel should call destroy as part of restore() but there is no guarantee that the resume kernel did or didn't load the ccp driver. There is also no callback opportunity for the resume kernel to destroy before handing back control to the existing kernel. Similar problems could potentially exist with the use of kdump and crash handling. I actually reproduced this issue like this: 1) rmmod ccp 2) hibernate the system 3) resume the system 4) modprobe ccp The resume kernel will have loaded ccp but never destroyed and then when I try to modprobe it fails. Because of these possible cases add a flow that checks the error code from the PSP_CMD_TEE_RING_INIT call and tries to call PSP_CMD_TEE_RING_DESTROY if it failed. If this succeeds then call PSP_CMD_TEE_RING_INIT again. Fixes: f892a21f51162 ("crypto: ccp - use generic power management") Reported-by: Lars Francke Closes: https://lore.kernel.org/platform-driver-x86/CAD-Ua_gfJnQSo8ucS_7ZwzuhoBRJ14zXP7s8b-zX3ZcxcyWePw@mail.gmail.com/ Tested-by: Yijun Shen Signed-off-by: Mario Limonciello (AMD) Reviewed-by: Shyam Sundar S K Acked-by: Tom Lendacky Link: https://patch.msgid.link/20260116041132.153674-6-superm1@kernel.org Signed-off-by: Ilpo Järvinen Signed-off-by: Sasha Levin --- drivers/crypto/ccp/tee-dev.c | 14 ++++++++++++++ include/linux/psp.h | 1 + 2 files changed, 15 insertions(+) diff --git a/drivers/crypto/ccp/tee-dev.c b/drivers/crypto/ccp/tee-dev.c index ef1430f86ad62b..92ffa412622a24 100644 --- a/drivers/crypto/ccp/tee-dev.c +++ b/drivers/crypto/ccp/tee-dev.c @@ -113,6 +113,7 @@ static int tee_init_ring(struct psp_tee_device *tee) { int ring_size = MAX_RING_BUFFER_ENTRIES * sizeof(struct tee_ring_cmd); struct tee_init_ring_cmd *cmd; + bool retry = false; unsigned int reg; int ret; @@ -135,6 +136,7 @@ static int tee_init_ring(struct psp_tee_device *tee) /* Send command buffer details to Trusted OS by writing to * CPU-PSP message registers */ +retry_init: ret = psp_mailbox_command(tee->psp, PSP_CMD_TEE_RING_INIT, cmd, TEE_DEFAULT_CMD_TIMEOUT, ®); if (ret) { @@ -145,6 +147,18 @@ static int tee_init_ring(struct psp_tee_device *tee) } if (FIELD_GET(PSP_CMDRESP_STS, reg)) { + /* + * During the hibernate resume sequence driver may have gotten loaded + * but the ring not properly destroyed. If the ring doesn't work, try + * to destroy and re-init once. + */ + if (!retry && FIELD_GET(PSP_CMDRESP_STS, reg) == PSP_TEE_STS_RING_BUSY) { + dev_info(tee->dev, "tee: ring init command failed with busy status, retrying\n"); + if (tee_send_destroy_cmd(tee)) { + retry = true; + goto retry_init; + } + } dev_err(tee->dev, "tee: ring init command failed (%#010lx)\n", FIELD_GET(PSP_CMDRESP_STS, reg)); tee_free_ring(tee); diff --git a/include/linux/psp.h b/include/linux/psp.h index 92e60aeef21e13..b337dcce1e9916 100644 --- a/include/linux/psp.h +++ b/include/linux/psp.h @@ -18,6 +18,7 @@ * and should include an appropriate local definition in their source file. */ #define PSP_CMDRESP_STS GENMASK(15, 0) +#define PSP_TEE_STS_RING_BUSY 0x0000000d /* Ring already initialized */ #define PSP_CMDRESP_CMD GENMASK(23, 16) #define PSP_CMDRESP_RESERVED GENMASK(29, 24) #define PSP_CMDRESP_RECOVERY BIT(30) From ec121ad626c319085f6d40a52cd04e99b4554926 Mon Sep 17 00:00:00 2001 From: Zilin Guan Date: Thu, 22 Jan 2026 13:09:50 +0000 Subject: [PATCH 3622/3902] mtd: parsers: Fix memory leak in mtd_parser_tplink_safeloader_parse() [ Upstream commit 980ce2b02dd06a4fdf5fee38b2e14becf9cf7b8b ] The function mtd_parser_tplink_safeloader_parse() allocates buf via mtd_parser_tplink_safeloader_read_table(). If the allocation for parts[idx].name fails inside the loop, the code jumps to the err_free label without freeing buf, leading to a memory leak. Fix this by freeing the temporary buffer buf in the err_free label. Compile tested only. Issue found using a prototype static analysis tool and code review. Fixes: 00a3588084be ("mtd: parsers: add TP-Link SafeLoader partitions table parser") Signed-off-by: Zilin Guan Signed-off-by: Miquel Raynal Signed-off-by: Sasha Levin --- drivers/mtd/parsers/tplink_safeloader.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/mtd/parsers/tplink_safeloader.c b/drivers/mtd/parsers/tplink_safeloader.c index e358a029dc70cf..4fcaf92d22e4fe 100644 --- a/drivers/mtd/parsers/tplink_safeloader.c +++ b/drivers/mtd/parsers/tplink_safeloader.c @@ -116,6 +116,7 @@ static int mtd_parser_tplink_safeloader_parse(struct mtd_info *mtd, return idx; err_free: + kfree(buf); for (idx -= 1; idx >= 0; idx--) kfree(parts[idx].name); err_free_parts: From c627769730e98c4dbff0d5f5d194f467d79df326 Mon Sep 17 00:00:00 2001 From: Trond Myklebust Date: Sat, 3 Jan 2026 12:14:59 -0500 Subject: [PATCH 3623/3902] NFS/localio: Handle short writes by retrying [ Upstream commit 615762059d284b863f9163b53679d95b3dcdd495 ] The current code for handling short writes in localio just truncates the I/O and then sets an error. While that is close to how the ordinary NFS code behaves, it does mean there is a chance the data that got written is lost because it isn't persisted. To fix this, change localio so that the upper layers can direct the behaviour to persist any unstable data by rewriting it, and then continuing writing until an ENOSPC is hit. Fixes: 70ba381e1a43 ("nfs: add LOCALIO support") Signed-off-by: Trond Myklebust Reviewed-by: Mike Snitzer Signed-off-by: Anna Schumaker Signed-off-by: Sasha Levin --- fs/nfs/localio.c | 64 +++++++++++++++++++++++++++++++++++------------- 1 file changed, 47 insertions(+), 17 deletions(-) diff --git a/fs/nfs/localio.c b/fs/nfs/localio.c index f537bc3386bf26..ea7b35191d0af8 100644 --- a/fs/nfs/localio.c +++ b/fs/nfs/localio.c @@ -58,6 +58,11 @@ struct nfs_local_fsync_ctx { static bool localio_enabled __read_mostly = true; module_param(localio_enabled, bool, 0644); +static int nfs_local_do_read(struct nfs_local_kiocb *iocb, + const struct rpc_call_ops *call_ops); +static int nfs_local_do_write(struct nfs_local_kiocb *iocb, + const struct rpc_call_ops *call_ops); + static inline bool nfs_client_is_local(const struct nfs_client *clp) { return !!rcu_access_pointer(clp->cl_uuid.net); @@ -542,13 +547,50 @@ nfs_local_iocb_release(struct nfs_local_kiocb *iocb) nfs_local_iocb_free(iocb); } -static void -nfs_local_pgio_release(struct nfs_local_kiocb *iocb) +static void nfs_local_pgio_restart(struct nfs_local_kiocb *iocb, + struct nfs_pgio_header *hdr) +{ + int status = 0; + + iocb->kiocb.ki_pos = hdr->args.offset; + iocb->kiocb.ki_flags &= ~(IOCB_DSYNC | IOCB_SYNC | IOCB_DIRECT); + iocb->kiocb.ki_complete = NULL; + iocb->aio_complete_work = NULL; + iocb->end_iter_index = -1; + + switch (hdr->rw_mode) { + case FMODE_READ: + nfs_local_iters_init(iocb, ITER_DEST); + status = nfs_local_do_read(iocb, hdr->task.tk_ops); + break; + case FMODE_WRITE: + nfs_local_iters_init(iocb, ITER_SOURCE); + status = nfs_local_do_write(iocb, hdr->task.tk_ops); + break; + default: + status = -EOPNOTSUPP; + } + + if (status != 0) { + nfs_local_iocb_release(iocb); + hdr->task.tk_status = status; + nfs_local_hdr_release(hdr, hdr->task.tk_ops); + } +} + +static void nfs_local_pgio_release(struct nfs_local_kiocb *iocb) { struct nfs_pgio_header *hdr = iocb->hdr; + struct rpc_task *task = &hdr->task; + + task->tk_action = NULL; + task->tk_ops->rpc_call_done(task, hdr); - nfs_local_iocb_release(iocb); - nfs_local_hdr_release(hdr, hdr->task.tk_ops); + if (task->tk_action == NULL) { + nfs_local_iocb_release(iocb); + task->tk_ops->rpc_release(hdr); + } else + nfs_local_pgio_restart(iocb, hdr); } /* @@ -776,19 +818,7 @@ static void nfs_local_write_done(struct nfs_local_kiocb *iocb) pr_info_ratelimited("nfs: Unexpected direct I/O write alignment failure\n"); } - /* Handle short writes as if they are ENOSPC */ - status = hdr->res.count; - if (status > 0 && status < hdr->args.count) { - hdr->mds_offset += status; - hdr->args.offset += status; - hdr->args.pgbase += status; - hdr->args.count -= status; - nfs_set_pgio_error(hdr, -ENOSPC, hdr->args.offset); - status = -ENOSPC; - /* record -ENOSPC in terms of nfs_local_pgio_done */ - (void) nfs_local_pgio_done(iocb, status, true); - } - if (hdr->task.tk_status < 0) + if (status < 0) nfs_reset_boot_verifier(hdr->inode); } From ae26a4cf2baf0a44c538dc093504d1994b02dade Mon Sep 17 00:00:00 2001 From: Mike Snitzer Date: Wed, 7 Jan 2026 11:08:55 -0500 Subject: [PATCH 3624/3902] NFS/localio: prevent direct reclaim recursion into NFS via nfs_writepages [ Upstream commit 67435d2d8a33a75f9647724952cb1b18279d2e95 ] LOCALIO is an NFS loopback mount optimization that avoids using the network for READ, WRITE and COMMIT if the NFS client and server are determined to be on the same system. But because LOCALIO is still fundamentally "just NFS loopback mount" it is susceptible to recursion deadlock via direct reclaim, e.g.: NFS LOCALIO down to XFS and then back into NFS via nfs_writepages. Fix LOCALIO's potential for direct reclaim deadlock by ensuring that all its page cache allocations are done from GFP_NOFS context. Thanks to Ben Coddington for pointing out commit ad22c7a043c2 ("xfs: prevent stack overflows from page cache allocation"). Reported-by: John Cagle Tested-by: Allen Lu Suggested-by: Benjamin Coddington Fixes: 70ba381e1a43 ("nfs: add LOCALIO support") Signed-off-by: Mike Snitzer Signed-off-by: Anna Schumaker Signed-off-by: Sasha Levin --- fs/nfs/localio.c | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/fs/nfs/localio.c b/fs/nfs/localio.c index ea7b35191d0af8..ff430e6b773a5d 100644 --- a/fs/nfs/localio.c +++ b/fs/nfs/localio.c @@ -291,6 +291,18 @@ nfs_local_open_fh(struct nfs_client *clp, const struct cred *cred, } EXPORT_SYMBOL_GPL(nfs_local_open_fh); +/* + * Ensure all page cache allocations are done from GFP_NOFS context to + * prevent direct reclaim recursion back into NFS via nfs_writepages. + */ +static void +nfs_local_mapping_set_gfp_nofs_context(struct address_space *m) +{ + gfp_t gfp_mask = mapping_gfp_mask(m); + + mapping_set_gfp_mask(m, (gfp_mask & ~(__GFP_FS))); +} + static void nfs_local_iocb_free(struct nfs_local_kiocb *iocb) { @@ -315,6 +327,7 @@ nfs_local_iocb_alloc(struct nfs_pgio_header *hdr, return NULL; } + nfs_local_mapping_set_gfp_nofs_context(file->f_mapping); init_sync_kiocb(&iocb->kiocb, file); iocb->hdr = hdr; @@ -1010,6 +1023,8 @@ nfs_local_run_commit(struct file *filp, struct nfs_commit_data *data) end = LLONG_MAX; } + nfs_local_mapping_set_gfp_nofs_context(filp->f_mapping); + dprintk("%s: commit %llu - %llu\n", __func__, start, end); return vfs_fsync_range(filp, start, end, 0); } From 1d6933f32b29710b97592c44563fe7e37434995b Mon Sep 17 00:00:00 2001 From: Mike Snitzer Date: Wed, 7 Jan 2026 11:08:56 -0500 Subject: [PATCH 3625/3902] NFS/localio: use GFP_NOIO and non-memreclaim workqueue in nfs_local_commit [ Upstream commit 9bb0060f7860aa4561c5b21163dd45ceb66946a9 ] nfslocaliod_workqueue is a non-memreclaim workqueue (it isn't initialized with WQ_MEM_RECLAIM), see commit b9f5dd57f4a5 ("nfs/localio: use dedicated workqueues for filesystem read and write"). Use nfslocaliod_workqueue for LOCALIO's SYNC work. Also, set PF_LOCAL_THROTTLE | PF_MEMALLOC_NOIO in nfs_local_fsync_work. Fixes: b9f5dd57f4a5 ("nfs/localio: use dedicated workqueues for filesystem read and write") Signed-off-by: Mike Snitzer Signed-off-by: Anna Schumaker Signed-off-by: Sasha Levin --- fs/nfs/localio.c | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/fs/nfs/localio.c b/fs/nfs/localio.c index ff430e6b773a5d..d1a8f6fa9d74ec 100644 --- a/fs/nfs/localio.c +++ b/fs/nfs/localio.c @@ -1066,17 +1066,22 @@ nfs_local_fsync_ctx_free(struct nfs_local_fsync_ctx *ctx) static void nfs_local_fsync_work(struct work_struct *work) { + unsigned long old_flags = current->flags; struct nfs_local_fsync_ctx *ctx; int status; ctx = container_of(work, struct nfs_local_fsync_ctx, work); + current->flags |= PF_LOCAL_THROTTLE | PF_MEMALLOC_NOIO; + status = nfs_local_run_commit(nfs_to->nfsd_file_file(ctx->localio), ctx->data); nfs_local_commit_done(ctx->data, status); if (ctx->done != NULL) complete(ctx->done); nfs_local_fsync_ctx_free(ctx); + + current->flags = old_flags; } static struct nfs_local_fsync_ctx * @@ -1100,7 +1105,7 @@ int nfs_local_commit(struct nfsd_file *localio, { struct nfs_local_fsync_ctx *ctx; - ctx = nfs_local_fsync_ctx_alloc(data, localio, GFP_KERNEL); + ctx = nfs_local_fsync_ctx_alloc(data, localio, GFP_NOIO); if (!ctx) { nfs_local_commit_done(data, -ENOMEM); nfs_local_release_commit_data(localio, data, call_ops); @@ -1112,10 +1117,10 @@ int nfs_local_commit(struct nfsd_file *localio, if (how & FLUSH_SYNC) { DECLARE_COMPLETION_ONSTACK(done); ctx->done = &done; - queue_work(nfsiod_workqueue, &ctx->work); + queue_work(nfslocaliod_workqueue, &ctx->work); wait_for_completion(&done); } else - queue_work(nfsiod_workqueue, &ctx->work); + queue_work(nfslocaliod_workqueue, &ctx->work); return 0; } From 055cd68b8ebbd42aa425a594ccddd55e4c3d61d5 Mon Sep 17 00:00:00 2001 From: Mike Snitzer Date: Wed, 7 Jan 2026 11:08:57 -0500 Subject: [PATCH 3626/3902] NFS/localio: remove -EAGAIN handling in nfs_local_doio() [ Upstream commit e72a73957613653f50375db1f3a3fbb907a9c40b ] Handling -EAGAIN in nfs_local_doio() was introduced with commit 0978e5b85fc08 (nfs_do_local_{read,write} were made to have negative checks for correspoding iter method) but commit e43e9a3a3d66 since eliminated the possibility for this -EAGAIN early return. So remove nfs_local_doio()'s -EAGAIN handling that calls nfs_localio_disable_client() -- while it should never happen from nfs_do_local_{read,write} this particular -EAGAIN handling is now "dead" and so it has become a liability. Fixes: e43e9a3a3d66 ("nfs/localio: refactor iocb initialization") Signed-off-by: Mike Snitzer Signed-off-by: Anna Schumaker Signed-off-by: Sasha Levin --- fs/nfs/localio.c | 2 -- 1 file changed, 2 deletions(-) diff --git a/fs/nfs/localio.c b/fs/nfs/localio.c index d1a8f6fa9d74ec..358d686d2ae330 100644 --- a/fs/nfs/localio.c +++ b/fs/nfs/localio.c @@ -995,8 +995,6 @@ int nfs_local_doio(struct nfs_client *clp, struct nfsd_file *localio, } if (status != 0) { - if (status == -EAGAIN) - nfs_localio_disable_client(clp); nfs_local_iocb_release(iocb); hdr->task.tk_status = status; nfs_local_hdr_release(hdr, call_ops); From 02f720109fed51ab433dfbd4e82d8dc95afaa3ba Mon Sep 17 00:00:00 2001 From: "Cheatham, Benjamin" Date: Fri, 9 Jan 2026 07:57:38 -0600 Subject: [PATCH 3627/3902] cxl/core: Fix cxl_dport debugfs EINJ entries [ Upstream commit 4ed7952b9e87cf731ebc8251874416e60eb15230 ] Protocol error injection is only valid for CXL 2.0+ root ports and CXL 1.1 memory-mapped downstream ports as per the ACPI v6.5 spec (Table 8-31). The core code currently creates an 'einj_inject' file in CXL debugfs for all CXL 1.1 downstream ports and all PCI CXL 2.0+ downstream ports. This results in debugfs EINJ files that won't work due to platform/spec restrictions. Fix by limiting 'einj_inject' file creation to only CXL 1.1 dports and CXL 2.0+ root ports. Update the comment above the check to more accurately represent the requirements expected by the EINJ module and ACPI spec. Fixes: 8039804cfa73 ("cxl/core: Add CXL EINJ debugfs files") Signed-off-by: Ben Cheatham Reviewed-by: Jonathan Cameron Reviewed-by: Alison Schofield Reviewed-by: Dave Jiang Link: https://patch.msgid.link/6e9fb657-8264-4028-92e2-5428e2695bf1@amd.com Signed-off-by: Dave Jiang Signed-off-by: Sasha Levin --- drivers/cxl/core/port.c | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/drivers/cxl/core/port.c b/drivers/cxl/core/port.c index 804e4a48540f6f..85131872d7f6e0 100644 --- a/drivers/cxl/core/port.c +++ b/drivers/cxl/core/port.c @@ -823,16 +823,18 @@ DEFINE_DEBUGFS_ATTRIBUTE(cxl_einj_inject_fops, NULL, cxl_einj_inject, static void cxl_debugfs_create_dport_dir(struct cxl_dport *dport) { + struct cxl_port *parent = parent_port_of(dport->port); struct dentry *dir; if (!einj_cxl_is_initialized()) return; /* - * dport_dev needs to be a PCIe port for CXL 2.0+ ports because - * EINJ expects a dport SBDF to be specified for 2.0 error injection. + * Protocol error injection is only available for CXL 2.0+ root ports + * and CXL 1.1 downstream ports */ - if (!dport->rch && !dev_is_pci(dport->dport_dev)) + if (!dport->rch && + !(dev_is_pci(dport->dport_dev) && parent && is_cxl_root(parent))) return; dir = cxl_debugfs_create_dir(dev_name(dport->dport_dev)); From 409c2c5508f3d30627bea576f8676de523cb906e Mon Sep 17 00:00:00 2001 From: Li Zhijian Date: Fri, 16 Jan 2026 11:27:53 +0800 Subject: [PATCH 3628/3902] RDMA/rxe: Fix iova-to-va conversion for MR page sizes != PAGE_SIZE [ Upstream commit 12985e5915a0b8354796efadaaeb201eed115377 ] The current implementation incorrectly handles memory regions (MRs) with page sizes different from the system PAGE_SIZE. The core issue is that rxe_set_page() is called with mr->page_size step increments, but the page_list stores individual struct page pointers, each representing PAGE_SIZE of memory. ib_sg_to_page() has ensured that when i>=1 either a) SG[i-1].dma_end and SG[i].dma_addr are contiguous or b) SG[i-1].dma_end and SG[i].dma_addr are mr->page_size aligned. This leads to incorrect iova-to-va conversion in scenarios: 1) page_size < PAGE_SIZE (e.g., MR: 4K, system: 64K): ibmr->iova = 0x181800 sg[0]: dma_addr=0x181800, len=0x800 sg[1]: dma_addr=0x173000, len=0x1000 Access iova = 0x181800 + 0x810 = 0x182010 Expected VA: 0x173010 (second SG, offset 0x10) Before fix: - index = (0x182010 >> 12) - (0x181800 >> 12) = 1 - page_offset = 0x182010 & 0xFFF = 0x10 - xarray[1] stores system page base 0x170000 - Resulting VA: 0x170000 + 0x10 = 0x170010 (wrong) 2) page_size > PAGE_SIZE (e.g., MR: 64K, system: 4K): ibmr->iova = 0x18f800 sg[0]: dma_addr=0x18f800, len=0x800 sg[1]: dma_addr=0x170000, len=0x1000 Access iova = 0x18f800 + 0x810 = 0x190010 Expected VA: 0x170010 (second SG, offset 0x10) Before fix: - index = (0x190010 >> 16) - (0x18f800 >> 16) = 1 - page_offset = 0x190010 & 0xFFFF = 0x10 - xarray[1] stores system page for dma_addr 0x170000 - Resulting VA: system page of 0x170000 + 0x10 = 0x170010 (wrong) Yi Zhang reported a kernel panic[1] years ago related to this defect. Solution: 1. Replace xarray with pre-allocated rxe_mr_page array for sequential indexing (all MR page indices are contiguous) 2. Each rxe_mr_page stores both struct page* and offset within the system page 3. Handle MR page_size != PAGE_SIZE relationships: - page_size > PAGE_SIZE: Split MR pages into multiple system pages - page_size <= PAGE_SIZE: Store offset within system page 4. Add boundary checks and compatibility validation This ensures correct iova-to-va conversion regardless of MR page size and system PAGE_SIZE relationship, while improving performance through array-based sequential access. Tests on 4K and 64K PAGE_SIZE hosts: - rdma-core/pytests $ ./build/bin/run_tests.py --dev eth0_rxe - blktest: $ TIMEOUT=30 QUICK_RUN=1 USE_RXE=1 NVMET_TRTYPES=rdma ./check nvme srp rnbd [1] https://lore.kernel.org/all/CAHj4cs9XRqE25jyVw9rj9YugffLn5+f=1znaBEnu1usLOciD+g@mail.gmail.com/T/ Fixes: 592627ccbdff ("RDMA/rxe: Replace rxe_map and rxe_phys_buf by xarray") Signed-off-by: Li Zhijian Link: https://patch.msgid.link/20260116032753.2574363-1-lizhijian@fujitsu.com Signed-off-by: Leon Romanovsky Signed-off-by: Sasha Levin --- drivers/infiniband/sw/rxe/rxe_mr.c | 281 +++++++++++++++++--------- drivers/infiniband/sw/rxe/rxe_verbs.h | 10 +- 2 files changed, 194 insertions(+), 97 deletions(-) diff --git a/drivers/infiniband/sw/rxe/rxe_mr.c b/drivers/infiniband/sw/rxe/rxe_mr.c index bcb97b3ea58ac7..2c486bb616a7c5 100644 --- a/drivers/infiniband/sw/rxe/rxe_mr.c +++ b/drivers/infiniband/sw/rxe/rxe_mr.c @@ -72,14 +72,46 @@ void rxe_mr_init_dma(int access, struct rxe_mr *mr) mr->ibmr.type = IB_MR_TYPE_DMA; } +/* + * Convert iova to page_info index. The page_info stores pages of size + * PAGE_SIZE, but MRs can have different page sizes. This function + * handles the conversion for all cases: + * + * 1. mr->page_size > PAGE_SIZE: + * The MR's iova may not be aligned to mr->page_size. We use the + * aligned base (iova & page_mask) as reference, then calculate + * which PAGE_SIZE sub-page the iova falls into. + * + * 2. mr->page_size <= PAGE_SIZE: + * Use simple shift arithmetic since each page_info entry corresponds + * to one or more MR pages. + */ static unsigned long rxe_mr_iova_to_index(struct rxe_mr *mr, u64 iova) { - return (iova >> mr->page_shift) - (mr->ibmr.iova >> mr->page_shift); + int idx; + + if (mr_page_size(mr) > PAGE_SIZE) + idx = (iova - (mr->ibmr.iova & mr->page_mask)) >> PAGE_SHIFT; + else + idx = (iova >> mr->page_shift) - + (mr->ibmr.iova >> mr->page_shift); + + WARN_ON(idx >= mr->nbuf); + return idx; } +/* + * Convert iova to offset within the page_info entry. + * + * For mr_page_size > PAGE_SIZE, the offset is within the system page. + * For mr_page_size <= PAGE_SIZE, the offset is within the MR page size. + */ static unsigned long rxe_mr_iova_to_page_offset(struct rxe_mr *mr, u64 iova) { - return iova & (mr_page_size(mr) - 1); + if (mr_page_size(mr) > PAGE_SIZE) + return iova & (PAGE_SIZE - 1); + else + return iova & (mr_page_size(mr) - 1); } static bool is_pmem_page(struct page *pg) @@ -93,37 +125,69 @@ static bool is_pmem_page(struct page *pg) static int rxe_mr_fill_pages_from_sgt(struct rxe_mr *mr, struct sg_table *sgt) { - XA_STATE(xas, &mr->page_list, 0); struct sg_page_iter sg_iter; struct page *page; bool persistent = !!(mr->access & IB_ACCESS_FLUSH_PERSISTENT); + WARN_ON(mr_page_size(mr) != PAGE_SIZE); + __sg_page_iter_start(&sg_iter, sgt->sgl, sgt->orig_nents, 0); if (!__sg_page_iter_next(&sg_iter)) return 0; - do { - xas_lock(&xas); - while (true) { - page = sg_page_iter_page(&sg_iter); - - if (persistent && !is_pmem_page(page)) { - rxe_dbg_mr(mr, "Page can't be persistent\n"); - xas_set_err(&xas, -EINVAL); - break; - } + while (true) { + page = sg_page_iter_page(&sg_iter); - xas_store(&xas, page); - if (xas_error(&xas)) - break; - xas_next(&xas); - if (!__sg_page_iter_next(&sg_iter)) - break; + if (persistent && !is_pmem_page(page)) { + rxe_dbg_mr(mr, "Page can't be persistent\n"); + return -EINVAL; } - xas_unlock(&xas); - } while (xas_nomem(&xas, GFP_KERNEL)); - return xas_error(&xas); + mr->page_info[mr->nbuf].page = page; + mr->page_info[mr->nbuf].offset = 0; + mr->nbuf++; + + if (!__sg_page_iter_next(&sg_iter)) + break; + } + + return 0; +} + +static int __alloc_mr_page_info(struct rxe_mr *mr, int num_pages) +{ + mr->page_info = kcalloc(num_pages, sizeof(struct rxe_mr_page), + GFP_KERNEL); + if (!mr->page_info) + return -ENOMEM; + + mr->max_allowed_buf = num_pages; + mr->nbuf = 0; + + return 0; +} + +static int alloc_mr_page_info(struct rxe_mr *mr, int num_pages) +{ + int ret; + + WARN_ON(mr->num_buf); + ret = __alloc_mr_page_info(mr, num_pages); + if (ret) + return ret; + + mr->num_buf = num_pages; + + return 0; +} + +static void free_mr_page_info(struct rxe_mr *mr) +{ + if (!mr->page_info) + return; + + kfree(mr->page_info); + mr->page_info = NULL; } int rxe_mr_init_user(struct rxe_dev *rxe, u64 start, u64 length, @@ -134,8 +198,6 @@ int rxe_mr_init_user(struct rxe_dev *rxe, u64 start, u64 length, rxe_mr_init(access, mr); - xa_init(&mr->page_list); - umem = ib_umem_get(&rxe->ib_dev, start, length, access); if (IS_ERR(umem)) { rxe_dbg_mr(mr, "Unable to pin memory region err = %d\n", @@ -143,46 +205,24 @@ int rxe_mr_init_user(struct rxe_dev *rxe, u64 start, u64 length, return PTR_ERR(umem); } + err = alloc_mr_page_info(mr, ib_umem_num_pages(umem)); + if (err) + goto err2; + err = rxe_mr_fill_pages_from_sgt(mr, &umem->sgt_append.sgt); - if (err) { - ib_umem_release(umem); - return err; - } + if (err) + goto err1; mr->umem = umem; mr->ibmr.type = IB_MR_TYPE_USER; mr->state = RXE_MR_STATE_VALID; return 0; -} - -static int rxe_mr_alloc(struct rxe_mr *mr, int num_buf) -{ - XA_STATE(xas, &mr->page_list, 0); - int i = 0; - int err; - - xa_init(&mr->page_list); - - do { - xas_lock(&xas); - while (i != num_buf) { - xas_store(&xas, XA_ZERO_ENTRY); - if (xas_error(&xas)) - break; - xas_next(&xas); - i++; - } - xas_unlock(&xas); - } while (xas_nomem(&xas, GFP_KERNEL)); - - err = xas_error(&xas); - if (err) - return err; - - mr->num_buf = num_buf; - - return 0; +err1: + free_mr_page_info(mr); +err2: + ib_umem_release(umem); + return err; } int rxe_mr_init_fast(int max_pages, struct rxe_mr *mr) @@ -192,7 +232,7 @@ int rxe_mr_init_fast(int max_pages, struct rxe_mr *mr) /* always allow remote access for FMRs */ rxe_mr_init(RXE_ACCESS_REMOTE, mr); - err = rxe_mr_alloc(mr, max_pages); + err = alloc_mr_page_info(mr, max_pages); if (err) goto err1; @@ -205,26 +245,43 @@ int rxe_mr_init_fast(int max_pages, struct rxe_mr *mr) return err; } +/* + * I) MRs with page_size >= PAGE_SIZE, + * Split a large MR page (mr->page_size) into multiple PAGE_SIZE + * sub-pages and store them in page_info, offset is always 0. + * + * Called when mr->page_size > PAGE_SIZE. Each call to rxe_set_page() + * represents one mr->page_size region, which we must split into + * (mr->page_size >> PAGE_SHIFT) individual pages. + * + * II) MRs with page_size < PAGE_SIZE, + * Save each PAGE_SIZE page and its offset within the system page in page_info. + */ static int rxe_set_page(struct ib_mr *ibmr, u64 dma_addr) { struct rxe_mr *mr = to_rmr(ibmr); - struct page *page = ib_virt_dma_to_page(dma_addr); bool persistent = !!(mr->access & IB_ACCESS_FLUSH_PERSISTENT); - int err; + u32 i, pages_per_mr = mr_page_size(mr) >> PAGE_SHIFT; - if (persistent && !is_pmem_page(page)) { - rxe_dbg_mr(mr, "Page cannot be persistent\n"); - return -EINVAL; - } + pages_per_mr = MAX(1, pages_per_mr); - if (unlikely(mr->nbuf == mr->num_buf)) - return -ENOMEM; + for (i = 0; i < pages_per_mr; i++) { + u64 addr = dma_addr + i * PAGE_SIZE; + struct page *sub_page = ib_virt_dma_to_page(addr); - err = xa_err(xa_store(&mr->page_list, mr->nbuf, page, GFP_KERNEL)); - if (err) - return err; + if (unlikely(mr->nbuf >= mr->max_allowed_buf)) + return -ENOMEM; + + if (persistent && !is_pmem_page(sub_page)) { + rxe_dbg_mr(mr, "Page cannot be persistent\n"); + return -EINVAL; + } + + mr->page_info[mr->nbuf].page = sub_page; + mr->page_info[mr->nbuf].offset = addr & (PAGE_SIZE - 1); + mr->nbuf++; + } - mr->nbuf++; return 0; } @@ -234,6 +291,31 @@ int rxe_map_mr_sg(struct ib_mr *ibmr, struct scatterlist *sgl, struct rxe_mr *mr = to_rmr(ibmr); unsigned int page_size = mr_page_size(mr); + /* + * Ensure page_size and PAGE_SIZE are compatible for mapping. + * We require one to be a multiple of the other for correct + * iova-to-page conversion. + */ + if (!IS_ALIGNED(page_size, PAGE_SIZE) && + !IS_ALIGNED(PAGE_SIZE, page_size)) { + rxe_dbg_mr(mr, "MR page size %u must be compatible with PAGE_SIZE %lu\n", + page_size, PAGE_SIZE); + return -EINVAL; + } + + if (mr_page_size(mr) > PAGE_SIZE) { + /* resize page_info if needed */ + u32 map_mr_pages = (page_size >> PAGE_SHIFT) * mr->num_buf; + + if (map_mr_pages > mr->max_allowed_buf) { + rxe_dbg_mr(mr, "requested pages %u exceed max %u\n", + map_mr_pages, mr->max_allowed_buf); + free_mr_page_info(mr); + if (__alloc_mr_page_info(mr, map_mr_pages)) + return -ENOMEM; + } + } + mr->nbuf = 0; mr->page_shift = ilog2(page_size); mr->page_mask = ~((u64)page_size - 1); @@ -245,30 +327,30 @@ int rxe_map_mr_sg(struct ib_mr *ibmr, struct scatterlist *sgl, static int rxe_mr_copy_xarray(struct rxe_mr *mr, u64 iova, void *addr, unsigned int length, enum rxe_mr_copy_dir dir) { - unsigned int page_offset = rxe_mr_iova_to_page_offset(mr, iova); - unsigned long index = rxe_mr_iova_to_index(mr, iova); unsigned int bytes; - struct page *page; - void *va; + u8 *va; while (length) { - page = xa_load(&mr->page_list, index); - if (!page) + unsigned long index = rxe_mr_iova_to_index(mr, iova); + struct rxe_mr_page *info = &mr->page_info[index]; + unsigned int page_offset = rxe_mr_iova_to_page_offset(mr, iova); + + if (!info->page) return -EFAULT; - bytes = min_t(unsigned int, length, - mr_page_size(mr) - page_offset); - va = kmap_local_page(page); + page_offset += info->offset; + bytes = min_t(unsigned int, length, PAGE_SIZE - page_offset); + va = kmap_local_page(info->page); + if (dir == RXE_FROM_MR_OBJ) memcpy(addr, va + page_offset, bytes); else memcpy(va + page_offset, addr, bytes); kunmap_local(va); - page_offset = 0; addr += bytes; + iova += bytes; length -= bytes; - index++; } return 0; @@ -426,9 +508,6 @@ int copy_data( static int rxe_mr_flush_pmem_iova(struct rxe_mr *mr, u64 iova, unsigned int length) { - unsigned int page_offset; - unsigned long index; - struct page *page; unsigned int bytes; int err; u8 *va; @@ -438,15 +517,17 @@ static int rxe_mr_flush_pmem_iova(struct rxe_mr *mr, u64 iova, unsigned int leng return err; while (length > 0) { - index = rxe_mr_iova_to_index(mr, iova); - page = xa_load(&mr->page_list, index); - page_offset = rxe_mr_iova_to_page_offset(mr, iova); - if (!page) + unsigned long index = rxe_mr_iova_to_index(mr, iova); + struct rxe_mr_page *info = &mr->page_info[index]; + unsigned int page_offset = rxe_mr_iova_to_page_offset(mr, iova); + + if (!info->page) return -EFAULT; - bytes = min_t(unsigned int, length, - mr_page_size(mr) - page_offset); - va = kmap_local_page(page); + page_offset += info->offset; + bytes = min_t(unsigned int, length, PAGE_SIZE - page_offset); + + va = kmap_local_page(info->page); arch_wb_cache_pmem(va + page_offset, bytes); kunmap_local(va); @@ -502,6 +583,7 @@ enum resp_states rxe_mr_do_atomic_op(struct rxe_mr *mr, u64 iova, int opcode, } else { unsigned long index; int err; + struct rxe_mr_page *info; err = mr_check_range(mr, iova, sizeof(value)); if (err) { @@ -510,9 +592,12 @@ enum resp_states rxe_mr_do_atomic_op(struct rxe_mr *mr, u64 iova, int opcode, } page_offset = rxe_mr_iova_to_page_offset(mr, iova); index = rxe_mr_iova_to_index(mr, iova); - page = xa_load(&mr->page_list, index); - if (!page) + info = &mr->page_info[index]; + if (!info->page) return RESPST_ERR_RKEY_VIOLATION; + + page_offset += info->offset; + page = info->page; } if (unlikely(page_offset & 0x7)) { @@ -551,6 +636,7 @@ enum resp_states rxe_mr_do_atomic_write(struct rxe_mr *mr, u64 iova, u64 value) } else { unsigned long index; int err; + struct rxe_mr_page *info; /* See IBA oA19-28 */ err = mr_check_range(mr, iova, sizeof(value)); @@ -560,9 +646,12 @@ enum resp_states rxe_mr_do_atomic_write(struct rxe_mr *mr, u64 iova, u64 value) } page_offset = rxe_mr_iova_to_page_offset(mr, iova); index = rxe_mr_iova_to_index(mr, iova); - page = xa_load(&mr->page_list, index); - if (!page) + info = &mr->page_info[index]; + if (!info->page) return RESPST_ERR_RKEY_VIOLATION; + + page_offset += info->offset; + page = info->page; } /* See IBA A19.4.2 */ @@ -726,5 +815,5 @@ void rxe_mr_cleanup(struct rxe_pool_elem *elem) ib_umem_release(mr->umem); if (mr->ibmr.type != IB_MR_TYPE_DMA) - xa_destroy(&mr->page_list); + free_mr_page_info(mr); } diff --git a/drivers/infiniband/sw/rxe/rxe_verbs.h b/drivers/infiniband/sw/rxe/rxe_verbs.h index fd48075810dd10..1b8ed1031bd57f 100644 --- a/drivers/infiniband/sw/rxe/rxe_verbs.h +++ b/drivers/infiniband/sw/rxe/rxe_verbs.h @@ -335,6 +335,11 @@ static inline int rkey_is_mw(u32 rkey) return (index >= RXE_MIN_MW_INDEX) && (index <= RXE_MAX_MW_INDEX); } +struct rxe_mr_page { + struct page *page; + unsigned int offset; /* offset in system page */ +}; + struct rxe_mr { struct rxe_pool_elem elem; struct ib_mr ibmr; @@ -351,10 +356,13 @@ struct rxe_mr { unsigned int page_shift; u64 page_mask; + /* size of page_info when mr allocated */ u32 num_buf; + /* real size of page_info */ + u32 max_allowed_buf; u32 nbuf; - struct xarray page_list; + struct rxe_mr_page *page_info; }; static inline unsigned int mr_page_size(struct rxe_mr *mr) From bf4454da8b1e712714628c0a0d6e7845bb40790a Mon Sep 17 00:00:00 2001 From: Yi Liu Date: Thu, 22 Jan 2026 22:29:00 +0800 Subject: [PATCH 3629/3902] RDMA/uverbs: Validate wqe_size before using it in ib_uverbs_post_send [ Upstream commit 1956f0a74ccf5dc9c3ef717f2985c3ed3400aab0 ] ib_uverbs_post_send() uses cmd.wqe_size from userspace without any validation before passing it to kmalloc() and using the allocated buffer as struct ib_uverbs_send_wr. If a user provides a small wqe_size value (e.g., 1), kmalloc() will succeed, but subsequent accesses to user_wr->opcode, user_wr->num_sge, and other fields will read beyond the allocated buffer, resulting in an out-of-bounds read from kernel heap memory. This could potentially leak sensitive kernel information to userspace. Additionally, providing an excessively large wqe_size can trigger a WARNING in the memory allocation path, as reported by syzkaller. This is inconsistent with ib_uverbs_unmarshall_recv() which properly validates that wqe_size >= sizeof(struct ib_uverbs_recv_wr) before proceeding. Add the same validation for ib_uverbs_post_send() to ensure wqe_size is at least sizeof(struct ib_uverbs_send_wr). Fixes: c3bea3d2dc53 ("RDMA/uverbs: Use the iterator for ib_uverbs_unmarshall_recv()") Signed-off-by: Yi Liu Link: https://patch.msgid.link/20260122142900.2356276-2-liuy22@mails.tsinghua.edu.cn Signed-off-by: Leon Romanovsky Signed-off-by: Sasha Levin --- drivers/infiniband/core/uverbs_cmd.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/drivers/infiniband/core/uverbs_cmd.c b/drivers/infiniband/core/uverbs_cmd.c index ce16404cdfb8cc..3259e9848cc799 100644 --- a/drivers/infiniband/core/uverbs_cmd.c +++ b/drivers/infiniband/core/uverbs_cmd.c @@ -2049,7 +2049,10 @@ static int ib_uverbs_post_send(struct uverbs_attr_bundle *attrs) if (ret) return ret; - user_wr = kmalloc(cmd.wqe_size, GFP_KERNEL); + if (cmd.wqe_size < sizeof(struct ib_uverbs_send_wr)) + return -EINVAL; + + user_wr = kmalloc(cmd.wqe_size, GFP_KERNEL | __GFP_NOWARN); if (!user_wr) return -ENOMEM; From b2bc649c18fbe8a7fd38d17266da3dcbfbcc44d2 Mon Sep 17 00:00:00 2001 From: Zilin Guan Date: Mon, 26 Jan 2026 07:48:01 +0000 Subject: [PATCH 3630/3902] RDMA/mlx5: Fix memory leak in GET_DATA_DIRECT_SYSFS_PATH handler [ Upstream commit 9b9d253908478f504297ac283c514e5953ddafa6 ] The UVERBS_HANDLER(MLX5_IB_METHOD_GET_DATA_DIRECT_SYSFS_PATH) function allocates memory for the device path using kobject_get_path(). If the length of the device path exceeds the output buffer length, the function returns -ENOSPC but does not free the allocated memory, resulting in a memory leak. Add a kfree() call to the error path to ensure the allocated memory is properly freed. Compile tested only. Issue found using a prototype static analysis tool and code review. Fixes: ec7ad6530909 ("RDMA/mlx5: Introduce GET_DATA_DIRECT_SYSFS_PATH ioctl") Signed-off-by: Zilin Guan Link: https://patch.msgid.link/20260126074801.627898-1-zilin@seu.edu.cn Signed-off-by: Leon Romanovsky Signed-off-by: Sasha Levin --- drivers/infiniband/hw/mlx5/std_types.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/infiniband/hw/mlx5/std_types.c b/drivers/infiniband/hw/mlx5/std_types.c index 2fcf553044e15e..1ee31611b4b3f1 100644 --- a/drivers/infiniband/hw/mlx5/std_types.c +++ b/drivers/infiniband/hw/mlx5/std_types.c @@ -195,7 +195,7 @@ static int UVERBS_HANDLER(MLX5_IB_METHOD_GET_DATA_DIRECT_SYSFS_PATH)( int out_len = uverbs_attr_get_len(attrs, MLX5_IB_ATTR_GET_DATA_DIRECT_SYSFS_PATH); u32 dev_path_len; - char *dev_path; + char *dev_path = NULL; int ret; c = to_mucontext(ib_uverbs_get_ucontext(attrs)); @@ -223,9 +223,9 @@ static int UVERBS_HANDLER(MLX5_IB_METHOD_GET_DATA_DIRECT_SYSFS_PATH)( ret = uverbs_copy_to(attrs, MLX5_IB_ATTR_GET_DATA_DIRECT_SYSFS_PATH, dev_path, dev_path_len); - kfree(dev_path); end: + kfree(dev_path); mutex_unlock(&dev->data_direct_lock); return ret; } From 5ae9da022ee3c97e6469eabcddce9271501ddbad Mon Sep 17 00:00:00 2001 From: Li Zhijian Date: Tue, 20 Jan 2026 15:44:37 +0800 Subject: [PATCH 3631/3902] RDMA/rxe: Fix race condition in QP timer handlers [ Upstream commit 87bf646921430e303176edc4eb07c30160361b73 ] I encontered the following warning: WARNING: drivers/infiniband/sw/rxe/rxe_task.c:249 at rxe_sched_task+0x1c8/0x238 [rdma_rxe], CPU#0: swapper/0/0 ... libsha1 [last unloaded: ip6_udp_tunnel] CPU: 0 UID: 0 PID: 0 Comm: swapper/0 Tainted: G C 6.19.0-rc5-64k-v8+ #37 PREEMPT Tainted: [C]=CRAP Hardware name: Raspberry Pi 4 Model B Rev 1.2 Call trace: rxe_sched_task+0x1c8/0x238 [rdma_rxe] (P) retransmit_timer+0x130/0x188 [rdma_rxe] call_timer_fn+0x68/0x4d0 __run_timers+0x630/0x888 ... WARNING: drivers/infiniband/sw/rxe/rxe_task.c:38 at rxe_sched_task+0x1c0/0x238 [rdma_rxe], CPU#0: swapper/0/0 ... WARNING: drivers/infiniband/sw/rxe/rxe_task.c:111 at do_work+0x488/0x5c8 [rdma_rxe], CPU#3: kworker/u17:4/93400 ... refcount_t: underflow; use-after-free. WARNING: lib/refcount.c:28 at refcount_warn_saturate+0x138/0x1a0, CPU#3: kworker/u17:4/93400 The issue is caused by a race condition between retransmit_timer() and rxe_destroy_qp, leading to the Queue Pair's (QP) reference count dropping to zero during timer handler execution. It seems this warning is harmless because rxe_qp_do_cleanup() will flush all pending timers and requests. Example of flow causing the issue: CPU0 CPU1 retransmit_timer() { spin_lock_irqsave rxe_destroy_qp() __rxe_cleanup() __rxe_put() // qp->ref_count decrease to 0 rxe_qp_do_cleanup() { if (qp->valid) { rxe_sched_task() { WARN_ON(rxe_read(task->qp) <= 0); } } spin_unlock_irqrestore } spin_lock_irqsave qp->valid = 0 spin_unlock_irqrestore } Ensure the QP's reference count is maintained and its validity is checked within the timer callbacks by adding calls to rxe_get(qp) and corresponding rxe_put(qp) after use. Signed-off-by: Li Zhijian Fixes: d94671632572 ("RDMA/rxe: Rewrite rxe_task.c") Link: https://patch.msgid.link/20260120074437.623018-1-lizhijian@fujitsu.com Reviewed-by: Zhu Yanjun Signed-off-by: Leon Romanovsky Signed-off-by: Sasha Levin --- drivers/infiniband/sw/rxe/rxe_comp.c | 3 +++ drivers/infiniband/sw/rxe/rxe_req.c | 3 +++ 2 files changed, 6 insertions(+) diff --git a/drivers/infiniband/sw/rxe/rxe_comp.c b/drivers/infiniband/sw/rxe/rxe_comp.c index a5b2b62f596b05..1390e861bd1d74 100644 --- a/drivers/infiniband/sw/rxe/rxe_comp.c +++ b/drivers/infiniband/sw/rxe/rxe_comp.c @@ -119,12 +119,15 @@ void retransmit_timer(struct timer_list *t) rxe_dbg_qp(qp, "retransmit timer fired\n"); + if (!rxe_get(qp)) + return; spin_lock_irqsave(&qp->state_lock, flags); if (qp->valid) { qp->comp.timeout = 1; rxe_sched_task(&qp->send_task); } spin_unlock_irqrestore(&qp->state_lock, flags); + rxe_put(qp); } void rxe_comp_queue_pkt(struct rxe_qp *qp, struct sk_buff *skb) diff --git a/drivers/infiniband/sw/rxe/rxe_req.c b/drivers/infiniband/sw/rxe/rxe_req.c index 373b03f223bebc..12d03f390b0978 100644 --- a/drivers/infiniband/sw/rxe/rxe_req.c +++ b/drivers/infiniband/sw/rxe/rxe_req.c @@ -102,6 +102,8 @@ void rnr_nak_timer(struct timer_list *t) rxe_dbg_qp(qp, "nak timer fired\n"); + if (!rxe_get(qp)) + return; spin_lock_irqsave(&qp->state_lock, flags); if (qp->valid) { /* request a send queue retry */ @@ -110,6 +112,7 @@ void rnr_nak_timer(struct timer_list *t) rxe_sched_task(&qp->send_task); } spin_unlock_irqrestore(&qp->state_lock, flags); + rxe_put(qp); } static void req_check_sq_drain_done(struct rxe_qp *qp) From cc66fc9b5cf7f44a9c34cd0cc42fc3182880c393 Mon Sep 17 00:00:00 2001 From: Chuck Lever Date: Tue, 27 Jan 2026 19:53:59 -0500 Subject: [PATCH 3632/3902] RDMA/core: add rdma_rw_max_sge() helper for SQ sizing [ Upstream commit afcae7d7b8a278a6c29e064f99e5bafd4ac1fb37 ] svc_rdma_accept() computes sc_sq_depth as the sum of rq_depth and the number of rdma_rw contexts (ctxts). This value is used to allocate the Send CQ and to initialize the sc_sq_avail credit pool. However, when the device uses memory registration for RDMA operations, rdma_rw_init_qp() inflates the QP's max_send_wr by a factor of three per context to account for REG and INV work requests. The Send CQ and credit pool remain sized for only one work request per context, causing Send Queue exhaustion under heavy NFS WRITE workloads. Introduce rdma_rw_max_sge() to compute the actual number of Send Queue entries required for a given number of rdma_rw contexts. Upper layer protocols call this helper before creating a Queue Pair so that their Send CQs and credit accounting match the QP's true capacity. Update svc_rdma_accept() to use rdma_rw_max_sge() when computing sc_sq_depth, ensuring the credit pool reflects the work requests that rdma_rw_init_qp() will reserve. Reviewed-by: Christoph Hellwig Fixes: 00bd1439f464 ("RDMA/rw: Support threshold for registration vs scattering to local pages") Signed-off-by: Chuck Lever Link: https://patch.msgid.link/20260128005400.25147-5-cel@kernel.org Signed-off-by: Leon Romanovsky Signed-off-by: Sasha Levin --- drivers/infiniband/core/rw.c | 53 +++++++++++++++++------- include/rdma/rw.h | 2 + net/sunrpc/xprtrdma/svc_rdma_transport.c | 8 +++- 3 files changed, 46 insertions(+), 17 deletions(-) diff --git a/drivers/infiniband/core/rw.c b/drivers/infiniband/core/rw.c index 6354ddf2a274ce..2522ff1cc462c7 100644 --- a/drivers/infiniband/core/rw.c +++ b/drivers/infiniband/core/rw.c @@ -651,34 +651,57 @@ unsigned int rdma_rw_mr_factor(struct ib_device *device, u32 port_num, } EXPORT_SYMBOL(rdma_rw_mr_factor); +/** + * rdma_rw_max_send_wr - compute max Send WRs needed for RDMA R/W contexts + * @dev: RDMA device + * @port_num: port number + * @max_rdma_ctxs: number of rdma_rw_ctx structures + * @create_flags: QP create flags (pass IB_QP_CREATE_INTEGRITY_EN if + * data integrity will be enabled on the QP) + * + * Returns the total number of Send Queue entries needed for + * @max_rdma_ctxs. The result accounts for memory registration and + * invalidation work requests when the device requires them. + * + * ULPs use this to size Send Queues and Send CQs before creating a + * Queue Pair. + */ +unsigned int rdma_rw_max_send_wr(struct ib_device *dev, u32 port_num, + unsigned int max_rdma_ctxs, u32 create_flags) +{ + unsigned int factor = 1; + unsigned int result; + + if (create_flags & IB_QP_CREATE_INTEGRITY_EN || + rdma_rw_can_use_mr(dev, port_num)) + factor += 2; /* reg + inv */ + + if (check_mul_overflow(factor, max_rdma_ctxs, &result)) + return UINT_MAX; + return result; +} +EXPORT_SYMBOL(rdma_rw_max_send_wr); + void rdma_rw_init_qp(struct ib_device *dev, struct ib_qp_init_attr *attr) { - u32 factor; + unsigned int factor = 1; WARN_ON_ONCE(attr->port_num == 0); /* - * Each context needs at least one RDMA READ or WRITE WR. - * - * For some hardware we might need more, eventually we should ask the - * HCA driver for a multiplier here. - */ - factor = 1; - - /* - * If the device needs MRs to perform RDMA READ or WRITE operations, - * we'll need two additional MRs for the registrations and the - * invalidation. + * If the device uses MRs to perform RDMA READ or WRITE operations, + * or if data integrity is enabled, account for registration and + * invalidation work requests. */ if (attr->create_flags & IB_QP_CREATE_INTEGRITY_EN || rdma_rw_can_use_mr(dev, attr->port_num)) - factor += 2; /* inv + reg */ + factor += 2; /* reg + inv */ attr->cap.max_send_wr += factor * attr->cap.max_rdma_ctxs; /* - * But maybe we were just too high in the sky and the device doesn't - * even support all we need, and we'll have to live with what we get.. + * The device might not support all we need, and we'll have to + * live with what we get. */ attr->cap.max_send_wr = min_t(u32, attr->cap.max_send_wr, dev->attrs.max_qp_wr); diff --git a/include/rdma/rw.h b/include/rdma/rw.h index d606cac482338b..9a8f4b76ce588d 100644 --- a/include/rdma/rw.h +++ b/include/rdma/rw.h @@ -66,6 +66,8 @@ int rdma_rw_ctx_post(struct rdma_rw_ctx *ctx, struct ib_qp *qp, u32 port_num, unsigned int rdma_rw_mr_factor(struct ib_device *device, u32 port_num, unsigned int maxpages); +unsigned int rdma_rw_max_send_wr(struct ib_device *dev, u32 port_num, + unsigned int max_rdma_ctxs, u32 create_flags); void rdma_rw_init_qp(struct ib_device *dev, struct ib_qp_init_attr *attr); int rdma_rw_init_mrs(struct ib_qp *qp, struct ib_qp_init_attr *attr); void rdma_rw_cleanup_mrs(struct ib_qp *qp); diff --git a/net/sunrpc/xprtrdma/svc_rdma_transport.c b/net/sunrpc/xprtrdma/svc_rdma_transport.c index 3d7f1413df0233..12857381e86104 100644 --- a/net/sunrpc/xprtrdma/svc_rdma_transport.c +++ b/net/sunrpc/xprtrdma/svc_rdma_transport.c @@ -462,7 +462,10 @@ static struct svc_xprt *svc_rdma_accept(struct svc_xprt *xprt) newxprt->sc_max_bc_requests = 2; } - /* Arbitrary estimate of the needed number of rdma_rw contexts. + /* Estimate the needed number of rdma_rw contexts. The maximum + * Read and Write chunks have one segment each. Each request + * can involve one Read chunk and either a Write chunk or Reply + * chunk; thus a factor of three. */ maxpayload = min(xprt->xpt_server->sv_max_payload, RPCSVC_MAXPAYLOAD_RDMA); @@ -470,7 +473,8 @@ static struct svc_xprt *svc_rdma_accept(struct svc_xprt *xprt) rdma_rw_mr_factor(dev, newxprt->sc_port_num, maxpayload >> PAGE_SHIFT); - newxprt->sc_sq_depth = rq_depth + ctxts; + newxprt->sc_sq_depth = rq_depth + + rdma_rw_max_send_wr(dev, newxprt->sc_port_num, ctxts, 0); if (newxprt->sc_sq_depth > dev->attrs.max_qp_wr) newxprt->sc_sq_depth = dev->attrs.max_qp_wr; atomic_set(&newxprt->sc_sq_avail, newxprt->sc_sq_depth); From eacca141258750059e92c9485e3ed288b0d21c91 Mon Sep 17 00:00:00 2001 From: Yuxiong Wang Date: Thu, 29 Jan 2026 14:45:52 +0800 Subject: [PATCH 3633/3902] cxl: Fix premature commit_end increment on decoder commit failure [ Upstream commit 7b6f9d9b1ea05c9c22570126547c780e8c6c3f62 ] In cxl_decoder_commit(), commit_end is incremented before verifying whether the commit succeeded, and the CXL_DECODER_F_ENABLE bit in cxld->flags is only set after a successful commit. As a result, if the commit fails, commit_end has been incremented and cxld->reset() has no effect since the flag is not set, so commit_end remains incorrectly incremented. The inconsistency between commit_end and CXL_DECODER_F_ENABLE causes failure during subsequent either commit or reset operations. Fix this by incrementing commit_end only after confirming the commit succeeded. Also, remove the ineffective cxld->reset() call. According to CXL Spec r4.0 8.2.4.20.12 Committing Decoder Programming, since cxld_await_commit() has cleared the decoder commit bit on failure, no additional reset is required. [dj: Fixed commit log 80 char wrapping. ] [dj: Fix "Fixes" tag to correct hash length. ] [dj: Change spec to r4.0. ] Fixes: 176baefb2eb5 ("cxl/hdm: Commit decoder state to hardware") Signed-off-by: Yuxiong Wang Acked-by: Huang Ying Reviewed-by: Dave Jiang Reviewed-by: Alison Schofield Link: https://patch.msgid.link/20260129064552.31180-1-yuxiong.wang@linux.alibaba.com Signed-off-by: Dave Jiang Signed-off-by: Sasha Levin --- drivers/cxl/core/hdm.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/drivers/cxl/core/hdm.c b/drivers/cxl/core/hdm.c index 20dd6381080626..13dafac7c6d56d 100644 --- a/drivers/cxl/core/hdm.c +++ b/drivers/cxl/core/hdm.c @@ -844,14 +844,13 @@ static int cxl_decoder_commit(struct cxl_decoder *cxld) scoped_guard(rwsem_read, &cxl_rwsem.dpa) setup_hw_decoder(cxld, hdm); - port->commit_end++; rc = cxld_await_commit(hdm, cxld->id); if (rc) { dev_dbg(&port->dev, "%s: error %d committing decoder\n", dev_name(&cxld->dev), rc); - cxld->reset(cxld); return rc; } + port->commit_end++; cxld->flags |= CXL_DECODER_F_ENABLE; return 0; From 39b74ec0869f58b843ecb5c5a19b1b87cd995b6f Mon Sep 17 00:00:00 2001 From: Weigang He Date: Fri, 23 Jan 2026 05:26:08 +0000 Subject: [PATCH 3634/3902] mtd: parsers: ofpart: fix OF node refcount leak in parse_fixed_partitions() [ Upstream commit 7cce81df7d26d44123bd7620715c8349d96793d7 ] of_get_child_by_name() returns a node pointer with refcount incremented, which must be released with of_node_put() when done. However, in parse_fixed_partitions(), when dedicated is true (i.e., a "partitions" subnode was found), the ofpart_node obtained from of_get_child_by_name() is never released on any code path. Add of_node_put(ofpart_node) calls on all exit paths when dedicated is true to fix the reference count leak. This bug was detected by our static analysis tool. Fixes: 562b4e91d3b2 ("mtd: parsers: ofpart: fix parsing subpartitions") Signed-off-by: Weigang He Signed-off-by: Miquel Raynal Signed-off-by: Sasha Levin --- drivers/mtd/parsers/ofpart_core.c | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/drivers/mtd/parsers/ofpart_core.c b/drivers/mtd/parsers/ofpart_core.c index abfa687989182f..09961c6f39496e 100644 --- a/drivers/mtd/parsers/ofpart_core.c +++ b/drivers/mtd/parsers/ofpart_core.c @@ -77,6 +77,7 @@ static int parse_fixed_partitions(struct mtd_info *master, of_id = of_match_node(parse_ofpart_match_table, ofpart_node); if (dedicated && !of_id) { /* The 'partitions' subnode might be used by another parser */ + of_node_put(ofpart_node); return 0; } @@ -91,12 +92,18 @@ static int parse_fixed_partitions(struct mtd_info *master, nr_parts++; } - if (nr_parts == 0) + if (nr_parts == 0) { + if (dedicated) + of_node_put(ofpart_node); return 0; + } parts = kcalloc(nr_parts, sizeof(*parts), GFP_KERNEL); - if (!parts) + if (!parts) { + if (dedicated) + of_node_put(ofpart_node); return -ENOMEM; + } i = 0; for_each_child_of_node(ofpart_node, pp) { @@ -175,6 +182,9 @@ static int parse_fixed_partitions(struct mtd_info *master, if (quirks && quirks->post_parse) quirks->post_parse(master, parts, nr_parts); + if (dedicated) + of_node_put(ofpart_node); + *pparts = parts; return nr_parts; @@ -183,6 +193,8 @@ static int parse_fixed_partitions(struct mtd_info *master, master->name, pp, mtd_node); ret = -EINVAL; ofpart_none: + if (dedicated) + of_node_put(ofpart_node); of_node_put(pp); kfree(parts); return ret; From 980aa1f8d0dd1291682b588cad7ec8e34d2c462a Mon Sep 17 00:00:00 2001 From: Miquel Raynal Date: Fri, 9 Jan 2026 18:18:02 +0100 Subject: [PATCH 3635/3902] mtd: spinand: Fix kernel doc [ Upstream commit a57b1f07d2d35843a7ada30c8cf9a215c0931868 ] The @data buffer is 5 bytes, not 4, it has been extended for the need of devices with an extra ID bytes. Fixes: 34a956739d29 ("mtd: spinand: Add support for 5-byte IDs") Reviewed-by: Tudor Ambarus Signed-off-by: Miquel Raynal Signed-off-by: Sasha Levin --- include/linux/mtd/spinand.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/linux/mtd/spinand.h b/include/linux/mtd/spinand.h index 927c10d7876958..1c741145e49717 100644 --- a/include/linux/mtd/spinand.h +++ b/include/linux/mtd/spinand.h @@ -287,7 +287,7 @@ struct spinand_device; /** * struct spinand_id - SPI NAND id structure - * @data: buffer containing the id bytes. Currently 4 bytes large, but can + * @data: buffer containing the id bytes. Currently 5 bytes large, but can * be extended if required * @len: ID length */ From ac092258d0d6beac09dccc2a4f4ac9f3a474fb49 Mon Sep 17 00:00:00 2001 From: Weili Qian Date: Thu, 22 Jan 2026 10:02:02 +0800 Subject: [PATCH 3636/3902] hisi_acc_vfio_pci: fix VF reset timeout issue [ Upstream commit a22099ed7936f8e8dabbdbadd97d56047797116b ] If device error occurs during live migration, qemu will reset the VF. At this time, VF reset and device reset are performed simultaneously. The VF reset will timeout. Therefore, the QM_RESETTING flag is used to ensure that VF reset and device reset are performed serially. Fixes: b0eed085903e ("hisi_acc_vfio_pci: Add support for VFIO live migration") Signed-off-by: Weili Qian Link: https://lore.kernel.org/r/20260122020205.2884497-2-liulongfang@huawei.com Signed-off-by: Alex Williamson Signed-off-by: Sasha Levin --- .../vfio/pci/hisilicon/hisi_acc_vfio_pci.c | 24 +++++++++++++++++++ .../vfio/pci/hisilicon/hisi_acc_vfio_pci.h | 2 ++ 2 files changed, 26 insertions(+) diff --git a/drivers/vfio/pci/hisilicon/hisi_acc_vfio_pci.c b/drivers/vfio/pci/hisilicon/hisi_acc_vfio_pci.c index d07093d7cc3f5c..ed2ae035deb167 100644 --- a/drivers/vfio/pci/hisilicon/hisi_acc_vfio_pci.c +++ b/drivers/vfio/pci/hisilicon/hisi_acc_vfio_pci.c @@ -1169,9 +1169,32 @@ hisi_acc_vfio_pci_get_device_state(struct vfio_device *vdev, return 0; } +static void hisi_acc_vf_pci_reset_prepare(struct pci_dev *pdev) +{ + struct hisi_acc_vf_core_device *hisi_acc_vdev = hisi_acc_drvdata(pdev); + struct hisi_qm *qm = hisi_acc_vdev->pf_qm; + struct device *dev = &qm->pdev->dev; + u32 delay = 0; + + /* All reset requests need to be queued for processing */ + while (test_and_set_bit(QM_RESETTING, &qm->misc_ctl)) { + msleep(1); + if (++delay > QM_RESET_WAIT_TIMEOUT) { + dev_err(dev, "reset prepare failed\n"); + return; + } + } + + hisi_acc_vdev->set_reset_flag = true; +} + static void hisi_acc_vf_pci_aer_reset_done(struct pci_dev *pdev) { struct hisi_acc_vf_core_device *hisi_acc_vdev = hisi_acc_drvdata(pdev); + struct hisi_qm *qm = hisi_acc_vdev->pf_qm; + + if (hisi_acc_vdev->set_reset_flag) + clear_bit(QM_RESETTING, &qm->misc_ctl); if (hisi_acc_vdev->core_device.vdev.migration_flags != VFIO_MIGRATION_STOP_COPY) @@ -1690,6 +1713,7 @@ static const struct pci_device_id hisi_acc_vfio_pci_table[] = { MODULE_DEVICE_TABLE(pci, hisi_acc_vfio_pci_table); static const struct pci_error_handlers hisi_acc_vf_err_handlers = { + .reset_prepare = hisi_acc_vf_pci_reset_prepare, .reset_done = hisi_acc_vf_pci_aer_reset_done, .error_detected = vfio_pci_core_aer_err_detected, }; diff --git a/drivers/vfio/pci/hisilicon/hisi_acc_vfio_pci.h b/drivers/vfio/pci/hisilicon/hisi_acc_vfio_pci.h index 91002ceeebc18a..6253fa074003ea 100644 --- a/drivers/vfio/pci/hisilicon/hisi_acc_vfio_pci.h +++ b/drivers/vfio/pci/hisilicon/hisi_acc_vfio_pci.h @@ -27,6 +27,7 @@ #define ERROR_CHECK_TIMEOUT 100 #define CHECK_DELAY_TIME 100 +#define QM_RESET_WAIT_TIMEOUT 60000 #define QM_SQC_VFT_BASE_SHIFT_V2 28 #define QM_SQC_VFT_BASE_MASK_V2 GENMASK(15, 0) @@ -110,6 +111,7 @@ struct hisi_acc_vf_migration_file { struct hisi_acc_vf_core_device { struct vfio_pci_core_device core_device; u8 match_done; + bool set_reset_flag; /* * io_base is only valid when dev_opened is true, * which is protected by open_mutex. From 47abfc207ab02cf1297257e282e8048da63f0d08 Mon Sep 17 00:00:00 2001 From: Waqar Hameed Date: Fri, 23 Jan 2026 11:24:20 +0100 Subject: [PATCH 3637/3902] power: supply: pm8916_lbc: Fix use-after-free for extcon in IRQ handler [ Upstream commit 23067259919663580c6f81801847cfc7bd54fd1f ] Using the `devm_` variant for requesting IRQ _before_ the `devm_` variant for allocating/registering the `extcon` handle, means that the `extcon` handle will be deallocated/unregistered _before_ the interrupt handler (since `devm_` naturally deallocates in reverse allocation order). This means that during removal, there is a race condition where an interrupt can fire just _after_ the `extcon` handle has been freed, *but* just _before_ the corresponding unregistration of the IRQ handler has run. This will lead to the IRQ handler calling `extcon_set_state_sync()` with a freed `extcon` handle. Which usually crashes the system or otherwise silently corrupts the memory... Fix this racy use-after-free by making sure the IRQ is requested _after_ the registration of the `extcon` handle. Fixes: f8d7a3d21160 ("power: supply: Add driver for pm8916 lbc") Signed-off-by: Waqar Hameed Reviewed-by: Nikita Travkin Link: https://patch.msgid.link/e2a4cd2fcd42b6cd97d856c17c097289a2aed393.1769163273.git.waqar.hameed@axis.com Signed-off-by: Sebastian Reichel Signed-off-by: Sasha Levin --- drivers/power/supply/pm8916_lbc.c | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/drivers/power/supply/pm8916_lbc.c b/drivers/power/supply/pm8916_lbc.c index 3ca717d84aade6..6b631012a7959a 100644 --- a/drivers/power/supply/pm8916_lbc.c +++ b/drivers/power/supply/pm8916_lbc.c @@ -327,11 +327,6 @@ static int pm8916_lbc_charger_probe(struct platform_device *pdev) if (irq < 0) return irq; - ret = devm_request_threaded_irq(dev, irq, NULL, pm8916_lbc_charger_state_changed_irq, - IRQF_ONESHOT, "pm8916_lbc", chg); - if (ret) - return ret; - chg->edev = devm_extcon_dev_allocate(dev, pm8916_lbc_charger_cable); if (IS_ERR(chg->edev)) return PTR_ERR(chg->edev); @@ -340,6 +335,11 @@ static int pm8916_lbc_charger_probe(struct platform_device *pdev) if (ret < 0) return dev_err_probe(dev, ret, "failed to register extcon device\n"); + ret = devm_request_threaded_irq(dev, irq, NULL, pm8916_lbc_charger_state_changed_irq, + IRQF_ONESHOT, "pm8916_lbc", chg); + if (ret) + return ret; + ret = regmap_read(chg->regmap, chg->reg[LBC_USB] + PM8916_INT_RT_STS, &tmp); if (ret) goto comm_error; From f354f16e96989bd93c0f7ccb33b2c96851a94ea2 Mon Sep 17 00:00:00 2001 From: Val Packett Date: Tue, 20 Jan 2026 20:57:58 -0300 Subject: [PATCH 3638/3902] power: supply: qcom_battmgr: Recognize "LiP" as lithium-polymer [ Upstream commit c655f45480637aee326b5bd96488d35ab90db2b0 ] On the Dell Latitude 7455, the firmware uses "LiP" with a lowercase 'i' for the battery chemistry type, but only all-uppercase "LIP" was being recognized. Add the CamelCase variant to the check to fix the "Unknown battery technology" warning. Fixes: 202ac22b8e2e ("power: supply: qcom_battmgr: Add lithium-polymer entry") Signed-off-by: Val Packett Reviewed-by: Konrad Dybcio Reviewed-by: Dmitry Baryshkov Link: https://patch.msgid.link/20260120235831.479038-1-val@packett.cool Signed-off-by: Sebastian Reichel Signed-off-by: Sasha Levin --- drivers/power/supply/qcom_battmgr.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/drivers/power/supply/qcom_battmgr.c b/drivers/power/supply/qcom_battmgr.c index e6f01e0122e1c1..ff77dba29a3efc 100644 --- a/drivers/power/supply/qcom_battmgr.c +++ b/drivers/power/supply/qcom_battmgr.c @@ -1244,7 +1244,8 @@ static unsigned int qcom_battmgr_sc8280xp_parse_technology(const char *chemistry if ((!strncmp(chemistry, "LIO", BATTMGR_CHEMISTRY_LEN)) || (!strncmp(chemistry, "OOI", BATTMGR_CHEMISTRY_LEN))) return POWER_SUPPLY_TECHNOLOGY_LION; - if (!strncmp(chemistry, "LIP", BATTMGR_CHEMISTRY_LEN)) + if (!strncmp(chemistry, "LIP", BATTMGR_CHEMISTRY_LEN) || + !strncmp(chemistry, "LiP", BATTMGR_CHEMISTRY_LEN)) return POWER_SUPPLY_TECHNOLOGY_LIPO; pr_err("Unknown battery technology '%s'\n", chemistry); From 7bc44485182c2f4bb27ce070da1e1c0a2437ba8d Mon Sep 17 00:00:00 2001 From: Yi Liu Date: Thu, 29 Jan 2026 17:49:00 +0800 Subject: [PATCH 3639/3902] RDMA/uverbs: Add __GFP_NOWARN to ib_uverbs_unmarshall_recv() kmalloc [ Upstream commit 58b604dfc7bb753f91bc0ccd3fa705e14e6edfb4 ] Since wqe_size in ib_uverbs_unmarshall_recv() is user-provided and already validated, but can still be large, add __GFP_NOWARN to suppress memory allocation warnings for large sizes, consistent with the similar fix in ib_uverbs_post_send(). Fixes: 67cdb40ca444 ("[IB] uverbs: Implement more commands") Signed-off-by: Yi Liu Link: https://patch.msgid.link/20260129094900.3517706-1-liuy22@mails.tsinghua.edu.cn Signed-off-by: Leon Romanovsky Signed-off-by: Sasha Levin --- drivers/infiniband/core/uverbs_cmd.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/infiniband/core/uverbs_cmd.c b/drivers/infiniband/core/uverbs_cmd.c index 3259e9848cc799..f4616deeca5453 100644 --- a/drivers/infiniband/core/uverbs_cmd.c +++ b/drivers/infiniband/core/uverbs_cmd.c @@ -2242,7 +2242,7 @@ ib_uverbs_unmarshall_recv(struct uverbs_req_iter *iter, u32 wr_count, if (ret) return ERR_PTR(ret); - user_wr = kmalloc(wqe_size, GFP_KERNEL); + user_wr = kmalloc(wqe_size, GFP_KERNEL | __GFP_NOWARN); if (!user_wr) return ERR_PTR(-ENOMEM); From 48cb99e745614fe112996d48cc49580a01865135 Mon Sep 17 00:00:00 2001 From: Olga Kornievskaia Date: Mon, 26 Jan 2026 14:15:39 -0500 Subject: [PATCH 3640/3902] pNFS: fix a missing wake up while waiting on NFS_LAYOUT_DRAIN [ Upstream commit 5248d8474e594d156bee1ed10339cc16e207a28b ] It is possible to have a task get stuck on waiting on the NFS_LAYOUT_DRAIN in the following scenario 1. cpu a: waiter test NFS_LAYOUT_DRAIN (1) and plh_outstanding (1) 2. cpu b: atomic_dec_and_test() -> clear bit -> wake up 3. cpu c: sets NFS_LAYOUT_DRAIN again 4. cpu a: calls wait_on_bit() sleeps forever. To expand on this we have say 2 outstanding pnfs write IO that get ESTALE which causes both to call pnfs_destroy_layout() and set the NFS_LAYOUT_DRAIN bit but the 1st one doesn't call the pnfs_put_layout_hdr() yet (as that would prevent the 2nd ESTALE write from trying to call pnfs_destroy_layout()). If the 1st ESTALE write is the one that initially sets the NFS_LAYOUT_DRAIN so that new IO on this file initiates new LAYOUTGET. Another new write would find NFS_LAYOUT_DRAIN set and phl_outstanding>0 (step 1) and would wait_on_bit(). LAYOUTGET completes doing step 2. Now, the 2nd of ESTALE writes is calling pnfs_destory_layout() and set the NFS_LAYOUT_DRAIN bit (step 3). Finally, the waiting write wakes up to check the bit and goes back to sleep. The problem revolves around the fact that if NFS_LAYOUT_INVALID_STID was already set, it should not do the work of pnfs_mark_layout_stateid_invalid(), thus NFS_LAYOUT_DRAIN will not be set more than once for an invalid layout. Suggested-by: Trond Myklebust Fixes: 880265c77ac4 ("pNFS: Avoid a live lock condition in pnfs_update_layout()") Signed-off-by: Olga Kornievskaia Signed-off-by: Anna Schumaker Signed-off-by: Sasha Levin --- fs/nfs/pnfs.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/fs/nfs/pnfs.c b/fs/nfs/pnfs.c index 33bc6db0dc92f8..b3cb5ee9d821ef 100644 --- a/fs/nfs/pnfs.c +++ b/fs/nfs/pnfs.c @@ -463,7 +463,8 @@ pnfs_mark_layout_stateid_invalid(struct pnfs_layout_hdr *lo, }; struct pnfs_layout_segment *lseg, *next; - set_bit(NFS_LAYOUT_INVALID_STID, &lo->plh_flags); + if (test_and_set_bit(NFS_LAYOUT_INVALID_STID, &lo->plh_flags)) + return !list_empty(&lo->plh_segs); clear_bit(NFS_INO_LAYOUTCOMMIT, &NFS_I(lo->plh_inode)->flags); list_for_each_entry_safe(lseg, next, &lo->plh_segs, pls_list) pnfs_clear_lseg_state(lseg, lseg_list); From e5579ebaadc7b699868dad0f591a7bf83cd647e1 Mon Sep 17 00:00:00 2001 From: Zilin Guan Date: Sat, 31 Jan 2026 09:36:41 +0000 Subject: [PATCH 3641/3902] scsi: smartpqi: Fix memory leak in pqi_report_phys_luns() [ Upstream commit 41b37312bd9722af77ec7817ccf22d7a4880c289 ] pqi_report_phys_luns() fails to release the rpl_list buffer when encountering an unsupported data format or when the allocation for rpl_16byte_wwid_list fails. These early returns bypass the cleanup logic, leading to memory leaks. Consolidate the error handling by adding an out_free_rpl_list label and use goto statements to ensure rpl_list is consistently freed on failure. Compile tested only. Issue found using a prototype static analysis tool and code review. Fixes: 28ca6d876c5a ("scsi: smartpqi: Add extended report physical LUNs") Signed-off-by: Zilin Guan Tested-by: Don Brace Acked-by: Don Brace Link: https://patch.msgid.link/20260131093641.1008117-1-zilin@seu.edu.cn Signed-off-by: Martin K. Petersen Signed-off-by: Sasha Levin --- drivers/scsi/smartpqi/smartpqi_init.c | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/drivers/scsi/smartpqi/smartpqi_init.c b/drivers/scsi/smartpqi/smartpqi_init.c index 98e93900254cb2..5a6e1bb57e7c84 100644 --- a/drivers/scsi/smartpqi/smartpqi_init.c +++ b/drivers/scsi/smartpqi/smartpqi_init.c @@ -1241,7 +1241,8 @@ static inline int pqi_report_phys_luns(struct pqi_ctrl_info *ctrl_info, void **b dev_err(&ctrl_info->pci_dev->dev, "RPL returned unsupported data format %u\n", rpl_response_format); - return -EINVAL; + rc = -EINVAL; + goto out_free_rpl_list; } else { dev_warn(&ctrl_info->pci_dev->dev, "RPL returned extended format 2 instead of 4\n"); @@ -1253,8 +1254,10 @@ static inline int pqi_report_phys_luns(struct pqi_ctrl_info *ctrl_info, void **b rpl_16byte_wwid_list = kmalloc(struct_size(rpl_16byte_wwid_list, lun_entries, num_physicals), GFP_KERNEL); - if (!rpl_16byte_wwid_list) - return -ENOMEM; + if (!rpl_16byte_wwid_list) { + rc = -ENOMEM; + goto out_free_rpl_list; + } put_unaligned_be32(num_physicals * sizeof(struct report_phys_lun_16byte_wwid), &rpl_16byte_wwid_list->header.list_length); @@ -1275,6 +1278,10 @@ static inline int pqi_report_phys_luns(struct pqi_ctrl_info *ctrl_info, void **b *buffer = rpl_16byte_wwid_list; return 0; + +out_free_rpl_list: + kfree(rpl_list); + return rc; } static inline int pqi_report_logical_luns(struct pqi_ctrl_info *ctrl_info, void **buffer) From 77794465365bf1893e06faf684559eeee4adc305 Mon Sep 17 00:00:00 2001 From: Arnd Bergmann Date: Mon, 2 Feb 2026 10:50:18 +0100 Subject: [PATCH 3642/3902] scsi: ufs: host: mediatek: Require CONFIG_PM [ Upstream commit bbb8d98fb4536594cb104fd630ea0f7dce3771d6 ] The added print statement from a recent fix causes the driver to fail building when CONFIG_PM is disabled: drivers/ufs/host/ufs-mediatek.c: In function 'ufs_mtk_resume': drivers/ufs/host/ufs-mediatek.c:1890:40: error: 'struct dev_pm_info' has no member named 'request' 1890 | hba->dev->power.request, It seems unlikely that the driver can work at all without CONFIG_PM, so just add a dependency and remove the existing ifdef checks, rather than adding another ifdef. Fixes: 15ef3f5aa822 ("scsi: ufs: host: mediatek: Enhance recovery on resume failure") Signed-off-by: Arnd Bergmann Reviewed-by: AngeloGioacchino Del Regno Link: https://patch.msgid.link/20260202095052.1232703-1-arnd@kernel.org Signed-off-by: Martin K. Petersen Signed-off-by: Sasha Levin --- drivers/ufs/host/Kconfig | 1 + drivers/ufs/host/ufs-mediatek.c | 12 +++--------- include/ufs/ufshcd.h | 4 ---- 3 files changed, 4 insertions(+), 13 deletions(-) diff --git a/drivers/ufs/host/Kconfig b/drivers/ufs/host/Kconfig index 191fbd799ec5bc..48ee7e9b665efc 100644 --- a/drivers/ufs/host/Kconfig +++ b/drivers/ufs/host/Kconfig @@ -72,6 +72,7 @@ config SCSI_UFS_QCOM config SCSI_UFS_MEDIATEK tristate "Mediatek specific hooks to UFS controller platform driver" depends on SCSI_UFSHCD_PLATFORM && ARCH_MEDIATEK + depends on PM depends on RESET_CONTROLLER select PHY_MTK_UFS select RESET_TI_SYSCON diff --git a/drivers/ufs/host/ufs-mediatek.c b/drivers/ufs/host/ufs-mediatek.c index d0cbd96ad29dc5..3c63adca561d2b 100644 --- a/drivers/ufs/host/ufs-mediatek.c +++ b/drivers/ufs/host/ufs-mediatek.c @@ -2366,7 +2366,6 @@ static void ufs_mtk_remove(struct platform_device *pdev) ufshcd_pltfrm_remove(pdev); } -#ifdef CONFIG_PM_SLEEP static int ufs_mtk_system_suspend(struct device *dev) { struct ufs_hba *hba = dev_get_drvdata(dev); @@ -2413,9 +2412,7 @@ static int ufs_mtk_system_resume(struct device *dev) return ret; } -#endif -#ifdef CONFIG_PM static int ufs_mtk_runtime_suspend(struct device *dev) { struct ufs_hba *hba = dev_get_drvdata(dev); @@ -2454,13 +2451,10 @@ static int ufs_mtk_runtime_resume(struct device *dev) return ufshcd_runtime_resume(dev); } -#endif static const struct dev_pm_ops ufs_mtk_pm_ops = { - SET_SYSTEM_SLEEP_PM_OPS(ufs_mtk_system_suspend, - ufs_mtk_system_resume) - SET_RUNTIME_PM_OPS(ufs_mtk_runtime_suspend, - ufs_mtk_runtime_resume, NULL) + SYSTEM_SLEEP_PM_OPS(ufs_mtk_system_suspend, ufs_mtk_system_resume) + RUNTIME_PM_OPS(ufs_mtk_runtime_suspend, ufs_mtk_runtime_resume, NULL) .prepare = ufshcd_suspend_prepare, .complete = ufshcd_resume_complete, }; @@ -2470,7 +2464,7 @@ static struct platform_driver ufs_mtk_pltform = { .remove = ufs_mtk_remove, .driver = { .name = "ufshcd-mtk", - .pm = &ufs_mtk_pm_ops, + .pm = pm_ptr(&ufs_mtk_pm_ops), .of_match_table = ufs_mtk_of_match, }, }; diff --git a/include/ufs/ufshcd.h b/include/ufs/ufshcd.h index d949db3a467596..17fe07dac6a7e3 100644 --- a/include/ufs/ufshcd.h +++ b/include/ufs/ufshcd.h @@ -1350,17 +1350,13 @@ static inline void *ufshcd_get_variant(struct ufs_hba *hba) return hba->priv; } -#ifdef CONFIG_PM extern int ufshcd_runtime_suspend(struct device *dev); extern int ufshcd_runtime_resume(struct device *dev); -#endif -#ifdef CONFIG_PM_SLEEP extern int ufshcd_system_suspend(struct device *dev); extern int ufshcd_system_resume(struct device *dev); extern int ufshcd_system_freeze(struct device *dev); extern int ufshcd_system_thaw(struct device *dev); extern int ufshcd_system_restore(struct device *dev); -#endif extern int ufshcd_dme_reset(struct ufs_hba *hba); extern int ufshcd_dme_enable(struct ufs_hba *hba); From 25ab5e97d3c5f3ed594b4a65d1cc99dc24756681 Mon Sep 17 00:00:00 2001 From: Colin Ian King Date: Thu, 29 Jan 2026 15:53:32 +0000 Subject: [PATCH 3643/3902] scsi: csiostor: Fix dereference of null pointer rn [ Upstream commit 1982257570b84dc33753d536dd969fd357a014e9 ] The error exit path when rn is NULL ends up deferencing the null pointer rn via the use of the macro CSIO_INC_STATS. Fix this by adding a new error return path label after the use of the macro to avoid the deference. Fixes: a3667aaed569 ("[SCSI] csiostor: Chelsio FCoE offload driver") Signed-off-by: Colin Ian King Link: https://patch.msgid.link/20260129155332.196338-1-colin.i.king@gmail.com Signed-off-by: Martin K. Petersen Signed-off-by: Sasha Levin --- drivers/scsi/csiostor/csio_scsi.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/drivers/scsi/csiostor/csio_scsi.c b/drivers/scsi/csiostor/csio_scsi.c index 34bde6650fae0f..356a7c577ec3ef 100644 --- a/drivers/scsi/csiostor/csio_scsi.c +++ b/drivers/scsi/csiostor/csio_scsi.c @@ -2074,7 +2074,7 @@ csio_eh_lun_reset_handler(struct scsi_cmnd *cmnd) struct csio_scsi_level_data sld; if (!rn) - goto fail; + goto fail_ret; csio_dbg(hw, "Request to reset LUN:%llu (ssni:0x%x tgtid:%d)\n", cmnd->device->lun, rn->flowid, rn->scsi_id); @@ -2220,6 +2220,7 @@ csio_eh_lun_reset_handler(struct scsi_cmnd *cmnd) csio_put_scsi_ioreq_lock(hw, scsim, ioreq); fail: CSIO_INC_STATS(rn, n_lun_rst_fail); +fail_ret: return FAILED; } From e568088337e5a66b7e959153e0c9b6e68d98b85c Mon Sep 17 00:00:00 2001 From: Li Chen Date: Tue, 3 Feb 2026 10:13:51 +0800 Subject: [PATCH 3644/3902] nvdimm: virtio_pmem: serialize flush requests [ Upstream commit a9ba6733c7f1096c4506bf4e34a546e07242df74 ] Under heavy concurrent flush traffic, virtio-pmem can overflow its request virtqueue (req_vq): virtqueue_add_sgs() starts returning -ENOSPC and the driver logs "no free slots in the virtqueue". Shortly after that the device enters VIRTIO_CONFIG_S_NEEDS_RESET and flush requests fail with "virtio pmem device needs a reset". Serialize virtio_pmem_flush() with a per-device mutex so only one flush request is in-flight at a time. This prevents req_vq descriptor overflow under high concurrency. Reproducer (guest with virtio-pmem): - mkfs.ext4 -F /dev/pmem0 - mount -t ext4 -o dax,noatime /dev/pmem0 /mnt/bench - fio: ioengine=io_uring rw=randwrite bs=4k iodepth=64 numjobs=64 direct=1 fsync=1 runtime=30s time_based=1 - dmesg: "no free slots in the virtqueue" "virtio pmem device needs a reset" Fixes: 6e84200c0a29 ("virtio-pmem: Add virtio pmem driver") Signed-off-by: Li Chen Acked-by: Pankaj Gupta Acked-by: Michael S. Tsirkin Link: https://patch.msgid.link/20260203021353.121091-1-me@linux.beauty Signed-off-by: Ira Weiny Signed-off-by: Sasha Levin --- drivers/nvdimm/nd_virtio.c | 3 ++- drivers/nvdimm/virtio_pmem.c | 1 + drivers/nvdimm/virtio_pmem.h | 4 ++++ 3 files changed, 7 insertions(+), 1 deletion(-) diff --git a/drivers/nvdimm/nd_virtio.c b/drivers/nvdimm/nd_virtio.c index c3f07be4aa22ad..af82385be7c6aa 100644 --- a/drivers/nvdimm/nd_virtio.c +++ b/drivers/nvdimm/nd_virtio.c @@ -44,6 +44,8 @@ static int virtio_pmem_flush(struct nd_region *nd_region) unsigned long flags; int err, err1; + guard(mutex)(&vpmem->flush_lock); + /* * Don't bother to submit the request to the device if the device is * not activated. @@ -53,7 +55,6 @@ static int virtio_pmem_flush(struct nd_region *nd_region) return -EIO; } - might_sleep(); req_data = kmalloc(sizeof(*req_data), GFP_KERNEL); if (!req_data) return -ENOMEM; diff --git a/drivers/nvdimm/virtio_pmem.c b/drivers/nvdimm/virtio_pmem.c index 2396d19ce54969..77b1966619059c 100644 --- a/drivers/nvdimm/virtio_pmem.c +++ b/drivers/nvdimm/virtio_pmem.c @@ -64,6 +64,7 @@ static int virtio_pmem_probe(struct virtio_device *vdev) goto out_err; } + mutex_init(&vpmem->flush_lock); vpmem->vdev = vdev; vdev->priv = vpmem; err = init_vq(vpmem); diff --git a/drivers/nvdimm/virtio_pmem.h b/drivers/nvdimm/virtio_pmem.h index 0dddefe594c46a..f72cf17f9518fb 100644 --- a/drivers/nvdimm/virtio_pmem.h +++ b/drivers/nvdimm/virtio_pmem.h @@ -13,6 +13,7 @@ #include #include #include +#include #include struct virtio_pmem_request { @@ -35,6 +36,9 @@ struct virtio_pmem { /* Virtio pmem request queue */ struct virtqueue *req_vq; + /* Serialize flush requests to the device. */ + struct mutex flush_lock; + /* nvdimm bus registers virtio pmem device */ struct nvdimm_bus *nvdimm_bus; struct nvdimm_bus_descriptor nd_desc; From 47d3fed638664a72b2b84f02b8ef85d7fe75c048 Mon Sep 17 00:00:00 2001 From: Sagi Grimberg Date: Sat, 27 Dec 2025 12:46:29 +0200 Subject: [PATCH 3645/3902] fs/nfs: Fix readdir slow-start regression [ Upstream commit 42e7c876b182da65723700f6bc507a8aecb10d3b ] Commit 580f236737d1 ("NFS: Adjust the amount of readahead performed by NFS readdir") reduces the amount of readahead names caching done by the client. The downside of this approach is READDIR now may suffer from a slow-start issue, where initially it will fetch names that fit in a single page, then in 2, 4, 8 until the maximum supported transfer size (usually 1M). This patch tries to take a balanced approach between mitigating the slow-start issue still maintaining some efficiency gains. Fixes: 580f236737d1 ("NFS: Adjust the amount of readahead performed by NFS readdir") Signed-off-by: Sagi Grimberg Signed-off-by: Anna Schumaker Signed-off-by: Sasha Levin --- fs/nfs/dir.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/fs/nfs/dir.c b/fs/nfs/dir.c index 3b8250ee014126..a653a401b79701 100644 --- a/fs/nfs/dir.c +++ b/fs/nfs/dir.c @@ -72,7 +72,7 @@ const struct address_space_operations nfs_dir_aops = { .free_folio = nfs_readdir_clear_array, }; -#define NFS_INIT_DTSIZE PAGE_SIZE +#define NFS_INIT_DTSIZE SZ_64K static struct nfs_open_dir_context * alloc_nfs_open_dir_context(struct inode *dir) @@ -83,7 +83,7 @@ alloc_nfs_open_dir_context(struct inode *dir) ctx = kzalloc(sizeof(*ctx), GFP_KERNEL_ACCOUNT); if (ctx != NULL) { ctx->attr_gencount = nfsi->attr_gencount; - ctx->dtsize = NFS_INIT_DTSIZE; + ctx->dtsize = min(NFS_SERVER(dir)->dtsize, NFS_INIT_DTSIZE); spin_lock(&dir->i_lock); if (list_empty(&nfsi->open_files) && (nfsi->cache_validity & NFS_INO_DATA_INVAL_DEFER)) From 335dfe4bc6368e70e8c15419375cf609c4f85558 Mon Sep 17 00:00:00 2001 From: Miaoqian Lin Date: Thu, 11 Dec 2025 14:00:58 +0400 Subject: [PATCH 3646/3902] tracing: Properly process error handling in event_hist_trigger_parse() [ Upstream commit 0550069cc25f513ce1f109c88f7c1f01d63297db ] Memory allocated with trigger_data_alloc() requires trigger_data_free() for proper cleanup. Replace kfree() with trigger_data_free() to fix this. Found via static analysis and code review. This isn't a real bug due to the current code basically being an open coded version of trigger_data_free() without the synchronization. The synchronization isn't needed as this is the error path of creation and there's nothing to synchronize against yet. Replace the kfree() to be consistent with the allocation. Cc: Masami Hiramatsu Cc: Mathieu Desnoyers Cc: Tom Zanussi Link: https://patch.msgid.link/20251211100058.2381268-1-linmq006@gmail.com Fixes: e1f187d09e11 ("tracing: Have existing event_command.parse() implementations use helpers") Signed-off-by: Miaoqian Lin Signed-off-by: Steven Rostedt (Google) Signed-off-by: Sasha Levin --- kernel/trace/trace_events_hist.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/kernel/trace/trace_events_hist.c b/kernel/trace/trace_events_hist.c index 425ae26064bab0..45727c4cf9545d 100644 --- a/kernel/trace/trace_events_hist.c +++ b/kernel/trace/trace_events_hist.c @@ -6909,7 +6909,7 @@ static int event_hist_trigger_parse(struct event_command *cmd_ops, remove_hist_vars(hist_data); - kfree(trigger_data); + trigger_data_free(trigger_data); destroy_hist_data(hist_data); goto out; From ca8f2551772d120d3b37b2d933db0aba09542f71 Mon Sep 17 00:00:00 2001 From: Steven Rostedt Date: Mon, 26 Jan 2026 13:00:37 -0500 Subject: [PATCH 3647/3902] tracing: Remove duplicate ENABLE_EVENT_STR and DISABLE_EVENT_STR macros [ Upstream commit 9df0e49c5b9b8d051529be9994e4f92f2d20be6f ] The macros ENABLE_EVENT_STR and DISABLE_EVENT_STR were added to trace.h so that more than one file can have access to them, but was never removed from their original location. Remove the duplicates. Cc: Masami Hiramatsu Cc: Mathieu Desnoyers Cc: Tom Zanussi Link: https://patch.msgid.link/20260126130037.4ba201f9@gandalf.local.home Fixes: d0bad49bb0a09 ("tracing: Add enable_hist/disable_hist triggers") Signed-off-by: Steven Rostedt (Google) Signed-off-by: Sasha Levin --- kernel/trace/trace_events.c | 5 ----- 1 file changed, 5 deletions(-) diff --git a/kernel/trace/trace_events.c b/kernel/trace/trace_events.c index 099f0813290213..5cf55a9c6fad4b 100644 --- a/kernel/trace/trace_events.c +++ b/kernel/trace/trace_events.c @@ -3964,11 +3964,6 @@ void trace_put_event_file(struct trace_event_file *file) EXPORT_SYMBOL_GPL(trace_put_event_file); #ifdef CONFIG_DYNAMIC_FTRACE - -/* Avoid typos */ -#define ENABLE_EVENT_STR "enable_event" -#define DISABLE_EVENT_STR "disable_event" - struct event_probe_data { struct trace_event_file *file; unsigned long count; From 9a10843740d777707de3e8faca02752dde297127 Mon Sep 17 00:00:00 2001 From: Shengjiu Wang Date: Thu, 18 Dec 2025 15:17:50 +0800 Subject: [PATCH 3648/3902] remoteproc: imx_dsp_rproc: Only reset carveout memory at RPROC_OFFLINE state [ Upstream commit b490ddf27be28e64a39c08ae643d7b22561beaf6 ] Do not reset memory at suspend and resume stage, because some memory is used to save the software state for resume, if it is cleared, the resume operation can fail. Fixes: c4c432dfb00f ("remoteproc: imx_dsp_rproc: Add support of recovery and coredump process") Signed-off-by: Shengjiu Wang Reviewed-by: Daniel Baluta Reviewed-by: Iuliana Prodan Link: https://lore.kernel.org/r/20251218071750.2692132-1-shengjiu.wang@nxp.com Signed-off-by: Mathieu Poirier Signed-off-by: Sasha Levin --- drivers/remoteproc/imx_dsp_rproc.c | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/drivers/remoteproc/imx_dsp_rproc.c b/drivers/remoteproc/imx_dsp_rproc.c index 6e78a01755c7bd..e61a08df113e4d 100644 --- a/drivers/remoteproc/imx_dsp_rproc.c +++ b/drivers/remoteproc/imx_dsp_rproc.c @@ -1026,9 +1026,11 @@ static int imx_dsp_rproc_load(struct rproc *rproc, const struct firmware *fw) * Clear buffers after pm rumtime for internal ocram is not * accessible if power and clock are not enabled. */ - list_for_each_entry(carveout, &rproc->carveouts, node) { - if (carveout->va) - memset(carveout->va, 0, carveout->len); + if (rproc->state == RPROC_OFFLINE) { + list_for_each_entry(carveout, &rproc->carveouts, node) { + if (carveout->va) + memset(carveout->va, 0, carveout->len); + } } ret = imx_dsp_rproc_elf_load_segments(rproc, fw); From 2cafad617431f02393383c9faff17747da7d6f01 Mon Sep 17 00:00:00 2001 From: Sudeep Holla Date: Thu, 16 Oct 2025 20:08:15 +0100 Subject: [PATCH 3649/3902] Revert "mailbox/pcc: support mailbox management of the shared buffer" [ Upstream commit f82c3e62b6b8c31d8c56415bf38658f306fda4cb ] This reverts commit 5378bdf6a611a32500fccf13d14156f219bb0c85. Commit 5378bdf6a611 ("mailbox/pcc: support mailbox management of the shared buffer") attempted to introduce generic helpers for managing the PCC shared memory, but it largely duplicates functionality already provided by the mailbox core and leaves gaps: 1. TX preparation: The mailbox framework already supports this via ->tx_prepare callback for mailbox clients. The patch adds pcc_write_to_buffer() and expects clients to toggle pchan->chan.manage_writes, but no drivers set manage_writes, so pcc_write_to_buffer() has no users. 2. RX handling: Data reception is already delivered through mbox_chan_received_data() and client ->rx_callback. The patch adds an optional pchan->chan.rx_alloc, which again has no users and duplicates the existing path. 3. Completion handling: While adding last_tx_done is directionally useful, the implementation only covers Type 3/4 and fails to handle the absence of a command_complete register, so it is incomplete for other types. Given the duplication and incomplete coverage, revert this change. Any new requirements should be addressed in focused follow-ups rather than bundling multiple behavioral changes together. Fixes: 5378bdf6a611 ("mailbox/pcc: support mailbox management of the shared buffer") Signed-off-by: Sudeep Holla Signed-off-by: Jassi Brar Signed-off-by: Sasha Levin --- drivers/mailbox/pcc.c | 102 ++---------------------------------------- include/acpi/pcc.h | 29 ------------ 2 files changed, 4 insertions(+), 127 deletions(-) diff --git a/drivers/mailbox/pcc.c b/drivers/mailbox/pcc.c index ff292b9e0be9ee..0e0a66359d4c38 100644 --- a/drivers/mailbox/pcc.c +++ b/drivers/mailbox/pcc.c @@ -305,22 +305,6 @@ static void pcc_chan_acknowledge(struct pcc_chan_info *pchan) pcc_chan_reg_read_modify_write(&pchan->db); } -static void *write_response(struct pcc_chan_info *pchan) -{ - struct pcc_header pcc_header; - void *buffer; - int data_len; - - memcpy_fromio(&pcc_header, pchan->chan.shmem, - sizeof(pcc_header)); - data_len = pcc_header.length - sizeof(u32) + sizeof(struct pcc_header); - - buffer = pchan->chan.rx_alloc(pchan->chan.mchan->cl, data_len); - if (buffer != NULL) - memcpy_fromio(buffer, pchan->chan.shmem, data_len); - return buffer; -} - /** * pcc_mbox_irq - PCC mailbox interrupt handler * @irq: interrupt number @@ -332,8 +316,6 @@ static irqreturn_t pcc_mbox_irq(int irq, void *p) { struct pcc_chan_info *pchan; struct mbox_chan *chan = p; - struct pcc_header *pcc_header = chan->active_req; - void *handle = NULL; pchan = chan->con_priv; @@ -357,17 +339,7 @@ static irqreturn_t pcc_mbox_irq(int irq, void *p) * required to avoid any possible race in updatation of this flag. */ pchan->chan_in_use = false; - - if (pchan->chan.rx_alloc) - handle = write_response(pchan); - - if (chan->active_req) { - pcc_header = chan->active_req; - if (pcc_header->flags & PCC_CMD_COMPLETION_NOTIFY) - mbox_chan_txdone(chan, 0); - } - - mbox_chan_received_data(chan, handle); + mbox_chan_received_data(chan, NULL); pcc_chan_acknowledge(pchan); @@ -411,24 +383,9 @@ pcc_mbox_request_channel(struct mbox_client *cl, int subspace_id) pcc_mchan = &pchan->chan; pcc_mchan->shmem = acpi_os_ioremap(pcc_mchan->shmem_base_addr, pcc_mchan->shmem_size); - if (!pcc_mchan->shmem) - goto err; - - pcc_mchan->manage_writes = false; - - /* This indicates that the channel is ready to accept messages. - * This needs to happen after the channel has registered - * its callback. There is no access point to do that in - * the mailbox API. That implies that the mailbox client must - * have set the allocate callback function prior to - * sending any messages. - */ - if (pchan->type == ACPI_PCCT_TYPE_EXT_PCC_SLAVE_SUBSPACE) - pcc_chan_reg_read_modify_write(&pchan->cmd_update); - - return pcc_mchan; + if (pcc_mchan->shmem) + return pcc_mchan; -err: mbox_free_channel(chan); return ERR_PTR(-ENXIO); } @@ -459,38 +416,8 @@ void pcc_mbox_free_channel(struct pcc_mbox_chan *pchan) } EXPORT_SYMBOL_GPL(pcc_mbox_free_channel); -static int pcc_write_to_buffer(struct mbox_chan *chan, void *data) -{ - struct pcc_chan_info *pchan = chan->con_priv; - struct pcc_mbox_chan *pcc_mbox_chan = &pchan->chan; - struct pcc_header *pcc_header = data; - - if (!pchan->chan.manage_writes) - return 0; - - /* The PCC header length includes the command field - * but not the other values from the header. - */ - int len = pcc_header->length - sizeof(u32) + sizeof(struct pcc_header); - u64 val; - - pcc_chan_reg_read(&pchan->cmd_complete, &val); - if (!val) { - pr_info("%s pchan->cmd_complete not set", __func__); - return -1; - } - memcpy_toio(pcc_mbox_chan->shmem, data, len); - return 0; -} - - /** - * pcc_send_data - Called from Mailbox Controller code. If - * pchan->chan.rx_alloc is set, then the command complete - * flag is checked and the data is written to the shared - * buffer io memory. - * - * If pchan->chan.rx_alloc is not set, then it is used + * pcc_send_data - Called from Mailbox Controller code. Used * here only to ring the channel doorbell. The PCC client * specific read/write is done in the client driver in * order to maintain atomicity over PCC channel once @@ -506,37 +433,17 @@ static int pcc_send_data(struct mbox_chan *chan, void *data) int ret; struct pcc_chan_info *pchan = chan->con_priv; - ret = pcc_write_to_buffer(chan, data); - if (ret) - return ret; - ret = pcc_chan_reg_read_modify_write(&pchan->cmd_update); if (ret) return ret; ret = pcc_chan_reg_read_modify_write(&pchan->db); - if (!ret && pchan->plat_irq > 0) pchan->chan_in_use = true; return ret; } - -static bool pcc_last_tx_done(struct mbox_chan *chan) -{ - struct pcc_chan_info *pchan = chan->con_priv; - u64 val; - - pcc_chan_reg_read(&pchan->cmd_complete, &val); - if (!val) - return false; - else - return true; -} - - - /** * pcc_startup - Called from Mailbox Controller code. Used here * to request the interrupt. @@ -582,7 +489,6 @@ static const struct mbox_chan_ops pcc_chan_ops = { .send_data = pcc_send_data, .startup = pcc_startup, .shutdown = pcc_shutdown, - .last_tx_done = pcc_last_tx_done, }; /** diff --git a/include/acpi/pcc.h b/include/acpi/pcc.h index 9af3b502f83952..840bfc95bae332 100644 --- a/include/acpi/pcc.h +++ b/include/acpi/pcc.h @@ -17,35 +17,6 @@ struct pcc_mbox_chan { u32 latency; u32 max_access_rate; u16 min_turnaround_time; - - /* Set to true to indicate that the mailbox should manage - * writing the dat to the shared buffer. This differs from - * the case where the drivesr are writing to the buffer and - * using send_data only to ring the doorbell. If this flag - * is set, then the void * data parameter of send_data must - * point to a kernel-memory buffer formatted in accordance with - * the PCC specification. - * - * The active buffer management will include reading the - * notify_on_completion flag, and will then - * call mbox_chan_txdone when the acknowledgment interrupt is - * received. - */ - bool manage_writes; - - /* Optional callback that allows the driver - * to allocate the memory used for receiving - * messages. The return value is the location - * inside the buffer where the mailbox should write the data. - */ - void *(*rx_alloc)(struct mbox_client *cl, int size); -}; - -struct pcc_header { - u32 signature; - u32 flags; - u32 length; - u32 command; }; /* Generic Communications Channel Shared Memory Region */ From e76a2f1d40ea91b656ff069a8faa303daa6c2add Mon Sep 17 00:00:00 2001 From: Felix Gu Date: Sat, 31 Jan 2026 20:48:33 +0800 Subject: [PATCH 3650/3902] fbdev: of_display_timing: Fix device node reference leak in of_get_display_timings() [ Upstream commit c39ee2d264f98efa14aa46c9942114cb03c7baa6 ] Use for_each_child_of_node_scoped instead of for_each_child_of_node to ensure automatic of_node_put on early exit paths, preventing device node reference leak. Fixes: cc3f414cf2e4 ("video: add of helper for display timings/videomode") Signed-off-by: Felix Gu Signed-off-by: Helge Deller Signed-off-by: Sasha Levin --- drivers/video/of_display_timing.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/drivers/video/of_display_timing.c b/drivers/video/of_display_timing.c index bebd371c6b93ea..a4cd446ac5a59f 100644 --- a/drivers/video/of_display_timing.c +++ b/drivers/video/of_display_timing.c @@ -195,7 +195,7 @@ struct display_timings *of_get_display_timings(const struct device_node *np) disp->num_timings = 0; disp->native_mode = 0; - for_each_child_of_node(timings_np, entry) { + for_each_child_of_node_scoped(timings_np, child) { struct display_timing *dt; int r; @@ -206,7 +206,7 @@ struct display_timings *of_get_display_timings(const struct device_node *np) goto timingfail; } - r = of_parse_display_timing(entry, dt); + r = of_parse_display_timing(child, dt); if (r) { /* * to not encourage wrong devicetrees, fail in case of @@ -218,7 +218,7 @@ struct display_timings *of_get_display_timings(const struct device_node *np) goto timingfail; } - if (native_mode == entry) + if (native_mode == child) disp->native_mode = disp->num_timings; disp->timings[disp->num_timings] = dt; From 3d4202ee6494c0d576cdc104b12e0834ca8136a8 Mon Sep 17 00:00:00 2001 From: Felix Gu Date: Tue, 3 Feb 2026 20:14:58 +0800 Subject: [PATCH 3651/3902] fbdev: au1200fb: Fix a memory leak in au1200fb_drv_probe() [ Upstream commit ce4e25198a6aaaaf36248edf8daf3d744ec8e309 ] In au1200fb_drv_probe(), when platform_get_irq fails(), it directly returns from the function with an error code, which causes a memory leak. Replace it with a goto label to ensure proper cleanup. Fixes: 4e88761f5f8c ("fbdev: au1200fb: Fix missing IRQ check in au1200fb_drv_probe") Signed-off-by: Felix Gu Signed-off-by: Helge Deller Signed-off-by: Sasha Levin --- drivers/video/fbdev/au1200fb.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/drivers/video/fbdev/au1200fb.c b/drivers/video/fbdev/au1200fb.c index ed770222660b51..685e629e7e1642 100644 --- a/drivers/video/fbdev/au1200fb.c +++ b/drivers/video/fbdev/au1200fb.c @@ -1724,8 +1724,10 @@ static int au1200fb_drv_probe(struct platform_device *dev) /* Now hook interrupt too */ irq = platform_get_irq(dev, 0); - if (irq < 0) - return irq; + if (irq < 0) { + ret = irq; + goto failed; + } ret = request_irq(irq, au1200fb_handle_irq, IRQF_SHARED, "lcd", (void *)dev); From 19eb98a970c908d9b45a3558bcebef546d3ec24a Mon Sep 17 00:00:00 2001 From: Yao Zi Date: Thu, 20 Nov 2025 13:14:11 +0000 Subject: [PATCH 3652/3902] clk: thead: th1520-ap: Poll for PLL lock and wait for stability [ Upstream commit 892abfbed71e8e0fc5d6ccee1e975904805c6327 ] All PLLs found on TH1520 SoC take 21250ns at maximum to lock, and their lock status is indicated by register PLL_STS (offset 0x80 inside AP clock controller). We should poll the register to ensure the PLL actually locks after enabling it. Furthermore, a 30us delay is added after enabling the PLL, after which the PLL could be considered stable as stated by vendor clock code. Fixes: 56a48c1833aa ("clk: thead: add support for enabling/disabling PLLs") Reviewed-by: Drew Fustini Signed-off-by: Yao Zi Signed-off-by: Drew Fustini Signed-off-by: Sasha Levin --- drivers/clk/thead/clk-th1520-ap.c | 34 +++++++++++++++++++++++++++++-- 1 file changed, 32 insertions(+), 2 deletions(-) diff --git a/drivers/clk/thead/clk-th1520-ap.c b/drivers/clk/thead/clk-th1520-ap.c index 71ad03a998e8e1..d870f0c665f8a4 100644 --- a/drivers/clk/thead/clk-th1520-ap.c +++ b/drivers/clk/thead/clk-th1520-ap.c @@ -8,11 +8,14 @@ #include #include #include +#include #include #include #include #include +#define TH1520_PLL_STS 0x80 + #define TH1520_PLL_POSTDIV2 GENMASK(26, 24) #define TH1520_PLL_POSTDIV1 GENMASK(22, 20) #define TH1520_PLL_FBDIV GENMASK(19, 8) @@ -23,6 +26,13 @@ #define TH1520_PLL_FRAC GENMASK(23, 0) #define TH1520_PLL_FRAC_BITS 24 +/* + * All PLLs in TH1520 take 21250ns at maximum to lock, let's take its double + * for safety. + */ +#define TH1520_PLL_LOCK_TIMEOUT_US 44 +#define TH1520_PLL_STABLE_DELAY_US 30 + struct ccu_internal { u8 shift; u8 width; @@ -64,6 +74,7 @@ struct ccu_div { struct ccu_pll { struct ccu_common common; + u32 lock_sts_mask; }; #define TH_CCU_ARG(_shift, _width) \ @@ -299,9 +310,21 @@ static void ccu_pll_disable(struct clk_hw *hw) static int ccu_pll_enable(struct clk_hw *hw) { struct ccu_pll *pll = hw_to_ccu_pll(hw); + u32 reg; + int ret; - return regmap_clear_bits(pll->common.map, pll->common.cfg1, - TH1520_PLL_VCO_RST); + regmap_clear_bits(pll->common.map, pll->common.cfg1, + TH1520_PLL_VCO_RST); + + ret = regmap_read_poll_timeout_atomic(pll->common.map, TH1520_PLL_STS, + reg, reg & pll->lock_sts_mask, + 5, TH1520_PLL_LOCK_TIMEOUT_US); + if (ret) + return ret; + + udelay(TH1520_PLL_STABLE_DELAY_US); + + return 0; } static int ccu_pll_is_enabled(struct clk_hw *hw) @@ -389,6 +412,7 @@ static struct ccu_pll cpu_pll0_clk = { &clk_pll_ops, CLK_IS_CRITICAL), }, + .lock_sts_mask = BIT(1), }; static struct ccu_pll cpu_pll1_clk = { @@ -401,6 +425,7 @@ static struct ccu_pll cpu_pll1_clk = { &clk_pll_ops, CLK_IS_CRITICAL), }, + .lock_sts_mask = BIT(4), }; static struct ccu_pll gmac_pll_clk = { @@ -413,6 +438,7 @@ static struct ccu_pll gmac_pll_clk = { &clk_pll_ops, CLK_IS_CRITICAL), }, + .lock_sts_mask = BIT(3), }; static const struct clk_hw *gmac_pll_clk_parent[] = { @@ -433,6 +459,7 @@ static struct ccu_pll video_pll_clk = { &clk_pll_ops, CLK_IS_CRITICAL), }, + .lock_sts_mask = BIT(7), }; static const struct clk_hw *video_pll_clk_parent[] = { @@ -453,6 +480,7 @@ static struct ccu_pll dpu0_pll_clk = { &clk_pll_ops, 0), }, + .lock_sts_mask = BIT(8), }; static const struct clk_hw *dpu0_pll_clk_parent[] = { @@ -469,6 +497,7 @@ static struct ccu_pll dpu1_pll_clk = { &clk_pll_ops, 0), }, + .lock_sts_mask = BIT(9), }; static const struct clk_hw *dpu1_pll_clk_parent[] = { @@ -485,6 +514,7 @@ static struct ccu_pll tee_pll_clk = { &clk_pll_ops, CLK_IS_CRITICAL), }, + .lock_sts_mask = BIT(10), }; static const struct clk_parent_data c910_i0_parents[] = { From e9ed7237b8249227943c5d15240b36bdcad26602 Mon Sep 17 00:00:00 2001 From: Inochi Amaoto Date: Fri, 19 Dec 2025 09:28:17 +0800 Subject: [PATCH 3653/3902] clk: spacemit: Respect Kconfig setting when building modules [ Upstream commit 5ec8cbbc54c82c0bdae4dbf0e5aecf9817bde2b9 ] Currently, the SPACEMIT_CCU entry is only a switch for enabling entry SPACEMIT_K1_CCU. It does not guide the build for common clock codes even if it is a tristate entry. This makes this entry useless. Change the Makefile to add a separate build for common clock logic, so the SPACEMIT_CCU entry takes effect, also add necessary MODULE_LICENSE()/MODULE_DESCRIPTION()/EXPORT_SYMBOL() for the module build. Fixes: 1b72c59db0ad ("clk: spacemit: Add clock support for SpacemiT K1 SoC") Signed-off-by: Inochi Amaoto Reviewed-by: Yixun Lan Link: https://lore.kernel.org/r/20251219012819.440972-2-inochiama@gmail.com Signed-off-by: Yixun Lan Signed-off-by: Sasha Levin --- drivers/clk/spacemit/Makefile | 9 +++++++-- drivers/clk/spacemit/ccu-k1.c | 1 + drivers/clk/spacemit/ccu_common.c | 6 ++++++ drivers/clk/spacemit/ccu_ddn.c | 1 + drivers/clk/spacemit/ccu_mix.c | 9 +++++++++ drivers/clk/spacemit/ccu_pll.c | 1 + 6 files changed, 25 insertions(+), 2 deletions(-) create mode 100644 drivers/clk/spacemit/ccu_common.c diff --git a/drivers/clk/spacemit/Makefile b/drivers/clk/spacemit/Makefile index 5ec6da61db98e0..ad2bf315109b80 100644 --- a/drivers/clk/spacemit/Makefile +++ b/drivers/clk/spacemit/Makefile @@ -1,5 +1,10 @@ # SPDX-License-Identifier: GPL-2.0 -obj-$(CONFIG_SPACEMIT_K1_CCU) = spacemit-ccu-k1.o -spacemit-ccu-k1-y = ccu_pll.o ccu_mix.o ccu_ddn.o +obj-$(CONFIG_SPACEMIT_CCU) += spacemit-ccu.o +spacemit-ccu-y += ccu_common.o +spacemit-ccu-y += ccu_pll.o +spacemit-ccu-y += ccu_mix.o +spacemit-ccu-y += ccu_ddn.o + +obj-$(CONFIG_SPACEMIT_K1_CCU) += spacemit-ccu-k1.o spacemit-ccu-k1-y += ccu-k1.o diff --git a/drivers/clk/spacemit/ccu-k1.c b/drivers/clk/spacemit/ccu-k1.c index 4761bc1e3b6e60..01d9485b615d32 100644 --- a/drivers/clk/spacemit/ccu-k1.c +++ b/drivers/clk/spacemit/ccu-k1.c @@ -1204,6 +1204,7 @@ static struct platform_driver k1_ccu_driver = { }; module_platform_driver(k1_ccu_driver); +MODULE_IMPORT_NS("CLK_SPACEMIT"); MODULE_DESCRIPTION("SpacemiT K1 CCU driver"); MODULE_AUTHOR("Haylen Chu "); MODULE_LICENSE("GPL"); diff --git a/drivers/clk/spacemit/ccu_common.c b/drivers/clk/spacemit/ccu_common.c new file mode 100644 index 00000000000000..4412c4104dabb3 --- /dev/null +++ b/drivers/clk/spacemit/ccu_common.c @@ -0,0 +1,6 @@ +// SPDX-License-Identifier: GPL-2.0-only + +#include + +MODULE_DESCRIPTION("SpacemiT CCU common clock driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/clk/spacemit/ccu_ddn.c b/drivers/clk/spacemit/ccu_ddn.c index 5b16e273bee5b1..b5540e0781ffa3 100644 --- a/drivers/clk/spacemit/ccu_ddn.c +++ b/drivers/clk/spacemit/ccu_ddn.c @@ -84,3 +84,4 @@ const struct clk_ops spacemit_ccu_ddn_ops = { .determine_rate = ccu_ddn_determine_rate, .set_rate = ccu_ddn_set_rate, }; +EXPORT_SYMBOL_NS_GPL(spacemit_ccu_ddn_ops, "CLK_SPACEMIT"); diff --git a/drivers/clk/spacemit/ccu_mix.c b/drivers/clk/spacemit/ccu_mix.c index 7b799087537231..67f8b12b4f5b7d 100644 --- a/drivers/clk/spacemit/ccu_mix.c +++ b/drivers/clk/spacemit/ccu_mix.c @@ -198,24 +198,28 @@ const struct clk_ops spacemit_ccu_gate_ops = { .enable = ccu_gate_enable, .is_enabled = ccu_gate_is_enabled, }; +EXPORT_SYMBOL_NS_GPL(spacemit_ccu_gate_ops, "CLK_SPACEMIT"); const struct clk_ops spacemit_ccu_factor_ops = { .determine_rate = ccu_factor_determine_rate, .recalc_rate = ccu_factor_recalc_rate, .set_rate = ccu_factor_set_rate, }; +EXPORT_SYMBOL_NS_GPL(spacemit_ccu_factor_ops, "CLK_SPACEMIT"); const struct clk_ops spacemit_ccu_mux_ops = { .determine_rate = ccu_mix_determine_rate, .get_parent = ccu_mux_get_parent, .set_parent = ccu_mux_set_parent, }; +EXPORT_SYMBOL_NS_GPL(spacemit_ccu_mux_ops, "CLK_SPACEMIT"); const struct clk_ops spacemit_ccu_div_ops = { .determine_rate = ccu_mix_determine_rate, .recalc_rate = ccu_div_recalc_rate, .set_rate = ccu_mix_set_rate, }; +EXPORT_SYMBOL_NS_GPL(spacemit_ccu_div_ops, "CLK_SPACEMIT"); const struct clk_ops spacemit_ccu_factor_gate_ops = { .disable = ccu_gate_disable, @@ -226,6 +230,7 @@ const struct clk_ops spacemit_ccu_factor_gate_ops = { .recalc_rate = ccu_factor_recalc_rate, .set_rate = ccu_factor_set_rate, }; +EXPORT_SYMBOL_NS_GPL(spacemit_ccu_factor_gate_ops, "CLK_SPACEMIT"); const struct clk_ops spacemit_ccu_mux_gate_ops = { .disable = ccu_gate_disable, @@ -236,6 +241,7 @@ const struct clk_ops spacemit_ccu_mux_gate_ops = { .get_parent = ccu_mux_get_parent, .set_parent = ccu_mux_set_parent, }; +EXPORT_SYMBOL_NS_GPL(spacemit_ccu_mux_gate_ops, "CLK_SPACEMIT"); const struct clk_ops spacemit_ccu_div_gate_ops = { .disable = ccu_gate_disable, @@ -246,6 +252,7 @@ const struct clk_ops spacemit_ccu_div_gate_ops = { .recalc_rate = ccu_div_recalc_rate, .set_rate = ccu_mix_set_rate, }; +EXPORT_SYMBOL_NS_GPL(spacemit_ccu_div_gate_ops, "CLK_SPACEMIT"); const struct clk_ops spacemit_ccu_mux_div_gate_ops = { .disable = ccu_gate_disable, @@ -259,6 +266,7 @@ const struct clk_ops spacemit_ccu_mux_div_gate_ops = { .recalc_rate = ccu_div_recalc_rate, .set_rate = ccu_mix_set_rate, }; +EXPORT_SYMBOL_NS_GPL(spacemit_ccu_mux_div_gate_ops, "CLK_SPACEMIT"); const struct clk_ops spacemit_ccu_mux_div_ops = { .get_parent = ccu_mux_get_parent, @@ -268,3 +276,4 @@ const struct clk_ops spacemit_ccu_mux_div_ops = { .recalc_rate = ccu_div_recalc_rate, .set_rate = ccu_mix_set_rate, }; +EXPORT_SYMBOL_NS_GPL(spacemit_ccu_mux_div_ops, "CLK_SPACEMIT"); diff --git a/drivers/clk/spacemit/ccu_pll.c b/drivers/clk/spacemit/ccu_pll.c index d92f0dae65a490..76d0244873d87c 100644 --- a/drivers/clk/spacemit/ccu_pll.c +++ b/drivers/clk/spacemit/ccu_pll.c @@ -157,3 +157,4 @@ const struct clk_ops spacemit_ccu_pll_ops = { .determine_rate = ccu_pll_determine_rate, .is_enabled = ccu_pll_is_enabled, }; +EXPORT_SYMBOL_NS_GPL(spacemit_ccu_pll_ops, "CLK_SPACEMIT"); From b714c1d0bb437e46b1bb82fea5d0a138bbfe6f98 Mon Sep 17 00:00:00 2001 From: Vladimir Zapolskiy Date: Mon, 24 Nov 2025 23:20:11 +0200 Subject: [PATCH 3654/3902] clk: qcom: gcc-sm8550: Use floor ops for SDCC RCGs [ Upstream commit 1c06e3956054fb5a0930f07b02726b1774b6c700 ] In line with commit a27ac3806b0a ("clk: qcom: gcc-sm8450: Use floor ops for SDCC RCGs") done to fix issues with overclocked SD cards on SM8450 powered boards set floor clock operations for SDCC RCGs on SM8550. This change fixes initialization of some SD cards, where the problem is manifested by the SDHC driver: mmc0: Card appears overclocked; req 50000000 Hz, actual 100000000 Hz mmc0: error -110 whilst initialising SD card Fixes: 955f2ea3b9e9 ("clk: qcom: Add GCC driver for SM8550") Signed-off-by: Vladimir Zapolskiy Reviewed-by: Neil Armstrong Reviewed-by: Taniya Das Link: https://lore.kernel.org/r/20251124212012.3660189-2-vladimir.zapolskiy@linaro.org Signed-off-by: Bjorn Andersson Signed-off-by: Sasha Levin --- drivers/clk/qcom/gcc-sm8550.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/clk/qcom/gcc-sm8550.c b/drivers/clk/qcom/gcc-sm8550.c index 862a9bf73bcb5d..36a5b7de5b55d2 100644 --- a/drivers/clk/qcom/gcc-sm8550.c +++ b/drivers/clk/qcom/gcc-sm8550.c @@ -1025,7 +1025,7 @@ static struct clk_rcg2 gcc_sdcc2_apps_clk_src = { .parent_data = gcc_parent_data_9, .num_parents = ARRAY_SIZE(gcc_parent_data_9), .flags = CLK_SET_RATE_PARENT, - .ops = &clk_rcg2_shared_ops, + .ops = &clk_rcg2_shared_floor_ops, }, }; @@ -1048,7 +1048,7 @@ static struct clk_rcg2 gcc_sdcc4_apps_clk_src = { .parent_data = gcc_parent_data_0, .num_parents = ARRAY_SIZE(gcc_parent_data_0), .flags = CLK_SET_RATE_PARENT, - .ops = &clk_rcg2_shared_ops, + .ops = &clk_rcg2_shared_floor_ops, }, }; From aa14e7603835397665bf7fb38a02da1fa8dc5fa6 Mon Sep 17 00:00:00 2001 From: Vladimir Zapolskiy Date: Mon, 24 Nov 2025 23:20:12 +0200 Subject: [PATCH 3655/3902] clk: qcom: gcc-sm8650: Use floor ops for SDCC RCGs [ Upstream commit 8c4415fd17cd5979c31a4bf303acc702e9726033 ] In line with commit a27ac3806b0a ("clk: qcom: gcc-sm8450: Use floor ops for SDCC RCGs") done to fix issues with overclocked SD cards on SM8450 powered boards set floor clock operations for SDCC RCGs on SM8650. This change fixes initialization of some SD cards, where the problem is manifested by the SDHC driver: mmc0: Card appears overclocked; req 50000000 Hz, actual 100000000 Hz mmc0: error -110 whilst initialising SD card Fixes: c58225b7e3d7 ("clk: qcom: add the SM8650 Global Clock Controller driver, part 1") Signed-off-by: Vladimir Zapolskiy Reviewed-by: Neil Armstrong Reviewed-by: Taniya Das Link: https://lore.kernel.org/r/20251124212012.3660189-3-vladimir.zapolskiy@linaro.org Signed-off-by: Bjorn Andersson Signed-off-by: Sasha Levin --- drivers/clk/qcom/gcc-sm8650.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/clk/qcom/gcc-sm8650.c b/drivers/clk/qcom/gcc-sm8650.c index 24f98062b9dd50..2dd6444ce0365f 100644 --- a/drivers/clk/qcom/gcc-sm8650.c +++ b/drivers/clk/qcom/gcc-sm8650.c @@ -1257,7 +1257,7 @@ static struct clk_rcg2 gcc_sdcc2_apps_clk_src = { .parent_data = gcc_parent_data_11, .num_parents = ARRAY_SIZE(gcc_parent_data_11), .flags = CLK_SET_RATE_PARENT, - .ops = &clk_rcg2_shared_ops, + .ops = &clk_rcg2_shared_floor_ops, }, }; @@ -1279,7 +1279,7 @@ static struct clk_rcg2 gcc_sdcc4_apps_clk_src = { .parent_data = gcc_parent_data_0, .num_parents = ARRAY_SIZE(gcc_parent_data_0), .flags = CLK_SET_RATE_PARENT, - .ops = &clk_rcg2_shared_ops, + .ops = &clk_rcg2_shared_floor_ops, }, }; From f7b0686119fd25868dc954da4c54b15626199d6a Mon Sep 17 00:00:00 2001 From: Taniya Das Date: Mon, 5 Jan 2026 16:09:50 +0530 Subject: [PATCH 3656/3902] clk: qcom: rcg2: compute 2d using duty fraction directly MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [ Upstream commit d6205a1878dd4cc9664c4b4829b68a29c0426efc ] The duty-cycle calculation in clk_rcg2_set_duty_cycle() currently derives an intermediate percentage `duty_per = (num * 100) / den` and then computes: d = DIV_ROUND_CLOSEST(n * duty_per * 2, 100); This introduces integer truncation at the percentage step (division by `den`) and a redundant scaling by 100, which can reduce precision for large `den` and skew the final rounding. Compute `2d` directly from the duty fraction to preserve precision and avoid the unnecessary scaling: d = DIV_ROUND_CLOSEST(n * duty->num * 2, duty->den); This keeps the intended formula `d ≈ n * 2 * (num/den)` while performing a single, final rounded division, improving accuracy especially for small duty cycles or large denominators. It also removes the unused `duty_per` variable, simplifying the code. There is no functional changes beyond improved numerical accuracy. Fixes: 7f891faf596ed ("clk: qcom: clk-rcg2: Add support for duty-cycle for RCG") Signed-off-by: Taniya Das Link: https://lore.kernel.org/r/20260105-duty_cycle_precision-v2-1-d1d466a6330a@oss.qualcomm.com Signed-off-by: Bjorn Andersson Signed-off-by: Sasha Levin --- drivers/clk/qcom/clk-rcg2.c | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/drivers/clk/qcom/clk-rcg2.c b/drivers/clk/qcom/clk-rcg2.c index e18cb8807d7353..2838d4cb2d58ea 100644 --- a/drivers/clk/qcom/clk-rcg2.c +++ b/drivers/clk/qcom/clk-rcg2.c @@ -755,7 +755,7 @@ static int clk_rcg2_get_duty_cycle(struct clk_hw *hw, struct clk_duty *duty) static int clk_rcg2_set_duty_cycle(struct clk_hw *hw, struct clk_duty *duty) { struct clk_rcg2 *rcg = to_clk_rcg2(hw); - u32 notn_m, n, m, d, not2d, mask, duty_per, cfg; + u32 notn_m, n, m, d, not2d, mask, cfg; int ret; /* Duty-cycle cannot be modified for non-MND RCGs */ @@ -774,10 +774,8 @@ static int clk_rcg2_set_duty_cycle(struct clk_hw *hw, struct clk_duty *duty) n = (~(notn_m) + m) & mask; - duty_per = (duty->num * 100) / duty->den; - /* Calculate 2d value */ - d = DIV_ROUND_CLOSEST(n * duty_per * 2, 100); + d = DIV_ROUND_CLOSEST(n * duty->num * 2, duty->den); /* * Check bit widths of 2d. If D is too big reduce duty cycle. From e82d721b05e070b5044a1c307088829a802a2ab3 Mon Sep 17 00:00:00 2001 From: Martin Blumenstingl Date: Mon, 5 Jan 2026 21:47:08 +0100 Subject: [PATCH 3657/3902] clk: meson: gxbb: Limit the HDMI PLL OD to /4 on GXL/GXM SoCs [ Upstream commit 5b1a43950fd3162af0ce52b13c14a2d29b179d4f ] GXBB has the HDMI PLL OD in the HHI_HDMI_PLL_CNTL2 register while for GXL/GXM the OD has moved to HHI_HDMI_PLL_CNTL3. At first glance the rest of the OD setup seems identical. However, looking at the downstream kernel sources as well as testing shows that GXL only supports three OD values: - register value 0 means: divide by 1 - register value 1 means: divide by 2 - register value 2 means: divide by 4 Using register value 3 (which on GXBB means: divide by 8) still divides by 4 as verified using meson-clk-measure. Downstream sources are also only using OD register values 0, 1 and 2 for GXL (while for GXBB the downstream kernel sources are also using value 3). Add clk_div_table and have it replace the CLK_DIVIDER_POWER_OF_TWO flag to make the kernel's view of this register match with how the hardware actually works. Fixes: 69d92293274b ("clk: meson: add the gxl hdmi pll") Signed-off-by: Martin Blumenstingl Link: https://lore.kernel.org/r/20260105204710.447779-2-martin.blumenstingl@googlemail.com Signed-off-by: Jerome Brunet Signed-off-by: Sasha Levin --- drivers/clk/meson/gxbb.c | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/drivers/clk/meson/gxbb.c b/drivers/clk/meson/gxbb.c index 5a229c4ffae105..ec9a3414875ac3 100644 --- a/drivers/clk/meson/gxbb.c +++ b/drivers/clk/meson/gxbb.c @@ -349,12 +349,23 @@ static struct clk_regmap gxbb_hdmi_pll = { }, }; +/* + * GXL hdmi OD dividers are POWER_OF_TWO dividers but limited to /4. + * A divider value of 3 should map to /8 but instead map /4 so ignore it. + */ +static const struct clk_div_table gxl_hdmi_pll_od_div_table[] = { + { .val = 0, .div = 1 }, + { .val = 1, .div = 2 }, + { .val = 2, .div = 4 }, + { /* sentinel */ } +}; + static struct clk_regmap gxl_hdmi_pll_od = { .data = &(struct clk_regmap_div_data){ .offset = HHI_HDMI_PLL_CNTL + 8, .shift = 21, .width = 2, - .flags = CLK_DIVIDER_POWER_OF_TWO, + .table = gxl_hdmi_pll_od_div_table, }, .hw.init = &(struct clk_init_data){ .name = "hdmi_pll_od", @@ -372,7 +383,7 @@ static struct clk_regmap gxl_hdmi_pll_od2 = { .offset = HHI_HDMI_PLL_CNTL + 8, .shift = 23, .width = 2, - .flags = CLK_DIVIDER_POWER_OF_TWO, + .table = gxl_hdmi_pll_od_div_table, }, .hw.init = &(struct clk_init_data){ .name = "hdmi_pll_od2", @@ -390,7 +401,7 @@ static struct clk_regmap gxl_hdmi_pll = { .offset = HHI_HDMI_PLL_CNTL + 8, .shift = 19, .width = 2, - .flags = CLK_DIVIDER_POWER_OF_TWO, + .table = gxl_hdmi_pll_od_div_table, }, .hw.init = &(struct clk_init_data){ .name = "hdmi_pll", From 1bc262014a14ace3228d990617760e1265b0ac3e Mon Sep 17 00:00:00 2001 From: Martin Blumenstingl Date: Mon, 5 Jan 2026 21:47:09 +0100 Subject: [PATCH 3658/3902] clk: meson: g12a: Limit the HDMI PLL OD to /4 [ Upstream commit 7aa6c24697ef5db1402dd38743914493cd5b356d ] GXBB has the HDMI PLL OD in the HHI_HDMI_PLL_CNTL2 register while for G12A/G12B/SM1 the OD has moved to HHI_HDMI_PLL_CNTL0. At first glance the rest of the OD setup seems identical. However, looking at the downstream kernel sources as well as testing shows that G12A/G12B/SM1 only supports three OD values: - register value 0 means: divide by 1 - register value 1 means: divide by 2 - register value 2 means: divide by 4 Downstream sources are also only using OD register values 0, 1 and 2 for G12A/G12B/SM1 (while for GXBB the downstream kernel sources are also using value 3 which means: divide by 8). Add clk_div_table and have it replace the CLK_DIVIDER_POWER_OF_TWO flag to make the kernel's view of this register match with how the hardware actually works. Fixes: 085a4ea93d54 ("clk: meson: g12a: add peripheral clock controller") Signed-off-by: Martin Blumenstingl Link: https://lore.kernel.org/r/20260105204710.447779-3-martin.blumenstingl@googlemail.com Signed-off-by: Jerome Brunet Signed-off-by: Sasha Levin --- drivers/clk/meson/g12a.c | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/drivers/clk/meson/g12a.c b/drivers/clk/meson/g12a.c index 185b6348251dbd..d0d4c7b6dc8277 100644 --- a/drivers/clk/meson/g12a.c +++ b/drivers/clk/meson/g12a.c @@ -777,12 +777,23 @@ static struct clk_regmap g12a_hdmi_pll_dco = { }, }; +/* + * G12/SM1 hdmi OD dividers are POWER_OF_TWO dividers but limited to /4. + * A divider value of 3 should map to /8 but instead map /4 so ignore it. + */ +static const struct clk_div_table g12a_hdmi_pll_od_div_table[] = { + { .val = 0, .div = 1 }, + { .val = 1, .div = 2 }, + { .val = 2, .div = 4 }, + { /* sentinel */ } +}; + static struct clk_regmap g12a_hdmi_pll_od = { .data = &(struct clk_regmap_div_data){ .offset = HHI_HDMI_PLL_CNTL0, .shift = 16, .width = 2, - .flags = CLK_DIVIDER_POWER_OF_TWO, + .table = g12a_hdmi_pll_od_div_table, }, .hw.init = &(struct clk_init_data){ .name = "hdmi_pll_od", @@ -800,7 +811,7 @@ static struct clk_regmap g12a_hdmi_pll_od2 = { .offset = HHI_HDMI_PLL_CNTL0, .shift = 18, .width = 2, - .flags = CLK_DIVIDER_POWER_OF_TWO, + .table = g12a_hdmi_pll_od_div_table, }, .hw.init = &(struct clk_init_data){ .name = "hdmi_pll_od2", @@ -818,7 +829,7 @@ static struct clk_regmap g12a_hdmi_pll = { .offset = HHI_HDMI_PLL_CNTL0, .shift = 20, .width = 2, - .flags = CLK_DIVIDER_POWER_OF_TWO, + .table = g12a_hdmi_pll_od_div_table, }, .hw.init = &(struct clk_init_data){ .name = "hdmi_pll", From 527508eb5712d997827a016c0929c96013b20da0 Mon Sep 17 00:00:00 2001 From: Jagadeesh Kona Date: Thu, 27 Nov 2025 23:27:36 +0530 Subject: [PATCH 3659/3902] clk: qcom: gcc-sm8450: Update the SDCC RCGs to use shared_floor_ops [ Upstream commit 89428516f99572a9c37ebbb7859595881e7025a0 ] Use shared_floor_ops for the SDCC RCGs so the RCG is safely parked during disable and the new parent configuration is programmed in hardware only when the new parent is enabled, avoiding cases where the RCG configuration fails to update. Fixes: a27ac3806b0a ("clk: qcom: gcc-sm8450: Use floor ops for SDCC RCGs") Reviewed-by: Taniya Das Reviewed-by: Imran Shaik Reviewed-by: Dmitry Baryshkov Reviewed-by: Vladimir Zapolskiy Signed-off-by: Jagadeesh Kona Link: https://lore.kernel.org/r/20251127-sdcc_shared_floor_ops-v2-1-473afc86589c@oss.qualcomm.com Signed-off-by: Bjorn Andersson Signed-off-by: Sasha Levin --- drivers/clk/qcom/gcc-sm8450.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/clk/qcom/gcc-sm8450.c b/drivers/clk/qcom/gcc-sm8450.c index 65d7d52bce0343..b18bb34889ab28 100644 --- a/drivers/clk/qcom/gcc-sm8450.c +++ b/drivers/clk/qcom/gcc-sm8450.c @@ -1034,7 +1034,7 @@ static struct clk_rcg2 gcc_sdcc2_apps_clk_src = { .parent_data = gcc_parent_data_7, .num_parents = ARRAY_SIZE(gcc_parent_data_7), .flags = CLK_SET_RATE_PARENT, - .ops = &clk_rcg2_floor_ops, + .ops = &clk_rcg2_shared_floor_ops, }, }; @@ -1057,7 +1057,7 @@ static struct clk_rcg2 gcc_sdcc4_apps_clk_src = { .parent_data = gcc_parent_data_0, .num_parents = ARRAY_SIZE(gcc_parent_data_0), .flags = CLK_SET_RATE_PARENT, - .ops = &clk_rcg2_floor_ops, + .ops = &clk_rcg2_shared_floor_ops, }, }; From 7873e2a407a03ec068fddc103ca7d7a1ac8f4f98 Mon Sep 17 00:00:00 2001 From: Jagadeesh Kona Date: Thu, 27 Nov 2025 23:27:37 +0530 Subject: [PATCH 3660/3902] clk: qcom: gcc-sm8750: Update the SDCC RCGs to use shared_floor_ops [ Upstream commit a7231d4aa084e485394f9214ec9bcb2d1f65dde9 ] Use shared_floor_ops for the SDCC RCGs so the RCG is safely parked during disable and the new parent configuration is programmed in hardware only when the new parent is enabled, avoiding cases where the RCG configuration fails to update. Fixes: 3267c774f3ff ("clk: qcom: Add support for GCC on SM8750") Reviewed-by: Taniya Das Reviewed-by: Imran Shaik Reviewed-by: Dmitry Baryshkov Reviewed-by: Vladimir Zapolskiy Signed-off-by: Jagadeesh Kona Link: https://lore.kernel.org/r/20251127-sdcc_shared_floor_ops-v2-2-473afc86589c@oss.qualcomm.com Signed-off-by: Bjorn Andersson Signed-off-by: Sasha Levin --- drivers/clk/qcom/gcc-sm8750.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/clk/qcom/gcc-sm8750.c b/drivers/clk/qcom/gcc-sm8750.c index def86b71a3da53..db81569dd4b17d 100644 --- a/drivers/clk/qcom/gcc-sm8750.c +++ b/drivers/clk/qcom/gcc-sm8750.c @@ -1030,7 +1030,7 @@ static struct clk_rcg2 gcc_sdcc2_apps_clk_src = { .parent_data = gcc_parent_data_8, .num_parents = ARRAY_SIZE(gcc_parent_data_8), .flags = CLK_SET_RATE_PARENT, - .ops = &clk_rcg2_floor_ops, + .ops = &clk_rcg2_shared_floor_ops, }, }; @@ -1052,7 +1052,7 @@ static struct clk_rcg2 gcc_sdcc4_apps_clk_src = { .parent_data = gcc_parent_data_0, .num_parents = ARRAY_SIZE(gcc_parent_data_0), .flags = CLK_SET_RATE_PARENT, - .ops = &clk_rcg2_floor_ops, + .ops = &clk_rcg2_shared_floor_ops, }, }; From 37babf26bfb574cce17c216849b386db9322f297 Mon Sep 17 00:00:00 2001 From: Jagadeesh Kona Date: Thu, 27 Nov 2025 23:27:38 +0530 Subject: [PATCH 3661/3902] clk: qcom: gcc-sm4450: Update the SDCC RCGs to use shared_floor_ops [ Upstream commit 458e8a082186335380a9ab83003a385aec9bb254 ] Use shared_floor_ops for the SDCC RCGs so the RCG is safely parked during disable and the new parent configuration is programmed in hardware only when the new parent is enabled, avoiding cases where the RCG configuration fails to update. Fixes: c32c4ef98bac ("clk: qcom: Add GCC driver support for SM4450") Reviewed-by: Taniya Das Reviewed-by: Imran Shaik Reviewed-by: Dmitry Baryshkov Reviewed-by: Vladimir Zapolskiy Signed-off-by: Jagadeesh Kona Link: https://lore.kernel.org/r/20251127-sdcc_shared_floor_ops-v2-3-473afc86589c@oss.qualcomm.com Signed-off-by: Bjorn Andersson Signed-off-by: Sasha Levin --- drivers/clk/qcom/gcc-sm4450.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/drivers/clk/qcom/gcc-sm4450.c b/drivers/clk/qcom/gcc-sm4450.c index e2d9e4691c5b71..023d840e9f4ef0 100644 --- a/drivers/clk/qcom/gcc-sm4450.c +++ b/drivers/clk/qcom/gcc-sm4450.c @@ -769,7 +769,7 @@ static struct clk_rcg2 gcc_sdcc1_apps_clk_src = { .parent_data = gcc_parent_data_4, .num_parents = ARRAY_SIZE(gcc_parent_data_4), .flags = CLK_SET_RATE_PARENT, - .ops = &clk_rcg2_floor_ops, + .ops = &clk_rcg2_shared_floor_ops, }, }; @@ -791,7 +791,7 @@ static struct clk_rcg2 gcc_sdcc1_ice_core_clk_src = { .parent_data = gcc_parent_data_4, .num_parents = ARRAY_SIZE(gcc_parent_data_4), .flags = CLK_SET_RATE_PARENT, - .ops = &clk_rcg2_floor_ops, + .ops = &clk_rcg2_shared_floor_ops, }, }; @@ -815,7 +815,7 @@ static struct clk_rcg2 gcc_sdcc2_apps_clk_src = { .parent_data = gcc_parent_data_6, .num_parents = ARRAY_SIZE(gcc_parent_data_6), .flags = CLK_SET_RATE_PARENT, - .ops = &clk_rcg2_floor_ops, + .ops = &clk_rcg2_shared_floor_ops, }, }; From 76d5437ca4d0be2b06b45dcb04709c38c7e120cb Mon Sep 17 00:00:00 2001 From: Jagadeesh Kona Date: Thu, 27 Nov 2025 23:27:39 +0530 Subject: [PATCH 3662/3902] clk: qcom: gcc-sdx75: Update the SDCC RCGs to use shared_floor_ops [ Upstream commit 4b057462bb61a6571608ba393e6e018c9da9c9c3 ] Use shared_floor_ops for the SDCC RCGs so the RCG is safely parked during disable and the new parent configuration is programmed in hardware only when the new parent is enabled, avoiding cases where the RCG configuration fails to update. Fixes: 108cdc09b2de ("clk: qcom: Add GCC driver support for SDX75") Reviewed-by: Taniya Das Reviewed-by: Imran Shaik Reviewed-by: Dmitry Baryshkov Reviewed-by: Vladimir Zapolskiy Signed-off-by: Jagadeesh Kona Link: https://lore.kernel.org/r/20251127-sdcc_shared_floor_ops-v2-4-473afc86589c@oss.qualcomm.com Signed-off-by: Bjorn Andersson Signed-off-by: Sasha Levin --- drivers/clk/qcom/gcc-sdx75.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/clk/qcom/gcc-sdx75.c b/drivers/clk/qcom/gcc-sdx75.c index 453a6bf8e87863..1f3cd58483a2d6 100644 --- a/drivers/clk/qcom/gcc-sdx75.c +++ b/drivers/clk/qcom/gcc-sdx75.c @@ -1033,7 +1033,7 @@ static struct clk_rcg2 gcc_sdcc1_apps_clk_src = { .name = "gcc_sdcc1_apps_clk_src", .parent_data = gcc_parent_data_17, .num_parents = ARRAY_SIZE(gcc_parent_data_17), - .ops = &clk_rcg2_floor_ops, + .ops = &clk_rcg2_shared_floor_ops, }, }; @@ -1057,7 +1057,7 @@ static struct clk_rcg2 gcc_sdcc2_apps_clk_src = { .name = "gcc_sdcc2_apps_clk_src", .parent_data = gcc_parent_data_18, .num_parents = ARRAY_SIZE(gcc_parent_data_18), - .ops = &clk_rcg2_floor_ops, + .ops = &clk_rcg2_shared_floor_ops, }, }; From 16a21335eff9c80846308d369ccd100d5438413c Mon Sep 17 00:00:00 2001 From: Jagadeesh Kona Date: Thu, 27 Nov 2025 23:27:40 +0530 Subject: [PATCH 3663/3902] clk: qcom: gcc-milos: Update the SDCC RCGs to use shared_floor_ops [ Upstream commit 08da8d7dabb161cea14c6d3ad9b5037aaf6d4b7e ] Use shared_floor_ops for the SDCC RCGs to avoid any overclocking issues in SDCC usecases. Fixes: 88174d5d9422 ("clk: qcom: Add Global Clock controller (GCC) driver for Milos") Reviewed-by: Taniya Das Reviewed-by: Imran Shaik Reviewed-by: Dmitry Baryshkov Reviewed-by: Vladimir Zapolskiy Signed-off-by: Jagadeesh Kona Link: https://lore.kernel.org/r/20251127-sdcc_shared_floor_ops-v2-5-473afc86589c@oss.qualcomm.com Signed-off-by: Bjorn Andersson Signed-off-by: Sasha Levin --- drivers/clk/qcom/gcc-milos.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/drivers/clk/qcom/gcc-milos.c b/drivers/clk/qcom/gcc-milos.c index c9d61b05bafa16..81fa09ec55d7f6 100644 --- a/drivers/clk/qcom/gcc-milos.c +++ b/drivers/clk/qcom/gcc-milos.c @@ -917,7 +917,7 @@ static struct clk_rcg2 gcc_sdcc1_apps_clk_src = { .name = "gcc_sdcc1_apps_clk_src", .parent_data = gcc_parent_data_9, .num_parents = ARRAY_SIZE(gcc_parent_data_9), - .ops = &clk_rcg2_shared_ops, + .ops = &clk_rcg2_shared_floor_ops, }, }; @@ -938,7 +938,7 @@ static struct clk_rcg2 gcc_sdcc1_ice_core_clk_src = { .name = "gcc_sdcc1_ice_core_clk_src", .parent_data = gcc_parent_data_10, .num_parents = ARRAY_SIZE(gcc_parent_data_10), - .ops = &clk_rcg2_shared_ops, + .ops = &clk_rcg2_shared_floor_ops, }, }; @@ -962,7 +962,7 @@ static struct clk_rcg2 gcc_sdcc2_apps_clk_src = { .name = "gcc_sdcc2_apps_clk_src", .parent_data = gcc_parent_data_11, .num_parents = ARRAY_SIZE(gcc_parent_data_11), - .ops = &clk_rcg2_shared_ops, + .ops = &clk_rcg2_shared_floor_ops, }, }; From 357bdc307c5a1463fc1ef218864c3527c9a406d4 Mon Sep 17 00:00:00 2001 From: Jagadeesh Kona Date: Thu, 27 Nov 2025 23:27:41 +0530 Subject: [PATCH 3664/3902] clk: qcom: gcc-x1e80100: Update the SDCC RCGs to use shared_floor_ops [ Upstream commit a468047c4e1c56783204a3ac551b843b4277c8fc ] Use shared_floor_ops for the SDCC RCGs so the RCG is safely parked during disable and the new parent configuration is programmed in hardware only when the new parent is enabled, avoiding cases where the RCG configuration fails to update. Fixes: 161b7c401f4b ("clk: qcom: Add Global Clock controller (GCC) driver for X1E80100") Signed-off-by: Jagadeesh Kona Reviewed-by: Imran Shaik Reviewed-by: Vladimir Zapolskiy Link: https://lore.kernel.org/r/20251127-sdcc_shared_floor_ops-v2-6-473afc86589c@oss.qualcomm.com Signed-off-by: Bjorn Andersson Signed-off-by: Sasha Levin --- drivers/clk/qcom/gcc-x1e80100.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/clk/qcom/gcc-x1e80100.c b/drivers/clk/qcom/gcc-x1e80100.c index 301fc9fc32d8e6..ef8d2df188d3bf 100644 --- a/drivers/clk/qcom/gcc-x1e80100.c +++ b/drivers/clk/qcom/gcc-x1e80100.c @@ -1123,7 +1123,7 @@ static struct clk_rcg2 gcc_sdcc2_apps_clk_src = { .parent_data = gcc_parent_data_9, .num_parents = ARRAY_SIZE(gcc_parent_data_9), .flags = CLK_SET_RATE_PARENT, - .ops = &clk_rcg2_floor_ops, + .ops = &clk_rcg2_shared_floor_ops, }, }; @@ -1145,7 +1145,7 @@ static struct clk_rcg2 gcc_sdcc4_apps_clk_src = { .parent_data = gcc_parent_data_0, .num_parents = ARRAY_SIZE(gcc_parent_data_0), .flags = CLK_SET_RATE_PARENT, - .ops = &clk_rcg2_floor_ops, + .ops = &clk_rcg2_shared_floor_ops, }, }; From 8d7df9126dad4d1a952febb1bc3ca1fd19962e55 Mon Sep 17 00:00:00 2001 From: Jagadeesh Kona Date: Thu, 27 Nov 2025 23:27:42 +0530 Subject: [PATCH 3665/3902] clk: qcom: gcc-qdu1000: Update the SDCC RCGs to use shared_floor_ops [ Upstream commit 947c4b326c1f4dc64aed42170b39c2cf551ba8ca ] Use shared_floor_ops for the SDCC RCGs so the RCG is safely parked during disable and the new parent configuration is programmed in hardware only when the new parent is enabled, avoiding cases where the RCG configuration fails to update. Fixes: baa316580013 ("clk: qcom: gcc-qdu1000: Update the SDCC clock RCG ops") Signed-off-by: Jagadeesh Kona Reviewed-by: Imran Shaik Reviewed-by: Taniya Das Reviewed-by: Vladimir Zapolskiy Link: https://lore.kernel.org/r/20251127-sdcc_shared_floor_ops-v2-7-473afc86589c@oss.qualcomm.com Signed-off-by: Bjorn Andersson Signed-off-by: Sasha Levin --- drivers/clk/qcom/gcc-qdu1000.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/clk/qcom/gcc-qdu1000.c b/drivers/clk/qcom/gcc-qdu1000.c index dbe9e9437939af..915bb9b4ff8130 100644 --- a/drivers/clk/qcom/gcc-qdu1000.c +++ b/drivers/clk/qcom/gcc-qdu1000.c @@ -904,7 +904,7 @@ static struct clk_rcg2 gcc_sdcc5_apps_clk_src = { .name = "gcc_sdcc5_apps_clk_src", .parent_data = gcc_parent_data_8, .num_parents = ARRAY_SIZE(gcc_parent_data_8), - .ops = &clk_rcg2_floor_ops, + .ops = &clk_rcg2_shared_floor_ops, }, }; @@ -923,7 +923,7 @@ static struct clk_rcg2 gcc_sdcc5_ice_core_clk_src = { .name = "gcc_sdcc5_ice_core_clk_src", .parent_data = gcc_parent_data_2, .num_parents = ARRAY_SIZE(gcc_parent_data_2), - .ops = &clk_rcg2_floor_ops, + .ops = &clk_rcg2_shared_floor_ops, }, }; From a9d771adb49a18687abb8798004d08c382452c65 Mon Sep 17 00:00:00 2001 From: Jagadeesh Kona Date: Thu, 27 Nov 2025 23:27:43 +0530 Subject: [PATCH 3666/3902] clk: qcom: gcc-glymur: Update the SDCC RCGs to use shared_floor_ops [ Upstream commit d5639a6d72810023d257c935cb763aea1ada1abc ] Use shared_floor_ops for the SDCC RCGs so the RCG is safely parked during disable and the new parent configuration is programmed in hardware only when the new parent is enabled, avoiding cases where the RCG configuration fails to update. Fixes: efe504300a17 ("clk: qcom: gcc: Add support for Global Clock Controller") Signed-off-by: Jagadeesh Kona Reviewed-by: Imran Shaik Reviewed-by: Taniya Das Reviewed-by: Vladimir Zapolskiy Link: https://lore.kernel.org/r/20251127-sdcc_shared_floor_ops-v2-8-473afc86589c@oss.qualcomm.com Signed-off-by: Bjorn Andersson Signed-off-by: Sasha Levin --- drivers/clk/qcom/gcc-glymur.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/clk/qcom/gcc-glymur.c b/drivers/clk/qcom/gcc-glymur.c index d938e7dc5b66ec..17e860307fa108 100644 --- a/drivers/clk/qcom/gcc-glymur.c +++ b/drivers/clk/qcom/gcc-glymur.c @@ -2317,7 +2317,7 @@ static struct clk_rcg2 gcc_sdcc2_apps_clk_src = { .parent_data = gcc_parent_data_17, .num_parents = ARRAY_SIZE(gcc_parent_data_17), .flags = CLK_SET_RATE_PARENT, - .ops = &clk_rcg2_floor_ops, + .ops = &clk_rcg2_shared_floor_ops, }, }; @@ -2339,7 +2339,7 @@ static struct clk_rcg2 gcc_sdcc4_apps_clk_src = { .parent_data = gcc_parent_data_3, .num_parents = ARRAY_SIZE(gcc_parent_data_3), .flags = CLK_SET_RATE_PARENT, - .ops = &clk_rcg2_floor_ops, + .ops = &clk_rcg2_shared_floor_ops, }, }; From 9dbd33356e3d95c704ebc6dd26f1bf58bd15d68d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Barnab=C3=A1s=20Cz=C3=A9m=C3=A1n?= Date: Mon, 17 Nov 2025 18:58:47 +0100 Subject: [PATCH 3667/3902] clk: qcom: gcc-msm8953: Remove ALWAYS_ON flag from cpp_gdsc MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [ Upstream commit 5f613e7034187179a9d088ff5fd02b1089d0cf20 ] cpp_gdsc should not be always on, ALWAYS_ON flag was set accidentally. Fixes: 9bb6cfc3c77e ("clk: qcom: Add Global Clock Controller driver for MSM8953") Signed-off-by: Barnabás Czémán Reviewed-by: Konrad Dybcio Link: https://lore.kernel.org/r/20251117-fix-gdsc-cpp-msm8917-msm8953-v1-1-db33adcff28a@mainlining.org Signed-off-by: Bjorn Andersson Signed-off-by: Sasha Levin --- drivers/clk/qcom/gcc-msm8953.c | 1 - 1 file changed, 1 deletion(-) diff --git a/drivers/clk/qcom/gcc-msm8953.c b/drivers/clk/qcom/gcc-msm8953.c index 8f29ecc74c50bf..8fe1d3e421440c 100644 --- a/drivers/clk/qcom/gcc-msm8953.c +++ b/drivers/clk/qcom/gcc-msm8953.c @@ -3946,7 +3946,6 @@ static struct gdsc cpp_gdsc = { .pd = { .name = "cpp_gdsc", }, - .flags = ALWAYS_ON, .pwrsts = PWRSTS_OFF_ON, }; From b88e84b154b97318bd79fb20b4b87cce049db822 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Barnab=C3=A1s=20Cz=C3=A9m=C3=A1n?= Date: Mon, 17 Nov 2025 18:58:48 +0100 Subject: [PATCH 3668/3902] clk: qcom: gcc-msm8917: Remove ALWAYS_ON flag from cpp_gdsc MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [ Upstream commit e4eb42f290aecac0ba355b1f8d7243be6de11f32 ] cpp_gdsc should not be always on, ALWAYS_ON flag was set accidentally. Fixes: 33cc27a47d3a ("clk: qcom: Add global clock controller driver for MSM8917") Signed-off-by: Barnabás Czémán Reviewed-by: Konrad Dybcio Link: https://lore.kernel.org/r/20251117-fix-gdsc-cpp-msm8917-msm8953-v1-2-db33adcff28a@mainlining.org Signed-off-by: Bjorn Andersson Signed-off-by: Sasha Levin --- drivers/clk/qcom/gcc-msm8917.c | 1 - 1 file changed, 1 deletion(-) diff --git a/drivers/clk/qcom/gcc-msm8917.c b/drivers/clk/qcom/gcc-msm8917.c index 0a1aa623cd49af..9d1c5a9953e2c6 100644 --- a/drivers/clk/qcom/gcc-msm8917.c +++ b/drivers/clk/qcom/gcc-msm8917.c @@ -3409,7 +3409,6 @@ static struct gdsc cpp_gdsc = { .pd = { .name = "cpp_gdsc", }, - .flags = ALWAYS_ON, .pwrsts = PWRSTS_OFF_ON, }; From 87fa98532b45bed3b5ba2d822357a294c8fabc36 Mon Sep 17 00:00:00 2001 From: George Moussalem Date: Fri, 28 Nov 2025 15:03:19 +0400 Subject: [PATCH 3669/3902] clk: qcom: gcc-ipq5018: flag sleep clock as critical [ Upstream commit 04c4dc1f541135708d90a9b4632af51136f93ac3 ] The sleep clock never be disabled. To avoid the kernel trying to disable it and keep it always on, flag it as critical. Fixes: e3fdbef1bab8 ("clk: qcom: Add Global Clock controller (GCC) driver for IPQ5018") Signed-off-by: George Moussalem Reviewed-by: Konrad Dybcio Link: https://lore.kernel.org/r/20251128-ipq5018-sleep-clk-fix-v1-1-6f4b75ec336c@outlook.com Signed-off-by: Bjorn Andersson Signed-off-by: Sasha Levin --- drivers/clk/qcom/gcc-ipq5018.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/clk/qcom/gcc-ipq5018.c b/drivers/clk/qcom/gcc-ipq5018.c index dcda2be8c1a519..64792cda062021 100644 --- a/drivers/clk/qcom/gcc-ipq5018.c +++ b/drivers/clk/qcom/gcc-ipq5018.c @@ -1340,6 +1340,7 @@ static struct clk_branch gcc_sleep_clk_src = { .name = "gcc_sleep_clk_src", .parent_data = gcc_sleep_clk_data, .num_parents = ARRAY_SIZE(gcc_sleep_clk_data), + .flags = CLK_IS_CRITICAL, .ops = &clk_branch2_ops, }, }, From 0b45cddb7385553c3d2285c2a62eda46d200230f Mon Sep 17 00:00:00 2001 From: Brian Masney Date: Thu, 8 Jan 2026 16:16:32 -0500 Subject: [PATCH 3670/3902] clk: qcom: alpha-pll: convert from divider_round_rate() to divider_determine_rate() [ Upstream commit e1f08613e113f02a3ec18c9a7964de97f940acbf ] The divider_round_rate() function is now deprecated, so let's migrate to divider_determine_rate() instead so that this deprecated API can be removed. Note that when the main function itself was migrated to use determine_rate, this was mistakenly converted to: req->rate = divider_round_rate(...) This is invalid in the case when an error occurs since it can set the rate to a negative value. Fixes: 0e56e3369b60 ("clk: qcom: alpha-pll: convert from round_rate() to determine_rate()") Signed-off-by: Brian Masney Reviewed-by: Abel Vesa Reviewed-by: Konrad Dybcio Link: https://lore.kernel.org/r/20260108-clk-divider-round-rate-v1-14-535a3ed73bf3@redhat.com Signed-off-by: Bjorn Andersson Signed-off-by: Sasha Levin --- drivers/clk/qcom/clk-alpha-pll.c | 21 ++++++--------------- 1 file changed, 6 insertions(+), 15 deletions(-) diff --git a/drivers/clk/qcom/clk-alpha-pll.c b/drivers/clk/qcom/clk-alpha-pll.c index 6aeba40358c11e..a84e8bee653462 100644 --- a/drivers/clk/qcom/clk-alpha-pll.c +++ b/drivers/clk/qcom/clk-alpha-pll.c @@ -1257,11 +1257,8 @@ static int clk_alpha_pll_postdiv_determine_rate(struct clk_hw *hw, else table = clk_alpha_div_table; - req->rate = divider_round_rate(hw, req->rate, &req->best_parent_rate, - table, pll->width, - CLK_DIVIDER_POWER_OF_TWO); - - return 0; + return divider_determine_rate(hw, req, table, pll->width, + CLK_DIVIDER_POWER_OF_TWO); } static int clk_alpha_pll_postdiv_ro_determine_rate(struct clk_hw *hw, @@ -1617,11 +1614,8 @@ static int clk_trion_pll_postdiv_determine_rate(struct clk_hw *hw, { struct clk_alpha_pll_postdiv *pll = to_clk_alpha_pll_postdiv(hw); - req->rate = divider_round_rate(hw, req->rate, &req->best_parent_rate, - pll->post_div_table, - pll->width, CLK_DIVIDER_ROUND_CLOSEST); - - return 0; + return divider_determine_rate(hw, req, pll->post_div_table, pll->width, + CLK_DIVIDER_ROUND_CLOSEST); }; static int @@ -1657,11 +1651,8 @@ static int clk_alpha_pll_postdiv_fabia_determine_rate(struct clk_hw *hw, { struct clk_alpha_pll_postdiv *pll = to_clk_alpha_pll_postdiv(hw); - req->rate = divider_round_rate(hw, req->rate, &req->best_parent_rate, - pll->post_div_table, - pll->width, CLK_DIVIDER_ROUND_CLOSEST); - - return 0; + return divider_determine_rate(hw, req, pll->post_div_table, pll->width, + CLK_DIVIDER_ROUND_CLOSEST); } static int clk_alpha_pll_postdiv_fabia_set_rate(struct clk_hw *hw, From 0533f1cdb382d9c9ea0f92dffbaeb42cf006f01f Mon Sep 17 00:00:00 2001 From: Miaoqian Lin Date: Tue, 5 Aug 2025 07:03:58 +0400 Subject: [PATCH 3671/3902] clk: rockchip: Fix error pointer check after rockchip_clk_register_gate_link() [ Upstream commit a8d722f03923b1c6166d39482c6df8f017e185d9 ] Replace NULL check with IS_ERR_OR_NULL() check after calling rockchip_clk_register_gate_link() since this function returns error pointers (ERR_PTR). Fixes: c62fa612cfa6 ("clk: rockchip: implement linked gate clock support") Signed-off-by: Miaoqian Lin Link: https://patch.msgid.link/20250805030358.3665878-1-linmq006@gmail.com Signed-off-by: Heiko Stuebner Signed-off-by: Sasha Levin --- drivers/clk/rockchip/clk.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/clk/rockchip/clk.c b/drivers/clk/rockchip/clk.c index 19caf26c991bd5..2d30b1e24f013c 100644 --- a/drivers/clk/rockchip/clk.c +++ b/drivers/clk/rockchip/clk.c @@ -693,7 +693,7 @@ void rockchip_clk_register_late_branches(struct device *dev, break; } - if (!pdev) + if (IS_ERR_OR_NULL(pdev)) dev_err(dev, "failed to register device for clock %s\n", list->name); } } From 8ed504f525f1b7347e2067db98bf9068091b7ca3 Mon Sep 17 00:00:00 2001 From: Brian Masney Date: Fri, 5 Dec 2025 14:46:27 -0500 Subject: [PATCH 3672/3902] clk: microchip: core: remove duplicate determine_rate on pic32_sclk_ops [ Upstream commit d93faac66dc04650d924f8f9584216d14f48fb14 ] pic32_sclk_ops previously had a sclk_round_rate() member, and this was recently converted over to sclk_determine_rate() with the help of a Coccinelle semantic patch. pic32_sclk_ops now has two conflicting determine_rate ops members. Prior to the conversion, pic32_sclk_ops already had a determine_rate member that points to __clk_mux_determine_rate(). When both the round_rate() and determine_rate() ops are defined, the clk core only uses the determine_rate() op. Let's go ahead and drop the recently converted sclk_determine_rate() to match the previous functionality prior to the conversion. Fixes: e9f039c08cdc ("clk: microchip: core: convert from round_rate() to determine_rate()") Reported-by: kernel test robot Closes: https://lore.kernel.org/oe-kbuild-all/202511222115.uvHrP95A-lkp@intel.com/ Signed-off-by: Brian Masney Reviewed-by: Claudiu Beznea Link: https://lore.kernel.org/r/20251205-clk-microchip-fixes-v3-1-a02190705e47@redhat.com Signed-off-by: Claudiu Beznea Signed-off-by: Sasha Levin --- drivers/clk/microchip/clk-core.c | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/drivers/clk/microchip/clk-core.c b/drivers/clk/microchip/clk-core.c index b34348d491f3e1..a0163441dfe5c1 100644 --- a/drivers/clk/microchip/clk-core.c +++ b/drivers/clk/microchip/clk-core.c @@ -780,15 +780,6 @@ static unsigned long sclk_get_rate(struct clk_hw *hw, unsigned long parent_rate) return parent_rate / div; } -static int sclk_determine_rate(struct clk_hw *hw, - struct clk_rate_request *req) -{ - req->rate = calc_best_divided_rate(req->rate, req->best_parent_rate, - SLEW_SYSDIV, 1); - - return 0; -} - static int sclk_set_rate(struct clk_hw *hw, unsigned long rate, unsigned long parent_rate) { @@ -912,7 +903,6 @@ static int sclk_init(struct clk_hw *hw) const struct clk_ops pic32_sclk_ops = { .get_parent = sclk_get_parent, .set_parent = sclk_set_parent, - .determine_rate = sclk_determine_rate, .set_rate = sclk_set_rate, .recalc_rate = sclk_get_rate, .init = sclk_init, From 4fdbb1e3ffa3607d6b68a40e95712daa6e396fc8 Mon Sep 17 00:00:00 2001 From: Vladimir Zapolskiy Date: Tue, 13 Jan 2026 17:11:40 +0200 Subject: [PATCH 3673/3902] Input: adp5589 - remove a leftover header file MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [ Upstream commit f8a6e5eac701369afb5d69aba875dc5fec93003d ] In commit 3bdbd0858df6 ("Input: adp5589: remove the driver") the last user of include/linux/input/adp5589.h was removed along with the whole driver, thus the header file can be also removed. Signed-off-by: Vladimir Zapolskiy Reviewed-by: Laurent Pinchart Reviewed-by: Nuno Sá Fixes: 3bdbd0858df6 ("Input: adp5589: remove the driver") Link: https://patch.msgid.link/20260113151140.3843753-1-vz@mleia.com Signed-off-by: Dmitry Torokhov Signed-off-by: Sasha Levin --- include/linux/input/adp5589.h | 180 ---------------------------------- 1 file changed, 180 deletions(-) delete mode 100644 include/linux/input/adp5589.h diff --git a/include/linux/input/adp5589.h b/include/linux/input/adp5589.h deleted file mode 100644 index 0e4742c8c81e3c..00000000000000 --- a/include/linux/input/adp5589.h +++ /dev/null @@ -1,180 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-only */ -/* - * Analog Devices ADP5589/ADP5585 I/O Expander and QWERTY Keypad Controller - * - * Copyright 2010-2011 Analog Devices Inc. - */ - -#ifndef _ADP5589_H -#define _ADP5589_H - -/* - * ADP5589 specific GPI and Keymap defines - */ - -#define ADP5589_KEYMAPSIZE 88 - -#define ADP5589_GPI_PIN_ROW0 97 -#define ADP5589_GPI_PIN_ROW1 98 -#define ADP5589_GPI_PIN_ROW2 99 -#define ADP5589_GPI_PIN_ROW3 100 -#define ADP5589_GPI_PIN_ROW4 101 -#define ADP5589_GPI_PIN_ROW5 102 -#define ADP5589_GPI_PIN_ROW6 103 -#define ADP5589_GPI_PIN_ROW7 104 -#define ADP5589_GPI_PIN_COL0 105 -#define ADP5589_GPI_PIN_COL1 106 -#define ADP5589_GPI_PIN_COL2 107 -#define ADP5589_GPI_PIN_COL3 108 -#define ADP5589_GPI_PIN_COL4 109 -#define ADP5589_GPI_PIN_COL5 110 -#define ADP5589_GPI_PIN_COL6 111 -#define ADP5589_GPI_PIN_COL7 112 -#define ADP5589_GPI_PIN_COL8 113 -#define ADP5589_GPI_PIN_COL9 114 -#define ADP5589_GPI_PIN_COL10 115 -#define GPI_LOGIC1 116 -#define GPI_LOGIC2 117 - -#define ADP5589_GPI_PIN_ROW_BASE ADP5589_GPI_PIN_ROW0 -#define ADP5589_GPI_PIN_ROW_END ADP5589_GPI_PIN_ROW7 -#define ADP5589_GPI_PIN_COL_BASE ADP5589_GPI_PIN_COL0 -#define ADP5589_GPI_PIN_COL_END ADP5589_GPI_PIN_COL10 - -#define ADP5589_GPI_PIN_BASE ADP5589_GPI_PIN_ROW_BASE -#define ADP5589_GPI_PIN_END ADP5589_GPI_PIN_COL_END - -#define ADP5589_GPIMAPSIZE_MAX (ADP5589_GPI_PIN_END - ADP5589_GPI_PIN_BASE + 1) - -/* - * ADP5585 specific GPI and Keymap defines - */ - -#define ADP5585_KEYMAPSIZE 30 - -#define ADP5585_GPI_PIN_ROW0 37 -#define ADP5585_GPI_PIN_ROW1 38 -#define ADP5585_GPI_PIN_ROW2 39 -#define ADP5585_GPI_PIN_ROW3 40 -#define ADP5585_GPI_PIN_ROW4 41 -#define ADP5585_GPI_PIN_ROW5 42 -#define ADP5585_GPI_PIN_COL0 43 -#define ADP5585_GPI_PIN_COL1 44 -#define ADP5585_GPI_PIN_COL2 45 -#define ADP5585_GPI_PIN_COL3 46 -#define ADP5585_GPI_PIN_COL4 47 -#define GPI_LOGIC 48 - -#define ADP5585_GPI_PIN_ROW_BASE ADP5585_GPI_PIN_ROW0 -#define ADP5585_GPI_PIN_ROW_END ADP5585_GPI_PIN_ROW5 -#define ADP5585_GPI_PIN_COL_BASE ADP5585_GPI_PIN_COL0 -#define ADP5585_GPI_PIN_COL_END ADP5585_GPI_PIN_COL4 - -#define ADP5585_GPI_PIN_BASE ADP5585_GPI_PIN_ROW_BASE -#define ADP5585_GPI_PIN_END ADP5585_GPI_PIN_COL_END - -#define ADP5585_GPIMAPSIZE_MAX (ADP5585_GPI_PIN_END - ADP5585_GPI_PIN_BASE + 1) - -struct adp5589_gpi_map { - unsigned short pin; - unsigned short sw_evt; -}; - -/* scan_cycle_time */ -#define ADP5589_SCAN_CYCLE_10ms 0 -#define ADP5589_SCAN_CYCLE_20ms 1 -#define ADP5589_SCAN_CYCLE_30ms 2 -#define ADP5589_SCAN_CYCLE_40ms 3 - -/* RESET_CFG */ -#define RESET_PULSE_WIDTH_500us 0 -#define RESET_PULSE_WIDTH_1ms 1 -#define RESET_PULSE_WIDTH_2ms 2 -#define RESET_PULSE_WIDTH_10ms 3 - -#define RESET_TRIG_TIME_0ms (0 << 2) -#define RESET_TRIG_TIME_1000ms (1 << 2) -#define RESET_TRIG_TIME_1500ms (2 << 2) -#define RESET_TRIG_TIME_2000ms (3 << 2) -#define RESET_TRIG_TIME_2500ms (4 << 2) -#define RESET_TRIG_TIME_3000ms (5 << 2) -#define RESET_TRIG_TIME_3500ms (6 << 2) -#define RESET_TRIG_TIME_4000ms (7 << 2) - -#define RESET_PASSTHRU_EN (1 << 5) -#define RESET1_POL_HIGH (1 << 6) -#define RESET1_POL_LOW (0 << 6) -#define RESET2_POL_HIGH (1 << 7) -#define RESET2_POL_LOW (0 << 7) - -/* ADP5589 Mask Bits: - * C C C C C C C C C C C | R R R R R R R R - * 1 9 8 7 6 5 4 3 2 1 0 | 7 6 5 4 3 2 1 0 - * 0 - * ---------------- BIT ------------------ - * 1 1 1 1 1 1 1 1 1 0 0 | 0 0 0 0 0 0 0 0 - * 8 7 6 5 4 3 2 1 0 9 8 | 7 6 5 4 3 2 1 0 - */ - -#define ADP_ROW(x) (1 << (x)) -#define ADP_COL(x) (1 << (x + 8)) -#define ADP5589_ROW_MASK 0xFF -#define ADP5589_COL_MASK 0xFF -#define ADP5589_COL_SHIFT 8 -#define ADP5589_MAX_ROW_NUM 7 -#define ADP5589_MAX_COL_NUM 10 - -/* ADP5585 Mask Bits: - * C C C C C | R R R R R R - * 4 3 2 1 0 | 5 4 3 2 1 0 - * - * ---- BIT -- ----------- - * 1 0 0 0 0 | 0 0 0 0 0 0 - * 0 9 8 7 6 | 5 4 3 2 1 0 - */ - -#define ADP5585_ROW_MASK 0x3F -#define ADP5585_COL_MASK 0x1F -#define ADP5585_ROW_SHIFT 0 -#define ADP5585_COL_SHIFT 6 -#define ADP5585_MAX_ROW_NUM 5 -#define ADP5585_MAX_COL_NUM 4 - -#define ADP5585_ROW(x) (1 << ((x) & ADP5585_ROW_MASK)) -#define ADP5585_COL(x) (1 << (((x) & ADP5585_COL_MASK) + ADP5585_COL_SHIFT)) - -/* Put one of these structures in i2c_board_info platform_data */ - -struct adp5589_kpad_platform_data { - unsigned keypad_en_mask; /* Keypad (Rows/Columns) enable mask */ - const unsigned short *keymap; /* Pointer to keymap */ - unsigned short keymapsize; /* Keymap size */ - bool repeat; /* Enable key repeat */ - bool en_keylock; /* Enable key lock feature (ADP5589 only)*/ - unsigned char unlock_key1; /* Unlock Key 1 (ADP5589 only) */ - unsigned char unlock_key2; /* Unlock Key 2 (ADP5589 only) */ - unsigned char unlock_timer; /* Time in seconds [0..7] between the two unlock keys 0=disable (ADP5589 only) */ - unsigned char scan_cycle_time; /* Time between consecutive scan cycles */ - unsigned char reset_cfg; /* Reset config */ - unsigned short reset1_key_1; /* Reset Key 1 */ - unsigned short reset1_key_2; /* Reset Key 2 */ - unsigned short reset1_key_3; /* Reset Key 3 */ - unsigned short reset2_key_1; /* Reset Key 1 */ - unsigned short reset2_key_2; /* Reset Key 2 */ - unsigned debounce_dis_mask; /* Disable debounce mask */ - unsigned pull_dis_mask; /* Disable all pull resistors mask */ - unsigned pullup_en_100k; /* Pull-Up 100k Enable Mask */ - unsigned pullup_en_300k; /* Pull-Up 300k Enable Mask */ - unsigned pulldown_en_300k; /* Pull-Down 300k Enable Mask */ - const struct adp5589_gpi_map *gpimap; - unsigned short gpimapsize; - const struct adp5589_gpio_platform_data *gpio_data; -}; - -struct i2c_client; /* forward declaration */ - -struct adp5589_gpio_platform_data { - int gpio_start; /* GPIO Chip base # */ -}; - -#endif From cdab39223d57edb4e89e7c35ff2661b8a388049c Mon Sep 17 00:00:00 2001 From: Geert Uytterhoeven Date: Mon, 1 Dec 2025 10:42:26 +0100 Subject: [PATCH 3674/3902] clk: Move clk_{save,restore}_context() to COMMON_CLK section [ Upstream commit f47c1b77d0a2a9c0d49ec14302e74f933398d1a3 ] The clk_save_context() and clk_restore_context() helpers are only implemented by the Common Clock Framework. They are not available when using legacy clock frameworks. Dummy implementations are provided, but only if no clock support is available at all. Hence when CONFIG_HAVE_CLK=y, but CONFIG_COMMON_CLK is not enabled: m68k-linux-gnu-ld: drivers/net/phy/air_en8811h.o: in function `en8811h_resume': air_en8811h.c:(.text+0x83e): undefined reference to `clk_restore_context' m68k-linux-gnu-ld: drivers/net/phy/air_en8811h.o: in function `en8811h_suspend': air_en8811h.c:(.text+0x856): undefined reference to `clk_save_context' Fix this by moving forward declarations and dummy implementions from the HAVE_CLK to the COMMON_CLK section. Fixes: 8b95d1ce3300c411 ("clk: Add functions to save/restore clock context en-masse") Reported-by: kernel test robot Closes: https://lore.kernel.org/oe-kbuild-all/202511301553.eaEz1nEW-lkp@intel.com/ Signed-off-by: Geert Uytterhoeven Signed-off-by: Stephen Boyd Signed-off-by: Sasha Levin --- include/linux/clk.h | 48 ++++++++++++++++++++++----------------------- 1 file changed, 24 insertions(+), 24 deletions(-) diff --git a/include/linux/clk.h b/include/linux/clk.h index b607482ca77e98..64ff118ffb1a1d 100644 --- a/include/linux/clk.h +++ b/include/linux/clk.h @@ -228,6 +228,23 @@ int devm_clk_rate_exclusive_get(struct device *dev, struct clk *clk); */ void clk_rate_exclusive_put(struct clk *clk); +/** + * clk_save_context - save clock context for poweroff + * + * Saves the context of the clock register for powerstates in which the + * contents of the registers will be lost. Occurs deep within the suspend + * code so locking is not necessary. + */ +int clk_save_context(void); + +/** + * clk_restore_context - restore clock context after poweroff + * + * This occurs with all clocks enabled. Occurs deep within the resume code + * so locking is not necessary. + */ +void clk_restore_context(void); + #else static inline int clk_notifier_register(struct clk *clk, @@ -293,6 +310,13 @@ static inline int devm_clk_rate_exclusive_get(struct device *dev, struct clk *cl static inline void clk_rate_exclusive_put(struct clk *clk) {} +static inline int clk_save_context(void) +{ + return 0; +} + +static inline void clk_restore_context(void) {} + #endif #ifdef CONFIG_HAVE_CLK_PREPARE @@ -933,23 +957,6 @@ struct clk *clk_get_parent(struct clk *clk); */ struct clk *clk_get_sys(const char *dev_id, const char *con_id); -/** - * clk_save_context - save clock context for poweroff - * - * Saves the context of the clock register for powerstates in which the - * contents of the registers will be lost. Occurs deep within the suspend - * code so locking is not necessary. - */ -int clk_save_context(void); - -/** - * clk_restore_context - restore clock context after poweroff - * - * This occurs with all clocks enabled. Occurs deep within the resume code - * so locking is not necessary. - */ -void clk_restore_context(void); - #else /* !CONFIG_HAVE_CLK */ static inline struct clk *clk_get(struct device *dev, const char *id) @@ -1129,13 +1136,6 @@ static inline struct clk *clk_get_sys(const char *dev_id, const char *con_id) return NULL; } -static inline int clk_save_context(void) -{ - return 0; -} - -static inline void clk_restore_context(void) {} - #endif /* clk_prepare_enable helps cases using clk_enable in non-atomic context. */ From 22d691c02ad5c8bb3df265720d4b1de05bd262ac Mon Sep 17 00:00:00 2001 From: Brian Masney Date: Thu, 8 Jan 2026 16:16:33 -0500 Subject: [PATCH 3675/3902] clk: qcom: regmap-divider: convert from divider_ro_round_rate() to divider_ro_determine_rate() [ Upstream commit 349f02c0f5d4ee147c582b89cadd553bd534028a ] The divider_ro_round_rate() function is now deprecated, so let's migrate to divider_ro_determine_rate() instead so that this deprecated API can be removed. Note that when the main function itself was migrated to use determine_rate, this was mistakenly converted to: req->rate = divider_round_rate(...) This is invalid in the case when an error occurs since it can set the rate to a negative value. Fixes: b6f90511c165 ("clk: qcom: regmap-divider: convert from round_rate() to determine_rate()") Signed-off-by: Brian Masney Reviewed-by: Abel Vesa Reviewed-by: Konrad Dybcio Link: https://lore.kernel.org/r/20260108-clk-divider-round-rate-v1-15-535a3ed73bf3@redhat.com Signed-off-by: Bjorn Andersson Signed-off-by: Sasha Levin --- drivers/clk/qcom/clk-regmap-divider.c | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/drivers/clk/qcom/clk-regmap-divider.c b/drivers/clk/qcom/clk-regmap-divider.c index 4f5395f0ab6d0e..af9c01dd785372 100644 --- a/drivers/clk/qcom/clk-regmap-divider.c +++ b/drivers/clk/qcom/clk-regmap-divider.c @@ -26,12 +26,8 @@ static int div_ro_determine_rate(struct clk_hw *hw, val >>= divider->shift; val &= BIT(divider->width) - 1; - req->rate = divider_ro_round_rate(hw, req->rate, - &req->best_parent_rate, NULL, - divider->width, - CLK_DIVIDER_ROUND_CLOSEST, val); - - return 0; + return divider_ro_determine_rate(hw, req, NULL, divider->width, + CLK_DIVIDER_ROUND_CLOSEST, val); } static int div_determine_rate(struct clk_hw *hw, struct clk_rate_request *req) From 2a01a346d20ed1c27ab424e0b93cff8e850bfcda Mon Sep 17 00:00:00 2001 From: Brian Masney Date: Thu, 8 Jan 2026 16:16:34 -0500 Subject: [PATCH 3676/3902] clk: qcom: regmap-divider: convert from divider_round_rate() to divider_determine_rate() [ Upstream commit d8300e6e078a3a44ac0c75c6d8ba46d78ab94035 ] The divider_round_rate() function is now deprecated, so let's migrate to divider_determine_rate() instead so that this deprecated API can be removed. Note that when the main function itself was migrated to use determine_rate, this was mistakenly converted to: req->rate = divider_round_rate(...) This is invalid in the case when an error occurs since it can set the rate to a negative value. Fixes: b6f90511c165 ("clk: qcom: regmap-divider: convert from round_rate() to determine_rate()") Signed-off-by: Brian Masney Reviewed-by: Abel Vesa Reviewed-by: Konrad Dybcio Link: https://lore.kernel.org/r/20260108-clk-divider-round-rate-v1-16-535a3ed73bf3@redhat.com Signed-off-by: Bjorn Andersson Signed-off-by: Sasha Levin --- drivers/clk/qcom/clk-regmap-divider.c | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/drivers/clk/qcom/clk-regmap-divider.c b/drivers/clk/qcom/clk-regmap-divider.c index af9c01dd785372..672e82caf20504 100644 --- a/drivers/clk/qcom/clk-regmap-divider.c +++ b/drivers/clk/qcom/clk-regmap-divider.c @@ -34,12 +34,8 @@ static int div_determine_rate(struct clk_hw *hw, struct clk_rate_request *req) { struct clk_regmap_div *divider = to_clk_regmap_div(hw); - req->rate = divider_round_rate(hw, req->rate, &req->best_parent_rate, - NULL, - divider->width, - CLK_DIVIDER_ROUND_CLOSEST); - - return 0; + return divider_determine_rate(hw, req, NULL, divider->width, + CLK_DIVIDER_ROUND_CLOSEST); } static int div_set_rate(struct clk_hw *hw, unsigned long rate, From aeb606d885ef6a4902400edf8cb0ef4b8a754410 Mon Sep 17 00:00:00 2001 From: Petr Hodina Date: Wed, 7 Jan 2026 12:44:43 +0100 Subject: [PATCH 3677/3902] clk: qcom: dispcc-sdm845: Enable parents for pixel clocks [ Upstream commit a1d63493634e98360140027fef49d82b1ff0a267 ] Add CLK_OPS_PARENT_ENABLE to MDSS pixel clock sources to ensure parent clocks are enabled during clock operations, preventing potential stability issues during display configuration. Fixes: 81351776c9fb ("clk: qcom: Add display clock controller driver for SDM845") Signed-off-by: Petr Hodina Reviewed-by: Dmitry Baryshkov Reviewed-by: David Heidelberg Link: https://lore.kernel.org/r/20260107-stability-discussion-v2-1-ef7717b435ff@protonmail.com Signed-off-by: Bjorn Andersson Signed-off-by: Sasha Levin --- drivers/clk/qcom/dispcc-sdm845.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/clk/qcom/dispcc-sdm845.c b/drivers/clk/qcom/dispcc-sdm845.c index 2f9e9665d7e93f..78e43f6d750260 100644 --- a/drivers/clk/qcom/dispcc-sdm845.c +++ b/drivers/clk/qcom/dispcc-sdm845.c @@ -280,7 +280,7 @@ static struct clk_rcg2 disp_cc_mdss_pclk0_clk_src = { .name = "disp_cc_mdss_pclk0_clk_src", .parent_data = disp_cc_parent_data_4, .num_parents = ARRAY_SIZE(disp_cc_parent_data_4), - .flags = CLK_SET_RATE_PARENT, + .flags = CLK_SET_RATE_PARENT | CLK_OPS_PARENT_ENABLE, .ops = &clk_pixel_ops, }, }; @@ -295,7 +295,7 @@ static struct clk_rcg2 disp_cc_mdss_pclk1_clk_src = { .name = "disp_cc_mdss_pclk1_clk_src", .parent_data = disp_cc_parent_data_4, .num_parents = ARRAY_SIZE(disp_cc_parent_data_4), - .flags = CLK_SET_RATE_PARENT, + .flags = CLK_SET_RATE_PARENT | CLK_OPS_PARENT_ENABLE, .ops = &clk_pixel_ops, }, }; From e5001c92fc8662900e499bfc8548ead05b74b512 Mon Sep 17 00:00:00 2001 From: David Heidelberg Date: Sat, 17 Jan 2026 19:18:28 +0100 Subject: [PATCH 3678/3902] clk: qcom: dispcc-sm7150: Fix dispcc_mdss_pclk1_clk_src [ Upstream commit fab13d738c9bd645965464b881335f580d38a54e ] Set CLK_OPS_PARENT_ENABLE to ensure the parent gets prepared and enabled when switching to it. Fixes: e3c13e0caa8c ("clk: qcom: dispcc-sm7150: Fix dispcc_mdss_pclk0_clk_src") Signed-off-by: David Heidelberg Link: https://lore.kernel.org/r/20260117-sm7150-dispcc-fix-v1-1-2f39966bcad2@ixit.cz Signed-off-by: Bjorn Andersson Signed-off-by: Sasha Levin --- drivers/clk/qcom/dispcc-sm7150.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/clk/qcom/dispcc-sm7150.c b/drivers/clk/qcom/dispcc-sm7150.c index ddc7230b8aea77..923f0f38e804cb 100644 --- a/drivers/clk/qcom/dispcc-sm7150.c +++ b/drivers/clk/qcom/dispcc-sm7150.c @@ -370,7 +370,7 @@ static struct clk_rcg2 dispcc_mdss_pclk1_clk_src = { .name = "dispcc_mdss_pclk1_clk_src", .parent_data = dispcc_parent_data_4, .num_parents = ARRAY_SIZE(dispcc_parent_data_4), - .flags = CLK_SET_RATE_PARENT, + .flags = CLK_SET_RATE_PARENT | CLK_OPS_PARENT_ENABLE, .ops = &clk_pixel_ops, }, }; From aed53da569fb96eec09b4817b1953bcc2e467eea Mon Sep 17 00:00:00 2001 From: Dmitry Baryshkov Date: Sat, 17 Jan 2026 05:54:47 +0200 Subject: [PATCH 3679/3902] clk: qcom: gfx3d: add parent to parent request map [ Upstream commit 2583cb925ca1ce450aa5d74a05a67448db970193 ] After commit d228ece36345 ("clk: divider: remove round_rate() in favor of determine_rate()") determining GFX3D clock rate crashes, because the passed parent map doesn't provide the expected best_parent_hw clock (with the roundd_rate path before the offending commit the best_parent_hw was ignored). Set the field in parent_req in addition to setting it in the req, fixing the crash. clk_hw_round_rate (drivers/clk/clk.c:1764) (P) clk_divider_bestdiv (drivers/clk/clk-divider.c:336) divider_determine_rate (drivers/clk/clk-divider.c:358) clk_alpha_pll_postdiv_determine_rate (drivers/clk/qcom/clk-alpha-pll.c:1275) clk_core_determine_round_nolock (drivers/clk/clk.c:1606) clk_core_round_rate_nolock (drivers/clk/clk.c:1701) __clk_determine_rate (drivers/clk/clk.c:1741) clk_gfx3d_determine_rate (drivers/clk/qcom/clk-rcg2.c:1268) clk_core_determine_round_nolock (drivers/clk/clk.c:1606) clk_core_round_rate_nolock (drivers/clk/clk.c:1701) clk_core_round_rate_nolock (drivers/clk/clk.c:1710) clk_round_rate (drivers/clk/clk.c:1804) dev_pm_opp_set_rate (drivers/opp/core.c:1440 (discriminator 1)) msm_devfreq_target (drivers/gpu/drm/msm/msm_gpu_devfreq.c:51) devfreq_set_target (drivers/devfreq/devfreq.c:360) devfreq_update_target (drivers/devfreq/devfreq.c:426) devfreq_monitor (drivers/devfreq/devfreq.c:458) process_one_work (arch/arm64/include/asm/jump_label.h:36 include/trace/events/workqueue.h:110 kernel/workqueue.c:3284) worker_thread (kernel/workqueue.c:3356 (discriminator 2) kernel/workqueue.c:3443 (discriminator 2)) kthread (kernel/kthread.c:467) ret_from_fork (arch/arm64/kernel/entry.S:861) Fixes: 55213e1acec9 ("clk: qcom: Add gfx3d ping-pong PLL frequency switching") Signed-off-by: Dmitry Baryshkov Reviewed-by: Abel Vesa Reviewed-by: Konrad Dybcio Reviewed-by: Brian Masney Link: https://lore.kernel.org/r/20260117-db820-fix-gfx3d-v1-1-0f8894d71d63@oss.qualcomm.com Signed-off-by: Bjorn Andersson Signed-off-by: Sasha Levin --- drivers/clk/qcom/clk-rcg2.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/clk/qcom/clk-rcg2.c b/drivers/clk/qcom/clk-rcg2.c index 2838d4cb2d58ea..d0a5847f911144 100644 --- a/drivers/clk/qcom/clk-rcg2.c +++ b/drivers/clk/qcom/clk-rcg2.c @@ -1264,6 +1264,7 @@ static int clk_gfx3d_determine_rate(struct clk_hw *hw, if (req->max_rate < parent_req.max_rate) parent_req.max_rate = req->max_rate; + parent_req.best_parent_hw = req->best_parent_hw; ret = __clk_determine_rate(req->best_parent_hw, &parent_req); if (ret) return ret; From bfcc7cc9c36caeac92afc0437166d2d048ee021b Mon Sep 17 00:00:00 2001 From: Brian Masney Date: Thu, 8 Jan 2026 16:16:22 -0500 Subject: [PATCH 3680/3902] clk: actions: owl-composite: convert from owl_divider_helper_round_rate() to divider_determine_rate() [ Upstream commit d0b7c5bf6c5520c35fecff34da83d390405d3eaf ] owl_divider_helper_round_rate() is just a wrapper for divider_round_rate(), which is deprecated. Let's migrate to divider_determine_rate() instead so that this deprecated API can be removed. Acked-by: Manivannan Sadhasivam Signed-off-by: Brian Masney Stable-dep-of: 3ff3360440fa ("clk: actions: owl-divider: convert from divider_round_rate() to divider_determine_rate()") Signed-off-by: Sasha Levin --- drivers/clk/actions/owl-composite.c | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/drivers/clk/actions/owl-composite.c b/drivers/clk/actions/owl-composite.c index 00b74f8bc4375a..9540444307d6c9 100644 --- a/drivers/clk/actions/owl-composite.c +++ b/drivers/clk/actions/owl-composite.c @@ -57,15 +57,10 @@ static int owl_comp_div_determine_rate(struct clk_hw *hw, struct clk_rate_request *req) { struct owl_composite *comp = hw_to_owl_comp(hw); - long rate; - - rate = owl_divider_helper_round_rate(&comp->common, &comp->rate.div_hw, - req->rate, &req->best_parent_rate); - if (rate < 0) - return rate; + struct owl_divider_hw *div = &comp->rate.div_hw; - req->rate = rate; - return 0; + return divider_determine_rate(&comp->common.hw, req, div->table, + div->width, div->div_flags); } static unsigned long owl_comp_div_recalc_rate(struct clk_hw *hw, From d4274c2df180e45d385ff53b9729cc6a41fc2ec8 Mon Sep 17 00:00:00 2001 From: Brian Masney Date: Thu, 8 Jan 2026 16:16:23 -0500 Subject: [PATCH 3681/3902] clk: actions: owl-divider: convert from divider_round_rate() to divider_determine_rate() [ Upstream commit 3ff3360440fa8cc7ef5a4da628d3b770b46a4f73 ] The divider_round_rate() function is now deprecated, so let's migrate to divider_determine_rate() instead so that this deprecated API can be removed. Additionally, owl_divider_helper_round_rate() is no longer used, so let's drop that from the header file as well. Note that when the main function itself was migrated to use determine_rate, this was mistakenly converted to: req->rate = divider_round_rate(...) This is invalid in the case when an error occurs since it can set the rate to a negative value. Fixes: 1b04e12a8bcc ("clk: actions: owl-divider: convert from round_rate() to determine_rate()") Acked-by: Manivannan Sadhasivam Signed-off-by: Brian Masney Signed-off-by: Sasha Levin --- drivers/clk/actions/owl-divider.c | 17 ++--------------- drivers/clk/actions/owl-divider.h | 5 ----- 2 files changed, 2 insertions(+), 20 deletions(-) diff --git a/drivers/clk/actions/owl-divider.c b/drivers/clk/actions/owl-divider.c index 118f1393c6780f..316ace80e87e3b 100644 --- a/drivers/clk/actions/owl-divider.c +++ b/drivers/clk/actions/owl-divider.c @@ -13,26 +13,13 @@ #include "owl-divider.h" -long owl_divider_helper_round_rate(struct owl_clk_common *common, - const struct owl_divider_hw *div_hw, - unsigned long rate, - unsigned long *parent_rate) -{ - return divider_round_rate(&common->hw, rate, parent_rate, - div_hw->table, div_hw->width, - div_hw->div_flags); -} - static int owl_divider_determine_rate(struct clk_hw *hw, struct clk_rate_request *req) { struct owl_divider *div = hw_to_owl_divider(hw); - req->rate = owl_divider_helper_round_rate(&div->common, &div->div_hw, - req->rate, - &req->best_parent_rate); - - return 0; + return divider_determine_rate(hw, req, div->div_hw.table, + div->div_hw.width, div->div_hw.div_flags); } unsigned long owl_divider_helper_recalc_rate(struct owl_clk_common *common, diff --git a/drivers/clk/actions/owl-divider.h b/drivers/clk/actions/owl-divider.h index 083be6d80954d7..2ba957740c3813 100644 --- a/drivers/clk/actions/owl-divider.h +++ b/drivers/clk/actions/owl-divider.h @@ -56,11 +56,6 @@ static inline struct owl_divider *hw_to_owl_divider(const struct clk_hw *hw) return container_of(common, struct owl_divider, common); } -long owl_divider_helper_round_rate(struct owl_clk_common *common, - const struct owl_divider_hw *div_hw, - unsigned long rate, - unsigned long *parent_rate); - unsigned long owl_divider_helper_recalc_rate(struct owl_clk_common *common, const struct owl_divider_hw *div_hw, unsigned long parent_rate); From aa1ed67e584265ff8aa8bc43cdeb1075d6a76d0f Mon Sep 17 00:00:00 2001 From: Brian Masney Date: Thu, 8 Jan 2026 16:16:25 -0500 Subject: [PATCH 3682/3902] clk: bm1880: convert from divider_round_rate() to divider_determine_rate() [ Upstream commit 463b97bef0c9fb02b743d6b9f0d698cae81a1d9f ] The divider_round_rate() function is now deprecated, so let's migrate to divider_determine_rate() instead so that this deprecated API can be removed. Note that when the main function itself was migrated to use determine_rate, this was mistakenly converted to: req->rate = divider_round_rate(...) This is invalid in the case when an error occurs since it can set the rate to a negative value. Fixes: 64613d7fb42f ("clk: bm1880: convert from round_rate() to determine_rate()") Acked-by: Manivannan Sadhasivam Signed-off-by: Brian Masney Signed-off-by: Sasha Levin --- drivers/clk/clk-bm1880.c | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/drivers/clk/clk-bm1880.c b/drivers/clk/clk-bm1880.c index dac190bc6e19a6..d2617fe16d2e42 100644 --- a/drivers/clk/clk-bm1880.c +++ b/drivers/clk/clk-bm1880.c @@ -629,10 +629,7 @@ static int bm1880_clk_div_determine_rate(struct clk_hw *hw, return 0; } - req->rate = divider_round_rate(hw, req->rate, &req->best_parent_rate, - div->table, div->width, div->flags); - - return 0; + return divider_determine_rate(hw, req, div->table, div->width, div->flags); } static int bm1880_clk_div_set_rate(struct clk_hw *hw, unsigned long rate, From 0a5c1dff51150f2e8511e548cbda4c911aa354a8 Mon Sep 17 00:00:00 2001 From: Brian Masney Date: Thu, 8 Jan 2026 16:16:26 -0500 Subject: [PATCH 3683/3902] clk: hisilicon: clkdivider-hi6220: convert from divider_round_rate() to divider_determine_rate() [ Upstream commit e3a5249c140a1ded55937ba04247d530a85f0edc ] The divider_round_rate() function is now deprecated, so let's migrate to divider_determine_rate() instead so that this deprecated API can be removed. Note that when the main function itself was migrated to use determine_rate, this was mistakenly converted to: req->rate = divider_round_rate(...) This is invalid in the case when an error occurs since it can set the rate to a negative value. Fixes: 619a6210f398 ("clk: hisilicon: clkdivider-hi6220: convert from round_rate() to determine_rate()") Signed-off-by: Brian Masney Signed-off-by: Sasha Levin --- drivers/clk/hisilicon/clkdivider-hi6220.c | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/drivers/clk/hisilicon/clkdivider-hi6220.c b/drivers/clk/hisilicon/clkdivider-hi6220.c index 6bae18a84cb6c0..fd7ceb92d65157 100644 --- a/drivers/clk/hisilicon/clkdivider-hi6220.c +++ b/drivers/clk/hisilicon/clkdivider-hi6220.c @@ -60,10 +60,8 @@ static int hi6220_clkdiv_determine_rate(struct clk_hw *hw, { struct hi6220_clk_divider *dclk = to_hi6220_clk_divider(hw); - req->rate = divider_round_rate(hw, req->rate, &req->best_parent_rate, dclk->table, - dclk->width, CLK_DIVIDER_ROUND_CLOSEST); - - return 0; + return divider_determine_rate(hw, req, dclk->table, dclk->width, + CLK_DIVIDER_ROUND_CLOSEST); } static int hi6220_clkdiv_set_rate(struct clk_hw *hw, unsigned long rate, From c05f9dfa6959ddecb179bc84ffe578d6ce0248ce Mon Sep 17 00:00:00 2001 From: Brian Masney Date: Thu, 8 Jan 2026 16:16:27 -0500 Subject: [PATCH 3684/3902] clk: loongson1: convert from divider_round_rate() to divider_determine_rate() [ Upstream commit 11d3c676e7e0f00e3398199f85e47a0e22369866 ] The divider_round_rate() function is now deprecated, so let's migrate to divider_determine_rate() instead so that this deprecated API can be removed. Note that when the main function itself was migrated to use determine_rate, this was mistakenly converted to: req->rate = divider_round_rate(...) This is invalid in the case when an error occurs since it can set the rate to a negative value. Fixes: bb40a2ef4fc9 ("clk: loongson1: convert from round_rate() to determine_rate()") Reviewed-by: Keguang Zhang Tested-by: Keguang Zhang # on LS1B & LS1C Signed-off-by: Brian Masney Signed-off-by: Sasha Levin --- drivers/clk/clk-loongson1.c | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/drivers/clk/clk-loongson1.c b/drivers/clk/clk-loongson1.c index f9f060d08a5fae..1674181a1107dc 100644 --- a/drivers/clk/clk-loongson1.c +++ b/drivers/clk/clk-loongson1.c @@ -99,10 +99,7 @@ static int ls1x_divider_determine_rate(struct clk_hw *hw, struct ls1x_clk *ls1x_clk = to_ls1x_clk(hw); const struct ls1x_clk_div_data *d = ls1x_clk->data; - req->rate = divider_round_rate(hw, req->rate, &req->best_parent_rate, - d->table, d->width, d->flags); - - return 0; + return divider_determine_rate(hw, req, d->table, d->width, d->flags); } static int ls1x_divider_set_rate(struct clk_hw *hw, unsigned long rate, From 199625f9734ff3fffc76765d91da50dcdee79246 Mon Sep 17 00:00:00 2001 From: Brian Masney Date: Thu, 8 Jan 2026 16:16:29 -0500 Subject: [PATCH 3685/3902] clk: milbeaut: convert from divider_round_rate() to divider_determine_rate() [ Upstream commit 865e63b038c446d38593ddbcc362ebb62e6ff007 ] The divider_round_rate() function is now deprecated, so let's migrate to divider_determine_rate() instead so that this deprecated API can be removed. Note that when the main function itself was migrated to use determine_rate, this was mistakenly converted to: req->rate = divider_round_rate(...) This is invalid in the case when an error occurs since it can set the rate to a negative value. Fixes: 7b45988fcf78 ("clk: milbeaut: convert from round_rate() to determine_rate()") Signed-off-by: Brian Masney Signed-off-by: Sasha Levin --- drivers/clk/clk-milbeaut.c | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/drivers/clk/clk-milbeaut.c b/drivers/clk/clk-milbeaut.c index b4f9b7143eaa65..bb94d02a76cf10 100644 --- a/drivers/clk/clk-milbeaut.c +++ b/drivers/clk/clk-milbeaut.c @@ -407,10 +407,7 @@ static int m10v_clk_divider_determine_rate(struct clk_hw *hw, return 0; } - req->rate = divider_round_rate(hw, req->rate, &req->best_parent_rate, - divider->table, divider->width, divider->flags); - - return 0; + return divider_determine_rate(hw, req, divider->table, divider->width, divider->flags); } static int m10v_clk_divider_set_rate(struct clk_hw *hw, unsigned long rate, From 7f487ccf58b02960fa437a37c1588fcfe527f3b9 Mon Sep 17 00:00:00 2001 From: Brian Masney Date: Thu, 8 Jan 2026 16:16:30 -0500 Subject: [PATCH 3686/3902] clk: nuvoton: ma35d1-divider: convert from divider_round_rate() to divider_determine_rate() [ Upstream commit 9329d784ca9aad03b12508128797d40fd1f2e0c1 ] The divider_round_rate() function is now deprecated, so let's migrate to divider_determine_rate() instead so that this deprecated API can be removed. Note that when the main function itself was migrated to use determine_rate, this was mistakenly converted to: req->rate = divider_round_rate(...) This is invalid in the case when an error occurs since it can set the rate to a negative value. Fixes: 215f8aa095a1 ("clk: nuvoton: ma35d1-divider: convert from round_rate() to determine_rate()") Signed-off-by: Brian Masney Signed-off-by: Sasha Levin --- drivers/clk/nuvoton/clk-ma35d1-divider.c | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/drivers/clk/nuvoton/clk-ma35d1-divider.c b/drivers/clk/nuvoton/clk-ma35d1-divider.c index e39f53d5bf4578..e992e7c3034197 100644 --- a/drivers/clk/nuvoton/clk-ma35d1-divider.c +++ b/drivers/clk/nuvoton/clk-ma35d1-divider.c @@ -44,11 +44,8 @@ static int ma35d1_clkdiv_determine_rate(struct clk_hw *hw, { struct ma35d1_adc_clk_div *dclk = to_ma35d1_adc_clk_div(hw); - req->rate = divider_round_rate(hw, req->rate, &req->best_parent_rate, - dclk->table, dclk->width, - CLK_DIVIDER_ROUND_CLOSEST); - - return 0; + return divider_determine_rate(hw, req, dclk->table, dclk->width, + CLK_DIVIDER_ROUND_CLOSEST); } static int ma35d1_clkdiv_set_rate(struct clk_hw *hw, unsigned long rate, unsigned long parent_rate) From 73fbacf47588607acbe3c9a8375237f2e2a2569a Mon Sep 17 00:00:00 2001 From: Brian Masney Date: Thu, 8 Jan 2026 16:16:31 -0500 Subject: [PATCH 3687/3902] clk: nxp: lpc32xx: convert from divider_round_rate() to divider_determine_rate() [ Upstream commit af943663ccc266e6346e5645b13c0fca71d24395 ] The divider_round_rate() function is now deprecated, so let's migrate to divider_determine_rate() instead so that this deprecated API can be removed. Note that when the main function itself was migrated to use determine_rate, this was mistakenly converted to: req->rate = divider_round_rate(...) This is invalid in the case when an error occurs since it can set the rate to a negative value. Fixes: 0879768df240 ("clk: nxp: lpc32xx: convert from round_rate() to determine_rate()") Tested-by: Vladimir Zapolskiy Reviewed-by: Vladimir Zapolskiy Signed-off-by: Brian Masney Signed-off-by: Sasha Levin --- drivers/clk/nxp/clk-lpc32xx.c | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/drivers/clk/nxp/clk-lpc32xx.c b/drivers/clk/nxp/clk-lpc32xx.c index 23f980cf6a2b59..ae2fa5341a2e4f 100644 --- a/drivers/clk/nxp/clk-lpc32xx.c +++ b/drivers/clk/nxp/clk-lpc32xx.c @@ -975,10 +975,8 @@ static int clk_divider_determine_rate(struct clk_hw *hw, return 0; } - req->rate = divider_round_rate(hw, req->rate, &req->best_parent_rate, - divider->table, divider->width, divider->flags); - - return 0; + return divider_determine_rate(hw, req, divider->table, divider->width, + divider->flags); } static int clk_divider_set_rate(struct clk_hw *hw, unsigned long rate, From aab1749732515185028449bc8bb1d5d92eba3fb8 Mon Sep 17 00:00:00 2001 From: Brian Masney Date: Thu, 8 Jan 2026 16:16:35 -0500 Subject: [PATCH 3688/3902] clk: sophgo: sg2042-clkgen: convert from divider_round_rate() to divider_determine_rate() [ Upstream commit 77b04dc19693510ce8ed1c6eda5f5b833e208816 ] The divider_round_rate() function is now deprecated, so let's migrate to divider_determine_rate() instead so that this deprecated API can be removed. Note that when the main function itself was migrated to use determine_rate, this was mistakenly converted to: req->rate = divider_round_rate(...) This is invalid in the case when an error occurs since it can set the rate to a negative value. Note that this commit also removes a debugging message that's not really needed. Fixes: 9a3b6993613d ("clk: sophgo: sg2042-clkgen: convert from round_rate() to determine_rate()") Tested-by: Chen Wang Reviewed-by: Chen Wang Signed-off-by: Brian Masney Signed-off-by: Sasha Levin --- drivers/clk/sophgo/clk-sg2042-clkgen.c | 15 +++++---------- 1 file changed, 5 insertions(+), 10 deletions(-) diff --git a/drivers/clk/sophgo/clk-sg2042-clkgen.c b/drivers/clk/sophgo/clk-sg2042-clkgen.c index 683661b71787c9..9725ac4e050a4e 100644 --- a/drivers/clk/sophgo/clk-sg2042-clkgen.c +++ b/drivers/clk/sophgo/clk-sg2042-clkgen.c @@ -180,7 +180,6 @@ static int sg2042_clk_divider_determine_rate(struct clk_hw *hw, struct clk_rate_request *req) { struct sg2042_divider_clock *divider = to_sg2042_clk_divider(hw); - unsigned long ret_rate; u32 bestdiv; /* if read only, just return current value */ @@ -191,17 +190,13 @@ static int sg2042_clk_divider_determine_rate(struct clk_hw *hw, bestdiv = readl(divider->reg) >> divider->shift; bestdiv &= clk_div_mask(divider->width); } - ret_rate = DIV_ROUND_UP_ULL((u64)req->best_parent_rate, bestdiv); - } else { - ret_rate = divider_round_rate(hw, req->rate, &req->best_parent_rate, NULL, - divider->width, divider->div_flags); - } + req->rate = DIV_ROUND_UP_ULL((u64)req->best_parent_rate, bestdiv); - pr_debug("--> %s: divider_round_rate: val = %ld\n", - clk_hw_get_name(hw), ret_rate); - req->rate = ret_rate; + return 0; + } - return 0; + return divider_determine_rate(hw, req, NULL, divider->width, + divider->div_flags); } static int sg2042_clk_divider_set_rate(struct clk_hw *hw, From 141fd6e372781f7c49aadd0c0de2980cdb4f9ea7 Mon Sep 17 00:00:00 2001 From: Brian Masney Date: Thu, 8 Jan 2026 16:16:36 -0500 Subject: [PATCH 3689/3902] clk: sprd: div: convert from divider_round_rate() to divider_determine_rate() [ Upstream commit f78fb9422980ceeb340fa3a2e370ae8845798ec7 ] The divider_round_rate() function is now deprecated, so let's migrate to divider_determine_rate() instead so that this deprecated API can be removed. Note that when the main function itself was migrated to use determine_rate, this was mistakenly converted to: req->rate = divider_round_rate(...) This is invalid in the case when an error occurs since it can set the rate to a negative value. Fixes: deb4740a5ff8 ("clk: sprd: div: convert from round_rate() to determine_rate()") Signed-off-by: Brian Masney Signed-off-by: Sasha Levin --- drivers/clk/sprd/div.c | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/drivers/clk/sprd/div.c b/drivers/clk/sprd/div.c index 01342388196800..cd57163a7204c0 100644 --- a/drivers/clk/sprd/div.c +++ b/drivers/clk/sprd/div.c @@ -14,11 +14,7 @@ static int sprd_div_determine_rate(struct clk_hw *hw, { struct sprd_div *cd = hw_to_sprd_div(hw); - req->rate = divider_round_rate(&cd->common.hw, req->rate, - &req->best_parent_rate, - NULL, cd->div.width, 0); - - return 0; + return divider_determine_rate(&cd->common.hw, req, NULL, cd->div.width, 0); } unsigned long sprd_div_helper_recalc_rate(struct sprd_clk_common *common, From 29781f258a9f1acad589c7caf0a6e3bc605787a0 Mon Sep 17 00:00:00 2001 From: Brian Masney Date: Thu, 8 Jan 2026 16:16:37 -0500 Subject: [PATCH 3690/3902] clk: stm32: stm32-core: convert from divider_ro_round_rate() to divider_ro_determine_rate() [ Upstream commit 6587c9dacc89ad7014bf601fe851955429f13230 ] The divider_ro_round_rate() function is now deprecated, so let's migrate to divider_ro_determine_rate() instead so that this deprecated API can be removed. Note that when the main function itself was migrated to use determine_rate, this was mistakenly converted to: req->rate = divider_round_rate(...) This is invalid in the case when an error occurs since it can set the rate to a negative value. Fixes: cd1cb38836c0 ("clk: stm32: stm32-core: convert from round_rate() to determine_rate()") Signed-off-by: Brian Masney Signed-off-by: Sasha Levin --- drivers/clk/stm32/clk-stm32-core.c | 22 +++++++--------------- 1 file changed, 7 insertions(+), 15 deletions(-) diff --git a/drivers/clk/stm32/clk-stm32-core.c b/drivers/clk/stm32/clk-stm32-core.c index 72825b9c36a4d3..b95b9c591fda7d 100644 --- a/drivers/clk/stm32/clk-stm32-core.c +++ b/drivers/clk/stm32/clk-stm32-core.c @@ -369,13 +369,10 @@ static int clk_stm32_divider_determine_rate(struct clk_hw *hw, val = readl(div->base + divider->offset) >> divider->shift; val &= clk_div_mask(divider->width); - req->rate = divider_ro_round_rate(hw, req->rate, - &req->best_parent_rate, - divider->table, - divider->width, - divider->flags, val); - - return 0; + return divider_ro_determine_rate(hw, req, + divider->table, + divider->width, + divider->flags, val); } req->rate = divider_round_rate_parent(hw, clk_hw_get_parent(hw), @@ -455,14 +452,9 @@ static int clk_stm32_composite_determine_rate(struct clk_hw *hw, val = readl(composite->base + divider->offset) >> divider->shift; val &= clk_div_mask(divider->width); - rate = divider_ro_round_rate(hw, req->rate, &req->best_parent_rate, - divider->table, divider->width, divider->flags, - val); - if (rate < 0) - return rate; - - req->rate = rate; - return 0; + return divider_ro_determine_rate(hw, req, divider->table, + divider->width, divider->flags, + val); } rate = divider_round_rate_parent(hw, clk_hw_get_parent(hw), From c632744ff0af41461f04e7b2ce81e6afd06d4de3 Mon Sep 17 00:00:00 2001 From: Brian Masney Date: Thu, 8 Jan 2026 16:16:38 -0500 Subject: [PATCH 3691/3902] clk: stm32: stm32-core: convert from divider_round_rate_parent() to divider_determine_rate() [ Upstream commit 2532795a6d6bb9791d713ffa9d9433f293b45b14 ] The divider_round_rate_parent() function is now deprecated, so let's migrate to divider_determine_rate() instead so that this deprecated API can be removed. Note that when the main function itself was migrated to use determine_rate, this was mistakenly converted to: req->rate = divider_round_rate(...) This is invalid in the case when an error occurs since it can set the rate to a negative value. Fixes: cd1cb38836c0 ("clk: stm32: stm32-core: convert from round_rate() to determine_rate()") Signed-off-by: Brian Masney Signed-off-by: Sasha Levin --- drivers/clk/stm32/clk-stm32-core.c | 20 ++++---------------- 1 file changed, 4 insertions(+), 16 deletions(-) diff --git a/drivers/clk/stm32/clk-stm32-core.c b/drivers/clk/stm32/clk-stm32-core.c index b95b9c591fda7d..e921c25a929c30 100644 --- a/drivers/clk/stm32/clk-stm32-core.c +++ b/drivers/clk/stm32/clk-stm32-core.c @@ -375,13 +375,8 @@ static int clk_stm32_divider_determine_rate(struct clk_hw *hw, divider->flags, val); } - req->rate = divider_round_rate_parent(hw, clk_hw_get_parent(hw), - req->rate, - &req->best_parent_rate, - divider->table, - divider->width, divider->flags); - - return 0; + return divider_determine_rate(hw, req, divider->table, divider->width, + divider->flags); } static unsigned long clk_stm32_divider_recalc_rate(struct clk_hw *hw, @@ -438,7 +433,6 @@ static int clk_stm32_composite_determine_rate(struct clk_hw *hw, { struct clk_stm32_composite *composite = to_clk_stm32_composite(hw); const struct stm32_div_cfg *divider; - long rate; if (composite->div_id == NO_STM32_DIV) return 0; @@ -457,14 +451,8 @@ static int clk_stm32_composite_determine_rate(struct clk_hw *hw, val); } - rate = divider_round_rate_parent(hw, clk_hw_get_parent(hw), - req->rate, &req->best_parent_rate, - divider->table, divider->width, divider->flags); - if (rate < 0) - return rate; - - req->rate = rate; - return 0; + return divider_determine_rate(hw, req, divider->table, divider->width, + divider->flags); } static u8 clk_stm32_composite_get_parent(struct clk_hw *hw) From ccab4798a64fdec9de055d8f38b735e8e7c2a8b3 Mon Sep 17 00:00:00 2001 From: Brian Masney Date: Thu, 8 Jan 2026 16:16:39 -0500 Subject: [PATCH 3692/3902] clk: versaclock3: convert from divider_round_rate() to divider_determine_rate() [ Upstream commit 56c1cfb488cc17944c200edad96191a70a3783ba ] The divider_round_rate() function is now deprecated, so let's migrate to divider_determine_rate() instead so that this deprecated API can be removed. Note that when the main function itself was migrated to use determine_rate, this was mistakenly converted to: req->rate = divider_round_rate(...) This is invalid in the case when an error occurs since it can set the rate to a negative value. Fixes: 9e3372b2ebac ("clk: versaclock3: convert from round_rate() to determine_rate()") Signed-off-by: Brian Masney Signed-off-by: Sasha Levin --- drivers/clk/clk-versaclock3.c | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/drivers/clk/clk-versaclock3.c b/drivers/clk/clk-versaclock3.c index 1849863dbd673f..27b6cf70f3ae1b 100644 --- a/drivers/clk/clk-versaclock3.c +++ b/drivers/clk/clk-versaclock3.c @@ -523,11 +523,8 @@ static int vc3_div_determine_rate(struct clk_hw *hw, return 0; } - req->rate = divider_round_rate(hw, req->rate, &req->best_parent_rate, - div_data->table, - div_data->width, div_data->flags); - - return 0; + return divider_determine_rate(hw, req, div_data->table, div_data->width, + div_data->flags); } static int vc3_div_set_rate(struct clk_hw *hw, unsigned long rate, From df502559b124935f5589087fd7eacbf27ac1c136 Mon Sep 17 00:00:00 2001 From: Brian Masney Date: Thu, 8 Jan 2026 16:16:40 -0500 Subject: [PATCH 3693/3902] clk: x86: cgu: convert from divider_round_rate() to divider_determine_rate() [ Upstream commit bb1b0e63dbbd7150324cb4d6aef7854dbe26a617 ] The divider_round_rate() function is now deprecated, so let's migrate to divider_determine_rate() instead so that this deprecated API can be removed. Note that when the main function itself was migrated to use determine_rate, this was mistakenly converted to: req->rate = divider_round_rate(...) This is invalid in the case when an error occurs since it can set the rate to a negative value. Fixes: f7a6bed91a19 ("clk: x86: cgu: convert from round_rate() to determine_rate()") Signed-off-by: Brian Masney Signed-off-by: Sasha Levin --- drivers/clk/x86/clk-cgu.c | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/drivers/clk/x86/clk-cgu.c b/drivers/clk/x86/clk-cgu.c index d099667355f8d8..92ee05d75af2b2 100644 --- a/drivers/clk/x86/clk-cgu.c +++ b/drivers/clk/x86/clk-cgu.c @@ -137,10 +137,8 @@ static int lgm_clk_divider_determine_rate(struct clk_hw *hw, { struct lgm_clk_divider *divider = to_lgm_clk_divider(hw); - req->rate = divider_round_rate(hw, req->rate, &req->best_parent_rate, divider->table, - divider->width, divider->flags); - - return 0; + return divider_determine_rate(hw, req, divider->table, divider->width, + divider->flags); } static int From 053490ddace34432e457df932a8f97b849861a49 Mon Sep 17 00:00:00 2001 From: Brian Masney Date: Thu, 8 Jan 2026 16:16:41 -0500 Subject: [PATCH 3694/3902] clk: zynqmp: divider: convert from divider_round_rate() to divider_determine_rate() [ Upstream commit 30a807808c69a1907001ffb79289237a2ee97cfa ] The divider_round_rate() function is now deprecated, so let's migrate to divider_determine_rate() instead so that this deprecated API can be removed. Note that when the main function itself was migrated to use determine_rate, this was mistakenly converted to: req->rate = divider_round_rate(...) This is invalid in the case when an error occurs since it can set the rate to a negative value. Fixes: 0f9cf96a01fd ("clk: zynqmp: divider: convert from round_rate() to determine_rate()") Signed-off-by: Brian Masney Signed-off-by: Sasha Levin --- drivers/clk/zynqmp/divider.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/drivers/clk/zynqmp/divider.c b/drivers/clk/zynqmp/divider.c index c824eeacd8ebd4..de6f478d527d89 100644 --- a/drivers/clk/zynqmp/divider.c +++ b/drivers/clk/zynqmp/divider.c @@ -151,8 +151,9 @@ static int zynqmp_clk_divider_determine_rate(struct clk_hw *hw, width = fls(divider->max_div); - req->rate = divider_round_rate(hw, req->rate, &req->best_parent_rate, - NULL, width, divider->flags); + ret = divider_determine_rate(hw, req, NULL, width, divider->flags); + if (ret != 0) + return ret; if (divider->is_frac && (clk_hw_get_flags(hw) & CLK_SET_RATE_PARENT) && (req->rate % req->best_parent_rate)) From 1debd9ba7eb18af8fb63dc93517c6bbcab0e31ee Mon Sep 17 00:00:00 2001 From: Sjoerd Simons Date: Tue, 23 Dec 2025 12:05:17 +0100 Subject: [PATCH 3695/3902] clk: mediatek: Drop __initconst from gates [ Upstream commit 871afb43e41ad4e8246438de495a939cd0f8113c ] Since commit 8ceff24a754a ("clk: mediatek: clk-gate: Refactor mtk_clk_register_gate to use mtk_gate struct") the mtk_gate structs are no longer just used for initialization/registration, but also at runtime. So drop __initconst annotations. Fixes: 8ceff24a754a ("clk: mediatek: clk-gate: Refactor mtk_clk_register_gate to use mtk_gate struct") Signed-off-by: Sjoerd Simons Reviewed-by: AngeloGioacchino Del Regno Reviewed-by: Laura Nao Signed-off-by: Stephen Boyd Signed-off-by: Sasha Levin --- drivers/clk/mediatek/clk-mt7981-eth.c | 6 +++--- drivers/clk/mediatek/clk-mt8516.c | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/drivers/clk/mediatek/clk-mt7981-eth.c b/drivers/clk/mediatek/clk-mt7981-eth.c index 906aec9ddff54b..0655ebb6c561f6 100644 --- a/drivers/clk/mediatek/clk-mt7981-eth.c +++ b/drivers/clk/mediatek/clk-mt7981-eth.c @@ -31,7 +31,7 @@ static const struct mtk_gate_regs sgmii0_cg_regs = { .ops = &mtk_clk_gate_ops_no_setclr_inv, \ } -static const struct mtk_gate sgmii0_clks[] __initconst = { +static const struct mtk_gate sgmii0_clks[] = { GATE_SGMII0(CLK_SGM0_TX_EN, "sgm0_tx_en", "usb_tx250m", 2), GATE_SGMII0(CLK_SGM0_RX_EN, "sgm0_rx_en", "usb_eq_rx250m", 3), GATE_SGMII0(CLK_SGM0_CK0_EN, "sgm0_ck0_en", "usb_ln0", 4), @@ -53,7 +53,7 @@ static const struct mtk_gate_regs sgmii1_cg_regs = { .ops = &mtk_clk_gate_ops_no_setclr_inv, \ } -static const struct mtk_gate sgmii1_clks[] __initconst = { +static const struct mtk_gate sgmii1_clks[] = { GATE_SGMII1(CLK_SGM1_TX_EN, "sgm1_tx_en", "usb_tx250m", 2), GATE_SGMII1(CLK_SGM1_RX_EN, "sgm1_rx_en", "usb_eq_rx250m", 3), GATE_SGMII1(CLK_SGM1_CK1_EN, "sgm1_ck1_en", "usb_ln0", 4), @@ -75,7 +75,7 @@ static const struct mtk_gate_regs eth_cg_regs = { .ops = &mtk_clk_gate_ops_no_setclr_inv, \ } -static const struct mtk_gate eth_clks[] __initconst = { +static const struct mtk_gate eth_clks[] = { GATE_ETH(CLK_ETH_FE_EN, "eth_fe_en", "netsys_2x", 6), GATE_ETH(CLK_ETH_GP2_EN, "eth_gp2_en", "sgm_325m", 7), GATE_ETH(CLK_ETH_GP1_EN, "eth_gp1_en", "sgm_325m", 8), diff --git a/drivers/clk/mediatek/clk-mt8516.c b/drivers/clk/mediatek/clk-mt8516.c index 21eb052b0a539c..342a59019fea9b 100644 --- a/drivers/clk/mediatek/clk-mt8516.c +++ b/drivers/clk/mediatek/clk-mt8516.c @@ -544,7 +544,7 @@ static const struct mtk_gate_regs top5_cg_regs = { #define GATE_TOP5(_id, _name, _parent, _shift) \ GATE_MTK(_id, _name, _parent, &top5_cg_regs, _shift, &mtk_clk_gate_ops_setclr) -static const struct mtk_gate top_clks[] __initconst = { +static const struct mtk_gate top_clks[] = { /* TOP1 */ GATE_TOP1(CLK_TOP_THEM, "them", "ahb_infra_sel", 1), GATE_TOP1(CLK_TOP_APDMA, "apdma", "ahb_infra_sel", 2), From 6e5379c3b54578d8194ea9de06ab5b0978af97bc Mon Sep 17 00:00:00 2001 From: Nicolas Frattaroli Date: Mon, 15 Dec 2025 11:24:02 +0100 Subject: [PATCH 3696/3902] clk: mediatek: Add mfg_eb as parent to mt8196 mfgpll clocks [ Upstream commit 19024c9980c331908de0680283d572b80308654e ] All the MFGPLL require MFG_EB to be on for any operation on them, and they only tick when MFG_EB is on as well, therefore making this a parent-child relationship. This dependency wasn't clear during the initial upstreaming of these clock controllers, as it only made itself known when I could observe the effects of the clock by bringing up a different piece of hardware. Add a new PLL_PARENT_EN flag to mediatek's clk-pll.h, and check for it when initialising the pll to then translate it into the actual CLK_OPS_PARENT_ENABLE flag. Then add the mfg_eb parent to the mfgpll clocks, and set the new PLL_PARENT_EN flag. Fixes: 03dc02f8c7dc ("clk: mediatek: Add MT8196 mfg clock support") Reviewed-by: AngeloGioacchino Del Regno Signed-off-by: Nicolas Frattaroli Reviewed-by: AngeloGioacchino Del Regno Signed-off-by: Stephen Boyd Signed-off-by: Sasha Levin --- drivers/clk/mediatek/clk-mt8196-mfg.c | 13 +++++++------ drivers/clk/mediatek/clk-pll.c | 3 +++ drivers/clk/mediatek/clk-pll.h | 1 + 3 files changed, 11 insertions(+), 6 deletions(-) diff --git a/drivers/clk/mediatek/clk-mt8196-mfg.c b/drivers/clk/mediatek/clk-mt8196-mfg.c index ae1eb9de79ae29..f40795b47ff1fd 100644 --- a/drivers/clk/mediatek/clk-mt8196-mfg.c +++ b/drivers/clk/mediatek/clk-mt8196-mfg.c @@ -58,24 +58,25 @@ .pcw_shift = _pcw_shift, \ .pcwbits = _pcwbits, \ .pcwibits = MT8196_INTEGER_BITS, \ + .parent_name = "mfg_eb", \ } static const struct mtk_pll_data mfg_ao_plls[] = { - PLL(CLK_MFG_AO_MFGPLL, "mfgpll", MFGPLL_CON0, MFGPLL_CON0, 0, 0, 0, - BIT(0), MFGPLL_CON1, 24, 0, 0, 0, + PLL(CLK_MFG_AO_MFGPLL, "mfgpll", MFGPLL_CON0, MFGPLL_CON0, 0, 0, + PLL_PARENT_EN, BIT(0), MFGPLL_CON1, 24, 0, 0, 0, MFGPLL_CON1, 0, 22), }; static const struct mtk_pll_data mfgsc0_ao_plls[] = { PLL(CLK_MFGSC0_AO_MFGPLL_SC0, "mfgpll-sc0", MFGPLL_SC0_CON0, - MFGPLL_SC0_CON0, 0, 0, 0, BIT(0), MFGPLL_SC0_CON1, 24, 0, 0, 0, - MFGPLL_SC0_CON1, 0, 22), + MFGPLL_SC0_CON0, 0, 0, PLL_PARENT_EN, BIT(0), MFGPLL_SC0_CON1, 24, + 0, 0, 0, MFGPLL_SC0_CON1, 0, 22), }; static const struct mtk_pll_data mfgsc1_ao_plls[] = { PLL(CLK_MFGSC1_AO_MFGPLL_SC1, "mfgpll-sc1", MFGPLL_SC1_CON0, - MFGPLL_SC1_CON0, 0, 0, 0, BIT(0), MFGPLL_SC1_CON1, 24, 0, 0, 0, - MFGPLL_SC1_CON1, 0, 22), + MFGPLL_SC1_CON0, 0, 0, PLL_PARENT_EN, BIT(0), MFGPLL_SC1_CON1, 24, + 0, 0, 0, MFGPLL_SC1_CON1, 0, 22), }; static const struct of_device_id of_match_clk_mt8196_mfg[] = { diff --git a/drivers/clk/mediatek/clk-pll.c b/drivers/clk/mediatek/clk-pll.c index cd2b6ce551c6b0..de3eb02670554f 100644 --- a/drivers/clk/mediatek/clk-pll.c +++ b/drivers/clk/mediatek/clk-pll.c @@ -358,6 +358,9 @@ struct clk_hw *mtk_clk_register_pll_ops(struct mtk_clk_pll *pll, init.name = data->name; init.flags = (data->flags & PLL_AO) ? CLK_IS_CRITICAL : 0; + if (data->flags & PLL_PARENT_EN) + init.flags |= CLK_OPS_PARENT_ENABLE; + init.ops = pll_ops; if (data->parent_name) init.parent_names = &data->parent_name; diff --git a/drivers/clk/mediatek/clk-pll.h b/drivers/clk/mediatek/clk-pll.h index d71c150ce83e4b..de5a8fb7cbcfe9 100644 --- a/drivers/clk/mediatek/clk-pll.h +++ b/drivers/clk/mediatek/clk-pll.h @@ -21,6 +21,7 @@ struct mtk_pll_div_table { #define HAVE_RST_BAR BIT(0) #define PLL_AO BIT(1) +#define PLL_PARENT_EN BIT(2) #define POSTDIV_MASK GENMASK(2, 0) struct mtk_pll_data { From 71e5335fcec0807e04f00c26789afef14b198148 Mon Sep 17 00:00:00 2001 From: Haotian Zhang Date: Sun, 23 Nov 2025 23:43:15 +0800 Subject: [PATCH 3697/3902] clk: mediatek: Fix error handling in runtime PM setup [ Upstream commit aa2ad19210a6a444111bce55e8b69579f29318fb ] devm_pm_runtime_enable() can fail due to memory allocation. The current code ignores its return value, and when pm_runtime_resume_and_get() fails, it returns directly without unmapping the shared_io region. Add error handling for devm_pm_runtime_enable(). Reorder cleanup labels to properly unmap shared_io on pm_runtime_resume_and_get() failure. Fixes: 2f7b1d8b5505 ("clk: mediatek: Do a runtime PM get on controllers during probe") Signed-off-by: Haotian Zhang Reviewed-by: AngeloGioacchino Del Regno Signed-off-by: Stephen Boyd Signed-off-by: Sasha Levin --- drivers/clk/mediatek/clk-mtk.c | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/drivers/clk/mediatek/clk-mtk.c b/drivers/clk/mediatek/clk-mtk.c index 19cd27941747aa..deafe55a96cb1d 100644 --- a/drivers/clk/mediatek/clk-mtk.c +++ b/drivers/clk/mediatek/clk-mtk.c @@ -497,14 +497,16 @@ static int __mtk_clk_simple_probe(struct platform_device *pdev, if (mcd->need_runtime_pm) { - devm_pm_runtime_enable(&pdev->dev); + r = devm_pm_runtime_enable(&pdev->dev); + if (r) + goto unmap_io; /* * Do a pm_runtime_resume_and_get() to workaround a possible * deadlock between clk_register() and the genpd framework. */ r = pm_runtime_resume_and_get(&pdev->dev); if (r) - return r; + goto unmap_io; } /* Calculate how many clk_hw_onecell_data entries to allocate */ @@ -618,11 +620,11 @@ static int __mtk_clk_simple_probe(struct platform_device *pdev, free_data: mtk_free_clk_data(clk_data); free_base: - if (mcd->shared_io && base) - iounmap(base); - if (mcd->need_runtime_pm) pm_runtime_put(&pdev->dev); +unmap_io: + if (mcd->shared_io && base) + iounmap(base); return r; } From d53b019a60031dbd89c9dc5431f06fb2193da7e1 Mon Sep 17 00:00:00 2001 From: Krzysztof Kozlowski Date: Wed, 24 Dec 2025 12:42:11 +0100 Subject: [PATCH 3698/3902] clk: zynqmp: divider: Fix zynqmp_clk_divider_determine_rate kerneldoc [ Upstream commit 1b8773864904c7a25e45f1b12ab505bdb7e06568 ] After renaming round_rate->determine, kerneldoc does not match anymore, causing W=1 warnings: Warning: drivers/clk/zynqmp/divider.c:122 function parameter 'req' not described in 'zynqmp_clk_divider_determine_rate' Warning: drivers/clk/zynqmp/divider.c:122 expecting prototype for zynqmp_clk_divider_round_rate(). Prototype was for zynqmp_clk_divider_determine_rate() instead Fixes: 0f9cf96a01fd ("clk: zynqmp: divider: convert from round_rate() to determine_rate()") Signed-off-by: Krzysztof Kozlowski Reviewed-by: Brian Masney Signed-off-by: Stephen Boyd Signed-off-by: Sasha Levin --- drivers/clk/zynqmp/divider.c | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/drivers/clk/zynqmp/divider.c b/drivers/clk/zynqmp/divider.c index de6f478d527d89..984e577ea67115 100644 --- a/drivers/clk/zynqmp/divider.c +++ b/drivers/clk/zynqmp/divider.c @@ -111,10 +111,9 @@ static unsigned long zynqmp_clk_divider_recalc_rate(struct clk_hw *hw, } /** - * zynqmp_clk_divider_round_rate() - Round rate of divider clock + * zynqmp_clk_divider_determine_rate() - Determine rate of divider clock * @hw: handle between common and hardware-specific interfaces - * @rate: rate of clock to be set - * @prate: rate of parent clock + * @req: rate of clock to be set * * Return: 0 on success else error+reason */ From 52e4ac51e3fa7bff9515173aab4aeb41bdd7eae6 Mon Sep 17 00:00:00 2001 From: Krzysztof Kozlowski Date: Wed, 24 Dec 2025 12:42:12 +0100 Subject: [PATCH 3699/3902] clk: zynqmp: pll: Fix zynqmp_clk_divider_determine_rate kerneldoc [ Upstream commit 750e0e0a1652530618d2c07697618e705bc5061b ] After renaming round_rate->determine, kerneldoc does not match anymore, causing W=1 warnings: pll.c:102 function parameter 'req' not described in 'zynqmp_pll_determine_rate' pll.c:102 expecting prototype for zynqmp_pll_round_rate(). Prototype was for zynqmp_pll_determine_rate() instead Fixes: 193650c7a873 ("clk: zynqmp: pll: convert from round_rate() to determine_rate()") Signed-off-by: Krzysztof Kozlowski Reviewed-by: Brian Masney Signed-off-by: Stephen Boyd Signed-off-by: Sasha Levin --- drivers/clk/zynqmp/pll.c | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/drivers/clk/zynqmp/pll.c b/drivers/clk/zynqmp/pll.c index 630a3936c97c34..6bc2c3934f564f 100644 --- a/drivers/clk/zynqmp/pll.c +++ b/drivers/clk/zynqmp/pll.c @@ -91,10 +91,9 @@ static inline void zynqmp_pll_set_mode(struct clk_hw *hw, bool on) } /** - * zynqmp_pll_round_rate() - Round a clock frequency + * zynqmp_pll_determine_rate() - Round a clock frequency * @hw: Handle between common and hardware-specific interfaces - * @rate: Desired clock frequency - * @prate: Clock frequency of parent clock + * @req: Desired clock frequency * * Return: Frequency closest to @rate the hardware can generate */ From 86e6547ac6c4d1f3dcc896f7d0bc13f140f40038 Mon Sep 17 00:00:00 2001 From: Nicolas Frattaroli Date: Mon, 24 Nov 2025 12:07:01 +0100 Subject: [PATCH 3700/3902] interconnect: mediatek: Don't hijack parent device [ Upstream commit 510f8214440c553e81774c5822437ccf154e9e38 ] If the intention is that users of the interconnect declare their relationship to the child icc_emi node of the dvfsrc controller, then this code never worked. That's because it uses the parent dvfsrc device as the device it passes to the interconnect core framework, which means all the OF parsing is broken. Use the actual device instead, and pass the dvfsrc parent into the dvfsrc calls. Fixes: b45293799f75 ("interconnect: mediatek: Add MediaTek MT8183/8195 EMI Interconnect driver") Reviewed-by: AngeloGioacchino Del Regno Signed-off-by: Nicolas Frattaroli Link: https://lore.kernel.org/r/20251124-mt8196-dvfsrc-v2-12-d9c1334db9f3@collabora.com Signed-off-by: Georgi Djakov Signed-off-by: Sasha Levin --- drivers/interconnect/mediatek/icc-emi.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/interconnect/mediatek/icc-emi.c b/drivers/interconnect/mediatek/icc-emi.c index 7da740b5fa8d62..182aa2b0623af3 100644 --- a/drivers/interconnect/mediatek/icc-emi.c +++ b/drivers/interconnect/mediatek/icc-emi.c @@ -40,7 +40,7 @@ static int mtk_emi_icc_set(struct icc_node *src, struct icc_node *dst) if (unlikely(!src->provider)) return -EINVAL; - dev = src->provider->dev; + dev = src->provider->dev->parent; switch (node->ep) { case 0: @@ -97,7 +97,7 @@ int mtk_emi_icc_probe(struct platform_device *pdev) if (!data) return -ENOMEM; - provider->dev = pdev->dev.parent; + provider->dev = dev; provider->set = mtk_emi_icc_set; provider->aggregate = mtk_emi_icc_aggregate; provider->xlate = of_icc_xlate_onecell; From 2f0d9a5d552c671b36db4f149d7df22705fd3cfe Mon Sep 17 00:00:00 2001 From: Nicolas Frattaroli Date: Mon, 24 Nov 2025 12:07:02 +0100 Subject: [PATCH 3701/3902] interconnect: mediatek: Aggregate bandwidth with saturating add [ Upstream commit 6ffd02b82243d9907b5f5d2c7a2fc6a62669eece ] By using a regular non-overflow-checking add, the MediaTek icc-emi driver will happy wrap at U32_MAX + 1 to 0. As it's common for the interconnect core to fill in INT_MAX values, this is not a hypothetical situation, but something that actually happens in regular use. This would be pretty disasterous if anything used this driver. Replace the addition with an overflow-checked addition from overflow.h, and saturate to U32_MAX if an overflow is detected. Fixes: b45293799f75 ("interconnect: mediatek: Add MediaTek MT8183/8195 EMI Interconnect driver") Reviewed-by: AngeloGioacchino Del Regno Signed-off-by: Nicolas Frattaroli Link: https://lore.kernel.org/r/20251124-mt8196-dvfsrc-v2-13-d9c1334db9f3@collabora.com Signed-off-by: Georgi Djakov Signed-off-by: Sasha Levin --- drivers/interconnect/mediatek/icc-emi.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/drivers/interconnect/mediatek/icc-emi.c b/drivers/interconnect/mediatek/icc-emi.c index 182aa2b0623af3..dfa3a9cd939983 100644 --- a/drivers/interconnect/mediatek/icc-emi.c +++ b/drivers/interconnect/mediatek/icc-emi.c @@ -12,6 +12,7 @@ #include #include #include +#include #include #include @@ -22,7 +23,9 @@ static int mtk_emi_icc_aggregate(struct icc_node *node, u32 tag, u32 avg_bw, { struct mtk_icc_node *in = node->data; - *agg_avg += avg_bw; + if (check_add_overflow(*agg_avg, avg_bw, agg_avg)) + *agg_avg = U32_MAX; + *agg_peak = max_t(u32, *agg_peak, peak_bw); in->sum_avg = *agg_avg; From b0ce68420da60030b12258e663420b4c6199b6de Mon Sep 17 00:00:00 2001 From: AngeloGioacchino Del Regno Date: Thu, 13 Nov 2025 13:22:26 +0100 Subject: [PATCH 3702/3902] dmaengine: mediatek: uart-apdma: Fix above 4G addressing TX/RX [ Upstream commit 58ab9d7b6651d21e1cff1777529f2d3dd0b4e851 ] The VFF_4G_SUPPORT register is named differently in datasheets, and its name is "VFF_ADDR2"; was this named correctly from the beginning it would've been clearer that there was a mistake in the programming sequence. This register is supposed to hold the high bits to support the DMA addressing above 4G (so, more than 32 bits) and not a bit to "enable" the support for VFF 4G. Fix the name of this register, and also fix its usage by writing the upper 32 bits of the dma_addr_t on it when the SoC supports such feature. Fixes: 9135408c3ace ("dmaengine: mediatek: Add MediaTek UART APDMA support") Signed-off-by: AngeloGioacchino Del Regno Link: https://patch.msgid.link/20251113122229.23998-6-angelogioacchino.delregno@collabora.com Signed-off-by: Vinod Koul Signed-off-by: Sasha Levin --- drivers/dma/mediatek/mtk-uart-apdma.c | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/drivers/dma/mediatek/mtk-uart-apdma.c b/drivers/dma/mediatek/mtk-uart-apdma.c index 08e15177427b94..96c18c815f1df6 100644 --- a/drivers/dma/mediatek/mtk-uart-apdma.c +++ b/drivers/dma/mediatek/mtk-uart-apdma.c @@ -41,7 +41,7 @@ #define VFF_STOP_CLR_B 0 #define VFF_EN_CLR_B 0 #define VFF_INT_EN_CLR_B 0 -#define VFF_4G_SUPPORT_CLR_B 0 +#define VFF_ADDR2_CLR_B 0 /* * interrupt trigger level for tx @@ -72,7 +72,7 @@ /* TX: the buffer size SW can write. RX: the buffer size HW can write. */ #define VFF_LEFT_SIZE 0x40 #define VFF_DEBUG_STATUS 0x50 -#define VFF_4G_SUPPORT 0x54 +#define VFF_ADDR2 0x54 struct mtk_uart_apdmadev { struct dma_device ddev; @@ -149,7 +149,7 @@ static void mtk_uart_apdma_start_tx(struct mtk_chan *c) mtk_uart_apdma_write(c, VFF_INT_FLAG, VFF_TX_INT_CLR_B); if (mtkd->support_33bits) - mtk_uart_apdma_write(c, VFF_4G_SUPPORT, VFF_4G_EN_B); + mtk_uart_apdma_write(c, VFF_ADDR2, upper_32_bits(d->addr)); } mtk_uart_apdma_write(c, VFF_EN, VFF_EN_B); @@ -192,7 +192,7 @@ static void mtk_uart_apdma_start_rx(struct mtk_chan *c) mtk_uart_apdma_write(c, VFF_INT_FLAG, VFF_RX_INT_CLR_B); if (mtkd->support_33bits) - mtk_uart_apdma_write(c, VFF_4G_SUPPORT, VFF_4G_EN_B); + mtk_uart_apdma_write(c, VFF_ADDR2, upper_32_bits(d->addr)); } mtk_uart_apdma_write(c, VFF_INT_EN, VFF_RX_INT_EN_B); @@ -298,7 +298,7 @@ static int mtk_uart_apdma_alloc_chan_resources(struct dma_chan *chan) } if (mtkd->support_33bits) - mtk_uart_apdma_write(c, VFF_4G_SUPPORT, VFF_4G_SUPPORT_CLR_B); + mtk_uart_apdma_write(c, VFF_ADDR2, VFF_ADDR2_CLR_B); err_pm: pm_runtime_put_noidle(mtkd->ddev.dev); From dd1e96fd9f60a9f20757ceaa6bffd0d7fdcef858 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nuno=20S=C3=A1?= Date: Tue, 4 Nov 2025 16:22:25 +0000 Subject: [PATCH 3703/3902] dma: dma-axi-dmac: fix SW cyclic transfers MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [ Upstream commit 9bd257181fd5c996d922e9991500ad27987cfbf4 ] If 'hw_cyclic' is false we should still be able to do cyclic transfers in "software". That was not working for the case where 'desc->num_sgs' is 1 because 'chan->next_desc' is never set with the current desc which means that the cyclic transfer only runs once and in the next SOT interrupt we do nothing since vchan_next_desc() will return NULL. Fix it by setting 'chan->next_desc' as soon as we get a new desc via vchan_next_desc(). Fixes: 0e3b67b348b8 ("dmaengine: Add support for the Analog Devices AXI-DMAC DMA controller") Signed-off-by: Nuno Sá base-commit: 398035178503bf662281bbffb4bebce1460a4bc5 change-id: 20251104-axi-dmac-fixes-and-improvs-e3ad512a329c Acked-by: Michael Hennerich Link: https://patch.msgid.link/20251104-axi-dmac-fixes-and-improvs-v1-1-3e6fd9328f72@analog.com Signed-off-by: Vinod Koul Signed-off-by: Sasha Levin --- drivers/dma/dma-axi-dmac.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/drivers/dma/dma-axi-dmac.c b/drivers/dma/dma-axi-dmac.c index 5b06b0dc67ee12..e22639822045f2 100644 --- a/drivers/dma/dma-axi-dmac.c +++ b/drivers/dma/dma-axi-dmac.c @@ -247,6 +247,7 @@ static void axi_dmac_start_transfer(struct axi_dmac_chan *chan) return; list_move_tail(&vdesc->node, &chan->active_descs); desc = to_axi_dmac_desc(vdesc); + chan->next_desc = desc; } sg = &desc->sg[desc->num_submitted]; @@ -265,8 +266,6 @@ static void axi_dmac_start_transfer(struct axi_dmac_chan *chan) else chan->next_desc = NULL; flags |= AXI_DMAC_FLAG_LAST; - } else { - chan->next_desc = desc; } sg->hw->id = axi_dmac_read(dmac, AXI_DMAC_REG_TRANSFER_ID); From ccc29a067399e911e1f0e8cc68def95d0f7164b0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nuno=20S=C3=A1?= Date: Tue, 4 Nov 2025 16:22:26 +0000 Subject: [PATCH 3704/3902] dma: dma-axi-dmac: fix HW scatter-gather not looking at the queue MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [ Upstream commit bbcbafb99df41a1d81403eb4f5bb443b38228b57 ] For HW scatter gather transfers we still need to look for the queue. The HW is capable of queueing 3 concurrent transfers and if we try more than that we'll get the submit queue full and should return. Otherwise, if we go ahead and program the new transfer, we end up discarding it. Fixes: e97dc7435972 ("dmaengine: axi-dmac: Add support for scatter-gather transfers") Signed-off-by: Nuno Sá base-commit: 398035178503bf662281bbffb4bebce1460a4bc5 change-id: 20251104-axi-dmac-fixes-and-improvs-e3ad512a329c Acked-by: Michael Hennerich Link: https://patch.msgid.link/20251104-axi-dmac-fixes-and-improvs-v1-2-3e6fd9328f72@analog.com Signed-off-by: Vinod Koul Signed-off-by: Sasha Levin --- drivers/dma/dma-axi-dmac.c | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/drivers/dma/dma-axi-dmac.c b/drivers/dma/dma-axi-dmac.c index e22639822045f2..0f25f6d8ae71fa 100644 --- a/drivers/dma/dma-axi-dmac.c +++ b/drivers/dma/dma-axi-dmac.c @@ -233,11 +233,9 @@ static void axi_dmac_start_transfer(struct axi_dmac_chan *chan) unsigned int flags = 0; unsigned int val; - if (!chan->hw_sg) { - val = axi_dmac_read(dmac, AXI_DMAC_REG_START_TRANSFER); - if (val) /* Queue is full, wait for the next SOT IRQ */ - return; - } + val = axi_dmac_read(dmac, AXI_DMAC_REG_START_TRANSFER); + if (val) /* Queue is full, wait for the next SOT IRQ */ + return; desc = chan->next_desc; From 05ce13d78226bf4100151cbd47029dba0653a9a1 Mon Sep 17 00:00:00 2001 From: Cristian Ciocaltea Date: Sun, 21 Dec 2025 12:36:23 +0200 Subject: [PATCH 3705/3902] phy: rockchip: samsung-hdptx: Pre-compute HDMI PLL config for 461.10125 MHz output [ Upstream commit f2daf0c67a1767ff6536aa3e96599afb42ca42e7 ] Attempting to make use of a 1080p@120Hz display mode with 10 bpc RGB on my Acer XV275K P3 monitor results in a blank image. A similar behavior has been reported on Philips 279M1RV. The faulty modeline is created by drm_gtf_mode_complex() based on the following EDID entry from the Standard Timings block: GTF: 1920x1080 119.999987 Hz 16:9 138.840 kHz 368.759000 MHz It's worth noting the computed pixel clock ends up being slightly higher at 368.881000 MHz. Nevertheless, this seems to work consistently fine with 8 bpc RGB. After switching to 10 bpc, the TMDS character rate expected for the mode increases to 461.101250 MHz, as per drm_hdmi_compute_mode_clock(). Since there is no entry for this rate in the ropll_tmds_cfg table, the necessary HDMI PLL configuration parameters are calculated dynamically by rk_hdptx_phy_clk_pll_calc(). However, the resulting output rate is not quite a perfect match, i.e. 461.100000 MHz. That proved to be the actual root cause of the problem. Add a new entry to the TMDS configuration table and provide the necessary frequency division coefficients for the PHY PLL to generate the expected 461.101250 MHz output. Fixes: 9d0ec51d7c22 ("phy: rockchip: samsung-hdptx: Add high color depth management") Tested-by: Derek Foreman Signed-off-by: Cristian Ciocaltea Link: https://patch.msgid.link/20251221-phy-hdptx-pll-fix-v2-1-ae4abf7f75a1@collabora.com Signed-off-by: Vinod Koul Signed-off-by: Sasha Levin --- drivers/phy/rockchip/phy-rockchip-samsung-hdptx.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/drivers/phy/rockchip/phy-rockchip-samsung-hdptx.c b/drivers/phy/rockchip/phy-rockchip-samsung-hdptx.c index 29de2f7bdae8a3..cafa618d70fdca 100644 --- a/drivers/phy/rockchip/phy-rockchip-samsung-hdptx.c +++ b/drivers/phy/rockchip/phy-rockchip-samsung-hdptx.c @@ -414,6 +414,8 @@ struct rk_hdptx_phy { static const struct ropll_config ropll_tmds_cfg[] = { { 594000000ULL, 124, 124, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 62, 1, 16, 5, 0, 1, 1, 0, 0x20, 0x0c, 1, 0x0e, 0, 0, }, + { 461101250ULL, 97, 97, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 71, 1, 53, 2, 6, + 35, 1, 0, 0x20, 0x0c, 1, 0x0e, 0, 0, }, { 371250000ULL, 155, 155, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 62, 1, 16, 5, 0, 1, 1, 0, 0x20, 0x0c, 1, 0x0e, 0, 0, }, { 297000000ULL, 124, 124, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 62, 1, 16, 5, 0, From 621a8bf292ede41df2f8f09d4198f4ab628267d7 Mon Sep 17 00:00:00 2001 From: Alper Ak Date: Sat, 27 Dec 2025 02:02:48 +0300 Subject: [PATCH 3706/3902] char: misc: Use IS_ERR() for filp_open() return value [ Upstream commit e849ada70c6b1ee22e9f4f5c0e38231dcee53f04 ] filp_open() never returns NULL, it returns either a valid pointer or an error pointer. Using IS_ERR_OR_NULL() is unnecessary. Additionally, if filp were NULL, PTR_ERR(NULL) would return 0, leading to a misleading error message. Fixes: 74d8361be344 ("char: misc: add test cases") Reported-by: kernel test robot Reported-by: Dan Carpenter Closes: https://lore.kernel.org/r/202506132058.thWZHlrb-lkp@intel.com/ Signed-off-by: Alper Ak Acked-by: Thadeu Lima de Souza Cascardo Link: https://patch.msgid.link/20251226230248.113073-1-alperyasinak1@gmail.com Signed-off-by: Greg Kroah-Hartman Signed-off-by: Sasha Levin --- drivers/char/misc_minor_kunit.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/char/misc_minor_kunit.c b/drivers/char/misc_minor_kunit.c index 6fc8b05169c575..e930c78e1ef97e 100644 --- a/drivers/char/misc_minor_kunit.c +++ b/drivers/char/misc_minor_kunit.c @@ -166,7 +166,7 @@ static void __init miscdev_test_can_open(struct kunit *test, struct miscdevice * KUNIT_FAIL(test, "failed to create node\n"); filp = filp_open(devname, O_RDONLY, 0); - if (IS_ERR_OR_NULL(filp)) + if (IS_ERR(filp)) KUNIT_FAIL(test, "failed to open misc device: %ld\n", PTR_ERR(filp)); else fput(filp); From ac00f8a0cb157f5f8d6ed8099524c31fdb94ca2d Mon Sep 17 00:00:00 2001 From: Arnd Bergmann Date: Tue, 23 Dec 2025 22:50:01 +0100 Subject: [PATCH 3707/3902] soundwire: intel_ace2x: add SND_HDA_CORE dependency [ Upstream commit dc3a6a942e9ee3f18560bfcb16c06bb94f37fabf ] The ace2x driver can optionally use the HDA infrastructure, but can still build without that. However, with SND_HDA_CORE=m and SND_HDA_ALIGNED_MMIO=y, it fails to link as built-in: aarch64-linux-ld: drivers/soundwire/intel_ace2x.o: in function `intel_shim_wake': intel_ace2x.c:(.text+0x2518): undefined reference to `snd_hdac_aligned_read' aarch64-linux-ld: intel_ace2x.c:(.text+0x25d4): undefined reference to `snd_hdac_aligned_read' aarch64-linux-ld: intel_ace2x.c:(.text+0x268c): undefined reference to `snd_hdac_aligned_write' Add a Kconfig dependency that forces the soundwire driver to be a loadable module if necessary. Fixes: 79e7123c078d ("soundwire: intel_ace2x: fix wakeup handling") Signed-off-by: Arnd Bergmann Link: https://patch.msgid.link/20251223215014.534756-1-arnd@kernel.org Signed-off-by: Vinod Koul Signed-off-by: Sasha Levin --- drivers/soundwire/Kconfig | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/soundwire/Kconfig b/drivers/soundwire/Kconfig index ad56393e4c93b8..196a7daaabdb88 100644 --- a/drivers/soundwire/Kconfig +++ b/drivers/soundwire/Kconfig @@ -40,6 +40,7 @@ config SOUNDWIRE_INTEL select AUXILIARY_BUS depends on ACPI && SND_SOC depends on SND_SOC_SOF_HDA_MLINK || !SND_SOC_SOF_HDA_MLINK + depends on SND_HDA_CORE || !SND_HDA_ALIGNED_MMIO help SoundWire Intel Master driver. If you have an Intel platform which has a SoundWire Master then From b244fbf3ff829d23972821f829b9b8b8a9f16849 Mon Sep 17 00:00:00 2001 From: Randy Dunlap Date: Sat, 27 Dec 2025 23:10:29 -0800 Subject: [PATCH 3708/3902] iio: test: drop dangling symbol in gain-time-scale helpers [ Upstream commit d63d868b312478523670b76007dcc5eaedc3ee07 ] The code for this never went upstream. It was replaced by other code, so this should be dropped. Link: https://bugzilla.kernel.org/show_bug.cgi?id=216748 Fixes: cf996f039679 ("iio: test: test gain-time-scale helpers") Signed-off-by: Randy Dunlap Signed-off-by: Jonathan Cameron Signed-off-by: Sasha Levin --- drivers/iio/test/Kconfig | 1 - 1 file changed, 1 deletion(-) diff --git a/drivers/iio/test/Kconfig b/drivers/iio/test/Kconfig index 6e65e929791ca2..4fc17dd0dcd770 100644 --- a/drivers/iio/test/Kconfig +++ b/drivers/iio/test/Kconfig @@ -8,7 +8,6 @@ config IIO_GTS_KUNIT_TEST tristate "Test IIO gain-time-scale helpers" if !KUNIT_ALL_TESTS depends on KUNIT select IIO_GTS_HELPER - select TEST_KUNIT_DEVICE_HELPERS default KUNIT_ALL_TESTS help build unit tests for the IIO light sensor gain-time-scale helpers. From c7f7b55ce2ad858b015c45cd1797cf23139a516e Mon Sep 17 00:00:00 2001 From: Randy Dunlap Date: Sun, 28 Dec 2025 11:06:03 -0800 Subject: [PATCH 3709/3902] usb: typec: ucsi: drop an unused Kconfig symbol [ Upstream commit c5177144b561dd4037a6a225d444b3604afbfbf2 ] EXTCON_TCSS_CROS_EC isn't used anywhere else in the kernel tree, so drop it from this Kconfig file. (unless it should be EXTCON_USBC_CROS_EC ?) Fixes: f1a2241778d9 ("usb: typec: ucsi: Implement ChromeOS UCSI driver") Signed-off-by: Randy Dunlap Reviewed-by: Abhishek Pandit-Subedi Reviewed-by: Benson Leung Link: https://patch.msgid.link/20251228190604.2484082-1-rdunlap@infradead.org Signed-off-by: Greg Kroah-Hartman Signed-off-by: Sasha Levin --- drivers/usb/typec/ucsi/Kconfig | 1 - 1 file changed, 1 deletion(-) diff --git a/drivers/usb/typec/ucsi/Kconfig b/drivers/usb/typec/ucsi/Kconfig index b812be4d0e674e..87dd992a4b9e9a 100644 --- a/drivers/usb/typec/ucsi/Kconfig +++ b/drivers/usb/typec/ucsi/Kconfig @@ -73,7 +73,6 @@ config CROS_EC_UCSI tristate "UCSI Driver for ChromeOS EC" depends on MFD_CROS_EC_DEV depends on CROS_USBPD_NOTIFY - depends on !EXTCON_TCSS_CROS_EC default MFD_CROS_EC_DEV help This driver enables UCSI support for a ChromeOS EC. The EC is From 06162d85f830582da6e9e5fcf9c9504d6da9ae0b Mon Sep 17 00:00:00 2001 From: Chaitanya Mishra Date: Thu, 8 Jan 2026 20:42:54 +0530 Subject: [PATCH 3710/3902] staging: greybus: lights: avoid NULL deref [ Upstream commit efcffd9a6ad8d190651498d5eda53bfc7cf683a7 ] gb_lights_light_config() stores channel_count before allocating the channels array. If kcalloc() fails, gb_lights_release() iterates the non-zero count and dereferences light->channels, which is NULL. Allocate channels first and only then publish channels_count so the cleanup path can't walk a NULL pointer. Fixes: 2870b52bae4c ("greybus: lights: add lights implementation") Link: https://lore.kernel.org/all/20260108103700.15384-1-chaitanyamishra.ai@gmail.com/ Reviewed-by: Rui Miguel Silva Signed-off-by: Chaitanya Mishra Link: https://patch.msgid.link/20260108151254.81553-1-chaitanyamishra.ai@gmail.com Signed-off-by: Greg Kroah-Hartman Signed-off-by: Sasha Levin --- drivers/staging/greybus/light.c | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/drivers/staging/greybus/light.c b/drivers/staging/greybus/light.c index e509fdc715dbbf..38c233a706c483 100644 --- a/drivers/staging/greybus/light.c +++ b/drivers/staging/greybus/light.c @@ -1008,14 +1008,18 @@ static int gb_lights_light_config(struct gb_lights *glights, u8 id) if (!strlen(conf.name)) return -EINVAL; - light->channels_count = conf.channel_count; light->name = kstrndup(conf.name, NAMES_MAX, GFP_KERNEL); if (!light->name) return -ENOMEM; - light->channels = kcalloc(light->channels_count, + light->channels = kcalloc(conf.channel_count, sizeof(struct gb_channel), GFP_KERNEL); if (!light->channels) return -ENOMEM; + /* + * Publish channels_count only after channels allocation so cleanup + * doesn't walk a NULL channels pointer on allocation failure. + */ + light->channels_count = conf.channel_count; /* First we collect all the configurations for all channels */ for (i = 0; i < light->channels_count; i++) { From 3f8b835a63341163da0400befb3c6e8f6d4085da Mon Sep 17 00:00:00 2001 From: Randy Dunlap Date: Sat, 10 Jan 2026 15:26:40 -0800 Subject: [PATCH 3711/3902] serial: imx: change SERIAL_IMX_CONSOLE to bool [ Upstream commit 79527d86ba91c2d9354832d19fd12b3baa66bd10 ] SERIAL_IMX_CONSOLE is a build option for the imx driver (SERIAL_IMX). It does not build a separate console driver file, so it can't be built as a module since it isn't built at all. Change the Kconfig symbol from tristate to bool and update the help text accordingly. Fixes: 0db4f9b91c86 ("tty: serial: imx: enable imx serial console port as module") Signed-off-by: Randy Dunlap Link: https://patch.msgid.link/20260110232643.3533351-2-rdunlap@infradead.org Signed-off-by: Greg Kroah-Hartman Signed-off-by: Sasha Levin --- drivers/tty/serial/Kconfig | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/drivers/tty/serial/Kconfig b/drivers/tty/serial/Kconfig index 282116765e6481..0981288d19c2c2 100644 --- a/drivers/tty/serial/Kconfig +++ b/drivers/tty/serial/Kconfig @@ -486,14 +486,14 @@ config SERIAL_IMX can enable its onboard serial port by enabling this option. config SERIAL_IMX_CONSOLE - tristate "Console on IMX serial port" + bool "Console on IMX serial port" depends on SERIAL_IMX select SERIAL_CORE_CONSOLE help If you have enabled the serial port on the Freescale IMX - CPU you can make it the console by answering Y/M to this option. + CPU you can make it the console by answering Y to this option. - Even if you say Y/M here, the currently visible virtual console + Even if you say Y here, the currently visible virtual console (/dev/tty0) will still be used as the system console by default, but you can alter that using a kernel command line option such as "console=ttymxc0". (Try "man bootparam" or see the documentation of From 12679ed78c09b0c30321cafe0ad24e59f3f4a6bb Mon Sep 17 00:00:00 2001 From: Randy Dunlap Date: Sat, 10 Jan 2026 15:26:43 -0800 Subject: [PATCH 3712/3902] serial: SH_SCI: improve "DMA support" prompt [ Upstream commit 93bb95a11238d66a4c9aa6eabf9774b073a5895c ] Having a prompt of "DMA support" suddenly appear during a "make oldconfig" can be confusing. Add a little helpful text to the prompt message. Fixes: 73a19e4c0301 ("serial: sh-sci: Add DMA support.") Signed-off-by: Randy Dunlap Reviewed-by: Geert Uytterhoeven Link: https://patch.msgid.link/20260110232643.3533351-5-rdunlap@infradead.org Signed-off-by: Greg Kroah-Hartman Signed-off-by: Sasha Levin --- drivers/tty/serial/Kconfig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/tty/serial/Kconfig b/drivers/tty/serial/Kconfig index 0981288d19c2c2..2b9c8b39d68f55 100644 --- a/drivers/tty/serial/Kconfig +++ b/drivers/tty/serial/Kconfig @@ -671,7 +671,7 @@ config SERIAL_SH_SCI_EARLYCON default ARCH_RENESAS config SERIAL_SH_SCI_DMA - bool "DMA support" if EXPERT + bool "Support for DMA on SuperH SCI(F)" if EXPERT depends on SERIAL_SH_SCI && DMA_ENGINE default ARCH_RENESAS From 5ca0f7b3718377bf635be83b289d3fa7d6b7b91b Mon Sep 17 00:00:00 2001 From: Dan Carpenter Date: Fri, 28 Nov 2025 10:17:49 +0300 Subject: [PATCH 3713/3902] gpib: Fix error code in ibonline() [ Upstream commit 96118565d24e7691e423d73be224b3a3fffc4680 ] This accidentally returns 1 on error, but it should return negative error codes. Fixes: 9dde4559e939 ("staging: gpib: Add GPIB common core driver") Signed-off-by: Dan Carpenter Link: https://patch.msgid.link/aSlMnaT1M104NJb2@stanley.mountain Signed-off-by: Greg Kroah-Hartman Signed-off-by: Sasha Levin --- drivers/staging/gpib/common/iblib.c | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/drivers/staging/gpib/common/iblib.c b/drivers/staging/gpib/common/iblib.c index 7cbb6a467177da..b672dd6aad25f2 100644 --- a/drivers/staging/gpib/common/iblib.c +++ b/drivers/staging/gpib/common/iblib.c @@ -227,11 +227,10 @@ int ibonline(struct gpib_board *board) #ifndef CONFIG_NIOS2 board->autospoll_task = kthread_run(&autospoll_thread, board, "gpib%d_autospoll_kthread", board->minor); - retval = IS_ERR(board->autospoll_task); - if (retval) { + if (IS_ERR(board->autospoll_task)) { dev_err(board->gpib_dev, "failed to create autospoll thread\n"); board->interface->detach(board); - return retval; + return PTR_ERR(board->autospoll_task); } #endif board->online = 1; From b30ba62cafd8bd705dacbcc5f6072f8e0dde9af2 Mon Sep 17 00:00:00 2001 From: Dan Carpenter Date: Fri, 28 Nov 2025 10:17:57 +0300 Subject: [PATCH 3714/3902] gpib: Fix error code in ni_usb_write_registers() [ Upstream commit 484e62252212c5b5fc62eaee5e4977143cb159c6 ] If ni_usb_receive_bulk_msg() succeeds but without reading 16 bytes, then the error code needs to be set. The current code returns success. Fixes: 4e127de14fa7 ("staging: gpib: Add National Instruments USB GPIB driver") Signed-off-by: Dan Carpenter Link: https://patch.msgid.link/aSlMpbE4IrQuBGFS@stanley.mountain Signed-off-by: Greg Kroah-Hartman Signed-off-by: Sasha Levin --- drivers/staging/gpib/ni_usb/ni_usb_gpib.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/staging/gpib/ni_usb/ni_usb_gpib.c b/drivers/staging/gpib/ni_usb/ni_usb_gpib.c index 1f8412de9fa329..fdcaa6c00bfeac 100644 --- a/drivers/staging/gpib/ni_usb/ni_usb_gpib.c +++ b/drivers/staging/gpib/ni_usb/ni_usb_gpib.c @@ -566,7 +566,7 @@ static int ni_usb_write_registers(struct ni_usb_priv *ni_priv, retval, bytes_read); ni_usb_dump_raw_block(in_data, bytes_read); kfree(in_data); - return retval; + return retval ?: -EINVAL; } mutex_unlock(&ni_priv->addressed_transfer_lock); From 9c97fcfb7a62dea893104a046d544da8ac23370b Mon Sep 17 00:00:00 2001 From: Zilin Guan Date: Tue, 30 Dec 2025 03:45:46 +0000 Subject: [PATCH 3715/3902] gpib: Fix memory leak in ni_usb_init() [ Upstream commit b89921eed8cf2d97250bac4be38dbcfbf048b586 ] In ni_usb_init(), if ni_usb_setup_init() fails, the function returns -EFAULT without freeing the allocated writes buffer, leading to a memory leak. Additionally, ni_usb_setup_init() returns 0 on failure, which causes ni_usb_init() to return -EFAULT, an inappropriate error code for this situation. Fix the leak by freeing writes in the error path. Modify ni_usb_setup_init() to return -EINVAL on failure and propagate this error code in ni_usb_init(). Fixes: 4e127de14fa7 ("staging: gpib: Add National Instruments USB GPIB driver") Suggested-by: Greg KH Suggested-by: Dave Penkler Co-developed-by: Jianhao Xu Signed-off-by: Jianhao Xu Signed-off-by: Zilin Guan Link: https://patch.msgid.link/20251230034546.929452-1-zilin@seu.edu.cn Signed-off-by: Greg Kroah-Hartman Signed-off-by: Sasha Levin --- drivers/staging/gpib/ni_usb/ni_usb_gpib.c | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/drivers/staging/gpib/ni_usb/ni_usb_gpib.c b/drivers/staging/gpib/ni_usb/ni_usb_gpib.c index fdcaa6c00bfeac..b6fddb437f5524 100644 --- a/drivers/staging/gpib/ni_usb/ni_usb_gpib.c +++ b/drivers/staging/gpib/ni_usb/ni_usb_gpib.c @@ -1780,7 +1780,7 @@ static int ni_usb_setup_init(struct gpib_board *board, struct ni_usb_register *w i++; if (i > NUM_INIT_WRITES) { dev_err(&usb_dev->dev, "bug!, buffer overrun, i=%i\n", i); - return 0; + return -EINVAL; } return i; } @@ -1799,10 +1799,12 @@ static int ni_usb_init(struct gpib_board *board) return -ENOMEM; writes_len = ni_usb_setup_init(board, writes); - if (writes_len) - retval = ni_usb_write_registers(ni_priv, writes, writes_len, &ibsta); - else - return -EFAULT; + if (writes_len < 0) { + kfree(writes); + return writes_len; + } + + retval = ni_usb_write_registers(ni_priv, writes, writes_len, &ibsta); kfree(writes); if (retval) { dev_err(&usb_dev->dev, "register write failed, retval=%i\n", retval); From 37fb15537cc052b75daed684b951a3d9016bec1c Mon Sep 17 00:00:00 2001 From: Matthew Schwartz Date: Sun, 4 Jan 2026 22:02:36 -0800 Subject: [PATCH 3716/3902] mmc: rtsx_pci_sdmmc: increase power-on settling delay to 5ms [ Upstream commit aced969e9bf3701dc75cfca57c78c031b7875b9d ] The existing 1ms delay in sd_power_on is insufficient and causes resume errors around 4% of the time. Increasing the delay to 5ms resolves this issue after testing 300 s2idle cycles. Fixes: 1f311c94aabd ("mmc: rtsx: add 74 Clocks in power on flow") Signed-off-by: Matthew Schwartz Link: https://patch.msgid.link/20260105060236.400366-3-matthew.schwartz@linux.dev Signed-off-by: Greg Kroah-Hartman Signed-off-by: Sasha Levin --- drivers/mmc/host/rtsx_pci_sdmmc.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/mmc/host/rtsx_pci_sdmmc.c b/drivers/mmc/host/rtsx_pci_sdmmc.c index 4db3328f46dfbd..b6cf1803c7d27b 100644 --- a/drivers/mmc/host/rtsx_pci_sdmmc.c +++ b/drivers/mmc/host/rtsx_pci_sdmmc.c @@ -937,7 +937,7 @@ static int sd_power_on(struct realtek_pci_sdmmc *host, unsigned char power_mode) if (err < 0) return err; - mdelay(1); + mdelay(5); err = rtsx_pci_write_register(pcr, CARD_OE, SD_OUTPUT_EN, SD_OUTPUT_EN); if (err < 0) From 664ffdf34c01810085e4d85508b361c3fdd2ab40 Mon Sep 17 00:00:00 2001 From: Petre Rodan Date: Wed, 14 Jan 2026 18:55:30 +0200 Subject: [PATCH 3717/3902] iio: pressure: mprls0025pa: fix spi_transfer struct initialisation [ Upstream commit 1e0ac56c92e26115cbc8cfc639843725cb3a7d6a ] Make sure that the spi_transfer struct is zeroed out before use. Fixes: a0858f0cd28e ("iio: pressure: mprls0025pa add SPI driver") Signed-off-by: Petre Rodan Reviewed-by: Andy Shevchenko Signed-off-by: Jonathan Cameron Signed-off-by: Sasha Levin --- drivers/iio/pressure/mprls0025pa_spi.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/iio/pressure/mprls0025pa_spi.c b/drivers/iio/pressure/mprls0025pa_spi.c index d04102f8a4a035..e6bb75de341191 100644 --- a/drivers/iio/pressure/mprls0025pa_spi.c +++ b/drivers/iio/pressure/mprls0025pa_spi.c @@ -40,7 +40,7 @@ static int mpr_spi_xfer(struct mpr_data *data, const u8 cmd, const u8 pkt_len) { struct spi_device *spi = to_spi_device(data->dev); struct mpr_spi_buf *buf = spi_get_drvdata(spi); - struct spi_transfer xfer; + struct spi_transfer xfer = { }; if (pkt_len > MPR_MEASUREMENT_RD_SIZE) return -EOVERFLOW; From 285af88d5da16032c751117c31e7f03b4f53ed7a Mon Sep 17 00:00:00 2001 From: Petre Rodan Date: Wed, 14 Jan 2026 18:55:31 +0200 Subject: [PATCH 3718/3902] iio: pressure: mprls0025pa: fix SPI CS delay violation [ Upstream commit 583fa86ca581595b1f534a8de6d49ba8b3bf7196 ] Based on the sensor datasheet in chapter 7.6 SPI timing, Table 20, during the SPI transfer there is a minimum time interval requirement between the CS being asserted and the first clock edge (tHDSS). This minimum interval of 2.5us is being violated if two consecutive SPI transfers are queued up. Fixes: a0858f0cd28e ("iio: pressure: mprls0025pa add SPI driver") Datasheet: https://prod-edam.honeywell.com/content/dam/honeywell-edam/sps/siot/en-us/products/sensors/pressure-sensors/board-mount-pressure-sensors/micropressure-mpr-series/documents/sps-siot-mpr-series-datasheet-32332628-ciid-172626.pdf?download=false Signed-off-by: Petre Rodan Reviewed-by: Andy Shevchenko Signed-off-by: Jonathan Cameron Signed-off-by: Sasha Levin --- drivers/iio/pressure/mprls0025pa_spi.c | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/drivers/iio/pressure/mprls0025pa_spi.c b/drivers/iio/pressure/mprls0025pa_spi.c index e6bb75de341191..cf17eb2e720830 100644 --- a/drivers/iio/pressure/mprls0025pa_spi.c +++ b/drivers/iio/pressure/mprls0025pa_spi.c @@ -8,6 +8,7 @@ * https://prod-edam.honeywell.com/content/dam/honeywell-edam/sps/siot/en-us/products/sensors/pressure-sensors/board-mount-pressure-sensors/micropressure-mpr-series/documents/sps-siot-mpr-series-datasheet-32332628-ciid-172626.pdf */ +#include #include #include #include @@ -40,17 +41,25 @@ static int mpr_spi_xfer(struct mpr_data *data, const u8 cmd, const u8 pkt_len) { struct spi_device *spi = to_spi_device(data->dev); struct mpr_spi_buf *buf = spi_get_drvdata(spi); - struct spi_transfer xfer = { }; + struct spi_transfer xfers[2] = { }; if (pkt_len > MPR_MEASUREMENT_RD_SIZE) return -EOVERFLOW; buf->tx[0] = cmd; - xfer.tx_buf = buf->tx; - xfer.rx_buf = data->buffer; - xfer.len = pkt_len; - return spi_sync_transfer(spi, &xfer, 1); + /* + * Dummy transfer with no data, just cause a 2.5us+ delay between the CS assert + * and the first clock edge as per the datasheet tHDSS timing requirement. + */ + xfers[0].delay.value = 2500; + xfers[0].delay.unit = SPI_DELAY_UNIT_NSECS; + + xfers[1].tx_buf = buf->tx; + xfers[1].rx_buf = data->buffer; + xfers[1].len = pkt_len; + + return spi_sync_transfer(spi, xfers, ARRAY_SIZE(xfers)); } static const struct mpr_ops mpr_spi_ops = { From 0beeade4823bccc19ff906bb538cca8ce086143f Mon Sep 17 00:00:00 2001 From: Petre Rodan Date: Wed, 14 Jan 2026 18:55:32 +0200 Subject: [PATCH 3719/3902] iio: pressure: mprls0025pa: fix interrupt flag [ Upstream commit fff3f1a7d805684e4701a70bfaeba39622b59dbc ] Interrupt falling/rising flags should only be defined in the device tree. Fixes: 713337d9143e ("iio: pressure: Honeywell mprls0025pa pressure sensor") Signed-off-by: Petre Rodan Reviewed-by: Andy Shevchenko Signed-off-by: Jonathan Cameron Signed-off-by: Sasha Levin --- drivers/iio/pressure/mprls0025pa.c | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/drivers/iio/pressure/mprls0025pa.c b/drivers/iio/pressure/mprls0025pa.c index 2336f2760eaeb7..4b23f87a822b13 100644 --- a/drivers/iio/pressure/mprls0025pa.c +++ b/drivers/iio/pressure/mprls0025pa.c @@ -418,10 +418,8 @@ int mpr_common_probe(struct device *dev, const struct mpr_ops *ops, int irq) data->offset = div_s64_rem(offset, NANO, &data->offset2); if (data->irq > 0) { - ret = devm_request_irq(dev, data->irq, mpr_eoc_handler, - IRQF_TRIGGER_RISING, - dev_name(dev), - data); + ret = devm_request_irq(dev, data->irq, mpr_eoc_handler, 0, + dev_name(dev), data); if (ret) return dev_err_probe(dev, ret, "request irq %d failed\n", data->irq); From b61512ff73d28978d26d56b756a51eaa99d3eaad Mon Sep 17 00:00:00 2001 From: Petre Rodan Date: Wed, 14 Jan 2026 18:55:33 +0200 Subject: [PATCH 3720/3902] iio: pressure: mprls0025pa: fix scan_type struct [ Upstream commit 8a228e036926f7e57421d750c3724e63f11b808a ] Fix the scan_type sign and realbits assignment. The pressure is a 24bit unsigned int between output_min and output_max. transfer function A: 10% to 90% of 2^24 transfer function B: 2.5% to 22.5% of 2^24 transfer function C: 20% to 80% of 2^24 [MPR_FUNCTION_A] = { .output_min = 1677722, .output_max = 15099494 } [MPR_FUNCTION_B] = { .output_min = 419430, .output_max = 3774874 } [MPR_FUNCTION_C] = { .output_min = 3355443, .output_max = 13421773 } Fixes: 713337d9143e ("iio: pressure: Honeywell mprls0025pa pressure sensor") Signed-off-by: Petre Rodan Reviewed-by: Andy Shevchenko Signed-off-by: Jonathan Cameron Signed-off-by: Sasha Levin --- drivers/iio/pressure/mprls0025pa.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/iio/pressure/mprls0025pa.c b/drivers/iio/pressure/mprls0025pa.c index 4b23f87a822b13..6ba45d4c16b301 100644 --- a/drivers/iio/pressure/mprls0025pa.c +++ b/drivers/iio/pressure/mprls0025pa.c @@ -160,8 +160,8 @@ static const struct iio_chan_spec mpr_channels[] = { BIT(IIO_CHAN_INFO_OFFSET), .scan_index = 0, .scan_type = { - .sign = 's', - .realbits = 32, + .sign = 'u', + .realbits = 24, .storagebits = 32, .endianness = IIO_CPU, }, From a75046f2efadca2553a206a899645091d72bb141 Mon Sep 17 00:00:00 2001 From: Petre Rodan Date: Wed, 14 Jan 2026 18:55:34 +0200 Subject: [PATCH 3721/3902] iio: pressure: mprls0025pa: fix pressure calculation [ Upstream commit d63403d4e31ae537fefc5c0ee9d90f29b4fc532b ] A sign change is needed for proper calculation of the pressure. This is a minor fix since it only affects users that might have custom silicon from Honeywell that has honeywell,pmin-pascal != 0. Also due to the fact that raw pressure values can not be lower than output_min (400k-3.3M) there is no need to calculate a decimal for the offset. Fixes: 713337d9143e ("iio: pressure: Honeywell mprls0025pa pressure sensor") Signed-off-by: Petre Rodan Reviewed-by: Andy Shevchenko Signed-off-by: Jonathan Cameron Signed-off-by: Sasha Levin --- drivers/iio/pressure/mprls0025pa.c | 26 +++++++++++--------------- drivers/iio/pressure/mprls0025pa.h | 2 -- 2 files changed, 11 insertions(+), 17 deletions(-) diff --git a/drivers/iio/pressure/mprls0025pa.c b/drivers/iio/pressure/mprls0025pa.c index 6ba45d4c16b301..d4133fef91fac4 100644 --- a/drivers/iio/pressure/mprls0025pa.c +++ b/drivers/iio/pressure/mprls0025pa.c @@ -59,7 +59,7 @@ * * Values given to the userspace in sysfs interface: * * raw - press_cnt - * * offset - (-1 * outputmin) - pmin / scale + * * offset - (-1 * outputmin) + pmin / scale * note: With all sensors from the datasheet pmin = 0 * which reduces the offset to (-1 * outputmin) */ @@ -313,8 +313,7 @@ static int mpr_read_raw(struct iio_dev *indio_dev, return IIO_VAL_INT_PLUS_NANO; case IIO_CHAN_INFO_OFFSET: *val = data->offset; - *val2 = data->offset2; - return IIO_VAL_INT_PLUS_NANO; + return IIO_VAL_INT; default: return -EINVAL; } @@ -330,8 +329,9 @@ int mpr_common_probe(struct device *dev, const struct mpr_ops *ops, int irq) struct mpr_data *data; struct iio_dev *indio_dev; const char *triplet; - s64 scale, offset; + s64 odelta, pdelta; u32 func; + s32 tmp; indio_dev = devm_iio_device_alloc(dev, sizeof(*data)); if (!indio_dev) @@ -405,17 +405,13 @@ int mpr_common_probe(struct device *dev, const struct mpr_ops *ops, int irq) data->outmin = mpr_func_spec[data->function].output_min; data->outmax = mpr_func_spec[data->function].output_max; - /* use 64 bit calculation for preserving a reasonable precision */ - scale = div_s64(((s64)(data->pmax - data->pmin)) * NANO, - data->outmax - data->outmin); - data->scale = div_s64_rem(scale, NANO, &data->scale2); - /* - * multiply with NANO before dividing by scale and later divide by NANO - * again. - */ - offset = ((-1LL) * (s64)data->outmin) * NANO - - div_s64(div_s64((s64)data->pmin * NANO, scale), NANO); - data->offset = div_s64_rem(offset, NANO, &data->offset2); + odelta = data->outmax - data->outmin; + pdelta = data->pmax - data->pmin; + + data->scale = div_s64_rem(div_s64(pdelta * NANO, odelta), NANO, &tmp); + data->scale2 = tmp; + + data->offset = div_s64(odelta * data->pmin, pdelta) - data->outmin; if (data->irq > 0) { ret = devm_request_irq(dev, data->irq, mpr_eoc_handler, 0, diff --git a/drivers/iio/pressure/mprls0025pa.h b/drivers/iio/pressure/mprls0025pa.h index d62a018eaff32b..b6944b30512677 100644 --- a/drivers/iio/pressure/mprls0025pa.h +++ b/drivers/iio/pressure/mprls0025pa.h @@ -53,7 +53,6 @@ enum mpr_func_id { * @scale: pressure scale * @scale2: pressure scale, decimal number * @offset: pressure offset - * @offset2: pressure offset, decimal number * @gpiod_reset: reset * @irq: end of conversion irq. used to distinguish between irq mode and * reading in a loop until data is ready @@ -75,7 +74,6 @@ struct mpr_data { int scale; int scale2; int offset; - int offset2; struct gpio_desc *gpiod_reset; int irq; struct completion completion; From 8665e18a707ca85d146a4d180a8f13804f6f242e Mon Sep 17 00:00:00 2001 From: Kery Qi Date: Mon, 12 Jan 2026 01:29:15 +0800 Subject: [PATCH 3722/3902] watchdog: starfive-wdt: Fix PM reference leak in probe error path [ Upstream commit 3f2d8d79cceb05a8b8dd200fa81c0dffc59ec46f ] The PM reference count is not expected to be incremented on return in functions starfive_wdt_probe. However, pm_runtime_get_sync will increment pm usage counter even failed. Forgetting to putting operation will result in a reference leak here. Replace it with pm_runtime_resume_and_get to keep usage counter balanced. Fixes: db728ea9c7be ("drivers: watchdog: Add StarFive Watchdog driver") Signed-off-by: Kery Qi Reviewed-by: Guenter Roeck Signed-off-by: Guenter Roeck Signed-off-by: Wim Van Sebroeck Signed-off-by: Sasha Levin --- drivers/watchdog/starfive-wdt.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/watchdog/starfive-wdt.c b/drivers/watchdog/starfive-wdt.c index ed71d3960a0f26..af55adc4a3c69b 100644 --- a/drivers/watchdog/starfive-wdt.c +++ b/drivers/watchdog/starfive-wdt.c @@ -446,7 +446,7 @@ static int starfive_wdt_probe(struct platform_device *pdev) platform_set_drvdata(pdev, wdt); pm_runtime_enable(&pdev->dev); if (pm_runtime_enabled(&pdev->dev)) { - ret = pm_runtime_get_sync(&pdev->dev); + ret = pm_runtime_resume_and_get(&pdev->dev); if (ret < 0) return ret; } else { From 1bec5f283dd5ece89d87e29e89b959cdf2a8f768 Mon Sep 17 00:00:00 2001 From: Antonio Borneo Date: Thu, 8 Jan 2026 16:24:27 +0100 Subject: [PATCH 3723/3902] coresight: etm3x: Fix cpulocked warning on cpuhp [ Upstream commit 1feb0377b9b816f89a04fc381eb19fc6bac9f4a4 ] When changes [1] and [2] have been applied to the driver etm4x, the same modifications have been also collapsed in [3] and applied in one shot to the driver etm3x. While doing this, the driver etm3x has not been aligned to etm4x on the use of non cpuslocked version of cpuhp callback setup APIs. The current code triggers two run-time warnings when the kernel is compiled with CONFIG_PROVE_LOCKING=y. Use non cpuslocked version of cpuhp callback setup APIs in driver etm3x, aligning it to the driver etm4x. [1] commit 2d1a8bfb61ec ("coresight: etm4x: Fix etm4_count race by moving cpuhp callbacks to init") [2] commit 22a550a306ad ("coresight: etm4x: Allow etm4x to be built as a module") [3] commit 97fe626ce64c ("coresight: etm3x: Allow etm3x to be built as a module") Fixes: 97fe626ce64c ("coresight: etm3x: Allow etm3x to be built as a module") Signed-off-by: Antonio Borneo Signed-off-by: Suzuki K Poulose Link: https://lore.kernel.org/r/20260108152427.357379-1-antonio.borneo@foss.st.com Signed-off-by: Sasha Levin --- drivers/hwtracing/coresight/coresight-etm3x-core.c | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/drivers/hwtracing/coresight/coresight-etm3x-core.c b/drivers/hwtracing/coresight/coresight-etm3x-core.c index a5e809589d3e38..0c011b7041696a 100644 --- a/drivers/hwtracing/coresight/coresight-etm3x-core.c +++ b/drivers/hwtracing/coresight/coresight-etm3x-core.c @@ -795,16 +795,16 @@ static int __init etm_hp_setup(void) { int ret; - ret = cpuhp_setup_state_nocalls_cpuslocked(CPUHP_AP_ARM_CORESIGHT_STARTING, - "arm/coresight:starting", - etm_starting_cpu, etm_dying_cpu); + ret = cpuhp_setup_state_nocalls(CPUHP_AP_ARM_CORESIGHT_STARTING, + "arm/coresight:starting", + etm_starting_cpu, etm_dying_cpu); if (ret) return ret; - ret = cpuhp_setup_state_nocalls_cpuslocked(CPUHP_AP_ONLINE_DYN, - "arm/coresight:online", - etm_online_cpu, NULL); + ret = cpuhp_setup_state_nocalls(CPUHP_AP_ONLINE_DYN, + "arm/coresight:online", + etm_online_cpu, NULL); /* HP dyn state ID returned in ret on success */ if (ret > 0) { From dd8b9ba3d9701832cfb5dcefd8b43250df28dbc2 Mon Sep 17 00:00:00 2001 From: Thomas Richard Date: Wed, 14 Jan 2026 17:50:23 +0100 Subject: [PATCH 3724/3902] phy: freescale: imx8qm-hsio: fix NULL pointer dereference [ Upstream commit 4dd5d4c0361af0a3fd24f45c815996abf4429770 ] During the probe the refclk_pad pointer is set to NULL if the 'fsl,refclk-pad-mode' property is not defined in the devicetree node. But in imx_hsio_configure_clk_pad() this pointer is unconditionally used which could result in a NULL pointer dereference. So check the pointer before to use it. Fixes: 82c56b6dd24f ("phy: freescale: imx8qm-hsio: Add i.MX8QM HSIO PHY driver support") Signed-off-by: Thomas Richard Reviewed-by: Richard Zhu Link: https://patch.msgid.link/20260114-phy-fsl-imx8qm-hsio-fix-null-pointer-dereference-v1-1-730e941be464@bootlin.com Signed-off-by: Vinod Koul Signed-off-by: Sasha Levin --- drivers/phy/freescale/phy-fsl-imx8qm-hsio.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/phy/freescale/phy-fsl-imx8qm-hsio.c b/drivers/phy/freescale/phy-fsl-imx8qm-hsio.c index 977d21d753a59a..279b8ac7822df7 100644 --- a/drivers/phy/freescale/phy-fsl-imx8qm-hsio.c +++ b/drivers/phy/freescale/phy-fsl-imx8qm-hsio.c @@ -251,7 +251,7 @@ static void imx_hsio_configure_clk_pad(struct phy *phy) struct imx_hsio_lane *lane = phy_get_drvdata(phy); struct imx_hsio_priv *priv = lane->priv; - if (strncmp(priv->refclk_pad, "output", 6) == 0) { + if (priv->refclk_pad && strncmp(priv->refclk_pad, "output", 6) == 0) { pll = true; regmap_update_bits(priv->misc, HSIO_CTRL0, HSIO_IOB_A_0_TXOE | HSIO_IOB_A_0_M1M0_MASK, From 38a07194bbcddb18d77dad40ba9978d994c0b74c Mon Sep 17 00:00:00 2001 From: Yicong Yang Date: Wed, 21 Jan 2026 18:15:42 +0800 Subject: [PATCH 3725/3902] coresight: tmc-etr: Fix race condition between sysfs and perf mode [ Upstream commit e6e43e82c79c97917cbe356c07e8a6f3f982ab53 ] When trying to run perf and sysfs mode simultaneously, the WARN_ON() in tmc_etr_enable_hw() is triggered sometimes: WARNING: CPU: 42 PID: 3911571 at drivers/hwtracing/coresight/coresight-tmc-etr.c:1060 tmc_etr_enable_hw+0xc0/0xd8 [coresight_tmc] [..snip..] Call trace: tmc_etr_enable_hw+0xc0/0xd8 [coresight_tmc] (P) tmc_enable_etr_sink+0x11c/0x250 [coresight_tmc] (L) tmc_enable_etr_sink+0x11c/0x250 [coresight_tmc] coresight_enable_path+0x1c8/0x218 [coresight] coresight_enable_sysfs+0xa4/0x228 [coresight] enable_source_store+0x58/0xa8 [coresight] dev_attr_store+0x20/0x40 sysfs_kf_write+0x4c/0x68 kernfs_fop_write_iter+0x120/0x1b8 vfs_write+0x2c8/0x388 ksys_write+0x74/0x108 __arm64_sys_write+0x24/0x38 el0_svc_common.constprop.0+0x64/0x148 do_el0_svc+0x24/0x38 el0_svc+0x3c/0x130 el0t_64_sync_handler+0xc8/0xd0 el0t_64_sync+0x1ac/0x1b0 ---[ end trace 0000000000000000 ]--- Since the enablement of sysfs mode is separeted into two critical regions, one for sysfs buffer allocation and another for hardware enablement, it's possible to race with the perf mode. Fix this by double check whether the perf mode's been used before enabling the hardware in sysfs mode. mode: [sysfs mode] [perf mode] tmc_etr_get_sysfs_buffer() spin_lock(&drvdata->spinlock) [sysfs buffer allocation] spin_unlock(&drvdata->spinlock) spin_lock(&drvdata->spinlock) tmc_etr_enable_hw() drvdata->etr_buf = etr_perf->etr_buf spin_unlock(&drvdata->spinlock) spin_lock(&drvdata->spinlock) tmc_etr_enable_hw() WARN_ON(drvdata->etr_buf) // WARN sicne etr_buf initialized at the perf side spin_unlock(&drvdata->spinlock) With this fix, we retain the check for CS_MODE_PERF in get_etr_sysfs_buf. This ensures we verify whether the perf mode's already running before we actually allocate the buffer. Then we can save the time of allocating/freeing the sysfs buffer if race with the perf mode. Fixes: 296b01fd106e ("coresight: Refactor out buffer allocation function for ETR") Signed-off-by: Yicong Yang Signed-off-by: Junhao He Signed-off-by: Suzuki K Poulose Link: https://lore.kernel.org/r/20260121101543.2017014-3-wangyushan12@huawei.com Signed-off-by: Sasha Levin --- drivers/hwtracing/coresight/coresight-tmc-etr.c | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/drivers/hwtracing/coresight/coresight-tmc-etr.c b/drivers/hwtracing/coresight/coresight-tmc-etr.c index 60b0e0a6da0575..9144b273d415f0 100644 --- a/drivers/hwtracing/coresight/coresight-tmc-etr.c +++ b/drivers/hwtracing/coresight/coresight-tmc-etr.c @@ -1306,6 +1306,19 @@ static int tmc_enable_etr_sink_sysfs(struct coresight_device *csdev) raw_spin_lock_irqsave(&drvdata->spinlock, flags); + /* + * Since the sysfs buffer allocation and the hardware enablement is not + * in the same critical region, it's possible to race with the perf. + */ + if (coresight_get_mode(csdev) == CS_MODE_PERF) { + drvdata->sysfs_buf = NULL; + raw_spin_unlock_irqrestore(&drvdata->spinlock, flags); + + /* Free allocated memory out side of the spinlock */ + tmc_etr_free_sysfs_buf(sysfs_buf); + return -EBUSY; + } + /* * In sysFS mode we can have multiple writers per sink. Since this * sink is already enabled no memory is needed and the HW need not be From 8e7d8aca8811a078ce1e9d8340eb45afc87f4f3a Mon Sep 17 00:00:00 2001 From: Greg Kroah-Hartman Date: Wed, 21 Jan 2026 15:49:31 +0100 Subject: [PATCH 3726/3902] Revert "mmc: rtsx_pci_sdmmc: increase power-on settling delay to 5ms" [ Upstream commit ff112f1ecd10b72004eac05bae395e1c65f0c63c ] This reverts commit aced969e9bf3701dc75cfca57c78c031b7875b9d. It was determined that this was not the correct "fix", so should be reverted. Fixes: aced969e9bf3 ("mmc: rtsx_pci_sdmmc: increase power-on settling delay to 5ms") Cc: Matthew Schwartz Cc: Ulf Hansson Signed-off-by: Greg Kroah-Hartman Signed-off-by: Sasha Levin --- drivers/mmc/host/rtsx_pci_sdmmc.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/mmc/host/rtsx_pci_sdmmc.c b/drivers/mmc/host/rtsx_pci_sdmmc.c index b6cf1803c7d27b..4db3328f46dfbd 100644 --- a/drivers/mmc/host/rtsx_pci_sdmmc.c +++ b/drivers/mmc/host/rtsx_pci_sdmmc.c @@ -937,7 +937,7 @@ static int sd_power_on(struct realtek_pci_sdmmc *host, unsigned char power_mode) if (err < 0) return err; - mdelay(5); + mdelay(1); err = rtsx_pci_write_register(pcr, CARD_OE, SD_OUTPUT_EN, SD_OUTPUT_EN); if (err < 0) From 5a4923726a165593d7601834a6fb2a10ab47b85d Mon Sep 17 00:00:00 2001 From: Haotian Zhang Date: Sun, 14 Dec 2025 22:58:03 +0800 Subject: [PATCH 3727/3902] mfd: arizona: Fix regulator resource leak on wm5102_clear_write_sequencer() failure [ Upstream commit 4feb753ba6e5e5bbaba868b841a2db41c21e56fa ] The wm5102_clear_write_sequencer() helper may return an error and just return, bypassing the cleanup sequence and causing regulators to remain enabled, leading to a resource leak. Change the direct return to jump to the err_reset label to properly free the resources. Fixes: 1c1c6bba57f5 ("mfd: wm5102: Ensure we always boot the device fully") Signed-off-by: Haotian Zhang Reviewed-by: Charles Keepax Link: https://patch.msgid.link/20251214145804.2037-1-vulab@iscas.ac.cn Signed-off-by: Lee Jones Signed-off-by: Sasha Levin --- drivers/mfd/arizona-core.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/mfd/arizona-core.c b/drivers/mfd/arizona-core.c index 85ff8717d85047..91975536d14d21 100644 --- a/drivers/mfd/arizona-core.c +++ b/drivers/mfd/arizona-core.c @@ -1100,7 +1100,7 @@ int arizona_dev_init(struct arizona *arizona) } else if (val & 0x01) { ret = wm5102_clear_write_sequencer(arizona); if (ret) - return ret; + goto err_reset; } break; default: From bedc5b06493cc7c592e7833a8952531a9d58667f Mon Sep 17 00:00:00 2001 From: Robert Marko Date: Mon, 12 Jan 2026 17:14:52 +0100 Subject: [PATCH 3728/3902] mfd: simple-mfd-i2c: Add Delta TN48M CPLD support [ Upstream commit 8f34c1a64c5394d2b51d3fba197947dc4b0b48a0 ] Delta TN48M switches have a Lattice CPLD that serves multiple purposes including being a GPIO expander. So, lets use the simple I2C MFD driver to provide the MFD core. Also add a virtual symbol which pulls in the simple-mfd-i2c driver and provide a common symbol on which the subdevice drivers can depend on. Fixes: b3dcb5de6209 ("gpio: Add Delta TN48M CPLD GPIO driver") Signed-off-by: Robert Marko Link: https://lore.kernel.org/20220131133049.77780-2-robert.marko@sartura.hr Link: https://lore.kernel.org/linux-gpio/20260112064950.3837737-1-rdunlap@infradead.org/ Signed-off-by: Linus Walleij Link: https://patch.msgid.link/20260112-mfd-tn48m-v11-1-00c798d8cd2a@kernel.org Signed-off-by: Lee Jones Signed-off-by: Sasha Levin --- drivers/mfd/Kconfig | 11 +++++++++++ drivers/mfd/simple-mfd-i2c.c | 1 + 2 files changed, 12 insertions(+) diff --git a/drivers/mfd/Kconfig b/drivers/mfd/Kconfig index 6cec1858947bf7..55a9fea95195bc 100644 --- a/drivers/mfd/Kconfig +++ b/drivers/mfd/Kconfig @@ -406,6 +406,17 @@ config MFD_CS47L92 help Support for Cirrus Logic CS42L92, CS47L92 and CS47L93 Smart Codecs +config MFD_TN48M_CPLD + tristate "Delta Networks TN48M switch CPLD driver" + depends on I2C + depends on ARCH_MVEBU || COMPILE_TEST + select MFD_SIMPLE_MFD_I2C + help + Select this option to enable support for Delta Networks TN48M switch + CPLD. It consists of reset and GPIO drivers. CPLD provides GPIOS-s + for the SFP slots as well as power supply related information. + SFP support depends on the GPIO driver being selected. + config PMIC_DA903X bool "Dialog Semiconductor DA9030/DA9034 PMIC Support" depends on I2C=y diff --git a/drivers/mfd/simple-mfd-i2c.c b/drivers/mfd/simple-mfd-i2c.c index 0a607a1e3ca1de..9f911afafc25ef 100644 --- a/drivers/mfd/simple-mfd-i2c.c +++ b/drivers/mfd/simple-mfd-i2c.c @@ -110,6 +110,7 @@ static const struct simple_mfd_data spacemit_p1 = { }; static const struct of_device_id simple_mfd_i2c_of_match[] = { + { .compatible = "delta,tn48m-cpld" }, { .compatible = "fsl,ls1028aqds-fpga" }, { .compatible = "fsl,lx2160aqds-fpga" }, { .compatible = "fsl,lx2160ardb-fpga" }, From 9b8c7a25809ef3440d9f5042359641b9575ec801 Mon Sep 17 00:00:00 2001 From: Svyatoslav Ryhel Date: Thu, 22 Jan 2026 17:34:25 +0200 Subject: [PATCH 3729/3902] drivers: iio: mpu3050: use dev_err_probe for regulator request [ Upstream commit b010880b9936da14f8035585ab57577aa05be23a ] Regulator requesting may result in deferred probing error which will abort driver probing. To avoid this just use dev_err_probe which handles deferred probing. Fixes: 3904b28efb2c ("iio: gyro: Add driver for the MPU-3050 gyroscope") Signed-off-by: Svyatoslav Ryhel Reviewed-by: Andy Shevchenko Signed-off-by: Jonathan Cameron Signed-off-by: Sasha Levin --- drivers/iio/gyro/mpu3050-core.c | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/drivers/iio/gyro/mpu3050-core.c b/drivers/iio/gyro/mpu3050-core.c index 67ae7d1012bc27..ee2fcd20545dee 100644 --- a/drivers/iio/gyro/mpu3050-core.c +++ b/drivers/iio/gyro/mpu3050-core.c @@ -1162,10 +1162,8 @@ int mpu3050_common_probe(struct device *dev, mpu3050->regs[1].supply = mpu3050_reg_vlogic; ret = devm_regulator_bulk_get(dev, ARRAY_SIZE(mpu3050->regs), mpu3050->regs); - if (ret) { - dev_err(dev, "Cannot get regulators\n"); - return ret; - } + if (ret) + return dev_err_probe(dev, ret, "Cannot get regulators\n"); ret = mpu3050_power_up(mpu3050); if (ret) From 0c930fcadb2fe772ca9461f491eab5d24244c207 Mon Sep 17 00:00:00 2001 From: Justin Chen Date: Tue, 20 Jan 2026 12:07:54 -0800 Subject: [PATCH 3730/3902] usb: bdc: fix sleep during atomic [ Upstream commit f1195ca3b4bbd001d3f1264dce91f83dec7777f5 ] bdc_run() can be ran during atomic context leading to a sleep during atomic warning. Fix this by replacing read_poll_timeout() with read_poll_timeout_atomic(). Fixes: 75ae051efc9b ("usb: gadget: bdc: use readl_poll_timeout() to simplify code") Signed-off-by: Justin Chen Reviewed-by: Florian Fainelli Link: https://patch.msgid.link/20260120200754.2488765-1-justin.chen@broadcom.com Signed-off-by: Greg Kroah-Hartman Signed-off-by: Sasha Levin --- drivers/usb/gadget/udc/bdc/bdc_core.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/usb/gadget/udc/bdc/bdc_core.c b/drivers/usb/gadget/udc/bdc/bdc_core.c index 5c3d8b64c0e76e..f47aac078f6be6 100644 --- a/drivers/usb/gadget/udc/bdc/bdc_core.c +++ b/drivers/usb/gadget/udc/bdc/bdc_core.c @@ -35,8 +35,8 @@ static int poll_oip(struct bdc *bdc, u32 usec) u32 status; int ret; - ret = readl_poll_timeout(bdc->regs + BDC_BDCSC, status, - (BDC_CSTS(status) != BDC_OIP), 10, usec); + ret = readl_poll_timeout_atomic(bdc->regs + BDC_BDCSC, status, + (BDC_CSTS(status) != BDC_OIP), 10, usec); if (ret) dev_err(bdc->dev, "operation timedout BDCSC: 0x%08x\n", status); else From ab08bb8363b4610534e3ad4030a187c232fba7b1 Mon Sep 17 00:00:00 2001 From: Randy Dunlap Date: Fri, 16 Jan 2026 17:08:45 +0000 Subject: [PATCH 3731/3902] nvmem: an8855: drop an unused Kconfig symbol [ Upstream commit 4796eaafd6a170db012395a40385d2baf4f4d118 ] MFD_AIROHA_AN8855 is referenced here but never defined, so drop it from the Kconfig file. Fixes: e2258cfd9b98 ("nvmem: an8855: Add support for Airoha AN8855 Switch EFUSE") Signed-off-by: Randy Dunlap Signed-off-by: Srinivas Kandagatla Link: https://patch.msgid.link/20260116170846.733558-4-srini@kernel.org Signed-off-by: Greg Kroah-Hartman Signed-off-by: Sasha Levin --- drivers/nvmem/Kconfig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/nvmem/Kconfig b/drivers/nvmem/Kconfig index e0d88d3199c11a..11b098705ec629 100644 --- a/drivers/nvmem/Kconfig +++ b/drivers/nvmem/Kconfig @@ -30,7 +30,7 @@ source "drivers/nvmem/layouts/Kconfig" config NVMEM_AN8855_EFUSE tristate "Airoha AN8855 eFuse support" - depends on MFD_AIROHA_AN8855 || COMPILE_TEST + depends on COMPILE_TEST help Say y here to enable support for reading eFuses on Airoha AN8855 Switch. These are e.g. used to store factory programmed From 18a3ebff869c9fdf75cb64b996bb2e35d10befbe Mon Sep 17 00:00:00 2001 From: Jose Javier Rodriguez Barbarin Date: Fri, 16 Jan 2026 12:21:41 +0100 Subject: [PATCH 3732/3902] mcb: fix incorrect sanity check [ Upstream commit bc2e4bc952e26dd93b978588219044bd8b24237b ] __mcb_register_driver() makes some sanity checks over mcb_driver to check if .probe and .remove callbacks are set. However, since commit 3bd13ae04ccc ("gpio: menz127: simplify error path and remove remove()") removed the .remove callback from menz127-gpio.c, not all mcb device drivers implement .remove callback. Remove .remove check to ensure all mcb device drivers can be loaded. Signed-off-by: Jose Javier Rodriguez Barbarin Fixes: 3bd13ae04ccc ("gpio: menz127: simplify error path and remove remove()") [ jth: added statement about menz127-gpio.c ] Signed-off-by: Johannes Thumshirn Link: https://patch.msgid.link/16fb55bd59d9c1d2ce2443f41d4dec2048f9a8ec.1768562302.git.jth@kernel.org Signed-off-by: Greg Kroah-Hartman Signed-off-by: Sasha Levin --- drivers/mcb/mcb-core.c | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/drivers/mcb/mcb-core.c b/drivers/mcb/mcb-core.c index c1367223e71a0f..3d487d75c483d7 100644 --- a/drivers/mcb/mcb-core.c +++ b/drivers/mcb/mcb-core.c @@ -85,7 +85,8 @@ static void mcb_remove(struct device *dev) struct mcb_device *mdev = to_mcb_device(dev); struct module *carrier_mod; - mdrv->remove(mdev); + if (mdrv->remove) + mdrv->remove(mdev); carrier_mod = mdev->dev.parent->driver->owner; module_put(carrier_mod); @@ -176,13 +177,13 @@ static const struct device_type mcb_carrier_device_type = { * @owner: The @mcb_driver's module * @mod_name: The name of the @mcb_driver's module * - * Register a @mcb_driver at the system. Perform some sanity checks, if - * the .probe and .remove methods are provided by the driver. + * Register a @mcb_driver at the system. Perform a sanity check, if + * .probe method is provided by the driver. */ int __mcb_register_driver(struct mcb_driver *drv, struct module *owner, const char *mod_name) { - if (!drv->probe || !drv->remove) + if (!drv->probe) return -EINVAL; drv->driver.owner = owner; From a5f1f8ebc6e2a351a138fd7e9609e7c1ef8966c6 Mon Sep 17 00:00:00 2001 From: Felix Gu Date: Tue, 27 Jan 2026 01:30:07 +0800 Subject: [PATCH 3733/3902] pinctrl: equilibrium: Fix device node reference leak in pinbank_init() [ Upstream commit c0b4a4feeb43305a754893d8d9c6b2b5a52d45ac ] When calling of_parse_phandle_with_fixed_args(), the caller is responsible to call of_node_put() to release the reference of device node. In pinbank_init(), the reference of the node obtained from the "gpio-ranges" property is never released, resulting in a reference count leak. Add the missing of_node_put() call to fix the leak. Fixes: 1948d5c51dba ("pinctrl: Add pinmux & GPIO controller driver for a new SoC") Signed-off-by: Felix Gu Acked-by: Andy Shevchenko Signed-off-by: Linus Walleij Signed-off-by: Sasha Levin --- drivers/pinctrl/pinctrl-equilibrium.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/pinctrl/pinctrl-equilibrium.c b/drivers/pinctrl/pinctrl-equilibrium.c index 2d04829b29c997..48b55c5bf8d4fd 100644 --- a/drivers/pinctrl/pinctrl-equilibrium.c +++ b/drivers/pinctrl/pinctrl-equilibrium.c @@ -846,6 +846,7 @@ static int pinbank_init(struct device_node *np, bank->pin_base = spec.args[1]; bank->nr_pins = spec.args[2]; + of_node_put(spec.np); bank->aval_pinmap = readl(bank->membase + REG_AVAIL); bank->id = id; From 8ae999c410e2bb1a4a2c1cc80e9ff672b8c1fd81 Mon Sep 17 00:00:00 2001 From: Qing Wang Date: Wed, 28 Jan 2026 14:24:04 +0100 Subject: [PATCH 3734/3902] ovl: Fix uninit-value in ovl_fill_real [ Upstream commit 1992330d90dd766fcf1730fd7bf2d6af65370ac4 ] Syzbot reported a KMSAN uninit-value issue in ovl_fill_real. This iusse's call chain is: __do_sys_getdents64() -> iterate_dir() ... -> ext4_readdir() -> fscrypt_fname_alloc_buffer() // alloc -> fscrypt_fname_disk_to_usr // write without tail '\0' -> dir_emit() -> ovl_fill_real() // read by strcmp() The string is used to store the decrypted directory entry name for an encrypted inode. As shown in the call chain, fscrypt_fname_disk_to_usr() write it without null-terminate. However, ovl_fill_real() uses strcmp() to compare the name against "..", which assumes a null-terminated string and may trigger a KMSAN uninit-value warning when the buffer tail contains uninit data. Reported-by: syzbot+d130f98b2c265fae5297@syzkaller.appspotmail.com Closes: https://syzkaller.appspot.com/bug?extid=d130f98b2c265fae5297 Fixes: 4edb83bb1041 ("ovl: constant d_ino for non-merge dirs") Signed-off-by: Qing Wang Signed-off-by: Amir Goldstein Link: https://patch.msgid.link/20260128132406.23768-2-amir73il@gmail.com Acked-by: Miklos Szeredi Reviewed-by: Eric Biggers Signed-off-by: Christian Brauner Signed-off-by: Sasha Levin --- fs/overlayfs/readdir.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fs/overlayfs/readdir.c b/fs/overlayfs/readdir.c index 1e9792cc557b8d..3c27e7a16f94b1 100644 --- a/fs/overlayfs/readdir.c +++ b/fs/overlayfs/readdir.c @@ -761,7 +761,7 @@ static bool ovl_fill_real(struct dir_context *ctx, const char *name, struct dir_context *orig_ctx = rdt->orig_ctx; bool res; - if (rdt->parent_ino && strcmp(name, "..") == 0) { + if (rdt->parent_ino && namelen == 2 && !strncmp(name, "..", 2)) { ino = rdt->parent_ino; } else if (rdt->cache) { struct ovl_cache_entry *p; From 517d9f2b963089b3d64c23accf7920d77f5a30c8 Mon Sep 17 00:00:00 2001 From: Harshit Mogalapalli Date: Tue, 27 Jan 2026 22:49:49 -0800 Subject: [PATCH 3735/3902] iio: sca3000: Fix a resource leak in sca3000_probe() [ Upstream commit 62b44ebc1f2c71db3ca2d4737c52e433f6f03038 ] spi->irq from request_threaded_irq() not released when iio_device_register() fails. Add an return value check and jump to a common error handler when iio_device_register() fails. Fixes: 9a4936dc89a3 ("staging:iio:accel:sca3000 Tidy up probe order to avoid a race.") Signed-off-by: Harshit Mogalapalli Reviewed-by: Andy Shevchenko Signed-off-by: Jonathan Cameron Signed-off-by: Sasha Levin --- drivers/iio/accel/sca3000.c | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/drivers/iio/accel/sca3000.c b/drivers/iio/accel/sca3000.c index bfa8a3f5a92f43..9ef4d6e2746697 100644 --- a/drivers/iio/accel/sca3000.c +++ b/drivers/iio/accel/sca3000.c @@ -1489,7 +1489,11 @@ static int sca3000_probe(struct spi_device *spi) if (ret) goto error_free_irq; - return iio_device_register(indio_dev); + ret = iio_device_register(indio_dev); + if (ret) + goto error_free_irq; + + return 0; error_free_irq: if (spi->irq) From 21aff1255d7284eaa019c503c2c1a611096ce38c Mon Sep 17 00:00:00 2001 From: Randy Dunlap Date: Sun, 28 Dec 2025 11:04:43 -0800 Subject: [PATCH 3736/3902] mips: LOONGSON32: drop a dangling Kconfig symbol [ Upstream commit d463fc5ca1ace0b2e8bb764df04fc12ecd6f8e2b ] CPU_HAS_LOAD_STORE_LR is not used anywhere in the kernel sources, so drop it. Fixes: 85c4354076ca ("MIPS: loongson32: Switch to generic core") Signed-off-by: Randy Dunlap Reviewed-by: Keguang Zhang Signed-off-by: Thomas Bogendoerfer Signed-off-by: Sasha Levin --- arch/mips/Kconfig | 1 - 1 file changed, 1 deletion(-) diff --git a/arch/mips/Kconfig b/arch/mips/Kconfig index e8683f58fd3e2a..83a6b68d8a39ae 100644 --- a/arch/mips/Kconfig +++ b/arch/mips/Kconfig @@ -1408,7 +1408,6 @@ config CPU_LOONGSON32 select CPU_MIPS32 select CPU_MIPSR2 select CPU_HAS_PREFETCH - select CPU_HAS_LOAD_STORE_LR select CPU_SUPPORTS_32BIT_KERNEL select CPU_SUPPORTS_HIGHMEM select CPU_SUPPORTS_CPUFREQ From 7446125afb6d9d4f15fd3b6c6e7e7cf0ca91557c Mon Sep 17 00:00:00 2001 From: Luca Boccassi Date: Tue, 27 Jan 2026 22:51:37 +0000 Subject: [PATCH 3737/3902] pidfs: return -EREMOTE when PIDFD_GET_INFO is called on another ns [ Upstream commit ab89060fbc92edd6e852bf0f533f29140afabe0e ] Currently it is not possible to distinguish between the case where a process has already exited and the case where a process is in a different namespace, as both return -ESRCH. glibc's pidfd_getpid() procfs-based implementation returns -EREMOTE in the latter, so that distinguishing the two is possible, as the fdinfo in procfs will list '0' as the PID in that case: https://sourceware.org/git/?p=glibc.git;a=blob;f=sysdeps/unix/sysv/linux/pidfd_getpid.c;h=860829cf07da2267484299ccb02861822c0d07b4;hb=HEAD#l121 Change the error code so that the kernel also returns -EREMOTE in that case. Fixes: 7477d7dce48a ("pidfs: allow to retrieve exit information") Signed-off-by: Luca Boccassi Link: https://patch.msgid.link/20260127225209.2293342-1-luca.boccassi@gmail.com Signed-off-by: Christian Brauner Signed-off-by: Sasha Levin --- fs/pidfs.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fs/pidfs.c b/fs/pidfs.c index f4d7dac1b44949..34987fcdd9a87e 100644 --- a/fs/pidfs.c +++ b/fs/pidfs.c @@ -321,7 +321,7 @@ static long pidfd_info(struct file *file, unsigned int cmd, unsigned long arg) * namespace hierarchy. */ if (!pid_in_current_pidns(pid)) - return -ESRCH; + return -EREMOTE; attr = READ_ONCE(pid->attr); if (mask & PIDFD_INFO_EXIT) { From a851ed0ff9015f855afb0c0728fc39142a719788 Mon Sep 17 00:00:00 2001 From: Luca Weiss Date: Wed, 28 Jan 2026 12:22:28 +0100 Subject: [PATCH 3738/3902] pinctrl: qcom: sm8250-lpass-lpi: Fix i2s2_data_groups definition [ Upstream commit eabf273c8466af3f033473c2d2267a6ea7946d57 ] The i2s2_data function is available on both gpio12 and gpio13. Fix the groups definition. Fixes: 6e261d1090d6 ("pinctrl: qcom: Add sm8250 lpass lpi pinctrl driver") Signed-off-by: Luca Weiss Reviewed-by: Dmitry Baryshkov Reviewed-by: Konrad Dybcio Signed-off-by: Linus Walleij Signed-off-by: Sasha Levin --- drivers/pinctrl/qcom/pinctrl-sm8250-lpass-lpi.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/pinctrl/qcom/pinctrl-sm8250-lpass-lpi.c b/drivers/pinctrl/qcom/pinctrl-sm8250-lpass-lpi.c index 64494a86490e2f..c27452eece3e6b 100644 --- a/drivers/pinctrl/qcom/pinctrl-sm8250-lpass-lpi.c +++ b/drivers/pinctrl/qcom/pinctrl-sm8250-lpass-lpi.c @@ -73,7 +73,7 @@ static const char * const i2s1_ws_groups[] = { "gpio7" }; static const char * const i2s1_data_groups[] = { "gpio8", "gpio9" }; static const char * const wsa_swr_clk_groups[] = { "gpio10" }; static const char * const wsa_swr_data_groups[] = { "gpio11" }; -static const char * const i2s2_data_groups[] = { "gpio12", "gpio12" }; +static const char * const i2s2_data_groups[] = { "gpio12", "gpio13" }; static const struct lpi_pingroup sm8250_groups[] = { LPI_PINGROUP(0, 0, swr_tx_clk, qua_mi2s_sclk, _, _), From fc356d8c3042f3fe9863b6e66be431f7b52c370e Mon Sep 17 00:00:00 2001 From: Felix Gu Date: Wed, 28 Jan 2026 02:23:12 +0800 Subject: [PATCH 3739/3902] pinctrl: meson: amlogic-a4: Fix device node reference leak in bank helpers [ Upstream commit e56aa18eba32fb68ac5e19e44670010095bb189c ] of_parse_phandle_with_fixed_args() increments the reference count of the returned device node, so it must be explicitly released using of_node_put() after use. Fix the reference leak in aml_bank_pins() and aml_bank_number() by adding the missing of_node_put() calls. Fixes: 6e9be3abb78c ("pinctrl: Add driver support for Amlogic SoCs") Signed-off-by: Felix Gu Reviewed-by: Xianwei Zhao Signed-off-by: Linus Walleij Signed-off-by: Sasha Levin --- drivers/pinctrl/meson/pinctrl-amlogic-a4.c | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/drivers/pinctrl/meson/pinctrl-amlogic-a4.c b/drivers/pinctrl/meson/pinctrl-amlogic-a4.c index d9e3a8d5932a82..f05d8261624a4e 100644 --- a/drivers/pinctrl/meson/pinctrl-amlogic-a4.c +++ b/drivers/pinctrl/meson/pinctrl-amlogic-a4.c @@ -725,8 +725,9 @@ static u32 aml_bank_pins(struct device_node *np) if (of_parse_phandle_with_fixed_args(np, "gpio-ranges", 3, 0, &of_args)) return 0; - else - return of_args.args[2]; + + of_node_put(of_args.np); + return of_args.args[2]; } static int aml_bank_number(struct device_node *np) @@ -736,8 +737,9 @@ static int aml_bank_number(struct device_node *np) if (of_parse_phandle_with_fixed_args(np, "gpio-ranges", 3, 0, &of_args)) return -EINVAL; - else - return of_args.args[1] >> 8; + + of_node_put(of_args.np); + return of_args.args[1] >> 8; } static unsigned int aml_count_pins(struct device_node *np) From e2e367e56bacb93ce5ac73f0b3297d5c83d38dd4 Mon Sep 17 00:00:00 2001 From: Wei Li Date: Tue, 20 Jan 2026 08:07:35 +0000 Subject: [PATCH 3740/3902] pinctrl: single: fix refcount leak in pcs_add_gpio_func() [ Upstream commit 353353309b0f7afa407df29e455f9d15b5acc296 ] of_parse_phandle_with_args() returns a device_node pointer with refcount incremented in gpiospec.np. The loop iterates through all phandles but never releases the reference, causing a refcount leak on each iteration. Add of_node_put() calls to release the reference after extracting the needed arguments and on the error path when devm_kzalloc() fails. This bug was detected by our static analysis tool and verified by my code review. Fixes: a1a277eb76b3 ("pinctrl: single: create new gpio function range") Signed-off-by: Wei Li Signed-off-by: Linus Walleij Signed-off-by: Sasha Levin --- drivers/pinctrl/pinctrl-single.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/drivers/pinctrl/pinctrl-single.c b/drivers/pinctrl/pinctrl-single.c index 998f23d6c3179e..d85e6c1f632186 100644 --- a/drivers/pinctrl/pinctrl-single.c +++ b/drivers/pinctrl/pinctrl-single.c @@ -1359,6 +1359,7 @@ static int pcs_add_gpio_func(struct device_node *node, struct pcs_device *pcs) } range = devm_kzalloc(pcs->dev, sizeof(*range), GFP_KERNEL); if (!range) { + of_node_put(gpiospec.np); ret = -ENOMEM; break; } @@ -1368,6 +1369,7 @@ static int pcs_add_gpio_func(struct device_node *node, struct pcs_device *pcs) mutex_lock(&pcs->mutex); list_add_tail(&range->node, &pcs->gpiofuncs); mutex_unlock(&pcs->mutex); + of_node_put(gpiospec.np); } return ret; } From 3c7d637bfc3dfbd6471c68bd767f7eb8b5b09eba Mon Sep 17 00:00:00 2001 From: Jiayu Du Date: Sun, 28 Dec 2025 23:49:47 +0800 Subject: [PATCH 3741/3902] pinctrl: canaan: k230: Fix NULL pointer dereference when parsing devicetree [ Upstream commit d8c128fb6c2277d95f3f6a4ce28b82c8370031f6 ] When probing the k230 pinctrl driver, the kernel triggers a NULL pointer dereference. The crash trace showed: [ 0.732084] Unable to handle kernel NULL pointer dereference at virtual address 0000000000000068 [ 0.740737] ... [ 0.776296] epc : k230_pinctrl_probe+0x1be/0x4fc In k230_pinctrl_parse_functions(), we attempt to retrieve the device pointer via info->pctl_dev->dev, but info->pctl_dev is only initialized after k230_pinctrl_parse_dt() completes. At the time of DT parsing, info->pctl_dev is still NULL, leading to the invalid dereference of info->pctl_dev->dev. Use the already available device pointer from platform_device instead of accessing through uninitialized pctl_dev. Fixes: d94a32ac688f ("pinctrl: canaan: k230: Fix order of DT parse and pinctrl register") Signed-off-by: Jiayu Du Signed-off-by: Linus Walleij Signed-off-by: Sasha Levin --- drivers/pinctrl/pinctrl-k230.c | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/drivers/pinctrl/pinctrl-k230.c b/drivers/pinctrl/pinctrl-k230.c index d716f23d837f7a..20f7c0f70eb77c 100644 --- a/drivers/pinctrl/pinctrl-k230.c +++ b/drivers/pinctrl/pinctrl-k230.c @@ -65,6 +65,7 @@ struct k230_pmx_func { }; struct k230_pinctrl { + struct device *dev; struct pinctrl_desc pctl; struct pinctrl_dev *pctl_dev; struct regmap *regmap_base; @@ -470,7 +471,7 @@ static int k230_pinctrl_parse_groups(struct device_node *np, struct k230_pinctrl *info, unsigned int index) { - struct device *dev = info->pctl_dev->dev; + struct device *dev = info->dev; const __be32 *list; int size, i, ret; @@ -511,7 +512,7 @@ static int k230_pinctrl_parse_functions(struct device_node *np, struct k230_pinctrl *info, unsigned int index) { - struct device *dev = info->pctl_dev->dev; + struct device *dev = info->dev; struct k230_pmx_func *func; struct k230_pin_group *grp; static unsigned int idx, i; @@ -596,6 +597,8 @@ static int k230_pinctrl_probe(struct platform_device *pdev) if (!info) return -ENOMEM; + info->dev = dev; + pctl = &info->pctl; pctl->name = "k230-pinctrl"; From 4f45047e3bf0a9837fec148b6b3b9b6f1ae14133 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Duje=20Mihanovi=C4=87?= Date: Wed, 17 Dec 2025 19:14:23 +0100 Subject: [PATCH 3742/3902] leds: expresswire: Fix chip state breakage MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [ Upstream commit f4b830a5371914239756b0599e5dc9d4c328e387 ] It is possible to put the KTD2801 chip in an unknown/undefined state by changing the brightness very rapidly (for example, with a brightness slider). When this happens, the brightness is stuck on max and cannot be changed until the chip is power cycled. Fix this by disabling interrupts while talking to the chip. While at it, make expresswire_power_off() use fsleep() and also unexport some functions meant to be internal. Fixes: 1368d06dd2c9 ("leds: Introduce ExpressWire library") Tested-by: Karel Balej Signed-off-by: Duje Mihanović Link: https://patch.msgid.link/20251217-expresswire-fix-v2-1-4a02b10acd96@dujemihanovic.xyz Signed-off-by: Lee Jones Signed-off-by: Sasha Levin --- drivers/leds/leds-expresswire.c | 24 +++++++++++++++++------- include/linux/leds-expresswire.h | 3 --- 2 files changed, 17 insertions(+), 10 deletions(-) diff --git a/drivers/leds/leds-expresswire.c b/drivers/leds/leds-expresswire.c index bb69be228a6d3c..25c6b159a6ee93 100644 --- a/drivers/leds/leds-expresswire.c +++ b/drivers/leds/leds-expresswire.c @@ -9,6 +9,7 @@ #include #include #include +#include #include #include @@ -16,37 +17,41 @@ void expresswire_power_off(struct expresswire_common_props *props) { gpiod_set_value_cansleep(props->ctrl_gpio, 0); - usleep_range(props->timing.poweroff_us, props->timing.poweroff_us * 2); + fsleep(props->timing.poweroff_us); } EXPORT_SYMBOL_NS_GPL(expresswire_power_off, "EXPRESSWIRE"); void expresswire_enable(struct expresswire_common_props *props) { + unsigned long flags; + + local_irq_save(flags); + gpiod_set_value(props->ctrl_gpio, 1); udelay(props->timing.detect_delay_us); gpiod_set_value(props->ctrl_gpio, 0); udelay(props->timing.detect_us); gpiod_set_value(props->ctrl_gpio, 1); + + local_irq_restore(flags); } EXPORT_SYMBOL_NS_GPL(expresswire_enable, "EXPRESSWIRE"); -void expresswire_start(struct expresswire_common_props *props) +static void expresswire_start(struct expresswire_common_props *props) { gpiod_set_value(props->ctrl_gpio, 1); udelay(props->timing.data_start_us); } -EXPORT_SYMBOL_NS_GPL(expresswire_start, "EXPRESSWIRE"); -void expresswire_end(struct expresswire_common_props *props) +static void expresswire_end(struct expresswire_common_props *props) { gpiod_set_value(props->ctrl_gpio, 0); udelay(props->timing.end_of_data_low_us); gpiod_set_value(props->ctrl_gpio, 1); udelay(props->timing.end_of_data_high_us); } -EXPORT_SYMBOL_NS_GPL(expresswire_end, "EXPRESSWIRE"); -void expresswire_set_bit(struct expresswire_common_props *props, bool bit) +static void expresswire_set_bit(struct expresswire_common_props *props, bool bit) { if (bit) { gpiod_set_value(props->ctrl_gpio, 0); @@ -60,13 +65,18 @@ void expresswire_set_bit(struct expresswire_common_props *props, bool bit) udelay(props->timing.short_bitset_us); } } -EXPORT_SYMBOL_NS_GPL(expresswire_set_bit, "EXPRESSWIRE"); void expresswire_write_u8(struct expresswire_common_props *props, u8 val) { + unsigned long flags; + + local_irq_save(flags); + expresswire_start(props); for (int i = 7; i >= 0; i--) expresswire_set_bit(props, val & BIT(i)); expresswire_end(props); + + local_irq_restore(flags); } EXPORT_SYMBOL_NS_GPL(expresswire_write_u8, "EXPRESSWIRE"); diff --git a/include/linux/leds-expresswire.h b/include/linux/leds-expresswire.h index a422921f4159f2..7f8c4795f69fa0 100644 --- a/include/linux/leds-expresswire.h +++ b/include/linux/leds-expresswire.h @@ -30,9 +30,6 @@ struct expresswire_common_props { void expresswire_power_off(struct expresswire_common_props *props); void expresswire_enable(struct expresswire_common_props *props); -void expresswire_start(struct expresswire_common_props *props); -void expresswire_end(struct expresswire_common_props *props); -void expresswire_set_bit(struct expresswire_common_props *props, bool bit); void expresswire_write_u8(struct expresswire_common_props *props, u8 val); #endif /* _LEDS_EXPRESSWIRE_H */ From 784936cad37baf0e5dcfec337ac1cfac80498ae3 Mon Sep 17 00:00:00 2001 From: Haotian Zhang Date: Fri, 9 Jan 2026 01:51:33 +0800 Subject: [PATCH 3743/3902] leds: qcom-lpg: Check the return value of regmap_bulk_write() [ Upstream commit f42033b5ce8c79c5db645916c9a72ee3e10cecfa ] The lpg_lut_store() function currently ignores the return value of regmap_bulk_write() and always returns 0. This can cause hardware write failures to go undetected, leading the caller to believe LUT programming succeeded when it may have failed. Check the return value of regmap_bulk_write() in lpg_lut_store and return the error to the caller on failure. Fixes: 24e2d05d1b68 ("leds: Add driver for Qualcomm LPG") Signed-off-by: Haotian Zhang Link: https://patch.msgid.link/20260108175133.638-1-vulab@iscas.ac.cn Signed-off-by: Lee Jones Signed-off-by: Sasha Levin --- drivers/leds/rgb/leds-qcom-lpg.c | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/drivers/leds/rgb/leds-qcom-lpg.c b/drivers/leds/rgb/leds-qcom-lpg.c index e197f548cddb03..a460782dadca41 100644 --- a/drivers/leds/rgb/leds-qcom-lpg.c +++ b/drivers/leds/rgb/leds-qcom-lpg.c @@ -369,7 +369,7 @@ static int lpg_lut_store(struct lpg *lpg, struct led_pattern *pattern, { unsigned int idx; u16 val; - int i; + int i, ret; idx = bitmap_find_next_zero_area(lpg->lut_bitmap, lpg->lut_size, 0, len, 0); @@ -379,8 +379,10 @@ static int lpg_lut_store(struct lpg *lpg, struct led_pattern *pattern, for (i = 0; i < len; i++) { val = pattern[i].brightness; - regmap_bulk_write(lpg->map, lpg->lut_base + LPG_LUT_REG(idx + i), - &val, sizeof(val)); + ret = regmap_bulk_write(lpg->map, lpg->lut_base + LPG_LUT_REG(idx + i), + &val, sizeof(val)); + if (ret) + return ret; } bitmap_set(lpg->lut_bitmap, idx, len); From 54540e574092dc85b31b99a18020fbee6b8043e4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Barnab=C3=A1s=20Cz=C3=A9m=C3=A1n?= Date: Fri, 16 Jan 2026 08:07:34 +0100 Subject: [PATCH 3744/3902] backlight: qcom-wled: Support ovp values for PMI8994 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [ Upstream commit f29f972a6e7e3f187ea4d89b98a76c1981ca4d53 ] WLED4 found in PMI8994 supports different ovp values. Fixes: 6fc632d3e3e0 ("video: backlight: qcom-wled: Add PMI8994 compatible") Signed-off-by: Barnabás Czémán Reviewed-by: Konrad Dybcio Reviewed-by: Daniel Thompson (RISCstar) Link: https://patch.msgid.link/20260116-pmi8950-wled-v3-2-e6c93de84079@mainlining.org Signed-off-by: Lee Jones Signed-off-by: Sasha Levin --- drivers/video/backlight/qcom-wled.c | 41 +++++++++++++++++++++++++++-- 1 file changed, 39 insertions(+), 2 deletions(-) diff --git a/drivers/video/backlight/qcom-wled.c b/drivers/video/backlight/qcom-wled.c index a63bb42c8f8b03..5decbd39b78990 100644 --- a/drivers/video/backlight/qcom-wled.c +++ b/drivers/video/backlight/qcom-wled.c @@ -1244,6 +1244,15 @@ static const struct wled_var_cfg wled4_ovp_cfg = { .size = ARRAY_SIZE(wled4_ovp_values), }; +static const u32 pmi8994_wled_ovp_values[] = { + 31000, 29500, 19400, 17800, +}; + +static const struct wled_var_cfg pmi8994_wled_ovp_cfg = { + .values = pmi8994_wled_ovp_values, + .size = ARRAY_SIZE(pmi8994_wled_ovp_values), +}; + static inline u32 wled5_ovp_values_fn(u32 idx) { /* @@ -1357,6 +1366,29 @@ static int wled_configure(struct wled *wled) }, }; + const struct wled_u32_opts pmi8994_wled_opts[] = { + { + .name = "qcom,current-boost-limit", + .val_ptr = &cfg->boost_i_limit, + .cfg = &wled4_boost_i_limit_cfg, + }, + { + .name = "qcom,current-limit-microamp", + .val_ptr = &cfg->string_i_limit, + .cfg = &wled4_string_i_limit_cfg, + }, + { + .name = "qcom,ovp-millivolt", + .val_ptr = &cfg->ovp, + .cfg = &pmi8994_wled_ovp_cfg, + }, + { + .name = "qcom,switching-freq", + .val_ptr = &cfg->switch_freq, + .cfg = &wled3_switch_freq_cfg, + }, + }; + const struct wled_u32_opts wled5_opts[] = { { .name = "qcom,current-boost-limit", @@ -1423,8 +1455,13 @@ static int wled_configure(struct wled *wled) break; case 4: - u32_opts = wled4_opts; - size = ARRAY_SIZE(wled4_opts); + if (of_device_is_compatible(dev->of_node, "qcom,pmi8994-wled")) { + u32_opts = pmi8994_wled_opts; + size = ARRAY_SIZE(pmi8994_wled_opts); + } else { + u32_opts = wled4_opts; + size = ARRAY_SIZE(wled4_opts); + } *cfg = wled4_config_defaults; wled->wled_set_brightness = wled4_set_brightness; wled->wled_sync_toggle = wled3_sync_toggle; From 0832e88bc69f7ee2440b15d3d7d87070c26e31bb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Barnab=C3=A1s=20Cz=C3=A9m=C3=A1n?= Date: Fri, 16 Jan 2026 08:07:36 +0100 Subject: [PATCH 3745/3902] backlight: qcom-wled: Change PM8950 WLED configurations MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [ Upstream commit 83333aa97441ba7ce32b91e8a007c72d316a1c67 ] PMI8950 WLED needs same configurations as PMI8994 WLED. Fixes: 10258bf4534b ("backlight: qcom-wled: Add PMI8950 compatible") Signed-off-by: Barnabás Czémán Reviewed-by: Konrad Dybcio Reviewed-by: Daniel Thompson (RISCstar) Link: https://patch.msgid.link/20260116-pmi8950-wled-v3-4-e6c93de84079@mainlining.org Signed-off-by: Lee Jones Signed-off-by: Sasha Levin --- drivers/video/backlight/qcom-wled.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/drivers/video/backlight/qcom-wled.c b/drivers/video/backlight/qcom-wled.c index 5decbd39b78990..8054e4787725ef 100644 --- a/drivers/video/backlight/qcom-wled.c +++ b/drivers/video/backlight/qcom-wled.c @@ -1455,7 +1455,8 @@ static int wled_configure(struct wled *wled) break; case 4: - if (of_device_is_compatible(dev->of_node, "qcom,pmi8994-wled")) { + if (of_device_is_compatible(dev->of_node, "qcom,pmi8950-wled") || + of_device_is_compatible(dev->of_node, "qcom,pmi8994-wled")) { u32_opts = pmi8994_wled_opts; size = ARRAY_SIZE(pmi8994_wled_opts); } else { From bda244871179543dd3be7d093236cb33b2fb1765 Mon Sep 17 00:00:00 2001 From: Jared Kangas Date: Tue, 13 Jan 2026 11:46:50 -0800 Subject: [PATCH 3746/3902] dmaengine: fsl-edma: don't explicitly disable clocks in .remove() [ Upstream commit 666c53e94c1d0bf0bdf14c49505ece9ddbe725bc ] The clocks in fsl_edma_engine::muxclk are allocated and enabled with devm_clk_get_enabled(), which automatically cleans these resources up, but these clocks are also manually disabled in fsl_edma_remove(). This causes warnings on driver removal for each clock: edma_module already disabled WARNING: CPU: 0 PID: 418 at drivers/clk/clk.c:1200 clk_core_disable+0x198/0x1c8 [...] Call trace: clk_core_disable+0x198/0x1c8 (P) clk_disable+0x34/0x58 fsl_edma_remove+0x74/0xe8 [fsl_edma] [...] ---[ end trace 0000000000000000 ]--- edma_module already unprepared WARNING: CPU: 0 PID: 418 at drivers/clk/clk.c:1059 clk_core_unprepare+0x1f8/0x220 [...] Call trace: clk_core_unprepare+0x1f8/0x220 (P) clk_unprepare+0x34/0x58 fsl_edma_remove+0x7c/0xe8 [fsl_edma] [...] ---[ end trace 0000000000000000 ]--- Fix these warnings by removing the unnecessary fsl_disable_clocks() call in fsl_edma_remove(). Fixes: a9903de3aa16 ("dmaengine: fsl-edma: refactor using devm_clk_get_enabled") Signed-off-by: Jared Kangas Reviewed-by: Frank Li Link: https://patch.msgid.link/20260113-fsl-edma-clock-removal-v1-1-2025b49e7bcc@redhat.com Signed-off-by: Vinod Koul Signed-off-by: Sasha Levin --- drivers/dma/fsl-edma-main.c | 1 - 1 file changed, 1 deletion(-) diff --git a/drivers/dma/fsl-edma-main.c b/drivers/dma/fsl-edma-main.c index 97583c7d51a2e8..093185768ad8ef 100644 --- a/drivers/dma/fsl-edma-main.c +++ b/drivers/dma/fsl-edma-main.c @@ -915,7 +915,6 @@ static void fsl_edma_remove(struct platform_device *pdev) of_dma_controller_free(np); dma_async_device_unregister(&fsl_edma->dma_dev); fsl_edma_cleanup_vchan(&fsl_edma->dma_dev); - fsl_disable_clocks(fsl_edma, fsl_edma->drvdata->dmamuxs); } static int fsl_edma_suspend_late(struct device *dev) From c76647799272272f681bf87a9cc13c93f9eb7792 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christoph=20B=C3=B6hmwalder?= Date: Thu, 5 Feb 2026 18:39:29 +0100 Subject: [PATCH 3747/3902] drbd: always set BLK_FEAT_STABLE_WRITES MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [ Upstream commit 2ebc8d600fb907fa6b1e7095c0b6d84fc47e91ea ] DRBD requires stable pages because it may read the same bio data multiple times for local disk I/O and network transmission, and in some cases for calculating checksums. The BLK_FEAT_STABLE_WRITES flag is set when the device is first created, but blk_set_stacking_limits() clears it whenever a backing device is attached. In some cases the flag may be inherited from the backing device, but we want it to be enabled at all times. Unconditionally re-enable BLK_FEAT_STABLE_WRITES in drbd_reconsider_queue_parameters() after the queue parameter negotiations. Also, document why we want this flag enabled in the first place. Fixes: 1a02f3a73f8c ("block: move the stable_writes flag to queue_limits") Signed-off-by: Christoph Böhmwalder Reviewed-by: Christoph Hellwig Signed-off-by: Jens Axboe Signed-off-by: Sasha Levin --- drivers/block/drbd/drbd_main.c | 3 --- drivers/block/drbd/drbd_nl.c | 20 +++++++++++++++++++- 2 files changed, 19 insertions(+), 4 deletions(-) diff --git a/drivers/block/drbd/drbd_main.c b/drivers/block/drbd/drbd_main.c index c73376886e7a51..1f6ac9202b66a1 100644 --- a/drivers/block/drbd/drbd_main.c +++ b/drivers/block/drbd/drbd_main.c @@ -2659,9 +2659,6 @@ enum drbd_ret_code drbd_create_device(struct drbd_config_context *adm_ctx, unsig * connect. */ .max_hw_sectors = DRBD_MAX_BIO_SIZE_SAFE >> 8, - .features = BLK_FEAT_WRITE_CACHE | BLK_FEAT_FUA | - BLK_FEAT_ROTATIONAL | - BLK_FEAT_STABLE_WRITES, }; device = minor_to_device(minor); diff --git a/drivers/block/drbd/drbd_nl.c b/drivers/block/drbd/drbd_nl.c index 91f3b8afb63ce6..b502038be0a923 100644 --- a/drivers/block/drbd/drbd_nl.c +++ b/drivers/block/drbd/drbd_nl.c @@ -1296,6 +1296,8 @@ void drbd_reconsider_queue_parameters(struct drbd_device *device, lim.max_segments = drbd_backing_dev_max_segments(device); } else { lim.max_segments = BLK_MAX_SEGMENTS; + lim.features = BLK_FEAT_WRITE_CACHE | BLK_FEAT_FUA | + BLK_FEAT_ROTATIONAL | BLK_FEAT_STABLE_WRITES; } lim.max_hw_sectors = new >> SECTOR_SHIFT; @@ -1318,8 +1320,24 @@ void drbd_reconsider_queue_parameters(struct drbd_device *device, lim.max_hw_discard_sectors = 0; } - if (bdev) + if (bdev) { blk_stack_limits(&lim, &b->limits, 0); + /* + * blk_set_stacking_limits() cleared the features, and + * blk_stack_limits() may or may not have inherited + * BLK_FEAT_STABLE_WRITES from the backing device. + * + * DRBD always requires stable writes because: + * 1. The same bio data is read for both local disk I/O and + * network transmission. If the page changes mid-flight, + * the local and remote copies could diverge. + * 2. When data integrity is enabled, DRBD calculates a + * checksum before sending the data. If the page changes + * between checksum calculation and transmission, the + * receiver will detect a checksum mismatch. + */ + lim.features |= BLK_FEAT_STABLE_WRITES; + } /* * If we can handle "zeroes" efficiently on the protocol, we want to do From a9d157a9bd38e00d3cba8ff51f1b197acb4bcbc2 Mon Sep 17 00:00:00 2001 From: Pavel Begunkov Date: Sun, 15 Feb 2026 22:06:52 +0000 Subject: [PATCH 3748/3902] io_uring: delay sqarray static branch disablement [ Upstream commit 56112578c71213a10c995a56835bddb5e9ab1ed0 ] io_key_has_sqarray static branch can be easily switched on/off by the user every time patching the kernel. That can be very disruptive as it might require heavy synchronisation across all CPUs. Use deferred static keys, which can rate-limit it by deferring, batching and potentially effectively eliminating dec+inc pairs. Fixes: 9b296c625ac1d ("io_uring: static_key for !IORING_SETUP_NO_SQARRAY") Signed-off-by: Pavel Begunkov Signed-off-by: Jens Axboe Signed-off-by: Sasha Levin --- io_uring/io_uring.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/io_uring/io_uring.c b/io_uring/io_uring.c index d8a35a49dd1acf..65af47b9135b84 100644 --- a/io_uring/io_uring.c +++ b/io_uring/io_uring.c @@ -148,7 +148,7 @@ static bool io_uring_try_cancel_requests(struct io_ring_ctx *ctx, static void io_queue_sqe(struct io_kiocb *req, unsigned int extra_flags); static void __io_req_caches_free(struct io_ring_ctx *ctx); -static __read_mostly DEFINE_STATIC_KEY_FALSE(io_key_has_sqarray); +static __read_mostly DEFINE_STATIC_KEY_DEFERRED_FALSE(io_key_has_sqarray, HZ); struct kmem_cache *req_cachep; static struct workqueue_struct *iou_wq __ro_after_init; @@ -2390,7 +2390,7 @@ static bool io_get_sqe(struct io_ring_ctx *ctx, const struct io_uring_sqe **sqe) unsigned mask = ctx->sq_entries - 1; unsigned head = ctx->cached_sq_head++ & mask; - if (static_branch_unlikely(&io_key_has_sqarray) && + if (static_branch_unlikely(&io_key_has_sqarray.key) && (!(ctx->flags & IORING_SETUP_NO_SQARRAY))) { head = READ_ONCE(ctx->sq_array[head]); if (unlikely(head >= ctx->sq_entries)) { @@ -2869,7 +2869,7 @@ static __cold void io_ring_ctx_free(struct io_ring_ctx *ctx) io_rings_free(ctx); if (!(ctx->flags & IORING_SETUP_NO_SQARRAY)) - static_branch_dec(&io_key_has_sqarray); + static_branch_slow_dec_deferred(&io_key_has_sqarray); percpu_ref_exit(&ctx->refs); free_uid(ctx->user); @@ -3817,7 +3817,7 @@ static __cold int io_uring_create(unsigned entries, struct io_uring_params *p, ctx->clock_offset = 0; if (!(ctx->flags & IORING_SETUP_NO_SQARRAY)) - static_branch_inc(&io_key_has_sqarray); + static_branch_deferred_inc(&io_key_has_sqarray); if ((ctx->flags & IORING_SETUP_DEFER_TASKRUN) && !(ctx->flags & IORING_SETUP_IOPOLL) && From 5e573e7783641c0c10734f1468889f14d495d7ab Mon Sep 17 00:00:00 2001 From: Jens Axboe Date: Mon, 16 Feb 2026 14:16:27 -0700 Subject: [PATCH 3749/3902] io_uring/cancel: de-unionize file and user_data in struct io_cancel_data [ Upstream commit 22dbb0987bd1e0ec3b1e4ad20756a98f99aa4a08 ] By having them share the same space in struct io_cancel_data, it ends up disallowing IORING_ASYNC_CANCEL_FD|IORING_ASYNC_CANCEL_USERDATA from working. Eg you cannot match on both a file and user_data for cancelation purposes. This obviously isn't a common use case as nobody has reported this, but it does result in -ENOENT potentially being returned when trying to match on both, rather than actually doing what the API says it would. Fixes: 4bf94615b888 ("io_uring: allow IORING_OP_ASYNC_CANCEL with 'fd' key") Signed-off-by: Jens Axboe Signed-off-by: Sasha Levin --- io_uring/cancel.h | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/io_uring/cancel.h b/io_uring/cancel.h index 43e9bb74e9d19a..eaa4069e258c9b 100644 --- a/io_uring/cancel.h +++ b/io_uring/cancel.h @@ -6,10 +6,8 @@ struct io_cancel_data { struct io_ring_ctx *ctx; - union { - u64 data; - struct file *file; - }; + u64 data; + struct file *file; u8 opcode; u32 flags; int seq; From 5a30cc03bde169ad558695b26da6ea7e55f6194a Mon Sep 17 00:00:00 2001 From: Bartlomiej Kubik Date: Wed, 26 Nov 2025 23:02:51 +0100 Subject: [PATCH 3750/3902] fs/ntfs3: Initialize new folios before use [ Upstream commit f223ebffa185cc8da934333c5a31ff2d4f992dc9 ] KMSAN reports an uninitialized value in longest_match_std(), invoked from ntfs_compress_write(). When new folios are allocated without being marked uptodate and ni_read_frame() is skipped because the caller expects the frame to be completely overwritten, some reserved folios may remain only partially filled, leaving the rest memory uninitialized. Fixes: 584f60ba22f7 ("ntfs3: Convert ntfs_get_frame_pages() to use a folio") Tested-by: syzbot+08d8956768c96a2c52cf@syzkaller.appspotmail.com Reported-by: syzbot+08d8956768c96a2c52cf@syzkaller.appspotmail.com Closes: https://syzkaller.appspot.com/bug?extid=08d8956768c96a2c52cf Signed-off-by: Bartlomiej Kubik Signed-off-by: Konstantin Komarov Signed-off-by: Sasha Levin --- fs/ntfs3/file.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fs/ntfs3/file.c b/fs/ntfs3/file.c index 83f0072f0896c7..3e61eaf28e0885 100644 --- a/fs/ntfs3/file.c +++ b/fs/ntfs3/file.c @@ -930,7 +930,7 @@ static int ntfs_get_frame_pages(struct address_space *mapping, pgoff_t index, folio = __filemap_get_folio(mapping, index, FGP_LOCK | FGP_ACCESSED | FGP_CREAT, - gfp_mask); + gfp_mask | __GFP_ZERO); if (IS_ERR(folio)) { while (npages--) { folio = page_folio(pages[npages]); From 4bf3bafb8e0635ed93e3cd4156dcbcc0fb960cb4 Mon Sep 17 00:00:00 2001 From: Edward Adam Davis Date: Sun, 28 Dec 2025 11:53:25 +0800 Subject: [PATCH 3751/3902] fs/ntfs3: prevent infinite loops caused by the next valid being the same [ Upstream commit 27b75ca4e51e3e4554dc85dbf1a0246c66106fd3 ] When processing valid within the range [valid : pos), if valid cannot be retrieved correctly, for example, if the retrieved valid value is always the same, this can trigger a potential infinite loop, similar to the hung problem reported by syzbot [1]. Adding a check for the valid value within the loop body, and terminating the loop and returning -EINVAL if the value is the same as the current value, can prevent this. [1] INFO: task syz.4.21:6056 blocked for more than 143 seconds. Call Trace: rwbase_write_lock+0x14f/0x750 kernel/locking/rwbase_rt.c:244 inode_lock include/linux/fs.h:1027 [inline] ntfs_file_write_iter+0xe6/0x870 fs/ntfs3/file.c:1284 Fixes: 4342306f0f0d ("fs/ntfs3: Add file operations and implementation") Reported-by: syzbot+bcf9e1868c1a0c7e04f1@syzkaller.appspotmail.com Closes: https://syzkaller.appspot.com/bug?extid=bcf9e1868c1a0c7e04f1 Signed-off-by: Edward Adam Davis Signed-off-by: Konstantin Komarov Signed-off-by: Sasha Levin --- fs/ntfs3/file.c | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/fs/ntfs3/file.c b/fs/ntfs3/file.c index 3e61eaf28e0885..cd7aaeef45fe9a 100644 --- a/fs/ntfs3/file.c +++ b/fs/ntfs3/file.c @@ -1012,8 +1012,12 @@ static ssize_t ntfs_compress_write(struct kiocb *iocb, struct iov_iter *from) goto out; if (lcn == SPARSE_LCN) { - ni->i_valid = valid = - frame_vbo + ((u64)clen << sbi->cluster_bits); + valid = frame_vbo + ((u64)clen << sbi->cluster_bits); + if (ni->i_valid == valid) { + err = -EINVAL; + goto out; + } + ni->i_valid = valid; continue; } From 78942172d5bff4d4afed8674abc09cc560ce44a0 Mon Sep 17 00:00:00 2001 From: Jiasheng Jiang Date: Sat, 17 Jan 2026 16:50:24 +0000 Subject: [PATCH 3752/3902] fs/ntfs3: Fix slab-out-of-bounds read in DeleteIndexEntryRoot [ Upstream commit b2bc7c44ed1779fc9eaab9a186db0f0d01439622 ] In the 'DeleteIndexEntryRoot' case of the 'do_action' function, the entry size ('esize') is retrieved from the log record without adequate bounds checking. Specifically, the code calculates the end of the entry ('e2') using: e2 = Add2Ptr(e1, esize); It then calculates the size for memmove using 'PtrOffset(e2, ...)', which subtracts the end pointer from the buffer limit. If 'esize' is maliciously large, 'e2' exceeds the used buffer size. This results in a negative offset which, when cast to size_t for memmove, interprets as a massive unsigned integer, leading to a heap buffer overflow. This commit adds a check to ensure that the entry size ('esize') strictly fits within the remaining used space of the index header before performing memory operations. Fixes: b46acd6a6a62 ("fs/ntfs3: Add NTFS journal") Signed-off-by: Jiasheng Jiang Signed-off-by: Konstantin Komarov Signed-off-by: Sasha Levin --- fs/ntfs3/fslog.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/fs/ntfs3/fslog.c b/fs/ntfs3/fslog.c index 38934e6978ecec..28bd611f580d9c 100644 --- a/fs/ntfs3/fslog.c +++ b/fs/ntfs3/fslog.c @@ -3429,6 +3429,9 @@ static int do_action(struct ntfs_log *log, struct OPEN_ATTR_ENRTY *oe, e1 = Add2Ptr(attr, le16_to_cpu(lrh->attr_off)); esize = le16_to_cpu(e1->size); + if (PtrOffset(e1, Add2Ptr(hdr, used)) < esize) + goto dirty_vol; + e2 = Add2Ptr(e1, esize); memmove(e1, e2, PtrOffset(e2, Add2Ptr(hdr, used))); From 02978747e90722651fb887116f55fd5710e59067 Mon Sep 17 00:00:00 2001 From: Sean V Kelley Date: Wed, 11 Feb 2026 21:22:54 +0000 Subject: [PATCH 3753/3902] ACPI: CPPC: Fix remaining for_each_possible_cpu() to use online CPUs [ Upstream commit 56eb0c0ed345da7815274aa821a8546a073d7e97 ] per_cpu(cpc_desc_ptr, cpu) object is initialized for only the online CPUs via acpi_soft_cpu_online() --> __acpi_processor_start() --> acpi_cppc_processor_probe(). However, send_pcc_cmd() and acpi_get_psd_map() still iterate over all possible CPUs. In acpi_get_psd_map(), encountering an offline CPU returns -EFAULT, causing cppc_cpufreq initialization to fail. This breaks systems booted with "nosmt" or "nosmt=force". Fix by using for_each_online_cpu() in both functions. Fixes: 80b8286aeec0 ("ACPI / CPPC: support for batching CPPC requests") Signed-off-by: Sean V Kelley Link: https://patch.msgid.link/20260211212254.30190-1-skelley@nvidia.com Signed-off-by: Rafael J. Wysocki Signed-off-by: Sasha Levin --- drivers/acpi/cppc_acpi.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/acpi/cppc_acpi.c b/drivers/acpi/cppc_acpi.c index e66e20d1f31b76..b59b0100d03c51 100644 --- a/drivers/acpi/cppc_acpi.c +++ b/drivers/acpi/cppc_acpi.c @@ -362,7 +362,7 @@ static int send_pcc_cmd(int pcc_ss_id, u16 cmd) end: if (cmd == CMD_WRITE) { if (unlikely(ret)) { - for_each_possible_cpu(i) { + for_each_online_cpu(i) { struct cpc_desc *desc = per_cpu(cpc_desc_ptr, i); if (!desc) @@ -524,7 +524,7 @@ int acpi_get_psd_map(unsigned int cpu, struct cppc_cpudata *cpu_data) else if (pdomain->coord_type == DOMAIN_COORD_TYPE_SW_ANY) cpu_data->shared_type = CPUFREQ_SHARED_TYPE_ANY; - for_each_possible_cpu(i) { + for_each_online_cpu(i) { if (i == cpu) continue; From ba16fb47e8975c4aa190e27cf18d1c1453be7e16 Mon Sep 17 00:00:00 2001 From: Kuppuswamy Sathyanarayanan Date: Wed, 11 Feb 2026 14:34:01 -0800 Subject: [PATCH 3754/3902] powercap: intel_rapl_tpmi: Remove FW_BUG from invalid version check [ Upstream commit c7d54dafa042cf379859dba265fe5afef6fa8770 ] On partitioned systems, multiple TPMI instances may exist per package, but RAPL registers are only valid on one instance since RAPL has package-scope control. Other instances return invalid versions during domain parsing, which is expected behavior on such systems. Currently this generates a firmware bug warning: intel_rapl_tpmi: [Firmware Bug]: Invalid version Remove the FW_BUG tag, downgrade to pr_debug(), and update the message to clarify that invalid versions are expected on partitioned systems where only one instance can be valid. Fixes: 9eef7f9da928 ("powercap: intel_rapl: Introduce RAPL TPMI interface driver") Reported-by: Zhang Rui Signed-off-by: Kuppuswamy Sathyanarayanan Reviewed-by: Srinivas Pandruvada Link: https://patch.msgid.link/20260211223401.1575776-1-sathyanarayanan.kuppuswamy@linux.intel.com Signed-off-by: Rafael J. Wysocki Signed-off-by: Sasha Levin --- drivers/powercap/intel_rapl_tpmi.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/powercap/intel_rapl_tpmi.c b/drivers/powercap/intel_rapl_tpmi.c index 82201bf4685d44..34c0bd1edd61a3 100644 --- a/drivers/powercap/intel_rapl_tpmi.c +++ b/drivers/powercap/intel_rapl_tpmi.c @@ -157,7 +157,7 @@ static int parse_one_domain(struct tpmi_rapl_package *trp, u32 offset) tpmi_domain_flags = tpmi_domain_header >> 32 & 0xffff; if (tpmi_domain_version == TPMI_VERSION_INVALID) { - pr_warn(FW_BUG "Invalid version\n"); + pr_debug("Invalid version, other instances may be valid\n"); return -ENODEV; } From f056c340b73962ebaffe93997b582bdf16dc6270 Mon Sep 17 00:00:00 2001 From: Josh Poimboeuf Date: Tue, 10 Feb 2026 13:45:22 -0800 Subject: [PATCH 3755/3902] kbuild: Add objtool to top-level clean target [ Upstream commit 68b4fe32d73789dea23e356f468de67c8367ef8f ] Objtool is an integral part of the build, make sure it gets cleaned by "make clean" and "make mrproper". Fixes: 442f04c34a1a ("objtool: Add tool to perform compile-time stack metadata validation") Reported-by: Jens Remus Closes: https://lore.kernel.org/15f2af3b-be33-46fc-b972-6b8e7e0aa52e@linux.ibm.com Signed-off-by: Josh Poimboeuf Tested-by: Jens Remus Link: https://patch.msgid.link/968faf2ed30fa8b3519f79f01a1ecfe7929553e5.1770759919.git.jpoimboe@kernel.org [nathan: use Closes: instead of Link: per checkpatch.pl] Signed-off-by: Nathan Chancellor Signed-off-by: Sasha Levin --- Makefile | 11 ++++++++++- tools/objtool/Makefile | 2 ++ 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/Makefile b/Makefile index c4b22ec262784b..bc34a825aba80d 100644 --- a/Makefile +++ b/Makefile @@ -1440,6 +1440,15 @@ ifneq ($(wildcard $(resolve_btfids_O)),) $(Q)$(MAKE) -sC $(srctree)/tools/bpf/resolve_btfids O=$(resolve_btfids_O) clean endif +PHONY += objtool_clean + +objtool_O = $(abspath $(objtree))/tools/objtool + +objtool_clean: +ifneq ($(wildcard $(objtool_O)),) + $(Q)$(MAKE) -sC $(abs_srctree)/tools/objtool O=$(objtool_O) srctree=$(abs_srctree) clean +endif + tools/: FORCE $(Q)mkdir -p $(objtree)/tools $(Q)$(MAKE) O=$(abspath $(objtree)) subdir=tools -C $(srctree)/tools/ @@ -1603,7 +1612,7 @@ vmlinuxclean: $(Q)$(CONFIG_SHELL) $(srctree)/scripts/link-vmlinux.sh clean $(Q)$(if $(ARCH_POSTLINK), $(MAKE) -f $(ARCH_POSTLINK) clean) -clean: archclean vmlinuxclean resolve_btfids_clean +clean: archclean vmlinuxclean resolve_btfids_clean objtool_clean # mrproper - Delete all generated files, including .config # diff --git a/tools/objtool/Makefile b/tools/objtool/Makefile index 8c20361dd100ef..99d3897e046c66 100644 --- a/tools/objtool/Makefile +++ b/tools/objtool/Makefile @@ -7,6 +7,8 @@ srctree := $(patsubst %/,%,$(dir $(CURDIR))) srctree := $(patsubst %/,%,$(dir $(srctree))) endif +RM ?= rm -f + LIBSUBCMD_DIR = $(srctree)/tools/lib/subcmd/ ifneq ($(OUTPUT),) LIBSUBCMD_OUTPUT = $(abspath $(OUTPUT))/libsubcmd From f6bc23794fa050a278815c079a0c5520f66abc9b Mon Sep 17 00:00:00 2001 From: Aristeu Rozanski Date: Mon, 2 Feb 2026 09:38:05 -0500 Subject: [PATCH 3756/3902] selftests/memfd: use IPC semaphore instead of SIGSTOP/SIGCONT [ Upstream commit b24335521de92fd2ee22460072b75367ca8860b0 ] selftests/memfd: use IPC semaphore instead of SIGSTOP/SIGCONT In order to synchronize new processes to test inheritance of memfd_noexec sysctl, memfd_test sets up the sysctl with a value before creating the new process. The new process then sends itself a SIGSTOP in order to wait for the parent to flip the sysctl value and send a SIGCONT signal. This would work as intended if it wasn't the fact that the new process is being created with CLONE_NEWPID, which creates a new PID namespace and the new process has PID 1 in this namespace. There're restrictions on sending signals to PID 1 and, although it's relaxed for other than root PID namespace, it's biting us here. In this specific case the SIGSTOP sent by the new process is ignored (no error to kill() is returned) and it never stops its execution. This is usually not noticiable as the parent usually manages to set the new sysctl value before the child has a chance to run and the test succeeds. But if you run the test in a loop, it eventually reproduces: while [ 1 ]; do ./memfd_test >log 2>&1 || break; done; cat log So this patch replaces the SIGSTOP/SIGCONT synchronization with IPC semaphore. Link: https://lkml.kernel.org/r/a7776389-b3d6-4b18-b438-0b0e3ed1fd3b@work Fixes: 6469b66e3f5a ("selftests: improve vm.memfd_noexec sysctl tests") Signed-off-by: Aristeu Rozanski Cc: Aleksa Sarai Cc: Shuah Khan Cc: liuye Cc: Lorenzo Stoakes Signed-off-by: Andrew Morton Signed-off-by: Sasha Levin --- tools/testing/selftests/memfd/memfd_test.c | 113 +++++++++++++++++++-- 1 file changed, 105 insertions(+), 8 deletions(-) diff --git a/tools/testing/selftests/memfd/memfd_test.c b/tools/testing/selftests/memfd/memfd_test.c index 5b993924cc3f5b..2ca07ea7202a5f 100644 --- a/tools/testing/selftests/memfd/memfd_test.c +++ b/tools/testing/selftests/memfd/memfd_test.c @@ -18,6 +18,9 @@ #include #include #include +#include +#include +#include #include #include @@ -39,6 +42,20 @@ F_SEAL_EXEC) #define MFD_NOEXEC_SEAL 0x0008U +union semun { + int val; + struct semid_ds *buf; + unsigned short int *array; + struct seminfo *__buf; +}; + +/* + * we use semaphores on nested wait tasks due the use of CLONE_NEWPID: the + * child will be PID 1 and can't send SIGSTOP to themselves due special + * treatment of the init task, so the SIGSTOP/SIGCONT synchronization + * approach can't be used here. + */ +#define SEM_KEY 0xdeadbeef /* * Default is not to test hugetlbfs @@ -1333,8 +1350,22 @@ static int sysctl_nested(void *arg) static int sysctl_nested_wait(void *arg) { - /* Wait for a SIGCONT. */ - kill(getpid(), SIGSTOP); + int sem = semget(SEM_KEY, 1, 0600); + struct sembuf sembuf; + + if (sem < 0) { + perror("semget:"); + abort(); + } + sembuf.sem_num = 0; + sembuf.sem_flg = 0; + sembuf.sem_op = 0; + + if (semop(sem, &sembuf, 1) < 0) { + perror("semop:"); + abort(); + } + return sysctl_nested(arg); } @@ -1355,7 +1386,9 @@ static void test_sysctl_sysctl2_failset(void) static int sysctl_nested_child(void *arg) { - int pid; + int pid, sem; + union semun semun; + struct sembuf sembuf; printf("%s nested sysctl 0\n", memfd_str); sysctl_assert_write("0"); @@ -1389,23 +1422,53 @@ static int sysctl_nested_child(void *arg) test_sysctl_sysctl2_failset); join_thread(pid); + sem = semget(SEM_KEY, 1, IPC_CREAT | 0600); + if (sem < 0) { + perror("semget:"); + return 1; + } + semun.val = 1; + sembuf.sem_op = -1; + sembuf.sem_flg = 0; + sembuf.sem_num = 0; + /* Verify that the rules are actually inherited after fork. */ printf("%s nested sysctl 0 -> 1 after fork\n", memfd_str); sysctl_assert_write("0"); + if (semctl(sem, 0, SETVAL, semun) < 0) { + perror("semctl:"); + return 1; + } + pid = spawn_thread(CLONE_NEWPID, sysctl_nested_wait, test_sysctl_sysctl1_failset); sysctl_assert_write("1"); - kill(pid, SIGCONT); + + /* Allow child to continue */ + if (semop(sem, &sembuf, 1) < 0) { + perror("semop:"); + return 1; + } join_thread(pid); printf("%s nested sysctl 0 -> 2 after fork\n", memfd_str); sysctl_assert_write("0"); + if (semctl(sem, 0, SETVAL, semun) < 0) { + perror("semctl:"); + return 1; + } + pid = spawn_thread(CLONE_NEWPID, sysctl_nested_wait, test_sysctl_sysctl2_failset); sysctl_assert_write("2"); - kill(pid, SIGCONT); + + /* Allow child to continue */ + if (semop(sem, &sembuf, 1) < 0) { + perror("semop:"); + return 1; + } join_thread(pid); /* @@ -1415,28 +1478,62 @@ static int sysctl_nested_child(void *arg) */ printf("%s nested sysctl 2 -> 1 after fork\n", memfd_str); sysctl_assert_write("2"); + + if (semctl(sem, 0, SETVAL, semun) < 0) { + perror("semctl:"); + return 1; + } + pid = spawn_thread(CLONE_NEWPID, sysctl_nested_wait, test_sysctl_sysctl2); sysctl_assert_write("1"); - kill(pid, SIGCONT); + + /* Allow child to continue */ + if (semop(sem, &sembuf, 1) < 0) { + perror("semop:"); + return 1; + } join_thread(pid); printf("%s nested sysctl 2 -> 0 after fork\n", memfd_str); sysctl_assert_write("2"); + + if (semctl(sem, 0, SETVAL, semun) < 0) { + perror("semctl:"); + return 1; + } + pid = spawn_thread(CLONE_NEWPID, sysctl_nested_wait, test_sysctl_sysctl2); sysctl_assert_write("0"); - kill(pid, SIGCONT); + + /* Allow child to continue */ + if (semop(sem, &sembuf, 1) < 0) { + perror("semop:"); + return 1; + } join_thread(pid); printf("%s nested sysctl 1 -> 0 after fork\n", memfd_str); sysctl_assert_write("1"); + + if (semctl(sem, 0, SETVAL, semun) < 0) { + perror("semctl:"); + return 1; + } + pid = spawn_thread(CLONE_NEWPID, sysctl_nested_wait, test_sysctl_sysctl1); sysctl_assert_write("0"); - kill(pid, SIGCONT); + /* Allow child to continue */ + if (semop(sem, &sembuf, 1) < 0) { + perror("semop:"); + return 1; + } join_thread(pid); + semctl(sem, 0, IPC_RMID); + return 0; } From 43fcc84cd9710c2bc5a9abe6379a6d93e06344b4 Mon Sep 17 00:00:00 2001 From: zhouwenhao Date: Mon, 2 Feb 2026 21:28:46 +0800 Subject: [PATCH 3757/3902] objpool: fix the overestimation of object pooling metadata size [ Upstream commit 5ed4b6b37c647d168ae31035b3f61b705997e043 ] objpool uses struct objpool_head to store metadata information, and its cpu_slots member points to an array of pointers that store the addresses of the percpu ring arrays. However, the memory size allocated during the initialization of cpu_slots is nr_cpu_ids * sizeof(struct objpool_slot). On a 64-bit machine, the size of struct objpool_slot is 16 bytes, which is twice the size of the actual pointer required, and the extra memory is never be used, resulting in a waste of memory. Therefore, the memory size required for cpu_slots needs to be corrected. Link: https://lkml.kernel.org/r/20260202132846.68257-1-zhouwenhao7600@gmail.com Fixes: b4edb8d2d464 ("lib: objpool added: ring-array based lockless MPMC") Signed-off-by: zhouwenhao Reviewed-by: Andrew Morton Cc: "Masami Hiramatsu (Google)" Cc: Matt Wu Cc: wuqiang.matt Signed-off-by: Andrew Morton Signed-off-by: Sasha Levin --- lib/objpool.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/objpool.c b/lib/objpool.c index b998b720c7329d..d98fadf1de169f 100644 --- a/lib/objpool.c +++ b/lib/objpool.c @@ -142,7 +142,7 @@ int objpool_init(struct objpool_head *pool, int nr_objs, int object_size, pool->gfp = gfp & ~__GFP_ZERO; pool->context = context; pool->release = release; - slot_size = nr_cpu_ids * sizeof(struct objpool_slot); + slot_size = nr_cpu_ids * sizeof(struct objpool_slot *); pool->cpu_slots = kzalloc(slot_size, pool->gfp); if (!pool->cpu_slots) return -ENOMEM; From b800728a8aef66f1b89591d784ef20031648a794 Mon Sep 17 00:00:00 2001 From: Zhai Can Date: Sun, 15 Feb 2026 00:14:52 +0800 Subject: [PATCH 3758/3902] ACPI: PM: Add unused power resource quirk for THUNDEROBOT ZERO [ Upstream commit cd7ef20ba8c6e936dba133b4136537a8ada22976 ] On the THUNDEROBOT ZERO laptop, the second NVMe slot and the discrete NVIDIA GPU are both controlled by power-resource PXP. Due to the SSDT table bug (lack of reference), PXP will be shut dow as an "unused" power resource during initialization, making the NVMe slot #2 + NVIDIA both inaccessible. This issue was introduced by commit a1224f34d72a ("ACPI: PM: Check states of power resources during initialization"). Here are test results on the three consecutive commits: (bad again!) a1224f34d72a ACPI: PM: Check states of power resources during initialization (good) bc2836859643 ACPI: PM: Do not turn off power resources in unknown state (bad) 519d81956ee2 Linux 5.15-rc6 On commit bc2836859643 ("ACPI: PM: Do not turn off power resources in unknown state") this was not an issue because the power resource state left UNKNOWN thus being ignored. See also commit 9b04d99788cf ("ACPI: PM: Do not turn of unused power resources on the Toshiba Click Mini") which is another almost identical case to this one. Fixes: a1224f34d72a ("ACPI: PM: Check states of power resources during initialization") Closes: https://bugzilla.kernel.org/show_bug.cgi?id=221087 Signed-off-by: Zhai Can Link: https://patch.msgid.link/20260214161452.2849346-1-bczhc0@126.com Signed-off-by: Rafael J. Wysocki Signed-off-by: Sasha Levin --- drivers/acpi/power.c | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/drivers/acpi/power.c b/drivers/acpi/power.c index 361a7721a6a873..7da5ae5594a727 100644 --- a/drivers/acpi/power.c +++ b/drivers/acpi/power.c @@ -1113,6 +1113,19 @@ static const struct dmi_system_id dmi_leave_unused_power_resources_on[] = { DMI_MATCH(DMI_PRODUCT_NAME, "SATELLITE Click Mini L9W-B"), }, }, + { + /* + * THUNDEROBOT ZERO laptop: Due to its SSDT table bug, power + * resource 'PXP' will be shut down on initialization, making + * the NVMe #2 and the NVIDIA dGPU both unavailable (they're + * both controlled by 'PXP'). + */ + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "THUNDEROBOT"), + DMI_MATCH(DMI_PRODUCT_NAME, "ZERO"), + } + + }, {} }; From 63ae78336f40bcd9a44952a7c6bafb9c88a8effd Mon Sep 17 00:00:00 2001 From: Aboorva Devarajan Date: Tue, 17 Feb 2026 00:20:02 +0530 Subject: [PATCH 3759/3902] cpuidle: Skip governor when only one idle state is available [ Upstream commit e5c9ffc6ae1bcdb1062527d611043681ac301aca ] On certain platforms (PowerNV systems without a power-mgt DT node), cpuidle may register only a single idle state. In cases where that single state is a polling state (state 0), the ladder governor may incorrectly treat state 1 as the first usable state and pass an out-of-bounds index. This can lead to a NULL enter callback being invoked, ultimately resulting in a system crash. [ 13.342636] cpuidle-powernv : Only Snooze is available [ 13.351854] Faulting instruction address: 0x00000000 [ 13.376489] NIP [0000000000000000] 0x0 [ 13.378351] LR [c000000001e01974] cpuidle_enter_state+0x2c4/0x668 Fix this by adding a bail-out in cpuidle_select() that returns state 0 directly when state_count <= 1, bypassing the governor and keeping the tick running. Fixes: dc2251bf98c6 ("cpuidle: Eliminate the CPUIDLE_DRIVER_STATE_START symbol") Signed-off-by: Aboorva Devarajan Reviewed-by: Christian Loehle Link: https://patch.msgid.link/20260216185005.1131593-2-aboorvad@linux.ibm.com Signed-off-by: Rafael J. Wysocki Signed-off-by: Sasha Levin --- drivers/cpuidle/cpuidle.c | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/drivers/cpuidle/cpuidle.c b/drivers/cpuidle/cpuidle.c index 56132e843c9919..8950796a493deb 100644 --- a/drivers/cpuidle/cpuidle.c +++ b/drivers/cpuidle/cpuidle.c @@ -357,6 +357,16 @@ noinstr int cpuidle_enter_state(struct cpuidle_device *dev, int cpuidle_select(struct cpuidle_driver *drv, struct cpuidle_device *dev, bool *stop_tick) { + /* + * If there is only a single idle state (or none), there is nothing + * meaningful for the governor to choose. Skip the governor and + * always use state 0 with the tick running. + */ + if (drv->state_count <= 1) { + *stop_tick = false; + return 0; + } + return cpuidle_curr_governor->select(drv, dev, stop_tick); } From 6f076ba274e29e2ec1848ab8b86ce463613ad789 Mon Sep 17 00:00:00 2001 From: Ralf Lici Date: Fri, 30 Jan 2026 18:32:48 +0100 Subject: [PATCH 3760/3902] ovpn: set sk_user_data before overriding callbacks [ Upstream commit 93686c472eb7b09a51b97a096449e7092fefcd1f ] During initialization, we override socket callbacks and set sk_user_data to an ovpn_socket instance. Currently, these two operations are decoupled: callbacks are overridden before sk_user_data is set. While existing callbacks perform safety checks for NULL or non-ovpn sk_user_data, this condition causes a "half-formed" state where valid packets arriving during attachment trigger error logs (e.g., "invoked on non ovpn socket"). Set sk_user_data before overriding the callbacks so that it can be accessed safely from them. Since we already check that the socket has no sk_user_data before setting it, this remains safe even if an interrupt accesses the socket after sk_user_data is set but before the callbacks are overridden. This also requires initializing all protocol-specific fields (such as tcp_tx_work and peer links) before calling ovpn_socket_attach, ensuring the ovpn_socket is fully formed before it becomes visible to any callback. Fixes: f6226ae7a0cd ("ovpn: introduce the ovpn_socket object") Signed-off-by: Ralf Lici Reviewed-by: Sabrina Dubroca Signed-off-by: Antonio Quartulli Signed-off-by: Sasha Levin --- drivers/net/ovpn/socket.c | 39 +++++++++++++++++++++------------------ drivers/net/ovpn/tcp.c | 9 +++++++-- drivers/net/ovpn/udp.c | 1 + 3 files changed, 29 insertions(+), 20 deletions(-) diff --git a/drivers/net/ovpn/socket.c b/drivers/net/ovpn/socket.c index 9750871ab65cee..448cee3b3f9fa2 100644 --- a/drivers/net/ovpn/socket.c +++ b/drivers/net/ovpn/socket.c @@ -200,6 +200,22 @@ struct ovpn_socket *ovpn_socket_new(struct socket *sock, struct ovpn_peer *peer) ovpn_sock->sk = sk; kref_init(&ovpn_sock->refcount); + /* TCP sockets are per-peer, therefore they are linked to their unique + * peer + */ + if (sk->sk_protocol == IPPROTO_TCP) { + INIT_WORK(&ovpn_sock->tcp_tx_work, ovpn_tcp_tx_work); + ovpn_sock->peer = peer; + ovpn_peer_hold(peer); + } else if (sk->sk_protocol == IPPROTO_UDP) { + /* in UDP we only link the ovpn instance since the socket is + * shared among multiple peers + */ + ovpn_sock->ovpn = peer->ovpn; + netdev_hold(peer->ovpn->dev, &ovpn_sock->dev_tracker, + GFP_KERNEL); + } + /* the newly created ovpn_socket is holding reference to sk, * therefore we increase its refcounter. * @@ -212,29 +228,16 @@ struct ovpn_socket *ovpn_socket_new(struct socket *sock, struct ovpn_peer *peer) ret = ovpn_socket_attach(ovpn_sock, sock, peer); if (ret < 0) { + if (sk->sk_protocol == IPPROTO_TCP) + ovpn_peer_put(peer); + else if (sk->sk_protocol == IPPROTO_UDP) + netdev_put(peer->ovpn->dev, &ovpn_sock->dev_tracker); + sock_put(sk); kfree(ovpn_sock); ovpn_sock = ERR_PTR(ret); - goto sock_release; - } - - /* TCP sockets are per-peer, therefore they are linked to their unique - * peer - */ - if (sk->sk_protocol == IPPROTO_TCP) { - INIT_WORK(&ovpn_sock->tcp_tx_work, ovpn_tcp_tx_work); - ovpn_sock->peer = peer; - ovpn_peer_hold(peer); - } else if (sk->sk_protocol == IPPROTO_UDP) { - /* in UDP we only link the ovpn instance since the socket is - * shared among multiple peers - */ - ovpn_sock->ovpn = peer->ovpn; - netdev_hold(peer->ovpn->dev, &ovpn_sock->dev_tracker, - GFP_KERNEL); } - rcu_assign_sk_user_data(sk, ovpn_sock); sock_release: release_sock(sk); return ovpn_sock; diff --git a/drivers/net/ovpn/tcp.c b/drivers/net/ovpn/tcp.c index 0d7f30360d8746..f0b4e07ba9245a 100644 --- a/drivers/net/ovpn/tcp.c +++ b/drivers/net/ovpn/tcp.c @@ -487,6 +487,7 @@ int ovpn_tcp_socket_attach(struct ovpn_socket *ovpn_sock, /* make sure no pre-existing encapsulation handler exists */ if (ovpn_sock->sk->sk_user_data) return -EBUSY; + rcu_assign_sk_user_data(ovpn_sock->sk, ovpn_sock); /* only a fully connected socket is expected. Connection should be * handled in userspace @@ -495,13 +496,14 @@ int ovpn_tcp_socket_attach(struct ovpn_socket *ovpn_sock, net_err_ratelimited("%s: provided TCP socket is not in ESTABLISHED state: %d\n", netdev_name(peer->ovpn->dev), ovpn_sock->sk->sk_state); - return -EINVAL; + ret = -EINVAL; + goto err; } ret = strp_init(&peer->tcp.strp, ovpn_sock->sk, &cb); if (ret < 0) { DEBUG_NET_WARN_ON_ONCE(1); - return ret; + goto err; } INIT_WORK(&peer->tcp.defer_del_work, ovpn_tcp_peer_del_work); @@ -536,6 +538,9 @@ int ovpn_tcp_socket_attach(struct ovpn_socket *ovpn_sock, strp_check_rcv(&peer->tcp.strp); return 0; +err: + rcu_assign_sk_user_data(ovpn_sock->sk, NULL); + return ret; } static void ovpn_tcp_close(struct sock *sk, long timeout) diff --git a/drivers/net/ovpn/udp.c b/drivers/net/ovpn/udp.c index d6a0f7a0b75d74..272b535ecaad4c 100644 --- a/drivers/net/ovpn/udp.c +++ b/drivers/net/ovpn/udp.c @@ -386,6 +386,7 @@ int ovpn_udp_socket_attach(struct ovpn_socket *ovpn_sock, struct socket *sock, struct ovpn_priv *ovpn) { struct udp_tunnel_sock_cfg cfg = { + .sk_user_data = ovpn_sock, .encap_type = UDP_ENCAP_OVPNINUDP, .encap_rcv = ovpn_udp_encap_recv, .encap_destroy = ovpn_udp_encap_destroy, From 3e4fbcb4e078915367ba5576cd70d76dbc970f95 Mon Sep 17 00:00:00 2001 From: Ralf Lici Date: Fri, 30 Jan 2026 18:32:49 +0100 Subject: [PATCH 3761/3902] ovpn: fix possible use-after-free in ovpn_net_xmit [ Upstream commit a5ec7baa44ea3a1d6aa0ca31c0ad82edf9affe41 ] When building the skb_list in ovpn_net_xmit, skb_share_check will free the original skb if it is shared. The current implementation continues to use the stale skb pointer for subsequent operations: - peer lookup, - skb_dst_drop (even though all segments produced by skb_gso_segment will have a dst attached), - ovpn_peer_stats_increment_tx. Fix this by moving the peer lookup and skb_dst_drop before segmentation so that the original skb is still valid when used. Return early if all segments fail skb_share_check and the list ends up empty. Also switch ovpn_peer_stats_increment_tx to use skb_list.next; the next patch fixes the stats logic. Fixes: 08857b5ec5d9 ("ovpn: implement basic TX path (UDP)") Signed-off-by: Ralf Lici Reviewed-by: Sabrina Dubroca Signed-off-by: Antonio Quartulli Signed-off-by: Sasha Levin --- drivers/net/ovpn/io.c | 52 ++++++++++++++++++++++++++----------------- 1 file changed, 31 insertions(+), 21 deletions(-) diff --git a/drivers/net/ovpn/io.c b/drivers/net/ovpn/io.c index 3e9e7f8444b344..f70c58b10599bc 100644 --- a/drivers/net/ovpn/io.c +++ b/drivers/net/ovpn/io.c @@ -365,7 +365,27 @@ netdev_tx_t ovpn_net_xmit(struct sk_buff *skb, struct net_device *dev) /* verify IP header size in network packet */ proto = ovpn_ip_check_protocol(skb); if (unlikely(!proto || skb->protocol != proto)) - goto drop; + goto drop_no_peer; + + /* retrieve peer serving the destination IP of this packet */ + peer = ovpn_peer_get_by_dst(ovpn, skb); + if (unlikely(!peer)) { + switch (skb->protocol) { + case htons(ETH_P_IP): + net_dbg_ratelimited("%s: no peer to send data to dst=%pI4\n", + netdev_name(ovpn->dev), + &ip_hdr(skb)->daddr); + break; + case htons(ETH_P_IPV6): + net_dbg_ratelimited("%s: no peer to send data to dst=%pI6c\n", + netdev_name(ovpn->dev), + &ipv6_hdr(skb)->daddr); + break; + } + goto drop_no_peer; + } + /* dst was needed for peer selection - it can now be dropped */ + skb_dst_drop(skb); if (skb_is_gso(skb)) { segments = skb_gso_segment(skb, 0); @@ -396,34 +416,24 @@ netdev_tx_t ovpn_net_xmit(struct sk_buff *skb, struct net_device *dev) __skb_queue_tail(&skb_list, curr); } - skb_list.prev->next = NULL; - /* retrieve peer serving the destination IP of this packet */ - peer = ovpn_peer_get_by_dst(ovpn, skb); - if (unlikely(!peer)) { - switch (skb->protocol) { - case htons(ETH_P_IP): - net_dbg_ratelimited("%s: no peer to send data to dst=%pI4\n", - netdev_name(ovpn->dev), - &ip_hdr(skb)->daddr); - break; - case htons(ETH_P_IPV6): - net_dbg_ratelimited("%s: no peer to send data to dst=%pI6c\n", - netdev_name(ovpn->dev), - &ipv6_hdr(skb)->daddr); - break; - } - goto drop; + /* no segments survived: don't jump to 'drop' because we already + * incremented the counter for each failure in the loop + */ + if (unlikely(skb_queue_empty(&skb_list))) { + ovpn_peer_put(peer); + return NETDEV_TX_OK; } - /* dst was needed for peer selection - it can now be dropped */ - skb_dst_drop(skb); + skb_list.prev->next = NULL; - ovpn_peer_stats_increment_tx(&peer->vpn_stats, skb->len); + ovpn_peer_stats_increment_tx(&peer->vpn_stats, skb_list.next->len); ovpn_send(ovpn, skb_list.next, peer); return NETDEV_TX_OK; drop: + ovpn_peer_put(peer); +drop_no_peer: dev_dstats_tx_dropped(ovpn->dev); skb_tx_error(skb); kfree_skb_list(skb); From f4edf0c7d21695d3615493a76299e88c6ce0e8a6 Mon Sep 17 00:00:00 2001 From: Ralf Lici Date: Fri, 30 Jan 2026 18:32:50 +0100 Subject: [PATCH 3762/3902] ovpn: fix VPN TX bytes counting [ Upstream commit b660b13d4c6379ca6360f24aaef8c5807fefd237 ] In ovpn_net_xmit, after GSO segmentation and segment processing, the first segment on the list is used to increment VPN TX statistics, which fails to account for any subsequent segments in the chain. Fix this by accumulating the length of every segment that successfully passes skb_share_check into a tx_bytes variable. This ensures the peer statistics accurately reflect the total data volume sent, regardless of whether the original packet was segmented. Fixes: 04ca14955f9a ("ovpn: store tunnel and transport statistics") Signed-off-by: Ralf Lici Reviewed-by: Sabrina Dubroca Signed-off-by: Antonio Quartulli Signed-off-by: Sasha Levin --- drivers/net/ovpn/io.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/drivers/net/ovpn/io.c b/drivers/net/ovpn/io.c index f70c58b10599bc..955c9a37e1f8db 100644 --- a/drivers/net/ovpn/io.c +++ b/drivers/net/ovpn/io.c @@ -355,6 +355,7 @@ netdev_tx_t ovpn_net_xmit(struct sk_buff *skb, struct net_device *dev) struct ovpn_priv *ovpn = netdev_priv(dev); struct sk_buff *segments, *curr, *next; struct sk_buff_head skb_list; + unsigned int tx_bytes = 0; struct ovpn_peer *peer; __be16 proto; int ret; @@ -414,6 +415,8 @@ netdev_tx_t ovpn_net_xmit(struct sk_buff *skb, struct net_device *dev) continue; } + /* only count what we actually send */ + tx_bytes += curr->len; __skb_queue_tail(&skb_list, curr); } @@ -426,7 +429,7 @@ netdev_tx_t ovpn_net_xmit(struct sk_buff *skb, struct net_device *dev) } skb_list.prev->next = NULL; - ovpn_peer_stats_increment_tx(&peer->vpn_stats, skb_list.next->len); + ovpn_peer_stats_increment_tx(&peer->vpn_stats, tx_bytes); ovpn_send(ovpn, skb_list.next, peer); return NETDEV_TX_OK; From dfd9571c31ef1c94085beb08083a72e98173645f Mon Sep 17 00:00:00 2001 From: Ido Schimmel Date: Mon, 9 Feb 2026 14:53:53 +0100 Subject: [PATCH 3763/3902] selftests: mlxsw: tc_restrictions: Fix test failure with new iproute2 [ Upstream commit a2646773a005b59fd1dc7ff3ba15df84889ca5d2 ] As explained in [1], iproute2 started rejecting tc-police burst sizes that result in an overflow. This can happen when the burst size is high enough and the rate is low enough. A couple of test cases specify such configurations, resulting in iproute2 errors and test failure. Fix by reducing the burst size so that the test will pass with both new and old iproute2 versions. [1] https://lore.kernel.org/netdev/20250916215731.3431465-1-jay.vosburgh@canonical.com/ Fixes: cb12d1763267 ("selftests: mlxsw: tc_restrictions: Test tc-police restrictions") Signed-off-by: Ido Schimmel Signed-off-by: Petr Machata Reviewed-by: Simon Horman Link: https://patch.msgid.link/88b00c6e85188aa6a065dc240206119b328c46e1.1770643998.git.petrm@nvidia.com Signed-off-by: Jakub Kicinski Signed-off-by: Sasha Levin --- tools/testing/selftests/drivers/net/mlxsw/tc_restrictions.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tools/testing/selftests/drivers/net/mlxsw/tc_restrictions.sh b/tools/testing/selftests/drivers/net/mlxsw/tc_restrictions.sh index 0441a18f098b10..aac8ef490feb80 100755 --- a/tools/testing/selftests/drivers/net/mlxsw/tc_restrictions.sh +++ b/tools/testing/selftests/drivers/net/mlxsw/tc_restrictions.sh @@ -317,7 +317,7 @@ police_limits_test() tc filter add dev $swp1 ingress pref 1 proto ip handle 101 \ flower skip_sw \ - action police rate 0.5kbit burst 1m conform-exceed drop/ok + action police rate 0.5kbit burst 2k conform-exceed drop/ok check_fail $? "Incorrect success to add police action with too low rate" tc filter add dev $swp1 ingress pref 1 proto ip handle 101 \ @@ -327,7 +327,7 @@ police_limits_test() tc filter add dev $swp1 ingress pref 1 proto ip handle 101 \ flower skip_sw \ - action police rate 1.5kbit burst 1m conform-exceed drop/ok + action police rate 1.5kbit burst 2k conform-exceed drop/ok check_err $? "Failed to add police action with low rate" tc filter del dev $swp1 ingress protocol ip pref 1 handle 101 flower From 87b7321ddc7d0ec93ef674bc2e9e1da78f9bf5b0 Mon Sep 17 00:00:00 2001 From: Yue Haibing Date: Wed, 11 Feb 2026 10:21:46 +0800 Subject: [PATCH 3764/3902] selftests: net: lib: Fix jq parsing error [ Upstream commit 10ec0fc0ccc525abc807b0ca8ad5a26a0bd56361 ] The testcase failed as below: $./vlan_bridge_binding.sh ... + adf_ip_link_set_up d1 + local name=d1 + shift + ip_link_is_up d1 + ip_link_has_flag d1 UP + local name=d1 + shift + local flag=UP + shift ++ ip -j link show d1 ++ jq --arg flag UP 'any(.[].flags.[]; . == $flag)' jq: error: syntax error, unexpected '[', expecting FORMAT or QQSTRING_START (Unix shell quoting issues?) at , line 1: any(.[].flags.[]; . == $flag) jq: 1 compile error Remove the extra dot (.) after flags array to fix this. Fixes: 4baa1d3a5080 ("selftests: net: lib: Add ip_link_has_flag()") Signed-off-by: Yue Haibing Reviewed-by: Petr Machata Link: https://patch.msgid.link/20260211022146.190948-1-yuehaibing@huawei.com Signed-off-by: Jakub Kicinski Signed-off-by: Sasha Levin --- tools/testing/selftests/net/lib.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/testing/selftests/net/lib.sh b/tools/testing/selftests/net/lib.sh index f448bafb3f208e..d0306b27fe95ef 100644 --- a/tools/testing/selftests/net/lib.sh +++ b/tools/testing/selftests/net/lib.sh @@ -576,7 +576,7 @@ ip_link_has_flag() local flag=$1; shift local state=$(ip -j link show "$name" | - jq --arg flag "$flag" 'any(.[].flags.[]; . == $flag)') + jq --arg flag "$flag" 'any(.[].flags[]; . == $flag)') [[ $state == true ]] } From ee231568d57dc1afa4781527f9eeaefc37b2bab1 Mon Sep 17 00:00:00 2001 From: "Russell King (Oracle)" Date: Thu, 16 Oct 2025 15:36:46 +0100 Subject: [PATCH 3765/3902] net: stmmac: remove broken PCS code [ Upstream commit 813882ae22756bcf9645d405e045c60e5aab0a93 ] Changing the netif_carrier_*() state behind phylink's back has always been prohibited because it messes up with phylinks state tracking, and means that phylink no longer guarantees to call the mac_link_down() and mac_link_up() methods at the appropriate times. This was later documented in the sfp-phylink network driver conversion guide. stmmac was converted to phylink in 2019, but nothing was done with the "PCS" code. Since then, apart from the updates as part of phylink development, nothing has happened with stmmac to improve its use of phylink, or even to address this point. A couple of years ago, a has_integrated_pcs boolean was added by Bart, which later became the STMMAC_FLAG_HAS_INTEGRATED_PCS flag, to avoid manipulating the netif_carrier_*() state. This flag is mis-named, because whenever the stmmac is synthesized for its native SGMII, TBI or RTBI interfaces, it has an "integrated PCS". This boolean/flag actually means "ignore the status from the integrated PCS". Discussing with Bart, the reasons for this are lost to the winds of time (which is why we should always document the reasons in the commit message.) RGMII also has in-band status, and the dwmac cores and stmmac code supports this but with one bug that saves the day. When dwmac cores are synthesised for RGMII only, they do not contain an integrated PCS, and so priv->dma_cap.pcs is clear, which prevents (incorrectly) the "RGMII PCS" being used, meaning we don't read the in-band status. However, a core synthesised for RGMII and also SGMII, TBI or RTBI will have this capability bit set, thus making these code paths reachable. The Jetson Xavier NX uses RGMII mode to talk to its PHY, and removing the incorrect check for priv->dma_cap.pcs reveals the theortical issue with netif_carrier_*() manipulation is real: dwc-eth-dwmac 2490000.ethernet eth0: Register MEM_TYPE_PAGE_POOL RxQ-0 dwc-eth-dwmac 2490000.ethernet eth0: PHY [stmmac-0:00] driver [RTL8211F Gigabit Ethernet] (irq=141) dwc-eth-dwmac 2490000.ethernet eth0: No Safety Features support found dwc-eth-dwmac 2490000.ethernet eth0: IEEE 1588-2008 Advanced Timestamp supported dwc-eth-dwmac 2490000.ethernet eth0: registered PTP clock dwc-eth-dwmac 2490000.ethernet eth0: configuring for phy/rgmii-id link mode 8021q: adding VLAN 0 to HW filter on device eth0 dwc-eth-dwmac 2490000.ethernet eth0: Adding VLAN ID 0 is not supported Link is Up - 1000/Full Link is Down Link is Up - 1000/Full This looks good until one realises that the phylink "Link" status messages are missing, even when the RJ45 cable is reconnected. Nothing one can do results in the interface working. The interrupt handler (which prints those "Link is" messages) always wins over phylink's resolve worker, meaning phylink never calls the mac_link_up() nor mac_link_down() methods. eth0 also sees no traffic received, and is unable to obtain a DHCP address: 3: eth0: mtu 1500 qdisc mq state UP group defa ult qlen 1000 link/ether e6:d3:6a:e6:92:de brd ff:ff:ff:ff:ff:ff RX: bytes packets errors dropped overrun mcast 0 0 0 0 0 0 TX: bytes packets errors dropped carrier collsns 27686 149 0 0 0 0 With the STMMAC_FLAG_HAS_INTEGRATED_PCS flag set, which disables the netif_carrier_*() manipulation then stmmac works normally: dwc-eth-dwmac 2490000.ethernet eth0: Register MEM_TYPE_PAGE_POOL RxQ-0 dwc-eth-dwmac 2490000.ethernet eth0: PHY [stmmac-0:00] driver [RTL8211F Gigabit Ethernet] (irq=141) dwc-eth-dwmac 2490000.ethernet eth0: No Safety Features support found dwc-eth-dwmac 2490000.ethernet eth0: IEEE 1588-2008 Advanced Timestamp supported dwc-eth-dwmac 2490000.ethernet eth0: registered PTP clock dwc-eth-dwmac 2490000.ethernet eth0: configuring for phy/rgmii-id link mode 8021q: adding VLAN 0 to HW filter on device eth0 dwc-eth-dwmac 2490000.ethernet eth0: Adding VLAN ID 0 is not supported Link is Up - 1000/Full dwc-eth-dwmac 2490000.ethernet eth0: Link is Up - 1Gbps/Full - flow control rx/tx and packets can be transferred. This clearly shows that when priv->hw->pcs is set, but STMMAC_FLAG_HAS_INTEGRATED_PCS is clear, the driver reliably fails. Discovering whether a platform falls into this is impossible as parsing all the dtsi and dts files to find out which use the stmmac driver, whether any of them use RGMII or SGMII and also depends whether an external interface is being used. The kernel likely doesn't contain all dts files either. The only driver that sets this flag uses the qcom,sa8775p-ethqos compatible, and uses SGMII or 2500BASE-X. but these are saved from this problem by the incorrect check for priv->dma_cap.pcs. So, we have to assume that for every other platform that uses SGMII with stmmac is using an external PCS. Moreover, ethtool output can be incorrect. With the full-duplex link negotiated, ethtool reports: Speed: 1000Mb/s Duplex: Half because with dwmac4, the full-duplex bit is in bit 16 of the status, priv->xstats.pcs_duplex becomes BIT(16) for full duplex, but the ethtool ksettings duplex member is u8 - so becomes zero. Moreover, the supported, advertised and link partner modes are all "not reported". Finally, ksettings_set() won't be able to set the advertisement on a PHY if this PCS code is activated, which is incorrect when SGMII is used with a PHY. Thus, remove: 1. the incorrect netif_carrier_*() manipulation. 2. the broken ethtool ksettings code. Given that all uses of STMMAC_FLAG_HAS_INTEGRATED_PCS are now gone, remove the flag from stmmac.h and dwmac-qcom-ethqos.c. Reviewed-by: Andrew Lunn Signed-off-by: Russell King (Oracle) Tested-by: Maxime Chevallier Tested-by: Lad Prabhakar Link: https://patch.msgid.link/E1v9P5y-0000000AolC-1QWH@rmk-PC.armlinux.org.uk Signed-off-by: Jakub Kicinski Stable-dep-of: babab1b42ed6 ("net: stmmac: fix oops when split header is enabled") Signed-off-by: Sasha Levin --- .../stmicro/stmmac/dwmac-qcom-ethqos.c | 4 -- .../ethernet/stmicro/stmmac/stmmac_ethtool.c | 55 ------------------- .../net/ethernet/stmicro/stmmac/stmmac_main.c | 9 --- include/linux/stmmac.h | 1 - 4 files changed, 69 deletions(-) diff --git a/drivers/net/ethernet/stmicro/stmmac/dwmac-qcom-ethqos.c b/drivers/net/ethernet/stmicro/stmmac/dwmac-qcom-ethqos.c index d8fd4d8f6ced76..f62825220cf70a 100644 --- a/drivers/net/ethernet/stmicro/stmmac/dwmac-qcom-ethqos.c +++ b/drivers/net/ethernet/stmicro/stmmac/dwmac-qcom-ethqos.c @@ -96,7 +96,6 @@ struct ethqos_emac_driver_data { bool rgmii_config_loopback_en; bool has_emac_ge_3; const char *link_clk_name; - bool has_integrated_pcs; u32 dma_addr_width; struct dwmac4_addrs dwmac4_addrs; bool needs_sgmii_loopback; @@ -282,7 +281,6 @@ static const struct ethqos_emac_driver_data emac_v4_0_0_data = { .rgmii_config_loopback_en = false, .has_emac_ge_3 = true, .link_clk_name = "phyaux", - .has_integrated_pcs = true, .needs_sgmii_loopback = true, .dma_addr_width = 36, .dwmac4_addrs = { @@ -856,8 +854,6 @@ static int qcom_ethqos_probe(struct platform_device *pdev) plat_dat->flags |= STMMAC_FLAG_TSO_EN; if (of_device_is_compatible(np, "qcom,qcs404-ethqos")) plat_dat->flags |= STMMAC_FLAG_RX_CLK_RUNS_IN_LPI; - if (data->has_integrated_pcs) - plat_dat->flags |= STMMAC_FLAG_HAS_INTEGRATED_PCS; if (data->dma_addr_width) plat_dat->host_dma_width = data->dma_addr_width; diff --git a/drivers/net/ethernet/stmicro/stmmac/stmmac_ethtool.c b/drivers/net/ethernet/stmicro/stmmac/stmmac_ethtool.c index 39fa1ec92f82f9..d89662b48087eb 100644 --- a/drivers/net/ethernet/stmicro/stmmac/stmmac_ethtool.c +++ b/drivers/net/ethernet/stmicro/stmmac/stmmac_ethtool.c @@ -322,47 +322,6 @@ static int stmmac_ethtool_get_link_ksettings(struct net_device *dev, { struct stmmac_priv *priv = netdev_priv(dev); - if (!(priv->plat->flags & STMMAC_FLAG_HAS_INTEGRATED_PCS) && - (priv->hw->pcs & STMMAC_PCS_RGMII || - priv->hw->pcs & STMMAC_PCS_SGMII)) { - u32 supported, advertising, lp_advertising; - - if (!priv->xstats.pcs_link) { - cmd->base.speed = SPEED_UNKNOWN; - cmd->base.duplex = DUPLEX_UNKNOWN; - return 0; - } - cmd->base.duplex = priv->xstats.pcs_duplex; - - cmd->base.speed = priv->xstats.pcs_speed; - - /* Encoding of PSE bits is defined in 802.3z, 37.2.1.4 */ - - ethtool_convert_link_mode_to_legacy_u32( - &supported, cmd->link_modes.supported); - ethtool_convert_link_mode_to_legacy_u32( - &advertising, cmd->link_modes.advertising); - ethtool_convert_link_mode_to_legacy_u32( - &lp_advertising, cmd->link_modes.lp_advertising); - - /* Reg49[3] always set because ANE is always supported */ - cmd->base.autoneg = ADVERTISED_Autoneg; - supported |= SUPPORTED_Autoneg; - advertising |= ADVERTISED_Autoneg; - lp_advertising |= ADVERTISED_Autoneg; - - cmd->base.port = PORT_OTHER; - - ethtool_convert_legacy_u32_to_link_mode( - cmd->link_modes.supported, supported); - ethtool_convert_legacy_u32_to_link_mode( - cmd->link_modes.advertising, advertising); - ethtool_convert_legacy_u32_to_link_mode( - cmd->link_modes.lp_advertising, lp_advertising); - - return 0; - } - return phylink_ethtool_ksettings_get(priv->phylink, cmd); } @@ -372,20 +331,6 @@ stmmac_ethtool_set_link_ksettings(struct net_device *dev, { struct stmmac_priv *priv = netdev_priv(dev); - if (!(priv->plat->flags & STMMAC_FLAG_HAS_INTEGRATED_PCS) && - (priv->hw->pcs & STMMAC_PCS_RGMII || - priv->hw->pcs & STMMAC_PCS_SGMII)) { - /* Only support ANE */ - if (cmd->base.autoneg != AUTONEG_ENABLE) - return -EINVAL; - - mutex_lock(&priv->lock); - stmmac_pcs_ctrl_ane(priv, 1, priv->hw->ps, 0); - mutex_unlock(&priv->lock); - - return 0; - } - return phylink_ethtool_ksettings_set(priv->phylink, cmd); } diff --git a/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c b/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c index 0dd17179c85d24..e707abc35e2dec 100644 --- a/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c +++ b/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c @@ -6012,15 +6012,6 @@ static void stmmac_common_interrupt(struct stmmac_priv *priv) for (queue = 0; queue < queues_count; queue++) stmmac_host_mtl_irq_status(priv, priv->hw, queue); - /* PCS link status */ - if (priv->hw->pcs && - !(priv->plat->flags & STMMAC_FLAG_HAS_INTEGRATED_PCS)) { - if (priv->xstats.pcs_link) - netif_carrier_on(priv->dev); - else - netif_carrier_off(priv->dev); - } - stmmac_timestamp_interrupt(priv, priv); } } diff --git a/include/linux/stmmac.h b/include/linux/stmmac.h index fa1318bac06c44..99022620457acc 100644 --- a/include/linux/stmmac.h +++ b/include/linux/stmmac.h @@ -171,7 +171,6 @@ struct dwmac4_addrs { u32 mtl_low_cred_offset; }; -#define STMMAC_FLAG_HAS_INTEGRATED_PCS BIT(0) #define STMMAC_FLAG_SPH_DISABLE BIT(1) #define STMMAC_FLAG_USE_PHY_WOL BIT(2) #define STMMAC_FLAG_HAS_SUN8I BIT(3) From 9e9f1263e210d810f0bcc4d507a0be390a5dc0d1 Mon Sep 17 00:00:00 2001 From: "Russell King (Oracle)" Date: Tue, 21 Oct 2025 08:26:49 +0100 Subject: [PATCH 3766/3902] net: stmmac: replace has_xxxx with core_type [ Upstream commit 26ab9830beabda863766be4a79dc590c7645f4d9 ] Replace the has_gmac, has_gmac4 and has_xgmac ints, of which only one can be set when matching a core to its driver backend, with an enumerated type carrying the DWMAC core type. Tested-by: Maxime Chevallier Signed-off-by: Russell King (Oracle) Acked-by: Chen-Yu Tsai Reviewed-by: Maxime Chevallier Tested-by: Mohd Ayaan Anwar Reviewed-by: Bartosz Golaszewski Link: https://patch.msgid.link/E1vB6ld-0000000BIPy-2Qi4@rmk-PC.armlinux.org.uk Signed-off-by: Jakub Kicinski Stable-dep-of: babab1b42ed6 ("net: stmmac: fix oops when split header is enabled") Signed-off-by: Sasha Levin --- drivers/net/ethernet/stmicro/stmmac/common.h | 5 ++ .../stmicro/stmmac/dwmac-dwc-qos-eth.c | 2 +- .../net/ethernet/stmicro/stmmac/dwmac-intel.c | 5 +- .../ethernet/stmicro/stmmac/dwmac-ipq806x.c | 2 +- .../ethernet/stmicro/stmmac/dwmac-loongson.c | 2 +- .../ethernet/stmicro/stmmac/dwmac-lpc18xx.c | 2 +- .../stmicro/stmmac/dwmac-qcom-ethqos.c | 2 +- .../net/ethernet/stmicro/stmmac/dwmac-rk.c | 4 +- .../net/ethernet/stmicro/stmmac/dwmac-s32.c | 2 +- .../ethernet/stmicro/stmmac/dwmac-socfpga.c | 2 +- .../net/ethernet/stmicro/stmmac/dwmac-sunxi.c | 2 +- .../net/ethernet/stmicro/stmmac/dwmac-tegra.c | 2 +- drivers/net/ethernet/stmicro/stmmac/hwif.c | 73 +++++++------------ .../net/ethernet/stmicro/stmmac/stmmac_est.c | 4 +- .../ethernet/stmicro/stmmac/stmmac_ethtool.c | 13 ++-- .../net/ethernet/stmicro/stmmac/stmmac_main.c | 34 +++++---- .../net/ethernet/stmicro/stmmac/stmmac_mdio.c | 14 ++-- .../net/ethernet/stmicro/stmmac/stmmac_pci.c | 4 +- .../ethernet/stmicro/stmmac/stmmac_platform.c | 9 +-- .../net/ethernet/stmicro/stmmac/stmmac_ptp.c | 4 +- include/linux/stmmac.h | 11 ++- 21 files changed, 94 insertions(+), 104 deletions(-) diff --git a/drivers/net/ethernet/stmicro/stmmac/common.h b/drivers/net/ethernet/stmicro/stmmac/common.h index 8f34c9ad457f07..23ec3a59ca8f17 100644 --- a/drivers/net/ethernet/stmicro/stmmac/common.h +++ b/drivers/net/ethernet/stmicro/stmmac/common.h @@ -43,6 +43,11 @@ #define DWXGMAC_ID 0x76 #define DWXLGMAC_ID 0x27 +static inline bool dwmac_is_xmac(enum dwmac_core_type core_type) +{ + return core_type == DWMAC_CORE_GMAC4 || core_type == DWMAC_CORE_XGMAC; +} + #define STMMAC_CHAN0 0 /* Always supported and default for all chips */ /* TX and RX Descriptor Length, these need to be power of two. diff --git a/drivers/net/ethernet/stmicro/stmmac/dwmac-dwc-qos-eth.c b/drivers/net/ethernet/stmicro/stmmac/dwmac-dwc-qos-eth.c index e8539cad4602e8..1d30f2fb984f1f 100644 --- a/drivers/net/ethernet/stmicro/stmmac/dwmac-dwc-qos-eth.c +++ b/drivers/net/ethernet/stmicro/stmmac/dwmac-dwc-qos-eth.c @@ -109,7 +109,7 @@ static int dwc_eth_dwmac_config_dt(struct platform_device *pdev, } /* dwc-qos needs GMAC4, AAL, TSO and PMT */ - plat_dat->has_gmac4 = 1; + plat_dat->core_type = DWMAC_CORE_GMAC4; plat_dat->dma_cfg->aal = 1; plat_dat->flags |= STMMAC_FLAG_TSO_EN; plat_dat->pmt = 1; diff --git a/drivers/net/ethernet/stmicro/stmmac/dwmac-intel.c b/drivers/net/ethernet/stmicro/stmmac/dwmac-intel.c index e74d00984b889e..b2194e414ec1f6 100644 --- a/drivers/net/ethernet/stmicro/stmmac/dwmac-intel.c +++ b/drivers/net/ethernet/stmicro/stmmac/dwmac-intel.c @@ -565,7 +565,7 @@ static void common_default_data(struct plat_stmmacenet_data *plat) { /* clk_csr_i = 20-35MHz & MDC = clk_csr_i/16 */ plat->clk_csr = STMMAC_CSR_20_35M; - plat->has_gmac = 1; + plat->core_type = DWMAC_CORE_GMAC; plat->force_sf_dma_mode = 1; plat->mdio_bus_data->needs_reset = true; @@ -612,8 +612,7 @@ static int intel_mgbe_common_data(struct pci_dev *pdev, plat->pdev = pdev; plat->phy_addr = -1; plat->clk_csr = STMMAC_CSR_250_300M; - plat->has_gmac = 0; - plat->has_gmac4 = 1; + plat->core_type = DWMAC_CORE_GMAC4; plat->force_sf_dma_mode = 0; plat->flags |= (STMMAC_FLAG_TSO_EN | STMMAC_FLAG_SPH_DISABLE); diff --git a/drivers/net/ethernet/stmicro/stmmac/dwmac-ipq806x.c b/drivers/net/ethernet/stmicro/stmmac/dwmac-ipq806x.c index ca4035cbb55b6d..c05f85534f0cac 100644 --- a/drivers/net/ethernet/stmicro/stmmac/dwmac-ipq806x.c +++ b/drivers/net/ethernet/stmicro/stmmac/dwmac-ipq806x.c @@ -473,7 +473,7 @@ static int ipq806x_gmac_probe(struct platform_device *pdev) return err; } - plat_dat->has_gmac = true; + plat_dat->core_type = DWMAC_CORE_GMAC; plat_dat->bsp_priv = gmac; plat_dat->set_clk_tx_rate = ipq806x_gmac_set_clk_tx_rate; plat_dat->multicast_filter_bins = 0; diff --git a/drivers/net/ethernet/stmicro/stmmac/dwmac-loongson.c b/drivers/net/ethernet/stmicro/stmmac/dwmac-loongson.c index 592aa9d636e505..2a3ac0136cdbcf 100644 --- a/drivers/net/ethernet/stmicro/stmmac/dwmac-loongson.c +++ b/drivers/net/ethernet/stmicro/stmmac/dwmac-loongson.c @@ -92,7 +92,7 @@ static void loongson_default_data(struct pci_dev *pdev, /* clk_csr_i = 20-35MHz & MDC = clk_csr_i/16 */ plat->clk_csr = STMMAC_CSR_20_35M; - plat->has_gmac = 1; + plat->core_type = DWMAC_CORE_GMAC; plat->force_sf_dma_mode = 1; /* Set default value for multicast hash bins */ diff --git a/drivers/net/ethernet/stmicro/stmmac/dwmac-lpc18xx.c b/drivers/net/ethernet/stmicro/stmmac/dwmac-lpc18xx.c index 2562a6d036a2a3..6fffc9dfbae553 100644 --- a/drivers/net/ethernet/stmicro/stmmac/dwmac-lpc18xx.c +++ b/drivers/net/ethernet/stmicro/stmmac/dwmac-lpc18xx.c @@ -41,7 +41,7 @@ static int lpc18xx_dwmac_probe(struct platform_device *pdev) if (IS_ERR(plat_dat)) return PTR_ERR(plat_dat); - plat_dat->has_gmac = true; + plat_dat->core_type = DWMAC_CORE_GMAC; reg = syscon_regmap_lookup_by_compatible("nxp,lpc1850-creg"); if (IS_ERR(reg)) { diff --git a/drivers/net/ethernet/stmicro/stmmac/dwmac-qcom-ethqos.c b/drivers/net/ethernet/stmicro/stmmac/dwmac-qcom-ethqos.c index f62825220cf70a..74c208dd86519f 100644 --- a/drivers/net/ethernet/stmicro/stmmac/dwmac-qcom-ethqos.c +++ b/drivers/net/ethernet/stmicro/stmmac/dwmac-qcom-ethqos.c @@ -846,7 +846,7 @@ static int qcom_ethqos_probe(struct platform_device *pdev) plat_dat->fix_mac_speed = ethqos_fix_mac_speed; plat_dat->dump_debug_regs = rgmii_dump; plat_dat->ptp_clk_freq_config = ethqos_ptp_clk_freq_config; - plat_dat->has_gmac4 = 1; + plat_dat->core_type = DWMAC_CORE_GMAC4; if (ethqos->has_emac_ge_3) plat_dat->dwmac4_addrs = &data->dwmac4_addrs; plat_dat->pmt = 1; diff --git a/drivers/net/ethernet/stmicro/stmmac/dwmac-rk.c b/drivers/net/ethernet/stmicro/stmmac/dwmac-rk.c index 0786816e05f048..643578266dfca7 100644 --- a/drivers/net/ethernet/stmicro/stmmac/dwmac-rk.c +++ b/drivers/net/ethernet/stmicro/stmmac/dwmac-rk.c @@ -1751,8 +1751,8 @@ static int rk_gmac_probe(struct platform_device *pdev) /* If the stmmac is not already selected as gmac4, * then make sure we fallback to gmac. */ - if (!plat_dat->has_gmac4) { - plat_dat->has_gmac = true; + if (plat_dat->core_type != DWMAC_CORE_GMAC4) { + plat_dat->core_type = DWMAC_CORE_GMAC; plat_dat->rx_fifo_size = 4096; plat_dat->tx_fifo_size = 2048; } diff --git a/drivers/net/ethernet/stmicro/stmmac/dwmac-s32.c b/drivers/net/ethernet/stmicro/stmmac/dwmac-s32.c index 221539d760bc83..ee095ac1320376 100644 --- a/drivers/net/ethernet/stmicro/stmmac/dwmac-s32.c +++ b/drivers/net/ethernet/stmicro/stmmac/dwmac-s32.c @@ -146,7 +146,7 @@ static int s32_dwmac_probe(struct platform_device *pdev) gmac->ioaddr = res.addr; /* S32CC core feature set */ - plat->has_gmac4 = true; + plat->core_type = DWMAC_CORE_GMAC4; plat->pmt = 1; plat->flags |= STMMAC_FLAG_SPH_DISABLE; plat->rx_fifo_size = 20480; diff --git a/drivers/net/ethernet/stmicro/stmmac/dwmac-socfpga.c b/drivers/net/ethernet/stmicro/stmmac/dwmac-socfpga.c index 354f01184e6cc2..2ff5db6d41ca08 100644 --- a/drivers/net/ethernet/stmicro/stmmac/dwmac-socfpga.c +++ b/drivers/net/ethernet/stmicro/stmmac/dwmac-socfpga.c @@ -497,7 +497,7 @@ static int socfpga_dwmac_probe(struct platform_device *pdev) plat_dat->pcs_init = socfpga_dwmac_pcs_init; plat_dat->pcs_exit = socfpga_dwmac_pcs_exit; plat_dat->select_pcs = socfpga_dwmac_select_pcs; - plat_dat->has_gmac = true; + plat_dat->core_type = DWMAC_CORE_GMAC; plat_dat->riwt_off = 1; diff --git a/drivers/net/ethernet/stmicro/stmmac/dwmac-sunxi.c b/drivers/net/ethernet/stmicro/stmmac/dwmac-sunxi.c index 1eadcf5d1ad632..7f560d78209d16 100644 --- a/drivers/net/ethernet/stmicro/stmmac/dwmac-sunxi.c +++ b/drivers/net/ethernet/stmicro/stmmac/dwmac-sunxi.c @@ -136,7 +136,7 @@ static int sun7i_gmac_probe(struct platform_device *pdev) /* platform data specifying hardware features and callbacks. * hardware features were copied from Allwinner drivers. */ plat_dat->tx_coe = 1; - plat_dat->has_gmac = true; + plat_dat->core_type = DWMAC_CORE_GMAC; plat_dat->bsp_priv = gmac; plat_dat->init = sun7i_gmac_init; plat_dat->exit = sun7i_gmac_exit; diff --git a/drivers/net/ethernet/stmicro/stmmac/dwmac-tegra.c b/drivers/net/ethernet/stmicro/stmmac/dwmac-tegra.c index dc903b846b1bf4..d765acbe375481 100644 --- a/drivers/net/ethernet/stmicro/stmmac/dwmac-tegra.c +++ b/drivers/net/ethernet/stmicro/stmmac/dwmac-tegra.c @@ -308,7 +308,7 @@ static int tegra_mgbe_probe(struct platform_device *pdev) goto disable_clks; } - plat->has_xgmac = 1; + plat->core_type = DWMAC_CORE_XGMAC; plat->flags |= STMMAC_FLAG_TSO_EN; plat->pmt = 1; plat->bsp_priv = mgbe; diff --git a/drivers/net/ethernet/stmicro/stmmac/hwif.c b/drivers/net/ethernet/stmicro/stmmac/hwif.c index 3f7c765dcb7971..00083ce525492e 100644 --- a/drivers/net/ethernet/stmicro/stmmac/hwif.c +++ b/drivers/net/ethernet/stmicro/stmmac/hwif.c @@ -106,9 +106,7 @@ int stmmac_reset(struct stmmac_priv *priv, void __iomem *ioaddr) } static const struct stmmac_hwif_entry { - bool gmac; - bool gmac4; - bool xgmac; + enum dwmac_core_type core_type; u32 min_id; u32 dev_id; const struct stmmac_regs_off regs; @@ -127,9 +125,7 @@ static const struct stmmac_hwif_entry { } stmmac_hw[] = { /* NOTE: New HW versions shall go to the end of this table */ { - .gmac = false, - .gmac4 = false, - .xgmac = false, + .core_type = DWMAC_CORE_MAC100, .min_id = 0, .regs = { .ptp_off = PTP_GMAC3_X_OFFSET, @@ -146,9 +142,7 @@ static const struct stmmac_hwif_entry { .setup = dwmac100_setup, .quirks = stmmac_dwmac1_quirks, }, { - .gmac = true, - .gmac4 = false, - .xgmac = false, + .core_type = DWMAC_CORE_GMAC, .min_id = 0, .regs = { .ptp_off = PTP_GMAC3_X_OFFSET, @@ -165,9 +159,7 @@ static const struct stmmac_hwif_entry { .setup = dwmac1000_setup, .quirks = stmmac_dwmac1_quirks, }, { - .gmac = false, - .gmac4 = true, - .xgmac = false, + .core_type = DWMAC_CORE_GMAC4, .min_id = 0, .regs = { .ptp_off = PTP_GMAC4_OFFSET, @@ -187,9 +179,7 @@ static const struct stmmac_hwif_entry { .setup = dwmac4_setup, .quirks = stmmac_dwmac4_quirks, }, { - .gmac = false, - .gmac4 = true, - .xgmac = false, + .core_type = DWMAC_CORE_GMAC4, .min_id = DWMAC_CORE_4_00, .regs = { .ptp_off = PTP_GMAC4_OFFSET, @@ -210,9 +200,7 @@ static const struct stmmac_hwif_entry { .setup = dwmac4_setup, .quirks = NULL, }, { - .gmac = false, - .gmac4 = true, - .xgmac = false, + .core_type = DWMAC_CORE_GMAC4, .min_id = DWMAC_CORE_4_10, .regs = { .ptp_off = PTP_GMAC4_OFFSET, @@ -233,9 +221,7 @@ static const struct stmmac_hwif_entry { .setup = dwmac4_setup, .quirks = NULL, }, { - .gmac = false, - .gmac4 = true, - .xgmac = false, + .core_type = DWMAC_CORE_GMAC4, .min_id = DWMAC_CORE_5_10, .regs = { .ptp_off = PTP_GMAC4_OFFSET, @@ -256,9 +242,7 @@ static const struct stmmac_hwif_entry { .setup = dwmac4_setup, .quirks = NULL, }, { - .gmac = false, - .gmac4 = false, - .xgmac = true, + .core_type = DWMAC_CORE_XGMAC, .min_id = DWXGMAC_CORE_2_10, .dev_id = DWXGMAC_ID, .regs = { @@ -280,9 +264,7 @@ static const struct stmmac_hwif_entry { .setup = dwxgmac2_setup, .quirks = NULL, }, { - .gmac = false, - .gmac4 = false, - .xgmac = true, + .core_type = DWMAC_CORE_XGMAC, .min_id = DWXLGMAC_CORE_2_00, .dev_id = DWXLGMAC_ID, .regs = { @@ -308,20 +290,18 @@ static const struct stmmac_hwif_entry { int stmmac_hwif_init(struct stmmac_priv *priv) { - bool needs_xgmac = priv->plat->has_xgmac; - bool needs_gmac4 = priv->plat->has_gmac4; - bool needs_gmac = priv->plat->has_gmac; + enum dwmac_core_type core_type = priv->plat->core_type; const struct stmmac_hwif_entry *entry; struct mac_device_info *mac; bool needs_setup = true; u32 id, dev_id = 0; int i, ret; - if (needs_gmac) { + if (core_type == DWMAC_CORE_GMAC) { id = stmmac_get_id(priv, GMAC_VERSION); - } else if (needs_gmac4 || needs_xgmac) { + } else if (dwmac_is_xmac(core_type)) { id = stmmac_get_id(priv, GMAC4_VERSION); - if (needs_xgmac) + if (core_type == DWMAC_CORE_XGMAC) dev_id = stmmac_get_dev_id(priv, GMAC4_VERSION); } else { id = 0; @@ -331,14 +311,16 @@ int stmmac_hwif_init(struct stmmac_priv *priv) priv->synopsys_id = id; /* Lets assume some safe values first */ - priv->ptpaddr = priv->ioaddr + - (needs_gmac4 ? PTP_GMAC4_OFFSET : PTP_GMAC3_X_OFFSET); - priv->mmcaddr = priv->ioaddr + - (needs_gmac4 ? MMC_GMAC4_OFFSET : MMC_GMAC3_X_OFFSET); - if (needs_gmac4) + if (core_type == DWMAC_CORE_GMAC4) { + priv->ptpaddr = priv->ioaddr + PTP_GMAC4_OFFSET; + priv->mmcaddr = priv->ioaddr + MMC_GMAC4_OFFSET; priv->estaddr = priv->ioaddr + EST_GMAC4_OFFSET; - else if (needs_xgmac) - priv->estaddr = priv->ioaddr + EST_XGMAC_OFFSET; + } else { + priv->ptpaddr = priv->ioaddr + PTP_GMAC3_X_OFFSET; + priv->mmcaddr = priv->ioaddr + MMC_GMAC3_X_OFFSET; + if (core_type == DWMAC_CORE_XGMAC) + priv->estaddr = priv->ioaddr + EST_XGMAC_OFFSET; + } /* Check for HW specific setup first */ if (priv->plat->setup) { @@ -355,16 +337,12 @@ int stmmac_hwif_init(struct stmmac_priv *priv) for (i = ARRAY_SIZE(stmmac_hw) - 1; i >= 0; i--) { entry = &stmmac_hw[i]; - if (needs_gmac ^ entry->gmac) - continue; - if (needs_gmac4 ^ entry->gmac4) - continue; - if (needs_xgmac ^ entry->xgmac) + if (core_type != entry->core_type) continue; /* Use synopsys_id var because some setups can override this */ if (priv->synopsys_id < entry->min_id) continue; - if (needs_xgmac && (dev_id ^ entry->dev_id)) + if (core_type == DWMAC_CORE_XGMAC && (dev_id ^ entry->dev_id)) continue; /* Only use generic HW helpers if needed */ @@ -400,6 +378,7 @@ int stmmac_hwif_init(struct stmmac_priv *priv) } dev_err(priv->device, "Failed to find HW IF (id=0x%x, gmac=%d/%d)\n", - id, needs_gmac, needs_gmac4); + id, core_type == DWMAC_CORE_GMAC, + core_type == DWMAC_CORE_GMAC4); return -EINVAL; } diff --git a/drivers/net/ethernet/stmicro/stmmac/stmmac_est.c b/drivers/net/ethernet/stmicro/stmmac/stmmac_est.c index 4b513d27a98890..afc516059b8975 100644 --- a/drivers/net/ethernet/stmicro/stmmac/stmmac_est.c +++ b/drivers/net/ethernet/stmicro/stmmac/stmmac_est.c @@ -53,7 +53,7 @@ static int est_configure(struct stmmac_priv *priv, struct stmmac_est *cfg, } ctrl = readl(est_addr + EST_CONTROL); - if (priv->plat->has_xgmac) { + if (priv->plat->core_type == DWMAC_CORE_XGMAC) { ctrl &= ~EST_XGMAC_PTOV; ctrl |= ((NSEC_PER_SEC / ptp_rate) * EST_XGMAC_PTOV_MUL) << EST_XGMAC_PTOV_SHIFT; @@ -148,7 +148,7 @@ static void est_irq_status(struct stmmac_priv *priv, struct net_device *dev, } if (status & EST_BTRE) { - if (priv->plat->has_xgmac) { + if (priv->plat->core_type == DWMAC_CORE_XGMAC) { btrl = FIELD_GET(EST_XGMAC_BTRL, status); btrl_max = FIELD_MAX(EST_XGMAC_BTRL); } else { diff --git a/drivers/net/ethernet/stmicro/stmmac/stmmac_ethtool.c b/drivers/net/ethernet/stmicro/stmmac/stmmac_ethtool.c index d89662b48087eb..81d4039e1c0826 100644 --- a/drivers/net/ethernet/stmicro/stmmac/stmmac_ethtool.c +++ b/drivers/net/ethernet/stmicro/stmmac/stmmac_ethtool.c @@ -303,9 +303,10 @@ static void stmmac_ethtool_getdrvinfo(struct net_device *dev, { struct stmmac_priv *priv = netdev_priv(dev); - if (priv->plat->has_gmac || priv->plat->has_gmac4) + if (priv->plat->core_type == DWMAC_CORE_GMAC || + priv->plat->core_type == DWMAC_CORE_GMAC4) strscpy(info->driver, GMAC_ETHTOOL_NAME, sizeof(info->driver)); - else if (priv->plat->has_xgmac) + else if (priv->plat->core_type == DWMAC_CORE_XGMAC) strscpy(info->driver, XGMAC_ETHTOOL_NAME, sizeof(info->driver)); else strscpy(info->driver, MAC100_ETHTOOL_NAME, @@ -351,9 +352,9 @@ static int stmmac_ethtool_get_regs_len(struct net_device *dev) { struct stmmac_priv *priv = netdev_priv(dev); - if (priv->plat->has_xgmac) + if (priv->plat->core_type == DWMAC_CORE_XGMAC) return XGMAC_REGSIZE * 4; - else if (priv->plat->has_gmac4) + else if (priv->plat->core_type == DWMAC_CORE_GMAC4) return GMAC4_REG_SPACE_SIZE; return REG_SPACE_SIZE; } @@ -368,12 +369,12 @@ static void stmmac_ethtool_gregs(struct net_device *dev, stmmac_dump_dma_regs(priv, priv->ioaddr, reg_space); /* Copy DMA registers to where ethtool expects them */ - if (priv->plat->has_gmac4) { + if (priv->plat->core_type == DWMAC_CORE_GMAC4) { /* GMAC4 dumps its DMA registers at its DMA_CHAN_BASE_ADDR */ memcpy(®_space[ETHTOOL_DMA_OFFSET], ®_space[GMAC4_DMA_CHAN_BASE_ADDR / 4], NUM_DWMAC4_DMA_REGS * 4); - } else if (!priv->plat->has_xgmac) { + } else if (priv->plat->core_type != DWMAC_CORE_XGMAC) { memcpy(®_space[ETHTOOL_DMA_OFFSET], ®_space[DMA_BUS_MODE / 4], NUM_DWMAC1000_DMA_REGS * 4); diff --git a/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c b/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c index e707abc35e2dec..a38976c65149c4 100644 --- a/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c +++ b/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c @@ -446,7 +446,7 @@ static void stmmac_get_rx_hwtstamp(struct stmmac_priv *priv, struct dma_desc *p, if (!priv->hwts_rx_en) return; /* For GMAC4, the valid timestamp is from CTX next desc. */ - if (priv->plat->has_gmac4 || priv->plat->has_xgmac) + if (dwmac_is_xmac(priv->plat->core_type)) desc = np; /* Check if timestamp is available */ @@ -697,7 +697,7 @@ static int stmmac_hwtstamp_get(struct net_device *dev, static int stmmac_init_tstamp_counter(struct stmmac_priv *priv, u32 systime_flags) { - bool xmac = priv->plat->has_gmac4 || priv->plat->has_xgmac; + bool xmac = dwmac_is_xmac(priv->plat->core_type); struct timespec64 now; u32 sec_inc = 0; u64 temp = 0; @@ -746,7 +746,7 @@ static int stmmac_init_tstamp_counter(struct stmmac_priv *priv, */ static int stmmac_init_timestamping(struct stmmac_priv *priv) { - bool xmac = priv->plat->has_gmac4 || priv->plat->has_xgmac; + bool xmac = dwmac_is_xmac(priv->plat->core_type); int ret; if (priv->plat->ptp_clk_freq_config) @@ -2398,7 +2398,7 @@ static void stmmac_dma_operation_mode(struct stmmac_priv *priv) txfifosz = priv->dma_cap.tx_fifo_size; /* Split up the shared Tx/Rx FIFO memory on DW QoS Eth and DW XGMAC */ - if (priv->plat->has_gmac4 || priv->plat->has_xgmac) { + if (dwmac_is_xmac(priv->plat->core_type)) { rxfifosz /= rx_channels_count; txfifosz /= tx_channels_count; } @@ -4514,7 +4514,8 @@ static netdev_tx_t stmmac_xmit(struct sk_buff *skb, struct net_device *dev) if (skb_is_gso(skb) && priv->tso) { if (gso & (SKB_GSO_TCPV4 | SKB_GSO_TCPV6)) return stmmac_tso_xmit(skb, dev); - if (priv->plat->has_gmac4 && (gso & SKB_GSO_UDP_L4)) + if (priv->plat->core_type == DWMAC_CORE_GMAC4 && + (gso & SKB_GSO_UDP_L4)) return stmmac_tso_xmit(skb, dev); } @@ -5984,7 +5985,7 @@ static void stmmac_common_interrupt(struct stmmac_priv *priv) u32 queue; bool xmac; - xmac = priv->plat->has_gmac4 || priv->plat->has_xgmac; + xmac = dwmac_is_xmac(priv->plat->core_type); queues_count = (rx_cnt > tx_cnt) ? rx_cnt : tx_cnt; if (priv->irq_wake) @@ -5998,7 +5999,7 @@ static void stmmac_common_interrupt(struct stmmac_priv *priv) stmmac_fpe_irq_status(priv); /* To handle GMAC own interrupts */ - if ((priv->plat->has_gmac) || xmac) { + if (priv->plat->core_type == DWMAC_CORE_GMAC || xmac) { int status = stmmac_host_irq_status(priv, priv->hw, &priv->xstats); if (unlikely(status)) { @@ -6359,7 +6360,7 @@ static int stmmac_dma_cap_show(struct seq_file *seq, void *v) (priv->dma_cap.mbps_1000) ? "Y" : "N"); seq_printf(seq, "\tHalf duplex: %s\n", (priv->dma_cap.half_duplex) ? "Y" : "N"); - if (priv->plat->has_xgmac) { + if (priv->plat->core_type == DWMAC_CORE_XGMAC) { seq_printf(seq, "\tNumber of Additional MAC address registers: %d\n", priv->dma_cap.multi_addr); @@ -6383,7 +6384,7 @@ static int stmmac_dma_cap_show(struct seq_file *seq, void *v) (priv->dma_cap.time_stamp) ? "Y" : "N"); seq_printf(seq, "\tIEEE 1588-2008 Advanced Time Stamp: %s\n", (priv->dma_cap.atime_stamp) ? "Y" : "N"); - if (priv->plat->has_xgmac) + if (priv->plat->core_type == DWMAC_CORE_XGMAC) seq_printf(seq, "\tTimestamp System Time Source: %s\n", dwxgmac_timestamp_source[priv->dma_cap.tssrc]); seq_printf(seq, "\t802.3az - Energy-Efficient Ethernet (EEE): %s\n", @@ -6392,7 +6393,7 @@ static int stmmac_dma_cap_show(struct seq_file *seq, void *v) seq_printf(seq, "\tChecksum Offload in TX: %s\n", (priv->dma_cap.tx_coe) ? "Y" : "N"); if (priv->synopsys_id >= DWMAC_CORE_4_00 || - priv->plat->has_xgmac) { + priv->plat->core_type == DWMAC_CORE_XGMAC) { seq_printf(seq, "\tIP Checksum Offload in RX: %s\n", (priv->dma_cap.rx_coe) ? "Y" : "N"); } else { @@ -7244,8 +7245,9 @@ static int stmmac_hw_init(struct stmmac_priv *priv) * has to be disable and this can be done by passing the * riwt_off field from the platform. */ - if (((priv->synopsys_id >= DWMAC_CORE_3_50) || - (priv->plat->has_xgmac)) && (!priv->plat->riwt_off)) { + if ((priv->synopsys_id >= DWMAC_CORE_3_50 || + priv->plat->core_type == DWMAC_CORE_XGMAC) && + !priv->plat->riwt_off) { priv->use_riwt = 1; dev_info(priv->device, "Enable RX Mitigation via HW Watchdog Timer\n"); @@ -7359,7 +7361,7 @@ static int stmmac_xdp_rx_timestamp(const struct xdp_md *_ctx, u64 *timestamp) return -ENODATA; /* For GMAC4, the valid timestamp is from CTX next desc. */ - if (priv->plat->has_gmac4 || priv->plat->has_xgmac) + if (dwmac_is_xmac(priv->plat->core_type)) desc_contains_ts = ndesc; /* Check if timestamp is available */ @@ -7515,7 +7517,7 @@ int stmmac_dvr_probe(struct device *device, if ((priv->plat->flags & STMMAC_FLAG_TSO_EN) && (priv->dma_cap.tsoen)) { ndev->hw_features |= NETIF_F_TSO | NETIF_F_TSO6; - if (priv->plat->has_gmac4) + if (priv->plat->core_type == DWMAC_CORE_GMAC4) ndev->hw_features |= NETIF_F_GSO_UDP_L4; priv->tso = true; dev_info(priv->device, "TSO feature enabled\n"); @@ -7568,7 +7570,7 @@ int stmmac_dvr_probe(struct device *device, #ifdef STMMAC_VLAN_TAG_USED /* Both mac100 and gmac support receive VLAN tag detection */ ndev->features |= NETIF_F_HW_VLAN_CTAG_RX | NETIF_F_HW_VLAN_STAG_RX; - if (priv->plat->has_gmac4 || priv->plat->has_xgmac) { + if (dwmac_is_xmac(priv->plat->core_type)) { ndev->hw_features |= NETIF_F_HW_VLAN_CTAG_RX; priv->hw->hw_vlan_en = true; } @@ -7596,7 +7598,7 @@ int stmmac_dvr_probe(struct device *device, /* MTU range: 46 - hw-specific max */ ndev->min_mtu = ETH_ZLEN - ETH_HLEN; - if (priv->plat->has_xgmac) + if (priv->plat->core_type == DWMAC_CORE_XGMAC) ndev->max_mtu = XGMAC_JUMBO_LEN; else if ((priv->plat->enh_desc) || (priv->synopsys_id >= DWMAC_CORE_4_00)) ndev->max_mtu = JUMBO_LEN; diff --git a/drivers/net/ethernet/stmicro/stmmac/stmmac_mdio.c b/drivers/net/ethernet/stmicro/stmmac/stmmac_mdio.c index f408737f6fc73e..2b55b02de380d8 100644 --- a/drivers/net/ethernet/stmicro/stmmac/stmmac_mdio.c +++ b/drivers/net/ethernet/stmicro/stmmac/stmmac_mdio.c @@ -301,7 +301,7 @@ static int stmmac_mdio_read_c22(struct mii_bus *bus, int phyaddr, int phyreg) struct stmmac_priv *priv = netdev_priv(bus->priv); u32 cmd; - if (priv->plat->has_gmac4) + if (priv->plat->core_type == DWMAC_CORE_GMAC4) cmd = MII_GMAC4_READ; else cmd = 0; @@ -344,7 +344,7 @@ static int stmmac_mdio_write_c22(struct mii_bus *bus, int phyaddr, int phyreg, struct stmmac_priv *priv = netdev_priv(bus->priv); u32 cmd; - if (priv->plat->has_gmac4) + if (priv->plat->core_type == DWMAC_CORE_GMAC4) cmd = MII_GMAC4_WRITE; else cmd = MII_ADDR_GWRITE; @@ -417,7 +417,7 @@ int stmmac_mdio_reset(struct mii_bus *bus) * on MDC, so perform a dummy mdio read. To be updated for GMAC4 * if needed. */ - if (!priv->plat->has_gmac4) + if (priv->plat->core_type != DWMAC_CORE_GMAC4) writel(0, priv->ioaddr + mii_address); #endif return 0; @@ -528,7 +528,7 @@ static u32 stmmac_clk_csr_set(struct stmmac_priv *priv) value = 0; } - if (priv->plat->has_xgmac) { + if (priv->plat->core_type == DWMAC_CORE_XGMAC) { if (clk_rate > 400000000) value = 0x5; else if (clk_rate > 350000000) @@ -600,7 +600,7 @@ int stmmac_mdio_register(struct net_device *ndev) new_bus->name = "stmmac"; - if (priv->plat->has_xgmac) { + if (priv->plat->core_type == DWMAC_CORE_XGMAC) { new_bus->read = &stmmac_xgmac2_mdio_read_c22; new_bus->write = &stmmac_xgmac2_mdio_write_c22; new_bus->read_c45 = &stmmac_xgmac2_mdio_read_c45; @@ -621,7 +621,7 @@ int stmmac_mdio_register(struct net_device *ndev) } else { new_bus->read = &stmmac_mdio_read_c22; new_bus->write = &stmmac_mdio_write_c22; - if (priv->plat->has_gmac4) { + if (priv->plat->core_type == DWMAC_CORE_GMAC4) { new_bus->read_c45 = &stmmac_mdio_read_c45; new_bus->write_c45 = &stmmac_mdio_write_c45; } @@ -649,7 +649,7 @@ int stmmac_mdio_register(struct net_device *ndev) } /* Looks like we need a dummy read for XGMAC only and C45 PHYs */ - if (priv->plat->has_xgmac) + if (priv->plat->core_type == DWMAC_CORE_XGMAC) stmmac_xgmac2_mdio_read_c45(new_bus, 0, 0, 0); /* If fixed-link is set, skip PHY scanning */ diff --git a/drivers/net/ethernet/stmicro/stmmac/stmmac_pci.c b/drivers/net/ethernet/stmicro/stmmac/stmmac_pci.c index 4e3aa611fda83f..94b3a3b272706c 100644 --- a/drivers/net/ethernet/stmicro/stmmac/stmmac_pci.c +++ b/drivers/net/ethernet/stmicro/stmmac/stmmac_pci.c @@ -23,7 +23,7 @@ static void common_default_data(struct plat_stmmacenet_data *plat) { /* clk_csr_i = 20-35MHz & MDC = clk_csr_i/16 */ plat->clk_csr = STMMAC_CSR_20_35M; - plat->has_gmac = 1; + plat->core_type = DWMAC_CORE_GMAC; plat->force_sf_dma_mode = 1; plat->mdio_bus_data->needs_reset = true; @@ -76,7 +76,7 @@ static int snps_gmac5_default_data(struct pci_dev *pdev, int i; plat->clk_csr = STMMAC_CSR_250_300M; - plat->has_gmac4 = 1; + plat->core_type = DWMAC_CORE_GMAC4; plat->force_sf_dma_mode = 1; plat->flags |= STMMAC_FLAG_TSO_EN; plat->pmt = 1; diff --git a/drivers/net/ethernet/stmicro/stmmac/stmmac_platform.c b/drivers/net/ethernet/stmicro/stmmac/stmmac_platform.c index 27bcaae07a7f22..fbb92cc6ab5989 100644 --- a/drivers/net/ethernet/stmicro/stmmac/stmmac_platform.c +++ b/drivers/net/ethernet/stmicro/stmmac/stmmac_platform.c @@ -552,12 +552,12 @@ stmmac_probe_config_dt(struct platform_device *pdev, u8 *mac) &pdev->dev, plat->unicast_filter_entries); plat->multicast_filter_bins = dwmac1000_validate_mcast_bins( &pdev->dev, plat->multicast_filter_bins); - plat->has_gmac = 1; + plat->core_type = DWMAC_CORE_GMAC; plat->pmt = 1; } if (of_device_is_compatible(np, "snps,dwmac-3.40a")) { - plat->has_gmac = 1; + plat->core_type = DWMAC_CORE_GMAC; plat->enh_desc = 1; plat->tx_coe = 1; plat->bugged_jumbo = 1; @@ -565,8 +565,7 @@ stmmac_probe_config_dt(struct platform_device *pdev, u8 *mac) } if (of_device_compatible_match(np, stmmac_gmac4_compats)) { - plat->has_gmac4 = 1; - plat->has_gmac = 0; + plat->core_type = DWMAC_CORE_GMAC4; plat->pmt = 1; if (of_property_read_bool(np, "snps,tso")) plat->flags |= STMMAC_FLAG_TSO_EN; @@ -580,7 +579,7 @@ stmmac_probe_config_dt(struct platform_device *pdev, u8 *mac) } if (of_device_is_compatible(np, "snps,dwxgmac")) { - plat->has_xgmac = 1; + plat->core_type = DWMAC_CORE_XGMAC; plat->pmt = 1; if (of_property_read_bool(np, "snps,tso")) plat->flags |= STMMAC_FLAG_TSO_EN; diff --git a/drivers/net/ethernet/stmicro/stmmac/stmmac_ptp.c b/drivers/net/ethernet/stmicro/stmmac/stmmac_ptp.c index 993ff4e87e5570..3e30172fa12948 100644 --- a/drivers/net/ethernet/stmicro/stmmac/stmmac_ptp.c +++ b/drivers/net/ethernet/stmicro/stmmac/stmmac_ptp.c @@ -57,7 +57,7 @@ static int stmmac_adjust_time(struct ptp_clock_info *ptp, s64 delta) bool xmac, est_rst = false; int ret; - xmac = priv->plat->has_gmac4 || priv->plat->has_xgmac; + xmac = dwmac_is_xmac(priv->plat->core_type); if (delta < 0) { neg_adj = 1; @@ -344,7 +344,7 @@ void stmmac_ptp_register(struct stmmac_priv *priv) /* Calculate the clock domain crossing (CDC) error if necessary */ priv->plat->cdc_error_adj = 0; - if (priv->plat->has_gmac4) + if (priv->plat->core_type == DWMAC_CORE_GMAC4) priv->plat->cdc_error_adj = (2 * NSEC_PER_SEC) / priv->plat->clk_ptp_rate; /* Update the ptp clock parameters based on feature discovery, when diff --git a/include/linux/stmmac.h b/include/linux/stmmac.h index 99022620457acc..151c81c560c8c4 100644 --- a/include/linux/stmmac.h +++ b/include/linux/stmmac.h @@ -171,6 +171,13 @@ struct dwmac4_addrs { u32 mtl_low_cred_offset; }; +enum dwmac_core_type { + DWMAC_CORE_MAC100, + DWMAC_CORE_GMAC, + DWMAC_CORE_GMAC4, + DWMAC_CORE_XGMAC, +}; + #define STMMAC_FLAG_SPH_DISABLE BIT(1) #define STMMAC_FLAG_USE_PHY_WOL BIT(2) #define STMMAC_FLAG_HAS_SUN8I BIT(3) @@ -186,6 +193,7 @@ struct dwmac4_addrs { #define STMMAC_FLAG_HWTSTAMP_CORRECT_LATENCY BIT(13) struct plat_stmmacenet_data { + enum dwmac_core_type core_type; int bus_id; int phy_addr; /* MAC ----- optional PCS ----- SerDes ----- optional PHY ----- Media @@ -219,7 +227,6 @@ struct plat_stmmacenet_data { struct stmmac_dma_cfg *dma_cfg; struct stmmac_safety_feature_cfg *safety_feat_cfg; int clk_csr; - int has_gmac; int enh_desc; int tx_coe; int rx_coe; @@ -282,10 +289,8 @@ struct plat_stmmacenet_data { struct reset_control *stmmac_rst; struct reset_control *stmmac_ahb_rst; struct stmmac_axi *axi; - int has_gmac4; int rss_en; int mac_port_sel_speed; - int has_xgmac; u8 vlan_fail_q; struct pci_dev *pdev; int int_snapshot_num; From b1f23df09e7dbf4c86b6908dff7efb8cb2b7d609 Mon Sep 17 00:00:00 2001 From: Jie Zhang Date: Mon, 9 Feb 2026 17:50:32 -0500 Subject: [PATCH 3767/3902] net: stmmac: fix oops when split header is enabled [ Upstream commit babab1b42ed68877ef669a08384becf281ad2582 ] For GMAC4, when split header is enabled, in some rare cases, the hardware does not fill buf2 of the first descriptor with payload. Thus we cannot assume buf2 is always fully filled if it is not the last descriptor. Otherwise, the length of buf2 of the second descriptor will be calculated wrong and cause an oops: Unable to handle kernel paging request at virtual address ffff00019246bfc0 ... x2 : 0000000000000040 x1 : ffff00019246bfc0 x0 : ffff00009246c000 Call trace: dcache_inval_poc+0x28/0x58 (P) dma_direct_sync_single_for_cpu+0x38/0x6c __dma_sync_single_for_cpu+0x34/0x6c stmmac_napi_poll_rx+0x8f0/0xb60 __napi_poll.constprop.0+0x30/0x144 net_rx_action+0x160/0x274 handle_softirqs+0x1b8/0x1fc ... To fix this, the PL bit-field in RDES3 register is used for all descriptors, whether it is the last descriptor or not. Fixes: ec222003bd94 ("net: stmmac: Prepare to add Split Header support") Reviewed-by: Jacob Keller Signed-off-by: Jie Zhang Link: https://patch.msgid.link/20260209225037.589130-1-jie.zhang@analog.com Signed-off-by: Jakub Kicinski Signed-off-by: Sasha Levin --- .../net/ethernet/stmicro/stmmac/stmmac_main.c | 20 ++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-) diff --git a/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c b/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c index a38976c65149c4..46299b7925b44a 100644 --- a/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c +++ b/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c @@ -4880,13 +4880,27 @@ static unsigned int stmmac_rx_buf2_len(struct stmmac_priv *priv, if (!priv->sph) return 0; - /* Not last descriptor */ - if (status & rx_not_ls) + /* For GMAC4, when split header is enabled, in some rare cases, the + * hardware does not fill buf2 of the first descriptor with payload. + * Thus we cannot assume buf2 is always fully filled if it is not + * the last descriptor. Otherwise, the length of buf2 of the second + * descriptor will be calculated wrong and cause an oops. + * + * If this is the last descriptor, 'plen' is the length of the + * received packet that was transferred to system memory. + * Otherwise, it is the accumulated number of bytes that have been + * transferred for the current packet. + * + * Thus 'plen - len' always gives the correct length of buf2. + */ + + /* Not GMAC4 and not last descriptor */ + if (priv->plat->core_type != DWMAC_CORE_GMAC4 && (status & rx_not_ls)) return priv->dma_conf.dma_buf_sz; + /* GMAC4 or last descriptor */ plen = stmmac_get_rx_frame_len(priv, p, coe); - /* Last descriptor */ return plen - len; } From fa79b29cc926cdedd44f11621b6db735cff303c8 Mon Sep 17 00:00:00 2001 From: Daniel Machon Date: Tue, 10 Feb 2026 14:44:01 +0100 Subject: [PATCH 3768/3902] net: sparx5/lan969x: fix DWRR cost max to match hardware register width [ Upstream commit 6c28aa8dfdf24f554d4c5d4ff7d723a95360d94a ] DWRR (Deficit Weighted Round Robin) scheduling distributes bandwidth across traffic classes based on per-queue cost values, where lower cost means higher bandwidth share. The SPX5_DWRR_COST_MAX constant is 63 (6 bits) but the hardware register field HSCH_DWRR_ENTRY_DWRR_COST is GENMASK(24, 20), only 5 bits wide (max 31). This causes sparx5_weight_to_hw_cost() to compute cost values that silently overflow via FIELD_PREP, resulting in incorrect scheduling weights. Set SPX5_DWRR_COST_MAX to 31 to match the hardware register width. Fixes: 211225428d65 ("net: microchip: sparx5: add support for offloading ets qdisc") Signed-off-by: Daniel Machon Reviewed-by: Simon Horman Link: https://patch.msgid.link/20260210-sparx5-fix-dwrr-cost-max-v1-1-58fbdbc25652@microchip.com Signed-off-by: Jakub Kicinski Signed-off-by: Sasha Levin --- drivers/net/ethernet/microchip/sparx5/sparx5_qos.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/net/ethernet/microchip/sparx5/sparx5_qos.h b/drivers/net/ethernet/microchip/sparx5/sparx5_qos.h index 1231a80335d7b1..04f76f1e23f60a 100644 --- a/drivers/net/ethernet/microchip/sparx5/sparx5_qos.h +++ b/drivers/net/ethernet/microchip/sparx5/sparx5_qos.h @@ -35,7 +35,7 @@ #define SPX5_SE_BURST_UNIT 4096 /* Dwrr */ -#define SPX5_DWRR_COST_MAX 63 +#define SPX5_DWRR_COST_MAX 31 struct sparx5_shaper { u32 mode; From 6eb26695a4b206bc41caf01680c38d407c75c7fd Mon Sep 17 00:00:00 2001 From: Ziyi Guo Date: Sun, 8 Feb 2026 22:56:00 +0000 Subject: [PATCH 3769/3902] net: mscc: ocelot: extract ocelot_xmit_timestamp() helper [ Upstream commit 29372f07f7969a2f0490793226ecf6c8c6bde0fa ] Extract the PTP timestamp handling logic from ocelot_port_xmit() into a separate ocelot_xmit_timestamp() helper function. This is a pure refactor with no behavioral change. The helper returns false if the skb was consumed (freed) due to a timestamp request failure, and true if the caller should continue with frame injection. The rew_op value is returned via pointer. This prepares for splitting ocelot_port_xmit() into separate FDMA and register injection paths in a subsequent patch. Signed-off-by: Ziyi Guo Reviewed-by: Vladimir Oltean Link: https://patch.msgid.link/20260208225602.1339325-2-n7l8m4@u.northwestern.edu Signed-off-by: Jakub Kicinski Stable-dep-of: 026f6513c588 ("net: mscc: ocelot: add missing lock protection in ocelot_port_xmit_inj()") Signed-off-by: Sasha Levin --- drivers/net/ethernet/mscc/ocelot_net.c | 36 ++++++++++++++++---------- 1 file changed, 22 insertions(+), 14 deletions(-) diff --git a/drivers/net/ethernet/mscc/ocelot_net.c b/drivers/net/ethernet/mscc/ocelot_net.c index 469784d3a1a67b..ef4a6c768de9bf 100644 --- a/drivers/net/ethernet/mscc/ocelot_net.c +++ b/drivers/net/ethernet/mscc/ocelot_net.c @@ -551,33 +551,41 @@ static int ocelot_port_stop(struct net_device *dev) return 0; } -static netdev_tx_t ocelot_port_xmit(struct sk_buff *skb, struct net_device *dev) +static bool ocelot_xmit_timestamp(struct ocelot *ocelot, int port, + struct sk_buff *skb, u32 *rew_op) { - struct ocelot_port_private *priv = netdev_priv(dev); - struct ocelot_port *ocelot_port = &priv->port; - struct ocelot *ocelot = ocelot_port->ocelot; - int port = priv->port.index; - u32 rew_op = 0; - - if (!static_branch_unlikely(&ocelot_fdma_enabled) && - !ocelot_can_inject(ocelot, 0)) - return NETDEV_TX_BUSY; - - /* Check if timestamping is needed */ if (ocelot->ptp && (skb_shinfo(skb)->tx_flags & SKBTX_HW_TSTAMP)) { struct sk_buff *clone = NULL; if (ocelot_port_txtstamp_request(ocelot, port, skb, &clone)) { kfree_skb(skb); - return NETDEV_TX_OK; + return false; } if (clone) OCELOT_SKB_CB(skb)->clone = clone; - rew_op = ocelot_ptp_rew_op(skb); + *rew_op = ocelot_ptp_rew_op(skb); } + return true; +} + +static netdev_tx_t ocelot_port_xmit(struct sk_buff *skb, struct net_device *dev) +{ + struct ocelot_port_private *priv = netdev_priv(dev); + struct ocelot_port *ocelot_port = &priv->port; + struct ocelot *ocelot = ocelot_port->ocelot; + int port = priv->port.index; + u32 rew_op = 0; + + if (!static_branch_unlikely(&ocelot_fdma_enabled) && + !ocelot_can_inject(ocelot, 0)) + return NETDEV_TX_BUSY; + + if (!ocelot_xmit_timestamp(ocelot, port, skb, &rew_op)) + return NETDEV_TX_OK; + if (static_branch_unlikely(&ocelot_fdma_enabled)) { ocelot_fdma_inject_frame(ocelot, port, rew_op, skb, dev); } else { From 2a90d6b837003fba29395c8d1fe85c71a00555c4 Mon Sep 17 00:00:00 2001 From: Ziyi Guo Date: Sun, 8 Feb 2026 22:56:01 +0000 Subject: [PATCH 3770/3902] net: mscc: ocelot: split xmit into FDMA and register injection paths [ Upstream commit 47f79b20e7fb885aa1623b759a68e8e27401ec4d ] Split ocelot_port_xmit() into two separate functions: - ocelot_port_xmit_fdma(): handles the FDMA injection path - ocelot_port_xmit_inj(): handles the register-based injection path The top-level ocelot_port_xmit() now dispatches to the appropriate function based on the ocelot_fdma_enabled static key. This is a pure refactor with no behavioral change. Separating the two code paths makes each one simpler and prepares for adding proper locking to the register injection path without affecting the FDMA path. Signed-off-by: Ziyi Guo Reviewed-by: Vladimir Oltean Link: https://patch.msgid.link/20260208225602.1339325-3-n7l8m4@u.northwestern.edu Signed-off-by: Jakub Kicinski Stable-dep-of: 026f6513c588 ("net: mscc: ocelot: add missing lock protection in ocelot_port_xmit_inj()") Signed-off-by: Sasha Levin --- drivers/net/ethernet/mscc/ocelot_net.c | 39 ++++++++++++++++++++------ 1 file changed, 30 insertions(+), 9 deletions(-) diff --git a/drivers/net/ethernet/mscc/ocelot_net.c b/drivers/net/ethernet/mscc/ocelot_net.c index ef4a6c768de9bf..a7966c174b2e22 100644 --- a/drivers/net/ethernet/mscc/ocelot_net.c +++ b/drivers/net/ethernet/mscc/ocelot_net.c @@ -571,7 +571,25 @@ static bool ocelot_xmit_timestamp(struct ocelot *ocelot, int port, return true; } -static netdev_tx_t ocelot_port_xmit(struct sk_buff *skb, struct net_device *dev) +static netdev_tx_t ocelot_port_xmit_fdma(struct sk_buff *skb, + struct net_device *dev) +{ + struct ocelot_port_private *priv = netdev_priv(dev); + struct ocelot_port *ocelot_port = &priv->port; + struct ocelot *ocelot = ocelot_port->ocelot; + int port = priv->port.index; + u32 rew_op = 0; + + if (!ocelot_xmit_timestamp(ocelot, port, skb, &rew_op)) + return NETDEV_TX_OK; + + ocelot_fdma_inject_frame(ocelot, port, rew_op, skb, dev); + + return NETDEV_TX_OK; +} + +static netdev_tx_t ocelot_port_xmit_inj(struct sk_buff *skb, + struct net_device *dev) { struct ocelot_port_private *priv = netdev_priv(dev); struct ocelot_port *ocelot_port = &priv->port; @@ -579,24 +597,27 @@ static netdev_tx_t ocelot_port_xmit(struct sk_buff *skb, struct net_device *dev) int port = priv->port.index; u32 rew_op = 0; - if (!static_branch_unlikely(&ocelot_fdma_enabled) && - !ocelot_can_inject(ocelot, 0)) + if (!ocelot_can_inject(ocelot, 0)) return NETDEV_TX_BUSY; if (!ocelot_xmit_timestamp(ocelot, port, skb, &rew_op)) return NETDEV_TX_OK; - if (static_branch_unlikely(&ocelot_fdma_enabled)) { - ocelot_fdma_inject_frame(ocelot, port, rew_op, skb, dev); - } else { - ocelot_port_inject_frame(ocelot, port, 0, rew_op, skb); + ocelot_port_inject_frame(ocelot, port, 0, rew_op, skb); - consume_skb(skb); - } + consume_skb(skb); return NETDEV_TX_OK; } +static netdev_tx_t ocelot_port_xmit(struct sk_buff *skb, struct net_device *dev) +{ + if (static_branch_unlikely(&ocelot_fdma_enabled)) + return ocelot_port_xmit_fdma(skb, dev); + + return ocelot_port_xmit_inj(skb, dev); +} + enum ocelot_action_type { OCELOT_MACT_LEARN, OCELOT_MACT_FORGET, From 51c32ae7fae14552d79f7139614b77c1bbd57a48 Mon Sep 17 00:00:00 2001 From: Ziyi Guo Date: Sun, 8 Feb 2026 22:56:02 +0000 Subject: [PATCH 3771/3902] net: mscc: ocelot: add missing lock protection in ocelot_port_xmit_inj() [ Upstream commit 026f6513c5880c2c89e38ad66bbec2868f978605 ] ocelot_port_xmit_inj() calls ocelot_can_inject() and ocelot_port_inject_frame() without holding the injection group lock. Both functions contain lockdep_assert_held() for the injection lock, and the correct caller felix_port_deferred_xmit() properly acquires the lock using ocelot_lock_inj_grp() before calling these functions. Add ocelot_lock_inj_grp()/ocelot_unlock_inj_grp() around the register injection path to fix the missing lock protection. The FDMA path is not affected as it uses its own locking mechanism. Fixes: c5e12ac3beb0 ("net: mscc: ocelot: serialize access to the injection/extraction groups") Signed-off-by: Ziyi Guo Reviewed-by: Vladimir Oltean Link: https://patch.msgid.link/20260208225602.1339325-4-n7l8m4@u.northwestern.edu Signed-off-by: Jakub Kicinski Signed-off-by: Sasha Levin --- drivers/net/ethernet/mscc/ocelot_net.c | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/drivers/net/ethernet/mscc/ocelot_net.c b/drivers/net/ethernet/mscc/ocelot_net.c index a7966c174b2e22..1b82693204640d 100644 --- a/drivers/net/ethernet/mscc/ocelot_net.c +++ b/drivers/net/ethernet/mscc/ocelot_net.c @@ -597,14 +597,22 @@ static netdev_tx_t ocelot_port_xmit_inj(struct sk_buff *skb, int port = priv->port.index; u32 rew_op = 0; - if (!ocelot_can_inject(ocelot, 0)) + ocelot_lock_inj_grp(ocelot, 0); + + if (!ocelot_can_inject(ocelot, 0)) { + ocelot_unlock_inj_grp(ocelot, 0); return NETDEV_TX_BUSY; + } - if (!ocelot_xmit_timestamp(ocelot, port, skb, &rew_op)) + if (!ocelot_xmit_timestamp(ocelot, port, skb, &rew_op)) { + ocelot_unlock_inj_grp(ocelot, 0); return NETDEV_TX_OK; + } ocelot_port_inject_frame(ocelot, port, 0, rew_op, skb); + ocelot_unlock_inj_grp(ocelot, 0); + consume_skb(skb); return NETDEV_TX_OK; From a0070e9635fd5b2dcf616aa063395c8dae13e4e5 Mon Sep 17 00:00:00 2001 From: Andre Carvalho Date: Sat, 29 Nov 2025 12:24:19 +0000 Subject: [PATCH 3772/3902] selftests: netconsole: remove log noise due to socat exit [ Upstream commit e3b8cbf40c6e60a7a935bd8980884d5741a7a77b ] This removes some noise that can be distracting while looking at selftests by redirecting socat stderr to /dev/null. Before this commit, netcons_basic would output: Running with target mode: basic (ipv6) 2025/11/29 12:08:03 socat[259] W exiting on signal 15 2025/11/29 12:08:03 socat[271] W exiting on signal 15 basic : ipv6 : Test passed Running with target mode: basic (ipv4) 2025/11/29 12:08:05 socat[329] W exiting on signal 15 2025/11/29 12:08:05 socat[322] W exiting on signal 15 basic : ipv4 : Test passed Running with target mode: extended (ipv6) 2025/11/29 12:08:08 socat[386] W exiting on signal 15 2025/11/29 12:08:08 socat[386] W exiting on signal 15 2025/11/29 12:08:08 socat[380] W exiting on signal 15 extended : ipv6 : Test passed Running with target mode: extended (ipv4) 2025/11/29 12:08:10 socat[440] W exiting on signal 15 2025/11/29 12:08:10 socat[435] W exiting on signal 15 2025/11/29 12:08:10 socat[435] W exiting on signal 15 extended : ipv4 : Test passed After these changes, output looks like: Running with target mode: basic (ipv6) basic : ipv6 : Test passed Running with target mode: basic (ipv4) basic : ipv4 : Test passed Running with target mode: extended (ipv6) extended : ipv6 : Test passed Running with target mode: extended (ipv4) extended : ipv4 : Test passed Signed-off-by: Andre Carvalho Reviewed-by: Simon Horman Link: https://patch.msgid.link/20251129-netcons-socat-noise-v1-1-605a0cea8fca@gmail.com Signed-off-by: Jakub Kicinski Stable-dep-of: a68a9bd086c2 ("selftests: netconsole: Increase port listening timeout") Signed-off-by: Sasha Levin --- tools/testing/selftests/drivers/net/lib/sh/lib_netcons.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/testing/selftests/drivers/net/lib/sh/lib_netcons.sh b/tools/testing/selftests/drivers/net/lib/sh/lib_netcons.sh index 87f89fd92f8c11..ae8abff4be409e 100644 --- a/tools/testing/selftests/drivers/net/lib/sh/lib_netcons.sh +++ b/tools/testing/selftests/drivers/net/lib/sh/lib_netcons.sh @@ -249,7 +249,7 @@ function listen_port_and_save_to() { # Just wait for 2 seconds timeout 2 ip netns exec "${NAMESPACE}" \ - socat "${SOCAT_MODE}":"${PORT}",fork "${OUTPUT}" + socat "${SOCAT_MODE}":"${PORT}",fork "${OUTPUT}" 2> /dev/null } # Only validate that the message arrived properly From d1410317337c91a9017d25fda15c4dfd71d3f79b Mon Sep 17 00:00:00 2001 From: Pin-yen Lin Date: Mon, 9 Feb 2026 16:59:36 -0800 Subject: [PATCH 3773/3902] selftests: netconsole: Increase port listening timeout [ Upstream commit a68a9bd086c2822d0c629443bd16ad1317afe501 ] wait_for_port() can wait up to 2 seconds with the sleep and the polling in wait_local_port_listen() combined. So, in netcons_basic.sh, the socat process could die before the test writes to the netconsole. Increase the timeout to 3 seconds to make netcons_basic.sh pass consistently. Fixes: 3dc6c76391cb ("selftests: net: Add IPv6 support to netconsole basic tests") Signed-off-by: Pin-yen Lin Reviewed-by: Simon Horman Link: https://patch.msgid.link/20260210005939.3230550-1-treapking@google.com Signed-off-by: Jakub Kicinski Signed-off-by: Sasha Levin --- tools/testing/selftests/drivers/net/lib/sh/lib_netcons.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tools/testing/selftests/drivers/net/lib/sh/lib_netcons.sh b/tools/testing/selftests/drivers/net/lib/sh/lib_netcons.sh index ae8abff4be409e..64d3941576d5db 100644 --- a/tools/testing/selftests/drivers/net/lib/sh/lib_netcons.sh +++ b/tools/testing/selftests/drivers/net/lib/sh/lib_netcons.sh @@ -247,8 +247,8 @@ function listen_port_and_save_to() { SOCAT_MODE="UDP6-LISTEN" fi - # Just wait for 2 seconds - timeout 2 ip netns exec "${NAMESPACE}" \ + # Just wait for 3 seconds + timeout 3 ip netns exec "${NAMESPACE}" \ socat "${SOCAT_MODE}":"${PORT}",fork "${OUTPUT}" 2> /dev/null } From 03b5051e02f5a3772eee57493ad697d4b505b0c2 Mon Sep 17 00:00:00 2001 From: Kuniyuki Iwashima Date: Wed, 11 Feb 2026 17:50:21 +0000 Subject: [PATCH 3774/3902] ipv6: Fix out-of-bound access in fib6_add_rt2node(). [ Upstream commit 8244f959e2c125c849e569f5b23ed49804cce695 ] syzbot reported out-of-bound read in fib6_add_rt2node(). [0] When IPv6 route is created with RTA_NH_ID, struct fib6_info does not have the trailing struct fib6_nh. The cited commit started to check !iter->fib6_nh->fib_nh_gw_family to ensure that rt6_qualify_for_ecmp() will return false for iter. If iter->nh is not NULL, rt6_qualify_for_ecmp() returns false anyway. Let's check iter->nh before reading iter->fib6_nh and avoid OOB read. [0]: BUG: KASAN: slab-out-of-bounds in fib6_add_rt2node+0x349c/0x3500 net/ipv6/ip6_fib.c:1142 Read of size 1 at addr ffff8880384ba6de by task syz.0.18/5500 CPU: 0 UID: 0 PID: 5500 Comm: syz.0.18 Not tainted syzkaller #0 PREEMPT(full) Hardware name: QEMU Standard PC (Q35 + ICH9, 2009), BIOS 1.16.3-debian-1.16.3-2 04/01/2014 Call Trace: dump_stack_lvl+0xe8/0x150 lib/dump_stack.c:120 print_address_description mm/kasan/report.c:378 [inline] print_report+0xba/0x230 mm/kasan/report.c:482 kasan_report+0x117/0x150 mm/kasan/report.c:595 fib6_add_rt2node+0x349c/0x3500 net/ipv6/ip6_fib.c:1142 fib6_add_rt2node_nh net/ipv6/ip6_fib.c:1363 [inline] fib6_add+0x910/0x18c0 net/ipv6/ip6_fib.c:1531 __ip6_ins_rt net/ipv6/route.c:1351 [inline] ip6_route_add+0xde/0x1b0 net/ipv6/route.c:3957 inet6_rtm_newroute+0x268/0x19e0 net/ipv6/route.c:5660 rtnetlink_rcv_msg+0x7d5/0xbe0 net/core/rtnetlink.c:6958 netlink_rcv_skb+0x232/0x4b0 net/netlink/af_netlink.c:2550 netlink_unicast_kernel net/netlink/af_netlink.c:1318 [inline] netlink_unicast+0x80f/0x9b0 net/netlink/af_netlink.c:1344 netlink_sendmsg+0x813/0xb40 net/netlink/af_netlink.c:1894 sock_sendmsg_nosec net/socket.c:727 [inline] __sock_sendmsg net/socket.c:742 [inline] ____sys_sendmsg+0xa68/0xad0 net/socket.c:2592 ___sys_sendmsg+0x2a5/0x360 net/socket.c:2646 __sys_sendmsg net/socket.c:2678 [inline] __do_sys_sendmsg net/socket.c:2683 [inline] __se_sys_sendmsg net/socket.c:2681 [inline] __x64_sys_sendmsg+0x1bd/0x2a0 net/socket.c:2681 do_syscall_x64 arch/x86/entry/syscall_64.c:63 [inline] do_syscall_64+0xe2/0xf80 arch/x86/entry/syscall_64.c:94 entry_SYSCALL_64_after_hwframe+0x77/0x7f RIP: 0033:0x7f9316b9aeb9 Code: ff c3 66 2e 0f 1f 84 00 00 00 00 00 0f 1f 44 00 00 48 89 f8 48 89 f7 48 89 d6 48 89 ca 4d 89 c2 4d 89 c8 4c 8b 4c 24 08 0f 05 <48> 3d 01 f0 ff ff 73 01 c3 48 c7 c1 e8 ff ff ff f7 d8 64 89 01 48 RSP: 002b:00007ffd8809b678 EFLAGS: 00000246 ORIG_RAX: 000000000000002e RAX: ffffffffffffffda RBX: 00007f9316e15fa0 RCX: 00007f9316b9aeb9 RDX: 0000000000000000 RSI: 0000200000004380 RDI: 0000000000000003 RBP: 00007f9316c08c1f R08: 0000000000000000 R09: 0000000000000000 R10: 0000000000000000 R11: 0000000000000246 R12: 0000000000000000 R13: 00007f9316e15fac R14: 00007f9316e15fa0 R15: 00007f9316e15fa0 Allocated by task 5499: kasan_save_stack mm/kasan/common.c:57 [inline] kasan_save_track+0x3e/0x80 mm/kasan/common.c:78 poison_kmalloc_redzone mm/kasan/common.c:398 [inline] __kasan_kmalloc+0x93/0xb0 mm/kasan/common.c:415 kasan_kmalloc include/linux/kasan.h:263 [inline] __do_kmalloc_node mm/slub.c:5657 [inline] __kmalloc_noprof+0x40c/0x7e0 mm/slub.c:5669 kmalloc_noprof include/linux/slab.h:961 [inline] kzalloc_noprof include/linux/slab.h:1094 [inline] fib6_info_alloc+0x30/0xf0 net/ipv6/ip6_fib.c:155 ip6_route_info_create+0x142/0x860 net/ipv6/route.c:3820 ip6_route_add+0x49/0x1b0 net/ipv6/route.c:3949 inet6_rtm_newroute+0x268/0x19e0 net/ipv6/route.c:5660 rtnetlink_rcv_msg+0x7d5/0xbe0 net/core/rtnetlink.c:6958 netlink_rcv_skb+0x232/0x4b0 net/netlink/af_netlink.c:2550 netlink_unicast_kernel net/netlink/af_netlink.c:1318 [inline] netlink_unicast+0x80f/0x9b0 net/netlink/af_netlink.c:1344 netlink_sendmsg+0x813/0xb40 net/netlink/af_netlink.c:1894 sock_sendmsg_nosec net/socket.c:727 [inline] __sock_sendmsg net/socket.c:742 [inline] ____sys_sendmsg+0xa68/0xad0 net/socket.c:2592 ___sys_sendmsg+0x2a5/0x360 net/socket.c:2646 __sys_sendmsg net/socket.c:2678 [inline] __do_sys_sendmsg net/socket.c:2683 [inline] __se_sys_sendmsg net/socket.c:2681 [inline] __x64_sys_sendmsg+0x1bd/0x2a0 net/socket.c:2681 do_syscall_x64 arch/x86/entry/syscall_64.c:63 [inline] do_syscall_64+0xe2/0xf80 arch/x86/entry/syscall_64.c:94 entry_SYSCALL_64_after_hwframe+0x77/0x7f Fixes: bbf4a17ad9ff ("ipv6: Fix ECMP sibling count mismatch when clearing RTF_ADDRCONF") Reported-by: syzbot+707d6a5da1ab9e0c6f9d@syzkaller.appspotmail.com Closes: https://lore.kernel.org/netdev/698cbfba.050a0220.2eeac1.009d.GAE@google.com/ Signed-off-by: Kuniyuki Iwashima Reviewed-by: Fernando Fernandez Mancera Reviewed-by: Shigeru Yoshida Link: https://patch.msgid.link/20260211175133.3657034-1-kuniyu@google.com Signed-off-by: Jakub Kicinski Signed-off-by: Sasha Levin --- net/ipv6/ip6_fib.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/net/ipv6/ip6_fib.c b/net/ipv6/ip6_fib.c index c6439e30e892af..cc149227b49f4a 100644 --- a/net/ipv6/ip6_fib.c +++ b/net/ipv6/ip6_fib.c @@ -1139,7 +1139,7 @@ static int fib6_add_rt2node(struct fib6_node *fn, struct fib6_info *rt, fib6_add_gc_list(iter); } if (!(rt->fib6_flags & (RTF_ADDRCONF | RTF_PREFIX_RT)) && - !iter->fib6_nh->fib_nh_gw_family) { + (iter->nh || !iter->fib6_nh->fib_nh_gw_family)) { iter->fib6_flags &= ~RTF_ADDRCONF; iter->fib6_flags &= ~RTF_PREFIX_RT; } From 37954733984f647790cf7e615244ab19b39773d9 Mon Sep 17 00:00:00 2001 From: Daniel Machon Date: Thu, 12 Feb 2026 12:02:30 +0100 Subject: [PATCH 3775/3902] net: sparx5/lan969x: fix PTP clock max_adj value [ Upstream commit a49d2a2c37a6252c41cbdd505f9d1c58d5a3817a ] The max_adj field in ptp_clock_info tells userspace how much the PHC clock frequency can be adjusted. ptp4l reads this and will never request a correction larger than max_adj. On both sparx5 and lan969x the clock offset may never converge because the servo needs a frequency correction larger than the current max_adj of 200000 (200 ppm) allows. The servo rails at the max and the offset stays in the tens of microseconds. The hardware has no inherent max adjustment limit; frequency correction is done by writing a 64-bit clock period increment to CLK_PER_CFG, and the register has plenty of range. The 200000 value was just an overly conservative software limit. The max_adj is shared between sparx5 and lan969x, and the increased value is safe for both. Fix this by increasing max_adj to 10000000 (10000 ppm), giving the servo sufficient headroom. Fixes: 0933bd04047c ("net: sparx5: Add support for ptp clocks") Signed-off-by: Daniel Machon Reviewed-by: Maxime Chevallier Link: https://patch.msgid.link/20260212-sparx5-ptp-max-adj-v2-v1-1-06b200e50ce3@microchip.com Signed-off-by: Jakub Kicinski Signed-off-by: Sasha Levin --- drivers/net/ethernet/microchip/sparx5/sparx5_ptp.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/net/ethernet/microchip/sparx5/sparx5_ptp.c b/drivers/net/ethernet/microchip/sparx5/sparx5_ptp.c index 2f168700f63c11..8b2e07821a9505 100644 --- a/drivers/net/ethernet/microchip/sparx5/sparx5_ptp.c +++ b/drivers/net/ethernet/microchip/sparx5/sparx5_ptp.c @@ -576,7 +576,7 @@ static int sparx5_ptp_adjtime(struct ptp_clock_info *ptp, s64 delta) static struct ptp_clock_info sparx5_ptp_clock_info = { .owner = THIS_MODULE, .name = "sparx5 ptp", - .max_adj = 200000, + .max_adj = 10000000, .gettime64 = sparx5_ptp_gettime64, .settime64 = sparx5_ptp_settime64, .adjtime = sparx5_ptp_adjtime, From 223cfef4812bdfa5ac5c1aa761cdba03cfe2c9cd Mon Sep 17 00:00:00 2001 From: Chengfeng Ye Date: Wed, 11 Feb 2026 19:13:29 +0000 Subject: [PATCH 3776/3902] fbnic: close fw_log race between users and teardown [ Upstream commit ee5492fd88cfc079c19fbeac78e9e53b7f6c04f3 ] Fixes a theoretical race on fw_log between the teardown path and fw_log write functions. fw_log is written inside fbnic_fw_log_write() and can be reached from the mailbox handler fbnic_fw_msix_intr(), but fw_log is freed before IRQ/MBX teardown during cleanup, resulting in a potential data race of dereferencing a freed/null variable. Possible Interleaving Scenario: CPU0: fbnic_fw_msix_intr() // Entry fbnic_fw_log_write() if (fbnic_fw_log_ready()) // true ... preempt ... CPU1: fbnic_remove() // Entry fbnic_fw_log_free() vfree(log->data_start); log->data_start = NULL; CPU0: continues, walks log->entries or writes to log->data_start The initialization also has an incorrect order problem, as the fw_log is currently allocated after MBX setup during initialization. Fix the problems by adjusting the synchronization order to put initialization in place before the mailbox is enabled, and not cleared until after the mailbox has been disabled. Fixes: ecc53b1b46c89 ("eth: fbnic: Enable firmware logging") Signed-off-by: Chengfeng Ye Link: https://patch.msgid.link/20260211191329.530886-1-dg573847474@gmail.com Signed-off-by: Jakub Kicinski Signed-off-by: Sasha Levin --- .../net/ethernet/meta/fbnic/fbnic_fw_log.c | 3 --- drivers/net/ethernet/meta/fbnic/fbnic_pci.c | 19 ++++++++++++------- 2 files changed, 12 insertions(+), 10 deletions(-) diff --git a/drivers/net/ethernet/meta/fbnic/fbnic_fw_log.c b/drivers/net/ethernet/meta/fbnic/fbnic_fw_log.c index 85a883dba385fe..d8a9a7d7c2375f 100644 --- a/drivers/net/ethernet/meta/fbnic/fbnic_fw_log.c +++ b/drivers/net/ethernet/meta/fbnic/fbnic_fw_log.c @@ -51,8 +51,6 @@ int fbnic_fw_log_init(struct fbnic_dev *fbd) log->data_start = data; log->data_end = data + FBNIC_FW_LOG_SIZE; - fbnic_fw_log_enable(fbd, true); - return 0; } @@ -63,7 +61,6 @@ void fbnic_fw_log_free(struct fbnic_dev *fbd) if (!fbnic_fw_log_ready(fbd)) return; - fbnic_fw_log_disable(fbd); INIT_LIST_HEAD(&log->entries); log->size = 0; vfree(log->data_start); diff --git a/drivers/net/ethernet/meta/fbnic/fbnic_pci.c b/drivers/net/ethernet/meta/fbnic/fbnic_pci.c index 0fa90baad5f88e..698b8a85afb318 100644 --- a/drivers/net/ethernet/meta/fbnic/fbnic_pci.c +++ b/drivers/net/ethernet/meta/fbnic/fbnic_pci.c @@ -303,11 +303,17 @@ static int fbnic_probe(struct pci_dev *pdev, const struct pci_device_id *ent) goto free_irqs; } + err = fbnic_fw_log_init(fbd); + if (err) + dev_warn(fbd->dev, + "Unable to initialize firmware log buffer: %d\n", + err); + err = fbnic_fw_request_mbx(fbd); if (err) { dev_err(&pdev->dev, "Firmware mailbox initialization failure\n"); - goto free_irqs; + goto free_fw_log; } /* Send the request to enable the FW logging to host. Note if this @@ -315,11 +321,7 @@ static int fbnic_probe(struct pci_dev *pdev, const struct pci_device_id *ent) * possible the FW is just too old to support the logging and needs * to be updated. */ - err = fbnic_fw_log_init(fbd); - if (err) - dev_warn(fbd->dev, - "Unable to initialize firmware log buffer: %d\n", - err); + fbnic_fw_log_enable(fbd, true); fbnic_devlink_register(fbd); fbnic_devlink_otp_check(fbd, "error detected during probe"); @@ -363,6 +365,8 @@ static int fbnic_probe(struct pci_dev *pdev, const struct pci_device_id *ent) * firmware updates for fixes. */ return 0; +free_fw_log: + fbnic_fw_log_free(fbd); free_irqs: fbnic_free_irqs(fbd); err_destroy_health: @@ -397,8 +401,9 @@ static void fbnic_remove(struct pci_dev *pdev) fbnic_hwmon_unregister(fbd); fbnic_dbg_fbd_exit(fbd); fbnic_devlink_unregister(fbd); - fbnic_fw_log_free(fbd); + fbnic_fw_log_disable(fbd); fbnic_fw_free_mbx(fbd); + fbnic_fw_log_free(fbd); fbnic_free_irqs(fbd); fbnic_devlink_health_destroy(fbd); From 068708543a851d947dd173d6894b94338cec1860 Mon Sep 17 00:00:00 2001 From: Amery Hung Date: Mon, 9 Feb 2026 15:01:34 -0800 Subject: [PATCH 3777/3902] libbpf: Fix invalid write loop logic in bpf_linker__add_buf() [ Upstream commit 04999b99e81eaa7b6223ec1c03af3bcb4ac57aaa ] Fix bpf_linker__add_buf()'s logic of copying data from memory buffer into memfd. In the event of short write not writing entire buf_sz bytes into memfd file, we'll append bytes from the beginning of buf *again* (corrupting ELF file contents) instead of correctly appending the rest of not-yet-read buf contents. Closes: https://github.com/libbpf/libbpf/issues/945 Fixes: 6d5e5e5d7ce1 ("libbpf: Extend linker API to support in-memory ELF files") Signed-off-by: Amery Hung Signed-off-by: Andrii Nakryiko Acked-by: Jiri Olsa Link: https://lore.kernel.org/bpf/20260209230134.3530521-1-ameryhung@gmail.com Signed-off-by: Alexei Starovoitov Signed-off-by: Sasha Levin --- tools/lib/bpf/linker.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/lib/bpf/linker.c b/tools/lib/bpf/linker.c index 56ae77047bc367..b86b8ae3b63416 100644 --- a/tools/lib/bpf/linker.c +++ b/tools/lib/bpf/linker.c @@ -581,7 +581,7 @@ int bpf_linker__add_buf(struct bpf_linker *linker, void *buf, size_t buf_sz, written = 0; while (written < buf_sz) { - ret = write(fd, buf, buf_sz); + ret = write(fd, buf + written, buf_sz - written); if (ret < 0) { ret = -errno; pr_warn("failed to write '%s': %s\n", filename, errstr(ret)); From eac65c272f3b49021a843cba5107d63627395e0e Mon Sep 17 00:00:00 2001 From: Anton Protopopov Date: Mon, 9 Feb 2026 13:29:04 +0000 Subject: [PATCH 3778/3902] bpf: Fix a potential use-after-free of BTF object [ Upstream commit ccd2d799ed4467c07f5ee18c2f5c59bcc990822c ] Refcounting in the check_pseudo_btf_id() function is incorrect: the __check_pseudo_btf_id() function might get called with a zero refcounted btf. Fix this, and patch related code accordingly. v3: rephrase a comment (AI) v2: fix a refcount leak introduced in v1 (AI) Reported-by: syzbot+5a0f1995634f7c1dadbf@syzkaller.appspotmail.com Closes: https://syzkaller.appspot.com/bug?extid=5a0f1995634f7c1dadbf Fixes: 76145f725532 ("bpf: Refactor check_pseudo_btf_id") Signed-off-by: Anton Protopopov Link: https://lore.kernel.org/r/20260209132904.63908-1-a.s.protopopov@gmail.com Signed-off-by: Alexei Starovoitov Signed-off-by: Sasha Levin --- kernel/bpf/verifier.c | 52 +++++++++++++++++++++---------------------- 1 file changed, 26 insertions(+), 26 deletions(-) diff --git a/kernel/bpf/verifier.c b/kernel/bpf/verifier.c index 4338d233beecfa..dade674ffe071b 100644 --- a/kernel/bpf/verifier.c +++ b/kernel/bpf/verifier.c @@ -20219,29 +20219,29 @@ static int find_btf_percpu_datasec(struct btf *btf) } /* - * Add btf to the used_btfs array and return the index. (If the btf was - * already added, then just return the index.) Upon successful insertion - * increase btf refcnt, and, if present, also refcount the corresponding - * kernel module. + * Add btf to the env->used_btfs array. If needed, refcount the + * corresponding kernel module. To simplify caller's logic + * in case of error or if btf was added before the function + * decreases the btf refcount. */ static int __add_used_btf(struct bpf_verifier_env *env, struct btf *btf) { struct btf_mod_pair *btf_mod; + int ret = 0; int i; /* check whether we recorded this BTF (and maybe module) already */ for (i = 0; i < env->used_btf_cnt; i++) if (env->used_btfs[i].btf == btf) - return i; + goto ret_put; if (env->used_btf_cnt >= MAX_USED_BTFS) { verbose(env, "The total number of btfs per program has reached the limit of %u\n", MAX_USED_BTFS); - return -E2BIG; + ret = -E2BIG; + goto ret_put; } - btf_get(btf); - btf_mod = &env->used_btfs[env->used_btf_cnt]; btf_mod->btf = btf; btf_mod->module = NULL; @@ -20250,12 +20250,18 @@ static int __add_used_btf(struct bpf_verifier_env *env, struct btf *btf) if (btf_is_module(btf)) { btf_mod->module = btf_try_get_module(btf); if (!btf_mod->module) { - btf_put(btf); - return -ENXIO; + ret = -ENXIO; + goto ret_put; } } - return env->used_btf_cnt++; + env->used_btf_cnt++; + return 0; + +ret_put: + /* Either error or this BTF was already added */ + btf_put(btf); + return ret; } /* replace pseudo btf_id with kernel symbol address */ @@ -20352,9 +20358,7 @@ static int check_pseudo_btf_id(struct bpf_verifier_env *env, btf_fd = insn[1].imm; if (btf_fd) { - CLASS(fd, f)(btf_fd); - - btf = __btf_get_by_fd(f); + btf = btf_get_by_fd(btf_fd); if (IS_ERR(btf)) { verbose(env, "invalid module BTF object FD specified.\n"); return -EINVAL; @@ -20364,17 +20368,17 @@ static int check_pseudo_btf_id(struct bpf_verifier_env *env, verbose(env, "kernel is missing BTF, make sure CONFIG_DEBUG_INFO_BTF=y is specified in Kconfig.\n"); return -EINVAL; } + btf_get(btf_vmlinux); btf = btf_vmlinux; } err = __check_pseudo_btf_id(env, insn, aux, btf); - if (err) + if (err) { + btf_put(btf); return err; + } - err = __add_used_btf(env, btf); - if (err < 0) - return err; - return 0; + return __add_used_btf(env, btf); } static bool is_tracing_prog_type(enum bpf_prog_type type) @@ -24094,13 +24098,9 @@ static int add_fd_from_fd_array(struct bpf_verifier_env *env, int fd) return 0; } - btf = __btf_get_by_fd(f); - if (!IS_ERR(btf)) { - err = __add_used_btf(env, btf); - if (err < 0) - return err; - return 0; - } + btf = btf_get_by_fd(fd); + if (!IS_ERR(btf)) + return __add_used_btf(env, btf); verbose(env, "fd %d is not pointing to valid bpf_map or btf\n", fd); return PTR_ERR(map); From ca2a44b31c4d4de006430f3da18df2f8edd51733 Mon Sep 17 00:00:00 2001 From: Anton Protopopov Date: Fri, 13 Feb 2026 21:29:49 +0000 Subject: [PATCH 3779/3902] bpf: Add a map/btf from a fd array more consistently [ Upstream commit b0b1a8583d8e797114e613139e3e3318a1704690 ] The add_fd_from_fd_array() function takes a file descriptor as a parameter and tries to add either map or btf to the corresponding list of used objects. As was reported by Dan Carpenter, since the commit c81e4322acf0 ("bpf: Fix a potential use-after-free of BTF object"), the fdget() is called twice on the file descriptor, and thus userspace, potentially, can replace the file pointed to by the file descriptor in between the two calls. On practice, this shouldn't break anything on the kernel side, but for consistency fix the code such that only one fdget() is executed. Reported-by: Dan Carpenter Closes: https://lore.kernel.org/r/aY689z7gHNv8rgVO@stanley.mountain/ Fixes: ccd2d799ed44 ("bpf: Fix a potential use-after-free of BTF object") Signed-off-by: Anton Protopopov Link: https://lore.kernel.org/r/20260213212949.759321-1-a.s.protopopov@gmail.com Signed-off-by: Alexei Starovoitov Signed-off-by: Sasha Levin --- kernel/bpf/verifier.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/kernel/bpf/verifier.c b/kernel/bpf/verifier.c index dade674ffe071b..c4fa2268dbbc57 100644 --- a/kernel/bpf/verifier.c +++ b/kernel/bpf/verifier.c @@ -24098,9 +24098,11 @@ static int add_fd_from_fd_array(struct bpf_verifier_env *env, int fd) return 0; } - btf = btf_get_by_fd(fd); - if (!IS_ERR(btf)) + btf = __btf_get_by_fd(f); + if (!IS_ERR(btf)) { + btf_get(btf); return __add_used_btf(env, btf); + } verbose(env, "fd %d is not pointing to valid bpf_map or btf\n", fd); return PTR_ERR(map); From 9f9600daaa49ead8367396867c9009e367439947 Mon Sep 17 00:00:00 2001 From: Mohsin Bashir Date: Thu, 13 Nov 2025 15:26:10 -0800 Subject: [PATCH 3780/3902] eth: fbnic: Configure RDE settings for pause frame [ Upstream commit 0135333914d63181f823bd340ae96737c8a820ca ] fbnic supports pause frames. When pause frames are enabled presumably user expects lossless operation from the NIC. Make sure we configure RDE (Rx DMA Engine) to DROP_NEVER mode to avoid discards due to delays in fetching Rx descriptors from the host. While at it enable DROP_NEVER when NIC only has a single queue configured. In this case the NIC acts as a FIFO so there's no risk of head-of-line blocking other queues by making RDE wait. If pause is disabled this just moves the packet loss from the DMA engine to the Rx buffer. Remove redundant call to fbnic_config_drop_mode_rcq(), introduced by commit 0cb4c0a13723 ("eth: fbnic: Implement Rx queue alloc/start/stop/free"). This call does not add value as fbnic_enable_rcq(), which is called immediately afterward, already handles this. Although we do not support autoneg at this time, preserve tx_pause in .mac_link_up instead of fbnic_phylink_get_pauseparam() Signed-off-by: Mohsin Bashir Reviewed-by: Andrew Lunn Link: https://patch.msgid.link/20251113232610.1151712-1-mohsin.bashr@gmail.com Signed-off-by: Jakub Kicinski Stable-dep-of: bbeb3bfbffe0 ("eth: fbnic: set FBNIC_QUEUE_RDE_CTL0_EN_HDR_SPLIT on RDE_CTL0") Signed-off-by: Sasha Levin --- .../net/ethernet/meta/fbnic/fbnic_netdev.h | 2 ++ .../net/ethernet/meta/fbnic/fbnic_phylink.c | 3 +++ drivers/net/ethernet/meta/fbnic/fbnic_txrx.c | 26 ++++++++++++++++--- drivers/net/ethernet/meta/fbnic/fbnic_txrx.h | 1 + 4 files changed, 28 insertions(+), 4 deletions(-) diff --git a/drivers/net/ethernet/meta/fbnic/fbnic_netdev.h b/drivers/net/ethernet/meta/fbnic/fbnic_netdev.h index b0a87c57910f26..e6ca23a9957d9f 100644 --- a/drivers/net/ethernet/meta/fbnic/fbnic_netdev.h +++ b/drivers/net/ethernet/meta/fbnic/fbnic_netdev.h @@ -73,6 +73,8 @@ struct fbnic_net { /* Time stamping filter config */ struct kernel_hwtstamp_config hwtstamp_config; + + bool tx_pause; }; int __fbnic_open(struct fbnic_net *fbn); diff --git a/drivers/net/ethernet/meta/fbnic/fbnic_phylink.c b/drivers/net/ethernet/meta/fbnic/fbnic_phylink.c index 7ce3fdd252828d..62701923cfe96f 100644 --- a/drivers/net/ethernet/meta/fbnic/fbnic_phylink.c +++ b/drivers/net/ethernet/meta/fbnic/fbnic_phylink.c @@ -208,6 +208,9 @@ fbnic_phylink_mac_link_up(struct phylink_config *config, struct fbnic_net *fbn = netdev_priv(netdev); struct fbnic_dev *fbd = fbn->fbd; + fbn->tx_pause = tx_pause; + fbnic_config_drop_mode(fbn, tx_pause); + fbd->mac->link_up(fbd, tx_pause, rx_pause); } diff --git a/drivers/net/ethernet/meta/fbnic/fbnic_txrx.c b/drivers/net/ethernet/meta/fbnic/fbnic_txrx.c index b1e8ce89870f73..e99d17660230c5 100644 --- a/drivers/net/ethernet/meta/fbnic/fbnic_txrx.c +++ b/drivers/net/ethernet/meta/fbnic/fbnic_txrx.c @@ -2573,11 +2573,15 @@ static void fbnic_enable_bdq(struct fbnic_ring *hpq, struct fbnic_ring *ppq) } static void fbnic_config_drop_mode_rcq(struct fbnic_napi_vector *nv, - struct fbnic_ring *rcq) + struct fbnic_ring *rcq, bool tx_pause) { + struct fbnic_net *fbn = netdev_priv(nv->napi.dev); u32 drop_mode, rcq_ctl; - drop_mode = FBNIC_QUEUE_RDE_CTL0_DROP_IMMEDIATE; + if (!tx_pause && fbn->num_rx_queues > 1) + drop_mode = FBNIC_QUEUE_RDE_CTL0_DROP_IMMEDIATE; + else + drop_mode = FBNIC_QUEUE_RDE_CTL0_DROP_NEVER; /* Specify packet layout */ rcq_ctl = FIELD_PREP(FBNIC_QUEUE_RDE_CTL0_DROP_MODE_MASK, drop_mode) | @@ -2587,6 +2591,21 @@ static void fbnic_config_drop_mode_rcq(struct fbnic_napi_vector *nv, fbnic_ring_wr32(rcq, FBNIC_QUEUE_RDE_CTL0, rcq_ctl); } +void fbnic_config_drop_mode(struct fbnic_net *fbn, bool tx_pause) +{ + int i, t; + + for (i = 0; i < fbn->num_napi; i++) { + struct fbnic_napi_vector *nv = fbn->napi[i]; + + for (t = 0; t < nv->rxt_count; t++) { + struct fbnic_q_triad *qt = &nv->qt[nv->txt_count + t]; + + fbnic_config_drop_mode_rcq(nv, &qt->cmpl, tx_pause); + } + } +} + static void fbnic_config_rim_threshold(struct fbnic_ring *rcq, u16 nv_idx, u32 rx_desc) { u32 threshold; @@ -2636,7 +2655,7 @@ static void fbnic_enable_rcq(struct fbnic_napi_vector *nv, u32 hds_thresh = fbn->hds_thresh; u32 rcq_ctl = 0; - fbnic_config_drop_mode_rcq(nv, rcq); + fbnic_config_drop_mode_rcq(nv, rcq, fbn->tx_pause); /* Force lower bound on MAX_HEADER_BYTES. Below this, all frames should * be split at L4. It would also result in the frames being split at @@ -2699,7 +2718,6 @@ static void __fbnic_nv_enable(struct fbnic_napi_vector *nv) &nv->napi); fbnic_enable_bdq(&qt->sub0, &qt->sub1); - fbnic_config_drop_mode_rcq(nv, &qt->cmpl); fbnic_enable_rcq(nv, &qt->cmpl); } } diff --git a/drivers/net/ethernet/meta/fbnic/fbnic_txrx.h b/drivers/net/ethernet/meta/fbnic/fbnic_txrx.h index ca37da5a0b1797..27776e844e29bf 100644 --- a/drivers/net/ethernet/meta/fbnic/fbnic_txrx.h +++ b/drivers/net/ethernet/meta/fbnic/fbnic_txrx.h @@ -184,6 +184,7 @@ void fbnic_reset_netif_queues(struct fbnic_net *fbn); irqreturn_t fbnic_msix_clean_rings(int irq, void *data); void fbnic_napi_enable(struct fbnic_net *fbn); void fbnic_napi_disable(struct fbnic_net *fbn); +void fbnic_config_drop_mode(struct fbnic_net *fbn, bool tx_pause); void fbnic_enable(struct fbnic_net *fbn); void fbnic_disable(struct fbnic_net *fbn); void fbnic_flush(struct fbnic_net *fbn); From 9f441ef914527fd94228e496c843af8e56ca278b Mon Sep 17 00:00:00 2001 From: Bobby Eshleman Date: Wed, 11 Feb 2026 17:00:41 -0800 Subject: [PATCH 3781/3902] eth: fbnic: set FBNIC_QUEUE_RDE_CTL0_EN_HDR_SPLIT on RDE_CTL0 [ Upstream commit bbeb3bfbffe0279fa47c041658b037fb38a93965 ] Fix EN_HDR_SPLIT configuration by writing the field to RDE_CTL0 instead of RDE_CTL1. Because drop mode configuration and header splitting enablement both use RDE_CTL0, we consolidate these configurations into the single function fbnic_config_drop_mode. Fixes: 2b30fc01a6c7 ("eth: fbnic: Add support for HDS configuration") Signed-off-by: Bobby Eshleman Acked-by: Mohsin Bashir Link: https://patch.msgid.link/20260211-fbnic-tcp-hds-fixes-v1-1-55d050e6f606@meta.com Signed-off-by: Paolo Abeni Signed-off-by: Sasha Levin --- drivers/net/ethernet/meta/fbnic/fbnic_txrx.c | 25 +++++++++++--------- 1 file changed, 14 insertions(+), 11 deletions(-) diff --git a/drivers/net/ethernet/meta/fbnic/fbnic_txrx.c b/drivers/net/ethernet/meta/fbnic/fbnic_txrx.c index e99d17660230c5..fbdf79b6ad2ded 100644 --- a/drivers/net/ethernet/meta/fbnic/fbnic_txrx.c +++ b/drivers/net/ethernet/meta/fbnic/fbnic_txrx.c @@ -2573,7 +2573,8 @@ static void fbnic_enable_bdq(struct fbnic_ring *hpq, struct fbnic_ring *ppq) } static void fbnic_config_drop_mode_rcq(struct fbnic_napi_vector *nv, - struct fbnic_ring *rcq, bool tx_pause) + struct fbnic_ring *rcq, bool tx_pause, + bool hdr_split) { struct fbnic_net *fbn = netdev_priv(nv->napi.dev); u32 drop_mode, rcq_ctl; @@ -2586,22 +2587,26 @@ static void fbnic_config_drop_mode_rcq(struct fbnic_napi_vector *nv, /* Specify packet layout */ rcq_ctl = FIELD_PREP(FBNIC_QUEUE_RDE_CTL0_DROP_MODE_MASK, drop_mode) | FIELD_PREP(FBNIC_QUEUE_RDE_CTL0_MIN_HROOM_MASK, FBNIC_RX_HROOM) | - FIELD_PREP(FBNIC_QUEUE_RDE_CTL0_MIN_TROOM_MASK, FBNIC_RX_TROOM); + FIELD_PREP(FBNIC_QUEUE_RDE_CTL0_MIN_TROOM_MASK, FBNIC_RX_TROOM) | + FIELD_PREP(FBNIC_QUEUE_RDE_CTL0_EN_HDR_SPLIT, hdr_split); fbnic_ring_wr32(rcq, FBNIC_QUEUE_RDE_CTL0, rcq_ctl); } -void fbnic_config_drop_mode(struct fbnic_net *fbn, bool tx_pause) +void fbnic_config_drop_mode(struct fbnic_net *fbn, bool txp) { + bool hds; int i, t; + hds = fbn->hds_thresh < FBNIC_HDR_BYTES_MIN; + for (i = 0; i < fbn->num_napi; i++) { struct fbnic_napi_vector *nv = fbn->napi[i]; for (t = 0; t < nv->rxt_count; t++) { struct fbnic_q_triad *qt = &nv->qt[nv->txt_count + t]; - fbnic_config_drop_mode_rcq(nv, &qt->cmpl, tx_pause); + fbnic_config_drop_mode_rcq(nv, &qt->cmpl, txp, hds); } } } @@ -2652,20 +2657,18 @@ static void fbnic_enable_rcq(struct fbnic_napi_vector *nv, { struct fbnic_net *fbn = netdev_priv(nv->napi.dev); u32 log_size = fls(rcq->size_mask); - u32 hds_thresh = fbn->hds_thresh; u32 rcq_ctl = 0; - - fbnic_config_drop_mode_rcq(nv, rcq, fbn->tx_pause); + bool hdr_split; + u32 hds_thresh; /* Force lower bound on MAX_HEADER_BYTES. Below this, all frames should * be split at L4. It would also result in the frames being split at * L2/L3 depending on the frame size. */ - if (fbn->hds_thresh < FBNIC_HDR_BYTES_MIN) { - rcq_ctl = FBNIC_QUEUE_RDE_CTL0_EN_HDR_SPLIT; - hds_thresh = FBNIC_HDR_BYTES_MIN; - } + hdr_split = fbn->hds_thresh < FBNIC_HDR_BYTES_MIN; + fbnic_config_drop_mode_rcq(nv, rcq, fbn->tx_pause, hdr_split); + hds_thresh = max(fbn->hds_thresh, FBNIC_HDR_BYTES_MIN); rcq_ctl |= FIELD_PREP(FBNIC_QUEUE_RDE_CTL1_PADLEN_MASK, FBNIC_RX_PAD) | FIELD_PREP(FBNIC_QUEUE_RDE_CTL1_MAX_HDR_MASK, hds_thresh) | FIELD_PREP(FBNIC_QUEUE_RDE_CTL1_PAYLD_OFF_MASK, From aa090d2346def2d4d45a9dfc687031bd0606d154 Mon Sep 17 00:00:00 2001 From: Bobby Eshleman Date: Wed, 11 Feb 2026 17:00:42 -0800 Subject: [PATCH 3782/3902] eth: fbnic: increase FBNIC_HDR_BYTES_MIN from 128 to 256 bytes [ Upstream commit bd254115f38db3c046332bb62e8719e0dc7c2b53 ] Increase FBNIC_HDR_BYTES_MIN from 128 to 256 bytes. The previous minimum was too small to guarantee that very long L2+L3+L4 headers always fit within the header buffer. When EN_HDR_SPLIT is disabled and a packet exceeds MAX_HEADER_BYTES, splitting occurs at that byte offset instead of the header boundary, resulting in some of the header landing in the payload page. The increased minimum ensures headers always fit with the MAX_HEADER_BYTES cut off and land in the header page. Fixes: 2b30fc01a6c7 ("eth: fbnic: Add support for HDS configuration") Signed-off-by: Bobby Eshleman Acked-by: Mohsin Bashir Link: https://patch.msgid.link/20260211-fbnic-tcp-hds-fixes-v1-2-55d050e6f606@meta.com Signed-off-by: Paolo Abeni Signed-off-by: Sasha Levin --- drivers/net/ethernet/meta/fbnic/fbnic_txrx.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/net/ethernet/meta/fbnic/fbnic_txrx.h b/drivers/net/ethernet/meta/fbnic/fbnic_txrx.h index 27776e844e29bf..51a98f27d5d912 100644 --- a/drivers/net/ethernet/meta/fbnic/fbnic_txrx.h +++ b/drivers/net/ethernet/meta/fbnic/fbnic_txrx.h @@ -66,7 +66,7 @@ struct fbnic_net; (4096 - FBNIC_RX_HROOM - FBNIC_RX_TROOM - FBNIC_RX_PAD) #define FBNIC_HDS_THRESH_DEFAULT \ (1536 - FBNIC_RX_PAD) -#define FBNIC_HDR_BYTES_MIN 128 +#define FBNIC_HDR_BYTES_MIN 256 struct fbnic_pkt_buff { struct xdp_buff buff; From c55ba7501f97dd3ea80bcf233ba5035e534007f7 Mon Sep 17 00:00:00 2001 From: Bobby Eshleman Date: Wed, 11 Feb 2026 17:00:43 -0800 Subject: [PATCH 3783/3902] eth: fbnic: set DMA_HINT_L4 for all flows [ Upstream commit 0f30a31b55c4179fc55613a75ef41d496687d465 ] fbnic always advertises ETHTOOL_TCP_DATA_SPLIT_ENABLED via ethtool .get_ringparam. To enable proper splitting for all flow types, even for IP/Ethernet flows, this patch sets DMA_HINT_L4 unconditionally for all RSS and NFC flow steering rules. According to the spec, L4 falls back to L3 if no valid L4 is found, and L3 falls back to L2 if no L3 is found. This makes sure that the correct header boundary is used regardless of traffic type. This is important for zero-copy use cases where we must ensure that all ZC packets are split correctly. Fixes: 2b30fc01a6c7 ("eth: fbnic: Add support for HDS configuration") Signed-off-by: Bobby Eshleman Link: https://patch.msgid.link/20260211-fbnic-tcp-hds-fixes-v1-3-55d050e6f606@meta.com Signed-off-by: Paolo Abeni Signed-off-by: Sasha Levin --- drivers/net/ethernet/meta/fbnic/fbnic_ethtool.c | 3 +++ drivers/net/ethernet/meta/fbnic/fbnic_rpc.c | 5 ++--- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/drivers/net/ethernet/meta/fbnic/fbnic_ethtool.c b/drivers/net/ethernet/meta/fbnic/fbnic_ethtool.c index 95fac020eb93c7..08aed4103323e0 100644 --- a/drivers/net/ethernet/meta/fbnic/fbnic_ethtool.c +++ b/drivers/net/ethernet/meta/fbnic/fbnic_ethtool.c @@ -1142,6 +1142,9 @@ static int fbnic_set_cls_rule_ins(struct fbnic_net *fbn, return -EINVAL; } + dest |= FIELD_PREP(FBNIC_RPC_ACT_TBL0_DMA_HINT, + FBNIC_RCD_HDR_AL_DMA_HINT_L4); + /* Write action table values */ act_tcam->dest = dest; act_tcam->rss_en_mask = fbnic_flow_hash_2_rss_en_mask(fbn, hash_idx); diff --git a/drivers/net/ethernet/meta/fbnic/fbnic_rpc.c b/drivers/net/ethernet/meta/fbnic/fbnic_rpc.c index 7f31e890031c06..42a186db43ea93 100644 --- a/drivers/net/ethernet/meta/fbnic/fbnic_rpc.c +++ b/drivers/net/ethernet/meta/fbnic/fbnic_rpc.c @@ -338,9 +338,8 @@ void fbnic_rss_reinit(struct fbnic_dev *fbd, struct fbnic_net *fbn) else if (tstamp_mask & (1u << flow_type)) dest |= FBNIC_RPC_ACT_TBL0_TS_ENA; - if (act1_value[flow_type] & FBNIC_RPC_TCAM_ACT1_L4_VALID) - dest |= FIELD_PREP(FBNIC_RPC_ACT_TBL0_DMA_HINT, - FBNIC_RCD_HDR_AL_DMA_HINT_L4); + dest |= FIELD_PREP(FBNIC_RPC_ACT_TBL0_DMA_HINT, + FBNIC_RCD_HDR_AL_DMA_HINT_L4); rss_en_mask = fbnic_flow_hash_2_rss_en_mask(fbn, flow_type); From f998b2c4bec487063a586695159f9a1856e81c56 Mon Sep 17 00:00:00 2001 From: Antonio Quartulli Date: Thu, 12 Feb 2026 22:31:19 +0100 Subject: [PATCH 3784/3902] ovpn: tcp - don't deref NULL sk_socket member after tcp_close() [ Upstream commit 94560267d6c41b1ff3fafbab726e3f8a55a6af34 ] When deleting a peer in case of keepalive expiration, the peer is removed from the OpenVPN hashtable and is temporary inserted in a "release list" for further processing. This happens in: ovpn_peer_keepalive_work() unlock_ovpn(release_list) This processing includes detaching from the socket being used to talk to this peer, by restoring its original proto and socket ops/callbacks. In case of TCP it may happen that, while the peer is sitting in the release list, userspace decides to close the socket. This will result in a concurrent execution of: tcp_close(sk) __tcp_close(sk) sock_orphan(sk) sk_set_socket(sk, NULL) The last function call will set sk->sk_socket to NULL. When the releasing routine is resumed, ovpn_tcp_socket_detach() will attempt to dereference sk->sk_socket to restore its original ops member. This operation will crash due to sk->sk_socket being NULL. Fix this race condition by testing-and-accessing sk->sk_socket atomically under sk->sk_callback_lock. Link: https://lore.kernel.org/netdev/176996279620.3109699.15382994681575380467@eldamar.lan/ Link: https://github.com/OpenVPN/ovpn-net-next/issues/29 Signed-off-by: Antonio Quartulli Fixes: 11851cbd60ea ("ovpn: implement TCP transport") Link: https://patch.msgid.link/20260212213130.11497-1-antonio@openvpn.net Signed-off-by: Paolo Abeni Signed-off-by: Sasha Levin --- drivers/net/ovpn/tcp.c | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/drivers/net/ovpn/tcp.c b/drivers/net/ovpn/tcp.c index f0b4e07ba9245a..ec2bbc28c19666 100644 --- a/drivers/net/ovpn/tcp.c +++ b/drivers/net/ovpn/tcp.c @@ -199,7 +199,19 @@ void ovpn_tcp_socket_detach(struct ovpn_socket *ovpn_sock) sk->sk_data_ready = peer->tcp.sk_cb.sk_data_ready; sk->sk_write_space = peer->tcp.sk_cb.sk_write_space; sk->sk_prot = peer->tcp.sk_cb.prot; - sk->sk_socket->ops = peer->tcp.sk_cb.ops; + + /* tcp_close() may race this function and could set + * sk->sk_socket to NULL. It does so by invoking + * sock_orphan(), which holds sk_callback_lock before + * doing the assignment. + * + * For this reason we acquire the same lock to avoid + * sk_socket to disappear under our feet + */ + write_lock_bh(&sk->sk_callback_lock); + if (sk->sk_socket) + sk->sk_socket->ops = peer->tcp.sk_cb.ops; + write_unlock_bh(&sk->sk_callback_lock); rcu_assign_sk_user_data(sk, NULL); } From 36c28b028efba0f42218d41fed12c47ce217c1f1 Mon Sep 17 00:00:00 2001 From: Ziyi Guo Date: Thu, 12 Feb 2026 21:41:54 +0000 Subject: [PATCH 3785/3902] net: usb: catc: enable basic endpoint checking [ Upstream commit 9e7021d2aeae57c323a6f722ed7915686cdcc123 ] catc_probe() fills three URBs with hardcoded endpoint pipes without verifying the endpoint descriptors: - usb_sndbulkpipe(usbdev, 1) and usb_rcvbulkpipe(usbdev, 1) for TX/RX - usb_rcvintpipe(usbdev, 2) for interrupt status A malformed USB device can present these endpoints with transfer types that differ from what the driver assumes. Add a catc_usb_ep enum for endpoint numbers, replacing magic constants throughout. Add usb_check_bulk_endpoints() and usb_check_int_endpoints() calls after usb_set_interface() to verify endpoint types before use, rejecting devices with mismatched descriptors at probe time. Similar to - commit 90b7f2961798 ("net: usb: rtl8150: enable basic endpoint checking") which fixed the issue in rtl8150. Fixes: 1da177e4c3f4 ("Linux-2.6.12-rc2") Suggested-by: Simon Horman Signed-off-by: Ziyi Guo Link: https://patch.msgid.link/20260212214154.3609844-1-n7l8m4@u.northwestern.edu Signed-off-by: Paolo Abeni Signed-off-by: Sasha Levin --- drivers/net/usb/catc.c | 37 +++++++++++++++++++++++++++++++------ 1 file changed, 31 insertions(+), 6 deletions(-) diff --git a/drivers/net/usb/catc.c b/drivers/net/usb/catc.c index 6759388692f8e9..3c824340ffb068 100644 --- a/drivers/net/usb/catc.c +++ b/drivers/net/usb/catc.c @@ -64,6 +64,16 @@ static const char driver_name[] = "catc"; #define CTRL_QUEUE 16 /* Max control requests in flight (power of two) */ #define RX_PKT_SZ 1600 /* Max size of receive packet for F5U011 */ +/* + * USB endpoints. + */ + +enum catc_usb_ep { + CATC_USB_EP_CONTROL = 0, + CATC_USB_EP_BULK = 1, + CATC_USB_EP_INT_IN = 2, +}; + /* * Control requests. */ @@ -772,6 +782,13 @@ static int catc_probe(struct usb_interface *intf, const struct usb_device_id *id u8 broadcast[ETH_ALEN]; u8 *macbuf; int pktsz, ret = -ENOMEM; + static const u8 bulk_ep_addr[] = { + CATC_USB_EP_BULK | USB_DIR_OUT, + CATC_USB_EP_BULK | USB_DIR_IN, + 0}; + static const u8 int_ep_addr[] = { + CATC_USB_EP_INT_IN | USB_DIR_IN, + 0}; macbuf = kmalloc(ETH_ALEN, GFP_KERNEL); if (!macbuf) @@ -784,6 +801,14 @@ static int catc_probe(struct usb_interface *intf, const struct usb_device_id *id goto fail_mem; } + /* Verify that all required endpoints are present */ + if (!usb_check_bulk_endpoints(intf, bulk_ep_addr) || + !usb_check_int_endpoints(intf, int_ep_addr)) { + dev_err(dev, "Missing or invalid endpoints\n"); + ret = -ENODEV; + goto fail_mem; + } + netdev = alloc_etherdev(sizeof(struct catc)); if (!netdev) goto fail_mem; @@ -828,14 +853,14 @@ static int catc_probe(struct usb_interface *intf, const struct usb_device_id *id usb_fill_control_urb(catc->ctrl_urb, usbdev, usb_sndctrlpipe(usbdev, 0), NULL, NULL, 0, catc_ctrl_done, catc); - usb_fill_bulk_urb(catc->tx_urb, usbdev, usb_sndbulkpipe(usbdev, 1), - NULL, 0, catc_tx_done, catc); + usb_fill_bulk_urb(catc->tx_urb, usbdev, usb_sndbulkpipe(usbdev, CATC_USB_EP_BULK), + NULL, 0, catc_tx_done, catc); - usb_fill_bulk_urb(catc->rx_urb, usbdev, usb_rcvbulkpipe(usbdev, 1), - catc->rx_buf, pktsz, catc_rx_done, catc); + usb_fill_bulk_urb(catc->rx_urb, usbdev, usb_rcvbulkpipe(usbdev, CATC_USB_EP_BULK), + catc->rx_buf, pktsz, catc_rx_done, catc); - usb_fill_int_urb(catc->irq_urb, usbdev, usb_rcvintpipe(usbdev, 2), - catc->irq_buf, 2, catc_irq_done, catc, 1); + usb_fill_int_urb(catc->irq_urb, usbdev, usb_rcvintpipe(usbdev, CATC_USB_EP_INT_IN), + catc->irq_buf, 2, catc_irq_done, catc, 1); if (!catc->is_f5u011) { u32 *buf; From 654780dee9eae419e1648ea58462c4efe54518fa Mon Sep 17 00:00:00 2001 From: Ziyi Guo Date: Thu, 12 Feb 2026 22:40:40 +0000 Subject: [PATCH 3786/3902] xen-netback: reject zero-queue configuration from guest [ Upstream commit 6d1dc8014334c7fb25719999bca84d811e60a559 ] A malicious or buggy Xen guest can write "0" to the xenbus key "multi-queue-num-queues". The connect() function in the backend only validates the upper bound (requested_num_queues > xenvif_max_queues) but not zero, allowing requested_num_queues=0 to reach vzalloc(array_size(0, sizeof(struct xenvif_queue))), which triggers WARN_ON_ONCE(!size) in __vmalloc_node_range(). On systems with panic_on_warn=1, this allows a guest-to-host denial of service. The Xen network interface specification requires the queue count to be "greater than zero". Add a zero check to match the validation already present in xen-blkback, which has included this guard since its multi-queue support was added. Fixes: 8d3d53b3e433 ("xen-netback: Add support for multiple queues") Signed-off-by: Ziyi Guo Reviewed-by: Juergen Gross Link: https://patch.msgid.link/20260212224040.86674-1-n7l8m4@u.northwestern.edu Signed-off-by: Paolo Abeni Signed-off-by: Sasha Levin --- drivers/net/xen-netback/xenbus.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/drivers/net/xen-netback/xenbus.c b/drivers/net/xen-netback/xenbus.c index a78a25b872409a..61b547aab286a2 100644 --- a/drivers/net/xen-netback/xenbus.c +++ b/drivers/net/xen-netback/xenbus.c @@ -735,10 +735,11 @@ static void connect(struct backend_info *be) */ requested_num_queues = xenbus_read_unsigned(dev->otherend, "multi-queue-num-queues", 1); - if (requested_num_queues > xenvif_max_queues) { + if (requested_num_queues > xenvif_max_queues || + requested_num_queues == 0) { /* buggy or malicious guest */ xenbus_dev_fatal(dev, -EINVAL, - "guest requested %u queues, exceeding the maximum of %u.", + "guest requested %u queues, but valid range is 1 - %u.", requested_num_queues, xenvif_max_queues); return; } From 024355273593735c5f0a8c2faf5dfdcd758c4908 Mon Sep 17 00:00:00 2001 From: Allison Henderson Date: Thu, 12 Feb 2026 20:54:09 -0700 Subject: [PATCH 3787/3902] net/rds: rds_sendmsg should not discard payload_len [ Upstream commit da29e453dcb3aa7cabead7915f5f945d0add3a52 ] Commit 3db6e0d172c9 ("rds: use RCU to synchronize work-enqueue with connection teardown") modifies rds_sendmsg to avoid enqueueing work while a tear down is in progress. However, it also changed the return value of rds_sendmsg to that of rds_send_xmit instead of the payload_len. This means the user may incorrectly receive errno values when it should have simply received a payload of 0 while the peer attempts a reconnections. So this patch corrects the teardown handling code to only use the out error path in that case, thus restoring the original payload_len return value. Fixes: 3db6e0d172c9 ("rds: use RCU to synchronize work-enqueue with connection teardown") Reviewed-by: Simon Horman Signed-off-by: Allison Henderson Link: https://patch.msgid.link/20260213035409.1963391-1-achender@kernel.org Signed-off-by: Paolo Abeni Signed-off-by: Sasha Levin --- net/rds/send.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/net/rds/send.c b/net/rds/send.c index 0b3d0ef2f008b6..071c5dca969a23 100644 --- a/net/rds/send.c +++ b/net/rds/send.c @@ -1382,9 +1382,11 @@ int rds_sendmsg(struct socket *sock, struct msghdr *msg, size_t payload_len) else queue_delayed_work(rds_wq, &cpath->cp_send_w, 1); rcu_read_unlock(); + + if (ret) + goto out; } - if (ret) - goto out; + rds_message_put(rm); for (ind = 0; ind < vct.indx; ind++) From fae260fc84e1eae8f590c7907e53e8768df2d986 Mon Sep 17 00:00:00 2001 From: Nikolay Aleksandrov Date: Fri, 13 Feb 2026 09:00:30 +0200 Subject: [PATCH 3788/3902] net: bridge: mcast: always update mdb_n_entries for vlan contexts [ Upstream commit 8b769e311a86bb9d15c5658ad283b86fc8f080a2 ] syzbot triggered a warning[1] about the number of mdb entries in a context. It turned out that there are multiple ways to trigger that warning today (some got added during the years), the root cause of the problem is that the increase is done conditionally, and over the years these different conditions increased so there were new ways to trigger the warning, that is to do a decrease which wasn't paired with a previous increase. For example one way to trigger it is with flush: $ ip l add br0 up type bridge vlan_filtering 1 mcast_snooping 1 $ ip l add dumdum up master br0 type dummy $ bridge mdb add dev br0 port dumdum grp 239.0.0.1 permanent vid 1 $ ip link set dev br0 down $ ip link set dev br0 type bridge mcast_vlan_snooping 1 ^^^^ this will enable snooping, but will not update mdb_n_entries because in __br_multicast_enable_port_ctx() we check !netif_running $ bridge mdb flush dev br0 ^^^ this will trigger the warning because it will delete the pg which we added above, which will try to decrease mdb_n_entries Fix the problem by removing the conditional increase and always keep the count up-to-date while the vlan exists. In order to do that we have to first initialize it on port-vlan context creation, and then always increase or decrease the value regardless of mcast options. To keep the current behaviour we have to enforce the mdb limit only if the context is port's or if the port-vlan's mcast snooping is enabled. [1] ------------[ cut here ]------------ n == 0 WARNING: net/bridge/br_multicast.c:718 at br_multicast_port_ngroups_dec_one net/bridge/br_multicast.c:718 [inline], CPU#0: syz.4.4607/22043 WARNING: net/bridge/br_multicast.c:718 at br_multicast_port_ngroups_dec net/bridge/br_multicast.c:771 [inline], CPU#0: syz.4.4607/22043 WARNING: net/bridge/br_multicast.c:718 at br_multicast_del_pg+0x1bbe/0x1e20 net/bridge/br_multicast.c:825, CPU#0: syz.4.4607/22043 Modules linked in: CPU: 0 UID: 0 PID: 22043 Comm: syz.4.4607 Not tainted syzkaller #0 PREEMPT(full) Hardware name: Google Google Compute Engine/Google Compute Engine, BIOS Google 01/24/2026 RIP: 0010:br_multicast_port_ngroups_dec_one net/bridge/br_multicast.c:718 [inline] RIP: 0010:br_multicast_port_ngroups_dec net/bridge/br_multicast.c:771 [inline] RIP: 0010:br_multicast_del_pg+0x1bbe/0x1e20 net/bridge/br_multicast.c:825 Code: 41 5f 5d e9 04 7a 48 f7 e8 3f 73 5c f7 90 0f 0b 90 e9 cf fd ff ff e8 31 73 5c f7 90 0f 0b 90 e9 16 fd ff ff e8 23 73 5c f7 90 <0f> 0b 90 e9 60 fd ff ff e8 15 73 5c f7 eb 05 e8 0e 73 5c f7 48 8b RSP: 0018:ffffc9000c207220 EFLAGS: 00010293 RAX: ffffffff8a68042d RBX: ffff88807c6f1800 RCX: ffff888066e90000 RDX: 0000000000000000 RSI: 0000000000000000 RDI: 0000000000000000 RBP: 0000000000000000 R08: ffff888066e90000 R09: 000000000000000c R10: 000000000000000c R11: 0000000000000000 R12: ffff8880303ef800 R13: dffffc0000000000 R14: ffff888050eb11c4 R15: 1ffff1100a1d6238 FS: 00007fa45921b6c0(0000) GS:ffff8881256f5000(0000) knlGS:0000000000000000 CS: 0010 DS: 0000 ES: 0000 CR0: 0000000080050033 CR2: 00007fa4591f9ff8 CR3: 0000000081df2000 CR4: 00000000003526f0 Call Trace: br_mdb_flush_pgs net/bridge/br_mdb.c:1525 [inline] br_mdb_flush net/bridge/br_mdb.c:1544 [inline] br_mdb_del_bulk+0x5e2/0xb20 net/bridge/br_mdb.c:1561 rtnl_mdb_del+0x48a/0x640 net/core/rtnetlink.c:-1 rtnetlink_rcv_msg+0x77e/0xbe0 net/core/rtnetlink.c:6967 netlink_rcv_skb+0x232/0x4b0 net/netlink/af_netlink.c:2550 netlink_unicast_kernel net/netlink/af_netlink.c:1318 [inline] netlink_unicast+0x80f/0x9b0 net/netlink/af_netlink.c:1344 netlink_sendmsg+0x813/0xb40 net/netlink/af_netlink.c:1894 sock_sendmsg_nosec net/socket.c:727 [inline] __sock_sendmsg net/socket.c:742 [inline] ____sys_sendmsg+0xa68/0xad0 net/socket.c:2592 ___sys_sendmsg+0x2a5/0x360 net/socket.c:2646 __sys_sendmsg net/socket.c:2678 [inline] __do_sys_sendmsg net/socket.c:2683 [inline] __se_sys_sendmsg net/socket.c:2681 [inline] __x64_sys_sendmsg+0x1bd/0x2a0 net/socket.c:2681 do_syscall_x64 arch/x86/entry/syscall_64.c:63 [inline] do_syscall_64+0xe2/0xf80 arch/x86/entry/syscall_64.c:94 entry_SYSCALL_64_after_hwframe+0x77/0x7f RIP: 0033:0x7fa45839aeb9 Code: ff c3 66 2e 0f 1f 84 00 00 00 00 00 0f 1f 44 00 00 48 89 f8 48 89 f7 48 89 d6 48 89 ca 4d 89 c2 4d 89 c8 4c 8b 4c 24 08 0f 05 <48> 3d 01 f0 ff ff 73 01 c3 48 c7 c1 e8 ff ff ff f7 d8 64 89 01 48 RSP: 002b:00007fa45921b028 EFLAGS: 00000246 ORIG_RAX: 000000000000002e RAX: ffffffffffffffda RBX: 00007fa458615fa0 RCX: 00007fa45839aeb9 RDX: 0000000000000000 RSI: 00002000000000c0 RDI: 0000000000000004 RBP: 00007fa458408c1f R08: 0000000000000000 R09: 0000000000000000 R10: 0000000000000000 R11: 0000000000000246 R12: 0000000000000000 R13: 00007fa458616038 R14: 00007fa458615fa0 R15: 00007fff0b59fae8 Fixes: b57e8d870d52 ("net: bridge: Maintain number of MDB entries in net_bridge_mcast_port") Reported-by: syzbot+d5d1b7343531d17bd3c5@syzkaller.appspotmail.com Closes: https://lore.kernel.org/netdev/aYrWbRp83MQR1ife@debil/T/#t Reviewed-by: Ido Schimmel Signed-off-by: Nikolay Aleksandrov Link: https://patch.msgid.link/20260213070031.1400003-2-nikolay@nvidia.com Signed-off-by: Paolo Abeni Signed-off-by: Sasha Levin --- net/bridge/br_multicast.c | 45 ++++++++++++++++----------------------- 1 file changed, 18 insertions(+), 27 deletions(-) diff --git a/net/bridge/br_multicast.c b/net/bridge/br_multicast.c index 22d12e54596685..5855eb0502085f 100644 --- a/net/bridge/br_multicast.c +++ b/net/bridge/br_multicast.c @@ -244,14 +244,11 @@ br_multicast_port_vid_to_port_ctx(struct net_bridge_port *port, u16 vid) lockdep_assert_held_once(&port->br->multicast_lock); - if (!br_opt_get(port->br, BROPT_MCAST_VLAN_SNOOPING_ENABLED)) - return NULL; - /* Take RCU to access the vlan. */ rcu_read_lock(); vlan = br_vlan_find(nbp_vlan_group_rcu(port), vid); - if (vlan && !br_multicast_port_ctx_vlan_disabled(&vlan->port_mcast_ctx)) + if (vlan) pmctx = &vlan->port_mcast_ctx; rcu_read_unlock(); @@ -701,7 +698,10 @@ br_multicast_port_ngroups_inc_one(struct net_bridge_mcast_port *pmctx, u32 max = READ_ONCE(pmctx->mdb_max_entries); u32 n = READ_ONCE(pmctx->mdb_n_entries); - if (max && n >= max) { + /* enforce the max limit when it's a port pmctx or a port-vlan pmctx + * with snooping enabled + */ + if (!br_multicast_port_ctx_vlan_disabled(pmctx) && max && n >= max) { NL_SET_ERR_MSG_FMT_MOD(extack, "%s is already in %u groups, and mcast_max_groups=%u", what, n, max); return -E2BIG; @@ -736,9 +736,7 @@ static int br_multicast_port_ngroups_inc(struct net_bridge_port *port, return err; } - /* Only count on the VLAN context if VID is given, and if snooping on - * that VLAN is enabled. - */ + /* Only count on the VLAN context if VID is given */ if (!group->vid) return 0; @@ -2011,6 +2009,18 @@ void br_multicast_port_ctx_init(struct net_bridge_port *port, timer_setup(&pmctx->ip6_own_query.timer, br_ip6_multicast_port_query_expired, 0); #endif + /* initialize mdb_n_entries if a new port vlan is being created */ + if (vlan) { + struct net_bridge_port_group *pg; + u32 n = 0; + + spin_lock_bh(&port->br->multicast_lock); + hlist_for_each_entry(pg, &port->mglist, mglist) + if (pg->key.addr.vid == vlan->vid) + n++; + WRITE_ONCE(pmctx->mdb_n_entries, n); + spin_unlock_bh(&port->br->multicast_lock); + } } void br_multicast_port_ctx_deinit(struct net_bridge_mcast_port *pmctx) @@ -2094,25 +2104,6 @@ static void __br_multicast_enable_port_ctx(struct net_bridge_mcast_port *pmctx) br_ip4_multicast_add_router(brmctx, pmctx); br_ip6_multicast_add_router(brmctx, pmctx); } - - if (br_multicast_port_ctx_is_vlan(pmctx)) { - struct net_bridge_port_group *pg; - u32 n = 0; - - /* The mcast_n_groups counter might be wrong. First, - * BR_VLFLAG_MCAST_ENABLED is toggled before temporary entries - * are flushed, thus mcast_n_groups after the toggle does not - * reflect the true values. And second, permanent entries added - * while BR_VLFLAG_MCAST_ENABLED was disabled, are not reflected - * either. Thus we have to refresh the counter. - */ - - hlist_for_each_entry(pg, &pmctx->port->mglist, mglist) { - if (pg->key.addr.vid == pmctx->vlan->vid) - n++; - } - WRITE_ONCE(pmctx->mdb_n_entries, n); - } } static void br_multicast_enable_port_ctx(struct net_bridge_mcast_port *pmctx) From a6187e1d47f35f6e0b7cc0c0bca5b8f46dcadfb1 Mon Sep 17 00:00:00 2001 From: Aleksei Oladko Date: Fri, 13 Feb 2026 13:19:05 +0000 Subject: [PATCH 3789/3902] selftests: forwarding: vxlan_bridge_1d: fix test failure with br_netfilter enabled [ Upstream commit 02cb2e6bacbb08ebf6acb61be816efd11e1f4a21 ] The test generates VXLAN traffic using mausezahn, where the encapsulated inner IPv4 packet contains a zero IP header checksum. After VXLAN decapsulation, such packets do not pass sanity checks in br_netfilter and are dropped, which causes the test to fail. Fix this by calculating and setting a valid IPv4 header checksum for the encapsulated packet generated by mausezahn, so that the packet is accepted by br_netfilter. Fixed by using the payload_template_calc_checksum() / payload_template_expand_checksum() helpers that are only available in v6.3 and newer kernels. Fixes: a0b61f3d8ebf ("selftests: forwarding: vxlan_bridge_1d: Add an ECN decap test") Signed-off-by: Aleksei Oladko Reviewed-by: Ido Schimmel Link: https://patch.msgid.link/20260213131907.43351-2-aleksey.oladko@virtuozzo.com Signed-off-by: Paolo Abeni Signed-off-by: Sasha Levin --- .../net/forwarding/vxlan_bridge_1d.sh | 26 ++++++++++++------- 1 file changed, 16 insertions(+), 10 deletions(-) diff --git a/tools/testing/selftests/net/forwarding/vxlan_bridge_1d.sh b/tools/testing/selftests/net/forwarding/vxlan_bridge_1d.sh index b43816dd998cab..457f41d5e584b5 100755 --- a/tools/testing/selftests/net/forwarding/vxlan_bridge_1d.sh +++ b/tools/testing/selftests/net/forwarding/vxlan_bridge_1d.sh @@ -567,6 +567,21 @@ vxlan_encapped_ping_do() local inner_tos=$1; shift local outer_tos=$1; shift + local ipv4hdr=$(: + )"45:"$( : IP version + IHL + )"$inner_tos:"$( : IP TOS + )"00:54:"$( : IP total length + )"99:83:"$( : IP identification + )"40:00:"$( : IP flags + frag off + )"40:"$( : IP TTL + )"01:"$( : IP proto + )"CHECKSUM:"$( : IP header csum + )"c0:00:02:03:"$( : IP saddr: 192.0.2.3 + )"c0:00:02:01"$( : IP daddr: 192.0.2.1 + ) + local checksum=$(payload_template_calc_checksum "$ipv4hdr") + ipv4hdr=$(payload_template_expand_checksum "$ipv4hdr" $checksum) + $MZ $dev -c $count -d 100msec -q \ -b $next_hop_mac -B $dest_ip \ -t udp tos=$outer_tos,sp=23456,dp=$VXPORT,p=$(: @@ -577,16 +592,7 @@ vxlan_encapped_ping_do() )"$dest_mac:"$( : ETH daddr )"$(mac_get w2):"$( : ETH saddr )"08:00:"$( : ETH type - )"45:"$( : IP version + IHL - )"$inner_tos:"$( : IP TOS - )"00:54:"$( : IP total length - )"99:83:"$( : IP identification - )"40:00:"$( : IP flags + frag off - )"40:"$( : IP TTL - )"01:"$( : IP proto - )"00:00:"$( : IP header csum - )"c0:00:02:03:"$( : IP saddr: 192.0.2.3 - )"c0:00:02:01:"$( : IP daddr: 192.0.2.1 + )"$ipv4hdr:"$( : IPv4 header )"08:"$( : ICMP type )"00:"$( : ICMP code )"8b:f2:"$( : ICMP csum From 96815220626b291d3497c4c051062228a1fccca3 Mon Sep 17 00:00:00 2001 From: Aleksei Oladko Date: Fri, 13 Feb 2026 13:19:06 +0000 Subject: [PATCH 3790/3902] selftests: forwarding: vxlan_bridge_1d_ipv6: fix test failure with br_netfilter enabled MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [ Upstream commit ce9f6aec0fb780dafc1dfc5f47c688422aff464a ] The test generates VXLAN traffic using mausezahn, where the encapsulated inner IPv6 packet has an incorrect payload length set in the IPv6 header. After VXLAN decapsulation, such packets do not pass sanity checks in br_netfilter and are dropped, which causes the test to fail. Fix this by setting the correct IPv6 payload length for the encapsulated packet generated by mausezahn, so that the packet is accepted by br_netfilter. tools/testing/selftests/net/forwarding/vxlan_bridge_1d_ipv6.sh lines 698-706 )"00:03:"$( : Payload length )"3a:"$( : Next header )"04:"$( : Hop limit )"$saddr:"$( : IP saddr )"$daddr:"$( : IP daddr )"80:"$( : ICMPv6.type )"00:"$( : ICMPv6.code )"00:"$( : ICMPv6.checksum ) Data after IPv6 header: • 80: — 1 byte (ICMPv6 type) • 00: — 1 byte (ICMPv6 code) • 00: — 1 byte (ICMPv6 checksum, truncated) Total: 3 bytes → 00:03 is correct. The old value 00:08 did not match the actual payload size. Fixes: b07e9957f220 ("selftests: forwarding: Add VxLAN tests with a VLAN-unaware bridge for IPv6") Signed-off-by: Aleksei Oladko Reviewed-by: Ido Schimmel Link: https://patch.msgid.link/20260213131907.43351-3-aleksey.oladko@virtuozzo.com Signed-off-by: Paolo Abeni Signed-off-by: Sasha Levin --- tools/testing/selftests/net/forwarding/vxlan_bridge_1d_ipv6.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/testing/selftests/net/forwarding/vxlan_bridge_1d_ipv6.sh b/tools/testing/selftests/net/forwarding/vxlan_bridge_1d_ipv6.sh index a603f7b0a08f07..e642feeada0e7e 100755 --- a/tools/testing/selftests/net/forwarding/vxlan_bridge_1d_ipv6.sh +++ b/tools/testing/selftests/net/forwarding/vxlan_bridge_1d_ipv6.sh @@ -695,7 +695,7 @@ vxlan_encapped_ping_do() )"6"$( : IP version )"$inner_tos"$( : Traffic class )"0:00:00:"$( : Flow label - )"00:08:"$( : Payload length + )"00:03:"$( : Payload length )"3a:"$( : Next header )"04:"$( : Hop limit )"$saddr:"$( : IP saddr From 9a6b2ba7367f473b9827e78bbefadb1556083ff7 Mon Sep 17 00:00:00 2001 From: Florian Westphal Date: Wed, 11 Feb 2026 12:53:09 +0100 Subject: [PATCH 3791/3902] netfilter: nf_conntrack_h323: don't pass uninitialised l3num value [ Upstream commit a6d28eb8efe96b3e35c92efdf1bfacb0cccf541f ] Mihail Milev reports: Error: UNINIT (CWE-457): net/netfilter/nf_conntrack_h323_main.c:1189:2: var_decl: Declaring variable "tuple" without initializer. net/netfilter/nf_conntrack_h323_main.c:1197:2: uninit_use_in_call: Using uninitialized value "tuple.src.l3num" when calling "__nf_ct_expect_find". net/netfilter/nf_conntrack_expect.c:142:2: read_value: Reading value "tuple->src.l3num" when calling "nf_ct_expect_dst_hash". 1195| tuple.dst.protonum = IPPROTO_TCP; 1196| 1197|-> exp = __nf_ct_expect_find(net, nf_ct_zone(ct), &tuple); 1198| if (exp && exp->master == ct) 1199| return exp; Switch this to a C99 initialiser and set the l3num value. Fixes: f587de0e2feb ("[NETFILTER]: nf_conntrack/nf_nat: add H.323 helper port") Signed-off-by: Florian Westphal Signed-off-by: Sasha Levin --- net/netfilter/nf_conntrack_h323_main.c | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/net/netfilter/nf_conntrack_h323_main.c b/net/netfilter/nf_conntrack_h323_main.c index 14f73872f64778..e35814d68ce30b 100644 --- a/net/netfilter/nf_conntrack_h323_main.c +++ b/net/netfilter/nf_conntrack_h323_main.c @@ -1186,13 +1186,13 @@ static struct nf_conntrack_expect *find_expect(struct nf_conn *ct, { struct net *net = nf_ct_net(ct); struct nf_conntrack_expect *exp; - struct nf_conntrack_tuple tuple; + struct nf_conntrack_tuple tuple = { + .src.l3num = nf_ct_l3num(ct), + .dst.protonum = IPPROTO_TCP, + .dst.u.tcp.port = port, + }; - memset(&tuple.src.u3, 0, sizeof(tuple.src.u3)); - tuple.src.u.tcp.port = 0; memcpy(&tuple.dst.u3, addr, sizeof(tuple.dst.u3)); - tuple.dst.u.tcp.port = port; - tuple.dst.protonum = IPPROTO_TCP; exp = __nf_ct_expect_find(net, nf_ct_zone(ct), &tuple); if (exp && exp->master == ct) From bae53b3baf2ff2f45f9205c438818fc055601a54 Mon Sep 17 00:00:00 2001 From: Julian Anastasov Date: Sat, 14 Feb 2026 16:58:50 +0200 Subject: [PATCH 3792/3902] ipvs: do not keep dest_dst if dev is going down [ Upstream commit 8fde939b0206afc1d5846217a01a16b9bc8c7896 ] There is race between the netdev notifier ip_vs_dst_event() and the code that caches dst with dev that is going down. As the FIB can be notified for the closed device after our handler finishes, it is possible valid route to be returned and cached resuling in a leaked dev reference until the dest is not removed. To prevent new dest_dst to be attached to dest just after the handler dropped the old one, add a netif_running() check to make sure the notifier handler is not currently running for device that is closing. Fixes: 7a4f0761fce3 ("IPVS: init and cleanup restructuring") Signed-off-by: Julian Anastasov Signed-off-by: Florian Westphal Signed-off-by: Sasha Levin --- net/netfilter/ipvs/ip_vs_xmit.c | 46 ++++++++++++++++++++++++++------- 1 file changed, 36 insertions(+), 10 deletions(-) diff --git a/net/netfilter/ipvs/ip_vs_xmit.c b/net/netfilter/ipvs/ip_vs_xmit.c index 618fbe1240b54c..ecbcdc43263d67 100644 --- a/net/netfilter/ipvs/ip_vs_xmit.c +++ b/net/netfilter/ipvs/ip_vs_xmit.c @@ -295,6 +295,12 @@ static inline bool decrement_ttl(struct netns_ipvs *ipvs, return true; } +/* rt has device that is down */ +static bool rt_dev_is_down(const struct net_device *dev) +{ + return dev && !netif_running(dev); +} + /* Get route to destination or remote server */ static int __ip_vs_get_out_rt(struct netns_ipvs *ipvs, int skb_af, struct sk_buff *skb, @@ -310,9 +316,11 @@ __ip_vs_get_out_rt(struct netns_ipvs *ipvs, int skb_af, struct sk_buff *skb, if (dest) { dest_dst = __ip_vs_dst_check(dest); - if (likely(dest_dst)) + if (likely(dest_dst)) { rt = dst_rtable(dest_dst->dst_cache); - else { + if (ret_saddr) + *ret_saddr = dest_dst->dst_saddr.ip; + } else { dest_dst = ip_vs_dest_dst_alloc(); spin_lock_bh(&dest->dst_lock); if (!dest_dst) { @@ -328,14 +336,22 @@ __ip_vs_get_out_rt(struct netns_ipvs *ipvs, int skb_af, struct sk_buff *skb, ip_vs_dest_dst_free(dest_dst); goto err_unreach; } - __ip_vs_dst_set(dest, dest_dst, &rt->dst, 0); + /* It is forbidden to attach dest->dest_dst if + * device is going down. + */ + if (!rt_dev_is_down(dst_dev_rcu(&rt->dst))) + __ip_vs_dst_set(dest, dest_dst, &rt->dst, 0); + else + noref = 0; spin_unlock_bh(&dest->dst_lock); IP_VS_DBG(10, "new dst %pI4, src %pI4, refcnt=%d\n", &dest->addr.ip, &dest_dst->dst_saddr.ip, rcuref_read(&rt->dst.__rcuref)); + if (ret_saddr) + *ret_saddr = dest_dst->dst_saddr.ip; + if (!noref) + ip_vs_dest_dst_free(dest_dst); } - if (ret_saddr) - *ret_saddr = dest_dst->dst_saddr.ip; } else { noref = 0; @@ -472,9 +488,11 @@ __ip_vs_get_out_rt_v6(struct netns_ipvs *ipvs, int skb_af, struct sk_buff *skb, if (dest) { dest_dst = __ip_vs_dst_check(dest); - if (likely(dest_dst)) + if (likely(dest_dst)) { rt = dst_rt6_info(dest_dst->dst_cache); - else { + if (ret_saddr) + *ret_saddr = dest_dst->dst_saddr.in6; + } else { u32 cookie; dest_dst = ip_vs_dest_dst_alloc(); @@ -495,14 +513,22 @@ __ip_vs_get_out_rt_v6(struct netns_ipvs *ipvs, int skb_af, struct sk_buff *skb, } rt = dst_rt6_info(dst); cookie = rt6_get_cookie(rt); - __ip_vs_dst_set(dest, dest_dst, &rt->dst, cookie); + /* It is forbidden to attach dest->dest_dst if + * device is going down. + */ + if (!rt_dev_is_down(dst_dev_rcu(&rt->dst))) + __ip_vs_dst_set(dest, dest_dst, &rt->dst, cookie); + else + noref = 0; spin_unlock_bh(&dest->dst_lock); IP_VS_DBG(10, "new dst %pI6, src %pI6, refcnt=%d\n", &dest->addr.in6, &dest_dst->dst_saddr.in6, rcuref_read(&rt->dst.__rcuref)); + if (ret_saddr) + *ret_saddr = dest_dst->dst_saddr.in6; + if (!noref) + ip_vs_dest_dst_free(dest_dst); } - if (ret_saddr) - *ret_saddr = dest_dst->dst_saddr.in6; } else { noref = 0; dst = __ip_vs_route_output_v6(net, daddr, ret_saddr, do_xfrm, From 50422613185d505201167e8bdd2f2700790d5db6 Mon Sep 17 00:00:00 2001 From: Pablo Neira Ayuso Date: Tue, 17 Feb 2026 12:56:39 +0100 Subject: [PATCH 3793/3902] net: remove WARN_ON_ONCE when accessing forward path array [ Upstream commit 008e7a7c293b30bc43e4368dac6ea3808b75a572 ] Although unlikely, recent support for IPIP tunnels increases chances of reaching this WARN_ON_ONCE if userspace manages to build a sufficiently long forward path. Remove it. Fixes: ddb94eafab8b ("net: resolve forwarding path from virtual netdevice and HW destination address") Signed-off-by: Pablo Neira Ayuso Signed-off-by: Florian Westphal Signed-off-by: Sasha Levin --- net/core/dev.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/net/core/dev.c b/net/core/dev.c index 5b536860138d19..ff70c902a4196b 100644 --- a/net/core/dev.c +++ b/net/core/dev.c @@ -738,7 +738,7 @@ static struct net_device_path *dev_fwd_path(struct net_device_path_stack *stack) { int k = stack->num_paths++; - if (WARN_ON_ONCE(k >= NET_DEVICE_PATH_STACK_MAX)) + if (k >= NET_DEVICE_PATH_STACK_MAX) return NULL; return &stack->path[k]; From dbd0af8083dd201f07c49110b2ee93710abdff28 Mon Sep 17 00:00:00 2001 From: Inseo An Date: Tue, 17 Feb 2026 21:14:40 +0900 Subject: [PATCH 3794/3902] netfilter: nf_tables: fix use-after-free in nf_tables_addchain() [ Upstream commit 71e99ee20fc3f662555118cf1159443250647533 ] nf_tables_addchain() publishes the chain to table->chains via list_add_tail_rcu() (in nft_chain_add()) before registering hooks. If nf_tables_register_hook() then fails, the error path calls nft_chain_del() (list_del_rcu()) followed by nf_tables_chain_destroy() with no RCU grace period in between. This creates two use-after-free conditions: 1) Control-plane: nf_tables_dump_chains() traverses table->chains under rcu_read_lock(). A concurrent dump can still be walking the chain when the error path frees it. 2) Packet path: for NFPROTO_INET, nf_register_net_hook() briefly installs the IPv4 hook before IPv6 registration fails. Packets entering nft_do_chain() via the transient IPv4 hook can still be dereferencing chain->blob_gen_X when the error path frees the chain. Add synchronize_rcu() between nft_chain_del() and the chain destroy so that all RCU readers -- both dump threads and in-flight packet evaluation -- have finished before the chain is freed. Fixes: 91c7b38dc9f0 ("netfilter: nf_tables: use new transaction infrastructure to handle chain") Signed-off-by: Inseo An Signed-off-by: Florian Westphal Signed-off-by: Sasha Levin --- net/netfilter/nf_tables_api.c | 1 + 1 file changed, 1 insertion(+) diff --git a/net/netfilter/nf_tables_api.c b/net/netfilter/nf_tables_api.c index 9051f2c3595a20..df367638cdef0f 100644 --- a/net/netfilter/nf_tables_api.c +++ b/net/netfilter/nf_tables_api.c @@ -2822,6 +2822,7 @@ static int nf_tables_addchain(struct nft_ctx *ctx, u8 family, u8 policy, err_register_hook: nft_chain_del(chain); + synchronize_rcu(); err_chain_add: nft_trans_destroy(trans); err_trans: From 71b5fc8e8e17eace1304cec7d080e56a6c2d507d Mon Sep 17 00:00:00 2001 From: Eric Dumazet Date: Mon, 16 Feb 2026 10:22:02 +0000 Subject: [PATCH 3795/3902] ipv6: fix a race in ip6_sock_set_v6only() [ Upstream commit 452a3eee22c57a5786ae6db5c97f3b0ec13bb3b7 ] It is unlikely that this function will be ever called with isk->inet_num being not zero. Perform the check on isk->inet_num inside the locked section for complete safety. Fixes: 9b115749acb24 ("ipv6: add ip6_sock_set_v6only") Signed-off-by: Eric Dumazet Reviewed-by: Simon Horman Reviewed-by: Fernando Fernandez Mancera Link: https://patch.msgid.link/20260216102202.3343588-1-edumazet@google.com Signed-off-by: Jakub Kicinski Signed-off-by: Sasha Levin --- include/net/ipv6.h | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/include/net/ipv6.h b/include/net/ipv6.h index 2ccdf85f34f164..f0936df7567e3d 100644 --- a/include/net/ipv6.h +++ b/include/net/ipv6.h @@ -1280,12 +1280,15 @@ int ipv6_sock_mc_drop(struct sock *sk, int ifindex, static inline int ip6_sock_set_v6only(struct sock *sk) { - if (inet_sk(sk)->inet_num) - return -EINVAL; + int ret = 0; + lock_sock(sk); - sk->sk_ipv6only = true; + if (inet_sk(sk)->inet_num) + ret = -EINVAL; + else + sk->sk_ipv6only = true; release_sock(sk); - return 0; + return ret; } static inline void ip6_sock_set_recverr(struct sock *sk) From 5c939ebbd72f4e3ae6d1426afa243c1ffa5877f8 Mon Sep 17 00:00:00 2001 From: Jakub Kicinski Date: Tue, 17 Feb 2026 11:41:50 -0800 Subject: [PATCH 3796/3902] bpftool: Fix truncated netlink dumps [ Upstream commit 3b39d73cc3379360a33eb583b17f21fe55e1288e ] Netlink requires that the recv buffer used during dumps is at least min(PAGE_SIZE, 8k) (see the man page). Otherwise the messages will get truncated. Make sure bpftool follows this requirement, avoid missing information on systems with large pages. Acked-by: Quentin Monnet Fixes: 7084566a236f ("tools/bpftool: Remove libbpf_internal.h usage in bpftool") Signed-off-by: Jakub Kicinski Link: https://lore.kernel.org/r/20260217194150.734701-1-kuba@kernel.org Signed-off-by: Alexei Starovoitov Signed-off-by: Sasha Levin --- tools/bpf/bpftool/net.c | 5 ++++- tools/lib/bpf/netlink.c | 4 +++- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/tools/bpf/bpftool/net.c b/tools/bpf/bpftool/net.c index cfc6f944f7c33a..1a06b0b5eef350 100644 --- a/tools/bpf/bpftool/net.c +++ b/tools/bpf/bpftool/net.c @@ -156,7 +156,7 @@ static int netlink_recv(int sock, __u32 nl_pid, __u32 seq, bool multipart = true; struct nlmsgerr *err; struct nlmsghdr *nh; - char buf[4096]; + char buf[8192]; int len, ret; while (multipart) { @@ -201,6 +201,9 @@ static int netlink_recv(int sock, __u32 nl_pid, __u32 seq, return ret; } } + + if (len) + p_err("Invalid message or trailing data in Netlink response: %d bytes left", len); } ret = 0; done: diff --git a/tools/lib/bpf/netlink.c b/tools/lib/bpf/netlink.c index c997e69d507fef..c9a78fb16f115a 100644 --- a/tools/lib/bpf/netlink.c +++ b/tools/lib/bpf/netlink.c @@ -143,7 +143,7 @@ static int libbpf_netlink_recv(int sock, __u32 nl_pid, int seq, struct nlmsghdr *nh; int len, ret; - ret = alloc_iov(&iov, 4096); + ret = alloc_iov(&iov, 8192); if (ret) goto done; @@ -212,6 +212,8 @@ static int libbpf_netlink_recv(int sock, __u32 nl_pid, int seq, } } } + if (len) + pr_warn("Invalid message or trailing data in Netlink response: %d bytes left\n", len); } ret = 0; done: From 4e34deebfb880111e318db0cbb81b36e57335521 Mon Sep 17 00:00:00 2001 From: Arnd Bergmann Date: Mon, 16 Feb 2026 11:54:54 +0100 Subject: [PATCH 3797/3902] net: psp: select CONFIG_SKB_EXTENSIONS [ Upstream commit 6e980df452169f82674f2e650079c1fe0aee343d ] psp now uses skb extensions, failing to build when that is disabled: In file included from include/net/psp.h:7, from net/psp/psp_sock.c:9: include/net/psp/functions.h: In function '__psp_skb_coalesce_diff': include/net/psp/functions.h:60:13: error: implicit declaration of function 'skb_ext_find'; did you mean 'skb_ext_copy'? [-Wimplicit-function-declaration] 60 | a = skb_ext_find(one, SKB_EXT_PSP); | ^~~~~~~~~~~~ | skb_ext_copy include/net/psp/functions.h:60:31: error: 'SKB_EXT_PSP' undeclared (first use in this function) 60 | a = skb_ext_find(one, SKB_EXT_PSP); | ^~~~~~~~~~~ include/net/psp/functions.h:60:31: note: each undeclared identifier is reported only once for each function it appears in include/net/psp/functions.h: In function '__psp_sk_rx_policy_check': include/net/psp/functions.h:94:53: error: 'SKB_EXT_PSP' undeclared (first use in this function) 94 | struct psp_skb_ext *pse = skb_ext_find(skb, SKB_EXT_PSP); | ^~~~~~~~~~~ net/psp/psp_sock.c: In function 'psp_sock_recv_queue_check': net/psp/psp_sock.c:164:41: error: 'SKB_EXT_PSP' undeclared (first use in this function) 164 | pse = skb_ext_find(skb, SKB_EXT_PSP); | ^~~~~~~~~~~ Select the Kconfig symbol as we do from its other users. Fixes: 6b46ca260e22 ("net: psp: add socket security association code") Signed-off-by: Arnd Bergmann Reviewed-by: Simon Horman Reviewed-by: Daniel Zahka Link: https://patch.msgid.link/20260216105500.2382181-1-arnd@kernel.org Signed-off-by: Jakub Kicinski Signed-off-by: Sasha Levin --- net/psp/Kconfig | 1 + 1 file changed, 1 insertion(+) diff --git a/net/psp/Kconfig b/net/psp/Kconfig index 371e8771f3bd38..84d6b0f2546067 100644 --- a/net/psp/Kconfig +++ b/net/psp/Kconfig @@ -6,6 +6,7 @@ config INET_PSP bool "PSP Security Protocol support" depends on INET select SKB_DECRYPTED + select SKB_EXTENSIONS select SOCK_VALIDATE_XMIT help Enable kernel support for the PSP Security Protocol (PSP). From d96b0d661ef0434b644ed48901e916bd9fe5f64a Mon Sep 17 00:00:00 2001 From: Eric Dumazet Date: Mon, 16 Feb 2026 10:01:49 +0000 Subject: [PATCH 3798/3902] ping: annotate data-races in ping_lookup() [ Upstream commit ad5dfde2a5733aaf652ea3e40c8c5e071e935901 ] isk->inet_num, isk->inet_rcv_saddr and sk->sk_bound_dev_if are read locklessly in ping_lookup(). Add READ_ONCE()/WRITE_ONCE() annotations. The race on isk->inet_rcv_saddr is probably coming from IPv6 support, but does not deserve a specific backport. Fixes: dbca1596bbb0 ("ping: convert to RCU lookups, get rid of rwlock") Signed-off-by: Eric Dumazet Reviewed-by: Kuniyuki Iwashima Link: https://patch.msgid.link/20260216100149.3319315-1-edumazet@google.com Signed-off-by: Jakub Kicinski Signed-off-by: Sasha Levin --- net/ipv4/ping.c | 31 +++++++++++++++++++------------ 1 file changed, 19 insertions(+), 12 deletions(-) diff --git a/net/ipv4/ping.c b/net/ipv4/ping.c index a5227d23bb0b5b..690f486173e01d 100644 --- a/net/ipv4/ping.c +++ b/net/ipv4/ping.c @@ -148,7 +148,7 @@ void ping_unhash(struct sock *sk) pr_debug("ping_unhash(isk=%p,isk->num=%u)\n", isk, isk->inet_num); spin_lock(&ping_table.lock); if (sk_del_node_init_rcu(sk)) { - isk->inet_num = 0; + WRITE_ONCE(isk->inet_num, 0); isk->inet_sport = 0; sock_prot_inuse_add(sock_net(sk), sk->sk_prot, -1); } @@ -181,31 +181,35 @@ static struct sock *ping_lookup(struct net *net, struct sk_buff *skb, u16 ident) } sk_for_each_rcu(sk, hslot) { + int bound_dev_if; + if (!net_eq(sock_net(sk), net)) continue; isk = inet_sk(sk); pr_debug("iterate\n"); - if (isk->inet_num != ident) + if (READ_ONCE(isk->inet_num) != ident) continue; + bound_dev_if = READ_ONCE(sk->sk_bound_dev_if); if (skb->protocol == htons(ETH_P_IP) && sk->sk_family == AF_INET) { + __be32 rcv_saddr = READ_ONCE(isk->inet_rcv_saddr); + pr_debug("found: %p: num=%d, daddr=%pI4, dif=%d\n", sk, - (int) isk->inet_num, &isk->inet_rcv_saddr, - sk->sk_bound_dev_if); + ident, &rcv_saddr, + bound_dev_if); - if (isk->inet_rcv_saddr && - isk->inet_rcv_saddr != ip_hdr(skb)->daddr) + if (rcv_saddr && rcv_saddr != ip_hdr(skb)->daddr) continue; #if IS_ENABLED(CONFIG_IPV6) } else if (skb->protocol == htons(ETH_P_IPV6) && sk->sk_family == AF_INET6) { pr_debug("found: %p: num=%d, daddr=%pI6c, dif=%d\n", sk, - (int) isk->inet_num, + ident, &sk->sk_v6_rcv_saddr, - sk->sk_bound_dev_if); + bound_dev_if); if (!ipv6_addr_any(&sk->sk_v6_rcv_saddr) && !ipv6_addr_equal(&sk->sk_v6_rcv_saddr, @@ -216,8 +220,8 @@ static struct sock *ping_lookup(struct net *net, struct sk_buff *skb, u16 ident) continue; } - if (sk->sk_bound_dev_if && sk->sk_bound_dev_if != dif && - sk->sk_bound_dev_if != sdif) + if (bound_dev_if && bound_dev_if != dif && + bound_dev_if != sdif) continue; goto exit; @@ -392,7 +396,9 @@ static void ping_set_saddr(struct sock *sk, struct sockaddr *saddr) if (saddr->sa_family == AF_INET) { struct inet_sock *isk = inet_sk(sk); struct sockaddr_in *addr = (struct sockaddr_in *) saddr; - isk->inet_rcv_saddr = isk->inet_saddr = addr->sin_addr.s_addr; + + isk->inet_saddr = addr->sin_addr.s_addr; + WRITE_ONCE(isk->inet_rcv_saddr, addr->sin_addr.s_addr); #if IS_ENABLED(CONFIG_IPV6) } else if (saddr->sa_family == AF_INET6) { struct sockaddr_in6 *addr = (struct sockaddr_in6 *) saddr; @@ -849,7 +855,8 @@ int ping_recvmsg(struct sock *sk, struct msghdr *msg, size_t len, int flags, struct sk_buff *skb; int copied, err; - pr_debug("ping_recvmsg(sk=%p,sk->num=%u)\n", isk, isk->inet_num); + pr_debug("ping_recvmsg(sk=%p,sk->num=%u)\n", isk, + READ_ONCE(isk->inet_num)); err = -EOPNOTSUPP; if (flags & MSG_OOB) From 2a06576b611e26cd0284ab138331bcd85785ea04 Mon Sep 17 00:00:00 2001 From: Jakub Kicinski Date: Fri, 13 Feb 2026 19:51:59 -0800 Subject: [PATCH 3799/3902] selftests: tc_actions: don't dump 2MB of \0 to stdout [ Upstream commit 32b70e62034aa72f8414ad4e9122cce7ad418c48 ] Since we started running selftests in NIPA we have been seeing tc_actions.sh generate a soft lockup warning on ~20% of the runs. On the pre-netdev foundation setup it was actually a missed irq splat from the console. Now it's either that or a lockup. I initially suspected a socket locking issue since the test is exercising local loopback with act_mirred. After hours of staring at this I noticed in strace that ncat when -o $file is specified _both_ saves the output to the file and still prints it to stdout. Because the file being sent is constructed with: dd conv=sparse status=none if=/dev/zero bs=1M count=2 of=$mirred ^^^^^^^^^ the data printed is all \0. Most terminals don't display nul characters (and neither does vng output capture save them). But QEMU's serial console still has to poke them thru which is very slow and causes the lockup (if the file is >600kB). Replace the '-o $file' with '> $file'. This speeds the test up from 2m20s to 18s on debug kernels, and prevents the warnings. Fixes: ca22da2fbd69 ("act_mirred: use the backlog for nested calls to mirred ingress") Reviewed-by: Simon Horman Link: https://patch.msgid.link/20260214035159.2119699-1-kuba@kernel.org Signed-off-by: Jakub Kicinski Signed-off-by: Sasha Levin --- tools/testing/selftests/net/forwarding/tc_actions.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/testing/selftests/net/forwarding/tc_actions.sh b/tools/testing/selftests/net/forwarding/tc_actions.sh index ea89e558672db0..86edbc7e2489b3 100755 --- a/tools/testing/selftests/net/forwarding/tc_actions.sh +++ b/tools/testing/selftests/net/forwarding/tc_actions.sh @@ -223,7 +223,7 @@ mirred_egress_to_ingress_tcp_test() ip_proto icmp \ action drop - ip vrf exec v$h1 ncat --recv-only -w10 -l -p 12345 -o $mirred_e2i_tf2 & + ip vrf exec v$h1 ncat --recv-only -w10 -l -p 12345 > $mirred_e2i_tf2 & local rpid=$! ip vrf exec v$h1 ncat -w1 --send-only 192.0.2.2 12345 <$mirred_e2i_tf1 wait -n $rpid From d34f7a8aa9a25b7e64e0e46e444697c0f702374d Mon Sep 17 00:00:00 2001 From: Eric Dumazet Date: Fri, 13 Feb 2026 14:25:57 +0000 Subject: [PATCH 3800/3902] macvlan: observe an RCU grace period in macvlan_common_newlink() error path [ Upstream commit e3f000f0dee1bfab52e2e61ca6a3835d9e187e35 ] valis reported that a race condition still happens after my prior patch. macvlan_common_newlink() might have made @dev visible before detecting an error, and its caller will directly call free_netdev(dev). We must respect an RCU period, either in macvlan or the core networking stack. After adding a temporary mdelay(1000) in macvlan_forward_source_one() to open the race window, valis repro was: ip link add p1 type veth peer p2 ip link set address 00:00:00:00:00:20 dev p1 ip link set up dev p1 ip link set up dev p2 ip link add mv0 link p2 type macvlan mode source (ip link add invalid% link p2 type macvlan mode source macaddr add 00:00:00:00:00:20 &) ; sleep 0.5 ; ping -c1 -I p1 1.2.3.4 PING 1.2.3.4 (1.2.3.4): 56 data bytes RTNETLINK answers: Invalid argument BUG: KASAN: slab-use-after-free in macvlan_forward_source (drivers/net/macvlan.c:408 drivers/net/macvlan.c:444) Read of size 8 at addr ffff888016bb89c0 by task e/175 CPU: 1 UID: 1000 PID: 175 Comm: e Not tainted 6.19.0-rc8+ #33 NONE Hardware name: QEMU Standard PC (i440FX + PIIX, 1996), BIOS 1.14.0-2 04/01/2014 Call Trace: dump_stack_lvl (lib/dump_stack.c:123) print_report (mm/kasan/report.c:379 mm/kasan/report.c:482) ? macvlan_forward_source (drivers/net/macvlan.c:408 drivers/net/macvlan.c:444) kasan_report (mm/kasan/report.c:597) ? macvlan_forward_source (drivers/net/macvlan.c:408 drivers/net/macvlan.c:444) macvlan_forward_source (drivers/net/macvlan.c:408 drivers/net/macvlan.c:444) ? tasklet_init (kernel/softirq.c:983) macvlan_handle_frame (drivers/net/macvlan.c:501) Allocated by task 169: kasan_save_stack (mm/kasan/common.c:58) kasan_save_track (./arch/x86/include/asm/current.h:25 mm/kasan/common.c:70 mm/kasan/common.c:79) __kasan_kmalloc (mm/kasan/common.c:419) __kvmalloc_node_noprof (./include/linux/kasan.h:263 mm/slub.c:5657 mm/slub.c:7140) alloc_netdev_mqs (net/core/dev.c:12012) rtnl_create_link (net/core/rtnetlink.c:3648) rtnl_newlink (net/core/rtnetlink.c:3830 net/core/rtnetlink.c:3957 net/core/rtnetlink.c:4072) rtnetlink_rcv_msg (net/core/rtnetlink.c:6958) netlink_rcv_skb (net/netlink/af_netlink.c:2550) netlink_unicast (net/netlink/af_netlink.c:1319 net/netlink/af_netlink.c:1344) netlink_sendmsg (net/netlink/af_netlink.c:1894) __sys_sendto (net/socket.c:727 net/socket.c:742 net/socket.c:2206) __x64_sys_sendto (net/socket.c:2209) do_syscall_64 (arch/x86/entry/syscall_64.c:63 arch/x86/entry/syscall_64.c:94) entry_SYSCALL_64_after_hwframe (arch/x86/entry/entry_64.S:131) Freed by task 169: kasan_save_stack (mm/kasan/common.c:58) kasan_save_track (./arch/x86/include/asm/current.h:25 mm/kasan/common.c:70 mm/kasan/common.c:79) kasan_save_free_info (mm/kasan/generic.c:587) __kasan_slab_free (mm/kasan/common.c:287) kfree (mm/slub.c:6674 mm/slub.c:6882) rtnl_newlink (net/core/rtnetlink.c:3845 net/core/rtnetlink.c:3957 net/core/rtnetlink.c:4072) rtnetlink_rcv_msg (net/core/rtnetlink.c:6958) netlink_rcv_skb (net/netlink/af_netlink.c:2550) netlink_unicast (net/netlink/af_netlink.c:1319 net/netlink/af_netlink.c:1344) netlink_sendmsg (net/netlink/af_netlink.c:1894) __sys_sendto (net/socket.c:727 net/socket.c:742 net/socket.c:2206) __x64_sys_sendto (net/socket.c:2209) do_syscall_64 (arch/x86/entry/syscall_64.c:63 arch/x86/entry/syscall_64.c:94) entry_SYSCALL_64_after_hwframe (arch/x86/entry/entry_64.S:131) Fixes: f8db6475a836 ("macvlan: fix error recovery in macvlan_common_newlink()") Signed-off-by: Eric Dumazet Reported-by: valis Link: https://patch.msgid.link/20260213142557.3059043-1-edumazet@google.com Signed-off-by: Jakub Kicinski Signed-off-by: Sasha Levin --- drivers/net/macvlan.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/drivers/net/macvlan.c b/drivers/net/macvlan.c index c509228be84d1b..4433b8e95b6acc 100644 --- a/drivers/net/macvlan.c +++ b/drivers/net/macvlan.c @@ -1572,6 +1572,11 @@ int macvlan_common_newlink(struct net_device *dev, if (create) macvlan_port_destroy(port->dev); } + /* @dev might have been made visible before an error was detected. + * Make sure to observe an RCU grace period before our caller + * (rtnl_newlink()) frees it. + */ + synchronize_net(); return err; } EXPORT_SYMBOL_GPL(macvlan_common_newlink); From d7eaa006c0444a5d4671be7efe6dbb33ef8b515e Mon Sep 17 00:00:00 2001 From: Dimitri Daskalakis Date: Sat, 14 Feb 2026 09:19:49 -0800 Subject: [PATCH 3801/3902] eth: fbnic: Add validation for MTU changes [ Upstream commit ccd8e87748ad083047d6c8544c5809b7f96cc8df ] Increasing the MTU beyond the HDS threshold causes the hardware to fragment packets across multiple buffers. If a single-buffer XDP program is attached, the driver will drop all multi-frag frames. While we can't prevent a remote sender from sending non-TCP packets larger than the MTU, this will prevent users from inadvertently breaking new TCP streams. Traditionally, drivers supported XDP with MTU less than 4Kb (packet per page). Fbnic currently prevents attaching XDP when MTU is too high. But it does not prevent increasing MTU after XDP is attached. Fixes: 1b0a3950dbd4 ("eth: fbnic: Add XDP pass, drop, abort support") Signed-off-by: Jakub Kicinski Signed-off-by: Dimitri Daskalakis Reviewed-by: Simon Horman Signed-off-by: David S. Miller Signed-off-by: Sasha Levin --- drivers/net/ethernet/meta/fbnic/fbnic_netdev.c | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/drivers/net/ethernet/meta/fbnic/fbnic_netdev.c b/drivers/net/ethernet/meta/fbnic/fbnic_netdev.c index e95be0e7bd9e0d..5cbf3ad175a543 100644 --- a/drivers/net/ethernet/meta/fbnic/fbnic_netdev.c +++ b/drivers/net/ethernet/meta/fbnic/fbnic_netdev.c @@ -262,6 +262,23 @@ static int fbnic_set_mac(struct net_device *netdev, void *p) return 0; } +static int fbnic_change_mtu(struct net_device *dev, int new_mtu) +{ + struct fbnic_net *fbn = netdev_priv(dev); + + if (fbnic_check_split_frames(fbn->xdp_prog, new_mtu, fbn->hds_thresh)) { + dev_err(&dev->dev, + "MTU %d is larger than HDS threshold %d in XDP mode\n", + new_mtu, fbn->hds_thresh); + + return -EINVAL; + } + + WRITE_ONCE(dev->mtu, new_mtu); + + return 0; +} + void fbnic_clear_rx_mode(struct fbnic_dev *fbd) { struct net_device *netdev = fbd->netdev; @@ -533,6 +550,7 @@ static const struct net_device_ops fbnic_netdev_ops = { .ndo_start_xmit = fbnic_xmit_frame, .ndo_features_check = fbnic_features_check, .ndo_set_mac_address = fbnic_set_mac, + .ndo_change_mtu = fbnic_change_mtu, .ndo_set_rx_mode = fbnic_set_rx_mode, .ndo_get_stats64 = fbnic_get_stats64, .ndo_bpf = fbnic_bpf, From 2c7384dbfd6a4cfc2f31c82301aa75f31d7e9c56 Mon Sep 17 00:00:00 2001 From: Eric Dumazet Date: Mon, 16 Feb 2026 14:28:28 +0000 Subject: [PATCH 3802/3902] icmp: prevent possible overflow in icmp_global_allow() [ Upstream commit 034bbd806298e9ba4197dd1587b0348ee30996ea ] Following expression can overflow if sysctl_icmp_msgs_per_sec is big enough. sysctl_icmp_msgs_per_sec * delta / HZ; Fixes: 4cdf507d5452 ("icmp: add a global rate limitation") Signed-off-by: Eric Dumazet Reviewed-by: Kuniyuki Iwashima Link: https://patch.msgid.link/20260216142832.3834174-2-edumazet@google.com Signed-off-by: Jakub Kicinski Signed-off-by: Sasha Levin --- net/ipv4/icmp.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/net/ipv4/icmp.c b/net/ipv4/icmp.c index 9323ee0a6ac483..3e19a5d465b838 100644 --- a/net/ipv4/icmp.c +++ b/net/ipv4/icmp.c @@ -248,7 +248,8 @@ bool icmp_global_allow(struct net *net) if (delta < HZ / 50) return false; - incr = READ_ONCE(net->ipv4.sysctl_icmp_msgs_per_sec) * delta / HZ; + incr = READ_ONCE(net->ipv4.sysctl_icmp_msgs_per_sec); + incr = div_u64((u64)incr * delta, HZ); if (!incr) return false; From 66036be40f7f72a3dbed1c70614e5b8d17a40ed2 Mon Sep 17 00:00:00 2001 From: Eric Dumazet Date: Mon, 16 Feb 2026 14:28:29 +0000 Subject: [PATCH 3803/3902] inet: move icmp_global_{credit,stamp} to a separate cache line [ Upstream commit 87b08913a9ae82082e276d237ece08fc8ee24380 ] icmp_global_credit was meant to be changed ~1000 times per second, but if an admin sets net.ipv4.icmp_msgs_per_sec to a very high value, icmp_global_credit changes can inflict false sharing to surrounding fields that are read mostly. Move icmp_global_credit and icmp_global_stamp to a separate cacheline aligned group. Fixes: b056b4cd9178 ("icmp: move icmp_global.credit and icmp_global.stamp to per netns storage") Signed-off-by: Eric Dumazet Reviewed-by: Kuniyuki Iwashima Link: https://patch.msgid.link/20260216142832.3834174-3-edumazet@google.com Signed-off-by: Jakub Kicinski Signed-off-by: Sasha Levin --- include/net/netns/ipv4.h | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/include/net/netns/ipv4.h b/include/net/netns/ipv4.h index 34eb3aecb3f21c..62166da0455481 100644 --- a/include/net/netns/ipv4.h +++ b/include/net/netns/ipv4.h @@ -87,6 +87,12 @@ struct netns_ipv4 { int sysctl_tcp_rmem[3]; __cacheline_group_end(netns_ipv4_read_rx); + /* ICMP rate limiter hot cache line. */ + __cacheline_group_begin_aligned(icmp); + atomic_t icmp_global_credit; + u32 icmp_global_stamp; + __cacheline_group_end_aligned(icmp); + struct inet_timewait_death_row tcp_death_row; struct udp_table *udp_table; @@ -139,8 +145,7 @@ struct netns_ipv4 { int sysctl_icmp_ratemask; int sysctl_icmp_msgs_per_sec; int sysctl_icmp_msgs_burst; - atomic_t icmp_global_credit; - u32 icmp_global_stamp; + u32 ip_rt_min_pmtu; int ip_rt_mtu_expires; int ip_rt_min_advmss; From 9be9100774b51b3f88424a4a708181c817e3b46b Mon Sep 17 00:00:00 2001 From: Eric Dumazet Date: Mon, 16 Feb 2026 14:28:30 +0000 Subject: [PATCH 3804/3902] ipv6: icmp: remove obsolete code in icmpv6_xrlim_allow() [ Upstream commit 0201eedb69b24a6be9b7c1716287a89c4dde2320 ] Following part was needed before the blamed commit, because inet_getpeer_v6() second argument was the prefix. /* Give more bandwidth to wider prefixes. */ if (rt->rt6i_dst.plen < 128) tmo >>= ((128 - rt->rt6i_dst.plen)>>5); Now inet_getpeer_v6() retrieves hosts, we need to remove @tmo adjustement or wider prefixes likes /24 allow 8x more ICMP to be sent for a given ratelimit. As we had this issue for a while, this patch changes net.ipv6.icmp.ratelimit default value from 1000ms to 100ms to avoid potential regressions. Also add a READ_ONCE() when reading net->ipv6.sysctl.icmpv6_time. Fixes: fd0273d7939f ("ipv6: Remove external dependency on rt6i_dst and rt6i_src") Signed-off-by: Eric Dumazet Reviewed-by: Kuniyuki Iwashima Cc: Martin KaFai Lau Link: https://patch.msgid.link/20260216142832.3834174-4-edumazet@google.com Signed-off-by: Jakub Kicinski Signed-off-by: Sasha Levin --- Documentation/networking/ip-sysctl.rst | 7 ++++--- net/ipv6/af_inet6.c | 2 +- net/ipv6/icmp.c | 7 +------ 3 files changed, 6 insertions(+), 10 deletions(-) diff --git a/Documentation/networking/ip-sysctl.rst b/Documentation/networking/ip-sysctl.rst index a06cb99d66dcdc..7a637e87005fee 100644 --- a/Documentation/networking/ip-sysctl.rst +++ b/Documentation/networking/ip-sysctl.rst @@ -3195,12 +3195,13 @@ enhanced_dad - BOOLEAN =========== ratelimit - INTEGER - Limit the maximal rates for sending ICMPv6 messages. + Limit the maximal rates for sending ICMPv6 messages to a particular + peer. 0 to disable any limiting, - otherwise the minimal space between responses in milliseconds. + otherwise the space between responses in milliseconds. - Default: 1000 + Default: 100 ratemask - list of comma separated ranges For ICMPv6 message types matching the ranges in the ratemask, limit diff --git a/net/ipv6/af_inet6.c b/net/ipv6/af_inet6.c index 1b0314644e0ccc..0e8f48835869cd 100644 --- a/net/ipv6/af_inet6.c +++ b/net/ipv6/af_inet6.c @@ -955,7 +955,7 @@ static int __net_init inet6_net_init(struct net *net) int err = 0; net->ipv6.sysctl.bindv6only = 0; - net->ipv6.sysctl.icmpv6_time = 1*HZ; + net->ipv6.sysctl.icmpv6_time = HZ / 10; net->ipv6.sysctl.icmpv6_echo_ignore_all = 0; net->ipv6.sysctl.icmpv6_echo_ignore_multicast = 0; net->ipv6.sysctl.icmpv6_echo_ignore_anycast = 0; diff --git a/net/ipv6/icmp.c b/net/ipv6/icmp.c index 306eec18e82c17..35b32dcf581fff 100644 --- a/net/ipv6/icmp.c +++ b/net/ipv6/icmp.c @@ -217,14 +217,9 @@ static bool icmpv6_xrlim_allow(struct sock *sk, u8 type, } else if (dev && (dev->flags & IFF_LOOPBACK)) { res = true; } else { - struct rt6_info *rt = dst_rt6_info(dst); - int tmo = net->ipv6.sysctl.icmpv6_time; + int tmo = READ_ONCE(net->ipv6.sysctl.icmpv6_time); struct inet_peer *peer; - /* Give more bandwidth to wider prefixes. */ - if (rt->rt6i_dst.plen < 128) - tmo >>= ((128 - rt->rt6i_dst.plen)>>5); - peer = inet_getpeer_v6(net->ipv6.peers, &fl6->daddr); res = inet_peer_xrlim_allow(peer, tmo); } From 72726d22786bb5b4860e2a5acf0d5824b5744e8d Mon Sep 17 00:00:00 2001 From: Hariprasad Kelam Date: Mon, 16 Feb 2026 14:33:38 +0530 Subject: [PATCH 3805/3902] octeontx2-af: Fix default entries mcam entry action [ Upstream commit 45be47bf5d7db0f762a93e9c0ede6cb3c91edf3b ] As per design, AF should update the default MCAM action only when mcam_index is -1. A bug in the previous patch caused default entries to be changed even when the request was not for them. Fixes: 570ba37898ec ("octeontx2-af: Update RSS algorithm index") Signed-off-by: Hariprasad Kelam Reviewed-by: Simon Horman Link: https://patch.msgid.link/20260216090338.1318976-1-hkelam@marvell.com Signed-off-by: Jakub Kicinski Signed-off-by: Sasha Levin --- .../ethernet/marvell/octeontx2/af/rvu_npc.c | 41 ++++++++++--------- 1 file changed, 22 insertions(+), 19 deletions(-) diff --git a/drivers/net/ethernet/marvell/octeontx2/af/rvu_npc.c b/drivers/net/ethernet/marvell/octeontx2/af/rvu_npc.c index c7c70429eb6c10..8658cb2143dfc7 100644 --- a/drivers/net/ethernet/marvell/octeontx2/af/rvu_npc.c +++ b/drivers/net/ethernet/marvell/octeontx2/af/rvu_npc.c @@ -1042,32 +1042,35 @@ void rvu_npc_update_flowkey_alg_idx(struct rvu *rvu, u16 pcifunc, int nixlf, rvu_write64(rvu, blkaddr, NPC_AF_MCAMEX_BANKX_ACTION(index, bank), *(u64 *)&action); - /* update the VF flow rule action with the VF default entry action */ - if (mcam_index < 0) - npc_update_vf_flow_entry(rvu, mcam, blkaddr, pcifunc, - *(u64 *)&action); - /* update the action change in default rule */ pfvf = rvu_get_pfvf(rvu, pcifunc); if (pfvf->def_ucast_rule) pfvf->def_ucast_rule->rx_action = action; - index = npc_get_nixlf_mcam_index(mcam, pcifunc, - nixlf, NIXLF_PROMISC_ENTRY); + if (mcam_index < 0) { + /* update the VF flow rule action with the VF default + * entry action + */ + npc_update_vf_flow_entry(rvu, mcam, blkaddr, pcifunc, + *(u64 *)&action); - /* If PF's promiscuous entry is enabled, - * Set RSS action for that entry as well - */ - npc_update_rx_action_with_alg_idx(rvu, action, pfvf, index, blkaddr, - alg_idx); + index = npc_get_nixlf_mcam_index(mcam, pcifunc, + nixlf, NIXLF_PROMISC_ENTRY); - index = npc_get_nixlf_mcam_index(mcam, pcifunc, - nixlf, NIXLF_ALLMULTI_ENTRY); - /* If PF's allmulti entry is enabled, - * Set RSS action for that entry as well - */ - npc_update_rx_action_with_alg_idx(rvu, action, pfvf, index, blkaddr, - alg_idx); + /* If PF's promiscuous entry is enabled, + * Set RSS action for that entry as well + */ + npc_update_rx_action_with_alg_idx(rvu, action, pfvf, index, + blkaddr, alg_idx); + + index = npc_get_nixlf_mcam_index(mcam, pcifunc, + nixlf, NIXLF_ALLMULTI_ENTRY); + /* If PF's allmulti entry is enabled, + * Set RSS action for that entry as well + */ + npc_update_rx_action_with_alg_idx(rvu, action, pfvf, index, + blkaddr, alg_idx); + } } void npc_enadis_default_mce_entry(struct rvu *rvu, u16 pcifunc, From f5a1f13c9e0fdd7e63a79df585dbb3fa2f306596 Mon Sep 17 00:00:00 2001 From: Dimitri Daskalakis Date: Tue, 17 Feb 2026 19:06:20 -0800 Subject: [PATCH 3806/3902] eth: fbnic: Advertise supported XDP features. [ Upstream commit e977fcb3a318b53b47f23b44ac237fceb1b731fe ] Drivers are supposed to advertise the XDP features they support. This was missed while adding XDP support. Before: $ ynl --family netdev --dump dev-get ... {'ifindex': 3, 'xdp-features': set(), 'xdp-rx-metadata-features': set(), 'xsk-features': set()}, ... After: $ ynl --family netdev --dump dev-get ... {'ifindex': 3, 'xdp-features': {'basic', 'rx-sg'}, 'xdp-rx-metadata-features': set(), 'xsk-features': set()}, ... Fixes: 168deb7b31b2 ("eth: fbnic: Add support for XDP_TX action") Signed-off-by: Jakub Kicinski Signed-off-by: Dimitri Daskalakis Reviewed-by: Simon Horman Link: https://patch.msgid.link/20260218030620.3329608-1-dimitri.daskalakis1@gmail.com Signed-off-by: Paolo Abeni Signed-off-by: Sasha Levin --- drivers/net/ethernet/meta/fbnic/fbnic_netdev.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/drivers/net/ethernet/meta/fbnic/fbnic_netdev.c b/drivers/net/ethernet/meta/fbnic/fbnic_netdev.c index 5cbf3ad175a543..cbedaa037cfa88 100644 --- a/drivers/net/ethernet/meta/fbnic/fbnic_netdev.c +++ b/drivers/net/ethernet/meta/fbnic/fbnic_netdev.c @@ -808,6 +808,8 @@ struct net_device *fbnic_netdev_alloc(struct fbnic_dev *fbd) netdev->hw_enc_features |= netdev->features; netdev->features |= NETIF_F_NTUPLE; + netdev->xdp_features = NETDEV_XDP_ACT_BASIC | NETDEV_XDP_ACT_RX_SG; + netdev->min_mtu = IPV6_MIN_MTU; netdev->max_mtu = FBNIC_MAX_JUMBO_FRAME_SIZE - ETH_HLEN; From 02baf80d1e333fc8aa68d6ce6cecc3ec216ed5c0 Mon Sep 17 00:00:00 2001 From: Vikas Gupta Date: Wed, 18 Feb 2026 10:57:55 +0530 Subject: [PATCH 3807/3902] bnge: fix reserving resources from FW [ Upstream commit 604530085b2ef484843c723a105b6fd3218b4710 ] HWRM_FUNC_CFG is used to reserve resources, whereas HWRM_FUNC_QCFG is intended for querying resource information from the firmware. Since __bnge_hwrm_reserve_pf_rings() reserves resources for a specific PF, the command type should be HWRM_FUNC_CFG. Fixes: 627c67f038d2 ("bng_en: Add resource management support") Signed-off-by: Vikas Gupta Reviewed-by: Bhargava Chenna Marreddy Reviewed-by: Simon Horman Link: https://patch.msgid.link/20260218052755.4097468-1-vikas.gupta@broadcom.com Signed-off-by: Jakub Kicinski Signed-off-by: Sasha Levin --- drivers/net/ethernet/broadcom/bnge/bnge_hwrm_lib.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/net/ethernet/broadcom/bnge/bnge_hwrm_lib.c b/drivers/net/ethernet/broadcom/bnge/bnge_hwrm_lib.c index 198f49b40dbf00..2994f10446a63c 100644 --- a/drivers/net/ethernet/broadcom/bnge/bnge_hwrm_lib.c +++ b/drivers/net/ethernet/broadcom/bnge/bnge_hwrm_lib.c @@ -442,7 +442,7 @@ __bnge_hwrm_reserve_pf_rings(struct bnge_dev *bd, struct bnge_hw_rings *hwr) struct hwrm_func_cfg_input *req; u32 enables = 0; - if (bnge_hwrm_req_init(bd, req, HWRM_FUNC_QCFG)) + if (bnge_hwrm_req_init(bd, req, HWRM_FUNC_CFG)) return NULL; req->fid = cpu_to_le16(0xffff); From fef13c403be3fb685cb06419e6b3623106aab5ba Mon Sep 17 00:00:00 2001 From: Hangbin Liu Date: Wed, 18 Feb 2026 06:09:19 +0000 Subject: [PATCH 3808/3902] bonding: alb: fix UAF in rlb_arp_recv during bond up/down [ Upstream commit e6834a4c474697df23ab9948fd3577b26bf48656 ] The ALB RX path may access rx_hashtbl concurrently with bond teardown. During rapid bond up/down cycles, rlb_deinitialize() frees rx_hashtbl while RX handlers are still running, leading to a null pointer dereference detected by KASAN. However, the root cause is that rlb_arp_recv() can still be accessed after setting recv_probe to NULL, which is actually a use-after-free (UAF) issue. That is the reason for using the referenced commit in the Fixes tag. [ 214.174138] Oops: general protection fault, probably for non-canonical address 0xdffffc000000001d: 0000 [#1] SMP KASAN PTI [ 214.186478] KASAN: null-ptr-deref in range [0x00000000000000e8-0x00000000000000ef] [ 214.194933] CPU: 30 UID: 0 PID: 2375 Comm: ping Kdump: loaded Not tainted 6.19.0-rc8+ #2 PREEMPT(voluntary) [ 214.205907] Hardware name: Dell Inc. PowerEdge R730/0WCJNT, BIOS 2.14.0 01/14/2022 [ 214.214357] RIP: 0010:rlb_arp_recv+0x505/0xab0 [bonding] [ 214.220320] Code: 0f 85 2b 05 00 00 48 b8 00 00 00 00 00 fc ff df 40 0f b6 ed 48 c1 e5 06 49 03 ad 78 01 00 00 48 8d 7d 28 48 89 fa 48 c1 ea 03 <0f> b6 04 02 84 c0 74 06 0f 8e 12 05 00 00 80 7d 28 00 0f 84 8c 00 [ 214.241280] RSP: 0018:ffffc900073d8870 EFLAGS: 00010206 [ 214.247116] RAX: dffffc0000000000 RBX: ffff888168556822 RCX: ffff88816855681e [ 214.255082] RDX: 000000000000001d RSI: dffffc0000000000 RDI: 00000000000000e8 [ 214.263048] RBP: 00000000000000c0 R08: 0000000000000002 R09: ffffed11192021c8 [ 214.271013] R10: ffff8888c9010e43 R11: 0000000000000001 R12: 1ffff92000e7b119 [ 214.278978] R13: ffff8888c9010e00 R14: ffff888168556822 R15: ffff888168556810 [ 214.286943] FS: 00007f85d2d9cb80(0000) GS:ffff88886ccb3000(0000) knlGS:0000000000000000 [ 214.295966] CS: 0010 DS: 0000 ES: 0000 CR0: 0000000080050033 [ 214.302380] CR2: 00007f0d047b5e34 CR3: 00000008a1c2e002 CR4: 00000000001726f0 [ 214.310347] Call Trace: [ 214.313070] [ 214.315318] ? __pfx_rlb_arp_recv+0x10/0x10 [bonding] [ 214.320975] bond_handle_frame+0x166/0xb60 [bonding] [ 214.326537] ? __pfx_bond_handle_frame+0x10/0x10 [bonding] [ 214.332680] __netif_receive_skb_core.constprop.0+0x576/0x2710 [ 214.339199] ? __pfx_arp_process+0x10/0x10 [ 214.343775] ? sched_balance_find_src_group+0x98/0x630 [ 214.349513] ? __pfx___netif_receive_skb_core.constprop.0+0x10/0x10 [ 214.356513] ? arp_rcv+0x307/0x690 [ 214.360311] ? __pfx_arp_rcv+0x10/0x10 [ 214.364499] ? __lock_acquire+0x58c/0xbd0 [ 214.368975] __netif_receive_skb_one_core+0xae/0x1b0 [ 214.374518] ? __pfx___netif_receive_skb_one_core+0x10/0x10 [ 214.380743] ? lock_acquire+0x10b/0x140 [ 214.385026] process_backlog+0x3f1/0x13a0 [ 214.389502] ? process_backlog+0x3aa/0x13a0 [ 214.394174] __napi_poll.constprop.0+0x9f/0x370 [ 214.399233] net_rx_action+0x8c1/0xe60 [ 214.403423] ? __pfx_net_rx_action+0x10/0x10 [ 214.408193] ? lock_acquire.part.0+0xbd/0x260 [ 214.413058] ? sched_clock_cpu+0x6c/0x540 [ 214.417540] ? mark_held_locks+0x40/0x70 [ 214.421920] handle_softirqs+0x1fd/0x860 [ 214.426302] ? __pfx_handle_softirqs+0x10/0x10 [ 214.431264] ? __neigh_event_send+0x2d6/0xf50 [ 214.436131] do_softirq+0xb1/0xf0 [ 214.439830] The issue is reproducible by repeatedly running ip link set bond0 up/down while receiving ARP messages, where rlb_arp_recv() can race with rlb_deinitialize() and dereference a freed rx_hashtbl entry. Fix this by setting recv_probe to NULL and then calling synchronize_net() to wait for any concurrent RX processing to finish. This ensures that no RX handler can access rx_hashtbl after it is freed in bond_alb_deinitialize(). Reported-by: Liang Li Fixes: 3aba891dde38 ("bonding: move processing of recv handlers into handle_frame()") Reviewed-by: Nikolay Aleksandrov Acked-by: Jay Vosburgh Signed-off-by: Hangbin Liu Link: https://patch.msgid.link/20260218060919.101574-1-liuhangbin@gmail.com Signed-off-by: Jakub Kicinski Signed-off-by: Sasha Levin --- drivers/net/bonding/bond_main.c | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/drivers/net/bonding/bond_main.c b/drivers/net/bonding/bond_main.c index 166dff47a029ff..dba8f68690947c 100644 --- a/drivers/net/bonding/bond_main.c +++ b/drivers/net/bonding/bond_main.c @@ -4405,9 +4405,13 @@ static int bond_close(struct net_device *bond_dev) bond_work_cancel_all(bond); bond->send_peer_notif = 0; + WRITE_ONCE(bond->recv_probe, NULL); + + /* Wait for any in-flight RX handlers */ + synchronize_net(); + if (bond_is_lb(bond)) bond_alb_deinitialize(bond); - bond->recv_probe = NULL; if (BOND_MODE(bond) == BOND_MODE_8023AD && bond->params.broadcast_neighbor) From b13e896fc47e0ab1381269b54e68975e92166b9a Mon Sep 17 00:00:00 2001 From: Shay Drory Date: Wed, 18 Feb 2026 09:28:59 +0200 Subject: [PATCH 3809/3902] net/mlx5: Fix multiport device check over light SFs [ Upstream commit 47bf2e813817159f4d195be83a9b5a640ee6baec ] Driver is using num_vhca_ports capability to distinguish between multiport master device and multiport slave device. num_vhca_ports is a capability the driver sets according to the MAX num_vhca_ports capability reported by FW. On the other hand, light SFs doesn't set the above capbility. This leads to wrong results whenever light SFs is checking whether he is a multiport master or slave. Therefore, use the MAX capability to distinguish between master and slave devices. Fixes: e71383fb9cd1 ("net/mlx5: Light probe local SFs") Signed-off-by: Shay Drory Reviewed-by: Moshe Shemesh Signed-off-by: Tariq Toukan Reviewed-by: Jacob Keller Link: https://patch.msgid.link/20260218072904.1764634-2-tariqt@nvidia.com Signed-off-by: Jakub Kicinski Signed-off-by: Sasha Levin --- include/linux/mlx5/driver.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/include/linux/mlx5/driver.h b/include/linux/mlx5/driver.h index 5405ca1038f9ea..85c2b3d358ec1e 100644 --- a/include/linux/mlx5/driver.h +++ b/include/linux/mlx5/driver.h @@ -1274,12 +1274,12 @@ static inline bool mlx5_rl_is_supported(struct mlx5_core_dev *dev) static inline int mlx5_core_is_mp_slave(struct mlx5_core_dev *dev) { return MLX5_CAP_GEN(dev, affiliate_nic_vport_criteria) && - MLX5_CAP_GEN(dev, num_vhca_ports) <= 1; + MLX5_CAP_GEN_MAX(dev, num_vhca_ports) <= 1; } static inline int mlx5_core_is_mp_master(struct mlx5_core_dev *dev) { - return MLX5_CAP_GEN(dev, num_vhca_ports) > 1; + return MLX5_CAP_GEN_MAX(dev, num_vhca_ports) > 1; } static inline int mlx5_core_mp_enabled(struct mlx5_core_dev *dev) From 54522509cb9aade1de14da2423c95d197200d46b Mon Sep 17 00:00:00 2001 From: Gal Pressman Date: Wed, 18 Feb 2026 09:29:01 +0200 Subject: [PATCH 3810/3902] net/mlx5: Fix misidentification of write combining CQE during poll loop [ Upstream commit d451994ebc7d4392610bd4b2ab339b255deb4143 ] The write combining completion poll loop uses usleep_range() which can sleep much longer than requested due to scheduler latency. Under load, we witnessed a 20ms+ delay until the process was rescheduled, causing the jiffies based timeout to expire while the thread is sleeping. The original do-while loop structure (poll, sleep, check timeout) would exit without a final poll when waking after timeout, missing a CQE that arrived during sleep. Instead of the open-coded while loop, use the kernel's poll_timeout_us() which always performs an additional check after the sleep expiration, and is less error-prone. Note: poll_timeout_us() doesn't accept a sleep range, by passing 10 sleep_us the sleep range effectively changes from 2-10 to 3-10 usecs. Fixes: d98995b4bf98 ("net/mlx5: Reimplement write combining test") Signed-off-by: Gal Pressman Reviewed-by: Jianbo Liu Signed-off-by: Tariq Toukan Reviewed-by: Jacob Keller Link: https://patch.msgid.link/20260218072904.1764634-4-tariqt@nvidia.com Signed-off-by: Jakub Kicinski Signed-off-by: Sasha Levin --- drivers/net/ethernet/mellanox/mlx5/core/wc.c | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) diff --git a/drivers/net/ethernet/mellanox/mlx5/core/wc.c b/drivers/net/ethernet/mellanox/mlx5/core/wc.c index 05e5fd777d4f41..8701b7b6a2d515 100644 --- a/drivers/net/ethernet/mellanox/mlx5/core/wc.c +++ b/drivers/net/ethernet/mellanox/mlx5/core/wc.c @@ -2,6 +2,7 @@ // Copyright (c) 2024, NVIDIA CORPORATION & AFFILIATES. All rights reserved. #include +#include #include #include "lib/clock.h" #include "mlx5_core.h" @@ -14,7 +15,7 @@ #define TEST_WC_NUM_WQES 255 #define TEST_WC_LOG_CQ_SZ (order_base_2(TEST_WC_NUM_WQES)) #define TEST_WC_SQ_LOG_WQ_SZ TEST_WC_LOG_CQ_SZ -#define TEST_WC_POLLING_MAX_TIME_JIFFIES msecs_to_jiffies(100) +#define TEST_WC_POLLING_MAX_TIME_USEC (100 * USEC_PER_MSEC) struct mlx5_wc_cq { /* data path - accessed per cqe */ @@ -358,7 +359,6 @@ static int mlx5_wc_poll_cq(struct mlx5_wc_sq *sq) static void mlx5_core_test_wc(struct mlx5_core_dev *mdev) { unsigned int offset = 0; - unsigned long expires; struct mlx5_wc_sq *sq; int i, err; @@ -388,13 +388,9 @@ static void mlx5_core_test_wc(struct mlx5_core_dev *mdev) mlx5_wc_post_nop(sq, &offset, true); - expires = jiffies + TEST_WC_POLLING_MAX_TIME_JIFFIES; - do { - err = mlx5_wc_poll_cq(sq); - if (err) - usleep_range(2, 10); - } while (mdev->wc_state == MLX5_WC_STATE_UNINITIALIZED && - time_is_after_jiffies(expires)); + poll_timeout_us(mlx5_wc_poll_cq(sq), + mdev->wc_state != MLX5_WC_STATE_UNINITIALIZED, 10, + TEST_WC_POLLING_MAX_TIME_USEC, false); mlx5_wc_destroy_sq(sq); From 4329514c61abefe4961541b128c549b017bab5ad Mon Sep 17 00:00:00 2001 From: Cosmin Ratiu Date: Wed, 18 Feb 2026 09:29:03 +0200 Subject: [PATCH 3811/3902] net/mlx5e: Fix deadlocks between devlink and netdev instance locks [ Upstream commit 83ac0304a2d77519dae1e54c9713cbe1aedf19c9 ] In the mentioned "Fixes" commit, various work tasks triggering devlink health reporter recovery were switched to use netdev_trylock to protect against concurrent tear down of the channels being recovered. But this had the side effect of introducing potential deadlocks because of incorrect lock ordering. The correct lock order is described by the init flow: probe_one -> mlx5_init_one (acquires devlink lock) -> mlx5_init_one_devl_locked -> mlx5_register_device -> mlx5_rescan_drivers_locked -...-> mlx5e_probe -> _mlx5e_probe -> register_netdev (acquires rtnl lock) -> register_netdevice (acquires netdev lock) => devlink lock -> rtnl lock -> netdev lock. But in the current recovery flow, the order is wrong: mlx5e_tx_err_cqe_work (acquires netdev lock) -> mlx5e_reporter_tx_err_cqe -> mlx5e_health_report -> devlink_health_report (acquires devlink lock => boom!) -> devlink_health_reporter_recover -> mlx5e_tx_reporter_recover -> mlx5e_tx_reporter_recover_from_ctx -> mlx5e_tx_reporter_err_cqe_recover The same pattern exists in: mlx5e_reporter_rx_timeout mlx5e_reporter_tx_ptpsq_unhealthy mlx5e_reporter_tx_timeout Fix these by moving the netdev_trylock calls from the work handlers lower in the call stack, in the respective recovery functions, where they are actually necessary. Fixes: 8f7b00307bf1 ("net/mlx5e: Convert mlx5 netdevs to instance locking") Signed-off-by: Cosmin Ratiu Reviewed-by: Dragos Tatulea Signed-off-by: Tariq Toukan Reviewed-by: Jacob Keller Link: https://patch.msgid.link/20260218072904.1764634-6-tariqt@nvidia.com Signed-off-by: Jakub Kicinski Signed-off-by: Sasha Levin --- .../net/ethernet/mellanox/mlx5/core/en/ptp.c | 14 ----- .../mellanox/mlx5/core/en/reporter_rx.c | 13 +++++ .../mellanox/mlx5/core/en/reporter_tx.c | 52 +++++++++++++++++-- .../net/ethernet/mellanox/mlx5/core/en_main.c | 40 -------------- 4 files changed, 61 insertions(+), 58 deletions(-) diff --git a/drivers/net/ethernet/mellanox/mlx5/core/en/ptp.c b/drivers/net/ethernet/mellanox/mlx5/core/en/ptp.c index c93ee969ea6477..ec715b158a3421 100644 --- a/drivers/net/ethernet/mellanox/mlx5/core/en/ptp.c +++ b/drivers/net/ethernet/mellanox/mlx5/core/en/ptp.c @@ -448,22 +448,8 @@ static void mlx5e_ptpsq_unhealthy_work(struct work_struct *work) { struct mlx5e_ptpsq *ptpsq = container_of(work, struct mlx5e_ptpsq, report_unhealthy_work); - struct mlx5e_txqsq *sq = &ptpsq->txqsq; - - /* Recovering the PTP SQ means re-enabling NAPI, which requires the - * netdev instance lock. However, SQ closing has to wait for this work - * task to finish while also holding the same lock. So either get the - * lock or find that the SQ is no longer enabled and thus this work is - * not relevant anymore. - */ - while (!netdev_trylock(sq->netdev)) { - if (!test_bit(MLX5E_SQ_STATE_ENABLED, &sq->state)) - return; - msleep(20); - } mlx5e_reporter_tx_ptpsq_unhealthy(ptpsq); - netdev_unlock(sq->netdev); } static int mlx5e_ptp_open_txqsq(struct mlx5e_ptp *c, u32 tisn, diff --git a/drivers/net/ethernet/mellanox/mlx5/core/en/reporter_rx.c b/drivers/net/ethernet/mellanox/mlx5/core/en/reporter_rx.c index b1415992ffa244..a09a7c05820d68 100644 --- a/drivers/net/ethernet/mellanox/mlx5/core/en/reporter_rx.c +++ b/drivers/net/ethernet/mellanox/mlx5/core/en/reporter_rx.c @@ -1,6 +1,8 @@ // SPDX-License-Identifier: GPL-2.0 // Copyright (c) 2019 Mellanox Technologies. +#include + #include "health.h" #include "params.h" #include "txrx.h" @@ -177,6 +179,16 @@ static int mlx5e_rx_reporter_timeout_recover(void *ctx) rq = ctx; priv = rq->priv; + /* Acquire netdev instance lock to synchronize with channel close and + * reopen flows. Either successfully obtain the lock, or detect that + * channels are closing for another reason, making this work no longer + * necessary. + */ + while (!netdev_trylock(rq->netdev)) { + if (!test_bit(MLX5E_STATE_CHANNELS_ACTIVE, &rq->priv->state)) + return 0; + msleep(20); + } mutex_lock(&priv->state_lock); eq = rq->cq.mcq.eq; @@ -186,6 +198,7 @@ static int mlx5e_rx_reporter_timeout_recover(void *ctx) clear_bit(MLX5E_SQ_STATE_ENABLED, &rq->icosq->state); mutex_unlock(&priv->state_lock); + netdev_unlock(rq->netdev); return err; } diff --git a/drivers/net/ethernet/mellanox/mlx5/core/en/reporter_tx.c b/drivers/net/ethernet/mellanox/mlx5/core/en/reporter_tx.c index 9e2cf191ed3086..9f6454102cf799 100644 --- a/drivers/net/ethernet/mellanox/mlx5/core/en/reporter_tx.c +++ b/drivers/net/ethernet/mellanox/mlx5/core/en/reporter_tx.c @@ -1,6 +1,8 @@ /* SPDX-License-Identifier: GPL-2.0 */ /* Copyright (c) 2019 Mellanox Technologies. */ +#include + #include "health.h" #include "en/ptp.h" #include "en/devlink.h" @@ -78,6 +80,18 @@ static int mlx5e_tx_reporter_err_cqe_recover(void *ctx) if (!test_bit(MLX5E_SQ_STATE_RECOVERING, &sq->state)) return 0; + /* Recovering queues means re-enabling NAPI, which requires the netdev + * instance lock. However, SQ closing flows have to wait for work tasks + * to finish while also holding the netdev instance lock. So either get + * the lock or find that the SQ is no longer enabled and thus this work + * is not relevant anymore. + */ + while (!netdev_trylock(dev)) { + if (!test_bit(MLX5E_SQ_STATE_ENABLED, &sq->state)) + return 0; + msleep(20); + } + err = mlx5_core_query_sq_state(mdev, sq->sqn, &state); if (err) { netdev_err(dev, "Failed to query SQ 0x%x state. err = %d\n", @@ -113,9 +127,11 @@ static int mlx5e_tx_reporter_err_cqe_recover(void *ctx) else mlx5e_trigger_napi_sched(sq->cq.napi); + netdev_unlock(dev); return 0; out: clear_bit(MLX5E_SQ_STATE_RECOVERING, &sq->state); + netdev_unlock(dev); return err; } @@ -136,10 +152,24 @@ static int mlx5e_tx_reporter_timeout_recover(void *ctx) sq = to_ctx->sq; eq = sq->cq.mcq.eq; priv = sq->priv; + + /* Recovering the TX queues implies re-enabling NAPI, which requires + * the netdev instance lock. + * However, channel closing flows have to wait for this work to finish + * while holding the same lock. So either get the lock or find that + * channels are being closed for other reason and this work is not + * relevant anymore. + */ + while (!netdev_trylock(sq->netdev)) { + if (!test_bit(MLX5E_STATE_CHANNELS_ACTIVE, &priv->state)) + return 0; + msleep(20); + } + err = mlx5e_health_channel_eq_recover(sq->netdev, eq, sq->cq.ch_stats); if (!err) { to_ctx->status = 0; /* this sq recovered */ - return err; + goto out; } mutex_lock(&priv->state_lock); @@ -147,7 +177,7 @@ static int mlx5e_tx_reporter_timeout_recover(void *ctx) mutex_unlock(&priv->state_lock); if (!err) { to_ctx->status = 1; /* all channels recovered */ - return err; + goto out; } to_ctx->status = err; @@ -155,7 +185,8 @@ static int mlx5e_tx_reporter_timeout_recover(void *ctx) netdev_err(priv->netdev, "mlx5e_safe_reopen_channels failed recovering from a tx_timeout, err(%d).\n", err); - +out: + netdev_unlock(sq->netdev); return err; } @@ -172,10 +203,22 @@ static int mlx5e_tx_reporter_ptpsq_unhealthy_recover(void *ctx) return 0; priv = ptpsq->txqsq.priv; + netdev = priv->netdev; + + /* Recovering the PTP SQ means re-enabling NAPI, which requires the + * netdev instance lock. However, SQ closing has to wait for this work + * task to finish while also holding the same lock. So either get the + * lock or find that the SQ is no longer enabled and thus this work is + * not relevant anymore. + */ + while (!netdev_trylock(netdev)) { + if (!test_bit(MLX5E_SQ_STATE_ENABLED, &ptpsq->txqsq.state)) + return 0; + msleep(20); + } mutex_lock(&priv->state_lock); chs = &priv->channels; - netdev = priv->netdev; carrier_ok = netif_carrier_ok(netdev); netif_carrier_off(netdev); @@ -192,6 +235,7 @@ static int mlx5e_tx_reporter_ptpsq_unhealthy_recover(void *ctx) netif_carrier_on(netdev); mutex_unlock(&priv->state_lock); + netdev_unlock(netdev); return err; } diff --git a/drivers/net/ethernet/mellanox/mlx5/core/en_main.c b/drivers/net/ethernet/mellanox/mlx5/core/en_main.c index 59e17b41c3a677..cb993ad2d9ad9e 100644 --- a/drivers/net/ethernet/mellanox/mlx5/core/en_main.c +++ b/drivers/net/ethernet/mellanox/mlx5/core/en_main.c @@ -688,19 +688,7 @@ static void mlx5e_rq_timeout_work(struct work_struct *timeout_work) struct mlx5e_rq, rx_timeout_work); - /* Acquire netdev instance lock to synchronize with channel close and - * reopen flows. Either successfully obtain the lock, or detect that - * channels are closing for another reason, making this work no longer - * necessary. - */ - while (!netdev_trylock(rq->netdev)) { - if (!test_bit(MLX5E_STATE_CHANNELS_ACTIVE, &rq->priv->state)) - return; - msleep(20); - } - mlx5e_reporter_rx_timeout(rq); - netdev_unlock(rq->netdev); } static int mlx5e_alloc_mpwqe_rq_drop_page(struct mlx5e_rq *rq) @@ -1997,20 +1985,7 @@ void mlx5e_tx_err_cqe_work(struct work_struct *recover_work) struct mlx5e_txqsq *sq = container_of(recover_work, struct mlx5e_txqsq, recover_work); - /* Recovering queues means re-enabling NAPI, which requires the netdev - * instance lock. However, SQ closing flows have to wait for work tasks - * to finish while also holding the netdev instance lock. So either get - * the lock or find that the SQ is no longer enabled and thus this work - * is not relevant anymore. - */ - while (!netdev_trylock(sq->netdev)) { - if (!test_bit(MLX5E_SQ_STATE_ENABLED, &sq->state)) - return; - msleep(20); - } - mlx5e_reporter_tx_err_cqe(sq); - netdev_unlock(sq->netdev); } static struct dim_cq_moder mlx5e_get_def_tx_moderation(u8 cq_period_mode) @@ -5102,19 +5077,6 @@ static void mlx5e_tx_timeout_work(struct work_struct *work) struct net_device *netdev = priv->netdev; int i; - /* Recovering the TX queues implies re-enabling NAPI, which requires - * the netdev instance lock. - * However, channel closing flows have to wait for this work to finish - * while holding the same lock. So either get the lock or find that - * channels are being closed for other reason and this work is not - * relevant anymore. - */ - while (!netdev_trylock(netdev)) { - if (!test_bit(MLX5E_STATE_CHANNELS_ACTIVE, &priv->state)) - return; - msleep(20); - } - for (i = 0; i < netdev->real_num_tx_queues; i++) { struct netdev_queue *dev_queue = netdev_get_tx_queue(netdev, i); @@ -5127,8 +5089,6 @@ static void mlx5e_tx_timeout_work(struct work_struct *work) /* break if tried to reopened channels */ break; } - - netdev_unlock(netdev); } static void mlx5e_tx_timeout(struct net_device *dev, unsigned int txqueue) From d80311e398ebe49395233ed10431ae8ccf2ef9ed Mon Sep 17 00:00:00 2001 From: Cosmin Ratiu Date: Wed, 18 Feb 2026 09:29:04 +0200 Subject: [PATCH 3812/3902] net/mlx5e: Use unsigned for mlx5e_get_max_num_channels MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [ Upstream commit 57a94d4b22b0c6cc5d601e6b6238d78fb923d991 ] The max number of channels is always an unsigned int, use the correct type to fix compilation errors done with strict type checking, e.g.: error: call to ‘__compiletime_assert_1110’ declared with attribute error: min(mlx5e_get_devlink_param_num_doorbells(mdev), mlx5e_get_max_num_channels(mdev)) signedness error Fixes: 74a8dadac17e ("net/mlx5e: Preparations for supporting larger number of channels") Signed-off-by: Cosmin Ratiu Reviewed-by: Dragos Tatulea Signed-off-by: Tariq Toukan Reviewed-by: Jacob Keller Link: https://patch.msgid.link/20260218072904.1764634-7-tariqt@nvidia.com Signed-off-by: Jakub Kicinski Signed-off-by: Sasha Levin --- drivers/net/ethernet/mellanox/mlx5/core/en.h | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/drivers/net/ethernet/mellanox/mlx5/core/en.h b/drivers/net/ethernet/mellanox/mlx5/core/en.h index b34b85539f3b1c..5bced924a24fb5 100644 --- a/drivers/net/ethernet/mellanox/mlx5/core/en.h +++ b/drivers/net/ethernet/mellanox/mlx5/core/en.h @@ -179,7 +179,8 @@ static inline u16 mlx5_min_rx_wqes(int wq_type, u32 wq_size) } /* Use this function to get max num channels (rxqs/txqs) only to create netdev */ -static inline int mlx5e_get_max_num_channels(struct mlx5_core_dev *mdev) +static inline unsigned int +mlx5e_get_max_num_channels(struct mlx5_core_dev *mdev) { return is_kdump_kernel() ? MLX5E_MIN_NUM_CHANNELS : From fea017a7f6abe179decf575a2d8464c74edb3964 Mon Sep 17 00:00:00 2001 From: System Administrator Date: Thu, 9 Oct 2025 16:35:00 +0000 Subject: [PATCH 3813/3902] apparmor: fix NULL pointer dereference in __unix_needs_revalidation MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [ Upstream commit e2938ad00b21340c0362562dfedd7cfec0554d67 ] When receiving file descriptors via SCM_RIGHTS, both the socket pointer and the socket's sk pointer can be NULL during socket setup or teardown, causing NULL pointer dereferences in __unix_needs_revalidation(). This is a regression in AppArmor 5.0.0 (kernel 6.17+) where the new __unix_needs_revalidation() function was added without proper NULL checks. The crash manifests as: BUG: kernel NULL pointer dereference, address: 0x0000000000000018 RIP: aa_file_perm+0xb7/0x3b0 (or +0xbe/0x3b0, +0xc0/0x3e0) Call Trace: apparmor_file_receive+0x42/0x80 security_file_receive+0x2e/0x50 receive_fd+0x1d/0xf0 scm_detach_fds+0xad/0x1c0 The function dereferences sock->sk->sk_family without checking if either sock or sock->sk is NULL first. Add NULL checks for both sock and sock->sk before accessing sk_family. Fixes: 88fec3526e841 ("apparmor: make sure unix socket labeling is correctly updated.") Reported-by: Jamin Mc Closes: https://bugzilla.proxmox.com/show_bug.cgi?id=7083 Closes: https://gitlab.com/apparmor/apparmor/-/issues/568 Signed-off-by: Fabian Grünbichler Signed-off-by: System Administrator Signed-off-by: John Johansen Signed-off-by: Sasha Levin --- security/apparmor/file.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/security/apparmor/file.c b/security/apparmor/file.c index c758204028780f..919dbbbc87ab62 100644 --- a/security/apparmor/file.c +++ b/security/apparmor/file.c @@ -578,6 +578,9 @@ static bool __unix_needs_revalidation(struct file *file, struct aa_label *label, return false; if (request & NET_PEER_MASK) return false; + /* sock and sock->sk can be NULL for sockets being set up or torn down */ + if (!sock || !sock->sk) + return false; if (sock->sk->sk_family == PF_UNIX) { struct aa_sk_ctx *ctx = aa_sock(sock->sk); From ccb66a3c6c8f51b3ed1bc003b70bb9ff99e8d835 Mon Sep 17 00:00:00 2001 From: John Johansen Date: Mon, 24 Nov 2025 15:07:42 -0800 Subject: [PATCH 3814/3902] apparmor: fix NULL sock in aa_sock_file_perm [ Upstream commit 00b67657535dfea56e84d11492f5c0f61d0af297 ] Deal with the potential that sock and sock-sk can be NULL during socket setup or teardown. This could lead to an oops. The fix for NULL pointer dereference in __unix_needs_revalidation shows this is at least possible for af_unix sockets. While the fix for af_unix sockets applies for newer mediation this is still the fall back path for older af_unix mediation and other sockets, so ensure it is covered. Fixes: 56974a6fcfef6 ("apparmor: add base infastructure for socket mediation") Reviewed-by: Georgia Garcia Signed-off-by: John Johansen Signed-off-by: Sasha Levin --- security/apparmor/net.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/security/apparmor/net.c b/security/apparmor/net.c index 45cf25605c345b..44c04102062f3d 100644 --- a/security/apparmor/net.c +++ b/security/apparmor/net.c @@ -326,8 +326,10 @@ int aa_sock_file_perm(const struct cred *subj_cred, struct aa_label *label, struct socket *sock = (struct socket *) file->private_data; AA_BUG(!label); - AA_BUG(!sock); - AA_BUG(!sock->sk); + + /* sock && sock->sk can be NULL for sockets being set up or torn down */ + if (!sock || !sock->sk) + return 0; if (sock->sk->sk_family == PF_UNIX) return aa_unix_file_perm(subj_cred, label, op, request, file); From 23f112bd6144e815153462e12d313ac3e7027168 Mon Sep 17 00:00:00 2001 From: Helge Deller Date: Tue, 25 Nov 2025 16:11:07 +0100 Subject: [PATCH 3815/3902] AppArmor: Allow apparmor to handle unaligned dfa tables MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [ Upstream commit 64802f731214a51dfe3c6c27636b3ddafd003eb0 ] The dfa tables can originate from kernel or userspace and 8-byte alignment isn't always guaranteed and as such may trigger unaligned memory accesses on various architectures. Resulting in the following [   73.901376] WARNING: CPU: 0 PID: 341 at security/apparmor/match.c:316 aa_dfa_unpack+0x6cc/0x720 [   74.015867] Modules linked in: binfmt_misc evdev flash sg drm drm_panel_orientation_quirks backlight i2c_core configfs nfnetlink autofs4 ext4 crc16 mbcache jbd2 hid_generic usbhid sr_mod hid cdrom sd_mod ata_generic ohci_pci ehci_pci ehci_hcd ohci_hcd pata_ali libata sym53c8xx scsi_transport_spi tg3 scsi_mod usbcore libphy scsi_common mdio_bus usb_common [   74.428977] CPU: 0 UID: 0 PID: 341 Comm: apparmor_parser Not tainted 6.18.0-rc6+ #9 NONE [   74.536543] Call Trace: [   74.568561] [<0000000000434c24>] dump_stack+0x8/0x18 [   74.633757] [<0000000000476438>] __warn+0xd8/0x100 [   74.696664] [<00000000004296d4>] warn_slowpath_fmt+0x34/0x74 [   74.771006] [<00000000008db28c>] aa_dfa_unpack+0x6cc/0x720 [   74.843062] [<00000000008e643c>] unpack_pdb+0xbc/0x7e0 [   74.910545] [<00000000008e7740>] unpack_profile+0xbe0/0x1300 [   74.984888] [<00000000008e82e0>] aa_unpack+0xe0/0x6a0 [   75.051226] [<00000000008e3ec4>] aa_replace_profiles+0x64/0x1160 [   75.130144] [<00000000008d4d90>] policy_update+0xf0/0x280 [   75.201057] [<00000000008d4fc8>] profile_replace+0xa8/0x100 [   75.274258] [<0000000000766bd0>] vfs_write+0x90/0x420 [   75.340594] [<00000000007670cc>] ksys_write+0x4c/0xe0 [   75.406932] [<0000000000767174>] sys_write+0x14/0x40 [   75.472126] [<0000000000406174>] linux_sparc_syscall+0x34/0x44 [   75.548802] ---[ end trace 0000000000000000 ]--- [   75.609503] dfa blob stream 0xfff0000008926b96 not aligned. [   75.682695] Kernel unaligned access at TPC[8db2a8] aa_dfa_unpack+0x6e8/0x720 Work around it by using the get_unaligned_xx() helpers. Fixes: e6e8bf418850d ("apparmor: fix restricted endian type warnings for dfa unpack") Reported-by: John Paul Adrian Glaubitz Closes: https://github.com/sparclinux/issues/issues/30 Signed-off-by: Helge Deller Signed-off-by: John Johansen Signed-off-by: Sasha Levin --- security/apparmor/match.c | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/security/apparmor/match.c b/security/apparmor/match.c index c5a91600842a16..26e82ba879d44c 100644 --- a/security/apparmor/match.c +++ b/security/apparmor/match.c @@ -15,6 +15,7 @@ #include #include #include +#include #include "include/lib.h" #include "include/match.h" @@ -42,11 +43,11 @@ static struct table_header *unpack_table(char *blob, size_t bsize) /* loaded td_id's start at 1, subtract 1 now to avoid doing * it every time we use td_id as an index */ - th.td_id = be16_to_cpu(*(__be16 *) (blob)) - 1; + th.td_id = get_unaligned_be16(blob) - 1; if (th.td_id > YYTD_ID_MAX) goto out; - th.td_flags = be16_to_cpu(*(__be16 *) (blob + 2)); - th.td_lolen = be32_to_cpu(*(__be32 *) (blob + 8)); + th.td_flags = get_unaligned_be16(blob + 2); + th.td_lolen = get_unaligned_be32(blob + 8); blob += sizeof(struct table_header); if (!(th.td_flags == YYTD_DATA16 || th.td_flags == YYTD_DATA32 || @@ -313,14 +314,14 @@ struct aa_dfa *aa_dfa_unpack(void *blob, size_t size, int flags) if (size < sizeof(struct table_set_header)) goto fail; - if (ntohl(*(__be32 *) data) != YYTH_MAGIC) + if (get_unaligned_be32(data) != YYTH_MAGIC) goto fail; - hsize = ntohl(*(__be32 *) (data + 4)); + hsize = get_unaligned_be32(data + 4); if (size < hsize) goto fail; - dfa->flags = ntohs(*(__be16 *) (data + 12)); + dfa->flags = get_unaligned_be16(data + 12); if (dfa->flags & ~(YYTH_FLAGS)) goto fail; @@ -329,7 +330,7 @@ struct aa_dfa *aa_dfa_unpack(void *blob, size_t size, int flags) * if (dfa->flags & YYTH_FLAGS_OOB_TRANS) { * if (hsize < 16 + 4) * goto fail; - * dfa->max_oob = ntol(*(__be32 *) (data + 16)); + * dfa->max_oob = get_unaligned_be32(data + 16); * if (dfa->max <= MAX_OOB_SUPPORTED) { * pr_err("AppArmor DFA OOB greater than supported\n"); * goto fail; From e027999049c493fb728ead5a90db76942181a935 Mon Sep 17 00:00:00 2001 From: Helge Deller Date: Wed, 26 Nov 2025 21:15:04 +0100 Subject: [PATCH 3816/3902] apparmor: Fix & Optimize table creation from possibly unaligned memory [ Upstream commit 6fc367bfd4c8886e6b1742aabbd1c0bdc310db3a ] Source blob may come from userspace and might be unaligned. Try to optize the copying process by avoiding unaligned memory accesses. - Added Fixes tag - Added "Fix &" to description as this doesn't just optimize but fixes a potential unaligned memory access Fixes: e6e8bf418850d ("apparmor: fix restricted endian type warnings for dfa unpack") Signed-off-by: Helge Deller [jj: remove duplicate word "convert" in comment trigger checkpatch warning] Signed-off-by: John Johansen Signed-off-by: Sasha Levin --- security/apparmor/include/match.h | 12 +++++++----- security/apparmor/match.c | 7 +++---- 2 files changed, 10 insertions(+), 9 deletions(-) diff --git a/security/apparmor/include/match.h b/security/apparmor/include/match.h index 1fbe82f5021b16..0dde8eda3d1a59 100644 --- a/security/apparmor/include/match.h +++ b/security/apparmor/include/match.h @@ -104,16 +104,18 @@ struct aa_dfa { struct table_header *tables[YYTD_ID_TSIZE]; }; -#define byte_to_byte(X) (X) - #define UNPACK_ARRAY(TABLE, BLOB, LEN, TTYPE, BTYPE, NTOHX) \ do { \ typeof(LEN) __i; \ TTYPE *__t = (TTYPE *) TABLE; \ BTYPE *__b = (BTYPE *) BLOB; \ - for (__i = 0; __i < LEN; __i++) { \ - __t[__i] = NTOHX(__b[__i]); \ - } \ + BUILD_BUG_ON(sizeof(TTYPE) != sizeof(BTYPE)); \ + if (IS_ENABLED(CONFIG_CPU_BIG_ENDIAN)) \ + memcpy(__t, __b, (LEN) * sizeof(BTYPE)); \ + else /* copy & convert from big-endian */ \ + for (__i = 0; __i < LEN; __i++) { \ + __t[__i] = NTOHX(&__b[__i]); \ + } \ } while (0) static inline size_t table_size(size_t len, size_t el_size) diff --git a/security/apparmor/match.c b/security/apparmor/match.c index 26e82ba879d44c..bbeb3be68572f1 100644 --- a/security/apparmor/match.c +++ b/security/apparmor/match.c @@ -67,14 +67,13 @@ static struct table_header *unpack_table(char *blob, size_t bsize) table->td_flags = th.td_flags; table->td_lolen = th.td_lolen; if (th.td_flags == YYTD_DATA8) - UNPACK_ARRAY(table->td_data, blob, th.td_lolen, - u8, u8, byte_to_byte); + memcpy(table->td_data, blob, th.td_lolen); else if (th.td_flags == YYTD_DATA16) UNPACK_ARRAY(table->td_data, blob, th.td_lolen, - u16, __be16, be16_to_cpu); + u16, __be16, get_unaligned_be16); else if (th.td_flags == YYTD_DATA32) UNPACK_ARRAY(table->td_data, blob, th.td_lolen, - u32, __be32, be32_to_cpu); + u32, __be32, get_unaligned_be32); else goto fail; /* if table was vmalloced make sure the page tables are synced From ac8f179e5c9eecd0160dce9f1c8a0236fab81d76 Mon Sep 17 00:00:00 2001 From: Ryan Lee Date: Tue, 13 Jan 2026 09:35:57 -0800 Subject: [PATCH 3817/3902] apparmor: return -ENOMEM in unpack_perms_table upon alloc failure [ Upstream commit 74b7105e53e80a4072bd3e1a50be7aa15e3f0a01 ] In policy_unpack.c:unpack_perms_table, the perms struct is allocated via kcalloc, with the position being reset if the allocation fails. However, the error path results in -EPROTO being retured instead of -ENOMEM. Fix this to return the correct error code. Reported-by: Zygmunt Krynicki Fixes: fd1b2b95a2117 ("apparmor: add the ability for policy to specify a permission table") Reviewed-by: Tyler Hicks Signed-off-by: Ryan Lee Signed-off-by: John Johansen Signed-off-by: Sasha Levin --- security/apparmor/policy_unpack.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/security/apparmor/policy_unpack.c b/security/apparmor/policy_unpack.c index 7523971e37d9c0..dd602bd5fca998 100644 --- a/security/apparmor/policy_unpack.c +++ b/security/apparmor/policy_unpack.c @@ -687,8 +687,10 @@ static ssize_t unpack_perms_table(struct aa_ext *e, struct aa_perms **perms) if (!aa_unpack_array(e, NULL, &size)) goto fail_reset; *perms = kcalloc(size, sizeof(struct aa_perms), GFP_KERNEL); - if (!*perms) - goto fail_reset; + if (!*perms) { + e->pos = pos; + return -ENOMEM; + } for (i = 0; i < size; i++) { if (!unpack_perm(e, version, &(*perms)[i])) goto fail; From 1eadeb462ec365eba0219086326e64cbf9021fcd Mon Sep 17 00:00:00 2001 From: Ryan Lee Date: Wed, 7 Jan 2026 11:48:54 -0800 Subject: [PATCH 3818/3902] apparmor: fix boolean argument in apparmor_mmap_file [ Upstream commit 48d5268e911abcf7674ec33c9b0b3e952be1175e ] The previous value of GFP_ATOMIC is an int and not a bool, potentially resulting in UB when being assigned to a bool. In addition, the mmap hook is called outside of locks (i.e. in a non-atomic context), so we can pass a fixed constant value of false instead to common_mmap. Signed-off-by: Ryan Lee Signed-off-by: John Johansen Stable-dep-of: 4a134723f9f1 ("apparmor: move check for aa_null file to cover all cases") Signed-off-by: Sasha Levin --- security/apparmor/lsm.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/security/apparmor/lsm.c b/security/apparmor/lsm.c index b3f7a3258a2cf7..b02c6c8951cd68 100644 --- a/security/apparmor/lsm.c +++ b/security/apparmor/lsm.c @@ -583,7 +583,7 @@ static int common_mmap(const char *op, struct file *file, unsigned long prot, static int apparmor_mmap_file(struct file *file, unsigned long reqprot, unsigned long prot, unsigned long flags) { - return common_mmap(OP_FMMAP, file, prot, flags, GFP_ATOMIC); + return common_mmap(OP_FMMAP, file, prot, flags, false); } static int apparmor_file_mprotect(struct vm_area_struct *vma, From f3cb5e58a65d53cdb9456d19dc69af82f89a0d7a Mon Sep 17 00:00:00 2001 From: John Johansen Date: Sat, 17 Jan 2026 23:40:03 -0800 Subject: [PATCH 3819/3902] apparmor: drop in_atomic flag in common_mmap, and common_file_perm [ Upstream commit c3f27ccdb2dce3f0f2814574d06017f46c11fa29 ] with the previous changes to mmap the in_atomic flag is now always false, so drop it. Suggested-by: Tyler Hicks Reviewed-by: Georgia Garcia Signed-off-by: John Johansen Stable-dep-of: 4a134723f9f1 ("apparmor: move check for aa_null file to cover all cases") Signed-off-by: Sasha Levin --- security/apparmor/lsm.c | 21 +++++++++------------ 1 file changed, 9 insertions(+), 12 deletions(-) diff --git a/security/apparmor/lsm.c b/security/apparmor/lsm.c index b02c6c8951cd68..4e44bd5bf1d916 100644 --- a/security/apparmor/lsm.c +++ b/security/apparmor/lsm.c @@ -519,8 +519,7 @@ static void apparmor_file_free_security(struct file *file) aa_put_label(rcu_access_pointer(ctx->label)); } -static int common_file_perm(const char *op, struct file *file, u32 mask, - bool in_atomic) +static int common_file_perm(const char *op, struct file *file, u32 mask) { struct aa_label *label; int error = 0; @@ -531,7 +530,7 @@ static int common_file_perm(const char *op, struct file *file, u32 mask, return -EACCES; label = __begin_current_label_crit_section(&needput); - error = aa_file_perm(op, current_cred(), label, file, mask, in_atomic); + error = aa_file_perm(op, current_cred(), label, file, mask, false); __end_current_label_crit_section(label, needput); return error; @@ -539,13 +538,12 @@ static int common_file_perm(const char *op, struct file *file, u32 mask, static int apparmor_file_receive(struct file *file) { - return common_file_perm(OP_FRECEIVE, file, aa_map_file_to_perms(file), - false); + return common_file_perm(OP_FRECEIVE, file, aa_map_file_to_perms(file)); } static int apparmor_file_permission(struct file *file, int mask) { - return common_file_perm(OP_FPERM, file, mask, false); + return common_file_perm(OP_FPERM, file, mask); } static int apparmor_file_lock(struct file *file, unsigned int cmd) @@ -555,11 +553,11 @@ static int apparmor_file_lock(struct file *file, unsigned int cmd) if (cmd == F_WRLCK) mask |= MAY_WRITE; - return common_file_perm(OP_FLOCK, file, mask, false); + return common_file_perm(OP_FLOCK, file, mask); } static int common_mmap(const char *op, struct file *file, unsigned long prot, - unsigned long flags, bool in_atomic) + unsigned long flags) { int mask = 0; @@ -577,21 +575,20 @@ static int common_mmap(const char *op, struct file *file, unsigned long prot, if (prot & PROT_EXEC) mask |= AA_EXEC_MMAP; - return common_file_perm(op, file, mask, in_atomic); + return common_file_perm(op, file, mask); } static int apparmor_mmap_file(struct file *file, unsigned long reqprot, unsigned long prot, unsigned long flags) { - return common_mmap(OP_FMMAP, file, prot, flags, false); + return common_mmap(OP_FMMAP, file, prot, flags); } static int apparmor_file_mprotect(struct vm_area_struct *vma, unsigned long reqprot, unsigned long prot) { return common_mmap(OP_FMPROT, vma->vm_file, prot, - !(vma->vm_flags & VM_SHARED) ? MAP_PRIVATE : 0, - false); + !(vma->vm_flags & VM_SHARED) ? MAP_PRIVATE : 0); } #ifdef CONFIG_IO_URING From 7757757908e0326fe73a91b650b0b5b872cfc593 Mon Sep 17 00:00:00 2001 From: Ryan Lee Date: Wed, 7 Jan 2026 11:47:02 -0800 Subject: [PATCH 3820/3902] apparmor: account for in_atomic removal in common_file_perm [ Upstream commit 9b829c0aa96e9385b1e9a308d3eb054b95fbeda2 ] If we are not in an atomic context in common_file_perm, then we don't have to use the atomic versions, resulting in improved performance outside of atomic contexts. Signed-off-by: Ryan Lee Signed-off-by: John Johansen Stable-dep-of: 4a134723f9f1 ("apparmor: move check for aa_null file to cover all cases") Signed-off-by: Sasha Levin --- security/apparmor/lsm.c | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/security/apparmor/lsm.c b/security/apparmor/lsm.c index 4e44bd5bf1d916..5fc99fe8d38a44 100644 --- a/security/apparmor/lsm.c +++ b/security/apparmor/lsm.c @@ -523,15 +523,14 @@ static int common_file_perm(const char *op, struct file *file, u32 mask) { struct aa_label *label; int error = 0; - bool needput; /* don't reaudit files closed during inheritance */ if (unlikely(file->f_path.dentry == aa_null.dentry)) return -EACCES; - label = __begin_current_label_crit_section(&needput); + label = begin_current_label_crit_section(); error = aa_file_perm(op, current_cred(), label, file, mask, false); - __end_current_label_crit_section(label, needput); + end_current_label_crit_section(label); return error; } From b2a8011ae8749215f3939881239b454728a2d88b Mon Sep 17 00:00:00 2001 From: John Johansen Date: Sat, 13 Sep 2025 02:22:21 -0700 Subject: [PATCH 3821/3902] apparmor: move check for aa_null file to cover all cases [ Upstream commit 4a134723f9f1ad2f3621566259db673350d19cb1 ] files with a dentry pointing aa_null.dentry where already rejected as part of file_inheritance. Unfortunately the check in common_file_perm() is insufficient to cover all cases causing unnecessary audit messages without the original files context. Eg. [ 442.886474] audit: type=1400 audit(1704822661.616:329): apparmor="DENIED" operation="file_inherit" class="file" namespace="root//lxd-juju-98527a-0_" profile="snap.lxd.activate" name="/apparmor/.null" pid=9525 comm="snap-exec" Further examples of this are in the logs of https://bugs.launchpad.net/ubuntu/+source/apparmor/+bug/2120439 https://bugs.launchpad.net/ubuntu/+source/snapd/+bug/1952084 https://bugs.launchpad.net/snapd/+bug/2049099 These messages have no value and should not be sent to the logs. AppArmor was already filtering the out in some cases but the original patch did not catch all cases. Fix this by push the existing check down into two functions that should cover all cases. Link: https://bugs.launchpad.net/ubuntu/+source/apparmor/+bug/2122743 Fixes: 192ca6b55a86 ("apparmor: revalidate files during exec") Reviewed-by: Georgia Garcia Signed-off-by: John Johansen Signed-off-by: Sasha Levin --- security/apparmor/file.c | 12 ++++++++++-- security/apparmor/lsm.c | 4 ---- 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/security/apparmor/file.c b/security/apparmor/file.c index 919dbbbc87ab62..7de23e85cd5d01 100644 --- a/security/apparmor/file.c +++ b/security/apparmor/file.c @@ -154,8 +154,12 @@ static int path_name(const char *op, const struct cred *subj_cred, const char *info = NULL; int error; - error = aa_path_name(path, flags, buffer, name, &info, - labels_profile(label)->disconnected); + /* don't reaudit files closed during inheritance */ + if (unlikely(path->dentry == aa_null.dentry)) + error = -EACCES; + else + error = aa_path_name(path, flags, buffer, name, &info, + labels_profile(label)->disconnected); if (error) { fn_for_each_confined(label, profile, aa_audit_file(subj_cred, @@ -616,6 +620,10 @@ int aa_file_perm(const char *op, const struct cred *subj_cred, AA_BUG(!label); AA_BUG(!file); + /* don't reaudit files closed during inheritance */ + if (unlikely(file->f_path.dentry == aa_null.dentry)) + return -EACCES; + fctx = file_ctx(file); rcu_read_lock(); diff --git a/security/apparmor/lsm.c b/security/apparmor/lsm.c index 5fc99fe8d38a44..be3678d08ed23c 100644 --- a/security/apparmor/lsm.c +++ b/security/apparmor/lsm.c @@ -524,10 +524,6 @@ static int common_file_perm(const char *op, struct file *file, u32 mask) struct aa_label *label; int error = 0; - /* don't reaudit files closed during inheritance */ - if (unlikely(file->f_path.dentry == aa_null.dentry)) - return -EACCES; - label = begin_current_label_crit_section(); error = aa_file_perm(op, current_cred(), label, file, mask, false); end_current_label_crit_section(label); From 9bf1fa150775b0c6b794e4b6a2c0395e13777999 Mon Sep 17 00:00:00 2001 From: John Johansen Date: Sun, 9 Nov 2025 14:16:54 -0800 Subject: [PATCH 3822/3902] apparmor: fix rlimit for posix cpu timers [ Upstream commit 6ca56813f4a589f536adceb42882855d91fb1125 ] Posix cpu timers requires an additional step beyond setting the rlimit. Refactor the code so its clear when what code is setting the limit and conditionally update the posix cpu timers when appropriate. Fixes: baa73d9e478ff ("posix-timers: Make them configurable") Reviewed-by: Georgia Garcia Signed-off-by: John Johansen Signed-off-by: Sasha Levin --- security/apparmor/resource.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/security/apparmor/resource.c b/security/apparmor/resource.c index 8e80db3ae21c09..64212b39ba4bbc 100644 --- a/security/apparmor/resource.c +++ b/security/apparmor/resource.c @@ -196,6 +196,11 @@ void __aa_transition_rlimits(struct aa_label *old_l, struct aa_label *new_l) rules->rlimits.limits[j].rlim_max); /* soft limit should not exceed hard limit */ rlim->rlim_cur = min(rlim->rlim_cur, rlim->rlim_max); + if (j == RLIMIT_CPU && + rlim->rlim_cur != RLIM_INFINITY && + IS_ENABLED(CONFIG_POSIX_TIMERS)) + (void) update_rlimit_cpu(current->group_leader, + rlim->rlim_cur); } } } From 9ef25645775b4375c455d3b44ce90c22dfe4a2c8 Mon Sep 17 00:00:00 2001 From: John Johansen Date: Fri, 14 Nov 2025 00:14:36 -0800 Subject: [PATCH 3823/3902] apparmor: remove apply_modes_to_perms from label_match [ Upstream commit b2e27be2948f2f8c38421cd554b5fc9383215648 ] The modes shouldn't be applied at the point of label match, it just results in them being applied multiple times. Instead they should be applied after which is already being done by all callers so it can just be dropped from label_match. Reviewed-by: Georgia Garcia Signed-off-by: John Johansen Stable-dep-of: a4c9efa4dbad ("apparmor: make label_match return a consistent value") Signed-off-by: Sasha Levin --- security/apparmor/label.c | 3 --- 1 file changed, 3 deletions(-) diff --git a/security/apparmor/label.c b/security/apparmor/label.c index 913678f199c358..02ee128f53d13f 100644 --- a/security/apparmor/label.c +++ b/security/apparmor/label.c @@ -1317,7 +1317,6 @@ static int label_compound_match(struct aa_profile *profile, goto fail; } *perms = *aa_lookup_perms(rules->policy, state); - aa_apply_modes_to_perms(profile, perms); if ((perms->allow & request) != request) return -EACCES; @@ -1370,7 +1369,6 @@ static int label_components_match(struct aa_profile *profile, next: tmp = *aa_lookup_perms(rules->policy, state); - aa_apply_modes_to_perms(profile, &tmp); aa_perms_accum(perms, &tmp); label_for_each_cont(i, label, tp) { if (!aa_ns_visible(profile->ns, tp->ns, subns)) @@ -1379,7 +1377,6 @@ static int label_components_match(struct aa_profile *profile, if (!state) goto fail; tmp = *aa_lookup_perms(rules->policy, state); - aa_apply_modes_to_perms(profile, &tmp); aa_perms_accum(perms, &tmp); } From 4610d536ff097adf470b3e52bb1eba96068bd501 Mon Sep 17 00:00:00 2001 From: John Johansen Date: Thu, 13 Nov 2025 23:59:38 -0800 Subject: [PATCH 3824/3902] apparmor: make label_match return a consistent value [ Upstream commit a4c9efa4dbad6dacad6e8b274e30e814c8353097 ] compound match is inconsistent in returning a state or an integer error this is problemati if the error is ever used as a state in the state machine Fixes: f1bd904175e81 ("apparmor: add the base fns() for domain labels") Reviewed-by: Georgia Garcia Signed-off-by: John Johansen Signed-off-by: Sasha Levin --- security/apparmor/label.c | 20 +++++++++----------- 1 file changed, 9 insertions(+), 11 deletions(-) diff --git a/security/apparmor/label.c b/security/apparmor/label.c index 02ee128f53d13f..1d3fa5c28d97fc 100644 --- a/security/apparmor/label.c +++ b/security/apparmor/label.c @@ -1278,7 +1278,7 @@ static inline aa_state_t match_component(struct aa_profile *profile, * @request: permissions to request * @perms: perms struct to set * - * Returns: 0 on success else ERROR + * Returns: state match stopped at or DFA_NOMATCH if aborted early * * For the label A//&B//&C this does the perm match for A//&B//&C * @perms should be preinitialized with allperms OR a previous permission @@ -1305,7 +1305,7 @@ static int label_compound_match(struct aa_profile *profile, /* no component visible */ *perms = allperms; - return 0; + return state; next: label_for_each_cont(i, label, tp) { @@ -1317,14 +1317,11 @@ static int label_compound_match(struct aa_profile *profile, goto fail; } *perms = *aa_lookup_perms(rules->policy, state); - if ((perms->allow & request) != request) - return -EACCES; - - return 0; + return state; fail: *perms = nullperms; - return state; + return DFA_NOMATCH; } /** @@ -1406,11 +1403,12 @@ int aa_label_match(struct aa_profile *profile, struct aa_ruleset *rules, struct aa_label *label, aa_state_t state, bool subns, u32 request, struct aa_perms *perms) { - int error = label_compound_match(profile, rules, label, state, subns, - request, perms); - if (!error) - return error; + aa_state_t tmp = label_compound_match(profile, rules, label, state, subns, + request, perms); + if ((perms->allow & request) == request) + return 0; + /* failed compound_match try component matches */ *perms = allperms; return label_components_match(profile, rules, label, state, subns, request, perms); From 80c334acc6d0bee8605a358a33e69b4aea1ffb92 Mon Sep 17 00:00:00 2001 From: Zhengmian Hu Date: Mon, 19 Jan 2026 19:03:07 -0500 Subject: [PATCH 3825/3902] apparmor: avoid per-cpu hold underflow in aa_get_buffer [ Upstream commit 640cf2f09575c9dc344b3f7be2498d31e3923ead ] When aa_get_buffer() pulls from the per-cpu list it unconditionally decrements cache->hold. If hold reaches 0 while count is still non-zero, the unsigned decrement wraps to UINT_MAX. This keeps hold non-zero for a very long time, so aa_put_buffer() never returns buffers to the global list, which can starve other CPUs and force repeated kmalloc(aa_g_path_max) allocations. Guard the decrement so hold never underflows. Fixes: ea9bae12d028 ("apparmor: cache buffers on percpu list if there is lock contention") Signed-off-by: Zhengmian Hu Signed-off-by: John Johansen Signed-off-by: Sasha Levin --- security/apparmor/lsm.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/security/apparmor/lsm.c b/security/apparmor/lsm.c index be3678d08ed23c..13c9bfdf65ff57 100644 --- a/security/apparmor/lsm.c +++ b/security/apparmor/lsm.c @@ -2136,7 +2136,8 @@ char *aa_get_buffer(bool in_atomic) if (!list_empty(&cache->head)) { aa_buf = list_first_entry(&cache->head, union aa_buffer, list); list_del(&aa_buf->list); - cache->hold--; + if (cache->hold) + cache->hold--; cache->count--; put_cpu_ptr(&aa_local_buffers); return &aa_buf->buffer[0]; From 1d2b2b58fde9059a488bc25399e6c3d74e9b5548 Mon Sep 17 00:00:00 2001 From: Georgia Garcia Date: Thu, 29 Jan 2026 15:58:45 -0300 Subject: [PATCH 3826/3902] apparmor: fix invalid deref of rawdata when export_binary is unset [ Upstream commit df9ac55abd18628bd8cff687ea043660532a3654 ] If the export_binary parameter is disabled on runtime, profiles that were loaded before that will still have their rawdata stored in apparmorfs, with a symbolic link to the rawdata on the policy directory. When one of those profiles are replaced, the rawdata is set to NULL, but when trying to resolve the symbolic links to rawdata for that profile, it will try to dereference profile->rawdata->name when profile->rawdata is now NULL causing an oops. Fix it by checking if rawdata is set. [ 168.653080] BUG: kernel NULL pointer dereference, address: 0000000000000088 [ 168.657420] #PF: supervisor read access in kernel mode [ 168.660619] #PF: error_code(0x0000) - not-present page [ 168.663613] PGD 0 P4D 0 [ 168.665450] Oops: Oops: 0000 [#1] SMP NOPTI [ 168.667836] CPU: 1 UID: 0 PID: 1729 Comm: ls Not tainted 6.19.0-rc7+ #3 PREEMPT(voluntary) [ 168.672308] Hardware name: QEMU Ubuntu 24.04 PC (i440FX + PIIX, 1996), BIOS 1.16.3-debian-1.16.3-2 04/01/2014 [ 168.679327] RIP: 0010:rawdata_get_link_base.isra.0+0x23/0x330 [ 168.682768] Code: 90 90 90 90 90 90 90 0f 1f 44 00 00 55 48 89 e5 41 57 41 56 41 55 41 54 53 48 83 ec 18 48 89 55 d0 48 85 ff 0f 84 e3 01 00 00 <48> 83 3c 25 88 00 00 00 00 0f 84 d4 01 00 00 49 89 f6 49 89 cc e8 [ 168.689818] RSP: 0018:ffffcdcb8200fb80 EFLAGS: 00010282 [ 168.690871] RAX: ffffffffaee74ec0 RBX: 0000000000000000 RCX: ffffffffb0120158 [ 168.692251] RDX: ffffcdcb8200fbe0 RSI: ffff88c187c9fa80 RDI: ffff88c186c98a80 [ 168.693593] RBP: ffffcdcb8200fbc0 R08: 0000000000000000 R09: 0000000000000000 [ 168.694941] R10: 0000000000000000 R11: 0000000000000000 R12: ffff88c186c98a80 [ 168.696289] R13: 00007fff005aaa20 R14: 0000000000000080 R15: ffff88c188f4fce0 [ 168.697637] FS: 0000790e81c58280(0000) GS:ffff88c20a957000(0000) knlGS:0000000000000000 [ 168.699227] CS: 0010 DS: 0000 ES: 0000 CR0: 0000000080050033 [ 168.700349] CR2: 0000000000000088 CR3: 000000012fd3e000 CR4: 0000000000350ef0 [ 168.701696] Call Trace: [ 168.702325] [ 168.702995] rawdata_get_link_data+0x1c/0x30 [ 168.704145] vfs_readlink+0xd4/0x160 [ 168.705152] do_readlinkat+0x114/0x180 [ 168.706214] __x64_sys_readlink+0x1e/0x30 [ 168.708653] x64_sys_call+0x1d77/0x26b0 [ 168.709525] do_syscall_64+0x81/0x500 [ 168.710348] ? do_statx+0x72/0xb0 [ 168.711109] ? putname+0x3e/0x80 [ 168.711845] ? __x64_sys_statx+0xb7/0x100 [ 168.712711] ? x64_sys_call+0x10fc/0x26b0 [ 168.713577] ? do_syscall_64+0xbf/0x500 [ 168.714412] ? do_user_addr_fault+0x1d2/0x8d0 [ 168.715404] ? irqentry_exit+0xb2/0x740 [ 168.716359] ? exc_page_fault+0x90/0x1b0 [ 168.717307] entry_SYSCALL_64_after_hwframe+0x76/0x7e Fixes: 1180b4c757aab ("apparmor: fix dangling symlinks to policy rawdata after replacement") Signed-off-by: Georgia Garcia Signed-off-by: John Johansen Signed-off-by: Sasha Levin --- security/apparmor/apparmorfs.c | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/security/apparmor/apparmorfs.c b/security/apparmor/apparmorfs.c index 391a586d0557f0..7803b973b4c427 100644 --- a/security/apparmor/apparmorfs.c +++ b/security/apparmor/apparmorfs.c @@ -1639,6 +1639,15 @@ static const char *rawdata_get_link_base(struct dentry *dentry, label = aa_get_label_rcu(&proxy->label); profile = labels_profile(label); + + /* rawdata can be null when aa_g_export_binary is unset during + * runtime and a profile is replaced + */ + if (!profile->rawdata) { + aa_put_label(label); + return ERR_PTR(-ENOENT); + } + depth = profile_depth(profile); target = gen_symlink_name(depth, profile->rawdata->name, name); aa_put_label(label); From aebd195e93052e33e38cd083133909d63c89735a Mon Sep 17 00:00:00 2001 From: John Johansen Date: Mon, 2 Feb 2026 04:12:02 -0800 Subject: [PATCH 3827/3902] apparmor: fix aa_label to return state from compount and component match [ Upstream commit 9058798652c8bc0584ed1fb0766a1015046c06e8 ] aa-label_match is not correctly returning the state in all cases. The only reason this didn't cause a error is that all callers currently ignore the return value. Reported-by: kernel test robot Closes: https://lore.kernel.org/oe-kbuild-all/202602020631.wXgZosyU-lkp@intel.com/ Fixes: a4c9efa4dbad6 ("apparmor: make label_match return a consistent value") Signed-off-by: John Johansen Signed-off-by: Sasha Levin --- security/apparmor/label.c | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/security/apparmor/label.c b/security/apparmor/label.c index 1d3fa5c28d97fc..dd6c58f595ba8d 100644 --- a/security/apparmor/label.c +++ b/security/apparmor/label.c @@ -1334,7 +1334,7 @@ static int label_compound_match(struct aa_profile *profile, * @request: permissions to request * @perms: an initialized perms struct to add accumulation to * - * Returns: 0 on success else ERROR + * Returns: the state the match finished in, may be the none matching state * * For the label A//&B//&C this does the perm match for each of A and B and C * @perms should be preinitialized with allperms OR a previous permission @@ -1362,7 +1362,7 @@ static int label_components_match(struct aa_profile *profile, } /* no subcomponents visible - no change in perms */ - return 0; + return state; next: tmp = *aa_lookup_perms(rules->policy, state); @@ -1378,13 +1378,13 @@ static int label_components_match(struct aa_profile *profile, } if ((perms->allow & request) != request) - return -EACCES; + return DFA_NOMATCH; - return 0; + return state; fail: *perms = nullperms; - return -EACCES; + return DFA_NOMATCH; } /** @@ -1406,7 +1406,7 @@ int aa_label_match(struct aa_profile *profile, struct aa_ruleset *rules, aa_state_t tmp = label_compound_match(profile, rules, label, state, subns, request, perms); if ((perms->allow & request) == request) - return 0; + return tmp; /* failed compound_match try component matches */ *perms = allperms; From d1370ef2ecf7d4df25e3e1e430cd191b1e7f8596 Mon Sep 17 00:00:00 2001 From: Zilin Guan Date: Thu, 29 Jan 2026 09:25:32 +0000 Subject: [PATCH 3828/3902] drm/amdgpu: Fix memory leak in amdgpu_acpi_enumerate_xcc() [ Upstream commit c9be63d565789b56ca7b0197e2cb78a3671f95a8 ] In amdgpu_acpi_enumerate_xcc(), if amdgpu_acpi_dev_init() returns -ENOMEM, the function returns directly without releasing the allocated xcc_info, resulting in a memory leak. Fix this by ensuring that xcc_info is properly freed in the error paths. Compile tested only. Issue found using a prototype static analysis tool and code review. Fixes: 4d5275ab0b18 ("drm/amdgpu: Add parsing of acpi xcc objects") Reviewed-by: Lijo Lazar Signed-off-by: Zilin Guan Signed-off-by: Alex Deucher Signed-off-by: Sasha Levin --- drivers/gpu/drm/amd/amdgpu/amdgpu_acpi.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_acpi.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_acpi.c index 6c62e27b980023..67db986eda3f64 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_acpi.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_acpi.c @@ -1136,8 +1136,10 @@ static int amdgpu_acpi_enumerate_xcc(void) if (!dev_info) ret = amdgpu_acpi_dev_init(&dev_info, xcc_info, sbdf); - if (ret == -ENOMEM) + if (ret == -ENOMEM) { + kfree(xcc_info); return ret; + } if (!dev_info) { kfree(xcc_info); From 9ae85b0c1909b6c6bfd2636b04cdaf7f520bf2b5 Mon Sep 17 00:00:00 2001 From: Zilin Guan Date: Thu, 29 Jan 2026 09:05:42 +0000 Subject: [PATCH 3829/3902] drm/amdgpu: Use kvfree instead of kfree in amdgpu_gmc_get_nps_memranges() [ Upstream commit 0c44d61945c4a80775292d96460aa2f22e62f86c ] amdgpu_discovery_get_nps_info() internally allocates memory for ranges using kvcalloc(), which may use vmalloc() for large allocation. Using kfree() to release vmalloc memory will lead to a memory corruption. Use kvfree() to safely handle both kmalloc and vmalloc allocations. Compile tested only. Issue found using a prototype static analysis tool and code review. Fixes: b194d21b9bcc ("drm/amdgpu: Use NPS ranges from discovery table") Reviewed-by: Lijo Lazar Signed-off-by: Zilin Guan Signed-off-by: Alex Deucher Signed-off-by: Sasha Levin --- drivers/gpu/drm/amd/amdgpu/amdgpu_gmc.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_gmc.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_gmc.c index aef1ba1bdca9e3..01ad5cc008a965 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_gmc.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_gmc.c @@ -1381,7 +1381,7 @@ int amdgpu_gmc_get_nps_memranges(struct amdgpu_device *adev, if (!*exp_ranges) *exp_ranges = range_cnt; err: - kfree(ranges); + kvfree(ranges); return ret; } From 2fef8c2ac67e7c1b0409d23653300b134c63e54c Mon Sep 17 00:00:00 2001 From: Zilin Guan Date: Thu, 29 Jan 2026 08:35:15 +0000 Subject: [PATCH 3830/3902] drm/amdgpu: Fix memory leak in amdgpu_ras_init() [ Upstream commit ee41e5b63c8210525c936ee637a2c8d185ce873c ] When amdgpu_nbio_ras_sw_init() fails in amdgpu_ras_init(), the function returns directly without freeing the allocated con structure, leading to a memory leak. Fix this by jumping to the release_con label to properly clean up the allocated memory before returning the error code. Compile tested only. Issue found using a prototype static analysis tool and code review. Fixes: fdc94d3a8c88 ("drm/amdgpu: Rework pcie_bif ras sw_init") Reviewed-by: Tao Zhou Signed-off-by: Zilin Guan Signed-off-by: Alex Deucher Signed-off-by: Sasha Levin --- drivers/gpu/drm/amd/amdgpu/amdgpu_ras.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_ras.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_ras.c index e0ee211508607e..3fd19859055a57 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_ras.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_ras.c @@ -4137,7 +4137,7 @@ int amdgpu_ras_init(struct amdgpu_device *adev) * to handle fatal error */ r = amdgpu_nbio_ras_sw_init(adev); if (r) - return r; + goto release_con; if (adev->nbio.ras && adev->nbio.ras->init_ras_controller_interrupt) { From 3759040d6d7c1f8f4144bd52357d3220b3972684 Mon Sep 17 00:00:00 2001 From: Alex Deucher Date: Tue, 14 Oct 2025 17:01:05 -0400 Subject: [PATCH 3831/3902] drm/amdgpu: move reset debug disable handling [ Upstream commit ad0a48e531a3137cec16bb5f8f60c8cc8de06b01 ] Move everything to the supported resets masks rather than having an explicit misc checks for this. Reviewed-by: Jesse Zhang Signed-off-by: Alex Deucher Stable-dep-of: 46a2cb7d24f2 ("drm/amdgpu/sdma5: enable queue resets unconditionally") Signed-off-by: Sasha Levin --- drivers/gpu/drm/amd/amdgpu/amdgpu_job.c | 8 +++----- drivers/gpu/drm/amd/amdgpu/amdgpu_ring.c | 3 --- drivers/gpu/drm/amd/amdgpu/gfx_v10_0.c | 3 ++- drivers/gpu/drm/amd/amdgpu/gfx_v11_0.c | 6 ++++-- drivers/gpu/drm/amd/amdgpu/gfx_v12_0.c | 3 ++- drivers/gpu/drm/amd/amdgpu/gfx_v9_0.c | 2 +- drivers/gpu/drm/amd/amdgpu/gfx_v9_4_3.c | 6 ++++-- drivers/gpu/drm/amd/amdgpu/sdma_v4_4_2.c | 8 ++++++-- drivers/gpu/drm/amd/amdgpu/sdma_v5_0.c | 3 ++- drivers/gpu/drm/amd/amdgpu/sdma_v5_2.c | 6 ++++-- drivers/gpu/drm/amd/amdgpu/sdma_v6_0.c | 3 ++- drivers/gpu/drm/amd/amdgpu/sdma_v7_0.c | 3 ++- 12 files changed, 32 insertions(+), 22 deletions(-) diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_job.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_job.c index d020a890a0ea42..630af847f29ff3 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_job.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_job.c @@ -130,11 +130,9 @@ static enum drm_gpu_sched_stat amdgpu_job_timedout(struct drm_sched_job *s_job) } /* attempt a per ring reset */ - if (unlikely(adev->debug_disable_gpu_ring_reset)) { - dev_err(adev->dev, "Ring reset disabled by debug mask\n"); - } else if (amdgpu_gpu_recovery && - amdgpu_ring_is_reset_type_supported(ring, AMDGPU_RESET_TYPE_PER_QUEUE) && - ring->funcs->reset) { + if (amdgpu_gpu_recovery && + amdgpu_ring_is_reset_type_supported(ring, AMDGPU_RESET_TYPE_PER_QUEUE) && + ring->funcs->reset) { dev_err(adev->dev, "Starting %s ring reset\n", s_job->sched->name); r = amdgpu_ring_reset(ring, job->vmid, &job->hw_fence); diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_ring.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_ring.c index 5ec5c3ff22bb07..304564ec2f59a1 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_ring.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_ring.c @@ -460,9 +460,6 @@ bool amdgpu_ring_soft_recovery(struct amdgpu_ring *ring, unsigned int vmid, ktime_t deadline; bool ret; - if (unlikely(ring->adev->debug_disable_soft_recovery)) - return false; - deadline = ktime_add_us(ktime_get(), 10000); if (amdgpu_sriov_vf(ring->adev) || !ring->funcs->soft_recovery || !fence) diff --git a/drivers/gpu/drm/amd/amdgpu/gfx_v10_0.c b/drivers/gpu/drm/amd/amdgpu/gfx_v10_0.c index 726b2bdfbba332..003bcece715eb6 100644 --- a/drivers/gpu/drm/amd/amdgpu/gfx_v10_0.c +++ b/drivers/gpu/drm/amd/amdgpu/gfx_v10_0.c @@ -4956,7 +4956,8 @@ static int gfx_v10_0_sw_init(struct amdgpu_ip_block *ip_block) amdgpu_get_soft_full_reset_mask(&adev->gfx.gfx_ring[0]); adev->gfx.compute_supported_reset = amdgpu_get_soft_full_reset_mask(&adev->gfx.compute_ring[0]); - if (!amdgpu_sriov_vf(adev)) { + if (!amdgpu_sriov_vf(adev) && + !adev->debug_disable_gpu_ring_reset) { adev->gfx.compute_supported_reset |= AMDGPU_RESET_TYPE_PER_QUEUE; adev->gfx.gfx_supported_reset |= AMDGPU_RESET_TYPE_PER_QUEUE; } diff --git a/drivers/gpu/drm/amd/amdgpu/gfx_v11_0.c b/drivers/gpu/drm/amd/amdgpu/gfx_v11_0.c index c936772c037255..1dd9fd486eecfb 100644 --- a/drivers/gpu/drm/amd/amdgpu/gfx_v11_0.c +++ b/drivers/gpu/drm/amd/amdgpu/gfx_v11_0.c @@ -1821,13 +1821,15 @@ static int gfx_v11_0_sw_init(struct amdgpu_ip_block *ip_block) case IP_VERSION(11, 0, 3): if ((adev->gfx.me_fw_version >= 2280) && (adev->gfx.mec_fw_version >= 2410) && - !amdgpu_sriov_vf(adev)) { + !amdgpu_sriov_vf(adev) && + !adev->debug_disable_gpu_ring_reset) { adev->gfx.compute_supported_reset |= AMDGPU_RESET_TYPE_PER_QUEUE; adev->gfx.gfx_supported_reset |= AMDGPU_RESET_TYPE_PER_QUEUE; } break; default: - if (!amdgpu_sriov_vf(adev)) { + if (!amdgpu_sriov_vf(adev) && + !adev->debug_disable_gpu_ring_reset) { adev->gfx.compute_supported_reset |= AMDGPU_RESET_TYPE_PER_QUEUE; adev->gfx.gfx_supported_reset |= AMDGPU_RESET_TYPE_PER_QUEUE; } diff --git a/drivers/gpu/drm/amd/amdgpu/gfx_v12_0.c b/drivers/gpu/drm/amd/amdgpu/gfx_v12_0.c index f80e9e356e2527..50e39b9d9df6f8 100644 --- a/drivers/gpu/drm/amd/amdgpu/gfx_v12_0.c +++ b/drivers/gpu/drm/amd/amdgpu/gfx_v12_0.c @@ -1547,7 +1547,8 @@ static int gfx_v12_0_sw_init(struct amdgpu_ip_block *ip_block) case IP_VERSION(12, 0, 1): if ((adev->gfx.me_fw_version >= 2660) && (adev->gfx.mec_fw_version >= 2920) && - !amdgpu_sriov_vf(adev)) { + !amdgpu_sriov_vf(adev) && + !adev->debug_disable_gpu_ring_reset) { adev->gfx.compute_supported_reset |= AMDGPU_RESET_TYPE_PER_QUEUE; adev->gfx.gfx_supported_reset |= AMDGPU_RESET_TYPE_PER_QUEUE; } diff --git a/drivers/gpu/drm/amd/amdgpu/gfx_v9_0.c b/drivers/gpu/drm/amd/amdgpu/gfx_v9_0.c index dd19a97436db95..7d0a2d239b78dd 100644 --- a/drivers/gpu/drm/amd/amdgpu/gfx_v9_0.c +++ b/drivers/gpu/drm/amd/amdgpu/gfx_v9_0.c @@ -2409,7 +2409,7 @@ static int gfx_v9_0_sw_init(struct amdgpu_ip_block *ip_block) amdgpu_get_soft_full_reset_mask(&adev->gfx.gfx_ring[0]); adev->gfx.compute_supported_reset = amdgpu_get_soft_full_reset_mask(&adev->gfx.compute_ring[0]); - if (!amdgpu_sriov_vf(adev)) + if (!amdgpu_sriov_vf(adev) && !adev->debug_disable_gpu_ring_reset) adev->gfx.compute_supported_reset |= AMDGPU_RESET_TYPE_PER_QUEUE; r = amdgpu_gfx_kiq_init(adev, GFX9_MEC_HPD_SIZE, 0); diff --git a/drivers/gpu/drm/amd/amdgpu/gfx_v9_4_3.c b/drivers/gpu/drm/amd/amdgpu/gfx_v9_4_3.c index c90cbe053ef37b..a4ebb6c5af5574 100644 --- a/drivers/gpu/drm/amd/amdgpu/gfx_v9_4_3.c +++ b/drivers/gpu/drm/amd/amdgpu/gfx_v9_4_3.c @@ -1149,14 +1149,16 @@ static int gfx_v9_4_3_sw_init(struct amdgpu_ip_block *ip_block) case IP_VERSION(9, 4, 3): case IP_VERSION(9, 4, 4): if ((adev->gfx.mec_fw_version >= 155) && - !amdgpu_sriov_vf(adev)) { + !amdgpu_sriov_vf(adev) && + !adev->debug_disable_gpu_ring_reset) { adev->gfx.compute_supported_reset |= AMDGPU_RESET_TYPE_PER_QUEUE; adev->gfx.compute_supported_reset |= AMDGPU_RESET_TYPE_PER_PIPE; } break; case IP_VERSION(9, 5, 0): if ((adev->gfx.mec_fw_version >= 21) && - !amdgpu_sriov_vf(adev)) { + !amdgpu_sriov_vf(adev) && + !adev->debug_disable_gpu_ring_reset) { adev->gfx.compute_supported_reset |= AMDGPU_RESET_TYPE_PER_QUEUE; adev->gfx.compute_supported_reset |= AMDGPU_RESET_TYPE_PER_PIPE; } diff --git a/drivers/gpu/drm/amd/amdgpu/sdma_v4_4_2.c b/drivers/gpu/drm/amd/amdgpu/sdma_v4_4_2.c index 36b1ca73c2ed32..a1443990d5c60d 100644 --- a/drivers/gpu/drm/amd/amdgpu/sdma_v4_4_2.c +++ b/drivers/gpu/drm/amd/amdgpu/sdma_v4_4_2.c @@ -2361,11 +2361,15 @@ static void sdma_v4_4_2_update_reset_mask(struct amdgpu_device *adev) switch (amdgpu_ip_version(adev, GC_HWIP, 0)) { case IP_VERSION(9, 4, 3): case IP_VERSION(9, 4, 4): - if ((adev->gfx.mec_fw_version >= 0xb0) && amdgpu_dpm_reset_sdma_is_supported(adev)) + if ((adev->gfx.mec_fw_version >= 0xb0) && + amdgpu_dpm_reset_sdma_is_supported(adev) && + !adev->debug_disable_gpu_ring_reset) adev->sdma.supported_reset |= AMDGPU_RESET_TYPE_PER_QUEUE; break; case IP_VERSION(9, 5, 0): - if ((adev->gfx.mec_fw_version >= 0xf) && amdgpu_dpm_reset_sdma_is_supported(adev)) + if ((adev->gfx.mec_fw_version >= 0xf) && + amdgpu_dpm_reset_sdma_is_supported(adev) && + !adev->debug_disable_gpu_ring_reset) adev->sdma.supported_reset |= AMDGPU_RESET_TYPE_PER_QUEUE; break; default: diff --git a/drivers/gpu/drm/amd/amdgpu/sdma_v5_0.c b/drivers/gpu/drm/amd/amdgpu/sdma_v5_0.c index 7dc67a22a7a01d..8ddc4df06a1fde 100644 --- a/drivers/gpu/drm/amd/amdgpu/sdma_v5_0.c +++ b/drivers/gpu/drm/amd/amdgpu/sdma_v5_0.c @@ -1429,7 +1429,8 @@ static int sdma_v5_0_sw_init(struct amdgpu_ip_block *ip_block) case IP_VERSION(5, 0, 2): case IP_VERSION(5, 0, 5): if ((adev->sdma.instance[0].fw_version >= 35) && - !amdgpu_sriov_vf(adev)) + !amdgpu_sriov_vf(adev) && + !adev->debug_disable_gpu_ring_reset) adev->sdma.supported_reset |= AMDGPU_RESET_TYPE_PER_QUEUE; break; default: diff --git a/drivers/gpu/drm/amd/amdgpu/sdma_v5_2.c b/drivers/gpu/drm/amd/amdgpu/sdma_v5_2.c index 3bd44c24f692db..c6a619514a8ad9 100644 --- a/drivers/gpu/drm/amd/amdgpu/sdma_v5_2.c +++ b/drivers/gpu/drm/amd/amdgpu/sdma_v5_2.c @@ -1348,12 +1348,14 @@ static int sdma_v5_2_sw_init(struct amdgpu_ip_block *ip_block) case IP_VERSION(5, 2, 3): case IP_VERSION(5, 2, 4): if ((adev->sdma.instance[0].fw_version >= 76) && - !amdgpu_sriov_vf(adev)) + !amdgpu_sriov_vf(adev) && + !adev->debug_disable_gpu_ring_reset) adev->sdma.supported_reset |= AMDGPU_RESET_TYPE_PER_QUEUE; break; case IP_VERSION(5, 2, 5): if ((adev->sdma.instance[0].fw_version >= 34) && - !amdgpu_sriov_vf(adev)) + !amdgpu_sriov_vf(adev) && + !adev->debug_disable_gpu_ring_reset) adev->sdma.supported_reset |= AMDGPU_RESET_TYPE_PER_QUEUE; break; default: diff --git a/drivers/gpu/drm/amd/amdgpu/sdma_v6_0.c b/drivers/gpu/drm/amd/amdgpu/sdma_v6_0.c index 3c6568d5019947..21704004498728 100644 --- a/drivers/gpu/drm/amd/amdgpu/sdma_v6_0.c +++ b/drivers/gpu/drm/amd/amdgpu/sdma_v6_0.c @@ -1356,7 +1356,8 @@ static int sdma_v6_0_sw_init(struct amdgpu_ip_block *ip_block) case IP_VERSION(6, 0, 2): case IP_VERSION(6, 0, 3): if ((adev->sdma.instance[0].fw_version >= 21) && - !amdgpu_sriov_vf(adev)) + !amdgpu_sriov_vf(adev) && + !adev->debug_disable_gpu_ring_reset) adev->sdma.supported_reset |= AMDGPU_RESET_TYPE_PER_QUEUE; break; default: diff --git a/drivers/gpu/drm/amd/amdgpu/sdma_v7_0.c b/drivers/gpu/drm/amd/amdgpu/sdma_v7_0.c index 326ecc8d37d21d..2b81344dcd668a 100644 --- a/drivers/gpu/drm/amd/amdgpu/sdma_v7_0.c +++ b/drivers/gpu/drm/amd/amdgpu/sdma_v7_0.c @@ -1337,7 +1337,8 @@ static int sdma_v7_0_sw_init(struct amdgpu_ip_block *ip_block) adev->sdma.supported_reset = amdgpu_get_soft_full_reset_mask(&adev->sdma.instance[0].ring); - if (!amdgpu_sriov_vf(adev)) + if (!amdgpu_sriov_vf(adev) && + !adev->debug_disable_gpu_ring_reset) adev->sdma.supported_reset |= AMDGPU_RESET_TYPE_PER_QUEUE; r = amdgpu_sdma_sysfs_reset_mask_init(adev); From 0d8ba96c792fc4b5339b00e0d7e18fde5c56dedf Mon Sep 17 00:00:00 2001 From: Alex Deucher Date: Tue, 3 Feb 2026 11:51:45 -0500 Subject: [PATCH 3832/3902] drm/amdgpu/sdma5: enable queue resets unconditionally [ Upstream commit 46a2cb7d24f21132e970cab52359210c3f5ea3c6 ] There is no firmware version dependency. Fixes: 59fd50b8663b ("drm/amdgpu: Add sysfs interface for sdma reset mask") Cc: Jesse Zhang Reviewed-by: Jesse.Zhang Signed-off-by: Alex Deucher Signed-off-by: Sasha Levin --- drivers/gpu/drm/amd/amdgpu/sdma_v5_0.c | 15 +++------------ 1 file changed, 3 insertions(+), 12 deletions(-) diff --git a/drivers/gpu/drm/amd/amdgpu/sdma_v5_0.c b/drivers/gpu/drm/amd/amdgpu/sdma_v5_0.c index 8ddc4df06a1fde..45e2933214a802 100644 --- a/drivers/gpu/drm/amd/amdgpu/sdma_v5_0.c +++ b/drivers/gpu/drm/amd/amdgpu/sdma_v5_0.c @@ -1424,18 +1424,9 @@ static int sdma_v5_0_sw_init(struct amdgpu_ip_block *ip_block) adev->sdma.supported_reset = amdgpu_get_soft_full_reset_mask(&adev->sdma.instance[0].ring); - switch (amdgpu_ip_version(adev, SDMA0_HWIP, 0)) { - case IP_VERSION(5, 0, 0): - case IP_VERSION(5, 0, 2): - case IP_VERSION(5, 0, 5): - if ((adev->sdma.instance[0].fw_version >= 35) && - !amdgpu_sriov_vf(adev) && - !adev->debug_disable_gpu_ring_reset) - adev->sdma.supported_reset |= AMDGPU_RESET_TYPE_PER_QUEUE; - break; - default: - break; - } + if (!amdgpu_sriov_vf(adev) && + !adev->debug_disable_gpu_ring_reset) + adev->sdma.supported_reset |= AMDGPU_RESET_TYPE_PER_QUEUE; /* Allocate memory for SDMA IP Dump buffer */ ptr = kcalloc(adev->sdma.num_instances * reg_count, sizeof(uint32_t), GFP_KERNEL); From 5211aa12e46fc3266c7e8aaf6b2c58c88f87acc4 Mon Sep 17 00:00:00 2001 From: Alex Deucher Date: Tue, 3 Feb 2026 11:52:46 -0500 Subject: [PATCH 3833/3902] drm/amdgpu/sdma5.2: enable queue resets unconditionally [ Upstream commit 314d30ad50622fc0d70da71509f9dff21545be14 ] There is no firmware version dependency. This also enables sdma queue resets on all SDMA 5.2.x based chips. Fixes: 59fd50b8663b ("drm/amdgpu: Add sysfs interface for sdma reset mask") Cc: Jesse Zhang Reviewed-by: Jesse.Zhang Signed-off-by: Alex Deucher Signed-off-by: Sasha Levin --- drivers/gpu/drm/amd/amdgpu/sdma_v5_2.c | 22 +++------------------- 1 file changed, 3 insertions(+), 19 deletions(-) diff --git a/drivers/gpu/drm/amd/amdgpu/sdma_v5_2.c b/drivers/gpu/drm/amd/amdgpu/sdma_v5_2.c index c6a619514a8ad9..5b982cc91af391 100644 --- a/drivers/gpu/drm/amd/amdgpu/sdma_v5_2.c +++ b/drivers/gpu/drm/amd/amdgpu/sdma_v5_2.c @@ -1342,25 +1342,9 @@ static int sdma_v5_2_sw_init(struct amdgpu_ip_block *ip_block) adev->sdma.supported_reset = amdgpu_get_soft_full_reset_mask(&adev->sdma.instance[0].ring); - switch (amdgpu_ip_version(adev, SDMA0_HWIP, 0)) { - case IP_VERSION(5, 2, 0): - case IP_VERSION(5, 2, 2): - case IP_VERSION(5, 2, 3): - case IP_VERSION(5, 2, 4): - if ((adev->sdma.instance[0].fw_version >= 76) && - !amdgpu_sriov_vf(adev) && - !adev->debug_disable_gpu_ring_reset) - adev->sdma.supported_reset |= AMDGPU_RESET_TYPE_PER_QUEUE; - break; - case IP_VERSION(5, 2, 5): - if ((adev->sdma.instance[0].fw_version >= 34) && - !amdgpu_sriov_vf(adev) && - !adev->debug_disable_gpu_ring_reset) - adev->sdma.supported_reset |= AMDGPU_RESET_TYPE_PER_QUEUE; - break; - default: - break; - } + if (!amdgpu_sriov_vf(adev) && + !adev->debug_disable_gpu_ring_reset) + adev->sdma.supported_reset |= AMDGPU_RESET_TYPE_PER_QUEUE; /* Allocate memory for SDMA IP Dump buffer */ ptr = kcalloc(adev->sdma.num_instances * reg_count, sizeof(uint32_t), GFP_KERNEL); From ca2eff3617860c9081c80b6df179fce1d3a4e25b Mon Sep 17 00:00:00 2001 From: Alex Deucher Date: Tue, 3 Feb 2026 11:53:51 -0500 Subject: [PATCH 3834/3902] drm/amdgpu/sdma6: enable queue resets unconditionally [ Upstream commit 56423871e9eef1dd069bddef895207fa5ce275fe ] There is no firmware version dependency. This also enables sdma queue resets on all SDMA 6.x based chips. Fixes: 59fd50b8663b ("drm/amdgpu: Add sysfs interface for sdma reset mask") Cc: Jesse Zhang Reviewed-by: Jesse.Zhang Signed-off-by: Alex Deucher Signed-off-by: Sasha Levin --- drivers/gpu/drm/amd/amdgpu/sdma_v6_0.c | 15 +++------------ 1 file changed, 3 insertions(+), 12 deletions(-) diff --git a/drivers/gpu/drm/amd/amdgpu/sdma_v6_0.c b/drivers/gpu/drm/amd/amdgpu/sdma_v6_0.c index 21704004498728..6809c6d4be5b1b 100644 --- a/drivers/gpu/drm/amd/amdgpu/sdma_v6_0.c +++ b/drivers/gpu/drm/amd/amdgpu/sdma_v6_0.c @@ -1351,18 +1351,9 @@ static int sdma_v6_0_sw_init(struct amdgpu_ip_block *ip_block) adev->sdma.supported_reset = amdgpu_get_soft_full_reset_mask(&adev->sdma.instance[0].ring); - switch (amdgpu_ip_version(adev, SDMA0_HWIP, 0)) { - case IP_VERSION(6, 0, 0): - case IP_VERSION(6, 0, 2): - case IP_VERSION(6, 0, 3): - if ((adev->sdma.instance[0].fw_version >= 21) && - !amdgpu_sriov_vf(adev) && - !adev->debug_disable_gpu_ring_reset) - adev->sdma.supported_reset |= AMDGPU_RESET_TYPE_PER_QUEUE; - break; - default: - break; - } + if (!amdgpu_sriov_vf(adev) && + !adev->debug_disable_gpu_ring_reset) + adev->sdma.supported_reset |= AMDGPU_RESET_TYPE_PER_QUEUE; if (amdgpu_sdma_ras_sw_init(adev)) { dev_err(adev->dev, "Failed to initialize sdma ras block!\n"); From a62afa5a595ec4a5bbb63a1d9efd6470b8ca39f2 Mon Sep 17 00:00:00 2001 From: Li RongQing Date: Thu, 5 Feb 2026 04:40:10 -0500 Subject: [PATCH 3835/3902] mshv: fix SRCU protection in irqfd resampler ack handler [ Upstream commit 2e7577cd5ddc1f86d1b6c48caf3cfa87dbb14e34 ] Replace hlist_for_each_entry_rcu() with hlist_for_each_entry_srcu() in mshv_irqfd_resampler_ack() to correctly handle SRCU-protected linked list traversal. The function uses SRCU (sleepable RCU) synchronization via partition->pt_irq_srcu, but was incorrectly using the RCU variant for list iteration. This could lead to race conditions when the list is modified concurrently. Also add srcu_read_lock_held() assertion as required by hlist_for_each_entry_srcu() to ensure we're in the proper read-side critical section. Fixes: 621191d709b14 ("Drivers: hv: Introduce mshv_root module to expose /dev/mshv to VMMs") Signed-off-by: Li RongQing Reviewed-by: Anirudh Rayabharam (Microsoft) Acked-by: Stanislav Kinsburskii Signed-off-by: Wei Liu Signed-off-by: Sasha Levin --- drivers/hv/mshv_eventfd.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/drivers/hv/mshv_eventfd.c b/drivers/hv/mshv_eventfd.c index 806674722868b6..05d643f54f45a7 100644 --- a/drivers/hv/mshv_eventfd.c +++ b/drivers/hv/mshv_eventfd.c @@ -87,8 +87,9 @@ static void mshv_irqfd_resampler_ack(struct mshv_irq_ack_notifier *mian) idx = srcu_read_lock(&partition->pt_irq_srcu); - hlist_for_each_entry_rcu(irqfd, &resampler->rsmplr_irqfd_list, - irqfd_resampler_hnode) { + hlist_for_each_entry_srcu(irqfd, &resampler->rsmplr_irqfd_list, + irqfd_resampler_hnode, + srcu_read_lock_held(&partition->pt_irq_srcu)) { if (hv_should_clear_interrupt(irqfd->irqfd_lapic_irq.lapic_control.interrupt_type)) hv_call_clear_virtual_interrupt(partition->pt_id); From 9a2a5da002775376498e8814df4a87cd629a3a0c Mon Sep 17 00:00:00 2001 From: Ziyi Guo Date: Tue, 10 Feb 2026 18:57:14 +0000 Subject: [PATCH 3836/3902] ASoC: fsl_xcvr: Revert fix missing lock in fsl_xcvr_mode_put() [ Upstream commit 9f16d96e1222391a6b996a1b676bec14fb91e3b2 ] This reverts commit f51424872760 ("ASoC: fsl_xcvr: fix missing lock in fsl_xcvr_mode_put()"). The original patch attempted to acquire the card->controls_rwsem lock in fsl_xcvr_mode_put(). However, this function is called from the upper ALSA core function snd_ctl_elem_write(), which already holds the write lock on controls_rwsem for the whole put operation. So there is no need to simply hold the lock for fsl_xcvr_activate_ctl() again. Acquiring the read lock while holding the write lock in the same thread results in a deadlock and a hung task, as reported by Alexander Stein. Fixes: f51424872760 ("ASoC: fsl_xcvr: fix missing lock in fsl_xcvr_mode_put()") Reported-by: Alexander Stein Closes: https://lore.kernel.org/linux-sound/5056506.GXAFRqVoOG@steina-w/ Signed-off-by: Ziyi Guo Link: https://patch.msgid.link/20260210185714.556385-1-n7l8m4@u.northwestern.edu Signed-off-by: Mark Brown Signed-off-by: Sasha Levin --- sound/soc/fsl/fsl_xcvr.c | 3 --- 1 file changed, 3 deletions(-) diff --git a/sound/soc/fsl/fsl_xcvr.c b/sound/soc/fsl/fsl_xcvr.c index 51669e5fe8888e..58db4906a01d50 100644 --- a/sound/soc/fsl/fsl_xcvr.c +++ b/sound/soc/fsl/fsl_xcvr.c @@ -223,13 +223,10 @@ static int fsl_xcvr_mode_put(struct snd_kcontrol *kcontrol, xcvr->mode = snd_soc_enum_item_to_val(e, item[0]); - down_read(&card->snd_card->controls_rwsem); fsl_xcvr_activate_ctl(dai, fsl_xcvr_arc_mode_kctl.name, (xcvr->mode == FSL_XCVR_MODE_ARC)); fsl_xcvr_activate_ctl(dai, fsl_xcvr_earc_capds_kctl.name, (xcvr->mode == FSL_XCVR_MODE_EARC)); - up_read(&card->snd_card->controls_rwsem); - /* Allow playback for SPDIF only */ rtd = snd_soc_get_pcm_runtime(card, card->dai_link); rtd->pcm->streams[SNDRV_PCM_STREAM_PLAYBACK].substream_count = From 63680351142e87e0f123482333d15d03c8db6b61 Mon Sep 17 00:00:00 2001 From: Kaushlendra Kumar Date: Fri, 9 Jan 2026 08:55:49 +0530 Subject: [PATCH 3837/3902] drm/i915/acpi: free _DSM package when no connectors [ Upstream commit 57b85fd53fccfdf14ce7b36d919c31aa752255f8 ] acpi_evaluate_dsm_typed() returns an ACPI package in pkg. When pkg->package.count == 0, we returned without freeing pkg, leaking memory. Free pkg before returning on the empty case. Signed-off-by: Kaushlendra Kumar Fixes: 337d7a1621c7 ("drm/i915: Fix invalid access to ACPI _DSM objects") Reviewed-by: Jani Nikula Link: https://patch.msgid.link/20260109032549.1826303-1-kaushlendra.kumar@intel.com Signed-off-by: Jani Nikula (cherry picked from commit c0a27a0ca8a34e96d08bb05a2c5d5ccf63fb8dc0) Signed-off-by: Joonas Lahtinen Signed-off-by: Sasha Levin --- drivers/gpu/drm/i915/display/intel_acpi.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/gpu/drm/i915/display/intel_acpi.c b/drivers/gpu/drm/i915/display/intel_acpi.c index 1addd628824137..1e8b9d1756988d 100644 --- a/drivers/gpu/drm/i915/display/intel_acpi.c +++ b/drivers/gpu/drm/i915/display/intel_acpi.c @@ -96,6 +96,7 @@ static void intel_dsm_platform_mux_info(acpi_handle dhandle) if (!pkg->package.count) { DRM_DEBUG_DRIVER("no connection in _DSM\n"); + ACPI_FREE(pkg); return; } From e37630e34e246f3f538da1e34bb1482191f6bb8d Mon Sep 17 00:00:00 2001 From: Alexandre Ferrieux Date: Wed, 11 Feb 2026 11:27:32 +0100 Subject: [PATCH 3838/3902] ASoC: codecs: aw88261: Fix erroneous bitmask logic in Awinic init [ Upstream commit b82fa9b0c26eeb2fde6017f7de2c3c544484efef ] The aw88261_dev_reg_update() function sets the Awinic registers in a rather nonuniform way: - most registers get directly overwritten from the firmware blob - but a handful of them need more delicate logic to preserve some bits from their current value, according to a register- specific mask For the latter, the logic is basically NEW = (OLD & MASK) | (VAL & ~MASK) However, the ~MASK value is hand-computed, and in the specific case of the SYSCTRL register, in a buggy way. This patch restores the proper ~MASK value. Fixes: 028a2ae25691 ("ASoC: codecs: Add aw88261 amplifier driver") Signed-off-by: Alexandre Ferrieux Link: https://patch.msgid.link/20260211-aw88261-fwname-v1-1-e24e833a019d@fairphone.com Signed-off-by: Mark Brown Signed-off-by: Sasha Levin --- sound/soc/codecs/aw88261.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/sound/soc/codecs/aw88261.c b/sound/soc/codecs/aw88261.c index de11ae8dd9d9f7..124c0a58d08bd2 100644 --- a/sound/soc/codecs/aw88261.c +++ b/sound/soc/codecs/aw88261.c @@ -423,9 +423,10 @@ static int aw88261_dev_reg_update(struct aw88261 *aw88261, if (ret) break; + /* keep all three bits from current hw status */ read_val &= (~AW88261_AMPPD_MASK) | (~AW88261_PWDN_MASK) | (~AW88261_HMUTE_MASK); - reg_val &= (AW88261_AMPPD_MASK | AW88261_PWDN_MASK | AW88261_HMUTE_MASK); + reg_val &= (AW88261_AMPPD_MASK & AW88261_PWDN_MASK & AW88261_HMUTE_MASK); reg_val |= read_val; /* enable uls hmute */ From 96f42087b05801263b1f4c1129f525c0bbcfa3eb Mon Sep 17 00:00:00 2001 From: Kai-Heng Feng Date: Tue, 10 Feb 2026 22:20:57 +0800 Subject: [PATCH 3839/3902] PCI: Validate window resource type in pbus_select_window_for_type() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [ Upstream commit e5f72cb9cea599dc9f5a9b80a33560a1d06f01cc ] After ebe091ad81e1 ("PCI: Use pbus_select_window_for_type() during IO window sizing") and ae88d0b9c57f ("PCI: Use pbus_select_window_for_type() during mem window sizing"), many bridge windows can't get resources assigned: pci 0006:05:00.0: bridge window [??? 0x00001000-0x00001fff flags 0x20080000]: can't assign; no space pci 0006:05:00.0: bridge window [??? 0x00001000-0x00001fff flags 0x20080000]: failed to assign Those commits replace find_bus_resource_of_type() with pbus_select_window_for_type(), and the latter lacks resource type validation. Add the resource type validation back to pbus_select_window_for_type() to match the original behavior. Fixes: 74afce3dfcba ("PCI: Add bridge window selection functions") Link: https://bugzilla.kernel.org/show_bug.cgi?id=221072 Signed-off-by: Kai-Heng Feng Signed-off-by: Bjorn Helgaas Reviewed-by: Ilpo Järvinen Link: https://patch.msgid.link/20260210142058.82701-1-kaihengf@nvidia.com Signed-off-by: Sasha Levin --- drivers/pci/setup-bus.c | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/drivers/pci/setup-bus.c b/drivers/pci/setup-bus.c index 4f4890196e63e4..cc592ccff54247 100644 --- a/drivers/pci/setup-bus.c +++ b/drivers/pci/setup-bus.c @@ -221,14 +221,21 @@ static struct resource *pbus_select_window_for_type(struct pci_bus *bus, switch (iores_type) { case IORESOURCE_IO: - return pci_bus_resource_n(bus, PCI_BUS_BRIDGE_IO_WINDOW); + win = pci_bus_resource_n(bus, PCI_BUS_BRIDGE_IO_WINDOW); + if (win && (win->flags & IORESOURCE_IO)) + return win; + return NULL; case IORESOURCE_MEM: mmio = pci_bus_resource_n(bus, PCI_BUS_BRIDGE_MEM_WINDOW); mmio_pref = pci_bus_resource_n(bus, PCI_BUS_BRIDGE_PREF_MEM_WINDOW); - if (!(type & IORESOURCE_PREFETCH) || - !(mmio_pref->flags & IORESOURCE_MEM)) + if (mmio && !(mmio->flags & IORESOURCE_MEM)) + mmio = NULL; + if (mmio_pref && !(mmio_pref->flags & IORESOURCE_MEM)) + mmio_pref = NULL; + + if (!(type & IORESOURCE_PREFETCH) || !mmio_pref) return mmio; if ((type & IORESOURCE_MEM_64) || From 2b36c0c1bcbbe15f6cfa9652084b3124c835a150 Mon Sep 17 00:00:00 2001 From: Srinivasan Shanmugam Date: Fri, 6 Feb 2026 21:18:11 +0530 Subject: [PATCH 3840/3902] drm/amdkfd: Fix watch_id bounds checking in debug address watch v2 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [ Upstream commit 5a19302cab5cec7ae7f1a60c619951e6c17d8742 ] The address watch clear code receives watch_id as an unsigned value (u32), but some helper functions were using a signed int and checked bits by shifting with watch_id. If a very large watch_id is passed from userspace, it can be converted to a negative value. This can cause invalid shifts and may access memory outside the watch_points array. drm/amdkfd: Fix watch_id bounds checking in debug address watch v2 Fix this by checking that watch_id is within MAX_WATCH_ADDRESSES before using it. Also use BIT(watch_id) to test and clear bits safely. This keeps the behavior unchanged for valid watch IDs and avoids undefined behavior for invalid ones. Fixes the below: drivers/gpu/drm/amd/amdgpu/../amdkfd/kfd_debug.c:448 kfd_dbg_trap_clear_dev_address_watch() error: buffer overflow 'pdd->watch_points' 4 <= u32max user_rl='0-3,2147483648-u32max' uncapped drivers/gpu/drm/amd/amdgpu/../amdkfd/kfd_debug.c 433 int kfd_dbg_trap_clear_dev_address_watch(struct kfd_process_device *pdd, 434 uint32_t watch_id) 435 { 436 int r; 437 438 if (!kfd_dbg_owns_dev_watch_id(pdd, watch_id)) kfd_dbg_owns_dev_watch_id() doesn't check for negative values so if watch_id is larger than INT_MAX it leads to a buffer overflow. (Negative shifts are undefined). 439 return -EINVAL; 440 441 if (!pdd->dev->kfd->shared_resources.enable_mes) { 442 r = debug_lock_and_unmap(pdd->dev->dqm); 443 if (r) 444 return r; 445 } 446 447 amdgpu_gfx_off_ctrl(pdd->dev->adev, false); --> 448 pdd->watch_points[watch_id] = pdd->dev->kfd2kgd->clear_address_watch( 449 pdd->dev->adev, 450 watch_id); v2: (as per, Jonathan Kim) - Add early watch_id >= MAX_WATCH_ADDRESSES validation in the set path to match the clear path. - Drop the redundant bounds check in kfd_dbg_owns_dev_watch_id(). Fixes: e0f85f4690d0 ("drm/amdkfd: add debug set and clear address watch points operation") Reported-by: Dan Carpenter Cc: Jonathan Kim Cc: Felix Kuehling Cc: Alex Deucher Cc: Christian König Signed-off-by: Srinivasan Shanmugam Reviewed-by: Jonathan Kim Signed-off-by: Alex Deucher Signed-off-by: Sasha Levin --- drivers/gpu/drm/amd/amdkfd/kfd_debug.c | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/drivers/gpu/drm/amd/amdkfd/kfd_debug.c b/drivers/gpu/drm/amd/amdkfd/kfd_debug.c index ba99e0f258aee2..986cb297de8f83 100644 --- a/drivers/gpu/drm/amd/amdkfd/kfd_debug.c +++ b/drivers/gpu/drm/amd/amdkfd/kfd_debug.c @@ -401,27 +401,25 @@ static int kfd_dbg_get_dev_watch_id(struct kfd_process_device *pdd, int *watch_i return -ENOMEM; } -static void kfd_dbg_clear_dev_watch_id(struct kfd_process_device *pdd, int watch_id) +static void kfd_dbg_clear_dev_watch_id(struct kfd_process_device *pdd, u32 watch_id) { spin_lock(&pdd->dev->watch_points_lock); /* process owns device watch point so safe to clear */ - if ((pdd->alloc_watch_ids >> watch_id) & 0x1) { - pdd->alloc_watch_ids &= ~(0x1 << watch_id); - pdd->dev->alloc_watch_ids &= ~(0x1 << watch_id); + if (pdd->alloc_watch_ids & BIT(watch_id)) { + pdd->alloc_watch_ids &= ~BIT(watch_id); + pdd->dev->alloc_watch_ids &= ~BIT(watch_id); } spin_unlock(&pdd->dev->watch_points_lock); } -static bool kfd_dbg_owns_dev_watch_id(struct kfd_process_device *pdd, int watch_id) +static bool kfd_dbg_owns_dev_watch_id(struct kfd_process_device *pdd, u32 watch_id) { bool owns_watch_id = false; spin_lock(&pdd->dev->watch_points_lock); - owns_watch_id = watch_id < MAX_WATCH_ADDRESSES && - ((pdd->alloc_watch_ids >> watch_id) & 0x1); - + owns_watch_id = pdd->alloc_watch_ids & BIT(watch_id); spin_unlock(&pdd->dev->watch_points_lock); return owns_watch_id; @@ -432,6 +430,9 @@ int kfd_dbg_trap_clear_dev_address_watch(struct kfd_process_device *pdd, { int r; + if (watch_id >= MAX_WATCH_ADDRESSES) + return -EINVAL; + if (!kfd_dbg_owns_dev_watch_id(pdd, watch_id)) return -EINVAL; @@ -469,6 +470,9 @@ int kfd_dbg_trap_set_dev_address_watch(struct kfd_process_device *pdd, if (r) return r; + if (*watch_id >= MAX_WATCH_ADDRESSES) + return -EINVAL; + if (!pdd->dev->kfd->shared_resources.enable_mes) { r = debug_lock_and_unmap(pdd->dev->dqm); if (r) { From 8eb62f5adc12b124004a7d6c0eafcb541ed569dd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Timur=20Krist=C3=B3f?= Date: Sun, 18 Jan 2026 15:57:41 +0100 Subject: [PATCH 3841/3902] drm/amd/display: Reject cursor plane on DCE when scaled differently than primary MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [ Upstream commit 41af6215cdbcecd12920f211239479027904abf3 ] Currently DCE doesn't support the overlay cursor, so the dm_crtc_get_cursor_mode() function returns DM_CURSOR_NATIVE_MODE unconditionally. The outcome is that it doesn't check for the conditions that would necessitate the overlay cursor, meaning that it doesn't reject cases where the native cursor mode isn't supported on DCE. Remove the early return from dm_crtc_get_cursor_mode() for DCE and instead let it perform the necessary checks and return DM_CURSOR_OVERLAY_MODE. Add a later check that rejects when DM_CURSOR_OVERLAY_MODE would be used with DCE. Fixes: 1b04dcca4fb1 ("drm/amd/display: Introduce overlay cursor mode") Closes: https://gitlab.freedesktop.org/drm/amd/-/issues/4600 Suggested-by: Leo Li Signed-off-by: Timur Kristóf Reviewed-by: Rodrigo Siqueira Signed-off-by: Alex Deucher Signed-off-by: Sasha Levin --- drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c index 6252afd1d087fd..ccf13bb5281bf9 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c @@ -12027,10 +12027,9 @@ static int dm_crtc_get_cursor_mode(struct amdgpu_device *adev, /* Overlay cursor not supported on HW before DCN * DCN401 does not have the cursor-on-scaled-plane or cursor-on-yuv-plane restrictions - * as previous DCN generations, so enable native mode on DCN401 in addition to DCE + * as previous DCN generations, so enable native mode on DCN401 */ - if (amdgpu_ip_version(adev, DCE_HWIP, 0) == 0 || - amdgpu_ip_version(adev, DCE_HWIP, 0) == IP_VERSION(4, 0, 1)) { + if (amdgpu_ip_version(adev, DCE_HWIP, 0) == IP_VERSION(4, 0, 1)) { *cursor_mode = DM_CURSOR_NATIVE_MODE; return 0; } @@ -12350,6 +12349,12 @@ static int amdgpu_dm_atomic_check(struct drm_device *dev, * need to be added for DC to not disable a plane by mistake */ if (dm_new_crtc_state->cursor_mode == DM_CURSOR_OVERLAY_MODE) { + if (amdgpu_ip_version(adev, DCE_HWIP, 0) == 0) { + drm_dbg(dev, "Overlay cursor not supported on DCE\n"); + ret = -EINVAL; + goto fail; + } + ret = drm_atomic_add_affected_planes(state, crtc); if (ret) goto fail; From 263e28add4f4472cfa95150d218955d1945aa413 Mon Sep 17 00:00:00 2001 From: Srinivasan Shanmugam Date: Fri, 6 Feb 2026 20:49:23 +0530 Subject: [PATCH 3842/3902] drm/amd/display: Fix out-of-bounds stream encoder index v3 [ Upstream commit abde491143e4e12eecc41337910aace4e8d59603 ] eng_id can be negative and that stream_enc_regs[] can be indexed out of bounds. eng_id is used directly as an index into stream_enc_regs[], which has only 5 entries. When eng_id is 5 (ENGINE_ID_DIGF) or negative, this can access memory past the end of the array. Add a bounds check using ARRAY_SIZE() before using eng_id as an index. The unsigned cast also rejects negative values. This avoids out-of-bounds access. Fixes the below smatch error: dcn*_resource.c: stream_encoder_create() may index stream_enc_regs[eng_id] out of bounds (size 5). drivers/gpu/drm/amd/amdgpu/../display/dc/resource/dcn351/dcn351_resource.c 1246 static struct stream_encoder *dcn35_stream_encoder_create( 1247 enum engine_id eng_id, 1248 struct dc_context *ctx) 1249 { ... 1255 1256 /* Mapping of VPG, AFMT, DME register blocks to DIO block instance */ 1257 if (eng_id <= ENGINE_ID_DIGF) { ENGINE_ID_DIGF is 5. should <= be dc_bios, 1283 eng_id, vpg, afmt, --> 1284 &stream_enc_regs[eng_id], ^^^^^^^^^^^^^^^^^^^^^^^ This stream_enc_regs[] array has 5 elements so we are one element beyond the end of the array. ... 1287 return &enc1->base; 1288 } v2: use explicit bounds check as suggested by Roman/Dan; avoid unsigned int cast v3: The compiler already knows how to compare the two values, so the cast (int) is not needed. (Roman) Fixes: 2728e9c7c842 ("drm/amd/display: add DC changes for DCN351") Reported-by: Dan Carpenter Cc: Harry Wentland Cc: Mario Limonciello Cc: Alex Hung Cc: Aurabindo Pillai Cc: ChiaHsuan Chung Cc: Roman Li Signed-off-by: Srinivasan Shanmugam Reviewed-by: Roman Li Signed-off-by: Alex Deucher Signed-off-by: Sasha Levin --- .../drm/amd/display/dc/resource/dcn315/dcn315_resource.c | 8 ++++---- .../drm/amd/display/dc/resource/dcn316/dcn316_resource.c | 8 ++++---- .../drm/amd/display/dc/resource/dcn32/dcn32_resource.c | 8 ++++---- .../drm/amd/display/dc/resource/dcn321/dcn321_resource.c | 8 ++++---- .../drm/amd/display/dc/resource/dcn35/dcn35_resource.c | 8 ++++---- .../drm/amd/display/dc/resource/dcn351/dcn351_resource.c | 8 ++++---- 6 files changed, 24 insertions(+), 24 deletions(-) diff --git a/drivers/gpu/drm/amd/display/dc/resource/dcn315/dcn315_resource.c b/drivers/gpu/drm/amd/display/dc/resource/dcn315/dcn315_resource.c index 82cc78c291d824..12c2a0d9fb2a34 100644 --- a/drivers/gpu/drm/amd/display/dc/resource/dcn315/dcn315_resource.c +++ b/drivers/gpu/drm/amd/display/dc/resource/dcn315/dcn315_resource.c @@ -1226,12 +1226,12 @@ static struct stream_encoder *dcn315_stream_encoder_create( /*PHYB is wired off in HW, allow front end to remapping, otherwise needs more changes*/ /* Mapping of VPG, AFMT, DME register blocks to DIO block instance */ - if (eng_id <= ENGINE_ID_DIGF) { - vpg_inst = eng_id; - afmt_inst = eng_id; - } else + if (eng_id < 0 || eng_id >= ARRAY_SIZE(stream_enc_regs)) return NULL; + vpg_inst = eng_id; + afmt_inst = eng_id; + enc1 = kzalloc(sizeof(struct dcn10_stream_encoder), GFP_KERNEL); vpg = dcn31_vpg_create(ctx, vpg_inst); afmt = dcn31_afmt_create(ctx, afmt_inst); diff --git a/drivers/gpu/drm/amd/display/dc/resource/dcn316/dcn316_resource.c b/drivers/gpu/drm/amd/display/dc/resource/dcn316/dcn316_resource.c index 636110e48d01b6..3c77c14c5a5ed0 100644 --- a/drivers/gpu/drm/amd/display/dc/resource/dcn316/dcn316_resource.c +++ b/drivers/gpu/drm/amd/display/dc/resource/dcn316/dcn316_resource.c @@ -1220,12 +1220,12 @@ static struct stream_encoder *dcn316_stream_encoder_create( int afmt_inst; /* Mapping of VPG, AFMT, DME register blocks to DIO block instance */ - if (eng_id <= ENGINE_ID_DIGF) { - vpg_inst = eng_id; - afmt_inst = eng_id; - } else + if (eng_id < 0 || eng_id >= ARRAY_SIZE(stream_enc_regs)) return NULL; + vpg_inst = eng_id; + afmt_inst = eng_id; + enc1 = kzalloc(sizeof(struct dcn10_stream_encoder), GFP_KERNEL); vpg = dcn31_vpg_create(ctx, vpg_inst); afmt = dcn31_afmt_create(ctx, afmt_inst); diff --git a/drivers/gpu/drm/amd/display/dc/resource/dcn32/dcn32_resource.c b/drivers/gpu/drm/amd/display/dc/resource/dcn32/dcn32_resource.c index 3965a7f1b64b7d..9cace432ce3642 100644 --- a/drivers/gpu/drm/amd/display/dc/resource/dcn32/dcn32_resource.c +++ b/drivers/gpu/drm/amd/display/dc/resource/dcn32/dcn32_resource.c @@ -1208,12 +1208,12 @@ static struct stream_encoder *dcn32_stream_encoder_create( int afmt_inst; /* Mapping of VPG, AFMT, DME register blocks to DIO block instance */ - if (eng_id <= ENGINE_ID_DIGF) { - vpg_inst = eng_id; - afmt_inst = eng_id; - } else + if (eng_id < 0 || eng_id >= ARRAY_SIZE(stream_enc_regs)) return NULL; + vpg_inst = eng_id; + afmt_inst = eng_id; + enc1 = kzalloc(sizeof(struct dcn10_stream_encoder), GFP_KERNEL); vpg = dcn32_vpg_create(ctx, vpg_inst); afmt = dcn32_afmt_create(ctx, afmt_inst); diff --git a/drivers/gpu/drm/amd/display/dc/resource/dcn321/dcn321_resource.c b/drivers/gpu/drm/amd/display/dc/resource/dcn321/dcn321_resource.c index ad214986f7ac71..26fd5c03c01475 100644 --- a/drivers/gpu/drm/amd/display/dc/resource/dcn321/dcn321_resource.c +++ b/drivers/gpu/drm/amd/display/dc/resource/dcn321/dcn321_resource.c @@ -1189,12 +1189,12 @@ static struct stream_encoder *dcn321_stream_encoder_create( int afmt_inst; /* Mapping of VPG, AFMT, DME register blocks to DIO block instance */ - if (eng_id <= ENGINE_ID_DIGF) { - vpg_inst = eng_id; - afmt_inst = eng_id; - } else + if (eng_id < 0 || eng_id >= ARRAY_SIZE(stream_enc_regs)) return NULL; + vpg_inst = eng_id; + afmt_inst = eng_id; + enc1 = kzalloc(sizeof(struct dcn10_stream_encoder), GFP_KERNEL); vpg = dcn321_vpg_create(ctx, vpg_inst); afmt = dcn321_afmt_create(ctx, afmt_inst); diff --git a/drivers/gpu/drm/amd/display/dc/resource/dcn35/dcn35_resource.c b/drivers/gpu/drm/amd/display/dc/resource/dcn35/dcn35_resource.c index 06bec7dcc7556b..e8d74ceb9dc290 100644 --- a/drivers/gpu/drm/amd/display/dc/resource/dcn35/dcn35_resource.c +++ b/drivers/gpu/drm/amd/display/dc/resource/dcn35/dcn35_resource.c @@ -1271,12 +1271,12 @@ static struct stream_encoder *dcn35_stream_encoder_create( int afmt_inst; /* Mapping of VPG, AFMT, DME register blocks to DIO block instance */ - if (eng_id <= ENGINE_ID_DIGF) { - vpg_inst = eng_id; - afmt_inst = eng_id; - } else + if (eng_id < 0 || eng_id >= ARRAY_SIZE(stream_enc_regs)) return NULL; + vpg_inst = eng_id; + afmt_inst = eng_id; + enc1 = kzalloc(sizeof(struct dcn10_stream_encoder), GFP_KERNEL); vpg = dcn31_vpg_create(ctx, vpg_inst); afmt = dcn31_afmt_create(ctx, afmt_inst); diff --git a/drivers/gpu/drm/amd/display/dc/resource/dcn351/dcn351_resource.c b/drivers/gpu/drm/amd/display/dc/resource/dcn351/dcn351_resource.c index 7974e306126e06..532e5d9bc43378 100644 --- a/drivers/gpu/drm/amd/display/dc/resource/dcn351/dcn351_resource.c +++ b/drivers/gpu/drm/amd/display/dc/resource/dcn351/dcn351_resource.c @@ -1251,12 +1251,12 @@ static struct stream_encoder *dcn35_stream_encoder_create( int afmt_inst; /* Mapping of VPG, AFMT, DME register blocks to DIO block instance */ - if (eng_id <= ENGINE_ID_DIGF) { - vpg_inst = eng_id; - afmt_inst = eng_id; - } else + if (eng_id < 0 || eng_id >= ARRAY_SIZE(stream_enc_regs)) return NULL; + vpg_inst = eng_id; + afmt_inst = eng_id; + enc1 = kzalloc(sizeof(struct dcn10_stream_encoder), GFP_KERNEL); vpg = dcn31_vpg_create(ctx, vpg_inst); afmt = dcn31_afmt_create(ctx, afmt_inst); From 0f93a80eb3fd596ddc5730d05e0e8c88e1aa2891 Mon Sep 17 00:00:00 2001 From: Felix Gu Date: Thu, 12 Feb 2026 20:41:40 +0800 Subject: [PATCH 3843/3902] spi: wpcm-fiu: Fix potential NULL pointer dereference in wpcm_fiu_probe() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [ Upstream commit 888a0a802c467bbe34a42167bdf9d7331333440a ] platform_get_resource_byname() can return NULL, which would cause a crash when passed the pointer to resource_size(). Move the fiu->memory_size assignment after the error check for devm_ioremap_resource() to prevent the potential NULL pointer dereference. Fixes: 9838c182471e ("spi: wpcm-fiu: Add direct map support") Signed-off-by: Felix Gu Reviewed-by: J. Neuschäfer Link: https://patch.msgid.link/20260212-wpcm-v1-1-5b7c4f526aac@gmail.com Signed-off-by: Mark Brown Signed-off-by: Sasha Levin --- drivers/spi/spi-wpcm-fiu.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/spi/spi-wpcm-fiu.c b/drivers/spi/spi-wpcm-fiu.c index a9aee2a6c7dcbc..c47b56f0933f14 100644 --- a/drivers/spi/spi-wpcm-fiu.c +++ b/drivers/spi/spi-wpcm-fiu.c @@ -459,11 +459,11 @@ static int wpcm_fiu_probe(struct platform_device *pdev) res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "memory"); fiu->memory = devm_ioremap_resource(dev, res); - fiu->memory_size = min_t(size_t, resource_size(res), MAX_MEMORY_SIZE_TOTAL); if (IS_ERR(fiu->memory)) return dev_err_probe(dev, PTR_ERR(fiu->memory), "Failed to map flash memory window\n"); + fiu->memory_size = min_t(size_t, resource_size(res), MAX_MEMORY_SIZE_TOTAL); fiu->shm_regmap = syscon_regmap_lookup_by_phandle_optional(dev->of_node, "nuvoton,shm"); wpcm_fiu_hw_init(fiu); From 0c42190a2f2690da43b42ebc9571ff9fd5381870 Mon Sep 17 00:00:00 2001 From: Alexander Egorenkov Date: Mon, 16 Feb 2026 07:29:16 +0100 Subject: [PATCH 3844/3902] s390/kexec: Make KEXEC_SIG available when CONFIG_MODULES=n [ Upstream commit dd3411959b57df6e05a3ccbac67b0a836871c0c4 ] The commit c8424e776b09 ("MODSIGN: Export module signature definitions") replaced the dependency of KEXEC_SIG on SYSTEM_DATA_VERIFICATION with the dependency on MODULE_SIG_FORMAT. This change disables KEXEC_SIG in s390 kernels built with MODULES=n if nothing else selects MODULE_SIG_FORMAT. Furthermore, the signature verification in s390 kexec does not require MODULE_SIG_FORMAT because it requires only the struct module_signature and, therefore, does not depend on code in kernel/module_signature.c. But making ARCH_SUPPORTS_KEXEC_SIG depend on SYSTEM_DATA_VERIFICATION is also incorrect because it makes KEXEC_SIG available on s390 only if some other arbitrary option (for instance a file system or device driver) selects it directly or indirectly. To properly make KEXEC_SIG available for s390 kernels built with MODULES=y as well as MODULES=n _and_ also not depend on arbitrary options selecting SYSTEM_DATA_VERIFICATION, set ARCH_SUPPORTS_KEXEC_SIG=y for s390 and select SYSTEM_DATA_VERIFICATION when KEXEC_SIG=y. Fixes: c8424e776b09 ("MODSIGN: Export module signature definitions") Suggested-by: Heiko Carstens Signed-off-by: Alexander Egorenkov Signed-off-by: Heiko Carstens Signed-off-by: Sasha Levin --- arch/s390/Kconfig | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/arch/s390/Kconfig b/arch/s390/Kconfig index df22b10d91415e..e60d2b823e0916 100644 --- a/arch/s390/Kconfig +++ b/arch/s390/Kconfig @@ -270,6 +270,7 @@ config S390 select SPARSE_IRQ select SWIOTLB select SYSCTL_EXCEPTION_TRACE + select SYSTEM_DATA_VERIFICATION if KEXEC_SIG select THREAD_INFO_IN_TASK select TRACE_IRQFLAGS_SUPPORT select TTY @@ -296,7 +297,7 @@ config ARCH_SUPPORTS_KEXEC_FILE def_bool y config ARCH_SUPPORTS_KEXEC_SIG - def_bool MODULE_SIG_FORMAT + def_bool y config ARCH_SUPPORTS_KEXEC_PURGATORY def_bool y From 5ba475d1f3419365892aab6645cfaad0d3b52c3f Mon Sep 17 00:00:00 2001 From: Michal Wajdeczko Date: Tue, 3 Feb 2026 20:37:45 +0100 Subject: [PATCH 3845/3902] drm/xe/configfs: Fix 'parameter name omitted' errors [ Upstream commit 2a673fb4d787ce6672862cb693112378bff86abb ] On some configs and old compilers we can get following build errors: ../drivers/gpu/drm/xe/xe_configfs.h: In function 'xe_configfs_get_ctx_restore_mid_bb': ../drivers/gpu/drm/xe/xe_configfs.h:40:76: error: parameter name omitted static inline u32 xe_configfs_get_ctx_restore_mid_bb(struct pci_dev *pdev, enum xe_engine_class, ^~~~~~~~~~~~~~~~~~~~ ../drivers/gpu/drm/xe/xe_configfs.h: In function 'xe_configfs_get_ctx_restore_post_bb': ../drivers/gpu/drm/xe/xe_configfs.h:42:77: error: parameter name omitted static inline u32 xe_configfs_get_ctx_restore_post_bb(struct pci_dev *pdev, enum xe_engine_class, ^~~~~~~~~~~~~~~~~~~~ when trying to define our configfs stub functions. Fix that. Fixes: 7a4756b2fd04 ("drm/xe/lrc: Allow to add user commands mid context switch") Signed-off-by: Michal Wajdeczko Cc: Rodrigo Vivi Reviewed-by: Rodrigo Vivi Reviewed-by: Shuicheng Lin Link: https://patch.msgid.link/20260203193745.576-1-michal.wajdeczko@intel.com (cherry picked from commit f59cde8a2452b392115d2af8f1143a94725f4827) Signed-off-by: Rodrigo Vivi Signed-off-by: Sasha Levin --- drivers/gpu/drm/xe/xe_configfs.h | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/drivers/gpu/drm/xe/xe_configfs.h b/drivers/gpu/drm/xe/xe_configfs.h index c61e0e47ed94c1..08cce375ae0f37 100644 --- a/drivers/gpu/drm/xe/xe_configfs.h +++ b/drivers/gpu/drm/xe/xe_configfs.h @@ -19,9 +19,11 @@ void xe_configfs_check_device(struct pci_dev *pdev); bool xe_configfs_get_survivability_mode(struct pci_dev *pdev); u64 xe_configfs_get_engines_allowed(struct pci_dev *pdev); bool xe_configfs_get_psmi_enabled(struct pci_dev *pdev); -u32 xe_configfs_get_ctx_restore_mid_bb(struct pci_dev *pdev, enum xe_engine_class, +u32 xe_configfs_get_ctx_restore_mid_bb(struct pci_dev *pdev, + enum xe_engine_class class, const u32 **cs); -u32 xe_configfs_get_ctx_restore_post_bb(struct pci_dev *pdev, enum xe_engine_class, +u32 xe_configfs_get_ctx_restore_post_bb(struct pci_dev *pdev, + enum xe_engine_class class, const u32 **cs); #else static inline int xe_configfs_init(void) { return 0; } @@ -30,9 +32,11 @@ static inline void xe_configfs_check_device(struct pci_dev *pdev) { } static inline bool xe_configfs_get_survivability_mode(struct pci_dev *pdev) { return false; } static inline u64 xe_configfs_get_engines_allowed(struct pci_dev *pdev) { return U64_MAX; } static inline bool xe_configfs_get_psmi_enabled(struct pci_dev *pdev) { return false; } -static inline u32 xe_configfs_get_ctx_restore_mid_bb(struct pci_dev *pdev, enum xe_engine_class, +static inline u32 xe_configfs_get_ctx_restore_mid_bb(struct pci_dev *pdev, + enum xe_engine_class class, const u32 **cs) { return 0; } -static inline u32 xe_configfs_get_ctx_restore_post_bb(struct pci_dev *pdev, enum xe_engine_class, +static inline u32 xe_configfs_get_ctx_restore_post_bb(struct pci_dev *pdev, + enum xe_engine_class class, const u32 **cs) { return 0; } #endif From ef074e9bee05559ee9d601c8287025ffcaed640d Mon Sep 17 00:00:00 2001 From: Shuicheng Lin Date: Fri, 30 Jan 2026 16:56:22 +0000 Subject: [PATCH 3846/3902] drm/xe/mmio: Avoid double-adjust in 64-bit reads [ Upstream commit 4a9b4e1fa52a6aaa1adbb7f759048df14afed54c ] xe_mmio_read64_2x32() was adjusting register addresses and then calling xe_mmio_read32(), which applies the adjustment again. This may shift accesses twice if adj_offset < adj_limit. There is no issue currently, as for media gt, adj_offset > adj_limit, so the 2nd adjust will be a no-op. But it may not work in future. To fix it, replace the adjusted-address comparison with a direct sanity check that ensures the MMIO address adjustment cutoff never falls within the 8-byte range of a 64-bit register. And let xe_mmio_read32() handle address translation. v2: rewrite the sanity check in a more natural way. (Matt) v3: Add Fixes tag. (Jani) Fixes: 07431945d8ae ("drm/xe: Avoid 64-bit register reads") Reviewed-by: Matt Roper Cc: Jani Nikula Cc: Rodrigo Vivi Signed-off-by: Shuicheng Lin Link: https://patch.msgid.link/20260130165621.471408-2-shuicheng.lin@intel.com Signed-off-by: Matt Roper (cherry picked from commit a30f999681126b128a43137793ac84b6a5b7443f) Signed-off-by: Rodrigo Vivi Signed-off-by: Sasha Levin --- drivers/gpu/drm/xe/xe_mmio.c | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/drivers/gpu/drm/xe/xe_mmio.c b/drivers/gpu/drm/xe/xe_mmio.c index ef6f3ea573a2cd..6752881af093ab 100644 --- a/drivers/gpu/drm/xe/xe_mmio.c +++ b/drivers/gpu/drm/xe/xe_mmio.c @@ -260,11 +260,11 @@ u64 xe_mmio_read64_2x32(struct xe_mmio *mmio, struct xe_reg reg) struct xe_reg reg_udw = { .addr = reg.addr + 0x4 }; u32 ldw, udw, oldudw, retries; - reg.addr = xe_mmio_adjusted_addr(mmio, reg.addr); - reg_udw.addr = xe_mmio_adjusted_addr(mmio, reg_udw.addr); - - /* we shouldn't adjust just one register address */ - xe_tile_assert(mmio->tile, reg_udw.addr == reg.addr + 0x4); + /* + * The two dwords of a 64-bit register can never straddle the offset + * adjustment cutoff. + */ + xe_tile_assert(mmio->tile, !in_range(mmio->adj_limit, reg.addr + 1, 7)); oldudw = xe_mmio_read32(mmio, reg_udw); for (retries = 5; retries; --retries) { From 940b8daa52906e94924c4878484e3ad6a33874e6 Mon Sep 17 00:00:00 2001 From: Matt Roper Date: Thu, 5 Feb 2026 14:05:09 -0800 Subject: [PATCH 3847/3902] drm/xe/xe2_hpg: Fix handling of Wa_14019988906 & Wa_14019877138 [ Upstream commit bc6387a2e0c1562faa56ce2a98cef50cab809e08 ] The PSS_CHICKEN register has been part of the RCS engine's LRC since it was first introduced in Xe_LP. That means that any workarounds that adjust its value (such as Wa_14019988906 and Wa_14019877138) need to be implemented in the lrc_was[] table so that they become part of the default LRC from which all subsequent LRCs are copied. Although these workarounds were implemented correctly on most platforms, they were incorrectly placed on the engine_was[] table for Xe2_HPG. Move the workarounds to the proper lrc_was[] table and switch the 'xe_rtp_match_first_render_or_compute' rule to specifically match the RCS since that's the engine whose LRC manages the register. Bspec: 65182 Fixes: 7f3ee7d88058 ("drm/xe/xe2hpg: Add initial GT workarounds") Reviewed-by: Shekhar Chauhan Link: https://patch.msgid.link/20260205220508.51905-2-matthew.d.roper@intel.com Signed-off-by: Matt Roper (cherry picked from commit e04c609eedf4d6748ac0bcada4de1275b034fed6) Signed-off-by: Rodrigo Vivi Signed-off-by: Sasha Levin --- drivers/gpu/drm/xe/xe_wa.c | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) diff --git a/drivers/gpu/drm/xe/xe_wa.c b/drivers/gpu/drm/xe/xe_wa.c index d209434fd7fc21..2a2e9f2c091631 100644 --- a/drivers/gpu/drm/xe/xe_wa.c +++ b/drivers/gpu/drm/xe/xe_wa.c @@ -567,16 +567,6 @@ static const struct xe_rtp_entry_sr engine_was[] = { FUNC(xe_rtp_match_first_render_or_compute)), XE_RTP_ACTIONS(SET(ROW_CHICKEN, EARLY_EOT_DIS)) }, - { XE_RTP_NAME("14019988906"), - XE_RTP_RULES(GRAPHICS_VERSION_RANGE(2001, 2002), - FUNC(xe_rtp_match_first_render_or_compute)), - XE_RTP_ACTIONS(SET(XEHP_PSS_CHICKEN, FLSH_IGNORES_PSD)) - }, - { XE_RTP_NAME("14019877138"), - XE_RTP_RULES(GRAPHICS_VERSION_RANGE(2001, 2002), - FUNC(xe_rtp_match_first_render_or_compute)), - XE_RTP_ACTIONS(SET(XEHP_PSS_CHICKEN, FD_END_COLLECT)) - }, { XE_RTP_NAME("14020338487"), XE_RTP_RULES(GRAPHICS_VERSION_RANGE(2001, 2002), FUNC(xe_rtp_match_first_render_or_compute)), @@ -873,6 +863,14 @@ static const struct xe_rtp_entry_sr lrc_was[] = { XE_RTP_RULES(GRAPHICS_VERSION(2001), ENGINE_CLASS(RENDER)), XE_RTP_ACTIONS(SET(WM_CHICKEN3, HIZ_PLANE_COMPRESSION_DIS)) }, + { XE_RTP_NAME("14019988906"), + XE_RTP_RULES(GRAPHICS_VERSION_RANGE(2001, 2002), ENGINE_CLASS(RENDER)), + XE_RTP_ACTIONS(SET(XEHP_PSS_CHICKEN, FLSH_IGNORES_PSD)) + }, + { XE_RTP_NAME("14019877138"), + XE_RTP_RULES(GRAPHICS_VERSION_RANGE(2001, 2002), ENGINE_CLASS(RENDER)), + XE_RTP_ACTIONS(SET(XEHP_PSS_CHICKEN, FD_END_COLLECT)) + }, { XE_RTP_NAME("14021490052"), XE_RTP_RULES(GRAPHICS_VERSION(2001), ENGINE_CLASS(RENDER)), XE_RTP_ACTIONS(SET(FF_MODE, From ed945276a76e9054a45ac75653cae466ad440f17 Mon Sep 17 00:00:00 2001 From: Shuicheng Lin Date: Mon, 2 Feb 2026 18:18:54 +0000 Subject: [PATCH 3848/3902] drm/xe: Make xe_modparam.force_vram_bar_size signed [ Upstream commit 1acec6ef0511b92e7974cc5a8768bfd3a659feaf ] vram_bar_size is registered as an int module parameter and is documented to accept negative values to disable BAR resizing. Store it as an int in xe_modparam as well, so negative values work as intended and the module_param type matches. Fixes: 80742a1aa26e ("drm/xe: Allow to drop vram resizing") Reviewed-by: Michal Wajdeczko Signed-off-by: Shuicheng Lin Link: https://patch.msgid.link/20260202181853.1095736-2-shuicheng.lin@intel.com Signed-off-by: Matt Roper (cherry picked from commit 25c9aa4dcb5ef2ad9f354d19f8f1eeb690d1c161) Signed-off-by: Rodrigo Vivi Signed-off-by: Sasha Levin --- drivers/gpu/drm/xe/xe_module.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/gpu/drm/xe/xe_module.h b/drivers/gpu/drm/xe/xe_module.h index 5a3bfea8b7b4c4..b6684953927016 100644 --- a/drivers/gpu/drm/xe/xe_module.h +++ b/drivers/gpu/drm/xe/xe_module.h @@ -12,7 +12,7 @@ struct xe_modparam { bool force_execlist; bool probe_display; - u32 force_vram_bar_size; + int force_vram_bar_size; int guc_log_level; char *guc_firmware_path; char *huc_firmware_path; From 778f326af79f5028aa67938f6d9c2c79fcf94418 Mon Sep 17 00:00:00 2001 From: Raag Jadav Date: Thu, 12 Feb 2026 11:26:22 +0530 Subject: [PATCH 3849/3902] drm/xe/bo: Redirect faults to dummy page for wedged device [ Upstream commit 4e83a8d58e1c721a89b3ffe15f549007080272e2 ] As per uapi documentation[1], the prerequisite for wedged device is to redirected page faults to a dummy page. Follow it. [1] Documentation/gpu/drm-uapi.rst v2: Add uapi reference and fixes tag (Matthew Brost) Fixes: 7bc00751f877 ("drm/xe: Use device wedged event") Signed-off-by: Raag Jadav Reviewed-by: Matthew Brost Link: https://patch.msgid.link/20260212055622.2054991-1-raag.jadav@intel.com Signed-off-by: Matt Roper (cherry picked from commit c020fff70d757612933711dd3cc3751d7d782d3c) Signed-off-by: Rodrigo Vivi Signed-off-by: Sasha Levin --- drivers/gpu/drm/xe/xe_bo.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/gpu/drm/xe/xe_bo.c b/drivers/gpu/drm/xe/xe_bo.c index e2e28ff73925be..a270aef7c49807 100644 --- a/drivers/gpu/drm/xe/xe_bo.c +++ b/drivers/gpu/drm/xe/xe_bo.c @@ -1895,7 +1895,7 @@ static vm_fault_t xe_bo_cpu_fault(struct vm_fault *vmf) int err = 0; int idx; - if (!drm_dev_enter(&xe->drm, &idx)) + if (xe_device_wedged(xe) || !drm_dev_enter(&xe->drm, &idx)) return ttm_bo_vm_dummy_page(vmf, vmf->vma->vm_page_prot); ret = xe_bo_cpu_fault_fastpath(vmf, xe, bo, needs_rpm); From 7098d3743105f7d522c5bb6c9763e30028039b26 Mon Sep 17 00:00:00 2001 From: Dmitry Torokhov Date: Tue, 17 Feb 2026 14:11:49 -0800 Subject: [PATCH 3850/3902] gpio: amd-fch: ionly return allowed values from amd_fch_gpio_get() [ Upstream commit fbd03587ba732c612b8a569d1cf5bed72bd3a27c ] As of 86ef402d805d ("gpiolib: sanitize the return value of gpio_chip::get()") gpiolib requires drivers implementing GPIOs to only return 0, 1 or negative error for the get() callbacks. Ensure that amd-fch complies with this requirement. Fixes: 86ef402d805d ("gpiolib: sanitize the return value of gpio_chip::get()") Reported-and-tested-by: Tj Signed-off-by: Dmitry Torokhov Link: https://patch.msgid.link/aZTlwnvHt2Gho4yN@google.com Signed-off-by: Bartosz Golaszewski Signed-off-by: Sasha Levin --- drivers/gpio/gpio-amd-fch.c | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/drivers/gpio/gpio-amd-fch.c b/drivers/gpio/gpio-amd-fch.c index e6c6c3ec7656e9..9f329938202bff 100644 --- a/drivers/gpio/gpio-amd-fch.c +++ b/drivers/gpio/gpio-amd-fch.c @@ -8,6 +8,7 @@ * */ +#include #include #include #include @@ -120,15 +121,15 @@ static int amd_fch_gpio_get(struct gpio_chip *gc, unsigned int offset) { unsigned long flags; - int ret; + u32 val; struct amd_fch_gpio_priv *priv = gpiochip_get_data(gc); void __iomem *ptr = amd_fch_gpio_addr(priv, offset); spin_lock_irqsave(&priv->lock, flags); - ret = (readl_relaxed(ptr) & AMD_FCH_GPIO_FLAG_READ); + val = readl_relaxed(ptr); spin_unlock_irqrestore(&priv->lock, flags); - return ret; + return FIELD_GET(AMD_FCH_GPIO_FLAG_READ, val); } static int amd_fch_gpio_request(struct gpio_chip *chip, From 9b18bf59977f5c5bc3b11b210520f62500a7adf3 Mon Sep 17 00:00:00 2001 From: "Kiryl Shutsemau (Meta)" Date: Tue, 17 Feb 2026 10:49:56 +0000 Subject: [PATCH 3851/3902] efi: Fix reservation of unaccepted memory table [ Upstream commit 0862438c90487e79822d5647f854977d50381505 ] The reserve_unaccepted() function incorrectly calculates the size of the memblock reservation for the unaccepted memory table. It aligns the size of the table, but fails to account for cases where the table's starting physical address (efi.unaccepted) is not page-aligned. If the table starts at an offset within a page and its end crosses into a subsequent page that the aligned size does not cover, the end of the table will not be reserved. This can lead to the table being overwritten or inaccessible, causing a kernel panic in accept_memory(). This issue was observed when starting Intel TDX VMs with specific memory sizes (e.g., > 64GB). Fix this by calculating the end address first (including the unaligned start) and then aligning it up, ensuring the entire range is covered by the reservation. Fixes: 8dbe33956d96 ("efi/unaccepted: Make sure unaccepted table is mapped") Reported-by: Moritz Sanft Signed-off-by: Kiryl Shutsemau (Meta) Reviewed-by: Tom Lendacky Acked-by: Mike Rapoport (Microsoft) Signed-off-by: Ard Biesheuvel Signed-off-by: Sasha Levin --- drivers/firmware/efi/efi.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/drivers/firmware/efi/efi.c b/drivers/firmware/efi/efi.c index fc407d891348f0..c3cf5541ed682f 100644 --- a/drivers/firmware/efi/efi.c +++ b/drivers/firmware/efi/efi.c @@ -691,13 +691,13 @@ static __init int match_config_table(const efi_guid_t *guid, static __init void reserve_unaccepted(struct efi_unaccepted_memory *unaccepted) { - phys_addr_t start, size; + phys_addr_t start, end; start = PAGE_ALIGN_DOWN(efi.unaccepted); - size = PAGE_ALIGN(sizeof(*unaccepted) + unaccepted->size); + end = PAGE_ALIGN(efi.unaccepted + sizeof(*unaccepted) + unaccepted->size); - memblock_add(start, size); - memblock_reserve(start, size); + memblock_add(start, end - start); + memblock_reserve(start, end - start); } int __init efi_config_parse_tables(const efi_config_table_t *config_tables, From 38944f3c500614ef9c0ff35f3f86693557e5ddb9 Mon Sep 17 00:00:00 2001 From: Filipe Manana Date: Mon, 13 Oct 2025 13:57:09 +0100 Subject: [PATCH 3852/3902] btrfs: remove fs_info argument from btrfs_try_granting_tickets() [ Upstream commit e3df6408b13a75cf73e543e53453f28261874c6f ] We don't need it since we can grab fs_info from the given space_info. So remove the fs_info argument. Reviewed-by: Qu Wenruo Reviewed-by: Johannes Thumshirn Reviewed-by: Anand Jain Signed-off-by: Filipe Manana Reviewed-by: David Sterba Signed-off-by: David Sterba Stable-dep-of: 5870ec7c8fe5 ("btrfs: reset block group size class when it becomes empty") Signed-off-by: Sasha Levin --- fs/btrfs/block-group.c | 4 ++-- fs/btrfs/block-rsv.c | 2 +- fs/btrfs/space-info.c | 14 +++++++------- fs/btrfs/space-info.h | 5 ++--- 4 files changed, 12 insertions(+), 13 deletions(-) diff --git a/fs/btrfs/block-group.c b/fs/btrfs/block-group.c index 8bf501fbcc0bef..035b04e7658d16 100644 --- a/fs/btrfs/block-group.c +++ b/fs/btrfs/block-group.c @@ -3836,7 +3836,7 @@ int btrfs_add_reserved_bytes(struct btrfs_block_group *cache, * that happens. */ if (num_bytes < ram_bytes) - btrfs_try_granting_tickets(cache->fs_info, space_info); + btrfs_try_granting_tickets(space_info); out: spin_unlock(&cache->lock); spin_unlock(&space_info->lock); @@ -3874,7 +3874,7 @@ void btrfs_free_reserved_bytes(struct btrfs_block_group *cache, u64 num_bytes, cache->delalloc_bytes -= num_bytes; spin_unlock(&cache->lock); - btrfs_try_granting_tickets(cache->fs_info, space_info); + btrfs_try_granting_tickets(space_info); spin_unlock(&space_info->lock); } diff --git a/fs/btrfs/block-rsv.c b/fs/btrfs/block-rsv.c index 5ad6de738aee01..75cd35570a287b 100644 --- a/fs/btrfs/block-rsv.c +++ b/fs/btrfs/block-rsv.c @@ -387,7 +387,7 @@ void btrfs_update_global_block_rsv(struct btrfs_fs_info *fs_info) num_bytes = block_rsv->reserved - block_rsv->size; btrfs_space_info_update_bytes_may_use(sinfo, -num_bytes); block_rsv->reserved = block_rsv->size; - btrfs_try_granting_tickets(fs_info, sinfo); + btrfs_try_granting_tickets(sinfo); } block_rsv->full = (block_rsv->reserved == block_rsv->size); diff --git a/fs/btrfs/space-info.c b/fs/btrfs/space-info.c index e5c18a29eb7e6e..474ed47095ba7d 100644 --- a/fs/btrfs/space-info.c +++ b/fs/btrfs/space-info.c @@ -378,7 +378,7 @@ void btrfs_add_bg_to_space_info(struct btrfs_fs_info *info, btrfs_space_info_update_bytes_zone_unusable(space_info, block_group->zone_unusable); if (block_group->length > 0) space_info->full = false; - btrfs_try_granting_tickets(info, space_info); + btrfs_try_granting_tickets(space_info); spin_unlock(&space_info->lock); block_group->space_info = space_info; @@ -528,9 +528,9 @@ static void remove_ticket(struct btrfs_space_info *space_info, * This is for space we already have accounted in space_info->bytes_may_use, so * basically when we're returning space from block_rsv's. */ -void btrfs_try_granting_tickets(struct btrfs_fs_info *fs_info, - struct btrfs_space_info *space_info) +void btrfs_try_granting_tickets(struct btrfs_space_info *space_info) { + struct btrfs_fs_info *fs_info = space_info->fs_info; struct list_head *head; enum btrfs_reserve_flush_enum flush = BTRFS_RESERVE_NO_FLUSH; @@ -1129,7 +1129,7 @@ static bool maybe_fail_all_tickets(struct btrfs_fs_info *fs_info, * the list. */ if (!aborted) - btrfs_try_granting_tickets(fs_info, space_info); + btrfs_try_granting_tickets(space_info); } return (tickets_id != space_info->tickets_id); } @@ -1549,7 +1549,7 @@ static void priority_reclaim_metadata_space(struct btrfs_fs_info *fs_info, * ticket in front of a smaller ticket that can now be satisfied with * the available space. */ - btrfs_try_granting_tickets(fs_info, space_info); + btrfs_try_granting_tickets(space_info); spin_unlock(&space_info->lock); } @@ -1577,7 +1577,7 @@ static void priority_reclaim_data_space(struct btrfs_fs_info *fs_info, ticket->error = -ENOSPC; remove_ticket(space_info, ticket); - btrfs_try_granting_tickets(fs_info, space_info); + btrfs_try_granting_tickets(space_info); spin_unlock(&space_info->lock); } @@ -2200,5 +2200,5 @@ void btrfs_return_free_space(struct btrfs_space_info *space_info, u64 len) grant: /* Add to any tickets we may have. */ if (len) - btrfs_try_granting_tickets(fs_info, space_info); + btrfs_try_granting_tickets(space_info); } diff --git a/fs/btrfs/space-info.h b/fs/btrfs/space-info.h index a846f63585c950..596a1e923ddfe9 100644 --- a/fs/btrfs/space-info.h +++ b/fs/btrfs/space-info.h @@ -283,8 +283,7 @@ int btrfs_reserve_metadata_bytes(struct btrfs_fs_info *fs_info, struct btrfs_space_info *space_info, u64 orig_bytes, enum btrfs_reserve_flush_enum flush); -void btrfs_try_granting_tickets(struct btrfs_fs_info *fs_info, - struct btrfs_space_info *space_info); +void btrfs_try_granting_tickets(struct btrfs_space_info *space_info); int btrfs_can_overcommit(struct btrfs_fs_info *fs_info, const struct btrfs_space_info *space_info, u64 bytes, enum btrfs_reserve_flush_enum flush); @@ -295,7 +294,7 @@ static inline void btrfs_space_info_free_bytes_may_use( { spin_lock(&space_info->lock); btrfs_space_info_update_bytes_may_use(space_info, -num_bytes); - btrfs_try_granting_tickets(space_info->fs_info, space_info); + btrfs_try_granting_tickets(space_info); spin_unlock(&space_info->lock); } int btrfs_reserve_data_bytes(struct btrfs_space_info *space_info, u64 bytes, From 74fcfce2a5eeef97e42cbf4cc693afac42e4fba6 Mon Sep 17 00:00:00 2001 From: Filipe Manana Date: Mon, 20 Oct 2025 12:47:26 +0100 Subject: [PATCH 3853/3902] btrfs: reduce block group critical section in btrfs_free_reserved_bytes() [ Upstream commit 8b6fa164ab59f9e3f24e627fe09a0234783e7a8b ] There's no need to update the space_info fields (bytes_reserved, max_extent_size, bytes_readonly, bytes_zone_unusable) while holding the block group's spinlock. So move those updates to happen after we unlock the block group (and while holding the space_info locked of course), so that all we do under the block group's critical section is to update the block group itself. Reviewed-by: Johannes Thumshirn Signed-off-by: Filipe Manana Reviewed-by: David Sterba Signed-off-by: David Sterba Stable-dep-of: 5870ec7c8fe5 ("btrfs: reset block group size class when it becomes empty") Signed-off-by: Sasha Levin --- fs/btrfs/block-group.c | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/fs/btrfs/block-group.c b/fs/btrfs/block-group.c index 035b04e7658d16..144868f02e2aaf 100644 --- a/fs/btrfs/block-group.c +++ b/fs/btrfs/block-group.c @@ -3859,21 +3859,24 @@ void btrfs_free_reserved_bytes(struct btrfs_block_group *cache, u64 num_bytes, bool is_delalloc) { struct btrfs_space_info *space_info = cache->space_info; + bool bg_ro; spin_lock(&space_info->lock); spin_lock(&cache->lock); - if (cache->ro) + bg_ro = cache->ro; + cache->reserved -= num_bytes; + if (is_delalloc) + cache->delalloc_bytes -= num_bytes; + spin_unlock(&cache->lock); + + if (bg_ro) space_info->bytes_readonly += num_bytes; else if (btrfs_is_zoned(cache->fs_info)) space_info->bytes_zone_unusable += num_bytes; - cache->reserved -= num_bytes; + space_info->bytes_reserved -= num_bytes; space_info->max_extent_size = 0; - if (is_delalloc) - cache->delalloc_bytes -= num_bytes; - spin_unlock(&cache->lock); - btrfs_try_granting_tickets(space_info); spin_unlock(&space_info->lock); } From 55d02a533a8aef4d395fc8a0d000adb72b6bfaf8 Mon Sep 17 00:00:00 2001 From: Jiasheng Jiang Date: Wed, 14 Jan 2026 01:13:38 +0000 Subject: [PATCH 3854/3902] btrfs: reset block group size class when it becomes empty [ Upstream commit 5870ec7c8fe57a8b2c65005e5da5efc054faa3e6 ] Block group size classes are managed consistently everywhere. Currently, btrfs_use_block_group_size_class() sets a block group's size class to specialize it for a specific allocation size. However, this size class remains "stale" even if the block group becomes completely empty (both used and reserved bytes reach zero). This happens in two scenarios: 1. When space reservations are freed (e.g., due to errors or transaction aborts) via btrfs_free_reserved_bytes(). 2. When the last extent in a block group is freed via btrfs_update_block_group(). While size classes are advisory, a stale size class can cause find_free_extent to unnecessarily skip candidate block groups during initial search loops. This undermines the purpose of size classes to reduce fragmentation by keeping block groups restricted to a specific size class when they could be reused for any size. Fix this by resetting the size class to BTRFS_BG_SZ_NONE whenever a block group's used and reserved counts both reach zero. This ensures that empty block groups are fully available for any allocation size in the next cycle. Fixes: 52bb7a2166af ("btrfs: introduce size class to block group allocator") Reviewed-by: Boris Burkov Signed-off-by: Jiasheng Jiang Reviewed-by: David Sterba Signed-off-by: David Sterba Signed-off-by: Sasha Levin --- fs/btrfs/block-group.c | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/fs/btrfs/block-group.c b/fs/btrfs/block-group.c index 144868f02e2aaf..f7f6d8cb33114f 100644 --- a/fs/btrfs/block-group.c +++ b/fs/btrfs/block-group.c @@ -3681,6 +3681,14 @@ int btrfs_write_dirty_block_groups(struct btrfs_trans_handle *trans) return ret; } +static void btrfs_maybe_reset_size_class(struct btrfs_block_group *bg) +{ + lockdep_assert_held(&bg->lock); + if (btrfs_block_group_should_use_size_class(bg) && + bg->used == 0 && bg->reserved == 0) + bg->size_class = BTRFS_BG_SZ_NONE; +} + int btrfs_update_block_group(struct btrfs_trans_handle *trans, u64 bytenr, u64 num_bytes, bool alloc) { @@ -3745,6 +3753,7 @@ int btrfs_update_block_group(struct btrfs_trans_handle *trans, old_val -= num_bytes; cache->used = old_val; cache->pinned += num_bytes; + btrfs_maybe_reset_size_class(cache); btrfs_space_info_update_bytes_pinned(space_info, num_bytes); space_info->bytes_used -= num_bytes; space_info->disk_used -= num_bytes * factor; @@ -3865,6 +3874,7 @@ void btrfs_free_reserved_bytes(struct btrfs_block_group *cache, u64 num_bytes, spin_lock(&cache->lock); bg_ro = cache->ro; cache->reserved -= num_bytes; + btrfs_maybe_reset_size_class(cache); if (is_delalloc) cache->delalloc_bytes -= num_bytes; spin_unlock(&cache->lock); From 6ca11deb6c94423d1feb4184dfedf72a59b4c053 Mon Sep 17 00:00:00 2001 From: Filipe Manana Date: Tue, 3 Feb 2026 18:03:35 +0000 Subject: [PATCH 3855/3902] btrfs: use the correct type to initialize block reserve for delayed refs [ Upstream commit 2155d0c0a761a56ce7ede83a26eb23ea0f935260 ] When initializing the delayed refs block reserve for a transaction handle we are passing a type of BTRFS_BLOCK_RSV_DELOPS, which is meant for delayed items and not for delayed refs. The correct type for delayed refs is BTRFS_BLOCK_RSV_DELREFS. On release of any excess space reserved in a local delayed refs reserve, we also should transfer that excess space to the global block reserve (it it's full, we return to the space info for general availability). By initializing a transaction's local delayed refs block reserve with a type of BTRFS_BLOCK_RSV_DELOPS, we were also causing any excess space released from the delayed block reserve (fs_info->delayed_block_rsv, used for delayed inodes and items) to be transferred to the global block reserve instead of the global delayed refs block reserve. This was an unintentional change in commit 28270e25c69a ("btrfs: always reserve space for delayed refs when starting transaction"), but it's not particularly serious as things tend to cancel out each other most of the time and it's relatively rare to be anywhere near exhaustion of the global reserve. Fix this by initializing a transaction's local delayed refs reserve with a type of BTRFS_BLOCK_RSV_DELREFS and making btrfs_block_rsv_release() attempt to transfer unused space from such a reserve into the global block reserve, just as we did before that commit for when the block reserve is a delayed refs rsv. Reported-by: Alex Lyakas Link: https://lore.kernel.org/linux-btrfs/CAOcd+r0FHG5LWzTSu=LknwSoqxfw+C00gFAW7fuX71+Z5AfEew@mail.gmail.com/ Fixes: 28270e25c69a ("btrfs: always reserve space for delayed refs when starting transaction") Reviewed-by: Alex Lyakas Signed-off-by: Filipe Manana Signed-off-by: David Sterba Signed-off-by: Sasha Levin --- fs/btrfs/block-rsv.c | 7 ++++--- fs/btrfs/transaction.c | 2 +- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/fs/btrfs/block-rsv.c b/fs/btrfs/block-rsv.c index 75cd35570a287b..fc378d2038a2d2 100644 --- a/fs/btrfs/block-rsv.c +++ b/fs/btrfs/block-rsv.c @@ -278,10 +278,11 @@ u64 btrfs_block_rsv_release(struct btrfs_fs_info *fs_info, struct btrfs_block_rsv *target = NULL; /* - * If we are a delayed block reserve then push to the global rsv, - * otherwise dump into the global delayed reserve if it is not full. + * If we are a delayed refs block reserve then push to the global + * reserve, otherwise dump into the global delayed refs reserve if it is + * not full. */ - if (block_rsv->type == BTRFS_BLOCK_RSV_DELOPS) + if (block_rsv->type == BTRFS_BLOCK_RSV_DELREFS) target = global_rsv; else if (block_rsv != global_rsv && !btrfs_block_rsv_full(delayed_rsv)) target = delayed_rsv; diff --git a/fs/btrfs/transaction.c b/fs/btrfs/transaction.c index b537bba7678061..089712b15d603a 100644 --- a/fs/btrfs/transaction.c +++ b/fs/btrfs/transaction.c @@ -726,7 +726,7 @@ start_transaction(struct btrfs_root *root, unsigned int num_items, h->type = type; INIT_LIST_HEAD(&h->new_bgs); - btrfs_init_metadata_block_rsv(fs_info, &h->delayed_rsv, BTRFS_BLOCK_RSV_DELOPS); + btrfs_init_metadata_block_rsv(fs_info, &h->delayed_rsv, BTRFS_BLOCK_RSV_DELREFS); smp_mb(); if (cur_trans->state >= TRANS_STATE_COMMIT_START && From d7cf2314dd5e8661c05d076cd627eea9a7f76616 Mon Sep 17 00:00:00 2001 From: Filipe Manana Date: Wed, 4 Feb 2026 17:15:53 +0000 Subject: [PATCH 3856/3902] btrfs: fix invalid leaf access in btrfs_quota_enable() if ref key not found [ Upstream commit ecb7c2484cfc83a93658907580035a8adf1e0a92 ] If btrfs_search_slot_for_read() returns 1, it means we did not find any key greater than or equals to the key we asked for, meaning we have reached the end of the tree and therefore the path is not valid. If this happens we need to break out of the loop and stop, instead of continuing and accessing an invalid path. Fixes: 5223cc60b40a ("btrfs: drop the path before adding qgroup items when enabling qgroups") Reviewed-by: Qu Wenruo Signed-off-by: Filipe Manana Reviewed-by: David Sterba Signed-off-by: David Sterba Signed-off-by: Sasha Levin --- fs/btrfs/qgroup.c | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/fs/btrfs/qgroup.c b/fs/btrfs/qgroup.c index 7a1dd250e92c09..302bb3ecf39a32 100644 --- a/fs/btrfs/qgroup.c +++ b/fs/btrfs/qgroup.c @@ -1157,11 +1157,14 @@ int btrfs_quota_enable(struct btrfs_fs_info *fs_info, } if (ret > 0) { /* - * Shouldn't happen, but in case it does we - * don't need to do the btrfs_next_item, just - * continue. + * Shouldn't happen because the key should still + * be there (return 0), but in case it does it + * means we have reached the end of the tree - + * there are no more leaves with items that have + * a key greater than or equals to @found_key, + * so just stop the search loop. */ - continue; + break; } } ret = btrfs_next_item(tree_root, path); From 7e92f716a55bf5fcd96d9763fe6b55bb560e7fc0 Mon Sep 17 00:00:00 2001 From: Ethan Tidmore Date: Wed, 18 Feb 2026 13:09:03 -0600 Subject: [PATCH 3857/3902] x86/hyperv: Fix error pointer dereference [ Upstream commit 705d01c8d78121ee1634bfc602ac4b0ad1438fab ] The function idle_thread_get() can return an error pointer and is not checked for it. Add check for error pointer. Detected by Smatch: arch/x86/hyperv/hv_vtl.c:126 hv_vtl_bringup_vcpu() error: 'idle' dereferencing possible ERR_PTR() Fixes: 2b4b90e053a29 ("x86/hyperv: Use per cpu initial stack for vtl context") Signed-off-by: Ethan Tidmore Signed-off-by: Wei Liu Signed-off-by: Sasha Levin --- arch/x86/hyperv/hv_vtl.c | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/arch/x86/hyperv/hv_vtl.c b/arch/x86/hyperv/hv_vtl.c index 042e8712d8dea6..8aafccf7a52c70 100644 --- a/arch/x86/hyperv/hv_vtl.c +++ b/arch/x86/hyperv/hv_vtl.c @@ -105,7 +105,7 @@ static void hv_vtl_ap_entry(void) static int hv_vtl_bringup_vcpu(u32 target_vp_index, int cpu, u64 eip_ignored) { - u64 status; + u64 status, rsp, rip; int ret = 0; struct hv_enable_vp_vtl *input; unsigned long irq_flags; @@ -118,9 +118,11 @@ static int hv_vtl_bringup_vcpu(u32 target_vp_index, int cpu, u64 eip_ignored) struct desc_struct *gdt; struct task_struct *idle = idle_thread_get(cpu); - u64 rsp = (unsigned long)idle->thread.sp; + if (IS_ERR(idle)) + return PTR_ERR(idle); - u64 rip = (u64)&hv_vtl_ap_entry; + rsp = (unsigned long)idle->thread.sp; + rip = (u64)&hv_vtl_ap_entry; native_store_gdt(&gdt_ptr); store_idt(&idt_ptr); From 9fbb4f14462f0830516694dc7ea7be5d70e6ca3b Mon Sep 17 00:00:00 2001 From: Detlev Casanova Date: Wed, 18 Feb 2026 15:18:34 -0500 Subject: [PATCH 3858/3902] ASoC: rockchip: i2s-tdm: Use param rate if not provided by set_sysclk [ Upstream commit 0783052534f547f8f201dd4554b1df9f1f8615b5 ] Drivers will not always call set_sysclk() for all clocks, especially when default mclk-fs can be used. When that is the case, use the clock rate set in the params multiplied by the default mclk-fs. Fixes: 5323186e2e8d ("ASoC: rockchip: i2s_tdm: Re-add the set_sysclk callback") Signed-off-by: Detlev Casanova Reported-by: Luca Ceresoli Link: https://patch.msgid.link/20260218201834.924358-1-detlev.casanova@collabora.com Signed-off-by: Mark Brown Signed-off-by: Sasha Levin --- sound/soc/rockchip/rockchip_i2s_tdm.c | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/sound/soc/rockchip/rockchip_i2s_tdm.c b/sound/soc/rockchip/rockchip_i2s_tdm.c index d9a1fab7f40310..e4697ee0addc87 100644 --- a/sound/soc/rockchip/rockchip_i2s_tdm.c +++ b/sound/soc/rockchip/rockchip_i2s_tdm.c @@ -22,6 +22,7 @@ #define DRV_NAME "rockchip-i2s-tdm" +#define DEFAULT_MCLK_FS 256 #define CH_GRP_MAX 4 /* The max channel 8 / 2 */ #define MULTIPLEX_CH_MAX 10 @@ -665,6 +666,15 @@ static int rockchip_i2s_tdm_hw_params(struct snd_pcm_substream *substream, mclk_rate = i2s_tdm->mclk_rx_freq; } + /* + * When the dai/component driver doesn't need to set mclk-fs for a specific + * clock, it can skip the call to set_sysclk() for that clock. + * In that case, simply use the clock rate from the params and multiply it by + * the default mclk-fs value. + */ + if (!mclk_rate) + mclk_rate = DEFAULT_MCLK_FS * params_rate(params); + err = clk_set_rate(mclk, mclk_rate); if (err) return err; From 557f65399133f5be3ad3215f9dbd6caba6b243ca Mon Sep 17 00:00:00 2001 From: Mario Kleiner Date: Fri, 6 Feb 2026 23:38:28 +0100 Subject: [PATCH 3859/3902] drm/amd/display: Use same max plane scaling limits for all 64 bpp formats [ Upstream commit f0157ce46cf0e5e2257e19d590c9b16036ce26d4 ] The plane scaling hw seems to have the same min/max plane scaling limits for all 16 bpc / 64 bpp interleaved pixel color formats. Therefore add cases to amdgpu_dm_plane_get_min_max_dc_plane_scaling() for all the 16 bpc fixed-point / unorm formats to use the same .fp16 up/downscaling factor limits as used by the fp16 floating point formats. So far, 16 bpc unorm formats were not handled, and the default: path returned max/min factors for 32 bpp argb8888 formats, which were wrong and bigger than what many DCE / DCN hw generations could handle. The result sometimes was misscaling of framebuffers with DRM_FORMAT_XRGB16161616, DRM_FORMAT_ARGB16161616, DRM_FORMAT_XBGR16161616, DRM_FORMAT_ABGR16161616, leading to very wrong looking display, as tested on Polaris11 / DCE-11.2. So far this went unnoticed, because only few userspace clients used such 16 bpc unorm framebuffers, and those didn't use hw plane scaling, so they did not experience this issue. With upcoming Mesa 26 exposing 16 bpc unorm formats under both OpenGL and Vulkan under Wayland, and the upcoming GNOME 50 Mutter Wayland compositor allowing for direct scanout of these formats, the scaling hw will be used on these formats if possible for HiDPI display scaling, so it is important to use the correct hw scaling limits to avoid wrong display. Tested on AMD Polaris 11 / DCE 11.2 with upcoming Mesa 26 and GNOME 50 on HiDPI displays with scaling enabled. The mutter Wayland compositor now correctly falls back to scaling via desktop compositing instead of direct scanout, thereby avoiding wrong image display. For unscaled mode, it correctly uses direct scanout. Fixes: 580204038f5b ("drm/amd/display: Enable support for 16 bpc fixed-point framebuffers.") Signed-off-by: Mario Kleiner Tested-by: Mario Kleiner Cc: Alex Deucher Cc: Harry Wentland Cc: Leo Li Signed-off-by: Alex Deucher Signed-off-by: Sasha Levin --- drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_plane.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_plane.c b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_plane.c index e027798ece0322..9bb7475e80bad6 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_plane.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_plane.c @@ -1059,10 +1059,15 @@ static void amdgpu_dm_plane_get_min_max_dc_plane_scaling(struct drm_device *dev, *min_downscale = plane_cap->max_downscale_factor.nv12; break; + /* All 64 bpp formats have the same fp16 scaling limits */ case DRM_FORMAT_XRGB16161616F: case DRM_FORMAT_ARGB16161616F: case DRM_FORMAT_XBGR16161616F: case DRM_FORMAT_ABGR16161616F: + case DRM_FORMAT_XRGB16161616: + case DRM_FORMAT_ARGB16161616: + case DRM_FORMAT_XBGR16161616: + case DRM_FORMAT_ABGR16161616: *max_upscale = plane_cap->max_upscale_factor.fp16; *min_downscale = plane_cap->max_downscale_factor.fp16; break; From fc086c0ce3db0eefbbeb66a5b1e626296336e33a Mon Sep 17 00:00:00 2001 From: "Thomas Richard (TI)" Date: Fri, 30 Jan 2026 11:05:45 +0100 Subject: [PATCH 3860/3902] usb: cdns3: fix role switching during resume commit 87e4b043b98a1d269be0b812f383881abee0ca45 upstream. If the role change while we are suspended, the cdns3 driver switches to the new mode during resume. However, switching to host mode in this context causes a NULL pointer dereference. The host role's start() operation registers a xhci-hcd device, but its probe is deferred while we are in the resume path. The host role's resume() operation assumes the xhci-hcd device is already probed, which is not the case, leading to the dereference. Since the start() operation of the new role is already called, the resume operation can be skipped. So skip the resume operation for the new role if a role switch occurs during resume. Once the resume sequence is complete, the xhci-hcd device can be probed in case of host mode. Unable to handle kernel NULL pointer dereference at virtual address 0000000000000208 Mem abort info: ... Data abort info: ... [0000000000000208] pgd=0000000000000000, p4d=0000000000000000 Internal error: Oops: 0000000096000004 [#1] SMP Modules linked in: CPU: 0 UID: 0 PID: 146 Comm: sh Not tainted 6.19.0-rc7-00013-g6e64f4aabfae-dirty #135 PREEMPT Hardware name: Texas Instruments J7200 EVM (DT) pstate: 20000005 (nzCv daif -PAN -UAO -TCO -DIT -SSBS BTYPE=--) pc : usb_hcd_is_primary_hcd+0x0/0x1c lr : cdns_host_resume+0x24/0x5c ... Call trace: usb_hcd_is_primary_hcd+0x0/0x1c (P) cdns_resume+0x6c/0xbc cdns3_controller_resume.isra.0+0xe8/0x17c cdns3_plat_resume+0x18/0x24 platform_pm_resume+0x2c/0x68 dpm_run_callback+0x90/0x248 device_resume+0x100/0x24c dpm_resume+0x190/0x2ec dpm_resume_end+0x18/0x34 suspend_devices_and_enter+0x2b0/0xa44 pm_suspend+0x16c/0x5fc state_store+0x80/0xec kobj_attr_store+0x18/0x2c sysfs_kf_write+0x7c/0x94 kernfs_fop_write_iter+0x130/0x1dc vfs_write+0x240/0x370 ksys_write+0x70/0x108 __arm64_sys_write+0x1c/0x28 invoke_syscall+0x48/0x10c el0_svc_common.constprop.0+0x40/0xe0 do_el0_svc+0x1c/0x28 el0_svc+0x34/0x108 el0t_64_sync_handler+0xa0/0xe4 el0t_64_sync+0x198/0x19c Code: 52800003 f9407ca5 d63f00a0 17ffffe4 (f9410401) ---[ end trace 0000000000000000 ]--- Cc: stable Fixes: 2cf2581cd229 ("usb: cdns3: add power lost support for system resume") Signed-off-by: Thomas Richard (TI) Acked-by: Peter Chen Link: https://patch.msgid.link/20260130-usb-cdns3-fix-role-switching-during-resume-v1-1-44c456852b52@bootlin.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/cdns3/core.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/usb/cdns3/core.c b/drivers/usb/cdns3/core.c index 1243a5cea91b52..f0e32227c0b791 100644 --- a/drivers/usb/cdns3/core.c +++ b/drivers/usb/cdns3/core.c @@ -551,7 +551,7 @@ int cdns_resume(struct cdns *cdns) } } - if (cdns->roles[cdns->role]->resume) + if (!role_changed && cdns->roles[cdns->role]->resume) cdns->roles[cdns->role]->resume(cdns, power_lost); return 0; From 561834f6d6f52b8a1791331e94b2aac753491d2a Mon Sep 17 00:00:00 2001 From: Yao Zi Date: Thu, 5 Feb 2026 15:56:44 +0000 Subject: [PATCH 3861/3902] MIPS: Work around LLVM bug when gp is used as global register variable commit 30bfc2d6a1132a89a5f1c3b96c59cf3e4d076ea3 upstream. On MIPS, __current_thread_info is defined as global register variable locating in $gp, and is simply assigned with new address during kernel relocation. This however is broken with LLVM, which always restores $gp if it finds $gp is clobbered in any form, including when intentionally through a global register variable. This is against GCC's documentation[1], which requires a callee-saved register used as global register variable not to be restored if it's clobbered. As a result, $gp will continue to point to the unrelocated kernel after the epilog of relocate_kernel(), leading to an early crash in init_idle, [ 0.000000] CPU 0 Unable to handle kernel paging request at virtual address 0000000000000000, epc == ffffffff81afada8, ra == ffffffff81afad90 [ 0.000000] Oops[#1]: [ 0.000000] CPU: 0 UID: 0 PID: 0 Comm: swapper Tainted: G W 6.19.0-rc5-00262-gd3eeb99bbc99-dirty #188 VOLUNTARY [ 0.000000] Tainted: [W]=WARN [ 0.000000] Hardware name: loongson,loongson64v-4core-virtio [ 0.000000] $ 0 : 0000000000000000 0000000000000000 0000000000000001 0000000000000000 [ 0.000000] $ 4 : ffffffff80b80ec0 ffffffff80b53d48 0000000000000000 00000000000f4240 [ 0.000000] $ 8 : 0000000000000100 ffffffff81d82f80 ffffffff81d82f80 0000000000000001 [ 0.000000] $12 : 0000000000000000 ffffffff81776f58 00000000000005da 0000000000000002 [ 0.000000] $16 : ffffffff80b80e40 0000000000000000 ffffffff80b81614 9800000005dfbe80 [ 0.000000] $20 : 00000000540000e0 ffffffff81980000 0000000000000000 ffffffff80f81c80 [ 0.000000] $24 : 0000000000000a26 ffffffff8114fb90 [ 0.000000] $28 : ffffffff80b50000 ffffffff80b53d40 0000000000000000 ffffffff81afad90 [ 0.000000] Hi : 0000000000000000 [ 0.000000] Lo : 0000000000000000 [ 0.000000] epc : ffffffff81afada8 init_idle+0x130/0x270 [ 0.000000] ra : ffffffff81afad90 init_idle+0x118/0x270 [ 0.000000] Status: 540000e2 KX SX UX KERNEL EXL [ 0.000000] Cause : 00000008 (ExcCode 02) [ 0.000000] BadVA : 0000000000000000 [ 0.000000] PrId : 00006305 (ICT Loongson-3) [ 0.000000] Process swapper (pid: 0, threadinfo=(____ptrval____), task=(____ptrval____), tls=0000000000000000) [ 0.000000] Stack : 9800000005dfbf00 ffffffff8178e950 0000000000000000 0000000000000000 [ 0.000000] 0000000000000000 ffffffff81970000 000000000000003f ffffffff810a6528 [ 0.000000] 0000000000000001 9800000005dfbe80 9800000005dfbf00 ffffffff81980000 [ 0.000000] ffffffff810a6450 ffffffff81afb6c0 0000000000000000 ffffffff810a2258 [ 0.000000] ffffffff81d82ec8 ffffffff8198d010 ffffffff81b67e80 ffffffff8197dd98 [ 0.000000] ffffffff81d81c80 ffffffff81930000 0000000000000040 0000000000000000 [ 0.000000] 0000000000000000 0000000000000000 0000000000000000 0000000000000000 [ 0.000000] 0000000000000000 000000000000009e ffffffff9fc01000 0000000000000000 [ 0.000000] 0000000000000000 0000000000000000 0000000000000000 0000000000000000 [ 0.000000] 0000000000000000 ffffffff81ae86dc ffffffff81b3c741 0000000000000002 [ 0.000000] ... [ 0.000000] Call Trace: [ 0.000000] [] init_idle+0x130/0x270 [ 0.000000] [] sched_init+0x5c8/0x6c0 [ 0.000000] [] start_kernel+0x27c/0x7a8 This bug has been reported to LLVM[2] and affects version from (at least) 18 to 21. Let's work around this by using inline assembly to assign $gp before a fix is widely available. Cc: stable@vger.kernel.org Link: https://gcc.gnu.org/onlinedocs/gcc-15.2.0/gcc/Global-Register-Variables.html # [1] Link: https://github.com/llvm/llvm-project/issues/176546 # [2] Signed-off-by: Yao Zi Acked-by: Nathan Chancellor Signed-off-by: Thomas Bogendoerfer Signed-off-by: Greg Kroah-Hartman --- arch/mips/kernel/relocate.c | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/arch/mips/kernel/relocate.c b/arch/mips/kernel/relocate.c index 7f1c136ad85062..59833210542fff 100644 --- a/arch/mips/kernel/relocate.c +++ b/arch/mips/kernel/relocate.c @@ -420,7 +420,20 @@ void *__init relocate_kernel(void) goto out; /* The current thread is now within the relocated image */ +#ifndef CONFIG_CC_IS_CLANG __current_thread_info = RELOCATED(&init_thread_union); +#else + /* + * LLVM may wrongly restore $gp ($28) in epilog even if it's + * intentionally modified. Work around this by using inline + * assembly to assign $gp. $gp couldn't be listed as output or + * clobber, or LLVM will still restore its original value. + * See also LLVM upstream issue + * https://github.com/llvm/llvm-project/issues/176546 + */ + asm volatile("move $28, %0" : : + "r" (RELOCATED(&init_thread_union))); +#endif /* Return the new kernel's entry point */ kernel_entry = RELOCATED(start_kernel); From f86ddca5e4197aabcb8561e3dd4460e705ed35f3 Mon Sep 17 00:00:00 2001 From: Zhang Yi Date: Sat, 29 Nov 2025 18:32:33 +0800 Subject: [PATCH 3862/3902] ext4: subdivide EXT4_EXT_DATA_VALID1 commit 22784ca541c0f01c5ebad14e8228298dc0a390ed upstream. When splitting an extent, if the EXT4_GET_BLOCKS_CONVERT flag is set and it is necessary to split the target extent in the middle, ext4_split_extent() first handles splitting the latter half of the extent and passes the EXT4_EXT_DATA_VALID1 flag. This flag implies that all blocks before the split point contain valid data; however, this assumption is incorrect. Therefore, subdivid EXT4_EXT_DATA_VALID1 into EXT4_EXT_DATA_ENTIRE_VALID1 and EXT4_EXT_DATA_PARTIAL_VALID1, which indicate that the first half of the extent is either entirely valid or only partially valid, respectively. These two flags cannot be set simultaneously. This patch does not use EXT4_EXT_DATA_PARTIAL_VALID1, it only replaces EXT4_EXT_DATA_VALID1 with EXT4_EXT_DATA_ENTIRE_VALID1 at the location where it is set, no logical changes. Signed-off-by: Zhang Yi Reviewed-by: Ojaswin Mujoo Reviewed-by: Baokun Li Cc: stable@kernel.org Message-ID: <20251129103247.686136-2-yi.zhang@huaweicloud.com> Signed-off-by: Theodore Ts'o Signed-off-by: Greg Kroah-Hartman --- fs/ext4/extents.c | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/fs/ext4/extents.c b/fs/ext4/extents.c index ca5499e9412b07..ac28bd371d8958 100644 --- a/fs/ext4/extents.c +++ b/fs/ext4/extents.c @@ -43,8 +43,13 @@ #define EXT4_EXT_MARK_UNWRIT1 0x2 /* mark first half unwritten */ #define EXT4_EXT_MARK_UNWRIT2 0x4 /* mark second half unwritten */ -#define EXT4_EXT_DATA_VALID1 0x8 /* first half contains valid data */ -#define EXT4_EXT_DATA_VALID2 0x10 /* second half contains valid data */ +/* first half contains valid data */ +#define EXT4_EXT_DATA_ENTIRE_VALID1 0x8 /* has entirely valid data */ +#define EXT4_EXT_DATA_PARTIAL_VALID1 0x10 /* has partially valid data */ +#define EXT4_EXT_DATA_VALID1 (EXT4_EXT_DATA_ENTIRE_VALID1 | \ + EXT4_EXT_DATA_PARTIAL_VALID1) + +#define EXT4_EXT_DATA_VALID2 0x20 /* second half contains valid data */ static __le32 ext4_extent_block_csum(struct inode *inode, struct ext4_extent_header *eh) @@ -3190,8 +3195,9 @@ static struct ext4_ext_path *ext4_split_extent_at(handle_t *handle, unsigned int ee_len, depth; int err = 0; - BUG_ON((split_flag & (EXT4_EXT_DATA_VALID1 | EXT4_EXT_DATA_VALID2)) == - (EXT4_EXT_DATA_VALID1 | EXT4_EXT_DATA_VALID2)); + BUG_ON((split_flag & EXT4_EXT_DATA_VALID1) == EXT4_EXT_DATA_VALID1); + BUG_ON((split_flag & EXT4_EXT_DATA_VALID1) && + (split_flag & EXT4_EXT_DATA_VALID2)); ext_debug(inode, "logical block %llu\n", (unsigned long long)split); @@ -3373,7 +3379,7 @@ static struct ext4_ext_path *ext4_split_extent(handle_t *handle, split_flag1 |= EXT4_EXT_MARK_UNWRIT1 | EXT4_EXT_MARK_UNWRIT2; if (split_flag & EXT4_EXT_DATA_VALID2) - split_flag1 |= EXT4_EXT_DATA_VALID1; + split_flag1 |= EXT4_EXT_DATA_ENTIRE_VALID1; path = ext4_split_extent_at(handle, inode, path, map->m_lblk + map->m_len, split_flag1, flags1); if (IS_ERR(path)) @@ -3732,7 +3738,7 @@ static struct ext4_ext_path *ext4_split_convert_extents(handle_t *handle, /* Convert to unwritten */ if (flags & EXT4_GET_BLOCKS_CONVERT_UNWRITTEN) { - split_flag |= EXT4_EXT_DATA_VALID1; + split_flag |= EXT4_EXT_DATA_ENTIRE_VALID1; /* Convert to initialized */ } else if (flags & EXT4_GET_BLOCKS_CONVERT) { split_flag |= ee_block + ee_len <= eof_block ? From d67c8ecf3d8fda9b8ef80e6f665d84b6d6ac9d88 Mon Sep 17 00:00:00 2001 From: Zhang Yi Date: Sat, 29 Nov 2025 18:32:34 +0800 Subject: [PATCH 3863/3902] ext4: don't zero the entire extent if EXT4_EXT_DATA_PARTIAL_VALID1 commit 1bf6974822d1dba86cf11b5f05498581cf3488a2 upstream. When allocating initialized blocks from a large unwritten extent, or when splitting an unwritten extent during end I/O and converting it to initialized, there is currently a potential issue of stale data if the extent needs to be split in the middle. 0 A B N [UUUUUUUUUUUU] U: unwritten extent [--DDDDDDDD--] D: valid data |<- ->| ----> this range needs to be initialized ext4_split_extent() first try to split this extent at B with EXT4_EXT_DATA_ENTIRE_VALID1 and EXT4_EXT_MAY_ZEROOUT flag set, but ext4_split_extent_at() failed to split this extent due to temporary lack of space. It zeroout B to N and mark the entire extent from 0 to N as written. 0 A B N [WWWWWWWWWWWW] W: written extent [SSDDDDDDDDZZ] Z: zeroed, S: stale data ext4_split_extent() then try to split this extent at A with EXT4_EXT_DATA_VALID2 flag set. This time, it split successfully and left a stale written extent from 0 to A. 0 A B N [WW|WWWWWWWWWW] [SS|DDDDDDDDZZ] Fix this by pass EXT4_EXT_DATA_PARTIAL_VALID1 to ext4_split_extent_at() when splitting at B, don't convert the entire extent to written and left it as unwritten after zeroing out B to N. The remaining work is just like the standard two-part split. ext4_split_extent() will pass the EXT4_EXT_DATA_VALID2 flag when it calls ext4_split_extent_at() for the second time, allowing it to properly handle the split. If the split is successful, it will keep extent from 0 to A as unwritten. Signed-off-by: Zhang Yi Reviewed-by: Ojaswin Mujoo Reviewed-by: Baokun Li Cc: stable@kernel.org Message-ID: <20251129103247.686136-3-yi.zhang@huaweicloud.com> Signed-off-by: Theodore Ts'o Signed-off-by: Greg Kroah-Hartman --- fs/ext4/extents.c | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/fs/ext4/extents.c b/fs/ext4/extents.c index ac28bd371d8958..68172b85847d9f 100644 --- a/fs/ext4/extents.c +++ b/fs/ext4/extents.c @@ -3310,6 +3310,15 @@ static struct ext4_ext_path *ext4_split_extent_at(handle_t *handle, } if (!err) { + /* + * The first half contains partially valid data, the + * splitting of this extent has not been completed, fix + * extent length and ext4_split_extent() split will the + * first half again. + */ + if (split_flag & EXT4_EXT_DATA_PARTIAL_VALID1) + goto fix_extent_len; + /* update the extent length and mark as initialized */ ex->ee_len = cpu_to_le16(ee_len); ext4_ext_try_to_merge(handle, inode, path, ex); @@ -3379,7 +3388,9 @@ static struct ext4_ext_path *ext4_split_extent(handle_t *handle, split_flag1 |= EXT4_EXT_MARK_UNWRIT1 | EXT4_EXT_MARK_UNWRIT2; if (split_flag & EXT4_EXT_DATA_VALID2) - split_flag1 |= EXT4_EXT_DATA_ENTIRE_VALID1; + split_flag1 |= map->m_lblk > ee_block ? + EXT4_EXT_DATA_PARTIAL_VALID1 : + EXT4_EXT_DATA_ENTIRE_VALID1; path = ext4_split_extent_at(handle, inode, path, map->m_lblk + map->m_len, split_flag1, flags1); if (IS_ERR(path)) From 5b1f4290453314e11cd8e15c7baa8a9b76c19b23 Mon Sep 17 00:00:00 2001 From: Zhang Yi Date: Sat, 29 Nov 2025 18:32:37 +0800 Subject: [PATCH 3864/3902] ext4: don't cache extent during splitting extent commit 8b4b19a2f96348d70bfa306ef7d4a13b0bcbea79 upstream. Caching extents during the splitting process is risky, as it may result in stale extents remaining in the status tree. Moreover, in most cases, the corresponding extent block entries are likely already cached before the split happens, making caching here not particularly useful. Assume we have an unwritten extent, and then DIO writes the first half. [UUUUUUUUUUUUUUUU] on-disk extent U: unwritten extent [UUUUUUUUUUUUUUUU] extent status tree |<- ->| ----> dio write this range First, when ext4_split_extent_at() splits this extent, it truncates the existing extent and then inserts a new one. During this process, this extent status entry may be shrunk, and calls to ext4_find_extent() and ext4_cache_extents() may occur, which could potentially insert the truncated range as a hole into the extent status tree. After the split is completed, this hole is not replaced with the correct status. [UUUUUUU|UUUUUUUU] on-disk extent U: unwritten extent [UUUUUUU|HHHHHHHH] extent status tree H: hole Then, the outer calling functions will not correct this remaining hole extent either. Finally, if we perform a delayed buffer write on this latter part, it will re-insert the delayed extent and cause an error in space accounting. In adition, if the unwritten extent cache is not shrunk during the splitting, ext4_cache_extents() also conflicts with existing extents when caching extents. In the future, we will add checks when caching extents, which will trigger a warning. Therefore, Do not cache extents that are being split. Signed-off-by: Zhang Yi Reviewed-by: Ojaswin Mujoo Reviewed-by: Baokun Li Cc: stable@kernel.org Message-ID: <20251129103247.686136-6-yi.zhang@huaweicloud.com> Signed-off-by: Theodore Ts'o Signed-off-by: Greg Kroah-Hartman --- fs/ext4/extents.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/fs/ext4/extents.c b/fs/ext4/extents.c index 68172b85847d9f..1f057df0be2ed2 100644 --- a/fs/ext4/extents.c +++ b/fs/ext4/extents.c @@ -3199,6 +3199,9 @@ static struct ext4_ext_path *ext4_split_extent_at(handle_t *handle, BUG_ON((split_flag & EXT4_EXT_DATA_VALID1) && (split_flag & EXT4_EXT_DATA_VALID2)); + /* Do not cache extents that are in the process of being modified. */ + flags |= EXT4_EX_NOCACHE; + ext_debug(inode, "logical block %llu\n", (unsigned long long)split); ext4_ext_show_leaf(inode, path); @@ -3381,6 +3384,9 @@ static struct ext4_ext_path *ext4_split_extent(handle_t *handle, ee_len = ext4_ext_get_actual_len(ex); unwritten = ext4_ext_is_unwritten(ex); + /* Do not cache extents that are in the process of being modified. */ + flags |= EXT4_EX_NOCACHE; + if (map->m_lblk + map->m_len < ee_block + ee_len) { split_flag1 = split_flag & EXT4_EXT_MAY_ZEROOUT; flags1 = flags | EXT4_GET_BLOCKS_PRE_IO; From c2ee51d684adca7645e4aa74adca13f6750390bc Mon Sep 17 00:00:00 2001 From: Zhang Yi Date: Sat, 29 Nov 2025 18:32:38 +0800 Subject: [PATCH 3865/3902] ext4: drop extent cache after doing PARTIAL_VALID1 zeroout commit 6d882ea3b0931b43530d44149b79fcd4ffc13030 upstream. When splitting an unwritten extent in the middle and converting it to initialized in ext4_split_extent() with the EXT4_EXT_MAY_ZEROOUT and EXT4_EXT_DATA_VALID2 flags set, it could leave a stale unwritten extent. Assume we have an unwritten file and buffered write in the middle of it without dioread_nolock enabled, it will allocate blocks as written extent. 0 A B N [UUUUUUUUUUUU] on-disk extent U: unwritten extent [UUUUUUUUUUUU] extent status tree [--DDDDDDDD--] D: valid data |<- ->| ----> this range needs to be initialized ext4_split_extent() first try to split this extent at B with EXT4_EXT_DATA_PARTIAL_VALID1 and EXT4_EXT_MAY_ZEROOUT flag set, but ext4_split_extent_at() failed to split this extent due to temporary lack of space. It zeroout B to N and leave the entire extent as unwritten. 0 A B N [UUUUUUUUUUUU] on-disk extent [UUUUUUUUUUUU] extent status tree [--DDDDDDDDZZ] Z: zeroed data ext4_split_extent() then try to split this extent at A with EXT4_EXT_DATA_VALID2 flag set. This time, it split successfully and leave an written extent from A to N. 0 A B N [UUWWWWWWWWWW] on-disk extent W: written extent [UUUUUUUUUUUU] extent status tree [--DDDDDDDDZZ] Finally ext4_map_create_blocks() only insert extent A to B to the extent status tree, and leave an stale unwritten extent in the status tree. 0 A B N [UUWWWWWWWWWW] on-disk extent W: written extent [UUWWWWWWWWUU] extent status tree [--DDDDDDDDZZ] Fix this issue by always cached extent status entry after zeroing out the second part. Signed-off-by: Zhang Yi Reviewed-by: Baokun Li Cc: stable@kernel.org Reviewed-by: Ojaswin Mujoo Message-ID: <20251129103247.686136-7-yi.zhang@huaweicloud.com> Signed-off-by: Theodore Ts'o Signed-off-by: Greg Kroah-Hartman --- fs/ext4/extents.c | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/fs/ext4/extents.c b/fs/ext4/extents.c index 1f057df0be2ed2..37d02e5bbc936a 100644 --- a/fs/ext4/extents.c +++ b/fs/ext4/extents.c @@ -3319,8 +3319,16 @@ static struct ext4_ext_path *ext4_split_extent_at(handle_t *handle, * extent length and ext4_split_extent() split will the * first half again. */ - if (split_flag & EXT4_EXT_DATA_PARTIAL_VALID1) + if (split_flag & EXT4_EXT_DATA_PARTIAL_VALID1) { + /* + * Drop extent cache to prevent stale unwritten + * extents remaining after zeroing out. + */ + ext4_es_remove_extent(inode, + le32_to_cpu(zero_ex.ee_block), + ext4_ext_get_actual_len(&zero_ex)); goto fix_extent_len; + } /* update the extent length and mark as initialized */ ex->ee_len = cpu_to_le16(ee_len); From 808f3191498f300174523c54cab101e18795ae4e Mon Sep 17 00:00:00 2001 From: Zhang Yi Date: Sat, 29 Nov 2025 18:32:39 +0800 Subject: [PATCH 3866/3902] ext4: drop extent cache when splitting extent fails commit 79b592e8f1b435796cbc2722190368e3e8ffd7a1 upstream. When the split extent fails, we might leave some extents still being processed and return an error directly, which will result in stale extent entries remaining in the extent status tree. So drop all of the remaining potentially stale extents if the splitting fails. Signed-off-by: Zhang Yi Reviewed-by: Baokun Li Cc: stable@kernel.org Reviewed-by: Ojaswin Mujoo Message-ID: <20251129103247.686136-8-yi.zhang@huaweicloud.com> Signed-off-by: Theodore Ts'o Signed-off-by: Greg Kroah-Hartman --- fs/ext4/extents.c | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/fs/ext4/extents.c b/fs/ext4/extents.c index 37d02e5bbc936a..7402271003fa82 100644 --- a/fs/ext4/extents.c +++ b/fs/ext4/extents.c @@ -3267,7 +3267,7 @@ static struct ext4_ext_path *ext4_split_extent_at(handle_t *handle, err = PTR_ERR(path); if (err != -ENOSPC && err != -EDQUOT && err != -ENOMEM) - return path; + goto out_path; /* * Get a new path to try to zeroout or fix the extent length. @@ -3281,7 +3281,7 @@ static struct ext4_ext_path *ext4_split_extent_at(handle_t *handle, if (IS_ERR(path)) { EXT4_ERROR_INODE(inode, "Failed split extent on %u, err %ld", split, PTR_ERR(path)); - return path; + goto out_path; } depth = ext_depth(inode); ex = path[depth].p_ext; @@ -3358,6 +3358,10 @@ static struct ext4_ext_path *ext4_split_extent_at(handle_t *handle, ext4_free_ext_path(path); path = ERR_PTR(err); } +out_path: + if (IS_ERR(path)) + /* Remove all remaining potentially stale extents. */ + ext4_es_remove_extent(inode, ee_block, ee_len); ext4_ext_show_leaf(inode, path); return path; } From 12615ab4bfb69678e5d961b28bb70040299e51b1 Mon Sep 17 00:00:00 2001 From: Zilin Guan Date: Thu, 25 Dec 2025 08:48:00 +0000 Subject: [PATCH 3867/3902] ext4: fix memory leak in ext4_ext_shift_extents() commit ca81109d4a8f192dc1cbad4a1ee25246363c2833 upstream. In ext4_ext_shift_extents(), if the extent is NULL in the while loop, the function returns immediately without releasing the path obtained via ext4_find_extent(), leading to a memory leak. Fix this by jumping to the out label to ensure the path is properly released. Fixes: a18ed359bdddc ("ext4: always check ext4_ext_find_extent result") Signed-off-by: Zilin Guan Reviewed-by: Zhang Yi Reviewed-by: Baokun Li Link: https://patch.msgid.link/20251225084800.905701-1-zilin@seu.edu.cn Signed-off-by: Theodore Ts'o Cc: stable@kernel.org Signed-off-by: Greg Kroah-Hartman --- fs/ext4/extents.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/fs/ext4/extents.c b/fs/ext4/extents.c index 7402271003fa82..ae7f2d6b32e32e 100644 --- a/fs/ext4/extents.c +++ b/fs/ext4/extents.c @@ -5410,7 +5410,8 @@ ext4_ext_shift_extents(struct inode *inode, handle_t *handle, if (!extent) { EXT4_ERROR_INODE(inode, "unexpected hole at %lu", (unsigned long) *iterator); - return -EFSCORRUPTED; + ret = -EFSCORRUPTED; + goto out; } if (SHIFT == SHIFT_LEFT && *iterator > le32_to_cpu(extent->ee_block)) { From 29a07d691d282faf38c33d4b61839b89399110f9 Mon Sep 17 00:00:00 2001 From: Yongjian Sun Date: Tue, 6 Jan 2026 17:08:20 +0800 Subject: [PATCH 3868/3902] ext4: fix e4b bitmap inconsistency reports commit bdc56a9c46b2a99c12313122b9352b619a2e719e upstream. A bitmap inconsistency issue was observed during stress tests under mixed huge-page workloads. Ext4 reported multiple e4b bitmap check failures like: ext4_mb_complex_scan_group:2508: group 350, 8179 free clusters as per group info. But got 8192 blocks Analysis and experimentation confirmed that the issue is caused by a race condition between page migration and bitmap modification. Although this timing window is extremely narrow, it is still hit in practice: folio_lock ext4_mb_load_buddy __migrate_folio check ref count folio_mc_copy __filemap_get_folio folio_try_get(folio) ...... mb_mark_used ext4_mb_unload_buddy __folio_migrate_mapping folio_ref_freeze folio_unlock The root cause of this issue is that the fast path of load_buddy only increments the folio's reference count, which is insufficient to prevent concurrent folio migration. We observed that the folio migration process acquires the folio lock. Therefore, we can determine whether to take the fast path in load_buddy by checking the lock status. If the folio is locked, we opt for the slow path (which acquires the lock) to close this concurrency window. Additionally, this change addresses the following issues: When the DOUBLE_CHECK macro is enabled to inspect bitmap-related issues, the following error may be triggered: corruption in group 324 at byte 784(6272): f in copy != ff on disk/prealloc Analysis reveals that this is a false positive. There is a specific race window where the bitmap and the group descriptor become momentarily inconsistent, leading to this error report: ext4_mb_load_buddy ext4_mb_load_buddy __filemap_get_folio(create|lock) folio_lock ext4_mb_init_cache folio_mark_uptodate __filemap_get_folio(no lock) ...... mb_mark_used mb_mark_used_double mb_cmp_bitmaps mb_set_bits(e4b->bd_bitmap) folio_unlock The original logic assumed that since mb_cmp_bitmaps is called when the bitmap is newly loaded from disk, the folio lock would be sufficient to prevent concurrent access. However, this overlooks a specific race condition: if another process attempts to load buddy and finds the folio is already in an uptodate state, it will immediately begin using it without holding folio lock. Signed-off-by: Yongjian Sun Reviewed-by: Zhang Yi Reviewed-by: Baokun Li Reviewed-by: Jan Kara Link: https://patch.msgid.link/20260106090820.836242-1-sunyongjian@huaweicloud.com Signed-off-by: Theodore Ts'o Cc: stable@kernel.org Signed-off-by: Greg Kroah-Hartman --- fs/ext4/mballoc.c | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/fs/ext4/mballoc.c b/fs/ext4/mballoc.c index 65335248825ce4..2a6ed0b2785519 100644 --- a/fs/ext4/mballoc.c +++ b/fs/ext4/mballoc.c @@ -1712,16 +1712,17 @@ ext4_mb_load_buddy_gfp(struct super_block *sb, ext4_group_t group, /* Avoid locking the folio in the fast path ... */ folio = __filemap_get_folio(inode->i_mapping, pnum, FGP_ACCESSED, 0); - if (IS_ERR(folio) || !folio_test_uptodate(folio)) { + if (IS_ERR(folio) || !folio_test_uptodate(folio) || folio_test_locked(folio)) { + /* + * folio_test_locked is employed to detect ongoing folio + * migrations, since concurrent migrations can lead to + * bitmap inconsistency. And if we are not uptodate that + * implies somebody just created the folio but is yet to + * initialize it. We can drop the folio reference and + * try to get the folio with lock in both cases to avoid + * concurrency. + */ if (!IS_ERR(folio)) - /* - * drop the folio reference and try - * to get the folio with lock. If we - * are not uptodate that implies - * somebody just created the folio but - * is yet to initialize it. So - * wait for it to initialize. - */ folio_put(folio); folio = __filemap_get_folio(inode->i_mapping, pnum, FGP_LOCK | FGP_ACCESSED | FGP_CREAT, gfp); @@ -1763,7 +1764,7 @@ ext4_mb_load_buddy_gfp(struct super_block *sb, ext4_group_t group, poff = block % blocks_per_page; folio = __filemap_get_folio(inode->i_mapping, pnum, FGP_ACCESSED, 0); - if (IS_ERR(folio) || !folio_test_uptodate(folio)) { + if (IS_ERR(folio) || !folio_test_uptodate(folio) || folio_test_locked(folio)) { if (!IS_ERR(folio)) folio_put(folio); folio = __filemap_get_folio(inode->i_mapping, pnum, From 3924aea2c33df3864929c1acd178bfc29d8f005f Mon Sep 17 00:00:00 2001 From: Brian Foster Date: Tue, 13 Jan 2026 12:19:05 -0500 Subject: [PATCH 3869/3902] ext4: fix dirtyclusters double decrement on fs shutdown commit 94a8cea54cd935c54fa2fba70354757c0fc245e3 upstream. fstests test generic/388 occasionally reproduces a warning in ext4_put_super() associated with the dirty clusters count: WARNING: CPU: 7 PID: 76064 at fs/ext4/super.c:1324 ext4_put_super+0x48c/0x590 [ext4] Tracing the failure shows that the warning fires due to an s_dirtyclusters_counter value of -1. IOW, this appears to be a spurious decrement as opposed to some sort of leak. Further tracing of the dirty cluster count deltas and an LLM scan of the resulting output identified the cause as a double decrement in the error path between ext4_mb_mark_diskspace_used() and the caller ext4_mb_new_blocks(). First, note that generic/388 is a shutdown vs. fsstress test and so produces a random set of operations and shutdown injections. In the problematic case, the shutdown triggers an error return from the ext4_handle_dirty_metadata() call(s) made from ext4_mb_mark_context(). The changed value is non-zero at this point, so ext4_mb_mark_diskspace_used() does not exit after the error bubbles up from ext4_mb_mark_context(). Instead, the former decrements both cluster counters and returns the error up to ext4_mb_new_blocks(). The latter falls into the !ar->len out path which decrements the dirty clusters counter a second time, creating the inconsistency. To avoid this problem and simplify ownership of the cluster reservation in this codepath, lift the counter reduction to a single place in the caller. This makes it more clear that ext4_mb_new_blocks() is responsible for acquiring cluster reservation (via ext4_claim_free_clusters()) in the !delalloc case as well as releasing it, regardless of whether it ends up consumed or returned due to failure. Fixes: 0087d9fb3f29 ("ext4: Fix s_dirty_blocks_counter if block allocation failed with nodelalloc") Signed-off-by: Brian Foster Reviewed-by: Baokun Li Link: https://patch.msgid.link/20260113171905.118284-1-bfoster@redhat.com Signed-off-by: Theodore Ts'o Cc: stable@kernel.org Signed-off-by: Greg Kroah-Hartman --- fs/ext4/mballoc-test.c | 2 +- fs/ext4/mballoc.c | 21 +++++---------------- 2 files changed, 6 insertions(+), 17 deletions(-) diff --git a/fs/ext4/mballoc-test.c b/fs/ext4/mballoc-test.c index a9416b20ff64c9..4abb40d4561ce3 100644 --- a/fs/ext4/mballoc-test.c +++ b/fs/ext4/mballoc-test.c @@ -567,7 +567,7 @@ test_mark_diskspace_used_range(struct kunit *test, bitmap = mbt_ctx_bitmap(sb, TEST_GOAL_GROUP); memset(bitmap, 0, sb->s_blocksize); - ret = ext4_mb_mark_diskspace_used(ac, NULL, 0); + ret = ext4_mb_mark_diskspace_used(ac, NULL); KUNIT_ASSERT_EQ(test, ret, 0); max = EXT4_CLUSTERS_PER_GROUP(sb); diff --git a/fs/ext4/mballoc.c b/fs/ext4/mballoc.c index 2a6ed0b2785519..6ba43082414cf2 100644 --- a/fs/ext4/mballoc.c +++ b/fs/ext4/mballoc.c @@ -4181,8 +4181,7 @@ ext4_mb_mark_context(handle_t *handle, struct super_block *sb, bool state, * Returns 0 if success or error code */ static noinline_for_stack int -ext4_mb_mark_diskspace_used(struct ext4_allocation_context *ac, - handle_t *handle, unsigned int reserv_clstrs) +ext4_mb_mark_diskspace_used(struct ext4_allocation_context *ac, handle_t *handle) { struct ext4_group_desc *gdp; struct ext4_sb_info *sbi; @@ -4237,13 +4236,6 @@ ext4_mb_mark_diskspace_used(struct ext4_allocation_context *ac, BUG_ON(changed != ac->ac_b_ex.fe_len); #endif percpu_counter_sub(&sbi->s_freeclusters_counter, ac->ac_b_ex.fe_len); - /* - * Now reduce the dirty block count also. Should not go negative - */ - if (!(ac->ac_flags & EXT4_MB_DELALLOC_RESERVED)) - /* release all the reserved blocks if non delalloc */ - percpu_counter_sub(&sbi->s_dirtyclusters_counter, - reserv_clstrs); return err; } @@ -6328,7 +6320,7 @@ ext4_fsblk_t ext4_mb_new_blocks(handle_t *handle, ext4_mb_pa_put_free(ac); } if (likely(ac->ac_status == AC_STATUS_FOUND)) { - *errp = ext4_mb_mark_diskspace_used(ac, handle, reserv_clstrs); + *errp = ext4_mb_mark_diskspace_used(ac, handle); if (*errp) { ext4_discard_allocated_blocks(ac); goto errout; @@ -6359,12 +6351,9 @@ ext4_fsblk_t ext4_mb_new_blocks(handle_t *handle, out: if (inquota && ar->len < inquota) dquot_free_block(ar->inode, EXT4_C2B(sbi, inquota - ar->len)); - if (!ar->len) { - if ((ar->flags & EXT4_MB_DELALLOC_RESERVED) == 0) - /* release all the reserved blocks if non delalloc */ - percpu_counter_sub(&sbi->s_dirtyclusters_counter, - reserv_clstrs); - } + /* release any reserved blocks */ + if (reserv_clstrs) + percpu_counter_sub(&sbi->s_dirtyclusters_counter, reserv_clstrs); trace_ext4_allocate_blocks(ar, (unsigned long long)block); From 34c803edc0b3365a42efcf9815acab63b4cf54e0 Mon Sep 17 00:00:00 2001 From: Jan Kara Date: Wed, 14 Jan 2026 19:28:18 +0100 Subject: [PATCH 3870/3902] ext4: always allocate blocks only from groups inode can use commit 4865c768b563deff1b6a6384e74a62f143427b42 upstream. For filesystems with more than 2^32 blocks inodes using indirect block based format cannot use blocks beyond the 32-bit limit. ext4_mb_scan_groups_linear() takes care to not select these unsupported groups for such inodes however other functions selecting groups for allocation don't. So far this is harmless because the other selection functions are used only with mb_optimize_scan and this is currently disabled for inodes with indirect blocks however in the following patch we want to enable mb_optimize_scan regardless of inode format. Reviewed-by: Baokun Li Reviewed-by: Zhang Yi Signed-off-by: Jan Kara Acked-by: Pedro Falcato Cc: stable@kernel.org Link: https://patch.msgid.link/20260114182836.14120-3-jack@suse.cz Signed-off-by: Theodore Ts'o Signed-off-by: Greg Kroah-Hartman --- fs/ext4/mballoc.c | 29 ++++++++++++++++++++--------- 1 file changed, 20 insertions(+), 9 deletions(-) diff --git a/fs/ext4/mballoc.c b/fs/ext4/mballoc.c index 6ba43082414cf2..0cf009940c5092 100644 --- a/fs/ext4/mballoc.c +++ b/fs/ext4/mballoc.c @@ -892,6 +892,21 @@ mb_update_avg_fragment_size(struct super_block *sb, struct ext4_group_info *grp) } } +static ext4_group_t ext4_get_allocation_groups_count( + struct ext4_allocation_context *ac) +{ + ext4_group_t ngroups = ext4_get_groups_count(ac->ac_sb); + + /* non-extent files are limited to low blocks/groups */ + if (!(ext4_test_inode_flag(ac->ac_inode, EXT4_INODE_EXTENTS))) + ngroups = EXT4_SB(ac->ac_sb)->s_blockfile_groups; + + /* Pairs with smp_wmb() in ext4_update_super() */ + smp_rmb(); + + return ngroups; +} + static int ext4_mb_scan_groups_xa_range(struct ext4_allocation_context *ac, struct xarray *xa, ext4_group_t start, ext4_group_t end) @@ -899,7 +914,7 @@ static int ext4_mb_scan_groups_xa_range(struct ext4_allocation_context *ac, struct super_block *sb = ac->ac_sb; struct ext4_sb_info *sbi = EXT4_SB(sb); enum criteria cr = ac->ac_criteria; - ext4_group_t ngroups = ext4_get_groups_count(sb); + ext4_group_t ngroups = ext4_get_allocation_groups_count(ac); unsigned long group = start; struct ext4_group_info *grp; @@ -951,7 +966,7 @@ static int ext4_mb_scan_groups_p2_aligned(struct ext4_allocation_context *ac, ext4_group_t start, end; start = group; - end = ext4_get_groups_count(ac->ac_sb); + end = ext4_get_allocation_groups_count(ac); wrap_around: for (i = ac->ac_2order; i < MB_NUM_ORDERS(ac->ac_sb); i++) { ret = ext4_mb_scan_groups_largest_free_order_range(ac, i, @@ -1001,7 +1016,7 @@ static int ext4_mb_scan_groups_goal_fast(struct ext4_allocation_context *ac, ext4_group_t start, end; start = group; - end = ext4_get_groups_count(ac->ac_sb); + end = ext4_get_allocation_groups_count(ac); wrap_around: i = mb_avg_fragment_size_order(ac->ac_sb, ac->ac_g_ex.fe_len); for (; i < MB_NUM_ORDERS(ac->ac_sb); i++) { @@ -1083,7 +1098,7 @@ static int ext4_mb_scan_groups_best_avail(struct ext4_allocation_context *ac, min_order = fls(ac->ac_o_ex.fe_len); start = group; - end = ext4_get_groups_count(ac->ac_sb); + end = ext4_get_allocation_groups_count(ac); wrap_around: for (i = order; i >= min_order; i--) { int frag_order; @@ -1182,11 +1197,7 @@ static int ext4_mb_scan_groups(struct ext4_allocation_context *ac) int ret = 0; ext4_group_t start; struct ext4_sb_info *sbi = EXT4_SB(ac->ac_sb); - ext4_group_t ngroups = ext4_get_groups_count(ac->ac_sb); - - /* non-extent files are limited to low blocks/groups */ - if (!(ext4_test_inode_flag(ac->ac_inode, EXT4_INODE_EXTENTS))) - ngroups = sbi->s_blockfile_groups; + ext4_group_t ngroups = ext4_get_allocation_groups_count(ac); /* searching for the right group start from the goal value specified */ start = ac->ac_g_ex.fe_group; From 3be216fd6f89e653c6b76a309d07646d2baa1d40 Mon Sep 17 00:00:00 2001 From: Jan Kara Date: Wed, 14 Jan 2026 19:28:19 +0100 Subject: [PATCH 3871/3902] ext4: use optimized mballoc scanning regardless of inode format commit 3574c322b1d0eb32dbd76b469cb08f9a67641599 upstream. Currently we don't used mballoc optimized scanning (using max free extent order and avg free extent order group lists) for inodes with indirect block based format. This is confusing for users and I don't see a good reason for that. Even with indirect block based inode format we can spend big amount of time searching for free blocks for large filesystems with fragmented free space. To add to the confusion before commit 077d0c2c78df ("ext4: make mb_optimize_scan performance mount option work with extents") optimized scanning was applied *only* to indirect block based inodes so that commit appears as a performance regression to some users. Just use optimized scanning whenever it is enabled by mount options. Reviewed-by: Baokun Li Reviewed-by: Zhang Yi Signed-off-by: Jan Kara Cc: stable@kernel.org Link: https://patch.msgid.link/20260114182836.14120-4-jack@suse.cz Signed-off-by: Theodore Ts'o Signed-off-by: Greg Kroah-Hartman --- fs/ext4/mballoc.c | 2 -- 1 file changed, 2 deletions(-) diff --git a/fs/ext4/mballoc.c b/fs/ext4/mballoc.c index 0cf009940c5092..412289e5c0afee 100644 --- a/fs/ext4/mballoc.c +++ b/fs/ext4/mballoc.c @@ -1148,8 +1148,6 @@ static inline int should_optimize_scan(struct ext4_allocation_context *ac) return 0; if (ac->ac_criteria >= CR_GOAL_LEN_SLOW) return 0; - if (!ext4_test_inode_flag(ac->ac_inode, EXT4_INODE_EXTENTS)) - return 0; return 1; } From 62a16b2e3ce9b7fc92898d957316573e3f847ab7 Mon Sep 17 00:00:00 2001 From: Linus Walleij Date: Tue, 3 Feb 2026 11:23:01 +0100 Subject: [PATCH 3872/3902] ata: pata_ftide010: Fix some DMA timings commit ff4a46c278ac6a4b3f39be1492a4568b6dcc6105 upstream. The FTIDE010 has been missing some timing settings since its inception, since the upstream OpenWrt patch was missing these. The community has since come up with the appropriate timings. Fixes: be4e456ed3a5 ("ata: Add driver for Faraday Technology FTIDE010") Cc: stable@vger.kernel.org Signed-off-by: Linus Walleij Signed-off-by: Niklas Cassel Signed-off-by: Greg Kroah-Hartman --- drivers/ata/pata_ftide010.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/drivers/ata/pata_ftide010.c b/drivers/ata/pata_ftide010.c index c3a8384c3e04d4..c41da296eb389d 100644 --- a/drivers/ata/pata_ftide010.c +++ b/drivers/ata/pata_ftide010.c @@ -122,10 +122,10 @@ static const u8 mwdma_50_active_time[3] = {6, 2, 2}; static const u8 mwdma_50_recovery_time[3] = {6, 2, 1}; static const u8 mwdma_66_active_time[3] = {8, 3, 3}; static const u8 mwdma_66_recovery_time[3] = {8, 2, 1}; -static const u8 udma_50_setup_time[6] = {3, 3, 2, 2, 1, 1}; +static const u8 udma_50_setup_time[6] = {3, 3, 2, 2, 1, 9}; static const u8 udma_50_hold_time[6] = {3, 1, 1, 1, 1, 1}; -static const u8 udma_66_setup_time[7] = {4, 4, 3, 2, }; -static const u8 udma_66_hold_time[7] = {}; +static const u8 udma_66_setup_time[7] = {4, 4, 3, 2, 1, 9, 9}; +static const u8 udma_66_hold_time[7] = {4, 2, 1, 1, 1, 1, 1}; /* * We set 66 MHz for all MWDMA modes From 410336fc82ac9e1a294af56b9ede0e0e0910ba61 Mon Sep 17 00:00:00 2001 From: Damien Le Moal Date: Wed, 17 Dec 2025 14:05:25 +0900 Subject: [PATCH 3873/3902] ata: libata-scsi: refactor ata_scsi_translate() commit bb3a8154b1a1dc2c86d037482c0a2cf9186829ed upstream. Factor out of ata_scsi_translate() the code handling queued command deferral using the port qc_defer callback and issuing the queued command with ata_qc_issue() into the new function ata_scsi_qc_issue(), and simplify the goto used in ata_scsi_translate(). While at it, also add a lockdep annotation to check that the port lock is held when ata_scsi_translate() is called. No functional changes. Cc: stable@vger.kernel.org Signed-off-by: Damien Le Moal Reviewed-by: Niklas Cassel Reviewed-by: Martin K. Petersen Reviewed-by: John Garry Reviewed-by: Igor Pylypiv Signed-off-by: Greg Kroah-Hartman --- drivers/ata/libata-scsi.c | 81 ++++++++++++++++++++++++--------------- 1 file changed, 50 insertions(+), 31 deletions(-) diff --git a/drivers/ata/libata-scsi.c b/drivers/ata/libata-scsi.c index 434774e71fe61e..4ecf344493840e 100644 --- a/drivers/ata/libata-scsi.c +++ b/drivers/ata/libata-scsi.c @@ -1690,6 +1690,42 @@ static void ata_scsi_qc_complete(struct ata_queued_cmd *qc) ata_qc_done(qc); } +static int ata_scsi_qc_issue(struct ata_port *ap, struct ata_queued_cmd *qc) +{ + int ret; + + if (!ap->ops->qc_defer) + goto issue; + + /* Check if the command needs to be deferred. */ + ret = ap->ops->qc_defer(qc); + switch (ret) { + case 0: + break; + case ATA_DEFER_LINK: + ret = SCSI_MLQUEUE_DEVICE_BUSY; + break; + case ATA_DEFER_PORT: + ret = SCSI_MLQUEUE_HOST_BUSY; + break; + default: + WARN_ON_ONCE(1); + ret = SCSI_MLQUEUE_HOST_BUSY; + break; + } + + if (ret) { + /* Force a requeue of the command to defer its execution. */ + ata_qc_free(qc); + return ret; + } + +issue: + ata_qc_issue(qc); + + return 0; +} + /** * ata_scsi_translate - Translate then issue SCSI command to ATA device * @dev: ATA device to which the command is addressed @@ -1713,66 +1749,49 @@ static void ata_scsi_qc_complete(struct ata_queued_cmd *qc) * spin_lock_irqsave(host lock) * * RETURNS: - * 0 on success, SCSI_ML_QUEUE_DEVICE_BUSY if the command - * needs to be deferred. + * 0 on success, SCSI_ML_QUEUE_DEVICE_BUSY or SCSI_MLQUEUE_HOST_BUSY if the + * command needs to be deferred. */ static int ata_scsi_translate(struct ata_device *dev, struct scsi_cmnd *cmd, ata_xlat_func_t xlat_func) { struct ata_port *ap = dev->link->ap; struct ata_queued_cmd *qc; - int rc; + lockdep_assert_held(ap->lock); + + /* + * ata_scsi_qc_new() calls scsi_done(cmd) in case of failure. So we + * have nothing further to do when allocating a qc fails. + */ qc = ata_scsi_qc_new(dev, cmd); if (!qc) - goto err_mem; + return 0; /* data is present; dma-map it */ if (cmd->sc_data_direction == DMA_FROM_DEVICE || cmd->sc_data_direction == DMA_TO_DEVICE) { if (unlikely(scsi_bufflen(cmd) < 1)) { ata_dev_warn(dev, "WARNING: zero len r/w req\n"); - goto err_did; + cmd->result = (DID_ERROR << 16); + goto done; } ata_sg_init(qc, scsi_sglist(cmd), scsi_sg_count(cmd)); - qc->dma_dir = cmd->sc_data_direction; } qc->complete_fn = ata_scsi_qc_complete; if (xlat_func(qc)) - goto early_finish; - - if (ap->ops->qc_defer) { - if ((rc = ap->ops->qc_defer(qc))) - goto defer; - } - - /* select device, send command to hardware */ - ata_qc_issue(qc); + goto done; - return 0; - -early_finish: - ata_qc_free(qc); - scsi_done(cmd); - return 0; + return ata_scsi_qc_issue(ap, qc); -err_did: +done: ata_qc_free(qc); - cmd->result = (DID_ERROR << 16); scsi_done(cmd); -err_mem: return 0; - -defer: - ata_qc_free(qc); - if (rc == ATA_DEFER_LINK) - return SCSI_MLQUEUE_DEVICE_BUSY; - else - return SCSI_MLQUEUE_HOST_BUSY; } /** From 888cd7e40adb2ef4af1b4d3b6e2e83ad409ae8c2 Mon Sep 17 00:00:00 2001 From: Damien Le Moal Date: Wed, 17 Dec 2025 16:40:48 +0900 Subject: [PATCH 3874/3902] ata: libata-scsi: avoid Non-NCQ command starvation commit 0ea84089dbf62a92dc7889c79e6b18fc89260808 upstream. When a non-NCQ command is issued while NCQ commands are being executed, ata_scsi_qc_issue() indicates to the SCSI layer that the command issuing should be deferred by returning SCSI_MLQUEUE_XXX_BUSY. This command deferring is correct and as mandated by the ACS specifications since NCQ and non-NCQ commands cannot be mixed. However, in the case of a host adapter using multiple submission queues, when the target device is under a constant load of NCQ commands, there are no guarantees that requeueing the non-NCQ command will be executed later and it may be deferred again repeatedly as other submission queues can constantly issue NCQ commands from different CPUs ahead of the non-NCQ command. This can lead to very long delays for the execution of non-NCQ commands, and even complete starvation for these commands in the worst case scenario. Since the block layer and the SCSI layer do not distinguish between queueable (NCQ) and non queueable (non-NCQ) commands, libata-scsi SAT implementation must ensure forward progress for non-NCQ commands in the presence of NCQ command traffic. This is similar to what SAS HBAs with a hardware/firmware based SAT implementation do. Implement such forward progress guarantee by limiting requeueing of non-NCQ commands from ata_scsi_qc_issue(): when a non-NCQ command is received and NCQ commands are in-flight, do not force a requeue of the non-NCQ command by returning SCSI_MLQUEUE_XXX_BUSY and instead return 0 to indicate that the command was accepted but hold on to the qc using the new deferred_qc field of struct ata_port. This deferred qc will be issued using the work item deferred_qc_work running the function ata_scsi_deferred_qc_work() once all in-flight commands complete, which is checked with the port qc_defer() callback return value indicating that no further delay is necessary. This check is done using the helper function ata_scsi_schedule_deferred_qc() which is called from ata_scsi_qc_complete(). This thus excludes this mechanism from all internal non-NCQ commands issued by ATA EH. When a port deferred_qc is non NULL, that is, the port has a command waiting for the device queue to drain, the issuing of all incoming commands (both NCQ and non-NCQ) is deferred using the regular busy mechanism. This simplifies the code and also avoids potential denial of service problems if a user issues too many non-NCQ commands. Finally, whenever ata EH is scheduled, regardless of the reason, a deferred qc is always requeued so that it can be retried once EH completes. This is done by calling the function ata_scsi_requeue_deferred_qc() from ata_eh_set_pending(). This avoids the need for any special processing for the deferred qc in case of NCQ error, link or device reset, or device timeout. Reported-by: Xingui Yang Reported-by: Igor Pylypiv Fixes: bdb01301f3ea ("scsi: Add host and host template flag 'host_tagset'") Cc: stable@vger.kernel.org Signed-off-by: Damien Le Moal Reviewed-by: Niklas Cassel Reviewed-by: Martin K. Petersen Reviewed-by: John Garry Tested-by: Igor Pylypiv Tested-by: Xingui Yang Signed-off-by: Greg Kroah-Hartman --- drivers/ata/libata-core.c | 5 +++ drivers/ata/libata-eh.c | 6 +++ drivers/ata/libata-scsi.c | 93 +++++++++++++++++++++++++++++++++++++++ drivers/ata/libata.h | 2 + include/linux/libata.h | 3 ++ 5 files changed, 109 insertions(+) diff --git a/drivers/ata/libata-core.c b/drivers/ata/libata-core.c index 699919e4579e19..947215834830ac 100644 --- a/drivers/ata/libata-core.c +++ b/drivers/ata/libata-core.c @@ -5552,6 +5552,7 @@ struct ata_port *ata_port_alloc(struct ata_host *host) mutex_init(&ap->scsi_scan_mutex); INIT_DELAYED_WORK(&ap->hotplug_task, ata_scsi_hotplug); INIT_DELAYED_WORK(&ap->scsi_rescan_task, ata_scsi_dev_rescan); + INIT_WORK(&ap->deferred_qc_work, ata_scsi_deferred_qc_work); INIT_LIST_HEAD(&ap->eh_done_q); init_waitqueue_head(&ap->eh_wait_q); init_completion(&ap->park_req_pending); @@ -6162,6 +6163,10 @@ static void ata_port_detach(struct ata_port *ap) } } + /* Make sure the deferred qc work finished. */ + cancel_work_sync(&ap->deferred_qc_work); + WARN_ON(ap->deferred_qc); + /* Tell EH to disable all devices */ ap->pflags |= ATA_PFLAG_UNLOADING; ata_port_schedule_eh(ap); diff --git a/drivers/ata/libata-eh.c b/drivers/ata/libata-eh.c index 2586e77ebf45d0..b90b17f680f822 100644 --- a/drivers/ata/libata-eh.c +++ b/drivers/ata/libata-eh.c @@ -917,6 +917,12 @@ static void ata_eh_set_pending(struct ata_port *ap, bool fastdrain) ap->pflags |= ATA_PFLAG_EH_PENDING; + /* + * If we have a deferred qc, requeue it so that it is retried once EH + * completes. + */ + ata_scsi_requeue_deferred_qc(ap); + if (!fastdrain) return; diff --git a/drivers/ata/libata-scsi.c b/drivers/ata/libata-scsi.c index 4ecf344493840e..27ad145996052f 100644 --- a/drivers/ata/libata-scsi.c +++ b/drivers/ata/libata-scsi.c @@ -1657,8 +1657,77 @@ static void ata_qc_done(struct ata_queued_cmd *qc) done(cmd); } +void ata_scsi_deferred_qc_work(struct work_struct *work) +{ + struct ata_port *ap = + container_of(work, struct ata_port, deferred_qc_work); + struct ata_queued_cmd *qc; + unsigned long flags; + + spin_lock_irqsave(ap->lock, flags); + + /* + * If we still have a deferred qc and we are not in EH, issue it. In + * such case, we should not need any more deferring the qc, so warn if + * qc_defer() says otherwise. + */ + qc = ap->deferred_qc; + if (qc && !ata_port_eh_scheduled(ap)) { + WARN_ON_ONCE(ap->ops->qc_defer(qc)); + ap->deferred_qc = NULL; + ata_qc_issue(qc); + } + + spin_unlock_irqrestore(ap->lock, flags); +} + +void ata_scsi_requeue_deferred_qc(struct ata_port *ap) +{ + struct ata_queued_cmd *qc = ap->deferred_qc; + struct scsi_cmnd *scmd; + + lockdep_assert_held(ap->lock); + + /* + * If we have a deferred qc when a reset occurs or NCQ commands fail, + * do not try to be smart about what to do with this deferred command + * and simply retry it by completing it with DID_SOFT_ERROR. + */ + if (!qc) + return; + + scmd = qc->scsicmd; + ap->deferred_qc = NULL; + ata_qc_free(qc); + scmd->result = (DID_SOFT_ERROR << 16); + scsi_done(scmd); +} + +static void ata_scsi_schedule_deferred_qc(struct ata_port *ap) +{ + struct ata_queued_cmd *qc = ap->deferred_qc; + + lockdep_assert_held(ap->lock); + + /* + * If we have a deferred qc, then qc_defer() is defined and we can use + * this callback to determine if this qc is good to go, unless EH has + * been scheduled. + */ + if (!qc) + return; + + if (ata_port_eh_scheduled(ap)) { + ata_scsi_requeue_deferred_qc(ap); + return; + } + if (!ap->ops->qc_defer(qc)) + queue_work(system_highpri_wq, &ap->deferred_qc_work); +} + static void ata_scsi_qc_complete(struct ata_queued_cmd *qc) { + struct ata_port *ap = qc->ap; struct scsi_cmnd *cmd = qc->scsicmd; u8 *cdb = cmd->cmnd; bool have_sense = qc->flags & ATA_QCFLAG_SENSE_VALID; @@ -1688,6 +1757,8 @@ static void ata_scsi_qc_complete(struct ata_queued_cmd *qc) } ata_qc_done(qc); + + ata_scsi_schedule_deferred_qc(ap); } static int ata_scsi_qc_issue(struct ata_port *ap, struct ata_queued_cmd *qc) @@ -1697,6 +1768,16 @@ static int ata_scsi_qc_issue(struct ata_port *ap, struct ata_queued_cmd *qc) if (!ap->ops->qc_defer) goto issue; + /* + * If we already have a deferred qc, then rely on the SCSI layer to + * requeue and defer all incoming commands until the deferred qc is + * processed, once all on-going commands complete. + */ + if (ap->deferred_qc) { + ata_qc_free(qc); + return SCSI_MLQUEUE_DEVICE_BUSY; + } + /* Check if the command needs to be deferred. */ ret = ap->ops->qc_defer(qc); switch (ret) { @@ -1715,6 +1796,18 @@ static int ata_scsi_qc_issue(struct ata_port *ap, struct ata_queued_cmd *qc) } if (ret) { + /* + * We must defer this qc: if this is not an NCQ command, keep + * this qc as a deferred one and report to the SCSI layer that + * we issued it so that it is not requeued. The deferred qc will + * be issued with the port deferred_qc_work once all on-going + * commands complete. + */ + if (!ata_is_ncq(qc->tf.protocol)) { + ap->deferred_qc = qc; + return 0; + } + /* Force a requeue of the command to defer its execution. */ ata_qc_free(qc); return ret; diff --git a/drivers/ata/libata.h b/drivers/ata/libata.h index e5b977a8d3e1e5..612fe59828180c 100644 --- a/drivers/ata/libata.h +++ b/drivers/ata/libata.h @@ -161,6 +161,8 @@ void ata_scsi_sdev_config(struct scsi_device *sdev); int ata_scsi_dev_config(struct scsi_device *sdev, struct queue_limits *lim, struct ata_device *dev); int __ata_scsi_queuecmd(struct scsi_cmnd *scmd, struct ata_device *dev); +void ata_scsi_deferred_qc_work(struct work_struct *work); +void ata_scsi_requeue_deferred_qc(struct ata_port *ap); /* libata-eh.c */ extern unsigned int ata_internal_cmd_timeout(struct ata_device *dev, u8 cmd); diff --git a/include/linux/libata.h b/include/linux/libata.h index 7a98de1cc995c2..3b8bdea8516d04 100644 --- a/include/linux/libata.h +++ b/include/linux/libata.h @@ -899,6 +899,9 @@ struct ata_port { u64 qc_active; int nr_active_links; /* #links with active qcs */ + struct work_struct deferred_qc_work; + struct ata_queued_cmd *deferred_qc; + struct ata_link link; /* host default link */ struct ata_link *slave_link; /* see ata_slave_link_init() */ From d79b9097a6a2b91471b40755f1225364be5d85ff Mon Sep 17 00:00:00 2001 From: Chuck Lever Date: Fri, 26 Dec 2025 10:15:32 -0500 Subject: [PATCH 3875/3902] SUNRPC: auth_gss: fix memory leaks in XDR decoding error paths commit 3e6397b056335cc56ef0e9da36c95946a19f5118 upstream. The gssx_dec_ctx(), gssx_dec_status(), and gssx_dec_name() functions allocate memory via gssx_dec_buffer(), which calls kmemdup(). When a subsequent decode operation fails, these functions return immediately without freeing previously allocated buffers, causing memory leaks. The leak in gssx_dec_ctx() is particularly relevant because the caller (gssp_accept_sec_context_upcall) initializes several buffer length fields to non-zero values, resulting in memory allocation: struct gssx_ctx rctxh = { .exported_context_token.len = GSSX_max_output_handle_sz, .mech.len = GSS_OID_MAX_LEN, .src_name.display_name.len = GSSX_max_princ_sz, .targ_name.display_name.len = GSSX_max_princ_sz }; If, for example, gssx_dec_name() succeeds for src_name but fails for targ_name, the memory allocated for exported_context_token, mech, and src_name.display_name remains unreferenced and cannot be reclaimed. Add error handling with goto-based cleanup to free any previously allocated buffers before returning an error. Reported-by: Xingjing Deng Closes: https://lore.kernel.org/linux-nfs/CAK+ZN9qttsFDu6h1FoqGadXjMx1QXqPMoYQ=6O9RY4SxVTvKng@mail.gmail.com/ Fixes: 1d658336b05f ("SUNRPC: Add RPC based upcall mechanism for RPCGSS auth") Cc: stable@vger.kernel.org Reviewed-by: Jeff Layton Signed-off-by: Chuck Lever Signed-off-by: Greg Kroah-Hartman --- net/sunrpc/auth_gss/gss_rpc_xdr.c | 82 ++++++++++++++++++++++++------- 1 file changed, 64 insertions(+), 18 deletions(-) diff --git a/net/sunrpc/auth_gss/gss_rpc_xdr.c b/net/sunrpc/auth_gss/gss_rpc_xdr.c index 7d2cdc2bd374e2..f320c0a8e60493 100644 --- a/net/sunrpc/auth_gss/gss_rpc_xdr.c +++ b/net/sunrpc/auth_gss/gss_rpc_xdr.c @@ -320,29 +320,47 @@ static int gssx_dec_status(struct xdr_stream *xdr, /* status->minor_status */ p = xdr_inline_decode(xdr, 8); - if (unlikely(p == NULL)) - return -ENOSPC; + if (unlikely(p == NULL)) { + err = -ENOSPC; + goto out_free_mech; + } p = xdr_decode_hyper(p, &status->minor_status); /* status->major_status_string */ err = gssx_dec_buffer(xdr, &status->major_status_string); if (err) - return err; + goto out_free_mech; /* status->minor_status_string */ err = gssx_dec_buffer(xdr, &status->minor_status_string); if (err) - return err; + goto out_free_major_status_string; /* status->server_ctx */ err = gssx_dec_buffer(xdr, &status->server_ctx); if (err) - return err; + goto out_free_minor_status_string; /* we assume we have no options for now, so simply consume them */ /* status->options */ err = dummy_dec_opt_array(xdr, &status->options); + if (err) + goto out_free_server_ctx; + return 0; + +out_free_server_ctx: + kfree(status->server_ctx.data); + status->server_ctx.data = NULL; +out_free_minor_status_string: + kfree(status->minor_status_string.data); + status->minor_status_string.data = NULL; +out_free_major_status_string: + kfree(status->major_status_string.data); + status->major_status_string.data = NULL; +out_free_mech: + kfree(status->mech.data); + status->mech.data = NULL; return err; } @@ -505,28 +523,35 @@ static int gssx_dec_name(struct xdr_stream *xdr, /* name->name_type */ err = gssx_dec_buffer(xdr, &dummy_netobj); if (err) - return err; + goto out_free_display_name; /* name->exported_name */ err = gssx_dec_buffer(xdr, &dummy_netobj); if (err) - return err; + goto out_free_display_name; /* name->exported_composite_name */ err = gssx_dec_buffer(xdr, &dummy_netobj); if (err) - return err; + goto out_free_display_name; /* we assume we have no attributes for now, so simply consume them */ /* name->name_attributes */ err = dummy_dec_nameattr_array(xdr, &dummy_name_attr_array); if (err) - return err; + goto out_free_display_name; /* we assume we have no options for now, so simply consume them */ /* name->extensions */ err = dummy_dec_opt_array(xdr, &dummy_option_array); + if (err) + goto out_free_display_name; + return 0; + +out_free_display_name: + kfree(name->display_name.data); + name->display_name.data = NULL; return err; } @@ -649,32 +674,34 @@ static int gssx_dec_ctx(struct xdr_stream *xdr, /* ctx->state */ err = gssx_dec_buffer(xdr, &ctx->state); if (err) - return err; + goto out_free_exported_context_token; /* ctx->need_release */ err = gssx_dec_bool(xdr, &ctx->need_release); if (err) - return err; + goto out_free_state; /* ctx->mech */ err = gssx_dec_buffer(xdr, &ctx->mech); if (err) - return err; + goto out_free_state; /* ctx->src_name */ err = gssx_dec_name(xdr, &ctx->src_name); if (err) - return err; + goto out_free_mech; /* ctx->targ_name */ err = gssx_dec_name(xdr, &ctx->targ_name); if (err) - return err; + goto out_free_src_name; /* ctx->lifetime */ p = xdr_inline_decode(xdr, 8+8); - if (unlikely(p == NULL)) - return -ENOSPC; + if (unlikely(p == NULL)) { + err = -ENOSPC; + goto out_free_targ_name; + } p = xdr_decode_hyper(p, &ctx->lifetime); /* ctx->ctx_flags */ @@ -683,17 +710,36 @@ static int gssx_dec_ctx(struct xdr_stream *xdr, /* ctx->locally_initiated */ err = gssx_dec_bool(xdr, &ctx->locally_initiated); if (err) - return err; + goto out_free_targ_name; /* ctx->open */ err = gssx_dec_bool(xdr, &ctx->open); if (err) - return err; + goto out_free_targ_name; /* we assume we have no options for now, so simply consume them */ /* ctx->options */ err = dummy_dec_opt_array(xdr, &ctx->options); + if (err) + goto out_free_targ_name; + + return 0; +out_free_targ_name: + kfree(ctx->targ_name.display_name.data); + ctx->targ_name.display_name.data = NULL; +out_free_src_name: + kfree(ctx->src_name.display_name.data); + ctx->src_name.display_name.data = NULL; +out_free_mech: + kfree(ctx->mech.data); + ctx->mech.data = NULL; +out_free_state: + kfree(ctx->state.data); + ctx->state.data = NULL; +out_free_exported_context_token: + kfree(ctx->exported_context_token.data); + ctx->exported_context_token.data = NULL; return err; } From c20f925214249bb4fc04f7e197bea142a6438af6 Mon Sep 17 00:00:00 2001 From: Daniel Hodges Date: Fri, 6 Feb 2026 15:41:46 -0500 Subject: [PATCH 3876/3902] SUNRPC: fix gss_auth kref leak in gss_alloc_msg error path commit dd2fdc3504592d85e549c523b054898a036a6afe upstream. Commit 5940d1cf9f42 ("SUNRPC: Rebalance a kref in auth_gss.c") added a kref_get(&gss_auth->kref) call to balance the gss_put_auth() done in gss_release_msg(), but forgot to add a corresponding kref_put() on the error path when kstrdup_const() fails. If service_name is non-NULL and kstrdup_const() fails, the function jumps to err_put_pipe_version which calls put_pipe_version() and kfree(gss_msg), but never releases the gss_auth reference. This leads to a kref leak where the gss_auth structure is never freed. Add a forward declaration for gss_free_callback() and call kref_put() in the err_put_pipe_version error path to properly release the reference taken earlier. Fixes: 5940d1cf9f42 ("SUNRPC: Rebalance a kref in auth_gss.c") Cc: stable@vger.kernel.org Signed-off-by: Daniel Hodges Signed-off-by: Anna Schumaker Signed-off-by: Greg Kroah-Hartman --- net/sunrpc/auth_gss/auth_gss.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/net/sunrpc/auth_gss/auth_gss.c b/net/sunrpc/auth_gss/auth_gss.c index 5c095cb8cb201e..bb3c3db2713b1f 100644 --- a/net/sunrpc/auth_gss/auth_gss.c +++ b/net/sunrpc/auth_gss/auth_gss.c @@ -39,6 +39,8 @@ static const struct rpc_authops authgss_ops; static const struct rpc_credops gss_credops; static const struct rpc_credops gss_nullops; +static void gss_free_callback(struct kref *kref); + #define GSS_RETRY_EXPIRED 5 static unsigned int gss_expired_cred_retry_delay = GSS_RETRY_EXPIRED; @@ -551,6 +553,7 @@ gss_alloc_msg(struct gss_auth *gss_auth, } return gss_msg; err_put_pipe_version: + kref_put(&gss_auth->kref, gss_free_callback); put_pipe_version(gss_auth->net); err_free_msg: kfree(gss_msg); From 05072d352ed1088d5188c00cd38da217aa85a81b Mon Sep 17 00:00:00 2001 From: Abel Vesa Date: Wed, 24 Dec 2025 12:53:27 +0200 Subject: [PATCH 3877/3902] dt-bindings: phy: qcom-edp: Add missing clock for X Elite commit 6b99eeacf6abb1ff2d6463c84e490343f39cf11a upstream. On X Elite platform, the eDP PHY uses one more clock called ref. The current X Elite devices supported upstream work fine without this clock, because the boot firmware leaves this clock enabled. But we should not rely on that. Also, even though this change breaks the ABI, it is needed in order to make the driver disables this clock along with the other ones, for a proper bring-down of the entire PHY. So attach the this ref clock to the PHY. Cc: stable@vger.kernel.org # v6.10 Fixes: 5d5607861350 ("dt-bindings: phy: qcom-edp: Add X1E80100 PHY compatibles") Reviewed-by: Krzysztof Kozlowski Reviewed-by: Bjorn Andersson Signed-off-by: Abel Vesa Link: https://patch.msgid.link/20251224-phy-qcom-edp-add-missing-refclk-v5-1-3f45d349b5ac@oss.qualcomm.com Signed-off-by: Vinod Koul Signed-off-by: Greg Kroah-Hartman --- .../devicetree/bindings/phy/qcom,edp-phy.yaml | 28 ++++++++++++++++++- 1 file changed, 27 insertions(+), 1 deletion(-) diff --git a/Documentation/devicetree/bindings/phy/qcom,edp-phy.yaml b/Documentation/devicetree/bindings/phy/qcom,edp-phy.yaml index eb97181cbb9579..bfc4d75f50ff9e 100644 --- a/Documentation/devicetree/bindings/phy/qcom,edp-phy.yaml +++ b/Documentation/devicetree/bindings/phy/qcom,edp-phy.yaml @@ -37,12 +37,15 @@ properties: - description: PLL register block clocks: - maxItems: 2 + minItems: 2 + maxItems: 3 clock-names: + minItems: 2 items: - const: aux - const: cfg_ahb + - const: ref "#clock-cells": const: 1 @@ -64,6 +67,29 @@ required: - "#clock-cells" - "#phy-cells" +allOf: + - if: + properties: + compatible: + enum: + - qcom,x1e80100-dp-phy + then: + properties: + clocks: + minItems: 3 + maxItems: 3 + clock-names: + minItems: 3 + maxItems: 3 + else: + properties: + clocks: + minItems: 2 + maxItems: 2 + clock-names: + minItems: 2 + maxItems: 2 + additionalProperties: false examples: From a16695e3ac32df044cd1842f072425cac319fca0 Mon Sep 17 00:00:00 2001 From: Vikram Sharma Date: Fri, 7 Nov 2025 21:55:20 +0530 Subject: [PATCH 3878/3902] dt-bindings: media: qcom,qcs8300-camss: Add missing power supplies commit 555e882051a3a7ecc2bcee2b2047822249dcd074 upstream. Add missing vdda-phy-supply and vdda-pll-supply in the (monaco)qcs8300 camss binding. While enabling imx412 sensor for qcs8300 we see a need to add these supplies which were missing in initial submission. Fixes: 634a2958fae30 ("media: dt-bindings: Add qcom,qcs8300-camss compatible") Cc: stable@vger.kernel.org Co-developed-by: Nihal Kumar Gupta Signed-off-by: Nihal Kumar Gupta Signed-off-by: Vikram Sharma Reviewed-by: Krzysztof Kozlowski Signed-off-by: Bryan O'Donoghue Signed-off-by: Hans Verkuil Signed-off-by: Greg Kroah-Hartman --- .../bindings/media/qcom,qcs8300-camss.yaml | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/Documentation/devicetree/bindings/media/qcom,qcs8300-camss.yaml b/Documentation/devicetree/bindings/media/qcom,qcs8300-camss.yaml index 80a4540a22dc23..e5f170aa4d9ee7 100644 --- a/Documentation/devicetree/bindings/media/qcom,qcs8300-camss.yaml +++ b/Documentation/devicetree/bindings/media/qcom,qcs8300-camss.yaml @@ -120,6 +120,14 @@ properties: items: - const: top + vdda-phy-supply: + description: + Phandle to a 0.88V regulator supply to CSI PHYs. + + vdda-pll-supply: + description: + Phandle to 1.2V regulator supply to CSI PHYs pll block. + ports: $ref: /schemas/graph.yaml#/properties/ports @@ -160,6 +168,8 @@ required: - power-domains - power-domain-names - ports + - vdda-phy-supply + - vdda-pll-supply additionalProperties: false @@ -328,6 +338,9 @@ examples: power-domains = <&camcc CAM_CC_TITAN_TOP_GDSC>; power-domain-names = "top"; + vdda-phy-supply = <&vreg_l4a_0p88>; + vdda-pll-supply = <&vreg_l1c_1p2>; + ports { #address-cells = <1>; #size-cells = <0>; From 8562d921e4eeb8ee4f042533f5de880732b42b74 Mon Sep 17 00:00:00 2001 From: Shengjiu Wang Date: Thu, 12 Feb 2026 10:18:27 +0800 Subject: [PATCH 3879/3902] ASoC: dt-bindings: asahi-kasei,ak4458: set unevaluatedProperties:false commit 50a634f1d795721ce68583c78ba493f1d7aa8bc2 upstream. When including the dai-common.yaml, and allow '#sound-dai-cells' and "sound-name-prefix' to be used, should use unevaluatedProperties:false according to writing-bindings.rst. Fixes: 8d7de4a014f5 ("ASoC: dt-bindings: asahi-kasei,ak4458: Reference common DAI properties") Cc: stable@vger.kernel.org Signed-off-by: Shengjiu Wang Reviewed-by: Krzysztof Kozlowski Link: https://patch.msgid.link/20260212021829.3244736-2-shengjiu.wang@nxp.com Signed-off-by: Mark Brown Signed-off-by: Greg Kroah-Hartman --- Documentation/devicetree/bindings/sound/asahi-kasei,ak4458.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Documentation/devicetree/bindings/sound/asahi-kasei,ak4458.yaml b/Documentation/devicetree/bindings/sound/asahi-kasei,ak4458.yaml index 1fdbeecc5eff9d..259e97b7a3c0f1 100644 --- a/Documentation/devicetree/bindings/sound/asahi-kasei,ak4458.yaml +++ b/Documentation/devicetree/bindings/sound/asahi-kasei,ak4458.yaml @@ -60,7 +60,7 @@ allOf: properties: dsd-path: false -additionalProperties: false +unevaluatedProperties: false examples: - | From 1c1148cbebad2bef4609515e126dc1322d9d2168 Mon Sep 17 00:00:00 2001 From: Shengjiu Wang Date: Thu, 12 Feb 2026 10:18:28 +0800 Subject: [PATCH 3880/3902] ASoC: dt-bindings: asahi-kasei,ak4458: Fix the supply names commit e570a5ca307f6d7a6acd080fc219db2ce3c0737b upstream. In the original txt format binding document ak4458.txt, the supply names are 'AVDD-supply', 'DVDD-supply', and they are also used in driver. But in the commit converting to yaml format, they are changed to 'avdd-supply', 'dvdd-supply'. After search all the dts file, these names 'AVDD-supply', 'DVDD-supply', 'avdd-supply', 'dvdd-supply' are not used in any dts file. So it is safe to fix this yaml binding document. Fixes: 009e83b591dd ("ASoC: dt-bindings: ak4458: Convert to dtschema") Cc: stable@vger.kernel.org Signed-off-by: Shengjiu Wang Reviewed-by: Krzysztof Kozlowski Link: https://patch.msgid.link/20260212021829.3244736-3-shengjiu.wang@nxp.com Signed-off-by: Mark Brown Signed-off-by: Greg Kroah-Hartman --- .../devicetree/bindings/sound/asahi-kasei,ak4458.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Documentation/devicetree/bindings/sound/asahi-kasei,ak4458.yaml b/Documentation/devicetree/bindings/sound/asahi-kasei,ak4458.yaml index 259e97b7a3c0f1..3a3313ea0890a4 100644 --- a/Documentation/devicetree/bindings/sound/asahi-kasei,ak4458.yaml +++ b/Documentation/devicetree/bindings/sound/asahi-kasei,ak4458.yaml @@ -21,10 +21,10 @@ properties: reg: maxItems: 1 - avdd-supply: + AVDD-supply: description: Analog power supply - dvdd-supply: + DVDD-supply: description: Digital power supply reset-gpios: From 84dbf4feaa937a71d53f894e0a950c7af4176f44 Mon Sep 17 00:00:00 2001 From: Shengjiu Wang Date: Thu, 12 Feb 2026 10:18:29 +0800 Subject: [PATCH 3881/3902] ASoC: dt-bindings: asahi-kasei,ak5558: Fix the supply names commit 80ca113671a005430207d351cb403c1637106212 upstream. In the original txt format binding document ak4458.txt, the supply names are 'AVDD-supply', 'DVDD-supply', and they are also used in driver. But in the commit converting to yaml format, they are changed to 'avdd-supply', 'dvdd-supply'. After search all the dts file, these names 'AVDD-supply', 'DVDD-supply', 'avdd-supply', 'dvdd-supply' are not used in any dts file. So it is safe to fix the yaml binding document. Fixes: 829d78e3ea32 ("ASoC: dt-bindings: ak5558: Convert to dtschema") Cc: stable@vger.kernel.org Signed-off-by: Shengjiu Wang Reviewed-by: Krzysztof Kozlowski Link: https://patch.msgid.link/20260212021829.3244736-4-shengjiu.wang@nxp.com Signed-off-by: Mark Brown Signed-off-by: Greg Kroah-Hartman --- .../devicetree/bindings/sound/asahi-kasei,ak5558.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Documentation/devicetree/bindings/sound/asahi-kasei,ak5558.yaml b/Documentation/devicetree/bindings/sound/asahi-kasei,ak5558.yaml index d3d494ae8abfeb..dc8f85f266bf30 100644 --- a/Documentation/devicetree/bindings/sound/asahi-kasei,ak5558.yaml +++ b/Documentation/devicetree/bindings/sound/asahi-kasei,ak5558.yaml @@ -19,10 +19,10 @@ properties: reg: maxItems: 1 - avdd-supply: + AVDD-supply: description: A 1.8V supply that powers up the AVDD pin. - dvdd-supply: + DVDD-supply: description: A 1.2V supply that powers up the DVDD pin. reset-gpios: From 6cadda9d1921dc72a408080416cdbf582ca3ccef Mon Sep 17 00:00:00 2001 From: Eric Naim Date: Tue, 10 Feb 2026 17:34:02 +0800 Subject: [PATCH 3882/3902] ALSA: hda/realtek: Add quirk for Gigabyte G5 KF5 (2023) commit 405d59fdd2038a65790eaad8c1013d37a2af6561 upstream. Fixes microphone detection when a headset is connected to the audio jack using the ALC256. Cc: stable@vger.kernel.org Signed-off-by: Eric Naim Link: https://patch.msgid.link/20260210093403.21514-1-dnaim@cachyos.org Signed-off-by: Takashi Iwai Signed-off-by: Greg Kroah-Hartman --- sound/hda/codecs/realtek/alc269.c | 1 + 1 file changed, 1 insertion(+) diff --git a/sound/hda/codecs/realtek/alc269.c b/sound/hda/codecs/realtek/alc269.c index a16cb45ac59e34..773cbb3a637fec 100644 --- a/sound/hda/codecs/realtek/alc269.c +++ b/sound/hda/codecs/realtek/alc269.c @@ -6952,6 +6952,7 @@ static const struct hda_quirk alc269_fixup_tbl[] = { SND_PCI_QUIRK(0x144d, 0xc886, "Samsung Galaxy Book3 Pro (NP964XFG)", ALC298_FIXUP_SAMSUNG_AMP_V2_4_AMPS), SND_PCI_QUIRK(0x144d, 0xc1ca, "Samsung Galaxy Book3 Pro 360 (NP960QFG)", ALC298_FIXUP_SAMSUNG_AMP_V2_4_AMPS), SND_PCI_QUIRK(0x144d, 0xc1cc, "Samsung Galaxy Book3 Ultra (NT960XFH)", ALC298_FIXUP_SAMSUNG_AMP_V2_4_AMPS), + SND_PCI_QUIRK(0x1458, 0x900e, "Gigabyte G5 KF5 (2023)", ALC2XX_FIXUP_HEADSET_MIC), SND_PCI_QUIRK(0x1458, 0xfa53, "Gigabyte BXBT-2807", ALC283_FIXUP_HEADSET_MIC), SND_PCI_QUIRK(0x1462, 0xb120, "MSI Cubi MS-B120", ALC283_FIXUP_HEADSET_MIC), SND_PCI_QUIRK(0x1462, 0xb171, "Cubi N 8GL (MS-B171)", ALC283_FIXUP_HEADSET_MIC), From 356a6afadb44a490de26758e303925dd8a388fb9 Mon Sep 17 00:00:00 2001 From: Takashi Iwai Date: Tue, 17 Feb 2026 11:44:11 +0100 Subject: [PATCH 3883/3902] ALSA: hda/conexant: Fix headphone jack handling on Acer Swift SF314 commit 7bc0df86c2384bc1e2012a2c946f82305054da64 upstream. Acer Swift SF314 (SSID 1025:136d) needs a bit of tweaks of the pin configurations for NID 0x16 and 0x19 to make the headphone / headset jack working. NID 0x17 can remain as is for the working speaker, and the built-in mic is supported via SOF. Cc: Link: https://bugzilla.kernel.org/show_bug.cgi?id=221086 Link: https://patch.msgid.link/20260217104414.62911-1-tiwai@suse.de Signed-off-by: Takashi Iwai Signed-off-by: Greg Kroah-Hartman --- sound/hda/codecs/conexant.c | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/sound/hda/codecs/conexant.c b/sound/hda/codecs/conexant.c index d6fba746030195..0c517378a6d28f 100644 --- a/sound/hda/codecs/conexant.c +++ b/sound/hda/codecs/conexant.c @@ -299,6 +299,7 @@ enum { CXT_PINCFG_SWS_JS201D, CXT_PINCFG_TOP_SPEAKER, CXT_FIXUP_HP_A_U, + CXT_FIXUP_ACER_SWIFT_HP, }; /* for hda_fixup_thinkpad_acpi() */ @@ -1024,6 +1025,14 @@ static const struct hda_fixup cxt_fixups[] = { .type = HDA_FIXUP_FUNC, .v.func = cxt_fixup_hp_a_u, }, + [CXT_FIXUP_ACER_SWIFT_HP] = { + .type = HDA_FIXUP_PINS, + .v.pins = (const struct hda_pintbl[]) { + { 0x16, 0x0321403f }, /* Headphone */ + { 0x19, 0x40f001f0 }, /* Mic */ + { } + }, + }, }; static const struct hda_quirk cxt5045_fixups[] = { @@ -1073,6 +1082,7 @@ static const struct hda_quirk cxt5066_fixups[] = { SND_PCI_QUIRK(0x1025, 0x0543, "Acer Aspire One 522", CXT_FIXUP_STEREO_DMIC), SND_PCI_QUIRK(0x1025, 0x054c, "Acer Aspire 3830TG", CXT_FIXUP_ASPIRE_DMIC), SND_PCI_QUIRK(0x1025, 0x054f, "Acer Aspire 4830T", CXT_FIXUP_ASPIRE_DMIC), + SND_PCI_QUIRK(0x1025, 0x136d, "Acer Swift SF314", CXT_FIXUP_ACER_SWIFT_HP), SND_PCI_QUIRK(0x103c, 0x8079, "HP EliteBook 840 G3", CXT_FIXUP_HP_DOCK), SND_PCI_QUIRK(0x103c, 0x807C, "HP EliteBook 820 G3", CXT_FIXUP_HP_DOCK), SND_PCI_QUIRK(0x103c, 0x80FD, "HP ProBook 640 G2", CXT_FIXUP_HP_DOCK), From 5c8e29b5208b823c8d5730e7a7aff0218593666c Mon Sep 17 00:00:00 2001 From: Lewis Mason Date: Tue, 10 Feb 2026 23:13:37 +0000 Subject: [PATCH 3884/3902] ALSA: hda/realtek: Add quirk for Samsung Galaxy Book3 Pro 360 (NP965QFG) commit 3a6b7dc431aab90744e973254604855e654294ae upstream. The Samsung Galaxy Book3 Pro 360 NP965QFG (subsystem ID 0x144d:0xc1cb) uses the same Realtek ALC298 codec and amplifier configuration as the NP960QFG (0x144d:0xc1ca). Apply the same ALC298_FIXUP_SAMSUNG_AMP_V2_4_AMPS fixup to enable the internal speakers. Cc: stable@vger.kernel.org Signed-off-by: Lewis Mason Link: https://patch.msgid.link/20260210231337.7265-1-lewis@ocuru.co.uk Signed-off-by: Takashi Iwai Signed-off-by: Greg Kroah-Hartman --- sound/hda/codecs/realtek/alc269.c | 1 + 1 file changed, 1 insertion(+) diff --git a/sound/hda/codecs/realtek/alc269.c b/sound/hda/codecs/realtek/alc269.c index 773cbb3a637fec..66da4584aa7a5f 100644 --- a/sound/hda/codecs/realtek/alc269.c +++ b/sound/hda/codecs/realtek/alc269.c @@ -6951,6 +6951,7 @@ static const struct hda_quirk alc269_fixup_tbl[] = { SND_PCI_QUIRK(0x144d, 0xc872, "Samsung Galaxy Book2 Pro (NP950XEE)", ALC298_FIXUP_SAMSUNG_AMP_V2_2_AMPS), SND_PCI_QUIRK(0x144d, 0xc886, "Samsung Galaxy Book3 Pro (NP964XFG)", ALC298_FIXUP_SAMSUNG_AMP_V2_4_AMPS), SND_PCI_QUIRK(0x144d, 0xc1ca, "Samsung Galaxy Book3 Pro 360 (NP960QFG)", ALC298_FIXUP_SAMSUNG_AMP_V2_4_AMPS), + SND_PCI_QUIRK(0x144d, 0xc1cb, "Samsung Galaxy Book3 Pro 360 (NP965QFG)", ALC298_FIXUP_SAMSUNG_AMP_V2_4_AMPS), SND_PCI_QUIRK(0x144d, 0xc1cc, "Samsung Galaxy Book3 Ultra (NT960XFH)", ALC298_FIXUP_SAMSUNG_AMP_V2_4_AMPS), SND_PCI_QUIRK(0x1458, 0x900e, "Gigabyte G5 KF5 (2023)", ALC2XX_FIXUP_HEADSET_MIC), SND_PCI_QUIRK(0x1458, 0xfa53, "Gigabyte BXBT-2807", ALC283_FIXUP_HEADSET_MIC), From b5fc86d753dd4c281a943b92f0eef02d31af03d7 Mon Sep 17 00:00:00 2001 From: Jeongjun Park Date: Mon, 19 Jan 2026 17:25:51 +0900 Subject: [PATCH 3885/3902] drm/exynos: vidi: use priv->vidi_dev for ctx lookup in vidi_connection_ioctl() commit d3968a0d85b211e197f2f4f06268a7031079e0d0 upstream. vidi_connection_ioctl() retrieves the driver_data from drm_dev->dev to obtain a struct vidi_context pointer. However, drm_dev->dev is the exynos-drm master device, and the driver_data contained therein is not the vidi component device, but a completely different device. This can lead to various bugs, ranging from null pointer dereferences and garbage value accesses to, in unlucky cases, out-of-bounds errors, use-after-free errors, and more. To resolve this issue, we need to store/delete the vidi device pointer in exynos_drm_private->vidi_dev during bind/unbind, and then read this exynos_drm_private->vidi_dev within ioctl() to obtain the correct struct vidi_context pointer. Cc: Signed-off-by: Jeongjun Park Signed-off-by: Inki Dae Signed-off-by: Greg Kroah-Hartman --- drivers/gpu/drm/exynos/exynos_drm_drv.h | 1 + drivers/gpu/drm/exynos/exynos_drm_vidi.c | 14 +++++++++++++- 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/drivers/gpu/drm/exynos/exynos_drm_drv.h b/drivers/gpu/drm/exynos/exynos_drm_drv.h index 23646e55f142c3..06c29ff2aac0e1 100644 --- a/drivers/gpu/drm/exynos/exynos_drm_drv.h +++ b/drivers/gpu/drm/exynos/exynos_drm_drv.h @@ -199,6 +199,7 @@ struct drm_exynos_file_private { struct exynos_drm_private { struct device *g2d_dev; struct device *dma_dev; + struct device *vidi_dev; void *mapping; /* for atomic commit */ diff --git a/drivers/gpu/drm/exynos/exynos_drm_vidi.c b/drivers/gpu/drm/exynos/exynos_drm_vidi.c index b80410a3e4aadb..bf5ba545891746 100644 --- a/drivers/gpu/drm/exynos/exynos_drm_vidi.c +++ b/drivers/gpu/drm/exynos/exynos_drm_vidi.c @@ -231,9 +231,14 @@ ATTRIBUTE_GROUPS(vidi); int vidi_connection_ioctl(struct drm_device *drm_dev, void *data, struct drm_file *file_priv) { - struct vidi_context *ctx = dev_get_drvdata(drm_dev->dev); + struct exynos_drm_private *priv = drm_dev->dev_private; + struct device *dev = priv ? priv->vidi_dev : NULL; + struct vidi_context *ctx = dev ? dev_get_drvdata(dev) : NULL; struct drm_exynos_vidi_connection *vidi = data; + if (!ctx) + return -ENODEV; + if (!vidi) { DRM_DEV_DEBUG_KMS(ctx->dev, "user data for vidi is null.\n"); @@ -393,6 +398,7 @@ static int vidi_bind(struct device *dev, struct device *master, void *data) { struct vidi_context *ctx = dev_get_drvdata(dev); struct drm_device *drm_dev = data; + struct exynos_drm_private *priv = drm_dev->dev_private; struct drm_encoder *encoder = &ctx->encoder; struct exynos_drm_plane *exynos_plane; struct exynos_drm_plane_config plane_config = { 0 }; @@ -400,6 +406,8 @@ static int vidi_bind(struct device *dev, struct device *master, void *data) int ret; ctx->drm_dev = drm_dev; + if (priv) + priv->vidi_dev = dev; plane_config.pixel_formats = formats; plane_config.num_pixel_formats = ARRAY_SIZE(formats); @@ -445,8 +453,12 @@ static int vidi_bind(struct device *dev, struct device *master, void *data) static void vidi_unbind(struct device *dev, struct device *master, void *data) { struct vidi_context *ctx = dev_get_drvdata(dev); + struct drm_device *drm_dev = data; + struct exynos_drm_private *priv = drm_dev->dev_private; timer_delete_sync(&ctx->timer); + if (priv) + priv->vidi_dev = NULL; } static const struct component_ops vidi_component_ops = { From 4949e32387fe315b59ad5f422c9fc52836fbdd1e Mon Sep 17 00:00:00 2001 From: Jeongjun Park Date: Mon, 19 Jan 2026 17:25:52 +0900 Subject: [PATCH 3886/3902] drm/exynos: vidi: fix to avoid directly dereferencing user pointer commit d4c98c077c7fb2dfdece7d605e694b5ea2665085 upstream. In vidi_connection_ioctl(), vidi->edid(user pointer) is directly dereferenced in the kernel. This allows arbitrary kernel memory access from the user space, so instead of directly accessing the user pointer in the kernel, we should modify it to copy edid to kernel memory using copy_from_user() and use it. Cc: Signed-off-by: Jeongjun Park Signed-off-by: Inki Dae Signed-off-by: Greg Kroah-Hartman --- drivers/gpu/drm/exynos/exynos_drm_vidi.c | 22 ++++++++++++++++++---- 1 file changed, 18 insertions(+), 4 deletions(-) diff --git a/drivers/gpu/drm/exynos/exynos_drm_vidi.c b/drivers/gpu/drm/exynos/exynos_drm_vidi.c index bf5ba545891746..37733f2ac0e7a6 100644 --- a/drivers/gpu/drm/exynos/exynos_drm_vidi.c +++ b/drivers/gpu/drm/exynos/exynos_drm_vidi.c @@ -262,13 +262,27 @@ int vidi_connection_ioctl(struct drm_device *drm_dev, void *data, if (vidi->connection) { const struct drm_edid *drm_edid; - const struct edid *raw_edid; + const void __user *edid_userptr = u64_to_user_ptr(vidi->edid); + void *edid_buf; + struct edid hdr; size_t size; - raw_edid = (const struct edid *)(unsigned long)vidi->edid; - size = (raw_edid->extensions + 1) * EDID_LENGTH; + if (copy_from_user(&hdr, edid_userptr, sizeof(hdr))) + return -EFAULT; - drm_edid = drm_edid_alloc(raw_edid, size); + size = (hdr.extensions + 1) * EDID_LENGTH; + + edid_buf = kmalloc(size, GFP_KERNEL); + if (!edid_buf) + return -ENOMEM; + + if (copy_from_user(edid_buf, edid_userptr, size)) { + kfree(edid_buf); + return -EFAULT; + } + + drm_edid = drm_edid_alloc(edid_buf, size); + kfree(edid_buf); if (!drm_edid) return -ENOMEM; From b20266f1880b100a0479a0e4b4f987542b8175f8 Mon Sep 17 00:00:00 2001 From: Jan Kiszka Date: Mon, 16 Feb 2026 17:24:56 +0100 Subject: [PATCH 3887/3902] Drivers: hv: vmbus: Use kthread for vmbus interrupts on PREEMPT_RT commit f8e6343b7a89c7c649db5a9e309ba7aa20401813 upstream. Resolves the following lockdep report when booting PREEMPT_RT on Hyper-V with related guest support enabled: [ 1.127941] hv_vmbus: registering driver hyperv_drm [ 1.132518] ============================= [ 1.132519] [ BUG: Invalid wait context ] [ 1.132521] 6.19.0-rc8+ #9 Not tainted [ 1.132524] ----------------------------- [ 1.132525] swapper/0/0 is trying to lock: [ 1.132526] ffff8b9381bb3c90 (&channel->sched_lock){....}-{3:3}, at: vmbus_chan_sched+0xc4/0x2b0 [ 1.132543] other info that might help us debug this: [ 1.132544] context-{2:2} [ 1.132545] 1 lock held by swapper/0/0: [ 1.132547] #0: ffffffffa010c4c0 (rcu_read_lock){....}-{1:3}, at: vmbus_chan_sched+0x31/0x2b0 [ 1.132557] stack backtrace: [ 1.132560] CPU: 0 UID: 0 PID: 0 Comm: swapper/0 Not tainted 6.19.0-rc8+ #9 PREEMPT_{RT,(lazy)} [ 1.132565] Hardware name: Microsoft Corporation Virtual Machine/Virtual Machine, BIOS Hyper-V UEFI Release v4.1 09/25/2025 [ 1.132567] Call Trace: [ 1.132570] [ 1.132573] dump_stack_lvl+0x6e/0xa0 [ 1.132581] __lock_acquire+0xee0/0x21b0 [ 1.132592] lock_acquire+0xd5/0x2d0 [ 1.132598] ? vmbus_chan_sched+0xc4/0x2b0 [ 1.132606] ? lock_acquire+0xd5/0x2d0 [ 1.132613] ? vmbus_chan_sched+0x31/0x2b0 [ 1.132619] rt_spin_lock+0x3f/0x1f0 [ 1.132623] ? vmbus_chan_sched+0xc4/0x2b0 [ 1.132629] ? vmbus_chan_sched+0x31/0x2b0 [ 1.132634] vmbus_chan_sched+0xc4/0x2b0 [ 1.132641] vmbus_isr+0x2c/0x150 [ 1.132648] __sysvec_hyperv_callback+0x5f/0xa0 [ 1.132654] sysvec_hyperv_callback+0x88/0xb0 [ 1.132658] [ 1.132659] [ 1.132660] asm_sysvec_hyperv_callback+0x1a/0x20 As code paths that handle vmbus IRQs use sleepy locks under PREEMPT_RT, the vmbus_isr execution needs to be moved into thread context. Open- coding this allows to skip the IPI that irq_work would additionally bring and which we do not need, being an IRQ, never an NMI. This affects both x86 and arm64, therefore hook into the common driver logic. Signed-off-by: Jan Kiszka Reviewed-by: Florian Bezdeka Tested-by: Florian Bezdeka Reviewed-by: Michael Kelley Tested-by: Michael Kelley Signed-off-by: Wei Liu Signed-off-by: Greg Kroah-Hartman --- drivers/hv/vmbus_drv.c | 66 +++++++++++++++++++++++++++++++++++++++++- 1 file changed, 65 insertions(+), 1 deletion(-) diff --git a/drivers/hv/vmbus_drv.c b/drivers/hv/vmbus_drv.c index 69591dc7bad269..3ab62277b6be6c 100644 --- a/drivers/hv/vmbus_drv.c +++ b/drivers/hv/vmbus_drv.c @@ -25,6 +25,7 @@ #include #include #include +#include #include #include @@ -1306,7 +1307,7 @@ static void vmbus_chan_sched(struct hv_per_cpu_context *hv_cpu) } } -static void vmbus_isr(void) +static void __vmbus_isr(void) { struct hv_per_cpu_context *hv_cpu = this_cpu_ptr(hv_context.cpu_context); @@ -1330,6 +1331,53 @@ static void vmbus_isr(void) add_interrupt_randomness(vmbus_interrupt); } +static DEFINE_PER_CPU(bool, vmbus_irq_pending); +static DEFINE_PER_CPU(struct task_struct *, vmbus_irqd); + +static void vmbus_irqd_wake(void) +{ + struct task_struct *tsk = __this_cpu_read(vmbus_irqd); + + __this_cpu_write(vmbus_irq_pending, true); + wake_up_process(tsk); +} + +static void vmbus_irqd_setup(unsigned int cpu) +{ + sched_set_fifo(current); +} + +static int vmbus_irqd_should_run(unsigned int cpu) +{ + return __this_cpu_read(vmbus_irq_pending); +} + +static void run_vmbus_irqd(unsigned int cpu) +{ + __this_cpu_write(vmbus_irq_pending, false); + __vmbus_isr(); +} + +static bool vmbus_irq_initialized; + +static struct smp_hotplug_thread vmbus_irq_threads = { + .store = &vmbus_irqd, + .setup = vmbus_irqd_setup, + .thread_should_run = vmbus_irqd_should_run, + .thread_fn = run_vmbus_irqd, + .thread_comm = "vmbus_irq/%u", +}; + +static void vmbus_isr(void) +{ + if (IS_ENABLED(CONFIG_PREEMPT_RT)) { + vmbus_irqd_wake(); + } else { + lockdep_hardirq_threaded(); + __vmbus_isr(); + } +} + static irqreturn_t vmbus_percpu_isr(int irq, void *dev_id) { vmbus_isr(); @@ -1375,6 +1423,13 @@ static int vmbus_bus_init(void) * the VMbus interrupt handler. */ + if (IS_ENABLED(CONFIG_PREEMPT_RT) && !vmbus_irq_initialized) { + ret = smpboot_register_percpu_thread(&vmbus_irq_threads); + if (ret) + goto err_kthread; + vmbus_irq_initialized = true; + } + if (vmbus_irq == -1) { hv_setup_vmbus_handler(vmbus_isr); } else { @@ -1449,6 +1504,11 @@ static int vmbus_bus_init(void) free_percpu(vmbus_evt); } err_setup: + if (IS_ENABLED(CONFIG_PREEMPT_RT) && vmbus_irq_initialized) { + smpboot_unregister_percpu_thread(&vmbus_irq_threads); + vmbus_irq_initialized = false; + } +err_kthread: bus_unregister(&hv_bus); return ret; } @@ -2914,6 +2974,10 @@ static void __exit vmbus_exit(void) free_percpu_irq(vmbus_irq, vmbus_evt); free_percpu(vmbus_evt); } + if (IS_ENABLED(CONFIG_PREEMPT_RT) && vmbus_irq_initialized) { + smpboot_unregister_percpu_thread(&vmbus_irq_threads); + vmbus_irq_initialized = false; + } for_each_online_cpu(cpu) { struct hv_per_cpu_context *hv_cpu = per_cpu_ptr(hv_context.cpu_context, cpu); From 73c2a1255f0fd318644c9d8022c7c023a2de1a36 Mon Sep 17 00:00:00 2001 From: Joanne Koong Date: Thu, 4 Dec 2025 13:51:14 -0800 Subject: [PATCH 3888/3902] io_uring/rsrc: clean up buffer cloning arg validation commit b8201b50e403815f941d1c6581a27fdbfe7d0fd4 upstream. Get rid of some redundant checks and move the src arg validation to before the buffer table allocation, which simplifies error handling. Signed-off-by: Joanne Koong Signed-off-by: Jens Axboe Signed-off-by: Greg Kroah-Hartman --- io_uring/rsrc.c | 27 ++++++--------------------- 1 file changed, 6 insertions(+), 21 deletions(-) diff --git a/io_uring/rsrc.c b/io_uring/rsrc.c index 160b4de2d00d3c..44442bf4827e47 100644 --- a/io_uring/rsrc.c +++ b/io_uring/rsrc.c @@ -1185,12 +1185,16 @@ static int io_clone_buffers(struct io_ring_ctx *ctx, struct io_ring_ctx *src_ctx return -EBUSY; nbufs = src_ctx->buf_table.nr; + if (!nbufs) + return -ENXIO; if (!arg->nr) arg->nr = nbufs; else if (arg->nr > nbufs) return -EINVAL; else if (arg->nr > IORING_MAX_REG_BUFFERS) return -EINVAL; + if (check_add_overflow(arg->nr, arg->src_off, &off) || off > nbufs) + return -EOVERFLOW; if (check_add_overflow(arg->nr, arg->dst_off, &nbufs)) return -EOVERFLOW; if (nbufs > IORING_MAX_REG_BUFFERS) @@ -1210,21 +1214,6 @@ static int io_clone_buffers(struct io_ring_ctx *ctx, struct io_ring_ctx *src_ctx } } - ret = -ENXIO; - nbufs = src_ctx->buf_table.nr; - if (!nbufs) - goto out_free; - ret = -EINVAL; - if (!arg->nr) - arg->nr = nbufs; - else if (arg->nr > nbufs) - goto out_free; - ret = -EOVERFLOW; - if (check_add_overflow(arg->nr, arg->src_off, &off)) - goto out_free; - if (off > nbufs) - goto out_free; - off = arg->dst_off; i = arg->src_off; nr = arg->nr; @@ -1237,8 +1226,8 @@ static int io_clone_buffers(struct io_ring_ctx *ctx, struct io_ring_ctx *src_ctx } else { dst_node = io_rsrc_node_alloc(ctx, IORING_RSRC_BUFFER); if (!dst_node) { - ret = -ENOMEM; - goto out_free; + io_rsrc_data_free(ctx, &data); + return -ENOMEM; } refcount_inc(&src_node->buf->refs); @@ -1274,10 +1263,6 @@ static int io_clone_buffers(struct io_ring_ctx *ctx, struct io_ring_ctx *src_ctx WARN_ON_ONCE(ctx->buf_table.nr); ctx->buf_table = data; return 0; - -out_free: - io_rsrc_data_free(ctx, &data); - return ret; } /* From 5566ebcf41ae1b4bf67f857f8c46f4c413d97ad0 Mon Sep 17 00:00:00 2001 From: Martin KaFai Lau Date: Wed, 12 Nov 2025 15:23:31 -0800 Subject: [PATCH 3889/3902] selftests/bpf: Test bpf_skb_check_mtu(BPF_MTU_CHK_SEGS) when transport_header is not set commit 6cc73f35406cae1f053e984e8de40e6dc9681446 upstream. Add a test to check that bpf_skb_check_mtu(BPF_MTU_CHK_SEGS) is rejected (-EINVAL) if skb->transport_header is not set. The test needs to lower the MTU of the loopback device. Thus, take this opportunity to run the test in a netns by adding "ns_" to the test name. The "serial_" prefix can then be removed. Signed-off-by: Martin KaFai Lau Link: https://lore.kernel.org/r/20251112232331.1566074-2-martin.lau@linux.dev Signed-off-by: Alexei Starovoitov Signed-off-by: Greg Kroah-Hartman --- .../selftests/bpf/prog_tests/check_mtu.c | 23 ++++++++++++++++++- .../selftests/bpf/progs/test_check_mtu.c | 12 ++++++++++ 2 files changed, 34 insertions(+), 1 deletion(-) diff --git a/tools/testing/selftests/bpf/prog_tests/check_mtu.c b/tools/testing/selftests/bpf/prog_tests/check_mtu.c index 2a9a30650350ed..65b4512967e746 100644 --- a/tools/testing/selftests/bpf/prog_tests/check_mtu.c +++ b/tools/testing/selftests/bpf/prog_tests/check_mtu.c @@ -153,6 +153,26 @@ static void test_check_mtu_run_tc(struct test_check_mtu *skel, ASSERT_EQ(mtu_result, mtu_expect, "MTU-compare-user"); } +static void test_chk_segs_flag(struct test_check_mtu *skel, __u32 mtu) +{ + int err, prog_fd = bpf_program__fd(skel->progs.tc_chk_segs_flag); + struct __sk_buff skb = { + .gso_size = 10, + }; + LIBBPF_OPTS(bpf_test_run_opts, topts, + .data_in = &pkt_v4, + .data_size_in = sizeof(pkt_v4), + .ctx_in = &skb, + .ctx_size_in = sizeof(skb), + ); + + /* Lower the mtu to test the BPF_MTU_CHK_SEGS */ + SYS_NOFAIL("ip link set dev lo mtu 10"); + err = bpf_prog_test_run_opts(prog_fd, &topts); + SYS_NOFAIL("ip link set dev lo mtu %u", mtu); + ASSERT_OK(err, "test_run"); + ASSERT_EQ(topts.retval, BPF_OK, "retval"); +} static void test_check_mtu_tc(__u32 mtu, __u32 ifindex) { @@ -177,11 +197,12 @@ static void test_check_mtu_tc(__u32 mtu, __u32 ifindex) test_check_mtu_run_tc(skel, skel->progs.tc_minus_delta, mtu); test_check_mtu_run_tc(skel, skel->progs.tc_input_len, mtu); test_check_mtu_run_tc(skel, skel->progs.tc_input_len_exceed, mtu); + test_chk_segs_flag(skel, mtu); cleanup: test_check_mtu__destroy(skel); } -void serial_test_check_mtu(void) +void test_ns_check_mtu(void) { int mtu_lo; diff --git a/tools/testing/selftests/bpf/progs/test_check_mtu.c b/tools/testing/selftests/bpf/progs/test_check_mtu.c index 2ec1de11a3ae4a..7b6b2b342c1de1 100644 --- a/tools/testing/selftests/bpf/progs/test_check_mtu.c +++ b/tools/testing/selftests/bpf/progs/test_check_mtu.c @@ -7,6 +7,7 @@ #include #include +#include char _license[] SEC("license") = "GPL"; @@ -288,3 +289,14 @@ int tc_input_len_exceed(struct __sk_buff *ctx) global_bpf_mtu_xdp = mtu_len; return retval; } + +SEC("tc") +int tc_chk_segs_flag(struct __sk_buff *ctx) +{ + __u32 mtu_len = 0; + int err; + + err = bpf_check_mtu(ctx, GLOBAL_USER_IFINDEX, &mtu_len, 0, BPF_MTU_CHK_SEGS); + + return err == -EINVAL ? BPF_OK : BPF_DROP; +} From ff763ac9771303310484d7b6c31575610b24715d Mon Sep 17 00:00:00 2001 From: Ivan Lipski Date: Fri, 16 Jan 2026 10:03:54 -0500 Subject: [PATCH 3890/3902] drm/amd/display: Clear HDMI HPD pending work only if it is enabled commit 17b2c526fd8026d8e0f4c0e7f94fc517e3901589 upstream. [Why&How] On amdgpu_dm_connector_destroy(), the driver attempts to cancel pending HDMI HPD work without checking if the HDMI HPD is enabled. Added a check that it is enabled before clearing it. Fixes: 6a681cd90345 ("drm/amd/display: Add an hdmi_hpd_debounce_delay_ms module") Signed-off-by: Ivan Lipski Reviewed-by: Mario Limonciello (AMD) Signed-off-by: Alex Deucher Signed-off-by: Greg Kroah-Hartman --- drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c index ccf13bb5281bf9..eb41766449980e 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c @@ -7548,10 +7548,12 @@ static void amdgpu_dm_connector_destroy(struct drm_connector *connector) drm_dp_mst_topology_mgr_destroy(&aconnector->mst_mgr); /* Cancel and flush any pending HDMI HPD debounce work */ - cancel_delayed_work_sync(&aconnector->hdmi_hpd_debounce_work); - if (aconnector->hdmi_prev_sink) { - dc_sink_release(aconnector->hdmi_prev_sink); - aconnector->hdmi_prev_sink = NULL; + if (aconnector->hdmi_hpd_debounce_delay_ms) { + cancel_delayed_work_sync(&aconnector->hdmi_hpd_debounce_work); + if (aconnector->hdmi_prev_sink) { + dc_sink_release(aconnector->hdmi_prev_sink); + aconnector->hdmi_prev_sink = NULL; + } } if (aconnector->bl_idx != -1) { From a86c53332a81f37c6964f071be7b18e5bbff9a6b Mon Sep 17 00:00:00 2001 From: Huacai Chen Date: Tue, 3 Feb 2026 14:29:01 +0800 Subject: [PATCH 3891/3902] net: stmmac: dwmac-loongson: Set clk_csr_i to 100-150MHz commit e1aa5ef892fb4fa9014a25e87b64b97347919d37 upstream. Current clk_csr_i setting of Loongson STMMAC (including LS7A1000/2000 and LS2K1000/2000/3000) are copy & paste from other drivers. In fact, Loongson STMMAC use 125MHz clocks and need 62 freq division to within 2.5MHz, meeting most PHY MDC requirement. So fix by setting clk_csr_i to 100-150MHz, otherwise some PHYs may link fail. Cc: stable@vger.kernel.org Fixes: 30bba69d7db40e7 ("stmmac: pci: Add dwmac support for Loongson") Signed-off-by: Hongliang Wang Signed-off-by: Huacai Chen Link: https://patch.msgid.link/20260203062901.2158236-1-chenhuacai@loongson.cn Signed-off-by: Jakub Kicinski Signed-off-by: Greg Kroah-Hartman --- drivers/net/ethernet/stmicro/stmmac/dwmac-loongson.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/net/ethernet/stmicro/stmmac/dwmac-loongson.c b/drivers/net/ethernet/stmicro/stmmac/dwmac-loongson.c index 2a3ac0136cdbcf..47bc3aeee857d1 100644 --- a/drivers/net/ethernet/stmicro/stmmac/dwmac-loongson.c +++ b/drivers/net/ethernet/stmicro/stmmac/dwmac-loongson.c @@ -90,8 +90,8 @@ static void loongson_default_data(struct pci_dev *pdev, /* Get bus_id, this can be overwritten later */ plat->bus_id = pci_dev_id(pdev); - /* clk_csr_i = 20-35MHz & MDC = clk_csr_i/16 */ - plat->clk_csr = STMMAC_CSR_20_35M; + /* clk_csr_i = 100-150MHz & MDC = clk_csr_i/62 */ + plat->clk_csr = STMMAC_CSR_100_150M; plat->core_type = DWMAC_CORE_GMAC; plat->force_sf_dma_mode = 1; From a488528a7182b62d55b0c9a67358f04d089500e0 Mon Sep 17 00:00:00 2001 From: Ivan Lipski Date: Tue, 13 Jan 2026 17:29:59 -0500 Subject: [PATCH 3892/3902] drm/amd/display: Add an hdmi_hpd_debounce_delay_ms module commit 6a681cd9034587fe3550868bacfbd639d1c6891f upstream. [Why&How] Right now, the HDMI HPD filter is enabled by default at 1500ms. We want to disable it by default, as most modern displays with HDMI do not require it for DPMS mode. The HPD can instead be enabled as a driver parameter with a custom delay value in ms (up to 5000ms). Fixes: c918e75e1ed9 ("drm/amd/display: Add an HPD filter for HDMI") Closes: https://gitlab.freedesktop.org/drm/amd/-/issues/4859 Signed-off-by: Ivan Lipski Reviewed-by: Mario Limonciello (AMD) Signed-off-by: Alex Deucher Signed-off-by: Greg Kroah-Hartman --- drivers/gpu/drm/amd/amdgpu/amdgpu.h | 2 ++ drivers/gpu/drm/amd/amdgpu/amdgpu_drv.c | 11 +++++++++++ drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c | 15 ++++++++++++--- drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.h | 5 ++++- 4 files changed, 29 insertions(+), 4 deletions(-) diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu.h b/drivers/gpu/drm/amd/amdgpu/amdgpu.h index 6f5b4a0e0a343f..803b6fc360a07b 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu.h +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu.h @@ -274,6 +274,8 @@ extern int amdgpu_rebar; extern int amdgpu_wbrf; extern int amdgpu_user_queue; +extern uint amdgpu_hdmi_hpd_debounce_delay_ms; + #define AMDGPU_VM_MAX_NUM_CTX 4096 #define AMDGPU_SG_THRESHOLD (256*1024*1024) #define AMDGPU_WAIT_IDLE_TIMEOUT_IN_MS 3000 diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_drv.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_drv.c index 3aa33c1de29b55..335f7e2f4ce557 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_drv.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_drv.c @@ -246,6 +246,7 @@ int amdgpu_damage_clips = -1; /* auto */ int amdgpu_umsch_mm_fwlog; int amdgpu_rebar = -1; /* auto */ int amdgpu_user_queue = -1; +uint amdgpu_hdmi_hpd_debounce_delay_ms; DECLARE_DYNDBG_CLASSMAP(drm_debug_classes, DD_CLASS_TYPE_DISJOINT_BITS, 0, "DRM_UT_CORE", @@ -1128,6 +1129,16 @@ module_param_named(rebar, amdgpu_rebar, int, 0444); MODULE_PARM_DESC(user_queue, "Enable user queues (-1 = auto (default), 0 = disable, 1 = enable, 2 = enable UQs and disable KQs)"); module_param_named(user_queue, amdgpu_user_queue, int, 0444); +/* + * DOC: hdmi_hpd_debounce_delay_ms (uint) + * HDMI HPD disconnect debounce delay in milliseconds. + * + * Used to filter short disconnect->reconnect HPD toggles some HDMI sinks + * generate while entering/leaving power save. Set to 0 to disable by default. + */ +MODULE_PARM_DESC(hdmi_hpd_debounce_delay_ms, "HDMI HPD disconnect debounce delay in milliseconds (0 to disable (by default), 1500 is common)"); +module_param_named(hdmi_hpd_debounce_delay_ms, amdgpu_hdmi_hpd_debounce_delay_ms, uint, 0644); + /* These devices are not supported by amdgpu. * They are supported by the mach64, r128, radeon drivers */ diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c index eb41766449980e..a0077fe79ed269 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c @@ -8717,9 +8717,18 @@ void amdgpu_dm_connector_init_helper(struct amdgpu_display_manager *dm, mutex_init(&aconnector->hpd_lock); mutex_init(&aconnector->handle_mst_msg_ready); - aconnector->hdmi_hpd_debounce_delay_ms = AMDGPU_DM_HDMI_HPD_DEBOUNCE_MS; - INIT_DELAYED_WORK(&aconnector->hdmi_hpd_debounce_work, hdmi_hpd_debounce_work); - aconnector->hdmi_prev_sink = NULL; + /* + * If HDMI HPD debounce delay is set, use the minimum between selected + * value and AMDGPU_DM_MAX_HDMI_HPD_DEBOUNCE_MS + */ + if (amdgpu_hdmi_hpd_debounce_delay_ms) { + aconnector->hdmi_hpd_debounce_delay_ms = min(amdgpu_hdmi_hpd_debounce_delay_ms, + AMDGPU_DM_MAX_HDMI_HPD_DEBOUNCE_MS); + INIT_DELAYED_WORK(&aconnector->hdmi_hpd_debounce_work, hdmi_hpd_debounce_work); + aconnector->hdmi_prev_sink = NULL; + } else { + aconnector->hdmi_hpd_debounce_delay_ms = 0; + } /* * configure support HPD hot plug connector_>polled default value is 0 diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.h b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.h index 8ca73895759893..adcd7ea696719a 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.h +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.h @@ -59,7 +59,10 @@ #define AMDGPU_HDR_MULT_DEFAULT (0x100000000LL) -#define AMDGPU_DM_HDMI_HPD_DEBOUNCE_MS 1500 +/* + * Maximum HDMI HPD debounce delay in milliseconds + */ +#define AMDGPU_DM_MAX_HDMI_HPD_DEBOUNCE_MS 5000 /* #include "include/amdgpu_dal_power_if.h" #include "amdgpu_dm_irq.h" From 33abac5b5a5303ba2c66d89e063a806033be07fc Mon Sep 17 00:00:00 2001 From: Damien Le Moal Date: Fri, 20 Feb 2026 13:43:00 +0900 Subject: [PATCH 3893/3902] ata: libata-eh: correctly handle deferred qc timeouts commit eddb98ad9364b4e778768785d46cfab04ce52100 upstream. A deferred qc may timeout while waiting for the device queue to drain to be submitted. In such case, since the qc is not active, ata_scsi_cmd_error_handler() ends up calling scsi_eh_finish_cmd(), which frees the qc. But as the port deferred_qc field still references this finished/freed qc, the deferred qc work may eventually attempt to call ata_qc_issue() against this invalid qc, leading to errors such as reported by UBSAN (syzbot run): UBSAN: shift-out-of-bounds in drivers/ata/libata-core.c:5166:24 shift exponent 4210818301 is too large for 64-bit type 'long long unsigned int' ... Call Trace: __dump_stack lib/dump_stack.c:94 [inline] dump_stack_lvl+0x100/0x190 lib/dump_stack.c:120 ubsan_epilogue+0xa/0x30 lib/ubsan.c:233 __ubsan_handle_shift_out_of_bounds+0x279/0x2a0 lib/ubsan.c:494 ata_qc_issue.cold+0x38/0x9f drivers/ata/libata-core.c:5166 ata_scsi_deferred_qc_work+0x154/0x1f0 drivers/ata/libata-scsi.c:1679 process_one_work+0x9d7/0x1920 kernel/workqueue.c:3275 process_scheduled_works kernel/workqueue.c:3358 [inline] worker_thread+0x5da/0xe40 kernel/workqueue.c:3439 kthread+0x370/0x450 kernel/kthread.c:467 ret_from_fork+0x754/0xd80 arch/x86/kernel/process.c:158 ret_from_fork_asm+0x1a/0x30 arch/x86/entry/entry_64.S:245 Fix this by checking if the qc of a timed out SCSI command is a deferred one, and in such case, clear the port deferred_qc field and finish the SCSI command with DID_TIME_OUT. Reported-by: syzbot+1f77b8ca15336fff21ff@syzkaller.appspotmail.com Fixes: 0ea84089dbf6 ("ata: libata-scsi: avoid Non-NCQ command starvation") Signed-off-by: Damien Le Moal Reviewed-by: Hannes Reinecke Reviewed-by: Igor Pylypiv Signed-off-by: Greg Kroah-Hartman --- drivers/ata/libata-eh.c | 22 +++++++++++++++++++--- 1 file changed, 19 insertions(+), 3 deletions(-) diff --git a/drivers/ata/libata-eh.c b/drivers/ata/libata-eh.c index b90b17f680f822..258e657f3527c2 100644 --- a/drivers/ata/libata-eh.c +++ b/drivers/ata/libata-eh.c @@ -640,12 +640,28 @@ void ata_scsi_cmd_error_handler(struct Scsi_Host *host, struct ata_port *ap, set_host_byte(scmd, DID_OK); ata_qc_for_each_raw(ap, qc, i) { - if (qc->flags & ATA_QCFLAG_ACTIVE && - qc->scsicmd == scmd) + if (qc->scsicmd != scmd) + continue; + if ((qc->flags & ATA_QCFLAG_ACTIVE) || + qc == ap->deferred_qc) break; } - if (i < ATA_MAX_QUEUE) { + if (qc == ap->deferred_qc) { + /* + * This is a deferred command that timed out while + * waiting for the command queue to drain. Since the qc + * is not active yet (deferred_qc is still set, so the + * deferred qc work has not issued the command yet), + * simply signal the timeout by finishing the SCSI + * command and clear the deferred qc to prevent the + * deferred qc work from issuing this qc. + */ + WARN_ON_ONCE(qc->flags & ATA_QCFLAG_ACTIVE); + ap->deferred_qc = NULL; + set_host_byte(scmd, DID_TIME_OUT); + scsi_eh_finish_cmd(scmd, &ap->eh_done_q); + } else if (i < ATA_MAX_QUEUE) { /* the scmd has an associated qc */ if (!(qc->flags & ATA_QCFLAG_EH)) { /* which hasn't failed yet, timeout */ From bcf08144a20af5c52b52ed149cfcb02d1a2b88e8 Mon Sep 17 00:00:00 2001 From: Damien Le Moal Date: Fri, 20 Feb 2026 12:09:12 +0900 Subject: [PATCH 3894/3902] ata: libata-core: fix cancellation of a port deferred qc work commit 55db009926634b20955bd8abbee921adbc8d2cb4 upstream. cancel_work_sync() is a sleeping function so it cannot be called with the spin lock of a port being held. Move the call to this function in ata_port_detach() after EH completes, with the port lock released, together with other work cancellation calls. Fixes: 0ea84089dbf6 ("ata: libata-scsi: avoid Non-NCQ command starvation") Signed-off-by: Damien Le Moal Reviewed-by: Hannes Reinecke Reviewed-by: Igor Pylypiv Signed-off-by: Greg Kroah-Hartman --- drivers/ata/libata-core.c | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/drivers/ata/libata-core.c b/drivers/ata/libata-core.c index 947215834830ac..b29067759cc276 100644 --- a/drivers/ata/libata-core.c +++ b/drivers/ata/libata-core.c @@ -6163,10 +6163,6 @@ static void ata_port_detach(struct ata_port *ap) } } - /* Make sure the deferred qc work finished. */ - cancel_work_sync(&ap->deferred_qc_work); - WARN_ON(ap->deferred_qc); - /* Tell EH to disable all devices */ ap->pflags |= ATA_PFLAG_UNLOADING; ata_port_schedule_eh(ap); @@ -6177,9 +6173,11 @@ static void ata_port_detach(struct ata_port *ap) /* wait till EH commits suicide */ ata_port_wait_eh(ap); - /* it better be dead now */ + /* It better be dead now and not have any remaining deferred qc. */ WARN_ON(!(ap->pflags & ATA_PFLAG_UNLOADED)); + WARN_ON(ap->deferred_qc); + cancel_work_sync(&ap->deferred_qc_work); cancel_delayed_work_sync(&ap->hotplug_task); cancel_delayed_work_sync(&ap->scsi_rescan_task); From 2ad6657a787fe8caf39b6d050e0238650e2d81fe Mon Sep 17 00:00:00 2001 From: Greg Kroah-Hartman Date: Thu, 26 Feb 2026 14:59:47 -0800 Subject: [PATCH 3895/3902] Linux 6.18.14 Link: https://lore.kernel.org/r/20260225012348.915798704@linuxfoundation.org Tested-by: Shung-Hsi Yu Tested-by: Salvatore Bonaccorso Tested-by: Ron Economos Tested-by: Jon Hunter Tested-by: Jeffrin Jose T Link: https://lore.kernel.org/r/20260225151847.709818960@linuxfoundation.org Tested-by: Peter Schneider Tested-by: Jeffrin Jose T Tested-by: Brett A C Sheffield Tested-by: Florian Fainelli Tested-by: Jon Hunter Tested-by: Luna Jernberg Tested-by: Justin M. Forbes Tested-by: Miguel Ojeda Signed-off-by: Greg Kroah-Hartman --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index bc34a825aba80d..d166d069509991 100644 --- a/Makefile +++ b/Makefile @@ -1,7 +1,7 @@ # SPDX-License-Identifier: GPL-2.0 VERSION = 6 PATCHLEVEL = 18 -SUBLEVEL = 13 +SUBLEVEL = 14 EXTRAVERSION = NAME = Baby Opossum Posse From c1f305ac054f2358cf8fa086d15293a680900b7f Mon Sep 17 00:00:00 2001 From: Pablo Neira Ayuso Date: Wed, 21 Jan 2026 01:08:44 +0100 Subject: [PATCH 3896/3902] netfilter: nf_tables: add .abort_skip_removal flag for set types MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit commit f175b46d9134f708358b5404730c6dfa200fbf3c upstream. The pipapo set backend is the only user of the .abort interface so far. To speed up pipapo abort path, removals are skipped. The follow up patch updates the rbtree to use to build an array of ordered elements, then use binary search. This needs a new .abort interface but, unlike pipapo, it also need to undo/remove elements. Add a flag and use it from the pipapo set backend. Signed-off-by: Pablo Neira Ayuso Signed-off-by: Florian Westphal Cc: "Kris Karas (Bug Reporting)" Cc: Genes Lists Cc: Philip Müller Signed-off-by: Greg Kroah-Hartman --- include/net/netfilter/nf_tables.h | 2 ++ net/netfilter/nf_tables_api.c | 3 ++- net/netfilter/nft_set_pipapo.c | 2 ++ 3 files changed, 6 insertions(+), 1 deletion(-) diff --git a/include/net/netfilter/nf_tables.h b/include/net/netfilter/nf_tables.h index 7eac73f9b4ce34..05f57ba6224476 100644 --- a/include/net/netfilter/nf_tables.h +++ b/include/net/netfilter/nf_tables.h @@ -456,6 +456,7 @@ struct nft_set_ext; * @init: initialize private data of new set instance * @destroy: destroy private data of set instance * @gc_init: initialize garbage collection + * @abort_skip_removal: skip removal of elements from abort path * @elemsize: element private size * * Operations lookup, update and delete have simpler interfaces, are faster @@ -513,6 +514,7 @@ struct nft_set_ops { const struct nft_set *set); void (*gc_init)(const struct nft_set *set); + bool abort_skip_removal; unsigned int elemsize; }; diff --git a/net/netfilter/nf_tables_api.c b/net/netfilter/nf_tables_api.c index df367638cdef0f..d4babc4d3bff38 100644 --- a/net/netfilter/nf_tables_api.c +++ b/net/netfilter/nf_tables_api.c @@ -7821,7 +7821,8 @@ static bool nft_trans_elems_new_abort(const struct nft_ctx *ctx, continue; } - if (!te->set->ops->abort || nft_setelem_is_catchall(te->set, te->elems[i].priv)) + if (!te->set->ops->abort_skip_removal || + nft_setelem_is_catchall(te->set, te->elems[i].priv)) nft_setelem_remove(ctx->net, te->set, te->elems[i].priv); if (!nft_setelem_is_catchall(te->set, te->elems[i].priv)) diff --git a/net/netfilter/nft_set_pipapo.c b/net/netfilter/nft_set_pipapo.c index 6d77a5f0088ad0..18e1903b1d3d0b 100644 --- a/net/netfilter/nft_set_pipapo.c +++ b/net/netfilter/nft_set_pipapo.c @@ -2370,6 +2370,7 @@ const struct nft_set_type nft_set_pipapo_type = { .gc_init = nft_pipapo_gc_init, .commit = nft_pipapo_commit, .abort = nft_pipapo_abort, + .abort_skip_removal = true, .elemsize = offsetof(struct nft_pipapo_elem, ext), }, }; @@ -2394,6 +2395,7 @@ const struct nft_set_type nft_set_pipapo_avx2_type = { .gc_init = nft_pipapo_gc_init, .commit = nft_pipapo_commit, .abort = nft_pipapo_abort, + .abort_skip_removal = true, .elemsize = offsetof(struct nft_pipapo_elem, ext), }, }; From df0dc1b06fb6b6461f9838694bf84079eca7562a Mon Sep 17 00:00:00 2001 From: Greg Kroah-Hartman Date: Fri, 27 Feb 2026 16:05:10 -0500 Subject: [PATCH 3897/3902] Linux 6.18.15 Signed-off-by: Greg Kroah-Hartman --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index d166d069509991..e4aa2e76ea563a 100644 --- a/Makefile +++ b/Makefile @@ -1,7 +1,7 @@ # SPDX-License-Identifier: GPL-2.0 VERSION = 6 PATCHLEVEL = 18 -SUBLEVEL = 14 +SUBLEVEL = 15 EXTRAVERSION = NAME = Baby Opossum Posse From 31a3db484370d5b5d25c93b8bd0ce1fd0c833fa4 Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Thu, 28 Aug 2025 11:56:06 +0200 Subject: [PATCH 3898/3902] spmi: apple: Add "apple,t8103-spmi" compatible After discussion with the devicetree maintainers we agreed to not extend lists with the generic compatible "apple,spmi" anymore [1]. Use "apple,t8103-spmi" as base compatible as it is the SoC the driver and bindings were written for. [1]: https://lore.kernel.org/asahi/12ab93b7-1fc2-4ce0-926e-c8141cfe81bf@kernel.org/ Reviewed-by: Neal Gompa Signed-off-by: Janne Grunau --- drivers/spmi/spmi-apple-controller.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/spmi/spmi-apple-controller.c b/drivers/spmi/spmi-apple-controller.c index 697b3e8bb02356..87e3ee9d4f2aa5 100644 --- a/drivers/spmi/spmi-apple-controller.c +++ b/drivers/spmi/spmi-apple-controller.c @@ -149,6 +149,7 @@ static int apple_spmi_probe(struct platform_device *pdev) } static const struct of_device_id apple_spmi_match_table[] = { + { .compatible = "apple,t8103-spmi", }, { .compatible = "apple,spmi", }, {} }; From 6aabecf10a7a21e19ba54bec099583e68bc744e9 Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Thu, 28 Aug 2025 12:19:02 +0200 Subject: [PATCH 3899/3902] watchdog: apple: Add "apple,t8103-wdt" compatible After discussion with the devicetree maintainers we agreed to not extend lists with the generic compatible "apple,wdt" anymore [1]. Use "apple,t8103-wdt" as base compatible as it is the SoC the driver and bindings were written for. [1]: https://lore.kernel.org/asahi/12ab93b7-1fc2-4ce0-926e-c8141cfe81bf@kernel.org/ Reviewed-by: Neal Gompa Signed-off-by: Janne Grunau --- drivers/watchdog/apple_wdt.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/watchdog/apple_wdt.c b/drivers/watchdog/apple_wdt.c index 66a158f67a712b..6b9b0f9b05cedf 100644 --- a/drivers/watchdog/apple_wdt.c +++ b/drivers/watchdog/apple_wdt.c @@ -218,6 +218,7 @@ static int apple_wdt_suspend(struct device *dev) static DEFINE_SIMPLE_DEV_PM_OPS(apple_wdt_pm_ops, apple_wdt_suspend, apple_wdt_resume); static const struct of_device_id apple_wdt_of_match[] = { + { .compatible = "apple,t8103-wdt" }, { .compatible = "apple,wdt" }, {}, }; From 7457799f05a85196fe9419cbff56c43367bf7912 Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Thu, 28 Aug 2025 12:29:15 +0200 Subject: [PATCH 3900/3902] clk: clk-apple-nco: Add "apple,t8103-nco" compatible After discussion with the devicetree maintainers we agreed to not extend lists with the generic compatible "apple,nco" anymore [1]. Use "apple,t8103-nco" as base compatible as it is the SoC the driver and bindings were written for. [1]: https://lore.kernel.org/asahi/12ab93b7-1fc2-4ce0-926e-c8141cfe81bf@kernel.org/ Reviewed-by: Neal Gompa Signed-off-by: Janne Grunau --- drivers/clk/clk-apple-nco.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/clk/clk-apple-nco.c b/drivers/clk/clk-apple-nco.c index d3ced4a0f029ec..434c067968bbc1 100644 --- a/drivers/clk/clk-apple-nco.c +++ b/drivers/clk/clk-apple-nco.c @@ -320,6 +320,7 @@ static int applnco_probe(struct platform_device *pdev) } static const struct of_device_id applnco_ids[] = { + { .compatible = "apple,t8103-nco" }, { .compatible = "apple,nco" }, { } }; From 5011f80f6a07a11ea0f49fe89845ccaac98d4e5e Mon Sep 17 00:00:00 2001 From: Oliver Bestmann Date: Sun, 1 Feb 2026 16:26:35 +0100 Subject: [PATCH 3901/3902] drm/apple: support interchange compression Signed-off-by: Oliver Bestmann --- drivers/gpu/drm/apple/iomfb_plane.h | 31 ++++++- drivers/gpu/drm/apple/plane.c | 135 +++++++++++++++++++++++++++- 2 files changed, 160 insertions(+), 6 deletions(-) diff --git a/drivers/gpu/drm/apple/iomfb_plane.h b/drivers/gpu/drm/apple/iomfb_plane.h index 0701978200311a..46eb7ab3fed4c7 100644 --- a/drivers/gpu/drm/apple/iomfb_plane.h +++ b/drivers/gpu/drm/apple/iomfb_plane.h @@ -49,6 +49,14 @@ enum dcp_xfer_func { DCP_XFER_FUNC_HDR = 16, }; +enum dcp_address_format { + DCP_ADDRESS_FORMAT_INTERCHANGE_TILED = 5, +}; + +enum dcp_compression_type { + DCP_COMPRESSION_TYPE_INTERCHANGE_TILED = 3, +}; + struct dcp_rect { u32 x; u32 y; @@ -67,7 +75,26 @@ struct dcp_plane_info { u16 tile_size; u8 tile_w; u8 tile_h; - u32 unk[13]; + u8 unk1[0xd]; + u8 address_format; + u8 unk2[0x26]; +} __packed; + +struct dcp_compression_info { + u32 tile_w; + u32 tile_h; + u32 meta_offset; + u32 data_offset; + u32 tile_meta_bytes; + u32 tiles_w; + u32 tiles_h; + u32 unk1; + u32 compresson_type; + u32 unk3; + u8 _pad1[3]; + u32 tile_bytes; + u32 row_stride; + u8 pad2; } __packed; struct dcp_component_types { @@ -100,7 +127,7 @@ struct dcp_surface { u64 has_comp; struct dcp_plane_info planes[DCP_SURF_MAX_PLANES]; u64 has_planes; - u32 compression_info[DCP_SURF_MAX_PLANES][13]; + struct dcp_compression_info compression_info[DCP_SURF_MAX_PLANES]; u64 has_compr_info; u32 unk_num; u32 unk_denom; diff --git a/drivers/gpu/drm/apple/plane.c b/drivers/gpu/drm/apple/plane.c index 2f0b76ad84ad65..9df3ad833fd743 100644 --- a/drivers/gpu/drm/apple/plane.c +++ b/drivers/gpu/drm/apple/plane.c @@ -9,15 +9,61 @@ #include #include -#include #include +#include #include #include #include #include +#include +#include + +#ifndef DRM_FORMAT_MOD_APPLE_INTERCHANGE_COMPRESSED +#define DRM_FORMAT_MOD_APPLE_INTERCHANGE_COMPRESSED fourcc_mod_code(APPLE, 3) +#endif + #define FRAC_16_16(mult, div) (((mult) << 16) / (div)) +#define APPLE_GPU_CACHELINE 16 + +struct apple_interchange_layout { + u32 tiles_width; + u32 tiles_height; + u32 tile_size_B; + u32 meta_offset; + u32 meta_size; +}; + +static struct apple_interchange_layout +get_apple_interchange_layout(u32 width, u32 height, u32 bpp) +{ + u32 tw = DIV_ROUND_UP(width, 16); + u32 th = DIV_ROUND_UP(height, 16); + u32 tsize_B = 16 * 16 * (bpp / 8); + + u32 meta_offset = ALIGN(tw * th * tsize_B, APPLE_GPU_CACHELINE); + + /* + * The metadata buffer contains 8 bytes per 16x16 compression tile. + * Addressing is fully twiddled, so both width and height are padded to + * powers-of-two. + */ + u32 w_tl = roundup_pow_of_two(tw); + u32 h_tl = roundup_pow_of_two(th); + u32 B_per_tl = 8; + + u32 meta_size = ALIGN(w_tl * h_tl * B_per_tl, APPLE_GPU_CACHELINE); + + return (struct apple_interchange_layout){ + .tiles_width = tw, + .tiles_height = th, + .tile_size_B = tsize_B, + .meta_offset = meta_offset, + .meta_size = meta_size, + }; +} + static int apple_plane_atomic_check(struct drm_plane *plane, struct drm_atomic_state *state) { @@ -105,6 +151,38 @@ static int apple_plane_atomic_check(struct drm_plane *plane, return -EINVAL; } + /* + * The calculated size of the compression meta data must fit into the size + * of the gem object + */ + for (u32 i = 0; i < new_plane_state->fb->format->num_planes; i++) { + struct drm_framebuffer *fb = new_plane_state->fb; + + if (fb->modifier != DRM_FORMAT_MOD_APPLE_INTERCHANGE_COMPRESSED) + continue; + + u32 width = + drm_format_info_plane_width(fb->format, fb->width, i); + u32 height = + drm_format_info_plane_height(fb->format, fb->height, i); + u32 bpp = drm_format_info_bpp(fb->format, i); + struct apple_interchange_layout l = + get_apple_interchange_layout(width, height, bpp); + + u32 required_size = + fb->offsets[0] + l.meta_offset + l.meta_size; + + struct drm_gem_dma_object *obj = drm_fb_dma_get_gem_obj(fb, i); + if (obj && required_size > obj->base.size) { + dev_err_ratelimited( + state->dev->dev, + "dcp: atomic_check, required size of %u bytes is more than gem size of %zu\n", + required_size, obj->base.size); + + return -EINVAL; + } + } + return 0; } @@ -248,7 +326,6 @@ static void apple_plane_atomic_update(struct drm_plane *plane, .stride = fb->pitches[0], .width = fb->width, .height = fb->height, - .buf_size = fb->height * fb->pitches[0], // .surface_id = req->swap.surf_ids[l], /* Only used for compressed or multiplanar surfaces */ @@ -257,6 +334,7 @@ static void apple_plane_atomic_update(struct drm_plane *plane, .pel_h = 1, .has_comp = 1, .has_planes = 1, + .has_compr_info = 1, }; /* Populate plane information for planar formats */ @@ -279,8 +357,33 @@ static void apple_plane_atomic_update(struct drm_plane *plane, .tile_h = bh, }; - if (i > 0) - surf->buf_size += surf->planes[i].size; + if (fb->modifier == DRM_FORMAT_MOD_APPLE_INTERCHANGE_COMPRESSED) { + u32 bpp = drm_format_info_bpp(fmt, i); + struct apple_interchange_layout l = + get_apple_interchange_layout(width, height, bpp); + + surf->planes[i].tile_w = 16; + surf->planes[i].tile_h = 16; + surf->planes[i].stride = l.tiles_width * l.tile_size_B; + surf->planes[i].size = l.meta_offset + l.meta_size; + surf->planes[i].tile_size = l.tile_size_B; + surf->planes[i].address_format = DCP_ADDRESS_FORMAT_INTERCHANGE_TILED; + + surf->compression_info[i] = (struct dcp_compression_info){ + .tile_w = 16, + .tile_h = 16, + .data_offset = 0, + .meta_offset = l.meta_offset, + .tile_meta_bytes = 8, + .tiles_w = l.tiles_width, + .tiles_h = l.tiles_height, + .tile_bytes = l.tile_size_B, + .row_stride = l.tiles_width * l.tile_size_B, + .compresson_type = DCP_COMPRESSION_TYPE_INTERCHANGE_TILED, + }; + } + + surf->buf_size += surf->planes[i].size; } /* the obvious helper call drm_fb_dma_get_gem_addr() adjusts @@ -337,6 +440,28 @@ apple_plane_duplicate_state(struct drm_plane *plane) return &apple_plane_state->base; } +static bool apple_plane_format_mod_supported(struct drm_plane *plane, + uint32_t format, uint64_t modifier) +{ + if (modifier != DRM_FORMAT_MOD_APPLE_INTERCHANGE_COMPRESSED) { + return true; + } + + // interchange is only supported with rgba formats for now. + switch (format) { + case DRM_FORMAT_XRGB2101010: + case DRM_FORMAT_ARGB2101010: + case DRM_FORMAT_XRGB8888: + case DRM_FORMAT_ARGB8888: + case DRM_FORMAT_XBGR8888: + case DRM_FORMAT_ABGR8888: + return true; + + default: + return false; + } +} + // void apple_plane_destroy_state(struct drm_plane *plane, // struct drm_plane_state *state) // { @@ -350,6 +475,7 @@ static const struct drm_plane_funcs apple_plane_funcs = { .atomic_duplicate_state = apple_plane_duplicate_state, // .atomic_destroy_state = apple_plane_destroy_state, .atomic_destroy_state = drm_atomic_helper_plane_destroy_state, + .format_mod_supported = apple_plane_format_mod_supported, }; /* @@ -426,6 +552,7 @@ static const u32 dcp_overlay_formats_12_x[] = { }; u64 apple_format_modifiers[] = { + DRM_FORMAT_MOD_APPLE_INTERCHANGE_COMPRESSED, DRM_FORMAT_MOD_LINEAR, DRM_FORMAT_MOD_INVALID }; From 3ccde48f63cf85f632704002f23cf42d71c78f15 Mon Sep 17 00:00:00 2001 From: Oliver Bestmann Date: Tue, 10 Mar 2026 14:23:50 +0100 Subject: [PATCH 3902/3902] fixup!: do not check pitch with INTERCHANGE modifier Signed-off-by: Oliver Bestmann --- drivers/gpu/drm/apple/plane.c | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/drivers/gpu/drm/apple/plane.c b/drivers/gpu/drm/apple/plane.c index 9df3ad833fd743..a401bc92289166 100644 --- a/drivers/gpu/drm/apple/plane.c +++ b/drivers/gpu/drm/apple/plane.c @@ -128,12 +128,14 @@ static int apple_plane_atomic_check(struct drm_plane *plane, return -EINVAL; } - /* - * Pitches have to be 64-byte aligned. - */ - for (u32 i = 0; i < new_plane_state->fb->format->num_planes; i++) - if (new_plane_state->fb->pitches[i] & 63) - return -EINVAL; + if (new_plane_state->fb->modifier != DRM_FORMAT_MOD_APPLE_INTERCHANGE_COMPRESSED) { + /* + * Pitches have to be 64-byte aligned for non-tiled modifiers. + */ + for (u32 i = 0; i < new_plane_state->fb->format->num_planes; i++) + if (new_plane_state->fb->pitches[i] & 63) + return -EINVAL; + } /* * FIXME: dcp can currently only use multi-planar buffers using the same